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