/*
===========================================================================
Copyright (C) 1999 - 2005, Id Software, Inc.
Copyright (C) 2000 - 2013, Raven Software, Inc.
Copyright (C) 2001 - 2013, Activision, Inc.
Copyright (C) 2013 - 2015, OpenJK contributors
This file is part of the OpenJK source code.
OpenJK is free software; you can redistribute it and/or modify it
under the terms of the GNU General Public License version 2 as
published by the Free Software Foundation.
This program 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 this program; if not, see .
===========================================================================
*/
// cg_ents.c -- present snapshot entities, happens every single frame
#include "cg_headers.h"
#include "cg_media.h"
#include "../game/g_functions.h"
#include "../ghoul2/G2.h"
#include "FxScheduler.h"
#include "../game/wp_saber.h"
#include "../game/g_vehicles.h"
extern void CG_AddSaberBlade( centity_t *cent, centity_t *scent, refEntity_t *saber, int renderfx, int modelIndex, vec3_t origin, vec3_t angles);
extern void CG_CheckSaberInWater( centity_t *cent, centity_t *scent, int saberNum, int modelIndex, vec3_t origin, vec3_t angles );
extern void CG_ForcePushBlur( const vec3_t org, qboolean darkSide = qfalse );
extern void CG_AddForceSightShell( refEntity_t *ent, centity_t *cent );
extern qboolean CG_PlayerCanSeeCent( centity_t *cent );
extern cvar_t *debug_subdivision;
/*
======================
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
cgi_R_LerpTag( &lerped, parentModel, parent->oldframe, parent->frame,
1.0f - 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, orientation_t *tagOrient ) {
int i;
orientation_t lerped;
vec3_t tempAxis[3];
// lerp the tag
cgi_R_LerpTag( &lerped, parentModel, parent->oldframe, parent->frame,
1.0f - parent->backlerp, tagName );
if ( tagOrient )
{
VectorCopy( lerped.origin, tagOrient->origin );
for ( i = 0 ; i < 3 ; i++ )
{
VectorCopy( lerped.axis[i], tagOrient->axis[i] );
}
}
// 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 );
}
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
======================
*/
vec3_t *CG_SetEntitySoundPosition( centity_t *cent ) {
static vec3_t v3Return;
if ( cent->currentState.solid == SOLID_BMODEL ) {
vec3_t origin;
float *v;
v = cgs.inlineModelMidpoints[ cent->currentState.modelindex ];
VectorAdd( cent->lerpOrigin, v, origin );
cgi_S_UpdateEntityPosition( cent->currentState.number, origin );
VectorCopy(origin, v3Return);
} else {
if ( cent->currentState.eType == ET_PLAYER
&& cent->gent
&& cent->gent->client
&& cent->gent->ghoul2.IsValid()
&& cent->gent->ghoul2[0].animModelIndexOffset )//If it has an animOffset it's a cinematic anim
{//I might be running out of my bounding box, so use my headPoint from the last render frame...?
//NOTE: if I'm not rendered, will this not update correctly? Would cent->lerpOrigin be any more updated?
VectorCopy(cent->gent->client->renderInfo.eyePoint, v3Return);
}
else
{//just use my org
VectorCopy(cent->lerpOrigin, v3Return);
}
cgi_S_UpdateEntityPosition( cent->currentState.number, v3Return );
}
return &v3Return;
}
/*
==================
CG_EntityEffects
Add continuous entity effects, like local entity emission and lighting
==================
*/
static void CG_EntityEffects( centity_t *cent ) {
// update sound origins
vec3_t v3Origin;
VectorCopy(*CG_SetEntitySoundPosition( cent ),v3Origin);
// add loop sound
if ( cent->currentState.loopSound )
{
soundChannel_t chan = CHAN_AUTO;
gentity_t *ent = cent->gent;
if ( ent->s.eFlags & EF_LESS_ATTEN )
{
chan = CHAN_LESS_ATTEN;
}
sfxHandle_t sfx = ( cent->currentState.eType == ET_MOVER ) ? cent->currentState.loopSound : cgs.sound_precache[ cent->currentState.loopSound ];
// Only play sound if being drawn.
if ( !( ent->s.eFlags & EF_NODRAW ) )
{
cgi_S_AddLoopingSound( cent->currentState.number, v3Origin/*cent->lerpOrigin*/, vec3_origin, sfx, chan );
}
}
// 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;
cgi_R_AddLightToScene( cent->lerpOrigin, i, r, g, b );
}
}
void CG_AddRefEntWithTransportEffect ( centity_t *cent, refEntity_t *ent )
{
// We are a normal thing....
cgi_R_AddRefEntityToScene (ent);
if ( ent->renderfx & RF_PULSATE && cent->gent->owner && cent->gent->owner->health &&
!cent->gent->owner->s.number && cent->gent->owner->client && //only for player
cent->gent->owner->client->ps.saberEntityState == SES_RETURNING &&
cent->currentState.saberActive == qfalse )
{
// if we are the saber and we have been dropped, do a glow so it can be spotted easier
float wv;
vec3_t org;
ent->customShader = cgi_R_RegisterShader( "gfx/effects/solidWhite_cull" );
ent->renderfx = RF_RGB_TINT;
wv = sin( cg.time * 0.003f ) * 0.08f + 0.1f;
ent->shaderRGBA[0] = wv * 255;
ent->shaderRGBA[1] = wv * 255;
ent->shaderRGBA[2] = wv * 0;
cgi_R_AddRefEntityToScene (ent);
for ( int i = -4; i < 10; i += 1 )
{
VectorMA( ent->origin, -i, ent->axis[2], org );
FX_AddSprite( org, NULL, NULL, 5.5f, 5.5f, wv, wv, 0.0f, 0.0f, 1.0f, cgs.media.yellowDroppedSaberShader, 0x08000000 );
}
if ( cent->gent->owner->s.weapon == WP_SABER )
{//he's still controlling me
FX_AddSprite( cent->gent->owner->client->renderInfo.handRPoint, NULL, NULL, 8.0f, 8.0f, wv, wv, 0.0f, 0.0f, 1.0f, cgs.media.yellowDroppedSaberShader, 0x08000000 );
}
}
}
/*
Ghoul2 Insert Start
*/
// Copy the ghoul2 data into the ref ent correctly
void CG_SetGhoul2Info( refEntity_t *ent, centity_t *cent)
{
ent->ghoul2 = ¢->gent->ghoul2;
VectorCopy( cent->currentState.modelScale, ent->modelScale);
ent->radius = cent->currentState.radius;
VectorCopy (cent->lerpAngles, ent->angles);
}
// write in the axis and stuff
void G2_BoltToGhoul2Model(centity_t *cent, refEntity_t *ent)
{
// extract the wraith ID from the bolt info
int modelNum = cent->currentState.boltInfo >> MODEL_SHIFT;
modelNum &= MODEL_AND;
int boltNum = cent->currentState.boltInfo >> BOLT_SHIFT;
boltNum &= BOLT_AND;
int entNum = cent->currentState.boltInfo >> ENTITY_SHIFT;
entNum &= ENTITY_AND;
mdxaBone_t boltMatrix;
// go away and get me the bolt position for this frame please
gi.G2API_GetBoltMatrix(cent->gent->ghoul2, modelNum, boltNum, &boltMatrix, cg_entities[entNum].currentState.angles, cg_entities[entNum].currentState.origin, cg.time, cgs.model_draw, cent->currentState.modelScale);
// set up the axis and origin we need for the actual effect spawning
ent->origin[0] = boltMatrix.matrix[0][3];
ent->origin[1] = boltMatrix.matrix[1][3];
ent->origin[2] = boltMatrix.matrix[2][3];
ent->axis[0][0] = boltMatrix.matrix[0][0];
ent->axis[0][1] = boltMatrix.matrix[1][0];
ent->axis[0][2] = boltMatrix.matrix[2][0];
ent->axis[1][0] = boltMatrix.matrix[0][1];
ent->axis[1][1] = boltMatrix.matrix[1][1];
ent->axis[1][2] = boltMatrix.matrix[2][1];
ent->axis[2][0] = boltMatrix.matrix[0][2];
ent->axis[2][1] = boltMatrix.matrix[1][2];
ent->axis[2][2] = boltMatrix.matrix[2][2];
}
void ScaleModelAxis(refEntity_t *ent)
{ // scale the model should we need to
if (ent->modelScale[0] && ent->modelScale[0] != 1.0f)
{
VectorScale( ent->axis[0], ent->modelScale[0] , ent->axis[0] );
ent->nonNormalizedAxes = qtrue;
}
if (ent->modelScale[1] && ent->modelScale[1] != 1.0f)
{
VectorScale( ent->axis[1], ent->modelScale[1] , ent->axis[1] );
ent->nonNormalizedAxes = qtrue;
}
if (ent->modelScale[2] && ent->modelScale[2] != 1.0f)
{
VectorScale( ent->axis[2], ent->modelScale[2] , ent->axis[2] );
ent->nonNormalizedAxes = qtrue;
}
}
/*
Ghoul2 Insert End
*/
/*
==================
CG_General
==================
*/
static void CG_General( centity_t *cent )
{
refEntity_t ent;
entityState_t *s1;
s1 = ¢->currentState;
/*
Ghoul2 Insert Start
*/
// if set to invisible, skip
if (!s1->modelindex && !cent->gent->ghoul2.IsValid() ) {
return;
}
/*
Ghoul2 Insert End
*/
if ( s1->eFlags & EF_NODRAW )
{
// If you don't like it doing NODRAW, then don't set the flag
return;
}
memset (&ent, 0, sizeof(ent));
// set frame
if ( cent->currentState.eFlags & EF_DISABLE_SHADER_ANIM )
{
// by setting the shader time to the current time, we can force an animating shader to not animate
ent.shaderTime = cg.time * 0.001f;
}
if ( s1->eFlags & EF_SHADER_ANIM )
{
// Deliberately setting it up so that shader anim will completely override any kind of model animation frame setting.
ent.renderfx|=RF_SETANIMINDEX;
ent.skinNum = s1->frame;
}
else if ( s1->eFlags & EF_ANIM_ONCE )
{
//s1->frame++;
//ent.frame = s1->frame;
ent.frame = cent->gent->s.frame;
ent.renderfx|=RF_CAP_FRAMES;
}
else if ( s1->eFlags & EF_ANIM_ALLFAST )
{
ent.frame = (cg.time / 100);
ent.renderfx|=RF_WRAP_FRAMES;
}
else
{
ent.frame = s1->frame;
}
ent.oldframe = ent.frame;
ent.backlerp = 0;
/*
Ghoul2 Insert Start
*/
CG_SetGhoul2Info(&ent, cent);
/*
Ghoul2 Insert End
*/
VectorCopy( cent->lerpOrigin, ent.origin);
VectorCopy( cent->lerpOrigin, ent.oldorigin);
ent.hModel = cgs.model_draw[s1->modelindex];
if ( !ent.radius )
{// Set default g2 cull radius.
ent.radius = 60;
}
if ( s1->eFlags & EF_AUTO_SIZE && cent->gent )
{
cgi_R_ModelBounds( ent.hModel, cent->gent->mins, cent->gent->maxs );
//Only do this once
cent->gent->s.eFlags &= ~EF_AUTO_SIZE;
}
// player model
if (s1->number == cg.snap->ps.clientNum)
{
ent.renderfx |= RF_THIRD_PERSON; // only draw from mirrors
}
/*
Ghoul2 Insert Start
*/
// are we bolted to a Ghoul2 model?
if (s1->boltInfo)
{
G2_BoltToGhoul2Model(cent, &ent);
}
else
{
//-------------------------------------------------------
// Start of chair
//-------------------------------------------------------
if ( cent->gent->s.weapon == WP_EMPLACED_GUN || ( cent->gent->activator && cent->gent->activator->owner &&
cent->gent->activator->s.eFlags & EF_LOCKED_TO_WEAPON ))
{
vec3_t temp;
if ( cent->gent->health <= 0 && cent->gent->e_ThinkFunc == thinkF_NULL )
{
if ( !cent->gent->bounceCount )
{//not an EWeb
ent.customShader = cgi_R_RegisterShader( "models/map_objects/imp_mine/turret_chair_dmg" );
}
VectorSet( temp, 0, 0, 1 );
// add a big scorch mark under the gun
CG_ImpactMark( cgs.media.scavMarkShader, cent->lerpOrigin, temp,
0, 1,1,1, 1.0f, qfalse, 92, qtrue );
CG_ImpactMark( cgs.media.scavMarkShader, cent->lerpOrigin, temp,
90, 1,1,1, 1.0f, qfalse, 48, qtrue );
}
else
{
VectorSet( temp, 0, 0, 1 );
if ( !( cent->gent->svFlags & SVF_INACTIVE ))
{
if ( !cent->gent->bounceCount )
{//not an EWeb
ent.customShader = cgi_R_RegisterShader( "models/map_objects/imp_mine/turret_chair_on" );
}
}
// shadow under the gun
CG_ImpactMark( cgs.media.shadowMarkShader, cent->lerpOrigin, temp,
0, 1,1,1, 1.0f, qfalse, 32, qtrue );
}
}
if ( cent->gent->activator && cent->gent->activator->owner &&
cent->gent->activator->s.eFlags & EF_LOCKED_TO_WEAPON &&
cent->gent->activator->owner->s.number == cent->currentState.number ) // gun number must be same as current entities number
{
centity_t *cc = &cg_entities[cent->gent->activator->s.number];
const weaponData_t *wData = NULL;
if ( cc->currentState.weapon )
{
wData = &weaponData[cc->currentState.weapon];
}
if ( !( cc->currentState.eFlags & EF_FIRING ) && !( cc->currentState.eFlags & EF_ALT_FIRING ))
{
// not animating..pausing was leaving the barrels in a bad state
gi.G2API_PauseBoneAnim( ¢->gent->ghoul2[cent->gent->playerModel], "model_root", cg.time );
// gi.G2API_SetBoneAnimIndex( ¢->gent->ghoul2[cent->gent->playerModel], cent->gent->rootBone,
// 0, 0, BONE_ANIM_OVERRIDE, 1.0f, cg.time );
}
// get alternating muzzle end bolts
int bolt = cent->gent->handRBolt;
mdxaBone_t boltMatrix;
if ( !cc->gent->fxID || bolt == -1 )
{
bolt = cent->gent->handLBolt;
}
if ( bolt == -1 )
{
bolt = 0;
}
gi.G2API_GetBoltMatrix( cent->gent->ghoul2, 0, bolt,
&boltMatrix, cent->lerpAngles, cent->lerpOrigin, cg.time,
cgs.model_draw, cent->currentState.modelScale );
// store the muzzle point and direction so that we can fire in the right direction
gi.G2API_GiveMeVectorFromMatrix( boltMatrix, ORIGIN, cc->gent->client->renderInfo.muzzlePoint );
if ( cent->gent->bounceCount )
{//EWeb - *sigh* the muzzle tag on this is not aligned like th eone on the emplaced gun... consistency anyone...?
gi.G2API_GiveMeVectorFromMatrix( boltMatrix, NEGATIVE_X, cc->gent->client->renderInfo.muzzleDir );
}
else
{//Emplaced gun
gi.G2API_GiveMeVectorFromMatrix( boltMatrix, POSITIVE_Y, cc->gent->client->renderInfo.muzzleDir );
}
cc->gent->client->renderInfo.mPCalcTime = cg.time;
// HACK: adding in muzzle flashes
if ( cc->muzzleFlashTime > 0 && wData )
{
const char *effect = NULL;
cc->muzzleFlashTime = 0;
// Try and get a default muzzle so we have one to fall back on
if ( wData->mMuzzleEffect[0] )
{
effect = &wData->mMuzzleEffect[0];
}
if ( cc->currentState.eFlags & EF_ALT_FIRING )
{
// We're alt-firing, so see if we need to override with a custom alt-fire effect
if ( wData->mAltMuzzleEffect[0] )
{
effect = &wData->mAltMuzzleEffect[0];
}
}
if ( cc->currentState.eFlags & EF_FIRING || cc->currentState.eFlags & EF_ALT_FIRING )
{
if ( cent->gent->bounceCount )
{//EWeb
gi.G2API_SetBoneAnimIndex( ¢->gent->ghoul2[cent->gent->playerModel], cent->gent->rootBone,
2, 4, BONE_ANIM_OVERRIDE_FREEZE, 0.6f, cg.time, -1, -1 );
}
else
{//Emplaced Gun
gi.G2API_SetBoneAnimIndex( ¢->gent->ghoul2[cent->gent->playerModel], cent->gent->rootBone,
0, 3, BONE_ANIM_OVERRIDE_FREEZE, 0.6f, cg.time, -1, -1 );
}
if ( effect )
{
// We got an effect and we're firing, so let 'er rip.
theFxScheduler.PlayEffect( effect, cc->gent->client->renderInfo.muzzlePoint,
cc->gent->client->renderInfo.muzzleDir );
}
}
}
VectorCopy( cent->gent->s.apos.trBase, cent->lerpAngles );
}
//-------------------------------------------------------
// End of chair
//-------------------------------------------------------
AnglesToAxis( cent->lerpAngles, ent.axis );
}
//copy modelscale, if any
VectorCopy( cent->currentState.modelScale, ent.modelScale );
//apply modelscale, if any
ScaleModelAxis(&ent);
if (cent->gent->ghoul2.size())
{
//FIXME: use a flag for this, not a strcmp
if ( cent->gent->classname && Q_stricmp( "limb", cent->gent->classname ) == 0 )
{//limb, copy RGB
ent.shaderRGBA[0] = cent->gent->startRGBA[0];
ent.shaderRGBA[1] = cent->gent->startRGBA[1];
ent.shaderRGBA[2] = cent->gent->startRGBA[2];
}
if ( s1->weapon == WP_SABER && cent->gent && cent->gent->owner && cent->gent->owner->inuse )
{//flying lightsaber
//FIXME: better way to tell what it is would be nice
if ( cent->gent->classname && !Q_stricmp( "limb", cent->gent->classname ) )
{//limb, just add blade
if ( cent->gent->owner->client )
{
if ( cent->gent->owner->client->ps.saber[0].Length() > 0 )
{
CG_AddSaberBlade( &cg_entities[cent->gent->owner->s.number],
&cg_entities[cent->gent->s.number], NULL, ent.renderfx,
cent->gent->weaponModel[0], cent->lerpOrigin, cent->lerpAngles );
}
else if ( cent->gent->owner->client->ps.saberEventFlags & SEF_INWATER )
{
CG_CheckSaberInWater( &cg_entities[cent->gent->owner->s.number],
&cg_entities[cent->gent->s.number], 0, cent->gent->weaponModel[0],
cent->lerpOrigin, cent->lerpAngles );
}
}
}
else
{//thrown saber
//light? sound?
if ( cent->gent->owner->client && g_entities[cent->currentState.otherEntityNum].client && g_entities[cent->currentState.otherEntityNum].client->ps.saber[0].Active() )
{//saber is in-flight and active, play a sound on it
if ( cent->gent->owner->client->ps.saberEntityState == SES_RETURNING
&& cent->gent->owner->client->ps.saber[0].type != SABER_STAR )
{
cgi_S_AddLoopingSound( cent->currentState.number,
cent->lerpOrigin, vec3_origin,
cgs.sound_precache[g_entities[cent->currentState.clientNum].client->ps.saber[0].soundLoop] );
}
else
{
int spinSound;
if ( cent->gent->owner->client->ps.saber[0].spinSound
&& cgs.sound_precache[cent->gent->owner->client->ps.saber[0].spinSound] )
{
spinSound = cgs.sound_precache[cent->gent->owner->client->ps.saber[0].spinSound];
}
else if ( cent->gent->owner->client->ps.saber[0].type == SABER_SITH_SWORD )
{
spinSound = cgi_S_RegisterSound( "sound/weapons/saber/saberspinoff.wav" );
}
else
{
switch ( cent->gent->owner->client->ps.forcePowerLevel[FP_SABERTHROW] )
{
case FORCE_LEVEL_1:
spinSound = cgi_S_RegisterSound( "sound/weapons/saber/saberspin3.wav" );
break;
case FORCE_LEVEL_2:
spinSound = cgi_S_RegisterSound( "sound/weapons/saber/saberspin2.wav" );
break;
default:
case FORCE_LEVEL_3:
spinSound = cgi_S_RegisterSound( "sound/weapons/saber/saberspin1.wav" );
break;
}
}
cgi_S_AddLoopingSound( cent->currentState.number, cent->lerpOrigin,
vec3_origin, spinSound );
/*
if ( cg_weapons[WP_SABER].missileSound )
{
cgi_S_AddLoopingSound( cent->currentState.number, cent->lerpOrigin, vec3_origin, cg_weapons[WP_SABER].missileSound );
}
*/
}
}
if ( cent->gent->owner->client )
{
if ( cent->gent->owner->client->ps.saber[0].Length() > 0 )
{//only add the blade if it's on
CG_AddSaberBlade( &cg_entities[cent->gent->owner->s.number],
&cg_entities[cent->gent->s.number], NULL, ent.renderfx,
0, cent->lerpOrigin, cent->lerpAngles );
}
else if ( cent->gent->owner->client->ps.saberEventFlags & SEF_INWATER )
{
CG_CheckSaberInWater( &cg_entities[cent->gent->owner->s.number],
&cg_entities[cent->gent->s.number], 0, 0, cent->lerpOrigin,
cent->lerpAngles );
}
}
if ( cent->gent->owner->health )
{
//make sure we can always be seen
ent.renderfx |= RF_PULSATE;
}
}
}
}
else
{
if ( s1->weapon == WP_SABER && cent->gent && cent->gent->owner )
{//flying lightsaber
//light? sound?
if ( cent->gent->owner->client && cent->currentState.saberActive )
{//saber is in-flight and active, play a sound on it
if ( cent->gent->owner->client->ps.saberEntityState == SES_RETURNING
&& cent->gent->owner->client->ps.saber[0].type != SABER_STAR )
{
if ( cg_weapons[WP_SABER].firingSound )
{
cgi_S_AddLoopingSound( cent->currentState.number,
cent->lerpOrigin, vec3_origin, cg_weapons[WP_SABER].firingSound );
}
}
else
{
int spinSound;
if ( cent->gent->owner->client->ps.saber[0].spinSound
&& cgs.sound_precache[cent->gent->owner->client->ps.saber[0].spinSound] )
{
spinSound = cgs.sound_precache[cent->gent->owner->client->ps.saber[0].spinSound];
}
else if ( cent->gent->owner->client->ps.saber[0].type == SABER_SITH_SWORD )
{
spinSound = cgi_S_RegisterSound( "sound/weapons/saber/saberspinoff.wav" );
}
else
{
switch ( cent->gent->owner->client->ps.forcePowerLevel[FP_SABERTHROW] )
{
case FORCE_LEVEL_1:
spinSound = cgi_S_RegisterSound( "sound/weapons/saber/saberspin3.wav" );
break;
case FORCE_LEVEL_2:
spinSound = cgi_S_RegisterSound( "sound/weapons/saber/saberspin2.wav" );
break;
default:
case FORCE_LEVEL_3:
spinSound = cgi_S_RegisterSound( "sound/weapons/saber/saberspin1.wav" );
break;
}
}
cgi_S_AddLoopingSound( cent->currentState.number, cent->lerpOrigin,
vec3_origin, spinSound );
/*
if ( cg_weapons[WP_SABER].missileSound )
{
cgi_S_AddLoopingSound( cent->currentState.number, cent->lerpOrigin, vec3_origin, cg_weapons[WP_SABER].missileSound );
}
*/
}
}
CG_AddSaberBlade( &cg_entities[cent->gent->owner->s.number],
NULL, &ent, ent.renderfx, 0, NULL, NULL );
if ( cent->gent->owner->health )
{
//make sure we can always be seen
ent.renderfx |= RF_PULSATE;
}
}
}
/*
Ghoul2 Insert End
*/
if ( cent->gent && cent->gent->forcePushTime > cg.time )
{//FIXME: if I'm a rather large model, this will look kind of stupid...
CG_ForcePushBlur( cent->lerpOrigin );
}
CG_AddRefEntWithTransportEffect( cent, &ent );
if ( cent->gent->health <= 0
&& cent->gent->s.weapon == WP_EMPLACED_GUN
&& cent->gent->e_ThinkFunc )
{
// make the gun pulse red to warn about it exploding
float val = (1.0f - (float)(cent->gent->nextthink - cg.time) / 3200.0f ) * 0.3f;
ent.customShader = cgi_R_RegisterShader( "gfx/effects/solidWhite" );
ent.shaderRGBA[0] = (sin( cg.time * 0.04f ) * val * 0.4f + val) * 255;
ent.shaderRGBA[1] = ent.shaderRGBA[2] = 0;
ent.renderfx |= RF_RGB_TINT;
cgi_R_AddRefEntityToScene( &ent );
}
//--------------------------
if ( s1->eFlags & EF_FIRING && cent->gent->inuse )
{
//special code for adding the beam to the attached tripwire mine
vec3_t beamOrg;
int handle = 0;
SEffectTemplate *temp;
VectorMA( ent.origin, 6.6f, ent.axis[0], beamOrg );// forward
// overriding the effect, so give us a copy first
temp = theFxScheduler.GetEffectCopy( "tripMine/laser", &handle );
if ( temp )
{
// have a copy, so get the line element out of there
CPrimitiveTemplate *prim = theFxScheduler.GetPrimitiveCopy( temp, "line1" );
if ( prim )
{
// we have the primitive, so modify the endpoint
prim->mOrigin2X.SetRange( cent->gent->pos4[0], cent->gent->pos4[0] );
prim->mOrigin2Y.SetRange( cent->gent->pos4[1], cent->gent->pos4[1] );
prim->mOrigin2Z.SetRange( cent->gent->pos4[2], cent->gent->pos4[2] );
// have a copy, so get the line element out of there
CPrimitiveTemplate *prim = theFxScheduler.GetPrimitiveCopy( temp, "line2" );
if ( prim )
{
// we have the primitive, so modify the cent->gent->pos3point
prim->mOrigin2X.SetRange( cent->gent->pos4[0], cent->gent->pos4[0] );
prim->mOrigin2Y.SetRange( cent->gent->pos4[1], cent->gent->pos4[1] );
prim->mOrigin2Z.SetRange( cent->gent->pos4[2], cent->gent->pos4[2] );
// play the modified effect
theFxScheduler.PlayEffect( handle, beamOrg, ent.axis[0] );
}
}
}
theFxScheduler.PlayEffect( "tripMine/laserImpactGlow", cent->gent->pos4, ent.axis[0] );
}
if ( s1->eFlags & EF_PROX_TRIP )
{
//special code for adding the glow end to proximity tripmine
vec3_t beamOrg;
VectorMA( ent.origin, 6.6f, ent.axis[0], beamOrg );// forward
theFxScheduler.PlayEffect( "tripMine/glowBit", beamOrg, ent.axis[0] );
}
if ( s1->eFlags & EF_ALT_FIRING )
{
// hack for the spotlight
vec3_t org, axis[3], dir;
AngleVectors( cent->lerpAngles, dir, NULL, NULL );
CG_GetTagWorldPosition( &ent, "tag_flash", org, axis );
theFxScheduler.PlayEffect( "env/light_cone", org, axis[0] );
VectorMA( cent->lerpOrigin, cent->gent->radius - 5, dir, org ); // stay a bit back from the impact point...this may not be enough?
cgi_R_AddLightToScene( org, 225, 1.0f, 1.0f, 1.0f );
}
//-----------------------------------------------------------
if ( cent->gent->flags & (FL_DMG_BY_HEAVY_WEAP_ONLY | FL_SHIELDED ))
{
// Dumb assumption, but I guess we must be a shielded ion_cannon?? We should probably verify
// if it's an ion_cannon that's Heavy Weapon only, we don't want to make it shielded do we...?
if ( (!strcmp( "misc_ion_cannon", cent->gent->classname )) && (cent->gent->flags & FL_SHIELDED ))
{
// must be doing "pain"....er, impact
if ( cent->gent->painDebounceTime > cg.time )
{
float t = (float)(cent->gent->painDebounceTime - cg.time ) / 1000.0f;
// Only display when we have damage
if ( t >= 0.0f && t <= 1.0f )
{
t *= Q_flrand(0.0f, 1.0f);
ent.shaderRGBA[0] = ent.shaderRGBA[1] = ent.shaderRGBA[2] = 255.0f * t;
ent.shaderRGBA[3] = 255;
ent.renderfx &= ~RF_ALPHA_FADE;
ent.renderfx |= RF_RGB_TINT;
ent.customShader = cgi_R_RegisterShader( "gfx/misc/ion_shield" );
cgi_R_AddRefEntityToScene( &ent );
}
}
}
}
//draw force sight shell around it, too
if ((cg.snap->ps.forcePowersActive & (1 << FP_SEE))
&& cg.snap->ps.clientNum != cent->currentState.number
&& CG_PlayerCanSeeCent( cent ) )
{//so player can see dark missiles/explosives
if ( s1->weapon == WP_THERMAL
|| s1->weapon == WP_DET_PACK
|| s1->weapon == WP_TRIP_MINE
|| (cent->gent&¢->gent->e_UseFunc==useF_ammo_power_converter_use)
|| (cent->gent&¢->gent->e_UseFunc==useF_shield_power_converter_use)
|| (s1->eFlags&EF_FORCE_VISIBLE) )
{//really, we only need to do this for things like thermals, detpacks and tripmines, no?
CG_AddForceSightShell( &ent, cent );
}
}
}
/*
==================
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;
}
cgi_S_StartSound (NULL, cent->currentState.number, CHAN_ITEM, cgs.sound_precache[cent->currentState.eventParm] );
// ent->s.frame = ent->wait * 10;
// ent->s.clientNum = ent->random * 10;
cent->miscTime = (int)(cg.time + cent->currentState.frame * 100 + cent->currentState.clientNum * 100 * Q_flrand(-1.0f, 1.0f));
}
/*
==================
CG_Item
==================
*/
static void CG_Item( centity_t *cent )
{
refEntity_t ent;
entityState_t *es;
gitem_t *item;
// int msec;
// float frac;
float scale;
es = ¢->currentState;
if ( es->modelindex >= bg_numItems )
{
CG_Error( "Bad item index %i on entity", es->modelindex );
}
/*
Ghoul2 Insert Start
*/
// if set to invisible, skip
if ( (!es->modelindex && !cent->gent->ghoul2.IsValid() ) || ( es->eFlags & EF_NODRAW ) )
{
return;
}
/*
Ghoul2 Insert End
*/
if ( cent->gent && !cent->gent->inuse )
{
// Yeah, I know....items were being freed on touch, but it could still get here and draw incorrectly...
return;
}
item = &bg_itemlist[ es->modelindex ];
if ( cg_simpleItems.integer )
{
memset( &ent, 0, sizeof( ent ) );
ent.reType = RT_SPRITE;
VectorCopy( cent->lerpOrigin, ent.origin );
ent.origin[2] += 16;
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;
ent.renderfx |= RF_FORCE_ENT_ALPHA;
cgi_R_AddRefEntityToScene(&ent);
return;
}
memset (&ent, 0, sizeof(ent));
// items bob up and down continuously
if( item->giType == IT_HOLOCRON )
{
scale = 0.005f + cent->currentState.number * 0.00001f;
cent->lerpOrigin[2] += (float)(4 + cos( ( cg.time + 1000 ) * scale ) * 3)+8; // just raised them up a bit
}
// autorotate at one of two speeds
// if ( item->giType == IT_HEALTH ) {
// VectorCopy( cg.autoAnglesFast, cent->lerpAngles );
// AxisCopy( cg.autoAxisFast, ent.axis );
// } else {
if( item->giType == IT_HOLOCRON )
{
VectorCopy( cg.autoAngles, cent->lerpAngles );
AxisCopy( cg.autoAxis, ent.axis );
}
// 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 ) {
// weaponInfo_t *wi;
//
// 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
// }
vec3_t spinAngles;
//AxisClear( ent.axis );
VectorCopy( cent->gent->s.angles, spinAngles );
if ( cent->gent->ghoul2.IsValid()
&& cent->gent->ghoul2.size() )
{//since modelindex is used by items as an index into items(not models), we need to ignore the hModel here to force it to use the ghoul2 model if we have one
ent.hModel = cgs.model_draw[0];
}
else
{
ent.hModel = cg_items[es->modelindex].models;
}
/*
Ghoul2 Insert Start
*/
CG_SetGhoul2Info(&ent, cent);
/*
Ghoul2 Insert End
*/
VectorCopy( cent->lerpOrigin, ent.origin);
VectorCopy( cent->lerpOrigin, ent.oldorigin);
ent.nonNormalizedAxes = qfalse;
// lovely...this is for weapons that should be oriented vertically. For weapons lockers and such.
if ( cent->gent->spawnflags & 16 )
{ //VectorClear( spinAngles );
if ( item->giType == IT_WEAPON
&& item->giTag == WP_SABER )
{
if ( cent->gent->random )
{//pitch specified
spinAngles[PITCH] += cent->gent->random;
}
else
{
spinAngles[PITCH] -= 20;
}
}
else
{
spinAngles[PITCH] -= 75;
}
}
if( item->giType != IT_HOLOCRON )
{
AnglesToAxis( spinAngles, ent.axis );
}
// 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.5f, ent.axis[0] );
// VectorScale( ent.axis[1], 1.5f, ent.axis[1] );
// VectorScale( ent.axis[2], 1.5f, ent.axis[2] );
// ent.nonNormalizedAxes = qtrue;
// }
// add to refresh list
cgi_R_AddRefEntityToScene(&ent);
if ((cg.snap->ps.forcePowersActive & (1 << FP_SEE))
&& cg.snap->ps.clientNum != cent->currentState.number
&& CG_PlayerCanSeeCent( cent ) )
{
CG_AddForceSightShell( &ent, cent );
}
if ( item->giType == IT_WEAPON
&& item->giTag == WP_SABER
&& (!cent->gent || !(cent->gent->spawnflags&64/*ITMSF_NOGLOW*/)) )
{
// saber pickup item needs to be more visible
float wv;
vec3_t org;
ent.customShader = cgi_R_RegisterShader( "gfx/effects/solidWhite_cull" );
ent.renderfx = RF_RGB_TINT;
wv = sin( cg.time * 0.002f ) * 0.08f + 0.2f;
ent.shaderRGBA[0] = ent.shaderRGBA[1] = wv * 255;
ent.shaderRGBA[2] = 0;
cgi_R_AddRefEntityToScene(&ent);
for ( int i = -4; i < 10; i += 1 )
{
VectorMA( ent.origin, -i, ent.axis[2], org );
FX_AddSprite( org, NULL, NULL, 10.0f, 10.0f, wv * 0.5f, wv * 0.5f, 0.0f, 0.0f, 1.0f, cgs.media.yellowDroppedSaberShader, 0x08000000 );
}
// THIS light looks crappy...maybe it should just be removed...
cgi_R_AddLightToScene( ent.origin, wv * 350 + 180, 1.0f, 1.0f, 0.0f );
}
}
//============================================================================
/*
===============
CG_Missile
===============
*/
static void CG_Missile( centity_t *cent ) {
refEntity_t ent;
entityState_t *s1;
const weaponInfo_t *weapon;
const weaponData_t *wData;
if ( !cent->gent->inuse )
return;
s1 = ¢->currentState;
if ( s1->weapon >= WP_NUM_WEAPONS ) {
s1->weapon = 0;
}
weapon = &cg_weapons[s1->weapon];
wData = &weaponData[s1->weapon];
if ( s1->pos.trType != TR_INTERPOLATE )
{
// calculate the axis
VectorCopy( s1->angles, cent->lerpAngles );
}
if ( s1->otherEntityNum2 && (g_vehWeaponInfo[s1->otherEntityNum2].iShotFX || g_vehWeaponInfo[s1->otherEntityNum2].iModel) )
{
vec3_t forward;
if (s1->eFlags & EF_USE_ANGLEDELTA)
{
AngleVectors(cent->currentState.angles, forward, 0, 0);
}
else
{
if ( VectorNormalize2( cent->gent->s.pos.trDelta, forward ) == 0.0f )
{
if ( VectorNormalize2( s1->pos.trDelta, forward ) == 0.0f )
{
forward[2] = 1.0f;
}
}
}
// hack the scale of the forward vector if we were just fired or bounced...this will shorten up the tail for a split second so tails don't clip so harshly
int dif = cg.time - cent->gent->s.pos.trTime;
if ( dif < 75 )
{
if ( dif < 0 )
{
dif = 0;
}
float scale = ( dif / 75.0f ) * 0.95f + 0.05f;
VectorScale( forward, scale, forward );
}
CG_PlayEffectID(g_vehWeaponInfo[s1->otherEntityNum2].iShotFX, cent->lerpOrigin, forward);
if ( g_vehWeaponInfo[s1->otherEntityNum2].iLoopSound )
{
vec3_t velocity;
EvaluateTrajectoryDelta( ¢->currentState.pos, cg.time, velocity );
if (cgs.sound_precache[g_vehWeaponInfo[s1->otherEntityNum2].iLoopSound] != NULL_SFX)
{
cgi_S_AddLoopingSound( cent->currentState.number, cent->lerpOrigin, velocity, cgs.sound_precache[g_vehWeaponInfo[s1->otherEntityNum2].iLoopSound] );
}
}
//add custom model
if ( !g_vehWeaponInfo[s1->otherEntityNum2].iModel )
{
return;
}
}
else if ( cent->gent->alt_fire )
{
// add trails
if ( weapon->alt_missileTrailFunc )
weapon->alt_missileTrailFunc( cent, weapon );
// add dynamic light
if ( wData->alt_missileDlight )
cgi_R_AddLightToScene(cent->lerpOrigin, wData->alt_missileDlight,
wData->alt_missileDlightColor[0], wData->alt_missileDlightColor[1], wData->alt_missileDlightColor[2] );
// add missile sound
if ( weapon->alt_missileSound )
cgi_S_AddLoopingSound( cent->currentState.number, cent->lerpOrigin, vec3_origin, weapon->alt_missileSound );
//Don't draw something without a model
if ( weapon->alt_missileModel == NULL_HANDLE )
return;
}
else
{
// add trails
if ( weapon->missileTrailFunc )
weapon->missileTrailFunc( cent, weapon );
// add dynamic light
if ( wData->missileDlight )
cgi_R_AddLightToScene(cent->lerpOrigin, wData->missileDlight,
wData->missileDlightColor[0], wData->missileDlightColor[1], wData->missileDlightColor[2] );
// add missile sound
if ( weapon->missileSound )
cgi_S_AddLoopingSound( cent->currentState.number, cent->lerpOrigin, vec3_origin, weapon->missileSound );
//Don't draw something without a model
if ( weapon->missileModel == NULL_HANDLE )
return;
}
// create the render entity
memset (&ent, 0, sizeof(ent));
VectorCopy( cent->lerpOrigin, ent.origin);
VectorCopy( cent->lerpOrigin, ent.oldorigin);
/*
Ghoul2 Insert Start
*/
CG_SetGhoul2Info(&ent, cent);
/*
Ghoul2 Insert End
*/
// flicker between two skins
ent.skinNum = cg.clientFrame & 1;
ent.renderfx = /*weapon->missileRenderfx | */RF_NOSHADOW;
if ( s1->otherEntityNum2 && g_vehWeaponInfo[s1->otherEntityNum2].iModel && cgs.model_draw[g_vehWeaponInfo[s1->otherEntityNum2].iModel] != NULL_HANDLE)
ent.hModel = cgs.model_draw[g_vehWeaponInfo[s1->otherEntityNum2].iModel];
else if ( cent->gent->alt_fire )
ent.hModel = weapon->alt_missileModel;
else
ent.hModel = weapon->missileModel;
// spin as it moves
if ( s1->apos.trType != TR_INTERPOLATE )
{
// convert direction of travel into axis
if ( VectorNormalize2( s1->pos.trDelta, ent.axis[0] ) == 0 ) {
ent.axis[0][2] = 1;
}
if ( s1->pos.trType != TR_STATIONARY )
{
if ( s1->eFlags & EF_MISSILE_STICK )
RotateAroundDirection( ent.axis, cg.time * 0.5f );//Did this so regular missiles don't get broken
else
RotateAroundDirection( ent.axis, cg.time * 0.25f );//JFM:FLOAT FIX
}
else
{
if ( s1->eFlags & EF_MISSILE_STICK )
RotateAroundDirection( ent.axis, (float)s1->pos.trTime * 0.5f );
else
RotateAroundDirection( ent.axis, (float)s1->time );
}
}
else
{
AnglesToAxis( cent->lerpAngles, ent.axis );
}
// add to refresh list, possibly with quad glow
CG_AddRefEntityWithPowerups( &ent, s1->powerups, NULL );
if ((cg.snap->ps.forcePowersActive & (1 << FP_SEE))
&& cg.snap->ps.clientNum != cent->currentState.number
&& CG_PlayerCanSeeCent( cent ) )
{//so player can see dark missiles/explosives
if ( s1->weapon == WP_THERMAL
|| s1->weapon == WP_DET_PACK
|| s1->weapon == WP_TRIP_MINE
|| (s1->eFlags&EF_FORCE_VISIBLE) )
{//really, we only need to do this for things like thermals, detpacks and tripmines, no?
CG_AddForceSightShell( &ent, cent );
}
}
}
/*
===============
CG_Mover
===============
*/
#define DOOR_OPENING 1
#define DOOR_CLOSING 2
#define DOOR_OPEN 3
#define DOOR_CLOSED 4
static void CG_Mover( centity_t *cent ) {
refEntity_t ent;
entityState_t *s1;
s1 = ¢->currentState;
// create the render entity
memset (&ent, 0, sizeof(ent));
//FIXME: why are these always 0, 0, 0???!
VectorCopy( cent->lerpOrigin, ent.origin);
VectorCopy( cent->lerpOrigin, ent.oldorigin);
AnglesToAxis( cent->lerpAngles, ent.axis );
/*
Ghoul2 Insert Start
*/
CG_SetGhoul2Info(&ent, cent);
/*
Ghoul2 Insert End
*/
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.model_draw[s1->modelindex];
}
// If there isn't an hModel for this mover, an RGB axis model will get drawn.
if ( !ent.hModel )
{
return;
}
if ( cent->currentState.eFlags & EF_DISABLE_SHADER_ANIM )
{
// by setting the shader time to the current time, we can force an animating shader to not animate
ent.shaderTime = cg.time * 0.001f;
}
// add the secondary model
if ( s1->solid == SOLID_BMODEL && s1->modelindex2 )
{
// vec3_t org;
if ( !(s1->eFlags & EF_NODRAW) )
{
// add to refresh list
CG_AddRefEntWithTransportEffect( cent, &ent );
}
/* // Um, this does not interpolate nicely? Not sure why it was here....
VectorAdd(cent->gent->absmin, cent->gent->absmax, org);
VectorScale(org, 0.5, org);
VectorCopy( org, ent.origin);
VectorCopy( org, ent.oldorigin);
*/
if ( !VectorCompare( vec3_origin, cent->gent->modelAngles ) )
{//we have a rotational offset for the model for this brush
vec3_t modelAngles;
VectorAdd( cent->lerpAngles, cent->gent->modelAngles, modelAngles );
AnglesToAxis( modelAngles, ent.axis );
}
ent.hModel = cgs.model_draw[s1->modelindex2];
}
// I changed it to always do it because nodraw seemed like it should actually do what it says. Be aware that if you change this,
// the movers for the shooting gallery on doom_detention will break.
if ( (s1->eFlags & EF_NODRAW) )
{
return;
}
//fall through and render the hModel or...
//We're a normal model being moved, animate our model
ent.skinNum = 0;
if ( s1->eFlags & EF_ANIM_ONCE )
{//FIXME: needs to anim at once per 100 ms
ent.frame = cent->gent->s.frame;
ent.renderfx|=RF_CAP_FRAMES;
}
else if ( s1->eFlags & EF_ANIM_ALLFAST )
{
ent.frame = (cg.time / 100);
ent.renderfx|=RF_WRAP_FRAMES;
}
else
{
ent.frame = s1->frame;
}
if ( s1->eFlags & EF_SHADER_ANIM )
{
ent.renderfx|=RF_SETANIMINDEX;
ent.skinNum = s1->frame;
//ent.shaderTime = cg.time*0.001f - s1->frame/s1->time;//NOTE: s1->time is number of frames
}
// add to refresh list
CG_AddRefEntWithTransportEffect( cent, &ent );
if ((cg.snap->ps.forcePowersActive & (1 << FP_SEE))
&& cg.snap->ps.clientNum != cent->currentState.number
&& (s1->eFlags&EF_FORCE_VISIBLE) )
{//so player can see func_breakables
CG_AddForceSightShell( &ent, cent );
}
}
/*
===============
CG_Beam
Also called as an event
===============
*/
void CG_Beam( centity_t *cent, int color ) {
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.skinNum = color;
ent.renderfx = RF_NOSHADOW;
/*
Ghoul2 Insert Start
*/
CG_SetGhoul2Info(&ent, cent);
/*
Ghoul2 Insert End
*/
// add to refresh list
cgi_R_AddRefEntityToScene(&ent);
}
static vec2_t st[] =
{
{ 0.0f, 0.0f }, { 1.0f, 0.0f }, { 1.0f, 1.0f }, { 0.0f, 1.0f }
};
void CG_Cube( vec3_t mins, vec3_t maxs, vec3_t color, float alpha )
{
vec3_t point[4], rot={0,0,0};
int vec[3];
int axis, i;
for ( axis = 0, vec[0] = 0, vec[1] = 1, vec[2] = 2; axis < 3; axis++, vec[0]++, vec[1]++, vec[2]++ )
{
for ( i = 0; i < 3; i++ )
{
if ( vec[i] > 2 )
{
vec[i] = 0;
}
}
point[0][vec[1]] = mins[vec[1]];
point[0][vec[2]] = mins[vec[2]];
point[1][vec[1]] = mins[vec[1]];
point[1][vec[2]] = maxs[vec[2]];
point[2][vec[1]] = maxs[vec[1]];
point[2][vec[2]] = maxs[vec[2]];
point[3][vec[1]] = maxs[vec[1]];
point[3][vec[2]] = mins[vec[2]];
//- face
point[0][vec[0]] = point[1][vec[0]] = point[2][vec[0]] = point[3][vec[0]] = mins[vec[0]];
FX_AddPoly( point, st, 4, NULL, NULL, alpha, alpha, 0.0f,
color, color, 0.0f, rot, 0.0f, 0.0f,
100, cgs.media.solidWhiteShader, 0 );
//+ face
point[0][vec[0]] = point[1][vec[0]] = point[2][vec[0]] = point[3][vec[0]] = maxs[vec[0]];
FX_AddPoly( point, st, 4, NULL, NULL, alpha, alpha, 0.0f,
color, color, 0.0f, rot, 0.0f, 0.0f,
100, cgs.media.solidWhiteShader, 0 );
}
}
void CG_CubeOutline( vec3_t mins, vec3_t maxs, int time, unsigned int color, float alpha )
{
vec3_t point1, point2, point3, point4;
int vec[3];
int axis, i;
for ( axis = 0, vec[0] = 0, vec[1] = 1, vec[2] = 2; axis < 3; axis++, vec[0]++, vec[1]++, vec[2]++ )
{
for ( i = 0; i < 3; i++ )
{
if ( vec[i] > 2 )
{
vec[i] = 0;
}
}
point1[vec[1]] = mins[vec[1]];
point1[vec[2]] = mins[vec[2]];
point2[vec[1]] = mins[vec[1]];
point2[vec[2]] = maxs[vec[2]];
point3[vec[1]] = maxs[vec[1]];
point3[vec[2]] = maxs[vec[2]];
point4[vec[1]] = maxs[vec[1]];
point4[vec[2]] = mins[vec[2]];
//- face
point1[vec[0]] = point2[vec[0]] = point3[vec[0]] = point4[vec[0]] = mins[vec[0]];
CG_TestLine( point1, point2, time, color, 1 );
CG_TestLine( point2, point3, time, color, 1 );
CG_TestLine( point1, point4, time, color, 1 );
CG_TestLine( point4, point3, time, color, 1 );
//+ face
point1[vec[0]] = point2[vec[0]] = point3[vec[0]] = point4[vec[0]] = maxs[vec[0]];
CG_TestLine( point1, point2, time, color, 1 );
CG_TestLine( point2, point3, time, color, 1 );
CG_TestLine( point1, point4, time, color, 1 );
CG_TestLine( point4, point1, time, color, 1 );
}
}
void CG_Line( vec3_t start, vec3_t end, vec3_t color, float alpha )
{
/*FX_AddLine( start,
end,
1.0f,
1.0,
1.0f,
alpha,
alpha,
color,
color,
100.0f,
cgs.media.whiteShader );*/
}
/*
===============
CG_Portal
===============
*/
static void CG_Portal( centity_t *cent ) {
refEntity_t ent;
entityState_t *s1;
s1 = ¢->currentState;
//FIXME: this tends to give a bad axis[1], perhaps we
//should just do the VectorSubtraction here rather than
//on the game side. Would also allow camera to follow
//a moving target.
// 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.frame = s1->frame; // rotation speed
ent.skinNum = (int)(s1->clientNum/256.0 * 360); // roll offset
/*
Ghoul2 Insert Start
*/
CG_SetGhoul2Info(&ent, cent);
/*
Ghoul2 Insert End
*/
// add to refresh list
cgi_R_AddRefEntityToScene(&ent);
}
/*
=========================
CG_AdjustPositionForMover
Also called by client movement prediction code
=========================
*/
void CG_AdjustPositionForMover( const vec3_t in, int moverNum, int atTime, vec3_t out ) {
centity_t *cent;
vec3_t oldOrigin, origin, deltaOrigin;
// vec3_t oldAngles, angles, deltaAngles;
if ( moverNum <= 0 ) {
VectorCopy( in, out );
return;
}
cent = &cg_entities[ moverNum ];
if ( cent->currentState.eType != ET_MOVER ) {
VectorCopy( in, out );
return;
}
EvaluateTrajectory( ¢->currentState.pos, cg.snap->serverTime, oldOrigin );
// EvaluateTrajectory( ¢->currentState.apos, cg.snap->serverTime, oldAngles );
EvaluateTrajectory( ¢->currentState.pos, atTime, origin );
// EvaluateTrajectory( ¢->currentState.apos, atTime, angles );
VectorSubtract( origin, oldOrigin, deltaOrigin );
// VectorSubtract( angles, oldAngles, deltaAngles );
VectorAdd( in, deltaOrigin, out );
// FIXME: origin change when on a rotating object
}
/*
===============
CG_CalcEntityLerpPositions
===============
*/
extern char *vtos( const vec3_t v );
#if 1
void CG_CalcEntityLerpPositions( centity_t *cent ) {
if ( cent->gent && cent->gent->client && cent->gent->client->NPC_class == CLASS_VEHICLE && cent->nextState ) //cent->currentState.vehicleIndex != VEHICLE_NONE )
{
float f = cg.frameInterpolation;
cent->currentState.vehicleAngles[0] = LerpAngle( cent->currentState.vehicleAngles[0], cent->nextState->vehicleAngles[0], f );
cent->currentState.vehicleAngles[1] = LerpAngle( cent->currentState.vehicleAngles[1], cent->nextState->vehicleAngles[1], f );
cent->currentState.vehicleAngles[2] = LerpAngle( cent->currentState.vehicleAngles[2], cent->nextState->vehicleAngles[2], f );
}
if ( cent->currentState.number == cg.snap->ps.clientNum)
{
// if the player, take position from prediction
VectorCopy( cg.predicted_player_state.origin, cent->lerpOrigin );
VectorCopy( cg.predicted_player_state.viewangles, cent->lerpAngles );
/*
Ghoul2 Insert Start
*/
// LerpBoneAngleOverrides(cent);
/*
Ghoul2 Insert End
*/
return;
}
//FIXME: prediction on clients in timescale results in jerky positional translation
if ( cent->interpolate )
{
// if the entity has a valid next state, interpolate a value between the frames
// unless it is a mover with a known start and stop
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_AddCEntity: cg.nextSnap == NULL" );
}
f = cg.frameInterpolation;
if ( cent->currentState.apos.trType == TR_INTERPOLATE && cent->nextState )
{
EvaluateTrajectory( ¢->currentState.apos, cg.snap->serverTime, current );
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 );
/*
if(cent->gent && cent->currentState.clientNum != 0 && !VectorCompare(current, next))
{
Com_Printf("%s last/next/lerp apos %s/%s/%s, f = %4.2f\n", cent->gent->script_targetname, vtos(current), vtos(next), vtos(cent->lerpAngles), f);
}
*/
/*
Ghoul2 Insert Start
*/
// now the nasty stuff - this will interpolate all ghoul2 models bone angle overrides per model attached to this cent
/*
if (cent->gent->ghoul2.size())
{
LerpBoneAngleOverrides(cent);
}
*/
/*
Ghoul2 Insert End
*/
}
if ( cent->currentState.pos.trType == TR_INTERPOLATE && cent->nextState )
{
// this will linearize a sine or parabolic curve, but it is important
// to not extrapolate player positions if more recent data is available
EvaluateTrajectory( ¢->currentState.pos, cg.snap->serverTime, current );
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] );
/*
if ( cent->gent && cent->currentState.clientNum != 0 )
{
Com_Printf("%s last/next/lerp pos %s/%s/%s, f = %4.2f\n", cent->gent->script_targetname, vtos(current), vtos(next), vtos(cent->lerpOrigin), f);
}
*/
return;//FIXME: should this be outside this if?
}
}
else
{
if ( cent->currentState.apos.trType == TR_INTERPOLATE )
{
EvaluateTrajectory( ¢->currentState.apos, cg.snap->serverTime, cent->lerpAngles );
}
if ( cent->currentState.pos.trType == TR_INTERPOLATE )
{
EvaluateTrajectory( ¢->currentState.pos, cg.snap->serverTime, cent->lerpOrigin );
/*
if(cent->gent && cent->currentState.clientNum != 0 )
{
Com_Printf("%s last/next/lerp pos %s, f = 1.0\n", cent->gent->script_targetname, vtos(cent->lerpOrigin) );
}
*/
return;
}
}
// FIXME: if it's blocked, it wigs out, draws it in a predicted spot, but never
// makes it there - we need to predict it in the right place if this is happens...
// just use the current frame and evaluate as best we can
trajectory_t *posData = ¢->currentState.pos;
{
gentity_t *ent = &g_entities[cent->currentState.number];
if ( ent && ent->inuse)
{
if ( ent->s.eFlags & EF_BLOCKED_MOVER || ent->s.pos.trType == TR_STATIONARY )
{//this mover has stopped moving and is going to wig out if we predict it
//based on last frame's info- cut across the network and use the currentOrigin
VectorCopy( ent->currentOrigin, cent->lerpOrigin );
posData = NULL;
}
else
{
posData = &ent->s.pos;
}
}
}
if ( posData )
{
EvaluateTrajectory( posData, cg.time, cent->lerpOrigin );
}
// FIXME: this will stomp an apos trType of TR_INTERPOLATE!!
EvaluateTrajectory( ¢->currentState.apos, cg.time, cent->lerpAngles );
// adjust for riding a mover
CG_AdjustPositionForMover( cent->lerpOrigin, cent->currentState.groundEntityNum, cg.time, cent->lerpOrigin );
/*
Ghoul2 Insert Start
*/
// now the nasty stuff - this will interpolate all ghoul2 models bone angle overrides per model attached to this cent
/*
if (cent->gent->ghoul2.size())
{
LerpBoneAngleOverrides(cent);
}
*/
/*
Ghoul2 Insert End
*/
// FIXME: perform general error decay?
}
#else
void CG_CalcEntityLerpPositions( centity_t *cent )
{
if ( cent->currentState.number == cg.snap->ps.clientNum)
{
// if the player, take position from prediction
VectorCopy( cg.predicted_player_state.origin, cent->lerpOrigin );
VectorCopy( cg.predicted_player_state.viewangles, cent->lerpAngles );
OutputDebugString(va("b=(%6.2f,%6.2f,%6.2f)\n",cent->lerpOrigin[0],cent->lerpOrigin[1],cent->lerpOrigin[2]));
return;
}
if (cent->currentState.number != cg.snap->ps.clientNum&¢->interpolate && cent->currentState.pos.trType == TR_INTERPOLATE)
{
if (cent->interpolate)
{
OutputDebugString(va("[%3d] interp %4.2f t=%6d st = %6d nst = %6d b=%6.2f nb=%6.2f\n",
cent->currentState.number,
cg.frameInterpolation,
cg.time,
cg.snap->serverTime,
cg.nextSnap->serverTime,
cent->currentState.pos.trBase[0],
cent->nextState.pos.trBase[0]));
}
else
{
OutputDebugString(va("[%3d] nonext %4.2f t=%6d st = %6d nst = %6d b=%6.2f nb=%6.2f\n",
cent->currentState.number,
cg.frameInterpolation,
cg.time,
cg.snap->serverTime,
0,
cent->currentState.pos.trBase[0],
0.0f));
}
}
//FIXME: prediction on clients in timescale results in jerky positional translation
if (cent->interpolate &&
(cent->currentState.number == cg.snap->ps.clientNum ||
cent->interpolate && cent->currentState.pos.trType == TR_INTERPOLATE ) )
{
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_AddCEntity: cg.nextSnap == NULL" );
}
f = cg.frameInterpolation;
EvaluateTrajectory( ¢->currentState.apos, cg.snap->serverTime, current );
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 );
EvaluateTrajectory( ¢->currentState.pos, cg.snap->serverTime, current );
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] );
return;
}
// just use the current frame and evaluate as best we can
trajectory_t *posData = ¢->currentState.pos;
{
gentity_t *ent = &g_entities[cent->currentState.number];
if ( ent && ent->inuse)
{
if ( ent->s.eFlags & EF_BLOCKED_MOVER || ent->s.pos.trType == TR_STATIONARY )
{//this mover has stopped moving and is going to wig out if we predict it
//based on last frame's info- cut across the network and use the currentOrigin
VectorCopy( ent->currentOrigin, cent->lerpOrigin );
posData = NULL;
}
else
{
posData = &ent->s.pos;
EvaluateTrajectory(&ent->s.pos,cg.time, cent->lerpOrigin );
}
}
else
{
EvaluateTrajectory( ¢->currentState.pos, cg.snap->serverTime, cent->lerpOrigin );
}
}
EvaluateTrajectory( ¢->currentState.apos, cg.time, cent->lerpAngles );
// adjust for riding a mover
CG_AdjustPositionForMover( cent->lerpOrigin, cent->currentState.groundEntityNum, cg.time, cent->lerpOrigin );
}
#endif
/*
===============
CG_AddLocalSet
===============
*/
static void CG_AddLocalSet( centity_t *cent )
{
cent->gent->setTime = cgi_S_AddLocalSet( cent->gent->soundSet, cg.refdef.vieworg, cent->lerpOrigin, cent->gent->s.number, cent->gent->setTime );
}
/*
-------------------------
CAS_GetBModelSound
-------------------------
*/
sfxHandle_t CAS_GetBModelSound( const char *name, int stage )
{
return cgi_AS_GetBModelSound( name, stage );
}
void CG_DLightThink ( centity_t *cent )
{
if(cent->gent)
{
float tDelta = cg.time - cent->gent->painDebounceTime;
float percentage = ( tDelta/((float)cent->gent->speed) );
vec3_t org;
vec4_t currentRGBA;
gentity_t *owner = NULL;
int i;
if ( percentage >= 1.0f )
{//We hit the end
percentage = 1.0f;
switch( cent->gent->pushDebounceTime )
{
case 0://Fading from start to final
if ( cent->gent->spawnflags & 8 )
{//PULSER
if ( tDelta - cent->gent->speed - cent->gent->wait >= 0 )
{//Time to start fading down
cent->gent->painDebounceTime = cg.time;
cent->gent->pushDebounceTime = 1;
percentage = 0.0f;
}
}
else
{//Stick on startRGBA
percentage = 0.0f;
}
break;
case 1://Fading from final to start
if ( tDelta - cent->gent->speed - cent->gent->radius >= 0 )
{//Time to start fading up
cent->gent->painDebounceTime = cg.time;
cent->gent->pushDebounceTime = 0;
percentage = 0.0f;
}
break;
case 2://Fading from 0 intensity to start intensity
//Time to start fading from start to final
cent->gent->painDebounceTime = cg.time;
cent->gent->pushDebounceTime = 0;
percentage = 0.0f;
break;
case 3://Fading from current intensity to 0 intensity
//Time to turn off
cent->gent->misc_dlight_active = qfalse;
cent->gent->e_clThinkFunc = clThinkF_NULL;
cent->gent->s.eType = ET_GENERAL;
cent->gent->svFlags &= ~SVF_BROADCAST;
return;
break;
default:
break;
}
}
switch( cent->gent->pushDebounceTime )
{
case 0://Fading from start to final
for ( i = 0; i < 4; i++ )
{
currentRGBA[i] = cent->gent->startRGBA[i] + ( (cent->gent->finalRGBA[i] - cent->gent->startRGBA[i]) * percentage );
}
break;
case 1://Fading from final to start
for ( i = 0; i < 4; i++ )
{
currentRGBA[i] = cent->gent->finalRGBA[i] + ( (cent->gent->startRGBA[i] - cent->gent->finalRGBA[i]) * percentage );
}
break;
case 2://Fading from 0 intensity to start
for ( i = 0; i < 3; i++ )
{
currentRGBA[i] = cent->gent->startRGBA[i];
}
currentRGBA[3] = cent->gent->startRGBA[3] * percentage;
break;
case 3://Fading from current intensity to 0
for ( i = 0; i < 3; i++ )
{//FIXME: use last
currentRGBA[i] = cent->gent->startRGBA[i];
}
currentRGBA[3] = cent->gent->startRGBA[3] - (cent->gent->startRGBA[3] * percentage);
break;
default:
return;
break;
}
if ( cent->gent->owner )
{
owner = cent->gent->owner;
}
else
{
owner = cent->gent;
}
if ( owner->s.pos.trType == TR_INTERPOLATE )
{
VectorCopy( cg_entities[owner->s.number].lerpOrigin, org );
}
else
{
VectorCopy( owner->currentOrigin, org );
}
cgi_R_AddLightToScene(org, currentRGBA[3]*10, currentRGBA[0], currentRGBA[1], currentRGBA[2] );
}
}
void CG_Limb ( centity_t *cent )
{//first time we're drawn, remove us from the owner ent
if ( cent->gent && cent->gent->owner && cent->gent->owner->ghoul2.size() )
{
gentity_t *owner = cent->gent->owner;
if ( cent->gent->aimDebounceTime )
{//done with dismemberment, just waiting to mark owner dismemberable again
if ( cent->gent->aimDebounceTime > cg.time )
{//still waiting
return;
}
//done!
owner->client->dismembered = false;
//done!
cent->gent->e_clThinkFunc = clThinkF_NULL;
}
else
{
extern cvar_t *g_saberRealisticCombat;
//3) turn off w/descendants that surf in original model
#if 0
if ( cent->gent->target )//stubTagName )
{//add smoke to cap surf, spawn effect
if ( cent->gent->delay <= cg.time )
{//debounced so it only plays effect once every 50ms
int newBolt = gi.G2API_AddBolt( &owner->ghoul2[owner->playerModel], cent->gent->target );
if ( newBolt != -1 )
{
cent->gent->delay = cg.time + 50;
CG_PlayEffectBolted( "saber/limb_bolton", owner->playerModel, newBolt, owner->s.number, owner->s.origin ); //ent origin used to make FX culling work
}
}
}
#endif
if ( cent->gent->target2 )//limbName
{//turn the limb off
//NOTE: MUST use G2SURFACEFLAG_NODESCENDANTS
gi.G2API_SetSurfaceOnOff( &owner->ghoul2[owner->playerModel], cent->gent->target2, 0x00000100 );//G2SURFACEFLAG_NODESCENDANTS
}
if ( cent->gent->target3 )//stubCapName )
{//turn on caps
gi.G2API_SetSurfaceOnOff( &owner->ghoul2[owner->playerModel], cent->gent->target3, 0 );
}
if ( owner->weaponModel[0] > 0 )
{//the corpse hasn't dropped their weapon
if ( cent->gent->count == BOTH_DISMEMBER_RARM || cent->gent->count == BOTH_DISMEMBER_TORSO1 )//&& ent->s.weapon == WP_SABER && ent->weaponModel[0] != -1 )
{//FIXME: is this first check needed with this lower one?
gi.G2API_RemoveGhoul2Model( owner->ghoul2, owner->weaponModel[0] );
owner->weaponModel[0] = -1;
}
}
if ( owner->client->NPC_class == CLASS_PROTOCOL
|| debug_subdivision->integer
|| g_saberRealisticCombat->integer )
{
//wait 100ms before allowing owner to be dismembered again
cent->gent->aimDebounceTime = cg.time + 100;
return;
}
else
{
//done!
cent->gent->e_clThinkFunc = clThinkF_NULL;
}
}
}
}
extern Vehicle_t *G_IsRidingVehicle( gentity_t *ent );
qboolean MatrixMode = qfalse;
extern cvar_t *g_skippingcin;
void CG_MatrixEffect ( centity_t *cent )
{
float MATRIX_EFFECT_TIME = 1000.0f;
if ( (cent->currentState.boltInfo&MEF_MULTI_SPIN) )
{//multiple spins
if ( cent->currentState.time2 > 0 )
{//with a custom amount of time per spin
MATRIX_EFFECT_TIME = cent->currentState.time2;
}
}
else
{
if ( cent->currentState.eventParm
&& cent->currentState.eventParm != MATRIX_EFFECT_TIME )
{//not a falling multi-spin, so stretch out the effect over the whole desired length (or: condenses it, too)
MATRIX_EFFECT_TIME = cent->currentState.eventParm;
}
}
//VectorCopy( cent->lerpOrigin, cg.refdef.vieworg );
float totalElapsedTime = (float)(cg.time - cent->currentState.time);
float elapsedTime = totalElapsedTime;
bool stopEffect = ((totalElapsedTime > cent->currentState.eventParm) || cg.missionStatusShow || in_camera);
if (!stopEffect && (cent->currentState.boltInfo&MEF_HIT_GROUND_STOP) && g_entities[cent->currentState.otherEntityNum].client)
{
if (g_entities[cent->currentState.otherEntityNum].client->ps.groundEntityNum!=ENTITYNUM_NONE)
{
stopEffect = true;
}
else if (g_entities[cent->currentState.otherEntityNum].client->NPC_class == CLASS_VEHICLE)
{
Vehicle_t* pVeh = g_entities[cent->currentState.otherEntityNum].m_pVehicle;
if (pVeh && !(pVeh->m_ulFlags&VEH_FLYING))
{
stopEffect = true;
}
}
}
if (!stopEffect && (cent->currentState.boltInfo&MEF_LOOK_AT_ENEMY))
{
if (!g_entities[cent->currentState.otherEntityNum].lastEnemy ||
!g_entities[cent->currentState.otherEntityNum].lastEnemy->inuse)
{
stopEffect = true;
}
}
if (stopEffect)
{//time is up or this is a falling spin and they hit the ground or mission end screen is up
cg.overrides.active &= ~(/*CG_OVERRIDE_3RD_PERSON_ENT|*/CG_OVERRIDE_3RD_PERSON_RNG|CG_OVERRIDE_3RD_PERSON_ANG|CG_OVERRIDE_3RD_PERSON_POF);
//cg.overrides.thirdPersonEntity = 0;
cg.overrides.thirdPersonAngle = 0;
cg.overrides.thirdPersonPitchOffset = 0;
cg.overrides.thirdPersonRange = 0;
if ( g_skippingcin->integer )
{//skipping? don't mess with timescale
/*
if ( g_timescale->integer < 100 )
{//something messed up timescale, reset it?
cgi_Cvar_Set( "timescale", "100" );
}
*/
}
else
{//set it back to 1
cgi_Cvar_Set( "timescale", "1.0" );
}
MatrixMode = qfalse;
cent->gent->e_clThinkFunc = clThinkF_NULL;
cent->gent->e_ThinkFunc = thinkF_G_FreeEntity;
cent->gent->nextthink = cg.time + 500;
return;
}
else
{
while ( elapsedTime > MATRIX_EFFECT_TIME )
{
elapsedTime -= MATRIX_EFFECT_TIME;
}
}
MatrixMode = qtrue;
//FIXME: move the position towards them and back?
//cg.overrides.active |= CG_OVERRIDE_3RD_PERSON_ENT;
//cg.overrides.thirdPersonEntity = cent->currentState.otherEntityNum;
if (cent->currentState.boltInfo&MEF_LOOK_AT_ENEMY)
{
vec3_t toEnemy;
vec3_t toEnemyAngles;
VectorCopy(cg_entities[g_entities[cent->currentState.otherEntityNum].lastEnemy->s.number].lerpOrigin, toEnemy);
// CG_DrawEdge(cg_entities[cent->currentState.otherEntityNum].lerpOrigin, toEnemy, EDGE_NORMAL);
VectorSubtract(cg_entities[cent->currentState.otherEntityNum].lerpOrigin, toEnemy, toEnemy);
vectoangles(toEnemy, toEnemyAngles);
cg.overrides.active |= CG_OVERRIDE_3RD_PERSON_ANG;
cg.overrides.thirdPersonAngle =
toEnemyAngles[1] -
cg_entities[cent->currentState.otherEntityNum].lerpAngles[1] +
145.0f;
cg.overrides.thirdPersonAngle = AngleNormalize180(cg.overrides.thirdPersonAngle);
float MATRIX_EFFECT_TIME_HALF = MATRIX_EFFECT_TIME/2.0f;
float X = 1.0f;
if (elapsedTime>MATRIX_EFFECT_TIME_HALF)
{
X -= ((elapsedTime - MATRIX_EFFECT_TIME_HALF)/MATRIX_EFFECT_TIME_HALF);
}
cg.overrides.thirdPersonAngle *= X;
// gi.Printf("%f\n", cg.overrides.thirdPersonAngle);
cg.overrides.thirdPersonPitchOffset = 0.0f;
cg.overrides.thirdPersonRange = cg_thirdPersonRange.value * 3.0f;
}
if ( !(cent->currentState.boltInfo&MEF_NO_SPIN) )
{//rotate around them
//rotate
cg.overrides.active |= CG_OVERRIDE_3RD_PERSON_ANG;
cg.overrides.thirdPersonAngle = 360.0f*elapsedTime/MATRIX_EFFECT_TIME;
if ( (cent->currentState.boltInfo&MEF_REVERSE_SPIN) )
{
cg.overrides.thirdPersonAngle *= -1;
}
}
//do all the slowdown and vert bob stuff
if ( cent->currentState.angles2[0] )
{
cgi_Cvar_Set( "timescale", va("%4.2f",cent->currentState.angles2[0]) );
}
else if ( !(cent->currentState.boltInfo&MEF_NO_TIMESCALE) )
{//ramp the timescale
//slowdown
float timescale = (elapsedTime/MATRIX_EFFECT_TIME);
if ( timescale < 0.01f )
{
timescale = 0.01f;
}
cgi_Cvar_Set( "timescale", va("%4.2f",timescale) );
}
else
{//FIXME: MEF_HIT_GROUND_STOP: if they're on the ground, stop spinning and stop timescale
//FIXME: if they go to the menu, restore timescale?
//cgi_Cvar_Set( "timescale", "1.0ff" );
}
if ( !(cent->currentState.boltInfo&MEF_NO_VERTBOB) )
{//bob the pitch
//pitch
//dip - FIXME: use pitchOffet?
cg.overrides.active |= CG_OVERRIDE_3RD_PERSON_POF;
cg.overrides.thirdPersonPitchOffset = cg_thirdPersonPitchOffset.value;
if ( elapsedTime < MATRIX_EFFECT_TIME*0.33f )
{
cg.overrides.thirdPersonPitchOffset -= 30.0f*elapsedTime/(MATRIX_EFFECT_TIME*0.33);
}
else if ( elapsedTime > MATRIX_EFFECT_TIME*0.66f )
{
cg.overrides.thirdPersonPitchOffset -= 30.0f*(MATRIX_EFFECT_TIME-elapsedTime)/(MATRIX_EFFECT_TIME*0.33);
}
else
{
cg.overrides.thirdPersonPitchOffset -= 30.0f;
}
}
if ( !(cent->currentState.boltInfo&MEF_NO_RANGEVAR) )
{//vary the camera range
//pull back
cg.overrides.active |= CG_OVERRIDE_3RD_PERSON_RNG;
cg.overrides.thirdPersonRange = cg_thirdPersonRange.value;
if ( elapsedTime < MATRIX_EFFECT_TIME*0.33 )
{
cg.overrides.thirdPersonRange += 80.0f*elapsedTime/(MATRIX_EFFECT_TIME*0.33);
}
else if ( elapsedTime > MATRIX_EFFECT_TIME*0.66 )
{
cg.overrides.thirdPersonRange += 80.0f*(MATRIX_EFFECT_TIME-elapsedTime)/(MATRIX_EFFECT_TIME*0.33);
}
else
{
cg.overrides.thirdPersonRange += 80.0f;
}
}
//Hack for VR - Disable all camera hijacking again, not sure if this will work, but worth a try
cg.overrides.active &= ~(/*CG_OVERRIDE_3RD_PERSON_ENT|*/CG_OVERRIDE_3RD_PERSON_RNG|CG_OVERRIDE_3RD_PERSON_ANG|CG_OVERRIDE_3RD_PERSON_POF);
}
static void CG_Think ( centity_t *cent )
{
if(!cent->gent)
{
return;
}
CEntity_ThinkFunc(cent); // cent->gent->clThink(cent);
}
static void CG_Clouds( centity_t *cent )
{
refEntity_t ent;
// create the render entity
memset( &ent, 0, sizeof( ent ));
VectorCopy( cent->lerpOrigin, ent.origin );
ent.shaderRGBA[0] = ent.shaderRGBA[1] = ent.shaderRGBA[2] = ent.shaderRGBA[3] = 255;
ent.radius = cent->gent->radius;
ent.backlerp = cent->gent->wait;
ent.reType = RT_CLOUDS;
if ( cent->gent->spawnflags & 1 ) // TUBE type, the one with a hole in the middle
{
ent.rotation = cent->gent->random;
ent.renderfx = RF_GROW;// tube flag
}
if ( cent->gent->spawnflags & 2 ) // ALT type, uses a different shader
{
ent.customShader = cgi_R_RegisterShader( "gfx/world/haze2" );
}
else
{
ent.customShader = cgi_R_RegisterShader( "gfx/world/haze" );
}
cgi_R_AddRefEntityToScene( &ent );
}
/*
===============
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;
}
//we must have restarted the game
if (!cent->gent)
{
return;
}
cent->snapShotTime = cg.time;
// calculate the current origin
CG_CalcEntityLerpPositions( cent );
// add automatic effects
CG_EntityEffects( cent );
// add local sound set if any
if ( cent->gent && cent->gent->soundSet && cent->gent->soundSet[0] && cent->currentState.eType != ET_MOVER )
{
CG_AddLocalSet( cent );
}
/*
Ghoul2 Insert Start
*/
// do this before we copy the data to refEnts
if (cent->gent->ghoul2.IsValid())
{
trap_G2_SetGhoul2ModelIndexes(cent->gent->ghoul2, cgs.model_draw, cgs.skins);
}
/*
Ghoul2 Insert End
*/
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:
case ET_TERRAIN:
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, 0 );
break;
case ET_PORTAL:
CG_Portal( cent );
break;
case ET_SPEAKER:
if ( cent->gent && cent->gent->soundSet && cent->gent->soundSet[0] )
{
break;
}
CG_Speaker( cent );
break;
case ET_THINKER:
CG_General( cent );
CG_Think( cent );
break;
case ET_CLOUD: // dumb
CG_Clouds( cent );
break;
}
}
/*
===============
CG_AddPacketEntities
===============
*/
void CG_AddPacketEntities( qboolean isPortal ) {
int num;
centity_t *cent;
playerState_t *ps;
if (isPortal)
{
for ( num = 0 ; num < cg.snap->numEntities ; num++ )
{
cent = &cg_entities[ cg.snap->entities[ num ].number ];
if (cent->currentState.isPortalEnt)
{
CG_AddCEntity( cent );
}
}
return;
}
// 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;
}
//OutputDebugString(va("interp %4.2f ct=%6d nt=%6d st=%6d\n",cg.frameInterpolation,cg.time,cg.nextSnap->serverTime,cg.snap->serverTime));
}
else
{
cg.frameInterpolation = 0; // actually, it should never be used, because
// no entities should be marked as interpolating
//OutputDebugString(va("noterp %4.2f ct=%6d nt=%6d st=%6d\n",cg.frameInterpolation,cg.time,0,cg.snap->serverTime));
}
// the auto-rotating items will all have the same axis
cg.autoAngles[0] = 0;
cg.autoAngles[1] = ( cg.time & 2047 ) * 360 / 2048.0f;
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.predicted_player_state;
PlayerStateToEntityState( ps, &cg_entities[ ps->clientNum ].currentState );
// cent = &cg_entities[ ps->clientNum ]; // not needed now that player is in the snap packet
// CG_AddCEntity( cent ); //
// 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 );
}
for(num=0;numcurrentValid)
{
CG_AddCEntity( cent );
}
}
}
//rww - This function is not currently called. Use it as the client-side ROFF
//callback once that's implemented fully.
void CG_ROFF_NotetrackCallback( centity_t *cent, const char *notetrack)
{
int i = 0, r = 0, objectID = 0, anglesGathered = 0, posoffsetGathered = 0;
char type[256];
char argument[512];
char addlArg[512];
char errMsg[256];
char t[64];
int addlArgs = 0;
vec3_t parsedAngles, parsedOffset, useAngles, useOrigin, forward, right, up;
if (!cent || !notetrack)
{
return;
}
//notetrack = "effect effects/explosion1.efx 0+0+64 0-0-1";
while (notetrack[i] && notetrack[i] != ' ')
{
type[i] = notetrack[i];
i++;
}
type[i] = '\0';
if (notetrack[i] != ' ')
{ //didn't pass in a valid notetrack type, or forgot the argument for it
return;
}
i++;
while (notetrack[i] && notetrack[i] != ' ')
{
argument[r] = notetrack[i];
r++;
i++;
}
argument[r] = '\0';
if (!r)
{
return;
}
if (notetrack[i] == ' ')
{ //additional arguments...
addlArgs = 1;
i++;
r = 0;
while (notetrack[i])
{
addlArg[r] = notetrack[i];
r++;
i++;
}
addlArg[r] = '\0';
}
if (strcmp(type, "effect") == 0)
{
if (!addlArgs)
{
//sprintf(errMsg, "Offset position argument for 'effect' type is invalid.");
//goto functionend;
VectorClear(parsedOffset);
goto defaultoffsetposition;
}
i = 0;
while (posoffsetGathered < 3)
{
r = 0;
while (addlArg[i] && addlArg[i] != '+' && addlArg[i] != ' ')
{
t[r] = addlArg[i];
r++;
i++;
}
t[r] = '\0';
i++;
if (!r)
{ //failure..
//sprintf(errMsg, "Offset position argument for 'effect' type is invalid.");
//goto functionend;
VectorClear(parsedOffset);
i = 0;
goto defaultoffsetposition;
}
parsedOffset[posoffsetGathered] = atof(t);
posoffsetGathered++;
}
if (posoffsetGathered < 3)
{
Q_strncpyz(errMsg, "Offset position argument for 'effect' type is invalid.", sizeof(errMsg));
goto functionend;
}
i--;
if (addlArg[i] != ' ')
{
addlArgs = 0;
}
defaultoffsetposition:
objectID = theFxScheduler.RegisterEffect(argument);
if (objectID)
{
if (addlArgs)
{ //if there is an additional argument for an effect it is expected to be XANGLE-YANGLE-ZANGLE
i++;
while (anglesGathered < 3)
{
r = 0;
while (addlArg[i] && addlArg[i] != '-')
{
t[r] = addlArg[i];
r++;
i++;
}
t[r] = '\0';
i++;
if (!r)
{ //failed to get a new part of the vector
anglesGathered = 0;
break;
}
parsedAngles[anglesGathered] = atof(t);
anglesGathered++;
}
if (anglesGathered)
{
VectorCopy(parsedAngles, useAngles);
}
else
{ //failed to parse angles from the extra argument provided..
VectorCopy(cent->lerpAngles, useAngles);
}
}
else
{ //if no constant angles, play in direction entity is facing
VectorCopy(cent->lerpAngles, useAngles);
}
AngleVectors(useAngles, forward, right, up);
VectorCopy(cent->lerpOrigin, useOrigin);
//forward
useOrigin[0] += forward[0]*parsedOffset[0];
useOrigin[1] += forward[1]*parsedOffset[0];
useOrigin[2] += forward[2]*parsedOffset[0];
//right
useOrigin[0] += right[0]*parsedOffset[1];
useOrigin[1] += right[1]*parsedOffset[1];
useOrigin[2] += right[2]*parsedOffset[1];
//up
useOrigin[0] += up[0]*parsedOffset[2];
useOrigin[1] += up[1]*parsedOffset[2];
useOrigin[2] += up[2]*parsedOffset[2];
theFxScheduler.PlayEffect(objectID, useOrigin, useAngles);
}
}
else if (strcmp(type, "sound") == 0)
{
objectID = cgi_S_RegisterSound(argument);
cgi_S_StartSound(cent->lerpOrigin, cent->currentState.number, CHAN_BODY, objectID);
}
else if (strcmp(type, "loop") == 0)
{ //handled server-side
return;
}
//else if ...
else
{
if (type[0])
{
Com_Printf("^3Warning: \"%s\" is an invalid ROFF notetrack function\n", type);
}
else
{
Com_Printf("^3Warning: Notetrack is missing function and/or arguments\n");
}
}
return;
functionend:
Com_Printf("^3Type-specific notetrack error: %s\n", errMsg);
return;
}