get_initial_character_stats(); } // call the parent constructor parent::__construct( $name, '', $stats === '' ? [] : $stats ); $this->id = $id; $this->user_id = $user_id; $this->adventurebook_id = $adventurebook_id; $this->location_id = $location_id; $this->inventory = $inventory === '' ? [] : $inventory; $this->next_action = $next_action; $this->created_at = $created_at; } public static function create_character_by_params( $id, $user_id, $adventurebook_id, $name, $location_id = 0, $stats = '', $inventory = '', $next_action = '', $created_at = '' ): Character { return new Character( $id, $user_id, $adventurebook_id, $name, $location_id, $stats, $inventory, $next_action, $created_at ); } public static function create_character_by_result( $result ): Character { return new Character( $result['id'], $result['user_id'], $result['adventurebook_id'], $result['name'], $result['location_id'], json_decode( $result['stats'], true ), json_decode( $result['inventory'], true ), $result['next_action'], $result['created_at'] ); } /** * Save the character to the database. * If the character already has an ID, it will be updated. * If the character does not have an ID, it will be created. * * @return int The ID of the character. */ public function save_to_db(): int { global $wpdb; $table_name = $wpdb->prefix . 'adventurebook_characters'; // if this character already has an ID, we should update it instead of creating a new one if ( $this->id > 0 ) { $wpdb->update( $table_name, array( // phpcs:ignore WordPress.DB.DirectDatabaseQuery 'user_id' => $this->user_id, 'name' => $this->name, 'adventurebook_id' => $this->adventurebook_id, 'location_id' => $this->location_id, 'stats' => json_encode( $this->stats ), 'inventory' => json_encode( $this->inventory ), 'next_action' => $this->next_action, ), array( 'id' => $this->id ) ); return $this->id; } // insert the new character into the database $wpdb->insert( $table_name, array(// phpcs:ignore WordPress.DB.DirectDatabaseQuery 'user_id' => $this->user_id, 'name' => $this->name, 'adventurebook_id' => $this->adventurebook_id, 'location_id' => $this->location_id, 'stats' => json_encode( $this->stats ), 'inventory' => json_encode( $this->inventory ), 'next_action' => $this->next_action, 'created_at' => current_time( 'mysql' ), ) ); $new_character_id = $wpdb->insert_id; $this->id = $new_character_id; // iterate through all item_spawns and create a duplicate of it for this character $item_spawn_points = Item_Spawn::get_all_item_spawn_points(); foreach ( $item_spawn_points as $item_spawn_point ) { // create an item spawn point for each item spawn Item_Spawn::create_item_spawn_point( $new_character_id, $item_spawn_point->id ); } // iterate through all enemy spawn points and create a duplicate of it for this character $enemy_spawn_points = Enemy_Spawn::get_all_enemy_spawn_points(); foreach ( $enemy_spawn_points as $enemy_spawn_point ) { Enemy_Spawn::create_enemy_spawn_point( $new_character_id, $enemy_spawn_point->id ); } return $new_character_id; } /** Delete the character from the database. * This will also delete all items that belong to this character. */ public function delete_from_db(): void { global $wpdb; // delete the character from the database $wpdb->delete( $wpdb->prefix . "adventurebook_characters", array( 'id' => $this->id ) );// phpcs:ignore WordPress.DB.DirectDatabaseQuery // delete all items that belong to this character Item::delete_items_by_character_id( $this->id ); // delete all enemy spawn points that belong to this character Enemy_Spawn::delete_enemy_spawn_points_by_character_id( $this->id ); // get the adventurebook id of the character $adventurebook_id = $this->adventurebook_id; $user_id = get_current_user_id(); // if the deleted character was the current character, remove the current_character_id meta $current_character_id = get_user_meta( $user_id, 'adventurebook_current_character_id_' . $adventurebook_id, true ); if ( $this->id == $current_character_id ) { delete_user_meta( $user_id, 'adventurebook_current_character_id_' . $adventurebook_id ); } // if the user still has characters, set the first character as the current character $characters = $wpdb->get_results( $wpdb->prepare( "SELECT * FROM " . $wpdb->prefix . "adventurebook_characters WHERE user_id = %d", $user_id ) );// phpcs:ignore WordPress.DB.DirectDatabaseQuery if ( $characters ) { update_user_meta( $user_id, 'adventurebook_current_character_id_' . $adventurebook_id, $characters[0]->id ); } } public static function get_characters_by_adventurebook_id( int $adventurebook_id ): array { global $wpdb; $results = $wpdb->get_results( $wpdb->prepare( "SELECT * FROM " . $wpdb->prefix . "adventurebook_characters WHERE adventurebook_id = %d", $adventurebook_id ), ARRAY_A );// phpcs:ignore WordPress.DB.DirectDatabaseQuery $characters = array(); foreach ( $results as $result ) { $characters[] = Character::create_character_by_result( $result ); } return $characters; } public static function get_characters_by_user_id( int $user_id ): array { global $wpdb; $results = $wpdb->get_results( $wpdb->prepare( "SELECT * FROM " . $wpdb->prefix . "adventurebook_characters WHERE user_id = %d", $user_id ), ARRAY_A );// phpcs:ignore WordPress.DB.DirectDatabaseQuery $characters = array(); foreach ( $results as $result ) { $characters[] = Character::create_character_by_result( $result ); } return $characters; } public static function get_character_by_id( int $id ): ?Character { global $wpdb; $result = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM " . $wpdb->prefix . "adventurebook_characters WHERE id = %d", $id ), ARRAY_A );// phpcs:ignore WordPress.DB.DirectDatabaseQuery if ( $result === null ) { return null; } return Character::create_character_by_result( $result ); } /** * render the item list of this character as html * needed for the character overview (management page) * * @return string */ public function get_item_list_html(): string { global $adventurebook_current_character, $adventurebook_current_character_id, $adventurebook_id, $page_id, $wealth_unit; // get the current combat system $adventurebook_combat_system = adventurebook_get_combat_system_for_adventurebook( $this->adventurebook_id ); $inventory = $this->get_inventory(); $inventory = adventurebook_sort_items_by_name( $inventory ); // if the inventory is empty, we return here if ( empty( $inventory ) ) { $content = '
  • ' . esc_html( __( 'Your inventory is empty.', 'adventurebook' ) ) . '
  • '; return $content; } $content = ''; return $content; } /** * Get all slotted items of this character * Slotted items are items that are in the stat "SLOTS" and are currently equipped * slot "SLOTS" has the format ["Weapon:item_id", "Shield:item_id"...] * if the item_id is 0, the slot is empty. * * @return array an associative array with the slot name as key and the Item object as value * e.g. ["Weapon" => Item, "Shield" => Item...] * if a slot is empty, it will not be included in the array */ public function get_slotted_items(): array { $slots_stat = $this->get_slots(); $slotted_items = []; foreach ( $slots_stat as $slot_key => $item_id ) { $item = Item::get_item_by_id( $item_id ); if ( $item ) { $slotted_items[ $slot_key ] = $item; } } return $slotted_items; } /** * Uses an item with this item_id from the character's inventory * The item must have a "Use" stat that contains the script code to be executed * In addition it can have a "UseData" stat that contains additional data for the use script * * @param int $item_id * * @return array the script output of the use effect */ public function use_item( int $item_id ): array { $output = []; if ( $this->has_item_with_id( $item_id ) ) { $item = Item::get_item_by_id( $item_id ); // check if the item has a "UseEffect" stat if ( $item->is_usable() ) { $use_script = $item->get_stat( "Use" ); $use_data = $item->get_stat( "UseData" ); // apply the use effect to the character // the use effect is a script(code) // e.g. "Use" => "script_code" $script = Script::get_script_by_code( $use_script ); // echo the output of the script if ( $script ) { $output = $script->run_script( $this, $use_data ); // decrease the "Charges" stat of the item by 1, if it exists // but only if the output does not contain a directive to not decrease charges // in this case the output contains a string with "[preserve_charges]" $do_not_decrease_charges = false; foreach ( $output as $output_entry ) { if ( is_string( $output_entry ) && strpos( $output_entry, '[preserve_charges]' ) !== false ) { $do_not_decrease_charges = true; break; } } if ( $do_not_decrease_charges ) { return $output; } // decrease charges, Charges = -1 means infinite charges if ( isset( $item->stats['Charges'] ) ) { if( intval( $item->get_stat( 'Charges' ) ) === -1 ) { // infinite charges, do nothing return $output; } $charges = intval( $item->get_stat( 'Charges' ) ) - 1; } else { $charges = 0; } // invalidate inventory cache, as the item charges have changed adventurebook_invalidate_inventory_cache($this->id); if ( $charges > 0 ) { $item->stats['Charges'] = $charges; $item->save_to_db(); } elseif ( $charges == 0 ) { // if the item has no more charges, it is removed from the game Item::delete_item( $item_id ); } } } } return $output; } public function has_item_with_id( int $item_id ): bool { $inventory = $this->get_inventory( false ); foreach ( $inventory as $item ) { if ( $item->id === $item_id ) { return true; } } return false; } /** Check if the character has an item with the given item code in his inventory * * @param string $item_code The code of the item to check for * * @return bool True if the character has the item, false otherwise */ public function has_item_with_code(string $item_code): bool { $inventory = $this->get_inventory(false); foreach ( $inventory as $item ) { if ( $item->code === $item_code ) { return true; } } return false; } /** Move the character to a different zone * * @param int $zone_id The ID of the zone to move the character to * * upon moving to a different zone, all enemy spawns for this character in the new zone need to be checked * * @return void */ public function move_to_zone( int $zone_id ): void { // we want to know if the character has just entered a new zone so that he can surprise enemies // see CombatSystem::doCombat() set_transient( 'adventurebook_just_entered_zone_' . $this->id, true ); $this->location_id = $zone_id; $this->save_to_db(); // we save that we have visited the zone (for map purposes) $this->set_zone_visited( $zone_id ); } /** Set that the character has visited the given zone * * @param int $zone_id The ID of the zone to check */ public function set_zone_visited( int $zone_id ): void { $visited_zones = $this->get_game_var( 'VisitedZones' ); if ( is_array( $visited_zones ) ) { if ( ! in_array( $zone_id, $visited_zones, true ) ) { $visited_zones[] = $zone_id; $this->set_game_var( 'VisitedZones', $visited_zones ); $this->save_to_db(); } } else { $visited_zones = array( $zone_id ); $this->set_game_var( 'VisitedZones', $visited_zones ); $this->save_to_db(); } } public function get_visited_page_ids(): array { // In diesem Plugin ist eine „Zone" identisch mit einer Seite // (location_id = Seiten-ID). Besuche werden in move_to_zone()/ // set_zone_visited() unter 'VisitedZones' gespeichert — 'VisitedPages' // wurde nie geschrieben. Daher hier VisitedZones lesen (das ist die // Liste der besuchten Seiten-IDs; nötig u. a. für grüne Karten-Marker // und die Auto-Karte für Spieler). $visited_pages = $this->get_game_var( 'VisitedZones' ); if ( is_array( $visited_pages ) ) { return array_map( 'intval', $visited_pages ); } return array(); } public function has_visited_zone( int $zone_id ): bool { $visited_zones = $this->get_game_var( 'VisitedZones' ); if ( is_array( $visited_zones ) ) { return in_array( $zone_id, $visited_zones, true ); } return false; } /** Respawn the character by setting its current health to max health * and saving the character to the database * * @return void */ public function respawn(): void { // get the combat system for this adventurebook $adventurebook_combat_system = adventurebook_get_combat_system_for_adventurebook( $this->adventurebook_id ); // set the current health to max health $this->recalculate_max_health(); // save the character to the database $this->save_to_db(); } public function get_combat_system(): Combat_System { return adventurebook_get_combat_system_for_adventurebook( $this->adventurebook_id ); } public function get_respawn_zone_id(): int { return intval( $this->get_stat( 'RespawnZone' ) ); } public function get_respawn_zone_title(): string { $respawn_zone_id = $this->get_respawn_zone_id(); if ( ! $respawn_zone_id ) { return ''; } $respawn_zone = get_post( $respawn_zone_id ); if ( ! $respawn_zone ) { return ''; } return $respawn_zone->post_title; } /** Set the spawn point of the character to the given zone ID * * @param int $zone_id The ID of the zone to set as spawn point. If 0, the current location is used. */ public function set_spawn_point( int $zone_id = 0 ): void { if($zone_id == 0) { $zone_id = $this->location_id; } $this->stats['RespawnZone'] = $zone_id; $this->save_to_db(); } static public function delete_character_by_id( int $id ): bool { // first, get the character $character = self::get_character_by_id( $id ); if ( $character ) { // remove all items that belong to this character Item::delete_items_by_character_id( $character->id ); // remove all enemy spawn points that belong to this character Enemy_Spawn::delete_enemy_spawn_points_by_character_id( $character->id ); // delete the character from the database $character->delete_from_db(); return true; } return false; } static public function delete_orphaned_characters(): int { global $wpdb; $deleted_count = 0; $table_name = esc_sql($wpdb->prefix . 'adventurebook_characters'); $results = $wpdb->get_results( "SELECT * FROM $table_name", ARRAY_A ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery, WordPress.DB.PreparedSQL.InterpolatedNotPrepared foreach ( $results as $result ) { $user_id = intval( $result['user_id'] ); $user = get_user_by( 'id', $user_id ); if ( ! $user ) { // user does not exist anymore, delete the character if(self::delete_character_by_id( intval( $result['id'] ) )) { $deleted_count++; } } // get the adventurebook_id of the character $adventurebook_id = intval( $result['adventurebook_id'] ); $adventurebook = get_post( $adventurebook_id ); if ( ! $adventurebook || $adventurebook->post_type !== 'adventurebook' ) { // adventurebook does not exist anymore, delete the character self::delete_character_by_id( intval( $result['id'] ) ); $deleted_count++; } } return $deleted_count; } }