/* * This file is part of the AzerothCore 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 "GameGraveyard.h" #include "DBCStores.h" #include "DatabaseEnv.h" #include "Log.h" #include "MapMgr.h" #include "ScriptMgr.h" Graveyard* Graveyard::instance() { static Graveyard instance; return &instance; } void Graveyard::LoadGraveyardFromDB() { uint32 oldMSTime = getMSTime(); _graveyardStore.clear(); QueryResult result = WorldDatabase.Query("SELECT ID, Map, x, y, z, Comment FROM game_graveyard"); if (!result) { LOG_WARN("server.loading", ">> Loaded 0 graveyard. Table `game_graveyard` is empty!"); LOG_INFO("server.loading", " "); return; } int32 Count = 0; do { GraveyardStruct Graveyard; Field* fields = result->Fetch(); Graveyard.ID = fields[0].Get(); Graveyard.Map = fields[1].Get(); Graveyard.x = fields[2].Get(); Graveyard.y = fields[3].Get(); Graveyard.z = fields[4].Get(); Graveyard.name = fields[5].Get(); if (!Utf8toWStr(Graveyard.name, Graveyard.wnameLow)) { LOG_ERROR("sql.sql", "Wrong UTF8 name for id {} in `game_graveyard` table, ignoring.", Graveyard.ID); continue; } wstrToLower(Graveyard.wnameLow); _graveyardStore[Graveyard.ID] = std::move(Graveyard); ++Count; } while (result->NextRow()); LOG_INFO("server.loading", ">> Loaded {} Graveyard in {} ms", Count, GetMSTimeDiffToNow(oldMSTime)); LOG_INFO("server.loading", " "); } GraveyardStruct const* Graveyard::GetGraveyard(uint32 ID) const { GraveyardContainer::const_iterator itr = _graveyardStore.find(ID); if (itr != _graveyardStore.end()) return &itr->second; return nullptr; } GraveyardStruct const* Graveyard::GetDefaultGraveyard(TeamId teamId) { enum DefaultGraveyard { HORDE_GRAVEYARD = 10, // Crossroads ALLIANCE_GRAVEYARD = 4, // Westfall }; return GetGraveyard(teamId == TEAM_HORDE ? HORDE_GRAVEYARD : ALLIANCE_GRAVEYARD); } GraveyardStruct const* Graveyard::GetClosestGraveyard(Player* player, TeamId teamId, bool nearCorpse) { uint32 graveyardOverride = 0; sScriptMgr->OnPlayerBeforeChooseGraveyard(player, teamId, nearCorpse, graveyardOverride); if (graveyardOverride) { return GetGraveyard(graveyardOverride); } WorldLocation loc = player->GetWorldLocation(); if (nearCorpse) { loc = player->GetCorpseLocation(); } uint32 mapId = loc.GetMapId(); float x = loc.GetPositionX(); float y = loc.GetPositionY(); float z = loc.GetPositionZ(); uint32 zoneId = 0; uint32 areaId = 0; player->GetZoneAndAreaId(zoneId, areaId); if (!zoneId && !areaId) { if (z > -500) { LOG_ERROR("sql.sql", "GetClosestGraveyard: unable to find zoneId and areaId for map {} coords ({}, {}, {})", mapId, x, y, z); return GetDefaultGraveyard(teamId); } } // Simulate std. algorithm: // found some graveyard associated to (ghost_zone, ghost_map) // // if mapId == graveyard.mapId (ghost in plain zone or city or battleground) and search graveyard at same map // then check faction // if mapId != graveyard.mapId (ghost in instance) and search any graveyard associated // then check faction // Fetch the graveyards linked to the areaId first, presumably the closer ones. GraveyardMapBounds range = GraveyardStore.equal_range(areaId); // No graveyards linked to the area, search zone. if (range.first == range.second) { range = GraveyardStore.equal_range(zoneId); } else // Found a graveyard linked to the area, check if it's a valid one. { GraveyardData const& graveyardLink = range.first->second; if (!graveyardLink.IsNeutralOrFriendlyToTeam(teamId)) { // Not a friendly or neutral graveyard, search zone. range = GraveyardStore.equal_range(zoneId); } } MapEntry const* map = sMapStore.LookupEntry(mapId); // not need to check validity of map object; MapId _MUST_ be valid here if (range.first == range.second && !map->IsBattlegroundOrArena()) { LOG_ERROR("sql.sql", "Table `graveyard_zone` incomplete: Zone {} Team {} does not have a linked graveyard.", zoneId, teamId); return GetDefaultGraveyard(teamId); } // at corpse map bool foundNear = false; float distNear = 10000; GraveyardStruct const* entryNear = nullptr; // at entrance map for corpse map bool foundEntr = false; float distEntr = 10000; GraveyardStruct const* entryEntr = nullptr; // some where other GraveyardStruct const* entryFar = nullptr; MapEntry const* mapEntry = sMapStore.LookupEntry(mapId); for (; range.first != range.second; ++range.first) { GraveyardData const& graveyardLink = range.first->second; GraveyardStruct const* entry = GetGraveyard(graveyardLink.safeLocId); if (!entry) { LOG_ERROR("sql.sql", "Table `graveyard_zone` has record for not existing `game_graveyard` table {}, skipped.", graveyardLink.safeLocId); continue; } // Skip enemy faction graveyard. if (!graveyardLink.IsNeutralOrFriendlyToTeam(teamId)) { continue; } // Skip Archerus graveyards if the player isn't a Death Knight. enum DeathKnightGraveyards { GRAVEYARD_EBON_HOLD = 1369, GRAVEYARD_ARCHERUS = 1405 }; if (!player->IsClass(CLASS_DEATH_KNIGHT, CLASS_CONTEXT_GRAVEYARD) && (graveyardLink.safeLocId == GRAVEYARD_EBON_HOLD || graveyardLink.safeLocId == GRAVEYARD_ARCHERUS)) { continue; } // find now nearest graveyard at other map if (mapId != entry->Map) { // if find graveyard at different map from where entrance placed (or no entrance data), use any first if (!mapEntry || mapEntry->entrance_map < 0 || uint32(mapEntry->entrance_map) != entry->Map || (mapEntry->entrance_x == 0 && mapEntry->entrance_y == 0)) { // not have any corrdinates for check distance anyway entryFar = entry; continue; } // at entrance map calculate distance (2D); float dist2 = (entry->x - mapEntry->entrance_x) * (entry->x - mapEntry->entrance_x) + (entry->y - mapEntry->entrance_y) * (entry->y - mapEntry->entrance_y); if (foundEntr) { if (dist2 < distEntr) { distEntr = dist2; entryEntr = entry; } } else { foundEntr = true; distEntr = dist2; entryEntr = entry; } } // find now nearest graveyard at same map else { float dist2 = (entry->x - x) * (entry->x - x) + (entry->y - y) * (entry->y - y) + (entry->z - z) * (entry->z - z); if (foundNear) { if (dist2 < distNear) { distNear = dist2; entryNear = entry; } } else { foundNear = true; distNear = dist2; entryNear = entry; } } } if (entryNear) return entryNear; if (entryEntr) return entryEntr; return entryFar; } GraveyardData const* Graveyard::FindGraveyardData(uint32 id, uint32 zoneId) { GraveyardMapBounds range = GraveyardStore.equal_range(zoneId); for (; range.first != range.second; ++range.first) { GraveyardData const& data = range.first->second; if (data.safeLocId == id) return &data; } return nullptr; } bool Graveyard::AddGraveyardLink(uint32 id, uint32 zoneId, TeamId teamId, bool persist /*= true*/) { if (FindGraveyardData(id, zoneId)) return false; // add link to loaded data GraveyardData data; data.safeLocId = id; data.teamId = teamId; GraveyardStore.insert(WGGraveyardContainer::value_type(zoneId, data)); // add link to DB if (persist) { WorldDatabasePreparedStatement* stmt = WorldDatabase.GetPreparedStatement(WORLD_INS_GRAVEYARD_ZONE); stmt->SetData(0, id); stmt->SetData(1, zoneId); // Xinef: DB Data compatibility... stmt->SetData(2, uint16(teamId == TEAM_NEUTRAL ? 0 : (teamId == TEAM_ALLIANCE ? ALLIANCE : HORDE))); WorldDatabase.Execute(stmt); } return true; } void Graveyard::RemoveGraveyardLink(uint32 id, uint32 zoneId, TeamId teamId, bool persist /*= false*/) { GraveyardMapBoundsNonConst range = GraveyardStore.equal_range(zoneId); if (range.first == range.second) { LOG_ERROR("sql.sql", "Table `graveyard_zone` incomplete: Zone {} Team {} does not have a linked graveyard.", zoneId, teamId); return; } bool found = false; for (; range.first != range.second; ++range.first) { GraveyardData& data = range.first->second; // skip not matching safezone id if (data.safeLocId != id) continue; // skip enemy faction graveyard at same map (normal area, city, or battleground) // team == 0 case can be at call from .neargrave if (data.teamId != TEAM_NEUTRAL && teamId != TEAM_NEUTRAL && data.teamId != teamId) continue; found = true; break; } // no match, return if (!found) return; // remove from links GraveyardStore.erase(range.first); // remove link from DB if (persist) { WorldDatabasePreparedStatement* stmt = WorldDatabase.GetPreparedStatement(WORLD_DEL_GRAVEYARD_ZONE); stmt->SetData(0, id); stmt->SetData(1, zoneId); // Xinef: DB Data compatibility... stmt->SetData(2, uint16(teamId == TEAM_NEUTRAL ? 0 : (teamId == TEAM_ALLIANCE ? ALLIANCE : HORDE))); WorldDatabase.Execute(stmt); } } void Graveyard::LoadGraveyardZones() { uint32 oldMSTime = getMSTime(); GraveyardStore.clear(); // need for reload case // 0 1 2 QueryResult result = WorldDatabase.Query("SELECT ID, GhostZone, Faction FROM graveyard_zone"); if (!result) { LOG_WARN("server.loading", ">> Loaded 0 Graveyard-Zone Links. DB Table `graveyard_zone` Is Empty."); LOG_INFO("server.loading", " "); return; } uint32 count = 0; do { ++count; Field* fields = result->Fetch(); uint32 safeLocId = fields[0].Get(); uint32 zoneId = fields[1].Get(); uint32 team = fields[2].Get(); TeamId teamId = team == 0 ? TEAM_NEUTRAL : (team == ALLIANCE ? TEAM_ALLIANCE : TEAM_HORDE); GraveyardStruct const* entry = GetGraveyard(safeLocId); if (!entry) { LOG_ERROR("sql.sql", "Table `graveyard_zone` has a record for not existing `game_graveyard` table {}, skipped.", safeLocId); continue; } AreaTableEntry const* areaEntry = sAreaTableStore.LookupEntry(zoneId); if (!areaEntry) { LOG_ERROR("sql.sql", "Table `graveyard_zone` has a record for not existing zone id ({}), skipped.", zoneId); continue; } if (team != 0 && team != HORDE && team != ALLIANCE) { LOG_ERROR("sql.sql", "Table `graveyard_zone` has a record for non player faction ({}), skipped.", team); continue; } if (!AddGraveyardLink(safeLocId, zoneId, teamId, false)) LOG_ERROR("sql.sql", "Table `graveyard_zone` has a duplicate record for Graveyard (ID: {}) and Zone (ID: {}), skipped.", safeLocId, zoneId); } while (result->NextRow()); LOG_INFO("server.loading", ">> Loaded {} Graveyard-Zone Links in {} ms", count, GetMSTimeDiffToNow(oldMSTime)); LOG_INFO("server.loading", " "); } GraveyardStruct const* Graveyard::GetGraveyard(const std::string& name) const { // explicit name case std::wstring wname; if (!Utf8toWStr(name, wname)) return nullptr; // converting string that we try to find to lower case wstrToLower(wname); // Alternative first GameTele what contains wnameLow as substring in case no GameTele location found const GraveyardStruct* alt = nullptr; for (GraveyardContainer::const_iterator itr = _graveyardStore.begin(); itr != _graveyardStore.end(); ++itr) { if (itr->second.wnameLow == wname) return &itr->second; else if (!alt && itr->second.wnameLow.find(wname) != std::wstring::npos) alt = &itr->second; } return alt; }