//========= 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; }