From f56bb35301836e56582a575a75864392a0177875 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B8rgen=20P=2E=20Tjern=C3=B8?= Date: Mon, 2 Dec 2013 19:31:46 -0800 Subject: Fix line endings. WHAMMY. --- sp/src/game/server/gameinterface.cpp | 6866 +++++++++++++++++----------------- 1 file changed, 3433 insertions(+), 3433 deletions(-) (limited to 'sp/src/game/server/gameinterface.cpp') diff --git a/sp/src/game/server/gameinterface.cpp b/sp/src/game/server/gameinterface.cpp index b4d09bae..c9357138 100644 --- a/sp/src/game/server/gameinterface.cpp +++ b/sp/src/game/server/gameinterface.cpp @@ -1,3433 +1,3433 @@ -//========= Copyright Valve Corporation, All rights reserved. ============// -// -// Purpose: encapsulates and implements all the accessing of the game dll from external -// sources (only the engine at the time of writing) -// This files ONLY contains functions and data necessary to build an interface -// to external modules -//===========================================================================// - -#include "cbase.h" -#include "gamestringpool.h" -#include "mapentities_shared.h" -#include "game.h" -#include "entityapi.h" -#include "client.h" -#include "saverestore.h" -#include "entitylist.h" -#include "gamerules.h" -#include "soundent.h" -#include "player.h" -#include "server_class.h" -#include "ai_node.h" -#include "ai_link.h" -#include "ai_saverestore.h" -#include "ai_networkmanager.h" -#include "ndebugoverlay.h" -#include "ivoiceserver.h" -#include -#include "movehelper_server.h" -#include "networkstringtable_gamedll.h" -#include "filesystem.h" -#include "func_areaportalwindow.h" -#include "igamesystem.h" -#include "init_factory.h" -#include "vstdlib/random.h" -#include "env_wind_shared.h" -#include "engine/IEngineSound.h" -#include "ispatialpartition.h" -#include "textstatsmgr.h" -#include "bitbuf.h" -#include "saverestoretypes.h" -#include "physics_saverestore.h" -#include "achievement_saverestore.h" -#include "tier0/vprof.h" -#include "effect_dispatch_data.h" -#include "engine/IStaticPropMgr.h" -#include "TemplateEntities.h" -#include "ai_speech.h" -#include "soundenvelope.h" -#include "usermessages.h" -#include "physics.h" -#include "igameevents.h" -#include "EventLog.h" -#include "datacache/idatacache.h" -#include "engine/ivdebugoverlay.h" -#include "shareddefs.h" -#include "props.h" -#include "timedeventmgr.h" -#include "gameinterface.h" -#include "eventqueue.h" -#include "hltvdirector.h" -#if defined( REPLAY_ENABLED ) -#include "replay/iserverreplaycontext.h" -#endif -#include "SoundEmitterSystem/isoundemittersystembase.h" -#include "AI_ResponseSystem.h" -#include "saverestore_stringtable.h" -#include "util.h" -#include "tier0/icommandline.h" -#include "datacache/imdlcache.h" -#include "engine/iserverplugin.h" -#ifdef _WIN32 -#include "ienginevgui.h" -#endif -#include "ragdoll_shared.h" -#include "toolframework/iserverenginetools.h" -#include "sceneentity.h" -#include "appframework/IAppSystemGroup.h" -#include "scenefilecache/ISceneFileCache.h" -#include "tier2/tier2.h" -#include "particles/particles.h" -#include "gamestats.h" -#include "ixboxsystem.h" -#include "engine/imatchmaking.h" -#include "hl2orange.spa.h" -#include "particle_parse.h" -#ifndef NO_STEAM -#include "steam/steam_gameserver.h" -#endif -#include "tier3/tier3.h" -#include "serverbenchmark_base.h" -#include "querycache.h" - - -#ifdef TF_DLL -#include "gc_clientsystem.h" -#include "econ_item_inventory.h" -#include "steamworks_gamestats.h" -#include "tf/tf_gc_server.h" -#include "tf_gamerules.h" -#include "tf_lobby.h" -#include "player_vs_environment/tf_population_manager.h" - -extern ConVar tf_mm_trusted; -extern ConVar tf_mm_servermode; -#endif - -#ifdef USE_NAV_MESH -#include "nav_mesh.h" -#endif - -#ifdef NEXT_BOT -#include "NextBotManager.h" -#endif - -#ifdef USES_ECON_ITEMS -#include "econ_item_system.h" -#endif // USES_ECON_ITEMS - -#ifdef CSTRIKE_DLL // BOTPORT: TODO: move these ifdefs out -#include "bot/bot.h" -#endif - -#ifdef PORTAL -#include "prop_portal_shared.h" -#include "portal_player.h" -#endif - -#if defined( REPLAY_ENABLED ) -#include "replay/ireplaysystem.h" -#endif - -extern IToolFrameworkServer *g_pToolFrameworkServer; -extern IParticleSystemQuery *g_pParticleSystemQuery; - -extern ConVar commentary; - -#ifndef NO_STEAM -// this context is not available on dedicated servers -// WARNING! always check if interfaces are available before using -static CSteamAPIContext s_SteamAPIContext; -CSteamAPIContext *steamapicontext = &s_SteamAPIContext; - -// this context is not available on a pure client connected to a remote server. -// WARNING! always check if interfaces are available before using -static CSteamGameServerAPIContext s_SteamGameServerAPIContext; -CSteamGameServerAPIContext *steamgameserverapicontext = &s_SteamGameServerAPIContext; -#endif - -IUploadGameStats *gamestatsuploader = NULL; - -// memdbgon must be the last include file in a .cpp file!!! -#include "tier0/memdbgon.h" - -CTimedEventMgr g_NetworkPropertyEventMgr; - -ISaveRestoreBlockHandler *GetEventQueueSaveRestoreBlockHandler(); -ISaveRestoreBlockHandler *GetCommentarySaveRestoreBlockHandler(); - -CUtlLinkedList g_MapEntityRefs; - -// Engine interfaces. -IVEngineServer *engine = NULL; -IVoiceServer *g_pVoiceServer = NULL; -#if !defined(_STATIC_LINKED) -IFileSystem *filesystem = NULL; -#else -extern IFileSystem *filesystem; -#endif -INetworkStringTableContainer *networkstringtable = NULL; -IStaticPropMgrServer *staticpropmgr = NULL; -IUniformRandomStream *random = NULL; -IEngineSound *enginesound = NULL; -ISpatialPartition *partition = NULL; -IVModelInfo *modelinfo = NULL; -IEngineTrace *enginetrace = NULL; -IGameEventManager2 *gameeventmanager = NULL; -IDataCache *datacache = NULL; -IVDebugOverlay * debugoverlay = NULL; -ISoundEmitterSystemBase *soundemitterbase = NULL; -IServerPluginHelpers *serverpluginhelpers = NULL; -IServerEngineTools *serverenginetools = NULL; -ISceneFileCache *scenefilecache = NULL; -IXboxSystem *xboxsystem = NULL; // Xbox 360 only -IMatchmaking *matchmaking = NULL; // Xbox 360 only -#if defined( REPLAY_ENABLED ) -IReplaySystem *g_pReplay = NULL; -IServerReplayContext *g_pReplayServerContext = NULL; -#endif - -IGameSystem *SoundEmitterSystem(); - -bool ModelSoundsCacheInit(); -void ModelSoundsCacheShutdown(); - -void SceneManager_ClientActive( CBasePlayer *player ); - -class IMaterialSystem; -class IStudioRender; - -#ifdef _DEBUG -static ConVar s_UseNetworkVars( "UseNetworkVars", "1", FCVAR_CHEAT, "For profiling, toggle network vars." ); -#endif - -extern ConVar sv_noclipduringpause; -ConVar sv_massreport( "sv_massreport", "0" ); -ConVar sv_force_transmit_ents( "sv_force_transmit_ents", "0", FCVAR_CHEAT | FCVAR_DEVELOPMENTONLY, "Will transmit all entities to client, regardless of PVS conditions (will still skip based on transmit flags, however)." ); - -ConVar sv_autosave( "sv_autosave", "1", 0, "Set to 1 to autosave game on level transition. Does not affect autosave triggers." ); -ConVar *sv_maxreplay = NULL; -static ConVar *g_pcv_commentary = NULL; -static ConVar *g_pcv_ThreadMode = NULL; -static ConVar *g_pcv_hideServer = NULL; - -// String tables -INetworkStringTable *g_pStringTableParticleEffectNames = NULL; -INetworkStringTable *g_pStringTableEffectDispatch = NULL; -INetworkStringTable *g_pStringTableVguiScreen = NULL; -INetworkStringTable *g_pStringTableMaterials = NULL; -INetworkStringTable *g_pStringTableInfoPanel = NULL; -INetworkStringTable *g_pStringTableClientSideChoreoScenes = NULL; -INetworkStringTable *g_pStringTableServerMapCycle = NULL; - -#ifdef TF_DLL -INetworkStringTable *g_pStringTableServerPopFiles = NULL; -INetworkStringTable *g_pStringTableServerMapCycleMvM = NULL; -#endif - -CStringTableSaveRestoreOps g_VguiScreenStringOps; - -// Holds global variables shared between engine and game. -CGlobalVars *gpGlobals; -edict_t *g_pDebugEdictBase = 0; -static int g_nCommandClientIndex = 0; - -// The chapter number of the current -static int g_nCurrentChapterIndex = -1; - -#ifdef _DEBUG -static ConVar sv_showhitboxes( "sv_showhitboxes", "-1", FCVAR_CHEAT, "Send server-side hitboxes for specified entity to client (NOTE: this uses lots of bandwidth, use on listen server only)." ); -#endif - -void PrecachePointTemplates(); - -static ClientPutInServerOverrideFn g_pClientPutInServerOverride = NULL; -static void UpdateChapterRestrictions( const char *mapname ); - -static void UpdateRichPresence ( void ); - - -#if !defined( _XBOX ) // Don't doubly define this symbol. -CSharedEdictChangeInfo *g_pSharedChangeInfo = NULL; - -#endif - -IChangeInfoAccessor *CBaseEdict::GetChangeAccessor() -{ - return engine->GetChangeAccessor( (const edict_t *)this ); -} - -const IChangeInfoAccessor *CBaseEdict::GetChangeAccessor() const -{ - return engine->GetChangeAccessor( (const edict_t *)this ); -} - -const char *GetHintTypeDescription( CAI_Hint *pHint ); - -void ClientPutInServerOverride( ClientPutInServerOverrideFn fn ) -{ - g_pClientPutInServerOverride = fn; -} - -ConVar ai_post_frame_navigation( "ai_post_frame_navigation", "0" ); -class CPostFrameNavigationHook; -extern CPostFrameNavigationHook *PostFrameNavigationSystem( void ); - -//----------------------------------------------------------------------------- -// Purpose: -// Output : int -//----------------------------------------------------------------------------- -int UTIL_GetCommandClientIndex( void ) -{ - // -1 == unknown,dedicated server console - // 0 == player 1 - - // Convert to 1 based offset - return (g_nCommandClientIndex+1); -} - -//----------------------------------------------------------------------------- -// Purpose: -// Output : CBasePlayer -//----------------------------------------------------------------------------- -CBasePlayer *UTIL_GetCommandClient( void ) -{ - int idx = UTIL_GetCommandClientIndex(); - if ( idx > 0 ) - { - return UTIL_PlayerByIndex( idx ); - } - - // HLDS console issued command - return NULL; -} - -//----------------------------------------------------------------------------- -// Purpose: Retrieves the MOD directory for the active game (ie. "hl2") -//----------------------------------------------------------------------------- - -bool UTIL_GetModDir( char *lpszTextOut, unsigned int nSize ) -{ - // Must pass in a buffer at least large enough to hold the desired string - const char *pGameDir = CommandLine()->ParmValue( "-game", "hl2" ); - Assert( strlen(pGameDir) <= nSize ); - if ( strlen(pGameDir) > nSize ) - return false; - - Q_strncpy( lpszTextOut, pGameDir, nSize ); - if ( Q_strnchr( lpszTextOut, '/', nSize ) || Q_strnchr( lpszTextOut, '\\', nSize ) ) - { - // Strip the last directory off (which will be our game dir) - Q_StripLastDir( lpszTextOut, nSize ); - - // Find the difference in string lengths and take that difference from the original string as the mod dir - int dirlen = Q_strlen( lpszTextOut ); - Q_strncpy( lpszTextOut, pGameDir + dirlen, Q_strlen( pGameDir ) - dirlen + 1 ); - } - - return true; -} - -extern void InitializeCvars( void ); - -CBaseEntity* FindPickerEntity( CBasePlayer* pPlayer ); -CAI_Node* FindPickerAINode( CBasePlayer* pPlayer, NodeType_e nNodeType ); -CAI_Link* FindPickerAILink( CBasePlayer* pPlayer ); -float GetFloorZ(const Vector &origin); -void UpdateAllClientData( void ); -void DrawMessageEntities(); - -#include "ai_network.h" - -// For now just using one big AI network -extern ConVar think_limit; - - -#if 0 -//----------------------------------------------------------------------------- -// Purpose: Draw output overlays for any measure sections -// Input : -//----------------------------------------------------------------------------- -void DrawMeasuredSections(void) -{ - int row = 1; - float rowheight = 0.025; - - CMeasureSection *p = CMeasureSection::GetList(); - while ( p ) - { - char str[256]; - Q_snprintf(str,sizeof(str),"%s",p->GetName()); - NDebugOverlay::ScreenText( 0.01,0.51+(row*rowheight),str, 255,255,255,255, 0.0 ); - - Q_snprintf(str,sizeof(str),"%5.2f\n",p->GetTime().GetMillisecondsF()); - //Q_snprintf(str,sizeof(str),"%3.3f\n",p->GetTime().GetSeconds() * 100.0 / engine->Time()); - NDebugOverlay::ScreenText( 0.28,0.51+(row*rowheight),str, 255,255,255,255, 0.0 ); - - Q_snprintf(str,sizeof(str),"%5.2f\n",p->GetMaxTime().GetMillisecondsF()); - //Q_snprintf(str,sizeof(str),"%3.3f\n",p->GetTime().GetSeconds() * 100.0 / engine->Time()); - NDebugOverlay::ScreenText( 0.34,0.51+(row*rowheight),str, 255,255,255,255, 0.0 ); - - - row++; - - p = p->GetNext(); - } - - bool sort_reset = false; - - // Time to redo sort? - if ( measure_resort.GetFloat() > 0.0 && - engine->Time() >= CMeasureSection::m_dNextResort ) - { - // Redo it - CMeasureSection::SortSections(); - // Set next time - CMeasureSection::m_dNextResort = engine->Time() + measure_resort.GetFloat(); - // Flag to reset sort accumulator, too - sort_reset = true; - } - - // Iterate through the sections now - p = CMeasureSection::GetList(); - while ( p ) - { - // Update max - p->UpdateMax(); - - // Reset regular accum. - p->Reset(); - // Reset sort accum less often - if ( sort_reset ) - { - p->SortReset(); - } - p = p->GetNext(); - } - -} -#endif - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -void DrawAllDebugOverlays( void ) -{ - // If in debug select mode print the selection entities name or classname - if (CBaseEntity::m_bInDebugSelect) - { - CBasePlayer* pPlayer = UTIL_PlayerByIndex( CBaseEntity::m_nDebugPlayer ); - - if (pPlayer) - { - // First try to trace a hull to an entity - CBaseEntity *pEntity = FindPickerEntity( pPlayer ); - - if ( pEntity ) - { - pEntity->DrawDebugTextOverlays(); - pEntity->DrawBBoxOverlay(); - pEntity->SendDebugPivotOverlay(); - } - } - } - - // -------------------------------------------------------- - // Draw debug overlay lines - // -------------------------------------------------------- - UTIL_DrawOverlayLines(); - - // ------------------------------------------------------------------------ - // If in wc_edit mode draw a box to highlight which node I'm looking at - // ------------------------------------------------------------------------ - if (engine->IsInEditMode()) - { - CBasePlayer* pPlayer = UTIL_PlayerByIndex( CBaseEntity::m_nDebugPlayer ); - if (pPlayer) - { - if (g_pAINetworkManager->GetEditOps()->m_bLinkEditMode) - { - CAI_Link* pAILink = FindPickerAILink(pPlayer); - if (pAILink) - { - // For now just using one big AI network - Vector startPos = g_pBigAINet->GetNode(pAILink->m_iSrcID)->GetPosition(g_pAINetworkManager->GetEditOps()->m_iHullDrawNum); - Vector endPos = g_pBigAINet->GetNode(pAILink->m_iDestID)->GetPosition(g_pAINetworkManager->GetEditOps()->m_iHullDrawNum); - Vector linkDir = startPos-endPos; - float linkLen = VectorNormalize( linkDir ); - - // Draw in green if link that's been turned off - if (pAILink->m_LinkInfo & bits_LINK_OFF) - { - NDebugOverlay::BoxDirection(startPos, Vector(-4,-4,-4), Vector(-linkLen,4,4), linkDir, 0,255,0,40,0); - } - else - { - NDebugOverlay::BoxDirection(startPos, Vector(-4,-4,-4), Vector(-linkLen,4,4), linkDir, 255,0,0,40,0); - } - } - } - else - { - CAI_Node* pAINode; - if (g_pAINetworkManager->GetEditOps()->m_bAirEditMode) - { - pAINode = FindPickerAINode(pPlayer,NODE_AIR); - } - else - { - pAINode = FindPickerAINode(pPlayer,NODE_GROUND); - } - - if (pAINode) - { - Vector vecPos = pAINode->GetPosition(g_pAINetworkManager->GetEditOps()->m_iHullDrawNum); - NDebugOverlay::Box( vecPos, Vector(-8,-8,-8), Vector(8,8,8), 255,0,0,40,0); - - if ( pAINode->GetHint() ) - { - CBaseEntity *pEnt = (CBaseEntity *)pAINode->GetHint(); - if ( pEnt->GetEntityName() != NULL_STRING ) - { - NDebugOverlay::Text( vecPos + Vector(0,0,6), STRING(pEnt->GetEntityName()), false, 0 ); - } - NDebugOverlay::Text( vecPos, GetHintTypeDescription( pAINode->GetHint() ), false, 0 ); - } - } - } - // ------------------------------------ - // If in air edit mode draw guide line - // ------------------------------------ - if (g_pAINetworkManager->GetEditOps()->m_bAirEditMode) - { - UTIL_DrawPositioningOverlay(g_pAINetworkManager->GetEditOps()->m_flAirEditDistance); - } - else - { - NDebugOverlay::DrawGroundCrossHairOverlay(); - } - } - } - - // For not just using one big AI Network - if ( g_pAINetworkManager ) - { - g_pAINetworkManager->GetEditOps()->DrawAINetworkOverlay(); - } - - // PERFORMANCE: only do this in developer mode - if ( g_pDeveloper->GetInt() && !engine->IsDedicatedServer() ) - { - // iterate through all objects for debug overlays - const CEntInfo *pInfo = gEntList.FirstEntInfo(); - - for ( ;pInfo; pInfo = pInfo->m_pNext ) - { - CBaseEntity *ent = (CBaseEntity *)pInfo->m_pEntity; - // HACKHACK: to flag off these calls - if ( ent->m_debugOverlays || ent->m_pTimedOverlay ) - { - MDLCACHE_CRITICAL_SECTION(); - ent->DrawDebugGeometryOverlays(); - } - } - } - - if ( sv_massreport.GetInt() ) - { - // iterate through all objects for debug overlays - const CEntInfo *pInfo = gEntList.FirstEntInfo(); - - for ( ;pInfo; pInfo = pInfo->m_pNext ) - { - CBaseEntity *ent = (CBaseEntity *)pInfo->m_pEntity; - if (!ent->VPhysicsGetObject()) - continue; - - char tempstr[512]; - Q_snprintf(tempstr, sizeof(tempstr),"%s: Mass: %.2f kg / %.2f lb (%s)", - STRING( ent->GetModelName() ), ent->VPhysicsGetObject()->GetMass(), - kg2lbs(ent->VPhysicsGetObject()->GetMass()), - GetMassEquivalent(ent->VPhysicsGetObject()->GetMass())); - ent->EntityText(0, tempstr, 0); - } - } - - // A hack to draw point_message entities w/o developer required - DrawMessageEntities(); -} - -CServerGameDLL g_ServerGameDLL; -// INTERFACEVERSION_SERVERGAMEDLL_VERSION_8 is compatible with the latest since we're only adding things to the end, so expose that as well. -EXPOSE_SINGLE_INTERFACE_GLOBALVAR(CServerGameDLL, IServerGameDLL008, INTERFACEVERSION_SERVERGAMEDLL_VERSION_8, g_ServerGameDLL ); -EXPOSE_SINGLE_INTERFACE_GLOBALVAR(CServerGameDLL, IServerGameDLL, INTERFACEVERSION_SERVERGAMEDLL, g_ServerGameDLL); - -// When bumping the version to this interface, check that our assumption is still valid and expose the older version in the same way -COMPILE_TIME_ASSERT( INTERFACEVERSION_SERVERGAMEDLL_INT == 9 ); - -bool CServerGameDLL::DLLInit( CreateInterfaceFn appSystemFactory, - CreateInterfaceFn physicsFactory, CreateInterfaceFn fileSystemFactory, - CGlobalVars *pGlobals) -{ - ConnectTier1Libraries( &appSystemFactory, 1 ); - ConnectTier2Libraries( &appSystemFactory, 1 ); - ConnectTier3Libraries( &appSystemFactory, 1 ); - - // Connected in ConnectTier1Libraries - if ( cvar == NULL ) - return false; - -#ifndef _X360 - s_SteamAPIContext.Init(); - s_SteamGameServerAPIContext.Init(); -#endif - - // init each (seperated for ease of debugging) - if ( (engine = (IVEngineServer*)appSystemFactory(INTERFACEVERSION_VENGINESERVER, NULL)) == NULL ) - return false; - if ( (g_pVoiceServer = (IVoiceServer*)appSystemFactory(INTERFACEVERSION_VOICESERVER, NULL)) == NULL ) - return false; - if ( (networkstringtable = (INetworkStringTableContainer *)appSystemFactory(INTERFACENAME_NETWORKSTRINGTABLESERVER,NULL)) == NULL ) - return false; - if ( (staticpropmgr = (IStaticPropMgrServer *)appSystemFactory(INTERFACEVERSION_STATICPROPMGR_SERVER,NULL)) == NULL ) - return false; - if ( (random = (IUniformRandomStream *)appSystemFactory(VENGINE_SERVER_RANDOM_INTERFACE_VERSION, NULL)) == NULL ) - return false; - if ( (enginesound = (IEngineSound *)appSystemFactory(IENGINESOUND_SERVER_INTERFACE_VERSION, NULL)) == NULL ) - return false; - if ( (partition = (ISpatialPartition *)appSystemFactory(INTERFACEVERSION_SPATIALPARTITION, NULL)) == NULL ) - return false; - if ( (modelinfo = (IVModelInfo *)appSystemFactory(VMODELINFO_SERVER_INTERFACE_VERSION, NULL)) == NULL ) - return false; - if ( (enginetrace = (IEngineTrace *)appSystemFactory(INTERFACEVERSION_ENGINETRACE_SERVER,NULL)) == NULL ) - return false; - if ( (filesystem = (IFileSystem *)fileSystemFactory(FILESYSTEM_INTERFACE_VERSION,NULL)) == NULL ) - return false; - if ( (gameeventmanager = (IGameEventManager2 *)appSystemFactory(INTERFACEVERSION_GAMEEVENTSMANAGER2,NULL)) == NULL ) - return false; - if ( (datacache = (IDataCache*)appSystemFactory(DATACACHE_INTERFACE_VERSION, NULL )) == NULL ) - return false; - if ( (soundemitterbase = (ISoundEmitterSystemBase *)appSystemFactory(SOUNDEMITTERSYSTEM_INTERFACE_VERSION, NULL)) == NULL ) - return false; -#ifndef _XBOX - if ( (gamestatsuploader = (IUploadGameStats *)appSystemFactory( INTERFACEVERSION_UPLOADGAMESTATS, NULL )) == NULL ) - return false; -#endif - if ( !mdlcache ) - return false; - if ( (serverpluginhelpers = (IServerPluginHelpers *)appSystemFactory(INTERFACEVERSION_ISERVERPLUGINHELPERS, NULL)) == NULL ) - return false; - if ( (scenefilecache = (ISceneFileCache *)appSystemFactory( SCENE_FILE_CACHE_INTERFACE_VERSION, NULL )) == NULL ) - return false; - if ( IsX360() && (xboxsystem = (IXboxSystem *)appSystemFactory( XBOXSYSTEM_INTERFACE_VERSION, NULL )) == NULL ) - return false; - if ( IsX360() && (matchmaking = (IMatchmaking *)appSystemFactory( VENGINE_MATCHMAKING_VERSION, NULL )) == NULL ) - return false; - - // If not running dedicated, grab the engine vgui interface - if ( !engine->IsDedicatedServer() ) - { -#ifdef _WIN32 - // This interface is optional, and is only valid when running with -tools - serverenginetools = ( IServerEngineTools * )appSystemFactory( VSERVERENGINETOOLS_INTERFACE_VERSION, NULL ); -#endif - } - - // Yes, both the client and game .dlls will try to Connect, the soundemittersystem.dll will handle this gracefully - if ( !soundemitterbase->Connect( appSystemFactory ) ) - return false; - - // cache the globals - gpGlobals = pGlobals; - - g_pSharedChangeInfo = engine->GetSharedEdictChangeInfo(); - - MathLib_Init( 2.2f, 2.2f, 0.0f, 2.0f ); - - // save these in case other system inits need them - factorylist_t factories; - factories.engineFactory = appSystemFactory; - factories.fileSystemFactory = fileSystemFactory; - factories.physicsFactory = physicsFactory; - FactoryList_Store( factories ); - - // load used game events - gameeventmanager->LoadEventsFromFile("resource/gameevents.res"); - - // init the cvar list first in case inits want to reference them - InitializeCvars(); - - // Initialize the particle system - if ( !g_pParticleSystemMgr->Init( g_pParticleSystemQuery ) ) - { - return false; - } - - sv_cheats = g_pCVar->FindVar( "sv_cheats" ); - if ( !sv_cheats ) - return false; - - g_pcv_commentary = g_pCVar->FindVar( "commentary" ); - g_pcv_ThreadMode = g_pCVar->FindVar( "host_thread_mode" ); - g_pcv_hideServer = g_pCVar->FindVar( "hide_server" ); - - sv_maxreplay = g_pCVar->FindVar( "sv_maxreplay" ); - - g_pGameSaveRestoreBlockSet->AddBlockHandler( GetEntitySaveRestoreBlockHandler() ); - g_pGameSaveRestoreBlockSet->AddBlockHandler( GetPhysSaveRestoreBlockHandler() ); - g_pGameSaveRestoreBlockSet->AddBlockHandler( GetAISaveRestoreBlockHandler() ); - g_pGameSaveRestoreBlockSet->AddBlockHandler( GetTemplateSaveRestoreBlockHandler() ); - g_pGameSaveRestoreBlockSet->AddBlockHandler( GetDefaultResponseSystemSaveRestoreBlockHandler() ); - g_pGameSaveRestoreBlockSet->AddBlockHandler( GetCommentarySaveRestoreBlockHandler() ); - g_pGameSaveRestoreBlockSet->AddBlockHandler( GetEventQueueSaveRestoreBlockHandler() ); - g_pGameSaveRestoreBlockSet->AddBlockHandler( GetAchievementSaveRestoreBlockHandler() ); - - // The string system must init first + shutdown last - IGameSystem::Add( GameStringSystem() ); - - // Physics must occur before the sound envelope manager - IGameSystem::Add( PhysicsGameSystem() ); - - // Used to service deferred navigation queries for NPCs - IGameSystem::Add( (IGameSystem *) PostFrameNavigationSystem() ); - - // Add game log system - IGameSystem::Add( GameLogSystem() ); -#ifndef _XBOX - // Add HLTV director - IGameSystem::Add( HLTVDirectorSystem() ); -#endif - // Add sound emitter - IGameSystem::Add( SoundEmitterSystem() ); - - // load Mod specific game events ( MUST be before InitAllSystems() so it can pickup the mod specific events) - gameeventmanager->LoadEventsFromFile("resource/ModEvents.res"); - -#ifdef CSTRIKE_DLL // BOTPORT: TODO: move these ifdefs out - InstallBotControl(); -#endif - - if ( !IGameSystem::InitAllSystems() ) - return false; - -#if defined( REPLAY_ENABLED ) - if ( gameeventmanager->LoadEventsFromFile( "resource/replayevents.res" ) <= 0 ) - { - Warning( "\n*\n* replayevents.res MISSING.\n*\n\n" ); - return false; - } -#endif - - // Due to dependencies, these are not autogamesystems - if ( !ModelSoundsCacheInit() ) - { - return false; - } - - InvalidateQueryCache(); - - // Parse the particle manifest file & register the effects within it - ParseParticleEffects( false, false ); - - // try to get debug overlay, may be NULL if on HLDS - debugoverlay = (IVDebugOverlay *)appSystemFactory( VDEBUG_OVERLAY_INTERFACE_VERSION, NULL ); - -#ifndef _XBOX -#ifdef USE_NAV_MESH - // create the Navigation Mesh interface - TheNavMesh = NavMeshFactory(); -#endif - - // init the gamestatsupload connection - gamestatsuploader->InitConnection(); -#endif - - return true; -} - -void CServerGameDLL::PostInit() -{ - IGameSystem::PostInitAllSystems(); -} - -void CServerGameDLL::DLLShutdown( void ) -{ - - // Due to dependencies, these are not autogamesystems - ModelSoundsCacheShutdown(); - - g_pGameSaveRestoreBlockSet->RemoveBlockHandler( GetAchievementSaveRestoreBlockHandler() ); - g_pGameSaveRestoreBlockSet->RemoveBlockHandler( GetCommentarySaveRestoreBlockHandler() ); - g_pGameSaveRestoreBlockSet->RemoveBlockHandler( GetEventQueueSaveRestoreBlockHandler() ); - g_pGameSaveRestoreBlockSet->RemoveBlockHandler( GetDefaultResponseSystemSaveRestoreBlockHandler() ); - g_pGameSaveRestoreBlockSet->RemoveBlockHandler( GetTemplateSaveRestoreBlockHandler() ); - g_pGameSaveRestoreBlockSet->RemoveBlockHandler( GetAISaveRestoreBlockHandler() ); - g_pGameSaveRestoreBlockSet->RemoveBlockHandler( GetPhysSaveRestoreBlockHandler() ); - g_pGameSaveRestoreBlockSet->RemoveBlockHandler( GetEntitySaveRestoreBlockHandler() ); - - char *pFilename = g_TextStatsMgr.GetStatsFilename(); - if ( !pFilename || !pFilename[0] ) - { - g_TextStatsMgr.SetStatsFilename( "stats.txt" ); - } - g_TextStatsMgr.WriteFile( filesystem ); - - IGameSystem::ShutdownAllSystems(); - -#ifdef CSTRIKE_DLL // BOTPORT: TODO: move these ifdefs out - RemoveBotControl(); -#endif - -#ifndef _XBOX -#ifdef USE_NAV_MESH - // destroy the Navigation Mesh interface - if ( TheNavMesh ) - { - delete TheNavMesh; - TheNavMesh = NULL; - } -#endif - // reset (shutdown) the gamestatsupload connection - gamestatsuploader->InitConnection(); -#endif - -#ifndef _X360 - s_SteamAPIContext.Clear(); // Steam API context shutdown - s_SteamGameServerAPIContext.Clear(); -#endif - - gameeventmanager = NULL; - - DisconnectTier3Libraries(); - DisconnectTier2Libraries(); - ConVar_Unregister(); - DisconnectTier1Libraries(); -} - -bool CServerGameDLL::ReplayInit( CreateInterfaceFn fnReplayFactory ) -{ -#if defined( REPLAY_ENABLED ) - if ( !IsPC() ) - return false; - if ( (g_pReplay = ( IReplaySystem *)fnReplayFactory( REPLAY_INTERFACE_VERSION, NULL )) == NULL ) - return false; - if ( (g_pReplayServerContext = g_pReplay->SV_GetContext()) == NULL ) - return false; - return true; -#else - return false; -#endif -} - -//----------------------------------------------------------------------------- -// Purpose: See shareddefs.h for redefining this. Don't even think about it, though, for HL2. Or you will pay. ywb 9/22/03 -// Output : float -//----------------------------------------------------------------------------- -float CServerGameDLL::GetTickInterval( void ) const -{ - float tickinterval = DEFAULT_TICK_INTERVAL; - -//============================================================================= -// HPE_BEGIN: -// [Forrest] For Counter-Strike, set default tick rate of 66 and removed -tickrate command line parameter. -//============================================================================= -// Ignoring this for now, server ops are abusing it -#if !defined( TF_DLL ) && !defined( CSTRIKE_DLL ) && !defined( DOD_DLL ) -//============================================================================= -// HPE_END -//============================================================================= - // override if tick rate specified in command line - if ( CommandLine()->CheckParm( "-tickrate" ) ) - { - float tickrate = CommandLine()->ParmValue( "-tickrate", 0 ); - if ( tickrate > 10 ) - tickinterval = 1.0f / tickrate; - } -#endif - - return tickinterval; -} - -// This is called when a new game is started. (restart, map) -bool CServerGameDLL::GameInit( void ) -{ - ResetGlobalState(); - engine->ServerCommand( "exec game.cfg\n" ); - engine->ServerExecute( ); - CBaseEntity::sm_bAccurateTriggerBboxChecks = true; - - IGameEvent *event = gameeventmanager->CreateEvent( "game_init" ); - if ( event ) - { - gameeventmanager->FireEvent( event ); - } - - return true; -} - -// This is called when a game ends (server disconnect, death, restart, load) -// NOT on level transitions within a game -void CServerGameDLL::GameShutdown( void ) -{ - ResetGlobalState(); -} - -static bool g_OneWayTransition = false; -void Game_SetOneWayTransition( void ) -{ - g_OneWayTransition = true; -} - -static CUtlVector g_RestoredEntities; -// just for debugging, assert that this is the only time this function is called -static bool g_InRestore = false; - -void AddRestoredEntity( CBaseEntity *pEntity ) -{ - Assert(g_InRestore); - if ( !pEntity ) - return; - - g_RestoredEntities.AddToTail( EHANDLE(pEntity) ); -} - -void EndRestoreEntities() -{ - if ( !g_InRestore ) - return; - - // The entire hierarchy is restored, so we can call GetAbsOrigin again. - //CBaseEntity::SetAbsQueriesValid( true ); - - // Call all entities' OnRestore handlers - for ( int i = g_RestoredEntities.Count()-1; i >=0; --i ) - { - CBaseEntity *pEntity = g_RestoredEntities[i].Get(); - if ( pEntity && !pEntity->IsDormant() ) - { - MDLCACHE_CRITICAL_SECTION(); - pEntity->OnRestore(); - } - } - - g_RestoredEntities.Purge(); - - IGameSystem::OnRestoreAllSystems(); - - g_InRestore = false; - gEntList.CleanupDeleteList(); - - // HACKHACK: UNDONE: We need to redesign the main loop with respect to save/load/server activate - g_ServerGameDLL.ServerActivate( NULL, 0, 0 ); - CBaseEntity::SetAllowPrecache( false ); -} - -void BeginRestoreEntities() -{ - if ( g_InRestore ) - { - DevMsg( "BeginRestoreEntities without previous EndRestoreEntities.\n" ); - gEntList.CleanupDeleteList(); - } - g_RestoredEntities.Purge(); - g_InRestore = true; - - CBaseEntity::SetAllowPrecache( true ); - - // No calls to GetAbsOrigin until the entire hierarchy is restored! - //CBaseEntity::SetAbsQueriesValid( false ); -} - -//----------------------------------------------------------------------------- -// Purpose: This prevents sv.tickcount/gpGlobals->tickcount from advancing during restore which -// would cause a lot of the NPCs to fast forward their think times to the same -// tick due to some ticks being elapsed during restore where there was no simulation going on -//----------------------------------------------------------------------------- -bool CServerGameDLL::IsRestoring() -{ - return g_InRestore; -} - -// Called any time a new level is started (after GameInit() also on level transitions within a game) -bool CServerGameDLL::LevelInit( const char *pMapName, char const *pMapEntities, char const *pOldLevel, char const *pLandmarkName, bool loadGame, bool background ) -{ - VPROF("CServerGameDLL::LevelInit"); - -#ifdef USES_ECON_ITEMS - GameItemSchema_t *pItemSchema = ItemSystem()->GetItemSchema(); - if ( pItemSchema ) - { - pItemSchema->BInitFromDelayedBuffer(); - } -#endif // USES_ECON_ITEMS - - ResetWindspeed(); - UpdateChapterRestrictions( pMapName ); - - if ( IsX360() && !background && (gpGlobals->maxClients == 1) && (g_nCurrentChapterIndex >= 0) ) - { - // Single player games tell xbox live what game & chapter the user is playing - UpdateRichPresence(); - } - - //Tony; parse custom manifest if exists! - ParseParticleEffectsMap( pMapName, false ); - - // IGameSystem::LevelInitPreEntityAllSystems() is called when the world is precached - // That happens either in LoadGameState() or in MapEntity_ParseAllEntities() - if ( loadGame ) - { - if ( pOldLevel ) - { - gpGlobals->eLoadType = MapLoad_Transition; - } - else - { - gpGlobals->eLoadType = MapLoad_LoadGame; - } - - BeginRestoreEntities(); - if ( !engine->LoadGameState( pMapName, 1 ) ) - { - if ( pOldLevel ) - { - MapEntity_ParseAllEntities( pMapEntities ); - } - else - { - // Regular save load case - return false; - } - } - - if ( pOldLevel ) - { - engine->LoadAdjacentEnts( pOldLevel, pLandmarkName ); - } - - if ( g_OneWayTransition ) - { - engine->ClearSaveDirAfterClientLoad(); - } - - if ( pOldLevel && sv_autosave.GetBool() == true ) - { - // This is a single-player style level transition. - // Queue up an autosave one second into the level - CBaseEntity *pAutosave = CBaseEntity::Create( "logic_autosave", vec3_origin, vec3_angle, NULL ); - if ( pAutosave ) - { - g_EventQueue.AddEvent( pAutosave, "Save", 1.0, NULL, NULL ); - g_EventQueue.AddEvent( pAutosave, "Kill", 1.1, NULL, NULL ); - } - } - } - else - { - if ( background ) - { - gpGlobals->eLoadType = MapLoad_Background; - } - else - { - gpGlobals->eLoadType = MapLoad_NewGame; - } - - // Clear out entity references, and parse the entities into it. - g_MapEntityRefs.Purge(); - CMapLoadEntityFilter filter; - MapEntity_ParseAllEntities( pMapEntities, &filter ); - - g_pServerBenchmark->StartBenchmark(); - - // Now call the mod specific parse - LevelInit_ParseAllEntities( pMapEntities ); - } - - // Check low violence settings for this map - g_RagdollLVManager.SetLowViolence( pMapName ); - - // Now that all of the active entities have been loaded in, precache any entities who need point_template parameters - // to be parsed (the above code has loaded all point_template entities) - PrecachePointTemplates(); - - // load MOTD from file into stringtable - LoadMessageOfTheDay(); - - // Sometimes an ent will Remove() itself during its precache, so RemoveImmediate won't happen. - // This makes sure those ents get cleaned up. - gEntList.CleanupDeleteList(); - - g_AIFriendliesTalkSemaphore.Release(); - g_AIFoesTalkSemaphore.Release(); - g_OneWayTransition = false; - - // clear any pending autosavedangerous - m_fAutoSaveDangerousTime = 0.0f; - m_fAutoSaveDangerousMinHealthToCommit = 0.0f; - return true; -} - -//----------------------------------------------------------------------------- -// Purpose: called after every level change and load game, iterates through all the -// active entities and gives them a chance to fix up their state -//----------------------------------------------------------------------------- -#ifdef DEBUG -bool g_bReceivedChainedActivate; -bool g_bCheckForChainedActivate; -#define BeginCheckChainedActivate() if (0) ; else { g_bCheckForChainedActivate = true; g_bReceivedChainedActivate = false; } -#define EndCheckChainedActivate( bCheck ) \ - if (0) ; else \ - { \ - if ( bCheck ) \ - { \ - char msg[ 1024 ]; \ - Q_snprintf( msg, sizeof( msg ), "Entity (%i/%s/%s) failed to call base class Activate()\n", pClass->entindex(), pClass->GetClassname(), STRING( pClass->GetEntityName() ) ); \ - AssertMsg( g_bReceivedChainedActivate == true, msg ); \ - } \ - g_bCheckForChainedActivate = false; \ - } -#else -#define BeginCheckChainedActivate() ((void)0) -#define EndCheckChainedActivate( bCheck ) ((void)0) -#endif - -void CServerGameDLL::ServerActivate( edict_t *pEdictList, int edictCount, int clientMax ) -{ - // HACKHACK: UNDONE: We need to redesign the main loop with respect to save/load/server activate - if ( g_InRestore ) - return; - - if ( gEntList.ResetDeleteList() != 0 ) - { - Msg( "ERROR: Entity delete queue not empty on level start!\n" ); - } - - for ( CBaseEntity *pClass = gEntList.FirstEnt(); pClass != NULL; pClass = gEntList.NextEnt(pClass) ) - { - if ( pClass && !pClass->IsDormant() ) - { - MDLCACHE_CRITICAL_SECTION(); - - BeginCheckChainedActivate(); - pClass->Activate(); - - // We don't care if it finished activating if it decided to remove itself. - EndCheckChainedActivate( !( pClass->GetEFlags() & EFL_KILLME ) ); - } - } - - IGameSystem::LevelInitPostEntityAllSystems(); - // No more precaching after PostEntityAllSystems!!! - CBaseEntity::SetAllowPrecache( false ); - - // only display the think limit when the game is run with "developer" mode set - if ( !g_pDeveloper->GetInt() ) - { - think_limit.SetValue( 0 ); - } - -#ifndef _XBOX -#ifdef USE_NAV_MESH - // load the Navigation Mesh for this map - TheNavMesh->Load(); - TheNavMesh->OnServerActivate(); -#endif -#endif - -#ifdef CSTRIKE_DLL // BOTPORT: TODO: move these ifdefs out - TheBots->ServerActivate(); -#endif - -#ifdef NEXT_BOT - TheNextBots().OnMapLoaded(); -#endif -} - -//----------------------------------------------------------------------------- -// Purpose: Called after the steam API has been activated post-level startup -//----------------------------------------------------------------------------- -void CServerGameDLL::GameServerSteamAPIActivated( void ) -{ -#ifndef NO_STEAM - steamgameserverapicontext->Init(); - if ( steamgameserverapicontext->SteamGameServer() && engine->IsDedicatedServer() ) - { - steamgameserverapicontext->SteamGameServer()->GetGameplayStats(); - } -#endif - -#ifdef TF_DLL - GCClientSystem()->GameServerActivate(); - InventoryManager()->GameServerSteamAPIActivated(); -#endif -} - -//----------------------------------------------------------------------------- -// Purpose: Called after the steam API has been activated post-level startup -//----------------------------------------------------------------------------- -void CServerGameDLL::GameServerSteamAPIShutdown( void ) -{ -#if !defined( NO_STEAM ) - if ( steamgameserverapicontext ) - { - steamgameserverapicontext->Clear(); - } -#endif -#ifdef TF_DLL - GCClientSystem()->Shutdown(); -#endif -} - -//----------------------------------------------------------------------------- -// Purpose: Called at the start of every game frame -//----------------------------------------------------------------------------- -ConVar trace_report( "trace_report", "0" ); - -void CServerGameDLL::GameFrame( bool simulating ) -{ - VPROF( "CServerGameDLL::GameFrame" ); - - // Don't run frames until fully restored - if ( g_InRestore ) - return; - - if ( CBaseEntity::IsSimulatingOnAlternateTicks() ) - { - // only run simulation on even numbered ticks - if ( gpGlobals->tickcount & 1 ) - { - UpdateAllClientData(); - return; - } - // If we're skipping frames, then the frametime is 2x the normal tick - gpGlobals->frametime *= 2.0f; - } - - float oldframetime = gpGlobals->frametime; - -#ifdef _DEBUG - // For profiling.. let them enable/disable the networkvar manual mode stuff. - g_bUseNetworkVars = s_UseNetworkVars.GetBool(); -#endif - - extern void GameStartFrame( void ); - extern void ServiceEventQueue( void ); - extern void Physics_RunThinkFunctions( bool simulating ); - - // Delete anything that was marked for deletion - // outside of server frameloop (e.g., in response to concommand) - gEntList.CleanupDeleteList(); - - IGameSystem::FrameUpdatePreEntityThinkAllSystems(); - GameStartFrame(); - -#ifndef _XBOX -#ifdef USE_NAV_MESH - TheNavMesh->Update(); -#endif - -#ifdef NEXT_BOT - TheNextBots().Update(); -#endif - - gamestatsuploader->UpdateConnection(); -#endif - - UpdateQueryCache(); - g_pServerBenchmark->UpdateBenchmark(); - - Physics_RunThinkFunctions( simulating ); - - IGameSystem::FrameUpdatePostEntityThinkAllSystems(); - - // UNDONE: Make these systems IGameSystems and move these calls into FrameUpdatePostEntityThink() - // service event queue, firing off any actions whos time has come - ServiceEventQueue(); - - // free all ents marked in think functions - gEntList.CleanupDeleteList(); - - // FIXME: Should this only occur on the final tick? - UpdateAllClientData(); - - if ( g_pGameRules ) - { - g_pGameRules->EndGameFrame(); - } - - if ( trace_report.GetBool() ) - { - int total = 0, totals[3]; - for ( int i = 0; i < 3; i++ ) - { - totals[i] = enginetrace->GetStatByIndex( i, true ); - if ( totals[i] > 0 ) - { - total += totals[i]; - } - } - - if ( total ) - { - Msg("Trace: %d, contents %d, enumerate %d\n", totals[0], totals[1], totals[2] ); - } - } - - // Any entities that detect network state changes on a timer do it here. - g_NetworkPropertyEventMgr.FireEvents(); - - gpGlobals->frametime = oldframetime; -} - -//----------------------------------------------------------------------------- -// Purpose: Called every frame even if not ticking -// Input : simulating - -//----------------------------------------------------------------------------- -void CServerGameDLL::PreClientUpdate( bool simulating ) -{ - if ( !simulating ) - return; - - /* - if (game_speeds.GetInt()) - { - DrawMeasuredSections(); - } - */ - -//#ifdef _DEBUG - allow this in release for now - DrawAllDebugOverlays(); -//#endif - - IGameSystem::PreClientUpdateAllSystems(); - -#ifdef _DEBUG - if ( sv_showhitboxes.GetInt() == -1 ) - return; - - if ( sv_showhitboxes.GetInt() == 0 ) - { - // assume it's text - CBaseEntity *pEntity = NULL; - - while (1) - { - pEntity = gEntList.FindEntityByName( pEntity, sv_showhitboxes.GetString() ); - if ( !pEntity ) - break; - - CBaseAnimating *anim = dynamic_cast< CBaseAnimating * >( pEntity ); - - if (anim) - { - anim->DrawServerHitboxes(); - } - } - return; - } - - CBaseAnimating *anim = dynamic_cast< CBaseAnimating * >( CBaseEntity::Instance( engine->PEntityOfEntIndex( sv_showhitboxes.GetInt() ) ) ); - if ( !anim ) - return; - - anim->DrawServerHitboxes(); -#endif -} - -void CServerGameDLL::Think( bool finalTick ) -{ - if ( m_fAutoSaveDangerousTime != 0.0f && m_fAutoSaveDangerousTime < gpGlobals->curtime ) - { - // The safety timer for a dangerous auto save has expired - CBasePlayer *pPlayer = UTIL_PlayerByIndex( 1 ); - - if ( pPlayer && ( pPlayer->GetDeathTime() == 0.0f || pPlayer->GetDeathTime() > gpGlobals->curtime ) - && !pPlayer->IsSinglePlayerGameEnding() - ) - { - if( pPlayer->GetHealth() >= m_fAutoSaveDangerousMinHealthToCommit ) - { - // The player isn't dead, so make the dangerous auto save safe - engine->ServerCommand( "autosavedangerousissafe\n" ); - } - } - - m_fAutoSaveDangerousTime = 0.0f; - m_fAutoSaveDangerousMinHealthToCommit = 0.0f; - } -} - -void CServerGameDLL::OnQueryCvarValueFinished( QueryCvarCookie_t iCookie, edict_t *pPlayerEntity, EQueryCvarValueStatus eStatus, const char *pCvarName, const char *pCvarValue ) -{ -} - - -// Called when a level is shutdown (including changing levels) -void CServerGameDLL::LevelShutdown( void ) -{ -#ifndef NO_STEAM - IGameSystem::LevelShutdownPreClearSteamAPIContextAllSystems(); - - steamgameserverapicontext->Clear(); -#endif - - g_pServerBenchmark->EndBenchmark(); - - MDLCACHE_CRITICAL_SECTION(); - IGameSystem::LevelShutdownPreEntityAllSystems(); - - // YWB: - // This entity pointer is going away now and is corrupting memory on level transitions/restarts - CSoundEnt::ShutdownSoundEnt(); - - gEntList.Clear(); - - InvalidateQueryCache(); - - IGameSystem::LevelShutdownPostEntityAllSystems(); - - // In case we quit out during initial load - CBaseEntity::SetAllowPrecache( false ); - - g_nCurrentChapterIndex = -1; - -#ifndef _XBOX -#ifdef USE_NAV_MESH - // reset the Navigation Mesh - if ( TheNavMesh ) - { - TheNavMesh->Reset(); - } -#endif -#endif -} - -//----------------------------------------------------------------------------- -// Purpose: -// Input : -// Output : ServerClass* -//----------------------------------------------------------------------------- -ServerClass* CServerGameDLL::GetAllServerClasses() -{ - return g_pServerClassHead; -} - - -const char *CServerGameDLL::GetGameDescription( void ) -{ - return ::GetGameDescription(); -} - -void CServerGameDLL::CreateNetworkStringTables( void ) -{ - // Create any shared string tables here (and only here!) - // E.g.: xxx = networkstringtable->CreateStringTable( "SceneStrings", 512 ); - g_pStringTableParticleEffectNames = networkstringtable->CreateStringTable( "ParticleEffectNames", MAX_PARTICLESYSTEMS_STRINGS ); - g_pStringTableEffectDispatch = networkstringtable->CreateStringTable( "EffectDispatch", MAX_EFFECT_DISPATCH_STRINGS ); - g_pStringTableVguiScreen = networkstringtable->CreateStringTable( "VguiScreen", MAX_VGUI_SCREEN_STRINGS ); - g_pStringTableMaterials = networkstringtable->CreateStringTable( "Materials", MAX_MATERIAL_STRINGS ); - g_pStringTableInfoPanel = networkstringtable->CreateStringTable( "InfoPanel", MAX_INFOPANEL_STRINGS ); - g_pStringTableClientSideChoreoScenes = networkstringtable->CreateStringTable( "Scenes", MAX_CHOREO_SCENES_STRINGS ); - g_pStringTableServerMapCycle = networkstringtable->CreateStringTable( "ServerMapCycle", 128 ); - -#ifdef TF_DLL - g_pStringTableServerPopFiles = networkstringtable->CreateStringTable( "ServerPopFiles", 128 ); - g_pStringTableServerMapCycleMvM = networkstringtable->CreateStringTable( "ServerMapCycleMvM", 128 ); -#endif - - bool bPopFilesValid = true; - (void)bPopFilesValid; // Avoid unreferenced variable warning - -#ifdef TF_DLL - bPopFilesValid = ( g_pStringTableServerPopFiles != NULL ); -#endif - - Assert( g_pStringTableParticleEffectNames && - g_pStringTableEffectDispatch && - g_pStringTableVguiScreen && - g_pStringTableMaterials && - g_pStringTableInfoPanel && - g_pStringTableClientSideChoreoScenes && - g_pStringTableServerMapCycle && - bPopFilesValid - ); - - // Need this so we have the error material always handy - PrecacheMaterial( "debug/debugempty" ); - Assert( GetMaterialIndex( "debug/debugempty" ) == 0 ); - - PrecacheParticleSystem( "error" ); // ensure error particle system is handy - Assert( GetParticleSystemIndex( "error" ) == 0 ); - - CreateNetworkStringTables_GameRules(); - - // Set up save/load utilities for string tables - g_VguiScreenStringOps.Init( g_pStringTableVguiScreen ); -} - -CSaveRestoreData *CServerGameDLL::SaveInit( int size ) -{ - return ::SaveInit(size); -} - -//----------------------------------------------------------------------------- -// Purpose: Saves data from a struct into a saverestore object, to be saved to disk -// Input : *pSaveData - the saverestore object -// char *pname - the name of the data to write -// *pBaseData - the struct into which the data is to be read -// *pFields - pointer to an array of data field descriptions -// fieldCount - the size of the array (number of field descriptions) -//----------------------------------------------------------------------------- -void CServerGameDLL::SaveWriteFields( CSaveRestoreData *pSaveData, const char *pname, void *pBaseData, datamap_t *pMap, typedescription_t *pFields, int fieldCount ) -{ - CSave saveHelper( pSaveData ); - saveHelper.WriteFields( pname, pBaseData, pMap, pFields, fieldCount ); -} - - -//----------------------------------------------------------------------------- -// Purpose: Reads data from a save/restore block into a structure -// Input : *pSaveData - the saverestore object -// char *pname - the name of the data to extract from -// *pBaseData - the struct into which the data is to be restored -// *pFields - pointer to an array of data field descriptions -// fieldCount - the size of the array (number of field descriptions) -//----------------------------------------------------------------------------- - -//----------------------------------------------------------------------------- - -void CServerGameDLL::SaveReadFields( CSaveRestoreData *pSaveData, const char *pname, void *pBaseData, datamap_t *pMap, typedescription_t *pFields, int fieldCount ) -{ - CRestore restoreHelper( pSaveData ); - restoreHelper.ReadFields( pname, pBaseData, pMap, pFields, fieldCount ); -} - -//----------------------------------------------------------------------------- - -void CServerGameDLL::SaveGlobalState( CSaveRestoreData *s ) -{ - ::SaveGlobalState(s); -} - -void CServerGameDLL::RestoreGlobalState(CSaveRestoreData *s) -{ - ::RestoreGlobalState(s); -} - -void CServerGameDLL::Save( CSaveRestoreData *s ) -{ - CSave saveHelper( s ); - g_pGameSaveRestoreBlockSet->Save( &saveHelper ); -} - -void CServerGameDLL::Restore( CSaveRestoreData *s, bool b) -{ - CRestore restore(s); - g_pGameSaveRestoreBlockSet->Restore( &restore, b ); - g_pGameSaveRestoreBlockSet->PostRestore(); -} - -//----------------------------------------------------------------------------- -// Purpose: -// Input : msg_type - -// *name - -// size - -// Output : Returns true on success, false on failure. -//----------------------------------------------------------------------------- - -bool CServerGameDLL::GetUserMessageInfo( int msg_type, char *name, int maxnamelength, int& size ) -{ - if ( !usermessages->IsValidIndex( msg_type ) ) - return false; - - Q_strncpy( name, usermessages->GetUserMessageName( msg_type ), maxnamelength ); - size = usermessages->GetUserMessageSize( msg_type ); - return true; -} - -CStandardSendProxies* CServerGameDLL::GetStandardSendProxies() -{ - return &g_StandardSendProxies; -} - -int CServerGameDLL::CreateEntityTransitionList( CSaveRestoreData *s, int a) -{ - CRestore restoreHelper( s ); - // save off file base - int base = restoreHelper.GetReadPos(); - - int movedCount = ::CreateEntityTransitionList(s, a); - if ( movedCount ) - { - g_pGameSaveRestoreBlockSet->CallBlockHandlerRestore( GetPhysSaveRestoreBlockHandler(), base, &restoreHelper, false ); - g_pGameSaveRestoreBlockSet->CallBlockHandlerRestore( GetAISaveRestoreBlockHandler(), base, &restoreHelper, false ); - } - - GetPhysSaveRestoreBlockHandler()->PostRestore(); - GetAISaveRestoreBlockHandler()->PostRestore(); - - return movedCount; -} - -void CServerGameDLL::PreSave( CSaveRestoreData *s ) -{ - g_pGameSaveRestoreBlockSet->PreSave( s ); -} - -#include "client_textmessage.h" - -// This little hack lets me marry BSP names to messages in titles.txt -typedef struct -{ - const char *pBSPName; - const char *pTitleName; -} TITLECOMMENT; - -// this list gets searched for the first partial match, so some are out of order -static TITLECOMMENT gTitleComments[] = -{ -#ifdef HL1_DLL - { "t0a0", "#T0A0TITLE" }, - { "c0a0", "#HL1_Chapter1_Title" }, - { "c1a0", "#HL1_Chapter2_Title" }, - { "c1a1", "#HL1_Chapter3_Title" }, - { "c1a2", "#HL1_Chapter4_Title" }, - { "c1a3", "#HL1_Chapter5_Title" }, - { "c1a4", "#HL1_Chapter6_Title" }, - { "c2a1", "#HL1_Chapter7_Title" }, - { "c2a2", "#HL1_Chapter8_Title" }, - { "c2a3", "#HL1_Chapter9_Title" }, - { "c2a4d", "#HL1_Chapter11_Title" }, // These must appear before "C2A4" so all other map names starting with C2A4 get that title - { "c2a4e", "#HL1_Chapter11_Title" }, - { "c2a4f", "#HL1_Chapter11_Title" }, - { "c2a4g", "#HL1_Chapter11_Title" }, - { "c2a4", "#HL1_Chapter10_Title" }, - { "c2a5", "#HL1_Chapter12_Title" }, - { "c3a1", "#HL1_Chapter13_Title" }, - { "c3a2", "#HL1_Chapter14_Title" }, - { "c4a1a", "#HL1_Chapter17_Title" }, // Order is important, see above - { "c4a1b", "#HL1_Chapter17_Title" }, - { "c4a1c", "#HL1_Chapter17_Title" }, - { "c4a1d", "#HL1_Chapter17_Title" }, - { "c4a1e", "#HL1_Chapter17_Title" }, - { "c4a1", "#HL1_Chapter15_Title" }, - { "c4a2", "#HL1_Chapter16_Title" }, - { "c4a3", "#HL1_Chapter18_Title" }, - { "c5a1", "#HL1_Chapter19_Title" }, -#elif defined PORTAL - { "testchmb_a_00", "#Portal_Chapter1_Title" }, - { "testchmb_a_01", "#Portal_Chapter1_Title" }, - { "testchmb_a_02", "#Portal_Chapter2_Title" }, - { "testchmb_a_03", "#Portal_Chapter2_Title" }, - { "testchmb_a_04", "#Portal_Chapter3_Title" }, - { "testchmb_a_05", "#Portal_Chapter3_Title" }, - { "testchmb_a_06", "#Portal_Chapter4_Title" }, - { "testchmb_a_07", "#Portal_Chapter4_Title" }, - { "testchmb_a_08_advanced", "#Portal_Chapter5_Title" }, - { "testchmb_a_08", "#Portal_Chapter5_Title" }, - { "testchmb_a_09_advanced", "#Portal_Chapter6_Title" }, - { "testchmb_a_09", "#Portal_Chapter6_Title" }, - { "testchmb_a_10_advanced", "#Portal_Chapter7_Title" }, - { "testchmb_a_10", "#Portal_Chapter7_Title" }, - { "testchmb_a_11_advanced", "#Portal_Chapter8_Title" }, - { "testchmb_a_11", "#Portal_Chapter8_Title" }, - { "testchmb_a_13_advanced", "#Portal_Chapter9_Title" }, - { "testchmb_a_13", "#Portal_Chapter9_Title" }, - { "testchmb_a_14_advanced", "#Portal_Chapter10_Title" }, - { "testchmb_a_14", "#Portal_Chapter10_Title" }, - { "testchmb_a_15", "#Portal_Chapter11_Title" }, - { "escape_", "#Portal_Chapter11_Title" }, - { "background2", "#Portal_Chapter12_Title" }, -#else - { "intro", "#HL2_Chapter1_Title" }, - - { "d1_trainstation_05", "#HL2_Chapter2_Title" }, - { "d1_trainstation_06", "#HL2_Chapter2_Title" }, - - { "d1_trainstation_", "#HL2_Chapter1_Title" }, - - { "d1_canals_06", "#HL2_Chapter4_Title" }, - { "d1_canals_07", "#HL2_Chapter4_Title" }, - { "d1_canals_08", "#HL2_Chapter4_Title" }, - { "d1_canals_09", "#HL2_Chapter4_Title" }, - { "d1_canals_1", "#HL2_Chapter4_Title" }, - - { "d1_canals_0", "#HL2_Chapter3_Title" }, - - { "d1_eli_", "#HL2_Chapter5_Title" }, - - { "d1_town_", "#HL2_Chapter6_Title" }, - - { "d2_coast_09", "#HL2_Chapter8_Title" }, - { "d2_coast_1", "#HL2_Chapter8_Title" }, - { "d2_prison_01", "#HL2_Chapter8_Title" }, - - { "d2_coast_", "#HL2_Chapter7_Title" }, - - { "d2_prison_06", "#HL2_Chapter9a_Title" }, - { "d2_prison_07", "#HL2_Chapter9a_Title" }, - { "d2_prison_08", "#HL2_Chapter9a_Title" }, - - { "d2_prison_", "#HL2_Chapter9_Title" }, - - { "d3_c17_01", "#HL2_Chapter9a_Title" }, - { "d3_c17_09", "#HL2_Chapter11_Title" }, - { "d3_c17_1", "#HL2_Chapter11_Title" }, - - { "d3_c17_", "#HL2_Chapter10_Title" }, - - { "d3_citadel_", "#HL2_Chapter12_Title" }, - - { "d3_breen_", "#HL2_Chapter13_Title" }, - { "credits", "#HL2_Chapter14_Title" }, - - { "ep1_citadel_00", "#episodic_Chapter1_Title" }, - { "ep1_citadel_01", "#episodic_Chapter1_Title" }, - { "ep1_citadel_02b", "#episodic_Chapter1_Title" }, - { "ep1_citadel_02", "#episodic_Chapter1_Title" }, - { "ep1_citadel_03", "#episodic_Chapter2_Title" }, - { "ep1_citadel_04", "#episodic_Chapter2_Title" }, - { "ep1_c17_00a", "#episodic_Chapter3_Title" }, - { "ep1_c17_00", "#episodic_Chapter3_Title" }, - { "ep1_c17_01", "#episodic_Chapter4_Title" }, - { "ep1_c17_02b", "#episodic_Chapter4_Title" }, - { "ep1_c17_02", "#episodic_Chapter4_Title" }, - { "ep1_c17_05", "#episodic_Chapter5_Title" }, - { "ep1_c17_06", "#episodic_Chapter5_Title" }, - - { "ep2_outland_01a", "#ep2_Chapter1_Title" }, - { "ep2_outland_01", "#ep2_Chapter1_Title" }, - { "ep2_outland_02", "#ep2_Chapter2_Title" }, - { "ep2_outland_03", "#ep2_Chapter2_Title" }, - { "ep2_outland_04", "#ep2_Chapter2_Title" }, - { "ep2_outland_05", "#ep2_Chapter3_Title" }, - - { "ep2_outland_06a", "#ep2_Chapter4_Title" }, - { "ep2_outland_06", "#ep2_Chapter3_Title" }, - - { "ep2_outland_07", "#ep2_Chapter4_Title" }, - { "ep2_outland_08", "#ep2_Chapter4_Title" }, - { "ep2_outland_09", "#ep2_Chapter5_Title" }, - - { "ep2_outland_10a", "#ep2_Chapter5_Title" }, - { "ep2_outland_10", "#ep2_Chapter5_Title" }, - - { "ep2_outland_11a", "#ep2_Chapter6_Title" }, - { "ep2_outland_11", "#ep2_Chapter6_Title" }, - - { "ep2_outland_12a", "#ep2_Chapter7_Title" }, - { "ep2_outland_12", "#ep2_Chapter6_Title" }, -#endif -}; - -#ifdef _XBOX -void CServerGameDLL::GetTitleName( const char *pMapName, char* pTitleBuff, int titleBuffSize ) -{ - // Try to find a matching title comment for this mapname - for ( int i = 0; i < ARRAYSIZE(gTitleComments); i++ ) - { - if ( !Q_strnicmp( pMapName, gTitleComments[i].pBSPName, strlen(gTitleComments[i].pBSPName) ) ) - { - Q_strncpy( pTitleBuff, gTitleComments[i].pTitleName, titleBuffSize ); - return; - } - } - Q_strncpy( pTitleBuff, pMapName, titleBuffSize ); -} -#endif - -void CServerGameDLL::GetSaveComment( char *text, int maxlength, float flMinutes, float flSeconds, bool bNoTime ) -{ - char comment[64]; - const char *pName; - int i; - - char const *mapname = STRING( gpGlobals->mapname ); - - pName = NULL; - - // Try to find a matching title comment for this mapname - for ( i = 0; i < ARRAYSIZE(gTitleComments) && !pName; i++ ) - { - if ( !Q_strnicmp( mapname, gTitleComments[i].pBSPName, strlen(gTitleComments[i].pBSPName) ) ) - { - // found one - int j; - - // Got a message, post-process it to be save name friendly - Q_strncpy( comment, gTitleComments[i].pTitleName, sizeof( comment ) ); - pName = comment; - j = 0; - // Strip out CRs - while ( j < 64 && comment[j] ) - { - if ( comment[j] == '\n' || comment[j] == '\r' ) - comment[j] = 0; - else - j++; - } - break; - } - } - - // If we didn't get one, use the designer's map name, or the BSP name itself - if ( !pName ) - { - pName = mapname; - } - - if ( bNoTime ) - { - Q_snprintf( text, maxlength, "%-64.64s", pName ); - } - else - { - int minutes = flMinutes; - int seconds = flSeconds; - - // Wow, this guy/gal must suck...! - if ( minutes >= 1000 ) - { - minutes = 999; - seconds = 59; - } - - int minutesAdd = ( seconds / 60 ); - seconds %= 60; - - // add the elapsed time at the end of the comment, for the ui to parse out - Q_snprintf( text, maxlength, "%-64.64s %03d:%02d", pName, (minutes + minutesAdd), seconds ); - } -} - -void CServerGameDLL::WriteSaveHeaders( CSaveRestoreData *s ) -{ - CSave saveHelper( s ); - g_pGameSaveRestoreBlockSet->WriteSaveHeaders( &saveHelper ); - g_pGameSaveRestoreBlockSet->PostSave(); -} - -void CServerGameDLL::ReadRestoreHeaders( CSaveRestoreData *s ) -{ - CRestore restoreHelper( s ); - g_pGameSaveRestoreBlockSet->PreRestore(); - g_pGameSaveRestoreBlockSet->ReadRestoreHeaders( &restoreHelper ); -} - -void CServerGameDLL::PreSaveGameLoaded( char const *pSaveName, bool bInGame ) -{ - gamestats->Event_PreSaveGameLoaded( pSaveName, bInGame ); -} - -//----------------------------------------------------------------------------- -// Purpose: Returns true if the game DLL wants the server not to be made public. -// Used by commentary system to hide multiplayer commentary servers from the master. -//----------------------------------------------------------------------------- -bool CServerGameDLL::ShouldHideServer( void ) -{ - if ( g_pcv_commentary && g_pcv_commentary->GetBool() ) - return true; - - if ( g_pcv_hideServer && g_pcv_hideServer->GetBool() ) - return true; - - if ( gpGlobals->eLoadType == MapLoad_Background ) - return true; - - #if defined( TF_DLL ) - if ( GTFGCClientSystem()->ShouldHideServer() ) - return true; - #endif - return false; -} - -//----------------------------------------------------------------------------- -// -//----------------------------------------------------------------------------- -void CServerGameDLL::InvalidateMdlCache() -{ - CBaseAnimating *pAnimating; - for ( CBaseEntity *pEntity = gEntList.FirstEnt(); pEntity != NULL; pEntity = gEntList.NextEnt(pEntity) ) - { - pAnimating = dynamic_cast(pEntity); - if ( pAnimating ) - { - pAnimating->InvalidateMdlCache(); - } - } -} - -// interface to the new GC based lobby system -IServerGCLobby *CServerGameDLL::GetServerGCLobby() -{ -#ifdef TF_DLL - return GTFGCClientSystem(); -#else - return NULL; -#endif -} - - -void CServerGameDLL::SetServerHibernation( bool bHibernating ) -{ - m_bIsHibernating = bHibernating; - -#ifdef INFESTED_DLL - if ( engine && engine->IsDedicatedServer() && m_bIsHibernating && ASWGameRules() ) - { - ASWGameRules()->OnServerHibernating(); - } -#endif - -#ifdef TF_DLL - GTFGCClientSystem()->SetHibernation( bHibernating ); -#endif -} - -const char *CServerGameDLL::GetServerBrowserMapOverride() -{ -#ifdef TF_DLL - if ( TFGameRules() && TFGameRules()->IsMannVsMachineMode() && g_pPopulationManager && g_pPopulationManager->GetPopulationFilenameShort() != '\0' ) - { - return g_pPopulationManager->GetPopulationFilenameShort(); - } -#endif - return NULL; -} - -const char *CServerGameDLL::GetServerBrowserGameData() -{ - CUtlString sResult; - -#ifdef TF_DLL - sResult.Format( "tf_mm_trusted:%d,tf_mm_servermode:%d", tf_mm_trusted.GetInt(), tf_mm_servermode.GetInt() ); - - CTFLobby *pLobby = GTFGCClientSystem()->GetLobby(); - if ( pLobby == NULL ) - { - sResult.Append( ",lobby:0" ); - } - else - { - sResult.Append( CFmtStr( ",lobby:%016llx", pLobby->GetGroupID() ) ); - } - if ( TFGameRules() && TFGameRules()->IsMannVsMachineMode() ) - { - sResult.Append( CFmtStr( ",mannup:%d", ( pLobby && pLobby->GetPlayingForBraggingRights() ) ? 1 : 0 ) ); - } -#endif - - static char rchResult[2048]; - V_strcpy_safe( rchResult, sResult ); - return rchResult; -} - -//----------------------------------------------------------------------------- -// Purpose: Called during a transition, to build a map adjacency list -//----------------------------------------------------------------------------- -void CServerGameDLL::BuildAdjacentMapList( void ) -{ - // retrieve the pointer to the save data - CSaveRestoreData *pSaveData = gpGlobals->pSaveData; - - if ( pSaveData ) - pSaveData->levelInfo.connectionCount = BuildChangeList( pSaveData->levelInfo.levelList, MAX_LEVEL_CONNECTIONS ); -} - -//----------------------------------------------------------------------------- -// Purpose: Sanity-check to verify that a path is a relative path inside the game dir -// Taken From: engine/cmd.cpp -//----------------------------------------------------------------------------- -static bool IsValidPath( const char *pszFilename ) -{ - if ( !pszFilename ) - { - return false; - } - - if ( Q_strlen( pszFilename ) <= 0 || - Q_IsAbsolutePath( pszFilename ) || // to protect absolute paths - Q_strstr( pszFilename, ".." ) ) // to protect relative paths - { - return false; - } - - return true; -} - -static void ValidateMOTDFilename( IConVar *pConVar, const char *oldValue, float flOldValue ) -{ - ConVarRef var( pConVar ); - if ( !IsValidPath( var.GetString() ) ) - { - var.SetValue( var.GetDefault() ); - } -} - -static ConVar motdfile( "motdfile", "motd.txt", 0, "The MOTD file to load.", ValidateMOTDFilename ); -static ConVar motdfile_text( "motdfile_text", "motd_text.txt", 0, "The text-only MOTD file to use for clients that have disabled HTML MOTDs.", ValidateMOTDFilename ); -void CServerGameDLL::LoadMessageOfTheDay() -{ - LoadSpecificMOTDMsg( motdfile, "motd" ); - LoadSpecificMOTDMsg( motdfile_text, "motd_text" ); -} - -void CServerGameDLL::LoadSpecificMOTDMsg( const ConVar &convar, const char *pszStringName ) -{ -#ifndef _XBOX - CUtlBuffer buf; - - // Generate preferred filename, which is in the cfg folder. - char szPreferredFilename[ MAX_PATH ]; - V_sprintf_safe( szPreferredFilename, "cfg/%s", convar.GetString() ); - - // Check the preferred filename first - char szResolvedFilename[ MAX_PATH ]; - V_strcpy_safe( szResolvedFilename, szPreferredFilename ); - bool bFound = filesystem->ReadFile( szResolvedFilename, "GAME", buf ); - - // Not found? Try in the root, which is the old place it used to go. - if ( !bFound ) - { - - V_strcpy_safe( szResolvedFilename, convar.GetString() ); - bFound = filesystem->ReadFile( szResolvedFilename, "GAME", buf ); - } - - // Still not found? See if we can try the default. - if ( !bFound && !V_stricmp( convar.GetString(), convar.GetDefault() ) ) - { - V_strcpy_safe( szResolvedFilename, szPreferredFilename ); - char *dotTxt = V_stristr( szResolvedFilename, ".txt" ); - Assert ( dotTxt != NULL ); - if ( dotTxt ) V_strcpy( dotTxt, "_default.txt" ); - bFound = filesystem->ReadFile( szResolvedFilename, "GAME", buf ); - } - - if ( !bFound ) - { - Msg( "'%s' not found; not loaded\n", szPreferredFilename ); - return; - } - - if ( buf.TellPut() > 2048 ) - { - Warning("'%s' is too big; not loaded\n", szResolvedFilename ); - return; - } - buf.PutChar( '\0' ); - - if ( V_stricmp( szPreferredFilename, szResolvedFilename ) == 0) - { - Msg( "Set %s from file '%s'\n", pszStringName, szResolvedFilename ); - } - else - { - Msg( "Set %s from file '%s'. ('%s' was not found.)\n", pszStringName, szResolvedFilename, szPreferredFilename ); - } - - g_pStringTableInfoPanel->AddString( CBaseEntity::IsServer(), pszStringName, buf.TellPut(), buf.Base() ); -#endif -} - -// keeps track of which chapters the user has unlocked -ConVar sv_unlockedchapters( "sv_unlockedchapters", "1", FCVAR_ARCHIVE | FCVAR_ARCHIVE_XBOX ); - -//----------------------------------------------------------------------------- -// Purpose: Updates which chapters are unlocked -//----------------------------------------------------------------------------- -void UpdateChapterRestrictions( const char *mapname ) -{ - // look at the chapter for this map - char chapterTitle[64]; - chapterTitle[0] = 0; - for ( int i = 0; i < ARRAYSIZE(gTitleComments); i++ ) - { - if ( !Q_strnicmp( mapname, gTitleComments[i].pBSPName, strlen(gTitleComments[i].pBSPName) ) ) - { - // found - Q_strncpy( chapterTitle, gTitleComments[i].pTitleName, sizeof( chapterTitle ) ); - int j = 0; - while ( j < 64 && chapterTitle[j] ) - { - if ( chapterTitle[j] == '\n' || chapterTitle[j] == '\r' ) - chapterTitle[j] = 0; - else - j++; - } - - break; - } - } - - if ( !chapterTitle[0] ) - return; - - // make sure the specified chapter title is unlocked - strlwr( chapterTitle ); - - // Get our active mod directory name - char modDir[MAX_PATH]; - if ( UTIL_GetModDir( modDir, sizeof(modDir) ) == false ) - return; - - char chapterNumberPrefix[64]; - Q_snprintf(chapterNumberPrefix, sizeof(chapterNumberPrefix), "#%s_chapter", modDir); - - const char *newChapterNumber = strstr( chapterTitle, chapterNumberPrefix ); - if ( newChapterNumber ) - { - // cut off the front - newChapterNumber += strlen( chapterNumberPrefix ); - char newChapter[32]; - Q_strncpy( newChapter, newChapterNumber, sizeof(newChapter) ); - - // cut off the end - char *end = strstr( newChapter, "_title" ); - if ( end ) - { - *end = 0; - } - - int nNewChapter = atoi( newChapter ); - - // HACK: HL2 added a zany chapter "9a" which wreaks - // havoc in this stupid atoi-based chapter code. - if ( !Q_stricmp( modDir, "hl2" ) ) - { - if ( !Q_stricmp( newChapter, "9a" ) ) - { - nNewChapter = 10; - } - else if ( nNewChapter > 9 ) - { - nNewChapter++; - } - } - - // ok we have the string, see if it's newer - const char *unlockedChapter = sv_unlockedchapters.GetString(); - int nUnlockedChapter = atoi( unlockedChapter ); - - if ( nUnlockedChapter < nNewChapter ) - { - // ok we're at a higher chapter, unlock - sv_unlockedchapters.SetValue( nNewChapter ); - - // HACK: Call up through a better function than this? 7/23/07 - jdw - if ( IsX360() ) - { - engine->ServerCommand( "host_writeconfig\n" ); - } - } - - g_nCurrentChapterIndex = nNewChapter; - } -} - -//----------------------------------------------------------------------------- -// Purpose: Update xbox live data for the user's presence -//----------------------------------------------------------------------------- -void UpdateRichPresence ( void ) -{ - // This assumes we're playing a single player game - Assert ( gpGlobals->maxClients == 1 ); - - // Shouldn't get here unless we're playing a map and we've updated sv_unlockedchapters - Assert ( g_nCurrentChapterIndex >= 0 ); - - // Get our active mod directory name - char modDir[MAX_PATH]; - if ( UTIL_GetModDir( modDir, sizeof(modDir) ) == false ) - return; - - // Get presence data based on the game we're playing - uint iGameID, iChapterIndex, iChapterID, iGamePresenceID; - iGameID = iChapterIndex = iChapterID = iGamePresenceID = 0; - if ( Q_stristr( modDir, "hl2" ) ) - { - iGameID = CONTEXT_GAME_GAME_HALF_LIFE_2; - iChapterID = CONTEXT_CHAPTER_HL2; - iChapterIndex = g_nCurrentChapterIndex - 1; - iGamePresenceID = CONTEXT_PRESENCE_HL2_INGAME; - } - else if ( Q_stristr( modDir, "episodic" ) ) - { - iGameID = CONTEXT_GAME_GAME_EPISODE_ONE; - iChapterID = CONTEXT_CHAPTER_EP1; - iChapterIndex = g_nCurrentChapterIndex - 1; - iGamePresenceID = CONTEXT_PRESENCE_EP1_INGAME; - } - else if ( Q_stristr( modDir, "ep2" ) ) - { - iGameID = CONTEXT_GAME_GAME_EPISODE_TWO; - iChapterID = CONTEXT_CHAPTER_EP2; - iChapterIndex = g_nCurrentChapterIndex - 1; - iGamePresenceID = CONTEXT_PRESENCE_EP2_INGAME; - } - else if ( Q_stristr( modDir, "portal" ) ) - { - iGameID = CONTEXT_GAME_GAME_PORTAL; - iChapterID = CONTEXT_CHAPTER_PORTAL; - iChapterIndex = g_nCurrentChapterIndex - 1; - iGamePresenceID = CONTEXT_PRESENCE_PORTAL_INGAME; - } - else - { - Warning( "UpdateRichPresence failed in GameInterface. Didn't recognize -game parameter." ); - } - -#if defined( _X360 ) - - // Set chapter context based on mapname - if ( !xboxsystem->UserSetContext( XBX_GetPrimaryUserId(), iChapterID, iChapterIndex, true ) ) - { - Warning( "GameInterface: UserSetContext failed.\n" ); - } - - if ( commentary.GetBool() ) - { - // Set presence to show the user is playing developer commentary - if ( !xboxsystem->UserSetContext( XBX_GetPrimaryUserId(), X_CONTEXT_PRESENCE, CONTEXT_PRESENCE_COMMENTARY, true ) ) - { - Warning( "GameInterface: UserSetContext failed.\n" ); - } - } - else - { - // Set presence to show the user is in-game - if ( !xboxsystem->UserSetContext( XBX_GetPrimaryUserId(), X_CONTEXT_PRESENCE, iGamePresenceID, true ) ) - { - Warning( "GameInterface: UserSetContext failed.\n" ); - } - } - - // Set which game the user is playing - if ( !xboxsystem->UserSetContext( XBX_GetPrimaryUserId(), CONTEXT_GAME, iGameID, true ) ) - { - Warning( "GameInterface: UserSetContext failed.\n" ); - } - - if ( !xboxsystem->UserSetContext( XBX_GetPrimaryUserId(), X_CONTEXT_GAME_TYPE, X_CONTEXT_GAME_TYPE_STANDARD, true ) ) - { - Warning( "GameInterface: UserSetContext failed.\n" ); - } - - if ( !xboxsystem->UserSetContext( XBX_GetPrimaryUserId(), X_CONTEXT_GAME_MODE, CONTEXT_GAME_MODE_SINGLEPLAYER, true ) ) - { - Warning( "GameInterface: UserSetContext failed.\n" ); - } -#endif -} - -//----------------------------------------------------------------------------- -// Precaches a vgui screen overlay material -//----------------------------------------------------------------------------- -void PrecacheMaterial( const char *pMaterialName ) -{ - Assert( pMaterialName && pMaterialName[0] ); - g_pStringTableMaterials->AddString( CBaseEntity::IsServer(), pMaterialName ); -} - - -//----------------------------------------------------------------------------- -// Converts a previously precached material into an index -//----------------------------------------------------------------------------- -int GetMaterialIndex( const char *pMaterialName ) -{ - if (pMaterialName) - { - int nIndex = g_pStringTableMaterials->FindStringIndex( pMaterialName ); - - if (nIndex != INVALID_STRING_INDEX ) - { - return nIndex; - } - else - { - DevMsg("Warning! GetMaterialIndex: couldn't find material %s\n ", pMaterialName ); - return 0; - } - } - - // This is the invalid string index - return 0; -} - -//----------------------------------------------------------------------------- -// Converts a previously precached material index into a string -//----------------------------------------------------------------------------- -const char *GetMaterialNameFromIndex( int nMaterialIndex ) -{ - return g_pStringTableMaterials->GetString( nMaterialIndex ); -} - - -//----------------------------------------------------------------------------- -// Precaches a vgui screen overlay material -//----------------------------------------------------------------------------- -void PrecacheParticleSystem( const char *pParticleSystemName ) -{ - Assert( pParticleSystemName && pParticleSystemName[0] ); - g_pStringTableParticleEffectNames->AddString( CBaseEntity::IsServer(), pParticleSystemName ); -} - - -//----------------------------------------------------------------------------- -// Converts a previously precached material into an index -//----------------------------------------------------------------------------- -int GetParticleSystemIndex( const char *pParticleSystemName ) -{ - if ( pParticleSystemName ) - { - int nIndex = g_pStringTableParticleEffectNames->FindStringIndex( pParticleSystemName ); - if (nIndex != INVALID_STRING_INDEX ) - return nIndex; - - DevWarning("Server: Missing precache for particle system \"%s\"!\n", pParticleSystemName ); - } - - // This is the invalid string index - return 0; -} - -//----------------------------------------------------------------------------- -// Converts a previously precached material index into a string -//----------------------------------------------------------------------------- -const char *GetParticleSystemNameFromIndex( int nMaterialIndex ) -{ - if ( nMaterialIndex < g_pStringTableParticleEffectNames->GetMaxStrings() ) - return g_pStringTableParticleEffectNames->GetString( nMaterialIndex ); - return "error"; -} - -//----------------------------------------------------------------------------- -// Returns true if host_thread_mode is set to non-zero (and engine is running in threaded mode) -//----------------------------------------------------------------------------- -bool IsEngineThreaded() -{ - if ( g_pcv_ThreadMode ) - { - return g_pcv_ThreadMode->GetBool(); - } - return false; -} - -class CServerGameEnts : public IServerGameEnts -{ -public: - virtual void SetDebugEdictBase(edict_t *base); - virtual void MarkEntitiesAsTouching( edict_t *e1, edict_t *e2 ); - virtual void FreeContainingEntity( edict_t * ); - virtual edict_t* BaseEntityToEdict( CBaseEntity *pEnt ); - virtual CBaseEntity* EdictToBaseEntity( edict_t *pEdict ); - virtual void CheckTransmit( CCheckTransmitInfo *pInfo, const unsigned short *pEdictIndices, int nEdicts ); -}; -EXPOSE_SINGLE_INTERFACE(CServerGameEnts, IServerGameEnts, INTERFACEVERSION_SERVERGAMEENTS); - -void CServerGameEnts::SetDebugEdictBase(edict_t *base) -{ - g_pDebugEdictBase = base; -} - -//----------------------------------------------------------------------------- -// Purpose: Marks entities as touching -// Input : *e1 - -// *e2 - -//----------------------------------------------------------------------------- -void CServerGameEnts::MarkEntitiesAsTouching( edict_t *e1, edict_t *e2 ) -{ - CBaseEntity *entity = GetContainingEntity( e1 ); - CBaseEntity *entityTouched = GetContainingEntity( e2 ); - if ( entity && entityTouched ) - { - // HACKHACK: UNDONE: Pass in the trace here??!?!? - trace_t tr; - UTIL_ClearTrace( tr ); - tr.endpos = (entity->GetAbsOrigin() + entityTouched->GetAbsOrigin()) * 0.5; - entity->PhysicsMarkEntitiesAsTouching( entityTouched, tr ); - } -} - -void CServerGameEnts::FreeContainingEntity( edict_t *e ) -{ - ::FreeContainingEntity(e); -} - -edict_t* CServerGameEnts::BaseEntityToEdict( CBaseEntity *pEnt ) -{ - if ( pEnt ) - return pEnt->edict(); - else - return NULL; -} - -CBaseEntity* CServerGameEnts::EdictToBaseEntity( edict_t *pEdict ) -{ - if ( pEdict ) - return CBaseEntity::Instance( pEdict ); - else - return NULL; -} - - -/* Yuck.. ideally this would be in CServerNetworkProperty's header, but it requires CBaseEntity and -// inlining it gives a nice speedup. -inline void CServerNetworkProperty::CheckTransmit( CCheckTransmitInfo *pInfo ) -{ - // If we have a transmit proxy, let it hook our ShouldTransmit return value. - if ( m_pTransmitProxy ) - { - nShouldTransmit = m_pTransmitProxy->ShouldTransmit( pInfo, nShouldTransmit ); - } - - if ( m_pOuter->ShouldTransmit( pInfo ) ) - { - m_pOuter->SetTransmit( pInfo ); - } -} */ - -void CServerGameEnts::CheckTransmit( CCheckTransmitInfo *pInfo, const unsigned short *pEdictIndices, int nEdicts ) -{ - // NOTE: for speed's sake, this assumes that all networkables are CBaseEntities and that the edict list - // is consecutive in memory. If either of these things change, then this routine needs to change, but - // ideally we won't be calling any virtual from this routine. This speedy routine was added as an - // optimization which would be nice to keep. - edict_t *pBaseEdict = engine->PEntityOfEntIndex( 0 ); - - // get recipient player's skybox: - CBaseEntity *pRecipientEntity = CBaseEntity::Instance( pInfo->m_pClientEnt ); - - Assert( pRecipientEntity && pRecipientEntity->IsPlayer() ); - if ( !pRecipientEntity ) - return; - - MDLCACHE_CRITICAL_SECTION(); - CBasePlayer *pRecipientPlayer = static_cast( pRecipientEntity ); - const int skyBoxArea = pRecipientPlayer->m_Local.m_skybox3d.area; - -#ifndef _X360 - const bool bIsHLTV = pRecipientPlayer->IsHLTV(); - const bool bIsReplay = pRecipientPlayer->IsReplay(); - - // m_pTransmitAlways must be set if HLTV client - Assert( bIsHLTV == ( pInfo->m_pTransmitAlways != NULL) || - bIsReplay == ( pInfo->m_pTransmitAlways != NULL) ); -#endif - - for ( int i=0; i < nEdicts; i++ ) - { - int iEdict = pEdictIndices[i]; - - edict_t *pEdict = &pBaseEdict[iEdict]; - Assert( pEdict == engine->PEntityOfEntIndex( iEdict ) ); - int nFlags = pEdict->m_fStateFlags & (FL_EDICT_DONTSEND|FL_EDICT_ALWAYS|FL_EDICT_PVSCHECK|FL_EDICT_FULLCHECK); - - // entity needs no transmit - if ( nFlags & FL_EDICT_DONTSEND ) - continue; - - // entity is already marked for sending - if ( pInfo->m_pTransmitEdict->Get( iEdict ) ) - continue; - - if ( nFlags & FL_EDICT_ALWAYS ) - { - // FIXME: Hey! Shouldn't this be using SetTransmit so as - // to also force network down dependent entities? - while ( true ) - { - // mark entity for sending - pInfo->m_pTransmitEdict->Set( iEdict ); - -#ifndef _X360 - if ( bIsHLTV || bIsReplay ) - { - pInfo->m_pTransmitAlways->Set( iEdict ); - } -#endif - CServerNetworkProperty *pEnt = static_cast( pEdict->GetNetworkable() ); - if ( !pEnt ) - break; - - CServerNetworkProperty *pParent = pEnt->GetNetworkParent(); - if ( !pParent ) - break; - - pEdict = pParent->edict(); - iEdict = pParent->entindex(); - } - continue; - } - - // FIXME: Would like to remove all dependencies - CBaseEntity *pEnt = ( CBaseEntity * )pEdict->GetUnknown(); - Assert( dynamic_cast< CBaseEntity* >( pEdict->GetUnknown() ) == pEnt ); - - if ( nFlags == FL_EDICT_FULLCHECK ) - { - // do a full ShouldTransmit() check, may return FL_EDICT_CHECKPVS - nFlags = pEnt->ShouldTransmit( pInfo ); - - Assert( !(nFlags & FL_EDICT_FULLCHECK) ); - - if ( nFlags & FL_EDICT_ALWAYS ) - { - pEnt->SetTransmit( pInfo, true ); - continue; - } - } - - // don't send this entity - if ( !( nFlags & FL_EDICT_PVSCHECK ) ) - continue; - - CServerNetworkProperty *netProp = static_cast( pEdict->GetNetworkable() ); - -#ifndef _X360 - if ( bIsHLTV || bIsReplay ) - { - // for the HLTV/Replay we don't cull against PVS - if ( netProp->AreaNum() == skyBoxArea ) - { - pEnt->SetTransmit( pInfo, true ); - } - else - { - pEnt->SetTransmit( pInfo, false ); - } - continue; - } -#endif - - // Always send entities in the player's 3d skybox. - // Sidenote: call of AreaNum() ensures that PVS data is up to date for this entity - bool bSameAreaAsSky = netProp->AreaNum() == skyBoxArea; - if ( bSameAreaAsSky ) - { - pEnt->SetTransmit( pInfo, true ); - continue; - } - - bool bInPVS = netProp->IsInPVS( pInfo ); - if ( bInPVS || sv_force_transmit_ents.GetBool() ) - { - // only send if entity is in PVS - pEnt->SetTransmit( pInfo, false ); - continue; - } - - // If the entity is marked "check PVS" but it's in hierarchy, walk up the hierarchy looking for the - // for any parent which is also in the PVS. If none are found, then we don't need to worry about sending ourself - CBaseEntity *orig = pEnt; - CServerNetworkProperty *check = netProp->GetNetworkParent(); - - // BUG BUG: I think it might be better to build up a list of edict indices which "depend" on other answers and then - // resolve them in a second pass. Not sure what happens if an entity has two parents who both request PVS check? - while ( check ) - { - int checkIndex = check->entindex(); - - // Parent already being sent - if ( pInfo->m_pTransmitEdict->Get( checkIndex ) ) - { - orig->SetTransmit( pInfo, true ); - break; - } - - edict_t *checkEdict = check->edict(); - int checkFlags = checkEdict->m_fStateFlags & (FL_EDICT_DONTSEND|FL_EDICT_ALWAYS|FL_EDICT_PVSCHECK|FL_EDICT_FULLCHECK); - if ( checkFlags & FL_EDICT_DONTSEND ) - break; - - if ( checkFlags & FL_EDICT_ALWAYS ) - { - orig->SetTransmit( pInfo, true ); - break; - } - - if ( checkFlags == FL_EDICT_FULLCHECK ) - { - // do a full ShouldTransmit() check, may return FL_EDICT_CHECKPVS - CBaseEntity *pCheckEntity = check->GetBaseEntity(); - nFlags = pCheckEntity->ShouldTransmit( pInfo ); - Assert( !(nFlags & FL_EDICT_FULLCHECK) ); - if ( nFlags & FL_EDICT_ALWAYS ) - { - pCheckEntity->SetTransmit( pInfo, true ); - orig->SetTransmit( pInfo, true ); - } - break; - } - - if ( checkFlags & FL_EDICT_PVSCHECK ) - { - // Check pvs - check->RecomputePVSInformation(); - bool bMoveParentInPVS = check->IsInPVS( pInfo ); - if ( bMoveParentInPVS ) - { - orig->SetTransmit( pInfo, true ); - break; - } - } - - // Continue up chain just in case the parent itself has a parent that's in the PVS... - check = check->GetNetworkParent(); - } - } - -// Msg("A:%i, N:%i, F: %i, P: %i\n", always, dontSend, fullCheck, PVS ); -} - - -CServerGameClients g_ServerGameClients; -// INTERFACEVERSION_SERVERGAMECLIENTS_VERSION_3 is compatible with the latest since we're only adding things to the end, so expose that as well. -EXPOSE_SINGLE_INTERFACE_GLOBALVAR(CServerGameClients, IServerGameClients003, INTERFACEVERSION_SERVERGAMECLIENTS_VERSION_3, g_ServerGameClients ); -EXPOSE_SINGLE_INTERFACE_GLOBALVAR(CServerGameClients, IServerGameClients, INTERFACEVERSION_SERVERGAMECLIENTS, g_ServerGameClients ); - - -//----------------------------------------------------------------------------- -// Purpose: called when a player tries to connect to the server -// Input : *pEdict - the new player -// char *pszName - the players name -// char *pszAddress - the IP address of the player -// reject - output - fill in with the reason why -// maxrejectlen -- sizeof output buffer -// the player was not allowed to connect. -// Output : Returns TRUE if player is allowed to join, FALSE if connection is denied. -//----------------------------------------------------------------------------- -bool CServerGameClients::ClientConnect( edict_t *pEdict, const char *pszName, const char *pszAddress, char *reject, int maxrejectlen ) -{ - if ( !g_pGameRules ) - return false; - - return g_pGameRules->ClientConnected( pEdict, pszName, pszAddress, reject, maxrejectlen ); -} - -//----------------------------------------------------------------------------- -// Purpose: Called when a player is fully active (i.e. ready to receive messages) -// Input : *pEntity - the player -//----------------------------------------------------------------------------- -void CServerGameClients::ClientActive( edict_t *pEdict, bool bLoadGame ) -{ - MDLCACHE_CRITICAL_SECTION(); - - ::ClientActive( pEdict, bLoadGame ); - - // If we just loaded from a save file, call OnRestore on valid entities - EndRestoreEntities(); - - if ( gpGlobals->eLoadType != MapLoad_LoadGame ) - { - // notify all entities that the player is now in the game - for ( CBaseEntity *pEntity = gEntList.FirstEnt(); pEntity != NULL; pEntity = gEntList.NextEnt(pEntity) ) - { - pEntity->PostClientActive(); - } - } - - // Tell the sound controller to check looping sounds - CBasePlayer *pPlayer = ( CBasePlayer * )CBaseEntity::Instance( pEdict ); - CSoundEnvelopeController::GetController().CheckLoopingSoundsForPlayer( pPlayer ); - SceneManager_ClientActive( pPlayer ); - - #if defined( TF_DLL ) - Assert( pPlayer ); - if ( pPlayer && !pPlayer->IsFakeClient() ) - { - CSteamID steamID; - if ( pPlayer->GetSteamID( &steamID ) ) - { - GTFGCClientSystem()->ClientActive( steamID ); - } - else - { - Log("WARNING: ClientActive, but we don't know his SteamID?\n"); - } - } - #endif -} - -//----------------------------------------------------------------------------- -// Purpose: -// Input : *pPlayer - the player -//----------------------------------------------------------------------------- -void CServerGameClients::ClientSpawned( edict_t *pPlayer ) -{ - if ( g_pGameRules ) - { - g_pGameRules->ClientSpawned( pPlayer ); - } -} - -//----------------------------------------------------------------------------- -// Purpose: called when a player disconnects from a server -// Input : *pEdict - the player -//----------------------------------------------------------------------------- -void CServerGameClients::ClientDisconnect( edict_t *pEdict ) -{ - extern bool g_fGameOver; - - CBasePlayer *player = ( CBasePlayer * )CBaseEntity::Instance( pEdict ); - if ( player ) - { - if ( !g_fGameOver ) - { - player->SetMaxSpeed( 0.0f ); - - CSound *pSound; - pSound = CSoundEnt::SoundPointerForIndex( CSoundEnt::ClientSoundIndex( pEdict ) ); - { - // since this client isn't around to think anymore, reset their sound. - if ( pSound ) - { - pSound->Reset(); - } - } - - // since the edict doesn't get deleted, fix it so it doesn't interfere. - player->RemoveFlag( FL_AIMTARGET ); // don't attract autoaim - player->AddFlag( FL_DONTTOUCH ); // stop it touching anything - player->AddFlag( FL_NOTARGET ); // stop NPCs noticing it - player->AddSolidFlags( FSOLID_NOT_SOLID ); // nonsolid - - if ( g_pGameRules ) - { - g_pGameRules->ClientDisconnected( pEdict ); - gamestats->Event_PlayerDisconnected( player ); - } - } - - // Make sure all Untouch()'s are called for this client leaving - CBaseEntity::PhysicsRemoveTouchedList( player ); - CBaseEntity::PhysicsRemoveGroundList( player ); - -#if !defined( NO_ENTITY_PREDICTION ) - // Make sure anything we "own" is simulated by the server from now on - player->ClearPlayerSimulationList(); -#endif - #if defined( TF_DLL ) - if ( !player->IsFakeClient() ) - { - CSteamID steamID; - if ( player->GetSteamID( &steamID ) ) - { - GTFGCClientSystem()->ClientDisconnected( steamID ); - } - else - { - Log("WARNING: ClientDisconnected, but we don't know his SteamID?\n"); - } - } - #endif - } -} - -void CServerGameClients::ClientPutInServer( edict_t *pEntity, const char *playername ) -{ - if ( g_pClientPutInServerOverride ) - g_pClientPutInServerOverride( pEntity, playername ); - else - ::ClientPutInServer( pEntity, playername ); -} - -void CServerGameClients::ClientCommand( edict_t *pEntity, const CCommand &args ) -{ - CBasePlayer *pPlayer = ToBasePlayer( GetContainingEntity( pEntity ) ); - ::ClientCommand( pPlayer, args ); -} - -//----------------------------------------------------------------------------- -// Purpose: called after the player changes userinfo - gives dll a chance to modify -// it before it gets sent into the rest of the engine-> -// Input : *pEdict - the player -// *infobuffer - their infobuffer -//----------------------------------------------------------------------------- -void CServerGameClients::ClientSettingsChanged( edict_t *pEdict ) -{ - // Is the client spawned yet? - if ( !pEdict->GetUnknown() ) - return; - - CBasePlayer *player = ( CBasePlayer * )CBaseEntity::Instance( pEdict ); - - if ( !player ) - return; - - bool bAllowNetworkingClientSettingsChange = g_pGameRules->IsConnectedUserInfoChangeAllowed( player ); - if ( bAllowNetworkingClientSettingsChange ) - { - -#define QUICKGETCVARVALUE(v) (engine->GetClientConVarValue( player->entindex(), v )) - - // get network setting for prediction & lag compensation - - // Unfortunately, we have to duplicate the code in cdll_bounded_cvars.cpp here because the client - // doesn't send the virtualized value up (because it has no way to know when the virtualized value - // changes). Possible todo: put the responsibility on the bounded cvar to notify the engine when - // its virtualized value has changed. - - player->m_nUpdateRate = Q_atoi( QUICKGETCVARVALUE("cl_updaterate") ); - static const ConVar *pMinUpdateRate = g_pCVar->FindVar( "sv_minupdaterate" ); - static const ConVar *pMaxUpdateRate = g_pCVar->FindVar( "sv_maxupdaterate" ); - if ( pMinUpdateRate && pMaxUpdateRate ) - player->m_nUpdateRate = clamp( player->m_nUpdateRate, (int) pMinUpdateRate->GetFloat(), (int) pMaxUpdateRate->GetFloat() ); - - bool useInterpolation = Q_atoi( QUICKGETCVARVALUE("cl_interpolate") ) != 0; - if ( useInterpolation ) - { - float flLerpRatio = Q_atof( QUICKGETCVARVALUE("cl_interp_ratio") ); - if ( flLerpRatio == 0 ) - flLerpRatio = 1.0f; - float flLerpAmount = Q_atof( QUICKGETCVARVALUE("cl_interp") ); - - static const ConVar *pMin = g_pCVar->FindVar( "sv_client_min_interp_ratio" ); - static const ConVar *pMax = g_pCVar->FindVar( "sv_client_max_interp_ratio" ); - if ( pMin && pMax && pMin->GetFloat() != -1 ) - { - flLerpRatio = clamp( flLerpRatio, pMin->GetFloat(), pMax->GetFloat() ); - } - else - { - if ( flLerpRatio == 0 ) - flLerpRatio = 1.0f; - } - // #define FIXME_INTERP_RATIO - player->m_fLerpTime = MAX( flLerpAmount, flLerpRatio / player->m_nUpdateRate ); - } - else - { - player->m_fLerpTime = 0.0f; - } - -#if !defined( NO_ENTITY_PREDICTION ) - bool usePrediction = Q_atoi( QUICKGETCVARVALUE("cl_predict")) != 0; - - if ( usePrediction ) - { - player->m_bPredictWeapons = Q_atoi( QUICKGETCVARVALUE("cl_predictweapons")) != 0; - player->m_bLagCompensation = Q_atoi( QUICKGETCVARVALUE("cl_lagcompensation")) != 0; - } - else -#endif - { - player->m_bPredictWeapons = false; - player->m_bLagCompensation = false; - } - - -#undef QUICKGETCVARVALUE - } - - g_pGameRules->ClientSettingsChanged( player ); -} - - -#ifdef PORTAL -//----------------------------------------------------------------------------- -// Purpose: Runs CFuncAreaPortalBase::UpdateVisibility on each portal -// Input : pAreaPortal - The Area portal to test for visibility from portals -// Output : int - 1 if any portal needs this area portal open, 0 otherwise. -//----------------------------------------------------------------------------- -int TestAreaPortalVisibilityThroughPortals ( CFuncAreaPortalBase* pAreaPortal, edict_t *pViewEntity, unsigned char *pvs, int pvssize ) -{ - int iPortalCount = CProp_Portal_Shared::AllPortals.Count(); - if( iPortalCount == 0 ) - return 0; - - CProp_Portal **pPortals = CProp_Portal_Shared::AllPortals.Base(); - - for ( int i = 0; i != iPortalCount; ++i ) - { - CProp_Portal* pLocalPortal = pPortals[ i ]; - if ( pLocalPortal && pLocalPortal->m_bActivated ) - { - CProp_Portal* pRemotePortal = pLocalPortal->m_hLinkedPortal.Get(); - - // Make sure this portal's linked portal is in the PVS before we add what it can see - if ( pRemotePortal && pRemotePortal->m_bActivated && pRemotePortal->NetworkProp() && - pRemotePortal->NetworkProp()->IsInPVS( pViewEntity, pvs, pvssize ) ) - { - bool bIsOpenOnClient = true; - float fovDistanceAdjustFactor = 1.0f; - Vector portalOrg = pLocalPortal->GetAbsOrigin(); - int iPortalNeedsThisPortalOpen = pAreaPortal->UpdateVisibility( portalOrg, fovDistanceAdjustFactor, bIsOpenOnClient ); - - // Stop checking on success, this portal needs to be open - if ( iPortalNeedsThisPortalOpen ) - { - return iPortalNeedsThisPortalOpen; - } - } - } - } - - return 0; -} -#endif - -//----------------------------------------------------------------------------- -// Purpose: A client can have a separate "view entity" indicating that his/her view should depend on the origin of that -// view entity. If that's the case, then pViewEntity will be non-NULL and will be used. Otherwise, the current -// entity's origin is used. Either is offset by the m_vecViewOffset to get the eye position. -// From the eye position, we set up the PAS and PVS to use for filtering network messages to the client. At this point, we could -// override the actual PAS or PVS values, or use a different origin. -// NOTE: Do not cache the values of pas and pvs, as they depend on reusable memory in the engine, they are only good for this one frame -// Input : *pViewEntity - -// *pClient - -// **pvs - -// **pas - -//----------------------------------------------------------------------------- -void CServerGameClients::ClientSetupVisibility( edict_t *pViewEntity, edict_t *pClient, unsigned char *pvs, int pvssize ) -{ - Vector org; - - // Reset the PVS!!! - engine->ResetPVS( pvs, pvssize ); - - g_pToolFrameworkServer->PreSetupVisibility(); - - // Find the client's PVS - CBaseEntity *pVE = NULL; - if ( pViewEntity ) - { - pVE = GetContainingEntity( pViewEntity ); - // If we have a viewentity, it overrides the player's origin - if ( pVE ) - { - org = pVE->EyePosition(); - engine->AddOriginToPVS( org ); - } - } - - float fovDistanceAdjustFactor = 1; - - CBasePlayer *pPlayer = ( CBasePlayer * )GetContainingEntity( pClient ); - if ( pPlayer ) - { - org = pPlayer->EyePosition(); - pPlayer->SetupVisibility( pVE, pvs, pvssize ); - UTIL_SetClientVisibilityPVS( pClient, pvs, pvssize ); - fovDistanceAdjustFactor = pPlayer->GetFOVDistanceAdjustFactorForNetworking(); - } - - unsigned char portalBits[MAX_AREA_PORTAL_STATE_BYTES]; - memset( portalBits, 0, sizeof( portalBits ) ); - - int portalNums[512]; - int isOpen[512]; - int iOutPortal = 0; - - for( unsigned short i = g_AreaPortals.Head(); i != g_AreaPortals.InvalidIndex(); i = g_AreaPortals.Next(i) ) - { - CFuncAreaPortalBase *pCur = g_AreaPortals[i]; - - bool bIsOpenOnClient = true; - - // Update our array of which portals are open and flush it if necessary. - portalNums[iOutPortal] = pCur->m_portalNumber; - isOpen[iOutPortal] = pCur->UpdateVisibility( org, fovDistanceAdjustFactor, bIsOpenOnClient ); - -#ifdef PORTAL - // If the client doesn't need this open, test if portals might need this area portal open - if ( isOpen[iOutPortal] == 0 ) - { - isOpen[iOutPortal] = TestAreaPortalVisibilityThroughPortals( pCur, pViewEntity, pvs, pvssize ); - } -#endif - - ++iOutPortal; - if ( iOutPortal >= ARRAYSIZE( portalNums ) ) - { - engine->SetAreaPortalStates( portalNums, isOpen, iOutPortal ); - iOutPortal = 0; - } - - // Version 0 portals (ie: shipping Half-Life 2 era) are always treated as open - // for purposes of the m_chAreaPortalBits array on the client. - if ( pCur->m_iPortalVersion == 0 ) - bIsOpenOnClient = true; - - if ( bIsOpenOnClient ) - { - if ( pCur->m_portalNumber < 0 ) - continue; - else if ( pCur->m_portalNumber >= sizeof( portalBits ) * 8 ) - Error( "ClientSetupVisibility: portal number (%d) too large", pCur->m_portalNumber ); - else - portalBits[pCur->m_portalNumber >> 3] |= (1 << (pCur->m_portalNumber & 7)); - } - } - - // Flush the remaining areaportal states. - engine->SetAreaPortalStates( portalNums, isOpen, iOutPortal ); - - // Update the area bits that get sent to the client. - pPlayer->m_Local.UpdateAreaBits( pPlayer, portalBits ); - -#ifdef PORTAL - // *After* the player's view has updated its area bits, add on any other areas seen by portals - CPortal_Player* pPortalPlayer = dynamic_cast( pPlayer ); - if ( pPortalPlayer ) - { - pPortalPlayer->UpdatePortalViewAreaBits( pvs, pvssize ); - } -#endif //PORTAL -} - - - - -//----------------------------------------------------------------------------- -// Purpose: -// Input : *player - -// *buf - -// numcmds - -// totalcmds - -// dropped_packets - -// ignore - -// paused - -// Output : float -//----------------------------------------------------------------------------- -#define CMD_MAXBACKUP 64 - -float CServerGameClients::ProcessUsercmds( edict_t *player, bf_read *buf, int numcmds, int totalcmds, - int dropped_packets, bool ignore, bool paused ) -{ - int i; - CUserCmd *from, *to; - - // We track last three command in case we drop some - // packets but get them back. - CUserCmd cmds[ CMD_MAXBACKUP ]; - - CUserCmd cmdNull; // For delta compression - - Assert( numcmds >= 0 ); - Assert( ( totalcmds - numcmds ) >= 0 ); - - CBasePlayer *pPlayer = NULL; - CBaseEntity *pEnt = CBaseEntity::Instance(player); - if ( pEnt && pEnt->IsPlayer() ) - { - pPlayer = static_cast< CBasePlayer * >( pEnt ); - } - // Too many commands? - if ( totalcmds < 0 || totalcmds >= ( CMD_MAXBACKUP - 1 ) ) - { - const char *name = "unknown"; - if ( pPlayer ) - { - name = pPlayer->GetPlayerName(); - } - - Msg("CBasePlayer::ProcessUsercmds: too many cmds %i sent for player %s\n", totalcmds, name ); - // FIXME: Need a way to drop the client from here - //SV_DropClient ( host_client, false, "CMD_MAXBACKUP hit" ); - buf->SetOverflowFlag(); - return 0.0f; - } - - // Initialize for reading delta compressed usercmds - cmdNull.Reset(); - from = &cmdNull; - for ( i = totalcmds - 1; i >= 0; i-- ) - { - to = &cmds[ i ]; - ReadUsercmd( buf, to, from ); - from = to; - } - - // Client not fully connected or server has gone inactive or is paused, just ignore - if ( ignore || !pPlayer ) - { - return 0.0f; - } - - MDLCACHE_CRITICAL_SECTION(); - pPlayer->ProcessUsercmds( cmds, numcmds, totalcmds, dropped_packets, paused ); - - return TICK_INTERVAL; -} - - -void CServerGameClients::PostClientMessagesSent_DEPRECIATED( void ) -{ -} - -// Sets the client index for the client who typed the command into his/her console -void CServerGameClients::SetCommandClient( int index ) -{ - g_nCommandClientIndex = index; -} - -int CServerGameClients::GetReplayDelay( edict_t *pEdict, int &entity ) -{ - CBasePlayer *pPlayer = ( CBasePlayer * )CBaseEntity::Instance( pEdict ); - - if ( !pPlayer ) - return 0; - - entity = pPlayer->GetReplayEntity(); - - return pPlayer->GetDelayTicks(); -} - - -//----------------------------------------------------------------------------- -// The client's userinfo data lump has changed -//----------------------------------------------------------------------------- -void CServerGameClients::ClientEarPosition( edict_t *pEdict, Vector *pEarOrigin ) -{ - CBasePlayer *pPlayer = ( CBasePlayer * )CBaseEntity::Instance( pEdict ); - if (pPlayer) - { - *pEarOrigin = pPlayer->EarPosition(); - } - else - { - // Shouldn't happen - Assert(0); - *pEarOrigin = vec3_origin; - } -} - -//----------------------------------------------------------------------------- -// Purpose: -// Input : *player - -// Output : CPlayerState -//----------------------------------------------------------------------------- -CPlayerState *CServerGameClients::GetPlayerState( edict_t *player ) -{ - // Is the client spawned yet? - if ( !player || !player->GetUnknown() ) - return NULL; - - CBasePlayer *pBasePlayer = ( CBasePlayer * )CBaseEntity::Instance( player ); - if ( !pBasePlayer ) - return NULL; - - return &pBasePlayer->pl; -} - -//----------------------------------------------------------------------------- -// Purpose: Anything this game .dll wants to add to the bug reporter text (e.g., the entity/model under the picker crosshair) -// can be added here -// Input : *buf - -// buflen - -//----------------------------------------------------------------------------- -void CServerGameClients::GetBugReportInfo( char *buf, int buflen ) -{ - recentNPCSpeech_t speech[ SPEECH_LIST_MAX_SOUNDS ]; - int num; - int i; - - buf[ 0 ] = 0; - - if ( gpGlobals->maxClients == 1 ) - { - CBaseEntity *ent = FindPickerEntity( UTIL_PlayerByIndex(1) ); - if ( ent ) - { - Q_snprintf( buf, buflen, "Picker %i/%s - ent %s model %s\n", - ent->entindex(), - ent->GetClassname(), - STRING( ent->GetEntityName() ), - STRING( ent->GetModelName() ) ); - } - - // get any sounds that were spoken by NPCs recently - num = GetRecentNPCSpeech( speech ); - if ( num > 0 ) - { - Q_snprintf( buf, buflen, "%sRecent NPC speech:\n", buf ); - for( i = 0; i < num; i++ ) - { - Q_snprintf( buf, buflen, "%s time: %6.3f sound name: %s scene: %s\n", buf, speech[ i ].time, speech[ i ].name, speech[ i ].sceneName ); - } - Q_snprintf( buf, buflen, "%sCurrent time: %6.3f\n", buf, gpGlobals->curtime ); - } - } -} - -//----------------------------------------------------------------------------- -// Purpose: A user has had their network id setup and validated -//----------------------------------------------------------------------------- -void CServerGameClients::NetworkIDValidated( const char *pszUserName, const char *pszNetworkID ) -{ -} - -// The client has submitted a keyvalues command -void CServerGameClients::ClientCommandKeyValues( edict_t *pEntity, KeyValues *pKeyValues ) -{ - if ( !pKeyValues ) - return; - - if ( g_pGameRules ) - { - g_pGameRules->ClientCommandKeyValues( pEntity, pKeyValues ); - } -} - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -static bf_write *g_pMsgBuffer = NULL; - -void EntityMessageBegin( CBaseEntity * entity, bool reliable /*= false*/ ) -{ - Assert( !g_pMsgBuffer ); - - Assert ( entity ); - - g_pMsgBuffer = engine->EntityMessageBegin( entity->entindex(), entity->GetServerClass(), reliable ); -} - -void UserMessageBegin( IRecipientFilter& filter, const char *messagename ) -{ - Assert( !g_pMsgBuffer ); - - Assert( messagename ); - - int msg_type = usermessages->LookupUserMessage( messagename ); - - if ( msg_type == -1 ) - { - Error( "UserMessageBegin: Unregistered message '%s'\n", messagename ); - } - - g_pMsgBuffer = engine->UserMessageBegin( &filter, msg_type ); -} - -void MessageEnd( void ) -{ - Assert( g_pMsgBuffer ); - - engine->MessageEnd(); - - g_pMsgBuffer = NULL; -} - -void MessageWriteByte( int iValue) -{ - if (!g_pMsgBuffer) - Error( "WRITE_BYTE called with no active message\n" ); - - g_pMsgBuffer->WriteByte( iValue ); -} - -void MessageWriteChar( int iValue) -{ - if (!g_pMsgBuffer) - Error( "WRITE_CHAR called with no active message\n" ); - - g_pMsgBuffer->WriteChar( iValue ); -} - -void MessageWriteShort( int iValue) -{ - if (!g_pMsgBuffer) - Error( "WRITE_SHORT called with no active message\n" ); - - g_pMsgBuffer->WriteShort( iValue ); -} - -void MessageWriteWord( int iValue ) -{ - if (!g_pMsgBuffer) - Error( "WRITE_WORD called with no active message\n" ); - - g_pMsgBuffer->WriteWord( iValue ); -} - -void MessageWriteLong( int iValue) -{ - if (!g_pMsgBuffer) - Error( "WriteLong called with no active message\n" ); - - g_pMsgBuffer->WriteLong( iValue ); -} - -void MessageWriteFloat( float flValue) -{ - if (!g_pMsgBuffer) - Error( "WriteFloat called with no active message\n" ); - - g_pMsgBuffer->WriteFloat( flValue ); -} - -void MessageWriteAngle( float flValue) -{ - if (!g_pMsgBuffer) - Error( "WriteAngle called with no active message\n" ); - - g_pMsgBuffer->WriteBitAngle( flValue, 8 ); -} - -void MessageWriteCoord( float flValue) -{ - if (!g_pMsgBuffer) - Error( "WriteCoord called with no active message\n" ); - - g_pMsgBuffer->WriteBitCoord( flValue ); -} - -void MessageWriteVec3Coord( const Vector& rgflValue) -{ - if (!g_pMsgBuffer) - Error( "WriteVec3Coord called with no active message\n" ); - - g_pMsgBuffer->WriteBitVec3Coord( rgflValue ); -} - -void MessageWriteVec3Normal( const Vector& rgflValue) -{ - if (!g_pMsgBuffer) - Error( "WriteVec3Normal called with no active message\n" ); - - g_pMsgBuffer->WriteBitVec3Normal( rgflValue ); -} - -void MessageWriteAngles( const QAngle& rgflValue) -{ - if (!g_pMsgBuffer) - Error( "WriteVec3Normal called with no active message\n" ); - - g_pMsgBuffer->WriteBitAngles( rgflValue ); -} - -void MessageWriteString( const char *sz ) -{ - if (!g_pMsgBuffer) - Error( "WriteString called with no active message\n" ); - - g_pMsgBuffer->WriteString( sz ); -} - -void MessageWriteEntity( int iValue) -{ - if (!g_pMsgBuffer) - Error( "WriteEntity called with no active message\n" ); - - g_pMsgBuffer->WriteShort( iValue ); -} - -void MessageWriteEHandle( CBaseEntity *pEntity ) -{ - if (!g_pMsgBuffer) - Error( "WriteEHandle called with no active message\n" ); - - long iEncodedEHandle; - - if( pEntity ) - { - EHANDLE hEnt = pEntity; - - int iSerialNum = hEnt.GetSerialNumber() & (1 << NUM_NETWORKED_EHANDLE_SERIAL_NUMBER_BITS) - 1; - iEncodedEHandle = hEnt.GetEntryIndex() | (iSerialNum << MAX_EDICT_BITS); - } - else - { - iEncodedEHandle = INVALID_NETWORKED_EHANDLE_VALUE; - } - - g_pMsgBuffer->WriteLong( iEncodedEHandle ); -} - -// bitwise -void MessageWriteBool( bool bValue ) -{ - if (!g_pMsgBuffer) - Error( "WriteBool called with no active message\n" ); - - g_pMsgBuffer->WriteOneBit( bValue ? 1 : 0 ); -} - -void MessageWriteUBitLong( unsigned int data, int numbits ) -{ - if (!g_pMsgBuffer) - Error( "WriteUBitLong called with no active message\n" ); - - g_pMsgBuffer->WriteUBitLong( data, numbits ); -} - -void MessageWriteSBitLong( int data, int numbits ) -{ - if (!g_pMsgBuffer) - Error( "WriteSBitLong called with no active message\n" ); - - g_pMsgBuffer->WriteSBitLong( data, numbits ); -} - -void MessageWriteBits( const void *pIn, int nBits ) -{ - if (!g_pMsgBuffer) - Error( "WriteBits called with no active message\n" ); - - g_pMsgBuffer->WriteBits( pIn, nBits ); -} - -class CServerDLLSharedAppSystems : public IServerDLLSharedAppSystems -{ -public: - CServerDLLSharedAppSystems() - { - AddAppSystem( "soundemittersystem" DLL_EXT_STRING, SOUNDEMITTERSYSTEM_INTERFACE_VERSION ); - AddAppSystem( "scenefilecache" DLL_EXT_STRING, SCENE_FILE_CACHE_INTERFACE_VERSION ); - } - - virtual int Count() - { - return m_Systems.Count(); - } - virtual char const *GetDllName( int idx ) - { - return m_Systems[ idx ].m_pModuleName; - } - virtual char const *GetInterfaceName( int idx ) - { - return m_Systems[ idx ].m_pInterfaceName; - } -private: - void AddAppSystem( char const *moduleName, char const *interfaceName ) - { - AppSystemInfo_t sys; - sys.m_pModuleName = moduleName; - sys.m_pInterfaceName = interfaceName; - m_Systems.AddToTail( sys ); - } - - CUtlVector< AppSystemInfo_t > m_Systems; -}; - -EXPOSE_SINGLE_INTERFACE( CServerDLLSharedAppSystems, IServerDLLSharedAppSystems, SERVER_DLL_SHARED_APPSYSTEMS ); - - -//----------------------------------------------------------------------------- -// -//----------------------------------------------------------------------------- -void CServerGameTags::GetTaggedConVarList( KeyValues *pCvarTagList ) -{ - if ( pCvarTagList && g_pGameRules ) - { - g_pGameRules->GetTaggedConVarList( pCvarTagList ); - } -} - - - -#ifndef NO_STEAM - -CSteamID GetSteamIDForPlayerIndex( int iPlayerIndex ) -{ - const CSteamID *pResult = engine->GetClientSteamIDByPlayerIndex( iPlayerIndex ); - if ( pResult ) - return *pResult; - - // Return a bogus steam ID - return CSteamID(); -} - -#endif +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: encapsulates and implements all the accessing of the game dll from external +// sources (only the engine at the time of writing) +// This files ONLY contains functions and data necessary to build an interface +// to external modules +//===========================================================================// + +#include "cbase.h" +#include "gamestringpool.h" +#include "mapentities_shared.h" +#include "game.h" +#include "entityapi.h" +#include "client.h" +#include "saverestore.h" +#include "entitylist.h" +#include "gamerules.h" +#include "soundent.h" +#include "player.h" +#include "server_class.h" +#include "ai_node.h" +#include "ai_link.h" +#include "ai_saverestore.h" +#include "ai_networkmanager.h" +#include "ndebugoverlay.h" +#include "ivoiceserver.h" +#include +#include "movehelper_server.h" +#include "networkstringtable_gamedll.h" +#include "filesystem.h" +#include "func_areaportalwindow.h" +#include "igamesystem.h" +#include "init_factory.h" +#include "vstdlib/random.h" +#include "env_wind_shared.h" +#include "engine/IEngineSound.h" +#include "ispatialpartition.h" +#include "textstatsmgr.h" +#include "bitbuf.h" +#include "saverestoretypes.h" +#include "physics_saverestore.h" +#include "achievement_saverestore.h" +#include "tier0/vprof.h" +#include "effect_dispatch_data.h" +#include "engine/IStaticPropMgr.h" +#include "TemplateEntities.h" +#include "ai_speech.h" +#include "soundenvelope.h" +#include "usermessages.h" +#include "physics.h" +#include "igameevents.h" +#include "EventLog.h" +#include "datacache/idatacache.h" +#include "engine/ivdebugoverlay.h" +#include "shareddefs.h" +#include "props.h" +#include "timedeventmgr.h" +#include "gameinterface.h" +#include "eventqueue.h" +#include "hltvdirector.h" +#if defined( REPLAY_ENABLED ) +#include "replay/iserverreplaycontext.h" +#endif +#include "SoundEmitterSystem/isoundemittersystembase.h" +#include "AI_ResponseSystem.h" +#include "saverestore_stringtable.h" +#include "util.h" +#include "tier0/icommandline.h" +#include "datacache/imdlcache.h" +#include "engine/iserverplugin.h" +#ifdef _WIN32 +#include "ienginevgui.h" +#endif +#include "ragdoll_shared.h" +#include "toolframework/iserverenginetools.h" +#include "sceneentity.h" +#include "appframework/IAppSystemGroup.h" +#include "scenefilecache/ISceneFileCache.h" +#include "tier2/tier2.h" +#include "particles/particles.h" +#include "gamestats.h" +#include "ixboxsystem.h" +#include "engine/imatchmaking.h" +#include "hl2orange.spa.h" +#include "particle_parse.h" +#ifndef NO_STEAM +#include "steam/steam_gameserver.h" +#endif +#include "tier3/tier3.h" +#include "serverbenchmark_base.h" +#include "querycache.h" + + +#ifdef TF_DLL +#include "gc_clientsystem.h" +#include "econ_item_inventory.h" +#include "steamworks_gamestats.h" +#include "tf/tf_gc_server.h" +#include "tf_gamerules.h" +#include "tf_lobby.h" +#include "player_vs_environment/tf_population_manager.h" + +extern ConVar tf_mm_trusted; +extern ConVar tf_mm_servermode; +#endif + +#ifdef USE_NAV_MESH +#include "nav_mesh.h" +#endif + +#ifdef NEXT_BOT +#include "NextBotManager.h" +#endif + +#ifdef USES_ECON_ITEMS +#include "econ_item_system.h" +#endif // USES_ECON_ITEMS + +#ifdef CSTRIKE_DLL // BOTPORT: TODO: move these ifdefs out +#include "bot/bot.h" +#endif + +#ifdef PORTAL +#include "prop_portal_shared.h" +#include "portal_player.h" +#endif + +#if defined( REPLAY_ENABLED ) +#include "replay/ireplaysystem.h" +#endif + +extern IToolFrameworkServer *g_pToolFrameworkServer; +extern IParticleSystemQuery *g_pParticleSystemQuery; + +extern ConVar commentary; + +#ifndef NO_STEAM +// this context is not available on dedicated servers +// WARNING! always check if interfaces are available before using +static CSteamAPIContext s_SteamAPIContext; +CSteamAPIContext *steamapicontext = &s_SteamAPIContext; + +// this context is not available on a pure client connected to a remote server. +// WARNING! always check if interfaces are available before using +static CSteamGameServerAPIContext s_SteamGameServerAPIContext; +CSteamGameServerAPIContext *steamgameserverapicontext = &s_SteamGameServerAPIContext; +#endif + +IUploadGameStats *gamestatsuploader = NULL; + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +CTimedEventMgr g_NetworkPropertyEventMgr; + +ISaveRestoreBlockHandler *GetEventQueueSaveRestoreBlockHandler(); +ISaveRestoreBlockHandler *GetCommentarySaveRestoreBlockHandler(); + +CUtlLinkedList g_MapEntityRefs; + +// Engine interfaces. +IVEngineServer *engine = NULL; +IVoiceServer *g_pVoiceServer = NULL; +#if !defined(_STATIC_LINKED) +IFileSystem *filesystem = NULL; +#else +extern IFileSystem *filesystem; +#endif +INetworkStringTableContainer *networkstringtable = NULL; +IStaticPropMgrServer *staticpropmgr = NULL; +IUniformRandomStream *random = NULL; +IEngineSound *enginesound = NULL; +ISpatialPartition *partition = NULL; +IVModelInfo *modelinfo = NULL; +IEngineTrace *enginetrace = NULL; +IGameEventManager2 *gameeventmanager = NULL; +IDataCache *datacache = NULL; +IVDebugOverlay * debugoverlay = NULL; +ISoundEmitterSystemBase *soundemitterbase = NULL; +IServerPluginHelpers *serverpluginhelpers = NULL; +IServerEngineTools *serverenginetools = NULL; +ISceneFileCache *scenefilecache = NULL; +IXboxSystem *xboxsystem = NULL; // Xbox 360 only +IMatchmaking *matchmaking = NULL; // Xbox 360 only +#if defined( REPLAY_ENABLED ) +IReplaySystem *g_pReplay = NULL; +IServerReplayContext *g_pReplayServerContext = NULL; +#endif + +IGameSystem *SoundEmitterSystem(); + +bool ModelSoundsCacheInit(); +void ModelSoundsCacheShutdown(); + +void SceneManager_ClientActive( CBasePlayer *player ); + +class IMaterialSystem; +class IStudioRender; + +#ifdef _DEBUG +static ConVar s_UseNetworkVars( "UseNetworkVars", "1", FCVAR_CHEAT, "For profiling, toggle network vars." ); +#endif + +extern ConVar sv_noclipduringpause; +ConVar sv_massreport( "sv_massreport", "0" ); +ConVar sv_force_transmit_ents( "sv_force_transmit_ents", "0", FCVAR_CHEAT | FCVAR_DEVELOPMENTONLY, "Will transmit all entities to client, regardless of PVS conditions (will still skip based on transmit flags, however)." ); + +ConVar sv_autosave( "sv_autosave", "1", 0, "Set to 1 to autosave game on level transition. Does not affect autosave triggers." ); +ConVar *sv_maxreplay = NULL; +static ConVar *g_pcv_commentary = NULL; +static ConVar *g_pcv_ThreadMode = NULL; +static ConVar *g_pcv_hideServer = NULL; + +// String tables +INetworkStringTable *g_pStringTableParticleEffectNames = NULL; +INetworkStringTable *g_pStringTableEffectDispatch = NULL; +INetworkStringTable *g_pStringTableVguiScreen = NULL; +INetworkStringTable *g_pStringTableMaterials = NULL; +INetworkStringTable *g_pStringTableInfoPanel = NULL; +INetworkStringTable *g_pStringTableClientSideChoreoScenes = NULL; +INetworkStringTable *g_pStringTableServerMapCycle = NULL; + +#ifdef TF_DLL +INetworkStringTable *g_pStringTableServerPopFiles = NULL; +INetworkStringTable *g_pStringTableServerMapCycleMvM = NULL; +#endif + +CStringTableSaveRestoreOps g_VguiScreenStringOps; + +// Holds global variables shared between engine and game. +CGlobalVars *gpGlobals; +edict_t *g_pDebugEdictBase = 0; +static int g_nCommandClientIndex = 0; + +// The chapter number of the current +static int g_nCurrentChapterIndex = -1; + +#ifdef _DEBUG +static ConVar sv_showhitboxes( "sv_showhitboxes", "-1", FCVAR_CHEAT, "Send server-side hitboxes for specified entity to client (NOTE: this uses lots of bandwidth, use on listen server only)." ); +#endif + +void PrecachePointTemplates(); + +static ClientPutInServerOverrideFn g_pClientPutInServerOverride = NULL; +static void UpdateChapterRestrictions( const char *mapname ); + +static void UpdateRichPresence ( void ); + + +#if !defined( _XBOX ) // Don't doubly define this symbol. +CSharedEdictChangeInfo *g_pSharedChangeInfo = NULL; + +#endif + +IChangeInfoAccessor *CBaseEdict::GetChangeAccessor() +{ + return engine->GetChangeAccessor( (const edict_t *)this ); +} + +const IChangeInfoAccessor *CBaseEdict::GetChangeAccessor() const +{ + return engine->GetChangeAccessor( (const edict_t *)this ); +} + +const char *GetHintTypeDescription( CAI_Hint *pHint ); + +void ClientPutInServerOverride( ClientPutInServerOverrideFn fn ) +{ + g_pClientPutInServerOverride = fn; +} + +ConVar ai_post_frame_navigation( "ai_post_frame_navigation", "0" ); +class CPostFrameNavigationHook; +extern CPostFrameNavigationHook *PostFrameNavigationSystem( void ); + +//----------------------------------------------------------------------------- +// Purpose: +// Output : int +//----------------------------------------------------------------------------- +int UTIL_GetCommandClientIndex( void ) +{ + // -1 == unknown,dedicated server console + // 0 == player 1 + + // Convert to 1 based offset + return (g_nCommandClientIndex+1); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : CBasePlayer +//----------------------------------------------------------------------------- +CBasePlayer *UTIL_GetCommandClient( void ) +{ + int idx = UTIL_GetCommandClientIndex(); + if ( idx > 0 ) + { + return UTIL_PlayerByIndex( idx ); + } + + // HLDS console issued command + return NULL; +} + +//----------------------------------------------------------------------------- +// Purpose: Retrieves the MOD directory for the active game (ie. "hl2") +//----------------------------------------------------------------------------- + +bool UTIL_GetModDir( char *lpszTextOut, unsigned int nSize ) +{ + // Must pass in a buffer at least large enough to hold the desired string + const char *pGameDir = CommandLine()->ParmValue( "-game", "hl2" ); + Assert( strlen(pGameDir) <= nSize ); + if ( strlen(pGameDir) > nSize ) + return false; + + Q_strncpy( lpszTextOut, pGameDir, nSize ); + if ( Q_strnchr( lpszTextOut, '/', nSize ) || Q_strnchr( lpszTextOut, '\\', nSize ) ) + { + // Strip the last directory off (which will be our game dir) + Q_StripLastDir( lpszTextOut, nSize ); + + // Find the difference in string lengths and take that difference from the original string as the mod dir + int dirlen = Q_strlen( lpszTextOut ); + Q_strncpy( lpszTextOut, pGameDir + dirlen, Q_strlen( pGameDir ) - dirlen + 1 ); + } + + return true; +} + +extern void InitializeCvars( void ); + +CBaseEntity* FindPickerEntity( CBasePlayer* pPlayer ); +CAI_Node* FindPickerAINode( CBasePlayer* pPlayer, NodeType_e nNodeType ); +CAI_Link* FindPickerAILink( CBasePlayer* pPlayer ); +float GetFloorZ(const Vector &origin); +void UpdateAllClientData( void ); +void DrawMessageEntities(); + +#include "ai_network.h" + +// For now just using one big AI network +extern ConVar think_limit; + + +#if 0 +//----------------------------------------------------------------------------- +// Purpose: Draw output overlays for any measure sections +// Input : +//----------------------------------------------------------------------------- +void DrawMeasuredSections(void) +{ + int row = 1; + float rowheight = 0.025; + + CMeasureSection *p = CMeasureSection::GetList(); + while ( p ) + { + char str[256]; + Q_snprintf(str,sizeof(str),"%s",p->GetName()); + NDebugOverlay::ScreenText( 0.01,0.51+(row*rowheight),str, 255,255,255,255, 0.0 ); + + Q_snprintf(str,sizeof(str),"%5.2f\n",p->GetTime().GetMillisecondsF()); + //Q_snprintf(str,sizeof(str),"%3.3f\n",p->GetTime().GetSeconds() * 100.0 / engine->Time()); + NDebugOverlay::ScreenText( 0.28,0.51+(row*rowheight),str, 255,255,255,255, 0.0 ); + + Q_snprintf(str,sizeof(str),"%5.2f\n",p->GetMaxTime().GetMillisecondsF()); + //Q_snprintf(str,sizeof(str),"%3.3f\n",p->GetTime().GetSeconds() * 100.0 / engine->Time()); + NDebugOverlay::ScreenText( 0.34,0.51+(row*rowheight),str, 255,255,255,255, 0.0 ); + + + row++; + + p = p->GetNext(); + } + + bool sort_reset = false; + + // Time to redo sort? + if ( measure_resort.GetFloat() > 0.0 && + engine->Time() >= CMeasureSection::m_dNextResort ) + { + // Redo it + CMeasureSection::SortSections(); + // Set next time + CMeasureSection::m_dNextResort = engine->Time() + measure_resort.GetFloat(); + // Flag to reset sort accumulator, too + sort_reset = true; + } + + // Iterate through the sections now + p = CMeasureSection::GetList(); + while ( p ) + { + // Update max + p->UpdateMax(); + + // Reset regular accum. + p->Reset(); + // Reset sort accum less often + if ( sort_reset ) + { + p->SortReset(); + } + p = p->GetNext(); + } + +} +#endif + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void DrawAllDebugOverlays( void ) +{ + // If in debug select mode print the selection entities name or classname + if (CBaseEntity::m_bInDebugSelect) + { + CBasePlayer* pPlayer = UTIL_PlayerByIndex( CBaseEntity::m_nDebugPlayer ); + + if (pPlayer) + { + // First try to trace a hull to an entity + CBaseEntity *pEntity = FindPickerEntity( pPlayer ); + + if ( pEntity ) + { + pEntity->DrawDebugTextOverlays(); + pEntity->DrawBBoxOverlay(); + pEntity->SendDebugPivotOverlay(); + } + } + } + + // -------------------------------------------------------- + // Draw debug overlay lines + // -------------------------------------------------------- + UTIL_DrawOverlayLines(); + + // ------------------------------------------------------------------------ + // If in wc_edit mode draw a box to highlight which node I'm looking at + // ------------------------------------------------------------------------ + if (engine->IsInEditMode()) + { + CBasePlayer* pPlayer = UTIL_PlayerByIndex( CBaseEntity::m_nDebugPlayer ); + if (pPlayer) + { + if (g_pAINetworkManager->GetEditOps()->m_bLinkEditMode) + { + CAI_Link* pAILink = FindPickerAILink(pPlayer); + if (pAILink) + { + // For now just using one big AI network + Vector startPos = g_pBigAINet->GetNode(pAILink->m_iSrcID)->GetPosition(g_pAINetworkManager->GetEditOps()->m_iHullDrawNum); + Vector endPos = g_pBigAINet->GetNode(pAILink->m_iDestID)->GetPosition(g_pAINetworkManager->GetEditOps()->m_iHullDrawNum); + Vector linkDir = startPos-endPos; + float linkLen = VectorNormalize( linkDir ); + + // Draw in green if link that's been turned off + if (pAILink->m_LinkInfo & bits_LINK_OFF) + { + NDebugOverlay::BoxDirection(startPos, Vector(-4,-4,-4), Vector(-linkLen,4,4), linkDir, 0,255,0,40,0); + } + else + { + NDebugOverlay::BoxDirection(startPos, Vector(-4,-4,-4), Vector(-linkLen,4,4), linkDir, 255,0,0,40,0); + } + } + } + else + { + CAI_Node* pAINode; + if (g_pAINetworkManager->GetEditOps()->m_bAirEditMode) + { + pAINode = FindPickerAINode(pPlayer,NODE_AIR); + } + else + { + pAINode = FindPickerAINode(pPlayer,NODE_GROUND); + } + + if (pAINode) + { + Vector vecPos = pAINode->GetPosition(g_pAINetworkManager->GetEditOps()->m_iHullDrawNum); + NDebugOverlay::Box( vecPos, Vector(-8,-8,-8), Vector(8,8,8), 255,0,0,40,0); + + if ( pAINode->GetHint() ) + { + CBaseEntity *pEnt = (CBaseEntity *)pAINode->GetHint(); + if ( pEnt->GetEntityName() != NULL_STRING ) + { + NDebugOverlay::Text( vecPos + Vector(0,0,6), STRING(pEnt->GetEntityName()), false, 0 ); + } + NDebugOverlay::Text( vecPos, GetHintTypeDescription( pAINode->GetHint() ), false, 0 ); + } + } + } + // ------------------------------------ + // If in air edit mode draw guide line + // ------------------------------------ + if (g_pAINetworkManager->GetEditOps()->m_bAirEditMode) + { + UTIL_DrawPositioningOverlay(g_pAINetworkManager->GetEditOps()->m_flAirEditDistance); + } + else + { + NDebugOverlay::DrawGroundCrossHairOverlay(); + } + } + } + + // For not just using one big AI Network + if ( g_pAINetworkManager ) + { + g_pAINetworkManager->GetEditOps()->DrawAINetworkOverlay(); + } + + // PERFORMANCE: only do this in developer mode + if ( g_pDeveloper->GetInt() && !engine->IsDedicatedServer() ) + { + // iterate through all objects for debug overlays + const CEntInfo *pInfo = gEntList.FirstEntInfo(); + + for ( ;pInfo; pInfo = pInfo->m_pNext ) + { + CBaseEntity *ent = (CBaseEntity *)pInfo->m_pEntity; + // HACKHACK: to flag off these calls + if ( ent->m_debugOverlays || ent->m_pTimedOverlay ) + { + MDLCACHE_CRITICAL_SECTION(); + ent->DrawDebugGeometryOverlays(); + } + } + } + + if ( sv_massreport.GetInt() ) + { + // iterate through all objects for debug overlays + const CEntInfo *pInfo = gEntList.FirstEntInfo(); + + for ( ;pInfo; pInfo = pInfo->m_pNext ) + { + CBaseEntity *ent = (CBaseEntity *)pInfo->m_pEntity; + if (!ent->VPhysicsGetObject()) + continue; + + char tempstr[512]; + Q_snprintf(tempstr, sizeof(tempstr),"%s: Mass: %.2f kg / %.2f lb (%s)", + STRING( ent->GetModelName() ), ent->VPhysicsGetObject()->GetMass(), + kg2lbs(ent->VPhysicsGetObject()->GetMass()), + GetMassEquivalent(ent->VPhysicsGetObject()->GetMass())); + ent->EntityText(0, tempstr, 0); + } + } + + // A hack to draw point_message entities w/o developer required + DrawMessageEntities(); +} + +CServerGameDLL g_ServerGameDLL; +// INTERFACEVERSION_SERVERGAMEDLL_VERSION_8 is compatible with the latest since we're only adding things to the end, so expose that as well. +EXPOSE_SINGLE_INTERFACE_GLOBALVAR(CServerGameDLL, IServerGameDLL008, INTERFACEVERSION_SERVERGAMEDLL_VERSION_8, g_ServerGameDLL ); +EXPOSE_SINGLE_INTERFACE_GLOBALVAR(CServerGameDLL, IServerGameDLL, INTERFACEVERSION_SERVERGAMEDLL, g_ServerGameDLL); + +// When bumping the version to this interface, check that our assumption is still valid and expose the older version in the same way +COMPILE_TIME_ASSERT( INTERFACEVERSION_SERVERGAMEDLL_INT == 9 ); + +bool CServerGameDLL::DLLInit( CreateInterfaceFn appSystemFactory, + CreateInterfaceFn physicsFactory, CreateInterfaceFn fileSystemFactory, + CGlobalVars *pGlobals) +{ + ConnectTier1Libraries( &appSystemFactory, 1 ); + ConnectTier2Libraries( &appSystemFactory, 1 ); + ConnectTier3Libraries( &appSystemFactory, 1 ); + + // Connected in ConnectTier1Libraries + if ( cvar == NULL ) + return false; + +#ifndef _X360 + s_SteamAPIContext.Init(); + s_SteamGameServerAPIContext.Init(); +#endif + + // init each (seperated for ease of debugging) + if ( (engine = (IVEngineServer*)appSystemFactory(INTERFACEVERSION_VENGINESERVER, NULL)) == NULL ) + return false; + if ( (g_pVoiceServer = (IVoiceServer*)appSystemFactory(INTERFACEVERSION_VOICESERVER, NULL)) == NULL ) + return false; + if ( (networkstringtable = (INetworkStringTableContainer *)appSystemFactory(INTERFACENAME_NETWORKSTRINGTABLESERVER,NULL)) == NULL ) + return false; + if ( (staticpropmgr = (IStaticPropMgrServer *)appSystemFactory(INTERFACEVERSION_STATICPROPMGR_SERVER,NULL)) == NULL ) + return false; + if ( (random = (IUniformRandomStream *)appSystemFactory(VENGINE_SERVER_RANDOM_INTERFACE_VERSION, NULL)) == NULL ) + return false; + if ( (enginesound = (IEngineSound *)appSystemFactory(IENGINESOUND_SERVER_INTERFACE_VERSION, NULL)) == NULL ) + return false; + if ( (partition = (ISpatialPartition *)appSystemFactory(INTERFACEVERSION_SPATIALPARTITION, NULL)) == NULL ) + return false; + if ( (modelinfo = (IVModelInfo *)appSystemFactory(VMODELINFO_SERVER_INTERFACE_VERSION, NULL)) == NULL ) + return false; + if ( (enginetrace = (IEngineTrace *)appSystemFactory(INTERFACEVERSION_ENGINETRACE_SERVER,NULL)) == NULL ) + return false; + if ( (filesystem = (IFileSystem *)fileSystemFactory(FILESYSTEM_INTERFACE_VERSION,NULL)) == NULL ) + return false; + if ( (gameeventmanager = (IGameEventManager2 *)appSystemFactory(INTERFACEVERSION_GAMEEVENTSMANAGER2,NULL)) == NULL ) + return false; + if ( (datacache = (IDataCache*)appSystemFactory(DATACACHE_INTERFACE_VERSION, NULL )) == NULL ) + return false; + if ( (soundemitterbase = (ISoundEmitterSystemBase *)appSystemFactory(SOUNDEMITTERSYSTEM_INTERFACE_VERSION, NULL)) == NULL ) + return false; +#ifndef _XBOX + if ( (gamestatsuploader = (IUploadGameStats *)appSystemFactory( INTERFACEVERSION_UPLOADGAMESTATS, NULL )) == NULL ) + return false; +#endif + if ( !mdlcache ) + return false; + if ( (serverpluginhelpers = (IServerPluginHelpers *)appSystemFactory(INTERFACEVERSION_ISERVERPLUGINHELPERS, NULL)) == NULL ) + return false; + if ( (scenefilecache = (ISceneFileCache *)appSystemFactory( SCENE_FILE_CACHE_INTERFACE_VERSION, NULL )) == NULL ) + return false; + if ( IsX360() && (xboxsystem = (IXboxSystem *)appSystemFactory( XBOXSYSTEM_INTERFACE_VERSION, NULL )) == NULL ) + return false; + if ( IsX360() && (matchmaking = (IMatchmaking *)appSystemFactory( VENGINE_MATCHMAKING_VERSION, NULL )) == NULL ) + return false; + + // If not running dedicated, grab the engine vgui interface + if ( !engine->IsDedicatedServer() ) + { +#ifdef _WIN32 + // This interface is optional, and is only valid when running with -tools + serverenginetools = ( IServerEngineTools * )appSystemFactory( VSERVERENGINETOOLS_INTERFACE_VERSION, NULL ); +#endif + } + + // Yes, both the client and game .dlls will try to Connect, the soundemittersystem.dll will handle this gracefully + if ( !soundemitterbase->Connect( appSystemFactory ) ) + return false; + + // cache the globals + gpGlobals = pGlobals; + + g_pSharedChangeInfo = engine->GetSharedEdictChangeInfo(); + + MathLib_Init( 2.2f, 2.2f, 0.0f, 2.0f ); + + // save these in case other system inits need them + factorylist_t factories; + factories.engineFactory = appSystemFactory; + factories.fileSystemFactory = fileSystemFactory; + factories.physicsFactory = physicsFactory; + FactoryList_Store( factories ); + + // load used game events + gameeventmanager->LoadEventsFromFile("resource/gameevents.res"); + + // init the cvar list first in case inits want to reference them + InitializeCvars(); + + // Initialize the particle system + if ( !g_pParticleSystemMgr->Init( g_pParticleSystemQuery ) ) + { + return false; + } + + sv_cheats = g_pCVar->FindVar( "sv_cheats" ); + if ( !sv_cheats ) + return false; + + g_pcv_commentary = g_pCVar->FindVar( "commentary" ); + g_pcv_ThreadMode = g_pCVar->FindVar( "host_thread_mode" ); + g_pcv_hideServer = g_pCVar->FindVar( "hide_server" ); + + sv_maxreplay = g_pCVar->FindVar( "sv_maxreplay" ); + + g_pGameSaveRestoreBlockSet->AddBlockHandler( GetEntitySaveRestoreBlockHandler() ); + g_pGameSaveRestoreBlockSet->AddBlockHandler( GetPhysSaveRestoreBlockHandler() ); + g_pGameSaveRestoreBlockSet->AddBlockHandler( GetAISaveRestoreBlockHandler() ); + g_pGameSaveRestoreBlockSet->AddBlockHandler( GetTemplateSaveRestoreBlockHandler() ); + g_pGameSaveRestoreBlockSet->AddBlockHandler( GetDefaultResponseSystemSaveRestoreBlockHandler() ); + g_pGameSaveRestoreBlockSet->AddBlockHandler( GetCommentarySaveRestoreBlockHandler() ); + g_pGameSaveRestoreBlockSet->AddBlockHandler( GetEventQueueSaveRestoreBlockHandler() ); + g_pGameSaveRestoreBlockSet->AddBlockHandler( GetAchievementSaveRestoreBlockHandler() ); + + // The string system must init first + shutdown last + IGameSystem::Add( GameStringSystem() ); + + // Physics must occur before the sound envelope manager + IGameSystem::Add( PhysicsGameSystem() ); + + // Used to service deferred navigation queries for NPCs + IGameSystem::Add( (IGameSystem *) PostFrameNavigationSystem() ); + + // Add game log system + IGameSystem::Add( GameLogSystem() ); +#ifndef _XBOX + // Add HLTV director + IGameSystem::Add( HLTVDirectorSystem() ); +#endif + // Add sound emitter + IGameSystem::Add( SoundEmitterSystem() ); + + // load Mod specific game events ( MUST be before InitAllSystems() so it can pickup the mod specific events) + gameeventmanager->LoadEventsFromFile("resource/ModEvents.res"); + +#ifdef CSTRIKE_DLL // BOTPORT: TODO: move these ifdefs out + InstallBotControl(); +#endif + + if ( !IGameSystem::InitAllSystems() ) + return false; + +#if defined( REPLAY_ENABLED ) + if ( gameeventmanager->LoadEventsFromFile( "resource/replayevents.res" ) <= 0 ) + { + Warning( "\n*\n* replayevents.res MISSING.\n*\n\n" ); + return false; + } +#endif + + // Due to dependencies, these are not autogamesystems + if ( !ModelSoundsCacheInit() ) + { + return false; + } + + InvalidateQueryCache(); + + // Parse the particle manifest file & register the effects within it + ParseParticleEffects( false, false ); + + // try to get debug overlay, may be NULL if on HLDS + debugoverlay = (IVDebugOverlay *)appSystemFactory( VDEBUG_OVERLAY_INTERFACE_VERSION, NULL ); + +#ifndef _XBOX +#ifdef USE_NAV_MESH + // create the Navigation Mesh interface + TheNavMesh = NavMeshFactory(); +#endif + + // init the gamestatsupload connection + gamestatsuploader->InitConnection(); +#endif + + return true; +} + +void CServerGameDLL::PostInit() +{ + IGameSystem::PostInitAllSystems(); +} + +void CServerGameDLL::DLLShutdown( void ) +{ + + // Due to dependencies, these are not autogamesystems + ModelSoundsCacheShutdown(); + + g_pGameSaveRestoreBlockSet->RemoveBlockHandler( GetAchievementSaveRestoreBlockHandler() ); + g_pGameSaveRestoreBlockSet->RemoveBlockHandler( GetCommentarySaveRestoreBlockHandler() ); + g_pGameSaveRestoreBlockSet->RemoveBlockHandler( GetEventQueueSaveRestoreBlockHandler() ); + g_pGameSaveRestoreBlockSet->RemoveBlockHandler( GetDefaultResponseSystemSaveRestoreBlockHandler() ); + g_pGameSaveRestoreBlockSet->RemoveBlockHandler( GetTemplateSaveRestoreBlockHandler() ); + g_pGameSaveRestoreBlockSet->RemoveBlockHandler( GetAISaveRestoreBlockHandler() ); + g_pGameSaveRestoreBlockSet->RemoveBlockHandler( GetPhysSaveRestoreBlockHandler() ); + g_pGameSaveRestoreBlockSet->RemoveBlockHandler( GetEntitySaveRestoreBlockHandler() ); + + char *pFilename = g_TextStatsMgr.GetStatsFilename(); + if ( !pFilename || !pFilename[0] ) + { + g_TextStatsMgr.SetStatsFilename( "stats.txt" ); + } + g_TextStatsMgr.WriteFile( filesystem ); + + IGameSystem::ShutdownAllSystems(); + +#ifdef CSTRIKE_DLL // BOTPORT: TODO: move these ifdefs out + RemoveBotControl(); +#endif + +#ifndef _XBOX +#ifdef USE_NAV_MESH + // destroy the Navigation Mesh interface + if ( TheNavMesh ) + { + delete TheNavMesh; + TheNavMesh = NULL; + } +#endif + // reset (shutdown) the gamestatsupload connection + gamestatsuploader->InitConnection(); +#endif + +#ifndef _X360 + s_SteamAPIContext.Clear(); // Steam API context shutdown + s_SteamGameServerAPIContext.Clear(); +#endif + + gameeventmanager = NULL; + + DisconnectTier3Libraries(); + DisconnectTier2Libraries(); + ConVar_Unregister(); + DisconnectTier1Libraries(); +} + +bool CServerGameDLL::ReplayInit( CreateInterfaceFn fnReplayFactory ) +{ +#if defined( REPLAY_ENABLED ) + if ( !IsPC() ) + return false; + if ( (g_pReplay = ( IReplaySystem *)fnReplayFactory( REPLAY_INTERFACE_VERSION, NULL )) == NULL ) + return false; + if ( (g_pReplayServerContext = g_pReplay->SV_GetContext()) == NULL ) + return false; + return true; +#else + return false; +#endif +} + +//----------------------------------------------------------------------------- +// Purpose: See shareddefs.h for redefining this. Don't even think about it, though, for HL2. Or you will pay. ywb 9/22/03 +// Output : float +//----------------------------------------------------------------------------- +float CServerGameDLL::GetTickInterval( void ) const +{ + float tickinterval = DEFAULT_TICK_INTERVAL; + +//============================================================================= +// HPE_BEGIN: +// [Forrest] For Counter-Strike, set default tick rate of 66 and removed -tickrate command line parameter. +//============================================================================= +// Ignoring this for now, server ops are abusing it +#if !defined( TF_DLL ) && !defined( CSTRIKE_DLL ) && !defined( DOD_DLL ) +//============================================================================= +// HPE_END +//============================================================================= + // override if tick rate specified in command line + if ( CommandLine()->CheckParm( "-tickrate" ) ) + { + float tickrate = CommandLine()->ParmValue( "-tickrate", 0 ); + if ( tickrate > 10 ) + tickinterval = 1.0f / tickrate; + } +#endif + + return tickinterval; +} + +// This is called when a new game is started. (restart, map) +bool CServerGameDLL::GameInit( void ) +{ + ResetGlobalState(); + engine->ServerCommand( "exec game.cfg\n" ); + engine->ServerExecute( ); + CBaseEntity::sm_bAccurateTriggerBboxChecks = true; + + IGameEvent *event = gameeventmanager->CreateEvent( "game_init" ); + if ( event ) + { + gameeventmanager->FireEvent( event ); + } + + return true; +} + +// This is called when a game ends (server disconnect, death, restart, load) +// NOT on level transitions within a game +void CServerGameDLL::GameShutdown( void ) +{ + ResetGlobalState(); +} + +static bool g_OneWayTransition = false; +void Game_SetOneWayTransition( void ) +{ + g_OneWayTransition = true; +} + +static CUtlVector g_RestoredEntities; +// just for debugging, assert that this is the only time this function is called +static bool g_InRestore = false; + +void AddRestoredEntity( CBaseEntity *pEntity ) +{ + Assert(g_InRestore); + if ( !pEntity ) + return; + + g_RestoredEntities.AddToTail( EHANDLE(pEntity) ); +} + +void EndRestoreEntities() +{ + if ( !g_InRestore ) + return; + + // The entire hierarchy is restored, so we can call GetAbsOrigin again. + //CBaseEntity::SetAbsQueriesValid( true ); + + // Call all entities' OnRestore handlers + for ( int i = g_RestoredEntities.Count()-1; i >=0; --i ) + { + CBaseEntity *pEntity = g_RestoredEntities[i].Get(); + if ( pEntity && !pEntity->IsDormant() ) + { + MDLCACHE_CRITICAL_SECTION(); + pEntity->OnRestore(); + } + } + + g_RestoredEntities.Purge(); + + IGameSystem::OnRestoreAllSystems(); + + g_InRestore = false; + gEntList.CleanupDeleteList(); + + // HACKHACK: UNDONE: We need to redesign the main loop with respect to save/load/server activate + g_ServerGameDLL.ServerActivate( NULL, 0, 0 ); + CBaseEntity::SetAllowPrecache( false ); +} + +void BeginRestoreEntities() +{ + if ( g_InRestore ) + { + DevMsg( "BeginRestoreEntities without previous EndRestoreEntities.\n" ); + gEntList.CleanupDeleteList(); + } + g_RestoredEntities.Purge(); + g_InRestore = true; + + CBaseEntity::SetAllowPrecache( true ); + + // No calls to GetAbsOrigin until the entire hierarchy is restored! + //CBaseEntity::SetAbsQueriesValid( false ); +} + +//----------------------------------------------------------------------------- +// Purpose: This prevents sv.tickcount/gpGlobals->tickcount from advancing during restore which +// would cause a lot of the NPCs to fast forward their think times to the same +// tick due to some ticks being elapsed during restore where there was no simulation going on +//----------------------------------------------------------------------------- +bool CServerGameDLL::IsRestoring() +{ + return g_InRestore; +} + +// Called any time a new level is started (after GameInit() also on level transitions within a game) +bool CServerGameDLL::LevelInit( const char *pMapName, char const *pMapEntities, char const *pOldLevel, char const *pLandmarkName, bool loadGame, bool background ) +{ + VPROF("CServerGameDLL::LevelInit"); + +#ifdef USES_ECON_ITEMS + GameItemSchema_t *pItemSchema = ItemSystem()->GetItemSchema(); + if ( pItemSchema ) + { + pItemSchema->BInitFromDelayedBuffer(); + } +#endif // USES_ECON_ITEMS + + ResetWindspeed(); + UpdateChapterRestrictions( pMapName ); + + if ( IsX360() && !background && (gpGlobals->maxClients == 1) && (g_nCurrentChapterIndex >= 0) ) + { + // Single player games tell xbox live what game & chapter the user is playing + UpdateRichPresence(); + } + + //Tony; parse custom manifest if exists! + ParseParticleEffectsMap( pMapName, false ); + + // IGameSystem::LevelInitPreEntityAllSystems() is called when the world is precached + // That happens either in LoadGameState() or in MapEntity_ParseAllEntities() + if ( loadGame ) + { + if ( pOldLevel ) + { + gpGlobals->eLoadType = MapLoad_Transition; + } + else + { + gpGlobals->eLoadType = MapLoad_LoadGame; + } + + BeginRestoreEntities(); + if ( !engine->LoadGameState( pMapName, 1 ) ) + { + if ( pOldLevel ) + { + MapEntity_ParseAllEntities( pMapEntities ); + } + else + { + // Regular save load case + return false; + } + } + + if ( pOldLevel ) + { + engine->LoadAdjacentEnts( pOldLevel, pLandmarkName ); + } + + if ( g_OneWayTransition ) + { + engine->ClearSaveDirAfterClientLoad(); + } + + if ( pOldLevel && sv_autosave.GetBool() == true ) + { + // This is a single-player style level transition. + // Queue up an autosave one second into the level + CBaseEntity *pAutosave = CBaseEntity::Create( "logic_autosave", vec3_origin, vec3_angle, NULL ); + if ( pAutosave ) + { + g_EventQueue.AddEvent( pAutosave, "Save", 1.0, NULL, NULL ); + g_EventQueue.AddEvent( pAutosave, "Kill", 1.1, NULL, NULL ); + } + } + } + else + { + if ( background ) + { + gpGlobals->eLoadType = MapLoad_Background; + } + else + { + gpGlobals->eLoadType = MapLoad_NewGame; + } + + // Clear out entity references, and parse the entities into it. + g_MapEntityRefs.Purge(); + CMapLoadEntityFilter filter; + MapEntity_ParseAllEntities( pMapEntities, &filter ); + + g_pServerBenchmark->StartBenchmark(); + + // Now call the mod specific parse + LevelInit_ParseAllEntities( pMapEntities ); + } + + // Check low violence settings for this map + g_RagdollLVManager.SetLowViolence( pMapName ); + + // Now that all of the active entities have been loaded in, precache any entities who need point_template parameters + // to be parsed (the above code has loaded all point_template entities) + PrecachePointTemplates(); + + // load MOTD from file into stringtable + LoadMessageOfTheDay(); + + // Sometimes an ent will Remove() itself during its precache, so RemoveImmediate won't happen. + // This makes sure those ents get cleaned up. + gEntList.CleanupDeleteList(); + + g_AIFriendliesTalkSemaphore.Release(); + g_AIFoesTalkSemaphore.Release(); + g_OneWayTransition = false; + + // clear any pending autosavedangerous + m_fAutoSaveDangerousTime = 0.0f; + m_fAutoSaveDangerousMinHealthToCommit = 0.0f; + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: called after every level change and load game, iterates through all the +// active entities and gives them a chance to fix up their state +//----------------------------------------------------------------------------- +#ifdef DEBUG +bool g_bReceivedChainedActivate; +bool g_bCheckForChainedActivate; +#define BeginCheckChainedActivate() if (0) ; else { g_bCheckForChainedActivate = true; g_bReceivedChainedActivate = false; } +#define EndCheckChainedActivate( bCheck ) \ + if (0) ; else \ + { \ + if ( bCheck ) \ + { \ + char msg[ 1024 ]; \ + Q_snprintf( msg, sizeof( msg ), "Entity (%i/%s/%s) failed to call base class Activate()\n", pClass->entindex(), pClass->GetClassname(), STRING( pClass->GetEntityName() ) ); \ + AssertMsg( g_bReceivedChainedActivate == true, msg ); \ + } \ + g_bCheckForChainedActivate = false; \ + } +#else +#define BeginCheckChainedActivate() ((void)0) +#define EndCheckChainedActivate( bCheck ) ((void)0) +#endif + +void CServerGameDLL::ServerActivate( edict_t *pEdictList, int edictCount, int clientMax ) +{ + // HACKHACK: UNDONE: We need to redesign the main loop with respect to save/load/server activate + if ( g_InRestore ) + return; + + if ( gEntList.ResetDeleteList() != 0 ) + { + Msg( "ERROR: Entity delete queue not empty on level start!\n" ); + } + + for ( CBaseEntity *pClass = gEntList.FirstEnt(); pClass != NULL; pClass = gEntList.NextEnt(pClass) ) + { + if ( pClass && !pClass->IsDormant() ) + { + MDLCACHE_CRITICAL_SECTION(); + + BeginCheckChainedActivate(); + pClass->Activate(); + + // We don't care if it finished activating if it decided to remove itself. + EndCheckChainedActivate( !( pClass->GetEFlags() & EFL_KILLME ) ); + } + } + + IGameSystem::LevelInitPostEntityAllSystems(); + // No more precaching after PostEntityAllSystems!!! + CBaseEntity::SetAllowPrecache( false ); + + // only display the think limit when the game is run with "developer" mode set + if ( !g_pDeveloper->GetInt() ) + { + think_limit.SetValue( 0 ); + } + +#ifndef _XBOX +#ifdef USE_NAV_MESH + // load the Navigation Mesh for this map + TheNavMesh->Load(); + TheNavMesh->OnServerActivate(); +#endif +#endif + +#ifdef CSTRIKE_DLL // BOTPORT: TODO: move these ifdefs out + TheBots->ServerActivate(); +#endif + +#ifdef NEXT_BOT + TheNextBots().OnMapLoaded(); +#endif +} + +//----------------------------------------------------------------------------- +// Purpose: Called after the steam API has been activated post-level startup +//----------------------------------------------------------------------------- +void CServerGameDLL::GameServerSteamAPIActivated( void ) +{ +#ifndef NO_STEAM + steamgameserverapicontext->Init(); + if ( steamgameserverapicontext->SteamGameServer() && engine->IsDedicatedServer() ) + { + steamgameserverapicontext->SteamGameServer()->GetGameplayStats(); + } +#endif + +#ifdef TF_DLL + GCClientSystem()->GameServerActivate(); + InventoryManager()->GameServerSteamAPIActivated(); +#endif +} + +//----------------------------------------------------------------------------- +// Purpose: Called after the steam API has been activated post-level startup +//----------------------------------------------------------------------------- +void CServerGameDLL::GameServerSteamAPIShutdown( void ) +{ +#if !defined( NO_STEAM ) + if ( steamgameserverapicontext ) + { + steamgameserverapicontext->Clear(); + } +#endif +#ifdef TF_DLL + GCClientSystem()->Shutdown(); +#endif +} + +//----------------------------------------------------------------------------- +// Purpose: Called at the start of every game frame +//----------------------------------------------------------------------------- +ConVar trace_report( "trace_report", "0" ); + +void CServerGameDLL::GameFrame( bool simulating ) +{ + VPROF( "CServerGameDLL::GameFrame" ); + + // Don't run frames until fully restored + if ( g_InRestore ) + return; + + if ( CBaseEntity::IsSimulatingOnAlternateTicks() ) + { + // only run simulation on even numbered ticks + if ( gpGlobals->tickcount & 1 ) + { + UpdateAllClientData(); + return; + } + // If we're skipping frames, then the frametime is 2x the normal tick + gpGlobals->frametime *= 2.0f; + } + + float oldframetime = gpGlobals->frametime; + +#ifdef _DEBUG + // For profiling.. let them enable/disable the networkvar manual mode stuff. + g_bUseNetworkVars = s_UseNetworkVars.GetBool(); +#endif + + extern void GameStartFrame( void ); + extern void ServiceEventQueue( void ); + extern void Physics_RunThinkFunctions( bool simulating ); + + // Delete anything that was marked for deletion + // outside of server frameloop (e.g., in response to concommand) + gEntList.CleanupDeleteList(); + + IGameSystem::FrameUpdatePreEntityThinkAllSystems(); + GameStartFrame(); + +#ifndef _XBOX +#ifdef USE_NAV_MESH + TheNavMesh->Update(); +#endif + +#ifdef NEXT_BOT + TheNextBots().Update(); +#endif + + gamestatsuploader->UpdateConnection(); +#endif + + UpdateQueryCache(); + g_pServerBenchmark->UpdateBenchmark(); + + Physics_RunThinkFunctions( simulating ); + + IGameSystem::FrameUpdatePostEntityThinkAllSystems(); + + // UNDONE: Make these systems IGameSystems and move these calls into FrameUpdatePostEntityThink() + // service event queue, firing off any actions whos time has come + ServiceEventQueue(); + + // free all ents marked in think functions + gEntList.CleanupDeleteList(); + + // FIXME: Should this only occur on the final tick? + UpdateAllClientData(); + + if ( g_pGameRules ) + { + g_pGameRules->EndGameFrame(); + } + + if ( trace_report.GetBool() ) + { + int total = 0, totals[3]; + for ( int i = 0; i < 3; i++ ) + { + totals[i] = enginetrace->GetStatByIndex( i, true ); + if ( totals[i] > 0 ) + { + total += totals[i]; + } + } + + if ( total ) + { + Msg("Trace: %d, contents %d, enumerate %d\n", totals[0], totals[1], totals[2] ); + } + } + + // Any entities that detect network state changes on a timer do it here. + g_NetworkPropertyEventMgr.FireEvents(); + + gpGlobals->frametime = oldframetime; +} + +//----------------------------------------------------------------------------- +// Purpose: Called every frame even if not ticking +// Input : simulating - +//----------------------------------------------------------------------------- +void CServerGameDLL::PreClientUpdate( bool simulating ) +{ + if ( !simulating ) + return; + + /* + if (game_speeds.GetInt()) + { + DrawMeasuredSections(); + } + */ + +//#ifdef _DEBUG - allow this in release for now + DrawAllDebugOverlays(); +//#endif + + IGameSystem::PreClientUpdateAllSystems(); + +#ifdef _DEBUG + if ( sv_showhitboxes.GetInt() == -1 ) + return; + + if ( sv_showhitboxes.GetInt() == 0 ) + { + // assume it's text + CBaseEntity *pEntity = NULL; + + while (1) + { + pEntity = gEntList.FindEntityByName( pEntity, sv_showhitboxes.GetString() ); + if ( !pEntity ) + break; + + CBaseAnimating *anim = dynamic_cast< CBaseAnimating * >( pEntity ); + + if (anim) + { + anim->DrawServerHitboxes(); + } + } + return; + } + + CBaseAnimating *anim = dynamic_cast< CBaseAnimating * >( CBaseEntity::Instance( engine->PEntityOfEntIndex( sv_showhitboxes.GetInt() ) ) ); + if ( !anim ) + return; + + anim->DrawServerHitboxes(); +#endif +} + +void CServerGameDLL::Think( bool finalTick ) +{ + if ( m_fAutoSaveDangerousTime != 0.0f && m_fAutoSaveDangerousTime < gpGlobals->curtime ) + { + // The safety timer for a dangerous auto save has expired + CBasePlayer *pPlayer = UTIL_PlayerByIndex( 1 ); + + if ( pPlayer && ( pPlayer->GetDeathTime() == 0.0f || pPlayer->GetDeathTime() > gpGlobals->curtime ) + && !pPlayer->IsSinglePlayerGameEnding() + ) + { + if( pPlayer->GetHealth() >= m_fAutoSaveDangerousMinHealthToCommit ) + { + // The player isn't dead, so make the dangerous auto save safe + engine->ServerCommand( "autosavedangerousissafe\n" ); + } + } + + m_fAutoSaveDangerousTime = 0.0f; + m_fAutoSaveDangerousMinHealthToCommit = 0.0f; + } +} + +void CServerGameDLL::OnQueryCvarValueFinished( QueryCvarCookie_t iCookie, edict_t *pPlayerEntity, EQueryCvarValueStatus eStatus, const char *pCvarName, const char *pCvarValue ) +{ +} + + +// Called when a level is shutdown (including changing levels) +void CServerGameDLL::LevelShutdown( void ) +{ +#ifndef NO_STEAM + IGameSystem::LevelShutdownPreClearSteamAPIContextAllSystems(); + + steamgameserverapicontext->Clear(); +#endif + + g_pServerBenchmark->EndBenchmark(); + + MDLCACHE_CRITICAL_SECTION(); + IGameSystem::LevelShutdownPreEntityAllSystems(); + + // YWB: + // This entity pointer is going away now and is corrupting memory on level transitions/restarts + CSoundEnt::ShutdownSoundEnt(); + + gEntList.Clear(); + + InvalidateQueryCache(); + + IGameSystem::LevelShutdownPostEntityAllSystems(); + + // In case we quit out during initial load + CBaseEntity::SetAllowPrecache( false ); + + g_nCurrentChapterIndex = -1; + +#ifndef _XBOX +#ifdef USE_NAV_MESH + // reset the Navigation Mesh + if ( TheNavMesh ) + { + TheNavMesh->Reset(); + } +#endif +#endif +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : +// Output : ServerClass* +//----------------------------------------------------------------------------- +ServerClass* CServerGameDLL::GetAllServerClasses() +{ + return g_pServerClassHead; +} + + +const char *CServerGameDLL::GetGameDescription( void ) +{ + return ::GetGameDescription(); +} + +void CServerGameDLL::CreateNetworkStringTables( void ) +{ + // Create any shared string tables here (and only here!) + // E.g.: xxx = networkstringtable->CreateStringTable( "SceneStrings", 512 ); + g_pStringTableParticleEffectNames = networkstringtable->CreateStringTable( "ParticleEffectNames", MAX_PARTICLESYSTEMS_STRINGS ); + g_pStringTableEffectDispatch = networkstringtable->CreateStringTable( "EffectDispatch", MAX_EFFECT_DISPATCH_STRINGS ); + g_pStringTableVguiScreen = networkstringtable->CreateStringTable( "VguiScreen", MAX_VGUI_SCREEN_STRINGS ); + g_pStringTableMaterials = networkstringtable->CreateStringTable( "Materials", MAX_MATERIAL_STRINGS ); + g_pStringTableInfoPanel = networkstringtable->CreateStringTable( "InfoPanel", MAX_INFOPANEL_STRINGS ); + g_pStringTableClientSideChoreoScenes = networkstringtable->CreateStringTable( "Scenes", MAX_CHOREO_SCENES_STRINGS ); + g_pStringTableServerMapCycle = networkstringtable->CreateStringTable( "ServerMapCycle", 128 ); + +#ifdef TF_DLL + g_pStringTableServerPopFiles = networkstringtable->CreateStringTable( "ServerPopFiles", 128 ); + g_pStringTableServerMapCycleMvM = networkstringtable->CreateStringTable( "ServerMapCycleMvM", 128 ); +#endif + + bool bPopFilesValid = true; + (void)bPopFilesValid; // Avoid unreferenced variable warning + +#ifdef TF_DLL + bPopFilesValid = ( g_pStringTableServerPopFiles != NULL ); +#endif + + Assert( g_pStringTableParticleEffectNames && + g_pStringTableEffectDispatch && + g_pStringTableVguiScreen && + g_pStringTableMaterials && + g_pStringTableInfoPanel && + g_pStringTableClientSideChoreoScenes && + g_pStringTableServerMapCycle && + bPopFilesValid + ); + + // Need this so we have the error material always handy + PrecacheMaterial( "debug/debugempty" ); + Assert( GetMaterialIndex( "debug/debugempty" ) == 0 ); + + PrecacheParticleSystem( "error" ); // ensure error particle system is handy + Assert( GetParticleSystemIndex( "error" ) == 0 ); + + CreateNetworkStringTables_GameRules(); + + // Set up save/load utilities for string tables + g_VguiScreenStringOps.Init( g_pStringTableVguiScreen ); +} + +CSaveRestoreData *CServerGameDLL::SaveInit( int size ) +{ + return ::SaveInit(size); +} + +//----------------------------------------------------------------------------- +// Purpose: Saves data from a struct into a saverestore object, to be saved to disk +// Input : *pSaveData - the saverestore object +// char *pname - the name of the data to write +// *pBaseData - the struct into which the data is to be read +// *pFields - pointer to an array of data field descriptions +// fieldCount - the size of the array (number of field descriptions) +//----------------------------------------------------------------------------- +void CServerGameDLL::SaveWriteFields( CSaveRestoreData *pSaveData, const char *pname, void *pBaseData, datamap_t *pMap, typedescription_t *pFields, int fieldCount ) +{ + CSave saveHelper( pSaveData ); + saveHelper.WriteFields( pname, pBaseData, pMap, pFields, fieldCount ); +} + + +//----------------------------------------------------------------------------- +// Purpose: Reads data from a save/restore block into a structure +// Input : *pSaveData - the saverestore object +// char *pname - the name of the data to extract from +// *pBaseData - the struct into which the data is to be restored +// *pFields - pointer to an array of data field descriptions +// fieldCount - the size of the array (number of field descriptions) +//----------------------------------------------------------------------------- + +//----------------------------------------------------------------------------- + +void CServerGameDLL::SaveReadFields( CSaveRestoreData *pSaveData, const char *pname, void *pBaseData, datamap_t *pMap, typedescription_t *pFields, int fieldCount ) +{ + CRestore restoreHelper( pSaveData ); + restoreHelper.ReadFields( pname, pBaseData, pMap, pFields, fieldCount ); +} + +//----------------------------------------------------------------------------- + +void CServerGameDLL::SaveGlobalState( CSaveRestoreData *s ) +{ + ::SaveGlobalState(s); +} + +void CServerGameDLL::RestoreGlobalState(CSaveRestoreData *s) +{ + ::RestoreGlobalState(s); +} + +void CServerGameDLL::Save( CSaveRestoreData *s ) +{ + CSave saveHelper( s ); + g_pGameSaveRestoreBlockSet->Save( &saveHelper ); +} + +void CServerGameDLL::Restore( CSaveRestoreData *s, bool b) +{ + CRestore restore(s); + g_pGameSaveRestoreBlockSet->Restore( &restore, b ); + g_pGameSaveRestoreBlockSet->PostRestore(); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : msg_type - +// *name - +// size - +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- + +bool CServerGameDLL::GetUserMessageInfo( int msg_type, char *name, int maxnamelength, int& size ) +{ + if ( !usermessages->IsValidIndex( msg_type ) ) + return false; + + Q_strncpy( name, usermessages->GetUserMessageName( msg_type ), maxnamelength ); + size = usermessages->GetUserMessageSize( msg_type ); + return true; +} + +CStandardSendProxies* CServerGameDLL::GetStandardSendProxies() +{ + return &g_StandardSendProxies; +} + +int CServerGameDLL::CreateEntityTransitionList( CSaveRestoreData *s, int a) +{ + CRestore restoreHelper( s ); + // save off file base + int base = restoreHelper.GetReadPos(); + + int movedCount = ::CreateEntityTransitionList(s, a); + if ( movedCount ) + { + g_pGameSaveRestoreBlockSet->CallBlockHandlerRestore( GetPhysSaveRestoreBlockHandler(), base, &restoreHelper, false ); + g_pGameSaveRestoreBlockSet->CallBlockHandlerRestore( GetAISaveRestoreBlockHandler(), base, &restoreHelper, false ); + } + + GetPhysSaveRestoreBlockHandler()->PostRestore(); + GetAISaveRestoreBlockHandler()->PostRestore(); + + return movedCount; +} + +void CServerGameDLL::PreSave( CSaveRestoreData *s ) +{ + g_pGameSaveRestoreBlockSet->PreSave( s ); +} + +#include "client_textmessage.h" + +// This little hack lets me marry BSP names to messages in titles.txt +typedef struct +{ + const char *pBSPName; + const char *pTitleName; +} TITLECOMMENT; + +// this list gets searched for the first partial match, so some are out of order +static TITLECOMMENT gTitleComments[] = +{ +#ifdef HL1_DLL + { "t0a0", "#T0A0TITLE" }, + { "c0a0", "#HL1_Chapter1_Title" }, + { "c1a0", "#HL1_Chapter2_Title" }, + { "c1a1", "#HL1_Chapter3_Title" }, + { "c1a2", "#HL1_Chapter4_Title" }, + { "c1a3", "#HL1_Chapter5_Title" }, + { "c1a4", "#HL1_Chapter6_Title" }, + { "c2a1", "#HL1_Chapter7_Title" }, + { "c2a2", "#HL1_Chapter8_Title" }, + { "c2a3", "#HL1_Chapter9_Title" }, + { "c2a4d", "#HL1_Chapter11_Title" }, // These must appear before "C2A4" so all other map names starting with C2A4 get that title + { "c2a4e", "#HL1_Chapter11_Title" }, + { "c2a4f", "#HL1_Chapter11_Title" }, + { "c2a4g", "#HL1_Chapter11_Title" }, + { "c2a4", "#HL1_Chapter10_Title" }, + { "c2a5", "#HL1_Chapter12_Title" }, + { "c3a1", "#HL1_Chapter13_Title" }, + { "c3a2", "#HL1_Chapter14_Title" }, + { "c4a1a", "#HL1_Chapter17_Title" }, // Order is important, see above + { "c4a1b", "#HL1_Chapter17_Title" }, + { "c4a1c", "#HL1_Chapter17_Title" }, + { "c4a1d", "#HL1_Chapter17_Title" }, + { "c4a1e", "#HL1_Chapter17_Title" }, + { "c4a1", "#HL1_Chapter15_Title" }, + { "c4a2", "#HL1_Chapter16_Title" }, + { "c4a3", "#HL1_Chapter18_Title" }, + { "c5a1", "#HL1_Chapter19_Title" }, +#elif defined PORTAL + { "testchmb_a_00", "#Portal_Chapter1_Title" }, + { "testchmb_a_01", "#Portal_Chapter1_Title" }, + { "testchmb_a_02", "#Portal_Chapter2_Title" }, + { "testchmb_a_03", "#Portal_Chapter2_Title" }, + { "testchmb_a_04", "#Portal_Chapter3_Title" }, + { "testchmb_a_05", "#Portal_Chapter3_Title" }, + { "testchmb_a_06", "#Portal_Chapter4_Title" }, + { "testchmb_a_07", "#Portal_Chapter4_Title" }, + { "testchmb_a_08_advanced", "#Portal_Chapter5_Title" }, + { "testchmb_a_08", "#Portal_Chapter5_Title" }, + { "testchmb_a_09_advanced", "#Portal_Chapter6_Title" }, + { "testchmb_a_09", "#Portal_Chapter6_Title" }, + { "testchmb_a_10_advanced", "#Portal_Chapter7_Title" }, + { "testchmb_a_10", "#Portal_Chapter7_Title" }, + { "testchmb_a_11_advanced", "#Portal_Chapter8_Title" }, + { "testchmb_a_11", "#Portal_Chapter8_Title" }, + { "testchmb_a_13_advanced", "#Portal_Chapter9_Title" }, + { "testchmb_a_13", "#Portal_Chapter9_Title" }, + { "testchmb_a_14_advanced", "#Portal_Chapter10_Title" }, + { "testchmb_a_14", "#Portal_Chapter10_Title" }, + { "testchmb_a_15", "#Portal_Chapter11_Title" }, + { "escape_", "#Portal_Chapter11_Title" }, + { "background2", "#Portal_Chapter12_Title" }, +#else + { "intro", "#HL2_Chapter1_Title" }, + + { "d1_trainstation_05", "#HL2_Chapter2_Title" }, + { "d1_trainstation_06", "#HL2_Chapter2_Title" }, + + { "d1_trainstation_", "#HL2_Chapter1_Title" }, + + { "d1_canals_06", "#HL2_Chapter4_Title" }, + { "d1_canals_07", "#HL2_Chapter4_Title" }, + { "d1_canals_08", "#HL2_Chapter4_Title" }, + { "d1_canals_09", "#HL2_Chapter4_Title" }, + { "d1_canals_1", "#HL2_Chapter4_Title" }, + + { "d1_canals_0", "#HL2_Chapter3_Title" }, + + { "d1_eli_", "#HL2_Chapter5_Title" }, + + { "d1_town_", "#HL2_Chapter6_Title" }, + + { "d2_coast_09", "#HL2_Chapter8_Title" }, + { "d2_coast_1", "#HL2_Chapter8_Title" }, + { "d2_prison_01", "#HL2_Chapter8_Title" }, + + { "d2_coast_", "#HL2_Chapter7_Title" }, + + { "d2_prison_06", "#HL2_Chapter9a_Title" }, + { "d2_prison_07", "#HL2_Chapter9a_Title" }, + { "d2_prison_08", "#HL2_Chapter9a_Title" }, + + { "d2_prison_", "#HL2_Chapter9_Title" }, + + { "d3_c17_01", "#HL2_Chapter9a_Title" }, + { "d3_c17_09", "#HL2_Chapter11_Title" }, + { "d3_c17_1", "#HL2_Chapter11_Title" }, + + { "d3_c17_", "#HL2_Chapter10_Title" }, + + { "d3_citadel_", "#HL2_Chapter12_Title" }, + + { "d3_breen_", "#HL2_Chapter13_Title" }, + { "credits", "#HL2_Chapter14_Title" }, + + { "ep1_citadel_00", "#episodic_Chapter1_Title" }, + { "ep1_citadel_01", "#episodic_Chapter1_Title" }, + { "ep1_citadel_02b", "#episodic_Chapter1_Title" }, + { "ep1_citadel_02", "#episodic_Chapter1_Title" }, + { "ep1_citadel_03", "#episodic_Chapter2_Title" }, + { "ep1_citadel_04", "#episodic_Chapter2_Title" }, + { "ep1_c17_00a", "#episodic_Chapter3_Title" }, + { "ep1_c17_00", "#episodic_Chapter3_Title" }, + { "ep1_c17_01", "#episodic_Chapter4_Title" }, + { "ep1_c17_02b", "#episodic_Chapter4_Title" }, + { "ep1_c17_02", "#episodic_Chapter4_Title" }, + { "ep1_c17_05", "#episodic_Chapter5_Title" }, + { "ep1_c17_06", "#episodic_Chapter5_Title" }, + + { "ep2_outland_01a", "#ep2_Chapter1_Title" }, + { "ep2_outland_01", "#ep2_Chapter1_Title" }, + { "ep2_outland_02", "#ep2_Chapter2_Title" }, + { "ep2_outland_03", "#ep2_Chapter2_Title" }, + { "ep2_outland_04", "#ep2_Chapter2_Title" }, + { "ep2_outland_05", "#ep2_Chapter3_Title" }, + + { "ep2_outland_06a", "#ep2_Chapter4_Title" }, + { "ep2_outland_06", "#ep2_Chapter3_Title" }, + + { "ep2_outland_07", "#ep2_Chapter4_Title" }, + { "ep2_outland_08", "#ep2_Chapter4_Title" }, + { "ep2_outland_09", "#ep2_Chapter5_Title" }, + + { "ep2_outland_10a", "#ep2_Chapter5_Title" }, + { "ep2_outland_10", "#ep2_Chapter5_Title" }, + + { "ep2_outland_11a", "#ep2_Chapter6_Title" }, + { "ep2_outland_11", "#ep2_Chapter6_Title" }, + + { "ep2_outland_12a", "#ep2_Chapter7_Title" }, + { "ep2_outland_12", "#ep2_Chapter6_Title" }, +#endif +}; + +#ifdef _XBOX +void CServerGameDLL::GetTitleName( const char *pMapName, char* pTitleBuff, int titleBuffSize ) +{ + // Try to find a matching title comment for this mapname + for ( int i = 0; i < ARRAYSIZE(gTitleComments); i++ ) + { + if ( !Q_strnicmp( pMapName, gTitleComments[i].pBSPName, strlen(gTitleComments[i].pBSPName) ) ) + { + Q_strncpy( pTitleBuff, gTitleComments[i].pTitleName, titleBuffSize ); + return; + } + } + Q_strncpy( pTitleBuff, pMapName, titleBuffSize ); +} +#endif + +void CServerGameDLL::GetSaveComment( char *text, int maxlength, float flMinutes, float flSeconds, bool bNoTime ) +{ + char comment[64]; + const char *pName; + int i; + + char const *mapname = STRING( gpGlobals->mapname ); + + pName = NULL; + + // Try to find a matching title comment for this mapname + for ( i = 0; i < ARRAYSIZE(gTitleComments) && !pName; i++ ) + { + if ( !Q_strnicmp( mapname, gTitleComments[i].pBSPName, strlen(gTitleComments[i].pBSPName) ) ) + { + // found one + int j; + + // Got a message, post-process it to be save name friendly + Q_strncpy( comment, gTitleComments[i].pTitleName, sizeof( comment ) ); + pName = comment; + j = 0; + // Strip out CRs + while ( j < 64 && comment[j] ) + { + if ( comment[j] == '\n' || comment[j] == '\r' ) + comment[j] = 0; + else + j++; + } + break; + } + } + + // If we didn't get one, use the designer's map name, or the BSP name itself + if ( !pName ) + { + pName = mapname; + } + + if ( bNoTime ) + { + Q_snprintf( text, maxlength, "%-64.64s", pName ); + } + else + { + int minutes = flMinutes; + int seconds = flSeconds; + + // Wow, this guy/gal must suck...! + if ( minutes >= 1000 ) + { + minutes = 999; + seconds = 59; + } + + int minutesAdd = ( seconds / 60 ); + seconds %= 60; + + // add the elapsed time at the end of the comment, for the ui to parse out + Q_snprintf( text, maxlength, "%-64.64s %03d:%02d", pName, (minutes + minutesAdd), seconds ); + } +} + +void CServerGameDLL::WriteSaveHeaders( CSaveRestoreData *s ) +{ + CSave saveHelper( s ); + g_pGameSaveRestoreBlockSet->WriteSaveHeaders( &saveHelper ); + g_pGameSaveRestoreBlockSet->PostSave(); +} + +void CServerGameDLL::ReadRestoreHeaders( CSaveRestoreData *s ) +{ + CRestore restoreHelper( s ); + g_pGameSaveRestoreBlockSet->PreRestore(); + g_pGameSaveRestoreBlockSet->ReadRestoreHeaders( &restoreHelper ); +} + +void CServerGameDLL::PreSaveGameLoaded( char const *pSaveName, bool bInGame ) +{ + gamestats->Event_PreSaveGameLoaded( pSaveName, bInGame ); +} + +//----------------------------------------------------------------------------- +// Purpose: Returns true if the game DLL wants the server not to be made public. +// Used by commentary system to hide multiplayer commentary servers from the master. +//----------------------------------------------------------------------------- +bool CServerGameDLL::ShouldHideServer( void ) +{ + if ( g_pcv_commentary && g_pcv_commentary->GetBool() ) + return true; + + if ( g_pcv_hideServer && g_pcv_hideServer->GetBool() ) + return true; + + if ( gpGlobals->eLoadType == MapLoad_Background ) + return true; + + #if defined( TF_DLL ) + if ( GTFGCClientSystem()->ShouldHideServer() ) + return true; + #endif + return false; +} + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +void CServerGameDLL::InvalidateMdlCache() +{ + CBaseAnimating *pAnimating; + for ( CBaseEntity *pEntity = gEntList.FirstEnt(); pEntity != NULL; pEntity = gEntList.NextEnt(pEntity) ) + { + pAnimating = dynamic_cast(pEntity); + if ( pAnimating ) + { + pAnimating->InvalidateMdlCache(); + } + } +} + +// interface to the new GC based lobby system +IServerGCLobby *CServerGameDLL::GetServerGCLobby() +{ +#ifdef TF_DLL + return GTFGCClientSystem(); +#else + return NULL; +#endif +} + + +void CServerGameDLL::SetServerHibernation( bool bHibernating ) +{ + m_bIsHibernating = bHibernating; + +#ifdef INFESTED_DLL + if ( engine && engine->IsDedicatedServer() && m_bIsHibernating && ASWGameRules() ) + { + ASWGameRules()->OnServerHibernating(); + } +#endif + +#ifdef TF_DLL + GTFGCClientSystem()->SetHibernation( bHibernating ); +#endif +} + +const char *CServerGameDLL::GetServerBrowserMapOverride() +{ +#ifdef TF_DLL + if ( TFGameRules() && TFGameRules()->IsMannVsMachineMode() && g_pPopulationManager && g_pPopulationManager->GetPopulationFilenameShort() != '\0' ) + { + return g_pPopulationManager->GetPopulationFilenameShort(); + } +#endif + return NULL; +} + +const char *CServerGameDLL::GetServerBrowserGameData() +{ + CUtlString sResult; + +#ifdef TF_DLL + sResult.Format( "tf_mm_trusted:%d,tf_mm_servermode:%d", tf_mm_trusted.GetInt(), tf_mm_servermode.GetInt() ); + + CTFLobby *pLobby = GTFGCClientSystem()->GetLobby(); + if ( pLobby == NULL ) + { + sResult.Append( ",lobby:0" ); + } + else + { + sResult.Append( CFmtStr( ",lobby:%016llx", pLobby->GetGroupID() ) ); + } + if ( TFGameRules() && TFGameRules()->IsMannVsMachineMode() ) + { + sResult.Append( CFmtStr( ",mannup:%d", ( pLobby && pLobby->GetPlayingForBraggingRights() ) ? 1 : 0 ) ); + } +#endif + + static char rchResult[2048]; + V_strcpy_safe( rchResult, sResult ); + return rchResult; +} + +//----------------------------------------------------------------------------- +// Purpose: Called during a transition, to build a map adjacency list +//----------------------------------------------------------------------------- +void CServerGameDLL::BuildAdjacentMapList( void ) +{ + // retrieve the pointer to the save data + CSaveRestoreData *pSaveData = gpGlobals->pSaveData; + + if ( pSaveData ) + pSaveData->levelInfo.connectionCount = BuildChangeList( pSaveData->levelInfo.levelList, MAX_LEVEL_CONNECTIONS ); +} + +//----------------------------------------------------------------------------- +// Purpose: Sanity-check to verify that a path is a relative path inside the game dir +// Taken From: engine/cmd.cpp +//----------------------------------------------------------------------------- +static bool IsValidPath( const char *pszFilename ) +{ + if ( !pszFilename ) + { + return false; + } + + if ( Q_strlen( pszFilename ) <= 0 || + Q_IsAbsolutePath( pszFilename ) || // to protect absolute paths + Q_strstr( pszFilename, ".." ) ) // to protect relative paths + { + return false; + } + + return true; +} + +static void ValidateMOTDFilename( IConVar *pConVar, const char *oldValue, float flOldValue ) +{ + ConVarRef var( pConVar ); + if ( !IsValidPath( var.GetString() ) ) + { + var.SetValue( var.GetDefault() ); + } +} + +static ConVar motdfile( "motdfile", "motd.txt", 0, "The MOTD file to load.", ValidateMOTDFilename ); +static ConVar motdfile_text( "motdfile_text", "motd_text.txt", 0, "The text-only MOTD file to use for clients that have disabled HTML MOTDs.", ValidateMOTDFilename ); +void CServerGameDLL::LoadMessageOfTheDay() +{ + LoadSpecificMOTDMsg( motdfile, "motd" ); + LoadSpecificMOTDMsg( motdfile_text, "motd_text" ); +} + +void CServerGameDLL::LoadSpecificMOTDMsg( const ConVar &convar, const char *pszStringName ) +{ +#ifndef _XBOX + CUtlBuffer buf; + + // Generate preferred filename, which is in the cfg folder. + char szPreferredFilename[ MAX_PATH ]; + V_sprintf_safe( szPreferredFilename, "cfg/%s", convar.GetString() ); + + // Check the preferred filename first + char szResolvedFilename[ MAX_PATH ]; + V_strcpy_safe( szResolvedFilename, szPreferredFilename ); + bool bFound = filesystem->ReadFile( szResolvedFilename, "GAME", buf ); + + // Not found? Try in the root, which is the old place it used to go. + if ( !bFound ) + { + + V_strcpy_safe( szResolvedFilename, convar.GetString() ); + bFound = filesystem->ReadFile( szResolvedFilename, "GAME", buf ); + } + + // Still not found? See if we can try the default. + if ( !bFound && !V_stricmp( convar.GetString(), convar.GetDefault() ) ) + { + V_strcpy_safe( szResolvedFilename, szPreferredFilename ); + char *dotTxt = V_stristr( szResolvedFilename, ".txt" ); + Assert ( dotTxt != NULL ); + if ( dotTxt ) V_strcpy( dotTxt, "_default.txt" ); + bFound = filesystem->ReadFile( szResolvedFilename, "GAME", buf ); + } + + if ( !bFound ) + { + Msg( "'%s' not found; not loaded\n", szPreferredFilename ); + return; + } + + if ( buf.TellPut() > 2048 ) + { + Warning("'%s' is too big; not loaded\n", szResolvedFilename ); + return; + } + buf.PutChar( '\0' ); + + if ( V_stricmp( szPreferredFilename, szResolvedFilename ) == 0) + { + Msg( "Set %s from file '%s'\n", pszStringName, szResolvedFilename ); + } + else + { + Msg( "Set %s from file '%s'. ('%s' was not found.)\n", pszStringName, szResolvedFilename, szPreferredFilename ); + } + + g_pStringTableInfoPanel->AddString( CBaseEntity::IsServer(), pszStringName, buf.TellPut(), buf.Base() ); +#endif +} + +// keeps track of which chapters the user has unlocked +ConVar sv_unlockedchapters( "sv_unlockedchapters", "1", FCVAR_ARCHIVE | FCVAR_ARCHIVE_XBOX ); + +//----------------------------------------------------------------------------- +// Purpose: Updates which chapters are unlocked +//----------------------------------------------------------------------------- +void UpdateChapterRestrictions( const char *mapname ) +{ + // look at the chapter for this map + char chapterTitle[64]; + chapterTitle[0] = 0; + for ( int i = 0; i < ARRAYSIZE(gTitleComments); i++ ) + { + if ( !Q_strnicmp( mapname, gTitleComments[i].pBSPName, strlen(gTitleComments[i].pBSPName) ) ) + { + // found + Q_strncpy( chapterTitle, gTitleComments[i].pTitleName, sizeof( chapterTitle ) ); + int j = 0; + while ( j < 64 && chapterTitle[j] ) + { + if ( chapterTitle[j] == '\n' || chapterTitle[j] == '\r' ) + chapterTitle[j] = 0; + else + j++; + } + + break; + } + } + + if ( !chapterTitle[0] ) + return; + + // make sure the specified chapter title is unlocked + strlwr( chapterTitle ); + + // Get our active mod directory name + char modDir[MAX_PATH]; + if ( UTIL_GetModDir( modDir, sizeof(modDir) ) == false ) + return; + + char chapterNumberPrefix[64]; + Q_snprintf(chapterNumberPrefix, sizeof(chapterNumberPrefix), "#%s_chapter", modDir); + + const char *newChapterNumber = strstr( chapterTitle, chapterNumberPrefix ); + if ( newChapterNumber ) + { + // cut off the front + newChapterNumber += strlen( chapterNumberPrefix ); + char newChapter[32]; + Q_strncpy( newChapter, newChapterNumber, sizeof(newChapter) ); + + // cut off the end + char *end = strstr( newChapter, "_title" ); + if ( end ) + { + *end = 0; + } + + int nNewChapter = atoi( newChapter ); + + // HACK: HL2 added a zany chapter "9a" which wreaks + // havoc in this stupid atoi-based chapter code. + if ( !Q_stricmp( modDir, "hl2" ) ) + { + if ( !Q_stricmp( newChapter, "9a" ) ) + { + nNewChapter = 10; + } + else if ( nNewChapter > 9 ) + { + nNewChapter++; + } + } + + // ok we have the string, see if it's newer + const char *unlockedChapter = sv_unlockedchapters.GetString(); + int nUnlockedChapter = atoi( unlockedChapter ); + + if ( nUnlockedChapter < nNewChapter ) + { + // ok we're at a higher chapter, unlock + sv_unlockedchapters.SetValue( nNewChapter ); + + // HACK: Call up through a better function than this? 7/23/07 - jdw + if ( IsX360() ) + { + engine->ServerCommand( "host_writeconfig\n" ); + } + } + + g_nCurrentChapterIndex = nNewChapter; + } +} + +//----------------------------------------------------------------------------- +// Purpose: Update xbox live data for the user's presence +//----------------------------------------------------------------------------- +void UpdateRichPresence ( void ) +{ + // This assumes we're playing a single player game + Assert ( gpGlobals->maxClients == 1 ); + + // Shouldn't get here unless we're playing a map and we've updated sv_unlockedchapters + Assert ( g_nCurrentChapterIndex >= 0 ); + + // Get our active mod directory name + char modDir[MAX_PATH]; + if ( UTIL_GetModDir( modDir, sizeof(modDir) ) == false ) + return; + + // Get presence data based on the game we're playing + uint iGameID, iChapterIndex, iChapterID, iGamePresenceID; + iGameID = iChapterIndex = iChapterID = iGamePresenceID = 0; + if ( Q_stristr( modDir, "hl2" ) ) + { + iGameID = CONTEXT_GAME_GAME_HALF_LIFE_2; + iChapterID = CONTEXT_CHAPTER_HL2; + iChapterIndex = g_nCurrentChapterIndex - 1; + iGamePresenceID = CONTEXT_PRESENCE_HL2_INGAME; + } + else if ( Q_stristr( modDir, "episodic" ) ) + { + iGameID = CONTEXT_GAME_GAME_EPISODE_ONE; + iChapterID = CONTEXT_CHAPTER_EP1; + iChapterIndex = g_nCurrentChapterIndex - 1; + iGamePresenceID = CONTEXT_PRESENCE_EP1_INGAME; + } + else if ( Q_stristr( modDir, "ep2" ) ) + { + iGameID = CONTEXT_GAME_GAME_EPISODE_TWO; + iChapterID = CONTEXT_CHAPTER_EP2; + iChapterIndex = g_nCurrentChapterIndex - 1; + iGamePresenceID = CONTEXT_PRESENCE_EP2_INGAME; + } + else if ( Q_stristr( modDir, "portal" ) ) + { + iGameID = CONTEXT_GAME_GAME_PORTAL; + iChapterID = CONTEXT_CHAPTER_PORTAL; + iChapterIndex = g_nCurrentChapterIndex - 1; + iGamePresenceID = CONTEXT_PRESENCE_PORTAL_INGAME; + } + else + { + Warning( "UpdateRichPresence failed in GameInterface. Didn't recognize -game parameter." ); + } + +#if defined( _X360 ) + + // Set chapter context based on mapname + if ( !xboxsystem->UserSetContext( XBX_GetPrimaryUserId(), iChapterID, iChapterIndex, true ) ) + { + Warning( "GameInterface: UserSetContext failed.\n" ); + } + + if ( commentary.GetBool() ) + { + // Set presence to show the user is playing developer commentary + if ( !xboxsystem->UserSetContext( XBX_GetPrimaryUserId(), X_CONTEXT_PRESENCE, CONTEXT_PRESENCE_COMMENTARY, true ) ) + { + Warning( "GameInterface: UserSetContext failed.\n" ); + } + } + else + { + // Set presence to show the user is in-game + if ( !xboxsystem->UserSetContext( XBX_GetPrimaryUserId(), X_CONTEXT_PRESENCE, iGamePresenceID, true ) ) + { + Warning( "GameInterface: UserSetContext failed.\n" ); + } + } + + // Set which game the user is playing + if ( !xboxsystem->UserSetContext( XBX_GetPrimaryUserId(), CONTEXT_GAME, iGameID, true ) ) + { + Warning( "GameInterface: UserSetContext failed.\n" ); + } + + if ( !xboxsystem->UserSetContext( XBX_GetPrimaryUserId(), X_CONTEXT_GAME_TYPE, X_CONTEXT_GAME_TYPE_STANDARD, true ) ) + { + Warning( "GameInterface: UserSetContext failed.\n" ); + } + + if ( !xboxsystem->UserSetContext( XBX_GetPrimaryUserId(), X_CONTEXT_GAME_MODE, CONTEXT_GAME_MODE_SINGLEPLAYER, true ) ) + { + Warning( "GameInterface: UserSetContext failed.\n" ); + } +#endif +} + +//----------------------------------------------------------------------------- +// Precaches a vgui screen overlay material +//----------------------------------------------------------------------------- +void PrecacheMaterial( const char *pMaterialName ) +{ + Assert( pMaterialName && pMaterialName[0] ); + g_pStringTableMaterials->AddString( CBaseEntity::IsServer(), pMaterialName ); +} + + +//----------------------------------------------------------------------------- +// Converts a previously precached material into an index +//----------------------------------------------------------------------------- +int GetMaterialIndex( const char *pMaterialName ) +{ + if (pMaterialName) + { + int nIndex = g_pStringTableMaterials->FindStringIndex( pMaterialName ); + + if (nIndex != INVALID_STRING_INDEX ) + { + return nIndex; + } + else + { + DevMsg("Warning! GetMaterialIndex: couldn't find material %s\n ", pMaterialName ); + return 0; + } + } + + // This is the invalid string index + return 0; +} + +//----------------------------------------------------------------------------- +// Converts a previously precached material index into a string +//----------------------------------------------------------------------------- +const char *GetMaterialNameFromIndex( int nMaterialIndex ) +{ + return g_pStringTableMaterials->GetString( nMaterialIndex ); +} + + +//----------------------------------------------------------------------------- +// Precaches a vgui screen overlay material +//----------------------------------------------------------------------------- +void PrecacheParticleSystem( const char *pParticleSystemName ) +{ + Assert( pParticleSystemName && pParticleSystemName[0] ); + g_pStringTableParticleEffectNames->AddString( CBaseEntity::IsServer(), pParticleSystemName ); +} + + +//----------------------------------------------------------------------------- +// Converts a previously precached material into an index +//----------------------------------------------------------------------------- +int GetParticleSystemIndex( const char *pParticleSystemName ) +{ + if ( pParticleSystemName ) + { + int nIndex = g_pStringTableParticleEffectNames->FindStringIndex( pParticleSystemName ); + if (nIndex != INVALID_STRING_INDEX ) + return nIndex; + + DevWarning("Server: Missing precache for particle system \"%s\"!\n", pParticleSystemName ); + } + + // This is the invalid string index + return 0; +} + +//----------------------------------------------------------------------------- +// Converts a previously precached material index into a string +//----------------------------------------------------------------------------- +const char *GetParticleSystemNameFromIndex( int nMaterialIndex ) +{ + if ( nMaterialIndex < g_pStringTableParticleEffectNames->GetMaxStrings() ) + return g_pStringTableParticleEffectNames->GetString( nMaterialIndex ); + return "error"; +} + +//----------------------------------------------------------------------------- +// Returns true if host_thread_mode is set to non-zero (and engine is running in threaded mode) +//----------------------------------------------------------------------------- +bool IsEngineThreaded() +{ + if ( g_pcv_ThreadMode ) + { + return g_pcv_ThreadMode->GetBool(); + } + return false; +} + +class CServerGameEnts : public IServerGameEnts +{ +public: + virtual void SetDebugEdictBase(edict_t *base); + virtual void MarkEntitiesAsTouching( edict_t *e1, edict_t *e2 ); + virtual void FreeContainingEntity( edict_t * ); + virtual edict_t* BaseEntityToEdict( CBaseEntity *pEnt ); + virtual CBaseEntity* EdictToBaseEntity( edict_t *pEdict ); + virtual void CheckTransmit( CCheckTransmitInfo *pInfo, const unsigned short *pEdictIndices, int nEdicts ); +}; +EXPOSE_SINGLE_INTERFACE(CServerGameEnts, IServerGameEnts, INTERFACEVERSION_SERVERGAMEENTS); + +void CServerGameEnts::SetDebugEdictBase(edict_t *base) +{ + g_pDebugEdictBase = base; +} + +//----------------------------------------------------------------------------- +// Purpose: Marks entities as touching +// Input : *e1 - +// *e2 - +//----------------------------------------------------------------------------- +void CServerGameEnts::MarkEntitiesAsTouching( edict_t *e1, edict_t *e2 ) +{ + CBaseEntity *entity = GetContainingEntity( e1 ); + CBaseEntity *entityTouched = GetContainingEntity( e2 ); + if ( entity && entityTouched ) + { + // HACKHACK: UNDONE: Pass in the trace here??!?!? + trace_t tr; + UTIL_ClearTrace( tr ); + tr.endpos = (entity->GetAbsOrigin() + entityTouched->GetAbsOrigin()) * 0.5; + entity->PhysicsMarkEntitiesAsTouching( entityTouched, tr ); + } +} + +void CServerGameEnts::FreeContainingEntity( edict_t *e ) +{ + ::FreeContainingEntity(e); +} + +edict_t* CServerGameEnts::BaseEntityToEdict( CBaseEntity *pEnt ) +{ + if ( pEnt ) + return pEnt->edict(); + else + return NULL; +} + +CBaseEntity* CServerGameEnts::EdictToBaseEntity( edict_t *pEdict ) +{ + if ( pEdict ) + return CBaseEntity::Instance( pEdict ); + else + return NULL; +} + + +/* Yuck.. ideally this would be in CServerNetworkProperty's header, but it requires CBaseEntity and +// inlining it gives a nice speedup. +inline void CServerNetworkProperty::CheckTransmit( CCheckTransmitInfo *pInfo ) +{ + // If we have a transmit proxy, let it hook our ShouldTransmit return value. + if ( m_pTransmitProxy ) + { + nShouldTransmit = m_pTransmitProxy->ShouldTransmit( pInfo, nShouldTransmit ); + } + + if ( m_pOuter->ShouldTransmit( pInfo ) ) + { + m_pOuter->SetTransmit( pInfo ); + } +} */ + +void CServerGameEnts::CheckTransmit( CCheckTransmitInfo *pInfo, const unsigned short *pEdictIndices, int nEdicts ) +{ + // NOTE: for speed's sake, this assumes that all networkables are CBaseEntities and that the edict list + // is consecutive in memory. If either of these things change, then this routine needs to change, but + // ideally we won't be calling any virtual from this routine. This speedy routine was added as an + // optimization which would be nice to keep. + edict_t *pBaseEdict = engine->PEntityOfEntIndex( 0 ); + + // get recipient player's skybox: + CBaseEntity *pRecipientEntity = CBaseEntity::Instance( pInfo->m_pClientEnt ); + + Assert( pRecipientEntity && pRecipientEntity->IsPlayer() ); + if ( !pRecipientEntity ) + return; + + MDLCACHE_CRITICAL_SECTION(); + CBasePlayer *pRecipientPlayer = static_cast( pRecipientEntity ); + const int skyBoxArea = pRecipientPlayer->m_Local.m_skybox3d.area; + +#ifndef _X360 + const bool bIsHLTV = pRecipientPlayer->IsHLTV(); + const bool bIsReplay = pRecipientPlayer->IsReplay(); + + // m_pTransmitAlways must be set if HLTV client + Assert( bIsHLTV == ( pInfo->m_pTransmitAlways != NULL) || + bIsReplay == ( pInfo->m_pTransmitAlways != NULL) ); +#endif + + for ( int i=0; i < nEdicts; i++ ) + { + int iEdict = pEdictIndices[i]; + + edict_t *pEdict = &pBaseEdict[iEdict]; + Assert( pEdict == engine->PEntityOfEntIndex( iEdict ) ); + int nFlags = pEdict->m_fStateFlags & (FL_EDICT_DONTSEND|FL_EDICT_ALWAYS|FL_EDICT_PVSCHECK|FL_EDICT_FULLCHECK); + + // entity needs no transmit + if ( nFlags & FL_EDICT_DONTSEND ) + continue; + + // entity is already marked for sending + if ( pInfo->m_pTransmitEdict->Get( iEdict ) ) + continue; + + if ( nFlags & FL_EDICT_ALWAYS ) + { + // FIXME: Hey! Shouldn't this be using SetTransmit so as + // to also force network down dependent entities? + while ( true ) + { + // mark entity for sending + pInfo->m_pTransmitEdict->Set( iEdict ); + +#ifndef _X360 + if ( bIsHLTV || bIsReplay ) + { + pInfo->m_pTransmitAlways->Set( iEdict ); + } +#endif + CServerNetworkProperty *pEnt = static_cast( pEdict->GetNetworkable() ); + if ( !pEnt ) + break; + + CServerNetworkProperty *pParent = pEnt->GetNetworkParent(); + if ( !pParent ) + break; + + pEdict = pParent->edict(); + iEdict = pParent->entindex(); + } + continue; + } + + // FIXME: Would like to remove all dependencies + CBaseEntity *pEnt = ( CBaseEntity * )pEdict->GetUnknown(); + Assert( dynamic_cast< CBaseEntity* >( pEdict->GetUnknown() ) == pEnt ); + + if ( nFlags == FL_EDICT_FULLCHECK ) + { + // do a full ShouldTransmit() check, may return FL_EDICT_CHECKPVS + nFlags = pEnt->ShouldTransmit( pInfo ); + + Assert( !(nFlags & FL_EDICT_FULLCHECK) ); + + if ( nFlags & FL_EDICT_ALWAYS ) + { + pEnt->SetTransmit( pInfo, true ); + continue; + } + } + + // don't send this entity + if ( !( nFlags & FL_EDICT_PVSCHECK ) ) + continue; + + CServerNetworkProperty *netProp = static_cast( pEdict->GetNetworkable() ); + +#ifndef _X360 + if ( bIsHLTV || bIsReplay ) + { + // for the HLTV/Replay we don't cull against PVS + if ( netProp->AreaNum() == skyBoxArea ) + { + pEnt->SetTransmit( pInfo, true ); + } + else + { + pEnt->SetTransmit( pInfo, false ); + } + continue; + } +#endif + + // Always send entities in the player's 3d skybox. + // Sidenote: call of AreaNum() ensures that PVS data is up to date for this entity + bool bSameAreaAsSky = netProp->AreaNum() == skyBoxArea; + if ( bSameAreaAsSky ) + { + pEnt->SetTransmit( pInfo, true ); + continue; + } + + bool bInPVS = netProp->IsInPVS( pInfo ); + if ( bInPVS || sv_force_transmit_ents.GetBool() ) + { + // only send if entity is in PVS + pEnt->SetTransmit( pInfo, false ); + continue; + } + + // If the entity is marked "check PVS" but it's in hierarchy, walk up the hierarchy looking for the + // for any parent which is also in the PVS. If none are found, then we don't need to worry about sending ourself + CBaseEntity *orig = pEnt; + CServerNetworkProperty *check = netProp->GetNetworkParent(); + + // BUG BUG: I think it might be better to build up a list of edict indices which "depend" on other answers and then + // resolve them in a second pass. Not sure what happens if an entity has two parents who both request PVS check? + while ( check ) + { + int checkIndex = check->entindex(); + + // Parent already being sent + if ( pInfo->m_pTransmitEdict->Get( checkIndex ) ) + { + orig->SetTransmit( pInfo, true ); + break; + } + + edict_t *checkEdict = check->edict(); + int checkFlags = checkEdict->m_fStateFlags & (FL_EDICT_DONTSEND|FL_EDICT_ALWAYS|FL_EDICT_PVSCHECK|FL_EDICT_FULLCHECK); + if ( checkFlags & FL_EDICT_DONTSEND ) + break; + + if ( checkFlags & FL_EDICT_ALWAYS ) + { + orig->SetTransmit( pInfo, true ); + break; + } + + if ( checkFlags == FL_EDICT_FULLCHECK ) + { + // do a full ShouldTransmit() check, may return FL_EDICT_CHECKPVS + CBaseEntity *pCheckEntity = check->GetBaseEntity(); + nFlags = pCheckEntity->ShouldTransmit( pInfo ); + Assert( !(nFlags & FL_EDICT_FULLCHECK) ); + if ( nFlags & FL_EDICT_ALWAYS ) + { + pCheckEntity->SetTransmit( pInfo, true ); + orig->SetTransmit( pInfo, true ); + } + break; + } + + if ( checkFlags & FL_EDICT_PVSCHECK ) + { + // Check pvs + check->RecomputePVSInformation(); + bool bMoveParentInPVS = check->IsInPVS( pInfo ); + if ( bMoveParentInPVS ) + { + orig->SetTransmit( pInfo, true ); + break; + } + } + + // Continue up chain just in case the parent itself has a parent that's in the PVS... + check = check->GetNetworkParent(); + } + } + +// Msg("A:%i, N:%i, F: %i, P: %i\n", always, dontSend, fullCheck, PVS ); +} + + +CServerGameClients g_ServerGameClients; +// INTERFACEVERSION_SERVERGAMECLIENTS_VERSION_3 is compatible with the latest since we're only adding things to the end, so expose that as well. +EXPOSE_SINGLE_INTERFACE_GLOBALVAR(CServerGameClients, IServerGameClients003, INTERFACEVERSION_SERVERGAMECLIENTS_VERSION_3, g_ServerGameClients ); +EXPOSE_SINGLE_INTERFACE_GLOBALVAR(CServerGameClients, IServerGameClients, INTERFACEVERSION_SERVERGAMECLIENTS, g_ServerGameClients ); + + +//----------------------------------------------------------------------------- +// Purpose: called when a player tries to connect to the server +// Input : *pEdict - the new player +// char *pszName - the players name +// char *pszAddress - the IP address of the player +// reject - output - fill in with the reason why +// maxrejectlen -- sizeof output buffer +// the player was not allowed to connect. +// Output : Returns TRUE if player is allowed to join, FALSE if connection is denied. +//----------------------------------------------------------------------------- +bool CServerGameClients::ClientConnect( edict_t *pEdict, const char *pszName, const char *pszAddress, char *reject, int maxrejectlen ) +{ + if ( !g_pGameRules ) + return false; + + return g_pGameRules->ClientConnected( pEdict, pszName, pszAddress, reject, maxrejectlen ); +} + +//----------------------------------------------------------------------------- +// Purpose: Called when a player is fully active (i.e. ready to receive messages) +// Input : *pEntity - the player +//----------------------------------------------------------------------------- +void CServerGameClients::ClientActive( edict_t *pEdict, bool bLoadGame ) +{ + MDLCACHE_CRITICAL_SECTION(); + + ::ClientActive( pEdict, bLoadGame ); + + // If we just loaded from a save file, call OnRestore on valid entities + EndRestoreEntities(); + + if ( gpGlobals->eLoadType != MapLoad_LoadGame ) + { + // notify all entities that the player is now in the game + for ( CBaseEntity *pEntity = gEntList.FirstEnt(); pEntity != NULL; pEntity = gEntList.NextEnt(pEntity) ) + { + pEntity->PostClientActive(); + } + } + + // Tell the sound controller to check looping sounds + CBasePlayer *pPlayer = ( CBasePlayer * )CBaseEntity::Instance( pEdict ); + CSoundEnvelopeController::GetController().CheckLoopingSoundsForPlayer( pPlayer ); + SceneManager_ClientActive( pPlayer ); + + #if defined( TF_DLL ) + Assert( pPlayer ); + if ( pPlayer && !pPlayer->IsFakeClient() ) + { + CSteamID steamID; + if ( pPlayer->GetSteamID( &steamID ) ) + { + GTFGCClientSystem()->ClientActive( steamID ); + } + else + { + Log("WARNING: ClientActive, but we don't know his SteamID?\n"); + } + } + #endif +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *pPlayer - the player +//----------------------------------------------------------------------------- +void CServerGameClients::ClientSpawned( edict_t *pPlayer ) +{ + if ( g_pGameRules ) + { + g_pGameRules->ClientSpawned( pPlayer ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: called when a player disconnects from a server +// Input : *pEdict - the player +//----------------------------------------------------------------------------- +void CServerGameClients::ClientDisconnect( edict_t *pEdict ) +{ + extern bool g_fGameOver; + + CBasePlayer *player = ( CBasePlayer * )CBaseEntity::Instance( pEdict ); + if ( player ) + { + if ( !g_fGameOver ) + { + player->SetMaxSpeed( 0.0f ); + + CSound *pSound; + pSound = CSoundEnt::SoundPointerForIndex( CSoundEnt::ClientSoundIndex( pEdict ) ); + { + // since this client isn't around to think anymore, reset their sound. + if ( pSound ) + { + pSound->Reset(); + } + } + + // since the edict doesn't get deleted, fix it so it doesn't interfere. + player->RemoveFlag( FL_AIMTARGET ); // don't attract autoaim + player->AddFlag( FL_DONTTOUCH ); // stop it touching anything + player->AddFlag( FL_NOTARGET ); // stop NPCs noticing it + player->AddSolidFlags( FSOLID_NOT_SOLID ); // nonsolid + + if ( g_pGameRules ) + { + g_pGameRules->ClientDisconnected( pEdict ); + gamestats->Event_PlayerDisconnected( player ); + } + } + + // Make sure all Untouch()'s are called for this client leaving + CBaseEntity::PhysicsRemoveTouchedList( player ); + CBaseEntity::PhysicsRemoveGroundList( player ); + +#if !defined( NO_ENTITY_PREDICTION ) + // Make sure anything we "own" is simulated by the server from now on + player->ClearPlayerSimulationList(); +#endif + #if defined( TF_DLL ) + if ( !player->IsFakeClient() ) + { + CSteamID steamID; + if ( player->GetSteamID( &steamID ) ) + { + GTFGCClientSystem()->ClientDisconnected( steamID ); + } + else + { + Log("WARNING: ClientDisconnected, but we don't know his SteamID?\n"); + } + } + #endif + } +} + +void CServerGameClients::ClientPutInServer( edict_t *pEntity, const char *playername ) +{ + if ( g_pClientPutInServerOverride ) + g_pClientPutInServerOverride( pEntity, playername ); + else + ::ClientPutInServer( pEntity, playername ); +} + +void CServerGameClients::ClientCommand( edict_t *pEntity, const CCommand &args ) +{ + CBasePlayer *pPlayer = ToBasePlayer( GetContainingEntity( pEntity ) ); + ::ClientCommand( pPlayer, args ); +} + +//----------------------------------------------------------------------------- +// Purpose: called after the player changes userinfo - gives dll a chance to modify +// it before it gets sent into the rest of the engine-> +// Input : *pEdict - the player +// *infobuffer - their infobuffer +//----------------------------------------------------------------------------- +void CServerGameClients::ClientSettingsChanged( edict_t *pEdict ) +{ + // Is the client spawned yet? + if ( !pEdict->GetUnknown() ) + return; + + CBasePlayer *player = ( CBasePlayer * )CBaseEntity::Instance( pEdict ); + + if ( !player ) + return; + + bool bAllowNetworkingClientSettingsChange = g_pGameRules->IsConnectedUserInfoChangeAllowed( player ); + if ( bAllowNetworkingClientSettingsChange ) + { + +#define QUICKGETCVARVALUE(v) (engine->GetClientConVarValue( player->entindex(), v )) + + // get network setting for prediction & lag compensation + + // Unfortunately, we have to duplicate the code in cdll_bounded_cvars.cpp here because the client + // doesn't send the virtualized value up (because it has no way to know when the virtualized value + // changes). Possible todo: put the responsibility on the bounded cvar to notify the engine when + // its virtualized value has changed. + + player->m_nUpdateRate = Q_atoi( QUICKGETCVARVALUE("cl_updaterate") ); + static const ConVar *pMinUpdateRate = g_pCVar->FindVar( "sv_minupdaterate" ); + static const ConVar *pMaxUpdateRate = g_pCVar->FindVar( "sv_maxupdaterate" ); + if ( pMinUpdateRate && pMaxUpdateRate ) + player->m_nUpdateRate = clamp( player->m_nUpdateRate, (int) pMinUpdateRate->GetFloat(), (int) pMaxUpdateRate->GetFloat() ); + + bool useInterpolation = Q_atoi( QUICKGETCVARVALUE("cl_interpolate") ) != 0; + if ( useInterpolation ) + { + float flLerpRatio = Q_atof( QUICKGETCVARVALUE("cl_interp_ratio") ); + if ( flLerpRatio == 0 ) + flLerpRatio = 1.0f; + float flLerpAmount = Q_atof( QUICKGETCVARVALUE("cl_interp") ); + + static const ConVar *pMin = g_pCVar->FindVar( "sv_client_min_interp_ratio" ); + static const ConVar *pMax = g_pCVar->FindVar( "sv_client_max_interp_ratio" ); + if ( pMin && pMax && pMin->GetFloat() != -1 ) + { + flLerpRatio = clamp( flLerpRatio, pMin->GetFloat(), pMax->GetFloat() ); + } + else + { + if ( flLerpRatio == 0 ) + flLerpRatio = 1.0f; + } + // #define FIXME_INTERP_RATIO + player->m_fLerpTime = MAX( flLerpAmount, flLerpRatio / player->m_nUpdateRate ); + } + else + { + player->m_fLerpTime = 0.0f; + } + +#if !defined( NO_ENTITY_PREDICTION ) + bool usePrediction = Q_atoi( QUICKGETCVARVALUE("cl_predict")) != 0; + + if ( usePrediction ) + { + player->m_bPredictWeapons = Q_atoi( QUICKGETCVARVALUE("cl_predictweapons")) != 0; + player->m_bLagCompensation = Q_atoi( QUICKGETCVARVALUE("cl_lagcompensation")) != 0; + } + else +#endif + { + player->m_bPredictWeapons = false; + player->m_bLagCompensation = false; + } + + +#undef QUICKGETCVARVALUE + } + + g_pGameRules->ClientSettingsChanged( player ); +} + + +#ifdef PORTAL +//----------------------------------------------------------------------------- +// Purpose: Runs CFuncAreaPortalBase::UpdateVisibility on each portal +// Input : pAreaPortal - The Area portal to test for visibility from portals +// Output : int - 1 if any portal needs this area portal open, 0 otherwise. +//----------------------------------------------------------------------------- +int TestAreaPortalVisibilityThroughPortals ( CFuncAreaPortalBase* pAreaPortal, edict_t *pViewEntity, unsigned char *pvs, int pvssize ) +{ + int iPortalCount = CProp_Portal_Shared::AllPortals.Count(); + if( iPortalCount == 0 ) + return 0; + + CProp_Portal **pPortals = CProp_Portal_Shared::AllPortals.Base(); + + for ( int i = 0; i != iPortalCount; ++i ) + { + CProp_Portal* pLocalPortal = pPortals[ i ]; + if ( pLocalPortal && pLocalPortal->m_bActivated ) + { + CProp_Portal* pRemotePortal = pLocalPortal->m_hLinkedPortal.Get(); + + // Make sure this portal's linked portal is in the PVS before we add what it can see + if ( pRemotePortal && pRemotePortal->m_bActivated && pRemotePortal->NetworkProp() && + pRemotePortal->NetworkProp()->IsInPVS( pViewEntity, pvs, pvssize ) ) + { + bool bIsOpenOnClient = true; + float fovDistanceAdjustFactor = 1.0f; + Vector portalOrg = pLocalPortal->GetAbsOrigin(); + int iPortalNeedsThisPortalOpen = pAreaPortal->UpdateVisibility( portalOrg, fovDistanceAdjustFactor, bIsOpenOnClient ); + + // Stop checking on success, this portal needs to be open + if ( iPortalNeedsThisPortalOpen ) + { + return iPortalNeedsThisPortalOpen; + } + } + } + } + + return 0; +} +#endif + +//----------------------------------------------------------------------------- +// Purpose: A client can have a separate "view entity" indicating that his/her view should depend on the origin of that +// view entity. If that's the case, then pViewEntity will be non-NULL and will be used. Otherwise, the current +// entity's origin is used. Either is offset by the m_vecViewOffset to get the eye position. +// From the eye position, we set up the PAS and PVS to use for filtering network messages to the client. At this point, we could +// override the actual PAS or PVS values, or use a different origin. +// NOTE: Do not cache the values of pas and pvs, as they depend on reusable memory in the engine, they are only good for this one frame +// Input : *pViewEntity - +// *pClient - +// **pvs - +// **pas - +//----------------------------------------------------------------------------- +void CServerGameClients::ClientSetupVisibility( edict_t *pViewEntity, edict_t *pClient, unsigned char *pvs, int pvssize ) +{ + Vector org; + + // Reset the PVS!!! + engine->ResetPVS( pvs, pvssize ); + + g_pToolFrameworkServer->PreSetupVisibility(); + + // Find the client's PVS + CBaseEntity *pVE = NULL; + if ( pViewEntity ) + { + pVE = GetContainingEntity( pViewEntity ); + // If we have a viewentity, it overrides the player's origin + if ( pVE ) + { + org = pVE->EyePosition(); + engine->AddOriginToPVS( org ); + } + } + + float fovDistanceAdjustFactor = 1; + + CBasePlayer *pPlayer = ( CBasePlayer * )GetContainingEntity( pClient ); + if ( pPlayer ) + { + org = pPlayer->EyePosition(); + pPlayer->SetupVisibility( pVE, pvs, pvssize ); + UTIL_SetClientVisibilityPVS( pClient, pvs, pvssize ); + fovDistanceAdjustFactor = pPlayer->GetFOVDistanceAdjustFactorForNetworking(); + } + + unsigned char portalBits[MAX_AREA_PORTAL_STATE_BYTES]; + memset( portalBits, 0, sizeof( portalBits ) ); + + int portalNums[512]; + int isOpen[512]; + int iOutPortal = 0; + + for( unsigned short i = g_AreaPortals.Head(); i != g_AreaPortals.InvalidIndex(); i = g_AreaPortals.Next(i) ) + { + CFuncAreaPortalBase *pCur = g_AreaPortals[i]; + + bool bIsOpenOnClient = true; + + // Update our array of which portals are open and flush it if necessary. + portalNums[iOutPortal] = pCur->m_portalNumber; + isOpen[iOutPortal] = pCur->UpdateVisibility( org, fovDistanceAdjustFactor, bIsOpenOnClient ); + +#ifdef PORTAL + // If the client doesn't need this open, test if portals might need this area portal open + if ( isOpen[iOutPortal] == 0 ) + { + isOpen[iOutPortal] = TestAreaPortalVisibilityThroughPortals( pCur, pViewEntity, pvs, pvssize ); + } +#endif + + ++iOutPortal; + if ( iOutPortal >= ARRAYSIZE( portalNums ) ) + { + engine->SetAreaPortalStates( portalNums, isOpen, iOutPortal ); + iOutPortal = 0; + } + + // Version 0 portals (ie: shipping Half-Life 2 era) are always treated as open + // for purposes of the m_chAreaPortalBits array on the client. + if ( pCur->m_iPortalVersion == 0 ) + bIsOpenOnClient = true; + + if ( bIsOpenOnClient ) + { + if ( pCur->m_portalNumber < 0 ) + continue; + else if ( pCur->m_portalNumber >= sizeof( portalBits ) * 8 ) + Error( "ClientSetupVisibility: portal number (%d) too large", pCur->m_portalNumber ); + else + portalBits[pCur->m_portalNumber >> 3] |= (1 << (pCur->m_portalNumber & 7)); + } + } + + // Flush the remaining areaportal states. + engine->SetAreaPortalStates( portalNums, isOpen, iOutPortal ); + + // Update the area bits that get sent to the client. + pPlayer->m_Local.UpdateAreaBits( pPlayer, portalBits ); + +#ifdef PORTAL + // *After* the player's view has updated its area bits, add on any other areas seen by portals + CPortal_Player* pPortalPlayer = dynamic_cast( pPlayer ); + if ( pPortalPlayer ) + { + pPortalPlayer->UpdatePortalViewAreaBits( pvs, pvssize ); + } +#endif //PORTAL +} + + + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *player - +// *buf - +// numcmds - +// totalcmds - +// dropped_packets - +// ignore - +// paused - +// Output : float +//----------------------------------------------------------------------------- +#define CMD_MAXBACKUP 64 + +float CServerGameClients::ProcessUsercmds( edict_t *player, bf_read *buf, int numcmds, int totalcmds, + int dropped_packets, bool ignore, bool paused ) +{ + int i; + CUserCmd *from, *to; + + // We track last three command in case we drop some + // packets but get them back. + CUserCmd cmds[ CMD_MAXBACKUP ]; + + CUserCmd cmdNull; // For delta compression + + Assert( numcmds >= 0 ); + Assert( ( totalcmds - numcmds ) >= 0 ); + + CBasePlayer *pPlayer = NULL; + CBaseEntity *pEnt = CBaseEntity::Instance(player); + if ( pEnt && pEnt->IsPlayer() ) + { + pPlayer = static_cast< CBasePlayer * >( pEnt ); + } + // Too many commands? + if ( totalcmds < 0 || totalcmds >= ( CMD_MAXBACKUP - 1 ) ) + { + const char *name = "unknown"; + if ( pPlayer ) + { + name = pPlayer->GetPlayerName(); + } + + Msg("CBasePlayer::ProcessUsercmds: too many cmds %i sent for player %s\n", totalcmds, name ); + // FIXME: Need a way to drop the client from here + //SV_DropClient ( host_client, false, "CMD_MAXBACKUP hit" ); + buf->SetOverflowFlag(); + return 0.0f; + } + + // Initialize for reading delta compressed usercmds + cmdNull.Reset(); + from = &cmdNull; + for ( i = totalcmds - 1; i >= 0; i-- ) + { + to = &cmds[ i ]; + ReadUsercmd( buf, to, from ); + from = to; + } + + // Client not fully connected or server has gone inactive or is paused, just ignore + if ( ignore || !pPlayer ) + { + return 0.0f; + } + + MDLCACHE_CRITICAL_SECTION(); + pPlayer->ProcessUsercmds( cmds, numcmds, totalcmds, dropped_packets, paused ); + + return TICK_INTERVAL; +} + + +void CServerGameClients::PostClientMessagesSent_DEPRECIATED( void ) +{ +} + +// Sets the client index for the client who typed the command into his/her console +void CServerGameClients::SetCommandClient( int index ) +{ + g_nCommandClientIndex = index; +} + +int CServerGameClients::GetReplayDelay( edict_t *pEdict, int &entity ) +{ + CBasePlayer *pPlayer = ( CBasePlayer * )CBaseEntity::Instance( pEdict ); + + if ( !pPlayer ) + return 0; + + entity = pPlayer->GetReplayEntity(); + + return pPlayer->GetDelayTicks(); +} + + +//----------------------------------------------------------------------------- +// The client's userinfo data lump has changed +//----------------------------------------------------------------------------- +void CServerGameClients::ClientEarPosition( edict_t *pEdict, Vector *pEarOrigin ) +{ + CBasePlayer *pPlayer = ( CBasePlayer * )CBaseEntity::Instance( pEdict ); + if (pPlayer) + { + *pEarOrigin = pPlayer->EarPosition(); + } + else + { + // Shouldn't happen + Assert(0); + *pEarOrigin = vec3_origin; + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *player - +// Output : CPlayerState +//----------------------------------------------------------------------------- +CPlayerState *CServerGameClients::GetPlayerState( edict_t *player ) +{ + // Is the client spawned yet? + if ( !player || !player->GetUnknown() ) + return NULL; + + CBasePlayer *pBasePlayer = ( CBasePlayer * )CBaseEntity::Instance( player ); + if ( !pBasePlayer ) + return NULL; + + return &pBasePlayer->pl; +} + +//----------------------------------------------------------------------------- +// Purpose: Anything this game .dll wants to add to the bug reporter text (e.g., the entity/model under the picker crosshair) +// can be added here +// Input : *buf - +// buflen - +//----------------------------------------------------------------------------- +void CServerGameClients::GetBugReportInfo( char *buf, int buflen ) +{ + recentNPCSpeech_t speech[ SPEECH_LIST_MAX_SOUNDS ]; + int num; + int i; + + buf[ 0 ] = 0; + + if ( gpGlobals->maxClients == 1 ) + { + CBaseEntity *ent = FindPickerEntity( UTIL_PlayerByIndex(1) ); + if ( ent ) + { + Q_snprintf( buf, buflen, "Picker %i/%s - ent %s model %s\n", + ent->entindex(), + ent->GetClassname(), + STRING( ent->GetEntityName() ), + STRING( ent->GetModelName() ) ); + } + + // get any sounds that were spoken by NPCs recently + num = GetRecentNPCSpeech( speech ); + if ( num > 0 ) + { + Q_snprintf( buf, buflen, "%sRecent NPC speech:\n", buf ); + for( i = 0; i < num; i++ ) + { + Q_snprintf( buf, buflen, "%s time: %6.3f sound name: %s scene: %s\n", buf, speech[ i ].time, speech[ i ].name, speech[ i ].sceneName ); + } + Q_snprintf( buf, buflen, "%sCurrent time: %6.3f\n", buf, gpGlobals->curtime ); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: A user has had their network id setup and validated +//----------------------------------------------------------------------------- +void CServerGameClients::NetworkIDValidated( const char *pszUserName, const char *pszNetworkID ) +{ +} + +// The client has submitted a keyvalues command +void CServerGameClients::ClientCommandKeyValues( edict_t *pEntity, KeyValues *pKeyValues ) +{ + if ( !pKeyValues ) + return; + + if ( g_pGameRules ) + { + g_pGameRules->ClientCommandKeyValues( pEntity, pKeyValues ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +static bf_write *g_pMsgBuffer = NULL; + +void EntityMessageBegin( CBaseEntity * entity, bool reliable /*= false*/ ) +{ + Assert( !g_pMsgBuffer ); + + Assert ( entity ); + + g_pMsgBuffer = engine->EntityMessageBegin( entity->entindex(), entity->GetServerClass(), reliable ); +} + +void UserMessageBegin( IRecipientFilter& filter, const char *messagename ) +{ + Assert( !g_pMsgBuffer ); + + Assert( messagename ); + + int msg_type = usermessages->LookupUserMessage( messagename ); + + if ( msg_type == -1 ) + { + Error( "UserMessageBegin: Unregistered message '%s'\n", messagename ); + } + + g_pMsgBuffer = engine->UserMessageBegin( &filter, msg_type ); +} + +void MessageEnd( void ) +{ + Assert( g_pMsgBuffer ); + + engine->MessageEnd(); + + g_pMsgBuffer = NULL; +} + +void MessageWriteByte( int iValue) +{ + if (!g_pMsgBuffer) + Error( "WRITE_BYTE called with no active message\n" ); + + g_pMsgBuffer->WriteByte( iValue ); +} + +void MessageWriteChar( int iValue) +{ + if (!g_pMsgBuffer) + Error( "WRITE_CHAR called with no active message\n" ); + + g_pMsgBuffer->WriteChar( iValue ); +} + +void MessageWriteShort( int iValue) +{ + if (!g_pMsgBuffer) + Error( "WRITE_SHORT called with no active message\n" ); + + g_pMsgBuffer->WriteShort( iValue ); +} + +void MessageWriteWord( int iValue ) +{ + if (!g_pMsgBuffer) + Error( "WRITE_WORD called with no active message\n" ); + + g_pMsgBuffer->WriteWord( iValue ); +} + +void MessageWriteLong( int iValue) +{ + if (!g_pMsgBuffer) + Error( "WriteLong called with no active message\n" ); + + g_pMsgBuffer->WriteLong( iValue ); +} + +void MessageWriteFloat( float flValue) +{ + if (!g_pMsgBuffer) + Error( "WriteFloat called with no active message\n" ); + + g_pMsgBuffer->WriteFloat( flValue ); +} + +void MessageWriteAngle( float flValue) +{ + if (!g_pMsgBuffer) + Error( "WriteAngle called with no active message\n" ); + + g_pMsgBuffer->WriteBitAngle( flValue, 8 ); +} + +void MessageWriteCoord( float flValue) +{ + if (!g_pMsgBuffer) + Error( "WriteCoord called with no active message\n" ); + + g_pMsgBuffer->WriteBitCoord( flValue ); +} + +void MessageWriteVec3Coord( const Vector& rgflValue) +{ + if (!g_pMsgBuffer) + Error( "WriteVec3Coord called with no active message\n" ); + + g_pMsgBuffer->WriteBitVec3Coord( rgflValue ); +} + +void MessageWriteVec3Normal( const Vector& rgflValue) +{ + if (!g_pMsgBuffer) + Error( "WriteVec3Normal called with no active message\n" ); + + g_pMsgBuffer->WriteBitVec3Normal( rgflValue ); +} + +void MessageWriteAngles( const QAngle& rgflValue) +{ + if (!g_pMsgBuffer) + Error( "WriteVec3Normal called with no active message\n" ); + + g_pMsgBuffer->WriteBitAngles( rgflValue ); +} + +void MessageWriteString( const char *sz ) +{ + if (!g_pMsgBuffer) + Error( "WriteString called with no active message\n" ); + + g_pMsgBuffer->WriteString( sz ); +} + +void MessageWriteEntity( int iValue) +{ + if (!g_pMsgBuffer) + Error( "WriteEntity called with no active message\n" ); + + g_pMsgBuffer->WriteShort( iValue ); +} + +void MessageWriteEHandle( CBaseEntity *pEntity ) +{ + if (!g_pMsgBuffer) + Error( "WriteEHandle called with no active message\n" ); + + long iEncodedEHandle; + + if( pEntity ) + { + EHANDLE hEnt = pEntity; + + int iSerialNum = hEnt.GetSerialNumber() & (1 << NUM_NETWORKED_EHANDLE_SERIAL_NUMBER_BITS) - 1; + iEncodedEHandle = hEnt.GetEntryIndex() | (iSerialNum << MAX_EDICT_BITS); + } + else + { + iEncodedEHandle = INVALID_NETWORKED_EHANDLE_VALUE; + } + + g_pMsgBuffer->WriteLong( iEncodedEHandle ); +} + +// bitwise +void MessageWriteBool( bool bValue ) +{ + if (!g_pMsgBuffer) + Error( "WriteBool called with no active message\n" ); + + g_pMsgBuffer->WriteOneBit( bValue ? 1 : 0 ); +} + +void MessageWriteUBitLong( unsigned int data, int numbits ) +{ + if (!g_pMsgBuffer) + Error( "WriteUBitLong called with no active message\n" ); + + g_pMsgBuffer->WriteUBitLong( data, numbits ); +} + +void MessageWriteSBitLong( int data, int numbits ) +{ + if (!g_pMsgBuffer) + Error( "WriteSBitLong called with no active message\n" ); + + g_pMsgBuffer->WriteSBitLong( data, numbits ); +} + +void MessageWriteBits( const void *pIn, int nBits ) +{ + if (!g_pMsgBuffer) + Error( "WriteBits called with no active message\n" ); + + g_pMsgBuffer->WriteBits( pIn, nBits ); +} + +class CServerDLLSharedAppSystems : public IServerDLLSharedAppSystems +{ +public: + CServerDLLSharedAppSystems() + { + AddAppSystem( "soundemittersystem" DLL_EXT_STRING, SOUNDEMITTERSYSTEM_INTERFACE_VERSION ); + AddAppSystem( "scenefilecache" DLL_EXT_STRING, SCENE_FILE_CACHE_INTERFACE_VERSION ); + } + + virtual int Count() + { + return m_Systems.Count(); + } + virtual char const *GetDllName( int idx ) + { + return m_Systems[ idx ].m_pModuleName; + } + virtual char const *GetInterfaceName( int idx ) + { + return m_Systems[ idx ].m_pInterfaceName; + } +private: + void AddAppSystem( char const *moduleName, char const *interfaceName ) + { + AppSystemInfo_t sys; + sys.m_pModuleName = moduleName; + sys.m_pInterfaceName = interfaceName; + m_Systems.AddToTail( sys ); + } + + CUtlVector< AppSystemInfo_t > m_Systems; +}; + +EXPOSE_SINGLE_INTERFACE( CServerDLLSharedAppSystems, IServerDLLSharedAppSystems, SERVER_DLL_SHARED_APPSYSTEMS ); + + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +void CServerGameTags::GetTaggedConVarList( KeyValues *pCvarTagList ) +{ + if ( pCvarTagList && g_pGameRules ) + { + g_pGameRules->GetTaggedConVarList( pCvarTagList ); + } +} + + + +#ifndef NO_STEAM + +CSteamID GetSteamIDForPlayerIndex( int iPlayerIndex ) +{ + const CSteamID *pResult = engine->GetClientSteamIDByPlayerIndex( iPlayerIndex ); + if ( pResult ) + return *pResult; + + // Return a bogus steam ID + return CSteamID(); +} + +#endif -- cgit v1.2.3