1257 lines
31 KiB
C
1257 lines
31 KiB
C
/*
|
|
===========================================================================
|
|
Copyright (C) 1999-2005 Id Software, Inc.
|
|
Copyright (C) 2000-2006 Tim Angus
|
|
|
|
This file is part of Tremulous.
|
|
|
|
Tremulous is free software; you can redistribute it
|
|
and/or modify it under the terms of the GNU General Public License as
|
|
published by the Free Software Foundation; either version 2 of the License,
|
|
or (at your option) any later version.
|
|
|
|
Tremulous is distributed in the hope that it will be
|
|
useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with Tremulous; if not, write to the Free Software
|
|
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
|
===========================================================================
|
|
*/
|
|
|
|
// cg_ents.c -- present snapshot entities, happens every single frame
|
|
|
|
|
|
#include "cg_local.h"
|
|
|
|
/*
|
|
======================
|
|
CG_DrawBoxFace
|
|
|
|
Draws a bounding box face
|
|
======================
|
|
*/
|
|
static void CG_DrawBoxFace( vec3_t a, vec3_t b, vec3_t c, vec3_t d )
|
|
{
|
|
polyVert_t verts[ 4 ];
|
|
vec4_t color = { 255.0f, 0.0f, 0.0f, 128.0f };
|
|
|
|
VectorCopy( d, verts[ 0 ].xyz );
|
|
verts[ 0 ].st[ 0 ] = 1;
|
|
verts[ 0 ].st[ 1 ] = 1;
|
|
Vector4Copy( color, verts[ 0 ].modulate );
|
|
|
|
VectorCopy( c, verts[ 1 ].xyz );
|
|
verts[ 1 ].st[ 0 ] = 1;
|
|
verts[ 1 ].st[ 1 ] = 0;
|
|
Vector4Copy( color, verts[ 1 ].modulate );
|
|
|
|
VectorCopy( b, verts[ 2 ].xyz );
|
|
verts[ 2 ].st[ 0 ] = 0;
|
|
verts[ 2 ].st[ 1 ] = 0;
|
|
Vector4Copy( color, verts[ 2 ].modulate );
|
|
|
|
VectorCopy( a, verts[ 3 ].xyz );
|
|
verts[ 3 ].st[ 0 ] = 0;
|
|
verts[ 3 ].st[ 1 ] = 1;
|
|
Vector4Copy( color, verts[ 3 ].modulate );
|
|
|
|
trap_R_AddPolyToScene( cgs.media.outlineShader, 4, verts );
|
|
}
|
|
|
|
/*
|
|
======================
|
|
CG_DrawBoundingBox
|
|
|
|
Draws a bounding box
|
|
======================
|
|
*/
|
|
void CG_DrawBoundingBox( vec3_t origin, vec3_t mins, vec3_t maxs )
|
|
{
|
|
vec3_t ppp, mpp, mmp, pmp;
|
|
vec3_t mmm, pmm, ppm, mpm;
|
|
|
|
ppp[ 0 ] = origin[ 0 ] + maxs[ 0 ];
|
|
ppp[ 1 ] = origin[ 1 ] + maxs[ 1 ];
|
|
ppp[ 2 ] = origin[ 2 ] + maxs[ 2 ];
|
|
|
|
mpp[ 0 ] = origin[ 0 ] + mins[ 0 ];
|
|
mpp[ 1 ] = origin[ 1 ] + maxs[ 1 ];
|
|
mpp[ 2 ] = origin[ 2 ] + maxs[ 2 ];
|
|
|
|
mmp[ 0 ] = origin[ 0 ] + mins[ 0 ];
|
|
mmp[ 1 ] = origin[ 1 ] + mins[ 1 ];
|
|
mmp[ 2 ] = origin[ 2 ] + maxs[ 2 ];
|
|
|
|
pmp[ 0 ] = origin[ 0 ] + maxs[ 0 ];
|
|
pmp[ 1 ] = origin[ 1 ] + mins[ 1 ];
|
|
pmp[ 2 ] = origin[ 2 ] + maxs[ 2 ];
|
|
|
|
ppm[ 0 ] = origin[ 0 ] + maxs[ 0 ];
|
|
ppm[ 1 ] = origin[ 1 ] + maxs[ 1 ];
|
|
ppm[ 2 ] = origin[ 2 ] + mins[ 2 ];
|
|
|
|
mpm[ 0 ] = origin[ 0 ] + mins[ 0 ];
|
|
mpm[ 1 ] = origin[ 1 ] + maxs[ 1 ];
|
|
mpm[ 2 ] = origin[ 2 ] + mins[ 2 ];
|
|
|
|
mmm[ 0 ] = origin[ 0 ] + mins[ 0 ];
|
|
mmm[ 1 ] = origin[ 1 ] + mins[ 1 ];
|
|
mmm[ 2 ] = origin[ 2 ] + mins[ 2 ];
|
|
|
|
pmm[ 0 ] = origin[ 0 ] + maxs[ 0 ];
|
|
pmm[ 1 ] = origin[ 1 ] + mins[ 1 ];
|
|
pmm[ 2 ] = origin[ 2 ] + mins[ 2 ];
|
|
|
|
//phew!
|
|
|
|
CG_DrawBoxFace( ppp, mpp, mmp, pmp );
|
|
CG_DrawBoxFace( ppp, pmp, pmm, ppm );
|
|
CG_DrawBoxFace( mpp, ppp, ppm, mpm );
|
|
CG_DrawBoxFace( mmp, mpp, mpm, mmm );
|
|
CG_DrawBoxFace( pmp, mmp, mmm, pmm );
|
|
CG_DrawBoxFace( mmm, mpm, ppm, pmm );
|
|
}
|
|
|
|
|
|
/*
|
|
======================
|
|
CG_PositionEntityOnTag
|
|
|
|
Modifies the entities position and axis by the given
|
|
tag location
|
|
======================
|
|
*/
|
|
void CG_PositionEntityOnTag( refEntity_t *entity, const refEntity_t *parent,
|
|
qhandle_t parentModel, char *tagName )
|
|
{
|
|
int i;
|
|
orientation_t lerped;
|
|
|
|
// lerp the tag
|
|
trap_R_LerpTag( &lerped, parentModel, parent->oldframe, parent->frame,
|
|
1.0 - parent->backlerp, tagName );
|
|
|
|
// FIXME: allow origin offsets along tag?
|
|
VectorCopy( parent->origin, entity->origin );
|
|
for( i = 0; i < 3; i++ )
|
|
VectorMA( entity->origin, lerped.origin[ i ], parent->axis[ i ], entity->origin );
|
|
|
|
// had to cast away the const to avoid compiler problems...
|
|
MatrixMultiply( lerped.axis, ( (refEntity_t *)parent )->axis, entity->axis );
|
|
entity->backlerp = parent->backlerp;
|
|
}
|
|
|
|
|
|
/*
|
|
======================
|
|
CG_PositionRotatedEntityOnTag
|
|
|
|
Modifies the entities position and axis by the given
|
|
tag location
|
|
======================
|
|
*/
|
|
void CG_PositionRotatedEntityOnTag( refEntity_t *entity, const refEntity_t *parent,
|
|
qhandle_t parentModel, char *tagName )
|
|
{
|
|
int i;
|
|
orientation_t lerped;
|
|
vec3_t tempAxis[ 3 ];
|
|
|
|
//AxisClear( entity->axis );
|
|
// lerp the tag
|
|
trap_R_LerpTag( &lerped, parentModel, parent->oldframe, parent->frame,
|
|
1.0 - parent->backlerp, tagName );
|
|
|
|
// FIXME: allow origin offsets along tag?
|
|
VectorCopy( parent->origin, entity->origin );
|
|
for( i = 0; i < 3; i++ )
|
|
VectorMA( entity->origin, lerped.origin[ i ], parent->axis[ i ], entity->origin );
|
|
|
|
// had to cast away the const to avoid compiler problems...
|
|
MatrixMultiply( entity->axis, lerped.axis, tempAxis );
|
|
MatrixMultiply( tempAxis, ( (refEntity_t *)parent )->axis, entity->axis );
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
==========================================================================
|
|
|
|
FUNCTIONS CALLED EACH FRAME
|
|
|
|
==========================================================================
|
|
*/
|
|
|
|
/*
|
|
======================
|
|
CG_SetEntitySoundPosition
|
|
|
|
Also called by event processing code
|
|
======================
|
|
*/
|
|
void CG_SetEntitySoundPosition( centity_t *cent )
|
|
{
|
|
if( cent->currentState.solid == SOLID_BMODEL )
|
|
{
|
|
vec3_t origin;
|
|
float *v;
|
|
|
|
v = cgs.inlineModelMidpoints[ cent->currentState.modelindex ];
|
|
VectorAdd( cent->lerpOrigin, v, origin );
|
|
trap_S_UpdateEntityPosition( cent->currentState.number, origin );
|
|
}
|
|
else
|
|
trap_S_UpdateEntityPosition( cent->currentState.number, cent->lerpOrigin );
|
|
}
|
|
|
|
/*
|
|
==================
|
|
CG_EntityEffects
|
|
|
|
Add continuous entity effects, like local entity emission and lighting
|
|
==================
|
|
*/
|
|
static void CG_EntityEffects( centity_t *cent )
|
|
{
|
|
// update sound origins
|
|
CG_SetEntitySoundPosition( cent );
|
|
|
|
// add loop sound
|
|
if( cent->currentState.loopSound )
|
|
{
|
|
if( cent->currentState.eType != ET_SPEAKER )
|
|
{
|
|
trap_S_AddLoopingSound( cent->currentState.number, cent->lerpOrigin, vec3_origin,
|
|
cgs.gameSounds[ cent->currentState.loopSound ] );
|
|
}
|
|
else
|
|
{
|
|
trap_S_AddRealLoopingSound( cent->currentState.number, cent->lerpOrigin, vec3_origin,
|
|
cgs.gameSounds[ cent->currentState.loopSound ] );
|
|
}
|
|
}
|
|
|
|
|
|
// constant light glow
|
|
if ( cent->currentState.constantLight )
|
|
{
|
|
int cl;
|
|
int i, r, g, b;
|
|
|
|
cl = cent->currentState.constantLight;
|
|
r = cl & 255;
|
|
g = ( cl >> 8 ) & 255;
|
|
b = ( cl >> 16 ) & 255;
|
|
i = ( ( cl >> 24 ) & 255 ) * 4;
|
|
trap_R_AddLightToScene( cent->lerpOrigin, i, r, g, b );
|
|
}
|
|
|
|
if( CG_IsTrailSystemValid( ¢->muzzleTS ) )
|
|
{
|
|
//FIXME hack to prevent tesla trails reaching too far
|
|
if( cent->currentState.eType == ET_BUILDABLE )
|
|
{
|
|
vec3_t front, back;
|
|
|
|
CG_AttachmentPoint( ¢->muzzleTS->frontAttachment, front );
|
|
CG_AttachmentPoint( ¢->muzzleTS->backAttachment, back );
|
|
|
|
if( Distance( front, back ) > ( TESLAGEN_RANGE * M_ROOT3 ) )
|
|
CG_DestroyTrailSystem( ¢->muzzleTS );
|
|
}
|
|
|
|
if( cg.time > cent->muzzleTSDeathTime && CG_IsTrailSystemValid( ¢->muzzleTS ) )
|
|
CG_DestroyTrailSystem( ¢->muzzleTS );
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
==================
|
|
CG_General
|
|
==================
|
|
*/
|
|
static void CG_General( centity_t *cent )
|
|
{
|
|
refEntity_t ent;
|
|
entityState_t *s1;
|
|
|
|
s1 = ¢->currentState;
|
|
|
|
// if set to invisible, skip
|
|
if( !s1->modelindex )
|
|
return;
|
|
|
|
memset( &ent, 0, sizeof( ent ) );
|
|
|
|
// set frame
|
|
|
|
ent.frame = s1->frame;
|
|
ent.oldframe = ent.frame;
|
|
ent.backlerp = 0;
|
|
|
|
VectorCopy( cent->lerpOrigin, ent.origin);
|
|
VectorCopy( cent->lerpOrigin, ent.oldorigin);
|
|
|
|
ent.hModel = cgs.gameModels[ s1->modelindex ];
|
|
|
|
// player model
|
|
if( s1->number == cg.snap->ps.clientNum )
|
|
ent.renderfx |= RF_THIRD_PERSON; // only draw from mirrors
|
|
|
|
// convert angles to axis
|
|
AnglesToAxis( cent->lerpAngles, ent.axis );
|
|
|
|
// add to refresh list
|
|
trap_R_AddRefEntityToScene( &ent );
|
|
}
|
|
|
|
/*
|
|
==================
|
|
CG_Speaker
|
|
|
|
Speaker entities can automatically play sounds
|
|
==================
|
|
*/
|
|
static void CG_Speaker( centity_t *cent )
|
|
{
|
|
if( ! cent->currentState.clientNum )
|
|
{ // FIXME: use something other than clientNum...
|
|
return; // not auto triggering
|
|
}
|
|
|
|
if( cg.time < cent->miscTime )
|
|
return;
|
|
|
|
trap_S_StartSound( NULL, cent->currentState.number, CHAN_ITEM, cgs.gameSounds[ cent->currentState.eventParm ] );
|
|
|
|
// ent->s.frame = ent->wait * 10;
|
|
// ent->s.clientNum = ent->random * 10;
|
|
cent->miscTime = cg.time + cent->currentState.frame * 100 + cent->currentState.clientNum * 100 * crandom( );
|
|
}
|
|
|
|
|
|
//============================================================================
|
|
|
|
/*
|
|
===============
|
|
CG_LaunchMissile
|
|
===============
|
|
*/
|
|
static void CG_LaunchMissile( centity_t *cent )
|
|
{
|
|
entityState_t *es;
|
|
const weaponInfo_t *wi;
|
|
particleSystem_t *ps;
|
|
trailSystem_t *ts;
|
|
weapon_t weapon;
|
|
weaponMode_t weaponMode;
|
|
|
|
es = ¢->currentState;
|
|
|
|
weapon = es->weapon;
|
|
if( weapon > WP_NUM_WEAPONS )
|
|
weapon = WP_NONE;
|
|
|
|
wi = &cg_weapons[ weapon ];
|
|
weaponMode = es->generic1;
|
|
|
|
if( wi->wim[ weaponMode ].missileParticleSystem )
|
|
{
|
|
ps = CG_SpawnNewParticleSystem( wi->wim[ weaponMode ].missileParticleSystem );
|
|
|
|
if( CG_IsParticleSystemValid( &ps ) )
|
|
{
|
|
CG_SetAttachmentCent( &ps->attachment, cent );
|
|
CG_AttachToCent( &ps->attachment );
|
|
}
|
|
}
|
|
|
|
if( wi->wim[ weaponMode ].missileTrailSystem )
|
|
{
|
|
ts = CG_SpawnNewTrailSystem( wi->wim[ weaponMode ].missileTrailSystem );
|
|
|
|
if( CG_IsTrailSystemValid( &ts ) )
|
|
{
|
|
CG_SetAttachmentCent( &ts->frontAttachment, cent );
|
|
CG_AttachToCent( &ts->frontAttachment );
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
===============
|
|
CG_Missile
|
|
===============
|
|
*/
|
|
static void CG_Missile( centity_t *cent )
|
|
{
|
|
refEntity_t ent;
|
|
entityState_t *es;
|
|
const weaponInfo_t *wi;
|
|
weapon_t weapon;
|
|
weaponMode_t weaponMode;
|
|
const weaponInfoMode_t *wim;
|
|
|
|
es = ¢->currentState;
|
|
|
|
weapon = es->weapon;
|
|
if( weapon > WP_NUM_WEAPONS )
|
|
weapon = WP_NONE;
|
|
|
|
wi = &cg_weapons[ weapon ];
|
|
weaponMode = es->generic1;
|
|
|
|
wim = &wi->wim[ weaponMode ];
|
|
|
|
// calculate the axis
|
|
VectorCopy( es->angles, cent->lerpAngles );
|
|
|
|
// add dynamic light
|
|
if( wim->missileDlight )
|
|
{
|
|
trap_R_AddLightToScene( cent->lerpOrigin, wim->missileDlight,
|
|
wim->missileDlightColor[ 0 ],
|
|
wim->missileDlightColor[ 1 ],
|
|
wim->missileDlightColor[ 2 ] );
|
|
}
|
|
|
|
// add missile sound
|
|
if( wim->missileSound )
|
|
{
|
|
vec3_t velocity;
|
|
|
|
BG_EvaluateTrajectoryDelta( ¢->currentState.pos, cg.time, velocity );
|
|
|
|
trap_S_AddLoopingSound( cent->currentState.number, cent->lerpOrigin, velocity, wim->missileSound );
|
|
}
|
|
|
|
// create the render entity
|
|
memset( &ent, 0, sizeof( ent ) );
|
|
VectorCopy( cent->lerpOrigin, ent.origin );
|
|
VectorCopy( cent->lerpOrigin, ent.oldorigin );
|
|
|
|
if( wim->usesSpriteMissle )
|
|
{
|
|
ent.reType = RT_SPRITE;
|
|
ent.radius = wim->missileSpriteSize;
|
|
ent.rotation = 0;
|
|
ent.customShader = wim->missileSprite;
|
|
ent.shaderRGBA[ 0 ] = 0xFF;
|
|
ent.shaderRGBA[ 1 ] = 0xFF;
|
|
ent.shaderRGBA[ 2 ] = 0xFF;
|
|
ent.shaderRGBA[ 3 ] = 0xFF;
|
|
}
|
|
else
|
|
{
|
|
ent.hModel = wim->missileModel;
|
|
ent.renderfx = wim->missileRenderfx | RF_NOSHADOW;
|
|
|
|
// convert direction of travel into axis
|
|
if( VectorNormalize2( es->pos.trDelta, ent.axis[ 0 ] ) == 0 )
|
|
ent.axis[ 0 ][ 2 ] = 1;
|
|
|
|
// spin as it moves
|
|
if( es->pos.trType != TR_STATIONARY && wim->missileRotates )
|
|
RotateAroundDirection( ent.axis, cg.time / 4 );
|
|
else
|
|
RotateAroundDirection( ent.axis, es->time );
|
|
|
|
if( wim->missileAnimates )
|
|
{
|
|
int timeSinceStart = cg.time - es->time;
|
|
|
|
if( wim->missileAnimLooping )
|
|
{
|
|
ent.frame = wim->missileAnimStartFrame +
|
|
(int)( ( timeSinceStart / 1000.0f ) * wim->missileAnimFrameRate ) %
|
|
wim->missileAnimNumFrames;
|
|
}
|
|
else
|
|
{
|
|
ent.frame = wim->missileAnimStartFrame +
|
|
(int)( ( timeSinceStart / 1000.0f ) * wim->missileAnimFrameRate );
|
|
|
|
if( ent.frame > ( wim->missileAnimStartFrame + wim->missileAnimNumFrames ) )
|
|
ent.frame = wim->missileAnimStartFrame + wim->missileAnimNumFrames;
|
|
}
|
|
}
|
|
}
|
|
|
|
//only refresh if there is something to display
|
|
if( wim->missileSprite || wim->missileModel )
|
|
trap_R_AddRefEntityToScene( &ent );
|
|
}
|
|
|
|
/*
|
|
===============
|
|
CG_Mover
|
|
===============
|
|
*/
|
|
static void CG_Mover( centity_t *cent )
|
|
{
|
|
refEntity_t ent;
|
|
entityState_t *s1;
|
|
|
|
s1 = ¢->currentState;
|
|
|
|
// create the render entity
|
|
memset( &ent, 0, sizeof( ent ) );
|
|
VectorCopy( cent->lerpOrigin, ent.origin );
|
|
VectorCopy( cent->lerpOrigin, ent.oldorigin );
|
|
AnglesToAxis( cent->lerpAngles, ent.axis );
|
|
|
|
ent.renderfx = RF_NOSHADOW;
|
|
|
|
// flicker between two skins (FIXME?)
|
|
ent.skinNum = ( cg.time >> 6 ) & 1;
|
|
|
|
// get the model, either as a bmodel or a modelindex
|
|
if( s1->solid == SOLID_BMODEL )
|
|
ent.hModel = cgs.inlineDrawModel[ s1->modelindex ];
|
|
else
|
|
ent.hModel = cgs.gameModels[ s1->modelindex ];
|
|
|
|
// add to refresh list
|
|
trap_R_AddRefEntityToScene( &ent );
|
|
|
|
// add the secondary model
|
|
if( s1->modelindex2 )
|
|
{
|
|
ent.skinNum = 0;
|
|
ent.hModel = cgs.gameModels[ s1->modelindex2 ];
|
|
trap_R_AddRefEntityToScene( &ent );
|
|
}
|
|
|
|
}
|
|
|
|
/*
|
|
===============
|
|
CG_Beam
|
|
|
|
Also called as an event
|
|
===============
|
|
*/
|
|
void CG_Beam( centity_t *cent )
|
|
{
|
|
refEntity_t ent;
|
|
entityState_t *s1;
|
|
|
|
s1 = ¢->currentState;
|
|
|
|
// create the render entity
|
|
memset( &ent, 0, sizeof( ent ) );
|
|
VectorCopy( s1->pos.trBase, ent.origin );
|
|
VectorCopy( s1->origin2, ent.oldorigin );
|
|
AxisClear( ent.axis );
|
|
ent.reType = RT_BEAM;
|
|
|
|
ent.renderfx = RF_NOSHADOW;
|
|
|
|
// add to refresh list
|
|
trap_R_AddRefEntityToScene( &ent );
|
|
}
|
|
|
|
|
|
/*
|
|
===============
|
|
CG_Portal
|
|
===============
|
|
*/
|
|
static void CG_Portal( centity_t *cent )
|
|
{
|
|
refEntity_t ent;
|
|
entityState_t *s1;
|
|
|
|
s1 = ¢->currentState;
|
|
|
|
// create the render entity
|
|
memset( &ent, 0, sizeof( ent ) );
|
|
VectorCopy( cent->lerpOrigin, ent.origin );
|
|
VectorCopy( s1->origin2, ent.oldorigin );
|
|
ByteToDir( s1->eventParm, ent.axis[ 0 ] );
|
|
PerpendicularVector( ent.axis[ 1 ], ent.axis[ 0 ] );
|
|
|
|
// negating this tends to get the directions like they want
|
|
// we really should have a camera roll value
|
|
VectorSubtract( vec3_origin, ent.axis[ 1 ], ent.axis[ 1 ] );
|
|
|
|
CrossProduct( ent.axis[ 0 ], ent.axis[ 1 ], ent.axis[ 2 ] );
|
|
ent.reType = RT_PORTALSURFACE;
|
|
ent.oldframe = s1->powerups;
|
|
ent.frame = s1->frame; // rotation speed
|
|
ent.skinNum = s1->clientNum / 256.0 * 360; // roll offset
|
|
|
|
// add to refresh list
|
|
trap_R_AddRefEntityToScene( &ent );
|
|
}
|
|
|
|
//============================================================================
|
|
|
|
#define SETBOUNDS(v1,v2,r) ((v1)[0]=(-r/2),(v1)[1]=(-r/2),(v1)[2]=(-r/2),\
|
|
(v2)[0]=(r/2),(v2)[1]=(r/2),(v2)[2]=(r/2))
|
|
#define RADIUSSTEP 0.5f
|
|
|
|
#define FLARE_OFF 0
|
|
#define FLARE_NOFADE 1
|
|
#define FLARE_TIMEFADE 2
|
|
#define FLARE_REALFADE 3
|
|
|
|
/*
|
|
=========================
|
|
CG_LightFlare
|
|
=========================
|
|
*/
|
|
static void CG_LightFlare( centity_t *cent )
|
|
{
|
|
refEntity_t flare;
|
|
entityState_t *es;
|
|
vec3_t forward, delta;
|
|
float len;
|
|
trace_t tr;
|
|
float maxAngle;
|
|
vec3_t mins, maxs, start, end;
|
|
float srcRadius, srLocal, ratio = 1.0f;
|
|
int entityNum;
|
|
|
|
es = ¢->currentState;
|
|
|
|
if( cg.renderingThirdPerson )
|
|
entityNum = MAGIC_TRACE_HACK;
|
|
else
|
|
entityNum = cg.predictedPlayerState.clientNum;
|
|
|
|
//don't draw light flares
|
|
if( cg_lightFlare.integer == FLARE_OFF )
|
|
return;
|
|
|
|
//flare is "off"
|
|
if( es->eFlags & EF_NODRAW )
|
|
return;
|
|
|
|
CG_Trace( &tr, cg.refdef.vieworg, NULL, NULL, es->angles2,
|
|
entityNum, MASK_SHOT );
|
|
|
|
//if there is no los between the view and the flare source
|
|
//it definately cannot be seen
|
|
if( tr.fraction < 1.0f || tr.allsolid )
|
|
return;
|
|
|
|
memset( &flare, 0, sizeof( flare ) );
|
|
|
|
flare.reType = RT_SPRITE;
|
|
flare.customShader = cgs.gameShaders[ es->modelindex ];
|
|
flare.shaderRGBA[ 0 ] = 0xFF;
|
|
flare.shaderRGBA[ 1 ] = 0xFF;
|
|
flare.shaderRGBA[ 2 ] = 0xFF;
|
|
flare.shaderRGBA[ 3 ] = 0xFF;
|
|
|
|
//flares always drawn before the rest of the scene
|
|
flare.renderfx |= RF_DEPTHHACK;
|
|
|
|
//bunch of geometry
|
|
AngleVectors( es->angles, forward, NULL, NULL );
|
|
VectorCopy( cent->lerpOrigin, flare.origin );
|
|
VectorSubtract( flare.origin, cg.refdef.vieworg, delta );
|
|
len = VectorLength( delta );
|
|
VectorNormalize( delta );
|
|
|
|
//flare is too close to camera to be drawn
|
|
if( len < es->generic1 )
|
|
return;
|
|
|
|
//don't bother for flares behind the view plane
|
|
if( DotProduct( delta, cg.refdef.viewaxis[ 0 ] ) < 0.0 )
|
|
return;
|
|
|
|
//only recalculate radius and ratio every three frames
|
|
if( !( cg.clientFrame % 2 ) )
|
|
{
|
|
//can only see the flare when in front of it
|
|
flare.radius = len / es->origin2[ 0 ];
|
|
|
|
if( es->origin2[ 2 ] == 0 )
|
|
srcRadius = srLocal = flare.radius / 2.0f;
|
|
else
|
|
srcRadius = srLocal = len / es->origin2[ 2 ];
|
|
|
|
maxAngle = es->origin2[ 1 ];
|
|
|
|
if( maxAngle > 0.0f )
|
|
{
|
|
float radiusMod = 1.0f - ( 180.0f - RAD2DEG(
|
|
acos( DotProduct( delta, forward ) ) ) ) / maxAngle;
|
|
|
|
if( radiusMod < 0.0f )
|
|
radiusMod = 0.0f;
|
|
|
|
flare.radius *= radiusMod;
|
|
}
|
|
|
|
if( flare.radius < 0.0f )
|
|
flare.radius = 0.0f;
|
|
|
|
VectorMA( flare.origin, -flare.radius, delta, end );
|
|
VectorMA( cg.refdef.vieworg, flare.radius, delta, start );
|
|
|
|
if( cg_lightFlare.integer == FLARE_REALFADE )
|
|
{
|
|
//"correct" flares
|
|
CG_BiSphereTrace( &tr, cg.refdef.vieworg, end,
|
|
1.0f, srcRadius, entityNum, MASK_SHOT );
|
|
|
|
if( tr.fraction < 1.0f )
|
|
ratio = tr.lateralFraction;
|
|
else
|
|
ratio = 1.0f;
|
|
}
|
|
else if( cg_lightFlare.integer == FLARE_TIMEFADE )
|
|
{
|
|
//draw timed flares
|
|
SETBOUNDS( mins, maxs, srcRadius );
|
|
CG_Trace( &tr, start, mins, maxs, end,
|
|
entityNum, MASK_SHOT );
|
|
|
|
if( ( tr.fraction < 1.0f || tr.startsolid ) && cent->lfs.status )
|
|
{
|
|
cent->lfs.status = qfalse;
|
|
cent->lfs.lastTime = cg.time;
|
|
}
|
|
else if( ( tr.fraction == 1.0f && !tr.startsolid ) && !cent->lfs.status )
|
|
{
|
|
cent->lfs.status = qtrue;
|
|
cent->lfs.lastTime = cg.time;
|
|
}
|
|
|
|
//fade flare up
|
|
if( cent->lfs.status )
|
|
{
|
|
if( cent->lfs.lastTime + es->time > cg.time )
|
|
ratio = (float)( cg.time - cent->lfs.lastTime ) / es->time;
|
|
}
|
|
|
|
//fade flare down
|
|
if( !cent->lfs.status )
|
|
{
|
|
if( cent->lfs.lastTime + es->time > cg.time )
|
|
{
|
|
ratio = (float)( cg.time - cent->lfs.lastTime ) / es->time;
|
|
ratio = 1.0f - ratio;
|
|
}
|
|
else
|
|
ratio = 0.0f;
|
|
}
|
|
}
|
|
else if( cg_lightFlare.integer == FLARE_NOFADE )
|
|
{
|
|
//draw nofade flares
|
|
SETBOUNDS( mins, maxs, srcRadius );
|
|
CG_Trace( &tr, start, mins, maxs, end,
|
|
entityNum, MASK_SHOT );
|
|
|
|
//flare source occluded
|
|
if( ( tr.fraction < 1.0f || tr.startsolid ) )
|
|
ratio = 0.0f;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
ratio = cent->lfs.lastRatio;
|
|
flare.radius = cent->lfs.lastRadius;
|
|
}
|
|
|
|
cent->lfs.lastRatio = ratio;
|
|
cent->lfs.lastRadius = flare.radius;
|
|
|
|
if( ratio < 1.0f )
|
|
{
|
|
flare.radius *= ratio;
|
|
flare.shaderRGBA[ 3 ] = (byte)( (float)flare.shaderRGBA[ 3 ] * ratio );
|
|
}
|
|
|
|
if( flare.radius <= 0.0f )
|
|
return;
|
|
|
|
trap_R_AddRefEntityToScene( &flare );
|
|
}
|
|
|
|
/*
|
|
=========================
|
|
CG_Lev2ZapChain
|
|
=========================
|
|
*/
|
|
static void CG_Lev2ZapChain( centity_t *cent )
|
|
{
|
|
int i;
|
|
entityState_t *es;
|
|
centity_t *source = NULL, *target = NULL;
|
|
|
|
es = ¢->currentState;
|
|
|
|
for( i = 0; i <= 2; i++ )
|
|
{
|
|
switch( i )
|
|
{
|
|
case 0:
|
|
if( es->time <= 0 )
|
|
continue;
|
|
|
|
source = &cg_entities[ es->powerups ];
|
|
target = &cg_entities[ es->time ];
|
|
break;
|
|
|
|
case 1:
|
|
if( es->time2 <= 0 )
|
|
continue;
|
|
|
|
source = &cg_entities[ es->time ];
|
|
target = &cg_entities[ es->time2 ];
|
|
break;
|
|
|
|
case 2:
|
|
if( es->constantLight <= 0 )
|
|
continue;
|
|
|
|
source = &cg_entities[ es->time2 ];
|
|
target = &cg_entities[ es->constantLight ];
|
|
break;
|
|
}
|
|
|
|
if( !CG_IsTrailSystemValid( ¢->level2ZapTS[ i ] ) )
|
|
cent->level2ZapTS[ i ] = CG_SpawnNewTrailSystem( cgs.media.level2ZapTS );
|
|
|
|
if( CG_IsTrailSystemValid( ¢->level2ZapTS[ i ] ) )
|
|
{
|
|
CG_SetAttachmentCent( ¢->level2ZapTS[ i ]->frontAttachment, source );
|
|
CG_SetAttachmentCent( ¢->level2ZapTS[ i ]->backAttachment, target );
|
|
CG_AttachToCent( ¢->level2ZapTS[ i ]->frontAttachment );
|
|
CG_AttachToCent( ¢->level2ZapTS[ i ]->backAttachment );
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
=========================
|
|
CG_AdjustPositionForMover
|
|
|
|
Also called by client movement prediction code
|
|
=========================
|
|
*/
|
|
void CG_AdjustPositionForMover( const vec3_t in, int moverNum, int fromTime, int toTime, vec3_t out )
|
|
{
|
|
centity_t *cent;
|
|
vec3_t oldOrigin, origin, deltaOrigin;
|
|
vec3_t oldAngles, angles, deltaAngles;
|
|
|
|
if( moverNum <= 0 || moverNum >= ENTITYNUM_MAX_NORMAL )
|
|
{
|
|
VectorCopy( in, out );
|
|
return;
|
|
}
|
|
|
|
cent = &cg_entities[ moverNum ];
|
|
|
|
if( cent->currentState.eType != ET_MOVER )
|
|
{
|
|
VectorCopy( in, out );
|
|
return;
|
|
}
|
|
|
|
BG_EvaluateTrajectory( ¢->currentState.pos, fromTime, oldOrigin );
|
|
BG_EvaluateTrajectory( ¢->currentState.apos, fromTime, oldAngles );
|
|
|
|
BG_EvaluateTrajectory( ¢->currentState.pos, toTime, origin );
|
|
BG_EvaluateTrajectory( ¢->currentState.apos, toTime, angles );
|
|
|
|
VectorSubtract( origin, oldOrigin, deltaOrigin );
|
|
VectorSubtract( angles, oldAngles, deltaAngles );
|
|
|
|
VectorAdd( in, deltaOrigin, out );
|
|
|
|
// FIXME: origin change when on a rotating object
|
|
}
|
|
|
|
|
|
/*
|
|
=============================
|
|
CG_InterpolateEntityPosition
|
|
=============================
|
|
*/
|
|
static void CG_InterpolateEntityPosition( centity_t *cent )
|
|
{
|
|
vec3_t current, next;
|
|
float f;
|
|
|
|
// it would be an internal error to find an entity that interpolates without
|
|
// a snapshot ahead of the current one
|
|
if( cg.nextSnap == NULL )
|
|
CG_Error( "CG_InterpoateEntityPosition: cg.nextSnap == NULL" );
|
|
|
|
f = cg.frameInterpolation;
|
|
|
|
// this will linearize a sine or parabolic curve, but it is important
|
|
// to not extrapolate player positions if more recent data is available
|
|
BG_EvaluateTrajectory( ¢->currentState.pos, cg.snap->serverTime, current );
|
|
BG_EvaluateTrajectory( ¢->nextState.pos, cg.nextSnap->serverTime, next );
|
|
|
|
cent->lerpOrigin[ 0 ] = current[ 0 ] + f * ( next[ 0 ] - current[ 0 ] );
|
|
cent->lerpOrigin[ 1 ] = current[ 1 ] + f * ( next[ 1 ] - current[ 1 ] );
|
|
cent->lerpOrigin[ 2 ] = current[ 2 ] + f * ( next[ 2 ] - current[ 2 ] );
|
|
|
|
BG_EvaluateTrajectory( ¢->currentState.apos, cg.snap->serverTime, current );
|
|
BG_EvaluateTrajectory( ¢->nextState.apos, cg.nextSnap->serverTime, next );
|
|
|
|
cent->lerpAngles[ 0 ] = LerpAngle( current[ 0 ], next[ 0 ], f );
|
|
cent->lerpAngles[ 1 ] = LerpAngle( current[ 1 ], next[ 1 ], f );
|
|
cent->lerpAngles[ 2 ] = LerpAngle( current[ 2 ], next[ 2 ], f );
|
|
|
|
}
|
|
|
|
/*
|
|
===============
|
|
CG_CalcEntityLerpPositions
|
|
|
|
===============
|
|
*/
|
|
static void CG_CalcEntityLerpPositions( centity_t *cent )
|
|
{
|
|
// this will be set to how far forward projectiles will be extrapolated
|
|
int timeshift = 0;
|
|
|
|
// if this player does not want to see extrapolated players
|
|
if( !cg_smoothClients.integer )
|
|
{
|
|
// make sure the clients use TR_INTERPOLATE
|
|
if( cent->currentState.number < MAX_CLIENTS )
|
|
{
|
|
cent->currentState.pos.trType = TR_INTERPOLATE;
|
|
cent->nextState.pos.trType = TR_INTERPOLATE;
|
|
}
|
|
}
|
|
|
|
if( cent->interpolate && cent->currentState.pos.trType == TR_INTERPOLATE )
|
|
{
|
|
CG_InterpolateEntityPosition( cent );
|
|
return;
|
|
}
|
|
|
|
// first see if we can interpolate between two snaps for
|
|
// linear extrapolated clients
|
|
if( cent->interpolate && cent->currentState.pos.trType == TR_LINEAR_STOP &&
|
|
cent->currentState.number < MAX_CLIENTS )
|
|
{
|
|
CG_InterpolateEntityPosition( cent );
|
|
return;
|
|
}
|
|
|
|
if( cg_projectileNudge.integer > 0 &&
|
|
cent->currentState.eType == ET_MISSILE &&
|
|
!( cg.snap->ps.pm_flags & PMF_FOLLOW ) )
|
|
{
|
|
timeshift = cg.ping;
|
|
}
|
|
|
|
// just use the current frame and evaluate as best we can
|
|
BG_EvaluateTrajectory( ¢->currentState.pos,
|
|
( cg.time + timeshift ), cent->lerpOrigin );
|
|
BG_EvaluateTrajectory( ¢->currentState.apos,
|
|
( cg.time + timeshift ), cent->lerpAngles );
|
|
|
|
if( timeshift )
|
|
{
|
|
trace_t tr;
|
|
vec3_t lastOrigin;
|
|
|
|
BG_EvaluateTrajectory( ¢->currentState.pos, cg.time, lastOrigin );
|
|
|
|
CG_Trace( &tr, lastOrigin, vec3_origin, vec3_origin, cent->lerpOrigin,
|
|
cent->currentState.number, MASK_SHOT );
|
|
|
|
// don't let the projectile go through the floor
|
|
if( tr.fraction < 1.0f )
|
|
VectorLerp( tr.fraction, lastOrigin, cent->lerpOrigin, cent->lerpOrigin );
|
|
}
|
|
|
|
// adjust for riding a mover if it wasn't rolled into the predicted
|
|
// player state
|
|
if( cent != &cg.predictedPlayerEntity )
|
|
{
|
|
CG_AdjustPositionForMover( cent->lerpOrigin, cent->currentState.groundEntityNum,
|
|
cg.snap->serverTime, cg.time, cent->lerpOrigin );
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
===============
|
|
CG_CEntityPVSEnter
|
|
|
|
===============
|
|
*/
|
|
static void CG_CEntityPVSEnter( centity_t *cent )
|
|
{
|
|
entityState_t *es = ¢->currentState;
|
|
|
|
if( cg_debugPVS.integer )
|
|
CG_Printf( "Entity %d entered PVS\n", cent->currentState.number );
|
|
|
|
switch( es->eType )
|
|
{
|
|
case ET_MISSILE:
|
|
CG_LaunchMissile( cent );
|
|
break;
|
|
}
|
|
|
|
//clear any particle systems from previous uses of this centity_t
|
|
cent->muzzlePS = NULL;
|
|
cent->muzzlePsTrigger = qfalse;
|
|
cent->jetPackPS = NULL;
|
|
cent->jetPackState = JPS_OFF;
|
|
cent->buildablePS = NULL;
|
|
cent->entityPS = NULL;
|
|
cent->entityPSMissing = qfalse;
|
|
|
|
//make sure that the buildable animations are in a consistent state
|
|
//when a buildable enters the PVS
|
|
cent->buildableAnim = cent->lerpFrame.animationNumber = BANIM_NONE;
|
|
cent->oldBuildableAnim = es->legsAnim;
|
|
}
|
|
|
|
|
|
/*
|
|
===============
|
|
CG_CEntityPVSLeave
|
|
|
|
===============
|
|
*/
|
|
static void CG_CEntityPVSLeave( centity_t *cent )
|
|
{
|
|
int i;
|
|
entityState_t *es = ¢->currentState;
|
|
|
|
if( cg_debugPVS.integer )
|
|
CG_Printf( "Entity %d left PVS\n", cent->currentState.number );
|
|
|
|
switch( es->eType )
|
|
{
|
|
case ET_LEV2_ZAP_CHAIN:
|
|
for( i = 0; i <= 2; i++ )
|
|
{
|
|
if( CG_IsTrailSystemValid( ¢->level2ZapTS[ i ] ) )
|
|
CG_DestroyTrailSystem( ¢->level2ZapTS[ i ] );
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
===============
|
|
CG_AddCEntity
|
|
|
|
===============
|
|
*/
|
|
static void CG_AddCEntity( centity_t *cent )
|
|
{
|
|
// event-only entities will have been dealt with already
|
|
if( cent->currentState.eType >= ET_EVENTS )
|
|
return;
|
|
|
|
// calculate the current origin
|
|
CG_CalcEntityLerpPositions( cent );
|
|
|
|
// add automatic effects
|
|
CG_EntityEffects( cent );
|
|
|
|
switch( cent->currentState.eType )
|
|
{
|
|
default:
|
|
CG_Error( "Bad entity type: %i\n", cent->currentState.eType );
|
|
break;
|
|
|
|
case ET_INVISIBLE:
|
|
case ET_PUSH_TRIGGER:
|
|
case ET_TELEPORT_TRIGGER:
|
|
break;
|
|
|
|
case ET_GENERAL:
|
|
CG_General( cent );
|
|
break;
|
|
|
|
case ET_CORPSE:
|
|
CG_Corpse( cent );
|
|
break;
|
|
|
|
case ET_PLAYER:
|
|
CG_Player( cent );
|
|
break;
|
|
|
|
case ET_BUILDABLE:
|
|
CG_Buildable( cent );
|
|
break;
|
|
|
|
case ET_MISSILE:
|
|
CG_Missile( cent );
|
|
break;
|
|
|
|
case ET_MOVER:
|
|
CG_Mover( cent );
|
|
break;
|
|
|
|
case ET_BEAM:
|
|
CG_Beam( cent );
|
|
break;
|
|
|
|
case ET_PORTAL:
|
|
CG_Portal( cent );
|
|
break;
|
|
|
|
case ET_SPEAKER:
|
|
CG_Speaker( cent );
|
|
break;
|
|
|
|
case ET_PARTICLE_SYSTEM:
|
|
CG_ParticleSystemEntity( cent );
|
|
break;
|
|
|
|
case ET_ANIMMAPOBJ:
|
|
CG_AnimMapObj( cent );
|
|
break;
|
|
|
|
case ET_MODELDOOR:
|
|
CG_ModelDoor( cent );
|
|
break;
|
|
|
|
case ET_LIGHTFLARE:
|
|
CG_LightFlare( cent );
|
|
break;
|
|
|
|
case ET_LEV2_ZAP_CHAIN:
|
|
CG_Lev2ZapChain( cent );
|
|
break;
|
|
}
|
|
}
|
|
|
|
/*
|
|
===============
|
|
CG_AddPacketEntities
|
|
|
|
===============
|
|
*/
|
|
void CG_AddPacketEntities( void )
|
|
{
|
|
int num;
|
|
centity_t *cent;
|
|
playerState_t *ps;
|
|
|
|
// set cg.frameInterpolation
|
|
if( cg.nextSnap )
|
|
{
|
|
int delta;
|
|
|
|
delta = ( cg.nextSnap->serverTime - cg.snap->serverTime );
|
|
|
|
if( delta == 0 )
|
|
cg.frameInterpolation = 0;
|
|
else
|
|
cg.frameInterpolation = (float)( cg.time - cg.snap->serverTime ) / delta;
|
|
}
|
|
else
|
|
{
|
|
cg.frameInterpolation = 0; // actually, it should never be used, because
|
|
// no entities should be marked as interpolating
|
|
}
|
|
|
|
// the auto-rotating items will all have the same axis
|
|
cg.autoAngles[ 0 ] = 0;
|
|
cg.autoAngles[ 1 ] = ( cg.time & 2047 ) * 360 / 2048.0;
|
|
cg.autoAngles[ 2 ] = 0;
|
|
|
|
cg.autoAnglesFast[ 0 ] = 0;
|
|
cg.autoAnglesFast[ 1 ] = ( cg.time & 1023 ) * 360 / 1024.0f;
|
|
cg.autoAnglesFast[ 2 ] = 0;
|
|
|
|
AnglesToAxis( cg.autoAngles, cg.autoAxis );
|
|
AnglesToAxis( cg.autoAnglesFast, cg.autoAxisFast );
|
|
|
|
// generate and add the entity from the playerstate
|
|
ps = &cg.predictedPlayerState;
|
|
BG_PlayerStateToEntityState( ps, &cg.predictedPlayerEntity.currentState, qfalse );
|
|
cg.predictedPlayerEntity.valid = qtrue;
|
|
CG_AddCEntity( &cg.predictedPlayerEntity );
|
|
|
|
// lerp the non-predicted value for lightning gun origins
|
|
CG_CalcEntityLerpPositions( &cg_entities[ cg.snap->ps.clientNum ] );
|
|
|
|
// scanner
|
|
CG_UpdateEntityPositions( );
|
|
|
|
for( num = 0; num < MAX_GENTITIES; num++ )
|
|
cg_entities[ num ].valid = qfalse;
|
|
|
|
// add each entity sent over by the server
|
|
for( num = 0; num < cg.snap->numEntities; num++ )
|
|
{
|
|
cent = &cg_entities[ cg.snap->entities[ num ].number ];
|
|
cent->valid = qtrue;
|
|
}
|
|
|
|
for( num = 0; num < MAX_GENTITIES; num++ )
|
|
{
|
|
cent = &cg_entities[ num ];
|
|
|
|
if( cent->valid && !cent->oldValid )
|
|
CG_CEntityPVSEnter( cent );
|
|
else if( !cent->valid && cent->oldValid )
|
|
CG_CEntityPVSLeave( cent );
|
|
|
|
cent->oldValid = cent->valid;
|
|
}
|
|
|
|
// add each entity sent over by the server
|
|
for( num = 0; num < cg.snap->numEntities; num++ )
|
|
{
|
|
cent = &cg_entities[ cg.snap->entities[ num ].number ];
|
|
CG_AddCEntity( cent );
|
|
}
|
|
|
|
//make an attempt at drawing bounding boxes of selected entity types
|
|
if( cg_drawBBOX.integer )
|
|
{
|
|
for( num = 0; num < cg.snap->numEntities; num++ )
|
|
{
|
|
float x, zd, zu;
|
|
vec3_t mins, maxs;
|
|
entityState_t *es;
|
|
|
|
cent = &cg_entities[ cg.snap->entities[ num ].number ];
|
|
es = ¢->currentState;
|
|
|
|
switch( es->eType )
|
|
{
|
|
case ET_BUILDABLE:
|
|
case ET_MISSILE:
|
|
case ET_CORPSE:
|
|
x = ( es->solid & 255 );
|
|
zd = ( ( es->solid >> 8 ) & 255 );
|
|
zu = ( ( es->solid >> 16 ) & 255 ) - 32;
|
|
|
|
mins[ 0 ] = mins[ 1 ] = -x;
|
|
maxs[ 0 ] = maxs[ 1 ] = x;
|
|
mins[ 2 ] = -zd;
|
|
maxs[ 2 ] = zu;
|
|
|
|
CG_DrawBoundingBox( cent->lerpOrigin, mins, maxs );
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|