/* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. Copyright (C) 2000-2009 Darklegion Development This file is part of Tremulous. Tremulous 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 2 of the License, or (at your option) any later version. Tremulous 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 Tremulous; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ // cg_ents.c -- present snapshot entities, happens every single frame #include "cg_local.h" /* ====================== CG_DrawBoxFace Draws a bounding box face ====================== */ static void CG_DrawBoxFace( vec3_t a, vec3_t b, vec3_t c, vec3_t d ) { polyVert_t verts[ 4 ]; vec4_t color = { 255.0f, 0.0f, 0.0f, 128.0f }; VectorCopy( d, verts[ 0 ].xyz ); verts[ 0 ].st[ 0 ] = 1; verts[ 0 ].st[ 1 ] = 1; Vector4Copy( color, verts[ 0 ].modulate ); VectorCopy( c, verts[ 1 ].xyz ); verts[ 1 ].st[ 0 ] = 1; verts[ 1 ].st[ 1 ] = 0; Vector4Copy( color, verts[ 1 ].modulate ); VectorCopy( b, verts[ 2 ].xyz ); verts[ 2 ].st[ 0 ] = 0; verts[ 2 ].st[ 1 ] = 0; Vector4Copy( color, verts[ 2 ].modulate ); VectorCopy( a, verts[ 3 ].xyz ); verts[ 3 ].st[ 0 ] = 0; verts[ 3 ].st[ 1 ] = 1; Vector4Copy( color, verts[ 3 ].modulate ); trap_R_AddPolyToScene( cgs.media.outlineShader, 4, verts ); } /* ====================== CG_DrawBoundingBox Draws a bounding box ====================== */ void CG_DrawBoundingBox( vec3_t origin, vec3_t mins, vec3_t maxs ) { vec3_t ppp, mpp, mmp, pmp; vec3_t mmm, pmm, ppm, mpm; ppp[ 0 ] = origin[ 0 ] + maxs[ 0 ]; ppp[ 1 ] = origin[ 1 ] + maxs[ 1 ]; ppp[ 2 ] = origin[ 2 ] + maxs[ 2 ]; mpp[ 0 ] = origin[ 0 ] + mins[ 0 ]; mpp[ 1 ] = origin[ 1 ] + maxs[ 1 ]; mpp[ 2 ] = origin[ 2 ] + maxs[ 2 ]; mmp[ 0 ] = origin[ 0 ] + mins[ 0 ]; mmp[ 1 ] = origin[ 1 ] + mins[ 1 ]; mmp[ 2 ] = origin[ 2 ] + maxs[ 2 ]; pmp[ 0 ] = origin[ 0 ] + maxs[ 0 ]; pmp[ 1 ] = origin[ 1 ] + mins[ 1 ]; pmp[ 2 ] = origin[ 2 ] + maxs[ 2 ]; ppm[ 0 ] = origin[ 0 ] + maxs[ 0 ]; ppm[ 1 ] = origin[ 1 ] + maxs[ 1 ]; ppm[ 2 ] = origin[ 2 ] + mins[ 2 ]; mpm[ 0 ] = origin[ 0 ] + mins[ 0 ]; mpm[ 1 ] = origin[ 1 ] + maxs[ 1 ]; mpm[ 2 ] = origin[ 2 ] + mins[ 2 ]; mmm[ 0 ] = origin[ 0 ] + mins[ 0 ]; mmm[ 1 ] = origin[ 1 ] + mins[ 1 ]; mmm[ 2 ] = origin[ 2 ] + mins[ 2 ]; pmm[ 0 ] = origin[ 0 ] + maxs[ 0 ]; pmm[ 1 ] = origin[ 1 ] + mins[ 1 ]; pmm[ 2 ] = origin[ 2 ] + mins[ 2 ]; //phew! CG_DrawBoxFace( ppp, mpp, mmp, pmp ); CG_DrawBoxFace( ppp, pmp, pmm, ppm ); CG_DrawBoxFace( mpp, ppp, ppm, mpm ); CG_DrawBoxFace( mmp, mpp, mpm, mmm ); CG_DrawBoxFace( pmp, mmp, mmm, pmm ); CG_DrawBoxFace( mmm, mpm, ppm, pmm ); } /* ====================== 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 trap_R_LerpTag( &lerped, parentModel, parent->oldframe, parent->frame, 1.0 - 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 ) { int i; orientation_t lerped; vec3_t tempAxis[ 3 ]; //AxisClear( entity->axis ); // lerp the tag trap_R_LerpTag( &lerped, parentModel, parent->oldframe, parent->frame, 1.0 - 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( 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 ); } /* ================== CG_EntityEffects Add continuous entity effects, like local entity emission and lighting ================== */ static void CG_EntityEffects( centity_t *cent ) { // update sound origins CG_SetEntitySoundPosition( cent ); // add loop sound if( cent->currentState.loopSound ) { if( cent->currentState.eType != ET_SPEAKER ) { trap_S_AddLoopingSound( cent->currentState.number, cent->lerpOrigin, vec3_origin, cgs.gameSounds[ cent->currentState.loopSound ] ); } else { trap_S_AddRealLoopingSound( cent->currentState.number, cent->lerpOrigin, vec3_origin, cgs.gameSounds[ cent->currentState.loopSound ] ); } } // constant light glow if ( cent->currentState.constantLight ) { int cl; int i, r, g, b; 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, r, g, b ); } if( CG_IsTrailSystemValid( ¢->muzzleTS ) ) { //FIXME hack to prevent tesla trails reaching too far if( cent->currentState.eType == ET_BUILDABLE ) { vec3_t front, back; CG_AttachmentPoint( ¢->muzzleTS->frontAttachment, front ); CG_AttachmentPoint( ¢->muzzleTS->backAttachment, back ); if( Distance( front, back ) > ( TESLAGEN_RANGE * M_ROOT3 ) ) CG_DestroyTrailSystem( ¢->muzzleTS ); } if( cg.time > cent->muzzleTSDeathTime && CG_IsTrailSystemValid( ¢->muzzleTS ) ) CG_DestroyTrailSystem( ¢->muzzleTS ); } } /* ================== 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; 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 // convert angles to axis AnglesToAxis( cent->lerpAngles, ent.axis ); // add to refresh list trap_R_AddRefEntityToScene( &ent ); } /* ================== CG_Speaker Speaker entities can automatically play sounds ================== */ static void CG_Speaker( centity_t *cent ) { if( ! cent->currentState.clientNum ) { // FIXME: use something other than clientNum... return; // not auto triggering } if( cg.time < cent->miscTime ) return; 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( ); } //============================================================================ /* =============== CG_LaunchMissile =============== */ static void CG_LaunchMissile( centity_t *cent ) { entityState_t *es; const weaponInfo_t *wi; particleSystem_t *ps; trailSystem_t *ts; weapon_t weapon; weaponMode_t weaponMode; es = ¢->currentState; weapon = es->weapon; if( weapon > WP_NUM_WEAPONS ) weapon = WP_NONE; wi = &cg_weapons[ weapon ]; weaponMode = es->generic1; if( wi->wim[ weaponMode ].missileParticleSystem ) { ps = CG_SpawnNewParticleSystem( wi->wim[ weaponMode ].missileParticleSystem ); if( CG_IsParticleSystemValid( &ps ) ) { CG_SetAttachmentCent( &ps->attachment, cent ); CG_AttachToCent( &ps->attachment ); ps->charge = es->torsoAnim; } } if( wi->wim[ weaponMode ].missileTrailSystem ) { ts = CG_SpawnNewTrailSystem( wi->wim[ weaponMode ].missileTrailSystem ); if( CG_IsTrailSystemValid( &ts ) ) { CG_SetAttachmentCent( &ts->frontAttachment, cent ); CG_AttachToCent( &ts->frontAttachment ); } } } /* =============== CG_Missile =============== */ static void CG_Missile( centity_t *cent ) { refEntity_t ent; entityState_t *es; const weaponInfo_t *wi; weapon_t weapon; weaponMode_t weaponMode; const weaponInfoMode_t *wim; es = ¢->currentState; weapon = es->weapon; if( weapon > WP_NUM_WEAPONS ) weapon = WP_NONE; wi = &cg_weapons[ weapon ]; weaponMode = es->generic1; wim = &wi->wim[ weaponMode ]; // calculate the axis VectorCopy( es->angles, cent->lerpAngles ); // add dynamic light if( wim->missileDlight ) { trap_R_AddLightToScene( cent->lerpOrigin, wim->missileDlight, wim->missileDlightColor[ 0 ], wim->missileDlightColor[ 1 ], wim->missileDlightColor[ 2 ] ); } // add missile sound if( wim->missileSound ) { vec3_t velocity; BG_EvaluateTrajectoryDelta( ¢->currentState.pos, cg.time, velocity ); trap_S_AddLoopingSound( cent->currentState.number, cent->lerpOrigin, velocity, wim->missileSound ); } // create the render entity memset( &ent, 0, sizeof( ent ) ); VectorCopy( cent->lerpOrigin, ent.origin ); VectorCopy( cent->lerpOrigin, ent.oldorigin ); if( wim->usesSpriteMissle ) { ent.reType = RT_SPRITE; ent.radius = wim->missileSpriteSize + wim->missileSpriteCharge * es->torsoAnim; ent.rotation = 0; ent.customShader = wim->missileSprite; ent.shaderRGBA[ 0 ] = 0xFF; ent.shaderRGBA[ 1 ] = 0xFF; ent.shaderRGBA[ 2 ] = 0xFF; ent.shaderRGBA[ 3 ] = 0xFF; } else { ent.hModel = wim->missileModel; ent.renderfx = wim->missileRenderfx | RF_NOSHADOW; // convert direction of travel into axis if( VectorNormalize2( es->pos.trDelta, ent.axis[ 0 ] ) == 0 ) ent.axis[ 0 ][ 2 ] = 1; // spin as it moves if( es->pos.trType != TR_STATIONARY && wim->missileRotates ) RotateAroundDirection( ent.axis, cg.time / 4 ); else RotateAroundDirection( ent.axis, es->time ); if( wim->missileAnimates ) { int timeSinceStart = cg.time - es->time; if( wim->missileAnimLooping ) { ent.frame = wim->missileAnimStartFrame + (int)( ( timeSinceStart / 1000.0f ) * wim->missileAnimFrameRate ) % wim->missileAnimNumFrames; } else { ent.frame = wim->missileAnimStartFrame + (int)( ( timeSinceStart / 1000.0f ) * wim->missileAnimFrameRate ); if( ent.frame > ( wim->missileAnimStartFrame + wim->missileAnimNumFrames ) ) ent.frame = wim->missileAnimStartFrame + wim->missileAnimNumFrames; } } } //only refresh if there is something to display if( wim->missileSprite || wim->missileModel ) trap_R_AddRefEntityToScene( &ent ); } /* =============== 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 = 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 ]; // 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 ); } } /* =============== 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_BEAM; 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.oldframe = s1->misc; ent.frame = s1->frame; // rotation speed ent.skinNum = s1->clientNum / 256.0 * 360; // roll offset // add to refresh list trap_R_AddRefEntityToScene( &ent ); } //============================================================================ #define SETBOUNDS(v1,v2,r) ((v1)[0]=(-r/2),(v1)[1]=(-r/2),(v1)[2]=(-r/2),\ (v2)[0]=(r/2),(v2)[1]=(r/2),(v2)[2]=(r/2)) #define RADIUSSTEP 0.5f #define FLARE_OFF 0 #define FLARE_NOFADE 1 #define FLARE_TIMEFADE 2 #define FLARE_REALFADE 3 /* ========================= CG_LightFlare ========================= */ static void CG_LightFlare( centity_t *cent ) { refEntity_t flare; entityState_t *es; vec3_t forward, delta; float len; trace_t tr; float maxAngle; vec3_t mins, maxs, start, end; float srcRadius, srLocal, ratio = 1.0f; int entityNum; es = ¢->currentState; if( cg.renderingThirdPerson ) entityNum = MAGIC_TRACE_HACK; else entityNum = cg.predictedPlayerState.clientNum; //don't draw light flares if( cg_lightFlare.integer == FLARE_OFF ) return; //flare is "off" if( es->eFlags & EF_NODRAW ) return; CG_Trace( &tr, cg.refdef.vieworg, NULL, NULL, es->angles2, entityNum, MASK_SHOT ); //if there is no los between the view and the flare source //it definately cannot be seen if( tr.fraction < 1.0f || tr.allsolid ) return; memset( &flare, 0, sizeof( flare ) ); flare.reType = RT_SPRITE; flare.customShader = cgs.gameShaders[ es->modelindex ]; flare.shaderRGBA[ 0 ] = 0xFF; flare.shaderRGBA[ 1 ] = 0xFF; flare.shaderRGBA[ 2 ] = 0xFF; flare.shaderRGBA[ 3 ] = 0xFF; //flares always drawn before the rest of the scene flare.renderfx |= RF_DEPTHHACK; //bunch of geometry AngleVectors( es->angles, forward, NULL, NULL ); VectorCopy( cent->lerpOrigin, flare.origin ); VectorSubtract( flare.origin, cg.refdef.vieworg, delta ); len = VectorLength( delta ); VectorNormalize( delta ); //flare is too close to camera to be drawn if( len < es->generic1 ) return; //don't bother for flares behind the view plane if( DotProduct( delta, cg.refdef.viewaxis[ 0 ] ) < 0.0 ) return; //only recalculate radius and ratio every three frames if( !( cg.clientFrame % 2 ) ) { //can only see the flare when in front of it flare.radius = len / es->origin2[ 0 ]; if( es->origin2[ 2 ] == 0 ) srcRadius = srLocal = flare.radius / 2.0f; else srcRadius = srLocal = len / es->origin2[ 2 ]; maxAngle = es->origin2[ 1 ]; if( maxAngle > 0.0f ) { float radiusMod = 1.0f - ( 180.0f - RAD2DEG( acos( DotProduct( delta, forward ) ) ) ) / maxAngle; if( radiusMod < 0.0f ) radiusMod = 0.0f; flare.radius *= radiusMod; } if( flare.radius < 0.0f ) flare.radius = 0.0f; VectorMA( flare.origin, -flare.radius, delta, end ); VectorMA( cg.refdef.vieworg, flare.radius, delta, start ); if( cg_lightFlare.integer == FLARE_REALFADE ) { //"correct" flares CG_BiSphereTrace( &tr, cg.refdef.vieworg, end, 1.0f, srcRadius, entityNum, MASK_SHOT ); if( tr.fraction < 1.0f ) ratio = tr.lateralFraction; else ratio = 1.0f; } else if( cg_lightFlare.integer == FLARE_TIMEFADE ) { //draw timed flares SETBOUNDS( mins, maxs, srcRadius ); CG_Trace( &tr, start, mins, maxs, end, entityNum, MASK_SHOT ); if( ( tr.fraction < 1.0f || tr.startsolid ) && cent->lfs.status ) { cent->lfs.status = qfalse; cent->lfs.lastTime = cg.time; } else if( ( tr.fraction == 1.0f && !tr.startsolid ) && !cent->lfs.status ) { cent->lfs.status = qtrue; cent->lfs.lastTime = cg.time; } //fade flare up if( cent->lfs.status ) { if( cent->lfs.lastTime + es->time > cg.time ) ratio = (float)( cg.time - cent->lfs.lastTime ) / es->time; } //fade flare down if( !cent->lfs.status ) { if( cent->lfs.lastTime + es->time > cg.time ) { ratio = (float)( cg.time - cent->lfs.lastTime ) / es->time; ratio = 1.0f - ratio; } else ratio = 0.0f; } } else if( cg_lightFlare.integer == FLARE_NOFADE ) { //draw nofade flares SETBOUNDS( mins, maxs, srcRadius ); CG_Trace( &tr, start, mins, maxs, end, entityNum, MASK_SHOT ); //flare source occluded if( ( tr.fraction < 1.0f || tr.startsolid ) ) ratio = 0.0f; } } else { ratio = cent->lfs.lastRatio; flare.radius = cent->lfs.lastRadius; } cent->lfs.lastRatio = ratio; cent->lfs.lastRadius = flare.radius; if( ratio < 1.0f ) { flare.radius *= ratio; flare.shaderRGBA[ 3 ] = (byte)( (float)flare.shaderRGBA[ 3 ] * ratio ); } if( flare.radius <= 0.0f ) return; trap_R_AddRefEntityToScene( &flare ); } /* ========================= CG_Lev2ZapChain ========================= */ static void CG_Lev2ZapChain( centity_t *cent ) { int i; entityState_t *es; centity_t *source = NULL, *target = NULL; int entityNums[ LEVEL2_AREAZAP_MAX_TARGETS + 1 ]; int count; es = ¢->currentState; count = BG_UnpackEntityNumbers( es, entityNums, LEVEL2_AREAZAP_MAX_TARGETS + 1 ); for( i = 1; i < count; i++ ) { if( i == 1 ) { // First entity is the attacker source = &cg_entities[ entityNums[ 0 ] ]; } else { // Subsequent zaps come from the first target source = &cg_entities[ entityNums[ 1 ] ]; } target = &cg_entities[ entityNums[ i ] ]; if( !CG_IsTrailSystemValid( ¢->level2ZapTS[ i ] ) ) cent->level2ZapTS[ i ] = CG_SpawnNewTrailSystem( cgs.media.level2ZapTS ); if( CG_IsTrailSystemValid( ¢->level2ZapTS[ i ] ) ) { CG_SetAttachmentCent( ¢->level2ZapTS[ i ]->frontAttachment, source ); CG_SetAttachmentCent( ¢->level2ZapTS[ i ]->backAttachment, target ); CG_AttachToCent( ¢->level2ZapTS[ i ]->frontAttachment ); CG_AttachToCent( ¢->level2ZapTS[ i ]->backAttachment ); } } } /* ========================= 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 ) { centity_t *cent; vec3_t oldOrigin, origin, deltaOrigin; vec3_t oldAngles, angles, deltaAngles; 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; } BG_EvaluateTrajectory( ¢->currentState.pos, fromTime, oldOrigin ); BG_EvaluateTrajectory( ¢->currentState.apos, fromTime, oldAngles ); BG_EvaluateTrajectory( ¢->currentState.pos, toTime, origin ); BG_EvaluateTrajectory( ¢->currentState.apos, toTime, angles ); VectorSubtract( origin, oldOrigin, deltaOrigin ); VectorSubtract( angles, oldAngles, deltaAngles ); VectorAdd( in, deltaOrigin, out ); // FIXME: origin change when on a rotating object } /* ============================= 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 ) CG_Error( "CG_InterpoateEntityPosition: cg.nextSnap == NULL" ); 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 ); BG_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 ] ); BG_EvaluateTrajectory( ¢->currentState.apos, cg.snap->serverTime, current ); BG_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 ); } /* =============== CG_CalcEntityLerpPositions =============== */ static void CG_CalcEntityLerpPositions( centity_t *cent ) { // this will be set to how far forward projectiles will be extrapolated int timeshift = 0; // if this player does not want to see extrapolated players if( !cg_smoothClients.integer ) { // make sure the clients use TR_INTERPOLATE if( cent->currentState.number < MAX_CLIENTS ) { cent->currentState.pos.trType = TR_INTERPOLATE; cent->nextState.pos.trType = TR_INTERPOLATE; } } if( cent->interpolate && cent->currentState.pos.trType == TR_INTERPOLATE ) { CG_InterpolateEntityPosition( cent ); return; } // 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; } if( cg_projectileNudge.integer && !cg.demoPlayback && cent->currentState.eType == ET_MISSILE && !( cg.snap->ps.pm_flags & PMF_FOLLOW ) ) { timeshift = cg.ping; } // just use the current frame and evaluate as best we can BG_EvaluateTrajectory( ¢->currentState.pos, ( cg.time + timeshift ), cent->lerpOrigin ); BG_EvaluateTrajectory( ¢->currentState.apos, ( cg.time + timeshift ), cent->lerpAngles ); if( timeshift ) { trace_t tr; vec3_t lastOrigin; BG_EvaluateTrajectory( ¢->currentState.pos, cg.time, lastOrigin ); CG_Trace( &tr, lastOrigin, vec3_origin, vec3_origin, cent->lerpOrigin, cent->currentState.number, MASK_SHOT ); // don't let the projectile go through the floor if( tr.fraction < 1.0f ) VectorLerp( tr.fraction, lastOrigin, cent->lerpOrigin, cent->lerpOrigin ); } // adjust for riding a mover if it wasn't rolled into the predicted // player state if( cent != &cg.predictedPlayerEntity ) { CG_AdjustPositionForMover( cent->lerpOrigin, cent->currentState.groundEntityNum, cg.snap->serverTime, cg.time, cent->lerpOrigin ); } } /* =============== CG_CEntityPVSEnter =============== */ static void CG_CEntityPVSEnter( centity_t *cent ) { entityState_t *es = ¢->currentState; if( cg_debugPVS.integer ) CG_Printf( "Entity %d entered PVS\n", cent->currentState.number ); switch( es->eType ) { case ET_MISSILE: CG_LaunchMissile( cent ); break; case ET_BUILDABLE: cent->lastBuildableHealth = es->generic1; break; } //clear any particle systems from previous uses of this centity_t cent->muzzlePS = NULL; cent->muzzlePsTrigger = qfalse; cent->jetPackPS = NULL; cent->jetPackState = JPS_OFF; cent->buildablePS = NULL; cent->entityPS = NULL; cent->entityPSMissing = qfalse; //make sure that the buildable animations are in a consistent state //when a buildable enters the PVS cent->buildableAnim = cent->lerpFrame.animationNumber = BANIM_NONE; cent->oldBuildableAnim = es->legsAnim; } /* =============== CG_CEntityPVSLeave =============== */ static void CG_CEntityPVSLeave( centity_t *cent ) { int i; entityState_t *es = ¢->currentState; if( cg_debugPVS.integer ) CG_Printf( "Entity %d left PVS\n", cent->currentState.number ); switch( es->eType ) { case ET_LEV2_ZAP_CHAIN: for( i = 0; i <= LEVEL2_AREAZAP_MAX_TARGETS; i++ ) { if( CG_IsTrailSystemValid( ¢->level2ZapTS[ i ] ) ) CG_DestroyTrailSystem( ¢->level2ZapTS[ i ] ); } break; } } /* =============== 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; // calculate the current origin CG_CalcEntityLerpPositions( cent ); // add automatic effects CG_EntityEffects( cent ); 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_LOCATION: break; case ET_GENERAL: CG_General( cent ); break; case ET_CORPSE: CG_Corpse( cent ); break; case ET_PLAYER: CG_Player( cent ); break; case ET_BUILDABLE: CG_Buildable( cent ); break; case ET_MISSILE: CG_Missile( cent ); break; case ET_MOVER: CG_Mover( cent ); break; case ET_BEAM: CG_Beam( cent ); break; case ET_PORTAL: CG_Portal( cent ); break; case ET_SPEAKER: CG_Speaker( cent ); break; case ET_PARTICLE_SYSTEM: CG_ParticleSystemEntity( cent ); break; case ET_ANIMMAPOBJ: CG_AnimMapObj( cent ); break; case ET_MODELDOOR: CG_ModelDoor( cent ); break; case ET_LIGHTFLARE: CG_LightFlare( cent ); break; case ET_LEV2_ZAP_CHAIN: CG_Lev2ZapChain( cent ); break; } } /* =============== CG_AddPacketEntities =============== */ void CG_AddPacketEntities( void ) { int num; centity_t *cent; playerState_t *ps; // set cg.frameInterpolation if( cg.nextSnap ) { int delta; delta = ( cg.nextSnap->serverTime - cg.snap->serverTime ); if( delta == 0 ) cg.frameInterpolation = 0; else cg.frameInterpolation = (float)( cg.time - cg.snap->serverTime ) / delta; } 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.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.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.predictedPlayerEntity.valid = qtrue; CG_AddCEntity( &cg.predictedPlayerEntity ); // lerp the non-predicted value for lightning gun origins CG_CalcEntityLerpPositions( &cg_entities[ cg.snap->ps.clientNum ] ); // scanner CG_UpdateEntityPositions( ); for( num = 0; num < MAX_GENTITIES; num++ ) cg_entities[ num ].valid = qfalse; // add each entity sent over by the server for( num = 0; num < cg.snap->numEntities; num++ ) { cent = &cg_entities[ cg.snap->entities[ num ].number ]; cent->valid = qtrue; } for( num = 0; num < MAX_GENTITIES; num++ ) { cent = &cg_entities[ num ]; if( cent->valid && !cent->oldValid ) CG_CEntityPVSEnter( cent ); else if( !cent->valid && cent->oldValid ) CG_CEntityPVSLeave( cent ); cent->oldValid = cent->valid; } // 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 ); } //make an attempt at drawing bounding boxes of selected entity types if( cg_drawBBOX.integer ) { for( num = 0; num < cg.snap->numEntities; num++ ) { float x, zd, zu; vec3_t mins, maxs; entityState_t *es; cent = &cg_entities[ cg.snap->entities[ num ].number ]; es = ¢->currentState; switch( es->eType ) { case ET_BUILDABLE: case ET_MISSILE: case ET_CORPSE: x = ( es->solid & 255 ); zd = ( ( es->solid >> 8 ) & 255 ); zu = ( ( es->solid >> 16 ) & 255 ) - 32; mins[ 0 ] = mins[ 1 ] = -x; maxs[ 0 ] = maxs[ 1 ] = x; mins[ 2 ] = -zd; maxs[ 2 ] = zu; CG_DrawBoundingBox( cent->lerpOrigin, mins, maxs ); break; default: break; } } } }