You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
2548 lines
65 KiB
2548 lines
65 KiB
//===== Copyright 1996-2005, Valve Corporation, All rights reserved. ======//
|
|
//
|
|
// Purpose:
|
|
//
|
|
// $Workfile: $
|
|
// $NoKeywords: $
|
|
//===========================================================================//
|
|
#include "server_pch.h"
|
|
#include <algorithm>
|
|
#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 "sv_plugin.h"
|
|
#include "singleplayersharedmemory.h"
|
|
#include "MapReslistGenerator.h"
|
|
#include "sv_steamauth.h"
|
|
#include "LoadScreenUpdate.h"
|
|
#include "paint.h"
|
|
#include "matchmaking/imatchframework.h"
|
|
#include "r_decal.h"
|
|
#include "igame.h"
|
|
#include "sv_packedentities.h"
|
|
#include "hltvserver.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, CPlayerBitVec& playerbits );
|
|
|
|
void DecalRemove( const model_t *model, const Vector& origin, float radius );
|
|
|
|
extern CNetworkStringTableContainer *networkStringTableContainerServer;
|
|
|
|
extern ConVar host_timescale;
|
|
|
|
CSharedEdictChangeInfo g_SharedEdictChangeInfo;
|
|
CSharedEdictChangeInfo *g_pSharedChangeInfo = &g_SharedEdictChangeInfo;
|
|
IAchievementMgr *g_pAchievementMgr = NULL;
|
|
CGamestatsData *g_pGamestatsData = NULL;
|
|
|
|
static ConVar sv_show_usermessage( "sv_show_usermessage", "0", 0, "Shows the user messages that the server is sending to clients. Setting this to 2 will show the contents of the message");
|
|
|
|
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++ )
|
|
{
|
|
//if the edict has any changes, force a full state change (which will release the change info)
|
|
if( sv.edicts[i].HasStateChanged() )
|
|
sv.edicts[i].StateChanged();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
g_SharedEdictChangeInfo.m_iSerialNumber++;
|
|
}
|
|
|
|
g_SharedEdictChangeInfo.m_nChangeInfos = 0;
|
|
}
|
|
|
|
|
|
// ---------------------------------------------------------------------- //
|
|
// Globals.
|
|
// ---------------------------------------------------------------------- //
|
|
|
|
struct MsgData
|
|
{
|
|
MsgData()
|
|
{
|
|
Reset();
|
|
|
|
// link buffers to messages
|
|
m_DataOut.StartWriting( entitydata, sizeof(entitydata) );
|
|
m_DataOut.SetDebugName( "s_MsgData.entityMsg.m_DataOut" );
|
|
}
|
|
|
|
void Reset()
|
|
{
|
|
filter = NULL;
|
|
reliable = false;
|
|
started = false;
|
|
entityMsg.Clear();
|
|
}
|
|
|
|
byte entitydata[ PAD_NUMBER( MAX_ENTITY_MSG_DATA, 4 ) ]; // buffer for outgoing entity messages
|
|
|
|
IRecipientFilter *filter; // clients who get this message
|
|
bool reliable;
|
|
|
|
bool started; // IS THERE A MESSAGE IN THE PROCESS OF BEING SENT?
|
|
|
|
CSVCMsg_EntityMsg_t entityMsg;
|
|
bf_write m_DataOut;
|
|
|
|
|
|
};
|
|
|
|
static MsgData s_MsgData;
|
|
|
|
void SeedRandomNumberGenerator( bool random_invariant )
|
|
{
|
|
if (!random_invariant)
|
|
{
|
|
int iSeed = -(long)Sys_FloatTime();
|
|
if (1000 < iSeed)
|
|
{
|
|
iSeed = -iSeed;
|
|
}
|
|
else if (-1000 < iSeed)
|
|
{
|
|
iSeed -= 22261048;
|
|
}
|
|
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;
|
|
}
|
|
|
|
//
|
|
// HLTV relay proxy whitelist
|
|
//
|
|
struct HltvRelayProxyWhitelistMask_t
|
|
{
|
|
uint32 a, b, c, d;
|
|
uint32 numbits;
|
|
|
|
bool operator==( HltvRelayProxyWhitelistMask_t const &other ) const
|
|
{
|
|
return 0 == Q_memcmp( this, &other, sizeof( *this ) );
|
|
}
|
|
};
|
|
static CUtlVector< HltvRelayProxyWhitelistMask_t > s_arrHltvRelayProxyWhitelist;
|
|
|
|
bool IsHltvRelayProxyWhitelisted( ns_address const &adr )
|
|
{
|
|
if ( !adr.IsType<netadr_t>() )
|
|
return false;
|
|
|
|
uint32 uiServerIP = adr.AsType<netadr_t>().GetIPHostByteOrder();
|
|
#define MAKE_IP_MASK( a, b, c, d, numbits ) ( uint32( ( (uint32(a)&0xFF) << 24 ) | ( (uint32(b)&0xFF) << 16 ) | ( (uint32(c)&0xFF) << 8 ) | ( (uint32(d)&0xFF) ) ) & ( (~0u) << (32-numbits) ) )
|
|
#define IPRANGEENTRY( a, b, c, d, numbits ) if ( ( uiServerIP & ( (~0u) << (32-numbits) ) ) == MAKE_IP_MASK( a, b, c, d, numbits ) ) return true;
|
|
FOR_EACH_VEC( s_arrHltvRelayProxyWhitelist, idx )
|
|
{
|
|
HltvRelayProxyWhitelistMask_t const &e = s_arrHltvRelayProxyWhitelist[idx];
|
|
IPRANGEENTRY( e.a, e.b, e.c, e.d, e.numbits );
|
|
}
|
|
#undef IPRANGEENTRY
|
|
#undef MAKE_IP_MASK
|
|
return false;
|
|
}
|
|
|
|
// ---------------------------------------------------------------------- //
|
|
// CVEngineServer
|
|
// ---------------------------------------------------------------------- //
|
|
class CVEngineServer : public IVEngineServer
|
|
{
|
|
public:
|
|
|
|
virtual void ChangeLevel( const char* s1, const char* s2)
|
|
{
|
|
static int last_spawncount;
|
|
|
|
// make sure we don't issue two changelevels
|
|
if (sv.GetSpawnCount() == last_spawncount)
|
|
return;
|
|
|
|
last_spawncount = sv.GetSpawnCount();
|
|
|
|
if ( !s1 )
|
|
{
|
|
Sys_Error( "CVEngineServer::Changelevel with NULL s1\n" );
|
|
}
|
|
|
|
char cmd[ 256 ];
|
|
|
|
if ( !s2 ) // no indication of where they are coming from; so just do a standard old changelevel
|
|
{
|
|
Q_snprintf( cmd, sizeof( cmd ), "changelevel %s\n", s1 );
|
|
}
|
|
else
|
|
{
|
|
Q_snprintf( cmd, sizeof( cmd ), "changelevel2 %s %s\n", s1, s2 );
|
|
}
|
|
|
|
Cbuf_AddText( CBUF_SERVER, cmd );
|
|
}
|
|
|
|
|
|
virtual int IsMapValid( const char *filename )
|
|
{
|
|
return modelloader->Map_IsValid( filename );
|
|
}
|
|
|
|
virtual bool IsDedicatedServer( void )
|
|
{
|
|
return sv.IsDedicated();
|
|
}
|
|
|
|
virtual int GetLocalClientIndex( void )
|
|
{
|
|
#ifndef DEDICATED
|
|
if ( sv.IsDedicated() )
|
|
{
|
|
return 0; // this query really makes no sense on a dedicated server, but we can agree that "local client" means "the person typing at the text console"
|
|
}
|
|
else
|
|
{
|
|
// return splitscreen->IsLocalPlayerResolvable() ? GetLocalClient().m_nPlayerSlot : GetBaseLocalClient().m_nPlayerSlot; // this is the same check as in Cmd_ExecuteCommand()
|
|
return GetBaseLocalClient().m_nPlayerSlot + 1; // Conver 0-based slot to 1-based client index. Look at UTIL_GetCommandClientIndex() usage for reference.
|
|
}
|
|
#else
|
|
return 0;
|
|
#endif
|
|
}
|
|
|
|
virtual int IsInEditMode( void )
|
|
{
|
|
#ifdef DEDICATED
|
|
return false;
|
|
#else
|
|
return g_bInEditMode;
|
|
#endif
|
|
}
|
|
|
|
virtual int IsInCommentaryMode( void )
|
|
{
|
|
#ifdef DEDICATED
|
|
return false;
|
|
#else
|
|
return g_bInCommentaryMode;
|
|
#endif
|
|
}
|
|
|
|
virtual KeyValues* GetLaunchOptions( void )
|
|
{
|
|
return g_pLaunchOptions;
|
|
}
|
|
|
|
virtual bool IsLevelMainMenuBackground( void )
|
|
{
|
|
return sv.IsLevelMainMenuBackground();
|
|
}
|
|
|
|
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 )
|
|
{
|
|
PR_CheckEmptyString( s );
|
|
SV_ForceExactFile( 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 *cl = sv.Client(i);
|
|
|
|
if ( cl->edict == e )
|
|
{
|
|
return cl->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 *cl = sv.Client(i);
|
|
|
|
if ( cl->edict == e )
|
|
{
|
|
return cl->GetNetworkIDString();
|
|
}
|
|
}
|
|
|
|
// Couldn't find it
|
|
return NULL;
|
|
|
|
}
|
|
|
|
virtual bool IsUserIDInUse( int userID )
|
|
{
|
|
if ( !sv.IsActive() )
|
|
return false;
|
|
|
|
for ( int i = 0; i < sv.GetClientCount(); i++ )
|
|
{
|
|
CGameClient *cl = sv.Client(i);
|
|
|
|
if ( cl->GetUserID() == userID )
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
|
|
// Couldn't find it
|
|
return false;
|
|
}
|
|
|
|
|
|
virtual int GetLoadingProgressForUserID( int userID )
|
|
{
|
|
if ( !sv.IsActive() )
|
|
return false;
|
|
|
|
for ( int i = 0; i < sv.GetClientCount(); i++ )
|
|
{
|
|
CGameClient *cl = sv.Client(i);
|
|
|
|
if ( cl->GetUserID() == userID )
|
|
{
|
|
return cl->m_nLoadingProgress;
|
|
}
|
|
}
|
|
|
|
// Couldn't find it
|
|
return -1;
|
|
}
|
|
|
|
virtual int GetEntityCount( void )
|
|
{
|
|
return sv.num_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 DEDICATED
|
|
return ::SaveAllocMemory(num, size);
|
|
#else
|
|
return NULL;
|
|
#endif
|
|
}
|
|
|
|
virtual void SaveFreeMemory( void *pSaveMem )
|
|
{
|
|
#ifndef DEDICATED
|
|
::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 >= (unsigned int)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 )
|
|
{
|
|
CSVCMsg_Sounds_t sndmsg;
|
|
|
|
sndmsg.set_reliable_sound( true );
|
|
sound.WriteDelta( NULL, sndmsg, sv.GetFinalTickTime() );
|
|
|
|
// 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);
|
|
|
|
CNETMsg_StringCmd_t 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( CBUF_SERVER, 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;
|
|
}
|
|
|
|
CNETMsg_StringCmd_t 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;
|
|
|
|
// Ensure the contract of deleting the key values here
|
|
KeyValues::AutoDelete autodelete_pCommand( pCommand );
|
|
|
|
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;
|
|
}
|
|
|
|
CSVCMsg_CmdKeyValues_t cmd;
|
|
CmdKeyValuesHelper::SVCMsg_SetKeyValues( 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 )
|
|
{
|
|
CSVCMsg_BSPDecal_t decal;
|
|
|
|
decal.mutable_pos()->set_x( origin.x );
|
|
decal.mutable_pos()->set_y( origin.y );
|
|
decal.mutable_pos()->set_z( origin.z );
|
|
decal.set_decal_texture_index( decalIndex );
|
|
decal.set_entity_index( entityIndex );
|
|
decal.set_model_index( modelIndex );
|
|
decal.set_low_priority( lowpriority );
|
|
|
|
if ( sv.allowsignonwrites )
|
|
{
|
|
decal.WriteToBuffer( sv.m_Signon );
|
|
}
|
|
else
|
|
{
|
|
sv.BroadcastMessage( decal, false, true );
|
|
}
|
|
}
|
|
|
|
void Message_DetermineMulticastRecipients( bool usepas, const Vector& origin, CPlayerBitVec& 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.entityMsg.set_ent_index( ent_index );
|
|
s_MsgData.entityMsg.set_class_id( ent_class->m_ClassID );
|
|
s_MsgData.m_DataOut.Reset();
|
|
|
|
return &s_MsgData.m_DataOut;
|
|
}
|
|
|
|
// Validates user message type and checks to see if it's variable length
|
|
// returns true if variable length
|
|
int Message_CheckMessageLength()
|
|
{
|
|
int bytesWritten = s_MsgData.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.ent_index(), bytesWritten, MAX_ENTITY_MSG_DATA );
|
|
return -1;
|
|
}
|
|
|
|
return bytesWritten; // all checks passed, estimated final length
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
s_MsgData.entityMsg.set_ent_data( s_MsgData.entitydata, s_MsgData.m_DataOut.GetNumBytesWritten() );
|
|
|
|
if ( s_MsgData.filter )
|
|
{
|
|
// send entity/user messages only to full connected clients in filter
|
|
sv.BroadcastMessage( s_MsgData.entityMsg, *s_MsgData.filter );
|
|
}
|
|
else
|
|
{
|
|
// send entity messages to all full connected clients
|
|
sv.BroadcastMessage( s_MsgData.entityMsg, true, s_MsgData.reliable );
|
|
}
|
|
|
|
s_MsgData.Reset(); // clear message data
|
|
}
|
|
|
|
virtual void SendUserMessage( IRecipientFilter& filter, int message, const ::google::protobuf::Message &msg )
|
|
{
|
|
CSVCMsg_UserMessage_t _userMsg;
|
|
|
|
if ( !msg.IsInitialized() )
|
|
{
|
|
Msg("SendUserMessage %s(%d) is not initialized! Probably missing required fields!\n", msg.GetTypeName().c_str(), message );
|
|
}
|
|
|
|
int size = msg.ByteSize();
|
|
|
|
if ( sv_show_usermessage.GetBool() )
|
|
{
|
|
Msg("SendUserMessage - %s(%d) bytes: %d\n", msg.GetTypeName().c_str(), message, size );
|
|
if( sv_show_usermessage.GetInt() > 1 )
|
|
Msg("%s", msg.DebugString().c_str() );
|
|
}
|
|
|
|
_userMsg.set_msg_type( message );
|
|
_userMsg.mutable_msg_data()->resize( size );
|
|
if ( !msg.SerializeWithCachedSizesToArray( (uint8*)&(*_userMsg.mutable_msg_data())[0] ) )
|
|
{
|
|
Msg( "SendUserMessage: Error serializing %s!\n", msg.GetTypeName().c_str() );
|
|
return;
|
|
}
|
|
|
|
sv.BroadcastMessage( _userMsg, filter );
|
|
}
|
|
|
|
/* 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 DEDICATED
|
|
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;
|
|
|
|
CSVCMsg_SetView_t view;
|
|
view.set_entity_index( NUM_FOR_EDICT(viewent) );
|
|
|
|
client->SendNetMsg( view );
|
|
}
|
|
|
|
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;
|
|
|
|
CSVCMsg_CrosshairAngle_t crossHairMsg;
|
|
|
|
crossHairMsg.mutable_angle()->set_x( pitch );
|
|
crossHairMsg.mutable_angle()->set_y( yaw );
|
|
crossHairMsg.mutable_angle()->set_z( 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;
|
|
}
|
|
|
|
fcl->UpdateUserSettings();
|
|
|
|
return fcl->edict;
|
|
|
|
}
|
|
|
|
// 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 float GetTimescale( void ) const
|
|
{
|
|
extern float CL_GetHltvReplayTimeScale();
|
|
return sv.GetTimescale() * host_timescale.GetFloat() * CL_GetHltvReplayTimeScale();
|
|
}
|
|
|
|
virtual void SetFakeClientConVarValue( edict_t *pEntity, const char *cvar, 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( cvar, value );
|
|
client->m_bConVarsChanged = true;
|
|
}
|
|
}
|
|
|
|
virtual CSharedEdictChangeInfo* GetSharedEdictChangeInfo()
|
|
{
|
|
return &g_SharedEdictChangeInfo;
|
|
}
|
|
|
|
virtual IChangeInfoAccessor *GetChangeAccessor( const edict_t *pEdict )
|
|
{
|
|
extern int NUM_FOR_EDICTINFO( const edict_t * e );
|
|
|
|
int idx = NUM_FOR_EDICTINFO( pEdict );
|
|
return &sv.edictchangeinfo[ idx ];
|
|
}
|
|
|
|
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( DEDICATED )
|
|
return saverestore->GetMostRecentlyLoadedFileName();
|
|
#else
|
|
return "";
|
|
#endif
|
|
}
|
|
|
|
virtual char const *GetSaveFileName()
|
|
{
|
|
#if !defined( DEDICATED )
|
|
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 SetAchievementMgr( IAchievementMgr *pAchievementMgr )
|
|
{
|
|
g_pAchievementMgr = pAchievementMgr;
|
|
}
|
|
|
|
virtual IAchievementMgr *GetAchievementMgr()
|
|
{
|
|
return g_pAchievementMgr;
|
|
}
|
|
|
|
virtual int GetAppID()
|
|
{
|
|
return GetSteamAppID();
|
|
}
|
|
|
|
virtual bool IsLowViolence();
|
|
|
|
virtual bool IsAnyClientLowViolence();
|
|
|
|
/*
|
|
=================
|
|
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( CBUF_SERVER, 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;
|
|
}
|
|
|
|
virtual ISPSharedMemory *GetSinglePlayerSharedMemorySpace( const char *szName, int ent_num = MAX_EDICTS )
|
|
{
|
|
return g_pSinglePlayerSharedMemoryManager->GetSharedMemory( szName, ent_num );
|
|
}
|
|
|
|
virtual void *AllocLevelStaticData( size_t bytes )
|
|
{
|
|
return Hunk_AllocName( bytes, "AllocLevelStaticData", false );
|
|
}
|
|
bool IsSplitScreenPlayer( int entnum )
|
|
{
|
|
if (entnum < 1 || entnum > sv.GetClientCount() )
|
|
return false;
|
|
|
|
CGameClient *client = sv.Client(entnum-1);
|
|
return client->IsSplitScreenUser();
|
|
}
|
|
|
|
edict_t *GetSplitScreenPlayerAttachToEdict( int ent_num )
|
|
{
|
|
if (ent_num < 1 || ent_num > sv.GetClientCount() )
|
|
return NULL;
|
|
|
|
CGameClient *client = sv.Client(ent_num-1);
|
|
if ( !client->IsSplitScreenUser() )
|
|
return NULL;
|
|
|
|
Assert( client->m_pAttachedTo );
|
|
if ( !client->m_pAttachedTo )
|
|
return NULL;
|
|
|
|
return static_cast< CGameClient * >( client->m_pAttachedTo )->edict;
|
|
}
|
|
|
|
CrossPlayPlatform_t GetClientCrossPlayPlatform( int entnum )
|
|
{
|
|
if (entnum < 1 || entnum > sv.GetClientCount() )
|
|
return CROSSPLAYPLATFORM_UNKNOWN;
|
|
|
|
CGameClient *client = sv.Client(entnum-1);
|
|
return client->GetClientPlatform();
|
|
}
|
|
|
|
void EnsureInstanceBaseline( int ent_num )
|
|
{
|
|
edict_t *pEnt = EDICT_NUM( ent_num );
|
|
Assert ( pEnt && pEnt->GetNetworkable() );
|
|
if ( pEnt && pEnt->GetNetworkable() )
|
|
{
|
|
SerializedEntityHandle_t handle = g_pSerializedEntities->AllocateSerializedEntity(__FILE__, __LINE__);
|
|
Assert( handle != SERIALIZED_ENTITY_HANDLE_INVALID);
|
|
if ( handle != SERIALIZED_ENTITY_HANDLE_INVALID )
|
|
{
|
|
ServerClass *pServerClass = pEnt->GetNetworkable()->GetServerClass();
|
|
SV_EnsureInstanceBaseline( pServerClass, ent_num, handle );
|
|
}
|
|
g_pSerializedEntities->ReleaseSerializedEntity( handle );
|
|
}
|
|
}
|
|
|
|
// Sets server reservation payload
|
|
bool ReserveServerForQueuedGame( char const *szReservationPayload )
|
|
{
|
|
bool bResult = sv.ReserveServerForQueuedGame( szReservationPayload );
|
|
if ( bResult && szReservationPayload && ( szReservationPayload[0] != 'R' ) )
|
|
{
|
|
ServerCommand( "removeallids\n" );
|
|
}
|
|
return bResult;
|
|
}
|
|
|
|
bool GetEngineHltvInfo( CEngineHltvInfo_t &info )
|
|
{
|
|
Q_memset( &info, 0, sizeof( info ) );
|
|
CActiveHltvServerIterator hltv;
|
|
if ( !hltv )
|
|
return false; // broadcast is not active, no active GOTV[] instances
|
|
|
|
info.m_bBroadcastActive = true;
|
|
info.m_bMasterProxy = !hltv->IsDemoPlayback() && hltv->IsMasterProxy();
|
|
|
|
if ( info.m_bMasterProxy )
|
|
info.m_flDelay = hltv->GetDirector()->GetDelay();
|
|
|
|
info.m_nTvPort = hltv->GetUDPPort();
|
|
info.m_flTime = hltv->GetTime();
|
|
|
|
hltv->GetGlobalStats( info.m_numProxies, info.m_numSlots, info.m_numClients );
|
|
hltv->GetLocalStats( info.m_numLocalProxies, info.m_numLocalSlots, info.m_numLocalClients );
|
|
hltv->GetRelayStats( info.m_numRelayProxies, info.m_numRelaySlots, info.m_numRelayClients );
|
|
hltv->GetExternalStats( info.m_numExternalTotalViewers, info.m_numExternalLinkedViewers );
|
|
|
|
const netadr_t *pRelayAdr = info.m_bMasterProxy ? NULL : hltv->GetRelayAddress();
|
|
if ( pRelayAdr )
|
|
{
|
|
info.m_relayAddress = pRelayAdr->GetIPHostByteOrder();
|
|
info.m_relayPort = pRelayAdr->GetPort();
|
|
}
|
|
else
|
|
{
|
|
info.m_relayAddress = 0;
|
|
info.m_relayPort = 0;
|
|
}
|
|
|
|
// while ( hltv.Next() )
|
|
// {
|
|
// // ... reduce the information from multiple broadcasts here?
|
|
// }
|
|
|
|
return true;
|
|
}
|
|
|
|
// Add HLTV proxy whitelist to bypass password and Steam Auth checks upon connection, as CIDR a.b.c.d/numbits
|
|
void AddHltvRelayProxyWhitelist( uint32 a, uint32 b, uint32 c, uint32 d, uint32 numbits )
|
|
{
|
|
HltvRelayProxyWhitelistMask_t mask;
|
|
Q_memset( &mask, 0, sizeof( mask ) );
|
|
mask.a = a;
|
|
mask.b = b;
|
|
mask.c = c;
|
|
mask.d = d;
|
|
mask.numbits = numbits;
|
|
// Add a mask if it hasn't been added yet
|
|
if ( s_arrHltvRelayProxyWhitelist.Find( mask ) == s_arrHltvRelayProxyWhitelist.InvalidIndex() )
|
|
s_arrHltvRelayProxyWhitelist.AddToTail( mask );
|
|
}
|
|
|
|
// On master HLTV this call updates number of external viewers and which portion of those are linked with Steam
|
|
void UpdateHltvExternalViewers( uint32 numTotalViewers, uint32 numLinkedViewers )
|
|
{
|
|
for ( CHltvServerIterator hltv; hltv; hltv.Next() )
|
|
{
|
|
hltv->UpdateHltvExternalViewers( numTotalViewers, numLinkedViewers );
|
|
}
|
|
}
|
|
|
|
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();
|
|
}
|
|
|
|
|
|
int GetNumSplitScreenUsersAttachedToEdict( int ent_num )
|
|
{
|
|
if (ent_num < 1 || ent_num > sv.GetClientCount() )
|
|
return 0;
|
|
|
|
CGameClient *client = sv.Client(ent_num-1);
|
|
if ( client->IsSplitScreenUser() )
|
|
return 0;
|
|
|
|
int c = 0;
|
|
for ( int i = 1; i < host_state.max_splitscreen_players; ++i )
|
|
{
|
|
if ( client->m_SplitScreenUsers[ i ] )
|
|
++c;
|
|
}
|
|
|
|
return c;
|
|
}
|
|
|
|
edict_t *GetSplitScreenPlayerForEdict( int ent_num, int nSlot )
|
|
{
|
|
if (ent_num < 1 || ent_num > sv.GetClientCount() )
|
|
return NULL;
|
|
|
|
CGameClient *client = sv.Client(ent_num-1);
|
|
if ( client->IsSplitScreenUser() )
|
|
return NULL;
|
|
|
|
if ( nSlot <= 0 || nSlot >= host_state.max_splitscreen_players )
|
|
return NULL;
|
|
|
|
CBaseClient *cl = client->m_SplitScreenUsers[ nSlot ];
|
|
if ( !cl )
|
|
return NULL;
|
|
|
|
return (( CGameClient * )cl)->edict;
|
|
}
|
|
|
|
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 bool IsCreatingReslist()
|
|
{
|
|
return MapReslistGenerator().IsEnabled();
|
|
}
|
|
virtual bool IsCreatingXboxReslist()
|
|
{
|
|
return MapReslistGenerator().IsCreatingForXbox();
|
|
}
|
|
|
|
virtual bool IsDedicatedServerForXbox()
|
|
{
|
|
return sv.IsDedicatedForXbox();
|
|
}
|
|
|
|
virtual bool IsDedicatedServerForPS3( void )
|
|
{
|
|
return sv.IsDedicatedForPS3();
|
|
}
|
|
|
|
virtual void Pause( bool bPause, bool bForce )
|
|
{
|
|
ConVarRef sv_pausable( "sv_pausable" );
|
|
int oldValue = sv_pausable.GetInt();
|
|
if ( bForce && !oldValue )
|
|
{
|
|
sv_pausable.SetValue( 1 );
|
|
}
|
|
|
|
sv.SetPaused( bPause );
|
|
|
|
if ( bForce && !oldValue )
|
|
{
|
|
sv_pausable.SetValue( 0 );
|
|
}
|
|
}
|
|
|
|
virtual void SetTimescale( float flTimescale )
|
|
{
|
|
sv.SetTimescale( flTimescale );
|
|
}
|
|
|
|
virtual bool HasPaintmap()
|
|
{
|
|
return g_PaintManager.m_bShouldRegister;
|
|
}
|
|
|
|
virtual bool SpherePaintSurface( const model_t *pModel, const Vector& vPosition, BYTE colorIndex, float flSphereRadius, float flPaintCoatPercent )
|
|
{
|
|
return ShootPaintSphere( pModel, vPosition, colorIndex, flSphereRadius, flPaintCoatPercent );
|
|
}
|
|
|
|
virtual void SphereTracePaintSurface( const model_t *pModel, const Vector& vPosition, const Vector& vContactNormal, float flSphereRadius, CUtlVector<BYTE>& surfColors )
|
|
{
|
|
TracePaintSphere( pModel, vPosition, vContactNormal, flSphereRadius, surfColors );
|
|
}
|
|
|
|
virtual void RemoveAllPaint()
|
|
{
|
|
g_PaintManager.RemoveAllPaint();
|
|
}
|
|
|
|
virtual void PaintAllSurfaces( BYTE color )
|
|
{
|
|
g_PaintManager.PaintAllSurfaces( color );
|
|
}
|
|
|
|
virtual void GetPaintmapDataRLE( CUtlVector<uint32> &data )
|
|
{
|
|
g_PaintManager.GetPaintmapDataRLE( data );
|
|
}
|
|
|
|
virtual void LoadPaintmapDataRLE( const CUtlVector<uint32> &data )
|
|
{
|
|
g_PaintManager.LoadPaintmapDataRLE( data );
|
|
}
|
|
|
|
virtual void RemovePaint( const model_t* pModel )
|
|
{
|
|
g_PaintManager.RemovePaint( pModel );
|
|
}
|
|
|
|
virtual void SendPaintmapDataToClient( edict_t *pPlayerEdict )
|
|
{
|
|
int entnum = NUM_FOR_EDICT( pPlayerEdict );
|
|
if (entnum < 1 || entnum > sv.GetClientCount() )
|
|
return;
|
|
|
|
// Entity numbers are offset by 1 from the player numbers
|
|
CGameClient *client = sv.Client(entnum-1);
|
|
if ( !client )
|
|
return;
|
|
|
|
CUtlVector< uint32 > data;
|
|
g_PaintManager.GetPaintmapDataRLE( data );
|
|
|
|
AssertMsg2( data.Count() < NET_MAX_PAYLOAD, "Sending paint data with size [%d bytes] to client, max payload is [%d bytes]\n", data.Count(), NET_MAX_PAYLOAD );
|
|
|
|
if ( data.Count() > 0 )
|
|
{
|
|
//handle endian issue between platforms
|
|
CByteswap swap;
|
|
swap.ActivateByteSwapping( !CByteswap::IsMachineBigEndian() );
|
|
swap.SwapBufferToTargetEndian( data.Base(), data.Base(), data.Count() );
|
|
|
|
CSVCMsg_PaintmapData_t svcPaintmap;
|
|
int nBytes = data.Count() * sizeof( data.Base()[0] );
|
|
svcPaintmap.set_paintmap( (void*)data.Base(), nBytes );
|
|
|
|
client->SendNetMsg( svcPaintmap, true );
|
|
}
|
|
}
|
|
|
|
|
|
// Returns the SteamID of the specified player. It'll be NULL if the player hasn't authenticated yet.
|
|
const CSteamID *GetClientSteamID( const edict_t *pPlayerEdict, bool bRequireFullyAuthenticated )
|
|
{
|
|
int entnum = NUM_FOR_EDICT( pPlayerEdict );
|
|
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;
|
|
|
|
if ( !client->m_SteamID.IsValid() )
|
|
return NULL;
|
|
|
|
if ( bRequireFullyAuthenticated && !client->IsFullyAuthenticated() )
|
|
return NULL;
|
|
|
|
return &client->m_SteamID;
|
|
}
|
|
|
|
// Returns the XUID of the specified player. It'll be NULL if the player hasn't connected yet.
|
|
XUID GetClientXUID( edict_t *pPlayerEdict )
|
|
{
|
|
int entnum = NUM_FOR_EDICT( pPlayerEdict );
|
|
if (entnum < 1 || entnum > sv.GetClientCount() )
|
|
return 0ull;
|
|
|
|
// Entity numbers are offset by 1 from the player numbers
|
|
CGameClient *client = sv.Client(entnum-1);
|
|
if ( !client )
|
|
return 0ull;
|
|
|
|
return client->GetClientXuid();
|
|
}
|
|
|
|
void SetGamestatsData( CGamestatsData *pGamestatsData )
|
|
{
|
|
g_pGamestatsData = pGamestatsData;
|
|
}
|
|
|
|
CGamestatsData *GetGamestatsData()
|
|
{
|
|
return g_pGamestatsData;
|
|
}
|
|
|
|
void HostValidateSession()
|
|
{
|
|
extern void HostValidateSessionImpl();
|
|
HostValidateSessionImpl();
|
|
}
|
|
|
|
void RefreshScreenIfNecessary()
|
|
{
|
|
::RefreshScreenIfNecessary();
|
|
}
|
|
|
|
float GetLatencyForChoreoSounds();
|
|
|
|
int GetServerVersion() const
|
|
{
|
|
return ::GetServerVersion();
|
|
}
|
|
|
|
bool WasShutDownRequested() const
|
|
{
|
|
extern bool sv_ShutDown_WasRequested();
|
|
return sv_ShutDown_WasRequested();
|
|
}
|
|
|
|
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 IsLogEnabled();
|
|
virtual bool LoadGameState( char const *pMapName, bool createPlayers );
|
|
virtual bool IsOverrideLoadGameEntsOn();
|
|
virtual void ForceFlushEntity( int iEntity );
|
|
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 ); }
|
|
|
|
public:
|
|
|
|
virtual bool IsActiveApp()
|
|
{
|
|
return game->IsActiveApp();
|
|
}
|
|
|
|
virtual void SetNoClipEnabled( bool bEnabled )
|
|
{
|
|
extern bool g_bNoClipEnabled;
|
|
g_bNoClipEnabled = bEnabled;
|
|
}
|
|
|
|
virtual bool StartClientHltvReplay( int nClientIndex, const HltvReplayParams_t ¶ms ) OVERRIDE
|
|
{
|
|
if ( IClient *pClient = sv.GetClient( nClientIndex ) )
|
|
{
|
|
return pClient->StartHltvReplay( params );
|
|
}
|
|
return false;
|
|
}
|
|
|
|
virtual void StopClientHltvReplay( int nClientIndex ) OVERRIDE
|
|
{
|
|
if ( IClient *pClient = sv.GetClient( nClientIndex ) )
|
|
{
|
|
pClient->StopHltvReplay();
|
|
}
|
|
}
|
|
|
|
virtual int GetClientHltvReplayDelay( int nClientIndex ) OVERRIDE
|
|
{
|
|
if ( IClient *pClient = sv.GetClient( nClientIndex ) )
|
|
{
|
|
return pClient->GetHltvReplayDelay();
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
virtual bool ClientCanStartHltvReplay( int nClientIndex ) OVERRIDE
|
|
{
|
|
if ( IClient* pClient = sv.GetClient( nClientIndex ) )
|
|
{
|
|
return pClient->CanStartHltvReplay();
|
|
}
|
|
return false;
|
|
}
|
|
|
|
virtual bool HasHltvReplay( ) OVERRIDE
|
|
{
|
|
CActiveHltvServerIterator hltv;
|
|
|
|
return hltv && hltv->GetOldestTick() > 0;
|
|
}
|
|
|
|
virtual void ClientResetReplayRequestTime( int nClientIndex ) OVERRIDE
|
|
{
|
|
if ( IClient* pClient = sv.GetClient( nClientIndex ) )
|
|
{
|
|
return pClient->ResetReplayRequestTime();
|
|
}
|
|
}
|
|
|
|
virtual bool AnyClientsInHltvReplayMode() OVERRIDE
|
|
{
|
|
return sv.AnyClientsInHltvReplayMode();
|
|
}
|
|
};
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Expose CVEngineServer to the game DLL.
|
|
//-----------------------------------------------------------------------------
|
|
static CVEngineServer g_VEngineServer;
|
|
EXPOSE_SINGLE_INTERFACE_GLOBALVAR(CVEngineServer, IVEngineServer, INTERFACEVERSION_VENGINESERVER, g_VEngineServer);
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// 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, using the reliable data stream
|
|
// Input : delay -
|
|
// *pSender -
|
|
// *pST -
|
|
// classID -
|
|
//-----------------------------------------------------------------------------
|
|
static void WriteReliableEvent( const SendTable *pST, float delay, int classID, SerializedEntityHandle_t handle, IClient *client, bf_write *buf )
|
|
{
|
|
CSVCMsg_TempEntities_t eventMsg;
|
|
|
|
eventMsg.set_reliable( true );
|
|
|
|
// special case 0 signals single reliable event
|
|
eventMsg.set_num_entries( 0 );
|
|
|
|
eventMsg.mutable_entity_data()->resize( CEventInfo::MAX_EVENT_DATA );
|
|
bf_write buffer( &(*eventMsg.mutable_entity_data())[0], eventMsg.entity_data().size() );
|
|
|
|
if ( delay == 0.0f )
|
|
{
|
|
buffer.WriteOneBit( 0 ); // no delay
|
|
}
|
|
else
|
|
{
|
|
buffer.WriteOneBit( 1 );
|
|
buffer.WriteUBitLong( delay*100.0f, 16 );
|
|
}
|
|
|
|
buffer.WriteOneBit( 1 ); // full update
|
|
|
|
buffer.WriteUBitLong( classID, sv.serverclassbits ); // classID
|
|
|
|
// write event properties
|
|
SendTable_WritePropList( pST, handle, &buffer, -1, NULL );
|
|
|
|
// write message
|
|
if ( client )
|
|
{
|
|
client->SendNetMsg( eventMsg, true );
|
|
}
|
|
|
|
if ( buf )
|
|
{
|
|
eventMsg.WriteToBuffer( *buf );
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// 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;
|
|
|
|
SerializedEntityHandle_t handle = g_pSerializedEntities->AllocateSerializedEntity(__FILE__, __LINE__);
|
|
|
|
// write all properties, if init or reliable message delta against zero values
|
|
if( !SendTable_Encode( pST, handle, pSender, classID, NULL ) )
|
|
{
|
|
Host_Error( "PlaybackTempEntity: SendTable_Encode returned false (ent %d), overflow?\n", classID );
|
|
return;
|
|
}
|
|
|
|
bool bSendReliable = filter.IsReliable();
|
|
|
|
// Reliable events are sent one by one to each recipient
|
|
// Unreliable events are queued below and added to delta update packet if there is space...
|
|
if ( bSendReliable )
|
|
{
|
|
int c = filter.GetRecipientCount();
|
|
for ( int slot = 0; slot < c; slot++ )
|
|
{
|
|
int index = filter.GetRecipientIndex( slot );
|
|
if ( index < 1 || index > sv.GetClientCount() )
|
|
continue;
|
|
CGameClient *cl = sv.Client( index - 1 );
|
|
if ( ( cl->IsFakeClient() && !cl->IsHLTV() ) || !cl->IsActive() )
|
|
continue;
|
|
|
|
WriteReliableEvent( pST, delay, classID, handle, cl, NULL );
|
|
}
|
|
|
|
g_pSerializedEntities->ReleaseSerializedEntity( handle );
|
|
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->m_Packed = handle;
|
|
|
|
// 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 );
|
|
}
|
|
bool CVEngineServer::IsLogEnabled()
|
|
{
|
|
return g_Log.IsActive();
|
|
}
|
|
|
|
// HACKHACK: Save/restore wrapper - Move this to a different interface
|
|
bool CVEngineServer::LoadGameState( char const *pMapName, bool createPlayers )
|
|
{
|
|
#ifndef DEDICATED
|
|
return saverestore->LoadGameState( pMapName, createPlayers ) != 0;
|
|
#else
|
|
return 0;
|
|
#endif
|
|
}
|
|
|
|
bool CVEngineServer::IsOverrideLoadGameEntsOn()
|
|
{
|
|
#ifndef DEDICATED
|
|
return saverestore->IsOverrideLoadGameEntsOn();
|
|
#else
|
|
return false;
|
|
#endif
|
|
}
|
|
|
|
void CVEngineServer::ForceFlushEntity( int iEntity )
|
|
{
|
|
if ( g_pLocalNetworkBackdoor )
|
|
{
|
|
g_pLocalNetworkBackdoor->ForceFlushEntity( iEntity );
|
|
}
|
|
}
|
|
|
|
void CVEngineServer::LoadAdjacentEnts( const char *pOldLevel, const char *pLandmarkName )
|
|
{
|
|
#ifndef DEDICATED
|
|
saverestore->LoadAdjacentEnts( pOldLevel, pLandmarkName );
|
|
#endif
|
|
}
|
|
|
|
void CVEngineServer::ClearSaveDir()
|
|
{
|
|
#ifndef DEDICATED
|
|
saverestore->ClearSaveDir();
|
|
#endif
|
|
}
|
|
|
|
void CVEngineServer::ClearSaveDirAfterClientLoad()
|
|
{
|
|
#ifndef DEDICATED
|
|
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
|
|
bool bAreaCheck = false;
|
|
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 ( !bAreaCheck && pPVSInfo->m_nAreaNum2 && pPVSInfo->m_nAreaNum2 != area )
|
|
{
|
|
// if you are touching more than 2 areas, do a check to get the approximate best area
|
|
bAreaCheck = true;
|
|
if ( 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
|
|
|
|
if ( bAreaCheck )
|
|
{
|
|
// make sure the area of your center is being tested, otherwise just pick the first ones
|
|
// in the list
|
|
int leaf = CM_PointLeafnum( center );
|
|
int area = CM_LeafArea(leaf);
|
|
if ( pPVSInfo->m_nAreaNum != area && pPVSInfo->m_nAreaNum2 != area )
|
|
{
|
|
pPVSInfo->m_nAreaNum = area;
|
|
}
|
|
}
|
|
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;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Called by the (multiplayer) server to determine violence settings.
|
|
//-----------------------------------------------------------------------------
|
|
bool CVEngineServer::IsAnyClientLowViolence()
|
|
{
|
|
for ( int i=0; i<sv.GetClientCount(); ++i )
|
|
{
|
|
CGameClient *client = sv.Client(i);
|
|
if ( client && client->IsLowViolenceClient() )
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Called by server to delay choreo sounds accordingly to IO latency.
|
|
//-----------------------------------------------------------------------------
|
|
float CVEngineServer::GetLatencyForChoreoSounds()
|
|
{
|
|
#ifdef DEDICATED
|
|
return 0.0f;
|
|
#elif LINUX
|
|
return 0.0f;
|
|
#else
|
|
extern ConVar snd_mixahead;
|
|
extern ConVar snd_delay_for_choreo_enabled;
|
|
extern ConVar snd_delay_for_choreo_reset_after_N_milliseconds;
|
|
extern float g_fDelayForChoreo;
|
|
extern uint32 g_nDelayForChoreoLastCheckInMs;
|
|
extern int g_nDelayForChoreoNumberOfSoundsPlaying;
|
|
|
|
float fResult = snd_mixahead.GetFloat();
|
|
if ( snd_delay_for_choreo_enabled.GetBool() )
|
|
{
|
|
float fDelayForChoreo = g_fDelayForChoreo;
|
|
if ( fDelayForChoreo != 0.0f )
|
|
{
|
|
// Let's see if we have to reset the delay due to choreo (do this just before we return any useful information from scene entity).
|
|
// Note that this access is not thread safe, however there is no dire consequence here in case of race conditions.
|
|
// Delay may be reset when it should not, or delay may not be reset when it should (that case would be corrected by a subsequent call anyway).
|
|
if ( g_nDelayForChoreoNumberOfSoundsPlaying == 0 )
|
|
{
|
|
// We only reset the delay if no other sound is still behind in term of latency. Several VCDs could be running in parallel.
|
|
// As if we do it later, like when the samples are ready, we are going to hit the timeout more easily. We would then lose previously accumulated delay,
|
|
// and the sound could be potentially cut off later.
|
|
uint32 nCurrentTime = Plat_MSTime();
|
|
uint32 nLastCheck = g_nDelayForChoreoLastCheckInMs;
|
|
if ( nLastCheck + snd_delay_for_choreo_reset_after_N_milliseconds.GetInt() < nCurrentTime )
|
|
{
|
|
// Msg( "Reset delay for choreo as no choreo has been executed for the past %f seconds. Old value=%f.\n", (float)( nCurrentTime - nLastCheck ) / 1000.0f, fDelayForChoreo );
|
|
g_fDelayForChoreo = fDelayForChoreo = 0.0f;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Remove the delay to the mix-ahead, which is going to push back the latency.
|
|
fResult -= fDelayForChoreo;
|
|
}
|
|
return fResult;
|
|
#endif
|
|
}
|
|
|