summaryrefslogtreecommitdiff
path: root/engine/sv_client.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'engine/sv_client.cpp')
-rw-r--r--engine/sv_client.cpp1682
1 files changed, 1682 insertions, 0 deletions
diff --git a/engine/sv_client.cpp b/engine/sv_client.cpp
new file mode 100644
index 0000000..6279a26
--- /dev/null
+++ b/engine/sv_client.cpp
@@ -0,0 +1,1682 @@
+//========= Copyright 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"
+#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"
+#if defined( REPLAY_ENABLED )
+#include "replayserver.h"
+#include "replay_internal.h"
+#endif
+#include "tier2/tier2.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, "Max bandwidth rate allowed on server, 0 == unlimited" );
+static ConVar sv_minrate( "sv_minrate", "3500", FCVAR_REPLICATED, "Min bandwidth rate allowed on server, 0 == unlimited" );
+
+ ConVar sv_maxupdaterate( "sv_maxupdaterate", "66", FCVAR_REPLICATED, "Maximum updates per second that the server will allow" );
+ ConVar sv_minupdaterate( "sv_minupdaterate", "10", FCVAR_REPLICATED, "Minimum updates per second that the server will allow" );
+
+ ConVar sv_stressbots("sv_stressbots", "0", FCVAR_DEVELOPMENTONLY, "If set to 1, the server calculates data and fills packets to bots. Used for perf testing.");
+static ConVar sv_allowdownload ("sv_allowdownload", "1", 0, "Allow clients to download files");
+static ConVar sv_allowupload ("sv_allowupload", "1", 0, "Allow clients to upload customizations files");
+ ConVar sv_sendtables ( "sv_sendtables", "0", FCVAR_DEVELOPMENTONLY, "Force full sendtable sending path." );
+
+
+extern ConVar sv_maxreplay;
+extern ConVar tv_snapshotrate;
+extern ConVar tv_transmitall;
+extern ConVar sv_pure_kick_clients;
+extern ConVar sv_pure_trace;
+
+// 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 SWDS
+ "rpt",
+ "rpt_connect",
+ "rpt_password",
+ "rpt_screenshot",
+ "rpt_download_log",
+#endif
+ NULL,
+};
+
+
+// Used on the server and on the client to bound its cl_rate cvar.
+int ClampClientRate( int nRate )
+{
+ if ( sv_maxrate.GetInt() > 0 )
+ {
+ nRate = clamp( nRate, MIN_RATE, sv_maxrate.GetInt() );
+ }
+
+ if ( sv_minrate.GetInt() > 0 )
+ {
+ nRate = clamp( nRate, sv_minrate.GetInt(), MAX_RATE );
+ }
+
+ return nRate;
+}
+
+
+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;
+}
+
+CGameClient::~CGameClient()
+{
+
+}
+
+bool CGameClient::ProcessClientInfo( CLC_ClientInfo *msg )
+{
+ CBaseClient::ProcessClientInfo( msg );
+
+ if ( m_bIsHLTV )
+ {
+ // Likely spoofing, or misconfiguration. Don't let Disconnect pathway believe this is a replay bot, it will
+ // asplode.
+ m_bIsHLTV = false;
+ Disconnect( "ProcessClientInfo: SourceTV can not connect to game directly.\n" );
+ return false;
+ }
+
+#if defined( REPLAY_ENABLED )
+ if ( m_bIsReplay )
+ {
+ // Likely spoofing, or misconfiguration. Don't let Disconnect pathway believe this is an hltv bot, it will
+ // asplode.
+ m_bIsReplay = false;
+ Disconnect( "ProcessClientInfo: 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::ProcessMove(CLC_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;
+
+
+ int totalcmds =msg->m_nBackupCommands + msg->m_nNewCommands;
+
+ // Decrement drop count by held back packet count
+ int netdrop = m_NetChannel->GetDropNumber();
+
+ bool ignore = !sv.IsActive();
+#ifdef SWDS
+ 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->m_NetChan->incoming_sequence,
+// m_Client->m_NetChan->incoming_sequence & SV_UPDATE_MASK );
+
+ int startbit = msg->m_DataIn.GetNumBitsRead();
+
+ serverGameClients->ProcessUsercmds
+ (
+ edict, // Player edict
+ &msg->m_DataIn,
+ msg->m_nNewCommands,
+ 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 ( msg->m_DataIn.IsOverflowed() )
+ {
+ Disconnect( "ProcessUsercmds: Overflowed reading usercmd data (check sending and receiving code for mismatches)!\n" );
+ return false;
+ }
+
+ int endbit = msg->m_DataIn.GetNumBitsRead();
+
+ if ( msg->m_nLength != (endbit-startbit) )
+ {
+ Disconnect( "ProcessUsercmds: Incorrect reading frame (check sending and receiving code for mismatches)!\n" );
+ return false;
+ }
+
+ return true;
+}
+
+bool CGameClient::ProcessVoiceData( CLC_VoiceData *msg )
+{
+ char voiceDataBuffer[4096];
+ int bitsRead = msg->m_DataIn.ReadBitsClamped( voiceDataBuffer, msg->m_nLength );
+
+ SV_BroadcastVoiceData( this, Bits2Bytes(bitsRead), voiceDataBuffer, msg->m_xuid );
+
+ return true;
+}
+
+bool CGameClient::ProcessCmdKeyValues( CLC_CmdKeyValues *msg )
+{
+ serverGameClients->ClientCommandKeyValues( edict, msg->GetKeyValues() );
+ return true;
+}
+
+bool CGameClient::ProcessRespondCvarValue( CLC_RespondCvarValue *msg )
+{
+ if ( msg->m_iCookie > 0 )
+ {
+ if ( g_pServerPluginHandler )
+ g_pServerPluginHandler->OnQueryCvarValueFinished( msg->m_iCookie, edict, msg->m_eStatusCode, msg->m_szCvarName, msg->m_szCvarValue );
+ }
+ else
+ {
+ // Negative cookie means the game DLL asked for the value.
+ if ( serverGameDLL && g_iServerGameDLLVersion >= 6 )
+ {
+#ifdef REL_TO_STAGING_MERGE_TODO
+ serverGameDLL->OnQueryCvarValueFinished( msg->m_iCookie, edict, msg->m_eStatusCode, msg->m_szCvarName, msg->m_szCvarValue );
+#endif
+ }
+ }
+
+ return true;
+}
+
+#include "pure_server.h"
+bool CGameClient::ProcessFileCRCCheck( CLC_FileCRCCheck *msg )
+{
+ // Ignore this message if we're not in pure server mode...
+ if ( !sv.IsInPureServerMode() )
+ return true;
+
+ char warningStr[1024] = {0};
+
+ // The client may send us files we don't care about, so filter them here
+// if ( !sv.GetPureServerWhitelist()->GetForceMatchList()->IsFileInList( msg->m_szFilename ) )
+// return true;
+
+ // first check against all the other files users have sent
+ FileHash_t filehash;
+ filehash.m_md5contents = msg->m_MD5;
+ filehash.m_crcIOSequence = msg->m_CRCIOs;
+ filehash.m_eFileHashType = msg->m_eFileHashType;
+ filehash.m_cbFileLen = msg->m_nFileFraction;
+ filehash.m_nPackFileNumber = msg->m_nPackFileNumber;
+ filehash.m_PackFileID = msg->m_PackFileID;
+
+ const char *path = msg->m_szPathID;
+ const char *fileName = msg->m_szFilename;
+ if ( g_PureFileTracker.DoesFileMatch( path, fileName, msg->m_nFileFraction, &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 ( sv_pure_kick_clients.GetInt() )
+ {
+ Disconnect( "%s", 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(), msg->m_szPathID, msg->m_szFilename );
+ }
+ }
+
+ return true;
+}
+bool CGameClient::ProcessFileMD5Check( CLC_FileMD5Check *msg )
+{
+ // Legacy message
+ return true;
+}
+
+
+#if defined( REPLAY_ENABLED )
+bool CGameClient::ProcessSaveReplay( CLC_SaveReplay *pMsg )
+{
+ // Don't allow on listen servers
+ if ( !sv.IsDedicated() )
+ return false;
+
+ if ( !g_pReplay )
+ return false;
+
+ g_pReplay->SV_NotifyReplayRequested();
+
+ return true;
+}
+#endif
+
+void CGameClient::DownloadCustomizations()
+{
+ 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, "game" ) )
+ continue; // we already have it
+
+ // we don't have it, request download from client
+
+ m_nCustomFiles[i].reqID = m_NetChannel->RequestFile( hexname.m_Filename );
+ }
+}
+
+void CGameClient::Connect( const char * szName, int nUserID, INetChannel *pNetChannel, bool bFakePlayer, int clientChallenge )
+{
+ CBaseClient::Connect( szName, nUserID, pNetChannel, bFakePlayer, clientChallenge );
+
+ 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 - server only
+ IGameEvent *event = g_GameEventManager.CreateEvent( "player_connect" );
+ {
+ event->SetInt( "userid", m_UserID );
+ event->SetInt( "index", m_nClientSlot );
+ event->SetString( "name", m_Name );
+ event->SetString("networkid", GetNetworkIDString() );
+ event->SetString( "address", m_NetChannel?m_NetChannel->GetAddress():"none" );
+ event->SetInt( "bot", m_bFakePlayer?1:0 );
+ g_GameEventManager.FireEvent( event, true );
+ }
+
+ // the only difference here is we don't send an
+ // IP to prevent hackers from doing evil things
+ event = g_GameEventManager.CreateEvent( "player_connect_client" );
+ if ( event )
+ {
+ event->SetInt( "userid", m_UserID );
+ event->SetInt( "index", m_nClientSlot );
+ event->SetString( "name", m_Name );
+ event->SetString( "networkid", GetNetworkIDString() );
+ event->SetInt( "bot", m_bFakePlayer ? 1 : 0 );
+ g_GameEventManager.FireEvent( event );
+ }
+}
+
+void CGameClient::SetupPackInfo( CFrameSnapshot *pSnapshot )
+{
+ // 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
+#ifndef _XBOX
+#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
+#endif
+ {
+ m_PackInfo.m_pTransmitAlways = NULL;
+ }
+
+ // Add frame to ClientFrame list
+
+ int nMaxFrames = MAX_CLIENT_FRAMES;
+
+ if ( sv_maxreplay.GetFloat() > 0 )
+ {
+ // if the server has replay features enabled, allow a way bigger frame buffer
+ nMaxFrames = max ( (float)nMaxFrames, sv_maxreplay.GetFloat() / m_Server->GetTickInterval() );
+ }
+
+ if ( nMaxFrames < AddClientFrame( m_pCurrentFrame ) )
+ {
+ // If the client has more than 64 frames, the server will start to eat too much memory.
+ RemoveOldestFrame();
+ }
+
+ // Since area to area visibility is determined by each player's PVS, copy
+ // the area network lookups into the ClientPackInfo_t
+ m_PackInfo.m_AreasNetworked = 0;
+ int areaCount = g_AreasNetworked.Count();
+ for ( int j = 0; j < areaCount; j++ )
+ {
+ m_PackInfo.m_Areas[m_PackInfo.m_AreasNetworked] = g_AreasNetworked[ j ];
+ m_PackInfo.m_AreasNetworked++;
+
+ // Msg("CGameClient::SetupPackInfo: too much areas (%i)", areaCount );
+ Assert( m_PackInfo.m_AreasNetworked < MAX_WORLD_AREAS );
+ }
+
+ CM_SetupAreaFloodNums( m_PackInfo.m_AreaFloodNums, &m_PackInfo.m_nMapAreas );
+}
+
+void CGameClient::SetupPrevPackInfo()
+{
+ 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 );
+ }
+
+ CBaseClient::SetRate( nRate, bForce );
+}
+void CGameClient::SetUpdateRate(int udpaterate, bool bForce)
+{
+ if ( !bForce )
+ {
+ if ( sv_maxupdaterate.GetInt() > 0 )
+ {
+ udpaterate = clamp( udpaterate, 1, sv_maxupdaterate.GetInt() );
+ }
+
+ if ( sv_minupdaterate.GetInt() > 0 )
+ {
+ udpaterate = clamp( udpaterate, sv_minupdaterate.GetInt(), 100 );
+ }
+ }
+
+ CBaseClient::SetUpdateRate( udpaterate, bForce );
+}
+
+
+void CGameClient::UpdateUserSettings()
+{
+ // set voice loopback
+ m_bVoiceLoopback = m_ConVars->GetInt( "voice_loopback", 0 ) != 0;
+
+ CBaseClient::UpdateUserSettings();
+
+ // Give entity dll a chance to look at the changes.
+ // Do this after CBaseClient::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;
+} */
+
+
+/*
+===================
+SV_FullClientUpdate
+
+sends all the info about *cl to *sb
+===================
+*/
+
+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 );
+ 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 );
+ }
+#ifndef _XBOX
+ if ( IsHLTV() )
+ {
+ hltv->Changelevel();
+ }
+
+#if defined( REPLAY_ENABLED )
+ if ( IsReplay() )
+ {
+ replay->Changelevel();
+ }
+#endif
+#endif
+ CBaseClient::Inactivate();
+
+ m_Sounds.Purge();
+ 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 CBaseClient::UpdateAcknowledgedFramecount( tick );
+}
+
+
+
+void CGameClient::Clear()
+{
+#ifndef _XBOX
+ if ( m_bIsHLTV && hltv )
+ {
+ hltv->Shutdown();
+ }
+
+#if defined( REPLAY_ENABLED )
+ if ( m_bIsReplay && replay )
+ {
+ replay->Shutdown();
+ }
+#endif
+#endif
+
+ CBaseClient::Clear();
+
+ // free all frames
+ DeleteClientFrames( -1 );
+
+ m_Sounds.Purge();
+ m_VoiceStreams.ClearAll();
+ m_VoiceProximity.ClearAll();
+ edict = NULL;
+ m_pViewEntity = NULL;
+ m_bVoiceLoopback = false;
+ m_LastMovementTick = 0;
+ m_nSoundSequence = 0;
+#if defined( REPLAY_ENABLED )
+ m_flLastSaveReplayTime = host_time;
+#endif
+}
+
+void CGameClient::Reconnect( void )
+{
+ // If the client was connected before, tell the game .dll to disconnect him/her.
+ sv.RemoveClientFromGame( this );
+
+ CBaseClient::Reconnect();
+}
+
+void CGameClient::Disconnect( const char *fmt, ... )
+{
+ va_list argptr;
+ char reason[1024];
+
+ if ( m_nSignonState == SIGNONSTATE_NONE )
+ return; // no recursion
+
+ va_start (argptr,fmt);
+ Q_vsnprintf (reason, sizeof( reason ), fmt,argptr);
+ va_end (argptr);
+
+ // 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", reason );
+ event->SetString("name", GetClientName() );
+ event->SetString("networkid", GetNetworkIDString() );
+ event->SetInt( "bot", m_bFakePlayer?1:0 );
+ g_GameEventManager.FireEvent( event );
+ }
+
+ m_Server->RemoveClientFromGame( this );
+
+ CBaseClient::Disconnect( "%s", reason );
+}
+
+bool CGameClient::SetSignonState(int state, int spawncount)
+{
+ if ( state == SIGNONSTATE_CONNECTED )
+ {
+ if ( !CheckConnect() )
+ return false;
+
+ m_NetChannel->SetTimeout( SIGNON_TIME_OUT ); // allow 5 minutes to load map
+ 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 );
+
+#ifdef _XBOX
+ // to save memory on the XBOX reduce reliable buffer size from 96 to 8 kB
+ m_NetChannel->SetMaxBufferSize( true, 8*1024 );
+#endif
+ }
+
+ return CBaseClient::SetSignonState( state, spawncount );
+}
+
+void CGameClient::SendSound( SoundInfo_t &sound, bool isReliable )
+{
+#if defined( REPLAY_ENABLED )
+ if ( IsFakeClient() && !IsHLTV() && !IsReplay() )
+#else
+ if ( IsFakeClient() && !IsHLTV() )
+#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 )
+ {
+ SVC_Sounds sndmsg;
+ char buffer[32];
+
+ m_nSoundSequence = ( m_nSoundSequence + 1 ) & SOUND_SEQNUMBER_MASK; // increase own sound sequence counter
+ sound.nSequenceNumber = 0; // don't transmit nSequenceNumber for reliable sounds
+
+ sndmsg.m_DataOut.StartWriting(buffer, sizeof(buffer) );
+ sndmsg.m_nNumSounds = 1;
+ sndmsg.m_bReliableSound = true;
+
+ SoundInfo_t defaultSound; defaultSound.SetDefault();
+
+ sound.WriteDelta( &defaultSound, sndmsg.m_DataOut );
+
+ // send reliable sound as single message
+ SendNetMsg( sndmsg, true );
+ return;
+ }
+
+ sound.nSequenceNumber = m_nSoundSequence;
+
+ m_Sounds.AddToTail( sound ); // queue sounds until snapshot is send
+}
+
+void CGameClient::WriteGameSounds( bf_write &buf )
+{
+ if ( m_Sounds.Count() <= 0 )
+ return;
+
+ char data[NET_MAX_PAYLOAD];
+ SVC_Sounds msg;
+ msg.m_DataOut.StartWriting( data, sizeof(data) );
+
+ int nSoundCount = FillSoundsMessage( msg );
+ msg.WriteToBuffer( buf );
+
+ if ( IsTracing() )
+ {
+ TraceNetworkData( buf, "Sounds [count=%d]", nSoundCount );
+ }
+}
+
+int CGameClient::FillSoundsMessage(SVC_Sounds &msg)
+{
+ int i, count = m_Sounds.Count();
+
+ // send max 64 sound in multiplayer per snapshot, 255 in SP
+ int max = m_Server->IsMultiplayer() ? 32 : 255;
+
+ // Discard events if we have too many to signal with 8 bits
+ if ( count > max )
+ count = max;
+
+ // Nothing to send
+ if ( !count )
+ return 0;
+
+ SoundInfo_t defaultSound; defaultSound.SetDefault();
+ SoundInfo_t *pDeltaSound = &defaultSound;
+
+ msg.m_nNumSounds = count;
+ msg.m_bReliableSound = false;
+ msg.SetReliable( false );
+
+ Assert( msg.m_DataOut.GetNumBitsLeft() > 0 );
+
+ for ( i = 0 ; i < count; i++ )
+ {
+ SoundInfo_t &sound = m_Sounds[ i ];
+ sound.WriteDelta( pDeltaSound, msg.m_DataOut );
+ pDeltaSound = &m_Sounds[ i ];
+ }
+
+ // remove added events from list
+ int remove = m_Sounds.Count() - ( count + max );
+
+ if ( remove > 0 )
+ {
+ DevMsg("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() <= max ); // keep ev_max temp ent for next update
+
+ return msg.m_nNumSounds;
+}
+
+
+
+bool CGameClient::CheckConnect( void )
+{
+ // Allow the game dll to reject this client.
+ char szRejectReason[128];
+ Q_strncpy( szRejectReason, "Connection rejected by game\n", sizeof( szRejectReason ) );
+
+ if ( !g_pServerPluginHandler->ClientConnect( edict, m_Name, m_NetChannel->GetAddress(), szRejectReason, sizeof( szRejectReason ) ) )
+ {
+ // Reject the connection and drop the client.
+ Disconnect( szRejectReason, m_Name );
+ return false;
+ }
+
+ return true;
+}
+
+void CGameClient::ActivatePlayer( void )
+{
+ CBaseClient::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" );
+}
+
+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
+ SVC_ClassInfo classmsg( true, m_Server->serverclasses );
+ m_NetChannel->SendNetMsg( classmsg );
+ }
+
+ if ( !CBaseClient::SendSignonData() )
+ return false;
+
+ m_nSoundSequence = 1; // reset sound sequence numbers after signon block
+
+ return true;
+}
+
+
+void CGameClient::SpawnPlayer( void )
+{
+ // 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
+ SVC_SetView setView( m_nEntityIndex );
+ SendNetMsg( setView );
+
+
+
+ CBaseClient::SpawnPlayer();
+
+ // notify that the player is spawning
+ serverGameClients->ClientSpawned( edict );
+}
+
+CClientFrame *CGameClient::GetDeltaFrame( int nTick )
+{
+#ifndef _XBOX
+ Assert ( !IsHLTV() ); // has no ClientFrames
+#if defined( REPLAY_ENABLED )
+ Assert ( !IsReplay() ); // has no ClientFrames
+#endif
+#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() )
+ return;
+
+ Assert( serverGameClients );
+ CPlayerState *pl = serverGameClients->GetPlayerState( edict );
+ Assert( pl );
+
+ if ( pl && pl->fixangle != FIXANGLE_NONE )
+ {
+ if ( pl->fixangle == FIXANGLE_RELATIVE )
+ {
+ SVC_FixAngle fixAngle( true, pl->anglechange );
+ m_NetChannel->SendNetMsg( fixAngle );
+ pl->anglechange.Init(); // clear
+ }
+ else
+ {
+ SVC_FixAngle fixAngle(false, pl->v_angle );
+ 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)
+{
+#ifndef _XBOX
+ if ( m_bIsHLTV )
+ {
+ // pass this message to HLTV
+ return hltv->SendNetMsg( msg, bForceReliable );
+ }
+#if defined( REPLAY_ENABLED )
+ if ( m_bIsReplay )
+ {
+ // pass this message to replay
+ return replay->SendNetMsg( msg, bForceReliable );
+ }
+#endif
+#endif
+ return CBaseClient::SendNetMsg( msg, bForceReliable);
+}
+
+bool CGameClient::ExecuteStringCommand( const char *pCommandString )
+{
+ // first let the baseclass handle it
+ if ( CBaseClient::ExecuteStringCommand( pCommandString ) )
+ return true;
+
+ // Determine whether the command is appropriate
+ CCommand args;
+ if ( !args.Tokenize( pCommandString ) )
+ return false;
+
+ if ( args.ArgC() == 0 )
+ return false;
+
+ if ( IsEngineClientCommand( args ) )
+ {
+ Cmd_ExecuteCommand( args, src_client, m_nClientSlot );
+ return true;
+ }
+
+ 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;
+ }
+
+ if ( pCommand->IsFlagSet( FCVAR_SPONLY ) )
+ {
+ if ( sv.IsMultiplayer() )
+ {
+ return false;
+ }
+ }
+
+ // Don't allow clients to execute commands marked as development only.
+ if ( pCommand->IsFlagSet( FCVAR_DEVELOPMENTONLY ) )
+ {
+ return false;
+ }
+
+ g_pServerPluginHandler->SetCommandClient( m_nClientSlot );
+ Cmd_Dispatch( pCommand, args );
+ }
+ else
+ {
+ g_pServerPluginHandler->ClientCommand( edict, args ); // TODO pass client id and string
+ }
+
+ return true;
+}
+
+void CGameClient::SendSnapshot( CClientFrame * pFrame )
+{
+ if ( m_bIsHLTV )
+ {
+#ifndef SHARED_NET_STRING_TABLES
+ // copy string updates from server to hltv stringtable
+ networkStringTableContainerServer->DirectUpdate( GetMaxAckTickCount() );
+#endif
+ char *buf = (char *)_alloca( NET_MAX_PAYLOAD );
+
+ // pack sounds to one message
+ if ( m_Sounds.Count() > 0 )
+ {
+ SVC_Sounds sounds;
+ sounds.m_DataOut.StartWriting( buf, NET_MAX_PAYLOAD );
+
+ FillSoundsMessage( sounds );
+ hltv->SendNetMsg( sounds );
+ }
+
+ int maxEnts = tv_transmitall.GetBool()?255:64;
+ hltv->WriteTempEntities( this, pFrame->GetSnapshot(), m_pLastSnapshot.GetObject(), *hltv->GetBuffer( HLTV_BUFFER_TEMPENTS ), maxEnts );
+
+ // add snapshot to HLTV server frame list
+ hltv->AddNewFrame( pFrame );
+
+ // remember this snapshot
+ m_pLastSnapshot = pFrame->GetSnapshot();
+
+ // fake acknowledgement, remove ClientFrame reference immediately
+ UpdateAcknowledgedFramecount( pFrame->tick_count );
+
+ return;
+ }
+
+#if defined( REPLAY_ENABLED )
+ if ( m_bIsReplay )
+ {
+#ifndef SHARED_NET_STRING_TABLES
+ // copy string updates from server to replay stringtable
+ networkStringTableContainerServer->DirectUpdate( GetMaxAckTickCount() );
+#endif
+ char *buf = (char *)_alloca( NET_MAX_PAYLOAD );
+
+ // pack sounds to one message
+ if ( m_Sounds.Count() > 0 )
+ {
+ SVC_Sounds sounds;
+ sounds.m_DataOut.StartWriting( buf, NET_MAX_PAYLOAD );
+
+ FillSoundsMessage( sounds );
+ replay->SendNetMsg( sounds );
+ }
+
+ int maxEnts = 255;
+ replay->WriteTempEntities( this, pFrame->GetSnapshot(), m_pLastSnapshot.GetObject(), *replay->GetBuffer( REPLAY_BUFFER_TEMPENTS ), maxEnts );
+
+ // add snapshot to Replay server frame list
+ if ( replay->AddNewFrame( pFrame ) )
+ {
+ // remember this snapshot
+ m_pLastSnapshot = pFrame->GetSnapshot();
+
+ // fake acknowledgement, remove ClientFrame reference immediately
+ UpdateAcknowledgedFramecount( pFrame->tick_count );
+ }
+
+ return;
+ }
+#endif
+
+ // update client viewangles update
+ WriteViewAngleUpdate();
+
+ CBaseClient::SendSnapshot( pFrame );
+}
+
+//-----------------------------------------------------------------------------
+// This function contains all the logic to determine if we should send a datagram
+// to a particular client
+//-----------------------------------------------------------------------------
+
+bool CGameClient::ShouldSendMessages( void )
+{
+#ifndef _XBOX
+ if ( m_bIsHLTV )
+ {
+ // calc snapshot interval
+ int nSnapshotInterval = 1.0f / ( m_Server->GetTickInterval() * tv_snapshotrate.GetFloat() );
+
+ // I am the HLTV client, record every nSnapshotInterval tick
+ return ( sv.m_nTickCount >= (hltv->m_nLastTick + nSnapshotInterval) );
+ }
+
+#if defined( REPLAY_ENABLED )
+ if ( m_bIsReplay )
+ {
+ const float replay_snapshotrate = 16.0f;
+
+ // calc snapshot interval
+ int nSnapshotInterval = 1.0f / ( m_Server->GetTickInterval() * replay_snapshotrate );
+
+ // I am the Replay client, record every nSnapshotInterval tick
+ return ( sv.m_nTickCount >= (replay->m_nLastTick + nSnapshotInterval) );
+ }
+#endif
+#endif
+ // If sv_stressbots is true, then treat a bot more like a regular client and do deltas and such for it.
+ if( IsFakeClient() )
+ {
+ if ( !sv_stressbots.GetBool() )
+ return false;
+ }
+
+ return CBaseClient::ShouldSendMessages();
+}
+
+void CGameClient::FileReceived( const char *fileName, unsigned int transferID )
+{
+ //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 )
+{
+ DevMsg( "File '%s' requested from client %s.\n", fileName, m_NetChannel->GetAddress() );
+
+ if ( sv_allowdownload.GetBool() )
+ {
+ m_NetChannel->SendFile( fileName, transferID );
+ }
+ else
+ {
+ m_NetChannel->DenyFile( fileName, transferID );
+ }
+}
+
+void CGameClient::FileDenied(const char *fileName, unsigned int transferID )
+{
+ ConMsg( "Downloading file '%s' from client %s failed.\n", fileName, GetClientName() );
+}
+
+void CGameClient::FileSent( const char *fileName, unsigned int transferID )
+{
+ ConMsg( "Sent file '%s' to client %s.\n", fileName, GetClientName() );
+}
+
+void CGameClient::PacketStart(int incoming_sequence, int 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)
+{
+#ifndef _XBOX
+ SV_RedirectEnd ();
+#endif
+ // Check for printf format tokens in this reason string. Crash exploit.
+ Disconnect ( (reason && !strchr( reason, '%' ) ) ? reason : "Connection closing" );
+}
+
+void CGameClient::ConnectionCrashed(const char *reason)
+{
+ if ( m_Name[0] && IsConnected() )
+ {
+ DebuggerBreakIfDebugging_StagingOnly();
+
+#ifndef _XBOX
+ SV_RedirectEnd ();
+#endif
+ // Check for printf format tokens in this reason string. Crash exploit.
+ Disconnect ( (reason && !strchr( reason, '%' ) ) ? reason : "Connection lost" );
+ }
+}
+
+CClientFrame *CGameClient::GetSendFrame()
+{
+ CClientFrame *pFrame = m_pCurrentFrame;
+
+ // just return if replay is disabled
+ if ( sv_maxreplay.GetFloat() <= 0 )
+ 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 CBaseClient::IgnoreTempEntity( event );
+}
+
+
+const CCheckTransmitInfo* CGameClient::GetPrevPackInfo()
+{
+ 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;
+
+ SVC_Sounds msg;
+
+ char *buf = (char *)_alloca( NET_MAX_PAYLOAD );
+
+ msg.m_DataOut.StartWriting( buf, NET_MAX_PAYLOAD );
+
+ msg.m_nNumSounds = m_Sounds.Count();
+ msg.m_bReliableSound = false;
+ msg.SetReliable( false );
+
+ Assert( msg.m_DataOut.GetNumBitsLeft() > 0 );
+
+ 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 );
+ }
+
+ if ( !bSndStop && s1.nSpecialDSP != s2.nSpecialDSP )
+ {
+ Msg( "special dsp mismatch %d %d\n", s1.nSpecialDSP, s2.nSpecialDSP );
+ }
+
+ // 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