mirror of
https://github.com/Mauler125/r5sdk.git
synced 2025-02-09 19:15:03 +01:00
* All libraries have been isolated from each other, and build into separate artifacts. * Project has been restructured to support isolating libraries. * CCrashHandler now calls a callback on crash (setup from core/dllmain.cpp, this can be setup in any way for any project. This callback is getting called when the apllication crashes. Useful for flushing buffers before closing handles to logging files for example). * Tier0 'CoreMsgV' function now calls a callback sink, which could be set by the user (currently setup to the SDK's internal logger in core/dllmain.cpp). TODO: * Add a batch file to autogenerate all projects. * Add support for dedicated server. * Add support for client dll. Bugs: * Game crashes on the title screen after the UI script compiler has finished (root cause unknown). * Curl error messages are getting logged twice for the dedicated server due to the removal of all "DEDICATED" preprocessor directives to support isolating projects. This has to be fixed properly!
480 lines
15 KiB
C++
480 lines
15 KiB
C++
//=============================================================================//
|
|
//
|
|
// Purpose: Runs the state machine for the host & server.
|
|
//
|
|
//=============================================================================//
|
|
// host_state.cpp:.
|
|
//
|
|
/////////////////////////////////////////////////////////////////////////////////
|
|
#include "core/stdafx.h"
|
|
#include "tier0/jobthread.h"
|
|
#include "tier0/commandline.h"
|
|
#include "tier0/fasttimer.h"
|
|
#include "tier1/cvar.h"
|
|
#include "tier1/NetAdr.h"
|
|
#include "tier2/socketcreator.h"
|
|
#include "vpc/keyvalues.h"
|
|
#include "datacache/mdlcache.h"
|
|
#ifndef CLIENT_DLL
|
|
#include "engine/server/sv_rcon.h"
|
|
#endif // !CLIENT_DLL
|
|
#ifndef DEDICATED
|
|
#include "engine/client/cl_rcon.h"
|
|
#include "engine/client/cl_main.h"
|
|
#include "engine/client/clientstate.h"
|
|
#endif // DEDICATED
|
|
#include "engine/cmd.h"
|
|
#include "engine/net.h"
|
|
#include "engine/gl_screen.h"
|
|
#include "engine/host.h"
|
|
#include "engine/host_cmd.h"
|
|
#include "engine/host_state.h"
|
|
#include "engine/sys_engine.h"
|
|
#include "engine/modelloader.h"
|
|
#include "engine/cmodel_bsp.h"
|
|
#ifndef CLIENT_DLL
|
|
#include "engine/server/server.h"
|
|
#endif // !CLIENT_DLL
|
|
#include "rtech/rtech_game.h"
|
|
#include "rtech/rtech_utils.h"
|
|
#include "rtech/stryder/stryder.h"
|
|
#ifndef DEDICATED
|
|
#include "vgui/vgui_baseui_interface.h"
|
|
#include "client/vengineclient_impl.h"
|
|
#endif // DEDICATED
|
|
#include "networksystem/pylon.h"
|
|
#ifndef CLIENT_DLL
|
|
#include "networksystem/bansystem.h"
|
|
#endif // !CLIENT_DLL
|
|
#include "networksystem/listmanager.h"
|
|
#include "public/edict.h"
|
|
#ifndef CLIENT_DLL
|
|
#include "game/server/gameinterface.h"
|
|
#endif // !CLIENT_DLL
|
|
#include "game/shared/vscript_shared.h"
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: state machine's main processing loop
|
|
//-----------------------------------------------------------------------------
|
|
void CHostState::FrameUpdate(CHostState* pHostState, double flCurrentTime, float flFrameTime)
|
|
{
|
|
static bool bInitialized = false;
|
|
static bool bResetIdleName = false;
|
|
if (!bInitialized)
|
|
{
|
|
g_pHostState->Setup();
|
|
bInitialized = true;
|
|
}
|
|
|
|
g_pHostState->Think();
|
|
#ifndef CLIENT_DLL
|
|
RCONServer()->RunFrame();
|
|
#endif // !CLIENT_DLL
|
|
#ifndef DEDICATED
|
|
RCONClient()->RunFrame();
|
|
#endif // !DEDICATED
|
|
|
|
HostStates_t oldState{};
|
|
|
|
// Disable "warning C4611: interaction between '_setjmp' and C++ object destruction is non-portable"
|
|
#pragma warning(push)
|
|
#pragma warning(disable : 4611)
|
|
if (setjmp(*host_abortserver))
|
|
{
|
|
g_pHostState->Init();
|
|
return;
|
|
}
|
|
#pragma warning(pop)
|
|
else
|
|
{
|
|
#ifndef CLIENT_DLL
|
|
*g_bAbortServerSet = true;
|
|
#endif // !CLIENT_DLL
|
|
do
|
|
{
|
|
Cbuf_Execute();
|
|
oldState = g_pHostState->m_iCurrentState;
|
|
|
|
switch (g_pHostState->m_iCurrentState)
|
|
{
|
|
case HostStates_t::HS_NEW_GAME:
|
|
{
|
|
g_pHostState->State_NewGame();
|
|
break;
|
|
}
|
|
case HostStates_t::HS_CHANGE_LEVEL_SP:
|
|
{
|
|
g_pHostState->State_ChangeLevelSP();
|
|
break;
|
|
}
|
|
case HostStates_t::HS_CHANGE_LEVEL_MP:
|
|
{
|
|
g_pHostState->State_ChangeLevelMP();
|
|
break;
|
|
}
|
|
case HostStates_t::HS_RUN:
|
|
{
|
|
if (!g_pHostState->m_bActiveGame)
|
|
{
|
|
if (bResetIdleName)
|
|
{
|
|
g_pHostState->ResetLevelName();
|
|
bResetIdleName = false;
|
|
}
|
|
}
|
|
else // Reset idle name the next non-active frame.
|
|
{
|
|
bResetIdleName = true;
|
|
}
|
|
|
|
CHostState_State_Run(&g_pHostState->m_iCurrentState, flCurrentTime, flFrameTime);
|
|
break;
|
|
}
|
|
case HostStates_t::HS_GAME_SHUTDOWN:
|
|
{
|
|
DevMsg(eDLL_T::ENGINE, "%s: Shutdown host game\n", __FUNCTION__);
|
|
CHostState_State_GameShutDown(g_pHostState);
|
|
break;
|
|
}
|
|
case HostStates_t::HS_RESTART:
|
|
{
|
|
DevMsg(eDLL_T::ENGINE, "%s: Restarting state machine\n", __FUNCTION__);
|
|
#ifndef DEDICATED
|
|
CL_EndMovie();
|
|
#endif // !DEDICATED
|
|
Stryder_SendOfflineRequest(); // We have hostnames nulled anyway.
|
|
g_pEngine->SetNextState(IEngine::DLL_RESTART);
|
|
break;
|
|
}
|
|
case HostStates_t::HS_SHUTDOWN:
|
|
{
|
|
DevMsg(eDLL_T::ENGINE, "%s: Shutdown state machine\n", __FUNCTION__);
|
|
#ifndef DEDICATED
|
|
CL_EndMovie();
|
|
#endif // !DEDICATED
|
|
Stryder_SendOfflineRequest(); // We have hostnames nulled anyway.
|
|
g_pEngine->SetNextState(IEngine::DLL_CLOSE);
|
|
break;
|
|
}
|
|
default:
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
} while ((oldState != HostStates_t::HS_RUN || (g_pHostState->m_iNextState == HostStates_t::HS_LOAD_GAME && single_frame_shutdown_for_reload->GetBool()))
|
|
&& oldState != HostStates_t::HS_SHUTDOWN
|
|
&& oldState != HostStates_t::HS_RESTART);
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: state machine initialization
|
|
//-----------------------------------------------------------------------------
|
|
void CHostState::Init(void)
|
|
{
|
|
if (m_iNextState != HostStates_t::HS_SHUTDOWN)
|
|
{
|
|
if (m_iNextState == HostStates_t::HS_GAME_SHUTDOWN)
|
|
{
|
|
CHostState_State_GameShutDown(this);
|
|
}
|
|
else
|
|
{
|
|
m_iCurrentState = HostStates_t::HS_RUN;
|
|
if (m_iNextState != HostStates_t::HS_SHUTDOWN || !single_frame_shutdown_for_reload->GetInt())
|
|
m_iNextState = HostStates_t::HS_RUN;
|
|
}
|
|
}
|
|
m_flShortFrameTime = 1.0f;
|
|
m_bActiveGame = false;
|
|
m_bRememberLocation = false;
|
|
m_bBackgroundLevel = false;
|
|
m_bWaitingForConnection = false;
|
|
m_levelName[0] = 0;
|
|
m_landMarkName[0] = 0;
|
|
m_mapGroupName[0] = 0;
|
|
m_bSplitScreenConnect = false;
|
|
m_bGameHasShutDownAndFlushedMemory = true;
|
|
m_vecLocation.Init();
|
|
m_angLocation.Init();
|
|
m_iServerState = HostStates_t::HS_NEW_GAME;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: state machine setup
|
|
//-----------------------------------------------------------------------------
|
|
void CHostState::Setup(void)
|
|
{
|
|
g_pHostState->LoadConfig();
|
|
#ifndef CLIENT_DLL
|
|
g_pBanSystem->Load();
|
|
#endif // !CLIENT_DLL
|
|
ConVar_PurgeHostNames();
|
|
|
|
#ifndef CLIENT_DLL
|
|
RCONServer()->Init();
|
|
#endif // !CLIENT_DLL
|
|
#ifndef DEDICATED
|
|
RCONClient()->Init();
|
|
#endif // !DEDICATED
|
|
|
|
if (net_useRandomKey->GetBool())
|
|
{
|
|
NET_GenerateKey();
|
|
}
|
|
#if !defined (DEDICATED) && !defined (CLIENT_DLL)
|
|
// Parallel processing of 'C_BaseAnimating::SetupBones()' is currently
|
|
// not supported on listen servers running the local client due to an
|
|
// engine bug specific to S3 that still needs to be addressed. Remove
|
|
// this once the issue has been solved:
|
|
if (cl_threaded_bone_setup->GetBool())
|
|
{
|
|
cl_threaded_bone_setup->SetValue(false);
|
|
}
|
|
#endif // !DEDICATED && !CLIENT_DLL
|
|
|
|
ResetLevelName();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: think
|
|
//-----------------------------------------------------------------------------
|
|
void CHostState::Think(void) const
|
|
{
|
|
#ifndef CLIENT_DLL
|
|
static bool bInitialized = false;
|
|
static CFastTimer statsTimer;
|
|
static CFastTimer banListTimer;
|
|
#ifdef DEDICATED
|
|
static CFastTimer pylonTimer;
|
|
#endif // DEDICATED
|
|
|
|
if (!bInitialized) // Initialize clocks.
|
|
{
|
|
statsTimer.Start();
|
|
banListTimer.Start();
|
|
#ifdef DEDICATED
|
|
pylonTimer.Start();
|
|
#endif // DEDICATED
|
|
bInitialized = true;
|
|
}
|
|
if (sv_autoReloadRate->GetBool())
|
|
{
|
|
if (g_ServerGlobalVariables->m_flCurTime > sv_autoReloadRate->GetDouble())
|
|
{
|
|
Cbuf_AddText(Cbuf_GetCurrentPlayer(), "reload\n", cmd_source_t::kCommandSrcCode);
|
|
}
|
|
}
|
|
if (statsTimer.GetDurationInProgress().GetSeconds() > sv_statusRefreshRate->GetDouble())
|
|
{
|
|
SetConsoleTitleA(Format("%s - %d/%d Players (%s on %s)",
|
|
hostname->GetString(), g_pServer->GetNumClients(),
|
|
g_ServerGlobalVariables->m_nMaxClients, KeyValues_GetCurrentPlaylist(), m_levelName).c_str());
|
|
|
|
statsTimer.Start();
|
|
}
|
|
if (sv_globalBanlist->GetBool() &&
|
|
banListTimer.GetDurationInProgress().GetSeconds() > sv_banlistRefreshRate->GetDouble())
|
|
{
|
|
SV_CheckForBan();
|
|
banListTimer.Start();
|
|
}
|
|
#ifdef DEDICATED
|
|
if (pylonTimer.GetDurationInProgress().GetSeconds() > sv_pylonRefreshRate->GetDouble())
|
|
{
|
|
const NetGameServer_t netGameServer
|
|
{
|
|
hostname->GetString(),
|
|
hostdesc->GetString(),
|
|
sv_pylonVisibility->GetInt() == EServerVisibility_t::HIDDEN,
|
|
g_pHostState->m_levelName,
|
|
mp_gamemode->GetString(),
|
|
hostip->GetString(),
|
|
hostport->GetString(),
|
|
g_pNetKey->GetBase64NetKey(),
|
|
std::to_string(*g_nServerRemoteChecksum),
|
|
SDK_VERSION,
|
|
std::to_string(g_pServer->GetNumClients()),
|
|
std::to_string(g_ServerGlobalVariables->m_nMaxClients),
|
|
std::chrono::duration_cast<std::chrono::milliseconds>(
|
|
std::chrono::system_clock::now().time_since_epoch()
|
|
).count()
|
|
};
|
|
|
|
std::thread(&CPylon::KeepAlive, g_pMasterServer, netGameServer).detach();
|
|
pylonTimer.Start();
|
|
}
|
|
#endif // DEDICATED
|
|
#endif // !CLIENT_DLL
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: load and execute configuration files
|
|
//-----------------------------------------------------------------------------
|
|
void CHostState::LoadConfig(void) const
|
|
{
|
|
if (CommandLine()->ParmValue("-launcher", 0) < 1) // Launcher level 1 indicates everything is handled from the commandline/launcher.
|
|
{
|
|
if (!CommandLine()->CheckParm("-devsdk"))
|
|
{
|
|
Cbuf_AddText(Cbuf_GetCurrentPlayer(), "exec \"autoexec.cfg\"", cmd_source_t::kCommandSrcCode);
|
|
#ifndef CLIENT_DLL
|
|
Cbuf_AddText(Cbuf_GetCurrentPlayer(), "exec \"autoexec_server.cfg\"", cmd_source_t::kCommandSrcCode);
|
|
Cbuf_AddText(Cbuf_GetCurrentPlayer(), "exec \"rcon_server.cfg\"", cmd_source_t::kCommandSrcCode);
|
|
#endif //!CLIENT_DLL
|
|
#ifndef DEDICATED
|
|
Cbuf_AddText(Cbuf_GetCurrentPlayer(), "exec \"autoexec_client.cfg\"", cmd_source_t::kCommandSrcCode);
|
|
Cbuf_AddText(Cbuf_GetCurrentPlayer(), "exec \"rcon_client.cfg\"", cmd_source_t::kCommandSrcCode);
|
|
#endif // !DEDICATED
|
|
}
|
|
else // Development configs.
|
|
{
|
|
Cbuf_AddText(Cbuf_GetCurrentPlayer(), "exec \"autoexec_dev.cfg\"", cmd_source_t::kCommandSrcCode);
|
|
#ifndef CLIENT_DLL
|
|
Cbuf_AddText(Cbuf_GetCurrentPlayer(), "exec \"autoexec_server_dev.cfg\"", cmd_source_t::kCommandSrcCode);
|
|
Cbuf_AddText(Cbuf_GetCurrentPlayer(), "exec \"rcon_server_dev.cfg\"", cmd_source_t::kCommandSrcCode);
|
|
#endif //!CLIENT_DLL
|
|
#ifndef DEDICATED
|
|
Cbuf_AddText(Cbuf_GetCurrentPlayer(), "exec \"autoexec_client_dev.cfg\"", cmd_source_t::kCommandSrcCode);
|
|
Cbuf_AddText(Cbuf_GetCurrentPlayer(), "exec \"rcon_client_dev.cfg\"", cmd_source_t::kCommandSrcCode);
|
|
#endif // !DEDICATED
|
|
}
|
|
#ifndef DEDICATED
|
|
Cbuf_AddText(Cbuf_GetCurrentPlayer(), "exec \"bind.cfg\"", cmd_source_t::kCommandSrcCode);
|
|
#endif // !DEDICATED
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: shutdown active game
|
|
//-----------------------------------------------------------------------------
|
|
void CHostState::GameShutDown(void)
|
|
{
|
|
if (m_bActiveGame)
|
|
{
|
|
#ifndef CLIENT_DLL
|
|
g_pServerGameDLL->GameShutdown();
|
|
#endif // !CLIENT_DLL
|
|
m_bActiveGame = false;
|
|
ResetLevelName();
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: initialize new game
|
|
//-----------------------------------------------------------------------------
|
|
void CHostState::State_NewGame(void)
|
|
{
|
|
DevMsg(eDLL_T::ENGINE, "%s: Loading level: '%s'\n", __FUNCTION__, g_pHostState->m_levelName);
|
|
|
|
LARGE_INTEGER time{};
|
|
bool bSplitScreenConnect = m_bSplitScreenConnect;
|
|
m_bSplitScreenConnect = 0;
|
|
#ifndef CLIENT_DLL
|
|
if (!g_pServerGameClients) // Init Game if it ain't valid.
|
|
{
|
|
SV_InitGameDLL();
|
|
}
|
|
#endif // !CLIENT_DLL
|
|
|
|
#ifndef CLIENT_DLL
|
|
if (!CModelLoader__Map_IsValid(g_pModelLoader, m_levelName) // Check if map is valid and if we can start a new game.
|
|
|| !v_Host_NewGame(m_levelName, nullptr, m_bBackgroundLevel, bSplitScreenConnect, time) || !g_pServerGameClients)
|
|
{
|
|
Error(eDLL_T::ENGINE, NO_ERROR, "%s: Level not valid\n", __FUNCTION__);
|
|
#ifndef DEDICATED
|
|
SCR_EndLoadingPlaque();
|
|
#endif // !DEDICATED
|
|
GameShutDown();
|
|
}
|
|
#endif // !CLIENT_DLL
|
|
|
|
m_iCurrentState = HostStates_t::HS_RUN; // Set current state to run.
|
|
|
|
// If our next state isn't a shutdown or its a forced shutdown then set next state to run.
|
|
if (m_iNextState != HostStates_t::HS_SHUTDOWN || !host_hasIrreversibleShutdown->GetBool())
|
|
{
|
|
m_iNextState = HostStates_t::HS_RUN;
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: change singleplayer level
|
|
//-----------------------------------------------------------------------------
|
|
void CHostState::State_ChangeLevelSP(void)
|
|
{
|
|
DevMsg(eDLL_T::ENGINE, "%s: Changing singleplayer level to: '%s'\n", __FUNCTION__, m_levelName);
|
|
m_flShortFrameTime = 1.5; // Set frame time.
|
|
|
|
if (CModelLoader__Map_IsValid(g_pModelLoader, m_levelName)) // Check if map is valid and if we can start a new game.
|
|
{
|
|
v_Host_ChangeLevel(true, m_levelName, m_mapGroupName); // Call change level as singleplayer level.
|
|
}
|
|
else
|
|
{
|
|
Error(eDLL_T::ENGINE, NO_ERROR, "%s: Unable to find level: '%s'\n", __FUNCTION__, m_levelName);
|
|
}
|
|
|
|
m_iCurrentState = HostStates_t::HS_RUN; // Set current state to run.
|
|
|
|
// If our next state isn't a shutdown or its a forced shutdown then set next state to run.
|
|
if (m_iNextState != HostStates_t::HS_SHUTDOWN || !host_hasIrreversibleShutdown->GetBool())
|
|
{
|
|
m_iNextState = HostStates_t::HS_RUN;
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: change multiplayer level
|
|
//-----------------------------------------------------------------------------
|
|
void CHostState::State_ChangeLevelMP(void)
|
|
{
|
|
DevMsg(eDLL_T::ENGINE, "%s: Changing multiplayer level to: '%s'\n", __FUNCTION__, m_levelName);
|
|
m_flShortFrameTime = 0.5; // Set frame time.
|
|
|
|
#ifndef CLIENT_DLL
|
|
g_pServerGameDLL->LevelShutdown();
|
|
#endif // !CLIENT_DLL
|
|
if (CModelLoader__Map_IsValid(g_pModelLoader, m_levelName)) // Check if map is valid and if we can start a new game.
|
|
{
|
|
#ifndef DEDICATED
|
|
g_pEngineVGui->EnabledProgressBarForNextLoad();
|
|
#endif // !DEDICATED
|
|
v_Host_ChangeLevel(false, m_levelName, m_mapGroupName); // Call change level as multiplayer level.
|
|
}
|
|
else
|
|
{
|
|
Error(eDLL_T::ENGINE, NO_ERROR, "%s: Unable to find level: '%s'\n", __FUNCTION__, m_levelName);
|
|
}
|
|
|
|
m_iCurrentState = HostStates_t::HS_RUN; // Set current state to run.
|
|
|
|
// If our next state isn't a shutdown or its a forced shutdown then set next state to run.
|
|
if (m_iNextState != HostStates_t::HS_SHUTDOWN || !host_hasIrreversibleShutdown->GetBool())
|
|
{
|
|
m_iNextState = HostStates_t::HS_RUN;
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: resets the level name
|
|
//-----------------------------------------------------------------------------
|
|
void CHostState::ResetLevelName(void)
|
|
{
|
|
static const char* szNoMap = "no_map";
|
|
Q_snprintf(const_cast<char*>(m_levelName), sizeof(m_levelName), "%s", szNoMap);
|
|
}
|
|
|
|
void VHostState::Attach(void) const
|
|
{
|
|
DetourAttach(&CHostState_FrameUpdate, &CHostState::FrameUpdate);
|
|
}
|
|
void VHostState::Detach(void) const
|
|
{
|
|
DetourDetach(&CHostState_FrameUpdate, &CHostState::FrameUpdate);
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
CHostState* g_pHostState = nullptr;
|