From 3bf9df6b2785fa6d951086978a3e66f49427166a Mon Sep 17 00:00:00 2001 From: FluorescentCIAAfricanAmerican <0934gj3049fk@protonmail.com> Date: Wed, 22 Apr 2020 12:56:21 -0400 Subject: 1 --- engine/host.cpp | 5025 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 5025 insertions(+) create mode 100644 engine/host.cpp (limited to 'engine/host.cpp') diff --git a/engine/host.cpp b/engine/host.cpp new file mode 100644 index 0000000..0ff16bb --- /dev/null +++ b/engine/host.cpp @@ -0,0 +1,5025 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//===========================================================================// + +#include "tier0/fasttimer.h" + +#ifdef _WIN32 +#include "tier0/memdbgon.h" // needed because in release builds crtdbg.h is handled specially if USE_MEM_DEBUG is defined +#include "tier0/memdbgoff.h" +#include // 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 +#include "SDL.h" +#endif + +#include "ixboxsystem.h" +extern IXboxSystem *g_pXboxSystem; + +extern ConVar cl_cloud_settings; +extern ConVar cl_logofile; + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +//----------------------------------------------------------------------------- +// Forward declarations +//----------------------------------------------------------------------------- +void CL_SetPagedPoolInfo(); +extern char *CM_EntityString( void ); +extern ConVar host_map; +extern ConVar sv_cheats; + +bool g_bDedicatedServerBenchmarkMode = false; +bool g_bAllowSecureServers = true; +bool g_bLowViolence = false; + +// These counters are for debugging in dumps. If these are non-zero it may indicate some kind of +// heap problem caused by the setjmp/longjmp error handling +int g_HostServerAbortCount = 0; +int g_HostErrorCount = 0; +int g_HostEndDemo = 0; + +char g_szDefaultLogoFileName[] = "materials/vgui/logos/spray.vtf"; + +int host_frameticks = 0; +int host_tickcount = 0; +int host_currentframetick = 0; + +static const char g_pModuleExtension[] = DLL_EXT_STRING; + +// Engine player info, no game related infos here +BEGIN_BYTESWAP_DATADESC( player_info_s ) + DEFINE_ARRAY( name, FIELD_CHARACTER, MAX_PLAYER_NAME_LENGTH ), + DEFINE_FIELD( userID, FIELD_INTEGER ), + DEFINE_ARRAY( guid, FIELD_CHARACTER, SIGNED_GUID_LEN + 1 ), + DEFINE_FIELD( friendsID, FIELD_INTEGER ), + DEFINE_ARRAY( friendsName, FIELD_CHARACTER, MAX_PLAYER_NAME_LENGTH ), + DEFINE_FIELD( fakeplayer, FIELD_BOOLEAN ), + DEFINE_FIELD( ishltv, FIELD_BOOLEAN ), +#if defined( REPLAY_ENABLED ) + DEFINE_FIELD( isreplay, FIELD_BOOLEAN ), +#endif + DEFINE_ARRAY( customFiles, FIELD_INTEGER, MAX_CUSTOM_FILES ), + DEFINE_FIELD( filesDownloaded, FIELD_INTEGER ), +END_BYTESWAP_DATADESC() + +//------------------------------------------ +enum +{ + FRAME_SEGMENT_INPUT = 0, + FRAME_SEGMENT_CLIENT, + FRAME_SEGMENT_SERVER, + FRAME_SEGMENT_RENDER, + FRAME_SEGMENT_SOUND, + FRAME_SEGMENT_CLDLL, + FRAME_SEGMENT_CMD_EXECUTE, + + NUM_FRAME_SEGMENTS, +}; + +class CFrameTimer +{ +public: + void ResetDeltas(); + + CFrameTimer() : swaptime(0) + { + ResetDeltas(); + } + + void MarkFrame(); + void StartFrameSegment( int i ) + { + starttime[i] = Sys_FloatTime(); + } + + void EndFrameSegment( int i ) + { + double dt = Sys_FloatTime() - starttime[i]; + deltas[ i ] += dt; + } + void MarkSwapTime( ) + { + double newswaptime = Sys_FloatTime(); + frametime = newswaptime - swaptime; + swaptime = newswaptime; + + ComputeFrameVariability(); + g_EngineStats.SetFrameTime( frametime ); + g_EngineStats.SetFPSVariability( m_flFPSVariability ); + + host_frametime_stddeviation = m_flFPSStdDeviationSeconds; + } + +private: + enum + { + FRAME_HISTORY_COUNT = 50 + }; + + friend void Host_Speeds(); + void ComputeFrameVariability(); + + double time_base; + double times[9]; + double swaptime; + double frametime; + double m_flFPSVariability; + double m_flFPSStdDeviationSeconds; + double starttime[NUM_FRAME_SEGMENTS]; + double deltas[NUM_FRAME_SEGMENTS]; + + float m_pFrameTimeHistory[FRAME_HISTORY_COUNT]; + int m_nFrameTimeHistoryIndex; +}; + + +static CFrameTimer g_HostTimes; + + +//------------------------------------------ + + +float host_time = 0.0; + +static ConVar violence_hblood( "violence_hblood","1", 0, "Draw human blood" ); +static ConVar violence_hgibs( "violence_hgibs","1", 0, "Show human gib entities" ); +static ConVar violence_ablood( "violence_ablood","1", 0, "Draw alien blood" ); +static ConVar violence_agibs( "violence_agibs","1", 0, "Show alien gib entities" ); + +// Marked as FCVAR_USERINFO so that the server can cull CC messages before networking them down to us!!! +ConVar closecaption( "closecaption", "0", FCVAR_ARCHIVE | FCVAR_ARCHIVE_XBOX | FCVAR_USERINFO, "Enable close captioning." ); +extern ConVar sv_unlockedchapters; + +void Snd_Restart_f() +{ +#ifndef SWDS + extern bool snd_firsttime; + + char szVoiceCodec[_MAX_PATH] = { 0 }; + int nVoiceSampleRate = Voice_ConfiguredSampleRate(); + + { + // This is not valid after voice shuts down + const char *pPreviousCodec = Voice_ConfiguredCodec(); + if ( pPreviousCodec && *pPreviousCodec ) + { + V_strncpy( szVoiceCodec, pPreviousCodec, sizeof( szVoiceCodec ) ); + } + } + + S_Shutdown(); + snd_firsttime = true; + cl.ClearSounds(); + S_Init(); + + // Restart voice if it was running + if ( szVoiceCodec[0] ) + Voice_Init( szVoiceCodec, nVoiceSampleRate ); + + // Do this or else it won't have anything in the cache. + if ( audiosourcecache && sv.GetMapName()[0] ) + { + audiosourcecache->LevelInit( sv.GetMapName() ); + } + + // Flush soundscapes so they don't stop. We don't insert text in the buffer here because + // cl_soundscape_flush is normally cheat-protected. + ConCommand *pCommand = (ConCommand*)dynamic_cast< const ConCommand* >( g_pCVar->FindCommand( "cl_soundscape_flush" ) ); + if ( pCommand ) + { + char const *argv[ 1 ] = { "cl_soundscape_flush" }; + + CCommand cmd( 1, argv ); + pCommand->Dispatch( cmd ); + } +#endif +} + +static ConCommand snd_restart( "snd_restart", Snd_Restart_f, "Restart sound system." ); + +// In other C files. +void Shader_Shutdown( void ); +void R_Shutdown( void ); + +bool g_bAbortServerSet = false; + +#ifdef _WIN32 +static bool s_bInitPME = false; +#endif + +CON_COMMAND( mem_dump, "Dump memory stats to text file." ) +{ + ConMsg("Writing memory stats to file memstats.txt\n"); + +#if defined( _MEMTEST ) + const char *pTest = sv.GetMapName(); + if ( !pTest || !pTest[0] ) + { + // possibly at menu + pTest = "unknown"; + } + MemAlloc_SetStatsExtraInfo( pTest,"" ); +#endif + MemAlloc_DumpStats(); +} + +CON_COMMAND( mem_compact, "" ) +{ + MemAlloc_CompactHeap(); +} + +CON_COMMAND( mem_eat, "" ) +{ + MemAlloc_Alloc( 1024* 1024 ); +} + +CON_COMMAND( mem_test, "" ) +{ + MemAlloc_CrtCheckMemory(); +} + +static ConVar host_competitive_ever_enabled( "host_competitive_ever_enabled", "0", FCVAR_HIDDEN, "Has competitive ever been enabled this run?", true, 0, true, 1, true, 1, false, 1, NULL ); + +static ConVar mem_test_each_frame( "mem_test_each_frame", "0", 0, "Run heap check at end of every frame\n" ); +static ConVar mem_test_every_n_seconds( "mem_test_every_n_seconds", "0", 0, "Run heap check at a specified interval\n" ); + +static ConVar singlestep( "singlestep", "0", FCVAR_CHEAT, "Run engine in single step mode ( set next to 1 to advance a frame )" ); +static ConVar cvarNext( "next", "0", FCVAR_CHEAT, "Set to 1 to advance to next frame ( when singlestep == 1 )" ); +// Print a debug message when the client or server cache is missed +ConVar host_showcachemiss( "host_showcachemiss", "0", 0, "Print a debug message when the client or server cache is missed." ); +static ConVar mem_dumpstats( "mem_dumpstats", "0", 0, "Dump current and max heap usage info to console at end of frame ( set to 2 for continuous output )\n" ); +static ConVar host_ShowIPCCallCount( "host_ShowIPCCallCount", "0", 0, "Print # of IPC calls this number of times per second. If set to -1, the # of IPC calls is shown every frame." ); + +#if defined( RAD_TELEMETRY_ENABLED ) +static void OnChangeTelemetryPause ( IConVar *var, const char *pOldValue, float flOldValue ) +{ + tmPause( TELEMETRY_LEVEL0, 1 ); +} + +static void OnChangeTelemetryResume ( IConVar *var, const char *pOldValue, float flOldValue ) +{ + tmPause( TELEMETRY_LEVEL0, 0 ); +} + +static void OnChangeTelemetryLevel ( IConVar *var, const char *pOldValue, float flOldValue ) +{ + char* pIEnd; + const char *pLevel = (( ConVar* )var)->GetString(); + + TelemetrySetLevel( strtoul( pLevel, &pIEnd, 0 ) ); +} + +static void OnChangeTelemetryFrameCount ( IConVar *var, const char *pOldValue, float flOldValue ) +{ + char* pIEnd; + const char *pFrameCount = (( ConVar* )var)->GetString(); + + g_Telemetry.FrameCount = strtoul( pFrameCount, &pIEnd, 0 ); + Msg( " TELEMETRY: Setting Telemetry FrameCount: '%d'\n", g_Telemetry.FrameCount ); +} + +static void OnChangeTelemetryServer ( IConVar *var, const char *pOldValue, float flOldValue ) +{ + const char *pServerAddress = (( ConVar* )var)->GetString(); + + Q_strncpy( g_Telemetry.ServerAddress, pServerAddress, ARRAYSIZE( g_Telemetry.ServerAddress ) ); + Msg( " TELEMETRY: Setting Telemetry server: '%s'\n", pServerAddress ); +} + +static void OnChangeTelemetryDemoStart ( IConVar *var, const char *pOldValue, float flOldValue ) +{ + char* pIEnd; + const char *pVal = (( ConVar* )var)->GetString(); + + g_Telemetry.DemoTickStart = strtoul( pVal, &pIEnd, 0 ); + if( g_Telemetry.DemoTickStart > 2000 ) + { + char cmd[ 256 ]; + + // If we're far away from the start of the demo file, then jump to ~1000 ticks before. + Q_snprintf( cmd, sizeof( cmd ), "demo_gototick %d", g_Telemetry.DemoTickStart - 1000 ); + Cbuf_AddText( cmd ); + } + Msg( " TELEMETRY: Setting Telemetry DemoTickStart: '%d'\n", g_Telemetry.DemoTickStart ); +} + +static void OnChangeTelemetryDemoEnd ( IConVar *var, const char *pOldValue, float flOldValue ) +{ + char* pIEnd; + const char *pVal = (( ConVar* )var)->GetString(); + + g_Telemetry.DemoTickEnd = strtoul( pVal, &pIEnd, 0 ); + Msg( " TELEMETRY: Setting Telemetry DemoTickEnd: '%d'\n", g_Telemetry.DemoTickEnd ); +} + +ConVar telemetry_pause( "telemetry_pause", "0", 0, "Pause Telemetry", OnChangeTelemetryPause ); +ConVar telemetry_resume( "telemetry_resume", "0", 0, "Resume Telemetry", OnChangeTelemetryResume ); +ConVar telemetry_framecount( "telemetry_framecount", "0", 0, "Set Telemetry count of frames to capture", OnChangeTelemetryFrameCount ); +ConVar telemetry_level( "telemetry_level", "0", 0, "Set Telemetry profile level: 0 being off.", OnChangeTelemetryLevel ); +ConVar telemetry_server( "telemetry_server", "localhost", 0, "Set Telemetry server", OnChangeTelemetryServer ); +ConVar telemetry_demostart( "telemetry_demostart", "0", 0, "When playing demo, start telemetry on tick #", OnChangeTelemetryDemoStart ); +ConVar telemetry_demoend( "telemetry_demoend", "0", 0, "When playing demo, stop telemetry on tick #", OnChangeTelemetryDemoEnd ); +#endif + +extern bool gfBackground; + +static bool host_checkheap = false; + +CCommonHostState host_state; + + +//----------------------------------------------------------------------------- + +enum HostThreadMode +{ + HTM_DISABLED, + HTM_DEFAULT, + HTM_FORCED, +}; + +ConVar host_thread_mode( "host_thread_mode", ( IsX360() ) ? "1" : "0", 0, "Run the host in threaded mode, (0 == off, 1 == if multicore, 2 == force)" ); +extern ConVar threadpool_affinity; +void OnChangeThreadAffinity( IConVar *var, const char *pOldValue, float flOldValue ) +{ + if ( g_pThreadPool->NumThreads() ) + { + g_pThreadPool->Distribute( threadpool_affinity.GetBool() ); + } +} + +ConVar threadpool_affinity( "threadpool_affinity", "1", 0, "Enable setting affinity", 0, 0, 0, 0, &OnChangeThreadAffinity ); + +#if 0 +extern ConVar threadpool_reserve; +CThreadEvent g_ReleaseThreadReservation( true ); +CInterlockedInt g_NumReservedThreads; + +void ThreadPoolReserverFunction() +{ + g_ReleaseThreadReservation.Wait(); + --g_NumReservedThreads; +} + +void ReserveThreads( int nToReserve ) +{ + nToReserve = clamp( nToReserve, 0, g_pThreadPool->NumThreads() ); + g_ReleaseThreadReservation.Set(); + + while ( g_NumReservedThreads != 0 ) + { + ThreadSleep( 0 ); + } + + g_ReleaseThreadReservation.Reset(); + + while ( nToReserve-- ) + { + g_NumReservedThreads++; + g_pThreadPool->QueueCall( &ThreadPoolReserverFunction )->Release(); + } + + Msg( "%d threads being reserved\n", (int)g_NumReservedThreads ); +} + +void OnChangeThreadReserve( IConVar *var, const char *pOldValue, float flOldValue ) +{ + ReserveThreads( threadpool_reserve.GetInt() ); +} + +ConVar threadpool_reserve( "threadpool_reserve", "0", 0, "Consume the specified number of threads in the thread pool", 0, 0, 0, 0, &OnChangeThreadReserve ); + +CON_COMMAND( threadpool_cycle_reserve, "Cycles threadpool reservation by powers of 2" ) +{ + int nCores = g_pThreadPool->NumThreads() + 1; + int nAvailableCores = nCores - g_NumReservedThreads; + Assert( nAvailableCores ); + int ratio = nCores / nAvailableCores; + ratio *= 2; + if ( ratio > nCores ) + { + ReserveThreads( 0 ); + } + else + { + ReserveThreads( nCores - nCores / ratio ); + } +} + +CON_COMMAND( thread_test_tslist, "" ) +{ + int nTests = ( args.ArgC() == 1 ) ? 10000 : atoi( args.Arg( 1 ) ); + RunTSListTests( nTests ); +} + +CON_COMMAND( thread_test_tsqueue, "" ) +{ + int nTests = ( args.ArgC() == 1 ) ? 10000 : atoi( args.Arg( 1 ) ); + RunTSQueueTests( nTests ); +} + +CON_COMMAND( threadpool_run_tests, "" ) +{ + int nTests = ( args.ArgC() == 1 ) ? 1 : atoi( args.Arg( 1 ) ); + for ( int i = 0; i < nTests; i++ ) + { + RunThreadPoolTests(); + } +} +#endif + +//----------------------------------------------------------------------------- + +/* +A server can always be started, even if the system started out as a client +to a remote system. + +A client can NOT be started if the system started as a dedicated server. +Memory is cleared / released when a server or client begins, not when they end. +*/ + + +// Ear position + orientation +static AudioState_t s_AudioState; + +engineparms_t host_parms; + +bool host_initialized = false; // true if into command execution + +float host_frametime = 0.0f; +float host_frametime_unbounded = 0.0f; +float host_frametime_stddeviation = 0.0f; +double realtime = 0; // without any filtering or bounding +double host_idealtime = 0; // "ideal" server time assuming perfect tick rate +float host_nexttick = 0; // next server tick in this many ms +float host_jitterhistory[128] = { 0 }; +unsigned int host_jitterhistorypos = 0; + +int host_framecount; +static int host_hunklevel; + +CGameClient *host_client; // current client + +jmp_buf host_abortserver; +jmp_buf host_enddemo; + +static ConVar host_profile( "host_profile","0" ); + +ConVar host_limitlocal( "host_limitlocal", "0", 0, "Apply cl_cmdrate and cl_updaterate to loopback connection" ); +ConVar host_framerate( "host_framerate","0", 0, "Set to lock per-frame time elapse." ); +ConVar host_timescale( "host_timescale","1.0", FCVAR_REPLICATED, "Prescale the clock by this amount." ); +ConVar host_speeds( "host_speeds","0", 0, "Show general system running times." ); // set for running times + +ConVar host_flush_threshold( "host_flush_threshold", "20", 0, "Memory threshold below which the host should flush caches between server instances" ); + +void HostTimerSpinMsChangedCallback( IConVar *var, const char *pOldString, float flOldValue ); +ConVar host_timer_spin_ms( "host_timer_spin_ms", "0", FCVAR_NONE, "Use CPU busy-loop for improved timer precision (dedicated only)", HostTimerSpinMsChangedCallback ); + +void HostTimerSpinMsChangedCallback( IConVar *var, const char *pOldString, float flOldValue ) +{ + const char *pForcedValue = CommandLine()->ParmValue( "+host_timer_spin_ms" ); + if ( pForcedValue != NULL ) + { + if ( V_strcmp( host_timer_spin_ms.GetString(), pForcedValue ) ) + { + Msg( "Value for host_timer_spin_ms is locked to %s by command line parameter.\n", pForcedValue ); + host_timer_spin_ms.SetValue( pForcedValue ); + } + } +} + +CON_COMMAND( host_timer_report, "Spew CPU timer jitter for the last 128 frames in microseconds (dedicated only)" ) +{ + if ( sv.IsDedicated() ) + { + for (int i = 1; i <= ARRAYSIZE( host_jitterhistory ); ++i) + { + unsigned int slot = ( i + host_jitterhistorypos ) % ARRAYSIZE( host_jitterhistory ); + Msg( "%1.3fms\n", host_jitterhistory[ slot ] * 1000 ); + } + } +} + +#ifdef REL_TO_STAGING_MERGE_TODO +// Do this when merging the game DLLs so FCVAR_CHEAT can be set on them at the same time. +ConVar developer( "developer", "0", FCVAR_CHEAT, "Set developer message level"); +#else +ConVar developer( "developer", "0", 0, "Set developer message level"); +#endif + +ConVar skill( "skill","1", FCVAR_ARCHIVE | FCVAR_ARCHIVE_XBOX, "Game skill level (1-3).", true, 1, true, 3 ); // 1 - 3 +ConVar deathmatch( "deathmatch","0", FCVAR_NOTIFY | FCVAR_INTERNAL_USE, "Running a deathmatch server." ); // 0, 1, or 2 +ConVar coop( "coop","0", FCVAR_NOTIFY, "Cooperative play." ); // 0 or 1 + +#ifdef _DEBUG +ConVar r_ForceRestore( "r_ForceRestore", "0", 0 ); +#endif // _DEBUG + +ConVar vcr_verbose( "vcr_verbose", "0", 0, "Write extra information into .vcr file." ); + +#ifndef SWDS +void CL_CheckToDisplayStartupMenus(); // in cl_main.cpp +#endif + + +bool GetFileFromRemoteStorage( ISteamRemoteStorage *pRemoteStorage, const char *pszRemoteFileName, const char *pszLocalFileName ) +{ + bool bSuccess = false; + + // check if file exists in Steam Cloud first + int32 nFileSize = pRemoteStorage->GetFileSize( pszRemoteFileName ); + + if ( nFileSize > 0 ) + { + CUtlMemory 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 [full]\n" ); + ConMsg( " 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= 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 \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<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; +} -- cgit v1.2.3