//========= 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 " , " 100 " , FCVAR_REPLICATED , " Maximum updates per second that the server will allow " ) ;
ConVar sv_minupdaterate ( " sv_minupdaterate " , " 20 " , 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