//This file contains environmental effects for the designers

#include "cg_local.h"
#include "fx_local.h"

// these flags should be synchronized with the spawnflags in g_fx.c for fx_bolt
#define BOLT_SPARKS		(1<<0)
#define BOLT_BORG		(1<<1)


qboolean SparkThink( localEntity_t *le )
{
	vec3_t	dir, direction, start, end;
	vec3_t	velocity;
	float	scale = 0, alpha = 0;
	int		numSparks = 0, i = 0, j = 0;
	sfxHandle_t snd = cgs.media.envSparkSound1;

	switch(irandom(1, 3))
	{
	case 1:
		snd = cgs.media.envSparkSound1;
		break;
	case 2:
		snd = cgs.media.envSparkSound2;
		break;
	case 3:
		snd = cgs.media.envSparkSound3;
		break;
	}
	trap_S_StartSound (le->refEntity.origin, ENTITYNUM_WORLD, CHAN_BODY, snd );

	CG_InitLensFlare( le->refEntity.origin, 
						40, 40,
						colorTable[CT_YELLOW], 1.2, 2.0, 1600, 200,
						colorTable[CT_YELLOW], 1600, 200, 600, 6,  qtrue, 
						0, 0, qfalse, qtrue, 
						qtrue, 0.7, cg.time, 90, 0, 300);


	VectorCopy(le->data.spawner.dir, dir);

	//AngleVectors( dir, dir, NULL, NULL );
	for ( j = 0; j < 3; j ++ )
		direction[j] = dir[j] + (0.25f * crandom());

	VectorNormalize( direction );

	//trap_S_StartSound( origin, ENTITYNUM_WORLD, CHAN_AUTO, cgi_S_RegisterSound( va( "sound/world/ric%d.wav", (rand() & 2)+1) ) );

	numSparks = 8 + (random() * 4.0f);
	
	scale = 0.2f + (random() *0.4);
	VectorMA( le->refEntity.origin, 24.0f + (crandom() * 4.0f), dir, end );

	//One long spark
	FX_AddLine( le->refEntity.origin,
				end,
				1.0f,
				scale,
				0.0f,
				1.0f,
				0.25f,
				125.0f,
				cgs.media.sparkShader );
	
	for ( i = 0; i < numSparks; i++ )
	{	
		scale = 0.2f + (random() *0.4);

		for ( j = 0; j < 3; j ++ )
			direction[j] = dir[j] + (0.25f * crandom());
		
		VectorNormalize(direction);

		VectorMA( le->refEntity.origin, 0.0f + ( random() * 2.0f ), direction, start );
		VectorMA( start, 2.0f + ( random() * 16.0f ), direction, end );

		FX_AddLine( start,
					end,
					1.0f,
					scale,
					0.0f,
					1.0f,
					0.25f,
					125.0f,
					cgs.media.sparkShader );
	}

	if ( rand() & 1 )
	{
		numSparks = 1 + (random() * 2.0f);
		for ( i = 0; i < numSparks; i++ )
		{	
			scale = 0.5f + (random() * 0.5f);

			VectorScale( direction, 250, velocity );

			FX_AddTrail(	start,
							velocity,
							qtrue,
							8.0f,
							-32.0f,
							scale,
							-scale,
							1.0f,
							0.5f,
							0.25f,
							700.0f,
							cgs.media.sparkShader);

		}
	}

	VectorMA( le->refEntity.origin, 1, dir, direction );

	scale = 6.0f + (random() * 8.0f);
	alpha = 0.1 + (random() * 0.4f);

	VectorSet( velocity, 0, 0, 8 );

	FX_AddSprite(	direction, 
					velocity, 
					qfalse, 
					scale,
					scale,
					alpha,
					0.0f,
					random()*45.0f,
					0.0f,
					1000.0f,
					cgs.media.steamShader );

	return qtrue;
}

/*
======================
CG_Spark

Creates a spark effect
======================
*/

void CG_Spark( vec3_t origin, vec3_t normal, int delay, int killTime )
{
	// give it a lifetime of 10 seconds because the refresh thinktime in g_fx.c is 10 seconds
	FX_AddSpawner( origin, normal, NULL, NULL, qfalse, delay, 1.5, killTime, SparkThink, 100 ); //10000

}



qboolean SteamThink( localEntity_t *le )
{
	float	speed = 200;
	vec3_t	direction;
	vec3_t	velocity		= { 0, 0, 128 };
	float	scale, dscale;
	vec3_t	origin;

	//FIXME: Whole lotta randoms...

	VectorCopy( le->data.spawner.dir, direction );
	//AngleVectors( direction, direction, NULL, NULL );
	//VectorNormalize(direction);

	//TiM : Offset by 1.  sometimes it can spawn in walls and the particles act weird then
	VectorMA( le->refEntity.origin, 1, direction, origin );

	direction[0] += (direction[0] * crandom() * le->data.spawner.variance);
	direction[1] += (direction[0] * crandom() * le->data.spawner.variance);
	direction[2] += (direction[0] * crandom() * le->data.spawner.variance);

	VectorScale( direction, speed, velocity );

	scale = 4.0f + (random());
	dscale = scale * 4.0;

	FX_AddSprite(	origin,
					velocity, 
					qfalse, 
					scale, 
					dscale, 
					1.0f, 
					0.0f,
					random() * 360,
					0.25f,
					300,//(len / speed) * 1000, 
					cgs.media.steamShader );

	return qtrue;
}

/*
======================
CG_Steam

Creates a steam effect
======================
*/

void CG_Steam( vec3_t position, vec3_t dir, int killTime )
{
	// give it a lifetime of 10 seconds because the refresh thinktime in g_fx.c is 10 seconds
	FX_AddSpawner( position, dir, NULL, NULL, qfalse, 0, 0.15, killTime, SteamThink, 100 ); //
}

/*
======================
CG_Bolt

Creates a electricity bolt effect
======================
*/
#define DATA_EFFECTS	0
#define DATA_CHAOS		1
#define DATA_RADIUS		2

//-----------------------------
void BoltSparkSpew( vec3_t origin, vec3_t normal )
{
	float	scale = 1.0f + ( random() * 1.0f );
	int		num = 0, i = 0;
	vec3_t	vel;

	trap_R_AddLightToScene( origin, 75 + (rand()&31), 1.0, 0.8, 1.0 );

	// Drop some sparks
	num = (int)(random() * 2) + 2;

	for ( i = 0; i < num; i++ )
	{
		scale = 0.6f + random();
		if ( rand() & 1 )
			FXE_Spray( normal, 70, 80, 0.9f, vel);
		else
			FXE_Spray( normal, 80, 200, 0.5f, vel);

		FX_AddTrail( origin, vel, qfalse, 8.0f + random() * 8, -48.0f,
								scale, -scale, 1.0f, 0.8f, 0.4f, 600.0f, cgs.media.spark2Shader );
	}
}


qboolean BoltFireback( localEntity_t *le)
{
//localEntity_t *FX_AddElectricity( vec3_t origin, vec3_t origin2, float stScale, float scale, float dscale, 
//									float startalpha, float endalpha, float killTime, qhandle_t shader, float deviation );							
	float killTime = (0 == le->data.spawner.delay)?10000:200;

	FX_AddElectricity(le->refEntity.origin, le->data.spawner.dir, 0.2f, 15.0, -15.0, 1.0, 0.5, killTime,
		cgs.media.bolt2Shader, le->data.spawner.variance );
	// is this spawner on a random delay?
	if (le->data.spawner.data1)
	{
		le->data.spawner.delay = flrandom(0,5000);
	}
	return qtrue;
}

//-----------------------------
qboolean BorgBoltFireback( localEntity_t *le)
{
	float killTime = (0 == le->data.spawner.delay)?10000:200;

	FX_AddElectricity(le->refEntity.origin, le->data.spawner.dir, 0.2f, 15.0, -5.0, 1.0, 0.5, killTime,
		cgs.media.borgLightningShaders[1], le->data.spawner.variance );
	// is this spawner on a random delay?
	if (le->data.spawner.data1)
	{
		le->data.spawner.delay = flrandom(0,5000);
	}
	return qtrue;
}

//-----------------------------
qboolean BoltFirebackSparks( localEntity_t *le)
{
	vec3_t	dir;
	float killTime = (0 == le->data.spawner.delay)?10000:200;

	VectorSubtract(le->refEntity.origin, le->data.spawner.dir, dir);
	VectorNormalize(dir);
	FX_AddElectricity(le->refEntity.origin, le->data.spawner.dir, 0.2f, 15.0, -15.0, 1.0, 0.5, killTime,
		cgs.media.bolt2Shader, le->data.spawner.variance );
	BoltSparkSpew(le->data.spawner.dir, dir);
	// is this spawner on a random delay?
	if (le->data.spawner.data1)
	{
		le->data.spawner.delay = flrandom(0,5000);
	}
	return qtrue;
}

//-----------------------------
qboolean BorgBoltFirebackSparks( localEntity_t *le)
{
	vec3_t	dir;
	float killTime = (0 == le->data.spawner.delay)?10000:200;

	VectorSubtract(le->refEntity.origin, le->data.spawner.dir, dir);
	VectorNormalize(dir);
	FX_AddElectricity(le->refEntity.origin, le->data.spawner.dir, 0.2f, 15.0, -15.0, 1.0, 0.5, killTime,
		cgs.media.borgLightningShaders[0], le->data.spawner.variance );
	BoltSparkSpew(le->data.spawner.dir, dir);
	// is this spawner on a random delay?
	if (le->data.spawner.data1)
	{
		le->data.spawner.delay = flrandom(0,5000);
	}
	return qtrue;
}

//-----------------------------
void CG_Bolt( centity_t *cent )
{
	localEntity_t *le = NULL;
	qboolean	bSparks = (qboolean)(cent->currentState.eventParm & BOLT_SPARKS);
	qboolean	bBorg = (qboolean)(cent->currentState.eventParm & BOLT_BORG);
	float		radius = cent->currentState.angles2[0], chaos = cent->currentState.angles2[1];
	float		delay = cent->currentState.time2 * 1000; // the value given by the designer is in seconds
	qboolean	bRandom = qfalse;

	if (delay < 0)
	{
		// random
		delay = flrandom(0.1, 5000.0);
		bRandom = qtrue;
	}
	if (delay > 10000)
	{
		delay = 10000;
	}

	if ( bBorg )
	{
		if (bSparks)
		{
			le = FX_AddSpawner( cent->lerpOrigin, cent->currentState.origin2, NULL, NULL, qfalse, delay,
				chaos, 10000, BorgBoltFirebackSparks, radius );
		}
		else
		{
			le = FX_AddSpawner( cent->lerpOrigin, cent->currentState.origin2, NULL, NULL, qfalse, delay,
				chaos, 10000, BorgBoltFireback, radius );
		}
	}
	else
	{
		if (bSparks)
		{
			le = FX_AddSpawner( cent->lerpOrigin, cent->currentState.origin2, NULL, NULL, qfalse, delay,
				chaos, 10000, BoltFirebackSparks, radius );
		}
		else
		{
			le = FX_AddSpawner( cent->lerpOrigin, cent->currentState.origin2, NULL, NULL, qfalse, delay,
				chaos, 10000, BoltFireback, radius );
		}
	}
	if (bRandom)
	{
		le->data.spawner.data1 = 1;
	}
}

void CG_TransporterPad(vec3_t origin)
{
	FX_TransporterPad(origin);
}

/*
===========================
Drip

Create timed drip effect
===========================
*/

qboolean DripCallback( localEntity_t *le )
{
	localEntity_t *trail = NULL;
	qhandle_t	shader = 0;

	switch (le->data.spawner.data1)
	{
	case 1:
		shader = cgs.media.oilDropShader;
		break;
	case 2:
		shader = cgs.media.greenDropShader;
		break;
	case 0:
	default:
		shader = cgs.media.waterDropShader;
		break;
	}
	trail = FX_AddTrail(le->refEntity.origin, le->pos.trDelta, qfalse, 4, -2, 1, 0, 0.8, 0.4, 0.0,
		300, shader);
	trail->leFlags |= LEF_ONE_FRAME;

	return qtrue;
}

//------------------------------------------------------------------------------
qboolean DripSplash( localEntity_t *le )
{
	float	scale = 1.0f + ( random() * 1.0f );
	int		num = 0, i = 0;
	vec3_t	vel, normal, origin;
	qhandle_t	shader = 0;

	switch (le->data.spawner.data1)
	{
	case 1:
		shader = cgs.media.oilDropShader;
		break;
	case 2:
		shader = cgs.media.greenDropShader;
		break;
	case 0:
	default:
		shader = cgs.media.waterDropShader;
		break;
	}

	VectorCopy(le->data.spawner.dir, normal);
	VectorCopy(le->refEntity.origin, origin);

	// splashing water droplets. which, I'm fairly certain, is an alternative band from Europe.
	num = (int)(random() * 2) + 6;

	for ( i = 0; i < num; i++ )
	{
		scale = 0.6f + random();
		if ( rand() & 1 )
			FXE_Spray( normal, 110, 80, 0.9f, vel);
		else
			FXE_Spray( normal, 150, 150, 0.5f, vel);

		FX_AddTrail( origin, vel, qtrue, 4.0f, 0.0f,
								scale, -scale, 1.0f, 0.8f, 0.4f, 200.0f, shader );
	}

	switch( rand() & 3 )
	{
	case 1:
		trap_S_StartSound( origin, ENTITYNUM_WORLD, CHAN_BODY, cgs.media.waterDropSound1 );
		break;
	case 2:
		trap_S_StartSound( origin, ENTITYNUM_WORLD, CHAN_BODY, cgs.media.waterDropSound2 );
		break;
	default:
		trap_S_StartSound( origin, ENTITYNUM_WORLD, CHAN_BODY, cgs.media.waterDropSound3 );
		break;
	}
	return qtrue;

}

qboolean JackTheDripper( localEntity_t *le )
{
	trace_t		trace;
	vec3_t		vel, down = {0,0,-1}, end, origin, new_origin;
	float		time, dis, diameter = 1.0;
	qhandle_t	shader = 0;
	int			maxDripsPerLifetime = 200; // given a 10 second lifetime
	int			desiredDrips = 1 + (int)(le->data.spawner.variance * (maxDripsPerLifetime-1)); // range of (1...max)
	float		percentLife = 1.0f - (le->endTime - cg.time)*le->lifeRate;
	localEntity_t *splash = NULL;

	switch (le->data.spawner.data1)
	{
	case 1:
		shader = cgs.media.oilDropShader;
		break;
	case 2:
		shader = cgs.media.greenDropShader;
		break;
	case 0:
	default:
		shader = cgs.media.waterDropShader;
		break;
	}

	// do we need to add a drip to maintain our drips-per-second rate?
	while ( (int)(flrandom(percentLife-0.05,percentLife+0.05)*desiredDrips) > le->data.spawner.data2)
	{
		VectorCopy(le->refEntity.origin, origin);

		// the more drips per second, spread them out from our origin point
		fxRandCircumferencePos(origin, down, 10*le->data.spawner.variance, new_origin);

		// Ideally, zero should be used for vel...so just use something sufficiently close
		VectorSet( vel, 0, 0, -0.00001 );

		// Find out where it will hit
		VectorMA( new_origin, 1024, down, end );
		CG_Trace( &trace, new_origin, NULL, NULL, end, 0, MASK_SHOT );
		if ( trace.fraction < 1.0 )
		{
			VectorSubtract( trace.endpos, new_origin, end );
			dis = VectorNormalize( end );

			time = sqrt( 2*dis / DEFAULT_GRAVITY ) * 1000;	// Calculate how long the thing will take to travel that distance

			// Falling drop
			splash = FX_AddParticle( new_origin, vel, qtrue, diameter, 0.0, 0.8, 0.8, 0.0, 0.0, time, shader, DripCallback );
			splash->data.spawner.data1 = le->data.spawner.data1;

			splash = FX_AddSpawner(trace.endpos, trace.plane.normal, vel, NULL, qfalse, time, 0, time + 200, DripSplash, 10);
			splash->data.spawner.data1 = le->data.spawner.data1;
		}
		else
		// Falling a long way so just send one that will fall for 2 secs, but don't spawn a splash
		{
			FX_AddParticle( new_origin, vel, qtrue, diameter, 0.0, 0.8, 0.8, 0.0, 0.0, 2000, shader, 0/*NULL*/ );
		}
		//increase our number-of-drips counter
		le->data.spawner.data2++;
	}
	return qtrue;
}

//------------------------------------------------------------------------------
void CG_Drip(centity_t *cent, int killTime )
{
	vec3_t down = {0,0,-1};
	localEntity_t *le = NULL;

	// clamp variance to [0...1]
	if (cent->currentState.angles2[0] < 0)
	{
		cent->currentState.angles2[0] = 0;
	}
	else if (cent->currentState.angles2[0] > 1)
	{
		cent->currentState.angles2[0] = 1;
	}
	// cent->currentState.angles2[0] is the degree of drippiness
	// cent->currentState.time2 is the type of drip (water, oil, etc.)
	le = FX_AddSpawner( cent->lerpOrigin, down, NULL, NULL, qfalse, 0,
		cent->currentState.angles2[0], killTime, JackTheDripper, cent->currentState.time2 );
	//init our number-of-drips counter
	le->data.spawner.data2 = 0;
}

//------------------------------------------------------------------------------
void CG_Chunks( vec3_t origin, vec3_t dir, float scale, material_type_t type )
{
	int				i, j, k;
	int				numChunks;
	float			baseScale = 1.0f, dist, radius;
	vec3_t			v;
	sfxHandle_t		snd = 0;
	localEntity_t	*le;
	refEntity_t		*re;

	if ( type == MT_NONE )
		return;

	if ( type >= NUM_CHUNK_TYPES )
	{
		CG_Printf( "^6Chunk has invalid material %d!\n", type);
		type = MT_METAL;	//something legal please
	}

	switch( type )
	{
	case MT_GLASS:
	case MT_GLASS_METAL:
		snd = cgs.media.glassChunkSound;
		break;

	case MT_METAL:
	case MT_STONE:
	case MT_WOOD:
	default:
		snd = cgs.media.metalChunkSound;
		break;
	}

	trap_S_StartSound( origin, ENTITYNUM_WORLD, CHAN_BODY, snd );

	numChunks = irandom( 8, 12 );

	// LOD num chunks
	VectorSubtract( cg.snap->ps.origin, origin, v );
	dist = VectorLength( v );

	if ( dist > 512 )
	{
		numChunks *= 512.0 / dist;		// 1/2 at 1024, 1/4 at 2048, etc.
	}

	// attempt to scale the size of the chunks based on the size of the brush
	radius = baseScale + ( ( scale - 128 ) / 128 );

	for ( i = 0; i < numChunks; i++ )
	{
		le = CG_AllocLocalEntity();
		re = &le->refEntity;

		le->leType = LE_FRAGMENT;
		le->endTime = cg.time + 2000;

		VectorCopy( origin, re->origin );

		for ( j = 0; j < 3; j++ )
		{
			re->origin[j] += crandom() * 12;
		}
		VectorCopy( re->origin, le->pos.trBase );

		//Velocity
		VectorSet( v, crandom(), crandom(), crandom() );
		VectorAdd( v, dir, v );
		VectorScale( v, flrandom( 100, 350 ), le->pos.trDelta );

		//Angular Velocity
		VectorSet( le->angles.trBase, crandom() * 360, crandom() * 360, crandom() * 360 );
		VectorSet( le->angles.trDelta, crandom() * 90, crandom() * 90, crandom() * 90 );

		AxisCopy( axisDefault, re->axis );

		le->data.fragment.radius = flrandom( radius * 0.7f, radius * 1.3f );

		re->nonNormalizedAxes = qtrue;

		if ( type == MT_GLASS_METAL )
		{
			if ( rand() & 1 )
			{
				re->hModel = cgs.media.chunkModels[MT_METAL][irandom(0,5)];
			}
			else
			{
				re->hModel = cgs.media.chunkModels[MT_GLASS][irandom(0,5)];
			}
		}
		else
		{
			re->hModel = cgs.media.chunkModels[type][irandom(0,5)];
		}
		
		le->pos.trType = TR_GRAVITY;
		le->pos.trTime = cg.time;
		le->angles.trType = TR_INTERPOLATE;
		le->angles.trTime = cg.time;
		le->bounceFactor = 0.2f + random() * 0.2f;
		le->leFlags |= LEF_TUMBLE;

		re->shaderRGBA[0] = re->shaderRGBA[1] = re->shaderRGBA[2] = re->shaderRGBA[3] = 255;

		// Make sure that we have the desired start size set
		for( k = 0; k < 3; k++)
		{
			VectorScale( le->refEntity.axis[k], le->data.fragment.radius, le->refEntity.axis[k] );
		}
	}
}

//TiM - org is where teh spray originates, and end is where the splash ends
//The fun part is corellating the height and direction to the bezier function... heh
void CG_FountainSpurt( vec3_t org, vec3_t end )
{
	int			/*i,*/ t;
	vec3_t		org1, org2, cpt1, cpt2;
	vec3_t		dir/*, dir2*/;
	//vec3_t		rgb = { 0.4f, 0.7f, 0.8f };
	//float		distance;
	//FXBezier	*fxb;

	// offset table, could have used sin/cos, I suppose
	//TiM - This was for the 4-way fountain. not needed no more
	/*const float m[][2] = { 
						1, 0,
						0, 1,
						-1, 0,
						0, -1 };*/

	// The origin shouldn't be in solid, otherwise the ent won't think.  So, place the spawner above
	//	the solid object, then move the spout spawn points down to where they should be.
	//org[2] -= 56; // magic number stuff
	//TiM... uh O_o I think that was another thing with the 4 way fountain... won't work too well O_o
	//Lessee what happens if we blatently disregard this lol

	// Create four spouts 
	//for ( i = 0; i < 4; i++ ) //TiM No!
	//{
		//TiM - Set a direction we can use to figure out the end from the start.... >.<
		//We'll need the literal direction between X + Y... Z is pretty easy to figure out
		//VectorSubtract( end, org, dir );
		//distance = (dir[0] + dir[1]) * 0.5f; //so get the average

		// Move the spout out from the exact center
		VectorCopy( org, org1 );

		//TiM: No offset for now
		//org1[0] += 35 * m[i][0];
		//org1[1] += 35 * m[i][1];

		// Create our Bezier path control points
		//TiM: judging from the hardcoded values, point 1 is positioned 45% units away from length, on same Z-axis 
		//VectorSet( cpt1, 50 * m[i][0], 50 * m[i][1], 0 );
		//VectorAdd( org1, cpt1, cpt1 );
		//VectorSet( cpt1, org1[0] + 100, org[1] + 100, 0 );

		VectorSet( cpt1, org[0] + ( end[0] - org[0] ) * 0.65f, org[1] + ( end[1] - org[1] ) * 0.65f, org1[2] );
		//VectorAdd( org1, cpt1, cpt1 );

		//Com_Printf("ORG = { %f, %f, %f }, CPT = { %f, %f, %f }\n", org1[0], org1[1], org1[2], cpt1[0], cpt1[1], cpt1[2] );

		//point 2 is positioned
		//TiM - point 2 I guess is the remaining 55%
		//VectorSet( cpt2, 60 * m[i][0], 60 * m[i][1], -78 );
		//VectorAdd( org1, cpt2, cpt2 );

		//VectorSet( cpt2, distance * 0.55, distance * 0.55, -Q_fabs(org1[2] - end[2]) );
		//VectorAdd( org1, cpt2, cpt2 );
		VectorCopy( end, cpt2 );

		// Create the second endpoint--for now just try and use the last control point 
		VectorCopy( cpt2, org2 );

		// Add the main spout
		FX_AddBezier( org1, org2, cpt1, cpt2, NULL, NULL, NULL, NULL, 4, 90, 
			cgs.media.fountainShader);

		//if ( fxb )
		//	fxb->SetSTScale( 0.7f );

		// Add a hazy faint spout
		FX_AddBezier( org1, org2, cpt1, cpt2, NULL, NULL, NULL, NULL, 10, 200, 
			cgs.media.fountainShader);

		//if ( fxb )
		//	fxb->SetSTScale( 0.7f );

		// Create misty bits at the impact point
		VectorSet( dir, crandom(), crandom(), crandom() + 4 ); // always move mostly up
		VectorScale( dir, random() * 3 + 2, dir );
		FX_AddSprite( org2, dir, qfalse, 20, -8, 0.3f, 0.0, 0, 0, 600, cgs.media.steamShader );

		// ripple shader
		VectorSet( dir, 0, 0, 1 ); // normal
		//VectorSet( dir2, crandom() * 8, crandom() * 8, 0 ); // random drift
		FX_AddQuad( org2, dir, 14.0f, 6.0f + random() * 16.0f, 0.2f, 0.0f, crandom() * 50, 800, cgs.media.rippleShader );

		// Spray from nozzle
		for ( t = 0; t < 2; t++ )
		{
			//VectorSet( dir, 45 * m[i][0] + crandom() * 12, 45 * m[i][1] + crandom() * 12, crandom() * 16 );
			//VectorSet( dir2, -5 * m[i][0], -5 * m[i][1], -95 );
			VectorSet( dir, 0.4 * ( end[0] - org[0] ) + crandom() * 12, 0.4 * ( end[1] - org[1] ) + crandom() * 12, crandom() * 16 );
			//VectorSet( dir2, -0.04 * distance, -0.04 * distance, -95 );

			FX_AddSprite( org1, dir, qtrue, 0.9f, 0.0f, 0.7f, 0.1f, 0.0f, 0.0f, 400.0f, cgs.media.waterDropShader );
		}

		// Impact splashes
		for ( t = 0; t < 3; t++ )
		{
			VectorCopy( org2, org1 );
			org1[0] += crandom() * 2;
			org1[1] += crandom() * 2;
			//VectorSet( dir, m[i][0] * 14 + crandom() * 16, m[i][1] * 14 + crandom() * 16, 50 + random() * 50 );
			VectorSet( dir, ( end[0] - org[0] ) * 0.127 + crandom() * 16, ( end[1] - org[1] ) * 0.127 + crandom() * 16, 50 + random() * 50 );
			//VectorSet( dir2, 0, 0, -250 );
			FX_AddSprite( org1, dir, qtrue, 1.1f, -0.4f, 0.7f, 0.1f, 0.0f, 0.0f, 400.0f, cgs.media.waterDropShader );
		}
	//}
	//Com_Printf( S_COLOR_RED "Rendering Fountain\n" );
}

/*================
CG_ElectricalExplosion
=================*/

void smoke_puffs( vec3_t position, vec3_t dest, vec3_t dir, vec3_t user )
{
	vec3_t direc;

	direc[0] = crandom() * 7;
	direc[1] = crandom() * 7;
	direc[2] = random() * 6 + 8;

	FX_AddSprite( position, direc, qfalse, 6.0f, 10.0f, 0.3f, 0.0f, 0.0f, 0.0f, 2200, cgs.media.steamShader );
}

//------------------------------------------------------------------------------
void electric_spark( vec3_t pos, vec3_t normal, vec3_t dir, vec3_t user )
{
	CG_Spark( pos, normal, 0, 10000 );
}

//------------------------------------------------------------------------------
void CG_ElectricalExplosion( vec3_t start, vec3_t dir, float radius )
{
	localEntity_t	*le;
	vec3_t			pos, temp/*, angles*/;
	int				i, numSparks;
	float			scale;

	// Spawn some delayed smoke
	/*FX_AddSpawner( start, dir, NULL, NULL, 150, 40, qfalse, 9000, smoke_puffs );
	vectoangles( dir, angles );
	FX_AddSpawner( start, angles, NULL, NULL, 900, 800, 4000, FXF_DELAY_SPAWN, electric_spark );*/

	// Create the sparks for the explosion
	numSparks = 46 + (random() * 8.0f);
	 
	for ( i = 0; i < numSparks; i++ )
	{	
		scale = 0.7f + random(); //0.2

		FX_AddTrail( start,
								NULL,
								qfalse,
								8.0f + random() * 6,
								-16.0f,
								scale,
								-scale,
								1.0f,
								0.0f,
								0.25f,
								700.0f,
								cgs.media.spark2Shader );
 
		/*if ( particle == NULL )
			return;*/

		//FXE_Spray( dir, 200, 200, 0.3f, 500 + (rand() & 300), (FXPrimitive *) particle );
	}

	// Create some initial smoke puffs
	for (i = 0; i < 12; i++)
	{
		VectorCopy( dir, temp );
		temp[0] += crandom() * 0.5f;
		temp[1] += crandom() * 0.5f;
		temp[2] += crandom() * 0.5f;

		VectorMA( start, random() * 16 + 8, temp, pos );
		VectorScale( temp, random() * 4 + 5, temp );

		FX_AddSprite( pos, temp, qfalse, radius * 5.3f/*16.0*/, 3.0f, 1.0f, 0.0f, 0.0f, 0.0f, 2700 + random() * 600, cgs.media.steamShader );
	}

	// Now place a cool explosion model on top
	VectorSubtract( cg.refdef.vieworg, start, dir );
	VectorNormalize( dir );

	//le = CG_MakeExplosion( start, dir, cgs.media.explosionModel, 6, cgs.media.electricalExplosionSlowShader, 500, qfalse, radius * 0.01f + ( crandom() * 0.3f)  );
	le = CG_MakeExplosion( start, dir, cgs.media.explosionModel, cgs.media.electricalExplosionSlowShader, 500, radius, qfalse );
	le->light = 150;
	//le->refEntity.		radius * 0.01f + ( crandom() * 0.3f)
	
	le->refEntity.renderfx |= RF_NOSHADOW;

	VectorSet( le->lightColor, 0.8f, 0.8f, 1.0f );
}

//RPG-X | GSIO01 | 09/05/2009:
void FX_PhaserFire2(vec3_t startpos, vec3_t endpos, vec3_t normal, qboolean impact, float scale)
{
	refEntity_t		beam;
	//float			size;
	//vec3_t			velocity;
	//int				sparks;
	vec3_t			rgb = { 1,0.9,0.6}, rgb2={1,0.3,0};

	// Draw beam first.
	memset( &beam, 0, sizeof( beam ) );
	VectorCopy( startpos, beam.origin);
	VectorCopy( endpos, beam.oldorigin );
	beam.reType = RT_LINE;
	beam.customShader = cgs.media.phaserShader;
	AxisClear( beam.axis );
	beam.shaderRGBA[0] = 0xff;
	beam.shaderRGBA[1] = 0xff;
	beam.shaderRGBA[2] = 0xff;
	beam.shaderRGBA[3] = 0xff;
	beam.data.line.width = scale + ( crandom() * 0.6f );
	beam.data.line.stscale = 5.0;
	trap_R_AddRefEntityToScene( &beam );
	if (impact)
	{
		FX_AddQuad2( endpos, normal, (0.75f * scale) + random() * .75 + 1.0f, 0.0f, 0.5f, 0.0f, rgb, rgb2, rand() % 360, 300 + random() * 200, 
			cgs.media.sunnyFlareShader );
	}
}

qboolean PhaserFX_Think(localEntity_t *le) {
	vec3_t	dir;
	qboolean impact = qfalse;
	le->data.spawner.nextthink = cg.time;
	VectorSubtract(le->data.spawner.dir, le->refEntity.origin, dir);
	VectorNormalize(dir);
	if(le->data.spawner.data2 == 1)
		impact = qtrue;

	FX_PhaserFire2(le->refEntity.origin, le->data.spawner.dir, dir, impact, le->data.spawner.data1);

	return qtrue;
}

void CG_PhaserFX(centity_t *cent) {
	localEntity_t *le;
	le = FX_AddSpawner(cent->currentState.origin, cent->currentState.origin2, NULL, NULL, qfalse, 0, 0, cent->currentState.time2, PhaserFX_Think, 10);
	le->data.spawner.data1 = cent->currentState.angles[0];
	le->data.spawner.data2 = cent->currentState.angles[2];
	le->data.spawner.nextthink = cg.time + (int)cent->currentState.angles[1];
}

qboolean TorpedoQFX_Think(localEntity_t *le)
{
	vec3_t	line1end, line2end, axis[3], vel, dis;
	float	dist;

	VectorSubtract(le->refEntity.origin, le->addOrigin, dis);
	dist = VectorLength(dis);
	if(dist < 0)
		dist *= -1;
	if(dist < 10)
		le->data.spawner.nextthink = le->endTime;

	AxisClear( axis ); 

	// convert direction of travel into axis
	if ( VectorNormalize2( le->data.spawner.dir, axis[0] ) == 0 ) {
		axis[0][2] = 1;
	}

	// spin as it moves
	RotateAroundDirection( axis, cg.time * 0.3f );// * 1.25f );

	VectorMA( le->refEntity.origin, -48.0f, axis[1], line1end ); // -24 is to high
	VectorMA( le->refEntity.origin,  48.0f, axis[1], line2end ); // 24 is to high
	FX_AddLine( line1end, line2end, 1.0f, random() * 18 + 2, 0.0f, 0.2 + random() * 0.2, 0.0f, 1, cgs.media.quantumGlow ); // replaced yellowParticleShader

	AxisClear( axis ); 

	// convert direction of travel into axis
	if ( VectorNormalize2( le->data.spawner.dir, axis[0] ) == 0 ) {
		axis[0][2] = 1;
	}

	// spin as it moves
	RotateAroundDirection( axis, -cg.time * 0.1f );// * 1.25f );

	VectorMA( le->refEntity.origin, 0.0f/*-128.0f*/, axis[2], line1end ); // -48 to high
	VectorMA( le->refEntity.origin,  128.0f, axis[2], line2end ); // 48 to high
	FX_AddLine( line1end, line2end, 1.0f, random() * 25 + 2, 0.0f, /*0.1 + random() * 0.2*/0.0f, 0.0f, 1, cgs.media.quantumGlow);
	//FX_AddSprite(line1end, NULL, qfalse, random() * 90 + 30, 4, 1.0f, 0.0f, 0, 0.0f, 1.0f, cgs.media.photonStar);

	FX_AddSprite( le->refEntity.origin, NULL,qfalse,random() * 60 + 30, 4, 0.5f, 0.0f, 0, 0.0f, 1.0f, cgs.media.quantumRays);	
	//FX_AddSprite2(le->refEntity.origin, NULL,qfalse,random() * 10 + 60, 0.0f, 0.1f, 0.1f, rgb, rgb, 0.0f, 0.0f, 1, cgs.media.whiteRingShader);
	FX_AddSprite( le->refEntity.origin, NULL,qfalse,random() * 40 + 8, 4, 0.5f, 0.0f, 0, 0.0f, 1.0f, cgs.media.quantumGlow );	

	VectorCopy(le->data.spawner.dir, vel);
	VectorNormalize(vel);
	VectorScale(vel, le->refEntity.oldorigin[0], vel);
	VectorAdd(le->refEntity.origin, vel, le->refEntity.origin);
	//le->data.spawner.nextthink = cg.time + 100;

	return qtrue;
}

qboolean TorpedoPFX_Think(localEntity_t *le)
{
	vec3_t	line1end, line2end, axis[3], vel, dis;
	float	dist;

	VectorSubtract(le->refEntity.origin, le->addOrigin, dis);
	dist = VectorLength(dis);
	if(dist < 0)
		dist *= -1;
	if(dist < 10)
		le->data.spawner.nextthink = le->endTime;

	AxisClear( axis ); 

	// convert direction of travel into axis
	if ( VectorNormalize2( le->data.spawner.dir, axis[0] ) == 0 ) {
		axis[0][2] = 1;
	}

	// spin as it moves
	RotateAroundDirection( axis, cg.time * 0.3f );// * 1.25f );

	VectorMA( le->refEntity.origin, -48.0f, axis[1], line1end ); // -24 is to high
	VectorMA( le->refEntity.origin,  48.0f, axis[1], line2end ); // 24 is to high
	FX_AddLine( line1end, line2end, 1.0f, random() * 18 + 2, 0.0f, 0.2 + random() * 0.2, 0.0f, 1, cgs.media.photonGlow ); // replaced yellowParticleShader

	AxisClear( axis ); 

	// convert direction of travel into axis
	if ( VectorNormalize2( le->data.spawner.dir, axis[0] ) == 0 ) {
		axis[0][2] = 1;
	}

	// spin as it moves
	RotateAroundDirection( axis, -cg.time * 0.1f );// * 1.25f );

	VectorMA( le->refEntity.origin, 0.0f/*-128.0f*/, axis[2], line1end ); // -48 to high
	VectorMA( le->refEntity.origin,  128.0f, axis[2], line2end ); // 48 to high
	FX_AddLine( line1end, line2end, 1.0f, random() * 25 + 2, 0.0f, /*0.1 + random() * 0.2*/0.0f, 0.0f, 1, cgs.media.photonGlow);
	//FX_AddSprite(line1end, NULL, qfalse, random() * 90 + 30, 4, 1.0f, 0.0f, 0, 0.0f, 1.0f, cgs.media.photonStar);

	FX_AddSprite( le->refEntity.origin, NULL,qfalse,random() * 60 + 30, 4, 0.5f, 0.0f, 0, 0.0f, 1.0f, cgs.media.photonRay);	
	//FX_AddSprite2(le->refEntity.origin, NULL,qfalse,random() * 10 + 60, 0.0f, 0.1f, 0.1f, rgb, rgb, 0.0f, 0.0f, 1, cgs.media.whiteRingShader);
	FX_AddSprite( le->refEntity.origin, NULL,qfalse,random() * 40 + 8, 4, 0.5f, 0.0f, 0, 0.0f, 1.0f, cgs.media.photonGlow );	

	VectorCopy(le->data.spawner.dir, vel);
	VectorNormalize(vel);
	VectorScale(vel, le->refEntity.oldorigin[0], vel);
	VectorAdd(le->refEntity.origin, vel, le->refEntity.origin);
	//le->data.spawner.nextthink = cg.time + 100;
	
	return qtrue;
}

void CG_TorpedoFX(centity_t *cent) {
	localEntity_t	*le;
	if(cent->currentState.eventParm & 1) { // quantum fx
		le = FX_AddSpawner(cent->currentState.origin, cent->currentState.angles, NULL, NULL, qfalse, 0, 0, 10000, TorpedoQFX_Think, 10);
	} else { // photon fx
		le = FX_AddSpawner(cent->currentState.origin, cent->currentState.angles, NULL, NULL, qfalse, 0, 0, 10000, TorpedoPFX_Think, 10);
	}
	le->refEntity.oldorigin[0] = cent->currentState.angles2[0];
	VectorCopy(cent->currentState.origin2, le->addOrigin);
}

qboolean ParitcleFire_CreateParticles(localEntity_t *le) {

	return qtrue;
}

qboolean ParticleFire_Think(localEntity_t *le) {
	vec3_t	velocity;
	vec3_t	origin;
	vec3_t	dir = { 0, 0 , 15 };
	float	speed;
	int i;
	vec3_t	startRGB	= { 1, 0.2, 0 };
	vec3_t	endRGB		= {	1, 0.9, 0.7 };

	dir[2] = Com_Clamp( 0.85f, 1.0f, dir[2] );

	for ( i = 0; i < 3; i++ )
	{
		velocity[i] = dir[i] + ( 0.2f * crandom());
	}

	VectorMA( le->refEntity.origin, 1, le->data.spawner.dir, origin);

	speed = le->data.spawner.data1 * 2.4;

	VectorScale( velocity, speed, velocity ); //speed

	FX_AddSprite2(	origin,
					velocity, 
					qfalse, //accel
					le->data.spawner.data1 + (flrandom(0.4,2) * le->data.spawner.data1 ),  
					le->data.spawner.data1 + (crandom() * le->data.spawner.data1 * 0.5f), 
					0.8, 
					0.0,
					startRGB,
					endRGB,
					0.1f,//16.0f + random() * 45.0f,
					0.1f,
					3500, 
					cgs.media.fireParticle ); //flags

	le->data.spawner.nextthink = cg.time + 750; // don't generate to many or we will get low fps

	return qtrue;
}

void CG_ParticleFire(vec3_t origin, int killtime, int size) {
	FX_AddSpawner(origin, NULL, NULL, NULL, qfalse, 0, 0, killtime, ParticleFire_Think, 10);
}

qboolean ShowTrigger_Think(localEntity_t *le) {
	vec4_t RGBA = { 0, 1, 0, 0.75 };
	vec3_t a, b, c, d;

	VectorCopy(le->refEntity.lightingOrigin, a);
	c[0] = b[0] = le->lightColor[0];
	c[2] = d[2] = le->lightColor[2];
	b[1] = c[1] = d[1] = a[1];
	d[0] = a[0];
	b[2] = a[2];

	FX_AddLine2(a, b, 0.5, 0.5, 0, 0.5, 0, 1, 1, RGBA, RGBA, 10000, cgs.media.whiteShader);
	FX_AddLine2(b, c, 0.5, 0.5, 0, 0.5, 0, 1, 1, RGBA, RGBA, 10000, cgs.media.whiteShader);
	FX_AddLine2(c, d, 0.5, 0.5, 0, 0.5, 0, 1, 1, RGBA, RGBA, 10000, cgs.media.whiteShader);
	FX_AddLine2(d, a, 0.5, 0.5, 0, 0.5, 0, 1, 1, RGBA, RGBA, 10000, cgs.media.whiteShader);

	return qtrue;
}

qboolean ShowTrigger_Think2(localEntity_t *le) {
	vec4_t RGBA = { 0, 1, 0, 0.75 };
	vec3_t e, f, g, h;

	VectorCopy(le->lightColor, g);
	h[0] = f[0] = le->refEntity.lightingOrigin[0];
	f[2] = e[2] = le->refEntity.lightingOrigin[2];
	e[1] = f[1] = h[1] = g[1];
	e[0] = g[0];
	h[2] = g[2];

	FX_AddLine2(f, e, 0.5, 0.5, 0, 0.5, 0, 1, 1, RGBA, RGBA, 10000, cgs.media.whiteShader);
	FX_AddLine2(e, g, 0.5, 0.5, 0, 0.5, 0, 1, 1, RGBA, RGBA, 10000, cgs.media.whiteShader);
	FX_AddLine2(g, h, 0.5, 0.5, 0, 0.5, 0, 1, 1, RGBA, RGBA, 10000, cgs.media.whiteShader);
	FX_AddLine2(h, f, 0.5, 0.5, 0, 0.5, 0, 1, 1, RGBA, RGBA, 10000, cgs.media.whiteShader);

	return qtrue;
}

qboolean ShowTrigger_Think3(localEntity_t *le) {
	vec4_t RGBA = { 0, 1, 0, 0.75 };
	vec3_t a, b, c, d, e, f, g, h;

	VectorCopy(le->refEntity.lightingOrigin, a);
	VectorCopy(le->lightColor, g);
	d[0] = f[0] = h[0] = a[0];
	b[0] = c[0] = e[0] = g[0];
	e[1] = f[1] = h[1] = g[1];
	b[1] = c[1] = d[1] = a[1];
	b[2] = f[2] = e[2] = a[2];
	h[2] = d[2] = c[2] = g[2];
 
	FX_AddLine2(a, f, 0.5, 0.5, 0, 0.5, 0, 1, 1, RGBA, RGBA, 10000, cgs.media.whiteShader);
	FX_AddLine2(d, h, 0.5, 0.5, 0, 0.5, 0, 1, 1, RGBA, RGBA, 10000, cgs.media.whiteShader);
	FX_AddLine2(b, e, 0.5, 0.5, 0, 0.5, 0, 1, 1, RGBA, RGBA, 10000, cgs.media.whiteShader);
	FX_AddLine2(c, g, 0.5, 0.5, 0, 0.5, 0, 1, 1, RGBA, RGBA, 10000, cgs.media.whiteShader);

	return qtrue;
}

void CG_ShowTrigger(centity_t *cent) {
	localEntity_t *le;
	if(cent->currentState.eventParm) {
		le = FX_AddSpawner(cent->currentState.origin, NULL, NULL, NULL, qfalse, 0, 0, 10000, ShowTrigger_Think, 0);
		VectorCopy(cent->currentState.apos.trBase, le->lightColor);
		VectorCopy(cent->currentState.pos.trBase, le->refEntity.lightingOrigin);
		le = FX_AddSpawner(cent->currentState.origin, NULL, NULL, NULL, qfalse, 0, 0, 10000, ShowTrigger_Think2, 0);
		VectorCopy(cent->currentState.apos.trBase, le->lightColor);
		VectorCopy(cent->currentState.pos.trBase, le->refEntity.lightingOrigin);
		le = FX_AddSpawner(cent->currentState.origin, NULL, NULL, NULL, qfalse, 0, 0, 10000, ShowTrigger_Think3, 0);
		VectorCopy(cent->currentState.apos.trBase, le->lightColor);
		VectorCopy(cent->currentState.pos.trBase, le->refEntity.lightingOrigin);
	} else {
		le = FX_AddSpawner(cent->currentState.origin, NULL, NULL, NULL, qfalse, 0, 0, 10000, ShowTrigger_Think, 0);
		VectorCopy(cent->currentState.origin2, le->lightColor);
		VectorCopy(cent->currentState.angles2, le->refEntity.lightingOrigin);
		le = FX_AddSpawner(cent->currentState.origin, NULL, NULL, NULL, qfalse, 0, 0, 10000, ShowTrigger_Think2, 0);
		VectorCopy(cent->currentState.origin2, le->lightColor);
		VectorCopy(cent->currentState.angles2, le->refEntity.lightingOrigin);
		le = FX_AddSpawner(cent->currentState.origin, NULL, NULL, NULL, qfalse, 0, 0, 10000, ShowTrigger_Think3, 0);
		VectorCopy(cent->currentState.origin2, le->lightColor);
		VectorCopy(cent->currentState.angles2, le->refEntity.lightingOrigin);
	}
}

//RPG-X | Harry Young | 03.12.2011
void FX_DisruptorFire2(vec3_t startpos, vec3_t endpos, vec3_t normal, qboolean impact, float scale)
{
	refEntity_t		beam;
	//float			size;
	//vec3_t			velocity;
	//int				sparks;
	vec3_t			rgb = { 1,0.9,0.6}, rgb2={1,0.3,0};

	// Draw beam first.
	memset( &beam, 0, sizeof( beam ) );
	VectorCopy( startpos, beam.origin);
	VectorCopy( endpos, beam.oldorigin );
	beam.reType = RT_LINE;
	beam.customShader = cgs.media.disruptorBeam;
	AxisClear( beam.axis );
	beam.shaderRGBA[0] = 0xff;
	beam.shaderRGBA[1] = 0xff;
	beam.shaderRGBA[2] = 0xff;
	beam.shaderRGBA[3] = 0xff;
	beam.data.line.width = scale + ( crandom() * 0.6f );
	beam.data.line.stscale = 5.0;
	trap_R_AddRefEntityToScene( &beam );
	if (impact)
	{
		FX_AddQuad2( endpos, normal, (0.75f * scale) + random() * .75 + 1.0f, 0.0f, 0.5f, 0.0f, rgb, rgb2, rand() % 360, 300 + random() * 200, 
			cgs.media.disruptorStreak );
	}
}

qboolean DisruptorFX_Think(localEntity_t *le) {
	vec3_t	dir;
	qboolean impact = qfalse;
	le->data.spawner.nextthink = cg.time;
	VectorSubtract(le->data.spawner.dir, le->refEntity.origin, dir);
	VectorNormalize(dir);
	if(le->data.spawner.data2 == 1)
		impact = qtrue;

	FX_DisruptorFire2(le->refEntity.origin, le->data.spawner.dir, dir, impact, le->data.spawner.data1);

	return qtrue;
}

void CG_DisruptorFX(centity_t *cent) {
	localEntity_t *le;
	le = FX_AddSpawner(cent->currentState.origin, cent->currentState.origin2, NULL, NULL, qfalse, 0, 0, cent->currentState.time2, DisruptorFX_Think, 10);
	le->data.spawner.data1 = cent->currentState.angles[0];
	le->data.spawner.data2 = cent->currentState.angles[2];
	le->data.spawner.nextthink = cg.time + (int)cent->currentState.angles[1];
}

// Additional ports from SP by Harry Young

/*
===========================
Laser

Create directed laser shot
===========================
*/
void CG_SmallSpark( vec3_t origin, vec3_t normal )
{
	vec3_t	dir, direction, start, end, velocity;
	float	scale;
	int		numSparks;

	AngleVectors( normal, normal, NULL, NULL );
	
	int j;
	for ( j = 0; j < 3; j ++ )
		normal[j] = normal[j] + (0.1f * crandom());

	VectorNormalize( normal );

	numSparks = 6 + (random() * 4.0f );
	
	int i;
	for ( i = 0; i < numSparks; i++ )
	{	
		scale = 0.1f + (random() *0.2f );

		for ( j = 0; j < 3; j ++ )
			dir[j] = normal[j] + (0.7f * crandom());

		VectorMA( origin, 0.0f + ( random() * 0.5f ), dir, start );
		VectorMA( start, 1.0f + ( random() * 1.5f ), dir, end );

		FX_AddLine( start,
					end,
					1.0f,
					scale,
					0.0f,
					1.0f,
					0.7f,
					4.0f,
					cgs.media.sparkShader );
	}

	VectorMA( origin, 1, normal, direction );

	scale = 2.0f + (random() * 3.0f );
	float alpha = 0.6f + (random() * 0.4f );

	VectorSet( velocity, crandom() * 2, crandom() * 2, 8 + random() * 4 );
	VectorMA( velocity, 5, normal, velocity );

	FX_AddSprite(	direction, 
					velocity, 
					qfalse, 
					scale,
					scale,
					alpha,
					0.0f,
					random() * 45.0f,
					0.0f,
					1000.0f,
					cgs.media.steamShader );
}


void CG_FireLaser( vec3_t start, vec3_t end, vec3_t normal, vec3_t laserRGB, float alpha )
{
	vec3_t	dir, right, up, angles, work, pos,
			sRGB;
	float	scale = 1.0f;
	int		life = 0;

	// Orient the laser spray
	VectorSubtract( end, start, dir );
	VectorNormalize( dir );

	VectorMA( end, 0.5f, normal, pos );
	MakeNormalVectors( normal, right, up );
	
	VectorSet( sRGB, 1.0f, 0.8f, 0.8f );	

	FX_AddSprite2( start, NULL, qfalse, 
					1.75f, 1.0f, 
					alpha, 0.0f, 
					laserRGB, laserRGB, 
					0.0f, 
					0.0f, 
					200, 
					cgs.media.waterDropShader );

	FX_AddLine3( start, end, 
					1.0f, 
					3.0f, 5.0f, 
					alpha, 0.0f, 
					laserRGB, laserRGB, 
					125, 
					cgs.media.whiteLaserShader ); 

	FX_AddLine( start, end, 
					1.0f, 
					0.3f, 5.0f, 
					random() * 0.4 + 0.4, 0.1f,
					125,
					cgs.media.whiteLaserShader );

	// Doing all of this extra stuff would look weird if it hits a player ent.
	//if ( !hit_ent )
	//{
		FX_AddQuad2( pos, normal, 
					3.5f, 1.0f, 
					alpha, 0.0f, 
					laserRGB, laserRGB, 
					0.0f, 
					200, 
					cgs.media.waterDropShader );
		int t;
		for ( t=0; t < 2; t ++ )
		{
			VectorMA( pos, crandom() * 0.5f, right, work );
			VectorMA( work, crandom() * 0.5f, up, work );

			scale = crandom() * 0.5f + 1.75f;
			life = crandom() * 300 + 2100;

			VectorSet( sRGB, 1.0f, 0.7f, 0.2f );
			FX_AddQuad2( work, normal,
					scale, -0.1f, 
					1.0f, 0.0f, 
					sRGB, sRGB, 
					0,
					life, 
					cgs.media.waterDropShader );
		}

		FX_AddQuad( pos, normal, 
					scale * 2.5f, 0.0f, 
					1.0f, 0.0f, 
					0, 
					life * 2, 
					cgs.media.smokeShader );
	
		vectoangles( normal, angles );
		CG_SmallSpark( end, angles );
	/*}
	else
	{
		// However, do add a little smoke puff
		FX_AddSprite2( pos, NULL, qfalse, 
						2.0f, 1.0f, 
						alpha, 0.0f, 
						laserRGB, laserRGB, 
						0.0f, 
						0.0f, 
						200, 
						cgs.media.waterDropShader );

		VectorMA( end, 1, normal, dir );
		scale = 1.0f + (random() * 3.0f);

		CG_Smoke( dir, normal, scale, 12.0f );
	}*/
}

//------------------------------------------------------------------------------
void CG_AimLaser( vec3_t start, vec3_t end, vec3_t normal )
{
	vec3_t		lRGB = {1.0,0.0,0.0};

	// Beam
	FX_AddLine3( start, end, 
					1.0f, 
					5.5f, 5.0f, 
					random() * 0.2 + 0.2, 0.1f,
					lRGB, lRGB,
					150,
					cgs.media.whiteLaserShader );

	FX_AddLine( start, end, 
					1.0f, 
					0.3f, 5.0f, 
					random() * 0.4 + 0.4, 0.1f,
					125,
					cgs.media.whiteLaserShader );

	// Flare at the start point
	FX_AddSprite( start, NULL, qfalse, 
					1.5 + random() * 4, 0.0,
					0.1f,0.0, 
					0.0,
					0.0,
					100,
					cgs.media.borgEyeFlareShader );

	// endpoint flare
	FX_AddSprite( end, NULL, qfalse, 
					2.5 + random() * 4, 0.0,
					0.1f,0.0, 
					0.0,
					0.0,
					100,
					cgs.media.borgEyeFlareShader );

	// oriented impact flare
	FX_AddQuad( end, normal, 
					1.5 + random() * 2, 0.0, 
					1.0, 0.0, 
					0.0, 
					120, 
					cgs.media.borgEyeFlareShader );
}

/*
======================
CG_CookingSteam

Creates a basic cooking steam effect
======================
*/
void CG_CookingSteam( vec3_t origin, float radius )
{
	vec3_t dir;

	VectorSet( dir, crandom()*2, crandom()*2, crandom() + radius); // always move mostly up
	VectorScale( dir, random() * 5 + 2, dir );

	FX_AddSprite( origin, dir, qfalse, radius, radius * 2, 0.4F, 0.0, 0, 0, 1000, cgs.media.steamShader );
}

/*
======================
CG_ElectricFire

Creates an electric fire effect
======================
*/

void CG_ElectricFire( vec3_t origin, vec3_t normal )
{
	void	*particle;
	vec3_t	dir, direction, start, end;
	vec3_t	velocity;
	float	scale, alpha;
	int		numSparks, i, j;

	AngleVectors( normal, normal, NULL, NULL);

	numSparks = 4 + (random() * 8.0f);
	
	for ( i = 0; i < numSparks; i++ )
	{	
		scale = 0.3f + (random() *0.4);

		for ( j = 0; j < 3; j ++ )
			dir[j] = normal[j] + (0.4f * crandom());
		
		VectorNormalize(dir);

		VectorMA( origin, -1.0f + ( random() * 2.0f ), dir, start );
		VectorMA( start, 2.0f + ( random() * 12.0f ), dir, end );

		FX_AddLine( start,
					end,
					1.0f,
					scale,
					0.0f,
					1.0f,
					0.0f,
					75.0f,
					cgs.media.sparkShader );
	}
 
	scale = 0.5f + (random() * 0.5f);

	VectorScale( normal, 300, velocity );

	particle = FX_AddTrail( start,
							velocity,
							qtrue,
							6.0f,
							-24.0f,
							scale,
							-scale,
							1.0f,
							0.5f,
							0.0f,
							200.0f,
							cgs.media.sparkShader);

	if ( particle == NULL )
		return;

	VectorMA( origin, 1, normal, direction );
	VectorSet( velocity, 0, 0, 8 );

	FXE_Spray( dir, 200, 200, 0.2f, velocity);

	VectorMA( origin, 1, normal, direction );
	VectorSet( velocity, 0, 0, 8 );

	for ( i = 0; i < 3; i++)
	{
		scale = 6.0f + (random() * 8.0f);
		alpha = 0.1 + (random() * 0.4f);

		FX_AddSprite( direction, 
					velocity, 
					qfalse, 
					scale,
					scale,
					alpha,
					0.0,
					random()*45.0f,
					0.0f,
					1000.0f,
					cgs.media.steamShader );

		VectorMA( velocity, 9.0, normal, velocity);
	}
}

/*
======================
CG_ForgeBolt

Creates an orange electricity bolt effect with a pulse that travels down the beam
======================
*/

#if 0
void ForgeBoltFireback( vec3_t start, vec3_t end, vec3_t velocity, vec3_t user )
{
	FX_AddElectricity( start, end, 1.0, user[DATA_RADIUS], 5.0, 1.0, 0.0, 200, cgs.media.pjBoltShader, 
						(int)user[DATA_EFFECTS], user[DATA_CHAOS] );
}

#endif

#if 0
bool ForgeBoltPulse( FXPrimitive *fx, centity_t *ent )
{
	vec3_t			origin, new_org;
	trace_t			trace;
	qboolean		remove = qfalse;

	VectorCopy( fx->m_origin, origin );
	fx->UpdateOrigin();
	VectorCopy( fx->m_origin, new_org );

	CG_Trace( &trace, origin, NULL, NULL, new_org, -1, CONTENTS_SOLID );

	if ( trace.fraction < 1.0f && !trace.startsolid && !trace.allsolid )
	{
		// The effect hit something, presumably a barrier, so kill it
		remove = qtrue;
		return false;
	}

	vec3_t		normal, rgb1 ={ 1.0F, 0.5F, 0.4F}, rgb2 ={ 1.0F, 1.0F, 0.3F};//, org;
	FXCylinder	*fxc;

	// Convert the direction of travel in to a normal;
	VectorCopy( fx->m_velocity, normal );
	VectorNormalize( normal );
	VectorScale( normal, -1, normal );

	fxc = FX_AddCylinder( new_org, normal, 16, 0, 16 - random() * 8, 0, 32 + random() * 24, 0, 0.2F, 0.2F, rgb1, rgb1, 1, cgs.media.psychicShader, 0.6F );

	if ( fxc == NULL )
		return false;

	fxc->SetFlags( FXF_WRAP );
	fxc->SetSTScale( Q_irand(1,3) );

	fxc = FX_AddCylinder( new_org, normal, 8, 0, 12 - random() * 8, 0, 24 + random() * 24, 0, 0.2F, 0.2F, rgb2, rgb2, 1, cgs.media.psychicShader, 0.6F );

	if ( fxc == NULL )
		return false;

	fxc->SetFlags( FXF_WRAP );
	fxc->SetSTScale( Q_irand(1,2) );

	return true;
}
#endif

#if 0
void CG_ForgeBolt( centity_t *cent )
{
	qboolean	pulse;
	int			effects;
	float		chaos, radius;
	
	// Set up all of the parms
	pulse = (cent->gent->spawnflags & 8) ? qtrue : qfalse;
	effects = (cent->gent->spawnflags & 16) ? FXF_TAPER : 0;
	effects = (cent->gent->spawnflags & 32) ? (FXF_WRAP | effects) : effects;
	chaos = cent->gent->random;
	radius = cent->gent->radius;

	// Delayed bolt that should "work" a while
	if ( cent->gent->spawnflags & 2 )
	{
		vec3_t data;

		// This sucks, but the spawn function needs some extra bits of info
		data[DATA_EFFECTS] = effects;
		data[DATA_CHAOS] = chaos;
		data[DATA_RADIUS] = radius;

		FX_AddSpawner( cent->lerpOrigin, cent->currentState.origin2, NULL, data, 70, random() * 25, 450, (void *) ForgeBoltFireback );
	}
	else
	{
		FX_AddElectricity( cent->lerpOrigin, cent->currentState.origin2, 1.0, radius, 5.0, 1.0, 0.0, 200, cgs.media.pjBoltShader, 
						effects, chaos );

		if ( rand() & 1 )
			FX_AddElectricity( cent->currentState.origin2, cent->lerpOrigin, 1.0, radius * 2, 5.0, 1.0, 0.0, 200, cgs.media.pjBoltShader, 
						effects, chaos );

		if ( cg.time > cent->gent->delay && pulse )
		{
			vec3_t	dir;
			float	amt;

			VectorSubtract( cent->currentState.origin2, cent->lerpOrigin, dir );
			VectorNormalize( dir );
			amt = 200 + random() * 100;
			VectorScale( dir, amt, dir );

			FX_AddParticle( cent, cent->lerpOrigin, dir, NULL, 16, 0.0, 1.0, 1.0,
						0.0, 0.0, 6000, cgs.media.ltblueParticleShader, FXF_NODRAW, ForgeBoltPulse );

			cent->gent->delay = cg.time + 500;
		}
	}

	// Bolt that generates sparks at the impact point
	if ( cent->gent->spawnflags & 4 )
	{
		vec3_t	dir;

		VectorSubtract( cent->lerpOrigin, cent->currentState.origin2, dir );
		VectorNormalize( dir );

		BoltSparkSpew( cent->currentState.origin2, dir, cgs.media.dkorangeParticleShader );
	}
}
#endif

/*
===========================
Plasma

Create directed and scaled plasma jet
===========================
*/

void CG_Plasma( vec3_t start, vec3_t end, vec3_t sRGB, vec3_t eRGB, int startalpha, int endalpha )
{
	vec3_t	v;
	float len, salpha = (startalpha / 255) , ealpha = (endalpha / 255);

	//detail = FX_DetailLevel( start, 16, 1200 );
	//if ( detail == 0 )
	//	return;

	// Orient the plasma
	VectorSubtract( end, start, v );
	len = VectorNormalize( v );

	// Stash a quad at the base to make the effect look a bit more solid
	//FX_AddQuad( sp, v, NULL, NULL, len * 0.36f, 0.0f, salpha, salpha, sRGB, sRGB, 0.0f, 45.0f, 0.0f, 200, cgs.media.prifleImpactShader );
	
	// Add a subtle, random flutter to the cone direction
	v[0] += crandom() * 0.04;
	v[1] += crandom() * 0.04;
	v[2] += crandom() * 0.04;
	
	// Wanted the effect to be scalable based on the length of the jet. 
	FX_AddCylinder2( start, v, len * 0.05, len * 2.0f, len * 0.16f, len * 0.32f, len * 0.40f, len * 0.64f, 
					salpha, ealpha, sRGB, eRGB, 200, cgs.media.plasmaShader, 0.3f );
	FX_AddCylinder2( start, v, len * 0.05, len * 4.0f, len * 0.16f, len * 0.32f, len * 0.28f, len * 0.64f, 
					salpha, ealpha, sRGB, eRGB, 200, cgs.media.plasmaShader, 0.2f );
	FX_AddCylinder2( start, v, len * 0.25, len * 8.0f, len * 0.20f, len * 0.32f, len * 0.02f, len * 0.32f, 
					salpha, ealpha, sRGB, eRGB, 200, cgs.media.plasmaShader, 0.1f );
}

/*
======================
CG_ParticleStream

particle stream fx for STASIS level
======================
*/

#if 0
bool particle_stream_think( FXPrimitive *fx, centity_t *ent )
{
	vec3_t old_org;

	// Make it flicker. . .always safe to do this
	fx->m_scale = random() * 12 + 2;
	fx->m_alpha = random() * 0.4 + 0.6;

	// If the ent was somehow removed, we don't want to continue any further.
	if ( !ent )
		return false;

	// Stash the old position so that we can draw a trailer line
	VectorCopy( fx->m_origin, old_org );

	// Update the position of the particle.
	fx->m_origin[0]  = cos(cg.time * 0.01 + fx->m_velocity[0]) * fx->m_velocity[1] + ent->lerpOrigin[0];
	fx->m_origin[1]  = sin(cg.time * 0.01 + fx->m_velocity[0]) * fx->m_velocity[1] + ent->lerpOrigin[1];
	fx->m_origin[2] += (fx->m_velocity[2] * cg.frametime * 0.001);

	FX_AddLine( fx->m_origin, old_org, 1.0f, 2.0f, -4.0f, 0.6f, 0.0, 500, cgs.media.IMOD2Shader );

	return true;
}
#endif

#if 0
void CG_ParticleStream( centity_t *cent )
{
	vec3_t	vel, org, dir;
	float	len, time;

	// This effect will currently only travel directly up or down--never sideways
	VectorSubtract( cent->currentState.origin2, cent->lerpOrigin, dir );
	len = VectorNormalize( dir );

	// since the movement direction is limited, use the velocity var a bit more efficiently
	vel[0] = random() * 360;						// random position around the cylinder
	vel[1] = random() > 0.9  ? 20 : 6;				// random radius
	vel[2] = dir[2] * 120 + dir[2] * random() * 50;	// random velocity (up or down)

	// Set the particle position
	org[0] = cos(cg.time * 0.01 + vel[0]) * vel[1] + cent->lerpOrigin[0];
	org[1] = sin(cg.time * 0.01 + vel[0]) * vel[1] + cent->lerpOrigin[1];
	org[2] = cent->lerpOrigin[2];

	// Calculate how long the thing should live based on it's velocity and the distance it has to travel
	time = len / vel[2] * 1000;

	// Use a couple of different kinds to break up the monotony
	if ( rand() & 1 )
	{
		FX_AddParticle( cent, org, vel, NULL, 16, 0.0, 1.0, 1.0,
						0.0, 0.0, time, cgs.media.ltblueParticleShader, 0, particle_stream_think );
	}
	else
	{
		FX_AddParticle( cent, org, vel, NULL, 16, 0.0, 1.0, 1.0,
						0.0, 0.0, time, cgs.media.purpleParticleShader, 0, particle_stream_think );
	}
}
#endif

/*
======================
CG_TransporterStream

particle stream fx for forge level
The particles will accelerate up to the half-way point of the cylinder, then deccelerate til they hit their target
======================
*/

#if 0
void CG_TransporterStream( centity_t *cent )
{
	vec3_t	vel, accel, dir, pos, right, up;
	float	len, time, acceleration, scale, dis, vf;
	int		t;
	VectorSubtract( cent->currentState.origin2, cent->lerpOrigin, dir );
	len = VectorNormalize( dir );
	MakeNormalVectors( dir, right, up );

	for ( t=0; t < 3; t++ )
	{
		// Create start offset within a circular radius
		VectorMA( cent->lerpOrigin, 8 * crandom(), right, pos );
		VectorMA( pos, 8 * crandom(), up, pos );

		acceleration = 80 + random() * 50;
		VectorScale( dir, acceleration, accel ); // acceleration vector
		VectorScale( dir, 0.0001, vel );		// Ideally, vel would be zero, so just make it really small

		dis = ( len * 0.8f );					// the two segs will be overlapping to cover up the middle

		// This is derived from dis = (vi)(t)  +  (1/2)(a)(t)^2 where the inital velocity (vi) = zero
		time = sqrt( 2  / acceleration * dis );	// Calculate how long the thing will take to travel that distance
		
		scale = 1.5f + random() * 4;

		// These will spawn at the base and accelerate towards the middle
		if ( rand() & 1 )
		{
			FX_AddSprite3( pos, vel, accel, 
						scale, 0.0f, 
						1.0f, 0.0f, 
						0.0f, 
						0.0f, 
						time * 1000, 
						cgs.media.orangeParticleShader );
		}
		else
		{
			FX_AddSprite3( pos, vel, accel, 
						scale, 0.0f, 
						1.0f, 0.0f, 
						0.0f, 
						0.0f, 
						time * 1000, 
						cgs.media.dkorangeParticleShader );
		}

		// These will be spawned somewhere in the middle and deccelerate till they reach the end of their target
		VectorMA( pos, len - dis, dir, pos );
		VectorScale( accel, -1, accel );

		vf = sqrt( 2 * dis * acceleration ); // calculate the how fast it would be moving at the end of its path
		VectorScale( dir, vf, vel );		//	this will be the _initial_ velocity for those starting in the middle

		if ( rand() & 1 )
		{
			FX_AddSprite3( pos, vel, accel, 
						scale, 0.0f, 
						0.0f, 1.0f, 
						0.0f, 
						0.0f, 
						time * 1000, 
						cgs.media.orangeParticleShader );
		}
		else
		{
			FX_AddSprite3( pos, vel, accel, 
						scale, 0.0f, 
						0.0f, 1.0f, 
						0.0f, 
						0.0f, 
						time * 1000, 
						cgs.media.dkorangeParticleShader );
		}
	}
}
#endif

/*
-------------------------
CG_ExplosionTrail
-------------------------
*/

#if 0
qboolean explosionTrailThink( localEntity_t	*fx )
{
	localEntity_t	*le=0;
	vec3_t			direction, origin, new_org, angles, dir;
	trace_t			trace;
	float			scale;
	int				i;
	qboolean		remove = qfalse;

	VectorCopy( fx->m_origin, origin );
	fx->UpdateOrigin();
	VectorCopy( fx->m_origin, new_org );

	CG_Trace( &trace, origin, NULL, NULL, new_org, -1, CONTENTS_SOLID );

	if ( trace.fraction < 1.0f && !trace.startsolid && !trace.allsolid )
	{
		// The effect hit something, presumably a barrier, so kill it
		// When the effect gets killed like this, it dies quickly and looks a bit thin.
		// Maybe something else should be done as well...
		remove = qtrue;
		//FIXME: FX_RemoveEffect( fx );
		return qfalse;
	}

	scale = 80 * 0.03f;

	VectorSubtract( new_org, origin, dir );
	VectorNormalize( dir );
	vectoangles( dir, angles );
		
	//Orient the explosions to face the camera
	VectorSubtract( cg.refdef.vieworg, origin, direction );
	VectorNormalize( direction );

	for ( i = 0; i < 3 + (int)remove * 6; i++)
	{
		angles[2] = crandom() * 360;

		AngleVectors( angles, NULL, dir, NULL );
		VectorMA( origin, random() * 50.0f, dir, new_org );

		le = CG_MakeExplosion( new_org, direction, cgs.media.explosionModel, 6, cgs.media.surfaceExplosionShader, 400 + (int)remove * 800, qfalse, random() * 1.0 + 0.8 );//random() * 1.0 + 1.0 );
	}

	le->light = 150;
	VectorSet( le->lightColor, 64, 192, 255 );

	//Shake the camera and damage everything in an area
	CG_ExplosionEffects( origin, 3.0f, 600 );
	G_RadiusDamage( origin, ent->gent, 150, 80, NULL, MOD_UNKNOWN );

	return qtrue;
}
#endif

#if 0
void CG_ExplosionTrail( centity_t *cent )
{
	vec3_t			dir;
	float			len;

	VectorSubtract( cent->currentState.origin2, cent->currentState.origin, dir );
	len = VectorNormalize( dir );
	VectorScale( dir, 325, dir );

	FX_AddParticle( cent->currentState.origin, dir, qfalse, 16, 0.0, 1.0, 1.0,
						0.0, 0.0, 6000, cgs.media.ltblueParticleShader, explosionTrailThink );
}
#endif

/*
----------------------
CG_BorgEnergyBeam

A scanning type beam
----------------------
*/

#if 0
void CG_BorgEnergyBeam( centity_t *cent )
{
	vec3_t		normal, angles, base, dir, dir2, rgb;
	float		len, alpha;

	VectorSubtract( cent->currentState.origin2, cent->lerpOrigin, normal );
	len = VectorNormalize( normal );
	vectoangles( normal, angles );
	alpha = Vector4to3( cent->gent->startRGBA, rgb );

	// Code to make the thing "snap" when it's doing the beam slices
	if ( abs( cent->gent->pos2[0] ) >= cent->gent->radius )
	{
		// Snap back to start and move to the next slice
		cent->gent->pos2[0] = cent->gent->radius;
		cent->gent->pos2[1] -= ( cg.frametime * 0.0003 * cent->gent->speed );
	}

	// The slice has moved past the end so snap back to the first slice position
	if ( abs( cent->gent->pos2[1] ) >= cent->gent->radius )
	{
		cent->gent->pos2[1] = cent->gent->radius;
	}

	// Always move across the slice
	cent->gent->pos2[0] -= ( cg.frametime * 0.001 * cent->gent->speed );


	if ( cent->gent->spawnflags & 2 )
	{
		// Trace a cone
		angles[2] = cent->gent->angle;
	}

	AngleVectors( angles, NULL, dir, dir2 );

	if ( cent->gent->spawnflags & 2 )
	{
		// Cone
		VectorMA( cent->currentState.origin2, cent->gent->radius, dir, base );
	}
	else
	{
		// Swinging pendulum
		VectorMA( cent->currentState.origin2, cent->gent->radius * ( sin( cent->gent->angle * 0.03f )), dir, base );
		VectorMA( base, cent->gent->radius * ( cos( cent->gent->angle * 0.003f )), dir2, base );
		// Do "snapping" beam slices
//		VectorMA( cent->currentState.origin2, cent->gent->pos2[0], dir, base );
//		VectorMA( base, cent->gent->pos2[1], dir2, base );
	}

	// Main trace laser
	FX_AddLine( cent->lerpOrigin, base, 64, 0.8f, 5.0f, alpha, 0.0, rgb, rgb, 120, cgs.media.whiteLaserShader );
	// Faint trail at base.  Is this really adding anything useful?
	FX_AddLine( cent->gent->pos1, base, 1, 1.0, 2.0, alpha * 0.2, 0.0, rgb, rgb, 1000, cgs.media.whiteLaserShader );
	// Faint trace cone, adds a bit of meat to the effect
	FX_AddTri( cent->lerpOrigin, cent->gent->pos1, base, alpha * 0.2, 0.0, rgb, rgb, 800, cgs.media.solidWhiteShader );
	// Laser impact point
	FX_AddSprite( base, NULL, NULL, random() * 2, 0.0, alpha, 0.0, rgb, rgb, 0.0, 0.0, 100, cgs.media.waterDropShader );

	VectorCopy( base, cent->gent->pos1 );
	cent->gent->angle += cent->gent->speed * 0.08f;
}
#endif

/*
----------------------
CG_ShimmeryThing

Creates column or cone of shimmering lines
Kind of looks like a teleporter effect
----------------------
*/

void CG_ShimmeryThing( vec3_t start, vec3_t end, vec3_t content )
{
	vec3_t	normal, angles, base, top, dir;
	float	len;
	int		i;
	int		taper = content[2];

	VectorSubtract( end, start, normal );
	len = VectorNormalize( normal );
	vectoangles( normal, angles );

	for ( i=0; i < 2; i++)
	{
		// Spawn the shards of light around a cylinder
		angles[2] = crandom() * 360;
		AngleVectors( angles, NULL, dir, NULL );

		// See if the effect should be tapered at the top
		if ( taper == 2 )
		{
			VectorMA( start, content[1] * 0.25f, dir, top );
		}
		else
		{
			VectorMA( start, content[1], dir, top );
		}

		VectorMA( end, content[1], dir, base );

		// Use a couple of different kinds to break up the monotony..
		if ( rand() & 1 )
		{
			FX_AddLine( top, base, 1.0f, len * 0.008f, len * 0.19f, 0.3f, 0.0f, random() * 200 + 600, cgs.media.ltblueParticleShader );
		}
		else
		{
			FX_AddLine( top, base, 1.0f, len * 0.008f, len * 0.19f, 0.2f, 0.0f, random() * 200 + 600, cgs.media.spark2Shader );
		}
	}
}

/*
----------------------
CG_Borg_Bolt

Yellow bolts that spark when the endpoints get close together
----------------------
*/

void CG_Borg_Bolt_static( centity_t *cent )
{
	vec3_t	diff;
	float	len;

	// Get the midpoint of the seg
	VectorSubtract( cent->currentState.origin2, cent->currentState.origin, diff );
	len = VectorNormalize( diff );

	// Use this to scale down the width of the bolts.  Otherwise, they will look pretty fairly nasty when they
	//	get too short.
	len = len / 32;

	FX_AddElectricity( cent->currentState.origin, cent->currentState.origin2, 1.0, len, 5.0, 1.0, 0.0, 200, cgs.media.phaserShader, 0 );
}

void CG_Borg_Bolt_dynamic( centity_t *cent )
{
	vec3_t	diff, neworg;
	float	len;


	// Get the midpoint of the seg
	VectorSubtract( cent->currentState.origin2, cent->currentState.origin, diff );
	len = VectorNormalize( diff );
	VectorMA( cent->currentState.origin, len * 0.5, diff, neworg );

	// If the length is pretty short, then spawn a glow spark
	if ( len > 0 && len < 12 )
	{
		int		ct, t;
		vec3_t	angles, dir, vel;
		localEntity_t	*particle;

		FX_AddSprite( neworg, NULL, qfalse, random() * (128 / len) + 12, 16.0, 0.6f, 0.0, 0.0, 0.0, 50, 
					cgs.media.yellowParticleShader );

		vectoangles( dir, angles );

		ct = 12 - len;

		// fun sparks
		for ( t=0; t < ct; t++ )
		{
			angles[1] = random() * 360;
			AngleVectors( angles, dir, NULL, NULL );
			dir[2] = random() * 0.3f;
			VectorScale( dir, 300, vel );

			particle = FX_AddTrail( neworg, NULL, qfalse, 8.0f + random() * 6, -16.0f, 1, -1,
							1.0f, 0.0f, 0.25f, 50, cgs.media.yellowParticleShader );

			if ( particle == NULL )
				return;

			FXE_Spray( dir, 100, 150, 0.5f, vel );
		}

		// If it's really short, spark and make a noise.  Tried this without the if (len>0... and it was way
		//	too obnoxious
		if ( len <= 5 )
		{
			trap_S_StartSound( neworg, 0, 0, trap_S_RegisterSound( "sound/enemies/borg/borgtaser.wav") );
		}
	}

	// Use this to scale down the width of the bolts.  Otherwise, they will look pretty fairly nasty when they
	//	get too short.
	len = len / 32;

	FX_AddElectricity( cent->currentState.origin, cent->currentState.origin2, 1.0, len, 5.0, 1.0, 0.0, 50, cgs.media.phaserShader, 0 );
}

/*
----------------------
CG_StasisDoor

Does Fade-Effect for func_stasis_door
----------------------
*/

void CG_StasisDoor(centity_t *cent, qboolean close) {
	localEntity_t *le;
	
	return;

	FX_AddQuad(cent->currentState.origin, NULL, 10, 100, 1, 1, 0, 1000, cgs.media.photonGlow);

	le = CG_AllocLocalEntity();
	le->leType = LE_STASISDOOR;

	VectorCopy(cent->lerpOrigin, le->refEntity.origin);
	VectorCopy(cent->lerpOrigin, le->refEntity.oldorigin);
	AnglesToAxis( cent->lerpAngles, le->refEntity.axis );

	le->refEntity.renderfx = RF_NOSHADOW | RF_FORCE_ENT_ALPHA;
	le->refEntity.skinNum = 0;

	le->refEntity.hModel = cgs.media.stasisDoorModel;

	trap_R_AddRefEntityToScene(&le->refEntity);
}
