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