summaryrefslogtreecommitdiff
path: root/engine/baseclient.cpp
diff options
context:
space:
mode:
authorFluorescentCIAAfricanAmerican <[email protected]>2020-04-22 12:56:21 -0400
committerFluorescentCIAAfricanAmerican <[email protected]>2020-04-22 12:56:21 -0400
commit3bf9df6b2785fa6d951086978a3e66f49427166a (patch)
tree2c0f1f0c63c4832882bc93814ebd2c2b1c6224e5 /engine/baseclient.cpp
downloadarchived-source-engine-2018-hl2-src-3bf9df6b2785fa6d951086978a3e66f49427166a.tar.xz
archived-source-engine-2018-hl2-src-3bf9df6b2785fa6d951086978a3e66f49427166a.zip
Diffstat (limited to 'engine/baseclient.cpp')
-rw-r--r--engine/baseclient.cpp1735
1 files changed, 1735 insertions, 0 deletions
diff --git a/engine/baseclient.cpp b/engine/baseclient.cpp
new file mode 100644
index 0000000..9e501e3
--- /dev/null
+++ b/engine/baseclient.cpp
@@ -0,0 +1,1735 @@
+//========= 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\nMap: %s\nPlayers: %i / %i\nBuild: %d\nServer 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, logData.String() );
+ if ( sv_netspike_output.GetInt() & 2 )
+ Log( "%s", logData.String() );
+ ETWMark1S( "netspike", 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
+}