summaryrefslogtreecommitdiff
path: root/engine/host.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'engine/host.cpp')
-rw-r--r--engine/host.cpp5025
1 files changed, 5025 insertions, 0 deletions
diff --git a/engine/host.cpp b/engine/host.cpp
new file mode 100644
index 0000000..0ff16bb
--- /dev/null
+++ b/engine/host.cpp
@@ -0,0 +1,5025 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+//===========================================================================//
+
+#include "tier0/fasttimer.h"
+
+#ifdef _WIN32
+#include "tier0/memdbgon.h" // needed because in release builds crtdbg.h is handled specially if USE_MEM_DEBUG is defined
+#include "tier0/memdbgoff.h"
+#include <crtdbg.h> // For getting at current heap size
+#endif
+
+#include "tier1/fmtstr.h"
+#include "vstdlib/jobthread.h"
+
+#ifdef USE_SDL
+ #include "appframework/ilaunchermgr.h"
+ extern ILauncherMgr *g_pLauncherMgr;
+#endif
+
+#include "server.h"
+#include "host_jmp.h"
+#include "screen.h"
+#include "keys.h"
+#include "cdll_int.h"
+#include "eiface.h"
+#include "sv_main.h"
+#include "sv_log.h"
+#include "shadowmgr.h"
+#include "zone.h"
+#include "gl_cvars.h"
+#include "sv_filter.h"
+#include "ivideomode.h"
+#include "vprof_engine.h"
+#include "iengine.h"
+#include "tier2/tier2.h"
+#include "enginethreads.h"
+#include "steam/steam_api.h"
+#include "LoadScreenUpdate.h"
+#include "datacache/idatacache.h"
+
+#if !defined SWDS
+#include "voice.h"
+#include "sound.h"
+#endif
+
+#include "icvar.h"
+
+#include "sys.h"
+#include "client.h"
+#include "cl_pred.h"
+#include "console.h"
+#include "view.h"
+#include "host.h"
+#include "decal.h"
+#include "gl_matsysiface.h"
+#include "gl_shader.h"
+#include "sys_dll.h"
+#include "cmodel_engine.h"
+#ifndef SWDS
+#include "con_nprint.h"
+#endif
+#include "filesystem.h"
+#include "filesystem_engine.h"
+#include "tier0/etwprof.h"
+#include "tier0/vcrmode.h"
+#include "traceinit.h"
+#include "host_saverestore.h"
+#include "l_studio.h"
+#include "cl_demo.h"
+#include "cdll_engine_int.h"
+#include "host_cmd.h"
+#include "host_state.h"
+#include "dt_instrumentation.h"
+#include "dt_instrumentation_server.h"
+#include "const.h"
+#include "bitbuf_errorhandler.h"
+#include "soundflags.h"
+#include "enginestats.h"
+#include "tier1/strtools.h"
+#include "testscriptmgr.h"
+#include "tmessage.h"
+#include "tier0/vprof.h"
+#include "tier0/icommandline.h"
+#include "materialsystem/imaterialsystemhardwareconfig.h"
+#include "MapReslistGenerator.h"
+#include "DownloadListGenerator.h"
+#include "download.h"
+#include "staticpropmgr.h"
+#include "GameEventManager.h"
+#include "iprediction.h"
+#include "netmessages.h"
+#include "cl_main.h"
+#include "hltvserver.h"
+#include "hltvtest.h"
+#if defined( REPLAY_ENABLED )
+#include "replayserver.h"
+#include "replay_internal.h"
+#endif
+#include "sys_mainwind.h"
+#include "host_phonehome.h"
+#ifndef SWDS
+#include "vgui_baseui_interface.h"
+#include "cl_steamauth.h"
+#endif
+#include "sv_remoteaccess.h" // NotifyDedicatedServerUI()
+#include "snd_audio_source.h"
+#include "sv_steamauth.h"
+#include "MapReslistGenerator.h"
+#include "DevShotGenerator.h"
+#include "sv_plugin.h"
+#include "toolframework/itoolframework.h"
+#include "ienginetoolinternal.h"
+#include "inputsystem/iinputsystem.h"
+#include "vgui_askconnectpanel.h"
+#include "cvar.h"
+#include "saverestoretypes.h"
+#include "filesystem/IQueuedLoader.h"
+#include "soundservice.h"
+#include "profile.h"
+#include "steam/isteamremotestorage.h"
+#if defined( _X360 )
+#include "xbox/xbox_win32stubs.h"
+#include "audio_pch.h"
+#endif
+#if defined( LINUX )
+#include <locale.h>
+#include "SDL.h"
+#endif
+
+#include "ixboxsystem.h"
+extern IXboxSystem *g_pXboxSystem;
+
+extern ConVar cl_cloud_settings;
+extern ConVar cl_logofile;
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include "tier0/memdbgon.h"
+
+//-----------------------------------------------------------------------------
+// Forward declarations
+//-----------------------------------------------------------------------------
+void CL_SetPagedPoolInfo();
+extern char *CM_EntityString( void );
+extern ConVar host_map;
+extern ConVar sv_cheats;
+
+bool g_bDedicatedServerBenchmarkMode = false;
+bool g_bAllowSecureServers = true;
+bool g_bLowViolence = false;
+
+// These counters are for debugging in dumps. If these are non-zero it may indicate some kind of
+// heap problem caused by the setjmp/longjmp error handling
+int g_HostServerAbortCount = 0;
+int g_HostErrorCount = 0;
+int g_HostEndDemo = 0;
+
+char g_szDefaultLogoFileName[] = "materials/vgui/logos/spray.vtf";
+
+int host_frameticks = 0;
+int host_tickcount = 0;
+int host_currentframetick = 0;
+
+static const char g_pModuleExtension[] = DLL_EXT_STRING;
+
+// Engine player info, no game related infos here
+BEGIN_BYTESWAP_DATADESC( player_info_s )
+ DEFINE_ARRAY( name, FIELD_CHARACTER, MAX_PLAYER_NAME_LENGTH ),
+ DEFINE_FIELD( userID, FIELD_INTEGER ),
+ DEFINE_ARRAY( guid, FIELD_CHARACTER, SIGNED_GUID_LEN + 1 ),
+ DEFINE_FIELD( friendsID, FIELD_INTEGER ),
+ DEFINE_ARRAY( friendsName, FIELD_CHARACTER, MAX_PLAYER_NAME_LENGTH ),
+ DEFINE_FIELD( fakeplayer, FIELD_BOOLEAN ),
+ DEFINE_FIELD( ishltv, FIELD_BOOLEAN ),
+#if defined( REPLAY_ENABLED )
+ DEFINE_FIELD( isreplay, FIELD_BOOLEAN ),
+#endif
+ DEFINE_ARRAY( customFiles, FIELD_INTEGER, MAX_CUSTOM_FILES ),
+ DEFINE_FIELD( filesDownloaded, FIELD_INTEGER ),
+END_BYTESWAP_DATADESC()
+
+//------------------------------------------
+enum
+{
+ FRAME_SEGMENT_INPUT = 0,
+ FRAME_SEGMENT_CLIENT,
+ FRAME_SEGMENT_SERVER,
+ FRAME_SEGMENT_RENDER,
+ FRAME_SEGMENT_SOUND,
+ FRAME_SEGMENT_CLDLL,
+ FRAME_SEGMENT_CMD_EXECUTE,
+
+ NUM_FRAME_SEGMENTS,
+};
+
+class CFrameTimer
+{
+public:
+ void ResetDeltas();
+
+ CFrameTimer() : swaptime(0)
+ {
+ ResetDeltas();
+ }
+
+ void MarkFrame();
+ void StartFrameSegment( int i )
+ {
+ starttime[i] = Sys_FloatTime();
+ }
+
+ void EndFrameSegment( int i )
+ {
+ double dt = Sys_FloatTime() - starttime[i];
+ deltas[ i ] += dt;
+ }
+ void MarkSwapTime( )
+ {
+ double newswaptime = Sys_FloatTime();
+ frametime = newswaptime - swaptime;
+ swaptime = newswaptime;
+
+ ComputeFrameVariability();
+ g_EngineStats.SetFrameTime( frametime );
+ g_EngineStats.SetFPSVariability( m_flFPSVariability );
+
+ host_frametime_stddeviation = m_flFPSStdDeviationSeconds;
+ }
+
+private:
+ enum
+ {
+ FRAME_HISTORY_COUNT = 50
+ };
+
+ friend void Host_Speeds();
+ void ComputeFrameVariability();
+
+ double time_base;
+ double times[9];
+ double swaptime;
+ double frametime;
+ double m_flFPSVariability;
+ double m_flFPSStdDeviationSeconds;
+ double starttime[NUM_FRAME_SEGMENTS];
+ double deltas[NUM_FRAME_SEGMENTS];
+
+ float m_pFrameTimeHistory[FRAME_HISTORY_COUNT];
+ int m_nFrameTimeHistoryIndex;
+};
+
+
+static CFrameTimer g_HostTimes;
+
+
+//------------------------------------------
+
+
+float host_time = 0.0;
+
+static ConVar violence_hblood( "violence_hblood","1", 0, "Draw human blood" );
+static ConVar violence_hgibs( "violence_hgibs","1", 0, "Show human gib entities" );
+static ConVar violence_ablood( "violence_ablood","1", 0, "Draw alien blood" );
+static ConVar violence_agibs( "violence_agibs","1", 0, "Show alien gib entities" );
+
+// Marked as FCVAR_USERINFO so that the server can cull CC messages before networking them down to us!!!
+ConVar closecaption( "closecaption", "0", FCVAR_ARCHIVE | FCVAR_ARCHIVE_XBOX | FCVAR_USERINFO, "Enable close captioning." );
+extern ConVar sv_unlockedchapters;
+
+void Snd_Restart_f()
+{
+#ifndef SWDS
+ extern bool snd_firsttime;
+
+ char szVoiceCodec[_MAX_PATH] = { 0 };
+ int nVoiceSampleRate = Voice_ConfiguredSampleRate();
+
+ {
+ // This is not valid after voice shuts down
+ const char *pPreviousCodec = Voice_ConfiguredCodec();
+ if ( pPreviousCodec && *pPreviousCodec )
+ {
+ V_strncpy( szVoiceCodec, pPreviousCodec, sizeof( szVoiceCodec ) );
+ }
+ }
+
+ S_Shutdown();
+ snd_firsttime = true;
+ cl.ClearSounds();
+ S_Init();
+
+ // Restart voice if it was running
+ if ( szVoiceCodec[0] )
+ Voice_Init( szVoiceCodec, nVoiceSampleRate );
+
+ // Do this or else it won't have anything in the cache.
+ if ( audiosourcecache && sv.GetMapName()[0] )
+ {
+ audiosourcecache->LevelInit( sv.GetMapName() );
+ }
+
+ // Flush soundscapes so they don't stop. We don't insert text in the buffer here because
+ // cl_soundscape_flush is normally cheat-protected.
+ ConCommand *pCommand = (ConCommand*)dynamic_cast< const ConCommand* >( g_pCVar->FindCommand( "cl_soundscape_flush" ) );
+ if ( pCommand )
+ {
+ char const *argv[ 1 ] = { "cl_soundscape_flush" };
+
+ CCommand cmd( 1, argv );
+ pCommand->Dispatch( cmd );
+ }
+#endif
+}
+
+static ConCommand snd_restart( "snd_restart", Snd_Restart_f, "Restart sound system." );
+
+// In other C files.
+void Shader_Shutdown( void );
+void R_Shutdown( void );
+
+bool g_bAbortServerSet = false;
+
+#ifdef _WIN32
+static bool s_bInitPME = false;
+#endif
+
+CON_COMMAND( mem_dump, "Dump memory stats to text file." )
+{
+ ConMsg("Writing memory stats to file memstats.txt\n");
+
+#if defined( _MEMTEST )
+ const char *pTest = sv.GetMapName();
+ if ( !pTest || !pTest[0] )
+ {
+ // possibly at menu
+ pTest = "unknown";
+ }
+ MemAlloc_SetStatsExtraInfo( pTest,"" );
+#endif
+ MemAlloc_DumpStats();
+}
+
+CON_COMMAND( mem_compact, "" )
+{
+ MemAlloc_CompactHeap();
+}
+
+CON_COMMAND( mem_eat, "" )
+{
+ MemAlloc_Alloc( 1024* 1024 );
+}
+
+CON_COMMAND( mem_test, "" )
+{
+ MemAlloc_CrtCheckMemory();
+}
+
+static ConVar host_competitive_ever_enabled( "host_competitive_ever_enabled", "0", FCVAR_HIDDEN, "Has competitive ever been enabled this run?", true, 0, true, 1, true, 1, false, 1, NULL );
+
+static ConVar mem_test_each_frame( "mem_test_each_frame", "0", 0, "Run heap check at end of every frame\n" );
+static ConVar mem_test_every_n_seconds( "mem_test_every_n_seconds", "0", 0, "Run heap check at a specified interval\n" );
+
+static ConVar singlestep( "singlestep", "0", FCVAR_CHEAT, "Run engine in single step mode ( set next to 1 to advance a frame )" );
+static ConVar cvarNext( "next", "0", FCVAR_CHEAT, "Set to 1 to advance to next frame ( when singlestep == 1 )" );
+// Print a debug message when the client or server cache is missed
+ConVar host_showcachemiss( "host_showcachemiss", "0", 0, "Print a debug message when the client or server cache is missed." );
+static ConVar mem_dumpstats( "mem_dumpstats", "0", 0, "Dump current and max heap usage info to console at end of frame ( set to 2 for continuous output )\n" );
+static ConVar host_ShowIPCCallCount( "host_ShowIPCCallCount", "0", 0, "Print # of IPC calls this number of times per second. If set to -1, the # of IPC calls is shown every frame." );
+
+#if defined( RAD_TELEMETRY_ENABLED )
+static void OnChangeTelemetryPause ( IConVar *var, const char *pOldValue, float flOldValue )
+{
+ tmPause( TELEMETRY_LEVEL0, 1 );
+}
+
+static void OnChangeTelemetryResume ( IConVar *var, const char *pOldValue, float flOldValue )
+{
+ tmPause( TELEMETRY_LEVEL0, 0 );
+}
+
+static void OnChangeTelemetryLevel ( IConVar *var, const char *pOldValue, float flOldValue )
+{
+ char* pIEnd;
+ const char *pLevel = (( ConVar* )var)->GetString();
+
+ TelemetrySetLevel( strtoul( pLevel, &pIEnd, 0 ) );
+}
+
+static void OnChangeTelemetryFrameCount ( IConVar *var, const char *pOldValue, float flOldValue )
+{
+ char* pIEnd;
+ const char *pFrameCount = (( ConVar* )var)->GetString();
+
+ g_Telemetry.FrameCount = strtoul( pFrameCount, &pIEnd, 0 );
+ Msg( " TELEMETRY: Setting Telemetry FrameCount: '%d'\n", g_Telemetry.FrameCount );
+}
+
+static void OnChangeTelemetryServer ( IConVar *var, const char *pOldValue, float flOldValue )
+{
+ const char *pServerAddress = (( ConVar* )var)->GetString();
+
+ Q_strncpy( g_Telemetry.ServerAddress, pServerAddress, ARRAYSIZE( g_Telemetry.ServerAddress ) );
+ Msg( " TELEMETRY: Setting Telemetry server: '%s'\n", pServerAddress );
+}
+
+static void OnChangeTelemetryDemoStart ( IConVar *var, const char *pOldValue, float flOldValue )
+{
+ char* pIEnd;
+ const char *pVal = (( ConVar* )var)->GetString();
+
+ g_Telemetry.DemoTickStart = strtoul( pVal, &pIEnd, 0 );
+ if( g_Telemetry.DemoTickStart > 2000 )
+ {
+ char cmd[ 256 ];
+
+ // If we're far away from the start of the demo file, then jump to ~1000 ticks before.
+ Q_snprintf( cmd, sizeof( cmd ), "demo_gototick %d", g_Telemetry.DemoTickStart - 1000 );
+ Cbuf_AddText( cmd );
+ }
+ Msg( " TELEMETRY: Setting Telemetry DemoTickStart: '%d'\n", g_Telemetry.DemoTickStart );
+}
+
+static void OnChangeTelemetryDemoEnd ( IConVar *var, const char *pOldValue, float flOldValue )
+{
+ char* pIEnd;
+ const char *pVal = (( ConVar* )var)->GetString();
+
+ g_Telemetry.DemoTickEnd = strtoul( pVal, &pIEnd, 0 );
+ Msg( " TELEMETRY: Setting Telemetry DemoTickEnd: '%d'\n", g_Telemetry.DemoTickEnd );
+}
+
+ConVar telemetry_pause( "telemetry_pause", "0", 0, "Pause Telemetry", OnChangeTelemetryPause );
+ConVar telemetry_resume( "telemetry_resume", "0", 0, "Resume Telemetry", OnChangeTelemetryResume );
+ConVar telemetry_framecount( "telemetry_framecount", "0", 0, "Set Telemetry count of frames to capture", OnChangeTelemetryFrameCount );
+ConVar telemetry_level( "telemetry_level", "0", 0, "Set Telemetry profile level: 0 being off.", OnChangeTelemetryLevel );
+ConVar telemetry_server( "telemetry_server", "localhost", 0, "Set Telemetry server", OnChangeTelemetryServer );
+ConVar telemetry_demostart( "telemetry_demostart", "0", 0, "When playing demo, start telemetry on tick #", OnChangeTelemetryDemoStart );
+ConVar telemetry_demoend( "telemetry_demoend", "0", 0, "When playing demo, stop telemetry on tick #", OnChangeTelemetryDemoEnd );
+#endif
+
+extern bool gfBackground;
+
+static bool host_checkheap = false;
+
+CCommonHostState host_state;
+
+
+//-----------------------------------------------------------------------------
+
+enum HostThreadMode
+{
+ HTM_DISABLED,
+ HTM_DEFAULT,
+ HTM_FORCED,
+};
+
+ConVar host_thread_mode( "host_thread_mode", ( IsX360() ) ? "1" : "0", 0, "Run the host in threaded mode, (0 == off, 1 == if multicore, 2 == force)" );
+extern ConVar threadpool_affinity;
+void OnChangeThreadAffinity( IConVar *var, const char *pOldValue, float flOldValue )
+{
+ if ( g_pThreadPool->NumThreads() )
+ {
+ g_pThreadPool->Distribute( threadpool_affinity.GetBool() );
+ }
+}
+
+ConVar threadpool_affinity( "threadpool_affinity", "1", 0, "Enable setting affinity", 0, 0, 0, 0, &OnChangeThreadAffinity );
+
+#if 0
+extern ConVar threadpool_reserve;
+CThreadEvent g_ReleaseThreadReservation( true );
+CInterlockedInt g_NumReservedThreads;
+
+void ThreadPoolReserverFunction()
+{
+ g_ReleaseThreadReservation.Wait();
+ --g_NumReservedThreads;
+}
+
+void ReserveThreads( int nToReserve )
+{
+ nToReserve = clamp( nToReserve, 0, g_pThreadPool->NumThreads() );
+ g_ReleaseThreadReservation.Set();
+
+ while ( g_NumReservedThreads != 0 )
+ {
+ ThreadSleep( 0 );
+ }
+
+ g_ReleaseThreadReservation.Reset();
+
+ while ( nToReserve-- )
+ {
+ g_NumReservedThreads++;
+ g_pThreadPool->QueueCall( &ThreadPoolReserverFunction )->Release();
+ }
+
+ Msg( "%d threads being reserved\n", (int)g_NumReservedThreads );
+}
+
+void OnChangeThreadReserve( IConVar *var, const char *pOldValue, float flOldValue )
+{
+ ReserveThreads( threadpool_reserve.GetInt() );
+}
+
+ConVar threadpool_reserve( "threadpool_reserve", "0", 0, "Consume the specified number of threads in the thread pool", 0, 0, 0, 0, &OnChangeThreadReserve );
+
+CON_COMMAND( threadpool_cycle_reserve, "Cycles threadpool reservation by powers of 2" )
+{
+ int nCores = g_pThreadPool->NumThreads() + 1;
+ int nAvailableCores = nCores - g_NumReservedThreads;
+ Assert( nAvailableCores );
+ int ratio = nCores / nAvailableCores;
+ ratio *= 2;
+ if ( ratio > nCores )
+ {
+ ReserveThreads( 0 );
+ }
+ else
+ {
+ ReserveThreads( nCores - nCores / ratio );
+ }
+}
+
+CON_COMMAND( thread_test_tslist, "" )
+{
+ int nTests = ( args.ArgC() == 1 ) ? 10000 : atoi( args.Arg( 1 ) );
+ RunTSListTests( nTests );
+}
+
+CON_COMMAND( thread_test_tsqueue, "" )
+{
+ int nTests = ( args.ArgC() == 1 ) ? 10000 : atoi( args.Arg( 1 ) );
+ RunTSQueueTests( nTests );
+}
+
+CON_COMMAND( threadpool_run_tests, "" )
+{
+ int nTests = ( args.ArgC() == 1 ) ? 1 : atoi( args.Arg( 1 ) );
+ for ( int i = 0; i < nTests; i++ )
+ {
+ RunThreadPoolTests();
+ }
+}
+#endif
+
+//-----------------------------------------------------------------------------
+
+/*
+A server can always be started, even if the system started out as a client
+to a remote system.
+
+A client can NOT be started if the system started as a dedicated server.
+Memory is cleared / released when a server or client begins, not when they end.
+*/
+
+
+// Ear position + orientation
+static AudioState_t s_AudioState;
+
+engineparms_t host_parms;
+
+bool host_initialized = false; // true if into command execution
+
+float host_frametime = 0.0f;
+float host_frametime_unbounded = 0.0f;
+float host_frametime_stddeviation = 0.0f;
+double realtime = 0; // without any filtering or bounding
+double host_idealtime = 0; // "ideal" server time assuming perfect tick rate
+float host_nexttick = 0; // next server tick in this many ms
+float host_jitterhistory[128] = { 0 };
+unsigned int host_jitterhistorypos = 0;
+
+int host_framecount;
+static int host_hunklevel;
+
+CGameClient *host_client; // current client
+
+jmp_buf host_abortserver;
+jmp_buf host_enddemo;
+
+static ConVar host_profile( "host_profile","0" );
+
+ConVar host_limitlocal( "host_limitlocal", "0", 0, "Apply cl_cmdrate and cl_updaterate to loopback connection" );
+ConVar host_framerate( "host_framerate","0", 0, "Set to lock per-frame time elapse." );
+ConVar host_timescale( "host_timescale","1.0", FCVAR_REPLICATED, "Prescale the clock by this amount." );
+ConVar host_speeds( "host_speeds","0", 0, "Show general system running times." ); // set for running times
+
+ConVar host_flush_threshold( "host_flush_threshold", "20", 0, "Memory threshold below which the host should flush caches between server instances" );
+
+void HostTimerSpinMsChangedCallback( IConVar *var, const char *pOldString, float flOldValue );
+ConVar host_timer_spin_ms( "host_timer_spin_ms", "0", FCVAR_NONE, "Use CPU busy-loop for improved timer precision (dedicated only)", HostTimerSpinMsChangedCallback );
+
+void HostTimerSpinMsChangedCallback( IConVar *var, const char *pOldString, float flOldValue )
+{
+ const char *pForcedValue = CommandLine()->ParmValue( "+host_timer_spin_ms" );
+ if ( pForcedValue != NULL )
+ {
+ if ( V_strcmp( host_timer_spin_ms.GetString(), pForcedValue ) )
+ {
+ Msg( "Value for host_timer_spin_ms is locked to %s by command line parameter.\n", pForcedValue );
+ host_timer_spin_ms.SetValue( pForcedValue );
+ }
+ }
+}
+
+CON_COMMAND( host_timer_report, "Spew CPU timer jitter for the last 128 frames in microseconds (dedicated only)" )
+{
+ if ( sv.IsDedicated() )
+ {
+ for (int i = 1; i <= ARRAYSIZE( host_jitterhistory ); ++i)
+ {
+ unsigned int slot = ( i + host_jitterhistorypos ) % ARRAYSIZE( host_jitterhistory );
+ Msg( "%1.3fms\n", host_jitterhistory[ slot ] * 1000 );
+ }
+ }
+}
+
+#ifdef REL_TO_STAGING_MERGE_TODO
+// Do this when merging the game DLLs so FCVAR_CHEAT can be set on them at the same time.
+ConVar developer( "developer", "0", FCVAR_CHEAT, "Set developer message level");
+#else
+ConVar developer( "developer", "0", 0, "Set developer message level");
+#endif
+
+ConVar skill( "skill","1", FCVAR_ARCHIVE | FCVAR_ARCHIVE_XBOX, "Game skill level (1-3).", true, 1, true, 3 ); // 1 - 3
+ConVar deathmatch( "deathmatch","0", FCVAR_NOTIFY | FCVAR_INTERNAL_USE, "Running a deathmatch server." ); // 0, 1, or 2
+ConVar coop( "coop","0", FCVAR_NOTIFY, "Cooperative play." ); // 0 or 1
+
+#ifdef _DEBUG
+ConVar r_ForceRestore( "r_ForceRestore", "0", 0 );
+#endif // _DEBUG
+
+ConVar vcr_verbose( "vcr_verbose", "0", 0, "Write extra information into .vcr file." );
+
+#ifndef SWDS
+void CL_CheckToDisplayStartupMenus(); // in cl_main.cpp
+#endif
+
+
+bool GetFileFromRemoteStorage( ISteamRemoteStorage *pRemoteStorage, const char *pszRemoteFileName, const char *pszLocalFileName )
+{
+ bool bSuccess = false;
+
+ // check if file exists in Steam Cloud first
+ int32 nFileSize = pRemoteStorage->GetFileSize( pszRemoteFileName );
+
+ if ( nFileSize > 0 )
+ {
+ CUtlMemory<char> buf( 0, nFileSize );
+ if ( pRemoteStorage->FileRead( pszRemoteFileName, buf.Base(), nFileSize ) == nFileSize )
+ {
+
+ char filepath[ 512 ];
+ Q_strncpy( filepath, pszLocalFileName, sizeof( filepath ) );
+ Q_StripFilename( filepath );
+ g_pFullFileSystem->CreateDirHierarchy( filepath, "MOD" );
+
+ FileHandle_t hFile = g_pFileSystem->Open( pszLocalFileName, "wb", "MOD" );
+ if( hFile )
+ {
+
+ bSuccess = g_pFileSystem->Write( buf.Base(), nFileSize, hFile ) == nFileSize;
+ g_pFileSystem->Close( hFile );
+
+ if ( bSuccess )
+ {
+ DevMsg( "[Cloud]: SUCCEESS retrieved %s from remote storage into %s\n", pszRemoteFileName, pszLocalFileName );
+ }
+ else
+ {
+ DevMsg( "[Cloud]: FAILED retrieved %s from remote storage into %s\n", pszRemoteFileName, pszLocalFileName );
+ }
+ }
+ }
+ }
+
+ return bSuccess;
+}
+
+
+void CCommonHostState::SetWorldModel( model_t *pModel )
+{
+ worldmodel = pModel;
+ if ( pModel )
+ {
+ worldbrush = pModel->brush.pShared;
+ }
+ else
+ {
+ worldbrush = NULL;
+ }
+}
+
+void Host_DefaultMapFileName( const char *pFullMapName, /* out */ char *pDiskName, unsigned int nDiskNameSize )
+{
+ if ( IsPC() || !IsX360() )
+ {
+ // pc names are as is
+ Q_snprintf( pDiskName, nDiskNameSize, "maps/%s.bsp", pFullMapName );
+ }
+ else if ( IsX360() )
+ {
+ Q_snprintf( pDiskName, nDiskNameSize, "maps/%s.360.bsp", pFullMapName );
+ }
+}
+
+void Host_SetAudioState( const AudioState_t &audioState )
+{
+ memcpy( &s_AudioState, &audioState, sizeof(AudioState_t) );
+}
+
+bool Host_IsSinglePlayerGame( void )
+{
+ if ( sv.IsActive() )
+ {
+ return !sv.IsMultiplayer();
+ }
+ else
+ {
+ return cl.m_nMaxClients == 1;
+ }
+}
+
+void CheckForFlushMemory( const char *pCurrentMapName, const char *pDestMapName )
+{
+ if ( host_flush_threshold.GetInt() == 0 )
+ return;
+
+#if defined(_X360)
+ // There are three cases in which we flush memory
+ // Case 1: changing from one map to another
+ // -> flush temp data caches
+ // Case 2: loading any map (inc. A to A) and free memory is below host_flush_threshold MB
+ // -> flush everything
+ // Case 3: loading a 'blacklisted' map (the known biggest memory users, or where texture sets change)
+ // -> flush everything
+ static const char *mapBlackList[] =
+ {
+ // --hl2--
+ "d1_canals_01",
+ "d1_canals_05",
+ "d1_eli_01",
+ "d1_town_01",
+ "d2_coast_01",
+ "d2_prison_01",
+ "d3_c17_01",
+ "d3_c17_05",
+ "d3_c17_09",
+ "d3_citadel_01",
+ "d3_breen_01",
+ // --ep1--
+ "ep1_c17_02",
+ "ep1_c17_02b",
+ "ep1_c17_05",
+ "ep1_c17_06",
+ // --ep2--
+ "ep2_outland_06a",
+ "ep2_outland_09",
+ "ep2_outland_11",
+ "ep2_outland_12",
+ "ep2_outland_12a",
+ // --tf--
+ "tc_hydro"
+ };
+
+ char szCurrentMapName[MAX_PATH];
+ char szDestMapName[MAX_PATH];
+ if ( pCurrentMapName )
+ {
+ V_FileBase( pCurrentMapName, szCurrentMapName, sizeof( szCurrentMapName ) );
+ }
+ else
+ {
+ szCurrentMapName[0] = '\0';
+ }
+ pCurrentMapName = szCurrentMapName;
+
+ if ( pDestMapName )
+ {
+ V_FileBase( pDestMapName, szDestMapName, sizeof( szDestMapName ) );
+ }
+ else
+ {
+ szDestMapName[0] = '\0';
+ }
+ pDestMapName = szDestMapName;
+
+ bool bIsMapChanging = pCurrentMapName[0] && V_stricmp( pCurrentMapName, pDestMapName );
+
+ bool bIsDestMapBlacklisted = false;
+ for ( int i = 0; i < ARRAYSIZE( mapBlackList ); i++ )
+ {
+ if ( pDestMapName && !V_stricmp( pDestMapName, mapBlackList[i] ) )
+ {
+ bIsDestMapBlacklisted = true;
+ }
+ }
+
+ DevMsg( "---CURRENT(%s), NEXT(%s)\n", (pCurrentMapName[0] ? pCurrentMapName : "----"), (pDestMapName[0] ? pDestMapName : "----") );
+ if ( bIsMapChanging )
+ {
+ DevMsg( "---CHANGING MAPS!\n" );
+ }
+ if ( bIsDestMapBlacklisted )
+ {
+ DevMsg( "---BLACKLISTED!\n" );
+ }
+
+ MEMORYSTATUS stat;
+ GlobalMemoryStatus( &stat );
+ if ( ( stat.dwAvailPhys < host_flush_threshold.GetInt() * 1024 * 1024 ) ||
+ ( bIsDestMapBlacklisted && bIsMapChanging ) )
+ {
+ // Flush everything; ALL data is reloaded from scratch
+ SV_FlushMemoryOnNextServer();
+ g_pDataCache->Flush();
+ DevWarning( "---FULL FLUSH\n" );
+ }
+ else if ( bIsMapChanging )
+ {
+ // Flush temporary data (async anim, non-locked async audio)
+ g_pMDLCache->Flush( MDLCACHE_FLUSH_ANIMBLOCK );
+ wavedatacache->Flush();
+ DevWarning( "---PARTIAL FLUSH\n" );
+ }
+ DevMsg( "---- --- ----\n" );
+#endif
+}
+
+void Host_AbortServer()
+{
+ g_HostServerAbortCount++;
+ longjmp( host_abortserver, 1 );
+}
+
+/*
+================
+Host_EndGame
+================
+*/
+void Host_EndGame (bool bShowMainMenu, const char *message, ...)
+{
+ int oldn;
+ va_list argptr;
+ char string[1024];
+
+ va_start (argptr,message);
+ Q_vsnprintf (string,sizeof(string),message,argptr);
+ va_end (argptr);
+ ConMsg ("Host_EndGame: %s\n",string);
+
+#ifndef SWDS
+ scr_disabled_for_loading = true;
+#endif
+
+ oldn = cl.demonum;
+ cl.demonum = -1;
+
+ Host_Disconnect(bShowMainMenu);
+
+ cl.demonum = oldn;
+
+ if ( sv.IsDedicated() )
+ {
+ Sys_Error ("Host_EndGame: %s\n",string); // dedicated servers exit
+ return;
+ }
+
+ if (cl.demonum != -1)
+ {
+#ifndef SWDS
+ CL_NextDemo ();
+#endif
+ g_HostEndDemo++;
+ longjmp (host_enddemo, 1);
+ }
+ else
+ {
+
+#ifndef SWDS
+ scr_disabled_for_loading = false;
+#endif
+ if ( g_bAbortServerSet )
+ {
+ Host_AbortServer();
+ }
+ }
+}
+
+/*
+================
+Host_Error
+
+This shuts down both the client and server
+================
+*/
+void Host_Error (const char *error, ...)
+{
+ va_list argptr;
+ char string[1024];
+ static bool inerror = false;
+
+ DebuggerBreakIfDebugging_StagingOnly();
+
+ g_HostErrorCount++;
+
+ if (inerror)
+ {
+ Sys_Error ("Host_Error: recursively entered");
+ }
+ inerror = true;
+
+#ifndef SWDS
+ // CL_WriteMessageHistory(); TODO must be done by network layer
+#endif
+
+ va_start (argptr,error);
+ Q_vsnprintf(string,sizeof(string),error,argptr);
+ va_end (argptr);
+
+ if ( sv.IsDedicated() )
+ {
+ // dedicated servers just exit
+ Sys_Error( "Host_Error: %s\n", string );
+ return;
+ }
+
+#ifndef SWDS
+ // Reenable screen updates
+ SCR_EndLoadingPlaque ();
+#endif
+ ConMsg( "\nHost_Error: %s\n\n", string );
+
+ Host_Disconnect( true, string );
+
+ cl.demonum = -1;
+
+ inerror = false;
+
+ if ( g_bAbortServerSet )
+ {
+ Host_AbortServer();
+ }
+}
+
+#ifndef SWDS
+
+ButtonCode_t nInvalidKeyBindings[] =
+{
+ KEY_PAD_0,
+ KEY_PAD_1,
+ KEY_PAD_2,
+ KEY_PAD_3,
+ KEY_PAD_4,
+ KEY_PAD_5,
+ KEY_PAD_6,
+ KEY_PAD_7,
+ KEY_PAD_8,
+ KEY_PAD_9,
+ KEY_PAD_DIVIDE,
+ KEY_PAD_MULTIPLY,
+ KEY_PAD_MINUS,
+ KEY_PAD_PLUS,
+ KEY_PAD_ENTER,
+ KEY_PAD_DECIMAL,
+ KEY_ESCAPE,
+ KEY_SCROLLLOCK,
+ KEY_INSERT,
+ KEY_DELETE,
+ KEY_HOME,
+ KEY_END,
+ KEY_PAGEUP,
+ KEY_PAGEDOWN,
+ KEY_BREAK,
+ KEY_LSHIFT,
+ KEY_RSHIFT,
+ KEY_LALT,
+ KEY_RALT,
+ KEY_LCONTROL,
+ KEY_RCONTROL,
+ KEY_LWIN,
+ KEY_RWIN,
+ KEY_APP,
+ KEY_UP,
+ KEY_LEFT,
+ KEY_DOWN,
+ KEY_RIGHT,
+ KEY_CAPSLOCKTOGGLE,
+ KEY_NUMLOCKTOGGLE,
+ KEY_SCROLLLOCKTOGGLE,
+ KEY_BACKQUOTE,
+};
+
+//******************************************
+// SetupNewBindings
+//
+// In the rare case that we ship an update
+// where we need a key to be bound to a given
+// con-command, this function do its best to
+// bind those keys, based on a file called
+// newbindings.txt.
+//******************************************
+void SetupNewBindings()
+{
+ char szBindCmd[ 256 ];
+
+ // Load the file
+ const char *pFilename = "scripts\\newbindings.txt";
+ KeyValues *pNewBindingsData = new KeyValues( pFilename );
+ if ( !pNewBindingsData->LoadFromFile( g_pFileSystem, pFilename ) )
+ {
+ pNewBindingsData->deleteThis();
+ return;
+ }
+
+ FOR_EACH_TRUE_SUBKEY( pNewBindingsData, pSubKey )
+ {
+ // Get the binding
+ const char *pBinding = pSubKey->GetName();
+ if ( !pBinding )
+ continue;
+
+ // Get the ideal key
+ const char *pIdealKey = pSubKey->GetString( "ideal_key", NULL );
+ if ( !pIdealKey )
+ continue;
+
+ // Force the key binding if the ideal key is bound to an irrelevant command, which is the
+ // case for CS:S, which binds F6 to "quick save," which has no used in the game at all.
+ const char *pOverrideIfCmd = pSubKey->GetString( "override_if", NULL );
+ if ( pOverrideIfCmd )
+ {
+ const char *pCurrentBindingForKey = ::Key_BindingForKey( g_pInputSystem->StringToButtonCode( pIdealKey ) );
+ if ( !pCurrentBindingForKey || !V_stricmp( pOverrideIfCmd, pCurrentBindingForKey ) )
+ {
+ V_snprintf( szBindCmd, sizeof( szBindCmd ), "bind \"%s\" \"%s\"", pIdealKey, pBinding );
+ Cbuf_AddText( szBindCmd );
+ continue;
+ }
+ }
+
+ // Already have a key for this command?
+ if ( ::Key_NameForBinding( pBinding ) )
+ continue;
+
+ // No binding. Is the ideal key available?
+ ButtonCode_t bcIdealKey = g_pInputSystem->StringToButtonCode( pIdealKey );
+ const char *pCurrentBindingForIdealKey = ::Key_BindingForKey( bcIdealKey );
+ if ( !pCurrentBindingForIdealKey )
+ {
+ // Yes - bind to the ideal key.
+ V_snprintf( szBindCmd, sizeof( szBindCmd ), "bind \"%s\" \"%s\"", pIdealKey, pBinding );
+ Cbuf_AddText( szBindCmd );
+ continue;
+ }
+
+ // Ideal key already bound - find another key at random and bind it
+ bool bFound = false;
+ int nNumAttempts = 1;
+ for ( int nCurButton = (int)KEY_0; nCurButton <= (int)KEY_LAST; ++nCurButton, ++nNumAttempts )
+ {
+ // Don't consider numpad, windows keys, etc
+ bool bFoundInvalidKey = false;
+ for ( int iKeyIndex = 0; iKeyIndex < sizeof( nInvalidKeyBindings )/sizeof( nInvalidKeyBindings[0] ); iKeyIndex++ )
+ {
+ if ( nCurButton == (int)nInvalidKeyBindings[iKeyIndex] )
+ {
+ bFoundInvalidKey = true;
+ break;
+ }
+ }
+
+ if ( bFoundInvalidKey )
+ continue;
+
+ // Key available?
+ ButtonCode_t bcCurButton = (ButtonCode_t)nCurButton;
+ if ( !::Key_BindingForKey( bcCurButton ) )
+ {
+ // Yes - use it.
+ V_snprintf( szBindCmd, sizeof( szBindCmd ), "bind \"%s\" \"%s\"", g_pInputSystem->ButtonCodeToString( bcCurButton ), pBinding );
+ Cbuf_AddText( szBindCmd );
+ bFound = true;
+ break;
+ }
+ }
+
+ // We tried really hard - did we fail?
+ if ( !bFound )
+ {
+ Warning( "Unable to bind a key for command \"%s\" after %i attempt(s).\n", pBinding, nNumAttempts );
+ }
+ }
+}
+
+//******************************************
+// UseDefuaultBinding
+//
+// If the config.cfg file is not present, this
+// function is called to set the default key
+// bindings to match those defined in kb_def.lst
+//******************************************
+void UseDefaultBindings( void )
+{
+ FileHandle_t f;
+ char szFileName[ _MAX_PATH ];
+ char token[ 1024 ];
+ char szKeyName[ 256 ];
+
+ // read kb_def file to get default key binds
+ Q_snprintf( szFileName, sizeof( szFileName ), "%skb_def.lst", SCRIPT_DIR );
+ f = g_pFileSystem->Open( szFileName, "r");
+ if ( !f )
+ {
+ ConMsg( "Couldn't open kb_def.lst\n" );
+ return;
+ }
+
+ // read file into memory
+ int size = g_pFileSystem->Size(f);
+ char *startbuf = new char[ size ];
+ g_pFileSystem->Read( startbuf, size, f );
+ g_pFileSystem->Close( f );
+
+ const char *buf = startbuf;
+ while ( 1 )
+ {
+ buf = COM_ParseFile( buf, token, sizeof( token ) );
+ if ( strlen( token ) <= 0 )
+ break;
+ Q_strncpy ( szKeyName, token, sizeof( szKeyName ) );
+
+ buf = COM_ParseFile( buf, token, sizeof( token ) );
+ if ( strlen( token ) <= 0 ) // Error
+ break;
+
+ // finally, bind key
+ Key_SetBinding ( g_pInputSystem->StringToButtonCode( szKeyName ), token );
+ }
+ delete [] startbuf; // cleanup on the way out
+}
+
+static bool g_bConfigCfgExecuted = false;
+
+//-----------------------------------------------------------------------------
+// Purpose: Write out our 360 exclusive settings to internal storage
+//-----------------------------------------------------------------------------
+void Host_WriteConfiguration_360( void )
+{
+#ifdef _X360
+ if ( XBX_GetStorageDeviceId() == XBX_INVALID_STORAGE_ID || XBX_GetStorageDeviceId() == XBX_STORAGE_DECLINED )
+ return;
+
+ // Construct the name for our config settings for this mod
+ char strFilename[MAX_PATH];
+ Q_snprintf( strFilename, sizeof(strFilename), "cfg:/%s_config.cfg", GetCurrentMod() );
+
+ // Always throw away all keys that are left over.
+ CUtlBuffer configBuff( 0, 0, CUtlBuffer::TEXT_BUFFER);
+ configBuff.Printf( "unbindall\n" );
+
+ Key_WriteBindings( configBuff );
+ cv->WriteVariables( configBuff );
+
+ ConVarRef mat_monitorgamma( "mat_monitorgamma" );
+ ConVarRef mat_monitorgamma_tv_enabled( "mat_monitorgamma_tv_enabled" );
+
+ char strVideoFilename[MAX_PATH];
+ CUtlBuffer videoBuff( 0, 0, CUtlBuffer::TEXT_BUFFER);
+ Q_snprintf( strVideoFilename, sizeof(strVideoFilename), "cfg:/video_config.cfg" );
+ videoBuff.Printf( "mat_monitorgamma %f\n", mat_monitorgamma.GetFloat() );
+ videoBuff.Printf( "mat_monitorgamma_tv_enabled %d\n", mat_monitorgamma_tv_enabled.GetBool() );
+
+ // Anything to write?
+ if ( configBuff.TellMaxPut() )
+ {
+ g_pFileSystem->WriteFile( strFilename, NULL, configBuff );
+ }
+
+ if ( videoBuff.TellMaxPut() )
+ {
+ g_pFileSystem->WriteFile( strVideoFilename, NULL, videoBuff );
+ }
+
+ g_pXboxSystem->FinishContainerWrites();
+#endif // #ifdef _X360
+}
+
+/*
+===============
+Host_WriteConfiguration
+
+Writes key bindings and archived cvars to config.cfg
+===============
+*/
+
+void Host_WriteConfiguration( const char *filename, bool bAllVars )
+{
+ const bool cbIsUserRequested = ( filename != NULL );
+
+ if ( !filename )
+ filename = "config.cfg";
+
+ if ( !g_bConfigCfgExecuted )
+ return;
+
+ if ( !host_initialized )
+ return;
+
+ // Don't write config when in default--most of the values are defaults which is not what the player wants.
+ // If bAllVars is set, go ahead and write out the file anyways, since it was requested explicitly.
+ if ( !cbIsUserRequested && ( CommandLine()->CheckParm( "-default" ) || host_competitive_ever_enabled.GetBool() ) )
+ return;
+
+ // Write to internal storage on the 360
+ if ( IsX360() )
+ {
+ Host_WriteConfiguration_360();
+ return;
+ }
+
+ // If in map editing mode don't save configuration
+ if (g_bInEditMode)
+ {
+ ConMsg( "skipping %s output when in map edit mode\n", filename );
+ return;
+ }
+
+ // dedicated servers initialize the host but don't parse and set the
+ // config.cfg cvars
+ if ( sv.IsDedicated() )
+ return;
+
+ if ( IsPC() && Key_CountBindings() <= 1 )
+ {
+ ConMsg( "skipping %s output, no keys bound\n", filename );
+ return;
+ }
+
+ // force any queued convar changes to flush before reading/writing them
+ UpdateMaterialSystemConfig();
+
+ // Generate a new .cfg file.
+ char szFileName[MAX_PATH];
+ CUtlBuffer configBuff( 0, 0, CUtlBuffer::TEXT_BUFFER);
+
+ Q_snprintf( szFileName, sizeof(szFileName), "cfg/%s", filename );
+ g_pFileSystem->CreateDirHierarchy( "cfg", "MOD" );
+ if ( g_pFileSystem->FileExists( szFileName, "MOD" ) && !g_pFileSystem->IsFileWritable( szFileName, "MOD" ) )
+ {
+ ConMsg( "Config file %s is read-only!!\n", szFileName );
+ return;
+ }
+
+ // Always throw away all keys that are left over.
+ configBuff.Printf( "unbindall\n" );
+
+ Key_WriteBindings( configBuff );
+ cv->WriteVariables( configBuff, bAllVars );
+
+#if !defined( SWDS )
+ bool down;
+ if ( g_ClientDLL->IN_IsKeyDown( "in_jlook", down ) && down )
+ {
+ configBuff.Printf( "+jlook\n" );
+ }
+#endif // SWDS
+
+ if ( !configBuff.TellMaxPut() )
+ {
+ // nothing to write
+ return;
+ }
+
+#if defined(NO_STEAM)
+ AssertMsg( false, "SteamCloud not available on Xbox 360. Badger Martin to fix this." );
+#else
+ ISteamRemoteStorage *pRemoteStorage = SteamClient()?(ISteamRemoteStorage *)SteamClient()->GetISteamGenericInterface(
+ SteamAPI_GetHSteamUser(), SteamAPI_GetHSteamPipe(), STEAMREMOTESTORAGE_INTERFACE_VERSION ):NULL;
+
+ if ( pRemoteStorage )
+ {
+ int32 availableBytes, totalBytes = 0;
+ if ( pRemoteStorage->GetQuota( &totalBytes, &availableBytes ) )
+ {
+ if ( totalBytes > 0 )
+ {
+ if ( cl_cloud_settings.GetInt() == STEAMREMOTESTORAGE_CLOUD_ON )
+ {
+ // TODO put MOD dir in pathname
+ if ( pRemoteStorage->FileWrite( szFileName, configBuff.Base(), configBuff.TellMaxPut() ) )
+ {
+ DevMsg( "[Cloud]: SUCCEESS saving %s in remote storage\n", szFileName );
+ }
+ else
+ {
+ // probably a quota issue. TODO what to do ?
+ DevMsg( "[Cloud]: FAILED saving %s in remote storage\n", szFileName );
+ }
+
+ // write the current logo file
+ char szLogoFileName[MAX_PATH];
+ Q_strncpy( szLogoFileName, cl_logofile.GetString(), sizeof(szLogoFileName) ); // .vtf file
+
+ if ( g_pFileSystem->FileExists( szLogoFileName, "MOD" ) )
+ {
+ // store logo .VTF file
+ FileHandle_t hFile = g_pFileSystem->Open( szLogoFileName, "rb", "MOD" );
+ if ( FILESYSTEM_INVALID_HANDLE != hFile )
+ {
+ unsigned int unSize = g_pFileSystem->Size( hFile );
+
+ byte *pBuffer = (byte*) malloc( unSize );
+ if ( g_pFileSystem->Read( pBuffer, unSize, hFile ) == unSize )
+ {
+ Q_SetExtension( g_szDefaultLogoFileName, ".vtf", sizeof(g_szDefaultLogoFileName) );
+ if ( pRemoteStorage->FileWrite( g_szDefaultLogoFileName, pBuffer, unSize ) )
+ {
+ DevMsg( "[Cloud]: SUCCEESS saving %s in remote storage\n", g_szDefaultLogoFileName );
+ }
+ else
+ {
+ DevMsg( "[Cloud]: FAILED saving %s in remote storage\n", g_szDefaultLogoFileName );
+ }
+ }
+ free( pBuffer );
+ g_pFileSystem->Close( hFile );
+ }
+
+ // store logo .VMT file
+ Q_SetExtension( szLogoFileName, ".vmt", sizeof(szLogoFileName) );
+ hFile = g_pFileSystem->Open( szLogoFileName, "rb", "MOD" );
+ if ( FILESYSTEM_INVALID_HANDLE != hFile )
+ {
+ unsigned int unSize = g_pFileSystem->Size( hFile );
+
+ byte *pBuffer = (byte*) malloc( unSize );
+ if ( g_pFileSystem->Read( pBuffer, unSize, hFile ) == unSize )
+ {
+ Q_SetExtension( g_szDefaultLogoFileName, ".vmt", sizeof(g_szDefaultLogoFileName) );
+ if ( pRemoteStorage->FileWrite( g_szDefaultLogoFileName, pBuffer, unSize ) )
+ {
+ DevMsg( "[Cloud]: SUCCEESS saving %s in remote storage\n", g_szDefaultLogoFileName );
+ }
+ else
+ {
+ DevMsg( "[Cloud]: FAILED saving %s in remote storage\n", g_szDefaultLogoFileName );
+ }
+ }
+ free( pBuffer );
+ g_pFileSystem->Close( hFile );
+ }
+ }
+ }
+ }
+ }
+
+ // even if SteamCloud worked we still safe the same file locally
+ }
+#endif
+
+
+ // make a persistent copy that async will use and free
+ char *tempBlock = new char[configBuff.TellMaxPut()];
+ Q_memcpy( tempBlock, configBuff.Base(), configBuff.TellMaxPut() );
+
+ // async write the buffer, and then free it
+ g_pFileSystem->AsyncWrite( szFileName, tempBlock, configBuff.TellMaxPut(), true );
+
+ ConMsg( "Host_WriteConfiguration: Wrote %s\n", szFileName );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Retrieve and set any defaults from the user's gamer profile
+//-----------------------------------------------------------------------------
+bool XBX_SetProfileDefaultSettings( void )
+{
+ // These defined values can't play nicely with the PC, so we need to ignore them for that build target
+#ifdef _X360
+ // These will act as indices into the array that is returned by the API
+ enum
+ {
+ XPS_GAMER_DIFFICULTY,
+ XPS_GAMER_ACTION_MOVEMENT_CONTROL,
+ XPS_GAMER_YAXIS_INVERSION,
+ XPS_OPTION_CONTROLLER_VIBRATION,
+ NUM_PROFILE_SETTINGS
+ };
+
+ // These are the values we're interested in having returned (must match the indices above)
+ const DWORD dwSettingIds[ NUM_PROFILE_SETTINGS ] =
+ {
+ XPROFILE_GAMER_DIFFICULTY,
+ XPROFILE_GAMER_ACTION_MOVEMENT_CONTROL,
+ XPROFILE_GAMER_YAXIS_INVERSION,
+ XPROFILE_OPTION_CONTROLLER_VIBRATION
+ };
+
+ // Must have a valid primary user by this point
+ int nPrimaryID = XBX_GetPrimaryUserId();
+
+ // First, we call with a NULL pointer and zero size to retrieve the buffer size we'll get back
+ DWORD dwResultSize = 0; // Must be zero to get the correct size back
+ XUSER_READ_PROFILE_SETTING_RESULT *pResults = NULL;
+ DWORD dwError = XUserReadProfileSettings( 0, // Family ID (current title)
+ nPrimaryID, // User ID
+ NUM_PROFILE_SETTINGS,
+ dwSettingIds,
+ &dwResultSize,
+ pResults,
+ NULL );
+
+ // We need this to inform us that it's given us a size back for the buffer
+ if ( dwError != ERROR_INSUFFICIENT_BUFFER )
+ return false;
+
+ // Now we allocate that buffer and supply it to the call
+ BYTE *pData = (BYTE *) stackalloc( dwResultSize );
+ ZeroMemory( pData, dwResultSize );
+
+ pResults = (XUSER_READ_PROFILE_SETTING_RESULT *) pData;
+
+ dwError = XUserReadProfileSettings( 0, // Family ID (current title)
+ nPrimaryID, // User ID
+ NUM_PROFILE_SETTINGS,
+ dwSettingIds,
+ &dwResultSize,
+ pResults,
+ NULL ); // Not overlapped, must be synchronous
+
+ // We now have a raw buffer of results
+ if ( dwError != ERROR_SUCCESS )
+ return false;
+
+ //
+ // Skill
+ //
+
+ XUSER_PROFILE_SETTING *pSetting = pResults->pSettings + XPS_GAMER_DIFFICULTY;
+ Assert( pSetting->data.type == XUSER_DATA_TYPE_INT32 );
+
+ int nSkillSetting = pSetting->data.nData;
+ int nResultSkill = 0;
+ switch( nSkillSetting )
+ {
+ case XPROFILE_GAMER_DIFFICULTY_NORMAL:
+ nResultSkill = 2;
+ break;
+
+ case XPROFILE_GAMER_DIFFICULTY_HARD:
+ nResultSkill = 3;
+ break;
+
+ case XPROFILE_GAMER_DIFFICULTY_EASY:
+ default:
+ nResultSkill = 1;
+ break;
+ }
+
+ // If the mod has no difficulty setting, only easy is allowed
+ KeyValues *modinfo = new KeyValues("ModInfo");
+ if ( modinfo->LoadFromFile( g_pFileSystem, "gameinfo.txt" ) )
+ {
+ if ( stricmp(modinfo->GetString("nodifficulty", "0"), "1") == 0 )
+ nResultSkill = 1;
+ }
+ modinfo->deleteThis();
+
+ char szScratch[MAX_PATH];
+ Q_snprintf( szScratch, sizeof(szScratch), "skill %d", nResultSkill );
+ Cbuf_AddText( szScratch );
+
+ //
+ // Movement control
+ //
+
+ pSetting = pResults->pSettings + XPS_GAMER_ACTION_MOVEMENT_CONTROL;
+ Assert( pSetting->data.type == XUSER_DATA_TYPE_INT32 );
+ Q_snprintf( szScratch, sizeof(szScratch), "joy_movement_stick %d", ( pSetting->data.nData == XPROFILE_ACTION_MOVEMENT_CONTROL_L_THUMBSTICK ) ? 0 : 1 );
+ Cbuf_AddText( szScratch );
+ Q_snprintf( szScratch, sizeof(szScratch), "joy_movement_stick_default %d", ( pSetting->data.nData == XPROFILE_ACTION_MOVEMENT_CONTROL_L_THUMBSTICK ) ? 0 : 1 );
+ Cbuf_AddText( szScratch );
+ Cbuf_AddText( "joyadvancedupdate" );
+
+ //
+ // Y-Inversion
+ //
+
+ pSetting = pResults->pSettings + XPS_GAMER_YAXIS_INVERSION;
+ Assert( pSetting->data.type == XUSER_DATA_TYPE_INT32 );
+ Q_snprintf( szScratch, sizeof(szScratch), "joy_inverty %d", pSetting->data.nData );
+ Cbuf_AddText( szScratch );
+ Q_snprintf( szScratch, sizeof(szScratch), "joy_inverty_default %d", pSetting->data.nData );
+ Cbuf_AddText( szScratch );
+
+ //
+ // Vibration control
+ //
+
+ pSetting = pResults->pSettings + XPS_OPTION_CONTROLLER_VIBRATION;
+ Assert( pSetting->data.type == XUSER_DATA_TYPE_INT32 );
+ Q_snprintf( szScratch, sizeof(szScratch), "cl_rumblescale %d", ( pSetting->data.nData != 0 ) ? 1 : 0 );
+ Cbuf_AddText( szScratch );
+
+ // Execute all commands we've queued up
+ Cbuf_Execute();
+#endif // _X360
+
+ return true;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Read our configuration from the 360, filling in defaults on our first run
+//-----------------------------------------------------------------------------
+void Host_ReadConfiguration_360( void )
+{
+#ifdef _X360
+
+ // Exec our defaults on the first pass
+ if ( g_bConfigCfgExecuted == false )
+ {
+ // First, we exec our default configuration for the 360
+ Cbuf_AddText( "exec config.360.cfg game\n" );
+ Cbuf_Execute();
+ }
+
+ // Can't do any more in this function if we don't have a valid user id
+ if ( XBX_GetPrimaryUserId() == INVALID_USER_ID )
+ return;
+
+ // Build the config name we're looking for
+ char strFileName[MAX_PATH];
+ Q_snprintf( strFileName, sizeof(strFileName), "cfg:/%s_config.cfg", GetCurrentMod() );
+
+ bool bStorageDeviceValid = ( XBX_GetStorageDeviceId() != XBX_INVALID_STORAGE_ID && XBX_GetStorageDeviceId() != XBX_STORAGE_DECLINED );
+ bool bSaveConfig = false;
+
+ // Call through normal API function once the content container is opened
+ if ( CommandLine()->CheckParm( "-forcexboxreconfig" ) || bStorageDeviceValid == false || g_pFileSystem->FileExists( strFileName ) == false )
+ {
+ // If we've already done this in this session, never do it again (we don't want to stomp their settings under any circumstances)
+ if ( g_bConfigCfgExecuted == false )
+ {
+ // Get and set all our default setting we care about from the Xbox
+ XBX_SetProfileDefaultSettings();
+ }
+
+ // Save out what we have
+ bSaveConfig = true;
+ }
+ else
+ {
+ // Otherwise, exec the user settings stored on the 360
+ char szCommand[MAX_PATH];
+ Q_snprintf( szCommand, sizeof( szCommand ), "exec %s_config.cfg x360\n", GetCurrentMod() );
+ Cbuf_AddText( szCommand );
+
+ // Exec the video config as well
+ Q_snprintf( szCommand, sizeof( szCommand ), "exec video_config.cfg x360\n", GetCurrentMod() );
+ Cbuf_AddText( szCommand );
+ Cbuf_Execute();
+ }
+
+ // Mark that we've loaded a config and can now save it
+ g_bConfigCfgExecuted = true;
+
+ if ( bSaveConfig )
+ {
+ // An ugly hack, but we can probably save this safely
+ bool saveinit = host_initialized;
+ host_initialized = true;
+ Host_WriteConfiguration_360();
+ host_initialized = saveinit;
+ }
+#endif // _X360
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : false -
+//-----------------------------------------------------------------------------
+void Host_ReadConfiguration()
+{
+ if ( sv.IsDedicated() )
+ return;
+
+ // Rebind keys and set cvars
+ if ( !g_pFileSystem )
+ {
+ Sys_Error( "Host_ReadConfiguration: g_pFileSystem == NULL\n" );
+ }
+
+ // Handle the 360 case
+ if ( IsX360() )
+ {
+ Host_ReadConfiguration_360();
+ return;
+ }
+
+ bool saveconfig = false;
+
+ ISteamRemoteStorage *pRemoteStorage = SteamClient()?(ISteamRemoteStorage *)SteamClient()->GetISteamGenericInterface(
+ SteamAPI_GetHSteamUser(), SteamAPI_GetHSteamPipe(), STEAMREMOTESTORAGE_INTERFACE_VERSION ):NULL;
+
+ if ( pRemoteStorage )
+ {
+ // if cloud settings is default but remote storage does not exist yet, set it to sync all because this is the first
+ // computer the game is run on--default to copying everything to the cloud
+ if ( !pRemoteStorage->FileExists( "cfg/config.cfg" ) )
+ {
+ DevMsg( "[Cloud]: Default setting with remote data non-existent, sync all\n" );
+ cl_cloud_settings.SetValue( STEAMREMOTESTORAGE_CLOUD_ON );
+ }
+
+ if ( cl_cloud_settings.GetInt() == STEAMREMOTESTORAGE_CLOUD_ON )
+ {
+ // config files are run through the exec command which got pretty complicated with all the splitscreen
+ // stuff. Steam UFS doens't support split screen well (2 users ?)
+ GetFileFromRemoteStorage( pRemoteStorage, "cfg/config.cfg", "cfg/config.cfg" );
+ }
+ }
+
+ if ( g_pFileSystem->FileExists( "//mod/cfg/config.cfg" ) )
+ {
+ Cbuf_AddText( "exec config.cfg\n" );
+ }
+ else
+ {
+ Cbuf_AddText( "exec config_default.cfg\n" );
+ saveconfig = true;
+ }
+
+ Cbuf_Execute();
+
+ if ( pRemoteStorage )
+ {
+ if ( cl_cloud_settings.GetInt() == STEAMREMOTESTORAGE_CLOUD_ON )
+ {
+ // get logo .VTF file
+ Q_SetExtension( g_szDefaultLogoFileName, ".vtf", sizeof(g_szDefaultLogoFileName) );
+ GetFileFromRemoteStorage( pRemoteStorage, g_szDefaultLogoFileName, g_szDefaultLogoFileName );
+
+ cl_logofile.SetValue( g_szDefaultLogoFileName );
+
+ // get logo .VMT file
+ Q_SetExtension( g_szDefaultLogoFileName, ".vmt", sizeof(g_szDefaultLogoFileName) );
+ GetFileFromRemoteStorage( pRemoteStorage, g_szDefaultLogoFileName, g_szDefaultLogoFileName );
+ }
+ }
+
+ // check to see if we actually set any keys, if not, load defaults from kb_def.lst
+ // so we at least have basics setup.
+ int nNumBinds = Key_CountBindings();
+ if ( nNumBinds == 0 )
+ {
+ UseDefaultBindings();
+ }
+ else
+ {
+ // Setup new bindings - if we ship an update that requires that every user has a key bound for
+ // X concommand, SetupNewBindings() will do its best to ensure that a key is bound. We assume
+ // that kb_def.lst includes any bindings that SetupNewBindings() might include in the case
+ // that nNumBinds == 0 above.
+ SetupNewBindings();
+ }
+
+ Key_SetBinding( KEY_ESCAPE, "cancelselect" );
+
+ // Make sure that something is always bound to console
+ if (NULL == Key_NameForBinding("toggleconsole"))
+ {
+ // If nothing is bound to it then bind it to '
+ Key_SetBinding( KEY_BACKQUOTE, "toggleconsole" );
+ }
+
+ SetupDefaultAskConnectAcceptKey();
+
+ g_bConfigCfgExecuted = true;
+
+ if ( saveconfig )
+ {
+ // An ugly hack, but we can probably save this safely
+ bool saveinit = host_initialized;
+ host_initialized = true;
+ Host_WriteConfiguration();
+ host_initialized = saveinit;
+ }
+}
+
+CON_COMMAND( host_writeconfig, "Store current settings to config.cfg (or specified .cfg file)." )
+{
+ if ( args.ArgC() > 3 )
+ {
+ ConMsg( "Usage: writeconfig <filename.cfg> [full]\n" );
+ ConMsg( "<filename.cfg> is required, optionally specify \"full\" to write out all archived convars.\n" );
+ ConMsg( "By default, only non-default values are written out.\n" );
+ return;
+ }
+
+ if ( args.ArgC() >= 2 )
+ {
+ bool bWriteAll = ( args.ArgC() == 3 && V_stricmp( args[ 2 ], "full" ) == 0 );
+
+ char const *filename = args[ 1 ];
+ if ( !filename || !filename[ 0 ] )
+ {
+ return;
+ }
+
+ char outfile[ MAX_QPATH ];
+ // Strip path and extension from filename
+ Q_FileBase( filename, outfile, sizeof( outfile ) );
+ Host_WriteConfiguration( va( "%s.cfg", outfile ), bWriteAll );
+ if ( !bWriteAll )
+ ConMsg( "Wrote partial config file \"%s\" out, to write full file use host_writeconfig \"%s\" full\n", outfile, outfile );
+ }
+ else
+ {
+ Host_WriteConfiguration( NULL, true );
+ }
+}
+
+#endif
+
+//-----------------------------------------------------------------------------
+// Purpose: Does a quick parse of the config.cfg to read cvars that
+// need to be read before any games systems are initialized
+// assumes only cvars and filesystem are initialized
+//-----------------------------------------------------------------------------
+void Host_ReadPreStartupConfiguration()
+{
+ FileHandle_t f = NULL;
+ if ( IsX360() )
+ {
+ // 360 config is less restrictive and can be anywhere in the game path
+ f = g_pFileSystem->Open( "//game/cfg/config.360.cfg", "rt" );
+ }
+ else
+ {
+ f = g_pFileSystem->Open( "//mod/cfg/config.cfg", "rt" );
+ }
+
+ if ( !f )
+ return;
+
+ // read file into memory
+ int size = g_pFileSystem->Size(f);
+ char *configBuffer = new char[ size + 1 ];
+ g_pFileSystem->Read( configBuffer, size, f );
+ configBuffer[size] = 0;
+ g_pFileSystem->Close( f );
+
+ // parse out file
+ static const char *s_PreStartupConfigConVars[] =
+ {
+ "sv_unlockedchapters", // needed to display the startup graphic while loading
+ "snd_legacy_surround", // needed to init the sound system
+ "gameui_xbox", // needed to initialize the correct UI
+ "save_in_memory" // needed to preread data from the correct location in UI
+ };
+
+ // loop through looking for all the cvars to apply
+ for (int i = 0; i < ARRAYSIZE(s_PreStartupConfigConVars); i++)
+ {
+ const char *search = Q_stristr(configBuffer, s_PreStartupConfigConVars[i]);
+ if (search)
+ {
+ // read over the token
+ search = COM_Parse(search);
+
+ // read the value
+ COM_Parse(search);
+
+ // apply the value
+ ConVar *var = (ConVar *)g_pCVar->FindVar( s_PreStartupConfigConVars[i] );
+ if ( var )
+ {
+ var->SetValue( com_token );
+ }
+ }
+ }
+
+ // free
+ delete [] configBuffer;
+}
+
+void Host_RecomputeSpeed_f( void )
+{
+ ConMsg( "Recomputing clock speed...\n" );
+
+ CClockSpeedInit::Init();
+ ConMsg( "Clock speed: %.0f Mhz\n", CFastTimer::GetClockSpeed() / 1000000.0 );
+}
+
+static ConCommand recompute_speed( "recompute_speed", Host_RecomputeSpeed_f, "Recomputes clock speed (for debugging purposes).", FCVAR_CHEAT );
+
+void DTI_Flush_f()
+{
+ DTI_Flush();
+ ServerDTI_Flush();
+}
+
+static ConCommand dti_flush( "dti_flush", DTI_Flush_f, "Write out the datatable instrumentation files (you must run with -dti for this to work)." );
+
+/*
+==================
+Host_ShutdownServer
+
+This only happens at the end of a game, not between levels
+==================
+*/
+void Host_ShutdownServer( void )
+{
+ if ( !sv.IsActive() )
+ return;
+
+ Host_AllowQueuedMaterialSystem( false );
+ // clear structures
+#if !defined( SWDS )
+ g_pShadowMgr->LevelShutdown();
+#endif
+ StaticPropMgr()->LevelShutdown();
+
+ Host_FreeStateAndWorld( true );
+ sv.Shutdown();// sv.Shutdown() references some heap memory, so run it before Host_FreeToLowMark()
+ Host_FreeToLowMark( true );
+
+ IGameEvent *event = g_GameEventManager.CreateEvent( "server_shutdown" );
+
+ if ( event )
+ {
+ event->SetString( "reason", "restart" );
+ g_GameEventManager.FireEvent( event );
+ }
+
+ g_Log.Close();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : time -
+// Output : bool
+//-----------------------------------------------------------------------------
+void Host_AccumulateTime( float dt )
+{
+ // Accumulate some time
+ realtime += dt;
+
+ bool bUseNormalTickTime = true;
+#if !defined(SWDS)
+ if ( demoplayer->IsPlayingTimeDemo() )
+ bUseNormalTickTime = false;
+#endif
+ if ( g_bDedicatedServerBenchmarkMode )
+ bUseNormalTickTime = false;
+
+ if ( bUseNormalTickTime )
+ {
+ host_frametime = dt;
+ }
+ else
+ {
+ // Used to help increase reproducibility of timedemos
+ host_frametime = host_state.interval_per_tick;
+ }
+
+#if 1
+ if ( host_framerate.GetFloat() > 0
+#if !defined(SWDS)
+ && ( CanCheat() || demoplayer->IsPlayingBack() )
+#endif
+ )
+ {
+ float fps = host_framerate.GetFloat();
+ if ( fps > 1 )
+ {
+ fps = 1.0f/fps;
+ }
+ host_frametime = fps;
+
+#if !defined(SWDS) && defined( REPLAY_ENABLED )
+ extern IDemoPlayer *g_pReplayDemoPlayer;
+ if ( demoplayer->IsPlayingBack() && demoplayer == g_pReplayDemoPlayer )
+ {
+ // adjust time scale if playing back demo
+ host_frametime *= demoplayer->GetPlaybackTimeScale();
+ }
+#endif
+
+ host_frametime_unbounded = host_frametime;
+ }
+ else if (host_timescale.GetFloat() > 0
+#if !defined(SWDS)
+ && ( CanCheat() || demoplayer->IsPlayingBack() )
+#endif
+ )
+ {
+ float fullscale = host_timescale.GetFloat();
+
+#if !defined(SWDS)
+ if ( demoplayer->IsPlayingBack() )
+ {
+ // adjust time scale if playing back demo
+ fullscale *= demoplayer->GetPlaybackTimeScale();
+ }
+#endif
+
+ host_frametime *= fullscale;
+
+ host_frametime_unbounded = host_frametime;
+
+#ifndef NO_TOOLFRAMEWORK
+ if ( CommandLine()->CheckParm( "-tools" ) == NULL )
+ {
+#endif
+ host_frametime = min( (double)host_frametime, MAX_FRAMETIME * fullscale);
+#ifndef NO_TOOLFRAMEWORK
+ }
+#endif
+ }
+ else
+#ifndef NO_TOOLFRAMEWORK
+ if ( CommandLine()->CheckParm( "-tools" ) != NULL )
+ {
+ host_frametime_unbounded = host_frametime;
+ }
+ else
+#endif // !NO_TOOLFRAMEWORK
+ { // don't allow really long or short frames
+ host_frametime_unbounded = host_frametime;
+ host_frametime = min( (double)host_frametime, MAX_FRAMETIME );
+ host_frametime = max( (double)host_frametime, MIN_FRAMETIME );
+ }
+#endif
+
+ // Adjust the client clock very slightly to keep it in line with the server clock.
+ float adj = cl.GetClockDriftMgr().AdjustFrameTime( host_frametime ) - host_frametime;
+ host_frametime += adj;
+ host_frametime_unbounded += adj;
+
+ if ( g_pSoundServices ) // not present on linux server
+ g_pSoundServices->SetSoundFrametime(dt, host_frametime);
+
+}
+
+#define FPS_AVG_FRAC 0.9f
+
+float g_fFramesPerSecond = 0.0f;
+
+/*
+==================
+Host_PostFrameRate
+==================
+*/
+void Host_PostFrameRate( float frameTime )
+{
+ frameTime = clamp( frameTime, 0.0001f, 1.0f );
+
+ float fps = 1.0f / frameTime;
+ g_fFramesPerSecond = g_fFramesPerSecond * FPS_AVG_FRAC + ( 1.0f - FPS_AVG_FRAC ) * fps;
+}
+
+/*
+==================
+Host_GetHostInfo
+==================
+*/
+void Host_GetHostInfo(float *fps, int *nActive, int *nMaxPlayers, char *pszMap, int maxlen )
+{
+ // Count clients, report
+ int clients = sv.GetNumClients();
+
+ *fps = g_fFramesPerSecond;
+ *nActive = clients;
+
+ if (pszMap)
+ {
+ if (sv.m_szMapname && sv.m_szMapname[0])
+ Q_strncpy(pszMap, sv.m_szMapname, maxlen );
+ else
+ pszMap[0] = '\0';
+ }
+
+ *nMaxPlayers = sv.GetMaxClients();
+}
+
+static bool AppearsNumeric( char const *in )
+{
+ char const *p = in;
+ int special[ 3 ];
+ Q_memset( special, 0, sizeof( special ) );
+
+ for ( ; *p; p++ )
+ {
+ if ( *p == '-' )
+ {
+ special[0]++;
+ continue;
+ }
+
+ if ( *p == '+' )
+ {
+ special[1]++;
+ continue;
+ }
+
+ if ( *p >= '0' && *p <= '9' )
+ {
+ continue;
+ }
+
+ if ( *p == '.' )
+ {
+ special[2]++;
+ continue;
+ }
+
+ return false;
+ }
+
+ // Can't have multiple +, -, or decimals
+ for ( int i = 0; i < 3; i++ )
+ {
+ if ( special[ i ] > 1 )
+ return false;
+ }
+
+ // can't be + and - at same time
+ if ( special[ 0 ] && special[ 1 ] )
+ return false;
+
+ return true;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: If the value is numeric, remove unnecessary trailing zeros
+// Input : *invalue -
+// Output : char const
+//-----------------------------------------------------------------------------
+char const * Host_CleanupConVarStringValue( char const *invalue )
+{
+ static char clean[ 256 ];
+
+ Q_snprintf( clean, sizeof( clean ), "%s", invalue );
+
+ // Don't mess with empty string
+ // Otherwise, if it appears numeric and has a decimal, try to strip all zeroes after decimal
+ if ( Q_strlen( clean ) >= 1 && AppearsNumeric( clean ) && Q_strstr( clean, "." ) )
+ {
+ char *end = clean + strlen( clean ) - 1;
+ while ( *end && end >= clean )
+ {
+ // Removing trailing zeros
+ if ( *end != '0' )
+ {
+ // Remove decimal, zoo
+ if ( *end == '.' )
+ {
+ if ( end == clean )
+ {
+ *end = '0';
+ }
+ else
+ {
+ *end = 0;
+ }
+ }
+ break;
+ }
+
+ *end-- = 0;
+ }
+ }
+
+ return clean;
+}
+
+int Host_CountVariablesWithFlags( int flags, bool nonDefault )
+{
+ int i = 0;
+ const ConCommandBase *var;
+
+ for ( var = g_pCVar->GetCommands() ; var ; var=var->GetNext() )
+ {
+ if ( var->IsCommand() )
+ continue;
+
+ const ConVar *pCvar = ( const ConVar * )var;
+
+ if ( !pCvar->IsFlagSet( flags ) )
+ continue;
+
+ // It's == to the default value, don't count
+ if ( nonDefault && !Q_strcasecmp( pCvar->GetDefault(), pCvar->GetString() ) )
+ continue;
+
+ i++;
+ }
+
+ return i;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : msg -
+//-----------------------------------------------------------------------------
+void Host_BuildConVarUpdateMessage( NET_SetConVar *cvarMsg, int flags, bool nonDefault )
+{
+ int count = Host_CountVariablesWithFlags( flags, nonDefault );
+
+ // Nothing to send
+ if ( count <= 0 )
+ return;
+
+ // Too many to send, error out and have mod author get a clue.
+ if ( count > 255 )
+ {
+ Sys_Error( "Engine only supports 255 ConVars marked %i\n", flags );
+ return;
+ }
+
+ const ConCommandBase *var;
+
+ for ( var = g_pCVar->GetCommands() ; var ; var=var->GetNext() )
+ {
+ if ( var->IsCommand() )
+ continue;
+
+ const ConVar *pCvar = ( const ConVar * )var;
+
+ if ( !pCvar->IsFlagSet( flags ) )
+ continue;
+
+ // It's == to the default value, don't count
+ if ( nonDefault && !Q_strcasecmp( pCvar->GetDefault(), pCvar->GetString() ) )
+ continue;
+
+ NET_SetConVar::cvar_t acvar;
+
+ Q_strncpy( acvar.name, pCvar->GetName(), MAX_OSPATH );
+ Q_strncpy( acvar.value, Host_CleanupConVarStringValue( pCvar->GetString() ), MAX_OSPATH );
+
+ cvarMsg->m_ConVars.AddToTail( acvar );
+ }
+
+ // Make sure this count matches original one!!!
+ Assert( cvarMsg->m_ConVars.Count() == count );
+}
+
+#if !defined( SWDS )
+// FIXME: move me somewhere more appropriate
+void CL_SendVoicePacket(bool bFinal)
+{
+#if !defined( NO_VOICE )
+ if ( !Voice_IsRecording() )
+ return;
+
+ CLC_VoiceData voiceMsg;
+
+ // Get whatever compressed data there is and and send it.
+ char uchVoiceData[2048];
+
+ voiceMsg.m_DataOut.StartWriting( uchVoiceData, sizeof(uchVoiceData) );
+
+ voiceMsg.m_nLength = Voice_GetCompressedData( uchVoiceData, sizeof(uchVoiceData), bFinal ) * 8;
+
+ if( !voiceMsg.m_nLength )
+ return;
+
+ voiceMsg.m_DataOut.SeekToBit( voiceMsg.m_nLength ); // set correct writing position
+
+ if ( cl.IsActive() )
+ {
+ cl.m_NetChannel->SendNetMsg( voiceMsg );
+ }
+#endif
+}
+
+#if defined ( _X360 )
+
+
+void CL_ProcessXboxVoiceData()
+{
+ if ( Audio_GetXVoice() == NULL )
+ return;
+
+ if ( Audio_GetXVoice()->VoiceUpdateData() == true )
+ {
+ if ( cl.IsActive() )
+ {
+ Audio_GetXVoice()->VoiceSendData( cl.m_NetChannel );
+ }
+ }
+}
+
+#endif
+
+void CL_ProcessVoiceData()
+{
+ VPROF_BUDGET( "CL_ProcessVoiceData", VPROF_BUDGETGROUP_OTHER_NETWORKING );
+
+#if !defined( NO_VOICE )
+ Voice_Idle(host_frametime);
+ CL_SendVoicePacket(false);
+#endif
+
+#if defined ( _X360 )
+
+ CL_ProcessXboxVoiceData();
+#endif
+
+}
+#endif
+
+
+
+
+
+
+/*
+=====================
+Host_UpdateScreen
+
+Refresh the screen
+=====================
+*/
+void Host_UpdateScreen( void )
+{
+#ifndef SWDS
+
+#ifdef _DEBUG
+ if( r_ForceRestore.GetInt() )
+ {
+ ForceMatSysRestore();
+ r_ForceRestore.SetValue(0);
+ }
+#endif // _DEBUG
+
+ // Refresh the screen
+ SCR_UpdateScreen ();
+#endif
+}
+
+/*
+====================
+Host_UpdateSounds
+
+Update sound subsystem and cd audio
+====================
+*/
+void Host_UpdateSounds( void )
+{
+#if !defined( SWDS )
+ // update audio
+ if ( cl.IsActive() )
+ {
+ S_Update( &s_AudioState );
+ }
+ else
+ {
+ S_Update( NULL );
+ }
+#endif
+}
+
+/*
+==============================
+Host_Speeds
+
+==============================
+*/
+void CFrameTimer::ResetDeltas()
+{
+ for ( int i = 0; i < NUM_FRAME_SEGMENTS; i++ )
+ {
+ deltas[ i ] = 0.0f;
+ }
+}
+
+void CFrameTimer::MarkFrame()
+{
+ double frameTime;
+ double fps;
+
+ // ConDMsg("%f %f %f\n", time1, time2, time3 );
+
+ float fs_input = (deltas[FRAME_SEGMENT_INPUT])*1000.0;
+ float fs_client = (deltas[FRAME_SEGMENT_CLIENT])*1000.0;
+ float fs_server = (deltas[FRAME_SEGMENT_SERVER])*1000.0;
+ float fs_render = (deltas[FRAME_SEGMENT_RENDER])*1000.0;
+ float fs_sound = (deltas[FRAME_SEGMENT_SOUND])*1000.0;
+ float fs_cldll = (deltas[FRAME_SEGMENT_CLDLL])*1000.0;
+ float fs_exec = (deltas[FRAME_SEGMENT_CMD_EXECUTE])*1000.0;
+
+ ResetDeltas();
+
+ frameTime = host_frametime;
+ //frameTime /= 1000.0;
+ if ( frameTime < 0.0001 )
+ {
+ fps = 999.0;
+ }
+ else
+ {
+ fps = 1.0 / frameTime;
+ }
+
+ if (host_speeds.GetInt())
+ {
+ int ent_count = 0;
+ int i;
+ static int last_host_tickcount;
+
+ int ticks = host_tickcount - last_host_tickcount;
+ last_host_tickcount = host_tickcount;
+
+ // count used entities
+ for (i=0 ; i<sv.num_edicts ; i++)
+ {
+ if (!sv.edicts[i].IsFree())
+ ent_count++;
+ }
+
+ char sz[ 256 ];
+ Q_snprintf( sz, sizeof( sz ),
+ "%3i fps -- inp(%.2f) sv(%.2f) cl(%.2f) render(%.2f) snd(%.2f) cl_dll(%.2f) exec(%.2f) ents(%d) ticks(%d)",
+ (int)fps,
+ fs_input,
+ fs_server,
+ fs_client,
+ fs_render,
+ fs_sound,
+ fs_cldll,
+ fs_exec,
+ ent_count,
+ ticks );
+
+#ifndef SWDS
+ if ( host_speeds.GetInt() >= 2 )
+ {
+ Con_NPrintf ( 0, sz );
+ }
+ else
+ {
+ ConDMsg ( "%s\n", sz );
+ }
+#endif
+ }
+
+}
+
+#define FRAME_TIME_FILTER_TIME 0.5f
+
+void CFrameTimer::ComputeFrameVariability()
+{
+ m_pFrameTimeHistory[m_nFrameTimeHistoryIndex] = frametime;
+ if ( ++m_nFrameTimeHistoryIndex >= FRAME_HISTORY_COUNT )
+ {
+ m_nFrameTimeHistoryIndex = 0;
+ }
+
+ // Compute a low-pass filter of the frame time over the last half-second
+ // Count the number of samples that live within the last half-second
+ int i = m_nFrameTimeHistoryIndex;
+ int nMaxSamples = 0;
+ float flTotalTime = 0.0f;
+ while( (nMaxSamples < FRAME_HISTORY_COUNT) && (flTotalTime <= FRAME_TIME_FILTER_TIME) )
+ {
+ if ( --i < 0 )
+ {
+ i = FRAME_HISTORY_COUNT - 1;
+ }
+ if ( m_pFrameTimeHistory[i] == 0.0f )
+ break;
+
+ flTotalTime += m_pFrameTimeHistory[i];
+ ++nMaxSamples;
+ }
+
+ if ( nMaxSamples == 0 )
+ {
+ m_flFPSVariability = 0.0f;
+ m_flFPSStdDeviationSeconds = 0.0f;
+ return;
+ }
+
+ float flExponent = -2.0f / (int)nMaxSamples;
+
+ i = m_nFrameTimeHistoryIndex;
+ float flAverageTime = 0.0f;
+ float flExpCurveArea = 0.0f;
+ int n = 0;
+ while( n < nMaxSamples )
+ {
+ if ( --i < 0 )
+ {
+ i = FRAME_HISTORY_COUNT - 1;
+ }
+ flExpCurveArea += exp( flExponent * n );
+ flAverageTime += m_pFrameTimeHistory[i] * exp( flExponent * n );
+ ++n;
+ }
+
+ flAverageTime /= flExpCurveArea;
+
+ float flAveFPS = 0.0f;
+ if ( flAverageTime != 0.0f )
+ {
+ flAveFPS = 1.0f / flAverageTime;
+ }
+
+ float flCurrentFPS = 0.0f;
+ if ( frametime != 0.0f )
+ {
+ flCurrentFPS = 1.0f / frametime;
+ }
+
+ // Now subtract out the current fps to get variability in FPS
+ m_flFPSVariability = fabs( flCurrentFPS - flAveFPS );
+
+ // Now compute variance/stddeviation
+ double sum = 0.0f;
+ int count =0;
+ for ( int j = 0; j < FRAME_HISTORY_COUNT; ++j )
+ {
+ if ( m_pFrameTimeHistory[ j ] == 0.0f )
+ continue;
+
+ double ft = min( (double)m_pFrameTimeHistory[ j ], 0.25 );
+
+ sum += ft;
+ ++count;
+
+ }
+
+ if ( count <= 1 )
+ {
+ return;
+ }
+
+ double avg = sum / (double)count;
+ double devSquared = 0.0f;
+ for ( int j = 0; j < FRAME_HISTORY_COUNT; ++j )
+ {
+ if ( m_pFrameTimeHistory[ j ] == 0.0f )
+ continue;
+
+ double ft = min( (double)m_pFrameTimeHistory[ j ], 0.25 );
+
+ double dt = ft - avg;
+
+ devSquared += ( dt * dt );
+ }
+
+ double variance = devSquared / (double)( count - 1 );
+ m_flFPSStdDeviationSeconds = sqrt( variance );
+}
+
+void Host_Speeds()
+{
+ g_HostTimes.MarkFrame();
+#if !defined(SWDS)
+ ToClientDemoPlayer( demoplayer )->MarkFrame( g_HostTimes.m_flFPSVariability );
+#endif
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: When singlestep == 1, then you must set next == 1 to run to the
+// next frame.
+// Output : Returns true on success, false on failure.
+//-----------------------------------------------------------------------------
+bool Host_ShouldRun( void )
+{
+ static int current_tick = -1;
+
+ // See if we are single stepping
+ if ( !singlestep.GetInt() )
+ {
+ return true;
+ }
+
+ // Did user set "next" to 1?
+ if ( cvarNext.GetInt() )
+ {
+ // Did we finally finish this frame ( Host_ShouldRun is called in 3 spots to pause
+ // three different things ).
+ if ( current_tick != (host_tickcount-1) )
+ {
+ // Okay, time to reset to halt execution again
+ cvarNext.SetValue( 0 );
+ return false;
+ }
+
+ // Otherwise, keep running this one frame since we are still finishing this frame
+ return true;
+ }
+ else
+ {
+ // Remember last frame without "next" being reset ( time is locked )
+ current_tick = host_tickcount;
+ // Time is locked
+ return false;
+ }
+}
+
+static ConVar mem_periodicdumps( "mem_periodicdumps", "0", 0, "Write periodic memstats dumps every n seconds." );
+static double g_flLastPeriodicMemDump = -1.0f;
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+static float g_TimeLastMemTest;
+void Host_CheckDumpMemoryStats( void )
+{
+ if ( mem_test_each_frame.GetBool() )
+ {
+ if ( !MemAlloc_CrtCheckMemory() )
+ {
+ DebuggerBreakIfDebugging();
+ Error( "Heap is corrupt\n" );
+ }
+ }
+ else if ( mem_test_every_n_seconds.GetInt() > 0 )
+ {
+ float now = Plat_FloatTime();
+ if ( now - g_TimeLastMemTest > mem_test_every_n_seconds.GetInt() )
+ {
+ g_TimeLastMemTest = now;
+ if ( !MemAlloc_CrtCheckMemory() )
+ {
+ DebuggerBreakIfDebugging();
+ Error( "Heap is corrupt\n" );
+ }
+ }
+ }
+
+ if ( mem_periodicdumps.GetFloat() > 0.0f )
+ {
+ double curtime = Plat_FloatTime();
+ if ( curtime - g_flLastPeriodicMemDump > mem_periodicdumps.GetFloat() )
+ {
+ const char *pTest = sv.GetMapName();
+ if ( !pTest || !pTest[0] )
+ {
+ // possibly at menu
+ pTest = "nomap";
+ }
+
+ char mapname[ 256 ];
+ Q_FileBase( pTest, mapname, sizeof( mapname ) );
+#if defined( _MEMTEST )
+ MemAlloc_SetStatsExtraInfo( pTest, "" );
+#endif
+ MemAlloc_DumpStatsFileBase( mapname );
+ g_flLastPeriodicMemDump = curtime;
+ }
+ }
+
+
+#if defined(_WIN32)
+ if ( mem_dumpstats.GetInt() <= 0 )
+ return;
+
+ if ( mem_dumpstats.GetInt() == 1 )
+ mem_dumpstats.SetValue( 0 ); // reset cvar, dump stats only once
+
+ _CrtMemState state;
+ Q_memset( &state, 0, sizeof( state ) );
+ _CrtMemCheckpoint( &state );
+
+ unsigned int size = 0;
+
+ for ( int use = 0; use < _MAX_BLOCKS; use++)
+ {
+ size += state.lSizes[ use ];
+ }
+ Msg("MEMORY: Run-time Heap\n------------------------------------\n");
+
+ Msg( "\tHigh water %s\n", Q_pretifymem( state.lHighWaterCount,4 ) );
+ Msg( "\tCurrent mem %s\n", Q_pretifymem( size,4 ) );
+ Msg("------------------------------------\n");
+ int hunk = Hunk_MallocSize();
+ Msg("\tAllocated outside hunk: %s\n", Q_pretifymem( size - hunk ) );
+#endif
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void _Host_SetGlobalTime()
+{
+ // Server
+ g_ServerGlobalVariables.realtime = realtime;
+ g_ServerGlobalVariables.framecount = host_framecount;
+ g_ServerGlobalVariables.absoluteframetime = host_frametime;
+ g_ServerGlobalVariables.interval_per_tick = host_state.interval_per_tick;
+ g_ServerGlobalVariables.serverCount = Host_GetServerCount();
+#ifndef SWDS
+ // Client
+ g_ClientGlobalVariables.realtime = realtime;
+ g_ClientGlobalVariables.framecount = host_framecount;
+ g_ClientGlobalVariables.absoluteframetime = host_frametime;
+ g_ClientGlobalVariables.interval_per_tick = host_state.interval_per_tick;
+#endif
+}
+
+/*
+==================
+_Host_RunFrame
+
+Runs all active servers
+==================
+*/
+
+void _Host_RunFrame_Input( float accumulated_extra_samples, bool bFinalTick )
+{
+ VPROF_BUDGET( "_Host_RunFrame_Input", _T("Input") );
+
+ // Run a test script?
+ static bool bFirstFrame = true;
+ if ( bFirstFrame )
+ {
+ bFirstFrame = false;
+ const char *pScriptFilename = CommandLine()->ParmValue( "-testscript" );
+ if ( pScriptFilename )
+ {
+ if ( !GetTestScriptMgr()->StartTestScript( pScriptFilename ) )
+ {
+ Error( "StartTestScript( %s ) failed.", pScriptFilename );
+ }
+ }
+ }
+
+ g_HostTimes.StartFrameSegment( FRAME_SEGMENT_INPUT );
+
+#ifndef SWDS
+ // Client can process input
+ ClientDLL_ProcessInput( );
+
+ g_HostTimes.StartFrameSegment( FRAME_SEGMENT_CMD_EXECUTE );
+
+ // process console commands
+ Cbuf_Execute ();
+
+ g_HostTimes.EndFrameSegment( FRAME_SEGMENT_CMD_EXECUTE );
+
+ // Send any current movement commands to server and flush reliable buffer even if not moving yet.
+ CL_Move( accumulated_extra_samples, bFinalTick );
+
+#endif
+
+ g_HostTimes.EndFrameSegment( FRAME_SEGMENT_INPUT );
+}
+
+void _Host_RunFrame_Server( bool finaltick )
+{
+ VPROF_BUDGET( "_Host_RunFrame_Server", VPROF_BUDGETGROUP_GAME );
+ VPROF_INCREMENT_COUNTER( "ticks", 1 );
+ tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s", __FUNCTION__ );
+
+ // Run the Server frame ( read, run physics, respond )
+ g_HostTimes.StartFrameSegment( FRAME_SEGMENT_SERVER );
+ SV_Frame ( finaltick );
+ g_HostTimes.EndFrameSegment( FRAME_SEGMENT_SERVER );
+
+ // Look for connectionless rcon packets on dedicated servers
+ // SV_CheckRcom(); TODO
+}
+
+void _Host_RunFrame_Server_Async( int numticks )
+{
+ tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s %d", __FUNCTION__, numticks );
+
+ for ( int tick = 0; tick < numticks; tick++ )
+ {
+ g_ServerGlobalVariables.tickcount = sv.m_nTickCount;
+ g_ServerGlobalVariables.simTicksThisFrame = numticks - tick;
+ bool bFinalTick = ( tick == (numticks - 1) );
+ _Host_RunFrame_Server( bFinalTick );
+ }
+}
+
+
+void _Host_RunFrame_Client( bool framefinished )
+{
+#ifndef SWDS
+ VPROF( "_Host_RunFrame_Client" );
+ tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s %d", __FUNCTION__, framefinished );
+
+ g_HostTimes.StartFrameSegment( FRAME_SEGMENT_CLIENT );
+
+ // Get any current state update from server, etc.
+ CL_ReadPackets( framefinished );
+
+#if defined( VOICE_OVER_IP )
+ // Send any enqueued voice data to the server
+ CL_ProcessVoiceData();
+#endif // VOICE_OVER_IP
+
+ cl.CheckUpdatingSteamResources();
+ cl.CheckFileCRCsWithServer();
+
+ // Resend connection request if needed.
+ cl.RunFrame();
+
+ if ( CL_IsHL2Demo() || CL_IsPortalDemo() ) // don't need sv.IsDedicated() because ded servers don't run this
+ {
+ void CL_DemoCheckGameUIRevealTime();
+ CL_DemoCheckGameUIRevealTime();
+ }
+
+ Steam3Client().RunFrame();
+
+ g_HostTimes.EndFrameSegment( FRAME_SEGMENT_CLIENT );
+
+ // This takes 1 usec, so it's pretty cheap...
+ CL_SetPagedPoolInfo();
+#endif
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Used to set limits on certain convars in multiplayer/sv_cheats mode.
+// Returns true if it was called recursively and it early-outed.
+//-----------------------------------------------------------------------------
+bool CheckVarRange_Generic( ConVar *pVar, int minVal, int maxVal )
+{
+ if ( !CanCheat() && !Host_IsSinglePlayerGame() )
+ {
+ int clampedValue = clamp( pVar->GetInt(), minVal, maxVal );
+ if ( clampedValue != pVar->GetInt() )
+ {
+ Warning( "sv_cheats=0 prevented changing %s outside of the range [%d,%d] (was %d).\n", pVar->GetName(), minVal, maxVal, pVar->GetInt() );
+ pVar->SetValue( clampedValue );
+ }
+ }
+
+ return false;
+}
+
+
+void CheckSpecialCheatVars()
+{
+ static ConVar *mat_picmip = NULL;
+ if ( !mat_picmip )
+ mat_picmip = g_pCVar->FindVar( "mat_picmip" );
+
+ // In multiplayer, don't allow them to set mat_picmip > 2.
+ if ( mat_picmip )
+ CheckVarRange_Generic( mat_picmip, -1, 2 );
+
+ CheckVarRange_r_rootlod();
+ CheckVarRange_r_lod();
+ HandleServerAllowColorCorrection();
+}
+
+
+void _Host_RunFrame_Render()
+{
+#ifndef SWDS
+ VPROF( "_Host_RunFrame_Render" );
+ tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "_Host_RunFrame_Render" );
+
+ CheckSpecialCheatVars();
+
+ int nOrgNoRendering = mat_norendering.GetInt();
+
+ if ( cl_takesnapshot )
+ {
+ // turn off no-rendering mode, if taking screenshot
+ mat_norendering.SetValue( 0 );
+ }
+
+ // update video if not running in background
+ g_HostTimes.StartFrameSegment( FRAME_SEGMENT_RENDER );
+
+ CL_LatchInterpolationAmount();
+
+ {
+ VPROF( "_Host_RunFrame_Render - UpdateScreen" );
+ tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "_Host_RunFrame_Render - UpdateScreen" );
+ Host_UpdateScreen();
+ }
+ {
+ VPROF( "_Host_RunFrame_Render - CL_DecayLights" );
+ tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "_Host_RunFrame_Render - CL_DecayLights" );
+ CL_DecayLights ();
+ }
+
+ g_HostTimes.EndFrameSegment( FRAME_SEGMENT_RENDER );
+
+ saverestore->OnFrameRendered();
+
+#ifdef USE_SDL
+ if ( g_pLauncherMgr )
+ {
+ g_pLauncherMgr->OnFrameRendered();
+ }
+#endif
+
+ mat_norendering.SetValue( nOrgNoRendering );
+#endif
+}
+
+void CL_FindInterpolatedAddAngle( float t, float& frac, AddAngle **prev, AddAngle **next )
+{
+ int c = cl.addangle.Count();
+
+ *prev = NULL;
+ *next = NULL;
+
+ AddAngle *pentry = NULL;
+ for ( int i = 0; i < c; i++ )
+ {
+ AddAngle *entry = &cl.addangle[ i ];
+
+ *next = entry;
+
+ // Time is earlier
+ if ( t < entry->starttime )
+ {
+ if ( i == 0 )
+ {
+ *prev = *next;
+ frac = 0.0f;
+ return;
+ }
+
+ // Avoid div by zero
+ if ( entry->starttime == pentry->starttime )
+ {
+ frac = 0.0f;
+ return;
+ }
+
+ // Time spans the two entries
+ frac = ( t - pentry->starttime ) / ( entry->starttime - pentry->starttime );
+ frac = clamp( frac, 0.0f, 1.0f );
+ return;
+ }
+
+ *prev = *next;
+ pentry = entry;
+ }
+}
+
+void CL_DiscardOldAddAngleEntries( float t )
+{
+ float killtime = t - host_state.interval_per_tick - 0.1f;
+
+ for ( int i = 0; i < cl.addangle.Count(); i++ )
+ {
+ AddAngle *p = &cl.addangle[ i ];
+ if ( p->starttime <= killtime )
+ {
+ cl.addangle.Remove( i );
+ --i;
+ }
+ }
+
+ // It's safe to reset the master counter once all the entries decay
+ if ( cl.addangle.Count() == 0 )
+ {
+ cl.prevaddangletotal = cl.addangletotal = 0.0f;
+ }
+}
+
+#ifndef SWDS
+void CL_ApplyAddAngle()
+{
+ tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s", __FUNCTION__ );
+
+ float curtime = cl.GetTime() - host_state.interval_per_tick;
+
+ AddAngle *prev = NULL, *next = NULL;
+ float frac = 0.0f;
+
+ float addangletotal = 0.0f;
+
+ CL_FindInterpolatedAddAngle( curtime, frac, &prev, &next );
+
+ if ( prev && next )
+ {
+ addangletotal = prev->total + frac * ( next->total - prev->total );
+ }
+ else
+ {
+ addangletotal = cl.prevaddangletotal;
+ }
+
+ float amove = addangletotal - cl.prevaddangletotal;
+
+ // Update view angles
+ cl.viewangles[ 1 ] += amove;
+ // Update client .dll view of angles
+ g_pClientSidePrediction->SetLocalViewAngles( cl.viewangles );
+
+ // Remember last total
+ cl.prevaddangletotal = addangletotal;
+
+ CL_DiscardOldAddAngleEntries( curtime );
+}
+#endif
+
+void _Host_RunFrame_Sound()
+{
+#ifndef SWDS
+
+ VPROF_BUDGET( "_Host_RunFrame_Sound", VPROF_BUDGETGROUP_OTHER_SOUND );
+
+ g_HostTimes.StartFrameSegment( FRAME_SEGMENT_SOUND );
+
+ Host_UpdateSounds();
+
+ g_HostTimes.EndFrameSegment( FRAME_SEGMENT_SOUND );
+#endif
+}
+
+float Host_GetSoundDuration( const char *pSample )
+{
+#ifndef SWDS
+ if (!sv.IsDedicated())
+ {
+ extern float SV_GetSoundDuration(const char *pSample);
+ extern float AudioSource_GetSoundDuration(CSfxTable *pSfx);
+ int index = cl.LookupSoundIndex(pSample);
+ if (index >= 0)
+ return AudioSource_GetSoundDuration(cl.GetSound(index));
+ return SV_GetSoundDuration(pSample);
+ }
+#endif
+ return 0.0f;
+}
+
+CON_COMMAND( host_runofftime, "Run off some time without rendering/updating sounds\n" )
+{
+ if ( args.ArgC() != 2 )
+ {
+ ConMsg( "Usage: host_runofftime <seconds>\n" );
+ return;
+ }
+
+ if ( !sv.IsActive() )
+ {
+ ConMsg( "host_ruofftime: must be running a server\n" );
+ return;
+ }
+
+ if ( sv.IsMultiplayer() )
+ {
+ ConMsg( "host_ruofftime: only valid in single player\n" );
+ return;
+ }
+
+ float advanceTime = atof( args[1] );
+ if ( advanceTime <= 0.0f )
+ return;
+
+ // 15 minutes is a _long_ time!!!
+ if ( advanceTime > 15.0f * 60.0f )
+ {
+ ConMsg( "host_runofftime would run off %.2f minutes!!! ignoring\n",
+ advanceTime / 60.0f );
+ return;
+ }
+
+ ConMsg( "Skipping ahead for %f seconds\n", advanceTime );
+
+ SCR_UpdateScreen();
+ SCR_UpdateScreen ();
+}
+
+#if !defined( _X360 )
+S_API int SteamGameServer_GetIPCCallCount();
+#else
+S_API int SteamGameServer_GetIPCCallCount() { return 0; }
+#endif
+void Host_ShowIPCCallCount()
+{
+ // If set to 0 then get out.
+ if ( host_ShowIPCCallCount.GetInt() == 0 )
+ return;
+
+ static float s_flLastTime = 0;
+ static int s_nLastTick = host_tickcount;
+ static int s_nLastFrame = host_framecount;
+
+ // Figure out how often they want to update.
+ double flInterval = 0;
+ if ( host_ShowIPCCallCount.GetFloat() > 0 )
+ {
+ flInterval = 1.0f / host_ShowIPCCallCount.GetFloat();
+ }
+
+ // This is called every frame so increment the frame counter.
+ double flCurTime = Plat_FloatTime();
+ if ( flCurTime - s_flLastTime >= flInterval )
+ {
+ uint32 callCount;
+ ISteamClient *pSteamClient = SteamClient();
+ if ( pSteamClient )
+ {
+ callCount = pSteamClient->GetIPCCallCount();
+ }
+ else
+ {
+ // Ok, we're a dedicated server and we need to use this to get it.
+ callCount = (uint32)SteamGameServer_GetIPCCallCount();
+ }
+
+ // Avoid a divide by zero.
+ int frameCount = host_framecount - s_nLastFrame;
+ int tickCount = host_tickcount - s_nLastTick;
+ if ( frameCount == 0 || tickCount == 0 )
+ return;
+
+ Msg( "host_ShowIPCCallCount: %d IPC calls in the past [%d frames, %d ticks] Avg: [%.2f/frame, %.2f/tick]\n",
+ callCount, frameCount, tickCount, (float)callCount / frameCount, (float)callCount / tickCount );
+
+ s_flLastTime = flCurTime;
+ s_nLastTick = host_tickcount;
+ s_nLastFrame = host_framecount;
+ }
+}
+
+void Host_SetClientInSimulation( bool bInSimulation )
+{
+#ifndef SWDS
+ // Tracker 77931: If the game is paused, then lock the client clock at the previous tick boundary
+ // (otherwise we'll keep interpolating through the "remainder" time causing the paused characters
+ // to twitch like they have the shakes)
+ // TODO: Since this rounds down on the frame we paused, we could see a slight backsliding. We could remember the last "remainder" before pause and re-use it and
+ // set insimulation == false to be mroe exact. We'd still have to deal with the timing difference between
+ // when pause/unpause happens on the server versus the client
+ cl.insimulation = bInSimulation || cl.IsPaused();
+
+ // Compute absolute/render time stamp
+ g_ClientGlobalVariables.curtime = cl.GetTime();
+ g_ClientGlobalVariables.frametime = cl.GetFrameTime();
+#endif
+}
+
+static ConVar host_Sleep( "host_sleep", "0", FCVAR_CHEAT, "Force the host to sleep a certain number of milliseconds each frame." );
+extern ConVar sv_alternateticks;
+#define LOG_FRAME_OUTPUT 0
+
+void _Host_RunFrame (float time)
+{
+ MDLCACHE_COARSE_LOCK_(g_pMDLCache);
+ static double host_remainder = 0.0f;
+ double prevremainder;
+ bool shouldrender;
+
+#if defined( RAD_TELEMETRY_ENABLED )
+ if( g_Telemetry.DemoTickEnd == ( uint32 )-1 )
+ {
+ Cbuf_AddText( "quit\n" );
+ }
+#endif
+
+ int numticks;
+ {
+ // Profile scope specific to the top of this function, protect from setjmp() problems
+ VPROF( "_Host_RunFrame_Upto_MarkFrame" );
+
+ if ( host_checkheap )
+ {
+#if defined(_WIN32)
+ if ( _heapchk() != _HEAPOK )
+ {
+ Sys_Error( "_Host_RunFrame (top): _heapchk() != _HEAPOK\n" );
+ }
+#endif
+ }
+
+ // When playing back a VCR file, don't do host_sleep. That way, if it was recorded with
+ // host_sleep on, it'll play back way Faster.
+ if ( host_Sleep.GetInt() && VCRGetMode() != VCR_Playback )
+ {
+ Sys_Sleep( host_Sleep.GetInt() );
+ }
+
+ // Slow down the playback?
+ if ( g_iVCRPlaybackSleepInterval )
+ {
+ Sys_Sleep( g_iVCRPlaybackSleepInterval );
+ }
+
+ MapReslistGenerator().RunFrame();
+
+ static int lastrunoffsecond = -1;
+
+ if ( setjmp ( host_enddemo ) )
+ return; // demo finished.
+
+ // decide the simulation time
+ Host_AccumulateTime ( time );
+ _Host_SetGlobalTime();
+
+ shouldrender = !sv.IsDedicated();
+
+ // FIXME: Could track remainder as fractional ticks instead of msec
+ prevremainder = host_remainder;
+ if ( prevremainder < 0 )
+ prevremainder = 0;
+
+ #if !defined(SWDS)
+ if ( !demoplayer->IsPlaybackPaused() )
+ #endif
+ {
+ host_remainder += host_frametime;
+ }
+
+ numticks = 0; // how many ticks we will simulate this frame
+ if ( host_remainder >= host_state.interval_per_tick )
+ {
+ numticks = (int)( floor( host_remainder / host_state.interval_per_tick ) );
+
+ // round to nearest even ending tick in alternate ticks mode so the last
+ // tick is always simulated prior to updating the network data
+ // NOTE: alternate ticks only applies in SP!!!
+ if ( Host_IsSinglePlayerGame() &&
+ sv_alternateticks.GetBool() )
+ {
+ int startTick = g_ServerGlobalVariables.tickcount;
+ int endTick = startTick + numticks;
+ endTick = AlignValue( endTick, 2 );
+ numticks = endTick - startTick;
+ }
+
+ host_remainder -= numticks * host_state.interval_per_tick;
+ }
+
+ host_nexttick = host_state.interval_per_tick - host_remainder;
+
+ g_pMDLCache->MarkFrame();
+ }
+
+ {
+ // Profile scope, protect from setjmp() problems
+ VPROF( "_Host_RunFrame" );
+ tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "_Host_RunFrame" );
+
+ g_HostTimes.StartFrameSegment( FRAME_SEGMENT_CMD_EXECUTE );
+
+ // process console commands
+ Cbuf_Execute ();
+
+ // initialize networking for dedicated server after commandline & autoexec.cfg have been parsed
+ if ( NET_IsDedicated() && !NET_IsMultiplayer() )
+ NET_SetMutiplayer( true );
+
+ g_HostTimes.EndFrameSegment( FRAME_SEGMENT_CMD_EXECUTE );
+
+ // Msg( "Running %i ticks (%f remainder) for frametime %f total %f tick %f delta %f\n", numticks, remainder, host_frametime, host_time );
+ g_ServerGlobalVariables.interpolation_amount = 0.0f;
+#ifndef SWDS
+ g_ClientGlobalVariables.interpolation_amount = 0.0f;
+
+ cl.insimulation = true;
+#endif
+
+ host_frameticks = numticks;
+ host_currentframetick = 0;
+
+#if !defined( SWDS )
+ // This is to make the tool do both sim + rendering on the initial frame
+ // cl.IsActive changes in the loop below, as does scr_nextdrawtick
+ // We're just caching off the state here so that we have a consistent return value
+ // for enginetool->IsInGame the entire frame
+ g_pEngineToolInternal->SetIsInGame( cl.IsActive() && ( scr_nextdrawtick == 0 ) );
+#endif
+ CJob *pGameJob = NULL;
+
+// threaded path only supported in listen server
+#ifndef SWDS
+ if ( !IsEngineThreaded() )
+#endif
+ {
+#ifndef SWDS
+ if ( g_ClientDLL )
+ {
+ g_ClientDLL->IN_SetSampleTime(host_frametime);
+ }
+ g_ClientGlobalVariables.simTicksThisFrame = 1;
+#endif
+ cl.m_tickRemainder = host_remainder;
+ g_ServerGlobalVariables.simTicksThisFrame = 1;
+ cl.SetFrameTime( host_frametime );
+ for ( int tick = 0; tick < numticks; tick++ )
+ {
+ // Emit an ETW event every simulation frame.
+ ETWSimFrameMark( sv.IsDedicated() );
+
+ double now = Plat_FloatTime();
+ float jitter = now - host_idealtime;
+
+ // Track jitter (delta between ideal time and actual tick execution time)
+ host_jitterhistory[ host_jitterhistorypos ] = jitter;
+ host_jitterhistorypos = ( host_jitterhistorypos + 1 ) % ARRAYSIZE(host_jitterhistory);
+
+ // Very slowly decay "ideal" towards current wall clock unless delta is large
+ if ( fabs( jitter ) > 1.0f )
+ {
+ host_idealtime = now;
+ }
+ else
+ {
+ host_idealtime = 0.99 * host_idealtime + 0.01 * now;
+ }
+
+ // process any asynchronous network traffic (TCP), set net_time
+ NET_RunFrame( now );
+
+ // Only send updates on final tick so we don't re-encode network data multiple times per frame unnecessarily
+ bool bFinalTick = ( tick == (numticks - 1) );
+
+ // initialize networking for dedicated server after commandline & autoexec.cfg have been parsed
+ if ( NET_IsDedicated() && !NET_IsMultiplayer() )
+ NET_SetMutiplayer( true );
+
+ g_ServerGlobalVariables.tickcount = sv.m_nTickCount;
+ // NOTE: Do we want do this at start or end of this loop?
+ ++host_tickcount;
+ ++host_currentframetick;
+#ifndef SWDS
+ g_ClientGlobalVariables.tickcount = cl.GetClientTickCount();
+
+ // Make sure state is correct
+ CL_CheckClientState();
+#endif
+ //-------------------
+ // input processing
+ //-------------------
+ _Host_RunFrame_Input( prevremainder, bFinalTick );
+ prevremainder = 0;
+ //-------------------
+ //
+ // server operations
+ //
+ //-------------------
+
+ _Host_RunFrame_Server( bFinalTick );
+
+ // Additional networking ops for SPLITPACKET stuff (99.9% of the time this will be an empty list of work)
+ NET_SendQueuedPackets();
+ //-------------------
+ //
+ // client operations
+ //
+ //-------------------
+#ifndef SWDS
+ if ( !sv.IsDedicated() )
+ {
+ _Host_RunFrame_Client( bFinalTick );
+ }
+
+ toolframework->Think( bFinalTick );
+#endif
+
+ host_idealtime += host_state.interval_per_tick;
+ }
+
+ // run HLTV if active
+ if ( hltv )
+ {
+ tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "hltv->RunFrame()" );
+ hltv->RunFrame();
+ }
+
+ if ( hltvtest )
+ {
+ tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "hltvtest->RunFrame()" );
+ hltvtest->RunFrame();
+ }
+
+#if defined( REPLAY_ENABLED )
+ // run replay if active
+ if ( replay )
+ {
+ replay->RunFrame();
+ }
+
+ // Update server-side replay history manager
+ if ( sv.IsDedicated() && g_pServerReplayContext && g_pServerReplayContext->IsInitialized() )
+ {
+ g_pServerReplayContext->Think();
+ }
+#endif
+
+#ifndef SWDS
+ // This is a hack to let timedemo pull messages from the queue faster than every 15 msec
+ // Also when demoplayer is skipping packets to a certain tick we should process the queue
+ // as quickly as we can.
+ if ( numticks == 0 && ( demoplayer->IsPlayingTimeDemo() || demoplayer->IsSkipping() ) )
+ {
+ _Host_RunFrame_Client( true );
+ }
+
+ if ( !sv.IsDedicated() )
+ {
+ tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "Host_SetClientInSimulation" );
+
+ // This causes cl.gettime() to return the true clock being used for rendering (tickcount * rate + remainder)
+ Host_SetClientInSimulation( false );
+ // Now allow for interpolation on client
+ g_ClientGlobalVariables.interpolation_amount = ( cl.m_tickRemainder / host_state.interval_per_tick );
+
+#if defined( REPLAY_ENABLED )
+ // Update client-side replay history manager - called here since interpolation_amount is set
+ if ( g_pClientReplayContext && g_pClientReplayContext->IsInitialized() )
+ {
+ g_pClientReplayContext->Think();
+ }
+#endif
+
+ //-------------------
+ // Run prediction if it hasn't been run yet
+ //-------------------
+ // If we haven't predicted/simulated the player (multiplayer with prediction enabled and
+ // not a listen server with zero frame lag, then go ahead and predict now
+ CL_RunPrediction( PREDICTION_NORMAL );
+
+ CL_ApplyAddAngle();
+
+ // The mouse is always simulated for the current frame's time
+ // This makes updates smooth in every case
+ // continuous controllers affecting the view are also simulated this way
+ // but they have a cap applied by IN_SetSampleTime() so they are not also
+ // simulated during input gathering
+ CL_ExtraMouseUpdate( g_ClientGlobalVariables.frametime );
+ }
+#endif
+#if defined( REPLAY_ENABLED )
+ // Let the replay system think
+ if ( g_pReplay )
+ {
+ g_pReplay->Think();
+ }
+#endif
+#if LOG_FRAME_OUTPUT
+ if ( !cl.IsPaused() || !sv.IsPaused() )
+ {
+ Msg("=============SIM: CLIENT %5d + %d, SERVER %5d + %d\t REM: %.2f\n", cl.GetClientTickCount(), numticks, sv.m_nTickCount, numticks, host_remainder*1000.0f );
+ }
+#endif
+ }
+#ifndef SWDS
+ else
+ {
+ static int numticks_last_frame = 0;
+ static float host_remainder_last_frame = 0, prev_remainder_last_frame = 0, last_frame_time = 0;
+
+ int clientticks;
+ int serverticks;
+
+ clientticks = numticks_last_frame;
+ cl.m_tickRemainder = host_remainder_last_frame;
+ cl.SetFrameTime( last_frame_time );
+ if ( g_ClientDLL )
+ {
+ g_ClientDLL->IN_SetSampleTime(last_frame_time);
+ }
+
+ last_frame_time = host_frametime;
+
+ serverticks = numticks;
+ g_ClientGlobalVariables.simTicksThisFrame = clientticks;
+ g_ServerGlobalVariables.simTicksThisFrame = serverticks;
+ g_ServerGlobalVariables.tickcount = sv.m_nTickCount;
+
+ // THREADED: Run Client
+ // -------------------
+ for ( int tick = 0; tick < clientticks; tick++ )
+ {
+ // process any asynchronous network traffic (TCP), set net_time
+ NET_RunFrame( Plat_FloatTime() );
+
+ // Only send updates on final tick so we don't re-encode network data multiple times per frame unnecessarily
+ bool bFinalTick = ( tick == (clientticks - 1) );
+
+ // initialize networking for dedicated server after commandline & autoexec.cfg have been parsed
+ if ( NET_IsDedicated() && !NET_IsMultiplayer() )
+ NET_SetMutiplayer( true );
+ g_ClientGlobalVariables.tickcount = cl.GetClientTickCount();
+
+ // Make sure state is correct
+ CL_CheckClientState();
+ // Additional networking ops for SPLITPACKET stuff (99.9% of the time this will be an empty list of work)
+ NET_SendQueuedPackets();
+ //-------------------
+ //
+ // client operations
+ //
+ //-------------------
+ if ( !sv.IsDedicated() )
+ {
+ _Host_RunFrame_Client( bFinalTick );
+ }
+ toolframework->Think( bFinalTick );
+ }
+ // This is a hack to let timedemo pull messages from the queue faster than every 15 msec
+ // Also when demoplayer is skipping packets to a certain tick we should process the queue
+ // as quickly as we can.
+ if ( clientticks == 0 && ( demoplayer->IsPlayingTimeDemo() || demoplayer->IsSkipping() ) )
+ {
+ _Host_RunFrame_Client( true );
+ }
+
+ // This causes cl.gettime() to return the true clock being used for rendering (tickcount * rate + remainder)
+ Host_SetClientInSimulation( false );
+ // Now allow for interpolation on client
+ g_ClientGlobalVariables.interpolation_amount = ( cl.m_tickRemainder / host_state.interval_per_tick );
+
+ //-------------------
+ // Run prediction if it hasn't been run yet
+ //-------------------
+ // If we haven't predicted/simulated the player (multiplayer with prediction enabled and
+ // not a listen server with zero frame lag, then go ahead and predict now
+ CL_RunPrediction( PREDICTION_NORMAL );
+
+ CL_ApplyAddAngle();
+
+ Host_SetClientInSimulation( true );
+
+ // THREADED: Run Input
+ // -------------------
+ int saveTick = g_ClientGlobalVariables.tickcount;
+
+ for ( int tick = 0; tick < serverticks; tick++ )
+ {
+ // NOTE: Do we want do this at start or end of this loop?
+ ++host_tickcount;
+ ++host_currentframetick;
+ g_ClientGlobalVariables.tickcount = host_tickcount;
+ bool bFinalTick = tick==(serverticks-1) ? true : false;
+ _Host_RunFrame_Input( prevremainder, bFinalTick );
+ prevremainder = 0;
+ // process any asynchronous network traffic (TCP), set net_time
+ NET_RunFrame( Plat_FloatTime() );
+ }
+
+ Host_SetClientInSimulation( false );
+
+ // The mouse is always simulated for the current frame's time
+ // This makes updates smooth in every case
+ // continuous controllers affecting the view are also simulated this way
+ // but they have a cap applied by IN_SetSampleTime() so they are not also
+ // simulated during input gathering
+ CL_ExtraMouseUpdate( g_ClientGlobalVariables.frametime );
+
+ g_ClientGlobalVariables.tickcount = saveTick;
+ numticks_last_frame = numticks;
+ host_remainder_last_frame = host_remainder;
+
+ // THREADED: Run Server
+ // -------------------
+ // set net_time once before running the server
+ NET_SetTime( Plat_FloatTime() );
+ pGameJob = new CFunctorJob( CreateFunctor( _Host_RunFrame_Server_Async, serverticks ) );
+ if ( IsX360() )
+ {
+ pGameJob->SetServiceThread( g_nServerThread );
+ }
+ g_pThreadPool->AddJob( pGameJob );
+#if LOG_FRAME_OUTPUT
+ if ( !cl.IsPaused() || !sv.IsPaused() )
+ {
+ Msg("=============SIM: CLIENT %5d + %d, SERVER %5d + %d\t REM: %.2f\n", cl.GetClientTickCount(), clientticks, sv.m_nTickCount, serverticks, host_remainder*1000.0f );
+ }
+#endif
+ }
+#endif // SWDS
+
+ g_Log.RunFrame();
+
+ if ( shouldrender )
+ {
+#if LOG_FRAME_OUTPUT
+ if ( !cl.IsPaused() || !sv.IsPaused() )
+ {
+ static float lastFrameTime = 0;
+ float frametime = g_ClientGlobalVariables.curtime - lastFrameTime;
+ Msg("RENDER AT: %6.4f: %.2fms [%.2fms implicit] frametime\n",
+ g_ClientGlobalVariables.curtime, g_ClientGlobalVariables.frametime*1000.0f, frametime * 1000.0f);
+ lastFrameTime = g_ClientGlobalVariables.curtime;
+ }
+#endif
+ //-------------------
+ // rendering
+ //-------------------
+ _Host_RunFrame_Render();
+
+ //-------------------
+ // sound
+ //-------------------
+ _Host_RunFrame_Sound();
+
+ if ( g_bVCRSingleStep )
+ {
+ VCR_EnterPausedState();
+ }
+ }
+ else
+ {
+ tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "modelloader->UpdateDynamicModels" );
+ VPROF( "UpdateDynamicModels" );
+ CMDLCacheCriticalSection critsec( g_pMDLCache );
+ modelloader->UpdateDynamicModels();
+ }
+
+ //-------------------
+ // simulation
+ //-------------------
+ g_HostTimes.MarkSwapTime( );
+#ifndef SWDS
+ if ( !sv.IsDedicated() )
+ {
+ VPROF( "_Host_RunFrame - ClientDLL_Update" );
+ tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "_Host_RunFrame - ClientDLL_Update" );
+
+ // Client-side simulation
+ g_HostTimes.StartFrameSegment( FRAME_SEGMENT_CLDLL );
+
+ ClientDLL_Update();
+
+ g_HostTimes.EndFrameSegment( FRAME_SEGMENT_CLDLL );
+ }
+#endif
+ if ( pGameJob )
+ {
+ {
+ VPROF_BUDGET( "WaitForAsyncServer", "AsyncServer" );
+ if ( Host_IsSinglePlayerGame() )
+ {
+ // This should change to a YieldWait if the server starts wanting to parallel process. If
+ // so, will need some route for the server to queue up work it wants to execute outside
+ // its frame, otherwise some of it would be performed during the yield. Right now
+ // need to wait for server so we don't stall on queued AI operations (toml 7/3/2007)
+ pGameJob->ExecuteAndRelease();
+ }
+ else
+ {
+ pGameJob->WaitForFinishAndRelease();
+ }
+ }
+ SV_FrameExecuteThreadDeferred();
+ }
+
+ //-------------------
+ // time
+ //-------------------
+
+ Host_Speeds();
+
+ Host_UpdateMapList();
+
+ host_framecount++;
+#if !defined(SWDS)
+ if ( !demoplayer->IsPlaybackPaused() )
+#endif
+ {
+ host_time = host_tickcount * host_state.interval_per_tick + cl.m_tickRemainder;
+ }
+
+ Host_PostFrameRate( host_frametime );
+
+ if ( host_checkheap )
+ {
+#ifdef _WIN32
+ if ( _heapchk() != _HEAPOK )
+ {
+ Sys_Error( "_Host_RunFrame (bottom): _heapchk() != _HEAPOK\n" );
+ }
+#endif
+ }
+
+ Host_CheckDumpMemoryStats();
+
+ GetTestScriptMgr()->CheckPoint( "frame_end" );
+ } // Profile scope, protect from setjmp() problems
+
+ Host_ShowIPCCallCount();
+}
+/*
+==============================
+Host_Frame
+
+==============================
+*/
+void Host_RunFrame( float time )
+{
+ static double timetotal = 0;
+ static int timecount = 0;
+ static double timestart = 0;
+
+#ifndef SWDS
+ if ( !scr_drawloading && sv.IsActive() && cl.IsActive() && !sv.m_bLoadgame)
+ {
+ switch ( host_thread_mode.GetInt() )
+ {
+ case HTM_DISABLED: g_bThreadedEngine = false; break;
+ case HTM_DEFAULT: g_bThreadedEngine = ( g_pThreadPool->NumThreads() > 0 ); break;
+ case HTM_FORCED: g_bThreadedEngine = true; break;
+ }
+ }
+ else
+#endif
+ {
+ g_bThreadedEngine = false;
+ }
+
+ if ( !host_profile.GetBool() )
+ {
+ _Host_RunFrame( time );
+ return;
+ }
+
+ double time1 = Sys_FloatTime();
+
+ _Host_RunFrame( time );
+
+ double time2 = Sys_FloatTime();
+
+ timetotal += time2 - time1; // time in seconds
+ timecount++;
+
+ if (timecount < 1000)
+ return;
+
+ float fps = 1000/(time2 - timestart);
+
+ ConMsg ("host_profile : %i clients, %.1f msec, %.1f fps\n",
+ sv.GetNumClients(), timetotal, fps );
+
+ timecount = 0;
+ timetotal = 0;
+ timestart = time2;
+}
+
+
+//-----------------------------------------------------------------------------
+// A more secure means of enforcing low violence.
+//-----------------------------------------------------------------------------
+bool IsLowViolence_Secure()
+{
+#ifndef DEDICATED
+ if ( !IsX360() && Steam3Client().SteamApps() )
+ {
+ // let Steam determine current violence settings
+ return Steam3Client().SteamApps()->BIsLowViolence();
+ }
+ else if ( IsX360() )
+ {
+ // Low violence for the 360 is enabled by the presence of a file.
+ if ( g_pFileSystem->FileExists( "cfg/violence.cfg" ) )
+ {
+ return true;
+ }
+
+ return false;
+ }
+#endif
+
+ return false;
+}
+
+
+//-----------------------------------------------------------------------------
+// If "User Token 2" exists in HKEY_CURRENT_USER/Software/Valve/Half-Life/Settings
+// then we disable gore. Obviously not very secure.
+//-----------------------------------------------------------------------------
+bool IsLowViolence_Registry()
+{
+ char szSubKey[128];
+ int nBufferLen;
+ char szBuffer[128];
+ bool bReducedGore = false;
+
+ memset( szBuffer, 0, 128 );
+
+ char const *appname = "Source";
+ Q_snprintf(szSubKey, sizeof( szSubKey ), "Software\\Valve\\%s\\Settings", appname );
+
+ nBufferLen = 127;
+ Q_strncpy( szBuffer, "", sizeof( szBuffer ) );
+
+ Sys_GetRegKeyValue( szSubKey, "User Token 2", szBuffer, nBufferLen, szBuffer );
+
+ // Gore reduction active?
+ bReducedGore = ( Q_strlen( szBuffer ) > 0 ) ? true : false;
+ if ( !bReducedGore )
+ {
+ Sys_GetRegKeyValue( szSubKey, "User Token 3", szBuffer, nBufferLen, szBuffer );
+
+ bReducedGore = ( Q_strlen( szBuffer ) > 0 ) ? true : false;
+ }
+
+ char gamedir[MAX_OSPATH];
+ Q_FileBase( com_gamedir, gamedir, sizeof( gamedir ) );
+
+ // also check mod specific directories for LV changes
+ Q_snprintf(szSubKey, sizeof( szSubKey ), "Software\\Valve\\%s\\%s\\Settings", appname, gamedir );
+
+ nBufferLen = 127;
+ Q_strncpy( szBuffer, "", sizeof( szBuffer ) );
+
+ Sys_GetRegKeyValue( szSubKey, "User Token 2", szBuffer, nBufferLen, szBuffer );
+ if ( Q_strlen( szBuffer ) > 0 )
+ {
+ bReducedGore = true;
+ }
+
+ Sys_GetRegKeyValue( szSubKey, "User Token 3", szBuffer, nBufferLen, szBuffer );
+ if ( Q_strlen( szBuffer ) > 0 )
+ {
+ bReducedGore = true;
+ }
+
+ return bReducedGore;
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+void Host_CheckGore( void )
+{
+ bool bLowViolenceRegistry = false;
+ bool bLowViolenceSecure = false;
+
+ //
+ // First check the old method of enabling low violence via the registry.
+ //
+#ifdef WIN32
+ bLowViolenceRegistry = IsLowViolence_Registry();
+#endif
+ //
+ // Next check the new method of enabling low violence based on country of purchase
+ // and other means that are inaccessible by the user.
+ //
+ if ( GetCurrentMod() && Q_stricmp( GetCurrentMod(), "cstrike" ) != 0 )
+ bLowViolenceSecure = IsLowViolence_Secure();
+
+ //
+ // If either method says "yes" to low violence, we're in low violence mode.
+ //
+ if ( bLowViolenceRegistry || bLowViolenceSecure )
+ {
+ g_bLowViolence = true;
+
+ if ( bLowViolenceRegistry )
+ {
+ violence_hblood.SetValue( 0 );
+ violence_hgibs.SetValue( 0 );
+ violence_ablood.SetValue( 0 );
+ violence_agibs.SetValue( 0 );
+ }
+ }
+ else
+ {
+ g_bLowViolence = false;
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void Host_InitProcessor( void )
+{
+ const CPUInformation& pi = *GetCPUInformation();
+
+ // Compute Frequency in Mhz:
+ char* szFrequencyDenomination = "Mhz";
+ double fFrequency = pi.m_Speed / 1000000.0;
+
+ // Adjust to Ghz if nessecary:
+ if( fFrequency > 1000.0 )
+ {
+ fFrequency /= 1000.0;
+ szFrequencyDenomination = "Ghz";
+ }
+
+ char szFeatureString[256];
+ Q_strncpy( szFeatureString, pi.m_szProcessorID, sizeof( szFeatureString ) );
+ Q_strncat( szFeatureString, " ", sizeof( szFeatureString ), COPY_ALL_CHARACTERS );
+
+ if( pi.m_bSSE )
+ {
+ if( MathLib_SSEEnabled() ) Q_strncat(szFeatureString, "SSE ", sizeof( szFeatureString ), COPY_ALL_CHARACTERS );
+ else Q_strncat(szFeatureString, "(SSE) ", sizeof( szFeatureString ), COPY_ALL_CHARACTERS );
+ }
+
+ if( pi.m_bSSE2 )
+ {
+ if( MathLib_SSE2Enabled() ) Q_strncat(szFeatureString, "SSE2 ", sizeof( szFeatureString ), COPY_ALL_CHARACTERS );
+ else Q_strncat(szFeatureString, "(SSE2) ", sizeof( szFeatureString ), COPY_ALL_CHARACTERS );
+ }
+
+ if( pi.m_bMMX )
+ {
+ if( MathLib_MMXEnabled() ) Q_strncat(szFeatureString, "MMX ", sizeof( szFeatureString ), COPY_ALL_CHARACTERS );
+ else Q_strncat(szFeatureString, "(MMX) ", sizeof( szFeatureString ), COPY_ALL_CHARACTERS );
+ }
+
+ if( pi.m_b3DNow )
+ {
+ if( MathLib_3DNowEnabled() ) Q_strncat(szFeatureString, "3DNow ", sizeof( szFeatureString ), COPY_ALL_CHARACTERS );
+ else Q_strncat(szFeatureString, "(3DNow) ", sizeof( szFeatureString ), COPY_ALL_CHARACTERS );
+ }
+
+ if( pi.m_bRDTSC ) Q_strncat(szFeatureString, "RDTSC ", sizeof( szFeatureString ), COPY_ALL_CHARACTERS );
+ if( pi.m_bCMOV ) Q_strncat(szFeatureString, "CMOV ", sizeof( szFeatureString ), COPY_ALL_CHARACTERS );
+ if( pi.m_bFCMOV ) Q_strncat(szFeatureString, "FCMOV ", sizeof( szFeatureString ), COPY_ALL_CHARACTERS );
+
+ // Remove the trailing space. There will always be one.
+ szFeatureString[Q_strlen(szFeatureString)-1] = '\0';
+
+ // Dump CPU information:
+ if( pi.m_nLogicalProcessors == 1 )
+ {
+ ConDMsg( "1 CPU, Frequency: %.01f %s, Features: %s\n",
+ fFrequency,
+ szFrequencyDenomination,
+ szFeatureString
+ );
+ }
+ else
+ {
+ char buffer[256] = "";
+ if( pi.m_nPhysicalProcessors != pi.m_nLogicalProcessors )
+ {
+ Q_snprintf(buffer, sizeof( buffer ), " (%i physical)", (int) pi.m_nPhysicalProcessors );
+ }
+
+ ConDMsg( "%i CPUs%s, Frequency: %.01f %s, Features: %s\n",
+ (int)pi.m_nLogicalProcessors,
+ buffer,
+ fFrequency,
+ szFrequencyDenomination,
+ szFeatureString
+ );
+ }
+
+#if defined( _WIN32 )
+ if ( s_bInitPME )
+ {
+ // Initialize the performance monitoring events code.
+ InitPME();
+ }
+#endif
+}
+
+//-----------------------------------------------------------------------------
+// Specifically used by the model loading code to mark models
+// touched by the current map
+//-----------------------------------------------------------------------------
+int Host_GetServerCount( void )
+{
+ if (cl.m_nSignonState >= SIGNONSTATE_NEW)
+ {
+ // the server count cannot be relied on until the server info message
+ // the new state guarantees its validity
+ return cl.m_nServerCount;
+ }
+ else if (sv.m_State >= ss_loading)
+ {
+ return sv.GetSpawnCount();
+ }
+
+ // this is unfortunate, and happens, but the caller is too early in the protocol or a demo
+ // cannot identify the correct server count
+ // return the same count that demo will use
+ return gHostSpawnCount;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void Host_PostInit()
+{
+ if ( serverGameDLL )
+ {
+ serverGameDLL->PostInit();
+ }
+
+#if !defined( SWDS )
+ if ( g_ClientDLL )
+ {
+ g_ClientDLL->PostInit();
+ }
+
+ toolframework->PostInit();
+
+ if ( !sv.IsDedicated() )
+ {
+ // vgui needs other systems to finalize
+ EngineVGui()->PostInit();
+ }
+
+#if defined( LINUX )
+ const char en_US[] = "en_US.UTF-8";
+ const char *CurrentLocale = setlocale( LC_ALL, NULL );
+ if ( !CurrentLocale )
+ CurrentLocale = "c";
+ if ( Q_stricmp( CurrentLocale, en_US ) )
+ {
+ char MessageText[ 512 ];
+
+ V_sprintf_safe( MessageText, "SetLocale('%s') failed. Using '%s'.\n"
+ "You may have limited glyph support.\n"
+ "Please install '%s' locale.",
+ en_US, CurrentLocale, en_US );
+ SDL_ShowSimpleMessageBox( 0, "Warning", MessageText, GetAssertDialogParent() );
+ }
+#endif // LINUX
+
+#endif
+}
+
+void HLTV_Init()
+{
+ Assert ( hltv == NULL );
+ Assert ( hltvtest == NULL );
+}
+
+void HLTV_Shutdown()
+{
+ if ( hltv )
+ {
+ hltv->Shutdown();
+ delete hltv;
+ hltv = NULL;
+ }
+
+ if ( hltvtest )
+ {
+ delete hltvtest;
+ hltvtest = NULL;
+ }
+}
+
+// Check with steam to see if the requested file (requires full path) is a valid, signed binary
+bool DLL_LOCAL Host_IsValidSignature( const char *pFilename, bool bAllowUnknown )
+{
+#if defined( SWDS ) || defined(_X360)
+ return true;
+#else
+ if ( sv.IsDedicated() || IsOSX() || IsLinux() )
+ {
+ // dedicated servers and Mac and Linux binaries don't check signatures
+ return true;
+ }
+ else
+ {
+ if ( Steam3Client().SteamUtils() )
+ {
+ SteamAPICall_t hAPICall = Steam3Client().SteamUtils()->CheckFileSignature( pFilename );
+ bool bAPICallFailed = true;
+ while ( !Steam3Client().SteamUtils()->IsAPICallCompleted(hAPICall, &bAPICallFailed) )
+ {
+ SteamAPI_RunCallbacks();
+ ThreadSleep( 1 );
+ }
+
+ if( bAPICallFailed )
+ {
+ Warning( "CheckFileSignature API call on %s failed", pFilename );
+ }
+ else
+ {
+ CheckFileSignature_t result;
+ Steam3Client().SteamUtils()->GetAPICallResult( hAPICall, &result, sizeof(result), result.k_iCallback, &bAPICallFailed );
+ if( bAPICallFailed )
+ {
+ Warning( "CheckFileSignature API call on %s failed\n", pFilename );
+ }
+ else
+ {
+ if( result.m_eCheckFileSignature == k_ECheckFileSignatureValidSignature || result.m_eCheckFileSignature == k_ECheckFileSignatureNoSignaturesFoundForThisApp )
+ return true;
+ if ( bAllowUnknown && result.m_eCheckFileSignature == k_ECheckFileSignatureNoSignaturesFoundForThisFile )
+ return true;
+ Warning( "No valid signature found for %s\n", pFilename );
+ }
+ }
+ }
+ }
+
+ return false;
+#endif // SWDS
+}
+
+
+// Ask steam if it is ok to load this DLL. Unsigned DLLs should not be loaded unless
+// the client is running -insecure (testing a plugin for example)
+// This keeps legitimate users with modified binaries from getting VAC banned because of them
+bool DLL_LOCAL Host_AllowLoadModule( const char *pFilename, const char *pPathID, bool bAllowUnknown, bool bIsServerOnly /* = false */ )
+{
+#if defined( SWDS ) || defined ( OSX ) || defined( LINUX )
+ // dedicated servers and Mac and Linux binaries don't check signatures
+ return true;
+#else
+
+ if ( sv.IsDedicated() || bIsServerOnly )
+ {
+ // dedicated servers and Mac binaries don't check signatures
+ return true;
+ }
+ else
+ {
+ // check signature
+ bool bSignatureIsValid = false;
+
+ // Do we need to do the signature checking? If secure servers are disabled, just skip it.
+ if ( Host_IsSecureServerAllowed() )
+ {
+ if ( Steam3Client().SteamUtils() )
+ {
+ char szDllname[512];
+
+ V_strncpy( szDllname, pFilename, sizeof(szDllname) );
+ V_SetExtension( szDllname, g_pModuleExtension, sizeof(szDllname) );
+ if ( pPathID )
+ {
+ char szFullPath[ 512 ];
+ const char *pFullPath = g_pFileSystem->RelativePathToFullPath( szDllname, pPathID, szFullPath, sizeof(szFullPath) );
+ if ( !pFullPath )
+ {
+ Warning("Can't find %s on disk\n", szDllname );
+ bSignatureIsValid = false;
+ }
+ else
+ {
+ bSignatureIsValid = Host_IsValidSignature( pFullPath, bAllowUnknown );
+ }
+ }
+ else
+ {
+ bSignatureIsValid = Host_IsValidSignature( szDllname, bAllowUnknown );
+ }
+ }
+ else
+ {
+ Warning("Steam is not active, running in -insecure mode.\n");
+ Host_DisallowSecureServers();
+ }
+ }
+ else
+ {
+ Warning("Loading unsigned module %s\nAccess to secure servers is disabled.\n", pFilename );
+ return true;
+ }
+
+ return bSignatureIsValid;
+ }
+#endif // SWDS
+}
+
+bool DLL_LOCAL Host_IsSecureServerAllowed()
+{
+ if ( CommandLine()->FindParm( "-insecure" ) || CommandLine()->FindParm( "-textmode" ) )
+ g_bAllowSecureServers = false;
+
+ return g_bAllowSecureServers;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void Host_Init( bool bDedicated )
+{
+ realtime = 0;
+ host_idealtime = 0;
+
+#if defined(_WIN32)
+ if ( CommandLine()->FindParm( "-pme" ) )
+ {
+ s_bInitPME = true;
+ }
+#endif
+
+ if ( Host_IsSecureServerAllowed() )
+ {
+ // double check the engine's signature in case it was hooked/modified
+ if ( !Host_AllowLoadModule( "engine" DLL_EXT_STRING, "EXECUTABLE_PATH", false, bDedicated ) )
+ {
+ // not supposed to load this but we will anyway
+ Host_DisallowSecureServers();
+ }
+ }
+
+ ThreadPoolStartParams_t startParams;
+ if ( IsX360() )
+ {
+ // 360 overrides defaults, 2 computation threads distributed to core 1 and 2
+ startParams.nThreads = 2;
+ startParams.nStackSize = 256*1024;
+ startParams.fDistribute = TRS_TRUE;
+ startParams.bUseAffinityTable = true;
+ startParams.iAffinityTable[0] = XBOX_PROCESSOR_2;
+ startParams.iAffinityTable[1] = XBOX_PROCESSOR_4;
+ ThreadSetAffinity( NULL, 1 );
+ }
+ if ( g_pThreadPool )
+ g_pThreadPool->Start( startParams, "CmpJob" );
+
+ // From const.h, the loaded game .dll will give us the correct value which is transmitted to the client
+ host_state.interval_per_tick = DEFAULT_TICK_INTERVAL;
+
+ InstallBitBufErrorHandler();
+
+ TRACEINIT( Memory_Init(), Memory_Shutdown() );
+
+ TRACEINIT( Con_Init(), Con_Shutdown() );
+
+ TRACEINIT( Cbuf_Init(), Cbuf_Shutdown() );
+
+ TRACEINIT( Cmd_Init(), Cmd_Shutdown() );
+
+ TRACEINIT( g_pCVar->Init(), g_pCVar->Shutdown() ); // So we can list cvars with "cvarlst"
+
+#ifndef SWDS
+ TRACEINIT( V_Init(), V_Shutdown() );
+#endif
+
+ TRACEINIT( COM_Init(), COM_Shutdown() );
+
+
+#ifndef SWDS
+ TRACEINIT( saverestore->Init(), saverestore->Shutdown() );
+#endif
+
+ TRACEINIT( Filter_Init(), Filter_Shutdown() );
+
+#ifndef SWDS
+ TRACEINIT( Key_Init(), Key_Shutdown() );
+#endif
+
+ // Check for special -dev flag
+ if ( CommandLine()->FindParm( "-dev" ) || ( CommandLine()->FindParm( "-allowdebug" ) && !CommandLine()->FindParm( "-nodev" ) ) )
+ {
+ sv_cheats.SetValue( 1 );
+ developer.SetValue( 1 );
+ }
+#ifdef _DEBUG
+ developer.SetValue( 1 );
+#endif
+
+ // Should have read info from steam.inf by now.
+ Assert( GetSteamInfIDVersionInfo().AppID != k_uAppIdInvalid );
+
+ if ( CommandLine()->FindParm( "-nocrashdialog" ) )
+ {
+ // stop the various windows error message boxes from showing up (used by the auto-builder so it doesn't block on error)
+ Sys_NoCrashDialog();
+ }
+
+ TRACEINIT( NET_Init( bDedicated ), NET_Shutdown() );
+
+ TRACEINIT( g_GameEventManager.Init(), g_GameEventManager.Shutdown() );
+
+ TRACEINIT( sv.Init( bDedicated ), sv.Shutdown() );
+
+#if defined( REPLAY_ENABLED )
+ if ( Replay_IsSupportedModAndPlatform() )
+ {
+ TRACEINIT( ReplaySystem_Init( bDedicated ), ReplaySystem_Shutdown() );
+ }
+#endif
+
+ if ( !CommandLine()->FindParm( "-nogamedll" ) )
+ {
+ SV_InitGameDLL();
+ }
+
+ TRACEINIT( g_Log.Init(), g_Log.Shutdown() );
+
+ TRACEINIT( HLTV_Init(), HLTV_Shutdown() );
+
+ ConDMsg( "Heap: %5.2f Mb\n", host_parms.memsize/(1024.0f*1024.0f) );
+
+#if !defined( SWDS )
+ if ( !bDedicated )
+ {
+ TRACEINIT( CL_Init(), CL_Shutdown() );
+
+ // NOTE: This depends on the mod search path being set up
+ TRACEINIT( InitMaterialSystem(), ShutdownMaterialSystem() );
+
+ TRACEINIT( modelloader->Init(), modelloader->Shutdown() );
+
+ TRACEINIT( StaticPropMgr()->Init(), StaticPropMgr()->Shutdown() );
+
+ TRACEINIT( InitStudioRender(), ShutdownStudioRender() );
+
+ //startup vgui
+ TRACEINIT( EngineVGui()->Init(), EngineVGui()->Shutdown() );
+
+ TRACEINIT( TextMessageInit(), TextMessageShutdown() );
+
+ TRACEINIT( ClientDLL_Init(), ClientDLL_Shutdown() );
+
+ TRACEINIT( SCR_Init(), SCR_Shutdown() );
+
+ TRACEINIT( R_Init(), R_Shutdown() );
+
+ TRACEINIT( Decal_Init(), Decal_Shutdown() );
+
+ // hookup interfaces
+ EngineVGui()->Connect();
+ }
+ else
+#endif
+ {
+ TRACEINIT( InitMaterialSystem(), ShutdownMaterialSystem() );
+
+ TRACEINIT( modelloader->Init(), modelloader->Shutdown() );
+
+ TRACEINIT( StaticPropMgr()->Init(), StaticPropMgr()->Shutdown() );
+
+ TRACEINIT( InitStudioRender(), ShutdownStudioRender() );
+
+ TRACEINIT( Decal_Init(), Decal_Shutdown() );
+
+ cl.m_nSignonState = SIGNONSTATE_NONE; // disable client
+ }
+
+#ifndef SWDS
+ Host_ReadConfiguration();
+ TRACEINIT( S_Init(), S_Shutdown() );
+#endif
+
+ // Execute valve.rc
+ Cbuf_AddText( "exec valve.rc\n" );
+
+#if defined( REPLAY_ENABLED )
+ // Execute replay.cfg if this is TF and they want to use the replay system
+ if ( Replay_IsSupportedModAndPlatform() && CommandLine()->CheckParm( "-replay" ) )
+ {
+ const char *pConfigName = CommandLine()->ParmValue( "-replay", "replay.cfg" );
+ Cbuf_AddText( va( "exec %s\n", pConfigName ) );
+ }
+#endif
+
+ // Execute mod-specfic settings, without falling back based on search path.
+ // This lets us set overrides for games while letting mods of those games
+ // use the default settings.
+ if ( g_pFileSystem->FileExists( "//mod/cfg/modsettings.cfg" ) )
+ {
+ Cbuf_AddText( "exec modsettings.cfg mod\n" );
+ }
+
+ // Mark DLL as active
+ // eng->SetNextState( InEditMode() ? IEngine::DLL_PAUSED : IEngine::DLL_ACTIVE );
+
+ // Deal with Gore Settings
+ Host_CheckGore();
+
+ TelemetryTick();
+
+ // Initialize processor subsystem, and print relevant information:
+ Host_InitProcessor();
+
+ // Mark hunklevel at end of startup
+ Hunk_AllocName( 0, "-HOST_HUNKLEVEL-" );
+ host_hunklevel = Hunk_LowMark();
+
+#ifdef SOURCE_MT
+ if ( CommandLine()->FindParm( "-swapcores" ) )
+ {
+ g_nMaterialSystemThread = 1;
+ g_nServerThread = 0;
+ }
+#endif
+
+ Host_AllowQueuedMaterialSystem( false );
+
+ // Finished initializing
+ host_initialized = true;
+
+ host_checkheap = CommandLine()->FindParm( "-heapcheck" ) ? true : false;
+
+ if ( host_checkheap )
+ {
+#if defined( _WIN32 )
+ if ( _heapchk() != _HEAPOK )
+ {
+ Sys_Error( "Host_Init: _heapchk() != _HEAPOK\n" );
+ }
+#endif
+ }
+
+ // go directly to run state with no active game
+ HostState_Init();
+
+ // check for reslist generation
+ if ( CommandLine()->FindParm( "-makereslists" ) )
+ {
+ MapReslistGenerator().StartReslistGeneration();
+ }
+
+ // check for devshot generation
+ if ( CommandLine()->FindParm( "-makedevshots" ) )
+ {
+ DevShotGenerator().StartDevShotGeneration();
+ }
+
+ // if running outside of steam and NOT a dedicated server then phone home (or if "-phonehome" is passed on the command line)
+ if ( !sv.IsDedicated() || CommandLine()->FindParm( "-phonehome" ) )
+ {
+ // In debug, only run this check if -phonehome is on the command line (so a debug build will "just work").
+ if ( IsDebug() && CommandLine()->FindParm( "-phonehome" ) )
+ {
+ phonehome->Init();
+ phonehome->Message( IPhoneHome::PHONE_MSG_ENGINESTART, NULL );
+ }
+ }
+
+#ifndef SWDS
+ // Rebuild audio caches
+ if ( !sv.IsDedicated() && S_IsInitted() )
+ {
+ if ( !MapReslistGenerator().IsEnabled() )
+ {
+ // only build caches if we aren't' generating reslists (you need reslists to make the caches)
+ extern void CheckCacheBuild();
+ CheckCacheBuild();
+ }
+ }
+#endif
+
+ Host_PostInit();
+ EndLoadingUpdates( );
+ CMatRenderContextPtr pRenderContext( g_pMaterialSystem );
+ pRenderContext->SetNonInteractiveTempFullscreenBuffer( NULL, MATERIAL_NON_INTERACTIVE_MODE_STARTUP );
+ pRenderContext->SetNonInteractivePacifierTexture( NULL, 0, 0, 0 );
+}
+
+//-----------------------------------------------------------------------------
+// Adds hints to the loader to keep resources that are in the transition volume,
+// as they may not be part of the next map's reslist.
+//-----------------------------------------------------------------------------
+void AddTransitionResources( CSaveRestoreData *pSaveData, const char *pLevelName, const char *pLandmarkName )
+{
+ if ( !IsX360() || ( g_pFileSystem->GetDVDMode() != DVDMODE_STRICT ) )
+ {
+ return;
+ }
+
+ // get the bit marked for the next level
+ int transitionMask = 0;
+ for ( int i = 0; i < pSaveData->levelInfo.connectionCount; i++ )
+ {
+ if ( !Q_stricmp( pLevelName, pSaveData->levelInfo.levelList[i].mapName ) && !Q_stricmp( pLandmarkName, pSaveData->levelInfo.levelList[i].landmarkName ) )
+ {
+ transitionMask = 1<<i;
+ break;
+ }
+ }
+
+ if ( !transitionMask )
+ {
+ // nothing to do
+ return;
+ }
+
+ const char *pModelName;
+ bool bHasHumans = false;
+ for ( int i = 0; i < pSaveData->NumEntities(); i++ )
+ {
+ if ( pSaveData->GetEntityInfo(i)->flags & transitionMask )
+ {
+ // this entity will cross the transition and needs to be preserved
+ // add to the next map's resource list which effectively keeps it from being purged
+ // only care about the actual mdl and not any of its dependants
+ pModelName = pSaveData->GetEntityInfo(i)->modelname.ToCStr();
+ g_pQueuedLoader->AddMapResource( pModelName );
+
+ // humans require a post pass
+ if ( !bHasHumans && V_stristr( pModelName, "models/humans" ) )
+ {
+ bHasHumans = true;
+ }
+ }
+ }
+
+ if ( bHasHumans )
+ {
+ // the presence of any human entity in the transition needs to ensure all the human mdls stay
+ int count = modelloader->GetCount();
+ for ( int i = 0; i < count; i++ )
+ {
+ pModelName = modelloader->GetName( modelloader->GetModelForIndex( i ) );
+ if ( V_stristr( pModelName, "models/humans" ) )
+ {
+ g_pQueuedLoader->AddMapResource( pModelName );
+ }
+ }
+ }
+}
+
+bool Host_Changelevel( bool loadfromsavedgame, const char *mapname, const char *start )
+{
+ char _startspot[MAX_QPATH];
+ char *startspot;
+ char oldlevel[MAX_PATH];
+#if !defined(SWDS)
+ CSaveRestoreData *pSaveData = NULL;
+#endif
+ bool bTransitionBySave = false;
+
+ if ( !sv.IsActive() )
+ {
+ ConMsg("Only the server may changelevel\n");
+ return false;
+ }
+
+#ifndef SWDS
+ // FIXME: Even needed?
+ if ( demoplayer->IsPlayingBack() )
+ {
+ ConMsg("Changelevel invalid during demo playback\n");
+ SCR_EndLoadingPlaque();
+ return false;
+ }
+
+#endif
+
+#ifndef SWDS
+ SCR_BeginLoadingPlaque();
+
+ // stop sounds (especially looping!)
+ S_StopAllSounds(true);
+#endif
+
+ // Prepare new level
+ sv.InactivateClients();
+
+ // The qualified name of the map, excluding path/extension
+ char szMapName[MAX_PATH] = { 0 };
+ // The file to load the map from.
+ char szMapFile[MAX_PATH] = { 0 };
+ Q_strncpy( szMapName, mapname, sizeof( szMapName ) );
+ Host_DefaultMapFileName( szMapName, szMapFile, sizeof( szMapFile ) );
+
+ // Ask serverDLL to prepare this load
+ if ( g_iServerGameDLLVersion >= 10 )
+ {
+ serverGameDLL->PrepareLevelResources( szMapName, sizeof( szMapName ), szMapFile, sizeof( szMapFile ) );
+ }
+
+ if ( !modelloader->Map_IsValid( szMapFile ) )
+ {
+#ifndef SWDS
+ SCR_EndLoadingPlaque();
+#endif
+ // We have already inactivated clients at this point due to PrepareLevelResources being blocking, false alarm,
+ // tell them to reconnect (which doesn't mean full reconnect, just start rejoining the map)
+ //
+ // In the likely case that the game DLL tries another map this is harmless, they'll wait on the game server in
+ // the connect process if its in another level change by time they get there.
+ sv.ReconnectClients();
+ return false;
+ }
+
+ // If changing from the same map to the same map, optimize by not closing and reopening
+ // the packfile which is embedded in the .bsp; we do this by incrementing the packfile's
+ // refcount via BeginMapAccess()/EndMapAccess() through the base filesystem API.
+ struct LocalMapAccessScope
+ {
+ LocalMapAccessScope() : bEnabled( false ) { }
+ ~LocalMapAccessScope() { if ( bEnabled ) g_pFileSystem->EndMapAccess(); }
+ bool bEnabled;
+ };
+
+ LocalMapAccessScope mapscope;
+ if ( V_strcmp( sv.GetMapName(), szMapName ) == 0 )
+ {
+ g_pFileSystem->BeginMapAccess();
+ mapscope.bEnabled = true;
+ }
+
+ g_pFileSystem->AsyncFinishAll();
+
+ if ( !start )
+ startspot = NULL;
+ else
+ {
+ Q_strncpy (_startspot, start, sizeof( _startspot ) );
+ startspot = _startspot;
+ }
+
+ Warning( "---- Host_Changelevel ----\n" );
+ CheckForFlushMemory( sv.GetMapName(), szMapName );
+
+#if !defined( SWDS )
+ // Always save as an xsave if we're on the X360
+ saverestore->SetIsXSave( IsX360() );
+
+ // Add on time passed since the last time we kept track till this transition
+ int iAdditionalSeconds = g_ServerGlobalVariables.curtime - saverestore->GetMostRecentElapsedTimeSet();
+ int iElapsedSeconds = saverestore->GetMostRecentElapsedSeconds() + iAdditionalSeconds;
+ int iElapsedMinutes = saverestore->GetMostRecentElapsedMinutes() + ( iElapsedSeconds / 60 );
+ saverestore->SetMostRecentElapsedMinutes( iElapsedMinutes );
+ saverestore->SetMostRecentElapsedSeconds( ( iElapsedSeconds % 60 ) );
+
+ if ( bTransitionBySave )
+ {
+ char comment[80];
+ // Pass in the total elapsed time so it gets added to the elapsed time for this map.
+ serverGameDLL->GetSaveComment(
+ comment,
+ sizeof( comment ),
+ saverestore->GetMostRecentElapsedMinutes(),
+ saverestore->GetMostRecentElapsedSeconds() );
+
+ if ( !saverestore->SaveGameSlot( "_transition", comment, false, true, szMapName, startspot ) )
+ {
+ Warning( "Failed to save data for transition\n" );
+ SCR_EndLoadingPlaque();
+ return false;
+ }
+
+ // Not going to load a save after the transition, so add this map's elapsed time to the total elapsed time
+ int totalSeconds = g_ServerGlobalVariables.curtime + saverestore->GetMostRecentElapsedSeconds();
+ saverestore->SetMostRecentElapsedMinutes( (int)( totalSeconds / 60.0f ) + saverestore->GetMostRecentElapsedMinutes() );
+ saverestore->SetMostRecentElapsedSeconds( (int)fmod( totalSeconds, 60.0f ) );
+ }
+#endif
+
+ Q_strncpy( oldlevel, sv.GetMapName(), sizeof( oldlevel ) );
+
+#if !defined(SWDS)
+ if ( loadfromsavedgame )
+ {
+ if ( !bTransitionBySave )
+ {
+ // save the current level's state
+ saverestore->SaveGameState( true, &pSaveData );
+
+ if ( !pSaveData )
+ {
+ Warning( "Failed to save data for transition\n" );
+ SCR_EndLoadingPlaque();
+ return false;
+ }
+ }
+
+ // ensure resources in the transition volume stay
+ AddTransitionResources( pSaveData, szMapName, startspot );
+ }
+#endif
+ g_pServerPluginHandler->LevelShutdown();
+
+#if !defined(SWDS)
+ audiosourcecache->LevelShutdown();
+#endif
+
+#if !defined(SWDS)
+ saverestore->FinishAsyncSave();
+#endif
+
+ if ( sv.RestartOnLevelChange() )
+ {
+ Cbuf_Clear();
+ Cbuf_AddText( "quit\n" );
+ return false;
+ }
+
+ DownloadListGenerator().OnLevelLoadStart( szMapName );
+
+ if ( !sv.SpawnServer( szMapName, szMapFile, startspot ) )
+ {
+#ifndef SWDS
+ SCR_EndLoadingPlaque();
+#endif
+ return false;
+ }
+
+#ifndef SWDS
+ if ( loadfromsavedgame )
+ {
+ if ( !bTransitionBySave )
+ {
+ // Finish saving gamestate
+ saverestore->Finish( pSaveData );
+ }
+
+ g_ServerGlobalVariables.curtime = sv.GetTime();
+
+ audiosourcecache->LevelInit( szMapName );
+ g_pServerPluginHandler->LevelInit( szMapName, CM_EntityString(), oldlevel, startspot, true, false );
+
+ sv.SetPaused( true ); // pause until client connects
+ sv.m_bLoadgame = true;
+ }
+ else
+#endif
+ {
+ g_ServerGlobalVariables.curtime = sv.GetTime();
+#if !defined(SWDS)
+ audiosourcecache->LevelInit( szMapName );
+#endif
+ g_pServerPluginHandler->LevelInit( szMapName, CM_EntityString(), NULL, NULL, false, false );
+ }
+
+ SV_ActivateServer();
+
+#if !defined(SWDS)
+ // Offset stored elapsed time by the current elapsed time for this new map
+ int maptime = sv.GetTime();
+ int minutes = (int)( maptime / 60.0f );
+ int seconds = (int)fmod( maptime, 60.0f );
+ saverestore->SetMostRecentElapsedMinutes( saverestore->GetMostRecentElapsedMinutes() - minutes );
+ saverestore->SetMostRecentElapsedSeconds( saverestore->GetMostRecentElapsedSeconds() - seconds );
+#endif
+
+ NotifyDedicatedServerUI("UpdateMap");
+
+ DownloadListGenerator().OnLevelLoadEnd();
+
+ return true;
+}
+
+/*
+===============================================================================
+
+SERVER TRANSITIONS
+
+===============================================================================
+*/
+bool Host_NewGame( char *mapName, bool loadGame, bool bBackgroundLevel, const char *pszOldMap, const char *pszLandmark, bool bOldSave )
+{
+ VPROF( "Host_NewGame" );
+ COM_TimestampedLog( "Host_NewGame" );
+
+ char previousMapName[MAX_PATH] = { 0 };
+ Q_strncpy( previousMapName, host_map.GetString(), sizeof( previousMapName ) );
+
+#ifndef SWDS
+ SCR_BeginLoadingPlaque();
+#endif
+
+ // The qualified name of the map, excluding path/extension
+ char szMapName[MAX_PATH] = { 0 };
+ // The file to load the map from.
+ char szMapFile[MAX_PATH] = { 0 };
+ Q_strncpy( szMapName, mapName, sizeof( szMapName ) );
+ Host_DefaultMapFileName( szMapName, szMapFile, sizeof( szMapFile ) );
+
+ // Steam may not have been started yet, ensure it is available to the game DLL before we ask it to prepare level
+ // resources
+ SV_InitGameServerSteam();
+
+ // Ask serverDLL to prepare this load
+ if ( g_iServerGameDLLVersion >= 10 )
+ {
+ serverGameDLL->PrepareLevelResources( szMapName, sizeof( szMapName ), szMapFile, sizeof( szMapFile ) );
+ }
+
+ if ( !modelloader->Map_IsValid( szMapFile ) )
+ {
+#ifndef SWDS
+ SCR_EndLoadingPlaque();
+#endif
+ return false;
+ }
+
+ DevMsg( "---- Host_NewGame ----\n" );
+ host_map.SetValue( szMapName );
+
+ CheckForFlushMemory( previousMapName, szMapName );
+
+ if (MapReslistGenerator().IsEnabled())
+ {
+ // uncache all the materials, so their files get referenced again for the reslists
+ // undone for now, since we're just trying to get a global reslist, not per-map accurate
+ // materials->UncacheAllMaterials();
+ MapReslistGenerator().OnLevelLoadStart(szMapName);
+ // cache 'em back in!
+ // materials->CacheUsedMaterials();
+ }
+ DownloadListGenerator().OnLevelLoadStart(szMapName);
+
+ if ( !loadGame )
+ {
+ VPROF( "Host_NewGame_HostState_RunGameInit" );
+ HostState_RunGameInit();
+ }
+
+ // init network mode
+ VPROF_SCOPE_BEGIN( "Host_NewGame_SpawnServer" );
+
+ NET_SetMutiplayer( sv.IsMultiplayer() );
+
+ NET_ListenSocket( sv.m_Socket, true ); // activated server TCP socket
+
+ // let's not have any servers with no name
+ if ( host_name.GetString()[0] == 0 )
+ {
+ host_name.SetValue( serverGameDLL->GetGameDescription() );
+ }
+
+ if ( !sv.SpawnServer ( szMapName, szMapFile, NULL ) )
+ {
+ return false;
+ }
+
+ sv.m_bIsLevelMainMenuBackground = bBackgroundLevel;
+
+ VPROF_SCOPE_END();
+
+ // make sure the time is set
+ g_ServerGlobalVariables.curtime = sv.GetTime();
+
+ COM_TimestampedLog( "serverGameDLL->LevelInit" );
+
+#ifndef SWDS
+ EngineVGui()->UpdateProgressBar(PROGRESS_LEVELINIT);
+
+ audiosourcecache->LevelInit( szMapName );
+#endif
+
+ g_pServerPluginHandler->LevelInit( szMapName, CM_EntityString(), pszOldMap, pszLandmark, loadGame && !bOldSave, bBackgroundLevel );
+
+ if ( loadGame && !bOldSave )
+ {
+ sv.SetPaused( true ); // pause until all clients connect
+ sv.m_bLoadgame = true;
+ g_ServerGlobalVariables.curtime = sv.GetTime();
+ }
+
+ if( !SV_ActivateServer() )
+ {
+ return false;
+ }
+
+ // Connect the local client when a "map" command is issued.
+ if ( !sv.IsDedicated() )
+ {
+ COM_TimestampedLog( "Stuff 'connect localhost' to console" );
+
+ char str[512];
+ Q_snprintf( str, sizeof( str ), "connect localhost:%d listenserver", sv.GetUDPPort() );
+ Cbuf_AddText( str );
+ }
+ else
+ {
+ // Dedicated server triggers map load here.
+ GetTestScriptMgr()->CheckPoint( "FinishedMapLoad" );
+ }
+
+#ifndef SWDS
+ if ( !loadGame || bOldSave )
+ {
+ // clear the most recent remember save, so the level will just restart if the player dies
+ saverestore->ForgetRecentSave();
+ }
+
+ saverestore->SetMostRecentElapsedMinutes( 0 );
+ saverestore->SetMostRecentElapsedSeconds( 0 );
+#endif
+
+ if (MapReslistGenerator().IsEnabled())
+ {
+ MapReslistGenerator().OnLevelLoadEnd();
+ }
+ DownloadListGenerator().OnLevelLoadEnd();
+ return true;
+}
+
+void Host_FreeStateAndWorld( bool server )
+{
+ bool bNeedsPurge = false;
+
+ Assert( host_initialized );
+ Assert( host_hunklevel );
+
+ // If called by the client and we are running a listen server, just ignore
+ if ( !server && sv.IsActive() )
+ return;
+
+ // HACKHACK: You can't clear the hunk unless the client data is free
+ // since this gets called by the server, it's necessary to wipe the client
+ // in case we are on a listen server
+#ifndef SWDS
+ if ( server && !sv.IsDedicated() )
+ {
+ CL_ClearState();
+ }
+#endif
+
+ // The world model relies on the low hunk, so we need to force it to unload
+ if ( host_state.worldmodel )
+ {
+ modelloader->UnreferenceModel( host_state.worldmodel, IModelLoader::FMODELLOADER_SERVER );
+ modelloader->UnreferenceModel( host_state.worldmodel, IModelLoader::FMODELLOADER_CLIENT );
+ host_state.SetWorldModel( NULL );
+ bNeedsPurge = server && true;
+ }
+
+ // Unload and reset dynamic models
+ if ( server )
+ {
+ extern IVModelInfo* modelinfo;
+ modelinfo->OnLevelChange();
+ }
+#ifndef SWDS
+ else
+ {
+ extern IVModelInfoClient* modelinfoclient;
+ modelinfoclient->OnLevelChange();
+ }
+#endif
+
+ modelloader->UnloadUnreferencedModels();
+
+ g_TimeLastMemTest = 0;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void Host_FreeToLowMark( bool server )
+{
+ Assert( host_initialized );
+ Assert( host_hunklevel );
+
+ // If called by the client and we are running a listen server, just ignore
+ if ( !server && sv.IsActive() )
+ return;
+
+ CM_FreeMap();
+
+ if ( host_hunklevel )
+ {
+ // See if we are going to obliterate any malloc'd pointers
+ Hunk_FreeToLowMark(host_hunklevel);
+ }
+}
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void Host_Shutdown(void)
+{
+ extern void ShutdownMixerControls();
+
+ if ( host_checkheap )
+ {
+#ifdef _WIN32
+ if ( _heapchk() != _HEAPOK )
+ {
+ Sys_Error( "Host_Shutdown (top): _heapchk() != _HEAPOK\n" );
+ }
+#endif
+ }
+
+ // Check for recursive shutdown, should never happen
+ static bool shutting_down = false;
+ if ( shutting_down )
+ {
+ Msg( "Recursive shutdown!!!\n" );
+ return;
+ }
+ shutting_down = true;
+
+ phonehome->Message( IPhoneHome::PHONE_MSG_ENGINEEND, NULL );
+ phonehome->Shutdown();
+
+#ifndef SWDS
+ // Store active configuration settings
+ Host_WriteConfiguration();
+#endif
+
+ // Disconnect from server
+ Host_Disconnect(true);
+
+#ifndef SWDS
+ // keep ConMsg from trying to update the screen
+ scr_disabled_for_loading = true;
+#endif
+
+#if defined VOICE_OVER_IP && !defined SWDS && !defined( NO_VOICE ) //!defined(_XBOX)
+ Voice_Deinit();
+#endif // VOICE_OVER_IP
+
+ // TODO, Trace this
+ CM_FreeMap();
+
+ host_initialized = false;
+
+#if defined(VPROF_ENABLED)
+ VProfRecord_Shutdown();
+#endif
+
+#if !defined SWDS
+ if ( !sv.IsDedicated() )
+ {
+ TRACESHUTDOWN( Decal_Shutdown() );
+
+ TRACESHUTDOWN( R_Shutdown() );
+
+ TRACESHUTDOWN( SCR_Shutdown() );
+
+ TRACESHUTDOWN( S_Shutdown() );
+
+ TRACESHUTDOWN( ClientDLL_Shutdown() );
+
+ TRACESHUTDOWN( TextMessageShutdown() );
+
+ TRACESHUTDOWN( EngineVGui()->Shutdown() );
+
+ TRACESHUTDOWN( StaticPropMgr()->Shutdown() );
+
+ // Model loader must shutdown before StudioRender
+ // because it calls into StudioRender
+ TRACESHUTDOWN( modelloader->Shutdown() );
+
+ TRACESHUTDOWN( ShutdownStudioRender() );
+
+ TRACESHUTDOWN( ShutdownMaterialSystem() );
+
+ TRACESHUTDOWN( CL_Shutdown() );
+ }
+ else
+#endif
+ {
+ TRACESHUTDOWN( Decal_Shutdown() );
+
+ TRACESHUTDOWN( modelloader->Shutdown() );
+
+ TRACESHUTDOWN( ShutdownStudioRender() );
+
+ TRACESHUTDOWN( StaticPropMgr()->Shutdown() );
+
+ TRACESHUTDOWN( ShutdownMaterialSystem() );
+ }
+
+#if defined( REPLAY_ENABLED )
+ if ( Replay_IsSupportedModAndPlatform() )
+ {
+ TRACESHUTDOWN( ReplaySystem_Shutdown() );
+ }
+#endif
+
+ TRACESHUTDOWN( HLTV_Shutdown() );
+
+ TRACESHUTDOWN( g_Log.Shutdown() );
+
+ TRACESHUTDOWN( g_GameEventManager.Shutdown() );
+
+ TRACESHUTDOWN( sv.Shutdown() );
+
+ TRACESHUTDOWN( NET_Shutdown() );
+
+#ifndef SWDS
+ TRACESHUTDOWN( Key_Shutdown() );
+#ifndef _X360
+ TRACESHUTDOWN( ShutdownMixerControls() );
+#endif
+#endif
+
+ TRACESHUTDOWN( Filter_Shutdown() );
+
+#ifndef SWDS
+ TRACESHUTDOWN( saverestore->Shutdown() );
+#endif
+
+ TRACESHUTDOWN( COM_Shutdown() );
+
+ // TRACESHUTDOWN( Host_ShutdownVCR() );
+#ifndef SWDS
+ TRACESHUTDOWN( V_Shutdown() );
+#endif
+
+ TRACESHUTDOWN( g_pCVar->Shutdown() );
+
+ TRACESHUTDOWN( Cmd_Shutdown() );
+
+ TRACESHUTDOWN( Cbuf_Shutdown() );
+
+ TRACESHUTDOWN( Con_Shutdown() );
+
+ TRACESHUTDOWN( Memory_Shutdown() );
+
+ if ( g_pThreadPool )
+ g_pThreadPool->Stop();
+
+ DTI_Term();
+ ServerDTI_Term();
+
+#if defined(_WIN32)
+ if ( s_bInitPME )
+ {
+ ShutdownPME();
+ }
+#endif
+
+ if ( host_checkheap )
+ {
+#ifdef _WIN32
+ if ( _heapchk() != _HEAPOK )
+ {
+ Sys_Error( "Host_Shutdown (bottom): _heapchk() != _HEAPOK\n" );
+ }
+#endif
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Centralize access to enabling QMS.
+//-----------------------------------------------------------------------------
+bool Host_AllowQueuedMaterialSystem( bool bAllow )
+{
+#if !defined DEDICATED
+ // g_bAllowThreadedSound = bAllow;
+ // NOTE: Moved this to materialsystem for integrating with other mqm changes
+ return g_pMaterialSystem->AllowThreading( bAllow, g_nMaterialSystemThread );
+#endif
+ return false;
+}