mirror of
https://github.com/YGGverse/xash3d-fwgs.git
synced 2025-01-09 22:58:00 +00:00
5e0a0765ce
The `.editorconfig` file in this repo is configured to trim all trailing whitespace regardless of whether the line is modified. Trims all trailing whitespace in the repository to make the codebase easier to work with in editors that respect `.editorconfig`. `git blame` becomes less useful on these lines but it already isn't very useful. Commands: ``` find . -type f -name '*.h' -exec sed --in-place 's/[[:space:]]\+$//' {} \+ find . -type f -name '*.c' -exec sed --in-place 's/[[:space:]]\+$//' {} \+ ```
1011 lines
25 KiB
C
1011 lines
25 KiB
C
/*
|
|
sv_frame.c - server world snapshot
|
|
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 "const.h"
|
|
#include "net_encode.h"
|
|
|
|
typedef struct
|
|
{
|
|
int num_entities;
|
|
entity_state_t entities[MAX_VISIBLE_PACKET];
|
|
byte sended[MAX_EDICTS_BYTES];
|
|
} sv_ents_t;
|
|
|
|
int c_fullsend; // just a debug counter
|
|
int c_notsend;
|
|
|
|
/*
|
|
=======================
|
|
SV_EntityNumbers
|
|
=======================
|
|
*/
|
|
static int SV_EntityNumbers( const void *a, const void *b )
|
|
{
|
|
int ent1, ent2;
|
|
|
|
ent1 = ((entity_state_t *)a)->number;
|
|
ent2 = ((entity_state_t *)b)->number;
|
|
|
|
// watcom libc compares ents with itself
|
|
if( ent1 == ent2 )
|
|
return 0;
|
|
|
|
if( ent1 < ent2 )
|
|
return -1;
|
|
return 1;
|
|
}
|
|
|
|
/*
|
|
=============
|
|
SV_AddEntitiesToPacket
|
|
|
|
=============
|
|
*/
|
|
static void SV_AddEntitiesToPacket( edict_t *pViewEnt, edict_t *pClient, client_frame_t *frame, sv_ents_t *ents, qboolean from_client )
|
|
{
|
|
edict_t *ent;
|
|
byte *clientpvs;
|
|
byte *clientphs;
|
|
qboolean fullvis = false;
|
|
sv_client_t *cl = NULL;
|
|
qboolean player;
|
|
entity_state_t *state;
|
|
int e;
|
|
|
|
// during an error shutdown message we may need to transmit
|
|
// the shutdown message after the server has shutdown, so
|
|
// specifically check for it
|
|
if( sv.state == ss_dead )
|
|
return;
|
|
|
|
cl = SV_ClientFromEdict( pClient, true );
|
|
|
|
ASSERT( cl != NULL );
|
|
|
|
// portals can't change hostflags
|
|
if( from_client )
|
|
{
|
|
// setup hostflags
|
|
if( FBitSet( cl->flags, FCL_LOCAL_WEAPONS ))
|
|
SetBits( sv.hostflags, SVF_SKIPLOCALHOST );
|
|
else ClearBits( sv.hostflags, SVF_SKIPLOCALHOST );
|
|
|
|
// reset viewents each frame
|
|
cl->num_viewents = 0;
|
|
}
|
|
|
|
svgame.dllFuncs.pfnSetupVisibility( pViewEnt, pClient, &clientpvs, &clientphs );
|
|
if( !clientpvs ) fullvis = true;
|
|
|
|
// g-cont: of course we can send world but not want to do it :-)
|
|
for( e = 1; e < svgame.numEntities; e++ )
|
|
{
|
|
byte *pset;
|
|
|
|
ent = EDICT_NUM( e );
|
|
|
|
// don't double add an entity through portals (in case this already added)
|
|
if( CHECKVISBIT( ents->sended, e ))
|
|
continue;
|
|
|
|
if( e >= 1 && e <= svs.maxclients )
|
|
player = 1;
|
|
else player = 0;
|
|
|
|
if( player )
|
|
{
|
|
sv_client_t *cl = &svs.clients[e - 1];
|
|
|
|
if( cl->state != cs_spawned )
|
|
continue;
|
|
|
|
if( FBitSet( cl->flags, FCL_HLTV_PROXY ))
|
|
continue;
|
|
}
|
|
|
|
if( FBitSet( ent->v.effects, EF_REQUEST_PHS ))
|
|
pset = clientphs;
|
|
else pset = clientpvs;
|
|
|
|
state = &ents->entities[ents->num_entities];
|
|
|
|
// add entity to the net packet
|
|
if( svgame.dllFuncs.pfnAddToFullPack( state, e, ent, pClient, sv.hostflags, player, pset ))
|
|
{
|
|
// to prevent adds it twice through portals
|
|
SETVISBIT( ents->sended, e );
|
|
|
|
if( SV_IsValidEdict( ent->v.aiment ) && FBitSet( ent->v.aiment->v.effects, EF_MERGE_VISIBILITY ))
|
|
{
|
|
if( cl->num_viewents < MAX_VIEWENTS )
|
|
{
|
|
cl->viewentity[cl->num_viewents] = ent->v.aiment;
|
|
cl->num_viewents++;
|
|
}
|
|
}
|
|
|
|
// if we are full, silently discard entities
|
|
if( ents->num_entities < ( MAX_VISIBLE_PACKET - 1 ))
|
|
{
|
|
ents->num_entities++; // entity accepted
|
|
c_fullsend++; // debug counter
|
|
|
|
}
|
|
else
|
|
{
|
|
// visibility list is full
|
|
// continue counting entities,
|
|
// so we know how many it's ovreflowed
|
|
c_notsend++;
|
|
}
|
|
}
|
|
|
|
if( fullvis ) continue; // portal ents will be added anyway, ignore recursion
|
|
|
|
// if it's a portal entity, add everything visible from its camera position
|
|
if( from_client && FBitSet( ent->v.effects, EF_MERGE_VISIBILITY ))
|
|
{
|
|
SetBits( sv.hostflags, SVF_MERGE_VISIBILITY );
|
|
SV_AddEntitiesToPacket( ent, pClient, frame, ents, false );
|
|
ClearBits( sv.hostflags, SVF_MERGE_VISIBILITY );
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
=============================================================================
|
|
|
|
Encode a client frame onto the network channel
|
|
|
|
=============================================================================
|
|
*/
|
|
/*
|
|
=============
|
|
SV_FindBestBaseline
|
|
|
|
trying to deltas with previous entities
|
|
=============
|
|
*/
|
|
int SV_FindBestBaseline( sv_client_t *cl, int index, entity_state_t **baseline, entity_state_t *to, client_frame_t *frame, qboolean player )
|
|
{
|
|
int bestBitCount;
|
|
int i, bitCount;
|
|
int bestfound, j;
|
|
|
|
bestBitCount = j = Delta_TestBaseline( *baseline, to, player, sv.time );
|
|
bestfound = index;
|
|
|
|
// lookup backward for previous 64 states and try to interpret current delta as baseline
|
|
for( i = index - 1; bestBitCount > 0 && i >= 0 && ( index - i ) < ( MAX_CUSTOM_BASELINES - 1 ); i-- )
|
|
{
|
|
// don't worry about underflow in circular buffer
|
|
entity_state_t *test = &svs.packet_entities[(frame->first_entity+i) % svs.num_client_entities];
|
|
|
|
if( to->entityType == test->entityType )
|
|
{
|
|
bitCount = Delta_TestBaseline( test, to, player, sv.time );
|
|
|
|
if( bitCount < bestBitCount )
|
|
{
|
|
bestBitCount = bitCount;
|
|
bestfound = i;
|
|
}
|
|
}
|
|
}
|
|
|
|
// using delta from previous entity as baseline for current
|
|
if( index != bestfound )
|
|
*baseline = &svs.packet_entities[(frame->first_entity+bestfound) % svs.num_client_entities];
|
|
return index - bestfound;
|
|
}
|
|
|
|
/*
|
|
=============
|
|
SV_FindBestBaselineForStatic
|
|
|
|
trying to deltas with previous static entities
|
|
=============
|
|
*/
|
|
int SV_FindBestBaselineForStatic( int index, entity_state_t **baseline, entity_state_t *to )
|
|
{
|
|
int bestBitCount;
|
|
int i, bitCount;
|
|
int bestfound, j;
|
|
|
|
bestBitCount = j = Delta_TestBaseline( *baseline, to, false, sv.time );
|
|
bestfound = index;
|
|
|
|
// lookup backward for previous 64 states and try to interpret current delta as baseline
|
|
for( i = index - 1; bestBitCount > 0 && i >= 0 && ( index - i ) < ( MAX_CUSTOM_BASELINES - 1 ); i-- )
|
|
{
|
|
// don't worry about underflow in circular buffer
|
|
entity_state_t *test = &svs.static_entities[i];
|
|
|
|
bitCount = Delta_TestBaseline( test, to, false, sv.time );
|
|
|
|
if( bitCount < bestBitCount )
|
|
{
|
|
bestBitCount = bitCount;
|
|
bestfound = i;
|
|
}
|
|
}
|
|
|
|
// using delta from previous entity as baseline for current
|
|
if( index != bestfound )
|
|
*baseline = &svs.static_entities[bestfound];
|
|
return index - bestfound;
|
|
}
|
|
|
|
/*
|
|
=============
|
|
SV_EmitPacketEntities
|
|
|
|
Writes a delta update of an entity_state_t list to the message->
|
|
=============
|
|
*/
|
|
static void SV_EmitPacketEntities( sv_client_t *cl, client_frame_t *to, sizebuf_t *msg )
|
|
{
|
|
entity_state_t *oldent, *newent;
|
|
int oldindex, newindex;
|
|
int i, oldnum, newnum;
|
|
qboolean player;
|
|
int oldmax;
|
|
client_frame_t *from;
|
|
|
|
// this is the frame that we are going to delta update from
|
|
if( cl->delta_sequence != -1 )
|
|
{
|
|
from = &cl->frames[cl->delta_sequence & SV_UPDATE_MASK];
|
|
oldmax = from->num_entities;
|
|
|
|
// the snapshot's entities may still have rolled off the buffer, though
|
|
if( from->first_entity <= ( svs.next_client_entities - svs.num_client_entities ))
|
|
{
|
|
Con_DPrintf( S_WARN "%s: delta request from out of date entities.\n", cl->name );
|
|
MSG_BeginServerCmd( msg, svc_packetentities );
|
|
MSG_WriteUBitLong( msg, to->num_entities - 1, MAX_VISIBLE_PACKET_BITS );
|
|
|
|
from = NULL;
|
|
oldmax = 0;
|
|
}
|
|
else
|
|
{
|
|
MSG_BeginServerCmd( msg, svc_deltapacketentities );
|
|
MSG_WriteUBitLong( msg, to->num_entities - 1, MAX_VISIBLE_PACKET_BITS );
|
|
MSG_WriteByte( msg, cl->delta_sequence );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
from = NULL;
|
|
oldmax = 0;
|
|
|
|
MSG_BeginServerCmd( msg, svc_packetentities );
|
|
MSG_WriteUBitLong( msg, to->num_entities - 1, MAX_VISIBLE_PACKET_BITS );
|
|
}
|
|
|
|
newent = NULL;
|
|
oldent = NULL;
|
|
newindex = 0;
|
|
oldindex = 0;
|
|
|
|
while( newindex < to->num_entities || oldindex < oldmax )
|
|
{
|
|
if( newindex >= to->num_entities )
|
|
{
|
|
newnum = MAX_ENTNUMBER;
|
|
player = false;
|
|
}
|
|
else
|
|
{
|
|
newent = &svs.packet_entities[(to->first_entity+newindex) % svs.num_client_entities];
|
|
player = SV_IsPlayerIndex( newent->number );
|
|
newnum = newent->number;
|
|
}
|
|
|
|
if( oldindex >= oldmax )
|
|
{
|
|
oldnum = MAX_ENTNUMBER;
|
|
}
|
|
else
|
|
{
|
|
oldent = &svs.packet_entities[(from->first_entity+oldindex) % svs.num_client_entities];
|
|
oldnum = oldent->number;
|
|
}
|
|
|
|
if( newnum == oldnum )
|
|
{
|
|
// delta update from old position
|
|
// because the force parm is false, this will not result
|
|
// in any bytes being emited if the entity has not changed at all
|
|
MSG_WriteDeltaEntity( oldent, newent, msg, false, player, sv.time, 0 );
|
|
oldindex++;
|
|
newindex++;
|
|
continue;
|
|
}
|
|
|
|
if( newnum < oldnum )
|
|
{
|
|
entity_state_t *baseline = &svs.baselines[newnum];
|
|
const char *classname = SV_ClassName( EDICT_NUM( newnum ));
|
|
int offset = 0;
|
|
|
|
// trying to reduce message by select optimal baseline
|
|
if( !sv_instancedbaseline.value || !sv.num_instanced || sv.last_valid_baseline > newnum )
|
|
{
|
|
offset = SV_FindBestBaseline( cl, newindex, &baseline, newent, to, player );
|
|
}
|
|
else
|
|
{
|
|
for( i = 0; i < sv.num_instanced; i++ )
|
|
{
|
|
if( !Q_strcmp( classname, sv.instanced[i].classname ))
|
|
{
|
|
baseline = &sv.instanced[i].baseline;
|
|
offset = -i;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// this is a new entity, send it from the baseline
|
|
MSG_WriteDeltaEntity( baseline, newent, msg, true, player, sv.time, offset );
|
|
newindex++;
|
|
continue;
|
|
}
|
|
|
|
if( newnum > oldnum )
|
|
{
|
|
edict_t *ed = EDICT_NUM( oldent->number );
|
|
qboolean force = false;
|
|
|
|
// check if entity completely removed from server
|
|
if( ed->free || FBitSet( ed->v.flags, FL_KILLME ))
|
|
force = true;
|
|
|
|
// remove from message
|
|
MSG_WriteDeltaEntity( oldent, NULL, msg, force, false, sv.time, 0 );
|
|
oldindex++;
|
|
continue;
|
|
}
|
|
}
|
|
|
|
MSG_WriteUBitLong( msg, LAST_EDICT, MAX_ENTITY_BITS ); // end of packetentities
|
|
}
|
|
|
|
/*
|
|
=============
|
|
SV_EmitEvents
|
|
|
|
=============
|
|
*/
|
|
static void SV_EmitEvents( sv_client_t *cl, client_frame_t *to, sizebuf_t *msg )
|
|
{
|
|
event_state_t *es;
|
|
event_info_t *info;
|
|
entity_state_t *state;
|
|
event_args_t nullargs;
|
|
int ev_count = 0;
|
|
int count, ent_index;
|
|
int i, j, ev;
|
|
|
|
memset( &nullargs, 0, sizeof( nullargs ));
|
|
es = &cl->events;
|
|
|
|
// count events
|
|
for( ev = 0; ev < MAX_EVENT_QUEUE; ev++ )
|
|
{
|
|
if( es->ei[ev].index )
|
|
ev_count++;
|
|
}
|
|
|
|
// nothing to send
|
|
if( !ev_count ) return; // nothing to send
|
|
|
|
if ( ev_count >= MAX_EVENT_QUEUE / 2 )
|
|
ev_count = ( MAX_EVENT_QUEUE / 2 ) - 1;
|
|
|
|
for( i = 0; i < MAX_EVENT_QUEUE; i++ )
|
|
{
|
|
info = &es->ei[i];
|
|
if( info->index == 0 )
|
|
continue;
|
|
|
|
ent_index = info->entity_index;
|
|
|
|
for( j = 0; j < to->num_entities; j++ )
|
|
{
|
|
state = &svs.packet_entities[(to->first_entity+j) % svs.num_client_entities];
|
|
if( state->number == ent_index )
|
|
break;
|
|
}
|
|
|
|
if( j < to->num_entities )
|
|
{
|
|
info->packet_index = j;
|
|
info->args.ducking = 0;
|
|
|
|
if( !FBitSet( info->args.flags, FEVENT_ORIGIN ))
|
|
VectorClear( info->args.origin );
|
|
|
|
if( !FBitSet( info->args.flags, FEVENT_ANGLES ))
|
|
VectorClear( info->args.angles );
|
|
|
|
VectorClear( info->args.velocity );
|
|
}
|
|
else
|
|
{
|
|
// couldn't find
|
|
info->packet_index = to->num_entities;
|
|
info->args.entindex = ent_index;
|
|
}
|
|
}
|
|
|
|
MSG_BeginServerCmd( msg, svc_event ); // create message
|
|
MSG_WriteUBitLong( msg, ev_count, 5 ); // up to MAX_EVENT_QUEUE events
|
|
|
|
for( count = i = 0; i < MAX_EVENT_QUEUE; i++ )
|
|
{
|
|
info = &es->ei[i];
|
|
|
|
if( info->index == 0 )
|
|
{
|
|
info->packet_index = -1;
|
|
info->entity_index = -1;
|
|
continue;
|
|
}
|
|
|
|
// only send if there's room
|
|
if( count < ev_count )
|
|
{
|
|
MSG_WriteUBitLong( msg, info->index, MAX_EVENT_BITS ); // 1024 events
|
|
|
|
if( info->packet_index == -1 )
|
|
{
|
|
MSG_WriteOneBit( msg, 0 );
|
|
}
|
|
else
|
|
{
|
|
MSG_WriteOneBit( msg, 1 );
|
|
MSG_WriteUBitLong( msg, info->packet_index, MAX_ENTITY_BITS );
|
|
|
|
if( !memcmp( &nullargs, &info->args, sizeof( event_args_t )))
|
|
{
|
|
MSG_WriteOneBit( msg, 0 );
|
|
}
|
|
else
|
|
{
|
|
MSG_WriteOneBit( msg, 1 );
|
|
MSG_WriteDeltaEvent( msg, &nullargs, &info->args );
|
|
}
|
|
}
|
|
|
|
if( info->fire_time )
|
|
{
|
|
MSG_WriteOneBit( msg, 1 );
|
|
MSG_WriteWord( msg, ( info->fire_time * 100.0f ));
|
|
}
|
|
else MSG_WriteOneBit( msg, 0 );
|
|
}
|
|
|
|
info->index = 0;
|
|
info->packet_index = -1;
|
|
info->entity_index = -1;
|
|
count++;
|
|
}
|
|
}
|
|
|
|
/*
|
|
=============
|
|
SV_EmitPings
|
|
|
|
=============
|
|
*/
|
|
void SV_EmitPings( sizebuf_t *msg )
|
|
{
|
|
sv_client_t *cl;
|
|
int packet_loss;
|
|
int i, ping;
|
|
|
|
MSG_BeginServerCmd( msg, svc_pings );
|
|
|
|
for( i = 0, cl = svs.clients; i < svs.maxclients; i++, cl++ )
|
|
{
|
|
if( cl->state != cs_spawned )
|
|
continue;
|
|
|
|
SV_GetPlayerStats( cl, &ping, &packet_loss );
|
|
|
|
// there are 25 bits for each client
|
|
MSG_WriteOneBit( msg, 1 );
|
|
MSG_WriteUBitLong( msg, i, MAX_CLIENT_BITS );
|
|
MSG_WriteUBitLong( msg, ping, 12 );
|
|
MSG_WriteUBitLong( msg, packet_loss, 7 );
|
|
}
|
|
|
|
// end marker
|
|
MSG_WriteOneBit( msg, 0 );
|
|
}
|
|
|
|
/*
|
|
==================
|
|
SV_WriteClientdataToMessage
|
|
|
|
==================
|
|
*/
|
|
void SV_WriteClientdataToMessage( sv_client_t *cl, sizebuf_t *msg )
|
|
{
|
|
clientdata_t nullcd;
|
|
clientdata_t *from_cd, *to_cd;
|
|
weapon_data_t nullwd;
|
|
weapon_data_t *from_wd, *to_wd;
|
|
client_frame_t *frame;
|
|
edict_t *clent;
|
|
int i;
|
|
|
|
memset( &nullcd, 0, sizeof( nullcd ));
|
|
frame = &cl->frames[cl->netchan.outgoing_sequence & SV_UPDATE_MASK];
|
|
frame->senttime = host.realtime;
|
|
frame->ping_time = -1.0f;
|
|
clent = cl->edict;
|
|
|
|
if( cl->chokecount != 0 )
|
|
{
|
|
MSG_BeginServerCmd( msg, svc_choke );
|
|
cl->chokecount = 0;
|
|
}
|
|
|
|
// update client fixangle
|
|
switch( clent->v.fixangle )
|
|
{
|
|
case 1:
|
|
MSG_BeginServerCmd( msg, svc_setangle );
|
|
MSG_WriteVec3Angles( msg, clent->v.angles );
|
|
break;
|
|
case 2:
|
|
MSG_BeginServerCmd( msg, svc_addangle );
|
|
MSG_WriteBitAngle( msg, clent->v.avelocity[YAW], 16 );
|
|
clent->v.avelocity[YAW] = 0.0f;
|
|
break;
|
|
}
|
|
|
|
clent->v.fixangle = 0; // reset fixangle
|
|
|
|
memset( &frame->clientdata, 0, sizeof( frame->clientdata ));
|
|
|
|
// update clientdata_t
|
|
svgame.dllFuncs.pfnUpdateClientData( clent, FBitSet( cl->flags, FCL_LOCAL_WEAPONS ), &frame->clientdata );
|
|
|
|
MSG_BeginServerCmd( msg, svc_clientdata );
|
|
if( FBitSet( cl->flags, FCL_HLTV_PROXY )) return; // don't send more nothing
|
|
|
|
if( cl->delta_sequence == -1 ) from_cd = &nullcd;
|
|
else from_cd = &cl->frames[cl->delta_sequence & SV_UPDATE_MASK].clientdata;
|
|
to_cd = &frame->clientdata;
|
|
|
|
if( cl->delta_sequence == -1 )
|
|
{
|
|
MSG_WriteOneBit( msg, 0 ); // no delta-compression
|
|
}
|
|
else
|
|
{
|
|
MSG_WriteOneBit( msg, 1 ); // we are delta-ing from
|
|
MSG_WriteByte( msg, cl->delta_sequence );
|
|
}
|
|
|
|
// write clientdata_t
|
|
MSG_WriteClientData( msg, from_cd, to_cd, sv.time );
|
|
|
|
if( FBitSet( cl->flags, FCL_LOCAL_WEAPONS ) && svgame.dllFuncs.pfnGetWeaponData( clent, frame->weapondata ))
|
|
{
|
|
memset( &nullwd, 0, sizeof( nullwd ));
|
|
|
|
for( i = 0; i < MAX_LOCAL_WEAPONS; i++ )
|
|
{
|
|
if( cl->delta_sequence == -1 ) from_wd = &nullwd;
|
|
else from_wd = &cl->frames[cl->delta_sequence & SV_UPDATE_MASK].weapondata[i];
|
|
to_wd = &frame->weapondata[i];
|
|
|
|
MSG_WriteWeaponData( msg, from_wd, to_wd, sv.time, i );
|
|
}
|
|
}
|
|
|
|
// end marker
|
|
MSG_WriteOneBit( msg, 0 );
|
|
}
|
|
|
|
/*
|
|
==================
|
|
SV_WriteEntitiesToClient
|
|
|
|
==================
|
|
*/
|
|
void SV_WriteEntitiesToClient( sv_client_t *cl, sizebuf_t *msg )
|
|
{
|
|
client_frame_t *frame;
|
|
entity_state_t *state;
|
|
static sv_ents_t frame_ents;
|
|
int i, send_pings;
|
|
|
|
frame = &cl->frames[cl->netchan.outgoing_sequence & SV_UPDATE_MASK];
|
|
send_pings = SV_ShouldUpdatePing( cl );
|
|
|
|
memset( frame_ents.sended, 0, sizeof( frame_ents.sended ));
|
|
ClearBits( sv.hostflags, SVF_MERGE_VISIBILITY );
|
|
|
|
// clear everything in this snapshot
|
|
frame_ents.num_entities = c_fullsend = c_notsend = 0;
|
|
|
|
// add all the entities directly visible to the eye, which
|
|
// may include portal entities that merge other viewpoints
|
|
SV_AddEntitiesToPacket( cl->pViewEntity, cl->edict, frame, &frame_ents, true );
|
|
|
|
if( c_notsend != cl->ignored_ents )
|
|
{
|
|
if( c_notsend > 0 )
|
|
Con_Printf( S_ERROR "Too many entities in visible packet list. Ignored %d entities\n", c_notsend );
|
|
cl->ignored_ents = c_notsend;
|
|
}
|
|
|
|
// if there were portals visible, there may be out of order entities
|
|
// in the list which will need to be resorted for the delta compression
|
|
// to work correctly. This also catches the error condition
|
|
// of an entity being included twice.
|
|
qsort( frame_ents.entities, frame_ents.num_entities, sizeof( frame_ents.entities[0] ), SV_EntityNumbers );
|
|
|
|
// it will break all connected clients, but it takes more than one week to overflow it
|
|
if(( (uint)svs.next_client_entities ) + frame_ents.num_entities >= 0x7FFFFFFE )
|
|
{
|
|
svs.next_client_entities = 0;
|
|
|
|
// delta is broken for now, cannot keep connected clients
|
|
SV_FinalMessage( "Server will restart due delta is outdated\n", true );
|
|
}
|
|
|
|
// copy the entity states out
|
|
frame->first_entity = svs.next_client_entities;
|
|
frame->num_entities = 0;
|
|
|
|
for( i = 0; i < frame_ents.num_entities; i++ )
|
|
{
|
|
// add it to the circular packet_entities array
|
|
state = &svs.packet_entities[svs.next_client_entities % svs.num_client_entities];
|
|
*state = frame_ents.entities[i];
|
|
svs.next_client_entities++;
|
|
frame->num_entities++;
|
|
}
|
|
|
|
SV_EmitPacketEntities( cl, frame, msg );
|
|
SV_EmitEvents( cl, frame, msg );
|
|
if( send_pings ) SV_EmitPings( msg );
|
|
}
|
|
|
|
/*
|
|
===============================================================================
|
|
|
|
FRAME UPDATES
|
|
|
|
===============================================================================
|
|
*/
|
|
/*
|
|
=======================
|
|
SV_SendClientDatagram
|
|
=======================
|
|
*/
|
|
void SV_SendClientDatagram( sv_client_t *cl )
|
|
{
|
|
byte msg_buf[MAX_DATAGRAM];
|
|
sizebuf_t msg;
|
|
|
|
MSG_Init( &msg, "Datagram", msg_buf, sizeof( msg_buf ));
|
|
|
|
// always send servertime at new frame
|
|
MSG_BeginServerCmd( &msg, svc_time );
|
|
MSG_WriteFloat( &msg, sv.time );
|
|
|
|
SV_WriteClientdataToMessage( cl, &msg );
|
|
SV_WriteEntitiesToClient( cl, &msg );
|
|
|
|
// copy the accumulated multicast datagram
|
|
// for this client out to the message
|
|
if( MSG_CheckOverflow( &cl->datagram ))
|
|
{
|
|
Con_Printf( S_WARN "%s overflowed for %s\n", MSG_GetName( &cl->datagram ), cl->name );
|
|
}
|
|
else
|
|
{
|
|
if( MSG_GetNumBytesWritten( &cl->datagram ) < MSG_GetNumBytesLeft( &msg ))
|
|
MSG_WriteBits( &msg, MSG_GetData( &cl->datagram ), MSG_GetNumBitsWritten( &cl->datagram ));
|
|
else Con_DPrintf( S_WARN "Ignoring unreliable datagram for %s, would overflow on msg\n", cl->name );
|
|
}
|
|
|
|
MSG_Clear( &cl->datagram );
|
|
|
|
if( MSG_CheckOverflow( &msg ))
|
|
{
|
|
// must have room left for the packet header
|
|
Con_Printf( S_ERROR "%s overflowed for %s\n", MSG_GetName( &msg ), cl->name );
|
|
MSG_Clear( &msg );
|
|
}
|
|
|
|
// send the datagram
|
|
Netchan_TransmitBits( &cl->netchan, MSG_GetNumBitsWritten( &msg ), MSG_GetData( &msg ));
|
|
}
|
|
|
|
/*
|
|
=======================
|
|
SV_UpdateUserInfo
|
|
=======================
|
|
*/
|
|
void SV_UpdateUserInfo( sv_client_t *cl )
|
|
{
|
|
SV_FullClientUpdate( cl, &sv.reliable_datagram );
|
|
ClearBits( cl->flags, FCL_RESEND_USERINFO );
|
|
cl->next_sendinfotime = host.realtime + 1.0;
|
|
}
|
|
|
|
/*
|
|
=======================
|
|
SV_UpdateToReliableMessages
|
|
=======================
|
|
*/
|
|
void SV_UpdateToReliableMessages( void )
|
|
{
|
|
sv_client_t *cl;
|
|
int i;
|
|
|
|
// check for changes to be sent over the reliable streams to all clients
|
|
for( i = 0, cl = svs.clients; i < svs.maxclients; i++, cl++ )
|
|
{
|
|
if( !cl->edict ) continue; // not in game yet
|
|
|
|
if( cl->state != cs_spawned )
|
|
continue;
|
|
|
|
if( FBitSet( cl->flags, FCL_RESEND_USERINFO ) && cl->next_sendinfotime <= host.realtime )
|
|
{
|
|
if( MSG_GetNumBytesLeft( &sv.reliable_datagram ) >= ( Q_strlen( cl->userinfo ) + 6 ))
|
|
SV_UpdateUserInfo( cl );
|
|
}
|
|
|
|
if( FBitSet( cl->flags, FCL_RESEND_MOVEVARS ))
|
|
{
|
|
SV_FullUpdateMovevars( cl, &cl->netchan.message );
|
|
ClearBits( cl->flags, FCL_RESEND_MOVEVARS );
|
|
}
|
|
}
|
|
|
|
// clear the server datagram if it overflowed.
|
|
if( MSG_CheckOverflow( &sv.datagram ))
|
|
{
|
|
Con_DPrintf( S_ERROR "sv.datagram overflowed!\n" );
|
|
MSG_Clear( &sv.datagram );
|
|
}
|
|
|
|
// clear the server datagram if it overflowed.
|
|
if( MSG_CheckOverflow( &sv.spec_datagram ))
|
|
{
|
|
Con_DPrintf( S_ERROR "sv.spec_datagram overflowed!\n" );
|
|
MSG_Clear( &sv.spec_datagram );
|
|
}
|
|
|
|
// now send the reliable and server datagrams to all clients.
|
|
for( i = 0, cl = svs.clients; i < svs.maxclients; i++, cl++ )
|
|
{
|
|
if( cl->state < cs_connected || FBitSet( cl->flags, FCL_FAKECLIENT ))
|
|
continue; // reliables go to all connected or spawned
|
|
|
|
if( MSG_GetNumBytesWritten( &sv.reliable_datagram ) < MSG_GetNumBytesLeft( &cl->netchan.message ))
|
|
MSG_WriteBits( &cl->netchan.message, MSG_GetBuf( &sv.reliable_datagram ), MSG_GetNumBitsWritten( &sv.reliable_datagram ));
|
|
else Netchan_CreateFragments( &cl->netchan, &sv.reliable_datagram );
|
|
|
|
if( MSG_GetNumBytesWritten( &sv.datagram ) < MSG_GetNumBytesLeft( &cl->datagram ))
|
|
MSG_WriteBits( &cl->datagram, MSG_GetBuf( &sv.datagram ), MSG_GetNumBitsWritten( &sv.datagram ));
|
|
else Con_DPrintf( S_WARN "Ignoring unreliable datagram for %s, would overflow\n", cl->name );
|
|
|
|
if( FBitSet( cl->flags, FCL_HLTV_PROXY ))
|
|
{
|
|
if( MSG_GetNumBytesWritten( &sv.spec_datagram ) < MSG_GetNumBytesLeft( &cl->datagram ))
|
|
MSG_WriteBits( &cl->datagram, MSG_GetBuf( &sv.spec_datagram ), MSG_GetNumBitsWritten( &sv.spec_datagram ));
|
|
else Con_DPrintf( S_WARN "Ignoring spectator datagram for %s, would overflow\n", cl->name );
|
|
}
|
|
}
|
|
|
|
// now clear the reliable and datagram buffers.
|
|
MSG_Clear( &sv.reliable_datagram );
|
|
MSG_Clear( &sv.spec_datagram );
|
|
MSG_Clear( &sv.datagram );
|
|
}
|
|
|
|
/*
|
|
=======================
|
|
SV_SendClientMessages
|
|
=======================
|
|
*/
|
|
void SV_SendClientMessages( void )
|
|
{
|
|
sv_client_t *cl;
|
|
int i;
|
|
|
|
if( sv.state == ss_dead )
|
|
return;
|
|
|
|
SV_UpdateToReliableMessages ();
|
|
|
|
// send a message to each connected client
|
|
for( i = 0, sv.current_client = svs.clients; i < svs.maxclients; i++, sv.current_client++ )
|
|
{
|
|
cl = sv.current_client;
|
|
|
|
if( cl->state <= cs_zombie || FBitSet( cl->flags, FCL_FAKECLIENT ))
|
|
continue;
|
|
|
|
if( FBitSet( cl->flags, FCL_SKIP_NET_MESSAGE ))
|
|
{
|
|
ClearBits( cl->flags, FCL_SKIP_NET_MESSAGE );
|
|
continue;
|
|
}
|
|
|
|
if( !host_limitlocal->value && NET_IsLocalAddress( cl->netchan.remote_address ))
|
|
SetBits( cl->flags, FCL_SEND_NET_MESSAGE );
|
|
|
|
if( cl->state == cs_spawned )
|
|
{
|
|
if(( host.realtime + sv.frametime ) >= cl->next_messagetime )
|
|
SetBits( cl->flags, FCL_SEND_NET_MESSAGE );
|
|
}
|
|
|
|
// if the reliable message overflowed, drop the client
|
|
if( MSG_CheckOverflow( &cl->netchan.message ))
|
|
{
|
|
MSG_Clear( &cl->netchan.message );
|
|
MSG_Clear( &cl->datagram );
|
|
SV_BroadcastPrintf( NULL, "%s overflowed\n", cl->name );
|
|
Con_DPrintf( S_ERROR "reliable overflow for %s\n", cl->name );
|
|
SV_DropClient( cl, false );
|
|
SetBits( cl->flags, FCL_SEND_NET_MESSAGE );
|
|
cl->netchan.cleartime = 0.0; // don't choke this message
|
|
}
|
|
else if( FBitSet( cl->flags, FCL_SEND_NET_MESSAGE ))
|
|
{
|
|
// If we haven't gotten a message in sv_failuretime seconds, then stop sending messages to this client
|
|
// until we get another packet in from the client. This prevents crash/drop and reconnect where they are
|
|
// being hosed with "sequenced packet without connection" packets.
|
|
if( sv_failuretime.value < ( host.realtime - cl->netchan.last_received ))
|
|
ClearBits( cl->flags, FCL_SEND_NET_MESSAGE );
|
|
}
|
|
|
|
// only send messages if the client has sent one
|
|
// and the bandwidth is not choked
|
|
if( FBitSet( cl->flags, FCL_SEND_NET_MESSAGE ))
|
|
{
|
|
// bandwidth choke active?
|
|
if( !Netchan_CanPacket( &cl->netchan, cl->state == cs_spawned ))
|
|
{
|
|
cl->chokecount++;
|
|
continue;
|
|
}
|
|
|
|
// now that we were able to send, reset timer to point to next possible send time.
|
|
cl->next_messagetime = host.realtime + sv.frametime + cl->cl_updaterate;
|
|
ClearBits( cl->flags, FCL_SEND_NET_MESSAGE );
|
|
|
|
// NOTE: we should send frame even if server is not simulated to prevent overflow
|
|
if( cl->state == cs_spawned )
|
|
SV_SendClientDatagram( cl );
|
|
else Netchan_TransmitBits( &cl->netchan, 0, NULL ); // just update reliable
|
|
}
|
|
}
|
|
|
|
// reset current client
|
|
sv.current_client = NULL;
|
|
}
|
|
|
|
/*
|
|
=======================
|
|
SV_SendMessagesToAll
|
|
|
|
e.g. before changing level
|
|
=======================
|
|
*/
|
|
void SV_SendMessagesToAll( void )
|
|
{
|
|
sv_client_t *cl;
|
|
int i;
|
|
|
|
if( sv.state == ss_dead )
|
|
return;
|
|
|
|
for( i = 0, cl = svs.clients; i < svs.maxclients; i++, cl++ )
|
|
{
|
|
if( cl->state >= cs_connected )
|
|
SetBits( cl->flags, FCL_SEND_NET_MESSAGE );
|
|
}
|
|
|
|
SV_SendClientMessages();
|
|
}
|
|
|
|
/*
|
|
=======================
|
|
SV_SkipUpdates
|
|
|
|
used before changing level
|
|
=======================
|
|
*/
|
|
void SV_SkipUpdates( void )
|
|
{
|
|
sv_client_t *cl;
|
|
int i;
|
|
|
|
if( sv.state == ss_dead )
|
|
return;
|
|
|
|
for( i = 0, cl = svs.clients; i < svs.maxclients; i++, cl++ )
|
|
{
|
|
if( cl->state != cs_spawned || FBitSet( cl->flags, FCL_FAKECLIENT ))
|
|
continue;
|
|
|
|
SetBits( cl->flags, FCL_SKIP_NET_MESSAGE );
|
|
}
|
|
}
|
|
|
|
/*
|
|
=======================
|
|
SV_InactivateClients
|
|
|
|
Purpose: Prepare for level transition, etc.
|
|
=======================
|
|
*/
|
|
void SV_InactivateClients( void )
|
|
{
|
|
int i;
|
|
sv_client_t *cl;
|
|
|
|
if( sv.state == ss_dead )
|
|
return;
|
|
|
|
// send a message to each connected client
|
|
for( i = 0, cl = svs.clients; i < svs.maxclients; i++, cl++ )
|
|
{
|
|
if( !cl->state || !cl->edict )
|
|
continue;
|
|
|
|
if( !cl->edict )
|
|
continue;
|
|
|
|
if( FBitSet( cl->edict->v.flags, FL_FAKECLIENT ))
|
|
{
|
|
SV_DropClient( cl, false );
|
|
continue;
|
|
}
|
|
|
|
if( cl->state > cs_connected )
|
|
cl->state = cs_connected;
|
|
|
|
COM_ClearCustomizationList( &cl->customdata, false );
|
|
memset( cl->physinfo, 0, MAX_PHYSINFO_STRING );
|
|
|
|
// NOTE: many mods sending messages that must be applied on a next level
|
|
// e.g. CryOfFear sending HideHud and PlayMp3 that affected after map change
|
|
if( svgame.globals->changelevel )
|
|
continue;
|
|
|
|
MSG_Clear( &cl->netchan.message );
|
|
MSG_Clear( &cl->datagram );
|
|
}
|
|
}
|