id = intval( $id ); $this->code = $code; $this->page_id = intval( $page_id ); $this->respawn_time = intval( $respawn_time ); $this->last_defeated = $last_defeated; $this->character_id = intval( $character_id ); $this->parent_id = intval( $parent_id ); $this->alternate_page_ids = (string) $alternate_page_ids; } public function get_enemy_definition(): ?Enemy_Definition { return Enemy_Definition::get_enemy_definition_by_id( $this->id ); } public static function get_enemy_spawn_by_id( $id ): ?Enemy_Spawn { global $wpdb; $result = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM ".$wpdb->prefix."adventurebook_enemy_spawns WHERE id = %d", $id ), ARRAY_A );// phpcs:ignore WordPress.DB.DirectDatabaseQuery if ( $result === null ) { return null; } return new Enemy_Spawn( $result['id'], $result['code'], $result['page_id'], null, $result['respawn_time'], $result['character_id'], $result['parent_id'], $result['alternate_page_ids'] ); } public static function get_enemy_spawns_by_page_id( $page_id, $character_id = 0 ): array { global $wpdb; if ( $character_id > 0 ) { $results = $wpdb->get_results( $wpdb->prepare( "SELECT * FROM ".$wpdb->prefix . "adventurebook_enemy_spawns WHERE page_id = %d AND character_id = %d", $page_id, $character_id ), ARRAY_A );// phpcs:ignore WordPress.DB.DirectDatabaseQuery } else { $results = $wpdb->get_results( $wpdb->prepare( "SELECT * FROM ".$wpdb->prefix . "adventurebook_enemy_spawns WHERE page_id = %d AND character_id = 0", $page_id ), ARRAY_A );// phpcs:ignore WordPress.DB.DirectDatabaseQuery } $enemy_spawns = array(); foreach ( $results as $result ) { $enemy_spawns[] = new Enemy_Spawn( $result['id'], $result['code'], $result['page_id'], $result['last_defeated'], $result['respawn_time'], $result['character_id'], $result['parent_id'], $result['alternate_page_ids'] ); } return $enemy_spawns; } /** * Create a new enemy spawn point template (character_id = 0) for the given page. * This will also create an instance of this spawn point for each existing character in the same adventurebook. * * @param string $code The enemy code to spawn * @param int $page_id The page ID where the enemy should spawn * @param int $respawn_time The respawn time in seconds (0 = no respawn) * @param string $alternate_page_ids Comma-separated list of additional page IDs where this enemy can also spawn */ public static function create_enemy_spawn(string $code, int $page_id, int $respawn_time = 0, string $alternate_page_ids = ''): void { self::create_enemy_spawn_point_root( $code, $page_id, $respawn_time, $alternate_page_ids ); } /** * Internal function to create the root spawn point and instances for existing characters. * * @param string $code The enemy code to spawn * @param int $page_id The page ID where the enemy should spawn * @param int $respawn_time The respawn time in seconds (0 = no respawn) * @param string $alternate_page_ids Comma-separated list of additional page IDs where this enemy can also spawn */ public static function create_enemy_spawn_point_root(string $code, int $page_id, int $respawn_time = 0, string $alternate_page_ids = ''): void { global $wpdb; $table_name = $wpdb->prefix . 'adventurebook_enemy_spawns'; $wpdb->insert( $table_name, array(// phpcs:ignore WordPress.DB.DirectDatabaseQuery 'code' => $code, 'page_id' => $page_id, 'respawn_time' => $respawn_time, 'last_defeated' => null, 'character_id' => 0, 'parent_id' => 0, 'alternate_page_ids' => $alternate_page_ids, ) ); // now create an instance of this spawn point for each existing character in the same adventurebook, with parent_id set to the id of the root spawn point // first, get the id of the newly created spawn point $spawn_id = $wpdb->insert_id; // get the adventurebook_id of the page (it's the parent id of the page) $adventurebook_id = wp_get_post_parent_id( $page_id ); // get all characters in this adventurebook $characters = Character::get_characters_by_adventurebook_id( $adventurebook_id ); foreach ( $characters as $character ) { self::create_enemy_spawn_point( $character->id, $spawn_id ); } } public static function create_enemy_spawn_point( $character_id, $parent_spawn_point_id): void { global $wpdb; // get the parent spawn point $parent_spawn_point = self::get_enemy_spawn_by_id( $parent_spawn_point_id ); if ( ! $parent_spawn_point ) { return; } $table_name = $wpdb->prefix . 'adventurebook_enemy_spawns'; // set the last_defeated to null so the enemy can spawn immediately $wpdb->insert( $table_name, array(// phpcs:ignore WordPress.DB.DirectDatabaseQuery 'code' => $parent_spawn_point->code, 'page_id' => $parent_spawn_point->page_id, 'respawn_time' => $parent_spawn_point->respawn_time, 'last_defeated' => null, 'character_id' => $character_id, 'parent_id' => $parent_spawn_point_id, 'alternate_page_ids' => $parent_spawn_point->alternate_page_ids, ) ); } public static function delete_enemy_spawn_points_by_code( $code ): void { global $wpdb; // first, get all spawn points with this code $spawns = $wpdb->get_results( $wpdb->prepare( "SELECT * FROM ".$wpdb->prefix . "adventurebook_enemy_spawns WHERE code = %s", $code ), ARRAY_A );// phpcs:ignore WordPress.DB.DirectDatabaseQuery foreach ( $spawns as $spawn ) { // delete each enemy for this spawn point Enemy::delete_enemies_for_spawn_id( $spawn['id'] ); self::delete_enemy_spawn_point( $spawn['id'] ); } } public static function delete_enemy_spawn_point( $id ): void { global $wpdb; // first, get all child spawn points (instances for characters) and delete their enemies $child_spawns = $wpdb->get_results( $wpdb->prepare( "SELECT * FROM ".$wpdb->prefix . "adventurebook_enemy_spawns WHERE parent_id = %d", $id ), ARRAY_A );// phpcs:ignore WordPress.DB.DirectDatabaseQuery // remove all enemy instances associated with these spawn points foreach ( $child_spawns as $child_spawn ) { Enemy::delete_enemies_for_spawn_id( $child_spawn['id'] ); } // then remove all enemies associated with this spawn point Enemy::delete_enemies_for_spawn_id( $id ); $table_name = $wpdb->prefix . 'adventurebook_enemy_spawns'; // delete all adventurebook_enemy_spawns with parent_id = $id (child spawn points) $wpdb->delete( $table_name, array( 'parent_id' => $id ) );// phpcs:ignore WordPress.DB.DirectDatabaseQuery // then delete the spawn point itself $wpdb->delete( $table_name, array( 'id' => $id ) );// phpcs:ignore WordPress.DB.DirectDatabaseQuery } public static function delete_enemy_spawn_points_by_character_id( $character_id ): void { global $wpdb; // remove all enemy instances associated with these spawn points $enemy_spawns = Enemy_Spawn::get_all_enemy_spawn_points( $character_id ); foreach ( $enemy_spawns as $enemy_spawn ) { Enemy::delete_enemies_for_spawn_id( $enemy_spawn->id ); } $table_name = $wpdb->prefix . 'adventurebook_enemy_spawns'; $wpdb->delete( $table_name, array( 'character_id' => $character_id ) );// phpcs:ignore WordPress.DB.DirectDatabaseQuery } /** * Delete all enemy spawn points that have no valid parent_id or character_id * * @return int Number of deleted spawn points */ public static function delete_orphaned_enemy_spawn_points(): int { global $wpdb; // get all enemy spawn points where parent_id does not exist in the table or where character_id is set but has no entry in the characters table $results = $wpdb->get_results( "SELECT es.id FROM ".$wpdb->prefix . "adventurebook_enemy_spawns es LEFT JOIN ".$wpdb->prefix . "adventurebook_characters c ON es.character_id = c.id WHERE (es.parent_id != 0 AND es.parent_id NOT IN (SELECT id FROM ".$wpdb->prefix . "adventurebook_enemy_spawns)) OR (es.character_id != 0 AND c.id IS NULL)", ARRAY_A );// phpcs:ignore WordPress.DB.DirectDatabaseQuery foreach ( $results as $result ) { // delete each enemy for this spawn point Enemy::delete_enemies_for_spawn_id( $result['id'] ); Enemy_Spawn::delete_enemy_spawn_point( $result['id'] ); } return count( $results ); } public static function get_all_enemy_spawn_points( $character_id = 0 ): array { global $wpdb; if ( $character_id > 0 ) { $results = $wpdb->get_results( $wpdb->prepare( "SELECT * FROM ".$wpdb->prefix . "adventurebook_enemy_spawns WHERE character_id = %d", $character_id ), ARRAY_A );// phpcs:ignore WordPress.DB.DirectDatabaseQuery } else { $results = $wpdb->get_results( "SELECT * FROM ".$wpdb->prefix . "adventurebook_enemy_spawns WHERE character_id = 0", ARRAY_A );// phpcs:ignore WordPress.DB.DirectDatabaseQuery } $enemy_spawns = array(); foreach ( $results as $result ) { $enemy_spawns[] = new Enemy_Spawn( $result['id'], $result['code'], $result['page_id'], $result['last_defeated'], $result['respawn_time'], $result['character_id'], $result['parent_id'], $result['alternate_page_ids'] ); } return $enemy_spawns; } /** * Check if any enemies need to be respawned for the given character. * This function should be called whenever a page is loaded or an action is taken. * * @param int $character_id The ID of the character to check for respawns. */ public static function check_for_respawns( int $character_id ): void { global $wpdb; // get all enemy spawns for this character and his current location reset them if respawn_time has passed $character = Character::get_character_by_id( $character_id ); if ( ! $character ) { return; } $enemy_spawns = Enemy_Spawn::get_enemy_spawns_by_page_id($character->location_id, $character_id); foreach ( $enemy_spawns as $enemy_spawn ) { /* @var $enemy_spawn Enemy_Spawn */ // check if enemy instance already exists for this spawn point // if it does, skip to the next spawn point if ( $enemy_spawn->is_enemy_spawned() ) { continue; } // if respawn_time is 0 and last_defeated is not NULL, the enemy was defeated and should not respawn if ( $enemy_spawn->respawn_time === 0 && ! empty( $enemy_spawn->last_defeated ) ) { continue; } $last_defeated = !empty($enemy_spawn->last_defeated) ? strtotime($enemy_spawn->last_defeated) : time() - $enemy_spawn->respawn_time - 1; // if last_defeated is NULL, set it to a time in the past so the enemy can respawn immediately $respawn_time = $enemy_spawn->respawn_time; if ($last_defeated + $respawn_time <= time()) { // respawn the enemy now, set last_defeated NULL $wpdb->update( $wpdb->prefix . "adventurebook_enemy_spawns", array( 'last_defeated' => null ), array( 'id' => $enemy_spawn->id ) );// phpcs:ignore WordPress.DB.DirectDatabaseQuery // spawn a new enemy instance for this character in the possible locations $possible_page_ids = $enemy_spawn->get_possible_spawn_locations_page_ids(); // choose one of the possible page IDs at random $spawn_page_id = $possible_page_ids[array_rand($possible_page_ids)]; // create the enemy instance $enemy_definition = Enemy_Definition::get_enemy_definition_by_code( $enemy_spawn->code ); Enemy::create_enemy( $enemy_definition->code, $enemy_definition->stats, $enemy_definition->is_hostile, $enemy_spawn->id , $spawn_page_id ); } } } public function is_enemy_spawned(): bool { $existing_enemies = Enemy::get_enemies_for_spawn_id( $this->id ); // if there is at least one enemy for this spawn point, return true if ( count( $existing_enemies ) > 0 ) { return true; } // otherwise return false return false; } public function update_last_defeated(): void { global $wpdb; $now = gmdate( 'Y-m-d H:i:s' ); $wpdb->update( $wpdb->prefix . "adventurebook_enemy_spawns", array( 'last_defeated' => $now ), array( 'id' => $this->id ) );// phpcs:ignore WordPress.DB.DirectDatabaseQuery } public function get_possible_spawn_locations_page_ids(): array { $possible_page_ids = array( $this->page_id ); if ( ! empty( $this->alternate_page_ids ) ) { $additional_page_ids = explode( ',', $this->alternate_page_ids ); foreach ( $additional_page_ids as $additional_page_id ) { $additional_page_id = intval( trim( $additional_page_id ) ); if ( $additional_page_id > 0 && ! in_array( $additional_page_id, $possible_page_ids, true ) ) { $possible_page_ids[] = $additional_page_id; } } } return $possible_page_ids; } /** * Get all pages where an enemy with the given code is spawned * * @param string $code * @return array Array of pages */ public static function get_all_pages_for_enemy_spawn_code(string $code): array { global $wpdb; $results = $wpdb->get_results($wpdb->prepare("SELECT page_id FROM ".$wpdb->prefix."adventurebook_enemy_spawns WHERE code = %s AND parent_id = 0", $code), ARRAY_A);// phpcs:ignore WordPress.DB.DirectDatabaseQuery $pages = array(); foreach ($results as $result) { // chedk if the page exists $page_id = intval($result['page_id']); // check if a page with this ID exists if (get_post_status( $page_id ) === false) { continue; } // check if the page is already in the list // check if the page is already in the array $found = false; foreach ($pages as $page) { if ($page->ID === $page_id) { $found = true; break; } } if ($found) { continue; } // get the page $pages[] = get_post( $page_id ); } return $pages; } /** * convert the given result to a list of enemy spawns * * @param array> $results * @return array */ public static function get_enemy_spawns_by_result($results): array { $enemy_spawns = array(); foreach ($results as $result) { $enemy_spawns[] = new Enemy_Spawn($result['id'], $result['code'], $result['page_id'], $result['last_defeated'], $result['respawn_time'], $result['character_id'], $result['parent_id'], $result['alternate_page_ids']); } return $enemy_spawns; } }