Xash3D FWGS engine.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

1500 lines
36 KiB

/*
cl_frame.c - client 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 "client.h"
#include "net_encode.h"
#include "entity_types.h"
#include "pm_local.h"
#include "cl_tent.h"
#include "studio.h"
#include "dlight.h"
#include "sound.h"
#include "input.h"
// #define STUDIO_INTERPOLATION_FIX
/*
==================
CL_IsPlayerIndex
detect player entity
==================
*/
qboolean CL_IsPlayerIndex( int idx )
{
return ( idx >= 1 && idx <= cl.maxclients );
}
/*
=========================================================================
FRAME INTERPOLATION
=========================================================================
*/
/*
==================
CL_UpdatePositions
Store another position into interpolation circular buffer
==================
*/
static void CL_UpdatePositions( cl_entity_t *ent )
{
position_history_t *ph, *prev;
prev = &ent->ph[ent->current_position];
ent->current_position = (ent->current_position + 1) & HISTORY_MASK;
ph = &ent->ph[ent->current_position];
VectorCopy( ent->curstate.origin, ph->origin );
VectorCopy( ent->curstate.angles, ph->angles );
ph->animtime = ent->curstate.animtime;
// a1ba: for some reason, this sometimes still may happen
// at this time, I'm not sure whether this bug happens in delta readwrite code
// or server just decides to go backwards and really sends these values
if( ph->animtime < prev->animtime )
{
// try to deduce real animtime by looking up the difference between
// server messages (cl.mtime is never modified ny the interpolation code)
float diff = Q_max( 0, ent->curstate.msg_time - ent->prevstate.msg_time );
ph->animtime = prev->animtime + diff;
}
}
/*
==================
CL_ResetPositions
Interpolation init or reset after teleporting
==================
*/
static void CL_ResetPositions( cl_entity_t *ent )
{
position_history_t store;
if( !ent ) return;
store = ent->ph[ent->current_position];
ent->current_position = 1;
memset( ent->ph, 0, sizeof( position_history_t ) * HISTORY_MAX );
memcpy( &ent->ph[1], &store, sizeof( position_history_t ));
memcpy( &ent->ph[0], &store, sizeof( position_history_t ));
}
/*
==================
CL_EntityTeleported
check for instant movement in case
we don't want interpolate this
==================
*/
static qboolean CL_EntityTeleported( cl_entity_t *ent )
{
float len, maxlen;
vec3_t delta;
VectorSubtract( ent->curstate.origin, ent->prevstate.origin, delta );
// compute potential max movement in units per frame and compare with entity movement
maxlen = ( clgame.movevars.maxvelocity * ( 1.0f / GAME_FPS ));
len = VectorLength( delta );
return (len > maxlen);
}
/*
==================
CL_CompareTimestamps
round-off floating errors
==================
*/
static qboolean CL_CompareTimestamps( float t1, float t2 )
{
int iTime1 = t1 * 1000;
int iTime2 = t2 * 1000;
return (( iTime1 - iTime2 ) <= 1 );
}
/*
==================
CL_EntityIgnoreLerp
some ents will be ignore lerping
==================
*/
static qboolean CL_EntityIgnoreLerp( cl_entity_t *e )
{
if( cl_nointerp.value > 0.f )
return true;
if( e->model && e->model->type == mod_alias )
return false;
return (e->curstate.movetype == MOVETYPE_NONE) ? true : false;
}
/*
==================
CL_EntityCustomLerp
==================
*/
static qboolean CL_EntityCustomLerp( cl_entity_t *e )
{
switch( e->curstate.movetype )
{
case MOVETYPE_NONE:
case MOVETYPE_STEP:
case MOVETYPE_WALK:
case MOVETYPE_FLY:
case MOVETYPE_COMPOUND:
return false;
// ABSOLUTELY STUPID HACK TO ALLOW MONSTERS
// INTERPOLATION IN GRAVGUNMOD COOP
// MUST BE REMOVED ONCE WE REMOVE 48 PROTO SUPPORT
case MOVETYPE_TOSS:
if( cls.legacymode && e->model && e->model->type == mod_studio )
return false;
}
return true;
}
/*
==================
CL_ParametricMove
check for parametrical moved entities
==================
*/
static qboolean CL_ParametricMove( cl_entity_t *ent )
{
float frac, dt, t;
vec3_t delta;
if( ent->curstate.starttime == 0.0f || ent->curstate.impacttime == 0.0f )
return false;
VectorSubtract( ent->curstate.endpos, ent->curstate.startpos, delta );
dt = ent->curstate.impacttime - ent->curstate.starttime;
if( dt != 0.0f )
{
if( ent->lastmove > cl.time )
t = ent->lastmove;
else t = cl.time;
frac = ( t - ent->curstate.starttime ) / dt;
frac = bound( 0.0f, frac, 1.0f );
VectorMA( ent->curstate.startpos, frac, delta, ent->curstate.origin );
ent->lastmove = t;
}
VectorNormalize( delta );
if( VectorLength( delta ) > 0.0f )
VectorAngles( delta, ent->curstate.angles ); // re-aim projectile
return true;
}
/*
====================
CL_UpdateLatchedVars
====================
*/
static void CL_UpdateLatchedVars( cl_entity_t *ent )
{
if( !ent->model || ( ent->model->type != mod_alias && ent->model->type != mod_studio ))
return; // below fields used only for alias and studio interpolation
VectorCopy( ent->prevstate.origin, ent->latched.prevorigin );
VectorCopy( ent->prevstate.angles, ent->latched.prevangles );
if( ent->model->type == mod_alias )
ent->latched.prevframe = ent->prevstate.frame;
ent->latched.prevanimtime = ent->prevstate.animtime;
if( ent->curstate.sequence != ent->prevstate.sequence )
{
memcpy( ent->latched.prevseqblending, ent->prevstate.blending, sizeof( ent->latched.prevseqblending ));
ent->latched.prevsequence = ent->prevstate.sequence;
ent->latched.sequencetime = ent->curstate.animtime;
}
memcpy( ent->latched.prevcontroller, ent->prevstate.controller, sizeof( ent->latched.prevcontroller ));
memcpy( ent->latched.prevblending, ent->prevstate.blending, sizeof( ent->latched.prevblending ));
// update custom latched vars
if( clgame.drawFuncs.CL_UpdateLatchedVars != NULL )
clgame.drawFuncs.CL_UpdateLatchedVars( ent, false );
}
/*
====================
CL_GetStudioEstimatedFrame
====================
*/
static float CL_GetStudioEstimatedFrame( cl_entity_t *ent )
{
studiohdr_t *pstudiohdr;
mstudioseqdesc_t *pseqdesc;
int sequence;
if( ent->model != NULL && ent->model->type == mod_studio )
{
pstudiohdr = (studiohdr_t *)Mod_StudioExtradata( ent->model );
if( pstudiohdr )
{
sequence = bound( 0, ent->curstate.sequence, pstudiohdr->numseq - 1 );
pseqdesc = (mstudioseqdesc_t *)((byte *)pstudiohdr + pstudiohdr->seqindex) + sequence;
return ref.dllFuncs.R_StudioEstimateFrame( ent, pseqdesc, cl.time );
}
}
return 0;
}
/*
====================
CL_ResetLatchedVars
====================
*/
void CL_ResetLatchedVars( cl_entity_t *ent, qboolean full_reset )
{
if( !ent->model || ( ent->model->type != mod_alias && ent->model->type != mod_studio ))
return; // below fields used only for alias and studio interpolation
if( full_reset )
{
// don't modify for sprites to avoid broke sprite interp
memcpy( ent->latched.prevblending, ent->curstate.blending, sizeof( ent->latched.prevblending ));
ent->latched.sequencetime = ent->curstate.animtime;
memcpy( ent->latched.prevcontroller, ent->curstate.controller, sizeof( ent->latched.prevcontroller ));
if( ent->model->type == mod_studio )
ent->latched.prevframe = CL_GetStudioEstimatedFrame( ent );
else if( ent->model->type == mod_alias )
ent->latched.prevframe = ent->curstate.frame;
ent->prevstate = ent->curstate;
}
ent->latched.prevanimtime = ent->curstate.animtime = cl.mtime[0];
VectorCopy( ent->curstate.origin, ent->latched.prevorigin );
VectorCopy( ent->curstate.angles, ent->latched.prevangles );
ent->latched.prevsequence = ent->curstate.sequence;
// update custom latched vars
if( clgame.drawFuncs.CL_UpdateLatchedVars != NULL )
clgame.drawFuncs.CL_UpdateLatchedVars( ent, true );
}
/*
==================
CL_ProcessEntityUpdate
apply changes since new frame received
==================
*/
static void CL_ProcessEntityUpdate( cl_entity_t *ent )
{
qboolean parametric;
ent->model = CL_ModelHandle( ent->curstate.modelindex );
ent->index = ent->curstate.number;
if( FBitSet( ent->curstate.entityType, ENTITY_NORMAL ))
COM_NormalizeAngles( ent->curstate.angles );
parametric = ent->curstate.starttime != 0.0f && ent->curstate.impacttime != 0.0f;
// allow interpolation on bmodels too
if( ent->model && ent->model->type == mod_brush )
ent->curstate.animtime = ent->curstate.msg_time;
if( CL_EntityCustomLerp( ent ) && !parametric )
ent->curstate.animtime = ent->curstate.msg_time;
if( !CL_CompareTimestamps( ent->curstate.animtime, ent->prevstate.animtime ) || CL_EntityIgnoreLerp( ent ))
{
CL_UpdateLatchedVars( ent );
CL_UpdatePositions( ent );
}
// g-cont. it should be done for all the players?
if( ent->player && !FBitSet( host.features, ENGINE_COMPUTE_STUDIO_LERP ))
ent->curstate.angles[PITCH] /= -3.0f;
VectorCopy( ent->curstate.origin, ent->origin );
VectorCopy( ent->curstate.angles, ent->angles );
// initialize attachments for now
VectorCopy( ent->origin, ent->attachment[0] );
VectorCopy( ent->origin, ent->attachment[1] );
VectorCopy( ent->origin, ent->attachment[2] );
VectorCopy( ent->origin, ent->attachment[3] );
}
/*
==================
CL_FindInterpolationUpdates
find two timestamps
==================
*/
static qboolean CL_FindInterpolationUpdates( cl_entity_t *ent, double targettime, position_history_t **ph0, position_history_t **ph1 )
{
qboolean extrapolate = true;
uint i, i0, i1, imod;
imod = ent->current_position;
i0 = (imod - 0) & HISTORY_MASK; // curpos (lerp end)
i1 = (imod - 1) & HISTORY_MASK; // oldpos (lerp start)
for( i = 1; i < HISTORY_MAX - 1; i++ )
{
double at = ent->ph[( imod - i ) & HISTORY_MASK].animtime;
if( at == 0.0f )
break;
if( targettime > at )
{
// found it
i0 = (( imod - i ) + 1 ) & HISTORY_MASK;
i1 = (( imod - i ) + 0 ) & HISTORY_MASK;
extrapolate = false;
break;
}
}
if( ph0 != NULL ) *ph0 = &ent->ph[i0];
if( ph1 != NULL ) *ph1 = &ent->ph[i1];
return extrapolate;
}
/*
==================
CL_PureOrigin
non-local players interpolation
==================
*/
static void CL_PureOrigin( cl_entity_t *ent, double t, vec3_t outorigin, vec3_t outangles )
{
double t1, t0, frac;
position_history_t *ph0, *ph1;
vec3_t delta;
// NOTE: ph0 is next, ph1 is a prev
CL_FindInterpolationUpdates( ent, t, &ph0, &ph1 );
if ( !ph0 || !ph1 )
return;
t0 = ph0->animtime;
t1 = ph1->animtime;
if( t0 != 0.0 )
{
vec4_t q, q1, q2;
VectorSubtract( ph0->origin, ph1->origin, delta );
if( !Q_equal( t0, t1 ))
frac = ( t - t1 ) / ( t0 - t1 );
else frac = 1.0;
frac = bound( 0.0, frac, 1.2 );
VectorMA( ph1->origin, frac, delta, outorigin );
AngleQuaternion( ph0->angles, q1, false );
AngleQuaternion( ph1->angles, q2, false );
QuaternionSlerp( q2, q1, frac, q );
QuaternionAngle( q, outangles );
}
else
{
// no backup found
VectorCopy( ph1->origin, outorigin );
VectorCopy( ph1->angles, outangles );
}
}
/*
==================
CL_InterpolateModel
non-players interpolation
==================
*/
static int CL_InterpolateModel( cl_entity_t *e )
{
position_history_t *ph0 = NULL, *ph1 = NULL;
vec3_t origin, angles, delta;
double t, t1, t2, frac;
vec4_t q, q1, q2;
VectorCopy( e->curstate.origin, e->origin );
VectorCopy( e->curstate.angles, e->angles );
if( cls.timedemo || !e->model )
return 1;
if( cls.demoplayback == DEMO_QUAKE1 )
{
// quake lerping is easy
VectorLerp( e->prevstate.origin, cl.lerpFrac, e->curstate.origin, e->origin );
AngleQuaternion( e->prevstate.angles, q1, false );
AngleQuaternion( e->curstate.angles, q2, false );
QuaternionSlerp( q1, q2, cl.lerpFrac, q );
QuaternionAngle( q, e->angles );
return 1;
}
if( cl.maxclients <= 1 )
return 1;
if( e->model->type == mod_brush && !cl_bmodelinterp.value )
return 1;
if( cl.local.moving && cl.local.onground == e->index )
return 1;
t = cl.time - cl_interp.value;
CL_FindInterpolationUpdates( e, t, &ph0, &ph1 );
if( ph0 == NULL || ph1 == NULL )
return 0;
t1 = ph1->animtime;
t2 = ph0->animtime;
if( t - t1 < 0.0f )
return 0;
if( t1 == 0.0f )
{
VectorCopy( ph0->origin, e->origin );
VectorCopy( ph0->angles, e->angles );
return 0;
}
if( Q_equal( t2, t1 ))
{
VectorCopy( ph0->origin, e->origin );
VectorCopy( ph0->angles, e->angles );
return 1;
}
VectorSubtract( ph0->origin, ph1->origin, delta );
frac = (t - t1) / (t2 - t1);
if( frac < 0.0f )
return 0;
if( frac > 1.0f )
frac = 1.0f;
VectorMA( ph1->origin, frac, delta, origin );
AngleQuaternion( ph0->angles, q1, false );
AngleQuaternion( ph1->angles, q2, false );
QuaternionSlerp( q2, q1, frac, q );
QuaternionAngle( q, angles );
VectorCopy( origin, e->origin );
VectorCopy( angles, e->angles );
return 1;
}
/*
=============
CL_ComputePlayerOrigin
interpolate non-local clients
=============
*/
void CL_ComputePlayerOrigin( cl_entity_t *ent )
{
double targettime;
vec4_t q, q1, q2;
vec3_t origin;
vec3_t angles;
if( !ent->player )
return;
if( cl_nointerp.value > 0.f )
{
VectorCopy( ent->curstate.angles, ent->angles );
VectorCopy( ent->curstate.origin, ent->origin );
return;
}
if( cls.demoplayback == DEMO_QUAKE1 )
{
// quake lerping is easy
VectorLerp( ent->prevstate.origin, cl.lerpFrac, ent->curstate.origin, ent->origin );
AngleQuaternion( ent->prevstate.angles, q1, false );
AngleQuaternion( ent->curstate.angles, q2, false );
QuaternionSlerp( q1, q2, cl.lerpFrac, q );
QuaternionAngle( q, ent->angles );
return;
}
targettime = cl.time - cl_interp.value;
CL_PureOrigin( ent, targettime, origin, angles );
VectorCopy( angles, ent->angles );
VectorCopy( origin, ent->origin );
}
/*
=================
CL_ProcessPlayerState
process player states after the new packet has received
=================
*/
void CL_ProcessPlayerState( int playerindex, entity_state_t *state )
{
entity_state_t *ps;
ps = &cl.frames[cl.parsecountmod].playerstate[playerindex];
ps->number = state->number;
ps->messagenum = cl.parsecount;
ps->msg_time = cl.mtime[0];
clgame.dllFuncs.pfnProcessPlayerState( ps, state );
}
/*
=================
CL_ResetLatchedState
reset latched state if this frame entity was teleported
or just EF_NOINTERP was set
=================
*/
static void CL_ResetLatchedState( int pnum, frame_t *frame, cl_entity_t *ent )
{
if( CHECKVISBIT( frame->flags, pnum ))
{
VectorCopy( ent->curstate.origin, ent->latched.prevorigin );
VectorCopy( ent->curstate.angles, ent->latched.prevangles );
CL_ResetLatchedVars( ent, true );
CL_ResetPositions( ent );
// parametric interpolation will starts at this point
if( ent->curstate.starttime != 0.0f && ent->curstate.impacttime != 0.0f )
ent->lastmove = cl.time;
}
}
/*
=================
CL_ProcessPacket
process player states after the new packet has received
=================
*/
void CL_ProcessPacket( frame_t *frame )
{
entity_state_t *state;
cl_entity_t *ent;
int pnum;
for( pnum = 0; pnum < frame->num_entities; pnum++ )
{
// request the entity state from circular buffer
state = &cls.packet_entities[(frame->first_entity+pnum) % cls.num_client_entities];
state->messagenum = cl.parsecount;
state->msg_time = cl.mtime[0];
// mark all the players
ent = &clgame.entities[state->number];
ent->player = CL_IsPlayerIndex( state->number );
if( state->number == ( cl.playernum + 1 ))
clgame.dllFuncs.pfnTxferLocalOverrides( state, &frame->clientdata );
// shuffle states
ent->prevstate = ent->curstate;
ent->curstate = *state;
CL_ProcessEntityUpdate( ent );
CL_ResetLatchedState( pnum, frame, ent );
if( !ent->player ) continue;
CL_ProcessPlayerState(( state->number - 1 ), state );
if( state->number == ( cl.playernum + 1 ))
CL_CheckPredictionError();
}
}
/*
=========================================================================
FRAME PARSING
=========================================================================
*/
static qboolean CL_ParseEntityNumFromPacket( sizebuf_t *msg, int *newnum )
{
if( cls.legacymode )
{
*newnum = MSG_ReadWord( msg );
if( *newnum == 0 )
return false;
}
else
{
*newnum = MSG_ReadUBitLong( msg, MAX_ENTITY_BITS );
if( *newnum == LAST_EDICT )
return false;
}
return true;
}
/*
=================
CL_FlushEntityPacket
Read and ignore whole entity packet.
=================
*/
static void CL_FlushEntityPacket( sizebuf_t *msg )
{
int newnum;
entity_state_t from, to;
memset( &from, 0, sizeof( from ));
cl.frames[cl.parsecountmod].valid = false;
cl.validsequence = 0; // can't render a frame
// read it all, but ignore it
while( 1 )
{
if( !CL_ParseEntityNumFromPacket( msg, &newnum ))
break; // done
if( MSG_CheckOverflow( msg ))
Host_Error( "CL_FlushEntityPacket: overflow\n" );
MSG_ReadDeltaEntity( msg, &from, &to, newnum, CL_IsPlayerIndex( newnum ) ? DELTA_PLAYER : DELTA_ENTITY, cl.mtime[0] );
}
}
/*
=================
CL_DeltaEntity
processing delta update
=================
*/
static void CL_DeltaEntity( sizebuf_t *msg, frame_t *frame, int newnum, entity_state_t *old, qboolean has_update )
{
cl_entity_t *ent;
entity_state_t *state;
qboolean newent = (old) ? false : true;
int pack = frame->num_entities;
int delta_type = DELTA_ENTITY;
qboolean alive = true;
// alloc next slot to store update
state = &cls.packet_entities[cls.next_client_entities % cls.num_client_entities];
if( CL_IsPlayerIndex( newnum )) delta_type = DELTA_PLAYER;
if(( newnum < 0 ) || ( newnum >= clgame.maxEntities ))
{
Con_DPrintf( S_ERROR "CL_DeltaEntity: invalid newnum: %d\n", newnum );
if( has_update )
MSG_ReadDeltaEntity( msg, old, state, newnum, delta_type, cl.mtime[0] );
return;
}
ent = CL_EDICT_NUM( newnum );
ent->index = newnum; // enumerate entity index
if( newent ) old = &ent->baseline;
if( has_update )
alive = MSG_ReadDeltaEntity( msg, old, state, newnum, delta_type, cl.mtime[0] );
else memcpy( state, old, sizeof( entity_state_t ));
if( !alive )
{
CL_KillDeadBeams( ent ); // release dead beams
#if 0
// this is for reference
if( state->number == -1 )
Con_DPrintf( "Entity %i was removed from server\n", newnum );
else Con_Dprintf( "Entity %i was removed from delta-message\n", newnum );
#endif
return;
}
if( newent )
{
// interpolation must be reset
SETVISBIT( frame->flags, pack );
// release beams from previous entity
CL_KillDeadBeams( ent );
}
// add entity to packet
cls.next_client_entities++;
frame->num_entities++;
}
/*
==================
CL_ParsePacketEntities
An svc_packetentities has just been parsed, deal with the
rest of the data stream.
==================
*/
int CL_ParsePacketEntities( sizebuf_t *msg, qboolean delta )
{
frame_t *newframe, *oldframe;
int oldindex, newnum, oldnum;
int playerbytes = 0;
int oldpacket;
int bufStart;
entity_state_t *oldent;
qboolean player;
int count;
// save first uncompressed packet as timestamp
if( cls.changelevel && !delta && cls.demorecording )
CL_WriteDemoJumpTime();
// sentinel count. save it for debug checking
if( cls.legacymode )
count = MSG_ReadWord( msg );
else count = MSG_ReadUBitLong( msg, MAX_VISIBLE_PACKET_BITS ) + 1;
newframe = &cl.frames[cl.parsecountmod];
// allocate parse entities
memset( newframe->flags, 0, sizeof( newframe->flags ));
newframe->first_entity = cls.next_client_entities;
newframe->num_entities = 0;
newframe->valid = true; // assume valid
if( delta )
{
int subtracted;
oldpacket = MSG_ReadByte( msg );
subtracted = ( cls.netchan.incoming_sequence - oldpacket ) & 0xFF;
if( subtracted == 0 )
{
Con_NPrintf( 2, "^3Warning:^1 update too old\n^7\n" );
CL_FlushEntityPacket( msg );
return playerbytes;
}
if( subtracted >= CL_UPDATE_MASK )
{
// we can't use this, it is too old
Con_NPrintf( 2, "^3Warning:^1 delta frame is too old^7\n" );
CL_FlushEntityPacket( msg );
return playerbytes;
}
oldframe = &cl.frames[oldpacket & CL_UPDATE_MASK];
if(( cls.next_client_entities - oldframe->first_entity ) > ( cls.num_client_entities - NUM_PACKET_ENTITIES ))
{
Con_NPrintf( 2, "^3Warning:^1 delta frame is too old^7\n" );
CL_FlushEntityPacket( msg );
return playerbytes;
}
}
else
{
// this is a full update that we can start delta compressing from now
oldframe = NULL;
oldpacket = -1; // delta too old or is initial message
cl.send_reply = true; // send reply
cls.demowaiting = false; // we can start recording now
}
// mark current delta state
cl.validsequence = cls.netchan.incoming_sequence;
oldent = NULL;
oldindex = 0;
if( !oldframe )
{
oldnum = MAX_ENTNUMBER;
}
else
{
if( oldindex >= oldframe->num_entities )
{
oldnum = MAX_ENTNUMBER;
}
else
{
oldent = &cls.packet_entities[(oldframe->first_entity+oldindex) % cls.num_client_entities];
oldnum = oldent->number;
}
}
while( 1 )
{
if( !CL_ParseEntityNumFromPacket( msg, &newnum ))
break; // done
if( MSG_CheckOverflow( msg ))
Host_Error( "CL_ParsePacketEntities: overflow\n" );
player = CL_IsPlayerIndex( newnum );
while( oldnum < newnum )
{
// one or more entities from the old packet are unchanged
CL_DeltaEntity( msg, newframe, oldnum, oldent, false );
oldindex++;
if( oldindex >= oldframe->num_entities )
{
oldnum = MAX_ENTNUMBER;
}
else
{
oldent = &cls.packet_entities[(oldframe->first_entity+oldindex) % cls.num_client_entities];
oldnum = oldent->number;
}
}
if( oldnum == newnum )
{
// delta from previous state
bufStart = MSG_GetNumBytesRead( msg );
CL_DeltaEntity( msg, newframe, newnum, oldent, true );
if( player ) playerbytes += MSG_GetNumBytesRead( msg ) - bufStart;
oldindex++;
if( oldindex >= oldframe->num_entities )
{
oldnum = MAX_ENTNUMBER;
}
else
{
oldent = &cls.packet_entities[(oldframe->first_entity+oldindex) % cls.num_client_entities];
oldnum = oldent->number;
}
continue;
}
if( oldnum > newnum )
{
// delta from baseline ?
bufStart = MSG_GetNumBytesRead( msg );
CL_DeltaEntity( msg, newframe, newnum, NULL, true );
if( player ) playerbytes += MSG_GetNumBytesRead( msg ) - bufStart;
continue;
}
}
// any remaining entities in the old frame are copied over
while( oldnum != MAX_ENTNUMBER )
{
// one or more entities from the old packet are unchanged
CL_DeltaEntity( msg, newframe, oldnum, oldent, false );
oldindex++;
if( oldindex >= oldframe->num_entities )
{
oldnum = MAX_ENTNUMBER;
}
else
{
oldent = &cls.packet_entities[(oldframe->first_entity+oldindex) % cls.num_client_entities];
oldnum = oldent->number;
}
}
if( newframe->num_entities != count && newframe->num_entities != 0 )
Con_Reportf( S_WARN "CL_Parse%sPacketEntities: (%i should be %i)\n", delta ? "Delta" : "", newframe->num_entities, count );
if( !newframe->valid )
return playerbytes; // frame is not valid but message was parsed
// now process packet.
CL_ProcessPacket( newframe );
// add new entities into physic lists
CL_SetSolidEntities();
// first update is the final signon stage where we actually receive an entity (i.e., the world at least)
if( cls.signon == ( SIGNONS - 1 ))
{
// we are done with signon sequence.
cls.signon = SIGNONS;
// Clear loading plaque.
CL_SignonReply ();
}
return playerbytes;
}
/*
==========================================================================
INTERPOLATE BETWEEN FRAMES TO GET RENDERING PARMS
==========================================================================
*/
/*
=============
CL_AddVisibleEntity
all the visible entities should pass this filter
=============
*/
qboolean CL_AddVisibleEntity( cl_entity_t *ent, int entityType )
{
qboolean draw_player = true;
if( !ent || !ent->model )
return false;
// don't add the player in firstperson mode
if( RP_LOCALCLIENT( ent ))
{
cl.local.apply_effects = true;
if( !CL_IsThirdPerson( ) && ( ent->index == cl.viewentity ))
{
// we don't draw player in default renderer in firstperson mode
// but let the client.dll know about player entity anyway
// for use in custom renderers
draw_player = false;
}
}
// check for adding this entity
if( !clgame.dllFuncs.pfnAddEntity( entityType, ent, ent->model->name ))
{
// local player was reject by game code, so ignore any effects
if( RP_LOCALCLIENT( ent ))
cl.local.apply_effects = false;
return false;
}
if( !draw_player )
return false;
if( entityType == ET_BEAM )
{
ref.dllFuncs.CL_AddCustomBeam( ent );
return true;
}
else if( !ref.dllFuncs.R_AddEntity( ent, entityType ))
{
return false;
}
// because pTemp->entity.curstate.effects
// is already occupied by FTENT_FLICKER
if( entityType != ET_TEMPENTITY && !RP_LOCALCLIENT( ent ) )
{
// apply client-side effects
CL_AddEntityEffects( ent );
// alias & studiomodel efefcts
CL_AddModelEffects( ent );
}
return true;
}
/*
=============
CL_LinkCustomEntity
Add server beam to draw list
=============
*/
static void CL_LinkCustomEntity( cl_entity_t *ent, entity_state_t *state )
{
ent->curstate.movetype = state->modelindex; // !!!
if( ent->model->type != mod_sprite )
Con_Reportf( S_WARN "bad model on beam ( %s )\n", ent->model->name );
ent->latched.prevsequence = ent->curstate.sequence;
VectorCopy( ent->origin, ent->latched.prevorigin );
VectorCopy( ent->angles, ent->latched.prevangles );
ent->prevstate = ent->curstate;
CL_AddVisibleEntity( ent, ET_BEAM );
}
/*
=============
CL_LinkPlayers
Create visible entities in the correct position
for all current players
=============
*/
static void CL_LinkPlayers( frame_t *frame )
{
entity_state_t *state;
cl_entity_t *ent;
int i;
ent = CL_GetLocalPlayer();
// apply muzzleflash to weaponmodel
if( ent && FBitSet( ent->curstate.effects, EF_MUZZLEFLASH ))
SetBits( clgame.viewent.curstate.effects, EF_MUZZLEFLASH );
// check all the clients but add only visible
for( i = 0, state = frame->playerstate; i < MAX_CLIENTS; i++, state++ )
{
if( state->messagenum != cl.parsecount )
continue; // not present this frame
if( !state->modelindex || FBitSet( state->effects, EF_NODRAW ))
continue;
ent = &clgame.entities[i + 1];
// fixup the player indexes...
if( ent->index != ( i + 1 )) ent->index = (i + 1);
if( i == cl.playernum )
{
if( cls.demoplayback != DEMO_QUAKE1 )
{
VectorCopy( state->origin, ent->origin );
VectorCopy( state->origin, ent->prevstate.origin );
VectorCopy( state->origin, ent->curstate.origin );
}
VectorCopy( ent->curstate.angles, ent->angles );
}
if( FBitSet( ent->curstate.effects, EF_NOINTERP ))
CL_ResetLatchedVars( ent, false );
if( CL_EntityTeleported( ent ))
{
VectorCopy( ent->curstate.origin, ent->latched.prevorigin );
VectorCopy( ent->curstate.angles, ent->latched.prevangles );
CL_ResetPositions( ent );
}
if ( i == cl.playernum )
{
// using interpolation only for local player angles
CL_ComputePlayerOrigin( ent );
if( cls.demoplayback == DEMO_QUAKE1 )
VectorLerp( ent->prevstate.origin, cl.lerpFrac, ent->curstate.origin, cl.simorg );
VectorCopy( cl.simorg, ent->origin );
}
else
{
VectorCopy( ent->curstate.origin, ent->origin );
VectorCopy( ent->curstate.angles, ent->angles );
// interpolate non-local clients
CL_ComputePlayerOrigin( ent );
}
VectorCopy( ent->origin, ent->attachment[0] );
VectorCopy( ent->origin, ent->attachment[1] );
VectorCopy( ent->origin, ent->attachment[2] );
VectorCopy( ent->origin, ent->attachment[3] );
CL_AddVisibleEntity( ent, ET_PLAYER );
}
// apply local player effects if entity is not added
if( cl.local.apply_effects ) CL_AddEntityEffects( CL_GetLocalPlayer( ));
}
/*
===============
CL_LinkPacketEntities
===============
*/
static void CL_LinkPacketEntities( frame_t *frame )
{
cl_entity_t *ent;
entity_state_t *state;
qboolean parametric;
qboolean interpolate;
int i;
for( i = 0; i < frame->num_entities; i++ )
{
state = &cls.packet_entities[(frame->first_entity + i) % cls.num_client_entities];
// clients are should be done in CL_LinkPlayers
if( state->number >= 1 && state->number <= cl.maxclients )
continue;
// if set to invisible, skip
if( !state->modelindex || FBitSet( state->effects, EF_NODRAW ))
continue;
ent = CL_GetEntityByIndex( state->number );
if( !ent )
{
Con_Reportf( S_ERROR "CL_LinkPacketEntity: bad entity %i\n", state->number );
continue;
}
// animtime must keep an actual
ent->curstate.animtime = state->animtime;
ent->curstate.frame = state->frame;
interpolate = false;
if( !ent->model ) continue;
if( ent->curstate.rendermode == kRenderNormal )
{
// auto 'solid' faces
if( FBitSet( ent->model->flags, MODEL_TRANSPARENT ) && Host_IsQuakeCompatible( ))
{
ent->curstate.rendermode = kRenderTransAlpha;
ent->curstate.renderamt = 255;
}
}
parametric = ( ent->curstate.impacttime != 0.0f && ent->curstate.starttime != 0.0f );
if( !parametric && ent->curstate.movetype != MOVETYPE_COMPOUND )
{
if( ent->curstate.animtime == ent->prevstate.animtime && !VectorCompare( ent->curstate.origin, ent->prevstate.origin ))
ent->lastmove = cl.time + 0.2;
if( FBitSet( ent->curstate.eflags, EFLAG_SLERP ))
{
if( ent->curstate.animtime != 0.0f && ( ent->model->type == mod_alias || ent->model->type == mod_studio ))
{
#ifdef STUDIO_INTERPOLATION_FIX
if( ent->lastmove >= cl.time )
VectorCopy( ent->curstate.origin, ent->latched.prevorigin );
if( FBitSet( host.features, ENGINE_COMPUTE_STUDIO_LERP ))
interpolate = true;
else ent->curstate.movetype = MOVETYPE_STEP;
#else
if( ent->lastmove >= cl.time )
{
float at = ent->curstate.animtime;
CL_ResetLatchedVars( ent, true );
if( cl_fixmodelinterpolationartifacts.value )
ent->latched.prevanimtime = ent->curstate.animtime = at;
VectorCopy( ent->curstate.origin, ent->latched.prevorigin );
VectorCopy( ent->curstate.angles, ent->latched.prevangles );
if( !FBitSet( host.features, ENGINE_COMPUTE_STUDIO_LERP ))
{
// disable step interpolation in client.dll
ent->curstate.movetype = MOVETYPE_NONE;
}
}
else
{
if( FBitSet( host.features, ENGINE_COMPUTE_STUDIO_LERP ))
{
interpolate = true;
}
else
{
// restore step interpolation in client.dll
ent->curstate.movetype = MOVETYPE_STEP;
}
}
#endif
}
}
}
if( ent->model->type == mod_brush )
{
CL_InterpolateModel( ent );
}
else
{
if( parametric )
{
CL_ParametricMove( ent );
VectorCopy( ent->curstate.origin, ent->origin );
VectorCopy( ent->curstate.angles, ent->angles );
}
else if( CL_EntityCustomLerp( ent ))
{
if ( !CL_InterpolateModel( ent ))
continue;
}
// a1ba: in GoldSrc this is done for cstrike and czero
// but let modders use this as an engine feature
else if( FBitSet( host.features, ENGINE_STEP_POSHISTORY_LERP ) &&
ent->curstate.movetype == MOVETYPE_STEP && !NET_IsLocalAddress( cls.netchan.remote_address ))
{
if( !CL_InterpolateModel( ent ))
continue;
}
#if 0
// ABSOLUTELY STUPID HACK TO ALLOW MONSTERS
// INTERPOLATION IN GRAVGUNMOD COOP
// MUST BE REMOVED ONCE WE REMOVE 48 PROTO SUPPORT
else if( cls.legacymode && ent->model->type == mod_studio && ent->curstate.movetype == MOVETYPE_TOSS )
{
if( !CL_InterpolateModel( ent ))
continue;
}
#endif
else
{
// no interpolation right now
VectorCopy( ent->curstate.origin, ent->origin );
VectorCopy( ent->curstate.angles, ent->angles );
}
if( ent->model->type == mod_studio )
{
if( interpolate && FBitSet( host.features, ENGINE_COMPUTE_STUDIO_LERP ))
ref.dllFuncs.R_StudioLerpMovement( ent, cl.time, ent->origin, ent->angles );
}
}
if( !FBitSet( state->entityType, ENTITY_NORMAL ))
{
CL_LinkCustomEntity( ent, state );
continue;
}
if( ent->model->type != mod_brush )
{
// NOTE: never pass sprites with rendercolor '0 0 0' it's a stupid Valve Hammer Editor bug
if( !ent->curstate.rendercolor.r && !ent->curstate.rendercolor.g && !ent->curstate.rendercolor.b )
ent->curstate.rendercolor.r = ent->curstate.rendercolor.g = ent->curstate.rendercolor.b = 255;
}
// XASH SPECIFIC
if( ent->curstate.rendermode == kRenderNormal && ent->curstate.renderfx == kRenderFxNone )
ent->curstate.renderamt = 255.0f;
if( ent->curstate.aiment != 0 && ent->curstate.movetype != MOVETYPE_COMPOUND )
ent->curstate.movetype = MOVETYPE_FOLLOW;
if( FBitSet( ent->curstate.effects, EF_NOINTERP ))
CL_ResetLatchedVars( ent, false );
if( CL_EntityTeleported( ent ))
{
VectorCopy( ent->curstate.origin, ent->latched.prevorigin );
VectorCopy( ent->curstate.angles, ent->latched.prevangles );
CL_ResetPositions( ent );
}
VectorCopy( ent->origin, ent->attachment[0] );
VectorCopy( ent->origin, ent->attachment[1] );
VectorCopy( ent->origin, ent->attachment[2] );
VectorCopy( ent->origin, ent->attachment[3] );
CL_AddVisibleEntity( ent, ET_NORMAL );
}
}
/*
===============
CL_MoveThirdpersonCamera
think thirdperson
===============
*/
void CL_MoveThirdpersonCamera( void )
{
if( cls.state == ca_disconnected || cls.state == ca_cinematic )
return;
// think thirdperson camera
clgame.dllFuncs.CAM_Think ();
}
/*
===============
CL_EmitEntities
add visible entities to refresh list
process frame interpolation etc
===============
*/
void CL_EmitEntities( void )
{
if( cl.paused ) return; // don't waste time
// not in server yet, no entities to redraw
if( cls.state != ca_active || !cl.validsequence )
return;
// make sure we have at least one valid update
if( !cl.frames[cl.parsecountmod].valid )
return;
// animate lightestyles
ref.dllFuncs.CL_RunLightStyles ();
// decay dynamic lights
CL_DecayLights ();
// compute last interpolation amount
CL_UpdateFrameLerp ();
// set client ideal pitch when mlook is disabled
CL_SetIdealPitch ();
ref.dllFuncs.R_ClearScene ();
// link all the visible clients first
CL_LinkPlayers ( &cl.frames[cl.parsecountmod] );
// link all the entities that actually have update
CL_LinkPacketEntities ( &cl.frames[cl.parsecountmod] );
// link custom user temp entities
clgame.dllFuncs.pfnCreateEntities();
// evaluate temp entities
CL_TempEntUpdate ();
// fire events (client and server)
CL_FireEvents ();
// handle spectator camera movement
CL_MoveSpectatorCamera();
// perfomance test
CL_TestLights();
}
/*
==========================================================================
SOUND ENGINE IMPLEMENTATION
==========================================================================
*/
qboolean CL_GetEntitySpatialization( channel_t *ch )
{
cl_entity_t *ent;
qboolean valid_origin;
if( ch->entnum == 0 )
{
ch->staticsound = true;
return true; // static sound
}
if(( ch->entnum - 1 ) == cl.playernum )
{
VectorCopy( refState.vieworg, ch->origin );
return true;
}
valid_origin = VectorIsNull( ch->origin ) ? false : true;
ent = CL_GetEntityByIndex( ch->entnum );
// entity is not present on the client but has valid origin
if( !ent || !ent->model || ent->curstate.messagenum != cl.parsecount )
return valid_origin;
// setup origin
if( ent->model->type == mod_brush )
{
VectorAverage( ent->model->mins, ent->model->maxs, ch->origin );
VectorAdd( ent->origin, ch->origin, ch->origin );
}
else
{
VectorCopy( ent->origin, ch->origin );
}
return true;
}
qboolean CL_GetMovieSpatialization( rawchan_t *ch )
{
cl_entity_t *ent;
qboolean valid_origin;
valid_origin = VectorIsNull( ch->origin ) ? false : true;
ent = CL_GetEntityByIndex( ch->entnum );
// entity is not present on the client but has valid origin
if( !ent || !ent->index || ent->curstate.messagenum == 0 )
return valid_origin;
// setup origin
if( ent->model->type == mod_brush )
{
VectorAverage( ent->model->mins, ent->model->maxs, ch->origin );
VectorAdd( ent->origin, ch->origin, ch->origin );
}
else
{
VectorCopy( ent->origin, ch->origin );
}
return true;
}
void CL_ExtraUpdate( void )
{
clgame.dllFuncs.IN_Accumulate();
S_ExtraUpdate();
}