Maps/Spawns

* Entities (Objects, NPCs, ect) can now easily be assigned to a different map to be displayed on by clicking their pip on the map
 * Entities with already assigned area (by TrinityCore) that were unable to be matched onto the map are no longer discarded. They'll now show up in appropriate listviews.
 * Entities without already assigned area that are also unable to be matched onto the map now get an area assigned as long as the relationship areaId <=> mapId is unique (read instanced areas)
This commit is contained in:
Sarjuuk
2020-05-26 19:51:53 +02:00
parent 7db841b823
commit 62acd541b2
20 changed files with 542 additions and 72 deletions

View File

@@ -55,6 +55,24 @@ class AjaxHandler
return $this->contentType;
}
protected function reqPOST(string ...$keys) : bool
{
foreach ($keys as $k)
if (!isset($this->_post[$k]) || $this->_post[$k] === null || $this->_post[$k] === '')
return false;
return true;
}
protected function reqGET(string ...$keys) : bool
{
foreach ($keys as $k)
if (!isset($this->_get[$k]) || $this->_get[$k] === null || $this->_get[$k] === '')
return false;
return true;
}
protected function checkEmptySet(string $val) : bool
{
return $val === ''; // parameter is expected to be empty

View File

@@ -5,7 +5,7 @@ if (!defined('AOWOW_REVISION'))
class AjaxAdmin extends AjaxHandler
{
protected $validParams = ['screenshots', 'siteconfig', 'weight-presets'];
protected $validParams = ['screenshots', 'siteconfig', 'weight-presets', 'spawn-override'];
protected $_get = array(
'action' => [FILTER_SANITIZE_STRING, FILTER_FLAG_STRIP_LOW | FILTER_FLAG_STRIP_HIGH ],
'id' => [FILTER_CALLBACK, ['options' => 'AjaxHandler::checkIdListUnsigned']],
@@ -14,11 +14,14 @@ class AjaxAdmin extends AjaxHandler
'type' => [FILTER_CALLBACK, ['options' => 'AjaxHandler::checkInt'] ],
'typeid' => [FILTER_CALLBACK, ['options' => 'AjaxHandler::checkInt'] ],
'user' => [FILTER_CALLBACK, ['options' => 'AjaxAdmin::checkUser'] ],
'val' => [FILTER_CALLBACK, ['options' => 'AjaxHandler::checkFulltext'] ]
'val' => [FILTER_CALLBACK, ['options' => 'AjaxHandler::checkFulltext'] ],
'guid' => [FILTER_CALLBACK, ['options' => 'AjaxHandler::checkInt'] ],
'area' => [FILTER_CALLBACK, ['options' => 'AjaxHandler::checkInt'] ],
'floor' => [FILTER_CALLBACK, ['options' => 'AjaxHandler::checkInt'] ]
);
protected $_post = array(
'alt' => [FILTER_SANITIZE_STRING, FILTER_FLAG_STRIP_LOW ],
'id' => [FILTER_SANITIZE_NUMBER_INT, null ],
'id' => [FILTER_CALLBACK, ['options' => 'AjaxHandler::checkInt']],
'scale' => [FILTER_CALLBACK, ['options' => 'AjaxAdmin::checkScale']],
'__icon' => [FILTER_CALLBACK, ['options' => 'AjaxAdmin::checkKey'] ]
);
@@ -71,6 +74,13 @@ class AjaxAdmin extends AjaxHandler
if ($this->_get['action'] == 'save')
$this->handler = 'wtSave';
}
else if ($this->params[0] == 'spawn-override')
{
if (!User::isInGroup(U_GROUP_MODERATOR))
return;
$this->handler = 'spawnPosFix';
}
}
// get all => null (optional)
@@ -115,7 +125,7 @@ class AjaxAdmin extends AjaxHandler
// resp: ''
protected function ssApprove() : void
{
if (!$this->_get['id'])
if (!$this->reqGET('id'))
{
trigger_error('AjaxAdmin::ssApprove - screenshotId empty', E_USER_ERROR);
return;
@@ -190,7 +200,7 @@ class AjaxAdmin extends AjaxHandler
// resp: ''
protected function ssSticky() : void
{
if (!$this->_get['id'])
if (!$this->reqGET('id'))
{
trigger_error('AjaxAdmin::ssSticky - screenshotId empty', E_USER_ERROR);
return;
@@ -217,7 +227,7 @@ class AjaxAdmin extends AjaxHandler
// 2 steps: 1) remove from sight, 2) remove from disk
protected function ssDelete() : void
{
if (!$this->_get['id'])
if (!$this->reqGET('id'))
{
trigger_error('AjaxAdmin::ssDelete - screenshotId empty', E_USER_ERROR);
return;
@@ -266,7 +276,7 @@ class AjaxAdmin extends AjaxHandler
// resp: ''
protected function ssRelocate() : void
{
if (!$this->_get['id'] || !$this->_get['typeid'])
if (!$this->reqGET('id', 'typeid'))
{
trigger_error('AjaxAdmin::ssRelocate - screenshotId or typeId empty', E_USER_ERROR);
return;
@@ -317,7 +327,7 @@ class AjaxAdmin extends AjaxHandler
protected function confRemove() : string
{
if (!$this->_get['key'])
if (!$this->reqGET('key'))
return 'invalid configuration option given';
if (DB::Aowow()->query('DELETE FROM ?_config WHERE `key` = ? AND (`flags` & ?d) = 0', $this->_get['key'], CON_FLAG_PERSISTENT))
@@ -357,7 +367,7 @@ class AjaxAdmin extends AjaxHandler
protected function wtSave() : string
{
if (!$this->_post['id'] || !$this->_post['__icon'])
if (!$this->reqPOST('id', '__icon'))
return '3';
// save to db
@@ -385,6 +395,73 @@ class AjaxAdmin extends AjaxHandler
return '0';
}
protected function spawnPosFix() : string
{
if (!$this->reqGET('type', 'guid', 'area', 'floor'))
return '-4';
$guid = $this->_get['guid'];
$type = $this->_get['type'];
$area = $this->_get['area'];
$floor = $this->_get['floor'];
if (!in_array($type, [TYPE_NPC, TYPE_OBJECT, TYPE_SOUND, TYPE_AREATRIGGER]))
return '-3';
DB::Aowow()->query('REPLACE INTO ?_spawns_override VALUES (?d, ?d, ?d, ?d, ?d)', $type, $guid, $area, $floor, AOWOW_REVISION);
if ($wPos = Game::getWorldPosForGUID($type, $guid))
{
if ($point = Game::worldPosToZonePos($wPos[$guid]['mapId'], $wPos[$guid]['posX'], $wPos[$guid]['posY'], $area, $floor))
{
$p = array(
'posX' => $point[0]['posX'],
'posY' => $point[0]['posY'],
'areaId' => $point[0]['areaId'],
'floor' => $point[0]['floor']
);
DB::Aowow()->query('UPDATE ?_spawns SET ?a WHERE `type` = ?d AND `guid` = ?d', $p, $type, $guid);
// if creature try for waypoints
if ($type != TYPE_NPC)
return '1';
$jobs = array(
'SELECT -w.id AS `entry`, w.point AS `pointId`, w.position_y AS `posX`, w.position_x AS `posY` FROM creature_addon ca JOIN waypoint_data w ON w.id = ca.path_id WHERE ca.guid = ?d AND ca.path_id <> 0',
'SELECT `entry`, `pointId`, `location_y` AS `posX`, `location_x` AS `posY` FROM `script_waypoint` WHERE `entry` = ?d',
'SELECT `entry`, `pointId`, `position_y` AS `posX`, `position_x` AS `posY` FROM `waypoints` WHERE `entry` = ?d'
);
foreach ($jobs as $idx => $job)
{
if ($swp = DB::World()->select($job, $idx ? $wPos['id'] : $guid))
{
foreach ($swp as $w)
{
if ($point = Game::worldPosToZonePos($wPos[$guid]['mapId'], $w['posX'], $w['posY'], $area, $floor))
{
$p = array(
'posX' => $point[0]['posX'],
'posY' => $point[0]['posY'],
'areaId' => $point[0]['areaId'],
'floor' => $point[0]['floor']
);
}
DB::Aowow()->query('UPDATE ?_creature_waypoints SET ?a WHERE `creatureOrPath` = ?d AND `point` = ?d', $p, $w['entry'], $w['pointId']);
}
}
}
return '1';
}
return '-2';
}
return '-1';
}
protected function checkKey(string $val) : string
{
// expecting string

View File

@@ -560,10 +560,10 @@ trait spawnHelper
$this->spawnResult[SPAWNINFO_SHORT] = new StdClass;
// first get zone/floor with the most spawns
if ($res = DB::Aowow()->selectRow('SELECT areaId, floor FROM ?_spawns WHERE type = ?d && typeId = ?d GROUP BY areaId, floor ORDER BY count(1) DESC LIMIT 1', self::$type, $this->id))
if ($res = DB::Aowow()->selectRow('SELECT areaId, floor FROM ?_spawns WHERE type = ?d AND typeId = ?d AND posX > 0 AND posY > 0 GROUP BY areaId, floor ORDER BY count(1) DESC LIMIT 1', self::$type, $this->id))
{
// get relevant spawn points
$points = DB::Aowow()->select('SELECT posX, posY FROM ?_spawns WHERE type = ?d && typeId = ?d && areaId = ?d && floor = ?d', self::$type, $this->id, $res['areaId'], $res['floor']);
$points = DB::Aowow()->select('SELECT posX, posY FROM ?_spawns WHERE type = ?d AND typeId = ?d AND areaId = ?d AND floor = ?d AND posX > 0 AND posY > 0', self::$type, $this->id, $res['areaId'], $res['floor']);
$spawns = [];
foreach ($points as $p)
$spawns[] = [$p['posX'], $p['posY']];
@@ -575,14 +575,18 @@ trait spawnHelper
private function createFullSpawns() // for display on map (objsct/npc detail page)
{
$data = [];
$wpSum = [];
$wpIdx = 0;
$spawns = DB::Aowow()->select("SELECT * FROM ?_spawns WHERE type = ?d AND typeId = ?d", self::$type, $this->id);
$data = [];
$wpSum = [];
$wpIdx = 0;
$worldPos = [];
$spawns = DB::Aowow()->select("SELECT * FROM ?_spawns WHERE type = ?d AND typeId = ?d AND posX > 0 AND posY > 0", self::$type, $this->id);
if (!$spawns)
return;
if (User::isInGroup(U_GROUP_MODERATOR))
$worldPos = Game::getWorldPosForGUID(self::$type, ...array_column($spawns, 'guid'));
foreach ($spawns as $s)
{
// check, if we can attach waypoints to creature
@@ -648,7 +652,49 @@ trait spawnHelper
$info[5] = 'Orientation'.Lang::main('colon').$o[0].'° ('.$o[1].')';
}
// $footer = '<span class="q2">Click to move to different floor</span>';
if (User::isInGroup(U_GROUP_MODERATOR))
{
if ($points = Game::worldPosToZonePos($worldPos[$s['guid']]['mapId'], $worldPos[$s['guid']]['posX'], $worldPos[$s['guid']]['posY']))
{
$floors = [];
foreach ($points as $p)
{
if ($p['floor'])
$floors[$p['areaId']][] = $p['floor'];
if (isset($menu[$p['areaId']]))
continue;
else if ($p['areaId'] == $s['areaId'])
$menu[$p['areaId']] = [$p['areaId'], '$g_zones['.$p['areaId'].']', '', null, ['class' => 'checked q0']];
else
$menu[$p['areaId']] = [$p['areaId'], '$g_zones['.$p['areaId'].']', '$spawnposfix.bind(null, '.self::$type.', '.$s['guid'].', '.$p['areaId'].', -1)', null, null];
}
foreach ($floors as $area => $f)
{
$menu[$area][2] = '';
$menu[$area][3] = [];
if ($menu[$area][4])
$menu[$area][4]['class'] = 'checked';
foreach ($f as $n)
{
if ($n == $s['floor'])
$menu[$area][3][] = [$n, '$g_zone_areas['.$area.']['.($n-1).']', '', null, ['class' => 'checked q0']];
else
$menu[$area][3][] = [$n, '$g_zone_areas['.$area.']['.($n-1).']', '$spawnposfix.bind(null, '.self::$type.', '.$s['guid'].', '.$area.', '.$n.')'];
}
}
$menu = array_values($menu);
}
if ($menu)
{
$footer = '<br /><span class="q2">Click to move displayed spawn point</span>';
array_unshift($menu, [null, "Move to..."]);
}
}
}
if ($info)
@@ -698,12 +744,12 @@ trait spawnHelper
$this->spawnResult[SPAWNINFO_ZONES] = $res;
}
private function createQuestSpawns() // [zoneId => [floor => [[x1, y1], [x2, y2], ..]]]
private function createQuestSpawns() // [zoneId => [floor => [[x1, y1], [x2, y2], ..]]] mapper on quest detail page
{
if (self::$type == TYPE_SOUND)
return;
$res = DB::Aowow()->select('SELECT areaId, floor, typeId, posX, posY FROM ?_spawns WHERE type = ?d && typeId IN (?a)', self::$type, $this->getFoundIDs());
$res = DB::Aowow()->select('SELECT areaId, floor, typeId, posX, posY FROM ?_spawns WHERE type = ?d AND typeId IN (?a) AND posX > 0 AND posY > 0', self::$type, $this->getFoundIDs());
$spawns = [];
foreach ($res as $data)
{

View File

@@ -203,6 +203,62 @@ class Game
return $pages;
}
public static function getWorldPosForGUID(int $type, int ...$guids) : array
{
$result = [];
switch ($type)
{
case TYPE_NPC:
$result = DB::World()->select('SELECT `guid` AS ARRAY_KEY, `id`, `map` AS `mapId`, `position_y` AS `posX`, `position_x` AS `posY` FROM creature WHERE `guid` IN (?a)', $guids);
break;
case TYPE_OBJECT:
$result = DB::World()->select('SELECT `guid` AS ARRAY_KEY, `id`, `map` AS `mapId`, `position_y` AS `posX`, `position_x` AS `posY` FROM gameobject WHERE `guid` IN (?a)', $guids);
break;
case TYPE_SOUND:
$result = DB::AoWoW()->select('SELECT `soundId` AS ARRAY_KEY, `soundId` AS `id`, `mapId`, `posX`, `posY` FROM dbc_soundemitters WHERE `soundId` IN (?a)', $guids);
break;
case TYPE_AREATRIGGER:
$result = DB::AoWoW()->select('SELECT `id` AS ARRAY_KEY, `id`, `mapId`, `posX`, `posY` FROM dbc_areatrigger WHERE `id` IN (?a)', $guids);
break;
default:
trigger_error('Game::getWorldPosForGUID - instanced with unsupported TYPE '.$type, E_USER_WARNING);
}
return $result;
}
public static function worldPosToZonePos(int $mapId, float $posX, float $posY, int $areaId = 0, int $floor = -1) : array
{
if (!$mapId < 0)
return [];
$query = 'SELECT
dm.id,
wma.areaId,
IFNULL(dm.floor, 0) AS floor,
100 - ROUND(IF(dm.id IS NOT NULL, (?f - dm.minY) * 100 / (dm.maxY - dm.minY), (?f - wma.right) * 100 / (wma.left - wma.right)), 1) AS `posX`,
100 - ROUND(IF(dm.id IS NOT NULL, (?f - dm.minX) * 100 / (dm.maxX - dm.minX), (?f - wma.bottom) * 100 / (wma.top - wma.bottom)), 1) AS `posY`,
SQRT(POWER(abs(IF(dm.id IS NOT NULL, (?f - dm.minY) * 100 / (dm.maxY - dm.minY), (?f - wma.right) * 100 / (wma.left - wma.right)) - 50), 2) +
POWER(abs(IF(dm.id IS NOT NULL, (?f - dm.minX) * 100 / (dm.maxX - dm.minX), (?f - wma.bottom) * 100 / (wma.top - wma.bottom)) - 50), 2)) AS `dist`
FROM
dbc_worldmaparea wma
LEFT JOIN
dbc_dungeonmap dm ON dm.mapId = IF(?d AND (wma.mapId NOT IN (0, 1, 530, 571) OR wma.areaId = 4395), wma.mapId, -1)
WHERE
wma.mapId = ?d AND IF(?d, wma.areaId = ?d, wma.areaId <> 0){ AND IF(dm.floor IS NULL, 1, dm.floor = ?d)}
HAVING
(`posX` BETWEEN 0.1 AND 99.9 AND `posY` BETWEEN 0.1 AND 99.9)
ORDER BY
`dist` ASC';
// dist BETWEEN 0 (center) AND 70.7 (corner)
$points = DB::Aowow()->select($query, $posX, $posX, $posY, $posY, $posX, $posX, $posY, $posY, 1, $mapId, $areaId, $areaId, $floor < 0 ? DBSIMPLE_SKIP : $floor);
if (!$points) // retry: TC counts pre-instance subareas as instance-maps .. which have no map file
$points = DB::Aowow()->select($query, $posX, $posX, $posY, $posY, $posX, $posX, $posY, $posY, 0, $mapId, 0, 0, DBSIMPLE_SKIP);
return $points;
}
}
?>

View File

@@ -1,6 +1,6 @@
<?php
define('AOWOW_REVISION', 30);
define('AOWOW_REVISION', 31);
define('CLI', PHP_SAPI === 'cli');