diff --git a/NorthstarDLL/engine/hoststate.cpp b/NorthstarDLL/engine/hoststate.cpp index 2ad21fcf27..08a974484a 100644 --- a/NorthstarDLL/engine/hoststate.cpp +++ b/NorthstarDLL/engine/hoststate.cpp @@ -29,6 +29,14 @@ void ServerStartingOrChangingMap() { ConVar* Cvar_mp_gamemode = g_pCVar->FindVar("mp_gamemode"); + if (g_pServerPresence->IsDraining()) + { + spdlog::info("server drain complete (server is changing map/mode), quitting"); + Cbuf_AddText(Cbuf_GetCurrentPlayer(), "quit", cmd_source_t::kCommandSrcCode); + Cbuf_Execute(); + return; + } + // directly call _Cmd_Exec_f to avoid weirdness with ; being in mp_gamemode potentially // if we ran exec {mp_gamemode} and mp_gamemode contained semicolons, this could be used to execute more commands char* commandBuf[1040]; // assumedly this is the size of CCommand since we don't have an actual constructor diff --git a/NorthstarDLL/masterserver/masterserver.cpp b/NorthstarDLL/masterserver/masterserver.cpp index c2bbdfd80e..5ccfbf9d10 100644 --- a/NorthstarDLL/masterserver/masterserver.cpp +++ b/NorthstarDLL/masterserver/masterserver.cpp @@ -817,8 +817,11 @@ void MasterServerManager::ProcessConnectionlessPacketSigreq1(std::string data) if (obj.HasMember("username") && obj["username"].IsString()) username = obj["username"].GetString(); + std::string reject; - if (!g_pBanSystem->IsUIDAllowed(uid)) + if (g_pServerPresence->IsDraining()) + reject = "Server is shutting down."; + else if (!g_pBanSystem->IsUIDAllowed(uid)) reject = "Banned from this server."; std::string pdata; @@ -1356,7 +1359,7 @@ void MasterServerPresenceReporter::InternalUpdateServer(const ServerPresence* pS fmt::format( "{}/server/" "update_values?id={}&port={}&authPort=udp&name={}&description={}&map={}&playlist={}&playerCount={}&" - "maxPlayers={}&password={}", + "maxPlayers={}&isDraining={}&password={}", hostname.c_str(), serverId.c_str(), threadedPresence.m_iPort, @@ -1366,6 +1369,7 @@ void MasterServerPresenceReporter::InternalUpdateServer(const ServerPresence* pS playlistEscaped, threadedPresence.m_iPlayerCount, threadedPresence.m_iMaxPlayers, + threadedPresence.m_bIsDraining, passwordEscaped) .c_str()); diff --git a/NorthstarDLL/server/auth/serverauthentication.cpp b/NorthstarDLL/server/auth/serverauthentication.cpp index d5653dccff..1bd2e79cab 100644 --- a/NorthstarDLL/server/auth/serverauthentication.cpp +++ b/NorthstarDLL/server/auth/serverauthentication.cpp @@ -248,7 +248,9 @@ bool,, (R2::CBaseClient* self, char* pName, void* pNetChannel, char bFakePlayer, if (!bFakePlayer) { - if (!g_pServerAuthentication->VerifyPlayerName(pNextPlayerToken, pName, pVerifiedName)) + if (g_pServerPresence->IsDraining()) + pAuthenticationFailure = "Server is shutting down."; + else if (!g_pServerAuthentication->VerifyPlayerName(pNextPlayerToken, pName, pVerifiedName)) pAuthenticationFailure = "Invalid Name."; else if (!g_pBanSystem->IsUIDAllowed(iNextPlayerUid)) pAuthenticationFailure = "Banned From server."; @@ -328,6 +330,13 @@ void,, (R2::CBaseClient* self, uint32_t unknownButAlways1, const char* pReason, g_pServerPresence->SetPlayerCount(g_pServerAuthentication->m_PlayerAuthenticationData.size()); + if (g_pServerPresence->IsDraining() && !g_pServerAuthentication->m_PlayerAuthenticationData.size()) + { + spdlog::info("server drain complete (server is empty), quitting"); + R2::Cbuf_AddText(R2::Cbuf_GetCurrentPlayer(), "quit", R2::cmd_source_t::kCommandSrcCode); + R2::Cbuf_Execute(); + } + _CBaseClient__Disconnect(self, unknownButAlways1, buf); } diff --git a/NorthstarDLL/server/serverpresence.cpp b/NorthstarDLL/server/serverpresence.cpp index ed9185c10d..b0107df223 100644 --- a/NorthstarDLL/server/serverpresence.cpp +++ b/NorthstarDLL/server/serverpresence.cpp @@ -221,8 +221,28 @@ void ServerPresenceManager::SetPlayerCount(const int iPlayerCount) m_ServerPresence.m_iPlayerCount = iPlayerCount; } +void ServerPresenceManager::SetIsDraining(bool bIsDraining) +{ + m_ServerPresence.m_bIsDraining = bIsDraining; +} + +bool ServerPresenceManager::IsDraining() const +{ + // TODO: is there a better place for this? + return m_ServerPresence.m_bIsDraining; +} + ON_DLL_LOAD_RELIESON("engine.dll", ServerPresence, ConVar, (CModule module)) { g_pServerPresence->CreateConVars(); Cvar_hostname = module.Offset(0x1315BAE8).Deref().RCast(); } + +ADD_SQFUNC("void", NSDrainServer, "bool drain, int timeout", "", ScriptContext::SERVER) { + bool drain = g_pSquirrel->getbool(sqvm, 1); + int timeout = g_pSquirrel->getinteger(sqvm, 2); + g_pServerPresence->SetIsDraining(drain); + // TODO: notify the connected clients when this is set? + // TODO: auto exit after a timeout or match end or player count reaches zero (use the quit command for a graceful exit) + return SQRESULT_NULL; +} diff --git a/NorthstarDLL/server/serverpresence.h b/NorthstarDLL/server/serverpresence.h index 3aabecdeb8..6e03b0cbc8 100644 --- a/NorthstarDLL/server/serverpresence.h +++ b/NorthstarDLL/server/serverpresence.h @@ -19,6 +19,8 @@ struct ServerPresence int m_iPlayerCount; int m_iMaxPlayers; + bool m_bIsDraining; + ServerPresence() { memset(this, 0, sizeof(this)); @@ -39,6 +41,8 @@ struct ServerPresence m_iPlayerCount = obj->m_iPlayerCount; m_iMaxPlayers = obj->m_iMaxPlayers; + + m_bIsDraining = obj->m_bIsDraining; } }; @@ -72,6 +76,7 @@ class ServerPresenceManager ConVar* Cvar_ns_report_sp_server_to_masterserver; public: + void AddPresenceReporter(ServerPresenceReporter* reporter); void CreateConVars(); @@ -89,6 +94,9 @@ class ServerPresenceManager void SetMap(const char* pMapName, bool isInitialising = false); void SetPlaylist(const char* pPlaylistName); void SetPlayerCount(const int iPlayerCount); + + void SetIsDraining(bool bIsDraining); + bool IsDraining() const; }; extern ServerPresenceManager* g_pServerPresence;