720 lines
18 KiB
C
720 lines
18 KiB
C
//=======================================================================
|
|
// Copyright XashXT Group 2008 ©
|
|
// cl_frame.c - client world snapshot
|
|
//=======================================================================
|
|
|
|
#include "common.h"
|
|
#include "client.h"
|
|
#include "net_encode.h"
|
|
#include "entity_types.h"
|
|
#include "cl_tent.h"
|
|
#include "dlight.h"
|
|
#include "input.h"
|
|
|
|
qboolean CL_IsPlayerIndex( int idx )
|
|
{
|
|
if( idx > 0 && idx <= cl.maxclients )
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
=========================================================================
|
|
|
|
FRAME PARSING
|
|
|
|
=========================================================================
|
|
*/
|
|
void CL_UpdateEntityFields( cl_entity_t *ent )
|
|
{
|
|
// FIXME: this very-very temporary stuffffffff
|
|
// make me lerping
|
|
VectorCopy( ent->curstate.origin, ent->origin );
|
|
VectorCopy( ent->curstate.angles, ent->angles );
|
|
|
|
ent->model = CM_ClipHandleToModel( ent->curstate.modelindex );
|
|
ent->curstate.msg_time = cl.time;
|
|
|
|
// apply scale to studiomodels and sprites only
|
|
if( ent->model && ent->model->type != mod_brush && !ent->curstate.scale )
|
|
ent->curstate.scale = 1.0f;
|
|
}
|
|
|
|
qboolean CL_AddVisibleEntity( cl_entity_t *ent, int entityType )
|
|
{
|
|
model_t *mod;
|
|
|
|
mod = CM_ClipHandleToModel( ent->curstate.modelindex );
|
|
if( !mod ) return false;
|
|
|
|
// if entity is beam add it here
|
|
// because render doesn't know how to draw beams
|
|
if( entityType == ET_BEAM )
|
|
{
|
|
CL_AddCustomBeam( ent );
|
|
return true;
|
|
}
|
|
|
|
if( entityType == ET_TEMPENTITY )
|
|
{
|
|
// copy actual origin and angles back to let StudioModelRenderer
|
|
// get actual value directly from curstate
|
|
VectorCopy( ent->origin, ent->curstate.origin );
|
|
VectorCopy( ent->angles, ent->curstate.angles );
|
|
}
|
|
|
|
// don't add himself on firstperson
|
|
if(( ent->index - 1 ) == cl.playernum && ent != &clgame.viewent && !cl.thirdperson )
|
|
{
|
|
}
|
|
else
|
|
{
|
|
// check for adding this entity
|
|
if( !clgame.dllFuncs.pfnAddEntity( entityType, ent, mod->name ))
|
|
return false;
|
|
|
|
if( !R_AddEntity( ent, entityType ))
|
|
return false;
|
|
}
|
|
|
|
// apply effects
|
|
if( ent->curstate.effects & EF_BRIGHTFIELD )
|
|
CL_EntityParticles( ent );
|
|
|
|
// add in muzzleflash effect
|
|
if( ent->curstate.effects & EF_MUZZLEFLASH )
|
|
{
|
|
vec3_t pos;
|
|
|
|
if( ent == &clgame.viewent )
|
|
ent->curstate.effects &= ~EF_MUZZLEFLASH;
|
|
|
|
VectorCopy( ent->attachment[0], pos );
|
|
|
|
// make sure what attachment is valid
|
|
if( !VectorCompare( pos, ent->origin ))
|
|
{
|
|
dlight_t *dl = CL_AllocElight( 0 );
|
|
|
|
VectorCopy( pos, dl->origin );
|
|
dl->die = cl.time + 0.05f;
|
|
dl->color.r = 255;
|
|
dl->color.g = 180;
|
|
dl->color.b = 64;
|
|
dl->radius = 100;
|
|
}
|
|
}
|
|
|
|
// add light effect
|
|
if( ent->curstate.effects & EF_LIGHT )
|
|
{
|
|
dlight_t *dl = CL_AllocDlight( 0 );
|
|
VectorCopy( ent->origin, dl->origin );
|
|
dl->die = cl.time + 0.001f; // die at next frame
|
|
dl->color.r = 100;
|
|
dl->color.g = 100;
|
|
dl->color.b = 100;
|
|
dl->radius = 200;
|
|
CL_RocketFlare( ent->origin );
|
|
}
|
|
|
|
// add dimlight
|
|
if( ent->curstate.effects & EF_DIMLIGHT )
|
|
{
|
|
if( entityType == ET_PLAYER )
|
|
{
|
|
CL_UpadteFlashlight( ent );
|
|
}
|
|
else
|
|
{
|
|
dlight_t *dl = CL_AllocDlight( 0 );
|
|
VectorCopy( ent->origin, dl->origin );
|
|
dl->die = cl.time + 0.001f; // die at next frame
|
|
dl->color.r = 255;
|
|
dl->color.g = 255;
|
|
dl->color.b = 255;
|
|
dl->radius = Com_RandomLong( 200, 230 );
|
|
}
|
|
}
|
|
|
|
if( ent->curstate.effects & EF_BRIGHTLIGHT )
|
|
{
|
|
dlight_t *dl = CL_AllocDlight( 0 );
|
|
VectorSet( dl->origin, ent->origin[0], ent->origin[1], ent->origin[2] + 16 );
|
|
dl->die = cl.time + 0.001f; // die at next frame
|
|
dl->color.r = 255;
|
|
dl->color.g = 255;
|
|
dl->color.b = 255;
|
|
dl->radius = Com_RandomLong( 400, 430 );
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
==================
|
|
CL_WeaponAnim
|
|
|
|
Set new weapon animation
|
|
==================
|
|
*/
|
|
void CL_WeaponAnim( int iAnim, int body )
|
|
{
|
|
cl_entity_t *view = &clgame.viewent;
|
|
|
|
// anim is changed. update latchedvars
|
|
if( iAnim != view->curstate.sequence )
|
|
{
|
|
int i;
|
|
|
|
// save current blends to right lerping from last sequence
|
|
for( i = 0; i < 2; i++ )
|
|
view->latched.prevseqblending[i] = view->curstate.blending[i];
|
|
view->latched.prevsequence = view->curstate.sequence; // save old sequence
|
|
|
|
// save animtime
|
|
view->latched.prevanimtime = view->curstate.animtime;
|
|
view->syncbase = -0.01f; // back up to get 0'th frame animations
|
|
}
|
|
|
|
view->curstate.animtime = cl.time; // start immediately
|
|
view->curstate.framerate = 1.0f;
|
|
view->curstate.sequence = iAnim;
|
|
view->latched.prevframe = 0.0f;
|
|
view->curstate.scale = 1.0f;
|
|
view->curstate.frame = 0.0f;
|
|
view->curstate.body = body;
|
|
}
|
|
|
|
/*
|
|
==================
|
|
CL_UpdateStudioVars
|
|
|
|
Update studio latched vars so interpolation work properly
|
|
==================
|
|
*/
|
|
void CL_UpdateStudioVars( cl_entity_t *ent, entity_state_t *newstate )
|
|
{
|
|
int i;
|
|
|
|
if( newstate->effects & EF_NOINTERP )
|
|
{
|
|
ent->latched.sequencetime = 0.0f; // no lerping between sequences
|
|
ent->latched.prevsequence = newstate->sequence; // keep an actual
|
|
ent->latched.prevanimtime = newstate->animtime;
|
|
|
|
VectorCopy( newstate->origin, ent->latched.prevorigin );
|
|
VectorCopy( newstate->angles, ent->latched.prevangles );
|
|
|
|
// copy controllers
|
|
for( i = 0; i < 4; i++ )
|
|
ent->latched.prevcontroller[i] = newstate->controller[i];
|
|
|
|
// copy blends
|
|
for( i = 0; i < 4; i++ )
|
|
ent->latched.prevblending[i] = newstate->blending[i];
|
|
|
|
newstate->effects &= ~EF_NOINTERP;
|
|
return;
|
|
}
|
|
|
|
// sequence has changed, hold the previous sequence info
|
|
if( newstate->sequence != ent->curstate.sequence )
|
|
{
|
|
if( ent->index > 0 && ent->index <= cl.maxclients )
|
|
ent->latched.sequencetime = ent->curstate.animtime + 0.01f;
|
|
else ent->latched.sequencetime = ent->curstate.animtime + 0.1f;
|
|
|
|
// save current blends to right lerping from last sequence
|
|
for( i = 0; i < 4; i++ )
|
|
ent->latched.prevseqblending[i] = ent->curstate.blending[i];
|
|
ent->latched.prevsequence = ent->curstate.sequence; // save old sequence
|
|
ent->syncbase = -0.01f; // back up to get 0'th frame animations
|
|
}
|
|
|
|
if( newstate->animtime != ent->curstate.animtime )
|
|
{
|
|
// client got new packet, shuffle animtimes
|
|
ent->latched.prevanimtime = ent->curstate.animtime;
|
|
VectorCopy( newstate->origin, ent->latched.prevorigin );
|
|
VectorCopy( newstate->angles, ent->latched.prevangles );
|
|
|
|
for( i = 0; i < 4; i++ )
|
|
ent->latched.prevcontroller[i] = newstate->controller[i];
|
|
}
|
|
|
|
// copy controllers
|
|
for( i = 0; i < 4; i++ )
|
|
{
|
|
if( ent->curstate.controller[i] != newstate->controller[i] )
|
|
ent->latched.prevcontroller[i] = ent->curstate.controller[i];
|
|
}
|
|
|
|
// copy blends
|
|
for( i = 0; i < 4; i++ )
|
|
ent->latched.prevblending[i] = ent->curstate.blending[i];
|
|
|
|
if( !VectorCompare( newstate->origin, ent->curstate.origin ))
|
|
VectorCopy( ent->curstate.origin, ent->latched.prevorigin );
|
|
|
|
if( !VectorCompare( newstate->angles, ent->curstate.angles ))
|
|
VectorCopy( ent->curstate.angles, ent->latched.prevangles );
|
|
}
|
|
|
|
void CL_DeltaEntity( sizebuf_t *msg, frame_t *frame, int newnum, entity_state_t *old, qboolean unchanged )
|
|
{
|
|
cl_entity_t *ent;
|
|
entity_state_t *state;
|
|
qboolean newent = (old) ? false : true;
|
|
qboolean noInterp = false;
|
|
int result = 1;
|
|
|
|
ent = EDICT_NUM( newnum );
|
|
state = &cls.packet_entities[cls.next_client_entities % cls.num_client_entities];
|
|
|
|
if( newent ) old = &ent->baseline;
|
|
if( unchanged )
|
|
{
|
|
*state = *old;
|
|
}
|
|
else
|
|
{
|
|
result = MSG_ReadDeltaEntity( msg, old, state, newnum, CL_IsPlayerIndex( newnum ), sv_time( ));
|
|
}
|
|
|
|
if( !result )
|
|
{
|
|
if( newent ) Host_Error( "Cl_DeltaEntity: tried to release new entity\n" );
|
|
|
|
if( state->number == -1 )
|
|
{
|
|
// Msg( "Entity %s was removed from server\n", ent->curstate.classname );
|
|
CL_FreeEntity( ent );
|
|
}
|
|
else
|
|
{
|
|
// Msg( "Entity %s was removed from delta-message\n", ent->curstate.classname );
|
|
ent->curstate.effects |= EF_NODRAW; // don't rendering
|
|
CL_KillDeadBeams( ent ); // release dead beams
|
|
}
|
|
|
|
// entity was delta removed
|
|
return;
|
|
}
|
|
|
|
// entity present in currentframe
|
|
state->messagenum = cl.parsecount;
|
|
|
|
cls.next_client_entities++;
|
|
frame->num_entities++;
|
|
|
|
if( !ent->index )
|
|
{
|
|
CL_InitEntity( ent );
|
|
noInterp = true;
|
|
}
|
|
|
|
// set player state
|
|
ent->player = CL_IsPlayerIndex( ent->index );
|
|
|
|
if( state->effects & EF_NOINTERP || noInterp )
|
|
{
|
|
// duplicate the current state so lerping doesn't hurt anything
|
|
ent->prevstate = *state;
|
|
}
|
|
else
|
|
{
|
|
// shuffle the last state to previous
|
|
ent->prevstate = ent->curstate;
|
|
}
|
|
|
|
// NOTE: always check modelindex for new state not current
|
|
if( Mod_GetType( state->modelindex ) == mod_studio )
|
|
CL_UpdateStudioVars( ent, state );
|
|
|
|
// set right current state
|
|
ent->curstate = *state;
|
|
|
|
if( ent->player )
|
|
{
|
|
clgame.dllFuncs.pfnProcessPlayerState( &frame->playerstate[ent->index-1], state );
|
|
|
|
frame->playerstate[ent->index-1].number = ent->index;
|
|
|
|
// fill private structure for local client
|
|
if(( ent->index - 1 ) == cl.playernum )
|
|
frame->local.playerstate = frame->playerstate[ent->index-1];
|
|
}
|
|
}
|
|
|
|
/*
|
|
=================
|
|
CL_FlushEntityPacket
|
|
=================
|
|
*/
|
|
void CL_FlushEntityPacket( sizebuf_t *msg )
|
|
{
|
|
int newnum;
|
|
entity_state_t from, to;
|
|
|
|
MsgDev( D_INFO, "FlushEntityPacket()\n" );
|
|
Mem_Set( &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 = BF_ReadWord( msg );
|
|
if( !newnum ) break; // done
|
|
|
|
if( BF_CheckOverflow( msg ))
|
|
Host_Error( "CL_FlushEntityPacket: read overflow\n" );
|
|
|
|
while( newnum >= clgame.numEntities )
|
|
clgame.numEntities++;
|
|
|
|
MSG_ReadDeltaEntity( msg, &from, &to, newnum, CL_IsPlayerIndex( newnum ), sv_time( ));
|
|
}
|
|
}
|
|
|
|
/*
|
|
==================
|
|
CL_ParsePacketEntities
|
|
|
|
An svc_packetentities has just been parsed, deal with the
|
|
rest of the data stream.
|
|
==================
|
|
*/
|
|
void CL_ParsePacketEntities( sizebuf_t *msg, qboolean delta )
|
|
{
|
|
frame_t *newframe, *oldframe;
|
|
int oldindex, newnum, oldnum;
|
|
entity_state_t *oldent;
|
|
int count;
|
|
|
|
// first, allocate packet for new frame
|
|
count = BF_ReadWord( msg );
|
|
|
|
newframe = &cl.frames[cl.parsecountmod];
|
|
|
|
// allocate parse entities
|
|
newframe->first_entity = cls.next_client_entities;
|
|
newframe->num_entities = 0;
|
|
|
|
if( delta )
|
|
{
|
|
int subtracted, delta_sequence;
|
|
|
|
delta_sequence = BF_ReadByte( msg );
|
|
subtracted = ((( cls.netchan.incoming_sequence & 0xFF ) - delta_sequence ) & 0xFF );
|
|
|
|
if( subtracted == 0 )
|
|
{
|
|
Host_Error( "CL_DeltaPacketEntities: update too old, connection dropped.\n" );
|
|
return;
|
|
}
|
|
|
|
if( subtracted >= CL_UPDATE_MASK )
|
|
{
|
|
// we can't use this, it is too old
|
|
CL_FlushEntityPacket( msg );
|
|
return;
|
|
}
|
|
|
|
oldframe = &cl.frames[delta_sequence & CL_UPDATE_MASK];
|
|
|
|
if( !oldframe->valid )
|
|
{
|
|
// should never happen
|
|
MsgDev( D_INFO, "delta from invalid frame (not supposed to happen!)\n" );
|
|
}
|
|
|
|
if(( oldframe->delta_sequence & 0xFF ) != (( delta_sequence - 1 ) & 0xFF ))
|
|
{
|
|
// The frame that the server did the delta from
|
|
// is too old, so we can't reconstruct it properly.
|
|
MsgDev( D_INFO, "CL_ParsePacketEntities: delta frame too old\n" );
|
|
}
|
|
else if( cls.next_client_entities - oldframe->first_entity > cls.num_client_entities - 128 )
|
|
{
|
|
MsgDev( D_INFO, "CL_ParsePacketEntities: delta parse_entities too old\n" );
|
|
}
|
|
else newframe->valid = true; // valid delta parse
|
|
|
|
if(( cl.delta_sequence & CL_UPDATE_MASK ) != ( delta_sequence & CL_UPDATE_MASK ))
|
|
MsgDev( D_WARN, "CL_ParsePacketEntities: mismatch delta_sequence %i != %i\n", cl.delta_sequence, delta );
|
|
|
|
// keep sequence an actual
|
|
newframe->delta_sequence = delta_sequence;
|
|
}
|
|
else
|
|
{
|
|
// this is a full update that we can start delta compressing from now
|
|
newframe->delta_sequence = ( cls.netchan.incoming_sequence - 1 ) & 0xFF;
|
|
newframe->valid = true;
|
|
oldframe = NULL;
|
|
|
|
cl.force_send_usercmd = 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 = BF_ReadWord( msg );
|
|
if( !newnum ) break; // end of packet entities
|
|
|
|
if( BF_CheckOverflow( msg ))
|
|
Host_Error( "CL_ParsePacketEntities: read overflow\n" );
|
|
|
|
while( newnum >= clgame.numEntities )
|
|
clgame.numEntities++;
|
|
|
|
while( oldnum < newnum )
|
|
{
|
|
// one or more entities from the old packet are unchanged
|
|
CL_DeltaEntity( msg, newframe, oldnum, oldent, true );
|
|
|
|
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
|
|
CL_DeltaEntity( msg, newframe, newnum, 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;
|
|
}
|
|
continue;
|
|
}
|
|
|
|
if( oldnum > newnum )
|
|
{
|
|
// delta from baseline ?
|
|
CL_DeltaEntity( msg, newframe, newnum, NULL, false );
|
|
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, true );
|
|
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;
|
|
}
|
|
}
|
|
|
|
cl.frame = *newframe;
|
|
if( !cl.frame.valid ) return;
|
|
|
|
if( cls.state != ca_active )
|
|
{
|
|
cl_entity_t *player;
|
|
|
|
// client entered the game
|
|
cls.state = ca_active;
|
|
cl.force_refdef = true;
|
|
cls.changelevel = false; // changelevel is done
|
|
|
|
player = CL_GetLocalPlayer();
|
|
SCR_MakeLevelShot(); // make levelshot if needs
|
|
|
|
Cvar_SetFloat( "scr_loading", 0.0f ); // reset progress bar
|
|
|
|
if( cls.disable_servercount != cl.servercount && cl.video_prepped )
|
|
SCR_EndLoadingPlaque(); // get rid of loading plaque
|
|
|
|
// getting a valid frame message ends the connection process
|
|
VectorCopy( player->origin, cl.predicted_origin );
|
|
VectorCopy( player->angles, cl.predicted_angles );
|
|
}
|
|
|
|
CL_CheckPredictionError();
|
|
}
|
|
|
|
/*
|
|
==========================================================================
|
|
|
|
INTERPOLATE BETWEEN FRAMES TO GET RENDERING PARMS
|
|
|
|
==========================================================================
|
|
*/
|
|
/*
|
|
===============
|
|
CL_AddPacketEntities
|
|
|
|
===============
|
|
*/
|
|
void CL_AddPacketEntities( frame_t *frame )
|
|
{
|
|
cl_entity_t *ent, *clent;
|
|
int e, entityType;
|
|
|
|
// now recalc actual entcount
|
|
for( ; !EDICT_NUM( clgame.numEntities - 1 )->index; clgame.numEntities-- );
|
|
|
|
clent = CL_GetLocalPlayer();
|
|
if( !clent ) return;
|
|
|
|
// update client vars
|
|
clgame.dllFuncs.pfnTxferLocalOverrides( &clent->curstate, &cl.frame.local.client );
|
|
|
|
for( e = 1; e < clgame.numEntities; e++ )
|
|
{
|
|
ent = CL_GetEntityByIndex( e );
|
|
if( !ent || !ent->index ) continue;
|
|
|
|
// entity not visible for this client
|
|
if( ent->curstate.effects & EF_NODRAW )
|
|
continue;
|
|
|
|
CL_UpdateEntityFields( ent );
|
|
|
|
if( ent->player ) entityType = ET_PLAYER;
|
|
else if( ent->curstate.entityType == ENTITY_BEAM )
|
|
entityType = ET_BEAM;
|
|
else entityType = ET_NORMAL;
|
|
|
|
CL_AddVisibleEntity( ent, entityType );
|
|
}
|
|
}
|
|
|
|
/*
|
|
===============
|
|
CL_AddEntities
|
|
|
|
Emits all entities, particles, and lights to the refresh
|
|
===============
|
|
*/
|
|
void CL_AddEntities( void )
|
|
{
|
|
if( cls.state != ca_active )
|
|
return;
|
|
|
|
cl.num_custombeams = 0;
|
|
|
|
clgame.dllFuncs.CAM_Think();
|
|
|
|
CL_AddPacketEntities( &cl.frame );
|
|
clgame.dllFuncs.pfnCreateEntities();
|
|
|
|
CL_FireEvents(); // so tempents can be created immediately
|
|
CL_AddTempEnts();
|
|
|
|
// perfomance test
|
|
CL_TestLights();
|
|
}
|
|
|
|
//
|
|
// sound engine implementation
|
|
//
|
|
qboolean CL_GetEntitySpatialization( int entnum, vec3_t origin, vec3_t velocity )
|
|
{
|
|
cl_entity_t *ent;
|
|
qboolean from_baseline = false;
|
|
|
|
if( entnum < 0 || entnum > clgame.numEntities )
|
|
return false;
|
|
|
|
ent = EDICT_NUM( entnum );
|
|
|
|
if( !ent->index || ent->curstate.number != entnum )
|
|
{
|
|
// this entity isn't visible but maybe it have baseline ?
|
|
if( ent->baseline.number != entnum )
|
|
return false;
|
|
from_baseline = true;
|
|
}
|
|
|
|
if( from_baseline )
|
|
{
|
|
// setup origin and velocity
|
|
if( origin ) VectorCopy( ent->baseline.origin, origin );
|
|
if( velocity ) VectorCopy( ent->baseline.velocity, velocity );
|
|
|
|
// if a brush model, offset the origin
|
|
if( origin && Mod_GetType( ent->baseline.modelindex ) == mod_brush )
|
|
{
|
|
vec3_t mins, maxs, midPoint;
|
|
|
|
Mod_GetBounds( ent->baseline.modelindex, mins, maxs );
|
|
VectorAverage( mins, maxs, midPoint );
|
|
VectorAdd( origin, midPoint, origin );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
|
|
// setup origin and velocity
|
|
if( origin ) VectorCopy( ent->origin, origin );
|
|
if( velocity ) VectorCopy( ent->curstate.velocity, velocity );
|
|
|
|
// if a brush model, offset the origin
|
|
if( origin && Mod_GetType( ent->curstate.modelindex ) == mod_brush )
|
|
{
|
|
vec3_t mins, maxs, midPoint;
|
|
|
|
Mod_GetBounds( ent->curstate.modelindex, mins, maxs );
|
|
VectorAverage( mins, maxs, midPoint );
|
|
VectorAdd( origin, midPoint, origin );
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void CL_ExtraUpdate( void )
|
|
{
|
|
clgame.dllFuncs.IN_Accumulate();
|
|
S_ExtraUpdate();
|
|
} |