diff --git a/Source/Framework/Constants/AreaTriggerConst.cs b/Source/Framework/Constants/AreaTriggerConst.cs index 6c5f23570..1c69a1012 100644 --- a/Source/Framework/Constants/AreaTriggerConst.cs +++ b/Source/Framework/Constants/AreaTriggerConst.cs @@ -47,7 +47,8 @@ namespace Framework.Constants { Cast = 0, AddAura = 1, - Max = 2 + Teleport = 2, + Max = 3 } public enum AreaTriggerActionUserTypes diff --git a/Source/Game/DataStorage/AreaTriggerDataStorage.cs b/Source/Game/DataStorage/AreaTriggerDataStorage.cs index 664de105c..1a5eefdf1 100644 --- a/Source/Game/DataStorage/AreaTriggerDataStorage.cs +++ b/Source/Game/DataStorage/AreaTriggerDataStorage.cs @@ -19,6 +19,7 @@ using Framework.Constants; using Framework.Database; using Framework.GameMath; using Game.Entities; +using Game.Maps; using System.Collections.Generic; namespace Game.DataStorage @@ -27,6 +28,24 @@ namespace Game.DataStorage { AreaTriggerDataStorage() { } + public AreaTriggerTemplate GetAreaTriggerServerTemplate(uint areaTriggerId) + { + return _areaTriggerTemplateMap.LookupByKey(new AreaTriggerTemplateKey(areaTriggerId, true)); + } + + public SortedSet GetAreaTriggersForMapAndCell(uint mapId, uint cellId) + { + if (!_areaTriggersGrid.ContainsKey(mapId)) + return null; + + return _areaTriggersGrid[mapId].LookupByKey(cellId); + } + + public AreaTriggerServerPosition GetAreaTriggerServerPosition(uint spawnId) + { + return _areaTriggerSpawnMap.LookupByKey(spawnId); + } + public void LoadAreaTriggerTemplates() { uint oldMSTime = Time.GetMSTime(); @@ -60,6 +79,17 @@ namespace Game.DataStorage continue; } + + if (action.ActionType == AreaTriggerActionTypes.Teleport) + { + WorldSafeLocsEntry safeLoc = Global.ObjectMgr.GetWorldSafeLoc(action.Param); + if (safeLoc == null) + { + Log.outError(LogFilter.Sql, $"Table `areatrigger_template_actions` has invalid entry ({areaTriggerId}) with TargetType=Teleport and Param ({action.Param}) not a valid world safe loc entry"); + continue; + } + } + actionsByAreaTrigger.Add(areaTriggerId, action); } while (templateActions.NextRow()); @@ -110,15 +140,16 @@ namespace Game.DataStorage Log.outInfo(LogFilter.ServerLoading, "Loaded 0 AreaTrigger templates splines. DB table `spell_areatrigger_splines` is empty."); } - // 0 1 2 3 4 5 6 7 8 9 - SQLResult templates = DB.World.Query("SELECT Id, Type, Flags, Data0, Data1, Data2, Data3, Data4, Data5, ScriptName FROM `areatrigger_template`"); + // 0 1 2 3 4 5 6 7 8 9 10 + SQLResult templates = DB.World.Query("SELECT Id, IsServer, Type, Flags, Data0, Data1, Data2, Data3, Data4, Data5, ScriptName FROM `areatrigger_template`"); if (!templates.IsEmpty()) { do { AreaTriggerTemplate areaTriggerTemplate = new AreaTriggerTemplate(); areaTriggerTemplate.Id = templates.Read(0); - AreaTriggerTypes type = (AreaTriggerTypes)templates.Read(1); + bool isServer = templates.Read(1) == 1; + AreaTriggerTypes type = (AreaTriggerTypes)templates.Read(2); if (type >= AreaTriggerTypes.Max) { @@ -127,25 +158,82 @@ namespace Game.DataStorage } areaTriggerTemplate.TriggerType = type; - areaTriggerTemplate.Flags = (AreaTriggerFlags)templates.Read(2); + areaTriggerTemplate.Flags = (AreaTriggerFlags)templates.Read(3); + + if (isServer && areaTriggerTemplate.Flags != 0) + { + Log.outError(LogFilter.Sql, $"Table `areatrigger_template` has listed server-side areatrigger (Id: {areaTriggerTemplate.Id}) with none-zero flags"); + continue; + } unsafe { for (byte i = 0; i < SharedConst.MaxAreatriggerEntityData; ++i) - areaTriggerTemplate.DefaultDatas.Data[i] = templates.Read(3 + i); + areaTriggerTemplate.DefaultDatas.Data[i] = templates.Read(4 + i); } - areaTriggerTemplate.ScriptId = Global.ObjectMgr.GetScriptId(templates.Read(9)); + areaTriggerTemplate.ScriptId = Global.ObjectMgr.GetScriptId(templates.Read(10)); areaTriggerTemplate.PolygonVertices = verticesByAreaTrigger[areaTriggerTemplate.Id]; areaTriggerTemplate.PolygonVerticesTarget = verticesTargetByAreaTrigger[areaTriggerTemplate.Id]; areaTriggerTemplate.Actions = actionsByAreaTrigger[areaTriggerTemplate.Id]; + areaTriggerTemplate.IsServerSide = isServer; areaTriggerTemplate.InitMaxSearchRadius(); - _areaTriggerTemplateStore[areaTriggerTemplate.Id] = areaTriggerTemplate; + _areaTriggerTemplateMap[new AreaTriggerTemplateKey(areaTriggerTemplate.Id, isServer)] = areaTriggerTemplate; } while (templates.NextRow()); } + // Load area trigger positions (to put them on the server) + // 0 1 2 3 4 5 6 7 8 9 10 + SQLResult templatePos = DB.World.Query("SELECT SpawnId, Id, IsServer, MapId, PosX, PosY, PosZ, PhaseId, PhaseGroup, PhaseUseFlags, Comment FROM `areatrigger_positions`"); + if (!templatePos.IsEmpty()) + { + do + { + AreaTriggerServerPosition position = new AreaTriggerServerPosition(); + position.SpawnId = templatePos.Read(0); + position.Id = templatePos.Read(1); + position.IsServer = templatePos.Read(2) == 1; + uint mapId = templatePos.Read(3); + + float posX = templatePos.Read(4); + float posY = templatePos.Read(5); + float posZ = templatePos.Read(6); + position.Location = new WorldLocation(mapId, posX, posY, posZ); + + if (!GridDefines.IsValidMapCoord(position.Location)) + { + Log.outError(LogFilter.Sql, $"Table `areatrigger_srv_position` has listed an invalid position: Id: {position.Id}, IsServer: {position.IsServer}. MapId ({mapId}), Position ({posX}, {posY}, {posZ})"); + continue; + } + + position.PhaseId = templatePos.Read(7); + position.PhaseGroup = templatePos.Read(8); + position.PhaseUseFlags = templatePos.Read(9); + + var template = _areaTriggerTemplateMap.LookupByKey(new AreaTriggerTemplateKey(position.Id, position.IsServer)); + if (template == null) + { + Log.outError(LogFilter.Sql, $"Table `areatrigger_srv_position` has listed areatrigger that doesn't exist: Id: {position.Id}, IsServer: {position.IsServer}"); + continue; + } + + // Add the trigger to a map::cell map, which is later used by GridLoader to query + CellCoord cellCoord = GridDefines.ComputeCellCoord(position.Location.GetPositionX(), position.Location.GetPositionY()); + if (!_areaTriggersGrid.ContainsKey(mapId)) + _areaTriggersGrid[mapId] = new Dictionary>(); + + if (!_areaTriggersGrid[mapId].ContainsKey(cellCoord.GetId())) + _areaTriggersGrid[mapId][cellCoord.GetId()] = new SortedSet(); + + _areaTriggersGrid[mapId][cellCoord.GetId()].Add(position.SpawnId); + + // add the position to the map + _areaTriggerSpawnMap[(uint)position.SpawnId] = position; + } while (templates.NextRow()); + } + // 0 1 2 3 4 5 6 7 8 9 10 SQLResult areatriggerSpellMiscs = DB.World.Query("SELECT SpellMiscId, AreaTriggerId, MoveCurveId, ScaleCurveId, MorphCurveId, FacingCurveId, AnimId, AnimKitId, DecalPropertiesId, TimeToTarget, TimeToTargetScale FROM `spell_areatrigger`"); if (!areatriggerSpellMiscs.IsEmpty()) @@ -256,12 +344,12 @@ namespace Game.DataStorage Log.outInfo(LogFilter.ServerLoading, "Loaded 0 AreaTrigger templates circular movement infos. DB table `spell_areatrigger_circular` is empty."); } - Log.outInfo(LogFilter.ServerLoading, "Loaded {0} spell areatrigger templates in {1} ms.", _areaTriggerTemplateStore.Count, Time.GetMSTimeDiffToNow(oldMSTime)); + Log.outInfo(LogFilter.ServerLoading, "Loaded {0} spell areatrigger templates in {1} ms.", _areaTriggerTemplateMap.Count, Time.GetMSTimeDiffToNow(oldMSTime)); } public AreaTriggerTemplate GetAreaTriggerTemplate(uint areaTriggerId) { - return _areaTriggerTemplateStore.LookupByKey(areaTriggerId); + return _areaTriggerTemplateMap.LookupByKey(new AreaTriggerTemplateKey(areaTriggerId, false)); } public AreaTriggerMiscTemplate GetAreaTriggerMiscTemplate(uint spellMiscValue) @@ -269,7 +357,9 @@ namespace Game.DataStorage return _areaTriggerTemplateSpellMisc.LookupByKey(spellMiscValue); } - Dictionary _areaTriggerTemplateStore = new Dictionary(); + Dictionary>> _areaTriggersGrid = new Dictionary>>(); + Dictionary _areaTriggerSpawnMap = new Dictionary(); + Dictionary _areaTriggerTemplateMap = new Dictionary(); Dictionary _areaTriggerTemplateSpellMisc = new Dictionary(); } } diff --git a/Source/Game/Entities/AreaTrigger/AreaTrigger.cs b/Source/Game/Entities/AreaTrigger/AreaTrigger.cs index aa9cb127e..e086c031a 100644 --- a/Source/Game/Entities/AreaTrigger/AreaTrigger.cs +++ b/Source/Game/Entities/AreaTrigger/AreaTrigger.cs @@ -16,16 +16,16 @@ */ using Framework.Constants; +using Framework.Dynamic; using Framework.GameMath; using Game.AI; using Game.Maps; using Game.Movement; +using Game.Networking; using Game.Networking.Packets; using Game.Spells; using System; using System.Collections.Generic; -using Framework.Dynamic; -using Game.Networking; namespace Game.Entities { @@ -98,6 +98,8 @@ namespace Game.Entities return false; } + _areaTriggerTemplate = _areaTriggerMiscTemplate.Template; + _Create(ObjectGuid.Create(HighGuid.AreaTrigger, GetMapId(), GetTemplate().Id, caster.GetMap().GenerateLowGuid(HighGuid.AreaTrigger))); SetEntry(GetTemplate().Id); @@ -210,11 +212,58 @@ namespace Game.Entities return at; } + bool LoadFromDB(uint spawnId, Map map, bool addToMap, bool allowDuplicate) + { + AreaTriggerServerPosition position = Global.AreaTriggerDataStorage.GetAreaTriggerServerPosition(spawnId); + if (position == null) + return false; + + AreaTriggerTemplate areaTriggerTemplate = Global.AreaTriggerDataStorage.GetAreaTriggerServerTemplate(position.Id); + if (areaTriggerTemplate == null) + return false; + + return CreateServer(map, areaTriggerTemplate, position); + } + + bool CreateServer(Map map, AreaTriggerTemplate areaTriggerTemplate, AreaTriggerServerPosition position) + { + SetMap(map); + Relocate(position.Location); + if (!IsPositionValid()) + { + Log.outError(LogFilter.AreaTrigger, $"AreaTriggerServer (id {areaTriggerTemplate.Id}) not created. Invalid coordinates (X: {GetPositionX()} Y: {GetPositionY()})"); + return false; + } + + _areaTriggerTemplate = areaTriggerTemplate; + + _Create(ObjectGuid.Create(HighGuid.AreaTrigger, GetMapId(), areaTriggerTemplate.Id, GetMap().GenerateLowGuid(HighGuid.AreaTrigger))); + + SetEntry(areaTriggerTemplate.Id); + + SetObjectScale(1.0f); + + if (position.PhaseId != 0 || position.PhaseGroup != 0 || position.PhaseUseFlags != 0) + PhasingHandler.InitDbPhaseShift(GetPhaseShift(), (PhaseUseFlagsValues)position.PhaseUseFlags, position.PhaseId, position.PhaseGroup); + + UpdateShape(); + + AI_Initialize(); + + return true; + } + public override void Update(uint diff) { base.Update(diff); _timeSinceCreated += diff; + if (IsServerSide()) + { + UpdateTargetList(); + return; + } + // "If" order matter here, Orbit > Attached > Splines if (HasOrbit()) { @@ -423,7 +472,7 @@ namespace Game.Entities public AreaTriggerTemplate GetTemplate() { - return _areaTriggerMiscTemplate.Template; + return _areaTriggerTemplate; } public uint GetScriptId() @@ -565,12 +614,12 @@ namespace Game.Entities void DoActions(Unit unit) { - Unit caster = GetCaster(); + Unit caster = IsServerSide() ? unit : GetCaster(); if (caster) { foreach (AreaTriggerAction action in GetTemplate().Actions) - { - if (UnitFitToActionRequirement(unit, caster, action)) + { + if (IsServerSide() || UnitFitToActionRequirement(unit, caster, action)) { switch (action.ActionType) { @@ -580,6 +629,15 @@ namespace Game.Entities case AreaTriggerActionTypes.AddAura: caster.AddAura(action.Param, unit); break; + case AreaTriggerActionTypes.Teleport: + WorldSafeLocsEntry safeLoc = Global.ObjectMgr.GetWorldSafeLoc(action.Param); + if (safeLoc != null) + { + Player player = caster.ToPlayer(); + if (player != null) + player.TeleportTo(safeLoc.Loc); + } + break; default: break; } @@ -909,6 +967,10 @@ namespace Game.Entities AreaTriggerAI GetAI() { return _ai; } + public bool IsServerSide() { return _areaTriggerTemplate.IsServerSide; } + + public override bool IsNeverVisibleFor(WorldObject seer) { return IsServerSide(); } + [System.Diagnostics.Conditional("DEBUG")] void DebugVisualizePosition() { @@ -975,5 +1037,7 @@ namespace Game.Entities List _insideUnits = new List(); AreaTriggerAI _ai; + + AreaTriggerTemplate _areaTriggerTemplate; } } diff --git a/Source/Game/Entities/AreaTrigger/AreaTriggerTemplate.cs b/Source/Game/Entities/AreaTrigger/AreaTriggerTemplate.cs index 8bd15c16d..c18d42cbf 100644 --- a/Source/Game/Entities/AreaTrigger/AreaTriggerTemplate.cs +++ b/Source/Game/Entities/AreaTrigger/AreaTriggerTemplate.cs @@ -16,12 +16,12 @@ */ using Framework.Constants; +using Framework.Dynamic; using Framework.GameMath; +using Game.Networking; using System; using System.Collections.Generic; using System.Runtime.InteropServices; -using Framework.Dynamic; -using Game.Networking; namespace Game.Entities { @@ -187,6 +187,27 @@ namespace Game.Entities } } + struct AreaTriggerTemplateKey + { + public uint Id; + public bool IsServer; + + public AreaTriggerTemplateKey(uint id, bool isServer) + { + Id = id; + IsServer = isServer; + } + + public static bool operator ==(AreaTriggerTemplateKey A, AreaTriggerTemplateKey B) + { + return A.Id == B.Id && A.IsServer == B.IsServer; + } + public static bool operator !=(AreaTriggerTemplateKey A, AreaTriggerTemplateKey B) + { + return A.Id != B.Id && A.IsServer != B.IsServer; + } + } + public class AreaTriggerTemplate : AreaTriggerData { public unsafe void InitMaxSearchRadius() @@ -244,6 +265,7 @@ namespace Game.Entities public List PolygonVertices = new List(); public List PolygonVerticesTarget = new List(); public List Actions = new List(); + public bool IsServerSide; } public unsafe class AreaTriggerMiscTemplate @@ -283,6 +305,17 @@ namespace Game.Entities public List SplinePoints = new List(); } + public class AreaTriggerServerPosition + { + public ulong SpawnId; + public uint Id; + public bool IsServer; + public WorldLocation Location; + public uint PhaseId; + public uint PhaseGroup; + public byte PhaseUseFlags; + } + public struct AreaTriggerAction { public uint Param; diff --git a/Source/Game/Maps/ObjectGridLoader.cs b/Source/Game/Maps/ObjectGridLoader.cs index 2002cf45d..65fb37323 100644 --- a/Source/Game/Maps/ObjectGridLoader.cs +++ b/Source/Game/Maps/ObjectGridLoader.cs @@ -76,13 +76,23 @@ namespace Game.Maps LoadHelper(cellguids.creatures, cellCoord, ref i_creatures, i_map); } + public override void Visit(IList objs) + { + CellCoord cellCoord = i_cell.GetCellCoord(); + SortedSet areaTriggers = Global.AreaTriggerDataStorage.GetAreaTriggersForMapAndCell(i_map.GetId(), cellCoord.GetId()); + if (areaTriggers.Empty()) + return; + + LoadHelper(areaTriggers, cellCoord, ref i_areaTriggers, i_map); + } + void LoadHelper(SortedSet guid_set, CellCoord cell, ref uint count, Map map) where T : WorldObject, new() { foreach (var guid in guid_set) { T obj = new T(); // Don't spawn at all if there's a respawn time - if ((obj.IsTypeId(TypeId.Unit) && map.GetCreatureRespawnTime(guid) == 0) || (obj.IsTypeId(TypeId.GameObject) && map.GetGORespawnTime(guid) == 0)) + if ((obj.IsTypeId(TypeId.Unit) && map.GetCreatureRespawnTime(guid) == 0) || (obj.IsTypeId(TypeId.GameObject) && map.GetGORespawnTime(guid) == 0) || obj.IsTypeId(TypeId.AreaTrigger)) { //TC_LOG_INFO("misc", "DEBUG: LoadHelper from table: %s for (guid: %u) Loading", table, guid); if (obj.IsTypeId(TypeId.Unit)) @@ -156,6 +166,7 @@ namespace Game.Maps uint i_gameObjects; uint i_creatures; public uint i_corpses; + uint i_areaTriggers; } class ObjectWorldLoader : Notifier diff --git a/Source/Game/Scripting/CoreScripts.cs b/Source/Game/Scripting/CoreScripts.cs index 5c8577c8b..332023dac 100644 --- a/Source/Game/Scripting/CoreScripts.cs +++ b/Source/Game/Scripting/CoreScripts.cs @@ -364,6 +364,7 @@ namespace Game.Scripting // Called when the area trigger is activated by a player. public virtual bool OnTrigger(Player player, AreaTriggerRecord trigger, bool entered) { return false; } + public virtual bool OnTriggerServer(Player player, AreaTriggerTemplate triggerTemplate, bool entered) { return false; } } public class OnlyOnceAreaTriggerScript : AreaTriggerScript