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