summaryrefslogtreecommitdiff
path: root/engine/cl_main.cpp
diff options
context:
space:
mode:
authorFluorescentCIAAfricanAmerican <[email protected]>2020-04-22 12:56:21 -0400
committerFluorescentCIAAfricanAmerican <[email protected]>2020-04-22 12:56:21 -0400
commit3bf9df6b2785fa6d951086978a3e66f49427166a (patch)
tree2c0f1f0c63c4832882bc93814ebd2c2b1c6224e5 /engine/cl_main.cpp
downloadarchived-source-engine-2018-hl2-src-3bf9df6b2785fa6d951086978a3e66f49427166a.tar.xz
archived-source-engine-2018-hl2-src-3bf9df6b2785fa6d951086978a3e66f49427166a.zip
Diffstat (limited to 'engine/cl_main.cpp')
-rw-r--r--engine/cl_main.cpp3140
1 files changed, 3140 insertions, 0 deletions
diff --git a/engine/cl_main.cpp b/engine/cl_main.cpp
new file mode 100644
index 0000000..8c99ee5
--- /dev/null
+++ b/engine/cl_main.cpp
@@ -0,0 +1,3140 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $Workfile: $
+// $Date: $
+// $NoKeywords: $
+//===========================================================================//
+
+#include "client_pch.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 "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 "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 4000
+
+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.
+=================
+*/
+
+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
+
+ 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
+
+}
+
+//-----------------------------------------------------------------------------
+// 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.PurgeAndDeleteElements();
+}
+
+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 (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() )
+ {
+ NET_Tick mymsg( cl.m_nDeltaTick, 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") )
+ 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()
+{
+ if ( Steam3Client().SteamApps() )
+ {
+ cl_language.SetValue( Steam3Client().SteamApps()->GetCurrentGameLanguage() );
+ }
+ else
+ {
+ 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;