q3rally/engine/code/cgame/cg_ents.c
zturtleman 6bc3b33eab ioquake3 resync to revision 3522 from 3511.
Fix axis returned by IQM's LerpTag
Calculate bounds for unanimated IQM models
Fix loading favorites as initial source in server browser
Improve finding obelisk entitynum for bot AI
Fix SDL audio playback with surround sound
Fix predicting entity origin on rotating mover
Allow binds to use hex values for all key codes
Disable pulseaudio capture regardless of SDL version
Fix SDL audio playback with 16-bit stereo sound
Make s_info command display channels instead of stereo
Fix cross-compiling using mingw-w64 on Ubuntu 18.04
2018-10-16 17:48:51 +00:00

1455 lines
39 KiB
C

/*
===========================================================================
Copyright (C) 1999-2005 Id Software, Inc.
Copyright (C) 2002-2015 Q3Rally Team (Per Thormann - q3rally@gmail.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 ] );
}
}
else {
trap_S_StopLoopingSound(cent->currentState.number);
}
// 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 = &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
}
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
// 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);
if ( item->giType == IT_WEAPON && wi && wi->barrelModel ) {
refEntity_t barrel;
vec3_t angles;
memset( &barrel, 0, sizeof( barrel ) );
barrel.hModel = wi->barrelModel;
VectorCopy( ent.lightingOrigin, barrel.lightingOrigin );
barrel.shadowPlane = ent.shadowPlane;
barrel.renderfx = ent.renderfx;
angles[YAW] = 0;
angles[PITCH] = 0;
angles[ROLL] = 0;
AnglesToAxis( angles, barrel.axis );
CG_PositionRotatedEntityOnTag( &barrel, &ent, wi->weaponModel, "tag_barrel" );
barrel.nonNormalizedAxes = ent.nonNormalizedAxes;
trap_R_AddRefEntityToScene( &barrel );
}
// 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_CreateRotationMatrix
================
*/
void CG_CreateRotationMatrix(vec3_t angles, vec3_t matrix[3]) {
AngleVectors(angles, matrix[0], matrix[1], matrix[2]);
VectorInverse(matrix[1]);
}
/*
================
CG_TransposeMatrix
================
*/
void CG_TransposeMatrix(vec3_t matrix[3], vec3_t transpose[3]) {
int i, j;
for (i = 0; i < 3; i++) {
for (j = 0; j < 3; j++) {
transpose[i][j] = matrix[j][i];
}
}
}
/*
================
CG_RotatePoint
================
*/
void CG_RotatePoint(vec3_t point, vec3_t matrix[3]) {
vec3_t tvec;
VectorCopy(point, tvec);
point[0] = DotProduct(matrix[0], tvec);
point[1] = DotProduct(matrix[1], tvec);
point[2] = DotProduct(matrix[2], tvec);
}
/*
=========================
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, vec3_t angles_in, vec3_t angles_out) {
centity_t *cent;
vec3_t oldOrigin, origin, deltaOrigin;
vec3_t oldAngles, angles, deltaAngles;
vec3_t matrix[3], transpose[3];
vec3_t org, org2, move2;
if ( moverNum <= 0 || moverNum >= ENTITYNUM_MAX_NORMAL ) {
VectorCopy( in, out );
VectorCopy(angles_in, angles_out);
return;
}
cent = &cg_entities[ moverNum ];
if ( cent->currentState.eType != ET_MOVER ) {
VectorCopy( in, out );
VectorCopy(angles_in, angles_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 );
// origin change when on a rotating object
CG_CreateRotationMatrix( deltaAngles, transpose );
CG_TransposeMatrix( transpose, matrix );
VectorSubtract( in, oldOrigin, org );
VectorCopy( org, org2 );
CG_RotatePoint( org2, matrix );
VectorSubtract( org2, org, move2 );
VectorAdd( deltaOrigin, move2, deltaOrigin );
VectorAdd( in, deltaOrigin, out );
VectorAdd( angles_in, deltaAngles, angles_out );
}
/*
=============================
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, cent->lerpAngles, cent->lerpAngles);
}
}
/*
===============
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 * Q_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", 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
}