You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
5028 lines
130 KiB
5028 lines
130 KiB
//========= Copyright Valve Corporation, All rights reserved. ============// |
|
// |
|
// Purpose: |
|
// |
|
//===========================================================================// |
|
|
|
#include "tier0/fasttimer.h" |
|
|
|
#ifdef _WIN32 |
|
#include "tier0/memdbgon.h" // needed because in release builds crtdbg.h is handled specially if USE_MEM_DEBUG is defined |
|
#include "tier0/memdbgoff.h" |
|
#include <crtdbg.h> // For getting at current heap size |
|
#endif |
|
|
|
#include "tier1/fmtstr.h" |
|
#include "vstdlib/jobthread.h" |
|
|
|
#ifdef USE_SDL |
|
#include "appframework/ilaunchermgr.h" |
|
extern ILauncherMgr *g_pLauncherMgr; |
|
#endif |
|
|
|
#include "server.h" |
|
#include "host_jmp.h" |
|
#include "screen.h" |
|
#include "keys.h" |
|
#include "cdll_int.h" |
|
#include "eiface.h" |
|
#include "sv_main.h" |
|
#include "sv_log.h" |
|
#include "shadowmgr.h" |
|
#include "zone.h" |
|
#include "gl_cvars.h" |
|
#include "sv_filter.h" |
|
#include "ivideomode.h" |
|
#include "vprof_engine.h" |
|
#include "iengine.h" |
|
#include "tier2/tier2.h" |
|
#include "enginethreads.h" |
|
#include "steam/steam_api.h" |
|
#include "LoadScreenUpdate.h" |
|
#include "datacache/idatacache.h" |
|
|
|
#if !defined SWDS |
|
#include "voice.h" |
|
#include "sound.h" |
|
#endif |
|
|
|
#include "icvar.h" |
|
|
|
#include "sys.h" |
|
#include "client.h" |
|
#include "cl_pred.h" |
|
#include "console.h" |
|
#include "view.h" |
|
#include "host.h" |
|
#include "decal.h" |
|
#include "gl_matsysiface.h" |
|
#include "gl_shader.h" |
|
#include "sys_dll.h" |
|
#include "cmodel_engine.h" |
|
#ifndef SWDS |
|
#include "con_nprint.h" |
|
#endif |
|
#include "filesystem.h" |
|
#include "filesystem_engine.h" |
|
#include "tier0/etwprof.h" |
|
#include "tier0/vcrmode.h" |
|
#include "traceinit.h" |
|
#include "host_saverestore.h" |
|
#include "l_studio.h" |
|
#include "cl_demo.h" |
|
#include "cdll_engine_int.h" |
|
#include "host_cmd.h" |
|
#include "host_state.h" |
|
#include "dt_instrumentation.h" |
|
#include "dt_instrumentation_server.h" |
|
#include "const.h" |
|
#include "bitbuf_errorhandler.h" |
|
#include "soundflags.h" |
|
#include "enginestats.h" |
|
#include "tier1/strtools.h" |
|
#include "testscriptmgr.h" |
|
#include "tmessage.h" |
|
#include "tier0/vprof.h" |
|
#include "tier0/icommandline.h" |
|
#include "materialsystem/imaterialsystemhardwareconfig.h" |
|
#include "MapReslistGenerator.h" |
|
#include "DownloadListGenerator.h" |
|
#include "download.h" |
|
#include "staticpropmgr.h" |
|
#include "GameEventManager.h" |
|
#include "iprediction.h" |
|
#include "netmessages.h" |
|
#include "cl_main.h" |
|
#include "hltvserver.h" |
|
#include "hltvtest.h" |
|
#if defined( REPLAY_ENABLED ) |
|
#include "replayserver.h" |
|
#include "replay_internal.h" |
|
#endif |
|
#include "sys_mainwind.h" |
|
#include "host_phonehome.h" |
|
#ifndef SWDS |
|
#include "vgui_baseui_interface.h" |
|
#include "cl_steamauth.h" |
|
#endif |
|
#include "sv_remoteaccess.h" // NotifyDedicatedServerUI() |
|
#include "snd_audio_source.h" |
|
#include "sv_steamauth.h" |
|
#include "MapReslistGenerator.h" |
|
#include "DevShotGenerator.h" |
|
#include "sv_plugin.h" |
|
#include "toolframework/itoolframework.h" |
|
#include "ienginetoolinternal.h" |
|
#include "inputsystem/iinputsystem.h" |
|
#include "vgui_askconnectpanel.h" |
|
#include "cvar.h" |
|
#include "saverestoretypes.h" |
|
#include "filesystem/IQueuedLoader.h" |
|
#include "soundservice.h" |
|
#include "profile.h" |
|
#include "steam/isteamremotestorage.h" |
|
#if defined( _X360 ) |
|
#include "xbox/xbox_win32stubs.h" |
|
#include "audio_pch.h" |
|
#endif |
|
#if defined( LINUX ) |
|
#include <locale.h> |
|
|
|
#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; |
|
|
|
Host_AllowQueuedMaterialSystem( false ); |
|
// clear structures |
|
#if !defined( SWDS ) |
|
g_pShadowMgr->LevelShutdown(); |
|
#endif |
|
StaticPropMgr()->LevelShutdown(); |
|
|
|
Host_FreeStateAndWorld( true ); |
|
sv.Shutdown();// sv.Shutdown() references some heap memory, so run it before Host_FreeToLowMark() |
|
Host_FreeToLowMark( true ); |
|
|
|
IGameEvent *event = g_GameEventManager.CreateEvent( "server_shutdown" ); |
|
|
|
if ( event ) |
|
{ |
|
event->SetString( "reason", "restart" ); |
|
g_GameEventManager.FireEvent( event ); |
|
} |
|
|
|
g_Log.Close(); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Input : time - |
|
// Output : bool |
|
//----------------------------------------------------------------------------- |
|
void Host_AccumulateTime( float dt ) |
|
{ |
|
// Accumulate some time |
|
realtime += dt; |
|
|
|
bool bUseNormalTickTime = true; |
|
#if !defined(SWDS) |
|
if ( demoplayer->IsPlayingTimeDemo() ) |
|
bUseNormalTickTime = false; |
|
#endif |
|
if ( g_bDedicatedServerBenchmarkMode ) |
|
bUseNormalTickTime = false; |
|
|
|
if ( bUseNormalTickTime ) |
|
{ |
|
host_frametime = dt; |
|
} |
|
else |
|
{ |
|
// Used to help increase reproducibility of timedemos |
|
host_frametime = host_state.interval_per_tick; |
|
} |
|
|
|
#if 1 |
|
if ( host_framerate.GetFloat() > 0 |
|
#if !defined(SWDS) |
|
&& ( CanCheat() || demoplayer->IsPlayingBack() ) |
|
#endif |
|
) |
|
{ |
|
float fps = host_framerate.GetFloat(); |
|
if ( fps > 1 ) |
|
{ |
|
fps = 1.0f/fps; |
|
} |
|
host_frametime = fps; |
|
|
|
#if !defined(SWDS) && defined( REPLAY_ENABLED ) |
|
extern IDemoPlayer *g_pReplayDemoPlayer; |
|
if ( demoplayer->IsPlayingBack() && demoplayer == g_pReplayDemoPlayer ) |
|
{ |
|
// adjust time scale if playing back demo |
|
host_frametime *= demoplayer->GetPlaybackTimeScale(); |
|
} |
|
#endif |
|
|
|
host_frametime_unbounded = host_frametime; |
|
} |
|
else if (host_timescale.GetFloat() > 0 |
|
#if !defined(SWDS) |
|
&& ( CanCheat() || demoplayer->IsPlayingBack() ) |
|
#endif |
|
) |
|
{ |
|
float fullscale = host_timescale.GetFloat(); |
|
|
|
#if !defined(SWDS) |
|
if ( demoplayer->IsPlayingBack() ) |
|
{ |
|
// adjust time scale if playing back demo |
|
fullscale *= demoplayer->GetPlaybackTimeScale(); |
|
} |
|
#endif |
|
|
|
host_frametime *= fullscale; |
|
|
|
host_frametime_unbounded = host_frametime; |
|
|
|
#ifndef NO_TOOLFRAMEWORK |
|
if ( CommandLine()->CheckParm( "-tools" ) == NULL ) |
|
{ |
|
#endif |
|
host_frametime = min( (double)host_frametime, MAX_FRAMETIME * fullscale); |
|
#ifndef NO_TOOLFRAMEWORK |
|
} |
|
#endif |
|
} |
|
else |
|
#ifndef NO_TOOLFRAMEWORK |
|
if ( CommandLine()->CheckParm( "-tools" ) != NULL ) |
|
{ |
|
host_frametime_unbounded = host_frametime; |
|
} |
|
else |
|
#endif // !NO_TOOLFRAMEWORK |
|
{ // don't allow really long or short frames |
|
host_frametime_unbounded = host_frametime; |
|
host_frametime = min( (double)host_frametime, MAX_FRAMETIME ); |
|
host_frametime = max( (double)host_frametime, MIN_FRAMETIME ); |
|
} |
|
#endif |
|
|
|
// Adjust the client clock very slightly to keep it in line with the server clock. |
|
float adj = cl.GetClockDriftMgr().AdjustFrameTime( host_frametime ) - host_frametime; |
|
host_frametime += adj; |
|
host_frametime_unbounded += adj; |
|
|
|
if ( g_pSoundServices ) // not present on linux server |
|
g_pSoundServices->SetSoundFrametime(dt, host_frametime); |
|
|
|
} |
|
|
|
#define FPS_AVG_FRAC 0.9f |
|
|
|
float g_fFramesPerSecond = 0.0f; |
|
|
|
/* |
|
================== |
|
Host_PostFrameRate |
|
================== |
|
*/ |
|
void Host_PostFrameRate( float frameTime ) |
|
{ |
|
frameTime = clamp( frameTime, 0.0001f, 1.0f ); |
|
|
|
float fps = 1.0f / frameTime; |
|
g_fFramesPerSecond = g_fFramesPerSecond * FPS_AVG_FRAC + ( 1.0f - FPS_AVG_FRAC ) * fps; |
|
} |
|
|
|
/* |
|
================== |
|
Host_GetHostInfo |
|
================== |
|
*/ |
|
void Host_GetHostInfo(float *fps, int *nActive, int *nMaxPlayers, char *pszMap, int maxlen ) |
|
{ |
|
// Count clients, report |
|
int clients = sv.GetNumClients(); |
|
|
|
*fps = g_fFramesPerSecond; |
|
*nActive = clients; |
|
|
|
if (pszMap) |
|
{ |
|
if (sv.m_szMapname && sv.m_szMapname[0]) |
|
Q_strncpy(pszMap, sv.m_szMapname, maxlen ); |
|
else |
|
pszMap[0] = '\0'; |
|
} |
|
|
|
*nMaxPlayers = sv.GetMaxClients(); |
|
} |
|
|
|
static bool AppearsNumeric( char const *in ) |
|
{ |
|
char const *p = in; |
|
int special[ 3 ]; |
|
Q_memset( special, 0, sizeof( special ) ); |
|
|
|
for ( ; *p; p++ ) |
|
{ |
|
if ( *p == '-' ) |
|
{ |
|
special[0]++; |
|
continue; |
|
} |
|
|
|
if ( *p == '+' ) |
|
{ |
|
special[1]++; |
|
continue; |
|
} |
|
|
|
if ( *p >= '0' && *p <= '9' ) |
|
{ |
|
continue; |
|
} |
|
|
|
if ( *p == '.' ) |
|
{ |
|
special[2]++; |
|
continue; |
|
} |
|
|
|
return false; |
|
} |
|
|
|
// Can't have multiple +, -, or decimals |
|
for ( int i = 0; i < 3; i++ ) |
|
{ |
|
if ( special[ i ] > 1 ) |
|
return false; |
|
} |
|
|
|
// can't be + and - at same time |
|
if ( special[ 0 ] && special[ 1 ] ) |
|
return false; |
|
|
|
return true; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: If the value is numeric, remove unnecessary trailing zeros |
|
// Input : *invalue - |
|
// Output : char const |
|
//----------------------------------------------------------------------------- |
|
char const * Host_CleanupConVarStringValue( char const *invalue ) |
|
{ |
|
static char clean[ 256 ]; |
|
|
|
Q_snprintf( clean, sizeof( clean ), "%s", invalue ); |
|
|
|
// Don't mess with empty string |
|
// Otherwise, if it appears numeric and has a decimal, try to strip all zeroes after decimal |
|
if ( Q_strlen( clean ) >= 1 && AppearsNumeric( clean ) && Q_strstr( clean, "." ) ) |
|
{ |
|
char *end = clean + strlen( clean ) - 1; |
|
while ( *end && end >= clean ) |
|
{ |
|
// Removing trailing zeros |
|
if ( *end != '0' ) |
|
{ |
|
// Remove decimal, zoo |
|
if ( *end == '.' ) |
|
{ |
|
if ( end == clean ) |
|
{ |
|
*end = '0'; |
|
} |
|
else |
|
{ |
|
*end = 0; |
|
} |
|
} |
|
break; |
|
} |
|
|
|
*end-- = 0; |
|
} |
|
} |
|
|
|
return clean; |
|
} |
|
|
|
int Host_CountVariablesWithFlags( int flags, bool nonDefault ) |
|
{ |
|
int i = 0; |
|
const ConCommandBase *var; |
|
|
|
for ( var = g_pCVar->GetCommands() ; var ; var=var->GetNext() ) |
|
{ |
|
if ( var->IsCommand() ) |
|
continue; |
|
|
|
const ConVar *pCvar = ( const ConVar * )var; |
|
|
|
if ( !pCvar->IsFlagSet( flags ) ) |
|
continue; |
|
|
|
// It's == to the default value, don't count |
|
if ( nonDefault && !Q_strcasecmp( pCvar->GetDefault(), pCvar->GetString() ) ) |
|
continue; |
|
|
|
i++; |
|
} |
|
|
|
return i; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Input : msg - |
|
//----------------------------------------------------------------------------- |
|
void Host_BuildConVarUpdateMessage( NET_SetConVar *cvarMsg, int flags, bool nonDefault ) |
|
{ |
|
int count = Host_CountVariablesWithFlags( flags, nonDefault ); |
|
|
|
// Nothing to send |
|
if ( count <= 0 ) |
|
return; |
|
|
|
// Too many to send, error out and have mod author get a clue. |
|
if ( count > 255 ) |
|
{ |
|
Sys_Error( "Engine only supports 255 ConVars marked %i\n", flags ); |
|
return; |
|
} |
|
|
|
const ConCommandBase *var; |
|
|
|
for ( var = g_pCVar->GetCommands() ; var ; var=var->GetNext() ) |
|
{ |
|
if ( var->IsCommand() ) |
|
continue; |
|
|
|
const ConVar *pCvar = ( const ConVar * )var; |
|
|
|
if ( !pCvar->IsFlagSet( flags ) ) |
|
continue; |
|
|
|
// It's == to the default value, don't count |
|
if ( nonDefault && !Q_strcasecmp( pCvar->GetDefault(), pCvar->GetString() ) ) |
|
continue; |
|
|
|
NET_SetConVar::cvar_t acvar; |
|
|
|
Q_strncpy( acvar.name, pCvar->GetName(), MAX_OSPATH ); |
|
Q_strncpy( acvar.value, Host_CleanupConVarStringValue( pCvar->GetString() ), MAX_OSPATH ); |
|
|
|
cvarMsg->m_ConVars.AddToTail( acvar ); |
|
} |
|
|
|
// Make sure this count matches original one!!! |
|
Assert( cvarMsg->m_ConVars.Count() == count ); |
|
} |
|
|
|
#if !defined( SWDS ) |
|
// FIXME: move me somewhere more appropriate |
|
void CL_SendVoicePacket(bool bFinal) |
|
{ |
|
#if !defined( NO_VOICE ) |
|
if ( !Voice_IsRecording() ) |
|
return; |
|
|
|
CLC_VoiceData voiceMsg; |
|
|
|
// Get whatever compressed data there is and and send it. |
|
char uchVoiceData[2048]; |
|
|
|
voiceMsg.m_DataOut.StartWriting( uchVoiceData, sizeof(uchVoiceData) ); |
|
|
|
voiceMsg.m_nLength = Voice_GetCompressedData( uchVoiceData, sizeof(uchVoiceData), bFinal ) * 8; |
|
|
|
if( !voiceMsg.m_nLength ) |
|
return; |
|
|
|
voiceMsg.m_DataOut.SeekToBit( voiceMsg.m_nLength ); // set correct writing position |
|
|
|
if ( cl.IsActive() ) |
|
{ |
|
cl.m_NetChannel->SendNetMsg( voiceMsg ); |
|
} |
|
#endif |
|
} |
|
|
|
#if defined ( _X360 ) |
|
|
|
|
|
void CL_ProcessXboxVoiceData() |
|
{ |
|
if ( Audio_GetXVoice() == NULL ) |
|
return; |
|
|
|
if ( Audio_GetXVoice()->VoiceUpdateData() == true ) |
|
{ |
|
if ( cl.IsActive() ) |
|
{ |
|
Audio_GetXVoice()->VoiceSendData( cl.m_NetChannel ); |
|
} |
|
} |
|
} |
|
|
|
#endif |
|
|
|
void CL_ProcessVoiceData() |
|
{ |
|
VPROF_BUDGET( "CL_ProcessVoiceData", VPROF_BUDGETGROUP_OTHER_NETWORKING ); |
|
|
|
#if !defined( NO_VOICE ) |
|
Voice_Idle(host_frametime); |
|
CL_SendVoicePacket(false); |
|
#endif |
|
|
|
#if defined ( _X360 ) |
|
|
|
CL_ProcessXboxVoiceData(); |
|
#endif |
|
|
|
} |
|
#endif |
|
|
|
|
|
|
|
|
|
|
|
|
|
/* |
|
===================== |
|
Host_UpdateScreen |
|
|
|
Refresh the screen |
|
===================== |
|
*/ |
|
void Host_UpdateScreen( void ) |
|
{ |
|
#ifndef SWDS |
|
|
|
#ifdef _DEBUG |
|
if( r_ForceRestore.GetInt() ) |
|
{ |
|
ForceMatSysRestore(); |
|
r_ForceRestore.SetValue(0); |
|
} |
|
#endif // _DEBUG |
|
|
|
// Refresh the screen |
|
SCR_UpdateScreen (); |
|
#endif |
|
} |
|
|
|
/* |
|
==================== |
|
Host_UpdateSounds |
|
|
|
Update sound subsystem and cd audio |
|
==================== |
|
*/ |
|
void Host_UpdateSounds( void ) |
|
{ |
|
#if !defined( SWDS ) |
|
// update audio |
|
if ( cl.IsActive() ) |
|
{ |
|
S_Update( &s_AudioState ); |
|
} |
|
else |
|
{ |
|
S_Update( NULL ); |
|
} |
|
#endif |
|
} |
|
|
|
/* |
|
============================== |
|
Host_Speeds |
|
|
|
============================== |
|
*/ |
|
void CFrameTimer::ResetDeltas() |
|
{ |
|
for ( int i = 0; i < NUM_FRAME_SEGMENTS; i++ ) |
|
{ |
|
deltas[ i ] = 0.0f; |
|
} |
|
} |
|
|
|
void CFrameTimer::MarkFrame() |
|
{ |
|
double frameTime; |
|
double fps; |
|
|
|
// ConDMsg("%f %f %f\n", time1, time2, time3 ); |
|
|
|
float fs_input = (deltas[FRAME_SEGMENT_INPUT])*1000.0; |
|
float fs_client = (deltas[FRAME_SEGMENT_CLIENT])*1000.0; |
|
float fs_server = (deltas[FRAME_SEGMENT_SERVER])*1000.0; |
|
float fs_render = (deltas[FRAME_SEGMENT_RENDER])*1000.0; |
|
float fs_sound = (deltas[FRAME_SEGMENT_SOUND])*1000.0; |
|
float fs_cldll = (deltas[FRAME_SEGMENT_CLDLL])*1000.0; |
|
float fs_exec = (deltas[FRAME_SEGMENT_CMD_EXECUTE])*1000.0; |
|
|
|
ResetDeltas(); |
|
|
|
frameTime = host_frametime; |
|
//frameTime /= 1000.0; |
|
if ( frameTime < 0.0001 ) |
|
{ |
|
fps = 999.0; |
|
} |
|
else |
|
{ |
|
fps = 1.0 / frameTime; |
|
} |
|
|
|
if (host_speeds.GetInt()) |
|
{ |
|
int ent_count = 0; |
|
int i; |
|
static int last_host_tickcount; |
|
|
|
int ticks = host_tickcount - last_host_tickcount; |
|
last_host_tickcount = host_tickcount; |
|
|
|
// count used entities |
|
for (i=0 ; i<sv.num_edicts ; i++) |
|
{ |
|
if (!sv.edicts[i].IsFree()) |
|
ent_count++; |
|
} |
|
|
|
char sz[ 256 ]; |
|
Q_snprintf( sz, sizeof( sz ), |
|
"%3i fps -- inp(%.2f) sv(%.2f) cl(%.2f) render(%.2f) snd(%.2f) cl_dll(%.2f) exec(%.2f) ents(%d) ticks(%d)", |
|
(int)fps, |
|
fs_input, |
|
fs_server, |
|
fs_client, |
|
fs_render, |
|
fs_sound, |
|
fs_cldll, |
|
fs_exec, |
|
ent_count, |
|
ticks ); |
|
|
|
#ifndef SWDS |
|
if ( host_speeds.GetInt() >= 2 ) |
|
{ |
|
Con_NPrintf ( 0, sz ); |
|
} |
|
else |
|
{ |
|
ConDMsg ( "%s\n", sz ); |
|
} |
|
#endif |
|
} |
|
|
|
} |
|
|
|
#define FRAME_TIME_FILTER_TIME 0.5f |
|
|
|
void CFrameTimer::ComputeFrameVariability() |
|
{ |
|
m_pFrameTimeHistory[m_nFrameTimeHistoryIndex] = frametime; |
|
if ( ++m_nFrameTimeHistoryIndex >= FRAME_HISTORY_COUNT ) |
|
{ |
|
m_nFrameTimeHistoryIndex = 0; |
|
} |
|
|
|
// Compute a low-pass filter of the frame time over the last half-second |
|
// Count the number of samples that live within the last half-second |
|
int i = m_nFrameTimeHistoryIndex; |
|
int nMaxSamples = 0; |
|
float flTotalTime = 0.0f; |
|
while( (nMaxSamples < FRAME_HISTORY_COUNT) && (flTotalTime <= FRAME_TIME_FILTER_TIME) ) |
|
{ |
|
if ( --i < 0 ) |
|
{ |
|
i = FRAME_HISTORY_COUNT - 1; |
|
} |
|
if ( m_pFrameTimeHistory[i] == 0.0f ) |
|
break; |
|
|
|
flTotalTime += m_pFrameTimeHistory[i]; |
|
++nMaxSamples; |
|
} |
|
|
|
if ( nMaxSamples == 0 ) |
|
{ |
|
m_flFPSVariability = 0.0f; |
|
m_flFPSStdDeviationSeconds = 0.0f; |
|
return; |
|
} |
|
|
|
float flExponent = -2.0f / (int)nMaxSamples; |
|
|
|
i = m_nFrameTimeHistoryIndex; |
|
float flAverageTime = 0.0f; |
|
float flExpCurveArea = 0.0f; |
|
int n = 0; |
|
while( n < nMaxSamples ) |
|
{ |
|
if ( --i < 0 ) |
|
{ |
|
i = FRAME_HISTORY_COUNT - 1; |
|
} |
|
flExpCurveArea += exp( flExponent * n ); |
|
flAverageTime += m_pFrameTimeHistory[i] * exp( flExponent * n ); |
|
++n; |
|
} |
|
|
|
flAverageTime /= flExpCurveArea; |
|
|
|
float flAveFPS = 0.0f; |
|
if ( flAverageTime != 0.0f ) |
|
{ |
|
flAveFPS = 1.0f / flAverageTime; |
|
} |
|
|
|
float flCurrentFPS = 0.0f; |
|
if ( frametime != 0.0f ) |
|
{ |
|
flCurrentFPS = 1.0f / frametime; |
|
} |
|
|
|
// Now subtract out the current fps to get variability in FPS |
|
m_flFPSVariability = fabs( flCurrentFPS - flAveFPS ); |
|
|
|
// Now compute variance/stddeviation |
|
double sum = 0.0f; |
|
int count =0; |
|
for ( int j = 0; j < FRAME_HISTORY_COUNT; ++j ) |
|
{ |
|
if ( m_pFrameTimeHistory[ j ] == 0.0f ) |
|
continue; |
|
|
|
double ft = min( (double)m_pFrameTimeHistory[ j ], 0.25 ); |
|
|
|
sum += ft; |
|
++count; |
|
|
|
} |
|
|
|
if ( count <= 1 ) |
|
{ |
|
return; |
|
} |
|
|
|
double avg = sum / (double)count; |
|
double devSquared = 0.0f; |
|
for ( int j = 0; j < FRAME_HISTORY_COUNT; ++j ) |
|
{ |
|
if ( m_pFrameTimeHistory[ j ] == 0.0f ) |
|
continue; |
|
|
|
double ft = min( (double)m_pFrameTimeHistory[ j ], 0.25 ); |
|
|
|
double dt = ft - avg; |
|
|
|
devSquared += ( dt * dt ); |
|
} |
|
|
|
double variance = devSquared / (double)( count - 1 ); |
|
m_flFPSStdDeviationSeconds = sqrt( variance ); |
|
} |
|
|
|
void Host_Speeds() |
|
{ |
|
g_HostTimes.MarkFrame(); |
|
#if !defined(SWDS) |
|
ToClientDemoPlayer( demoplayer )->MarkFrame( g_HostTimes.m_flFPSVariability ); |
|
#endif |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: When singlestep == 1, then you must set next == 1 to run to the |
|
// next frame. |
|
// Output : Returns true on success, false on failure. |
|
//----------------------------------------------------------------------------- |
|
bool Host_ShouldRun( void ) |
|
{ |
|
static int current_tick = -1; |
|
|
|
// See if we are single stepping |
|
if ( !singlestep.GetInt() ) |
|
{ |
|
return true; |
|
} |
|
|
|
// Did user set "next" to 1? |
|
if ( cvarNext.GetInt() ) |
|
{ |
|
// Did we finally finish this frame ( Host_ShouldRun is called in 3 spots to pause |
|
// three different things ). |
|
if ( current_tick != (host_tickcount-1) ) |
|
{ |
|
// Okay, time to reset to halt execution again |
|
cvarNext.SetValue( 0 ); |
|
return false; |
|
} |
|
|
|
// Otherwise, keep running this one frame since we are still finishing this frame |
|
return true; |
|
} |
|
else |
|
{ |
|
// Remember last frame without "next" being reset ( time is locked ) |
|
current_tick = host_tickcount; |
|
// Time is locked |
|
return false; |
|
} |
|
} |
|
|
|
static ConVar mem_periodicdumps( "mem_periodicdumps", "0", 0, "Write periodic memstats dumps every n seconds." ); |
|
static double g_flLastPeriodicMemDump = -1.0f; |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
static float g_TimeLastMemTest; |
|
void Host_CheckDumpMemoryStats( void ) |
|
{ |
|
if ( mem_test_each_frame.GetBool() ) |
|
{ |
|
if ( !MemAlloc_CrtCheckMemory() ) |
|
{ |
|
DebuggerBreakIfDebugging(); |
|
Error( "Heap is corrupt\n" ); |
|
} |
|
} |
|
else if ( mem_test_every_n_seconds.GetInt() > 0 ) |
|
{ |
|
float now = Plat_FloatTime(); |
|
if ( now - g_TimeLastMemTest > mem_test_every_n_seconds.GetInt() ) |
|
{ |
|
g_TimeLastMemTest = now; |
|
if ( !MemAlloc_CrtCheckMemory() ) |
|
{ |
|
DebuggerBreakIfDebugging(); |
|
Error( "Heap is corrupt\n" ); |
|
} |
|
} |
|
} |
|
|
|
if ( mem_periodicdumps.GetFloat() > 0.0f ) |
|
{ |
|
double curtime = Plat_FloatTime(); |
|
if ( curtime - g_flLastPeriodicMemDump > mem_periodicdumps.GetFloat() ) |
|
{ |
|
const char *pTest = sv.GetMapName(); |
|
if ( !pTest || !pTest[0] ) |
|
{ |
|
// possibly at menu |
|
pTest = "nomap"; |
|
} |
|
|
|
char mapname[ 256 ]; |
|
Q_FileBase( pTest, mapname, sizeof( mapname ) ); |
|
#if defined( _MEMTEST ) |
|
MemAlloc_SetStatsExtraInfo( pTest, "" ); |
|
#endif |
|
MemAlloc_DumpStatsFileBase( mapname ); |
|
g_flLastPeriodicMemDump = curtime; |
|
} |
|
} |
|
|
|
|
|
#if defined(_WIN32) |
|
if ( mem_dumpstats.GetInt() <= 0 ) |
|
return; |
|
|
|
if ( mem_dumpstats.GetInt() == 1 ) |
|
mem_dumpstats.SetValue( 0 ); // reset cvar, dump stats only once |
|
|
|
_CrtMemState state; |
|
Q_memset( &state, 0, sizeof( state ) ); |
|
_CrtMemCheckpoint( &state ); |
|
|
|
unsigned int size = 0; |
|
|
|
for ( int use = 0; use < _MAX_BLOCKS; use++) |
|
{ |
|
size += state.lSizes[ use ]; |
|
} |
|
Msg("MEMORY: Run-time Heap\n------------------------------------\n"); |
|
|
|
Msg( "\tHigh water %s\n", Q_pretifymem( state.lHighWaterCount,4 ) ); |
|
Msg( "\tCurrent mem %s\n", Q_pretifymem( size,4 ) ); |
|
Msg("------------------------------------\n"); |
|
int hunk = Hunk_MallocSize(); |
|
Msg("\tAllocated outside hunk: %s\n", Q_pretifymem( size - hunk ) ); |
|
#endif |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void _Host_SetGlobalTime() |
|
{ |
|
// Server |
|
g_ServerGlobalVariables.realtime = realtime; |
|
g_ServerGlobalVariables.framecount = host_framecount; |
|
g_ServerGlobalVariables.absoluteframetime = host_frametime; |
|
g_ServerGlobalVariables.interval_per_tick = host_state.interval_per_tick; |
|
g_ServerGlobalVariables.serverCount = Host_GetServerCount(); |
|
#ifndef SWDS |
|
// Client |
|
g_ClientGlobalVariables.realtime = realtime; |
|
g_ClientGlobalVariables.framecount = host_framecount; |
|
g_ClientGlobalVariables.absoluteframetime = host_frametime; |
|
g_ClientGlobalVariables.interval_per_tick = host_state.interval_per_tick; |
|
#endif |
|
} |
|
|
|
/* |
|
================== |
|
_Host_RunFrame |
|
|
|
Runs all active servers |
|
================== |
|
*/ |
|
|
|
void _Host_RunFrame_Input( float accumulated_extra_samples, bool bFinalTick ) |
|
{ |
|
VPROF_BUDGET( "_Host_RunFrame_Input", _T("Input") ); |
|
|
|
// Run a test script? |
|
static bool bFirstFrame = true; |
|
if ( bFirstFrame ) |
|
{ |
|
bFirstFrame = false; |
|
const char *pScriptFilename = CommandLine()->ParmValue( "-testscript" ); |
|
if ( pScriptFilename ) |
|
{ |
|
if ( !GetTestScriptMgr()->StartTestScript( pScriptFilename ) ) |
|
{ |
|
Error( "StartTestScript( %s ) failed.", pScriptFilename ); |
|
} |
|
} |
|
} |
|
|
|
g_HostTimes.StartFrameSegment( FRAME_SEGMENT_INPUT ); |
|
|
|
#ifndef SWDS |
|
// Client can process input |
|
ClientDLL_ProcessInput( ); |
|
|
|
g_HostTimes.StartFrameSegment( FRAME_SEGMENT_CMD_EXECUTE ); |
|
|
|
// process console commands |
|
Cbuf_Execute (); |
|
|
|
g_HostTimes.EndFrameSegment( FRAME_SEGMENT_CMD_EXECUTE ); |
|
|
|
// Send any current movement commands to server and flush reliable buffer even if not moving yet. |
|
CL_Move( accumulated_extra_samples, bFinalTick ); |
|
|
|
#endif |
|
|
|
g_HostTimes.EndFrameSegment( FRAME_SEGMENT_INPUT ); |
|
} |
|
|
|
void _Host_RunFrame_Server( bool finaltick ) |
|
{ |
|
VPROF_BUDGET( "_Host_RunFrame_Server", VPROF_BUDGETGROUP_GAME ); |
|
VPROF_INCREMENT_COUNTER( "ticks", 1 ); |
|
tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s", __FUNCTION__ ); |
|
|
|
// Run the Server frame ( read, run physics, respond ) |
|
g_HostTimes.StartFrameSegment( FRAME_SEGMENT_SERVER ); |
|
SV_Frame ( finaltick ); |
|
g_HostTimes.EndFrameSegment( FRAME_SEGMENT_SERVER ); |
|
|
|
// Look for connectionless rcon packets on dedicated servers |
|
// SV_CheckRcom(); TODO |
|
} |
|
|
|
void _Host_RunFrame_Server_Async( int numticks ) |
|
{ |
|
tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s %d", __FUNCTION__, numticks ); |
|
|
|
for ( int tick = 0; tick < numticks; tick++ ) |
|
{ |
|
g_ServerGlobalVariables.tickcount = sv.m_nTickCount; |
|
g_ServerGlobalVariables.simTicksThisFrame = numticks - tick; |
|
bool bFinalTick = ( tick == (numticks - 1) ); |
|
_Host_RunFrame_Server( bFinalTick ); |
|
} |
|
} |
|
|
|
|
|
void _Host_RunFrame_Client( bool framefinished ) |
|
{ |
|
#ifndef SWDS |
|
VPROF( "_Host_RunFrame_Client" ); |
|
tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s %d", __FUNCTION__, framefinished ); |
|
|
|
g_HostTimes.StartFrameSegment( FRAME_SEGMENT_CLIENT ); |
|
|
|
// Get any current state update from server, etc. |
|
CL_ReadPackets( framefinished ); |
|
|
|
#if defined( VOICE_OVER_IP ) |
|
// Send any enqueued voice data to the server |
|
CL_ProcessVoiceData(); |
|
#endif // VOICE_OVER_IP |
|
|
|
cl.CheckUpdatingSteamResources(); |
|
cl.CheckFileCRCsWithServer(); |
|
|
|
// Resend connection request if needed. |
|
cl.RunFrame(); |
|
|
|
if ( CL_IsHL2Demo() || CL_IsPortalDemo() ) // don't need sv.IsDedicated() because ded servers don't run this |
|
{ |
|
void CL_DemoCheckGameUIRevealTime(); |
|
CL_DemoCheckGameUIRevealTime(); |
|
} |
|
|
|
Steam3Client().RunFrame(); |
|
|
|
g_HostTimes.EndFrameSegment( FRAME_SEGMENT_CLIENT ); |
|
|
|
// This takes 1 usec, so it's pretty cheap... |
|
CL_SetPagedPoolInfo(); |
|
#endif |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Used to set limits on certain convars in multiplayer/sv_cheats mode. |
|
// Returns true if it was called recursively and it early-outed. |
|
//----------------------------------------------------------------------------- |
|
bool CheckVarRange_Generic( ConVar *pVar, int minVal, int maxVal ) |
|
{ |
|
if ( !CanCheat() && !Host_IsSinglePlayerGame() ) |
|
{ |
|
int clampedValue = clamp( pVar->GetInt(), minVal, maxVal ); |
|
if ( clampedValue != pVar->GetInt() ) |
|
{ |
|
Warning( "sv_cheats=0 prevented changing %s outside of the range [%d,%d] (was %d).\n", pVar->GetName(), minVal, maxVal, pVar->GetInt() ); |
|
pVar->SetValue( clampedValue ); |
|
} |
|
} |
|
|
|
return false; |
|
} |
|
|
|
|
|
void CheckSpecialCheatVars() |
|
{ |
|
static ConVar *mat_picmip = NULL; |
|
if ( !mat_picmip ) |
|
mat_picmip = g_pCVar->FindVar( "mat_picmip" ); |
|
|
|
// In multiplayer, don't allow them to set mat_picmip > 2. |
|
if ( mat_picmip ) |
|
CheckVarRange_Generic( mat_picmip, -1, 2 ); |
|
|
|
CheckVarRange_r_rootlod(); |
|
CheckVarRange_r_lod(); |
|
HandleServerAllowColorCorrection(); |
|
} |
|
|
|
|
|
void _Host_RunFrame_Render() |
|
{ |
|
#ifndef SWDS |
|
VPROF( "_Host_RunFrame_Render" ); |
|
tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "_Host_RunFrame_Render" ); |
|
|
|
CheckSpecialCheatVars(); |
|
|
|
int nOrgNoRendering = mat_norendering.GetInt(); |
|
|
|
if ( cl_takesnapshot ) |
|
{ |
|
// turn off no-rendering mode, if taking screenshot |
|
mat_norendering.SetValue( 0 ); |
|
} |
|
|
|
// update video if not running in background |
|
g_HostTimes.StartFrameSegment( FRAME_SEGMENT_RENDER ); |
|
|
|
CL_LatchInterpolationAmount(); |
|
|
|
{ |
|
VPROF( "_Host_RunFrame_Render - UpdateScreen" ); |
|
tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "_Host_RunFrame_Render - UpdateScreen" ); |
|
Host_UpdateScreen(); |
|
} |
|
{ |
|
VPROF( "_Host_RunFrame_Render - CL_DecayLights" ); |
|
tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "_Host_RunFrame_Render - CL_DecayLights" ); |
|
CL_DecayLights (); |
|
} |
|
|
|
g_HostTimes.EndFrameSegment( FRAME_SEGMENT_RENDER ); |
|
|
|
saverestore->OnFrameRendered(); |
|
|
|
#ifdef USE_SDL |
|
if ( g_pLauncherMgr ) |
|
{ |
|
g_pLauncherMgr->OnFrameRendered(); |
|
} |
|
#endif |
|
|
|
mat_norendering.SetValue( nOrgNoRendering ); |
|
#endif |
|
} |
|
|
|
void CL_FindInterpolatedAddAngle( float t, float& frac, AddAngle **prev, AddAngle **next ) |
|
{ |
|
int c = cl.addangle.Count(); |
|
|
|
*prev = NULL; |
|
*next = NULL; |
|
|
|
AddAngle *pentry = NULL; |
|
for ( int i = 0; i < c; i++ ) |
|
{ |
|
AddAngle *entry = &cl.addangle[ i ]; |
|
|
|
*next = entry; |
|
|
|
// Time is earlier |
|
if ( t < entry->starttime ) |
|
{ |
|
if ( i == 0 ) |
|
{ |
|
*prev = *next; |
|
frac = 0.0f; |
|
return; |
|
} |
|
|
|
// Avoid div by zero |
|
if ( entry->starttime == pentry->starttime ) |
|
{ |
|
frac = 0.0f; |
|
return; |
|
} |
|
|
|
// Time spans the two entries |
|
frac = ( t - pentry->starttime ) / ( entry->starttime - pentry->starttime ); |
|
frac = clamp( frac, 0.0f, 1.0f ); |
|
return; |
|
} |
|
|
|
*prev = *next; |
|
pentry = entry; |
|
} |
|
} |
|
|
|
void CL_DiscardOldAddAngleEntries( float t ) |
|
{ |
|
float killtime = t - host_state.interval_per_tick - 0.1f; |
|
|
|
for ( int i = 0; i < cl.addangle.Count(); i++ ) |
|
{ |
|
AddAngle *p = &cl.addangle[ i ]; |
|
if ( p->starttime <= killtime ) |
|
{ |
|
cl.addangle.Remove( i ); |
|
--i; |
|
} |
|
} |
|
|
|
// It's safe to reset the master counter once all the entries decay |
|
if ( cl.addangle.Count() == 0 ) |
|
{ |
|
cl.prevaddangletotal = cl.addangletotal = 0.0f; |
|
} |
|
} |
|
|
|
#ifndef SWDS |
|
void CL_ApplyAddAngle() |
|
{ |
|
tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s", __FUNCTION__ ); |
|
|
|
float curtime = cl.GetTime() - host_state.interval_per_tick; |
|
|
|
AddAngle *prev = NULL, *next = NULL; |
|
float frac = 0.0f; |
|
|
|
float addangletotal = 0.0f; |
|
|
|
CL_FindInterpolatedAddAngle( curtime, frac, &prev, &next ); |
|
|
|
if ( prev && next ) |
|
{ |
|
addangletotal = prev->total + frac * ( next->total - prev->total ); |
|
} |
|
else |
|
{ |
|
addangletotal = cl.prevaddangletotal; |
|
} |
|
|
|
float amove = addangletotal - cl.prevaddangletotal; |
|
|
|
// Update view angles |
|
cl.viewangles[ 1 ] += amove; |
|
// Update client .dll view of angles |
|
g_pClientSidePrediction->SetLocalViewAngles( cl.viewangles ); |
|
|
|
// Remember last total |
|
cl.prevaddangletotal = addangletotal; |
|
|
|
CL_DiscardOldAddAngleEntries( curtime ); |
|
} |
|
#endif |
|
|
|
void _Host_RunFrame_Sound() |
|
{ |
|
#ifndef SWDS |
|
|
|
VPROF_BUDGET( "_Host_RunFrame_Sound", VPROF_BUDGETGROUP_OTHER_SOUND ); |
|
|
|
g_HostTimes.StartFrameSegment( FRAME_SEGMENT_SOUND ); |
|
|
|
Host_UpdateSounds(); |
|
|
|
g_HostTimes.EndFrameSegment( FRAME_SEGMENT_SOUND ); |
|
#endif |
|
} |
|
|
|
float Host_GetSoundDuration( const char *pSample ) |
|
{ |
|
#ifndef SWDS |
|
if (!sv.IsDedicated()) |
|
{ |
|
extern float SV_GetSoundDuration(const char *pSample); |
|
extern float AudioSource_GetSoundDuration(CSfxTable *pSfx); |
|
int index = cl.LookupSoundIndex(pSample); |
|
if (index >= 0) |
|
return AudioSource_GetSoundDuration(cl.GetSound(index)); |
|
return SV_GetSoundDuration(pSample); |
|
} |
|
#endif |
|
return 0.0f; |
|
} |
|
|
|
CON_COMMAND( host_runofftime, "Run off some time without rendering/updating sounds\n" ) |
|
{ |
|
if ( args.ArgC() != 2 ) |
|
{ |
|
ConMsg( "Usage: host_runofftime <seconds>\n" ); |
|
return; |
|
} |
|
|
|
if ( !sv.IsActive() ) |
|
{ |
|
ConMsg( "host_ruofftime: must be running a server\n" ); |
|
return; |
|
} |
|
|
|
if ( sv.IsMultiplayer() ) |
|
{ |
|
ConMsg( "host_ruofftime: only valid in single player\n" ); |
|
return; |
|
} |
|
|
|
float advanceTime = atof( args[1] ); |
|
if ( advanceTime <= 0.0f ) |
|
return; |
|
|
|
// 15 minutes is a _long_ time!!! |
|
if ( advanceTime > 15.0f * 60.0f ) |
|
{ |
|
ConMsg( "host_runofftime would run off %.2f minutes!!! ignoring\n", |
|
advanceTime / 60.0f ); |
|
return; |
|
} |
|
|
|
ConMsg( "Skipping ahead for %f seconds\n", advanceTime ); |
|
|
|
SCR_UpdateScreen(); |
|
SCR_UpdateScreen (); |
|
} |
|
|
|
#if !defined( _X360 ) |
|
S_API int SteamGameServer_GetIPCCallCount(); |
|
#else |
|
S_API int SteamGameServer_GetIPCCallCount() { return 0; } |
|
#endif |
|
void Host_ShowIPCCallCount() |
|
{ |
|
// If set to 0 then get out. |
|
if ( host_ShowIPCCallCount.GetInt() == 0 ) |
|
return; |
|
|
|
static float s_flLastTime = 0; |
|
static int s_nLastTick = host_tickcount; |
|
static int s_nLastFrame = host_framecount; |
|
|
|
// Figure out how often they want to update. |
|
double flInterval = 0; |
|
if ( host_ShowIPCCallCount.GetFloat() > 0 ) |
|
{ |
|
flInterval = 1.0f / host_ShowIPCCallCount.GetFloat(); |
|
} |
|
|
|
// This is called every frame so increment the frame counter. |
|
double flCurTime = Plat_FloatTime(); |
|
if ( flCurTime - s_flLastTime >= flInterval ) |
|
{ |
|
uint32 callCount; |
|
ISteamClient *pSteamClient = SteamClient(); |
|
if ( pSteamClient ) |
|
{ |
|
callCount = pSteamClient->GetIPCCallCount(); |
|
} |
|
else |
|
{ |
|
// Ok, we're a dedicated server and we need to use this to get it. |
|
callCount = (uint32)SteamGameServer_GetIPCCallCount(); |
|
} |
|
|
|
// Avoid a divide by zero. |
|
int frameCount = host_framecount - s_nLastFrame; |
|
int tickCount = host_tickcount - s_nLastTick; |
|
if ( frameCount == 0 || tickCount == 0 ) |
|
return; |
|
|
|
Msg( "host_ShowIPCCallCount: %d IPC calls in the past [%d frames, %d ticks] Avg: [%.2f/frame, %.2f/tick]\n", |
|
callCount, frameCount, tickCount, (float)callCount / frameCount, (float)callCount / tickCount ); |
|
|
|
s_flLastTime = flCurTime; |
|
s_nLastTick = host_tickcount; |
|
s_nLastFrame = host_framecount; |
|
} |
|
} |
|
|
|
void Host_SetClientInSimulation( bool bInSimulation ) |
|
{ |
|
#ifndef SWDS |
|
// Tracker 77931: If the game is paused, then lock the client clock at the previous tick boundary |
|
// (otherwise we'll keep interpolating through the "remainder" time causing the paused characters |
|
// to twitch like they have the shakes) |
|
// TODO: Since this rounds down on the frame we paused, we could see a slight backsliding. We could remember the last "remainder" before pause and re-use it and |
|
// set insimulation == false to be mroe exact. We'd still have to deal with the timing difference between |
|
// when pause/unpause happens on the server versus the client |
|
cl.insimulation = bInSimulation || cl.IsPaused(); |
|
|
|
// Compute absolute/render time stamp |
|
g_ClientGlobalVariables.curtime = cl.GetTime(); |
|
g_ClientGlobalVariables.frametime = cl.GetFrameTime(); |
|
#endif |
|
} |
|
|
|
static ConVar host_Sleep( "host_sleep", "0", FCVAR_CHEAT, "Force the host to sleep a certain number of milliseconds each frame." ); |
|
extern ConVar sv_alternateticks; |
|
#define LOG_FRAME_OUTPUT 0 |
|
|
|
void _Host_RunFrame (float time) |
|
{ |
|
MDLCACHE_COARSE_LOCK_(g_pMDLCache); |
|
static double host_remainder = 0.0f; |
|
double prevremainder; |
|
bool shouldrender; |
|
|
|
#if defined( RAD_TELEMETRY_ENABLED ) |
|
if( g_Telemetry.DemoTickEnd == ( uint32 )-1 ) |
|
{ |
|
Cbuf_AddText( "quit\n" ); |
|
} |
|
#endif |
|
|
|
int numticks; |
|
{ |
|
// Profile scope specific to the top of this function, protect from setjmp() problems |
|
VPROF( "_Host_RunFrame_Upto_MarkFrame" ); |
|
|
|
if ( host_checkheap ) |
|
{ |
|
#if defined(_WIN32) |
|
if ( _heapchk() != _HEAPOK ) |
|
{ |
|
Sys_Error( "_Host_RunFrame (top): _heapchk() != _HEAPOK\n" ); |
|
} |
|
#endif |
|
} |
|
|
|
// When playing back a VCR file, don't do host_sleep. That way, if it was recorded with |
|
// host_sleep on, it'll play back way Faster. |
|
if ( host_Sleep.GetInt() && VCRGetMode() != VCR_Playback ) |
|
{ |
|
Sys_Sleep( host_Sleep.GetInt() ); |
|
} |
|
|
|
// Slow down the playback? |
|
if ( g_iVCRPlaybackSleepInterval ) |
|
{ |
|
Sys_Sleep( g_iVCRPlaybackSleepInterval ); |
|
} |
|
|
|
MapReslistGenerator().RunFrame(); |
|
|
|
static int lastrunoffsecond = -1; |
|
|
|
if ( setjmp ( host_enddemo ) ) |
|
return; // demo finished. |
|
|
|
// decide the simulation time |
|
Host_AccumulateTime ( time ); |
|
_Host_SetGlobalTime(); |
|
|
|
shouldrender = !sv.IsDedicated(); |
|
|
|
// FIXME: Could track remainder as fractional ticks instead of msec |
|
prevremainder = host_remainder; |
|
if ( prevremainder < 0 ) |
|
prevremainder = 0; |
|
|
|
#if !defined(SWDS) |
|
if ( !demoplayer->IsPlaybackPaused() ) |
|
#endif |
|
{ |
|
host_remainder += host_frametime; |
|
} |
|
|
|
numticks = 0; // how many ticks we will simulate this frame |
|
if ( host_remainder >= host_state.interval_per_tick ) |
|
{ |
|
numticks = (int)( floor( host_remainder / host_state.interval_per_tick ) ); |
|
|
|
// round to nearest even ending tick in alternate ticks mode so the last |
|
// tick is always simulated prior to updating the network data |
|
// NOTE: alternate ticks only applies in SP!!! |
|
if ( Host_IsSinglePlayerGame() && |
|
sv_alternateticks.GetBool() ) |
|
{ |
|
int startTick = g_ServerGlobalVariables.tickcount; |
|
int endTick = startTick + numticks; |
|
endTick = AlignValue( endTick, 2 ); |
|
numticks = endTick - startTick; |
|
} |
|
|
|
host_remainder -= numticks * host_state.interval_per_tick; |
|
} |
|
|
|
host_nexttick = host_state.interval_per_tick - host_remainder; |
|
|
|
g_pMDLCache->MarkFrame(); |
|
} |
|
|
|
{ |
|
// Profile scope, protect from setjmp() problems |
|
VPROF( "_Host_RunFrame" ); |
|
tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "_Host_RunFrame" ); |
|
|
|
g_HostTimes.StartFrameSegment( FRAME_SEGMENT_CMD_EXECUTE ); |
|
|
|
// process console commands |
|
Cbuf_Execute (); |
|
|
|
// initialize networking for dedicated server after commandline & autoexec.cfg have been parsed |
|
if ( NET_IsDedicated() && !NET_IsMultiplayer() ) |
|
NET_SetMutiplayer( true ); |
|
|
|
g_HostTimes.EndFrameSegment( FRAME_SEGMENT_CMD_EXECUTE ); |
|
|
|
// Msg( "Running %i ticks (%f remainder) for frametime %f total %f tick %f delta %f\n", numticks, remainder, host_frametime, host_time ); |
|
g_ServerGlobalVariables.interpolation_amount = 0.0f; |
|
#ifndef SWDS |
|
g_ClientGlobalVariables.interpolation_amount = 0.0f; |
|
|
|
cl.insimulation = true; |
|
#endif |
|
|
|
host_frameticks = numticks; |
|
host_currentframetick = 0; |
|
|
|
#if !defined( SWDS ) |
|
// This is to make the tool do both sim + rendering on the initial frame |
|
// cl.IsActive changes in the loop below, as does scr_nextdrawtick |
|
// We're just caching off the state here so that we have a consistent return value |
|
// for enginetool->IsInGame the entire frame |
|
g_pEngineToolInternal->SetIsInGame( cl.IsActive() && ( scr_nextdrawtick == 0 ) ); |
|
#endif |
|
CJob *pGameJob = NULL; |
|
|
|
// threaded path only supported in listen server |
|
#ifndef SWDS |
|
if ( !IsEngineThreaded() ) |
|
#endif |
|
{ |
|
#ifndef SWDS |
|
if ( g_ClientDLL ) |
|
{ |
|
g_ClientDLL->IN_SetSampleTime(host_frametime); |
|
} |
|
g_ClientGlobalVariables.simTicksThisFrame = 1; |
|
#endif |
|
cl.m_tickRemainder = host_remainder; |
|
g_ServerGlobalVariables.simTicksThisFrame = 1; |
|
cl.SetFrameTime( host_frametime ); |
|
for ( int tick = 0; tick < numticks; tick++ ) |
|
{ |
|
// Emit an ETW event every simulation frame. |
|
ETWSimFrameMark( sv.IsDedicated() ); |
|
|
|
double now = Plat_FloatTime(); |
|
float jitter = now - host_idealtime; |
|
|
|
// Track jitter (delta between ideal time and actual tick execution time) |
|
host_jitterhistory[ host_jitterhistorypos ] = jitter; |
|
host_jitterhistorypos = ( host_jitterhistorypos + 1 ) % ARRAYSIZE(host_jitterhistory); |
|
|
|
// Very slowly decay "ideal" towards current wall clock unless delta is large |
|
if ( fabs( jitter ) > 1.0f ) |
|
{ |
|
host_idealtime = now; |
|
} |
|
else |
|
{ |
|
host_idealtime = 0.99 * host_idealtime + 0.01 * now; |
|
} |
|
|
|
// process any asynchronous network traffic (TCP), set net_time |
|
NET_RunFrame( now ); |
|
|
|
// Only send updates on final tick so we don't re-encode network data multiple times per frame unnecessarily |
|
bool bFinalTick = ( tick == (numticks - 1) ); |
|
|
|
// initialize networking for dedicated server after commandline & autoexec.cfg have been parsed |
|
if ( NET_IsDedicated() && !NET_IsMultiplayer() ) |
|
NET_SetMutiplayer( true ); |
|
|
|
g_ServerGlobalVariables.tickcount = sv.m_nTickCount; |
|
// NOTE: Do we want do this at start or end of this loop? |
|
++host_tickcount; |
|
++host_currentframetick; |
|
#ifndef SWDS |
|
g_ClientGlobalVariables.tickcount = cl.GetClientTickCount(); |
|
|
|
// Make sure state is correct |
|
CL_CheckClientState(); |
|
#endif |
|
//------------------- |
|
// input processing |
|
//------------------- |
|
_Host_RunFrame_Input( prevremainder, bFinalTick ); |
|
prevremainder = 0; |
|
//------------------- |
|
// |
|
// server operations |
|
// |
|
//------------------- |
|
|
|
_Host_RunFrame_Server( bFinalTick ); |
|
|
|
// Additional networking ops for SPLITPACKET stuff (99.9% of the time this will be an empty list of work) |
|
NET_SendQueuedPackets(); |
|
//------------------- |
|
// |
|
// client operations |
|
// |
|
//------------------- |
|
#ifndef SWDS |
|
if ( !sv.IsDedicated() ) |
|
{ |
|
_Host_RunFrame_Client( bFinalTick ); |
|
} |
|
|
|
toolframework->Think( bFinalTick ); |
|
#endif |
|
|
|
host_idealtime += host_state.interval_per_tick; |
|
} |
|
|
|
// run HLTV if active |
|
if ( hltv ) |
|
{ |
|
tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "hltv->RunFrame()" ); |
|
hltv->RunFrame(); |
|
} |
|
|
|
if ( hltvtest ) |
|
{ |
|
tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "hltvtest->RunFrame()" ); |
|
hltvtest->RunFrame(); |
|
} |
|
|
|
#if defined( REPLAY_ENABLED ) |
|
// run replay if active |
|
if ( replay ) |
|
{ |
|
replay->RunFrame(); |
|
} |
|
|
|
// Update server-side replay history manager |
|
if ( sv.IsDedicated() && g_pServerReplayContext && g_pServerReplayContext->IsInitialized() ) |
|
{ |
|
g_pServerReplayContext->Think(); |
|
} |
|
#endif |
|
|
|
#ifndef SWDS |
|
// This is a hack to let timedemo pull messages from the queue faster than every 15 msec |
|
// Also when demoplayer is skipping packets to a certain tick we should process the queue |
|
// as quickly as we can. |
|
if ( numticks == 0 && ( demoplayer->IsPlayingTimeDemo() || demoplayer->IsSkipping() ) ) |
|
{ |
|
_Host_RunFrame_Client( true ); |
|
} |
|
|
|
if ( !sv.IsDedicated() ) |
|
{ |
|
tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "Host_SetClientInSimulation" ); |
|
|
|
// This causes cl.gettime() to return the true clock being used for rendering (tickcount * rate + remainder) |
|
Host_SetClientInSimulation( false ); |
|
// Now allow for interpolation on client |
|
g_ClientGlobalVariables.interpolation_amount = ( cl.m_tickRemainder / host_state.interval_per_tick ); |
|
|
|
#if defined( REPLAY_ENABLED ) |
|
// Update client-side replay history manager - called here since interpolation_amount is set |
|
if ( g_pClientReplayContext && g_pClientReplayContext->IsInitialized() ) |
|
{ |
|
g_pClientReplayContext->Think(); |
|
} |
|
#endif |
|
|
|
//------------------- |
|
// Run prediction if it hasn't been run yet |
|
//------------------- |
|
// If we haven't predicted/simulated the player (multiplayer with prediction enabled and |
|
// not a listen server with zero frame lag, then go ahead and predict now |
|
CL_RunPrediction( PREDICTION_NORMAL ); |
|
|
|
CL_ApplyAddAngle(); |
|
|
|
// The mouse is always simulated for the current frame's time |
|
// This makes updates smooth in every case |
|
// continuous controllers affecting the view are also simulated this way |
|
// but they have a cap applied by IN_SetSampleTime() so they are not also |
|
// simulated during input gathering |
|
CL_ExtraMouseUpdate( g_ClientGlobalVariables.frametime ); |
|
} |
|
#endif |
|
#if defined( REPLAY_ENABLED ) |
|
// Let the replay system think |
|
if ( g_pReplay ) |
|
{ |
|
g_pReplay->Think(); |
|
} |
|
#endif |
|
#if LOG_FRAME_OUTPUT |
|
if ( !cl.IsPaused() || !sv.IsPaused() ) |
|
{ |
|
Msg("=============SIM: CLIENT %5d + %d, SERVER %5d + %d\t REM: %.2f\n", cl.GetClientTickCount(), numticks, sv.m_nTickCount, numticks, host_remainder*1000.0f ); |
|
} |
|
#endif |
|
} |
|
#ifndef SWDS |
|
else |
|
{ |
|
static int numticks_last_frame = 0; |
|
static float host_remainder_last_frame = 0, prev_remainder_last_frame = 0, last_frame_time = 0; |
|
|
|
int clientticks; |
|
int serverticks; |
|
|
|
clientticks = numticks_last_frame; |
|
cl.m_tickRemainder = host_remainder_last_frame; |
|
cl.SetFrameTime( last_frame_time ); |
|
if ( g_ClientDLL ) |
|
{ |
|
g_ClientDLL->IN_SetSampleTime(last_frame_time); |
|
} |
|
|
|
last_frame_time = host_frametime; |
|
|
|
serverticks = numticks; |
|
g_ClientGlobalVariables.simTicksThisFrame = clientticks; |
|
g_ServerGlobalVariables.simTicksThisFrame = serverticks; |
|
g_ServerGlobalVariables.tickcount = sv.m_nTickCount; |
|
|
|
// THREADED: Run Client |
|
// ------------------- |
|
for ( int tick = 0; tick < clientticks; tick++ ) |
|
{ |
|
// process any asynchronous network traffic (TCP), set net_time |
|
NET_RunFrame( Plat_FloatTime() ); |
|
|
|
// Only send updates on final tick so we don't re-encode network data multiple times per frame unnecessarily |
|
bool bFinalTick = ( tick == (clientticks - 1) ); |
|
|
|
// initialize networking for dedicated server after commandline & autoexec.cfg have been parsed |
|
if ( NET_IsDedicated() && !NET_IsMultiplayer() ) |
|
NET_SetMutiplayer( true ); |
|
g_ClientGlobalVariables.tickcount = cl.GetClientTickCount(); |
|
|
|
// Make sure state is correct |
|
CL_CheckClientState(); |
|
// Additional networking ops for SPLITPACKET stuff (99.9% of the time this will be an empty list of work) |
|
NET_SendQueuedPackets(); |
|
//------------------- |
|
// |
|
// client operations |
|
// |
|
//------------------- |
|
if ( !sv.IsDedicated() ) |
|
{ |
|
_Host_RunFrame_Client( bFinalTick ); |
|
} |
|
toolframework->Think( bFinalTick ); |
|
} |
|
// This is a hack to let timedemo pull messages from the queue faster than every 15 msec |
|
// Also when demoplayer is skipping packets to a certain tick we should process the queue |
|
// as quickly as we can. |
|
if ( clientticks == 0 && ( demoplayer->IsPlayingTimeDemo() || demoplayer->IsSkipping() ) ) |
|
{ |
|
_Host_RunFrame_Client( true ); |
|
} |
|
|
|
// This causes cl.gettime() to return the true clock being used for rendering (tickcount * rate + remainder) |
|
Host_SetClientInSimulation( false ); |
|
// Now allow for interpolation on client |
|
g_ClientGlobalVariables.interpolation_amount = ( cl.m_tickRemainder / host_state.interval_per_tick ); |
|
|
|
//------------------- |
|
// Run prediction if it hasn't been run yet |
|
//------------------- |
|
// If we haven't predicted/simulated the player (multiplayer with prediction enabled and |
|
// not a listen server with zero frame lag, then go ahead and predict now |
|
CL_RunPrediction( PREDICTION_NORMAL ); |
|
|
|
CL_ApplyAddAngle(); |
|
|
|
Host_SetClientInSimulation( true ); |
|
|
|
// THREADED: Run Input |
|
// ------------------- |
|
int saveTick = g_ClientGlobalVariables.tickcount; |
|
|
|
for ( int tick = 0; tick < serverticks; tick++ ) |
|
{ |
|
// NOTE: Do we want do this at start or end of this loop? |
|
++host_tickcount; |
|
++host_currentframetick; |
|
g_ClientGlobalVariables.tickcount = host_tickcount; |
|
bool bFinalTick = tick==(serverticks-1) ? true : false; |
|
_Host_RunFrame_Input( prevremainder, bFinalTick ); |
|
prevremainder = 0; |
|
// process any asynchronous network traffic (TCP), set net_time |
|
NET_RunFrame( Plat_FloatTime() ); |
|
} |
|
|
|
Host_SetClientInSimulation( false ); |
|
|
|
// The mouse is always simulated for the current frame's time |
|
// This makes updates smooth in every case |
|
// continuous controllers affecting the view are also simulated this way |
|
// but they have a cap applied by IN_SetSampleTime() so they are not also |
|
// simulated during input gathering |
|
CL_ExtraMouseUpdate( g_ClientGlobalVariables.frametime ); |
|
|
|
g_ClientGlobalVariables.tickcount = saveTick; |
|
numticks_last_frame = numticks; |
|
host_remainder_last_frame = host_remainder; |
|
|
|
// THREADED: Run Server |
|
// ------------------- |
|
// set net_time once before running the server |
|
NET_SetTime( Plat_FloatTime() ); |
|
pGameJob = new CFunctorJob( CreateFunctor( _Host_RunFrame_Server_Async, serverticks ) ); |
|
if ( IsX360() ) |
|
{ |
|
pGameJob->SetServiceThread( g_nServerThread ); |
|
} |
|
g_pThreadPool->AddJob( pGameJob ); |
|
#if LOG_FRAME_OUTPUT |
|
if ( !cl.IsPaused() || !sv.IsPaused() ) |
|
{ |
|
Msg("=============SIM: CLIENT %5d + %d, SERVER %5d + %d\t REM: %.2f\n", cl.GetClientTickCount(), clientticks, sv.m_nTickCount, serverticks, host_remainder*1000.0f ); |
|
} |
|
#endif |
|
} |
|
#endif // SWDS |
|
|
|
g_Log.RunFrame(); |
|
|
|
if ( shouldrender ) |
|
{ |
|
#if LOG_FRAME_OUTPUT |
|
if ( !cl.IsPaused() || !sv.IsPaused() ) |
|
{ |
|
static float lastFrameTime = 0; |
|
float frametime = g_ClientGlobalVariables.curtime - lastFrameTime; |
|
Msg("RENDER AT: %6.4f: %.2fms [%.2fms implicit] frametime\n", |
|
g_ClientGlobalVariables.curtime, g_ClientGlobalVariables.frametime*1000.0f, frametime * 1000.0f); |
|
lastFrameTime = g_ClientGlobalVariables.curtime; |
|
} |
|
#endif |
|
//------------------- |
|
// rendering |
|
//------------------- |
|
_Host_RunFrame_Render(); |
|
|
|
//------------------- |
|
// sound |
|
//------------------- |
|
_Host_RunFrame_Sound(); |
|
|
|
if ( g_bVCRSingleStep ) |
|
{ |
|
VCR_EnterPausedState(); |
|
} |
|
} |
|
else |
|
{ |
|
tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "modelloader->UpdateDynamicModels" ); |
|
VPROF( "UpdateDynamicModels" ); |
|
CMDLCacheCriticalSection critsec( g_pMDLCache ); |
|
modelloader->UpdateDynamicModels(); |
|
} |
|
|
|
//------------------- |
|
// simulation |
|
//------------------- |
|
g_HostTimes.MarkSwapTime( ); |
|
#ifndef SWDS |
|
if ( !sv.IsDedicated() ) |
|
{ |
|
VPROF( "_Host_RunFrame - ClientDLL_Update" ); |
|
tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "_Host_RunFrame - ClientDLL_Update" ); |
|
|
|
// Client-side simulation |
|
g_HostTimes.StartFrameSegment( FRAME_SEGMENT_CLDLL ); |
|
|
|
ClientDLL_Update(); |
|
|
|
g_HostTimes.EndFrameSegment( FRAME_SEGMENT_CLDLL ); |
|
} |
|
#endif |
|
if ( pGameJob ) |
|
{ |
|
{ |
|
VPROF_BUDGET( "WaitForAsyncServer", "AsyncServer" ); |
|
if ( Host_IsSinglePlayerGame() ) |
|
{ |
|
// This should change to a YieldWait if the server starts wanting to parallel process. If |
|
// so, will need some route for the server to queue up work it wants to execute outside |
|
// its frame, otherwise some of it would be performed during the yield. Right now |
|
// need to wait for server so we don't stall on queued AI operations (toml 7/3/2007) |
|
pGameJob->ExecuteAndRelease(); |
|
} |
|
else |
|
{ |
|
pGameJob->WaitForFinishAndRelease(); |
|
} |
|
} |
|
SV_FrameExecuteThreadDeferred(); |
|
} |
|
|
|
//------------------- |
|
// time |
|
//------------------- |
|
|
|
Host_Speeds(); |
|
|
|
Host_UpdateMapList(); |
|
|
|
host_framecount++; |
|
#if !defined(SWDS) |
|
if ( !demoplayer->IsPlaybackPaused() ) |
|
#endif |
|
{ |
|
host_time = host_tickcount * host_state.interval_per_tick + cl.m_tickRemainder; |
|
} |
|
|
|
Host_PostFrameRate( host_frametime ); |
|
|
|
if ( host_checkheap ) |
|
{ |
|
#ifdef _WIN32 |
|
if ( _heapchk() != _HEAPOK ) |
|
{ |
|
Sys_Error( "_Host_RunFrame (bottom): _heapchk() != _HEAPOK\n" ); |
|
} |
|
#endif |
|
} |
|
|
|
Host_CheckDumpMemoryStats(); |
|
|
|
GetTestScriptMgr()->CheckPoint( "frame_end" ); |
|
} // Profile scope, protect from setjmp() problems |
|
|
|
Host_ShowIPCCallCount(); |
|
} |
|
/* |
|
============================== |
|
Host_Frame |
|
|
|
============================== |
|
*/ |
|
void Host_RunFrame( float time ) |
|
{ |
|
static double timetotal = 0; |
|
static int timecount = 0; |
|
static double timestart = 0; |
|
|
|
#ifndef SWDS |
|
if ( !scr_drawloading && sv.IsActive() && cl.IsActive() && !sv.m_bLoadgame) |
|
{ |
|
switch ( host_thread_mode.GetInt() ) |
|
{ |
|
case HTM_DISABLED: g_bThreadedEngine = false; break; |
|
case HTM_DEFAULT: g_bThreadedEngine = ( g_pThreadPool->NumThreads() > 0 ); break; |
|
case HTM_FORCED: g_bThreadedEngine = true; break; |
|
} |
|
} |
|
else |
|
#endif |
|
{ |
|
g_bThreadedEngine = false; |
|
} |
|
|
|
if ( !host_profile.GetBool() ) |
|
{ |
|
_Host_RunFrame( time ); |
|
return; |
|
} |
|
|
|
double time1 = Sys_FloatTime(); |
|
|
|
_Host_RunFrame( time ); |
|
|
|
double time2 = Sys_FloatTime(); |
|
|
|
timetotal += time2 - time1; // time in seconds |
|
timecount++; |
|
|
|
if (timecount < 1000) |
|
return; |
|
|
|
float fps = 1000/(time2 - timestart); |
|
|
|
ConMsg ("host_profile : %i clients, %.1f msec, %.1f fps\n", |
|
sv.GetNumClients(), timetotal, fps ); |
|
|
|
timecount = 0; |
|
timetotal = 0; |
|
timestart = time2; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// A more secure means of enforcing low violence. |
|
//----------------------------------------------------------------------------- |
|
bool IsLowViolence_Secure() |
|
{ |
|
#ifndef DEDICATED |
|
if ( !IsX360() && Steam3Client().SteamApps() ) |
|
{ |
|
// let Steam determine current violence settings |
|
return Steam3Client().SteamApps()->BIsLowViolence(); |
|
} |
|
else if ( IsX360() ) |
|
{ |
|
// Low violence for the 360 is enabled by the presence of a file. |
|
if ( g_pFileSystem->FileExists( "cfg/violence.cfg" ) ) |
|
{ |
|
return true; |
|
} |
|
|
|
return false; |
|
} |
|
#endif |
|
|
|
return false; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// If "User Token 2" exists in HKEY_CURRENT_USER/Software/Valve/Half-Life/Settings |
|
// then we disable gore. Obviously not very secure. |
|
//----------------------------------------------------------------------------- |
|
bool IsLowViolence_Registry() |
|
{ |
|
char szSubKey[128]; |
|
int nBufferLen; |
|
char szBuffer[128]; |
|
bool bReducedGore = false; |
|
|
|
memset( szBuffer, 0, 128 ); |
|
|
|
char const *appname = "Source"; |
|
Q_snprintf(szSubKey, sizeof( szSubKey ), "Software\\Valve\\%s\\Settings", appname ); |
|
|
|
nBufferLen = 127; |
|
Q_strncpy( szBuffer, "", sizeof( szBuffer ) ); |
|
|
|
Sys_GetRegKeyValue( szSubKey, "User Token 2", szBuffer, nBufferLen, szBuffer ); |
|
|
|
// Gore reduction active? |
|
bReducedGore = ( Q_strlen( szBuffer ) > 0 ) ? true : false; |
|
if ( !bReducedGore ) |
|
{ |
|
Sys_GetRegKeyValue( szSubKey, "User Token 3", szBuffer, nBufferLen, szBuffer ); |
|
|
|
bReducedGore = ( Q_strlen( szBuffer ) > 0 ) ? true : false; |
|
} |
|
|
|
char gamedir[MAX_OSPATH]; |
|
Q_FileBase( com_gamedir, gamedir, sizeof( gamedir ) ); |
|
|
|
// also check mod specific directories for LV changes |
|
Q_snprintf(szSubKey, sizeof( szSubKey ), "Software\\Valve\\%s\\%s\\Settings", appname, gamedir ); |
|
|
|
nBufferLen = 127; |
|
Q_strncpy( szBuffer, "", sizeof( szBuffer ) ); |
|
|
|
Sys_GetRegKeyValue( szSubKey, "User Token 2", szBuffer, nBufferLen, szBuffer ); |
|
if ( Q_strlen( szBuffer ) > 0 ) |
|
{ |
|
bReducedGore = true; |
|
} |
|
|
|
Sys_GetRegKeyValue( szSubKey, "User Token 3", szBuffer, nBufferLen, szBuffer ); |
|
if ( Q_strlen( szBuffer ) > 0 ) |
|
{ |
|
bReducedGore = true; |
|
} |
|
|
|
return bReducedGore; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
//----------------------------------------------------------------------------- |
|
void Host_CheckGore( void ) |
|
{ |
|
bool bLowViolenceRegistry = false; |
|
bool bLowViolenceSecure = false; |
|
|
|
// |
|
// First check the old method of enabling low violence via the registry. |
|
// |
|
#ifdef WIN32 |
|
bLowViolenceRegistry = IsLowViolence_Registry(); |
|
#endif |
|
// |
|
// Next check the new method of enabling low violence based on country of purchase |
|
// and other means that are inaccessible by the user. |
|
// |
|
if ( GetCurrentMod() && Q_stricmp( GetCurrentMod(), "cstrike" ) != 0 ) |
|
bLowViolenceSecure = IsLowViolence_Secure(); |
|
|
|
// |
|
// If either method says "yes" to low violence, we're in low violence mode. |
|
// |
|
if ( bLowViolenceRegistry || bLowViolenceSecure ) |
|
{ |
|
g_bLowViolence = true; |
|
|
|
if ( bLowViolenceRegistry ) |
|
{ |
|
violence_hblood.SetValue( 0 ); |
|
violence_hgibs.SetValue( 0 ); |
|
violence_ablood.SetValue( 0 ); |
|
violence_agibs.SetValue( 0 ); |
|
} |
|
} |
|
else |
|
{ |
|
g_bLowViolence = false; |
|
} |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void Host_InitProcessor( void ) |
|
{ |
|
const CPUInformation& pi = *GetCPUInformation(); |
|
|
|
// Compute Frequency in Mhz: |
|
char* szFrequencyDenomination = "Mhz"; |
|
double fFrequency = pi.m_Speed / 1000000.0; |
|
|
|
// Adjust to Ghz if nessecary: |
|
if( fFrequency > 1000.0 ) |
|
{ |
|
fFrequency /= 1000.0; |
|
szFrequencyDenomination = "Ghz"; |
|
} |
|
|
|
char szFeatureString[256]; |
|
Q_strncpy( szFeatureString, pi.m_szProcessorID, sizeof( szFeatureString ) ); |
|
Q_strncat( szFeatureString, " ", sizeof( szFeatureString ), COPY_ALL_CHARACTERS ); |
|
|
|
if( pi.m_bSSE ) |
|
{ |
|
if( MathLib_SSEEnabled() ) Q_strncat(szFeatureString, "SSE ", sizeof( szFeatureString ), COPY_ALL_CHARACTERS ); |
|
else Q_strncat(szFeatureString, "(SSE) ", sizeof( szFeatureString ), COPY_ALL_CHARACTERS ); |
|
} |
|
|
|
if( pi.m_bSSE2 ) |
|
{ |
|
if( MathLib_SSE2Enabled() ) Q_strncat(szFeatureString, "SSE2 ", sizeof( szFeatureString ), COPY_ALL_CHARACTERS ); |
|
else Q_strncat(szFeatureString, "(SSE2) ", sizeof( szFeatureString ), COPY_ALL_CHARACTERS ); |
|
} |
|
|
|
if( pi.m_bMMX ) |
|
{ |
|
if( MathLib_MMXEnabled() ) Q_strncat(szFeatureString, "MMX ", sizeof( szFeatureString ), COPY_ALL_CHARACTERS ); |
|
else Q_strncat(szFeatureString, "(MMX) ", sizeof( szFeatureString ), COPY_ALL_CHARACTERS ); |
|
} |
|
|
|
if( pi.m_b3DNow ) |
|
{ |
|
if( MathLib_3DNowEnabled() ) Q_strncat(szFeatureString, "3DNow ", sizeof( szFeatureString ), COPY_ALL_CHARACTERS ); |
|
else Q_strncat(szFeatureString, "(3DNow) ", sizeof( szFeatureString ), COPY_ALL_CHARACTERS ); |
|
} |
|
|
|
if( pi.m_bRDTSC ) Q_strncat(szFeatureString, "RDTSC ", sizeof( szFeatureString ), COPY_ALL_CHARACTERS ); |
|
if( pi.m_bCMOV ) Q_strncat(szFeatureString, "CMOV ", sizeof( szFeatureString ), COPY_ALL_CHARACTERS ); |
|
if( pi.m_bFCMOV ) Q_strncat(szFeatureString, "FCMOV ", sizeof( szFeatureString ), COPY_ALL_CHARACTERS ); |
|
|
|
// Remove the trailing space. There will always be one. |
|
szFeatureString[Q_strlen(szFeatureString)-1] = '\0'; |
|
|
|
// Dump CPU information: |
|
if( pi.m_nLogicalProcessors == 1 ) |
|
{ |
|
ConDMsg( "1 CPU, Frequency: %.01f %s, Features: %s\n", |
|
fFrequency, |
|
szFrequencyDenomination, |
|
szFeatureString |
|
); |
|
} |
|
else |
|
{ |
|
char buffer[256] = ""; |
|
if( pi.m_nPhysicalProcessors != pi.m_nLogicalProcessors ) |
|
{ |
|
Q_snprintf(buffer, sizeof( buffer ), " (%i physical)", (int) pi.m_nPhysicalProcessors ); |
|
} |
|
|
|
ConDMsg( "%i CPUs%s, Frequency: %.01f %s, Features: %s\n", |
|
(int)pi.m_nLogicalProcessors, |
|
buffer, |
|
fFrequency, |
|
szFrequencyDenomination, |
|
szFeatureString |
|
); |
|
} |
|
|
|
#if defined( _WIN32 ) |
|
if ( s_bInitPME ) |
|
{ |
|
// Initialize the performance monitoring events code. |
|
InitPME(); |
|
} |
|
#endif |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Specifically used by the model loading code to mark models |
|
// touched by the current map |
|
//----------------------------------------------------------------------------- |
|
int Host_GetServerCount( void ) |
|
{ |
|
if (cl.m_nSignonState >= SIGNONSTATE_NEW) |
|
{ |
|
// the server count cannot be relied on until the server info message |
|
// the new state guarantees its validity |
|
return cl.m_nServerCount; |
|
} |
|
else if (sv.m_State >= ss_loading) |
|
{ |
|
return sv.GetSpawnCount(); |
|
} |
|
|
|
// this is unfortunate, and happens, but the caller is too early in the protocol or a demo |
|
// cannot identify the correct server count |
|
// return the same count that demo will use |
|
return gHostSpawnCount; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void Host_PostInit() |
|
{ |
|
if ( serverGameDLL ) |
|
{ |
|
serverGameDLL->PostInit(); |
|
} |
|
|
|
#if !defined( SWDS ) |
|
if ( g_ClientDLL ) |
|
{ |
|
g_ClientDLL->PostInit(); |
|
} |
|
|
|
toolframework->PostInit(); |
|
|
|
if ( !sv.IsDedicated() ) |
|
{ |
|
// vgui needs other systems to finalize |
|
EngineVGui()->PostInit(); |
|
} |
|
|
|
#if defined( LINUX ) |
|
const char en_US[] = "en_US.UTF-8"; |
|
const char *CurrentLocale = setlocale( LC_ALL, NULL ); |
|
if ( !CurrentLocale ) |
|
CurrentLocale = "c"; |
|
if ( Q_stricmp( CurrentLocale, en_US ) ) |
|
{ |
|
char MessageText[ 512 ]; |
|
|
|
V_sprintf_safe( MessageText, "SetLocale('%s') failed. Using '%s'.\n" |
|
"You may have limited glyph support.\n" |
|
"Please install '%s' locale.", |
|
en_US, CurrentLocale, en_US ); |
|
SDL_ShowSimpleMessageBox( 0, "Warning", MessageText, GetAssertDialogParent() ); |
|
} |
|
#endif // LINUX |
|
|
|
#endif |
|
} |
|
|
|
void HLTV_Init() |
|
{ |
|
Assert ( hltv == NULL ); |
|
Assert ( hltvtest == NULL ); |
|
} |
|
|
|
void HLTV_Shutdown() |
|
{ |
|
if ( hltv ) |
|
{ |
|
hltv->Shutdown(); |
|
delete hltv; |
|
hltv = NULL; |
|
} |
|
|
|
if ( hltvtest ) |
|
{ |
|
delete hltvtest; |
|
hltvtest = NULL; |
|
} |
|
} |
|
|
|
// Check with steam to see if the requested file (requires full path) is a valid, signed binary |
|
bool DLL_LOCAL Host_IsValidSignature( const char *pFilename, bool bAllowUnknown ) |
|
{ |
|
#if defined( SWDS ) || defined(_X360) |
|
return true; |
|
#else |
|
if ( sv.IsDedicated() || IsOSX() || IsLinux() ) |
|
{ |
|
// dedicated servers and Mac and Linux binaries don't check signatures |
|
return true; |
|
} |
|
else |
|
{ |
|
if ( Steam3Client().SteamUtils() ) |
|
{ |
|
SteamAPICall_t hAPICall = Steam3Client().SteamUtils()->CheckFileSignature( pFilename ); |
|
bool bAPICallFailed = true; |
|
while ( !Steam3Client().SteamUtils()->IsAPICallCompleted(hAPICall, &bAPICallFailed) ) |
|
{ |
|
SteamAPI_RunCallbacks(); |
|
ThreadSleep( 1 ); |
|
} |
|
|
|
if( bAPICallFailed ) |
|
{ |
|
Warning( "CheckFileSignature API call on %s failed", pFilename ); |
|
} |
|
else |
|
{ |
|
CheckFileSignature_t result; |
|
Steam3Client().SteamUtils()->GetAPICallResult( hAPICall, &result, sizeof(result), result.k_iCallback, &bAPICallFailed ); |
|
if( bAPICallFailed ) |
|
{ |
|
Warning( "CheckFileSignature API call on %s failed\n", pFilename ); |
|
} |
|
else |
|
{ |
|
if( result.m_eCheckFileSignature == k_ECheckFileSignatureValidSignature || result.m_eCheckFileSignature == k_ECheckFileSignatureNoSignaturesFoundForThisApp ) |
|
return true; |
|
if ( bAllowUnknown && result.m_eCheckFileSignature == k_ECheckFileSignatureNoSignaturesFoundForThisFile ) |
|
return true; |
|
Warning( "No valid signature found for %s\n", pFilename ); |
|
} |
|
} |
|
} |
|
} |
|
|
|
return false; |
|
#endif // SWDS |
|
} |
|
|
|
|
|
// Ask steam if it is ok to load this DLL. Unsigned DLLs should not be loaded unless |
|
// the client is running -insecure (testing a plugin for example) |
|
// This keeps legitimate users with modified binaries from getting VAC banned because of them |
|
bool DLL_LOCAL Host_AllowLoadModule( const char *pFilename, const char *pPathID, bool bAllowUnknown, bool bIsServerOnly /* = false */ ) |
|
{ |
|
#if defined( SWDS ) || defined ( OSX ) || defined( LINUX ) |
|
// dedicated servers and Mac and Linux binaries don't check signatures |
|
return true; |
|
#else |
|
|
|
if ( sv.IsDedicated() || bIsServerOnly ) |
|
{ |
|
// dedicated servers and Mac binaries don't check signatures |
|
return true; |
|
} |
|
else |
|
{ |
|
// check signature |
|
bool bSignatureIsValid = false; |
|
|
|
// Do we need to do the signature checking? If secure servers are disabled, just skip it. |
|
if ( Host_IsSecureServerAllowed() ) |
|
{ |
|
if ( Steam3Client().SteamUtils() ) |
|
{ |
|
char szDllname[512]; |
|
|
|
V_strncpy( szDllname, pFilename, sizeof(szDllname) ); |
|
V_SetExtension( szDllname, g_pModuleExtension, sizeof(szDllname) ); |
|
if ( pPathID ) |
|
{ |
|
char szFullPath[ 512 ]; |
|
const char *pFullPath = g_pFileSystem->RelativePathToFullPath( szDllname, pPathID, szFullPath, sizeof(szFullPath) ); |
|
if ( !pFullPath ) |
|
{ |
|
Warning("Can't find %s on disk\n", szDllname ); |
|
bSignatureIsValid = false; |
|
} |
|
else |
|
{ |
|
bSignatureIsValid = Host_IsValidSignature( pFullPath, bAllowUnknown ); |
|
} |
|
} |
|
else |
|
{ |
|
bSignatureIsValid = Host_IsValidSignature( szDllname, bAllowUnknown ); |
|
} |
|
} |
|
else |
|
{ |
|
Warning("Steam is not active, running in -insecure mode.\n"); |
|
Host_DisallowSecureServers(); |
|
} |
|
} |
|
else |
|
{ |
|
Warning("Loading unsigned module %s\nAccess to secure servers is disabled.\n", pFilename ); |
|
return true; |
|
} |
|
|
|
return bSignatureIsValid; |
|
} |
|
#endif // SWDS |
|
} |
|
|
|
bool DLL_LOCAL Host_IsSecureServerAllowed() |
|
{ |
|
if ( CommandLine()->FindParm( "-insecure" ) || CommandLine()->FindParm( "-textmode" ) ) |
|
g_bAllowSecureServers = false; |
|
|
|
return g_bAllowSecureServers; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void Host_Init( bool bDedicated ) |
|
{ |
|
realtime = 0; |
|
host_idealtime = 0; |
|
|
|
#if defined(_WIN32) |
|
if ( CommandLine()->FindParm( "-pme" ) ) |
|
{ |
|
s_bInitPME = true; |
|
} |
|
#endif |
|
|
|
if ( Host_IsSecureServerAllowed() ) |
|
{ |
|
// double check the engine's signature in case it was hooked/modified |
|
if ( !Host_AllowLoadModule( "engine" DLL_EXT_STRING, "EXECUTABLE_PATH", false, bDedicated ) ) |
|
{ |
|
// not supposed to load this but we will anyway |
|
Host_DisallowSecureServers(); |
|
} |
|
} |
|
|
|
ThreadPoolStartParams_t startParams; |
|
if ( IsX360() ) |
|
{ |
|
// 360 overrides defaults, 2 computation threads distributed to core 1 and 2 |
|
startParams.nThreads = 2; |
|
startParams.nStackSize = 256*1024; |
|
startParams.fDistribute = TRS_TRUE; |
|
startParams.bUseAffinityTable = true; |
|
startParams.iAffinityTable[0] = XBOX_PROCESSOR_2; |
|
startParams.iAffinityTable[1] = XBOX_PROCESSOR_4; |
|
ThreadSetAffinity( NULL, 1 ); |
|
} |
|
if ( g_pThreadPool ) |
|
g_pThreadPool->Start( startParams, "CmpJob" ); |
|
|
|
// From const.h, the loaded game .dll will give us the correct value which is transmitted to the client |
|
host_state.interval_per_tick = DEFAULT_TICK_INTERVAL; |
|
|
|
InstallBitBufErrorHandler(); |
|
|
|
TRACEINIT( Memory_Init(), Memory_Shutdown() ); |
|
|
|
TRACEINIT( Con_Init(), Con_Shutdown() ); |
|
|
|
TRACEINIT( Cbuf_Init(), Cbuf_Shutdown() ); |
|
|
|
TRACEINIT( Cmd_Init(), Cmd_Shutdown() ); |
|
|
|
TRACEINIT( g_pCVar->Init(), g_pCVar->Shutdown() ); // So we can list cvars with "cvarlst" |
|
|
|
#ifndef SWDS |
|
TRACEINIT( V_Init(), V_Shutdown() ); |
|
#endif |
|
|
|
TRACEINIT( COM_Init(), COM_Shutdown() ); |
|
|
|
|
|
#ifndef SWDS |
|
TRACEINIT( saverestore->Init(), saverestore->Shutdown() ); |
|
#endif |
|
|
|
TRACEINIT( Filter_Init(), Filter_Shutdown() ); |
|
|
|
#ifndef SWDS |
|
TRACEINIT( Key_Init(), Key_Shutdown() ); |
|
#endif |
|
|
|
// Check for special -dev flag |
|
if ( CommandLine()->FindParm( "-dev" ) || ( CommandLine()->FindParm( "-allowdebug" ) && !CommandLine()->FindParm( "-nodev" ) ) ) |
|
{ |
|
sv_cheats.SetValue( 1 ); |
|
developer.SetValue( 1 ); |
|
} |
|
#ifdef _DEBUG |
|
developer.SetValue( 1 ); |
|
#endif |
|
|
|
// Should have read info from steam.inf by now. |
|
Assert( GetSteamInfIDVersionInfo().AppID != k_uAppIdInvalid ); |
|
|
|
if ( CommandLine()->FindParm( "-nocrashdialog" ) ) |
|
{ |
|
// stop the various windows error message boxes from showing up (used by the auto-builder so it doesn't block on error) |
|
Sys_NoCrashDialog(); |
|
} |
|
|
|
TRACEINIT( NET_Init( bDedicated ), NET_Shutdown() ); |
|
|
|
TRACEINIT( g_GameEventManager.Init(), g_GameEventManager.Shutdown() ); |
|
|
|
TRACEINIT( sv.Init( bDedicated ), sv.Shutdown() ); |
|
|
|
#if defined( REPLAY_ENABLED ) |
|
if ( Replay_IsSupportedModAndPlatform() ) |
|
{ |
|
TRACEINIT( ReplaySystem_Init( bDedicated ), ReplaySystem_Shutdown() ); |
|
} |
|
#endif |
|
|
|
if ( !CommandLine()->FindParm( "-nogamedll" ) ) |
|
{ |
|
SV_InitGameDLL(); |
|
} |
|
|
|
TRACEINIT( g_Log.Init(), g_Log.Shutdown() ); |
|
|
|
TRACEINIT( HLTV_Init(), HLTV_Shutdown() ); |
|
|
|
ConDMsg( "Heap: %5.2f Mb\n", host_parms.memsize/(1024.0f*1024.0f) ); |
|
|
|
#if !defined( SWDS ) |
|
if ( !bDedicated ) |
|
{ |
|
TRACEINIT( CL_Init(), CL_Shutdown() ); |
|
|
|
// NOTE: This depends on the mod search path being set up |
|
TRACEINIT( InitMaterialSystem(), ShutdownMaterialSystem() ); |
|
|
|
TRACEINIT( modelloader->Init(), modelloader->Shutdown() ); |
|
|
|
TRACEINIT( StaticPropMgr()->Init(), StaticPropMgr()->Shutdown() ); |
|
|
|
TRACEINIT( InitStudioRender(), ShutdownStudioRender() ); |
|
|
|
//startup vgui |
|
TRACEINIT( EngineVGui()->Init(), EngineVGui()->Shutdown() ); |
|
|
|
TRACEINIT( TextMessageInit(), TextMessageShutdown() ); |
|
|
|
TRACEINIT( ClientDLL_Init(), ClientDLL_Shutdown() ); |
|
|
|
TRACEINIT( SCR_Init(), SCR_Shutdown() ); |
|
|
|
TRACEINIT( R_Init(), R_Shutdown() ); |
|
|
|
TRACEINIT( Decal_Init(), Decal_Shutdown() ); |
|
|
|
// hookup interfaces |
|
EngineVGui()->Connect(); |
|
} |
|
else |
|
#endif |
|
{ |
|
TRACEINIT( InitMaterialSystem(), ShutdownMaterialSystem() ); |
|
|
|
TRACEINIT( modelloader->Init(), modelloader->Shutdown() ); |
|
|
|
TRACEINIT( StaticPropMgr()->Init(), StaticPropMgr()->Shutdown() ); |
|
|
|
TRACEINIT( InitStudioRender(), ShutdownStudioRender() ); |
|
|
|
TRACEINIT( Decal_Init(), Decal_Shutdown() ); |
|
|
|
cl.m_nSignonState = SIGNONSTATE_NONE; // disable client |
|
} |
|
|
|
#ifndef SWDS |
|
Host_ReadConfiguration(); |
|
TRACEINIT( S_Init(), S_Shutdown() ); |
|
#endif |
|
|
|
// Execute valve.rc |
|
Cbuf_AddText( "exec valve.rc\n" ); |
|
|
|
#if defined( REPLAY_ENABLED ) |
|
// Execute replay.cfg if this is TF and they want to use the replay system |
|
if ( Replay_IsSupportedModAndPlatform() && CommandLine()->CheckParm( "-replay" ) ) |
|
{ |
|
const char *pConfigName = CommandLine()->ParmValue( "-replay", "replay.cfg" ); |
|
Cbuf_AddText( va( "exec %s\n", pConfigName ) ); |
|
} |
|
#endif |
|
|
|
// Execute mod-specfic settings, without falling back based on search path. |
|
// This lets us set overrides for games while letting mods of those games |
|
// use the default settings. |
|
if ( g_pFileSystem->FileExists( "//mod/cfg/modsettings.cfg" ) ) |
|
{ |
|
Cbuf_AddText( "exec modsettings.cfg mod\n" ); |
|
} |
|
|
|
// Mark DLL as active |
|
// eng->SetNextState( InEditMode() ? IEngine::DLL_PAUSED : IEngine::DLL_ACTIVE ); |
|
|
|
// Deal with Gore Settings |
|
Host_CheckGore(); |
|
|
|
TelemetryTick(); |
|
|
|
// Initialize processor subsystem, and print relevant information: |
|
Host_InitProcessor(); |
|
|
|
// Mark hunklevel at end of startup |
|
Hunk_AllocName( 0, "-HOST_HUNKLEVEL-" ); |
|
host_hunklevel = Hunk_LowMark(); |
|
|
|
#ifdef SOURCE_MT |
|
if ( CommandLine()->FindParm( "-swapcores" ) ) |
|
{ |
|
g_nMaterialSystemThread = 1; |
|
g_nServerThread = 0; |
|
} |
|
#endif |
|
|
|
Host_AllowQueuedMaterialSystem( false ); |
|
|
|
// Finished initializing |
|
host_initialized = true; |
|
|
|
host_checkheap = CommandLine()->FindParm( "-heapcheck" ) ? true : false; |
|
|
|
if ( host_checkheap ) |
|
{ |
|
#if defined( _WIN32 ) |
|
if ( _heapchk() != _HEAPOK ) |
|
{ |
|
Sys_Error( "Host_Init: _heapchk() != _HEAPOK\n" ); |
|
} |
|
#endif |
|
} |
|
|
|
// go directly to run state with no active game |
|
HostState_Init(); |
|
|
|
// check for reslist generation |
|
if ( CommandLine()->FindParm( "-makereslists" ) ) |
|
{ |
|
MapReslistGenerator().StartReslistGeneration(); |
|
} |
|
|
|
// check for devshot generation |
|
if ( CommandLine()->FindParm( "-makedevshots" ) ) |
|
{ |
|
DevShotGenerator().StartDevShotGeneration(); |
|
} |
|
|
|
// if running outside of steam and NOT a dedicated server then phone home (or if "-phonehome" is passed on the command line) |
|
if ( !sv.IsDedicated() || CommandLine()->FindParm( "-phonehome" ) ) |
|
{ |
|
// In debug, only run this check if -phonehome is on the command line (so a debug build will "just work"). |
|
if ( IsDebug() && CommandLine()->FindParm( "-phonehome" ) ) |
|
{ |
|
phonehome->Init(); |
|
phonehome->Message( IPhoneHome::PHONE_MSG_ENGINESTART, NULL ); |
|
} |
|
} |
|
|
|
#ifndef SWDS |
|
// Rebuild audio caches |
|
if ( !sv.IsDedicated() && S_IsInitted() ) |
|
{ |
|
if ( !MapReslistGenerator().IsEnabled() ) |
|
{ |
|
// only build caches if we aren't' generating reslists (you need reslists to make the caches) |
|
extern void CheckCacheBuild(); |
|
CheckCacheBuild(); |
|
} |
|
} |
|
#endif |
|
|
|
Host_PostInit(); |
|
EndLoadingUpdates( ); |
|
CMatRenderContextPtr pRenderContext( g_pMaterialSystem ); |
|
pRenderContext->SetNonInteractiveTempFullscreenBuffer( NULL, MATERIAL_NON_INTERACTIVE_MODE_STARTUP ); |
|
pRenderContext->SetNonInteractivePacifierTexture( NULL, 0, 0, 0 ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Adds hints to the loader to keep resources that are in the transition volume, |
|
// as they may not be part of the next map's reslist. |
|
//----------------------------------------------------------------------------- |
|
void AddTransitionResources( CSaveRestoreData *pSaveData, const char *pLevelName, const char *pLandmarkName ) |
|
{ |
|
if ( !IsX360() || ( g_pFileSystem->GetDVDMode() != DVDMODE_STRICT ) ) |
|
{ |
|
return; |
|
} |
|
|
|
// get the bit marked for the next level |
|
int transitionMask = 0; |
|
for ( int i = 0; i < pSaveData->levelInfo.connectionCount; i++ ) |
|
{ |
|
if ( !Q_stricmp( pLevelName, pSaveData->levelInfo.levelList[i].mapName ) && !Q_stricmp( pLandmarkName, pSaveData->levelInfo.levelList[i].landmarkName ) ) |
|
{ |
|
transitionMask = 1<<i; |
|
break; |
|
} |
|
} |
|
|
|
if ( !transitionMask ) |
|
{ |
|
// nothing to do |
|
return; |
|
} |
|
|
|
const char *pModelName; |
|
bool bHasHumans = false; |
|
for ( int i = 0; i < pSaveData->NumEntities(); i++ ) |
|
{ |
|
if ( pSaveData->GetEntityInfo(i)->flags & transitionMask ) |
|
{ |
|
// this entity will cross the transition and needs to be preserved |
|
// add to the next map's resource list which effectively keeps it from being purged |
|
// only care about the actual mdl and not any of its dependants |
|
pModelName = pSaveData->GetEntityInfo(i)->modelname.ToCStr(); |
|
g_pQueuedLoader->AddMapResource( pModelName ); |
|
|
|
// humans require a post pass |
|
if ( !bHasHumans && V_stristr( pModelName, "models/humans" ) ) |
|
{ |
|
bHasHumans = true; |
|
} |
|
} |
|
} |
|
|
|
if ( bHasHumans ) |
|
{ |
|
// the presence of any human entity in the transition needs to ensure all the human mdls stay |
|
int count = modelloader->GetCount(); |
|
for ( int i = 0; i < count; i++ ) |
|
{ |
|
pModelName = modelloader->GetName( modelloader->GetModelForIndex( i ) ); |
|
if ( V_stristr( pModelName, "models/humans" ) ) |
|
{ |
|
g_pQueuedLoader->AddMapResource( pModelName ); |
|
} |
|
} |
|
} |
|
} |
|
|
|
bool Host_Changelevel( bool loadfromsavedgame, const char *mapname, const char *start ) |
|
{ |
|
char _startspot[MAX_QPATH]; |
|
char *startspot; |
|
char oldlevel[MAX_PATH]; |
|
#if !defined(SWDS) |
|
CSaveRestoreData *pSaveData = NULL; |
|
#endif |
|
bool bTransitionBySave = false; |
|
|
|
if ( !sv.IsActive() ) |
|
{ |
|
ConMsg("Only the server may changelevel\n"); |
|
return false; |
|
} |
|
|
|
#ifndef SWDS |
|
// FIXME: Even needed? |
|
if ( demoplayer->IsPlayingBack() ) |
|
{ |
|
ConMsg("Changelevel invalid during demo playback\n"); |
|
SCR_EndLoadingPlaque(); |
|
return false; |
|
} |
|
|
|
#endif |
|
|
|
#ifndef SWDS |
|
SCR_BeginLoadingPlaque(); |
|
|
|
// stop sounds (especially looping!) |
|
S_StopAllSounds(true); |
|
#endif |
|
|
|
// Prepare new level |
|
sv.InactivateClients(); |
|
|
|
// The qualified name of the map, excluding path/extension |
|
char szMapName[MAX_PATH] = { 0 }; |
|
// The file to load the map from. |
|
char szMapFile[MAX_PATH] = { 0 }; |
|
Q_strncpy( szMapName, mapname, sizeof( szMapName ) ); |
|
Host_DefaultMapFileName( szMapName, szMapFile, sizeof( szMapFile ) ); |
|
|
|
// Ask serverDLL to prepare this load |
|
if ( g_iServerGameDLLVersion >= 10 ) |
|
{ |
|
serverGameDLL->PrepareLevelResources( szMapName, sizeof( szMapName ), szMapFile, sizeof( szMapFile ) ); |
|
} |
|
|
|
if ( !modelloader->Map_IsValid( szMapFile ) ) |
|
{ |
|
#ifndef SWDS |
|
SCR_EndLoadingPlaque(); |
|
#endif |
|
// We have already inactivated clients at this point due to PrepareLevelResources being blocking, false alarm, |
|
// tell them to reconnect (which doesn't mean full reconnect, just start rejoining the map) |
|
// |
|
// In the likely case that the game DLL tries another map this is harmless, they'll wait on the game server in |
|
// the connect process if its in another level change by time they get there. |
|
sv.ReconnectClients(); |
|
return false; |
|
} |
|
|
|
// If changing from the same map to the same map, optimize by not closing and reopening |
|
// the packfile which is embedded in the .bsp; we do this by incrementing the packfile's |
|
// refcount via BeginMapAccess()/EndMapAccess() through the base filesystem API. |
|
struct LocalMapAccessScope |
|
{ |
|
LocalMapAccessScope() : bEnabled( false ) { } |
|
~LocalMapAccessScope() { if ( bEnabled ) g_pFileSystem->EndMapAccess(); } |
|
bool bEnabled; |
|
}; |
|
|
|
LocalMapAccessScope mapscope; |
|
if ( V_strcmp( sv.GetMapName(), szMapName ) == 0 ) |
|
{ |
|
g_pFileSystem->BeginMapAccess(); |
|
mapscope.bEnabled = true; |
|
} |
|
|
|
g_pFileSystem->AsyncFinishAll(); |
|
|
|
if ( !start ) |
|
startspot = NULL; |
|
else |
|
{ |
|
Q_strncpy (_startspot, start, sizeof( _startspot ) ); |
|
startspot = _startspot; |
|
} |
|
|
|
Warning( "---- Host_Changelevel ----\n" ); |
|
CheckForFlushMemory( sv.GetMapName(), szMapName ); |
|
|
|
#if !defined( SWDS ) |
|
// Always save as an xsave if we're on the X360 |
|
saverestore->SetIsXSave( IsX360() ); |
|
|
|
// Add on time passed since the last time we kept track till this transition |
|
int iAdditionalSeconds = g_ServerGlobalVariables.curtime - saverestore->GetMostRecentElapsedTimeSet(); |
|
int iElapsedSeconds = saverestore->GetMostRecentElapsedSeconds() + iAdditionalSeconds; |
|
int iElapsedMinutes = saverestore->GetMostRecentElapsedMinutes() + ( iElapsedSeconds / 60 ); |
|
saverestore->SetMostRecentElapsedMinutes( iElapsedMinutes ); |
|
saverestore->SetMostRecentElapsedSeconds( ( iElapsedSeconds % 60 ) ); |
|
|
|
if ( bTransitionBySave ) |
|
{ |
|
char comment[80]; |
|
// Pass in the total elapsed time so it gets added to the elapsed time for this map. |
|
serverGameDLL->GetSaveComment( |
|
comment, |
|
sizeof( comment ), |
|
saverestore->GetMostRecentElapsedMinutes(), |
|
saverestore->GetMostRecentElapsedSeconds() ); |
|
|
|
if ( !saverestore->SaveGameSlot( "_transition", comment, false, true, szMapName, startspot ) ) |
|
{ |
|
Warning( "Failed to save data for transition\n" ); |
|
SCR_EndLoadingPlaque(); |
|
return false; |
|
} |
|
|
|
// Not going to load a save after the transition, so add this map's elapsed time to the total elapsed time |
|
int totalSeconds = g_ServerGlobalVariables.curtime + saverestore->GetMostRecentElapsedSeconds(); |
|
saverestore->SetMostRecentElapsedMinutes( (int)( totalSeconds / 60.0f ) + saverestore->GetMostRecentElapsedMinutes() ); |
|
saverestore->SetMostRecentElapsedSeconds( (int)fmod( totalSeconds, 60.0f ) ); |
|
} |
|
#endif |
|
|
|
Q_strncpy( oldlevel, sv.GetMapName(), sizeof( oldlevel ) ); |
|
|
|
#if !defined(SWDS) |
|
if ( loadfromsavedgame ) |
|
{ |
|
if ( !bTransitionBySave ) |
|
{ |
|
// save the current level's state |
|
saverestore->SaveGameState( true, &pSaveData ); |
|
|
|
if ( !pSaveData ) |
|
{ |
|
Warning( "Failed to save data for transition\n" ); |
|
SCR_EndLoadingPlaque(); |
|
return false; |
|
} |
|
} |
|
|
|
// ensure resources in the transition volume stay |
|
AddTransitionResources( pSaveData, szMapName, startspot ); |
|
} |
|
#endif |
|
g_pServerPluginHandler->LevelShutdown(); |
|
|
|
#if !defined(SWDS) |
|
audiosourcecache->LevelShutdown(); |
|
#endif |
|
|
|
#if !defined(SWDS) |
|
saverestore->FinishAsyncSave(); |
|
#endif |
|
|
|
if ( sv.RestartOnLevelChange() ) |
|
{ |
|
Cbuf_Clear(); |
|
Cbuf_AddText( "quit\n" ); |
|
return false; |
|
} |
|
|
|
DownloadListGenerator().OnLevelLoadStart( szMapName ); |
|
|
|
if ( !sv.SpawnServer( szMapName, szMapFile, startspot ) ) |
|
{ |
|
#ifndef SWDS |
|
SCR_EndLoadingPlaque(); |
|
#endif |
|
return false; |
|
} |
|
|
|
#ifndef SWDS |
|
if ( loadfromsavedgame ) |
|
{ |
|
if ( !bTransitionBySave ) |
|
{ |
|
// Finish saving gamestate |
|
saverestore->Finish( pSaveData ); |
|
} |
|
|
|
g_ServerGlobalVariables.curtime = sv.GetTime(); |
|
|
|
audiosourcecache->LevelInit( szMapName ); |
|
g_pServerPluginHandler->LevelInit( szMapName, CM_EntityString(), oldlevel, startspot, true, false ); |
|
|
|
sv.SetPaused( true ); // pause until client connects |
|
sv.m_bLoadgame = true; |
|
} |
|
else |
|
#endif |
|
{ |
|
g_ServerGlobalVariables.curtime = sv.GetTime(); |
|
#if !defined(SWDS) |
|
audiosourcecache->LevelInit( szMapName ); |
|
#endif |
|
g_pServerPluginHandler->LevelInit( szMapName, CM_EntityString(), NULL, NULL, false, false ); |
|
} |
|
|
|
SV_ActivateServer(); |
|
|
|
#if !defined(SWDS) |
|
// Offset stored elapsed time by the current elapsed time for this new map |
|
int maptime = sv.GetTime(); |
|
int minutes = (int)( maptime / 60.0f ); |
|
int seconds = (int)fmod( maptime, 60.0f ); |
|
saverestore->SetMostRecentElapsedMinutes( saverestore->GetMostRecentElapsedMinutes() - minutes ); |
|
saverestore->SetMostRecentElapsedSeconds( saverestore->GetMostRecentElapsedSeconds() - seconds ); |
|
#endif |
|
|
|
NotifyDedicatedServerUI("UpdateMap"); |
|
|
|
DownloadListGenerator().OnLevelLoadEnd(); |
|
|
|
return true; |
|
} |
|
|
|
/* |
|
=============================================================================== |
|
|
|
SERVER TRANSITIONS |
|
|
|
=============================================================================== |
|
*/ |
|
bool Host_NewGame( char *mapName, bool loadGame, bool bBackgroundLevel, const char *pszOldMap, const char *pszLandmark, bool bOldSave ) |
|
{ |
|
VPROF( "Host_NewGame" ); |
|
COM_TimestampedLog( "Host_NewGame" ); |
|
|
|
char previousMapName[MAX_PATH] = { 0 }; |
|
Q_strncpy( previousMapName, host_map.GetString(), sizeof( previousMapName ) ); |
|
|
|
#ifndef SWDS |
|
SCR_BeginLoadingPlaque(); |
|
#endif |
|
|
|
// The qualified name of the map, excluding path/extension |
|
char szMapName[MAX_PATH] = { 0 }; |
|
// The file to load the map from. |
|
char szMapFile[MAX_PATH] = { 0 }; |
|
Q_strncpy( szMapName, mapName, sizeof( szMapName ) ); |
|
Host_DefaultMapFileName( szMapName, szMapFile, sizeof( szMapFile ) ); |
|
|
|
// Steam may not have been started yet, ensure it is available to the game DLL before we ask it to prepare level |
|
// resources |
|
SV_InitGameServerSteam(); |
|
|
|
// Ask serverDLL to prepare this load |
|
if ( g_iServerGameDLLVersion >= 10 ) |
|
{ |
|
serverGameDLL->PrepareLevelResources( szMapName, sizeof( szMapName ), szMapFile, sizeof( szMapFile ) ); |
|
} |
|
|
|
if ( !modelloader->Map_IsValid( szMapFile ) ) |
|
{ |
|
#ifndef SWDS |
|
SCR_EndLoadingPlaque(); |
|
#endif |
|
return false; |
|
} |
|
|
|
DevMsg( "---- Host_NewGame ----\n" ); |
|
host_map.SetValue( szMapName ); |
|
|
|
CheckForFlushMemory( previousMapName, szMapName ); |
|
|
|
if (MapReslistGenerator().IsEnabled()) |
|
{ |
|
// uncache all the materials, so their files get referenced again for the reslists |
|
// undone for now, since we're just trying to get a global reslist, not per-map accurate |
|
// materials->UncacheAllMaterials(); |
|
MapReslistGenerator().OnLevelLoadStart(szMapName); |
|
// cache 'em back in! |
|
// materials->CacheUsedMaterials(); |
|
} |
|
DownloadListGenerator().OnLevelLoadStart(szMapName); |
|
|
|
if ( !loadGame ) |
|
{ |
|
VPROF( "Host_NewGame_HostState_RunGameInit" ); |
|
HostState_RunGameInit(); |
|
} |
|
|
|
// init network mode |
|
VPROF_SCOPE_BEGIN( "Host_NewGame_SpawnServer" ); |
|
|
|
NET_SetMutiplayer( sv.IsMultiplayer() ); |
|
|
|
NET_ListenSocket( sv.m_Socket, true ); // activated server TCP socket |
|
|
|
// let's not have any servers with no name |
|
if ( host_name.GetString()[0] == 0 ) |
|
{ |
|
host_name.SetValue( serverGameDLL->GetGameDescription() ); |
|
} |
|
|
|
if ( !sv.SpawnServer ( szMapName, szMapFile, NULL ) ) |
|
{ |
|
return false; |
|
} |
|
|
|
sv.m_bIsLevelMainMenuBackground = bBackgroundLevel; |
|
|
|
VPROF_SCOPE_END(); |
|
|
|
// make sure the time is set |
|
g_ServerGlobalVariables.curtime = sv.GetTime(); |
|
|
|
COM_TimestampedLog( "serverGameDLL->LevelInit" ); |
|
|
|
#ifndef SWDS |
|
EngineVGui()->UpdateProgressBar(PROGRESS_LEVELINIT); |
|
|
|
audiosourcecache->LevelInit( szMapName ); |
|
#endif |
|
|
|
g_pServerPluginHandler->LevelInit( szMapName, CM_EntityString(), pszOldMap, pszLandmark, loadGame && !bOldSave, bBackgroundLevel ); |
|
|
|
if ( loadGame && !bOldSave ) |
|
{ |
|
sv.SetPaused( true ); // pause until all clients connect |
|
sv.m_bLoadgame = true; |
|
g_ServerGlobalVariables.curtime = sv.GetTime(); |
|
} |
|
|
|
if( !SV_ActivateServer() ) |
|
{ |
|
return false; |
|
} |
|
|
|
// Connect the local client when a "map" command is issued. |
|
if ( !sv.IsDedicated() ) |
|
{ |
|
COM_TimestampedLog( "Stuff 'connect localhost' to console" ); |
|
|
|
char str[512]; |
|
Q_snprintf( str, sizeof( str ), "connect localhost:%d listenserver", sv.GetUDPPort() ); |
|
Cbuf_AddText( str ); |
|
} |
|
else |
|
{ |
|
// Dedicated server triggers map load here. |
|
GetTestScriptMgr()->CheckPoint( "FinishedMapLoad" ); |
|
} |
|
|
|
#ifndef SWDS |
|
if ( !loadGame || bOldSave ) |
|
{ |
|
// clear the most recent remember save, so the level will just restart if the player dies |
|
saverestore->ForgetRecentSave(); |
|
} |
|
|
|
saverestore->SetMostRecentElapsedMinutes( 0 ); |
|
saverestore->SetMostRecentElapsedSeconds( 0 ); |
|
#endif |
|
|
|
if (MapReslistGenerator().IsEnabled()) |
|
{ |
|
MapReslistGenerator().OnLevelLoadEnd(); |
|
} |
|
DownloadListGenerator().OnLevelLoadEnd(); |
|
return true; |
|
} |
|
|
|
void Host_FreeStateAndWorld( bool server ) |
|
{ |
|
bool bNeedsPurge = false; |
|
|
|
Assert( host_initialized ); |
|
Assert( host_hunklevel ); |
|
|
|
// If called by the client and we are running a listen server, just ignore |
|
if ( !server && sv.IsActive() ) |
|
return; |
|
|
|
// HACKHACK: You can't clear the hunk unless the client data is free |
|
// since this gets called by the server, it's necessary to wipe the client |
|
// in case we are on a listen server |
|
#ifndef SWDS |
|
if ( server && !sv.IsDedicated() ) |
|
{ |
|
CL_ClearState(); |
|
} |
|
#endif |
|
|
|
// The world model relies on the low hunk, so we need to force it to unload |
|
if ( host_state.worldmodel ) |
|
{ |
|
modelloader->UnreferenceModel( host_state.worldmodel, IModelLoader::FMODELLOADER_SERVER ); |
|
modelloader->UnreferenceModel( host_state.worldmodel, IModelLoader::FMODELLOADER_CLIENT ); |
|
host_state.SetWorldModel( NULL ); |
|
bNeedsPurge = server && true; |
|
} |
|
|
|
// Unload and reset dynamic models |
|
if ( server ) |
|
{ |
|
extern IVModelInfo* modelinfo; |
|
modelinfo->OnLevelChange(); |
|
} |
|
#ifndef SWDS |
|
else |
|
{ |
|
extern IVModelInfoClient* modelinfoclient; |
|
modelinfoclient->OnLevelChange(); |
|
} |
|
#endif |
|
|
|
modelloader->UnloadUnreferencedModels(); |
|
|
|
g_TimeLastMemTest = 0; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void Host_FreeToLowMark( bool server ) |
|
{ |
|
Assert( host_initialized ); |
|
Assert( host_hunklevel ); |
|
|
|
// If called by the client and we are running a listen server, just ignore |
|
if ( !server && sv.IsActive() ) |
|
return; |
|
|
|
CM_FreeMap(); |
|
|
|
if ( host_hunklevel ) |
|
{ |
|
// See if we are going to obliterate any malloc'd pointers |
|
Hunk_FreeToLowMark(host_hunklevel); |
|
} |
|
} |
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void Host_Shutdown(void) |
|
{ |
|
extern void ShutdownMixerControls(); |
|
|
|
if ( host_checkheap ) |
|
{ |
|
#ifdef _WIN32 |
|
if ( _heapchk() != _HEAPOK ) |
|
{ |
|
Sys_Error( "Host_Shutdown (top): _heapchk() != _HEAPOK\n" ); |
|
} |
|
#endif |
|
} |
|
|
|
// Check for recursive shutdown, should never happen |
|
static bool shutting_down = false; |
|
if ( shutting_down ) |
|
{ |
|
Msg( "Recursive shutdown!!!\n" ); |
|
return; |
|
} |
|
shutting_down = true; |
|
|
|
phonehome->Message( IPhoneHome::PHONE_MSG_ENGINEEND, NULL ); |
|
phonehome->Shutdown(); |
|
|
|
#ifndef SWDS |
|
// Store active configuration settings |
|
Host_WriteConfiguration(); |
|
#endif |
|
|
|
// Disconnect from server |
|
Host_Disconnect(true); |
|
|
|
#ifndef SWDS |
|
// keep ConMsg from trying to update the screen |
|
scr_disabled_for_loading = true; |
|
#endif |
|
|
|
#if defined VOICE_OVER_IP && !defined SWDS && !defined( NO_VOICE ) //!defined(_XBOX) |
|
Voice_Deinit(); |
|
#endif // VOICE_OVER_IP |
|
|
|
// TODO, Trace this |
|
CM_FreeMap(); |
|
|
|
host_initialized = false; |
|
|
|
#if defined(VPROF_ENABLED) |
|
VProfRecord_Shutdown(); |
|
#endif |
|
|
|
#if !defined SWDS |
|
if ( !sv.IsDedicated() ) |
|
{ |
|
TRACESHUTDOWN( Decal_Shutdown() ); |
|
|
|
TRACESHUTDOWN( R_Shutdown() ); |
|
|
|
TRACESHUTDOWN( SCR_Shutdown() ); |
|
|
|
TRACESHUTDOWN( S_Shutdown() ); |
|
|
|
TRACESHUTDOWN( ClientDLL_Shutdown() ); |
|
|
|
TRACESHUTDOWN( TextMessageShutdown() ); |
|
|
|
TRACESHUTDOWN( EngineVGui()->Shutdown() ); |
|
|
|
TRACESHUTDOWN( StaticPropMgr()->Shutdown() ); |
|
|
|
// Model loader must shutdown before StudioRender |
|
// because it calls into StudioRender |
|
TRACESHUTDOWN( modelloader->Shutdown() ); |
|
|
|
TRACESHUTDOWN( ShutdownStudioRender() ); |
|
|
|
TRACESHUTDOWN( ShutdownMaterialSystem() ); |
|
|
|
TRACESHUTDOWN( CL_Shutdown() ); |
|
} |
|
else |
|
#endif |
|
{ |
|
TRACESHUTDOWN( Decal_Shutdown() ); |
|
|
|
TRACESHUTDOWN( modelloader->Shutdown() ); |
|
|
|
TRACESHUTDOWN( ShutdownStudioRender() ); |
|
|
|
TRACESHUTDOWN( StaticPropMgr()->Shutdown() ); |
|
|
|
TRACESHUTDOWN( ShutdownMaterialSystem() ); |
|
} |
|
|
|
#if defined( REPLAY_ENABLED ) |
|
if ( Replay_IsSupportedModAndPlatform() ) |
|
{ |
|
TRACESHUTDOWN( ReplaySystem_Shutdown() ); |
|
} |
|
#endif |
|
|
|
TRACESHUTDOWN( HLTV_Shutdown() ); |
|
|
|
TRACESHUTDOWN( g_Log.Shutdown() ); |
|
|
|
TRACESHUTDOWN( g_GameEventManager.Shutdown() ); |
|
|
|
TRACESHUTDOWN( sv.Shutdown() ); |
|
|
|
TRACESHUTDOWN( NET_Shutdown() ); |
|
|
|
#ifndef SWDS |
|
TRACESHUTDOWN( Key_Shutdown() ); |
|
#ifndef _X360 |
|
TRACESHUTDOWN( ShutdownMixerControls() ); |
|
#endif |
|
#endif |
|
|
|
TRACESHUTDOWN( Filter_Shutdown() ); |
|
|
|
#ifndef SWDS |
|
TRACESHUTDOWN( saverestore->Shutdown() ); |
|
#endif |
|
|
|
TRACESHUTDOWN( COM_Shutdown() ); |
|
|
|
// TRACESHUTDOWN( Host_ShutdownVCR() ); |
|
#ifndef SWDS |
|
TRACESHUTDOWN( V_Shutdown() ); |
|
#endif |
|
|
|
TRACESHUTDOWN( g_pCVar->Shutdown() ); |
|
|
|
TRACESHUTDOWN( Cmd_Shutdown() ); |
|
|
|
TRACESHUTDOWN( Cbuf_Shutdown() ); |
|
|
|
TRACESHUTDOWN( Con_Shutdown() ); |
|
|
|
TRACESHUTDOWN( Memory_Shutdown() ); |
|
|
|
if ( g_pThreadPool ) |
|
g_pThreadPool->Stop(); |
|
|
|
DTI_Term(); |
|
ServerDTI_Term(); |
|
|
|
#if defined(_WIN32) |
|
if ( s_bInitPME ) |
|
{ |
|
ShutdownPME(); |
|
} |
|
#endif |
|
|
|
if ( host_checkheap ) |
|
{ |
|
#ifdef _WIN32 |
|
if ( _heapchk() != _HEAPOK ) |
|
{ |
|
Sys_Error( "Host_Shutdown (bottom): _heapchk() != _HEAPOK\n" ); |
|
} |
|
#endif |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Centralize access to enabling QMS. |
|
//----------------------------------------------------------------------------- |
|
bool Host_AllowQueuedMaterialSystem( bool bAllow ) |
|
{ |
|
#if !defined DEDICATED |
|
// g_bAllowThreadedSound = bAllow; |
|
// NOTE: Moved this to materialsystem for integrating with other mqm changes |
|
return g_pMaterialSystem->AllowThreading( bAllow, g_nMaterialSystemThread ); |
|
#endif |
|
return false; |
|
}
|
|
|