You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
993 lines
25 KiB
993 lines
25 KiB
//========= Copyright Valve Corporation, All rights reserved. ============// |
|
// |
|
// Purpose: |
|
// |
|
//=============================================================================// |
|
|
|
#ifndef SWDS |
|
#include "screen.h" |
|
#include "cl_main.h" |
|
#include "iprediction.h" |
|
#include "proto_oob.h" |
|
#include "demo.h" |
|
#include "tier0/icommandline.h" |
|
#include "ispatialpartitioninternal.h" |
|
#include "GameEventManager.h" |
|
#include "cdll_engine_int.h" |
|
#include "voice.h" |
|
#include "host_cmd.h" |
|
#include "server.h" |
|
#include "convar.h" |
|
#include "dt_recv_eng.h" |
|
#include "dt_common_eng.h" |
|
#include "LocalNetworkBackdoor.h" |
|
#include "vox.h" |
|
#include "sound.h" |
|
#include "r_efx.h" |
|
#include "r_local.h" |
|
#include "decal_private.h" |
|
#include "vgui_baseui_interface.h" |
|
#include "host_state.h" |
|
#include "cl_ents_parse.h" |
|
#include "eiface.h" |
|
#include "server.h" |
|
#include "cl_demoactionmanager.h" |
|
#include "decal.h" |
|
#include "r_decal.h" |
|
#include "materialsystem/imaterial.h" |
|
#include "EngineSoundInternal.h" |
|
#include "ivideomode.h" |
|
#include "download.h" |
|
#include "GameUI/IGameUI.h" |
|
#include "cl_demo.h" |
|
#include "cdll_engine_int.h" |
|
|
|
#if defined( REPLAY_ENABLED ) |
|
#include "replay/ienginereplay.h" |
|
#include "replay_internal.h" |
|
#endif |
|
|
|
#include "audio_pch.h" |
|
|
|
#if defined ( _X360 ) |
|
#include "matchmaking.h" |
|
#endif |
|
|
|
// memdbgon must be the last include file in a .cpp file!!! |
|
#include "tier0/memdbgon.h" |
|
|
|
extern IVEngineClient *engineClient; |
|
|
|
extern CNetworkStringTableContainer *networkStringTableContainerClient; |
|
extern CNetworkStringTableContainer *networkStringTableContainerServer; |
|
|
|
static ConVar cl_allowupload ( "cl_allowupload", "1", FCVAR_ARCHIVE, "Client uploads customization files" ); |
|
static ConVar cl_voice_filter( "cl_voice_filter", "", 0, "Filter voice by name substring" ); // filter incoming voice data |
|
|
|
static ConVar *replay_voice_during_playback = NULL; |
|
|
|
extern ConCommand quit; |
|
|
|
void CClientState::ConnectionClosing( const char * reason ) |
|
{ |
|
// if connected, shut down host |
|
if ( m_nSignonState > SIGNONSTATE_NONE ) |
|
{ |
|
ConMsg( "Disconnect: %s.\n", reason ); |
|
if ( !Q_stricmp( reason, INVALID_STEAM_TICKET ) |
|
|| !Q_stricmp( reason, INVALID_STEAM_LOGON_TICKET_CANCELED ) ) |
|
{ |
|
g_eSteamLoginFailure = STEAMLOGINFAILURE_BADTICKET; |
|
} |
|
else if ( !Q_stricmp( reason, INVALID_STEAM_LOGON_NOT_CONNECTED ) ) |
|
{ |
|
g_eSteamLoginFailure = STEAMLOGINFAILURE_NOSTEAMLOGIN; |
|
} |
|
else if ( !Q_stricmp( reason, INVALID_STEAM_LOGGED_IN_ELSEWHERE ) ) |
|
{ |
|
g_eSteamLoginFailure = STEAMLOGINFAILURE_LOGGED_IN_ELSEWHERE; |
|
} |
|
else if ( !Q_stricmp( reason, INVALID_STEAM_VACBANSTATE ) ) |
|
{ |
|
g_eSteamLoginFailure = STEAMLOGINFAILURE_VACBANNED; |
|
} |
|
else |
|
{ |
|
g_eSteamLoginFailure = STEAMLOGINFAILURE_NONE; |
|
} |
|
|
|
if ( reason && reason[0] == '#' ) |
|
{ |
|
COM_ExplainDisconnection( true, reason ); |
|
} |
|
else |
|
{ |
|
COM_ExplainDisconnection( true, "Disconnect: %s.\n", reason ); |
|
} |
|
SCR_EndLoadingPlaque(); |
|
Host_Disconnect( true, reason ); |
|
} |
|
} |
|
|
|
|
|
void CClientState::ConnectionCrashed( const char * reason ) |
|
{ |
|
// if connected, shut down host |
|
if ( m_nSignonState > SIGNONSTATE_NONE ) |
|
{ |
|
DebuggerBreakIfDebugging_StagingOnly(); |
|
|
|
COM_ExplainDisconnection( true, "Disconnect: %s.\n", reason ); |
|
SCR_EndLoadingPlaque(); |
|
Host_EndGame ( true, "%s", reason ); |
|
} |
|
} |
|
|
|
|
|
void CClientState::FileRequested(const char *fileName, unsigned int transferID) |
|
{ |
|
ConMsg( "File '%s' requested from server %s.\n", fileName, m_NetChannel->GetAddress() ); |
|
|
|
if ( !cl_allowupload.GetBool() ) |
|
{ |
|
ConMsg( "File uploading disabled.\n" ); |
|
m_NetChannel->DenyFile( fileName, transferID ); |
|
return; |
|
} |
|
|
|
// TODO check if file valid for uploading |
|
m_NetChannel->SendFile( fileName, transferID ); |
|
} |
|
|
|
void CClientState::FileReceived( const char * fileName, unsigned int transferID ) |
|
{ |
|
#ifndef _XBOX |
|
// check if the client donwload manager requested this file |
|
CL_FileReceived( fileName, transferID ); |
|
// notify client dll |
|
if ( g_ClientDLL ) |
|
{ |
|
g_ClientDLL->FileReceived( fileName, transferID ); |
|
} |
|
#endif |
|
} |
|
|
|
void CClientState::FileDenied(const char *fileName, unsigned int transferID ) |
|
{ |
|
#ifndef _XBOX |
|
// check if the file download manager requested that file |
|
CL_FileDenied( fileName, transferID ); |
|
#endif |
|
} |
|
|
|
void CClientState::FileSent( const char *fileName, unsigned int transferID ) |
|
{ |
|
} |
|
|
|
void CClientState::PacketStart( int incoming_sequence, int outgoing_acknowledged ) |
|
{ |
|
// Ack'd incoming messages. |
|
m_nCurrentSequence = incoming_sequence; |
|
command_ack = outgoing_acknowledged; |
|
} |
|
|
|
|
|
void CClientState::PacketEnd() |
|
{ |
|
// |
|
// we don't know if it is ok to save a demo message until |
|
// after we have parsed the frame |
|
// |
|
|
|
// Play any sounds we received this packet |
|
CL_DispatchSounds(); |
|
|
|
// Did we get any messages this tick (i.e., did we call PreEntityPacketReceived)? |
|
if ( GetServerTickCount() != m_nDeltaTick ) |
|
return; |
|
|
|
// How many commands total did we run this frame |
|
int commands_acknowledged = command_ack - last_command_ack; |
|
|
|
// COM_Log( "cl.log", "Server ack'd %i commands this frame\n", commands_acknowledged ); |
|
|
|
//Msg( "%i/%i CL_PostReadMessages: last ack %i most recent %i acked %i commands\n", |
|
// host_framecount, cl.tickcount, |
|
// cl.last_command_ack, |
|
// cl.netchan->outgoing_sequence - 1, |
|
// commands_acknowledged ); |
|
|
|
// Highest command parsed from messages |
|
last_command_ack = command_ack; |
|
|
|
// Let prediction copy off pristine data and report any errors, etc. |
|
g_pClientSidePrediction->PostNetworkDataReceived( commands_acknowledged ); |
|
|
|
#ifndef _XBOX |
|
demoaction->DispatchEvents(); |
|
#endif |
|
} |
|
|
|
#undef CreateEvent |
|
void CClientState::Disconnect( const char *pszReason, bool bShowMainMenu ) |
|
{ |
|
#if defined( REPLAY_ENABLED ) |
|
if ( g_pClientReplayContext && IsConnected() ) |
|
{ |
|
g_pClientReplayContext->OnClientSideDisconnect(); |
|
} |
|
#endif |
|
|
|
CBaseClientState::Disconnect( pszReason, bShowMainMenu ); |
|
|
|
#ifndef _X360 |
|
IGameEvent *event = g_GameEventManager.CreateEvent( "client_disconnect" ); |
|
if ( event ) |
|
{ |
|
if ( !pszReason ) |
|
pszReason = ""; |
|
event->SetString( "message", pszReason ); |
|
g_GameEventManager.FireEventClientSide( event ); |
|
} |
|
#endif |
|
|
|
// stop any demo activities |
|
#ifndef _XBOX |
|
demoplayer->StopPlayback(); |
|
demorecorder->StopRecording(); |
|
#endif |
|
|
|
S_StopAllSounds( true ); |
|
|
|
R_DecalTermAll(); |
|
|
|
if ( m_nMaxClients > 1 ) |
|
{ |
|
if ( EngineVGui()->IsConsoleVisible() == false ) |
|
{ |
|
// start progress bar immediately for multiplayer level transitions |
|
EngineVGui()->EnabledProgressBarForNextLoad(); |
|
} |
|
} |
|
|
|
CL_ClearState(); |
|
|
|
#ifndef _XBOX |
|
// End any in-progress downloads |
|
CL_HTTPStop_f(); |
|
#endif |
|
|
|
// stop loading progress bar |
|
if (bShowMainMenu) |
|
{ |
|
SCR_EndLoadingPlaque(); |
|
} |
|
|
|
// notify game ui dll of out-of-in-game status |
|
EngineVGui()->NotifyOfServerDisconnect(); |
|
|
|
if (bShowMainMenu && !engineClient->IsDrawingLoadingImage() && (cl.demonum == -1)) |
|
{ |
|
// we're not in the middle of loading something, so show the UI |
|
if ( EngineVGui() ) |
|
{ |
|
EngineVGui()->ActivateGameUI(); |
|
} |
|
} |
|
|
|
HostState_OnClientDisconnected(); |
|
|
|
// if we played a demo from the startdemos list, play next one |
|
if (cl.demonum != -1) |
|
{ |
|
CL_NextDemo(); |
|
} |
|
} |
|
|
|
|
|
bool CClientState::ProcessTick( NET_Tick *msg ) |
|
{ |
|
int tick = msg->m_nTick; |
|
|
|
m_NetChannel->SetRemoteFramerate( msg->m_flHostFrameTime, msg->m_flHostFrameTimeStdDeviation ); |
|
|
|
m_ClockDriftMgr.SetServerTick( tick ); |
|
|
|
// Remember this for GetLastTimeStamp(). |
|
m_flLastServerTickTime = tick * host_state.interval_per_tick; |
|
|
|
// Use the server tick while reading network data (used for interpolation samples, etc). |
|
g_ClientGlobalVariables.tickcount = tick; |
|
g_ClientGlobalVariables.curtime = tick * host_state.interval_per_tick; |
|
g_ClientGlobalVariables.frametime = (tick - oldtickcount) * host_state.interval_per_tick; // We used to call GetFrameTime() here, but 'insimulation' is always |
|
// true so we have this code right in here to keep it simple. |
|
|
|
return true; |
|
} |
|
|
|
|
|
bool CClientState::ProcessStringCmd( NET_StringCmd *msg ) |
|
{ |
|
return CBaseClientState::ProcessStringCmd( msg ); |
|
} |
|
|
|
|
|
bool CClientState::ProcessServerInfo( SVC_ServerInfo *msg ) |
|
{ |
|
// Reset client state |
|
CL_ClearState(); |
|
|
|
if ( !CBaseClientState::ProcessServerInfo( msg ) ) |
|
{ |
|
Disconnect( "CBaseClientState::ProcessServerInfo failed", true ); |
|
return false; |
|
} |
|
#ifndef _XBOX |
|
if ( demoplayer->IsPlayingBack() ) |
|
{ |
|
// Because a server doesn't run during |
|
// demoplayback, but the decal system relies on this... |
|
m_nServerCount = gHostSpawnCount; |
|
} |
|
else |
|
{ |
|
// tell demo recorder that new map is loaded and we are receiving |
|
// it's signon data (will be written into extra demo header file) |
|
demorecorder->SetSignonState( SIGNONSTATE_NEW ); |
|
} |
|
#endif |
|
// is server a HLTV proxy ? |
|
ishltv = msg->m_bIsHLTV; |
|
|
|
#if defined( REPLAY_ENABLED ) |
|
// is server a replay proxy ? |
|
isreplay = msg->m_bIsReplay; |
|
#endif |
|
|
|
// The MD5 of the server map must match the MD5 of the client map. or else |
|
// the client is probably cheating. |
|
V_memcpy( serverMD5.bits, msg->m_nMapMD5.bits, MD5_DIGEST_LENGTH ); |
|
|
|
// Multiplayer game? |
|
if ( m_nMaxClients > 1 ) |
|
{ |
|
if ( mp_decals.GetInt() < r_decals.GetInt() ) |
|
{ |
|
r_decals.SetValue( mp_decals.GetInt() ); |
|
} |
|
} |
|
|
|
g_ClientGlobalVariables.maxClients = m_nMaxClients; |
|
g_ClientGlobalVariables.network_protocol = msg->m_nProtocol; |
|
|
|
#ifdef SHARED_NET_STRING_TABLES |
|
// use same instance of StringTableContainer as the server does |
|
m_StringTableContainer = networkStringTableContainerServer; |
|
CL_HookClientStringTables(); |
|
#else |
|
// use own instance of StringTableContainer |
|
m_StringTableContainer = networkStringTableContainerClient; |
|
#endif |
|
|
|
CL_ReallocateDynamicData( m_nMaxClients ); |
|
|
|
if ( sv.IsPaused() ) |
|
{ |
|
if ( msg->m_fTickInterval != host_state.interval_per_tick ) |
|
{ |
|
Host_Error( "Expecting interval_per_tick %f, got %f\n", |
|
host_state.interval_per_tick, msg->m_fTickInterval ); |
|
return false; |
|
} |
|
} |
|
else |
|
{ |
|
host_state.interval_per_tick = msg->m_fTickInterval; |
|
} |
|
|
|
// Re-init hud video, especially if we changed game directories |
|
ClientDLL_HudVidInit(); |
|
|
|
// Don't verify the map and player .mdl crc's until after any missing resources have |
|
// been downloaded. This will still occur before requesting the rest of the signon. |
|
|
|
|
|
gHostSpawnCount = m_nServerCount; |
|
|
|
videomode->MarkClientViewRectDirty(); // leave intermission full screen |
|
return true; |
|
} |
|
|
|
bool CClientState::ProcessClassInfo( SVC_ClassInfo *msg ) |
|
{ |
|
if ( msg->m_bCreateOnClient ) |
|
{ |
|
#ifndef _XBOX |
|
if ( !demoplayer->IsPlayingBack() ) |
|
#endif |
|
{ |
|
// Create all of the send tables locally |
|
DataTable_CreateClientTablesFromServerTables(); |
|
|
|
// Now create all of the server classes locally, too |
|
DataTable_CreateClientClassInfosFromServerClasses( this ); |
|
|
|
// store the current data tables in demo file to make sure |
|
// they are the same during playback |
|
#ifndef _XBOX |
|
demorecorder->RecordServerClasses( serverGameDLL->GetAllServerClasses() ); |
|
#endif |
|
} |
|
|
|
LinkClasses(); // link server and client classes |
|
} |
|
else |
|
{ |
|
CBaseClientState::ProcessClassInfo( msg ); |
|
} |
|
|
|
#ifdef DEDICATED |
|
bool bAllowMismatches = false; |
|
#else |
|
bool bAllowMismatches = ( demoplayer && demoplayer->IsPlayingBack() ); |
|
#endif // DEDICATED |
|
|
|
if ( !RecvTable_CreateDecoders( serverGameDLL->GetStandardSendProxies(), bAllowMismatches ) ) // create receive table decoders |
|
{ |
|
Host_EndGame( true, "CL_ParseClassInfo_EndClasses: CreateDecoders failed.\n" ); |
|
return false; |
|
} |
|
|
|
#ifndef _XBOX |
|
if ( !demoplayer->IsPlayingBack() ) |
|
#endif |
|
{ |
|
CLocalNetworkBackdoor::InitFastCopy(); |
|
} |
|
|
|
return true; |
|
} |
|
|
|
bool CClientState::ProcessSetPause( SVC_SetPause *msg ) |
|
{ |
|
CBaseClientState::ProcessSetPause( msg ); |
|
|
|
return true; |
|
} |
|
|
|
bool CClientState::ProcessSetPauseTimed( SVC_SetPauseTimed *msg ) |
|
{ |
|
CBaseClientState::ProcessSetPauseTimed( msg ); |
|
|
|
return true; |
|
} |
|
|
|
bool CClientState::ProcessVoiceInit( SVC_VoiceInit *msg ) |
|
{ |
|
#if !defined( NO_VOICE )//#ifndef _XBOX |
|
if ( msg->m_szVoiceCodec[0] == 0 ) |
|
{ |
|
Voice_Deinit(); |
|
} |
|
else |
|
{ |
|
Voice_Init( msg->m_szVoiceCodec, msg->m_nSampleRate ); |
|
} |
|
#endif |
|
return true; |
|
} |
|
|
|
ConVar voice_debugfeedback( "voice_debugfeedback", "0" ); |
|
|
|
bool CClientState::ProcessVoiceData( SVC_VoiceData *msg ) |
|
{ |
|
char chReceived[4096]; |
|
int bitsRead = msg->m_DataIn.ReadBitsClamped( chReceived, msg->m_nLength ); |
|
|
|
#if defined ( _X360 ) |
|
DWORD dwLength = msg->m_nLength; |
|
XUID xuid = msg->m_xuid; |
|
Audio_GetXVoice()->PlayIncomingVoiceData( xuid, (byte*)chReceived, dwLength ); |
|
|
|
if ( voice_debugfeedback.GetBool() ) |
|
{ |
|
Msg( "Received voice from: %d\n", msg->m_nFromClient + 1 ); |
|
} |
|
|
|
return true; |
|
#endif |
|
|
|
#if !defined( NO_VOICE )//#ifndef _XBOX |
|
int iEntity = msg->m_nFromClient + 1; |
|
if ( iEntity == (m_nPlayerSlot + 1) ) |
|
{ |
|
Voice_LocalPlayerTalkingAck(); |
|
} |
|
|
|
player_info_t playerinfo; |
|
engineClient->GetPlayerInfo( iEntity, &playerinfo ); |
|
|
|
if ( Q_strlen( cl_voice_filter.GetString() ) > 0 && Q_strstr( playerinfo.name, cl_voice_filter.GetString() ) == NULL ) |
|
return true; |
|
|
|
#if defined( REPLAY_ENABLED ) |
|
extern IEngineClientReplay *g_pEngineClientReplay; |
|
bool bInReplay = engineClient->IsPlayingDemo() && g_pEngineClientReplay && g_pEngineClientReplay->IsPlayingReplayDemo(); |
|
|
|
if ( replay_voice_during_playback == NULL ) |
|
{ |
|
replay_voice_during_playback = g_pCVar->FindVar( "replay_voice_during_playback" ); |
|
Assert( replay_voice_during_playback != NULL ); |
|
} |
|
|
|
// Don't play back voice data during replay unless the client specified it to. |
|
if ( bInReplay && replay_voice_during_playback && !replay_voice_during_playback->GetBool() ) |
|
return true; |
|
#endif |
|
|
|
// Data length can be zero when the server is just acking a client's voice data. |
|
if ( bitsRead == 0 ) |
|
return true; |
|
|
|
if ( !Voice_Enabled() ) |
|
{ |
|
return true; |
|
} |
|
|
|
// Have we already initialized the channels for this guy? |
|
int nChannel = Voice_GetChannel( iEntity ); |
|
if ( nChannel == VOICE_CHANNEL_ERROR ) |
|
{ |
|
// Create a channel in the voice engine and a channel in the sound engine for this guy. |
|
nChannel = Voice_AssignChannel( iEntity, msg->m_bProximity ); |
|
if ( nChannel == VOICE_CHANNEL_ERROR ) |
|
{ |
|
// If they used -nosound, then it's not a problem. |
|
if ( S_IsInitted() ) |
|
ConDMsg("ProcessVoiceData: Voice_AssignChannel failed for client %d!\n", iEntity-1); |
|
|
|
return true; |
|
} |
|
} |
|
|
|
// Give the voice engine the data (it in turn gives it to the mixer for the sound engine). |
|
Voice_AddIncomingData( nChannel, chReceived, Bits2Bytes( bitsRead ), m_nCurrentSequence ); |
|
#endif |
|
return true; |
|
}; |
|
|
|
bool CClientState::ProcessPrefetch( SVC_Prefetch *msg ) |
|
{ |
|
char const *soundname = cl.GetSoundName( msg->m_nSoundIndex ); |
|
if ( soundname && soundname [ 0 ] ) |
|
{ |
|
EngineSoundClient()->PrefetchSound( soundname ); |
|
} |
|
return true; |
|
} |
|
|
|
void CClientState::ProcessSoundsWithProtoVersion( SVC_Sounds *msg, CUtlVector< SoundInfo_t > &sounds, int nProtoVersion ) |
|
{ |
|
SoundInfo_t defaultSound; defaultSound.SetDefault(); |
|
SoundInfo_t *pDeltaSound = &defaultSound; |
|
|
|
// Max is 32 in multiplayer and 255 in singleplayer |
|
// Reserve this memory up front so it doesn't realloc under pDeltaSound pointing at it |
|
sounds.EnsureCapacity( 256 ); |
|
|
|
for ( int i = 0; i < msg->m_nNumSounds; i++ ) |
|
{ |
|
int nSound = sounds.AddToTail(); |
|
SoundInfo_t *pSound = &(sounds[ nSound ]); |
|
|
|
pSound->ReadDelta( pDeltaSound, msg->m_DataIn, nProtoVersion ); |
|
|
|
pDeltaSound = pSound; // copy delta values |
|
|
|
if ( msg->m_bReliableSound ) |
|
{ |
|
// client is incrementing the reliable sequence numbers itself |
|
m_nSoundSequence = ( m_nSoundSequence + 1 ) & SOUND_SEQNUMBER_MASK; |
|
Assert ( pSound->nSequenceNumber == 0 ); |
|
pSound->nSequenceNumber = m_nSoundSequence; |
|
} |
|
} |
|
} |
|
|
|
bool CClientState::ProcessSounds( SVC_Sounds *msg ) |
|
{ |
|
if ( msg->m_DataIn.IsOverflowed() ) |
|
{ |
|
// Overflowed before we even started! There's nothing we can do with this buffer. |
|
return false; |
|
} |
|
|
|
CUtlVector< SoundInfo_t > sounds; |
|
|
|
int startbit = msg->m_DataIn.GetNumBitsRead(); |
|
|
|
// Process with the reported proto version |
|
ProcessSoundsWithProtoVersion( msg, sounds, g_ClientGlobalVariables.network_protocol ); |
|
|
|
int nRelativeBitsRead = msg->m_DataIn.GetNumBitsRead() - startbit; |
|
|
|
if ( msg->m_nLength != nRelativeBitsRead || msg->m_DataIn.IsOverflowed() ) |
|
{ |
|
// The number of bits read is not what we expect! |
|
sounds.RemoveAll(); |
|
|
|
int nFallbackProtocol = 0; |
|
|
|
// If the demo file thinks it's version 18 or 19, it might actually be the other. |
|
// This is a work around for when we broke compatibility Halloween 2011. |
|
// -Jeep |
|
if ( g_ClientGlobalVariables.network_protocol == PROTOCOL_VERSION_18 ) |
|
{ |
|
nFallbackProtocol = PROTOCOL_VERSION_19; |
|
} |
|
else if ( g_ClientGlobalVariables.network_protocol == PROTOCOL_VERSION_19 ) |
|
{ |
|
nFallbackProtocol = PROTOCOL_VERSION_18; |
|
} |
|
|
|
if ( nFallbackProtocol != 0 ) |
|
{ |
|
// Roll back our buffer to before we read those bits and wipe the overflow flag |
|
msg->m_DataIn.Reset(); |
|
msg->m_DataIn.Seek( startbit ); |
|
|
|
// Try again with the fallback version |
|
ProcessSoundsWithProtoVersion( msg, sounds, nFallbackProtocol ); |
|
|
|
nRelativeBitsRead = msg->m_DataIn.GetNumBitsRead() - startbit; |
|
} |
|
} |
|
|
|
if ( msg->m_nLength == nRelativeBitsRead ) |
|
{ |
|
// Now that we know the bits were read correctly, add all the sounds |
|
for ( int i = 0; i < sounds.Count(); ++i ) |
|
{ |
|
// Add all received sounds to sorted queue (sounds may arrive in multiple messages), |
|
// will be processed after all packets have been completely parsed |
|
CL_AddSound( sounds[ i ] ); |
|
} |
|
|
|
// read the correct number of bits |
|
return true; |
|
} |
|
|
|
// Wipe the overflow flag and set the buffer to how much we expected to read |
|
msg->m_DataIn.Reset(); |
|
msg->m_DataIn.Seek( startbit + msg->m_nLength ); |
|
|
|
// didn't read the correct number of bits with either proto version attempt |
|
return false; |
|
} |
|
|
|
|
|
bool CClientState::ProcessFixAngle( SVC_FixAngle *msg ) |
|
{ |
|
for (int i=0 ; i<3 ; i++) |
|
{ |
|
// Clamp between -180 and 180 |
|
if (msg->m_Angle[i]>180) |
|
{ |
|
msg->m_Angle[i] -= 360; |
|
} |
|
} |
|
|
|
if ( msg->m_bRelative ) |
|
{ |
|
// Update running counter |
|
addangletotal += msg->m_Angle[YAW]; |
|
|
|
AddAngle a; |
|
a.total = addangletotal; |
|
a.starttime = m_flLastServerTickTime; |
|
|
|
addangle.AddToTail( a ); |
|
} |
|
else |
|
{ |
|
|
|
viewangles = msg->m_Angle; |
|
} |
|
|
|
return true; |
|
} |
|
|
|
|
|
bool CClientState::ProcessCrosshairAngle( SVC_CrosshairAngle *msg ) |
|
{ |
|
g_ClientDLL->SetCrosshairAngle( msg->m_Angle ); |
|
|
|
return true; |
|
} |
|
|
|
|
|
bool CClientState::ProcessBSPDecal( SVC_BSPDecal *msg ) |
|
{ |
|
model_t * model; |
|
|
|
if ( msg->m_nEntityIndex ) |
|
{ |
|
model = GetModel( msg->m_nModelIndex ); |
|
} |
|
else |
|
{ |
|
model = host_state.worldmodel; |
|
if ( !model ) |
|
{ |
|
Warning( "ProcessBSPDecal: Trying to project on world before host_state.worldmodel is set!!!\n" ); |
|
} |
|
} |
|
|
|
if ( model == NULL ) |
|
{ |
|
IMaterial *mat = Draw_DecalMaterial( msg->m_nDecalTextureIndex ); |
|
char const *matname = "???"; |
|
if ( mat ) |
|
{ |
|
matname = mat->GetName(); |
|
} |
|
|
|
Warning( "Warning! Static BSP decal (%s), on NULL model index %i for entity index %i.\n", |
|
matname, |
|
msg->m_nModelIndex, |
|
msg->m_nEntityIndex ); |
|
return true; |
|
} |
|
|
|
if (r_decals.GetInt()) |
|
{ |
|
g_pEfx->DecalShoot( |
|
msg->m_nDecalTextureIndex, |
|
msg->m_nEntityIndex, |
|
model, |
|
vec3_origin, |
|
vec3_angle, |
|
msg->m_Pos, |
|
NULL, |
|
msg->m_bLowPriority ? 0 : FDECAL_PERMANENT ); |
|
} |
|
|
|
return true; |
|
} |
|
|
|
|
|
bool CClientState::ProcessGameEvent(SVC_GameEvent *msg) |
|
{ |
|
tmZoneFiltered( TELEMETRY_LEVEL0, 50, TMZF_NONE, "%s", __FUNCTION__ ); |
|
|
|
int startbit = msg->m_DataIn.GetNumBitsRead(); |
|
|
|
IGameEvent *event = g_GameEventManager.UnserializeEvent( &msg->m_DataIn ); |
|
|
|
int length = msg->m_DataIn.GetNumBitsRead() - startbit; |
|
|
|
if ( length != msg->m_nLength ) |
|
{ |
|
DevMsg("CClientState::ProcessGameEvent: KeyValue length mismatch.\n" ); |
|
return true; |
|
} |
|
|
|
if ( !event ) |
|
{ |
|
DevMsg("CClientState::ProcessGameEvent: UnserializeKeyValue failed.\n" ); |
|
return true; |
|
} |
|
|
|
g_GameEventManager.FireEventClientSide( event ); |
|
|
|
return true; |
|
} |
|
|
|
|
|
bool CClientState::ProcessUserMessage(SVC_UserMessage *msg) |
|
{ |
|
// buffer for incoming user message |
|
ALIGN4 byte userdata[ MAX_USER_MSG_DATA ] ALIGN4_POST = { 0 }; |
|
bf_read userMsg( "UserMessage(read)", userdata, sizeof( userdata ) ); |
|
int bitsRead = msg->m_DataIn.ReadBitsClamped( userdata, msg->m_nLength ); |
|
userMsg.StartReading( userdata, Bits2Bytes( bitsRead ) ); |
|
|
|
// dispatch message to client.dll |
|
if ( !g_ClientDLL->DispatchUserMessage( msg->m_nMsgType, userMsg ) ) |
|
{ |
|
ConMsg( "Couldn't dispatch user message (%i)\n", msg->m_nMsgType ); |
|
return false; |
|
} |
|
|
|
return true; |
|
} |
|
|
|
|
|
bool CClientState::ProcessEntityMessage(SVC_EntityMessage *msg) |
|
{ |
|
// Look up entity |
|
IClientNetworkable *entity = entitylist->GetClientNetworkable( msg->m_nEntityIndex ); |
|
|
|
if ( !entity ) |
|
{ |
|
// message was send to use, even we don't have this entity TODO change that on server side |
|
return true; |
|
} |
|
|
|
// route to entity |
|
MDLCACHE_CRITICAL_SECTION_( g_pMDLCache ); |
|
|
|
// buffer for incoming user message |
|
ALIGN4 byte entityData[ MAX_ENTITY_MSG_DATA ] ALIGN4_POST = { 0 }; |
|
bf_read entMsg( "EntityMessage(read)", entityData, sizeof( entityData ) ); |
|
int bitsRead = msg->m_DataIn.ReadBitsClamped( entityData, msg->m_nLength ); |
|
entMsg.StartReading( entityData, Bits2Bytes( bitsRead ) ); |
|
|
|
entity->ReceiveMessage( msg->m_nClassID, entMsg ); |
|
|
|
return true; |
|
} |
|
|
|
|
|
bool CClientState::ProcessPacketEntities( SVC_PacketEntities *msg ) |
|
{ |
|
if ( !msg->m_bIsDelta ) |
|
{ |
|
// Delta too old or is initial message |
|
#ifndef _XBOX |
|
// we can start recording now that we've received an uncompressed packet |
|
demorecorder->SetSignonState( SIGNONSTATE_FULL ); |
|
#endif |
|
// Tell prediction that we're recreating entities due to an uncompressed packet arriving |
|
if ( g_pClientSidePrediction ) |
|
{ |
|
g_pClientSidePrediction->OnReceivedUncompressedPacket(); |
|
} |
|
} |
|
else |
|
{ |
|
if ( m_nDeltaTick == -1 ) |
|
{ |
|
// we requested a full update but still got a delta compressed packet. ignore it. |
|
return true; |
|
} |
|
|
|
// Preprocessing primarily does client prediction. So if we're processing deltas--do it |
|
// otherwise, we're about to be told exactly what the state of everything is--so skip it. |
|
CL_PreprocessEntities(); // setup client prediction |
|
} |
|
|
|
TRACE_PACKET(( "CL Receive (%d <-%d)\n", m_nCurrentSequence, msg->m_nDeltaFrom )); |
|
TRACE_PACKET(( "CL Num Ents (%d)\n", msg->m_nUpdatedEntries )); |
|
|
|
if ( g_pLocalNetworkBackdoor ) |
|
{ |
|
if ( m_nSignonState == SIGNONSTATE_SPAWN ) |
|
{ |
|
// We are done with signon sequence. |
|
SetSignonState( SIGNONSTATE_FULL, m_nServerCount ); |
|
} |
|
|
|
// ignore message, all entities are transmitted using fast local memcopy routines |
|
m_nDeltaTick = GetServerTickCount(); |
|
return true; |
|
} |
|
|
|
if ( !CL_ProcessPacketEntities ( msg ) ) |
|
return false; |
|
|
|
return CBaseClientState::ProcessPacketEntities( msg ); |
|
} |
|
|
|
|
|
bool CClientState::ProcessTempEntities( SVC_TempEntities *msg ) |
|
{ |
|
bool bReliable = false; |
|
|
|
float fire_time = cl.GetTime(); |
|
|
|
#ifndef _XBOX |
|
// delay firing temp ents by cl_interp in multiplayer or demoplayback |
|
if ( cl.m_nMaxClients > 1 || demoplayer->IsPlayingBack() ) |
|
{ |
|
float flInterpAmount = GetClientInterpAmount(); |
|
fire_time += flInterpAmount; |
|
} |
|
#endif |
|
|
|
if ( msg->m_nNumEntries == 0 ) |
|
{ |
|
bReliable = true; |
|
msg->m_nNumEntries = 1; |
|
} |
|
|
|
int flags = bReliable ? FEV_RELIABLE : 0; |
|
|
|
// Don't actually queue unreliable events if playing a demo and skipping ahead |
|
#ifndef _XBOX |
|
if ( !bReliable && demoplayer->IsSkipping() ) |
|
{ |
|
return true; |
|
} |
|
#endif |
|
bf_read &buffer = msg->m_DataIn; // shortcut |
|
|
|
int classID = -1; |
|
void *from = NULL; |
|
C_ServerClassInfo *pServerClass = NULL; |
|
ClientClass *pClientClass = NULL; |
|
ALIGN4 unsigned char data[CEventInfo::MAX_EVENT_DATA] ALIGN4_POST; |
|
bf_write toBuf( data, sizeof(data) ); |
|
CEventInfo *ei = NULL; |
|
|
|
for (int i = 0; i < msg->m_nNumEntries; i++ ) |
|
{ |
|
float delay = 0.0f; |
|
|
|
if ( buffer.ReadOneBit() ) |
|
{ |
|
delay = (float)buffer.ReadSBitLong( 8 ) / 100.0f; |
|
} |
|
|
|
toBuf.Reset(); |
|
|
|
if ( buffer.ReadOneBit() ) |
|
{ |
|
from = NULL; // full update |
|
|
|
classID = buffer.ReadUBitLong( m_nServerClassBits ); // classID |
|
|
|
// Look up the client class, etc. |
|
|
|
// Match the server classes to the client classes. |
|
pServerClass = m_pServerClasses ? &m_pServerClasses[ classID - 1 ] : NULL; |
|
|
|
if ( !pServerClass ) |
|
{ |
|
DevMsg("CL_QueueEvent: missing server class info for %i.\n", classID - 1 ); |
|
return false; |
|
} |
|
|
|
// See if the client .dll has a handler for this class |
|
pClientClass = FindClientClass( pServerClass->m_ClassName ); |
|
|
|
if ( !pClientClass || !pClientClass->m_pRecvTable ) |
|
{ |
|
DevMsg("CL_QueueEvent: missing client receive table for %s.\n", pServerClass->m_ClassName ); |
|
return false; |
|
} |
|
|
|
RecvTable_MergeDeltas( pClientClass->m_pRecvTable, NULL, &buffer, &toBuf ); |
|
} |
|
else |
|
{ |
|
Assert( ei ); |
|
|
|
unsigned int buffer_size = PAD_NUMBER( Bits2Bytes( ei->bits ), 4 ); |
|
bf_read fromBuf( ei->pData, buffer_size ); |
|
|
|
RecvTable_MergeDeltas( pClientClass->m_pRecvTable, &fromBuf, &buffer, &toBuf ); |
|
} |
|
|
|
// Add a slot |
|
ei = &cl.events[ cl.events.AddToTail() ]; |
|
|
|
Assert( ei ); |
|
|
|
int size = Bits2Bytes(toBuf.GetNumBitsWritten() ); |
|
|
|
ei->classID = classID; |
|
ei->fire_delay = fire_time + delay; |
|
ei->flags = flags; |
|
ei->pClientClass = pClientClass; |
|
ei->bits = toBuf.GetNumBitsWritten(); |
|
|
|
// deltaBitsReader.ReadNextPropIndex reads uint32s, so make sure we alloc in 4-byte chunks. |
|
ei->pData = new byte[ ALIGN_VALUE( size, 4 ) ]; // copy raw data |
|
Q_memcpy( ei->pData, data, size ); |
|
} |
|
|
|
return true; |
|
} |
|
|
|
#endif // swds
|
|
|