
// 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;

#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
	step = (1000*3)/VectorLength(le->pos.trDelta);
#endif

	if (cent && cent->currentState.aiChar == AICHAR_ZOMBIE)
		step = 30;

	t = step * ( (cg.time - cg.frametime + step ) / step );
	t2 = step * ( cg.time / step );

	for ( ; t <= t2; t += step ) {
		BG_EvaluateTrajectory( &le->pos, t, newOrigin );
		
#ifdef BLOOD_PARTICLE_TRAIL
		CG_Particle_Bleed (cgs.media.smokePuffShader, newOrigin, vec3_origin, 0, 500+rand()%200);
#else	
		
		
		if (cent && cent->currentState.aiChar == AICHAR_ZOMBIE)
		{
			CG_Particle_Bleed (cgs.media.smokePuffShader, newOrigin, vec3_origin, 1, 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,
									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;

	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 );

			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 ) {
	if ( le->leBounceSoundType == LEBS_BLOOD ) {
		// half the gibs will make splat sounds
		if ( rand() & 1 ) {
			int r = rand()&3;
			sfxHandle_t	s;

			if ( r < 2 ) {
				s = cgs.media.gibBounce1Sound;
			} else if ( r == 2 ) {
				s = cgs.media.gibBounce2Sound;
			} else {
				s = cgs.media.gibBounce3Sound;
			}
			trap_S_StartSound( trace->endpos, ENTITYNUM_WORLD, CHAN_AUTO, s );
		}
	} else if ( le->leBounceSoundType == LEBS_BRASS ) {

//----(SA) added
	} else if ( le->leBounceSoundType == LEBS_ROCK ) {
		// half the hits will make thunk sounds	(this is just to start since we don't even have the sound yet... (SA))
		if ( rand() & 1 ) {
			int r = rand()&3;
			sfxHandle_t	s;

			if ( r < 2 ) {
				s = cgs.media.debBounce1Sound;
			} else if ( r == 2 ) {
				s = cgs.media.debBounce2Sound;
			} else {
				s = cgs.media.debBounce3Sound;
			}
			trap_S_StartSound( trace->endpos, ENTITYNUM_WORLD, CHAN_AUTO, s );
		}
//----(SA) end

	} else if ( le->leBounceSoundType == LEBS_BONE ) {

		trap_S_StartSound( trace->endpos, ENTITYNUM_WORLD, CHAN_AUTO, cgs.media.boneBounceSound );

	}

	// 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 );
	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;
			}
	}
	else {

	}
}


//----(SA)	added

/*
==============
CG_AddEmitter
==============
*/
void CG_AddEmitter(localEntity_t *le) {
	vec3_t	dir;	

	if(le->breakCount > cg.time)	// using 'breakCount' for 'wait'
		return;

	// TODO: look up particle script and use proper effect rather than this check
	//if(water){}
	//else if(oil) {}
	//else if(steam) {}
	//else if(wine) {}
	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


/*
================
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 );
}

// Ridah
/*
================
CG_AddMovingTracer
================
*/
void CG_AddMovingTracer( localEntity_t *le ) {
	vec3_t start, end, dir;

	BG_EvaluateTrajectory( &le->pos, cg.time, start );
	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 );

//		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->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 ( trap_CM_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 );

		lifeFrac = (float)(time - le->startTime) / (float)(le->endTime - le->startTime);

		//if (lifeFrac > 0.2) {
			// add a trail
			le->headJuncIndex = CG_AddTrailJunc( le->headJuncIndex, 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;

	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_AddClientCritter
================
*/
void CG_AddClientCritter( localEntity_t *le ) {
	vec3_t	newOrigin;
	trace_t	trace;
	int		time, step = 25, i;
	vec3_t	v, ang, v2, oDelta;
	localEntity_t backup;
	float	oldSpeed, enemyDist, of;
	vec3_t	enemyPos;
	float alpha;

	if (cg_entities[le->ownerNum].currentState.otherEntityNum2 == cg.snap->ps.clientNum) {
		VectorCopy( cg.snap->ps.origin, enemyPos );
		enemyPos[2] += cg.snap->ps.viewheight;
	} else {
		VectorCopy( cg_entities[le->ownerNum].currentState.origin2, enemyPos );
	}

	VectorCopy( le->pos.trDelta, oDelta );

	// vary the enemyPos to create a psuedo-randomness
	of = (float)cg.time + le->startTime;
	enemyPos[0] += 12 * (sin(of/100) * cos(of/78));
	enemyPos[1] += 12 * (sin(of/70) * cos(of/82));
	enemyPos[2] += 12 * (sin(of/67) * cos(of/98));

	time = le->lastTrailTime+step;

	while (time <= cg.time) {
		if (time > le->refEntity.fadeStartTime) {
			alpha = (float)(time - le->refEntity.fadeStartTime)/(float)(le->refEntity.fadeEndTime - le->refEntity.fadeStartTime);
			if (alpha < 0) alpha = 0;
			else if (alpha > 1) alpha = 1;
		} else {
			alpha = 1.0;
		}

		// calculate new position
		BG_EvaluateTrajectory( &le->pos, time, newOrigin );

		VectorSubtract( enemyPos, le->refEntity.origin, v );
		enemyDist = VectorNormalize( v );

		// trace a line from previous position to new position
		CG_Trace( &trace, le->refEntity.origin, NULL, NULL, newOrigin, le->ownerNum, MASK_SHOT );

		// if stuck, kill it
		if (trace.startsolid || (trace.fraction < 1.0)) {
			// kill it
			CG_FreeLocalEntity( le );
			return;
		}

		// moved some distance
		VectorCopy( trace.endpos, le->refEntity.origin );

		if (le->leType == LE_ZOMBIE_SPIRIT) {
			le->headJuncIndex = CG_AddTrailJunc( le->headJuncIndex,
												cgs.media.zombieSpiritTrailShader,
												time,
												STYPE_STRETCH,
												le->refEntity.origin,
												(int)le->effectWidth,	// trail life
												0.3 * alpha,
												0.0,
												le->radius,
												0,
												0,//TJFL_FIXDISTORT,
												colorWhite,
												colorWhite,
												1.0, 1 );
		}

		// tracking factor
		if (le->leType == LE_ZOMBIE_BAT)
			le->bounceFactor = 3.0*(float)step/1000.0;
		else
			le->bounceFactor = 5.0*(float)step/1000.0;
		oldSpeed = VectorLength( le->pos.trDelta );

		// track the enemy
		backup = *le;
		VectorSubtract( enemyPos, le->refEntity.origin, v );
		enemyDist = VectorNormalize( v );

		if (alpha > 0.5 && (le->lastSpiritDmgTime < time - 100) && enemyDist < 24) {
			// inflict the damage!
			CG_ClientDamage( cg_entities[le->ownerNum].currentState.otherEntityNum2, le->ownerNum, CLDMG_SPIRIT );
			le->lastSpiritDmgTime = time;
		}

		VectorMA( le->pos.trDelta, le->bounceFactor*oldSpeed, v, le->pos.trDelta );
		//VectorCopy( v, le->pos.trDelta );
		if (VectorLength(le->pos.trDelta) < 1) {
			CG_FreeLocalEntity( le );
			return;
		}

		le->bounceFactor = 5.0*(float)step/1000.0;	// avoidance factor

		// the intersection is a fraction of the frametime
		le->pos.trTime = time;
		VectorCopy( le->refEntity.origin, le->pos.trBase );
		VectorNormalize( le->pos.trDelta );
		VectorScale( le->pos.trDelta, oldSpeed, le->pos.trDelta );

		// now trace ahead of time, if we're going to hit something, then avoid it
		// only avoid dangers if we don't have direct sight to the enemy
		trap_CM_BoxTrace( &trace, le->refEntity.origin, enemyPos, NULL, NULL, 0, MASK_SOLID );
		if (trace.fraction < 1.0) {
			BG_EvaluateTrajectory( &le->pos, time+1000, newOrigin );

			// if we would go passed the enemy, don't bother
			if (VectorDistance( le->refEntity.origin, enemyPos) > VectorDistance( le->refEntity.origin, newOrigin )) {

				trap_CM_BoxTrace( &trace, le->refEntity.origin, newOrigin, NULL, NULL, 0, MASK_SOLID );

				if (trace.fraction < 1.0) {
					// make sure we are not heading away from the enemy too much
					VectorNormalize2( le->pos.trDelta, v2 );
					if (DotProduct( v, v2 ) > 0.7) {
						// avoid world geometry
						backup = *le;
						le->bounceFactor = (1.0 - trace.fraction)*10.0*(float)step/1000.0;	// tracking and avoidance factor
						// reflect the velocity on the trace plane
						VectorMA( le->pos.trDelta, le->bounceFactor*oldSpeed, trace.plane.normal, le->pos.trDelta );
						if (VectorLength(le->pos.trDelta) < 1) {
							CG_FreeLocalEntity( le );
							return;
						}
						// the intersection is a fraction of the frametime
						le->pos.trTime = time;
						VectorCopy( le->refEntity.origin, le->pos.trBase );
						VectorNormalize( le->pos.trDelta );
						VectorScale( le->pos.trDelta, oldSpeed, le->pos.trDelta );
						//
						// double check end velocity
						VectorNormalize2( le->pos.trDelta, v2 );
						if (DotProduct( v, v2 ) <= 0.2) {
							// restore
							*le = backup;
						}
					}
				}
			}
		}

		// set the angles
		VectorNormalize2( le->pos.trDelta, v );
		// HACK!!! skull model is back-to-front, need to fix
		if (le->leType == LE_ZOMBIE_SPIRIT)
			VectorInverse( v );
		vectoangles( v, ang );
		AnglesToAxis( ang, le->refEntity.axis );
		// lean when turning
		if (le->leType == LE_ZOMBIE_BAT) {
			VectorSubtract( le->pos.trDelta, oDelta, v2 );
			ang[ROLL] = -5.0 * DotProduct( le->refEntity.axis[1], v2 );
			if (fabs(ang[ROLL]) > 80) {
				if (ang[ROLL] > 80) ang[ROLL] = 80;
				else ang[ROLL] = -80;
			}
		}
		AnglesToAxis( ang, le->refEntity.axis );

		// HACK: the skull is slightly higher than the origin
		if (le->leType == LE_ZOMBIE_SPIRIT) {
			// set the size scale
			for (i=0; i<3; i++)
				VectorScale( le->refEntity.axis[i], 0.35, le->refEntity.axis[i] );
			VectorMA( le->refEntity.origin, -10, le->refEntity.axis[2], le->refEntity.origin );
		}

		le->lastTrailTime = time;
		time += step;
	}

	// Bats, set the frame
	if (le->leType == LE_ZOMBIE_BAT) {
		#define	BAT_ANIM_FRAMETIME	30
		le->refEntity.frame = (cg.time/BAT_ANIM_FRAMETIME+1)%19;
		le->refEntity.oldframe = (cg.time/BAT_ANIM_FRAMETIME)%19;
		le->refEntity.backlerp = 1.0 - ((float)(cg.time%BAT_ANIM_FRAMETIME)/(float)BAT_ANIM_FRAMETIME);
	}

	// add the sound
	if (le->loopingSound) {
		if (cg.time > le->refEntity.fadeStartTime)
			trap_S_AddLoopingSound( 0, le->refEntity.origin, vec3_origin, le->loopingSound, 255 - (int)(255.0 * (float)(cg.time - le->refEntity.fadeStartTime) / (float)(le->refEntity.fadeEndTime - le->refEntity.fadeStartTime)) );
		else if (le->startTime + 1000 > cg.time)
			trap_S_AddLoopingSound( 0, le->refEntity.origin, vec3_origin, le->loopingSound, (int)(255.0 * (float)(cg.time - le->startTime) / 1000.0) );
		else
			trap_S_AddLoopingSound( 0, le->refEntity.origin, vec3_origin, le->loopingSound, 255);
	}

	trap_R_AddRefEntityToScene( &le->refEntity );
/*
	// HACK: the skull is slightly higher than the origin
	if (le->leType == LE_ZOMBIE_SPIRIT) {
		// set the size scale
		for (i=0; i<3; i++)
			VectorScale( le->refEntity.axis[i], 1.0/0.35, le->refEntity.axis[i] );
		VectorMA( le->refEntity.origin,  10, le->refEntity.axis[2], le->refEntity.origin );
	}
*/
	// Bats, add the flame
	if (le->leType == LE_ZOMBIE_BAT) {
//		float lightSize, alpha;
		//
		le->refEntity.shaderRGBA[3] = 255;
		VectorNormalize2( le->pos.trDelta, v );
		VectorInverse( v );
		v[2] += 1;
		VectorNormalize2( v, le->refEntity.fireRiseDir );

		le->refEntity.customShader = cgs.media.onFireShader2;
		trap_R_AddRefEntityToScene( &le->refEntity );
		le->refEntity.shaderTime = 1434;
		trap_R_AddRefEntityToScene( &le->refEntity );
//		le->refEntity.customShader = cgs.media.onFireShader;
//		le->refEntity.shaderTime = 0;
//		trap_R_AddRefEntityToScene( &le->refEntity );

		le->refEntity.customShader = 0;
		le->refEntity.shaderTime = 0;
/*
		// drop a dlight
		lightSize = 1.0 + 0.2*(sin(1.0*cg.time/50.0) * cos(1.0*cg.time/43.0));
		alpha = 0.2 * (lightSize / 1.2);
		trap_R_AddLightToScene( le->refEntity.origin, 150.0 + 80.0*lightSize, 1.000000*alpha, 0.603922*alpha, 0.207843*alpha, 0 );
		// add some sound
		trap_S_AddLoopingSound( -1, le->refEntity.origin, vec3_origin, cgs.media.flameSound, 100 );
		trap_S_AddLoopingSound( -1, le->refEntity.origin, vec3_origin, cgs.media.flameBlowSound, 100 );
*/
	}
}

/*
================
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);
}
// 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 );

	// 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->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.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.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 ) {
		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 );
	}
}

/*
================
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 ) {
		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 );
		// 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;
		
		}
	}
}

