diff options
| author | FluorescentCIAAfricanAmerican <[email protected]> | 2020-04-22 12:56:21 -0400 |
|---|---|---|
| committer | FluorescentCIAAfricanAmerican <[email protected]> | 2020-04-22 12:56:21 -0400 |
| commit | 3bf9df6b2785fa6d951086978a3e66f49427166a (patch) | |
| tree | 2c0f1f0c63c4832882bc93814ebd2c2b1c6224e5 /engine/vengineserver_impl.cpp | |
| download | archived-source-engine-2018-hl2-src-master.tar.xz archived-source-engine-2018-hl2-src-master.zip | |
Diffstat (limited to 'engine/vengineserver_impl.cpp')
| -rw-r--r-- | engine/vengineserver_impl.cpp | 2103 |
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; +} |