diff options
Diffstat (limited to 'engine/sv_main.cpp')
| -rw-r--r-- | engine/sv_main.cpp | 2965 |
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 = ¤tLevelData; + // 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(); + } +} + |