/*
* 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 .
*/
/** \file
\ingroup world
*/
#include "World.h"
#include "AccountMgr.h"
#include "AchievementMgr.h"
#include "AreaTriggerDataStore.h"
#include "ArenaTeamMgr.h"
#include "AuctionHouseBot.h"
#include "AuctionHouseMgr.h"
#include "AuthenticationPackets.h"
#include "BattlePetMgr.h"
#include "BattlefieldMgr.h"
#include "BattlegroundMgr.h"
#include "BattlenetRpcErrorCodes.h"
#include "BlackMarketMgr.h"
#include "CalendarMgr.h"
#include "ChannelMgr.h"
#include "CharacterCache.h"
#include "CharacterDatabaseCleaner.h"
#include "CharacterTemplateDataStore.h"
#include "Chat.h"
#include "ChatCommand.h"
#include "ChatPackets.h"
#include "Config.h"
#include "Containers.h"
#include "ConversationDataStore.h"
#include "CreatureAIRegistry.h"
#include "CreatureGroups.h"
#include "CreatureTextMgr.h"
#include "DB2Stores.h"
#include "DatabaseEnv.h"
#include "DetourMemoryFunctions.h"
#include "DisableMgr.h"
#include "GameEventMgr.h"
#include "GameObjectModel.h"
#include "GameTables.h"
#include "GameTime.h"
#include "GarrisonMgr.h"
#include "GitRevision.h"
#include "GridNotifiersImpl.h"
#include "GroupMgr.h"
#include "GuildMgr.h"
#include "IPLocation.h"
#include "InstanceLockMgr.h"
#include "ItemBonusMgr.h"
#include "LFGMgr.h"
#include "Language.h"
#include "LanguageMgr.h"
#include "Log.h"
#include "LootItemStorage.h"
#include "LootMgr.h"
#include "M2Stores.h"
#include "MMapFactory.h"
#include "Map.h"
#include "MapManager.h"
#include "MapUtils.h"
#include "Metric.h"
#include "MiscPackets.h"
#include "ObjectAccessor.h"
#include "ObjectMgr.h"
#include "OutdoorPvPMgr.h"
#include "PetitionMgr.h"
#include "Player.h"
#include "PlayerDump.h"
#include "PoolMgr.h"
#include "QuestPools.h"
#include "RealmList.h"
#include "ScenarioMgr.h"
#include "ScriptMgr.h"
#include "ScriptReloadMgr.h"
#include "SkillDiscovery.h"
#include "SkillExtraItems.h"
#include "SmartScriptMgr.h"
#include "SpellMgr.h"
#include "SupportMgr.h"
#include "TaxiPathGraph.h"
#include "TerrainMgr.h"
#include "TraitMgr.h"
#include "TransportMgr.h"
#include "Unit.h"
#include "UpdateTime.h"
#include "VMapFactory.h"
#include "VMapManager2.h"
#include "WardenCheckMgr.h"
#include "WaypointManager.h"
#include "WeatherMgr.h"
#include "WhoListStorage.h"
#include "WorldSession.h"
#include "WorldStateMgr.h"
#include
TC_GAME_API std::atomic World::m_stopEvent(false);
TC_GAME_API uint8 World::m_ExitCode = SHUTDOWN_EXIT_CODE;
TC_GAME_API std::atomic World::m_worldLoopCounter(0);
struct PersistentWorldVariable
{
std::string Id;
};
PersistentWorldVariable const World::NextCurrencyResetTimeVarId{ "NextCurrencyResetTime" };
PersistentWorldVariable const World::NextWeeklyQuestResetTimeVarId{ "NextWeeklyQuestResetTime" };
PersistentWorldVariable const World::NextBGRandomDailyResetTimeVarId{ "NextBGRandomDailyResetTime" };
PersistentWorldVariable const World::CharacterDatabaseCleaningFlagsVarId{ "PersistentCharacterCleanFlags" };
PersistentWorldVariable const World::NextGuildDailyResetTimeVarId{ "NextGuildDailyResetTime" };
PersistentWorldVariable const World::NextMonthlyQuestResetTimeVarId{ "NextMonthlyQuestResetTime" };
PersistentWorldVariable const World::NextDailyQuestResetTimeVarId{ "NextDailyQuestResetTime" };
PersistentWorldVariable const World::NextOldCalendarEventDeletionTimeVarId{ "NextOldCalendarEventDeletionTime" };
PersistentWorldVariable const World::NextGuildWeeklyResetTimeVarId{ "NextGuildWeeklyResetTime" };
/// World constructor
World::World()
{
m_playerLimit = 0;
m_allowedSecurityLevel = SEC_PLAYER;
m_allowMovement = true;
m_ShutdownMask = 0;
m_ShutdownTimer = 0;
m_maxActiveSessionCount = 0;
m_maxQueuedSessionCount = 0;
m_PlayerCount = 0;
m_MaxPlayerCount = 0;
m_NextDailyQuestReset = 0;
m_NextWeeklyQuestReset = 0;
m_NextMonthlyQuestReset = 0;
m_NextRandomBGReset = 0;
m_NextCalendarOldEventsDeletionTime = 0;
m_NextGuildReset = 0;
m_NextCurrencyReset = 0;
m_defaultDbcLocale = LOCALE_enUS;
m_availableDbcLocaleMask = 0;
mail_timer = 0;
mail_timer_expires = 0;
blackmarket_timer = 0;
m_isClosed = false;
m_CleaningFlags = 0;
memset(rate_values, 0, sizeof(rate_values));
memset(m_int_configs, 0, sizeof(m_int_configs));
memset(m_int64_configs, 0, sizeof(m_int64_configs));
memset(m_bool_configs, 0, sizeof(m_bool_configs));
memset(m_float_configs, 0, sizeof(m_float_configs));
_guidWarn = false;
_guidAlert = false;
_warnDiff = 0;
_warnShutdownTime = GameTime::GetGameTime();
}
/// World destructor
World::~World()
{
///- Empty the kicked session set
while (!m_sessions.empty())
{
// not remove from queue, prevent loading new sessions
delete m_sessions.begin()->second;
m_sessions.erase(m_sessions.begin());
}
CliCommandHolder* command = nullptr;
while (cliCmdQueue.next(command))
delete command;
VMAP::VMapFactory::clear();
MMAP::MMapFactory::clear();
/// @todo free addSessQueue
}
World* World::instance()
{
static World instance;
return &instance;
}
/// Find a player in a specified zone
Player* World::FindPlayerInZone(uint32 zone)
{
///- circle through active sessions and return the first player found in the zone
SessionMap::const_iterator itr;
for (itr = m_sessions.begin(); itr != m_sessions.end(); ++itr)
{
if (!itr->second)
continue;
Player* player = itr->second->GetPlayer();
if (!player)
continue;
if (player->IsInWorld() && player->GetZoneId() == zone)
return player;
}
return nullptr;
}
bool World::IsClosed() const
{
return m_isClosed;
}
void World::SetClosed(bool val)
{
m_isClosed = val;
// Invert the value, for simplicity for scripters.
sScriptMgr->OnOpenStateChange(!val);
}
void World::LoadDBAllowedSecurityLevel()
{
if (std::shared_ptr currentRealm = sRealmList->GetCurrentRealm())
SetPlayerSecurityLimit(currentRealm->AllowedSecurityLevel);
}
void World::SetPlayerSecurityLimit(AccountTypes _sec)
{
AccountTypes sec = _sec < SEC_CONSOLE ? _sec : SEC_PLAYER;
bool update = sec > m_allowedSecurityLevel;
m_allowedSecurityLevel = sec;
if (update)
KickAllLess(m_allowedSecurityLevel);
}
void World::SetMotd(std::string motd)
{
/// we are using a string copy here to allow modifications in script hooks
sScriptMgr->OnMotdChange(motd);
_motd.clear();
std::vector tokens = Trinity::Tokenize(motd, '@', true);
_motd.reserve(tokens.size());
for (std::string_view const& token : tokens)
_motd.emplace_back(token);
}
std::vector const& World::GetMotd() const
{
return _motd;
}
void World::TriggerGuidWarning()
{
// Lock this only to prevent multiple maps triggering at the same time
std::lock_guard lock(_guidAlertLock);
time_t gameTime = GameTime::GetGameTime();
time_t today = (gameTime / DAY) * DAY;
// Check if our window to restart today has passed. 5 mins until quiet time
while (gameTime >= GetLocalHourTimestamp(today, getIntConfig(CONFIG_RESPAWN_RESTARTQUIETTIME)) - 1810)
today += DAY;
// Schedule restart for 30 minutes before quiet time, or as long as we have
_warnShutdownTime = GetLocalHourTimestamp(today, getIntConfig(CONFIG_RESPAWN_RESTARTQUIETTIME)) - 1800;
_guidWarn = true;
SendGuidWarning();
}
void World::TriggerGuidAlert()
{
// Lock this only to prevent multiple maps triggering at the same time
std::lock_guard lock(_guidAlertLock);
DoGuidAlertRestart();
_guidAlert = true;
_guidWarn = false;
}
void World::DoGuidWarningRestart()
{
if (m_ShutdownTimer)
return;
ShutdownServ(1800, SHUTDOWN_MASK_RESTART, RESTART_EXIT_CODE);
_warnShutdownTime += HOUR;
}
void World::DoGuidAlertRestart()
{
if (m_ShutdownTimer)
return;
ShutdownServ(300, SHUTDOWN_MASK_RESTART, RESTART_EXIT_CODE, _alertRestartReason);
}
void World::SendGuidWarning()
{
if (!m_ShutdownTimer && _guidWarn && getIntConfig(CONFIG_RESPAWN_GUIDWARNING_FREQUENCY) > 0)
SendServerMessage(SERVER_MSG_STRING, _guidWarningMsg.c_str());
_warnDiff = 0;
}
/// Find a session by its id
WorldSession* World::FindSession(uint32 id) const
{
SessionMap::const_iterator itr = m_sessions.find(id);
if (itr != m_sessions.end())
return itr->second; // also can return nullptr for kicked session
else
return nullptr;
}
/// Remove a given session
bool World::RemoveSession(uint32 id)
{
///- Find the session, kick the user, but we can't delete session at this moment to prevent iterator invalidation
SessionMap::const_iterator itr = m_sessions.find(id);
if (itr != m_sessions.end() && itr->second)
{
if (itr->second->PlayerLoading())
return false;
itr->second->KickPlayer("World::RemoveSession");
}
return true;
}
void World::AddSession(WorldSession* s)
{
addSessQueue.add(s);
}
void World::AddInstanceSocket(std::weak_ptr sock, uint64 connectToKey)
{
_linkSocketQueue.add(std::make_pair(sock, connectToKey));
}
void World::AddSession_(WorldSession* s)
{
ASSERT(s);
//NOTE - Still there is race condition in WorldSession* being used in the Sockets
///- kick already loaded player with same account (if any) and remove session
///- if player is in loading and want to load again, return
if (!RemoveSession(s->GetAccountId()))
{
s->KickPlayer("World::AddSession_ Couldn't remove the other session while on loading screen");
delete s; // session not added yet in session list, so not listed in queue
return;
}
// decrease session counts only at not reconnection case
bool decrease_session = true;
// if session already exist, prepare to it deleting at next world update
// NOTE - KickPlayer() should be called on "old" in RemoveSession()
{
SessionMap::const_iterator old = m_sessions.find(s->GetAccountId());
if (old != m_sessions.end())
{
// prevent decrease sessions count if session queued
if (RemoveQueuedPlayer(old->second))
decrease_session = false;
// not remove replaced session form queue if listed
Trinity::Containers::MultimapErasePair(m_sessionsByBnetGuid, old->second->GetBattlenetAccountGUID(), old->second);
delete old->second;
}
}
m_sessions[s->GetAccountId()] = s;
m_sessionsByBnetGuid.emplace(s->GetBattlenetAccountGUID(), s);
uint32 Sessions = GetActiveAndQueuedSessionCount();
uint32 pLimit = GetPlayerAmountLimit();
uint32 QueueSize = GetQueuedSessionCount(); //number of players in the queue
//so we don't count the user trying to
//login as a session and queue the socket that we are using
if (decrease_session)
--Sessions;
if (pLimit > 0 && Sessions >= pLimit && !s->HasPermission(rbac::RBAC_PERM_SKIP_QUEUE) && !HasRecentlyDisconnected(s))
{
AddQueuedPlayer(s);
UpdateMaxSessionCounters();
TC_LOG_INFO("misc", "PlayerQueue: Account id {} is in Queue Position ({}).", s->GetAccountId(), ++QueueSize);
return;
}
s->InitializeSession();
UpdateMaxSessionCounters();
// Updates the population
if (pLimit > 0)
{
float popu = (float)GetActiveSessionCount(); // updated number of users on the server
popu /= pLimit;
LoginDatabasePreparedStatement* stmt = LoginDatabase.GetPreparedStatement(LOGIN_UPD_REALM_POPULATION);
stmt->setFloat(0, popu);
stmt->setUInt32(1, sRealmList->GetCurrentRealmId().Realm);
LoginDatabase.Execute(stmt);
TC_LOG_INFO("misc", "Server Population ({}).", popu);
}
}
bool World::HasRecentlyDisconnected(WorldSession* session)
{
if (!session)
return false;
if (uint32 tolerance = getIntConfig(CONFIG_INTERVAL_DISCONNECT_TOLERANCE))
{
for (DisconnectMap::iterator i = m_disconnects.begin(); i != m_disconnects.end();)
{
if (difftime(i->second, GameTime::GetGameTime()) < tolerance)
{
if (i->first == session->GetAccountId())
return true;
++i;
}
else
m_disconnects.erase(i++);
}
}
return false;
}
int32 World::GetQueuePos(WorldSession* sess)
{
uint32 position = 1;
for (Queue::const_iterator iter = m_QueuedPlayer.begin(); iter != m_QueuedPlayer.end(); ++iter, ++position)
if ((*iter) == sess)
return position;
return 0;
}
void World::AddQueuedPlayer(WorldSession* sess)
{
sess->SetInQueue(true);
m_QueuedPlayer.push_back(sess);
// The 1st SMSG_AUTH_RESPONSE needs to contain other info too.
sess->SendAuthResponse(ERROR_OK, true, GetQueuePos(sess));
}
bool World::RemoveQueuedPlayer(WorldSession* sess)
{
// sessions count including queued to remove (if removed_session set)
uint32 sessions = GetActiveSessionCount();
uint32 position = 1;
Queue::iterator iter = m_QueuedPlayer.begin();
// search to remove and count skipped positions
bool found = false;
for (; iter != m_QueuedPlayer.end(); ++iter, ++position)
{
if (*iter == sess)
{
sess->SetInQueue(false);
sess->ResetTimeOutTime(false);
iter = m_QueuedPlayer.erase(iter);
found = true; // removing queued session
break;
}
}
// iter point to next socked after removed or end()
// position store position of removed socket and then new position next socket after removed
// if session not queued then we need decrease sessions count
if (!found && sessions)
--sessions;
// accept first in queue
if ((!m_playerLimit || sessions < m_playerLimit) && !m_QueuedPlayer.empty())
{
WorldSession* pop_sess = m_QueuedPlayer.front();
pop_sess->InitializeSession();
m_QueuedPlayer.pop_front();
// update iter to point first queued socket or end() if queue is empty now
iter = m_QueuedPlayer.begin();
position = 1;
}
// update position from iter to end()
// iter point to first not updated socket, position store new position
for (; iter != m_QueuedPlayer.end(); ++iter, ++position)
(*iter)->SendAuthWaitQueue(position);
return found;
}
template
struct ConfigOptionLoadDefinition
{
std::string_view Name;
T DefaultValue = { };
IndexType Index = { };
Optional Min, Max;
bool Reloadable = true;
};
template
using ConfigOptionLoadDefinitionArray = std::array, Max>;
template
static void StoreConfigValue(T& oldValue, std::type_identity_t value, ConfigOptionLoadDefinition const& definition, bool reload)
{
if constexpr (!std::is_same_v)
{
if (definition.Min && value < *definition.Min)
{
TC_LOG_ERROR("server.loading", "{} {} must be >= {}. Using {} instead.", definition.Name, value, *definition.Min, *definition.Min);
value = *definition.Min;
}
if (definition.Max && value > *definition.Max)
{
TC_LOG_ERROR("server.loading", "{} {} must be <= {}. Using {} instead.", definition.Name, value, *definition.Max, *definition.Max);
value = *definition.Max;
}
}
if (reload && !definition.Reloadable)
{
if (value != oldValue)
TC_LOG_ERROR("server.loading", "{} option can't be changed at worldserver.conf reload, using current value ({}).", definition.Name, oldValue);
return;
}
oldValue = value;
}
/// Initialize config values
void World::LoadConfigSettings(bool reload)
{
if (reload)
{
std::vector configErrors;
if (!sConfigMgr->Reload(configErrors))
{
for (std::string const& configError : configErrors)
TC_LOG_ERROR("misc", "World settings reload fail: {}.", configError);
return;
}
sLog->LoadFromConfig();
sMetric->LoadFromConfigs();
}
m_defaultDbcLocale = LocaleConstant(sConfigMgr->GetIntDefault("DBC.Locale"sv, 0));
if (m_defaultDbcLocale >= TOTAL_LOCALES || m_defaultDbcLocale == LOCALE_none)
{
TC_LOG_ERROR("server.loading", "Incorrect DBC.Locale! Must be >= 0 and < {} and not {} (set to 0)", TOTAL_LOCALES, LOCALE_none);
m_defaultDbcLocale = LOCALE_enUS;
}
TC_LOG_INFO("server.loading", "Using {} DBC Locale", localeNames[m_defaultDbcLocale]);
///- Read the player limit and the Message of the day from the config file
SetPlayerAmountLimit(sConfigMgr->GetIntDefault("PlayerLimit"sv, 100));
SetMotd(sConfigMgr->GetStringDefault("Motd"sv, "Welcome to a Trinity Core Server."sv));
uint32 databaseCacheVersion = m_int_configs[CONFIG_CLIENTCACHE_VERSION];
static constexpr ConfigOptionLoadDefinitionArray bools =
{ {
{ .Name = "Support.Enabled"sv, .DefaultValue = true, .Index = CONFIG_SUPPORT_ENABLED },
{ .Name = "Support.TicketsEnabled"sv, .DefaultValue = false, .Index = CONFIG_SUPPORT_TICKETS_ENABLED },
{ .Name = "Support.BugsEnabled"sv, .DefaultValue = false, .Index = CONFIG_SUPPORT_BUGS_ENABLED },
{ .Name = "Support.ComplaintsEnabled"sv, .DefaultValue = false, .Index = CONFIG_SUPPORT_COMPLAINTS_ENABLED },
{ .Name = "Support.SuggestionsEnabled"sv, .DefaultValue = false, .Index = CONFIG_SUPPORT_SUGGESTIONS_ENABLED },
{ .Name = "DurabilityLoss.InPvP"sv, .DefaultValue = false, .Index = CONFIG_DURABILITY_LOSS_IN_PVP },
{ .Name = "AddonChannel"sv, .DefaultValue = true, .Index = CONFIG_ADDON_CHANNEL },
{ .Name = "CleanCharacterDB"sv, .DefaultValue = false, .Index = CONFIG_CLEAN_CHARACTER_DB },
{ .Name = "PreserveCustomChannels"sv, .DefaultValue = false, .Index = CONFIG_PRESERVE_CUSTOM_CHANNELS },
{ .Name = "GridUnload"sv, .DefaultValue = true, .Index = CONFIG_GRID_UNLOAD },
{ .Name = "BaseMapLoadAllGrids"sv, .DefaultValue = false, .Index = CONFIG_BASEMAP_LOAD_GRIDS },
{ .Name = "InstanceMapLoadAllGrids"sv, .DefaultValue = false, .Index = CONFIG_INSTANCEMAP_LOAD_GRIDS },
{ .Name = "BattlegroundMapLoadAllGrids"sv, .DefaultValue = true, .Index = CONFIG_BATTLEGROUNDMAP_LOAD_GRIDS },
{ .Name = "PlayerSave.Stats.SaveOnlyOnLogout"sv, .DefaultValue = true, .Index = CONFIG_STATS_SAVE_ONLY_ON_LOGOUT },
{ .Name = "Creature.RegenHPCannotReachTargetInRaid"sv, .DefaultValue = true, .Index = CONFIG_REGEN_HP_CANNOT_REACH_TARGET_IN_RAID },
{ .Name = "AllowTwoSide.Interaction.Calendar"sv, .DefaultValue = false, .Index = CONFIG_ALLOW_TWO_SIDE_INTERACTION_CALENDAR },
{ .Name = "AllowTwoSide.Interaction.Channel"sv, .DefaultValue = false, .Index = CONFIG_ALLOW_TWO_SIDE_INTERACTION_CHANNEL },
{ .Name = "AllowTwoSide.Interaction.Group"sv, .DefaultValue = false, .Index = CONFIG_ALLOW_TWO_SIDE_INTERACTION_GROUP },
{ .Name = "AllowTwoSide.Interaction.Guild"sv, .DefaultValue = false, .Index = CONFIG_ALLOW_TWO_SIDE_INTERACTION_GUILD },
{ .Name = "AllowTwoSide.Interaction.Auction"sv, .DefaultValue = true, .Index = CONFIG_ALLOW_TWO_SIDE_INTERACTION_AUCTION },
{ .Name = "AllowTwoSide.Trade"sv, .DefaultValue = false, .Index = CONFIG_ALLOW_TWO_SIDE_TRADE },
{ .Name = "CharacterCreating.DisableAlliedRaceAchievementRequirement"sv, .DefaultValue = false, .Index = CONFIG_CHARACTER_CREATING_DISABLE_ALLIED_RACE_ACHIEVEMENT_REQUIREMENT },
{ .Name = "AllFlightPaths"sv, .DefaultValue = false, .Index = CONFIG_ALL_TAXI_PATHS },
{ .Name = "InstantFlightPaths"sv, .DefaultValue = false, .Index = CONFIG_INSTANT_TAXI },
{ .Name = "Instance.IgnoreLevel"sv, .DefaultValue = false, .Index = CONFIG_INSTANCE_IGNORE_LEVEL },
{ .Name = "Instance.IgnoreRaid"sv, .DefaultValue = false, .Index = CONFIG_INSTANCE_IGNORE_RAID },
{ .Name = "CastUnstuck"sv, .DefaultValue = true, .Index = CONFIG_CAST_UNSTUCK },
{ .Name = "GM.AllowInvite"sv, .DefaultValue = false, .Index = CONFIG_ALLOW_GM_GROUP },
{ .Name = "GM.LowerSecurity"sv, .DefaultValue = false, .Index = CONFIG_GM_LOWER_SECURITY },
{ .Name = "SkillChance.Prospecting"sv, .DefaultValue = false, .Index = CONFIG_SKILL_PROSPECTING },
{ .Name = "SkillChance.Milling"sv, .DefaultValue = false, .Index = CONFIG_SKILL_MILLING },
{ .Name = "ActivateWeather"sv, .DefaultValue = true, .Index = CONFIG_WEATHER },
{ .Name = "Event.Announce"sv, .DefaultValue = false, .Index = CONFIG_EVENT_ANNOUNCE },
{ .Name = "Quests.EnableQuestTracker"sv, .DefaultValue = false, .Index = CONFIG_QUEST_ENABLE_QUEST_TRACKER },
{ .Name = "Quests.IgnoreRaid"sv, .DefaultValue = false, .Index = CONFIG_QUEST_IGNORE_RAID },
{ .Name = "Quests.IgnoreAutoAccept"sv, .DefaultValue = false, .Index = CONFIG_QUEST_IGNORE_AUTO_ACCEPT },
{ .Name = "Quests.IgnoreAutoComplete"sv, .DefaultValue = false, .Index = CONFIG_QUEST_IGNORE_AUTO_COMPLETE },
{ .Name = "DetectPosCollision"sv, .DefaultValue = true, .Index = CONFIG_DETECT_POS_COLLISION },
{ .Name = "Channel.RestrictedLfg"sv, .DefaultValue = true, .Index = CONFIG_RESTRICTED_LFG_CHANNEL },
{ .Name = "ChatFakeMessagePreventing"sv, .DefaultValue = false, .Index = CONFIG_CHAT_FAKE_MESSAGE_PREVENTING },
{ .Name = "Death.CorpseReclaimDelay.PvP"sv, .DefaultValue = true, .Index = CONFIG_DEATH_CORPSE_RECLAIM_DELAY_PVP },
{ .Name = "Death.CorpseReclaimDelay.PvE"sv, .DefaultValue = true, .Index = CONFIG_DEATH_CORPSE_RECLAIM_DELAY_PVE },
{ .Name = "Death.Bones.World"sv, .DefaultValue = true, .Index = CONFIG_DEATH_BONES_WORLD },
{ .Name = "Death.Bones.BattlegroundOrArena"sv, .DefaultValue = true, .Index = CONFIG_DEATH_BONES_BG_OR_ARENA },
{ .Name = "Die.Command.Mode"sv, .DefaultValue = true, .Index = CONFIG_DIE_COMMAND_MODE },
{ .Name = "DeclinedNames"sv, .DefaultValue = false, .Index = CONFIG_DECLINED_NAMES_USED },
{ .Name = "Battleground.CastDeserter"sv, .DefaultValue = true, .Index = CONFIG_BATTLEGROUND_CAST_DESERTER },
{ .Name = "Battleground.QueueAnnouncer.Enable"sv, .DefaultValue = false, .Index = CONFIG_BATTLEGROUND_QUEUE_ANNOUNCER_ENABLE },
{ .Name = "Battleground.QueueAnnouncer.PlayerOnly"sv, .DefaultValue = false, .Index = CONFIG_BATTLEGROUND_QUEUE_ANNOUNCER_PLAYERONLY },
{ .Name = "Battleground.StoreStatistics.Enable"sv, .DefaultValue = false, .Index = CONFIG_BATTLEGROUND_STORE_STATISTICS_ENABLE },
{ .Name = "Battleground.GiveXPForKills"sv, .DefaultValue = false, .Index = CONFIG_BG_XP_FOR_KILL },
{ .Name = "Arena.QueueAnnouncer.Enable"sv, .DefaultValue = false, .Index = CONFIG_ARENA_QUEUE_ANNOUNCER_ENABLE },
{ .Name = "Arena.ArenaSeason.InProgress"sv, .DefaultValue = false, .Index = CONFIG_ARENA_SEASON_IN_PROGRESS },
{ .Name = "ArenaLog.ExtendedInfo"sv, .DefaultValue = false, .Index = CONFIG_ARENA_LOG_EXTENDED_INFO },
{ .Name = "OffhandCheckAtSpellUnlearn"sv, .DefaultValue = true, .Index = CONFIG_OFFHAND_CHECK_AT_SPELL_UNLEARN },
{ .Name = "Respawn.DynamicEscortNPC"sv, .DefaultValue = false, .Index = CONFIG_RESPAWN_DYNAMIC_ESCORTNPC },
{ .Name = "mmap.enablePathFinding"sv, .DefaultValue = true, .Index = CONFIG_ENABLE_MMAPS },
{ .Name = "vmap.enableIndoorCheck"sv, .DefaultValue = true, .Index = CONFIG_VMAP_INDOOR_CHECK },
{ .Name = "PlayerStart.AllSpells"sv, .DefaultValue = false, .Index = CONFIG_START_ALL_SPELLS },
{ .Name = "ResetDuelCooldowns"sv, .DefaultValue = false, .Index = CONFIG_RESET_DUEL_COOLDOWNS },
{ .Name = "ResetDuelHealthMana"sv, .DefaultValue = false, .Index = CONFIG_RESET_DUEL_HEALTH_MANA },
{ .Name = "PlayerStart.MapsExplored"sv, .DefaultValue = false, .Index = CONFIG_START_ALL_EXPLORED },
{ .Name = "PlayerStart.AllReputation"sv, .DefaultValue = false, .Index = CONFIG_START_ALL_REP },
{ .Name = "PvPToken.Enable"sv, .DefaultValue = false, .Index = CONFIG_PVP_TOKEN_ENABLE },
{ .Name = "NoResetTalentsCost"sv, .DefaultValue = false, .Index = CONFIG_NO_RESET_TALENT_COST },
{ .Name = "ShowKickInWorld"sv, .DefaultValue = false, .Index = CONFIG_SHOW_KICK_IN_WORLD },
{ .Name = "ShowMuteInWorld"sv, .DefaultValue = false, .Index = CONFIG_SHOW_MUTE_IN_WORLD },
{ .Name = "ShowBanInWorld"sv, .DefaultValue = false, .Index = CONFIG_SHOW_BAN_IN_WORLD },
{ .Name = "Warden.Enabled"sv, .DefaultValue = false, .Index = CONFIG_WARDEN_ENABLED },
{ .Name = "FeatureSystem.CharacterUndelete.Enabled"sv, .DefaultValue = false, .Index = CONFIG_FEATURE_SYSTEM_CHARACTER_UNDELETE_ENABLED },
{ .Name = "DBC.EnforceItemAttributes"sv, .DefaultValue = true, .Index = CONFIG_DBC_ENFORCE_ITEM_ATTRIBUTES },
{ .Name = "InstancesResetAnnounce"sv, .DefaultValue = false, .Index = CONFIG_INSTANCES_RESET_ANNOUNCE },
{ .Name = "AutoBroadcast.On"sv, .DefaultValue = false, .Index = CONFIG_AUTOBROADCAST },
{ .Name = "PlayerDump.DisallowPaths"sv, .DefaultValue = true, .Index = CONFIG_PDUMP_NO_PATHS },
{ .Name = "PlayerDump.DisallowOverwrite"sv, .DefaultValue = true, .Index = CONFIG_PDUMP_NO_OVERWRITE },
{ .Name = "Wintergrasp.Enable"sv, .DefaultValue = false, .Index = CONFIG_WINTERGRASP_ENABLE },
{ .Name = "TolBarad.Enable"sv, .DefaultValue = true, .Index = CONFIG_TOLBARAD_ENABLE },
{ .Name = "Stats.Limits.Enable"sv, .DefaultValue = false, .Index = CONFIG_STATS_LIMITS_ENABLE },
{ .Name = "Allow.IP.Based.Action.Logging"sv, .DefaultValue = false, .Index = CONFIG_IP_BASED_ACTION_LOGGING },
{ .Name = "Calculate.Creature.Zone.Area.Data"sv, .DefaultValue = false, .Index = CONFIG_CALCULATE_CREATURE_ZONE_AREA_DATA },
{ .Name = "Calculate.Gameoject.Zone.Area.Data"sv, .DefaultValue = false, .Index = CONFIG_CALCULATE_GAMEOBJECT_ZONE_AREA_DATA },
{ .Name = "BlackMarket.Enabled"sv, .DefaultValue = true, .Index = CONFIG_BLACKMARKET_ENABLED },
{ .Name = "HotSwap.Enabled"sv, .DefaultValue = true, .Index = CONFIG_HOTSWAP_ENABLED },
{ .Name = "HotSwap.EnableReCompiler"sv, .DefaultValue = true, .Index = CONFIG_HOTSWAP_RECOMPILER_ENABLED },
{ .Name = "HotSwap.EnableEarlyTermination"sv, .DefaultValue = true, .Index = CONFIG_HOTSWAP_EARLY_TERMINATION_ENABLED },
{ .Name = "HotSwap.EnableBuildFileRecreation"sv, .DefaultValue = true, .Index = CONFIG_HOTSWAP_BUILD_FILE_RECREATION_ENABLED },
{ .Name = "HotSwap.EnableInstall"sv, .DefaultValue = true, .Index = CONFIG_HOTSWAP_INSTALL_ENABLED },
{ .Name = "HotSwap.EnablePrefixCorrection"sv, .DefaultValue = true, .Index = CONFIG_HOTSWAP_PREFIX_CORRECTION_ENABLED },
{ .Name = "PreventRenameCharacterOnCustomization"sv, .DefaultValue = false, .Index = CONFIG_PREVENT_RENAME_CUSTOMIZATION },
{ .Name = "PartyRaidWarnings"sv, .DefaultValue = false, .Index = CONFIG_CHAT_PARTY_RAID_WARNINGS },
{ .Name = "CacheDataQueries"sv, .DefaultValue = true, .Index = CONFIG_CACHE_DATA_QUERIES },
{ .Name = "Creature.CheckInvalidPosition"sv, .DefaultValue = false, .Index = CONFIG_CREATURE_CHECK_INVALID_POSITION },
{ .Name = "GameObject.CheckInvalidPosition"sv, .DefaultValue = false, .Index = CONFIG_GAME_OBJECT_CHECK_INVALID_POSITION },
{ .Name = "CheckGameObjectLoS"sv, .DefaultValue = true, .Index = CONFIG_CHECK_GOBJECT_LOS },
{ .Name = "AllowLoggingIPAddressesInDatabase"sv, .DefaultValue = true, .Index = CONFIG_ALLOW_LOGGING_IP_ADDRESSES_IN_DATABASE },
{ .Name = "Loot.EnableAELoot"sv, .DefaultValue = true, .Index = CONFIG_ENABLE_AE_LOOT },
{ .Name = "Load.Locales"sv, .DefaultValue = true, .Index = CONFIG_LOAD_LOCALES },
} };
static constexpr ConfigOptionLoadDefinitionArray ints =
{ {
{ .Name = "Server.LoginInfo"sv, .DefaultValue = 0, .Index = CONFIG_ENABLE_SINFO_LOGIN },
{ .Name = "XP.Boost.Daymask"sv, .DefaultValue = 0, .Index = CONFIG_XP_BOOST_DAYMASK },
{ .Name = "Compression"sv, .DefaultValue = 1, .Index = CONFIG_COMPRESSION, .Min = Z_BEST_SPEED, .Max = Z_BEST_COMPRESSION },
{ .Name = "PersistentCharacterCleanFlags"sv, .DefaultValue = 0, .Index = CONFIG_PERSISTENT_CHARACTER_CLEAN_FLAGS },
{ .Name = "Auction.ReplicateItemsCooldown"sv, .DefaultValue = 900, .Index = CONFIG_AUCTION_REPLICATE_DELAY },
{ .Name = "Auction.SearchDelay"sv, .DefaultValue = 300, .Index = CONFIG_AUCTION_SEARCH_DELAY, .Min = 100, .Max = 10000 },
{ .Name = "Auction.TaintedSearchDelay"sv, .DefaultValue = 3000, .Index = CONFIG_AUCTION_TAINTED_SEARCH_DELAY, .Min = 100, .Max = 10000 },
{ .Name = "ChatLevelReq.Channel"sv, .DefaultValue = 1, .Index = CONFIG_CHAT_CHANNEL_LEVEL_REQ },
{ .Name = "ChatLevelReq.Whisper"sv, .DefaultValue = 1, .Index = CONFIG_CHAT_WHISPER_LEVEL_REQ },
{ .Name = "ChatLevelReq.Emote"sv, .DefaultValue = 1, .Index = CONFIG_CHAT_EMOTE_LEVEL_REQ },
{ .Name = "ChatLevelReq.Say"sv, .DefaultValue = 1, .Index = CONFIG_CHAT_SAY_LEVEL_REQ },
{ .Name = "ChatLevelReq.Yell"sv, .DefaultValue = 1, .Index = CONFIG_CHAT_YELL_LEVEL_REQ },
{ .Name = "PartyLevelReq"sv, .DefaultValue = 1, .Index = CONFIG_PARTY_LEVEL_REQ },
{ .Name = "LevelReq.Trade"sv, .DefaultValue = 1, .Index = CONFIG_TRADE_LEVEL_REQ },
{ .Name = "LevelReq.Auction"sv, .DefaultValue = 1, .Index = CONFIG_AUCTION_LEVEL_REQ },
{ .Name = "LevelReq.Mail"sv, .DefaultValue = 1, .Index = CONFIG_MAIL_LEVEL_REQ },
{ .Name = "PreserveCustomChannelDuration"sv, .DefaultValue = 14, .Index = CONFIG_PRESERVE_CUSTOM_CHANNEL_DURATION },
{ .Name = "PreserveCustomChannelInterval"sv, .DefaultValue = 5, .Index = CONFIG_PRESERVE_CUSTOM_CHANNEL_INTERVAL },
{ .Name = "PlayerSaveInterval"sv, .DefaultValue = 15 * MINUTE * IN_MILLISECONDS, .Index = CONFIG_INTERVAL_SAVE },
{ .Name = "DisconnectToleranceInterval"sv, .DefaultValue = 0, .Index = CONFIG_INTERVAL_DISCONNECT_TOLERANCE },
{ .Name = "PlayerSave.Stats.MinLevel"sv, .DefaultValue = 0, .Index = CONFIG_MIN_LEVEL_STAT_SAVE, .Max = STRONG_MAX_LEVEL },
{ .Name = "GridCleanUpDelay"sv, .DefaultValue = 5 * MINUTE * IN_MILLISECONDS, .Index = CONFIG_INTERVAL_GRIDCLEAN, .Min = MIN_GRID_DELAY },
{ .Name = "MapUpdateInterval"sv, .DefaultValue = 10, .Index = CONFIG_INTERVAL_MAPUPDATE, .Min = MIN_MAP_UPDATE_DELAY },
{ .Name = "ChangeWeatherInterval"sv, .DefaultValue = 10 * MINUTE * IN_MILLISECONDS, .Index = CONFIG_INTERVAL_CHANGEWEATHER },
{ .Name = "WorldServerPort"sv, .DefaultValue = 8085, .Index = CONFIG_PORT_WORLD, .Min = 1, .Max = std::numeric_limits::max(), .Reloadable = false },
{ .Name = "SocketTimeOutTime"sv, .DefaultValue = 900000, .Index = CONFIG_SOCKET_TIMEOUTTIME },
{ .Name = "SocketTimeOutTimeActive"sv, .DefaultValue = 60000, .Index = CONFIG_SOCKET_TIMEOUTTIME_ACTIVE },
{ .Name = "SessionAddDelay"sv, .DefaultValue = 10000, .Index = CONFIG_SESSION_ADD_DELAY },
{ .Name = "MinQuestScaledXPRatio"sv, .DefaultValue = 0, .Index = CONFIG_MIN_QUEST_SCALED_XP_RATIO, .Max = 100 },
{ .Name = "MinCreatureScaledXPRatio"sv, .DefaultValue = 0, .Index = CONFIG_MIN_CREATURE_SCALED_XP_RATIO, .Max = 100 },
{ .Name = "MinDiscoveredScaledXPRatio"sv, .DefaultValue = 0, .Index = CONFIG_MIN_DISCOVERED_SCALED_XP_RATIO, .Max = 100 },
{ .Name = "GameType"sv, .DefaultValue = 0, .Index = CONFIG_GAME_TYPE, .Reloadable = false },
{ .Name = "RealmZone"sv, .DefaultValue = HARDCODED_DEVELOPMENT_REALM_CATEGORY_ID, .Index = CONFIG_REALM_ZONE, .Reloadable = false },
{ .Name = "StrictPlayerNames"sv, .DefaultValue = 0, .Index = CONFIG_STRICT_PLAYER_NAMES },
{ .Name = "StrictCharterNames"sv, .DefaultValue = 0, .Index = CONFIG_STRICT_CHARTER_NAMES },
{ .Name = "StrictPetNames"sv, .DefaultValue = 0, .Index = CONFIG_STRICT_PET_NAMES },
{ .Name = "MinPlayerName"sv, .DefaultValue = 2, .Index = CONFIG_MIN_PLAYER_NAME, .Min = 1, .Max = MAX_PLAYER_NAME },
{ .Name = "MinCharterName"sv, .DefaultValue = 2, .Index = CONFIG_MIN_CHARTER_NAME, .Min = 1, .Max = MAX_CHARTER_NAME },
{ .Name = "MinPetName"sv, .DefaultValue = 2, .Index = CONFIG_MIN_PET_NAME, .Min = 1, .Max = MAX_PET_NAME },
{ .Name = "Guild.CharterCost"sv, .DefaultValue = 1000, .Index = CONFIG_CHARTER_COST_GUILD },
{ .Name = "ArenaTeam.CharterCost.2v2"sv, .DefaultValue = 800000, .Index = CONFIG_CHARTER_COST_ARENA_2v2 },
{ .Name = "ArenaTeam.CharterCost.3v3"sv, .DefaultValue = 1200000, .Index = CONFIG_CHARTER_COST_ARENA_3v3 },
{ .Name = "ArenaTeam.CharterCost.5v5"sv, .DefaultValue = 2000000, .Index = CONFIG_CHARTER_COST_ARENA_5v5 },
{ .Name = "CharacterCreating.Disabled"sv, .DefaultValue = 0, .Index = CONFIG_CHARACTER_CREATING_DISABLED },
{ .Name = "CharacterCreating.Disabled.ClassMask"sv, .DefaultValue = 0, .Index = CONFIG_CHARACTER_CREATING_DISABLED_CLASSMASK },
{ .Name = "CharactersPerRealm"sv, .DefaultValue = 60, .Index = CONFIG_CHARACTERS_PER_REALM, .Min = 1, .Max = MAX_CHARACTERS_PER_REALM },
{ .Name = "CharactersPerAccount"sv, .DefaultValue = 60, .Index = CONFIG_CHARACTERS_PER_ACCOUNT, .Min = 1, .Max = MAX_CHARACTERS_PER_REALM },
{ .Name = "CharacterCreating.EvokersPerRealm"sv, .DefaultValue = 1, .Index = CONFIG_CHARACTER_CREATING_EVOKERS_PER_REALM, .Min = 1, .Max = MAX_CHARACTERS_PER_REALM },
{ .Name = "CharacterCreating.MinLevelForDemonHunter"sv, .DefaultValue = 0, .Index = CONFIG_CHARACTER_CREATING_MIN_LEVEL_FOR_DEMON_HUNTER },
{ .Name = "CharacterCreating.MinLevelForEvoker"sv, .DefaultValue = 50, .Index = CONFIG_CHARACTER_CREATING_MIN_LEVEL_FOR_EVOKER },
{ .Name = "SkipCinematics"sv, .DefaultValue = 0, .Index = CONFIG_SKIP_CINEMATICS, .Min = 0, .Max = 2 },
{ .Name = "MaxPlayerLevel"sv, .DefaultValue = DEFAULT_MAX_LEVEL, .Index = CONFIG_MAX_PLAYER_LEVEL, .Min = 1, .Max = MAX_LEVEL, .Reloadable = false },
{ .Name = "MinDualSpecLevel"sv, .DefaultValue = 40, .Index = CONFIG_MIN_DUALSPEC_LEVEL },
{ .Name = "StartPlayerLevel"sv, .DefaultValue = 1, .Index = CONFIG_START_PLAYER_LEVEL, .Min = 1 },
{ .Name = "StartDeathKnightPlayerLevel"sv, .DefaultValue = 8, .Index = CONFIG_START_DEATH_KNIGHT_PLAYER_LEVEL, .Min = 1 },
{ .Name = "StartDemonHunterPlayerLevel"sv, .DefaultValue = 8, .Index = CONFIG_START_DEMON_HUNTER_PLAYER_LEVEL, .Min = 1 },
{ .Name = "StartEvokerPlayerLevel"sv, .DefaultValue = 10, .Index = CONFIG_START_EVOKER_PLAYER_LEVEL, .Min = 1 },
{ .Name = "StartAlliedRacePlayerLevel"sv, .DefaultValue = 10, .Index = CONFIG_START_ALLIED_RACE_LEVEL, .Min = 1 },
{ .Name = "Currency.ResetHour"sv, .DefaultValue = 3, .Index = CONFIG_CURRENCY_RESET_HOUR, .Min = 0, .Max = 23 },
{ .Name = "Currency.ResetDay"sv, .DefaultValue = 3, .Index = CONFIG_CURRENCY_RESET_DAY, .Min = 0, .Max = 6 },
{ .Name = "Currency.ResetInterval"sv, .DefaultValue = 7, .Index = CONFIG_CURRENCY_RESET_INTERVAL, .Min = 1 },
{ .Name = "RecruitAFriend.MaxLevel"sv, .DefaultValue = DEFAULT_MAX_LEVEL, .Index = CONFIG_MAX_RECRUIT_A_FRIEND_BONUS_PLAYER_LEVEL, .Min = 1 },
{ .Name = "RecruitAFriend.MaxDifference"sv, .DefaultValue = 4, .Index = CONFIG_MAX_RECRUIT_A_FRIEND_BONUS_PLAYER_LEVEL_DIFFERENCE },
{ .Name = "ResetSchedule.WeekDay"sv, .DefaultValue = 2, .Index = CONFIG_RESET_SCHEDULE_WEEK_DAY, .Min = 0, .Max = 6 },
{ .Name = "ResetSchedule.Hour"sv, .DefaultValue = 8, .Index = CONFIG_RESET_SCHEDULE_HOUR, .Min = 0, .Max = 23 },
{ .Name = "Instance.UnloadDelay"sv, .DefaultValue = 30 * MINUTE * IN_MILLISECONDS, .Index = CONFIG_INSTANCE_UNLOAD_DELAY },
{ .Name = "Quests.DailyResetTime"sv, .DefaultValue = 3, .Index = CONFIG_DAILY_QUEST_RESET_TIME_HOUR, .Min = 0, .Max = 23 },
{ .Name = "Quests.WeeklyResetWDay"sv, .DefaultValue = 3, .Index = CONFIG_WEEKLY_QUEST_RESET_TIME_WDAY, .Min = 0, .Max = 6 },
{ .Name = "MaxPrimaryTradeSkill"sv, .DefaultValue = 2, .Index = CONFIG_MAX_PRIMARY_TRADE_SKILL },
{ .Name = "MinPetitionSigns"sv, .DefaultValue = 4, .Index = CONFIG_MIN_PETITION_SIGNS, .Max = 4 },
{ .Name = "GM.LoginState"sv, .DefaultValue = 2, .Index = CONFIG_GM_LOGIN_STATE },
{ .Name = "GM.Visible"sv, .DefaultValue = 2, .Index = CONFIG_GM_VISIBLE_STATE },
{ .Name = "GM.Chat"sv, .DefaultValue = 2, .Index = CONFIG_GM_CHAT },
{ .Name = "GM.WhisperingTo"sv, .DefaultValue = 2, .Index = CONFIG_GM_WHISPERING_TO },
{ .Name = "GM.FreezeAuraDuration"sv, .DefaultValue = 0, .Index = CONFIG_GM_FREEZE_DURATION },
{ .Name = "GM.InGMList.Level"sv, .DefaultValue = SEC_ADMINISTRATOR, .Index = CONFIG_GM_LEVEL_IN_GM_LIST },
{ .Name = "GM.InWhoList.Level"sv, .DefaultValue = SEC_ADMINISTRATOR, .Index = CONFIG_GM_LEVEL_IN_WHO_LIST },
{ .Name = "GM.StartLevel"sv, .DefaultValue = 1, .Index = CONFIG_START_GM_LEVEL, .Min = 1, .Max = MAX_LEVEL },
{ .Name = "GM.ForceShutdownThreshold"sv, .DefaultValue = 30, .Index = CONFIG_FORCE_SHUTDOWN_THRESHOLD },
{ .Name = "Visibility.GroupMode"sv, .DefaultValue = 1, .Index = CONFIG_GROUP_VISIBILITY, .Min = 0, .Max = 3 },
{ .Name = "MailDeliveryDelay"sv, .DefaultValue = HOUR, .Index = CONFIG_MAIL_DELIVERY_DELAY },
{ .Name = "CleanOldMailTime"sv, .DefaultValue = 4, .Index = CONFIG_CLEAN_OLD_MAIL_TIME, .Min = 0, .Max = 23 },
{ .Name = "UpdateUptimeInterval"sv, .DefaultValue = 10, .Index = CONFIG_UPTIME_UPDATE, .Min = 1 },
{ .Name = "LogDB.Opt.ClearInterval"sv, .DefaultValue = 10, .Index = CONFIG_LOGDB_CLEARINTERVAL, .Min = 1 },
{ .Name = "LogDB.Opt.ClearTime"sv, .DefaultValue = 1209600, .Index = CONFIG_LOGDB_CLEARTIME, .Min = 1 },
{ .Name = "SkillChance.Orange"sv, .DefaultValue = 100, .Index = CONFIG_SKILL_CHANCE_ORANGE },
{ .Name = "SkillChance.Yellow"sv, .DefaultValue = 75, .Index = CONFIG_SKILL_CHANCE_YELLOW },
{ .Name = "SkillChance.Green"sv, .DefaultValue = 25, .Index = CONFIG_SKILL_CHANCE_GREEN },
{ .Name = "SkillChance.Grey"sv, .DefaultValue = 0, .Index = CONFIG_SKILL_CHANCE_GREY },
{ .Name = "SkillChance.MiningSteps"sv, .DefaultValue = 75, .Index = CONFIG_SKILL_CHANCE_MINING_STEPS },
{ .Name = "SkillChance.SkinningSteps"sv, .DefaultValue = 75, .Index = CONFIG_SKILL_CHANCE_SKINNING_STEPS },
{ .Name = "SkillGain.Crafting"sv, .DefaultValue = 1, .Index = CONFIG_SKILL_GAIN_CRAFTING },
{ .Name = "SkillGain.Gathering"sv, .DefaultValue = 1, .Index = CONFIG_SKILL_GAIN_GATHERING },
{ .Name = "MaxOverspeedPings"sv, .DefaultValue = 2, .Index = CONFIG_MAX_OVERSPEED_PINGS },
{ .Name = "ClientCacheVersion"sv, .DefaultValue = 0, .Index = CONFIG_CLIENTCACHE_VERSION },
{ .Name = "DisableWaterBreath"sv, .DefaultValue = SEC_CONSOLE, .Index = CONFIG_DISABLE_BREATHING },
{ .Name = "Expansion"sv, .DefaultValue = CURRENT_EXPANSION, .Index = CONFIG_EXPANSION, .Min = 0, .Max = MAX_EXPANSIONS, .Reloadable = false },
{ .Name = "ChatFlood.MessageCount"sv, .DefaultValue = 10, .Index = CONFIG_CHATFLOOD_MESSAGE_COUNT },
{ .Name = "ChatFlood.MessageDelay"sv, .DefaultValue = 1, .Index = CONFIG_CHATFLOOD_MESSAGE_DELAY },
{ .Name = "ChatFlood.AddonMessageCount"sv, .DefaultValue = 100, .Index = CONFIG_CHATFLOOD_ADDON_MESSAGE_COUNT },
{ .Name = "ChatFlood.AddonMessageDelay"sv, .DefaultValue = 1, .Index = CONFIG_CHATFLOOD_ADDON_MESSAGE_DELAY },
{ .Name = "ChatFlood.MuteTime"sv, .DefaultValue = 10, .Index = CONFIG_CHATFLOOD_MUTE_TIME },
{ .Name = "CreatureFamilyAssistanceDelay"sv, .DefaultValue = 1500, .Index = CONFIG_CREATURE_FAMILY_ASSISTANCE_DELAY },
{ .Name = "CreatureFamilyFleeDelay"sv, .DefaultValue = 7000, .Index = CONFIG_CREATURE_FAMILY_FLEE_DELAY },
{ .Name = "WorldBossLevelDiff"sv, .DefaultValue = 3, .Index = CONFIG_WORLD_BOSS_LEVEL_DIFF },
{ .Name = "Quests.LowLevelHideDiff"sv, .DefaultValue = 4, .Index = CONFIG_QUEST_LOW_LEVEL_HIDE_DIFF, .Max = MAX_LEVEL },
{ .Name = "Quests.HighLevelHideDiff"sv, .DefaultValue = 7, .Index = CONFIG_QUEST_HIGH_LEVEL_HIDE_DIFF, .Max = MAX_LEVEL },
{ .Name = "Battleground.Random.ResetHour"sv, .DefaultValue = 6, .Index = CONFIG_RANDOM_BG_RESET_HOUR, .Min = 0, .Max = 23 },
{ .Name = "Calendar.DeleteOldEventsHour"sv, .DefaultValue = 6, .Index = CONFIG_CALENDAR_DELETE_OLD_EVENTS_HOUR, .Min = 0, .Max = 23 },
{ .Name = "Guild.ResetHour"sv, .DefaultValue = 6, .Index = CONFIG_GUILD_RESET_HOUR, .Min = 0, .Max = 23 },
{ .Name = "TalentsInspecting"sv, .DefaultValue = 1, .Index = CONFIG_TALENTS_INSPECTING },
{ .Name = "ChatStrictLinkChecking.Severity"sv, .DefaultValue = 0, .Index = CONFIG_CHAT_STRICT_LINK_CHECKING_SEVERITY },
{ .Name = "ChatStrictLinkChecking.Kick"sv, .DefaultValue = 0, .Index = CONFIG_CHAT_STRICT_LINK_CHECKING_KICK },
{ .Name = "Corpse.Decay.Normal"sv, .DefaultValue = 60, .Index = CONFIG_CORPSE_DECAY_NORMAL },
{ .Name = "Corpse.Decay.Elite"sv, .DefaultValue = 300, .Index = CONFIG_CORPSE_DECAY_ELITE },
{ .Name = "Corpse.Decay.RareElite"sv, .DefaultValue = 300, .Index = CONFIG_CORPSE_DECAY_RAREELITE },
{ .Name = "Corpse.Decay.Obsolete"sv, .DefaultValue = 3600, .Index = CONFIG_CORPSE_DECAY_OBSOLETE },
{ .Name = "Corpse.Decay.Rare"sv, .DefaultValue = 300, .Index = CONFIG_CORPSE_DECAY_RARE },
{ .Name = "Corpse.Decay.Trivial"sv, .DefaultValue = 300, .Index = CONFIG_CORPSE_DECAY_TRIVIAL },
{ .Name = "Corpse.Decay.MinusMob"sv, .DefaultValue = 150, .Index = CONFIG_CORPSE_DECAY_MINUSMOB },
{ .Name = "Death.SicknessLevel"sv, .DefaultValue = 11, .Index = CONFIG_DEATH_SICKNESS_LEVEL },
{ .Name = "Battleground.ReportAFK"sv, .DefaultValue = 3, .Index = CONFIG_BATTLEGROUND_REPORT_AFK, .Min = 1, .Max = 9 },
{ .Name = "Battleground.InvitationType"sv, .DefaultValue = 0, .Index = CONFIG_BATTLEGROUND_INVITATION_TYPE },
{ .Name = "Battleground.PrematureFinishTimer"sv, .DefaultValue = 5 * MINUTE * IN_MILLISECONDS, .Index = CONFIG_BATTLEGROUND_PREMATURE_FINISH_TIMER },
{ .Name = "Battleground.PremadeGroupWaitForMatch"sv, .DefaultValue = 30 * MINUTE * IN_MILLISECONDS, .Index = CONFIG_BATTLEGROUND_PREMADE_GROUP_WAIT_FOR_MATCH },
{ .Name = "Arena.MaxRatingDifference"sv, .DefaultValue = 150, .Index = CONFIG_ARENA_MAX_RATING_DIFFERENCE },
{ .Name = "Arena.RatingDiscardTimer"sv, .DefaultValue = 10 * MINUTE * IN_MILLISECONDS, .Index = CONFIG_ARENA_RATING_DISCARD_TIMER },
{ .Name = "Arena.RatedUpdateTimer"sv, .DefaultValue = 5 * IN_MILLISECONDS, .Index = CONFIG_ARENA_RATED_UPDATE_TIMER },
{ .Name = "Arena.ArenaSeason.ID"sv, .DefaultValue = 32, .Index = CONFIG_ARENA_SEASON_ID },
{ .Name = "Arena.ArenaStartRating"sv, .DefaultValue = 0, .Index = CONFIG_ARENA_START_RATING },
{ .Name = "Arena.ArenaStartPersonalRating"sv, .DefaultValue = 1000, .Index = CONFIG_ARENA_START_PERSONAL_RATING },
{ .Name = "Arena.ArenaStartMatchmakerRating"sv, .DefaultValue = 1500, .Index = CONFIG_ARENA_START_MATCHMAKER_RATING },
{ .Name = "Creature.PickPocketRefillDelay"sv, .DefaultValue = 10 * MINUTE, .Index = CONFIG_CREATURE_PICKPOCKET_REFILL },
{ .Name = "Creature.MovingStopTimeForPlayer"sv, .DefaultValue = 3 * MINUTE * IN_MILLISECONDS, .Index = CONFIG_CREATURE_STOP_FOR_PLAYER },
{ .Name = "Guild.NewsLogRecordsCount"sv, .DefaultValue = GUILD_NEWSLOG_MAX_RECORDS, .Index = CONFIG_GUILD_NEWS_LOG_COUNT, .Max = GUILD_NEWSLOG_MAX_RECORDS },
{ .Name = "Guild.EventLogRecordsCount"sv, .DefaultValue = GUILD_EVENTLOG_MAX_RECORDS, .Index = CONFIG_GUILD_EVENT_LOG_COUNT, .Max = GUILD_EVENTLOG_MAX_RECORDS },
{ .Name = "Guild.BankEventLogRecordsCount"sv, .DefaultValue = GUILD_BANKLOG_MAX_RECORDS, .Index = CONFIG_GUILD_BANK_EVENT_LOG_COUNT, .Max = GUILD_BANKLOG_MAX_RECORDS },
{ .Name = "Visibility.Notify.Period.OnContinents"sv, .DefaultValue = DEFAULT_VISIBILITY_NOTIFY_PERIOD, .Index = CONFIG_VISIBILITY_NOTIFY_PERIOD_CONTINENT },
{ .Name = "Visibility.Notify.Period.InInstances"sv, .DefaultValue = DEFAULT_VISIBILITY_NOTIFY_PERIOD, .Index = CONFIG_VISIBILITY_NOTIFY_PERIOD_INSTANCE },
{ .Name = "Visibility.Notify.Period.InBG"sv, .DefaultValue = DEFAULT_VISIBILITY_NOTIFY_PERIOD, .Index = CONFIG_VISIBILITY_NOTIFY_PERIOD_BATTLEGROUND },
{ .Name = "Visibility.Notify.Period.InArenas"sv, .DefaultValue = DEFAULT_VISIBILITY_NOTIFY_PERIOD, .Index = CONFIG_VISIBILITY_NOTIFY_PERIOD_ARENA },
{ .Name = "CharDelete.Method"sv, .DefaultValue = 0, .Index = CONFIG_CHARDELETE_METHOD },
{ .Name = "CharDelete.MinLevel"sv, .DefaultValue = 0, .Index = CONFIG_CHARDELETE_MIN_LEVEL },
{ .Name = "CharDelete.DeathKnight.MinLevel"sv, .DefaultValue = 0, .Index = CONFIG_CHARDELETE_DEATH_KNIGHT_MIN_LEVEL },
{ .Name = "CharDelete.DemonHunter.MinLevel"sv, .DefaultValue = 0, .Index = CONFIG_CHARDELETE_DEMON_HUNTER_MIN_LEVEL },
{ .Name = "CharDelete.KeepDays"sv, .DefaultValue = 30, .Index = CONFIG_CHARDELETE_KEEP_DAYS },
{ .Name = "NoGrayAggro.Above"sv, .DefaultValue = 0, .Index = CONFIG_NO_GRAY_AGGRO_ABOVE },
{ .Name = "NoGrayAggro.Below"sv, .DefaultValue = 0, .Index = CONFIG_NO_GRAY_AGGRO_BELOW },
{ .Name = "Respawn.MinCheckIntervalMS"sv, .DefaultValue = 5000, .Index = CONFIG_RESPAWN_MINCHECKINTERVALMS },
{ .Name = "Respawn.DynamicMode"sv, .DefaultValue = 0, .Index = CONFIG_RESPAWN_DYNAMICMODE, .Min = 0, .Max = 1 },
{ .Name = "Respawn.GuidWarnLevel"sv, .DefaultValue = 12000000, .Index = CONFIG_RESPAWN_GUIDWARNLEVEL, .Min = 0, .Max = 16777215 },
{ .Name = "Respawn.GuidAlertLevel"sv, .DefaultValue = 16000000, .Index = CONFIG_RESPAWN_GUIDALERTLEVEL, .Min = 0, .Max = 16777215 },
{ .Name = "Respawn.RestartQuietTime"sv, .DefaultValue = 3, .Index = CONFIG_RESPAWN_RESTARTQUIETTIME, .Min = 0, .Max = 23 },
{ .Name = "Respawn.DynamicMinimumCreature"sv, .DefaultValue = 10, .Index = CONFIG_RESPAWN_DYNAMICMINIMUM_CREATURE },
{ .Name = "Respawn.DynamicMinimumGameObject"sv, .DefaultValue = 10, .Index = CONFIG_RESPAWN_DYNAMICMINIMUM_GAMEOBJECT },
{ .Name = "Respawn.WarningFrequency"sv, .DefaultValue = 1800, .Index = CONFIG_RESPAWN_GUIDWARNING_FREQUENCY },
{ .Name = "MaxWhoListReturns"sv, .DefaultValue = 49, .Index = CONFIG_MAX_WHO },
{ .Name = "HonorPointsAfterDuel"sv, .DefaultValue = 0, .Index = CONFIG_HONOR_AFTER_DUEL },
{ .Name = "PvPToken.MapAllowType"sv, .DefaultValue = 4, .Index = CONFIG_PVP_TOKEN_MAP_TYPE, .Min = 1, .Max = 4 },
{ .Name = "PvPToken.ItemID"sv, .DefaultValue = 29434, .Index = CONFIG_PVP_TOKEN_ID },
{ .Name = "PvPToken.ItemCount"sv, .DefaultValue = 1, .Index = CONFIG_PVP_TOKEN_COUNT, .Min = 1 },
{ .Name = "MapUpdate.Threads"sv, .DefaultValue = 1, .Index = CONFIG_NUMTHREADS, .Min = 1 },
{ .Name = "Command.LookupMaxResults"sv, .DefaultValue = 0, .Index = CONFIG_MAX_RESULTS_LOOKUP_COMMANDS },
{ .Name = "Warden.NumInjectionChecks"sv, .DefaultValue = 9, .Index = CONFIG_WARDEN_NUM_INJECT_CHECKS },
{ .Name = "Warden.NumLuaSandboxChecks"sv, .DefaultValue = 1, .Index = CONFIG_WARDEN_NUM_LUA_CHECKS },
{ .Name = "Warden.NumClientModChecks"sv, .DefaultValue = 1, .Index = CONFIG_WARDEN_NUM_CLIENT_MOD_CHECKS },
{ .Name = "Warden.BanDuration"sv, .DefaultValue = 86400, .Index = CONFIG_WARDEN_CLIENT_BAN_DURATION },
{ .Name = "Warden.ClientCheckHoldOff"sv, .DefaultValue = 30, .Index = CONFIG_WARDEN_CLIENT_CHECK_HOLDOFF },
{ .Name = "Warden.ClientCheckFailAction"sv, .DefaultValue = 0, .Index = CONFIG_WARDEN_CLIENT_FAIL_ACTION },
{ .Name = "Warden.ClientResponseDelay"sv, .DefaultValue = 600, .Index = CONFIG_WARDEN_CLIENT_RESPONSE_DELAY },
{ .Name = "FeatureSystem.CharacterUndelete.Cooldown"sv, .DefaultValue = 2592000, .Index = CONFIG_FEATURE_SYSTEM_CHARACTER_UNDELETE_COOLDOWN },
{ .Name = "DungeonFinder.OptionsMask"sv, .DefaultValue = 1, .Index = CONFIG_LFG_OPTIONSMASK },
{ .Name = "Account.PasswordChangeSecurity"sv, .DefaultValue = 0, .Index = CONFIG_ACC_PASSCHANGESEC },
{ .Name = "Battleground.RewardWinnerHonorFirst"sv, .DefaultValue = 27000, .Index = CONFIG_BG_REWARD_WINNER_HONOR_FIRST },
{ .Name = "Battleground.RewardWinnerConquestFirst"sv, .DefaultValue = 10000, .Index = CONFIG_BG_REWARD_WINNER_CONQUEST_FIRST },
{ .Name = "Battleground.RewardWinnerHonorLast"sv, .DefaultValue = 13500, .Index = CONFIG_BG_REWARD_WINNER_HONOR_LAST },
{ .Name = "Battleground.RewardWinnerConquestLast"sv, .DefaultValue = 5000, .Index = CONFIG_BG_REWARD_WINNER_CONQUEST_LAST },
{ .Name = "Battleground.RewardLoserHonorFirst"sv, .DefaultValue = 4500, .Index = CONFIG_BG_REWARD_LOSER_HONOR_FIRST },
{ .Name = "Battleground.RewardLoserHonorLast"sv, .DefaultValue = 3500, .Index = CONFIG_BG_REWARD_LOSER_HONOR_LAST },
{ .Name = "AccountInstancesPerHour"sv, .DefaultValue = 10, .Index = CONFIG_MAX_INSTANCES_PER_HOUR },
{ .Name = "AutoBroadcast.Center"sv, .DefaultValue = 0, .Index = CONFIG_AUTOBROADCAST_CENTER },
{ .Name = "AutoBroadcast.Timer"sv, .DefaultValue = 60000, .Index = CONFIG_AUTOBROADCAST_INTERVAL },
{ .Name = "MaxPingTime"sv, .DefaultValue = 30, .Index = CONFIG_DB_PING_INTERVAL },
{ .Name = "Guild.SaveInterval"sv, .DefaultValue = 15, .Index = CONFIG_GUILD_SAVE_INTERVAL },
{ .Name = "Wintergrasp.PlayerMax"sv, .DefaultValue = 100, .Index = CONFIG_WINTERGRASP_PLR_MAX },
{ .Name = "Wintergrasp.PlayerMin"sv, .DefaultValue = 0, .Index = CONFIG_WINTERGRASP_PLR_MIN },
{ .Name = "Wintergrasp.PlayerMinLvl"sv, .DefaultValue = 77, .Index = CONFIG_WINTERGRASP_PLR_MIN_LVL },
{ .Name = "Wintergrasp.BattleTimer"sv, .DefaultValue = 30, .Index = CONFIG_WINTERGRASP_BATTLETIME },
{ .Name = "Wintergrasp.NoBattleTimer"sv, .DefaultValue = 150, .Index = CONFIG_WINTERGRASP_NOBATTLETIME },
{ .Name = "Wintergrasp.CrashRestartTimer"sv, .DefaultValue = 10, .Index = CONFIG_WINTERGRASP_RESTART_AFTER_CRASH },
{ .Name = "TolBarad.PlayerMax"sv, .DefaultValue = 100, .Index = CONFIG_TOLBARAD_PLR_MAX },
{ .Name = "TolBarad.PlayerMin"sv, .DefaultValue = 0, .Index = CONFIG_TOLBARAD_PLR_MIN },
{ .Name = "TolBarad.PlayerMinLvl"sv, .DefaultValue = 85, .Index = CONFIG_TOLBARAD_PLR_MIN_LVL },
{ .Name = "TolBarad.BattleTimer"sv, .DefaultValue = 15, .Index = CONFIG_TOLBARAD_BATTLETIME },
{ .Name = "TolBarad.BonusTime"sv, .DefaultValue = 5, .Index = CONFIG_TOLBARAD_BONUSTIME },
{ .Name = "TolBarad.NoBattleTimer"sv, .DefaultValue = 150, .Index = CONFIG_TOLBARAD_NOBATTLETIME },
{ .Name = "TolBarad.CrashRestartTimer"sv, .DefaultValue = 10, .Index = CONFIG_TOLBARAD_RESTART_AFTER_CRASH },
{ .Name = "PacketSpoof.Policy"sv, .DefaultValue = WorldSession::DosProtection::POLICY_KICK, .Index = CONFIG_PACKET_SPOOF_POLICY },
{ .Name = "PacketSpoof.BanMode"sv, .DefaultValue = BAN_ACCOUNT, .Index = CONFIG_PACKET_SPOOF_BANMODE, .Min = BAN_ACCOUNT, .Max = BAN_IP },
{ .Name = "PacketSpoof.BanDuration"sv, .DefaultValue = 86400, .Index = CONFIG_PACKET_SPOOF_BANDURATION },
{ .Name = "AuctionHouseBot.Update.Interval"sv, .DefaultValue = 20, .Index = CONFIG_AHBOT_UPDATE_INTERVAL },
{ .Name = "BlackMarket.MaxAuctions"sv, .DefaultValue = 12, .Index = CONFIG_BLACKMARKET_MAXAUCTIONS },
{ .Name = "BlackMarket.UpdatePeriod"sv, .DefaultValue = 24, .Index = CONFIG_BLACKMARKET_UPDATE_PERIOD },
{ .Name = "Pvp.FactionBalance.LevelCheckDiff"sv, .DefaultValue = 0, .Index = CONFIG_FACTION_BALANCE_LEVEL_CHECK_DIFF },
} };
static constexpr ConfigOptionLoadDefinitionArray int64s =
{ {
{ .Name = "CharacterCreating.Disabled.RaceMask"sv, .DefaultValue = 0, .Index = CONFIG_CHARACTER_CREATING_DISABLED_RACEMASK },
{ .Name = "StartPlayerMoney"sv, .DefaultValue = 0, .Index = CONFIG_START_PLAYER_MONEY, .Min = 0, .Max = MAX_MONEY_AMOUNT },
} };
static constexpr ConfigOptionLoadDefinitionArray floats =
{ {
{ .Name = "MaxGroupXPDistance"sv, .DefaultValue = 74.0f, .Index = CONFIG_GROUP_XP_DISTANCE },
{ .Name = "MaxRecruitAFriendBonusDistance"sv, .DefaultValue = 100.0f, .Index = CONFIG_MAX_RECRUIT_A_FRIEND_DISTANCE },
{ .Name = "MonsterSight"sv, .DefaultValue = 50.0f, .Index = CONFIG_SIGHT_MONSTER },
{ .Name = "CreatureFamilyFleeAssistanceRadius"sv, .DefaultValue = 30.0f, .Index = CONFIG_CREATURE_FAMILY_FLEE_ASSISTANCE_RADIUS },
{ .Name = "CreatureFamilyAssistanceRadius"sv, .DefaultValue = 10.0f, .Index = CONFIG_CREATURE_FAMILY_ASSISTANCE_RADIUS },
{ .Name = "ThreatRadius"sv, .DefaultValue = 60.0f, .Index = CONFIG_THREAT_RADIUS },
{ .Name = "ListenRange.Say"sv, .DefaultValue = 25.0f, .Index = CONFIG_LISTEN_RANGE_SAY },
{ .Name = "ListenRange.TextEmote"sv, .DefaultValue = 25.0f, .Index = CONFIG_LISTEN_RANGE_TEXTEMOTE },
{ .Name = "ListenRange.Yell"sv, .DefaultValue = 300.0f, .Index = CONFIG_LISTEN_RANGE_YELL },
{ .Name = "Arena.ArenaWinRatingModifier1"sv, .DefaultValue = 48.0f, .Index = CONFIG_ARENA_WIN_RATING_MODIFIER_1 },
{ .Name = "Arena.ArenaWinRatingModifier2"sv, .DefaultValue = 24.0f, .Index = CONFIG_ARENA_WIN_RATING_MODIFIER_2 },
{ .Name = "Arena.ArenaLoseRatingModifier"sv, .DefaultValue = 24.0f, .Index = CONFIG_ARENA_LOSE_RATING_MODIFIER },
{ .Name = "Arena.ArenaMatchmakerRatingModifier"sv, .DefaultValue = 24.0f, .Index = CONFIG_ARENA_MATCHMAKER_RATING_MODIFIER },
{ .Name = "Visibility.Distance.Continents"sv, .DefaultValue = DEFAULT_VISIBILITY_DISTANCE, .Index = CONFIG_MAX_VISIBILITY_DISTANCE_CONTINENT, .Min = 0.0f, .Max = MAX_VISIBILITY_DISTANCE },
{ .Name = "Visibility.Distance.Instances"sv, .DefaultValue = DEFAULT_VISIBILITY_INSTANCE, .Index = CONFIG_MAX_VISIBILITY_DISTANCE_INSTANCE, .Min = 0.0f, .Max = MAX_VISIBILITY_DISTANCE },
{ .Name = "Visibility.Distance.BG"sv, .DefaultValue = DEFAULT_VISIBILITY_BGARENAS, .Index = CONFIG_MAX_VISIBILITY_DISTANCE_BATTLEGROUND, .Min = 0.0f, .Max = MAX_VISIBILITY_DISTANCE },
{ .Name = "Visibility.Distance.Arenas"sv, .DefaultValue = DEFAULT_VISIBILITY_BGARENAS, .Index = CONFIG_MAX_VISIBILITY_DISTANCE_ARENA, .Min = 0.0f, .Max = MAX_VISIBILITY_DISTANCE },
{ .Name = "Respawn.DynamicRateCreature"sv, .DefaultValue = 10.0f, .Index = CONFIG_RESPAWN_DYNAMICRATE_CREATURE, .Min = 0.0f },
{ .Name = "Respawn.DynamicRateGameObject"sv, .DefaultValue = 10.0f, .Index = CONFIG_RESPAWN_DYNAMICRATE_GAMEOBJECT, .Min = 0.0f },
{ .Name = "Stats.Limits.Dodge"sv, .DefaultValue = 95.0f, .Index = CONFIG_STATS_LIMITS_DODGE },
{ .Name = "Stats.Limits.Parry"sv, .DefaultValue = 95.0f, .Index = CONFIG_STATS_LIMITS_PARRY },
{ .Name = "Stats.Limits.Block"sv, .DefaultValue = 95.0f, .Index = CONFIG_STATS_LIMITS_BLOCK },
{ .Name = "Stats.Limits.Crit"sv, .DefaultValue = 95.0f, .Index = CONFIG_STATS_LIMITS_CRIT },
{ .Name = "Pvp.FactionBalance.Pct5"sv, .DefaultValue = 0.6f, .Index = CONFIG_CALL_TO_ARMS_5_PCT },
{ .Name = "Pvp.FactionBalance.Pct10"sv, .DefaultValue = 0.7f, .Index = CONFIG_CALL_TO_ARMS_10_PCT },
{ .Name = "Pvp.FactionBalance.Pct20"sv, .DefaultValue = 0.8f, .Index = CONFIG_CALL_TO_ARMS_20_PCT },
} };
static constexpr ConfigOptionLoadDefinitionArray rates =
{ {
{ .Name = "Rate.Health"sv, .DefaultValue = 1.0f, .Index = RATE_HEALTH, .Min = 0.01f },
{ .Name = "Rate.Mana"sv, .DefaultValue = 1.0f, .Index = RATE_POWER_MANA, .Min = 0.01f },
{ .Name = "Rate.Rage.Gain"sv, .DefaultValue = 1.0f, .Index = RATE_POWER_RAGE_INCOME, .Min = 0.01f },
{ .Name = "Rate.Rage.Loss"sv, .DefaultValue = 1.0f, .Index = RATE_POWER_RAGE_LOSS, .Min = 0.01f },
{ .Name = "Rate.Focus"sv, .DefaultValue = 1.0f, .Index = RATE_POWER_FOCUS, .Min = 0.01f },
{ .Name = "Rate.Energy"sv, .DefaultValue = 1.0f, .Index = RATE_POWER_ENERGY, .Min = 0.01f },
{ .Name = "Rate.ComboPoints.Loss"sv, .DefaultValue = 1.0f, .Index = RATE_POWER_COMBO_POINTS_LOSS, .Min = 0.01f },
{ .Name = "Rate.RunicPower.Gain"sv, .DefaultValue = 1.0f, .Index = RATE_POWER_RUNIC_POWER_INCOME, .Min = 0.01f },
{ .Name = "Rate.RunicPower.Loss"sv, .DefaultValue = 1.0f, .Index = RATE_POWER_RUNIC_POWER_LOSS, .Min = 0.01f },
{ .Name = "Rate.SoulShards.Loss"sv, .DefaultValue = 1.0f, .Index = RATE_POWER_SOUL_SHARDS, .Min = 0.01f },
{ .Name = "Rate.LunarPower.Loss"sv, .DefaultValue = 1.0f, .Index = RATE_POWER_LUNAR_POWER, .Min = 0.01f },
{ .Name = "Rate.HolyPower.Loss"sv, .DefaultValue = 1.0f, .Index = RATE_POWER_HOLY_POWER, .Min = 0.01f },
{ .Name = "Rate.Maelstrom.Loss"sv, .DefaultValue = 1.0f, .Index = RATE_POWER_MAELSTROM, .Min = 0.01f },
{ .Name = "Rate.Chi.Loss"sv, .DefaultValue = 1.0f, .Index = RATE_POWER_CHI, .Min = 0.01f },
{ .Name = "Rate.Insanity.Loss"sv, .DefaultValue = 1.0f, .Index = RATE_POWER_INSANITY, .Min = 0.01f },
{ .Name = "Rate.ArcaneCharges.Loss"sv, .DefaultValue = 1.0f, .Index = RATE_POWER_ARCANE_CHARGES, .Min = 0.01f },
{ .Name = "Rate.Fury.Loss"sv, .DefaultValue = 1.0f, .Index = RATE_POWER_FURY, .Min = 0.01f },
{ .Name = "Rate.Pain.Loss"sv, .DefaultValue = 1.0f, .Index = RATE_POWER_PAIN, .Min = 0.01f },
{ .Name = "Rate.Essence.Loss"sv, .DefaultValue = 1.0f, .Index = RATE_POWER_ESSENCE, .Min = 0.01f },
{ .Name = "Rate.Skill.Discovery"sv, .DefaultValue = 1.0f, .Index = RATE_SKILL_DISCOVERY },
{ .Name = "Rate.Drop.Item.Poor"sv, .DefaultValue = 1.0f, .Index = RATE_DROP_ITEM_POOR },
{ .Name = "Rate.Drop.Item.Normal"sv, .DefaultValue = 1.0f, .Index = RATE_DROP_ITEM_NORMAL },
{ .Name = "Rate.Drop.Item.Uncommon"sv, .DefaultValue = 1.0f, .Index = RATE_DROP_ITEM_UNCOMMON },
{ .Name = "Rate.Drop.Item.Rare"sv, .DefaultValue = 1.0f, .Index = RATE_DROP_ITEM_RARE },
{ .Name = "Rate.Drop.Item.Epic"sv, .DefaultValue = 1.0f, .Index = RATE_DROP_ITEM_EPIC },
{ .Name = "Rate.Drop.Item.Legendary"sv, .DefaultValue = 1.0f, .Index = RATE_DROP_ITEM_LEGENDARY },
{ .Name = "Rate.Drop.Item.Artifact"sv, .DefaultValue = 1.0f, .Index = RATE_DROP_ITEM_ARTIFACT },
{ .Name = "Rate.Drop.Item.Referenced"sv, .DefaultValue = 1.0f, .Index = RATE_DROP_ITEM_REFERENCED },
{ .Name = "Rate.Drop.Item.ReferencedAmount"sv, .DefaultValue = 1.0f, .Index = RATE_DROP_ITEM_REFERENCED_AMOUNT },
{ .Name = "Rate.Drop.Money"sv, .DefaultValue = 1.0f, .Index = RATE_DROP_MONEY },
{ .Name = "Rate.XP.Kill"sv, .DefaultValue = 1.0f, .Index = RATE_XP_KILL },
{ .Name = "Rate.XP.BattlegroundKill"sv, .DefaultValue = 1.0f, .Index = RATE_XP_BG_KILL },
{ .Name = "Rate.XP.Quest"sv, .DefaultValue = 1.0f, .Index = RATE_XP_QUEST },
{ .Name = "Rate.XP.Explore"sv, .DefaultValue = 1.0f, .Index = RATE_XP_EXPLORE },
{ .Name = "XP.Boost.Rate"sv, .DefaultValue = 2.0f, .Index = RATE_XP_BOOST },
{ .Name = "Rate.RepairCost"sv, .DefaultValue = 1.0f, .Index = RATE_REPAIRCOST, .Min = 0.0f },
{ .Name = "Rate.Reputation.Gain"sv, .DefaultValue = 1.0f, .Index = RATE_REPUTATION_GAIN },
{ .Name = "Rate.Reputation.LowLevel.Kill"sv, .DefaultValue = 1.0f, .Index = RATE_REPUTATION_LOWLEVEL_KILL },
{ .Name = "Rate.Reputation.LowLevel.Quest"sv, .DefaultValue = 1.0f, .Index = RATE_REPUTATION_LOWLEVEL_QUEST },
{ .Name = "Rate.Reputation.RecruitAFriendBonus"sv, .DefaultValue = 0.1f, .Index = RATE_REPUTATION_RECRUIT_A_FRIEND_BONUS},
{ .Name = "Rate.Creature.HP.Normal"sv, .DefaultValue = 1.0f, .Index = RATE_CREATURE_HP_NORMAL },
{ .Name = "Rate.Creature.HP.Elite"sv, .DefaultValue = 1.0f, .Index = RATE_CREATURE_HP_ELITE },
{ .Name = "Rate.Creature.HP.RareElite"sv, .DefaultValue = 1.0f, .Index = RATE_CREATURE_HP_RAREELITE },
{ .Name = "Rate.Creature.HP.Obsolete"sv, .DefaultValue = 1.0f, .Index = RATE_CREATURE_HP_OBSOLETE },
{ .Name = "Rate.Creature.HP.Rare"sv, .DefaultValue = 1.0f, .Index = RATE_CREATURE_HP_RARE },
{ .Name = "Rate.Creature.HP.Trivial"sv, .DefaultValue = 1.0f, .Index = RATE_CREATURE_HP_TRIVIAL },
{ .Name = "Rate.Creature.HP.MinusMob"sv, .DefaultValue = 1.0f, .Index = RATE_CREATURE_HP_MINUSMOB },
{ .Name = "Rate.Creature.Damage.Normal"sv, .DefaultValue = 1.0f, .Index = RATE_CREATURE_DAMAGE_NORMAL },
{ .Name = "Rate.Creature.Damage.Elite"sv, .DefaultValue = 1.0f, .Index = RATE_CREATURE_DAMAGE_ELITE },
{ .Name = "Rate.Creature.Damage.RareElite"sv, .DefaultValue = 1.0f, .Index = RATE_CREATURE_DAMAGE_RAREELITE },
{ .Name = "Rate.Creature.Damage.Obsolete"sv, .DefaultValue = 1.0f, .Index = RATE_CREATURE_DAMAGE_OBSOLETE },
{ .Name = "Rate.Creature.Damage.Rare"sv, .DefaultValue = 1.0f, .Index = RATE_CREATURE_DAMAGE_RARE },
{ .Name = "Rate.Creature.Damage.Trivial"sv, .DefaultValue = 1.0f, .Index = RATE_CREATURE_DAMAGE_TRIVIAL },
{ .Name = "Rate.Creature.Damage.MinusMob"sv, .DefaultValue = 1.0f, .Index = RATE_CREATURE_DAMAGE_MINUSMOB },
{ .Name = "Rate.Creature.SpellDamage.Normal"sv, .DefaultValue = 1.0f, .Index = RATE_CREATURE_SPELLDAMAGE_NORMAL },
{ .Name = "Rate.Creature.SpellDamage.Elite"sv, .DefaultValue = 1.0f, .Index = RATE_CREATURE_SPELLDAMAGE_ELITE },
{ .Name = "Rate.Creature.SpellDamage.RareElite"sv, .DefaultValue = 1.0f, .Index = RATE_CREATURE_SPELLDAMAGE_RAREELITE },
{ .Name = "Rate.Creature.SpellDamage.Obsolete"sv, .DefaultValue = 1.0f, .Index = RATE_CREATURE_SPELLDAMAGE_OBSOLETE },
{ .Name = "Rate.Creature.SpellDamage.Rare"sv, .DefaultValue = 1.0f, .Index = RATE_CREATURE_SPELLDAMAGE_RARE },
{ .Name = "Rate.Creature.SpellDamage.Trivial"sv, .DefaultValue = 1.0f, .Index = RATE_CREATURE_SPELLDAMAGE_TRIVIAL },
{ .Name = "Rate.Creature.SpellDamage.MinusMob"sv, .DefaultValue = 1.0f, .Index = RATE_CREATURE_SPELLDAMAGE_MINUSMOB },
{ .Name = "Rate.Creature.Aggro"sv, .DefaultValue = 1.0f, .Index = RATE_CREATURE_AGGRO },
{ .Name = "Rate.Rest.InGame"sv, .DefaultValue = 1.0f, .Index = RATE_REST_INGAME },
{ .Name = "Rate.Rest.Offline.InTavernOrCity"sv, .DefaultValue = 1.0f, .Index = RATE_REST_OFFLINE_IN_TAVERN_OR_CITY },
{ .Name = "Rate.Rest.Offline.InWilderness"sv, .DefaultValue = 1.0f, .Index = RATE_REST_OFFLINE_IN_WILDERNESS },
{ .Name = "Rate.Damage.Fall"sv, .DefaultValue = 1.0f, .Index = RATE_DAMAGE_FALL },
{ .Name = "Rate.Auction.Time"sv, .DefaultValue = 1.0f, .Index = RATE_AUCTION_TIME },
{ .Name = "Rate.Auction.Deposit"sv, .DefaultValue = 1.0f, .Index = RATE_AUCTION_DEPOSIT },
{ .Name = "Rate.Auction.Cut"sv, .DefaultValue = 1.0f, .Index = RATE_AUCTION_CUT },
{ .Name = "Rate.Honor"sv, .DefaultValue = 1.0f, .Index = RATE_HONOR },
{ .Name = "Rate.InstanceResetTime"sv, .DefaultValue = 1.0f, .Index = RATE_INSTANCE_RESET_TIME },
{ .Name = "Rate.MoveSpeed"sv, .DefaultValue = 1.0f, .Index = RATE_MOVESPEED, .Min = 0.01f },
{ .Name = "Rate.Corpse.Decay.Looted"sv, .DefaultValue = 0.5f, .Index = RATE_CORPSE_DECAY_LOOTED },
{ .Name = "DurabilityLoss.OnDeath"sv, .DefaultValue = 10.0f, .Index = RATE_DURABILITY_LOSS_ON_DEATH, .Min = 0.0f, .Max = 100.0f },
{ .Name = "DurabilityLossChance.Damage"sv, .DefaultValue = 0.5f, .Index = RATE_DURABILITY_LOSS_DAMAGE, .Min = 0.0f },
{ .Name = "DurabilityLossChance.Absorb"sv, .DefaultValue = 0.5f, .Index = RATE_DURABILITY_LOSS_ABSORB, .Min = 0.0f },
{ .Name = "DurabilityLossChance.Parry"sv, .DefaultValue = 0.05f, .Index = RATE_DURABILITY_LOSS_PARRY, .Min = 0.0f },
{ .Name = "DurabilityLossChance.Block"sv, .DefaultValue = 0.05f, .Index = RATE_DURABILITY_LOSS_BLOCK, .Min = 0.0f },
{ .Name = "Rate.Quest.Money.Reward"sv, .DefaultValue = 1.0f, .Index = RATE_MONEY_QUEST, .Min = 0.0f },
{ .Name = "Rate.Quest.Money.Max.Level.Reward"sv, .DefaultValue = 1.0f, .Index = RATE_MONEY_MAX_LEVEL_QUEST, .Min = 0.0f },
} };
for (ConfigOptionLoadDefinition const& definition : bools)
StoreConfigValue(m_bool_configs[definition.Index], sConfigMgr->GetBoolDefault(definition.Name, definition.DefaultValue), definition, reload);
for (ConfigOptionLoadDefinition const& definition : ints)
StoreConfigValue(m_int_configs[definition.Index], sConfigMgr->GetIntDefault(definition.Name, definition.DefaultValue), definition, reload);
for (ConfigOptionLoadDefinition const& definition : int64s)
StoreConfigValue(m_int64_configs[definition.Index], sConfigMgr->GetInt64Default(definition.Name, definition.DefaultValue), definition, reload);
for (ConfigOptionLoadDefinition const& definition : floats)
StoreConfigValue(m_float_configs[definition.Index], sConfigMgr->GetFloatDefault(definition.Name, definition.DefaultValue), definition, reload);
for (ConfigOptionLoadDefinition const& definition : rates)
StoreConfigValue(rate_values[definition.Index], sConfigMgr->GetFloatDefault(definition.Name, definition.DefaultValue), definition, reload);
///- Get string for new logins (newly created characters)
SetNewCharString(sConfigMgr->GetStringDefault("PlayerStart.String"sv, ""sv));
for (uint8 i = 0; i < MAX_MOVE_TYPE; ++i)
playerBaseMoveSpeed[i] = baseMoveSpeed[i] * rate_values[RATE_MOVESPEED];
rate_values[RATE_DURABILITY_LOSS_ON_DEATH] /= 100.0f;
if (m_bool_configs[CONFIG_GRID_UNLOAD])
{
if (m_bool_configs[CONFIG_BASEMAP_LOAD_GRIDS])
{
TC_LOG_ERROR("server.loading", "BaseMapLoadAllGrids enabled, but GridUnload also enabled. GridUnload must be disabled to enable base map pre-loading. Base map pre-loading disabled");
m_bool_configs[CONFIG_BASEMAP_LOAD_GRIDS] = false;
}
if (m_bool_configs[CONFIG_INSTANCEMAP_LOAD_GRIDS])
{
TC_LOG_ERROR("server.loading", "InstanceMapLoadAllGrids enabled, but GridUnload also enabled. GridUnload must be disabled to enable instance map pre-loading. Instance map pre-loading disabled");
m_bool_configs[CONFIG_INSTANCEMAP_LOAD_GRIDS] = false;
}
}
// Config values are in "milliseconds" but we handle SocketTimeOut only as "seconds" so divide by 1000
m_int_configs[CONFIG_SOCKET_TIMEOUTTIME] /= 1000;
m_int_configs[CONFIG_SOCKET_TIMEOUTTIME_ACTIVE] /= 1000;
// must be after CONFIG_CHARACTERS_PER_REALM
if (m_int_configs[CONFIG_CHARACTERS_PER_ACCOUNT] < m_int_configs[CONFIG_CHARACTERS_PER_REALM])
{
TC_LOG_ERROR("server.loading", "CharactersPerAccount ({}) can't be less than CharactersPerRealm ({}).", m_int_configs[CONFIG_CHARACTERS_PER_ACCOUNT], m_int_configs[CONFIG_CHARACTERS_PER_REALM]);
m_int_configs[CONFIG_CHARACTERS_PER_ACCOUNT] = m_int_configs[CONFIG_CHARACTERS_PER_REALM];
}
auto validateStartLevel = [&](WorldIntConfigs config, char const* name)
{
uint32 maxLevel = m_int_configs[CONFIG_MAX_PLAYER_LEVEL];
if (m_int_configs[config] > maxLevel)
{
TC_LOG_ERROR("server.loading", "{} ({}) must be in range 1..MaxPlayerLevel({}). Set to {}.", name, m_int_configs[config], maxLevel, maxLevel);
m_int_configs[config] = maxLevel;
}
};
validateStartLevel(CONFIG_START_PLAYER_LEVEL, "StartPlayerLevel");
validateStartLevel(CONFIG_START_DEATH_KNIGHT_PLAYER_LEVEL, "StartDeathKnightPlayerLevel");
validateStartLevel(CONFIG_START_DEMON_HUNTER_PLAYER_LEVEL, "StartDemonHunterPlayerLevel");
validateStartLevel(CONFIG_START_EVOKER_PLAYER_LEVEL, "StartEvokerPlayerLevel");
validateStartLevel(CONFIG_START_ALLIED_RACE_LEVEL, "StartDemonHunterPlayerLevel");
validateStartLevel(CONFIG_MAX_RECRUIT_A_FRIEND_BONUS_PLAYER_LEVEL, "RecruitAFriend.MaxLevel");
if (m_int_configs[CONFIG_START_GM_LEVEL] < m_int_configs[CONFIG_START_PLAYER_LEVEL])
{
TC_LOG_ERROR("server.loading", "GM.StartLevel ({}) must be in range StartPlayerLevel({})..{}. Set to {}.",
m_int_configs[CONFIG_START_GM_LEVEL], m_int_configs[CONFIG_START_PLAYER_LEVEL], MAX_LEVEL, m_int_configs[CONFIG_START_PLAYER_LEVEL]);
m_int_configs[CONFIG_START_GM_LEVEL] = m_int_configs[CONFIG_START_PLAYER_LEVEL];
}
TC_LOG_INFO("server.loading", "Will clear `logs` table of entries older than {} seconds every {} minutes.",
m_int_configs[CONFIG_LOGDB_CLEARTIME], m_int_configs[CONFIG_LOGDB_CLEARINTERVAL]);
if (m_int_configs[CONFIG_MAX_OVERSPEED_PINGS] != 0 && m_int_configs[CONFIG_MAX_OVERSPEED_PINGS] < 2)
{
TC_LOG_ERROR("server.loading", "MaxOverspeedPings ({}) must be in range 2..infinity (or 0 to disable check). Set to 2.", m_int_configs[CONFIG_MAX_OVERSPEED_PINGS]);
m_int_configs[CONFIG_MAX_OVERSPEED_PINGS] = 2;
}
// always use declined names in the russian client
if (Cfg_CategoriesEntry const* category = sCfgCategoriesStore.LookupEntry(m_int_configs[CONFIG_REALM_ZONE]))
if (category->GetCreateCharsetMask().HasFlag(CfgCategoriesCharsets::Russian))
m_bool_configs[CONFIG_DECLINED_NAMES_USED] = true;
if (!m_int_configs[CONFIG_CLIENTCACHE_VERSION])
m_int_configs[CONFIG_CLIENTCACHE_VERSION] = databaseCacheVersion;
TC_LOG_INFO("server.loading", "Client cache version set to: {}", m_int_configs[CONFIG_CLIENTCACHE_VERSION]);
auto validateVisibilityDistance = [&](WorldFloatConfigs config, char const* name)
{
float minVisibilityDistance = 45.0f * rate_values[RATE_CREATURE_AGGRO];
if (m_float_configs[config] < minVisibilityDistance)
{
TC_LOG_ERROR("server.loading", "{} can't be less max aggro radius {}", name, minVisibilityDistance);
m_float_configs[config] = minVisibilityDistance;
}
};
// visibility on continents
validateVisibilityDistance(CONFIG_MAX_VISIBILITY_DISTANCE_CONTINENT, "Visibility.Distance.Continents");
// visibility in instances
validateVisibilityDistance(CONFIG_MAX_VISIBILITY_DISTANCE_INSTANCE, "Visibility.Distance.Instances");
// visibility in BG
validateVisibilityDistance(CONFIG_MAX_VISIBILITY_DISTANCE_BATTLEGROUND, "Visibility.Distance.BG");
// Visibility in Arenas
validateVisibilityDistance(CONFIG_MAX_VISIBILITY_DISTANCE_ARENA, "Visibility.Distance.Arenas");
// No aggro from gray mobs
if (m_int_configs[CONFIG_NO_GRAY_AGGRO_ABOVE] > m_int_configs[CONFIG_MAX_PLAYER_LEVEL])
{
TC_LOG_ERROR("server.loading", "NoGrayAggro.Above ({}) must be in range 0..{}. Set to {}.", m_int_configs[CONFIG_NO_GRAY_AGGRO_ABOVE], m_int_configs[CONFIG_MAX_PLAYER_LEVEL], m_int_configs[CONFIG_MAX_PLAYER_LEVEL]);
m_int_configs[CONFIG_NO_GRAY_AGGRO_ABOVE] = m_int_configs[CONFIG_MAX_PLAYER_LEVEL];
}
if (m_int_configs[CONFIG_NO_GRAY_AGGRO_BELOW] > m_int_configs[CONFIG_MAX_PLAYER_LEVEL])
{
TC_LOG_ERROR("server.loading", "NoGrayAggro.Below ({}) must be in range 0..{}. Set to {}.", m_int_configs[CONFIG_NO_GRAY_AGGRO_BELOW], m_int_configs[CONFIG_MAX_PLAYER_LEVEL], m_int_configs[CONFIG_MAX_PLAYER_LEVEL]);
m_int_configs[CONFIG_NO_GRAY_AGGRO_BELOW] = m_int_configs[CONFIG_MAX_PLAYER_LEVEL];
}
if (m_int_configs[CONFIG_NO_GRAY_AGGRO_ABOVE] > 0 && m_int_configs[CONFIG_NO_GRAY_AGGRO_ABOVE] < m_int_configs[CONFIG_NO_GRAY_AGGRO_BELOW])
{
TC_LOG_ERROR("server.loading", "NoGrayAggro.Below ({}) cannot be greater than NoGrayAggro.Above ({}). Set to {}.", m_int_configs[CONFIG_NO_GRAY_AGGRO_BELOW], m_int_configs[CONFIG_NO_GRAY_AGGRO_ABOVE], m_int_configs[CONFIG_NO_GRAY_AGGRO_ABOVE]);
m_int_configs[CONFIG_NO_GRAY_AGGRO_BELOW] = m_int_configs[CONFIG_NO_GRAY_AGGRO_ABOVE];
}
// Respawn Settings
_guidWarningMsg = sConfigMgr->GetStringDefault("Respawn.WarningMessage"sv, "There will be an unscheduled server restart at 03:00. The server will be available again shortly after."sv);
_alertRestartReason = sConfigMgr->GetStringDefault("Respawn.AlertRestartReason"sv, "Urgent Maintenance"sv);
///- Read the "Data" directory from the config file
std::string dataPath = sConfigMgr->GetStringDefault("DataDir"sv, "./"sv);
if (dataPath.empty() || (dataPath.back() != '/' && dataPath.back() != '\\'))
dataPath.push_back('/');
if (dataPath[0] == '~')
{
#if TRINITY_PLATFORM != TRINITY_PLATFORM_WINDOWS
#define USER_HOME_DIRECTORY_VARIABLE "HOME"
#else
#define USER_HOME_DIRECTORY_VARIABLE "USERPROFILE"
#endif
if (char const* home = std::getenv(USER_HOME_DIRECTORY_VARIABLE))
dataPath.replace(0, 1, home);
#undef USER_HOME_DIRECTORY_VARIABLE
}
if (reload)
{
if (dataPath != m_dataPath)
TC_LOG_ERROR("server.loading", "DataDir option can't be changed at worldserver.conf reload, using current value ({}).", m_dataPath);
}
else
{
m_dataPath = std::move(dataPath);
TC_LOG_INFO("server.loading", "Using DataDir {}", m_dataPath);
}
TC_LOG_INFO("server.loading", "WORLD: MMap data directory is: {}mmaps", m_dataPath);
bool enableLOS = sConfigMgr->GetBoolDefault("vmap.enableLOS"sv, true);
bool enableHeight = sConfigMgr->GetBoolDefault("vmap.enableHeight"sv, true);
if (!enableHeight)
TC_LOG_ERROR("server.loading", "VMap height checking disabled! Creatures movements and other various things WILL be broken! Expect no support.");
VMAP::VMapFactory::createOrGetVMapManager()->setEnableLineOfSightCalc(enableLOS);
VMAP::VMapFactory::createOrGetVMapManager()->setEnableHeightCalc(enableHeight);
TC_LOG_INFO("server.loading", "VMap support included. LineOfSight: {}, getHeight: {}, indoorCheck: {}", enableLOS, enableHeight, m_bool_configs[CONFIG_VMAP_INDOOR_CHECK]);
TC_LOG_INFO("server.loading", "VMap data directory is: {}vmaps", m_dataPath);
if (m_bool_configs[CONFIG_START_ALL_SPELLS])
TC_LOG_WARN("server.loading", "PlayerStart.AllSpells enabled - may not function as intended!");
//packet spoof punishment
if (m_int_configs[CONFIG_PACKET_SPOOF_BANMODE] == BAN_CHARACTER)
m_int_configs[CONFIG_PACKET_SPOOF_BANMODE] = BAN_ACCOUNT;
_gameRules =
{
{ .Rule = ::GameRule::TransmogEnabled, .Value = true }
};
if (reload)
{
sSupportMgr->SetSupportSystemStatus(m_bool_configs[CONFIG_SUPPORT_ENABLED]);
sSupportMgr->SetTicketSystemStatus(m_bool_configs[CONFIG_SUPPORT_TICKETS_ENABLED]);
sSupportMgr->SetBugSystemStatus(m_bool_configs[CONFIG_SUPPORT_BUGS_ENABLED]);
sSupportMgr->SetComplaintSystemStatus(m_bool_configs[CONFIG_SUPPORT_COMPLAINTS_ENABLED]);
sSupportMgr->SetSuggestionSystemStatus(m_bool_configs[CONFIG_SUPPORT_SUGGESTIONS_ENABLED]);
sMapMgr->SetGridCleanUpDelay(m_int_configs[CONFIG_INTERVAL_GRIDCLEAN]);
sMapMgr->SetMapUpdateInterval(m_int_configs[CONFIG_INTERVAL_MAPUPDATE]);
m_timers[WUPDATE_UPTIME].SetInterval(m_int_configs[CONFIG_UPTIME_UPDATE] * MINUTE * IN_MILLISECONDS);
m_timers[WUPDATE_UPTIME].Reset();
m_timers[WUPDATE_CLEANDB].SetInterval(m_int_configs[CONFIG_LOGDB_CLEARINTERVAL] * MINUTE * IN_MILLISECONDS);
m_timers[WUPDATE_CLEANDB].Reset();
m_timers[WUPDATE_AUTOBROADCAST].SetInterval(m_int_configs[CONFIG_AUTOBROADCAST_INTERVAL]);
m_timers[WUPDATE_AUTOBROADCAST].Reset();
sWorldStateMgr->SetValue(WS_CURRENT_PVP_SEASON_ID, getBoolConfig(CONFIG_ARENA_SEASON_IN_PROGRESS) ? getIntConfig(CONFIG_ARENA_SEASON_ID) : 0, false, nullptr);
sWorldStateMgr->SetValue(WS_PREVIOUS_PVP_SEASON_ID, getIntConfig(CONFIG_ARENA_SEASON_ID) - getBoolConfig(CONFIG_ARENA_SEASON_IN_PROGRESS), false, nullptr);
// call ScriptMgr if we're reloading the configuration
sScriptMgr->OnConfigLoad(reload);
}
}
/// Initialize the World
bool World::SetInitialWorldSettings()
{
sLog->SetRealmId(sRealmList->GetCurrentRealmId().Realm);
///- Server startup begin
uint32 startupBegin = getMSTime();
///- Initialize the random number generator
srand((unsigned int)GameTime::GetGameTime());
///- Initialize detour memory management
dtAllocSetCustom(dtCustomAlloc, dtCustomFree);
///- Initialize VMapManager function pointers (to untangle game/collision circular deps)
VMAP::VMapManager2* vmmgr2 = VMAP::VMapFactory::createOrGetVMapManager();
vmmgr2->GetLiquidFlagsPtr = &DB2Manager::GetLiquidFlags;
vmmgr2->IsVMAPDisabledForPtr = &DisableMgr::IsVMAPDisabledFor;
///- Initialize config settings
LoadConfigSettings();
///- Initialize Allowed Security Level
LoadDBAllowedSecurityLevel();
///- Init highest guids before any table loading to prevent using not initialized guids in some code.
sObjectMgr->SetHighestGuids();
///- Check the existence of the map files for all races' startup areas.
if (!TerrainMgr::ExistMapAndVMap(0, -6240.32f, 331.033f)
|| !TerrainMgr::ExistMapAndVMap(0, -8949.95f, -132.493f)
|| !TerrainMgr::ExistMapAndVMap(1, -618.518f, -4251.67f)
|| !TerrainMgr::ExistMapAndVMap(0, 1676.35f, 1677.45f)
|| !TerrainMgr::ExistMapAndVMap(1, 10311.3f, 832.463f)
|| !TerrainMgr::ExistMapAndVMap(1, -2917.58f, -257.98f)
|| (m_int_configs[CONFIG_EXPANSION] && (
!TerrainMgr::ExistMapAndVMap(530, 10349.6f, -6357.29f) ||
!TerrainMgr::ExistMapAndVMap(530, -3961.64f, -13931.2f))))
{
TC_LOG_FATAL("server.loading", "Unable to load map and vmap data for starting zones - server shutting down!");
return false;
}
///- Initialize pool manager
sPoolMgr->Initialize();
///- Initialize game event manager
sGameEventMgr->Initialize();
///- Loading strings. Getting no records means core load has to be canceled because no error message can be output.
TC_LOG_INFO("server.loading", "Loading Trinity strings...");
if (!sObjectMgr->LoadTrinityStrings())
return false; // Error message displayed in function already
///- Update the realm entry in the database with the realm type from the config file
//No SQL injection as values are treated as integers
// not send custom type REALM_FFA_PVP to realm list
uint32 server_type = IsFFAPvPRealm() ? uint32(REALM_TYPE_PVP) : getIntConfig(CONFIG_GAME_TYPE);
uint32 realm_zone = getIntConfig(CONFIG_REALM_ZONE);
LoginDatabase.PExecute("UPDATE realmlist SET icon = {}, timezone = {} WHERE id = '{}'", server_type, realm_zone, sRealmList->GetCurrentRealmId().Realm); // One-time query
TC_LOG_INFO("server.loading", "Loading GameObject models...");
if (!LoadGameObjectModelList(m_dataPath))
{
TC_LOG_FATAL("server.loading", "Unable to load gameobject models (part of vmaps), objects using WMO models will crash the client - server shutting down!");
return false;
}
TC_LOG_INFO("server.loading", "Initialize data stores...");
///- Load DB2s
m_availableDbcLocaleMask = sDB2Manager.LoadStores(m_dataPath, m_defaultDbcLocale);
if (!(m_availableDbcLocaleMask & (1 << m_defaultDbcLocale)))
{
TC_LOG_FATAL("server.loading", "Unable to load db2 files for {} locale specified in DBC.Locale config!", localeNames[m_defaultDbcLocale]);
return false;
}
TC_LOG_INFO("server.loading", "Loading hotfix blobs...");
sDB2Manager.LoadHotfixBlob(m_availableDbcLocaleMask);
TC_LOG_INFO("server.loading", "Loading hotfix info...");
sDB2Manager.LoadHotfixData(m_availableDbcLocaleMask);
TC_LOG_INFO("server.loading", "Loading hotfix optional data...");
sDB2Manager.LoadHotfixOptionalData(m_availableDbcLocaleMask);
TC_LOG_INFO("server.loading", "Indexing loaded data stores...");
sDB2Manager.IndexLoadedStores();
///- Load M2 fly by cameras
LoadM2Cameras(m_dataPath);
///- Load GameTables
LoadGameTables(m_dataPath);
//Load weighted graph on taxi nodes path
TaxiPathGraph::Initialize();
// Load IP Location Database
sIPLocation->Load();
// always use declined names in the russian client
if (Cfg_CategoriesEntry const* category = sCfgCategoriesStore.LookupEntry(m_int_configs[CONFIG_REALM_ZONE]))
if (category->GetCreateCharsetMask().HasFlag(CfgCategoriesCharsets::Russian))
m_bool_configs[CONFIG_DECLINED_NAMES_USED] = true;
std::unordered_map> mapData;
for (MapEntry const* mapEntry : sMapStore)
{
mapData.emplace(std::piecewise_construct, std::forward_as_tuple(mapEntry->ID), std::forward_as_tuple());
if (mapEntry->ParentMapID != -1)
{
ASSERT(mapEntry->CosmeticParentMapID == -1 || mapEntry->ParentMapID == mapEntry->CosmeticParentMapID,
"Inconsistent parent map data for map %u (ParentMapID = %hd, CosmeticParentMapID = %hd)",
mapEntry->ID, mapEntry->ParentMapID, mapEntry->CosmeticParentMapID);
mapData[mapEntry->ParentMapID].push_back(mapEntry->ID);
}
else if (mapEntry->CosmeticParentMapID != -1)
mapData[mapEntry->CosmeticParentMapID].push_back(mapEntry->ID);
}
sTerrainMgr.InitializeParentMapData(mapData);
vmmgr2->InitializeThreadUnsafe(mapData);
MMAP::MMapManager* mmmgr = MMAP::MMapFactory::createOrGetMMapManager();
mmmgr->InitializeThreadUnsafe(mapData);
///- Initialize static helper structures
AIRegistry::Initialize();
TC_LOG_INFO("server.loading", "Initializing PlayerDump tables...");
PlayerDump::InitializeTables();
TC_LOG_INFO("server.loading", "Loading SpellInfo store...");
sSpellMgr->LoadSpellInfoStore();
TC_LOG_INFO("server.loading", "Loading serverside spells...");
sSpellMgr->LoadSpellInfoServerside();
TC_LOG_INFO("server.loading", "Loading SpellInfo corrections...");
sSpellMgr->LoadSpellInfoCorrections();
TC_LOG_INFO("server.loading", "Loading SkillLineAbilityMultiMap Data...");
sSpellMgr->LoadSkillLineAbilityMap();
TC_LOG_INFO("server.loading", "Loading SpellInfo custom attributes...");
sSpellMgr->LoadSpellInfoCustomAttributes();
TC_LOG_INFO("server.loading", "Loading SpellInfo diminishing infos...");
sSpellMgr->LoadSpellInfoDiminishing();
TC_LOG_INFO("server.loading", "Loading SpellInfo immunity infos...");
sSpellMgr->LoadSpellInfoImmunities();
TC_LOG_INFO("server.loading", "Loading SpellInfo target caps...");
sSpellMgr->LoadSpellInfoTargetCaps();
TC_LOG_INFO("server.loading", "Loading PetFamilySpellsStore Data...");
sSpellMgr->LoadPetFamilySpellsStore();
TC_LOG_INFO("server.loading", "Loading Spell Totem models...");
sSpellMgr->LoadSpellTotemModel();
TC_LOG_INFO("server.loading", "Loading Traits...");
TraitMgr::Load();
TC_LOG_INFO("server.loading", "Loading languages..."); // must be after LoadSpellInfoStore and LoadSkillLineAbilityMap
sLanguageMgr->LoadLanguages();
TC_LOG_INFO("server.loading", "Loading languages words...");
sLanguageMgr->LoadLanguagesWords();
TC_LOG_INFO("server.loading", "Loading Instance Template...");
sObjectMgr->LoadInstanceTemplate();
// Must be called before `respawn` data
TC_LOG_INFO("server.loading", "Loading instances...");
sMapMgr->InitInstanceIds();
sInstanceLockMgr.Load();
TC_LOG_INFO("server.loading", "Loading Localization strings...");
uint32 oldMSTime = getMSTime();
if (m_bool_configs[CONFIG_LOAD_LOCALES])
{
sObjectMgr->LoadCreatureLocales();
sObjectMgr->LoadGameObjectLocales();
sObjectMgr->LoadQuestTemplateLocale();
sObjectMgr->LoadQuestOfferRewardLocale();
sObjectMgr->LoadQuestRequestItemsLocale();
sObjectMgr->LoadQuestObjectivesLocale();
sObjectMgr->LoadPageTextLocales();
sObjectMgr->LoadGossipMenuItemsLocales();
sObjectMgr->LoadPointOfInterestLocales();
}
sObjectMgr->SetDBCLocaleIndex(GetDefaultDbcLocale()); // Get once for all the locale index of DBC language (console/broadcasts)
TC_LOG_INFO("server.loading", ">> Localization strings loaded in {} ms", GetMSTimeDiffToNow(oldMSTime));
TC_LOG_INFO("server.loading", "Loading Account Roles and Permissions...");
sAccountMgr->LoadRBAC();
TC_LOG_INFO("server.loading", "Loading Page Texts...");
sObjectMgr->LoadPageTexts();
TC_LOG_INFO("server.loading", "Loading Game Object Templates..."); // must be after LoadPageTexts
sObjectMgr->LoadDestructibleHitpoints();
sObjectMgr->LoadGameObjectTemplate();
TC_LOG_INFO("server.loading", "Loading Game Object template addons...");
sObjectMgr->LoadGameObjectTemplateAddons();
TC_LOG_INFO("server.loading", "Loading Transport templates...");
sTransportMgr->LoadTransportTemplates();
TC_LOG_INFO("server.loading", "Loading Transport animations and rotations...");
sTransportMgr->LoadTransportAnimationAndRotation();
TC_LOG_INFO("server.loading", "Loading Transport spawns...");
sTransportMgr->LoadTransportSpawns();
TC_LOG_INFO("server.loading", "Loading Spell Rank Data...");
sSpellMgr->LoadSpellRanks();
TC_LOG_INFO("server.loading", "Loading Spell Required Data...");
sSpellMgr->LoadSpellRequired();
TC_LOG_INFO("server.loading", "Loading Spell Group types...");
sSpellMgr->LoadSpellGroups();
TC_LOG_INFO("server.loading", "Loading Spell Learn Skills...");
sSpellMgr->LoadSpellLearnSkills(); // must be after LoadSpellRanks
TC_LOG_INFO("server.loading", "Loading SpellInfo SpellSpecific and AuraState...");
sSpellMgr->LoadSpellInfoSpellSpecificAndAuraState(); // must be after LoadSpellRanks
TC_LOG_INFO("server.loading", "Loading Spell Learn Spells...");
sSpellMgr->LoadSpellLearnSpells();
TC_LOG_INFO("server.loading", "Loading Spell Proc conditions and data...");
sSpellMgr->LoadSpellProcs();
TC_LOG_INFO("server.loading", "Loading Aggro Spells Definitions...");
sSpellMgr->LoadSpellThreats();
TC_LOG_INFO("server.loading", "Loading Spell Group Stack Rules...");
sSpellMgr->LoadSpellGroupStackRules();
TC_LOG_INFO("server.loading", "Loading NPC Texts...");
sObjectMgr->LoadNPCText();
TC_LOG_INFO("server.loading", "Loading Enchant Spells Proc datas...");
sSpellMgr->LoadSpellEnchantProcData();
TC_LOG_INFO("server.loading", "Loading item bonus data...");
ItemBonusMgr::Load();
TC_LOG_INFO("server.loading", "Loading Random item bonus list definitions...");
LoadItemRandomBonusListTemplates();
TC_LOG_INFO("server.loading", "Loading Disables"); // must be before loading quests and items
DisableMgr::LoadDisables();
TC_LOG_INFO("server.loading", "Loading Items..."); // must be after LoadRandomEnchantmentsTable and LoadPageTexts
sObjectMgr->LoadItemTemplates();
TC_LOG_INFO("server.loading", "Loading Item set names..."); // must be after LoadItemPrototypes
sObjectMgr->LoadItemTemplateAddon();
TC_LOG_INFO("misc", "Loading Item Scripts..."); // must be after LoadItemPrototypes
sObjectMgr->LoadItemScriptNames();
TC_LOG_INFO("server.loading", "Loading Creature Model Based Info Data...");
sObjectMgr->LoadCreatureModelInfo();
TC_LOG_INFO("server.loading", "Loading Creature templates...");
sObjectMgr->LoadCreatureTemplates();
TC_LOG_INFO("server.loading", "Loading Equipment templates..."); // must be after LoadCreatureTemplates
sObjectMgr->LoadEquipmentTemplates();
TC_LOG_INFO("server.loading", "Loading Creature template addons...");
sObjectMgr->LoadCreatureTemplateAddons();
TC_LOG_INFO("server.loading", "Loading Creature template difficulty...");
sObjectMgr->LoadCreatureTemplateDifficulty();
TC_LOG_INFO("server.loading", "Loading Creature template sparring...");
sObjectMgr->LoadCreatureTemplateSparring();
TC_LOG_INFO("server.loading", "Loading Reputation Reward Rates...");
sObjectMgr->LoadReputationRewardRate();
TC_LOG_INFO("server.loading", "Loading Creature Reputation OnKill Data...");
sObjectMgr->LoadReputationOnKill();
TC_LOG_INFO("server.loading", "Loading Reputation Spillover Data...");
sObjectMgr->LoadReputationSpilloverTemplate();
TC_LOG_INFO("server.loading", "Loading Points Of Interest Data...");
sObjectMgr->LoadPointsOfInterest();
TC_LOG_INFO("server.loading", "Loading Creature Base Stats...");
sObjectMgr->LoadCreatureClassLevelStats();
TC_LOG_INFO("server.loading", "Loading Spawn Group Templates...");
sObjectMgr->LoadSpawnGroupTemplates();
TC_LOG_INFO("server.loading", "Loading Creature Data...");
sObjectMgr->LoadCreatures();
TC_LOG_INFO("server.loading", "Loading Temporary Summon Data...");
sObjectMgr->LoadTempSummons(); // must be after LoadCreatureTemplates() and LoadGameObjectTemplates()
TC_LOG_INFO("server.loading", "Loading pet levelup spells...");
sSpellMgr->LoadPetLevelupSpellMap();
TC_LOG_INFO("server.loading", "Loading pet default spells additional to levelup spells...");
sSpellMgr->LoadPetDefaultSpells();
TC_LOG_INFO("server.loading", "Loading Creature Addon Data...");
sObjectMgr->LoadCreatureAddons(); // must be after LoadCreatureTemplates() and LoadCreatures()
TC_LOG_INFO("server.loading", "Loading Creature Movement Overrides...");
sObjectMgr->LoadCreatureMovementOverrides(); // must be after LoadCreatures()
TC_LOG_INFO("server.loading", "Loading Gameobject Data...");
sObjectMgr->LoadGameObjects();
TC_LOG_INFO("server.loading", "Loading Spawn Group Data...");
sObjectMgr->LoadSpawnGroups();
TC_LOG_INFO("server.loading", "Loading instance spawn groups...");
sObjectMgr->LoadInstanceSpawnGroups();
TC_LOG_INFO("server.loading", "Loading GameObject Addon Data...");
sObjectMgr->LoadGameObjectAddons(); // must be after LoadGameObjects()
TC_LOG_INFO("server.loading", "Loading GameObject faction and flags overrides...");
sObjectMgr->LoadGameObjectOverrides(); // must be after LoadGameObjects()
TC_LOG_INFO("server.loading", "Loading GameObject Quest Items...");
sObjectMgr->LoadGameObjectQuestItems();
TC_LOG_INFO("server.loading", "Loading Creature Quest Items...");
sObjectMgr->LoadCreatureQuestItems();
TC_LOG_INFO("server.loading", "Loading Creature Quest Currencies...");
sObjectMgr->LoadCreatureQuestCurrencies();
TC_LOG_INFO("server.loading", "Loading Creature Linked Respawn...");
sObjectMgr->LoadLinkedRespawn(); // must be after LoadCreatures(), LoadGameObjects()
TC_LOG_INFO("server.loading", "Loading Weather Data...");
WeatherMgr::LoadWeatherData();
TC_LOG_INFO("server.loading", "Loading Quests...");
sObjectMgr->LoadQuests(); // must be loaded after DBCs, creature_template, items, gameobject tables
TC_LOG_INFO("server.loading", "Checking Quest Disables");
DisableMgr::CheckQuestDisables(); // must be after loading quests
TC_LOG_INFO("server.loading", "Loading Quest POI");
sObjectMgr->LoadQuestPOI();
TC_LOG_INFO("server.loading", "Loading Quests Starters and Enders...");
sObjectMgr->LoadQuestStartersAndEnders(); // must be after quest load
TC_LOG_INFO("server.loading", "Loading Quest Greetings...");
sObjectMgr->LoadQuestGreetings();
if (m_bool_configs[CONFIG_LOAD_LOCALES])
sObjectMgr->LoadQuestGreetingLocales();
TC_LOG_INFO("server.loading", "Loading Objects Pooling Data...");
sPoolMgr->LoadFromDB();
TC_LOG_INFO("server.loading", "Loading Quest Pooling Data...");
sQuestPoolMgr->LoadFromDB(); // must be after quest templates
TC_LOG_INFO("server.loading", "Loading World State templates...");
sWorldStateMgr->LoadFromDB(); // must be loaded before battleground, outdoor PvP, game events and conditions
TC_LOG_INFO("server.loading", "Loading Game Event Data..."); // must be after loading pools fully
sGameEventMgr->LoadFromDB();
TC_LOG_INFO("server.loading", "Loading creature summoned data...");
sObjectMgr->LoadCreatureSummonedData(); // must be after LoadCreatureTemplates() and LoadQuests()
TC_LOG_INFO("server.loading", "Loading UNIT_NPC_FLAG_SPELLCLICK Data..."); // must be after LoadQuests
sObjectMgr->LoadNPCSpellClickSpells();
TC_LOG_INFO("server.loading", "Loading Vehicle Templates...");
sObjectMgr->LoadVehicleTemplate(); // must be after LoadCreatureTemplates()
TC_LOG_INFO("server.loading", "Loading Vehicle Template Accessories...");
sObjectMgr->LoadVehicleTemplateAccessories(); // must be after LoadCreatureTemplates() and LoadNPCSpellClickSpells()
TC_LOG_INFO("server.loading", "Loading Vehicle Accessories...");
sObjectMgr->LoadVehicleAccessories(); // must be after LoadCreatureTemplates() and LoadNPCSpellClickSpells()
TC_LOG_INFO("server.loading", "Loading Vehicle Seat Addon Data...");
sObjectMgr->LoadVehicleSeatAddon(); // must be after loading DBC
TC_LOG_INFO("server.loading", "Loading SpellArea Data..."); // must be after quest load
sSpellMgr->LoadSpellAreas();
TC_LOG_INFO("server.loading", "Loading World locations...");
sObjectMgr->LoadWorldSafeLocs(); // must be before LoadAreaTriggerTeleports and LoadGraveyardZones
TC_LOG_INFO("server.loading", "Loading Area Trigger Teleports definitions...");
sObjectMgr->LoadAreaTriggerTeleports();
TC_LOG_INFO("server.loading", "Loading Area Trigger Polygon data...");
sObjectMgr->LoadAreaTriggerPolygons();
TC_LOG_INFO("server.loading", "Loading Access Requirements...");
sObjectMgr->LoadAccessRequirements(); // must be after item template load
TC_LOG_INFO("server.loading", "Loading Quest Area Triggers...");
sObjectMgr->LoadQuestAreaTriggers(); // must be after LoadQuests
TC_LOG_INFO("server.loading", "Loading Tavern Area Triggers...");
sObjectMgr->LoadTavernAreaTriggers();
TC_LOG_INFO("server.loading", "Loading AreaTrigger script names...");
sObjectMgr->LoadAreaTriggerScripts();
TC_LOG_INFO("server.loading", "Loading LFG entrance positions..."); // Must be after areatriggers
sLFGMgr->LoadLFGDungeons();
TC_LOG_INFO("server.loading", "Loading LFG rewards...");
sLFGMgr->LoadRewards();
TC_LOG_INFO("server.loading", "Loading Graveyard-zone links...");
sObjectMgr->LoadGraveyardZones();
TC_LOG_INFO("server.loading", "Loading spell pet auras...");
sSpellMgr->LoadSpellPetAuras();
TC_LOG_INFO("server.loading", "Loading Spell target coordinates...");
sSpellMgr->LoadSpellTargetPositions();
TC_LOG_INFO("server.loading", "Loading linked spells...");
sSpellMgr->LoadSpellLinked();
TC_LOG_INFO("server.loading", "Loading Scenes Templates..."); // must be before LoadPlayerInfo
sObjectMgr->LoadSceneTemplates();
TC_LOG_INFO("server.loading", "Loading Player Create Data...");
sObjectMgr->LoadPlayerInfo();
TC_LOG_INFO("server.loading", "Loading Exploration BaseXP Data...");
sObjectMgr->LoadExplorationBaseXP();
TC_LOG_INFO("server.loading", "Loading Pet Name Parts...");
sObjectMgr->LoadPetNames();
TC_LOG_INFO("server.loading", "Loading AreaTrigger Templates...");
sAreaTriggerDataStore->LoadAreaTriggerTemplates();
TC_LOG_INFO("server.loading", "Loading AreaTrigger Spawns...");
sAreaTriggerDataStore->LoadAreaTriggerSpawns();
TC_LOG_INFO("server.loading", "Loading Conversation Templates...");
sConversationDataStore->LoadConversationTemplates();
TC_LOG_INFO("server.loading", "Loading Player Choices...");
sObjectMgr->LoadPlayerChoices();
TC_LOG_INFO("server.loading", "Loading Spawn Tracking Templates...");
sObjectMgr->LoadSpawnTrackingTemplates();
TC_LOG_INFO("server.loading", "Loading Spawn Tracking Quest Objectives...");
sObjectMgr->LoadSpawnTrackingQuestObjectives();
TC_LOG_INFO("server.loading", "Loading Spawn Tracking Spawns...");
sObjectMgr->LoadSpawnTrackings();
TC_LOG_INFO("server.loading", "Loading Spawn Tracking Spawn States...");
sObjectMgr->LoadSpawnTrackingStates();
if (m_bool_configs[CONFIG_LOAD_LOCALES])
{
TC_LOG_INFO("server.loading", "Loading Player Choices Locales...");
sObjectMgr->LoadPlayerChoicesLocale();
}
TC_LOG_INFO("server.loading", "Loading UIMap questlines...");
sObjectMgr->LoadUiMapQuestLines();
TC_LOG_INFO("server.loading", "Loading UIMap quests...");
sObjectMgr->LoadUiMapQuests();
TC_LOG_INFO("server.loading", "Loading Jump Charge Params...");
sObjectMgr->LoadJumpChargeParams();
CharacterDatabaseCleaner::CleanDatabase();
TC_LOG_INFO("server.loading", "Loading the max pet number...");
sObjectMgr->LoadPetNumber();
TC_LOG_INFO("server.loading", "Loading pet level stats...");
sObjectMgr->LoadPetLevelInfo();
TC_LOG_INFO("server.loading", "Loading Player level dependent mail rewards...");
sObjectMgr->LoadMailLevelRewards();
// Loot tables
LoadLootTables();
TC_LOG_INFO("server.loading", "Loading Skill Discovery Table...");
LoadSkillDiscoveryTable();
TC_LOG_INFO("server.loading", "Loading Skill Extra Item Table...");
LoadSkillExtraItemTable();
TC_LOG_INFO("server.loading", "Loading Skill Perfection Data Table...");
LoadSkillPerfectItemTable();
TC_LOG_INFO("server.loading", "Loading Skill Fishing base level requirements...");
sObjectMgr->LoadFishingBaseSkillLevel();
TC_LOG_INFO("server.loading", "Loading skill tier info...");
sObjectMgr->LoadSkillTiers();
TC_LOG_INFO("server.loading", "Loading Criteria Modifier trees...");
sCriteriaMgr->LoadCriteriaModifiersTree();
TC_LOG_INFO("server.loading", "Loading Criteria Lists...");
sCriteriaMgr->LoadCriteriaList();
TC_LOG_INFO("server.loading", "Loading Criteria Data...");
sCriteriaMgr->LoadCriteriaData();
TC_LOG_INFO("server.loading", "Loading Achievements...");
sAchievementMgr->LoadAchievementReferenceList();
TC_LOG_INFO("server.loading", "Loading Achievements Scripts...");
sAchievementMgr->LoadAchievementScripts();
TC_LOG_INFO("server.loading", "Loading Achievement Rewards...");
sAchievementMgr->LoadRewards();
if (m_bool_configs[CONFIG_LOAD_LOCALES])
{
TC_LOG_INFO("server.loading", "Loading Achievement Reward Locales...");
sAchievementMgr->LoadRewardLocales();
}
TC_LOG_INFO("server.loading", "Loading Completed Achievements...");
sAchievementMgr->LoadCompletedAchievements();
// Load before guilds and arena teams
TC_LOG_INFO("server.loading", "Loading character cache store...");
sCharacterCache->LoadCharacterCacheStorage();
///- Load dynamic data tables from the database
TC_LOG_INFO("server.loading", "Loading Auctions...");
sAuctionMgr->LoadAuctions();
if (m_bool_configs[CONFIG_BLACKMARKET_ENABLED])
{
TC_LOG_INFO("server.loading", "Loading Black Market Templates...");
sBlackMarketMgr->LoadTemplates();
TC_LOG_INFO("server.loading", "Loading Black Market Auctions...");
sBlackMarketMgr->LoadAuctions();
}
TC_LOG_INFO("server.loading", "Loading Guild rewards...");
sGuildMgr->LoadGuildRewards();
TC_LOG_INFO("server.loading", "Loading Guilds...");
sGuildMgr->LoadGuilds();
TC_LOG_INFO("server.loading", "Loading ArenaTeams...");
sArenaTeamMgr->LoadArenaTeams();
TC_LOG_INFO("server.loading", "Loading Groups...");
sGroupMgr->LoadGroups();
TC_LOG_INFO("server.loading", "Loading ReservedNames...");
sObjectMgr->LoadReservedPlayersNames();
TC_LOG_INFO("server.loading", "Loading GameObjects for quests...");
sObjectMgr->LoadGameObjectForQuests();
TC_LOG_INFO("server.loading", "Loading BattleMasters...");
sBattlegroundMgr->LoadBattleMastersEntry(); // must be after load CreatureTemplate
TC_LOG_INFO("server.loading", "Loading GameTeleports...");
sObjectMgr->LoadGameTele();
TC_LOG_INFO("server.loading", "Loading Trainers...");
sObjectMgr->LoadTrainers(); // must be after load CreatureTemplate
TC_LOG_INFO("server.loading", "Loading Gossip menu...");
sObjectMgr->LoadGossipMenu();
TC_LOG_INFO("server.loading", "Loading Gossip menu options...");
sObjectMgr->LoadGossipMenuItems();
TC_LOG_INFO("server.loading", "Loading Gossip menu addon...");
sObjectMgr->LoadGossipMenuAddon();
TC_LOG_INFO("server.loading", "Loading Creature Template Gossip...");
sObjectMgr->LoadCreatureTemplateGossip();
TC_LOG_INFO("server.loading", "Loading Creature trainers...");
sObjectMgr->LoadCreatureTrainers(); // must be after LoadGossipMenuItems
TC_LOG_INFO("server.loading", "Loading Vendors...");
sObjectMgr->LoadVendors(); // must be after load CreatureTemplate and ItemTemplate
TC_LOG_INFO("server.loading", "Loading Waypoint paths...");
sWaypointMgr->LoadPaths();
TC_LOG_INFO("server.loading", "Loading Creature Formations...");
sFormationMgr->LoadCreatureFormations();
TC_LOG_INFO("server.loading", "Loading Persistend World Variables...");
LoadPersistentWorldVariables();
sWorldStateMgr->SetValue(WS_CURRENT_PVP_SEASON_ID, getBoolConfig(CONFIG_ARENA_SEASON_IN_PROGRESS) ? getIntConfig(CONFIG_ARENA_SEASON_ID) : 0, false, nullptr);
sWorldStateMgr->SetValue(WS_PREVIOUS_PVP_SEASON_ID, getIntConfig(CONFIG_ARENA_SEASON_ID) - getBoolConfig(CONFIG_ARENA_SEASON_IN_PROGRESS), false, nullptr);
sObjectMgr->LoadPhases();
TC_LOG_INFO("server.loading", "Loading Conditions...");
sConditionMgr->LoadConditions();
TC_LOG_INFO("server.loading", "Loading faction change achievement pairs...");
sObjectMgr->LoadFactionChangeAchievements();
TC_LOG_INFO("server.loading", "Loading faction change spell pairs...");
sObjectMgr->LoadFactionChangeSpells();
TC_LOG_INFO("server.loading", "Loading faction change quest pairs...");
sObjectMgr->LoadFactionChangeQuests();
TC_LOG_INFO("server.loading", "Loading faction change item pairs...");
sObjectMgr->LoadFactionChangeItems();
TC_LOG_INFO("server.loading", "Loading faction change reputation pairs...");
sObjectMgr->LoadFactionChangeReputations();
TC_LOG_INFO("server.loading", "Loading faction change title pairs...");
sObjectMgr->LoadFactionChangeTitles();
TC_LOG_INFO("server.loading", "Loading mount definitions...");
CollectionMgr::LoadMountDefinitions();
TC_LOG_INFO("server.loading", "Loading warband scene definitions...");
CollectionMgr::LoadWarbandSceneDefinitions();
TC_LOG_INFO("server.loading", "Loading GM bugs...");
sSupportMgr->LoadBugTickets();
TC_LOG_INFO("server.loading", "Loading GM complaints...");
sSupportMgr->LoadComplaintTickets();
TC_LOG_INFO("server.loading", "Loading GM suggestions...");
sSupportMgr->LoadSuggestionTickets();
/*TC_LOG_INFO("server.loading", "Loading GM surveys...");
sSupportMgr->LoadSurveys();*/
TC_LOG_INFO("server.loading", "Loading garrison info...");
sGarrisonMgr.Initialize();
///- Handle outdated emails (delete/return)
TC_LOG_INFO("server.loading", "Returning old mails...");
sObjectMgr->ReturnOrDeleteOldMails(false);
TC_LOG_INFO("server.loading", "Loading Autobroadcasts...");
LoadAutobroadcasts();
///- Load and initialize scripts
sObjectMgr->LoadSpellScripts(); // must be after load Creature/Gameobject(Template/Data)
sObjectMgr->LoadEventScripts(); // must be after load Creature/Gameobject(Template/Data)
TC_LOG_INFO("server.loading", "Loading spell script names...");
sObjectMgr->LoadSpellScriptNames();
TC_LOG_INFO("server.loading", "Loading Creature Texts...");
sCreatureTextMgr->LoadCreatureTexts();
if (m_bool_configs[CONFIG_LOAD_LOCALES])
{
TC_LOG_INFO("server.loading", "Loading Creature Text Locales...");
sCreatureTextMgr->LoadCreatureTextLocales();
}
TC_LOG_INFO("server.loading", "Loading creature StaticFlags overrides...");
sObjectMgr->LoadCreatureStaticFlagsOverride(); // must be after LoadCreatures
TC_LOG_INFO("server.loading", "Initializing Scripts...");
sScriptMgr->Initialize();
sScriptMgr->OnConfigLoad(false); // must be done after the ScriptMgr has been properly initialized
TC_LOG_INFO("server.loading", "Validating spell scripts...");
sObjectMgr->ValidateSpellScripts();
TC_LOG_INFO("server.loading", "Loading SmartAI scripts...");
sSmartScriptMgr->LoadSmartAIFromDB();
TC_LOG_INFO("server.loading", "Loading Calendar data...");
sCalendarMgr->LoadFromDB();
TC_LOG_INFO("server.loading", "Loading Petitions...");
sPetitionMgr->LoadPetitions();
TC_LOG_INFO("server.loading", "Loading Signatures...");
sPetitionMgr->LoadSignatures();
TC_LOG_INFO("server.loading", "Loading Item loot...");
sLootItemStorage->LoadStorageFromDB();
TC_LOG_INFO("server.loading", "Initialize query data...");
sObjectMgr->InitializeQueriesData(QUERY_DATA_ALL);
TC_LOG_INFO("server.loading", "Initialize commands...");
Trinity::ChatCommands::LoadCommandMap();
///- Initialize game time and timers
TC_LOG_INFO("server.loading", "Initialize game time and timers");
GameTime::UpdateGameTimers();
LoginDatabase.PExecute("INSERT INTO uptime (realmid, starttime, uptime, revision) VALUES({}, {}, 0, '{}')",
sRealmList->GetCurrentRealmId().Realm, uint32(GameTime::GetStartTime()), GitRevision::GetFullVersion()); // One-time query
m_timers[WUPDATE_AUCTIONS].SetInterval(MINUTE*IN_MILLISECONDS);
m_timers[WUPDATE_AUCTIONS_PENDING].SetInterval(250);
m_timers[WUPDATE_UPTIME].SetInterval(m_int_configs[CONFIG_UPTIME_UPDATE]*MINUTE*IN_MILLISECONDS);
//Update "uptime" table based on configuration entry in minutes.
m_timers[WUPDATE_CORPSES].SetInterval(20 * MINUTE * IN_MILLISECONDS);
//erase corpses every 20 minutes
m_timers[WUPDATE_CLEANDB].SetInterval(m_int_configs[CONFIG_LOGDB_CLEARINTERVAL]*MINUTE*IN_MILLISECONDS);
// clean logs table every 14 days by default
m_timers[WUPDATE_AUTOBROADCAST].SetInterval(getIntConfig(CONFIG_AUTOBROADCAST_INTERVAL));
m_timers[WUPDATE_DELETECHARS].SetInterval(DAY*IN_MILLISECONDS); // check for chars to delete every day
// for AhBot
m_timers[WUPDATE_AHBOT].SetInterval(getIntConfig(CONFIG_AHBOT_UPDATE_INTERVAL) * IN_MILLISECONDS); // every 20 sec
m_timers[WUPDATE_PINGDB].SetInterval(getIntConfig(CONFIG_DB_PING_INTERVAL)*MINUTE*IN_MILLISECONDS); // Mysql ping time in minutes
m_timers[WUPDATE_GUILDSAVE].SetInterval(getIntConfig(CONFIG_GUILD_SAVE_INTERVAL) * MINUTE * IN_MILLISECONDS);
m_timers[WUPDATE_BLACKMARKET].SetInterval(10 * IN_MILLISECONDS);
m_timers[WUPDATE_CHECK_FILECHANGES].SetInterval(500);
m_timers[WUPDATE_WHO_LIST].SetInterval(5 * IN_MILLISECONDS); // update who list cache every 5 seconds
m_timers[WUPDATE_CHANNEL_SAVE].SetInterval(getIntConfig(CONFIG_PRESERVE_CUSTOM_CHANNEL_INTERVAL) * MINUTE * IN_MILLISECONDS);
//to set mailtimer to return mails every day between 4 and 5 am
//mailtimer is increased when updating auctions
//one second is 1000 -(tested on win system)
/// @todo Get rid of magic numbers
tm localTm;
time_t gameTime = GameTime::GetGameTime();
localtime_r(&gameTime, &localTm);
uint8 CleanOldMailsTime = getIntConfig(CONFIG_CLEAN_OLD_MAIL_TIME);
mail_timer = ((((localTm.tm_hour + (24 - CleanOldMailsTime)) % 24)* HOUR * IN_MILLISECONDS) / m_timers[WUPDATE_AUCTIONS].GetInterval());
//1440
mail_timer_expires = ((DAY * IN_MILLISECONDS) / (m_timers[WUPDATE_AUCTIONS].GetInterval()));
TC_LOG_INFO("server.loading", "Mail timer set to: {}, mail return is called every {} minutes", uint64(mail_timer), uint64(mail_timer_expires));
///- Initialize MapManager
TC_LOG_INFO("server.loading", "Starting Map System");
sMapMgr->Initialize();
TC_LOG_INFO("server.loading", "Starting Game Event system...");
uint32 nextGameEvent = sGameEventMgr->StartSystem();
m_timers[WUPDATE_EVENTS].SetInterval(nextGameEvent); //depend on next event
// Delete all characters which have been deleted X days before
Player::DeleteOldCharacters();
TC_LOG_INFO("server.loading", "Initialize AuctionHouseBot...");
sAuctionBot->Initialize();
TC_LOG_INFO("server.loading", "Initializing chat channels...");
ChannelMgr::LoadFromDB();
TC_LOG_INFO("server.loading", "Initializing Opcodes...");
opcodeTable.Initialize();
WorldPackets::Auth::ConnectTo::InitializeEncryption();
WorldPackets::Auth::EnterEncryptedMode::InitializeEncryption();
TC_LOG_INFO("server.loading", "Starting Arena Season...");
sGameEventMgr->StartArenaSeason();
sSupportMgr->Initialize();
///- Initialize Battlegrounds
TC_LOG_INFO("server.loading", "Starting Battleground System");
sBattlegroundMgr->LoadBattlegroundTemplates();
sBattlegroundMgr->LoadBattlegroundScriptTemplate();
///- Initialize outdoor pvp
TC_LOG_INFO("server.loading", "Starting Outdoor PvP System");
sOutdoorPvPMgr->InitOutdoorPvP();
///- Initialize Battlefield
TC_LOG_INFO("server.loading", "Starting Battlefield System");
sBattlefieldMgr->InitBattlefield();
///- Initialize Warden
TC_LOG_INFO("server.loading", "Loading Warden Checks...");
sWardenCheckMgr->LoadWardenChecks();
TC_LOG_INFO("server.loading", "Loading Warden Action Overrides...");
sWardenCheckMgr->LoadWardenOverrides();
TC_LOG_INFO("server.loading", "Deleting expired bans...");
LoginDatabase.Execute("DELETE FROM ip_banned WHERE unbandate <= UNIX_TIMESTAMP() AND unbandate<>bandate"); // One-time query
TC_LOG_INFO("server.loading", "Initializing quest reset times...");
InitQuestResetTimes();
CheckScheduledResetTimes();
TC_LOG_INFO("server.loading", "Calculate random battleground reset time...");
InitRandomBGResetTime();
TC_LOG_INFO("server.loading", "Calculate deletion of old calendar events time...");
InitCalendarOldEventsDeletionTime();
TC_LOG_INFO("server.loading", "Calculate guild limitation(s) reset time...");
InitGuildResetTime();
TC_LOG_INFO("server.loading", "Calculate next currency reset time...");
InitCurrencyResetTime();
TC_LOG_INFO("server.loading", "Loading race and class expansion requirements...");
sObjectMgr->LoadRaceAndClassExpansionRequirements();
TC_LOG_INFO("server.loading", "Loading character templates...");
sCharacterTemplateDataStore->LoadCharacterTemplates();
TC_LOG_INFO("server.loading", "Loading battle pets info...");
BattlePets::BattlePetMgr::Initialize();
TC_LOG_INFO("server.loading", "Loading scenarios");
sScenarioMgr->LoadDB2Data();
sScenarioMgr->LoadDBData();
TC_LOG_INFO("server.loading", "Loading scenario poi data");
sScenarioMgr->LoadScenarioPOI();
TC_LOG_INFO("server.loading", "Loading phase names...");
sObjectMgr->LoadPhaseNames();
uint32 startupDuration = GetMSTimeDiffToNow(startupBegin);
TC_LOG_INFO("server.worldserver", "World initialized in {} minutes {} seconds", startupDuration / 60000, startupDuration % 60000 / 1000);
TC_METRIC_EVENT("events", "World initialized", Trinity::StringFormat("World initialized in {} minutes {} seconds", startupDuration / 60000, startupDuration % 60000 / 1000));
return true;
}
void World::SetForcedWarModeFactionBalanceState(TeamId team, int32 reward)
{
sWorldStateMgr->SetValueAndSaveInDb(WS_WAR_MODE_HORDE_BUFF_VALUE, 10 + (team == TEAM_ALLIANCE ? reward : 0), false, nullptr);
sWorldStateMgr->SetValueAndSaveInDb(WS_WAR_MODE_ALLIANCE_BUFF_VALUE, 10 + (team == TEAM_HORDE ? reward : 0), false, nullptr);
}
void World::DisableForcedWarModeFactionBalanceState()
{
UpdateWarModeRewardValues();
}
void World::LoadAutobroadcasts()
{
uint32 oldMSTime = getMSTime();
m_Autobroadcasts.clear();
LoginDatabasePreparedStatement* stmt = LoginDatabase.GetPreparedStatement(LOGIN_SEL_AUTOBROADCAST);
stmt->setInt32(0, sRealmList->GetCurrentRealmId().Realm);
PreparedQueryResult result = LoginDatabase.Query(stmt);
if (!result)
{
TC_LOG_INFO("server.loading", ">> Loaded 0 autobroadcasts definitions. DB table `autobroadcast` is empty for this realm!");
return;
}
do
{
Field* fields = result->Fetch();
uint8 id = fields[0].GetUInt8();
m_Autobroadcasts[id] = { .Message = fields[2].GetString(), .Weight = fields[1].GetUInt8() };
} while (result->NextRow());
TC_LOG_INFO("server.loading", ">> Loaded {} autobroadcast definitions in {} ms", m_Autobroadcasts.size(), GetMSTimeDiffToNow(oldMSTime));
}
/// Update the World !
void World::Update(uint32 diff)
{
TC_METRIC_TIMER("world_update_time_total");
///- Update the game time and check for shutdown time
_UpdateGameTime();
time_t currentGameTime = GameTime::GetGameTime();
sWorldUpdateTime.UpdateWithDiff(diff);
///- Update the different timers
for (int i = 0; i < WUPDATE_COUNT; ++i)
{
if (m_timers[i].GetCurrent() >= 0)
m_timers[i].Update(diff);
else
m_timers[i].SetCurrent(0);
}
///- Update Who List Storage
if (m_timers[WUPDATE_WHO_LIST].Passed())
{
TC_METRIC_TIMER("world_update_time", TC_METRIC_TAG("type", "Update who list"));
m_timers[WUPDATE_WHO_LIST].Reset();
sWhoListStorageMgr->Update();
}
if (IsStopped() || m_timers[WUPDATE_CHANNEL_SAVE].Passed())
{
m_timers[WUPDATE_CHANNEL_SAVE].Reset();
if (getBoolConfig(CONFIG_PRESERVE_CUSTOM_CHANNELS))
{
TC_METRIC_TIMER("world_update_time", TC_METRIC_TAG("type", "Save custom channels"));
ChannelMgr* mgr1 = ASSERT_NOTNULL(ChannelMgr::ForTeam(PANDARIA_NEUTRAL));
mgr1->SaveToDB();
ChannelMgr* mgr2 = ASSERT_NOTNULL(ChannelMgr::ForTeam(ALLIANCE));
if (mgr1 != mgr2)
mgr2->SaveToDB();
ChannelMgr* mgr3 = ASSERT_NOTNULL(ChannelMgr::ForTeam(HORDE));
if (mgr1 != mgr3)
mgr3->SaveToDB();
}
}
{
TC_METRIC_TIMER("world_update_time", TC_METRIC_TAG("type", "Check daily reset times"));
CheckScheduledResetTimes();
}
if (currentGameTime > m_NextRandomBGReset)
{
TC_METRIC_TIMER("world_update_time", TC_METRIC_TAG("type", "Reset random BG"));
ResetRandomBG();
}
if (currentGameTime > m_NextCalendarOldEventsDeletionTime)
{
TC_METRIC_TIMER("world_update_time", TC_METRIC_TAG("type", "Delete old calendar events"));
CalendarDeleteOldEvents();
}
if (currentGameTime > m_NextGuildReset)
{
TC_METRIC_TIMER("world_update_time", TC_METRIC_TAG("type", "Reset guild cap"));
ResetGuildCap();
}
if (currentGameTime > m_NextCurrencyReset)
{
TC_METRIC_TIMER("world_update_time", TC_METRIC_TAG("type", "Reset currency weekly cap"));
ResetCurrencyWeekCap();
}
/// - Handle auctions when the timer has passed
if (m_timers[WUPDATE_AUCTIONS].Passed())
{
TC_METRIC_TIMER("world_update_time", TC_METRIC_TAG("type", "Update expired auctions"));
m_timers[WUPDATE_AUCTIONS].Reset();
///- Update mails (return old mails with item, or delete them)
//(tested... works on win)
if (++mail_timer > mail_timer_expires)
{
mail_timer = 0;
sObjectMgr->ReturnOrDeleteOldMails(true);
}
///- Handle expired auctions
sAuctionMgr->Update();
}
if (m_timers[WUPDATE_AUCTIONS_PENDING].Passed())
{
TC_METRIC_TIMER("world_update_time", TC_METRIC_TAG("type", "Update pending auctions"));
m_timers[WUPDATE_AUCTIONS_PENDING].Reset();
sAuctionMgr->UpdatePendingAuctions();
}
if (m_timers[WUPDATE_BLACKMARKET].Passed())
{
TC_METRIC_TIMER("world_update_time", TC_METRIC_TAG("type", "Update pending black market auctions"));
m_timers[WUPDATE_BLACKMARKET].Reset();
///- Update blackmarket, refresh auctions if necessary
if ((blackmarket_timer * m_timers[WUPDATE_BLACKMARKET].GetInterval() >=
getIntConfig(CONFIG_BLACKMARKET_UPDATE_PERIOD) * HOUR * IN_MILLISECONDS)
|| !blackmarket_timer)
{
sBlackMarketMgr->RefreshAuctions();
blackmarket_timer = 1; // timer is 0 on startup
}
else
{
++blackmarket_timer;
sBlackMarketMgr->Update();
}
}
///
- Handle AHBot operations
if (m_timers[WUPDATE_AHBOT].Passed())
{
TC_METRIC_TIMER("world_update_time", TC_METRIC_TAG("type", "Update AHBot"));
sAuctionBot->Update();
m_timers[WUPDATE_AHBOT].Reset();
}
/// Synchronize all scripts with their ids before updating the sScriptReloadMgr
sScriptMgr->SyncScripts();
///
- Handle file changes
if (m_timers[WUPDATE_CHECK_FILECHANGES].Passed())
{
TC_METRIC_TIMER("world_update_time", TC_METRIC_TAG("type", "Update HotSwap"));
sScriptReloadMgr->Update();
m_timers[WUPDATE_CHECK_FILECHANGES].Reset();
}
{
///
- Handle session updates when the timer has passed
TC_METRIC_TIMER("world_update_time", TC_METRIC_TAG("type", "Update sessions"));
UpdateSessions(diff);
}
///
- Update uptime table
if (m_timers[WUPDATE_UPTIME].Passed())
{
TC_METRIC_TIMER("world_update_time", TC_METRIC_TAG("type", "Update uptime"));
uint32 tmpDiff = GameTime::GetUptime();
uint32 maxOnlinePlayers = GetMaxPlayerCount();
m_timers[WUPDATE_UPTIME].Reset();
LoginDatabasePreparedStatement* stmt = LoginDatabase.GetPreparedStatement(LOGIN_UPD_UPTIME_PLAYERS);
stmt->setUInt32(0, tmpDiff);
stmt->setUInt16(1, uint16(maxOnlinePlayers));
stmt->setUInt32(2, sRealmList->GetCurrentRealmId().Realm);
stmt->setUInt32(3, uint32(GameTime::GetStartTime()));
LoginDatabase.Execute(stmt);
}
///
- Clean logs table
if (getIntConfig(CONFIG_LOGDB_CLEARTIME) > 0) // if not enabled, ignore the timer
{
if (m_timers[WUPDATE_CLEANDB].Passed())
{
TC_METRIC_TIMER("world_update_time", TC_METRIC_TAG("type", "Clean logs table"));
m_timers[WUPDATE_CLEANDB].Reset();
LoginDatabasePreparedStatement* stmt = LoginDatabase.GetPreparedStatement(LOGIN_DEL_OLD_LOGS);
stmt->setUInt32(0, getIntConfig(CONFIG_LOGDB_CLEARTIME));
stmt->setUInt32(1, uint32(GameTime::GetGameTime()));
stmt->setUInt32(2, sRealmList->GetCurrentRealmId().Realm);
LoginDatabase.Execute(stmt);
}
}
///
- Handle all other objects
///- Update objects when the timer has passed (maps, transport, creatures, ...)
{
TC_METRIC_TIMER("world_update_time", TC_METRIC_TAG("type", "Update maps"));
sMapMgr->Update(diff);
}
{
TC_METRIC_TIMER("world_update_time", TC_METRIC_TAG("type", "Terrain data cleanup"));
sTerrainMgr.Update(diff);
}
if (getBoolConfig(CONFIG_AUTOBROADCAST))
{
if (m_timers[WUPDATE_AUTOBROADCAST].Passed())
{
TC_METRIC_TIMER("world_update_time", TC_METRIC_TAG("type", "Send autobroadcast"));
m_timers[WUPDATE_AUTOBROADCAST].Reset();
SendAutoBroadcast();
}
}
{
TC_METRIC_TIMER("world_update_time", TC_METRIC_TAG("type", "Update battlegrounds"));
sBattlegroundMgr->Update(diff);
}
{
TC_METRIC_TIMER("world_update_time", TC_METRIC_TAG("type", "Update outdoor pvp"));
sOutdoorPvPMgr->Update(diff);
}
{
TC_METRIC_TIMER("world_update_time", TC_METRIC_TAG("type", "Update battlefields"));
sBattlefieldMgr->Update(diff);
}
///- Delete all characters which have been deleted X days before
if (m_timers[WUPDATE_DELETECHARS].Passed())
{
TC_METRIC_TIMER("world_update_time", TC_METRIC_TAG("type", "Delete old characters"));
m_timers[WUPDATE_DELETECHARS].Reset();
Player::DeleteOldCharacters();
}
{
TC_METRIC_TIMER("world_update_time", TC_METRIC_TAG("type", "Update groups"));
sGroupMgr->Update(diff);
}
{
TC_METRIC_TIMER("world_update_time", TC_METRIC_TAG("type", "Update LFG"));
sLFGMgr->Update(diff);
}
{
TC_METRIC_TIMER("world_update_time", TC_METRIC_TAG("type", "Process query callbacks"));
// execute callbacks from sql queries that were queued recently
ProcessQueryCallbacks();
}
///- Erase corpses once every 20 minutes
if (m_timers[WUPDATE_CORPSES].Passed())
{
TC_METRIC_TIMER("world_update_time", TC_METRIC_TAG("type", "Remove old corpses"));
m_timers[WUPDATE_CORPSES].Reset();
sMapMgr->DoForAllMaps([](Map* map)
{
map->RemoveOldCorpses();
});
}
///- Process Game events when necessary
if (m_timers[WUPDATE_EVENTS].Passed())
{
TC_METRIC_TIMER("world_update_time", TC_METRIC_TAG("type", "Update game events"));
m_timers[WUPDATE_EVENTS].Reset(); // to give time for Update() to be processed
uint32 nextGameEvent = sGameEventMgr->Update();
m_timers[WUPDATE_EVENTS].SetInterval(nextGameEvent);
m_timers[WUPDATE_EVENTS].Reset();
}
///- Ping to keep MySQL connections alive
if (m_timers[WUPDATE_PINGDB].Passed())
{
TC_METRIC_TIMER("world_update_time", TC_METRIC_TAG("type", "Ping MySQL"));
m_timers[WUPDATE_PINGDB].Reset();
TC_LOG_DEBUG("misc", "Ping MySQL to keep connection alive");
CharacterDatabase.KeepAlive();
LoginDatabase.KeepAlive();
WorldDatabase.KeepAlive();
HotfixDatabase.KeepAlive();
}
if (m_timers[WUPDATE_GUILDSAVE].Passed())
{
TC_METRIC_TIMER("world_update_time", TC_METRIC_TAG("type", "Save guilds"));
m_timers[WUPDATE_GUILDSAVE].Reset();
sGuildMgr->SaveGuilds();
}
// Check for shutdown warning
if (_guidWarn && !_guidAlert)
{
_warnDiff += diff;
if (GameTime::GetGameTime() >= _warnShutdownTime)
DoGuidWarningRestart();
else if (_warnDiff > getIntConfig(CONFIG_RESPAWN_GUIDWARNING_FREQUENCY) * IN_MILLISECONDS)
SendGuidWarning();
}
{
TC_METRIC_TIMER("world_update_time", TC_METRIC_TAG("type", "Process cli commands"));
// And last, but not least handle the issued cli commands
ProcessCliCommands();
}
{
TC_METRIC_TIMER("world_update_time", TC_METRIC_TAG("type", "Update world scripts"));
sScriptMgr->OnWorldUpdate(diff);
}
{
TC_METRIC_TIMER("world_update_time", TC_METRIC_TAG("type", "Update metrics"));
// Stats logger update
sMetric->Update();
TC_METRIC_VALUE("update_time_diff", diff);
}
}
void World::ForceGameEventUpdate()
{
m_timers[WUPDATE_EVENTS].Reset(); // to give time for Update() to be processed
uint32 nextGameEvent = sGameEventMgr->Update();
m_timers[WUPDATE_EVENTS].SetInterval(nextGameEvent);
m_timers[WUPDATE_EVENTS].Reset();
}
/// Send a packet to all players (except self if mentioned)
void World::SendGlobalMessage(WorldPacket const* packet, WorldSession* self, Optional team)
{
SessionMap::const_iterator itr;
for (itr = m_sessions.begin(); itr != m_sessions.end(); ++itr)
{
if (itr->second &&
itr->second->GetPlayer() &&
itr->second->GetPlayer()->IsInWorld() &&
itr->second != self &&
(!team || itr->second->GetPlayer()->GetTeam() == team))
{
itr->second->SendPacket(packet);
}
}
}
/// Send a packet to all GMs (except self if mentioned)
void World::SendGlobalGMMessage(WorldPacket const* packet, WorldSession* self, Optional team)
{
for (SessionMap::const_iterator itr = m_sessions.begin(); itr != m_sessions.end(); ++itr)
{
// check if session and can receive global GM Messages and its not self
WorldSession* session = itr->second;
if (!session || session == self || !session->HasPermission(rbac::RBAC_PERM_RECEIVE_GLOBAL_GM_TEXTMESSAGE))
continue;
// Player should be in world
Player* player = session->GetPlayer();
if (!player || !player->IsInWorld())
continue;
// Send only to same team, if team is given
if (!team || player->GetTeam() == team)
session->SendPacket(packet);
}
}
namespace Trinity
{
class WorldWorldTextBuilder
{
class MultiplePacketSender
{
public:
void operator()(Player const* receiver) const
{
for (std::unique_ptr const& packet : Packets)
receiver->SendDirectMessage(packet->GetRawPacket());
}
std::vector> Packets;
};
public:
static size_t const BufferSize = 2048;
explicit WorldWorldTextBuilder(uint32 textId, va_list* args = nullptr) : i_textId(textId), i_args(args) { }
MultiplePacketSender* operator()(LocaleConstant locale)
{
char const* text = sObjectMgr->GetTrinityString(i_textId, locale);
char strBuffer[BufferSize];
if (i_args)
{
// we need copy va_list before use or original va_list will corrupted
va_list ap;
va_copy(ap, *i_args);
vsnprintf(strBuffer, BufferSize, text, ap);
va_end(ap);
}
else
{
std::strncpy(strBuffer, text, BufferSize);
strBuffer[BufferSize - 1] = '\0';
}
MultiplePacketSender* sender = new MultiplePacketSender();
do_helper(sender->Packets, strBuffer);
return sender;
}
private:
void do_helper(std::vector>& dataList, char* text)
{
while (char* line = ChatHandler::LineFromMessage(text))
{
WorldPackets::Chat::Chat* packet = new WorldPackets::Chat::Chat();
packet->Initialize(CHAT_MSG_SYSTEM, LANG_UNIVERSAL, nullptr, nullptr, line);
packet->Write();
dataList.emplace_back(packet);
}
}
uint32 i_textId;
va_list* i_args;
};
} // namespace Trinity
/// Send a System Message to all players (except self if mentioned)
void World::SendWorldText(uint32 string_id, ...)
{
va_list ap;
va_start(ap, string_id);
Trinity::WorldWorldTextBuilder wt_builder(string_id, &ap);
Trinity::LocalizedDo wt_do(wt_builder);
for (SessionMap::const_iterator itr = m_sessions.begin(); itr != m_sessions.end(); ++itr)
{
if (!itr->second || !itr->second->GetPlayer() || !itr->second->GetPlayer()->IsInWorld())
continue;
wt_do(itr->second->GetPlayer());
}
va_end(ap);
}
/// Send a System Message to all GMs (except self if mentioned)
void World::SendGMText(uint32 string_id, ...)
{
va_list ap;
va_start(ap, string_id);
Trinity::WorldWorldTextBuilder wt_builder(string_id, &ap);
Trinity::LocalizedDo wt_do(wt_builder);
for (SessionMap::const_iterator itr = m_sessions.begin(); itr != m_sessions.end(); ++itr)
{
// Session should have permissions to receive global gm messages
WorldSession* session = itr->second;
if (!session || !session->HasPermission(rbac::RBAC_PERM_RECEIVE_GLOBAL_GM_TEXTMESSAGE))
continue;
// Player should be in world
Player* player = session->GetPlayer();
if (!player || !player->IsInWorld())
continue;
wt_do(player);
}
va_end(ap);
}
/// DEPRECATED, only for debug purpose. Send a System Message to all players (except self if mentioned)
void World::SendGlobalText(char const* text, WorldSession* self)
{
// need copy to prevent corruption by strtok call in LineFromMessage original string
char* buf = strdup(text);
char* pos = buf;
while (char* line = ChatHandler::LineFromMessage(pos))
{
WorldPackets::Chat::Chat packet;
packet.Initialize(CHAT_MSG_SYSTEM, LANG_UNIVERSAL, nullptr, nullptr, line);
SendGlobalMessage(packet.Write(), self);
}
free(buf);
}
/// Send a packet to all players (or players selected team) in the zone (except self if mentioned)
bool World::SendZoneMessage(uint32 zone, WorldPacket const* packet, WorldSession* self, Optional team)
{
bool foundPlayerToSend = false;
SessionMap::const_iterator itr;
for (itr = m_sessions.begin(); itr != m_sessions.end(); ++itr)
{
if (itr->second &&
itr->second->GetPlayer() &&
itr->second->GetPlayer()->IsInWorld() &&
itr->second->GetPlayer()->GetZoneId() == zone &&
itr->second != self &&
(!team || itr->second->GetPlayer()->GetTeam() == team))
{
itr->second->SendPacket(packet);
foundPlayerToSend = true;
}
}
return foundPlayerToSend;
}
/// Send a System Message to all players in the zone (except self if mentioned)
void World::SendZoneText(uint32 zone, char const* text, WorldSession* self, Optional team)
{
WorldPackets::Chat::Chat packet;
packet.Initialize(CHAT_MSG_SYSTEM, LANG_UNIVERSAL, nullptr, nullptr, text);
SendZoneMessage(zone, packet.Write(), self, team);
}
/// Kick (and save) all players
void World::KickAll()
{
m_QueuedPlayer.clear(); // prevent send queue update packet and login queued sessions
// session not removed at kick and will removed in next update tick
for (SessionMap::const_iterator itr = m_sessions.begin(); itr != m_sessions.end(); ++itr)
itr->second->KickPlayer("World::KickAll");
}
/// Kick (and save) all players with security level less `sec`
void World::KickAllLess(AccountTypes sec)
{
// session not removed at kick and will removed in next update tick
for (SessionMap::const_iterator itr = m_sessions.begin(); itr != m_sessions.end(); ++itr)
if (itr->second->GetSecurity() < sec)
itr->second->KickPlayer("World::KickAllLess");
}
/// Ban an account or ban an IP address, duration will be parsed using TimeStringToSecs if it is positive, otherwise permban
BanReturn World::BanAccount(BanMode mode, std::string const& nameOrIP, std::string const& duration, std::string const& reason, std::string const& author)
{
uint32 duration_secs = TimeStringToSecs(duration);
return BanAccount(mode, nameOrIP, duration_secs, reason, author);
}
/// Ban an account or ban an IP address, duration is in seconds if positive, otherwise permban
BanReturn World::BanAccount(BanMode mode, std::string const& nameOrIP, uint32 duration_secs, std::string const& reason, std::string const& author)
{
PreparedQueryResult resultAccounts = PreparedQueryResult(nullptr); //used for kicking
// Prevent banning an already banned account
if (mode == BAN_ACCOUNT && AccountMgr::IsBannedAccount(nameOrIP))
return BAN_EXISTS;
///- Update the database with ban information
switch (mode)
{
case BAN_IP:
{
// No SQL injection with prepared statements
LoginDatabasePreparedStatement* stmt = LoginDatabase.GetPreparedStatement(LOGIN_SEL_ACCOUNT_BY_IP);
stmt->setString(0, nameOrIP);
resultAccounts = LoginDatabase.Query(stmt);
stmt = LoginDatabase.GetPreparedStatement(LOGIN_INS_IP_BANNED);
stmt->setString(0, nameOrIP);
stmt->setUInt32(1, duration_secs);
stmt->setString(2, author);
stmt->setString(3, reason);
LoginDatabase.Execute(stmt);
break;
}
case BAN_ACCOUNT:
{
// No SQL injection with prepared statements
LoginDatabasePreparedStatement* stmt = LoginDatabase.GetPreparedStatement(LOGIN_SEL_ACCOUNT_ID_BY_NAME);
stmt->setString(0, nameOrIP);
resultAccounts = LoginDatabase.Query(stmt);
break;
}
case BAN_CHARACTER:
{
// No SQL injection with prepared statements
CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_ACCOUNT_BY_NAME);
stmt->setString(0, nameOrIP);
resultAccounts = CharacterDatabase.Query(stmt);
break;
}
default:
return BAN_SYNTAX_ERROR;
}
if (!resultAccounts)
{
if (mode == BAN_IP)
return BAN_SUCCESS; // ip correctly banned but nobody affected (yet)
else
return BAN_NOTFOUND; // Nobody to ban
}
///- Disconnect all affected players (for IP it can be several)
LoginDatabaseTransaction trans = LoginDatabase.BeginTransaction();
do
{
Field* fieldsAccount = resultAccounts->Fetch();
uint32 account = fieldsAccount[0].GetUInt32();
if (mode != BAN_IP)
{
// make sure there is only one active ban
LoginDatabasePreparedStatement* stmt = LoginDatabase.GetPreparedStatement(LOGIN_UPD_ACCOUNT_NOT_BANNED);
stmt->setUInt32(0, account);
trans->Append(stmt);
// No SQL injection with prepared statements
stmt = LoginDatabase.GetPreparedStatement(LOGIN_INS_ACCOUNT_BANNED);
stmt->setUInt32(0, account);
stmt->setUInt32(1, duration_secs);
stmt->setString(2, author);
stmt->setString(3, reason);
trans->Append(stmt);
}
if (WorldSession* sess = FindSession(account))
if (std::string(sess->GetPlayerName()) != author)
sess->KickPlayer("World::BanAccount Banning account");
} while (resultAccounts->NextRow());
LoginDatabase.CommitTransaction(trans);
return BAN_SUCCESS;
}
/// Remove a ban from an account or IP address
bool World::RemoveBanAccount(BanMode mode, std::string const& nameOrIP)
{
LoginDatabasePreparedStatement* stmt = nullptr;
if (mode == BAN_IP)
{
stmt = LoginDatabase.GetPreparedStatement(LOGIN_DEL_IP_NOT_BANNED);
stmt->setString(0, nameOrIP);
LoginDatabase.Execute(stmt);
}
else
{
uint32 account = 0;
if (mode == BAN_ACCOUNT)
account = AccountMgr::GetId(nameOrIP);
else if (mode == BAN_CHARACTER)
account = sCharacterCache->GetCharacterAccountIdByName(nameOrIP);
if (!account)
return false;
//NO SQL injection as account is uint32
stmt = LoginDatabase.GetPreparedStatement(LOGIN_UPD_ACCOUNT_NOT_BANNED);
stmt->setUInt32(0, account);
LoginDatabase.Execute(stmt);
}
return true;
}
/// Ban an account or ban an IP address, duration will be parsed using TimeStringToSecs if it is positive, otherwise permban
BanReturn World::BanCharacter(std::string const& name, std::string const& duration, std::string const& reason, std::string const& author)
{
Player* banned = ObjectAccessor::FindConnectedPlayerByName(name);
ObjectGuid guid;
uint32 duration_secs = TimeStringToSecs(duration);
/// Pick a player to ban if not online
if (!banned)
{
guid = sCharacterCache->GetCharacterGuidByName(name);
if (guid.IsEmpty())
return BAN_NOTFOUND; // Nobody to ban
}
else
guid = banned->GetGUID();
//Use transaction in order to ensure the order of the queries
CharacterDatabaseTransaction trans = CharacterDatabase.BeginTransaction();
// make sure there is only one active ban
CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_UPD_CHARACTER_BAN);
stmt->setUInt64(0, guid.GetCounter());
trans->Append(stmt);
stmt = CharacterDatabase.GetPreparedStatement(CHAR_INS_CHARACTER_BAN);
stmt->setUInt64(0, guid.GetCounter());
stmt->setInt64(1, duration_secs);
stmt->setString(2, author);
stmt->setString(3, reason);
trans->Append(stmt);
CharacterDatabase.CommitTransaction(trans);
if (banned)
banned->GetSession()->KickPlayer("World::BanCharacter Banning character");
return BAN_SUCCESS;
}
/// Remove a ban from a character
bool World::RemoveBanCharacter(std::string const& name)
{
Player* banned = ObjectAccessor::FindConnectedPlayerByName(name);
ObjectGuid guid;
/// Pick a player to ban if not online
if (!banned)
{
guid = sCharacterCache->GetCharacterGuidByName(name);
if (guid.IsEmpty())
return false; // Nobody to ban
}
else
guid = banned->GetGUID();
CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_UPD_CHARACTER_BAN);
stmt->setUInt64(0, guid.GetCounter());
CharacterDatabase.Execute(stmt);
return true;
}
/// Update the game time
void World::_UpdateGameTime()
{
///- update the time
time_t lastGameTime = GameTime::GetGameTime();
GameTime::UpdateGameTimers();
uint32 elapsed = uint32(GameTime::GetGameTime() - lastGameTime);
///- if there is a shutdown timer
if (!IsStopped() && m_ShutdownTimer > 0 && elapsed > 0)
{
///- ... and it is overdue, stop the world (set m_stopEvent)
if (m_ShutdownTimer <= elapsed)
{
if (!(m_ShutdownMask & SHUTDOWN_MASK_IDLE) || GetActiveAndQueuedSessionCount() == 0)
m_stopEvent = true; // exist code already set
else
m_ShutdownTimer = 1; // minimum timer value to wait idle state
}
///- ... else decrease it and if necessary display a shutdown countdown to the users
else
{
m_ShutdownTimer -= elapsed;
ShutdownMsg();
}
}
}
/// Shutdown the server
void World::ShutdownServ(uint32 time, uint32 options, uint8 exitcode, const std::string& reason)
{
// ignore if server shutdown at next tick
if (IsStopped())
return;
m_ShutdownMask = options;
m_ExitCode = exitcode;
///- If the shutdown time is 0, evaluate shutdown on next tick (no message)
if (time == 0)
m_ShutdownTimer = 1;
///- Else set the shutdown timer and warn users
else
{
m_ShutdownTimer = time;
ShutdownMsg(true, nullptr, reason);
}
sScriptMgr->OnShutdownInitiate(ShutdownExitCode(exitcode), ShutdownMask(options));
}
/// Display a shutdown message to the user(s)
void World::ShutdownMsg(bool show, Player* player, const std::string& reason)
{
// not show messages for idle shutdown mode
if (m_ShutdownMask & SHUTDOWN_MASK_IDLE)
return;
///- Display a message every 12 hours, hours, 5 minutes, minute, 5 seconds and finally seconds
if (show ||
(m_ShutdownTimer < 5* MINUTE && (m_ShutdownTimer % 15) == 0) || // < 5 min; every 15 sec
(m_ShutdownTimer < 15 * MINUTE && (m_ShutdownTimer % MINUTE) == 0) || // < 15 min ; every 1 min
(m_ShutdownTimer < 30 * MINUTE && (m_ShutdownTimer % (5 * MINUTE)) == 0) || // < 30 min ; every 5 min
(m_ShutdownTimer < 12 * HOUR && (m_ShutdownTimer % HOUR) == 0) || // < 12 h ; every 1 h
(m_ShutdownTimer > 12 * HOUR && (m_ShutdownTimer % (12 * HOUR)) == 0)) // > 12 h ; every 12 h
{
std::string str = secsToTimeString(m_ShutdownTimer, TimeFormat::Numeric);
if (!reason.empty())
str += " - " + reason;
ServerMessageType msgid = (m_ShutdownMask & SHUTDOWN_MASK_RESTART) ? SERVER_MSG_RESTART_TIME : SERVER_MSG_SHUTDOWN_TIME;
SendServerMessage(msgid, str, player);
TC_LOG_DEBUG("misc", "Server is {} in {}", (m_ShutdownMask & SHUTDOWN_MASK_RESTART ? "restart" : "shuttingdown"), str);
}
}
/// Cancel a planned server shutdown
uint32 World::ShutdownCancel()
{
// nothing cancel or too late
if (!m_ShutdownTimer || m_stopEvent)
return 0;
ServerMessageType msgid = (m_ShutdownMask & SHUTDOWN_MASK_RESTART) ? SERVER_MSG_RESTART_CANCELLED : SERVER_MSG_SHUTDOWN_CANCELLED;
uint32 oldTimer = m_ShutdownTimer;
m_ShutdownMask = 0;
m_ShutdownTimer = 0;
m_ExitCode = SHUTDOWN_EXIT_CODE; // to default value
SendServerMessage(msgid);
TC_LOG_DEBUG("misc", "Server {} cancelled.", (m_ShutdownMask & SHUTDOWN_MASK_RESTART ? "restart" : "shutdown"));
sScriptMgr->OnShutdownCancel();
return oldTimer;
}
/// Send a server message to the user(s)
void World::SendServerMessage(ServerMessageType messageID, std::string_view stringParam /*= {}*/, Player const* player /*= nullptr*/)
{
ServerMessagesEntry const* serverMessage = sServerMessagesStore.LookupEntry(messageID);
if (!serverMessage)
return;
WorldPackets::Chat::ChatServerMessage chatServerMessage;
chatServerMessage.MessageID = int32(messageID);
if (strstr(serverMessage->Text[player ? player->GetSession()->GetSessionDbcLocale() : GetDefaultDbcLocale()], "%s"))
chatServerMessage.StringParam = stringParam;
if (player)
player->SendDirectMessage(chatServerMessage.Write());
else
SendGlobalMessage(chatServerMessage.Write());
}
void World::UpdateSessions(uint32 diff)
{
{
TC_METRIC_DETAILED_NO_THRESHOLD_TIMER("world_update_time",
TC_METRIC_TAG("type", "Add sessions"),
TC_METRIC_TAG("parent_type", "Update sessions"));
///- Add new sessions
WorldSession* sess = nullptr;
while (addSessQueue.next(sess))
AddSession_(sess);
}
{
std::pair, uint64> linkInfo;
while (_linkSocketQueue.next(linkInfo))
{
WorldSession::ConnectToKey key = { .Raw = linkInfo.second };
WorldSession::AddInstanceConnection(FindSession(key.Fields.AccountId), linkInfo.first, key);
}
}
///- Then send an update signal to remaining ones
for (SessionMap::iterator itr = m_sessions.begin(), next; itr != m_sessions.end(); itr = next)
{
next = itr;
++next;
///- and remove not active sessions from the list
WorldSession* pSession = itr->second;
WorldSessionFilter updater(pSession);
[[maybe_unused]] uint32 currentSessionId = itr->first;
TC_METRIC_DETAILED_TIMER("world_update_sessions_time", TC_METRIC_TAG("account_id", std::to_string(currentSessionId)));
if (!pSession->Update(diff, updater)) // As interval = 0
{
if (!RemoveQueuedPlayer(itr->second) && itr->second && getIntConfig(CONFIG_INTERVAL_DISCONNECT_TOLERANCE))
m_disconnects[itr->second->GetAccountId()] = GameTime::GetGameTime();
RemoveQueuedPlayer(pSession);
m_sessions.erase(itr);
Trinity::Containers::MultimapErasePair(m_sessionsByBnetGuid, pSession->GetBattlenetAccountGUID(), pSession);
delete pSession;
}
}
}
// This handles the issued and queued CLI commands
void World::ProcessCliCommands()
{
CliCommandHolder::Print zprint = nullptr;
void* callbackArg = nullptr;
CliCommandHolder* command = nullptr;
while (cliCmdQueue.next(command))
{
TC_LOG_INFO("misc", "CLI command under processing...");
zprint = command->m_print;
callbackArg = command->m_callbackArg;
CliHandler handler(callbackArg, zprint);
handler.ParseCommands(command->m_command);
if (command->m_commandFinished)
command->m_commandFinished(callbackArg, !handler.HasSentErrorMessage());
delete command;
}
}
void World::SendAutoBroadcast()
{
if (m_Autobroadcasts.empty())
return;
auto itr = Trinity::Containers::SelectRandomWeightedContainerElement(m_Autobroadcasts, [](AutobroadcastContainer::value_type const& pair)
{
return pair.second.Weight;
});
uint32 abcenter = getIntConfig(CONFIG_AUTOBROADCAST_CENTER);
if (abcenter == 0)
SendWorldText(LANG_AUTO_BROADCAST, itr->second.Message.c_str());
else if (abcenter == 1)
SendGlobalMessage(WorldPackets::Chat::PrintNotification(itr->second.Message).Write());
else if (abcenter == 2)
{
SendWorldText(LANG_AUTO_BROADCAST, itr->second.Message.c_str());
SendGlobalMessage(WorldPackets::Chat::PrintNotification(itr->second.Message).Write());
}
TC_LOG_DEBUG("misc", "AutoBroadcast: '{}'", itr->second.Message);
}
void World::UpdateRealmCharCount(uint32 accountId)
{
CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_CHARACTER_COUNT);
stmt->setUInt32(0, accountId);
_queryProcessor.AddCallback(CharacterDatabase.AsyncQuery(stmt).WithPreparedCallback([this](PreparedQueryResult result)
{
_UpdateRealmCharCount(std::move(result));
}));
}
void World::_UpdateRealmCharCount(PreparedQueryResult resultCharCount)
{
if (resultCharCount)
{
Field* fields = resultCharCount->Fetch();
uint32 accountId = fields[0].GetUInt32();
uint8 charCount = uint8(fields[1].GetUInt64());
LoginDatabaseTransaction trans = LoginDatabase.BeginTransaction();
LoginDatabasePreparedStatement* stmt = LoginDatabase.GetPreparedStatement(LOGIN_REP_REALM_CHARACTERS);
stmt->setUInt8(0, charCount);
stmt->setUInt32(1, accountId);
stmt->setUInt32(2, sRealmList->GetCurrentRealmId().Realm);
trans->Append(stmt);
LoginDatabase.CommitTransaction(trans);
}
}
void World::InitQuestResetTimes()
{
m_NextDailyQuestReset = GetPersistentWorldVariable(NextDailyQuestResetTimeVarId);
m_NextWeeklyQuestReset = GetPersistentWorldVariable(NextWeeklyQuestResetTimeVarId);
m_NextMonthlyQuestReset = GetPersistentWorldVariable(NextMonthlyQuestResetTimeVarId);
}
static time_t GetNextDailyResetTime(time_t t)
{
return GetLocalHourTimestamp(t, sWorld->getIntConfig(CONFIG_DAILY_QUEST_RESET_TIME_HOUR), true);
}
void World::DailyReset()
{
// reset all saved quest status
CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_RESET_CHARACTER_QUESTSTATUS_DAILY);
CharacterDatabase.Execute(stmt);
stmt = CharacterDatabase.GetPreparedStatement(CHAR_UPD_CHARACTER_GARRISON_FOLLOWER_ACTIVATIONS);
stmt->setUInt32(0, 1);
CharacterDatabase.Execute(stmt);
// reset all quest status in memory
for (SessionMap::const_iterator itr = m_sessions.begin(); itr != m_sessions.end(); ++itr)
if (Player* player = itr->second->GetPlayer())
player->DailyReset();
// reselect pools
sQuestPoolMgr->ChangeDailyQuests();
// store next reset time
time_t now = GameTime::GetGameTime();
time_t next = GetNextDailyResetTime(now);
ASSERT(now < next);
m_NextDailyQuestReset = next;
SetPersistentWorldVariable(NextDailyQuestResetTimeVarId, uint64(next));
TC_LOG_INFO("misc", "Daily quests for all characters have been reset.");
}
static time_t GetNextWeeklyResetTime(time_t t)
{
t = GetNextDailyResetTime(t);
tm time = TimeBreakdown(t);
int wday = time.tm_wday;
int target = sWorld->getIntConfig(CONFIG_WEEKLY_QUEST_RESET_TIME_WDAY);
if (target < wday)
wday -= 7;
t += (DAY * (target - wday));
return t;
}
void World::ResetWeeklyQuests()
{
// reset all saved quest status
CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_RESET_CHARACTER_QUESTSTATUS_WEEKLY);
CharacterDatabase.Execute(stmt);
// reset all quest status in memory
for (SessionMap::const_iterator itr = m_sessions.begin(); itr != m_sessions.end(); ++itr)
if (Player* player = itr->second->GetPlayer())
player->ResetWeeklyQuestStatus();
// reselect pools
sQuestPoolMgr->ChangeWeeklyQuests();
// Update faction balance
UpdateWarModeRewardValues();
// store next reset time
time_t now = GameTime::GetGameTime();
time_t next = GetNextWeeklyResetTime(now);
ASSERT(now < next);
m_NextWeeklyQuestReset = next;
SetPersistentWorldVariable(NextWeeklyQuestResetTimeVarId, uint64(next));
TC_LOG_INFO("misc", "Weekly quests for all characters have been reset.");
}
static time_t GetNextMonthlyResetTime(time_t t)
{
t = GetNextDailyResetTime(t);
tm time = TimeBreakdown(t);
if (time.tm_mday == 1)
return t;
time.tm_mday = 1;
time.tm_mon += 1;
return mktime(&time);
}
void World::ResetMonthlyQuests()
{
// reset all saved quest status
CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_RESET_CHARACTER_QUESTSTATUS_MONTHLY);
CharacterDatabase.Execute(stmt);
// reset all quest status in memory
for (SessionMap::const_iterator itr = m_sessions.begin(); itr != m_sessions.end(); ++itr)
if (Player* player = itr->second->GetPlayer())
player->ResetMonthlyQuestStatus();
// reselect pools
sQuestPoolMgr->ChangeMonthlyQuests();
// store next reset time
time_t now = GameTime::GetGameTime();
time_t next = GetNextMonthlyResetTime(now);
ASSERT(now < next);
m_NextMonthlyQuestReset = next;
SetPersistentWorldVariable(NextMonthlyQuestResetTimeVarId, uint64(next));
TC_LOG_INFO("misc", "Monthly quests for all characters have been reset.");
}
void World::CheckScheduledResetTimes()
{
time_t const now = GameTime::GetGameTime();
if (m_NextDailyQuestReset <= now)
DailyReset();
if (m_NextWeeklyQuestReset <= now)
ResetWeeklyQuests();
if (m_NextMonthlyQuestReset <= now)
ResetMonthlyQuests();
}
void World::InitRandomBGResetTime()
{
time_t bgtime = GetPersistentWorldVariable(NextBGRandomDailyResetTimeVarId);
if (!bgtime)
m_NextRandomBGReset = GameTime::GetGameTime(); // game time not yet init
// generate time by config
time_t curTime = GameTime::GetGameTime();
tm localTm;
localtime_r(&curTime, &localTm);
localTm.tm_hour = getIntConfig(CONFIG_RANDOM_BG_RESET_HOUR);
localTm.tm_min = 0;
localTm.tm_sec = 0;
// current day reset time
time_t nextDayResetTime = mktime(&localTm);
// next reset time before current moment
if (curTime >= nextDayResetTime)
nextDayResetTime += DAY;
// normalize reset time
m_NextRandomBGReset = bgtime < curTime ? nextDayResetTime - DAY : nextDayResetTime;
if (!bgtime)
SetPersistentWorldVariable(NextBGRandomDailyResetTimeVarId, uint32(m_NextRandomBGReset));
}
void World::InitCalendarOldEventsDeletionTime()
{
time_t now = GameTime::GetGameTime();
time_t nextDeletionTime = GetLocalHourTimestamp(now, getIntConfig(CONFIG_CALENDAR_DELETE_OLD_EVENTS_HOUR));
time_t currentDeletionTime = GetPersistentWorldVariable(NextOldCalendarEventDeletionTimeVarId);
// If the reset time saved in the worldstate is before now it means the server was offline when the reset was supposed to occur.
// In this case we set the reset time in the past and next world update will do the reset and schedule next one in the future.
if (currentDeletionTime < now)
m_NextCalendarOldEventsDeletionTime = nextDeletionTime - DAY;
else
m_NextCalendarOldEventsDeletionTime = nextDeletionTime;
if (!currentDeletionTime)
SetPersistentWorldVariable(NextOldCalendarEventDeletionTimeVarId, uint64(m_NextCalendarOldEventsDeletionTime));
}
void World::InitGuildResetTime()
{
time_t gtime = GetPersistentWorldVariable(NextGuildDailyResetTimeVarId);
if (!gtime)
m_NextGuildReset = GameTime::GetGameTime(); // game time not yet init
// generate time by config
time_t curTime = GameTime::GetGameTime();
tm localTm;
localtime_r(&curTime, &localTm);
localTm.tm_hour = getIntConfig(CONFIG_GUILD_RESET_HOUR);
localTm.tm_min = 0;
localTm.tm_sec = 0;
// current day reset time
time_t nextDayResetTime = mktime(&localTm);
// next reset time before current moment
if (curTime >= nextDayResetTime)
nextDayResetTime += DAY;
// normalize reset time
m_NextGuildReset = gtime < curTime ? nextDayResetTime - DAY : nextDayResetTime;
if (!gtime)
SetPersistentWorldVariable(NextGuildDailyResetTimeVarId, uint32(m_NextGuildReset));
}
void World::InitCurrencyResetTime()
{
time_t currencytime = GetPersistentWorldVariable(NextCurrencyResetTimeVarId);
if (!currencytime)
m_NextCurrencyReset = GameTime::GetGameTime(); // game time not yet init
// generate time by config
time_t curTime = GameTime::GetGameTime();
tm localTm;
localtime_r(&curTime, &localTm);
localTm.tm_wday = getIntConfig(CONFIG_CURRENCY_RESET_DAY);
localTm.tm_hour = getIntConfig(CONFIG_CURRENCY_RESET_HOUR);
localTm.tm_min = 0;
localTm.tm_sec = 0;
// current week reset time
time_t nextWeekResetTime = mktime(&localTm);
// next reset time before current moment
if (curTime >= nextWeekResetTime)
nextWeekResetTime += getIntConfig(CONFIG_CURRENCY_RESET_INTERVAL) * DAY;
// normalize reset time
m_NextCurrencyReset = currencytime < curTime ? nextWeekResetTime - getIntConfig(CONFIG_CURRENCY_RESET_INTERVAL) * DAY : nextWeekResetTime;
if (!currencytime)
SetPersistentWorldVariable(NextCurrencyResetTimeVarId, uint32(m_NextCurrencyReset));
}
void World::ResetCurrencyWeekCap()
{
CharacterDatabase.Execute("UPDATE `character_currency` SET `WeeklyQuantity` = 0");
for (SessionMap::const_iterator itr = m_sessions.begin(); itr != m_sessions.end(); ++itr)
if (itr->second->GetPlayer())
itr->second->GetPlayer()->ResetCurrencyWeekCap();
m_NextCurrencyReset = time_t(m_NextCurrencyReset + DAY * getIntConfig(CONFIG_CURRENCY_RESET_INTERVAL));
SetPersistentWorldVariable(NextCurrencyResetTimeVarId, uint32(m_NextCurrencyReset));
}
void World::ResetEventSeasonalQuests(uint16 event_id, time_t eventStartTime)
{
TC_LOG_INFO("misc", "Seasonal quests reset for all characters.");
CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_RESET_CHARACTER_QUESTSTATUS_SEASONAL_BY_EVENT);
stmt->setUInt16(0, event_id);
stmt->setInt64(1, eventStartTime);
CharacterDatabase.Execute(stmt);
for (SessionMap::const_iterator itr = m_sessions.begin(); itr != m_sessions.end(); ++itr)
if (itr->second->GetPlayer())
itr->second->GetPlayer()->ResetSeasonalQuestStatus(event_id, eventStartTime);
}
void World::ResetRandomBG()
{
TC_LOG_INFO("misc", "Random BG status reset for all characters.");
CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_BATTLEGROUND_RANDOM_ALL);
CharacterDatabase.Execute(stmt);
for (SessionMap::const_iterator itr = m_sessions.begin(); itr != m_sessions.end(); ++itr)
if (itr->second->GetPlayer())
itr->second->GetPlayer()->SetRandomWinner(false);
m_NextRandomBGReset = time_t(m_NextRandomBGReset + DAY);
SetPersistentWorldVariable(NextBGRandomDailyResetTimeVarId, uint32(m_NextRandomBGReset));
}
void World::CalendarDeleteOldEvents()
{
TC_LOG_INFO("misc", "Calendar deletion of old events.");
m_NextCalendarOldEventsDeletionTime = time_t(m_NextCalendarOldEventsDeletionTime + DAY);
SetPersistentWorldVariable(NextOldCalendarEventDeletionTimeVarId, uint64(m_NextCalendarOldEventsDeletionTime));
sCalendarMgr->DeleteOldEvents();
}
void World::ResetGuildCap()
{
m_NextGuildReset = time_t(m_NextGuildReset + DAY);
SetPersistentWorldVariable(NextGuildDailyResetTimeVarId, uint32(m_NextGuildReset));
uint32 week = GetPersistentWorldVariable(NextGuildWeeklyResetTimeVarId);
week = week < 7 ? week + 1 : 1;
TC_LOG_INFO("misc", "Guild Daily Cap reset. Week: {}", week == 1);
SetPersistentWorldVariable(NextGuildWeeklyResetTimeVarId, week);
sGuildMgr->ResetTimes(week == 1);
}
void World::UpdateMaxSessionCounters()
{
m_maxActiveSessionCount = std::max(m_maxActiveSessionCount, uint32(m_sessions.size()-m_QueuedPlayer.size()));
m_maxQueuedSessionCount = std::max(m_maxQueuedSessionCount, uint32(m_QueuedPlayer.size()));
}
void World::LoadDBVersion()
{
if (QueryResult result = WorldDatabase.Query("SELECT db_version, cache_id FROM version LIMIT 1"))
{
Field* fields = result->Fetch();
m_DBVersion = fields[0].GetString();
// will be overwrite by config values if different and non-0
m_int_configs[CONFIG_CLIENTCACHE_VERSION] = fields[1].GetUInt32();
}
if (m_DBVersion.empty())
m_DBVersion = "Unknown world database.";
}
void World::UpdateAreaDependentAuras()
{
SessionMap::const_iterator itr;
for (itr = m_sessions.begin(); itr != m_sessions.end(); ++itr)
if (itr->second && itr->second->GetPlayer() && itr->second->GetPlayer()->IsInWorld())
{
itr->second->GetPlayer()->UpdateAreaDependentAuras(itr->second->GetPlayer()->GetAreaId());
itr->second->GetPlayer()->UpdateZoneDependentAuras(itr->second->GetPlayer()->GetZoneId());
}
}
bool World::IsBattlePetJournalLockAcquired(ObjectGuid battlenetAccountGuid)
{
for (auto&& sessionForBnet : Trinity::Containers::MapEqualRange(m_sessionsByBnetGuid, battlenetAccountGuid))
if (sessionForBnet.second->GetBattlePetMgr()->HasJournalLock())
return true;
return false;
}
bool World::IsPvPRealm() const
{
return (getIntConfig(CONFIG_GAME_TYPE) == REALM_TYPE_PVP || getIntConfig(CONFIG_GAME_TYPE) == REALM_TYPE_RPPVP || getIntConfig(CONFIG_GAME_TYPE) == REALM_TYPE_FFA_PVP);
}
bool World::IsFFAPvPRealm() const
{
return getIntConfig(CONFIG_GAME_TYPE) == REALM_TYPE_FFA_PVP;
}
int32 World::GetPersistentWorldVariable(PersistentWorldVariable const& var) const
{
if (int32 const* value = Trinity::Containers::MapGetValuePtr(m_worldVariables, var.Id))
return *value;
return 0;
}
void World::SetPersistentWorldVariable(PersistentWorldVariable const& var, int32 value)
{
m_worldVariables[var.Id] = value;
CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_REP_WORLD_VARIABLE);
stmt->setString(0, var.Id);
stmt->setInt32(1, value);
CharacterDatabase.Execute(stmt);
}
void World::LoadPersistentWorldVariables()
{
uint32 oldMSTime = getMSTime();
if (QueryResult result = CharacterDatabase.Query("SELECT ID, Value FROM world_variable"))
{
do
{
Field* fields = result->Fetch();
m_worldVariables[fields[0].GetString()] = fields[1].GetInt32();
} while (result->NextRow());
}
TC_LOG_INFO("server.loading", ">> Loaded {} world variables in {} ms", m_worldVariables.size(), GetMSTimeDiffToNow(oldMSTime));
}
void World::ProcessQueryCallbacks()
{
_queryProcessor.ProcessReadyCallbacks();
}
void World::ReloadRBAC()
{
// Passive reload, we mark the data as invalidated and next time a permission is checked it will be reloaded
TC_LOG_INFO("rbac", "World::ReloadRBAC()");
for (SessionMap::const_iterator itr = m_sessions.begin(); itr != m_sessions.end(); ++itr)
if (WorldSession* session = itr->second)
session->InvalidateRBACData();
}
void World::RemoveOldCorpses()
{
m_timers[WUPDATE_CORPSES].SetCurrent(m_timers[WUPDATE_CORPSES].GetInterval());
}
void World::UpdateWarModeRewardValues()
{
std::array warModeEnabledFaction = { };
// Search for characters that have war mode enabled and played during the last week
CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_WAR_MODE_TUNING);
stmt->setUInt32(0, PLAYER_FLAGS_WAR_MODE_DESIRED);
stmt->setUInt32(1, PLAYER_FLAGS_WAR_MODE_DESIRED);
if (PreparedQueryResult result = CharacterDatabase.Query(stmt))
{
do
{
Field* fields = result->Fetch();
uint8 race = fields[0].GetUInt8();
if (ChrRacesEntry const* raceEntry = sChrRacesStore.LookupEntry(race))
{
if (FactionTemplateEntry const* raceFaction = sFactionTemplateStore.AssertEntry(raceEntry->FactionID))
{
if (raceFaction->FactionGroup & FACTION_MASK_ALLIANCE)
warModeEnabledFaction[TEAM_ALLIANCE] += fields[1].GetInt64();
else if (raceFaction->FactionGroup & FACTION_MASK_HORDE)
warModeEnabledFaction[TEAM_HORDE] += fields[1].GetInt64();
}
}
} while (result->NextRow());
}
TeamId dominantFaction = TEAM_ALLIANCE;
int32 outnumberedFactionReward = 0;
if (std::any_of(warModeEnabledFaction.begin(), warModeEnabledFaction.end(), [](int64 val) { return val != 0; }))
{
int64 dominantFactionCount = warModeEnabledFaction[TEAM_ALLIANCE];
if (warModeEnabledFaction[TEAM_ALLIANCE] < warModeEnabledFaction[TEAM_HORDE])
{
dominantFactionCount = warModeEnabledFaction[TEAM_HORDE];
dominantFaction = TEAM_HORDE;
}
double total = warModeEnabledFaction[TEAM_ALLIANCE] + warModeEnabledFaction[TEAM_HORDE];
double pct = dominantFactionCount / total;
if (pct >= getFloatConfig(CONFIG_CALL_TO_ARMS_20_PCT))
outnumberedFactionReward = 20;
else if (pct >= getFloatConfig(CONFIG_CALL_TO_ARMS_10_PCT))
outnumberedFactionReward = 10;
else if (pct >= getFloatConfig(CONFIG_CALL_TO_ARMS_5_PCT))
outnumberedFactionReward = 5;
}
sWorldStateMgr->SetValueAndSaveInDb(WS_WAR_MODE_HORDE_BUFF_VALUE, 10 + (dominantFaction == TEAM_ALLIANCE ? outnumberedFactionReward : 0), false, nullptr);
sWorldStateMgr->SetValueAndSaveInDb(WS_WAR_MODE_ALLIANCE_BUFF_VALUE, 10 + (dominantFaction == TEAM_HORDE ? outnumberedFactionReward : 0), false, nullptr);
}
uint32 GetVirtualRealmAddress()
{
return sRealmList->GetCurrentRealmId().GetAddress();
}
CliCommandHolder::CliCommandHolder(void* callbackArg, char const* command, Print zprint, CommandFinished commandFinished)
: m_callbackArg(callbackArg), m_command(strdup(command)), m_print(zprint), m_commandFinished(commandFinished)
{
}
CliCommandHolder::~CliCommandHolder()
{
free(m_command);
}