q3rally/engine/code/cgame/cg_ents.c
2011-02-18 14:31:32 +00:00

1394 lines
38 KiB
C

/*
===========================================================================
Copyright (C) 1999-2005 Id Software, Inc.
Copyright (C) 2002-2009 Q3Rally Team (Per Thormann - perle@q3rally.com)
This file is part of q3rally source code.
q3rally 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.
q3rally 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 q3rally; 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"
// Q3Rally Code Start
// used to know when to update physics variables from server
//qboolean updateEnts;
// Q3Rally Code END
/*
======================
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 );
}
}
/*
==================
CG_General
==================
*/
static void CG_General( centity_t *cent ) {
refEntity_t ent;
entityState_t *s1;
s1 = &cent->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 = &cent->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
}
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
// Q3Rally Code Start
// if ( ( item->giType == IT_WEAPON ) || ( item->giType == IT_ARMOR ) ) {
if ( ( item->giType == IT_WEAPON ) || ( item->giType == IT_RFWEAPON ) ||
( item->giType == IT_ARMOR ) ) {
// Q3Rally Code END
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
// Q3Rally Code Start
ent.origin[2] += 6;
// Q3Rally Code END
// 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 );
// Q3Rally Code Start
// if ( item->giType == IT_HEALTH || item->giType == IT_POWERUP )
if ( item->giType == IT_HEALTH || item->giType == IT_POWERUP || item->giTag == HI_TURBO)
// Q3Rally Code END
{
if ( ( ent.hModel = cg_items[es->modelindex].models[1] ) != 0 )
{
// Q3Rally Code Start
/*
if ( item->giType == IT_POWERUP )
{
ent.origin[2] += 12;
spinAngles[1] = ( cg.time & 1023 ) * 360 / -1024.0f;
}
*/
if ( item->giType == IT_POWERUP || item->giTag == HI_TURBO)
{
spinAngles[ROLL] = ( (cg.time/2) & 1023 ) * 360 / -1024.0f;
spinAngles[1] = ( (cg.time/2) & 1023 ) * 360 / -1024.0f;
}
// Q3Rally Code END
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 );
}
}
}
// Q3Rally Code Start
if ( !cg_simpleItems.integer )
{
vec3_t spinAngles;
VectorClear ( spinAngles );
if (item->giTag == PW_HASTE){
if ( ( ent.hModel = cg_items[es->modelindex].models[2] ) != 0 )
{
AxisCopy( cg.autoAxis, 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 );
}
}
}
// Q3Rally Code END
}
//============================================================================
/*
===============
CG_Missile
===============
*/
static void CG_Missile( centity_t *cent ) {
refEntity_t ent;
entityState_t *s1;
const weaponInfo_t *weapon;
// int col;
s1 = &cent->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( &cent->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;
}
if ( cent->currentState.weapon == WP_FLAME_THROWER ) {
ent.reType = RT_SPRITE;
ent.radius = 32;
ent.rotation = 0;
ent.customShader = cgs.media.flameBallShader;
trap_R_AddRefEntityToScene( &ent );
return;
}
// Q3Rally Code Start
if (cent->currentState.weapon == RWP_MINE){
if (cgs.gametype >= GT_TEAM){
switch(cgs.clientinfo[cent->currentState.otherEntityNum].team){
default:
case TEAM_RED:
ent.customSkin = trap_R_RegisterSkin( "models/rearfire/red.skin" );
break;
case TEAM_BLUE:
ent.customSkin = trap_R_RegisterSkin( "models/rearfire/blue.skin" );
break;
case TEAM_GREEN:
ent.customSkin = trap_R_RegisterSkin( "models/rearfire/green.skin" );
break;
case TEAM_YELLOW:
ent.customSkin = trap_R_RegisterSkin( "models/rearfire/yellow.skin" );
break;
}
}
else {
ent.skinNum = 0;
}
}
else{
// flicker between two skins
ent.skinNum = cg.clientFrame & 1;
}
//ent.skinNum = cg.clientFrame & 1;
// Q3Rally Code END
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 ) {
// Q3Rally Code Start
if (cent->currentState.weapon == RWP_MINE)
AxisClear(ent.axis);
else
// Q3Rally Code END
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
===============
*/
// Q3Rally Code Start - removed
/*
static void CG_Grapple( centity_t *cent ) {
refEntity_t ent;
entityState_t *s1;
const weaponInfo_t *weapon;
s1 = &cent->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 );
}
*/
/* Q3Rally Code Start
==================
CG_Auxent
car wheel entities
==================
*/
static void CG_Auxent( centity_t *cent ) {
refEntity_t ent;
entityState_t *s1;
s1 = &cent->currentState;
if ( s1->otherEntityNum == cg.snap->ps.clientNum && !cg.newSnap )
return;
cg_entities[s1->otherEntityNum].wheelSpeeds[s1->otherEntityNum2] = s1->apos.trDelta[0];
cg_entities[s1->otherEntityNum].wheelSkidding[s1->otherEntityNum2] = s1->frame;
cg_entities[s1->otherEntityNum].steeringAngle = s1->apos.trDelta[1];
if (s1->otherEntityNum == cg.snap->ps.clientNum){
// Com_Printf("updating wheels\n");
cg.car.sPoints[s1->otherEntityNum2].w = s1->apos.trDelta[0];
cg.car.sPoints[s1->otherEntityNum2].slipping = s1->frame;
cg.car.wheelAngle = s1->apos.trDelta[1];
VectorCopy(s1->pos.trBase, cg.car.sPoints[s1->otherEntityNum2].r);
VectorCopy(s1->pos.trDelta, cg.car.sPoints[s1->otherEntityNum2].v);
VectorCopy(s1->origin2, cg.car.sPoints[s1->otherEntityNum2].normals[0]);
cg.car.sPoints[s1->otherEntityNum2].onGround = s1->groundEntityNum;
}
// 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_Weather
==================
*/
static void CG_Weather( centity_t *cent ) {
entityState_t *s1;
s1 = &cent->currentState;
// CG_EffectParse( "T=RAIN,B=5 10,C=0.5,G=0.5 2,BV=0,GV=0 100,W=1 2,D=300" );
CG_Atmospheric_SetParticles( s1->weapon, s1->powerups, s1->legsAnim );
}
// Q3Rally Code END
/*
===============
CG_Mover
===============
*/
static void CG_Mover( centity_t *cent ) {
refEntity_t ent;
entityState_t *s1;
s1 = &cent->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 = &cent->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 = &cent->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, 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( &cent->currentState.pos, fromTime, oldOrigin );
BG_EvaluateTrajectory( &cent->currentState.apos, fromTime, oldAngles );
BG_EvaluateTrajectory( &cent->currentState.pos, toTime, origin );
BG_EvaluateTrajectory( &cent->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( &cent->currentState.pos, cg.snap->serverTime, current );
BG_EvaluateTrajectory( &cent->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( &cent->currentState.apos, cg.snap->serverTime, current );
BG_EvaluateTrajectory( &cent->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;
// Q3Rally Code Start
cent->currentState.apos.trType = TR_INTERPOLATE;
cent->nextState.apos.trType = TR_INTERPOLATE;
// Q3Rally Code END
}
}
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( &cent->currentState.pos, cg.time, cent->lerpOrigin );
BG_EvaluateTrajectory( &cent->currentState.apos, cg.time, cent->lerpAngles );
// Q3Rally Code Start
/*
if( !cg_paused.integer &&
cent->currentState.number < MAX_CLIENTS &&
cent->currentState.number != cg.snap->ps.clientNum )
{
Com_Printf( "angles %f %f %f\n", cent->lerpAngles[0], cent->lerpAngles[1], cent->lerpAngles[2] );
}
*/
// Q3Rally Code END
// 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
===============
*/
// Q3Rally Code Start
// static void CG_AddCEntity( centity_t *cent ) {
void CG_AddCEntity( centity_t *cent ) {
// Q3Rally Code END
// 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;
// Q3Rally Code Start
case ET_CHECKPOINT:
// Q3Rally Code END
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;
// Q3Rally Code Start
case ET_BREAKGLASS:
CG_Mover( cent );
break;
case ET_BREAKWOOD:
CG_Mover( cent );
break;
case ET_BREAKMETAL:
CG_Mover( cent );
break;
// Q3Rally Code END
case ET_BEAM:
CG_Beam( cent );
break;
case ET_PORTAL:
CG_Portal( cent );
break;
case ET_SPEAKER:
CG_Speaker( cent );
break;
// Q3Rally Code Start
/*
case ET_GRAPPLE:
CG_Grapple( cent );
break;
*/
case ET_AUXENT:
CG_Auxent( cent );
break;
case ET_WEATHER:
CG_Weather( cent );
break;
case ET_SCRIPTED:
CG_Scripted_Object( cent );
break;
// Q3Rally Code END
case ET_TEAM:
CG_TeamBase( cent );
break;
}
}
/*
===============
CG_AddPacketEntities
===============
*/
// Q3Rally Code Start
//static vec3_t lastVel;
//static vec3_t lastAngM;
//static int lastPMType;
// Q3Rally Code END
void CG_AddPacketEntities( void ) {
int num;
centity_t *cent;
playerState_t *ps;
// Q3Rally Code Start
// char value[16];
// int i;
// Q3Rally Code END
// 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 );
}
// Q3Rally Code Start
if( cg_drawBotPaths.integer )
CG_DrawCheckpointLinks();
if ( cg.newSnap )
{
// int weaponTime;
/*
float m[3][3];
float m2[3][3];
vec3_t angles, delta_angles;
float time = 0.5f;
*/
// Com_Printf("updating car body variables\n");
if ( !cg_paused.integer && cg_debugpredict.integer )
Com_Printf( "updateEnts\n" );
/*
if ( !cg_paused.integer )
{
int frontTime;
int rearTime;
int newFrontTime;
int newRearTime;
rearTime = ( cg.predictedPlayerState.weaponTime & REAR_WEAPON_TIME_MASK ) >> 16;
newRearTime = ( cg.snap->ps.weaponTime & REAR_WEAPON_TIME_MASK ) >> 16;
Com_Printf( "cg forward: cur weapon time %i new weap time %i\n", cg.predictedPlayerState.weaponTime & NORMAL_WEAPON_TIME_MASK, cg.snap->ps.weaponTime & NORMAL_WEAPON_TIME_MASK );
Com_Printf( "cg rear: cur weapon time %i new weap time %i\n", rearTime, newRearTime );
Com_Printf( "cg: cur weapon time %i new weap time %i\n", cg.predictedPlayerState.weaponTime, cg.snap->ps.weaponTime );
}
*/
// cg.predictedPlayerState = cg.snap->ps;
// HACK: save the rear weapon time because for some stupid reason q3
// engine fucks that part up when it sends the weapon time.
{
int rearTime = cg.predictedPlayerState.weaponTime & REAR_WEAPON_TIME_MASK;
cg.predictedPlayerState = cg.snap->ps;
cg.predictedPlayerState.weaponTime &= ~REAR_WEAPON_TIME_MASK;
cg.predictedPlayerState.weaponTime |= rearTime;
}
CG_UpdateCarFromPS ( &cg.snap->ps );
/*
m[0][0] = 0; m[0][1] = time * -cg.car.sBody.w[2]; m[0][2] = time * cg.car.sBody.w[1];
m[1][0] = time * cg.car.sBody.w[2]; m[1][1] = 0; m[1][2] = time * -cg.car.sBody.w[0];
m[2][0] = time * -cg.car.sBody.w[1]; m[2][1] = time * cg.car.sBody.w[0]; m[2][2] = 0;
MatrixMultiply(m, cg.car.sBody.t, m2);
MatrixAdd(cg.car.sBody.t, m2, cg.car.tBody.t);
OrthonormalizeOrientation(cg.car.tBody.t);
OrientationToAngles( cg.car.tBody.t, angles );
OrientationToDeltaAngles( cg.car.sBody.t, cg.car.sBody.w, delta_angles );
Com_Printf( "view angles1 (%f %f %f)\n", cg.snap->ps.viewangles[0], cg.snap->ps.viewangles[1], cg.snap->ps.viewangles[2] );
Com_Printf( "delta angles (%f %f %f)\n", delta_angles[0], delta_angles[1], delta_angles[2] );
Com_Printf( "view angles2 (%f %f %f)\n", angles[0], angles[1], angles[2] );
*/
if (!cg_paused.integer){
// Com_Printf("client time %d\n", ps->commandTime);
/* Car
Com_Printf("springStrength %f\n", cg.car.springStrength);
Com_Printf("springMaxLength %f\n", cg.car.springMaxLength);
Com_Printf("springMinLength %f\n", cg.car.springMinLength);
Com_Printf("shockStrength %f\n", cg.car.shockStrength);
Com_Printf("wheelAngle %f\n", cg.car.wheelAngle);
Com_Printf("throttle %f\n", cg.car.throttle);
Com_Printf("gear %d\n", cg.car.gear);
Com_Printf("rpm %f\n", cg.car.rpm);
Com_Printf("aCOF %f\n", cg.car.aCOF);
Com_Printf("sCOF %f\n", cg.car.sCOF);
Com_Printf("kCOF %f\n", cg.car.kCOF);
Com_Printf("dfCOF %f\n", cg.car.dfCOF);
Com_Printf("ewCOF %f\n", cg.car.ewCOF);
Com_Printf("inverseBodyInertiaTensor:\n");
Com_Printf("%f, %f, %f\n", cg.car.inverseBodyInertiaTensor[0][0], cg.car.inverseBodyInertiaTensor[0][1], cg.car.inverseBodyInertiaTensor[0][2]);
Com_Printf("%f, %f, %f\n", cg.car.inverseBodyInertiaTensor[1][0], cg.car.inverseBodyInertiaTensor[1][1], cg.car.inverseBodyInertiaTensor[1][2]);
Com_Printf("%f, %f, %f\n", cg.car.inverseBodyInertiaTensor[2][0], cg.car.inverseBodyInertiaTensor[2][1], cg.car.inverseBodyInertiaTensor[2][2]);
*/
/* Body
Com_Printf("r %f, %f, %f\n", cg.car.sBody.r[0], cg.car.sBody.r[1], cg.car.sBody.r[2]);
Com_Printf("v %f, %f, %f\n", cg.car.sBody.v[0], cg.car.sBody.v[1], cg.car.sBody.v[2]);
Com_Printf("w %f, %f, %f\n", cg.car.sBody.w[0], cg.car.sBody.w[1], cg.car.sBody.w[2]);
Com_Printf("L %f, %f, %f\n", cg.car.sBody.L[0], cg.car.sBody.L[1], cg.car.sBody.L[2]);
Com_Printf("CoM %f, %f, %f\n", cg.car.sBody.CoM[0], cg.car.sBody.CoM[1], cg.car.sBody.CoM[2]);
Com_Printf("t:\n");
Com_Printf("%f, %f, %f\n", cg.car.sBody.t[0][0], cg.car.sBody.t[0][1], cg.car.sBody.t[0][2]);
Com_Printf("%f, %f, %f\n", cg.car.sBody.t[1][0], cg.car.sBody.t[1][1], cg.car.sBody.t[1][2]);
Com_Printf("%f, %f, %f\n", cg.car.sBody.t[2][0], cg.car.sBody.t[2][1], cg.car.sBody.t[2][2]);
*/
/* Point
i = 4;
Com_Printf("r %f, %f, %f\n", cg.car.tPoints[i].r[0], cg.car.tPoints[i].r[1], cg.car.tPoints[i].r[2]);
Com_Printf("v %f, %f, %f\n", cg.car.tPoints[i].v[0], cg.car.tPoints[i].v[1], cg.car.tPoints[i].v[2]);
Com_Printf("w %f\n", cg.car.tPoints[i].w);
Com_Printf("netForce %f, %f, %f\n", cg.car.sPoints[i].netForce[0], cg.car.sPoints[i].netForce[1], cg.car.sPoints[i].netForce[2]);
Com_Printf("netMoment %f\n", cg.car.sPoints[i].netMoment);
Com_Printf("normals %f, %f, %f\n", cg.car.sPoints[i].normals[0][0], cg.car.sPoints[i].normals[0][1], cg.car.sPoints[i].normals[0][2]);
Com_Printf("mass %f\n", cg.car.sPoints[i].mass);
Com_Printf("elasticity %f\n", cg.car.sPoints[i].elasticity);
Com_Printf("kcof %f\n", cg.car.sPoints[i].kcof);
Com_Printf("scof %f\n", cg.car.sPoints[i].scof);
Com_Printf("fluidDensity %f\n", cg.car.sPoints[i].fluidDensity);
Com_Printf("onGround %d\n", cg.car.sPoints[i].onGround);
Com_Printf("slipping %d\n", cg.car.sPoints[i].slipping);
*/
}
}
// VectorCopy(cg.snap->ps.velocity, lastVel);
// VectorCopy(cg.snap->ps.origin, lastAngM);
// lastPMType = cg.snap->ps.pm_type;
cg.newSnap = qfalse;
// Q3Rally Code END
}