source-engine/engine/sv_main.cpp
FluorescentCIAAfricanAmerican 3bf9df6b27 1
2020-04-22 12:56:21 -04:00

2966 lines
82 KiB
C++

//
// 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();
}
}