summaryrefslogtreecommitdiff
path: root/engine/host_state.cpp
diff options
context:
space:
mode:
authorFluorescentCIAAfricanAmerican <[email protected]>2020-04-22 12:56:21 -0400
committerFluorescentCIAAfricanAmerican <[email protected]>2020-04-22 12:56:21 -0400
commit3bf9df6b2785fa6d951086978a3e66f49427166a (patch)
tree2c0f1f0c63c4832882bc93814ebd2c2b1c6224e5 /engine/host_state.cpp
downloadarchived-source-engine-2018-hl2-src-3bf9df6b2785fa6d951086978a3e66f49427166a.tar.xz
archived-source-engine-2018-hl2-src-3bf9df6b2785fa6d951086978a3e66f49427166a.zip
Diffstat (limited to 'engine/host_state.cpp')
-rw-r--r--engine/host_state.cpp803
1 files changed, 803 insertions, 0 deletions
diff --git a/engine/host_state.cpp b/engine/host_state.cpp
new file mode 100644
index 0000000..edcb922
--- /dev/null
+++ b/engine/host_state.cpp
@@ -0,0 +1,803 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose: Runs the state machine for the host & server
+//
+// $NoKeywords: $
+//=============================================================================//
+
+
+#include "host_state.h"
+#include "eiface.h"
+#include "quakedef.h"
+#include "server.h"
+#include "sv_main.h"
+#include "host_cmd.h"
+#include "host.h"
+#include "screen.h"
+#include "icliententity.h"
+#include "icliententitylist.h"
+#include "client.h"
+#include "host_jmp.h"
+#include "tier0/vprof.h"
+#include "tier0/icommandline.h"
+#include "filesystem_engine.h"
+#include "zone.h"
+#include "iengine.h"
+#include "snd_audio_source.h"
+#include "sv_steamauth.h"
+#ifndef SWDS
+#include "vgui_baseui_interface.h"
+#endif
+#include "sv_plugin.h"
+#include "cl_main.h"
+#include "sv_steamauth.h"
+#include "datacache/imdlcache.h"
+#include "sys_dll.h"
+#include "testscriptmgr.h"
+#if defined( REPLAY_ENABLED )
+#include "replay_internal.h"
+#include "replayserver.h"
+#endif
+#include "GameEventManager.h"
+#include "tier0/etwprof.h"
+
+#include "ccs.h"
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include "tier0/memdbgon.h"
+
+extern bool g_bAbortServerSet;
+#ifndef SWDS
+extern ConVar reload_materials;
+#endif
+
+typedef enum
+{
+ HS_NEW_GAME = 0,
+ HS_LOAD_GAME,
+ HS_CHANGE_LEVEL_SP,
+ HS_CHANGE_LEVEL_MP,
+ HS_RUN,
+ HS_GAME_SHUTDOWN,
+ HS_SHUTDOWN,
+ HS_RESTART,
+} HOSTSTATES;
+
+// a little class that manages the state machine for the host
+class CHostState
+{
+public:
+ CHostState();
+ void Init();
+ void FrameUpdate( float time );
+ void SetNextState( HOSTSTATES nextState );
+
+ void RunGameInit();
+
+ void SetState( HOSTSTATES newState, bool clearNext );
+ void GameShutdown();
+
+ void State_NewGame();
+ void State_LoadGame();
+ void State_ChangeLevelMP();
+ void State_ChangeLevelSP();
+ void State_Run( float time );
+ void State_GameShutdown();
+ void State_Shutdown();
+ void State_Restart();
+
+ bool IsGameShuttingDown();
+
+ void RememberLocation();
+ void OnClientConnected(); // Client fully connected
+ void OnClientDisconnected(); // Client disconnected
+
+ HOSTSTATES m_currentState;
+ HOSTSTATES m_nextState;
+ Vector m_vecLocation;
+ QAngle m_angLocation;
+ char m_levelName[256];
+ char m_landmarkName[256];
+ char m_saveName[256];
+ float m_flShortFrameTime; // run a few one-tick frames to avoid large timesteps while loading assets
+
+ bool m_activeGame;
+ bool m_bRememberLocation;
+ bool m_bBackgroundLevel;
+ bool m_bWaitingForConnection;
+};
+
+static bool Host_ValidGame( void );
+static CHostState g_HostState;
+
+
+//-----------------------------------------------------------------------------
+// external API for manipulating the host state machine
+//-----------------------------------------------------------------------------
+void HostState_Init()
+{
+ g_HostState.Init();
+}
+
+void HostState_Frame( float time )
+{
+ g_HostState.FrameUpdate( time );
+}
+
+void HostState_RunGameInit()
+{
+ g_HostState.RunGameInit();
+ g_ServerGlobalVariables.bMapLoadFailed = false;
+}
+
+//-----------------------------------------------------------------------------
+// start a new game as soon as possible
+//-----------------------------------------------------------------------------
+void HostState_NewGame( char const *pMapName, bool remember_location, bool background )
+{
+ Q_strncpy( g_HostState.m_levelName, pMapName, sizeof( g_HostState.m_levelName ) );
+
+ g_HostState.m_landmarkName[0] = 0;
+ g_HostState.m_bRememberLocation = remember_location;
+ g_HostState.m_bWaitingForConnection = true;
+ g_HostState.m_bBackgroundLevel = background;
+ if ( remember_location )
+ {
+ g_HostState.RememberLocation();
+ }
+ g_HostState.SetNextState( HS_NEW_GAME );
+}
+
+//-----------------------------------------------------------------------------
+// load a new game as soon as possible
+//-----------------------------------------------------------------------------
+void HostState_LoadGame( char const *pSaveFileName, bool remember_location )
+{
+#ifndef SWDS
+ // Make sure the freaking save file exists....
+ if ( !saverestore->SaveFileExists( pSaveFileName ) )
+ {
+ Warning("Save file %s can't be found!\n", pSaveFileName );
+ SCR_EndLoadingPlaque();
+ return;
+ }
+
+ Q_strncpy( g_HostState.m_saveName, pSaveFileName, sizeof( g_HostState.m_saveName ) );
+
+ // Tell the game .dll we are loading another game
+ serverGameDLL->PreSaveGameLoaded( pSaveFileName, sv.IsActive() );
+
+ g_HostState.m_bRememberLocation = remember_location;
+ g_HostState.m_bBackgroundLevel = false;
+ g_HostState.m_bWaitingForConnection = true;
+ if ( remember_location )
+ {
+ g_HostState.RememberLocation();
+ }
+
+ g_HostState.SetNextState( HS_LOAD_GAME );
+#endif
+}
+
+// change level (single player style - smooth transition)
+void HostState_ChangeLevelSP( char const *pNewLevel, char const *pLandmarkName )
+{
+ Q_strncpy( g_HostState.m_levelName, pNewLevel, sizeof( g_HostState.m_levelName ) );
+ Q_strncpy( g_HostState.m_landmarkName, pLandmarkName, sizeof( g_HostState.m_landmarkName ) );
+ g_HostState.SetNextState( HS_CHANGE_LEVEL_SP );
+}
+
+// change level (multiplayer style - respawn all connected clients)
+void HostState_ChangeLevelMP( char const *pNewLevel, char const *pLandmarkName )
+{
+ Steam3Server().NotifyOfLevelChange();
+
+ Q_strncpy( g_HostState.m_levelName, pNewLevel, sizeof( g_HostState.m_levelName ) );
+ Q_strncpy( g_HostState.m_landmarkName, pLandmarkName, sizeof( g_HostState.m_landmarkName ) );
+ g_HostState.SetNextState( HS_CHANGE_LEVEL_MP );
+}
+
+// shutdown the game as soon as possible
+void HostState_GameShutdown()
+{
+ Steam3Server().NotifyOfLevelChange();
+
+ // This will get called during shutdown, ignore it.
+ if ( g_HostState.m_currentState != HS_SHUTDOWN &&
+ g_HostState.m_currentState != HS_RESTART &&
+ g_HostState.m_currentState != HS_GAME_SHUTDOWN
+ )
+ {
+ g_HostState.SetNextState( HS_GAME_SHUTDOWN );
+ }
+}
+
+// shutdown the engine/program as soon as possible
+void HostState_Shutdown()
+{
+#if defined( REPLAY_ENABLED )
+ // If we're recording the game, finalize the replay so players can download it.
+ if ( sv.IsDedicated() && g_pReplay && g_pReplay->IsRecording() )
+ {
+ // Stop recording and publish all blocks/session info data synchronously.
+ g_pReplay->SV_EndRecordingSession( true );
+ }
+#endif
+
+ g_HostState.SetNextState( HS_SHUTDOWN );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Restart the engine
+//-----------------------------------------------------------------------------
+void HostState_Restart()
+{
+ g_HostState.SetNextState( HS_RESTART );
+}
+
+bool HostState_IsGameShuttingDown()
+{
+ return g_HostState.IsGameShuttingDown();
+}
+
+bool HostState_IsShuttingDown()
+{
+ return ( g_HostState.m_currentState == HS_SHUTDOWN ||
+ g_HostState.m_currentState == HS_RESTART ||
+ g_HostState.m_currentState == HS_GAME_SHUTDOWN );
+}
+
+
+void HostState_OnClientConnected()
+{
+ g_HostState.OnClientConnected();
+}
+
+void HostState_OnClientDisconnected()
+{
+ g_HostState.OnClientDisconnected();
+}
+
+void HostState_SetSpawnPoint(Vector &position, QAngle &angle)
+{
+ g_HostState.m_angLocation = angle;
+ g_HostState.m_vecLocation = position;
+ g_HostState.m_bRememberLocation = true;
+}
+
+//-----------------------------------------------------------------------------
+static void WatchDogHandler()
+{
+ Host_Error( "WatchdogHandler called - server exiting.\n" );
+}
+
+//-----------------------------------------------------------------------------
+// Class implementation
+//-----------------------------------------------------------------------------
+
+CHostState::CHostState()
+{
+}
+
+void CHostState::Init()
+{
+ SetState( HS_RUN, true );
+ m_currentState = HS_RUN;
+ m_nextState = HS_RUN;
+ m_activeGame = false;
+ m_levelName[0] = 0;
+ m_saveName[0] = 0;
+ m_landmarkName[0] = 0;
+ m_bRememberLocation = 0;
+ m_bBackgroundLevel = false;
+ m_vecLocation.Init();
+ m_angLocation.Init();
+ m_bWaitingForConnection = false;
+ m_flShortFrameTime = 1.0;
+
+ CCS_Init();
+
+ Plat_SetWatchdogHandlerFunction( WatchDogHandler );
+}
+
+void CHostState::SetState( HOSTSTATES newState, bool clearNext )
+{
+ m_currentState = newState;
+ if ( clearNext )
+ {
+ m_nextState = newState;
+ }
+}
+
+void CHostState::SetNextState( HOSTSTATES next )
+{
+ Assert( m_currentState == HS_RUN );
+ m_nextState = next;
+}
+
+void CHostState::RunGameInit()
+{
+ Assert( !m_activeGame );
+
+ if ( serverGameDLL )
+ {
+ serverGameDLL->GameInit();
+ }
+ m_activeGame = true;
+}
+
+void CHostState::GameShutdown()
+{
+ if ( m_activeGame )
+ {
+ serverGameDLL->GameShutdown();
+ m_activeGame = false;
+ }
+}
+
+
+// These State_ functions execute that state's code right away
+// The external API queues up state changes to happen when the state machine is processed.
+void CHostState::State_NewGame()
+{
+ CETWScope timer( "CHostState::State_NewGame" );
+
+ if ( Host_ValidGame() )
+ {
+ // Demand load game .dll if running with -nogamedll flag, etc.
+ if ( !serverGameClients )
+ {
+ SV_InitGameDLL();
+ }
+
+ if ( !serverGameClients )
+ {
+ Warning( "Can't start game, no valid server.dll loaded\n" );
+ }
+ else
+ {
+ if ( Host_NewGame( m_levelName, false, m_bBackgroundLevel ) )
+ {
+ // succesfully started the new game
+ SetState( HS_RUN, true );
+ return;
+ }
+ }
+ }
+
+ SCR_EndLoadingPlaque();
+
+ // new game failed
+ GameShutdown();
+ // run the server at the console
+ SetState( HS_RUN, true );
+}
+
+void CHostState::State_LoadGame()
+{
+#ifndef SWDS
+ HostState_RunGameInit();
+
+ if ( saverestore->LoadGame( m_saveName ) )
+ {
+ // succesfully started the new game
+ GetTestScriptMgr()->CheckPoint( "load_game" );
+ SetState( HS_RUN, true );
+ return;
+ }
+#endif
+
+ SCR_EndLoadingPlaque();
+
+ // load game failed
+ GameShutdown();
+ // run the server at the console
+ SetState( HS_RUN, true );
+
+ if ( IsX360() )
+ {
+ // On the 360 we need to return to the background map
+ g_ServerGlobalVariables.bMapLoadFailed = true;
+ Cbuf_Clear();
+ Cbuf_AddText( "startupmenu force" );
+ Cbuf_Execute();
+ }
+}
+
+
+void CHostState::State_ChangeLevelMP()
+{
+ if ( Host_ValidGame() )
+ {
+ Steam3Server().NotifyOfLevelChange();
+
+#ifndef SWDS
+ // start progress bar immediately for multiplayer level transitions
+ EngineVGui()->EnabledProgressBarForNextLoad();
+#endif
+ if ( Host_Changelevel( false, m_levelName, m_landmarkName ) )
+ {
+ SetState( HS_RUN, true );
+ return;
+ }
+ }
+ // fail
+ ConMsg( "Unable to change level!\n" );
+ SetState( HS_RUN, true );
+
+ IGameEvent *event = g_GameEventManager.CreateEvent( "server_changelevel_failed" );
+ if ( event )
+ {
+ event->SetString( "levelname", m_levelName );
+ g_GameEventManager.FireEvent( event );
+ }
+}
+
+
+void CHostState::State_ChangeLevelSP()
+{
+ if ( Host_ValidGame() )
+ {
+ Host_Changelevel( true, m_levelName, m_landmarkName );
+ SetState( HS_RUN, true );
+ return;
+ }
+ // fail
+ ConMsg( "Unable to change level!\n" );
+ SetState( HS_RUN, true );
+}
+
+static bool IsClientActive()
+{
+ if ( !sv.IsActive() )
+ return cl.IsActive();
+
+ for ( int i = 0; i < sv.GetClientCount(); i++ )
+ {
+ CGameClient *pClient = sv.Client( i );
+ if ( pClient->IsActive() )
+ return true;
+ }
+ return false;
+}
+
+static bool IsClientConnected()
+{
+ if ( !sv.IsActive() )
+ return cl.IsConnected();
+
+ for ( int i = 0; i < sv.GetClientCount(); i++ )
+ {
+ CGameClient *pClient = sv.Client( i );
+ if ( pClient->IsConnected() )
+ return true;
+ }
+ return false;
+}
+
+void CHostState::State_Run( float frameTime )
+{
+ static bool s_bFirstRunFrame = true;
+
+ if ( m_flShortFrameTime > 0 )
+ {
+ if ( IsClientActive() )
+ {
+ m_flShortFrameTime = (m_flShortFrameTime > frameTime) ? (m_flShortFrameTime-frameTime) : 0;
+ }
+ // Only clamp time if client is in process of connecting or is already connected.
+ if ( IsClientConnected() )
+ {
+ frameTime = min( frameTime, host_state.interval_per_tick );
+ }
+ }
+
+ // Nice big timeout to play it safe while still ensuring that we don't get stuck in
+ // infinite loops.
+ int nTimerWaitSeconds = 60;
+ if ( s_bFirstRunFrame )
+ {
+ // The first frame can take a while especially during fork startup.
+ s_bFirstRunFrame = false;
+ nTimerWaitSeconds *= 2;
+ }
+ if ( sv.IsDedicated() )
+ {
+ Plat_BeginWatchdogTimer( nTimerWaitSeconds );
+ }
+
+ Host_RunFrame( frameTime );
+
+ if ( sv.IsDedicated() )
+ {
+ Plat_EndWatchdogTimer();
+ }
+
+ switch( m_nextState )
+ {
+ case HS_RUN:
+ break;
+
+ case HS_LOAD_GAME:
+ case HS_NEW_GAME:
+#if !defined( SWDS )
+ SCR_BeginLoadingPlaque();
+#endif
+ // FALL THROUGH INTENTIONALLY TO SHUTDOWN
+
+ case HS_SHUTDOWN:
+ case HS_RESTART:
+ // NOTE: The game must be shutdown before a new game can start,
+ // before a game can load, and before the system can be shutdown.
+ // This is done here instead of pathfinding through a state transition graph.
+ // That would be equivalent as the only way to get from HS_RUN to HS_LOAD_GAME is through HS_GAME_SHUTDOWN.
+ case HS_GAME_SHUTDOWN:
+ SetState( HS_GAME_SHUTDOWN, false );
+ break;
+
+ case HS_CHANGE_LEVEL_MP:
+ case HS_CHANGE_LEVEL_SP:
+ SetState( m_nextState, true );
+ break;
+
+ default:
+ SetState( HS_RUN, true );
+ break;
+ }
+}
+
+void CHostState::State_GameShutdown()
+{
+ if ( serverGameDLL )
+ {
+ Steam3Server().NotifyOfLevelChange();
+ g_pServerPluginHandler->LevelShutdown();
+#if !defined(SWDS)
+ audiosourcecache->LevelShutdown();
+#endif
+ }
+
+ GameShutdown();
+#ifndef SWDS
+ saverestore->ClearSaveDir();
+#endif
+ Host_ShutdownServer();
+
+ switch( m_nextState )
+ {
+ case HS_LOAD_GAME:
+ case HS_NEW_GAME:
+ case HS_SHUTDOWN:
+ case HS_RESTART:
+ SetState( m_nextState, true );
+ break;
+ default:
+ SetState( HS_RUN, true );
+ break;
+ }
+}
+
+
+// Tell the launcher we're done.
+void CHostState::State_Shutdown()
+{
+ CCS_Shutdown();
+
+#if !defined(SWDS)
+ CL_EndMovie();
+#endif
+
+ eng->SetNextState( IEngine::DLL_CLOSE );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CHostState::State_Restart( void )
+{
+ // Just like a regular shutdown
+ State_Shutdown();
+
+ // But signal launcher/front end to restart engine
+ eng->SetNextState( IEngine::DLL_RESTART );
+}
+
+
+//-----------------------------------------------------------------------------
+// this is the state machine's main processing loop
+//-----------------------------------------------------------------------------
+void CHostState::FrameUpdate( float time )
+{
+ CCS_Tick( time );
+
+#if _DEBUG
+ int loopCount = 0;
+#endif
+
+ if ( setjmp (host_abortserver) )
+ {
+ Init();
+ return;
+ }
+
+ g_bAbortServerSet = true;
+
+ while ( true )
+ {
+ int oldState = m_currentState;
+
+ // execute the current state (and transition to the next state if not in HS_RUN)
+ switch( m_currentState )
+ {
+ case HS_NEW_GAME:
+ g_pMDLCache->BeginMapLoad();
+ State_NewGame();
+ break;
+ case HS_LOAD_GAME:
+ g_pMDLCache->BeginMapLoad();
+ State_LoadGame();
+ break;
+ case HS_CHANGE_LEVEL_MP:
+ g_pMDLCache->BeginMapLoad();
+ m_flShortFrameTime = 0.5f;
+ State_ChangeLevelMP();
+ break;
+ case HS_CHANGE_LEVEL_SP:
+ g_pMDLCache->BeginMapLoad();
+ m_flShortFrameTime = 1.5f; // 1.5s of slower frames
+ State_ChangeLevelSP();
+ break;
+ case HS_RUN:
+ State_Run( time );
+ break;
+ case HS_GAME_SHUTDOWN:
+ State_GameShutdown();
+ break;
+ case HS_SHUTDOWN:
+ State_Shutdown();
+ break;
+ case HS_RESTART:
+ g_pMDLCache->BeginMapLoad();
+ State_Restart();
+ break;
+ }
+
+ // only do a single pass at HS_RUN per frame. All other states loop until they reach HS_RUN
+ if ( oldState == HS_RUN )
+ break;
+
+ // shutting down
+ if ( oldState == HS_SHUTDOWN ||
+ oldState == HS_RESTART )
+ break;
+
+ // Only HS_RUN is allowed to persist across loops!!!
+ // Also, detect circular state graphs (more than 8 cycling changes is probably a loop)
+ // NOTE: In the current graph, there are at most 2.
+#if _DEBUG
+ if ( (m_currentState == oldState) || (++loopCount > 8) )
+ {
+ Host_Error( "state crash!\n" );
+ }
+#endif
+ }
+}
+
+
+bool CHostState::IsGameShuttingDown( void )
+{
+ return ( ( m_currentState == HS_GAME_SHUTDOWN ) || ( m_nextState == HS_GAME_SHUTDOWN ) );
+}
+
+void CHostState::RememberLocation()
+{
+#ifndef SWDS
+ Assert( m_bRememberLocation );
+
+ m_vecLocation = MainViewOrigin();
+ VectorAngles( MainViewForward(), m_angLocation );
+
+ IClientEntity *localPlayer = entitylist ? entitylist->GetClientEntity( cl.m_nPlayerSlot + 1 ) : NULL;
+ if ( localPlayer )
+ {
+ m_vecLocation = localPlayer->GetAbsOrigin();
+ }
+
+ m_vecLocation.z -= 64.0f; // subtract out a bit of Z to position the player lower
+#endif
+}
+
+void CHostState::OnClientConnected()
+{
+ if ( !m_bWaitingForConnection )
+ return;
+ m_bWaitingForConnection = false;
+
+ if ( m_bRememberLocation )
+ {
+ m_bRememberLocation = false;
+ Cbuf_AddText( va( "setpos_exact %f %f %f\n", m_vecLocation.x, m_vecLocation.y, m_vecLocation.z ) );
+ Cbuf_AddText( va( "setang_exact %f %f %f\n", m_angLocation.x, m_angLocation.y, m_angLocation.z ) );
+ }
+
+#if !defined( SWDS )
+ if ( reload_materials.GetBool() )
+ {
+ // building cubemaps requires the materials to reload after map
+ reload_materials.SetValue( 0 );
+ Cbuf_AddText( "mat_reloadallmaterials\n" );
+ }
+
+ // Spew global texture memory usage if asked to
+ if( CommandLine()->CheckParm( "-dumpvidmemstats" ) )
+ {
+ FileHandle_t fp;
+ fp = g_pFileSystem->Open( "vidmemstats.txt", "a" );
+
+ g_pFileSystem->FPrintf( fp, "%s:\n", g_HostState.m_levelName );
+
+#ifdef VPROF_ENABLED
+ CVProfile *pProf = &g_VProfCurrentProfile;
+
+ int prefixLen = strlen( "TexGroup_Global_" );
+ float total = 0.0f;
+ for ( int i=0; i < pProf->GetNumCounters(); i++ )
+ {
+ if ( pProf->GetCounterGroup( i ) == COUNTER_GROUP_TEXTURE_GLOBAL )
+ {
+ // The counters are in bytes and the panel is all in kilobytes.
+ float value = pProf->GetCounterValue( i ) * ( 1.0f / ( 1024.0f * 1024.0f ) );
+ total += value;
+ const char *pName = pProf->GetCounterName( i );
+ if( Q_strnicmp( pName, "TexGroup_Global_", prefixLen ) == 0 )
+ {
+ pName += prefixLen;
+ }
+ g_pFileSystem->FPrintf( fp, "%s: %0.3fMB\n", pName, value );
+ }
+ }
+ g_pFileSystem->FPrintf( fp, "vidmem total: %0.3fMB\n", total );
+#endif
+
+#if 0
+ g_pFileSystem->FPrintf( fp, "hunk total: %0.3fMB\n", Cache_TotalUsed() * ( 1.0f / ( 1024.0f * 1024.0f ) ) );
+ g_pFileSystem->FPrintf( fp, "hunk sound: %0.3fMB\n", Cache_TotalUsed_Sound() * ( 1.0f / ( 1024.0f * 1024.0f ) ) );
+ g_pFileSystem->FPrintf( fp, "hunk models: %0.3fMB\n", Cache_TotalUsed_Models() * ( 1.0f / ( 1024.0f * 1024.0f ) ) );
+#endif
+ g_pFileSystem->FPrintf( fp, "---------------------------------\n" );
+ g_pFileSystem->Close( fp );
+ Cbuf_AddText( "quit\n" );
+ }
+#endif
+}
+
+void CHostState::OnClientDisconnected()
+{
+}
+
+// Determine if this is a valid game
+static bool Host_ValidGame( void )
+{
+ // No multi-client single player games
+ if ( sv.IsMultiplayer() )
+ {
+ if ( deathmatch.GetInt() )
+ return true;
+ }
+ else
+ {
+ /*
+ // Single player must have CD validation
+ if ( IsValidCD() )
+ {
+ return true;
+ }
+ */
+ return true;
+ }
+
+ ConDMsg("Unable to launch game\n");
+ return false;
+}
+