static void CG_AddSpawner( localEntity_t *le ) { refEntity_t *re; vec3_t dir; trace_t trace; re = &le->refEntity; if (le->leFlags & LEF_MOVE) { // kef -- do these two lines _before_ copying origin into oldorigin VectorSubtract(re->oldorigin, re->origin, dir); VectorNormalize(dir); VectorCopy(re->origin, re->oldorigin); BG_EvaluateTrajectory( &le->pos, cg.time, re->origin ); if (le->leFlags & LEF_USE_COLLISION) { // trace a line from previous position to new position CG_Trace( &trace, re->oldorigin, NULL, NULL, re->origin, -1, CONTENTS_SOLID ); if ( trace.fraction != 1.0 ) { // Hit something. // 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 ( trap_CM_PointContents( trace.endpos, 0 ) & CONTENTS_NODROP ) { CG_FreeLocalEntity( le ); return; } // reflect the velocity on the trace plane CG_ReflectVelocity( le, &trace ); } VectorSubtract(re->oldorigin, re->origin, dir); VectorNormalize(dir); } } // kef -- here's where I, in my infinite wisdom, have decided to emulate the singleplayer //particle think function if (cg.time < le->data.spawner.nextthink) { return; } le->data.spawner.nextthink = cg.time + (le->data.spawner.delay + (le->data.spawner.delay*flrandom(-le->data.spawner.variance,le->data.spawner.variance))); if (le->data.spawner.thinkFn) { le->data.spawner.thinkFn(le); } if (le->data.spawner.dontDie) { le->endTime = le->endTime + 10000; } }
/* ================ CG_AddBloodElements ================ */ void CG_AddBloodElements( 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 ); // 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, 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_AddFragment ================ */ void CG_AddFragment( localEntity_t *le ) { vec3_t newOrigin; trace_t trace; if (le->forceAlpha) { le->refEntity.renderfx |= RF_FORCE_ENT_ALPHA; le->refEntity.shaderRGBA[3] = le->forceAlpha; } if ( le->pos.trType == TR_STATIONARY ) { // sink into the ground if near the removal time int t; float t_e; t = le->endTime - cg.time; if ( t < (SINK_TIME*2) ) { le->refEntity.renderfx |= RF_FORCE_ENT_ALPHA; t_e = (float)((float)(le->endTime - cg.time)/(SINK_TIME*2)); t_e = (int)((t_e)*255); if (t_e > 255) { t_e = 255; } if (t_e < 1) { t_e = 1; } if (le->refEntity.shaderRGBA[3] && t_e > le->refEntity.shaderRGBA[3]) { t_e = le->refEntity.shaderRGBA[3]; } le->refEntity.shaderRGBA[3] = t_e; trap->R_AddRefEntityToScene( &le->refEntity ); } else { trap->R_AddRefEntityToScene( &le->refEntity ); } return; } // calculate new position BG_EvaluateTrajectory( &le->pos, cg.time, newOrigin ); // 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 ); AnglesToAxis( angles, le->refEntity.axis ); ScaleModelAxis(&le->refEntity); } 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 ( trap->CM_PointContents( trace.endpos, 0 ) & CONTENTS_NODROP ) { CG_FreeLocalEntity( le ); return; } if (!trace.startsolid) { // leave a mark CG_FragmentBounceMark( le, &trace ); // do a bouncy sound CG_FragmentBounceSound( le, &trace ); if (le->bounceSound) { //specified bounce sound (debris) trap->S_StartSound(le->pos.trBase, ENTITYNUM_WORLD, CHAN_AUTO, le->bounceSound); } // reflect the velocity on the trace plane CG_ReflectVelocity( le, &trace ); trap->R_AddRefEntityToScene( &le->refEntity ); } }
/* ================ CG_AddFragment ================ */ void CG_AddFragment( localEntity_t *le ) { vec3_t newOrigin; trace_t trace; // used to sink into the ground, but it looks better to maybe just fade them out int t; t = le->endTime - cg.time; if ( t < FRAG_FADE_TIME ) { le->refEntity.renderfx |= RF_ALPHA_FADE; le->refEntity.shaderRGBA[0] = le->refEntity.shaderRGBA[1] = le->refEntity.shaderRGBA[2] = 255; le->refEntity.shaderRGBA[3] = ((float)t / FRAG_FADE_TIME) * 255.0f; } if ( le->pos.trType == TR_STATIONARY ) { if ( !(cgi_CM_PointContents( le->refEntity.origin, 0 ) & CONTENTS_SOLID )) { // thing is no longer in solid, so let gravity take it back VectorCopy( le->refEntity.origin, le->pos.trBase ); VectorClear( le->pos.trDelta ); le->pos.trTime = cg.time; le->pos.trType = TR_GRAVITY; } cgi_R_AddRefEntityToScene( &le->refEntity ); return; } // calculate new position EvaluateTrajectory( &le->pos, cg.time, newOrigin ); le->refEntity.renderfx |= RF_LIGHTING_ORIGIN; VectorCopy( newOrigin, le->refEntity.lightingOrigin ); // trace a line from previous position to new position CG_Trace( &trace, le->refEntity.origin, NULL, NULL, newOrigin, le->ownerGentNum, CONTENTS_SOLID ); if ( trace.fraction == 1.0 ) { // still in free fall VectorCopy( newOrigin, le->refEntity.origin ); if ( le->leFlags & LEF_TUMBLE ) { vec3_t angles; EvaluateTrajectory( &le->angles, cg.time, angles ); AnglesToAxis( angles, le->refEntity.axis ); for(int k = 0; k < 3; k++) { VectorScale(le->refEntity.axis[k], le->radius, le->refEntity.axis[k]); } } cgi_R_AddRefEntityToScene( &le->refEntity ); 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 ( cgi_CM_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 ); //FIXME: if LEF_TUMBLE, change avelocity too? cgi_R_AddRefEntityToScene( &le->refEntity ); }
/* ================ CG_AddFragment ================ */ void CG_AddFragment( 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 ); CG_AddRefEntityWithMinLight( &le->refEntity ); le->refEntity.origin[2] = oldZ; } else { CG_AddRefEntityWithMinLight( &le->refEntity ); } return; } // calculate new position BG_EvaluateTrajectory( &le->pos, cg.time, newOrigin ); // 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 ); AnglesToAxis( angles, le->refEntity.axis ); } CG_AddRefEntityWithMinLight( &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; } // 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 ); CG_AddRefEntityWithMinLight( &le->refEntity ); }
/* ================ 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( -1, le->refEntity.origin, vec3_origin, cgs.media.flameCrackSound, (int)(20.0*flameAlpha) ); } //----(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); } } //----(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 ); if (hasFlame) { // calc the flame dir VectorSubtract( le->refEntity.origin, newOrigin, flameDir ); if (VectorLength( flameDir ) == 0) { flameDir[2] = 1; // play a burning sound when not moving trap_S_AddLoopingSound( 0, newOrigin, vec3_origin, cgs.media.flameSound, (int)(0.3*255.0*flameAlpha) ); } else { VectorNormalize( flameDir ); // play a flame blow sound when moving trap_S_AddLoopingSound( 0, newOrigin, vec3_origin, cgs.media.flameBlowSound, (int)(0.3*255.0*flameAlpha) ); } } // 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 ); 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] ); } } // 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 ( trap_CM_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 ); // break on contact? if (le->breakCount) { clientInfo_t *ci; int clientNum; localEntity_t *nle; vec3_t dir; clientNum = le->ownerNum; if ( clientNum < 0 || clientNum >= MAX_CLIENTS ) { CG_Error( "Bad clientNum on player entity"); } ci = &cgs.clientinfo[ clientNum ]; // 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 = ci->gibModels[rand()%2]; else nle->refEntity.hModel = ci->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 ); return; } if (le->pos.trType == TR_STATIONARY && le->leMarkType == LEMT_BLOOD) { // RF, disabled for performance reasons in boss1 //if (le->leBounceSoundType) // CG_BloodPool (le, cgs.media.bloodPool, &trace); // 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 ); }
/* ================ 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 ); // 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, 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, 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, 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 ( trap_CM_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 (VectorLength(le->pos.trDelta) < 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 ); // 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 ); 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 ( trap_CM_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); }
/* =================== CG_AddParticle For trek, special explosion stuff sometimes wants these =================== */ static void CG_AddParticle( localEntity_t *le ) { refEntity_t *re; float frac, alpha; vec3_t delta, dir; float len; trace_t trace; re = &le->refEntity; //safety check - since this renders over all, make sure we can't see this thru a wall if ( re->renderfx & RF_DEPTHHACK ) { CG_Trace( &trace, re->origin, NULL, NULL, cg.refdef.vieworg, cg.predictedPlayerState.clientNum, MASK_SOLID ); if ( trace.fraction != 1.0 ) return; } frac = ( cg.time - le->startTime ) / ( float ) ( le->endTime - le->startTime ); if ( le->leFlags & LEF_SINE_SCALE ) { //frac = 1.0-(0.5f * sin( 4.0f * frac + 0.75f ) + 0.5f); //TiM: Sine calc //+ 1.5f frac = 1.0-(0.65 * sin( 3.0 * frac +0.75 ) + 0.35); } if ( frac > 1 ) frac = 1.0; // can happen during connection problems else if (frac < 0) frac = 0.0; //CG_Printf( "%f\n", frac ); // Use the liferate to set the scale over time. if ( !(le->leFlags & LEF_REVERSE_SCALE) ) re->data.sprite.radius = le->data.particle.radius + (le->data.particle.dradius*frac); else re->data.sprite.radius = le->data.particle.radius - (le->data.particle.dradius*frac); if (re->data.sprite.radius <= 0) { CG_FreeLocalEntity( le ); return; } if (le->leFlags & LEF_MOVE) { // kef -- do these two lines _before_ copying origin into oldorigin VectorSubtract(re->oldorigin, re->origin, dir); VectorNormalize(dir); VectorCopy(re->origin, re->oldorigin); BG_EvaluateTrajectory( &le->pos, cg.time, re->origin ); if (le->leFlags & LEF_USE_COLLISION) { // trace a line from previous position to new position CG_Trace( &trace, re->oldorigin, NULL, NULL, re->origin, -1, CONTENTS_SOLID ); if ( trace.fraction != 1.0 ) { // Hit something. // 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 ( trap_CM_PointContents( trace.endpos, 0 ) & CONTENTS_NODROP ) { CG_FreeLocalEntity( le ); return; } // reflect the velocity on the trace plane CG_ReflectVelocity( le, &trace ); } VectorSubtract(re->oldorigin, re->origin, dir); VectorNormalize(dir); } } // if the view would be "inside" the sprite, kill the sprite // so it doesn't add too much overdraw VectorSubtract( re->origin, cg.refdef.vieworg, delta ); len = VectorLength( delta ); if ( len < le->data.particle.radius ) { CG_FreeLocalEntity( le ); return; } // kef -- here's where I, in my infinite wisdom, have decided to emulate the singleplayer //particle think function VectorNegate(dir, le->data.particle.dir); if (le->data.particle.thinkFn) { le->data.particle.thinkFn(le); } // Calculate the current alpha. alpha = le->alpha + (le->dalpha * frac); re->shaderRGBA[0] = 0xff * alpha; re->shaderRGBA[1] = 0xff * alpha; re->shaderRGBA[2] = 0xff * alpha; re->shaderRGBA[3] = 0xff; re->reType = RT_SPRITE; trap_R_AddRefEntityToScene( re ); }
/* =================== CG_AddViewSprite For trek, view sprites like smoke and the like. =================== */ void CG_AddViewSprite( localEntity_t *le ) { refEntity_t *re; float frac, alpha; vec3_t delta; float len; trace_t trace; vec3_t curRGB; re = &le->refEntity; frac = ( cg.time - le->startTime ) / ( float ) ( le->endTime - le->startTime ); if ( frac > 1 ) frac = 1.0; // can happen during connection problems else if (frac < 0) frac = 0.0; // Use the liferate to set the scale over time. re->data.sprite.radius = le->data.sprite.radius + (le->data.sprite.dradius*frac); if (re->data.sprite.radius <= 0) { CG_FreeLocalEntity( le ); return; } if (le->leFlags & LEF_MOVE) { VectorCopy(re->origin, re->oldorigin); BG_EvaluateTrajectory( &le->pos, cg.time, re->origin ); if (le->leFlags & LEF_USE_COLLISION) { // trace a line from previous position to new position CG_Trace( &trace, re->oldorigin, NULL, NULL, re->origin, -1, CONTENTS_SOLID ); if ( trace.fraction != 1.0 ) { // Hit something. // 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 ( trap_CM_PointContents( trace.endpos, 0 ) & CONTENTS_NODROP ) { CG_FreeLocalEntity( le ); return; } // reflect the velocity on the trace plane CG_ReflectVelocity( le, &trace ); } } } // if the view would be "inside" the sprite, kill the sprite // so it doesn't add too much overdraw VectorSubtract( re->origin, cg.refdef.vieworg, delta ); len = VectorLength( delta ); if ( len < le->data.sprite.radius ) { CG_FreeLocalEntity( le ); return; } // Calculate the current alpha. alpha = le->alpha + (le->dalpha * frac); VectorMA(le->data.sprite.startRGB, frac, le->data.sprite.dRGB, curRGB); re->shaderRGBA[0] = 0xff * alpha * curRGB[0]; re->shaderRGBA[1] = 0xff * alpha * curRGB[1]; re->shaderRGBA[2] = 0xff * alpha * curRGB[2]; re->shaderRGBA[3] = 0xff; re->reType = RT_SPRITE; trap_R_AddRefEntityToScene( re ); }
/* =================== CG_AddTrail For trek, for sparks and the like. =================== */ void CG_AddTrail( localEntity_t *le ) { refEntity_t *re; float frac, length, alpha; vec3_t dir; trace_t trace; vec3_t curRGB; re = &le->refEntity; frac = (cg.time - le->startTime) / ( float ) ( le->endTime - le->startTime ); if ( frac > 1 ) frac = 1.0; // can happen during connection problems else if (frac < 0) frac = 0.0; // Use the liferate to set the scale over time. re->data.line.width = le->data.trail.width + (le->data.trail.dwidth * frac); if (re->data.line.width <= 0) { CG_FreeLocalEntity( le ); return; } if (!(le->leFlags & LEF_MOVE)) { return; } // kef -- do these two lines _before_ copying origin into oldorigin VectorSubtract(re->oldorigin, re->origin, dir); VectorNormalize(dir); VectorCopy(re->origin, re->oldorigin); BG_EvaluateTrajectory( &le->pos, cg.time, re->origin ); if (le->leFlags & LEF_USE_COLLISION) { // trace a line from previous position to new position CG_Trace( &trace, re->oldorigin, NULL, NULL, re->origin, -1, CONTENTS_SOLID ); if ( trace.fraction != 1.0 ) { // Hit something. // 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 ( trap_CM_PointContents( trace.endpos, 0 ) & CONTENTS_NODROP ) { CG_FreeLocalEntity( le ); return; } // reflect the velocity on the trace plane CG_ReflectVelocity( le, &trace ); } VectorSubtract(re->oldorigin, re->origin, dir); VectorNormalize(dir); } // Set the length based on the velocity of the bit. length = le->data.trail.length + (le->data.trail.dlength * frac); if (length <= 0) { CG_FreeLocalEntity( le ); return; } VectorMA(re->origin, length, dir, re->oldorigin); // We will assume here that we want additive transparency effects. alpha = le->alpha + (le->dalpha * frac); VectorMA(le->data.trail.startRGB, frac, le->data.trail.dRGB, curRGB); re->shaderRGBA[0] = 0xff * alpha * curRGB[0]; re->shaderRGBA[1] = 0xff * alpha * curRGB[1]; re->shaderRGBA[2] = 0xff * alpha * curRGB[2]; re->shaderRGBA[3] = 0xff; // Yes, we could apply c to this too, but fading the color is better for lines. re->reType = RT_LINE; trap_R_AddRefEntityToScene( re ); }
/* ================ CG_AddFragment ================ */ void CG_AddFragment( localEntity_t *le ) { vec3_t newOrigin; trace_t trace; int k; 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 ); } return; } // calculate new position BG_EvaluateTrajectory( &le->pos, cg.time, newOrigin ); // trace a line from previous position to new position CG_Trace( &trace, le->refEntity.origin, NULL, NULL, newOrigin, 0 /*le->ownerGentNum*/, 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 ); AnglesToAxis( angles, le->refEntity.axis ); for(k = 0; k < 3; k++) { VectorScale(le->refEntity.axis[k], le->data.fragment.radius, le->refEntity.axis[k]); } } trap_R_AddRefEntityToScene( &le->refEntity ); 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 ( trap_CM_PointContents( trace.endpos, 0 ) & CONTENTS_NODROP ) { CG_FreeLocalEntity( le ); return; } // reflect the velocity on the trace plane CG_ReflectVelocity( le, &trace ); //FIXME: if LEF_TUMBLE, change avelocity too? trap_R_AddRefEntityToScene( &le->refEntity ); }
/* ======================================================================================================================================= CG_AddFragment ======================================================================================================================================= */ void CG_AddFragment(localEntity_t *le) { vec3_t newOrigin, angles; trace_t trace; if (le->pos.trType == TR_STATIONARY) { // sink into the ground if near the removal time int t; float oldZ; CG_AdjustPositionForMover(le->refEntity.origin, le->groundEntityNum, le->pos.trTime, cg.time, le->refEntity.origin, le->angles.trBase, le->angles.trBase); AnglesToAxis(le->angles.trBase, le->refEntity.axis); le->pos.trTime = cg.time; 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); } return; } // never free fragments while they're flying if (le->endTime < cg.time + 2000) { le->endTime = cg.time + 2000; } // calculate new position BG_EvaluateTrajectory(&le->pos, cg.time, newOrigin); // 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); VectorClear(angles); switch (le->leFlags) { case LEF_TUMBLE: BG_EvaluateTrajectory(&le->angles, cg.time, angles); break; case LEF_GIBS: angles[1] = ((cg.time & 2047) * 360 / 2048.0 + 120); angles[0] = ((cg.time & 2047) * 360 / 2048.0); angles[2] = ((cg.time & 2047) * 360 / 2048.0 + 240); break; default: break; } AnglesToAxis(angles, le->refEntity.axis); trap_R_AddRefEntityToScene(&le->refEntity); // add a blood trail if (le->leBounceSoundType == LEBS_BLOOD) { CG_BloodTrail(le); } return; } // fragment inside mover, find the direction/origin of impact if (trace.allsolid && cg_entities[trace.entityNum].currentState.eType == ET_MOVER) { vec3_t origin, angles, dir; float dist; int oldTime; trace_t tr; // get last location if (cg.time == le->pos.trTime) { // fragment was added this frame. no good way to fix this. CG_FreeLocalEntity(le); return; } else { oldTime = le->pos.trTime; } BG_EvaluateTrajectory(&le->pos, oldTime, origin); VectorClear(angles); // add the distance mover has moved since then CG_AdjustPositionForMover(origin, trace.entityNum, oldTime, cg.time, origin, angles, angles); // nudge the origin farther to avoid being co-planar VectorSubtract(origin, newOrigin, dir); dist = VectorNormalize(dir); VectorMA(origin, dist, dir, origin); CG_Trace(&tr, origin, NULL, NULL, newOrigin, -1, CONTENTS_SOLID); // found impact. restore allsolid because trace fraction won't work correct in CG_ReflectVelocity if (!tr.allsolid) { trace = tr; trace.allsolid = qtrue; } } // 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_AddFragment ================ */ void CG_AddFragment(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); } return; } // calculate new position BG_EvaluateTrajectory(&le->pos, cg.time, newOrigin); // 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) { #if 0 vec3_t angles; BG_EvaluateTrajectory(&le->angles, cg.time, angles); AnglesToAxis(angles, le->refEntity.axis); #else // Tr3B - new quaternion code quat_t qrot; // angular rotation for this frame float angle = le->angVel * (cg.time - le->angles.trTime) * 0.001 / 2; // create the rotation quaternion qrot[3] = cos(angle); // real part VectorScale(le->rotAxis, sin(angle), qrot); // imaginary part QuatNormalize(qrot); // create the new orientation QuatMultiply0(le->quatOrient, qrot); // apply the combined previous rotations around other axes QuatMultiply1(le->quatOrient, le->quatRot, qrot); // convert the orientation into the form the renderer wants QuatToAxis(qrot, le->refEntity.axis); le->angles.trTime = cg.time; #endif } 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(trap_CM_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_AddMissile ================ */ static void CG_AddMissile( localEntity_t *le ) { refEntity_t *re; const weaponInfo_t *weapon; trace_t trace; centity_t *other; qboolean inWater; // just existing for server entity deletion if ( le->leFlags & LEF_FINISHED ) { return; } // get weapon info if ( le->ti.weapon > WP_NUM_WEAPONS ) { le->ti.weapon = 0; } weapon = &cg_weapons[le->ti.weapon]; re = &le->refEntity; // calculate position BG_EvaluateTrajectory( &le->pos, cg.time, re->origin ); // special case for flames if ( re->reType == RT_SPRITE ) { int deltaTime; // check for water if ( trap_CM_PointContents( re->origin, 0 ) & CONTENTS_WATER ) { // do a trace to get water surface normals CG_Trace( &trace, re->oldorigin, NULL, NULL, re->origin, le->owner, MASK_WATER ); CG_FreeLocalEntity( le ); CG_MakeExplosion( trace.endpos, trace.plane.normal, cgs.media.ringFlashModel, cgs.media.vaporShader, 500, qfalse, qtrue ); return; } // change radius over time deltaTime = cg.time - le->startTime; re->radius = deltaTime * deltaTime * ( random()*0.4f + 0.8f ) / 2000.0f + 9; // do a trace sometimes if ( le->ti.trailTime++ > 5 ) { le->ti.trailTime = 0; CG_Trace( &trace, re->oldorigin, NULL, NULL, re->origin, le->owner, MASK_SHOT ); VectorCopy( re->origin, re->oldorigin ); // hit something if ( trace.fraction < 1.0 ) { CG_MissileHitWall( le->ti.weapon, 0, trace.endpos, trace.plane.normal, IMPACTSOUND_DEFAULT ); CG_FreeLocalEntity( le ); return; } } // add to refresh list trap_R_AddRefEntityToScene( re ); return; } // add trails if ( weapon->missileTrailFunc ) weapon->missileTrailFunc( &le->ti, cg.time ); // add dynamic light if ( weapon->missileDlight ) { trap_R_AddLightToScene( re->origin, weapon->missileDlight, weapon->missileDlightColor[0], weapon->missileDlightColor[1], weapon->missileDlightColor[2] ); } // flicker between two skins re->skinNum = cg.clientFrame & 1; // convert direction of travel into axis if ( VectorNormalize2( le->pos.trDelta, re->axis[0] ) == 0 ) { re->axis[0][2] = 1; } // spin as it moves if ( le->pos.trType != TR_STATIONARY ) { if ( le->pos.trType == TR_GRAVITY ) { RotateAroundDirection( re->axis, cg.time / 4 ); } else if ( le->pos.trType == TR_WATER_GRAVITY ) { RotateAroundDirection( re->axis, cg.time / 8 ); } else { RotateAroundDirection( re->axis, cg.time ); } } else { RotateAroundDirection( re->axis, 0 ); } // trace a line from previous position to new position CG_Trace( &trace, re->oldorigin, NULL, NULL, re->origin, le->owner, MASK_SHOT ); VectorCopy( re->origin, re->oldorigin ); // draw BIG grenades if ( weLi[le->ti.weapon].category == CT_EXPLOSIVE ) { AxisScale( re->axis, GRENADE_SCALE, re->axis ); } if ( trace.fraction != 1.0 ) { // hit the sky or something like that if ( trace.surfaceFlags & SURF_NOIMPACT ) { le->leFlags |= LEF_FINISHED; le->endTime = cg.time + 500; return; } // impact other = &cg_entities[trace.entityNum]; if ( le->bounceFactor > 0 && ( le->bounceFactor == BOUNCE_FACTOR_HALF || other->currentState.eType != ET_PLAYER ) ) { // reflect the velocity on the trace plane CG_ReflectVelocity( le, &trace ); if ( cg.predictedImpacts < MAX_PREDICTED_IMPACTS ) { cg.predictedImpacts++; cg.predictedImpactsDecTime = cg.time; } // do bounce sound if ( rand() & 1 ) { trap_S_StartSound( le->pos.trBase, 0, CHAN_AUTO, cgs.media.hgrenb1aSound ); } else { trap_S_StartSound( le->pos.trBase, 0, CHAN_AUTO, cgs.media.hgrenb2aSound ); } } else { // explode missile if ( cg.predictedImpacts < MAX_PREDICTED_IMPACTS ) { cg.predictedImpacts++; cg.predictedImpactsDecTime = cg.time; } if ( other->currentState.eType == ET_PLAYER ) { CG_MissileHitPlayer( le->ti.weapon, 0, trace.endpos, trace.plane.normal, trace.entityNum ); } else if ( !(trace.surfaceFlags & SURF_NOIMPACT) ) { CG_MissileHitWall( le->ti.weapon, 0, trace.endpos, trace.plane.normal, (trace.surfaceFlags & SURF_METALSTEPS) ? IMPACTSOUND_METAL : IMPACTSOUND_DEFAULT ); } // store the entity for deleting the server entity le->leFlags |= LEF_FINISHED; le->endTime = cg.time + 500; return; } } // check for medium change if ( trap_CM_PointContents( re->origin, 0 ) & CONTENTS_WATER ) inWater = qtrue; else inWater = qfalse; if ( ( !inWater && le->pos.trType == TR_WATER_GRAVITY ) || ( inWater && le->pos.trType == TR_GRAVITY ) ) { // setup new tr vec3_t newDelta; BG_EvaluateTrajectoryDelta( &le->pos, cg.time, newDelta ); VectorCopy( re->origin, le->pos.trBase ); VectorCopy( newDelta, le->pos.trDelta ); le->pos.trTime = cg.time; if ( inWater ) le->pos.trType = TR_WATER_GRAVITY; else le->pos.trType = TR_GRAVITY; } // add to refresh list trap_R_AddRefEntityToScene( re ); }
/* ================ CG_AddFragment ================ */ void CG_AddFragment( 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 ); } return; } // calculate new position BG_EvaluateTrajectory( &le->pos, cg.time, newOrigin ); // 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 ) { qboolean inWater; vec3_t newDelta; // still in free fall VectorCopy( newOrigin, le->refEntity.origin ); // check for water inWater = CG_PointContents( newOrigin, -1 ) & CONTENTS_WATER; if ( inWater && le->pos.trType == TR_GRAVITY ) { BG_EvaluateTrajectoryDelta( &le->pos, cg.time, newDelta ); le->pos.trType = TR_WATER_GRAVITY; VectorCopy( newOrigin, le->pos.trBase ); VectorCopy( newDelta, le->pos.trDelta ); le->pos.trTime = cg.time; } if ( !inWater && le->pos.trType == TR_WATER_GRAVITY ) { BG_EvaluateTrajectoryDelta( &le->pos, cg.time, newDelta ); le->pos.trType = TR_GRAVITY; VectorCopy( newOrigin, le->pos.trBase ); VectorCopy( newDelta, le->pos.trDelta ); le->pos.trTime = cg.time; } if ( le->leFlags & LEF_TUMBLE ) { vec3_t angles; BG_EvaluateTrajectory( &le->angles, cg.time, angles ); AnglesToAxis( angles, le->refEntity.axis ); } trap_R_AddRefEntityToScene( &le->refEntity ); // add a fire trail // CG_FireTrail( 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 ( trap_CM_PointContents( trace.endpos, 0 ) & CONTENTS_NODROP ) { CG_FreeLocalEntity( le ); return; } // stop rotation le->angles.trType = TR_STATIONARY; // leave a mark CG_FragmentBounceMark( le, &trace ); // reflect the velocity on the trace plane CG_ReflectVelocity( le, &trace ); trap_R_AddRefEntityToScene( &le->refEntity ); }