//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose: baseclient.cpp: implementation of the CBaseClient class.
//
//===========================================================================//
# include "client_pch.h"
# include "tier0/etwprof.h"
# include "eiface.h"
# include "baseclient.h"
# include "server.h"
# include "host.h"
# include "networkstringtable.h"
# include "framesnapshot.h"
# include "GameEventManager.h"
# include "LocalNetworkBackdoor.h"
# include "dt_send_eng.h"
# ifndef SWDS
# include "vgui_baseui_interface.h"
# endif
# include "sv_remoteaccess.h" // NotifyDedicatedServerUI()
# include "MapReslistGenerator.h"
# include "sv_steamauth.h"
# include "matchmaking.h"
# include "iregistry.h"
# include "sv_main.h"
# include "hltvserver.h"
# include <ctype.h>
# if defined( REPLAY_ENABLED )
# include "replay_internal.h"
# endif
// memdbgon must be the last include file in a .cpp file!!!
# include "tier0/memdbgon.h"
extern IServerGameDLL * serverGameDLL ;
extern ConVar tv_enable ;
ConVar sv_namechange_cooldown_seconds ( " sv_namechange_cooldown_seconds " , " 30.0 " , FCVAR_NONE , " When a client name change is received, wait N seconds before allowing another name change " ) ;
ConVar sv_netspike_on_reliable_snapshot_overflow ( " sv_netspike_on_reliable_snapshot_overflow " , " 0 " , FCVAR_NONE , " If nonzero, the server will dump a netspike trace if a client is dropped due to reliable snapshot overflow " ) ;
ConVar sv_netspike_sendtime_ms ( " sv_netspike_sendtime_ms " , " 0 " , FCVAR_NONE , " If nonzero, the server will dump a netspike trace if it takes more than N ms to prepare a snapshot to a single client. This feature does take some CPU cycles, so it should be left off when not in use. " ) ;
ConVar sv_netspike_output ( " sv_netspike_output " , " 1 " , FCVAR_NONE , " Where the netspike data be written? Sum of the following values: 1=netspike.txt, 2=ordinary server log " ) ;
//////////////////////////////////////////////////////////////////////
// Construction/Destruction
//////////////////////////////////////////////////////////////////////
CBaseClient : : CBaseClient ( )
{
// init all pointers
m_NetChannel = NULL ;
m_ConVars = NULL ;
m_Server = NULL ;
m_pBaseline = NULL ;
m_bIsHLTV = false ;
# if defined( REPLAY_ENABLED )
m_bIsReplay = false ;
# endif
m_bConVarsChanged = false ;
m_bInitialConVarsSet = false ;
m_bSendServerInfo = false ;
m_bFullyAuthenticated = false ;
m_fTimeLastNameChange = 0.0 ;
m_szPendingNameChange [ 0 ] = ' \0 ' ;
m_bReportFakeClient = true ;
m_iTracing = 0 ;
m_bPlayerNameLocked = false ;
}
CBaseClient : : ~ CBaseClient ( )
{
}
void CBaseClient : : SetRate ( int nRate , bool bForce )
{
if ( m_NetChannel )
m_NetChannel - > SetDataRate ( nRate ) ;
}
int CBaseClient : : GetRate ( void ) const
{
if ( m_NetChannel )
{
return m_NetChannel - > GetDataRate ( ) ;
}
else
{
return 0 ;
}
}
bool CBaseClient : : FillUserInfo ( player_info_s & userInfo )
{
Q_memset ( & userInfo , 0 , sizeof ( userInfo ) ) ;
if ( ! m_Name [ 0 ] | | ! IsConnected ( ) )
return false ; // inactive user, no more data available
Q_strncpy ( userInfo . name , GetClientName ( ) , MAX_PLAYER_NAME_LENGTH ) ;
V_strcpy_safe ( userInfo . guid , GetNetworkIDString ( ) ) ;
userInfo . friendsID = m_nFriendsID ;
Q_strncpy ( userInfo . friendsName , m_FriendsName , sizeof ( m_FriendsName ) ) ;
userInfo . userID = GetUserID ( ) ;
userInfo . fakeplayer = IsFakeClient ( ) ;
userInfo . ishltv = IsHLTV ( ) ;
# if defined( REPLAY_ENABLED )
userInfo . isreplay = IsReplay ( ) ;
# endif
for ( int i = 0 ; i < MAX_CUSTOM_FILES ; i + + )
userInfo . customFiles [ i ] = m_nCustomFiles [ i ] . crc ;
userInfo . filesDownloaded = m_nFilesDownloaded ;
return true ;
}
//-----------------------------------------------------------------------------
// Purpose: Send text to client
// Input : *fmt -
// ... -
//-----------------------------------------------------------------------------
void CBaseClient : : ClientPrintf ( const char * fmt , . . . )
{
if ( ! m_NetChannel )
{
return ;
}
va_list argptr ;
char string [ 1024 ] ;
va_start ( argptr , fmt ) ;
Q_vsnprintf ( string , sizeof ( string ) , fmt , argptr ) ;
va_end ( argptr ) ;
SVC_Print print ( string ) ;
m_NetChannel - > SendNetMsg ( print ) ;
}
//-----------------------------------------------------------------------------
// Purpose: Send text to client
// Input : *fmt -
// ... -
//-----------------------------------------------------------------------------
bool CBaseClient : : SendNetMsg ( INetMessage & msg , bool bForceReliable )
{
if ( ! m_NetChannel )
{
return true ;
}
int nStartBit = m_NetChannel - > GetNumBitsWritten ( msg . IsReliable ( ) | | bForceReliable ) ;
bool bret = m_NetChannel - > SendNetMsg ( msg , bForceReliable ) ;
if ( IsTracing ( ) )
{
int nBits = m_NetChannel - > GetNumBitsWritten ( msg . IsReliable ( ) | | bForceReliable ) - nStartBit ;
TraceNetworkMsg ( nBits , " NetMessage %s " , msg . GetName ( ) ) ;
}
return bret ;
}
char const * CBaseClient : : GetUserSetting ( char const * pchCvar ) const
{
if ( ! m_ConVars | | ! pchCvar | | ! pchCvar [ 0 ] )
{
return " " ;
}
const char * value = m_ConVars - > GetString ( pchCvar , " " ) ;
if ( value [ 0 ] = = 0 )
{
// check if this var even existed
if ( m_ConVars - > GetDataType ( pchCvar ) = = KeyValues : : TYPE_NONE )
{
DevMsg ( " GetUserSetting: cvar '%s' unknown. \n " , pchCvar ) ;
}
}
return value ;
}
void CBaseClient : : SetUserCVar ( const char * pchCvar , const char * value )
{
if ( ! pchCvar | | ! value )
return ;
// Name is handled differently
if ( ! Q_stricmp ( pchCvar , " name " ) )
{
//Msg("CBaseClient::SetUserCVar[index=%d]('name', '%s')\n", m_nClientSlot, value );
ClientRequestNameChange ( value ) ;
return ;
}
m_ConVars - > SetString ( pchCvar , value ) ;
}
void CBaseClient : : SetUpdateRate ( int udpaterate , bool bForce )
{
udpaterate = clamp ( udpaterate , 1 , 100 ) ;
m_fSnapshotInterval = 1.0f / udpaterate ;
}
int CBaseClient : : GetUpdateRate ( void ) const
{
if ( m_fSnapshotInterval > 0 )
return ( int ) ( 1.0f / m_fSnapshotInterval ) ;
else
return 0 ;
}
void CBaseClient : : FreeBaselines ( )
{
if ( m_pBaseline )
{
m_pBaseline - > ReleaseReference ( ) ;
m_pBaseline = NULL ;
}
m_nBaselineUpdateTick = - 1 ;
m_nBaselineUsed = 0 ;
m_BaselinesSent . ClearAll ( ) ;
}
void CBaseClient : : Clear ( )
{
// Throw away any residual garbage in the channel.
if ( m_NetChannel )
{
m_NetChannel - > Shutdown ( " Disconnect by server. \n " ) ;
m_NetChannel = NULL ;
}
if ( m_ConVars )
{
m_ConVars - > deleteThis ( ) ;
m_ConVars = NULL ;
}
FreeBaselines ( ) ;
// This used to be a memset, but memset will screw up any embedded classes
// and we want to preserve some things like index.
m_nSignonState = SIGNONSTATE_NONE ;
m_nDeltaTick = - 1 ;
m_nSignonTick = 0 ;
m_nStringTableAckTick = 0 ;
m_pLastSnapshot = NULL ;
m_nForceWaitForTick = - 1 ;
m_bFakePlayer = false ;
m_bIsHLTV = false ;
# if defined( REPLAY_ENABLED )
m_bIsReplay = false ;
# endif
m_fNextMessageTime = 0 ;
m_fSnapshotInterval = 0 ;
m_bReceivedPacket = false ;
m_UserID = 0 ;
m_Name [ 0 ] = 0 ;
m_nFriendsID = 0 ;
m_FriendsName [ 0 ] = 0 ;
m_nSendtableCRC = 0 ;
m_nBaselineUpdateTick = - 1 ;
m_nBaselineUsed = 0 ;
m_nFilesDownloaded = 0 ;
m_bConVarsChanged = false ;
m_bSendServerInfo = false ;
m_bFullyAuthenticated = false ;
m_fTimeLastNameChange = 0.0 ;
m_szPendingNameChange [ 0 ] = ' \0 ' ;
Q_memset ( m_nCustomFiles , 0 , sizeof ( m_nCustomFiles ) ) ;
}
bool CBaseClient : : SetSignonState ( int state , int spawncount )
{
MDLCACHE_COARSE_LOCK_ ( g_pMDLCache ) ;
switch ( m_nSignonState )
{
case SIGNONSTATE_CONNECTED : // client is connected, leave client in this state and let SendPendingSignonData do the rest
m_bSendServerInfo = true ;
break ;
case SIGNONSTATE_NEW : // client got server info, send prespawn datam_Client->SendServerInfo()
if ( ! SendSignonData ( ) )
return false ;
break ;
case SIGNONSTATE_PRESPAWN : SpawnPlayer ( ) ;
break ;
case SIGNONSTATE_SPAWN : ActivatePlayer ( ) ;
break ;
case SIGNONSTATE_FULL : OnSignonStateFull ( ) ;
break ;
case SIGNONSTATE_CHANGELEVEL : break ;
}
return true ;
}
void CBaseClient : : Reconnect ( void )
{
ConMsg ( " Forcing client reconnect (%i) \n " , m_nSignonState ) ;
m_NetChannel - > Clear ( ) ;
m_nSignonState = SIGNONSTATE_CONNECTED ;
NET_SignonState signon ( m_nSignonState , - 1 ) ;
m_NetChannel - > SendNetMsg ( signon ) ;
}
void CBaseClient : : Inactivate ( void )
{
FreeBaselines ( ) ;
m_nDeltaTick = - 1 ;
m_nSignonTick = 0 ;
m_nStringTableAckTick = 0 ;
m_pLastSnapshot = NULL ;
m_nForceWaitForTick = - 1 ;
m_nSignonState = SIGNONSTATE_CHANGELEVEL ;
if ( m_NetChannel )
{
// don't do that for fakeclients
m_NetChannel - > Clear ( ) ;
if ( NET_IsMultiplayer ( ) )
{
NET_SignonState signon ( m_nSignonState , m_Server - > GetSpawnCount ( ) ) ;
SendNetMsg ( signon ) ;
// force sending message now
m_NetChannel - > Transmit ( ) ;
}
}
// don't receive event messages anymore
g_GameEventManager . RemoveListener ( this ) ;
}
//---------------------------------------------------------------------------
// Purpose: Determine whether or not a character should be ignored in a player's name.
//---------------------------------------------------------------------------
inline bool BIgnoreCharInName ( unsigned char cChar , bool bIsFirstCharacter )
{
// Don't copy '%' or '~' chars across
// Don't copy '#' chars across if they would go into the first position in the name
// Don't allow color codes ( less than COLOR_MAX )
return cChar = = ' % ' | | cChar = = ' ~ ' | | cChar < 0x09 | | ( bIsFirstCharacter & & cChar = = ' # ' ) ;
}
void ValidateName ( char * pszName , int nBuffSize )
{
if ( ! pszName )
return ;
// did we get an empty string for the name?
if ( Q_strlen ( pszName ) < = 0 )
{
Q_snprintf ( pszName , nBuffSize , " unnamed " ) ;
}
else
{
Q_RemoveAllEvilCharacters ( pszName ) ;
const unsigned char * pChar = ( unsigned char * ) pszName ;
// also skip characters we're going to ignore
while ( * pChar & & ( isspace ( * pChar ) | | BIgnoreCharInName ( * pChar , true ) ) )
{
+ + pChar ;
}
// did we get all the way to the end of the name without a non-whitespace character?
if ( * pChar = = ' \0 ' )
{
Q_snprintf ( pszName , nBuffSize , " unnamed " ) ;
}
}
}
void CBaseClient : : SetName ( const char * playerName )
{
char name [ MAX_PLAYER_NAME_LENGTH ] ;
Q_strncpy ( name , playerName , sizeof ( name ) ) ;
// Clear any pending name change
m_szPendingNameChange [ 0 ] = ' \0 ' ;
// quick check to make sure the name isn't empty or full of whitespace
ValidateName ( name , sizeof ( name ) ) ;
if ( Q_strncmp ( name , m_Name , sizeof ( m_Name ) ) = = 0 )
return ; // didn't change
int i ;
int dupc = 1 ;
char * p , * val ;
char newname [ MAX_PLAYER_NAME_LENGTH ] ;
// remove evil char '%'
char * pFrom = ( char * ) name ;
char * pTo = m_Name ;
char * pLimit = & m_Name [ sizeof ( m_Name ) - 1 ] ;
while ( * pFrom & & pTo < pLimit )
{
// Don't copy '%' or '~' chars across
// Don't copy '#' chars across if they would go into the first position in the name
// Don't allow color codes ( less than COLOR_MAX )
if ( ! BIgnoreCharInName ( * pFrom , pTo = = & m_Name [ 0 ] ) )
{
* pTo + + = * pFrom ;
}
pFrom + + ;
}
* pTo = 0 ;
Assert ( m_Name [ 0 ] ! = ' \0 ' ) ; // this should've been caught by ValidateName
if ( m_Name [ 0 ] = = ' \0 ' )
{
V_strncpy ( m_Name , " unnamed " , sizeof ( m_Name ) ) ;
}
val = m_Name ;
// Don't care about duplicate names on the xbox. It can only occur when a player
// is reconnecting after crashing, and we don't want to ever show the (X) then.
if ( ! IsX360 ( ) )
{
// Check to see if another user by the same name exists
while ( true )
{
for ( i = 0 ; i < m_Server - > GetClientCount ( ) ; i + + )
{
IClient * client = m_Server - > GetClient ( i ) ;
if ( ! client - > IsConnected ( ) | | client = = this )
continue ;
// If it's 2 bots they're allowed to have matching names, otherwise there's a conflict
if ( ! Q_stricmp ( client - > GetClientName ( ) , val ) & & ! ( IsFakeClient ( ) & & client - > IsFakeClient ( ) ) )
{
CBaseClient * pClient = dynamic_cast < CBaseClient * > ( client ) ;
if ( IsFakeClient ( ) & & pClient )
{
// We're a bot so we get to keep the name... change the other guy
pClient - > m_Name [ 0 ] = ' \0 ' ;
pClient - > SetName ( val ) ;
}
else
{
break ;
}
}
}
if ( i > = m_Server - > GetClientCount ( ) )
break ;
p = val ;
if ( val [ 0 ] = = ' ( ' )
{
if ( val [ 2 ] = = ' ) ' )
{
p = val + 3 ;
}
else if ( val [ 3 ] = = ' ) ' ) //assumes max players is < 100
{
p = val + 4 ;
}
}
Q_snprintf ( newname , sizeof ( newname ) , " (%d)%-.*s " , dupc + + , MAX_PLAYER_NAME_LENGTH - 4 , p ) ;
Q_strncpy ( m_Name , newname , sizeof ( m_Name ) ) ;
val = m_Name ;
}
}
m_ConVars - > SetString ( " name " , m_Name ) ;
m_bConVarsChanged = true ;
m_Server - > UserInfoChanged ( m_nClientSlot ) ;
}
void CBaseClient : : ActivatePlayer ( )
{
COM_TimestampedLog ( " CBaseClient::ActivatePlayer " ) ;
// tell server to update the user info table (if not already done)
m_Server - > UserInfoChanged ( m_nClientSlot ) ;
m_nSignonState = SIGNONSTATE_FULL ;
MapReslistGenerator ( ) . OnPlayerSpawn ( ) ;
# ifndef _XBOX
// update the UI
NotifyDedicatedServerUI ( " UpdatePlayers " ) ;
# endif
}
void CBaseClient : : SpawnPlayer ( void )
{
COM_TimestampedLog ( " CBaseClient::SpawnPlayer " ) ;
if ( ! IsFakeClient ( ) )
{
// free old baseline snapshot
FreeBaselines ( ) ;
// create baseline snapshot for real clients
m_pBaseline = framesnapshotmanager - > CreateEmptySnapshot ( 0 , MAX_EDICTS ) ;
}
// Set client clock to match server's
NET_Tick tick ( m_Server - > GetTick ( ) , host_frametime_unbounded , host_frametime_stddeviation ) ;
SendNetMsg ( tick , true ) ;
// Spawned into server, not fully active, though
m_nSignonState = SIGNONSTATE_SPAWN ;
NET_SignonState signonState ( m_nSignonState , m_Server - > GetSpawnCount ( ) ) ;
SendNetMsg ( signonState ) ;
}
bool CBaseClient : : SendSignonData ( void )
{
COM_TimestampedLog ( " CBaseClient::SendSignonData " ) ;
# ifndef SWDS
EngineVGui ( ) - > UpdateProgressBar ( PROGRESS_SENDSIGNONDATA ) ;
# endif
if ( m_Server - > m_Signon . IsOverflowed ( ) )
{
Host_Error ( " Signon buffer overflowed %i bytes!!! \n " , m_Server - > m_Signon . GetNumBytesWritten ( ) ) ;
return false ;
}
m_NetChannel - > SendData ( m_Server - > m_Signon ) ;
m_nSignonState = SIGNONSTATE_PRESPAWN ;
NET_SignonState signonState ( m_nSignonState , m_Server - > GetSpawnCount ( ) ) ;
return m_NetChannel - > SendNetMsg ( signonState ) ;
}
void CBaseClient : : Connect ( const char * szName , int nUserID , INetChannel * pNetChannel , bool bFakePlayer , int clientChallenge )
{
COM_TimestampedLog ( " CBaseClient::Connect " ) ;
# ifndef SWDS
EngineVGui ( ) - > UpdateProgressBar ( PROGRESS_SIGNONCONNECT ) ;
# endif
Clear ( ) ;
m_ConVars = new KeyValues ( " userinfo " ) ;
m_bInitialConVarsSet = false ;
m_UserID = nUserID ;
SetName ( szName ) ;
m_fTimeLastNameChange = 0.0 ;
m_bFakePlayer = bFakePlayer ;
m_NetChannel = pNetChannel ;
if ( m_NetChannel & & m_Server & & m_Server - > IsMultiplayer ( ) )
{
m_NetChannel - > SetCompressionMode ( true ) ;
}
m_clientChallenge = clientChallenge ;
m_nSignonState = SIGNONSTATE_CONNECTED ;
if ( bFakePlayer )
{
// Hidden fake players and the HLTV/Replay bot will get removed by CSteam3Server::SendUpdatedServerDetails.
Steam3Server ( ) . NotifyLocalClientConnect ( this ) ;
}
}
//-----------------------------------------------------------------------------
// Purpose: Drops client from server, with explanation
// Input : *cl -
// crash -
// *fmt -
// ... -
//-----------------------------------------------------------------------------
void CBaseClient : : Disconnect ( const char * fmt , . . . )
{
va_list argptr ;
char string [ 1024 ] ;
if ( m_nSignonState = = SIGNONSTATE_NONE )
return ; // no recursion
# if !defined( SWDS ) && defined( ENABLE_RPT )
SV_NotifyRPTOfDisconnect ( m_nClientSlot ) ;
# endif
# ifndef _XBOX
Steam3Server ( ) . NotifyClientDisconnect ( this ) ;
# endif
m_nSignonState = SIGNONSTATE_NONE ;
// clear user info
m_Server - > UserInfoChanged ( m_nClientSlot ) ;
va_start ( argptr , fmt ) ;
Q_vsnprintf ( string , sizeof ( string ) , fmt , argptr ) ;
va_end ( argptr ) ;
ConMsg ( " Dropped %s from server (%s) \n " , GetClientName ( ) , string ) ;
// remove the client as listener
g_GameEventManager . RemoveListener ( this ) ;
// Send the remaining reliable buffer so the client finds out the server is shutting down.
if ( m_NetChannel )
{
m_NetChannel - > Shutdown ( string ) ;
m_NetChannel = NULL ;
}
Clear ( ) ; // clear state
# ifndef _XBOX
NotifyDedicatedServerUI ( " UpdatePlayers " ) ;
# endif
Steam3Server ( ) . SendUpdatedServerDetails ( ) ; // Update the master server.
}
void CBaseClient : : FireGameEvent ( IGameEvent * event )
{
tmZoneFiltered ( TELEMETRY_LEVEL0 , 50 , TMZF_NONE , " %s " , __FUNCTION__ ) ;
char buffer_data [ MAX_EVENT_BYTES ] ;
SVC_GameEvent eventMsg ;
eventMsg . m_DataOut . StartWriting ( buffer_data , sizeof ( buffer_data ) ) ;
// create bitstream from KeyValues
if ( g_GameEventManager . SerializeEvent ( event , & eventMsg . m_DataOut ) )
{
if ( m_NetChannel )
{
bool bSent = m_NetChannel - > SendNetMsg ( eventMsg ) ;
if ( ! bSent )
DevMsg ( " GameEventManager: failed to send event '%s'. \n " , event - > GetName ( ) ) ;
}
}
else
{
DevMsg ( " GameEventManager: failed to serialize event '%s'. \n " , event - > GetName ( ) ) ;
}
}
bool CBaseClient : : SendServerInfo ( void )
{
COM_TimestampedLog ( " CBaseClient::SendServerInfo " ) ;
// supporting smaller stack
byte * buffer = ( byte * ) MemAllocScratch ( NET_MAX_PAYLOAD ) ;
bf_write msg ( " SV_SendServerinfo->msg " , buffer , NET_MAX_PAYLOAD ) ;
// Only send this message to developer console, or multiplayer clients.
if ( developer . GetBool ( ) | | m_Server - > IsMultiplayer ( ) )
{
char devtext [ 2048 ] ;
int curplayers = m_Server - > GetNumClients ( ) ;
Q_snprintf ( devtext , sizeof ( devtext ) ,
" \n %s \n Map: %s \n Players: %i / %i \n Build: %d \n Server Number: %i \n \n " ,
serverGameDLL - > GetGameDescription ( ) ,
m_Server - > GetMapName ( ) ,
curplayers , m_Server - > GetMaxClients ( ) ,
build_number ( ) ,
m_Server - > GetSpawnCount ( ) ) ;
SVC_Print printMsg ( devtext ) ;
printMsg . WriteToBuffer ( msg ) ;
}
SVC_ServerInfo serverinfo ; // create serverinfo message
serverinfo . m_nPlayerSlot = m_nClientSlot ; // own slot number
m_Server - > FillServerInfo ( serverinfo ) ; // fill rest of info message
serverinfo . WriteToBuffer ( msg ) ;
if ( IsX360 ( ) & & serverinfo . m_nMaxClients > 1 )
{
Msg ( " Telling clients to connect " ) ;
g_pMatchmaking - > TellClientsToConnect ( ) ;
}
// send first tick
m_nSignonTick = m_Server - > m_nTickCount ;
NET_Tick signonTick ( m_nSignonTick , 0 , 0 ) ;
signonTick . WriteToBuffer ( msg ) ;
// write stringtable baselines
# ifndef SHARED_NET_STRING_TABLES
m_Server - > m_StringTables - > WriteBaselines ( msg ) ;
# endif
// Write replicated ConVars to non-listen server clients only
if ( ! m_NetChannel - > IsLoopback ( ) )
{
NET_SetConVar convars ;
Host_BuildConVarUpdateMessage ( & convars , FCVAR_REPLICATED , true ) ;
convars . WriteToBuffer ( msg ) ;
}
m_bSendServerInfo = false ;
// send signon state
m_nSignonState = SIGNONSTATE_NEW ;
NET_SignonState signonMsg ( m_nSignonState , m_Server - > GetSpawnCount ( ) ) ;
signonMsg . WriteToBuffer ( msg ) ;
// send server info as one data block
if ( ! m_NetChannel - > SendData ( msg ) )
{
MemFreeScratch ( ) ;
Disconnect ( " Server info data overflow " ) ;
return false ;
}
COM_TimestampedLog ( " CBaseClient::SendServerInfo(finished) " ) ;
MemFreeScratch ( ) ;
return true ;
}
CClientFrame * CBaseClient : : GetDeltaFrame ( int nTick )
{
Assert ( 0 ) ; // derive moe
return NULL ; // CBaseClient has no delta frames
}
void CBaseClient : : WriteGameSounds ( bf_write & buf )
{
// CBaseClient has no events
}
void CBaseClient : : ConnectionStart ( INetChannel * chan )
{
REGISTER_NET_MSG ( Tick ) ;
REGISTER_NET_MSG ( StringCmd ) ;
REGISTER_NET_MSG ( SetConVar ) ;
REGISTER_NET_MSG ( SignonState ) ;
REGISTER_CLC_MSG ( ClientInfo ) ;
REGISTER_CLC_MSG ( Move ) ;
REGISTER_CLC_MSG ( VoiceData ) ;
REGISTER_CLC_MSG ( BaselineAck ) ;
REGISTER_CLC_MSG ( ListenEvents ) ;
REGISTER_CLC_MSG ( RespondCvarValue ) ;
REGISTER_CLC_MSG ( FileCRCCheck ) ;
REGISTER_CLC_MSG ( FileMD5Check ) ;
# if defined( REPLAY_ENABLED )
REGISTER_CLC_MSG ( SaveReplay ) ;
# endif
REGISTER_CLC_MSG ( CmdKeyValues ) ;
}
bool CBaseClient : : ProcessTick ( NET_Tick * msg )
{
m_NetChannel - > SetRemoteFramerate ( msg - > m_flHostFrameTime , msg - > m_flHostFrameTimeStdDeviation ) ;
return UpdateAcknowledgedFramecount ( msg - > m_nTick ) ;
}
bool CBaseClient : : ProcessStringCmd ( NET_StringCmd * msg )
{
ExecuteStringCommand ( msg - > m_szCommand ) ;
return true ;
}
bool CBaseClient : : ProcessSetConVar ( NET_SetConVar * msg )
{
for ( int i = 0 ; i < msg - > m_ConVars . Count ( ) ; i + + )
{
const char * name = msg - > m_ConVars [ i ] . name ;
const char * value = msg - > m_ConVars [ i ] . value ;
// Discard any convar change request if contains funky characters
bool bFunky = false ;
for ( const char * s = name ; * s ! = ' \0 ' ; + + s )
{
if ( ! V_isalnum ( * s ) & & * s ! = ' _ ' )
{
bFunky = true ;
break ;
}
}
if ( bFunky )
{
Msg ( " Ignoring convar change request for variable '%s' from client %s; invalid characters in the variable name \n " , name , GetClientName ( ) ) ;
continue ;
}
// "name" convar is handled differently
if ( V_stricmp ( name , " name " ) = = 0 )
{
ClientRequestNameChange ( value ) ;
continue ;
}
// The initial set of convars must contain all client convars that are flagged userinfo. This is a simple fix to
// exploits that send bogus data later, and catches bugs (why are new userinfo convars appearing later?)
if ( m_bInitialConVarsSet & & ! m_ConVars - > FindKey ( name ) )
{
# ifndef _DEBUG // warn all the time in debug build
static double s_dblLastWarned = 0.0 ;
double dblTimeNow = Plat_FloatTime ( ) ;
if ( dblTimeNow - s_dblLastWarned > 10 )
# endif
{
# ifndef _DEBUG
s_dblLastWarned = dblTimeNow ;
# endif
Warning ( " Client \" %s \" userinfo ignored: \" %s \" = \" %s \" \n " ,
this - > GetClientName ( ) , name , value ) ;
}
continue ;
}
m_ConVars - > SetString ( name , value ) ;
// DevMsg( 1, " UserInfo update %s: %s = %s\n", m_Client->m_Name, name, value );
}
m_bConVarsChanged = true ;
m_bInitialConVarsSet = true ;
return true ;
}
bool CBaseClient : : ProcessSignonState ( NET_SignonState * msg )
{
if ( msg - > m_nSignonState = = SIGNONSTATE_CHANGELEVEL )
{
return true ; // ignore this message
}
if ( msg - > m_nSignonState > SIGNONSTATE_CONNECTED )
{
if ( msg - > m_nSpawnCount ! = m_Server - > GetSpawnCount ( ) )
{
Reconnect ( ) ;
return true ;
}
}
// client must acknowledge our current state, otherwise start again
if ( msg - > m_nSignonState ! = m_nSignonState )
{
Reconnect ( ) ;
return true ;
}
return SetSignonState ( msg - > m_nSignonState , msg - > m_nSpawnCount ) ;
}
bool CBaseClient : : ProcessClientInfo ( CLC_ClientInfo * msg )
{
if ( m_nSignonState ! = SIGNONSTATE_NEW )
{
Warning ( " Dropping ClientInfo packet from client not in appropriate state \n " ) ;
return false ;
}
m_nSendtableCRC = msg - > m_nSendTableCRC ;
// Protect against spoofed packets claiming to be HLTV clients
if ( ( hltv & & hltv - > IsTVRelay ( ) ) | | tv_enable . GetBool ( ) )
{
m_bIsHLTV = msg - > m_bIsHLTV ;
}
else
{
m_bIsHLTV = false ;
}
# if defined( REPLAY_ENABLED )
m_bIsReplay = msg - > m_bIsReplay ;
# endif
m_nFilesDownloaded = 0 ;
m_nFriendsID = msg - > m_nFriendsID ;
Q_strncpy ( m_FriendsName , msg - > m_FriendsName , sizeof ( m_FriendsName ) ) ;
for ( int i = 0 ; i < MAX_CUSTOM_FILES ; i + + )
{
m_nCustomFiles [ i ] . crc = msg - > m_nCustomFiles [ i ] ;
m_nCustomFiles [ i ] . reqID = 0 ;
}
if ( msg - > m_nServerCount ! = m_Server - > GetSpawnCount ( ) )
{
Reconnect ( ) ; // client still in old game, reconnect
}
return true ;
}
bool CBaseClient : : ProcessBaselineAck ( CLC_BaselineAck * msg )
{
if ( msg - > m_nBaselineTick ! = m_nBaselineUpdateTick )
{
// This occurs when there are multiple ack's queued up for processing from a client.
return true ;
}
if ( msg - > m_nBaselineNr ! = m_nBaselineUsed )
{
DevMsg ( " CBaseClient::ProcessBaselineAck: wrong baseline nr received (%i) \n " , msg - > m_nBaselineTick ) ;
return true ;
}
Assert ( m_pBaseline ) ;
// copy ents send as full updates this frame into baseline stuff
CClientFrame * frame = GetDeltaFrame ( m_nBaselineUpdateTick ) ;
if ( frame = = NULL )
{
// Will get here if we have a lot of packet loss and finally receive a stale ack from
// remote client. Our "window" could be well beyond what it's acking, so just ignore the ack.
return true ;
}
CFrameSnapshot * pSnapshot = frame - > GetSnapshot ( ) ;
if ( pSnapshot = = NULL )
{
// TODO if client lags for a couple of seconds the snapshot is lost
// fix: don't remove snapshots that are labled a possible basline candidates
// or: send full update
DevMsg ( " CBaseClient::ProcessBaselineAck: invalid frame snapshot (%i) \n " , m_nBaselineUpdateTick ) ;
return false ;
}
int index = m_BaselinesSent . FindNextSetBit ( 0 ) ;
while ( index > = 0 )
{
// get new entity
PackedEntityHandle_t hNewEntity = pSnapshot - > m_pEntities [ index ] . m_pPackedData ;
if ( hNewEntity = = INVALID_PACKED_ENTITY_HANDLE )
{
DevMsg ( " CBaseClient::ProcessBaselineAck: invalid packet handle (%i) \n " , index ) ;
return false ;
}
PackedEntityHandle_t hOldEntity = m_pBaseline - > m_pEntities [ index ] . m_pPackedData ;
if ( hOldEntity ! = INVALID_PACKED_ENTITY_HANDLE )
{
// remove reference before overwriting packed entity
framesnapshotmanager - > RemoveEntityReference ( hOldEntity ) ;
}
// increase reference
framesnapshotmanager - > AddEntityReference ( hNewEntity ) ;
// copy entity handle, class & serial number to
m_pBaseline - > m_pEntities [ index ] = pSnapshot - > m_pEntities [ index ] ;
// go to next entity
index = m_BaselinesSent . FindNextSetBit ( index + 1 ) ;
}
m_pBaseline - > m_nTickCount = m_nBaselineUpdateTick ;
// flip used baseline flag
m_nBaselineUsed = ( m_nBaselineUsed = = 1 ) ? 0 : 1 ;
m_nBaselineUpdateTick = - 1 ; // ready to update baselines again
return true ;
}
bool CBaseClient : : ProcessListenEvents ( CLC_ListenEvents * msg )
{
// first remove the client as listener
g_GameEventManager . RemoveListener ( this ) ;
for ( int i = 0 ; i < MAX_EVENT_NUMBER ; i + + )
{
if ( msg - > m_EventArray . Get ( i ) )
{
CGameEventDescriptor * descriptor = g_GameEventManager . GetEventDescriptor ( i ) ;
if ( descriptor )
{
g_GameEventManager . AddListener ( this , descriptor , CGameEventManager : : CLIENTSTUB ) ;
}
else
{
DevMsg ( " ProcessListenEvents: game event %i not found. \n " , i ) ;
return false ;
}
}
}
return true ;
}
extern int GetNetSpikeValue ( ) ;
void CBaseClient : : StartTrace ( bf_write & msg )
{
// Should we be tracing?
m_Trace . m_nMinWarningBytes = 0 ;
if ( ! IsHLTV ( ) & & ! IsReplay ( ) & & ! IsFakeClient ( ) )
m_Trace . m_nMinWarningBytes = GetNetSpikeValue ( ) ;
if ( m_iTracing < 2 )
{
if ( m_Trace . m_nMinWarningBytes < = 0 & & sv_netspike_sendtime_ms . GetFloat ( ) < = 0.0f )
{
m_iTracing = 0 ;
return ;
}
m_iTracing = 1 ;
}
m_Trace . m_nStartBit = msg . GetNumBitsWritten ( ) ;
m_Trace . m_nCurBit = m_Trace . m_nStartBit ;
m_Trace . m_StartSendTime = Plat_FloatTime ( ) ;
}
# define SERVER_PACKETS_LOG "netspike.txt"
void CBaseClient : : EndTrace ( bf_write & msg )
{
if ( m_iTracing = = 0 )
return ;
VPROF_BUDGET ( " CBaseClient::EndTrace " , VPROF_BUDGETGROUP_OTHER_NETWORKING ) ;
int bits = m_Trace . m_nCurBit - m_Trace . m_nStartBit ;
float flElapsedMs = ( Plat_FloatTime ( ) - m_Trace . m_StartSendTime ) * 1000.0 ;
int nBitThreshold = m_Trace . m_nMinWarningBytes < < 3 ;
if ( m_iTracing < 2 // not forced
& & ( nBitThreshold < = 0 | | bits < nBitThreshold ) // didn't exceed data threshold
& & ( sv_netspike_sendtime_ms . GetFloat ( ) < = 0.0f | | flElapsedMs < sv_netspike_sendtime_ms . GetFloat ( ) ) ) // didn't exceed time threshold
{
m_Trace . m_Records . RemoveAll ( ) ;
m_iTracing = 0 ;
return ;
}
CUtlBuffer logData ( 0 , 0 , CUtlBuffer : : TEXT_BUFFER ) ;
logData . Printf ( " %f/%d Player [%s][%d][adr:%s] was sent a datagram %d bits (%8.3f bytes), took %.2fms \n " ,
realtime ,
host_tickcount ,
GetClientName ( ) ,
GetPlayerSlot ( ) ,
GetNetChannel ( ) - > GetAddress ( ) ,
bits , ( float ) bits / 8.0f ,
flElapsedMs
) ;
// Write header line to the log if we aren't writing the whole thing
if ( ( sv_netspike_output . GetInt ( ) & 2 ) = = 0 )
Log ( " netspike: %s " , logData . String ( ) ) ;
for ( int i = 0 ; i < m_Trace . m_Records . Count ( ) ; + + i )
{
Spike_t & sp = m_Trace . m_Records [ i ] ;
logData . Printf ( " %64.64s : %8d bits (%8.3f bytes) \n " , sp . m_szDesc , sp . m_nBits , ( float ) sp . m_nBits / 8.0f ) ;
}
if ( sv_netspike_output . GetInt ( ) & 1 )
COM_LogString ( SERVER_PACKETS_LOG , ( const char * ) logData . String ( ) ) ;
if ( sv_netspike_output . GetInt ( ) & 2 )
Log ( " %s " , ( const char * ) logData . String ( ) ) ;
ETWMark1S ( " netspike " , ( const char * ) logData . String ( ) ) ;
m_Trace . m_Records . RemoveAll ( ) ;
m_iTracing = 0 ;
}
void CBaseClient : : TraceNetworkData ( bf_write & msg , char const * fmt , . . . )
{
if ( ! IsTracing ( ) )
return ;
VPROF_BUDGET ( " CBaseClient::TraceNetworkData " , VPROF_BUDGETGROUP_OTHER_NETWORKING ) ;
char buf [ 64 ] ;
va_list argptr ;
va_start ( argptr , fmt ) ;
Q_vsnprintf ( buf , sizeof ( buf ) , fmt , argptr ) ;
va_end ( argptr ) ;
Spike_t t ;
Q_strncpy ( t . m_szDesc , buf , sizeof ( t . m_szDesc ) ) ;
t . m_nBits = msg . GetNumBitsWritten ( ) - m_Trace . m_nCurBit ;
m_Trace . m_Records . AddToTail ( t ) ;
m_Trace . m_nCurBit = msg . GetNumBitsWritten ( ) ;
}
void CBaseClient : : TraceNetworkMsg ( int nBits , char const * fmt , . . . )
{
if ( ! IsTracing ( ) )
return ;
VPROF_BUDGET ( " CBaseClient::TraceNetworkMsg " , VPROF_BUDGETGROUP_OTHER_NETWORKING ) ;
char buf [ 64 ] ;
va_list argptr ;
va_start ( argptr , fmt ) ;
Q_vsnprintf ( buf , sizeof ( buf ) , fmt , argptr ) ;
va_end ( argptr ) ;
Spike_t t ;
Q_strncpy ( t . m_szDesc , buf , sizeof ( t . m_szDesc ) ) ;
t . m_nBits = nBits ;
m_Trace . m_Records . AddToTail ( t ) ;
}
void CBaseClient : : SendSnapshot ( CClientFrame * pFrame )
{
// never send the same snapshot twice
if ( m_pLastSnapshot = = pFrame - > GetSnapshot ( ) )
{
m_NetChannel - > Transmit ( ) ;
return ;
}
// 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_nForceWaitForTick > 0 )
{
// just continue transmitting reliable data
m_NetChannel - > Transmit ( ) ;
return ;
}
VPROF_BUDGET ( " SendSnapshot " , VPROF_BUDGETGROUP_OTHER_NETWORKING ) ;
tmZoneFiltered ( TELEMETRY_LEVEL0 , 50 , TMZF_NONE , " %s " , __FUNCTION__ ) ;
bool bFailedOnce = false ;
write_again :
bf_write msg ( " CBaseClient::SendSnapshot " , m_SnapshotScratchBuffer , sizeof ( m_SnapshotScratchBuffer ) ) ;
TRACE_PACKET ( ( " SendSnapshot(%d) \n " , pFrame - > tick_count ) ) ;
// now create client snapshot packet
CClientFrame * deltaFrame = GetDeltaFrame ( m_nDeltaTick ) ; // NULL if delta_tick is not found
if ( ! deltaFrame )
{
// We need to send a full update and reset the instanced baselines
OnRequestFullUpdate ( ) ;
}
// send tick time
NET_Tick tickmsg ( pFrame - > tick_count , host_frametime_unbounded , host_frametime_stddeviation ) ;
StartTrace ( msg ) ;
tickmsg . WriteToBuffer ( msg ) ;
if ( IsTracing ( ) )
{
TraceNetworkData ( msg , " NET_Tick " ) ;
}
# ifndef SHARED_NET_STRING_TABLES
// in LocalNetworkBackdoor mode we updated the stringtables already in SV_ComputeClientPacks()
if ( ! g_pLocalNetworkBackdoor )
{
// Update shared client/server string tables. Must be done before sending entities
m_Server - > m_StringTables - > WriteUpdateMessage ( this , GetMaxAckTickCount ( ) , msg ) ;
}
# endif
int nDeltaStartBit = 0 ;
if ( IsTracing ( ) )
{
nDeltaStartBit = msg . GetNumBitsWritten ( ) ;
}
// send entity update, delta compressed if deltaFrame != NULL
m_Server - > WriteDeltaEntities ( this , pFrame , deltaFrame , msg ) ;
if ( IsTracing ( ) )
{
int nBits = msg . GetNumBitsWritten ( ) - nDeltaStartBit ;
TraceNetworkMsg ( nBits , " Total Delta " ) ;
}
// send all unreliable temp entities between last and current frame
// send max 64 events in multi player, 255 in SP
int nMaxTempEnts = m_Server - > IsMultiplayer ( ) ? 64 : 255 ;
m_Server - > WriteTempEntities ( this , pFrame - > GetSnapshot ( ) , m_pLastSnapshot . GetObject ( ) , msg , nMaxTempEnts ) ;
if ( IsTracing ( ) )
{
TraceNetworkData ( msg , " Temp Entities " ) ;
}
WriteGameSounds ( msg ) ;
// write message to packet and check for overflow
if ( msg . IsOverflowed ( ) )
{
bool bWasTracing = IsTracing ( ) ;
if ( bWasTracing )
{
TraceNetworkMsg ( 0 , " Finished [delta %s] " , deltaFrame ? " yes " : " no " ) ;
EndTrace ( msg ) ;
}
if ( ! deltaFrame )
{
if ( ! bWasTracing )
{
// Check for debugging by dumping a snapshot
if ( sv_netspike_on_reliable_snapshot_overflow . GetBool ( ) )
{
if ( ! bFailedOnce ) // shouldn't be necessary, but just in case
{
Warning ( " RELIABLE SNAPSHOT OVERFLOW! Triggering trace to see what is so large \n " ) ;
bFailedOnce = true ;
m_iTracing = 2 ;
goto write_again ;
}
m_iTracing = 0 ;
}
}
// if this is a reliable snapshot, drop the client
Disconnect ( " ERROR! Reliable snapshot overflow. " ) ;
return ;
}
else
{
// unreliable snapshots may be dropped
ConMsg ( " WARNING: msg overflowed for %s \n " , m_Name ) ;
msg . Reset ( ) ;
}
}
// remember this snapshot
m_pLastSnapshot = pFrame - > GetSnapshot ( ) ;
// Don't send the datagram to fakeplayers unless sv_stressbots is on (which will make m_NetChannel non-null).
if ( m_bFakePlayer & & ! m_NetChannel )
{
m_nDeltaTick = pFrame - > tick_count ;
m_nStringTableAckTick = m_nDeltaTick ;
return ;
}
bool bSendOK ;
// is this is a full entity update (no delta) ?
if ( ! deltaFrame )
{
VPROF_BUDGET ( " SendSnapshot Transmit Full " , VPROF_BUDGETGROUP_OTHER_NETWORKING ) ;
// 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
{
VPROF_BUDGET ( " SendSnapshot Transmit Delta " , VPROF_BUDGETGROUP_OTHER_NETWORKING ) ;
// just send it as unreliable snapshot
bSendOK = m_NetChannel - > SendDatagram ( & msg ) > 0 ;
}
if ( bSendOK )
{
if ( IsTracing ( ) )
{
TraceNetworkMsg ( 0 , " Finished [delta %s] " , deltaFrame ? " yes " : " no " ) ;
EndTrace ( msg ) ;
}
}
else
{
Disconnect ( " ERROR! Couldn't send snapshot. " ) ;
}
}
bool CBaseClient : : ExecuteStringCommand ( const char * pCommand )
{
if ( ! pCommand | | ! pCommand [ 0 ] )
return false ;
if ( ! Q_stricmp ( pCommand , " demorestart " ) )
{
DemoRestart ( ) ;
// trick, dont return true, so serverGameClients gets this command too
return false ;
}
return false ;
}
void CBaseClient : : DemoRestart ( )
{
}
bool CBaseClient : : ShouldSendMessages ( void )
{
if ( ! IsConnected ( ) )
return false ;
// if the reliable message overflowed, drop the client
if ( m_NetChannel & & m_NetChannel - > IsOverflowed ( ) )
{
m_NetChannel - > Reset ( ) ;
Disconnect ( " %s overflowed reliable buffer \n " , m_Name ) ;
return false ;
}
// check, if it's time to send the next packet
bool bSendMessage = m_fNextMessageTime < = net_time ;
if ( ! bSendMessage & & ! IsActive ( ) )
{
// if we are in signon modem instantly reply if
// we got a answer and have reliable data waiting
if ( m_bReceivedPacket & & m_NetChannel & & m_NetChannel - > HasPendingReliableData ( ) )
{
bSendMessage = true ;
}
}
if ( bSendMessage & & m_NetChannel & & ! m_NetChannel - > CanPacket ( ) )
{
// we would like to send a message, but bandwidth isn't available yet
// tell netchannel that we are choking a packet
m_NetChannel - > SetChoked ( ) ;
// Record an ETW event to indicate that we are throttling.
ETWThrottled ( ) ;
bSendMessage = false ;
}
return bSendMessage ;
}
void CBaseClient : : UpdateSendState ( void )
{
// wait for next incoming packet
m_bReceivedPacket = false ;
// in single player mode always send messages
if ( ! m_Server - > IsMultiplayer ( ) & & ! host_limitlocal . GetFloat ( ) )
{
m_fNextMessageTime = net_time ; // send ASAP and
m_bReceivedPacket = true ; // don't wait for incoming packets
}
else if ( IsActive ( ) ) // multiplayer mode
{
// snapshot mode: send snapshots frequently
float maxDelta = min ( m_Server - > GetTickInterval ( ) , m_fSnapshotInterval ) ;
float delta = clamp ( ( float ) ( net_time - m_fNextMessageTime ) , 0.0f , maxDelta ) ;
m_fNextMessageTime = net_time + m_fSnapshotInterval - delta ;
}
else // multiplayer signon mode
{
if ( m_NetChannel & & m_NetChannel - > HasPendingReliableData ( ) & &
m_NetChannel - > GetTimeSinceLastReceived ( ) < 1.0f )
{
// if we have pending reliable data send as fast as possible
m_fNextMessageTime = net_time ;
}
else
{
// signon mode: only respond on request or after 1 second
m_fNextMessageTime = net_time + 1.0f ;
}
}
}
void CBaseClient : : UpdateUserSettings ( )
{
int rate = m_ConVars - > GetInt ( " rate " , DEFAULT_RATE ) ;
if ( sv . IsActive ( ) )
{
// If we're running a local listen server then set the rate very high
// in order to avoid delays due to network throttling. This allows for
// easier profiling of other issues (it removes most of the frame-render
// time which can otherwise dominate profiles) and saves developer time
// by making maps and models load much faster.
if ( rate = = DEFAULT_RATE )
{
// Only override the rate if the user hasn't customized it.
// The max rate should be a million or so in order to truly
// eliminate networking delays.
rate = MAX_RATE ;
}
}
// set server to client network rate
SetRate ( rate , false ) ;
// set server to client update rate
SetUpdateRate ( m_ConVars - > GetInt ( " cl_updaterate " , 20 ) , false ) ;
SetMaxRoutablePayloadSize ( m_ConVars - > GetInt ( " net_maxroutable " , MAX_ROUTABLE_PAYLOAD ) ) ;
m_Server - > UserInfoChanged ( m_nClientSlot ) ;
m_bConVarsChanged = false ;
}
void CBaseClient : : ClientRequestNameChange ( const char * pszNewName )
{
// This is called several times. Only show a status message the first time.
bool bShowStatusMessage = ( m_szPendingNameChange [ 0 ] = = ' \0 ' ) ;
V_strcpy_safe ( m_szPendingNameChange , pszNewName ) ;
CheckFlushNameChange ( bShowStatusMessage ) ;
}
void CBaseClient : : CheckFlushNameChange ( bool bShowStatusMessage /*= false*/ )
{
if ( ! IsConnected ( ) )
return ;
if ( m_szPendingNameChange [ 0 ] = = ' \0 ' )
return ;
if ( m_bPlayerNameLocked )
return ;
// Did they change it back to the original?
if ( ! Q_strcmp ( m_szPendingNameChange , m_Name ) )
{
// Nothing really pending, they already changed it back
// we had a chance to apply the other one!
m_szPendingNameChange [ 0 ] = ' \0 ' ;
return ;
}
// Check for throttling name changes
// Don't do it on bots
if ( ! IsFakeClient ( ) & & IsNameChangeOnCooldown ( bShowStatusMessage ) )
{
return ;
}
// Set the new name
m_fTimeLastNameChange = Plat_FloatTime ( ) ;
SetName ( m_szPendingNameChange ) ;
}
bool CBaseClient : : IsNameChangeOnCooldown ( bool bShowStatusMessage /*= false*/ )
{
// Check cooldown. The first name change is free
if ( m_fTimeLastNameChange > 0.0 )
{
// Too recent?
double timeNow = Plat_FloatTime ( ) ;
double dNextChangeTime = m_fTimeLastNameChange + sv_namechange_cooldown_seconds . GetFloat ( ) ;
if ( timeNow < dNextChangeTime )
{
// Cooldown period still active; throttle the name change
if ( bShowStatusMessage )
{
ClientPrintf ( " You have changed your name recently, and must wait %i seconds. \n " , ( int ) abs ( timeNow - dNextChangeTime ) ) ;
}
return true ;
}
}
return false ;
}
void CBaseClient : : OnRequestFullUpdate ( )
{
VPROF_BUDGET ( " CBaseClient::OnRequestFullUpdate " , VPROF_BUDGETGROUP_OTHER_NETWORKING ) ;
// client requests a full update
m_pLastSnapshot = NULL ;
// free old baseline snapshot
FreeBaselines ( ) ;
// and create new baseline snapshot
m_pBaseline = framesnapshotmanager - > CreateEmptySnapshot ( 0 , MAX_EDICTS ) ;
DevMsg ( " Sending full update to Client %s \n " , GetClientName ( ) ) ;
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : *cl -
//-----------------------------------------------------------------------------
bool CBaseClient : : UpdateAcknowledgedFramecount ( int tick )
{
if ( IsFakeClient ( ) )
{
// fake clients are always fine
m_nDeltaTick = tick ;
m_nStringTableAckTick = tick ;
return true ;
}
// are we waiting for full reliable update acknowledge
if ( m_nForceWaitForTick > 0 )
{
if ( tick > m_nForceWaitForTick )
{
// we should never get here since full updates are transmitted as reliable data now
// Disconnect("Acknowledging reliable snapshot failed.\n");
return true ;
}
else if ( tick = = - 1 )
{
if ( ! m_NetChannel - > HasPendingReliableData ( ) )
{
// that's strange: we sent the client a full update, and it was fully received ( no reliable data in waiting buffers )
// but the client is requesting another full update.
//
// This can happen if they request full updates in succession really quickly (using cl_fullupdate or "record X;stop" quickly).
// There was a bug here where if we just return out, the client will have nuked its entities and we'd send it
// a supposedly uncompressed update but m_nDeltaTick was not -1, so it was delta'd and it'd miss lots of stuff.
// Led to clients getting full spectator mode radar while their player was not a spectator.
ConDMsg ( " Client forced immediate full update. \n " ) ;
m_nForceWaitForTick = m_nDeltaTick = - 1 ;
OnRequestFullUpdate ( ) ;
return true ;
}
}
else if ( tick < m_nForceWaitForTick )
{
// keep on waiting, do nothing
return true ;
}
else // ( tick == m_nForceWaitForTick )
{
// great, the client acknowledge the tick we send the full update
m_nForceWaitForTick = - 1 ;
// continue sending snapshots...
}
}
else
{
if ( m_nDeltaTick = = - 1 )
{
// we still want to send a full update, don't change delta_tick from -1
return true ;
}
if ( tick = = - 1 )
{
OnRequestFullUpdate ( ) ;
}
else
{
if ( m_nDeltaTick > tick )
{
// client already acknowledged new tick and now switch back to older
// thats not allowed since we always delete older frames
Disconnect ( " Client delta ticks out of order. \n " ) ;
return false ;
}
}
}
// get acknowledged client frame
m_nDeltaTick = tick ;
if ( m_nDeltaTick > - 1 )
{
m_nStringTableAckTick = m_nDeltaTick ;
}
if ( ( m_nBaselineUpdateTick > - 1 ) & & ( m_nDeltaTick > m_nBaselineUpdateTick ) )
{
// server sent a baseline update, but it wasn't acknowledged yet so it was probably lost.
m_nBaselineUpdateTick = - 1 ;
}
return true ;
}
//-----------------------------------------------------------------------------
// Purpose: return a string version of the userid
//-----------------------------------------------------------------------------
const char * GetUserIDString ( const USERID_t & id )
{
static char idstr [ MAX_NETWORKID_LENGTH ] ;
idstr [ 0 ] = 0 ;
switch ( id . idtype )
{
case IDTYPE_STEAM :
{
CSteamID nullID ;
if ( Steam3Server ( ) . BLanOnly ( ) & & nullID = = id . steamid )
{
V_strcpy_safe ( idstr , " STEAM_ID_LAN " ) ;
}
else if ( nullID = = id . steamid )
{
V_strcpy_safe ( idstr , " STEAM_ID_PENDING " ) ;
}
else
{
V_sprintf_safe ( idstr , " %s " , id . steamid . Render ( ) ) ;
}
}
break ;
case IDTYPE_HLTV :
{
V_strcpy_safe ( idstr , " HLTV " ) ;
}
break ;
case IDTYPE_REPLAY :
{
V_strcpy_safe ( idstr , " REPLAY " ) ;
}
break ;
default :
{
V_strcpy_safe ( idstr , " UNKNOWN " ) ;
}
break ;
}
return idstr ;
}
//-----------------------------------------------------------------------------
// Purpose: return a string version of the userid
//-----------------------------------------------------------------------------
const char * CBaseClient : : GetNetworkIDString ( ) const
{
if ( IsFakeClient ( ) )
{
return " BOT " ;
}
return ( GetUserIDString ( GetNetworkID ( ) ) ) ;
}
bool CBaseClient : : IgnoreTempEntity ( CEventInfo * event )
{
int iPlayerIndex = GetPlayerSlot ( ) + 1 ;
return ! event - > filter . IncludesPlayer ( iPlayerIndex ) ;
}
const USERID_t CBaseClient : : GetNetworkID ( ) const
{
USERID_t userID ;
userID . steamid = m_SteamID ;
userID . idtype = IDTYPE_STEAM ;
return userID ;
}
void CBaseClient : : SetSteamID ( const CSteamID & steamID )
{
m_SteamID = steamID ;
}
void CBaseClient : : SetMaxRoutablePayloadSize ( int nMaxRoutablePayloadSize )
{
if ( m_NetChannel )
{
m_NetChannel - > SetMaxRoutablePayloadSize ( nMaxRoutablePayloadSize ) ;
}
}
int CBaseClient : : GetMaxAckTickCount ( ) const
{
int nMaxTick = m_nSignonTick ;
if ( m_nDeltaTick > nMaxTick )
{
nMaxTick = m_nDeltaTick ;
}
if ( m_nStringTableAckTick > nMaxTick )
{
nMaxTick = m_nStringTableAckTick ;
}
return nMaxTick ;
}
bool CBaseClient : : ProcessCmdKeyValues ( CLC_CmdKeyValues * msg )
{
return true ;
}
void CBaseClient : : OnSignonStateFull ( )
{
# if defined( REPLAY_ENABLED )
if ( g_pReplay & & g_pServerReplayContext )
{
g_pServerReplayContext - > CreateSessionOnClient ( m_nClientSlot ) ;
}
# endif
}