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(); } }