id = $id; $this->code = $code; $this->page_id = $page_id; $this->respawn_time = $respawn_time; $this->last_spawned = $last_spawned; $this->data = $data; $this->character_id = $character_id; $this->parent_id = $parent_id; } public function get_item_definition(): ?Item_Definition { return Item_Definition::get_item_definition_by_code( $this->code ); } public function save(): void { global $wpdb; $table_name = $wpdb->prefix . 'adventurebook_item_spawns'; // if id is 0, insert a new row if ( $this->id === 0 ) { $wpdb->insert( $table_name, array(// phpcs:ignore WordPress.DB.DirectDatabaseQuery 'code' => $this->code, 'page_id' => $this->page_id, 'respawn_time' => $this->respawn_time, 'last_spawned' => $this->last_spawned, 'data' => $this->data, 'character_id' => $this->character_id, 'parent_id' => $this->parent_id, ) ); $this->id = $wpdb->insert_id; return; } $wpdb->update( $table_name, array(// phpcs:ignore WordPress.DB.DirectDatabaseQuery 'code' => $this->code, 'page_id' => $this->page_id, 'respawn_time' => $this->respawn_time, 'last_spawned' => $this->last_spawned, 'data' => $this->data, 'character_id' => $this->character_id, 'parent_id' => $this->parent_id, ), array( 'id' => $this->id ) ); } public static function get_item_spawn_by_id( $id ): ?Item_Spawn { global $wpdb; $result = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM " . $wpdb->prefix . "adventurebook_item_spawns WHERE id = %d", $id ), ARRAY_A );// phpcs:ignore WordPress.DB.DirectDatabaseQuery if ( $result === null ) { return null; } return new Item_Spawn( $result['id'], $result['code'], $result['page_id'], $result['respawn_time'], $result['last_spawned'], $result['data'], $result['character_id'], $result['parent_id'] ); } public static function get_item_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_item_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_item_spawns WHERE page_id = %d AND character_id = 0", $page_id ), ARRAY_A );// phpcs:ignore WordPress.DB.DirectDatabaseQuery } $item_spawns = array(); foreach ( $results as $result ) { $item_spawns[] = new Item_Spawn( $result['id'], $result['code'], $result['page_id'], $result['respawn_time'], $result['last_spawned'], $result['data'], $result['character_id'], $result['parent_id'] ); } return $item_spawns; } public static function create_item_spawn_point( $character_id, $parent_spawn_point_id ): void { global $wpdb; // get the parent spawn point $parent_spawn_point = self::get_item_spawn_by_id( $parent_spawn_point_id ); if ( ! $parent_spawn_point ) { return; } $table_name = $wpdb->prefix . 'adventurebook_item_spawns'; // set the last_spawned to null so the item 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_spawned' => null, 'data' => $parent_spawn_point->data, 'character_id' => $character_id, 'parent_id' => $parent_spawn_point_id, ) ); } public function is_item_spawned(): bool { $existing_items = Item::get_items_for_spawn_id( $this->id ); // if there is at least one item for this spawn point, return true if ( count( $existing_items ) > 0 ) { return true; } // otherwise return false return false; } public function update_last_spawned(): void { global $wpdb; $now = gmdate( 'Y-m-d H:i:s' ); $wpdb->update( $wpdb->prefix . "adventurebook_item_spawns", array( 'last_spawned' => $now ), array( 'id' => $this->id ) );// phpcs:ignore WordPress.DB.DirectDatabaseQuery } /** * Check if any items 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 item 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; } $item_spawns = Item_Spawn::get_item_spawns_by_page_id( $character->location_id, $character_id ); foreach ( $item_spawns as $item_spawn ) { /* @var $item_spawn Item_Spawn */ // check if item instance already exists for this spawn point // if it does, skip to the next spawn point // (this means that the character has not picked up the item yet) if ( $item_spawn->is_item_spawned() ) { continue; } // if respawn_time is 0 and last_spawned is not NULL, the item was picked up and should not respawn if ( $item_spawn->respawn_time === 0 && ! empty( $item_spawn->last_spawned ) ) { continue; } $last_spawned = ! empty( $item_spawn->last_spawned ) ? strtotime( $item_spawn->last_spawned ) : time() - $item_spawn->respawn_time - 1; // if last_spawned is NULL, set it to a time in the past so the enemy can respawn immediately $respawn_time = $item_spawn->respawn_time; if ( $last_spawned + $respawn_time <= time() ) { // respawn the item now, set last_spawned NULL $wpdb->update( $wpdb->prefix . "adventurebook_item_spawns", array( 'last_spawned' => null ), array( 'id' => $item_spawn->id ) );// phpcs:ignore WordPress.DB.DirectDatabaseQuery // create the item instance $item_definition = $item_spawn->get_item_definition(); $item = Item::create_item( $character_id, $item_spawn->code, $item_spawn->page_id, null, $item_spawn->id ); // if the item code is "money", and data is set, use data as min;max for random money amount if ( $item_definition && $item_definition->code === 'money' && ! empty( $item_spawn->data ) ) { $parts = explode( ';', $item_spawn->data ); // if data has only one part, use it as fixed amount if ( count( $parts ) === 1 ) { $amount = intval( $parts[0] ); $item->stats = array( 'Charges' => $amount ); $item->save_to_db(); } // if data has two parts, use them as min and max for random amount if ( count( $parts ) === 2 ) { $min = intval( $parts[0] ); $max = intval( $parts[1] ); if ( $min < $max ) { $amount = wp_rand( $min, $max ); $item->stats = array( 'Charges' => $amount ); $item->save_to_db(); } } } } } } /** * Create a new item spawn point and create item spawn points for each character in the same adventurebook * * @param string $code * @param int $page_id * @param int $respawn_time * @param string $data * * @return bool */ public static function create_item_spawn( string $code, int $page_id, int $respawn_time = 0, string $data = "" ): bool { global $wpdb; $table_name = $wpdb->prefix . 'adventurebook_item_spawns'; $result = $wpdb->insert( $table_name, array(// phpcs:ignore WordPress.DB.DirectDatabaseQuery 'code' => $code, 'page_id' => $page_id, 'respawn_time' => $respawn_time, 'data' => $data, ) ); // now create an item_spawn for each character in the same adventurebook with parent_id set to the newly created spawn point if ( $result !== false ) { $insert_id = $wpdb->insert_id; $item_spawn = self::get_item_spawn_by_id( $insert_id ); if ( $item_spawn !== null ) { // get all characters in the same adventurebook $characters = Character::get_characters_by_adventurebook_id( wp_get_post_parent_id( $page_id ) ); foreach ( $characters as $character ) { Item_Spawn::create_item_spawn_point( $character->id, $item_spawn->id ); } } } return $result; } public static function delete_item_spawn_point( $id ): void { global $wpdb; // first, get all child spawn points (instances for characters) and delete their items $child_spawns = $wpdb->get_results( $wpdb->prepare( "SELECT * FROM " . $wpdb->prefix . "adventurebook_item_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 ) { Item::delete_items_for_spawn_id( $child_spawn['id'] ); } // then remove all items associated with this spawn point Item::delete_items_for_spawn_id( $id ); $table_name = $wpdb->prefix . 'adventurebook_item_spawns'; // delete all adventurebook_item_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 } /** * Delete all item spawn points that have no valid parent_id or character_id * * @return int Number of deleted spawn points */ public static function delete_orphaned_item_spawn_points(): int { global $wpdb; // get all item spawn points where parent_id does not exist in the table and is > 0 or where character_id is set but has no entry in the characters table $results = $wpdb->get_results( "SELECT * FROM " . $wpdb->prefix . "adventurebook_item_spawns WHERE (parent_id > 0 AND parent_id NOT IN (SELECT id FROM " . $wpdb->prefix . "adventurebook_item_spawns)) OR (character_id > 0 AND character_id NOT IN (SELECT id FROM " . $wpdb->prefix . "adventurebook_characters))", ARRAY_A );// phpcs:ignore WordPress.DB.DirectDatabaseQuery foreach ( $results as $result ) { // delete each item for this spawn point Item::delete_items_for_spawn_id( $result['id'] ); self::delete_item_spawn_point( $result['id'] ); } return count( $results ); } /** Get all item spawn points, optionally filtered by character ID * * @param int $character_id * * @return Item_Spawn[] */ public static function get_all_item_spawn_points( $character_id = 0 ): array { global $wpdb; if ( $character_id > 0 ) { $results = $wpdb->get_results( $wpdb->prepare( "SELECT * FROM " . $wpdb->prefix . "adventurebook_item_spawns WHERE character_id = %d", $character_id ), ARRAY_A );// phpcs:ignore WordPress.DB.DirectDatabaseQuery } else { $results = $wpdb->get_results( "SELECT * FROM " . $wpdb->prefix . "adventurebook_item_spawns WHERE character_id = 0", ARRAY_A );// phpcs:ignore WordPress.DB.DirectDatabaseQuery } $item_spawns = array(); foreach ( $results as $result ) { $item_spawns[] = new Item_Spawn( $result['id'], $result['code'], $result['page_id'], $result['respawn_time'], $result['last_spawned'], $result['data'], $result['character_id'], $result['parent_id'] ); } return $item_spawns; } public static function get_all_item_spawns(): array { global $wpdb; $results = $wpdb->get_results( "SELECT * FROM " . $wpdb->prefix . "adventurebook_item_spawns", ARRAY_A );// phpcs:ignore WordPress.DB.DirectDatabaseQuery $item_spawns = array(); foreach ( $results as $result ) { $item_spawns[] = new Item_Spawn( $result['id'], $result['code'], $result['page_id'] ); } return $item_spawns; } /** * Get all pages where an item with the given code is spawned * * @param string $code * * @return array Array of pages */ public static function get_all_pages_for_item_spawn_code( string $code ): array { global $wpdb; $results = $wpdb->get_results( $wpdb->prepare( "SELECT page_id FROM " . $wpdb->prefix . "adventurebook_item_spawns WHERE code = %s", $code ), ARRAY_A );// phpcs:ignore WordPress.DB.DirectDatabaseQuery $pages = array(); foreach ( $results as $result ) { // check if a page with this ID exists $page_id = intval( $result['page_id'] ); if ( get_post_status( $page_id ) === false ) { continue; } // 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; } public static function get_item_spawns_by_result( $results ): array { $item_spawns = array(); foreach ( $results as $result ) { $item_spawns[] = new Item_Spawn( $result['id'], $result['code'], $result['page_id'] ); } return $item_spawns; } }