5230 lines
115 KiB
5230 lines
115 KiB
/* |
|
sv_game.c - gamedll interaction |
|
Copyright (C) 2008 Uncle Mike |
|
|
|
This program is free software: you can redistribute it and/or modify |
|
it under the terms of the GNU General Public License as published by |
|
the Free Software Foundation, either version 3 of the License, or |
|
(at your option) any later version. |
|
|
|
This program is distributed in the hope that it will be useful, |
|
but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
GNU General Public License for more details. |
|
*/ |
|
|
|
#include "common.h" |
|
#include "server.h" |
|
#include "net_encode.h" |
|
#include "event_flags.h" |
|
#include "library.h" |
|
#include "pm_defs.h" |
|
#include "studio.h" |
|
#include "const.h" |
|
#include "render_api.h" // modelstate_t |
|
#include "ref_common.h" // decals |
|
|
|
#define ENTVARS_COUNT ARRAYSIZE( gEntvarsDescription ) |
|
|
|
// fatpvs stuff |
|
static byte fatpvs[MAX_MAP_LEAFS/8]; |
|
static byte fatphs[MAX_MAP_LEAFS/8]; |
|
static byte clientpvs[MAX_MAP_LEAFS/8]; // for find client in PVS |
|
static vec3_t viewPoint[MAX_CLIENTS]; |
|
|
|
// exports |
|
typedef void (__cdecl *LINK_ENTITY_FUNC)( entvars_t *pev ); |
|
typedef void (__stdcall *GIVEFNPTRSTODLL)( enginefuncs_t* engfuncs, globalvars_t *pGlobals ); |
|
|
|
edict_t *SV_EdictNum( int n ) |
|
{ |
|
if(( n >= 0 ) && ( n < GI->max_edicts )) |
|
return svgame.edicts + n; |
|
return NULL; |
|
} |
|
|
|
#ifndef NDEBUG |
|
qboolean SV_CheckEdict( const edict_t *e, const char *file, const int line ) |
|
{ |
|
int n; |
|
|
|
if( !e ) return false; // may be NULL |
|
|
|
n = ((int)((edict_t *)(e) - svgame.edicts)); |
|
|
|
if(( n >= 0 ) && ( n < GI->max_edicts )) |
|
return !e->free; |
|
Con_Printf( "bad entity %i (called at %s:%i)\n", n, file, line ); |
|
|
|
return false; |
|
} |
|
#endif |
|
|
|
static edict_t *SV_PEntityOfEntIndex( const int iEntIndex, const qboolean allentities ) |
|
{ |
|
if( iEntIndex >= 0 && iEntIndex < GI->max_edicts ) |
|
{ |
|
edict_t *pEdict = EDICT_NUM( iEntIndex ); |
|
qboolean player = allentities ? iEntIndex <= svs.maxclients : iEntIndex < svs.maxclients; |
|
|
|
if( !iEntIndex || FBitSet( host.features, ENGINE_QUAKE_COMPATIBLE )) |
|
return pEdict; // just get access to array |
|
|
|
if( SV_IsValidEdict( pEdict ) && pEdict->pvPrivateData ) |
|
return pEdict; |
|
|
|
// g-cont: world and clients can be accessed even without private data |
|
if( SV_IsValidEdict( pEdict ) && player ) |
|
return pEdict; |
|
} |
|
|
|
return NULL; |
|
} |
|
|
|
|
|
/* |
|
============= |
|
EntvarsDescription |
|
|
|
entavrs table for FindEntityByString |
|
============= |
|
*/ |
|
static TYPEDESCRIPTION gEntvarsDescription[] = |
|
{ |
|
DEFINE_ENTITY_FIELD( classname, FIELD_STRING ), |
|
DEFINE_ENTITY_FIELD( globalname, FIELD_STRING ), |
|
DEFINE_ENTITY_FIELD( model, FIELD_MODELNAME ), |
|
DEFINE_ENTITY_FIELD( viewmodel, FIELD_MODELNAME ), |
|
DEFINE_ENTITY_FIELD( weaponmodel, FIELD_MODELNAME ), |
|
DEFINE_ENTITY_FIELD( target, FIELD_STRING ), |
|
DEFINE_ENTITY_FIELD( targetname, FIELD_STRING ), |
|
DEFINE_ENTITY_FIELD( netname, FIELD_STRING ), |
|
DEFINE_ENTITY_FIELD( message, FIELD_STRING ), |
|
DEFINE_ENTITY_FIELD( noise, FIELD_SOUNDNAME ), |
|
DEFINE_ENTITY_FIELD( noise1, FIELD_SOUNDNAME ), |
|
DEFINE_ENTITY_FIELD( noise2, FIELD_SOUNDNAME ), |
|
DEFINE_ENTITY_FIELD( noise3, FIELD_SOUNDNAME ), |
|
}; |
|
|
|
/* |
|
============= |
|
SV_GetEntvarsDescription |
|
|
|
entavrs table for FindEntityByString |
|
============= |
|
*/ |
|
TYPEDESCRIPTION *SV_GetEntvarsDescirption( int number ) |
|
{ |
|
if( number < 0 || number >= ENTVARS_COUNT ) |
|
return NULL; |
|
return &gEntvarsDescription[number]; |
|
} |
|
|
|
/* |
|
============= |
|
SV_SysError |
|
|
|
tell the game.dll about system error |
|
============= |
|
*/ |
|
void SV_SysError( const char *error_string ) |
|
{ |
|
Log_Printf( "FATAL ERROR (shutting down): %s\n", error_string ); |
|
|
|
if( svgame.hInstance != NULL ) |
|
svgame.dllFuncs.pfnSys_Error( error_string ); |
|
} |
|
|
|
/* |
|
============= |
|
SV_Serverinfo |
|
|
|
get server infostring |
|
============= |
|
*/ |
|
char *SV_Serverinfo( void ) |
|
{ |
|
return svs.serverinfo; |
|
} |
|
|
|
/* |
|
============= |
|
SV_LocalInfo |
|
|
|
get local infostring |
|
============= |
|
*/ |
|
char *SV_Localinfo( void ) |
|
{ |
|
return svs.localinfo; |
|
} |
|
|
|
/* |
|
============= |
|
SV_AngleMod |
|
|
|
do modulo on entity angles |
|
============= |
|
*/ |
|
float SV_AngleMod( float ideal, float current, float speed ) |
|
{ |
|
float move; |
|
|
|
current = anglemod( current ); |
|
|
|
if( current == ideal ) // already there? |
|
return current; |
|
|
|
move = ideal - current; |
|
|
|
if( ideal > current ) |
|
{ |
|
if( move >= 180 ) |
|
move = move - 360; |
|
} |
|
else |
|
{ |
|
if( move <= -180 ) |
|
move = move + 360; |
|
} |
|
|
|
if( move > 0 ) |
|
{ |
|
if( move > speed ) |
|
move = speed; |
|
} |
|
else |
|
{ |
|
if( move < -speed ) |
|
move = -speed; |
|
} |
|
|
|
return anglemod( current + move ); |
|
} |
|
|
|
/* |
|
============= |
|
SV_SetMinMaxSize |
|
|
|
update entity bounds, relink into world |
|
============= |
|
*/ |
|
void SV_SetMinMaxSize( edict_t *e, const float *mins, const float *maxs, qboolean relink ) |
|
{ |
|
int i; |
|
|
|
if( !SV_IsValidEdict( e )) |
|
return; |
|
|
|
for( i = 0; i < 3; i++ ) |
|
{ |
|
if( mins[i] > maxs[i] ) |
|
{ |
|
Con_Printf( S_ERROR "%s[%i] has backwards mins/maxs\n", SV_ClassName( e ), NUM_FOR_EDICT( e )); |
|
if( relink ) SV_LinkEdict( e, false ); // just relink edict and exit |
|
return; |
|
} |
|
} |
|
|
|
VectorCopy( mins, e->v.mins ); |
|
VectorCopy( maxs, e->v.maxs ); |
|
VectorSubtract( maxs, mins, e->v.size ); |
|
if( relink ) SV_LinkEdict( e, false ); |
|
} |
|
|
|
/* |
|
============= |
|
SV_CopyTraceToGlobal |
|
|
|
each trace will share their result into global state |
|
============= |
|
*/ |
|
void SV_CopyTraceToGlobal( trace_t *trace ) |
|
{ |
|
svgame.globals->trace_allsolid = trace->allsolid; |
|
svgame.globals->trace_startsolid = trace->startsolid; |
|
svgame.globals->trace_fraction = trace->fraction; |
|
svgame.globals->trace_plane_dist = trace->plane.dist; |
|
svgame.globals->trace_inopen = trace->inopen; |
|
svgame.globals->trace_inwater = trace->inwater; |
|
VectorCopy( trace->endpos, svgame.globals->trace_endpos ); |
|
VectorCopy( trace->plane.normal, svgame.globals->trace_plane_normal ); |
|
svgame.globals->trace_hitgroup = trace->hitgroup; |
|
svgame.globals->trace_flags = 0; // g-cont: always reset config flags when trace is finished |
|
|
|
if( SV_IsValidEdict( trace->ent )) |
|
svgame.globals->trace_ent = trace->ent; |
|
else svgame.globals->trace_ent = svgame.edicts; |
|
} |
|
|
|
/* |
|
============== |
|
SV_SetModel |
|
============== |
|
*/ |
|
void SV_SetModel( edict_t *ent, const char *modelname ) |
|
{ |
|
char name[MAX_QPATH]; |
|
qboolean found = false; |
|
model_t *mod; |
|
int i = 1; |
|
|
|
if( !SV_IsValidEdict( ent )) |
|
{ |
|
Con_Printf( S_WARN "SV_SetModel: invalid entity %s\n", SV_ClassName( ent )); |
|
return; |
|
} |
|
|
|
if( !modelname || modelname[0] <= ' ' ) |
|
{ |
|
Con_Printf( S_WARN "SV_SetModel: null name\n" ); |
|
return; |
|
} |
|
|
|
if( *modelname == '\\' || *modelname == '/' ) |
|
modelname++; |
|
|
|
Q_strncpy( name, modelname, sizeof( name )); |
|
COM_FixSlashes( name ); |
|
|
|
i = SV_ModelIndex( name ); |
|
if( i == 0 ) |
|
{ |
|
if( sv.state == ss_active ) |
|
Con_Printf( S_ERROR "SV_SetModel: failed to set model %s: world model cannot be changed\n", name ); |
|
return; |
|
} |
|
|
|
if( COM_CheckString( name )) |
|
{ |
|
ent->v.model = MAKE_STRING( sv.model_precache[i] ); |
|
ent->v.modelindex = i; |
|
mod = sv.models[i]; |
|
} |
|
else |
|
{ |
|
// model will be cleared |
|
ent->v.model = ent->v.modelindex = 0; |
|
mod = NULL; |
|
} |
|
|
|
// set the model size |
|
if( mod && mod->type != mod_studio ) |
|
SV_SetMinMaxSize( ent, mod->mins, mod->maxs, true ); |
|
else SV_SetMinMaxSize( ent, vec3_origin, vec3_origin, true ); |
|
} |
|
|
|
/* |
|
============= |
|
SV_ConvertTrace |
|
|
|
convert trace_t to TraceResult |
|
============= |
|
*/ |
|
void SV_ConvertTrace( TraceResult *dst, trace_t *src ) |
|
{ |
|
if( !src || !dst ) return; |
|
|
|
dst->fAllSolid = src->allsolid; |
|
dst->fStartSolid = src->startsolid; |
|
dst->fInOpen = src->inopen; |
|
dst->fInWater = src->inwater; |
|
dst->flFraction = src->fraction; |
|
VectorCopy( src->endpos, dst->vecEndPos ); |
|
dst->flPlaneDist = src->plane.dist; |
|
VectorCopy( src->plane.normal, dst->vecPlaneNormal ); |
|
dst->pHit = src->ent; |
|
dst->iHitgroup = src->hitgroup; |
|
|
|
// g-cont: always reset config flags when trace is finished |
|
svgame.globals->trace_flags = 0; |
|
} |
|
|
|
/* |
|
============= |
|
SV_CheckClientVisiblity |
|
|
|
Check visibility through client camera, portal camera, etc |
|
============= |
|
*/ |
|
qboolean SV_CheckClientVisiblity( sv_client_t *cl, const byte *mask ) |
|
{ |
|
int i, clientnum; |
|
vec3_t vieworg; |
|
mleaf_t *leaf; |
|
|
|
if( !mask ) return true; // GoldSrc rules |
|
|
|
clientnum = cl - svs.clients; |
|
VectorCopy( viewPoint[clientnum], vieworg ); |
|
|
|
// Invasion issues: wrong camera position received in ENGINE_SET_PVS |
|
if( cl->pViewEntity && !VectorCompare( vieworg, cl->pViewEntity->v.origin )) |
|
VectorCopy( cl->pViewEntity->v.origin, vieworg ); |
|
|
|
leaf = Mod_PointInLeaf( vieworg, sv.worldmodel->nodes ); |
|
|
|
if( CHECKVISBIT( mask, leaf->cluster )) |
|
return true; // visible from player view or camera view |
|
|
|
// now check all the portal cameras |
|
for( i = 0; i < cl->num_viewents; i++ ) |
|
{ |
|
edict_t *view = cl->viewentity[i]; |
|
|
|
if( !SV_IsValidEdict( view )) |
|
continue; |
|
|
|
VectorAdd( view->v.origin, view->v.view_ofs, vieworg ); |
|
leaf = Mod_PointInLeaf( vieworg, sv.worldmodel->nodes ); |
|
|
|
if( CHECKVISBIT( mask, leaf->cluster )) |
|
return true; // visible from portal camera view |
|
} |
|
|
|
// not visible from any viewpoint |
|
return false; |
|
} |
|
|
|
/* |
|
================= |
|
SV_Multicast |
|
|
|
Sends the contents of sv.multicast to a subset of the clients, |
|
then clears sv.multicast. |
|
|
|
MSG_INIT write message into signon buffer |
|
MSG_ONE send to one client (ent can't be NULL) |
|
MSG_ALL same as broadcast (origin can be NULL) |
|
MSG_PVS send to clients potentially visible from org |
|
MSG_PHS send to clients potentially audible from org |
|
================= |
|
*/ |
|
static int SV_Multicast( int dest, const vec3_t origin, const edict_t *ent, qboolean usermessage, qboolean filter ) |
|
{ |
|
byte *mask = NULL; |
|
int j, numclients = svs.maxclients; |
|
sv_client_t *cl, *current = svs.clients; |
|
qboolean reliable = false; |
|
qboolean specproxy = false; |
|
int numsends = 0; |
|
|
|
// some mods trying to send messages after SV_FinalMessage |
|
if( !svs.initialized || sv.state == ss_dead ) |
|
{ |
|
MSG_Clear( &sv.multicast ); |
|
return 0; |
|
} |
|
|
|
switch( dest ) |
|
{ |
|
case MSG_INIT: |
|
if( sv.state == ss_loading ) |
|
{ |
|
// copy to signon buffer |
|
MSG_WriteBits( &sv.signon, MSG_GetData( &sv.multicast ), MSG_GetNumBitsWritten( &sv.multicast )); |
|
MSG_Clear( &sv.multicast ); |
|
return 1; |
|
} |
|
// intentional fallthrough (in-game MSG_INIT it's a MSG_ALL reliable) |
|
case MSG_ALL: |
|
reliable = true; |
|
// intentional fallthrough |
|
case MSG_BROADCAST: |
|
// nothing to sort |
|
break; |
|
case MSG_PAS_R: |
|
reliable = true; |
|
// intentional fallthrough |
|
case MSG_PAS: |
|
if( origin == NULL ) return false; |
|
// NOTE: GoldSource not using PHS for singleplayer |
|
Mod_FatPVS( origin, FATPHS_RADIUS, fatphs, world.fatbytes, false, ( svs.maxclients == 1 )); |
|
mask = fatphs; // using the FatPVS like a PHS |
|
break; |
|
case MSG_PVS_R: |
|
reliable = true; |
|
// intentional fallthrough |
|
case MSG_PVS: |
|
if( origin == NULL ) return 0; |
|
mask = Mod_GetPVSForPoint( origin ); |
|
break; |
|
case MSG_ONE: |
|
reliable = true; |
|
// intentional fallthrough |
|
case MSG_ONE_UNRELIABLE: |
|
if( !SV_IsValidEdict( ent )) return 0; |
|
j = NUM_FOR_EDICT( ent ); |
|
if( j < 1 || j > numclients ) return 0; |
|
current = svs.clients + (j - 1); |
|
numclients = 1; // send to one |
|
break; |
|
case MSG_SPEC: |
|
specproxy = reliable = true; |
|
break; |
|
default: |
|
Host_Error( "SV_Multicast: bad dest: %i\n", dest ); |
|
return 0; |
|
} |
|
|
|
// send the data to all relevent clients (or once only) |
|
for( j = 0, cl = current; j < numclients; j++, cl++ ) |
|
{ |
|
if( cl->state == cs_free || cl->state == cs_zombie ) |
|
continue; |
|
|
|
if( cl->state != cs_spawned && ( !reliable || usermessage )) |
|
continue; |
|
|
|
if( specproxy && !FBitSet( cl->flags, FCL_HLTV_PROXY )) |
|
continue; |
|
|
|
if( !cl->edict || FBitSet( cl->flags, FCL_FAKECLIENT )) |
|
continue; |
|
|
|
// reject step sounds while predicting is enabled |
|
// FIXME: make sure what this code doesn't cutoff something important!!! |
|
if( filter && cl == sv.current_client && FBitSet( sv.current_client->flags, FCL_PREDICT_MOVEMENT )) |
|
continue; |
|
|
|
if( SV_IsValidEdict( ent ) && ent->v.groupinfo && cl->edict->v.groupinfo ) |
|
{ |
|
if( svs.groupop == GROUP_OP_AND && !FBitSet( cl->edict->v.groupinfo, ent->v.groupinfo )) |
|
continue; |
|
|
|
if( svs.groupop == GROUP_OP_NAND && FBitSet( cl->edict->v.groupinfo, ent->v.groupinfo )) |
|
continue; |
|
} |
|
|
|
if( !SV_CheckClientVisiblity( cl, mask )) |
|
continue; |
|
|
|
if( specproxy ) MSG_WriteBits( &sv.spec_datagram, MSG_GetData( &sv.multicast ), MSG_GetNumBitsWritten( &sv.multicast )); |
|
else if( reliable ) MSG_WriteBits( &cl->netchan.message, MSG_GetData( &sv.multicast ), MSG_GetNumBitsWritten( &sv.multicast )); |
|
else MSG_WriteBits( &cl->datagram, MSG_GetData( &sv.multicast ), MSG_GetNumBitsWritten( &sv.multicast )); |
|
numsends++; |
|
} |
|
|
|
MSG_Clear( &sv.multicast ); |
|
|
|
return numsends; // just for debug |
|
} |
|
|
|
/* |
|
======================= |
|
SV_GetReliableDatagram |
|
|
|
Get shared reliable buffer |
|
======================= |
|
*/ |
|
sizebuf_t *SV_GetReliableDatagram( void ) |
|
{ |
|
return &sv.reliable_datagram; |
|
} |
|
|
|
/* |
|
======================= |
|
SV_RestoreCustomDecal |
|
|
|
Let the user spawn decal in game code |
|
======================= |
|
*/ |
|
qboolean SV_RestoreCustomDecal( decallist_t *entry, edict_t *pEdict, qboolean adjacent ) |
|
{ |
|
if( svgame.physFuncs.pfnRestoreDecal != NULL ) |
|
{ |
|
if( !pEdict ) pEdict = EDICT_NUM( entry->entityIndex ); |
|
// true if decal was sucessfully restored at the game-side |
|
return svgame.physFuncs.pfnRestoreDecal( entry, pEdict, adjacent ); |
|
} |
|
return false; |
|
} |
|
|
|
/* |
|
======================= |
|
SV_CreateDecal |
|
|
|
NOTE: static decals only accepted when game is loading |
|
======================= |
|
*/ |
|
void SV_CreateDecal( sizebuf_t *msg, const float *origin, int decalIndex, int entityIndex, int modelIndex, int flags, float scale ) |
|
{ |
|
if( msg == &sv.signon && sv.state != ss_loading ) |
|
return; |
|
|
|
// this can happens if serialized map contain 4096 static decals... |
|
if( MSG_GetNumBytesLeft( msg ) < 20 ) |
|
{ |
|
sv.ignored_world_decals++; |
|
return; |
|
} |
|
|
|
// static decals are posters, it's always reliable |
|
MSG_BeginServerCmd( msg, svc_bspdecal ); |
|
MSG_WriteVec3Coord( msg, origin ); |
|
MSG_WriteWord( msg, decalIndex ); |
|
MSG_WriteShort( msg, entityIndex ); |
|
if( entityIndex > 0 ) |
|
MSG_WriteWord( msg, modelIndex ); |
|
MSG_WriteByte( msg, flags ); |
|
MSG_WriteWord( msg, scale * 4096 ); |
|
} |
|
|
|
/* |
|
======================= |
|
SV_CreateStaticEntity |
|
|
|
NOTE: static entities only accepted when game is loading |
|
======================= |
|
*/ |
|
qboolean SV_CreateStaticEntity( sizebuf_t *msg, int index ) |
|
{ |
|
entity_state_t nullstate, *baseline; |
|
entity_state_t *state; |
|
int offset; |
|
|
|
if( index >= ( MAX_STATIC_ENTITIES - 1 )) |
|
{ |
|
if( !sv.static_ents_overflow ) |
|
{ |
|
Con_Printf( S_WARN "MAX_STATIC_ENTITIES limit exceeded (%d)\n", MAX_STATIC_ENTITIES ); |
|
sv.static_ents_overflow = true; |
|
} |
|
|
|
sv.ignored_static_ents++; // continue overflowed entities |
|
return false; |
|
} |
|
|
|
// this can happens if serialized map contain too many static entities... |
|
if( MSG_GetNumBytesLeft( msg ) < 50 ) |
|
{ |
|
sv.ignored_static_ents++; |
|
return false; |
|
} |
|
|
|
state = &svs.static_entities[index]; // allocate a new one |
|
memset( &nullstate, 0, sizeof( nullstate )); |
|
baseline = &nullstate; |
|
|
|
// restore modelindex from modelname (already precached) |
|
state->modelindex = pfnModelIndex( STRING( state->messagenum )); |
|
state->entityType = ENTITY_NORMAL; // select delta-encode |
|
state->number = 0; |
|
|
|
// trying to compress with previous delta's |
|
offset = SV_FindBestBaselineForStatic( index, &baseline, state ); |
|
|
|
MSG_BeginServerCmd( msg, svc_spawnstatic ); |
|
MSG_WriteDeltaEntity( baseline, state, msg, true, DELTA_STATIC, sv.time, offset ); |
|
|
|
return true; |
|
} |
|
|
|
/* |
|
================= |
|
SV_RestartStaticEnts |
|
|
|
Write all the static ents into demo |
|
================= |
|
*/ |
|
void SV_RestartStaticEnts( void ) |
|
{ |
|
int i; |
|
|
|
// remove all the static entities on the client |
|
CL_ClearStaticEntities(); |
|
|
|
// resend them again |
|
for( i = 0; i < sv.num_static_entities; i++ ) |
|
SV_CreateStaticEntity( &sv.reliable_datagram, i ); |
|
} |
|
|
|
/* |
|
================= |
|
SV_RestartAmbientSounds |
|
|
|
Write ambient sounds into demo |
|
================= |
|
*/ |
|
void SV_RestartAmbientSounds( void ) |
|
{ |
|
soundlist_t soundInfo[256]; |
|
string curtrack, looptrack; |
|
int i, nSounds; |
|
int position; |
|
|
|
if( !SV_Active( )) return; |
|
|
|
nSounds = S_GetCurrentStaticSounds( soundInfo, 256 ); |
|
|
|
for( i = 0; i < nSounds; i++ ) |
|
{ |
|
soundlist_t *si = &soundInfo[i]; |
|
|
|
if( !si->looping || si->entnum == -1 ) |
|
continue; |
|
|
|
S_StopSound( si->entnum, si->channel, si->name ); |
|
SV_StartSound( SV_PEntityOfEntIndex( si->entnum, true ), CHAN_STATIC, si->name, si->volume, si->attenuation, 0, si->pitch ); |
|
} |
|
|
|
#if !XASH_DEDICATED // TODO: ??? |
|
// restart soundtrack |
|
if( S_StreamGetCurrentState( curtrack, looptrack, &position )) |
|
{ |
|
SV_StartMusic( curtrack, looptrack, position ); |
|
} |
|
#endif |
|
} |
|
|
|
/* |
|
================= |
|
SV_RestartDecals |
|
|
|
Write all the decals into demo |
|
================= |
|
*/ |
|
void SV_RestartDecals( void ) |
|
{ |
|
decallist_t *entry; |
|
int decalIndex; |
|
int modelIndex; |
|
sizebuf_t *msg; |
|
int i; |
|
|
|
if( !SV_Active( )) return; |
|
|
|
// g-cont. add space for studiodecals if present |
|
host.decalList = (decallist_t *)Z_Calloc( sizeof( decallist_t ) * MAX_RENDER_DECALS * 2 ); |
|
|
|
#if !XASH_DEDICATED |
|
if( !Host_IsDedicated() ) |
|
{ |
|
host.numdecals = ref.dllFuncs.R_CreateDecalList( host.decalList ); |
|
|
|
// remove decals from map |
|
ref.dllFuncs.R_ClearAllDecals(); |
|
} |
|
else |
|
#endif // XASH_DEDICATED |
|
{ |
|
// we probably running a dedicated server |
|
host.numdecals = 0; |
|
} |
|
|
|
// write decals into reliable datagram |
|
msg = SV_GetReliableDatagram(); |
|
|
|
// restore decals and write them into network message |
|
for( i = 0; i < host.numdecals; i++ ) |
|
{ |
|
entry = &host.decalList[i]; |
|
modelIndex = SV_PEntityOfEntIndex( entry->entityIndex, true )->v.modelindex; |
|
|
|
// game override |
|
if( SV_RestoreCustomDecal( entry, SV_PEntityOfEntIndex( entry->entityIndex, true ), false )) |
|
continue; |
|
|
|
decalIndex = pfnDecalIndex( entry->name ); |
|
|
|
// studiodecals will be restored at game-side |
|
if( !FBitSet( entry->flags, FDECAL_STUDIO )) |
|
SV_CreateDecal( msg, entry->position, decalIndex, entry->entityIndex, modelIndex, entry->flags, entry->scale ); |
|
} |
|
|
|
Z_Free( host.decalList ); |
|
host.decalList = NULL; |
|
host.numdecals = 0; |
|
} |
|
|
|
/* |
|
============== |
|
SV_BoxInPVS |
|
|
|
check brush boxes in fat pvs |
|
============== |
|
*/ |
|
qboolean SV_BoxInPVS( const vec3_t org, const vec3_t absmin, const vec3_t absmax ) |
|
{ |
|
if( !Mod_BoxVisible( absmin, absmax, Mod_GetPVSForPoint( org ))) |
|
return false; |
|
return true; |
|
} |
|
|
|
/* |
|
============= |
|
SV_ChangeLevel |
|
|
|
Issue changing level |
|
============= |
|
*/ |
|
void SV_QueueChangeLevel( const char *level, const char *landname ) |
|
{ |
|
uint flags, smooth = false; |
|
char mapname[MAX_QPATH]; |
|
char *spawn_entity; |
|
|
|
// hold mapname to other place |
|
Q_strncpy( mapname, level, sizeof( mapname )); |
|
COM_StripExtension( mapname ); |
|
|
|
if( COM_CheckString( landname )) |
|
smooth = true; |
|
|
|
// determine spawn entity classname |
|
if( svs.maxclients == 1 ) |
|
spawn_entity = GI->sp_entity; |
|
else spawn_entity = GI->mp_entity; |
|
|
|
flags = SV_MapIsValid( mapname, spawn_entity, landname ); |
|
|
|
if( FBitSet( flags, MAP_INVALID_VERSION )) |
|
{ |
|
Con_Printf( S_ERROR "changelevel: %s is invalid or not supported\n", mapname ); |
|
return; |
|
} |
|
|
|
if( !FBitSet( flags, MAP_IS_EXIST )) |
|
{ |
|
Con_Printf( S_ERROR "changelevel: map %s doesn't exist\n", mapname ); |
|
return; |
|
} |
|
|
|
if( smooth && !FBitSet( flags, MAP_HAS_LANDMARK )) |
|
{ |
|
if( sv_validate_changelevel->value ) |
|
{ |
|
// NOTE: we find valid map but specified landmark it's doesn't exist |
|
// run simple changelevel like in q1, throw warning |
|
Con_Printf( S_WARN "changelevel: %s doesn't contain landmark [%s]. smooth transition was disabled\n", mapname, landname ); |
|
smooth = false; |
|
} |
|
} |
|
|
|
if( svs.maxclients > 1 ) |
|
smooth = false; // multiplayer doesn't support smooth transition |
|
|
|
if( smooth && !Q_stricmp( sv.name, level )) |
|
{ |
|
Con_Printf( S_ERROR "can't changelevel with same map. Ignored.\n" ); |
|
return; |
|
} |
|
|
|
if( !smooth && !FBitSet( flags, MAP_HAS_SPAWNPOINT )) |
|
{ |
|
if( sv_validate_changelevel->value ) |
|
{ |
|
Con_Printf( S_ERROR "changelevel: %s doesn't have a valid spawnpoint. Ignored.\n", mapname ); |
|
return; |
|
} |
|
} |
|
|
|
// bad changelevel position invoke enables in one-way transition |
|
if( sv.framecount < 15 ) |
|
{ |
|
if( sv_validate_changelevel->value ) |
|
{ |
|
Con_Printf( S_WARN "an infinite changelevel was detected and will be disabled until a next save\\restore\n" ); |
|
return; // lock with svs.spawncount here |
|
} |
|
} |
|
|
|
SV_SkipUpdates (); |
|
|
|
// changelevel will be executed on a next frame |
|
if( smooth ) COM_ChangeLevel( mapname, landname, sv.background ); // Smoothed Half-Life changelevel |
|
else COM_ChangeLevel( mapname, NULL, sv.background ); // Classic Quake changlevel |
|
} |
|
|
|
/* |
|
============== |
|
SV_WriteEntityPatch |
|
|
|
Create entity patch for selected map |
|
============== |
|
*/ |
|
void SV_WriteEntityPatch( const char *filename ) |
|
{ |
|
int lumpofs = 0, lumplen = 0; |
|
byte buf[MAX_TOKEN]; // 1 kb |
|
string bspfilename; |
|
dheader_t *header; |
|
dlump_t entities; |
|
file_t *f; |
|
|
|
Q_snprintf( bspfilename, sizeof( bspfilename ), "maps/%s.bsp", filename ); |
|
|
|
f = FS_Open( bspfilename, "rb", false ); |
|
if( !f ) return; |
|
|
|
memset( buf, 0, MAX_TOKEN ); |
|
FS_Read( f, buf, MAX_TOKEN ); |
|
header = (dheader_t *)buf; |
|
|
|
// check all the lumps and some other errors |
|
if( !Mod_TestBmodelLumps( f, bspfilename, buf, true, &entities )) |
|
{ |
|
FS_Close( f ); |
|
return; |
|
} |
|
|
|
lumpofs = entities.fileofs; |
|
lumplen = entities.filelen; |
|
|
|
if( lumplen >= 10 ) |
|
{ |
|
char *entities = NULL; |
|
|
|
FS_Seek( f, lumpofs, SEEK_SET ); |
|
entities = (char *)Z_Calloc( lumplen + 1 ); |
|
FS_Read( f, entities, lumplen ); |
|
FS_WriteFile( va( "maps/%s.ent", filename ), entities, lumplen ); |
|
Con_Printf( "Write 'maps/%s.ent'\n", filename ); |
|
Mem_Free( entities ); |
|
} |
|
|
|
FS_Close( f ); |
|
} |
|
|
|
/* |
|
============== |
|
SV_ReadEntityScript |
|
|
|
pfnMapIsValid use this |
|
============== |
|
*/ |
|
static char *SV_ReadEntityScript( const char *filename, int *flags ) |
|
{ |
|
string bspfilename, entfilename; |
|
int lumpofs = 0, lumplen = 0; |
|
byte buf[MAX_TOKEN]; |
|
char *ents = NULL; |
|
dheader_t *header; |
|
dlump_t entities; |
|
size_t ft1, ft2; |
|
file_t *f; |
|
|
|
*flags = 0; |
|
|
|
Q_snprintf( bspfilename, sizeof( bspfilename ), "maps/%s.bsp", filename ); |
|
|
|
f = FS_Open( bspfilename, "rb", false ); |
|
if( !f ) return NULL; |
|
|
|
SetBits( *flags, MAP_IS_EXIST ); |
|
memset( buf, 0, MAX_TOKEN ); |
|
FS_Read( f, buf, MAX_TOKEN ); |
|
header = (dheader_t *)buf; |
|
|
|
// check all the lumps and some other errors |
|
if( !Mod_TestBmodelLumps( f, bspfilename, buf, (host_developer.value) ? false : true, &entities )) |
|
{ |
|
SetBits( *flags, MAP_INVALID_VERSION ); |
|
FS_Close( f ); |
|
return NULL; |
|
} |
|
|
|
// after call Mod_TestBmodelLumps we gurantee what map is valid |
|
lumpofs = entities.fileofs; |
|
lumplen = entities.filelen; |
|
|
|
// check for entfile too |
|
Q_snprintf( entfilename, sizeof( entfilename ), "maps/%s.ent", filename ); |
|
|
|
// make sure what entity patch is newer than bsp |
|
ft1 = FS_FileTime( bspfilename, false ); |
|
ft2 = FS_FileTime( entfilename, true ); |
|
|
|
if( ft2 != -1 && ft1 < ft2 ) |
|
{ |
|
// grab .ent files only from gamedir |
|
ents = (char *)FS_LoadFile( entfilename, NULL, true ); |
|
} |
|
|
|
// at least entities should contain "{ "classname" "worldspawn" }\0" |
|
// for correct spawn the level |
|
if( !ents && lumplen >= 32 ) |
|
{ |
|
FS_Seek( f, lumpofs, SEEK_SET ); |
|
ents = Z_Calloc( lumplen + 1 ); |
|
FS_Read( f, ents, lumplen ); |
|
} |
|
FS_Close( f ); // all done |
|
|
|
return ents; |
|
} |
|
|
|
/* |
|
============== |
|
SV_MapIsValid |
|
|
|
Validate map |
|
============== |
|
*/ |
|
uint SV_MapIsValid( const char *filename, const char *spawn_entity, const char *landmark_name ) |
|
{ |
|
uint flags = 0; |
|
char *pfile; |
|
char *ents; |
|
|
|
ents = SV_ReadEntityScript( filename, &flags ); |
|
|
|
if( ents ) |
|
{ |
|
qboolean need_landmark; |
|
char token[MAX_TOKEN]; |
|
string check_name; |
|
|
|
need_landmark = COM_CheckString( landmark_name ); |
|
|
|
// g-cont. in-dev mode we can entering on map even without "info_player_start" |
|
if( !need_landmark && host_developer.value ) |
|
{ |
|
// not transition |
|
Mem_Free( ents ); |
|
|
|
// skip spawnpoint checks in devmode |
|
return (flags|MAP_HAS_SPAWNPOINT); |
|
} |
|
|
|
pfile = ents; |
|
|
|
while(( pfile = COM_ParseFile( pfile, token, sizeof( token ))) != NULL ) |
|
{ |
|
if( !Q_strcmp( token, "classname" )) |
|
{ |
|
// check classname for spawn entity |
|
pfile = COM_ParseFile( pfile, check_name, sizeof( check_name )); |
|
if( !Q_strcmp( spawn_entity, check_name )) |
|
{ |
|
SetBits( flags, MAP_HAS_SPAWNPOINT ); |
|
|
|
// we already find landmark, stop the parsing |
|
if( need_landmark && FBitSet( flags, MAP_HAS_LANDMARK )) |
|
break; |
|
} |
|
} |
|
else if( need_landmark && !Q_strcmp( token, "targetname" )) |
|
{ |
|
// check targetname for landmark entity |
|
pfile = COM_ParseFile( pfile, check_name, sizeof( check_name )); |
|
|
|
if( !Q_strcmp( landmark_name, check_name )) |
|
{ |
|
SetBits( flags, MAP_HAS_LANDMARK ); |
|
|
|
// we already find spawnpoint, stop the parsing |
|
if( FBitSet( flags, MAP_HAS_SPAWNPOINT )) |
|
break; |
|
} |
|
} |
|
} |
|
|
|
Mem_Free( ents ); |
|
} |
|
|
|
return flags; |
|
} |
|
|
|
/* |
|
============== |
|
SV_FreePrivateData |
|
|
|
release private edict memory |
|
============== |
|
*/ |
|
void GAME_EXPORT SV_FreePrivateData( edict_t *pEdict ) |
|
{ |
|
if( !pEdict || !pEdict->pvPrivateData ) |
|
return; |
|
|
|
// NOTE: new interface can be missing |
|
if( svgame.dllFuncs2.pfnOnFreeEntPrivateData != NULL ) |
|
svgame.dllFuncs2.pfnOnFreeEntPrivateData( pEdict ); |
|
|
|
if( Mem_IsAllocatedExt( svgame.mempool, pEdict->pvPrivateData )) |
|
Mem_Free( pEdict->pvPrivateData ); |
|
|
|
pEdict->pvPrivateData = NULL; |
|
} |
|
|
|
/* |
|
============== |
|
SV_InitEdict |
|
|
|
clear edict for reuse |
|
============== |
|
*/ |
|
void SV_InitEdict( edict_t *pEdict ) |
|
{ |
|
Assert( pEdict != NULL ); |
|
|
|
SV_FreePrivateData( pEdict ); |
|
memset( &pEdict->v, 0, sizeof( entvars_t )); |
|
pEdict->v.pContainingEntity = pEdict; |
|
pEdict->v.controller[0] = 0x7F; |
|
pEdict->v.controller[1] = 0x7F; |
|
pEdict->v.controller[2] = 0x7F; |
|
pEdict->v.controller[3] = 0x7F; |
|
pEdict->free = false; |
|
} |
|
|
|
/* |
|
============== |
|
SV_FreeEdict |
|
|
|
unlink edict from world and free it |
|
============== |
|
*/ |
|
void SV_FreeEdict( edict_t *pEdict ) |
|
{ |
|
Assert( pEdict != NULL ); |
|
if( pEdict->free ) return; |
|
|
|
// unlink from world |
|
SV_UnlinkEdict( pEdict ); |
|
|
|
SV_FreePrivateData( pEdict ); |
|
|
|
// mark edict as freed |
|
pEdict->freetime = sv.time; |
|
pEdict->serialnumber++; // invalidate EHANDLE's |
|
pEdict->v.solid = SOLID_NOT; |
|
pEdict->v.flags = 0; |
|
pEdict->v.model = 0; |
|
pEdict->v.takedamage = 0; |
|
pEdict->v.modelindex = 0; |
|
pEdict->v.nextthink = -1; |
|
pEdict->v.colormap = 0; |
|
pEdict->v.frame = 0; |
|
pEdict->v.scale = 0; |
|
pEdict->v.gravity = 0; |
|
pEdict->v.skin = 0; |
|
|
|
VectorClear( pEdict->v.angles ); |
|
VectorClear( pEdict->v.origin ); |
|
pEdict->free = true; |
|
} |
|
|
|
/* |
|
============== |
|
SV_AllocEdict |
|
|
|
allocate new or reuse existing |
|
============== |
|
*/ |
|
edict_t *SV_AllocEdict( void ) |
|
{ |
|
edict_t *e; |
|
int i; |
|
|
|
for( i = svs.maxclients + 1; i < svgame.numEntities; i++ ) |
|
{ |
|
e = EDICT_NUM( i ); |
|
// the first couple seconds of server time can involve a lot of |
|
// freeing and allocating, so relax the replacement policy |
|
if( e->free && ( e->freetime < 2.0f || ( sv.time - e->freetime ) > 0.5f )) |
|
{ |
|
SV_InitEdict( e ); |
|
return e; |
|
} |
|
} |
|
|
|
if( i >= GI->max_edicts ) |
|
Host_Error( "ED_AllocEdict: no free edicts (max is %d)\n", GI->max_edicts ); |
|
|
|
svgame.numEntities++; |
|
e = EDICT_NUM( i ); |
|
SV_InitEdict( e ); |
|
|
|
return e; |
|
} |
|
|
|
/* |
|
============== |
|
SV_GetEntityClass |
|
|
|
get pointer for entity class |
|
============== |
|
*/ |
|
LINK_ENTITY_FUNC SV_GetEntityClass( const char *pszClassName ) |
|
{ |
|
// allocate edict private memory (passed by dlls) |
|
return (LINK_ENTITY_FUNC)COM_GetProcAddress( svgame.hInstance, pszClassName ); |
|
} |
|
|
|
/* |
|
============== |
|
SV_AllocPrivateData |
|
|
|
allocate private data for a given edict |
|
============== |
|
*/ |
|
edict_t* SV_AllocPrivateData( edict_t *ent, string_t className ) |
|
{ |
|
const char *pszClassName; |
|
LINK_ENTITY_FUNC SpawnEdict; |
|
|
|
pszClassName = STRING( className ); |
|
|
|
if( !ent ) |
|
{ |
|
// allocate a new one |
|
ent = SV_AllocEdict(); |
|
} |
|
else if( ent->free ) |
|
{ |
|
SV_InitEdict( ent ); // re-init edict |
|
} |
|
|
|
ent->v.classname = className; |
|
ent->v.pContainingEntity = ent; // re-link |
|
|
|
// allocate edict private memory (passed by dlls) |
|
SpawnEdict = SV_GetEntityClass( pszClassName ); |
|
|
|
if( !SpawnEdict ) |
|
{ |
|
// attempt to create custom entity (Xash3D extension) |
|
if( svgame.physFuncs.SV_CreateEntity && svgame.physFuncs.SV_CreateEntity( ent, pszClassName ) != -1 ) |
|
return ent; |
|
|
|
SpawnEdict = SV_GetEntityClass( "custom" ); |
|
|
|
if( !SpawnEdict ) |
|
{ |
|
Con_Printf( S_ERROR "No spawn function for %s\n", STRING( className )); |
|
|
|
// free entity immediately |
|
SV_FreeEdict( ent ); |
|
|
|
return NULL; |
|
} |
|
|
|
SetBits( ent->v.flags, FL_CUSTOMENTITY ); // it's a custom entity but not a beam! |
|
} |
|
|
|
SpawnEdict( &ent->v ); |
|
|
|
return ent; |
|
} |
|
|
|
/* |
|
============== |
|
SV_CreateNamedEntity |
|
|
|
create specified entity, alloc private data |
|
============== |
|
*/ |
|
edict_t* SV_CreateNamedEntity( edict_t *ent, string_t className ) |
|
{ |
|
edict_t *ed = SV_AllocPrivateData( ent, className ); |
|
|
|
// for some reasons this flag should be immediately cleared |
|
if( ed ) ClearBits( ed->v.flags, FL_CUSTOMENTITY ); |
|
|
|
return ed; |
|
} |
|
|
|
/* |
|
============== |
|
SV_FreeEdicts |
|
|
|
release all the edicts from server |
|
============== |
|
*/ |
|
void SV_FreeEdicts( void ) |
|
{ |
|
int i = 0; |
|
edict_t *ent; |
|
|
|
for( i = 0; i < svgame.numEntities; i++ ) |
|
{ |
|
ent = EDICT_NUM( i ); |
|
if( ent->free ) continue; |
|
SV_FreeEdict( ent ); |
|
} |
|
} |
|
|
|
/* |
|
============== |
|
SV_PlaybackReliableEvent |
|
|
|
reliable event is must be delivered always |
|
============== |
|
*/ |
|
void SV_PlaybackReliableEvent( sizebuf_t *msg, word eventindex, float delay, event_args_t *args ) |
|
{ |
|
event_args_t nullargs; |
|
|
|
memset( &nullargs, 0, sizeof( nullargs )); |
|
|
|
MSG_BeginServerCmd( msg, svc_event_reliable ); |
|
|
|
// send event index |
|
MSG_WriteUBitLong( msg, eventindex, MAX_EVENT_BITS ); |
|
|
|
if( delay ) |
|
{ |
|
// send event delay |
|
MSG_WriteOneBit( msg, 1 ); |
|
MSG_WriteWord( msg, ( delay * 100.0f )); |
|
} |
|
else MSG_WriteOneBit( msg, 0 ); |
|
|
|
// reliable events not use delta-compression just null-compression |
|
MSG_WriteDeltaEvent( msg, &nullargs, args ); |
|
} |
|
|
|
/* |
|
============== |
|
SV_ClassName |
|
|
|
template to get edict classname |
|
============== |
|
*/ |
|
const char *SV_ClassName( const edict_t *e ) |
|
{ |
|
if( !e ) return "(null)"; |
|
if( e->free ) return "freed"; |
|
return STRING( e->v.classname ); |
|
} |
|
|
|
/* |
|
============== |
|
SV_IsValidCmd |
|
|
|
command validation |
|
============== |
|
*/ |
|
static qboolean SV_IsValidCmd( const char *pCmd ) |
|
{ |
|
size_t len = Q_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; |
|
} |
|
|
|
/* |
|
============== |
|
SV_AllocPrivateData |
|
|
|
get edict that attached to the client structure |
|
============== |
|
*/ |
|
sv_client_t *SV_ClientFromEdict( const edict_t *pEdict, qboolean spawned_only ) |
|
{ |
|
int i; |
|
|
|
if( !SV_IsValidEdict( pEdict )) |
|
return NULL; |
|
|
|
i = NUM_FOR_EDICT( pEdict ) - 1; |
|
|
|
if( i < 0 || i >= svs.maxclients ) |
|
return NULL; |
|
|
|
if( spawned_only ) |
|
{ |
|
if( svs.clients[i].state != cs_spawned ) |
|
return NULL; |
|
} |
|
|
|
return (svs.clients + i); |
|
} |
|
|
|
/* |
|
=============================================================================== |
|
|
|
Game Builtin Functions |
|
|
|
=============================================================================== |
|
*/ |
|
/* |
|
========= |
|
pfnPrecacheModel |
|
|
|
========= |
|
*/ |
|
int GAME_EXPORT pfnPrecacheModel( const char *s ) |
|
{ |
|
qboolean optional = false; |
|
int i; |
|
|
|
if( *s == '!' ) |
|
{ |
|
optional = true; |
|
s++; |
|
} |
|
|
|
if(( i = SV_ModelIndex( s )) == 0 ) |
|
return 0; |
|
|
|
sv.models[i] = Mod_ForName( sv.model_precache[i], false, true ); |
|
|
|
if( !optional ) |
|
SetBits( sv.model_precache_flags[i], RES_FATALIFMISSING ); |
|
|
|
return i; |
|
} |
|
|
|
/* |
|
================= |
|
pfnSetModel |
|
|
|
================= |
|
*/ |
|
void GAME_EXPORT pfnSetModel( edict_t *e, const char *m ) |
|
{ |
|
SV_SetModel( e, m ); |
|
} |
|
|
|
/* |
|
================= |
|
pfnModelIndex |
|
|
|
================= |
|
*/ |
|
int GAME_EXPORT pfnModelIndex( const char *m ) |
|
{ |
|
char name[MAX_QPATH]; |
|
int i; |
|
|
|
if( !COM_CheckString( m )) |
|
return 0; |
|
|
|
if( *m == '\\' || *m == '/' ) m++; |
|
Q_strncpy( name, m, sizeof( name )); |
|
COM_FixSlashes( name ); |
|
|
|
for( i = 1; i < MAX_MODELS && sv.model_precache[i][0]; i++ ) |
|
{ |
|
if( !Q_stricmp( sv.model_precache[i], name )) |
|
return i; |
|
} |
|
|
|
Con_Printf( S_ERROR "Cannot get index for model %s: not precached\n", name ); |
|
return 0; |
|
} |
|
|
|
/* |
|
================= |
|
pfnModelFrames |
|
|
|
================= |
|
*/ |
|
int GAME_EXPORT pfnModelFrames( int modelIndex ) |
|
{ |
|
model_t *pmodel = SV_ModelHandle( modelIndex ); |
|
|
|
if( pmodel != NULL ) |
|
return pmodel->numframes; |
|
return 1; |
|
} |
|
|
|
/* |
|
================= |
|
pfnSetSize |
|
|
|
================= |
|
*/ |
|
void GAME_EXPORT pfnSetSize( edict_t *e, const float *rgflMin, const float *rgflMax ) |
|
{ |
|
if( !SV_IsValidEdict( e )) |
|
return; |
|
|
|
SV_SetMinMaxSize( e, rgflMin, rgflMax, true ); |
|
} |
|
|
|
/* |
|
================= |
|
pfnChangeLevel |
|
|
|
================= |
|
*/ |
|
void GAME_EXPORT pfnChangeLevel( const char *level, const char *landmark ) |
|
{ |
|
static uint last_spawncount = 0; |
|
char landname[MAX_QPATH]; |
|
char *text; |
|
|
|
if( !COM_CheckString( level ) || sv.state != ss_active ) |
|
return; // ??? |
|
|
|
// make sure we don't issue two changelevels |
|
if( svs.spawncount == last_spawncount ) |
|
return; |
|
last_spawncount = svs.spawncount; |
|
landname[0] ='\0'; |
|
|
|
#ifdef HACKS_RELATED_HLMODS |
|
// g-cont. some level-designers wrote landmark name with space |
|
// and Cmd_TokenizeString separating all the after space as next argument |
|
// emulate this bug for compatibility |
|
if( COM_CheckString( landmark )) |
|
{ |
|
text = (char *)landname; |
|
while( *landmark && ((byte)*landmark) != ' ' ) |
|
*text++ = *landmark++; |
|
*text = '\0'; |
|
} |
|
#else |
|
Q_strncpy( landname, landmark, sizeof( landname )); |
|
#endif |
|
SV_QueueChangeLevel( level, landname ); |
|
} |
|
|
|
/* |
|
================= |
|
pfnGetSpawnParms |
|
|
|
OBSOLETE, UNUSED |
|
================= |
|
*/ |
|
void GAME_EXPORT pfnGetSpawnParms( edict_t *ent ) |
|
{ |
|
} |
|
|
|
/* |
|
================= |
|
pfnSaveSpawnParms |
|
|
|
OBSOLETE, UNUSED |
|
================= |
|
*/ |
|
void GAME_EXPORT pfnSaveSpawnParms( edict_t *ent ) |
|
{ |
|
} |
|
|
|
/* |
|
================= |
|
pfnVecToYaw |
|
|
|
================= |
|
*/ |
|
float GAME_EXPORT pfnVecToYaw( const float *rgflVector ) |
|
{ |
|
return SV_VecToYaw( rgflVector ); |
|
} |
|
|
|
/* |
|
================= |
|
pfnMoveToOrigin |
|
|
|
================= |
|
*/ |
|
void GAME_EXPORT pfnMoveToOrigin( edict_t *ent, const float *pflGoal, float dist, int iMoveType ) |
|
{ |
|
if( !pflGoal || !SV_IsValidEdict( ent )) |
|
return; |
|
|
|
SV_MoveToOrigin( ent, pflGoal, dist, iMoveType ); |
|
} |
|
|
|
/* |
|
============== |
|
pfnChangeYaw |
|
|
|
============== |
|
*/ |
|
void GAME_EXPORT pfnChangeYaw( edict_t* ent ) |
|
{ |
|
if( !SV_IsValidEdict( ent )) |
|
return; |
|
|
|
ent->v.angles[YAW] = SV_AngleMod( ent->v.ideal_yaw, ent->v.angles[YAW], ent->v.yaw_speed ); |
|
} |
|
|
|
/* |
|
============== |
|
pfnChangePitch |
|
|
|
============== |
|
*/ |
|
void GAME_EXPORT pfnChangePitch( edict_t* ent ) |
|
{ |
|
if( !SV_IsValidEdict( ent )) |
|
return; |
|
|
|
ent->v.angles[PITCH] = SV_AngleMod( ent->v.idealpitch, ent->v.angles[PITCH], ent->v.pitch_speed ); |
|
} |
|
|
|
/* |
|
========= |
|
SV_FindEntityByString |
|
|
|
========= |
|
*/ |
|
edict_t *SV_FindEntityByString( edict_t *pStartEdict, const char *pszField, const char *pszValue ) |
|
{ |
|
int index = 0, e = 0; |
|
TYPEDESCRIPTION *desc = NULL; |
|
edict_t *ed; |
|
const char *t; |
|
|
|
if( !COM_CheckString( pszValue )) |
|
return svgame.edicts; |
|
|
|
if( pStartEdict ) e = NUM_FOR_EDICT( pStartEdict ); |
|
|
|
while(( desc = SV_GetEntvarsDescirption( index++ )) != NULL ) |
|
{ |
|
if( !Q_strcmp( pszField, desc->fieldName )) |
|
break; |
|
} |
|
|
|
if( desc == NULL ) |
|
{ |
|
Con_Printf( S_ERROR "FindEntityByString: field %s not a string\n", pszField ); |
|
return svgame.edicts; |
|
} |
|
|
|
for( e++; e < svgame.numEntities; e++ ) |
|
{ |
|
ed = EDICT_NUM( e ); |
|
if( !SV_IsValidEdict( ed )) continue; |
|
|
|
if( e <= svs.maxclients && !SV_ClientFromEdict( ed, ( svs.maxclients != 1 ))) |
|
continue; |
|
|
|
switch( desc->fieldType ) |
|
{ |
|
case FIELD_STRING: |
|
case FIELD_MODELNAME: |
|
case FIELD_SOUNDNAME: |
|
t = STRING( *(string_t *)&((byte *)&ed->v)[desc->fieldOffset] ); |
|
if( t != NULL && t != svgame.globals->pStringBase ) |
|
{ |
|
if( !Q_strcmp( t, pszValue )) |
|
return ed; |
|
} |
|
break; |
|
default: |
|
ASSERT( 0 ); |
|
break; |
|
} |
|
} |
|
|
|
return svgame.edicts; |
|
} |
|
|
|
/* |
|
========= |
|
SV_FindGlobalEntity |
|
|
|
ripped out from the hl.dll |
|
========= |
|
*/ |
|
edict_t *SV_FindGlobalEntity( string_t classname, string_t globalname ) |
|
{ |
|
edict_t *pent = SV_FindEntityByString( NULL, "globalname", STRING( globalname )); |
|
|
|
if( SV_IsValidEdict( pent )) |
|
{ |
|
// don't spam about error - game code already tell us |
|
if( Q_strcmp( SV_ClassName( pent ), STRING( classname ))) |
|
pent = NULL; |
|
} |
|
|
|
return pent; |
|
} |
|
|
|
/* |
|
============== |
|
pfnGetEntityIllum |
|
|
|
returns averaged lightvalue for entity |
|
============== |
|
*/ |
|
int GAME_EXPORT pfnGetEntityIllum( edict_t* pEnt ) |
|
{ |
|
if( !SV_IsValidEdict( pEnt )) |
|
return -1; |
|
|
|
return SV_LightForEntity( pEnt ); |
|
} |
|
|
|
/* |
|
================= |
|
pfnFindEntityInSphere |
|
|
|
find the entity in sphere |
|
================= |
|
*/ |
|
edict_t *pfnFindEntityInSphere( edict_t *pStartEdict, const float *org, float flRadius ) |
|
{ |
|
float distSquared; |
|
int j, e = 0; |
|
float eorg; |
|
edict_t *ent; |
|
|
|
flRadius *= flRadius; |
|
|
|
if( SV_IsValidEdict( pStartEdict )) |
|
e = NUM_FOR_EDICT( pStartEdict ); |
|
|
|
for( e++; e < svgame.numEntities; e++ ) |
|
{ |
|
ent = EDICT_NUM( e ); |
|
|
|
if( !SV_IsValidEdict( ent )) |
|
continue; |
|
|
|
// ignore clients that not in a game |
|
if( e <= svs.maxclients && !SV_ClientFromEdict( ent, true )) |
|
continue; |
|
|
|
distSquared = 0.0f; |
|
|
|
for( j = 0; j < 3 && distSquared <= flRadius; j++ ) |
|
{ |
|
if( org[j] < ent->v.absmin[j] ) |
|
eorg = org[j] - ent->v.absmin[j]; |
|
else if( org[j] > ent->v.absmax[j] ) |
|
eorg = org[j] - ent->v.absmax[j]; |
|
else eorg = 0.0f; |
|
|
|
distSquared += eorg * eorg; |
|
} |
|
|
|
if( distSquared < flRadius ) |
|
return ent; |
|
} |
|
|
|
return svgame.edicts; |
|
} |
|
|
|
/* |
|
================= |
|
SV_CheckClientPVS |
|
|
|
build the new client PVS |
|
================= |
|
*/ |
|
int SV_CheckClientPVS( int check, qboolean bMergePVS ) |
|
{ |
|
byte *pvs; |
|
vec3_t vieworg; |
|
sv_client_t *cl; |
|
int i, j, k; |
|
edict_t *ent = NULL; |
|
|
|
// cycle to the next one |
|
check = bound( 1, check, svs.maxclients ); |
|
|
|
if( check == svs.maxclients ) |
|
i = 1; // reset cycle |
|
else i = check + 1; |
|
|
|
for( ;; i++ ) |
|
{ |
|
if( i == ( svs.maxclients + 1 )) |
|
i = 1; |
|
|
|
ent = EDICT_NUM( i ); |
|
if( i == check ) break; // didn't find anything else |
|
|
|
if( ent->free || !ent->pvPrivateData || FBitSet( ent->v.flags, FL_NOTARGET )) |
|
continue; |
|
|
|
// anything that is a client, or has a client as an enemy |
|
break; |
|
} |
|
|
|
cl = SV_ClientFromEdict( ent, true ); |
|
memset( clientpvs, 0xFF, world.visbytes ); |
|
|
|
// get the PVS for the entity |
|
VectorAdd( ent->v.origin, ent->v.view_ofs, vieworg ); |
|
pvs = Mod_GetPVSForPoint( vieworg ); |
|
if( pvs ) memcpy( clientpvs, pvs, world.visbytes ); |
|
|
|
// transition in progress |
|
if( !cl ) return i; |
|
|
|
// now merge PVS with all the portal cameras |
|
for( k = 0; k < cl->num_viewents && bMergePVS; k++ ) |
|
{ |
|
edict_t *view = cl->viewentity[k]; |
|
|
|
if( !SV_IsValidEdict( view )) |
|
continue; |
|
|
|
VectorAdd( view->v.origin, view->v.view_ofs, vieworg ); |
|
pvs = Mod_GetPVSForPoint( vieworg ); |
|
|
|
for( j = 0; j < world.visbytes && pvs; j++ ) |
|
SetBits( clientpvs[j], pvs[j] ); |
|
} |
|
|
|
return i; |
|
} |
|
|
|
/* |
|
================= |
|
pfnFindClientInPVS |
|
|
|
================= |
|
*/ |
|
edict_t* GAME_EXPORT pfnFindClientInPVS( edict_t *pEdict ) |
|
{ |
|
edict_t *pClient; |
|
vec3_t view; |
|
float delta; |
|
model_t *mod; |
|
qboolean bMergePVS; |
|
mleaf_t *leaf; |
|
|
|
if( !SV_IsValidEdict( pEdict )) |
|
return svgame.edicts; |
|
|
|
delta = ( sv.time - sv.lastchecktime ); |
|
|
|
// don't merge visibility for portal entity, only for monsters |
|
bMergePVS = FBitSet( pEdict->v.flags, FL_MONSTER ) ? true : false; |
|
|
|
// find a new check if on a new frame |
|
if( delta < 0.0f || delta >= 0.1f ) |
|
{ |
|
sv.lastcheck = SV_CheckClientPVS( sv.lastcheck, bMergePVS ); |
|
sv.lastchecktime = sv.time; |
|
} |
|
|
|
// return check if it might be visible |
|
pClient = EDICT_NUM( sv.lastcheck ); |
|
|
|
if( !SV_ClientFromEdict( pClient, true )) |
|
return svgame.edicts; |
|
|
|
mod = SV_ModelHandle( pEdict->v.modelindex ); |
|
|
|
// portals & monitors |
|
// NOTE: this specific break "radiaton tick" in normal half-life. use only as feature |
|
if( FBitSet( host.features, ENGINE_PHYSICS_PUSHER_EXT ) && mod && mod->type == mod_brush && !FBitSet( mod->flags, MODEL_HAS_ORIGIN )) |
|
{ |
|
// handle PVS origin for bmodels |
|
VectorAverage( pEdict->v.mins, pEdict->v.maxs, view ); |
|
VectorAdd( view, pEdict->v.origin, view ); |
|
} |
|
else |
|
{ |
|
VectorAdd( pEdict->v.origin, pEdict->v.view_ofs, view ); |
|
} |
|
|
|
leaf = Mod_PointInLeaf( view, sv.worldmodel->nodes ); |
|
|
|
if( CHECKVISBIT( clientpvs, leaf->cluster )) |
|
return pClient; // client which currently in PVS |
|
|
|
return svgame.edicts; |
|
} |
|
|
|
/* |
|
================= |
|
pfnEntitiesInPVS |
|
|
|
================= |
|
*/ |
|
edict_t *pfnEntitiesInPVS( edict_t *pview ) |
|
{ |
|
edict_t *pchain, *ptest; |
|
vec3_t viewpoint; |
|
edict_t *pent; |
|
int i; |
|
|
|
if( !SV_IsValidEdict( pview )) |
|
return NULL; |
|
|
|
VectorAdd( pview->v.origin, pview->v.view_ofs, viewpoint ); |
|
pchain = EDICT_NUM( 0 ); |
|
|
|
for( i = 1; i < svgame.numEntities; i++ ) |
|
{ |
|
pent = EDICT_NUM( i ); |
|
|
|
if( !SV_IsValidEdict( pent )) |
|
continue; |
|
|
|
if( pent->v.movetype == MOVETYPE_FOLLOW && SV_IsValidEdict( pent->v.aiment )) |
|
ptest = pent->v.aiment; |
|
else ptest = pent; |
|
|
|
if( SV_BoxInPVS( viewpoint, ptest->v.absmin, ptest->v.absmax )) |
|
{ |
|
pent->v.chain = pchain; |
|
pchain = pent; |
|
} |
|
} |
|
|
|
return pchain; |
|
} |
|
|
|
/* |
|
============== |
|
pfnMakeVectors |
|
|
|
============== |
|
*/ |
|
void GAME_EXPORT pfnMakeVectors( const float *rgflVector ) |
|
{ |
|
AngleVectors( rgflVector, svgame.globals->v_forward, svgame.globals->v_right, svgame.globals->v_up ); |
|
} |
|
|
|
/* |
|
============== |
|
pfnRemoveEntity |
|
|
|
free edict private mem, unlink physics etc |
|
============== |
|
*/ |
|
void GAME_EXPORT pfnRemoveEntity( edict_t *e ) |
|
{ |
|
if( !SV_IsValidEdict( e )) |
|
return; |
|
|
|
// never free client or world entity |
|
if( NUM_FOR_EDICT( e ) < ( svs.maxclients + 1 )) |
|
{ |
|
Con_Printf( S_ERROR "can't delete %s\n", ( e == EDICT_NUM( 0 )) ? "world" : "client" ); |
|
return; |
|
} |
|
|
|
SV_FreeEdict( e ); |
|
} |
|
|
|
/* |
|
============== |
|
pfnCreateNamedEntity |
|
|
|
============== |
|
*/ |
|
edict_t* GAME_EXPORT pfnCreateNamedEntity( string_t className ) |
|
{ |
|
return SV_CreateNamedEntity( NULL, className ); |
|
} |
|
|
|
/* |
|
============= |
|
pfnMakeStatic |
|
|
|
move entity to client |
|
============= |
|
*/ |
|
static void GAME_EXPORT pfnMakeStatic( edict_t *ent ) |
|
{ |
|
entity_state_t *state; |
|
|
|
if( !SV_IsValidEdict( ent )) |
|
return; |
|
|
|
// fill the entity state |
|
state = &svs.static_entities[sv.num_static_entities]; // allocate a new one |
|
svgame.dllFuncs.pfnCreateBaseline( false, NUM_FOR_EDICT( ent ), state, ent, 0, vec3_origin, vec3_origin ); |
|
state->messagenum = ent->v.model; // member modelname |
|
|
|
if( SV_CreateStaticEntity( &sv.signon, sv.num_static_entities )) |
|
sv.num_static_entities++; |
|
|
|
// remove at end of the frame |
|
SetBits( ent->v.flags, FL_KILLME ); |
|
} |
|
|
|
/* |
|
============= |
|
pfnEntIsOnFloor |
|
|
|
legacy builtin |
|
============= |
|
*/ |
|
static int GAME_EXPORT pfnEntIsOnFloor( edict_t *e ) |
|
{ |
|
if( !SV_IsValidEdict( e )) |
|
return 0; |
|
|
|
return SV_CheckBottom( e, MOVE_NORMAL ); |
|
} |
|
|
|
/* |
|
=============== |
|
pfnDropToFloor |
|
|
|
=============== |
|
*/ |
|
int GAME_EXPORT pfnDropToFloor( edict_t* e ) |
|
{ |
|
qboolean monsterClip; |
|
trace_t trace; |
|
vec3_t end; |
|
|
|
if( !SV_IsValidEdict( e )) |
|
return 0; |
|
|
|
monsterClip = FBitSet( e->v.flags, FL_MONSTERCLIP ) ? true : false; |
|
VectorCopy( e->v.origin, end ); |
|
end[2] -= 256.0f; |
|
|
|
trace = SV_Move( e->v.origin, e->v.mins, e->v.maxs, end, MOVE_NORMAL, e, monsterClip ); |
|
|
|
if( trace.allsolid ) |
|
return -1; |
|
|
|
if( trace.fraction == 1.0f ) |
|
return 0; |
|
|
|
VectorCopy( trace.endpos, e->v.origin ); |
|
SV_LinkEdict( e, false ); |
|
SetBits( e->v.flags, FL_ONGROUND ); |
|
e->v.groundentity = trace.ent; |
|
|
|
return 1; |
|
} |
|
|
|
/* |
|
=============== |
|
pfnWalkMove |
|
|
|
=============== |
|
*/ |
|
int GAME_EXPORT pfnWalkMove( edict_t *ent, float yaw, float dist, int iMode ) |
|
{ |
|
vec3_t move; |
|
|
|
if( !SV_IsValidEdict( ent )) |
|
return 0; |
|
|
|
if( !FBitSet( ent->v.flags, FL_FLY|FL_SWIM|FL_ONGROUND )) |
|
return 0; |
|
|
|
yaw = DEG2RAD( yaw ); |
|
VectorSet( move, cos( yaw ) * dist, sin( yaw ) * dist, 0.0f ); |
|
|
|
switch( iMode ) |
|
{ |
|
case WALKMOVE_NORMAL: |
|
return SV_MoveStep( ent, move, true ); |
|
case WALKMOVE_WORLDONLY: |
|
return SV_MoveTest( ent, move, true ); |
|
case WALKMOVE_CHECKONLY: |
|
return SV_MoveStep( ent, move, false); |
|
} |
|
return 0; |
|
} |
|
|
|
/* |
|
================= |
|
pfnSetOrigin |
|
|
|
================= |
|
*/ |
|
void GAME_EXPORT pfnSetOrigin( edict_t *e, const float *rgflOrigin ) |
|
{ |
|
if( !SV_IsValidEdict( e )) |
|
return; |
|
|
|
VectorCopy( rgflOrigin, e->v.origin ); |
|
SV_LinkEdict( e, false ); |
|
} |
|
|
|
/* |
|
================= |
|
SV_BuildSoundMsg |
|
|
|
================= |
|
*/ |
|
int SV_BuildSoundMsg( sizebuf_t *msg, edict_t *ent, int chan, const char *sample, int vol, float attn, int flags, int pitch, const vec3_t pos ) |
|
{ |
|
int entityIndex; |
|
int sound_idx; |
|
qboolean spawn; |
|
|
|
if( vol < 0 || vol > 255 ) |
|
{ |
|
Con_Reportf( S_ERROR "SV_StartSound: volume = %i\n", vol ); |
|
vol = bound( 0, vol, 255 ); |
|
} |
|
|
|
if( attn < 0.0f || attn > 4.0f ) |
|
{ |
|
Con_Reportf( S_ERROR "SV_StartSound: attenuation %g must be in range 0-4\n", attn ); |
|
attn = bound( 0.0f, attn, 4.0f ); |
|
} |
|
|
|
if( chan < 0 || chan > 7 ) |
|
{ |
|
Con_Reportf( S_ERROR "SV_StartSound: channel must be in range 0-7\n" ); |
|
chan = bound( 0, chan, 7 ); |
|
} |
|
|
|
if( pitch < 0 || pitch > 255 ) |
|
{ |
|
Con_Reportf( S_ERROR "SV_StartSound: pitch = %i\n", pitch ); |
|
pitch = bound( 0, pitch, 255 ); |
|
} |
|
|
|
if( !COM_CheckString( sample )) |
|
{ |
|
Con_Reportf( S_ERROR "SV_StartSound: passed NULL sample\n" ); |
|
return 0; |
|
} |
|
|
|
if( sample[0] == '!' && Q_isdigit( sample + 1 )) |
|
{ |
|
sound_idx = Q_atoi( sample + 1 ); |
|
|
|
if( sound_idx >= MAX_SOUNDS_NONSENTENCE ) |
|
{ |
|
SetBits( flags, SND_SENTENCE|SND_SEQUENCE ); |
|
sound_idx -= MAX_SOUNDS_NONSENTENCE; |
|
} |
|
else SetBits( flags, SND_SENTENCE ); |
|
} |
|
else if( sample[0] == '#' && Q_isdigit( sample + 1 )) |
|
{ |
|
SetBits( flags, SND_SENTENCE|SND_SEQUENCE ); |
|
sound_idx = Q_atoi( sample + 1 ); |
|
} |
|
else |
|
{ |
|
// '*' is special symbol to handle stream sounds |
|
// (CHAN_VOICE but cannot be overriden) |
|
// originally handled on client side |
|
if( *sample == '*' ) |
|
chan = CHAN_STREAM; |
|
|
|
// precache_sound can be used twice: cache sounds when loading |
|
// and return sound index when server is active |
|
sound_idx = SV_SoundIndex( sample ); |
|
|
|
if( !sound_idx ) |
|
{ |
|
Con_Printf( S_ERROR "SV_StartSound: %s not precached (%d)\n", sample, sound_idx ); |
|
return 0; |
|
} |
|
} |
|
|
|
spawn = FBitSet( flags, SND_RESTORE_POSITION ) ? false : true; |
|
|
|
if( SV_IsValidEdict( ent ) && SV_IsValidEdict( ent->v.aiment )) |
|
entityIndex = NUM_FOR_EDICT( ent->v.aiment ); |
|
else if( SV_IsValidEdict( ent )) |
|
entityIndex = NUM_FOR_EDICT( ent ); |
|
else entityIndex = 0; // assume world |
|
|
|
if( vol != 255 ) SetBits( flags, SND_VOLUME ); |
|
if( attn != ATTN_NONE ) SetBits( flags, SND_ATTENUATION ); |
|
if( pitch != PITCH_NORM ) SetBits( flags, SND_PITCH ); |
|
|
|
// not sending (because this is out of range) |
|
ClearBits( flags, SND_RESTORE_POSITION ); |
|
ClearBits( flags, SND_FILTER_CLIENT ); |
|
ClearBits( flags, SND_SPAWNING ); |
|
|
|
if( spawn ) MSG_BeginServerCmd( msg, svc_sound ); |
|
else MSG_BeginServerCmd( msg, svc_restoresound ); |
|
MSG_WriteUBitLong( msg, flags, MAX_SND_FLAGS_BITS ); |
|
MSG_WriteUBitLong( msg, sound_idx, MAX_SOUND_BITS ); |
|
MSG_WriteUBitLong( msg, chan, MAX_SND_CHAN_BITS ); |
|
|
|
if( FBitSet( flags, SND_VOLUME )) MSG_WriteByte( msg, vol ); |
|
if( FBitSet( flags, SND_ATTENUATION )) MSG_WriteByte( msg, attn * 64 ); |
|
if( FBitSet( flags, SND_PITCH )) MSG_WriteByte( msg, pitch ); |
|
|
|
MSG_WriteUBitLong( msg, entityIndex, MAX_ENTITY_BITS ); |
|
MSG_WriteVec3Coord( msg, pos ); |
|
|
|
return 1; |
|
} |
|
|
|
/* |
|
================= |
|
SV_StartSound |
|
|
|
================= |
|
*/ |
|
void GAME_EXPORT SV_StartSound( edict_t *ent, int chan, const char *sample, float vol, float attn, int flags, int pitch ) |
|
{ |
|
qboolean filter = false; |
|
int msg_dest; |
|
vec3_t origin; |
|
|
|
if( !SV_IsValidEdict( ent )) |
|
return; |
|
|
|
VectorAverage( ent->v.mins, ent->v.maxs, origin ); |
|
VectorAdd( origin, ent->v.origin, origin ); |
|
|
|
if( FBitSet( flags, SND_SPAWNING )) |
|
msg_dest = MSG_INIT; |
|
else if( chan == CHAN_STATIC ) |
|
msg_dest = MSG_ALL; |
|
else if( FBitSet( host.features, ENGINE_QUAKE_COMPATIBLE )) |
|
msg_dest = MSG_ALL; |
|
else msg_dest = (svs.maxclients <= 1 ) ? MSG_ALL : MSG_PAS_R; |
|
|
|
// always sending stop sound command |
|
if( FBitSet( flags, SND_STOP )) |
|
msg_dest = MSG_ALL; |
|
|
|
if( FBitSet( flags, SND_FILTER_CLIENT )) |
|
filter = true; |
|
|
|
if( SV_BuildSoundMsg( &sv.multicast, ent, chan, sample, vol * 255, attn, flags, pitch, origin )) |
|
SV_Multicast( msg_dest, origin, NULL, false, filter ); |
|
} |
|
|
|
/* |
|
================= |
|
pfnEmitAmbientSound |
|
|
|
================= |
|
*/ |
|
void GAME_EXPORT pfnEmitAmbientSound( edict_t *ent, float *pos, const char *sample, float vol, float attn, int flags, int pitch ) |
|
{ |
|
int msg_dest; |
|
|
|
if( sv.state == ss_loading ) |
|
SetBits( flags, SND_SPAWNING ); |
|
|
|
if( FBitSet( flags, SND_SPAWNING )) |
|
msg_dest = MSG_INIT; |
|
else msg_dest = MSG_ALL; |
|
|
|
// always sending stop sound command |
|
if( FBitSet( flags, SND_STOP )) |
|
msg_dest = MSG_ALL; |
|
|
|
if( SV_BuildSoundMsg( &sv.multicast, ent, CHAN_STATIC, sample, vol * 255, attn, flags, pitch, pos )) |
|
SV_Multicast( msg_dest, pos, NULL, false, false ); |
|
} |
|
|
|
/* |
|
================= |
|
SV_StartMusic |
|
|
|
================= |
|
*/ |
|
void SV_StartMusic( const char *curtrack, const char *looptrack, int position ) |
|
{ |
|
MSG_BeginServerCmd( &sv.multicast, svc_stufftext ); |
|
MSG_WriteStringf( &sv.multicast, "music \"%s\" \"%s\" %d\n", curtrack, looptrack, position ); |
|
SV_Multicast( MSG_ALL, NULL, NULL, false, false ); |
|
} |
|
|
|
/* |
|
================= |
|
pfnTraceLine |
|
|
|
================= |
|
*/ |
|
static void GAME_EXPORT pfnTraceLine( const float *v1, const float *v2, int fNoMonsters, edict_t *pentToSkip, TraceResult *ptr ) |
|
{ |
|
trace_t trace; |
|
|
|
trace = SV_Move( v1, vec3_origin, vec3_origin, v2, fNoMonsters, pentToSkip, false ); |
|
if( !SV_IsValidEdict( trace.ent )) |
|
trace.ent = svgame.edicts; |
|
SV_ConvertTrace( ptr, &trace ); |
|
} |
|
|
|
/* |
|
================= |
|
pfnTraceToss |
|
|
|
================= |
|
*/ |
|
static void GAME_EXPORT pfnTraceToss( edict_t *pent, edict_t *pentToIgnore, TraceResult *ptr ) |
|
{ |
|
trace_t trace; |
|
|
|
if( !SV_IsValidEdict( pent )) |
|
return; |
|
|
|
trace = SV_MoveToss( pent, pentToIgnore ); |
|
SV_ConvertTrace( ptr, &trace ); |
|
} |
|
|
|
/* |
|
================= |
|
pfnTraceHull |
|
|
|
================= |
|
*/ |
|
static void GAME_EXPORT pfnTraceHull( const float *v1, const float *v2, int fNoMonsters, int hullNumber, edict_t *pentToSkip, TraceResult *ptr ) |
|
{ |
|
trace_t trace; |
|
|
|
if( hullNumber < 0 || hullNumber > 3 ) |
|
hullNumber = 0; |
|
|
|
trace = SV_Move( v1, sv.worldmodel->hulls[hullNumber].clip_mins, sv.worldmodel->hulls[hullNumber].clip_maxs, v2, fNoMonsters, pentToSkip, false ); |
|
SV_ConvertTrace( ptr, &trace ); |
|
} |
|
|
|
/* |
|
============= |
|
pfnTraceMonsterHull |
|
|
|
============= |
|
*/ |
|
static int GAME_EXPORT pfnTraceMonsterHull( edict_t *pEdict, const float *v1, const float *v2, int fNoMonsters, edict_t *pentToSkip, TraceResult *ptr ) |
|
{ |
|
qboolean monsterClip; |
|
trace_t trace; |
|
|
|
if( !SV_IsValidEdict( pEdict )) |
|
return 0; |
|
|
|
monsterClip = FBitSet( pEdict->v.flags, FL_MONSTERCLIP ) ? true : false; |
|
trace = SV_Move( v1, pEdict->v.mins, pEdict->v.maxs, v2, fNoMonsters, pentToSkip, monsterClip ); |
|
SV_ConvertTrace( ptr, &trace ); |
|
|
|
if( trace.allsolid || trace.fraction != 1.0f ) |
|
return true; |
|
return false; |
|
} |
|
|
|
/* |
|
============= |
|
pfnTraceModel |
|
|
|
============= |
|
*/ |
|
static void GAME_EXPORT pfnTraceModel( const float *v1, const float *v2, int hullNumber, edict_t *pent, TraceResult *ptr ) |
|
{ |
|
float *mins, *maxs; |
|
model_t *model; |
|
trace_t trace; |
|
|
|
if( !SV_IsValidEdict( pent )) |
|
return; |
|
|
|
if( hullNumber < 0 || hullNumber > 3 ) |
|
hullNumber = 0; |
|
|
|
mins = sv.worldmodel->hulls[hullNumber].clip_mins; |
|
maxs = sv.worldmodel->hulls[hullNumber].clip_maxs; |
|
model = SV_ModelHandle( pent->v.modelindex ); |
|
|
|
if( pent->v.solid == SOLID_CUSTOM ) |
|
{ |
|
// NOTE: always goes through custom clipping move |
|
// even if our callbacks is not initialized |
|
SV_CustomClipMoveToEntity( pent, v1, mins, maxs, v2, &trace ); |
|
} |
|
else if( model && model->type == mod_brush ) |
|
{ |
|
int oldmovetype = pent->v.movetype; |
|
int oldsolid = pent->v.solid; |
|
pent->v.movetype = MOVETYPE_PUSH; |
|
pent->v.solid = SOLID_BSP; |
|
|
|
SV_ClipMoveToEntity( pent, v1, mins, maxs, v2, &trace ); |
|
|
|
pent->v.movetype = oldmovetype; |
|
pent->v.solid = oldsolid; |
|
} |
|
else |
|
{ |
|
SV_ClipMoveToEntity( pent, v1, mins, maxs, v2, &trace ); |
|
} |
|
|
|
SV_ConvertTrace( ptr, &trace ); |
|
} |
|
|
|
/* |
|
============= |
|
pfnTraceTexture |
|
|
|
returns texture basename |
|
============= |
|
*/ |
|
static const char *pfnTraceTexture( edict_t *pTextureEntity, const float *v1, const float *v2 ) |
|
{ |
|
if( !SV_IsValidEdict( pTextureEntity )) |
|
return NULL; |
|
|
|
return SV_TraceTexture( pTextureEntity, v1, v2 ); |
|
} |
|
|
|
/* |
|
============= |
|
pfnTraceSphere |
|
|
|
OBSOLETE, UNUSED |
|
============= |
|
*/ |
|
void GAME_EXPORT pfnTraceSphere( const float *v1, const float *v2, int fNoMonsters, float radius, edict_t *pentToSkip, TraceResult *ptr ) |
|
{ |
|
} |
|
|
|
/* |
|
============= |
|
pfnGetAimVector |
|
|
|
NOTE: speed is unused |
|
============= |
|
*/ |
|
void GAME_EXPORT pfnGetAimVector( edict_t* ent, float speed, float *rgflReturn ) |
|
{ |
|
edict_t *check; |
|
vec3_t start, dir, end, bestdir; |
|
float dist, bestdist; |
|
int i, j; |
|
trace_t tr; |
|
|
|
VectorCopy( svgame.globals->v_forward, rgflReturn ); // assume failure if it returns early |
|
|
|
if( !SV_IsValidEdict( ent ) || FBitSet( ent->v.flags, FL_FAKECLIENT )) |
|
return; |
|
|
|
VectorCopy( ent->v.origin, start ); |
|
VectorAdd( start, ent->v.view_ofs, start ); |
|
|
|
// try sending a trace straight |
|
VectorCopy( svgame.globals->v_forward, dir ); |
|
VectorMA( start, 2048, dir, end ); |
|
tr = SV_Move( start, vec3_origin, vec3_origin, end, MOVE_NORMAL, ent, false ); |
|
|
|
// don't aim at teammate |
|
if( tr.ent && ( tr.ent->v.takedamage == DAMAGE_AIM || ent->v.team <= 0 || ent->v.team != tr.ent->v.team )) |
|
return; |
|
|
|
// try all possible entities |
|
VectorCopy( svgame.globals->v_forward, bestdir ); |
|
bestdist = Cvar_VariableValue( "sv_aim" ); |
|
|
|
check = EDICT_NUM( 1 ); // start at first client |
|
for( i = 1; i < svgame.numEntities; i++, check++ ) |
|
{ |
|
if( check->v.takedamage != DAMAGE_AIM ) |
|
continue; |
|
|
|
if( FBitSet( check->v.flags, FL_FAKECLIENT )) |
|
continue; |
|
|
|
if( ent->v.team > 0 && ent->v.team == check->v.team ) |
|
continue; |
|
|
|
if( check == ent ) |
|
continue; |
|
|
|
for( j = 0; j < 3; j++ ) |
|
end[j] = check->v.origin[j] + 0.5f * (check->v.mins[j] + check->v.maxs[j]); |
|
|
|
VectorSubtract( end, start, dir ); |
|
VectorNormalize( dir ); |
|
dist = DotProduct( dir, svgame.globals->v_forward ); |
|
|
|
if( dist < bestdist ) |
|
continue; // to far to turn |
|
|
|
tr = SV_Move( start, vec3_origin, vec3_origin, end, MOVE_NORMAL, ent, false ); |
|
|
|
if( tr.ent == check ) |
|
{ |
|
// can shoot at this one |
|
VectorCopy( dir, bestdir ); |
|
bestdist = dist; |
|
} |
|
} |
|
|
|
VectorCopy( bestdir, rgflReturn ); |
|
} |
|
|
|
/* |
|
========= |
|
pfnServerCommand |
|
|
|
========= |
|
*/ |
|
void GAME_EXPORT pfnServerCommand( const char* str ) |
|
{ |
|
if( !SV_IsValidCmd( str )) |
|
Con_Printf( S_ERROR "bad server command %s\n", str ); |
|
else Cbuf_AddText( str ); |
|
} |
|
|
|
/* |
|
========= |
|
pfnServerExecute |
|
|
|
========= |
|
*/ |
|
void GAME_EXPORT pfnServerExecute( void ) |
|
{ |
|
Cbuf_Execute(); |
|
} |
|
|
|
/* |
|
========= |
|
pfnClientCommand |
|
|
|
========= |
|
*/ |
|
void GAME_EXPORT pfnClientCommand( edict_t* pEdict, char* szFmt, ... ) _format( 2 ); |
|
void GAME_EXPORT pfnClientCommand( edict_t* pEdict, char* szFmt, ... ) |
|
{ |
|
sv_client_t *cl; |
|
string buffer; |
|
va_list args; |
|
|
|
if( sv.state != ss_active ) |
|
return; // early out |
|
|
|
if(( cl = SV_ClientFromEdict( pEdict, true )) == NULL ) |
|
{ |
|
Con_Printf( S_ERROR "stuffcmd: client is not spawned!\n" ); |
|
return; |
|
} |
|
|
|
if( FBitSet( cl->flags, FCL_FAKECLIENT )) |
|
return; |
|
|
|
va_start( args, szFmt ); |
|
Q_vsnprintf( buffer, MAX_STRING, szFmt, args ); |
|
va_end( args ); |
|
|
|
if( SV_IsValidCmd( buffer )) |
|
{ |
|
MSG_BeginServerCmd( &cl->netchan.message, svc_stufftext ); |
|
MSG_WriteString( &cl->netchan.message, buffer ); |
|
} |
|
else Con_Printf( S_ERROR "Tried to stuff bad command %s\n", buffer ); |
|
} |
|
|
|
/* |
|
================= |
|
pfnParticleEffect |
|
|
|
Make sure the event gets sent to all clients |
|
================= |
|
*/ |
|
void GAME_EXPORT pfnParticleEffect( const float *org, const float *dir, float color, float count ) |
|
{ |
|
int v; |
|
|
|
if( MSG_GetNumBytesLeft( &sv.datagram ) < 16 ) |
|
return; |
|
|
|
MSG_BeginServerCmd( &sv.datagram, svc_particle ); |
|
MSG_WriteVec3Coord( &sv.datagram, org ); |
|
v = bound( -128, dir[0] * 16.0f, 127 ); |
|
MSG_WriteChar( &sv.datagram, v ); |
|
v = bound( -128, dir[1] * 16.0f, 127 ); |
|
MSG_WriteChar( &sv.datagram, v ); |
|
v = bound( -128, dir[2] * 16.0f, 127 ); |
|
MSG_WriteChar( &sv.datagram, v ); |
|
MSG_WriteByte( &sv.datagram, count ); |
|
MSG_WriteByte( &sv.datagram, color ); |
|
MSG_WriteByte( &sv.datagram, 0 ); // z-vel |
|
} |
|
|
|
/* |
|
=============== |
|
pfnLightStyle |
|
|
|
=============== |
|
*/ |
|
void GAME_EXPORT pfnLightStyle( int style, const char* val ) |
|
{ |
|
if( style < 0 ) style = 0; |
|
if( style >= MAX_LIGHTSTYLES ) |
|
Host_Error( "SV_LightStyle: style: %i >= %d", style, MAX_LIGHTSTYLES ); |
|
if( sv.loadgame ) return; // don't let the world overwrite our restored styles |
|
|
|
SV_SetLightStyle( style, val, 0.0f ); // set correct style |
|
} |
|
|
|
/* |
|
================= |
|
pfnDecalIndex |
|
|
|
register decal name on client |
|
================= |
|
*/ |
|
int GAME_EXPORT pfnDecalIndex( const char *m ) |
|
{ |
|
int i; |
|
|
|
if( !COM_CheckString( m )) |
|
return -1; |
|
|
|
for( i = 1; i < MAX_DECALS && host.draw_decals[i][0]; i++ ) |
|
{ |
|
if( !Q_stricmp( host.draw_decals[i], m )) |
|
return i; |
|
} |
|
|
|
return -1; |
|
} |
|
|
|
/* |
|
============= |
|
pfnMessageBegin |
|
|
|
============= |
|
*/ |
|
void GAME_EXPORT pfnMessageBegin( int msg_dest, int msg_num, const float *pOrigin, edict_t *ed ) |
|
{ |
|
int i, iSize; |
|
|
|
if( svgame.msg_started ) |
|
Host_Error( "MessageBegin: New message started when msg '%s' has not been sent yet\n", svgame.msg_name ); |
|
svgame.msg_started = true; |
|
|
|
// check range |
|
msg_num = bound( svc_bad, msg_num, 255 ); |
|
|
|
if( msg_num <= svc_lastmsg ) |
|
{ |
|
svgame.msg_index = -msg_num; // this is a system message |
|
svgame.msg_name = svc_strings[msg_num]; |
|
|
|
if( msg_num == svc_temp_entity ) |
|
iSize = -1; // temp entity have variable size |
|
else iSize = 0; |
|
} |
|
else |
|
{ |
|
// check for existing |
|
for( i = 1; i < MAX_USER_MESSAGES && svgame.msg[i].name[0]; i++ ) |
|
{ |
|
if( svgame.msg[i].number == msg_num ) |
|
break; // found |
|
} |
|
|
|
if( i == MAX_USER_MESSAGES ) |
|
{ |
|
Host_Error( "MessageBegin: tried to send unregistered message %i\n", msg_num ); |
|
return; |
|
} |
|
|
|
svgame.msg_name = svgame.msg[i].name; |
|
iSize = svgame.msg[i].size; |
|
svgame.msg_index = i; |
|
} |
|
|
|
MSG_WriteCmdExt( &sv.multicast, msg_num, NS_SERVER, svgame.msg_name ); |
|
|
|
// save message destination |
|
if( pOrigin ) VectorCopy( pOrigin, svgame.msg_org ); |
|
else VectorClear( svgame.msg_org ); |
|
|
|
if( iSize == -1 ) |
|
{ |
|
// variable sized messages sent size as first short |
|
svgame.msg_size_index = MSG_GetNumBytesWritten( &sv.multicast ); |
|
MSG_WriteWord( &sv.multicast, 0 ); // reserve space for now |
|
} |
|
else svgame.msg_size_index = -1; // message has constant size |
|
|
|
svgame.msg_realsize = 0; |
|
svgame.msg_dest = msg_dest; |
|
svgame.msg_ent = ed; |
|
|
|
// enable message tracing |
|
svgame.msg_trace = sv_trace_messages.value != 0 && |
|
msg_num > svc_lastmsg && |
|
Q_strcmp( svgame.msg_name, "ReqState" ); |
|
|
|
if( svgame.msg_trace ) Con_Printf( "^3%s( %i, %s )\n", __FUNCTION__, msg_dest, svgame.msg_name ); |
|
} |
|
|
|
/* |
|
============= |
|
pfnMessageEnd |
|
|
|
============= |
|
*/ |
|
void GAME_EXPORT pfnMessageEnd( void ) |
|
{ |
|
const char *name = "Unknown"; |
|
float *org = NULL; |
|
word realsize; |
|
|
|
if( svgame.msg_name ) name = svgame.msg_name; |
|
if( !svgame.msg_started ) Host_Error( "MessageEnd: called with no active message\n" ); |
|
svgame.msg_started = false; |
|
|
|
if( MSG_CheckOverflow( &sv.multicast )) |
|
{ |
|
Con_Printf( S_ERROR "MessageEnd: %s has overflow multicast buffer\n", name ); |
|
MSG_Clear( &sv.multicast ); |
|
return; |
|
} |
|
|
|
// check for system message |
|
if( svgame.msg_index < 0 ) |
|
{ |
|
if( svgame.msg_size_index != -1 ) |
|
{ |
|
// variable sized message |
|
if( svgame.msg_realsize > MAX_USERMSG_LENGTH ) |
|
{ |
|
Con_Printf( S_ERROR "SV_Multicast: %s too long (more than %d bytes)\n", name, MAX_USERMSG_LENGTH ); |
|
MSG_Clear( &sv.multicast ); |
|
return; |
|
} |
|
else if( svgame.msg_realsize < 0 ) |
|
{ |
|
Con_Printf( S_ERROR "SV_Multicast: %s writes NULL message\n", name ); |
|
MSG_Clear( &sv.multicast ); |
|
return; |
|
} |
|
|
|
realsize = svgame.msg_realsize; |
|
memcpy( &sv.multicast.pData[svgame.msg_size_index], &realsize, sizeof( realsize )); |
|
} |
|
} |
|
else if( svgame.msg[svgame.msg_index].size != -1 ) |
|
{ |
|
int expsize = svgame.msg[svgame.msg_index].size; |
|
int realsize = svgame.msg_realsize; |
|
|
|
// compare sizes |
|
if( expsize != realsize ) |
|
{ |
|
Con_Printf( S_ERROR "SV_Multicast: %s expected %i bytes, it written %i. Ignored.\n", name, expsize, realsize ); |
|
MSG_Clear( &sv.multicast ); |
|
return; |
|
} |
|
} |
|
else if( svgame.msg_size_index != -1 ) |
|
{ |
|
// variable sized message |
|
if( svgame.msg_realsize > MAX_USERMSG_LENGTH ) |
|
{ |
|
Con_Printf( S_ERROR "SV_Multicast: %s too long (more than %d bytes)\n", name, MAX_USERMSG_LENGTH ); |
|
MSG_Clear( &sv.multicast ); |
|
return; |
|
} |
|
else if( svgame.msg_realsize < 0 ) |
|
{ |
|
Con_Printf( S_ERROR "SV_Multicast: %s writes NULL message\n", name ); |
|
MSG_Clear( &sv.multicast ); |
|
return; |
|
} |
|
|
|
realsize = svgame.msg_realsize; |
|
memcpy( &sv.multicast.pData[svgame.msg_size_index], &realsize, sizeof( realsize )); |
|
} |
|
else |
|
{ |
|
// this should never happen |
|
Con_Printf( S_ERROR "SV_Multicast: %s have encountered error\n", name ); |
|
MSG_Clear( &sv.multicast ); |
|
return; |
|
} |
|
|
|
// update some messages in case their was format was changed and we want to keep backward compatibility |
|
if( svgame.msg_index < 0 ) |
|
{ |
|
int svc_msg = abs( svgame.msg_index ); |
|
|
|
if(( svc_msg == svc_finale || svc_msg == svc_cutscene ) && svgame.msg_realsize == 0 ) |
|
MSG_WriteChar( &sv.multicast, 0 ); // write null string |
|
} |
|
|
|
if( !VectorIsNull( svgame.msg_org )) org = svgame.msg_org; |
|
svgame.msg_dest = bound( MSG_BROADCAST, svgame.msg_dest, MSG_SPEC ); |
|
|
|
SV_Multicast( svgame.msg_dest, org, svgame.msg_ent, true, false ); |
|
|
|
if( svgame.msg_trace ) Con_Printf( "^3%s()\n", __FUNCTION__ ); |
|
} |
|
|
|
/* |
|
============= |
|
pfnWriteByte |
|
|
|
============= |
|
*/ |
|
void GAME_EXPORT pfnWriteByte( int iValue ) |
|
{ |
|
if( iValue == -1 ) iValue = 0xFF; // convert char to byte |
|
MSG_WriteByte( &sv.multicast, (byte)iValue ); |
|
if( svgame.msg_trace ) Con_Printf( "\t^3%s( %i )\n", __FUNCTION__, iValue ); |
|
svgame.msg_realsize++; |
|
} |
|
|
|
/* |
|
============= |
|
pfnWriteChar |
|
|
|
============= |
|
*/ |
|
void GAME_EXPORT pfnWriteChar( int iValue ) |
|
{ |
|
MSG_WriteChar( &sv.multicast, (signed char)iValue ); |
|
if( svgame.msg_trace ) Con_Printf( "\t^3%s( %i )\n", __FUNCTION__, iValue ); |
|
svgame.msg_realsize++; |
|
} |
|
|
|
/* |
|
============= |
|
pfnWriteShort |
|
|
|
============= |
|
*/ |
|
void GAME_EXPORT pfnWriteShort( int iValue ) |
|
{ |
|
MSG_WriteShort( &sv.multicast, (short)iValue ); |
|
if( svgame.msg_trace ) Con_Printf( "\t^3%s( %i )\n", __FUNCTION__, iValue ); |
|
svgame.msg_realsize += 2; |
|
} |
|
|
|
/* |
|
============= |
|
pfnWriteLong |
|
|
|
============= |
|
*/ |
|
void GAME_EXPORT pfnWriteLong( int iValue ) |
|
{ |
|
MSG_WriteLong( &sv.multicast, iValue ); |
|
if( svgame.msg_trace ) Con_Printf( "\t^3%s( %i )\n", __FUNCTION__, iValue ); |
|
svgame.msg_realsize += 4; |
|
} |
|
|
|
/* |
|
============= |
|
pfnWriteAngle |
|
|
|
this is low-res angle |
|
============= |
|
*/ |
|
void GAME_EXPORT pfnWriteAngle( float flValue ) |
|
{ |
|
int iAngle = ((int)(( flValue ) * 256 / 360) & 255); |
|
|
|
MSG_WriteChar( &sv.multicast, iAngle ); |
|
if( svgame.msg_trace ) Con_Printf( "\t^3%s( %f )\n", __FUNCTION__, flValue ); |
|
svgame.msg_realsize += 1; |
|
} |
|
|
|
/* |
|
============= |
|
pfnWriteCoord |
|
|
|
============= |
|
*/ |
|
void GAME_EXPORT pfnWriteCoord( float flValue ) |
|
{ |
|
MSG_WriteCoord( &sv.multicast, flValue ); |
|
if( svgame.msg_trace ) Con_Printf( "\t^3%s( %f )\n", __FUNCTION__, flValue ); |
|
svgame.msg_realsize += 2; |
|
} |
|
|
|
/* |
|
============= |
|
pfnWriteBytes |
|
|
|
============= |
|
*/ |
|
void pfnWriteBytes( const byte *bytes, int count ) |
|
{ |
|
MSG_WriteBytes( &sv.multicast, bytes, count ); |
|
if( svgame.msg_trace ) Con_Printf( "\t^3%s( %i )\n", __FUNCTION__, count ); |
|
svgame.msg_realsize += count; |
|
} |
|
|
|
/* |
|
============= |
|
pfnWriteString |
|
|
|
============= |
|
*/ |
|
void GAME_EXPORT pfnWriteString( const char *src ) |
|
{ |
|
static char string[MAX_USERMSG_LENGTH]; |
|
int len = Q_strlen( src ) + 1; |
|
int rem = sizeof( string ) - 1; |
|
char *dst; |
|
|
|
if( len == 1 ) |
|
{ |
|
MSG_WriteChar( &sv.multicast, 0 ); |
|
svgame.msg_realsize += 1; |
|
return; // fast exit |
|
} |
|
|
|
// prepare string to sending |
|
dst = string; |
|
|
|
while( 1 ) |
|
{ |
|
// some escaped chars parsed as two symbols - merge it here |
|
if( src[0] == '\\' && src[1] == 'n' ) |
|
{ |
|
*dst++ = '\n'; |
|
src += 2; |
|
len -= 1; |
|
} |
|
else if( src[0] == '\\' && src[1] == 'r' ) |
|
{ |
|
*dst++ = '\r'; |
|
src += 2; |
|
len -= 1; |
|
} |
|
else if( src[0] == '\\' && src[1] == 't' ) |
|
{ |
|
*dst++ = '\t'; |
|
src += 2; |
|
len -= 1; |
|
} |
|
else if(( *dst++ = *src++ ) == 0 ) |
|
break; |
|
|
|
if( --rem <= 0 ) |
|
{ |
|
Con_Printf( S_ERROR "pfnWriteString: exceeds %i symbols\n", len ); |
|
*dst = '\0'; // string end (not included in count) |
|
len = Q_strlen( string ) + 1; |
|
break; |
|
} |
|
} |
|
|
|
*dst = '\0'; // string end (not included in count) |
|
MSG_WriteString( &sv.multicast, string ); |
|
if( svgame.msg_trace ) Con_Printf( "\t^3%s( %s )\n", __FUNCTION__, string ); |
|
|
|
// NOTE: some messages with constant string length can be marked as known sized |
|
svgame.msg_realsize += len; |
|
} |
|
|
|
/* |
|
============= |
|
pfnWriteEntity |
|
|
|
============= |
|
*/ |
|
void GAME_EXPORT pfnWriteEntity( int iValue ) |
|
{ |
|
if( iValue < 0 || iValue >= svgame.numEntities ) |
|
Host_Error( "MSG_WriteEntity: invalid entnumber %i\n", iValue ); |
|
MSG_WriteShort( &sv.multicast, (short)iValue ); |
|
if( svgame.msg_trace ) Con_Printf( "\t^3%s( %i )\n", __FUNCTION__, iValue ); |
|
svgame.msg_realsize += 2; |
|
} |
|
|
|
/* |
|
============= |
|
pfnAlertMessage |
|
|
|
============= |
|
*/ |
|
static void pfnAlertMessage( ALERT_TYPE type, char *szFmt, ... ) _format( 2 ); |
|
static void GAME_EXPORT pfnAlertMessage( ALERT_TYPE type, char *szFmt, ... ) |
|
{ |
|
char buffer[2048]; |
|
va_list args; |
|
|
|
if( type == at_logged && svs.maxclients > 1 ) |
|
{ |
|
va_start( args, szFmt ); |
|
Q_vsnprintf( buffer, sizeof( buffer ), szFmt, args ); |
|
va_end( args ); |
|
Log_Printf( "%s", buffer ); |
|
return; |
|
} |
|
|
|
if( host_developer.value <= DEV_NONE ) |
|
return; |
|
|
|
// g-cont: some mods have wrong aiconsole messages that crash the engine |
|
if( type == at_aiconsole && host_developer.value < DEV_EXTENDED ) |
|
return; |
|
|
|
va_start( args, szFmt ); |
|
Q_vsnprintf( buffer, sizeof( buffer ), szFmt, args ); |
|
va_end( args ); |
|
|
|
// check message for pass |
|
switch( type ) |
|
{ |
|
case at_notice: |
|
Con_Printf( S_NOTE "%s", buffer ); |
|
break; |
|
case at_console: |
|
Con_Printf( "%s", buffer ); |
|
break; |
|
case at_aiconsole: |
|
Con_DPrintf( "%s", buffer ); |
|
break; |
|
case at_warning: |
|
Con_Printf( S_WARN "%s", buffer ); |
|
break; |
|
case at_error: |
|
Con_Printf( S_ERROR "%s", buffer ); |
|
break; |
|
} |
|
} |
|
|
|
/* |
|
============= |
|
pfnEngineFprintf |
|
|
|
OBSOLETE, UNUSED |
|
============= |
|
*/ |
|
static void pfnEngineFprintf( FILE *pfile, char *szFmt, ... ) _format( 2 ); |
|
static void GAME_EXPORT pfnEngineFprintf( FILE *pfile, char *szFmt, ... ) |
|
{ |
|
} |
|
|
|
/* |
|
============= |
|
pfnBuildSoundMsg |
|
|
|
Customizable sound message |
|
============= |
|
*/ |
|
void GAME_EXPORT pfnBuildSoundMsg( edict_t *pSource, int chan, const char *samp, float fvol, float attn, int fFlags, int pitch, int msg_dest, int msg_type, const float *pOrigin, edict_t *pSend ) |
|
{ |
|
pfnMessageBegin( msg_dest, msg_type, pOrigin, pSend ); |
|
SV_BuildSoundMsg( &sv.multicast, pSource, chan, samp, fvol * 255, attn, fFlags, pitch, pOrigin ); |
|
pfnMessageEnd(); |
|
} |
|
|
|
/* |
|
============= |
|
pfnPvAllocEntPrivateData |
|
|
|
============= |
|
*/ |
|
void *pfnPvAllocEntPrivateData( edict_t *pEdict, long cb ) |
|
{ |
|
Assert( pEdict != NULL ); |
|
|
|
SV_FreePrivateData( pEdict ); |
|
|
|
if( cb > 0 ) |
|
{ |
|
// a poke646 have memory corrupt in somewhere - this is trashed last sixteen bytes :( |
|
pEdict->pvPrivateData = Mem_Calloc( svgame.mempool, (cb + 15) & ~15 ); |
|
} |
|
|
|
return pEdict->pvPrivateData; |
|
} |
|
|
|
/* |
|
============= |
|
pfnPvEntPrivateData |
|
|
|
we already have copy of this function in 'enginecallback.h' :-) |
|
============= |
|
*/ |
|
void *pfnPvEntPrivateData( edict_t *pEdict ) |
|
{ |
|
if( pEdict ) |
|
return pEdict->pvPrivateData; |
|
return NULL; |
|
} |
|
|
|
|
|
#ifdef XASH_64BIT |
|
static struct str64_s |
|
{ |
|
size_t maxstringarray; |
|
qboolean allowdup; |
|
char *staticstringarray; |
|
char *pstringarray; |
|
char *pstringarraystatic; |
|
char *pstringbase; |
|
char *poldstringbase; |
|
char *plast; |
|
qboolean dynamic; |
|
size_t maxalloc; |
|
size_t numdups; |
|
size_t numoverflows; |
|
size_t totalalloc; |
|
} str64; |
|
#endif |
|
|
|
/* |
|
================== |
|
SV_EmptyStringPool |
|
|
|
Free strings on server stop. Reset string pointer on 64 bits |
|
================== |
|
*/ |
|
void SV_EmptyStringPool( void ) |
|
{ |
|
#ifdef XASH_64BIT |
|
if( str64.dynamic ) // switch only after array fill (more space for multiplayer games) |
|
str64.pstringbase = str64.pstringarray; |
|
else |
|
{ |
|
str64.pstringbase = str64.poldstringbase = str64.pstringarraystatic; |
|
str64.plast = str64.pstringbase + 1; |
|
} |
|
#else |
|
Mem_EmptyPool( svgame.stringspool ); |
|
#endif |
|
} |
|
|
|
/* |
|
=============== |
|
SV_SetStringArrayMode |
|
|
|
use different arrays on 64 bit platforms |
|
set dynamic after complete server spawn |
|
this helps not to lose strings that belongs to static game part |
|
=============== |
|
*/ |
|
void SV_SetStringArrayMode( qboolean dynamic ) |
|
{ |
|
#ifdef XASH_64BIT |
|
Con_Reportf( "SV_SetStringArrayMode(%d) %d\n", dynamic, str64.dynamic ); |
|
|
|
if( dynamic == str64.dynamic ) |
|
return; |
|
|
|
str64.dynamic = dynamic; |
|
|
|
SV_EmptyStringPool(); |
|
#endif |
|
} |
|
|
|
#if XASH_64BIT && !XASH_WIN32 && !XASH_APPLE && !XASH_NSWITCH |
|
#define USE_MMAP |
|
#include <sys/mman.h> |
|
#endif |
|
|
|
/* |
|
================== |
|
SV_AllocStringPool |
|
|
|
alloc string pool on 32bit platforms |
|
alloc string array near the server library on 64bit platforms if possible |
|
alloc string array somewhere if not (MAKE_STRING will not work. Always call ALLOC_STRING instead, or crash) |
|
this case need patched game dll with MAKE_STRING checking ptrdiff size |
|
================== |
|
*/ |
|
void SV_AllocStringPool( void ) |
|
{ |
|
#ifdef XASH_64BIT |
|
void *ptr = NULL; |
|
string lenstr; |
|
|
|
Con_Reportf( "SV_AllocStringPool()\n" ); |
|
if( Sys_GetParmFromCmdLine( "-str64alloc", lenstr ) ) |
|
{ |
|
str64.maxstringarray = Q_atoi( lenstr ); |
|
if( str64.maxstringarray < 1024 || str64.maxstringarray >= INT_MAX ) |
|
str64.maxstringarray = 65536; |
|
} |
|
else str64.maxstringarray = 65536; |
|
if( Sys_CheckParm( "-str64dup" ) ) |
|
str64.allowdup = true; |
|
|
|
#ifdef USE_MMAP |
|
{ |
|
uint flags; |
|
size_t pagesize = sysconf( _SC_PAGESIZE ); |
|
int arrlen = (str64.maxstringarray * 2) & ~(pagesize - 1); |
|
void *base = svgame.dllFuncs.pfnGameInit; |
|
void *start = svgame.hInstance - arrlen; |
|
|
|
#if defined(MAP_ANON) |
|
flags = MAP_ANON | MAP_PRIVATE; |
|
#elif defined(MAP_ANONYMOUS) |
|
flags = MAP_ANONYMOUS | MAP_PRIVATE; |
|
#endif |
|
|
|
while( start - base > INT_MIN ) |
|
{ |
|
void *mapptr = mmap((void*)((unsigned long)start & ~(pagesize - 1)), arrlen, PROT_READ | PROT_WRITE, flags, 0, 0 ); |
|
if( mapptr && mapptr != (void*)-1 && mapptr - base > INT_MIN && mapptr - base < INT_MAX ) |
|
{ |
|
ptr = mapptr; |
|
break; |
|
} |
|
if( mapptr ) munmap( mapptr, arrlen ); |
|
start -= arrlen; |
|
} |
|
|
|
if( !ptr ) |
|
{ |
|
start = base; |
|
while( start - base < INT_MAX ) |
|
{ |
|
void *mapptr = mmap((void*)((unsigned long)start & ~(pagesize - 1)), arrlen, PROT_READ | PROT_WRITE, flags, 0, 0 ); |
|
if( mapptr && mapptr != (void*)-1 && mapptr - base > INT_MIN && mapptr - base < INT_MAX ) |
|
{ |
|
ptr = mapptr; |
|
break; |
|
} |
|
if( mapptr ) munmap( mapptr, arrlen ); |
|
start += arrlen; |
|
} |
|
} |
|
|
|
|
|
if( ptr ) |
|
{ |
|
Con_Reportf( "SV_AllocStringPool: Allocated string array near the server library: %p %p\n", base, ptr ); |
|
|
|
} |
|
else |
|
{ |
|
Con_Reportf( "SV_AllocStringPool: Failed to allocate string array near the server library!\n" ); |
|
ptr = str64.staticstringarray = Mem_Calloc(host.mempool, str64.maxstringarray * 2); |
|
} |
|
} |
|
#else |
|
ptr = str64.staticstringarray = Mem_Calloc(host.mempool, str64.maxstringarray * 2); |
|
#endif |
|
|
|
str64.pstringarray = ptr; |
|
str64.pstringarraystatic = (byte*)ptr + str64.maxstringarray; |
|
str64.pstringbase = str64.poldstringbase = ptr; |
|
str64.plast = (byte*)ptr + 1; |
|
svgame.globals->pStringBase = ptr; |
|
#else |
|
svgame.stringspool = Mem_AllocPool( "Server Strings" ); |
|
svgame.globals->pStringBase = ""; |
|
#endif |
|
} |
|
|
|
void SV_FreeStringPool( void ) |
|
{ |
|
#ifdef XASH_64BIT |
|
Con_Reportf( "SV_FreeStringPool()\n" ); |
|
|
|
#ifdef USE_MMAP |
|
if( str64.pstringarray != str64.staticstringarray ) |
|
munmap( str64.pstringarray, (str64.maxstringarray * 2) & ~(sysconf( _SC_PAGESIZE ) - 1) ); |
|
else |
|
#endif |
|
Mem_Free( str64.staticstringarray ); |
|
#else |
|
Mem_FreePool( &svgame.stringspool ); |
|
#endif |
|
} |
|
|
|
/* |
|
============= |
|
SV_AllocString |
|
|
|
allocate new engine string |
|
on 64bit platforms find in array string if deduplication enabled (default) |
|
if not found, add to array |
|
use -str64dup to disable deduplication, -str64alloc to set array size |
|
============= |
|
*/ |
|
string_t GAME_EXPORT SV_AllocString( const char *szValue ) |
|
{ |
|
const char *newString = NULL; |
|
int cmp; |
|
|
|
if( svgame.physFuncs.pfnAllocString != NULL ) |
|
return svgame.physFuncs.pfnAllocString( szValue ); |
|
|
|
#ifdef XASH_64BIT |
|
cmp = 1; |
|
|
|
if( !str64.allowdup ) |
|
for( newString = str64.poldstringbase + 1; |
|
newString < str64.plast && ( cmp = Q_strcmp( newString, szValue ) ); |
|
newString += Q_strlen( newString ) + 1 ); |
|
|
|
if( cmp ) |
|
{ |
|
uint len = Q_strlen( szValue ); |
|
|
|
if( str64.plast - str64.poldstringbase + len + 2 > str64.maxstringarray ) |
|
{ |
|
str64.plast = str64.pstringbase + 1; |
|
str64.poldstringbase = str64.pstringbase; |
|
str64.numoverflows++; |
|
} |
|
|
|
//MsgDev( D_NOTE, "SV_AllocString: %ld %s\n", str64.plast - svgame.globals->pStringBase, szValue ); |
|
memcpy( str64.plast, szValue, len + 1 ); |
|
str64.totalalloc += len + 1; |
|
|
|
newString = str64.plast; |
|
str64.plast += len + 1; |
|
} |
|
else |
|
str64.numdups++; |
|
//MsgDev( D_NOTE, "SV_AllocString: dup %ld %s\n", newString - svgame.globals->pStringBase, szValue ); |
|
|
|
if( newString - str64.pstringarray > str64.maxalloc ) |
|
str64.maxalloc = newString - str64.pstringarray; |
|
|
|
return newString - svgame.globals->pStringBase; |
|
#else |
|
newString = _copystring( svgame.stringspool, szValue, __FILE__, __LINE__ ); |
|
return newString - svgame.globals->pStringBase; |
|
#endif |
|
} |
|
|
|
#ifdef XASH_64BIT |
|
void SV_PrintStr64Stats_f( void ) |
|
{ |
|
Msg( "====================\n" ); |
|
Msg( "64 bit string pool statistics\n" ); |
|
Msg( "====================\n" ); |
|
Msg( "string array size: %lu\n", str64.maxstringarray ); |
|
Msg( "total alloc %lu\n", str64.totalalloc ); |
|
Msg( "maximum array usage: %lu\n", str64.maxalloc ); |
|
Msg( "overflow counter: %lu\n", str64.numoverflows ); |
|
Msg( "dup string counter: %lu\n", str64.numdups ); |
|
} |
|
#endif |
|
|
|
/* |
|
============= |
|
SV_MakeString |
|
|
|
make constant string |
|
============= |
|
*/ |
|
string_t SV_MakeString( const char *szValue ) |
|
{ |
|
if( svgame.physFuncs.pfnMakeString != NULL ) |
|
return svgame.physFuncs.pfnMakeString( szValue ); |
|
#ifdef XASH_64BIT |
|
{ |
|
long long ptrdiff = szValue - svgame.globals->pStringBase; |
|
if( ptrdiff > INT_MAX || ptrdiff < INT_MIN ) |
|
return SV_AllocString(szValue); |
|
else |
|
return (int)ptrdiff; |
|
} |
|
#else |
|
return szValue - svgame.globals->pStringBase; |
|
#endif |
|
} |
|
|
|
/* |
|
============= |
|
SV_GetString |
|
|
|
============= |
|
*/ |
|
const char *SV_GetString( string_t iString ) |
|
{ |
|
if( svgame.physFuncs.pfnGetString != NULL ) |
|
return svgame.physFuncs.pfnGetString( iString ); |
|
return (svgame.globals->pStringBase + iString); |
|
} |
|
|
|
/* |
|
============= |
|
pfnGetVarsOfEnt |
|
|
|
============= |
|
*/ |
|
entvars_t *pfnGetVarsOfEnt( edict_t *pEdict ) |
|
{ |
|
if( pEdict ) |
|
return &pEdict->v; |
|
return NULL; |
|
} |
|
|
|
/* |
|
============= |
|
pfnPEntityOfEntOffset |
|
|
|
============= |
|
*/ |
|
edict_t* GAME_EXPORT pfnPEntityOfEntOffset( int iEntOffset ) |
|
{ |
|
return (edict_t *)((byte *)svgame.edicts + iEntOffset); |
|
} |
|
|
|
/* |
|
============= |
|
pfnEntOffsetOfPEntity |
|
|
|
============= |
|
*/ |
|
int GAME_EXPORT pfnEntOffsetOfPEntity( const edict_t *pEdict ) |
|
{ |
|
return (byte *)pEdict - (byte *)svgame.edicts; |
|
} |
|
|
|
/* |
|
============= |
|
pfnIndexOfEdict |
|
|
|
============= |
|
*/ |
|
int GAME_EXPORT pfnIndexOfEdict( const edict_t *pEdict ) |
|
{ |
|
int number; |
|
|
|
if( !pEdict ) return 0; // world ? |
|
|
|
number = NUM_FOR_EDICT( pEdict ); |
|
if( number < 0 || number > GI->max_edicts ) |
|
Host_Error( "bad entity number %d\n", number ); |
|
return number; |
|
} |
|
|
|
/* |
|
============= |
|
pfnPEntityOfEntIndex |
|
|
|
============= |
|
*/ |
|
static edict_t *pfnPEntityOfEntIndex( int iEntIndex ) |
|
{ |
|
// have to be bug-compatible with GoldSrc in this function |
|
if( host.bugcomp == BUGCOMP_GOLDSRC ) |
|
return SV_PEntityOfEntIndex( iEntIndex, false ); |
|
return SV_PEntityOfEntIndex( iEntIndex, true ); |
|
} |
|
|
|
/* |
|
============= |
|
pfnPEntityOfEntIndexAllEntities |
|
|
|
============= |
|
*/ |
|
static edict_t *pfnPEntityOfEntIndexAllEntities( int iEntIndex ) |
|
{ |
|
return SV_PEntityOfEntIndex( iEntIndex, true ); |
|
} |
|
|
|
/* |
|
============= |
|
pfnFindEntityByVars |
|
|
|
debug thing |
|
============= |
|
*/ |
|
edict_t* GAME_EXPORT pfnFindEntityByVars( entvars_t *pvars ) |
|
{ |
|
edict_t *pEdict; |
|
int i; |
|
|
|
// don't pass invalid arguments |
|
if( !pvars ) return NULL; |
|
|
|
for( i = 0; i < GI->max_edicts; i++ ) |
|
{ |
|
pEdict = EDICT_NUM( i ); |
|
|
|
// g-cont: we should compare pointers |
|
if( &pEdict->v == pvars ) |
|
return pEdict; // found it |
|
} |
|
|
|
return NULL; |
|
} |
|
|
|
/* |
|
============= |
|
pfnGetModelPtr |
|
|
|
returns pointer to a studiomodel |
|
============= |
|
*/ |
|
static void *pfnGetModelPtr( edict_t *pEdict ) |
|
{ |
|
model_t *mod; |
|
|
|
if( !SV_IsValidEdict( pEdict )) |
|
return NULL; |
|
|
|
mod = SV_ModelHandle( pEdict->v.modelindex ); |
|
return Mod_StudioExtradata( mod ); |
|
} |
|
|
|
/* |
|
============= |
|
SV_SendUserReg |
|
|
|
============= |
|
*/ |
|
void SV_SendUserReg( sizebuf_t *msg, sv_user_message_t *user ) |
|
{ |
|
MSG_BeginServerCmd( msg, svc_usermessage ); |
|
MSG_WriteByte( msg, user->number ); |
|
MSG_WriteWord( msg, (word)user->size ); |
|
MSG_WriteString( msg, user->name ); |
|
} |
|
|
|
/* |
|
============= |
|
pfnRegUserMsg |
|
|
|
============= |
|
*/ |
|
int GAME_EXPORT pfnRegUserMsg( const char *pszName, int iSize ) |
|
{ |
|
int i; |
|
|
|
if( !COM_CheckString( pszName )) |
|
return svc_bad; |
|
|
|
if( Q_strlen( pszName ) >= sizeof( svgame.msg[0].name )) |
|
{ |
|
Con_Printf( S_ERROR "REG_USER_MSG: too long name %s\n", pszName ); |
|
return svc_bad; // force error |
|
} |
|
|
|
if( iSize > MAX_USERMSG_LENGTH ) |
|
{ |
|
Con_Printf( S_ERROR "REG_USER_MSG: %s has too big size %i\n", pszName, iSize ); |
|
return svc_bad; // force error |
|
} |
|
|
|
// make sure what size inrange |
|
iSize = bound( -1, iSize, MAX_USERMSG_LENGTH ); |
|
|
|
// message 0 is reserved for svc_bad |
|
for( i = 1; i < MAX_USER_MESSAGES && svgame.msg[i].name[0]; i++ ) |
|
{ |
|
// see if already registered |
|
if( !Q_strcmp( svgame.msg[i].name, pszName )) |
|
return svc_lastmsg + i; // offset |
|
} |
|
|
|
if( i == MAX_USER_MESSAGES ) |
|
{ |
|
Con_Printf( S_ERROR "REG_USER_MSG: user messages limit exceeded\n" ); |
|
return svc_bad; |
|
} |
|
|
|
// register new message |
|
Q_strncpy( svgame.msg[i].name, pszName, sizeof( svgame.msg[i].name )); |
|
svgame.msg[i].number = svc_lastmsg + i; |
|
svgame.msg[i].size = iSize; |
|
|
|
if( sv.state == ss_active ) |
|
{ |
|
// tell the client about new user message |
|
SV_SendUserReg( &sv.multicast, &svgame.msg[i] ); |
|
SV_Multicast( MSG_ALL, NULL, NULL, false, false ); |
|
} |
|
|
|
return svgame.msg[i].number; |
|
} |
|
|
|
/* |
|
============= |
|
pfnAnimationAutomove |
|
|
|
OBSOLETE, UNUSED |
|
============= |
|
*/ |
|
void GAME_EXPORT pfnAnimationAutomove( const edict_t* pEdict, float flTime ) |
|
{ |
|
} |
|
|
|
/* |
|
============= |
|
pfnGetBonePosition |
|
|
|
============= |
|
*/ |
|
static void GAME_EXPORT pfnGetBonePosition( const edict_t* pEdict, int iBone, float *rgflOrigin, float *rgflAngles ) |
|
{ |
|
if( !SV_IsValidEdict( pEdict )) |
|
return; |
|
Mod_GetBonePosition( pEdict, iBone, rgflOrigin, rgflAngles ); |
|
} |
|
|
|
/* |
|
============= |
|
pfnFunctionFromName |
|
|
|
============= |
|
*/ |
|
void *pfnFunctionFromName( const char *pName ) |
|
{ |
|
return COM_FunctionFromName_SR( svgame.hInstance, pName ); |
|
} |
|
|
|
/* |
|
============= |
|
pfnNameForFunction |
|
|
|
============= |
|
*/ |
|
const char *pfnNameForFunction( void *function ) |
|
{ |
|
return COM_NameForFunction( svgame.hInstance, function ); |
|
} |
|
|
|
/* |
|
============= |
|
pfnClientPrintf |
|
|
|
============= |
|
*/ |
|
void GAME_EXPORT pfnClientPrintf( edict_t* pEdict, PRINT_TYPE ptype, const char *szMsg ) |
|
{ |
|
sv_client_t *client; |
|
|
|
if(( client = SV_ClientFromEdict( pEdict, false )) == NULL ) |
|
{ |
|
Con_Printf( "tried to sprint to a non-client\n" ); |
|
return; |
|
} |
|
|
|
if( FBitSet( client->flags, FCL_FAKECLIENT )) |
|
return; |
|
|
|
switch( ptype ) |
|
{ |
|
case print_console: |
|
case print_chat: |
|
SV_ClientPrintf( client, "%s", szMsg ); |
|
break; |
|
case print_center: |
|
MSG_BeginServerCmd( &client->netchan.message, svc_centerprint ); |
|
MSG_WriteString( &client->netchan.message, szMsg ); |
|
break; |
|
} |
|
} |
|
|
|
/* |
|
============= |
|
pfnServerPrint |
|
|
|
print to the server console |
|
============= |
|
*/ |
|
void GAME_EXPORT pfnServerPrint( const char *szMsg ) |
|
{ |
|
if( FBitSet( host.features, ENGINE_QUAKE_COMPATIBLE )) |
|
SV_BroadcastPrintf( NULL, "%s", szMsg ); |
|
else Con_Printf( "%s", szMsg ); |
|
} |
|
|
|
/* |
|
============= |
|
pfnGetAttachment |
|
|
|
============= |
|
*/ |
|
static void GAME_EXPORT pfnGetAttachment( const edict_t *pEdict, int iAttachment, float *rgflOrigin, float *rgflAngles ) |
|
{ |
|
if( !SV_IsValidEdict( pEdict )) |
|
return; |
|
Mod_StudioGetAttachment( pEdict, iAttachment, rgflOrigin, rgflAngles ); |
|
} |
|
|
|
/* |
|
============= |
|
pfnCrosshairAngle |
|
|
|
============= |
|
*/ |
|
void GAME_EXPORT pfnCrosshairAngle( const edict_t *pClient, float pitch, float yaw ) |
|
{ |
|
sv_client_t *client; |
|
|
|
if(( client = SV_ClientFromEdict( pClient, true )) == NULL ) |
|
return; |
|
|
|
// fakeclients ignores it silently |
|
if( FBitSet( client->flags, FCL_FAKECLIENT )) |
|
return; |
|
|
|
if( pitch > 180.0f ) pitch -= 360; |
|
if( pitch < -180.0f ) pitch += 360; |
|
if( yaw > 180.0f ) yaw -= 360; |
|
if( yaw < -180.0f ) yaw += 360; |
|
|
|
MSG_BeginServerCmd( &client->netchan.message, svc_crosshairangle ); |
|
MSG_WriteChar( &client->netchan.message, pitch * 5 ); |
|
MSG_WriteChar( &client->netchan.message, yaw * 5 ); |
|
} |
|
|
|
/* |
|
============= |
|
pfnSetView |
|
|
|
============= |
|
*/ |
|
void GAME_EXPORT pfnSetView( const edict_t *pClient, const edict_t *pViewent ) |
|
{ |
|
sv_client_t *client; |
|
int viewEnt; |
|
|
|
if( !SV_IsValidEdict( pClient )) |
|
return; |
|
|
|
if(( client = SV_ClientFromEdict( pClient, false )) == NULL ) |
|
{ |
|
Con_Printf( S_ERROR "PF_SetView_I: not a client!\n" ); |
|
return; |
|
} |
|
|
|
if( !SV_IsValidEdict( pViewent ) || pClient == pViewent ) |
|
client->pViewEntity = NULL; // just reset viewentity |
|
else client->pViewEntity = (edict_t *)pViewent; |
|
|
|
// fakeclients ignore to send client message (but can see into the trigger_camera through the PVS) |
|
if( FBitSet( client->flags, FCL_FAKECLIENT )) |
|
return; |
|
|
|
if( client->pViewEntity ) |
|
viewEnt = NUM_FOR_EDICT( client->pViewEntity ); |
|
else viewEnt = NUM_FOR_EDICT( client->edict ); |
|
|
|
MSG_BeginServerCmd( &client->netchan.message, svc_setview ); |
|
MSG_WriteWord( &client->netchan.message, viewEnt ); |
|
} |
|
|
|
/* |
|
============= |
|
pfnStaticDecal |
|
|
|
============= |
|
*/ |
|
void GAME_EXPORT pfnStaticDecal( const float *origin, int decalIndex, int entityIndex, int modelIndex ) |
|
{ |
|
SV_CreateDecal( &sv.signon, origin, decalIndex, entityIndex, modelIndex, FDECAL_PERMANENT, 1.0f ); |
|
} |
|
|
|
/* |
|
============= |
|
pfnIsDedicatedServer |
|
|
|
============= |
|
*/ |
|
int GAME_EXPORT pfnIsDedicatedServer( void ) |
|
{ |
|
return Host_IsDedicated(); |
|
} |
|
|
|
/* |
|
============= |
|
pfnGetPlayerWONId |
|
|
|
OBSOLETE, UNUSED |
|
============= |
|
*/ |
|
uint GAME_EXPORT pfnGetPlayerWONId( edict_t *e ) |
|
{ |
|
return (uint)-1; |
|
} |
|
|
|
/* |
|
============= |
|
pfnIsMapValid |
|
|
|
vaild map must contain one info_player_deatchmatch |
|
============= |
|
*/ |
|
int GAME_EXPORT pfnIsMapValid( char *filename ) |
|
{ |
|
uint flags = SV_MapIsValid( filename, GI->mp_entity, NULL ); |
|
|
|
if( FBitSet( flags, MAP_IS_EXIST ) && FBitSet( flags, MAP_HAS_SPAWNPOINT )) |
|
return true; |
|
return false; |
|
} |
|
|
|
/* |
|
============= |
|
pfnFadeClientVolume |
|
|
|
============= |
|
*/ |
|
void GAME_EXPORT pfnFadeClientVolume( const edict_t *pEdict, int fadePercent, int fadeOutSeconds, int holdTime, int fadeInSeconds ) |
|
{ |
|
sv_client_t *cl; |
|
|
|
if(( cl = SV_ClientFromEdict( pEdict, true )) == NULL ) |
|
return; |
|
|
|
if( FBitSet( cl->flags, FCL_FAKECLIENT )) |
|
return; |
|
|
|
MSG_BeginServerCmd( &cl->netchan.message, svc_soundfade ); |
|
MSG_WriteByte( &cl->netchan.message, fadePercent ); |
|
MSG_WriteByte( &cl->netchan.message, holdTime ); |
|
MSG_WriteByte( &cl->netchan.message, fadeOutSeconds ); |
|
MSG_WriteByte( &cl->netchan.message, fadeInSeconds ); |
|
} |
|
|
|
/* |
|
============= |
|
pfnSetClientMaxspeed |
|
|
|
fakeclients can be changed speed to |
|
============= |
|
*/ |
|
void GAME_EXPORT pfnSetClientMaxspeed( const edict_t *pEdict, float fNewMaxspeed ) |
|
{ |
|
sv_client_t *cl; |
|
|
|
// not spawned clients allowed |
|
if(( cl = SV_ClientFromEdict( pEdict, false )) == NULL ) |
|
return; |
|
|
|
fNewMaxspeed = bound( -svgame.movevars.maxspeed, fNewMaxspeed, svgame.movevars.maxspeed ); |
|
Info_SetValueForKeyf( cl->physinfo, "maxspd", MAX_INFO_STRING, "%.f", fNewMaxspeed ); |
|
cl->edict->v.maxspeed = fNewMaxspeed; |
|
} |
|
|
|
/* |
|
============= |
|
pfnRunPlayerMove |
|
|
|
============= |
|
*/ |
|
void GAME_EXPORT pfnRunPlayerMove( edict_t *pClient, const float *viewangles, float fmove, float smove, float upmove, word buttons, byte impulse, byte msec ) |
|
{ |
|
sv_client_t *cl, *oldcl; |
|
usercmd_t cmd; |
|
uint seed; |
|
|
|
if(( cl = SV_ClientFromEdict( pClient, true )) == NULL ) |
|
return; |
|
|
|
if( !FBitSet( cl->flags, FCL_FAKECLIENT )) |
|
return; // only fakeclients allows |
|
|
|
oldcl = sv.current_client; |
|
|
|
sv.current_client = SV_ClientFromEdict( pClient, true ); |
|
sv.current_client->timebase = (sv.time + sv.frametime) - ((double)msec / 1000.0); |
|
|
|
memset( &cmd, 0, sizeof( cmd )); |
|
VectorCopy( viewangles, cmd.viewangles ); |
|
cmd.forwardmove = fmove; |
|
cmd.sidemove = smove; |
|
cmd.upmove = upmove; |
|
cmd.buttons = buttons; |
|
cmd.impulse = impulse; |
|
cmd.msec = msec; |
|
|
|
seed = COM_RandomLong( 0, 0x7fffffff ); // full range |
|
|
|
SV_RunCmd( cl, &cmd, seed ); |
|
|
|
cl->lastcmd = cmd; |
|
sv.current_client = oldcl; |
|
} |
|
|
|
/* |
|
============= |
|
pfnNumberOfEntities |
|
|
|
returns actual entity count |
|
============= |
|
*/ |
|
int GAME_EXPORT pfnNumberOfEntities( void ) |
|
{ |
|
int i, total = 0; |
|
|
|
for( i = 0; i < svgame.numEntities; i++ ) |
|
{ |
|
if( svgame.edicts[i].free ) |
|
continue; |
|
total++; |
|
} |
|
|
|
return total; |
|
} |
|
|
|
/* |
|
============= |
|
pfnGetInfoKeyBuffer |
|
|
|
============= |
|
*/ |
|
char *pfnGetInfoKeyBuffer( edict_t *e ) |
|
{ |
|
sv_client_t *cl; |
|
|
|
// NULL passes localinfo |
|
if( !SV_IsValidEdict( e )) |
|
return SV_Localinfo(); |
|
|
|
// world passes serverinfo |
|
if( e == svgame.edicts ) |
|
return SV_Serverinfo(); |
|
|
|
// userinfo for specified edict |
|
if(( cl = SV_ClientFromEdict( e, false )) != NULL ) |
|
return cl->userinfo; |
|
|
|
return (char*)""; // assume error |
|
} |
|
|
|
/* |
|
============= |
|
pfnSetValueForKey |
|
|
|
============= |
|
*/ |
|
void GAME_EXPORT pfnSetValueForKey( char *infobuffer, char *key, char *value ) |
|
{ |
|
if( infobuffer == svs.localinfo ) |
|
Info_SetValueForStarKey( infobuffer, key, value, MAX_LOCALINFO_STRING ); |
|
else if( infobuffer == svs.serverinfo ) |
|
Info_SetValueForStarKey( infobuffer, key, value, MAX_SERVERINFO_STRING ); |
|
else Con_Printf( S_ERROR "can't set client keys with SetValueForKey\n" ); |
|
} |
|
|
|
/* |
|
============= |
|
pfnSetClientKeyValue |
|
|
|
============= |
|
*/ |
|
void GAME_EXPORT pfnSetClientKeyValue( int clientIndex, char *infobuffer, char *key, char *value ) |
|
{ |
|
sv_client_t *cl; |
|
|
|
if( infobuffer == svs.localinfo || infobuffer == svs.serverinfo ) |
|
return; |
|
|
|
clientIndex -= 1; |
|
|
|
if( !svs.clients || clientIndex < 0 || clientIndex >= svs.maxclients ) |
|
return; |
|
|
|
// value not changed? |
|
if( !Q_strcmp( Info_ValueForKey( infobuffer, key ), value )) |
|
return; |
|
|
|
cl = &svs.clients[clientIndex]; |
|
|
|
Info_SetValueForStarKey( infobuffer, key, value, MAX_INFO_STRING ); |
|
SetBits( cl->flags, FCL_RESEND_USERINFO ); |
|
cl->next_sendinfotime = 0.0; // send immediately |
|
} |
|
|
|
/* |
|
============= |
|
pfnGetPhysicsKeyValue |
|
|
|
============= |
|
*/ |
|
const char *pfnGetPhysicsKeyValue( const edict_t *pClient, const char *key ) |
|
{ |
|
sv_client_t *cl; |
|
|
|
// pfnUserInfoChanged passed |
|
if(( cl = SV_ClientFromEdict( pClient, false )) == NULL ) |
|
{ |
|
Con_Printf( S_ERROR "GetPhysicsKeyValue: tried to a non-client!\n" ); |
|
return ""; |
|
} |
|
|
|
return Info_ValueForKey( cl->physinfo, key ); |
|
} |
|
|
|
/* |
|
============= |
|
pfnSetPhysicsKeyValue |
|
|
|
============= |
|
*/ |
|
void GAME_EXPORT pfnSetPhysicsKeyValue( const edict_t *pClient, const char *key, const char *value ) |
|
{ |
|
sv_client_t *cl; |
|
|
|
// pfnUserInfoChanged passed |
|
if(( cl = SV_ClientFromEdict( pClient, false )) == NULL ) |
|
{ |
|
Con_Printf( S_ERROR "SetPhysicsKeyValue: tried to a non-client!\n" ); |
|
return; |
|
} |
|
|
|
Info_SetValueForKey( cl->physinfo, key, value, MAX_INFO_STRING ); |
|
} |
|
|
|
/* |
|
============= |
|
pfnGetPhysicsInfoString |
|
|
|
============= |
|
*/ |
|
const char *pfnGetPhysicsInfoString( const edict_t *pClient ) |
|
{ |
|
sv_client_t *cl; |
|
|
|
// pfnUserInfoChanged passed |
|
if(( cl = SV_ClientFromEdict( pClient, false )) == NULL ) |
|
{ |
|
Con_Printf( S_ERROR "GetPhysicsInfoString: tried to a non-client!\n" ); |
|
return ""; |
|
} |
|
|
|
return cl->physinfo; |
|
} |
|
|
|
/* |
|
============= |
|
pfnPrecacheEvent |
|
|
|
register or returns already registered event id |
|
a type of event is ignored at this moment |
|
============= |
|
*/ |
|
word GAME_EXPORT pfnPrecacheEvent( int type, const char *psz ) |
|
{ |
|
return (word)SV_EventIndex( psz ); |
|
} |
|
|
|
/* |
|
============= |
|
pfnPlaybackEvent |
|
|
|
============= |
|
*/ |
|
void GAME_EXPORT SV_PlaybackEventFull( int flags, const edict_t *pInvoker, word eventindex, float delay, float *origin, |
|
float *angles, float fparam1, float fparam2, int iparam1, int iparam2, int bparam1, int bparam2 ) |
|
{ |
|
sv_client_t *cl; |
|
event_state_t *es; |
|
event_args_t args; |
|
event_info_t *ei = NULL; |
|
int j, slot, bestslot; |
|
int invokerIndex; |
|
byte *mask = NULL; |
|
vec3_t pvspoint; |
|
|
|
if( FBitSet( flags, FEV_CLIENT )) |
|
return; // someone stupid joke |
|
|
|
// first check event for out of bounds |
|
if( eventindex < 1 || eventindex >= MAX_EVENTS ) |
|
{ |
|
Con_Printf( S_ERROR "EV_Playback: invalid eventindex %i\n", eventindex ); |
|
return; |
|
} |
|
|
|
// check event for precached |
|
if( !COM_CheckString( sv.event_precache[eventindex] )) |
|
{ |
|
Con_Printf( S_ERROR "EV_Playback: event %i was not precached\n", eventindex ); |
|
return; |
|
} |
|
|
|
memset( &args, 0, sizeof( args )); |
|
|
|
if( origin && !VectorIsNull( origin )) |
|
{ |
|
VectorCopy( origin, args.origin ); |
|
args.flags |= FEVENT_ORIGIN; |
|
} |
|
|
|
if( angles && !VectorIsNull( angles )) |
|
{ |
|
VectorCopy( angles, args.angles ); |
|
args.flags |= FEVENT_ANGLES; |
|
} |
|
|
|
// copy other parms |
|
args.fparam1 = fparam1; |
|
args.fparam2 = fparam2; |
|
args.iparam1 = iparam1; |
|
args.iparam2 = iparam2; |
|
args.bparam1 = bparam1; |
|
args.bparam2 = bparam2; |
|
|
|
VectorClear( pvspoint ); |
|
|
|
if( SV_IsValidEdict( pInvoker )) |
|
{ |
|
// add the view_ofs to avoid problems with crossed contents line |
|
VectorAdd( pInvoker->v.origin, pInvoker->v.view_ofs, pvspoint ); |
|
args.entindex = invokerIndex = NUM_FOR_EDICT( pInvoker ); |
|
|
|
// g-cont. allow 'ducking' param for all entities |
|
args.ducking = FBitSet( pInvoker->v.flags, FL_DUCKING ) ? true : false; |
|
|
|
// this will be send only for reliable event |
|
if( !FBitSet( args.flags, FEVENT_ORIGIN )) |
|
VectorCopy( pInvoker->v.origin, args.origin ); |
|
|
|
// this will be send only for reliable event |
|
if( !FBitSet( args.flags, FEVENT_ANGLES )) |
|
VectorCopy( pInvoker->v.angles, args.angles ); |
|
} |
|
else |
|
{ |
|
VectorCopy( args.origin, pvspoint ); |
|
args.entindex = 0; |
|
invokerIndex = -1; |
|
} |
|
|
|
if( !FBitSet( flags, FEV_GLOBAL ) && VectorIsNull( pvspoint )) |
|
{ |
|
Con_DPrintf( S_ERROR "%s: not a FEV_GLOBAL event missing origin. Ignored.\n", sv.event_precache[eventindex] ); |
|
return; |
|
} |
|
|
|
// check event for some user errors |
|
if( FBitSet( flags, FEV_NOTHOST|FEV_HOSTONLY )) |
|
{ |
|
if( !SV_ClientFromEdict( pInvoker, true )) |
|
{ |
|
const char *ev_name = sv.event_precache[eventindex]; |
|
|
|
if( FBitSet( flags, FEV_NOTHOST )) |
|
{ |
|
Con_DPrintf( S_WARN "%s: specified FEV_NOTHOST when invoker not a client\n", ev_name ); |
|
ClearBits( flags, FEV_NOTHOST ); |
|
} |
|
|
|
if( FBitSet( flags, FEV_HOSTONLY )) |
|
{ |
|
Con_DPrintf( S_WARN "%s: specified FEV_HOSTONLY when invoker not a client\n", ev_name ); |
|
ClearBits( flags, FEV_HOSTONLY ); |
|
} |
|
} |
|
} |
|
|
|
SetBits( flags, FEV_SERVER ); // it's a server event! |
|
if( delay < 0.0f ) delay = 0.0f; // fixup negative delays |
|
|
|
// setup pvs cluster for invoker |
|
if( !FBitSet( flags, FEV_GLOBAL )) |
|
{ |
|
Mod_FatPVS( pvspoint, FATPHS_RADIUS, fatphs, world.fatbytes, false, ( svs.maxclients == 1 )); |
|
mask = fatphs; // using the FatPVS like a PHS |
|
} |
|
|
|
// process all the clients |
|
for( slot = 0, cl = svs.clients; slot < svs.maxclients; slot++, cl++ ) |
|
{ |
|
if( cl->state != cs_spawned || !cl->edict || FBitSet( cl->flags, FCL_FAKECLIENT )) |
|
continue; |
|
|
|
if( SV_IsValidEdict( pInvoker ) && pInvoker->v.groupinfo && cl->edict->v.groupinfo ) |
|
{ |
|
if( svs.groupop == GROUP_OP_AND && !FBitSet( cl->edict->v.groupinfo, pInvoker->v.groupinfo )) |
|
continue; |
|
|
|
if( svs.groupop == GROUP_OP_NAND && FBitSet( cl->edict->v.groupinfo, pInvoker->v.groupinfo )) |
|
continue; |
|
} |
|
|
|
if( SV_IsValidEdict( pInvoker )) |
|
{ |
|
if( !SV_CheckClientVisiblity( cl, mask )) |
|
continue; |
|
} |
|
|
|
if( FBitSet( flags, FEV_NOTHOST ) && cl == sv.current_client && FBitSet( cl->flags, FCL_LOCAL_WEAPONS )) |
|
continue; // will be played on client side |
|
|
|
if( FBitSet( flags, FEV_HOSTONLY ) && cl->edict != pInvoker ) |
|
continue; // sending only to invoker |
|
|
|
// all checks passed, send the event |
|
|
|
// reliable event |
|
if( FBitSet( flags, FEV_RELIABLE )) |
|
{ |
|
// skipping queue, write direct into reliable datagram |
|
SV_PlaybackReliableEvent( &cl->netchan.message, eventindex, delay, &args ); |
|
continue; |
|
} |
|
|
|
// unreliable event (stores in queue) |
|
es = &cl->events; |
|
bestslot = -1; |
|
|
|
if( FBitSet( flags, FEV_UPDATE )) |
|
{ |
|
for( j = 0; j < MAX_EVENT_QUEUE; j++ ) |
|
{ |
|
ei = &es->ei[j]; |
|
|
|
if( ei->index == eventindex && invokerIndex != -1 && invokerIndex == ei->entity_index ) |
|
{ |
|
bestslot = j; |
|
break; |
|
} |
|
} |
|
} |
|
|
|
if( bestslot == -1 ) |
|
{ |
|
for( j = 0; j < MAX_EVENT_QUEUE; j++ ) |
|
{ |
|
ei = &es->ei[j]; |
|
|
|
if( ei->index == 0 ) |
|
{ |
|
// found an empty slot |
|
bestslot = j; |
|
break; |
|
} |
|
} |
|
} |
|
|
|
// no slot found for this player, oh well |
|
if( bestslot == -1 ) continue; |
|
|
|
// add event to queue |
|
ei->index = eventindex; |
|
ei->fire_time = delay; |
|
ei->entity_index = invokerIndex; |
|
ei->packet_index = -1; |
|
ei->flags = flags; |
|
ei->args = args; |
|
} |
|
} |
|
|
|
/* |
|
============= |
|
pfnSetFatPVS |
|
|
|
The client will interpolate the view position, |
|
so we can't use a single PVS point |
|
============= |
|
*/ |
|
byte *pfnSetFatPVS( const float *org ) |
|
{ |
|
qboolean fullvis = false; |
|
|
|
if( !sv.worldmodel->visdata || sv_novis->value || !org || CL_DisableVisibility( )) |
|
fullvis = true; |
|
|
|
// portals can't change viewpoint! |
|
if( !FBitSet( sv.hostflags, SVF_MERGE_VISIBILITY )) |
|
{ |
|
vec3_t viewPos, offset; |
|
|
|
ASSERT( pfnGetCurrentPlayer() != -1 ); |
|
|
|
// see code from client.cpp for understanding: |
|
// org = pView->v.origin + pView->v.view_ofs; |
|
// if ( pView->v.flags & FL_DUCKING ) |
|
// { |
|
// org = org + ( VEC_HULL_MIN - VEC_DUCK_HULL_MIN ); |
|
// } |
|
// so we have unneeded duck calculations who have affect when player |
|
// is ducked into water. Remove offset to restore right PVS position |
|
if( FBitSet( sv.current_client->edict->v.flags, FL_DUCKING )) |
|
{ |
|
VectorSubtract( svgame.pmove->player_mins[0], svgame.pmove->player_mins[1], offset ); |
|
VectorSubtract( org, offset, viewPos ); |
|
} |
|
else VectorCopy( org, viewPos ); |
|
|
|
// build a new PVS frame |
|
Mod_FatPVS( viewPos, FATPVS_RADIUS, fatpvs, world.fatbytes, false, fullvis ); |
|
VectorCopy( viewPos, viewPoint[pfnGetCurrentPlayer()] ); |
|
} |
|
else |
|
{ |
|
// merge PVS |
|
Mod_FatPVS( org, FATPVS_RADIUS, fatpvs, world.fatbytes, true, fullvis ); |
|
} |
|
|
|
return fatpvs; |
|
} |
|
|
|
/* |
|
============= |
|
pfnSetFatPHS |
|
|
|
The client will interpolate the hear position, |
|
so we can't use a single PHS point |
|
============= |
|
*/ |
|
byte *pfnSetFatPAS( const float *org ) |
|
{ |
|
qboolean fullvis = false; |
|
|
|
if( !sv.worldmodel->visdata || sv_novis->value || !org || CL_DisableVisibility( )) |
|
fullvis = true; |
|
|
|
// portals can't change viewpoint! |
|
if( !FBitSet( sv.hostflags, SVF_MERGE_VISIBILITY )) |
|
{ |
|
vec3_t viewPos, offset; |
|
|
|
ASSERT( pfnGetCurrentPlayer() != -1 ); |
|
|
|
// see code from client.cpp for understanding: |
|
// org = pView->v.origin + pView->v.view_ofs; |
|
// if ( pView->v.flags & FL_DUCKING ) |
|
// { |
|
// org = org + ( VEC_HULL_MIN - VEC_DUCK_HULL_MIN ); |
|
// } |
|
// so we have unneeded duck calculations who have affect when player |
|
// is ducked into water. Remove offset to restore right PVS position |
|
if( FBitSet( sv.current_client->edict->v.flags, FL_DUCKING )) |
|
{ |
|
VectorSubtract( svgame.pmove->player_mins[0], svgame.pmove->player_mins[1], offset ); |
|
VectorSubtract( org, offset, viewPos ); |
|
} |
|
else VectorCopy( org, viewPos ); |
|
|
|
// build a new PHS frame |
|
Mod_FatPVS( viewPos, FATPHS_RADIUS, fatphs, world.fatbytes, false, fullvis ); |
|
} |
|
else |
|
{ |
|
// merge PHS |
|
Mod_FatPVS( org, FATPHS_RADIUS, fatphs, world.fatbytes, true, fullvis ); |
|
} |
|
|
|
return fatphs; |
|
} |
|
|
|
/* |
|
============= |
|
pfnCheckVisibility |
|
|
|
============= |
|
*/ |
|
int GAME_EXPORT pfnCheckVisibility( const edict_t *ent, byte *pset ) |
|
{ |
|
int i, leafnum; |
|
|
|
if( !SV_IsValidEdict( ent )) |
|
return 0; |
|
|
|
// vis not set - fullvis enabled |
|
if( !pset ) return 1; |
|
|
|
if( FBitSet( ent->v.flags, FL_CUSTOMENTITY ) && ent->v.owner && FBitSet( ent->v.owner->v.flags, FL_CLIENT )) |
|
ent = ent->v.owner; // upcast beams to my owner |
|
|
|
if( ent->headnode < 0 ) |
|
{ |
|
// check individual leafs |
|
for( i = 0; i < ent->num_leafs; i++ ) |
|
{ |
|
if( CHECKVISBIT( pset, ent->leafnums[i] )) |
|
return 1; // visible passed by leaf |
|
} |
|
|
|
return 0; |
|
} |
|
else |
|
{ |
|
for( i = 0; i < MAX_ENT_LEAFS; i++ ) |
|
{ |
|
leafnum = ent->leafnums[i]; |
|
if( leafnum == -1 ) break; |
|
|
|
if( CHECKVISBIT( pset, leafnum )) |
|
return 1; // visible passed by leaf |
|
} |
|
|
|
// too many leafs for individual check, go by headnode |
|
if( !Mod_HeadnodeVisible( &sv.worldmodel->nodes[ent->headnode], pset, &leafnum )) |
|
return 0; |
|
|
|
((edict_t *)ent)->leafnums[ent->num_leafs] = leafnum; |
|
((edict_t *)ent)->num_leafs = (ent->num_leafs + 1) % MAX_ENT_LEAFS; |
|
|
|
return 2; // visible passed by headnode |
|
} |
|
} |
|
|
|
/* |
|
============= |
|
pfnCanSkipPlayer |
|
|
|
============= |
|
*/ |
|
int GAME_EXPORT pfnCanSkipPlayer( const edict_t *player ) |
|
{ |
|
sv_client_t *cl; |
|
|
|
if(( cl = SV_ClientFromEdict( player, false )) == NULL ) |
|
return false; |
|
|
|
return FBitSet( cl->flags, FCL_LOCAL_WEAPONS ) ? true : false; |
|
} |
|
|
|
/* |
|
============= |
|
pfnGetCurrentPlayer |
|
|
|
============= |
|
*/ |
|
int GAME_EXPORT pfnGetCurrentPlayer( void ) |
|
{ |
|
int idx = sv.current_client - svs.clients; |
|
|
|
if( idx < 0 || idx >= svs.maxclients ) |
|
return -1; |
|
return idx; |
|
} |
|
|
|
/* |
|
============= |
|
pfnSetGroupMask |
|
|
|
============= |
|
*/ |
|
void GAME_EXPORT pfnSetGroupMask( int mask, int op ) |
|
{ |
|
svs.groupmask = mask; |
|
svs.groupop = op; |
|
} |
|
|
|
/* |
|
============= |
|
pfnCreateInstancedBaseline |
|
|
|
============= |
|
*/ |
|
int GAME_EXPORT pfnCreateInstancedBaseline( int classname, struct entity_state_s *baseline ) |
|
{ |
|
if( !baseline || sv.num_instanced >= MAX_CUSTOM_BASELINES ) |
|
return 0; |
|
|
|
// g-cont. must sure that classname is really allocated |
|
sv.instanced[sv.num_instanced].classname = SV_CopyString( STRING( classname )); |
|
sv.instanced[sv.num_instanced].baseline = *baseline; |
|
sv.num_instanced++; |
|
|
|
return sv.num_instanced; |
|
} |
|
|
|
/* |
|
============= |
|
pfnEndSection |
|
|
|
============= |
|
*/ |
|
void GAME_EXPORT pfnEndSection( const char *pszSection ) |
|
{ |
|
if( !Q_stricmp( "oem_end_credits", pszSection )) |
|
Host_Credits (); |
|
else Cbuf_AddText( "\ndisconnect\n" ); |
|
} |
|
|
|
/* |
|
============= |
|
pfnGetPlayerUserId |
|
|
|
============= |
|
*/ |
|
int GAME_EXPORT pfnGetPlayerUserId( edict_t *e ) |
|
{ |
|
sv_client_t *cl; |
|
|
|
if(( cl = SV_ClientFromEdict( e, false )) == NULL ) |
|
return -1; |
|
return cl->userid; |
|
} |
|
|
|
/* |
|
============= |
|
pfnGetPlayerStats |
|
|
|
============= |
|
*/ |
|
void GAME_EXPORT pfnGetPlayerStats( const edict_t *pClient, int *ping, int *packet_loss ) |
|
{ |
|
sv_client_t *cl; |
|
|
|
if( packet_loss ) *packet_loss = 0; |
|
if( ping ) *ping = 0; |
|
|
|
if(( cl = SV_ClientFromEdict( pClient, false )) == NULL ) |
|
return; |
|
|
|
if( packet_loss ) *packet_loss = cl->packet_loss; |
|
if( ping ) *ping = cl->latency * 1000; |
|
} |
|
|
|
/* |
|
============= |
|
pfnForceUnmodified |
|
|
|
============= |
|
*/ |
|
void GAME_EXPORT pfnForceUnmodified( FORCE_TYPE type, float *mins, float *maxs, const char *filename ) |
|
{ |
|
consistency_t *pc; |
|
int i; |
|
|
|
if( !COM_CheckString( filename )) |
|
return; |
|
|
|
if( sv.state == ss_loading ) |
|
{ |
|
for( i = 0; i < MAX_MODELS; i++ ) |
|
{ |
|
pc = &sv.consistency_list[i]; |
|
|
|
if( !pc->filename ) |
|
{ |
|
if( mins ) VectorCopy( mins, pc->mins ); |
|
if( maxs ) VectorCopy( maxs, pc->maxs ); |
|
pc->filename = SV_CopyString( filename ); |
|
pc->check_type = type; |
|
return; |
|
} |
|
else if( !Q_strcmp( filename, pc->filename )) |
|
return; |
|
} |
|
Host_Error( "MAX_MODELS limit exceeded (%d)\n", MAX_MODELS ); |
|
} |
|
else |
|
{ |
|
for( i = 0; i < MAX_MODELS; i++ ) |
|
{ |
|
pc = &sv.consistency_list[i]; |
|
if( !pc->filename ) continue; |
|
|
|
if( !Q_strcmp( filename, pc->filename )) |
|
return; |
|
} |
|
Con_Printf( S_ERROR "Failed to enforce consistency for %s: was not precached\n", filename ); |
|
} |
|
} |
|
|
|
/* |
|
============= |
|
pfnVoice_GetClientListening |
|
|
|
============= |
|
*/ |
|
qboolean GAME_EXPORT pfnVoice_GetClientListening( int iReceiver, int iSender ) |
|
{ |
|
iReceiver -= 1; |
|
iSender -= 1; |
|
|
|
if( iReceiver < 0 || iReceiver >= svs.maxclients || iSender < 0 || iSender > svs.maxclients ) |
|
return false; |
|
|
|
return (FBitSet( svs.clients[iSender].listeners, BIT( iReceiver )) != 0 ); |
|
} |
|
|
|
/* |
|
============= |
|
pfnVoice_SetClientListening |
|
|
|
============= |
|
*/ |
|
qboolean GAME_EXPORT pfnVoice_SetClientListening( int iReceiver, int iSender, qboolean bListen ) |
|
{ |
|
iReceiver -= 1; |
|
iSender -= 1; |
|
|
|
if( iReceiver < 0 || iReceiver >= svs.maxclients || iSender < 0 || iSender > svs.maxclients ) |
|
return false; |
|
|
|
if( bListen ) SetBits( svs.clients[iSender].listeners, BIT( iReceiver )); |
|
else ClearBits( svs.clients[iSender].listeners, BIT( iReceiver )); |
|
|
|
return true; |
|
} |
|
|
|
/* |
|
============= |
|
pfnGetPlayerAuthId |
|
|
|
These function must returns cd-key hashed value |
|
but Xash3D currently doesn't have any security checks |
|
return nullstring for now |
|
============= |
|
*/ |
|
const char *pfnGetPlayerAuthId( edict_t *e ) |
|
{ |
|
return SV_GetClientIDString( SV_ClientFromEdict( e, false )); |
|
} |
|
|
|
/* |
|
============= |
|
pfnQueryClientCvarValue |
|
|
|
request client cvar value |
|
============= |
|
*/ |
|
void GAME_EXPORT pfnQueryClientCvarValue( const edict_t *player, const char *cvarName ) |
|
{ |
|
sv_client_t *cl; |
|
|
|
if( !COM_CheckString( cvarName )) |
|
return; |
|
|
|
if(( cl = SV_ClientFromEdict( player, true )) != NULL ) |
|
{ |
|
MSG_BeginServerCmd( &cl->netchan.message, svc_querycvarvalue ); |
|
MSG_WriteString( &cl->netchan.message, cvarName ); |
|
} |
|
else |
|
{ |
|
if( svgame.dllFuncs2.pfnCvarValue ) |
|
svgame.dllFuncs2.pfnCvarValue( player, "Bad Player" ); |
|
Con_Printf( S_ERROR "QueryClientCvarValue: tried to send to a non-client!\n" ); |
|
} |
|
} |
|
|
|
/* |
|
============= |
|
pfnQueryClientCvarValue2 |
|
|
|
request client cvar value (bugfixed) |
|
============= |
|
*/ |
|
void GAME_EXPORT pfnQueryClientCvarValue2( const edict_t *player, const char *cvarName, int requestID ) |
|
{ |
|
sv_client_t *cl; |
|
|
|
if( !COM_CheckString( cvarName )) |
|
return; |
|
|
|
if(( cl = SV_ClientFromEdict( player, true )) != NULL ) |
|
{ |
|
MSG_BeginServerCmd( &cl->netchan.message, svc_querycvarvalue2 ); |
|
MSG_WriteLong( &cl->netchan.message, requestID ); |
|
MSG_WriteString( &cl->netchan.message, cvarName ); |
|
} |
|
else |
|
{ |
|
if( svgame.dllFuncs2.pfnCvarValue2 ) |
|
svgame.dllFuncs2.pfnCvarValue2( player, requestID, cvarName, "Bad Player" ); |
|
Con_Printf( S_ERROR "QueryClientCvarValue: tried to send to a non-client!\n" ); |
|
} |
|
} |
|
|
|
/* |
|
============= |
|
pfnEngineStub |
|
|
|
extended iface stubs |
|
============= |
|
*/ |
|
static int GAME_EXPORT pfnGetLocalizedStringLength( const char *label ) |
|
{ |
|
return 0; |
|
} |
|
|
|
// engine callbacks |
|
static enginefuncs_t gEngfuncs = |
|
{ |
|
pfnPrecacheModel, |
|
SV_SoundIndex, |
|
pfnSetModel, |
|
pfnModelIndex, |
|
pfnModelFrames, |
|
pfnSetSize, |
|
pfnChangeLevel, |
|
pfnGetSpawnParms, |
|
pfnSaveSpawnParms, |
|
pfnVecToYaw, |
|
VectorAngles, |
|
pfnMoveToOrigin, |
|
pfnChangeYaw, |
|
pfnChangePitch, |
|
SV_FindEntityByString, |
|
pfnGetEntityIllum, |
|
pfnFindEntityInSphere, |
|
pfnFindClientInPVS, |
|
pfnEntitiesInPVS, |
|
pfnMakeVectors, |
|
AngleVectors, |
|
SV_AllocEdict, |
|
pfnRemoveEntity, |
|
pfnCreateNamedEntity, |
|
pfnMakeStatic, |
|
pfnEntIsOnFloor, |
|
pfnDropToFloor, |
|
pfnWalkMove, |
|
pfnSetOrigin, |
|
SV_StartSound, |
|
pfnEmitAmbientSound, |
|
pfnTraceLine, |
|
pfnTraceToss, |
|
pfnTraceMonsterHull, |
|
pfnTraceHull, |
|
pfnTraceModel, |
|
pfnTraceTexture, |
|
pfnTraceSphere, |
|
pfnGetAimVector, |
|
pfnServerCommand, |
|
pfnServerExecute, |
|
pfnClientCommand, |
|
pfnParticleEffect, |
|
pfnLightStyle, |
|
pfnDecalIndex, |
|
SV_PointContents, |
|
pfnMessageBegin, |
|
pfnMessageEnd, |
|
pfnWriteByte, |
|
pfnWriteChar, |
|
pfnWriteShort, |
|
pfnWriteLong, |
|
pfnWriteAngle, |
|
pfnWriteCoord, |
|
pfnWriteString, |
|
pfnWriteEntity, |
|
pfnCvar_RegisterServerVariable, |
|
Cvar_VariableValue, |
|
Cvar_VariableString, |
|
Cvar_SetValue, |
|
Cvar_Set, |
|
pfnAlertMessage, |
|
pfnEngineFprintf, |
|
pfnPvAllocEntPrivateData, |
|
pfnPvEntPrivateData, |
|
SV_FreePrivateData, |
|
SV_GetString, |
|
SV_AllocString, |
|
pfnGetVarsOfEnt, |
|
pfnPEntityOfEntOffset, |
|
pfnEntOffsetOfPEntity, |
|
pfnIndexOfEdict, |
|
pfnPEntityOfEntIndex, |
|
pfnFindEntityByVars, |
|
pfnGetModelPtr, |
|
pfnRegUserMsg, |
|
pfnAnimationAutomove, |
|
pfnGetBonePosition, |
|
(void*)pfnFunctionFromName, |
|
(void*)pfnNameForFunction, |
|
pfnClientPrintf, |
|
pfnServerPrint, |
|
Cmd_Args, |
|
Cmd_Argv, |
|
(void*)Cmd_Argc, |
|
pfnGetAttachment, |
|
CRC32_Init, |
|
CRC32_ProcessBuffer, |
|
CRC32_ProcessByte, |
|
CRC32_Final, |
|
COM_RandomLong, |
|
COM_RandomFloat, |
|
pfnSetView, |
|
pfnTime, |
|
pfnCrosshairAngle, |
|
COM_LoadFileForMe, |
|
COM_FreeFile, |
|
pfnEndSection, |
|
COM_CompareFileTime, |
|
pfnGetGameDir, |
|
pfnCvar_RegisterEngineVariable, |
|
pfnFadeClientVolume, |
|
pfnSetClientMaxspeed, |
|
SV_FakeConnect, |
|
pfnRunPlayerMove, |
|
pfnNumberOfEntities, |
|
pfnGetInfoKeyBuffer, |
|
Info_ValueForKey, |
|
pfnSetValueForKey, |
|
pfnSetClientKeyValue, |
|
pfnIsMapValid, |
|
pfnStaticDecal, |
|
SV_GenericIndex, |
|
pfnGetPlayerUserId, |
|
pfnBuildSoundMsg, |
|
pfnIsDedicatedServer, |
|
pfnCVarGetPointer, |
|
pfnGetPlayerWONId, |
|
(void*)Info_RemoveKey, |
|
pfnGetPhysicsKeyValue, |
|
pfnSetPhysicsKeyValue, |
|
pfnGetPhysicsInfoString, |
|
pfnPrecacheEvent, |
|
SV_PlaybackEventFull, |
|
pfnSetFatPVS, |
|
pfnSetFatPAS, |
|
pfnCheckVisibility, |
|
Delta_SetField, |
|
Delta_UnsetField, |
|
Delta_AddEncoder, |
|
pfnGetCurrentPlayer, |
|
pfnCanSkipPlayer, |
|
Delta_FindField, |
|
Delta_SetFieldByIndex, |
|
Delta_UnsetFieldByIndex, |
|
pfnSetGroupMask, |
|
pfnCreateInstancedBaseline, |
|
pfnCVarDirectSet, |
|
pfnForceUnmodified, |
|
pfnGetPlayerStats, |
|
Cmd_AddServerCommand, |
|
pfnVoice_GetClientListening, |
|
pfnVoice_SetClientListening, |
|
pfnGetPlayerAuthId, |
|
pfnSequenceGet, |
|
pfnSequencePickSentence, |
|
COM_FileSize, |
|
Sound_GetApproxWavePlayLen, |
|
pfnIsCareerMatch, |
|
pfnGetLocalizedStringLength, |
|
pfnRegisterTutorMessageShown, |
|
pfnGetTimesTutorMessageShown, |
|
pfnProcessTutorMessageDecayBuffer, |
|
pfnConstructTutorMessageDecayBuffer, |
|
pfnResetTutorMessageDecayData, |
|
pfnQueryClientCvarValue, |
|
pfnQueryClientCvarValue2, |
|
COM_CheckParm, |
|
pfnPEntityOfEntIndexAllEntities, |
|
}; |
|
|
|
/* |
|
==================== |
|
SV_ParseEdict |
|
|
|
Parses an edict out of the given string, returning the new position |
|
ed should be a properly initialized empty edict. |
|
==================== |
|
*/ |
|
qboolean SV_ParseEdict( char **pfile, edict_t *ent ) |
|
{ |
|
KeyValueData pkvd[256]; // per one entity |
|
qboolean adjust_origin = false; |
|
int i, numpairs = 0; |
|
char *classname = NULL; |
|
char token[2048]; |
|
vec3_t origin; |
|
|
|
// go through all the dictionary pairs |
|
while( 1 ) |
|
{ |
|
string keyname; |
|
|
|
// parse key |
|
if(( *pfile = COM_ParseFile( *pfile, token, sizeof( token ))) == NULL ) |
|
Host_Error( "ED_ParseEdict: EOF without closing brace\n" ); |
|
if( token[0] == '}' ) break; // end of desc |
|
|
|
Q_strncpy( keyname, token, sizeof( keyname )); |
|
|
|
// parse value |
|
if(( *pfile = COM_ParseFile( *pfile, token, sizeof( token ))) == NULL ) |
|
Host_Error( "ED_ParseEdict: EOF without closing brace\n" ); |
|
|
|
if( token[0] == '}' ) |
|
Host_Error( "ED_ParseEdict: closing brace without data\n" ); |
|
|
|
// ignore attempts to set key "" |
|
if( !keyname[0] ) continue; |
|
|
|
// "wad" field is already handled |
|
if( !Q_strcmp( keyname, "wad" )) |
|
continue; |
|
|
|
// keynames with a leading underscore are used for |
|
// utility comments and are immediately discarded by engine |
|
if( FBitSet( world.flags, FWORLD_SKYSPHERE ) && keyname[0] == '_' ) |
|
continue; |
|
|
|
// ignore attempts to set value "" |
|
if( !token[0] ) continue; |
|
|
|
// create keyvalue strings |
|
pkvd[numpairs].szClassName = (char*)""; // unknown at this moment |
|
pkvd[numpairs].szKeyName = copystring( keyname ); |
|
pkvd[numpairs].szValue = copystring( token ); |
|
pkvd[numpairs].fHandled = false; |
|
|
|
if( !Q_strcmp( keyname, "classname" ) && classname == NULL ) |
|
classname = copystring( pkvd[numpairs].szValue ); |
|
if( ++numpairs >= 256 ) break; |
|
} |
|
|
|
if( classname == NULL ) |
|
{ |
|
// release allocated strings |
|
for( i = 0; i < numpairs; i++ ) |
|
{ |
|
Mem_Free( pkvd[i].szKeyName ); |
|
Mem_Free( pkvd[i].szValue ); |
|
} |
|
return false; |
|
} |
|
|
|
ent = SV_AllocPrivateData( ent, ALLOC_STRING( classname )); |
|
|
|
if( !SV_IsValidEdict( ent ) || FBitSet( ent->v.flags, FL_KILLME )) |
|
{ |
|
// release allocated strings |
|
for( i = 0; i < numpairs; i++ ) |
|
{ |
|
Mem_Free( pkvd[i].szKeyName ); |
|
Mem_Free( pkvd[i].szValue ); |
|
} |
|
return false; |
|
} |
|
|
|
if( FBitSet( ent->v.flags, FL_CUSTOMENTITY )) |
|
{ |
|
if( numpairs < 256 ) |
|
{ |
|
pkvd[numpairs].szClassName = (char*)"custom"; |
|
pkvd[numpairs].szKeyName = (char*)"customclass"; |
|
pkvd[numpairs].szValue = classname; |
|
pkvd[numpairs].fHandled = false; |
|
numpairs++; |
|
} |
|
|
|
// clear it now - no longer used |
|
ClearBits( ent->v.flags, FL_CUSTOMENTITY ); |
|
} |
|
|
|
#ifdef HACKS_RELATED_HLMODS |
|
// chemical existence have broked changelevels |
|
if( !Q_stricmp( GI->gamefolder, "ce" )) |
|
{ |
|
if( !Q_stricmp( sv.name, "ce08_02" ) && !Q_stricmp( classname, "info_player_start_force" )) |
|
adjust_origin = true; |
|
} |
|
#endif |
|
|
|
for( i = 0; i < numpairs; i++ ) |
|
{ |
|
if( !Q_strcmp( pkvd[i].szKeyName, "angle" )) |
|
{ |
|
float flYawAngle = Q_atof( pkvd[i].szValue ); |
|
|
|
Mem_Free( pkvd[i].szKeyName ); // will be replace with 'angles' |
|
Mem_Free( pkvd[i].szValue ); // release old value, so we don't need these |
|
pkvd[i].szKeyName = copystring( "angles" ); |
|
|
|
if( flYawAngle >= 0.0f ) |
|
{ |
|
char temp[MAX_VA_STRING]; |
|
|
|
Q_snprintf( temp, sizeof( temp ), "%g %g %g", ent->v.angles[0], flYawAngle, ent->v.angles[2] ); |
|
pkvd[i].szValue = copystring( temp ); |
|
} |
|
else if( flYawAngle == -1.0f ) |
|
pkvd[i].szValue = copystring( "-90 0 0" ); |
|
else if( flYawAngle == -2.0f ) |
|
pkvd[i].szValue = copystring( "90 0 0" ); |
|
else pkvd[i].szValue = copystring( "0 0 0" ); // technically an error |
|
} |
|
|
|
#ifdef HACKS_RELATED_HLMODS |
|
if( adjust_origin && !Q_strcmp( pkvd[i].szKeyName, "origin" )) |
|
{ |
|
char temp[MAX_VA_STRING]; |
|
char *pstart = pkvd[i].szValue; |
|
|
|
COM_ParseVector( &pstart, origin, 3 ); |
|
Mem_Free( pkvd[i].szValue ); // release old value, so we don't need these |
|
|
|
Q_snprintf( temp, sizeof( temp ), "%g %g %g", origin[0], origin[1], origin[2] - 16.0f ); |
|
pkvd[i].szValue = copystring( temp ); |
|
} |
|
#endif |
|
if( !Q_strcmp( pkvd[i].szKeyName, "light" )) |
|
{ |
|
Mem_Free( pkvd[i].szKeyName ); |
|
pkvd[i].szKeyName = copystring( "light_level" ); |
|
} |
|
|
|
if( !pkvd[i].fHandled ) |
|
{ |
|
pkvd[i].szClassName = classname; |
|
svgame.dllFuncs.pfnKeyValue( ent, &pkvd[i] ); |
|
} |
|
|
|
// no reason to keep this data |
|
if( Mem_IsAllocatedExt( host.mempool, pkvd[i].szKeyName )) |
|
Mem_Free( pkvd[i].szKeyName ); |
|
|
|
if( Mem_IsAllocatedExt( host.mempool, pkvd[i].szValue )) |
|
Mem_Free( pkvd[i].szValue ); |
|
} |
|
|
|
if( Mem_IsAllocatedExt( host.mempool, classname )) |
|
Mem_Free( classname ); |
|
|
|
return true; |
|
} |
|
|
|
/* |
|
================ |
|
SV_LoadFromFile |
|
|
|
The entities are directly placed in the array, rather than allocated with |
|
ED_Alloc, because otherwise an error loading the map would have entity |
|
number references out of order. |
|
|
|
Creates a server's entity / program execution context by |
|
parsing textual entity definitions out of an ent file. |
|
================ |
|
*/ |
|
void SV_LoadFromFile( const char *mapname, char *entities ) |
|
{ |
|
char token[2048]; |
|
qboolean create_world = true; |
|
int inhibited; |
|
edict_t *ent; |
|
|
|
Assert( entities != NULL ); |
|
|
|
// user dll can override spawn entities function (Xash3D extension) |
|
if( !svgame.physFuncs.SV_LoadEntities || !svgame.physFuncs.SV_LoadEntities( mapname, entities )) |
|
{ |
|
inhibited = 0; |
|
|
|
// parse ents |
|
while(( entities = COM_ParseFile( entities, token, sizeof( token ))) != NULL ) |
|
{ |
|
if( token[0] != '{' ) |
|
Host_Error( "ED_LoadFromFile: found %s when expecting {\n", token ); |
|
|
|
if( create_world ) |
|
{ |
|
create_world = false; |
|
ent = EDICT_NUM( 0 ); // already initialized |
|
} |
|
else ent = SV_AllocEdict(); |
|
|
|
if( !SV_ParseEdict( &entities, ent )) |
|
continue; |
|
|
|
if( svgame.dllFuncs.pfnSpawn( ent ) == -1 ) |
|
{ |
|
// game rejected the spawn |
|
if( !FBitSet( ent->v.flags, FL_KILLME )) |
|
{ |
|
SV_FreeEdict( ent ); |
|
inhibited++; |
|
} |
|
} |
|
} |
|
|
|
Con_DPrintf( "\n%i entities inhibited\n", inhibited ); |
|
} |
|
|
|
// reset world origin and angles for some reason |
|
VectorClear( svgame.edicts->v.origin ); |
|
VectorClear( svgame.edicts->v.angles ); |
|
} |
|
|
|
/* |
|
============== |
|
SpawnEntities |
|
|
|
Creates a server's entity / program execution context by |
|
parsing textual entity definitions out of an ent file. |
|
============== |
|
*/ |
|
void SV_SpawnEntities( const char *mapname ) |
|
{ |
|
edict_t *ent; |
|
|
|
// reset misc parms |
|
Cvar_Reset( "sv_zmax" ); |
|
Cvar_Reset( "sv_wateramp" ); |
|
Cvar_Reset( "sv_wateralpha" ); |
|
|
|
// reset sky parms |
|
Cvar_Reset( "sv_skycolor_r" ); |
|
Cvar_Reset( "sv_skycolor_g" ); |
|
Cvar_Reset( "sv_skycolor_b" ); |
|
Cvar_Reset( "sv_skyvec_x" ); |
|
Cvar_Reset( "sv_skyvec_y" ); |
|
Cvar_Reset( "sv_skyvec_z" ); |
|
Cvar_Reset( "sv_skyname" ); |
|
|
|
ent = EDICT_NUM( 0 ); |
|
if( ent->free ) SV_InitEdict( ent ); |
|
ent->v.model = MAKE_STRING( sv.model_precache[1] ); |
|
ent->v.modelindex = WORLD_INDEX; // world model |
|
ent->v.solid = SOLID_BSP; |
|
ent->v.movetype = MOVETYPE_PUSH; |
|
svgame.movevars.fog_settings = 0; |
|
|
|
svgame.globals->maxEntities = GI->max_edicts; |
|
svgame.globals->mapname = MAKE_STRING( sv.name ); |
|
svgame.globals->startspot = MAKE_STRING( sv.startspot ); |
|
svgame.globals->time = sv.time; |
|
|
|
// spawn the rest of the entities on the map |
|
SV_LoadFromFile( mapname, sv.worldmodel->entities ); |
|
} |
|
|
|
void SV_UnloadProgs( void ) |
|
{ |
|
if( !svgame.hInstance ) |
|
return; |
|
|
|
SV_DeactivateServer (); |
|
Delta_Shutdown (); |
|
/// TODO: reenable this when |
|
/// SV_UnloadProgs will be disabled |
|
//Mod_ClearUserData (); |
|
|
|
SV_FreeStringPool(); |
|
|
|
if( svgame.dllFuncs2.pfnGameShutdown != NULL ) |
|
svgame.dllFuncs2.pfnGameShutdown (); |
|
|
|
// now we can unload cvars |
|
Cvar_FullSet( "host_gameloaded", "0", FCVAR_READ_ONLY ); |
|
Cvar_FullSet( "sv_background", "0", FCVAR_READ_ONLY ); |
|
|
|
// free entity baselines |
|
Z_Free( svs.static_entities ); |
|
Z_Free( svs.baselines ); |
|
svs.baselines = NULL; |
|
|
|
// remove server cmds |
|
SV_KillOperatorCommands(); |
|
|
|
// must unlink all game cvars, |
|
// before pointers on them will be lost... |
|
Cvar_Unlink( FCVAR_EXTDLL ); |
|
Cmd_Unlink( CMD_SERVERDLL ); |
|
|
|
Mod_ResetStudioAPI (); |
|
|
|
svs.game_library_loaded = false; |
|
COM_FreeLibrary( svgame.hInstance ); |
|
Mem_FreePool( &svgame.mempool ); |
|
memset( &svgame, 0, sizeof( svgame )); |
|
} |
|
|
|
qboolean SV_LoadProgs( const char *name ) |
|
{ |
|
int i, version; |
|
static APIFUNCTION GetEntityAPI; |
|
static APIFUNCTION2 GetEntityAPI2; |
|
static GIVEFNPTRSTODLL GiveFnptrsToDll; |
|
static NEW_DLL_FUNCTIONS_FN GiveNewDllFuncs; |
|
static enginefuncs_t gpEngfuncs; |
|
static globalvars_t gpGlobals; |
|
static playermove_t gpMove; |
|
edict_t *e; |
|
|
|
if( svgame.hInstance ) SV_UnloadProgs(); |
|
|
|
// fill it in |
|
svgame.pmove = &gpMove; |
|
svgame.globals = &gpGlobals; |
|
svgame.mempool = Mem_AllocPool( "Server Edicts Zone" ); |
|
|
|
svgame.hInstance = COM_LoadLibrary( name, true, false ); |
|
if( !svgame.hInstance ) |
|
{ |
|
Mem_FreePool(&svgame.mempool); |
|
return false; |
|
} |
|
|
|
// make sure what new dll functions is cleared |
|
memset( &svgame.dllFuncs2, 0, sizeof( svgame.dllFuncs2 )); |
|
|
|
// make sure what physic functions is cleared |
|
memset( &svgame.physFuncs, 0, sizeof( svgame.physFuncs )); |
|
|
|
// make local copy of engfuncs to prevent overwrite it with bots.dll |
|
memcpy( &gpEngfuncs, &gEngfuncs, sizeof( gpEngfuncs )); |
|
|
|
GetEntityAPI = (APIFUNCTION)COM_GetProcAddress( svgame.hInstance, "GetEntityAPI" ); |
|
GetEntityAPI2 = (APIFUNCTION2)COM_GetProcAddress( svgame.hInstance, "GetEntityAPI2" ); |
|
GiveNewDllFuncs = (NEW_DLL_FUNCTIONS_FN)COM_GetProcAddress( svgame.hInstance, "GetNewDLLFunctions" ); |
|
|
|
if( !GetEntityAPI && !GetEntityAPI2 ) |
|
{ |
|
COM_FreeLibrary( svgame.hInstance ); |
|
Con_Printf( S_ERROR "SV_LoadProgs: failed to get address of GetEntityAPI proc\n" ); |
|
svgame.hInstance = NULL; |
|
Mem_FreePool(&svgame.mempool); |
|
return false; |
|
} |
|
|
|
GiveFnptrsToDll = (GIVEFNPTRSTODLL)COM_GetProcAddress( svgame.hInstance, "GiveFnptrsToDll" ); |
|
|
|
if( !GiveFnptrsToDll ) |
|
{ |
|
COM_FreeLibrary( svgame.hInstance ); |
|
Con_Printf( S_ERROR "SV_LoadProgs: failed to get address of GiveFnptrsToDll proc\n" ); |
|
svgame.hInstance = NULL; |
|
Mem_FreePool(&svgame.mempool); |
|
return false; |
|
} |
|
|
|
GiveFnptrsToDll( &gpEngfuncs, svgame.globals ); |
|
|
|
// get extended callbacks |
|
if( GiveNewDllFuncs ) |
|
{ |
|
version = NEW_DLL_FUNCTIONS_VERSION; |
|
|
|
if( !GiveNewDllFuncs( &svgame.dllFuncs2, &version )) |
|
{ |
|
if( version != NEW_DLL_FUNCTIONS_VERSION ) |
|
Con_Printf( S_WARN "SV_LoadProgs: new interface version %i should be %i\n", NEW_DLL_FUNCTIONS_VERSION, version ); |
|
memset( &svgame.dllFuncs2, 0, sizeof( svgame.dllFuncs2 )); |
|
} |
|
} |
|
|
|
version = INTERFACE_VERSION; |
|
|
|
if( GetEntityAPI2 ) |
|
{ |
|
if( !GetEntityAPI2( &svgame.dllFuncs, &version )) |
|
{ |
|
Con_Printf( S_WARN "SV_LoadProgs: interface version %i should be %i\n", INTERFACE_VERSION, version ); |
|
|
|
// fallback to old API |
|
if( !GetEntityAPI( &svgame.dllFuncs, version )) |
|
{ |
|
COM_FreeLibrary( svgame.hInstance ); |
|
Con_Printf( S_ERROR "SV_LoadProgs: couldn't get entity API\n" ); |
|
svgame.hInstance = NULL; |
|
Mem_FreePool(&svgame.mempool); |
|
return false; |
|
} |
|
} |
|
else Con_Reportf( "SV_LoadProgs: ^2initailized extended EntityAPI ^7ver. %i\n", version ); |
|
} |
|
else if( !GetEntityAPI( &svgame.dllFuncs, version )) |
|
{ |
|
COM_FreeLibrary( svgame.hInstance ); |
|
Con_Printf( S_ERROR "SV_LoadProgs: couldn't get entity API\n" ); |
|
svgame.hInstance = NULL; |
|
Mem_FreePool(&svgame.mempool); |
|
return false; |
|
} |
|
|
|
SV_InitOperatorCommands(); |
|
Mod_InitStudioAPI(); |
|
|
|
if( !SV_InitPhysicsAPI( )) |
|
{ |
|
Con_Printf( S_WARN "SV_LoadProgs: couldn't get physics API\n" ); |
|
} |
|
|
|
// grab function SV_SaveGameComment |
|
SV_InitSaveRestore (); |
|
|
|
svgame.globals->pStringBase = ""; // setup string base |
|
|
|
svgame.globals->maxEntities = GI->max_edicts; |
|
svgame.globals->maxClients = svs.maxclients; |
|
svgame.edicts = Mem_Calloc( svgame.mempool, sizeof( edict_t ) * GI->max_edicts ); |
|
svs.static_entities = Z_Calloc( sizeof( entity_state_t ) * MAX_STATIC_ENTITIES ); |
|
svs.baselines = Z_Calloc( sizeof( entity_state_t ) * GI->max_edicts ); |
|
svgame.numEntities = svs.maxclients + 1; // clients + world |
|
|
|
for( i = 0, e = svgame.edicts; i < GI->max_edicts; i++, e++ ) |
|
e->free = true; // mark all edicts as freed |
|
|
|
Cvar_FullSet( "host_gameloaded", "1", FCVAR_READ_ONLY ); |
|
SV_AllocStringPool(); |
|
|
|
// fire once |
|
Con_Printf( "Dll loaded for game ^2\"%s\"\n", svgame.dllFuncs.pfnGetGameDescription( )); |
|
|
|
// all done, initialize game |
|
svgame.dllFuncs.pfnGameInit(); |
|
|
|
// initialize pm_shared |
|
SV_InitClientMove(); |
|
|
|
Delta_Init (); |
|
|
|
// register custom encoders |
|
svgame.dllFuncs.pfnRegisterEncoders(); |
|
|
|
return true; |
|
}
|
|
|