/* * This file is part of the TrinityCore Project. See AUTHORS file for Copyright information * * 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 2 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 . */ #include "AreaTriggerDataStore.h" #include "AreaTriggerTemplate.h" #include "DB2Stores.h" #include "DatabaseEnv.h" #include "Log.h" #include "MapManager.h" #include "MapUtils.h" #include "ObjectMgr.h" #include "QueryResultStructured.h" #include "SpellMgr.h" #include "Timer.h" #include "Types.h" #include template <> struct std::hash { std::size_t operator()(AreaTriggerId const& value) const noexcept { size_t hashVal = 0; Trinity::hash_combine(hashVal, value.Id); Trinity::hash_combine(hashVal, value.IsCustom); return hashVal; } }; namespace { typedef std::unordered_map> AtCellObjectGuidsMap; typedef std::unordered_map, AtCellObjectGuidsMap> AtMapObjectGuids; AtMapObjectGuids _areaTriggerSpawnsByLocation; std::unordered_map _areaTriggerSpawnsBySpawnId; std::unordered_map _areaTriggerTemplateStore; std::unordered_map _areaTriggerCreateProperties; } void AreaTriggerDataStore::LoadAreaTriggerTemplates() { uint32 oldMSTime = getMSTime(); std::unordered_map>> verticesByCreateProperties; std::unordered_map>> verticesTargetByCreateProperties; std::unordered_map> splinesByCreateProperties; std::unordered_map> actionsByAreaTrigger; // 0 1 2 3 4 if (QueryResult templateActions = WorldDatabase.Query("SELECT AreaTriggerId, IsCustom, ActionType, ActionParam, TargetType FROM `areatrigger_template_actions`")) { do { Field* templateActionFields = templateActions->Fetch(); AreaTriggerId areaTriggerId = { templateActionFields[0].GetUInt32(), templateActionFields[1].GetBool()}; AreaTriggerAction action; action.Param = templateActionFields[3].GetUInt32(); uint32 actionType = templateActionFields[2].GetUInt32(); uint32 targetType = templateActionFields[4].GetUInt32(); if (actionType >= AREATRIGGER_ACTION_MAX) { TC_LOG_ERROR("sql.sql", "Table `areatrigger_template_actions` has invalid ActionType {} for AreaTriggerId ({},{}) and Param {}", actionType, areaTriggerId.Id, uint32(areaTriggerId.IsCustom), action.Param); continue; } if (targetType >= AREATRIGGER_ACTION_USER_MAX) { TC_LOG_ERROR("sql.sql", "Table `areatrigger_template_actions` has invalid TargetType {} for AreaTriggerId ({},{}) and Param {}", targetType, areaTriggerId.Id, uint32(areaTriggerId.IsCustom), action.Param); continue; } if (actionType == AREATRIGGER_ACTION_TELEPORT) { if (!sObjectMgr->GetWorldSafeLoc(action.Param)) { TC_LOG_ERROR("sql.sql", "Table `areatrigger_template_actions` has invalid entry for AreaTriggerId ({},{}) with TargetType=Teleport and Param ({}) not a valid world safe loc entry", areaTriggerId.Id, uint32(areaTriggerId.IsCustom), action.Param); continue; } } action.TargetType = AreaTriggerActionUserTypes(targetType); action.ActionType = AreaTriggerActionTypes(actionType); actionsByAreaTrigger[areaTriggerId].push_back(action); } while (templateActions->NextRow()); } else { TC_LOG_INFO("server.loading", ">> Loaded 0 AreaTrigger templates actions. DB table `areatrigger_template_actions` is empty."); } // 0 1 2 3 4 5 6 if (QueryResult vertices = WorldDatabase.Query("SELECT AreaTriggerCreatePropertiesId, IsCustom, Idx, VerticeX, VerticeY, VerticeTargetX, VerticeTargetY FROM `areatrigger_create_properties_polygon_vertex` ORDER BY `AreaTriggerCreatePropertiesId`, `IsCustom`, `Idx`")) { do { Field* verticeFields = vertices->Fetch(); AreaTriggerCreatePropertiesId createPropertiesId = { verticeFields[0].GetUInt32(), verticeFields[1].GetBool() }; verticesByCreateProperties[createPropertiesId].emplace_back(verticeFields[3].GetFloat(), verticeFields[4].GetFloat()); if (!verticeFields[5].IsNull() && !verticeFields[6].IsNull()) verticesTargetByCreateProperties[createPropertiesId].emplace_back(verticeFields[5].GetFloat(), verticeFields[6].GetFloat()); else if (verticeFields[5].IsNull() != verticeFields[6].IsNull()) TC_LOG_ERROR("sql.sql", "Table `areatrigger_create_properties_polygon_vertex` has listed invalid target vertices (AreaTriggerCreatePropertiesId: (Id: {}, IsCustom: {}), Index: {}).", createPropertiesId.Id, uint32(createPropertiesId.IsCustom), verticeFields[1].GetUInt32()); } while (vertices->NextRow()); } else { TC_LOG_INFO("server.loading", ">> Loaded 0 AreaTrigger polygon vertices. DB table `areatrigger_create_properties_polygon_vertex` is empty."); } // 0 1 2 3, 4 if (QueryResult splines = WorldDatabase.Query("SELECT AreaTriggerCreatePropertiesId, IsCustom, X, Y, Z FROM `areatrigger_create_properties_spline_point` ORDER BY `AreaTriggerCreatePropertiesId`, `IsCustom`, `Idx`")) { do { Field* splineFields = splines->Fetch(); AreaTriggerCreatePropertiesId createPropertiesId = { splineFields[0].GetUInt32(), splineFields[1].GetBool() }; splinesByCreateProperties[createPropertiesId].emplace_back(splineFields[2].GetFloat(), splineFields[3].GetFloat(), splineFields[4].GetFloat()); } while (splines->NextRow()); } else { TC_LOG_INFO("server.loading", ">> Loaded 0 AreaTrigger splines. DB table `areatrigger_create_properties_spline_point` is empty."); } // 0 1 2 3 4 if (QueryResult templates = WorldDatabase.Query("SELECT Id, IsCustom, Flags, ActionSetId, ActionSetFlags FROM `areatrigger_template`")) { do { Field* fields = templates->Fetch(); AreaTriggerTemplate areaTriggerTemplate; areaTriggerTemplate.Id.Id = fields[0].GetUInt32(); areaTriggerTemplate.Id.IsCustom = fields[1].GetBool(); areaTriggerTemplate.Flags = AreaTriggerFlag(fields[2].GetUInt32()); areaTriggerTemplate.ActionSetId = fields[3].GetUInt32(); areaTriggerTemplate.ActionSetFlags = AreaTriggerActionSetFlag(fields[4].GetUInt32()); areaTriggerTemplate.Actions = std::move(actionsByAreaTrigger[areaTriggerTemplate.Id]); _areaTriggerTemplateStore[areaTriggerTemplate.Id] = areaTriggerTemplate; } while (templates->NextRow()); } if (QueryResult areatriggerCreateProperties = WorldDatabase.Query("SELECT Id, IsCustom, AreaTriggerId, IsAreatriggerCustom, Flags, " "MoveCurveId, ScaleCurveId, MorphCurveId, FacingCurveId, AnimId, AnimKitId, DecalPropertiesId, SpellForVisuals, TimeToTargetScale, Speed, SpeedIsTime, " "Shape, ShapeData0, ShapeData1, ShapeData2, ShapeData3, ShapeData4, ShapeData5, ShapeData6, ShapeData7, ScriptName FROM `areatrigger_create_properties`")) { do { DEFINE_FIELD_ACCESSOR_CACHE_ANONYMOUS(ResultSet, (Id)(IsCustom)(AreaTriggerId)(IsAreatriggerCustom)(Flags) (MoveCurveId)(ScaleCurveId)(MorphCurveId)(FacingCurveId)(AnimId)(AnimKitId)(DecalPropertiesId)(SpellForVisuals)(TimeToTargetScale)(Speed)(SpeedIsTime) (Shape)(ShapeData0)(ShapeData1)(ShapeData2)(ShapeData3)(ShapeData4)(ShapeData5)(ShapeData6)(ShapeData7)(ScriptName) ) fields { *areatriggerCreateProperties }; AreaTriggerCreatePropertiesId createPropertiesId = { fields.Id().GetUInt32(), fields.IsCustom().GetBool() }; AreaTriggerId areaTriggerId = { fields.AreaTriggerId().GetUInt32(), fields.IsAreatriggerCustom().GetBool() }; AreaTriggerTemplate const* areaTriggerTemplate = GetAreaTriggerTemplate(areaTriggerId); AreaTriggerShapeType shape = AreaTriggerShapeType(fields.Shape().GetUInt8()); if (areaTriggerId.Id && !areaTriggerTemplate) { TC_LOG_ERROR("sql.sql", "Table `areatrigger_create_properties` references invalid AreaTrigger (Id: {}, IsCustom: {}) for AreaTriggerCreatePropertiesId (Id: {}, IsCustom: {})", areaTriggerId.Id, uint32(areaTriggerId.IsCustom), createPropertiesId.Id, uint32(createPropertiesId.IsCustom)); continue; } if (shape == AreaTriggerShapeType::Unk || shape >= AreaTriggerShapeType::Max) { TC_LOG_ERROR("sql.sql", "Table `areatrigger_create_properties` has listed AreaTriggerCreatePropertiesId (Id: {}, IsCustom: {}) with invalid shape {}.", createPropertiesId.Id, uint32(createPropertiesId.IsCustom), uint32(shape)); continue; } AreaTriggerCreateProperties& createProperties = _areaTriggerCreateProperties[createPropertiesId]; createProperties.Id = createPropertiesId; createProperties.Template = areaTriggerTemplate; createProperties.Flags = AreaTriggerCreatePropertiesFlag(fields.Flags().GetUInt32()); #define VALIDATE_AND_SET_CURVE(Curve, Value) \ createProperties.Curve = Value; \ if (createProperties.Curve && !sCurveStore.HasRecord(createProperties.Curve)) \ { \ TC_LOG_ERROR("sql.sql", "Table `areatrigger_create_properties` has listed AreaTrigger (Id: {}, IsCustom: {}) for AreaTriggerCreatePropertiesId (Id: {}, IsCustom: {}) with invalid " #Curve " ({}), set to 0!", \ areaTriggerId.Id, uint32(areaTriggerId.IsCustom), createPropertiesId.Id, uint32(createPropertiesId.IsCustom), createProperties.Curve); \ createProperties.Curve = 0; \ } VALIDATE_AND_SET_CURVE(MoveCurveId, fields.MoveCurveId().GetUInt32()); VALIDATE_AND_SET_CURVE(ScaleCurveId, fields.ScaleCurveId().GetUInt32()); VALIDATE_AND_SET_CURVE(MorphCurveId, fields.MorphCurveId().GetUInt32()); VALIDATE_AND_SET_CURVE(FacingCurveId, fields.FacingCurveId().GetUInt32()); #undef VALIDATE_AND_SET_CURVE createProperties.AnimId = fields.AnimId().GetInt32(); createProperties.AnimKitId = fields.AnimKitId().GetInt32(); createProperties.DecalPropertiesId = fields.DecalPropertiesId().GetUInt32(); createProperties.SpellForVisuals = fields.SpellForVisuals().GetInt32OrNull(); if (createProperties.SpellForVisuals) { if (!sSpellMgr->GetSpellInfo(*createProperties.SpellForVisuals, DIFFICULTY_NONE)) { TC_LOG_ERROR("sql.sql", "Table `areatrigger_create_properties` has AreaTriggerCreatePropertiesId (Id: {}, IsCustom: {}) with invalid SpellForVisual {}, set to none.", createPropertiesId.Id, uint32(createPropertiesId.IsCustom), *createProperties.SpellForVisuals); createProperties.SpellForVisuals.reset(); } } createProperties.TimeToTargetScale = fields.TimeToTargetScale().GetUInt32(); createProperties.Speed = fields.Speed().GetFloat(); createProperties.SpeedIsTime = fields.SpeedIsTime().GetBool(); std::array shapeData = { fields.ShapeData0().GetFloat(), fields.ShapeData1().GetFloat(), fields.ShapeData2().GetFloat(), fields.ShapeData3().GetFloat(), fields.ShapeData4().GetFloat(), fields.ShapeData5().GetFloat(), fields.ShapeData6().GetFloat(), fields.ShapeData7().GetFloat() }; switch (shape) { case AreaTriggerShapeType::Sphere: createProperties.Shape.Data.emplace(shapeData); break; case AreaTriggerShapeType::Box: createProperties.Shape.Data.emplace(shapeData); break; case AreaTriggerShapeType::Polygon: { AreaTriggerShapeInfo::Polygon& polygon = createProperties.Shape.Data.emplace(shapeData); if (polygon.Height <= 0.0f) { polygon.Height = 1.0f; if (polygon.HeightTarget <= 0.0f) polygon.HeightTarget = 1.0f; } if (std::vector>* vertices = Trinity::Containers::MapGetValuePtr(verticesByCreateProperties, createProperties.Id)) polygon.PolygonVertices = std::move(*vertices); if (std::vector>* vertices = Trinity::Containers::MapGetValuePtr(verticesTargetByCreateProperties, createProperties.Id)) polygon.PolygonVerticesTarget = std::move(*vertices); if (!polygon.PolygonVerticesTarget.empty() && polygon.PolygonVertices.size() != polygon.PolygonVerticesTarget.size()) { TC_LOG_ERROR("sql.sql", "Table `areatrigger_create_properties_polygon_vertex` has invalid target vertices, either all or none vertices must have a corresponding target vertex (AreaTriggerCreatePropertiesId: (Id: {}, IsCustom: {})).", createPropertiesId.Id, uint32(createPropertiesId.IsCustom)); polygon.PolygonVerticesTarget.clear(); } break; } case AreaTriggerShapeType::Cylinder: createProperties.Shape.Data.emplace(shapeData); break; case AreaTriggerShapeType::Disk: createProperties.Shape.Data.emplace(shapeData); break; case AreaTriggerShapeType::BoundedPlane: createProperties.Shape.Data.emplace(shapeData); break; default: break; } createProperties.ScriptId = sObjectMgr->GetScriptId(fields.ScriptName().GetStringView()); if (std::vector* spline = Trinity::Containers::MapGetValuePtr(splinesByCreateProperties, createProperties.Id)) createProperties.Movement = std::move(*spline); } while (areatriggerCreateProperties->NextRow()); } else { TC_LOG_INFO("server.loading", ">> Loaded 0 AreaTrigger create properties. DB table `areatrigger_create_properties` is empty."); } // 0 1 2 3 4 5 6 7 8 if (QueryResult circularMovementInfos = WorldDatabase.Query("SELECT AreaTriggerCreatePropertiesId, IsCustom, ExtraTimeForBlending, CircleRadius, BlendFromRadius, InitialAngle, ZOffset, CounterClockwise, CanLoop FROM `areatrigger_create_properties_orbit`")) { do { Field* circularMovementInfoFields = circularMovementInfos->Fetch(); AreaTriggerCreatePropertiesId createPropertiesId = { circularMovementInfoFields[0].GetUInt32(), circularMovementInfoFields[1].GetBool() }; AreaTriggerCreateProperties* createProperties = Trinity::Containers::MapGetValuePtr(_areaTriggerCreateProperties, createPropertiesId); if (!createProperties) { TC_LOG_ERROR("sql.sql", "Table `areatrigger_create_properties_orbit` reference invalid AreaTriggerCreatePropertiesId: (Id: {}, IsCustom: {})", createPropertiesId.Id, uint32(createPropertiesId.IsCustom)); continue; } AreaTriggerOrbitInfo& orbit = createProperties->Movement.emplace(); orbit.ExtraTimeForBlending = circularMovementInfoFields[2].GetInt32(); #define VALIDATE_AND_SET_FLOAT(Float, Value) \ orbit.Float = Value; \ if (!std::isfinite(orbit.Float)) \ { \ TC_LOG_ERROR("sql.sql", "Table `areatrigger_create_properties_orbit` has listed areatrigger (AreaTriggerCreatePropertiesId: {}, IsCustom: {}) with invalid " #Float " ({}), set to 0!", \ createPropertiesId.Id, uint32(createPropertiesId.IsCustom), orbit.Float); \ orbit.Float = 0.0f; \ } VALIDATE_AND_SET_FLOAT(Radius, circularMovementInfoFields[3].GetFloat()); VALIDATE_AND_SET_FLOAT(BlendFromRadius, circularMovementInfoFields[4].GetFloat()); VALIDATE_AND_SET_FLOAT(InitialAngle, circularMovementInfoFields[5].GetFloat()); VALIDATE_AND_SET_FLOAT(ZOffset, circularMovementInfoFields[6].GetFloat()); #undef VALIDATE_AND_SET_FLOAT orbit.CounterClockwise = circularMovementInfoFields[7].GetBool(); orbit.CanLoop = circularMovementInfoFields[8].GetBool(); } while (circularMovementInfos->NextRow()); } else { TC_LOG_INFO("server.loading", ">> Loaded 0 AreaTrigger templates circular movement infos. DB table `areatrigger_create_properties_orbit` is empty."); } TC_LOG_INFO("server.loading", ">> Loaded {} spell areatrigger templates in {} ms.", _areaTriggerTemplateStore.size(), GetMSTimeDiffToNow(oldMSTime)); } void AreaTriggerDataStore::LoadAreaTriggerSpawns() { // build single time for check spawnmask std::unordered_map> spawnMasks; for (MapDifficultyEntry const* mapDifficulty : sMapDifficultyStore) spawnMasks[mapDifficulty->MapID].insert(Difficulty(mapDifficulty->DifficultyID)); uint32 oldMSTime = getMSTime(); // 0 1 2 3 4 5 6 7 8 9 10 11 12 if (QueryResult templates = WorldDatabase.Query("SELECT SpawnId, AreaTriggerCreatePropertiesId, IsCustom, MapId, SpawnDifficulties, PosX, PosY, PosZ, Orientation, PhaseUseFlags, PhaseId, PhaseGroup, ScriptName FROM `areatrigger`")) { do { Field* fields = templates->Fetch(); ObjectGuid::LowType spawnId = fields[0].GetUInt64(); AreaTriggerCreatePropertiesId createPropertiesId = { fields[1].GetUInt32(), fields[2].GetBool() }; WorldLocation location(fields[3].GetUInt32(), fields[5].GetFloat(), fields[6].GetFloat(), fields[7].GetFloat(), fields[8].GetFloat()); AreaTriggerCreateProperties const* createProperties = GetAreaTriggerCreateProperties(createPropertiesId); if (!createProperties) { TC_LOG_ERROR("sql.sql", "Table `areatrigger` has listed AreaTriggerCreatePropertiesId (Id: {}, IsCustom: {}) that doesn't exist for SpawnId {}", createPropertiesId.Id, uint32(createPropertiesId.IsCustom), spawnId); continue; } if (createProperties->Flags != AreaTriggerCreatePropertiesFlag::None) { TC_LOG_ERROR("sql.sql", "Table `areatrigger` has listed AreaTriggerCreatePropertiesId (Id: {}, IsCustom: {}) with non - zero flags", createPropertiesId.Id, uint32(createPropertiesId.IsCustom)); continue; } if (createProperties->ScaleCurveId || createProperties->MorphCurveId || createProperties->FacingCurveId || createProperties->MoveCurveId) { TC_LOG_ERROR("sql.sql", "Table `areatrigger` has listed AreaTriggerCreatePropertiesId (Id: {}, IsCustom: {}) with curve values", createPropertiesId.Id, uint32(createPropertiesId.IsCustom)); continue; } if (createProperties->TimeToTargetScale) { TC_LOG_ERROR("sql.sql", "Table `areatrigger` has listed AreaTriggerCreatePropertiesId (Id: {}, IsCustom: {}) with time to target values", createPropertiesId.Id, uint32(createPropertiesId.IsCustom)); continue; } if (!std::holds_alternative(createProperties->Movement)) { std::string_view movementType = std::visit([&](MovementType const&) { if constexpr (std::is_same_v) return "spline"sv; else if constexpr (std::is_same_v) return "orbit"sv; else if constexpr (std::is_same_v) return ""sv; else static_assert(Trinity::dependant_false_v, "Unsupported movement type"); }, createProperties->Movement); TC_LOG_ERROR("sql.sql", "Table `areatrigger` has listed AreaTriggerCreatePropertiesId (Id: {}, IsCustom: {}) with {}", createPropertiesId.Id, uint32(createPropertiesId.IsCustom), movementType); continue; } if (!MapManager::IsValidMapCoord(location)) { TC_LOG_ERROR("sql.sql", "Table `areatrigger` has listed an invalid position: SpawnId: {}, MapId {}, Position {{}}", spawnId, location.GetMapId(), location.ToString()); continue; } std::vector difficulties = sObjectMgr->ParseSpawnDifficulties(fields[4].GetStringView(), "areatrigger", spawnId, location.GetMapId(), spawnMasks[location.GetMapId()]); if (difficulties.empty()) { TC_LOG_DEBUG("sql.sql", "Table `areatrigger` has areatrigger (GUID: {}) that is not spawned in any difficulty, skipped.", spawnId); continue; } AreaTriggerSpawn& spawn = _areaTriggerSpawnsBySpawnId[spawnId]; spawn.spawnId = spawnId; spawn.mapId = location.GetMapId(); spawn.Id = createPropertiesId; spawn.spawnPoint.Relocate(location); spawn.phaseUseFlags = fields[9].GetUInt8(); spawn.phaseId = fields[10].GetUInt32(); spawn.phaseGroup = fields[11].GetUInt32(); spawn.scriptId = sObjectMgr->GetScriptId(fields[12].GetStringView()); spawn.spawnGroupData = sObjectMgr->GetLegacySpawnGroup(); // Add the trigger to a map::cell map, which is later used by GridLoader to query CellCoord cellCoord = Trinity::ComputeCellCoord(spawn.spawnPoint.GetPositionX(), spawn.spawnPoint.GetPositionY()); for (Difficulty difficulty : difficulties) _areaTriggerSpawnsByLocation[{ spawn.mapId, difficulty }][cellCoord.GetId()].insert(spawnId); } while (templates->NextRow()); } TC_LOG_INFO("server.loading", ">> Loaded {} areatrigger spawns in {} ms.", _areaTriggerSpawnsBySpawnId.size(), GetMSTimeDiffToNow(oldMSTime)); } AreaTriggerTemplate const* AreaTriggerDataStore::GetAreaTriggerTemplate(AreaTriggerId const& areaTriggerId) const { return Trinity::Containers::MapGetValuePtr(_areaTriggerTemplateStore, areaTriggerId); } AreaTriggerCreateProperties const* AreaTriggerDataStore::GetAreaTriggerCreateProperties(AreaTriggerCreatePropertiesId const& areaTriggerCreatePropertiesId) const { return Trinity::Containers::MapGetValuePtr(_areaTriggerCreateProperties, areaTriggerCreatePropertiesId); } std::set const* AreaTriggerDataStore::GetAreaTriggersForMapAndCell(uint32 mapId, Difficulty difficulty, uint32 cellId) const { if (auto* atForMapAndDifficulty = Trinity::Containers::MapGetValuePtr(_areaTriggerSpawnsByLocation, { mapId, difficulty })) return Trinity::Containers::MapGetValuePtr(*atForMapAndDifficulty, cellId); return nullptr; } AreaTriggerSpawn const* AreaTriggerDataStore::GetAreaTriggerSpawn(ObjectGuid::LowType spawnId) const { return Trinity::Containers::MapGetValuePtr(_areaTriggerSpawnsBySpawnId, spawnId); } AreaTriggerDataStore* AreaTriggerDataStore::Instance() { static AreaTriggerDataStore instance; return &instance; }