summaryrefslogtreecommitdiff
path: root/engine/sv_main.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'engine/sv_main.cpp')
-rw-r--r--engine/sv_main.cpp2965
1 files changed, 2965 insertions, 0 deletions
diff --git a/engine/sv_main.cpp b/engine/sv_main.cpp
new file mode 100644
index 0000000..d2cdb55
--- /dev/null
+++ b/engine/sv_main.cpp
@@ -0,0 +1,2965 @@
+
+//
+// Purpose:
+//
+// $Workfile: $
+// $NoKeywords: $
+//===========================================================================//
+
+#include "server_pch.h"
+#include "decal.h"
+#include "host_cmd.h"
+#include "cmodel_engine.h"
+#include "sv_log.h"
+#include "zone.h"
+#include "sound.h"
+#include "vox.h"
+#include "EngineSoundInternal.h"
+#include "checksum_engine.h"
+#include "host.h"
+#include "keys.h"
+#include "vengineserver_impl.h"
+#include "sv_filter.h"
+#include "pr_edict.h"
+#include "screen.h"
+#include "sys_dll.h"
+#include "world.h"
+#include "sv_main.h"
+#include "networkstringtableserver.h"
+#include "datamap.h"
+#include "filesystem_engine.h"
+#include "string_t.h"
+#include "vstdlib/random.h"
+#include "networkstringtable.h"
+#include "dt_send_eng.h"
+#include "sv_packedentities.h"
+#include "testscriptmgr.h"
+#include "PlayerState.h"
+#include "saverestoretypes.h"
+#include "tier0/vprof.h"
+#include "proto_oob.h"
+#include "staticpropmgr.h"
+#include "checksum_crc.h"
+#include "console.h"
+#include "tier0/icommandline.h"
+#include "gl_matsysiface.h"
+#include "GameEventManager.h"
+#ifndef SWDS
+#include "vgui_baseui_interface.h"
+#endif
+#include "cbenchmark.h"
+#include "client.h"
+#include "hltvserver.h"
+#include "replay_internal.h"
+#include "replayserver.h"
+#include "KeyValues.h"
+#include "sv_logofile.h"
+#include "cl_steamauth.h"
+#include "sv_steamauth.h"
+#include "sv_plugin.h"
+#include "DownloadListGenerator.h"
+#include "sv_steamauth.h"
+#include "LocalNetworkBackdoor.h"
+#include "cvar.h"
+#include "enginethreads.h"
+#include "tier1/functors.h"
+#include "vstdlib/jobthread.h"
+#include "pure_server.h"
+#include "datacache/idatacache.h"
+#include "filesystem/IQueuedLoader.h"
+#include "vstdlib/jobthread.h"
+#include "SourceAppInfo.h"
+#include "cl_rcon.h"
+#include "host_state.h"
+#include "voice.h"
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include "tier0/memdbgon.h"
+
+extern CNetworkStringTableContainer *networkStringTableContainerServer;
+extern CNetworkStringTableContainer *networkStringTableContainerClient;
+//void OnHibernateWhenEmptyChanged( IConVar *var, const char *pOldValue, float flOldValue );
+//ConVar sv_hibernate_when_empty( "sv_hibernate_when_empty", "1", 0, "Puts the server into extremely low CPU usage mode when no clients connected", OnHibernateWhenEmptyChanged );
+//ConVar sv_hibernate_ms( "sv_hibernate_ms", "20", 0, "# of milliseconds to sleep per frame while hibernating" );
+//ConVar sv_hibernate_ms_vgui( "sv_hibernate_ms_vgui", "20", 0, "# of milliseconds to sleep per frame while hibernating but running the vgui dedicated server frontend" );
+//static ConVar sv_hibernate_postgame_delay( "sv_hibernate_postgame_delay", "5", 0, "# of seconds to wait after final client leaves before hibernating.");
+ConVar sv_shutdown_timeout_minutes( "sv_shutdown_timeout_minutes", "360", FCVAR_REPLICATED, "If sv_shutdown is pending, wait at most N minutes for server to drain before forcing shutdown." );
+
+static double s_timeForceShutdown = 0.0;
+
+extern ConVar deathmatch;
+extern ConVar sv_sendtables;
+
+// Server default maxplayers value
+#define DEFAULT_SERVER_CLIENTS 6
+// This many players on a Lan with same key, is ok.
+#define MAX_IDENTICAL_CDKEYS 5
+
+CGameServer sv;
+
+CGlobalVars g_ServerGlobalVariables( false );
+
+static int current_skill;
+
+static void SV_CheatsChanged_f( IConVar *pConVar, const char *pOldString, float flOldValue )
+{
+ ConVarRef var( pConVar );
+ if ( var.GetInt() == 0 )
+ {
+ // cheats were disabled, revert all cheat cvars to their default values
+ g_pCVar->RevertFlaggedConVars( FCVAR_CHEAT );
+
+ DevMsg( "FCVAR_CHEAT cvars reverted to defaults.\n" );
+ }
+}
+
+static bool g_sv_pure_waiting_on_reload = false;
+static int g_sv_pure_mode = 0;
+int GetSvPureMode()
+{
+ return g_sv_pure_mode;
+}
+
+static void SV_Pure_f( const CCommand &args )
+{
+ int pure_mode = -2;
+ if ( args.ArgC() == 2 )
+ {
+ pure_mode = atoi( args[1] );
+ }
+
+ Msg( "--------------------------------------------------------\n" );
+ if ( pure_mode >= -1 && pure_mode <= 2 )
+ {
+ // Not changing?
+ if ( pure_mode == GetSvPureMode() )
+ {
+ Msg( "sv_pure value unchanged (current value is %d).\n", GetSvPureMode() );
+ }
+ else
+ {
+
+ // Set the value.
+ g_sv_pure_mode = pure_mode;
+ Msg( "sv_pure set to %d.\n", g_sv_pure_mode );
+
+ if ( sv.IsActive() )
+ {
+ g_sv_pure_waiting_on_reload = true;
+ }
+ }
+ }
+ else
+ {
+ Msg( "sv_pure: Only allow client to use certain files.\n"
+ "\n"
+ " -1 - Do not apply any rules or restrict which files the client may load.\n"
+ " 0 - Apply rules in cfg/pure_server_minimal.txt only.\n"
+ " 1 - Apply rules in cfg/pure_server_full.txt and then cfg/pure_server_whitelist.txt.\n"
+ " 2 - Apply rules in cfg/pure_server_full.txt.\n"
+ "\n"
+ " See cfg/pure_server_whitelist_example.txt for more details.\n"
+ );
+ }
+
+ if ( pure_mode == -2 )
+ {
+ // If we're a client on a server with sv_pure = 1, display the current whitelist.
+#ifndef DEDICATED
+ if ( cl.IsConnected() )
+ {
+ Msg( "\n\n" );
+ extern void CL_PrintWhitelistInfo(); // from cl_main.cpp
+ CL_PrintWhitelistInfo();
+ }
+ else
+#endif
+ {
+ Msg( "\nCurrent sv_pure value is %d.\n", GetSvPureMode() );
+ }
+ }
+ if ( sv.IsActive() && g_sv_pure_waiting_on_reload )
+ {
+ Msg( "Note: Waiting for the next changelevel to apply the current value.\n" );
+ }
+ Msg( "--------------------------------------------------------\n" );
+}
+
+static ConCommand sv_pure( "sv_pure", SV_Pure_f, "Show user data." );
+
+ConVar sv_pure_kick_clients( "sv_pure_kick_clients", "1", 0, "If set to 1, the server will kick clients with mismatching files. Otherwise, it will issue a warning to the client." );
+ConVar sv_pure_trace( "sv_pure_trace", "0", 0, "If set to 1, the server will print a message whenever a client is verifying a CRC for a file." );
+ConVar sv_pure_consensus( "sv_pure_consensus", "5", 0, "Minimum number of file hashes to agree to form a consensus." );
+ConVar sv_pure_retiretime( "sv_pure_retiretime", "900", 0, "Seconds of server idle time to flush the sv_pure file hash cache." );
+
+ConVar sv_cheats( "sv_cheats", "0", FCVAR_NOTIFY|FCVAR_REPLICATED, "Allow cheats on server", SV_CheatsChanged_f );
+ConVar sv_lan( "sv_lan", "0", 0, "Server is a lan server ( no heartbeat, no authentication, no non-class C addresses )" );
+
+
+
+static ConVar sv_pausable( "sv_pausable","0", FCVAR_NOTIFY, "Is the server pausable." );
+static ConVar sv_contact( "sv_contact", "", FCVAR_NOTIFY, "Contact email for server sysop" );
+static ConVar sv_cacheencodedents("sv_cacheencodedents", "1", 0, "If set to 1, does an optimization to prevent extra SendTable_Encode calls.");
+static ConVar sv_voiceenable( "sv_voiceenable", "1", FCVAR_ARCHIVE|FCVAR_NOTIFY ); // set to 0 to disable all voice forwarding.
+ ConVar sv_downloadurl( "sv_downloadurl", "", FCVAR_REPLICATED, "Location from which clients can download missing files" );
+ ConVar sv_maxreplay("sv_maxreplay", "0", 0, "Maximum replay time in seconds", true, 0, true, 15 );
+
+static ConVar sv_consistency( "sv_consistency", "1", FCVAR_REPLICATED, "Legacy variable with no effect! This was deleted and then added as a temporary kludge to prevent players from being banned by servers running old versions of SMAC" );
+
+/// XXX(JohnS): When steam voice gets ugpraded to Opus we will probably default back to steam. At that time we should
+/// note that Steam voice is the highest quality codec below.
+static ConVar sv_voicecodec( "sv_voicecodec", "vaudio_celt", 0,
+ "Specifies which voice codec to use. Valid options are:\n"
+ "vaudio_speex - Legacy Speex codec (lowest quality)\n"
+ "vaudio_celt - Newer CELT codec\n"
+ "steam - Use Steam voice API" );
+
+
+ConVar sv_mincmdrate( "sv_mincmdrate", "10", FCVAR_REPLICATED, "This sets the minimum value for cl_cmdrate. 0 == unlimited." );
+ConVar sv_maxcmdrate( "sv_maxcmdrate", "66", FCVAR_REPLICATED, "(If sv_mincmdrate is > 0), this sets the maximum value for cl_cmdrate." );
+ConVar sv_client_cmdrate_difference( "sv_client_cmdrate_difference", "20", FCVAR_REPLICATED,
+ "cl_cmdrate is moved to within sv_client_cmdrate_difference units of cl_updaterate before it "
+ "is clamped between sv_mincmdrate and sv_maxcmdrate." );
+
+ConVar sv_client_min_interp_ratio( "sv_client_min_interp_ratio", "1", FCVAR_REPLICATED,
+ "This can be used to limit the value of cl_interp_ratio for connected clients "
+ "(only while they are connected).\n"
+ " -1 = let clients set cl_interp_ratio to anything\n"
+ " any other value = set minimum value for cl_interp_ratio"
+ );
+ConVar sv_client_max_interp_ratio( "sv_client_max_interp_ratio", "5", FCVAR_REPLICATED,
+ "This can be used to limit the value of cl_interp_ratio for connected clients "
+ "(only while they are connected). If sv_client_min_interp_ratio is -1, "
+ "then this cvar has no effect."
+ );
+ConVar sv_client_predict( "sv_client_predict", "-1", FCVAR_REPLICATED,
+ "This can be used to force the value of cl_predict for connected clients "
+ "(only while they are connected).\n"
+ " -1 = let clients set cl_predict to anything\n"
+ " 0 = force cl_predict to 0\n"
+ " 1 = force cl_predict to 1"
+ );
+
+ConVar sv_restrict_aspect_ratio_fov( "sv_restrict_aspect_ratio_fov", "1", FCVAR_REPLICATED,
+ "This can be used to limit the effective FOV of users using wide-screen\n"
+ "resolutions with aspect ratios wider than 1.85:1 (slightly wider than 16:9).\n"
+ " 0 = do not cap effective FOV\n"
+ " 1 = limit the effective FOV on windowed mode users using resolutions\n"
+ " greater than 1.85:1\n"
+ " 2 = limit the effective FOV on both windowed mode and full-screen users\n",
+ true, 0, true, 2);
+
+void OnTVEnablehanged( IConVar *pConVar, const char *pOldString, float flOldValue )
+{
+ ConVarRef var( pConVar );
+
+/*
+ ConVarRef replay_enable( "replay_enable" );
+ if ( var.GetBool() && replay_enable.IsValid() && replay_enable.GetBool() )
+ {
+ var.SetValue( 0 );
+ Warning( "Error: Replay is enabled. Please disable Replay if you wish to enable SourceTV.\n" );
+ return;
+ }
+*/
+
+ //Let's check maxclients and make sure we have room for SourceTV
+ if ( var.GetBool() == true )
+ {
+ sv.InitMaxClients();
+ }
+}
+
+ConVar tv_enable( "tv_enable", "0", FCVAR_NOTIFY, "Activates SourceTV on server.", OnTVEnablehanged );
+
+extern ConVar *sv_noclipduringpause;
+
+static bool s_bForceSend = false;
+
+void SV_ForceSend()
+{
+ s_bForceSend = true;
+}
+
+bool g_FlushMemoryOnNextServer;
+int g_FlushMemoryOnNextServerCounter;
+
+void SV_FlushMemoryOnNextServer()
+{
+ g_FlushMemoryOnNextServer = true;
+ g_FlushMemoryOnNextServerCounter++;
+}
+
+// Prints important entity creation/deletion events to console
+#if defined( _DEBUG )
+ConVar sv_deltatrace( "sv_deltatrace", "0", 0, "For debugging, print entity creation/deletion info to console." );
+#define TRACE_DELTA( text ) if ( sv_deltatrace.GetInt() ) { ConMsg( text ); };
+#else
+#define TRACE_DELTA( funcs )
+#endif
+
+
+#if defined( DEBUG_NETWORKING )
+
+//-----------------------------------------------------------------------------
+// Opens the recording file
+//-----------------------------------------------------------------------------
+
+static FILE* OpenRecordingFile()
+{
+ FILE* fp = 0;
+ static bool s_CantOpenFile = false;
+ static bool s_NeverOpened = true;
+ if (!s_CantOpenFile)
+ {
+ fp = fopen( "svtrace.txt", s_NeverOpened ? "wt" : "at" );
+ if (!fp)
+ {
+ s_CantOpenFile = true;
+ }
+ s_NeverOpened = false;
+ }
+ return fp;
+}
+
+//-----------------------------------------------------------------------------
+// Records an argument for a command, flushes when the command is done
+//-----------------------------------------------------------------------------
+/*
+void SpewToFile( char const* pFmt, ... )
+static void SpewToFile( const char* pFmt, ... )
+{
+ static CUtlVector<unsigned char> s_RecordingBuffer;
+
+ char temp[2048];
+ va_list args;
+
+ va_start( args, pFmt );
+ int len = Q_vsnprintf( temp, sizeof( temp ), pFmt, args );
+ va_end( args );
+ Assert( len < 2048 );
+
+ int idx = s_RecordingBuffer.AddMultipleToTail( len );
+ memcpy( &s_RecordingBuffer[idx], temp, len );
+ if ( 1 ) //s_RecordingBuffer.Size() > 8192)
+ {
+ FILE* fp = OpenRecordingFile();
+ fwrite( s_RecordingBuffer.Base(), 1, s_RecordingBuffer.Size(), fp );
+ fclose( fp );
+
+ s_RecordingBuffer.RemoveAll();
+ }
+}
+*/
+
+#endif // #if defined( DEBUG_NETWORKING )
+
+
+/*void SV_Init(bool isDedicated)
+{
+ sv.Init( isDedicated );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void SV_Shutdown( void )
+{
+ sv.Shutdown();
+}*/
+
+void CGameServer::Clear( void )
+{
+ m_pModelPrecacheTable = NULL;
+ m_pGenericPrecacheTable = NULL;
+ m_pSoundPrecacheTable = NULL;
+ m_pDecalPrecacheTable = NULL;
+ m_pDynamicModelsTable = NULL;
+ m_bIsLevelMainMenuBackground = false;
+
+ m_bLoadgame = false;
+
+ host_state.SetWorldModel( NULL );
+
+ Q_memset( m_szStartspot, 0, sizeof( m_szStartspot ) );
+
+ num_edicts = 0;
+ max_edicts = 0;
+ free_edicts = 0;
+ edicts = NULL;
+
+ // Clear the instance baseline indices in the ServerClasses.
+ if ( serverGameDLL )
+ {
+ for( ServerClass *pCur = serverGameDLL->GetAllServerClasses(); pCur; pCur=pCur->m_pNext )
+ {
+ pCur->m_InstanceBaselineIndex = INVALID_STRING_INDEX;
+ }
+ }
+
+ for ( int i = 0; i < m_TempEntities.Count(); i++ )
+ {
+ delete m_TempEntities[i];
+ }
+
+ m_TempEntities.Purge();
+
+ CBaseServer::Clear();
+}
+
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Create any client/server string tables needed internally by the engine
+//-----------------------------------------------------------------------------
+void CGameServer::CreateEngineStringTables( void )
+{
+ int i,j;
+
+ m_StringTables->SetTick( m_nTickCount ); // set first tick
+
+ bool bUseFilenameTables = false;
+
+ char szDownloadableFileTablename[255] = DOWNLOADABLE_FILE_TABLENAME;
+ char szModelPrecacheTablename[255] = MODEL_PRECACHE_TABLENAME;
+ char szGenericPrecacheTablename[255] = GENERIC_PRECACHE_TABLENAME;
+ char szSoundPrecacheTablename[255] = SOUND_PRECACHE_TABLENAME;
+ char szDecalPrecacheTablename[255] = DECAL_PRECACHE_TABLENAME;
+
+ // This was added into staging at some point and is not enabled in main or rel.
+ if ( 0 )
+ {
+ bUseFilenameTables = true;
+ Q_snprintf( szDownloadableFileTablename, 255, ":%s", DOWNLOADABLE_FILE_TABLENAME );
+ Q_snprintf( szModelPrecacheTablename, 255, ":%s", MODEL_PRECACHE_TABLENAME );
+ Q_snprintf( szGenericPrecacheTablename, 255, ":%s", GENERIC_PRECACHE_TABLENAME );
+ Q_snprintf( szSoundPrecacheTablename, 255, ":%s", SOUND_PRECACHE_TABLENAME );
+ Q_snprintf( szDecalPrecacheTablename, 255, ":%s", DECAL_PRECACHE_TABLENAME );
+ }
+
+ m_pDownloadableFileTable = m_StringTables->CreateStringTableEx(
+ szDownloadableFileTablename,
+ MAX_DOWNLOADABLE_FILES,
+ 0,
+ 0,
+ bUseFilenameTables );
+
+ m_pModelPrecacheTable = m_StringTables->CreateStringTableEx(
+ szModelPrecacheTablename,
+ MAX_MODELS,
+ sizeof ( CPrecacheUserData ),
+ PRECACHE_USER_DATA_NUMBITS,
+ bUseFilenameTables );
+
+ m_pGenericPrecacheTable = m_StringTables->CreateStringTableEx(
+ szGenericPrecacheTablename,
+ MAX_GENERIC,
+ sizeof ( CPrecacheUserData ),
+ PRECACHE_USER_DATA_NUMBITS,
+ bUseFilenameTables );
+
+ m_pSoundPrecacheTable = m_StringTables->CreateStringTableEx(
+ szSoundPrecacheTablename,
+ MAX_SOUNDS,
+ sizeof ( CPrecacheUserData ),
+ PRECACHE_USER_DATA_NUMBITS,
+ bUseFilenameTables );
+
+ m_pDecalPrecacheTable = m_StringTables->CreateStringTableEx(
+ szDecalPrecacheTablename,
+ MAX_BASE_DECALS,
+ sizeof ( CPrecacheUserData ),
+ PRECACHE_USER_DATA_NUMBITS,
+ bUseFilenameTables );
+
+ m_pInstanceBaselineTable = m_StringTables->CreateStringTable(
+ INSTANCE_BASELINE_TABLENAME,
+ MAX_DATATABLES );
+
+ m_pLightStyleTable = m_StringTables->CreateStringTable(
+ LIGHT_STYLES_TABLENAME,
+ MAX_LIGHTSTYLES );
+
+ m_pUserInfoTable = m_StringTables->CreateStringTable(
+ USER_INFO_TABLENAME,
+ 1<<ABSOLUTE_PLAYER_LIMIT_DW ); // make it a power of 2
+
+ // Fixed-size user data; bit value of either 0 or 1.
+ m_pDynamicModelsTable = m_StringTables->CreateStringTable( "DynamicModels", 2048, true, 1 );
+
+ // Send the query info..
+ m_pServerStartupTable = m_StringTables->CreateStringTable(
+ SERVER_STARTUP_DATA_TABLENAME,
+ 4 );
+ SetQueryPortFromSteamServer();
+ CopyPureServerWhitelistToStringTable();
+
+ Assert ( m_pModelPrecacheTable &&
+ m_pGenericPrecacheTable &&
+ m_pSoundPrecacheTable &&
+ m_pDecalPrecacheTable &&
+ m_pInstanceBaselineTable &&
+ m_pLightStyleTable &&
+ m_pUserInfoTable &&
+ m_pServerStartupTable &&
+ m_pDownloadableFileTable &&
+ m_pDynamicModelsTable );
+
+ // create an empty lightstyle table with unique index names
+ for ( i = 0; i<MAX_LIGHTSTYLES; i++ )
+ {
+ char name[8]; Q_snprintf( name, 8, "%i", i );
+ j = m_pLightStyleTable->AddString( true, name );
+ Assert( j==i ); // indices must match
+ }
+
+ for ( i = 0; i<GetMaxClients(); i++ )
+ {
+ char name[8]; Q_snprintf( name, 8, "%i", i );
+ j = m_pUserInfoTable->AddString( true, name );
+ Assert( j==i ); // indices must match
+ }
+
+ // set up the downloadable files generator
+ DownloadListGenerator().SetStringTable( m_pDownloadableFileTable );
+}
+
+void CGameServer::SetQueryPortFromSteamServer()
+{
+ if ( !m_pServerStartupTable )
+ return;
+
+ int queryPort = Steam3Server().GetQueryPort();
+ m_pServerStartupTable->AddString( true, "QueryPort", sizeof( queryPort ), &queryPort );
+}
+
+void CGameServer::CopyPureServerWhitelistToStringTable()
+{
+ if ( !m_pPureServerWhitelist )
+ return;
+
+ CUtlBuffer buf;
+ m_pPureServerWhitelist->Encode( buf );
+ m_pServerStartupTable->AddString( true, "PureServerWhitelist", buf.TellPut(), buf.Base() );
+}
+
+
+void SV_InstallClientStringTableMirrors( void )
+{
+#ifndef SWDS
+#ifndef SHARED_NET_STRING_TABLES
+
+ int numTables = networkStringTableContainerServer->GetNumTables();
+
+ for ( int i =0; i<numTables; i++)
+ {
+ // iterate through server tables
+ CNetworkStringTable *serverTable =
+ (CNetworkStringTable*)networkStringTableContainerServer->GetTable( i );
+
+ if ( !serverTable )
+ continue;
+
+ // get mathcing client table
+ CNetworkStringTable *clientTable =
+ (CNetworkStringTable*)networkStringTableContainerClient->FindTable( serverTable->GetTableName() );
+
+ if ( !clientTable )
+ {
+ DevMsg("SV_InstallClientStringTableMirrors! Missing client table \"%s\".\n ", serverTable->GetTableName() );
+ continue;
+ }
+
+ // link client table to server table
+ serverTable->SetMirrorTable( clientTable );
+ }
+#endif
+#endif
+}
+
+
+
+//-----------------------------------------------------------------------------
+// user <name or userid>
+//
+// Dump userdata / masterdata for a user
+//-----------------------------------------------------------------------------
+CON_COMMAND( user, "Show user data." )
+{
+ int uid;
+ int i;
+
+ if ( !sv.IsActive() )
+ {
+ ConMsg( "Can't 'user', not running a server\n" );
+ return;
+ }
+
+ if (args.ArgC() != 2)
+ {
+ ConMsg ("Usage: user <username / userid>\n");
+ return;
+ }
+
+ uid = atoi(args[1]);
+
+ for (i=0 ; i< sv.GetClientCount() ; i++)
+ {
+ IClient *pClient = sv.GetClient( i );
+
+ if ( !pClient->IsConnected() )
+ continue;
+
+ if ( (pClient->GetPlayerSlot()== uid ) || !Q_strcmp( pClient->GetClientName(), args[1]) )
+ {
+ ConMsg ("TODO: SV_User_f.\n");
+ return;
+ }
+ }
+
+ ConMsg ("User not in server.\n");
+}
+
+
+//-----------------------------------------------------------------------------
+// Dump userids for all current players
+//-----------------------------------------------------------------------------
+CON_COMMAND( users, "Show user info for players on server." )
+{
+ if ( !sv.IsActive() )
+ {
+ ConMsg( "Can't 'users', not running a server\n" );
+ return;
+ }
+
+ int c = 0;
+ ConMsg ("<slot:userid:\"name\">\n");
+ for ( int i=0 ; i< sv.GetClientCount() ; i++ )
+ {
+ IClient *pClient = sv.GetClient( i );
+
+ if ( pClient->IsConnected() )
+ {
+ ConMsg ("%i:%i:\"%s\"\n", pClient->GetPlayerSlot(), pClient->GetUserID(), pClient->GetClientName() );
+ c++;
+ }
+ }
+
+ ConMsg ( "%i users\n", c );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Determine the value of sv.maxclients
+//-----------------------------------------------------------------------------
+bool CL_IsHL2Demo(); // from cl_main.cpp
+bool CL_IsPortalDemo(); // from cl_main.cpp
+
+extern ConVar tv_enable;
+
+void SetupMaxPlayers( int iDesiredMaxPlayers )
+{
+ int minmaxplayers = 1;
+ int maxmaxplayers = ABSOLUTE_PLAYER_LIMIT;
+ int defaultmaxplayers = 1;
+
+ if ( serverGameClients )
+ {
+ serverGameClients->GetPlayerLimits( minmaxplayers, maxmaxplayers, defaultmaxplayers );
+
+ if ( minmaxplayers < 1 )
+ {
+ Sys_Error( "GetPlayerLimits: min maxplayers must be >= 1 (%i)", minmaxplayers );
+ }
+ else if ( defaultmaxplayers < 1 )
+ {
+ Sys_Error( "GetPlayerLimits: default maxplayers must be >= 1 (%i)", minmaxplayers );
+ }
+
+ if ( minmaxplayers > maxmaxplayers || defaultmaxplayers > maxmaxplayers )
+ {
+ Sys_Error( "GetPlayerLimits: min maxplayers %i > max %i", minmaxplayers, maxmaxplayers );
+ }
+
+ if ( maxmaxplayers > ABSOLUTE_PLAYER_LIMIT )
+ {
+ Sys_Error( "GetPlayerLimits: max players limited to %i", ABSOLUTE_PLAYER_LIMIT );
+ }
+ }
+
+ // Determine absolute limit
+ sv.m_nMaxClientsLimit = maxmaxplayers;
+
+ // Check for command line override
+ int newmaxplayers = iDesiredMaxPlayers;
+
+ if ( newmaxplayers >= 1 )
+ {
+ // Never go above what the game .dll can handle
+ newmaxplayers = min( newmaxplayers, maxmaxplayers );
+ sv.m_nMaxClientsLimit = newmaxplayers;
+ }
+ else
+ {
+ newmaxplayers = defaultmaxplayers;
+ }
+
+#if defined( REPLAY_ENABLED )
+ if ( Replay_IsSupportedModAndPlatform() && CommandLine()->CheckParm( "-replay" ) )
+ {
+ newmaxplayers += 1;
+ sv.m_nMaxClientsLimit += 1;
+ }
+#endif
+
+ if ( tv_enable.GetBool() )
+ {
+ newmaxplayers += 1;
+ sv.m_nMaxClientsLimit += 1;
+ }
+
+ newmaxplayers = clamp( newmaxplayers, minmaxplayers, sv.m_nMaxClientsLimit );
+
+ if ( ( CL_IsHL2Demo() || CL_IsPortalDemo() ) && !sv.IsDedicated() )
+ {
+ newmaxplayers = 1;
+ sv.m_nMaxClientsLimit = 1;
+ }
+
+ if ( sv.GetMaxClients() < newmaxplayers || !tv_enable.GetBool() )
+ sv.SetMaxClients( newmaxplayers );
+
+}
+
+void CGameServer::InitMaxClients( void )
+{
+ int newmaxplayers = CommandLine()->ParmValue( "-maxplayers", -1 );
+ if ( newmaxplayers == -1 )
+ {
+ newmaxplayers = CommandLine()->ParmValue( "+maxplayers", -1 );
+ }
+
+ SetupMaxPlayers( newmaxplayers );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Changes the maximum # of players allowed on the server.
+// Server cannot be running when command is issued.
+//-----------------------------------------------------------------------------
+CON_COMMAND( maxplayers, "Change the maximum number of players allowed on this server." )
+{
+ if ( args.ArgC () != 2 )
+ {
+ ConMsg ("\"maxplayers\" is \"%u\"\n", sv.GetMaxClients() );
+ return;
+ }
+
+ if ( sv.IsActive() )
+ {
+ ConMsg( "Cannot change maxplayers while the server is running\n");
+ return;
+ }
+
+ SetupMaxPlayers( Q_atoi( args[ 1 ] ) );
+}
+
+int SV_BuildSendTablesArray( ServerClass *pClasses, SendTable **pTables, int nMaxTables )
+{
+ int nTables = 0;
+
+ for( ServerClass *pCur=pClasses; pCur; pCur=pCur->m_pNext )
+ {
+ ErrorIfNot( nTables < nMaxTables, ("SV_BuildSendTablesArray: too many SendTables!") );
+ pTables[nTables] = pCur->m_pTable;
+ ++nTables;
+ }
+
+ return nTables;
+}
+
+
+// Builds an alternate copy of the datatable for any classes that have datatables with props excluded.
+void SV_InitSendTables( ServerClass *pClasses )
+{
+ SendTable *pTables[MAX_DATATABLES];
+ int nTables = SV_BuildSendTablesArray( pClasses, pTables, ARRAYSIZE( pTables ) );
+
+ SendTable_Init( pTables, nTables );
+}
+
+
+void SV_TermSendTables( ServerClass *pClasses )
+{
+ SendTable_Term();
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: returns which games/mods we're allowed to play
+//-----------------------------------------------------------------------------
+struct ModDirPermissions_t
+{
+ int m_iAppID;
+ const char *m_pchGameDir;
+};
+
+static ModDirPermissions_t g_ModDirPermissions[] =
+{
+ { GetAppSteamAppId( k_App_CSS ), GetAppModName( k_App_CSS ) },
+ { GetAppSteamAppId( k_App_DODS ), GetAppModName( k_App_DODS ) },
+ { GetAppSteamAppId( k_App_HL2MP ), GetAppModName( k_App_HL2MP ) },
+ { GetAppSteamAppId( k_App_LOST_COAST ), GetAppModName( k_App_LOST_COAST ) },
+ { GetAppSteamAppId( k_App_HL1DM ), GetAppModName( k_App_HL1DM ) },
+ { GetAppSteamAppId( k_App_PORTAL ), GetAppModName( k_App_PORTAL ) },
+ { GetAppSteamAppId( k_App_HL2 ), GetAppModName( k_App_HL2 ) },
+ { GetAppSteamAppId( k_App_HL2_EP1 ), GetAppModName( k_App_HL2_EP1 ) },
+ { GetAppSteamAppId( k_App_HL2_EP2 ), GetAppModName( k_App_HL2_EP2 ) },
+ { GetAppSteamAppId( k_App_TF2 ), GetAppModName( k_App_TF2 ) },
+};
+
+bool ServerDLL_Load( bool bIsServerOnly )
+{
+ // Load in the game .dll
+ LoadEntityDLLs( GetBaseDirectory(), bIsServerOnly );
+ return true;
+}
+
+void ServerDLL_Unload()
+{
+ UnloadEntityDLLs();
+}
+
+#if !defined(DEDICATED)
+#if !defined(_X360)
+// Put this function declaration at global scope to avoid the ambiguity of the most vexing parse.
+bool CL_IsHL2Demo();
+#endif
+#endif
+
+//-----------------------------------------------------------------------------
+// Purpose: Loads the game .dll
+//-----------------------------------------------------------------------------
+void SV_InitGameDLL( void )
+{
+ // Clear out the command buffer.
+ Cbuf_Execute();
+
+ // Don't initialize a second time
+ if ( sv.dll_initialized )
+ {
+ return;
+ }
+
+#if !defined(SWDS)
+#if !defined(_X360)
+ if ( CL_IsHL2Demo() && !sv.IsDedicated() && Q_stricmp( COM_GetModDirectory(), "hl2" ) )
+ {
+ Error( "The HL2 demo is unable to run Mods.\n" );
+ return;
+ }
+
+ if ( CL_IsPortalDemo() && !sv.IsDedicated() && Q_stricmp( COM_GetModDirectory(), "portal" ) )
+ {
+ Error( "The Portal demo is unable to run Mods.\n" );
+ return;
+ }
+
+ // check permissions
+ if ( Steam3Client().SteamApps() && !CL_IsHL2Demo() && !CL_IsPortalDemo() )
+ {
+ bool bVerifiedMod = false;
+
+ // find the game dir we're running
+ for ( int i = 0; i < ARRAYSIZE( g_ModDirPermissions ); i++ )
+ {
+ if ( !Q_stricmp( COM_GetModDirectory(), g_ModDirPermissions[i].m_pchGameDir ) )
+ {
+ // we've found the mod, make sure we own the app
+ if ( Steam3Client().SteamApps()->BIsSubscribedApp( g_ModDirPermissions[i].m_iAppID ) )
+ {
+ bVerifiedMod = true;
+ }
+ else
+ {
+ Error( "No permissions to run '%s'\n", COM_GetModDirectory() );
+ return;
+ }
+
+ break;
+ }
+ }
+
+ if ( !bVerifiedMod )
+ {
+ // make sure they can run the Source engine
+ if ( ! Steam3Client().SteamApps()->BIsSubscribedApp( 215 ) )
+ {
+ Error( "A Source engine game is required to run mods\n" );
+ return;
+ }
+ }
+ }
+
+#endif // _X360
+#endif
+
+ COM_TimestampedLog( "SV_InitGameDLL" );
+
+ if ( !serverGameDLL )
+ {
+ Warning( "Failed to load server binary\n" );
+ return;
+ }
+
+ // Flag that we've started the game .dll
+ sv.dll_initialized = true;
+
+ COM_TimestampedLog( "serverGameDLL->DLLInit" );
+
+ // Tell the game DLL to start up
+ if(!serverGameDLL->DLLInit(g_AppSystemFactory, g_AppSystemFactory, g_AppSystemFactory, &g_ServerGlobalVariables))
+ {
+ Host_Error("IDLLFunctions::DLLInit returned false.\n");
+ }
+
+ if ( CommandLine()->FindParm( "-NoLoadPluginsForClient" ) == 0 )
+ g_pServerPluginHandler->LoadPlugins(); // load 3rd party plugins
+
+
+ // let's not have any servers with no name
+ if ( host_name.GetString()[0] == 0 )
+ {
+ host_name.SetValue( serverGameDLL->GetGameDescription() );
+ }
+
+ sv_noclipduringpause = ( ConVar * )g_pCVar->FindVar( "sv_noclipduringpause" );
+
+ COM_TimestampedLog( "SV_InitSendTables" );
+
+ // Make extra copies of data tables if they have SendPropExcludes.
+ SV_InitSendTables( serverGameDLL->GetAllServerClasses() );
+
+ host_state.interval_per_tick = serverGameDLL->GetTickInterval();
+ if ( host_state.interval_per_tick < MINIMUM_TICK_INTERVAL ||
+ host_state.interval_per_tick > MAXIMUM_TICK_INTERVAL )
+ {
+ Sys_Error( "GetTickInterval returned bogus tick interval (%f)[%f to %f is valid range]", host_state.interval_per_tick,
+ MINIMUM_TICK_INTERVAL, MAXIMUM_TICK_INTERVAL );
+ }
+
+ // set maxclients limit based on Mod or commandline settings
+ sv.InitMaxClients();
+
+ // Execute and server commands the game .dll added at startup
+ Cbuf_Execute();
+
+#if defined( REPLAY_ENABLED )
+ extern IReplaySystem *g_pReplay;
+ if ( Replay_IsSupportedModAndPlatform() && sv.IsDedicated() )
+ {
+ if ( !serverGameDLL->ReplayInit( g_fnReplayFactory ) )
+ {
+ Sys_Error( "Server replay init failed" );
+ }
+
+ if ( sv.IsDedicated() && !g_pReplay->SV_Init( g_ServerFactory ) )
+ {
+ Sys_Error( "Replay system server init failed!" );
+ }
+ }
+#endif
+}
+
+//
+// Release resources associated with extension DLLs.
+//
+void SV_ShutdownGameDLL( void )
+{
+ if ( !sv.dll_initialized )
+ {
+ return;
+ }
+
+ if ( g_pReplay )
+ {
+ g_pReplay->SV_Shutdown();
+ }
+
+ // Delete any extra SendTable copies we've attached to the game DLL's classes, if any.
+ SV_TermSendTables( serverGameDLL->GetAllServerClasses() );
+ g_pServerPluginHandler->UnloadPlugins();
+ serverGameDLL->DLLShutdown();
+
+ UnloadEntityDLLs();
+
+ sv.dll_initialized = false;
+}
+
+
+ServerClass* SV_FindServerClass( const char *pName )
+{
+ ServerClass *pCur = serverGameDLL->GetAllServerClasses();
+ while ( pCur )
+ {
+ if ( Q_stricmp( pCur->GetName(), pName ) == 0 )
+ return pCur;
+
+ pCur = pCur->m_pNext;
+ }
+
+ return NULL;
+}
+
+ServerClass* SV_FindServerClass( int index )
+{
+ ServerClass *pCur = serverGameDLL->GetAllServerClasses();
+ int count = 0;
+
+ while ( (count < index) && (pCur != NULL) )
+ {
+ count++;
+ pCur = pCur->m_pNext;
+ }
+
+ return pCur;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: General initialization of the server
+//-----------------------------------------------------------------------------
+void CGameServer::Init (bool isDedicated)
+{
+ CBaseServer::Init( isDedicated );
+
+ m_FullSendTables.SetDebugName( "m_FullSendTables" );
+
+ dll_initialized = false;
+}
+
+bool CGameServer::IsPausable( void ) const
+{
+ // In single-player, they can always pause it. In multiplayer, check the cvar.
+ if ( IsMultiplayer() )
+ {
+ return sv_pausable.GetBool();
+ }
+ else
+ {
+ return true;
+ }
+}
+
+void CGameServer::Shutdown( void )
+{
+ m_bIsLevelMainMenuBackground = false;
+
+ CBaseServer::Shutdown();
+
+ // Actually performs a shutdown.
+ framesnapshotmanager->LevelChanged();
+
+ IGameEvent *event = g_GameEventManager.CreateEvent( "server_shutdown" );
+
+ if ( event )
+ {
+ event->SetString( "reason", "quit" );
+ g_GameEventManager.FireEvent( event );
+ }
+
+ Steam3Server().Shutdown();
+
+ if ( serverGameDLL && g_iServerGameDLLVersion >= 7 )
+ {
+ serverGameDLL->GameServerSteamAPIShutdown();
+ }
+
+ // Log_Printf( "Server shutdown.\n" );
+ g_Log.Close();
+}
+
+
+/*
+==================
+SV_StartSound
+
+Each entity can have eight independant sound sources, like voice,
+weapon, feet, etc.
+
+Channel 0 is an auto-allocate channel, the others override anything
+allready running on that entity/channel pair.
+
+An attenuation of 0 will play full volume everywhere in the level.
+Larger attenuations will drop off. (max 4 attenuation)
+
+Pitch should be PITCH_NORM (100) for no pitch shift. Values over 100 (up to 255)
+shift pitch higher, values lower than 100 lower the pitch.
+==================
+*/
+void SV_StartSound ( IRecipientFilter& filter, edict_t *pSoundEmittingEntity, int iChannel,
+ const char *pSample, float flVolume, soundlevel_t iSoundLevel, int iFlags,
+ int iPitch, int iSpecialDSP, const Vector *pOrigin, float soundtime, int speakerentity, CUtlVector< Vector >* pUtlVecOrigins )
+{
+
+ SoundInfo_t sound;
+ sound.SetDefault();
+
+ sound.nEntityIndex = pSoundEmittingEntity ? NUM_FOR_EDICT( pSoundEmittingEntity ) : 0;
+ sound.nChannel = iChannel;
+ sound.fVolume = flVolume;
+ sound.Soundlevel = iSoundLevel;
+ sound.nFlags = iFlags;
+ sound.nPitch = iPitch;
+ sound.nSpecialDSP = iSpecialDSP;
+ sound.nSpeakerEntity = speakerentity;
+
+ if ( iFlags & SND_STOP )
+ {
+ Assert( filter.IsReliable() );
+ }
+
+ // Compute the sound origin
+ if ( pOrigin )
+ {
+ VectorCopy( *pOrigin, sound.vOrigin );
+ }
+ else if ( pSoundEmittingEntity )
+ {
+ IServerEntity *serverEntity = pSoundEmittingEntity->GetIServerEntity();
+ if ( serverEntity )
+ {
+ CM_WorldSpaceCenter( serverEntity->GetCollideable(), &sound.vOrigin );
+ }
+ }
+
+ // Add actual sound origin to vector if requested
+ if ( pUtlVecOrigins )
+ {
+ (*pUtlVecOrigins).AddToTail( sound.vOrigin );
+ }
+
+ // set sound delay
+ if ( soundtime != 0.0f )
+ {
+ // add one tick since server time ends at the current tick
+ // we'd rather delay sounds slightly than skip the beginning samples
+ // so add one tick of latency
+ soundtime += sv.GetTickInterval();
+
+ sound.fDelay = soundtime - sv.GetFinalTickTime();
+ sound.nFlags |= SND_DELAY;
+#if 0
+ static float lastSoundTime = 0;
+ Msg("SV: [%.3f] Play %s at %.3f\n", soundtime - lastSoundTime, pSample, soundtime );
+ lastSoundTime = soundtime;
+#endif
+ }
+
+ // find precache number for sound
+
+ // if this is a sentence, get sentence number
+ if ( pSample && TestSoundChar(pSample, CHAR_SENTENCE) )
+ {
+ sound.bIsSentence = true;
+ sound.nSoundNum = Q_atoi( PSkipSoundChars(pSample) );
+ if ( sound.nSoundNum >= VOX_SentenceCount() )
+ {
+ ConMsg("SV_StartSound: invalid sentence number: %s", PSkipSoundChars(pSample));
+ return;
+ }
+ }
+ else
+ {
+ sound.bIsSentence = false;
+ sound.nSoundNum = sv.LookupSoundIndex( pSample );
+ if ( !sound.nSoundNum || !sv.GetSound( sound.nSoundNum ) )
+ {
+ ConMsg ("SV_StartSound: %s not precached (%d)\n", pSample, sound.nSoundNum );
+ return;
+ }
+ }
+
+ // now sound message is complete, send to clients in filter
+ sv.BroadcastSound( sound, filter );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Sets bits of playerbits based on valid multicast recipients
+// Input : usepas -
+// origin -
+// playerbits -
+//-----------------------------------------------------------------------------
+void SV_DetermineMulticastRecipients( bool usepas, const Vector& origin, CBitVec< ABSOLUTE_PLAYER_LIMIT >& playerbits )
+{
+ // determine cluster for origin
+ int cluster = CM_LeafCluster( CM_PointLeafnum( origin ) );
+ byte pvs[MAX_MAP_LEAFS/8];
+ int visType = usepas ? DVIS_PAS : DVIS_PVS;
+ const byte *pMask = CM_Vis( pvs, sizeof(pvs), cluster, visType );
+
+ playerbits.ClearAll();
+
+ // Check for relevent clients
+ for (int i = 0; i < sv.GetClientCount(); i++ )
+ {
+ CGameClient *pClient = sv.Client( i );
+
+ if ( !pClient->IsActive() )
+ continue;
+
+ // HACK: Should above also check pClient->spawned instead of this
+ if ( !pClient->edict || pClient->edict->IsFree() || pClient->edict->GetUnknown() == NULL )
+ continue;
+
+ // Always add the or Replay client
+#if defined( REPLAY_ENABLED )
+ if ( pClient->IsHLTV() || pClient->IsReplay() )
+#else
+ if ( pClient->IsHLTV() )
+#endif
+ {
+ playerbits.Set( i );
+ continue;
+ }
+
+ Vector vecEarPosition;
+ serverGameClients->ClientEarPosition( pClient->edict, &vecEarPosition );
+
+ int iBitNumber = CM_LeafCluster( CM_PointLeafnum( vecEarPosition ) );
+ if ( !(pMask[iBitNumber>>3] & (1<<(iBitNumber&7)) ) )
+ continue;
+
+ playerbits.Set( i );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Write single ConVar change to all connected clients
+// Input : *var -
+// *newValue -
+//-----------------------------------------------------------------------------
+void SV_ReplicateConVarChange( ConVar const *var, const char *newValue )
+{
+ Assert( var );
+ Assert( var->IsFlagSet( FCVAR_REPLICATED ) );
+ Assert( newValue );
+
+ if ( !sv.IsActive() || !sv.IsMultiplayer() )
+ return;
+
+ NET_SetConVar cvarMsg( var->GetName(), Host_CleanupConVarStringValue( newValue ) );
+
+ sv.BroadcastMessage( cvarMsg );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Execute a command on all clients or a particular client
+// Input : *var -
+// *newValue -
+//-----------------------------------------------------------------------------
+void SV_ExecuteRemoteCommand( const char *pCommand, int nClientSlot )
+{
+ if ( !sv.IsActive() || !sv.IsMultiplayer() )
+ return;
+
+ NET_StringCmd cmdMsg( pCommand );
+
+ if ( nClientSlot >= 0 )
+ {
+ CEngineSingleUserFilter filter( nClientSlot + 1, true );
+ sv.BroadcastMessage( cmdMsg, filter );
+ }
+ else
+ {
+ sv.BroadcastMessage( cmdMsg );
+ }
+}
+
+
+
+/*
+==============================================================================
+
+CLIENT SPAWNING
+
+==============================================================================
+*/
+
+CGameServer::CGameServer()
+{
+ m_nMaxClientsLimit = 0;
+ m_pPureServerWhitelist = NULL;
+ m_bHibernating = false;
+ m_bLoadedPlugins = false;
+ V_memset( m_szMapname, 0, sizeof( m_szMapname ) );
+ V_memset( m_szMapFilename, 0, sizeof( m_szMapFilename ) );
+}
+
+
+CGameServer::~CGameServer()
+{
+ if ( m_pPureServerWhitelist )
+ m_pPureServerWhitelist->Release();
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Disconnects the client and cleans out the m_pEnt CBasePlayer container object
+// Input : *clientedict -
+//-----------------------------------------------------------------------------
+void CGameServer::RemoveClientFromGame( CBaseClient *client )
+{
+ CGameClient *pClient = (CGameClient*)client;
+
+ // we must have an active server and a spawned client
+ // If we are a local server and we're disconnecting just return
+ if ( !pClient->edict || !pClient->IsSpawned() || !IsActive() || (pClient->GetNetChannel() && pClient->GetNetChannel()->IsLoopback() ) )
+ return;
+
+ Assert( g_pServerPluginHandler );
+
+ g_pServerPluginHandler->ClientDisconnect( pClient->edict );
+ // release the DLL entity that's attached to this edict, if any
+ serverGameEnts->FreeContainingEntity( pClient->edict );
+
+}
+
+static int s_iNetSpikeValue = -1;
+
+int GetNetSpikeValue()
+{
+ // Read from the command line the first time
+ if ( s_iNetSpikeValue < 0 )
+ s_iNetSpikeValue = Max( V_atoi( CommandLine()->ParmValue( "-netspike", "0" ) ), 0 );
+ return s_iNetSpikeValue;
+}
+
+const char szSvNetSpikeUsageText[] =
+ "Write network trace if amount of data sent to client exceeds N bytes. Use zero to disable tracing.\n"
+ "Note that having this enabled, even if never triggered, impacts performance. Set to zero when not in use.\n"
+ "For compatibility reasons, this command can be initialized on the command line with the -netspike option.";
+
+static void sv_netspike_f( const CCommand &args )
+{
+ if ( args.ArgC() != 2 )
+ {
+ Msg( "%s\n\n", szSvNetSpikeUsageText );
+ Msg( "sv_netspike value is currently %d\n", GetNetSpikeValue() );
+ return;
+ }
+
+ s_iNetSpikeValue = Max( V_atoi( args.Arg( 1 ) ), 0 );
+}
+
+static ConCommand sv_netspike( "sv_netspike", sv_netspike_f, szSvNetSpikeUsageText
+);
+
+CBaseClient *CGameServer::CreateNewClient(int slot )
+{
+ CBaseClient *pClient = new CGameClient( slot, this );
+ return pClient;
+}
+
+
+
+/*
+================
+SV_FinishCertificateCheck
+
+For LAN connections, make sure we don't have too many people with same cd key hash
+For Authenticated net connections, check the certificate and also double check won userid
+ from that certificate
+================
+*/
+bool CGameServer::FinishCertificateCheck( netadr_t &adr, int nAuthProtocol, const char *szRawCertificate, int clientChallenge )
+{
+ // Now check auth information
+ switch ( nAuthProtocol )
+ {
+ default:
+ case PROTOCOL_AUTHCERTIFICATE:
+ RejectConnection( adr, clientChallenge, "#GameUI_ServerAuthDisabled");
+ return false;
+
+ case PROTOCOL_STEAM:
+ return true; // the SteamAuthServer() state machine checks this
+ break;
+
+ case PROTOCOL_HASHEDCDKEY:
+ if ( AllowDebugDedicatedServerOutsideSteam() )
+ return true;
+
+ if ( !Host_IsSinglePlayerGame() || sv.IsDedicated()) // PROTOCOL_HASHEDCDKEY isn't allowed for multiplayer servers
+ {
+ RejectConnection( adr, clientChallenge, "#GameUI_ServerCDKeyAuthInvalid" );
+ return false;
+ }
+
+ if ( Q_strlen( szRawCertificate ) != 32 )
+ {
+ RejectConnection( adr, clientChallenge, "#GameUI_ServerInvalidCDKey" );
+ return false;
+ }
+
+ int nHashCount = 0;
+
+ // Now make sure that this hash isn't "overused"
+ for ( int i=0; i< GetClientCount(); i++ )
+ {
+ CBaseClient *pClient = Client(i);
+
+ if ( !pClient->IsConnected() )
+ continue;
+
+ if ( Q_strnicmp ( szRawCertificate, pClient->m_GUID, SIGNED_GUID_LEN ) )
+ continue;
+
+ nHashCount++;
+ }
+
+ if ( nHashCount >= MAX_IDENTICAL_CDKEYS )
+ {
+ RejectConnection( adr, clientChallenge, "#GameUI_ServerCDKeyInUse" );
+ return false;
+ }
+ break;
+ }
+
+ return true;
+}
+
+/*
+=============================================================================
+
+The PVS must include a small area around the client to allow head bobbing
+or other small motion on the client side. Otherwise, a bob might cause an
+entity that should be visible to not show up, especially when the bob
+crosses a waterline.
+
+=============================================================================
+*/
+
+static int s_FatBytes;
+static byte* s_pFatPVS = 0;
+
+CUtlVector<int> g_AreasNetworked;
+
+static void SV_AddToFatPVS( const Vector& org )
+{
+ int i;
+ byte pvs[MAX_MAP_LEAFS/8];
+
+ CM_Vis( pvs, sizeof(pvs), CM_LeafCluster( CM_PointLeafnum( org ) ), DVIS_PVS );
+ for (i=0 ; i<s_FatBytes ; i++)
+ {
+ s_pFatPVS[i] |= pvs[i];
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Zeroes out pvs, this way we can or together multiple pvs's for a player
+//-----------------------------------------------------------------------------
+void SV_ResetPVS( byte* pvs, int pvssize )
+{
+ s_pFatPVS = pvs;
+ s_FatBytes = Bits2Bytes(CM_NumClusters());
+
+ if ( s_FatBytes > pvssize )
+ {
+ Sys_Error( "SV_ResetPVS: Size %i too big for buffer %i\n", s_FatBytes, pvssize );
+ }
+
+ Q_memset (s_pFatPVS, 0, s_FatBytes);
+ g_AreasNetworked.RemoveAll();
+}
+
+/*
+=============
+Calculates a PVS that is the inclusive or of all leafs within 8 pixels of the
+given point.
+=============
+*/
+void SV_AddOriginToPVS( const Vector& origin )
+{
+ SV_AddToFatPVS( origin );
+ int area = CM_LeafArea( CM_PointLeafnum( origin ) );
+ int i;
+ for( i = 0; i < g_AreasNetworked.Count(); i++ )
+ {
+ if( g_AreasNetworked[i] == area )
+ {
+ return;
+ }
+ }
+ g_AreasNetworked.AddToTail( area );
+}
+
+void CGameServer::BroadcastSound( SoundInfo_t &sound, IRecipientFilter &filter )
+{
+ int num = filter.GetRecipientCount();
+
+ // don't add sounds while paused, unless we're in developer mode
+ if ( IsPaused() && !developer.GetInt() )
+ return;
+
+ for ( int i = 0; i < num; i++ )
+ {
+ int index = filter.GetRecipientIndex( i );
+
+ if ( index < 1 || index > GetClientCount() )
+ {
+ Msg( "CGameServer::BroadcastSound: Recipient Filter for sound (reliable: %s, init: %s) with bogus client index (%i) in list of %i clients\n",
+ filter.IsReliable() ? "yes" : "no",
+ filter.IsInitMessage() ? "yes" : "no",
+ index, num );
+
+ continue;
+ }
+
+ CGameClient *pClient = Client( index - 1 );
+
+ // client must be fully connect to hear sounds
+ if ( !pClient->IsActive() )
+ {
+ continue;
+ }
+
+ pClient->SendSound( sound, filter.IsReliable() );
+ }
+}
+
+bool CGameServer::IsInPureServerMode() const
+{
+ return (m_pPureServerWhitelist != NULL);
+}
+
+CPureServerWhitelist * CGameServer::GetPureServerWhitelist() const
+{
+ return m_pPureServerWhitelist;
+}
+
+//void OnHibernateWhenEmptyChanged( IConVar *var, const char *pOldValue, float flOldValue )
+//{
+// // We only need to do something special if we were preventing hibernation
+// // with sv_hibernate_when_empty but we would otherwise have been hibernating.
+// // In that case, punt all connected clients.
+// sv.UpdateHibernationState( );
+//}
+
+static bool s_bExitWhenEmpty = false;
+static ConVar sv_memlimit( "sv_memlimit", "0", 0,
+ "If set, whenever a game ends, if the total memory used by the server is "
+ "greater than this # of megabytes, the server will exit." );
+static ConVar sv_minuptimelimit( "sv_minuptimelimit", "0", 0,
+ "If set, whenever a game ends, if the server uptime is less than "
+ "this number of hours, the server will continue running regardless of sv_memlimit." );
+static ConVar sv_maxuptimelimit( "sv_maxuptimelimit", "0", 0,
+ "If set, whenever a game ends, if the server uptime exceeds "
+ "this number of hours, the server will exit." );
+
+#if 0
+static void sv_WasteMemory( void )
+{
+ uint8 *pWastedRam = new uint8[ 100 * 1024 * 1024 ];
+ memset( pWastedRam, 0xff, 100 * 1024 * 1024 ); // make sure it gets committed
+ Msg( "waste 100mb. using %dMB with an sv_memory_limit of %dMB\n", ApproximateProcessMemoryUsage() / ( 1024 * 1024 ), sv_memlimit.GetInt() );
+}
+
+static ConCommand sv_wastememory( "sv_wastememory", sv_WasteMemory, "Causes the server to allocate 100MB of ram and never free it", FCVAR_CHEAT );
+#endif
+
+
+static void sv_ShutDownCancel( void )
+{
+ if ( s_bExitWhenEmpty || ( s_timeForceShutdown > 0.0 ) )
+ {
+ ConMsg( "sv_shutdown canceled.\n" );
+ }
+ else
+ {
+ ConMsg( "sv_shutdown not pending.\n" );
+ }
+
+ s_bExitWhenEmpty = false;
+ s_timeForceShutdown = 0.0;
+}
+
+static ConCommand sv_shutdown_cancel( "sv_shutdown_cancel", sv_ShutDownCancel, "Cancels pending sv_shutdown command" );
+
+
+static void sv_ShutDown( void )
+{
+ if ( !sv.IsDedicated() )
+ {
+ Warning( "sv_shutdown only works on dedicated servers.\n" );
+ return;
+ }
+
+ s_bExitWhenEmpty = true;
+ Warning( "sv_shutdown command received.\n" );
+
+ double timeCurrent = Plat_FloatTime();
+ if ( sv.IsHibernating() || !sv.IsActive() )
+ {
+ Warning( "Server is inactive or hibernating. Shutting down right now\n" );
+ s_timeForceShutdown = timeCurrent + 5.0; // don't forget!
+ HostState_Shutdown();
+ }
+ else
+ {
+ // Check if we should update shutdown timeout
+ if ( s_timeForceShutdown == 0.0 && sv_shutdown_timeout_minutes.GetInt() > 0 )
+ s_timeForceShutdown = timeCurrent + sv_shutdown_timeout_minutes.GetInt() * 60.0;
+
+ // Print appropriate message
+ if ( s_timeForceShutdown > 0.0 )
+ {
+ Warning( "Server will shut down in %d seconds, or when it becomes empty.\n", (int)(s_timeForceShutdown - timeCurrent) );
+ }
+ else
+ {
+ Warning( "Server will shut down when it becomes empty.\n" );
+ }
+ }
+}
+
+static ConCommand sv_shutdown( "sv_shutdown", sv_ShutDown, "Sets the server to shutdown next time it's empty" );
+
+
+bool CGameServer::IsHibernating() const
+{
+ return m_bHibernating;
+}
+
+void CGameServer::SetHibernating( bool bHibernating )
+{
+ static double s_flPlatFloatTimeBeginUptime = Plat_FloatTime();
+
+ if ( m_bHibernating != bHibernating )
+ {
+ m_bHibernating = bHibernating;
+ Msg( m_bHibernating ? "Server is hibernating\n" : "Server waking up from hibernation\n" );
+ if ( m_bHibernating )
+ {
+ // see if we have any other connected bot clients
+ for ( int iClient = 0; iClient < m_Clients.Count(); iClient++ )
+ {
+ CBaseClient *pClient = m_Clients[iClient];
+ if ( pClient->IsFakeClient() && pClient->IsConnected() && !pClient->IsSplitScreenUser() && !pClient->IsReplay() && !pClient->IsHLTV() )
+ {
+ pClient->Disconnect( "Punting bot, server is hibernating" );
+ }
+ }
+
+ // A solo player using the game menu to return to lobby can leave the server paused
+ SetPaused( false );
+
+ // if we are hibernating, and we want to quit, quit
+ bool bExit = false;
+ if ( s_bExitWhenEmpty )
+ {
+ bExit = true;
+ Warning( "Server shutting down because sv_shutdown was requested and a server is empty.\n" );
+ }
+ else
+ {
+ if ( sv_memlimit.GetInt() )
+ {
+ if ( ApproximateProcessMemoryUsage() > 1024 * 1024 * sv_memlimit.GetInt() )
+ {
+ if ( ( sv_minuptimelimit.GetFloat() > 0 ) &&
+ ( ( Plat_FloatTime() - s_flPlatFloatTimeBeginUptime ) / 3600.0 < sv_minuptimelimit.GetFloat() ) )
+ {
+ Warning( "Server is using %dMB with an sv_memory_limit of %dMB, but will not shutdown because sv_minuptimelimit is %.3f hr while current uptime is %.3f\n",
+ ApproximateProcessMemoryUsage() / ( 1024 * 1024 ), sv_memlimit.GetInt(),
+ sv_minuptimelimit.GetFloat(), ( Plat_FloatTime() - s_flPlatFloatTimeBeginUptime ) / 3600.0 );
+ }
+ else
+ {
+ Warning( "Server shutting down because of using %dMB with an sv_memory_limit of %dMB\n", ApproximateProcessMemoryUsage() / ( 1024 * 1024 ), sv_memlimit.GetInt() );
+ bExit = true;
+ }
+ }
+ }
+
+ if ( ( sv_maxuptimelimit.GetFloat() > 0 ) &&
+ ( ( Plat_FloatTime() - s_flPlatFloatTimeBeginUptime ) / 3600.0 > sv_maxuptimelimit.GetFloat() ) )
+ {
+ Warning( "Server will shutdown because sv_maxuptimelimit is %.3f hr while current uptime is %.3f, using %dMB with an sv_memory_limit of %dMB\n",
+ sv_maxuptimelimit.GetFloat(), ( Plat_FloatTime() - s_flPlatFloatTimeBeginUptime ) / 3600.0,
+ ApproximateProcessMemoryUsage() / ( 1024 * 1024 ), sv_memlimit.GetInt() );
+ bExit = true;
+ }
+ }
+
+//#ifdef _LINUX
+// // if we are a child process running forked, we want to exit now. We want to "really" exit. no destructors, no nothing
+// if ( IsChildProcess() ) // are we a subprocess?
+// {
+// syscall( SYS_exit_group, 0 ); // we are not going to perform a normal c++ exit. We _dont_ want to run destructors, etc.
+// }
+//#endif
+ if ( bExit )
+ {
+ HostState_Shutdown();
+ }
+// ResetGameConVarsToDefaults();
+ }
+
+ if ( g_iServerGameDLLVersion >= 8 )
+ {
+ serverGameDLL->SetServerHibernation( m_bHibernating );
+ }
+
+ // Heartbeat ASAP
+ Steam3Server().SendUpdatedServerDetails();
+ if ( Steam3Server().SteamGameServer() )
+ {
+ Steam3Server().SteamGameServer()->ForceHeartbeat();
+ }
+ }
+}
+
+void CGameServer::UpdateHibernationState()
+{
+ if ( !IsDedicated() || sv.m_State == ss_dead )
+ return;
+
+ // is this the last client disconnecting?
+ bool bHaveAnyClients = false;
+
+ // see if we have any other connected clients
+ for ( int iClient = 0; iClient < m_Clients.Count(); iClient++ )
+ {
+ CBaseClient *pClient = m_Clients[iClient];
+ // don't consider the client being removed, it still shows as connected but won't be in a moment
+ if ( pClient->IsConnected() && ( pClient->IsSplitScreenUser() || !pClient->IsFakeClient() ) )
+ {
+ bHaveAnyClients = true;
+ break;
+ }
+ }
+
+ bool hibernateFromGCServer = ( g_iServerGameDLLVersion < 8 ) || !serverGameDLL->GetServerGCLobby() || serverGameDLL->GetServerGCLobby()->ShouldHibernate();
+
+ // If a restart was requested and we're supposed to reboot after XX amount of time, reboot the server.
+ if ( !bHaveAnyClients && ( sv_maxuptimelimit.GetFloat() > 0.0f ) &&
+ Steam3Server().SteamGameServer() && Steam3Server().SteamGameServer()->WasRestartRequested() )
+ {
+ hibernateFromGCServer = true;
+ s_bExitWhenEmpty = true;
+ }
+
+ //SetHibernating( sv_hibernate_when_empty.GetBool() && hibernateFromGCServer && !bHaveAnyClients );
+ SetHibernating( hibernateFromGCServer && !bHaveAnyClients );
+}
+
+void CGameServer::FinishRestore()
+{
+#ifndef SWDS
+ CSaveRestoreData currentLevelData;
+ char name[MAX_OSPATH];
+
+ if ( !m_bLoadgame )
+ return;
+
+ g_ServerGlobalVariables.pSaveData = &currentLevelData;
+ // Build the adjacent map list
+ serverGameDLL->BuildAdjacentMapList();
+
+ if ( !saverestore->IsXSave() )
+ {
+ Q_snprintf( name, sizeof( name ), "%s%s.HL2", saverestore->GetSaveDir(), m_szMapname );
+ }
+ else
+ {
+ Q_snprintf( name, sizeof( name ), "%s:\\%s.HL2", GetCurrentMod(), m_szMapname );
+ }
+
+ Q_FixSlashes( name );
+
+ saverestore->RestoreClientState( name, false );
+
+ if ( g_ServerGlobalVariables.eLoadType == MapLoad_Transition )
+ {
+ for ( int i = 0; i < currentLevelData.levelInfo.connectionCount; i++ )
+ {
+ saverestore->RestoreAdjacenClientState( currentLevelData.levelInfo.levelList[i].mapName );
+ }
+ }
+
+ saverestore->OnFinishedClientRestore();
+
+ g_ServerGlobalVariables.pSaveData = NULL;
+
+ // Reset
+ m_bLoadgame = false;
+ saverestore->SetIsXSave( IsX360() );
+#endif
+}
+
+void CGameServer::CopyTempEntities( CFrameSnapshot* pSnapshot )
+{
+ Assert( pSnapshot->m_pTempEntities == NULL );
+
+ if ( m_TempEntities.Count() > 0 )
+ {
+ // copy temp entities if any
+ pSnapshot->m_nTempEntities = m_TempEntities.Count();
+
+ pSnapshot->m_pTempEntities = new CEventInfo*[pSnapshot->m_nTempEntities];
+
+ Q_memcpy( pSnapshot->m_pTempEntities, m_TempEntities.Base(), m_TempEntities.Count() * sizeof( CEventInfo * ) );
+
+ // clear server list
+ m_TempEntities.RemoveAll();
+ }
+}
+
+// If enabled, random crashes start to appear in WriteTempEntities, etc. It looks like
+// one thread can be in WriteDeltaEntities while another is in WriteTempEntities, and both are
+// partying on g_FrameSnapshotManager.m_FrameSnapshots. Bruce sent e-mail from a customer
+// that stated these crashes don't occur when parallel_sendsnapshot is disabled. Zoid said:
+//
+// Easiest is just turn off parallel snapshots, it's not much of a win on servers where we
+// are running many instances anyway. It's off in Dota and CSGO dedicated servers.
+//
+// Bruce also had a patch to disable this in //ValveGames/staging/game/tf/cfg/unencrypted/print_instance_config.py
+static ConVar sv_parallel_sendsnapshot( "sv_parallel_sendsnapshot", "0" );
+
+static void SV_ParallelSendSnapshot( CGameClient *& pClient )
+{
+ // HLTV and replay clients must be handled on the main thread
+ // because they access and modify global state. Skip them.
+ if ( pClient->IsHLTV() )
+ return;
+#if defined( REPLAY_ENABLED )
+ if ( pClient->IsReplay() )
+ return;
+#endif
+ CClientFrame *pFrame = pClient->GetSendFrame();
+ if ( pFrame )
+ {
+ pClient->SendSnapshot( pFrame );
+ pClient->UpdateSendState();
+ }
+ // Replace this parallel processing array entry with NULL so
+ // that the calling code knows that this entry was handled.
+ pClient = NULL;
+}
+
+void CGameServer::SendClientMessages ( bool bSendSnapshots )
+{
+ VPROF_BUDGET( "SendClientMessages", VPROF_BUDGETGROUP_OTHER_NETWORKING );
+
+ // build individual updates
+ int receivingClientCount = 0;
+ CGameClient* pReceivingClients[ABSOLUTE_PLAYER_LIMIT];
+ for (int i=0; i< GetClientCount(); i++ )
+ {
+ CGameClient* client = Client(i);
+
+ // Update Host client send state...
+ if ( !client->ShouldSendMessages() )
+ continue;
+
+ // Append the unreliable data (player updates and packet entities)
+ if ( bSendSnapshots && client->IsActive() )
+ {
+ // Add this client to the list of clients we're gonna send to.
+ pReceivingClients[receivingClientCount] = client;
+ ++receivingClientCount;
+ }
+ else
+ {
+ // Connected, but inactive, just send reliable, sequenced info.
+ if ( client->IsFakeClient() )
+ continue;
+
+ // if client never send a netchannl packet yet, send S2C_CONNECTION
+ // because it could get lost in multiplayer
+ if ( NET_IsMultiplayer() && client->m_NetChannel->GetSequenceNr(FLOW_INCOMING) == 0 )
+ {
+ NET_OutOfBandPrintf ( m_Socket, client->m_NetChannel->GetRemoteAddress(), "%c00000000000000", S2C_CONNECTION );
+ }
+
+#ifdef SHARED_NET_STRING_TABLES
+ sv.m_StringTables->TriggerCallbacks( client->m_nDeltaTick );
+#endif
+
+ client->m_NetChannel->Transmit();
+ client->UpdateSendState();
+ }
+ }
+
+ if ( receivingClientCount )
+ {
+ // if any client wants an update, take new snapshot now
+ CFrameSnapshot* pSnapshot = framesnapshotmanager->TakeTickSnapshot( m_nTickCount );
+
+ // copy temp ents references to pSnapshot
+ CopyTempEntities( pSnapshot );
+
+ // Compute the client packs
+ SV_ComputeClientPacks( receivingClientCount, pReceivingClients, pSnapshot );
+
+ if ( receivingClientCount > 1 && sv_parallel_sendsnapshot.GetBool() )
+ {
+ // SV_ParallelSendSnapshot will not process HLTV or Replay clients as they
+ // must be run on the main thread due to un-threadsafe global state access.
+ // It will replace anything that it does process with a NULL pointer.
+ ParallelProcess( "SV_ParallelSendSnapshot", pReceivingClients, receivingClientCount, &SV_ParallelSendSnapshot );
+ }
+
+ for (int i = 0; i < receivingClientCount; ++i)
+ {
+ CGameClient *pClient = pReceivingClients[i];
+ if ( !pClient )
+ continue;
+ CClientFrame *pFrame = pClient->GetSendFrame();
+ if ( !pFrame )
+ continue;
+ pClient->SendSnapshot( pFrame );
+ pClient->UpdateSendState();
+ }
+
+ pSnapshot->ReleaseReference();
+ }
+}
+
+void CGameServer::SetMaxClients( int number )
+{
+ m_nMaxclients = clamp( number, 1, m_nMaxClientsLimit );
+
+ if ( tv_enable.GetBool() == false )
+ {
+ ConMsg( "maxplayers set to %i\n", m_nMaxclients );
+ }
+ else
+ {
+ ConMsg( "maxplayers set to %i (extra slot was added for SourceTV)\n", m_nMaxclients );
+ }
+
+ deathmatch.SetValue( m_nMaxclients > 1 );
+}
+
+//-----------------------------------------------------------------------------
+// A potential optimization of the client data sending; the optimization
+// is based around the fact that we think that we're spending all our time in
+// cache misses since we are accessing so much memory
+//-----------------------------------------------------------------------------
+
+
+
+
+
+/*
+==============================================================================
+SERVER SPAWNING
+
+==============================================================================
+*/
+
+void SV_WriteVoiceCodec(bf_write &pBuf)
+{
+ // Only send in multiplayer. Otherwise, we don't want voice.
+
+ const char *pCodec = sv.IsMultiplayer() ? sv_voicecodec.GetString() : NULL;
+ int nSampleRate = pCodec ? Voice_GetDefaultSampleRate( pCodec ) : 0;
+ SVC_VoiceInit voiceinit( pCodec, nSampleRate );
+ voiceinit.WriteToBuffer( pBuf );
+}
+
+// Gets voice data from a client and forwards it to anyone who can hear this client.
+ConVar voice_debugfeedbackfrom( "voice_debugfeedbackfrom", "0" );
+
+void SV_BroadcastVoiceData(IClient * pClient, int nBytes, char * data, int64 xuid )
+{
+ // Disable voice?
+ if( !sv_voiceenable.GetInt() )
+ return;
+
+ // Build voice message once
+ SVC_VoiceData voiceData;
+ voiceData.m_nFromClient = pClient->GetPlayerSlot();
+ voiceData.m_nLength = nBytes * 8; // length in bits
+ voiceData.m_DataOut = data;
+ voiceData.m_xuid = xuid;
+
+ if ( voice_debugfeedbackfrom.GetBool() )
+ {
+ Msg( "Sending voice from: %s - playerslot: %d\n", pClient->GetClientName(), pClient->GetPlayerSlot() + 1 );
+ }
+
+ for(int i=0; i < sv.GetClientCount(); i++)
+ {
+ IClient *pDestClient = sv.GetClient(i);
+
+ bool bSelf = (pDestClient == pClient);
+
+ // Only send voice to active clients
+ if( !pDestClient->IsActive() )
+ continue;
+
+ // Does the game code want cl sending to this client?
+
+ bool bHearsPlayer = pDestClient->IsHearingClient( voiceData.m_nFromClient );
+ voiceData.m_bProximity = pDestClient->IsProximityHearingClient( voiceData.m_nFromClient );
+
+ if ( IsX360() && bSelf == true )
+ continue;
+
+ if ( !bHearsPlayer && !bSelf )
+ continue;
+
+ voiceData.m_nLength = nBytes * 8;
+
+ // Is loopback enabled?
+ if( !bHearsPlayer )
+ {
+ // Still send something, just zero length (this is so the client
+ // can display something that shows knows the server knows it's talking).
+ voiceData.m_nLength = 0;
+ }
+
+ pDestClient->SendNetMsg( voiceData );
+ }
+}
+
+
+// UNDONE: "player.mdl" ??? This should be set by name in the DLL
+/*
+================
+SV_CreateBaseline
+
+================
+*/
+void SV_CreateBaseline (void)
+{
+ SV_WriteVoiceCodec( sv.m_Signon );
+
+ ServerClass *pClasses = serverGameDLL->GetAllServerClasses();
+
+ // Send SendTable info.
+ if ( sv_sendtables.GetInt() )
+ {
+#ifdef _XBOX
+ Error( "sv_sendtables not allowed on XBOX." );
+#endif
+ sv.m_FullSendTablesBuffer.EnsureCapacity( NET_MAX_PAYLOAD );
+ sv.m_FullSendTables.StartWriting( sv.m_FullSendTablesBuffer.Base(), sv.m_FullSendTablesBuffer.Count() );
+
+ SV_WriteSendTables( pClasses, sv.m_FullSendTables );
+
+ if ( sv.m_FullSendTables.IsOverflowed() )
+ {
+ Host_Error("SV_CreateBaseline: WriteSendTables overflow.\n" );
+ return;
+ }
+
+ // Send class descriptions.
+ SV_WriteClassInfos(pClasses, sv.m_FullSendTables);
+
+ if ( sv.m_FullSendTables.IsOverflowed() )
+ {
+ Host_Error("SV_CreateBaseline: WriteClassInfos overflow.\n" );
+ return;
+ }
+ }
+
+ // If we're using the local network backdoor, we'll never use the instance baselines.
+ if ( !g_pLocalNetworkBackdoor )
+ {
+ int count = 0;
+ int bytes = 0;
+
+ for ( int entnum = 0; entnum < sv.num_edicts ; entnum++)
+ {
+ // get the current server version
+ edict_t *edict = sv.edicts + entnum;
+
+ if ( edict->IsFree() || !edict->GetUnknown() )
+ continue;
+
+ ServerClass *pClass = edict->GetNetworkable() ? edict->GetNetworkable()->GetServerClass() : 0;
+
+ if ( !pClass )
+ {
+ Assert( pClass );
+ continue; // no Class ?
+ }
+
+ if ( pClass->m_InstanceBaselineIndex != INVALID_STRING_INDEX )
+ continue; // we already have a baseline for this class
+
+ SendTable *pSendTable = pClass->m_pTable;
+
+ //
+ // create entity baseline
+ //
+
+ ALIGN4 char packedData[MAX_PACKEDENTITY_DATA] ALIGN4_POST;
+ bf_write writeBuf( "SV_CreateBaseline->writeBuf", packedData, sizeof( packedData ) );
+
+
+ // create basline from zero values
+ if ( !SendTable_Encode(
+ pSendTable,
+ edict->GetUnknown(),
+ &writeBuf,
+ entnum,
+ NULL,
+ false
+ ) )
+ {
+ Host_Error("SV_CreateBaseline: SendTable_Encode returned false (ent %d).\n", entnum);
+ }
+
+ // copy baseline into baseline stringtable
+ SV_EnsureInstanceBaseline( pClass, entnum, packedData, writeBuf.GetNumBytesWritten() );
+
+ bytes += writeBuf.GetNumBytesWritten();
+ count ++;
+ }
+ DevMsg("Created class baseline: %i classes, %i bytes.\n", count,bytes);
+ }
+
+ g_GameEventManager.ReloadEventDefinitions();
+
+ SVC_GameEventList gameevents;
+ char data[NET_MAX_PAYLOAD];
+ gameevents.m_DataOut.StartWriting( data, sizeof(data) );
+
+ g_GameEventManager.WriteEventList( &gameevents );
+ gameevents.WriteToBuffer( sv.m_Signon );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Ensure steam context is initialized for multiplayer gameservers. Idempotent.
+//-----------------------------------------------------------------------------
+void SV_InitGameServerSteam()
+{
+ if ( sv.IsMultiplayer() )
+ {
+ Steam3Server().Activate( CSteam3Server::eServerTypeNormal );
+ sv.SetQueryPortFromSteamServer();
+ if ( serverGameDLL && g_iServerGameDLLVersion >= 6 )
+ {
+ serverGameDLL->GameServerSteamAPIActivated();
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : runPhysics -
+//-----------------------------------------------------------------------------
+bool SV_ActivateServer()
+{
+ COM_TimestampedLog( "SV_ActivateServer" );
+#ifndef SWDS
+ EngineVGui()->UpdateProgressBar(PROGRESS_ACTIVATESERVER);
+#endif
+
+ COM_TimestampedLog( "serverGameDLL->ServerActivate" );
+
+ host_state.interval_per_tick = serverGameDLL->GetTickInterval();
+ if ( host_state.interval_per_tick < MINIMUM_TICK_INTERVAL ||
+ host_state.interval_per_tick > MAXIMUM_TICK_INTERVAL )
+ {
+ Sys_Error( "GetTickInterval returned bogus tick interval (%f)[%f to %f is valid range]", host_state.interval_per_tick,
+ MINIMUM_TICK_INTERVAL, MAXIMUM_TICK_INTERVAL );
+ }
+
+ Msg( "SV_ActivateServer: setting tickrate to %.1f\n", 1.0f / host_state.interval_per_tick );
+
+ bool bPrevState = networkStringTableContainerServer->Lock( false );
+ // Activate the DLL server code
+ g_pServerPluginHandler->ServerActivate( sv.edicts, sv.num_edicts, sv.GetMaxClients() );
+
+ // all setup is completed, any further precache statements are errors
+ sv.m_State = ss_active;
+
+ COM_TimestampedLog( "SV_CreateBaseline" );
+
+ // create a baseline for more efficient communications
+ SV_CreateBaseline();
+
+ sv.allowsignonwrites = false;
+
+ // set skybox name
+ ConVar const *skyname = g_pCVar->FindVar( "sv_skyname" );
+
+ if ( skyname )
+ {
+ Q_strncpy( sv.m_szSkyname, skyname->GetString(), sizeof( sv.m_szSkyname ) );
+ }
+ else
+ {
+ Q_strncpy( sv.m_szSkyname, "unknown", sizeof( sv.m_szSkyname ) );
+ }
+
+ COM_TimestampedLog( "Send Reconnects" );
+
+ // Tell connected clients to reconnect
+ sv.ReconnectClients();
+
+ // Tell what kind of server has been started.
+ if ( sv.IsMultiplayer() )
+ {
+ ConDMsg ("%i player server started\n", sv.GetMaxClients() );
+ }
+ else
+ {
+ ConDMsg ("Game started\n");
+ }
+
+ // Replay setup
+#if defined( REPLAY_ENABLED )
+ if ( g_pReplay && g_pReplay->IsReplayEnabled() )
+ {
+ if ( !replay )
+ {
+ replay = new CReplayServer;
+ replay->Init( NET_IsDedicated() );
+ }
+
+ if ( replay->IsActive() )
+ {
+ // replay master already running, just activate client
+ replay->m_MasterClient->ActivatePlayer();
+ replay->StartMaster( replay->m_MasterClient );
+ }
+ else
+ {
+ // create new replay client
+ ConVarRef replay_name( "replay_name" );
+ CGameClient *pClient = (CGameClient*)sv.CreateFakeClient( replay_name.GetString() );
+ replay->StartMaster( pClient );
+ }
+ }
+ else
+ {
+
+ // make sure replay is disabled
+ if ( replay )
+ replay->Shutdown();
+ }
+#endif // #if defined( REPLAY_ENABLED )
+
+ // HLTV setup
+ if ( tv_enable.GetBool() )
+ {
+ if ( CommandLine()->FindParm("-nohltv") )
+ {
+ // let user know that SourceTV will not work
+ ConMsg ("SourceTV is disabled on this server.\n");
+ }
+ else
+ {
+ // create SourceTV object if not already there
+ if ( !hltv )
+ {
+ hltv = new CHLTVServer;
+ hltv->Init( NET_IsDedicated() );
+ }
+
+ if ( hltv->IsActive() && hltv->IsMasterProxy() )
+ {
+ // HLTV master already running, just activate client
+ hltv->m_MasterClient->ActivatePlayer();
+ hltv->StartMaster( hltv->m_MasterClient );
+ }
+ else
+ {
+ // create new HLTV client
+ CGameClient *pClient = (CGameClient*)sv.CreateFakeClient( tv_name.GetString() );
+ hltv->StartMaster( pClient );
+ }
+ }
+ }
+ else
+ {
+
+ // make sure HLTV is disabled
+ if ( hltv )
+ hltv->Shutdown();
+ }
+
+ if (sv.IsDedicated())
+ {
+ // purge unused models and their data hierarchy (materials, shaders, etc)
+ modelloader->PurgeUnusedModels();
+ }
+
+ SV_InitGameServerSteam();
+ networkStringTableContainerServer->Lock( bPrevState );
+
+ // Heartbeat the master server in case we turned SrcTV on or off.
+ Steam3Server().SendUpdatedServerDetails();
+ if ( Steam3Server().SteamGameServer() )
+ {
+ Steam3Server().SteamGameServer()->ForceHeartbeat();
+ }
+
+ COM_TimestampedLog( "SV_ActivateServer(finished)" );
+
+ return true;
+}
+
+#include "tier0/memdbgoff.h"
+
+static void SV_AllocateEdicts()
+{
+ sv.edicts = (edict_t *)Hunk_AllocName( sv.max_edicts*sizeof(edict_t), "edicts" );
+
+ COMPILE_TIME_ASSERT( MAX_EDICT_BITS+1 <= 8*sizeof(sv.edicts[0].m_EdictIndex) );
+
+ // Invoke the constructor so the vtable is set correctly..
+ for (int i = 0; i < sv.max_edicts; ++i)
+ {
+ new( &sv.edicts[i] ) edict_t;
+ sv.edicts[i].m_EdictIndex = i;
+ sv.edicts[i].freetime = 0;
+ }
+ ED_ClearFreeEdictList();
+
+ sv.edictchangeinfo = (IChangeInfoAccessor *)Hunk_AllocName( sv.max_edicts * sizeof( IChangeInfoAccessor ), "edictchangeinfo" );
+}
+
+#include "tier0/memdbgon.h"
+
+void CGameServer::ReloadWhitelist( const char *pMapName )
+{
+ // Always return - until we get the whitelist stuff resolved for TF2.
+ if ( m_pPureServerWhitelist )
+ {
+ m_pPureServerWhitelist->Release();
+ m_pPureServerWhitelist = NULL;
+ }
+
+ g_sv_pure_waiting_on_reload = false;
+
+ // Don't do sv_pure stuff in SP games.
+ if ( GetMaxClients() <= 1 )
+ return;
+
+ // Don't use the whitelist if sv_pure is not set.
+ if ( GetSvPureMode() < 0 )
+ return;
+
+ // There's a magic number we use in the steam.inf in P4 that we don't update.
+ // We can use this to detect if they are running out of P4, and if so, don't use the whitelist
+ const char *pszVersionInP4 = "2000";
+ if ( !Q_strcmp( GetSteamInfIDVersionInfo().szVersionString, pszVersionInP4 ) )
+ return;
+
+ m_pPureServerWhitelist = CPureServerWhitelist::Create( g_pFileSystem );
+
+ // Load it
+ m_pPureServerWhitelist->Load( GetSvPureMode() );
+
+ // Load user whitelists, if allowed
+ if ( GetSvPureMode() == 1 )
+ {
+
+ // Load the per-map whitelist.
+ const char *pMapWhitelistSuffix = "_whitelist.txt";
+ char testFilename[MAX_PATH] = "maps";
+ V_AppendSlash( testFilename, sizeof( testFilename ) );
+ V_strncat( testFilename, pMapName, sizeof( testFilename ) );
+ V_strncat( testFilename, pMapWhitelistSuffix, sizeof( testFilename ) );
+
+ KeyValues *kv = new KeyValues( "" );
+ if ( kv->LoadFromFile( g_pFileSystem, testFilename ) )
+ m_pPureServerWhitelist->LoadCommandsFromKeyValues( kv );
+ kv->deleteThis();
+ }
+
+}
+
+
+/*
+================
+SV_SpawnServer
+
+This is called at the start of each level
+================
+*/
+bool CGameServer::SpawnServer( const char *szMapName, const char *szMapFile, const char *startspot )
+{
+ int i;
+
+ Assert( serverGameClients );
+
+ if ( CommandLine()->FindParm( "-NoLoadPluginsForClient" ) != 0 )
+ {
+ if ( !m_bLoadedPlugins )
+ {
+ // Only load plugins once.
+ m_bLoadedPlugins = true;
+ g_pServerPluginHandler->LoadPlugins(); // load 3rd party plugins
+ }
+ }
+
+ // Reset the last used count on all models before beginning the new load -- The nServerCount value on models could
+ // be from a client load connecting to a different server, and we know we're at the beginning of a new load now.
+ modelloader->ResetModelServerCounts();
+
+ ReloadWhitelist( szMapName );
+
+ COM_TimestampedLog( "SV_SpawnServer(%s)", szMapName );
+#ifndef SWDS
+ EngineVGui()->UpdateProgressBar(PROGRESS_SPAWNSERVER);
+#endif
+ COM_SetupLogDir( szMapName );
+
+ g_Log.Open();
+ g_Log.Printf( "Loading map \"%s\"\n", szMapName );
+ g_Log.PrintServerVars();
+
+#ifndef SWDS
+ SCR_CenterStringOff();
+#endif
+
+ if ( startspot )
+ {
+ ConDMsg("Spawn Server: %s: [%s]\n", szMapName, startspot );
+ }
+ else
+ {
+ ConDMsg("Spawn Server: %s\n", szMapName );
+ }
+
+ // Any partially connected client will be restarted if the spawncount is not matched.
+ gHostSpawnCount = ++m_nSpawnCount;
+
+ //
+ // make cvars consistant
+ //
+ deathmatch.SetValue( IsMultiplayer() ? 1 : 0 );
+ if ( coop.GetInt() )
+ {
+ deathmatch.SetValue( 0 );
+ }
+
+ current_skill = (int)(skill.GetFloat() + 0.5);
+ current_skill = max( current_skill, 0 );
+ current_skill = min( current_skill, 3 );
+
+ skill.SetValue( (float)current_skill );
+
+ COM_TimestampedLog( "StaticPropMgr()->LevelShutdown()" );
+
+#if !defined( SWDS )
+ g_pShadowMgr->LevelShutdown();
+#endif // SWDS
+ StaticPropMgr()->LevelShutdown();
+
+ // if we have an hltv relay proxy running, stop it now
+ if ( hltv && !hltv->IsMasterProxy() )
+ {
+ hltv->Shutdown();
+ }
+
+ // NOTE: Replay system does not deal with relay proxies.
+
+ COM_TimestampedLog( "Host_FreeToLowMark" );
+
+ Host_FreeStateAndWorld( true );
+ Host_FreeToLowMark( true );
+
+ // Clear out the mapversion so it's reset when the next level loads. Needed for changelevels.
+ g_ServerGlobalVariables.mapversion = 0;
+
+ COM_TimestampedLog( "sv.Clear()" );
+
+ Clear();
+
+ COM_TimestampedLog( "framesnapshotmanager->LevelChanged()" );
+
+ // Clear out the state of the most recently sent packed entities from
+ // the snapshot manager
+ framesnapshotmanager->LevelChanged();
+
+ // set map name
+ Q_strncpy( m_szMapname, szMapName, sizeof( m_szMapname ) );
+ Q_strncpy( m_szMapFilename, szMapFile, sizeof( m_szMapFilename ) );
+
+ // set startspot
+ if (startspot)
+ {
+ Q_strncpy(m_szStartspot, startspot, sizeof( m_szStartspot ) );
+ }
+ else
+ {
+ m_szStartspot[0] = 0;
+ }
+
+ if ( g_FlushMemoryOnNextServer )
+ {
+ g_FlushMemoryOnNextServer = false;
+ if ( IsX360() )
+ {
+ g_pQueuedLoader->PurgeAll();
+ }
+ g_pDataCache->Flush();
+ g_pMaterialSystem->CompactMemory();
+ g_pFileSystem->AsyncFinishAll();
+#if !defined( SWDS )
+ extern CThreadMutex g_SndMutex;
+ g_SndMutex.Lock();
+ g_pFileSystem->AsyncSuspend();
+ g_pThreadPool->SuspendExecution();
+ MemAlloc_CompactHeap();
+ g_pThreadPool->ResumeExecution();
+ g_pFileSystem->AsyncResume();
+ g_SndMutex.Unlock();
+#endif // SWDS
+ }
+
+ // Preload any necessary data from the xzps:
+ g_pFileSystem->SetupPreloadData();
+ g_pMDLCache->InitPreloadData( false );
+
+ // Allocate server memory
+ max_edicts = MAX_EDICTS;
+
+
+ g_ServerGlobalVariables.maxEntities = max_edicts;
+ g_ServerGlobalVariables.maxClients = GetMaxClients();
+#ifndef SWDS
+ g_ClientGlobalVariables.network_protocol = PROTOCOL_VERSION;
+#endif
+
+ // Assume no entities beyond world and client slots
+ num_edicts = GetMaxClients()+1;
+
+ COM_TimestampedLog( "SV_AllocateEdicts" );
+
+ SV_AllocateEdicts();
+
+ serverGameEnts->SetDebugEdictBase( edicts );
+
+ allowsignonwrites = true;
+
+ serverclasses = 0; // number of unique server classes
+ serverclassbits = 0; // log2 of serverclasses
+
+ // Assign class ids to server classes here so we can encode temp ents into signon
+ // if needed
+ AssignClassIds();
+
+ COM_TimestampedLog( "Set up players" );
+
+ // allocate player data, and assign the values into the edicts
+ for ( i=0 ; i< GetClientCount() ; i++ )
+ {
+ CGameClient * pClient = Client(i);
+
+ // edict for a player is slot + 1, world = 0
+ pClient->edict = edicts + i + 1;
+
+ // Setup up the edict
+ InitializeEntityDLLFields( pClient->edict );
+ }
+
+ COM_TimestampedLog( "Set up players(done)" );
+
+ m_State = ss_loading;
+
+ // Set initial time values.
+ m_flTickInterval = host_state.interval_per_tick;
+ m_nTickCount = (int)( 1.0 / host_state.interval_per_tick ) + 1; // Start at appropriate 1
+
+ g_ServerGlobalVariables.tickcount = m_nTickCount;
+ g_ServerGlobalVariables.curtime = GetTime();
+
+ // Load the world model.
+ g_pFileSystem->AddSearchPath( szMapFile, "GAME", PATH_ADD_TO_HEAD );
+ g_pFileSystem->BeginMapAccess();
+
+ if ( !CommandLine()->FindParm( "-allowstalezip" ) )
+ {
+ if ( g_pFileSystem->FileExists( "stale.txt", "GAME" ) )
+ {
+ Warning( "This map is not final!! Needs to be rebuilt without -keepstalezip and without -onlyents\n" );
+ }
+ }
+
+ COM_TimestampedLog( "modelloader->GetModelForName(%s) -- Start", szMapFile );
+
+ host_state.SetWorldModel( modelloader->GetModelForName( szMapFile, IModelLoader::FMODELLOADER_SERVER ) );
+ if ( !host_state.worldmodel )
+ {
+ ConMsg( "Couldn't spawn server %s\n", szMapFile );
+ m_State = ss_dead;
+ g_pFileSystem->EndMapAccess();
+ return false;
+ }
+
+ COM_TimestampedLog( "modelloader->GetModelForName(%s) -- Finished", szMapFile );
+
+ if ( IsMultiplayer() && !IsX360() )
+ {
+#ifndef SWDS
+ EngineVGui()->UpdateProgressBar(PROGRESS_CRCMAP);
+#endif
+ // Server map CRC check.
+ V_memset( worldmapMD5.bits, 0, MD5_DIGEST_LENGTH );
+ if ( !MD5_MapFile( &worldmapMD5, szMapFile ) )
+ {
+ ConMsg( "Couldn't CRC server map: %s\n", szMapFile );
+ m_State = ss_dead;
+ g_pFileSystem->EndMapAccess();
+ return false;
+ }
+
+#ifndef SWDS
+ EngineVGui()->UpdateProgressBar(PROGRESS_CRCCLIENTDLL);
+#endif
+ }
+ else
+ {
+ V_memset( worldmapMD5.bits, 0, MD5_DIGEST_LENGTH );
+ }
+
+ m_StringTables = networkStringTableContainerServer;
+
+ COM_TimestampedLog( "SV_CreateNetworkStringTables" );
+
+#ifndef SWDS
+ EngineVGui()->UpdateProgressBar(PROGRESS_CREATENETWORKSTRINGTABLES);
+#endif
+
+ // Create network string tables ( including precache tables )
+ SV_CreateNetworkStringTables();
+
+ // Leave empty slots for models/sounds/generic (not for decals though)
+ PrecacheModel( "", 0 );
+ PrecacheGeneric( "", 0 );
+ PrecacheSound( "", 0 );
+
+ COM_TimestampedLog( "Precache world model (%s)", szMapFile );
+
+#ifndef SWDS
+ EngineVGui()->UpdateProgressBar(PROGRESS_PRECACHEWORLD);
+#endif
+ // Add in world
+ PrecacheModel( szMapFile, RES_FATALIFMISSING | RES_PRELOAD, host_state.worldmodel );
+
+ COM_TimestampedLog( "Precache brush models" );
+
+ // Add world submodels to the model cache
+ for ( i = 1 ; i < host_state.worldbrush->numsubmodels ; i++ )
+ {
+ // Add in world brush models
+ char localmodel[5]; // inline model names "*1", "*2" etc
+ Q_snprintf( localmodel, sizeof( localmodel ), "*%i", i );
+
+ PrecacheModel( localmodel, RES_FATALIFMISSING | RES_PRELOAD, modelloader->GetModelForName( localmodel, IModelLoader::FMODELLOADER_SERVER ) );
+ }
+
+#ifndef SWDS
+ EngineVGui()->UpdateProgressBar(PROGRESS_CLEARWORLD);
+#endif
+ COM_TimestampedLog( "SV_ClearWorld" );
+
+ // Clear world interaction links
+ // Loads and inserts static props
+ SV_ClearWorld();
+
+ //
+ // load the rest of the entities
+ //
+
+ COM_TimestampedLog( "InitializeEntityDLLFields" );
+
+ InitializeEntityDLLFields( edicts );
+
+ // Clear the free bit on the world edict (entindex: 0).
+ ED_ClearFreeFlag( &edicts[0] );
+
+ if (coop.GetFloat())
+ {
+ g_ServerGlobalVariables.coop = (coop.GetInt() != 0);
+ }
+ else
+ {
+ g_ServerGlobalVariables.deathmatch = (deathmatch.GetInt() != 0);
+ }
+
+ g_ServerGlobalVariables.mapname = MAKE_STRING( m_szMapname );
+ g_ServerGlobalVariables.startspot = MAKE_STRING( m_szStartspot );
+
+ GetTestScriptMgr()->CheckPoint( "map_load" );
+
+ // set game event
+ IGameEvent *event = g_GameEventManager.CreateEvent( "server_spawn" );
+ if ( event )
+ {
+ event->SetString( "hostname", host_name.GetString() );
+ event->SetString( "address", net_local_adr.ToString( false ) );
+ event->SetInt( "port", GetUDPPort() );
+ event->SetString( "game", com_gamedir );
+ event->SetString( "mapname", GetMapName() );
+ event->SetInt( "maxplayers", GetMaxClients() );
+ event->SetInt( "password", 0 ); // TODO
+#if defined( _WIN32 )
+ event->SetString( "os", "WIN32" );
+#elif defined ( LINUX )
+ event->SetString( "os", "LINUX" );
+#elif defined ( OSX )
+ event->SetString( "os", "OSX" );
+#else
+#error
+#endif
+ event->SetInt( "dedicated", IsDedicated() ? 1 : 0 );
+
+ g_GameEventManager.FireEvent( event );
+ }
+
+ COM_TimestampedLog( "SV_SpawnServer -- Finished" );
+
+ g_pFileSystem->EndMapAccess();
+ return true;
+}
+
+
+void CGameServer::UpdateMasterServerPlayers()
+{
+ if ( !Steam3Server().SteamGameServer() )
+ return;
+
+ for ( int i=0; i < GetClientCount() ; i++ )
+ {
+ CGameClient *client = Client(i);
+
+ if ( !client->IsConnected() )
+ continue;
+
+ CPlayerState *pl = serverGameClients->GetPlayerState( client->edict );
+ if ( !pl )
+ continue;
+
+ if ( !client->m_SteamID.IsValid() )
+ continue;
+
+ Steam3Server().SteamGameServer()->BUpdateUserData( client->m_SteamID, client->GetClientName(), pl->frags );
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// SV_IsSimulating
+//-----------------------------------------------------------------------------
+bool SV_IsSimulating( void )
+{
+ if ( sv.IsPaused() )
+ return false;
+
+#ifndef SWDS
+ // Don't simulate in single player if console is down or the bug UI is active and we're in a game
+ if ( !sv.IsMultiplayer() )
+ {
+ if ( g_LostVideoMemory )
+ return false;
+
+ // Don't simulate in single player if console is down or the bug UI is active and we're in a game
+ if ( cl.IsActive() && ( Con_IsVisible() || EngineVGui()->ShouldPause() ) )
+ return false;
+ }
+#endif //SWDS
+
+ return true;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Output : Returns true on success, false on failure.
+//-----------------------------------------------------------------------------
+bool SV_HasPlayers()
+{
+ /*int i;
+ for ( i = 0; i < sv.clients.Count(); i++ )
+ {
+ if ( sv.clients[ i ]->active )
+ {
+ return true;
+ }
+ }
+
+ return false; */
+
+ return sv.GetClientCount() > 0;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Run physics code (simulating == false means we're paused, but we'll still
+// allow player usercmds to be processed
+//-----------------------------------------------------------------------------
+void SV_Think( bool bIsSimulating )
+{
+ VPROF( "SV_Physics" );
+ tmZone( TELEMETRY_LEVEL1, TMZF_NONE, "SV_Think(%s)", bIsSimulating ? "simulating" : "not simulating" );
+
+// @FD The staging branch already did away with "frames" and wakes on tick
+// optimally. Currently the hibernating flag essentially means "is empty
+// and available to host a game," which is used for the GC matchmaking.
+ sv.UpdateHibernationState();
+
+ if ( s_timeForceShutdown > 0.0 )
+ {
+ if ( s_timeForceShutdown < Plat_FloatTime() )
+ {
+ Warning( "Server shutting down because sv_shutdown was requested and timeout has expired.\n" );
+ HostState_Shutdown();
+ }
+ }
+// if ( sv.IsDedicated() )
+// {
+// sv.UpdateReservedState();
+// if ( sv.IsHibernating() )
+// {
+// // if we're hibernating, just sleep for a while and do not call server.dll to run a frame
+// int nMilliseconds = sv_hibernate_ms.GetInt();
+//#ifndef DEDICATED // Non-Linux
+// if ( g_bIsVGuiBasedDedicatedServer )
+// {
+// // Keep VGUi happy
+// nMilliseconds = sv_hibernate_ms_vgui.GetInt();
+// }
+//#endif
+// g_pNetworkSystem->SleepUntilMessages( NS_SERVER, nMilliseconds );
+// return;
+// }
+// }
+
+ g_ServerGlobalVariables.tickcount = sv.m_nTickCount;
+ g_ServerGlobalVariables.curtime = sv.GetTime();
+ g_ServerGlobalVariables.frametime = bIsSimulating ? host_state.interval_per_tick : 0;
+
+ // in singleplayer only run think/simulation if localplayer is connected
+ bIsSimulating = bIsSimulating && ( sv.IsMultiplayer() || cl.IsActive() );
+
+ g_pServerPluginHandler->GameFrame( bIsSimulating );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : simulating -
+//-----------------------------------------------------------------------------
+void SV_PreClientUpdate(bool bIsSimulating )
+{
+ if ( !serverGameDLL )
+ return;
+
+ serverGameDLL->PreClientUpdate( bIsSimulating );
+}
+
+
+//-----------------------------------------------------------------------------
+//
+//-----------------------------------------------------------------------------
+/*
+==================
+SV_Frame
+
+==================
+*/
+CFunctor *g_pDeferredServerWork;
+
+void SV_FrameExecuteThreadDeferred()
+{
+ if ( g_pDeferredServerWork )
+ {
+ (*g_pDeferredServerWork)();
+ delete g_pDeferredServerWork;
+ g_pDeferredServerWork = NULL;
+ }
+}
+
+void SV_SendClientUpdates( bool bIsSimulating, bool bSendDuringPause )
+{
+ bool bForcedSend = s_bForceSend;
+ s_bForceSend = false;
+
+ // ask game.dll to add any debug graphics
+ SV_PreClientUpdate( bIsSimulating );
+
+ // This causes network messages to be sent
+ sv.SendClientMessages( bIsSimulating || bForcedSend );
+
+ // tricky, increase stringtable tick at least one tick
+ // so changes made after this point are not counted to this server
+ // frame since we already send out the client snapshots
+ networkStringTableContainerServer->SetTick( sv.m_nTickCount + 1 );
+}
+
+void SV_Frame( bool finalTick )
+{
+ VPROF( "SV_Frame" );
+
+ if ( serverGameDLL && finalTick )
+ {
+ serverGameDLL->Think( finalTick );
+ }
+
+ if ( !sv.IsActive() || !Host_ShouldRun() )
+ {
+ return;
+ }
+
+ g_ServerGlobalVariables.frametime = host_state.interval_per_tick;
+
+ bool bIsSimulating = SV_IsSimulating();
+ bool bSendDuringPause = sv_noclipduringpause ? sv_noclipduringpause->GetBool() : false;
+
+ // unlock sting tables to allow changes, helps to find unwanted changes (bebug build only)
+ networkStringTableContainerServer->Lock( false );
+
+ // Run any commands from client and play client Think functions if it is time.
+ sv.RunFrame(); // read network input etc
+
+ bool simulated = false;
+ if ( SV_HasPlayers() )
+ {
+ bool serverCanSimulate = ( serverGameDLL && !serverGameDLL->IsRestoring() ) ? true : false;
+
+ if ( serverCanSimulate && ( bIsSimulating || bSendDuringPause ) )
+ {
+ simulated = true;
+ sv.m_nTickCount++;
+
+ networkStringTableContainerServer->SetTick( sv.m_nTickCount );
+ }
+
+ SV_Think( bIsSimulating );
+ }
+ else if ( sv.IsMultiplayer() )
+ {
+ SV_Think( false ); // let the game.dll systems think
+ }
+
+ // This value is read on another thread, so this needs to only happen once per frame and be atomic.
+ sv.m_bSimulatingTicks = simulated;
+
+ // Send the results of movement and physics to the clients
+ if ( finalTick )
+ {
+ if ( !IsEngineThreaded() || sv.IsMultiplayer() )
+ SV_SendClientUpdates( bIsSimulating, bSendDuringPause );
+ else
+ g_pDeferredServerWork = CreateFunctor( SV_SendClientUpdates, bIsSimulating, bSendDuringPause );
+
+ }
+
+ // lock string tables
+ networkStringTableContainerServer->Lock( true );
+
+ // let the steam auth server process new connections
+ if ( IsPC() && sv.IsMultiplayer() )
+ {
+ Steam3Server().RunFrame();
+ }
+}
+