/*
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
==================
*/
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
==================
*/
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 )
{
	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
==================
*/
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( 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

==================
*/
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

====================
*/
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
==================
*/
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
=================
*/
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.
=================
*/
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
=================
*/
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
=============
*/
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
=============
*/
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

===============
*/
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();
}
