jkxr/Projects/Android/jni/OpenJK/codeJK2/cgame/cg_ents.cpp

2424 lines
64 KiB
C++
Raw Normal View History

/*
===========================================================================
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 <http://www.gnu.org/licenses/>.
===========================================================================
*/
// cg_ents.c -- present snapshot entities, happens every single frame
#include "../game/g_local.h"
#include "cg_local.h"
#include "cg_media.h"
#include "../game/g_functions.h"
#include "../../code/ghoul2/G2.h"
#include "FxScheduler.h"
#include "../game/wp_saber.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 modelIndex, vec3_t origin, vec3_t angles );
extern void CG_ForcePushBlur( const vec3_t org );
/*
======================
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 {
cgi_S_UpdateEntityPosition( cent->currentState.number, cent->lerpOrigin );
VectorCopy(cent->lerpOrigin, 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 )
{
sfxHandle_t sfx = ( cent->currentState.eType == ET_MOVER ) ? cent->currentState.loopSound : cgs.sound_precache[ cent->currentState.loopSound ];
cgi_S_AddLoopingSound( cent->currentState.number, v3Origin/*cent->lerpOrigin*/, vec3_origin, sfx );
}
// 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 = &cent->gent->ghoul2;
VectorCopy( cent->currentState.modelScale, ent->modelScale);
ent->radius = cent->currentState.radius;
VectorCopy (cent->lerpAngles, ent->angles);
}
// create 8 new points on screen around a model so we can see it's bounding box
void CG_CreateBBRefEnts(entityState_t *s1, vec3_t origin )
{
#if 0 //loadsavecrash _DEBUG
refEntity_t point[8];
int i;
vec3_t angles = {0,0,0};
for (i=0; i<8; i++)
{
memset (&point[i], 0, sizeof(refEntity_t));
point[i].reType = RT_SPRITE;
point[i].radius = 1;
point[i].customShader = cgi_R_RegisterShader("textures/tests/circle");
point[i].shaderRGBA[0] = 255;
point[i].shaderRGBA[1] = 255;
point[i].shaderRGBA[2] = 255;
point[i].shaderRGBA[3] = 255;
AnglesToAxis( angles, point[i].axis );
// now, we need to put the correct origins into each origin from the mins and max's
switch(i)
{
case 0:
VectorCopy(s1->mins, point[i].origin);
break;
case 1:
VectorCopy(s1->mins, point[i].origin);
point[i].origin[0] = s1->maxs[0];
break;
case 2:
VectorCopy(s1->mins, point[i].origin);
point[i].origin[1] = s1->maxs[1];
break;
case 3:
VectorCopy(s1->mins, point[i].origin);
point[i].origin[0] = s1->maxs[0];
point[i].origin[1] = s1->maxs[1];
break;
case 4:
VectorCopy(s1->maxs, point[i].origin);
break;
case 5:
VectorCopy(s1->maxs, point[i].origin);
point[i].origin[0] = s1->mins[0];
break;
case 6:
VectorCopy(s1->maxs, point[i].origin);
point[i].origin[1] = s1->mins[1];
break;
case 7:
VectorCopy(s1->maxs, point[i].origin);
point[i].origin[0] = s1->mins[0];
point[i].origin[1] = s1->mins[1];
break;
}
// add the original origin to each point and then stuff them out there
VectorAdd(point[i].origin, origin, point[i].origin);
cgi_R_AddRefEntityToScene (&point[i]);
}
#endif
}
// 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
==================
*/
extern int CG_SaberHumSoundForEnt( gentity_t *gent );
static void CG_General( centity_t *cent )
{
refEntity_t ent;
entityState_t *s1;
s1 = &cent->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 ( 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 ( 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 )
{
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 ))
{
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( &cent->gent->ghoul2[cent->gent->playerModel], "model_root", cg.time );
// gi.G2API_SetBoneAnimIndex( &cent->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 = cent->gent->handLBolt;
}
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 );
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 )
{
gi.G2API_SetBoneAnimIndex( &cent->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 );
}
ScaleModelAxis(&ent);
if (cent->gent->ghoul2.size())
{
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.saberLength > 0 )
{
CG_AddSaberBlade( &cg_entities[cent->gent->owner->s.number],
&cg_entities[cent->gent->s.number], NULL, ent.renderfx,
cent->gent->weaponModel, 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], cent->gent->weaponModel,
cent->lerpOrigin, cent->lerpAngles );
}
}
}
else
{//thrown saber
//light? sound?
if ( cent->gent->owner->client && cg_entities[cent->currentState.otherEntityNum].currentState.saberActive )
{//saber is in-flight and active, play a sound on it
if ( cent->gent->owner->client->ps.saberEntityState == SES_RETURNING )
{
cgi_S_AddLoopingSound( cent->currentState.number,
cent->lerpOrigin, vec3_origin,
CG_SaberHumSoundForEnt( cent->gent->owner ) );
}
else
{
int spinSound;
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.saberLength > 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, 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 )
{
if ( cg_weapons[WP_SABER].firingSound )
{
cgi_S_AddLoopingSound( cent->currentState.number,
cent->lerpOrigin, vec3_origin, cg_weapons[WP_SABER].firingSound );
}
}
else
{
int spinSound;
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 );
}
/*
Ghoul2 Insert Start
*/
if (cg_debugBB.integer)
{
CG_CreateBBRefEnts(s1, cent->lerpOrigin);
}
/*
Ghoul2 Insert End
*/
//--------------------------
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 );
}
}
}
}
}
/*
==================
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 = &cent->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.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;
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 );
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 );
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 ( item->giType == IT_WEAPON && item->giTag == WP_SABER )
{
// 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 = &cent->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 ( 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 ( 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 );
}
/*
===============
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 = &cent->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);
*/
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 );
}
/*
===============
CG_Beam
Also called as an event
===============
*/
void CG_Beam( centity_t *cent, int color ) {
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.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 = &cent->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( &cent->currentState.pos, cg.snap->serverTime, oldOrigin );
// EvaluateTrajectory( &cent->currentState.apos, cg.snap->serverTime, oldAngles );
EvaluateTrajectory( &cent->currentState.pos, atTime, origin );
// EvaluateTrajectory( &cent->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->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 )
{
EvaluateTrajectory( &cent->currentState.apos, cg.snap->serverTime, current );
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 );
/*
if(cent->gent && cent->currentState.eFlags & EF_NPC && !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 )
{
// this will linearize a sine or parabolic curve, but it is important
// to not extrapolate player positions if more recent data is available
EvaluateTrajectory( &cent->currentState.pos, cg.snap->serverTime, current );
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] );
/*
if ( cent->gent && cent->currentState.eFlags & EF_NPC )
{
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( &cent->currentState.apos, cg.snap->serverTime, cent->lerpAngles );
}
if ( cent->currentState.pos.trType == TR_INTERPOLATE )
{
EvaluateTrajectory( &cent->currentState.pos, cg.snap->serverTime, cent->lerpOrigin );
/*
if(cent->gent && cent->currentState.eFlags & EF_NPC )
{
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 = &cent->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( &cent->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&&cent->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( &cent->currentState.apos, cg.snap->serverTime, current );
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 );
EvaluateTrajectory( &cent->currentState.pos, cg.snap->serverTime, current );
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] );
return;
}
// just use the current frame and evaluate as best we can
trajectory_t *posData = &cent->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( &cent->currentState.pos, cg.snap->serverTime, cent->lerpOrigin );
}
}
EvaluateTrajectory( &cent->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 = qfalse;
//done!
cent->gent->e_clThinkFunc = clThinkF_NULL;
}
else
{
extern cvar_t *g_dismemberment;
extern cvar_t *g_saberRealisticCombat;
//3) turn off w/descendants that surf in original model
if ( cent->gent->target )//stubTagName )
{//add smoke to cap surf, spawn effect
int newBolt = gi.G2API_AddBolt( &owner->ghoul2[owner->playerModel], cent->gent->target );
if ( newBolt != -1 )
{
G_PlayEffect( "blaster/smoke_bolton", owner->playerModel, newBolt, owner->s.number );
}
}
if ( cent->gent->target2 )//limbName
{//turn the limb off
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 )
{//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 != -1 )
{//FIXME: is this first check needed with this lower one?
gi.G2API_RemoveGhoul2Model( owner->ghoul2, owner->weaponModel );
owner->weaponModel = -1;
}
}
if ( owner->client->NPC_class == CLASS_PROTOCOL
|| g_dismemberment->integer >= 11381138
|| 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;
}
}
}
}
qboolean MatrixMode = qfalse;
void CG_MatrixEffect ( centity_t *cent )
{
float MATRIX_EFFECT_TIME = 1000.0f;
//VectorCopy( cent->lerpOrigin, cg.refdef.vieworg );
float totalElapsedTime = (float)(cg.time - cent->currentState.time);
float elapsedTime = totalElapsedTime;
if ( totalElapsedTime > cent->currentState.eventParm //MATRIX_EFFECT_TIME
|| (cent->currentState.weapon&&g_entities[cent->currentState.otherEntityNum].client&&g_entities[cent->currentState.otherEntityNum].client->ps.groundEntityNum!=ENTITYNUM_NONE)
|| cg.missionStatusShow )
{//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;
cgi_Cvar_Set( "timescale", "1.0" );
MatrixMode = qfalse;
cent->gent->e_clThinkFunc = clThinkF_NULL;
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;
//rotate
cg.overrides.active |= CG_OVERRIDE_3RD_PERSON_ANG;
cg.overrides.thirdPersonAngle = 360.0f*elapsedTime/MATRIX_EFFECT_TIME;
if ( !cent->currentState.weapon )
{//go ahead and do all the slowdown and vert bob stuff
//slowdown
float timescale = (elapsedTime/MATRIX_EFFECT_TIME);
if ( timescale < 0.01f )
{
timescale = 0.01f;
}
cgi_Cvar_Set( "timescale", va("%4.2f",timescale) );
//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;
}
//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;
}
}
else
{//FIXME: if they're on the ground, stop spinning and stop timescale
//FIXME: if they go to the menu, restore timescale
cgi_Cvar_Set( "timescale", "0.25f" );
}
}
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;
}
// 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:
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( void ) {
int num;
centity_t *cent;
playerState_t *ps;
// set cg.frameInterpolation
if ( cg.nextSnap )
{
int delta;
delta = (cg.nextSnap->serverTime - cg.snap->serverTime);
if ( delta == 0 )
{
cg.frameInterpolation = 0;
}
else
{
cg.frameInterpolation = (float)( cg.time - cg.snap->serverTime ) / delta;
}
//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 );
}
}
//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;
}