/* =========================================================================== Wolfenstein: Enemy Territory GPL Source Code Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. This file is part of the Wolfenstein: Enemy Territory GPL Source Code (“Wolf ET Source Code”). Wolf ET Source Code is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Wolf ET Source Code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Wolf ET Source Code. If not, see . In addition, the Wolf: ET Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Wolf ET Source Code. If not, please request a copy in writing from id Software at the address below. If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. =========================================================================== */ /* * name: cg_ents.c * * desc: present snapshot entities, happens every single frame * */ #include "cg_local.h" /* ====================== CG_PositionEntityOnTag Modifies the entities position and axis by the given tag location ====================== */ void CG_PositionEntityOnTag( refEntity_t *entity, const refEntity_t *parent, const char *tagName, int startIndex, vec3_t *offset ) { int i; orientation_t lerped; // lerp the tag trap_R_LerpTag( &lerped, parent, tagName, startIndex ); // allow origin offsets along tag VectorCopy( parent->origin, entity->origin ); if ( offset ) { VectorAdd( lerped.origin, *offset, lerped.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 ); } /* ====================== CG_PositionRotatedEntityOnTag Modifies the entities position and axis by the given tag location ====================== */ void CG_PositionRotatedEntityOnTag( refEntity_t *entity, const refEntity_t *parent, const char *tagName ) { int i; orientation_t lerped; vec3_t tempAxis[3]; //AxisClear( entity->axis ); // lerp the tag trap_R_LerpTag( &lerped, parent, tagName, 0 ); // FIXME: allow origin offsets along tag? VectorCopy( parent->origin, entity->origin ); for ( i = 0 ; i < 3 ; i++ ) { VectorMA( entity->origin, lerped.origin[i], parent->axis[i], entity->origin ); } // had to cast away the const to avoid compiler problems... MatrixMultiply( entity->axis, lerped.axis, tempAxis ); MatrixMultiply( tempAxis, ( (refEntity_t *)parent )->axis, entity->axis ); } /* ========================================================================== FUNCTIONS CALLED EACH FRAME ========================================================================== */ /* ====================== CG_SetEntitySoundPosition Also called by event processing code ====================== */ void CG_SetEntitySoundPosition( centity_t *cent ) { if ( cent->currentState.solid == SOLID_BMODEL ) { vec3_t origin; float *v; v = cgs.inlineModelMidpoints[ cent->currentState.modelindex ]; VectorAdd( cent->lerpOrigin, v, origin ); trap_S_UpdateEntityPosition( cent->currentState.number, origin ); } else { trap_S_UpdateEntityPosition( cent->currentState.number, cent->lerpOrigin ); } } #define LS_FRAMETIME 100 // (ms) cycle through lightstyle characters at 10fps /* ============== CG_SetDlightIntensity ============== */ void CG_AddLightstyle( centity_t *cent ) { float lightval; int cl; int r, g, b; int stringlength; float offset; int offsetwhole; int otime; int lastch, nextch; if ( !cent->dl_stylestring ) { return; } otime = cg.time - cent->dl_time; stringlength = strlen( cent->dl_stylestring ); // it's been a long time since you were updated, lets assume a reset if ( otime > 2 * LS_FRAMETIME ) { otime = 0; cent->dl_frame = cent->dl_oldframe = 0; cent->dl_backlerp = 0; } cent->dl_time = cg.time; offset = ( (float)otime ) / LS_FRAMETIME; offsetwhole = (int)offset; cent->dl_backlerp += offset; if ( cent->dl_backlerp > 1 ) { // we're moving on to the next frame cent->dl_oldframe = cent->dl_oldframe + (int)cent->dl_backlerp; cent->dl_frame = cent->dl_oldframe + 1; if ( cent->dl_oldframe >= stringlength ) { cent->dl_oldframe = ( cent->dl_oldframe ) % stringlength; if ( cent->dl_oldframe < 3 && cent->dl_sound ) { // < 3 so if an alarm comes back into the pvs it will only start a sound if it's going to be closely synced with the light, otherwise wait till the next cycle trap_S_StartSound( NULL, cent->currentState.number, CHAN_AUTO, cgs.gameSounds[cent->dl_sound] ); } } if ( cent->dl_frame >= stringlength ) { cent->dl_frame = ( cent->dl_frame ) % stringlength; } cent->dl_backlerp = cent->dl_backlerp - (int)cent->dl_backlerp; } lastch = cent->dl_stylestring[cent->dl_oldframe] - 'a'; nextch = cent->dl_stylestring[cent->dl_frame] - 'a'; lightval = ( lastch * ( 1.0 - cent->dl_backlerp ) ) + ( nextch * cent->dl_backlerp ); // ydnar: dlight values go from 0-1.5ish #if 0 lightval = ( lightval * ( 1000.0f / 24.0f ) ) - 200.0f; // they want 'm' as the "middle" value as 300 lightval = max( 0.0f, lightval ); lightval = min( 1000.0f, lightval ); #else lightval *= 0.071429; lightval = max( 0.0f, lightval ); lightval = min( 20.0f, lightval ); #endif cl = cent->currentState.constantLight; r = cl & 255; g = ( cl >> 8 ) & 255; b = ( cl >> 16 ) & 255; //% trap_R_AddLightToScene( cent->lerpOrigin, lightval, 1.0, (float)r/255.0f, (float)g/255.0f, (float)b/255.0f, 0, 0 ); // overdraw forced to 0 for now // ydnar: if the dlight has angles, then it is a directional global dlight if ( cent->currentState.angles[ 0 ] || cent->currentState.angles[ 1 ] || cent->currentState.angles[ 2 ] ) { vec3_t normal; AngleVectors( cent->currentState.angles, normal, NULL, NULL ); trap_R_AddLightToScene( normal, 256, lightval, (float) r / 255.0f, (float) r / 255.0f, (float) r / 255.0f, 0, REF_DIRECTED_DLIGHT ); } // normal global dlight else { trap_R_AddLightToScene( cent->lerpOrigin, 256, lightval, (float) r / 255.0f, (float) g / 255.0f, (float) b / 255.0f, 0, 0 ); } } void CG_GetWindVector( vec3_t dir ); // JPW NERVE /* ================== CG_EntityEffects Add continuous entity effects, like local entity emission and lighting ================== */ static void CG_EntityEffects( centity_t *cent ) { static vec3_t dir; // update sound origins CG_SetEntitySoundPosition( cent ); // add loop sound if ( cent->currentState.loopSound ) { // ydnar: allow looped sounds to start at trigger time if ( cent->soundTime == 0 ) { cent->soundTime = trap_S_GetCurrentSoundTime(); } if ( cent->currentState.eType == ET_SPEAKER ) { int volume = cent->currentState.onFireStart; if ( cent->currentState.dmgFlags ) { // range is set trap_S_AddRealLoopingSound( cent->lerpOrigin, vec3_origin, cgs.gameSounds[ cent->currentState.loopSound ], cent->currentState.dmgFlags, volume, cent->soundTime ); } else { trap_S_AddRealLoopingSound( cent->lerpOrigin, vec3_origin, cgs.gameSounds[ cent->currentState.loopSound ], 1250, volume, cent->soundTime ); } } else if ( cent->currentState.eType == ET_MOVER ) { trap_S_AddLoopingSound( cent->lerpOrigin, vec3_origin, cgs.gameSounds[ cent->currentState.loopSound ], cent->currentState.onFireStart, cent->soundTime ); } else if ( cent->currentState.solid == SOLID_BMODEL ) { vec3_t origin; float *v; v = cgs.inlineModelMidpoints[ cent->currentState.modelindex ]; VectorAdd( cent->lerpOrigin, v, origin ); trap_S_AddLoopingSound( origin, vec3_origin, cgs.gameSounds[ cent->currentState.loopSound ], cent->currentState.onFireStart, cent->soundTime ); } else { trap_S_AddLoopingSound( cent->lerpOrigin, vec3_origin, cgs.gameSounds[ cent->currentState.loopSound ], 255, cent->soundTime ); } } else if ( cent->soundTime ) { cent->soundTime = 0; } // constant light glow if ( cent->currentState.constantLight ) { int cl; int i, r, g, b; if ( cent->dl_stylestring[0] != 0 ) { // it's probably a dlight CG_AddLightstyle( cent ); } else { cl = cent->currentState.constantLight; r = cl & 255; g = ( cl >> 8 ) & 255; b = ( cl >> 16 ) & 255; i = ( ( cl >> 24 ) & 255 ) * 4; trap_R_AddLightToScene( cent->lerpOrigin, i, 1.0, (float)r / 255.0f, (float)g / 255.0f, (float)b / 255.0f, 0, 0 ); } } // Ridah, flaming sounds if ( CG_EntOnFire( cent ) ) { // play a flame blow sound when moving trap_S_AddLoopingSound( cent->lerpOrigin, vec3_origin, cgs.media.flameBlowSound, (int)( 255.0 * ( 1.0 - fabs( cent->fireRiseDir[2] ) ) ), 0 ); // play a burning sound when not moving trap_S_AddLoopingSound( cent->lerpOrigin, vec3_origin, cgs.media.flameSound, (int)( 0.3 * 255.0 * ( pow( cent->fireRiseDir[2],2 ) ) ), 0 ); } // ydnar: overheating is a special effect if ( ( cent->currentState.eFlags & EF_OVERHEATING ) == EF_OVERHEATING ) { if ( cent->overheatTime < ( cg.time - 3000 ) ) { cent->overheatTime = cg.time; } if ( !( rand() % 3 ) ) { float alpha; vec3_t muzzle; if ( CG_CalcMuzzlePoint( ( cent - cg_entities ), muzzle ) ) { muzzle[ 2 ] -= DEFAULT_VIEWHEIGHT; } else { VectorCopy( cent->lerpOrigin, muzzle ); } alpha = 1.0f - ( (float) ( cg.time - cent->overheatTime ) / 3000.0f ); alpha *= 0.25f; CG_ParticleImpactSmokePuffExtended( cgs.media.smokeParticleShader, muzzle, 1000, 8, 20, 30, alpha, 8.f ); } } // DHM - Nerve :: If EF_SMOKING is set, emit smoke else if ( cent->currentState.eFlags & EF_SMOKING ) { float rnd = random(); if ( cent->lastTrailTime < cg.time ) { cent->lastTrailTime = cg.time + 100; // JPW NERVE -- use wind vector for smoke CG_GetWindVector( dir ); VectorScale( dir,20,dir ); // was 75, before that 55 if ( dir[2] < 10 ) { dir[2] += 10; } // dir[0] = crandom() * 10; // dir[1] = crandom() * 10; // dir[2] = 10 + rnd * 30; // jpw CG_SmokePuff( cent->lerpOrigin, dir, 15 + ( random() * 10 ), 0.3 + rnd, 0.3 + rnd, 0.3 + rnd, 0.4, 1500 + ( rand() % 500 ), cg.time, cg.time + 500, 0, cgs.media.smokePuffShader ); } } // dhm - end // JPW NERVE same thing but for smoking barrels instead of nasty server-side effect from single player else if ( cent->currentState.eFlags & EF_SMOKINGBLACK ) { float rnd = random(); if ( cent->lastTrailTime < cg.time ) { cent->lastTrailTime = cg.time + 75; CG_GetWindVector( dir ); VectorScale( dir,50,dir ); // was 75, before that 55 if ( dir[2] < 50 ) { dir[2] += 50; } CG_SmokePuff( cent->lerpOrigin, dir, 40 + random() * 70, //40+(rnd*40), rnd * 0.1, rnd * 0.1, rnd * 0.1, 1, 2800 + ( rand() % 4000 ), //2500+(random()*1500), cg.time, 0, 0, cgs.media.smokePuffShader ); } } // jpw } void CG_RailTrail2( clientInfo_t *ci, vec3_t start, vec3_t end ); /* ================== CG_General ================== */ static void CG_General( centity_t *cent ) { refEntity_t ent; entityState_t *s1; s1 = ¢->currentState; // if set to invisible, skip if ( !s1->modelindex ) { return; } memset( &ent, 0, sizeof( ent ) ); // set frame ent.frame = s1->frame; ent.oldframe = ent.frame; ent.backlerp = 0; if ( ent.frame ) { ent.oldframe -= 1; ent.backlerp = 1 - cg.frameInterpolation; if ( cent->currentState.time ) { ent.fadeStartTime = cent->currentState.time; ent.fadeEndTime = cent->currentState.time2; } } VectorCopy( cent->lerpOrigin, ent.origin ); VectorCopy( cent->lerpOrigin, ent.oldorigin ); ent.hModel = cgs.gameModels[s1->modelindex]; // player model if ( s1->number == cg.snap->ps.clientNum ) { ent.renderfx |= RF_THIRD_PERSON; // only draw from mirrors } if ( cent->currentState.eType == ET_MG42_BARREL ) { // grab angles from first person user or self if not // ATVI Wolfenstein Misc #469 - don't track until viewlocked if ( cent->currentState.otherEntityNum == cg.snap->ps.clientNum && cg.snap->ps.viewlocked ) { AnglesToAxis( cg.predictedPlayerState.viewangles, ent.axis ); } else { AnglesToAxis( cent->lerpAngles, ent.axis ); } } else if ( cent->currentState.eType == ET_AAGUN ) { // grab angles from first person user or self if not if ( cent->currentState.otherEntityNum == cg.snap->ps.clientNum && cg.snap->ps.viewlocked ) { AnglesToAxis( cg.predictedPlayerState.viewangles, ent.axis ); } else { //AnglesToAxis( cg_entities[cent->currentState.otherEntityNum].lerpAngles, ent.axis ); AnglesToAxis( cent->lerpAngles, ent.axis ); } /* { vec3_t v; VectorCopy( cent->lerpOrigin, v ); VectorMA( cent->lerpOrigin, 10, ent.axis[0], v ); CG_RailTrail2( NULL, cent->lerpOrigin, v ); VectorCopy( cent->lerpOrigin, v ); VectorMA( cent->lerpOrigin, 10, ent.axis[1], v ); CG_RailTrail2( NULL, cent->lerpOrigin, v ); VectorCopy( cent->lerpOrigin, v ); VectorMA( cent->lerpOrigin, 10, ent.axis[2], v ); CG_RailTrail2( NULL, cent->lerpOrigin, v ); return; }*/ } else { // convert angles to axis AnglesToAxis( cent->lerpAngles, ent.axis ); } // scale gamemodels if ( cent->currentState.eType == ET_GAMEMODEL ) { VectorScale( ent.axis[0], cent->currentState.angles2[0], ent.axis[0] ); VectorScale( ent.axis[1], cent->currentState.angles2[1], ent.axis[1] ); VectorScale( ent.axis[2], cent->currentState.angles2[2], ent.axis[2] ); ent.nonNormalizedAxes = qtrue; if ( cent->currentState.apos.trType ) { ent.reFlags |= REFLAG_ORIENT_LOD; } if ( s1->torsoAnim ) { if ( cg.time >= cent->lerpFrame.frameTime ) { cent->lerpFrame.oldFrameTime = cent->lerpFrame.frameTime; cent->lerpFrame.oldFrame = cent->lerpFrame.frame; while ( cg.time >= cent->lerpFrame.frameTime && // Mad Doc xkan, 11/18/2002 - if teamNum == 1, then we are supposed to stop // the animation when we reach the end of this loop // Gordon: 27/11/02: clientNum already does this. // xkan, 1/8/2003 - No, it does something a little different. !( s1->teamNum == 1 && cent->lerpFrame.frame + s1->frame == s1->legsAnim + s1->torsoAnim ) ) { cent->lerpFrame.frameTime += s1->weapon; cent->lerpFrame.frame++; if ( cent->lerpFrame.frame >= s1->legsAnim + s1->torsoAnim ) { if ( s1->clientNum ) { cent->lerpFrame.frame = s1->legsAnim + s1->torsoAnim - 1; cent->lerpFrame.oldFrame = s1->legsAnim + s1->torsoAnim - 1; } else { cent->lerpFrame.frame = s1->legsAnim; } } } } if ( cent->lerpFrame.frameTime == cent->lerpFrame.oldFrameTime ) { cent->lerpFrame.backlerp = 0; } else { cent->lerpFrame.backlerp = 1.0 - (float)( cg.time - cent->lerpFrame.oldFrameTime ) / ( cent->lerpFrame.frameTime - cent->lerpFrame.oldFrameTime ); } ent.frame = cent->lerpFrame.frame + s1->frame; // offset if ( ent.frame >= s1->legsAnim + s1->torsoAnim ) { ent.frame -= s1->torsoAnim; } ent.oldframe = cent->lerpFrame.oldFrame + s1->frame; // offset if ( ent.oldframe >= s1->legsAnim + s1->torsoAnim ) { ent.oldframe -= s1->torsoAnim; } ent.backlerp = cent->lerpFrame.backlerp; // CG_Printf( "Gamemodel: oldframe: %i frame: %i lerp: %f\n", ent.oldframe, ent.frame, ent.backlerp ); } // xkan, 11/27/2002 - only advance/change frame if the game model has not // been stopped (teamNum != 1) if ( cent->trailTime && s1->teamNum != 1 ) { cent->lerpFrame.oldFrame = cent->lerpFrame.frame; cent->lerpFrame.frame = s1->legsAnim; cent->lerpFrame.oldFrameTime = cent->lerpFrame.frameTime; cent->lerpFrame.frameTime = cg.time; ent.oldframe = ent.frame; ent.frame = s1->legsAnim; ent.backlerp = 0; } if ( cent->nextState.animMovetype != s1->animMovetype ) { cent->trailTime = 1; } else { cent->trailTime = 0; } if ( s1->modelindex2 ) { ent.customSkin = cgs.gameModelSkins[s1->modelindex2]; } } // special shader if under construction if ( cent->currentState.powerups == STATE_UNDERCONSTRUCTION ) { /*if( cent->currentState.solid == SOLID_BMODEL ) { ent.customShader = cgs.media.genericConstructionShaderBrush; } else { ent.customShader = cgs.media.genericConstructionShaderModel; }*/ ent.customShader = cgs.media.genericConstructionShader; } // add to refresh list trap_R_AddRefEntityToScene( &ent ); memcpy( ¢->refEnt, &ent, sizeof( refEntity_t ) ); } /* ================== CG_Speaker Speaker entities can automatically play sounds ================== */ static void CG_Speaker( centity_t *cent ) { if ( !cent->currentState.clientNum ) { // FIXME: use something other than clientNum... return; // not auto triggering } if ( cg.time < cent->miscTime ) { return; } trap_S_StartSound( NULL, cent->currentState.number, CHAN_ITEM, cgs.gameSounds[cent->currentState.eventParm] ); // ent->s.frame = ent->wait * 10; // ent->s.clientNum = ent->random * 10; cent->miscTime = cg.time + cent->currentState.frame * 100 + cent->currentState.clientNum * 100 * crandom(); } qboolean CG_PlayerSeesItem( playerState_t *ps, entityState_t *item, int atTime, int itemType ) { vec3_t vorigin, eorigin, viewa, dir; float dot, dist, foo; trace_t tr; BG_EvaluateTrajectory( &item->pos, atTime, eorigin, qfalse, item->effect2Time ); VectorCopy( ps->origin, vorigin ); vorigin[2] += ps->viewheight; // get the view loc up to the viewheight // eorigin[2] += 8; // and subtract the item's offset (that is used to place it on the ground) VectorSubtract( vorigin, eorigin, dir ); dist = VectorNormalize( dir ); // dir is now the direction from the item to the player if ( dist > 255 ) { return qfalse; // only run the remaining stuff on items that are close enough } // (SA) FIXME: do this without AngleVectors. // It'd be nice if the angle vectors for the player // have already been figured at this point and I can // just pick them up. (if anybody is storing this somewhere, // for the current frame please let me know so I don't // have to do redundant calcs) AngleVectors( ps->viewangles, viewa, 0, 0 ); dot = DotProduct( viewa, dir ); // give more range based on distance (the hit area is wider when closer) // foo = -0.94f - (dist/255.0f) * 0.057f; // (ranging from -0.94 to -0.997) (it happened to be a pretty good range) foo = -0.94f - ( dist * ( 1.0f / 255.0f ) ) * 0.057f; // (ranging from -0.94 to -0.997) (it happened to be a pretty good range) /// Com_Printf("test: if(%f > %f) return qfalse (dot > foo)\n", dot, foo); if ( dot > foo ) { return qfalse; } // (SA) okay, everything else is okay, so do a bloody trace. (so coronas on treasure doesn't show through walls) if ( itemType == IT_TREASURE ) { CG_Trace( &tr, vorigin, NULL, NULL, eorigin, -1, MASK_SOLID ); if ( tr.fraction != 1 ) { return qfalse; } } return qtrue; } /* ================== CG_Item ================== */ static void CG_Item( centity_t *cent ) { refEntity_t ent; entityState_t *es; gitem_t *item; // float scale; qboolean hasStand, highlight; float highlightFadeScale = 1.0f; es = ¢->currentState; hasStand = qfalse; highlight = qfalse; // (item index is stored in es->modelindex for item) if ( es->modelindex >= bg_numItems ) { CG_Error( "Bad item index %i on entity", es->modelindex ); } // if set to invisible, skip if ( !es->modelindex || ( es->eFlags & EF_NODRAW ) ) { return; } item = &bg_itemlist[ es->modelindex ]; // scale = 0.005 + cent->currentState.number * 0.00001; memset( &ent, 0, sizeof( ent ) ); ent.nonNormalizedAxes = qfalse; if ( item->giType == IT_WEAPON ) { weaponInfo_t *weaponInfo = &cg_weapons[item->giTag]; if ( weaponInfo->standModel ) { hasStand = qtrue; } if ( hasStand ) { // first try to put the weapon on it's 'stand' refEntity_t stand; memset( &stand, 0, sizeof( stand ) ); stand.hModel = weaponInfo->standModel; if ( es->eFlags & EF_SPINNING ) { if ( es->groundEntityNum == -1 || !es->groundEntityNum ) { // (SA) spinning with a stand will spin the stand and the attached weap (only when in the air) VectorCopy( cg.autoAnglesSlow, cent->lerpAngles ); VectorCopy( cg.autoAnglesSlow, cent->lastLerpAngles ); } else { VectorCopy( cent->lastLerpAngles, cent->lerpAngles ); // make a tossed weapon sit on the ground in a position that matches how it was yawed } } AnglesToAxis( cent->lerpAngles, stand.axis ); VectorCopy( cent->lerpOrigin, stand.origin ); // scale the stand to match the weapon scale ( the weapon will also be scaled inside CG_PositionEntityOnTag() ) VectorScale( stand.axis[0], 1.5, stand.axis[0] ); VectorScale( stand.axis[1], 1.5, stand.axis[1] ); VectorScale( stand.axis[2], 1.5, stand.axis[2] ); //----(SA) modified if ( cent->currentState.frame ) { CG_PositionEntityOnTag( &ent, &stand, va( "tag_stand%d", cent->currentState.frame ), 0, NULL ); } else { CG_PositionEntityOnTag( &ent, &stand, "tag_stand", 0, NULL ); } //----(SA) end VectorCopy( ent.origin, ent.oldorigin ); ent.nonNormalizedAxes = qtrue; } else { // then default to laying it on it's side if ( weaponInfo->droppedAnglesHack ) { cent->lerpAngles[2] += 90; } AnglesToAxis( cent->lerpAngles, ent.axis ); // increase the size of the weapons when they are presented as items VectorScale( ent.axis[0], 1.5, ent.axis[0] ); VectorScale( ent.axis[1], 1.5, ent.axis[1] ); VectorScale( ent.axis[2], 1.5, ent.axis[2] ); ent.nonNormalizedAxes = qtrue; VectorCopy( cent->lerpOrigin, ent.origin ); VectorCopy( cent->lerpOrigin, ent.oldorigin ); if ( es->eFlags & EF_SPINNING ) { // spinning will override the angles set by a stand if ( es->groundEntityNum == -1 || !es->groundEntityNum ) { // (SA) spinning with a stand will spin the stand and the attached weap (only when in the air) VectorCopy( cg.autoAnglesSlow, cent->lerpAngles ); VectorCopy( cg.autoAnglesSlow, cent->lastLerpAngles ); } else { VectorCopy( cent->lastLerpAngles, cent->lerpAngles ); // make a tossed weapon sit on the ground in a position that matches how it was yawed } } } } else { AnglesToAxis( cent->lerpAngles, ent.axis ); VectorCopy( cent->lerpOrigin, ent.origin ); VectorCopy( cent->lerpOrigin, ent.oldorigin ); if ( es->eFlags & EF_SPINNING ) { // spinning will override the angles set by a stand VectorCopy( cg.autoAnglesSlow, cent->lerpAngles ); AxisCopy( cg.autoAxisSlow, ent.axis ); } } if ( es->modelindex2 ) { // modelindex2 was specified for the ent, meaning it probably has an alternate model (as opposed to the one in the itemlist) // try to load it first, and if it fails, default to the itemlist model ent.hModel = cgs.gameModels[ es->modelindex2 ]; } else { //if( item->giType == IT_WEAPON && cg_items[es->modelindex].models[2]) // check if there's a specific model for weapon pickup placement // ent.hModel = cg_items[es->modelindex].models[2]; if ( item->giType == IT_WEAPON ) { ent.hModel = cg_weapons[item->giTag].weaponModel[W_PU_MODEL].model; if ( item->giTag == WP_AMMO ) { if ( cent->currentState.density == 2 ) { ent.customShader = cg_weapons[item->giTag].modModels[0]; } } } else { ent.hModel = cg_items[es->modelindex].models[0]; } } //----(SA) find midpoint for highlight corona. // Can't do it when item is registered since it wouldn't know about replacement model if ( !( cent->usehighlightOrigin ) ) { vec3_t mins, maxs, offset; int i; trap_R_ModelBounds( ent.hModel, mins, maxs ); // get bounds for ( i = 0 ; i < 3 ; i++ ) { offset[i] = mins[i] + 0.5 * ( maxs[i] - mins[i] ); // find object-space center } VectorCopy( cent->lerpOrigin, cent->highlightOrigin ); // set 'midpoint' to origin for ( i = 0 ; i < 3 ; i++ ) { // adjust midpoint by offset and orientation cent->highlightOrigin[i] += offset[0] * ent.axis[0][i] + offset[1] * ent.axis[1][i] + offset[2] * ent.axis[2][i]; } cent->usehighlightOrigin = qtrue; } // 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; // } // highlighting items the player looks at if ( cg_drawCrosshairPickups.integer ) { if ( cg_drawCrosshairPickups.integer == 2 ) { // '2' is 'force highlights' highlight = qtrue; } if ( CG_PlayerSeesItem( &cg.predictedPlayerState, es, cg.time, item->giType ) ) { highlight = qtrue; if ( item->giType == IT_TREASURE ) { trap_R_AddCoronaToScene( cent->highlightOrigin, 1, 0.85, 0.5, 2, cent->currentState.number, qtrue ); //----(SA) add corona to treasure } } else { if ( item->giType == IT_TREASURE ) { trap_R_AddCoronaToScene( cent->highlightOrigin, 1, 0.85, 0.5, 2, cent->currentState.number, qfalse ); //----(SA) "empty corona" for proper fades } } //----(SA) added fixed item highlight fading if ( highlight ) { if ( !cent->highlighted ) { cent->highlighted = qtrue; cent->highlightTime = cg.time; } ent.hilightIntensity = ( ( cg.time - cent->highlightTime ) / 250.0f ) * highlightFadeScale; // .25 sec to brighten up } else { if ( cent->highlighted ) { cent->highlighted = qfalse; cent->highlightTime = cg.time; } ent.hilightIntensity = 1.0f - ( ( cg.time - cent->highlightTime ) / 1000.0f ) * highlightFadeScale; // 1 sec to dim down (diff in time causes problems if you quickly flip to/away from looking at the item) } if ( ent.hilightIntensity < 0.25f ) { // leave a minlight ent.hilightIntensity = 0.25f; } if ( ent.hilightIntensity > 1 ) { ent.hilightIntensity = 1.0; } } //----(SA) end // add to refresh list trap_R_AddRefEntityToScene( &ent ); } //============================================================================ /* =============== CG_Bomb =============== */ static void CG_Bomb( centity_t *cent ) { refEntity_t ent, beam; entityState_t *s1; const weaponInfo_t *weapon; vec3_t end; trace_t trace; memset( &ent, 0, sizeof( ent ) ); s1 = ¢->currentState; weapon = &cg_weapons[WP_TRIPMINE]; VectorCopy( s1->origin2, ent.axis[0] ); PerpendicularVector( ent.axis[1], ent.axis[0] ); CrossProduct( ent.axis[0], ent.axis[1], ent.axis[2] ); VectorCopy( cent->lerpOrigin, ent.origin ); VectorCopy( cent->lerpOrigin, ent.oldorigin ); ent.hModel = weapon->missileModel; ent.renderfx = weapon->missileRenderfx | RF_NOSHADOW; CG_AddRefEntityWithPowerups( &ent, s1->powerups, TEAM_FREE, s1, vec3_origin ); memset( &beam, 0, sizeof( beam ) ); VectorCopy( cent->lerpOrigin, beam.origin ); VectorMA( cent->lerpOrigin, 4096, s1->origin2, end ); trap_CM_BoxTrace( &trace, cent->lerpOrigin, end, NULL, NULL, 0, MASK_SHOT ); VectorCopy( trace.endpos, beam.oldorigin ); beam.reType = RT_RAIL_CORE; beam.renderfx = RF_NOSHADOW; beam.customShader = cgs.media.railCoreShader; beam.shaderRGBA[0] = 255; beam.shaderRGBA[1] = 0; beam.shaderRGBA[2] = 0; beam.shaderRGBA[3] = 255; AxisClear( beam.axis ); trap_R_AddRefEntityToScene( &beam ); } /* =============== CG_Smoker =============== */ static void CG_Smoker( centity_t *cent ) { // this ent has some special setting up // time = speed // time2 = duration // angles2[0] = start_size // angles2[1] = end_size // angles2[2] = wait // dl_intensity = health // constantLight = delay // origin2 = normal to emit particles along if ( cg.time - cent->highlightTime > cent->currentState.constantLight ) { // FIXME: make this framerate independant? cent->highlightTime = cg.time; // fire a particle this frame if ( cent->currentState.modelindex2 ) { CG_ParticleSmoke( cgs.gameShaders[cent->currentState.modelindex2], cent ); } else if ( cent->currentState.density == 3 ) { // cannon CG_ParticleSmoke( cgs.media.smokePuffShaderdirty, cent ); } else if ( !( cent->currentState.density ) ) { CG_ParticleSmoke( cgs.media.smokePuffShader, cent ); } else { CG_ParticleSmoke( cgs.media.smokePuffShader, cent ); } } cent->lastTrailTime = cg.time; // time we were last received at the client } /* =============== CG_Missile =============== */ static void CG_DrawMineMarkerFlag( centity_t *cent, refEntity_t *ent, const weaponInfo_t *weapon ) { entityState_t *s1; s1 = ¢->currentState; ent->hModel = cent->currentState.otherEntityNum2 ? weapon->modModels[1] : weapon->modModels[0]; ent->origin[2] += 8; ent->oldorigin[2] += 8; // 20 frames if ( cg.time >= cent->lerpFrame.frameTime ) { cent->lerpFrame.oldFrameTime = cent->lerpFrame.frameTime; cent->lerpFrame.oldFrame = cent->lerpFrame.frame; while ( cg.time >= cent->lerpFrame.frameTime ) { cent->lerpFrame.frameTime += 50; // 1000 / fps (which is 20) cent->lerpFrame.frame++; if ( cent->lerpFrame.frame >= 20 ) { cent->lerpFrame.frame = 0; } } } if ( cent->lerpFrame.frameTime == cent->lerpFrame.oldFrameTime ) { cent->lerpFrame.backlerp = 0; } else { cent->lerpFrame.backlerp = 1.0 - (float)( cg.time - cent->lerpFrame.oldFrameTime ) / ( cent->lerpFrame.frameTime - cent->lerpFrame.oldFrameTime ); } ent->frame = cent->lerpFrame.frame + s1->frame; // offset if ( ent->frame >= 20 ) { ent->frame -= 20; } ent->oldframe = cent->lerpFrame.oldFrame + s1->frame; // offset if ( ent->oldframe >= 20 ) { ent->oldframe -= 20; } ent->backlerp = cent->lerpFrame.backlerp; } extern void CG_RocketTrail( centity_t *ent, const weaponInfo_t *wi ); static void CG_Missile( centity_t *cent ) { refEntity_t ent; entityState_t *s1; const weaponInfo_t *weapon; s1 = ¢->currentState; if ( s1->weapon > WP_NUM_WEAPONS ) { s1->weapon = 0; } weapon = &cg_weapons[s1->weapon]; // calculate the axis VectorCopy( s1->angles, cent->lerpAngles ); if ( s1->weapon == WP_SMOKE_BOMB ) { // Arnout: the smoke effect CG_RenderSmokeGrenadeSmoke( cent, weapon ); } else if ( s1->weapon == WP_SATCHEL && s1->clientNum == cg.snap->ps.clientNum ) { // rain - use snap client number so that the detonator works // right when spectating (#218) cg.satchelCharge = cent; } else if ( s1->weapon == WP_ARTY && s1->otherEntityNum2 && s1->teamNum == cgs.clientinfo[ cg.clientNum ].team ) { VectorCopy( cent->lerpOrigin, cg.artilleryRequestPos[s1->clientNum] ); cg.artilleryRequestTime[s1->clientNum] = cg.time; } // add trails if ( cent->currentState.eType == ET_FP_PARTS || cent->currentState.eType == ET_FIRE_COLUMN || cent->currentState.eType == ET_FIRE_COLUMN_SMOKE || cent->currentState.eType == ET_RAMJET ) { CG_RocketTrail( cent, NULL ); } else if ( weapon->missileTrailFunc ) { weapon->missileTrailFunc( cent, weapon ); } // add dynamic light if ( weapon->missileDlight ) { //% trap_R_AddLightToScene(cent->lerpOrigin, weapon->missileDlight, //% weapon->missileDlightColor[0], weapon->missileDlightColor[1], weapon->missileDlightColor[2], 0 ); trap_R_AddLightToScene( cent->lerpOrigin, weapon->missileDlight, 1.0, weapon->missileDlightColor[0], weapon->missileDlightColor[1], weapon->missileDlightColor[2], 0, 0 ); } //----(SA) whoops, didn't mean to check it in with the missile flare // add missile sound if ( weapon->missileSound ) { if ( cent->currentState.weapon == WP_GPG40 || cent->currentState.weapon == WP_M7 ) { if ( !cent->currentState.effect1Time ) { int flytime = cg.time - cent->currentState.pos.trTime; if ( flytime > 300 ) { // have a quick fade in so we don't have a pop vec3_t velocity; int volume = flytime > 375 ? 255 : ( 75.f / ( (float)flytime - 300.f ) ) * 255; BG_EvaluateTrajectoryDelta( ¢->currentState.pos, cg.time, velocity, qfalse, -1 ); trap_S_AddLoopingSound( cent->lerpOrigin, velocity, weapon->missileSound, volume, 0 ); } } } else { vec3_t velocity; BG_EvaluateTrajectoryDelta( ¢->currentState.pos, cg.time, velocity, qfalse, -1 ); trap_S_AddLoopingSound( cent->lerpOrigin, velocity, weapon->missileSound, 255, 0 ); } } // DHM - Nerve :: Don't tick until armed if ( cent->currentState.weapon == WP_DYNAMITE ) { if ( cent->currentState.teamNum < 4 ) { vec3_t velocity; BG_EvaluateTrajectoryDelta( ¢->currentState.pos, cg.time, velocity, qfalse, -1 ); trap_S_AddLoopingSound( cent->lerpOrigin, velocity, weapon->spindownSound, 255, 0 ); } } // create the render entity memset( &ent, 0, sizeof( ent ) ); VectorCopy( cent->lerpOrigin, ent.origin ); VectorCopy( cent->lerpOrigin, ent.oldorigin ); // flicker between two skins ent.skinNum = cg.clientFrame & 1; if ( cent->currentState.eType == ET_FP_PARTS ) { ent.hModel = cgs.gameModels[cent->currentState.modelindex]; } else if ( cent->currentState.eType == ET_EXPLO_PART ) { ent.hModel = cgs.gameModels[cent->currentState.modelindex]; } else if ( cent->currentState.eType == ET_FLAMEBARREL ) { ent.hModel = cgs.media.flamebarrel; } else if ( cent->currentState.eType == ET_FIRE_COLUMN || cent->currentState.eType == ET_FIRE_COLUMN_SMOKE ) { // it may have a model sometime in the future ent.hModel = 0; } else if ( cent->currentState.eType == ET_RAMJET ) { ent.hModel = 0; // ent.hModel = cgs.gameModels[cent->currentState.modelindex]; } else { team_t missileTeam = cent->currentState.weapon == WP_LANDMINE ? cent->currentState.teamNum % 4 : cent->currentState.teamNum; ent.hModel = weapon->missileModel; if ( missileTeam == TEAM_ALLIES ) { ent.customSkin = weapon->missileAlliedSkin; } else if ( missileTeam == TEAM_AXIS ) { ent.customSkin = weapon->missileAxisSkin; } } ent.renderfx = weapon->missileRenderfx | RF_NOSHADOW; if ( cent->currentState.weapon == WP_LANDMINE ) { if ( cgs.clientinfo[ cg.clientNum ].team == TEAM_SPECTATOR ) { return; } VectorCopy( ent.origin, ent.lightingOrigin ); ent.renderfx |= RF_LIGHTING_ORIGIN; if ( cent->currentState.teamNum < 4 ) { ent.origin[2] -= 8; ent.oldorigin[2] -= 8; if ( ( cgs.clientinfo[cg.snap->ps.clientNum].team != ( !cent->currentState.otherEntityNum2 ? TEAM_ALLIES : TEAM_AXIS ) ) ) { if ( cent->currentState.density - 1 == cg.snap->ps.clientNum ) { //ent.customShader = cgs.media.genericConstructionShaderModel; ent.customShader = cgs.media.genericConstructionShader; } else if ( !cent->currentState.modelindex2 ) { // see if we have the skill to see them and are close enough if ( cgs.clientinfo[cg.snap->ps.clientNum].skill[SK_BATTLE_SENSE] >= 4 ) { vec_t distSquared = DistanceSquared( cent->lerpOrigin, cg.predictedPlayerEntity.lerpOrigin ); if ( distSquared > Square( 256 ) ) { return; } else { //ent.customShader = cgs.media.genericConstructionShaderModel; ent.customShader = cgs.media.genericConstructionShader; } } else { return; } } else { CG_DrawMineMarkerFlag( cent, &ent, weapon ); } } else { CG_DrawMineMarkerFlag( cent, &ent, weapon ); /*if ( !cent->highlighted ) { cent->highlighted = qtrue; cent->highlightTime = cg.time; } ent.hilightIntensity = 0.5f * sin((cg.time-cent->highlightTime)/1000.f) + 1.f;*/ } } if ( cent->currentState.teamNum >= 8 ) { ent.origin[2] -= 8; ent.oldorigin[2] -= 8; } } // convert direction of travel into axis if ( cent->currentState.weapon == WP_MORTAR_SET ) { vec3_t delta; if ( VectorCompare( cent->rawOrigin, vec3_origin ) ) { VectorSubtract( cent->lerpOrigin, s1->pos.trBase, delta ); VectorCopy( cent->lerpOrigin, cent->rawOrigin ); } else { VectorSubtract( cent->lerpOrigin, cent->rawOrigin, delta ); if ( !VectorCompare( cent->lerpOrigin, cent->rawOrigin ) ) { VectorCopy( cent->lerpOrigin, cent->rawOrigin ); } } if ( VectorNormalize2( delta, ent.axis[0] ) == 0 ) { ent.axis[0][2] = 1; } } else if ( VectorNormalize2( s1->pos.trDelta, ent.axis[0] ) == 0 ) { ent.axis[0][2] = 1; } // spin as it moves if ( s1->pos.trType != TR_STATIONARY ) { RotateAroundDirection( ent.axis, cg.time / 4 ); } else { RotateAroundDirection( ent.axis, s1->time ); } // Rafael // Added this since it may be a propExlosion if ( ent.hModel ) { // add to refresh list, possibly with quad glow CG_AddRefEntityWithPowerups( &ent, s1->powerups, TEAM_FREE, s1, vec3_origin ); } } // DHM - Nerve :: capture and hold flag static animation_t multi_flagpoleAnims[] = { { 0, "", 0, 1, 0, 1000 / 15, 1000 / 15 }, // (no flags, idle) { 0, "", 0, 15, 0, 1000 / 15, 1000 / 15 }, // (axis flag rising) { 0, "", 490, 15, 0, 1000 / 15, 1000 / 15 }, // (american flag rising) { 0, "", 20, 211, 211, 1000 / 15, 1000 / 15 }, // (axis flag raised) { 0, "", 255, 211, 211, 1000 / 15, 1000 / 15 }, // (american flag raised) { 0, "", 235, 15, 0, 1000 / 15, 1000 / 15 }, // (axis switching to american) { 0, "", 470, 15, 0, 1000 / 15, 1000 / 15 }, // (american switching to axis) { 0, "", 510, 15, 0, 1000 / 15, 1000 / 15 }, // (axis flag falling) { 0, "", 530, 15, 0, 1000 / 15, 1000 / 15 } // (american flag falling) }; // dhm - end extern void CG_RunLerpFrame( centity_t *cent, clientInfo_t *ci, lerpFrame_t *lf, int newAnimation, float speedScale ); /* ============== CG_TrapSetAnim ============== */ static void CG_TrapSetAnim( centity_t *cent, lerpFrame_t *lf, int newAnim ) { // transition animation lf->animationNumber = cent->currentState.frame; // DHM - Nerve :: teamNum specifies which set of animations to use (only 1 exists right now) switch ( cent->currentState.teamNum ) { case 1: lf->animation = &multi_flagpoleAnims[ cent->currentState.frame ]; break; default: return; } lf->animationTime = lf->frameTime + lf->animation->initialLerp; } /* ============== CG_Trap // TODO: change from 'trap' to something else. 'trap' is a misnomer. it's actually used for other stuff too ============== */ static void CG_Trap( centity_t *cent ) { refEntity_t ent; entityState_t *cs; lerpFrame_t *traplf; memset( &ent, 0, sizeof( ent ) ); cs = ¢->currentState; traplf = ¢->lerpFrame; // initial setup if ( !traplf->oldFrameTime ) { traplf->frameTime = traplf->oldFrameTime = cg.time; CG_TrapSetAnim( cent, traplf, cs->frame ); traplf->frame = traplf->oldFrame = traplf->animation->firstFrame; } // transition to new anim if requested if ( ( traplf->animationNumber != cs->frame ) || !traplf->animation ) { CG_TrapSetAnim( cent, traplf, cs->frame ); } CG_RunLerpFrame( cent, NULL, traplf, 0, 1 ); // use existing lerp code rather than re-writing ent.frame = traplf->frame; ent.oldframe = traplf->oldFrame; ent.backlerp = traplf->backlerp; VectorCopy( cent->lerpOrigin, ent.origin ); VectorCopy( cent->lerpOrigin, ent.oldorigin ); ent.hModel = cgs.gameModels[cs->modelindex]; AnglesToAxis( cent->lerpAngles, ent.axis ); trap_R_AddRefEntityToScene( &ent ); memcpy( ¢->refEnt, &ent, sizeof( refEntity_t ) ); } //----(SA) end /* ============== CG_Corona ============== */ static void CG_Corona( centity_t *cent ) { trace_t tr; int r, g, b; int dli; qboolean visible = qfalse, behind = qfalse, toofar = qfalse; float dot, dist; vec3_t dir; if ( cg_coronas.integer == 0 ) { // if set to '0' no coronas return; } dli = cent->currentState.dl_intensity; r = dli & 255; g = ( dli >> 8 ) & 255; b = ( dli >> 16 ) & 255; // only coronas that are in your PVS are being added VectorSubtract( cg.refdef_current->vieworg, cent->lerpOrigin, dir ); dist = VectorNormalize2( dir, dir ); if ( dist > cg_coronafardist.integer ) { // performance variable cg_coronafardist will keep down super long traces toofar = qtrue; } dot = DotProduct( dir, cg.refdef_current->viewaxis[0] ); if ( dot >= -0.6 ) { // assumes ~90 deg fov (SA) changed value to 0.6 (screen corner at 90 fov) behind = qtrue; // use the dot to at least do trivial removal of those behind you. } // yeah, I could calc side planes to clip against, but would that be worth it? (much better than dumb dot>= thing?) // CG_Printf("dot: %f\n", dot); if ( cg_coronas.integer == 2 ) { // if set to '2' trace everything behind = qfalse; toofar = qfalse; } if ( !behind && !toofar ) { CG_Trace( &tr, cg.refdef_current->vieworg, NULL, NULL, cent->lerpOrigin, -1, MASK_SOLID | CONTENTS_BODY ); // added blockage by players. not sure how this is going to be since this is their bb, not their model (too much blockage) if ( tr.fraction == 1 ) { visible = qtrue; } trap_R_AddCoronaToScene( cent->lerpOrigin, (float)r / 255.0f, (float)g / 255.0f, (float)b / 255.0f, (float)cent->currentState.density / 255.0f, cent->currentState.number, visible ); } } /* ============== CG_Efx ============== */ static void CG_SpotlightEfx( centity_t *cent ) { vec3_t targetpos, normalized_direction, direction; float dist, fov = 90; vec4_t color = {1, 1, 1, .1}; int splinetarget = 0; char *cs; VectorCopy( cent->currentState.origin2, targetpos ); splinetarget = cent->overheatTime; if ( !splinetarget ) { cs = (char *)CG_ConfigString( CS_SPLINES + cent->currentState.density ); cent->overheatTime = splinetarget = CG_LoadCamera( va( "cameras/%s.camera", cs ) ); if ( splinetarget != -1 ) { trap_startCamera( splinetarget, cg.time ); } } else { vec3_t angles; if ( splinetarget != -1 ) { if ( trap_getCameraInfo( splinetarget, cg.time, &targetpos, &angles, &fov ) ) { } else { // loop trap_startCamera( splinetarget, cg.time ); trap_getCameraInfo( splinetarget, cg.time, &targetpos, &angles, &fov ); } } } normalized_direction[0] = direction[0] = targetpos[0] - cent->currentState.origin[0]; normalized_direction[1] = direction[1] = targetpos[1] - cent->currentState.origin[1]; normalized_direction[2] = direction[2] = targetpos[2] - cent->currentState.origin[2]; dist = VectorNormalize( normalized_direction ); if ( dist == 0 ) { return; } CG_Spotlight( cent, color, cent->currentState.origin, normalized_direction, 999, 2048, 10, fov, 0 ); } //----(SA) adding func_explosive /* =============== CG_Explosive This is currently almost exactly the same as CG_Mover It's split out so that any changes or experiments are unattached to anything else. =============== */ static void CG_Explosive( centity_t *cent ) { refEntity_t ent; entityState_t *s1; s1 = ¢->currentState; // create the render entity memset( &ent, 0, sizeof( ent ) ); VectorCopy( cent->lerpOrigin, ent.origin ); // VectorCopy( cent->lerpOrigin, ent.oldorigin); // VectorCopy( ent.origin, cent->lerpOrigin); AnglesToAxis( cent->lerpAngles, ent.axis ); ent.renderfx = RF_NOSHADOW; // get the model, either as a bmodel or a modelindex if ( s1->solid == SOLID_BMODEL ) { ent.hModel = cgs.inlineDrawModel[s1->modelindex]; } else { ent.hModel = cgs.gameModels[s1->modelindex]; } // add to refresh list // trap_R_AddRefEntityToScene(&ent); // add the secondary model if ( s1->modelindex2 ) { ent.skinNum = 0; ent.hModel = cgs.gameModels[s1->modelindex2]; trap_R_AddRefEntityToScene( &ent ); } else { trap_R_AddRefEntityToScene( &ent ); } } /* =============== CG_Constructible This is currently almost exactly the same as CG_Mover It's split out so that any changes or experiments are unattached to anything else. =============== */ static void CG_Constructible( centity_t *cent ) { refEntity_t ent; entityState_t *s1; s1 = ¢->currentState; // create the render entity memset( &ent, 0, sizeof( ent ) ); VectorCopy( cent->lerpOrigin, ent.origin ); VectorCopy( cent->lerpOrigin, ent.oldorigin ); // VectorCopy( ent.origin, cent->lerpOrigin); AnglesToAxis( cent->lerpAngles, ent.axis ); ent.renderfx = RF_NOSHADOW; // CG_Printf( "Adding constructible: %s\n", CG_ConfigString( CS_OID_NAMES + cent->currentState.otherEntityNum2 ) ); if ( s1->modelindex ) { // get the model, either as a bmodel or a modelindex //if ( s1->solid == SOLID_BMODEL ) { ent.hModel = cgs.inlineDrawModel[s1->modelindex]; //} else { // ent.hModel = cgs.gameModels[s1->modelindex]; //} // add to refresh list // trap_R_AddRefEntityToScene(&ent); // ent.shaderRGBA[0] = ent.shaderRGBA[1] = ent.shaderRGBA[2] = 0xff; // ent.shaderRGBA[3] = s1->density; //if( s1->angles2[0] < 255 ) //if( cent->currentState.powerups == STATE_UNDERCONSTRUCTION ) // ent.customShader = cgs.media.genericConstructionShaderBrush; trap_R_AddRefEntityToScene( &ent ); } // add the secondary model if ( s1->modelindex2 ) { if ( cent->currentState.powerups == STATE_UNDERCONSTRUCTION ) { //ent.customShader = cgs.media.genericConstructionShaderBrush; ent.customShader = cgs.media.genericConstructionShader; /*switch( cent->currentState.frame ) { case 1: trap_S_AddLoopingSound( cent->currentState.origin2, vec3_origin, cgs.media.buildSound[0], 255, 0 ); break; case 2: trap_S_AddLoopingSound( cent->currentState.origin2, vec3_origin, cgs.media.buildSound[1], 255, 0 ); break; case 3: trap_S_AddLoopingSound( cent->currentState.origin2, vec3_origin, cgs.media.buildSound[2], 255, 0 ); break; case 4: trap_S_AddLoopingSound( cent->currentState.origin2, vec3_origin, cgs.media.buildSound[3], 255, 0 ); break; }*/ } //if ( s1->solid == SOLID_BMODEL ) { ent.hModel = cgs.inlineDrawModel[s1->modelindex2]; //} else { // ent.hModel = cgs.gameModels[s1->modelindex2]; //} trap_R_AddRefEntityToScene( &ent ); } //else // trap_R_AddRefEntityToScene(&ent); } /* =============== CG_Waypoint =============== */ /* static void CG_Waypoint( centity_t *cent ) { refEntity_t ent; memset( &ent, 0, sizeof( ent ) ); ent.reType = RT_SPRITE; VectorCopy( cent->lerpOrigin, ent.origin ); ent.origin[2] += 24; VectorCopy( ent.origin, ent.oldorigin ); ent.radius = 14; switch( cent->currentState.frame ) { case WAYP_ATTACK: ent.customShader = cgs.media.waypointAttackShader; break; case WAYP_DEFEND: ent.customShader = cgs.media.waypointDefendShader; break; case WAYP_REGROUP: ent.customShader = cgs.media.waypointRegroupShader; break; // TAT 8/29/2002 - Use the bot shader for bots case WAYP_BOT: ent.customShader = cgs.media.waypointBotShader; break; // TAT 1/13/2003 - and the queued shader for queued commands case WAYP_BOTQUEUED: ent.customShader = cgs.media.waypointBotQueuedShader; break; } ent.shaderRGBA[0] = 255; ent.shaderRGBA[1] = 255; ent.shaderRGBA[2] = 255; ent.shaderRGBA[3] = 255; trap_R_AddRefEntityToScene(&ent); } */ /* =============== CG_ConstructibleMarker =============== */ /*static void CG_ConstructibleMarker( centity_t *cent ) { refEntity_t ent; entityState_t *s1; s1 = ¢->currentState; // create the render entity memset (&ent, 0, sizeof(ent)); VectorCopy( cent->lerpOrigin, ent.origin); VectorCopy( cent->lerpOrigin, ent.oldorigin); // special shader if under construction if( cent->currentState.powerups == STATE_UNDERCONSTRUCTION ) ent.customShader = cgs.media.genericConstructionShader; // add the secondary model if ( s1->modelindex2 ) { AnglesToAxis( cent->lerpAngles, ent.axis ); ent.skinNum = 0; ent.hModel = cgs.gameModels[s1->modelindex2]; ent.frame = s1->frame; if( s1->effect1Time ) ent.customSkin = cgs.gameModelSkins[s1->effect1Time]; trap_R_AddRefEntityToScene(&ent); memcpy( ¢->refEnt, &ent, sizeof(refEntity_t) ); } else { AnglesToAxis( vec3_origin, ent.axis ); trap_R_AddRefEntityToScene(&ent); } }*/ //----(SA) done // declaration for add bullet particles (might as well stick this one in a .h file I think) extern void CG_AddBulletParticles( vec3_t origin, vec3_t dir, int speed, int duration, int count, float randScale ); /* =============== CG_Mover =============== */ static void CG_Mover( centity_t *cent ) { refEntity_t ent; entityState_t *s1; s1 = ¢->currentState; // create the render entity memset( &ent, 0, sizeof( ent ) ); VectorCopy( cent->lerpOrigin, ent.origin ); VectorCopy( cent->lerpOrigin, ent.oldorigin ); AnglesToAxis( cent->lerpAngles, ent.axis ); ent.renderfx = 0; ent.skinNum = 0; // get the model, either as a bmodel or a modelindex if ( s1->solid == SOLID_BMODEL ) { ent.hModel = cgs.inlineDrawModel[s1->modelindex]; } else { ent.hModel = cgs.gameModels[s1->modelindex]; } // Rafael // testing for mike to get movers to scale if ( cent->currentState.density & 1 ) { VectorScale( ent.axis[0], cent->currentState.angles2[0], ent.axis[0] ); VectorScale( ent.axis[1], cent->currentState.angles2[1], ent.axis[1] ); VectorScale( ent.axis[2], cent->currentState.angles2[2], ent.axis[2] ); ent.nonNormalizedAxes = qtrue; } if ( cent->currentState.eType == ET_ALARMBOX ) { ent.renderfx |= RF_MINLIGHT; } // special shader if under construction if ( cent->currentState.powerups == STATE_UNDERCONSTRUCTION ) { /*if( cent->currentState.solid == SOLID_BMODEL ) { ent.customShader = cgs.media.genericConstructionShaderBrush; } else { ent.customShader = cgs.media.genericConstructionShaderModel; }*/ ent.customShader = cgs.media.genericConstructionShader; } // add the secondary model if ( s1->modelindex2 && !( cent->currentState.density & 2 ) ) { ent.hModel = cgs.gameModels[s1->modelindex2]; ent.frame = s1->frame; if ( s1->torsoAnim ) { if ( cg.time >= cent->lerpFrame.frameTime ) { cent->lerpFrame.oldFrameTime = cent->lerpFrame.frameTime; cent->lerpFrame.oldFrame = cent->lerpFrame.frame; while ( cg.time >= cent->lerpFrame.frameTime ) { cent->lerpFrame.frameTime += s1->weapon; cent->lerpFrame.frame++; if ( cent->lerpFrame.frame >= s1->legsAnim + s1->torsoAnim ) { cent->lerpFrame.frame = s1->legsAnim; } } } if ( cent->lerpFrame.frameTime == cent->lerpFrame.oldFrameTime ) { cent->lerpFrame.backlerp = 0; } else { cent->lerpFrame.backlerp = 1.0 - (float)( cg.time - cent->lerpFrame.oldFrameTime ) / ( cent->lerpFrame.frameTime - cent->lerpFrame.oldFrameTime ); } ent.frame = cent->lerpFrame.frame + s1->frame; // offset if ( ent.frame >= s1->legsAnim + s1->torsoAnim ) { ent.frame -= s1->torsoAnim; } ent.oldframe = cent->lerpFrame.oldFrame + s1->frame; // offset if ( ent.oldframe >= s1->legsAnim + s1->torsoAnim ) { ent.oldframe -= s1->torsoAnim; } ent.backlerp = cent->lerpFrame.backlerp; } if ( cent->trailTime ) { cent->lerpFrame.oldFrame = cent->lerpFrame.frame; cent->lerpFrame.frame = s1->legsAnim; cent->lerpFrame.oldFrameTime = cent->lerpFrame.frameTime; cent->lerpFrame.frameTime = cg.time; ent.oldframe = ent.frame; ent.frame = s1->legsAnim; ent.backlerp = 0; } if ( cent->nextState.animMovetype != s1->animMovetype ) { cent->trailTime = 1; } else { cent->trailTime = 0; } trap_R_AddRefEntityToScene( &ent ); memcpy( ¢->refEnt, &ent, sizeof( refEntity_t ) ); } else { trap_R_AddRefEntityToScene( &ent ); } // alarm box spark effects /* if( cent->currentState.eType == ET_ALARMBOX) { if(cent->currentState.frame == 2 ) { // i'm dead if(rand()%50 == 1) { vec3_t angNorm; // normalized angles VectorNormalize2(cent->lerpAngles, angNorm); // (origin, dir, speed, duration, count, 'randscale') CG_AddBulletParticles( cent->lerpOrigin, angNorm, 2, 800, 4, 16.0f ); trap_S_StartSound (NULL, cent->currentState.number, CHAN_AUTO, cgs.media.sparkSounds ); } } }*/ } void CG_Mover_PostProcess( centity_t* cent ) { refEntity_t mg42base; refEntity_t mg42upper; refEntity_t mg42gun; refEntity_t player; refEntity_t flash; vec_t* angles; int i; if ( !( cent->currentState.density & 4 ) ) { // mounted gun return; } if ( cg.snap->ps.eFlags & EF_MOUNTEDTANK && cg_entities[cg.snap->ps.clientNum].tagParent == cent->currentState.effect3Time ) { i = cg.snap->ps.clientNum; } else { for ( i = 0; i < MAX_CLIENTS; i++ ) { // Gordon: is this entity mounted on a tank, and attached to _OUR_ turret entity (which could be us) if ( cg_entities[i].currentValid && ( cg_entities[i].currentState.eFlags & EF_MOUNTEDTANK ) ) { if ( cg_entities[i].tagParent == cent->currentState.effect3Time ) { break; } } } } if ( i != MAX_CLIENTS ) { if ( i != cg.snap->ps.clientNum ) { angles = cg_entities[i].lerpAngles; } else { angles = cg.predictedPlayerState.viewangles; } } else { angles = vec3_origin; } cg_entities[cent->currentState.effect3Time].tankparent = cent - cg_entities; CG_AttachBitsToTank( &cg_entities[cent->currentState.effect3Time], &mg42base, &mg42upper, &mg42gun, &player, &flash, angles, "tag_player", cent->currentState.density & 8 ? qtrue : qfalse ); // Gordon: if we (or someone we're spectating) is on this tank, recalc our view values if ( cg.snap->ps.eFlags & EF_MOUNTEDTANK ) { centity_t* tank = &cg_entities[cg_entities[cg.snap->ps.clientNum].tagParent]; if ( tank == &cg_entities[cent->currentState.effect3Time] ) { CG_CalcViewValues(); } } VectorCopy( mg42base.origin, mg42base.lightingOrigin ); VectorCopy( mg42base.origin, mg42base.oldorigin ); VectorCopy( mg42upper.origin, mg42upper.lightingOrigin ); VectorCopy( mg42upper.origin, mg42upper.oldorigin ); VectorCopy( mg42gun.origin, mg42gun.lightingOrigin ); VectorCopy( mg42gun.origin, mg42gun.oldorigin ); trap_R_AddRefEntityToScene( &mg42base ); if ( i != cg.snap->ps.clientNum || cg.renderingThirdPerson ) { trap_R_AddRefEntityToScene( &mg42upper ); trap_R_AddRefEntityToScene( &mg42gun ); } } /* =============== CG_Beam_2 Gordon: new beam entity, for rope like stuff... =============== */ void CG_Beam_2( centity_t *cent ) { refEntity_t ent; entityState_t *s1; vec3_t origin, origin2; s1 = ¢->currentState; BG_EvaluateTrajectory( &s1->pos, cg.time, origin, qfalse, s1->effect1Time ); BG_EvaluateTrajectory( &s1->apos, cg.time, origin2, qfalse, s1->effect2Time ); // create the render entity memset( &ent, 0, sizeof( ent ) ); VectorCopy( origin, ent.origin ); VectorCopy( origin2, ent.oldorigin ); // CG_Printf( "O: %i %i %i OO: %i %i %i\n", (int)origin[0], (int)origin[1], (int)origin[2], (int)origin2[0], (int)origin2[1], (int)origin2[2] ); AxisClear( ent.axis ); ent.reType = RT_RAIL_CORE; ent.customShader = cgs.gameShaders[s1->modelindex2]; ent.radius = 8; ent.frame = 2; VectorScale( cent->currentState.angles2, 255, ent.shaderRGBA ); ent.shaderRGBA[3] = 255; // add to refresh list trap_R_AddRefEntityToScene( &ent ); } /* =============== CG_Beam Also called as an event =============== */ void CG_Beam( centity_t *cent ) { 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_RAIL_CORE; switch ( s1->legsAnim ) { case 1: ent.customShader = cgs.media.ropeShader; break; default: ent.customShader = cgs.media.railCoreShader; break; } ent.shaderRGBA[0] = s1->angles2[0] * 255; ent.shaderRGBA[1] = s1->angles2[1] * 255; ent.shaderRGBA[2] = s1->angles2[2] * 255; ent.shaderRGBA[3] = 255; ent.renderfx = RF_NOSHADOW; // add to refresh list trap_R_AddRefEntityToScene( &ent ); } /* =============== CG_Portal =============== */ static void CG_Portal( centity_t *cent ) { refEntity_t ent; entityState_t *s1; s1 = ¢->currentState; // create the render entity memset( &ent, 0, sizeof( ent ) ); VectorCopy( cent->lerpOrigin, ent.origin ); VectorCopy( s1->origin2, ent.oldorigin ); ByteToDir( s1->eventParm, ent.axis[0] ); PerpendicularVector( ent.axis[1], ent.axis[0] ); // negating this tends to get the directions like they want // we really should have a camera roll value VectorSubtract( vec3_origin, ent.axis[1], ent.axis[1] ); CrossProduct( ent.axis[0], ent.axis[1], ent.axis[2] ); ent.reType = RT_PORTALSURFACE; ent.frame = s1->frame; // rotation speed ent.skinNum = s1->clientNum / 256.0 * 360; // roll offset // add to refresh list trap_R_AddRefEntityToScene( &ent ); } /* =============== CG_Prop =============== */ static void CG_Prop( centity_t *cent ) { refEntity_t ent; entityState_t *s1; vec3_t angles; float scale; s1 = ¢->currentState; // create the render entity memset( &ent, 0, sizeof( ent ) ); if ( cg.renderingThirdPerson ) { VectorCopy( cent->lerpOrigin, ent.origin ); VectorCopy( cent->lerpOrigin, ent.oldorigin ); ent.frame = s1->frame; ent.oldframe = ent.frame; ent.backlerp = 0; } else { VectorCopy( cg.refdef_current->vieworg, ent.origin ); VectorCopy( cg.refdefViewAngles, angles ); if ( cg.bobcycle & 1 ) { scale = -cg.xyspeed; } else { scale = cg.xyspeed; } // modify angles from bobbing angles[ROLL] += scale * cg.bobfracsin * 0.005; angles[YAW] += scale * cg.bobfracsin * 0.01; angles[PITCH] += cg.xyspeed * cg.bobfracsin * 0.005; VectorCopy( angles, cent->lerpAngles ); ent.frame = s1->frame; ent.oldframe = ent.frame; ent.backlerp = 0; if ( cent->currentState.density ) { ent.frame = s1->frame + cent->currentState.density; ent.oldframe = ent.frame - 1; ent.backlerp = 1 - cg.frameInterpolation; ent.renderfx = RF_DEPTHHACK | RF_FIRST_PERSON; //CG_Printf ("frame %d oldframe %d\n", ent.frame, ent.oldframe); } else if ( ent.frame ) { ent.oldframe -= 1; ent.backlerp = 1 - cg.frameInterpolation; } else { ent.renderfx = RF_DEPTHHACK | RF_FIRST_PERSON; } } AnglesToAxis( cent->lerpAngles, ent.axis ); ent.renderfx |= RF_NOSHADOW; // flicker between two skins (FIXME?) ent.skinNum = ( cg.time >> 6 ) & 1; // get the model, either as a bmodel or a modelindex if ( s1->solid == SOLID_BMODEL ) { ent.hModel = cgs.inlineDrawModel[s1->modelindex]; } else { ent.hModel = cgs.gameModels[s1->modelindex]; } // special shader if under construction if ( cent->currentState.powerups == STATE_UNDERCONSTRUCTION ) { /*if( cent->currentState.solid == SOLID_BMODEL ) { ent.customShader = cgs.media.genericConstructionShaderBrush; } else { ent.customShader = cgs.media.genericConstructionShaderModel; }*/ ent.customShader = cgs.media.genericConstructionShader; } // add the secondary model if ( s1->modelindex2 ) { ent.skinNum = 0; ent.hModel = cgs.gameModels[s1->modelindex2]; ent.frame = s1->frame; trap_R_AddRefEntityToScene( &ent ); memcpy( ¢->refEnt, &ent, sizeof( refEntity_t ) ); } else { trap_R_AddRefEntityToScene( &ent ); } } typedef enum cabinetType_e { CT_AMMO, CT_HEALTH, CT_MAX, } cabinetType_t; #define MAX_CABINET_TAGS 6 typedef struct cabinetTag_s { const char* tagsnames[MAX_CABINET_TAGS]; const char* itemnames[MAX_CABINET_TAGS]; qhandle_t itemmodels[MAX_CABINET_TAGS]; const char* modelName; qhandle_t model; } cabinetTag_t; cabinetTag_t cabinetInfo[CT_MAX] = { { { "tag_ammo01", "tag_ammo02", "tag_ammo03", "tag_ammo04", "tag_ammo05", "tag_ammo06", /* "tag_obj1", "tag_obj1", "tag_obj1", "tag_obj1", "tag_obj1", "tag_obj1",*/ }, { "models/multiplayer/supplies/ammobox_wm.md3", "models/multiplayer/supplies/ammobox_wm.md3", "models/multiplayer/supplies/ammobox_wm.md3", "models/multiplayer/supplies/ammobox_wm.md3", "models/multiplayer/supplies/ammobox_wm.md3", "models/multiplayer/supplies/ammobox_wm.md3", }, { 0, 0, 0, 0, 0, 0 }, "models/mapobjects/supplystands/stand_ammo.md3", // "models/mapobjects/blitz_sd/blitzbody.md3", 0, }, { { "tag_Medikit_01", "tag_Medikit_02", "tag_Medikit_03", "tag_Medikit_04", "tag_Medikit_05", "tag_Medikit_06", }, { "models/multiplayer/supplies/healthbox_wm.md3", "models/multiplayer/supplies/healthbox_wm.md3", "models/multiplayer/supplies/healthbox_wm.md3", "models/multiplayer/supplies/healthbox_wm.md3", "models/multiplayer/supplies/healthbox_wm.md3", "models/multiplayer/supplies/healthbox_wm.md3", }, { 0, 0, 0, 0, 0, 0 }, "models/mapobjects/supplystands/stand_health.md3", 0, }, }; /* ========================= CG_Cabinet ========================= */ void CG_Cabinet( centity_t* cent, cabinetType_t type ) { refEntity_t cabinet; refEntity_t mini_me; int i, cnt; // int k; if ( type < 0 || type >= CT_MAX ) { return; } memset( &cabinet, 0, sizeof( cabinet ) ); memset( &mini_me, 0, sizeof( mini_me ) ); cabinet.hModel = cabinetInfo[type].model; // cabinet.hModel = cabinetInfo[type].itemmodels[0]; cabinet.frame = 0; cabinet.oldframe = 0; cabinet.backlerp = 0.f; VectorCopy( cent->lerpOrigin, cabinet.origin ); VectorCopy( cabinet.origin, cabinet.oldorigin ); VectorCopy( cabinet.origin, cabinet.lightingOrigin ); cabinet.lightingOrigin[2] += 16; cabinet.renderfx |= RF_MINLIGHT; AnglesToAxis( cent->lerpAngles, cabinet.axis ); if ( cent->currentState.onFireStart == -9999 ) { cnt = MAX_CABINET_TAGS; } else { cnt = MAX_CABINET_TAGS * ( cent->currentState.onFireStart / (float)cent->currentState.onFireEnd ); if ( cnt == 0 && cent->currentState.onFireStart ) { cnt = 1; } } for ( i = 0; i < cnt; i++ ) { mini_me.hModel = cabinetInfo[type].itemmodels[i]; CG_PositionEntityOnTag( &mini_me, &cabinet, cabinetInfo[type].tagsnames[i], 0, NULL ); VectorCopy( mini_me.origin, mini_me.oldorigin ); VectorCopy( mini_me.origin, mini_me.lightingOrigin ); mini_me.renderfx |= RF_MINLIGHT; trap_R_AddRefEntityToScene( &mini_me ); } /* for( k = 0; k < 3; k++ ) { VectorScale( cabinet.axis[k], 2.f, cabinet.axis[k] ); } cabinet.nonNormalizedAxes = qtrue;*/ trap_R_AddRefEntityToScene( &cabinet ); } void CG_SetupCabinets( void ) { int i, j; for ( i = 0; i < CT_MAX; i++ ) { cabinetInfo[i].model = trap_R_RegisterModel( cabinetInfo[i].modelName ); for ( j = 0; j < MAX_CABINET_TAGS; j++ ) { cabinetInfo[i].itemmodels[j] = trap_R_RegisterModel( cabinetInfo[i].itemnames[j] ); } } } /* ========================= CG_AdjustPositionForMover Also called by client movement prediction code ========================= */ void CG_AdjustPositionForMover( const vec3_t in, int moverNum, int fromTime, int toTime, vec3_t out, vec3_t outDeltaAngles ) { centity_t *cent; vec3_t oldOrigin, origin, deltaOrigin; vec3_t oldAngles, angles, deltaAngles; vec3_t transpose[3]; vec3_t matrix[3]; vec3_t move, org, org2; if ( outDeltaAngles ) { VectorClear( outDeltaAngles ); } if ( moverNum <= 0 || moverNum >= ENTITYNUM_MAX_NORMAL ) { VectorCopy( in, out ); return; } cent = &cg_entities[ moverNum ]; if ( cent->currentState.eType != ET_MOVER ) { VectorCopy( in, out ); return; } if ( !( cent->currentState.eFlags & EF_PATH_LINK ) ) { BG_EvaluateTrajectory( ¢->currentState.pos, fromTime, oldOrigin, qfalse, cent->currentState.effect2Time ); BG_EvaluateTrajectory( ¢->currentState.apos, fromTime, oldAngles, qtrue, cent->currentState.effect2Time ); BG_EvaluateTrajectory( ¢->currentState.pos, toTime, origin, qfalse, cent->currentState.effect2Time ); BG_EvaluateTrajectory( ¢->currentState.apos, toTime, angles, qtrue, cent->currentState.effect2Time ); VectorSubtract( origin, oldOrigin, deltaOrigin ); VectorSubtract( angles, oldAngles, deltaAngles ); } else { CG_AddLinkedEntity( cent, qtrue, fromTime ); VectorCopy( cent->lerpOrigin, oldOrigin ); VectorCopy( cent->lerpAngles, oldAngles ); CG_AddLinkedEntity( cent, qtrue, toTime ); VectorSubtract( cent->lerpOrigin, oldOrigin, deltaOrigin ); VectorSubtract( cent->lerpAngles, oldAngles, deltaAngles ); CG_AddLinkedEntity( cent, qtrue, cg.time ); } BG_CreateRotationMatrix( deltaAngles, transpose ); BG_TransposeMatrix( (const vec3_t *)transpose, matrix ); VectorSubtract( cg.snap->ps.origin, cent->lerpOrigin, org ); VectorCopy( org, org2 ); BG_RotatePoint( org2, (const vec3_t *)matrix ); VectorSubtract( org2, org, move ); VectorAdd( deltaOrigin, move, deltaOrigin ); VectorAdd( in, deltaOrigin, out ); if ( outDeltaAngles ) { VectorCopy( deltaAngles, outDeltaAngles ); } // F I X M E: origin change when on a rotating object // Gordon: Added } /* ============================= CG_InterpolateEntityPosition ============================= */ static void CG_InterpolateEntityPosition( centity_t *cent ) { vec3_t current, next; float f; // it would be an internal error to find an entity that interpolates without // a snapshot ahead of the current one if ( cg.nextSnap == NULL ) { // DHM - Nerve :: FIXME? There are some cases when in Limbo mode during a map restart // that were tripping this error. //CG_Error( "CG_InterpolateEntityPosition: cg.nextSnap == NULL" ); //CG_Printf("CG_InterpolateEntityPosition: cg.nextSnap == NULL"); return; } f = cg.frameInterpolation; // this will linearize a sine or parabolic curve, but it is important // to not extrapolate player positions if more recent data is available BG_EvaluateTrajectory( ¢->currentState.pos, cg.snap->serverTime, current, qfalse, cent->currentState.effect2Time ); BG_EvaluateTrajectory( ¢->nextState.pos, cg.nextSnap->serverTime, next, qfalse, cent->currentState.effect2Time ); cent->lerpOrigin[0] = current[0] + f * ( next[0] - current[0] ); cent->lerpOrigin[1] = current[1] + f * ( next[1] - current[1] ); cent->lerpOrigin[2] = current[2] + f * ( next[2] - current[2] ); BG_EvaluateTrajectory( ¢->currentState.apos, cg.snap->serverTime, current, qtrue, cent->currentState.effect2Time ); BG_EvaluateTrajectory( ¢->nextState.apos, cg.nextSnap->serverTime, next, qtrue, cent->currentState.effect2Time ); cent->lerpAngles[0] = LerpAngle( current[0], next[0], f ); cent->lerpAngles[1] = LerpAngle( current[1], next[1], f ); cent->lerpAngles[2] = LerpAngle( current[2], next[2], f ); } /* =============== CG_CalcEntityLerpPositions =============== */ void CG_CalcEntityLerpPositions( centity_t *cent ) { if ( cent->interpolate && cent->currentState.pos.trType == TR_INTERPOLATE ) { CG_InterpolateEntityPosition( cent ); return; } // NERVE - SMF - fix for jittery clients in multiplayer // first see if we can interpolate between two snaps for // linear extrapolated clients if ( cent->interpolate && cent->currentState.pos.trType == TR_LINEAR_STOP && cent->currentState.number < MAX_CLIENTS ) { CG_InterpolateEntityPosition( cent ); return; } // -NERVE - SMF // backup VectorCopy( cent->lerpAngles, cent->lastLerpAngles ); VectorCopy( cent->lerpOrigin, cent->lastLerpOrigin ); // just use the current frame and evaluate as best we can BG_EvaluateTrajectory( ¢->currentState.pos, cg.time, cent->lerpOrigin, qfalse, cent->currentState.effect2Time ); BG_EvaluateTrajectory( ¢->currentState.apos, cg.time, cent->lerpAngles, qtrue, cent->currentState.effect2Time ); // adjust for riding a mover if it wasn't rolled into the predicted // player state if ( cent != &cg.predictedPlayerEntity && !cg.showGameView ) { CG_AdjustPositionForMover( cent->lerpOrigin, cent->currentState.groundEntityNum, cg.snap->serverTime, cg.time, cent->lerpOrigin, NULL ); } } /* =============== CG_ProcessEntity =============== */ static void CG_ProcessEntity( centity_t *cent ) { switch ( cent->currentState.eType ) { default: // ydnar: test for actual bad entity type if ( cent->currentState.eType < 0 || cent->currentState.eType >= ET_EVENTS ) { CG_Error( "Bad entity type: %i\n", cent->currentState.eType ); } break; case ET_CONCUSSIVE_TRIGGER: case ET_CAMERA: case ET_INVISIBLE: case ET_PUSH_TRIGGER: case ET_TELEPORT_TRIGGER: case ET_OID_TRIGGER: case ET_AI_EFFECT: case ET_EXPLOSIVE_INDICATOR: // NERVE - SMF case ET_CONSTRUCTIBLE_INDICATOR: case ET_TANK_INDICATOR: case ET_TANK_INDICATOR_DEAD: case ET_COMMANDMAP_MARKER: // this one should _never_ reach the client #ifdef VISIBLE_TRIGGERS case ET_TRIGGER_MULTIPLE: case ET_TRIGGER_FLAGONLY: case ET_TRIGGER_FLAGONLY_MULTIPLE: #endif // VISIBLE_TRIGGERS break; case ET_SPEAKER: CG_Speaker( cent ); break; case ET_GAMEMODEL: case ET_MG42_BARREL: case ET_FOOTLOCKER: case ET_GENERAL: case ET_AAGUN: CG_General( cent ); break; case ET_CABINET_H: CG_Cabinet( cent, CT_HEALTH ); break; case ET_CABINET_A: CG_Cabinet( cent, CT_AMMO ); break; case ET_CORPSE: case ET_PLAYER: if ( cg.showGameView && cg.filtercams ) { break; } CG_Player( cent ); break; case ET_ITEM: CG_Item( cent ); break; case ET_MISSILE: case ET_FLAMEBARREL: case ET_FP_PARTS: case ET_FIRE_COLUMN: case ET_FIRE_COLUMN_SMOKE: case ET_EXPLO_PART: case ET_RAMJET: CG_Missile( cent ); break; case ET_EF_SPOTLIGHT: CG_SpotlightEfx( cent ); break; case ET_EXPLOSIVE: CG_Explosive( cent ); break; case ET_CONSTRUCTIBLE: CG_Constructible( cent ); break; /* case ET_WAYPOINT: // TAT - 8/29/2002 - draw the botgoal indicator the same way you draw the waypoint flag case ET_BOTGOAL_INDICATOR: CG_Waypoint( cent ); break;*/ /* case ET_CONSTRUCTIBLE_MARKER: CG_ConstructibleMarker( cent ); break;*/ case ET_TRAP: CG_Trap( cent ); break; case ET_CONSTRUCTIBLE_MARKER: case ET_ALARMBOX: case ET_MOVER: CG_Mover( cent ); break; case ET_PROP: CG_Prop( cent ); break; case ET_BEAM: CG_Beam( cent ); break; case ET_PORTAL: CG_Portal( cent ); break; case ET_CORONA: CG_Corona( cent ); break; case ET_BOMB: CG_Bomb( cent ); break; case ET_BEAM_2: CG_Beam_2( cent ); break; case ET_GAMEMANAGER: cgs.gameManager = cent; break; case ET_SMOKER: CG_Smoker( cent ); break; } } /* =============== CG_AddCEntity =============== */ void CG_AddCEntity( centity_t *cent ) { // event-only entities will have been dealt with already if ( cent->currentState.eType >= ET_EVENTS ) { return; } cent->processedFrame = cg.clientFrame; // calculate the current origin CG_CalcEntityLerpPositions( cent ); // add automatic effects CG_EntityEffects( cent ); // call the appropriate function which will add this entity to the view accordingly CG_ProcessEntity( cent ); } qboolean CG_AddLinkedEntity( centity_t *cent, qboolean ignoreframe, int atTime ) { entityState_t *s1; centity_t *centParent; entityState_t *sParent; vec3_t v; // event-only entities will have been dealt with already if ( cent->currentState.eType >= ET_EVENTS ) { return qtrue; } if ( !ignoreframe && ( cent->processedFrame == cg.clientFrame ) && cg.mvTotalClients < 2 ) { // already processed this frame return qtrue; } s1 = ¢->currentState; centParent = &cg_entities[s1->torsoAnim]; sParent = &( centParent->currentState ); // if parent isn't visible, then don't draw us if ( !centParent->currentValid ) { return qfalse; } // make sure all parents are added first if ( ( centParent->processedFrame != cg.clientFrame ) || ignoreframe ) { if ( sParent->eFlags & EF_PATH_LINK ) { if ( !CG_AddLinkedEntity( centParent, ignoreframe, atTime ) ) { return qfalse; } } } if ( !ignoreframe ) { cent->processedFrame = cg.clientFrame; } // Arnout: removed from here //VectorCopy( cent->lerpAngles, cent->lastLerpAngles ); //VectorCopy( cent->lerpOrigin, cent->lastLerpOrigin ); if ( !( sParent->eFlags & EF_PATH_LINK ) ) { if ( sParent->pos.trType == TR_LINEAR_PATH ) { int pos; float frac; if ( !( cent->backspline = BG_GetSplineData( sParent->effect2Time, ¢->back ) ) ) { return qfalse; } cent->backdelta = sParent->pos.trDuration ? ( atTime - sParent->pos.trTime ) / ( (float)sParent->pos.trDuration ) : 0; if ( cent->backdelta < 0.f ) { cent->backdelta = 0.f; } else if ( cent->backdelta > 1.f ) { cent->backdelta = 1.f; } if ( cent->back ) { cent->backdelta = 1 - cent->backdelta; } pos = floor( cent->backdelta * ( MAX_SPLINE_SEGMENTS ) ); if ( pos >= MAX_SPLINE_SEGMENTS ) { pos = MAX_SPLINE_SEGMENTS - 1; frac = cent->backspline->segments[pos].length; } else { frac = ( ( cent->backdelta * ( MAX_SPLINE_SEGMENTS ) ) - pos ) * cent->backspline->segments[pos].length; } VectorMA( cent->backspline->segments[pos].start, frac, cent->backspline->segments[pos].v_norm, v ); if ( sParent->apos.trBase[0] ) { BG_LinearPathOrigin2( sParent->apos.trBase[0], ¢->backspline, ¢->backdelta, v, cent->back ); } VectorCopy( v, cent->lerpOrigin ); if ( s1->angles2[0] ) { BG_LinearPathOrigin2( s1->angles2[0], ¢->backspline, ¢->backdelta, v, cent->back ); } VectorCopy( v, cent->origin2 ); if ( s1->angles2[0] < 0 ) { VectorSubtract( v, cent->lerpOrigin, v ); vectoangles( v, cent->lerpAngles ); } else if ( s1->angles2[0] > 0 ) { VectorSubtract( cent->lerpOrigin, v, v ); vectoangles( v, cent->lerpAngles ); } else { VectorClear( cent->lerpAngles ); } cent->moving = qtrue; } else { cent->moving = qfalse; VectorCopy( s1->pos.trBase, cent->lerpOrigin ); VectorCopy( s1->apos.trBase, cent->lerpAngles ); } } else { if ( centParent->moving ) { VectorCopy( centParent->origin2, v ); cent->back = centParent->back; cent->backdelta = centParent->backdelta; cent->backspline = centParent->backspline; VectorCopy( v, cent->lerpOrigin ); if ( s1->angles2[0] && cent->backspline ) { BG_LinearPathOrigin2( s1->angles2[0], ¢->backspline, ¢->backdelta, v, cent->back ); } VectorCopy( v, cent->origin2 ); if ( s1->angles2[0] < 0 ) { VectorSubtract( v, cent->lerpOrigin, v ); vectoangles( v, cent->lerpAngles ); } else if ( s1->angles2[0] > 0 ) { VectorSubtract( cent->lerpOrigin, v, v ); vectoangles( v, cent->lerpAngles ); } else { VectorClear( cent->lerpAngles ); } cent->moving = qtrue; } else { cent->moving = qfalse; VectorCopy( s1->pos.trBase, cent->lerpOrigin ); VectorCopy( s1->apos.trBase, cent->lerpAngles ); } } if ( !ignoreframe ) { // add automatic effects CG_EntityEffects( cent ); // call the appropriate function which will add this entity to the view accordingly CG_ProcessEntity( cent ); } return qtrue; } void CG_RailTrail2( clientInfo_t *ci, vec3_t start, vec3_t end ); /* ================== CG_AddEntityToTag ================== */ qboolean CG_AddEntityToTag( centity_t *cent ) { centity_t *centParent; entityState_t *sParent; refEntity_t ent; // event-only entities will have been dealt with already if ( cent->currentState.eType >= ET_EVENTS ) { return qfalse; } if ( cent->processedFrame == cg.clientFrame && cg.mvTotalClients < 2 ) { // already processed this frame return qtrue; } // calculate the current origin CG_CalcEntityLerpPositions( cent ); if ( cent->tagParent < MAX_CLIENTS ) { return qfalse; } centParent = &cg_entities[cent->tagParent]; sParent = ¢Parent->currentState; // if parent isn't visible, then don't draw us if ( !centParent->currentValid ) { return qfalse; } // make sure all parents are added first if ( centParent->processedFrame != cg.clientFrame ) { if ( !CG_AddCEntity_Filter( centParent ) ) { return qfalse; } } cent->processedFrame = cg.clientFrame; // start with default axis AnglesToAxis( vec3_origin, ent.axis ); // get the tag position from parent CG_PositionEntityOnTag( &ent, ¢Parent->refEnt, cent->tagName, 0, NULL ); VectorCopy( ent.origin, cent->lerpOrigin ); // we need to add the child's angles to the tag angles if ( cent->currentState.eType != ET_PLAYER ) { if ( !cent->currentState.density ) { // this entity should rotate with it's parent, but can turn around using it's own angles // Gordon: fixed to rotate about the object's axis, not the world vec3_t mat[3], mat2[3]; memcpy( mat2, ent.axis, sizeof( mat2 ) ); BG_CreateRotationMatrix( cent->lerpAngles, mat ); MatrixMultiply( mat, mat2, ent.axis ); AxisToAngles( ent.axis, cent->lerpAngles ); } else { // face our angles exactly BG_EvaluateTrajectory( ¢->currentState.apos, cg.time, cent->lerpAngles, qtrue, cent->currentState.effect2Time ); } } // add automatic effects CG_EntityEffects( cent ); // call the appropriate function which will add this entity to the view accordingly CG_ProcessEntity( cent ); return qtrue; } extern int cg_numSolidEntities; extern centity_t *cg_solidEntities[]; /* =============== CG_AddPacketEntities =============== */ qboolean CG_AddCEntity_Filter( centity_t* cent ) { if ( cent->processedFrame == cg.clientFrame && cg.mvTotalClients < 2 ) { return qtrue; } if ( cent->currentState.eFlags & EF_PATH_LINK ) { return CG_AddLinkedEntity( cent, qfalse, cg.time ); } if ( cent->currentState.eFlags & EF_TAGCONNECT ) { return CG_AddEntityToTag( cent ); } CG_AddCEntity( cent ); return qtrue; } void CG_AddPacketEntities( void ) { int num; playerState_t *ps; //int clcount; // set cg.frameInterpolation if ( cg.nextSnap ) { int delta; delta = ( cg.nextSnap->serverTime - cg.snap->serverTime ); if ( delta == 0 ) { cg.frameInterpolation = 0; } else { cg.frameInterpolation = (float)( cg.time - cg.snap->serverTime ) / delta; } } else { cg.frameInterpolation = 0; // actually, it should never be used, because // no entities should be marked as interpolating } // the auto-rotating items will all have the same axis cg.autoAnglesSlow[0] = 0; cg.autoAnglesSlow[1] = ( cg.time & 4095 ) * 360 / 4095.0; cg.autoAnglesSlow[2] = 0; cg.autoAngles[0] = 0; cg.autoAngles[1] = ( cg.time & 2047 ) * 360 / 2048.0; cg.autoAngles[2] = 0; cg.autoAnglesFast[0] = 0; cg.autoAnglesFast[1] = ( cg.time & 1023 ) * 360 / 1024.0f; cg.autoAnglesFast[2] = 0; AnglesToAxis( cg.autoAnglesSlow, cg.autoAxisSlow ); AnglesToAxis( cg.autoAngles, cg.autoAxis ); AnglesToAxis( cg.autoAnglesFast, cg.autoAxisFast ); // generate and add the entity from the playerstate ps = &cg.predictedPlayerState; BG_PlayerStateToEntityState( ps, &cg.predictedPlayerEntity.currentState, qfalse ); CG_AddCEntity( &cg.predictedPlayerEntity ); // lerp the non-predicted value for lightning gun origins CG_CalcEntityLerpPositions( &cg_entities[ cg.snap->ps.clientNum ] ); cg.satchelCharge = NULL; // Gordon: changing to a single loop, child will request that their parents are added first anyway for ( num = 0; num < cg.snap->numEntities ; num++ ) { CG_AddCEntity_Filter( &cg_entities[ cg.snap->entities[ num ].number ] ); } // Gordon: Add tank bits as a second loop, to stop recursion problems with tank bits not on base entity for ( num = 0; num < cg.snap->numEntities ; num++ ) { if ( cg_entities[ cg.snap->entities[ num ].number ].currentState.eType == ET_MOVER ) { CG_Mover_PostProcess( &cg_entities[ cg.snap->entities[ num ].number ] ); } } // Ridah, add the flamethrower sounds CG_UpdateFlamethrowerSounds(); } void CGRefEntityToTag( refEntity_t* ent, tag_t* tag ) { int i; VectorCopy( ent->origin, tag->origin ); for ( i = 0; i < 3; i++ ) { VectorCopy( ent->axis[i], tag->axis[i] ); } } void CGTagToRefEntity( refEntity_t* ent, tag_t* tag ) { int i; VectorCopy( tag->origin, ent->origin ); for ( i = 0; i < 3; i++ ) { VectorCopy( tag->axis[i], ent->axis[i] ); } } void CG_AttachBitsToTank( centity_t* tank, refEntity_t* mg42base, refEntity_t* mg42upper, refEntity_t* mg42gun, refEntity_t* player, refEntity_t* flash, vec_t* playerangles, const char* tagName, qboolean browning ) { refEntity_t ent; vec3_t angles; int i; memset( mg42base, 0, sizeof( refEntity_t ) ); memset( mg42gun, 0, sizeof( refEntity_t ) ); memset( mg42upper, 0, sizeof( refEntity_t ) ); memset( player, 0, sizeof( refEntity_t ) ); memset( flash, 0, sizeof( refEntity_t ) ); mg42base->hModel = cgs.media.hMountedMG42Base; mg42upper->hModel = cgs.media.hMountedMG42Nest; if ( browning ) { mg42gun->hModel = cgs.media.hMountedBrowning; } else { mg42gun->hModel = cgs.media.hMountedMG42; } if ( !CG_AddCEntity_Filter( tank ) ) { return; } if ( tank->tankframe != cg.clientFrame ) { tank->tankframe = cg.clientFrame; memset( &ent, 0, sizeof( refEntity_t ) ); if ( tank->currentState.solid == SOLID_BMODEL ) { ent.hModel = cgs.gameModels[tank->currentState.modelindex2]; } else { ent.hModel = cgs.gameModels[tank->currentState.modelindex]; } ent.frame = tank->lerpFrame.frame; ent.oldframe = tank->lerpFrame.oldFrame; ent.backlerp = tank->lerpFrame.backlerp; AnglesToAxis( tank->lerpAngles, ent.axis ); VectorCopy( tank->lerpOrigin, ent.origin ); AxisClear( mg42base->axis ); CG_PositionEntityOnTag( mg42base, &ent, tagName, 0, NULL ); VectorCopy( playerangles, angles ); angles[PITCH] = 0; for ( i = 0; i < MAX_CLIENTS; i++ ) { // Gordon: is this entity mounted on a tank, and attached to _OUR_ turret entity (which could be us) if ( cg_entities[i].currentValid && cg_entities[i].currentState.eFlags & EF_MOUNTEDTANK && cg_entities[i].tagParent == tank - cg_entities ) { angles[YAW] -= tank->lerpAngles[YAW]; angles[PITCH] -= tank->lerpAngles[PITCH]; break; } } AnglesToAxis( angles, mg42upper->axis ); CG_PositionRotatedEntityOnTag( mg42upper, mg42base, "tag_mg42nest" ); VectorCopy( playerangles, angles ); angles[YAW] = 0; angles[ROLL] = 0; AnglesToAxis( angles, mg42gun->axis ); CG_PositionRotatedEntityOnTag( mg42gun, mg42upper, "tag_mg42" ); CG_PositionEntityOnTag( player, mg42upper, "tag_playerpo", 0, NULL ); CG_PositionEntityOnTag( flash, mg42gun, "tag_flash", 0, NULL ); CGRefEntityToTag( mg42base, &tank->mountedMG42Base ); CGRefEntityToTag( mg42upper, &tank->mountedMG42Nest ); CGRefEntityToTag( mg42gun, &tank->mountedMG42 ); CGRefEntityToTag( player, &tank->mountedMG42Player ); CGRefEntityToTag( flash, &tank->mountedMG42Flash ); } CGTagToRefEntity( mg42base, &tank->mountedMG42Base ); CGTagToRefEntity( mg42upper, &tank->mountedMG42Nest ); CGTagToRefEntity( mg42gun, &tank->mountedMG42 ); CGTagToRefEntity( player, &tank->mountedMG42Player ); CGTagToRefEntity( flash, &tank->mountedMG42Flash ); }