= 100 || $set['posY'] >= 100) { $set = null; return true; } if (empty(self::$alphaMapCache[$areaId])) self::$alphaMapCache[$areaId] = imagecreatefrompng($file); // alphaMaps are 1000 x 1000, adapt points [black => valid point] if (!imagecolorat(self::$alphaMapCache[$areaId], $set['posX'] * 10, $set['posY'] * 10)) $set = null; return true; } public static function checkZonePos(array $points) : array { $result = []; foreach ($points as $res) { if (self::alphaMapCheck($res['areaId'], $res)) { if (!$res) continue; // some rough measure how central the spawn is on the map (the lower the number, the better) // 0: perfect center; 1: touches a border $q = abs( (($res['posX'] - 50) / 50) * (($res['posY'] - 50) / 50) ); if (empty($result) || $result[0] > $q) $result = [$q, $res]; } // capitals (auto-discovered) and no hand-made alphaMap available else if (in_array($res['areaId'], self::$capitalCities)) return $res; // add with lowest quality if alpha map is missing else if (empty($result)) $result = [1.0, $res]; } // spawn does not really match on a map, but we need at least one result if (!$result) { usort($points, fn($a, $b) => $a['dist'] <=> $b['dist']); $result = [1.0, $points[0]]; } return $result[1]; } public static function getForGUID(int $type, int ...$guids) : array { $result = []; switch ($type) { case Type::NPC: $result = DB::World()->selectAssoc('SELECT `guid` AS ARRAY_KEY, `id`, `map` AS `mapId`, `position_x` AS `posX`, `position_y` AS `posY` FROM creature WHERE `guid` IN %in', $guids); break; case Type::OBJECT: $result = DB::World()->selectAssoc('SELECT `guid` AS ARRAY_KEY, `id`, `map` AS `mapId`, `position_x` AS `posX`, `position_y` AS `posY` FROM gameobject WHERE `guid` IN %in', $guids); break; case Type::SOUND: $result = DB::AoWoW()->selectAssoc('SELECT `id` AS ARRAY_KEY, `soundId` AS `id`, `mapId`, `posX`, `posY` FROM ::soundemitters WHERE `id` IN %in', $guids); break; case Type::ZONE: $result = DB::Aowow()->selectAssoc('SELECT -`id` AS ARRAY_KEY, `id`, `parentMapId` AS `mapId`, `parentX` AS `posX`, `parentY` AS `posY` FROM ::zones WHERE -`id` IN %in', $guids); break; case Type::AREATRIGGER: $result = []; if ($base = array_filter($guids, fn($x) => $x > 0)) $result = array_replace($result, DB::AoWoW()->selectAssoc('SELECT `id` AS ARRAY_KEY, `id`, `mapId`, `posX`, `posY` FROM ::areatrigger WHERE `id` IN %in', $base)); if ($endpoints = array_filter($guids, fn($x) => $x < 0)) $result = array_replace($result, DB::World()->selectAssoc( 'SELECT -`ID` AS ARRAY_KEY, ID AS `id`, `target_map` AS `mapId`, `target_position_x` AS `posX`, `target_position_y` AS `posY` FROM areatrigger_teleport WHERE -`id` IN %in UNION SELECT -`entryorguid` AS ARRAY_KEY, entryorguid AS `id`, `action_param1` AS `mapId`, `target_x` AS `posX`, `target_y` AS `posY` FROM smart_scripts WHERE -`entryorguid` IN %in AND `source_type` = %i AND `action_type` = %i', $endpoints, $endpoints, SmartAI::SRC_TYPE_AREATRIGGER, SmartAction::ACTION_TELEPORT )); break; default: trigger_error('WorldPosition::getForGUID - unsupported TYPE #'.$type, E_USER_WARNING); } if ($diff = array_diff($guids, array_keys($result))) trigger_error('WorldPosition::getForGUID - no spawn points for TYPE #'.$type.' GUIDS: '.implode(', ', $diff), E_USER_WARNING); return $result; } public static function toZonePos(int $mapId, float $mapX, float $mapY, int $preferedAreaId = 0, int $preferedFloor = -1) : array { if (!$mapId < 0) return []; if (!isset(self::$zoneMapCache[$mapId])) self::initZoneMaps($mapId); $points = []; for ($i = 0; $i < 2; $i++) { foreach (self::$zoneMapCache[$mapId] as $area) { if (!$i && $preferedAreaId != 0 && $area['areaId'] != $preferedAreaId) continue; if (!$i && $preferedFloor >= 0 && $area['floor'] != $preferedFloor) continue; if ($mapX < $area['minX'] || $mapX > $area['maxX'] || $mapY < $area['minY'] || $mapY > $area['maxY']) continue; // dist BETWEEN 0 (center) AND 70.7 (corner) $posX = round(($area['maxY'] - $mapY) * 100 / ($area['maxY'] - $area['minY']), 1); $posY = round(($area['maxX'] - $mapX) * 100 / ($area['maxX'] - $area['minX']), 1); $dist = sqrt(pow(abs($posX - 50), 2) + pow(abs($posY - 50), 2)); $points[] = array( 'id' => $area['id'], 'areaId' => $area['areaId'], 'floor' => $area['floor'], 'multifloor' => $area['multifloor'], 'srcPrio' => $area['srcPrio'], 'posX' => $posX, 'posY' => $posY, 'dist' => $dist ); } // retry: pre-instance subareas belong to the instance-maps but are displayed on the outside. There also cases where the zone reaches outside it's own map. if ($points) break; } // sort by srcPrio DESC (primary), dist ASC (secondary) usort($points, fn($a, $b) => ($b['srcPrio'] <=> $a['srcPrio']) ?: ($a['dist'] <=> $b['dist'])); return $points; } private static function initZoneMaps(int $mapId) : void { self::$zoneMapCache[$mapId] = DB::Aowow()->selectAssoc( 'SELECT x.`id`, x.`areaId`, x.`minX`, x.`maxX`, x.`minY`, x.`maxY`, IF(x.`defaultDungeonMapId` < 0, x.`floor` + 1, x.`floor`) AS `floor`, IF(useDM.`id` IS NOT NULL OR x.`defaultDungeonMapId` < 0, 1, 0) AS `srcPrio`, IF(multiDM.`id` IS NOT NULL OR x.`defaultDungeonMapId` < 0, 1, 0) AS `multifloor` FROM (SELECT 0 AS `id`, `areaId`, `mapId`, `right` AS `minY`, `left` AS `maxY`, `top` AS `maxX`, `bottom` AS `minX`, 0 AS `floor`, 0 AS `worldMapAreaId`, `defaultDungeonMapId` FROM aowow_worldmaparea wma UNION SELECT dm.`id`, `areaId`, wma.`mapId`, `minY`, `maxY`, `maxX`, `minX`, `floor`, `worldMapAreaId`, `defaultDungeonMapId` FROM aowow_worldmaparea wma JOIN aowow_dungeonmap dm ON dm.`mapId` = wma.`mapId` WHERE wma.`mapId` NOT IN (0, 1, 530, 571) OR wma.`areaId` = 4395) x LEFT JOIN aowow_dungeonmap useDM ON useDM.`mapId` = x.`mapId` AND useDM.`worldMapAreaId` = x.`worldMapAreaId` AND useDM.`floor` = x.`floor` AND useDM.`worldMapAreaId` > 0 LEFT JOIN aowow_dungeonmap multiDM ON multiDM.`mapId` = x.`mapId` AND multiDM.`worldMapAreaId` = x.`worldMapAreaId` AND multiDM.`floor` <> x.`floor` AND multiDM.`worldMapAreaId` > 0 WHERE x.`mapId` = %i AND x.`areaId` <> 0 AND x.`minX` <> 0 AND x.`maxX` <> 0 AND x.`minY` <> 0 AND x.`maxY` <> 0 GROUP BY x.`id`, x.`areaId`', $mapId ) ?: []; } } ?>