mirror of
https://github.com/Sarjuuk/aowow.git
synced 2025-11-29 15:58:16 +08:00
Map/Spawns
* fix spawns for multifloor dungeonmaps that use their worldmaparea entry for coordinates * fix mapper feature: move visible spawn point * resolve confused X/Y coordinate remapping from a time before i knew world coordinated are rotated by 90° * PS: Ulduar floor indizes were funky. No idea why. * restore old define order for g_zone_areas
This commit is contained in:
@@ -404,9 +404,9 @@ class AjaxAdmin extends AjaxHandler
|
||||
if ($type == Type::NPC)
|
||||
{
|
||||
$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'
|
||||
'SELECT -w.id AS `entry`, w.point AS `pointId`, w.position_x AS `posX`, w.position_y 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_x` AS `posX`, `location_y` AS `posY` FROM `script_waypoint` WHERE `entry` = ?d',
|
||||
'SELECT `entry`, `pointId`, `position_x` AS `posX`, `position_y` AS `posY` FROM `waypoints` WHERE `entry` = ?d'
|
||||
);
|
||||
|
||||
foreach ($jobs as $idx => $job)
|
||||
|
||||
@@ -591,7 +591,8 @@ trait spawnHelper
|
||||
return;
|
||||
|
||||
if (User::isInGroup(U_GROUP_MODERATOR))
|
||||
$worldPos = Game::getWorldPosForGUID(self::$type, ...array_column($spawns, 'guid'));
|
||||
if ($guids = array_filter(array_column($spawns, 'guid'), function ($x) { return $x > 0; }))
|
||||
$worldPos = Game::getWorldPosForGUID(self::$type, ...$guids);
|
||||
|
||||
foreach ($spawns as $s)
|
||||
{
|
||||
@@ -671,7 +672,7 @@ trait spawnHelper
|
||||
$floors = [];
|
||||
foreach ($points as $p)
|
||||
{
|
||||
if ($p['floor'])
|
||||
if ($p['multifloor'])
|
||||
$floors[$p['areaId']][] = $p['floor'];
|
||||
|
||||
if (isset($menu[$p['areaId']]))
|
||||
@@ -679,7 +680,7 @@ trait spawnHelper
|
||||
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];
|
||||
$menu[$p['areaId']] = [$p['areaId'], '$g_zones['.$p['areaId'].']', '$spawnposfix.bind(null, '.self::$type.', '.$s['guid'].', '.$p['areaId'].', 0)', null, null];
|
||||
}
|
||||
|
||||
foreach ($floors as $area => $f)
|
||||
@@ -691,16 +692,10 @@ trait spawnHelper
|
||||
|
||||
foreach ($f as $n)
|
||||
{
|
||||
$jsRef = $n;
|
||||
if ($area != 4273) // Ulduar is weird maaaan.....
|
||||
$jsRef--;
|
||||
|
||||
// todo: 3959 (BT) and 4075 (Sunwell) start at level 0 or something
|
||||
|
||||
if ($n == $s['floor'])
|
||||
$menu[$area][3][] = [$jsRef, '$g_zone_areas['.$area.']['.$jsRef.']', '', null, ['class' => 'checked q0']];
|
||||
$menu[$area][3][] = [$n, '$g_zone_areas['.$area.']['.($n - 1).']', '', null, ['class' => 'checked q0']];
|
||||
else
|
||||
$menu[$area][3][] = [$jsRef, '$g_zone_areas['.$area.']['.$jsRef.']', '$spawnposfix.bind(null, '.self::$type.', '.$s['guid'].', '.$area.', '.$n.')'];
|
||||
$menu[$area][3][] = [$n, '$g_zone_areas['.$area.']['.($n - 1).']', '$spawnposfix.bind(null, '.self::$type.', '.$s['guid'].', '.$area.', '.$n.')'];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -311,10 +311,10 @@ class Game
|
||||
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);
|
||||
$result = DB::World()->select('SELECT `guid` AS ARRAY_KEY, `id`, `map` AS `mapId`, `position_x` AS `posX`, `position_y` 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);
|
||||
$result = DB::World()->select('SELECT `guid` AS ARRAY_KEY, `id`, `map` AS `mapId`, `position_x` AS `posX`, `position_y` AS `posY` FROM gameobject WHERE `guid` IN (?a)', $guids);
|
||||
break;
|
||||
case Type::SOUND:
|
||||
$result = DB::AoWoW()->select('SELECT `id` AS ARRAY_KEY, `soundId` AS `id`, `mapId`, `posX`, `posY` FROM ?_soundemitters WHERE `id` IN (?a)', $guids);
|
||||
@@ -337,40 +337,41 @@ class Game
|
||||
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
|
||||
?_worldmaparea wma
|
||||
LEFT JOIN
|
||||
?_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';
|
||||
$query =
|
||||
'SELECT
|
||||
x.`id`,
|
||||
x.`areaId`,
|
||||
IF(x.`defaultDungeonMapId` < 0, x.`floor` + 1, x.`floor`) AS `floor`,
|
||||
IF(dm.`id` IS NOT NULL OR x.`defaultDungeonMapId` < 0, 1, 0) AS `multifloor`,
|
||||
ROUND((x.`maxY` - ?d) * 100 / (x.`maxY` - x.`minY`), 1) AS `posX`,
|
||||
ROUND((x.`maxX` - ?d) * 100 / (x.`maxX` - x.`minX`), 1) AS `posY`,
|
||||
SQRT(POWER(ABS((x.`maxY` - ?d) * 100 / (x.`maxY` - x.`minY`) - 50), 2) +
|
||||
POWER(ABS((x.`maxX` - ?d) * 100 / (x.`maxX` - x.`minX`) - 50), 2)) AS `dist`
|
||||
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 ?_worldmaparea wma UNION
|
||||
SELECT dm.`id`, `areaId`, wma.`mapId`, `minY`, `maxY`, `maxX`, `minX`, `floor`, `worldMapAreaId`, `defaultDungeonMapId` FROM ?_worldmaparea wma
|
||||
JOIN ?_dungeonmap dm ON dm.`mapId` = wma.`mapId` WHERE wma.`mapId` NOT IN (0, 1, 530, 571) OR wma.`areaId` = 4395) x
|
||||
LEFT JOIN
|
||||
?_dungeonmap dm ON dm.`mapId` = x.`mapId` AND dm.`worldMapAreaId` = x.`worldMapAreaId` AND dm.`floor` <> x.`floor` AND dm.`worldMapAreaId` > 0
|
||||
WHERE
|
||||
x.`mapId` = ?d AND IF(?d, x.`areaId` = ?d, x.`areaId` <> 0){ AND x.`floor` = ?d - IF(x.`defaultDungeonMapId` < 0, 1, 0)}
|
||||
GROUP BY
|
||||
x.`id`, x.`areaId`
|
||||
HAVING
|
||||
(`posX` BETWEEN 0.1 AND 99.9 AND `posY` BETWEEN 0.1 AND 99.9)
|
||||
ORDER BY
|
||||
`multifloor` DESC, `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);
|
||||
|
||||
$points = DB::Aowow()->select($query, $posY, $posX, $posY, $posX, $mapId, $areaId, $areaId, $floor < 0 ? DBSIMPLE_SKIP : $floor);
|
||||
if (!$points) // 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.
|
||||
$points = DB::Aowow()->select($query, $posY, $posX, $posY, $posX, $mapId, 0, 0, DBSIMPLE_SKIP);
|
||||
if (!is_array($points))
|
||||
{
|
||||
trigger_error('Game::worldPosToZonePos - dbc query failed', E_USER_ERROR);
|
||||
return [];
|
||||
}
|
||||
|
||||
// Black Temple and Sunwell floor offset bullshit
|
||||
if ($points && in_array($mapId, [564, 580]))
|
||||
$points[0]['floor']++;
|
||||
|
||||
return $points;
|
||||
}
|
||||
|
||||
|
||||
@@ -174,7 +174,7 @@ class ZonePage extends GenericPage
|
||||
|
||||
// we cannot fetch spawns via lists. lists are grouped by entry
|
||||
$oSpawns = DB::Aowow()->select('SELECT * FROM ?_spawns WHERE `areaId` = ?d AND `type` = ?d AND `posX` > 0 AND `posY` > 0', $this->typeId, Type::OBJECT);
|
||||
$cSpawns = DB::Aowow()->select('SELECT * FROM ?_spawns WHERE areaId = ?d AND `type` = ?d AND `posX` > 0 AND `posY` > 0', $this->typeId, Type::NPC);
|
||||
$cSpawns = DB::Aowow()->select('SELECT * FROM ?_spawns WHERE `areaId` = ?d AND `type` = ?d AND `posX` > 0 AND `posY` > 0', $this->typeId, Type::NPC);
|
||||
$aSpawns = User::isInGroup(U_GROUP_STAFF) ? DB::Aowow()->select('SELECT * FROM ?_spawns WHERE `areaId` = ?d AND `type` = ?d AND `posX` > 0 AND `posY` > 0', $this->typeId, Type::AREATRIGGER) : [];
|
||||
|
||||
$conditions = [Cfg::get('SQL_LIMIT_NONE'), ['s.areaId', $this->typeId]];
|
||||
|
||||
@@ -78,8 +78,8 @@ lightId = x
|
||||
[areatrigger]
|
||||
id = n
|
||||
mapId = i
|
||||
posY = f
|
||||
posX = f
|
||||
posY = f
|
||||
UNUSED4 = x
|
||||
UNUSED5 = x
|
||||
UNUSED6 = x
|
||||
@@ -941,8 +941,8 @@ soundIdNight = i
|
||||
|
||||
[soundemitters]
|
||||
id = n
|
||||
posY = f
|
||||
posX = f
|
||||
posY = f
|
||||
UNUSED3 = x
|
||||
UNUSED4 = x
|
||||
UNUSED5 = x
|
||||
|
||||
@@ -38,10 +38,10 @@ CLISetup::registerSetup('sql', new class extends SetupScript
|
||||
CLI::write('[areatrigger] - calculation teleporter coordinates');
|
||||
|
||||
$addData = DB::World()->select(
|
||||
'SELECT ID AS ARRAY_KEY, Name AS `name`, target_map AS `map`, target_position_x AS `posY`, target_position_y AS `posX`, target_orientation AS `orientation`
|
||||
'SELECT ID AS ARRAY_KEY, Name AS `name`, target_map AS `map`, target_position_x AS `posX`, target_position_y AS `posY`, target_orientation AS `orientation`
|
||||
FROM areatrigger_teleport
|
||||
UNION
|
||||
SELECT entryorguid AS ARRAY_KEY, "TBD" AS `name`, action_param1 AS `map`, target_x AS `posY`, target_y AS `posX`, target_o AS `orientation`
|
||||
SELECT entryorguid AS ARRAY_KEY, "TBD" AS `name`, action_param1 AS `map`, target_x AS `posX`, target_y AS `posY`, target_o AS `orientation`
|
||||
FROM smart_scripts
|
||||
WHERE source_type = 2 AND action_type = 62'
|
||||
);
|
||||
|
||||
@@ -20,11 +20,11 @@ CLISetup::registerSetup("sql", new class extends SetupScript
|
||||
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` ' .
|
||||
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_x` AS `posX`, c.`position_y` 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` ' .
|
||||
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_x` AS `posX`, c.`position_y` AS `posY` ' .
|
||||
'FROM gameobject c',
|
||||
'`gameobject` spawns', Type::OBJECT],
|
||||
|
||||
@@ -36,15 +36,15 @@ CLISetup::registerSetup("sql", new class extends SetupScript
|
||||
'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` ' .
|
||||
5 => ['SELECT c.`guid`, w.`entry` AS `npcOrPath`, w.`pointId` AS `point`, c.`zoneId` AS `areaId`, c.`map`, w.`waittime` AS `wait`, w.`location_x` AS `posX`, w.`location_y` 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` ' .
|
||||
6 => ['SELECT c.`guid`, w.`entry` AS `npcOrPath`, w.`pointId` AS `point`, c.`zoneId` AS `areaId`, c.`map`, 0 AS `wait`, w.`position_x` AS `posX`, w.`position_y` 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` ' .
|
||||
7 => ['SELECT c.`guid`, -w.`id` AS `npcOrPath`, w.`point`, c.`zoneId` AS `areaId`, c.`map`, w.`delay` AS `wait`, w.`position_x` AS `posX`, w.`position_y` 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]
|
||||
);
|
||||
@@ -99,12 +99,11 @@ CLISetup::registerSetup("sql", new class extends SetupScript
|
||||
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..
|
||||
// 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['posX'] += $transports[$spawn['map']]['posX'];
|
||||
$spawn['posY'] += $transports[$spawn['map']]['posY'];
|
||||
$spawn['map'] = $transports[$spawn['map']]['mapId'];
|
||||
}
|
||||
|
||||
|
||||
3
setup/updates/1719333848_01.sql
Normal file
3
setup/updates/1719333848_01.sql
Normal file
@@ -0,0 +1,3 @@
|
||||
DROP TABLE IF EXISTS dbc_areatrigger, dbc_soundemitters;
|
||||
|
||||
UPDATE `aowow_dbversion` SET `sql` = CONCAT(IFNULL(`sql`, ''), ' areatrigger soundemitters spawns');
|
||||
@@ -19470,7 +19470,7 @@ Mapper.onlyOneFloor = {
|
||||
};
|
||||
|
||||
Mapper.zoneLevelOffset = {
|
||||
4273: 0 // Ulduar
|
||||
// 4273: 0 // Ulduar // aowow - removed. why did this exist. what did i miss..?
|
||||
};
|
||||
|
||||
Mapper.zoneDefaultLevel = {
|
||||
@@ -20401,9 +20401,7 @@ Mapper.prototype = {
|
||||
}
|
||||
};
|
||||
|
||||
/* aowow: already defined in locale_xx instead of being fetched later
|
||||
var g_zone_areas = {};
|
||||
*/
|
||||
|
||||
var MapViewer = new function()
|
||||
{
|
||||
|
||||
@@ -2133,8 +2133,6 @@ var g_zones = {
|
||||
4987: 'Das Rubinsanktum'
|
||||
};
|
||||
|
||||
var g_zone_areas = {};
|
||||
|
||||
var g_zone_categories = {
|
||||
0: 'Östliche Königreiche',
|
||||
1: 'Kalimdor',
|
||||
|
||||
@@ -2181,8 +2181,6 @@ var g_zones = {
|
||||
4987: 'The Ruby Sanctum'
|
||||
};
|
||||
|
||||
var g_zone_areas = {};
|
||||
|
||||
var g_zone_categories = {
|
||||
0: 'Eastern Kingdoms',
|
||||
1: 'Kalimdor',
|
||||
|
||||
@@ -2133,8 +2133,6 @@ var g_zones = {
|
||||
4987: 'El Sagrario Rubí'
|
||||
};
|
||||
|
||||
var g_zone_areas = {};
|
||||
|
||||
var g_zone_categories = {
|
||||
0: 'Reinos del Este',
|
||||
1: 'Kalimdor',
|
||||
|
||||
@@ -2133,8 +2133,6 @@ var g_zones = {
|
||||
4987: 'Le sanctum Rubis'
|
||||
};
|
||||
|
||||
var g_zone_areas = {};
|
||||
|
||||
var g_zone_categories = {
|
||||
0: 'Royaumes de l\'est',
|
||||
1: 'Kalimdor',
|
||||
|
||||
@@ -2133,8 +2133,6 @@ var g_zones = {
|
||||
4987: 'Рубиновое святилище'
|
||||
};
|
||||
|
||||
var g_zone_areas = {};
|
||||
|
||||
var g_zone_categories = {
|
||||
0: 'Восточные королевства',
|
||||
1: 'Калимдор',
|
||||
|
||||
@@ -2180,8 +2180,6 @@ var g_zones = {
|
||||
4987: '红玉圣殿'
|
||||
};
|
||||
|
||||
var g_zone_areas = {};
|
||||
|
||||
var g_zone_categories = {
|
||||
0: "东部王国",
|
||||
1: "卡利姆多",
|
||||
|
||||
Reference in New Issue
Block a user