[[], CLISetup::ARGV_PARAM, 'Compiles spawns/waypoints for type: GObject, NPC, Sound & Areatrigger from dbc and world db.'] ); protected $dbcSourceFiles = ['worldmaparea', 'map', 'taxipathnode', 'soundemitters', 'areatrigger', 'areatable']; protected $worldDependency = ['creature', 'creature_addon', 'gameobject', 'gameobject_template', 'vehicle_accessory', 'vehicle_accessory_template', 'script_waypoint', 'waypoints', 'waypoint_data']; protected $setupAfter = [['dungeonmap', 'worldmaparea', 'zones'], ['img-maps']]; private $querys = array( 1 => ['SELECT c.`guid`, 1 AS `type`, c.`id` AS `typeId`, c.`spawntimesecs` AS `respawn`, c.`phaseMask`, c.`zoneId` AS `areaId`, c.`map`, IFNULL(ca.`path_id`, 0) AS `pathId`, c.`position_y` AS `posX`, c.`position_x` AS `posY` ' . 'FROM creature c LEFT JOIN creature_addon ca ON ca.guid = c.guid', '`creature` spawns', Type::NPC], 2 => ['SELECT c.`guid`, 2 AS `type`, c.`id` AS `typeId`, c.`spawntimesecs` AS `respawn`, c.`phaseMask`, c.`zoneId` AS `areaId`, c.`map`, 0 AS `pathId`, c.`position_y` AS `posX`, c.`position_x` AS `posY` ' . 'FROM gameobject c', '`gameobject` spawns', Type::OBJECT], 3 => ['SELECT `id` AS `guid`, 19 AS `type`, `soundId` AS `typeId`, 0 AS `respawn`, 0 AS `phaseMask`, 0 AS `areaId`, `mapId` AS `map`, 0 AS `pathId`, `posX`, `posY` ' . 'FROM dbc_soundemitters', 'SoundEmitters.dbc spawns', Type::SOUND], 4 => ['SELECT `id` AS `guid`, 503 AS `type`, `id` AS `typeId`, 0 AS `respawn`, 0 AS `phaseMask`, 0 AS `areaId`, `mapId` AS `map`, 0 AS `pathId`, `posX`, `posY` ' . 'FROM dbc_areatrigger', 'AreaTrigger.dbc spawns', Type::AREATRIGGER], 5 => ['SELECT c.`guid`, w.`entry` AS `npcOrPath`, w.`pointId` AS `point`, c.`zoneId` AS `areaId`, c.`map`, w.`waittime` AS `wait`, w.`location_y` AS `posX`, w.`location_x` AS `posY` ' . 'FROM creature c JOIN script_waypoint w ON c.`id` = w.`entry`', '`script_waypoint`', Type::NPC], 6 => ['SELECT c.`guid`, w.`entry` AS `npcOrPath`, w.`pointId` AS `point`, c.`zoneId` AS `areaId`, c.`map`, 0 AS `wait`, w.`position_y` AS `posX`, w.`position_x` AS `posY` ' . 'FROM creature c JOIN waypoints w ON c.`id` = w.`entry`', '`waypoints`', Type::NPC], 7 => ['SELECT c.`guid`, -w.`id` AS `npcOrPath`, w.`point`, c.`zoneId` AS `areaId`, c.`map`, w.`delay` AS `wait`, w.`position_y` AS `posX`, w.`position_x` AS `posY` ' . 'FROM creature c JOIN creature_addon ca ON ca.`guid` = c.`guid` JOIN waypoint_data w ON w.`id` = ca.`path_id` WHERE ca.`path_id` <> 0', '`waypoint_data`', Type::NPC] ); public function generate(array $ids = []) : bool { /*********************/ /* truncate old data */ /*********************/ DB::Aowow()->query('TRUNCATE TABLE ?_spawns'); DB::Aowow()->query('TRUNCATE TABLE ?_creature_waypoints'); /**************************/ /* offsets for transports */ /**************************/ $transports = DB::World()->selectCol('SELECT `data0` AS `pathId`, `data6` AS ARRAY_KEY FROM gameobject_template WHERE `type` = ?d AND `data6` <> 0', OBJECT_MO_TRANSPORT); foreach ($transports as &$t) $t = DB::Aowow()->selectRow('SELECT `posX`, `posY`, `mapId` FROM dbc_taxipathnode tpn WHERE tpn.`pathId` = ?d AND `nodeIdx` = 0', $t); /*********************/ /* get override data */ /*********************/ $overrideData = DB::Aowow()->select('SELECT `type` AS ARRAY_KEY, `typeGuid` AS ARRAY_KEY2, `areaId`, `floor` FROM ?_spawns_override'); /**************/ /* perform... */ /**************/ foreach ($this->querys as $idx => $q) { $time = new Timer(500); $sum = 0; if ($idx == 3 || $idx == 4) $queryResult = DB::Aowow()->select($q[0]); else $queryResult = DB::World()->select($q[0]); $qryTotal = count($queryResult); $qtLen = strlen($qryTotal); $doneGUID = 0; foreach ($queryResult as $spawn) { $sum++; if ($time->update()) CLI::write(' * '.$idx.'/'.count($this->querys).': '. CLI::bold($q[1]).' - '.sprintf('%'.$qtLen.'d / %d (%4.1f%%)', $sum, $qryTotal, round(100 * $sum / $qryTotal, 1)), CLI::LOG_BLANK, true, true); // npc/object is on a transport -> apply offsets to path of transport // note, that the coordinates are mixed up .. again // also note, that transport DO spawn outside of displayable area maps .. another todo i guess.. if (isset($transports[$spawn['map']])) { $spawn['posX'] += $transports[$spawn['map']]['posY']; $spawn['posY'] += $transports[$spawn['map']]['posX']; $spawn['map'] = $transports[$spawn['map']]['mapId']; } $area = $spawn['areaId']; $floor = -1; if (isset($overrideData[$q[2]][$spawn['guid']])) { $area = $overrideData[$q[2]][$spawn['guid']]['areaId']; $floor = $overrideData[$q[2]][$spawn['guid']]['floor']; if ($doneGUID != $spawn['guid']) { CLI::write('[spawns] '.str_pad('['.$spawn['guid'].']', 9).' manually moved to [A:'.$spawn['areaId'].' => '.$area.'; F: '.$floor.']', CLI::LOG_INFO); $time->reset(); $doneGUID = $spawn['guid']; // do not spam on waypoints } } $points = Game::worldPosToZonePos($spawn['map'], $spawn['posX'], $spawn['posY'], $area, $floor); // cannot be placed, but we know the instanced map and can thus at least assign a zone if (!$points && !in_array($spawn['map'], [0, 1, 530, 571]) && $idx < 5) { // use server calculated areaId if possible $area = $area ?: DB::Aowow()->selectCell('SELECT `id` FROM ?_zones WHERE `mapId` = ?d AND `parentArea` = 0 AND (`cuFlags` & ?d) = 0', $spawn['map'], CUSTOM_EXCLUDE_FOR_LISTVIEW); if (!$area) { CLI::write('[spawns] '.str_pad('['.$spawn['guid'].']', 9).' could not be defaulted to instanced area by mapId [M:'.$spawn['map'].']', CLI::LOG_WARN); $time->reset(); continue; } $final = ['areaId' => $area, 'posX' => 0, 'posY' => 0, 'floor' => 0]; } else if (!$points) // still impossible (there are areas that are intentionally off the map (e.g. the isles south of tanaris)) { CLI::write('[spawns] '.str_pad('['.$spawn['guid'].']', 9).' '.($idx < 5 ? '' : 'with path/point ['.$spawn['npcOrPath'].'; '.$spawn['point'].'] ').'could not be matched to displayable area [A:'.$area.'; X:'.$spawn['posY'].'; Y:'.$spawn['posX'].']', CLI::LOG_WARN); $time->reset(); continue; } else // if areaId is set, area was determined by TC .. we're fine .. mostly $final = $area ? $points[0] : Game::checkCoords($points); if ($idx < 5) { $set = array( 'guid' => $spawn['guid'], 'type' => $spawn['type'], 'typeId' => $spawn['typeId'], 'respawn' => $spawn['respawn'], 'phaseMask' => $spawn['phaseMask'], 'pathId' => $spawn['pathId'], 'areaId' => $final['areaId'], 'floor' => $final['floor'], 'posX' => $final['posX'], 'posY' => $final['posY'] ); DB::Aowow()->query('REPLACE INTO ?_spawns (?#) VALUES (?a)', array_keys($set), array_values($set)); } else { $set = array( 'creatureOrPath' => $spawn['npcOrPath'], 'point' => $spawn['point'], 'wait' => $spawn['wait'], 'areaId' => $final['areaId'], 'floor' => $final['floor'], 'posX' => $final['posX'], 'posY' => $final['posY'] ); // REPLACE: because there is bogus data where one path may be assigned to multiple npcs DB::Aowow()->query('REPLACE INTO ?_creature_waypoints (?#) VALUES (?a)', array_keys($set), array_values($set)); } } } /*****************************/ /* spawn vehicle accessories */ /*****************************/ // get vehicle template accessories $accessories = DB::World()->select( 'SELECT vta.`accessory_entry` AS `typeId`, c.`guid`, vta.`entry`, COUNT(1) AS `nSeats` FROM vehicle_template_accessory vta LEFT JOIN creature c ON c.`id` = vta.`entry` GROUP BY `accessory_entry`, c.`guid` UNION SELECT va.`accessory_entry` AS `typeId`, va.`guid`, 0 AS `entry`, COUNT(1) AS `nSeats` FROM vehicle_accessory va GROUP BY `accessory_entry`, va.`guid`' ); // accessories may also be vehicles (e.g. "Kor'kron Infiltrator" is seated on "Kor'kron Suppression Turret" is seated on "Kor'kron Troop Transport") // so we will retry finding a spawned vehicle if none were found on the previous pass and a change occured $vGuid = 0; // not really used, but we need some kind of index $n = 0; $matches = -1; while ($matches) { $matches = 0; foreach ($accessories as $idx => $data) { $vehicles = []; if ($data['guid']) // vehicle already spawned $vehicles = DB::Aowow()->select('SELECT s.`areaId`, s.`posX`, s.`posY`, s.`floor` FROM ?_spawns s WHERE s.`guid` = ?d AND s.`type` = ?d', $data['guid'], Type::NPC); else if ($data['entry']) // vehicle on unspawned vehicle action $vehicles = DB::Aowow()->select('SELECT s.`areaId`, s.`posX`, s.`posY`, s.`floor` FROM ?_spawns s WHERE s.`typeId` = ?d AND s.`type` = ?d', $data['entry'], Type::NPC); if ($vehicles) { $matches++; foreach ($vehicles as $v) // if there is more than one vehicle, its probably due to overlapping zones for ($i = 0; $i < $data['nSeats']; $i++) DB::Aowow()->query('INSERT INTO ?_spawns (`guid`, `type`, `typeId`, `respawn`, `spawnMask`, `phaseMask`, `areaId`, `floor`, `posX`, `posY`, `pathId`) VALUES (?d, ?d, ?d, 0, 0, 1, ?d, ?d, ?f, ?f, 0)', --$vGuid, Type::NPC, $data['typeId'], $v['areaId'], $v['floor'], $v['posX'], $v['posY']); unset($accessories[$idx]); } } if ($matches) CLI::write(' * assigned '.$matches.' accessories on '.++$n.'. pass on vehicle accessories', CLI::LOG_BLANK, true, true); } if ($accessories) CLI::write('[spawns] - '.count($accessories).' accessories could not be fitted onto a spawned vehicle.', CLI::LOG_WARN); /********************************/ /* restrict difficulty displays */ /********************************/ DB::Aowow()->query('UPDATE ?_spawns s, dbc_worldmaparea wma, dbc_map m SET s.`spawnMask` = 0 WHERE s.`areaId` = wma.`areaId` AND wma.`mapId` = m.`id` AND m.`areaType` IN (0, 3, 4)'); return true; } }); ?>