/* * 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 "AreaTrigger.h" #include "AreaTriggerTemplate.h" #include "Containers.h" #include "DatabaseEnv.h" #include "DB2Stores.h" #include "Log.h" #include "MapManager.h" #include "ObjectMgr.h" #include "Timer.h" #include bool operator==(AreaTriggerId const& left, AreaTriggerId const& right) { return left.Id == right.Id && left.IsServerSide == right.IsServerSide; } namespace std { template <> struct hash { std::size_t operator()(AreaTriggerId const& value) const { size_t hashVal = 0; Trinity::hash_combine(hashVal, value.Id); Trinity::hash_combine(hashVal, value.IsServerSide); return hashVal; } }; } namespace { std::unordered_map, std::set> _areaTriggerSpawnsByLocation; std::unordered_map _areaTriggerSpawnsBySpawnId; std::unordered_map _areaTriggerTemplateStore; std::unordered_map _areaTriggerTemplateSpellMisc; } void AreaTriggerDataStore::LoadAreaTriggerTemplates() { uint32 oldMSTime = getMSTime(); std::unordered_map>> verticesBySpellMisc; std::unordered_map>> verticesTargetBySpellMisc; std::unordered_map> splinesBySpellMisc; std::unordered_map> actionsByAreaTrigger; // 0 1 2 3 4 if (QueryResult templateActions = WorldDatabase.Query("SELECT AreaTriggerId, IsServerSide, ActionType, ActionParam, TargetType FROM `areatrigger_template_actions`")) { do { Field* templateActionFields = templateActions->Fetch(); AreaTriggerId areaTriggerId = { templateActionFields[0].GetUInt32(), templateActionFields[1].GetUInt8() == 1 }; 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 (%u,%u) for AreaTriggerId %u and Param %u", actionType, areaTriggerId.Id, uint32(areaTriggerId.IsServerSide), action.Param); continue; } if (targetType >= AREATRIGGER_ACTION_USER_MAX) { TC_LOG_ERROR("sql.sql", "Table `areatrigger_template_actions` has invalid TargetType (%u,%u) for AreaTriggerId %u and Param %u", targetType, areaTriggerId.Id, uint32(areaTriggerId.IsServerSide), 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 (%u,%u) with TargetType=Teleport and Param (%u) not a valid world safe loc entry", areaTriggerId.Id, uint32(areaTriggerId.IsServerSide), 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 if (QueryResult vertices = WorldDatabase.Query("SELECT SpellMiscId, Idx, VerticeX, VerticeY, VerticeTargetX, VerticeTargetY FROM `spell_areatrigger_vertices` ORDER BY `SpellMiscId`, `Idx`")) { do { Field* verticeFields = vertices->Fetch(); uint32 spellMiscId = verticeFields[0].GetUInt32(); verticesBySpellMisc[spellMiscId].emplace_back(verticeFields[2].GetFloat(), verticeFields[3].GetFloat()); if (!verticeFields[4].IsNull() && !verticeFields[5].IsNull()) verticesTargetBySpellMisc[spellMiscId].emplace_back(verticeFields[4].GetFloat(), verticeFields[5].GetFloat()); else if (verticeFields[4].IsNull() != verticeFields[5].IsNull()) TC_LOG_ERROR("sql.sql", "Table `spell_areatrigger_vertices` has listed invalid target vertices (SpellMiscId: %u, Index: %u).", spellMiscId, verticeFields[1].GetUInt32()); } while (vertices->NextRow()); } else { TC_LOG_INFO("server.loading", ">> Loaded 0 AreaTrigger templates polygon vertices. DB table `areatrigger_template_polygon_vertices` is empty."); } // 0 1 2 3 if (QueryResult splines = WorldDatabase.Query("SELECT SpellMiscId, X, Y, Z FROM `spell_areatrigger_splines` ORDER BY `SpellMiscId`, `Idx`")) { do { Field* splineFields = splines->Fetch(); uint32 spellMiscId = splineFields[0].GetUInt32(); splinesBySpellMisc[spellMiscId].emplace_back(splineFields[1].GetFloat(), splineFields[2].GetFloat(), splineFields[3].GetFloat()); } while (splines->NextRow()); } else { TC_LOG_INFO("server.loading", ">> Loaded 0 AreaTrigger templates splines. DB table `spell_areatrigger_splines` is empty."); } // 0 1 2 3 4 5 6 7 8 9 10 if (QueryResult templates = WorldDatabase.Query("SELECT Id, IsServerSide, Type, Flags, Data0, Data1, Data2, Data3, Data4, Data5, ScriptName FROM `areatrigger_template`")) { do { Field* fields = templates->Fetch(); AreaTriggerTemplate areaTriggerTemplate; areaTriggerTemplate.Id.Id = fields[0].GetUInt32(); areaTriggerTemplate.Id.IsServerSide = fields[1].GetUInt8() == 1; uint8 type = fields[2].GetUInt8(); if (type >= AREATRIGGER_TYPE_MAX) { TC_LOG_ERROR("sql.sql", "Table `areatrigger_template` has listed areatrigger (Id: %u, IsServerSide: %u) with invalid type %u.", areaTriggerTemplate.Id.Id, uint32(areaTriggerTemplate.Id.IsServerSide), type); continue; } areaTriggerTemplate.Type = static_cast(type); areaTriggerTemplate.Flags = fields[3].GetUInt32(); if (areaTriggerTemplate.Id.IsServerSide && areaTriggerTemplate.Flags != 0) { TC_LOG_ERROR("sql.sql", "Table `areatrigger_template` has listed server-side areatrigger (Id: %u, IsServerSide: %u) with non-zero flags", areaTriggerTemplate.Id.Id, uint32(areaTriggerTemplate.Id.IsServerSide)); continue; } for (uint8 i = 0; i < MAX_AREATRIGGER_ENTITY_DATA; ++i) areaTriggerTemplate.DefaultDatas.Data[i] = fields[4 + i].GetFloat(); areaTriggerTemplate.ScriptId = sObjectMgr->GetScriptId(fields[10].GetString()); areaTriggerTemplate.Actions = std::move(actionsByAreaTrigger[areaTriggerTemplate.Id]); areaTriggerTemplate.InitMaxSearchRadius(); _areaTriggerTemplateStore[areaTriggerTemplate.Id] = areaTriggerTemplate; } while (templates->NextRow()); } // 0 1 2 3 4 5 6 7 8 9 10 if (QueryResult areatriggerSpellMiscs = WorldDatabase.Query("SELECT SpellMiscId, AreaTriggerId, MoveCurveId, ScaleCurveId, MorphCurveId, FacingCurveId, AnimId, AnimKitId, DecalPropertiesId, TimeToTarget, TimeToTargetScale FROM `spell_areatrigger`")) { do { AreaTriggerMiscTemplate miscTemplate; Field* areatriggerSpellMiscFields = areatriggerSpellMiscs->Fetch(); miscTemplate.MiscId = areatriggerSpellMiscFields[0].GetUInt32(); uint32 areatriggerId = areatriggerSpellMiscFields[1].GetUInt32(); miscTemplate.Template = GetAreaTriggerTemplate({ areatriggerId, false }); if (!miscTemplate.Template) { TC_LOG_ERROR("sql.sql", "Table `spell_areatrigger` reference invalid AreaTriggerId %u for miscId %u", areatriggerId, miscTemplate.MiscId); continue; } #define VALIDATE_AND_SET_CURVE(Curve, Value) \ miscTemplate.Curve = Value; \ if (miscTemplate.Curve && !sCurveStore.LookupEntry(miscTemplate.Curve)) \ { \ TC_LOG_ERROR("sql.sql", "Table `spell_areatrigger` has listed areatrigger (MiscId: %u, Id: %u) with invalid " #Curve " (%u), set to 0!", \ miscTemplate.MiscId, areatriggerId, miscTemplate.Curve); \ miscTemplate.Curve = 0; \ } VALIDATE_AND_SET_CURVE(MoveCurveId, areatriggerSpellMiscFields[2].GetUInt32()); VALIDATE_AND_SET_CURVE(ScaleCurveId, areatriggerSpellMiscFields[3].GetUInt32()); VALIDATE_AND_SET_CURVE(MorphCurveId, areatriggerSpellMiscFields[4].GetUInt32()); VALIDATE_AND_SET_CURVE(FacingCurveId, areatriggerSpellMiscFields[5].GetUInt32()); #undef VALIDATE_AND_SET_CURVE miscTemplate.AnimId = areatriggerSpellMiscFields[6].GetInt32(); miscTemplate.AnimKitId = areatriggerSpellMiscFields[7].GetInt32(); miscTemplate.DecalPropertiesId = areatriggerSpellMiscFields[8].GetUInt32(); miscTemplate.TimeToTarget = areatriggerSpellMiscFields[9].GetUInt32(); miscTemplate.TimeToTargetScale = areatriggerSpellMiscFields[10].GetUInt32(); miscTemplate.PolygonVertices = std::move(verticesBySpellMisc[miscTemplate.MiscId]); miscTemplate.PolygonVerticesTarget = std::move(verticesTargetBySpellMisc[miscTemplate.MiscId]); miscTemplate.SplinePoints = std::move(splinesBySpellMisc[miscTemplate.MiscId]); _areaTriggerTemplateSpellMisc[miscTemplate.MiscId] = miscTemplate; } while (areatriggerSpellMiscs->NextRow()); } else { TC_LOG_INFO("server.loading", ">> Loaded 0 Spell AreaTrigger templates. DB table `spell_areatrigger` is empty."); } // 0 1 2 3 4 5 6 7 if (QueryResult circularMovementInfos = WorldDatabase.Query("SELECT SpellMiscId, StartDelay, CircleRadius, BlendFromRadius, InitialAngle, ZOffset, CounterClockwise, CanLoop FROM `spell_areatrigger_circular` ORDER BY `SpellMiscId`")) { do { Field* circularMovementInfoFields = circularMovementInfos->Fetch(); uint32 spellMiscId = circularMovementInfoFields[0].GetUInt32(); auto atSpellMiscItr = _areaTriggerTemplateSpellMisc.find(spellMiscId); if (atSpellMiscItr == _areaTriggerTemplateSpellMisc.end()) { TC_LOG_ERROR("sql.sql", "Table `spell_areatrigger_circular` reference invalid SpellMiscId %u", spellMiscId); continue; } AreaTriggerOrbitInfo& orbitInfo = atSpellMiscItr->second.OrbitInfo; orbitInfo.StartDelay = circularMovementInfoFields[1].GetUInt32(); #define VALIDATE_AND_SET_FLOAT(Float, Value) \ orbitInfo.Float = Value; \ if (!std::isfinite(orbitInfo.Float)) \ { \ TC_LOG_ERROR("sql.sql", "Table `spell_areatrigger_circular` has listed areatrigger (MiscId: %u) with invalid " #Float " (%f), set to 0!", \ spellMiscId, orbitInfo.Float); \ orbitInfo.Float = 0.0f; \ } VALIDATE_AND_SET_FLOAT(Radius, circularMovementInfoFields[2].GetFloat()); VALIDATE_AND_SET_FLOAT(BlendFromRadius, circularMovementInfoFields[3].GetFloat()); VALIDATE_AND_SET_FLOAT(InitialAngle, circularMovementInfoFields[4].GetFloat()); VALIDATE_AND_SET_FLOAT(ZOffset, circularMovementInfoFields[5].GetFloat()); #undef VALIDATE_AND_SET_FLOAT orbitInfo.CounterClockwise = circularMovementInfoFields[6].GetBool(); orbitInfo.CanLoop = circularMovementInfoFields[7].GetBool(); } while (circularMovementInfos->NextRow()); } else { TC_LOG_INFO("server.loading", ">> Loaded 0 AreaTrigger templates circular movement infos. DB table `spell_areatrigger_circular` is empty."); } TC_LOG_INFO("server.loading", ">> Loaded " SZFMTD " spell areatrigger templates in %u ms.", _areaTriggerTemplateStore.size(), GetMSTimeDiffToNow(oldMSTime)); } void AreaTriggerDataStore::LoadAreaTriggerSpawns() { uint32 oldMSTime = getMSTime(); // Load area trigger positions (to put them on the server) // 0 1 2 3 4 5 6 7 8 9 10 if (QueryResult templates = WorldDatabase.Query("SELECT SpawnId, AreaTriggerId, IsServerSide, MapId, PosX, PosY, PosZ, Orientation, PhaseUseFlags, PhaseId, PhaseGroup FROM `areatrigger`")) { do { Field* fields = templates->Fetch(); ObjectGuid::LowType spawnId = fields[0].GetUInt64(); AreaTriggerId areaTriggerid = { fields[1].GetUInt32(), fields[2].GetUInt8() == 1 }; WorldLocation location(fields[3].GetUInt32(), fields[4].GetFloat(), fields[5].GetFloat(), fields[6].GetFloat(), fields[7].GetFloat()); if (!GetAreaTriggerTemplate(areaTriggerid)) { TC_LOG_ERROR("sql.sql", "Table `areatrigger` has listed areatrigger that doesn't exist: Id: %u, IsServerSide: %u for SpawnId " UI64FMTD, areaTriggerid.Id, uint32(areaTriggerid.IsServerSide), spawnId); continue; } if (!MapManager::IsValidMapCoord(location)) { TC_LOG_ERROR("sql.sql", "Table `areatrigger` has listed an invalid position: SpawnId: " UI64FMTD ", MapId %u, Position {%s}", spawnId, location.GetMapId(), location.ToString().c_str()); continue; } AreaTriggerSpawn spawn; spawn.SpawnId = spawnId; spawn.Id = areaTriggerid; spawn.Location.WorldRelocate(location); spawn.PhaseUseFlags = fields[8].GetUInt8(); spawn.PhaseId = fields[9].GetUInt32(); spawn.PhaseGroup = fields[10].GetUInt32(); // Add the trigger to a map::cell map, which is later used by GridLoader to query CellCoord cellCoord = Trinity::ComputeCellCoord(spawn.Location.GetPositionX(), spawn.Location.GetPositionY()); _areaTriggerSpawnsByLocation[{ spawn.Location.GetMapId(), cellCoord.GetId() }].insert(spawnId); // add the position to the map _areaTriggerSpawnsBySpawnId[spawnId] = spawn; } while (templates->NextRow()); } TC_LOG_INFO("server.loading", ">> Loaded " SZFMTD " areatrigger spawns in %u ms.", _areaTriggerSpawnsBySpawnId.size(), GetMSTimeDiffToNow(oldMSTime)); } AreaTriggerTemplate const* AreaTriggerDataStore::GetAreaTriggerTemplate(AreaTriggerId const& areaTriggerId) const { return Trinity::Containers::MapGetValuePtr(_areaTriggerTemplateStore, areaTriggerId); } AreaTriggerMiscTemplate const* AreaTriggerDataStore::GetAreaTriggerMiscTemplate(uint32 spellMiscValue) const { return Trinity::Containers::MapGetValuePtr(_areaTriggerTemplateSpellMisc, spellMiscValue); } std::set const* AreaTriggerDataStore::GetAreaTriggersForMapAndCell(uint32 mapId, uint32 cellId) const { return Trinity::Containers::MapGetValuePtr(_areaTriggerSpawnsByLocation, { mapId, cellId }); } AreaTriggerSpawn const* AreaTriggerDataStore::GetAreaTriggerSpawn(ObjectGuid::LowType spawnId) const { return Trinity::Containers::MapGetValuePtr(_areaTriggerSpawnsBySpawnId, spawnId); } AreaTriggerDataStore* AreaTriggerDataStore::Instance() { static AreaTriggerDataStore instance; return &instance; }