name = $name; $this->description = $description; $this->stats = $stats; // upon creation, the "CurHealth" stat is set to the "Health" stat if it exists if ( isset( $this->stats['Health'] ) && ! isset( $this->stats['CurHealth'] ) ) { $this->stats['CurHealth'] = $this->stats['Health']; } } public function get_name(): string { return $this->name; } public function set_name( string $name ): void { $this->name = $name; $this->save_to_db(); } public function get_description(): string { return $this->description; } public function get_stats(): array { return $this->stats; } /** Get a specific stat by name. * Returns null if the stat does not exist. * * a Stat MUST start with a capital letter! * e.g. "CurHealth", "Health", "Attack", "Defense", "Initiative", etc.) * * @param string $stat_name The name of the stat to retrieve. * * @return string The value of the stat, or null if it does not exist * TODO: some stats count as -3 if they do not exist (e.g. Initiative) */ public function get_stat( string $stat_name ): ?string { return $this->stats[ $stat_name ] ?? null; } public function get_slots(): array { return $this->stats['SLOTS'] ?? []; } /** Set a specific stat by name. * If the stat does not exist, it will be created. * * a Stat MUST start with a capital letter! * * @param string $stat_name The name of the stat to set. * @param int $value The value to set the stat to. * * @return void */ public function set_stat( string $stat_name, $value ): void { $this->stats[ $stat_name ] = $value; $this->save_to_db(); } public function isAlive(): bool { // if Health is not set or 0, the combattant is always alive if ( ! isset( $this->stats['Health'] ) || $this->stats['Health'] <= 0 ) { return true; } return ( $this->stats['CurHealth'] ?? 0 ) > 0; } public function take_damage( int $amount ): void { if ( isset( $this->stats['CurHealth'] ) ) { $this->stats['CurHealth'] -= $amount; if ( $this->stats['CurHealth'] < 0 ) { $this->stats['CurHealth'] = 0; } // save the combattant to the database if applicable if ( method_exists( $this, 'save_to_db' ) ) { $this->save_to_db(); } } } public function heal( int $amount ): int { if ( $amount == - 1 ) { // full heal $amount = $this->stats['Health'] - ( $this->stats['CurHealth'] ?? 0 ); } $healed_amount = 0; if ( isset( $this->stats['CurHealth'] ) && isset( $this->stats['Health'] ) ) { $healed_amount = $amount; $this->stats['CurHealth'] += $amount; if ( $this->stats['CurHealth'] > $this->stats['Health'] ) { // get the correct amount healed $healed_amount = $this->stats['Health'] - ( $this->stats['CurHealth'] - $amount ); $this->stats['CurHealth'] = $this->stats['Health']; } // save the combattant to the database if applicable if ( method_exists( $this, 'save_to_db' ) ) { $this->save_to_db(); } } return $healed_amount; } public function get_next_action(): ?string { return $this->next_action; } public function set_next_action( string $action ): void { $this->next_action = $action; $this->save_to_db(); } /** * Get the total value of a stat including bonuses from equipped items. * If the stat does not exist, it is considered to be 0. * * @param string $stat_name The name of the stat to retrieve. * * @return int The total value of the stat including item bonuses. */ public function get_total_stat_including_items( string $stat_name ): int { $base_stat = intval( $this->stats[ $stat_name ] ?? 0 ); $item_bonus = $this->get_stat_bonus_from_equipped_items( $stat_name ); return $base_stat + $item_bonus; } public function get_stat_bonus_from_equipped_items( string $stat_name ): int { $bonus = 0; $equipped_items = $this->get_equipped_items(); foreach ( $equipped_items as $item ) { $item_definition = Item_Definition::get_item_definition_by_code( $item->code ); if ( $item_definition ) { $item_stats = $item_definition->get_stats(); $bonus += intval( $item_stats[ $stat_name ] ?? 0 ); } } return $bonus; } /** * Check if the combattant has a specific slot for equipping items. * The slots are defined in the combattandt's stats under 'SLOTS'. * The value is a an associative array with slot names as keys and item_id as values * e.g. ['Weapon' => '1', 'Armor' => '2'] * item_id = 0 means the slot is empty * * @param string $slot_name The name of the slot to check. * * @return bool True if the combattant has the slot, false otherwise. */ public function has_slot( string $slot_name ): bool { $slots = $this->get_slots(); // search for the array key with the slot name return array_key_exists( $slot_name, $slots ); } public function get_slot_value( string $slot_name ): ?int { $slots = $this->get_slots(); if ( array_key_exists( $slot_name, $slots ) ) { return intval( $slots[ $slot_name ] ); } return null; // slot does not exist } /** Set the value of a specific slot. * The value is the item_id of the item equipped in this slot. * If the slot does not exist, it will be created. * * @param string $slot_name The name of the slot to set. * @param int $item_id The item_id to set the slot to (0 = empty). * * @return void */ public function set_slot_value( string $slot_name, int $item_id ): void { $slots = $this->get_slots(); $slots[ $slot_name ] = $item_id; $this->stats['SLOTS'] = $slots; // save the combattant to the database if applicable if ( method_exists( $this, 'save_to_db' ) ) { $this->save_to_db(); } } /** * Determine if the combattant can equip a given item. * This is possible if the combattant has the required slot for the item. * The slots are defined in the combattandt's stats under 'SLOTS'. * There has to be a slot with the same name as the item's slot stat. * For example, if the item has a stat 'Slot' with value 'Weapon', * the combattant must have 'Weapon' in its 'SLOTS' stat to equip it * * @param string $item_code the code of the item to check. * * @return bool True if the item can be equipped, false otherwise. */ public function can_equip_item( string $item_code ): bool { // get the item's slot and check if the combattant has this slot $item_definition = Item_Definition::get_item_definition_by_code( $item_code ); $item_stats = $item_definition->get_stats(); $item_slot = $item_stats['Slot'] ?? null; if ( $item_slot === null ) { return false; // item has no slot defined } if ( ! $this->has_slot( $item_slot ) ) { return false; // combattant does not have the required slot } return true; } /** * Equip an item to the combattant * This removes one instance of the item from the inventory * and adds it to the character's stats in the Slots stat * e.g. if the item has a Slot stat with value "Weapon", * the character's Slots stat will be updated to "Weapon:short_sword" * * @param int $item_id The code of the item to equip. * * @return bool True if the item was successfully equipped, false otherwise. */ public function equip_item( int $item_id ): bool { $item = Item::get_item_by_id( $item_id ); if ( ! $this->can_equip_item( $item->code ) ) { return false; // cannot equip the item } // check if the item is in the inventory if ( ! $this->has_item_with_id( $item_id ) ) { return false; // item not in inventory } // get the item's slot $item_slot = $item->get_item_slot(); // unequip any item currently in this slot and add it to the inventory again $this->unequip_slot( $item_slot ); // set the slot to the new item_id $this->set_slot_value( $item_slot, $item_id ); return true; } /** * Get all equipped items as an associative array * with slot names as keys and item ids as values * * @return array An associative array of equipped items. * e.g. ['Weapon' => (item sword), 'Armor' => (item leather_armor)] */ public function get_equipped_items(): array { $equipped_items = []; foreach ( $this->get_slots() as $slot_name => $item_id ) { $item = Item::get_item_by_id( $item_id ); if( $item ) $equipped_items[ $slot_name ] = $item; } return $equipped_items; } public function get_equipped_item_ids(): array { $equipped_item_ids = []; foreach ( $this->get_slots() as $slot_name => $item_id ) { if ( $item_id > 0 ) { $equipped_item_ids[] = $item_id; } } return $equipped_item_ids; } /** * Unequip the item in a given slot * this sets the value of the slot with the key $slot_name to 0 * * @param string $slot_name The name of the slot to unequip. * * @return void */ public function unequip_slot( string $slot_name ): void { // check if the combattant has this slot if ( ! $this->has_slot( $slot_name ) ) { return; // combattant does not have the slot } // set the given slot to 0 $this->set_slot_value( $slot_name, 0 ); // save the combattant to the database if applicable if ( method_exists( $this, 'save_to_db' ) ) { $this->save_to_db(); } } // region Inventory Management public function add_item_to_inventory( string $item_code ): void { // create a new item in the Item table with location_id = 0 (inventory) Item::create_item( $this->id, $item_code, 0 ); } /** * Delete an item from the inventory by its item code. * If multiple items with the same code exist, only one will be deleted. * First, all unequipped items in the inventory are searched. * If no item is found there, equipped items are searched as well. * * @param string $item_code The code of the item to delete. * * @return bool True if the item was found and deleted, false otherwise. */ public function delete_itemcode_from_inventory( string $item_code ): bool { // find an item with this code in the inventory and delete it from the db $inventory_items = $this->get_inventory(); foreach ( $inventory_items as $item ) { /** @var Item $item */ if ( $item->code === $item_code ) { Item::delete_item( $item->id ); return true; // item deleted } } // not found, we search for equipped items as well $equipped_items = $this->get_equipped_items(); foreach ( $equipped_items as $slot => $item ) { /** @var Item $item */ if ( $item->code === $item_code ) { $this->unequip_slot( $slot ); Item::delete_item( $item->id ); return true; // item deleted } } return false; // item not found } /** * Remove an item from the inventory and place it in the current location (zone) * * @param int $item_id The ID of the item to remove from the inventory. * * @return bool True if the item was successfully removed, false otherwise. */ public function remove_item_from_inventory( $item_id ): bool { // if the item is equipped, unequip it first $equipped_items = $this->get_equipped_items(); foreach ( $equipped_items as $slot => $item ) { /** @var Item $item */ if ( $item->id === $item_id ) { $this->unequip_slot( $slot ); break; } } // update this item_id in the Item table and set the location to the current character's zone $item = Item::get_item_by_id( $item_id ); if ( $item === null ) { return false; // item not found } $item->set_location( $this->location_id ); return true; } public function has_item_with_id( int $item_id ): bool { return in_array( $item_id, $this->inventory ); } public function get_item_count_by_code( string $code ): int { /** @var Item $inventory_item */ $count = 0; foreach ( $this->get_inventory() as $inventory_item ) { if ( $inventory_item->code == $code ) { $count ++; // continue to count all items with the same code } } return $count; } /** * Get all items in the inventory as an array of Item objects * these are entries in the adventurebook_items table with character_id = this character's id and location_id = 0 * * @return array An array of Item objects in the inventory */ public function get_inventory( $exclude_equipped = true ): array { $all_items_in_inventory = get_items_for_page( 0, $this->id ); // get all item_ids in the inventory $all_item_ids_in_inventory = array_map( function ( $item ) { return $item->id; }, $all_items_in_inventory ); if ( $exclude_equipped ) { $equipped_item_ids = $this->get_equipped_item_ids(); // filter out equipped items from the inventory $inventory_items = array_filter( $all_item_ids_in_inventory, function ( $item_id ) use ( $equipped_item_ids ) { return ! in_array( $item_id, $equipped_item_ids ); } ); // convert item ids to Item objects return array_map( function ( $item_id ) { return Item::get_item_by_id( $item_id ); }, $inventory_items ); } return $all_items_in_inventory; } // endregion Inventory Management public function addXP( int $xp ): string { // if the character has no "XP" stat, we cannot add XP if ( ! isset( $this->stats['XP'] ) || $xp <= 0 ) { return ''; } $this->stats['XP'] += $xp; $this->save_to_db(); /* translators: %d is the amount of XP gained */ return esc_html( sprintf( __( 'You gain %d XP!', 'adventurebook' ), $xp ) ); } /** Adds wealth to the creature's inventory * Usage: addWealth amount * e.g. addWealth 10 gold */ public function addWealth( int $amount, string $unit = '' ): string { // if the character has no "Wealth" stat, we cannot add wealth if ( ! isset( $this->stats['Wealth'] ) || $amount <= 0 ) { return ''; } $this->stats['Wealth'] += $amount; $this->save_to_db(); /* translators: %1$d is the amount of wealth gained, %2$s is the unit (e.g. gold) */ return esc_html( sprintf( __( 'You gain %1$d %2$s!', 'adventurebook' ), $amount, $unit ) ); } /** Removes wealth from the creature's inventory * Usage: removeWealth amount [unit] * e.g. removeWealth 10 gold */ public function removeWealth( int $amount, string $unit = '' ): string { // if the character has no "Wealth" stat, we cannot remove wealth if ( ! isset( $this->stats['Wealth'] ) || $amount <= 0 ) { return ''; } $this->stats['Wealth'] -= $amount; if ( $this->stats['Wealth'] < 0 ) { $this->stats['Wealth'] = 0; } $this->save_to_db(); /* translators: %1$d is the amount of wealth lost, %2$s is the unit (e.g. gold) */ return esc_html( sprintf( __( 'You lose %1$d %2$s!', 'adventurebook' ), $amount, $unit ) ); } /** Get a game variable by its name * Game variables are stored in the stats array under the key "VARS" as an associative array * If the variable does not exist, an empty string is returned * * @param string $var_name The name of the variable to get * * @return mixed The value of the variable, or an empty string if it does not exist */ public function get_game_var( string $var_name ) { $vars = $this->stats['VARS'] ?? []; if ( is_array( $vars ) && array_key_exists( $var_name, $vars ) ) { return $vars[ $var_name ]; } return ''; } /** Set a game variable by its name * Game variables are stored in the stats array under the key "VARS" as an associative array * * @param string $var_name The name of the variable to set * @param mixed $value The value to set the variable to * * @return void */ public function set_game_var( string $var_name, $value ): void { $vars = $this->stats['VARS'] ?? []; if ( ! is_array( $vars ) ) { $vars = []; } $vars[ $var_name ] = $value; $this->stats['VARS'] = $vars; $this->save_to_db(); } #region Skills Management /** Get a specific skill by name. * Returns 0 if the skill does not exist. * * Skills are saved in as an array value in stats with the key "SKILLS" * * @param string $skill_name The name of the skill to retrieve. * * @return int The value of the skill, or 0 if it does not exist */ public function get_skill( string $skill_name ): int { $skills = $this->stats['SKILLS'] ?? []; if ( is_array( $skills ) && array_key_exists( $skill_name, $skills ) ) { return intval( $skills[ $skill_name ] ); } return 0; } /** Set a specific skill by name. * If the skill does not exist, it will be created. * * Skills are saved in as an array value in stats with the key "SKILLS" * * @param string $skill_name The name of the skill to set. * @param int $amount The value to set the skill to. * * @return void */ public function set_skill( string $skill_name, int $amount ): void { $skills = $this->stats['SKILLS'] ?? []; if ( ! is_array( $skills ) ) { $skills = []; } $skills[ $skill_name ] = $amount; $this->stats['SKILLS'] = $skills; $this->save_to_db(); } /** Check if a skill can be raised. * * The combat system sets the rules for raising skills. * * @param string $skill_name The name of the skill to check. * @return bool True if the skill can be raised, false otherwise. */ public function can_raise_skill( string $skill_name ) : bool { // we will ask the Combat System if the skill can be raised $combat_system = get_combat_system_for_character_id( $this->id ); return $combat_system->combattant_can_raise_skill( $this, $skill_name ); } #endregion Skills Management }