summaryrefslogtreecommitdiff
path: root/source/sv_ents.c
diff options
context:
space:
mode:
authorAndrey Nazarov <skuller@skuller.net>2007-08-14 20:18:08 +0000
committerAndrey Nazarov <skuller@skuller.net>2007-08-14 20:18:08 +0000
commitf294db4ccf45f6274e65260dd6f9a2c5faa94313 (patch)
treee8cf1ba2bfe9c8417eec17faf912442f52fc4ef2 /source/sv_ents.c
Initial import of the new Q2PRO tree.
Diffstat (limited to 'source/sv_ents.c')
-rw-r--r--source/sv_ents.c725
1 files changed, 725 insertions, 0 deletions
diff --git a/source/sv_ents.c b/source/sv_ents.c
new file mode 100644
index 0000000..4f91e61
--- /dev/null
+++ b/source/sv_ents.c
@@ -0,0 +1,725 @@
+/*
+Copyright (C) 1997-2001 Id Software, Inc.
+
+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 2
+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.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+
+*/
+
+#include "sv_local.h"
+#include "mvd_local.h"
+
+/*
+=============================================================================
+
+Encode a client frame onto the network channel
+
+=============================================================================
+*/
+
+/*
+=============
+SV_EmitPacketEntities
+
+Writes a delta update of an entity_state_t list to the message.
+=============
+*/
+void SV_EmitPacketEntities( client_frame_t *from,
+ client_frame_t *to,
+ int clientEntityNum,
+ frameparam_t *param )
+{
+ entity_state_t *oldent, *newent;
+ byte *base;
+ uint32 oldindex, newindex;
+ int oldnum, newnum;
+ uint32 from_num_entities;
+ msgEsFlags_t flags;
+ uint32 i;
+
+ if( !from )
+ from_num_entities = 0;
+ else
+ from_num_entities = from->numEntities;
+
+ newindex = 0;
+ oldindex = 0;
+ oldent = newent = 0;
+ while( newindex < to->numEntities || oldindex < from_num_entities ) {
+ if( newindex >= to->numEntities ) {
+ newnum = 9999;
+ } else {
+ i = ( to->firstEntity + newindex ) % svs.numEntityStates;
+ newent = &svs.entityStates[i];
+ newnum = newent->number;
+ }
+
+ if( oldindex >= from_num_entities ) {
+ oldnum = 9999;
+ } else {
+ i = ( from->firstEntity + oldindex ) % svs.numEntityStates;
+ oldent = &svs.entityStates[i];
+ 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
+ // note that players are always 'newentities', this updates their
+ // oldorigin always and prevents warping
+ flags = 0;
+ if( newent->number <= param->maxplayers ) {
+ // flags |= MSG_ES_NEWENTITY; // FIXME: why?
+ if( clientEntityNum == MAX_EDICTS ||
+ newent->number == clientEntityNum )
+ {
+ flags |= MSG_ES_FIRSTPERSON;
+ }
+ }
+ MSG_WriteDeltaEntity( oldent, newent, flags );
+ oldindex++;
+ newindex++;
+ continue;
+ }
+
+ if( newnum < oldnum ) {
+ // this is a new entity, send it from the baseline
+ flags = MSG_ES_FORCE|MSG_ES_NEWENTITY;
+ if( newent->number <= param->maxplayers ) {
+ if( clientEntityNum == MAX_EDICTS ||
+ newent->number == clientEntityNum )
+ {
+ flags |= MSG_ES_FIRSTPERSON;
+ }
+ }
+ base = param->baselines[newnum >> SV_BASELINES_SHIFT];
+ if( base ) {
+ base += param->basesize * ( newnum & SV_BASELINES_MASK );
+ }
+ MSG_WriteDeltaEntity( ( entity_state_t * )base, newent, flags );
+ newindex++;
+ continue;
+ }
+
+ if( newnum > oldnum ) {
+ // the old entity isn't present in the new message
+ MSG_WriteDeltaEntity( oldent, NULL, MSG_ES_FORCE );
+ oldindex++;
+ continue;
+ }
+ }
+
+ MSG_WriteShort( 0 ); // end of packetentities
+}
+
+void SV_EmitPacketPlayers( client_frame_t *from,
+ client_frame_t *to,
+ msgPsFlags_t flags )
+{
+ player_state_t *oldps, *newps;
+ uint32 oldindex, newindex;
+ int oldnum, newnum;
+ uint32 from_num_players;
+ uint32 i;
+
+ if( !from ) {
+ from_num_players = 0;
+ } else {
+ from_num_players = from->numPlayers;
+ }
+
+ newindex = 0;
+ oldindex = 0;
+ oldps = newps = NULL;
+ while( newindex < to->numPlayers || oldindex < from_num_players ) {
+ if( newindex >= to->numPlayers ) {
+ newnum = 9999;
+ } else {
+ i = ( to->firstPlayer + newindex ) % svs.numPlayerStates;
+ newps = &svs.playerStates[i];
+ newnum = PPS_NUM( newps );
+ }
+
+ if( oldindex >= from_num_players ) {
+ oldnum = 9999;
+ } else {
+ i = ( from->firstPlayer + oldindex ) % svs.numPlayerStates;
+ oldps = &svs.playerStates[i];
+ oldnum = PPS_NUM( oldps );
+ }
+
+ 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 player has not changed at all
+ MSG_WriteDeltaPlayerstate_Packet( oldps, newps, flags );
+ oldindex++;
+ newindex++;
+ continue;
+ }
+
+ if( newnum < oldnum ) {
+ // this is a new player, send it from the baseline
+ MSG_WriteDeltaPlayerstate_Packet( NULL, newps,
+ flags | MSG_PS_FORCE );
+ newindex++;
+ continue;
+ }
+
+ if( newnum > oldnum ) {
+ // the old player isn't present in the new message
+ MSG_WriteDeltaPlayerstate_Packet( oldps, NULL,
+ flags | MSG_PS_FORCE );
+ oldindex++;
+ continue;
+ }
+ }
+
+ MSG_WriteByte( CLIENTNUM_NONE ); // end of packetplayers
+}
+
+/*
+==================
+SV_WriteFrameToClient_Default
+==================
+*/
+void SV_WriteFrameToClient_Default( client_t *client ) {
+ client_frame_t *frame, *oldframe;
+ player_state_t *state, *oldstate;
+ int lastframe;
+
+ // this is the frame we are creating
+ frame = &client->frames[sv.framenum & UPDATE_MASK];
+ state = &svs.playerStates[frame->firstPlayer % svs.numPlayerStates];
+ client->frame_latency[sv.framenum & LATENCY_MASK] = -1;
+
+ if( client->lastframe <= 0 ) {
+ // client is asking for a retransmit
+ oldframe = NULL;
+ oldstate = NULL;
+ lastframe = -1;
+ } else if( sv.framenum - client->lastframe > UPDATE_BACKUP - 1 ) {
+ // client hasn't gotten a good message through in a long time
+ Com_DPrintf( "%s: delta request from out-of-date packet.\n", client->name );
+ oldframe = NULL;
+ oldstate = NULL;
+ lastframe = -1;
+ } else {
+ // we have a valid message to delta from
+ oldframe = &client->frames[client->lastframe & UPDATE_MASK];
+ oldstate = &svs.playerStates[oldframe->firstPlayer % svs.numPlayerStates];
+ lastframe = client->lastframe;
+ if( svs.nextEntityStates - oldframe->firstEntity > svs.numEntityStates ) {
+ Com_DPrintf( "%s: delta request from out-of-date entities.\n", client->name );
+ oldframe = NULL;
+ oldstate = NULL;
+ lastframe = -1;
+ }
+ }
+
+ MSG_WriteByte( svc_frame );
+ MSG_WriteLong( sv.framenum );
+ MSG_WriteLong( lastframe ); // what we are delta'ing from
+ MSG_WriteByte( client->surpressCount ); // rate dropped packets
+ client->surpressCount = 0;
+ client->frameflags = 0;
+
+ // send over the areabits
+ MSG_WriteByte( frame->areabytes );
+ MSG_WriteData( frame->areabits, frame->areabytes );
+
+ // delta encode the playerstate
+ MSG_WriteByte( svc_playerinfo );
+ MSG_WriteDeltaPlayerstate_Default( oldstate, state );
+
+ // delta encode the entities
+ MSG_WriteByte( svc_packetentities );
+ SV_EmitPacketEntities( oldframe, frame, 0, &client->param );
+}
+
+/*
+==================
+SV_WriteFrameToClient_Enhanced
+==================
+*/
+void SV_WriteFrameToClient_Enhanced( client_t *client ) {
+ client_frame_t *frame, *oldframe;
+ player_state_t *oldstate, *state;
+ uint32 extraflags;
+ int delta, surpressed;
+ byte *b1, *b2;
+ msgPsFlags_t psFlags;
+ int clientEntityNum;
+
+ // this is the frame we are creating
+ frame = &client->frames[sv.framenum & UPDATE_MASK];
+ state = &svs.playerStates[frame->firstPlayer % svs.numPlayerStates];
+ client->frame_latency[sv.framenum & LATENCY_MASK] = -1;
+
+ if( client->lastframe <= 0 ) {
+ // client is asking for a retransmit
+ oldframe = NULL;
+ oldstate = NULL;
+ delta = 31;
+ } else if( sv.framenum - client->lastframe > UPDATE_BACKUP - 1 ) {
+ // client hasn't gotten a good message through in a long time
+ Com_DPrintf( "%s: delta request from out-of-date packet.\n", client->name );
+ oldframe = NULL;
+ oldstate = NULL;
+ delta = 31;
+ } else {
+ // we have a valid message to delta from
+ oldframe = &client->frames[client->lastframe & UPDATE_MASK];
+ oldstate = &svs.playerStates[oldframe->firstPlayer % svs.numPlayerStates];
+ delta = sv.framenum - client->lastframe;
+ if( svs.nextEntityStates - oldframe->firstEntity > svs.numEntityStates ) {
+ Com_DPrintf( "%s: delta request from out-of-date entities.\n", client->name );
+ oldframe = NULL;
+ oldstate = NULL;
+ delta = 31;
+ }
+ }
+
+ // first byte to be patched
+ b1 = SZ_GetSpace( &msg_write, 1 );
+
+ MSG_WriteLong( ( sv.framenum & FRAMENUM_MASK ) | ( delta << FRAMENUM_BITS ) );
+
+ // second byte to be patched
+ b2 = SZ_GetSpace( &msg_write, 1 );
+
+ // send over the areabits
+ MSG_WriteByte( frame->areabytes );
+ MSG_WriteData( frame->areabits, frame->areabytes );
+
+ // ignore some parts of playerstate if not recording demo
+ psFlags = 0;
+ if( !client->settings[CLS_RECORDING] ) {
+ if( client->settings[CLS_NOGUN] ) {
+ psFlags |= MSG_PS_IGNORE_GUNFRAMES;
+ if( client->settings[CLS_NOGUN] != 2 ) {
+ psFlags |= MSG_PS_IGNORE_GUNINDEX;
+ }
+ }
+ if( client->settings[CLS_NOBLEND] ) {
+ psFlags |= MSG_PS_IGNORE_BLEND;
+ }
+ if( state->pmove.pm_type < PM_DEAD ) {
+ if( !( state->pmove.pm_flags & PMF_NO_PREDICTION ) ) {
+ psFlags |= MSG_PS_IGNORE_VIEWANGLES;
+ }
+ } else {
+ // lying dead on a rotating platform?
+ psFlags |= MSG_PS_IGNORE_DELTAANGLES;
+ }
+ }
+
+ clientEntityNum = 0;
+ if( client->protocol == PROTOCOL_VERSION_Q2PRO ) {
+ if( state->pmove.pm_type < PM_DEAD ) {
+ clientEntityNum = frame->clientNum + 1;
+ }
+ if( client->settings[CLS_NOPREDICT] ) {
+ psFlags |= MSG_PS_IGNORE_PREDICTION;
+ }
+ surpressed = client->frameflags;
+ } else {
+ surpressed = client->surpressCount;
+ }
+
+ // delta encode the playerstate
+ extraflags = MSG_WriteDeltaPlayerstate_Enhanced( oldstate, state, psFlags );
+
+ if( client->protocol == PROTOCOL_VERSION_Q2PRO ) {
+ // delta encode the clientNum
+ int clientNum = oldframe ? oldframe->clientNum : 0;
+ if( clientNum != frame->clientNum ) {
+ extraflags |= EPS_CLIENTNUM;
+ MSG_WriteByte( frame->clientNum );
+ }
+ }
+
+ // save 3 high bits of extraflags
+ *b1 = svc_frame | ( ( ( extraflags & 0x70 ) << 1 ) );
+
+ // save 4 low bits of extraflags
+ *b2 = ( surpressed & SURPRESSCOUNT_MASK ) |
+ ( ( extraflags & 0x0F ) << SURPRESSCOUNT_BITS );
+
+ client->surpressCount = 0;
+ client->frameflags = 0;
+
+ // delta encode the entities
+ SV_EmitPacketEntities( oldframe, frame, clientEntityNum, &client->param );
+}
+
+/*
+=============================================================================
+
+Build a client frame structure
+
+=============================================================================
+*/
+
+#define BODY_QUEUE_SIZE 8
+
+qboolean SV_EdictPV( edict_t *ent, byte *mask ) {
+ cnode_t *node;
+ int i, l;
+
+ if( ent->num_clusters == -1 ) {
+ // too many leafs for individual check, go by headnode
+ node = CM_NodeNum( &sv.cm, ent->headnode );
+ return CM_HeadnodeVisible( node, mask );
+ }
+
+ // check individual leafs
+ for( i = 0; i < ent->num_clusters; i++ ) {
+ l = ent->clusternums[i];
+ if( Q_IsBitSet( mask, l ) ) {
+ return qtrue;
+ }
+ }
+ return qfalse; // not visible
+}
+/*
+=============
+SV_BuildClientFrame
+
+Decides which entities are going to be visible to the client, and
+copies off the playerstat and areabits.
+=============
+*/
+void SV_BuildClientFrame( client_t *client ) {
+ int e;
+ vec3_t org;
+ edict_t *ent;
+ edict_t *clent;
+ client_frame_t *frame;
+ entity_state_t *state;
+ player_state_t *ps;
+ int l;
+ int clientarea, clientcluster;
+ cleaf_t *leaf;
+ byte *clientphs;
+ byte *clientpvs;
+
+ clent = client->edict;
+ if( !clent->client )
+ return; // not in game yet
+
+ // this is the frame we are creating
+ frame = &client->frames[sv.framenum & UPDATE_MASK];
+
+ frame->senttime = svs.realtime; // save it for ping calc later
+
+ // find the client's PVS
+ ps = &clent->client->ps;
+ VectorMA( ps->viewoffset, 0.125f, ps->pmove.origin, org );
+
+ leaf = CM_PointLeaf( &sv.cm, org );
+ clientarea = CM_LeafArea( leaf );
+ clientcluster = CM_LeafCluster( leaf );
+
+ // calculate the visible areas
+ frame->areabytes = CM_WriteAreaBits( &sv.cm, frame->areabits, clientarea );
+
+ // grab the current player_state_t
+ frame->numPlayers = 1;
+ frame->firstPlayer = svs.nextPlayerStates;
+ svs.playerStates[svs.nextPlayerStates % svs.numPlayerStates] = *ps;
+ svs.nextPlayerStates++;
+
+ // grab the current clientNum
+#if 1
+ frame->clientNum = clent->client->clientNum;
+#else
+ // TODO: support from game DLL required
+ frame->clientNum = client->number;
+#endif
+
+ clientpvs = CM_FatPVS( &sv.cm, org );
+ clientphs = CM_ClusterPHS( &sv.cm, clientcluster );
+
+ // build up the list of visible entities
+ frame->numEntities = 0;
+ frame->firstEntity = svs.nextEntityStates;
+
+ for( e = 1; e < ge->num_edicts; e++ ) {
+ ent = EDICT_NUM( e );
+
+ // ignore ents without visible models
+ if( ent->svflags & SVF_NOCLIENT )
+ continue;
+
+ // ignore ents without visible models unless they have an effect
+ if( !ent->s.modelindex && !ent->s.effects && !ent->s.sound ) {
+ if( !ent->s.event ) {
+ continue;
+ }
+ if( ent->s.event == EV_FOOTSTEP && client->settings[CLS_NOFOOTSTEPS] ) {
+ continue;
+ }
+ }
+
+ if( ( ent->s.effects & EF_GIB ) && client->settings[CLS_NOGIBS] ) {
+ continue;
+ }
+
+ // ignore if not touching a PV leaf
+ if( ent != clent ) {
+ // check area
+ if( !CM_AreasConnected( &sv.cm, clientarea, ent->areanum ) ) {
+ // doors can legally straddle two areas, so
+ // we may need to check another one
+ if( !ent->areanum2 ||
+ !CM_AreasConnected( &sv.cm, clientarea, ent->areanum2 ) )
+ {
+ continue; // blocked by a door
+ }
+ }
+
+ // beams just check one point for PHS
+ if( ent->s.renderfx & RF_BEAM ) {
+ l = ent->clusternums[0];
+ if( !Q_IsBitSet( clientphs, l ) )
+ continue;
+ } else {
+ if( !SV_EdictPV( ent, clientpvs ) ) {
+ continue;
+ }
+
+ if( !ent->s.modelindex ) {
+ // don't send sounds if they will be attenuated away
+ vec3_t delta;
+ float len;
+
+ VectorSubtract( org, ent->s.origin, delta );
+ len = VectorLength( delta );
+ if( len > 400 )
+ continue;
+ }
+ }
+ }
+
+ if( ent->s.number != e ) {
+ Com_DPrintf( "FIXING ENT->S.NUMBER!!!\n" );
+ ent->s.number = e;
+ }
+
+ // add it to the circular client_entities array
+ state = &svs.entityStates[svs.nextEntityStates % svs.numEntityStates];
+ *state = ent->s;
+ if( ent->s.event == EV_FOOTSTEP && client->settings[CLS_NOFOOTSTEPS] ) {
+ state->event = 0;
+ }
+
+ if( sv_bodyque_hack->integer ) {
+ if( state->number >= sv_maxclients->integer + 1 &&
+ state->number <= sv_maxclients->integer + BODY_QUEUE_SIZE )
+ {
+ state->event = EV_OTHER_TELEPORT;
+ }
+ }
+
+ // TODO: support from game DLL required
+#if 1
+ if( client->protocol != PROTOCOL_VERSION_Q2PRO &&
+ e == frame->clientNum + 1 )
+ {
+ state->modelindex = 0;
+ }
+#endif
+
+ // don't mark players missiles as solid
+ if( ent->owner == client->edict )
+ state->solid = 0;
+
+ svs.nextEntityStates++;
+ frame->numEntities++;
+
+ if( frame->numEntities == MAX_PACKET_ENTITIES ) {
+ break;
+ }
+ }
+}
+
+/*
+=============
+SV_BuildProxyClientFrame
+
+=============
+*/
+void SV_BuildProxyClientFrame( client_t *client ) {
+ udpClient_t *mvdcl = EDICT_MVDCL( client->edict );
+ mvd_t *mvd = mvdcl->mvd;
+ mvdFrame_t *mvdframe = &mvd->frames[mvd->framenum & MVD_UPDATE_MASK];
+ int i, j, k, l;
+ vec3_t org;
+ client_frame_t *frame;
+ entity_state_t *state;
+ entityStateEx_t *ent;
+ player_state_t *ps;
+ int clientarea, clientcluster;
+ cleaf_t *leaf;
+ byte *clientphs;
+ byte *clientpvs;
+
+ // this is the frame we are creating
+ frame = &client->frames[sv.framenum & UPDATE_MASK];
+ frame->senttime = svs.realtime; // save it for ping calc later
+
+ // find the client's PVS
+ ps = &mvdcl->ps;
+ VectorMA( ps->viewoffset, 0.125f, ps->pmove.origin, org );
+
+ leaf = CM_PointLeaf( &mvd->cm, org );
+ clientarea = CM_LeafArea( leaf );
+ clientcluster = CM_LeafCluster( leaf );
+
+ // copy areabits
+ frame->areabytes = CM_WriteAreaBits( &mvd->cm, frame->areabits,
+ clientarea );
+
+ // grab the current player_state_t
+ frame->numPlayers = 1;
+ frame->firstPlayer = svs.nextPlayerStates;
+ svs.playerStates[svs.nextPlayerStates % svs.numPlayerStates] = *ps;
+ svs.nextPlayerStates++;
+
+ // grab the current clientNum
+ frame->clientNum = mvdcl->clientNum;
+
+ clientpvs = CM_FatPVS( &mvd->cm, org );
+ clientphs = CM_ClusterPHS( &mvd->cm, clientcluster );
+
+ // build up the list of visible entities
+ frame->numEntities = 0;
+ frame->firstEntity = svs.nextEntityStates;
+
+ for( i = 0; i < mvdframe->numEntities; i++ ) {
+ j = ( mvdframe->firstEntity + i ) & MVD_ENTITIES_MASK;
+ ent = &mvd->entityStates[j];
+
+ // ignore ents without visible models unless they have an effect
+ if( !ent->s.modelindex && !ent->s.effects && !ent->s.sound ) {
+ if( !ent->s.event ) {
+ continue;
+ }
+ if( ent->s.event == EV_FOOTSTEP && client->settings[CLS_NOFOOTSTEPS] ) {
+ continue;
+ }
+ }
+
+ if( ( ent->s.effects & EF_GIB ) && client->settings[CLS_NOGIBS] ) {
+ continue;
+ }
+
+ // ignore if not touching a PV leaf
+ if( ent->linked ) {
+ // check area
+ if( !CM_AreasConnected( &mvd->cm, clientarea, ent->areanum ) ) {
+ // doors can legally straddle two areas, so
+ // we may need to check another one
+ if( !ent->areanum2 ) {
+ continue;
+ }
+ if( !CM_AreasConnected( &mvd->cm, clientarea, ent->areanum2 ) ) {
+ continue; // blocked by a door
+ }
+ }
+
+ // beams just check one point for PHS
+ if( ent->s.renderfx & RF_BEAM ) {
+ l = ent->clusternums[0];
+ if( !Q_IsBitSet( clientphs, l ) ) {
+ continue;
+ }
+ } else {
+ if( ent->num_clusters == -1 ) {
+ // too many leafs for individual check, go by headnode
+ if( !CM_HeadnodeVisible( ent->headnode, clientpvs ) ) {
+ continue;
+ }
+ } else {
+ // check individual leafs
+ for( k = 0; k < ent->num_clusters; k++ ) {
+ l = ent->clusternums[k];
+ if( Q_IsBitSet( clientpvs, l ) ) {
+ break;
+ }
+ }
+ if( k == ent->num_clusters ) {
+ continue; // not visible
+ }
+ }
+
+ if( !ent->s.modelindex ) {
+ // don't send sounds if they will be attenuated away
+ vec3_t delta;
+ float len;
+
+ VectorSubtract( org, ent->s.origin, delta );
+ len = VectorLength( delta );
+ if( len > 400 ) {
+ continue;
+ }
+ }
+ }
+ }
+
+ // add it to the circular client_entities array
+ state = &svs.entityStates[svs.nextEntityStates % svs.numEntityStates];
+ *state = ent->s;
+ if( mvdframe->number == mvdcl->lastframe ) {
+ // have already seen this frame
+ state->event = 0;
+ if( !( state->renderfx & RF_BEAM ) ) {
+ VectorCopy( state->origin, state->old_origin );
+ }
+ } else {
+ if( state->event == EV_FOOTSTEP &&
+ client->settings[CLS_NOFOOTSTEPS] )
+ {
+ state->event = 0;
+ }
+
+ if( sv_bodyque_hack->integer ) {
+ if( state->number >= mvd->maxclients + 1 &&
+ state->number <= mvd->maxclients + BODY_QUEUE_SIZE )
+ {
+ state->event = EV_OTHER_TELEPORT;
+ }
+ }
+ }
+
+ if( client->protocol != PROTOCOL_VERSION_Q2PRO &&
+ state->number == frame->clientNum + 1 )
+ {
+ /* clear the modelindex for the player entity this
+ * client is following.
+ * FIXME: this way EF_FLAG effects are lost, etc */
+ state->modelindex = 0;
+ }
+
+ svs.nextEntityStates++;
+ frame->numEntities++;
+ }
+
+ mvdcl->lastframe = mvdframe->number;
+}
+
+