Kamay Xutax
cfa5b12eea
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. It might look a bit more unsmooth with lower fps but with high enough fps, the issue goes away anyway. It's not very noticeable which is very nice for us. No need to lag compensate the local player anymore !
5099 lines
133 KiB
C++
5099 lines
133 KiB
C++
//========= Copyright Valve Corporation, All rights reserved. ============//
|
|
//
|
|
// Purpose:
|
|
//
|
|
//===========================================================================//
|
|
|
|
#include "tier0/fasttimer.h"
|
|
|
|
#ifdef _WIN32
|
|
#include "tier0/memdbgon.h" // needed because in release builds crtdbg.h is handled specially if USE_MEM_DEBUG is defined
|
|
#include "tier0/memdbgoff.h"
|
|
#include <crtdbg.h> // For getting at current heap size
|
|
#endif
|
|
|
|
#include "tier1/fmtstr.h"
|
|
#include "vstdlib/jobthread.h"
|
|
|
|
#ifdef USE_SDL
|
|
#include "appframework/ilaunchermgr.h"
|
|
extern ILauncherMgr *g_pLauncherMgr;
|
|
#endif
|
|
|
|
#include "server.h"
|
|
#include "host_jmp.h"
|
|
#include "screen.h"
|
|
#include "keys.h"
|
|
#include "cdll_int.h"
|
|
#include "eiface.h"
|
|
#include "sv_main.h"
|
|
#include "sv_log.h"
|
|
#include "shadowmgr.h"
|
|
#include "zone.h"
|
|
#include "gl_cvars.h"
|
|
#include "sv_filter.h"
|
|
#include "ivideomode.h"
|
|
#include "vprof_engine.h"
|
|
#include "iengine.h"
|
|
#include "tier2/tier2.h"
|
|
#include "enginethreads.h"
|
|
#include "steam/steam_api.h"
|
|
#include "LoadScreenUpdate.h"
|
|
#include "datacache/idatacache.h"
|
|
#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", 0, "Set to lock per-frame time elapse." );
|
|
ConVar host_timescale( "host_timescale","1.0", FCVAR_REPLICATED, "Prescale the clock by this amount." );
|
|
ConVar host_speeds( "host_speeds","0", 0, "Show general system running times." ); // set for running times
|
|
|
|
ConVar host_flush_threshold( "host_flush_threshold", "20", 0, "Memory threshold below which the host should flush caches between server instances" );
|
|
|
|
void HostTimerSpinMsChangedCallback( IConVar *var, const char *pOldString, float flOldValue );
|
|
ConVar host_timer_spin_ms( "host_timer_spin_ms", "0", FCVAR_NONE, "Use CPU busy-loop for improved timer precision (dedicated only)", HostTimerSpinMsChangedCallback );
|
|
|
|
void HostTimerSpinMsChangedCallback( IConVar *var, const char *pOldString, float flOldValue )
|
|
{
|
|
const char *pForcedValue = CommandLine()->ParmValue( "+host_timer_spin_ms" );
|
|
if ( pForcedValue != NULL )
|
|
{
|
|
if ( V_strcmp( host_timer_spin_ms.GetString(), pForcedValue ) )
|
|
{
|
|
Msg( "Value for host_timer_spin_ms is locked to %s by command line parameter.\n", pForcedValue );
|
|
host_timer_spin_ms.SetValue( pForcedValue );
|
|
}
|
|
}
|
|
}
|
|
|
|
CON_COMMAND( host_timer_report, "Spew CPU timer jitter for the last 128 frames in microseconds (dedicated only)" )
|
|
{
|
|
if ( sv.IsDedicated() )
|
|
{
|
|
for (int i = 1; i <= ARRAYSIZE( host_jitterhistory ); ++i)
|
|
{
|
|
unsigned int slot = ( i + host_jitterhistorypos ) % ARRAYSIZE( host_jitterhistory );
|
|
Msg( "%1.3fms\n", host_jitterhistory[ slot ] * 1000 );
|
|
}
|
|
}
|
|
}
|
|
|
|
#ifdef REL_TO_STAGING_MERGE_TODO
|
|
// Do this when merging the game DLLs so FCVAR_CHEAT can be set on them at the same time.
|
|
ConVar developer( "developer", "0", FCVAR_CHEAT, "Set developer message level");
|
|
#else
|
|
ConVar developer( "developer", "0", 0, "Set developer message level");
|
|
#endif
|
|
|
|
ConVar skill( "skill","1", FCVAR_ARCHIVE | FCVAR_ARCHIVE_XBOX, "Game skill level (1-3).", true, 1, true, 3 ); // 1 - 3
|
|
ConVar deathmatch( "deathmatch","0", FCVAR_NOTIFY | FCVAR_INTERNAL_USE, "Running a deathmatch server." ); // 0, 1, or 2
|
|
ConVar coop( "coop","0", FCVAR_NOTIFY, "Cooperative play." ); // 0 or 1
|
|
|
|
#ifdef _DEBUG
|
|
ConVar r_ForceRestore( "r_ForceRestore", "0", 0 );
|
|
#endif // _DEBUG
|
|
|
|
ConVar vcr_verbose( "vcr_verbose", "0", 0, "Write extra information into .vcr file." );
|
|
|
|
#ifndef SWDS
|
|
void CL_CheckToDisplayStartupMenus(); // in cl_main.cpp
|
|
#endif
|
|
|
|
|
|
bool GetFileFromRemoteStorage( ISteamRemoteStorage *pRemoteStorage, const char *pszRemoteFileName, const char *pszLocalFileName )
|
|
{
|
|
bool bSuccess = false;
|
|
|
|
// check if file exists in Steam Cloud first
|
|
int32 nFileSize = pRemoteStorage->GetFileSize( pszRemoteFileName );
|
|
|
|
if ( nFileSize > 0 )
|
|
{
|
|
CUtlMemory<char> buf( 0, nFileSize );
|
|
if ( pRemoteStorage->FileRead( pszRemoteFileName, buf.Base(), nFileSize ) == nFileSize )
|
|
{
|
|
|
|
char filepath[ 512 ];
|
|
Q_strncpy( filepath, pszLocalFileName, sizeof( filepath ) );
|
|
Q_StripFilename( filepath );
|
|
g_pFullFileSystem->CreateDirHierarchy( filepath, "MOD" );
|
|
|
|
FileHandle_t hFile = g_pFileSystem->Open( pszLocalFileName, "wb", "MOD" );
|
|
if( hFile )
|
|
{
|
|
|
|
bSuccess = g_pFileSystem->Write( buf.Base(), nFileSize, hFile ) == nFileSize;
|
|
g_pFileSystem->Close( hFile );
|
|
|
|
if ( bSuccess )
|
|
{
|
|
DevMsg( "[Cloud]: SUCCEESS retrieved %s from remote storage into %s\n", pszRemoteFileName, pszLocalFileName );
|
|
}
|
|
else
|
|
{
|
|
DevMsg( "[Cloud]: FAILED retrieved %s from remote storage into %s\n", pszRemoteFileName, pszLocalFileName );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return bSuccess;
|
|
}
|
|
|
|
|
|
void CCommonHostState::SetWorldModel( model_t *pModel )
|
|
{
|
|
worldmodel = pModel;
|
|
if ( pModel )
|
|
{
|
|
worldbrush = pModel->brush.pShared;
|
|
}
|
|
else
|
|
{
|
|
worldbrush = NULL;
|
|
}
|
|
}
|
|
|
|
void Host_DefaultMapFileName( const char *pFullMapName, /* out */ char *pDiskName, unsigned int nDiskNameSize )
|
|
{
|
|
if ( IsPC() || !IsX360() )
|
|
{
|
|
// pc names are as is
|
|
Q_snprintf( pDiskName, nDiskNameSize, "maps/%s.bsp", pFullMapName );
|
|
}
|
|
else if ( IsX360() )
|
|
{
|
|
Q_snprintf( pDiskName, nDiskNameSize, "maps/%s.360.bsp", pFullMapName );
|
|
}
|
|
}
|
|
|
|
void Host_SetAudioState( const AudioState_t &audioState )
|
|
{
|
|
memcpy( &s_AudioState, &audioState, sizeof(AudioState_t) );
|
|
}
|
|
|
|
bool Host_IsSinglePlayerGame( void )
|
|
{
|
|
if ( sv.IsActive() )
|
|
{
|
|
return !sv.IsMultiplayer();
|
|
}
|
|
else
|
|
{
|
|
return cl.m_nMaxClients == 1;
|
|
}
|
|
}
|
|
|
|
void CheckForFlushMemory( const char *pCurrentMapName, const char *pDestMapName )
|
|
{
|
|
if ( host_flush_threshold.GetInt() == 0 )
|
|
return;
|
|
|
|
#if defined(_X360)
|
|
// There are three cases in which we flush memory
|
|
// Case 1: changing from one map to another
|
|
// -> flush temp data caches
|
|
// Case 2: loading any map (inc. A to A) and free memory is below host_flush_threshold MB
|
|
// -> flush everything
|
|
// Case 3: loading a 'blacklisted' map (the known biggest memory users, or where texture sets change)
|
|
// -> flush everything
|
|
static const char *mapBlackList[] =
|
|
{
|
|
// --hl2--
|
|
"d1_canals_01",
|
|
"d1_canals_05",
|
|
"d1_eli_01",
|
|
"d1_town_01",
|
|
"d2_coast_01",
|
|
"d2_prison_01",
|
|
"d3_c17_01",
|
|
"d3_c17_05",
|
|
"d3_c17_09",
|
|
"d3_citadel_01",
|
|
"d3_breen_01",
|
|
// --ep1--
|
|
"ep1_c17_02",
|
|
"ep1_c17_02b",
|
|
"ep1_c17_05",
|
|
"ep1_c17_06",
|
|
// --ep2--
|
|
"ep2_outland_06a",
|
|
"ep2_outland_09",
|
|
"ep2_outland_11",
|
|
"ep2_outland_12",
|
|
"ep2_outland_12a",
|
|
// --tf--
|
|
"tc_hydro"
|
|
};
|
|
|
|
char szCurrentMapName[MAX_PATH];
|
|
char szDestMapName[MAX_PATH];
|
|
if ( pCurrentMapName )
|
|
{
|
|
V_FileBase( pCurrentMapName, szCurrentMapName, sizeof( szCurrentMapName ) );
|
|
}
|
|
else
|
|
{
|
|
szCurrentMapName[0] = '\0';
|
|
}
|
|
pCurrentMapName = szCurrentMapName;
|
|
|
|
if ( pDestMapName )
|
|
{
|
|
V_FileBase( pDestMapName, szDestMapName, sizeof( szDestMapName ) );
|
|
}
|
|
else
|
|
{
|
|
szDestMapName[0] = '\0';
|
|
}
|
|
pDestMapName = szDestMapName;
|
|
|
|
bool bIsMapChanging = pCurrentMapName[0] && V_stricmp( pCurrentMapName, pDestMapName );
|
|
|
|
bool bIsDestMapBlacklisted = false;
|
|
for ( int i = 0; i < ARRAYSIZE( mapBlackList ); i++ )
|
|
{
|
|
if ( pDestMapName && !V_stricmp( pDestMapName, mapBlackList[i] ) )
|
|
{
|
|
bIsDestMapBlacklisted = true;
|
|
}
|
|
}
|
|
|
|
DevMsg( "---CURRENT(%s), NEXT(%s)\n", (pCurrentMapName[0] ? pCurrentMapName : "----"), (pDestMapName[0] ? pDestMapName : "----") );
|
|
if ( bIsMapChanging )
|
|
{
|
|
DevMsg( "---CHANGING MAPS!\n" );
|
|
}
|
|
if ( bIsDestMapBlacklisted )
|
|
{
|
|
DevMsg( "---BLACKLISTED!\n" );
|
|
}
|
|
|
|
MEMORYSTATUS stat;
|
|
GlobalMemoryStatus( &stat );
|
|
if ( ( stat.dwAvailPhys < host_flush_threshold.GetInt() * 1024 * 1024 ) ||
|
|
( bIsDestMapBlacklisted && bIsMapChanging ) )
|
|
{
|
|
// Flush everything; ALL data is reloaded from scratch
|
|
SV_FlushMemoryOnNextServer();
|
|
g_pDataCache->Flush();
|
|
DevWarning( "---FULL FLUSH\n" );
|
|
}
|
|
else if ( bIsMapChanging )
|
|
{
|
|
// Flush temporary data (async anim, non-locked async audio)
|
|
g_pMDLCache->Flush( MDLCACHE_FLUSH_ANIMBLOCK );
|
|
wavedatacache->Flush();
|
|
DevWarning( "---PARTIAL FLUSH\n" );
|
|
}
|
|
DevMsg( "---- --- ----\n" );
|
|
#endif
|
|
}
|
|
|
|
void Host_AbortServer()
|
|
{
|
|
g_HostServerAbortCount++;
|
|
longjmp( host_abortserver, 1 );
|
|
}
|
|
|
|
/*
|
|
================
|
|
Host_EndGame
|
|
================
|
|
*/
|
|
void Host_EndGame (bool bShowMainMenu, const char *message, ...)
|
|
{
|
|
int oldn;
|
|
va_list argptr;
|
|
char string[1024];
|
|
|
|
va_start (argptr,message);
|
|
Q_vsnprintf (string,sizeof(string),message,argptr);
|
|
va_end (argptr);
|
|
ConMsg ("Host_EndGame: %s\n",string);
|
|
|
|
#ifndef SWDS
|
|
scr_disabled_for_loading = true;
|
|
#endif
|
|
|
|
oldn = cl.demonum;
|
|
cl.demonum = -1;
|
|
|
|
Host_Disconnect(bShowMainMenu);
|
|
|
|
cl.demonum = oldn;
|
|
|
|
if ( sv.IsDedicated() )
|
|
{
|
|
Sys_Error ("Host_EndGame: %s\n",string); // dedicated servers exit
|
|
return;
|
|
}
|
|
|
|
if (cl.demonum != -1)
|
|
{
|
|
#ifndef SWDS
|
|
CL_NextDemo ();
|
|
#endif
|
|
g_HostEndDemo++;
|
|
longjmp (host_enddemo, 1);
|
|
}
|
|
else
|
|
{
|
|
|
|
#ifndef SWDS
|
|
scr_disabled_for_loading = false;
|
|
#endif
|
|
if ( g_bAbortServerSet )
|
|
{
|
|
Host_AbortServer();
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
Host_Error
|
|
|
|
This shuts down both the client and server
|
|
================
|
|
*/
|
|
void Host_Error (const char *error, ...)
|
|
{
|
|
va_list argptr;
|
|
char string[1024];
|
|
static bool inerror = false;
|
|
|
|
DebuggerBreakIfDebugging_StagingOnly();
|
|
|
|
g_HostErrorCount++;
|
|
|
|
if (inerror)
|
|
{
|
|
Sys_Error ("Host_Error: recursively entered");
|
|
}
|
|
inerror = true;
|
|
|
|
#ifndef SWDS
|
|
// CL_WriteMessageHistory(); TODO must be done by network layer
|
|
#endif
|
|
|
|
va_start (argptr,error);
|
|
Q_vsnprintf(string,sizeof(string),error,argptr);
|
|
va_end (argptr);
|
|
|
|
if ( sv.IsDedicated() )
|
|
{
|
|
// dedicated servers just exit
|
|
Sys_Error( "Host_Error: %s\n", string );
|
|
return;
|
|
}
|
|
|
|
#ifndef SWDS
|
|
// Reenable screen updates
|
|
SCR_EndLoadingPlaque ();
|
|
#endif
|
|
ConMsg( "\nHost_Error: %s\n\n", string );
|
|
|
|
Host_Disconnect( true, string );
|
|
|
|
cl.demonum = -1;
|
|
|
|
inerror = false;
|
|
|
|
if ( g_bAbortServerSet )
|
|
{
|
|
Host_AbortServer();
|
|
}
|
|
}
|
|
|
|
#ifndef SWDS
|
|
|
|
ButtonCode_t nInvalidKeyBindings[] =
|
|
{
|
|
KEY_PAD_0,
|
|
KEY_PAD_1,
|
|
KEY_PAD_2,
|
|
KEY_PAD_3,
|
|
KEY_PAD_4,
|
|
KEY_PAD_5,
|
|
KEY_PAD_6,
|
|
KEY_PAD_7,
|
|
KEY_PAD_8,
|
|
KEY_PAD_9,
|
|
KEY_PAD_DIVIDE,
|
|
KEY_PAD_MULTIPLY,
|
|
KEY_PAD_MINUS,
|
|
KEY_PAD_PLUS,
|
|
KEY_PAD_ENTER,
|
|
KEY_PAD_DECIMAL,
|
|
KEY_ESCAPE,
|
|
KEY_SCROLLLOCK,
|
|
KEY_INSERT,
|
|
KEY_DELETE,
|
|
KEY_HOME,
|
|
KEY_END,
|
|
KEY_PAGEUP,
|
|
KEY_PAGEDOWN,
|
|
KEY_BREAK,
|
|
KEY_LSHIFT,
|
|
KEY_RSHIFT,
|
|
KEY_LALT,
|
|
KEY_RALT,
|
|
KEY_LCONTROL,
|
|
KEY_RCONTROL,
|
|
KEY_LWIN,
|
|
KEY_RWIN,
|
|
KEY_APP,
|
|
KEY_UP,
|
|
KEY_LEFT,
|
|
KEY_DOWN,
|
|
KEY_RIGHT,
|
|
KEY_CAPSLOCKTOGGLE,
|
|
KEY_NUMLOCKTOGGLE,
|
|
KEY_SCROLLLOCKTOGGLE,
|
|
KEY_BACKQUOTE,
|
|
};
|
|
|
|
//******************************************
|
|
// SetupNewBindings
|
|
//
|
|
// In the rare case that we ship an update
|
|
// where we need a key to be bound to a given
|
|
// con-command, this function do its best to
|
|
// bind those keys, based on a file called
|
|
// newbindings.txt.
|
|
//******************************************
|
|
void SetupNewBindings()
|
|
{
|
|
char szBindCmd[ 256 ];
|
|
|
|
// Load the file
|
|
const char *pFilename = "scripts\\newbindings.txt";
|
|
KeyValues *pNewBindingsData = new KeyValues( pFilename );
|
|
if ( !pNewBindingsData->LoadFromFile( g_pFileSystem, pFilename ) )
|
|
{
|
|
pNewBindingsData->deleteThis();
|
|
return;
|
|
}
|
|
|
|
FOR_EACH_TRUE_SUBKEY( pNewBindingsData, pSubKey )
|
|
{
|
|
// Get the binding
|
|
const char *pBinding = pSubKey->GetName();
|
|
if ( !pBinding )
|
|
continue;
|
|
|
|
// Get the ideal key
|
|
const char *pIdealKey = pSubKey->GetString( "ideal_key", NULL );
|
|
if ( !pIdealKey )
|
|
continue;
|
|
|
|
// Force the key binding if the ideal key is bound to an irrelevant command, which is the
|
|
// case for CS:S, which binds F6 to "quick save," which has no used in the game at all.
|
|
const char *pOverrideIfCmd = pSubKey->GetString( "override_if", NULL );
|
|
if ( pOverrideIfCmd )
|
|
{
|
|
const char *pCurrentBindingForKey = ::Key_BindingForKey( g_pInputSystem->StringToButtonCode( pIdealKey ) );
|
|
if ( !pCurrentBindingForKey || !V_stricmp( pOverrideIfCmd, pCurrentBindingForKey ) )
|
|
{
|
|
V_snprintf( szBindCmd, sizeof( szBindCmd ), "bind \"%s\" \"%s\"", pIdealKey, pBinding );
|
|
Cbuf_AddText( szBindCmd );
|
|
continue;
|
|
}
|
|
}
|
|
|
|
// Already have a key for this command?
|
|
if ( ::Key_NameForBinding( pBinding ) )
|
|
continue;
|
|
|
|
// No binding. Is the ideal key available?
|
|
ButtonCode_t bcIdealKey = g_pInputSystem->StringToButtonCode( pIdealKey );
|
|
const char *pCurrentBindingForIdealKey = ::Key_BindingForKey( bcIdealKey );
|
|
if ( !pCurrentBindingForIdealKey )
|
|
{
|
|
// Yes - bind to the ideal key.
|
|
V_snprintf( szBindCmd, sizeof( szBindCmd ), "bind \"%s\" \"%s\"", pIdealKey, pBinding );
|
|
Cbuf_AddText( szBindCmd );
|
|
continue;
|
|
}
|
|
|
|
// Ideal key already bound - find another key at random and bind it
|
|
bool bFound = false;
|
|
int nNumAttempts = 1;
|
|
for ( int nCurButton = (int)KEY_0; nCurButton <= (int)KEY_LAST; ++nCurButton, ++nNumAttempts )
|
|
{
|
|
// Don't consider numpad, windows keys, etc
|
|
bool bFoundInvalidKey = false;
|
|
for ( int iKeyIndex = 0; iKeyIndex < sizeof( nInvalidKeyBindings )/sizeof( nInvalidKeyBindings[0] ); iKeyIndex++ )
|
|
{
|
|
if ( nCurButton == (int)nInvalidKeyBindings[iKeyIndex] )
|
|
{
|
|
bFoundInvalidKey = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if ( bFoundInvalidKey )
|
|
continue;
|
|
|
|
// Key available?
|
|
ButtonCode_t bcCurButton = (ButtonCode_t)nCurButton;
|
|
if ( !::Key_BindingForKey( bcCurButton ) )
|
|
{
|
|
// Yes - use it.
|
|
V_snprintf( szBindCmd, sizeof( szBindCmd ), "bind \"%s\" \"%s\"", g_pInputSystem->ButtonCodeToString( bcCurButton ), pBinding );
|
|
Cbuf_AddText( szBindCmd );
|
|
bFound = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// We tried really hard - did we fail?
|
|
if ( !bFound )
|
|
{
|
|
Warning( "Unable to bind a key for command \"%s\" after %i attempt(s).\n", pBinding, nNumAttempts );
|
|
}
|
|
}
|
|
}
|
|
|
|
//******************************************
|
|
// UseDefuaultBinding
|
|
//
|
|
// If the config.cfg file is not present, this
|
|
// function is called to set the default key
|
|
// bindings to match those defined in kb_def.lst
|
|
//******************************************
|
|
void UseDefaultBindings( void )
|
|
{
|
|
FileHandle_t f;
|
|
char szFileName[ _MAX_PATH ];
|
|
char token[ 1024 ];
|
|
char szKeyName[ 256 ];
|
|
|
|
// read kb_def file to get default key binds
|
|
Q_snprintf( szFileName, sizeof( szFileName ), "%skb_def.lst", SCRIPT_DIR );
|
|
f = g_pFileSystem->Open( szFileName, "r");
|
|
if ( !f )
|
|
{
|
|
ConMsg( "Couldn't open kb_def.lst\n" );
|
|
return;
|
|
}
|
|
|
|
// read file into memory
|
|
int size = g_pFileSystem->Size(f);
|
|
char *startbuf = new char[ size ];
|
|
g_pFileSystem->Read( startbuf, size, f );
|
|
g_pFileSystem->Close( f );
|
|
|
|
const char *buf = startbuf;
|
|
while ( 1 )
|
|
{
|
|
buf = COM_ParseFile( buf, token, sizeof( token ) );
|
|
if ( strlen( token ) <= 0 )
|
|
break;
|
|
Q_strncpy ( szKeyName, token, sizeof( szKeyName ) );
|
|
|
|
buf = COM_ParseFile( buf, token, sizeof( token ) );
|
|
if ( strlen( token ) <= 0 ) // Error
|
|
break;
|
|
|
|
// finally, bind key
|
|
Key_SetBinding ( g_pInputSystem->StringToButtonCode( szKeyName ), token );
|
|
}
|
|
delete [] startbuf; // cleanup on the way out
|
|
}
|
|
|
|
static bool g_bConfigCfgExecuted = false;
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Write out our 360 exclusive settings to internal storage
|
|
//-----------------------------------------------------------------------------
|
|
void Host_WriteConfiguration_360( void )
|
|
{
|
|
#ifdef _X360
|
|
if ( XBX_GetStorageDeviceId() == XBX_INVALID_STORAGE_ID || XBX_GetStorageDeviceId() == XBX_STORAGE_DECLINED )
|
|
return;
|
|
|
|
// Construct the name for our config settings for this mod
|
|
char strFilename[MAX_PATH];
|
|
Q_snprintf( strFilename, sizeof(strFilename), "cfg:/%s_config.cfg", GetCurrentMod() );
|
|
|
|
// Always throw away all keys that are left over.
|
|
CUtlBuffer configBuff( 0, 0, CUtlBuffer::TEXT_BUFFER);
|
|
configBuff.Printf( "unbindall\n" );
|
|
|
|
Key_WriteBindings( configBuff );
|
|
cv->WriteVariables( configBuff );
|
|
|
|
ConVarRef mat_monitorgamma( "mat_monitorgamma" );
|
|
ConVarRef mat_monitorgamma_tv_enabled( "mat_monitorgamma_tv_enabled" );
|
|
|
|
char strVideoFilename[MAX_PATH];
|
|
CUtlBuffer videoBuff( 0, 0, CUtlBuffer::TEXT_BUFFER);
|
|
Q_snprintf( strVideoFilename, sizeof(strVideoFilename), "cfg:/video_config.cfg" );
|
|
videoBuff.Printf( "mat_monitorgamma %f\n", mat_monitorgamma.GetFloat() );
|
|
videoBuff.Printf( "mat_monitorgamma_tv_enabled %d\n", mat_monitorgamma_tv_enabled.GetBool() );
|
|
|
|
// Anything to write?
|
|
if ( configBuff.TellMaxPut() )
|
|
{
|
|
g_pFileSystem->WriteFile( strFilename, NULL, configBuff );
|
|
}
|
|
|
|
if ( videoBuff.TellMaxPut() )
|
|
{
|
|
g_pFileSystem->WriteFile( strVideoFilename, NULL, videoBuff );
|
|
}
|
|
|
|
g_pXboxSystem->FinishContainerWrites();
|
|
#endif // #ifdef _X360
|
|
}
|
|
|
|
/*
|
|
===============
|
|
Host_WriteConfiguration
|
|
|
|
Writes key bindings and archived cvars to config.cfg
|
|
===============
|
|
*/
|
|
|
|
void Host_WriteConfiguration( const char *filename, bool bAllVars )
|
|
{
|
|
const bool cbIsUserRequested = ( filename != NULL );
|
|
|
|
if ( !filename )
|
|
filename = "config.cfg";
|
|
|
|
if ( !g_bConfigCfgExecuted )
|
|
return;
|
|
|
|
if ( !host_initialized )
|
|
return;
|
|
|
|
// Don't write config when in default--most of the values are defaults which is not what the player wants.
|
|
// If bAllVars is set, go ahead and write out the file anyways, since it was requested explicitly.
|
|
if ( !cbIsUserRequested && ( CommandLine()->CheckParm( "-default" ) || host_competitive_ever_enabled.GetBool() ) )
|
|
return;
|
|
|
|
// Write to internal storage on the 360
|
|
if ( IsX360() )
|
|
{
|
|
Host_WriteConfiguration_360();
|
|
return;
|
|
}
|
|
|
|
// If in map editing mode don't save configuration
|
|
if (g_bInEditMode)
|
|
{
|
|
ConMsg( "skipping %s output when in map edit mode\n", filename );
|
|
return;
|
|
}
|
|
|
|
// dedicated servers initialize the host but don't parse and set the
|
|
// config.cfg cvars
|
|
if ( sv.IsDedicated() )
|
|
return;
|
|
|
|
if ( IsPC() && Key_CountBindings() <= 1 )
|
|
{
|
|
ConMsg( "skipping %s output, no keys bound\n", filename );
|
|
return;
|
|
}
|
|
|
|
// force any queued convar changes to flush before reading/writing them
|
|
UpdateMaterialSystemConfig();
|
|
|
|
// Generate a new .cfg file.
|
|
char szFileName[MAX_PATH];
|
|
CUtlBuffer configBuff( 0, 0, CUtlBuffer::TEXT_BUFFER);
|
|
|
|
Q_snprintf( szFileName, sizeof(szFileName), "cfg/%s", filename );
|
|
g_pFileSystem->CreateDirHierarchy( "cfg", "MOD" );
|
|
if ( g_pFileSystem->FileExists( szFileName, "MOD" ) && !g_pFileSystem->IsFileWritable( szFileName, "MOD" ) )
|
|
{
|
|
ConMsg( "Config file %s is read-only!!\n", szFileName );
|
|
return;
|
|
}
|
|
|
|
// Always throw away all keys that are left over.
|
|
configBuff.Printf( "unbindall\n" );
|
|
|
|
Key_WriteBindings( configBuff );
|
|
cv->WriteVariables( configBuff, bAllVars );
|
|
|
|
#if !defined( SWDS )
|
|
bool down;
|
|
if ( g_ClientDLL->IN_IsKeyDown( "in_jlook", down ) && down )
|
|
{
|
|
configBuff.Printf( "+jlook\n" );
|
|
}
|
|
#endif // SWDS
|
|
|
|
if ( !configBuff.TellMaxPut() )
|
|
{
|
|
// nothing to write
|
|
return;
|
|
}
|
|
|
|
#if defined(NO_STEAM)
|
|
AssertMsg( false, "SteamCloud not available on Xbox 360. Badger Martin to fix this." );
|
|
#else
|
|
ISteamRemoteStorage *pRemoteStorage = SteamClient()?(ISteamRemoteStorage *)SteamClient()->GetISteamGenericInterface(
|
|
SteamAPI_GetHSteamUser(), SteamAPI_GetHSteamPipe(), STEAMREMOTESTORAGE_INTERFACE_VERSION ):NULL;
|
|
|
|
if ( pRemoteStorage )
|
|
{
|
|
int32 availableBytes, totalBytes = 0;
|
|
if ( pRemoteStorage->GetQuota( &totalBytes, &availableBytes ) )
|
|
{
|
|
if ( totalBytes > 0 )
|
|
{
|
|
if ( cl_cloud_settings.GetInt() == STEAMREMOTESTORAGE_CLOUD_ON )
|
|
{
|
|
// TODO put MOD dir in pathname
|
|
if ( pRemoteStorage->FileWrite( szFileName, configBuff.Base(), configBuff.TellMaxPut() ) )
|
|
{
|
|
DevMsg( "[Cloud]: SUCCEESS saving %s in remote storage\n", szFileName );
|
|
}
|
|
else
|
|
{
|
|
// probably a quota issue. TODO what to do ?
|
|
DevMsg( "[Cloud]: FAILED saving %s in remote storage\n", szFileName );
|
|
}
|
|
|
|
// write the current logo file
|
|
char szLogoFileName[MAX_PATH];
|
|
Q_strncpy( szLogoFileName, cl_logofile.GetString(), sizeof(szLogoFileName) ); // .vtf file
|
|
|
|
if ( g_pFileSystem->FileExists( szLogoFileName, "MOD" ) )
|
|
{
|
|
// store logo .VTF file
|
|
FileHandle_t hFile = g_pFileSystem->Open( szLogoFileName, "rb", "MOD" );
|
|
if ( FILESYSTEM_INVALID_HANDLE != hFile )
|
|
{
|
|
unsigned int unSize = g_pFileSystem->Size( hFile );
|
|
|
|
byte *pBuffer = (byte*) malloc( unSize );
|
|
if ( g_pFileSystem->Read( pBuffer, unSize, hFile ) == unSize )
|
|
{
|
|
Q_SetExtension( g_szDefaultLogoFileName, ".vtf", sizeof(g_szDefaultLogoFileName) );
|
|
if ( pRemoteStorage->FileWrite( g_szDefaultLogoFileName, pBuffer, unSize ) )
|
|
{
|
|
DevMsg( "[Cloud]: SUCCEESS saving %s in remote storage\n", g_szDefaultLogoFileName );
|
|
}
|
|
else
|
|
{
|
|
DevMsg( "[Cloud]: FAILED saving %s in remote storage\n", g_szDefaultLogoFileName );
|
|
}
|
|
}
|
|
free( pBuffer );
|
|
g_pFileSystem->Close( hFile );
|
|
}
|
|
|
|
// store logo .VMT file
|
|
Q_SetExtension( szLogoFileName, ".vmt", sizeof(szLogoFileName) );
|
|
hFile = g_pFileSystem->Open( szLogoFileName, "rb", "MOD" );
|
|
if ( FILESYSTEM_INVALID_HANDLE != hFile )
|
|
{
|
|
unsigned int unSize = g_pFileSystem->Size( hFile );
|
|
|
|
byte *pBuffer = (byte*) malloc( unSize );
|
|
if ( g_pFileSystem->Read( pBuffer, unSize, hFile ) == unSize )
|
|
{
|
|
Q_SetExtension( g_szDefaultLogoFileName, ".vmt", sizeof(g_szDefaultLogoFileName) );
|
|
if ( pRemoteStorage->FileWrite( g_szDefaultLogoFileName, pBuffer, unSize ) )
|
|
{
|
|
DevMsg( "[Cloud]: SUCCEESS saving %s in remote storage\n", g_szDefaultLogoFileName );
|
|
}
|
|
else
|
|
{
|
|
DevMsg( "[Cloud]: FAILED saving %s in remote storage\n", g_szDefaultLogoFileName );
|
|
}
|
|
}
|
|
free( pBuffer );
|
|
g_pFileSystem->Close( hFile );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// even if SteamCloud worked we still safe the same file locally
|
|
}
|
|
#endif
|
|
|
|
|
|
// make a persistent copy that async will use and free
|
|
char *tempBlock = new char[configBuff.TellMaxPut()];
|
|
Q_memcpy( tempBlock, configBuff.Base(), configBuff.TellMaxPut() );
|
|
|
|
// async write the buffer, and then free it
|
|
g_pFileSystem->AsyncWrite( szFileName, tempBlock, configBuff.TellMaxPut(), true );
|
|
|
|
ConMsg( "Host_WriteConfiguration: Wrote %s\n", szFileName );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Retrieve and set any defaults from the user's gamer profile
|
|
//-----------------------------------------------------------------------------
|
|
bool XBX_SetProfileDefaultSettings( void )
|
|
{
|
|
// These defined values can't play nicely with the PC, so we need to ignore them for that build target
|
|
#ifdef _X360
|
|
// These will act as indices into the array that is returned by the API
|
|
enum
|
|
{
|
|
XPS_GAMER_DIFFICULTY,
|
|
XPS_GAMER_ACTION_MOVEMENT_CONTROL,
|
|
XPS_GAMER_YAXIS_INVERSION,
|
|
XPS_OPTION_CONTROLLER_VIBRATION,
|
|
NUM_PROFILE_SETTINGS
|
|
};
|
|
|
|
// These are the values we're interested in having returned (must match the indices above)
|
|
const DWORD dwSettingIds[ NUM_PROFILE_SETTINGS ] =
|
|
{
|
|
XPROFILE_GAMER_DIFFICULTY,
|
|
XPROFILE_GAMER_ACTION_MOVEMENT_CONTROL,
|
|
XPROFILE_GAMER_YAXIS_INVERSION,
|
|
XPROFILE_OPTION_CONTROLLER_VIBRATION
|
|
};
|
|
|
|
// Must have a valid primary user by this point
|
|
int nPrimaryID = XBX_GetPrimaryUserId();
|
|
|
|
// First, we call with a NULL pointer and zero size to retrieve the buffer size we'll get back
|
|
DWORD dwResultSize = 0; // Must be zero to get the correct size back
|
|
XUSER_READ_PROFILE_SETTING_RESULT *pResults = NULL;
|
|
DWORD dwError = XUserReadProfileSettings( 0, // Family ID (current title)
|
|
nPrimaryID, // User ID
|
|
NUM_PROFILE_SETTINGS,
|
|
dwSettingIds,
|
|
&dwResultSize,
|
|
pResults,
|
|
NULL );
|
|
|
|
// We need this to inform us that it's given us a size back for the buffer
|
|
if ( dwError != ERROR_INSUFFICIENT_BUFFER )
|
|
return false;
|
|
|
|
// Now we allocate that buffer and supply it to the call
|
|
BYTE *pData = (BYTE *) stackalloc( dwResultSize );
|
|
ZeroMemory( pData, dwResultSize );
|
|
|
|
pResults = (XUSER_READ_PROFILE_SETTING_RESULT *) pData;
|
|
|
|
dwError = XUserReadProfileSettings( 0, // Family ID (current title)
|
|
nPrimaryID, // User ID
|
|
NUM_PROFILE_SETTINGS,
|
|
dwSettingIds,
|
|
&dwResultSize,
|
|
pResults,
|
|
NULL ); // Not overlapped, must be synchronous
|
|
|
|
// We now have a raw buffer of results
|
|
if ( dwError != ERROR_SUCCESS )
|
|
return false;
|
|
|
|
//
|
|
// Skill
|
|
//
|
|
|
|
XUSER_PROFILE_SETTING *pSetting = pResults->pSettings + XPS_GAMER_DIFFICULTY;
|
|
Assert( pSetting->data.type == XUSER_DATA_TYPE_INT32 );
|
|
|
|
int nSkillSetting = pSetting->data.nData;
|
|
int nResultSkill = 0;
|
|
switch( nSkillSetting )
|
|
{
|
|
case XPROFILE_GAMER_DIFFICULTY_NORMAL:
|
|
nResultSkill = 2;
|
|
break;
|
|
|
|
case XPROFILE_GAMER_DIFFICULTY_HARD:
|
|
nResultSkill = 3;
|
|
break;
|
|
|
|
case XPROFILE_GAMER_DIFFICULTY_EASY:
|
|
default:
|
|
nResultSkill = 1;
|
|
break;
|
|
}
|
|
|
|
// If the mod has no difficulty setting, only easy is allowed
|
|
KeyValues *modinfo = new KeyValues("ModInfo");
|
|
if ( modinfo->LoadFromFile( g_pFileSystem, "gameinfo.txt" ) )
|
|
{
|
|
if ( stricmp(modinfo->GetString("nodifficulty", "0"), "1") == 0 )
|
|
nResultSkill = 1;
|
|
}
|
|
modinfo->deleteThis();
|
|
|
|
char szScratch[MAX_PATH];
|
|
Q_snprintf( szScratch, sizeof(szScratch), "skill %d", nResultSkill );
|
|
Cbuf_AddText( szScratch );
|
|
|
|
//
|
|
// Movement control
|
|
//
|
|
|
|
pSetting = pResults->pSettings + XPS_GAMER_ACTION_MOVEMENT_CONTROL;
|
|
Assert( pSetting->data.type == XUSER_DATA_TYPE_INT32 );
|
|
Q_snprintf( szScratch, sizeof(szScratch), "joy_movement_stick %d", ( pSetting->data.nData == XPROFILE_ACTION_MOVEMENT_CONTROL_L_THUMBSTICK ) ? 0 : 1 );
|
|
Cbuf_AddText( szScratch );
|
|
Q_snprintf( szScratch, sizeof(szScratch), "joy_movement_stick_default %d", ( pSetting->data.nData == XPROFILE_ACTION_MOVEMENT_CONTROL_L_THUMBSTICK ) ? 0 : 1 );
|
|
Cbuf_AddText( szScratch );
|
|
Cbuf_AddText( "joyadvancedupdate" );
|
|
|
|
//
|
|
// Y-Inversion
|
|
//
|
|
|
|
pSetting = pResults->pSettings + XPS_GAMER_YAXIS_INVERSION;
|
|
Assert( pSetting->data.type == XUSER_DATA_TYPE_INT32 );
|
|
Q_snprintf( szScratch, sizeof(szScratch), "joy_inverty %d", pSetting->data.nData );
|
|
Cbuf_AddText( szScratch );
|
|
Q_snprintf( szScratch, sizeof(szScratch), "joy_inverty_default %d", pSetting->data.nData );
|
|
Cbuf_AddText( szScratch );
|
|
|
|
//
|
|
// Vibration control
|
|
//
|
|
|
|
pSetting = pResults->pSettings + XPS_OPTION_CONTROLLER_VIBRATION;
|
|
Assert( pSetting->data.type == XUSER_DATA_TYPE_INT32 );
|
|
Q_snprintf( szScratch, sizeof(szScratch), "cl_rumblescale %d", ( pSetting->data.nData != 0 ) ? 1 : 0 );
|
|
Cbuf_AddText( szScratch );
|
|
|
|
// Execute all commands we've queued up
|
|
Cbuf_Execute();
|
|
#endif // _X360
|
|
|
|
return true;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Read our configuration from the 360, filling in defaults on our first run
|
|
//-----------------------------------------------------------------------------
|
|
void Host_ReadConfiguration_360( void )
|
|
{
|
|
#ifdef _X360
|
|
|
|
// Exec our defaults on the first pass
|
|
if ( g_bConfigCfgExecuted == false )
|
|
{
|
|
// First, we exec our default configuration for the 360
|
|
Cbuf_AddText( "exec config.360.cfg game\n" );
|
|
Cbuf_Execute();
|
|
}
|
|
|
|
// Can't do any more in this function if we don't have a valid user id
|
|
if ( XBX_GetPrimaryUserId() == INVALID_USER_ID )
|
|
return;
|
|
|
|
// Build the config name we're looking for
|
|
char strFileName[MAX_PATH];
|
|
Q_snprintf( strFileName, sizeof(strFileName), "cfg:/%s_config.cfg", GetCurrentMod() );
|
|
|
|
bool bStorageDeviceValid = ( XBX_GetStorageDeviceId() != XBX_INVALID_STORAGE_ID && XBX_GetStorageDeviceId() != XBX_STORAGE_DECLINED );
|
|
bool bSaveConfig = false;
|
|
|
|
// Call through normal API function once the content container is opened
|
|
if ( CommandLine()->CheckParm( "-forcexboxreconfig" ) || bStorageDeviceValid == false || g_pFileSystem->FileExists( strFileName ) == false )
|
|
{
|
|
// If we've already done this in this session, never do it again (we don't want to stomp their settings under any circumstances)
|
|
if ( g_bConfigCfgExecuted == false )
|
|
{
|
|
// Get and set all our default setting we care about from the Xbox
|
|
XBX_SetProfileDefaultSettings();
|
|
}
|
|
|
|
// Save out what we have
|
|
bSaveConfig = true;
|
|
}
|
|
else
|
|
{
|
|
// Otherwise, exec the user settings stored on the 360
|
|
char szCommand[MAX_PATH];
|
|
Q_snprintf( szCommand, sizeof( szCommand ), "exec %s_config.cfg x360\n", GetCurrentMod() );
|
|
Cbuf_AddText( szCommand );
|
|
|
|
// Exec the video config as well
|
|
Q_snprintf( szCommand, sizeof( szCommand ), "exec video_config.cfg x360\n", GetCurrentMod() );
|
|
Cbuf_AddText( szCommand );
|
|
Cbuf_Execute();
|
|
}
|
|
|
|
// Mark that we've loaded a config and can now save it
|
|
g_bConfigCfgExecuted = true;
|
|
|
|
if ( bSaveConfig )
|
|
{
|
|
// An ugly hack, but we can probably save this safely
|
|
bool saveinit = host_initialized;
|
|
host_initialized = true;
|
|
Host_WriteConfiguration_360();
|
|
host_initialized = saveinit;
|
|
}
|
|
#endif // _X360
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
// Input : false -
|
|
//-----------------------------------------------------------------------------
|
|
void Host_ReadConfiguration()
|
|
{
|
|
if ( sv.IsDedicated() )
|
|
return;
|
|
|
|
// Rebind keys and set cvars
|
|
if ( !g_pFileSystem )
|
|
{
|
|
Sys_Error( "Host_ReadConfiguration: g_pFileSystem == NULL\n" );
|
|
}
|
|
|
|
// Handle the 360 case
|
|
if ( IsX360() )
|
|
{
|
|
Host_ReadConfiguration_360();
|
|
return;
|
|
}
|
|
|
|
bool saveconfig = false;
|
|
|
|
ISteamRemoteStorage *pRemoteStorage = SteamClient()?(ISteamRemoteStorage *)SteamClient()->GetISteamGenericInterface(
|
|
SteamAPI_GetHSteamUser(), SteamAPI_GetHSteamPipe(), STEAMREMOTESTORAGE_INTERFACE_VERSION ):NULL;
|
|
|
|
if ( pRemoteStorage )
|
|
{
|
|
// if cloud settings is default but remote storage does not exist yet, set it to sync all because this is the first
|
|
// computer the game is run on--default to copying everything to the cloud
|
|
if ( !pRemoteStorage->FileExists( "cfg/config.cfg" ) )
|
|
{
|
|
DevMsg( "[Cloud]: Default setting with remote data non-existent, sync all\n" );
|
|
cl_cloud_settings.SetValue( STEAMREMOTESTORAGE_CLOUD_ON );
|
|
}
|
|
|
|
if ( cl_cloud_settings.GetInt() == STEAMREMOTESTORAGE_CLOUD_ON )
|
|
{
|
|
// config files are run through the exec command which got pretty complicated with all the splitscreen
|
|
// stuff. Steam UFS doens't support split screen well (2 users ?)
|
|
GetFileFromRemoteStorage( pRemoteStorage, "cfg/config.cfg", "cfg/config.cfg" );
|
|
}
|
|
}
|
|
|
|
if ( g_pFileSystem->FileExists( "//mod/cfg/config.cfg" ) )
|
|
{
|
|
Cbuf_AddText( "exec config.cfg\n" );
|
|
}
|
|
else
|
|
{
|
|
Cbuf_AddText( "exec config_default.cfg\n" );
|
|
saveconfig = true;
|
|
}
|
|
|
|
Cbuf_Execute();
|
|
|
|
if ( pRemoteStorage )
|
|
{
|
|
if ( cl_cloud_settings.GetInt() == STEAMREMOTESTORAGE_CLOUD_ON )
|
|
{
|
|
// get logo .VTF file
|
|
Q_SetExtension( g_szDefaultLogoFileName, ".vtf", sizeof(g_szDefaultLogoFileName) );
|
|
GetFileFromRemoteStorage( pRemoteStorage, g_szDefaultLogoFileName, g_szDefaultLogoFileName );
|
|
|
|
cl_logofile.SetValue( g_szDefaultLogoFileName );
|
|
|
|
// get logo .VMT file
|
|
Q_SetExtension( g_szDefaultLogoFileName, ".vmt", sizeof(g_szDefaultLogoFileName) );
|
|
GetFileFromRemoteStorage( pRemoteStorage, g_szDefaultLogoFileName, g_szDefaultLogoFileName );
|
|
}
|
|
}
|
|
|
|
// check to see if we actually set any keys, if not, load defaults from kb_def.lst
|
|
// so we at least have basics setup.
|
|
int nNumBinds = Key_CountBindings();
|
|
if ( nNumBinds == 0 )
|
|
{
|
|
UseDefaultBindings();
|
|
}
|
|
else
|
|
{
|
|
// Setup new bindings - if we ship an update that requires that every user has a key bound for
|
|
// X concommand, SetupNewBindings() will do its best to ensure that a key is bound. We assume
|
|
// that kb_def.lst includes any bindings that SetupNewBindings() might include in the case
|
|
// that nNumBinds == 0 above.
|
|
SetupNewBindings();
|
|
}
|
|
|
|
Key_SetBinding( KEY_ESCAPE, "cancelselect" );
|
|
|
|
// Make sure that something is always bound to console
|
|
if (NULL == Key_NameForBinding("toggleconsole"))
|
|
{
|
|
// If nothing is bound to it then bind it to '
|
|
Key_SetBinding( KEY_BACKQUOTE, "toggleconsole" );
|
|
}
|
|
|
|
SetupDefaultAskConnectAcceptKey();
|
|
|
|
g_bConfigCfgExecuted = true;
|
|
|
|
if ( saveconfig )
|
|
{
|
|
// An ugly hack, but we can probably save this safely
|
|
bool saveinit = host_initialized;
|
|
host_initialized = true;
|
|
Host_WriteConfiguration();
|
|
host_initialized = saveinit;
|
|
}
|
|
}
|
|
|
|
CON_COMMAND( host_writeconfig, "Store current settings to config.cfg (or specified .cfg file)." )
|
|
{
|
|
if ( args.ArgC() > 3 )
|
|
{
|
|
ConMsg( "Usage: writeconfig <filename.cfg> [full]\n" );
|
|
ConMsg( "<filename.cfg> is required, optionally specify \"full\" to write out all archived convars.\n" );
|
|
ConMsg( "By default, only non-default values are written out.\n" );
|
|
return;
|
|
}
|
|
|
|
if ( args.ArgC() >= 2 )
|
|
{
|
|
bool bWriteAll = ( args.ArgC() == 3 && V_stricmp( args[ 2 ], "full" ) == 0 );
|
|
|
|
char const *filename = args[ 1 ];
|
|
if ( !filename || !filename[ 0 ] )
|
|
{
|
|
return;
|
|
}
|
|
|
|
char outfile[ MAX_QPATH ];
|
|
// Strip path and extension from filename
|
|
Q_FileBase( filename, outfile, sizeof( outfile ) );
|
|
Host_WriteConfiguration( va( "%s.cfg", outfile ), bWriteAll );
|
|
if ( !bWriteAll )
|
|
ConMsg( "Wrote partial config file \"%s\" out, to write full file use host_writeconfig \"%s\" full\n", outfile, outfile );
|
|
}
|
|
else
|
|
{
|
|
Host_WriteConfiguration( NULL, true );
|
|
}
|
|
}
|
|
|
|
#endif
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Does a quick parse of the config.cfg to read cvars that
|
|
// need to be read before any games systems are initialized
|
|
// assumes only cvars and filesystem are initialized
|
|
//-----------------------------------------------------------------------------
|
|
void Host_ReadPreStartupConfiguration()
|
|
{
|
|
FileHandle_t f = NULL;
|
|
if ( IsX360() )
|
|
{
|
|
// 360 config is less restrictive and can be anywhere in the game path
|
|
f = g_pFileSystem->Open( "//game/cfg/config.360.cfg", "rt" );
|
|
}
|
|
else
|
|
{
|
|
f = g_pFileSystem->Open( "//mod/cfg/config.cfg", "rt" );
|
|
}
|
|
|
|
if ( !f )
|
|
return;
|
|
|
|
// read file into memory
|
|
int size = g_pFileSystem->Size(f);
|
|
char *configBuffer = new char[ size + 1 ];
|
|
g_pFileSystem->Read( configBuffer, size, f );
|
|
configBuffer[size] = 0;
|
|
g_pFileSystem->Close( f );
|
|
|
|
// parse out file
|
|
static const char *s_PreStartupConfigConVars[] =
|
|
{
|
|
"sv_unlockedchapters", // needed to display the startup graphic while loading
|
|
"snd_legacy_surround", // needed to init the sound system
|
|
"gameui_xbox", // needed to initialize the correct UI
|
|
"save_in_memory" // needed to preread data from the correct location in UI
|
|
};
|
|
|
|
// loop through looking for all the cvars to apply
|
|
for (int i = 0; i < ARRAYSIZE(s_PreStartupConfigConVars); i++)
|
|
{
|
|
const char *search = Q_stristr(configBuffer, s_PreStartupConfigConVars[i]);
|
|
if (search)
|
|
{
|
|
// read over the token
|
|
search = COM_Parse(search);
|
|
|
|
// read the value
|
|
COM_Parse(search);
|
|
|
|
// apply the value
|
|
ConVar *var = (ConVar *)g_pCVar->FindVar( s_PreStartupConfigConVars[i] );
|
|
if ( var )
|
|
{
|
|
var->SetValue( com_token );
|
|
}
|
|
}
|
|
}
|
|
|
|
// free
|
|
delete [] configBuffer;
|
|
}
|
|
|
|
void Host_RecomputeSpeed_f( void )
|
|
{
|
|
ConMsg( "Recomputing clock speed...\n" );
|
|
|
|
CClockSpeedInit::Init();
|
|
ConMsg( "Clock speed: %.0f Mhz\n", CFastTimer::GetClockSpeed() / 1000000.0 );
|
|
}
|
|
|
|
static ConCommand recompute_speed( "recompute_speed", Host_RecomputeSpeed_f, "Recomputes clock speed (for debugging purposes).", FCVAR_CHEAT );
|
|
|
|
void DTI_Flush_f()
|
|
{
|
|
DTI_Flush();
|
|
ServerDTI_Flush();
|
|
}
|
|
|
|
static ConCommand dti_flush( "dti_flush", DTI_Flush_f, "Write out the datatable instrumentation files (you must run with -dti for this to work)." );
|
|
|
|
/*
|
|
==================
|
|
Host_ShutdownServer
|
|
|
|
This only happens at the end of a game, not between levels
|
|
==================
|
|
*/
|
|
void Host_ShutdownServer( void )
|
|
{
|
|
if ( !sv.IsActive() )
|
|
return;
|
|
|
|
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 accumulated_extra_samples, bool bFinalTick )
|
|
{
|
|
VPROF_BUDGET( "_Host_RunFrame_Input", _T("Input") );
|
|
|
|
// Run a test script?
|
|
static bool bFirstFrame = true;
|
|
if ( bFirstFrame )
|
|
{
|
|
bFirstFrame = false;
|
|
const char *pScriptFilename = CommandLine()->ParmValue( "-testscript" );
|
|
if ( pScriptFilename )
|
|
{
|
|
if ( !GetTestScriptMgr()->StartTestScript( pScriptFilename ) )
|
|
{
|
|
Error( "StartTestScript( %s ) failed.", pScriptFilename );
|
|
}
|
|
}
|
|
}
|
|
|
|
g_HostTimes.StartFrameSegment( FRAME_SEGMENT_INPUT );
|
|
|
|
#ifndef SWDS
|
|
// Client can process input
|
|
ClientDLL_ProcessInput( );
|
|
|
|
g_HostTimes.StartFrameSegment( FRAME_SEGMENT_CMD_EXECUTE );
|
|
|
|
// process console commands
|
|
Cbuf_Execute ();
|
|
|
|
g_HostTimes.EndFrameSegment( FRAME_SEGMENT_CMD_EXECUTE );
|
|
|
|
// Send any current movement commands to server and flush reliable buffer even if not moving yet.
|
|
CL_Move( accumulated_extra_samples, bFinalTick );
|
|
|
|
#endif
|
|
|
|
g_HostTimes.EndFrameSegment( FRAME_SEGMENT_INPUT );
|
|
}
|
|
|
|
void _Host_RunFrame_Server( bool finaltick )
|
|
{
|
|
VPROF_BUDGET( "_Host_RunFrame_Server", VPROF_BUDGETGROUP_GAME );
|
|
VPROF_INCREMENT_COUNTER( "ticks", 1 );
|
|
tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s", __FUNCTION__ );
|
|
|
|
// Run the Server frame ( read, run physics, respond )
|
|
g_HostTimes.StartFrameSegment( FRAME_SEGMENT_SERVER );
|
|
SV_Frame ( finaltick );
|
|
g_HostTimes.EndFrameSegment( FRAME_SEGMENT_SERVER );
|
|
|
|
// Look for connectionless rcon packets on dedicated servers
|
|
// SV_CheckRcom(); TODO
|
|
}
|
|
|
|
void _Host_RunFrame_Server_Async( int numticks )
|
|
{
|
|
tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s %d", __FUNCTION__, numticks );
|
|
|
|
for ( int tick = 0; tick < numticks; tick++ )
|
|
{
|
|
g_ServerGlobalVariables.tickcount = sv.m_nTickCount;
|
|
g_ServerGlobalVariables.simTicksThisFrame = numticks - tick;
|
|
bool bFinalTick = ( tick == (numticks - 1) );
|
|
_Host_RunFrame_Server( bFinalTick );
|
|
}
|
|
}
|
|
|
|
|
|
void _Host_RunFrame_Client( bool framefinished )
|
|
{
|
|
#ifndef SWDS
|
|
VPROF( "_Host_RunFrame_Client" );
|
|
tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s %d", __FUNCTION__, framefinished );
|
|
|
|
g_HostTimes.StartFrameSegment( FRAME_SEGMENT_CLIENT );
|
|
|
|
// Get any current state update from server, etc.
|
|
CL_ReadPackets( framefinished );
|
|
|
|
#if defined( VOICE_OVER_IP )
|
|
// Send any enqueued voice data to the server
|
|
CL_ProcessVoiceData();
|
|
#endif // VOICE_OVER_IP
|
|
|
|
cl.CheckUpdatingSteamResources();
|
|
cl.CheckFileCRCsWithServer();
|
|
|
|
// Resend connection request if needed.
|
|
cl.RunFrame();
|
|
|
|
if ( CL_IsHL2Demo() || CL_IsPortalDemo() ) // don't need sv.IsDedicated() because ded servers don't run this
|
|
{
|
|
void CL_DemoCheckGameUIRevealTime();
|
|
CL_DemoCheckGameUIRevealTime();
|
|
}
|
|
|
|
Steam3Client().RunFrame();
|
|
|
|
g_HostTimes.EndFrameSegment( FRAME_SEGMENT_CLIENT );
|
|
|
|
// This takes 1 usec, so it's pretty cheap...
|
|
CL_SetPagedPoolInfo();
|
|
#endif
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Used to set limits on certain convars in multiplayer/sv_cheats mode.
|
|
// Returns true if it was called recursively and it early-outed.
|
|
//-----------------------------------------------------------------------------
|
|
bool CheckVarRange_Generic( ConVar *pVar, int minVal, int maxVal )
|
|
{
|
|
if ( !CanCheat() && !Host_IsSinglePlayerGame() )
|
|
{
|
|
int clampedValue = clamp( pVar->GetInt(), minVal, maxVal );
|
|
if ( clampedValue != pVar->GetInt() )
|
|
{
|
|
Warning( "sv_cheats=0 prevented changing %s outside of the range [%d,%d] (was %d).\n", pVar->GetName(), minVal, maxVal, pVar->GetInt() );
|
|
pVar->SetValue( clampedValue );
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
void CheckSpecialCheatVars()
|
|
{
|
|
static ConVar *mat_picmip = NULL;
|
|
if ( !mat_picmip )
|
|
mat_picmip = g_pCVar->FindVar( "mat_picmip" );
|
|
|
|
CheckVarRange_r_rootlod();
|
|
CheckVarRange_r_lod();
|
|
HandleServerAllowColorCorrection();
|
|
}
|
|
|
|
|
|
void _Host_RunFrame_Render()
|
|
{
|
|
#ifndef SWDS
|
|
VPROF( "_Host_RunFrame_Render" );
|
|
tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "_Host_RunFrame_Render" );
|
|
|
|
CheckSpecialCheatVars();
|
|
|
|
int nOrgNoRendering = mat_norendering.GetInt();
|
|
|
|
if ( cl_takesnapshot )
|
|
{
|
|
// turn off no-rendering mode, if taking screenshot
|
|
mat_norendering.SetValue( 0 );
|
|
}
|
|
|
|
// update video if not running in background
|
|
g_HostTimes.StartFrameSegment( FRAME_SEGMENT_RENDER );
|
|
|
|
CL_LatchInterpolationAmount();
|
|
|
|
{
|
|
VPROF( "_Host_RunFrame_Render - UpdateScreen" );
|
|
tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "_Host_RunFrame_Render - UpdateScreen" );
|
|
Host_UpdateScreen();
|
|
}
|
|
{
|
|
VPROF( "_Host_RunFrame_Render - CL_DecayLights" );
|
|
tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "_Host_RunFrame_Render - CL_DecayLights" );
|
|
CL_DecayLights ();
|
|
}
|
|
|
|
g_HostTimes.EndFrameSegment( FRAME_SEGMENT_RENDER );
|
|
|
|
saverestore->OnFrameRendered();
|
|
|
|
#ifdef USE_SDL
|
|
if ( g_pLauncherMgr )
|
|
{
|
|
g_pLauncherMgr->OnFrameRendered();
|
|
}
|
|
#endif
|
|
|
|
mat_norendering.SetValue( nOrgNoRendering );
|
|
#endif
|
|
}
|
|
|
|
void CL_FindInterpolatedAddAngle( float t, float& frac, AddAngle **prev, AddAngle **next )
|
|
{
|
|
int c = cl.addangle.Count();
|
|
|
|
*prev = NULL;
|
|
*next = NULL;
|
|
|
|
AddAngle *pentry = NULL;
|
|
for ( int i = 0; i < c; i++ )
|
|
{
|
|
AddAngle *entry = &cl.addangle[ i ];
|
|
|
|
*next = entry;
|
|
|
|
// Time is earlier
|
|
if ( t < entry->starttime )
|
|
{
|
|
if ( i == 0 )
|
|
{
|
|
*prev = *next;
|
|
frac = 0.0f;
|
|
return;
|
|
}
|
|
|
|
// Avoid div by zero
|
|
if ( entry->starttime == pentry->starttime )
|
|
{
|
|
frac = 0.0f;
|
|
return;
|
|
}
|
|
|
|
// Time spans the two entries
|
|
frac = ( t - pentry->starttime ) / ( entry->starttime - pentry->starttime );
|
|
frac = clamp( frac, 0.0f, 1.0f );
|
|
return;
|
|
}
|
|
|
|
*prev = *next;
|
|
pentry = entry;
|
|
}
|
|
}
|
|
|
|
void CL_DiscardOldAddAngleEntries( float t )
|
|
{
|
|
float killtime = t - host_state.interval_per_tick - 0.1f;
|
|
|
|
for ( int i = 0; i < cl.addangle.Count(); i++ )
|
|
{
|
|
AddAngle *p = &cl.addangle[ i ];
|
|
if ( p->starttime <= killtime )
|
|
{
|
|
cl.addangle.Remove( i );
|
|
--i;
|
|
}
|
|
}
|
|
|
|
// It's safe to reset the master counter once all the entries decay
|
|
if ( cl.addangle.Count() == 0 )
|
|
{
|
|
cl.prevaddangletotal = cl.addangletotal = 0.0f;
|
|
}
|
|
}
|
|
|
|
#ifndef SWDS
|
|
void CL_ApplyAddAngle()
|
|
{
|
|
tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s", __FUNCTION__ );
|
|
|
|
float curtime = cl.GetTime() - host_state.interval_per_tick;
|
|
|
|
AddAngle *prev = NULL, *next = NULL;
|
|
float frac = 0.0f;
|
|
|
|
float addangletotal = 0.0f;
|
|
|
|
CL_FindInterpolatedAddAngle( curtime, frac, &prev, &next );
|
|
|
|
if ( prev && next )
|
|
{
|
|
addangletotal = prev->total + frac * ( next->total - prev->total );
|
|
}
|
|
else
|
|
{
|
|
addangletotal = cl.prevaddangletotal;
|
|
}
|
|
|
|
float amove = addangletotal - cl.prevaddangletotal;
|
|
|
|
// Update view angles
|
|
cl.viewangles[ 1 ] += amove;
|
|
// Update client .dll view of angles
|
|
g_pClientSidePrediction->SetLocalViewAngles( cl.viewangles );
|
|
|
|
// Remember last total
|
|
cl.prevaddangletotal = addangletotal;
|
|
|
|
CL_DiscardOldAddAngleEntries( curtime );
|
|
}
|
|
#endif
|
|
|
|
void _Host_RunFrame_Sound()
|
|
{
|
|
#ifndef SWDS
|
|
|
|
VPROF_BUDGET( "_Host_RunFrame_Sound", VPROF_BUDGETGROUP_OTHER_SOUND );
|
|
|
|
g_HostTimes.StartFrameSegment( FRAME_SEGMENT_SOUND );
|
|
|
|
Host_UpdateSounds();
|
|
|
|
g_HostTimes.EndFrameSegment( FRAME_SEGMENT_SOUND );
|
|
#endif
|
|
}
|
|
|
|
float Host_GetSoundDuration( const char *pSample )
|
|
{
|
|
#ifndef SWDS
|
|
if (!sv.IsDedicated())
|
|
{
|
|
extern float SV_GetSoundDuration(const char *pSample);
|
|
extern float AudioSource_GetSoundDuration(CSfxTable *pSfx);
|
|
int index = cl.LookupSoundIndex(pSample);
|
|
if (index >= 0)
|
|
return AudioSource_GetSoundDuration(cl.GetSound(index));
|
|
return SV_GetSoundDuration(pSample);
|
|
}
|
|
#endif
|
|
return 0.0f;
|
|
}
|
|
|
|
CON_COMMAND( host_runofftime, "Run off some time without rendering/updating sounds\n" )
|
|
{
|
|
if ( args.ArgC() != 2 )
|
|
{
|
|
ConMsg( "Usage: host_runofftime <seconds>\n" );
|
|
return;
|
|
}
|
|
|
|
if ( !sv.IsActive() )
|
|
{
|
|
ConMsg( "host_ruofftime: must be running a server\n" );
|
|
return;
|
|
}
|
|
|
|
if ( sv.IsMultiplayer() )
|
|
{
|
|
ConMsg( "host_ruofftime: only valid in single player\n" );
|
|
return;
|
|
}
|
|
|
|
float advanceTime = atof( args[1] );
|
|
if ( advanceTime <= 0.0f )
|
|
return;
|
|
|
|
// 15 minutes is a _long_ time!!!
|
|
if ( advanceTime > 15.0f * 60.0f )
|
|
{
|
|
ConMsg( "host_runofftime would run off %.2f minutes!!! ignoring\n",
|
|
advanceTime / 60.0f );
|
|
return;
|
|
}
|
|
|
|
ConMsg( "Skipping ahead for %f seconds\n", advanceTime );
|
|
|
|
SCR_UpdateScreen();
|
|
SCR_UpdateScreen ();
|
|
}
|
|
|
|
#if !defined( _X360 )
|
|
S_API int SteamGameServer_GetIPCCallCount();
|
|
#else
|
|
S_API int SteamGameServer_GetIPCCallCount() { return 0; }
|
|
#endif
|
|
void Host_ShowIPCCallCount()
|
|
{
|
|
// If set to 0 then get out.
|
|
if ( host_ShowIPCCallCount.GetInt() == 0 )
|
|
return;
|
|
|
|
static float s_flLastTime = 0;
|
|
static int s_nLastTick = host_tickcount;
|
|
static int s_nLastFrame = host_framecount;
|
|
|
|
// Figure out how often they want to update.
|
|
double flInterval = 0;
|
|
if ( host_ShowIPCCallCount.GetFloat() > 0 )
|
|
{
|
|
flInterval = 1.0f / host_ShowIPCCallCount.GetFloat();
|
|
}
|
|
|
|
// This is called every frame so increment the frame counter.
|
|
double flCurTime = Plat_FloatTime();
|
|
if ( flCurTime - s_flLastTime >= flInterval )
|
|
{
|
|
uint32 callCount;
|
|
ISteamClient *pSteamClient = SteamClient();
|
|
if ( pSteamClient )
|
|
{
|
|
callCount = pSteamClient->GetIPCCallCount();
|
|
}
|
|
else
|
|
{
|
|
// Ok, we're a dedicated server and we need to use this to get it.
|
|
callCount = (uint32)SteamGameServer_GetIPCCallCount();
|
|
}
|
|
|
|
// Avoid a divide by zero.
|
|
int frameCount = host_framecount - s_nLastFrame;
|
|
int tickCount = host_tickcount - s_nLastTick;
|
|
if ( frameCount == 0 || tickCount == 0 )
|
|
return;
|
|
|
|
Msg( "host_ShowIPCCallCount: %d IPC calls in the past [%d frames, %d ticks] Avg: [%.2f/frame, %.2f/tick]\n",
|
|
callCount, frameCount, tickCount, (float)callCount / frameCount, (float)callCount / tickCount );
|
|
|
|
s_flLastTime = flCurTime;
|
|
s_nLastTick = host_tickcount;
|
|
s_nLastFrame = host_framecount;
|
|
}
|
|
}
|
|
|
|
void Host_SetClientInSimulation( bool bInSimulation )
|
|
{
|
|
#ifndef SWDS
|
|
// Tracker 77931: If the game is paused, then lock the client clock at the previous tick boundary
|
|
// (otherwise we'll keep interpolating through the "remainder" time causing the paused characters
|
|
// to twitch like they have the shakes)
|
|
// TODO: Since this rounds down on the frame we paused, we could see a slight backsliding. We could remember the last "remainder" before pause and re-use it and
|
|
// set insimulation == false to be mroe exact. We'd still have to deal with the timing difference between
|
|
// when pause/unpause happens on the server versus the client
|
|
cl.insimulation = bInSimulation || cl.IsPaused();
|
|
|
|
// Compute absolute/render time stamp
|
|
g_ClientGlobalVariables.curtime = cl.GetTime();
|
|
g_ClientGlobalVariables.frametime = cl.GetFrameTime();
|
|
#endif
|
|
}
|
|
|
|
static ConVar host_Sleep( "host_sleep", "0", FCVAR_CHEAT, "Force the host to sleep a certain number of milliseconds each frame." );
|
|
extern ConVar sv_alternateticks;
|
|
#define LOG_FRAME_OUTPUT 0
|
|
|
|
void _Host_RunFrame (float time)
|
|
{
|
|
MDLCACHE_COARSE_LOCK_(g_pMDLCache);
|
|
static double host_remainder = 0.0f;
|
|
double prevremainder;
|
|
bool shouldrender;
|
|
|
|
#if defined( RAD_TELEMETRY_ENABLED )
|
|
if( g_Telemetry.DemoTickEnd == ( uint32 )-1 )
|
|
{
|
|
Cbuf_AddText( "quit\n" );
|
|
}
|
|
#endif
|
|
int numticks;
|
|
{
|
|
// Profile scope specific to the top of this function, protect from setjmp() problems
|
|
VPROF( "_Host_RunFrame_Upto_MarkFrame" );
|
|
|
|
if ( host_checkheap )
|
|
{
|
|
#if defined(_WIN32)
|
|
if ( _heapchk() != _HEAPOK )
|
|
{
|
|
Sys_Error( "_Host_RunFrame (top): _heapchk() != _HEAPOK\n" );
|
|
}
|
|
#endif
|
|
}
|
|
|
|
// When playing back a VCR file, don't do host_sleep. That way, if it was recorded with
|
|
// host_sleep on, it'll play back way Faster.
|
|
if ( host_Sleep.GetInt() && VCRGetMode() != VCR_Playback )
|
|
{
|
|
Sys_Sleep( host_Sleep.GetInt() );
|
|
}
|
|
|
|
// Slow down the playback?
|
|
if ( g_iVCRPlaybackSleepInterval )
|
|
{
|
|
Sys_Sleep( g_iVCRPlaybackSleepInterval );
|
|
}
|
|
|
|
MapReslistGenerator().RunFrame();
|
|
|
|
static int lastrunoffsecond = -1;
|
|
|
|
if ( setjmp ( host_enddemo ) )
|
|
return; // demo finished.
|
|
|
|
// decide the simulation time
|
|
Host_AccumulateTime ( time );
|
|
_Host_SetGlobalTime();
|
|
|
|
shouldrender = !sv.IsDedicated();
|
|
|
|
// FIXME: Could track remainder as fractional ticks instead of msec
|
|
prevremainder = host_remainder;
|
|
if ( prevremainder < 0 )
|
|
prevremainder = 0;
|
|
|
|
#if !defined(SWDS)
|
|
if ( !demoplayer->IsPlaybackPaused() )
|
|
#endif
|
|
{
|
|
host_remainder += host_frametime;
|
|
}
|
|
|
|
numticks = 0; // how many ticks we will simulate this frame
|
|
if ( host_remainder >= host_state.interval_per_tick )
|
|
{
|
|
numticks = (int)( floor( host_remainder / host_state.interval_per_tick ) );
|
|
|
|
// round to nearest even ending tick in alternate ticks mode so the last
|
|
// tick is always simulated prior to updating the network data
|
|
// NOTE: alternate ticks only applies in SP!!!
|
|
if ( Host_IsSinglePlayerGame() &&
|
|
sv_alternateticks.GetBool() )
|
|
{
|
|
int startTick = g_ServerGlobalVariables.tickcount;
|
|
int endTick = startTick + numticks;
|
|
endTick = AlignValue( endTick, 2 );
|
|
numticks = endTick - startTick;
|
|
}
|
|
|
|
host_remainder -= numticks * host_state.interval_per_tick;
|
|
}
|
|
|
|
host_nexttick = host_state.interval_per_tick - host_remainder;
|
|
|
|
g_pMDLCache->MarkFrame();
|
|
}
|
|
|
|
{
|
|
// Profile scope, protect from setjmp() problems
|
|
VPROF( "_Host_RunFrame" );
|
|
tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "_Host_RunFrame" );
|
|
|
|
g_HostTimes.StartFrameSegment( FRAME_SEGMENT_CMD_EXECUTE );
|
|
|
|
// process console commands
|
|
Cbuf_Execute ();
|
|
|
|
// initialize networking for dedicated server after commandline & autoexec.cfg have been parsed
|
|
if ( NET_IsDedicated() && !NET_IsMultiplayer() )
|
|
NET_SetMutiplayer( true );
|
|
|
|
g_HostTimes.EndFrameSegment( FRAME_SEGMENT_CMD_EXECUTE );
|
|
|
|
// Msg( "Running %i ticks (%f remainder) for frametime %f total %f tick %f delta %f\n", numticks, remainder, host_frametime, host_time );
|
|
g_ServerGlobalVariables.interpolation_amount = 0.0f;
|
|
#ifndef SWDS
|
|
g_ClientGlobalVariables.interpolation_amount = 0.0f;
|
|
|
|
cl.insimulation = true;
|
|
#endif
|
|
|
|
host_frameticks = numticks;
|
|
host_currentframetick = 0;
|
|
|
|
#if !defined( SWDS )
|
|
// This is to make the tool do both sim + rendering on the initial frame
|
|
// cl.IsActive changes in the loop below, as does scr_nextdrawtick
|
|
// We're just caching off the state here so that we have a consistent return value
|
|
// for enginetool->IsInGame the entire frame
|
|
g_pEngineToolInternal->SetIsInGame( cl.IsActive() && ( scr_nextdrawtick == 0 ) );
|
|
#endif
|
|
CJob *pGameJob = NULL;
|
|
|
|
// threaded path only supported in listen server
|
|
#ifndef SWDS
|
|
if ( !IsEngineThreaded() )
|
|
#endif
|
|
{
|
|
#ifndef SWDS
|
|
if ( g_ClientDLL )
|
|
{
|
|
g_ClientDLL->IN_SetSampleTime(host_frametime);
|
|
}
|
|
g_ClientGlobalVariables.simTicksThisFrame = 1;
|
|
#endif
|
|
cl.m_tickRemainder = host_remainder;
|
|
g_ServerGlobalVariables.simTicksThisFrame = 1;
|
|
cl.SetFrameTime( host_frametime );
|
|
for ( int tick = 0; tick < numticks; tick++ )
|
|
{
|
|
// Emit an ETW event every simulation frame.
|
|
ETWSimFrameMark( sv.IsDedicated() );
|
|
|
|
double now = Plat_FloatTime();
|
|
float jitter = now - host_idealtime;
|
|
|
|
// Track jitter (delta between ideal time and actual tick execution time)
|
|
host_jitterhistory[ host_jitterhistorypos ] = jitter;
|
|
host_jitterhistorypos = ( host_jitterhistorypos + 1 ) % ARRAYSIZE(host_jitterhistory);
|
|
|
|
// Very slowly decay "ideal" towards current wall clock unless delta is large
|
|
if ( fabs( jitter ) > 1.0f )
|
|
{
|
|
host_idealtime = now;
|
|
}
|
|
else
|
|
{
|
|
host_idealtime = 0.99 * host_idealtime + 0.01 * now;
|
|
}
|
|
|
|
// process any asynchronous network traffic (TCP), set net_time
|
|
NET_RunFrame( now );
|
|
|
|
// Only send updates on final tick so we don't re-encode network data multiple times per frame unnecessarily
|
|
bool bFinalTick = ( tick == (numticks - 1) );
|
|
|
|
// initialize networking for dedicated server after commandline & autoexec.cfg have been parsed
|
|
if ( NET_IsDedicated() && !NET_IsMultiplayer() )
|
|
NET_SetMutiplayer( true );
|
|
|
|
g_ServerGlobalVariables.tickcount = sv.m_nTickCount;
|
|
// NOTE: Do we want do this at start or end of this loop?
|
|
++host_tickcount;
|
|
++host_currentframetick;
|
|
#ifndef SWDS
|
|
g_ClientGlobalVariables.tickcount = cl.GetClientTickCount();
|
|
|
|
// Make sure state is correct
|
|
CL_CheckClientState();
|
|
#endif
|
|
//---------------------------------------------------------
|
|
// Run prediction, useful when fps is lower than tickrate.
|
|
//---------------------------------------------------------
|
|
// CL_RunPrediction( PREDICTION_NORMAL );
|
|
|
|
_Host_RunFrame_Input( prevremainder, bFinalTick );
|
|
prevremainder = 0;
|
|
|
|
//-------------------
|
|
//
|
|
// server operations
|
|
//
|
|
//-------------------
|
|
|
|
_Host_RunFrame_Server( bFinalTick );
|
|
|
|
// Additional networking ops for SPLITPACKET stuff (99.9% of the time this will be an empty list of work)
|
|
NET_SendQueuedPackets();
|
|
|
|
//-------------------
|
|
//
|
|
// client operations
|
|
//
|
|
//-------------------
|
|
#ifndef SWDS
|
|
if ( !sv.IsDedicated() )
|
|
{
|
|
_Host_RunFrame_Client( bFinalTick );
|
|
}
|
|
|
|
toolframework->Think( bFinalTick );
|
|
#endif
|
|
|
|
host_idealtime += host_state.interval_per_tick;
|
|
}
|
|
|
|
// run HLTV if active
|
|
if ( hltv )
|
|
{
|
|
tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "hltv->RunFrame()" );
|
|
hltv->RunFrame();
|
|
}
|
|
|
|
if ( hltvtest )
|
|
{
|
|
tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "hltvtest->RunFrame()" );
|
|
hltvtest->RunFrame();
|
|
}
|
|
|
|
#if defined( REPLAY_ENABLED )
|
|
// run replay if active
|
|
if ( replay )
|
|
{
|
|
replay->RunFrame();
|
|
}
|
|
|
|
// Update server-side replay history manager
|
|
if ( sv.IsDedicated() && g_pServerReplayContext && g_pServerReplayContext->IsInitialized() )
|
|
{
|
|
g_pServerReplayContext->Think();
|
|
}
|
|
#endif
|
|
|
|
#ifndef SWDS
|
|
// This is a hack to let timedemo pull messages from the queue faster than every 15 msec
|
|
// Also when demoplayer is skipping packets to a certain tick we should process the queue
|
|
// as quickly as we can.
|
|
if ( numticks == 0 && ( demoplayer->IsPlayingTimeDemo() || demoplayer->IsSkipping() ) )
|
|
{
|
|
_Host_RunFrame_Client( true );
|
|
}
|
|
|
|
if ( !sv.IsDedicated() )
|
|
{
|
|
tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "Host_SetClientInSimulation" );
|
|
|
|
// This causes cl.gettime() to return the true clock being used for rendering (tickcount * rate + remainder)
|
|
Host_SetClientInSimulation(false);
|
|
|
|
// TODO_ENHANCED:
|
|
// Notice_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.
|
|
//
|
|
// It might look a bit more unsmooth with lower fps
|
|
// but with high enough fps, the issue goes away anyway.
|
|
// It's not very noticeable which is very nice for us.
|
|
// No need to lag compensate the local player anymore !
|
|
if (numticks == 0)
|
|
{
|
|
g_ClientGlobalVariables.interpolation_amount = (cl.m_tickRemainder
|
|
/ host_state
|
|
.interval_per_tick);
|
|
}
|
|
else
|
|
{
|
|
g_ClientGlobalVariables.interpolation_amount = 0.0f;
|
|
#ifdef _DEBUG
|
|
printf("interpolation amount was %f, corrected to "
|
|
"fix interpolation issues.\n",
|
|
cl.m_tickRemainder
|
|
/ host_state.interval_per_tick);
|
|
#endif
|
|
}
|
|
|
|
#if defined(REPLAY_ENABLED)
|
|
// Update client-side replay history manager - called here
|
|
// since interpolation_amount is set
|
|
if ( g_pClientReplayContext && g_pClientReplayContext->IsInitialized() )
|
|
{
|
|
g_pClientReplayContext->Think();
|
|
}
|
|
#endif
|
|
|
|
//-------------------
|
|
// Run prediction if it hasn't been run yet
|
|
//-------------------
|
|
// If we haven't predicted/simulated the player
|
|
// (multiplayer with prediction enabled and
|
|
// not a listen server with zero frame lag, then go ahead
|
|
// and predict now
|
|
CL_RunPrediction( PREDICTION_NORMAL );
|
|
|
|
CL_ApplyAddAngle();
|
|
|
|
// The mouse is always simulated for the current frame's time
|
|
// This makes updates smooth in every case
|
|
// continuous controllers affecting the view are also simulated this way
|
|
// but they have a cap applied by IN_SetSampleTime() so they are not also
|
|
// simulated during input gathering
|
|
CL_ExtraMouseUpdate( g_ClientGlobalVariables.frametime );
|
|
}
|
|
#endif
|
|
#if defined( REPLAY_ENABLED )
|
|
// Let the replay system think
|
|
if ( g_pReplay )
|
|
{
|
|
g_pReplay->Think();
|
|
}
|
|
#endif
|
|
#if LOG_FRAME_OUTPUT
|
|
if ( !cl.IsPaused() || !sv.IsPaused() )
|
|
{
|
|
Msg("=============SIM: CLIENT %5d + %d, SERVER %5d + %d\t REM: %.2f\n", cl.GetClientTickCount(), numticks, sv.m_nTickCount, numticks, host_remainder*1000.0f );
|
|
}
|
|
#endif
|
|
}
|
|
#ifndef SWDS
|
|
else
|
|
{
|
|
static int numticks_last_frame = 0;
|
|
static float host_remainder_last_frame = 0, prev_remainder_last_frame = 0, last_frame_time = 0;
|
|
|
|
int clientticks;
|
|
int serverticks;
|
|
|
|
clientticks = numticks_last_frame;
|
|
cl.m_tickRemainder = host_remainder_last_frame;
|
|
cl.SetFrameTime( last_frame_time );
|
|
if ( g_ClientDLL )
|
|
{
|
|
g_ClientDLL->IN_SetSampleTime(last_frame_time);
|
|
}
|
|
|
|
last_frame_time = host_frametime;
|
|
|
|
serverticks = numticks;
|
|
g_ClientGlobalVariables.simTicksThisFrame = clientticks;
|
|
g_ServerGlobalVariables.simTicksThisFrame = serverticks;
|
|
g_ServerGlobalVariables.tickcount = sv.m_nTickCount;
|
|
|
|
// THREADED: Run Client
|
|
// -------------------
|
|
for ( int tick = 0; tick < clientticks; tick++ )
|
|
{
|
|
// process any asynchronous network traffic (TCP), set net_time
|
|
NET_RunFrame( Plat_FloatTime() );
|
|
|
|
// Only send updates on final tick so we don't re-encode network data multiple times per frame unnecessarily
|
|
bool bFinalTick = ( tick == (clientticks - 1) );
|
|
|
|
// initialize networking for dedicated server after commandline & autoexec.cfg have been parsed
|
|
if ( NET_IsDedicated() && !NET_IsMultiplayer() )
|
|
NET_SetMutiplayer( true );
|
|
g_ClientGlobalVariables.tickcount = cl.GetClientTickCount();
|
|
|
|
// Make sure state is correct
|
|
CL_CheckClientState();
|
|
// Additional networking ops for SPLITPACKET stuff (99.9% of the time this will be an empty list of work)
|
|
NET_SendQueuedPackets();
|
|
//-------------------
|
|
//
|
|
// client operations
|
|
//
|
|
//-------------------
|
|
if ( !sv.IsDedicated() )
|
|
{
|
|
_Host_RunFrame_Client( bFinalTick );
|
|
}
|
|
toolframework->Think( bFinalTick );
|
|
}
|
|
// This is a hack to let timedemo pull messages from the queue faster than every 15 msec
|
|
// Also when demoplayer is skipping packets to a certain tick we should process the queue
|
|
// as quickly as we can.
|
|
if ( clientticks == 0 && ( demoplayer->IsPlayingTimeDemo() || demoplayer->IsSkipping() ) )
|
|
{
|
|
_Host_RunFrame_Client( true );
|
|
}
|
|
|
|
// This causes cl.gettime() to return the true clock being used for rendering (tickcount * rate + remainder)
|
|
Host_SetClientInSimulation( false );
|
|
|
|
// Please check Notice_Enhanced.
|
|
if (numticks == 0)
|
|
{
|
|
g_ClientGlobalVariables.interpolation_amount = (cl.m_tickRemainder
|
|
/ host_state
|
|
.interval_per_tick);
|
|
}
|
|
else
|
|
{
|
|
g_ClientGlobalVariables.interpolation_amount = 0.0f;
|
|
#ifdef _DEBUG
|
|
printf("interpolation amount was %f, corrected to "
|
|
"fix interpolation issues.\n",
|
|
cl.m_tickRemainder
|
|
/ host_state.interval_per_tick);
|
|
#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();
|
|
|
|
Host_SetClientInSimulation( true );
|
|
|
|
// THREADED: Run Input
|
|
// -------------------
|
|
int saveTick = g_ClientGlobalVariables.tickcount;
|
|
|
|
for ( int tick = 0; tick < serverticks; tick++ )
|
|
{
|
|
// NOTE: Do we want do this at start or end of this loop?
|
|
++host_tickcount;
|
|
++host_currentframetick;
|
|
g_ClientGlobalVariables.tickcount = host_tickcount;
|
|
bool bFinalTick = tick==(serverticks-1) ? true : false;
|
|
// Run prediction before inputs if fps is lower than tickrate
|
|
// CL_RunPrediction( PREDICTION_NORMAL );
|
|
_Host_RunFrame_Input( prevremainder, bFinalTick );
|
|
prevremainder = 0;
|
|
// process any asynchronous network traffic (TCP), set net_time
|
|
NET_RunFrame( Plat_FloatTime() );
|
|
}
|
|
|
|
Host_SetClientInSimulation( false );
|
|
|
|
// The mouse is always simulated for the current frame's time
|
|
// This makes updates smooth in every case
|
|
// continuous controllers affecting the view are also simulated this way
|
|
// but they have a cap applied by IN_SetSampleTime() so they are not also
|
|
// simulated during input gathering
|
|
CL_ExtraMouseUpdate( g_ClientGlobalVariables.frametime );
|
|
|
|
g_ClientGlobalVariables.tickcount = saveTick;
|
|
numticks_last_frame = numticks;
|
|
host_remainder_last_frame = host_remainder;
|
|
|
|
// THREADED: Run Server
|
|
// -------------------
|
|
// set net_time once before running the server
|
|
NET_SetTime( Plat_FloatTime() );
|
|
pGameJob = new CFunctorJob( CreateFunctor( _Host_RunFrame_Server_Async, serverticks ) );
|
|
if ( IsX360() )
|
|
{
|
|
pGameJob->SetServiceThread( g_nServerThread );
|
|
}
|
|
g_pThreadPool->AddJob( pGameJob );
|
|
#if LOG_FRAME_OUTPUT
|
|
if ( !cl.IsPaused() || !sv.IsPaused() )
|
|
{
|
|
Msg("=============SIM: CLIENT %5d + %d, SERVER %5d + %d\t REM: %.2f\n", cl.GetClientTickCount(), clientticks, sv.m_nTickCount, serverticks, host_remainder*1000.0f );
|
|
}
|
|
#endif
|
|
}
|
|
#endif // SWDS
|
|
|
|
g_Log.RunFrame();
|
|
|
|
if ( shouldrender )
|
|
{
|
|
#if LOG_FRAME_OUTPUT
|
|
if ( !cl.IsPaused() || !sv.IsPaused() )
|
|
{
|
|
static float lastFrameTime = 0;
|
|
float frametime = g_ClientGlobalVariables.curtime - lastFrameTime;
|
|
Msg("RENDER AT: %6.4f: %.2fms [%.2fms implicit] frametime\n",
|
|
g_ClientGlobalVariables.curtime, g_ClientGlobalVariables.frametime*1000.0f, frametime * 1000.0f);
|
|
lastFrameTime = g_ClientGlobalVariables.curtime;
|
|
}
|
|
#endif
|
|
//-------------------
|
|
// rendering
|
|
//-------------------
|
|
_Host_RunFrame_Render();
|
|
|
|
//-------------------
|
|
// sound
|
|
//-------------------
|
|
_Host_RunFrame_Sound();
|
|
|
|
#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;
|
|
}
|