* @version 0.0.0 * @copyright Copyright (c) 2015, 2016 City Developer Team * @license GNU GPL 3.0 or any newer version * @link http://www.shipsimu.org * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ class CitySectionsDatabaseWrapper extends BaseDatabaseWrapper implements CitySectionsWrapper, Registerable { // Constants for database table names const DB_TABLE_CITY_SECTIONS = 'city_sections'; // Entry id const DB_COLUMN_ENTRY_ID = 'entry_id'; // Section id, an referenced city id and lot id const DB_COLUMN_SECTION_ID = 'city_section_id'; const DB_COLUMN_CITY_ID = 'city_id'; /* * Lot id, the lot id is only set if the player has "aquired" the sections * and has created a lot based on these sections. */ const DB_COLUMN_LOT_ID = 'lot_id'; // Section and sub type (e.g. residential, hut) const DB_COLUMN_SECTION_TYPE = 'section_type'; const DB_COLUMN_SECTION_SUB_TYPE = 'section_sub_type'; // X-Y-Z position const DB_COLUMN_SECTION_POSITION_X = 'section_position_x'; const DB_COLUMN_SECTION_POSITION_Y = 'section_position_y'; const DB_COLUMN_SECTION_POSITION_Z = 'section_position_z'; // Connected neigbouring sections const DB_COLUMN_SECTION_NEIGHBOUR_WEST_ID = 'section_neighbour_west_id'; const DB_COLUMN_SECTION_NEIGHBOUR_EAST_ID = 'section_neighbour_east_id'; const DB_COLUMN_SECTION_NEIGHBOUR_NORTH_ID = 'section_neighbour_north_id'; const DB_COLUMN_SECTION_NEIGHBOUR_SOUTH_ID = 'section_neighbour_south_id'; const DB_COLUMN_SECTION_NEIGHBOUR_UP_ID = 'section_neighbour_up_id'; const DB_COLUMN_SECTION_NEIGHBOUR_DOWN_ID = 'section_neighbour_down_id'; // Other settings: // Reserved section (see documentation) const DB_COLUMN_SECTION_RESERVED = 'section_reserved'; // Section types // @TODO "water" is not yet supported and may end up in a very random land. const SECTION_TYPE_WATER = 'water'; const SECTION_TYPE_EMPTY_LAND = 'land'; const SECTION_TYPE_AIR = 'air'; // Sub sections // @TODO All types of water are not supported yet. const SECTION_SUB_TYPE_GRASS = 'grass'; const SECTION_SUB_TYPE_AIR = 'air'; // Reserved flag const IS_NOT_RESERVED = 0; const IS_RESERVED = 1; /** * Protected constructor * * @return void */ protected function __construct () { // Call parent constructor parent::__construct(__CLASS__); } /** * Creates an instance of this database wrapper by a provided user class * * @return $wrapperInstance An instance of the created wrapper class */ public static final function createCitySectionsDatabaseWrapper () { // Get a new instance $wrapperInstance = new CitySectionsDatabaseWrapper(); // Set (primary!) table name $wrapperInstance->setTableName(self::DB_TABLE_CITY_SECTIONS); // Return the instance return $wrapperInstance; } /** * Checks if the given city id is found in sections table * * @param $cityId City id to check * @return $isFound Whether the city id is found */ public function ifCityHasSections ($cityId) { // Get search instance $searchInstance = ObjectFactory::createObjectByConfiguredName('search_criteria_class'); // Search for 'city_id' $searchInstance->addCriteria(self::DB_COLUMN_CITY_ID, $cityId); /* * Only one entry is enough to find, else this query could run very * long on large maps. */ $searchInstance->setLimit(1); // Execute it on database instance $resultInstance = $this->doSelectByCriteria($searchInstance); // Check if there is one entry $isFound = $resultInstance->next(); // Return result return $isFound; } /** * Expands the sections table with initial data for given city id * * @param $cityId City id to check * @return $ids Sections ids from initial expansion * @todo Add handling of water types to make a more cooler map */ public function doInitialCityExpansion ($cityId) { // Make sure this city has no sections assert(!$this->ifCityHasSections($cityId)); /* * "Cache" max "radius" for initial city expansion. It is not a real * radius but more a max expansion area (expand +- half of "radius" * from 0/0). The "zero point" is always calculated in. * * This gives a maxium initial area calculated as follows: * * totalInitialSections = (radius + 1) * (radius + 1) */ $radius = $this->getConfigInstance()->getConfigEntry('city_max_initial_xy_expansion_radius'); // Max up and down ... $maxUp = $this->getConfigInstance()->getConfigEntry('city_max_initial_up_expansion'); $maxDown = $this->getConfigInstance()->getConfigEntry('city_max_initial_down_expansion'); // Calculate total sections $totalSections = (($radius + 1) * ($radius + 1) * $maxUp * ($maxDown + 1)); // Debug message: /* DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput('[' . __METHOD__ . ':' . __LINE__ . ']: radius=' . $radius . ',maxUp=' . $maxUp . ',maxDown=' . $maxDown . ',totalSections=' . $totalSections); // Get data set instance $dataSetInstance = ObjectFactory::createObjectByConfiguredName('dataset_criteria_class', array(self::DB_TABLE_CITY_SECTIONS)); // Add values for "zero point" $dataSetInstance->addCriteria(self::DB_COLUMN_SECTION_ID , 1); $dataSetInstance->addCriteria(self::DB_COLUMN_CITY_ID , $cityId); $dataSetInstance->addCriteria(self::DB_COLUMN_LOT_ID , 0); $dataSetInstance->addCriteria(self::DB_COLUMN_SECTION_TYPE , self::SECTION_TYPE_AIR); $dataSetInstance->addCriteria(self::DB_COLUMN_SECTION_SUB_TYPE , self::SECTION_SUB_TYPE_AIR); $dataSetInstance->addCriteria(self::DB_COLUMN_SECTION_POSITION_X , 0); $dataSetInstance->addCriteria(self::DB_COLUMN_SECTION_POSITION_Y , 0); $dataSetInstance->addCriteria(self::DB_COLUMN_SECTION_POSITION_Z , 0); $dataSetInstance->addCriteria(self::DB_COLUMN_SECTION_NEIGHBOUR_NORTH_ID, 0); $dataSetInstance->addCriteria(self::DB_COLUMN_SECTION_NEIGHBOUR_SOUTH_ID, 0); $dataSetInstance->addCriteria(self::DB_COLUMN_SECTION_NEIGHBOUR_WEST_ID , 0); $dataSetInstance->addCriteria(self::DB_COLUMN_SECTION_NEIGHBOUR_EAST_ID , 0); $dataSetInstance->addCriteria(self::DB_COLUMN_SECTION_NEIGHBOUR_UP_ID , 0); $dataSetInstance->addCriteria(self::DB_COLUMN_SECTION_NEIGHBOUR_DOWN_ID , 0); $dataSetInstance->addCriteria(self::DB_COLUMN_SECTION_RESERVED , self::IS_NOT_RESERVED); // Set primary key to 'city_id'/'section_id' $dataSetInstance->setPrimaryKeyCombined(array(self::DB_COLUMN_CITY_ID, self::DB_COLUMN_SECTION_ID)); // Entry id for counting each entry $dataSetInstance->setUniqueKey(self::DB_COLUMN_ENTRY_ID); // Add "zero point" $this->queryInsertDataSet($dataSetInstance); // Set section id to 2 as 1 is already initialized + init array $sections = array(); // Output message to ask for user's patience ... self::createDebugInstance(__CLASS__)->debugOutput('[' . __METHOD__ . ':' . __LINE__ . ']: Writing ' . $totalSections . ' sections for city ' . $cityId . ' ... (this may takes some time)'); // Expand half of it to north/south (north=positive, south=negative) for ($north = 1; $north < round($radius / 2); $north++) { // Expand half of it to west/east (west=positive, east=negative) for ($west = 1; $west < round($radius / 2); $west++) { // Expand up/down (including "zero point") for ($z = $maxDown; $z < ($maxUp + 1); $z++) { // Debug message //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput('[' . __METHOD__ . ':' . __LINE__ . ']: north=' . $north . ',west=' . $west . ',z=' . $z); // Fill array up with south/east/down ids $sections[($north * -1)][($west * -1)][$z]['type'] = self::SECTION_TYPE_EMPTY_LAND; $sections[($north * -1)][($west * -1)][$z]['sub'] = self::SECTION_SUB_TYPE_GRASS; // Fill up array with north/west/up ids (only air) $sections[$north][$west][$z]['type'] = self::SECTION_TYPE_AIR; $sections[$north][$west][$z]['sub'] = self::SECTION_SUB_TYPE_AIR; } // END - for } // END - for } // END - for // Init section id with 2 as 1 is the "zero point" $sectionId = 2; // Loop through whole array again for writing it to database: north/south foreach ($sections as $x => $sectionX) { // Loop through west/east values foreach ($sectionX as $y => $sectionY) { // Loop through up/down values foreach ($sectionY as $z => $sectionData) { // Debug message //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput('[' . __METHOD__ . ':' . __LINE__ . ']: x=' . $x . ',y=' . $y . ',z=' . $z . ',sectionId=' . $sectionId); // Set all coordinates for positive directions $dataSetInstance->setCriteria(self::DB_COLUMN_SECTION_ID , $sectionId); $dataSetInstance->setCriteria(self::DB_COLUMN_SECTION_POSITION_X, $x); $dataSetInstance->setCriteria(self::DB_COLUMN_SECTION_POSITION_Y, $y); $dataSetInstance->setCriteria(self::DB_COLUMN_SECTION_POSITION_Z, $z); $dataSetInstance->setCriteria(self::DB_COLUMN_SECTION_TYPE , $sectionData['type']); $dataSetInstance->setCriteria(self::DB_COLUMN_SECTION_SUB_TYPE , $sectionData['sub']); // Add value to database $this->queryInsertDataSet($dataSetInstance); // Count id up $sectionId++; } // END - foreach // Debug message //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput('[' . __METHOD__ . ':' . __LINE__ . ']: x=' . $x . ',y=' . $y . ' has been written.'); } // END - foreach // Debug message //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput('[' . __METHOD__ . ':' . __LINE__ . ']: x=' . $x . ' has been written.'); } // END - foreach // Thank you for waiting! :-) self::createDebugInstance(__CLASS__)->debugOutput('[' . __METHOD__ . ':' . __LINE__ . ']: A total of ' . $totalSections . ' sections has been written for city id ' . $cityId . '.'); } }