mirror of
https://github.com/nillerusr/source-engine.git
synced 2024-12-23 14:46:53 +00:00
2967 lines
82 KiB
C++
2967 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 = ¤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();
|
|
}
|
|
}
|
|
|