//========= 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,  msg->m_nLagTick, msg->m_flHostFrameTime, msg->m_flHostFrameTimeStdDeviation);

    // 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
