mirror of
https://github.com/DrBeef/JKXR.git
synced 2024-11-27 14:32:09 +00:00
4597b03873
Opens in Android Studio but haven't even tried to build it yet (it won't.. I know that much!)
2423 lines
64 KiB
C++
2423 lines
64 KiB
C++
/*
|
|
===========================================================================
|
|
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 = ¢->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 = ¢->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( ¢->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 = 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( ¢->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 = ¢->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 = ¢->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 = ¢->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 = ¢->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->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( ¢->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.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( ¢->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.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( ¢->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.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 = ¢->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 = 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;
|
|
}
|