//===== Copyright © 1996-2005, 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 "replaydirector.h" #endif #include "SoundEmitterSystem/isoundemittersystembase.h" #include "nav_mesh.h" #include "AI_ResponseSystem.h" #include "saverestore_stringtable.h" #include "util.h" #include "tier0/icommandline.h" #include "datacache/imdlcache.h" #include "engine/iserverplugin.h" #include "env_debughistory.h" #include "util_shared.h" #include "player_voice_listener.h" #ifdef _WIN32 #include "ienginevgui.h" #include "vgui_gamedll_int.h" #include "vgui_controls/AnimationController.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 "matchmaking/imatchframework.h" #include "querycache.h" #include "particle_parse.h" #include "steam/steam_gameserver.h" #include "tier3/tier3.h" #include "serverbenchmark_base.h" #include "vscript/ivscript.h" #include "foundryhelpers_server.h" #include "entity_tools_server.h" #include "foundry/iserverfoundry.h" #include "point_template.h" #include "../../engine/iblackbox.h" #include "vstdlib/jobthread.h" #include "vscript_server.h" #include "tier2/tier2_logging.h" #include "fmtstr.h" #ifdef INFESTED_DLL #include "missionchooser/iasw_mission_chooser.h" #include "missionchooser/iasw_mission_chooser_source.h" #include "matchmaking/swarm/imatchext_swarm.h" #include "asw_gamerules.h" #endif #ifdef _WIN32 #include "IGameUIFuncs.h" #endif extern IToolFrameworkServer *g_pToolFrameworkServer; extern IParticleSystemQuery *g_pParticleSystemQuery; extern ConVar commentary; // this context is not available on dedicated servers // WARNING! always check if interfaces are available before using #if !defined(NO_STEAM) 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; IFileLoggingListener *filelogginglistener = NULL; IGameEventManager2 *gameeventmanager = NULL; IDataCache *datacache = NULL; IVDebugOverlay * debugoverlay = NULL; ISoundEmitterSystemBase *soundemitterbase = NULL; IServerPluginHelpers *serverpluginhelpers = NULL; #ifdef SERVER_USES_VGUI IEngineVGui *enginevgui = NULL; #endif // SERVER_USES_VGUI IServerEngineTools *serverenginetools = NULL; IServerFoundry *serverfoundry = NULL; ISceneFileCache *scenefilecache = NULL; #ifdef SERVER_USES_VGUI IGameUIFuncs *gameuifuncs = NULL; #endif // SERVER_USES_VGUI IXboxSystem *xboxsystem = NULL; // Xbox 360 only IScriptManager *scriptmanager = NULL; IBlackBox *blackboxrecorder = NULL; #ifdef INFESTED_DLL IASW_Mission_Chooser *missionchooser = NULL; IMatchExtSwarm *g_pMatchExtSwarm = NULL; #endif IGameSystem *SoundEmitterSystem(); void SoundSystemPreloadSounds( void ); 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; #if !defined(NO_STEAM) //----------------------------------------------------------------------------- // Purpose: singleton accessor //----------------------------------------------------------------------------- static CSteam3Server s_Steam3Server; CSteam3Server &Steam3Server() { return s_Steam3Server; } //----------------------------------------------------------------------------- // Purpose: Constructor //----------------------------------------------------------------------------- CSteam3Server::CSteam3Server() { m_bInitialized = false; } #endif // 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_pStringTableExtraParticleFiles = NULL; CStringTableSaveRestoreOps g_VguiScreenStringOps; // Holds global variables shared between engine and game. CGlobalVars *gpGlobals; static int g_nCommandClientIndex = 0; // The chapter number of the current static int g_nCurrentChapterIndex = -1; 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)." ); static ClientPutInServerOverrideFn g_pClientPutInServerOverride = NULL; static void UpdateChapterRestrictions( const char *mapname ); CSharedEdictChangeInfo *g_pSharedChangeInfo = NULL; 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 ); static bool g_bHeadTrackingEnabled = false; bool IsHeadTrackingEnabled() { #if defined( HL2_DLL ) return g_bHeadTrackingEnabled; #else return false; #endif } //----------------------------------------------------------------------------- // 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; } extern void InitializeCvars( void ); 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 / Plat_FloatTime()); 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 / Plat_FloatTime()); 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 && Plat_FloatTime() >= CMeasureSection::m_dNextResort ) { // Redo it CMeasureSection::SortSections(); // Set next time CMeasureSection::m_dNextResort = Plat_FloatTime() + 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 ) { NDebugOverlay::PurgeTextOverlays(); // 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 = pPlayer->FindPickerEntity(); 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 = pPlayer->FindPickerAILink(); 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); } // Draw in a pukey yellowish green if link that's "bashable", which is off to everyone but a special door basher else if (pAILink->m_LinkInfo & bits_LINK_ASW_BASHABLE) { NDebugOverlay::BoxDirection(startPos, Vector(-4,-4,-4), Vector(-linkLen,4,4), linkDir, 40,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 = pPlayer->FindPickerAINode(NODE_AIR); } else { pAINode = pPlayer->FindPickerAINode(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() ) { // 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(); } // enable threading of init functions on x360 static ConVar sv_threaded_init("sv_threaded_init", IsX360() ? "1" : "0"); static bool InitGameSystems( CreateInterfaceFn appSystemFactory ) { // The string system must init first + shutdown last IGameSystem::Add( GameStringSystem() ); // Physics must occur before the sound envelope manager IGameSystem::Add( PhysicsGameSystem() ); // Precache system must be next (requires physics game system) IGameSystem::Add( g_pPrecacheRegister ); // Used to service deferred navigation queries for NPCs IGameSystem::Add( (IGameSystem *) PostFrameNavigationSystem() ); // Add game log system IGameSystem::Add( GameLogSystem() ); // Add HLTV director IGameSystem::Add( HLTVDirectorSystem() ); #if defined( REPLAY_ENABLED ) // Add Replay director IGameSystem::Add( ReplayDirectorSystem() ); #endif // Add sound emitter IGameSystem::Add( SoundEmitterSystem() ); #ifdef SERVER_USES_VGUI // Startup vgui if ( enginevgui ) { if(!VGui_Startup( appSystemFactory )) return false; } #endif // SERVER_USES_VGUI // load Mod specific game events ( MUST be before InitAllSystems() so it can pickup the mod specific events) gameeventmanager->LoadEventsFromFile("resource/ModEvents.res"); if ( !IGameSystem::InitAllSystems() ) return false; // Due to dependencies, these are not autogamesystems if ( !ModelSoundsCacheInit() ) { return false; } // Parse the particle manifest file & register the effects within it // ParseParticleEffects( false ); InvalidateQueryCache(); // create the Navigation Mesh interface TheNavMesh = NavMeshFactory(); // init the gamestatsupload connection gamestatsuploader->InitConnection(); return true; } CServerGameDLL g_ServerGameDLL; EXPOSE_SINGLE_INTERFACE_GLOBALVAR(CServerGameDLL, IServerGameDLL, INTERFACEVERSION_SERVERGAMEDLL, g_ServerGameDLL); bool CServerGameDLL::DLLInit( CreateInterfaceFn appSystemFactory, CreateInterfaceFn physicsFactory, CreateInterfaceFn fileSystemFactory, CGlobalVars *pGlobals) { COM_TimestampedLog( "ConnectTier1/2/3Libraries - Start" ); ConnectTier1Libraries( &appSystemFactory, 1 ); ConnectTier2Libraries( &appSystemFactory, 1 ); ConnectTier3Libraries( &appSystemFactory, 1 ); COM_TimestampedLog( "ConnectTier1/2/3Libraries - Finish" ); // Connected in ConnectTier1Libraries if ( cvar == NULL ) return false; #if !defined( SWDS ) && !defined(NO_STEAM) SteamAPI_InitSafe(); s_SteamAPIContext.Init(); #endif #if !defined(NO_STEAM) s_SteamGameServerAPIContext.Init(); #endif COM_TimestampedLog( "Factories - Start" ); // 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 ( (filelogginglistener = (IFileLoggingListener *)appSystemFactory(FILELOGGINGLISTENER_INTERFACE_VERSION, 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; if ( (gamestatsuploader = (IUploadGameStats *)appSystemFactory( INTERFACEVERSION_UPLOADGAMESTATS, NULL )) == NULL ) return false; 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 ( (blackboxrecorder = (IBlackBox *)appSystemFactory(BLACKBOX_INTERFACE_VERSION, NULL)) == NULL ) return false; if ( (xboxsystem = (IXboxSystem *)appSystemFactory( XBOXSYSTEM_INTERFACE_VERSION, NULL )) == NULL ) return false; if ( !CommandLine()->CheckParm( "-noscripting") ) { scriptmanager = (IScriptManager *)appSystemFactory( VSCRIPT_INTERFACE_VERSION, NULL ); } #ifdef SERVER_USES_VGUI // If not running dedicated, grab the engine vgui interface if ( !engine->IsDedicatedServer() ) { #ifdef _WIN32 if ( ( enginevgui = ( IEngineVGui * )appSystemFactory(VENGINE_VGUI_VERSION, NULL)) == NULL ) return false; // This interface is optional, and is only valid when running with -tools serverenginetools = ( IServerEngineTools * )appSystemFactory( VSERVERENGINETOOLS_INTERFACE_VERSION, NULL ); gameuifuncs = (IGameUIFuncs * )appSystemFactory( VENGINE_GAMEUIFUNCS_VERSION, NULL ); #endif } #endif // SERVER_USES_VGUI #ifdef INFESTED_DLL if ( (missionchooser = (IASW_Mission_Chooser *)appSystemFactory(ASW_MISSION_CHOOSER_VERSION, NULL)) == NULL ) return false; if ( (g_pMatchExtSwarm = (IMatchExtSwarm *)appSystemFactory(IMATCHEXT_SWARM_INTERFACE, NULL)) == NULL ) return false; #endif if ( !g_pMatchFramework ) return false; if ( IMatchExtensions *pIMatchExtensions = g_pMatchFramework->GetMatchExtensions() ) pIMatchExtensions->RegisterExtensionInterface( INTERFACEVERSION_SERVERGAMEDLL, static_cast< IServerGameDLL * >( this ) ); COM_TimestampedLog( "Factories - Finish" ); COM_TimestampedLog( "soundemitterbase->Connect" ); // Yes, both the client and game .dlls will try to Connect, the soundemittersystem.dll will handle this gracefully if ( !soundemitterbase->Connect( appSystemFactory ) ) return false; if ( CommandLine()->FindParm( "-headtracking" ) ) g_bHeadTrackingEnabled = true; // cache the globals gpGlobals = pGlobals; g_pSharedChangeInfo = engine->GetSharedEdictChangeInfo(); COM_TimestampedLog( "MathLib_Init" ); 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 ); COM_TimestampedLog( "gameeventmanager->LoadEventsFromFile" ); // load used game events gameeventmanager->LoadEventsFromFile("resource/gameevents.res"); COM_TimestampedLog( "InitializeCvars" ); // init the cvar list first in case inits want to reference them InitializeCvars(); COM_TimestampedLog( "g_pParticleSystemMgr->Init" ); // Initialize the particle system bool bPrecacheParticles = IsPC() && !engine->IsCreatingXboxReslist(); if ( !g_pParticleSystemMgr->Init( g_pParticleSystemQuery, bPrecacheParticles ) ) { 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" ); sv_maxreplay = g_pCVar->FindVar( "sv_maxreplay" ); COM_TimestampedLog( "g_pGameSaveRestoreBlockSet" ); 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() ); g_pGameSaveRestoreBlockSet->AddBlockHandler( GetVScriptSaveRestoreBlockHandler() ); bool bInitSuccess = false; if ( sv_threaded_init.GetBool() ) { CFunctorJob *pGameJob = new CFunctorJob( CreateFunctor( ParseParticleEffects, false ) ); g_pThreadPool->AddJob( pGameJob ); bInitSuccess = InitGameSystems( appSystemFactory ); // FIXME: This method is a bit of a hack. // Try to update the screen every .06 seconds while waiting. float flLastUpdateTime = -1.0f; while( !pGameJob->IsFinished() ) { float flTime = Plat_FloatTime(); if ( flTime - flLastUpdateTime > .06f ) { flLastUpdateTime = flTime; engine->RefreshScreenIfNecessary(); } ThreadSleep( 0 ); } pGameJob->Release(); } else { COM_TimestampedLog( "ParseParticleEffects" ); ParseParticleEffects( false ); COM_TimestampedLog( "InitGameSystems - Start" ); bInitSuccess = InitGameSystems( appSystemFactory ); COM_TimestampedLog( "InitGameSystems - Finish" ); } // try to get debug overlay, may be NULL if on HLDS debugoverlay = (IVDebugOverlay *)appSystemFactory( VDEBUG_OVERLAY_INTERFACE_VERSION, NULL ); // init the gamestatsupload connection gamestatsuploader->InitConnection(); return true; } void CServerGameDLL::PostInit() { IGameSystem::PostInitAllSystems(); #ifdef SERVER_USES_VGUI if ( !engine->IsDedicatedServer() && enginevgui ) { if ( VGui_PostInit() ) { // all good } } #endif // SERVER_USES_VGUI } void CServerGameDLL::PostToolsInit() { if ( serverenginetools ) { serverfoundry = ( IServerFoundry * )serverenginetools->QueryInterface( VSERVERFOUNDRY_INTERFACE_VERSION ); } } void CServerGameDLL::DLLShutdown( void ) { // Due to dependencies, these are not autogamesystems ModelSoundsCacheShutdown(); g_pGameSaveRestoreBlockSet->RemoveBlockHandler( GetVScriptSaveRestoreBlockHandler() ); 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 SERVER_USES_VGUI if ( enginevgui ) { VGui_Shutdown(); } #endif // SERVER_USES_VGUI // destroy the Navigation Mesh interface if (TheNavMesh) { delete TheNavMesh; TheNavMesh = NULL; } #if !defined(NO_STEAM) s_SteamAPIContext.Clear(); // Steam API context shutdown s_SteamGameServerAPIContext.Clear(); // SteamAPI_Shutdown(); << Steam shutdown is controlled by engine #endif DisconnectTier3Libraries(); DisconnectTier2Libraries(); ConVar_Unregister(); DisconnectTier1Libraries(); } //----------------------------------------------------------------------------- // 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; // 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; } 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; } bool CServerGameDLL::SupportsSaveRestore() { #ifdef INFESTED_DLL return false; #endif return true; } // 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"); ResetWindspeed(); UpdateChapterRestrictions( pMapName ); // 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 && gpGlobals->maxClients == 1 ) { // 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; // ask for the latest game rules GameRules()->UpdateGameplayStatsFromSteam(); 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 // load the Navigation Mesh for this map TheNavMesh->Load(); #endif } //----------------------------------------------------------------------------- // Purpose: Called after the steam API has been activated post-level startup //----------------------------------------------------------------------------- void CServerGameDLL::GameServerSteamAPIActivated( void ) { #if !defined( NO_STEAM ) steamgameserverapicontext->Init(); #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; #ifndef NO_STEAM // All the calls to us from the engine prior to gameframe (like LevelInit & ServerActivate) // are done before the engine has got the Steam API connected, so we have to wait until now to connect ourselves. if ( Steam3Server().CheckInitialized() ) { GameRules()->UpdateGameplayStatsFromSteam(); } #endif g_bIsLogging = engine->IsLogEnabled(); 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(); HandleFoundryEntitySpawnRecords(); IGameSystem::FrameUpdatePreEntityThinkAllSystems(); GameStartFrame(); TheNavMesh->Update(); { VPROF( "gamestatsuploader->UpdateConnection" ); gamestatsuploader->UpdateConnection(); } { VPROF( "UpdateQueryCache" ); UpdateQueryCache(); } { VPROF( "g_pServerBenchmark->UpdateBenchmark" ); 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 ) { VPROF( "g_pGameRules->EndGameFrame" ); 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(); 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( INDEXENT( sv_showhitboxes.GetInt() ) ) ); if ( !anim ) return; anim->DrawServerHitboxes(); } 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 ) { #if !defined( NO_STEAM ) steamgameserverapicontext->Clear(); #endif MDLCACHE_CRITICAL_SECTION(); IGameSystem::LevelShutdownPreEntityAllSystems(); // YWB: // This entity pointer is going away now and is corrupting memory on level transitions/restarts CSoundEnt::ShutdownSoundEnt(); ClearDebugHistory(); gEntList.Clear(); InvalidateQueryCache(); IGameSystem::LevelShutdownPostEntityAllSystems(); // In case we quit out during initial load CBaseEntity::SetAllowPrecache( false ); TheNavMesh->Reset(); g_nCurrentChapterIndex = -1; CStudioHdr::CActivityToSequenceMapping::ResetMappings(); } //----------------------------------------------------------------------------- // 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_pStringTableExtraParticleFiles = networkstringtable->CreateStringTable( "ExtraParticleFilesTable", MAX_PARTICLESYSTEMS_STRINGS, 0, 0, NSF_DICTIONARY_ENABLED ); g_pStringTableParticleEffectNames = networkstringtable->CreateStringTable( "ParticleEffectNames", MAX_PARTICLESYSTEMS_STRINGS, 0, 0, NSF_DICTIONARY_ENABLED ); 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, 0, 0, NSF_DICTIONARY_ENABLED ); g_pStringTableInfoPanel = networkstringtable->CreateStringTable( "InfoPanel", MAX_INFOPANEL_STRINGS ); g_pStringTableClientSideChoreoScenes = networkstringtable->CreateStringTable( "Scenes", MAX_CHOREO_SCENES_STRINGS, 0, 0, NSF_DICTIONARY_ENABLED ); Assert( g_pStringTableParticleEffectNames && g_pStringTableEffectDispatch && g_pStringTableVguiScreen && g_pStringTableMaterials && g_pStringTableInfoPanel && g_pStringTableClientSideChoreoScenes && g_pStringTableExtraParticleFiles ); // 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 ); PrecacheEffect( "error" ); // ensure error effect is handy Assert( GetEffectIndex( "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) { if ( engine->IsOverrideLoadGameEntsOn() ) FoundryHelpers_ClearEntityHighlightEffects(); CRestore restore(s); g_pGameSaveRestoreBlockSet->Restore( &restore, b ); g_pGameSaveRestoreBlockSet->PostRestore(); if ( serverfoundry && engine->IsOverrideLoadGameEntsOn() ) serverfoundry->OnFinishedRestoreSavegame(); } //----------------------------------------------------------------------------- // 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 { char *pBSPName; char *pTitleName; } TITLECOMMENT; // this list gets searched for the first partial match, so some are out of order static TITLECOMMENT gTitleComments[] = { { "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" }, }; 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 ( gpGlobals && gpGlobals->eLoadType == MapLoad_Background ) return true; 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(); } } CStudioHdr::CActivityToSequenceMapping::ResetMappings(); } KeyValues * CServerGameDLL::FindLaunchOptionByValue( KeyValues *pLaunchOptions, char const *szLaunchOption ) { if ( !pLaunchOptions || !szLaunchOption || !*szLaunchOption ) return NULL; for ( KeyValues *val = pLaunchOptions->GetFirstSubKey(); val; val = val->GetNextKey() ) { char const *szValue = val->GetString(); if ( szValue && *szValue && !Q_stricmp( szValue, szLaunchOption ) ) return val; } return NULL; } bool CServerGameDLL::ShouldPreferSteamAuth() { return true; } bool CServerGameDLL::SupportsRandomMaps() { #ifdef INFESTED_DLL return true; #else return false; #endif } // return true to disconnect client due to timeout (used to do stricter timeouts when the game is sure the client isn't loading a map) bool CServerGameDLL::ShouldTimeoutClient( int nUserID, float flTimeSinceLastReceived ) { if ( !g_pGameRules ) return false; return g_pGameRules->ShouldTimeoutClient( nUserID, flTimeSinceLastReceived ); } void CServerGameDLL::GetMatchmakingTags( char *buf, size_t bufSize ) { #ifdef TERROR // Additional "tags" that L4D matchmaking wants to know about // static ConVarRef mp_gamemode( "mp_gamemode" ); extern ConVar mp_gamemode; if ( g_pMatchExtL4D ) { char const *szBaseGameMode = g_pMatchExtL4D->GetGameModeInfo( mp_gamemode.GetString() )->GetString( "base", mp_gamemode.GetString() ); if ( szBaseGameMode && szBaseGameMode[0] ) { Q_strncpy( buf, szBaseGameMode, bufSize ); return; } } Q_strncpy( buf, mp_gamemode.GetString(), bufSize ); #endif } void CServerGameDLL::GetMatchmakingGameData( char *buf, size_t bufSize ) { char * const bufBase = buf; #ifdef TERROR int len = 0; // Put the game key Q_snprintf( buf, bufSize, "g:l4d2," ); len = strlen( buf ); buf += len; bufSize -= len; // Supported l4d2 game types static ConVarRef sv_gametypes( "sv_gametypes" ); if ( sv_gametypes.IsValid() && sv_gametypes.GetString()[0] ) { Q_snprintf( buf, bufSize, "%s,", sv_gametypes.GetString() ); len = strlen( buf ); buf += len; bufSize -= len; } // Additional "game data" that L4D matchmaking wants to know about KeyValues *pAllMissions = g_pMatchExtL4D->GetAllMissions(); // Build a list of missions int numMissions = 0; for ( KeyValues *pMission = pAllMissions ? pAllMissions->GetFirstTrueSubKey() : NULL; pMission; pMission = pMission->GetNextTrueSubKey() ) { if ( pMission->GetInt( "BuiltIn" ) ) continue; char const *szMission = pMission->GetString( "CfgTag" ); int len = strlen( szMission ); if ( bufSize <= len + 1 ) { Warning( "GameData: Too many missions installed, not advertising for mission \"%s\"\n", pMission->GetString( "Name" ) ); continue; } Q_strncpy( buf, szMission, len + 1 ); buf += len; *( buf ++ ) = ','; bufSize -= len + 1; ++ numMissions; } #endif // Trim the last comma if anything was written if ( buf > bufBase ) buf[ -1 ] = 0; } void CServerGameDLL::ServerHibernationUpdate( bool bHibernating ) { m_bIsHibernating = bHibernating; #ifdef INFESTED_DLL if ( engine && engine->IsDedicatedServer() && m_bIsHibernating && ASWGameRules() ) { ASWGameRules()->OnServerHibernating(); } #endif } //----------------------------------------------------------------------------- // 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", FCVAR_RELEASE, "The MOTD file to load.", ValidateMOTDFilename ); static ConVar hostfile( "hostfile", "host.txt", FCVAR_RELEASE, "The HOST file to load.", ValidateMOTDFilename ); void LoadMOTDFile( const char *stringname, ConVar *pConvarFilename ) { char data[2048]; int length = filesystem->Size( pConvarFilename->GetString(), "GAME" ); if ( length <= 0 || length >= (sizeof(data)-1) ) { DevMsg("Invalid file size for %s\n", pConvarFilename->GetString() ); return; } FileHandle_t hFile = filesystem->Open( pConvarFilename->GetString(), "rb", "GAME" ); if ( hFile == FILESYSTEM_INVALID_HANDLE ) return; filesystem->Read( data, length, hFile ); filesystem->Close( hFile ); data[length] = 0; g_pStringTableInfoPanel->AddString( CBaseEntity::IsServer(), stringname, length+1, data ); } void CServerGameDLL::LoadMessageOfTheDay() { LoadMOTDFile( "motd", &motdfile ); LoadMOTDFile( "hostfile", &hostfile ); } // 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; } } //----------------------------------------------------------------------------- // 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 //----------------------------------------------------------------------------- int PrecacheParticleSystem( const char *pParticleSystemName ) { Assert( pParticleSystemName && pParticleSystemName[0] ); return g_pStringTableParticleEffectNames->AddString( CBaseEntity::IsServer(), pParticleSystemName ); } void PrecacheParticleFileAndSystems( const char *pParticleSystemFile ) { g_pParticleSystemMgr->ShouldLoadSheets( true ); g_pParticleSystemMgr->ReadParticleConfigFile( pParticleSystemFile, true, false ); g_pParticleSystemMgr->DecommitTempMemory(); Assert( pParticleSystemFile && pParticleSystemFile[0] ); g_pStringTableExtraParticleFiles->AddString( CBaseEntity::IsServer(), pParticleSystemFile ); CUtlVector systems; g_pParticleSystemMgr->GetParticleSystemsInFile( pParticleSystemFile, &systems ); int nCount = systems.Count(); for ( int i = 0; i < nCount; ++i ) { PrecacheParticleSystem( systems[i] ); } } void PrecacheGameSoundsFile( const char *pSoundFile ) { soundemitterbase->AddSoundsFromFile( pSoundFile, true ); SoundSystemPreloadSounds(); } //----------------------------------------------------------------------------- // 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"; } //----------------------------------------------------------------------------- // Precaches an effect (used by DispatchEffect) //----------------------------------------------------------------------------- void PrecacheEffect( const char *pEffectName ) { Assert( pEffectName && pEffectName[0] ); g_pStringTableEffectDispatch->AddString( CBaseEntity::IsServer(), pEffectName ); } //----------------------------------------------------------------------------- // Converts a previously precached effect into an index //----------------------------------------------------------------------------- int GetEffectIndex( const char *pEffectName ) { if ( pEffectName ) { int nIndex = g_pStringTableEffectDispatch->FindStringIndex( pEffectName ); if (nIndex != INVALID_STRING_INDEX ) return nIndex; DevWarning("Server: Missing precache for effect \"%s\"!\n", pEffectName ); } // This is the invalid string index return 0; } //----------------------------------------------------------------------------- // Converts a previously precached effect index into a string //----------------------------------------------------------------------------- const char *GetEffectNameFromIndex( int nEffectIndex ) { if ( nEffectIndex < g_pStringTableEffectDispatch->GetMaxStrings() ) return g_pStringTableEffectDispatch->GetString( nEffectIndex ); 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 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 ); virtual void PrepareForFullUpdate( edict_t *pEdict ); }; EXPOSE_SINGLE_INTERFACE(CServerGameEnts, IServerGameEnts, INTERFACEVERSION_SERVERGAMEENTS); //----------------------------------------------------------------------------- // 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 = gpGlobals->pEdicts; 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(); #if defined( REPLAY_ENABLED ) const bool bIsReplay = pRecipientPlayer->IsReplay(); #else const bool bIsReplay = false; #endif // 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]; #if _X360 if ( i < nEdicts-1 ) { PREFETCH360(&pBaseEdict[pEdictIndices[i+1]],0); } #endif edict_t *pEdict = &pBaseEdict[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; PREFETCH360(pEdict->GetUnknown(),0); // 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 ); } //----------------------------------------------------------------------------- // Purpose: called before a full update, so the server can flush any custom PVS info, etc //----------------------------------------------------------------------------- void CServerGameEnts::PrepareForFullUpdate( edict_t *pEdict ) { CBaseEntity *pEntity = CBaseEntity::Instance( pEdict ); Assert( pEntity && pEntity->IsPlayer() ); if ( !pEntity ) return; CBasePlayer *pPlayer = static_cast( pEntity ); pPlayer->PrepareForFullUpdate(); } CServerGameClients 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 ) { 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 ); } //----------------------------------------------------------------------------- // Purpose: Called when a player is fully connect ( initial baseline entities have been received ) // Input : *pEntity - the player //----------------------------------------------------------------------------- void CServerGameClients::ClientFullyConnect( edict_t *pEdict ) { ::ClientFullyConnect( pEdict ); } //----------------------------------------------------------------------------- // 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 } } void CServerGameClients::ClientPutInServer( edict_t *pEntity, const char *playername ) { if ( g_pClientPutInServerOverride ) g_pClientPutInServerOverride( pEntity, playername ); else ::ClientPutInServer( pEntity, playername ); CBasePlayer *pPlayer = ToBasePlayer( GetContainingEntity( pEntity ) ); if ( pPlayer ) { bool bIsSplitScreenPlayer = engine->IsSplitScreenPlayer( pPlayer->entindex() ); CBasePlayer *pAttachedTo = NULL; if ( bIsSplitScreenPlayer ) { pAttachedTo = (CBasePlayer *)::GetContainingEntity( engine->GetSplitScreenPlayerAttachToEdict( pPlayer->entindex() ) ); } pPlayer->SetSplitScreenPlayer( bIsSplitScreenPlayer, pAttachedTo ); } } 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; #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 = (int)clamp( player->m_nUpdateRate, pMinUpdateRate->GetFloat(), 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 ); } //----------------------------------------------------------------------------- // 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; CUtlVector< Vector > areaPortalOrigins; CBasePlayer *pPlayer = ( CBasePlayer * )GetContainingEntity( pClient ); if ( pPlayer ) { if ( !pVE ) { org = pPlayer->EyePosition(); } pPlayer->SetupVisibility( pVE, pvs, pvssize ); UTIL_SetClientVisibilityPVS( pClient, pvs, pvssize ); fovDistanceAdjustFactor = pPlayer->GetFOVDistanceAdjustFactorForNetworking(); areaPortalOrigins.AddToTail( org ); // Merge in areaportal "window" states from from split screen players by passing in the extra PVS origins!!! for ( int i = 1; i < MAX_SPLITSCREEN_PLAYERS; ++i ) { CBasePlayer *pl = (CBasePlayer *)ToBasePlayer( GetContainingEntity( engine->GetSplitScreenPlayerForEdict( pPlayer->entindex(), i ) ) ); if ( !pl ) continue; org = pl->EyePosition(); areaPortalOrigins.AddToTail( org ); } } else { Warning( "ClientSetupVisibility: No entity for edict!\n" ); areaPortalOrigins.AddToTail( org ); } 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( areaPortalOrigins, fovDistanceAdjustFactor, bIsOpenOnClient ); ++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. Assert( pPlayer ); if ( pPlayer ) { pPlayer->m_Local.UpdateAreaBits( pPlayer, portalBits ); } } //----------------------------------------------------------------------------- // 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( void ) { VPROF("CServerGameClients::PostClient"); gEntList.PostClientMessagesSent(); } // 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 = UTIL_PlayerByIndex(1) ? UTIL_PlayerByIndex(1)->FindPickerEntity() : NULL; 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 player sent a voice packet //----------------------------------------------------------------------------- void CServerGameClients::ClientVoice( edict_t *pEdict ) { CBasePlayer *pPlayer = ( CBasePlayer * )CBaseEntity::Instance( pEdict ); if (pPlayer) { pPlayer->OnVoiceTransmit(); // Notify the voice listener that we've spoken PlayerVoiceListener().AddPlayerSpeakTime( pPlayer ); } } //----------------------------------------------------------------------------- // Purpose: A user has had their network id setup and validated //----------------------------------------------------------------------------- void CServerGameClients::NetworkIDValidated( const char *pszUserName, const char *pszNetworkID ) { } int CServerGameClients::GetMaxSplitscreenPlayers() { return MAX_SPLITSCREEN_PLAYERS; } int CServerGameClients::GetMaxHumanPlayers() { if ( g_pGameRules ) { return g_pGameRules->GetMaxHumanPlayers(); } return -1; } // The client has submitted a keyvalues command void CServerGameClients::ClientCommandKeyValues( edict_t *pEntity, KeyValues *pKeyValues ) { if ( !pKeyValues ) return; char const *szCommand = pKeyValues->GetName(); if ( FStrEq( szCommand, "avatarinfo" ) ) { // Player is communicating team and avatar setting //TheDirector->PlayerAvatarSet( pEntity, pKeyValues ); } 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, messagename ); } 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 MessageWriteBitVecIntegral( const Vector& vecValue ) { if (!g_pMsgBuffer) Error( "MessageWriteBitVecIntegral called with no active message\n" ); for ( int i = 0; i < 3; ++i ) { g_pMsgBuffer->WriteBitCoordMP( vecValue[ i ], kCW_Integral ); } } 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", SOUNDEMITTERSYSTEM_INTERFACE_VERSION ); AddAppSystem( "scenefilecache", SCENE_FILE_CACHE_INTERFACE_VERSION ); #ifdef INFESTED_DLL AddAppSystem( "missionchooser", ASW_MISSION_CHOOSER_VERSION ); #endif } 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 ); } }