//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose: 
//
// $Workfile:     $
// $Date:         $
// $NoKeywords: $
//===========================================================================//

#include "client.h"
#include "client_pch.h"
#include "clockdriftmgr.h"
#include "sound.h"
#include <inetchannel.h>
#include "checksum_engine.h"
#include "con_nprint.h"
#include "r_local.h"
#include "gl_lightmap.h"
#include "console.h"
#include "sv_main.h"
#include "traceinit.h"
#include "cl_demo.h"
#include "cdll_engine_int.h"
#include "debugoverlay.h"
#include "filesystem_engine.h"
#include "icliententity.h"
#include "dt_recv_eng.h"
#include "vgui_baseui_interface.h"
#include "testscriptmgr.h"
#include <tier0/vprof.h>
#include <proto_oob.h>
#include "materialsystem/imaterialsystemhardwareconfig.h"
#include "gl_matsysiface.h"
#include "staticpropmgr.h"
#include "ispatialpartitioninternal.h"
#include "cbenchmark.h"
#include "vox.h"
#include "LocalNetworkBackdoor.h"
#include <tier0/icommandline.h>
#include "GameEventManager.h"
#include "host_saverestore.h"
#include "ivideomode.h"
#include "host_phonehome.h"
#include "decal.h"
#include "sv_rcon.h"
#include "cl_rcon.h"
#include "vgui_baseui_interface.h"
#include "snd_audio_source.h"
#include "iregistry.h"
#include "sys.h"
#include <vstdlib/random.h>
#include "tier0/etwprof.h"
#include "tier0/vcrmode.h"
#include "sys_dll.h"
#include "video/ivideoservices.h"
#include "cl_steamauth.h"
#include "filesystem/IQueuedLoader.h"
#include "tier2/tier2.h"
#include "host_state.h"
#include "enginethreads.h"
#include "vgui/ISystem.h"
#include "pure_server.h"
#include "SoundEmitterSystem/isoundemittersystembase.h"
#include "LoadScreenUpdate.h"
#include "tier0/systeminformation.h"
#include "steam/steam_api.h"
#include "SourceAppInfo.h"
#include "cl_steamauth.h"
#include "sv_steamauth.h"
#include "engine/ivmodelinfo.h"
#ifdef _X360
#include "xbox/xbox_launch.h"
#endif
#if defined( REPLAY_ENABLED )
#include "replay_internal.h"
#endif

#include "language.h"
#include "igame.h"

// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"

extern IVEngineClient *engineClient;

void R_UnloadSkys( void );
void CL_ResetEntityBits( void );
void EngineTool_UpdateScreenshot();
void WriteConfig_f( ConVar *var, const char *pOldString );

// If we get more than 250 messages in the incoming buffer queue, dump any above this #
#define MAX_INCOMING_MESSAGES		250
// Size of command send buffer
#define MAX_CMD_BUFFER				0x10000

CGlobalVarsBase g_ClientGlobalVariables( true );
IVideoRecorder *g_pVideoRecorder = NULL;

extern ConVar rcon_password;
extern ConVar host_framerate;
extern ConVar cl_clanid;

ConVar sv_unlockedchapters( "sv_unlockedchapters", "1", FCVAR_ARCHIVE | FCVAR_ARCHIVE_XBOX, "Highest unlocked game chapter." );

static ConVar tv_nochat	( "tv_nochat", "0", FCVAR_ARCHIVE | FCVAR_USERINFO, "Don't receive chat messages from other SourceTV spectators" );
static ConVar cl_LocalNetworkBackdoor( "cl_localnetworkbackdoor", "1", 0, "Enable network optimizations for single player games." );
static ConVar cl_ignorepackets( "cl_ignorepackets", "0", FCVAR_CHEAT, "Force client to ignore packets (for debugging)." );
static ConVar cl_playback_screenshots( "cl_playback_screenshots", "0", 0, "Allows the client to playback screenshot and jpeg commands in demos." );

#if defined( STAGING_ONLY ) || defined( _DEBUG )
static ConVar cl_block_usercommand( "cl_block_usercommand", "0", FCVAR_CHEAT, "Force client to not send usercommand (for debugging)." );
#endif // STAGING_ONLY || _DEBUG

ConVar dev_loadtime_map_start( "dev_loadtime_map_start", "0.0", FCVAR_HIDDEN);
ConVar dev_loadtime_map_elapsed( "dev_loadtime_map_elapsed", "0.0", FCVAR_HIDDEN );

MovieInfo_t cl_movieinfo;

// FIXME: put these on hunk?
dlight_t		cl_dlights[MAX_DLIGHTS];
dlight_t		cl_elights[MAX_ELIGHTS];
CFastPointLeafNum g_DLightLeafAccessors[MAX_DLIGHTS];
CFastPointLeafNum g_ELightLeafAccessors[MAX_ELIGHTS];

bool cl_takesnapshot = false;
static bool cl_takejpeg = false;
static bool cl_takesnapshot_internal = false;

static int cl_jpegquality = DEFAULT_JPEG_QUALITY;
static ConVar jpeg_quality( "jpeg_quality", "90", 0, "jpeg screenshot quality." );

static int	cl_snapshotnum = 0;
static char cl_snapshotname[MAX_OSPATH];
static char cl_snapshot_subdirname[MAX_OSPATH];

// Must match game .dll definition
// HACK HACK FOR E3 -- Remove this after E3
#define	HIDEHUD_ALL			( 1<<2 )

void PhonemeMP3Shutdown( void );

struct ResourceLocker 
{
	ResourceLocker()
	{
		g_pFileSystem->AsyncFinishAll();
		g_pFileSystem->AsyncSuspend();

		// Need to temporarily disable queued material system, then lock it
		m_QMS = Host_AllowQueuedMaterialSystem( false );
		m_MatLock = g_pMaterialSystem->Lock();
	}

	~ResourceLocker()
	{
		// Restore QMS
		materials->Unlock( m_MatLock );
		Host_AllowQueuedMaterialSystem( m_QMS );
		g_pFileSystem->AsyncResume();

		// ??? What?  Why?
		//// Need to purge cached materials due to a sv_pure change.
		//g_pMaterialSystem->UncacheAllMaterials();
	}

	bool			m_QMS;
	MaterialLock_t	m_MatLock;
};

// Reloads a list of files if they are still loaded
void CL_ReloadFilesInList( IFileList *pFilesToReload )
{
	if ( !pFilesToReload )
	{
		return;
	}

	ResourceLocker crashPreventer;

	// Handle materials..
	materials->ReloadFilesInList( pFilesToReload );

	// Handle models.. NOTE: this MUST come after materials->ReloadFilesInList because the
	// models need to know which materials got flushed.
	modelloader->ReloadFilesInList( pFilesToReload );

	S_ReloadFilesInList( pFilesToReload );

	// Let the client at it (for particles)
	if ( g_ClientDLL )
	{
		g_ClientDLL->ReloadFilesInList( pFilesToReload );
	}
}

void CL_HandlePureServerWhitelist( CPureServerWhitelist *pWhitelist, /* out */ IFileList *&pFilesToReload )
{
	// Free the old whitelist and get the new one.
	if ( cl.m_pPureServerWhitelist )
		cl.m_pPureServerWhitelist->Release();

	cl.m_pPureServerWhitelist = pWhitelist;
	if ( cl.m_pPureServerWhitelist )
		cl.m_pPureServerWhitelist->AddRef();

	g_pFileSystem->RegisterFileWhitelist( pWhitelist, &pFilesToReload );

	// Now that we've flushed any files that shouldn't have been on disk, we should have a CRC
	// set that we can check with the server.
	cl.m_bCheckCRCsWithServer = true;
}

void PrintSvPureWhitelistClassification( const CPureServerWhitelist *pWhiteList )
{
	if ( pWhiteList == NULL )
	{
		Msg( "The server is using sv_pure -1 (no file checking).\n" );
		return;
	}

	// Load up the default whitelist
	CPureServerWhitelist *pStandardList = CPureServerWhitelist::Create( g_pFullFileSystem );
	pStandardList->Load( 0 );
	if ( *pStandardList == *pWhiteList )
	{
		Msg( "The server is using sv_pure 0.  (Enforcing consistency for select files only)\n" );
	}
	else
	{
		pStandardList->Load( 2 );
		if ( *pStandardList == *pWhiteList )
		{
			Msg( "The server is using sv_pure 2.  (Fully pure)\n" );
		}
		else
		{
			Msg( "The server is using sv_pure 1.  (Custom pure server rules.)\n" );
		}
	}
	pStandardList->Release();
}

void CL_PrintWhitelistInfo()
{
	PrintSvPureWhitelistClassification( cl.m_pPureServerWhitelist );
	if ( cl.m_pPureServerWhitelist )
	{
		cl.m_pPureServerWhitelist->PrintWhitelistContents();
	}
}

// Console command to force a whitelist on the system.
#ifdef _DEBUG
void whitelist_f( const CCommand &args )
{
	int pureLevel = 2;
	if ( args.ArgC() == 2 )
	{
		pureLevel = atoi( args[1] );
	}
	else
	{
		Warning( "Whitelist 0, 1, or 2\n" );
	}

	if ( pureLevel == 0 )
	{
		Warning( "whitelist 0: CL_HandlePureServerWhitelist( NULL )\n" );
		IFileList *pFilesToReload = NULL;
		CL_HandlePureServerWhitelist( NULL, pFilesToReload );
		CL_ReloadFilesInList( pFilesToReload );
	}
	else
	{
		CPureServerWhitelist *pWhitelist = CPureServerWhitelist::Create( g_pFileSystem );
		pWhitelist->Load( pureLevel == 1 );
		IFileList *pFilesToReload = NULL;
		CL_HandlePureServerWhitelist( pWhitelist, pFilesToReload );
		CL_ReloadFilesInList( pFilesToReload );
		pWhitelist->Release();
	}
}
ConCommand whitelist( "whitelist", whitelist_f );
#endif

const CPrecacheUserData* CL_GetPrecacheUserData( INetworkStringTable *table, int index )
{
	int testLength;
	const CPrecacheUserData *data = ( CPrecacheUserData * )table->GetStringUserData( index, &testLength );
	if ( data )
	{
		ErrorIfNot( 
			testLength == sizeof( *data ),
			("CL_GetPrecacheUserData(%d,%d) - length (%d) invalid.", table->GetTableId(), index, testLength)
		);

	}
	return data;
}


//-----------------------------------------------------------------------------
// Purpose: setup the demo flag, split from CL_IsHL2Demo so CL_IsHL2Demo can be inline
//-----------------------------------------------------------------------------
static bool s_bIsHL2Demo = false;
void CL_InitHL2DemoFlag()
{
#if defined(_X360)
	s_bIsHL2Demo = false;
#else
	static bool initialized = false;
	if ( !initialized )
	{
		if ( Steam3Client().SteamApps() && !Q_stricmp( COM_GetModDirectory(), "hl2" ) )
		{
			initialized = true;

			// if user didn't buy HL2 yet, this must be the free demo
			if ( VCRGetMode() != VCR_Playback )
			{
				s_bIsHL2Demo = !Steam3Client().SteamApps()->BIsSubscribedApp( GetAppSteamAppId( k_App_HL2 ) );
			}
#if !defined( NO_VCR )
			VCRGenericValue( "e", &s_bIsHL2Demo, sizeof( s_bIsHL2Demo ) );
#endif
		}
		
		if ( !Q_stricmp( COM_GetModDirectory(), "hl2" ) && CommandLine()->CheckParm( "-demo" ) ) 
		{
			s_bIsHL2Demo = true;
		}
	}
#endif
}

//-----------------------------------------------------------------------------
// Purpose: Returns true if the user is playing the HL2 Demo (rather than the full game)
//-----------------------------------------------------------------------------
bool CL_IsHL2Demo()
{
	CL_InitHL2DemoFlag();
	return s_bIsHL2Demo;
}

static bool s_bIsPortalDemo = false;
void CL_InitPortalDemoFlag()
{
#if defined(_X360)
	s_bIsPortalDemo = false;
#else
	static bool initialized = false;
	if ( !initialized )
	{
		if ( Steam3Client().SteamApps() && !Q_stricmp( COM_GetModDirectory(), "portal" ) )
		{
			initialized = true;
		
			// if user didn't buy Portal yet, this must be the free demo
			if ( VCRGetMode() != VCR_Playback )
			{
				s_bIsPortalDemo = !Steam3Client().SteamApps()->BIsSubscribedApp( GetAppSteamAppId( k_App_PORTAL ) );
			}
				
#if !defined( NO_VCR )
			VCRGenericValue( "e", &s_bIsPortalDemo, sizeof( s_bIsPortalDemo ) );
#endif
		}
		
		if ( !Q_stricmp( COM_GetModDirectory(), "portal" ) && CommandLine()->CheckParm( "-demo" ) ) 
		{
			s_bIsPortalDemo = true;
		}
	}
#endif
}

//-----------------------------------------------------------------------------
// Purpose: Returns true if the user is playing the Portal Demo (rather than the full game)
//-----------------------------------------------------------------------------
bool CL_IsPortalDemo()
{
	CL_InitPortalDemoFlag();
	return s_bIsPortalDemo;
}


#ifdef _XBOX
extern void Host_WriteConfiguration( const char *dirname, const char *filename );
//-----------------------------------------------------------------------------
// Convar callback to write the user configuration 
//-----------------------------------------------------------------------------
void WriteConfig_f( ConVar *var, const char *pOldString )
{
	Host_WriteConfiguration( "xboxuser.cfg" );
}
#endif

//-----------------------------------------------------------------------------
// Purpose: If the client is in the process of connecting and the cl.signon hits
//  is complete, make sure the client thinks its totally connected.
//-----------------------------------------------------------------------------
void CL_CheckClientState( void )
{
	// Setup the local network backdoor (we do this each frame so it can be toggled on and off).
	bool useBackdoor = cl_LocalNetworkBackdoor.GetInt() && 
						(cl.m_NetChannel ? cl.m_NetChannel->IsLoopback() : false) &&
						sv.IsActive() &&
						!demorecorder->IsRecording() &&
						!demoplayer->IsPlayingBack() &&
						Host_IsSinglePlayerGame();

	CL_SetupLocalNetworkBackDoor( useBackdoor );
}



//-----------------------------------------------------------------------------
// bool CL_CheckCRCs( const char *pszMap )
//-----------------------------------------------------------------------------
bool CL_CheckCRCs( const char *pszMap )
{
	CRC32_t mapCRC;        // If this is the worldmap, CRC against server's map
	MD5Value_t mapMD5;
	V_memset( mapMD5.bits, 0, MD5_DIGEST_LENGTH );

	// Don't verify CRC if we are running a local server (i.e., we are playing single player, or we are the server in multiplay
	if ( sv.IsActive() ) // Single player
		return true;

	if ( IsX360() )
	{
		return true;
	}

	bool couldHash = false;
	if ( g_ClientGlobalVariables.network_protocol > PROTOCOL_VERSION_17 )
	{
		couldHash = MD5_MapFile( &mapMD5, pszMap );
	}
	else
	{
		CRC32_Init(&mapCRC);
		couldHash = CRC_MapFile( &mapCRC, pszMap );
	}

	if (!couldHash )
	{
		// Does the file exist?
		FileHandle_t fp = 0;
		int nSize = -1;

		nSize = COM_OpenFile( pszMap, &fp );
		if ( fp )
			g_pFileSystem->Close( fp );

		if ( nSize != -1 )
		{
			COM_ExplainDisconnection( true, "Couldn't CRC map %s, disconnecting\n", pszMap);
			Host_Error( "Bad map" );
		}
		else
		{
			COM_ExplainDisconnection( true, "Missing map %s,  disconnecting\n", pszMap);
			Host_Error( "Map is missing" );
		}

		return false;
	}

	bool hashValid = false;
	if ( g_ClientGlobalVariables.network_protocol > PROTOCOL_VERSION_17 )
	{
		hashValid = MD5_Compare( cl.serverMD5, mapMD5 );
	}

	// Hacked map
	if ( !hashValid && !demoplayer->IsPlayingBack())
	{
		if ( IsX360() )
		{
			Warning( "Disconnect: BSP CRC failed!\n" );
		}
		COM_ExplainDisconnection( true, "Your map [%s] differs from the server's.\n", pszMap );
		Host_Error( "Client's map differs from the server's" );
		return false;
	}

	return true;
}

//-----------------------------------------------------------------------------
// Purpose: 
// Input  : nMaxClients - 
//-----------------------------------------------------------------------------
void CL_ReallocateDynamicData( int maxclients )
{
	Assert( entitylist );
	if ( entitylist )
	{
		entitylist->SetMaxEntities( MAX_EDICTS );
	}
}

/*
=================
CL_ReadPackets

Updates the local time and reads/handles messages on client net connection.
=================
*/

extern ConVar cl_clock_correction;

void CL_ReadPackets ( bool bFinalTick )
{
	VPROF_BUDGET( "CL_ReadPackets", VPROF_BUDGETGROUP_OTHER_NETWORKING );
	tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s", __FUNCTION__ );

	if ( !Host_ShouldRun() )
		return;

	// update client times/tick
	if (cl_clock_correction.GetInt() <= 1)
	{
		cl.oldtickcount = cl.GetServerTickCount();
		if ( !cl.IsPaused() )
		{
			cl.SetClientTickCount( cl.GetClientTickCount() + 1 );
			
			// While clock correction is off, we have the old behavior of matching the client and server clocks.
			if ( !CClockDriftMgr::IsClockCorrectionEnabled() )
				cl.SetServerTickCount( cl.GetClientTickCount() );

			g_ClientGlobalVariables.tickcount = cl.GetClientTickCount();
			g_ClientGlobalVariables.curtime = cl.GetTime();
		}
		// 0 or tick_rate if simulating
		g_ClientGlobalVariables.frametime = cl.GetFrameTime();
	}

	// read packets, if any in queue
	if ( demoplayer->IsPlayingBack() && cl.m_NetChannel )
	{
		tmZoneFiltered( TELEMETRY_LEVEL0, 50, TMZF_NONE, "ReadPacket" );

		// process data from demo file
		cl.m_NetChannel->ProcessPlayback();
	}
	else
	{
		if ( !cl_ignorepackets.GetInt() )
		{
			tmZoneFiltered( TELEMETRY_LEVEL0, 50, TMZF_NONE, "ProcessSocket" );
			// process data from net socket
			NET_ProcessSocket( NS_CLIENT, &cl );
		}
	}

	// check timeout, but not if running _DEBUG engine
#if !defined( _DEBUG )
	// Only check on final frame because that's when the server might send us a packet in single player.  This avoids
	//  a bug where if you sit in the game code in the debugger then you get a timeout here on resuming the engine
	//  because the timestep is > 1 tick because of the debugging delay but the server hasn't sent the next packet yet.  ywb 9/5/03
	if ( (cl.m_NetChannel?cl.m_NetChannel->IsTimedOut():false) &&
		 bFinalTick &&
		 !demoplayer->IsPlayingBack() &&
		 cl.IsConnected() )
	{
		ConMsg ("\nServer connection timed out.\n");

		// Show the vgui dialog on timeout
		COM_ExplainDisconnection( false, "Lost connection to server.");
		if ( IsPC() )
		{
			EngineVGui()->ShowErrorMessage();
		}

		Host_Disconnect( true, "Lost connection" );
		return;
	}
#endif

    if (cl_clock_correction.GetInt() >= 2)
    {
		if (CClockDriftMgr::IsClockCorrectionEnabled())
		{
			cl.oldtickcount = cl.GetServerTickCount();
		}

		// Moved after process socket so we can receive the lastest updates.
		if ( !cl.IsPaused() )
		{
			// While clock correction is off, we have the old behavior of matching the client and server clocks.
			if (CClockDriftMgr::IsClockCorrectionEnabled())
			{
				cl.m_ClockDriftMgr.IncrementCachedTickCount(bFinalTick);
				g_ClientGlobalVariables.tickcount = cl.GetClientTickCount();
				g_ClientGlobalVariables.curtime = cl.GetTime();
			}
		}

		if (CClockDriftMgr::IsClockCorrectionEnabled())
		{
			// 0 or tick_rate if simulating
			g_ClientGlobalVariables.frametime = cl.GetFrameTime();
		}
    }
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CL_ClearState ( void )
{
	// clear out the current whitelist
	IFileList *pFilesToReload = NULL;
	CL_HandlePureServerWhitelist( NULL, pFilesToReload );
	CL_ReloadFilesInList( pFilesToReload );

	CL_ResetEntityBits();

	R_UnloadSkys();

	// clear decal index directories
	Decal_Init();

	StaticPropMgr()->LevelShutdownClient();

	// shutdown this level in the client DLL
	if ( g_ClientDLL )
	{
		if ( host_state.worldmodel )
		{
			char mapname[256];
			CL_SetupMapName( modelloader->GetName( host_state.worldmodel ), mapname, sizeof( mapname ) );
			phonehome->Message( IPhoneHome::PHONE_MSG_MAPEND, mapname );
		}
		audiosourcecache->LevelShutdown();
		g_ClientDLL->LevelShutdown();
	}

	R_LevelShutdown();
	if ( IsX360() )
	{
		// Reset material system temporary memory (frees up memory for map loading)
		bool bOnLevelShutdown = true;
		materials->ResetTempHWMemory( bOnLevelShutdown );
	}
	
	if ( g_pLocalNetworkBackdoor )
		g_pLocalNetworkBackdoor->ClearState();

	// clear other arrays	
	memset (cl_dlights, 0, sizeof(cl_dlights));
	memset (cl_elights, 0, sizeof(cl_elights));

	// Wipe the hunk ( unless the server is active )
	Host_FreeStateAndWorld( false );
	Host_FreeToLowMark( false );

	PhonemeMP3Shutdown();

	// Wipe the remainder of the structure.
	cl.Clear();
}

//-----------------------------------------------------------------------------
// Purpose: Used for sorting sounds
// Input  : &sound1 - 
//			&sound2 - 
// Output : static bool
//-----------------------------------------------------------------------------
static bool CL_SoundMessageLessFunc( SoundInfo_t const &sound1, SoundInfo_t const &sound2 )
{
	return sound1.nSequenceNumber < sound2.nSequenceNumber;
}

static CUtlRBTree< SoundInfo_t, int > g_SoundMessages( 0, 0, CL_SoundMessageLessFunc );
extern ConVar snd_show;

//-----------------------------------------------------------------------------
// Purpose: Add sound to queue
// Input  : sound - 
//-----------------------------------------------------------------------------
void CL_AddSound( const SoundInfo_t &sound )
{
	g_SoundMessages.Insert( sound );
}

//-----------------------------------------------------------------------------
// Purpose: Play sound packet
// Input  : sound - 
//-----------------------------------------------------------------------------
void CL_DispatchSound( const SoundInfo_t &sound )
{
	int nSoundNum = sound.nSoundNum;

	CSfxTable *pSfx;

	char name[ MAX_QPATH ];

	name[ 0 ] = 0;
	if ( sound.bIsSentence )
	{
		// make dummy sfx for sentences
		const char *pSentenceName = VOX_SentenceNameFromIndex( sound.nSoundNum );
		if ( !pSentenceName )
		{
			pSentenceName = "";
		}

		V_snprintf( name, sizeof( name ), "%c%s", CHAR_SENTENCE, pSentenceName );		
		pSfx = S_DummySfx( name );
	}
	else
	{
		V_strncpy( name, cl.GetSoundName( sound.nSoundNum ), sizeof( name ) );

		const char *pchTranslatedName = g_ClientDLL->TranslateEffectForVisionFilter( "sounds", name );
		if ( V_strcmp( pchTranslatedName, name ) != 0 )
		{
			V_strncpy( name, pchTranslatedName, sizeof( name ) );
			nSoundNum = cl.LookupSoundIndex( name );
		}

		pSfx = cl.GetSound( nSoundNum );
	}

	if ( snd_show.GetInt() >= 2 )
	{
		DevMsg( "%i (seq %i) %s : src %d : ch %d : %d dB : vol %.2f : time %.3f (%.4f delay) @%.1f %.1f %.1f\n", 
			host_framecount,
			sound.nSequenceNumber,
			name, 
			sound.nEntityIndex, 
			sound.nChannel, 
			sound.Soundlevel, 
			sound.fVolume, 
			cl.GetTime(),
			sound.fDelay,
			sound.vOrigin.x,
			sound.vOrigin.y,
			sound.vOrigin.z );
	}

	StartSoundParams_t params;
	params.staticsound = (sound.nChannel == CHAN_STATIC) ? true : false;
	params.soundsource = sound.nEntityIndex;
	params.entchannel = params.staticsound ? CHAN_STATIC : sound.nChannel;
	params.pSfx = pSfx;
	params.origin = sound.vOrigin;
	params.fvol = sound.fVolume;
	params.soundlevel = sound.Soundlevel;
	params.flags = sound.nFlags;
	params.pitch = sound.nPitch;
	params.specialdsp = sound.nSpecialDSP;
	params.fromserver = true;
	params.delay = sound.fDelay;
	// we always want to do this when this flag is set - even if the delay is zero we need to precisely
	// schedule this sound
	if ( sound.nFlags & SND_DELAY )
	{
		// anything adjusted less than 100ms forward was probably scheduled this frame
		if ( sound.fDelay > -0.100f )
		{
			float soundtime = cl.m_flLastServerTickTime + sound.fDelay;
			// this adjusts for host_thread_mode or any other cases where we're running more than one
			// tick at a time, but we get network updates on the first tick
			soundtime -= ((g_ClientGlobalVariables.simTicksThisFrame-1) * host_state.interval_per_tick);
			// this sound was networked over from the server, use server clock
			params.delay = S_ComputeDelayForSoundtime( soundtime, CLOCK_SYNC_SERVER );
#if 0
			static float lastSoundTime = 0;
			Msg("[%.3f] Play %s at %.3f %.1fsms delay\n", soundtime - lastSoundTime, name, soundtime, params.delay * 1000.0f );
			lastSoundTime = soundtime;
#endif
			if ( params.delay <= 0 )
			{
				// leave a little delay to flag the channel in the low-level sound system
				params.delay = 1e-6f;
			}
		}
		else
		{
			params.delay = sound.fDelay;
		}
	}
	params.speakerentity = sound.nSpeakerEntity;

	// Give the client DLL a chance to run arbitrary code to affect the sound parameters before we
	// play.
	g_ClientDLL->ClientAdjustStartSoundParams( params );

	if ( params.staticsound )
	{
		S_StartSound( params );
	}
	else
	{
		// Don't actually play non-static sounds if playing a demo and skipping ahead
		// but always stop sounds
		if ( demoplayer->IsSkipping() && !(sound.nFlags&SND_STOP) )
		{
			return;
		}
		S_StartSound( params );
	}
}

//-----------------------------------------------------------------------------
// Purpose: Called after reading network messages to play sounds encoded in the network packet
//-----------------------------------------------------------------------------
void CL_DispatchSounds( void )
{
	int i;
	// Walk list in sequence order
	i = g_SoundMessages.FirstInorder();
	while ( i != g_SoundMessages.InvalidIndex() )
	{
		SoundInfo_t const *msg = &g_SoundMessages[ i ];
		Assert( msg );
		if ( msg )
		{
			// Play the sound
			CL_DispatchSound( *msg );
		}
		i = g_SoundMessages.NextInorder( i );
	}

	// Reset the queue each time we empty it!!!
	g_SoundMessages.RemoveAll();
}


//-----------------------------------------------------------------------------
// Retry last connection (e.g., after we enter a password)
//-----------------------------------------------------------------------------
void CL_Retry()
{
	if ( !cl.m_szRetryAddress[ 0 ] )
	{
		ConMsg( "Can't retry, no previous connection\n" );
		return;
	}

	// Check that we can add the two execution markers
	bool bCanAddExecutionMarkers = Cbuf_HasRoomForExecutionMarkers( 2 );

	ConMsg( "Commencing connection retry to %s\n", cl.m_szRetryAddress );

	// We need to temporarily disable this execution marker so the connect command succeeds if it was executed by the server.
	// We would still need this even if we called CL_Connect directly because the connect process may execute commands which we want to succeed.
	const char *pszCommand = va( "connect %s %s\n", cl.m_szRetryAddress, cl.m_sRetrySourceTag.String() );
	if ( cl.m_bRestrictServerCommands && bCanAddExecutionMarkers )
		Cbuf_AddTextWithMarkers( eCmdExecutionMarker_Disable_FCVAR_SERVER_CAN_EXECUTE, pszCommand, eCmdExecutionMarker_Enable_FCVAR_SERVER_CAN_EXECUTE );
	else
		Cbuf_AddText( pszCommand );
}

CON_COMMAND_F( retry, "Retry connection to last server.", FCVAR_DONTRECORD | FCVAR_SERVER_CAN_EXECUTE | FCVAR_CLIENTCMD_CAN_EXECUTE )
{
	CL_Retry();
}


/*
=====================
CL_Connect_f

User command to connect to server
=====================
*/

void CL_Connect( const char *address, const char *pszSourceTag )
{
	// If it's not a single player connection to "localhost", initialize networking & stop listenserver
	if ( Q_strncmp( address, "localhost", 9 ) )
	{
		Host_Disconnect(false);	

		// allow remote
		NET_SetMutiplayer( true );		

		// start progress bar immediately for remote connection
		EngineVGui()->EnabledProgressBarForNextLoad();

		SCR_BeginLoadingPlaque();

		EngineVGui()->UpdateProgressBar(PROGRESS_BEGINCONNECT);
	}
	else
	{
		// we are connecting/reconnecting to local game
		// so don't stop listenserver 
		cl.Disconnect( "Connecting to local host", false );
	}

	// This happens as part of the load process anyway, but on slower systems it causes the server to timeout the
	// connection.  Use the opportunity to flush anything before starting a new connection.
	UpdateMaterialSystemConfig();

	cl.Connect( address, pszSourceTag );

	// Reset error conditions
	gfExtendedError = false;
}

CON_COMMAND_F( connect, "Connect to specified server.", FCVAR_DONTRECORD )
{
	// Default command processing considers ':' a command separator,
	// and we donly want spaces to count.  So we'll need to re-split the arg string
	CUtlVector<char*> vecArgs;
	V_SplitString( args.ArgS(), " ", vecArgs );

	// How many arguments?
	if ( vecArgs.Count() == 1  )
	{
		CL_Connect( vecArgs[0], "" );
	}
	else if ( vecArgs.Count() == 2 )
	{
		CL_Connect( vecArgs[0], vecArgs[1] );
	}
	else
	{
		ConMsg( "Usage:  connect <server>\n" );
	}
	vecArgs.PurgeAndDeleteElementsArray();
}

CON_COMMAND_F( redirect, "Redirect client to specified server.", FCVAR_DONTRECORD | FCVAR_SERVER_CAN_EXECUTE )
{
	if ( !CBaseClientState::ConnectMethodAllowsRedirects() )
	{
		ConMsg( "redirect: Current connection method does not allow silent redirects.\n");
		return;
	}

	// Default command processing considers ':' a command separator,
	// and we donly want spaces to count.  So we'll need to re-split the arg string
	CUtlVector<char*> vecArgs;
	V_SplitString( args.ArgS(), " ", vecArgs );

	if ( vecArgs.Count() == 1  )
	{
		CL_Connect( vecArgs[0], "redirect" );
	}
	else
	{
		ConMsg( "Usage:  redirect <server>\n" );
	}
	vecArgs.PurgeAndDeleteElements();
}

//-----------------------------------------------------------------------------
// Takes the map name, strips path and extension
//-----------------------------------------------------------------------------
void CL_SetupMapName( const char* pName, char* pFixedName, int maxlen )
{
	const char* pSlash = strrchr( pName, '\\' );
	const char* pSlash2 = strrchr( pName, '/' );
	if (pSlash2 > pSlash)
		pSlash = pSlash2;
	if (pSlash)
		++pSlash;
	else 
		pSlash = pName;

	Q_strncpy( pFixedName, pSlash, maxlen );
	char* pExt = strchr( pFixedName, '.' );
	if (pExt)
		*pExt = 0;
}

CPureServerWhitelist* CL_LoadWhitelist( INetworkStringTable *pTable, const char *pName )
{
	// If there is no entry for the pure server whitelist, then sv_pure is off and the client can do whatever it wants.
	int iString = pTable->FindStringIndex( pName );
	if ( iString == INVALID_STRING_INDEX )
		return NULL;

	int dataLen; 
	const void *pData = pTable->GetStringUserData( iString, &dataLen );
	if ( pData )
	{
		CUtlBuffer buf( pData, dataLen, CUtlBuffer::READ_ONLY );
		
		CPureServerWhitelist *pWhitelist = CPureServerWhitelist::Create( g_pFullFileSystem );
		pWhitelist->Decode( buf );
		return pWhitelist;
	}
	else
	{
		return NULL;
	}
}


void CL_CheckForPureServerWhitelist( /* out */ IFileList *&pFilesToReload )
{
#ifdef DISABLE_PURE_SERVER_STUFF
	return;
#endif

	// Don't do sv_pure stuff in SP games or HLTV/replay
	if ( cl.m_nMaxClients <= 1 || cl.ishltv || demoplayer->IsPlayingBack()
#ifdef REPLAY_ENABLED
		|| cl.isreplay
#endif // ifdef REPLAY_ENABLED
		)
		return;
	
	CPureServerWhitelist *pWhitelist = NULL;
	if ( cl.m_pServerStartupTable )
		pWhitelist = CL_LoadWhitelist( cl.m_pServerStartupTable, "PureServerWhitelist" );
		
	PrintSvPureWhitelistClassification( pWhitelist );
	CL_HandlePureServerWhitelist( pWhitelist, pFilesToReload );
	if ( pWhitelist )
	{
		pWhitelist->Release();
	}
}

int CL_GetServerQueryPort()
{
	// Yes, this is ugly getting this data out of a string table. Would be better to have it in our network protocol,
	// but we don't have a way to change the protocol without breaking things for people.
	if ( !cl.m_pServerStartupTable )
		return 0;
		
	int iString = cl.m_pServerStartupTable->FindStringIndex( "QueryPort" );
	if ( iString == INVALID_STRING_INDEX )
		return 0;
		
	int dataLen; 
	const void *pData = cl.m_pServerStartupTable->GetStringUserData( iString, &dataLen );
	if ( pData && dataLen == sizeof( int ) )
		return *((const int*)pData);
	else
		return 0;
}

/*
==================
CL_RegisterResources

Clean up and move to next part of sequence.
==================
*/
void CL_RegisterResources( void )
{
	// All done precaching.
	host_state.SetWorldModel( cl.GetModel( 1 ) );
	if ( !host_state.worldmodel )
	{
		Host_Error( "CL_RegisterResources:  host_state.worldmodel/cl.GetModel( 1 )==NULL\n" );
	}

	// Force main window to repaint... (only does something if running shaderapi
	videomode->InvalidateWindow();
}

void CL_FullyConnected( void )
{
	CETWScope timer( "CL_FullyConnected" );

	EngineVGui()->UpdateProgressBar( PROGRESS_FULLYCONNECTED );

	// This has to happen here, in phase 3, because it is in this phase
	// that raycasts against the world is supported (owing to the fact
	// that the world entity has been created by this point)
	StaticPropMgr()->LevelInitClient();

	if ( IsX360() )
	{
		// Notify the loader the end of the loading context, preloads are about to be purged
		g_pQueuedLoader->EndMapLoading( false );
	}

	// flush client-side dynamic models that have no refcount
	modelloader->FlushDynamicModels();

	// loading completed
	// can NOW safely purge unused models and their data hierarchy (materials, shaders, etc)
	modelloader->PurgeUnusedModels();

	// Purge the preload stores, oreder is critical
	g_pMDLCache->ShutdownPreloadData();

	// NOTE: purposely disabling for singleplayer, memory spike causing issues, preload's stay in
	// UNDONE: discard preload for TF to save memory
	// g_pFileSystem->DiscardPreloadData();

	// We initialize this list before the load, but don't perform reloads until after flushes have happened to avoid
	// unnecessary reloads of items that wont be used on this map.
	if ( cl.m_pPendingPureFileReloads )
	{
		CL_ReloadFilesInList( cl.m_pPendingPureFileReloads );
		cl.m_pPendingPureFileReloads->Release();
		cl.m_pPendingPureFileReloads = NULL;
	}

	// ***************************************************************
	// NO MORE PRELOAD DATA AVAILABLE PAST THIS POINT!!!
	// ***************************************************************

 	g_ClientDLL->LevelInitPostEntity();

	// communicate to tracker that we're in a game
	int ip = cl.m_NetChannel->GetRemoteAddress().GetIPNetworkByteOrder();
	short port = cl.m_NetChannel->GetRemoteAddress().GetPort();
	if (!port)
	{
		ip = net_local_adr.GetIPNetworkByteOrder();
		port = net_local_adr.GetPort();
	}

	int iQueryPort = CL_GetServerQueryPort();
	EngineVGui()->NotifyOfServerConnect(com_gamedir, ip, port, iQueryPort);

	GetTestScriptMgr()->CheckPoint( "FinishedMapLoad" );

	EngineVGui()->UpdateProgressBar( PROGRESS_READYTOPLAY );

	if ( !IsX360() || cl.m_nMaxClients == 1 )
	{
		// Need this to persist for multiplayer respawns, 360 can't reload
		CM_DiscardEntityString();
	}

	g_pMDLCache->EndMapLoad();

#if defined( _MEMTEST )
	Cbuf_AddText( "mem_dump\n" );
#endif

	if ( developer.GetInt() > 0 )
	{
		ConDMsg( "Signon traffic \"%s\":  incoming %s, outgoing %s\n",
			cl.m_NetChannel->GetName(),
			Q_pretifymem( cl.m_NetChannel->GetTotalData( FLOW_INCOMING ), 3 ),
			Q_pretifymem( cl.m_NetChannel->GetTotalData( FLOW_OUTGOING ), 3 ) );
	}

	if ( IsX360() )
	{
		// Reset material system temporary memory (once loading is complete), ready for in-map use
		bool bOnLevelShutdown = false;
		materials->ResetTempHWMemory( bOnLevelShutdown );
	}

	// allow normal screen updates
	SCR_EndLoadingPlaque();
	EndLoadingUpdates();

	// FIXME: Please oh please move this out of this spot...
	// It so does not belong here. Instead, we want some phase of the
	// client DLL where it knows its read in all entities
	if ( IsPC() )
	{
		int i;
		if( (i = CommandLine()->FindParm( "-buildcubemaps" )) != 0 )
		{
			int numIterations = 1;
			if( CommandLine()->ParmCount() > i + 1 )
			{
				numIterations = atoi( CommandLine()->GetParm(i+1) );
			}
			if( numIterations == 0 )
			{
				numIterations = 1;
			}
			char cmd[1024] = { 0 };
			V_snprintf( cmd, sizeof( cmd ), "buildcubemaps %u\nquit\n", numIterations );
			Cbuf_AddText( cmd );
		}
		else if( CommandLine()->FindParm( "-navanalyze" ) )
		{
			Cbuf_AddText( "nav_edit 1;nav_analyze_scripted\n" );
		}
		else if( CommandLine()->FindParm( "-navforceanalyze" ) )
		{
			Cbuf_AddText( "nav_edit 1;nav_analyze_scripted force\n" );
		}
		else if ( CommandLine()->FindParm("-exit") )
		{
			Cbuf_AddText( "quit\n" );
		}
	}

	// background maps are for main menu UI, QMS not needed or used, easier context
	if ( !engineClient->IsLevelMainMenuBackground() )
	{
		// map load complete, safe to allow QMS
		Host_AllowQueuedMaterialSystem( true );
	}

	// This is a Hack, but we need to suppress rendering for a bit in single player to let values settle on the client
	if ( (cl.m_nMaxClients == 1) && !demoplayer->IsPlayingBack() )
	{
		scr_nextdrawtick = host_tickcount + TIME_TO_TICKS( 0.25f );
	}

#ifdef _X360
	// At this point, check for a valid controller connection.  If it's been lost, then we need to pop our game UI up
	XINPUT_CAPABILITIES caps;
	if ( XInputGetCapabilities( XBX_GetPrimaryUserId(), XINPUT_FLAG_GAMEPAD, &caps ) == ERROR_DEVICE_NOT_CONNECTED )
	{
		EngineVGui()->ActivateGameUI();
	}
#endif // _X360

	// Now that we're connected, toggle the clan tag so it gets sent to the server
	int id = cl_clanid.GetInt();
	cl_clanid.SetValue( 0 );
	cl_clanid.SetValue( id );

	MemAlloc_CompactHeap();

	extern double g_flAccumulatedModelLoadTime;
	extern double g_flAccumulatedSoundLoadTime;
	extern double g_flAccumulatedModelLoadTimeStudio;
	extern double g_flAccumulatedModelLoadTimeVCollideSync;
	extern double g_flAccumulatedModelLoadTimeVCollideAsync;
	extern double g_flAccumulatedModelLoadTimeVirtualModel;
	extern double g_flAccumulatedModelLoadTimeStaticMesh;
	extern double g_flAccumulatedModelLoadTimeBrush;
	extern double g_flAccumulatedModelLoadTimeSprite;
	extern double g_flAccumulatedModelLoadTimeMaterialNamesOnly;
//	extern double g_flLoadStudioHdr;

	COM_TimestampedLog( "Sound Loading time %.4f", g_flAccumulatedSoundLoadTime );
	COM_TimestampedLog( "Model Loading time %.4f", g_flAccumulatedModelLoadTime );
	COM_TimestampedLog( "  Model Loading time studio %.4f", g_flAccumulatedModelLoadTimeStudio );
	COM_TimestampedLog( "    Model Loading time GetVCollide %.4f -sync", g_flAccumulatedModelLoadTimeVCollideSync );
	COM_TimestampedLog( "    Model Loading time GetVCollide %.4f -async", g_flAccumulatedModelLoadTimeVCollideAsync );
	COM_TimestampedLog( "    Model Loading time GetVirtualModel %.4f", g_flAccumulatedModelLoadTimeVirtualModel );
	COM_TimestampedLog( "    Model loading time Mod_GetModelMaterials only %.4f", g_flAccumulatedModelLoadTimeMaterialNamesOnly );
	COM_TimestampedLog( "  Model Loading time world %.4f", g_flAccumulatedModelLoadTimeBrush );
	COM_TimestampedLog( "  Model Loading time sprites %.4f", g_flAccumulatedModelLoadTimeSprite );
	COM_TimestampedLog( "  Model Loading time meshes %.4f", g_flAccumulatedModelLoadTimeStaticMesh );
//	COM_TimestampedLog( "    Model Loading time meshes studiohdr load %.4f", g_flLoadStudioHdr );

	COM_TimestampedLog( "*** Map Load Complete" );

	float map_loadtime_start = dev_loadtime_map_start.GetFloat();
	if (map_loadtime_start > 0.0)
	{
		float elapsed = Plat_FloatTime() - map_loadtime_start;
		dev_loadtime_map_elapsed.SetValue( elapsed );

		// Clear this for next time so we know we did.
		dev_loadtime_map_start.SetValue( 0.0f );
	}
}


/*
=====================
CL_NextDemo

Called to play the next demo in the demo loop
=====================
*/
void CL_NextDemo (void)
{
	char	str[1024];

	if (cl.demonum == -1)
		return;		// don't play demos

	SCR_BeginLoadingPlaque ();

	if ( cl.demos[cl.demonum].IsEmpty() || cl.demonum == MAX_DEMOS )
	{
		cl.demonum = 0;
		if ( cl.demos[cl.demonum].IsEmpty() )
		{
			scr_disabled_for_loading = false;

			ConMsg ("No demos listed with startdemos\n");
			cl.demonum = -1;
			return;
		}
		else if ( !demoplayer->ShouldLoopDemos() )
		{
			cl.demonum = -1;
			scr_disabled_for_loading = false;
			Host_Disconnect( true );

			demoplayer->OnLastDemoInLoopPlayed();

			return;
		}
	}

	Q_snprintf (str,sizeof( str ), "%s %s", CommandLine()->FindParm("-timedemoloop") ? "timedemo" : "playdemo", cl.demos[cl.demonum].Get());
	Cbuf_AddText (str);
	cl.demonum++;
}

ConVar cl_screenshotname( "cl_screenshotname", "", 0, "Custom Screenshot name" );

// We'll take a snapshot at the next available opportunity
void CL_TakeScreenshot(const char *name)
{
	cl_takesnapshot = true;
	cl_takejpeg = false;
	cl_takesnapshot_internal = false;

	if ( name != NULL )
	{
		Q_strncpy( cl_snapshotname, name, sizeof( cl_snapshotname ) );		
	}
	else
	{
		cl_snapshotname[0] = 0;

		if ( Q_strlen( cl_screenshotname.GetString() ) > 0 )
		{
			Q_snprintf( cl_snapshotname, sizeof( cl_snapshotname ), "%s", cl_screenshotname.GetString() );		
		}
	}

	cl_snapshot_subdirname[0] = 0;
}

CON_COMMAND_F( screenshot, "Take a screenshot.", FCVAR_CLIENTCMD_CAN_EXECUTE )
{
	GetTestScriptMgr()->SetWaitCheckPoint( "screenshot" );

	// Don't playback screenshots unless specifically requested.	
	if ( demoplayer->IsPlayingBack() && !cl_playback_screenshots.GetBool() )
		return;

	if( args.ArgC() == 2 )
	{
		CL_TakeScreenshot( args[ 1 ] );
	}
	else
	{
		CL_TakeScreenshot( NULL );
	}
}

CON_COMMAND_F( devshots_screenshot, "Used by the -makedevshots system to take a screenshot. For taking your own screenshots, use the 'screenshot' command instead.", FCVAR_DONTRECORD )
{
	CL_TakeScreenshot( NULL );

	// See if we got a subdirectory to store the devshots in
	if ( args.ArgC() == 2 )
	{
		Q_strncpy( cl_snapshot_subdirname, args[1], sizeof( cl_snapshot_subdirname ) );		

		// Use the first available shot in each subdirectory
		cl_snapshotnum = 0;
	}
}

// We'll take a snapshot at the next available opportunity
void CL_TakeJpeg(const char *name, int quality)
{
	// Don't playback screenshots unless specifically requested.	
	if ( demoplayer->IsPlayingBack() && !cl_playback_screenshots.GetBool() )
		return;
	
	cl_takesnapshot = true;
	cl_takejpeg = true;
	cl_jpegquality = clamp( quality, 1, 100 );
	cl_takesnapshot_internal = false;

	if ( name != NULL )
	{
		Q_strncpy( cl_snapshotname, name, sizeof( cl_snapshotname ) );		
	}
	else
	{
		cl_snapshotname[0] = 0;
	}
}

CON_COMMAND( jpeg, "Take a jpeg screenshot:  jpeg <filename> <quality 1-100>." )
{
	if( args.ArgC() >= 2 )
	{
		if ( args.ArgC() == 3 )
		{
			CL_TakeJpeg( args[ 1 ], Q_atoi( args[2] ) );
		}
		else
		{
			CL_TakeJpeg( args[ 1 ], jpeg_quality.GetInt() );
		}
	}
	else
	{
		CL_TakeJpeg( NULL, jpeg_quality.GetInt() );
	}
}

static void screenshot_internal( const CCommand &args )
{

	if( args.ArgC() != 2 )
	{
		Assert( args.ArgC() >= 2 );
		Warning( "__screenshot_internal - wrong number of arguments" );
		return;
	}
	Q_strncpy( cl_snapshotname, args[1], ARRAYSIZE(cl_snapshotname) );
	cl_takesnapshot = true;
	cl_takejpeg = true;
	cl_jpegquality = 70;
	cl_takesnapshot_internal = true;
}

ConCommand screenshot_internal_command( "__screenshot_internal", screenshot_internal, "Internal command to take a screenshot without renumbering or notifying Steam.", FCVAR_DONTRECORD | FCVAR_HIDDEN );

void CL_TakeSnapshotAndSwap()
{
	tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s", __FUNCTION__ );

	bool bReadPixelsFromFrontBuffer = g_pMaterialSystemHardwareConfig->ReadPixelsFromFrontBuffer();
	if ( bReadPixelsFromFrontBuffer )
	{
		Shader_SwapBuffers();
    }

    if (g_ClientGlobalVariables.client_taking_screenshot
          || g_ServerGlobalVariables.client_taking_screenshot)
    {
        CL_TakeScreenshot(NULL);
        g_ClientGlobalVariables.client_taking_screenshot = false;
        g_ServerGlobalVariables.client_taking_screenshot = false;
	}

	if (cl_takesnapshot)
    {        
		// Disable threading for the duration of the screenshots, because we need to get pointers to the (complete) 
		// back buffer right now.
		bool bEnabled = materials->AllowThreading( false, g_nMaterialSystemThread );

		char base[MAX_OSPATH];
		char filename[MAX_OSPATH];
		IClientEntity *world = entitylist->GetClientEntity( 0 );

		g_pFileSystem->CreateDirHierarchy( "screenshots", "DEFAULT_WRITE_PATH" );

		if ( cl_takesnapshot_internal )
		{

			// !KLUDGE! Don't save this screenshot to steam
			ConVarRef cl_savescreenshotstosteam( "cl_savescreenshotstosteam" );
			bool bSaveValue = cl_savescreenshotstosteam.GetBool();
			cl_savescreenshotstosteam.SetValue( false );

			Q_snprintf( filename, sizeof( filename ), "screenshots/%s.jpg", cl_snapshotname );
			videomode->TakeSnapshotJPEG( filename, cl_jpegquality );

			cl_savescreenshotstosteam.SetValue( bSaveValue );
		}
		else
		{
			if ( world && world->GetModel() )
			{
				Q_FileBase( modelloader->GetName( ( model_t *)world->GetModel() ), base, sizeof( base ) );

				if ( IsX360() )
				{
					// map name has an additional extension
					V_StripExtension( base, base, sizeof( base ) );
				}
			}
			else
			{
				Q_strncpy( base, "Snapshot", sizeof( base ) );
			}

			char extension[MAX_OSPATH];
			Q_snprintf( extension, sizeof( extension ), "%s.%s", GetPlatformExt(), cl_takejpeg ? "jpg" : "tga" );

			// Using a subdir? If so, create it
			if ( cl_snapshot_subdirname[0] )
			{
				Q_snprintf( filename, sizeof( filename ), "screenshots/%s/%s", base, cl_snapshot_subdirname );
				g_pFileSystem->CreateDirHierarchy( filename, "DEFAULT_WRITE_PATH" );
			}

			if ( cl_snapshotname[0] )
			{
				Q_strncpy( base, cl_snapshotname, sizeof( base ) );
				Q_snprintf( filename, sizeof( filename ), "screenshots/%s%s", base, extension );

				int iNumber = 0;
				char renamedfile[MAX_OSPATH];

				while ( 1 )
				{
					Q_snprintf( renamedfile, sizeof( renamedfile ), "screenshots/%s_%04d%s", base, iNumber++, extension );	
					if( !g_pFileSystem->GetFileTime( renamedfile ) )
						break;
				}

				if ( iNumber > 0 && g_pFileSystem->GetFileTime( filename ) )
				{
					g_pFileSystem->RenameFile(filename, renamedfile);
				}

				cl_screenshotname.SetValue( "" );
			}
			else
			{
				while( 1 )
				{
					if ( cl_snapshot_subdirname[0] )
					{
						Q_snprintf( filename, sizeof( filename ), "screenshots/%s/%s/%s%04d%s", base, cl_snapshot_subdirname, base, cl_snapshotnum++, extension  );
					}
					else
					{
						Q_snprintf( filename, sizeof( filename ), "screenshots/%s%04d%s", base, cl_snapshotnum++, extension  );
					}

					if( !g_pFileSystem->GetFileTime( filename ) )
					{
						// woo hoo!  The file doesn't exist already, so use it.
						break;
					}
				}
			}
			if ( cl_takejpeg )
			{
				videomode->TakeSnapshotJPEG( filename, cl_jpegquality );
				g_ServerRemoteAccess.UploadScreenshot( filename );
			}
			else
			{
				videomode->TakeSnapshotTGA( filename );
			}
		}
		cl_takesnapshot = false;
		cl_takesnapshot_internal = false;
		GetTestScriptMgr()->CheckPoint( "screenshot" );

		// Restore threading if it was previously enabled (if it wasn't this will do nothing).
		materials->AllowThreading( bEnabled, g_nMaterialSystemThread );
    }

	// If recording movie and the console is totally up, then write out this frame to movie file.
	if ( cl_movieinfo.IsRecording() && !Con_IsVisible() && !scr_drawloading )
	{
		videomode->WriteMovieFrame( cl_movieinfo );
		++cl_movieinfo.movieframe;
	}

	if( !bReadPixelsFromFrontBuffer )
	{
		Shader_SwapBuffers();
	}

	// take a screenshot for savegames if necessary
	saverestore->UpdateSaveGameScreenshots();

	// take screenshot for bx movie maker
	EngineTool_UpdateScreenshot();
}

static float s_flPreviousHostFramerate = 0;
ConVar cl_simulate_no_quicktime( "cl_simulate_no_quicktime", "0", FCVAR_HIDDEN );
void CL_StartMovie( const char *filename, int flags, int nWidth, int nHeight, float flFrameRate, int nJpegQuality, VideoSystem_t videoSystem )
{
	Assert( g_pVideoRecorder == NULL );

	// StartMove depends on host_framerate not being 0.
	s_flPreviousHostFramerate = host_framerate.GetFloat();
	host_framerate.SetValue( flFrameRate );

	cl_movieinfo.Reset();
	Q_strncpy( cl_movieinfo.moviename, filename, sizeof( cl_movieinfo.moviename ) );
	cl_movieinfo.type = flags;
	cl_movieinfo.jpeg_quality = nJpegQuality;

	bool bSuccess = true;
	if ( cl_movieinfo.DoVideo() || cl_movieinfo.DoVideoSound() )
	{
// HACK:  THIS MUST MATCH snd_device.h.  Should be exposed more cleanly!!!
#define SOUND_DMA_SPEED		44100		// hardware playback rate

		// MGP - switched over to using valve video services from avi
		if ( videoSystem == VideoSystem::NONE && g_pVideo )
		{
			// Find a video system based on features if they didn't specify a specific one.
			VideoSystemFeature_t neededFeatures = VideoSystemFeature::NO_FEATURES;
			if ( cl_movieinfo.DoVideo() )
				neededFeatures |= VideoSystemFeature::ENCODE_VIDEO_TO_FILE;
			if ( cl_movieinfo.DoVideoSound() )
				neededFeatures |= VideoSystemFeature::ENCODE_AUDIO_TO_FILE;
		
			videoSystem = g_pVideo->FindNextSystemWithFeature( neededFeatures );
		}

		if ( !cl_simulate_no_quicktime.GetBool() && g_pVideo && videoSystem != VideoSystem::NONE )
		{
			g_pVideoRecorder = g_pVideo->CreateVideoRecorder( videoSystem );
			if ( g_pVideoRecorder != NULL )
			{
				VideoFrameRate_t theFps;
	 			if ( IsIntegralValue( flFrameRate ) )
	 			{
	 				theFps.SetFPS( RoundFloatToInt( flFrameRate ), false );
	 			}
	 			else if ( IsIntegralValue( flFrameRate * 1001.0f / 1000.0f ) ) // 1001 is the ntsc divisor (30*1000/1001 = 29.97, etc)
	 			{
	 				theFps.SetFPS( RoundFloatToInt( flFrameRate + 0.05f ), true );
	 			}
	 			else
	 			{
					theFps.SetFPS( RoundFloatToInt( flFrameRate ), false );
	 			} 	

				const int nSize = 256;
				CFmtStrN<nSize> fmtFullFilename( "%s%c%s", com_gamedir, CORRECT_PATH_SEPARATOR, filename );

				char szFullFilename[nSize];
				V_FixupPathName( szFullFilename, nSize, fmtFullFilename.Access() );
#ifdef USE_WEBM_FOR_REPLAY
				V_DefaultExtension( szFullFilename, ".webm", sizeof( szFullFilename ) );
#else
				V_DefaultExtension( szFullFilename, ".mp4", sizeof( szFullFilename ) );
#endif

				g_pVideoRecorder->CreateNewMovieFile( szFullFilename, cl_movieinfo.DoVideoSound() );
#ifdef USE_WEBM_FOR_REPLAY
				g_pVideoRecorder->SetMovieVideoParameters( VideoEncodeCodec::WEBM_CODEC, nJpegQuality, nWidth, nHeight, theFps );
#else
				g_pVideoRecorder->SetMovieVideoParameters( VideoEncodeCodec::DEFAULT_CODEC, nJpegQuality, nWidth, nHeight, theFps );
#endif
				
				if ( cl_movieinfo.DoVideo() )
				{
					g_pVideoRecorder->SetMovieSourceImageParameters( VideoEncodeSourceFormat::BGR_24BIT, nWidth, nHeight );
				}
				
				if ( cl_movieinfo.DoVideoSound() )
				{
					g_pVideoRecorder->SetMovieSourceAudioParameters( AudioEncodeSourceFormat::AUDIO_16BIT_PCMStereo, SOUND_DMA_SPEED, AudioEncodeOptions::LIMIT_AUDIO_TRACK_TO_VIDEO_DURATION  );
				}
			}
			else
			{
				bSuccess = false;
			}
		}
		else
		{
			bSuccess = false;
		}
	}

	if ( bSuccess )
	{
		SND_MovieStart();
	}
	else
	{
#ifdef USE_WEBM_FOR_REPLAY
		Warning( "Failed to launch startmovie!\n" );
#else
		Warning( "Failed to launch startmovie!  If you are trying to use h264, please make sure you have QuickTime installed.\n" );
#endif
		CL_EndMovie();
	}
}

void CL_EndMovie()
{
	if ( !CL_IsRecordingMovie() )
		return;

	host_framerate.SetValue( s_flPreviousHostFramerate );
	s_flPreviousHostFramerate = 0.0f;

	SND_MovieEnd();

	if ( g_pVideoRecorder && ( cl_movieinfo.DoVideo() || cl_movieinfo.DoVideoSound() ) )
	{
		g_pVideoRecorder->FinishMovie();
		
		g_pVideo->DestroyVideoRecorder( g_pVideoRecorder );
		g_pVideoRecorder = NULL;
	}
	
	cl_movieinfo.Reset();
}

bool CL_IsRecordingMovie()
{
	return cl_movieinfo.IsRecording();
}

/*
===============
CL_StartMovie_f

Sets the engine up to dump frames
===============
*/

CON_COMMAND_F( startmovie, "Start recording movie frames.", FCVAR_DONTRECORD )
{
	if ( cmd_source != src_command )
		return;

	if( args.ArgC() < 2 )
	{
		ConMsg( "startmovie <filename>\n [\n" );
		ConMsg( " (default = TGAs + .wav file)\n" );
#ifdef USE_WEBM_FOR_REPLAY
		ConMsg( " webm = WebM encoded audio and video\n" );
#else
		ConMsg( " h264 = H.264-encoded audio and video (must have QuickTime installed!)\n" );
#endif
		ConMsg( " raw = TGAs + .wav file, same as default\n" );
		ConMsg( " tga = TGAs\n" );
		ConMsg( " jpg/jpeg = JPegs\n" );
		ConMsg( " wav = Write .wav audio file\n" );
		ConMsg( " jpeg_quality nnn = set jpeq quality to nnn (range 1 to 100), default %d\n", DEFAULT_JPEG_QUALITY );
		ConMsg( " ]\n" );
		ConMsg( "examples:\n" );
		ConMsg( "   startmovie testmovie jpg wav jpeg_qality 75\n" );
#ifdef USE_WEBM_FOR_REPLAY
		ConMsg( "   startmovie testmovie webm\n" );
#else
		ConMsg( "   startmovie testmovie h264   <--- requires QuickTime\n" );
		ConMsg( "AVI is no longer supported.\n" );
#endif
		return;
	}

	if ( CL_IsRecordingMovie() )
	{
		ConMsg( "Already recording movie!\n" );
		return;
	}

	int flags = MovieInfo_t::FMOVIE_TGA | MovieInfo_t::FMOVIE_WAV;
	VideoSystem_t videoSystem = VideoSystem::NONE;
	int nJpegQuality = DEFAULT_JPEG_QUALITY;

	if ( args.ArgC() > 2 )
	{
		flags = 0;
		for ( int i = 2; i < args.ArgC(); ++i )
		{
			if ( !Q_stricmp( args[ i ], "avi" ) )
			{
				//flags |= MovieInfo_t::FMOVIE_VID | MovieInfo_t::FMOVIE_VIDSOUND;
				//videoSystem = VideoSystem::AVI;
#ifdef USE_WEBM_FOR_REPLAY
				Warning( "AVI is not supported on this platform!  Use \"webm\".\n" );
#else
				Warning( "AVI is no longer supported!  Make sure QuickTime is installed and use \"h264\" - if you install QuickTime, you will need to reboot before using startmovie.\n" );
#endif
				return;
			}
#ifdef USE_WEBM_FOR_REPLAY
			else if ( !Q_stricmp( args[ i ], "webm" ) )
			{
				flags |= MovieInfo_t::FMOVIE_VID | MovieInfo_t::FMOVIE_VIDSOUND;
				videoSystem = VideoSystem::WEBM;
			}
			else if ( !Q_stricmp( args[ i ], "h264" ) )
			{
				Warning( "h264 is not supported on this platform!  Use \"webm\".\n" );
				return;
			}
#else
			else if ( !Q_stricmp( args[ i ], "h264" ) )
			{
				flags |= MovieInfo_t::FMOVIE_VID | MovieInfo_t::FMOVIE_VIDSOUND;
				videoSystem = VideoSystem::QUICKTIME;
			}
			else if ( !Q_stricmp( args[ i ], "webm" ) )
			{
				Warning( "WebM is not supported on this platform!  Make sure QuickTime is installed and use \"h264\" - if you install QuickTime, you will need to reboot before using startmovie.\n" );
				return;
			}
#endif
			if ( !Q_stricmp( args[ i ], "raw" ) )
			{
				flags |= MovieInfo_t::FMOVIE_TGA | MovieInfo_t::FMOVIE_WAV;
			}
			if ( !Q_stricmp( args[ i ], "tga" ) )
			{
				flags |= MovieInfo_t::FMOVIE_TGA;
			}
			if ( !Q_stricmp( args[ i ], "jpeg" ) || !Q_stricmp( args[ i ], "jpg" ) )
			{
				flags &= ~MovieInfo_t::FMOVIE_TGA;
				flags |= MovieInfo_t::FMOVIE_JPG;
			}
			if ( !Q_stricmp( args[ i ], "jpeg_quality" ) )
			{
				nJpegQuality = clamp( Q_atoi( args[ ++i ] ), 1, 100 );
			}
			if ( !Q_stricmp( args[ i ], "wav" ) )
			{
				flags |= MovieInfo_t::FMOVIE_WAV;
			}
			
		}
	}

	if ( flags == 0 )
	{
#ifdef USE_WEBM_FOR_REPLAY
		Warning( "Missing or unknown recording types, must specify one or both of 'webm' or 'raw'\n" );
#else
		Warning( "Missing or unknown recording types, must specify one or both of 'h264' or 'raw'\n" );
#endif
		return;
	}

	float flFrameRate = host_framerate.GetFloat();
	if ( flFrameRate == 0.0f )
	{
		flFrameRate = 30.0f;
	}
	CL_StartMovie( args[ 1 ], flags, videomode->GetModeStereoWidth(), videomode->GetModeStereoHeight(), flFrameRate, nJpegQuality, videoSystem );
	ConMsg( "Started recording movie, frames will record after console is cleared...\n" );
}


//-----------------------------------------------------------------------------
// Ends frame dumping
//-----------------------------------------------------------------------------
CON_COMMAND_F( endmovie, "Stop recording movie frames.", FCVAR_DONTRECORD )
{
	if( !CL_IsRecordingMovie() )
	{
		ConMsg( "No movie started.\n" );
	}
	else
	{
		CL_EndMovie();
		ConMsg( "Stopped recording movie...\n" );
	}
}

/*
=====================
CL_Rcon_f

  Send the rest of the command line over as
  an unconnected command.
=====================
*/
CON_COMMAND_F( rcon, "Issue an rcon command.", FCVAR_DONTRECORD )
{
	char	message[1024];   // Command message
	char    szParam[ 256 ];
	message[0] = 0;
	for (int i=1 ; i<args.ArgC() ; i++)
	{
		const char *pParam = args[i];
		// put quotes around empty arguments so we can pass things like this: rcon sv_password ""
		// otherwise the "" on the end is lost
		if ( strchr( pParam, ' ' ) || ( Q_strlen( pParam ) == 0 ) )
		{
			Q_snprintf( szParam, sizeof( szParam ), "\"%s\"", pParam );
			Q_strncat( message, szParam, sizeof( message ), COPY_ALL_CHARACTERS );
		}
		else
		{
			Q_strncat( message, pParam, sizeof( message ), COPY_ALL_CHARACTERS );
		}
		if ( i != ( args.ArgC() - 1 ) )
		{
			Q_strncat (message, " ", sizeof( message ), COPY_ALL_CHARACTERS);
		}
	}

	RCONClient().SendCmd( message );
}


CON_COMMAND_F( box, "Draw a debug box.", FCVAR_CHEAT )
{
	if( args.ArgC() != 7 )
	{
		ConMsg ("box x1 y1 z1 x2 y2 z2\n");
		return;
	}

	Vector mins, maxs;
	for (int i = 0; i < 3; ++i)
	{
		mins[i] = atof(args[i + 1]); 
		maxs[i] = atof(args[i + 4]); 
	}
	CDebugOverlay::AddBoxOverlay( vec3_origin, mins, maxs, vec3_angle, 255, 0, 0, 0, 100 );
}

/*
==============
CL_View_f  

Debugging changes the view entity to the specified index
===============
*/
CON_COMMAND_F( cl_view, "Set the view entity index.", FCVAR_CHEAT )
{
	int nNewView;

	if( args.ArgC() != 2 )
	{
		ConMsg ("cl_view entity#\nCurrent %i\n", cl.m_nViewEntity );
		return;
	}

	if ( cl.m_nMaxClients > 1 )
		return;

	nNewView = atoi( args[1] );
	if (!nNewView)
		return;

	if ( nNewView > entitylist->GetHighestEntityIndex() )
		return;

	cl.m_nViewEntity = nNewView;
	videomode->MarkClientViewRectDirty(); // Force recalculation
	ConMsg("View entity set to %i\n", nNewView);
}


static int CL_AllocLightFromArray( dlight_t *pLights, int lightCount, int key )
{
	int		i;

	// first look for an exact key match
	if (key)
	{
		for ( i = 0; i < lightCount; i++ )
		{
			if (pLights[i].key == key)
				return i;
		}
	}

	// then look for anything else
	for ( i = 0; i < lightCount; i++ )
	{
		if (pLights[i].die < cl.GetTime())
			return i;
	}

	return 0;
}

bool g_bActiveDlights = false;
bool g_bActiveElights = false;
/*
===============
CL_AllocDlight

===============
*/
dlight_t *CL_AllocDlight (int key)
{
	int i = CL_AllocLightFromArray( cl_dlights, MAX_DLIGHTS, key );
	dlight_t *dl = &cl_dlights[i];
	R_MarkDLightNotVisible( i );
	memset (dl, 0, sizeof(*dl));
	dl->key = key;
	r_dlightchanged |= (1 << i);
	r_dlightactive |= (1 << i);
	g_bActiveDlights = true;
	return dl;
}


/*
===============
CL_AllocElight

===============
*/
dlight_t *CL_AllocElight (int key)
{
	int i = CL_AllocLightFromArray( cl_elights, MAX_ELIGHTS, key );
	dlight_t *el = &cl_elights[i];
	memset (el, 0, sizeof(*el));
	el->key = key;
	g_bActiveElights = true;
	return el;
}


/*
===============
CL_DecayLights

===============
*/
void CL_DecayLights (void)
{
	int			i;
	dlight_t	*dl;
	float		time;
	
	time = cl.GetFrameTime();
	if ( time <= 0.0f )
		return;

	g_bActiveDlights = false;
	g_bActiveElights = false;
	dl = cl_dlights;

	r_dlightchanged = 0;
	r_dlightactive = 0;

	for (i=0 ; i<MAX_DLIGHTS ; i++, dl++)
	{
		if (!dl->IsRadiusGreaterThanZero())
		{
			R_MarkDLightNotVisible( i );
			continue;
		}

		if ( dl->die < cl.GetTime() )
		{
			r_dlightchanged |= (1 << i);
			dl->radius = 0;
		}
		else if (dl->decay)
		{
			r_dlightchanged |= (1 << i);

			dl->radius -= time*dl->decay;
			if (dl->radius < 0)
			{
				dl->radius = 0;
			}
		}

		if (dl->IsRadiusGreaterThanZero())
		{
			g_bActiveDlights = true;
			r_dlightactive |= (1 << i);
		}
		else
		{
			R_MarkDLightNotVisible( i );
		}
	}

	dl = cl_elights;
	for (i=0 ; i<MAX_ELIGHTS ; i++, dl++)
	{
		if (!dl->IsRadiusGreaterThanZero())
			continue;

		if (dl->die < cl.GetTime())
		{
			dl->radius = 0;
			continue;
		}
		
		dl->radius -= time*dl->decay;
		if (dl->radius < 0)
		{
			dl->radius = 0;
		}
		if ( dl->IsRadiusGreaterThanZero() )
		{
			g_bActiveElights = true;
		}
	}
}


void CL_ExtraMouseUpdate( float frametime )
{
	// Not ready for commands yet.
	if ( !cl.IsActive() )
		return;

	if ( !Host_ShouldRun() )
		return;

	// Don't create usercmds here during playback, they were encoded into the packet already
#if defined( REPLAY_ENABLED )
	if ( demoplayer->IsPlayingBack() && !cl.ishltv && !cl.isreplay )
		return;
#else
	if ( demoplayer->IsPlayingBack() && !cl.ishltv )
		return;
#endif

	// Have client .dll create and store usercmd structure
	g_ClientDLL->ExtraMouseSample( frametime, !cl.m_bPaused );
}

/*
=================
CL_SendMove

Constructs the movement command and sends it to the server if it's time.
=================
*/
void CL_SendMove( void )
{
#if defined( STAGING_ONLY ) || defined( _DEBUG )
	if ( cl_block_usercommand.GetBool() )
		return;
#endif // STAGING_ONLY || _DEBUG

	byte data[ MAX_CMD_BUFFER ];
	
	int nextcommandnr = cl.lastoutgoingcommand + cl.chokedcommands + 1;

	// send the client update packet

	CLC_Move moveMsg;

	moveMsg.m_DataOut.StartWriting( data, sizeof( data ) );

	// Determine number of backup commands to send along
	int cl_cmdbackup = 2;
	moveMsg.m_nBackupCommands = clamp( cl_cmdbackup, 0, MAX_BACKUP_COMMANDS );

	// How many real new commands have queued up
	moveMsg.m_nNewCommands = 1 + cl.chokedcommands;
	moveMsg.m_nNewCommands = clamp( moveMsg.m_nNewCommands, 0, MAX_NEW_COMMANDS );

	int numcmds = moveMsg.m_nNewCommands + moveMsg.m_nBackupCommands;

	int from = -1;	// first command is deltaed against zeros 

	bool bOK = true;

	for ( int to = nextcommandnr - numcmds + 1; to <= nextcommandnr; to++ )
	{
		bool isnewcmd = to >= (nextcommandnr - moveMsg.m_nNewCommands + 1);

		// first valid command number is 1
		bOK = bOK && g_ClientDLL->WriteUsercmdDeltaToBuffer( &moveMsg.m_DataOut, from, to, isnewcmd );
		from = to;
	}

	if ( bOK )
	{
		// only write message if all usercmds were written correctly, otherwise parsing would fail
		cl.m_NetChannel->SendNetMsg( moveMsg );
	}
}

void CL_Move(float accumulated_extra_samples, bool bFinalTick )
{
	if ( !cl.IsConnected() )
		return;

	if ( !Host_ShouldRun() )
		return;

	tmZoneFiltered( TELEMETRY_LEVEL0, 50, TMZF_NONE, "%s", __FUNCTION__ );

	// only send packets on the final tick in one engine frame
	bool bSendPacket = true;	

	// Don't create usercmds here during playback, they were encoded into the packet already
	if ( demoplayer->IsPlayingBack() )
	{
#if defined( REPLAY_ENABLED )
		if ( cl.ishltv || cl.isreplay )
#else
		if ( cl.ishltv )
#endif
		{
			// still do it when playing back a HLTV/replay demo
			bSendPacket = false;
		}
		else
		{
            return;
		}
	}

	// don't send packets if update time not reached or chnnel still sending
	// in loopback mode don't send only if host_limitlocal is enabled

	if ( ( !cl.m_NetChannel->IsLoopback() || host_limitlocal.GetInt() ) &&
		 ( ( net_time < cl.m_flNextCmdTime ) || !cl.m_NetChannel->CanPacket()  || !bFinalTick ) )
	{
		bSendPacket = false;
	}

	if ( cl.IsActive() )
	{
		VPROF( "CL_Move" );

		int nextcommandnr = cl.lastoutgoingcommand + cl.chokedcommands + 1;

		// Have client .dll create and store usercmd structure
		g_ClientDLL->CreateMove( 
			nextcommandnr, 
			host_state.interval_per_tick - accumulated_extra_samples,
			!cl.IsPaused() );

		// Store new usercmd to dem file
		if ( demorecorder->IsRecording() )
		{
			// Back up one because we've incremented outgoing_sequence each frame by 1 unit
			demorecorder->RecordUserInput( nextcommandnr );
		}

		if ( bSendPacket )
		{
			CL_SendMove();
		}
		else
		{
			// netchanll will increase internal outgoing sequnce number too
			cl.m_NetChannel->SetChoked();	
			// Mark command as held back so we'll send it next time
			cl.chokedcommands++;
		}
	}

	if ( !bSendPacket )
		return;

		// Request non delta compression if high packet loss, show warning message
	bool hasProblem = cl.m_NetChannel->IsTimingOut() && !demoplayer->IsPlayingBack() &&	cl.IsActive();

	// Request non delta compression if high packet loss, show warning message
	if ( hasProblem )
	{
		con_nprint_t np;
		np.time_to_live = 1.0;
		np.index = 2;
		np.fixed_width_font = false;
		np.color[ 0 ] = 1.0;
		np.color[ 1 ] = 0.2;
		np.color[ 2 ] = 0.2;
		
		float flTimeOut = cl.m_NetChannel->GetTimeoutSeconds();
		Assert( flTimeOut != -1.0f );
		float flRemainingTime = flTimeOut - cl.m_NetChannel->GetTimeSinceLastReceived();
		Con_NXPrintf( &np, "WARNING:  Connection Problem" );
		np.index = 3;
		Con_NXPrintf( &np, "Auto-disconnect in %.1f seconds", flRemainingTime );

		cl.ForceFullUpdate(); // sets m_nDeltaTick to -1
	}

	if ( cl.IsActive() )
    {
        // TODO_ENHANCED:
        // When fps is lower than tickrate,
        // the server doesn't really know how many ticks were processed this frame client-side.
        //
        // When the client tick count is sent to server, sv_max_usercmd_future_ticks doesn't take into account when
        // multiple commands are ran from client because it had low fps.
        // We could probably store in CUserCmd structure the current tick number to account for that ?
        // This works anyway.
        NET_Tick mymsg( cl.m_nDeltaTick, cl.m_ClockDriftMgr.m_nCachedRealClientTick + g_ClientGlobalVariables.currenttick, host_frametime_unbounded, host_frametime_stddeviation );
		cl.m_NetChannel->SendNetMsg( mymsg );
    }

	//COM_Log( "cl.log", "Sending command number %i(%i) to server\n", cl.m_NetChan->m_nOutSequenceNr, cl.m_NetChan->m_nOutSequenceNr & CL_UPDATE_MASK );

	// Remember outgoing command that we are sending
	cl.lastoutgoingcommand = cl.m_NetChannel->SendDatagram( NULL );

	cl.chokedcommands = 0;

	// calc next packet send time

	if ( cl.IsActive() )
	{
		// use full update rate when active
		float commandInterval = 1.0f / cl_cmdrate->GetFloat();
		float maxDelta = min ( host_state.interval_per_tick, commandInterval );
		float delta = clamp( (float)(net_time - cl.m_flNextCmdTime), 0.0f, maxDelta );
		cl.m_flNextCmdTime = net_time + commandInterval - delta;
	}
	else
	{
		// during signon process send only 5 packets/second
		cl.m_flNextCmdTime = net_time + ( 1.0f / 5.0f );
	}

}

#define TICK_INTERVAL			(host_state.interval_per_tick)
#define ROUND_TO_TICKS( t )		( TICK_INTERVAL * TIME_TO_TICKS( t ) )

void CL_LatchInterpolationAmount()
{
	if ( !cl.IsConnected() )
		return;

	float dt = cl.m_NetChannel->GetTimeSinceLastReceived();
	float flClientInterpolationAmount = ROUND_TO_TICKS( cl.GetClientInterpAmount() );

	float flInterp = 0.0f;
	if ( flClientInterpolationAmount > 0.001 )
	{
		flInterp = clamp( dt / flClientInterpolationAmount, 0.0f, 3.0f );
	}
	cl.m_NetChannel->SetInterpolationAmount( flInterp );
}

//-----------------------------------------------------------------------------
// Purpose: 
// Input  : *pMessage - 
//-----------------------------------------------------------------------------
void CL_HudMessage( const char *pMessage )
{
	if ( g_ClientDLL )
	{
		g_ClientDLL->HudText( pMessage );
	}
}

CON_COMMAND_F( cl_showents, "Dump entity list to console.", FCVAR_CHEAT )
{
	for ( int i = 0; i < entitylist->GetMaxEntities(); i++ )
	{
		char entStr[256], classStr[256];
		IClientNetworkable *pEnt;

		if((pEnt = entitylist->GetClientNetworkable(i)) != NULL)
		{
			entStr[0] = 0;
			Q_snprintf(classStr, sizeof( classStr ), "'%s'", pEnt->GetClientClass()->m_pNetworkName);
		}
		else
		{
			Q_snprintf(entStr, sizeof( entStr ), "(missing), ");
			Q_snprintf(classStr, sizeof( classStr ), "(missing)");
		}

		if ( pEnt )
			ConMsg("Ent %3d: %s class %s\n", i, entStr, classStr);
	}
}


//-----------------------------------------------------------------------------
// Purpose: returns true if the background level should be loaded on startup
//-----------------------------------------------------------------------------
bool CL_ShouldLoadBackgroundLevel( const CCommand &args )
{
	if ( InEditMode() )
		return false;

	// If TF2 and PC we don't want to load the background map.
	bool bIsTF2 = false;
	if ( ( Q_stricmp( COM_GetModDirectory(), "tf" ) == 0 ) || ( Q_stricmp( COM_GetModDirectory(), "tf_beta" ) == 0 ) )
	{
		bIsTF2 = true;
	}

	if ( bIsTF2 && IsPC() )
		return false;

	if ( args.ArgC() == 2 )
	{
		// presence of args identifies an end-of-game situation
		if ( IsX360() )
		{
			// 360 needs to get UI in the correct state to transition to the Background level
			// from the credits.
			EngineVGui()->OnCreditsFinished();
			return true;
		}

		if ( !Q_stricmp( args[1], "force" ) )
		{
			// Adrian: Have to do this so the menu shows up if we ever call this while in a level.
			Host_Disconnect( true );
			// pc can't get into background maps fast enough, so just show main menu
			return false;
		}

		if ( !Q_stricmp( args[1], "playendgamevid" ) )
		{
			// Bail back to the menu and play the end game video.
			CommandLine()->AppendParm( "-endgamevid", NULL ); 
			CommandLine()->RemoveParm( "-recapvid" );
			game->PlayStartupVideos();
			CommandLine()->RemoveParm( "-endgamevid" );
			cl.Disconnect( "Finished playing end game videos", true );
			return false;
		}

		if ( !Q_stricmp( args[1], "playrecapvid" ) )
		{
			// Bail back to the menu and play the recap video
			CommandLine()->AppendParm( "-recapvid", NULL ); 
			CommandLine()->RemoveParm( "-endgamevid" );
			HostState_Restart();
			return false;
		}
	}
	
	// if force is set, then always return true
	if (CommandLine()->CheckParm("-forcestartupmenu"))
		return true;

	// don't load the map in developer or console mode
	if ( developer.GetInt() || 
		CommandLine()->CheckParm("-console") || 
		CommandLine()->CheckParm("-dev") ||
		CommandLine()->CheckParm("-nobackgroundlevel") )
		return false;

	// don't load the map if we're going straight into a level
	if ( CommandLine()->CheckParm("+map") ||
		CommandLine()->CheckParm("+connect") ||
		CommandLine()->CheckParm("+playdemo") ||
		CommandLine()->CheckParm("+timedemo") ||
		CommandLine()->CheckParm("+timedemoquit") ||
		CommandLine()->CheckParm("+load") ||
		CommandLine()->CheckParm("-makereslists"))
		return false;

#ifdef _X360
	// check if we are accepting an invite
	if ( XboxLaunch()->GetLaunchFlags() & LF_INVITERESTART )
		return false;
#endif

	// nothing else is going on, so load the startup level

	return true;
}

#define DEFAULT_BACKGROUND_NAME	"background01"

int g_iRandomChapterIndex = -1;

int CL_GetBackgroundLevelIndex( int nNumChapters )
{
	if ( g_iRandomChapterIndex != -1 )
		return g_iRandomChapterIndex;

	int iChapterIndex = sv_unlockedchapters.GetInt();
	if ( iChapterIndex <= 0 )
	{
		// expected to be [1..N]
		iChapterIndex = 1;
	}

	if ( sv_unlockedchapters.GetInt() >= ( nNumChapters-1 ) )
	{
		RandomSeed( Plat_MSTime() );
		g_iRandomChapterIndex = iChapterIndex = RandomInt( 1, nNumChapters );
	}

	return iChapterIndex;
}

//-----------------------------------------------------------------------------
// Purpose: returns the name of the background level to load
//-----------------------------------------------------------------------------
void CL_GetBackgroundLevelName( char *pszBackgroundName, int bufSize, bool bMapName )
{
	Q_strncpy( pszBackgroundName, DEFAULT_BACKGROUND_NAME, bufSize );

	KeyValues *pChapterFile = new KeyValues( pszBackgroundName );

	if ( pChapterFile->LoadFromFile( g_pFileSystem, "scripts/ChapterBackgrounds.txt" ) )
	{
		KeyValues *pChapterRoot = pChapterFile;

		const char *szChapterIndex;
		int nNumChapters = 1;
		KeyValues *pChapters = pChapterFile->GetNextKey();
		if ( bMapName && pChapters )
		{
			const char *pszName = pChapters->GetName();
			if ( pszName && pszName[0] && !Q_strncmp( "BackgroundMaps", pszName, 14 ) )
			{
				pChapterRoot = pChapters;
				pChapters = pChapters->GetFirstSubKey();
			}
			else
			{
				pChapters = NULL;
			}
		}
		else
		{
			pChapters = NULL;
		}

		if ( !pChapters )
		{
			pChapters = pChapterFile->GetFirstSubKey();
		}

		// Find the highest indexed chapter
		while ( pChapters )
		{
			szChapterIndex = pChapters->GetName();

			if ( szChapterIndex )
			{
				int nChapter = atoi(szChapterIndex);

				if( nChapter > nNumChapters )
					nNumChapters = nChapter;
			}
			
			pChapters = pChapters->GetNextKey();
		}	

		int nChapterToLoad = CL_GetBackgroundLevelIndex( nNumChapters );

		// Find the chapter background with this index
		char buf[4];
		Q_snprintf( buf, sizeof(buf), "%d", nChapterToLoad );
		KeyValues *pLoadChapter = pChapterRoot->FindKey(buf);

		// Copy the background name
		if ( pLoadChapter )
		{
			Q_strncpy( pszBackgroundName, pLoadChapter->GetString(), bufSize );
		}
	}

	pChapterFile->deleteThis();
}

//-----------------------------------------------------------------------------
// Purpose: Callback to open the game menus
//-----------------------------------------------------------------------------
void CL_CheckToDisplayStartupMenus( const CCommand &args )
{
	if ( CL_ShouldLoadBackgroundLevel( args ) )
	{
		char szBackgroundName[_MAX_PATH];
		CL_GetBackgroundLevelName( szBackgroundName, sizeof(szBackgroundName), true );

		char cmd[_MAX_PATH];
		Q_snprintf( cmd, sizeof(cmd), "map_background %s\n", szBackgroundName );
		Cbuf_AddText( cmd );
	}
}

static float s_fDemoRevealGameUITime = -1;
float s_fDemoPlayMusicTime = -1;
static bool s_bIsRavenHolmn = false;
//-----------------------------------------------------------------------------
// Purpose: run the special demo logic when transitioning from the trainstation levels
//----------------------------------------------------------------------------
void CL_DemoTransitionFromTrainstation()
{
	// kick them out to GameUI instead and bring up the chapter page with raveholm unlocked
	sv_unlockedchapters.SetValue(6); // unlock ravenholm
	Cbuf_AddText( "sv_cheats 1; fadeout 1.5; sv_cheats 0;");
	Cbuf_Execute();
	s_fDemoRevealGameUITime = Sys_FloatTime() + 1.5;
	s_bIsRavenHolmn = false;
}

void CL_DemoTransitionFromRavenholm()
{
	Cbuf_AddText( "sv_cheats 1; fadeout 2; sv_cheats 0;");
	Cbuf_Execute();
	s_fDemoRevealGameUITime = Sys_FloatTime() + 1.9;
	s_bIsRavenHolmn = true;
}

void CL_DemoTransitionFromTestChmb()
{
	Cbuf_AddText( "sv_cheats 1; fadeout 2; sv_cheats 0;");
	Cbuf_Execute();
	s_fDemoRevealGameUITime = Sys_FloatTime() + 1.9;	
}


//-----------------------------------------------------------------------------
// Purpose: make the gameui appear after a certain interval
//----------------------------------------------------------------------------
void V_RenderVGuiOnly();
bool V_CheckGamma();
void CL_DemoCheckGameUIRevealTime( ) 
{
	if ( s_fDemoRevealGameUITime > 0 )
	{
		if ( s_fDemoRevealGameUITime < Sys_FloatTime() )
		{
			s_fDemoRevealGameUITime = -1;

			SCR_BeginLoadingPlaque();
			Cbuf_AddText( "disconnect;");

			CCommand args;
			CL_CheckToDisplayStartupMenus( args );

			s_fDemoPlayMusicTime = Sys_FloatTime() + 1.0;
		}
	}

	if ( s_fDemoPlayMusicTime > 0 )
	{
		V_CheckGamma();
		V_RenderVGuiOnly();
		if ( s_fDemoPlayMusicTime < Sys_FloatTime() )
		{
			s_fDemoPlayMusicTime = -1;
			EngineVGui()->ActivateGameUI();

			if ( CL_IsHL2Demo() )
			{
				if ( s_bIsRavenHolmn )
				{
					Cbuf_AddText( "play music/ravenholm_1.mp3;" );
				}
				else
				{
					EngineVGui()->ShowNewGameDialog(6);// bring up the new game dialog in game UI
				}
			}
		}
	}
}


//-----------------------------------------------------------------------------
// Purpose: setup a debug string that is uploaded on crash
//----------------------------------------------------------------------------
extern bool g_bV3SteamInterface;
char g_minidumpinfo[ 4096 ] = {0};
PAGED_POOL_INFO_t g_pagedpoolinfo = { 0 };
void DisplaySystemVersion( char *osversion, int maxlen );

void CL_SetPagedPoolInfo()
{
	if ( IsX360() )
		return;
#if !defined( _X360 ) && !defined(NO_STEAM) && !defined(SWDS)
	Plat_GetPagedPoolInfo( &g_pagedpoolinfo );
#endif
}

void CL_SetSteamCrashComment()
{
	if ( IsX360() )
		return;

	char map[ 80 ];
	char videoinfo[ 2048 ];
	char misc[ 256 ];
	char driverinfo[ 2048 ];
	char osversion[ 256 ];

	map[ 0 ] = 0;
	driverinfo[ 0 ] = 0;
	videoinfo[ 0 ] = 0;
	misc[ 0 ] = 0;
	osversion[ 0 ] = 0;

	if ( host_state.worldmodel )
	{
		CL_SetupMapName( modelloader->GetName( host_state.worldmodel ), map, sizeof( map ) );
	}

	DisplaySystemVersion( osversion, sizeof( osversion ) );

	MaterialAdapterInfo_t info;
	materials->GetDisplayAdapterInfo( materials->GetCurrentAdapter(), info );

	const char *dxlevel = "Unk";
	int nDxLevel = g_pMaterialSystemHardwareConfig->GetDXSupportLevel();
	if ( g_pMaterialSystemHardwareConfig )
	{
		dxlevel = COM_DXLevelToString( nDxLevel ) ;
	}

	// Make a string out of the high part and low parts of driver version
	char szDXDriverVersion[ 64 ];
	Q_snprintf( szDXDriverVersion, sizeof( szDXDriverVersion ), "%ld.%ld.%ld.%ld", 
		( long )( info.m_nDriverVersionHigh>>16 ), 
		( long )( info.m_nDriverVersionHigh & 0xffff ), 
		( long )( info.m_nDriverVersionLow>>16 ), 
		( long )( info.m_nDriverVersionLow & 0xffff ) );

	Q_snprintf( driverinfo, sizeof(driverinfo), "Driver Name:  %s\nDriver Version: %s\nVendorId / DeviceId:  0x%x / 0x%x\nSubSystem / Rev:  0x%x / 0x%x\nDXLevel:  %s [%d]\nVid:  %i x %i",
		info.m_pDriverName,
		szDXDriverVersion,
		info.m_VendorID,
		info.m_DeviceID,
		info.m_SubSysID,
		info.m_Revision,
		dxlevel ? dxlevel : "Unk", nDxLevel, 
		videomode->GetModeWidth(), videomode->GetModeHeight() );

	ConVarRef mat_picmip( "mat_picmip" );
	ConVarRef mat_forceaniso( "mat_forceaniso" );
	ConVarRef mat_trilinear( "mat_trilinear" );
	ConVarRef mat_antialias( "mat_antialias" );
	ConVarRef mat_aaquality( "mat_aaquality" );
	ConVarRef r_shadowrendertotexture( "r_shadowrendertotexture" );
	ConVarRef r_flashlightdepthtexture( "r_flashlightdepthtexture" );
#ifndef _X360
	ConVarRef r_waterforceexpensive( "r_waterforceexpensive" );
#endif
		ConVarRef r_waterforcereflectentities( "r_waterforcereflectentities" );
		ConVarRef mat_vsync( "mat_vsync" );
		ConVarRef r_rootlod( "r_rootlod" );
		ConVarRef mat_reducefillrate( "mat_reducefillrate" );
		ConVarRef mat_motion_blur_enabled( "mat_motion_blur_enabled" );
		ConVarRef mat_queue_mode( "mat_queue_mode" );

#ifdef _X360
	Q_snprintf( videoinfo, sizeof(videoinfo), "picmip: %i forceansio: %i trilinear: %i antialias: %i vsync: %i rootlod: %i reducefillrate: %i\n"\
		"shadowrendertotexture: %i r_flashlightdepthtexture %i waterforcereflectentities: %i mat_motion_blur_enabled: %i",
										mat_picmip.GetInt(), mat_forceaniso.GetInt(), mat_trilinear.GetInt(), mat_antialias.GetInt(), mat_aaquality.GetInt(),
										mat_vsync.GetInt(), r_rootlod.GetInt(), mat_reducefillrate.GetInt(), 
										r_shadowrendertotexture.GetInt(), r_flashlightdepthtexture.GetInt(),
										r_waterforcereflectentities.GetInt(),
										mat_motion_blur_enabled.GetInt() );
#else
		Q_snprintf( videoinfo, sizeof(videoinfo), "picmip: %i forceansio: %i trilinear: %i antialias: %i vsync: %i rootlod: %i reducefillrate: %i\n"\
			"shadowrendertotexture: %i r_flashlightdepthtexture %i waterforceexpensive: %i waterforcereflectentities: %i mat_motion_blur_enabled: %i mat_queue_mode %i",
											mat_picmip.GetInt(), mat_forceaniso.GetInt(), mat_trilinear.GetInt(), mat_antialias.GetInt(), 
											mat_vsync.GetInt(), r_rootlod.GetInt(), mat_reducefillrate.GetInt(), 
											r_shadowrendertotexture.GetInt(), r_flashlightdepthtexture.GetInt(),
											r_waterforceexpensive.GetInt(), r_waterforcereflectentities.GetInt(),
											mat_motion_blur_enabled.GetInt(), mat_queue_mode.GetInt() );
#endif
	int latency = 0;
	if ( cl.m_NetChannel )
	{
		latency = (int)( 1000.0f * cl.m_NetChannel->GetAvgLatency( FLOW_OUTGOING ) );
	}

	Q_snprintf( misc, sizeof( misc ), "skill:%i rate %i update %i cmd %i latency %i msec", 
		skill.GetInt(),
		cl_rate->GetInt(),
		(int)cl_updaterate->GetFloat(),
		(int)cl_cmdrate->GetFloat(),
		latency
	);

	const char *pNetChannel = "Not Connected";
	if ( cl.m_NetChannel )
	{
		pNetChannel = cl.m_NetChannel->GetRemoteAddress().ToString();
	}

		CL_SetPagedPoolInfo();

		Q_snprintf( g_minidumpinfo, sizeof(g_minidumpinfo),
				"Map: %s\n"\
				"Game: %s\n"\
				"Build: %i\n"\
				"Misc: %s\n"\
				"Net: %s\n"\
				"cmdline:%s\n"\
				"driver: %s\n"\
				"video: %s\n"\
				"OS: %s\n",
				map, com_gamedir, build_number(), misc, pNetChannel, CommandLine()->GetCmdLine(), driverinfo, videoinfo, osversion );

		char full[ 4096 ];
		Q_snprintf( full, sizeof( full ), "%sPP PAGES: used: %d, free %d\n", g_minidumpinfo, (int)g_pagedpoolinfo.numPagesUsed, (int)g_pagedpoolinfo.numPagesFree );

#ifndef NO_STEAM
	SteamAPI_SetMiniDumpComment( full );
#endif
}


//
// register commands
//
static ConCommand startupmenu( "startupmenu", &CL_CheckToDisplayStartupMenus, "Opens initial menu screen and loads the background bsp, but only if no other level is being loaded, and we're not in developer mode." );

ConVar cl_language( "cl_language", "english", FCVAR_USERINFO, "Language (from HKCU\\Software\\Valve\\Steam\\Language)" );
void CL_InitLanguageCvar()
{
	Msg("CL_InitLanguageCvar\n");
	if ( Steam3Client().SteamApps() )
	{
		cl_language.SetValue( Steam3Client().SteamApps()->GetCurrentGameLanguage() );
	}
	else
	{
		char *szLang = getenv("LANG");

		if ( CommandLine()->CheckParm( "-language" ) )
		{
			cl_language.SetValue( CommandLine()->ParmValue( "-language", "english") );
			return;
		}
		else if( szLang )
		{
			ELanguage lang = PchLanguageICUCodeToELanguage(szLang, k_Lang_English);
			const char *szShortLang = GetLanguageShortName(lang);
			if( Q_strncmp(szShortLang, "none", 4) != 0 )
			{
				cl_language.SetValue( szShortLang );
				return;
			}
		}

		cl_language.SetValue( "english" );
	}
}

void CL_ChangeCloudSettingsCvar( IConVar *var, const char *pOldValue, float flOldValue );
ConVar cl_cloud_settings( "cl_cloud_settings", "1", FCVAR_HIDDEN, "Cloud enabled from (from HKCU\\Software\\Valve\\Steam\\Apps\\appid\\Cloud)", CL_ChangeCloudSettingsCvar );
void CL_ChangeCloudSettingsCvar( IConVar *var, const char *pOldValue, float flOldValue )
{
	// !! bug do i need to do something linux-wise here.
	if ( IsPC() && Steam3Client().SteamRemoteStorage() )
	{
		ConVarRef ref( var->GetName() );
		Steam3Client().SteamRemoteStorage()->SetCloudEnabledForApp( ref.GetBool() );
		if ( cl_cloud_settings.GetInt() == STEAMREMOTESTORAGE_CLOUD_ON && flOldValue == STEAMREMOTESTORAGE_CLOUD_OFF )
		{
			// If we were just turned on, get our configuration from remote storage.
			engineClient->ReadConfiguration( false );
			engineClient->ClientCmd_Unrestricted( "refresh_options_dialog" );
		}

	}
}

void CL_InitCloudSettingsCvar()
{
	if ( IsPC()	&& Steam3Client().SteamRemoteStorage() )
	{
		int iCloudSettings = STEAMREMOTESTORAGE_CLOUD_OFF;
		if ( Steam3Client().SteamRemoteStorage()->IsCloudEnabledForApp() )
			iCloudSettings = STEAMREMOTESTORAGE_CLOUD_ON;
		
		cl_cloud_settings.SetValue( iCloudSettings );
	}
	else
	{
		// If not on PC or steam not available, set to 0 to make sure no replication occurs or is attempted
		cl_cloud_settings.SetValue( STEAMREMOTESTORAGE_CLOUD_OFF );
	}
}


/*
=================
CL_Init
=================
*/
void CL_Init (void)
{	
	cl.Clear();
	
	CL_InitLanguageCvar();
	CL_InitCloudSettingsCvar();
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CL_Shutdown( void )
{
}

CON_COMMAND_F( cl_fullupdate, "Forces the server to send a full update packet", FCVAR_CHEAT )
{
	cl.ForceFullUpdate();
}


#ifdef STAGING_ONLY

CON_COMMAND( cl_download, "Downloads a file from server." )
{
	if ( args.ArgC() != 2 )
		return;

	if ( !cl.m_NetChannel )
		return;
	
	cl.m_NetChannel->RequestFile( args[ 1 ] ); // just for testing stuff
}

#endif // STAGING_ONLY


CON_COMMAND_F( setinfo, "Adds a new user info value", FCVAR_CLIENTCMD_CAN_EXECUTE )
{
	if ( args.ArgC() != 3 )
	{
		Msg("Syntax: setinfo <key> <value>\n");
		return;
	}

	const char *name = args[ 1 ];
	const char *value = args[ 2 ];

	// Prevent players manually changing their name (their Steam account provides it now)
	if ( Q_stricmp( name, "name" ) == 0 )
		return;

	// Discard any convar change request if contains funky characters
	bool bFunky = false;
	for (const char *s = name ; *s != '\0' ; ++s )
	{
		if ( !V_isalnum(*s) && *s != '_' )
		{
			bFunky = true;
			break;
		}
	}
	if ( bFunky )
	{
		Msg( "Ignoring convar change request for variable '%s', which contains invalid character(s)\n", name );
		return;
	}

	ConCommandBase *pCommand = g_pCVar->FindCommandBase( name );

	ConVarRef sv_cheats( "sv_cheats" );

	if ( pCommand )
	{
		if ( pCommand->IsCommand() )		
		{
			Msg("Name %s is already registered as console command\n", name );
			return;
		}

		if ( !pCommand->IsFlagSet(FCVAR_USERINFO) )
		{
			Msg("Convar %s is already registered but not as user info value\n", name );
			return;
		}

		if ( pCommand->IsFlagSet( FCVAR_NOT_CONNECTED ) )
		{
#ifndef DEDICATED
			// Connected to server?
			if ( cl.IsConnected() )
			{
				extern IBaseClientDLL *g_ClientDLL;
				if ( pCommand->IsFlagSet( FCVAR_USERINFO ) && g_ClientDLL && g_ClientDLL->IsConnectedUserInfoChangeAllowed( NULL ) )
				{
					// Client.dll is allowing the convar change
				}
				else
				{
					ConMsg( "Can't change %s when playing, disconnect from the server or switch team to spectators\n", pCommand->GetName() );
					return;
				}
			}
#endif
		}

		if ( IsPC() )
		{
#if !defined(NO_STEAM)
			EUniverse eUniverse = GetSteamUniverse();
			if ( (( eUniverse != k_EUniverseBeta ) && ( eUniverse != k_EUniverseDev )) && pCommand->IsFlagSet( FCVAR_DEVELOPMENTONLY ) )
				return;
#endif 
		}

		if ( pCommand->IsFlagSet( FCVAR_CHEAT ) && sv_cheats.GetBool() == 0  )
		{
			Msg("Convar %s is marked as cheat and cheats are off\n", name );
			return;
		}
	}
	else
	{
		// cvar not found, create it now
		char *pszString = V_strdup( name );

		pCommand = new ConVar( pszString, "", FCVAR_USERINFO, "Custom user info value" );
	}

	ConVar *pConVar = (ConVar*)pCommand;

	pConVar->SetValue( value );

	if ( cl.IsConnected() )
	{
		// send changed cvar to server
		NET_SetConVar convar( name, value );
		cl.m_NetChannel->SendNetMsg( convar );
	}
}


CON_COMMAND( cl_precacheinfo, "Show precache info (client)." )
{
	if ( args.ArgC() == 2 )
	{
		cl.DumpPrecacheStats( args[ 1 ] );
		return;
	}
	
	// Show all data
	cl.DumpPrecacheStats( MODEL_PRECACHE_TABLENAME );
	cl.DumpPrecacheStats( DECAL_PRECACHE_TABLENAME );
	cl.DumpPrecacheStats( SOUND_PRECACHE_TABLENAME );
	cl.DumpPrecacheStats( GENERIC_PRECACHE_TABLENAME );
}

//-----------------------------------------------------------------------------
// Purpose: 
// Input  : *object - 
//			stringTable - 
//			stringNumber - 
//			*newString - 
//			*newData - 
//-----------------------------------------------------------------------------
void Callback_ModelChanged( void *object, INetworkStringTable *stringTable, int stringNumber, const char *newString, const void *newData )
{
	if ( stringTable == cl.m_pModelPrecacheTable )
	{
		// Index 0 is always NULL, just ignore it
		// Index 1 == the world, don't 
		if ( stringNumber > 1 )
		{
//			DevMsg( "Preloading model %s\n", newString );
			cl.SetModel( stringNumber );
		}
	}
	else
	{
		Assert( 0 ) ; // Callback_*Changed called with wrong stringtable
	}
}

//-----------------------------------------------------------------------------
// Purpose: 
// Input  : *object - 
//			stringTable - 
//			stringNumber - 
//			*newString - 
//			*newData - 
//-----------------------------------------------------------------------------
void Callback_GenericChanged( void *object, INetworkStringTable *stringTable, int stringNumber, const char *newString, const void *newData )
{
	if ( stringTable == cl.m_pGenericPrecacheTable )
	{
		// Index 0 is always NULL, just ignore it
		if ( stringNumber >= 1 )
		{
			cl.SetGeneric( stringNumber );
		}
	}
	else
	{
		Assert( 0 ) ; // Callback_*Changed called with wrong stringtable
	}
}

//-----------------------------------------------------------------------------
// Purpose: 
// Input  : *object - 
//			stringTable - 
//			stringNumber - 
//			*newString - 
//			*newData - 
//-----------------------------------------------------------------------------
void Callback_SoundChanged( void *object, INetworkStringTable *stringTable, int stringNumber, const char *newString, const void *newData )
{
	if ( stringTable == cl.m_pSoundPrecacheTable )
	{
		// Index 0 is always NULL, just ignore it
		if ( stringNumber >= 1 )
		{
			cl.SetSound( stringNumber );
		}
	}
	else
	{
		Assert( 0 ) ; // Callback_*Changed called with wrong stringtable
	}
}

void Callback_DecalChanged( void *object, INetworkStringTable *stringTable, int stringNumber, const char *newString, const void *newData )
{
	if ( stringTable == cl.m_pDecalPrecacheTable )
	{
		cl.SetDecal( stringNumber );
	}
	else
	{
		Assert( 0 ) ; // Callback_*Changed called with wrong stringtable
	}
}


void Callback_InstanceBaselineChanged( void *object, INetworkStringTable *stringTable, int stringNumber, const char *newString, const void *newData )
{
	Assert( stringTable == cl.m_pInstanceBaselineTable );
	// cl.UpdateInstanceBaseline( stringNumber );
}

void Callback_UserInfoChanged( void *object, INetworkStringTable *stringTable, int stringNumber, const char *newString, const void *newData )
{
	Assert( stringTable == cl.m_pUserInfoTable );

	// stringnumber == player slot

	player_info_t *player = (player_info_t*)newData;

	if ( !player )
		return; // player left the game

	// request custom user files if necessary
	for ( int i=0; i<MAX_CUSTOM_FILES; i++ )
	{
		cl.CheckOthersCustomFile( player->customFiles[i] );
	}

	// fire local client event game event
	IGameEvent * event = g_GameEventManager.CreateEvent( "player_info" );

	if ( event )
	{
		event->SetInt( "userid", player->userID );
		event->SetInt( "friendsid", player->friendsID );
		event->SetInt( "index", stringNumber );
		event->SetString( "name", player->name );
		event->SetString( "networkid", player->guid );
		event->SetBool( "bot", player->fakeplayer );

		g_GameEventManager.FireEventClientSide( event );
	}
}

void Callback_DynamicModelsChanged( void *object, INetworkStringTable *stringTable, int stringNumber, const char *newString, const void *newData )
{
#ifndef SWDS
	extern IVModelInfoClient *modelinfoclient;
	if ( modelinfoclient )
	{
		modelinfoclient->OnDynamicModelsStringTableChange( stringNumber, newString, newData );
	}
#endif
}

void CL_HookClientStringTables()
{
	// install hooks
	int numTables = cl.m_StringTableContainer->GetNumTables();

	for ( int i =0; i<numTables; i++)
	{
		// iterate through server tables
		CNetworkStringTable *pTable = 
			(CNetworkStringTable*)cl.m_StringTableContainer->GetTable( i );

		if ( !pTable )
			continue;

		cl.HookClientStringTable( pTable->GetTableName() );
	}
}
// Installs the all, and invokes cb for all existing items
void CL_InstallAndInvokeClientStringTableCallbacks()
{
	// install hooks
	int numTables = cl.m_StringTableContainer->GetNumTables();

	for ( int i =0; i<numTables; i++)
	{
		// iterate through server tables
		CNetworkStringTable *pTable = 
			(CNetworkStringTable*)cl.m_StringTableContainer->GetTable( i );

		if ( !pTable )
			continue;

		pfnStringChanged pOldFunction = pTable->GetCallback();

		cl.InstallStringTableCallback( pTable->GetTableName() );

		pfnStringChanged pNewFunction = pTable->GetCallback();
		if ( !pNewFunction )
			continue;

		// We already had it installed (e.g., from client .dll) so all of the callbacks have been called and don't need a second dose
		if ( pNewFunction == pOldFunction )
			continue;

		for ( int j = 0; j < pTable->GetNumStrings(); ++j )
		{
			int userDataSize;
			const void *pUserData = pTable->GetStringUserData( j, &userDataSize );
			(*pNewFunction)( NULL, pTable, j, pTable->GetString( j ), pUserData );
		}
	}
}

// Singleton client state
CClientState	cl;
