id = $id;
$this->character_id = $character_id;
$this->code = $code;
$this->location_id = $location_id;
$this->stats = json_decode( $stats, true );
$this->spawn_id = $spawn_id;
}
public function get_item_definition(): ?Item_Definition {
return Item_Definition::get_item_definition_by_code( $this->code );
}
public function get_name(): string {
$item_definition = Item_Definition::get_item_definition_by_code( $this->code );
return $item_definition->name;
}
public function get_description(): string {
$item_definition = Item_Definition::get_item_definition_by_code( $this->code );
return $item_definition->get_description();
}
// get stats as associative array
public function get_stats(): array {
if(!is_array($this->stats)) {
$this->stats = array();
}
return $this->stats;
}
public function get_base_price(): int {
// the Value stat is multiplied by Charges or 1 if Charges is not set or it is a resource
$item_definition = Item_Definition::get_item_definition_by_code( $this->code );
$charges = $this->get_stat( 'Charges' ) ?? 1;
if ( ! is_numeric( $charges ) || $charges < 1 || $this->is_resource() ) {
$charges = 1;
}
$base_value = $item_definition->get_stat( 'Value' );
return intval( $base_value ) * intval( $charges );
}
/**
* Gets the complete stats of the item, including the stats from the item_definition
* and any additional stats that might be stored in the item instance itself.
* Stats with the same name are overwritten by the item instance stats.
*
* @return array
*/
public function get_complete_stats(): array {
$item_definition = Item_Definition::get_item_definition_by_code( $this->code );
$definition_stats = $item_definition->get_stats();
// merge the two arrays, instance stats overwrite definition stats
$complete_stats = array_merge( $definition_stats, $this->get_stats() );
ksort( $complete_stats );
return $complete_stats;
}
// get a single stats by it's name (key)
public function get_stat( string $stat_name ) {
$stats = $this->get_complete_stats();
return $stats[ $stat_name ] ?? null;
}
public function get_image_url(): string {
$item_definition = Item_Definition::get_item_definition_by_code( $this->code );
return $item_definition->image_url;
}
/** * Generates the HTML representation of the item, including its image, name, and description.
*
* @param mixed $adventurebook_combat_system The combat system to determine which stats to show.
*
* @return string The HTML content for the item.
*/
public function get_item_html( Combat_System $adventurebook_combat_system, $count = 0, $additional_html = '' , $slot = ''): string {
$item = $this;
$item_id = 'adventurebook_item_' . $item->id; // eindeutige ID für JS-Selektoren
$content = '
';
// determine Stats
if ( $adventurebook_combat_system ) {
/* @var Combat_System $adventurebook_combat_system */
$stats = $adventurebook_combat_system->get_item_stats_to_show( $item->get_complete_stats() );
} else {
$stats = $item->get_complete_stats();
}
// Name + Toggle
$content .= '
code === 'money' ) {
$wealth_unit = 'Gold';
if ($adventurebook_combat_system) {
$wealth_unit = $adventurebook_combat_system->get_wealth_unit();
}
$charges = $item->get_stat('Charges') ?? 0;
$content .= esc_html($charges . ' ' . $wealth_unit);
} else if ( $item->get_slot() === 'Resource' ) {
$charges = $item->get_stat('Charges') ?? 0;
$content .= esc_html($charges . 'x ' . $item->get_name());
} else {
$content .= esc_html( $item->get_name() );
}
if ( $count > 1 ) {
$content .= ' (' . $count . 'x)';
}
if ( ! empty( $stats ) ) {
$content .= ' ▸';
}
$content .= '
'; // adventurebook_item_name
// Stats- and Description-Container (hidden)
$content .= '
';
// Image
$content .= '
';
$content .= $item->get_image_url()
? '
 ) . ')
'
: '
';
$content .= '
';
// Description
$content .= '
' . $item->get_description() . "
";
foreach ( $stats as $stat_name => $stat_value ) {
// if the $stat_name is "Use" or "Value", don't show it here
// the value will be shown in a shop
// in addition, if the item is money, don't show the "Charges" stat because it is already shown as amount of money (e.g. 13 Gold)
if ( $stat_name === 'Use' ||
$stat_name === 'Value' ||
($stat_name === 'Slot' && ($stat_value == 'Recipe' ||
$stat_value == 'Collectible' ||
$stat_value == 'Resource')) ||
$stat_name === 'Charges' && $item->code == 'money' ||
$stat_name === 'Charges' && $item->is_resource()) {
continue;
}
// if $stat_name is 'Charges' and stat_value is -1, show "Unlimited Uses"
if ( $stat_name === 'Charges' && intval($stat_value) === -1 ) {
$content .= '
' . esc_html( __('Unlimited Uses', 'adventurebook') ) . '';
} else {
$content .= '
' . esc_html( $stat_name ) . ': ' . esc_html( $stat_value );
}
$content .= '
';
}
// insert the additional HTML content here (e.g. action buttons like "Use", "Equip", "Drop", etc.)
$content .= $additional_html;
$content .= '
'; // adventurebook_item_stats
$content .= '
'; // adventurebook_item
return $content;
}
public static function get_item_by_id( $id ): ?Item {
global $wpdb;
$result = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM " . $wpdb->prefix . "adventurebook_items WHERE id = %d", $id ), ARRAY_A );// phpcs:ignore WordPress.DB.DirectDatabaseQuery
if ( $result === null ) {
return null;
}
return new Item( $result['id'], $result['character_id'], $result['code'], $result['location_id'], $result['stats'], $result['spawn_id'] );
}
public static function create_item( $character_id, $code, $location_id, ?array $stats = null, $spawn_id = 0 ): ?Item {
// check if the item definition exists
$item_definition = Item_Definition::get_item_definition_by_code( $code );
if ( $item_definition === null ) {
return null;
}
// get the stats which are copied to the item instance from the combat system
$combat_system = adventurebook_get_combat_system_for_character_id( $character_id );
$stats_to_be_copied = $combat_system->get_item_stats_to_be_instantiated();
// if no stats are given, copy the default stats from the item definition
// but only those stats which are defined to be copied by the combat system
// this way we avoid copying unnecessary stats like "Value" or "Description"
// to the item instance in the database
if ( $stats === null ) {
$stats = array();
foreach ( $stats_to_be_copied as $stat_name ) {
if ( isset( $item_definition->stats[ $stat_name ] ) ) {
$stats[ $stat_name ] = $item_definition->stats[ $stat_name ];
}
}
}
global $wpdb;
$table_name = $wpdb->prefix . 'adventurebook_items';
$wpdb->insert( $table_name, array(// phpcs:ignore WordPress.DB.DirectDatabaseQuery
'character_id' => $character_id,
'code' => $code,
'location_id' => $location_id,
'stats' => json_encode( $stats ),
'spawn_id' => $spawn_id,
) );
$id = $wpdb->insert_id;
return new Item( $id, $character_id, $code, $location_id, json_encode( $stats ), $spawn_id );
}
public static function delete_item( $id ): void {
global $wpdb;
$table_name = $wpdb->prefix . 'adventurebook_items';
$wpdb->delete( $table_name, array( 'id' => $id ) );// phpcs:ignore WordPress.DB.DirectDatabaseQuery
}
public static function get_items_by_character_id( $character_id ): array {
global $wpdb;
$results = $wpdb->get_results( $wpdb->prepare( "SELECT * FROM " . $wpdb->prefix . "adventurebook_items WHERE character_id = %d", $character_id ), ARRAY_A );// phpcs:ignore WordPress.DB.DirectDatabaseQuery
$items = array();
foreach ( $results as $result ) {
$items[] = new Item( $result['id'], $result['character_id'], $result['code'], $result['location_id'], $result['stats'] );
}
return $items;
}
/**
* Get all items associated with a specific spawn point.
*
* @param int $spawn_id The ID of the spawn point.
*
* @return Item[] An array of Item objects.
*/
public static function get_items_for_spawn_id( $spawn_id ): array {
global $wpdb;
$results = $wpdb->get_results( $wpdb->prepare( "SELECT * FROM " . $wpdb->prefix . "adventurebook_items WHERE spawn_id = %d", $spawn_id ), ARRAY_A );// phpcs:ignore WordPress.DB.DirectDatabaseQuery
$items = array();
foreach ( $results as $result ) {
$items[] = new Item( $result['id'], $result['character_id'], $result['code'], $result['location_id'], $result['stats'], $result['spawn_id'] );
}
return $items;
}
public static function delete_items_by_character_id( $character_id ): void {
global $wpdb;
$table_name = $wpdb->prefix . 'adventurebook_items';
$wpdb->delete( $table_name, array( 'character_id' => $character_id ) );// phpcs:ignore WordPress.DB.DirectDatabaseQuery
}
public function get_slot(): string {
$stats = $this->get_complete_stats();
return $stats['Slot'] ?? '';
}
public function set_location( int $location_id ): void {
$this->location_id = $location_id;
$this->save_to_db();
}
public function set_spawn_id( int $spawn_id ): void {
$this->spawn_id = $spawn_id;
$this->save_to_db();
}
public static function delete_items_for_spawn_id( $spawn_id ): void {
global $wpdb;
$table_name = $wpdb->prefix . 'adventurebook_items';
$wpdb->delete( $table_name, array( 'spawn_id' => $spawn_id ) );// phpcs:ignore WordPress.DB.DirectDatabaseQuery
}
/**
* Deletes all items that are associated with a spawn_id that no longer exists in the item_spawns table.
*
* @return int The number of deleted items.
*/
public static function delete_orphaned_items(): int {
global $wpdb;
$table_name = $wpdb->prefix . 'adventurebook_items';
$spawn_table_name = $wpdb->prefix . 'adventurebook_item_spawns';
// $wpdb->query( "DELETE FROM $table_name WHERE spawn_id != 0 AND spawn_id NOT IN (SELECT id FROM $spawn_table_name)" );// phpcs:ignore WordPress.DB.DirectDatabaseQuery,WordPress.DB.PreparedSQL.InterpolatedNotPrepared
// Escape the table names before interpolating into the SQL string.
// Table names cannot be used as placeholders in $wpdb->prepare(), so esc_sql() is used here.
$escaped_table = esc_sql( $table_name );
$escaped_spawn_table = esc_sql( $spawn_table_name );
$wpdb->query( "DELETE FROM " . $escaped_table . " WHERE spawn_id != 0 AND spawn_id NOT IN (SELECT id FROM " . $escaped_spawn_table . ")" ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery
return $wpdb->rows_affected;
}
public function save_to_db(): void {
global $wpdb;
$table_name = $wpdb->prefix . 'adventurebook_items';
$wpdb->update( $table_name, array(// phpcs:ignore WordPress.DB.DirectDatabaseQuery
'character_id' => $this->character_id,
'code' => $this->code,
'location_id' => $this->location_id,
'stats' => json_encode( $this->stats ),
'spawn_id' => $this->spawn_id,
), array( 'id' => $this->id ) );
}
public function is_usable(): bool {
$item_definition = Item_Definition::get_item_definition_by_code( $this->code );
return $item_definition->is_usable();
}
public function is_resource(): bool
{
$item_definition = Item_Definition::get_item_definition_by_code($this->code);
return $item_definition->is_resource();
}
public function get_charges(): int {
$charges = $this->get_stat( 'Charges' );
if ( ! is_numeric( $charges ) || $charges < 0 ) {
return 1;
}
return intval( $charges );
}
public function decrease_charges( int $amount = 1 ): void {
$current_charges = $this->get_charges();
$new_charges = max(0, $current_charges - $amount);
$this->stats['Charges'] = $new_charges;
$this->save_to_db();
}
public function set_charges( int $charges ): void {
$this->stats['Charges'] = $charges;
$this->save_to_db();
}
public function get_item_slot(): string {
$item_definition = Item_Definition::get_item_definition_by_code( $this->code );
return $item_definition->get_item_slot();
}
public function is_two_handed(): bool {
$item_definition = Item_Definition::get_item_definition_by_code( $this->code );
return $item_definition->is_two_handed();
}
/**
* Compares this item with another item to determine if they are identical.
* Two items are considered identical if they have the same item definition code
* and the same stats (both keys and values).
*
* Money items are never considered identical.
*
* this is used for a grouped inventory display
*
* @param ?Item $other The other item to compare with.
*
* @return bool True if the items are identical, false otherwise.
*/
public function is_identical_to_item( ?Item $other ): bool {
if ( $other == null || $this->code !== $other->code || $this->code == 'money' ) {
return false;
}
// compare stats
$stats1 = $this->get_complete_stats();
$stats2 = $other->get_complete_stats();
if ( count( $stats1 ) !== count( $stats2 ) ) {
return false;
}
foreach ( $stats1 as $key => $value ) {
if ( ! array_key_exists( $key, $stats2 ) || $stats2[ $key ] !== $value ) {
return false;
}
}
return true;
}
public function set_stat(string $string, mixed $value): void
{
$this->stats[$string] = $value;
$this->save_to_db();
}
}