/* * Copyright (C) 2012-2020 CypherCore * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ using Framework.Constants; using Framework.Database; using Framework.GameMath; using Game.Entities; using Game.Maps; using System.Collections.Generic; namespace Game.DataStorage { public class AreaTriggerDataStorage : Singleton { AreaTriggerDataStorage() { } public void LoadAreaTriggerTemplates() { uint oldMSTime = Time.GetMSTime(); MultiMap verticesByAreaTrigger = new MultiMap(); MultiMap verticesTargetByAreaTrigger = new MultiMap(); MultiMap splinesBySpellMisc = new MultiMap(); MultiMap actionsByAreaTrigger = new MultiMap(); // 0 1 2 3 4 SQLResult templateActions = DB.World.Query("SELECT AreaTriggerId, IsServerSide, ActionType, ActionParam, TargetType FROM `areatrigger_template_actions`"); if (!templateActions.IsEmpty()) { do { AreaTriggerId areaTriggerId = new() { Id = templateActions.Read(0), IsServerSide = templateActions.Read(1) == 1 }; AreaTriggerAction action; action.Param = templateActions.Read(3); action.ActionType = (AreaTriggerActionTypes)templateActions.Read(2); action.TargetType = (AreaTriggerActionUserTypes)templateActions.Read(4); if (action.ActionType >= AreaTriggerActionTypes.Max) { Log.outError(LogFilter.Sql, $"Table `areatrigger_template_actions` has invalid ActionType ({action.ActionType}, IsServerSide: {areaTriggerId.IsServerSide}) for AreaTriggerId {areaTriggerId.Id} and Param {action.Param}"); continue; } if (action.TargetType >= AreaTriggerActionUserTypes.Max) { Log.outError(LogFilter.Sql, $"Table `areatrigger_template_actions` has invalid TargetType ({action.TargetType}, IsServerSide: {areaTriggerId.IsServerSide}) for AreaTriggerId {areaTriggerId} and Param {action.Param}"); continue; } if (action.ActionType == AreaTriggerActionTypes.Teleport) { if (Global.ObjectMgr.GetWorldSafeLoc(action.Param) == null) { Log.outError(LogFilter.Sql, $"Table `areatrigger_template_actions` has invalid (Id: {areaTriggerId}, IsServerSide: {areaTriggerId.IsServerSide}) with TargetType=Teleport and Param ({action.Param}) not a valid world safe loc entry"); continue; } } actionsByAreaTrigger.Add(areaTriggerId, action); } while (templateActions.NextRow()); } else { Log.outInfo(LogFilter.ServerLoading, "Loaded 0 AreaTrigger templates actions. DB table `areatrigger_template_actions` is empty."); } // 0 1 2 3 4 5 SQLResult vertices = DB.World.Query("SELECT AreaTriggerId, Idx, VerticeX, VerticeY, VerticeTargetX, VerticeTargetY FROM `areatrigger_template_polygon_vertices` ORDER BY `AreaTriggerId`, `Idx`"); if (!vertices.IsEmpty()) { do { uint areaTriggerId = vertices.Read(0); verticesByAreaTrigger.Add(areaTriggerId, new Vector2(vertices.Read(2), vertices.Read(3))); if (!vertices.IsNull(4) && !vertices.IsNull(5)) verticesTargetByAreaTrigger.Add(areaTriggerId, new Vector2(vertices.Read(4), vertices.Read(5))); else if (vertices.IsNull(4) != vertices.IsNull(5)) Log.outError(LogFilter.Sql, "Table `areatrigger_template_polygon_vertices` has listed invalid target vertices (AreaTrigger: {0}, Index: {1}).", areaTriggerId, vertices.Read(1)); } while (vertices.NextRow()); } else { Log.outInfo(LogFilter.ServerLoading, "Loaded 0 AreaTrigger templates polygon vertices. DB table `areatrigger_template_polygon_vertices` is empty."); } // 0 1 2 3 SQLResult splines = DB.World.Query("SELECT SpellMiscId, X, Y, Z FROM `spell_areatrigger_splines` ORDER BY `SpellMiscId`, `Idx`"); if (!splines.IsEmpty()) { do { uint spellMiscId = splines.Read(0); Vector3 spline = new Vector3(splines.Read(1), splines.Read(2), splines.Read(3)); splinesBySpellMisc.Add(spellMiscId, spline); } while (splines.NextRow()); } else { 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 10 SQLResult templates = DB.World.Query("SELECT Id, IsServerSide, Type, Flags, Data0, Data1, Data2, Data3, Data4, Data5, ScriptName FROM `areatrigger_template`"); if (!templates.IsEmpty()) { do { AreaTriggerTemplate areaTriggerTemplate = new AreaTriggerTemplate(); areaTriggerTemplate.Id = new() { Id = templates.Read(0), IsServerSide = templates.Read(1) == 1 }; AreaTriggerTypes type = (AreaTriggerTypes)templates.Read(2); if (type >= AreaTriggerTypes.Max) { Log.outError(LogFilter.Sql, $"Table `areatrigger_template` has listed areatrigger (Id: {areaTriggerTemplate.Id.Id}, IsServerSide: {areaTriggerTemplate.Id.IsServerSide}) with invalid type {type}."); continue; } areaTriggerTemplate.TriggerType = type; areaTriggerTemplate.Flags = (AreaTriggerFlags)templates.Read(3); if (areaTriggerTemplate.Id.IsServerSide && areaTriggerTemplate.Flags != 0) { Log.outError(LogFilter.Sql, $"Table `areatrigger_template` has listed server-side areatrigger (Id: {areaTriggerTemplate.Id.Id}, IsServerSide: {areaTriggerTemplate.Id.IsServerSide}) with none-zero flags"); continue; } unsafe { for (byte i = 0; i < SharedConst.MaxAreatriggerEntityData; ++i) areaTriggerTemplate.DefaultDatas.Data[i] = templates.Read(4 + i); } areaTriggerTemplate.ScriptId = Global.ObjectMgr.GetScriptId(templates.Read(10)); if (!areaTriggerTemplate.Id.IsServerSide) { areaTriggerTemplate.PolygonVertices = verticesByAreaTrigger[areaTriggerTemplate.Id.Id]; areaTriggerTemplate.PolygonVerticesTarget = verticesTargetByAreaTrigger[areaTriggerTemplate.Id.Id]; } areaTriggerTemplate.Actions = actionsByAreaTrigger[areaTriggerTemplate.Id]; areaTriggerTemplate.InitMaxSearchRadius(); _areaTriggerTemplateStore[areaTriggerTemplate.Id] = areaTriggerTemplate; } 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()) { do { AreaTriggerMiscTemplate miscTemplate = new AreaTriggerMiscTemplate(); miscTemplate.MiscId = areatriggerSpellMiscs.Read(0); uint areatriggerId = areatriggerSpellMiscs.Read(1); miscTemplate.Template = GetAreaTriggerTemplate(new AreaTriggerId() { Id = areatriggerId, IsServerSide = false }); if (miscTemplate.Template == null) { Log.outError(LogFilter.Sql, "Table `spell_areatrigger` reference invalid AreaTriggerId {0} for miscId {1}", areatriggerId, miscTemplate.MiscId); continue; } uint ValidateAndSetCurve(uint value) { if (value != 0 && !CliDB.CurveStorage.ContainsKey(value)) { Log.outError(LogFilter.Sql, "Table `spell_areatrigger` has listed areatrigger (MiscId: {0}, Id: {1}) with invalid Curve ({2}), set to 0!", miscTemplate.MiscId, areatriggerId, value); return 0; } return value; } miscTemplate.MoveCurveId = ValidateAndSetCurve(areatriggerSpellMiscs.Read(2)); miscTemplate.ScaleCurveId = ValidateAndSetCurve(areatriggerSpellMiscs.Read(3)); miscTemplate.MorphCurveId = ValidateAndSetCurve(areatriggerSpellMiscs.Read(4)); miscTemplate.FacingCurveId = ValidateAndSetCurve(areatriggerSpellMiscs.Read(5)); miscTemplate.AnimId = areatriggerSpellMiscs.Read(6); miscTemplate.AnimKitId = areatriggerSpellMiscs.Read(7); miscTemplate.DecalPropertiesId = areatriggerSpellMiscs.Read(8); miscTemplate.TimeToTarget = areatriggerSpellMiscs.Read(9); miscTemplate.TimeToTargetScale = areatriggerSpellMiscs.Read(10); miscTemplate.SplinePoints = splinesBySpellMisc[miscTemplate.MiscId]; _areaTriggerTemplateSpellMisc[miscTemplate.MiscId] = miscTemplate; } while (areatriggerSpellMiscs.NextRow()); } else { Log.outInfo(LogFilter.ServerLoading, "Loaded 0 Spell AreaTrigger templates. DB table `spell_areatrigger` is empty."); } // 0 1 2 3 4 5 6 7 SQLResult circularMovementInfos = DB.World.Query("SELECT SpellMiscId, StartDelay, CircleRadius, BlendFromRadius, InitialAngle, ZOffset, CounterClockwise, CanLoop FROM `spell_areatrigger_circular` ORDER BY `SpellMiscId`"); if (!circularMovementInfos.IsEmpty()) { do { uint spellMiscId = circularMovementInfos.Read(0); var atSpellMisc = _areaTriggerTemplateSpellMisc.LookupByKey(spellMiscId); if (atSpellMisc == null) { Log.outError(LogFilter.Sql, $"Table `spell_areatrigger_circular` reference invalid SpellMiscId {spellMiscId}"); continue; } AreaTriggerOrbitInfo orbitInfo = new AreaTriggerOrbitInfo(); orbitInfo.StartDelay = circularMovementInfos.Read(1); orbitInfo.Radius = circularMovementInfos.Read(2); if (!float.IsInfinity(orbitInfo.Radius)) { Log.outError(LogFilter.Sql, $"Table `spell_areatrigger_circular` has listed areatrigger (MiscId: {spellMiscId}) with invalid Radius ({orbitInfo.Radius}), set to 0!"); orbitInfo.Radius = 0.0f; } orbitInfo.BlendFromRadius = circularMovementInfos.Read(3); if (!float.IsInfinity(orbitInfo.BlendFromRadius)) { Log.outError(LogFilter.Sql, $"Table `spell_areatrigger_circular` has listed areatrigger (MiscId: {spellMiscId}) with invalid BlendFromRadius ({orbitInfo.BlendFromRadius}), set to 0!"); orbitInfo.BlendFromRadius = 0.0f; } orbitInfo.InitialAngle = circularMovementInfos.Read(4); if (!float.IsInfinity(orbitInfo.InitialAngle)) { Log.outError(LogFilter.Sql, $"Table `spell_areatrigger_circular` has listed areatrigger (MiscId: {spellMiscId}) with invalid InitialAngle ({orbitInfo.InitialAngle}), set to 0!"); orbitInfo.InitialAngle = 0.0f; } orbitInfo.ZOffset = circularMovementInfos.Read(5); if (!float.IsInfinity(orbitInfo.ZOffset)) { Log.outError(LogFilter.Sql, $"Table `spell_areatrigger_circular` has listed areatrigger (MiscId: {spellMiscId}) with invalid ZOffset ({orbitInfo.ZOffset}), set to 0!"); orbitInfo.ZOffset = 0.0f; } orbitInfo.CounterClockwise = circularMovementInfos.Read(6); orbitInfo.CanLoop = circularMovementInfos.Read(7); atSpellMisc.OrbitInfo = orbitInfo; } while (circularMovementInfos.NextRow()); } else { Log.outInfo(LogFilter.ServerLoading, "Loaded 0 AreaTrigger templates circular movement infos. DB table `spell_areatrigger_circular` is empty."); } Log.outInfo(LogFilter.ServerLoading, $"Loaded {_areaTriggerTemplateStore.Count} spell areatrigger templates in {Time.GetMSTimeDiffToNow(oldMSTime)} ms."); } public void LoadAreaTriggerSpawns() { uint oldMSTime = Time.GetMSTime(); // Load area trigger positions (to put them on the server) // 0 1 2 3 4 5 6 7 8 9 10 SQLResult templates = DB.World.Query("SELECT SpawnId, AreaTriggerId, IsServerSide, MapId, PosX, PosY, PosZ, Orientation, PhaseUseFlags, PhaseId, PhaseGroup FROM `areatrigger`"); if (!templates.IsEmpty()) { do { ulong spawnId = templates.Read(0); AreaTriggerId areaTriggerId = new() { Id = templates.Read(1), IsServerSide = templates.Read(2) == 1 }; WorldLocation location = new(templates.Read(3), templates.Read(4), templates.Read(5), templates.Read(6), templates.Read(7)); if (GetAreaTriggerTemplate(areaTriggerId) == null) { Log.outError(LogFilter.Sql, $"Table `areatrigger` has listed areatrigger that doesn't exist: Id: {areaTriggerId.Id}, IsServerSide: {areaTriggerId.IsServerSide} for SpawnId {spawnId}"); continue; } if (!GridDefines.IsValidMapCoord(location)) { Log.outError(LogFilter.Sql, $"Table `areatrigger` has listed an invalid position: SpawnId: {spawnId}, MapId: {location.GetMapId()}, Position: {location}"); continue; } AreaTriggerSpawn spawn = new AreaTriggerSpawn(); spawn.SpawnId = spawnId; spawn.Id = areaTriggerId; spawn.Location = new WorldLocation(location); spawn.PhaseUseFlags = templates.Read(8); spawn.PhaseId = templates.Read(9); spawn.PhaseGroup = templates.Read(10); // Add the trigger to a map::cell map, which is later used by GridLoader to query CellCoord cellCoord = GridDefines.ComputeCellCoord(spawn.Location.GetPositionX(), spawn.Location.GetPositionY()); if (!_areaTriggerSpawnsByLocation.ContainsKey((spawn.Location.GetMapId(), cellCoord.GetId()))) _areaTriggerSpawnsByLocation[(spawn.Location.GetMapId(), cellCoord.GetId())] = new SortedSet(); _areaTriggerSpawnsByLocation[(spawn.Location.GetMapId(), cellCoord.GetId())].Add(spawnId); // add the position to the map _areaTriggerSpawnsBySpawnId[spawnId] = spawn; } while (templates.NextRow()); } Log.outInfo(LogFilter.ServerLoading, $"Loaded {_areaTriggerSpawnsBySpawnId.Count} areatrigger spawns in {Time.GetMSTimeDiffToNow(oldMSTime)} ms."); } public AreaTriggerTemplate GetAreaTriggerTemplate(AreaTriggerId areaTriggerId) { return _areaTriggerTemplateStore.LookupByKey(areaTriggerId); } public AreaTriggerMiscTemplate GetAreaTriggerMiscTemplate(uint spellMiscValue) { return _areaTriggerTemplateSpellMisc.LookupByKey(spellMiscValue); } public SortedSet GetAreaTriggersForMapAndCell(uint mapId, uint cellId) { return _areaTriggerSpawnsByLocation.LookupByKey((mapId, cellId)); } public AreaTriggerSpawn GetAreaTriggerSpawn(ulong spawnId) { return _areaTriggerSpawnsBySpawnId.LookupByKey(spawnId); } Dictionary<(uint mapId, uint cellId), SortedSet> _areaTriggerSpawnsByLocation = new Dictionary<(uint mapId, uint cellId), SortedSet>(); Dictionary _areaTriggerSpawnsBySpawnId = new Dictionary(); Dictionary _areaTriggerTemplateStore = new Dictionary(); Dictionary _areaTriggerTemplateSpellMisc = new Dictionary(); } }