mirror of
https://github.com/TrinityCore/TrinityCore.git
synced 2026-01-22 10:05:32 +01:00
- Logging System is asyncronous to improve performance.
- Each msg and Logger has a Log Type and Log Level assigned. Each msg is assigned the Logger of same Log Type or "root" Logger is selected if there is no Logger configured for the given Log Type
- Loggers have a list of Appenders to send the msg to. The Msg in the Logger is not sent to Appenders if the msg LogLevel is lower than Logger LogLevel.
- There are three (at the moment) types of Appenders: Console, File or DB (this is WIP, not working ATM). Msg is not written to the resource if msg LogLevel is lower than Appender LogLevel.
- Appender and Console Log levels can be changed while server is active with command '.set loglevel (a/l) name level'
Explanation of use with Sample config:
Appender.Console.Type=1 (1 = Console)
Appender.Console.Level=2 (2 = Debug)
Appender.Server.Type=2 (2 = File)
Appender.Server.Level=3 (3 = Info)
Appender.Server.File=Server.log
Appender.SQL.Type=2 (2 = File)
Appender.SQL.Level=1 (1 = Trace)
Appender.SQL.File=sql.log
Appenders=Console Server (NOTE: SQL has not been included here... that will make core ignore the config for "SQL" as it's not in this list)
Logger.root.Type=0 (0 = Default - if it's not created by config, server will create it with LogLevel = DISABLED)
Logger.root.Level=5 (5 = Error)
Logger.root.Appenders=Console
Logger.SQL.Type=26 (26 = SQL)
Logger.SQL.Level=3 (2 = Debug)
Logger.SQL.Appenders=Console Server SQL
Logger.SomeRandomName.Type=24 (24 = Guild)
Logger.SomeRandomName.Level=5 (5 = Error)
Loggers=root SQL SomeRandomName
* At loading Appender SQL will be ignored, as it's not present on "Appenders"
* sLog->outDebug(LOG_FILTER_GUILD, "Some log msg related to Guilds")
- Msg is sent to Logger of Type LOG_FILTER_GUILD (24). Logger with name SomeRandomName is found but it's LogLevel = 5 and Msg LogLevel=2... Msg is not logged
* sLog->outError(LOG_FILTER_GUILD, "Some error log msg related to Guilds")
- Msg is sent to Logger of Type LOG_FILTER_GUILD (24). Logger with name SomeRandomeName is found with proper LogLevel but Logger does not have any Appenders assigned to that logger... Msg is not logged
* sLog->outDebug(LOG_FILTER_SQL, "Some msg related to SQLs")
- Msg is sent to Logger SQL (matches type), as it matches LogLevel the msg is sent to Appenders Console, Server and SQL
- Appender Console has lower Log Level: Msg is logged to Console
- Appender Server has higher Log Level: Msg is not logged to file
- Appender SQL has lower Log Level: Msg is logged to file sql.log
* sLog->outDebug(LOG_FILTER_BATTLEGROUND, "Some msg related to Battelgrounds")
- Msg is sent to Logger root (Type 0) as no Logger was found with Type LOG_FILTER_BATTLEGROUND (13). As Logger has higher LogLevel msg is not sent to any appender
* sLog->outError(LOG_FILTER_BATTLEGROUND, "Some error msg related to Battelgrounds")
- Msg is sent to Logger root (Type 0) as no Logger was found with Type LOG_FILTER_BATTLEGROUND (13). Msg has lower LogLevel and is sent to Appender Console
- Appender Console has lower LogLevel: Msg is logged to Console
476 lines
16 KiB
C++
Executable File
476 lines
16 KiB
C++
Executable File
/*
|
|
* Copyright (C) 2008-2012 TrinityCore <http://www.trinitycore.org/>
|
|
* Copyright (C) 2005-2009 MaNGOS <http://getmangos.com/>
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify it
|
|
* under the terms of the GNU General Public License as published by the
|
|
* Free Software Foundation; either version 2 of the License, or (at your
|
|
* option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful, but WITHOUT
|
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
|
* more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License along
|
|
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
/** \file
|
|
\ingroup Trinityd
|
|
*/
|
|
|
|
#include <ace/Sig_Handler.h>
|
|
|
|
#include "Common.h"
|
|
#include "SystemConfig.h"
|
|
#include "SignalHandler.h"
|
|
#include "World.h"
|
|
#include "WorldRunnable.h"
|
|
#include "WorldSocket.h"
|
|
#include "WorldSocketMgr.h"
|
|
#include "Configuration/Config.h"
|
|
#include "Database/DatabaseEnv.h"
|
|
#include "Database/DatabaseWorkerPool.h"
|
|
|
|
#include "CliRunnable.h"
|
|
#include "Log.h"
|
|
#include "Master.h"
|
|
#include "RARunnable.h"
|
|
#include "TCSoap.h"
|
|
#include "Timer.h"
|
|
#include "Util.h"
|
|
#include "AuthSocket.h"
|
|
#include "RealmList.h"
|
|
|
|
#include "BigNumber.h"
|
|
|
|
#ifdef _WIN32
|
|
#include "ServiceWin32.h"
|
|
extern int m_ServiceStatus;
|
|
#endif
|
|
|
|
/// Handle worldservers's termination signals
|
|
class WorldServerSignalHandler : public Trinity::SignalHandler
|
|
{
|
|
public:
|
|
virtual void HandleSignal(int SigNum)
|
|
{
|
|
switch (SigNum)
|
|
{
|
|
case SIGINT:
|
|
World::StopNow(RESTART_EXIT_CODE);
|
|
break;
|
|
case SIGTERM:
|
|
#ifdef _WIN32
|
|
case SIGBREAK:
|
|
if (m_ServiceStatus != 1)
|
|
#endif /* _WIN32 */
|
|
World::StopNow(SHUTDOWN_EXIT_CODE);
|
|
break;
|
|
}
|
|
}
|
|
};
|
|
|
|
class FreezeDetectorRunnable : public ACE_Based::Runnable
|
|
{
|
|
public:
|
|
FreezeDetectorRunnable() { _delaytime = 0; }
|
|
uint32 m_loops, m_lastchange;
|
|
uint32 w_loops, w_lastchange;
|
|
uint32 _delaytime;
|
|
void SetDelayTime(uint32 t) { _delaytime = t; }
|
|
void run(void)
|
|
{
|
|
if (!_delaytime)
|
|
return;
|
|
sLog->outInfo(LOG_FILTER_WORLDSERVER, "Starting up anti-freeze thread (%u seconds max stuck time)...", _delaytime/1000);
|
|
m_loops = 0;
|
|
w_loops = 0;
|
|
m_lastchange = 0;
|
|
w_lastchange = 0;
|
|
while (!World::IsStopped())
|
|
{
|
|
ACE_Based::Thread::Sleep(1000);
|
|
uint32 curtime = getMSTime();
|
|
// normal work
|
|
if (w_loops != World::m_worldLoopCounter)
|
|
{
|
|
w_lastchange = curtime;
|
|
w_loops = World::m_worldLoopCounter;
|
|
}
|
|
// possible freeze
|
|
else if (getMSTimeDiff(w_lastchange, curtime) > _delaytime)
|
|
{
|
|
sLog->outError(LOG_FILTER_WORLDSERVER, "World Thread hangs, kicking out server!");
|
|
ASSERT(false);
|
|
}
|
|
}
|
|
sLog->outInfo(LOG_FILTER_WORLDSERVER, "Anti-freeze thread exiting without problems.");
|
|
}
|
|
};
|
|
|
|
Master::Master()
|
|
{
|
|
}
|
|
|
|
Master::~Master()
|
|
{
|
|
}
|
|
|
|
/// Main function
|
|
int Master::Run()
|
|
{
|
|
BigNumber seed1;
|
|
seed1.SetRand(16 * 8);
|
|
|
|
sLog->outInfo(LOG_FILTER_WORLDSERVER, "%s (worldserver-daemon)", _FULLVERSION);
|
|
sLog->outInfo(LOG_FILTER_WORLDSERVER, "<Ctrl-C> to stop.\n");
|
|
|
|
sLog->outInfo(LOG_FILTER_WORLDSERVER, " ______ __");
|
|
sLog->outInfo(LOG_FILTER_WORLDSERVER, "/\\__ _\\ __ __/\\ \\__");
|
|
sLog->outInfo(LOG_FILTER_WORLDSERVER, "\\/_/\\ \\/ _ __ /\\_\\ ___ /\\_\\ \\, _\\ __ __");
|
|
sLog->outInfo(LOG_FILTER_WORLDSERVER, " \\ \\ \\/\\`'__\\/\\ \\ /' _ `\\/\\ \\ \\ \\/ /\\ \\/\\ \\");
|
|
sLog->outInfo(LOG_FILTER_WORLDSERVER, " \\ \\ \\ \\ \\/ \\ \\ \\/\\ \\/\\ \\ \\ \\ \\ \\_\\ \\ \\_\\ \\");
|
|
sLog->outInfo(LOG_FILTER_WORLDSERVER, " \\ \\_\\ \\_\\ \\ \\_\\ \\_\\ \\_\\ \\_\\ \\__\\\\/`____ \\");
|
|
sLog->outInfo(LOG_FILTER_WORLDSERVER, " \\/_/\\/_/ \\/_/\\/_/\\/_/\\/_/\\/__/ `/___/> \\");
|
|
sLog->outInfo(LOG_FILTER_WORLDSERVER, " C O R E /\\___/");
|
|
sLog->outInfo(LOG_FILTER_WORLDSERVER, "http://TrinityCore.org \\/__/\n");
|
|
|
|
/// worldserver PID file creation
|
|
std::string pidfile = ConfigMgr::GetStringDefault("PidFile", "");
|
|
if (!pidfile.empty())
|
|
{
|
|
uint32 pid = CreatePIDFile(pidfile);
|
|
if (!pid)
|
|
{
|
|
sLog->outError(LOG_FILTER_WORLDSERVER, "Cannot create PID file %s.\n", pidfile.c_str());
|
|
return 1;
|
|
}
|
|
|
|
sLog->outInfo(LOG_FILTER_WORLDSERVER, "Daemon PID: %u\n", pid);
|
|
}
|
|
|
|
///- Start the databases
|
|
if (!_StartDB())
|
|
return 1;
|
|
|
|
// set server offline (not connectable)
|
|
LoginDatabase.DirectPExecute("UPDATE realmlist SET flag = (flag & ~%u) | %u WHERE id = '%d'", REALM_FLAG_OFFLINE, REALM_FLAG_INVALID, realmID);
|
|
|
|
///- Initialize the World
|
|
sWorld->SetInitialWorldSettings();
|
|
|
|
///- Initialize the signal handlers
|
|
WorldServerSignalHandler SignalINT, SignalTERM;
|
|
#ifdef _WIN32
|
|
WorldServerSignalHandler SignalBREAK;
|
|
#endif /* _WIN32 */
|
|
|
|
///- Register worldserver's signal handlers
|
|
ACE_Sig_Handler Handler;
|
|
Handler.register_handler(SIGINT, &SignalINT);
|
|
Handler.register_handler(SIGTERM, &SignalTERM);
|
|
#ifdef _WIN32
|
|
Handler.register_handler(SIGBREAK, &SignalBREAK);
|
|
#endif /* _WIN32 */
|
|
|
|
///- Launch WorldRunnable thread
|
|
ACE_Based::Thread world_thread(new WorldRunnable);
|
|
world_thread.setPriority(ACE_Based::Highest);
|
|
|
|
ACE_Based::Thread* cliThread = NULL;
|
|
|
|
#ifdef _WIN32
|
|
if (ConfigMgr::GetBoolDefault("Console.Enable", true) && (m_ServiceStatus == -1)/* need disable console in service mode*/)
|
|
#else
|
|
if (ConfigMgr::GetBoolDefault("Console.Enable", true))
|
|
#endif
|
|
{
|
|
///- Launch CliRunnable thread
|
|
cliThread = new ACE_Based::Thread(new CliRunnable);
|
|
}
|
|
|
|
ACE_Based::Thread rar_thread(new RARunnable);
|
|
|
|
///- Handle affinity for multiple processors and process priority on Windows
|
|
#ifdef _WIN32
|
|
{
|
|
HANDLE hProcess = GetCurrentProcess();
|
|
|
|
uint32 Aff = ConfigMgr::GetIntDefault("UseProcessors", 0);
|
|
if (Aff > 0)
|
|
{
|
|
ULONG_PTR appAff;
|
|
ULONG_PTR sysAff;
|
|
|
|
if (GetProcessAffinityMask(hProcess, &appAff, &sysAff))
|
|
{
|
|
ULONG_PTR curAff = Aff & appAff; // remove non accessible processors
|
|
|
|
if (!curAff)
|
|
{
|
|
sLog->outError(LOG_FILTER_WORLDSERVER, "Processors marked in UseProcessors bitmask (hex) %x are not accessible for the worldserver. Accessible processors bitmask (hex): %x", Aff, appAff);
|
|
}
|
|
else
|
|
{
|
|
if (SetProcessAffinityMask(hProcess, curAff))
|
|
sLog->outInfo(LOG_FILTER_WORLDSERVER, "Using processors (bitmask, hex): %x", curAff);
|
|
else
|
|
sLog->outError(LOG_FILTER_WORLDSERVER, "Can't set used processors (hex): %x", curAff);
|
|
}
|
|
}
|
|
}
|
|
|
|
bool Prio = ConfigMgr::GetBoolDefault("ProcessPriority", false);
|
|
|
|
//if (Prio && (m_ServiceStatus == -1) /* need set to default process priority class in service mode*/)
|
|
if (Prio)
|
|
{
|
|
if (SetPriorityClass(hProcess, HIGH_PRIORITY_CLASS))
|
|
sLog->outInfo(LOG_FILTER_WORLDSERVER, "worldserver process priority class set to HIGH");
|
|
else
|
|
sLog->outError(LOG_FILTER_WORLDSERVER, "Can't set worldserver process priority class.");
|
|
}
|
|
}
|
|
#endif
|
|
//Start soap serving thread
|
|
ACE_Based::Thread* soap_thread = NULL;
|
|
|
|
if (ConfigMgr::GetBoolDefault("SOAP.Enabled", false))
|
|
{
|
|
TCSoapRunnable* runnable = new TCSoapRunnable();
|
|
runnable->setListenArguments(ConfigMgr::GetStringDefault("SOAP.IP", "127.0.0.1"), ConfigMgr::GetIntDefault("SOAP.Port", 7878));
|
|
soap_thread = new ACE_Based::Thread(runnable);
|
|
}
|
|
|
|
///- Start up freeze catcher thread
|
|
if (uint32 freeze_delay = ConfigMgr::GetIntDefault("MaxCoreStuckTime", 0))
|
|
{
|
|
FreezeDetectorRunnable* fdr = new FreezeDetectorRunnable();
|
|
fdr->SetDelayTime(freeze_delay * 1000);
|
|
ACE_Based::Thread freeze_thread(fdr);
|
|
freeze_thread.setPriority(ACE_Based::Highest);
|
|
}
|
|
|
|
///- Launch the world listener socket
|
|
uint16 wsport = sWorld->getIntConfig(CONFIG_PORT_WORLD);
|
|
std::string bind_ip = ConfigMgr::GetStringDefault("BindIP", "0.0.0.0");
|
|
|
|
if (sWorldSocketMgr->StartNetwork(wsport, bind_ip.c_str()) == -1)
|
|
{
|
|
sLog->outError(LOG_FILTER_WORLDSERVER, "Failed to start network");
|
|
World::StopNow(ERROR_EXIT_CODE);
|
|
// go down and shutdown the server
|
|
}
|
|
|
|
// set server online (allow connecting now)
|
|
LoginDatabase.DirectPExecute("UPDATE realmlist SET flag = flag & ~%u, population = 0 WHERE id = '%u'", REALM_FLAG_INVALID, realmID);
|
|
|
|
sLog->outInfo(LOG_FILTER_WORLDSERVER, "%s (worldserver-daemon) ready...", _FULLVERSION);
|
|
|
|
// when the main thread closes the singletons get unloaded
|
|
// since worldrunnable uses them, it will crash if unloaded after master
|
|
world_thread.wait();
|
|
rar_thread.wait();
|
|
|
|
if (soap_thread)
|
|
{
|
|
soap_thread->wait();
|
|
soap_thread->destroy();
|
|
delete soap_thread;
|
|
}
|
|
|
|
// set server offline
|
|
LoginDatabase.DirectPExecute("UPDATE realmlist SET flag = flag | %u WHERE id = '%d'", REALM_FLAG_OFFLINE, realmID);
|
|
|
|
///- Clean database before leaving
|
|
ClearOnlineAccounts();
|
|
|
|
_StopDB();
|
|
|
|
sLog->outInfo(LOG_FILTER_WORLDSERVER, "Halting process...");
|
|
|
|
if (cliThread)
|
|
{
|
|
#ifdef _WIN32
|
|
|
|
// this only way to terminate CLI thread exist at Win32 (alt. way exist only in Windows Vista API)
|
|
//_exit(1);
|
|
// send keyboard input to safely unblock the CLI thread
|
|
INPUT_RECORD b[5];
|
|
HANDLE hStdIn = GetStdHandle(STD_INPUT_HANDLE);
|
|
b[0].EventType = KEY_EVENT;
|
|
b[0].Event.KeyEvent.bKeyDown = TRUE;
|
|
b[0].Event.KeyEvent.uChar.AsciiChar = 'X';
|
|
b[0].Event.KeyEvent.wVirtualKeyCode = 'X';
|
|
b[0].Event.KeyEvent.wRepeatCount = 1;
|
|
|
|
b[1].EventType = KEY_EVENT;
|
|
b[1].Event.KeyEvent.bKeyDown = FALSE;
|
|
b[1].Event.KeyEvent.uChar.AsciiChar = 'X';
|
|
b[1].Event.KeyEvent.wVirtualKeyCode = 'X';
|
|
b[1].Event.KeyEvent.wRepeatCount = 1;
|
|
|
|
b[2].EventType = KEY_EVENT;
|
|
b[2].Event.KeyEvent.bKeyDown = TRUE;
|
|
b[2].Event.KeyEvent.dwControlKeyState = 0;
|
|
b[2].Event.KeyEvent.uChar.AsciiChar = '\r';
|
|
b[2].Event.KeyEvent.wVirtualKeyCode = VK_RETURN;
|
|
b[2].Event.KeyEvent.wRepeatCount = 1;
|
|
b[2].Event.KeyEvent.wVirtualScanCode = 0x1c;
|
|
|
|
b[3].EventType = KEY_EVENT;
|
|
b[3].Event.KeyEvent.bKeyDown = FALSE;
|
|
b[3].Event.KeyEvent.dwControlKeyState = 0;
|
|
b[3].Event.KeyEvent.uChar.AsciiChar = '\r';
|
|
b[3].Event.KeyEvent.wVirtualKeyCode = VK_RETURN;
|
|
b[3].Event.KeyEvent.wVirtualScanCode = 0x1c;
|
|
b[3].Event.KeyEvent.wRepeatCount = 1;
|
|
DWORD numb;
|
|
WriteConsoleInput(hStdIn, b, 4, &numb);
|
|
|
|
cliThread->wait();
|
|
|
|
#else
|
|
|
|
cliThread->destroy();
|
|
|
|
#endif
|
|
|
|
delete cliThread;
|
|
}
|
|
|
|
// for some unknown reason, unloading scripts here and not in worldrunnable
|
|
// fixes a memory leak related to detaching threads from the module
|
|
//UnloadScriptingModule();
|
|
|
|
// Exit the process with specified return value
|
|
return World::GetExitCode();
|
|
}
|
|
|
|
/// Initialize connection to the databases
|
|
bool Master::_StartDB()
|
|
{
|
|
MySQL::Library_Init();
|
|
|
|
std::string dbstring;
|
|
uint8 async_threads, synch_threads;
|
|
|
|
dbstring = ConfigMgr::GetStringDefault("WorldDatabaseInfo", "");
|
|
if (dbstring.empty())
|
|
{
|
|
sLog->outError(LOG_FILTER_WORLDSERVER, "World database not specified in configuration file");
|
|
return false;
|
|
}
|
|
|
|
async_threads = ConfigMgr::GetIntDefault("WorldDatabase.WorkerThreads", 1);
|
|
if (async_threads < 1 || async_threads > 32)
|
|
{
|
|
sLog->outError(LOG_FILTER_WORLDSERVER, "World database: invalid number of worker threads specified. "
|
|
"Please pick a value between 1 and 32.");
|
|
return false;
|
|
}
|
|
|
|
synch_threads = ConfigMgr::GetIntDefault("WorldDatabase.SynchThreads", 1);
|
|
///- Initialise the world database
|
|
if (!WorldDatabase.Open(dbstring, async_threads, synch_threads))
|
|
{
|
|
sLog->outError(LOG_FILTER_WORLDSERVER, "Cannot connect to world database %s", dbstring.c_str());
|
|
return false;
|
|
}
|
|
|
|
///- Get character database info from configuration file
|
|
dbstring = ConfigMgr::GetStringDefault("CharacterDatabaseInfo", "");
|
|
if (dbstring.empty())
|
|
{
|
|
sLog->outError(LOG_FILTER_WORLDSERVER, "Character database not specified in configuration file");
|
|
return false;
|
|
}
|
|
|
|
async_threads = ConfigMgr::GetIntDefault("CharacterDatabase.WorkerThreads", 1);
|
|
if (async_threads < 1 || async_threads > 32)
|
|
{
|
|
sLog->outError(LOG_FILTER_WORLDSERVER, "Character database: invalid number of worker threads specified. "
|
|
"Please pick a value between 1 and 32.");
|
|
return false;
|
|
}
|
|
|
|
synch_threads = ConfigMgr::GetIntDefault("CharacterDatabase.SynchThreads", 2);
|
|
|
|
///- Initialise the Character database
|
|
if (!CharacterDatabase.Open(dbstring, async_threads, synch_threads))
|
|
{
|
|
sLog->outError(LOG_FILTER_WORLDSERVER, "Cannot connect to Character database %s", dbstring.c_str());
|
|
return false;
|
|
}
|
|
|
|
///- Get login database info from configuration file
|
|
dbstring = ConfigMgr::GetStringDefault("LoginDatabaseInfo", "");
|
|
if (dbstring.empty())
|
|
{
|
|
sLog->outError(LOG_FILTER_WORLDSERVER, "Login database not specified in configuration file");
|
|
return false;
|
|
}
|
|
|
|
async_threads = ConfigMgr::GetIntDefault("LoginDatabase.WorkerThreads", 1);
|
|
if (async_threads < 1 || async_threads > 32)
|
|
{
|
|
sLog->outError(LOG_FILTER_WORLDSERVER, "Login database: invalid number of worker threads specified. "
|
|
"Please pick a value between 1 and 32.");
|
|
return false;
|
|
}
|
|
|
|
synch_threads = ConfigMgr::GetIntDefault("LoginDatabase.SynchThreads", 1);
|
|
///- Initialise the login database
|
|
if (!LoginDatabase.Open(dbstring, async_threads, synch_threads))
|
|
{
|
|
sLog->outError(LOG_FILTER_WORLDSERVER, "Cannot connect to login database %s", dbstring.c_str());
|
|
return false;
|
|
}
|
|
|
|
///- Get the realm Id from the configuration file
|
|
realmID = ConfigMgr::GetIntDefault("RealmID", 0);
|
|
if (!realmID)
|
|
{
|
|
sLog->outError(LOG_FILTER_WORLDSERVER, "Realm ID not defined in configuration file");
|
|
return false;
|
|
}
|
|
sLog->outInfo(LOG_FILTER_WORLDSERVER, "Realm running as realm ID %d", realmID);
|
|
|
|
sLog->SetRealmID(realmID);
|
|
|
|
///- Clean the database before starting
|
|
ClearOnlineAccounts();
|
|
|
|
///- Insert version info into DB
|
|
WorldDatabase.PExecute("UPDATE version SET core_version = '%s', core_revision = '%s'", _FULLVERSION, _HASH); // One-time query
|
|
|
|
sWorld->LoadDBVersion();
|
|
|
|
sLog->outInfo(LOG_FILTER_WORLDSERVER, "Using World DB: %s", sWorld->GetDBVersion());
|
|
return true;
|
|
}
|
|
|
|
void Master::_StopDB()
|
|
{
|
|
CharacterDatabase.Close();
|
|
WorldDatabase.Close();
|
|
LoginDatabase.Close();
|
|
|
|
MySQL::Library_End();
|
|
}
|
|
|
|
/// Clear 'online' status for all accounts with characters in this realm
|
|
void Master::ClearOnlineAccounts()
|
|
{
|
|
// Reset online status for all accounts with characters on the current realm
|
|
LoginDatabase.DirectPExecute("UPDATE account SET online = 0 WHERE online > 0 AND id IN (SELECT acctid FROM realmcharacters WHERE realmid = %d)", realmID);
|
|
|
|
// Reset online status for all characters
|
|
CharacterDatabase.DirectExecute("UPDATE characters SET online = 0 WHERE online <> 0");
|
|
|
|
// Battleground instance ids reset at server restart
|
|
CharacterDatabase.DirectExecute("UPDATE character_battleground_data SET instanceId = 0");
|
|
}
|