Initial commit

This commit is contained in:
2026-01-25 22:24:57 +01:00
commit 9bf926e745
9378 changed files with 7405602 additions and 0 deletions

View File

@@ -0,0 +1,330 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*/
#include "BanMgr.h"
#include "AccountMgr.h"
#include "Chat.h"
#include "DatabaseEnv.h"
#include "GameTime.h"
#include "Language.h"
#include "ObjectAccessor.h"
#include "Player.h"
#include "ScriptMgr.h"
#include "World.h"
#include "WorldSession.h"
#include "WorldSessionMgr.h"
BanMgr* BanMgr::instance()
{
static BanMgr instance;
return &instance;
}
/// Ban an account, duration will be parsed using TimeStringToSecs if it is positive, otherwise permban
BanReturn BanMgr::BanAccount(std::string const& AccountName, std::string const& Duration, std::string const& Reason, std::string const& Author)
{
if (AccountName.empty() || Duration.empty())
return BAN_SYNTAX_ERROR;
uint32 DurationSecs = TimeStringToSecs(Duration);
uint32 AccountID = AccountMgr::GetId(AccountName);
if (!AccountID)
return BAN_NOTFOUND;
///- Disconnect all affected players (for IP it can be several)
LoginDatabaseTransaction trans = LoginDatabase.BeginTransaction();
// pussywizard: check existing ban to prevent overriding by a shorter one! >_>
LoginDatabasePreparedStatement* stmtAccountBanned = LoginDatabase.GetPreparedStatement(LOGIN_SEL_ACCOUNT_BANNED);
stmtAccountBanned->SetData(0, AccountID);
PreparedQueryResult banresult = LoginDatabase.Query(stmtAccountBanned);
if (banresult && ((*banresult)[0].Get<uint32>() == (*banresult)[1].Get<uint32>() || ((*banresult)[1].Get<uint32>() > GameTime::GetGameTime().count() + DurationSecs && DurationSecs)))
return BAN_LONGER_EXISTS;
// make sure there is only one active ban
LoginDatabasePreparedStatement* stmt = LoginDatabase.GetPreparedStatement(LOGIN_UPD_ACCOUNT_NOT_BANNED);
stmt->SetData(0, AccountID);
trans->Append(stmt);
// No SQL injection with prepared statements
stmt = LoginDatabase.GetPreparedStatement(LOGIN_INS_ACCOUNT_BANNED);
stmt->SetData(0, AccountID);
stmt->SetData(1, DurationSecs);
stmt->SetData(2, Author);
stmt->SetData(3, Reason);
trans->Append(stmt);
if (WorldSession* session = sWorldSessionMgr->FindSession(AccountID))
if (session->GetPlayerName() != Author)
session->KickPlayer("Ban Account at condition 'FindSession(account)->GetPlayerName() != author'");
if (WorldSession* session = sWorldSessionMgr->FindOfflineSession(AccountID))
if (session->GetPlayerName() != Author)
session->KickPlayer("Ban Account at condition 'FindOfflineSession(account)->GetPlayerName() != author'");
LoginDatabase.CommitTransaction(trans);
if (sWorld->getBoolConfig(CONFIG_SHOW_BAN_IN_WORLD))
{
bool IsPermanetly = true;
if (TimeStringToSecs(Duration) > 0)
IsPermanetly = false;
if (!IsPermanetly)
ChatHandler(nullptr).SendWorldText(LANG_BAN_ACCOUNT_YOUBANNEDMESSAGE_WORLD, Author, AccountName, secsToTimeString(TimeStringToSecs(Duration), true), Reason);
else
ChatHandler(nullptr).SendWorldText(LANG_BAN_ACCOUNT_YOUPERMBANNEDMESSAGE_WORLD, Author, AccountName, Reason);
}
return BAN_SUCCESS;
}
/// Ban an account by player name, duration will be parsed using TimeStringToSecs if it is positive, otherwise permban
BanReturn BanMgr::BanAccountByPlayerName(std::string const& CharacterName, std::string const& Duration, std::string const& Reason, std::string const& Author)
{
if (CharacterName.empty() || Duration.empty())
return BAN_SYNTAX_ERROR;
uint32 DurationSecs = TimeStringToSecs(Duration);
uint32 AccountID = sCharacterCache->GetCharacterAccountIdByName(CharacterName);
if (!AccountID)
return BAN_NOTFOUND;
///- Disconnect all affected players (for IP it can be several)
LoginDatabaseTransaction trans = LoginDatabase.BeginTransaction();
// pussywizard: check existing ban to prevent overriding by a shorter one! >_>
LoginDatabasePreparedStatement* stmtAccountBanned = LoginDatabase.GetPreparedStatement(LOGIN_SEL_ACCOUNT_BANNED);
stmtAccountBanned->SetData(0, AccountID);
PreparedQueryResult banresult = LoginDatabase.Query(stmtAccountBanned);
if (banresult && ((*banresult)[0].Get<uint32>() == (*banresult)[1].Get<uint32>() || ((*banresult)[1].Get<uint32>() > GameTime::GetGameTime().count() + DurationSecs && DurationSecs)))
return BAN_LONGER_EXISTS;
// make sure there is only one active ban
LoginDatabasePreparedStatement* stmt = LoginDatabase.GetPreparedStatement(LOGIN_UPD_ACCOUNT_NOT_BANNED);
stmt->SetData(0, AccountID);
trans->Append(stmt);
// No SQL injection with prepared statements
stmt = LoginDatabase.GetPreparedStatement(LOGIN_INS_ACCOUNT_BANNED);
stmt->SetData(0, AccountID);
stmt->SetData(1, DurationSecs);
stmt->SetData(2, Author);
stmt->SetData(3, Reason);
trans->Append(stmt);
if (WorldSession* session = sWorldSessionMgr->FindSession(AccountID))
if (session->GetPlayerName() != Author)
session->KickPlayer("Ban Account at condition 'FindSession(account)->GetPlayerName() != author'");
if (WorldSession* session = sWorldSessionMgr->FindOfflineSession(AccountID))
if (session->GetPlayerName() != Author)
session->KickPlayer("Ban Account at condition 'FindOfflineSession(account)->GetPlayerName() != author'");
LoginDatabase.CommitTransaction(trans);
if (sWorld->getBoolConfig(CONFIG_SHOW_BAN_IN_WORLD))
{
bool IsPermanetly = true;
if (TimeStringToSecs(Duration) > 0)
IsPermanetly = false;
std::string AccountName;
AccountMgr::GetName(AccountID, AccountName);
if (!IsPermanetly)
ChatHandler(nullptr).SendWorldText(LANG_BAN_ACCOUNT_YOUBANNEDMESSAGE_WORLD, Author, AccountName, secsToTimeString(TimeStringToSecs(Duration), true), Reason);
else
ChatHandler(nullptr).SendWorldText(LANG_BAN_ACCOUNT_YOUPERMBANNEDMESSAGE_WORLD, Author, AccountName, Reason);
}
return BAN_SUCCESS;
}
/// Ban an IP address, duration will be parsed using TimeStringToSecs if it is positive, otherwise permban
BanReturn BanMgr::BanIP(std::string const& IP, std::string const& Duration, std::string const& Reason, std::string const& Author)
{
if (IP.empty() || Duration.empty())
return BAN_SYNTAX_ERROR;
uint32 DurationSecs = TimeStringToSecs(Duration);
// No SQL injection with prepared statements
LoginDatabasePreparedStatement* stmt = LoginDatabase.GetPreparedStatement(LOGIN_SEL_ACCOUNT_BY_IP);
stmt->SetData(0, IP);
PreparedQueryResult resultAccounts = LoginDatabase.Query(stmt);
stmt = LoginDatabase.GetPreparedStatement(LOGIN_INS_IP_BANNED);
stmt->SetData(0, IP);
stmt->SetData(1, DurationSecs);
stmt->SetData(2, Author);
stmt->SetData(3, Reason);
LoginDatabase.Execute(stmt);
if (sWorld->getBoolConfig(CONFIG_SHOW_BAN_IN_WORLD))
{
bool IsPermanetly = true;
if (TimeStringToSecs(Duration) > 0)
IsPermanetly = false;
if (IsPermanetly)
ChatHandler(nullptr).SendWorldText(LANG_BAN_IP_YOUPERMBANNEDMESSAGE_WORLD, Author, IP, Reason);
else
ChatHandler(nullptr).SendWorldText(LANG_BAN_IP_YOUBANNEDMESSAGE_WORLD, Author, IP, secsToTimeString(TimeStringToSecs(Duration), true), Reason);
}
if (!resultAccounts)
return BAN_SUCCESS;
///- Disconnect all affected players (for IP it can be several)
LoginDatabaseTransaction trans = LoginDatabase.BeginTransaction();
do
{
Field* fields = resultAccounts->Fetch();
uint32 AccountID = fields[0].Get<uint32>();
if (WorldSession* session = sWorldSessionMgr->FindSession(AccountID))
if (session->GetPlayerName() != Author)
session->KickPlayer("Ban IP at condition 'FindSession(account)->GetPlayerName() != author'");
if (WorldSession* session = sWorldSessionMgr->FindOfflineSession(AccountID))
if (session->GetPlayerName() != Author)
session->KickPlayer("Ban IP at condition 'FindOfflineSession(account)->GetPlayerName() != author'");
} while (resultAccounts->NextRow());
LoginDatabase.CommitTransaction(trans);
return BAN_SUCCESS;
}
/// Ban an character, duration will be parsed using TimeStringToSecs if it is positive, otherwise permban
BanReturn BanMgr::BanCharacter(std::string const& CharacterName, std::string const& Duration, std::string const& Reason, std::string const& Author)
{
Player* target = ObjectAccessor::FindPlayerByName(CharacterName, false);
uint32 DurationSecs = TimeStringToSecs(Duration);
ObjectGuid TargetGUID;
/// Pick a player to ban if not online
if (!target)
{
TargetGUID = sCharacterCache->GetCharacterGuidByName(CharacterName);
if (!TargetGUID)
return BAN_NOTFOUND;
}
else
TargetGUID = target->GetGUID();
// make sure there is only one active ban
CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_UPD_CHARACTER_BAN);
stmt->SetData(0, TargetGUID.GetCounter());
CharacterDatabase.Execute(stmt);
stmt = CharacterDatabase.GetPreparedStatement(CHAR_INS_CHARACTER_BAN);
stmt->SetData(0, TargetGUID.GetCounter());
stmt->SetData(1, DurationSecs);
stmt->SetData(2, Author);
stmt->SetData(3, Reason);
CharacterDatabase.Execute(stmt);
if (target)
target->GetSession()->KickPlayer("Ban");
if (sWorld->getBoolConfig(CONFIG_SHOW_BAN_IN_WORLD))
{
bool IsPermanetly = true;
if (TimeStringToSecs(Duration) > 0)
IsPermanetly = false;
if (!IsPermanetly)
ChatHandler(nullptr).SendWorldText(LANG_BAN_CHARACTER_YOUBANNEDMESSAGE_WORLD, Author, CharacterName, secsToTimeString(TimeStringToSecs(Duration), true), Reason);
else
ChatHandler(nullptr).SendWorldText(LANG_BAN_CHARACTER_YOUPERMBANNEDMESSAGE_WORLD, Author, CharacterName, Reason);
}
return BAN_SUCCESS;
}
/// Remove a ban from an account
bool BanMgr::RemoveBanAccount(std::string const& AccountName)
{
uint32 AccountID = AccountMgr::GetId(AccountName);
if (!AccountID)
return false;
// NO SQL injection as account is uint32
LoginDatabasePreparedStatement* stmt = LoginDatabase.GetPreparedStatement(LOGIN_UPD_ACCOUNT_NOT_BANNED);
stmt->SetData(0, AccountID);
LoginDatabase.Execute(stmt);
return true;
}
/// Remove a ban from an player name
bool BanMgr::RemoveBanAccountByPlayerName(std::string const& CharacterName)
{
uint32 AccountID = sCharacterCache->GetCharacterAccountIdByName(CharacterName);
if (!AccountID)
return false;
// NO SQL injection as account is uint32
LoginDatabasePreparedStatement* stmt = LoginDatabase.GetPreparedStatement(LOGIN_UPD_ACCOUNT_NOT_BANNED);
stmt->SetData(0, AccountID);
LoginDatabase.Execute(stmt);
return true;
}
/// Remove a ban from an account
bool BanMgr::RemoveBanIP(std::string const& IP)
{
LoginDatabasePreparedStatement* stmt = LoginDatabase.GetPreparedStatement(LOGIN_DEL_IP_NOT_BANNED);
stmt->SetData(0, IP);
LoginDatabase.Execute(stmt);
return true;
}
/// Remove a ban from a character
bool BanMgr::RemoveBanCharacter(std::string const& CharacterName)
{
Player* pBanned = ObjectAccessor::FindPlayerByName(CharacterName, false);
ObjectGuid guid;
/// Pick a player to ban if not online
if (!pBanned)
guid = sCharacterCache->GetCharacterGuidByName(CharacterName);
else
guid = pBanned->GetGUID();
if (!guid)
return false;
CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_UPD_CHARACTER_BAN);
stmt->SetData(0, guid.GetCounter());
CharacterDatabase.Execute(stmt);
return true;
}

View File

@@ -0,0 +1,50 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*/
#ifndef _BAN_MANAGER_H
#define _BAN_MANAGER_H
#include "Common.h"
/// Ban function return codes
enum BanReturn
{
BAN_SUCCESS,
BAN_SYNTAX_ERROR,
BAN_NOTFOUND,
BAN_LONGER_EXISTS
};
class BanMgr
{
public:
static BanMgr* instance();
BanReturn BanAccount(std::string const& AccountName, std::string const& Duration, std::string const& Reason, std::string const& Author);
BanReturn BanAccountByPlayerName(std::string const& CharacterName, std::string const& Duration, std::string const& Reason, std::string const& Author);
BanReturn BanIP(std::string const& IP, std::string const& Duration, std::string const& Reason, std::string const& Author);
BanReturn BanCharacter(std::string const& CharacterName, std::string const& Duration, std::string const& Reason, std::string const& Author);
bool RemoveBanAccount(std::string const& AccountName);
bool RemoveBanAccountByPlayerName(std::string const& CharacterName);
bool RemoveBanIP(std::string const& IP);
bool RemoveBanCharacter(std::string const& CharacterName);
};
#define sBan BanMgr::instance()
#endif // _BAN_MANAGER_H

View File

@@ -0,0 +1,28 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*/
#include "DynamicVisibility.h"
uint8 DynamicVisibilityMgr::visibilitySettingsIndex = 0;
void DynamicVisibilityMgr::Update(uint32 sessionCount)
{
if (sessionCount >= (visibilitySettingsIndex + 1) * ((uint32)VISIBILITY_SETTINGS_PLAYER_INTERVAL) && visibilitySettingsIndex < VISIBILITY_SETTINGS_MAX_INTERVAL_NUM - 1)
++visibilitySettingsIndex;
else if (visibilitySettingsIndex && sessionCount < visibilitySettingsIndex * ((uint32)VISIBILITY_SETTINGS_PLAYER_INTERVAL) - 100)
--visibilitySettingsIndex;
}

View File

@@ -0,0 +1,58 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*/
#ifndef __DYNAMICVISIBILITY_H
#define __DYNAMICVISIBILITY_H
#include "Define.h"
struct VisibilitySettingData
{
uint32 visibilityNotifyDelay;
uint32 aiNotifyDelay;
float requiredMoveDistanceSq;
};
// pussywizard: dynamic visibility settings
// 7 player intervals: 0-499, 500-999, 1000-1499, 1500-1999, 2000-2499, 2500-2999, 3000+
// 5 map types: common, instance, raid, bg, arena
// feel free to add more intervals, change existing ones or move to conf file :P
#define VISIBILITY_SETTINGS_PLAYER_INTERVAL 500
#define VISIBILITY_SETTINGS_MAX_INTERVAL_NUM 7
const VisibilitySettingData VisibilitySettings[VISIBILITY_SETTINGS_MAX_INTERVAL_NUM][5] =
{
{ {300, 150, 1.0f}, {300, 150, 1.0f}, {300, 150, 1.0f}, {300, 150, 1.0f}, {300, 150, 1.0f} }, // 0-499
{ {400, 200, 2.25f}, {400, 200, 2.25f}, {400, 200, 2.25f}, {300, 150, 1.0f}, {300, 150, 1.0f} }, // 500-999
{ {500, 250, 4.0f}, {500, 250, 4.0f}, {500, 250, 4.0f}, {400, 200, 2.25f}, {300, 150, 1.0f} }, // 1000-1499
{ {700, 350, 6.25f}, {700, 350, 6.25f}, {700, 350, 6.25f}, {600, 300, 6.25f}, {300, 200, 1.0f} }, // 1500-1999
{ {1000, 500, 16.0f}, {1000, 500, 16.0f}, {1000, 500, 16.0f}, {1000, 500, 16.0f}, {300, 250, 1.0f} }, // 2000-2499
{ {1000, 500, 16.0f}, {1000, 500, 16.0f}, {1000, 500, 16.0f}, {1000, 500, 16.0f}, {300, 350, 1.0f} }, // 2500-2999
{ {1200, 550, 20.0f}, {1200, 550, 25.0f}, {1200, 550, 25.0f}, {1100, 550, 16.0f}, {300, 350, 1.0f} } // 3000+
};
class DynamicVisibilityMgr
{
public:
static void Update(uint32 sessionCount);
static uint32 GetVisibilityNotifyDelay(uint32 map_type) { return VisibilitySettings[visibilitySettingsIndex][map_type].visibilityNotifyDelay; }
static uint32 GetAINotifyDelay(uint32 map_type) { return VisibilitySettings[visibilitySettingsIndex][map_type].aiNotifyDelay; }
static float GetReqMoveDistSq(uint32 map_type) { return VisibilitySettings[visibilitySettingsIndex][map_type].requiredMoveDistanceSq; }
protected:
static uint8 visibilitySettingsIndex;
};
#endif

View File

@@ -0,0 +1,440 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*/
#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<uint32>();
Graveyard.Map = fields[1].Get<uint32>();
Graveyard.x = fields[2].Get<float>();
Graveyard.y = fields[3].Get<float>();
Graveyard.z = fields[4].Get<float>();
Graveyard.name = fields[5].Get<std::string>();
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>();
uint32 zoneId = fields[1].Get<uint32>();
uint32 team = fields[2].Get<uint16>();
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;
}

View File

@@ -0,0 +1,76 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*/
#ifndef _GAMEGRAVEYARD_H_
#define _GAMEGRAVEYARD_H_
#include "Player.h"
#include "SharedDefines.h"
#include <map>
#include <unordered_map>
struct GraveyardStruct
{
uint32 ID;
uint32 Map;
float x;
float y;
float z;
std::string name;
std::wstring wnameLow;
};
struct GraveyardData
{
uint32 safeLocId;
TeamId teamId;
[[nodiscard]] bool IsNeutralOrFriendlyToTeam(TeamId playerTeamId) const { return teamId == TEAM_NEUTRAL || playerTeamId == TEAM_NEUTRAL || teamId == playerTeamId; }
};
typedef std::multimap<uint32, GraveyardData> WGGraveyardContainer;
typedef std::pair<WGGraveyardContainer::const_iterator, WGGraveyardContainer::const_iterator> GraveyardMapBounds;
typedef std::pair<WGGraveyardContainer::iterator, WGGraveyardContainer::iterator> GraveyardMapBoundsNonConst;
class Graveyard
{
public:
static Graveyard* instance();
typedef std::unordered_map<uint32, GraveyardStruct> GraveyardContainer;
GraveyardStruct const* GetGraveyard(uint32 ID) const;
GraveyardStruct const* GetGraveyard(const std::string& name) const;
GraveyardStruct const* GetDefaultGraveyard(TeamId teamId);
GraveyardStruct const* GetClosestGraveyard(Player* player, TeamId teamId, bool nearCorpse = false);
GraveyardData const* FindGraveyardData(uint32 id, uint32 zone);
GraveyardContainer const& GetGraveyardData() const { return _graveyardStore; }
bool AddGraveyardLink(uint32 id, uint32 zoneId, TeamId teamId, bool persist = true);
void RemoveGraveyardLink(uint32 id, uint32 zoneId, TeamId teamId, bool persist = false);
void LoadGraveyardZones();
void LoadGraveyardFromDB();
private:
GraveyardContainer _graveyardStore;
// for wintergrasp only
WGGraveyardContainer GraveyardStore;
};
#define sGraveyard Graveyard::instance()
#endif // _GAMEGRAVEYARD_H_