css_enhanced_waf/engine/host.cpp
unknown d03c92b6f0 We have now better angle update
Since it was previously based on old frametime,
you would see your angle changes from previous frametime,
not the current one...
It's now fixed.
2024-09-12 22:33:24 +02:00

5096 lines
133 KiB
C++

//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
//===========================================================================//
#include "datamap.h"
#include "dbg.h"
#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"
#include "master.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>
#ifdef USE_SDL
#include "SDL.h"
#endif
#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", FCVAR_REPLICATED, "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;
master->ShutdownConnection();
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 frametime, 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( frametime, 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" );
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;
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();
#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;
}
cl.GetClockDriftMgr().m_nNumberOfTicks = numticks;
host_nexttick = host_state.interval_per_tick - host_remainder;
g_pMDLCache->MarkFrame();
}
#ifndef SWDS
const auto CalcInterpolationAmount = [&]()
{
// TODO_ENHANCED:
// This check permits to fix interpolation problems on the
// local player that valve has been (fucking finally)
// caring about on counter-strike 2.
//
// To recall the original issue, the
// problem that Valve cared about is that interpolation
// had some problems with interpolating the local
// player because the screen would never in the first
// place match the tick "screen", because interpolation
// amount could never reach 0.0 or 1.0
//
// Valve solution was to introduce bugs with lag
// compensating the local player and made the game worse,
// introducing a new way for cheaters to cheat even more
// on their games.
// I'm joking, but you can clearly see the outcome anyway.
//
// My solution is to simply set interpolation amount
// to 0.0 when a tick arrives.
//
// So when we shoot, we get the frame we shot with an
// interpolation amount at 0.0, perfectly aligned to user
// commands which is ideal for us.
//
// README_ENHANCED:
// Unfortunately, some players still notice it with low enough fps.
// This is a problem; we return then to lag compensating the local player.
// Two choices here:
//
// 1) For precision we might need to send camera position, that is proven to work.
// 2) Send interpolation_amount so we calculate it server and in runcommand.
//
// Both works, but the first one requires validation,
// the second doesn't at the expense of some unprecisions due to floats.
// The first one is the easier route to avoid issues.
g_ClientGlobalVariables.interpolation_amount = cl.m_tickRemainder / host_state.interval_per_tick;
};
#endif
{
// 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);
#ifndef SWDS
g_ClientGlobalVariables.next_interpolation_amount = cl.m_tickRemainder / host_state.interval_per_tick;
// TODO_ENHANCED:
// Update the mouse as first so we can get the right viewangles while rendering.
// 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
g_ClientGlobalVariables.frametime = host_frametime;
CL_ExtraMovementUpdate( host_frametime );
#endif
for ( int tick = 0; tick < numticks; tick++ )
{
g_ServerGlobalVariables.currenttick = tick;
#ifndef SWDS
g_ClientGlobalVariables.currenttick = tick;
#endif
// 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
//---------------------------------------------------------
// Run prediction, useful when fps is lower than tickrate.
//---------------------------------------------------------
// CL_RunPrediction( PREDICTION_NORMAL );
_Host_RunFrame_Input( host_frametime, bFinalTick );
//-------------------
//
// 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);
CalcInterpolationAmount();
#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();
}
#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;
g_ClientGlobalVariables.next_interpolation_amount = cl.m_tickRemainder / host_state.interval_per_tick;
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;
// TODO_ENHANCED:
// Update the mouse as first so we can get the right viewangles while rendering.
// 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
g_ClientGlobalVariables.frametime = host_frametime;
CL_ExtraMovementUpdate( host_frametime );
// THREADED: Run Client
// -------------------
for ( int tick = 0; tick < clientticks; tick++ )
{
g_ServerGlobalVariables.currenttick = tick;
g_ClientGlobalVariables.currenttick = 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() ) )
{
g_ServerGlobalVariables.currenttick = 0;
g_ClientGlobalVariables.currenttick = 0;
_Host_RunFrame_Client( true );
}
// This causes cl.gettime() to return the true clock being used for rendering (tickcount * rate + remainder)
Host_SetClientInSimulation( false );
CalcInterpolationAmount();
//-------------------
// 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++ )
{
g_ServerGlobalVariables.currenttick = tick;
g_ClientGlobalVariables.currenttick = 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;
// Run prediction before inputs if fps is lower than tickrate
// CL_RunPrediction( PREDICTION_NORMAL );
_Host_RunFrame_Input( host_frametime, bFinalTick );
// process any asynchronous network traffic (TCP), set net_time
NET_RunFrame( Plat_FloatTime() );
}
Host_SetClientInSimulation( false );
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();
#ifndef DEDICATED
if ( g_bVCRSingleStep )
{
VCR_EnterPausedState();
}
#endif
}
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 ) && !defined ANDROID
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() || IsBSD() )
{
// 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)
{
#ifndef ANDROID
extern void ShutdownMixerControls();
#endif
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() );
#if !defined _X360 && !defined ANDROID
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;
}