mirror of
https://github.com/FWGS/xash3d-fwgs
synced 2024-11-10 12:59:17 +01:00
1326 lines
32 KiB
C
1326 lines
32 KiB
C
/*
|
|
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 "gl_local.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
|
|
==================
|
|
*/
|
|
void CL_UpdatePositions( cl_entity_t *ent )
|
|
{
|
|
position_history_t *ph;
|
|
|
|
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; // !!!
|
|
}
|
|
|
|
/*
|
|
==================
|
|
CL_ResetPositions
|
|
|
|
Interpolation init or reset after teleporting
|
|
==================
|
|
*/
|
|
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
|
|
==================
|
|
*/
|
|
qboolean CL_EntityTeleported( cl_entity_t *ent )
|
|
{
|
|
int 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.0 / GAME_FPS ));
|
|
len = VectorLength( delta );
|
|
|
|
return (len > maxlen);
|
|
}
|
|
|
|
/*
|
|
==================
|
|
CL_CompareTimestamps
|
|
|
|
round-off floating errors
|
|
==================
|
|
*/
|
|
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
|
|
==================
|
|
*/
|
|
qboolean CL_EntityIgnoreLerp( cl_entity_t *e )
|
|
{
|
|
if( e->model && e->model->type == mod_alias )
|
|
return false;
|
|
|
|
return (e->curstate.movetype == MOVETYPE_NONE) ? true : false;
|
|
}
|
|
|
|
/*
|
|
==================
|
|
CL_EntityCustomLerp
|
|
|
|
==================
|
|
*/
|
|
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;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
==================
|
|
CL_ParametricMove
|
|
|
|
check for parametrical moved entities
|
|
==================
|
|
*/
|
|
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
|
|
|
|
====================
|
|
*/
|
|
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->prevstate.blending, ent->latched.prevseqblending, sizeof( ent->prevstate.blending ));
|
|
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 ));
|
|
}
|
|
|
|
/*
|
|
====================
|
|
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;
|
|
}
|
|
|
|
/*
|
|
==================
|
|
CL_ProcessEntityUpdate
|
|
|
|
apply changes since new frame received
|
|
==================
|
|
*/
|
|
void CL_ProcessEntityUpdate( cl_entity_t *ent )
|
|
{
|
|
qboolean parametric;
|
|
|
|
ent->model = CL_ModelHandle( ent->curstate.modelindex );
|
|
ent->index = ent->curstate.number;
|
|
|
|
// g-cont. make sure what it's no broke XashXT physics
|
|
COM_NormalizeAngles( ent->curstate.angles );
|
|
|
|
parametric = CL_ParametricMove( ent );
|
|
|
|
// 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
|
|
==================
|
|
*/
|
|
qboolean CL_FindInterpolationUpdates( cl_entity_t *ent, float targettime, position_history_t **ph0, position_history_t **ph1 )
|
|
{
|
|
qboolean extrapolate = true;
|
|
int i, i0, i1, imod;
|
|
float at;
|
|
|
|
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++ )
|
|
{
|
|
at = ent->ph[( imod - i ) & HISTORY_MASK].animtime;
|
|
if( at == 0.0 ) 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
|
|
==================
|
|
*/
|
|
void CL_PureOrigin( cl_entity_t *ent, float t, vec3_t outorigin, vec3_t outangles )
|
|
{
|
|
qboolean extrapolate;
|
|
float t1, t0, frac;
|
|
position_history_t *ph0, *ph1;
|
|
vec3_t delta;
|
|
|
|
// NOTE: ph0 is next, ph1 is a prev
|
|
extrapolate = CL_FindInterpolationUpdates( ent, t, &ph0, &ph1 );
|
|
|
|
if ( !ph0 || !ph1 )
|
|
return;
|
|
|
|
t0 = ph0->animtime;
|
|
t1 = ph1->animtime;
|
|
|
|
if( t0 != 0.0f )
|
|
{
|
|
vec4_t q, q1, q2;
|
|
|
|
VectorSubtract( ph0->origin, ph1->origin, delta );
|
|
|
|
if( t0 != t1 )
|
|
frac = ( t - t1 ) / ( t0 - t1 );
|
|
else frac = 1.0f;
|
|
|
|
frac = bound( 0.0f, frac, 1.2f );
|
|
|
|
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
|
|
==================
|
|
*/
|
|
int CL_InterpolateModel( cl_entity_t *e )
|
|
{
|
|
position_history_t *ph0 = NULL, *ph1 = NULL;
|
|
vec3_t origin, angles, delta;
|
|
float 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( cl.maxclients <= 1 && !FBitSet( host.features, ENGINE_FIXED_FRAMERATE ))
|
|
return 1;
|
|
|
|
if( e->model->type == mod_brush && !cl_bmodelinterp->value )
|
|
return 1;
|
|
|
|
if( cl.local.moving && cl.local.onground == e->index )
|
|
return 1;
|
|
|
|
if( cl.maxclients <= 1 && FBitSet( host.features, ENGINE_FIXED_FRAMERATE ))
|
|
t = cl.time - cl_serverframetime();
|
|
else 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( 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 )
|
|
{
|
|
float targettime;
|
|
vec3_t origin;
|
|
vec3_t angles;
|
|
|
|
if( !ent->player || ent->index == ( cl.playernum + 1 ))
|
|
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
|
|
=================
|
|
*/
|
|
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
|
|
|
|
=========================================================================
|
|
*/
|
|
/*
|
|
=================
|
|
CL_FlushEntityPacket
|
|
|
|
Read and ignore whole entity packet.
|
|
=================
|
|
*/
|
|
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 )
|
|
{
|
|
newnum = MSG_ReadUBitLong( msg, MAX_VISIBLE_PACKET_BITS );
|
|
if( newnum == LAST_EDICT ) break; // done
|
|
|
|
if( MSG_CheckOverflow( msg ))
|
|
Host_Error( "CL_FlushEntityPacket: overflow\n" );
|
|
|
|
MSG_ReadDeltaEntity( msg, &from, &to, newnum, CL_IsPlayerIndex( newnum ), cl.mtime[0] );
|
|
}
|
|
}
|
|
|
|
/*
|
|
=================
|
|
CL_DeltaEntity
|
|
|
|
processing delta update
|
|
=================
|
|
*/
|
|
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;
|
|
qboolean player = CL_IsPlayerIndex( newnum );
|
|
qboolean alive = true;
|
|
|
|
// alloc next slot to store update
|
|
state = &cls.packet_entities[cls.next_client_entities % cls.num_client_entities];
|
|
|
|
if(( newnum < 0 ) || ( newnum >= clgame.maxEntities ))
|
|
{
|
|
MsgDev( D_ERROR, "CL_DeltaEntity: invalid newnum: %d\n", newnum );
|
|
if( has_update )
|
|
MSG_ReadDeltaEntity( msg, old, state, newnum, player, 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, player, 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
|
|
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 ))
|
|
{
|
|
MsgDev( D_NOTE, "CL_ParsePacketEntities: delta frame is too old (flush)\n");
|
|
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 )
|
|
{
|
|
newnum = MSG_ReadUBitLong( msg, MAX_ENTITY_BITS );
|
|
if( newnum == LAST_EDICT ) break; // end of packet entities
|
|
|
|
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 )
|
|
MsgDev( D_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 )
|
|
{
|
|
if( !ent || !ent->model )
|
|
return 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;
|
|
}
|
|
|
|
// don't add the player in firstperson mode
|
|
if( RP_LOCALCLIENT( ent ) && !CL_IsThirdPerson( ) && ( ent->index == cl.viewentity ))
|
|
return false;
|
|
|
|
if( entityType == ET_BEAM )
|
|
{
|
|
CL_AddCustomBeam( ent );
|
|
return true;
|
|
}
|
|
else if( !R_AddEntity( ent, entityType ))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// because pTemp->entity.curstate.effects
|
|
// is already occupied by FTENT_FLICKER
|
|
if( entityType != ET_TEMPENTITY )
|
|
{
|
|
// no reason to do it twice
|
|
if( RP_LOCALCLIENT( ent ))
|
|
cl.local.apply_effects = false;
|
|
|
|
// apply client-side effects
|
|
CL_AddEntityEffects( ent );
|
|
|
|
// alias & studiomodel efefcts
|
|
CL_AddModelEffects( ent );
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
=============
|
|
CL_LinkCustomEntity
|
|
|
|
Add server beam to draw list
|
|
=============
|
|
*/
|
|
void CL_LinkCustomEntity( cl_entity_t *ent, entity_state_t *state )
|
|
{
|
|
ent->curstate.movetype = state->modelindex; // !!!
|
|
|
|
if( ent->model->type != mod_sprite )
|
|
MsgDev( D_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
|
|
=============
|
|
*/
|
|
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 );
|
|
cl.local.apply_effects = true;
|
|
|
|
// 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 )
|
|
{
|
|
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 )
|
|
{
|
|
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
|
|
|
|
===============
|
|
*/
|
|
void CL_LinkPacketEntities( frame_t *frame )
|
|
{
|
|
cl_entity_t *ent;
|
|
entity_state_t *state;
|
|
qboolean parametric;
|
|
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 )
|
|
{
|
|
MsgDev( D_ERROR, "CL_LinkPacketEntity: bad entity %i\n", state->number );
|
|
continue;
|
|
}
|
|
|
|
ent->curstate = *state;
|
|
|
|
// XASH SPECIFIC
|
|
if( ent->curstate.rendermode == kRenderNormal && ent->curstate.renderfx == kRenderFxNone )
|
|
ent->curstate.renderamt = 255.0f;
|
|
|
|
if( !ent->model ) continue;
|
|
|
|
if( ent->curstate.rendermode == kRenderNormal )
|
|
{
|
|
// auto 'solid' faces
|
|
if( FBitSet( ent->model->flags, MODEL_TRANSPARENT ) && FBitSet( host.features, ENGINE_QUAKE_COMPATIBLE ))
|
|
{
|
|
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 );
|
|
ent->curstate.movetype = MOVETYPE_STEP;
|
|
#else
|
|
if( ent->lastmove >= cl.time )
|
|
{
|
|
CL_ResetLatchedVars( ent, true );
|
|
VectorCopy( ent->curstate.origin, ent->latched.prevorigin );
|
|
VectorCopy( ent->curstate.angles, ent->latched.prevangles );
|
|
|
|
// disable step interpolation in client.dll
|
|
ent->curstate.movetype = MOVETYPE_NONE;
|
|
}
|
|
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;
|
|
}
|
|
else if( ent->curstate.movetype == MOVETYPE_STEP && !NET_IsLocalAddress( cls.netchan.remote_address ))
|
|
{
|
|
if( !CL_InterpolateModel( ent ))
|
|
continue;
|
|
}
|
|
else
|
|
{
|
|
// no interpolation right now
|
|
VectorCopy( ent->curstate.origin, ent->origin );
|
|
VectorCopy( ent->curstate.angles, ent->angles );
|
|
}
|
|
|
|
if( ent->model->type == mod_studio )
|
|
{
|
|
if( ent->curstate.movetype == MOVETYPE_STEP && FBitSet( host.features, ENGINE_COMPUTE_STUDIO_LERP ))
|
|
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;
|
|
}
|
|
|
|
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
|
|
|
|
R_ClearScene ();
|
|
|
|
// 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;
|
|
|
|
// compute last interpolation amount
|
|
CL_UpdateFrameLerp ();
|
|
|
|
// set client ideal pitch when mlook is disabled
|
|
CL_SetIdealPitch ();
|
|
|
|
// clear the scene befor start new frame
|
|
if( clgame.drawFuncs.R_ClearScene != NULL )
|
|
clgame.drawFuncs.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( RI.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->index || ent->curstate.messagenum == 0 )
|
|
return valid_origin;
|
|
|
|
#if 0
|
|
// uncomment this if you want enable additional check by PVS
|
|
if( ent->curstate.messagenum != cl.parsecount )
|
|
return valid_origin;
|
|
#endif
|
|
ch->movetype = ent->curstate.movetype;
|
|
|
|
// setup origin
|
|
VectorAverage( ent->curstate.mins, ent->curstate.maxs, ch->origin );
|
|
VectorAdd( ch->origin, ent->curstate.origin, ch->origin );
|
|
|
|
// setup mins\maxs
|
|
VectorAdd( ent->curstate.mins, ent->curstate.origin, ch->absmin );
|
|
VectorAdd( ent->curstate.maxs, ent->curstate.origin, ch->absmax );
|
|
|
|
// setup radius
|
|
if( ent->model != NULL && ent->model->radius ) ch->radius = ent->model->radius;
|
|
else ch->radius = RadiusFromBounds( ent->curstate.mins, ent->curstate.maxs );
|
|
|
|
return true;
|
|
}
|
|
|
|
qboolean CL_GetMovieSpatialization( rawchan_t *ch )
|
|
{
|
|
// UNDONE
|
|
return false;
|
|
}
|
|
|
|
void CL_ExtraUpdate( void )
|
|
{
|
|
clgame.dllFuncs.IN_Accumulate();
|
|
S_ExtraUpdate();
|
|
} |