mirror of
https://github.com/DrBeef/ioq3quest.git
synced 2024-11-14 08:31:00 +00:00
1044 lines
27 KiB
C
1044 lines
27 KiB
C
/*
|
|
===========================================================================
|
|
Copyright (C) 1999-2005 Id Software, Inc.
|
|
|
|
This file is part of Quake III Arena source code.
|
|
|
|
Quake III Arena source code 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.
|
|
|
|
Quake III Arena source code 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 Quake III Arena source code; 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_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;
|
|
float i, r, g, b;
|
|
|
|
cl = cent->currentState.constantLight;
|
|
r = (float) (cl & 0xFF) / 255.0;
|
|
g = (float) ((cl >> 8) & 0xFF) / 255.0;
|
|
b = (float) ((cl >> 16) & 0xFF) / 255.0;
|
|
i = (float) ((cl >> 24) & 0xFF) * 4.0;
|
|
trap_R_AddLightToScene(cent->lerpOrigin, i, r, g, b);
|
|
}
|
|
|
|
}
|
|
|
|
|
|
/*
|
|
==================
|
|
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_Item
|
|
==================
|
|
*/
|
|
static void CG_Item( centity_t *cent ) {
|
|
refEntity_t ent;
|
|
entityState_t *es;
|
|
gitem_t *item;
|
|
int msec;
|
|
float frac;
|
|
float scale;
|
|
weaponInfo_t *wi;
|
|
|
|
es = ¢->currentState;
|
|
if ( es->modelindex >= bg_numItems ) {
|
|
CG_Error( "Bad item index %i on entity", es->modelindex );
|
|
}
|
|
|
|
// if set to invisible, skip
|
|
if ( !es->modelindex || ( es->eFlags & EF_NODRAW ) ) {
|
|
return;
|
|
}
|
|
|
|
item = &bg_itemlist[ es->modelindex ];
|
|
if ( cg_simpleItems.integer && item->giType != IT_TEAM ) {
|
|
memset( &ent, 0, sizeof( ent ) );
|
|
ent.reType = RT_SPRITE;
|
|
VectorCopy( cent->lerpOrigin, ent.origin );
|
|
ent.radius = 14;
|
|
ent.customShader = cg_items[es->modelindex].icon;
|
|
ent.shaderRGBA[0] = 255;
|
|
ent.shaderRGBA[1] = 255;
|
|
ent.shaderRGBA[2] = 255;
|
|
ent.shaderRGBA[3] = 255;
|
|
trap_R_AddRefEntityToScene(&ent);
|
|
return;
|
|
}
|
|
|
|
// items bob up and down continuously
|
|
scale = 0.005 + cent->currentState.number * 0.00001;
|
|
cent->lerpOrigin[2] += 4 + cos( ( cg.time + 1000 ) * scale ) * 4;
|
|
|
|
memset (&ent, 0, sizeof(ent));
|
|
|
|
// autorotate at one of two speeds
|
|
if ( item->giType == IT_HEALTH ) {
|
|
VectorCopy( cg.autoAnglesFast, cent->lerpAngles );
|
|
AxisCopy( cg.autoAxisFast, ent.axis );
|
|
} else {
|
|
VectorCopy( cg.autoAngles, cent->lerpAngles );
|
|
AxisCopy( cg.autoAxis, ent.axis );
|
|
}
|
|
|
|
wi = NULL;
|
|
// the weapons have their origin where they attatch to player
|
|
// models, so we need to offset them or they will rotate
|
|
// eccentricly
|
|
if ( item->giType == IT_WEAPON ) {
|
|
wi = &cg_weapons[item->giTag];
|
|
cent->lerpOrigin[0] -=
|
|
wi->weaponMidpoint[0] * ent.axis[0][0] +
|
|
wi->weaponMidpoint[1] * ent.axis[1][0] +
|
|
wi->weaponMidpoint[2] * ent.axis[2][0];
|
|
cent->lerpOrigin[1] -=
|
|
wi->weaponMidpoint[0] * ent.axis[0][1] +
|
|
wi->weaponMidpoint[1] * ent.axis[1][1] +
|
|
wi->weaponMidpoint[2] * ent.axis[2][1];
|
|
cent->lerpOrigin[2] -=
|
|
wi->weaponMidpoint[0] * ent.axis[0][2] +
|
|
wi->weaponMidpoint[1] * ent.axis[1][2] +
|
|
wi->weaponMidpoint[2] * ent.axis[2][2];
|
|
|
|
cent->lerpOrigin[2] += 8; // an extra height boost
|
|
}
|
|
|
|
if( item->giType == IT_WEAPON && item->giTag == WP_RAILGUN ) {
|
|
clientInfo_t *ci = &cgs.clientinfo[cg.snap->ps.clientNum];
|
|
Byte4Copy( ci->c1RGBA, ent.shaderRGBA );
|
|
}
|
|
|
|
ent.hModel = cg_items[es->modelindex].models[0];
|
|
|
|
VectorCopy( cent->lerpOrigin, ent.origin);
|
|
VectorCopy( cent->lerpOrigin, ent.oldorigin);
|
|
|
|
ent.nonNormalizedAxes = qfalse;
|
|
|
|
// if just respawned, slowly scale up
|
|
msec = cg.time - cent->miscTime;
|
|
if ( msec >= 0 && msec < ITEM_SCALEUP_TIME ) {
|
|
frac = (float)msec / ITEM_SCALEUP_TIME;
|
|
VectorScale( ent.axis[0], frac, ent.axis[0] );
|
|
VectorScale( ent.axis[1], frac, ent.axis[1] );
|
|
VectorScale( ent.axis[2], frac, ent.axis[2] );
|
|
ent.nonNormalizedAxes = qtrue;
|
|
} else {
|
|
frac = 1.0;
|
|
}
|
|
|
|
// items without glow textures need to keep a minimum light value
|
|
// so they are always visible
|
|
if ( ( item->giType == IT_WEAPON ) ||
|
|
( item->giType == IT_ARMOR ) ) {
|
|
ent.renderfx |= RF_MINLIGHT;
|
|
}
|
|
|
|
// increase the size of the weapons when they are presented as items
|
|
if ( item->giType == IT_WEAPON ) {
|
|
VectorScale( ent.axis[0], 1.5, ent.axis[0] );
|
|
VectorScale( ent.axis[1], 1.5, ent.axis[1] );
|
|
VectorScale( ent.axis[2], 1.5, ent.axis[2] );
|
|
ent.nonNormalizedAxes = qtrue;
|
|
#ifdef MISSIONPACK
|
|
trap_S_AddLoopingSound( cent->currentState.number, cent->lerpOrigin, vec3_origin, cgs.media.weaponHoverSound );
|
|
#endif
|
|
}
|
|
|
|
#ifdef MISSIONPACK
|
|
if ( item->giType == IT_HOLDABLE && item->giTag == HI_KAMIKAZE ) {
|
|
VectorScale( ent.axis[0], 2, ent.axis[0] );
|
|
VectorScale( ent.axis[1], 2, ent.axis[1] );
|
|
VectorScale( ent.axis[2], 2, ent.axis[2] );
|
|
ent.nonNormalizedAxes = qtrue;
|
|
}
|
|
#endif
|
|
|
|
// add to refresh list
|
|
trap_R_AddRefEntityToScene(&ent);
|
|
|
|
#ifdef MISSIONPACK
|
|
if ( item->giType == IT_WEAPON && wi->barrelModel ) {
|
|
refEntity_t barrel;
|
|
|
|
memset( &barrel, 0, sizeof( barrel ) );
|
|
|
|
barrel.hModel = wi->barrelModel;
|
|
|
|
VectorCopy( ent.lightingOrigin, barrel.lightingOrigin );
|
|
barrel.shadowPlane = ent.shadowPlane;
|
|
barrel.renderfx = ent.renderfx;
|
|
|
|
CG_PositionRotatedEntityOnTag( &barrel, &ent, wi->weaponModel, "tag_barrel" );
|
|
|
|
AxisCopy( ent.axis, barrel.axis );
|
|
barrel.nonNormalizedAxes = ent.nonNormalizedAxes;
|
|
|
|
trap_R_AddRefEntityToScene( &barrel );
|
|
}
|
|
#endif
|
|
|
|
// accompanying rings / spheres for powerups
|
|
if ( !cg_simpleItems.integer )
|
|
{
|
|
vec3_t spinAngles;
|
|
|
|
VectorClear( spinAngles );
|
|
|
|
if ( item->giType == IT_HEALTH || item->giType == IT_POWERUP )
|
|
{
|
|
if ( ( ent.hModel = cg_items[es->modelindex].models[1] ) != 0 )
|
|
{
|
|
if ( item->giType == IT_POWERUP )
|
|
{
|
|
ent.origin[2] += 12;
|
|
spinAngles[1] = ( cg.time & 1023 ) * 360 / -1024.0f;
|
|
}
|
|
AnglesToAxis( spinAngles, ent.axis );
|
|
|
|
// scale up if respawning
|
|
if ( frac != 1.0 ) {
|
|
VectorScale( ent.axis[0], frac, ent.axis[0] );
|
|
VectorScale( ent.axis[1], frac, ent.axis[1] );
|
|
VectorScale( ent.axis[2], frac, ent.axis[2] );
|
|
ent.nonNormalizedAxes = qtrue;
|
|
}
|
|
trap_R_AddRefEntityToScene( &ent );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//============================================================================
|
|
|
|
/*
|
|
===============
|
|
CG_Missile
|
|
===============
|
|
*/
|
|
static void CG_Missile( centity_t *cent ) {
|
|
refEntity_t ent;
|
|
entityState_t *s1;
|
|
const weaponInfo_t *weapon;
|
|
// int col;
|
|
|
|
s1 = ¢->currentState;
|
|
if ( s1->weapon >= WP_NUM_WEAPONS ) {
|
|
s1->weapon = 0;
|
|
}
|
|
weapon = &cg_weapons[s1->weapon];
|
|
|
|
// calculate the axis
|
|
VectorCopy( s1->angles, cent->lerpAngles);
|
|
|
|
// add trails
|
|
if ( weapon->missileTrailFunc )
|
|
{
|
|
weapon->missileTrailFunc( cent, weapon );
|
|
}
|
|
/*
|
|
if ( cent->currentState.modelindex == TEAM_RED ) {
|
|
col = 1;
|
|
}
|
|
else if ( cent->currentState.modelindex == TEAM_BLUE ) {
|
|
col = 2;
|
|
}
|
|
else {
|
|
col = 0;
|
|
}
|
|
|
|
// add dynamic light
|
|
if ( weapon->missileDlight ) {
|
|
trap_R_AddLightToScene(cent->lerpOrigin, weapon->missileDlight,
|
|
weapon->missileDlightColor[col][0], weapon->missileDlightColor[col][1], weapon->missileDlightColor[col][2] );
|
|
}
|
|
*/
|
|
// add dynamic light
|
|
if ( weapon->missileDlight ) {
|
|
trap_R_AddLightToScene(cent->lerpOrigin, weapon->missileDlight,
|
|
weapon->missileDlightColor[0], weapon->missileDlightColor[1], weapon->missileDlightColor[2] );
|
|
}
|
|
|
|
// add missile sound
|
|
if ( weapon->missileSound ) {
|
|
vec3_t velocity;
|
|
|
|
BG_EvaluateTrajectoryDelta( ¢->currentState.pos, cg.time, velocity );
|
|
|
|
trap_S_AddLoopingSound( cent->currentState.number, cent->lerpOrigin, velocity, weapon->missileSound );
|
|
}
|
|
|
|
// create the render entity
|
|
memset (&ent, 0, sizeof(ent));
|
|
VectorCopy( cent->lerpOrigin, ent.origin);
|
|
VectorCopy( cent->lerpOrigin, ent.oldorigin);
|
|
|
|
if ( cent->currentState.weapon == WP_PLASMAGUN ) {
|
|
ent.reType = RT_SPRITE;
|
|
ent.radius = 16;
|
|
ent.rotation = 0;
|
|
ent.customShader = cgs.media.plasmaBallShader;
|
|
trap_R_AddRefEntityToScene( &ent );
|
|
return;
|
|
}
|
|
|
|
// flicker between two skins
|
|
ent.skinNum = cg.clientFrame & 1;
|
|
ent.hModel = weapon->missileModel;
|
|
ent.renderfx = weapon->missileRenderfx | RF_NOSHADOW;
|
|
|
|
#ifdef MISSIONPACK
|
|
if ( cent->currentState.weapon == WP_PROX_LAUNCHER ) {
|
|
if (s1->generic1 == TEAM_BLUE) {
|
|
ent.hModel = cgs.media.blueProxMine;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
// convert direction of travel into axis
|
|
if ( VectorNormalize2( s1->pos.trDelta, ent.axis[0] ) == 0 ) {
|
|
ent.axis[0][2] = 1;
|
|
}
|
|
|
|
// spin as it moves
|
|
if ( s1->pos.trType != TR_STATIONARY ) {
|
|
RotateAroundDirection( ent.axis, cg.time / 4 );
|
|
} else {
|
|
#ifdef MISSIONPACK
|
|
if ( s1->weapon == WP_PROX_LAUNCHER ) {
|
|
AnglesToAxis( cent->lerpAngles, ent.axis );
|
|
}
|
|
else
|
|
#endif
|
|
{
|
|
RotateAroundDirection( ent.axis, s1->time );
|
|
}
|
|
}
|
|
|
|
// add to refresh list, possibly with quad glow
|
|
CG_AddRefEntityWithPowerups( &ent, s1, TEAM_FREE );
|
|
}
|
|
|
|
/*
|
|
===============
|
|
CG_Grapple
|
|
|
|
This is called when the grapple is sitting up against the wall
|
|
===============
|
|
*/
|
|
static void CG_Grapple( centity_t *cent ) {
|
|
refEntity_t ent;
|
|
entityState_t *s1;
|
|
const weaponInfo_t *weapon;
|
|
|
|
s1 = ¢->currentState;
|
|
if ( s1->weapon >= WP_NUM_WEAPONS ) {
|
|
s1->weapon = 0;
|
|
}
|
|
weapon = &cg_weapons[s1->weapon];
|
|
|
|
// calculate the axis
|
|
VectorCopy( s1->angles, cent->lerpAngles);
|
|
|
|
#if 0 // FIXME add grapple pull sound here..?
|
|
// add missile sound
|
|
if ( weapon->missileSound ) {
|
|
trap_S_AddLoopingSound( cent->currentState.number, cent->lerpOrigin, vec3_origin, weapon->missileSound );
|
|
}
|
|
#endif
|
|
|
|
// Will draw cable if needed
|
|
CG_GrappleTrail ( cent, weapon );
|
|
|
|
// create the render entity
|
|
memset (&ent, 0, sizeof(ent));
|
|
VectorCopy( cent->lerpOrigin, ent.origin);
|
|
VectorCopy( cent->lerpOrigin, ent.oldorigin);
|
|
|
|
// flicker between two skins
|
|
ent.skinNum = cg.clientFrame & 1;
|
|
ent.hModel = weapon->missileModel;
|
|
ent.renderfx = weapon->missileRenderfx | RF_NOSHADOW;
|
|
|
|
// convert direction of travel into axis
|
|
if ( VectorNormalize2( s1->pos.trDelta, ent.axis[0] ) == 0 ) {
|
|
ent.axis[0][2] = 1;
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
|
|
/*
|
|
=========================
|
|
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;
|
|
//vec3_t 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 ) {
|
|
|
|
// 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;
|
|
}
|
|
|
|
// just use the current frame and evaluate as best we can
|
|
BG_EvaluateTrajectory( ¢->currentState.pos, cg.time, cent->lerpOrigin );
|
|
BG_EvaluateTrajectory( ¢->currentState.apos, cg.time, cent->lerpAngles );
|
|
|
|
// 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_TeamBase
|
|
===============
|
|
*/
|
|
static void CG_TeamBase( centity_t *cent ) {
|
|
refEntity_t model;
|
|
#ifdef MISSIONPACK
|
|
vec3_t angles;
|
|
int t, h;
|
|
float c;
|
|
|
|
if ( cgs.gametype == GT_CTF || cgs.gametype == GT_1FCTF ) {
|
|
#else
|
|
if ( cgs.gametype == GT_CTF) {
|
|
#endif
|
|
// show the flag base
|
|
memset(&model, 0, sizeof(model));
|
|
model.reType = RT_MODEL;
|
|
VectorCopy( cent->lerpOrigin, model.lightingOrigin );
|
|
VectorCopy( cent->lerpOrigin, model.origin );
|
|
AnglesToAxis( cent->currentState.angles, model.axis );
|
|
if ( cent->currentState.modelindex == TEAM_RED ) {
|
|
model.hModel = cgs.media.redFlagBaseModel;
|
|
}
|
|
else if ( cent->currentState.modelindex == TEAM_BLUE ) {
|
|
model.hModel = cgs.media.blueFlagBaseModel;
|
|
}
|
|
else {
|
|
model.hModel = cgs.media.neutralFlagBaseModel;
|
|
}
|
|
trap_R_AddRefEntityToScene( &model );
|
|
}
|
|
#ifdef MISSIONPACK
|
|
else if ( cgs.gametype == GT_OBELISK ) {
|
|
// show the obelisk
|
|
memset(&model, 0, sizeof(model));
|
|
model.reType = RT_MODEL;
|
|
VectorCopy( cent->lerpOrigin, model.lightingOrigin );
|
|
VectorCopy( cent->lerpOrigin, model.origin );
|
|
AnglesToAxis( cent->currentState.angles, model.axis );
|
|
|
|
model.hModel = cgs.media.overloadBaseModel;
|
|
trap_R_AddRefEntityToScene( &model );
|
|
// if hit
|
|
if ( cent->currentState.frame == 1) {
|
|
// show hit model
|
|
// modelindex2 is the health value of the obelisk
|
|
c = cent->currentState.modelindex2;
|
|
model.shaderRGBA[0] = 0xff;
|
|
model.shaderRGBA[1] = c;
|
|
model.shaderRGBA[2] = c;
|
|
model.shaderRGBA[3] = 0xff;
|
|
//
|
|
model.hModel = cgs.media.overloadEnergyModel;
|
|
trap_R_AddRefEntityToScene( &model );
|
|
}
|
|
// if respawning
|
|
if ( cent->currentState.frame == 2) {
|
|
if ( !cent->miscTime ) {
|
|
cent->miscTime = cg.time;
|
|
}
|
|
t = cg.time - cent->miscTime;
|
|
h = (cg_obeliskRespawnDelay.integer - 5) * 1000;
|
|
//
|
|
if (t > h) {
|
|
c = (float) (t - h) / h;
|
|
if (c > 1)
|
|
c = 1;
|
|
}
|
|
else {
|
|
c = 0;
|
|
}
|
|
// show the lights
|
|
AnglesToAxis( cent->currentState.angles, model.axis );
|
|
//
|
|
model.shaderRGBA[0] = c * 0xff;
|
|
model.shaderRGBA[1] = c * 0xff;
|
|
model.shaderRGBA[2] = c * 0xff;
|
|
model.shaderRGBA[3] = c * 0xff;
|
|
|
|
model.hModel = cgs.media.overloadLightsModel;
|
|
trap_R_AddRefEntityToScene( &model );
|
|
// show the target
|
|
if (t > h) {
|
|
if ( !cent->muzzleFlashTime ) {
|
|
trap_S_StartSound (cent->lerpOrigin, ENTITYNUM_NONE, CHAN_BODY, cgs.media.obeliskRespawnSound);
|
|
cent->muzzleFlashTime = 1;
|
|
}
|
|
VectorCopy(cent->currentState.angles, angles);
|
|
angles[YAW] += (float) 16 * acos(1-c) * 180 / M_PI;
|
|
AnglesToAxis( angles, model.axis );
|
|
|
|
VectorScale( model.axis[0], c, model.axis[0]);
|
|
VectorScale( model.axis[1], c, model.axis[1]);
|
|
VectorScale( model.axis[2], c, model.axis[2]);
|
|
|
|
model.shaderRGBA[0] = 0xff;
|
|
model.shaderRGBA[1] = 0xff;
|
|
model.shaderRGBA[2] = 0xff;
|
|
model.shaderRGBA[3] = 0xff;
|
|
//
|
|
model.origin[2] += 56;
|
|
model.hModel = cgs.media.overloadTargetModel;
|
|
trap_R_AddRefEntityToScene( &model );
|
|
}
|
|
else {
|
|
//FIXME: show animated smoke
|
|
}
|
|
}
|
|
else {
|
|
cent->miscTime = 0;
|
|
cent->muzzleFlashTime = 0;
|
|
// modelindex2 is the health value of the obelisk
|
|
c = cent->currentState.modelindex2;
|
|
model.shaderRGBA[0] = 0xff;
|
|
model.shaderRGBA[1] = c;
|
|
model.shaderRGBA[2] = c;
|
|
model.shaderRGBA[3] = 0xff;
|
|
// show the lights
|
|
model.hModel = cgs.media.overloadLightsModel;
|
|
trap_R_AddRefEntityToScene( &model );
|
|
// show the target
|
|
model.origin[2] += 56;
|
|
model.hModel = cgs.media.overloadTargetModel;
|
|
trap_R_AddRefEntityToScene( &model );
|
|
}
|
|
}
|
|
else if ( cgs.gametype == GT_HARVESTER ) {
|
|
// show harvester model
|
|
memset(&model, 0, sizeof(model));
|
|
model.reType = RT_MODEL;
|
|
VectorCopy( cent->lerpOrigin, model.lightingOrigin );
|
|
VectorCopy( cent->lerpOrigin, model.origin );
|
|
AnglesToAxis( cent->currentState.angles, model.axis );
|
|
|
|
if ( cent->currentState.modelindex == TEAM_RED ) {
|
|
model.hModel = cgs.media.harvesterModel;
|
|
model.customSkin = cgs.media.harvesterRedSkin;
|
|
}
|
|
else if ( cent->currentState.modelindex == TEAM_BLUE ) {
|
|
model.hModel = cgs.media.harvesterModel;
|
|
model.customSkin = cgs.media.harvesterBlueSkin;
|
|
}
|
|
else {
|
|
model.hModel = cgs.media.harvesterNeutralModel;
|
|
model.customSkin = 0;
|
|
}
|
|
trap_R_AddRefEntityToScene( &model );
|
|
}
|
|
#endif
|
|
}
|
|
|
|
/*
|
|
===============
|
|
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_PLAYER:
|
|
CG_Player( cent );
|
|
break;
|
|
case ET_ITEM:
|
|
CG_Item( 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_GRAPPLE:
|
|
CG_Grapple( cent );
|
|
break;
|
|
case ET_TEAM:
|
|
CG_TeamBase( 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_AddCEntity( &cg.predictedPlayerEntity );
|
|
|
|
// lerp the non-predicted value for lightning gun origins
|
|
CG_CalcEntityLerpPositions( &cg_entities[ cg.snap->ps.clientNum ] );
|
|
|
|
// 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 );
|
|
}
|
|
}
|
|
|