summaryrefslogtreecommitdiff
path: root/engine/vengineserver_impl.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'engine/vengineserver_impl.cpp')
-rw-r--r--engine/vengineserver_impl.cpp2103
1 files changed, 2103 insertions, 0 deletions
diff --git a/engine/vengineserver_impl.cpp b/engine/vengineserver_impl.cpp
new file mode 100644
index 0000000..c10bc80
--- /dev/null
+++ b/engine/vengineserver_impl.cpp
@@ -0,0 +1,2103 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $Workfile: $
+// $NoKeywords: $
+//===========================================================================//
+#include "server_pch.h"
+#include "tier0/valve_minmax_off.h"
+#include <algorithm>
+#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<CGameClient*>( 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<MAX_EDICTS>* 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<<CEventInfo::EVENT_INDEX_BITS)-1) )
+ {
+ // remove oldest effect
+ delete sv.m_TempEntities[0];
+ sv.m_TempEntities.Remove( 0 );
+ }
+
+ // Make this start at 1
+ classID = classID + 1;
+
+ // Encode now!
+ ALIGN4 unsigned char data[ CEventInfo::MAX_EVENT_DATA ] ALIGN4_POST;
+ bf_write buffer( "PlaybackTempEntity", data, sizeof(data) );
+
+ // write all properties, if init or reliable message delta against zero values
+ if( !SendTable_Encode( pST, pSender, &buffer, classID, NULL, false ) )
+ {
+ Host_Error( "PlaybackTempEntity: SendTable_Encode returned false (ent %d), overflow? %i\n", classID, buffer.IsOverflowed() ? 1 : 0 );
+ return;
+ }
+
+ // create CEventInfo:
+ CEventInfo *newEvent = new CEventInfo;
+
+ //copy client filter
+ newEvent->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;
+}