0, "Body" => 0, ...] * If the slot is used by an item, the value is the item_id, else it is 0 * * The inventory ID of an item is generated each time the item is added to the character's inventory. * It is not the same as the item definition ID. */ class Item_Definition { public $id; public $code; public $name; public $description; public $stats; public $image_url; public function __construct( $id, $code, $name, $description, $stats, $image_url ) { $this->id = $id; $this->code = $code; $this->name = $name; $this->description = $description; $this->stats = json_decode( $stats, true ); $this->image_url = $image_url; } public static function get_item_definition_by_id( $id ): ?Item_Definition { global $wpdb; $result = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM " . $wpdb->prefix . "adventurebook_item_definitions WHERE id = %d", $id ), ARRAY_A );// phpcs:ignore WordPress.DB.DirectDatabaseQuery if ( $result === null ) { return null; } return new Item_Definition( $result['id'], $result['code'], $result['name'], $result['description'], $result['stats'], $result['image_url'] ); } public static function get_item_definition_by_code( $code ): ?Item_Definition { global $wpdb; $result = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM " . $wpdb->prefix . "adventurebook_item_definitions WHERE code = %s", $code ), ARRAY_A );// phpcs:ignore WordPress.DB.DirectDatabaseQuery if ( $result === null ) { return null; } return new Item_Definition( $result['id'], $result['code'], $result['name'], $result['description'], $result['stats'], $result['image_url'] ); } public static function get_all_item_definitions(): array { global $wpdb; $results = $wpdb->get_results( "SELECT * FROM " . $wpdb->prefix . "adventurebook_item_definitions", ARRAY_A );// phpcs:ignore WordPress.DB.DirectDatabaseQuery $item_definitions = array(); foreach ( $results as $result ) { $item_definitions[] = new Item_Definition( $result['id'], $result['code'], $result['name'], $result['description'], $result['stats'], $result['image_url'] ); } // sort by name usort( $item_definitions, function ( $a, $b ) { return strcmp( $a->name, $b->name ); } ); return $item_definitions; } public static function create_item_definition( $code, $name, $description, $stats, $image_url ): void { global $wpdb; $table_name = $wpdb->prefix . 'adventurebook_item_definitions'; $wpdb->insert( $table_name, array(// phpcs:ignore WordPress.DB.DirectDatabaseQuery 'code' => $code, 'name' => $name, 'description' => $description, 'stats' => json_encode( $stats ), 'image_url' => $image_url ) ); } public static function update_item_definition( $id, $code, $name, $description, $stats, $image_url ): void { global $wpdb; $table_name = $wpdb->prefix . 'adventurebook_item_definitions'; $wpdb->update( $table_name, array(// phpcs:ignore WordPress.DB.DirectDatabaseQuery 'code' => $code, 'name' => $name, 'description' => $description, 'stats' => json_encode( $stats ), 'image_url' => $image_url ), array( 'id' => $id ) ); } public static function delete_item_definition( $id ): void { global $wpdb; $table_name = $wpdb->prefix . 'adventurebook_item_definitions'; $wpdb->delete( $table_name, array( 'id' => $id ) );// phpcs:ignore WordPress.DB.DirectDatabaseQuery } public function save_to_db(): void { if ( $this->id === 0 ) { self::create_item_definition( $this->code, $this->name, $this->description, $this->stats, $this->image_url ); } else { self::update_item_definition( $this->id, $this->code, $this->name, $this->description, $this->stats, $this->image_url ); } } // get stats for this item public function get_stats(): array { if(!is_array($this->stats)) { return array(); } return $this->stats; } /** * If this item is a recipe, get the resulting item code from the recipe data * It is the last part of the recipe data string, separated by ";" * * @param string $recipe_data The recipe data string * @return string The resulting item code */ public static function get_recipe_resulting_itemcode(string $recipe_data) : string { $parts = explode( ';', $recipe_data ); return trim( end( $parts ) ); } public static function get_recipe_needed_ingredients(string $recipe_data) : array { $parts = explode( ';', $recipe_data ); array_pop( $parts ); // remove the last part (resulting item code) $ingredients = array(); foreach ( $parts as $part ) { $ingredient_parts = explode( ':', $part ); if ( count( $ingredient_parts ) === 2 ) { $ingredient_code = trim( $ingredient_parts[0] ); $ingredient_quantity = intval( trim( $ingredient_parts[1] ) ); $ingredients[ $ingredient_code ] = $ingredient_quantity; } } return $ingredients; } public function get_description(): string { // if this item has the slot "Recipe", it has also a stat "UseData" which contains the recipe description $output = ''; if ( isset( $this->stats['Slot'] ) && $this->stats['Slot'] === 'Recipe' && isset( $this->stats['UseData'] ) && ! empty( $this->stats['UseData'] ) ) { $use_data = $this->stats['UseData']; $resulting_item_code = Item_Definition::get_recipe_resulting_itemcode( $use_data ); $resulting_item_definition = Item_Definition::get_item_definition_by_code( $resulting_item_code ); $resulting_item_name = $resulting_item_definition ? $resulting_item_definition->name : __('Unknown Item', 'adventurebook'); /* translators: %s is the name of the resulting item */ $output .= sprintf( __( 'This recipe can be used to craft: %s', 'adventurebook' ), esc_html( $resulting_item_name ) ) . '
'; // now print out the needed ingredients $needed_ingredients = Item_Definition::get_recipe_needed_ingredients( $use_data ); $output .= __( 'Needed ingredients:', 'adventurebook' ); // iterate over needed ingredients and show their names with the quantity as comma separated string (e.g. "1x Item_Name1, 4x Item_Name2") $ingredient_strings = array(); foreach ( $needed_ingredients as $ingredient_code => $quantity ) { $ingredient_definition = Item_Definition::get_item_definition_by_code( $ingredient_code ); $ingredient_name = $ingredient_definition ? $ingredient_definition->name : __('Unknown Item', 'adventurebook'); $ingredient_strings[] = intval( $quantity ) . 'x ' . esc_html( $ingredient_name ); } $output .= ' ' . implode( ', ', $ingredient_strings ); return $output; } return $this->description; } public function get_item_slot(): ?string { if ( isset( $this->stats['Slot'] ) && ! empty( $this->stats['Slot'] ) ) { return $this->stats['Slot']; } return null; } public function get_main_slot(): ?string { $slot = $this->get_item_slot(); if ( $slot === null ) { return null; } // if the slot contains a "+", return only the part before the "+" if ( str_contains( $slot, '+' ) ) { $parts = explode( '+', $slot ); return trim( $parts[0] ); } return $slot; } public function is_two_handed(): bool { $slot = $this->get_item_slot(); if ( $slot === null ) { return false; } $parts = explode( '+', $slot ); return count( $parts ) > 1; } /** * Generates the HTML representation of the item, including its image, name, and description. * * @param Combat_System $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 ): string { $item = $this; $item_id = 'adventurebook_item_' . $item->id; // eindeutige ID für JS-Selektoren $content = '
'; // Bild $content .= '
'; $content .= $item->image_url ? '' : ''; $content .= '
'; // Stats ermitteln if ( $adventurebook_combat_system ) { $stats = $adventurebook_combat_system->get_item_stats_to_show( $item->stats ); } else { $stats = $item->get_stats(); } // Name + Toggle $content .= '
name); if($count > 1) { $content .= ' ('.$count.'x)'; } $stats_to_show = 0; if (!empty($stats)) { // Stats-Container (versteckt) $stats_content = '
'; foreach ($stats as $stat_name => $stat_value) { // if the stat_name is "Use" or "Value", don't show it here (Value will be shown on the buy button) if ($stat_name === 'Use' || $stat_name === 'Value') { continue; } $stats_content .= '
' . esc_html($stat_name) . ': ' . esc_html($stat_value); $stats_content .= '
'; $stats_to_show++; } // only show the stats expander (+) if there are stats to show if( $stats_to_show > 0 ) { $content .= ' [+]'; } } $content .= '
'; // adventurebook_item_name // if an admin is logged in, show an edit button with a link to the item definition edit page if ( current_user_can( 'manage_options' ) && get_option( 'adventurebook_enable_inline_editing', '0' ) === '1' ) { $edit_url = admin_url( 'admin.php?page=adventurebook_items&action=edit&id=' . $item->id ); $content .= ' [EDIT]'; } // now show the stats container if there are stats to show if ($stats_to_show > 0 ) { $content .= $stats_content; $content .= '
'; // adventurebook_item_stats } // Beschreibung $content .= '
' . $item->description . "
"; $content .= '
'; // adventurebook_item return $content; } public function get_stat( $stat_name ): ?string { return $this->stats[$stat_name] ?? null; } public function is_usable(): bool { return isset($this->stats['Use']) && $this->stats['Use'] !== ''; } public function is_resource(): bool { return $this->get_item_slot() == 'Resource'; } public function get_base_price(): int { // the Value stat is multiplied by Charges or 1 if Charges is not set $item_definition = Item_Definition::get_item_definition_by_code( $this->code ); $charges = $this->get_stat( 'Charges' ) ?? 1; if( !is_numeric($charges) || $charges < 1 ) { $charges = 1; } $base_value = $item_definition->get_stat('Value'); return intval($base_value) * intval($charges); } /** * Get all item definitions for a specific adventure book * These are all (unique) item definitions that are used in the adventure book. * That means that an item_spawn must exist for the item definition with a page_id that belongs to the adventure book. * in addition all shop inventories must be considered, as they also contain items. * for this, we need to join the item_spawns table with the pages table to get the page_id and then filter by adventurebook_id. * * @param int $adventurebook_id The ID of the adventure book * @return array An array of Item_Definition objects */ public static function get_all_item_definitions_by_adventurebook_id( $adventurebook_id ): array { global $wpdb; $results = $wpdb->get_results( $wpdb->prepare( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching "SELECT DISTINCT id.* FROM " . $wpdb->prefix . "adventurebook_item_definitions id JOIN " . $wpdb->prefix . "adventurebook_item_spawns isp ON id.code = isp.code JOIN " . $wpdb->prefix . "posts p ON isp.page_id = p.ID WHERE p.post_parent = %d", $adventurebook_id ), ARRAY_A );// phpcs:ignore WordPress.DB.DirectDatabaseQuery $items = self::create_item_definitions_from_results( $results ); // now add items from shops // a shop is a CPT adventurebook_page with meta key '_adventurebook_shop_items', comma separated list of item codes $shop_results = $wpdb->get_results( $wpdb->prepare( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching "SELECT DISTINCT id.* FROM " . $wpdb->prefix . "adventurebook_item_definitions id JOIN " . $wpdb->prefix . "posts p ON p.post_parent = %d JOIN " . $wpdb->prefix . "postmeta pm ON pm.post_id = p.ID WHERE pm.meta_key = '_adventurebook_shop_items' AND FIND_IN_SET(id.code, pm.meta_value) > 0", $adventurebook_id ), ARRAY_A );// phpcs:ignore WordPress.DB.DirectDatabaseQuery $shop_items = self::create_item_definitions_from_results( $shop_results ); $merged = []; foreach ( $items as $item ) { $merged[ $item->code ] = $item; } foreach ( $shop_items as $item ) { if ( ! isset( $merged[ $item->code ] ) ) { $merged[ $item->code ] = $item; } } // get all item codes that are in the used enemys loottables $enemies = Enemy_Definition::get_all_enemy_definitions_by_adventurebook_id( $adventurebook_id ); foreach ( $enemies as $enemy ) { foreach ($enemy->get_loottable_item_definitions() as $item_definition) { /* @var $item_definition Item_Definition */ if(!isset($merged[ $item_definition->code])) { $merged[ $item_definition->code ] = $item_definition; } } } // get all used scripts for the adventure book $scripts = Script::get_all_scripts_by_adventurebook_id( $adventurebook_id ); foreach ( $scripts as $script ) { foreach ( $script->get_all_item_definitions() as $item_definition ) { /* @var $item_definition Item_Definition */ if ( ! isset( $merged[ $item_definition->code ] ) ) { $merged[ $item_definition->code ] = $item_definition; } } } return array_values( $merged ); } public static function create_item_definitions_from_results( $results ): array { $item_definitions = array(); foreach ( $results as $result ) { $item_definitions[] = new Item_Definition( $result['id'], $result['code'], $result['name'], $result['description'], $result['stats'], $result['image_url'] ); } return $item_definitions; } }