csgo-2018-source/engine/sv_client.cpp
2021-07-24 21:11:47 -07:00

2535 lines
72 KiB
C++

//===== Copyright © 1996-2005, Valve Corporation, All rights reserved. ======//
//
// Purpose:
//
//===========================================================================//
#include "server_pch.h"
#include "framesnapshot.h"
#include "checksum_engine.h"
#include "sv_main.h"
#include "GameEventManager.h"
#include "networkstringtable.h"
#include "demo.h"
#include "PlayerState.h"
#include "tier0/vprof.h"
#include "sv_packedentities.h"
#include "LocalNetworkBackdoor.h"
#include "testscriptmgr.h"
#include "hltvserver.h"
#if defined( REPLAY_ENABLED )
#include "replayserver.h"
#endif
#include "pr_edict.h"
#include "logofile_shared.h"
#include "dt_send_eng.h"
#include "sv_plugin.h"
#include "download.h"
#include "cmodel_engine.h"
#include "tier1/commandbuffer.h"
#include "gl_cvars.h"
#include "tier2/tier2.h"
#include "matchmaking/imatchframework.h"
#include "audio/public/vox.h" // TERROR: for net_showreliablesounds
#include "SoundEmitterSystem/isoundemittersystembase.h"
#include "ihltv.h"
#include "tier1/utlstringtoken.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
extern CNetworkStringTableContainer *networkStringTableContainerServer;
static ConVar sv_timeout( "sv_timeout", "65", 0, "After this many seconds without a message from a client, the client is dropped" );
static ConVar sv_maxrate( "sv_maxrate", "0", FCVAR_REPLICATED | FCVAR_RELEASE, "Max bandwidth rate allowed on server, 0 == unlimited", true, 0, true, MAX_RATE );
static ConVar sv_minrate( "sv_minrate", STRINGIFY( MIN_RATE ), FCVAR_REPLICATED | FCVAR_RELEASE, "Min bandwidth rate allowed on server, 0 == unlimited", true, 0, true, MAX_RATE );
ConVar sv_maxupdaterate( "sv_maxupdaterate", "64", FCVAR_REPLICATED | FCVAR_RELEASE, "Maximum updates per second that the server will allow" ); // we need to be able to set max rate to 128
ConVar sv_minupdaterate( "sv_minupdaterate", "64", FCVAR_REPLICATED | FCVAR_RELEASE, "Minimum updates per second that the server will allow" );
ConVar sv_stressbots("sv_stressbots", "0", FCVAR_RELEASE, "If set to 1, the server calculates data and fills packets to bots. Used for perf testing.");
ConVar sv_replaybots( "sv_replaybots", "1", FCVAR_RELEASE, "If set to 1, the server records data needed to replay network stream from bot's perspective" );
static ConVar sv_allowdownload ("sv_allowdownload", "1", FCVAR_RELEASE, "Allow clients to download files");
static ConVar sv_allowupload ("sv_allowupload", "1", FCVAR_RELEASE, "Allow clients to upload customizations files");
ConVar sv_sendtables ( "sv_sendtables", "0", FCVAR_DEVELOPMENTONLY, "Force full sendtable sending path." );
#if HLTV_REPLAY_ENABLED
ConVar spec_replay_enable( "spec_replay_enable", "0", FCVAR_RELEASE|FCVAR_REPLICATED, "Enable Killer Replay, requires hltv server running." );
#endif
ConVar spec_replay_message_time( "spec_replay_message_time", "9.5", FCVAR_RELEASE | FCVAR_REPLICATED, "How long to show the message about Killer Replay after death. The best setting is a bit shorter than spec_replay_autostart_delay + spec_replay_leadup_time + spec_replay_winddown_time" );
ConVar spec_replay_rate_limit( "spec_replay_rate_limit", "3", FCVAR_RELEASE | FCVAR_REPLICATED, "Minimum allowable pause between replay requests in seconds" );
static ConVar ss_voice_hearpartner( "ss_voice_hearpartner", "0", 0, "Route voice between splitscreen players on same system." );
ConVar sv_max_dropped_packets_to_process( "sv_max_dropped_packets_to_process", "10", FCVAR_RELEASE, "Max dropped packets to process. Lower settings prevent lagged players from simulating too far in the past. Setting of 0 disables cap." );
ConVar cl_allowdownload( "cl_allowdownload", "1", FCVAR_ARCHIVE, "Client downloads customization files" );
static ConVar sv_quota_stringcmdspersecond( "sv_quota_stringcmdspersecond", "40", FCVAR_RELEASE, "How many string commands per second clients are allowed to submit, 0 to disallow all string commands" );
// TERROR:
static ConVar net_showreliablesounds( "net_showreliablesounds", "0", FCVAR_CHEAT );
ConVar replay_debug( "replay_debug", "0", FCVAR_RELEASE | FCVAR_REPLICATED);
ConVar sv_allow_legacy_cmd_execution_from_client( "sv_allow_legacy_cmd_execution_from_client", "0", FCVAR_RELEASE, "Enables old concommand execution behavior allowing remote clients to run any command not explicitly flagged as disallowed." );
extern ConVar sv_maxreplay;
extern ConVar tv_snapshotrate;
extern ConVar tv_transmitall;
extern ConVar sv_pure_kick_clients;
extern ConVar sv_pure_trace;
#if defined( REPLAY_ENABLED )
extern ConVar replay_snapshotrate;
extern ConVar replay_transmitall;
#endif
// static ConVar sv_failuretime( "sv_failuretime", "0.5", 0, "After this long without a packet from client, don't send any more until client starts sending again" );
static const char * s_clcommands[] =
{
"status",
"pause",
"setpause",
"unpause",
"ping",
"rpt_server_enable",
"rpt_client_enable",
#ifndef DEDICATED
"rpt",
"rpt_connect",
"rpt_password",
"rpt_screenshot",
"rpt_download_log",
#endif
"ss_connect",
"ss_disconnect",
#if defined( REPLAY_ENABLED )
"request_replay_demo",
#endif
NULL,
};
// Used on the server and on the client to bound its cl_rate cvar.
int ClampClientRate( int nRate )
{
// Apply mod specific clamps
if ( sv_maxrate.GetInt() > 0 )
{
nRate = MIN( nRate, sv_maxrate.GetInt() );
}
if ( sv_minrate.GetInt() > 0 )
{
nRate = MAX( nRate, sv_minrate.GetInt() );
}
// Apply overall clamp
nRate = clamp( nRate, MIN_RATE, MAX_RATE );
return nRate;
}
// Validate minimum number of required clients to be connected to a server
enum ValidateMinRequiredClients_t
{
VALIDATE_SPAWN,
VALIDATE_DISCONNECT
};
void SV_ValidateMinRequiredClients( ValidateMinRequiredClients_t eReason )
{
// FIXME: This gives false positives for drops and disconnects. (kwd)
return;
if ( !IsX360() && !sv.IsDedicatedForXbox() )
return;
static ConVarRef s_director_min_start_players( "director_min_start_players", true );
if ( !s_director_min_start_players.IsValid() )
return;
int numRequiredByDirector = s_director_min_start_players.GetInt();
if ( numRequiredByDirector <= 0 )
return;
switch ( eReason )
{
case VALIDATE_SPAWN:
{
// If at least one client has already spawned in the server, if there is a required
// minimum number of players and some of the players are not yet connected to server
// then most likely they dropped out or failed to connect, need to lower the minimum
// number of required players and proceed with game.
int numConnected = sv.GetClientCount();
int numInState = 0;
// Determine how many clients are above signon state NEW
for ( int j = 0 ; j < numConnected ; j++ )
{
IClient *client = sv.GetClient( j );
if ( !client )
continue;
if ( !client->IsSpawned() )
continue;
++ numInState;
}
if ( numRequiredByDirector > numInState )
{
s_director_min_start_players.SetValue( numInState );
ConMsg( "SV_ValidateMinRequiredClients: spawn: lowered min start players to %d.\n",
numInState );
}
}
break;
case VALIDATE_DISCONNECT:
{
// If somebody disconnects from the server we decrement the minimum number of players required
-- numRequiredByDirector;
s_director_min_start_players.SetValue( numRequiredByDirector );
ConMsg( "SV_ValidateMinRequiredClients: disconnect: lowered min start players to %d.\n",
numRequiredByDirector );
}
break;
}
}
CGameClient::CGameClient(int slot, CBaseServer *pServer )
{
Clear();
m_nClientSlot = slot;
m_nEntityIndex = slot+1;
m_Server = pServer;
m_pCurrentFrame = NULL;
m_bIsInReplayMode = false;
// NULL out data we'll never use.
memset( &m_PrevPackInfo, 0, sizeof( m_PrevPackInfo ) );
m_PrevPackInfo.m_pTransmitEdict = &m_PrevTransmitEdict;
m_flTimeClientBecameFullyConnected = -1.0f;
m_flLastClientCommandQuotaStart = -1.0f;
m_numClientCommandsInQuota = 0;
}
CGameClient::~CGameClient()
{
}
bool CGameClient::CLCMsg_ClientInfo( const CCLCMsg_ClientInfo& msg )
{
BaseClass::CLCMsg_ClientInfo( msg );
if ( m_bIsHLTV )
{
Disconnect( "CLCMsg_ClientInfo: SourceTV can not connect to game directly.\n" );
return false;
}
#if defined( REPLAY_ENABLED )
if ( m_bIsReplay )
{
Disconnect( "CLCMsg_ClientInfo: Replay can not connect to game directly.\n" );
return false;
}
#endif
if ( sv_allowupload.GetBool() )
{
// download all missing customizations files from this client;
DownloadCustomizations();
}
return true;
}
bool CGameClient::CLCMsg_Move( const CCLCMsg_Move& msg )
{
// Don't process usercmds until the client is active. If we do, there can be weird behavior
// like the game trying to send reliable messages to the client and having those messages discarded.
if ( !IsActive() )
return true;
if ( m_LastMovementTick == sv.m_nTickCount )
{
// Only one movement command per frame, someone is cheating.
return true;
}
m_LastMovementTick = sv.m_nTickCount;
INetChannel *netchan = sv.GetBaseUserForSplitClient( this )->m_NetChannel;
int totalcmds = msg.num_backup_commands() + msg.num_new_commands();
// Decrement drop count by held back packet count
int netdrop = netchan->GetDropNumber();
// Dropped packet count is reported by clients
if ( sv_max_dropped_packets_to_process.GetInt() )
netdrop = Clamp( netdrop, 0, sv_max_dropped_packets_to_process.GetInt() );
bool ignore = !sv.IsActive();
#ifdef DEDICATED
bool paused = sv.IsPaused();
#else
bool paused = sv.IsPaused() || ( !sv.IsMultiplayer() && Con_IsVisible() );
#endif
// Make sure player knows of correct server time
g_ServerGlobalVariables.curtime = sv.GetTime();
g_ServerGlobalVariables.frametime = host_state.interval_per_tick;
// COM_Log( "sv.log", " executing %i move commands from client starting with command %i(%i)\n",
// numcmds,
// m_Client->netchan->incoming_sequence,
// m_Client->netchan->incoming_sequence & SV_UPDATE_MASK );
bf_read DataIn( &msg.data()[0], msg.data().size() );
serverGameClients->ProcessUsercmds
(
edict, // Player edict
&DataIn,
msg.num_new_commands(),
totalcmds, // Commands in packet
netdrop, // Number of dropped commands
ignore, // Don't actually run anything
paused // Run, but don't actually do any movement
);
if ( DataIn.IsOverflowed() )
{
Disconnect( "ProcessUsercmds: Overflowed reading usercmd data (check sending and receiving code for mismatches)!\n" );
return false;
}
return true;
}
bool CGameClient::CLCMsg_VoiceData( const CCLCMsg_VoiceData& msg )
{
serverGameClients->ClientVoice( edict );
SV_BroadcastVoiceData( this, msg );
return true;
}
bool CGameClient::CLCMsg_CmdKeyValues( const CCLCMsg_CmdKeyValues& msg )
{
KeyValues *keyvalues = CmdKeyValuesHelper::CLCMsg_GetKeyValues( msg );
KeyValues::AutoDelete autodelete_keyvalues( keyvalues );
serverGameClients->ClientCommandKeyValues( edict, keyvalues );
if ( IsX360() || NET_IsDedicatedForXbox() )
{
// See if a player was removed
if ( !Q_stricmp( keyvalues->GetName(), "OnPlayerRemovedFromSession" ) )
{
XUID xuid = keyvalues->GetUint64( "xuid", 0ull );
for ( int iPlayerIndex = 0; iPlayerIndex < sv.GetClientCount(); iPlayerIndex++ )
{
CBaseClient *pClient = (CBaseClient *) sv.GetClient( iPlayerIndex );
if ( !pClient || !xuid || pClient->GetClientXuid() != xuid )
continue;
pClient->Disconnect( "Player removed from host session\n" );
return true;
}
}
}
MEM_ALLOC_CREDIT();
KeyValues *pEvent = new KeyValues( "Server::CmdKeyValues" );
pEvent->AddSubKey( autodelete_keyvalues.Detach() );
pEvent->SetPtr( "edict", edict );
g_pMatchFramework->GetEventsSubscription()->BroadcastEvent( pEvent );
return true;
}
bool CGameClient::CLCMsg_HltvReplay( const CCLCMsg_HltvReplay &msg )
{
int nRequest = msg.request();
if ( nRequest == REPLAY_EVENT_STUCK_NEED_FULL_UPDATE )
{
if ( m_nForceWaitForTick > 0 )
{
// <sergiy> if we are indeed waiting for tick confirmation, the client may indeed be stuck. Let them have another update. This is prone to a mildly annoying attack: client can go into a loop requesting updates, which will raise server's CPU usage and traffic and may cause server to skip ticks on high-load casual servers. So later on, I should probably rate-limit this. But hopefully I'll find why the client gets stuck before too long.
UpdateAcknowledgedFramecount( -1 );
}
}
else if ( nRequest )
{
ClientReplayEventParams_t params( nRequest );
if ( params.m_flSlowdownRate > 0.01f && params.m_flSlowdownRate < 10.0f && params.m_flSlowdownLength > 0.01f && params.m_flSlowdownLength <= 5.0f )
{
// keep defaults in suspicious cases
params.m_flSlowdownRate = msg.slowdown_rate();
params.m_flSlowdownLength = msg.slowdown_length();
}
params.m_nPrimaryTargetEntIndex = msg.primary_target_ent_index();
params.m_flEventTime = msg.event_time();
serverGameClients->ClientReplayEvent( edict, params );
}
else
{
if ( IsHltvReplay() )
m_HltvReplayStats.nUserCancels++;
StopHltvReplay();
}
return true;
}
bool CGameClient::SVCMsg_UserMessage( const CSVCMsg_UserMessage &msg )
{
serverGameClients->ClientSvcUserMessage( edict, msg.msg_type(), msg.passthrough(), msg.msg_data().size(), &msg.msg_data()[0] );
return true;
}
bool CGameClient::CLCMsg_RespondCvarValue( const CCLCMsg_RespondCvarValue& msg )
{
if ( msg.cookie() > 0 )
{
if ( g_pServerPluginHandler )
g_pServerPluginHandler->OnQueryCvarValueFinished( ( EQueryCvarValueStatus )msg.cookie(), edict, ( EQueryCvarValueStatus )msg.status_code(), msg.name().c_str(), msg.value().c_str() );
}
else
{
// Negative cookie means the game DLL asked for the value.
if ( serverGameDLL && g_bServerGameDLLGreaterThanV5 )
{
#ifdef REL_TO_STAGING_MERGE_TODO
serverGameDLL->OnQueryCvarValueFinished( msg.cookie(), edict, msg.status_code(), msg.name().c_str(), msg.value().c_str() );
#endif
}
}
return true;
}
#include "pure_server.h"
bool CGameClient::CLCMsg_FileCRCCheck( const CCLCMsg_FileCRCCheck& msg )
{
// Ignore this message if we're not in pure server mode...
if ( !sv.IsInPureServerMode() )
return true;
char warningStr[1024] = {0};
// first check against all the other files users have sent
FileHash_t filehash;
V_memcpy( filehash.m_md5contents.bits, msg.md5().c_str(), MD5_DIGEST_LENGTH );
filehash.m_crcIOSequence = msg.crc();
filehash.m_eFileHashType = msg.file_hash_type();
filehash.m_cbFileLen = msg.file_len();
filehash.m_nPackFileNumber = msg.pack_file_number();
filehash.m_PackFileID = msg.pack_file_id();
const char *path = CCLCMsg_FileCRCCheck_t::GetPath( msg );
const char *fileName = CCLCMsg_FileCRCCheck_t::GetFileName( msg );
if ( g_PureFileTracker.DoesFileMatch( path, fileName, msg.file_fraction(), &filehash, GetNetworkID() ) )
{
// track successful file
}
else
{
V_snprintf( warningStr, sizeof( warningStr ), "Pure server: file [%s]\\%s does not match the server's file.", path, fileName );
}
// still ToDo:
// 1. make sure the user sends some files
// 2. make sure the user doesnt skip any files
// 3. make sure the user sends the right files...
if ( warningStr[0] )
{
if ( serverGameDLL )
{
serverGameDLL->OnPureServerFileValidationFailure( edict, path, fileName, filehash.m_crcIOSequence, filehash.m_eFileHashType,
filehash.m_cbFileLen, filehash.m_nPackFileNumber, filehash.m_PackFileID );
}
if ( sv_pure_kick_clients.GetInt() )
{
Disconnect( warningStr );
}
else
{
ClientPrintf( "Warning: %s\n", warningStr );
if ( sv_pure_trace.GetInt() >= 1 )
{
Msg( "[%s] %s\n", GetNetworkIDString(), warningStr );
}
}
}
else
{
if ( sv_pure_trace.GetInt() == 2 )
{
Msg( "Pure server CRC check: client %s passed check for [%s]\\%s\n", GetClientName(), path, fileName );
}
}
return true;
}
void CGameClient::DownloadCustomizations()
{
if ( !cl_allowdownload.GetBool() )
return; // client doesn't want to download any customizations
for ( int i=0; i<MAX_CUSTOM_FILES; i++ )
{
if ( m_nCustomFiles[i].crc == 0 )
continue; // slot not used
CCustomFilename hexname( m_nCustomFiles[i].crc );
if ( g_pFileSystem->FileExists( hexname.m_Filename ) )
continue; // we already have it
// we don't have it, request download from client
m_nCustomFiles[i].reqID = m_NetChannel->RequestFile( hexname.m_Filename, false );
}
}
void CGameClient::Connect(const char * szName, int nUserID, INetChannel *pNetChannel, bool bFakePlayer, CrossPlayPlatform_t clientPlatform, const CMsg_CVars *pVecCvars /*= NULL*/)
{
BaseClass::Connect( szName, nUserID, pNetChannel, bFakePlayer, clientPlatform, pVecCvars );
edict = EDICT_NUM( m_nEntityIndex );
// init PackInfo
m_PackInfo.m_pClientEnt = edict;
m_PackInfo.m_nPVSSize = sizeof( m_PackInfo.m_PVS );
// fire global game event
IGameEvent *event = g_GameEventManager.CreateEvent( "player_connect" );
{
event->SetInt( "userid", m_UserID );
event->SetInt( "index", m_nClientSlot );
event->SetString( "name", m_Name );
event->SetUint64( "xuid", GetClientXuid() );
event->SetString( "networkid", GetNetworkIDString() );
// event->SetString( "address", m_NetChannel?m_NetChannel->GetAddress():"none" );
event->SetInt( "bot", m_bFakePlayer?1:0 );
g_GameEventManager.FireEvent( event );
}
}
static ConVar sv_maxclientframes( "sv_maxclientframes", "128" );
static ConVar sv_extra_client_connect_time( "sv_extra_client_connect_time", "15.0", 0,
"Seconds after client connect during which extra frames are buffered to prevent non-delta'd update" );
void CGameClient::SetupPackInfo( CFrameSnapshot *pSnapshot )
{
Assert( !IsHltvReplay() );
// Compute Vis for each client
m_PackInfo.m_nPVSSize = (GetCollisionBSPData()->numclusters + 7) / 8;
serverGameClients->ClientSetupVisibility( (edict_t *)m_pViewEntity,
m_PackInfo.m_pClientEnt, m_PackInfo.m_PVS, m_PackInfo.m_nPVSSize );
// This is the frame we are creating, i.e., the next
// frame after the last one that the client acknowledged
m_pCurrentFrame = AllocateFrame();
m_pCurrentFrame->Init( pSnapshot );
m_PackInfo.m_pTransmitEdict = &m_pCurrentFrame->transmit_entity;
// if this client is the HLTV or Replay client, add the nocheck PVS bit array
// normal clients don't need that extra array
#if defined( REPLAY_ENABLED )
if ( IsHLTV() || IsReplay() )
#else
if ( IsHLTV() )
#endif
{
// the hltv client doesn't has a ClientFrame list
m_pCurrentFrame->transmit_always = new CBitVec<MAX_EDICTS>;
m_PackInfo.m_pTransmitAlways = m_pCurrentFrame->transmit_always;
}
else
{
m_PackInfo.m_pTransmitAlways = NULL;
}
// Add frame to ClientFrame list
int nMaxFrames = MAX( sv_maxclientframes.GetInt(), MAX_CLIENT_FRAMES );
// Only do this on dedicated servers (360 dedicated servers are !IsX360 so this check isn't strictly necessary)
// and non-x360 am servers due to concerns over memory growth on the consoles.
if ( ( !IsX360() || sv.IsDedicated() ) &&
( m_flTimeClientBecameFullyConnected != -1.0f ) &&
( realtime - m_flTimeClientBecameFullyConnected ) < sv_extra_client_connect_time.GetFloat() )
{
// For 15 seconds, the max will go from 128 (default) to 450 (assuming 0.0333 world tick interval)
// or to 960 (assuming 0.015625, 64 fps).
// In practice during changelevel on 360 I've seen it get up to 210 or so which is only 60% greater
// than the 128 frame max default, so 450 seems like it should capture all cases.
nMaxFrames = MAX( nMaxFrames, (int)( sv_extra_client_connect_time.GetFloat() / m_Server->GetTickInterval() ) );
// Msg( "Allowing up to %d frames for player for %f more seconds\n", nMaxFrames, realtime - m_flTimeClientBecameFullyConnected );
}
if ( sv_maxreplay.GetFloat() > 0 )
{
// if the server has replay features enabled, allow a way bigger frame buffer
nMaxFrames = MAX( nMaxFrames, sv_maxreplay.GetFloat() / m_Server->GetTickInterval() );
}
// During the startup period we retain additional frames. Once nMaxFrames drops we need to
// purge the extra frames or else we may permanently be using too much memory.
int frameCount = AddClientFrame( m_pCurrentFrame );
while ( nMaxFrames < frameCount )
{
// If the client has more than nMaxFrames frames, the server will start to eat too much memory.
RemoveOldestFrame();
--frameCount;
}
m_PackInfo.m_AreasNetworked = 0;
int areaCount = g_AreasNetworked.Count();
for ( int j = 0; j < areaCount; j++ )
{
// Msg("CGameClient::SetupPackInfo: too much areas (%i)", areaCount );
AssertOnce( m_PackInfo.m_AreasNetworked < MAX_WORLD_AREAS );
if ( m_PackInfo.m_AreasNetworked >= MAX_WORLD_AREAS )
break;
m_PackInfo.m_Areas[m_PackInfo.m_AreasNetworked] = g_AreasNetworked[ j ];
m_PackInfo.m_AreasNetworked++;
}
CM_SetupAreaFloodNums( m_PackInfo.m_AreaFloodNums, &m_PackInfo.m_nMapAreas );
}
ConVar spec_replay_rate_base( "spec_replay_rate_base", "1", FCVAR_RELEASE | FCVAR_REPLICATED, "Base time scale of Killer Replay.Experimental." );
void CGameClient::SetupHltvFrame( int nServerTick )
{
Assert( m_nHltvReplayDelay && m_pHltvReplayServer );
int nReplayTick = nServerTick - m_nHltvReplayDelay;
CClientFrame *pFrame = m_pHltvReplayServer->ExpandAndGetClientFrame( nReplayTick, false );
if ( !pFrame )
return;
m_pCurrentFrame = pFrame;
}
void CGameClient::SetupPrevPackInfo()
{
Assert( !IsHltvReplay() );
memcpy( &m_PrevTransmitEdict, m_PackInfo.m_pTransmitEdict, sizeof( m_PrevTransmitEdict ) );
// Copy the relevant fields into m_PrevPackInfo.
m_PrevPackInfo.m_AreasNetworked = m_PackInfo.m_AreasNetworked;
memcpy( m_PrevPackInfo.m_Areas, m_PackInfo.m_Areas, sizeof( m_PackInfo.m_Areas[0] ) * m_PackInfo.m_AreasNetworked );
m_PrevPackInfo.m_nPVSSize = m_PackInfo.m_nPVSSize;
memcpy( m_PrevPackInfo.m_PVS, m_PackInfo.m_PVS, m_PackInfo.m_nPVSSize );
m_PrevPackInfo.m_nMapAreas = m_PackInfo.m_nMapAreas;
memcpy( m_PrevPackInfo.m_AreaFloodNums, m_PackInfo.m_AreaFloodNums, m_PackInfo.m_nMapAreas * sizeof( m_PackInfo.m_nMapAreas ) );
}
/*
================
CheckRate
Make sure channel rate for active client is within server bounds
================
*/
void CGameClient::SetRate(int nRate, bool bForce )
{
if ( !bForce )
{
nRate = ClampClientRate( nRate );
}
BaseClass::SetRate( nRate, bForce );
}
void CGameClient::SetUpdateRate( float fUpdateRate, bool bForce )
{
if ( !bForce )
{
if ( CHLTVServer *hltv = GetAnyConnectedHltvServer() )
{
// Clients connected to our HLTV server will receive updates at tv_snapshotrate
fUpdateRate = hltv->GetSnapshotRate();
}
else
{
if ( sv_maxupdaterate.GetFloat() > 0 )
{
fUpdateRate = clamp( fUpdateRate, 1, sv_maxupdaterate.GetFloat() );
}
if ( sv_minupdaterate.GetInt() > 0 )
{
fUpdateRate = clamp( fUpdateRate, sv_minupdaterate.GetFloat(), 128.0f );
}
}
}
BaseClass::SetUpdateRate( fUpdateRate, bForce );
}
void CGameClient::UpdateUserSettings()
{
// set voice loopback
m_bVoiceLoopback = m_ConVars->GetInt( "voice_loopback", 0 ) != 0;
BaseClass::UpdateUserSettings();
// Give entity dll a chance to look at the changes.
// Do this after BaseClass::UpdateUserSettings() so name changes like prepending a (1)
// take effect before the server dll sees the name.
g_pServerPluginHandler->ClientSettingsChanged( edict );
}
//-----------------------------------------------------------------------------
// Purpose: A File has been received, if it's a logo, send it on to any other players who need it
// and return true, otherwise, return false
// Input : *cl -
// *filename -
// Output : Returns true on success, false on failure.
/*-----------------------------------------------------------------------------
bool CGameClient::ProcessIncomingLogo( const char *filename )
{
char crcfilename[ 512 ];
char logohex[ 16 ];
Q_binarytohex( (byte *)&logo, sizeof( logo ), logohex, sizeof( logohex ) );
Q_snprintf( crcfilename, sizeof( crcfilename ), "materials/decals/downloads/%s.vtf", logohex );
// It's not a logo file?
if ( Q_strcasecmp( filename, crcfilename ) )
{
return false;
}
// First, make sure crc is valid
CRC32_t check;
CRC_File( &check, crcfilename );
if ( check != logo )
{
ConMsg( "Incoming logo file didn't match player's logo CRC, ignoring\n" );
// Still note that it was a logo!
return true;
}
// Okay, looks good, see if any other players need this logo file
SV_SendLogo( check );
return true;
} */
bool CGameClient::IsHearingClient( int index ) const
{
#if defined( REPLAY_ENABLED )
if ( IsHLTV() || IsReplay() )
#else
if ( IsHLTV() )
#endif
return true;
if ( index == GetPlayerSlot() )
return m_bVoiceLoopback;
CGameClient *pClient = sv.Client( index );
// Don't send voice from one splitscreen partner to another on the same box
if ( !ss_voice_hearpartner.GetBool() &&
IsSplitScreenPartner( pClient ) )
{
return false;
}
return pClient->m_VoiceStreams.Get( GetPlayerSlot() ) != 0;
}
bool CGameClient::IsProximityHearingClient( int index ) const
{
CGameClient *pClient = sv.Client( index );
return pClient->m_VoiceProximity.Get( GetPlayerSlot() ) != 0;
}
void CGameClient::Inactivate( void )
{
if ( edict && !edict->IsFree() )
{
m_Server->RemoveClientFromGame( this );
}
if ( IsHLTV() )
{
if ( CHLTVServer *hltv = GetAnyConnectedHltvServer() )
{
hltv->Changelevel( true );
}
}
m_nHltvReplayDelay = 0;
m_pHltvReplayServer = NULL;
m_nHltvReplayStopAt = 0;
m_nHltvReplayStartAt = 0;
m_nHltvLastSendTick = 0; // last send tick, don't send ticks twice
#if defined( REPLAY_ENABLED )
if ( IsReplay() )
{
replay->Changelevel();
}
#endif
BaseClass::Inactivate();
m_Sounds.Purge();
ConVarRef voice_verbose( "voice_verbose" );
if ( voice_verbose.GetBool() )
{
Msg( "* CGameClient::Inactivate: Clearing m_VoiceStreams/m_VoiceProximity for %s (%s)\n", GetClientName(), GetNetChannel() ? GetNetChannel()->GetAddress() : "null" );
}
m_VoiceStreams.ClearAll();
m_VoiceProximity.ClearAll();
DeleteClientFrames( -1 ); // delete all
}
bool CGameClient::UpdateAcknowledgedFramecount(int tick)
{
// free old client frames which won't be used anymore
if ( tick != m_nDeltaTick )
{
// delta tick changed, free all frames smaller than tick
int removeTick = tick;
if ( sv_maxreplay.GetFloat() > 0 )
removeTick -= (sv_maxreplay.GetFloat() / m_Server->GetTickInterval() ); // keep a replay buffer
if ( removeTick > 0 )
{
DeleteClientFrames( removeTick );
}
}
return BaseClass::UpdateAcknowledgedFramecount( tick );
}
void CGameClient::Clear()
{
if ( m_bIsHLTV )
{
if ( CHLTVServer *hltv = GetAnyConnectedHltvServer() )
{
hltv->Shutdown();
}
}
#if defined( REPLAY_ENABLED )
if ( m_bIsReplay )
{
replay->Shutdown();
}
#endif
BaseClass::Clear();
m_HltvQueuedMessages.PurgeAndDeleteElements();
// free all frames
DeleteClientFrames( -1 );
m_Sounds.Purge();
ConVarRef voice_verbose( "voice_verbose" );
if ( voice_verbose.GetBool() )
{
Msg( "* CGameClient::Clear: Clearing m_VoiceStreams/m_VoiceProximity for %s (%s)\n", GetClientName(), GetNetChannel() ? GetNetChannel()->GetAddress() : "null" );
}
m_VoiceStreams.ClearAll();
m_VoiceProximity.ClearAll();
edict = NULL;
m_pViewEntity = NULL;
m_bVoiceLoopback = false;
m_LastMovementTick = 0;
m_nSoundSequence = 0;
m_flTimeClientBecameFullyConnected = -1.0f;
m_flLastClientCommandQuotaStart = -1.0f;
m_numClientCommandsInQuota = 0;
m_nHltvReplayDelay = 0;
m_pHltvReplayServer = NULL;
m_nHltvReplayStopAt = 0;
m_nHltvReplayStartAt = 0;
m_nHltvLastSendTick = 0;
m_flHltvLastReplayRequestTime = -spec_replay_message_time.GetFloat();
}
void CGameClient::Reconnect( void )
{
// If the client was connected before, tell the game .dll to disconnect him/her.
sv.RemoveClientFromGame( this );
BaseClass::Reconnect();
}
void CGameClient::PerformDisconnection( const char *pReason )
{
// notify other clients of player leaving the game
// send the username and network id so we don't depend on the CBasePlayer pointer
IGameEvent *event = g_GameEventManager.CreateEvent( "player_disconnect" );
if ( event )
{
event->SetInt("userid", GetUserID() );
event->SetString("reason", pReason );
event->SetString("name", GetClientName() );
event->SetUint64("xuid", GetClientXuid() );
event->SetString("networkid", GetNetworkIDString() );
g_GameEventManager.FireEvent( event );
}
m_Server->RemoveClientFromGame( this );
int nDisconnectSignonState = GetSignonState();
BaseClass::PerformDisconnection( pReason );
if ( nDisconnectSignonState >= SIGNONSTATE_NEW )
{
SV_ValidateMinRequiredClients( VALIDATE_DISCONNECT );
}
m_nHltvReplayDelay = 0;
m_pHltvReplayServer = NULL;
m_nHltvReplayStopAt = 0;
m_nHltvReplayStartAt = 0;
m_nHltvLastSendTick = 0;
}
HltvReplayStats_t m_DisconnectedClientsHltvReplayStats;
void CGameClient::Disconnect( const char *fmt )
{
// Remember what state we had when "Disconnect" got called
int nDisconnectSignonState = GetSignonState();
if ( nDisconnectSignonState == SIGNONSTATE_NONE )
return; // no recursion
m_DisconnectedClientsHltvReplayStats += m_HltvReplayStats;
m_HltvReplayStats.Reset();
BaseClass::Disconnect( fmt );
}
bool CGameClient::ProcessSignonStateMsg( int state, int spawncount )
{
if ( state == SIGNONSTATE_SPAWN || state == SIGNONSTATE_CHANGELEVEL )
{
StopHltvReplay();
}
else if ( state == SIGNONSTATE_CONNECTED )
{
if ( !CheckConnect() )
return false;
// Allow long enough time-out to load a map
float flTimeout = SIGNON_TIME_OUT;
if ( sv.IsDedicatedForXbox() )
flTimeout = SIGNON_TIME_OUT_360;
m_NetChannel->SetTimeout( flTimeout );
m_NetChannel->SetFileTransmissionMode( false );
m_NetChannel->SetMaxBufferSize( true, NET_MAX_PAYLOAD );
}
else if ( state == SIGNONSTATE_NEW )
{
if ( !sv.IsMultiplayer() )
{
// local client as received and create string tables,
// now link server tables to client tables
SV_InstallClientStringTableMirrors();
}
}
else if ( state == SIGNONSTATE_FULL )
{
if ( sv.m_bLoadgame )
{
// If this game was loaded from savegame, finish restoring game now
sv.FinishRestore();
}
m_NetChannel->SetTimeout( sv_timeout.GetFloat() ); // use smaller timeout limit
m_NetChannel->SetFileTransmissionMode( true );
g_pServerPluginHandler->ClientFullyConnect( edict );
}
return BaseClass::ProcessSignonStateMsg( state, spawncount );
}
void CGameClient::SendSound( SoundInfo_t &sound, bool isReliable )
{
#if defined( REPLAY_ENABLED )
if ( IsFakeClient() && !IsHLTV() && !IsReplay() && !IsSplitScreenUser() )
#else
if ( IsFakeClient() && !IsHLTV() && !IsSplitScreenUser() )
#endif
{
return; // dont send sound messages to bots
}
// don't send sound messages while client is replay mode
if ( m_bIsInReplayMode )
{
return;
}
// reliable sounds are send as single messages
if ( isReliable )
{
CSVCMsg_Sounds_t *sndmsg = new CSVCMsg_Sounds_t;
m_nSoundSequence = ( m_nSoundSequence + 1 ) & SOUND_SEQNUMBER_MASK; // increase own sound sequence counter
sound.nSequenceNumber = 0; // don't transmit nSequenceNumber for reliable sounds
sndmsg->set_reliable_sound( true );
sound.WriteDelta( NULL, *sndmsg, sv.GetFinalTickTime() );
if ( net_showreliablesounds.GetBool() )
{
const char *name = "<Unknown>";
if ( sound.bIsSentence )
{
name = VOX_SentenceNameFromIndex( sound.nSoundNum );
}
else
{
if( sound.nFlags & SND_IS_SCRIPTHANDLE )
{
name = sound.pszName;
}
else
{
name = sv.GetSound( sound.nSoundNum );
}
}
Warning( "reliable%s %s %d/%d/%d/%s\n",
((sound.nFlags & SND_STOP) != 0)?" stop":"",
(sound.bIsSentence)?"sentence":"sound",
sound.nEntityIndex, sound.nChannel, sound.nSoundNum, name );
}
// send reliable sound as single message
SendNetMsg( *sndmsg, true );
if ( m_nHltvReplayDelay )
m_HltvQueuedMessages.AddToTail( sndmsg );
else
delete sndmsg;
return;
}
sound.nSequenceNumber = m_nSoundSequence;
m_Sounds.AddToTail( sound ); // queue sounds until snapshot is send
}
void CGameClient::WriteGameSounds( bf_write &buf, int nMaxSounds )
{
if ( m_Sounds.Count() <= 0 )
return;
CSVCMsg_Sounds_t msg;
msg.SetReliable( false );
int nSoundCount = FillSoundsMessage( msg, nMaxSounds );
msg.WriteToBuffer( buf );
if ( IsTracing() )
{
TraceNetworkData( buf, "Sounds [count=%d]", nSoundCount );
}
}
static ConVar sv_sound_discardextraunreliable( "sv_sound_discardextraunreliable", "1" );
int CGameClient::FillSoundsMessage(CSVCMsg_Sounds &msg, int nMaxSounds )
{
int i, count = m_Sounds.Count();
// Discard events if we have too many to signal with 8 bits
if ( count > nMaxSounds )
count = nMaxSounds;
// Nothing to send
if ( !count )
return 0;
SoundInfo_t defaultSound;
SoundInfo_t *pDeltaSound = &defaultSound;
msg.set_reliable_sound( false );
float finalTickTime = m_Server->GetFinalTickTime();
for ( i = 0 ; i < count; i++ )
{
SoundInfo_t &sound = m_Sounds[ i ];
sound.WriteDelta( pDeltaSound, msg, finalTickTime );
pDeltaSound = &m_Sounds[ i ];
}
// remove added events from list
if ( sv_sound_discardextraunreliable.GetBool() )
{
if ( m_Sounds.Count() != count )
{
DevMsg( 2, "Warning! Dropped %i unreliable sounds for client %s.\n" , m_Sounds.Count() - count, m_Name );
}
m_Sounds.RemoveAll();
}
else
{
int remove = m_Sounds.Count() - ( count + nMaxSounds );
if ( remove > 0 )
{
DevMsg( 2, "Warning! Dropped %i unreliable sounds for client %s.\n" , remove, m_Name );
count+= remove;
}
if ( count > 0 )
{
m_Sounds.RemoveMultiple( 0, count );
}
}
Assert( m_Sounds.Count() <= nMaxSounds );
return msg.sounds_size();
}
bool CGameClient::CheckConnect( void )
{
// Allow the game dll to reject this client.
char szRejectReason[128];
Q_strncpy( szRejectReason, "Connection rejected by game", sizeof( szRejectReason ) );
if ( !g_pServerPluginHandler->ClientConnect( edict, m_Name, m_NetChannel->GetAddress(), szRejectReason, sizeof( szRejectReason ) ) )
{
// Reject the connection and drop the client.
Disconnect( szRejectReason );
return false;
}
return BaseClass::CheckConnect();
}
void CGameClient::ActivatePlayer( void )
{
BaseClass::ActivatePlayer();
COM_TimestampedLog( "CGameClient::ActivatePlayer -start" );
// call the spawn function
if ( !sv.m_bLoadgame )
{
g_ServerGlobalVariables.curtime = sv.GetTime();
COM_TimestampedLog( "g_pServerPluginHandler->ClientPutInServer" );
g_pServerPluginHandler->ClientPutInServer( edict, m_Name );
}
COM_TimestampedLog( "g_pServerPluginHandler->ClientActive" );
g_pServerPluginHandler->ClientActive( edict, sv.m_bLoadgame );
COM_TimestampedLog( "g_pServerPluginHandler->ClientSettingsChanged" );
g_pServerPluginHandler->ClientSettingsChanged( edict );
COM_TimestampedLog( "GetTestScriptMgr()->CheckPoint" );
GetTestScriptMgr()->CheckPoint( "client_connected" );
// don't send signonstate to client, client will switch to FULL as soon
// as the first full entity update packets has been received
// fire a activate event
IGameEvent *event = g_GameEventManager.CreateEvent( "player_activate" );
if ( event )
{
event->SetInt( "userid", GetUserID() );
g_GameEventManager.FireEvent( event );
}
COM_TimestampedLog( "CGameClient::ActivatePlayer -end" );
// We'll let them have additional snapshots so the remote can connect w/o requiring a second
// non-delta update (since the client gets the uncompressed world, then does a bunch of loading
// which can take 7-10 seconds, then ack's a NET_Tick/delta tick which is way out of date by then
m_flTimeClientBecameFullyConnected = realtime;
}
bool CGameClient::SendSignonData( void )
{
bool bClientHasdifferentTables = false;
if ( sv.m_FullSendTables.IsOverflowed() )
{
Host_Error( "Send Table signon buffer overflowed %i bytes!!!\n", sv.m_FullSendTables.GetNumBytesWritten() );
return false;
}
if ( SendTable_GetCRC() != (CRC32_t)0 )
{
bClientHasdifferentTables = m_nSendtableCRC != SendTable_GetCRC();
}
#ifdef _DEBUG
if ( sv_sendtables.GetInt() == 2 )
{
// force sending class tables, for debugging
bClientHasdifferentTables = true;
}
#endif
// Write the send tables & class infos if needed
if ( bClientHasdifferentTables )
{
if ( sv_sendtables.GetBool() )
{
// send client class table descriptions so it can rebuild tables
ConDMsg("Client sent different SendTable CRC, sending full tables.\n" );
m_NetChannel->SendData( sv.m_FullSendTables );
}
else
{
Disconnect( "Server uses different class tables" );
return false;
}
}
else
{
// use your class infos, CRC is correct
CSVCMsg_ClassInfo_t classmsg;
classmsg.set_create_on_client( true );
m_NetChannel->SendNetMsg( classmsg );
}
if ( !BaseClass::SendSignonData() )
return false;
m_nSoundSequence = 1; // reset sound sequence numbers after signon block
return true;
}
void CGameClient::SpawnPlayer( void )
{
SV_ValidateMinRequiredClients( VALIDATE_SPAWN );
// run the entrance script
if ( sv.m_bLoadgame )
{ // loaded games are fully inited already
// if this is the last client to be connected, unpause
sv.SetPaused( false );
}
else
{
// set up the edict
Assert( serverGameEnts );
serverGameEnts->FreeContainingEntity( edict );
InitializeEntityDLLFields( edict );
}
// restore default client entity and turn off replay mdoe
m_nEntityIndex = m_nClientSlot+1;
m_bIsInReplayMode = false;
// set view entity
CSVCMsg_SetView_t setView;
setView.set_entity_index( m_nEntityIndex );
SendNetMsg( setView );
BaseClass::SpawnPlayer();
}
CClientFrame *CGameClient::GetDeltaFrame( int nTick )
{
Assert ( !IsHLTV() ); // has no ClientFrames
#if defined( REPLAY_ENABLED )
Assert ( !IsReplay() ); // has no ClientFrames
#endif
if ( m_bIsInReplayMode )
{
int followEntity;
serverGameClients->GetReplayDelay( edict, followEntity );
Assert( followEntity > 0 );
CGameClient *pFollowEntity = sv.Client( followEntity-1 );
if ( pFollowEntity )
return pFollowEntity->GetClientFrame( nTick );
}
return GetClientFrame( nTick );
}
void CGameClient::WriteViewAngleUpdate()
{
//
// send the current viewpos offset from the view entity
//
// a fixangle might get lost in a dropped packet. Oh well.
if ( IsFakeClient() && !IsSplitScreenUser() )
return;
Assert( serverGameClients );
CPlayerState *pl = serverGameClients->GetPlayerState( edict );
Assert( pl || IsSplitScreenUser() );
if ( !pl )
return;
if ( pl->fixangle != FIXANGLE_NONE )
{
if ( pl->fixangle == FIXANGLE_RELATIVE )
{
CSVCMsg_FixAngle_t fixAngle;
fixAngle.set_relative( true );
fixAngle.mutable_angle()->set_x( pl->anglechange.x );
fixAngle.mutable_angle()->set_y( pl->anglechange.y );
fixAngle.mutable_angle()->set_z( pl->anglechange.z );
m_NetChannel->SendNetMsg( fixAngle );
pl->anglechange.Init(); // clear
}
else
{
CSVCMsg_FixAngle_t fixAngle;
fixAngle.set_relative( false );
fixAngle.mutable_angle()->set_x( pl->v_angle.x );
fixAngle.mutable_angle()->set_y( pl->v_angle.y );
fixAngle.mutable_angle()->set_z( pl->v_angle.z );
m_NetChannel->SendNetMsg( fixAngle );
}
pl->fixangle = FIXANGLE_NONE;
}
}
/*
===================
SV_ValidateClientCommand
Determine if passed in user command is valid.
===================
*/
bool CGameClient::IsEngineClientCommand( const CCommand &args ) const
{
if ( args.ArgC() == 0 )
return false;
for ( int i = 0; s_clcommands[i] != NULL; ++i )
{
if ( !Q_strcasecmp( args[0], s_clcommands[i] ) )
return true;
}
return false;
}
bool CGameClient::SendNetMsg( INetMessage &msg, bool bForceReliable, bool bVoice )
{
if ( m_bIsHLTV )
{
if ( CHLTVServer* hltv = GetAnyConnectedHltvServer() )
{// pass this message to HLTV
return hltv->SendNetMsg( msg, bForceReliable, bVoice );
}
else
{
Warning("HLTV client has no HLTV server connected\n");
return false;
}
}
#if defined( REPLAY_ENABLED )
if ( m_bIsReplay )
{
// pass this message to replay
return replay->SendNetMsg( msg, bForceReliable, bVoice );
}
#endif
if ( IsHltvReplay() )
{
if ( msg.GetType() != svc_VoiceData ) // let the voice messages through
{
Assert( !bVoice );
bool bResult = true;
if ( msg.GetType() == svc_UserMessage )
{
// chat: see UTIL_SayText2Filter(), Say_Host() and "player_say" GameMessage
CSVCMsg_UserMessage_t &userMessageHeader = ( CSVCMsg_UserMessage_t & )msg;
// Only send through those user messages that require real-time timeline on the client.
switch ( userMessageHeader.msg_type() )
{
case 22: // CS_UM_RadioText
case 5: //CS_UM_SayText
case 6: // CS_UM_SayText2
case 7: // CS_UM_TextMsg
case 18: // CS_UM_RawAudio
// mark this message as real-time and send with that flag, to distinguish it from the replay messages
userMessageHeader.set_passthrough( 1 );
bResult = BaseClass::SendNetMsg( msg, bForceReliable, bVoice );
userMessageHeader.clear_passthrough();
break;
}
}
return bResult; // just ignore all (other) new messages: we'll take them from hltv later if needed
}
Assert( bVoice );
}
return BaseClass::SendNetMsg( msg, bForceReliable, bVoice );
}
static CUtlStringToken s_HltvUnskippableEvents[] = {
"round_start",
"begin_new_match",
"game_newmap"
};
static CUtlStringToken s_HltvQueueableEvents[] = {
"teamplay_round_start","teamplay_round_end",
MakeStringToken( "endmatch_cmm_start_reveal_items" ),
"announce_phase_end",
"cs_match_end_restart",
"round_freeze_end"
};
static CUtlStringToken s_HltvPassThroughRealtimeEvents[] = {
"teamplay_broadcast_audio",
"player_chat",
"player_say",
"player_death",
"round_mvp",
"round_end"
};
bool IsInList( const char *pEventName, const char **ppList, int nListCount )
{
for ( int i = 0; i < nListCount; ++i )
{
if ( !V_strcmp( pEventName, ppList[ i ] ) )
{
return true;
}
}
return false;
}
bool IsInList( CUtlStringToken eventName, const CUtlStringToken *pTokenList, int nListCount )
{
for ( int i = 0; i < nListCount; ++i )
{
if ( eventName == pTokenList[ i ] )
{
return true;
}
}
return false;
}
void CGameClient::FireGameEvent( IGameEvent *event )
{
if ( IsHltvReplay() )
{
const char *pEventName = event->GetName(); // please don't fold the string variable, it's useful for debugging
CUtlStringToken eventName = MakeStringToken( pEventName );
if ( IsInList( eventName, s_HltvQueueableEvents, ARRAYSIZE( s_HltvQueueableEvents ) ) )
{
if ( event->IsReliable() ) // skip this event if it's not reliable
{
CSVCMsg_GameEvent_t *pEventMsg = new CSVCMsg_GameEvent_t;
if ( g_GameEventManager.SerializeEvent( event, pEventMsg ) )
{
m_HltvQueuedMessages.AddToTail( pEventMsg );
}
else
{
delete pEventMsg;
}
}
return;
}
else if ( IsInList( eventName, s_HltvUnskippableEvents, ARRAYSIZE( s_HltvUnskippableEvents ) ) )
{
Msg( "%s (%d) is skipping replay in progress (%d/%d) due to event %s\n", m_Name, m_UserID, sv.GetTick() - m_nHltvReplayDelay - m_nHltvReplayStartAt, m_nHltvReplayStopAt - m_nHltvReplayStartAt, pEventName );
StopHltvReplay();
return BaseClass::FireGameEvent( event );
}
else if ( IsInList( eventName, s_HltvPassThroughRealtimeEvents, ARRAYSIZE( s_HltvPassThroughRealtimeEvents ) ) )
{
return BaseClass::FireGameEvent( event, true ); // mark the event as real-time pass-through so that on the other end the client knows it's happening in real time, and it's not a replayed-back event
}
else
{
// For now, ignore game events by default, if they are sent while we're in a replay.
return;
}
}
return BaseClass::FireGameEvent( event );
}
bool CGameClient::ExecuteStringCommand( const char *pCommandString )
{
// first let the baseclass handle it
if ( BaseClass::ExecuteStringCommand( pCommandString ) )
return true;
// Determine whether the command is appropriate
CCommand args;
if ( !args.Tokenize( pCommandString, kCommandSrcNetClient ) )
return false;
if ( args.ArgC() == 0 )
return false;
// Disallow all string commands from client
// Special case for 0 here, as we don't kick in this case.
int cmdQuota = sv_quota_stringcmdspersecond.GetInt();
if ( cmdQuota == 0 )
return false;
// Client is about to execute a string command, check if we need to reset quota
if ( realtime - m_flLastClientCommandQuotaStart >= 1.0 )
{
// reset quota
m_flLastClientCommandQuotaStart = realtime;
m_numClientCommandsInQuota = 0;
}
++ m_numClientCommandsInQuota;
if ( m_numClientCommandsInQuota > cmdQuota )
{
// Disconnect player for Denial-of-service attack
// REI: Remove this define when we unify trunk/staging (trunk uses enum reasons for disconnection)
// REI: See network_connection.proto for where this is supposed to come from
#define NETWORK_DISCONNECT_SERVER_DOS "#GameUI_Disconnect_TooManyCommands"
Disconnect( NETWORK_DISCONNECT_SERVER_DOS );
return false;
}
if ( IsEngineClientCommand( args ) )
{
Cmd_ExecuteCommand( CBUF_SERVER, args, m_nClientSlot );
return true;
}
// FIXME: This logic seem strange; why can't we just go through Cmd_ExecuteCommand? We should check for
// permission (cheat, sponly, gamedll since we are coming from client code) there, right?
const ConCommandBase *pCommand = g_pCVar->FindCommandBase( args[ 0 ] );
if ( pCommand && pCommand->IsCommand() && pCommand->IsFlagSet( FCVAR_GAMEDLL ) )
{
// Allow cheat commands in singleplayer, debug, or multiplayer with sv_cheats on
// NOTE: Don't bother with rpt stuff; commands that matter there shouldn't have FCVAR_GAMEDLL set
if ( pCommand->IsFlagSet( FCVAR_CHEAT ) )
{
if ( sv.IsMultiplayer() && !CanCheat() )
return false;
}
else if ( !sv_allow_legacy_cmd_execution_from_client.GetBool() && !pCommand->IsFlagSet( FCVAR_GAMEDLL_FOR_REMOTE_CLIENTS )
#if !defined DEDICATED
&& ( sv.IsDedicated() || m_nClientSlot != GetBaseLocalClient().m_nPlayerSlot )
#endif
)
{
#if DEVELOPMENT_ONLY
Warning( "WARNING: Client sent concommand %s which is not flagged as executable, ignoring\n" );
#endif
return false;
}
if ( pCommand->IsFlagSet( FCVAR_SPONLY ) )
{
if ( sv.IsMultiplayer() )
{
return false;
}
}
// REI 7/25/2016:
// I added this here; this state is normally set by Cmd_ExecuteCommand when executing
// a kCmdSrcNetClient command.
//
// Is there a reason this code path goes directly to Cmd_Dispatch instead
// of the usual path through Cmd_ExecuteCommand?
cmd_clientslot = m_nClientSlot;
g_pServerPluginHandler->SetCommandClient( m_nClientSlot );
Cmd_Dispatch( pCommand, args );
}
else
{
g_pServerPluginHandler->ClientCommand( edict, args ); // TODO pass client id and string
}
return true;
}
extern ConVar sv_multiplayer_maxsounds;
bool CGameClient::SendSnapshot( CClientFrame * pFrame )
{
if ( IsHltvReplay() )
{
return SendHltvReplaySnapshot( pFrame );
}
if ( m_bIsHLTV )
{
SNPROF( "SendSnapshot - HLTV" );
CHLTVServer *hltv = GetAnyConnectedHltvServer();
// pack sounds to one message
if ( m_Sounds.Count() > 0 )
{
CSVCMsg_Sounds_t sounds;
sounds.SetReliable( false );
FillSoundsMessage( sounds, m_Server->IsMultiplayer() ? sv_multiplayer_maxsounds.GetInt() : 255 );
hltv->SendNetMsg( sounds );
}
int maxEnts = tv_transmitall.GetBool()?255:64;
CSVCMsg_TempEntities_t tempentsmsg;
hltv->WriteTempEntities( this, pFrame->GetSnapshot(), m_pLastSnapshot.GetObject(), tempentsmsg, maxEnts );
if ( tempentsmsg.num_entries() )
{
tempentsmsg.WriteToBuffer( *hltv->GetBuffer( HLTV_BUFFER_TEMPENTS ) );
}
// add snapshot to HLTV server frame list
hltv->AddNewDeltaFrame( pFrame );
// remember this snapshot
m_pLastSnapshot = pFrame->GetSnapshot();
Assert( !GetHltvReplayDelay() ); // hltv master client shouldn't be in killer replay mode
// fake acknowledgement, remove ClientFrame reference immediately
UpdateAcknowledgedFramecount( pFrame->tick_count );
return true;
}
#if defined( REPLAY_ENABLED )
if ( m_bIsReplay )
{
SNPROF( "SendSnapshot - Replay" );
char *buf = (char *)_alloca( NET_MAX_PAYLOAD );
// pack sounds to one message
if ( m_Sounds.Count() > 0 )
{
CSVCMsg_Sounds_t sounds;
sounds.SetReliable( false );
FillSoundsMessage( sounds, m_Server->IsMultiplayer() ? sv_multiplayer_maxsounds.GetInt() : 255 );
replay->SendNetMsg( sounds );
}
int maxEnts = replay_transmitall.GetBool()?255:64;
replay->WriteTempEntities( this, pFrame->GetSnapshot(), m_pLastSnapshot.GetObject(), *replay->GetBuffer( REPLAY_BUFFER_TEMPENTS ), maxEnts );
// add snapshot to Replay server frame list
replay->AddNewFrame( pFrame );
// remember this snapshot
m_pLastSnapshot = pFrame->GetSnapshot();
// fake acknowledgement, remove ClientFrame reference immediately
UpdateAcknowledgedFramecount( pFrame->tick_count );
return true;
}
#endif
// update client viewangles update
WriteViewAngleUpdate();
bool bRet;
bRet = BaseClass::SendSnapshot( pFrame );
if ( bRet )
{
// Send messages that were queued up during replay - they need fully created entities, which is why I'm sending them after SendSnapshot
for ( INetMessage * pMessage : m_HltvQueuedMessages )
{
if ( m_NetChannel )
{
if ( !m_NetChannel->SendNetMsg( *pMessage, true ) ) // we only queue reliable messages
break;
}
}
m_HltvQueuedMessages.PurgeAndDeleteElements();
//////////////////////////////////////////////////////////////////////////
if ( IsFakeClient() )
{
Assert( !GetHltvReplayDelay() ); // fake clients should not be in "killer replay" mode
// fake acknowledgement, remove ClientFrame reference immediately
UpdateAcknowledgedFramecount( pFrame->tick_count );
}
}
return bRet;
}
//ConVar replay_hltv_voice( "replay_hltv_voice", "0", FCVAR_RELEASE );
bool CGameClient::SendHltvReplaySnapshot( CClientFrame * pFrame )
{
Assert( IsHltvReplay() );
VPROF_BUDGET( "CGameClient::SendHltvReplaySnapshot", "HLTV" );
byte buf[ NET_MAX_PAYLOAD ];
bf_write msg( "CGameClient::SendHltvReplaySnapshot", buf, sizeof( buf ) );
// if we send a full snapshot (no delta-compression) before, wait until client
// received and acknowledge that update. don't spam client with full updates
if ( m_pLastSnapshot == pFrame->GetSnapshot() )
{
// never send the same snapshot twice
m_NetChannel->Transmit();
return false;
}
if ( m_nForceWaitForTick > 0 )
{
// just continue transmitting reliable data
Assert( !m_bFakePlayer ); // Should never happen
m_NetChannel->Transmit();
return false;
}
CClientFrame *pDeltaFrame = m_pHltvReplayServer->GetDeltaFrame( m_nDeltaTick ); // NULL if delta_tick is not found
CHLTVFrame *pLastFrame = ( CHLTVFrame* )m_pHltvReplayServer->GetDeltaFrame( m_nHltvLastSendTick );
if ( pLastFrame )
{
// start first frame after last send
pLastFrame = ( CHLTVFrame* )pLastFrame->m_pNext;
}
// add all reliable messages between ]lastframe,currentframe]
// add all tempent & sound messages between ]lastframe,currentframe]
while ( pLastFrame && pLastFrame->tick_count <= pFrame->tick_count )
{
m_NetChannel->SendData( pLastFrame->m_Messages[ HLTV_BUFFER_RELIABLE ], true );
if ( pDeltaFrame )
{
// if we send entities delta compressed, also send unreliable data
m_NetChannel->SendData( pLastFrame->m_Messages[ HLTV_BUFFER_UNRELIABLE ], false );
// we skip the voice messages here because we don't want to hear the 10-second-delayed voice.
// we might want to send the real-time voice though
// if ( replay_hltv_voice.GetBool() )
// {
// int nVoiceBits = pLastFrame->m_Messages[ HLTV_BUFFER_VOICE ].m_nDataBits;
// if ( nVoiceBits > 0 )
// {
// //Msg( "replaying voice: %d bits\n", nVoiceBits );
// m_NetChannel->SendData( pLastFrame->m_Messages[ HLTV_BUFFER_VOICE ], false ); // we separate voice, even though it's simply more unreliable data, because we don't send it in replay
// }
// }
}
pLastFrame = ( CHLTVFrame* )pLastFrame->m_pNext;
}
// now create client snapshot packet
// send tick time
CNETMsg_Tick_t tickmsg( pFrame->tick_count, host_frameendtime_computationduration, host_frametime_stddeviation, host_framestarttime_stddeviation );
tickmsg.set_hltv_replay_flags( 1 );
tickmsg.WriteToBuffer( msg );
// Update shared client/server string tables. Must be done before sending entities
m_Server->m_StringTables->WriteUpdateMessage( NULL, GetMaxAckTickCount(), msg );
// TODO delta cache whole snapshots, not just packet entities. then use net_Align
// send entity update, delta compressed if deltaFrame != NULL
{
CSVCMsg_PacketEntities_t packetmsg;
m_pHltvReplayServer->WriteDeltaEntities( this, pFrame, pDeltaFrame, packetmsg );
packetmsg.WriteToBuffer( msg );
}
// write message to packet and check for overflow
if ( msg.IsOverflowed() )
{
if ( !pDeltaFrame )
{
// if this is a reliable snapshot, drop the client
//Disconnect( NETWORK_DISCONNECT_SNAPSHOTOVERFLOW );
return false;
}
else
{
// unreliable snapshots may be dropped
ConMsg( "WARNING: msg overflowed for %s\n", m_Name );
msg.Reset();
}
}
// remember this snapshot
m_pLastSnapshot = pFrame->GetSnapshot();
m_nHltvLastSendTick = pFrame->tick_count;
// Don't send the datagram to fakeplayers
if ( m_bFakePlayer )
{
m_nDeltaTick = pFrame->tick_count;
return true;
}
bool bSendOK;
// is this is a full entity update (no delta) ?
if ( !pDeltaFrame )
{
if ( replay_debug.GetInt() > 10 )
Msg( "HLTV send full frame %d: %d bytes\n", pFrame->tick_count, ( msg.m_iCurBit + 7 ) / 8 );
// transmit snapshot as reliable data chunk
bSendOK = m_NetChannel->SendData( msg );
bSendOK = bSendOK && m_NetChannel->Transmit();
// remember this tickcount we send the reliable snapshot
// so we can continue sending other updates if this has been acknowledged
m_nForceWaitForTick = pFrame->tick_count;
}
else
{
if ( replay_debug.GetInt() > 10 )
Msg( "HLTV send %d-delta of frame %d: %d bytes\n", pFrame->tick_count - pDeltaFrame->tick_count, pFrame->tick_count, ( msg.m_iCurBit + 7 ) / 8 );
// just send it as unreliable snapshot
bSendOK = m_NetChannel->SendDatagram( &msg ) > 0;
}
if ( !bSendOK )
{
Disconnect( "Snapshot error" );
return false;
}
return true;
}
bool CGameClient::CanStartHltvReplay()
{
CActiveHltvServerIterator hltv;
if ( hltv && !IsFakeClient() )
{
int nOldestHltvTick = hltv->GetOldestTick();
return ( nOldestHltvTick > 0 ); // we should have some ticks in history to proceed successfully
}
return false;
}
void CGameClient::ResetReplayRequestTime()
{
m_flHltvLastReplayRequestTime = -spec_replay_message_time.GetFloat();
}
bool CGameClient::StartHltvReplay( const HltvReplayParams_t &params )
{
m_HltvReplayStats.nStartRequests++;
CActiveHltvServerIterator hltv;
if ( hltv && !IsFakeClient() )
{
if ( !params.m_bAbortCurrentReplay && m_nHltvReplayDelay )
{
// we're already in replay, do not abort it to start a new one
DevMsg( "Hltv Replay failure: already in replay\n" );
m_HltvReplayStats.nFailedReplays[ HltvReplayStats_t::FAILURE_ALREADY_IN_REPLAY ]++;
return false;
}
int nServerTick = sv.GetTick();
float flRealTime = Plat_FloatTime();
if ( fabsf( flRealTime - m_flHltvLastReplayRequestTime ) <= spec_replay_rate_limit.GetFloat() )
{
DevMsg( "Hltv Replay failure: requests are rate limited to no more than 1 per %g seconds\n", spec_replay_rate_limit.GetFloat() );
m_HltvReplayStats.nFailedReplays[ HltvReplayStats_t::FAILURE_TOO_FREQUENT ]++;
return false;
}
int nOldestHltvTick = hltv->GetOldestTick();
if ( nOldestHltvTick > 0 ) // we should have some ticks in history to proceed successfully
{
// GetMaxAckTickCount() cannot be older than signon tick, and I don't know how much logic quietly relies on it, so to make it easier on myself I just won't allow reaching further into the past than the signon time, at least for the first iteration of replay
int nOldestClientTick = Max( m_nSignonTick, nOldestHltvTick );
float flTickInterval = sv.GetTickInterval();
int nDesiredReplayDelay = params.m_flDelay / flTickInterval, nNewReplayDelay = nDesiredReplayDelay;
int nNewReplayStopAt = nServerTick + params.m_flStopAt / flTickInterval;
Assert( hltv->m_CurrentFrame->tick_count >= hltv->m_nFirstTick ); // first known tick should have happened at or before the time of the current recorded HLTV frame
if ( nServerTick - nNewReplayDelay < nOldestClientTick )
{
nNewReplayDelay = nServerTick - nOldestClientTick;
}
if ( nNewReplayDelay <= 0 || nNewReplayStopAt <= nServerTick - nNewReplayDelay )
{
m_HltvReplayStats.nFailedReplays[ HltvReplayStats_t::FAILURE_NO_FRAME ]++;
nNewReplayDelay = nNewReplayStopAt = 0;
m_pCurrentFrame = NULL;
}
else
{
m_pCurrentFrame = hltv->ExpandAndGetClientFrame( nServerTick - nNewReplayDelay, false );
if ( m_pCurrentFrame )
{
nNewReplayDelay = nServerTick - m_pCurrentFrame->tick_count;
}
else
{
m_HltvReplayStats.nFailedReplays[ HltvReplayStats_t::FAILURE_NO_FRAME2 ]++;
nNewReplayDelay = nNewReplayStopAt = 0;
}
}
if ( !m_pCurrentFrame || abs( nNewReplayDelay - nDesiredReplayDelay ) > 64 )
{
DevMsg( "Hltv replay delay %u cannot match the requested delay %u\n", nNewReplayDelay, nDesiredReplayDelay );
m_HltvReplayStats.nFailedReplays[ HltvReplayStats_t::FAILURE_CANNOT_MATCH_DELAY ]++;
nNewReplayDelay = nNewReplayStopAt = 0; // couldn't find anything decently approaching the desired delay in history
}
// now commit all the changes if needed
m_nHltvReplayStopAt = nNewReplayStopAt;
m_nHltvReplayStartAt = nServerTick;
if ( nNewReplayDelay != m_nHltvReplayDelay )
{
CSVCMsg_HltvReplay_t msg;
msg.set_delay( nNewReplayDelay );
msg.set_primary_target( params.m_nPrimaryTargetEntIndex );
msg.set_replay_stop_at( nNewReplayStopAt );
msg.set_replay_start_at( m_nHltvReplayStartAt );
if ( params.m_flSlowdownRate > 1.0f / 16.0f && params.m_flSlowdownBeginAt + 0.125f < params.m_flSlowdownEndAt )
{
m_flHltvReplaySlowdownRate = params.m_flSlowdownRate;
m_nHltvReplaySlowdownBeginAt = Max<int>( nServerTick - nNewReplayDelay, nServerTick + params.m_flSlowdownBeginAt / flTickInterval );
m_nHltvReplaySlowdownEndAt = Max<int>( m_nHltvReplaySlowdownBeginAt, nServerTick + params.m_flSlowdownEndAt / flTickInterval );
msg.set_replay_slowdown_rate( m_flHltvReplaySlowdownRate );
msg.set_replay_slowdown_begin( m_nHltvReplaySlowdownBeginAt );
msg.set_replay_slowdown_end( m_nHltvReplaySlowdownEndAt );
}
else
{
m_flHltvReplaySlowdownRate = 1.0f;
m_nHltvReplaySlowdownBeginAt = 0;
m_nHltvReplaySlowdownEndAt = 0;
}
SendNetMsg( msg, true );
//if( nNewReplayDelay) ExecuteStringCommand( "spectate" );
if ( replay_debug.GetBool() )
Msg( "Start HLMV Replay at %d, delay %d, until %d\n", nServerTick, nNewReplayDelay, nNewReplayStopAt );
m_nHltvReplayDelay = nNewReplayDelay;
m_pHltvReplayServer = hltv;
m_nDeltaTick = -1;
if ( m_nStringTableAckTick > nServerTick - nNewReplayDelay )
m_nStringTableAckTick = 0; // need to reset the stringtables, as they were updated in the future relative to the delayed stream
m_pLastSnapshot = NULL;
m_nHltvLastSendTick = 0;
FreeBaselines();
// all these data become invalid once we start sending HLTV packets from the past
m_PackInfo.Reset();
m_PrevPackInfo.Reset();
m_pCurrentFrame = NULL;
DeleteClientFrames( -1 ); // Should we clean up all the frames? Seems logical, as we'll never need them
m_flHltvLastReplayRequestTime = flRealTime;
m_HltvReplayStats.nSuccessfulStarts++;
return true;
}
}
else
{
DevMsg( "Hltv Replay failure: HLTV frame is not ready\n" );
m_HltvReplayStats.nFailedReplays[ HltvReplayStats_t::FAILURE_FRAME_NOT_READY ]++;
}
}
return false;
}
CBaseClient *CGameClient::GetPropCullClient()
{
return GetHltvReplayDelay() ? m_pHltvReplayServer->m_MasterClient : this;
}
static char s_HltvReplayBuffers[ 8 ][ 256 ];
uint s_nLastHltvReplayBuffer = 0;
const char *HltvReplayStats_t::AsString()const
{
if ( !nSuccessfulStarts && !nStartRequests && !nFullReplays && !nUserCancels && !nStopRequests )
{
return "";
}
s_nLastHltvReplayBuffer++;
if ( s_nLastHltvReplayBuffer >= ARRAYSIZE( s_HltvReplayBuffers ) )
s_nLastHltvReplayBuffer = 0;
char *pBuffer = s_HltvReplayBuffers[ s_nLastHltvReplayBuffer ];
CUtlString fails = ":";
int nTotalFailures = 0;
for ( int i = 0; i < NUM_FAILURES; ++i )
{
if ( i )
fails += ",";
if ( nFailedReplays[ i ] )
{
fails.Append( CFmtStr( "%u", nFailedReplays[ i ] ) );
nTotalFailures += nFailedReplays[ i ];
}
}
if ( nNetAbortReplays )
{
fails.Append( CFmtStr( "[%u!]", nNetAbortReplays ) );
nTotalFailures += nNetAbortReplays;
}
if ( !nTotalFailures )
fails = "";
V_snprintf( pBuffer, sizeof( s_HltvReplayBuffers[ s_nLastHltvReplayBuffer ] ), "%u/%u started, %u full, %u cancels / %u stops, %u fails%s", nSuccessfulStarts, nStartRequests, nFullReplays, nUserCancels, nStopRequests, nTotalFailures, fails.Get() );
return pBuffer;
}
void CGameClient::StepHltvReplayStatus( int nServerTick )
{
if ( IsHltvReplay() )
{
if ( m_nHltvLastSendTick >= m_nHltvReplayStopAt )
{
m_HltvReplayStats.nFullReplays++;
StopHltvReplay();
}
else if ( m_nForceWaitForTick > 0 )
{
if ( !m_pCurrentFrame || m_pCurrentFrame->tick_count >= m_nHltvReplayStopAt )
{
// client doesn't respond or there's no current frame -both indicating some problem. And we're past the time alotted for replay - we should just abort.
Msg( "Client %d (eidx %d, user id %d) %s - aborting wait for ack for tick %d, stopping replay at %d>=%d\n", m_nClientSlot, m_nEntityIndex, m_UserID, m_Name, m_nForceWaitForTick, m_pCurrentFrame ? m_pCurrentFrame->tick_count : 0, m_nHltvReplayStopAt );
m_HltvReplayStats.nNetAbortReplays++;
StopHltvReplay();
}
}
}
}
void CGameClient::StopHltvReplay()
{
if ( IsHltvReplay() )
{
m_HltvReplayStats.nStopRequests++;
if ( m_nHltvLastSendTick < m_nHltvReplayStopAt )
m_HltvReplayStats.nAbortStopRequests++;
m_nHltvReplayStopAt = 0;
m_nHltvReplayDelay = 0;
m_nDeltaTick = -1;
m_nForceWaitForTick = -1;
m_pLastSnapshot = NULL; // it doesn't matter what last snapshot we sent; we need to send a full frame update
m_nHltvLastSendTick = 0;
FreeBaselines();
m_pCurrentFrame = NULL;
DeleteClientFrames( -1 ); // Should we clean up all the frames? Seems logical, as we'll never need them
Assert( CountClientFrames() == 0 ); // we shouldn't have used the client frame manager to send HLTV stream to client
}
// just in case, send the end-of-hltv replay message even if we are not in replay
{
CSVCMsg_HltvReplay_t msg;
SendNetMsg( msg, true );
}
}
//-----------------------------------------------------------------------------
// This function contains all the logic to determine if we should send a datagram
// to a particular client
//-----------------------------------------------------------------------------
bool CGameClient::ShouldSendMessages( void )
{
if ( m_bIsHLTV )
{
// calc snapshot interval
if ( CHLTVServer *hltv = GetAnyConnectedHltvServer() )
{
int nSnapshotInterval = 1.0f / ( m_Server->GetTickInterval() * hltv->GetSnapshotRate() );
// I am the HLTV client, record every nSnapshotInterval tick
return ( sv.m_nTickCount >= ( hltv->m_nLastTick + nSnapshotInterval ) );
}
else
{
return false; // something is wrong, it'll assert in GetAnyConnectedHltvServer()..
}
}
#if defined( REPLAY_ENABLED )
if ( m_bIsReplay )
{
// calc snapshot interval
int nSnapshotInterval = 1.0f / ( m_Server->GetTickInterval() * replay_snapshotrate.GetFloat() );
// I am the Replay client, record every nSnapshotInterval tick
return ( sv.m_nTickCount >= (replay->m_nLastTick + nSnapshotInterval) );
}
#endif
// If sv_stressbots is true, then treat a bot more like a regular client and do deltas and such for it.
if( !sv_replaybots.GetBool() && IsFakeClient() )
{
if ( !sv_stressbots.GetBool() )
return false;
}
return BaseClass::ShouldSendMessages();
}
void CGameClient::FileReceived( const char *fileName, unsigned int transferID, bool bIsReplayDemoFile /* = false */ )
{
//check if file is one of our requested custom files
for ( int i=0; i<MAX_CUSTOM_FILES; i++ )
{
if ( m_nCustomFiles[i].reqID == transferID )
{
m_nFilesDownloaded++;
// broadcast update to other clients so they start downlaoding this file
m_Server->UserInfoChanged( m_nClientSlot );
return;
}
}
Msg( "CGameClient::FileReceived: %s not wanted.\n", fileName );
}
void CGameClient::FileRequested(const char *fileName, unsigned int transferID, bool bIsReplayDemoFile /* = false */ )
{
DevMsg( "File '%s' requested from client %s.\n", fileName, m_NetChannel->GetAddress() );
if ( sv_allowdownload.GetBool() )
{
m_NetChannel->SendFile( fileName, transferID, bIsReplayDemoFile );
}
else
{
m_NetChannel->DenyFile( fileName, transferID, bIsReplayDemoFile );
}
}
void CGameClient::FileDenied(const char *fileName, unsigned int transferID, bool bIsReplayDemoFile /* = false */ )
{
ConMsg( "Downloading file '%s' from client %s failed.\n", fileName, GetClientName() );
}
void CGameClient::FileSent(const char *fileName, unsigned int transferID, bool bIsReplayDemoFile /* = false */ )
{
ConMsg( "Sent file '%s' to client %s.\n", fileName, GetClientName() );
}
void CGameClient::PacketStart(int incoming_sequence, int outgoing_acknowledged)
{
for ( int i = 1; i < host_state.max_splitscreen_players; ++i )
{
if ( !m_SplitScreenUsers[ i ] )
continue;
m_SplitScreenUsers[ i ]->PacketStart( incoming_sequence, outgoing_acknowledged );
}
// make sure m_LastMovementTick != sv.tickcount
m_LastMovementTick = ( sv.m_nTickCount - 1 );
host_client = this;
// During connection, only respond if client sends a packet
m_bReceivedPacket = true;
}
void CGameClient::PacketEnd()
{
// Fix up clock in case prediction/etc. code reset it.
g_ServerGlobalVariables.frametime = host_state.interval_per_tick;
}
void CGameClient::ConnectionClosing(const char *reason)
{
SV_RedirectEnd();
Disconnect( (reason!=NULL)?reason:"Connection closing" );
}
void CGameClient::ConnectionCrashed(const char *reason)
{
if ( m_Name[0] && IsConnected() )
{
SV_RedirectEnd();
Disconnect( (reason!=NULL)?reason:"Connection lost" );
}
}
CClientFrame *CGameClient::GetSendFrame()
{
CClientFrame *pFrame = m_pCurrentFrame;
// just return if replay is disabled
if ( sv_maxreplay.GetFloat() <= 0 || IsHltvReplay() )
return pFrame;
int followEntity;
int delayTicks = serverGameClients->GetReplayDelay( edict, followEntity );
bool isInReplayMode = ( delayTicks > 0 );
if ( isInReplayMode != m_bIsInReplayMode )
{
// force a full update when modes are switched
m_nDeltaTick = -1;
m_bIsInReplayMode = isInReplayMode;
if ( isInReplayMode )
{
m_nEntityIndex = followEntity;
}
else
{
m_nEntityIndex = m_nClientSlot+1;
}
}
Assert( (m_nClientSlot+1 == m_nEntityIndex) || isInReplayMode );
if ( isInReplayMode )
{
CGameClient *pFollowPlayer = sv.Client( followEntity-1 );
if ( !pFollowPlayer )
return NULL;
pFrame = pFollowPlayer->GetClientFrame( sv.GetTick() - delayTicks, false );
if ( !pFrame )
return NULL;
if ( m_pLastSnapshot == pFrame->GetSnapshot() )
return NULL;
}
return pFrame;
}
bool CGameClient::IgnoreTempEntity( CEventInfo *event )
{
// in replay mode replay all temp entities
if ( m_bIsInReplayMode )
return false;
return BaseClass::IgnoreTempEntity( event );
}
const CCheckTransmitInfo* CGameClient::GetPrevPackInfo()
{
Assert( !IsHltvReplay() ); // we don't maintain this data during Hltv-fed replay
return &m_PrevPackInfo;
}
// This code is useful for verifying that the networking of soundinfo_t stuff isn't borked.
#if 0
#include "vstdlib/random.h"
class CTestSoundInfoNetworking
{
public:
CTestSoundInfoNetworking();
void RunTest();
private:
void CreateRandomSounds( int nCount );
void CreateRandomSound( SoundInfo_t &si );
void Compare( const SoundInfo_t &s1, const SoundInfo_t &s2 );
CUtlVector< SoundInfo_t > m_Sounds;
CUtlVector< SoundInfo_t > m_Received;
};
static CTestSoundInfoNetworking g_SoundTest;
CON_COMMAND( st, "sound test" )
{
int nCount = 1;
if ( args.ArgC() >= 2 )
{
nCount = clamp( Q_atoi( args.Arg( 1 ) ), 1, 100000 );
}
for ( int i = 0 ; i < nCount; ++i )
{
if ( !( i % 100 ) && i > 0 )
{
Msg( "Running test %d %f %% done\n",
i, 100.0f * (float)i/(float)nCount );
}
g_SoundTest.RunTest();
}
}
CTestSoundInfoNetworking::CTestSoundInfoNetworking()
{
}
void CTestSoundInfoNetworking::CreateRandomSound( SoundInfo_t &si )
{
int entindex = RandomInt( 0, MAX_EDICTS - 1 );
int channel = RandomInt( 0, 7 );
int soundnum = RandomInt( 0, MAX_SOUNDS - 1 );
Vector org = RandomVector( -16383, 16383 );
Vector dir = RandomVector( -1.0f, 1.0f );
float flVolume = RandomFloat( 0.1f, 1.0f );
bool bLooping = RandomInt( 0, 100 ) < 5;
int nPitch = RandomInt( 0, 100 ) < 5 ? RandomInt( 95, 105 ) : 100;
Vector lo = RandomInt( 0, 100 ) < 5 ? RandomVector( -16383, 16383 ) : org;
int speaker = RandomInt( 0, 100 ) < 2 ? RandomInt( 0, MAX_EDICTS - 1 ) : -1;
soundlevel_t level = soundlevel_t(RandomInt( 70, 150 ));
si.Set( entindex, channel, "foo.wav", org, dir, flVolume, level, bLooping, nPitch, lo, speaker );
si.nFlags = ( 1 << RandomInt( 0, 6 ) );
si.nSoundNum = soundnum;
si.bIsSentence = RandomInt( 0, 1 );
si.bIsAmbient = RandomInt( 0, 1 );
si.fDelay = RandomInt( 0, 100 ) < 2 ? RandomFloat( -0.1, 0.1f ) : 0.0f;
}
void CTestSoundInfoNetworking::CreateRandomSounds( int nCount )
{
m_Sounds.Purge();
m_Sounds.EnsureCount( nCount );
for ( int i = 0; i < nCount; ++i )
{
SoundInfo_t &si = m_Sounds[ i ];
CreateRandomSound( si );
}
}
void CTestSoundInfoNetworking::RunTest()
{
int m_nSoundSequence = 0;
CreateRandomSounds( 512 );
SoundInfo_t defaultSound; defaultSound.SetDefault();
SoundInfo_t *pDeltaSound = &defaultSound;
CSVCMsg_Sounds_t msg;
msg.set_reliable_sound( false );
msg.SetReliable( false );
for ( int i = 0 ; i < m_Sounds.Count(); i++ )
{
SoundInfo_t &sound = m_Sounds[ i ];
sound.WriteDelta( pDeltaSound, msg.m_DataOut );
pDeltaSound = &m_Sounds[ i ];
}
// Now read them out
defaultSound.SetDefault();
pDeltaSound = &defaultSound;
msg.m_DataIn.StartReading( buf, msg.m_DataOut.GetNumBytesWritten(), 0, msg.m_DataOut.GetNumBitsWritten() );
SoundInfo_t sound;
for ( int i=0; i<msg.m_nNumSounds; i++ )
{
sound.ReadDelta( pDeltaSound, msg.m_DataIn );
pDeltaSound = &sound; // copy delta values
if ( msg.m_bReliableSound )
{
// client is incrementing the reliable sequence numbers itself
m_nSoundSequence = ( m_nSoundSequence + 1 ) & SOUND_SEQNUMBER_MASK;
Assert ( sound.nSequenceNumber == 0 );
sound.nSequenceNumber = m_nSoundSequence;
}
// Add no ambient sounds to sorted queue, will be processed after packet has been completly parsed
// CL_AddSound( sound );
m_Received.AddToTail( sound );
}
// Now validate them
for ( int i = 0 ; i < msg.m_nNumSounds; ++i )
{
SoundInfo_t &server = m_Sounds[ i ];
SoundInfo_t &client = m_Received[ i ];
Compare( server, client );
}
m_Sounds.Purge();
m_Received.Purge();
}
void CTestSoundInfoNetworking::Compare( const SoundInfo_t &s1, const SoundInfo_t &s2 )
{
bool bSndStop = s2.nFlags == SND_STOP;
if ( !bSndStop && s1.nSequenceNumber != s2.nSequenceNumber )
{
Msg( "seq number mismatch %d %d\n", s1.nSequenceNumber, s2.nSequenceNumber );
}
if ( s1.nEntityIndex != s2.nEntityIndex )
{
Msg( "ent mismatch %d %d\n", s1.nEntityIndex, s2.nEntityIndex );
}
if ( s1.nChannel != s2.nChannel )
{
Msg( "channel mismatch %d %d\n", s1.nChannel, s2.nChannel );
}
Vector d;
d = s1.vOrigin - s2.vOrigin;
if ( !bSndStop && d.Length() > 32.0f )
{
Msg( "origin mismatch [%f] (%f %f %f) != (%f %f %f)\n", d.Length(), s1.vOrigin.x, s1.vOrigin.y, s1.vOrigin.z, s2.vOrigin.x, s2.vOrigin.y, s2.vOrigin.z );
}
// Vector vDirection;
float delta = fabs( s1.fVolume - s2.fVolume );
if ( !bSndStop && delta > 1.0f )
{
Msg( "vol mismatch %f %f\n", s1.fVolume, s2.fVolume );
}
if ( !bSndStop && s1.Soundlevel != s2.Soundlevel )
{
Msg( "sndlvl mismatch %d %d\n", s1.Soundlevel, s2.Soundlevel );
}
// bLooping;
if ( s1.bIsSentence != s2.bIsSentence )
{
Msg( "sentence mismatch %d %d\n", s1.bIsSentence ? 1 : 0, s2.bIsSentence ? 1 : 0 );
}
if ( s1.bIsAmbient != s2.bIsAmbient )
{
Msg( "ambient mismatch %d %d\n", s1.bIsAmbient ? 1 : 0, s2.bIsAmbient ? 1 : 0 );
}
if ( !bSndStop && s1.nPitch != s2.nPitch )
{
Msg( "pitch mismatch %d %d\n", s1.nPitch, s2.nPitch );
}
// Vector vListenerOrigin;
if ( s1.nFlags != s2.nFlags )
{
Msg( "flags mismatch %d %d\n", s1.nFlags, s2.nFlags );
}
if ( s1.nSoundNum != s2.nSoundNum )
{
Msg( "soundnum mismatch %d %d\n", s1.nSoundNum, s2.nSoundNum );
}
delta = fabs( s1.fDelay - s2.fDelay );
if ( !bSndStop && delta > 0.020f )
{
Msg( "delay mismatch %f %f\n", s1.fDelay, s2.fDelay );
}
if ( !bSndStop && s1.nSpeakerEntity != s2.nSpeakerEntity )
{
Msg( "speakerentity mismatch %d %d\n", s1.nSpeakerEntity, s2.nSpeakerEntity );
}
}
#endif