/* =========================================================================== 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. =========================================================================== */ // cg_localents.c -- every frame, generate renderer commands for locally // processed entities, like smoke puffs, gibs, shells, etc. #include "cg_local.h" // Ridah, increased this //#define MAX_LOCAL_ENTITIES 512 #define MAX_LOCAL_ENTITIES 768 // renderer can only handle 1024 entities max, so we should avoid // overwriting game entities // done. localEntity_t cg_localEntities[MAX_LOCAL_ENTITIES]; localEntity_t cg_activeLocalEntities; // double linked list localEntity_t *cg_freeLocalEntities; // single linked list // Ridah, debugging int localEntCount = 0; /* =================== CG_InitLocalEntities This is called at startup and for tournement restarts =================== */ void CG_InitLocalEntities( void ) { int i; memset( cg_localEntities, 0, sizeof( cg_localEntities ) ); cg_activeLocalEntities.next = &cg_activeLocalEntities; cg_activeLocalEntities.prev = &cg_activeLocalEntities; cg_freeLocalEntities = cg_localEntities; for ( i = 0 ; i < MAX_LOCAL_ENTITIES - 1 ; i++ ) { cg_localEntities[i].next = &cg_localEntities[i + 1]; } // Ridah, debugging localEntCount = 0; } /* ================== CG_FreeLocalEntity ================== */ void CG_FreeLocalEntity( localEntity_t *le ) { if ( !le->prev ) { CG_Error( "CG_FreeLocalEntity: not active" ); } // Ridah, debugging localEntCount--; // trap_Print( va("FreeLocalEntity: locelEntCount = %d\n", localEntCount) ); // done. // remove from the doubly linked active list le->prev->next = le->next; le->next->prev = le->prev; // the free list is only singly linked le->next = cg_freeLocalEntities; cg_freeLocalEntities = le; } /* =================== CG_AllocLocalEntity Will allways succeed, even if it requires freeing an old active entity =================== */ localEntity_t *CG_AllocLocalEntity( void ) { localEntity_t *le; if ( !cg_freeLocalEntities ) { // no free entities, so free the one at the end of the chain // remove the oldest active entity CG_FreeLocalEntity( cg_activeLocalEntities.prev ); } // Ridah, debugging localEntCount++; // trap_Print( va("AllocLocalEntity: locelEntCount = %d\n", localEntCount) ); // done. le = cg_freeLocalEntities; cg_freeLocalEntities = cg_freeLocalEntities->next; memset( le, 0, sizeof( *le ) ); // link into the active list le->next = cg_activeLocalEntities.next; le->prev = &cg_activeLocalEntities; cg_activeLocalEntities.next->prev = le; cg_activeLocalEntities.next = le; return le; } /* ==================================================================================== FRAGMENT PROCESSING A fragment localentity interacts with the environment in some way (hitting walls), or generates more localentities along a trail. ==================================================================================== */ /* ================ CG_BloodTrail Leave expanding blood puffs behind gibs ================ */ // use this to change between particle and trail code //#define BLOOD_PARTICLE_TRAIL void CG_BloodTrail( localEntity_t *le ) { int t; int t2; int step; vec3_t newOrigin; float vl; //bani #ifndef BLOOD_PARTICLE_TRAIL static vec3_t col = {1,1,1}; #endif centity_t *cent; cent = &cg_entities[le->ownerNum]; if ( !cg_blood.integer ) { return; } // step = 150; #ifdef BLOOD_PARTICLE_TRAIL step = 10; #else // time it takes to move 3 units vl = VectorLength( le->pos.trDelta ); // rain - need to check FLT_EPSILON because floating-point math sucks <3 if ( vl < FLT_EPSILON ) { return; } step = ( 1000 * 3 ) / vl; //bani - avoid another div by 0 //zinx - check against <= 0 instead of == 0, because it can still wrap; (3000 / (FLT_EPSILON*11.7f)) < 0 if ( step <= 0 ) { return; } #endif t = step * ( ( cg.time - cg.frametime + step ) / step ); t2 = step * ( cg.time / step ); for ( ; t <= t2; t += step ) { BG_EvaluateTrajectory( &le->pos, t, newOrigin, qfalse, -1 ); #ifdef BLOOD_PARTICLE_TRAIL CG_Particle_Bleed( cgs.media.smokePuffShader, newOrigin, vec3_origin, 0, 500 + rand() % 200 ); #else // Ridah, blood trail using trail code (should be faster since we don't have to spawn as many) le->headJuncIndex = CG_AddTrailJunc( le->headJuncIndex, le, // rain - zinx's trail fix cgs.media.bloodTrailShader, t, STYPE_STRETCH, newOrigin, 180, 1.0, // start alpha 0.0, // end alpha 12.0, 12.0, TJFL_NOCULL, col, col, 0, 0 ); #endif } } /* ================ CG_FragmentBounceMark ================ */ void CG_FragmentBounceMark( localEntity_t *le, trace_t *trace ) { int radius; vec4_t projection, color; if ( le->leMarkType == LEMT_BLOOD ) { static int lastBloodMark; // don't drop too many blood marks if ( !( lastBloodMark > cg.time || lastBloodMark > cg.time - 100 ) ) { radius = 16 + ( rand() & 31 ); //% CG_ImpactMark( cgs.media.bloodDotShaders[rand()%5], trace->endpos, trace->plane.normal, random()*360, //% 1,1,1,1, qtrue, radius, qfalse, cg_bloodTime.integer * 1000 ); #if 0 VectorSubtract( vec3_origin, trace->plane.normal, projection ); projection[ 3 ] = radius * 2.0f; VectorMA( trace->endpos, -8.0f, projection, markOrigin ); CG_ImpactMark( cgs.media.bloodDotShaders[ rand() % 5 ], markOrigin, projection, radius, random() * 360.0f, 1.0f, 1.0f, 1.0f, 1.0f, cg_bloodTime.integer * 1000 ); #else VectorSet( projection, 0, 0, -1 ); projection[ 3 ] = radius; Vector4Set( color, 1.0f, 1.0f, 1.0f, 1.0f ); trap_R_ProjectDecal( cgs.media.bloodDotShaders[ rand() % 5 ], 1, (vec3_t*) trace->endpos, projection, color, cg_bloodTime.integer * 1000, ( cg_bloodTime.integer * 1000 ) >> 4 ); #endif lastBloodMark = cg.time; } } // don't allow a fragment to make multiple marks, or they // pile up while settling le->leMarkType = LEMT_NONE; } /* ================ CG_FragmentBounceSound ================ */ void CG_FragmentBounceSound( localEntity_t *le, trace_t *trace ) { int rnd; // Gordon: bleh, has no-one heard of switch statements... switch ( le->leBounceSoundType ) { // Gordon: adding machinegun brass bouncy sound for tk case LEBS_BRASS: rnd = rand() % 3; if ( trace->surfaceFlags & SURF_METAL ) { trap_S_StartSoundVControl( trace->endpos, -1, CHAN_AUTO, cgs.media.sfx_brassSound[BRASSSOUND_METAL][rnd], 64 ); } else if ( trace->surfaceFlags & SURF_WOOD ) { trap_S_StartSoundVControl( trace->endpos, -1, CHAN_AUTO, cgs.media.sfx_brassSound[BRASSSOUND_WOOD][rnd], 64 ); } else if ( trace->surfaceFlags & ( SURF_GRAVEL | SURF_SNOW | SURF_CARPET | SURF_GRASS ) ) { trap_S_StartSoundVControl( trace->endpos, -1, CHAN_AUTO, cgs.media.sfx_brassSound[BRASSSOUND_SOFT][rnd], 64 ); } else { trap_S_StartSoundVControl( trace->endpos, -1, CHAN_AUTO, cgs.media.sfx_brassSound[BRASSSOUND_STONE][rnd], 64 ); } break; case LEBS_ROCK: rnd = rand() % 3; trap_S_StartSound( trace->endpos, -1, CHAN_AUTO, cgs.media.sfx_rubbleBounce[rnd] ); break; case LEBS_BONE: trap_S_StartSound( trace->endpos, -1, CHAN_AUTO, cgs.media.boneBounceSound ); break; default: return; } // don't allow a fragment to make multiple bounce sounds, // or it gets too noisy as they settle le->leBounceSoundType = LEBS_NONE; } /* ================ CG_ReflectVelocity ================ */ void CG_ReflectVelocity( localEntity_t *le, trace_t *trace ) { vec3_t velocity; float dot; int hitTime; // reflect the velocity on the trace plane hitTime = cg.time - cg.frametime + cg.frametime * trace->fraction; BG_EvaluateTrajectoryDelta( &le->pos, hitTime, velocity, qfalse, -1 ); dot = DotProduct( velocity, trace->plane.normal ); VectorMA( velocity, -2 * dot, trace->plane.normal, le->pos.trDelta ); VectorScale( le->pos.trDelta, le->bounceFactor, le->pos.trDelta ); VectorCopy( trace->endpos, le->pos.trBase ); le->pos.trTime = cg.time; // check for stop, making sure that even on low FPS systems it doesn't bobble if ( le->leMarkType == LEMT_BLOOD && trace->startsolid ) { //centity_t *cent; //cent = &cg_entities[trace->entityNum]; //if (cent && cent->currentState.apos.trType != TR_STATIONARY) // le->pos.trType = TR_STATIONARY; } else if ( trace->allsolid || ( trace->plane.normal[2] > 0 && ( le->pos.trDelta[2] < 40 || le->pos.trDelta[2] < -cg.frametime * le->pos.trDelta[2] ) ) ) { //----(SA) if it's a fragment and it's not resting on the world... // if(le->leType == LE_DEBRIS && trace->entityNum < (MAX_ENTITIES - 1)) if ( le->leType == LE_FRAGMENT && trace->entityNum < ( MAX_ENTITIES - 1 ) ) { le->pos.trType = TR_GRAVITY_PAUSED; } else { le->pos.trType = TR_STATIONARY; } } } //----(SA) added /* ============== CG_AddEmitter ============== */ void CG_AddEmitter( localEntity_t *le ) { vec3_t dir; if ( le->breakCount > cg.time ) { // using 'breakCount' for 'wait' return; } VectorScale( le->angles.trBase, 30, dir ); CG_Particle_OilParticle( cgs.media.oilParticle, le->pos.trBase, dir, 15000, le->ownerNum ); le->breakCount = cg.time + 50; } //----(SA) end void CG_Explodef( vec3_t origin, vec3_t dir, int mass, int type, qhandle_t sound, int forceLowGrav, qhandle_t shader ); /* ================ CG_AddFragment ================ */ void CG_AddFragment( localEntity_t *le ) { vec3_t newOrigin; trace_t trace; refEntity_t *re; float flameAlpha = 0.0; // TTimo: init vec3_t flameDir; qboolean hasFlame = qfalse; int i; // Ridah re = &le->refEntity; if ( !re->fadeStartTime || re->fadeEndTime < le->endTime ) { if ( le->endTime - cg.time > 5000 ) { re->fadeStartTime = le->endTime - 5000; } else { re->fadeStartTime = le->endTime - 1000; } re->fadeEndTime = le->endTime; } // Ridah, flaming gibs if ( le->onFireStart && ( le->onFireStart < cg.time && le->onFireEnd > cg.time ) ) { hasFlame = qtrue; // calc the alpha flameAlpha = 1.0 - ( (float)( cg.time - le->onFireStart ) / (float)( le->onFireEnd - le->onFireStart ) ); if ( flameAlpha < 0.0 ) { flameAlpha = 0.0; } if ( flameAlpha > 1.0 ) { flameAlpha = 1.0; } trap_S_AddLoopingSound( le->refEntity.origin, vec3_origin, cgs.media.flameCrackSound, (int)( 20.0 * flameAlpha ), 0 ); } //----(SA) added if ( le->leFlags & LEF_SMOKING ) { float alpha; refEntity_t flash; // create a little less smoke // TODO: FIXME: this is not quite right, because it'll become fps dependant - in a bad way. // the slower the fps, the /more/ smoke there'll be, probably driving the fps lower. if ( !( rand() % 5 ) ) { alpha = 1.0 - ( (float)( cg.time - le->startTime ) / (float)( le->endTime - le->startTime ) ); alpha *= 0.25f; memset( &flash, 0, sizeof( flash ) ); CG_PositionEntityOnTag( &flash, &le->refEntity, "tag_flash", 0, NULL ); CG_ParticleImpactSmokePuffExtended( cgs.media.smokeParticleShader, flash.origin, 1000, 8, 20, 20, alpha, 8.f ); } } //----(SA) end if ( le->pos.trType == TR_STATIONARY ) { int t; // Ridah, add the flame if ( hasFlame ) { refEntity_t backupEnt; backupEnt = le->refEntity; VectorClear( flameDir ); flameDir[2] = 1; le->refEntity.shaderRGBA[3] = ( unsigned char )( 255.0 * flameAlpha ); VectorCopy( flameDir, le->refEntity.fireRiseDir ); le->refEntity.customShader = cgs.media.onFireShader; trap_R_AddRefEntityToScene( &le->refEntity ); le->refEntity.customShader = cgs.media.onFireShader2; trap_R_AddRefEntityToScene( &le->refEntity ); le->refEntity = backupEnt; } t = le->endTime - cg.time; trap_R_AddRefEntityToScene( &le->refEntity ); return; } else if ( le->pos.trType == TR_GRAVITY_PAUSED ) { int t; // Ridah, add the flame if ( hasFlame ) { refEntity_t backupEnt; backupEnt = le->refEntity; VectorClear( flameDir ); flameDir[2] = 1; le->refEntity.shaderRGBA[3] = ( unsigned char )( 255.0 * flameAlpha ); VectorCopy( flameDir, le->refEntity.fireRiseDir ); le->refEntity.customShader = cgs.media.onFireShader; trap_R_AddRefEntityToScene( &le->refEntity ); le->refEntity.customShader = cgs.media.onFireShader2; trap_R_AddRefEntityToScene( &le->refEntity ); le->refEntity = backupEnt; } t = le->endTime - cg.time; trap_R_AddRefEntityToScene( &le->refEntity ); // trace a line from previous position down, to see if I should start falling again VectorCopy( le->refEntity.origin, newOrigin ); newOrigin [2] -= 5; CG_Trace( &trace, le->refEntity.origin, NULL, NULL, newOrigin, -1, CONTENTS_SOLID | CONTENTS_PLAYERCLIP | CONTENTS_MISSILECLIP ); if ( trace.fraction == 1.0 ) { // it's clear, start moving again VectorClear( le->pos.trDelta ); VectorClear( le->angles.trDelta ); le->pos.trType = TR_GRAVITY; // nothing below me, start falling again } else { return; } } // calculate new position BG_EvaluateTrajectory( &le->pos, cg.time, newOrigin, qfalse, -1 ); if ( hasFlame ) { // calc the flame dir VectorSubtract( le->refEntity.origin, newOrigin, flameDir ); if ( VectorLengthSquared( flameDir ) == 0 ) { flameDir[2] = 1; // play a burning sound when not moving trap_S_AddLoopingSound( newOrigin, vec3_origin, cgs.media.flameSound, (int)( 0.3 * 255.0 * flameAlpha ), 0 ); } else { VectorNormalize( flameDir ); // play a flame blow sound when moving trap_S_AddLoopingSound( newOrigin, vec3_origin, cgs.media.flameBlowSound, (int)( 0.3 * 255.0 * flameAlpha ), 0 ); } } // trace a line from previous position to new position CG_Trace( &trace, le->refEntity.origin, NULL, NULL, newOrigin, -1, CONTENTS_SOLID ); if ( trace.fraction == 1.0 ) { // still in free fall VectorCopy( newOrigin, le->refEntity.origin ); if ( le->leFlags & LEF_TUMBLE || le->angles.trType == TR_LINEAR ) { vec3_t angles; BG_EvaluateTrajectory( &le->angles, cg.time, angles, qtrue, -1 ); AnglesToAxis( angles, le->refEntity.axis ); if ( le->sizeScale && le->sizeScale != 1.0 ) { for ( i = 0; i < 3; i++ ) { VectorScale( le->refEntity.axis[i], le->sizeScale, le->refEntity.axis[i] ); } le->refEntity.nonNormalizedAxes = qtrue; } } else { AnglesToAxis( le->angles.trBase, le->refEntity.axis ); if ( le->sizeScale && le->sizeScale != 1.0 ) { for ( i = 0; i < 3; i++ ) { VectorScale( le->refEntity.axis[i], le->sizeScale, le->refEntity.axis[i] ); } le->refEntity.nonNormalizedAxes = qtrue; } } // Ridah, add the flame if ( hasFlame ) { refEntity_t backupEnt; backupEnt = le->refEntity; le->refEntity.shaderRGBA[3] = ( unsigned char )( 255.0 * flameAlpha ); VectorCopy( flameDir, le->refEntity.fireRiseDir ); le->refEntity.customShader = cgs.media.onFireShader; trap_R_AddRefEntityToScene( &le->refEntity ); le->refEntity.customShader = cgs.media.onFireShader2; trap_R_AddRefEntityToScene( &le->refEntity ); le->refEntity = backupEnt; } trap_R_AddRefEntityToScene( &le->refEntity ); // add a blood trail if ( le->leBounceSoundType == LEBS_BLOOD ) { CG_BloodTrail( le ); } return; } // if it is in a nodrop zone, remove it // this keeps gibs from waiting at the bottom of pits of death // and floating levels if ( CG_PointContents( trace.endpos, 0 ) & CONTENTS_NODROP ) { CG_FreeLocalEntity( le ); return; } // do a bouncy sound CG_FragmentBounceSound( le, &trace ); // reflect the velocity on the trace plane CG_ReflectVelocity( le, &trace ); if ( le->leFlags & LEF_TUMBLE_SLOW ) { VectorScale( le->angles.trDelta, 0.8, le->angles.trDelta ); } // break on contact? if ( le->breakCount ) { if ( le->leFlags & LEF_TUMBLE_SLOW ) { // Gordon: HACK HACK x_X vec3_t org, dir; float sizeScale; // make it smaller sizeScale = le->sizeScale * 0.8; if ( sizeScale < 0.7 ) { sizeScale = 0.7; } // move us a bit VectorNormalize2( le->pos.trDelta, dir ); VectorMA( trace.endpos, 4.0 * sizeScale, dir, org ); // randomize vel a bit VectorMA( le->pos.trDelta, VectorLength( le->pos.trDelta ) * 0.3, bytedirs[rand() % NUMVERTEXNORMALS], dir ); CG_Explodef( org, dir, sizeScale * 50, 0, 0, qfalse, trap_R_GetShaderFromModel( le->refEntity.hModel, 0, 0 ) ); CG_FreeLocalEntity( le ); return; } else { #if 0 // FIXME: re-add gibmodel support? clientInfo_t *ci; int clientNum; localEntity_t *nle; vec3_t dir; bg_character_t *character; clientNum = le->ownerNum; if ( clientNum < 0 || clientNum >= MAX_CLIENTS ) { CG_Error( "Bad clientNum on player entity" ); } ci = &cgs.clientinfo[ clientNum ]; character = CG_CharacterForClientinfo( ci, NULL ); // spawn some new fragments for ( i = 0; i <= le->breakCount; i++ ) { nle = CG_AllocLocalEntity(); memcpy( &( nle->leType ), &( le->leType ), sizeof( localEntity_t ) - 2 * sizeof( localEntity_t * ) ); if ( nle->breakCount-- < 2 ) { nle->refEntity.hModel = character->gibModels[rand() % 2]; } else { nle->refEntity.hModel = character->gibModels[rand() % 4]; } // make it smaller nle->endTime = cg.time + 5000 + rand() % 2000; nle->sizeScale *= 0.8; if ( nle->sizeScale < 0.7 ) { nle->sizeScale = 0.7; nle->leBounceSoundType = 0; } // move us a bit VectorNormalize2( nle->pos.trDelta, dir ); VectorMA( trace.endpos, 4.0 * le->sizeScale * i, dir, nle->pos.trBase ); // randomize vel a bit VectorMA( nle->pos.trDelta, VectorLength( nle->pos.trDelta ) * 0.3, bytedirs[rand() % NUMVERTEXNORMALS], nle->pos.trDelta ); } // we're done CG_FreeLocalEntity( le ); #endif // 0 return; } } if ( le->pos.trType == TR_STATIONARY && le->leMarkType == LEMT_BLOOD ) { // leave a mark if ( le->leMarkType ) { CG_FragmentBounceMark( le, &trace ); } } // Ridah, add the flame if ( hasFlame ) { refEntity_t backupEnt; backupEnt = le->refEntity; le->refEntity.shaderRGBA[3] = ( unsigned char )( 255.0 * flameAlpha ); VectorCopy( flameDir, le->refEntity.fireRiseDir ); le->refEntity.customShader = cgs.media.onFireShader; trap_R_AddRefEntityToScene( &le->refEntity ); le->refEntity.customShader = cgs.media.onFireShader2; trap_R_AddRefEntityToScene( &le->refEntity ); le->refEntity = backupEnt; } trap_R_AddRefEntityToScene( &le->refEntity ); } // Ridah /* ================ CG_AddMovingTracer ================ */ void CG_AddMovingTracer( localEntity_t *le ) { vec3_t start, end, dir; BG_EvaluateTrajectory( &le->pos, cg.time, start, qfalse, -1 ); VectorNormalize2( le->pos.trDelta, dir ); VectorMA( start, cg_tracerLength.value, dir, end ); CG_DrawTracer( start, end ); } /* ================ CG_AddSparkElements ================ */ void CG_AddSparkElements( localEntity_t *le ) { vec3_t newOrigin; trace_t trace; float time; float lifeFrac; time = (float)( cg.time - cg.frametime ); while ( 1 ) { // calculate new position BG_EvaluateTrajectory( &le->pos, cg.time, newOrigin, qfalse, -1 ); // if ((le->endTime - le->startTime) > 500) { // trace a line from previous position to new position CG_Trace( &trace, le->refEntity.origin, NULL, NULL, newOrigin, -1, MASK_SHOT ); // if stuck, kill it if ( trace.startsolid ) { // HACK, some walls screw up, so just pass through if starting in a solid VectorCopy( newOrigin, trace.endpos ); trace.fraction = 1.0; } // moved some distance VectorCopy( trace.endpos, le->refEntity.origin ); /* } else { // just move it there VectorCopy( newOrigin, le->refEntity.origin ); trace.fraction = 1.0; } */ time += cg.frametime * trace.fraction; lifeFrac = (float)( cg.time - le->startTime ) / (float)( le->endTime - le->startTime ); // add a trail le->headJuncIndex = CG_AddSparkJunc( le->headJuncIndex, le, // rain - zinx's trail fix le->refEntity.customShader, le->refEntity.origin, 200, 1.0 - lifeFrac, // start alpha 0.0, //1.0 - lifeFrac, // end alpha lifeFrac * 2.0 * ( ( ( le->endTime - le->startTime ) > 400 ) + 1 ) * 1.5, lifeFrac * 2.0 * ( ( ( le->endTime - le->startTime ) > 400 ) + 1 ) * 1.5 ); // if it is in a nodrop zone, remove it // this keeps gibs from waiting at the bottom of pits of death // and floating levels // for some reason SFM1.BSP is one big NODROP zone // if ( CG_PointContents( le->refEntity.origin, 0 ) & CONTENTS_NODROP ) { // CG_FreeLocalEntity( le ); // return; // } if ( trace.fraction < 1.0 ) { // just kill it CG_FreeLocalEntity( le ); return; /* // reflect the velocity on the trace plane CG_ReflectVelocity( le, &trace ); // the intersection is a fraction of the frametime le->pos.trTime = (int)time; */ } if ( trace.fraction == 1.0 || time >= (float)cg.time ) { return; } } } /* ================ CG_AddFuseSparkElements ================ */ void CG_AddFuseSparkElements( localEntity_t *le ) { float FUSE_SPARK_WIDTH = 1.0; int step = 10; float time; float lifeFrac; static vec3_t whiteColor = {1,1,1}; time = (float)( le->lastTrailTime ); while ( time < cg.time ) { // calculate new position BG_EvaluateTrajectory( &le->pos, time, le->refEntity.origin, qfalse, -1 ); lifeFrac = (float)( time - le->startTime ) / (float)( le->endTime - le->startTime ); //if (lifeFrac > 0.2) { // add a trail // rain - added le for zinx's trail fix le->headJuncIndex = CG_AddTrailJunc( le->headJuncIndex, le, cgs.media.sparkParticleShader, time, STYPE_STRETCH, le->refEntity.origin, (int)( lifeFrac * (float)( le->endTime - le->startTime ) / 2.0 ), 1.0 /*(1.0 - lifeFrac)*/, 0.0, FUSE_SPARK_WIDTH * ( 1.0 - lifeFrac ), FUSE_SPARK_WIDTH * ( 1.0 - lifeFrac ), TJFL_SPARKHEADFLARE, whiteColor, whiteColor, 0, 0 ); //} time += step; le->lastTrailTime = time; } } /* ================ CG_AddBloodElements ================ */ void CG_AddBloodElements( localEntity_t *le ) { vec3_t newOrigin; trace_t trace; float time; float lifeFrac; int numbounces; time = (float)( cg.time - cg.frametime ); for ( numbounces = 0; numbounces < 5; numbounces++ ) { // calculate new position BG_EvaluateTrajectory( &le->pos, cg.time, newOrigin, qfalse, -1 ); // trace a line from previous position to new position CG_Trace( &trace, le->refEntity.origin, NULL, NULL, newOrigin, -1, MASK_SHOT ); // if stuck, kill it if ( trace.startsolid ) { // HACK, some walls screw up, so just pass through if starting in a solid VectorCopy( newOrigin, trace.endpos ); trace.fraction = 1.0; } // moved some distance VectorCopy( trace.endpos, le->refEntity.origin ); time += cg.frametime * trace.fraction; lifeFrac = (float)( cg.time - le->startTime ) / (float)( le->endTime - le->startTime ); // add a trail le->headJuncIndex = CG_AddSparkJunc( le->headJuncIndex, le, // rain - zinx's trail fix cgs.media.bloodTrailShader, le->refEntity.origin, 200, 1.0 - lifeFrac, // start alpha 1.0 - lifeFrac, // end alpha 3.0, 5.0 ); if ( trace.fraction < 1.0 ) { // reflect the velocity on the trace plane CG_ReflectVelocity( le, &trace ); // TODO: spawn a blood decal here? // the intersection is a fraction of the frametime le->pos.trTime = (int)time; } if ( trace.fraction == 1.0 || time >= (float)cg.time ) { return; } } } /* ================ CG_AddDebrisElements ================ */ void CG_AddDebrisElements( localEntity_t *le ) { vec3_t newOrigin; trace_t trace; float lifeFrac; int t, step = 50; for ( t = le->lastTrailTime + step; t < cg.time; t += step ) { // calculate new position BG_EvaluateTrajectory( &le->pos, t, newOrigin, qfalse, -1 ); // trace a line from previous position to new position CG_Trace( &trace, le->refEntity.origin, NULL, NULL, newOrigin, -1, MASK_SHOT ); // if stuck, kill it if ( trace.startsolid ) { // HACK, some walls screw up, so just pass through if starting in a solid VectorCopy( newOrigin, trace.endpos ); trace.fraction = 1.0; } // moved some distance VectorCopy( trace.endpos, le->refEntity.origin ); // add a trail lifeFrac = (float)( t - le->startTime ) / (float)( le->endTime - le->startTime ); #if 0 // fire #if 1 // flame if ( le->effectWidth > 0 ) { le->headJuncIndex = CG_AddSparkJunc( le->headJuncIndex, le, // rain - zinx's trail fix cgs.media.fireTrailShader, le->refEntity.origin, (int)( 500.0 * ( 0.5 + 0.5 * ( 1.0 - lifeFrac ) ) ), // trail life 1.0, // alpha 0.5, // end alpha 3, // start width le->effectWidth ); // end width #else // spark line if ( le->effectWidth > 0 ) { le->headJuncIndex = CG_AddSparkJunc( le->headJuncIndex, le, // rain - zinx's trail fix cgs.media.sparkParticleShader, le->refEntity.origin, (int)( 600.0 * ( 0.5 + 0.5 * ( 0.5 - lifeFrac ) ) ), // trail life 1.0 - lifeFrac * 2, // alpha 0.5 * ( 1.0 - lifeFrac ), // end alpha 5.0 * ( 1.0 - lifeFrac ), // start width 5.0 * ( 1.0 - lifeFrac ) ); // end width #endif } #endif // smoke if ( le->effectFlags & 1 ) { le->headJuncIndex2 = CG_AddSmokeJunc( le->headJuncIndex2, le, // rain - zinx's trail fix cgs.media.smokeTrailShader, le->refEntity.origin, (int)( 2000.0 * ( 0.5 + 0.5 * ( 1.0 - lifeFrac ) ) ), // trail life 1.0 * ( trace.fraction == 1.0 ) * ( 0.5 + 0.5 * ( 1.0 - lifeFrac ) ), // alpha 1, // start width (int)( 60.0 * ( 0.5 + 0.5 * ( 1.0 - lifeFrac ) ) ) ); // end width } // if it is in a nodrop zone, remove it // this keeps gibs from waiting at the bottom of pits of death // and floating levels // if ( CG_PointContents( trace.endpos, 0 ) & CONTENTS_NODROP ) { // CG_FreeLocalEntity( le ); // return; // } if ( trace.fraction < 1.0 ) { // reflect the velocity on the trace plane CG_ReflectVelocity( le, &trace ); if ( VectorLengthSquared( le->pos.trDelta ) < SQR( 1 ) ) { CG_FreeLocalEntity( le ); return; } // the intersection is a fraction of the frametime le->pos.trTime = t; } le->lastTrailTime = t; } } // Rafael Shrapnel /* =============== CG_AddShrapnel =============== */ void CG_AddShrapnel( localEntity_t *le ) { vec3_t newOrigin; trace_t trace; if ( le->pos.trType == TR_STATIONARY ) { // sink into the ground if near the removal time int t; float oldZ; t = le->endTime - cg.time; if ( t < SINK_TIME ) { // we must use an explicit lighting origin, otherwise the // lighting would be lost as soon as the origin went // into the ground VectorCopy( le->refEntity.origin, le->refEntity.lightingOrigin ); le->refEntity.renderfx |= RF_LIGHTING_ORIGIN; oldZ = le->refEntity.origin[2]; le->refEntity.origin[2] -= 16 * ( 1.0 - (float)t / SINK_TIME ); trap_R_AddRefEntityToScene( &le->refEntity ); le->refEntity.origin[2] = oldZ; } else { trap_R_AddRefEntityToScene( &le->refEntity ); CG_AddParticleShrapnel( le ); } return; } // calculate new position BG_EvaluateTrajectory( &le->pos, cg.time, newOrigin, qfalse, -1 ); // trace a line from previous position to new position CG_Trace( &trace, le->refEntity.origin, NULL, NULL, newOrigin, -1, CONTENTS_SOLID ); if ( trace.fraction == 1.0 ) { // still in free fall VectorCopy( newOrigin, le->refEntity.origin ); if ( le->leFlags & LEF_TUMBLE ) { vec3_t angles; BG_EvaluateTrajectory( &le->angles, cg.time, angles, qtrue, -1 ); AnglesToAxis( angles, le->refEntity.axis ); } trap_R_AddRefEntityToScene( &le->refEntity ); CG_AddParticleShrapnel( le ); return; } // if it is in a nodrop zone, remove it // this keeps gibs from waiting at the bottom of pits of death // and floating levels if ( CG_PointContents( trace.endpos, 0 ) & CONTENTS_NODROP ) { CG_FreeLocalEntity( le ); return; } // leave a mark CG_FragmentBounceMark( le, &trace ); // do a bouncy sound CG_FragmentBounceSound( le, &trace ); // reflect the velocity on the trace plane CG_ReflectVelocity( le, &trace ); trap_R_AddRefEntityToScene( &le->refEntity ); CG_AddParticleShrapnel( le ); } // done. /* ===================================================================== TRIVIAL LOCAL ENTITIES These only do simple scaling or modulation before passing to the renderer ===================================================================== */ /* ==================== CG_AddFadeRGB ==================== */ void CG_AddFadeRGB( localEntity_t *le ) { refEntity_t *re; float c; re = &le->refEntity; c = ( le->endTime - cg.time ) * le->lifeRate; c *= 0xff; re->shaderRGBA[0] = le->color[0] * c; re->shaderRGBA[1] = le->color[1] * c; re->shaderRGBA[2] = le->color[2] * c; re->shaderRGBA[3] = le->color[3] * c; trap_R_AddRefEntityToScene( re ); } /* ================== CG_AddMoveScaleFade ================== */ static void CG_AddMoveScaleFade( localEntity_t *le ) { refEntity_t *re; float c; vec3_t delta; float len; re = &le->refEntity; // fade / grow time // c = ( le->endTime - cg.time ) * le->lifeRate; if ( le->fadeInTime > le->startTime && cg.time < le->fadeInTime ) { // fade / grow time c = 1.0 - (float) ( le->fadeInTime - cg.time ) / ( le->fadeInTime - le->startTime ); } else { // fade / grow time c = ( le->endTime - cg.time ) * le->lifeRate; } // Ridah, spark if ( !( le->leFlags & LEF_NOFADEALPHA ) ) { // done. re->shaderRGBA[3] = 0xff * c * le->color[3]; } if ( !( le->leFlags & LEF_PUFF_DONT_SCALE ) ) { c = ( le->endTime - cg.time ) * le->lifeRate; re->radius = le->radius * ( 1.0 - c ) + 8; } BG_EvaluateTrajectory( &le->pos, cg.time, re->origin, qfalse, -1 ); // if the view would be "inside" the sprite, kill the sprite // so it doesn't add too much overdraw VectorSubtract( re->origin, cg.refdef_current->vieworg, delta ); len = VectorLength( delta ); if ( len < le->radius ) { CG_FreeLocalEntity( le ); return; } trap_R_AddRefEntityToScene( re ); } /* =================== CG_AddScaleFade For rocket smokes that hang in place, fade out, and are removed if the view passes through them. There are often many of these, so it needs to be simple. =================== */ static void CG_AddScaleFade( localEntity_t *le ) { refEntity_t *re; float c; vec3_t delta; float len; re = &le->refEntity; // fade / grow time c = ( le->endTime - cg.time ) * le->lifeRate; re->shaderRGBA[3] = 0xff * c * le->color[3]; if ( !( le->leFlags & LEF_PUFF_DONT_SCALE ) ) { re->radius = le->radius * ( 1.0 - c ) + 8; } // if the view would be "inside" the sprite, kill the sprite // so it doesn't add too much overdraw VectorSubtract( re->origin, cg.refdef_current->vieworg, delta ); len = VectorLength( delta ); if ( len < le->radius ) { CG_FreeLocalEntity( le ); return; } trap_R_AddRefEntityToScene( re ); } /* ================= CG_AddFallScaleFade This is just an optimized CG_AddMoveScaleFade For blood mists that drift down, fade out, and are removed if the view passes through them. There are often 100+ of these, so it needs to be simple. ================= */ static void CG_AddFallScaleFade( localEntity_t *le ) { refEntity_t *re; float c; vec3_t delta; float len; re = &le->refEntity; // fade time c = ( le->endTime - cg.time ) * le->lifeRate; re->shaderRGBA[3] = 0xff * c * le->color[3]; re->origin[2] = le->pos.trBase[2] - ( 1.0 - c ) * le->pos.trDelta[2]; re->radius = le->radius * ( 1.0 - c ) + 16; // if the view would be "inside" the sprite, kill the sprite // so it doesn't add too much overdraw VectorSubtract( re->origin, cg.refdef_current->vieworg, delta ); len = VectorLength( delta ); if ( len < le->radius ) { CG_FreeLocalEntity( le ); return; } trap_R_AddRefEntityToScene( re ); } /* ================ CG_AddExplosion ================ */ static void CG_AddExplosion( localEntity_t *ex ) { refEntity_t *ent; ent = &ex->refEntity; // add the entity // RF, don't add if shader is invalid if ( ent->customShader >= 0 ) { trap_R_AddRefEntityToScene( ent ); } // add the dlight if ( ex->light || 1 ) { float light; light = (float)( cg.time - ex->startTime ) / ( ex->endTime - ex->startTime ); if ( light < 0.5 ) { light = 1.0; } else { light = 1.0 - ( light - 0.5 ) * 2; } light = ex->light * light; //% trap_R_AddLightToScene(ent->origin, light, ex->lightColor[0], ex->lightColor[1], ex->lightColor[2], 0 ); trap_R_AddLightToScene( ent->origin, 512, light, ex->lightColor[ 0 ], ex->lightColor[ 1 ], ex->lightColor[ 2 ], 0, 0 ); } } /* ================ CG_AddSpriteExplosion ================ */ static void CG_AddSpriteExplosion( localEntity_t *le ) { refEntity_t re; float c; re = le->refEntity; c = ( le->endTime - cg.time ) / ( float ) ( le->endTime - le->startTime ); if ( c > 1 ) { c = 1.0; // can happen during connection problems } re.shaderRGBA[0] = 0xff; re.shaderRGBA[1] = 0xff; re.shaderRGBA[2] = 0xff; re.shaderRGBA[3] = 0xff * c * 0.33; re.reType = RT_SPRITE; re.radius = 42 * ( 1.0 - c ) + 30; // Ridah, move away from surface VectorMA( le->pos.trBase, ( 1.0 - c ), le->pos.trDelta, re.origin ); // done. // RF, don't add if shader is invalid if ( re.customShader >= 0 ) { trap_R_AddRefEntityToScene( &re ); } // add the dlight if ( le->light || 1 ) { float light; // Ridah, modified this so the light fades out rather than shrinking /* light = (float)( cg.time - le->startTime ) / ( le->endTime - le->startTime ); if ( light < 0.5 ) { light = 1.0; } else { light = 1.0 - ( light - 0.5 ) * 2; } light = le->light * light; trap_R_AddLightToScene(re.origin, light, le->lightColor[0], le->lightColor[1], le->lightColor[2], 0 ); */ light = (float)( cg.time - le->startTime ) / ( le->endTime - le->startTime ); if ( light < 0.5 ) { light = 1.0; } else { light = 1.0 - ( light - 0.5 ) * 2; } //% trap_R_AddLightToScene(re.origin, le->light, light*le->lightColor[0], light*le->lightColor[1], light*le->lightColor[2], 0 ); trap_R_AddLightToScene( re.origin, 320, light, le->lightColor[ 0 ], le->lightColor[ 1 ], le->lightColor[ 2 ], 0, 0 ); // done. } } //============================================================================== /* =================== CG_AddLocalEntities =================== */ void CG_AddLocalEntities( void ) { localEntity_t *le, *next; // walk the list backwards, so any new local entities generated // (trails, marks, etc) will be present this frame le = cg_activeLocalEntities.prev; for ( ; le != &cg_activeLocalEntities ; le = next ) { // grab next now, so if the local entity is freed we // still have it next = le->prev; if ( cg.time >= le->endTime ) { CG_FreeLocalEntity( le ); continue; } switch ( le->leType ) { default: CG_Error( "Bad leType: %i", le->leType ); break; // Ridah case LE_MOVING_TRACER: CG_AddMovingTracer( le ); break; case LE_SPARK: CG_AddSparkElements( le ); break; case LE_FUSE_SPARK: CG_AddFuseSparkElements( le ); break; case LE_DEBRIS: CG_AddDebrisElements( le ); break; case LE_BLOOD: CG_AddBloodElements( le ); break; /* case LE_ZOMBIE_SPIRIT: case LE_ZOMBIE_BAT: CG_AddClientCritter( le ); break;*/ // done. case LE_MARK: break; case LE_SPRITE_EXPLOSION: CG_AddSpriteExplosion( le ); break; case LE_EXPLOSION: CG_AddExplosion( le ); break; case LE_FRAGMENT: // gibs and brass CG_AddFragment( le ); break; case LE_MOVE_SCALE_FADE: // water bubbles CG_AddMoveScaleFade( le ); break; case LE_FADE_RGB: // teleporters, railtrails CG_AddFadeRGB( le ); break; case LE_FALL_SCALE_FADE: // gib blood trails CG_AddFallScaleFade( le ); break; case LE_SCALE_FADE: // rocket trails CG_AddScaleFade( le ); break; case LE_EMITTER: CG_AddEmitter( le ); break; } } }