// Copyright (c) CypherCore All rights reserved. // Licensed under the GNU GENERAL PUBLIC LICENSE. See LICENSE file in the project root for full license information. using Framework.Constants; using Framework.Database; using Game.Entities; using Game.Maps; using System.Collections.Generic; using System.Numerics; namespace Game.DataStorage { public class AreaTriggerDataStorage : Singleton { AreaTriggerDataStorage() { } public void LoadAreaTriggerTemplates() { uint oldMSTime = Time.GetMSTime(); MultiMap verticesByCreateProperties = new(); MultiMap verticesTargetByCreateProperties = new(); MultiMap splinesByCreateProperties = new(); MultiMap actionsByAreaTrigger = new(); // 0 1 2 3 4 SQLResult templateActions = DB.World.Query("SELECT AreaTriggerId, IsCustom, ActionType, ActionParam, TargetType FROM `areatrigger_template_actions`"); if (!templateActions.IsEmpty()) { do { AreaTriggerId areaTriggerId = new(templateActions.Read(0), templateActions.Read(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}, for AreaTriggerId ({areaTriggerId.Id},{areaTriggerId.IsCustom}) and Param {action.Param}"); continue; } if (action.TargetType >= AreaTriggerActionUserTypes.Max) { Log.outError(LogFilter.Sql, $"Table `areatrigger_template_actions` has invalid TargetType ({action.TargetType} for AreaTriggerId ({areaTriggerId.Id},{areaTriggerId.IsCustom}) 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}, for AreaTriggerId ({areaTriggerId.Id},{areaTriggerId.IsCustom}) 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 6 SQLResult vertices = DB.World.Query("SELECT AreaTriggerCreatePropertiesId, IsCustom, Idx, VerticeX, VerticeY, VerticeTargetX, VerticeTargetY FROM `areatrigger_create_properties_polygon_vertex` ORDER BY `AreaTriggerCreatePropertiesId`, `IsCustom`, `Idx`"); if (!vertices.IsEmpty()) { do { AreaTriggerId createPropertiesId = new (vertices.Read(0), vertices.Read(1)); verticesByCreateProperties.Add(createPropertiesId, new Vector2(vertices.Read(3), vertices.Read(4))); if (!vertices.IsNull(5) && !vertices.IsNull(6)) verticesTargetByCreateProperties.Add(createPropertiesId, new Vector2(vertices.Read(5), vertices.Read(6))); else if (vertices.IsNull(5) != vertices.IsNull(6)) Log.outError(LogFilter.Sql, $"Table `areatrigger_create_properties_polygon_vertex` has listed invalid target vertices (AreaTriggerCreatePropertiesId: (Id: {createPropertiesId.Id}, IsCustom: {createPropertiesId.IsCustom}), Index: {vertices.Read(1)})."); } while (vertices.NextRow()); } else { Log.outInfo(LogFilter.ServerLoading, "Loaded 0 AreaTrigger polygon polygon vertices. DB table `areatrigger_create_properties_polygon_vertex` is empty."); } // 0 1 2 3 4 SQLResult splines = DB.World.Query("SELECT AreaTriggerCreatePropertiesId, IsCustom, X, Y, Z FROM `areatrigger_create_properties_spline_point` ORDER BY `AreaTriggerCreatePropertiesId`, `IsCustom`, `Idx`"); if (!splines.IsEmpty()) { do { AreaTriggerId createPropertiesId = new(splines.Read(0), splines.Read(1)); splinesByCreateProperties.Add(createPropertiesId, new(splines.Read(2), splines.Read(3), splines.Read(4))); } while (splines.NextRow()); } else { Log.outInfo(LogFilter.ServerLoading, "Loaded 0 AreaTrigger splines. DB table `areatrigger_create_properties_spline_point` is empty."); } // 0 1 2 3 4 SQLResult templates = DB.World.Query("SELECT Id, IsCustom, Flags, ActionSetId, ActionSetFlags FROM `areatrigger_template`"); if (!templates.IsEmpty()) { do { AreaTriggerTemplate areaTriggerTemplate = new(); areaTriggerTemplate.Id = new(templates.Read(0), templates.Read(1)); areaTriggerTemplate.Flags = (AreaTriggerFlag)templates.Read(2); areaTriggerTemplate.ActionSetId = templates.Read(3); areaTriggerTemplate.ActionSetFlags = (AreaTriggerActionSetFlag)templates.Read(4); areaTriggerTemplate.Actions = actionsByAreaTrigger[areaTriggerTemplate.Id]; _areaTriggerTemplateStore[areaTriggerTemplate.Id] = areaTriggerTemplate; } while (templates.NextRow()); } // 0 1 2 3 4 SQLResult areatriggerCreateProperties = DB.World.Query("SELECT Id, IsCustom, AreaTriggerId, IsAreatriggerCustom, Flags, " + //5 6 7 8 9 10 11 12 13 14 "MoveCurveId, ScaleCurveId, MorphCurveId, FacingCurveId, AnimId, AnimKitId, DecalPropertiesId, SpellForVisuals, TimeToTargetScale, Speed, " + //15 16 17 18 19 20 21 22 23 24 "Shape, ShapeData0, ShapeData1, ShapeData2, ShapeData3, ShapeData4, ShapeData5, ShapeData6, ShapeData7, ScriptName FROM `areatrigger_create_properties`"); if (!areatriggerCreateProperties.IsEmpty()) { do { AreaTriggerCreateProperties createProperties = new(); AreaTriggerId createPropertiesId = new(areatriggerCreateProperties.Read(0), areatriggerCreateProperties.Read(1)); createProperties.Id = createPropertiesId; AreaTriggerId areaTriggerId = new(areatriggerCreateProperties.Read(2), areatriggerCreateProperties.Read(3)); createProperties.Template = GetAreaTriggerTemplate(areaTriggerId); createProperties.Flags = (AreaTriggerCreatePropertiesFlag)areatriggerCreateProperties.Read(4); AreaTriggerShapeType shape = (AreaTriggerShapeType)areatriggerCreateProperties.Read(15); if (areaTriggerId.Id != 0 && createProperties.Template == null) { Log.outError(LogFilter.Sql, $"Table `areatrigger_create_properties` references invalid AreaTrigger (Id: {areaTriggerId.Id}, IsCustom: {areaTriggerId.IsCustom}) for AreaTriggerCreatePropertiesId (Id: {createPropertiesId.Id}, IsCustom: {createPropertiesId.IsCustom})"); continue; } if (shape >= AreaTriggerShapeType.Max) { Log.outError(LogFilter.Sql, $"Table `areatrigger_create_properties` has listed AreaTriggerCreatePropertiesId (Id: {createPropertiesId.Id}, IsCustom: {createPropertiesId.IsCustom}) with invalid shape {shape}."); continue; } uint ValidateAndSetCurve(uint value) { if (value != 0 && !CliDB.CurveStorage.ContainsKey(value)) { Log.outError(LogFilter.Sql, $"Table `areatrigger_create_properties` has listed AreaTrigger (Id: {areaTriggerId.Id}, IsCustom: {areaTriggerId.IsCustom}) for AreaTriggerCreatePropertiesId (Id: {createPropertiesId.Id}, IsCustom: {createPropertiesId.IsCustom}) with invalid Curve ({value}), set to 0!"); return 0; } return value; } createProperties.MoveCurveId = ValidateAndSetCurve(areatriggerCreateProperties.Read(5)); createProperties.ScaleCurveId = ValidateAndSetCurve(areatriggerCreateProperties.Read(6)); createProperties.MorphCurveId = ValidateAndSetCurve(areatriggerCreateProperties.Read(7)); createProperties.FacingCurveId = ValidateAndSetCurve(areatriggerCreateProperties.Read(8)); createProperties.AnimId = areatriggerCreateProperties.Read(9); createProperties.AnimKitId = areatriggerCreateProperties.Read(10); createProperties.DecalPropertiesId = areatriggerCreateProperties.Read(11); if (!areatriggerCreateProperties.IsNull(12)) { createProperties.SpellForVisuals = areatriggerCreateProperties.Read(12); if (!Global.SpellMgr.HasSpellInfo(createProperties.SpellForVisuals.Value, Difficulty.None)) { Log.outError(LogFilter.Sql, $"Table `areatrigger_create_properties` has AreaTriggerCreatePropertiesId (Id: {createPropertiesId.Id}, IsCustom: {createPropertiesId.IsCustom}) with invalid SpellForVisual {createProperties.SpellForVisuals.Value}, set to none."); createProperties.SpellForVisuals = null; } } createProperties.TimeToTargetScale = areatriggerCreateProperties.Read(13); createProperties.Speed = areatriggerCreateProperties.Read(14); createProperties.Shape.TriggerType = shape; unsafe { for (byte i = 0; i < SharedConst.MaxAreatriggerEntityData; ++i) createProperties.Shape.DefaultDatas.Data[i] = areatriggerCreateProperties.Read(16 + i); } createProperties.ScriptId = Global.ObjectMgr.GetScriptId(areatriggerCreateProperties.Read(24)); if (shape == AreaTriggerShapeType.Polygon) { if (createProperties.Shape.PolygonDatas.Height <= 0.0f) { createProperties.Shape.PolygonDatas.Height = 1.0f; if (createProperties.Shape.PolygonDatas.HeightTarget <= 0.0f) createProperties.Shape.PolygonDatas.HeightTarget = 1.0f; } } createProperties.Shape.PolygonVertices = verticesByCreateProperties[createProperties.Id]; createProperties.Shape.PolygonVerticesTarget = verticesTargetByCreateProperties[createProperties.Id]; if (!createProperties.Shape.PolygonVerticesTarget.Empty() && createProperties.Shape.PolygonVertices.Count != createProperties.Shape.PolygonVerticesTarget.Count) { Log.outError(LogFilter.Sql, $"Table `areatrigger_create_properties_polygon_vertex` has invalid target vertices, either all or none vertices must have a corresponding target vertex (AreaTriggerCreatePropertiesId: (Id: {createPropertiesId.Id}, IsCustom: {createPropertiesId.IsCustom}))."); createProperties.Shape.PolygonVerticesTarget.Clear(); } createProperties.SplinePoints = splinesByCreateProperties[createProperties.Id]; _areaTriggerCreateProperties[createProperties.Id] = createProperties; } while (areatriggerCreateProperties.NextRow()); } else { Log.outInfo(LogFilter.ServerLoading, "Loaded 0 AreaTrigger create properties. DB table `areatrigger_create_properties` is empty."); } // 0 1 2 3 4 5 6 7 8 SQLResult circularMovementInfos = DB.World.Query("SELECT AreaTriggerCreatePropertiesId, IsCustom, StartDelay, CircleRadius, BlendFromRadius, InitialAngle, ZOffset, CounterClockwise, CanLoop FROM `areatrigger_create_properties_orbit`"); if (!circularMovementInfos.IsEmpty()) { do { AreaTriggerId createPropertiesId = new(circularMovementInfos.Read(0), circularMovementInfos.Read(1)); var createProperties = _areaTriggerCreateProperties.LookupByKey(createPropertiesId); if (createProperties == null) { Log.outError(LogFilter.Sql, $"Table `areatrigger_create_properties_orbit` reference invalid AreaTriggerCreatePropertiesId: (Id: {createPropertiesId.Id}, IsCustom: {createPropertiesId.IsCustom})"); continue; } AreaTriggerOrbitInfo orbitInfo = new(); orbitInfo.StartDelay = circularMovementInfos.Read(2); float ValidateAndSetFloat(float value) { if (!float.IsFinite(value)) { Log.outError(LogFilter.Sql, $"Table `areatrigger_create_properties_orbit` has listed areatrigger (AreaTriggerCreatePropertiesId: {createPropertiesId.Id}, IsCustom: {createPropertiesId.IsCustom}) with invalid Float ({value}), set to 0!"); return 0; } return value; } orbitInfo.Radius = ValidateAndSetFloat(circularMovementInfos.Read(3)); orbitInfo.BlendFromRadius = circularMovementInfos.Read(4); orbitInfo.InitialAngle = circularMovementInfos.Read(5); orbitInfo.ZOffset = circularMovementInfos.Read(6); orbitInfo.CounterClockwise = circularMovementInfos.Read(7); orbitInfo.CanLoop = circularMovementInfos.Read(8); createProperties.OrbitInfo = orbitInfo; } while (circularMovementInfos.NextRow()); } else { Log.outInfo(LogFilter.ServerLoading, "Loaded 0 AreaTrigger templates circular movement infos. DB table `areatrigger_create_properties_orbit` is empty."); } Log.outInfo(LogFilter.ServerLoading, $"Loaded {_areaTriggerTemplateStore.Count} spell areatrigger templates in {Time.GetMSTimeDiffToNow(oldMSTime)} ms."); } public void LoadAreaTriggerSpawns() { // build single time for check spawnmask MultiMap spawnMasks = new(); foreach (var mapDifficulty in CliDB.MapDifficultyStorage.Values) spawnMasks.Add(mapDifficulty.MapID, (Difficulty)mapDifficulty.DifficultyID); 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 11 12 SQLResult result = DB.World.Query("SELECT SpawnId, AreaTriggerCreatePropertiesId, IsCustom, MapId, SpawnDifficulties, PosX, PosY, PosZ, Orientation, PhaseUseFlags, PhaseId, PhaseGroup, ScriptName FROM `areatrigger`"); if (!result.IsEmpty()) { do { ulong spawnId = result.Read(0); AreaTriggerId createPropertiesId = new(result.Read(1), result.Read(2)); WorldLocation location = new(result.Read(3), result.Read(5), result.Read(6), result.Read(7), result.Read(8)); AreaTriggerCreateProperties createProperties = GetAreaTriggerCreateProperties(createPropertiesId); if (createProperties == null) { Log.outError(LogFilter.Sql, $"Table `areatrigger` has listed AreaTriggerCreatePropertiesId (Id: {createPropertiesId.Id}, IsCustom: {createPropertiesId.IsCustom}) that doesn't exist for SpawnId {spawnId}"); continue; } if (createProperties.Flags != AreaTriggerCreatePropertiesFlag.None) { Log.outError(LogFilter.Sql, $"Table `areatrigger` has listed AreaTriggerCreatePropertiesId (Id: {createPropertiesId.Id}, IsCustom: {createPropertiesId.IsCustom}) with non - zero flags"); continue; } if (createProperties.ScaleCurveId != 0 || createProperties.MorphCurveId != 0 || createProperties.FacingCurveId != 0 || createProperties.MoveCurveId != 0) { Log.outError(LogFilter.Sql, $"Table `areatrigger` has listed AreaTriggerCreatePropertiesId (Id: {createPropertiesId.Id}, IsCustom: {createPropertiesId.IsCustom}) with curve values"); continue; } if (createProperties.TimeToTargetScale != 0) { Log.outError(LogFilter.Sql, $"Table `areatrigger` has listed AreaTriggerCreatePropertiesId (Id: {createPropertiesId.Id}, IsCustom: {createPropertiesId.IsCustom}) with time to target values"); continue; } if (createProperties.OrbitInfo != null) { Log.outError(LogFilter.Sql, $"Table `areatrigger` has listed AreaTriggerCreatePropertiesId (Id: {createPropertiesId.Id}, IsCustom: {createPropertiesId.IsCustom}) with orbit info"); continue; } if (createProperties.HasSplines()) { Log.outError(LogFilter.Sql, $"Table `areatrigger` has listed AreaTriggerCreatePropertiesId (Id: {createPropertiesId.Id}, IsCustom: {createPropertiesId.IsCustom}) with splines"); continue; } if (!GridDefines.IsValidMapCoord(location)) { Log.outError(LogFilter.Sql, $"Table `areatrigger` has listed an invalid position: SpawnId: {spawnId}, MapId {location.GetMapId()}, Position {location}"); continue; } var difficulties = Global.ObjectMgr.ParseSpawnDifficulties(result.Read(4), "areatrigger", spawnId, location.GetMapId(), spawnMasks[location.GetMapId()]); if (difficulties.Empty()) { Log.outDebug(LogFilter.Sql, $"Table `areatrigger` has areatrigger (GUID: {spawnId}) that is not spawned in any difficulty, skipped."); continue; } AreaTriggerSpawn spawn = new(); spawn.SpawnId = spawnId; spawn.MapId = location.GetMapId(); spawn.Id = createPropertiesId; spawn.SpawnPoint = new Position(location); spawn.PhaseUseFlags = (PhaseUseFlagsValues)result.Read(9); spawn.PhaseId = result.Read(10); spawn.PhaseGroup = result.Read(11); spawn.ScriptId = Global.ObjectMgr.GetScriptId(result.Read(12)); spawn.spawnGroupData = Global.ObjectMgr.GetLegacySpawnGroup(); // Add the trigger to a map::cell map, which is later used by GridLoader to query CellCoord cellCoord = GridDefines.ComputeCellCoord(spawn.SpawnPoint.GetPositionX(), spawn.SpawnPoint.GetPositionY()); foreach (Difficulty difficulty in difficulties) { if (!_areaTriggerSpawnsByLocation.ContainsKey((spawn.MapId, difficulty))) _areaTriggerSpawnsByLocation[(spawn.MapId, difficulty)] = new Dictionary>(); if (!_areaTriggerSpawnsByLocation[(spawn.MapId, difficulty)].ContainsKey(cellCoord.GetId())) _areaTriggerSpawnsByLocation[(spawn.MapId, difficulty)][cellCoord.GetId()] = new SortedSet(); _areaTriggerSpawnsByLocation[(spawn.MapId, difficulty)][cellCoord.GetId()].Add(spawnId); } // add the position to the map _areaTriggerSpawnsBySpawnId[spawnId] = spawn; } while (result.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 AreaTriggerCreateProperties GetAreaTriggerCreateProperties(AreaTriggerId areaTriggerCreatePropertiesId) { return _areaTriggerCreateProperties.LookupByKey(areaTriggerCreatePropertiesId); } public SortedSet GetAreaTriggersForMapAndCell(uint mapId, Difficulty difficulty, uint cellId) { var atForMapAndDifficulty = _areaTriggerSpawnsByLocation.LookupByKey((mapId, difficulty)); if (atForMapAndDifficulty != null) return atForMapAndDifficulty.LookupByKey(cellId); return null; } public AreaTriggerSpawn GetAreaTriggerSpawn(ulong spawnId) { return _areaTriggerSpawnsBySpawnId.LookupByKey(spawnId); } Dictionary<(uint, Difficulty), Dictionary>> _areaTriggerSpawnsByLocation = new(); Dictionary _areaTriggerSpawnsBySpawnId = new(); Dictionary _areaTriggerTemplateStore = new(); Dictionary _areaTriggerCreateProperties = new(); } }