From 22ec091beb32e2c44c964809c803f8fd0cf5f4c3 Mon Sep 17 00:00:00 2001 From: Hondacrx Date: Sun, 25 Aug 2024 20:45:39 -0400 Subject: [PATCH] Core/AreaTriggers: Implement height check for polygon db2 areatriggers Port From (https://github.com/TrinityCore/TrinityCore/commit/1c3268155d0165e150d239c3a808d5a8dddeae18) --- Source/Framework/Constants/CliDBConst.cs | 19 +++++++ .../Database/Databases/HotfixDatabase.cs | 5 ++ Source/Game/DataStorage/CliDB.cs | 2 + Source/Game/DataStorage/DB2Manager.cs | 39 +++++++------ Source/Game/DataStorage/Structs/P_Records.cs | 18 ++++-- Source/Game/Entities/Player/Player.cs | 3 +- Source/Game/Globals/ObjectManager.cs | 57 +++++++++++++------ Source/Game/Handlers/MiscHandler.cs | 2 +- Source/Game/World/WorldManager.cs | 3 + 9 files changed, 109 insertions(+), 39 deletions(-) diff --git a/Source/Framework/Constants/CliDBConst.cs b/Source/Framework/Constants/CliDBConst.cs index 9f8cc99e6..9bb52e7c9 100644 --- a/Source/Framework/Constants/CliDBConst.cs +++ b/Source/Framework/Constants/CliDBConst.cs @@ -1860,6 +1860,25 @@ namespace Framework.Constants HideIfUnknown = 0x40 } + public enum PathPropertyIndex + { + UseNewLiquidGenerateCode = 0, + AnimaCableId = 1, + AnimaPlayerCondition = 2, + AnimaStartTaper = 3, + AnimaEndTaper = 4, + VolumeHeight = 5, + AiPathGraphMaxStartDist = 6, + AiPathGraphMinTotalDist = 7, + AiPathGraphAreaControl = 8, + AiPathGraphAreaId = 9, + AiPathGraphWidth = 10, + AiPathDefaultFollowStyle = 11, + AiPathConstrainSteering = 12, + Phase = 13, + SteepSlopeDegrees = 14 + } + public enum PhaseEntryFlags : int { ReadOnly = 0x1, diff --git a/Source/Framework/Database/Databases/HotfixDatabase.cs b/Source/Framework/Database/Databases/HotfixDatabase.cs index 9922cdea5..3bc39a75f 100644 --- a/Source/Framework/Database/Databases/HotfixDatabase.cs +++ b/Source/Framework/Database/Databases/HotfixDatabase.cs @@ -993,6 +993,9 @@ namespace Framework.Database // PathNode.db2 PrepareStatement(HotfixStatements.SEL_PATH_NODE, "SELECT ID, PathID, Sequence, LocationID FROM path_node WHERE (`VerifiedBuild` > 0) = ?"); + // PathProperty.db2 + PrepareStatement(HotfixStatements.SEL_PATH_PROPERTY, "SELECT ID, PathID, PropertyIndex, Value FROM path_property WHERE (`VerifiedBuild` > 0) = ?"); + // Phase.db2 PrepareStatement(HotfixStatements.SEL_PHASE, "SELECT ID, Flags FROM phase WHERE (`VerifiedBuild` > 0) = ?"); @@ -2090,6 +2093,8 @@ namespace Framework.Database SEL_PATH_NODE, + SEL_PATH_PROPERTY, + SEL_PHASE, SEL_PHASE_X_PHASE_GROUP, diff --git a/Source/Game/DataStorage/CliDB.cs b/Source/Game/DataStorage/CliDB.cs index a4d8c5a62..c948ad938 100644 --- a/Source/Game/DataStorage/CliDB.cs +++ b/Source/Game/DataStorage/CliDB.cs @@ -254,6 +254,7 @@ namespace Game.DataStorage ParagonReputationStorage = ReadDB2("ParagonReputation.db2", HotfixStatements.SEL_PARAGON_REPUTATION); PathStorage = ReadDB2("Path.db2", HotfixStatements.SEL_PATH); PathNodeStorage = ReadDB2("PathNode.db2", HotfixStatements.SEL_PATH_NODE); + PathPropertyStorage = ReadDB2("PathProperty.db2", HotfixStatements.SEL_PATH_PROPERTY); PhaseStorage = ReadDB2("Phase.db2", HotfixStatements.SEL_PHASE); PhaseXPhaseGroupStorage = ReadDB2("PhaseXPhaseGroup.db2", HotfixStatements.SEL_PHASE_X_PHASE_GROUP); PlayerConditionStorage = ReadDB2("PlayerCondition.db2", HotfixStatements.SEL_PLAYER_CONDITION, HotfixStatements.SEL_PLAYER_CONDITION_LOCALE); @@ -697,6 +698,7 @@ namespace Game.DataStorage public static DB6Storage ParagonReputationStorage; public static DB6Storage PathStorage; public static DB6Storage PathNodeStorage; + public static DB6Storage PathPropertyStorage; public static DB6Storage PhaseStorage; public static DB6Storage PhaseXPhaseGroupStorage; public static DB6Storage PlayerConditionStorage; diff --git a/Source/Game/DataStorage/DB2Manager.cs b/Source/Game/DataStorage/DB2Manager.cs index 11710ecde..aacd1a066 100644 --- a/Source/Game/DataStorage/DB2Manager.cs +++ b/Source/Game/DataStorage/DB2Manager.cs @@ -423,28 +423,28 @@ namespace Game.DataStorage Dictionary> unsortedNodes = new(); foreach (var (_, pathNode) in CliDB.PathNodeStorage) { - if (CliDB.PathStorage.ContainsKey(pathNode.PathID)) + if (CliDB.PathStorage.HasRecord(pathNode.PathID) && CliDB.LocationStorage.HasRecord((uint)pathNode.LocationID)) { - if (CliDB.LocationStorage.ContainsKey(pathNode.LocationID)) - { - if (!unsortedNodes.ContainsKey(pathNode.PathID)) - unsortedNodes[pathNode.PathID] = new(); + if (!unsortedNodes.ContainsKey(pathNode.PathID)) + unsortedNodes[pathNode.PathID] = new(); - unsortedNodes[pathNode.PathID].Add(pathNode); - } - } + unsortedNodes[pathNode.PathID].Add(pathNode); + } } foreach (var (pathId, pathNodes) in unsortedNodes) { + if (!_paths.ContainsKey(pathId)) + _paths[pathId] = new(); + pathNodes.OrderBy(node => node.Sequence); - _pathNodes.AddRange(pathId, pathNodes.Select(node => - { - LocationRecord location = CliDB.LocationStorage.LookupByKey(node.LocationID); - return location.Pos; - })); + _paths[pathId].Locations.AddRange(pathNodes.Select(node => CliDB.LocationStorage.LookupByKey(node.LocationID).Pos)); } + foreach (var (_, pathProperty) in CliDB.PathPropertyStorage) + if (CliDB.PathStorage.HasRecord(pathProperty.PathID)) + _paths[pathProperty.PathID].Properties.Add(pathProperty); + foreach (var group in PhaseXPhaseGroupStorage.Values) { PhaseRecord phase = PhaseStorage.LookupByKey(group.PhaseId); @@ -1765,9 +1765,9 @@ namespace Game.DataStorage return _paragonReputations.LookupByKey(factionId); } - public List GetNodesForPath(uint pathId) + public PathDb2 GetPath(uint pathId) { - return _pathNodes.LookupByKey(pathId); + return _paths.LookupByKey(pathId); } public PvpDifficultyRecord GetBattlegroundBracketByLevel(uint mapid, uint level) @@ -2358,7 +2358,7 @@ namespace Game.DataStorage Dictionary[]> _nameGenData = new(); List[] _nameValidators = new List[(int)Locale.Total + 1]; Dictionary _paragonReputations = new(); - MultiMap _pathNodes = new(); + Dictionary _paths; MultiMap _phasesByGroup = new(); Dictionary _powerTypes = new(); Dictionary _pvpItemBonus = new(); @@ -2650,6 +2650,13 @@ namespace Game.DataStorage public short TargetLevelMax; } + public class PathDb2 + { + public uint Id; + public List Locations = new(); + public List Properties = new(); + } + public class ShapeshiftFormModelData { public uint OptionID; diff --git a/Source/Game/DataStorage/Structs/P_Records.cs b/Source/Game/DataStorage/Structs/P_Records.cs index 620f4a330..abcda2b59 100644 --- a/Source/Game/DataStorage/Structs/P_Records.cs +++ b/Source/Game/DataStorage/Structs/P_Records.cs @@ -33,6 +33,16 @@ namespace Game.DataStorage public int LocationID; } + public sealed class PathPropertyRecord + { + public uint Id; + public ushort PathID; + public byte PropertyIndex; + public int Value; + + public PathPropertyIndex GetPropertyIndex() { return (PathPropertyIndex)PropertyIndex; } + } + public sealed class PhaseRecord { public uint Id; @@ -132,9 +142,9 @@ namespace Game.DataStorage public uint[] CurrencyCount = new uint[4]; public uint[] QuestKillMonster = new uint[6]; public int[] MovementFlags = new int[2]; - public int[]TraitNodeEntryID = new int[4]; - public ushort[]TraitNodeEntryMinRank = new ushort[4]; - public ushort[]TraitNodeEntryMaxRank = new ushort[4]; + public int[] TraitNodeEntryID = new int[4]; + public ushort[] TraitNodeEntryMinRank = new ushort[4]; + public ushort[] TraitNodeEntryMaxRank = new ushort[4]; } public sealed class PowerDisplayRecord @@ -176,7 +186,7 @@ namespace Game.DataStorage public int AwardedAchievementID; public bool HasFlag(PrestigeLevelInfoFlags prestigeLevelInfoFlags) { return (Flags & (byte)prestigeLevelInfoFlags) != 0; } - public bool IsDisabled() { return HasFlag(PrestigeLevelInfoFlags.Disabled); } + public bool IsDisabled() { return HasFlag(PrestigeLevelInfoFlags.Disabled); } } public sealed class PvpDifficultyRecord diff --git a/Source/Game/Entities/Player/Player.cs b/Source/Game/Entities/Player/Player.cs index e08cd07a1..64d8eedb3 100644 --- a/Source/Game/Entities/Player/Player.cs +++ b/Source/Game/Entities/Player/Player.cs @@ -2449,7 +2449,8 @@ namespace Game.Entities return false; break; case 3: // Polygon - if (!IsInPolygon2D(areaTriggerPos, ObjectMgr.GetVerticesForAreaTrigger(areaTrigger))) + var polygon = ObjectMgr.GetAreaTriggerPolygon(areaTrigger.Id); + if (polygon == null || (polygon.Height.HasValue && GetPositionZ() > areaTrigger.Pos.Z + polygon.Height) || !IsInPolygon2D(areaTriggerPos, polygon.Vertices)) return false; break; case 4: // Cylinder diff --git a/Source/Game/Globals/ObjectManager.cs b/Source/Game/Globals/ObjectManager.cs index 6b1b77d11..d48cde631 100644 --- a/Source/Game/Globals/ObjectManager.cs +++ b/Source/Game/Globals/ObjectManager.cs @@ -751,23 +751,6 @@ namespace Game return pointsOfInterestStorage.LookupByKey(id); } - public List GetVerticesForAreaTrigger(AreaTriggerRecord areaTrigger) - { - List vertices = new(); - if (areaTrigger != null && areaTrigger.ShapeType == 3 /* Polygon */) - { - var pathNodes = Global.DB2Mgr.GetNodesForPath((uint)areaTrigger.ShapeID); - if (pathNodes != null) - vertices.AddRange(pathNodes.Select(dbcPosition => new Position(dbcPosition.X, dbcPosition.Y, dbcPosition.Z))); - - - // Drop first node (areatrigger position) - vertices.RemoveAt(0); - } - - return vertices; - } - public void LoadGraveyardZones() { uint oldMSTime = Time.GetMSTime(); @@ -5372,6 +5355,7 @@ namespace Game Log.outInfo(LogFilter.ServerLoading, "Loaded {0} instance templates in {1} ms", count, Time.GetMSTimeDiffToNow(time)); } + public void LoadGameTele() { uint oldMSTime = Time.GetMSTime(); @@ -5415,6 +5399,7 @@ namespace Game Log.outInfo(LogFilter.ServerLoading, "Loaded {0} GameTeleports in {1} ms", count, Time.GetMSTimeDiffToNow(oldMSTime)); } + public void LoadAreaTriggerTeleports() { uint oldMSTime = Time.GetMSTime(); @@ -5465,6 +5450,29 @@ namespace Game Log.outInfo(LogFilter.ServerLoading, "Loaded {0} area trigger teleport definitions in {1} ms", count, Time.GetMSTimeDiffToNow(oldMSTime)); } + + public void LoadAreaTriggerPolygons() + { + foreach (var (_, areaTrigger) in CliDB.AreaTriggerStorage) + { + if (areaTrigger.ShapeType != 3) + continue; + + PathDb2 path = Global.DB2Mgr.GetPath((uint)areaTrigger.ShapeID); + if (path == null || path.Locations.Count < 4) + continue; + + AreaTriggerPolygon polygon = new(); + polygon.Vertices.AddRange(path.Locations.Select(pos => new Position(pos.X, pos.Y, pos.Z))); + + foreach (var pathProperty in path.Properties) + if (pathProperty.GetPropertyIndex() == PathPropertyIndex.VolumeHeight) + polygon.Height = pathProperty.Value * 0.001f + 0.02f; + + _areaTriggerPolygons[areaTrigger.Id] = polygon; + } + } + public void LoadAccessRequirements() { uint oldMSTime = Time.GetMSTime(); @@ -5561,6 +5569,7 @@ namespace Game Log.outInfo(LogFilter.ServerLoading, "Loaded {0} access requirement definitions in {1} ms", count, Time.GetMSTimeDiffToNow(oldMSTime)); } + public void LoadSpawnGroupTemplates() { uint oldMSTime = Time.GetMSTime(); @@ -5621,6 +5630,7 @@ namespace Game else Log.outInfo(LogFilter.ServerLoading, "Loaded 0 spawn group templates. DB table `spawn_group_template` is empty."); } + public void LoadSpawnGroups() { uint oldMSTime = Time.GetMSTime(); @@ -5683,6 +5693,7 @@ namespace Game Log.outInfo(LogFilter.ServerLoading, $"Loaded {numMembers} spawn group members in {Time.GetMSTimeDiffToNow(oldMSTime)} ms"); } + public void LoadInstanceSpawnGroups() { uint oldMSTime = Time.GetMSTime(); @@ -5749,6 +5760,7 @@ namespace Game Log.outInfo(LogFilter.ServerLoading, $"Loaded {count} instance spawn groups in {Time.GetMSTimeDiffToNow(oldMSTime)} ms"); } + void OnDeleteSpawnData(SpawnData data) { var templateIt = _spawnGroupDataStorage.LookupByKey(data.spawnGroupData.groupId); @@ -5888,6 +5900,10 @@ namespace Game } } public List GetInstanceSpawnGroupsForMap(uint mapId) { return _instanceSpawnGroupStorage.LookupByKey(mapId); } + public AreaTriggerPolygon GetAreaTriggerPolygon(uint areaTriggerId) + { + return _areaTriggerPolygons.LookupByKey(areaTriggerId); + } //Player public void LoadPlayerInfo() @@ -11020,6 +11036,7 @@ namespace Game MultiMap _spawnGroupMapStorage = new(); MultiMap _spawnGroupsByMap = new(); MultiMap _instanceSpawnGroupStorage = new(); + Dictionary _areaTriggerPolygons = new(); //Spells /Skills / Phases Dictionary _phaseInfoById = new(); @@ -12234,4 +12251,10 @@ namespace Game public uint Parent; public uint ScriptId; } + + public class AreaTriggerPolygon + { + public List Vertices = new(); + public float? Height; + } } diff --git a/Source/Game/Handlers/MiscHandler.cs b/Source/Game/Handlers/MiscHandler.cs index 6a5977d11..77e9df631 100644 --- a/Source/Game/Handlers/MiscHandler.cs +++ b/Source/Game/Handlers/MiscHandler.cs @@ -171,7 +171,7 @@ namespace Game return; } - if (packet.Entered && !player.IsInAreaTrigger(atEntry)) + if (packet.Entered != player.IsInAreaTrigger(atEntry)) { Log.outDebug(LogFilter.Network, "HandleAreaTrigger: Player '{0}' ({1}) too far, ignore Area Trigger ID: {2}", player.GetName(), player.GetGUID().ToString(), packet.AreaTriggerID); diff --git a/Source/Game/World/WorldManager.cs b/Source/Game/World/WorldManager.cs index 3f5ef56f9..389a35dfd 100644 --- a/Source/Game/World/WorldManager.cs +++ b/Source/Game/World/WorldManager.cs @@ -740,6 +740,9 @@ namespace Game Log.outInfo(LogFilter.ServerLoading, "Loading Area Trigger Teleports definitions..."); Global.ObjectMgr.LoadAreaTriggerTeleports(); + Log.outInfo(LogFilter.ServerLoading, "Loading Area Trigger Polygon data..."); + Global.ObjectMgr.LoadAreaTriggerPolygons(); + Log.outInfo(LogFilter.ServerLoading, "Loading Access Requirements..."); Global.ObjectMgr.LoadAccessRequirements(); // must be after item template load