//========= Copyright Valve Corporation, All rights reserved. ============// // // Purpose: // // $Workfile: $ // $NoKeywords: $ //===========================================================================// #include "server_pch.h" #include "tier0/valve_minmax_off.h" #include #include "tier0/valve_minmax_on.h" #include "vengineserver_impl.h" #include "vox.h" #include "sound.h" #include "gl_model_private.h" #include "host_saverestore.h" #include "world.h" #include "l_studio.h" #include "decal.h" #include "sys_dll.h" #include "sv_log.h" #include "sv_main.h" #include "tier1/strtools.h" #include "collisionutils.h" #include "staticpropmgr.h" #include "string_t.h" #include "vstdlib/random.h" #include "EngineSoundInternal.h" #include "dt_send_eng.h" #include "PlayerState.h" #include "irecipientfilter.h" #include "sv_user.h" #include "server_class.h" #include "cdll_engine_int.h" #include "enginesingleuserfilter.h" #include "ispatialpartitioninternal.h" #include "con_nprint.h" #include "tmessage.h" #include "iscratchpad3d.h" #include "pr_edict.h" #include "networkstringtableserver.h" #include "networkstringtable.h" #include "LocalNetworkBackdoor.h" #include "host_phonehome.h" #include "matchmaking.h" #include "sv_plugin.h" #include "sv_steamauth.h" #include "replay_internal.h" #include "replayserver.h" #include "replay/iserverengine.h" // memdbgon must be the last include file in a .cpp file!!! #include "tier0/memdbgon.h" #define MAX_MESSAGE_SIZE 2500 #define MAX_TOTAL_ENT_LEAFS 128 void SV_DetermineMulticastRecipients( bool usepas, const Vector& origin, CBitVec< ABSOLUTE_PLAYER_LIMIT >& playerbits ); int MapList_ListMaps( const char *pszSubString, bool listobsolete, bool verbose, int maxcount, int maxitemlength, char maplist[][ 64 ] ); extern CNetworkStringTableContainer *networkStringTableContainerServer; CSharedEdictChangeInfo g_SharedEdictChangeInfo; CSharedEdictChangeInfo *g_pSharedChangeInfo = &g_SharedEdictChangeInfo; IAchievementMgr *g_pAchievementMgr = NULL; CGamestatsData *g_pGamestatsData = NULL; void InvalidateSharedEdictChangeInfos() { if ( g_SharedEdictChangeInfo.m_iSerialNumber == 0xFFFF ) { // Reset all edicts to 0. g_SharedEdictChangeInfo.m_iSerialNumber = 1; for ( int i=0; i < sv.num_edicts; i++ ) sv.edicts[i].SetChangeInfoSerialNumber( 0 ); } else { g_SharedEdictChangeInfo.m_iSerialNumber++; } g_SharedEdictChangeInfo.m_nChangeInfos = 0; } // ---------------------------------------------------------------------- // // Globals. // ---------------------------------------------------------------------- // struct MsgData { MsgData() { Reset(); // link buffers to messages entityMsg.m_DataOut.StartWriting( entitydata, sizeof(entitydata) ); entityMsg.m_DataOut.SetDebugName( "s_MsgData.entityMsg.m_DataOut" ); userMsg.m_DataOut.StartWriting( userdata, sizeof(userdata) ); userMsg.m_DataOut.SetDebugName( "s_MsgData.userMsg.m_DataOut" ); } void Reset() { filter = NULL; reliable = false; subtype = 0; started = false; usermessagesize = -1; usermessagename = NULL; currentMsg = NULL; } byte userdata[ PAD_NUMBER( MAX_USER_MSG_DATA, 4 ) ]; // buffer for outgoing user messages byte entitydata[ PAD_NUMBER( MAX_ENTITY_MSG_DATA, 4 ) ]; // buffer for outgoing entity messages IRecipientFilter *filter; // clients who get this message bool reliable; INetMessage *currentMsg; // pointer to entityMsg or userMessage int subtype; // usermessage index bool started; // IS THERE A MESSAGE IN THE PROCESS OF BEING SENT? int usermessagesize; char const *usermessagename; SVC_EntityMessage entityMsg; SVC_UserMessage userMsg; }; static MsgData s_MsgData; void SeedRandomNumberGenerator( bool random_invariant ) { if (!random_invariant) { long iSeed; g_pVCR->Hook_Time( &iSeed ); float flAppTime = Plat_FloatTime(); ThreadId_t threadId = ThreadGetCurrentId(); iSeed ^= (*((int *)&flAppTime)); iSeed ^= threadId; RandomSeed( iSeed ); } else { // Make those random numbers the same every time! RandomSeed( 0 ); } } // ---------------------------------------------------------------------- // // Static helpers. // ---------------------------------------------------------------------- // static void PR_CheckEmptyString (const char *s) { if (s[0] <= ' ') Host_Error ("Bad string: %s", s); } // Average a list a vertices to find an approximate "center" static void CenterVerts( Vector verts[], int vertCount, Vector& center ) { int i; float scale; if ( vertCount ) { Vector edge0, edge1, normal; VectorCopy( vec3_origin, center ); // sum up verts for ( i = 0; i < vertCount; i++ ) { VectorAdd( center, verts[i], center ); } scale = 1.0f / (float)vertCount; VectorScale( center, scale, center ); // divide by vertCount // Compute 2 poly edges VectorSubtract( verts[1], verts[0], edge0 ); VectorSubtract( verts[vertCount-1], verts[0], edge1 ); // cross for normal CrossProduct( edge0, edge1, normal ); // Find the component of center that is outside of the plane scale = DotProduct( center, normal ) - DotProduct( verts[0], normal ); // subtract it off VectorMA( center, scale, normal, center ); // center is in the plane now } } // Copy the list of verts from an msurface_t int a linear array static void SurfaceToVerts( model_t *model, SurfaceHandle_t surfID, Vector verts[], int *vertCount ) { if ( *vertCount > MSurf_VertCount( surfID ) ) *vertCount = MSurf_VertCount( surfID ); // Build the list of verts from 0 to n for ( int i = 0; i < *vertCount; i++ ) { int vertIndex = model->brush.pShared->vertindices[ MSurf_FirstVertIndex( surfID ) + i ]; Vector& vert = model->brush.pShared->vertexes[ vertIndex ].position; VectorCopy( vert, verts[i] ); } // vert[0] is the first and last vert, there is no copy } // Calculate the surface area of an arbitrary msurface_t polygon (convex with collinear verts) static float SurfaceArea( model_t *model, SurfaceHandle_t surfID ) { Vector center, verts[32]; int vertCount = 32; float area; int i; // Compute a "center" point and fan SurfaceToVerts( model, surfID, verts, &vertCount ); CenterVerts( verts, vertCount, center ); area = 0; // For a triangle of the center and each edge for ( i = 0; i < vertCount; i++ ) { Vector edge0, edge1, out; int next; next = (i+1)%vertCount; VectorSubtract( verts[i], center, edge0 ); // 0.5 * edge cross edge (0.5 is done once at the end) VectorSubtract( verts[next], center, edge1 ); CrossProduct( edge0, edge1, out ); area += VectorLength( out ); } return area * 0.5; // 0.5 here } // Average the list of vertices to find an approximate "center" static void SurfaceCenter( model_t *model, SurfaceHandle_t surfID, Vector& center ) { Vector verts[32]; // We limit faces to 32 verts elsewhere in the engine int vertCount = 32; SurfaceToVerts( model, surfID, verts, &vertCount ); CenterVerts( verts, vertCount, center ); } static bool ValidCmd( const char *pCmd ) { int len; len = strlen(pCmd); // Valid commands all have a ';' or newline '\n' as their last character if ( len && (pCmd[len-1] == '\n' || pCmd[len-1] == ';') ) return true; return false; } // ---------------------------------------------------------------------- // // CVEngineServer // ---------------------------------------------------------------------- // class CVEngineServer : public IVEngineServer { public: virtual void ChangeLevel( const char* s1, const char* s2) { if ( !s1 ) { Sys_Error( "CVEngineServer::Changelevel with NULL s1\n" ); } char cmd[ 256 ]; char s1Escaped[ sizeof( cmd ) ]; char s2Escaped[ sizeof( cmd ) ]; if ( !Cbuf_EscapeCommandArg( s1, s1Escaped, sizeof( s1Escaped ) ) || ( s2 && !Cbuf_EscapeCommandArg( s2, s2Escaped, sizeof( s2Escaped ) ))) { Warning( "Illegal map name in ChangeLevel\n" ); return; } int cmdLen = 0; if ( !s2 ) // no indication of where they are coming from; so just do a standard old changelevel { cmdLen = Q_snprintf( cmd, sizeof( cmd ), "changelevel %s\n", s1Escaped ); } else { cmdLen = Q_snprintf( cmd, sizeof( cmd ), "changelevel2 %s %s\n", s1Escaped, s2Escaped ); } if ( !cmdLen || cmdLen >= sizeof( cmd ) ) { Warning( "Paramter overflow in ChangeLevel\n" ); return; } Cbuf_AddText( cmd ); } virtual int IsMapValid( const char *filename ) { return modelloader->Map_IsValid( filename ); } virtual bool IsDedicatedServer( void ) { return sv.IsDedicated(); } virtual int IsInEditMode( void ) { #ifdef SWDS return false; #else return g_bInEditMode; #endif } virtual int IsInCommentaryMode( void ) { #ifdef SWDS return false; #else return g_bInCommentaryMode; #endif } virtual void NotifyEdictFlagsChange( int iEdict ) { if ( g_pLocalNetworkBackdoor ) g_pLocalNetworkBackdoor->NotifyEdictFlagsChange( iEdict ); } virtual const CCheckTransmitInfo* GetPrevCheckTransmitInfo( edict_t *pPlayerEdict ) { int entnum = NUM_FOR_EDICT( pPlayerEdict ); if ( entnum < 1 || entnum > sv.GetClientCount() ) { Error( "Invalid client specified in GetPrevCheckTransmitInfo\n" ); return NULL; } CGameClient *client = sv.Client( entnum-1 ); return client->GetPrevPackInfo(); } virtual int PrecacheDecal( const char *name, bool preload /*=false*/ ) { PR_CheckEmptyString( name ); int i = SV_FindOrAddDecal( name, preload ); if ( i >= 0 ) { return i; } Host_Error( "CVEngineServer::PrecacheDecal: '%s' overflow, too many decals", name ); return 0; } virtual int PrecacheModel( const char *s, bool preload /*= false*/ ) { PR_CheckEmptyString (s); int i = SV_FindOrAddModel( s, preload ); if ( i >= 0 ) { return i; } Host_Error( "CVEngineServer::PrecacheModel: '%s' overflow, too many models", s ); return 0; } virtual int PrecacheGeneric(const char *s, bool preload /*= false*/ ) { int i; PR_CheckEmptyString (s); i = SV_FindOrAddGeneric( s, preload ); if (i >= 0) { return i; } Host_Error ("CVEngineServer::PrecacheGeneric: '%s' overflow", s); return 0; } virtual bool IsModelPrecached( char const *s ) const { int idx = SV_ModelIndex( s ); return idx != -1 ? true : false; } virtual bool IsDecalPrecached( char const *s ) const { int idx = SV_DecalIndex( s ); return idx != -1 ? true : false; } virtual bool IsGenericPrecached( char const *s ) const { int idx = SV_GenericIndex( s ); return idx != -1 ? true : false; } virtual void ForceExactFile( const char *s ) { Warning( "ForceExactFile no longer supported. Use sv_pure instead. (%s)\n", s ); } virtual void ForceModelBounds( const char *s, const Vector &mins, const Vector &maxs ) { PR_CheckEmptyString( s ); SV_ForceModelBounds( s, mins, maxs ); } virtual void ForceSimpleMaterial( const char *s ) { PR_CheckEmptyString( s ); SV_ForceSimpleMaterial( s ); } virtual bool IsInternalBuild( void ) { return !phonehome->IsExternalBuild(); } //----------------------------------------------------------------------------- // Purpose: Precache a sentence file (parse on server, send to client) // Input : *s - file name //----------------------------------------------------------------------------- virtual int PrecacheSentenceFile( const char *s, bool preload /*= false*/ ) { // UNDONE: Set up preload flag // UNDONE: Send this data to the client to support multiple sentence files VOX_ReadSentenceFile( s ); return 0; } //----------------------------------------------------------------------------- // Purpose: Retrieves the pvs for an origin into the specified array // Input : *org - origin // outputpvslength - size of outputpvs array in bytes // *outputpvs - If null, then return value is the needed length // Output : int - length of pvs array used ( in bytes ) //----------------------------------------------------------------------------- virtual int GetClusterForOrigin( const Vector& org ) { return CM_LeafCluster( CM_PointLeafnum( org ) ); } virtual int GetPVSForCluster( int clusterIndex, int outputpvslength, unsigned char *outputpvs ) { int length = (CM_NumClusters()+7)>>3; if ( outputpvs ) { if ( outputpvslength < length ) { Sys_Error( "GetPVSForOrigin called with inusfficient sized pvs array, need %i bytes!", length ); return length; } CM_Vis( outputpvs, outputpvslength, clusterIndex, DVIS_PVS ); } return length; } //----------------------------------------------------------------------------- // Purpose: Test origin against pvs array retreived from GetPVSForOrigin // Input : *org - origin to chec // checkpvslength - length of pvs array // *checkpvs - // Output : bool - true if entity is visible //----------------------------------------------------------------------------- virtual bool CheckOriginInPVS( const Vector& org, const unsigned char *checkpvs, int checkpvssize ) { int clusterIndex = CM_LeafCluster( CM_PointLeafnum( org ) ); if ( clusterIndex < 0 ) return false; int offset = clusterIndex>>3; if ( offset > checkpvssize ) { Sys_Error( "CheckOriginInPVS: cluster would read past end of pvs data (%i:%i)\n", offset, checkpvssize ); return false; } if ( !(checkpvs[offset] & (1<<(clusterIndex&7)) ) ) { return false; } return true; } //----------------------------------------------------------------------------- // Purpose: Test origin against pvs array retreived from GetPVSForOrigin // Input : *org - origin to chec // checkpvslength - length of pvs array // *checkpvs - // Output : bool - true if entity is visible //----------------------------------------------------------------------------- virtual bool CheckBoxInPVS( const Vector& mins, const Vector& maxs, const unsigned char *checkpvs, int checkpvssize ) { if ( !CM_BoxVisible( mins, maxs, checkpvs, checkpvssize ) ) { return false; } return true; } virtual int GetPlayerUserId( const edict_t *e ) { if ( !sv.IsActive() || !e) return -1; for ( int i = 0; i < sv.GetClientCount(); i++ ) { CGameClient *pClient = sv.Client(i); if ( pClient->edict == e ) { return pClient->m_UserID; } } // Couldn't find it return -1; } virtual const char *GetPlayerNetworkIDString( const edict_t *e ) { if ( !sv.IsActive() || !e) return NULL; for ( int i = 0; i < sv.GetClientCount(); i++ ) { CGameClient *pGameClient = sv.Client(i); if ( pGameClient->edict == e ) { return pGameClient->GetNetworkIDString(); } } // Couldn't find it return NULL; } virtual bool IsPlayerNameLocked( const edict_t *pEdict ) { if ( !sv.IsActive() || !pEdict ) return false; for ( int i = 0; i < sv.GetClientCount(); i++ ) { CGameClient *pClient = sv.Client( i ); if ( pClient->edict == pEdict ) { return pClient->IsPlayerNameLocked(); } } return false; } virtual bool CanPlayerChangeName( const edict_t *pEdict ) { if ( !sv.IsActive() || !pEdict ) return false; for ( int i = 0; i < sv.GetClientCount(); i++ ) { CGameClient *pClient = sv.Client( i ); if ( pClient->edict == pEdict ) { return ( !pClient->IsPlayerNameLocked() && !pClient->IsNameChangeOnCooldown() ); } } return false; } // See header comment. This is the canonical map lookup spot, and a superset of the server gameDLL's // CanProvideLevel/PrepareLevelResources virtual eFindMapResult FindMap( /* in/out */ char *pMapName, int nMapNameMax ) { char szOriginalName[256] = { 0 }; V_strncpy( szOriginalName, pMapName, sizeof( szOriginalName ) ); IServerGameDLL::eCanProvideLevelResult eCanGameDLLProvide = IServerGameDLL::eCanProvideLevel_CannotProvide; if ( g_iServerGameDLLVersion >= 10 ) { eCanGameDLLProvide = serverGameDLL->CanProvideLevel( pMapName, nMapNameMax ); } if ( eCanGameDLLProvide == IServerGameDLL::eCanProvideLevel_Possibly ) { return eFindMap_PossiblyAvailable; } else if ( eCanGameDLLProvide == IServerGameDLL::eCanProvideLevel_CanProvide ) { // See if the game dll fixed up the map name return ( V_strcmp( szOriginalName, pMapName ) == 0 ) ? eFindMap_Found : eFindMap_NonCanonical; } AssertMsg( eCanGameDLLProvide == IServerGameDLL::eCanProvideLevel_CannotProvide, "Unhandled enum member" ); char szDiskName[MAX_PATH] = { 0 }; // Check if we can directly use this as a map Host_DefaultMapFileName( pMapName, szDiskName, sizeof( szDiskName ) ); if ( *szDiskName && modelloader->Map_IsValid( szDiskName, true ) ) { return eFindMap_Found; } // Fuzzy match in map list and check file char match[1][64] = { {0} }; if ( MapList_ListMaps( pMapName, false, false, 1, sizeof( match[0] ), match ) && *(match[0]) ) { Host_DefaultMapFileName( match[0], szDiskName, sizeof( szDiskName ) ); if ( modelloader->Map_IsValid( szDiskName, true ) ) { V_strncpy( pMapName, match[0], nMapNameMax ); return eFindMap_FuzzyMatch; } } return eFindMap_NotFound; } virtual int IndexOfEdict(const edict_t *pEdict) { if ( !pEdict ) { return 0; } int index = (int) ( pEdict - sv.edicts ); if ( index < 0 || index > sv.max_edicts ) { Sys_Error( "Bad entity in IndexOfEdict() index %i pEdict %p sv.edicts %p\n", index, pEdict, sv.edicts ); } return index; } // Returns a pointer to an entity from an index, but only if the entity // is a valid DLL entity (ie. has an attached class) virtual edict_t* PEntityOfEntIndex(int iEntIndex) { if ( iEntIndex >= 0 && iEntIndex < sv.max_edicts ) { edict_t *pEdict = EDICT_NUM( iEntIndex ); if ( !pEdict->IsFree() ) { return pEdict; } } return NULL; } virtual int GetEntityCount( void ) { return sv.num_edicts - sv.free_edicts; } virtual INetChannelInfo* GetPlayerNetInfo( int playerIndex ) { if ( playerIndex < 1 || playerIndex > sv.GetClientCount() ) return NULL; CGameClient *client = sv.Client( playerIndex - 1 ); return client->m_NetChannel; } virtual edict_t* CreateEdict( int iForceEdictIndex ) { edict_t *pedict = ED_Alloc( iForceEdictIndex ); if ( g_pServerPluginHandler ) { g_pServerPluginHandler->OnEdictAllocated( pedict ); } return pedict; } virtual void RemoveEdict(edict_t* ed) { if ( g_pServerPluginHandler ) { g_pServerPluginHandler->OnEdictFreed( ed ); } ED_Free(ed); } // // Request engine to allocate "cb" bytes on the entity's private data pointer. // virtual void *PvAllocEntPrivateData( long cb ) { return calloc( 1, cb ); } // // Release the private data memory, if any. // virtual void FreeEntPrivateData( void *pEntity ) { #if defined( _DEBUG ) && defined( WIN32 ) // set the memory to a known value int size = _msize( pEntity ); memset( pEntity, 0xDD, size ); #endif if ( pEntity ) { free( pEntity ); } } virtual void *SaveAllocMemory( size_t num, size_t size ) { #ifndef SWDS return ::SaveAllocMemory(num, size); #else return NULL; #endif } virtual void SaveFreeMemory( void *pSaveMem ) { #ifndef SWDS ::SaveFreeMemory(pSaveMem); #endif } /* ================= EmitAmbientSound ================= */ virtual void EmitAmbientSound( int entindex, const Vector& pos, const char *samp, float vol, soundlevel_t soundlevel, int fFlags, int pitch, float soundtime /*=0.0f*/ ) { SoundInfo_t sound; sound.SetDefault(); sound.nEntityIndex = entindex; sound.fVolume = vol; sound.Soundlevel = soundlevel; sound.nFlags = fFlags; sound.nPitch = pitch; sound.nChannel = CHAN_STATIC; sound.vOrigin = pos; sound.bIsAmbient = true; ASSERT_COORD( sound.vOrigin ); // set sound delay if ( soundtime != 0.0f ) { sound.fDelay = soundtime - sv.GetTime(); sound.nFlags |= SND_DELAY; } // if this is a sentence, get sentence number if ( TestSoundChar(samp, CHAR_SENTENCE) ) { sound.bIsSentence = true; sound.nSoundNum = Q_atoi( PSkipSoundChars(samp) ); if ( sound.nSoundNum >= VOX_SentenceCount() ) { ConMsg("EmitAmbientSound: invalid sentence number: %s", PSkipSoundChars(samp)); return; } } else { // check to see if samp was properly precached sound.bIsSentence = false; sound.nSoundNum = SV_SoundIndex( samp ); if (sound.nSoundNum <= 0) { ConMsg ("EmitAmbientSound: sound not precached: %s\n", samp); return; } } if ( (fFlags & SND_SPAWNING) && sv.allowsignonwrites ) { SVC_Sounds sndmsg; char buffer[32]; sndmsg.m_DataOut.StartWriting(buffer, sizeof(buffer) ); sndmsg.m_nNumSounds = 1; sndmsg.m_bReliableSound = true; SoundInfo_t defaultSound; defaultSound.SetDefault(); sound.WriteDelta( &defaultSound, sndmsg.m_DataOut ); // write into signon buffer if ( !sndmsg.WriteToBuffer( sv.m_Signon ) ) { Sys_Error( "EmitAmbientSound: Init message would overflow signon buffer!\n" ); return; } } else { if ( fFlags & SND_SPAWNING ) { DevMsg("EmitAmbientSound: warning, broadcasting sound labled as SND_SPAWNING.\n" ); } // send sound to all active players CEngineRecipientFilter filter; filter.AddAllPlayers(); filter.MakeReliable(); sv.BroadcastSound( sound, filter ); } } virtual void FadeClientVolume(const edict_t *clientent, float fadePercent, float fadeOutSeconds, float holdTime, float fadeInSeconds) { int entnum = NUM_FOR_EDICT(clientent); if (entnum < 1 || entnum > sv.GetClientCount() ) { ConMsg ("tried to DLL_FadeClientVolume a non-client\n"); return; } IClient *client = sv.Client(entnum-1); NET_StringCmd sndMsg( va("soundfade %.1f %.1f %.1f %.1f", fadePercent, holdTime, fadeOutSeconds, fadeInSeconds ) ); client->SendNetMsg( sndMsg ); } //----------------------------------------------------------------------------- // // Sentence API // //----------------------------------------------------------------------------- virtual int SentenceGroupPick( int groupIndex, char *name, int nameLen ) { if ( !name ) { Sys_Error( "SentenceGroupPick with NULL name\n" ); } Assert( nameLen > 0 ); return VOX_GroupPick( groupIndex, name, nameLen ); } virtual int SentenceGroupPickSequential( int groupIndex, char *name, int nameLen, int sentenceIndex, int reset ) { if ( !name ) { Sys_Error( "SentenceGroupPickSequential with NULL name\n" ); } Assert( nameLen > 0 ); return VOX_GroupPickSequential( groupIndex, name, nameLen, sentenceIndex, reset ); } virtual int SentenceIndexFromName( const char *pSentenceName ) { if ( !pSentenceName ) { Sys_Error( "SentenceIndexFromName with NULL pSentenceName\n" ); } int sentenceIndex = -1; VOX_LookupString( pSentenceName, &sentenceIndex ); return sentenceIndex; } virtual const char *SentenceNameFromIndex( int sentenceIndex ) { return VOX_SentenceNameFromIndex( sentenceIndex ); } virtual int SentenceGroupIndexFromName( const char *pGroupName ) { if ( !pGroupName ) { Sys_Error( "SentenceGroupIndexFromName with NULL pGroupName\n" ); } return VOX_GroupIndexFromName( pGroupName ); } virtual const char *SentenceGroupNameFromIndex( int groupIndex ) { return VOX_GroupNameFromIndex( groupIndex ); } virtual float SentenceLength( int sentenceIndex ) { return VOX_SentenceLength( sentenceIndex ); } //----------------------------------------------------------------------------- virtual int CheckHeadnodeVisible( int nodenum, const byte *visbits, int vissize ) { return CM_HeadnodeVisible(nodenum, visbits, vissize ); } /* ================= ServerCommand Sends text to servers execution buffer localcmd (string) ================= */ virtual void ServerCommand( const char *str ) { if ( !str ) { Sys_Error( "ServerCommand with NULL string\n" ); } if ( ValidCmd( str ) ) { Cbuf_AddText( str ); } else { ConMsg( "Error, bad server command %s\n", str ); } } /* ================= ServerExecute Executes all commands in server buffer localcmd (string) ================= */ virtual void ServerExecute( void ) { Cbuf_Execute(); } /* ================= ClientCommand Sends text over to the client's execution buffer stuffcmd (clientent, value) ================= */ virtual void ClientCommand(edict_t* pEdict, const char* szFmt, ...) { va_list argptr; static char szOut[1024]; va_start(argptr, szFmt); Q_vsnprintf(szOut, sizeof( szOut ), szFmt, argptr); va_end(argptr); if ( szOut[0] == 0 ) { Warning( "ClientCommand, 0 length string supplied.\n" ); return; } int entnum = NUM_FOR_EDICT( pEdict ); if ( ( entnum < 1 ) || ( entnum > sv.GetClientCount() ) ) { ConMsg("\n!!!\n\nStuffCmd: Some entity tried to stuff '%s' to console buffer of entity %i when maxclients was set to %i, ignoring\n\n", szOut, entnum, sv.GetMaxClients() ); return; } NET_StringCmd string( szOut ); sv.GetClient(entnum-1)->SendNetMsg( string ); } // Send a client command keyvalues // keyvalues are deleted inside the function virtual void ClientCommandKeyValues( edict_t *pEdict, KeyValues *pCommand ) { if ( !pCommand ) return; int entnum = NUM_FOR_EDICT( pEdict ); if ( ( entnum < 1 ) || ( entnum > sv.GetClientCount() ) ) { ConMsg("\n!!!\n\nClientCommandKeyValues: Some entity tried to stuff '%s' to console buffer of entity %i when maxclients was set to %i, ignoring\n\n", pCommand->GetName(), entnum, sv.GetMaxClients() ); return; } SVC_CmdKeyValues cmd( pCommand ); sv.GetClient(entnum-1)->SendNetMsg( cmd ); } /* =============== LightStyle void(float style, string value) lightstyle =============== */ virtual void LightStyle(int style, const char* val) { if ( !val ) { Sys_Error( "LightStyle with NULL value!\n" ); } // change the string in string table INetworkStringTable *stringTable = sv.GetLightStyleTable(); stringTable->SetStringUserData( style, Q_strlen(val)+1, val ); } virtual void StaticDecal( const Vector& origin, int decalIndex, int entityIndex, int modelIndex, bool lowpriority ) { SVC_BSPDecal decal; decal.m_Pos = origin; decal.m_nDecalTextureIndex = decalIndex; decal.m_nEntityIndex = entityIndex; decal.m_nModelIndex = modelIndex; decal.m_bLowPriority = lowpriority; if ( sv.allowsignonwrites ) { decal.WriteToBuffer( sv.m_Signon ); } else { sv.BroadcastMessage( decal, false, true ); } } void Message_DetermineMulticastRecipients( bool usepas, const Vector& origin, CBitVec< ABSOLUTE_PLAYER_LIMIT >& playerbits ) { SV_DetermineMulticastRecipients( usepas, origin, playerbits ); } /* =============================================================================== MESSAGE WRITING =============================================================================== */ virtual bf_write *EntityMessageBegin( int ent_index, ServerClass * ent_class, bool reliable ) { if ( s_MsgData.started ) { Sys_Error( "EntityMessageBegin: New message started before matching call to EndMessage.\n " ); return NULL; } s_MsgData.Reset(); Assert( ent_class ); s_MsgData.filter = NULL; s_MsgData.reliable = reliable; s_MsgData.started = true; s_MsgData.currentMsg = &s_MsgData.entityMsg; s_MsgData.entityMsg.m_nEntityIndex = ent_index; s_MsgData.entityMsg.m_nClassID = ent_class->m_ClassID; s_MsgData.entityMsg.m_DataOut.Reset(); return &s_MsgData.entityMsg.m_DataOut; } virtual bf_write *UserMessageBegin( IRecipientFilter *filter, int msg_index ) { if ( s_MsgData.started ) { Sys_Error( "UserMessageBegin: New message started before matching call to EndMessage.\n " ); return NULL; } s_MsgData.Reset(); Assert( filter ); s_MsgData.filter = filter; s_MsgData.reliable = filter->IsReliable(); s_MsgData.started = true; s_MsgData.currentMsg = &s_MsgData.userMsg; s_MsgData.userMsg.m_nMsgType = msg_index; s_MsgData.userMsg.m_DataOut.Reset(); return &s_MsgData.userMsg.m_DataOut; } // Validates user message type and checks to see if it's variable length // returns true if variable length int Message_CheckMessageLength() { if ( s_MsgData.currentMsg == &s_MsgData.userMsg ) { char msgname[ 256 ]; int msgsize = -1; int msgtype = s_MsgData.userMsg.m_nMsgType; if ( !serverGameDLL->GetUserMessageInfo( msgtype, msgname, sizeof(msgname), msgsize ) ) { Warning( "Unable to find user message for index %i\n", msgtype ); return -1; } int bytesWritten = s_MsgData.userMsg.m_DataOut.GetNumBytesWritten(); if ( msgsize == -1 ) { if ( bytesWritten > MAX_USER_MSG_DATA ) { Warning( "DLL_MessageEnd: Refusing to send user message %s of %i bytes to client, user message size limit is %i bytes\n", msgname, bytesWritten, MAX_USER_MSG_DATA ); return -1; } } else if ( msgsize != bytesWritten ) { Warning( "User Msg '%s': %d bytes written, expected %d\n", msgname, bytesWritten, msgsize ); return -1; } return bytesWritten; // all checks passed, estimated final length } if ( s_MsgData.currentMsg == &s_MsgData.entityMsg ) { int bytesWritten = s_MsgData.entityMsg.m_DataOut.GetNumBytesWritten(); if ( bytesWritten > MAX_ENTITY_MSG_DATA ) // TODO use a define or so { Warning( "Entity Message to %i, %i bytes written (max is %d)\n", s_MsgData.entityMsg.m_nEntityIndex, bytesWritten, MAX_ENTITY_MSG_DATA ); return -1; } return bytesWritten; // all checks passed, estimated final length } Warning( "MessageEnd unknown message type.\n" ); return -1; } virtual void MessageEnd( void ) { if ( !s_MsgData.started ) { Sys_Error( "MESSAGE_END called with no active message\n" ); return; } int length = Message_CheckMessageLength(); // check to see if it's a valid message if ( length < 0 ) { s_MsgData.Reset(); // clear message data return; } if ( s_MsgData.filter ) { // send entity/user messages only to full connected clients in filter sv.BroadcastMessage( *s_MsgData.currentMsg, *s_MsgData.filter ); } else { // send entity messages to all full connected clients sv.BroadcastMessage( *s_MsgData.currentMsg, true, s_MsgData.reliable ); } s_MsgData.Reset(); // clear message data } /* single print to a specific client */ virtual void ClientPrintf( edict_t *pEdict, const char *szMsg ) { int entnum = NUM_FOR_EDICT( pEdict ); if (entnum < 1 || entnum > sv.GetClientCount() ) { ConMsg ("tried to sprint to a non-client\n"); return; } sv.Client(entnum-1)->ClientPrintf( "%s", szMsg ); } #ifdef SWDS void Con_NPrintf( int pos, const char *fmt, ... ) { } void Con_NXPrintf( const struct con_nprint_s *info, const char *fmt, ... ) { } #else void Con_NPrintf( int pos, const char *fmt, ... ) { if ( IsDedicatedServer() ) return; va_list argptr; char text[4096]; va_start (argptr, fmt); Q_vsnprintf(text, sizeof( text ), fmt, argptr); va_end (argptr); ::Con_NPrintf( pos, "%s", text ); } void Con_NXPrintf( const struct con_nprint_s *info, const char *fmt, ... ) { if ( IsDedicatedServer() ) return; va_list argptr; char text[4096]; va_start (argptr, fmt); Q_vsnprintf(text, sizeof( text ), fmt, argptr); va_end (argptr); ::Con_NXPrintf( info, "%s", text ); } #endif virtual void SetView(const edict_t *clientent, const edict_t *viewent) { int clientnum = NUM_FOR_EDICT( clientent ); if (clientnum < 1 || clientnum > sv.GetClientCount() ) Host_Error ("DLL_SetView: not a client"); CGameClient *client = sv.Client(clientnum-1); client->m_pViewEntity = viewent; SVC_SetView view( NUM_FOR_EDICT(viewent) ); client->SendNetMsg( view ); } virtual float Time(void) { return Sys_FloatTime(); } virtual void CrosshairAngle(const edict_t *clientent, float pitch, float yaw) { int clientnum = NUM_FOR_EDICT( clientent ); if (clientnum < 1 || clientnum > sv.GetClientCount() ) Host_Error ("DLL_Crosshairangle: not a client"); IClient *client = sv.Client(clientnum-1); if (pitch > 180) pitch -= 360; if (pitch < -180) pitch += 360; if (yaw > 180) yaw -= 360; if (yaw < -180) yaw += 360; SVC_CrosshairAngle crossHairMsg; crossHairMsg.m_Angle.x = pitch; crossHairMsg.m_Angle.y = yaw; crossHairMsg.m_Angle.y = 0; client->SendNetMsg( crossHairMsg ); } virtual void GetGameDir( char *szGetGameDir, int maxlength ) { COM_GetGameDir(szGetGameDir, maxlength ); } virtual int CompareFileTime( const char *filename1, const char *filename2, int *iCompare) { return COM_CompareFileTime(filename1, filename2, iCompare); } virtual bool LockNetworkStringTables( bool lock ) { return networkStringTableContainerServer->Lock( lock ); } // For use with FAKE CLIENTS virtual edict_t* CreateFakeClient( const char *netname ) { CGameClient *fcl = static_cast( sv.CreateFakeClient( netname ) ); if ( !fcl ) { // server is full return NULL; } return fcl->edict; } // For use with FAKE CLIENTS virtual edict_t* CreateFakeClientEx( const char *netname, bool bReportFakeClient /*= true*/ ) { sv.SetReportNewFakeClients( bReportFakeClient ); edict_t *ret = CreateFakeClient( netname ); sv.SetReportNewFakeClients( true ); // Leave this set as true so other callers of sv.CreateFakeClient behave correctly. return ret; } // Get a keyvalue for s specified client virtual const char *GetClientConVarValue( int clientIndex, const char *name ) { if ( clientIndex < 1 || clientIndex > sv.GetClientCount() ) { DevMsg( 1, "GetClientConVarValue: player invalid index %i\n", clientIndex ); return ""; } return sv.GetClient( clientIndex - 1 )->GetUserSetting( name ); } virtual const char *ParseFile(const char *data, char *token, int maxlen) { return ::COM_ParseFile(data, token, maxlen ); } virtual bool CopyFile( const char *source, const char *destination ) { return ::COM_CopyFile( source, destination ); } virtual void AddOriginToPVS( const Vector& origin ) { ::SV_AddOriginToPVS(origin); } virtual void ResetPVS( byte* pvs, int pvssize ) { ::SV_ResetPVS( pvs, pvssize ); } virtual void SetAreaPortalState( int portalNumber, int isOpen ) { CM_SetAreaPortalState(portalNumber, isOpen); } virtual void SetAreaPortalStates( const int *portalNumbers, const int *isOpen, int nPortals ) { CM_SetAreaPortalStates( portalNumbers, isOpen, nPortals ); } virtual void DrawMapToScratchPad( IScratchPad3D *pPad, unsigned long iFlags ) { worldbrushdata_t *pData = host_state.worldmodel->brush.pShared; if ( !pData ) return; SurfaceHandle_t surfID = SurfaceHandleFromIndex( host_state.worldmodel->brush.firstmodelsurface, pData ); for (int i=0; i< host_state.worldmodel->brush.nummodelsurfaces; ++i, ++surfID) { // Don't bother with nodraw surfaces if( MSurf_Flags( surfID ) & SURFDRAW_NODRAW ) continue; CSPVertList vertList; for ( int iVert=0; iVert < MSurf_VertCount( surfID ); iVert++ ) { int iWorldVert = pData->vertindices[surfID->firstvertindex + iVert]; const Vector &vPos = pData->vertexes[iWorldVert].position; vertList.m_Verts.AddToTail( CSPVert( vPos ) ); } pPad->DrawPolygon( vertList ); } } const CBitVec* GetEntityTransmitBitsForClient( int iClientIndex ) { if ( iClientIndex < 0 || iClientIndex >= sv.GetClientCount() ) { Assert( false ); return NULL; } CGameClient *pClient = sv.Client( iClientIndex ); CClientFrame *deltaFrame = pClient->GetClientFrame( pClient->m_nDeltaTick ); if ( !deltaFrame ) return NULL; return &deltaFrame->transmit_entity; } virtual bool IsPaused() { return sv.IsPaused(); } virtual void SetFakeClientConVarValue( edict_t *pEntity, const char *pCvarName, const char *value ) { int clientnum = NUM_FOR_EDICT( pEntity ); if (clientnum < 1 || clientnum > sv.GetClientCount() ) Host_Error ("DLL_SetView: not a client"); CGameClient *client = sv.Client(clientnum-1); if ( client->IsFakeClient() ) { client->SetUserCVar( pCvarName, value ); client->m_bConVarsChanged = true; } } virtual CSharedEdictChangeInfo* GetSharedEdictChangeInfo() { return &g_SharedEdictChangeInfo; } virtual IChangeInfoAccessor *GetChangeAccessor( const edict_t *pEdict ) { return &sv.edictchangeinfo[ NUM_FOR_EDICT( pEdict ) ]; } virtual QueryCvarCookie_t StartQueryCvarValue( edict_t *pPlayerEntity, const char *pCvarName ) { int clientnum = NUM_FOR_EDICT( pPlayerEntity ); if (clientnum < 1 || clientnum > sv.GetClientCount() ) Host_Error( "StartQueryCvarValue: not a client" ); CGameClient *client = sv.Client( clientnum-1 ); return SendCvarValueQueryToClient( client, pCvarName, false ); } // Name of most recently load .sav file virtual char const *GetMostRecentlyLoadedFileName() { #if !defined( SWDS ) return saverestore->GetMostRecentlyLoadedFileName(); #else return ""; #endif } virtual char const *GetSaveFileName() { #if !defined( SWDS ) return saverestore->GetSaveFileName(); #else return ""; #endif } // Tells the engine we can immdiately re-use all edict indices // even though we may not have waited enough time virtual void AllowImmediateEdictReuse( ) { ED_AllowImmediateReuse(); } virtual void MultiplayerEndGame() { #if !defined( SWDS ) g_pMatchmaking->EndGame(); #endif } virtual void ChangeTeam( const char *pTeamName ) { #if !defined( SWDS ) g_pMatchmaking->ChangeTeam( pTeamName ); #endif } virtual void SetAchievementMgr( IAchievementMgr *pAchievementMgr ) { g_pAchievementMgr = pAchievementMgr; } virtual IAchievementMgr *GetAchievementMgr() { return g_pAchievementMgr; } virtual int GetAppID() { return GetSteamAppID(); } virtual bool IsLowViolence(); /* ================= InsertServerCommand Sends text to servers execution buffer localcmd (string) ================= */ virtual void InsertServerCommand( const char *str ) { if ( !str ) { Sys_Error( "InsertServerCommand with NULL string\n" ); } if ( ValidCmd( str ) ) { Cbuf_InsertText( str ); } else { ConMsg( "Error, bad server command %s (InsertServerCommand)\n", str ); } } bool GetPlayerInfo( int ent_num, player_info_t *pinfo ) { // Entity numbers are offset by 1 from the player numbers return sv.GetPlayerInfo( (ent_num-1), pinfo ); } bool IsClientFullyAuthenticated( edict_t *pEdict ) { int entnum = NUM_FOR_EDICT( pEdict ); if (entnum < 1 || entnum > sv.GetClientCount() ) return false; // Entity numbers are offset by 1 from the player numbers CGameClient *client = sv.Client(entnum-1); if ( client ) return client->IsFullyAuthenticated(); return false; } void SetDedicatedServerBenchmarkMode( bool bBenchmarkMode ) { g_bDedicatedServerBenchmarkMode = bBenchmarkMode; if ( bBenchmarkMode ) { extern ConVar sv_stressbots; sv_stressbots.SetValue( (int)1 ); } } // Returns the SteamID of the game server const CSteamID *GetGameServerSteamID() { if ( !Steam3Server().GetGSSteamID().IsValid() ) return NULL; return &Steam3Server().GetGSSteamID(); } // Returns the SteamID of the specified player. It'll be NULL if the player hasn't authenticated yet. const CSteamID *GetClientSteamID( edict_t *pPlayerEdict ) { int entnum = NUM_FOR_EDICT( pPlayerEdict ); return GetClientSteamIDByPlayerIndex( entnum ); } const CSteamID *GetClientSteamIDByPlayerIndex( int entnum ) { if (entnum < 1 || entnum > sv.GetClientCount() ) return NULL; // Entity numbers are offset by 1 from the player numbers CGameClient *client = sv.Client(entnum-1); if ( !client ) return NULL; // Make sure they are connected and Steam ID is valid if ( !client->IsConnected() || !client->m_SteamID.IsValid() ) return NULL; return &client->m_SteamID; } void SetGamestatsData( CGamestatsData *pGamestatsData ) { g_pGamestatsData = pGamestatsData; } CGamestatsData *GetGamestatsData() { return g_pGamestatsData; } virtual IReplaySystem *GetReplay() { return g_pReplay; } virtual int GetClusterCount() { CCollisionBSPData *pBSPData = GetCollisionBSPData(); if ( pBSPData && pBSPData->map_vis ) return pBSPData->map_vis->numclusters; return 0; } virtual int GetAllClusterBounds( bbox_t *pBBoxList, int maxBBox ) { CCollisionBSPData *pBSPData = GetCollisionBSPData(); if ( pBSPData && pBSPData->map_vis && host_state.worldbrush ) { // clamp to max clusters in the map if ( maxBBox > pBSPData->map_vis->numclusters ) { maxBBox = pBSPData->map_vis->numclusters; } // reset all of the bboxes for ( int i = 0; i < maxBBox; i++ ) { ClearBounds( pBBoxList[i].mins, pBBoxList[i].maxs ); } // add each leaf's bounds to the bounds for that cluster for ( int i = 0; i < host_state.worldbrush->numleafs; i++ ) { mleaf_t *pLeaf = &host_state.worldbrush->leafs[i]; // skip solid leaves and leaves with cluster < 0 if ( !(pLeaf->contents & CONTENTS_SOLID) && pLeaf->cluster >= 0 && pLeaf->cluster < maxBBox ) { Vector mins, maxs; mins = pLeaf->m_vecCenter - pLeaf->m_vecHalfDiagonal; maxs = pLeaf->m_vecCenter + pLeaf->m_vecHalfDiagonal; AddPointToBounds( mins, pBBoxList[pLeaf->cluster].mins, pBBoxList[pLeaf->cluster].maxs ); AddPointToBounds( maxs, pBBoxList[pLeaf->cluster].mins, pBBoxList[pLeaf->cluster].maxs ); } } return pBSPData->map_vis->numclusters; } return 0; } virtual int GetServerVersion() const OVERRIDE { return GetSteamInfIDVersionInfo().ServerVersion; } virtual float GetServerTime() const OVERRIDE { return sv.GetTime(); } virtual IServer *GetIServer() OVERRIDE { return (IServer *)&sv; } virtual void SetPausedForced( bool bPaused, float flDuration /*= -1.f*/ ) OVERRIDE { sv.SetPausedForced( bPaused, flDuration ); } private: // Purpose: Sends a temp entity to the client ( follows the format of the original MESSAGE_BEGIN stuff from HL1 virtual void PlaybackTempEntity( IRecipientFilter& filter, float delay, const void *pSender, const SendTable *pST, int classID ); virtual int CheckAreasConnected( int area1, int area2 ); virtual int GetArea( const Vector& origin ); virtual void GetAreaBits( int area, unsigned char *bits, int buflen ); virtual bool GetAreaPortalPlane( Vector const &vViewOrigin, int portalKey, VPlane *pPlane ); virtual client_textmessage_t *TextMessageGet( const char *pName ); virtual void LogPrint(const char * msg); virtual bool LoadGameState( char const *pMapName, bool createPlayers ); virtual void LoadAdjacentEnts( const char *pOldLevel, const char *pLandmarkName ); virtual void ClearSaveDir(); virtual void ClearSaveDirAfterClientLoad(); virtual const char* GetMapEntitiesString(); virtual void BuildEntityClusterList( edict_t *pEdict, PVSInfo_t *pPVSInfo ); virtual void CleanUpEntityClusterList( PVSInfo_t *pPVSInfo ); virtual void SolidMoved( edict_t *pSolidEnt, ICollideable *pSolidCollide, const Vector* pPrevAbsOrigin, bool accurateBboxTriggerChecks ); virtual void TriggerMoved( edict_t *pTriggerEnt, bool accurateBboxTriggerChecks ); virtual ISpatialPartition *CreateSpatialPartition( const Vector& worldmin, const Vector& worldmax ) { return ::CreateSpatialPartition( worldmin, worldmax ); } virtual void DestroySpatialPartition( ISpatialPartition *pPartition ) { ::DestroySpatialPartition( pPartition ); } }; // Backwards-compat shim that inherits newest then provides overrides for the legacy behavior class CVEngineServer22 : public CVEngineServer { virtual int IsMapValid( const char *filename ) OVERRIDE { // For users of the older interface, preserve here the old modelloader behavior of wrapping maps/%.bsp around // the filename. This went away in newer interfaces since maps can now live in other places. char szWrappedName[MAX_PATH] = { 0 }; V_snprintf( szWrappedName, sizeof( szWrappedName ), "maps/%s.bsp", filename ); return modelloader->Map_IsValid( szWrappedName ); } }; //----------------------------------------------------------------------------- // Expose CVEngineServer to the game DLL. //----------------------------------------------------------------------------- static CVEngineServer g_VEngineServer; static CVEngineServer22 g_VEngineServer22; // INTERFACEVERSION_VENGINESERVER_VERSION_21 is compatible with 22 latest since we only added virtuals to the end, so expose that as well. EXPOSE_SINGLE_INTERFACE_GLOBALVAR( CVEngineServer, IVEngineServer021, INTERFACEVERSION_VENGINESERVER_VERSION_21, g_VEngineServer22 ); EXPOSE_SINGLE_INTERFACE_GLOBALVAR( CVEngineServer, IVEngineServer022, INTERFACEVERSION_VENGINESERVER_VERSION_22, g_VEngineServer22 ); EXPOSE_SINGLE_INTERFACE_GLOBALVAR( CVEngineServer, IVEngineServer, INTERFACEVERSION_VENGINESERVER, g_VEngineServer ); // 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_VENGINESERVER_INT == 23 ); //----------------------------------------------------------------------------- // Expose CVEngineServer to the engine. //----------------------------------------------------------------------------- IVEngineServer *g_pVEngineServer = &g_VEngineServer; //----------------------------------------------------------------------------- // Used to allocate pvs infos //----------------------------------------------------------------------------- static CUtlMemoryPool s_PVSInfoAllocator( 128, 128 * 64, CUtlMemoryPool::GROW_SLOW, "pvsinfopool", 128 ); //----------------------------------------------------------------------------- // Purpose: Sends a temp entity to the client ( follows the format of the original MESSAGE_BEGIN stuff from HL1 // Input : msg_dest - // delay - // *origin - // *recipient - // *pSender - // *pST - // classID - //----------------------------------------------------------------------------- void CVEngineServer::PlaybackTempEntity( IRecipientFilter& filter, float delay, const void *pSender, const SendTable *pST, int classID ) { VPROF( "PlaybackTempEntity" ); // don't add more events to a snapshot than a client can receive if ( sv.m_TempEntities.Count() >= ((1<filter.AddPlayersFromFilter( &filter ); newEvent->classID = classID; newEvent->pSendTable= pST; newEvent->fire_delay= delay; newEvent->bits = buffer.GetNumBitsWritten(); int size = Bits2Bytes( buffer.GetNumBitsWritten() ); newEvent->pData = new byte[size]; Q_memcpy( newEvent->pData, data, size ); // add to list sv.m_TempEntities[sv.m_TempEntities.AddToTail()] = newEvent; } int CVEngineServer::CheckAreasConnected( int area1, int area2 ) { return CM_AreasConnected(area1, area2); } //----------------------------------------------------------------------------- // Purpose: // Input : *origin - // *bits - // Output : void //----------------------------------------------------------------------------- int CVEngineServer::GetArea( const Vector& origin ) { return CM_LeafArea( CM_PointLeafnum( origin ) ); } void CVEngineServer::GetAreaBits( int area, unsigned char *bits, int buflen ) { CM_WriteAreaBits( bits, buflen, area ); } bool CVEngineServer::GetAreaPortalPlane( Vector const &vViewOrigin, int portalKey, VPlane *pPlane ) { return CM_GetAreaPortalPlane( vViewOrigin, portalKey, pPlane ); } client_textmessage_t *CVEngineServer::TextMessageGet( const char *pName ) { return ::TextMessageGet( pName ); } void CVEngineServer::LogPrint(const char * msg) { g_Log.Print( msg ); } // HACKHACK: Save/restore wrapper - Move this to a different interface bool CVEngineServer::LoadGameState( char const *pMapName, bool createPlayers ) { #ifndef SWDS return saverestore->LoadGameState( pMapName, createPlayers ) != 0; #else return 0; #endif } void CVEngineServer::LoadAdjacentEnts( const char *pOldLevel, const char *pLandmarkName ) { #ifndef SWDS saverestore->LoadAdjacentEnts( pOldLevel, pLandmarkName ); #endif } void CVEngineServer::ClearSaveDir() { #ifndef SWDS saverestore->ClearSaveDir(); #endif } void CVEngineServer::ClearSaveDirAfterClientLoad() { #ifndef SWDS saverestore->RequestClearSaveDir(); #endif } const char* CVEngineServer::GetMapEntitiesString() { return CM_EntityString(); } //----------------------------------------------------------------------------- // Builds PVS information for an entity //----------------------------------------------------------------------------- inline bool SortClusterLessFunc( const int &left, const int &right ) { return left < right; } void CVEngineServer::BuildEntityClusterList( edict_t *pEdict, PVSInfo_t *pPVSInfo ) { int i, j; int topnode; int leafCount; int leafs[MAX_TOTAL_ENT_LEAFS], clusters[MAX_TOTAL_ENT_LEAFS]; int area; CleanUpEntityClusterList( pPVSInfo ); pPVSInfo->m_pClusters = 0; pPVSInfo->m_nClusterCount = 0; pPVSInfo->m_nAreaNum = 0; pPVSInfo->m_nAreaNum2 = 0; if ( !pEdict ) return; ICollideable *pCollideable = pEdict->GetCollideable(); Assert( pCollideable ); if ( !pCollideable ) return; topnode = -1; //get all leafs, including solids Vector vecWorldMins, vecWorldMaxs; pCollideable->WorldSpaceSurroundingBounds( &vecWorldMins, &vecWorldMaxs ); leafCount = CM_BoxLeafnums( vecWorldMins, vecWorldMaxs, leafs, MAX_TOTAL_ENT_LEAFS, &topnode ); // set areas for ( i = 0; i < leafCount; i++ ) { clusters[i] = CM_LeafCluster( leafs[i] ); area = CM_LeafArea( leafs[i] ); if ( area == 0 ) continue; // doors may legally straggle two areas, // but nothing should ever need more than that if ( pPVSInfo->m_nAreaNum && pPVSInfo->m_nAreaNum != area ) { if ( pPVSInfo->m_nAreaNum2 && pPVSInfo->m_nAreaNum2 != area && sv.IsLoading() ) { ConDMsg ("Object touching 3 areas at %f %f %f\n", vecWorldMins[0], vecWorldMins[1], vecWorldMins[2]); } pPVSInfo->m_nAreaNum2 = area; } else { pPVSInfo->m_nAreaNum = area; } } Vector center = (vecWorldMins+vecWorldMaxs) * 0.5f; // calc center pPVSInfo->m_nHeadNode = topnode; // save headnode // save origin pPVSInfo->m_vCenter[0] = center[0]; pPVSInfo->m_vCenter[1] = center[1]; pPVSInfo->m_vCenter[2] = center[2]; if ( leafCount >= MAX_TOTAL_ENT_LEAFS ) { // assume we missed some leafs, and mark by headnode pPVSInfo->m_nClusterCount = -1; return; } pPVSInfo->m_pClusters = pPVSInfo->m_pClustersInline; if ( leafCount >= 16 ) { std::make_heap( clusters, clusters + leafCount, SortClusterLessFunc ); std::sort_heap( clusters, clusters + leafCount, SortClusterLessFunc ); for ( i = 0; i < leafCount; i++ ) { if ( clusters[i] == -1 ) continue; // not a visible leaf if ( ( i > 0 ) && ( clusters[i] == clusters[i-1] ) ) continue; if ( pPVSInfo->m_nClusterCount == MAX_FAST_ENT_CLUSTERS ) { unsigned short *pClusters = (unsigned short *)s_PVSInfoAllocator.Alloc(); memcpy( pClusters, pPVSInfo->m_pClusters, MAX_FAST_ENT_CLUSTERS * sizeof(unsigned short) ); pPVSInfo->m_pClusters = pClusters; } else if ( pPVSInfo->m_nClusterCount == MAX_ENT_CLUSTERS ) { // assume we missed some leafs, and mark by headnode s_PVSInfoAllocator.Free( pPVSInfo->m_pClusters ); pPVSInfo->m_pClusters = 0; pPVSInfo->m_nClusterCount = -1; break; } pPVSInfo->m_pClusters[pPVSInfo->m_nClusterCount++] = (short)clusters[i]; } return; } for ( i = 0; i < leafCount; i++ ) { if ( clusters[i] == -1 ) continue; // not a visible leaf for ( j = 0; j < i; j++ ) { if ( clusters[j] == clusters[i] ) break; } if ( j != i ) continue; if ( pPVSInfo->m_nClusterCount == MAX_FAST_ENT_CLUSTERS ) { unsigned short *pClusters = (unsigned short*)s_PVSInfoAllocator.Alloc(); memcpy( pClusters, pPVSInfo->m_pClusters, MAX_FAST_ENT_CLUSTERS * sizeof(unsigned short) ); pPVSInfo->m_pClusters = pClusters; } else if ( pPVSInfo->m_nClusterCount == MAX_ENT_CLUSTERS ) { // assume we missed some leafs, and mark by headnode s_PVSInfoAllocator.Free( pPVSInfo->m_pClusters ); pPVSInfo->m_pClusters = 0; pPVSInfo->m_nClusterCount = -1; break; } pPVSInfo->m_pClusters[pPVSInfo->m_nClusterCount++] = (short)clusters[i]; } } //----------------------------------------------------------------------------- // Cleans up the cluster list //----------------------------------------------------------------------------- void CVEngineServer::CleanUpEntityClusterList( PVSInfo_t *pPVSInfo ) { if ( pPVSInfo->m_nClusterCount > MAX_FAST_ENT_CLUSTERS ) { s_PVSInfoAllocator.Free( pPVSInfo->m_pClusters ); pPVSInfo->m_pClusters = 0; pPVSInfo->m_nClusterCount = 0; } } //----------------------------------------------------------------------------- // Adds a handle to the list of entities to update when a partition query occurs //----------------------------------------------------------------------------- void CVEngineServer::SolidMoved( edict_t *pSolidEnt, ICollideable *pSolidCollide, const Vector* pPrevAbsOrigin, bool accurateBboxTriggerChecks ) { SV_SolidMoved( pSolidEnt, pSolidCollide, pPrevAbsOrigin, accurateBboxTriggerChecks ); } void CVEngineServer::TriggerMoved( edict_t *pTriggerEnt, bool accurateBboxTriggerChecks ) { SV_TriggerMoved( pTriggerEnt, accurateBboxTriggerChecks ); } //----------------------------------------------------------------------------- // Called by the server to determine violence settings. //----------------------------------------------------------------------------- bool CVEngineServer::IsLowViolence() { return g_bLowViolence; }