/*
===========================================================================
Copyright (C) 2006 Sjoerd van der Berg ( harekiet @ gmail.com )

This file is part of Quake III Arena source code.

Quake III Arena source code is free software; you can redistribute it
and/or modify it under the terms of the GNU General Public License as
published by the Free Software Foundation; either version 2 of the License,
or (at your option) any later version.

Quake III Arena source code is distributed in the hope that it will be
useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with Foobar; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
===========================================================================
*/
// fx_main.c Run FX Scripts and provide several commands for them

#include "client.h"
#include "fx_local.h"

fxMain_t fx;
cvar_t	*fx_Megs;
cvar_t	*fx_Vibrate;
cvar_t	*fx_Override;
cvar_t	*fx_Debug;

static ID_INLINE void fxMaskInput( const byte *mask, const void *readV, void *writeV ) {
	const long *read = (long *)readV;
	long *write = (long*)writeV;

	read += mask[0];
	mask++;
	while (1) {
		int count;
		/* Copy blocks */
		if (!mask[0])
			break;
		count = mask[0];
		do {
			*write++ = *read++;
			count--;
		} while (count > 0);
		/* Skip blocks */
		if (!mask[1])
			break;
		read += mask[1];
		mask += 2;
	}
}


static ID_INLINE void fxMaskOutput( const byte *mask, const void *readV, void *writeV ) {
	const long *read = (long *)readV;
	long *write = (long*)writeV;

	write += mask[0];
	mask++;
	while (1) {
		int count;
		/* Copy blocks */
		if (!mask[0])
			break;
		count = mask[0];
		do {
			*write++ = *read++;
			count--;
		} while (count > 0);
		/* Skip blocks */
		if (!mask[1])
			break;
		write += mask[1];
		mask += 2;
	}
}


//Some forward defines
static void *fxGetActive( unsigned int key1, unsigned int key2, int size );
static const void *fxRun( fxRun_t *run, const void *data );

static ID_INLINE int fxRand( void ) {
	fx.seed = (69069 * fx.seed + 1);
	return fx.seed;
}
static ID_INLINE float fxRandom( void ) {
	return (fxRand() & 0xffff) * (1.0f / 0x10000);
}

/* Very simple allocation function */
static void *fxAlloc( int size ) {
	int *search = fx.allocSearch;
	int *searchStart = search;
	
	size += sizeof( int );
	//TODO make size a multiple of sizeof (void *)
	while (1) {
		//Used block
		if ( search[0] == size ) {
			search[0] = -size;
			fx.allocSearch = (int*)(((char *)search) + size);
			return search + 1;
		} else if ( search[0] > size ) {
			fx.allocSearch = (int*)(((char *)search) + size);
			fx.allocSearch[0] = search[0] - size;
			search[0] = -size;
			return search + 1;
		} else if ( search[0] < 0 ) {
			search = (int*)(((char *)search) - search[0]);
		//Last block
		} else if (!search[0] ) {
			search = fx.allocStart;
		//Free block but too small, find a free one after it and join
		} else {
			int *searchNext = (int*)(((char *)search) + search[0]);
			/* Is the next one free, if so add it */
			if ( searchNext[0] > 0  ) {
				search[0] += searchNext[0];
				/* Added the start one and still not enough room? */
				if ( searchStart == searchNext && search[0] < size )
					break;
				continue;
			} else {
				search = searchNext;
			}
		}
		/* Check if it went a full loop, out of space then */
		if ( search == searchStart) {
			break;
		}
	}
	if ( fx_Debug->integer ) {
		Com_Printf("FX_Debug:Ran out of allocation space, needed %d\n", size );
	} else {
		Com_Error( ERR_DROP, "FX Ran out of allocation space for %d bytes, increase fx_Megs\n", size );
	}
	return 0;
}

static void fxVibrate( const vec3_t position, float strength ) {
	if ( fx.vibrate.used < FX_VIBRATE_QUEUE ) {
		int i = fx.vibrate.used++;
		VectorCopy( position, fx.vibrate.queue[i].origin );
		fx.vibrate.queue[i].strength = strength;
	}
}

static void fxDoVibrate( const vec3_t origin, const vec3_t position, float strength ) {
	vec3_t dist;
	float magnitude, oldmagnitude;

	VectorSubtract( origin, position, dist );

	magnitude = 1.0f - VectorLength( dist ) / 1000.0;
	if( magnitude <= 0 )
		return;
	magnitude = strength * magnitude * magnitude;

	if( fx.vibrate.time ) {
		// Adjust for current vibration
		int time = fx.time - fx.vibrate.time;
		if( time < 20 )
			time = 20;
		oldmagnitude	= fx.vibrate.magnitude / (time / 20.0f);
	}
	else {
		oldmagnitude	= 0;
	}

	fx.vibrate.magnitude	= magnitude + oldmagnitude;
	if( fx.vibrate.magnitude < 0 )
		fx.vibrate.magnitude= 0;

	fx.vibrate.offset	= fxRandom() * 360;	// Always on the 'up' half
	fx.vibrate.time		= fx.time;
}


void FX_VibrateView( float scale, vec3_t origin, vec3_t angles ) {
	int i, time;
	float magnitude;

	scale *= fx_Vibrate->value;
	if (scale <= 0) {
		fx.vibrate.used = 0;
		return;
	}
	for ( i = 0; i < fx.vibrate.used; i++ ) {
		fxDoVibrate( origin, fx.vibrate.queue[i].origin, fx.vibrate.queue[i].strength * scale ); 
	}
	fx.vibrate.used = 0;
	if( !fx.vibrate.time )
		return;

	time = fx.time - fx.vibrate.time;
	if( time < 20 )
		time = 20;
	magnitude = ((fx.vibrate.magnitude > 100) ? 100 : fx.vibrate.magnitude) / (time / 20.0f);
	if( magnitude <= 3 )
	{
		// There's not enough vibration to care about
		fx.vibrate.time = 0;
		return;
	}

	origin[2] += magnitude * 0.3 * sin( 1.4 * (fx.vibrate.offset + SQRTFAST(time)) );
	angles[ROLL] += magnitude * (0.1 * sin( 0.1 * (fx.vibrate.offset + SQRTFAST(time)) ) - 0.05);
}
static int fxSize( void *block ) {
	return ((int*)block)[-1];
}

static void fxFree( void *block ) {
	int *alloc = ((int*)block) - 1;
	alloc[0] = -alloc[0];
}

static const void *fxRunDecal( const fxRun_t *run, const fxRunDecal_t *decal ) { 
	int i;
	vec4_t color;
	
	for (i = 0;i<4;i++) 
		color[i] = run->color[i] * ( 1.0f / 255);

	re.DecalProject( run->shader, run->origin, run->dir, color, run->rotate, run->size, decal->flags, decal->life );
	return decal + 1;
}
static const void* fxRunBeam( const fxRun_t *run, const fxRunRender_t* render ) {
	refBeam_t beam;
	
	VectorCopy( run->origin, beam.start );
	VectorAdd( beam.start, run->dir, beam.end );
	beam.width = run->size;
	beam.rotateAngle = run->rotate;
	beam.shaderTime = run->shaderTime;
	beam.shader = run->shader;
	beam.renderfx = render->renderfx;
	Byte4Copy( run->color, beam.color );
	re.AddBeamToScene( &beam );
	return render + 1;
}

static const void* fxRunSpark( const fxRun_t *run, const fxRunRender_t* render ) {
	refBeam_t spark;
	
	VectorNormalize2( run->velocity, spark.end );
	VectorMA( run->origin, -0.5*run->size, spark.end, spark.start );
	VectorMA( run->origin, 0.5*run->size, spark.end, spark.end );
	spark.width = run->width;
	spark.rotateAngle = 0;
	spark.shaderTime = run->shaderTime;
	spark.shader = run->shader;
	spark.renderfx = render->renderfx;
	Byte4Copy( run->color, spark.color );
	re.AddBeamToScene( &spark );
	return render + 1;
}

static const void* fxRunRings( const fxRun_t *run, const fxRunRender_t* render ) {
	refRings_t rings;

	VectorCopy( run->origin, rings.start );
	VectorAdd( rings.start, run->dir, rings.end );
	rings.radius = run->size;
	rings.stepSize = run->width;
	rings.shaderTime = run->shaderTime;
	rings.shader = run->shader;
	rings.renderfx = render->renderfx;
	Byte4Copy( run->color, rings.color );
	re.AddRingsToScene( &rings );
	return render + 1;
}
static const void* fxRunSprite( const fxRun_t* run, const fxRunRender_t* render ) {
	refSprite_t sprite;
	VectorCopy( run->origin, sprite.origin );
	sprite.shader = run->shader;
	sprite.radius = run->size;
	sprite.rotation = run->rotate;
	sprite.shaderTime = run->shaderTime;
	Byte4Copy( run->color, sprite.color );
	sprite.renderfx = render->renderfx;
	re.AddSpriteToScene( &sprite );
	return render + 1;
}

static const void* fxRunQuad( const fxRun_t *run, const fxRunRender_t* render ) {
	polyVert_t verts[4];
	vec3_t left, up;

	PerpendicularVector( up, run->dir );
	RotatePointAroundVector( left, run->dir, up, run->angles[ROLL] );
	CrossProduct( run->dir, left, up );
	
	VectorScale( up, run->size, up );
	VectorScale( left, -run->size, left );
	verts[0].xyz[0] = run->origin[0] + left[0] + up[0];
	verts[0].xyz[1] = run->origin[1] + left[1] + up[1];
	verts[0].xyz[2] = run->origin[2] + left[2] + up[2];
	verts[1].xyz[0] = run->origin[0] - left[0] + up[0];
	verts[1].xyz[1] = run->origin[1] - left[1] + up[1];
	verts[1].xyz[2] = run->origin[2] - left[2] + up[2];
	verts[2].xyz[0] = run->origin[0] - left[0] - up[0];
	verts[2].xyz[1] = run->origin[1] - left[1] - up[1];
	verts[2].xyz[2] = run->origin[2] - left[2] - up[2];
	verts[3].xyz[0] = run->origin[0] + left[0] - up[0];
	verts[3].xyz[1] = run->origin[1] + left[1] - up[1];
	verts[3].xyz[2] = run->origin[2] + left[2] - up[2];

	Byte4Copy( run->color, verts[0].modulate );
	Byte4Copy( run->color, verts[1].modulate );
	Byte4Copy( run->color, verts[2].modulate );
	Byte4Copy( run->color, verts[3].modulate );
	verts[0].st[0] = 0;	verts[0].st[1] = 0;
	verts[1].st[0] = 1;	verts[1].st[1] = 0;
	verts[2].st[0] = 1;	verts[2].st[1] = 1;
	verts[3].st[0] = 0;	verts[3].st[1] = 1;
	re.AddPolyToScene( run->shader, 4, verts, 1 );
	return render + 1;
}
static const void* fxRunAnglesModel( const fxRun_t *run, const fxRunRender_t* render ) {
	refModel_t model;

	memset( &model, 0, sizeof( model ));
	VectorCopy( run->origin, model.origin );
	VectorCopy( run->origin, model.oldorigin );
	
	model.hModel = run->model;
	model.customShader = run->shader;
	model.shaderTime = run->shaderTime;
	model.renderfx = render->renderfx;
	Byte4Copy( run->color, model.shaderRGBA );

	// Setup the axis from the angles
	AnglesToAxis( run->angles, model.axis );
	if ( run->size > 0 && run->size != 1 ) {
		VectorScale( model.axis[0], run->size, model.axis[0] );
		VectorScale( model.axis[1], run->size, model.axis[1] );
		VectorScale( model.axis[2], run->size, model.axis[2] );
		model.nonNormalizedAxes = qtrue;
	}
	re.AddModelToScene( &model );
	return render + 1;
}
static const void* fxRunDirModel( const fxRun_t *run, const fxRunRender_t* render ) {
	refModel_t model;

	memset( &model, 0, sizeof( model ));
	VectorCopy( run->origin, model.origin );
	VectorCopy( run->origin, model.oldorigin );
	
	model.hModel = run->model;
	model.customShader = run->shader;
	model.shaderTime = run->shaderTime;
	model.renderfx = render->renderfx;
	Byte4Copy( run->color, model.shaderRGBA );

	// Setup the axis from the angles
	VectorCopy( run->dir, model.axis[0] );
	RotateAroundDirection( model.axis, run->rotate );
	if ( run->size != 1 ) {
		VectorScale( model.axis[0], run->size, model.axis[0] );
		VectorScale( model.axis[1], run->size, model.axis[1] );
		VectorScale( model.axis[2], run->size, model.axis[2] );
		model.nonNormalizedAxes = qtrue;
	}
	re.AddModelToScene( &model );
	return render + 1;
}
static const void* fxRunAxisModel( const fxRun_t *run, const fxRunRender_t* render ) {
	refModel_t model;

	memset( &model, 0, sizeof( model ));
	VectorCopy( run->origin, model.origin );
	VectorCopy( run->origin, model.oldorigin );
	
	model.hModel = run->model;
	model.customShader = run->shader;
	model.shaderTime = run->shaderTime;
	model.renderfx = render->renderfx;
	Byte4Copy( run->color, model.shaderRGBA );

	// Setup the axis from the angles
	if ( run->size != 1 ) {
		VectorScale( run->axis[0], run->size, model.axis[0] );
		VectorScale( run->axis[1], run->size, model.axis[1] );
		VectorScale( run->axis[2], run->size, model.axis[2] );
		model.nonNormalizedAxes = qtrue;
	} else {
		VectorCopy( run->axis[0], model.axis[0] );
		VectorCopy( run->axis[1], model.axis[1] );
		VectorCopy( run->axis[2], model.axis[2] );
		model.nonNormalizedAxes = qfalse;
	}
	re.AddModelToScene( &model );
	return render + 1;
}
static void fxRunLight( fxRun_t *run ) {
	float r = run->color[0] * ( 1 / 255.0f );
	float g = run->color[1] * ( 1 / 255.0f );
	float b = run->color[2] * ( 1 / 255.0f );
	re.AddLightToScene( run->origin, run->size, r, g, b);
}

static void fxRunTrace( fxRun_t *run ) {
	trace_t tr;
	vec3_t end;

	VectorMA( run->origin, 8000, run->dir, end );
	CM_BoxTrace( &tr, run->origin, end, 0, 0, 0, CONTENTS_SOLID, qfalse );
	if ( tr.fraction < 1 ) {
		VectorCopy( tr.endpos, run->origin );
		VectorCopy( tr.plane.normal, run->dir );
	}
}

static const void *fxRunShader( fxRun_t *run, const fxRunShader_t *shader ) {
	run->shader = shader->shader;
	return shader + 1;
}
static const void *fxRunShaderList( fxRun_t *run, const fxRunShaderList_t *shaderList ) {
	float i;
	qhandle_t *list = (qhandle_t*)fxRunMath( run, shaderList + 1, &i );
	run->shader = list[ (unsigned int)(i * shaderList->count) % shaderList->count ];
	return list + shaderList->count;
}
static const void *fxRunModel( fxRun_t *run, const fxRunModel_t *model ) {
	run->model = model->model;
	return model + 1;
}
static const void *fxRunModelList( fxRun_t *run, const fxRunModelList_t *modelList ) {
	float i;
	qhandle_t *list = (qhandle_t*)fxRunMath( run, modelList + 1, &i );
	run->model = list[ (unsigned int)(i * modelList->count) % modelList->count ];
	return list + modelList->count;
}
static const void *fxRunSoundList( const fxRun_t *run, const fxRunSoundList_t *sound ) {
	unsigned int index = fxRand() % sound->count;
	S_StartSound( run->origin, 0, 0, sound->handle[index] );	
	return &sound->handle[ sound->count ];
}
static const void *fxRunSound( const fxRun_t *run, const fxRunSound_t *sound ) {
	S_StartSound( run->origin, 0, 0, sound->handle );	
	return sound + 1;
}
static const void *fxRunLoopSound( const fxRun_t *run, const fxRunLoopSound_t *loopSound ) {
	S_LoopingSound( (void *)run->key, run->origin, run->velocity, loopSound->handle, 127 );
	return loopSound + 1;
}
static const void *fxRunColor( fxRun_t *run, const fxRunColor_t *color ) {
	run->color[0] = color->value[0];
	run->color[1] = color->value[1];
	run->color[2] = color->value[2];
	return color + 1;
}
static const void *fxRunColorList( fxRun_t *run, const fxRunColorList_t *colorList ) {
	float i;
	color4ub_t *list = (color4ub_t*)fxRunMath( run, colorList + 1, &i );
	byte *newColor;

	newColor = list[(unsigned int)(i * colorList->count) % colorList->count ];
	
	run->color[0] = newColor[0];
	run->color[1] = newColor[1];
	run->color[2] = newColor[2];
	return list + colorList->count;
}

static const void *fxRunColorBlend( fxRun_t *run, const fxRunColorBlend_t *colorBlend ) {
	float i;
	unsigned int index;
	int blend;
	color4ub_t *list = (color4ub_t*)fxRunMath( run, colorBlend + 1, &i );
	const byte *start, *end;

	i *= colorBlend->count-1;
	index = (int)( i );
	start = list[ index % colorBlend->count ];
	end = list[ (index + 1) % colorBlend->count ];
	blend = Q_ftol(i * 256.0f) & 255;

	run->color[0] = start[0] + ((( end[0] - start[0] ) * blend) >> 8);
	run->color[1] = start[1] + ((( end[1] - start[1] ) * blend) >> 8);
	run->color[2] = start[2] + ((( end[2] - start[2] ) * blend) >> 8);
	return list + colorBlend->count;
}

static const void *fxRunColorHue( fxRun_t *run, const void *data ) {
	float i;
	const void *next = fxRunMath( run, data, &i );
	float deg, angle, v;

	deg = (i * 360);
	angle = DEG2RAD( fmodf( deg, 120));
	v = ((cos(angle) / cos((M_PI / 3) - angle)) + 1) / 3;
	if ( deg <= 120) {
		run->color[0] = (v*255);
		run->color[1] = ((1-v)*255);
		run->color[2] = 0;
	} else if ( deg <= 240) {
		run->color[0] = 0;
		run->color[1] = (v*255);
		run->color[2] = ((1-v)*255);
	} else {
		run->color[0] = ((1-v)*255);
		run->color[1] = 0;
		run->color[2] = (v*255);
	}
	return next;
}

static const void *fxRunColorMath( fxRun_t *run, const fxRunColorMath_t *colorMath ) {
	float i;
	const void *next = fxRunMath( run, colorMath + 1, &i );
	byte *color = run->color + colorMath->index;
	if ( i < 0)
		color[0] = 0;
	else if ( i > 1.0f)
		color[0] = 255;
	else 
		color[0] = Q_ftol( 255 * i );
	return next;
}

static const void *fxRunColorScale( fxRun_t *run, const void *data ) {
	float i;
	const void *next = fxRunMath( run, data, &i );
	int s = Q_ftol(255 * i);
	run->color[0] = (s * run->color[0]) >> 8;
	run->color[1] = (s * run->color[1]) >> 8;
	run->color[2] = (s * run->color[2]) >> 8;
	return next;
}

static const void *fxRunColorFade( fxRun_t *run, const fxRunColorFade_t *colorFade ) {
	if ( run->lerp > colorFade->delay ) {
		int s = 255 - (run->lerp - colorFade->delay) * 255 * (1.0f / (1.0f - colorFade->delay));
		run->color[0] = (s * run->color[0]) >> 8;
		run->color[1] = (s * run->color[1]) >> 8;
		run->color[2] = (s * run->color[2]) >> 8;
	}
	return colorFade + 1;
}

static const void *fxRunAlphaFade( fxRun_t *run, const fxRunAlphaFade_t *alphaFade ) {
	if ( run->lerp > alphaFade->delay ) {
		unsigned int s = 255 + Q_ftol( (run->lerp - alphaFade->delay) * alphaFade->scale);
		run->color[3] = (s * run->color[3]) >> 8;
	}
	return alphaFade + 1;
}

static const void *fxRunVibrate( fxRun_t *run, const fxRunVibrate_t *vibrate) {
	fxVibrate( run->origin, vibrate->strength );
	return vibrate + 1;
}

static const void *fxRunScale( fxRun_t *run, const fxRunScale_t *scale) {
	const float *src = fxFloatSrc( run, scale->src );
	float *dst = fxFloatDst( run, scale->dst );
	float v;
	const void *next;

	next = fxRunMath( run, scale+1, &v );
	VectorScale( src, v, dst );
	return next;
}

static const void *fxRunValue( fxRun_t *run, const fxRunValue_t *value) {
	float *dst = fxFloatDst( run, value->dst );
	return fxRunMath( run, value + 1, dst );
}

static const void *fxRunClear( fxRun_t *run, const fxRunClear_t *clear) {
	float *dst = fxFloatDst( run, clear->dst );
	VectorClear( dst );
	return clear + 1;
}

static const void *fxRunAddScale( fxRun_t *run, const fxRunAddScale_t *addScale) {
	const float *src = fxFloatSrc( run, addScale->src );
	const float *scale = fxFloatSrc( run, addScale->scale );
	float *dst = fxFloatDst( run, addScale->dst );
	float v;
	const void *next;

	next = fxRunMath( run, addScale+1, &v );
	VectorMA( src, v, scale, dst );
	return next;
}

static const void *fxRunSubScale( fxRun_t *run, const fxRunSubScale_t *subScale) {
	const float *src = fxFloatSrc( run, subScale->src );
	const float *scale = fxFloatSrc( run, subScale->scale );
	float *dst = fxFloatDst( run, subScale->dst );
	float v;
	const void *next;

	next = fxRunMath( run, subScale+1, &v );
	VectorMA( src, -v, scale, dst );
	return next;
}


static const void *fxRunAdd( fxRun_t *run, const fxRunAdd_t *add) {
	const float *src1 = fxFloatSrc( run, add->src1 );
	const float *src2 = fxFloatSrc( run, add->src2 );
	float *dst = fxFloatDst( run, add->dst );
	VectorAdd( src1, src2, dst );
	return add + 1;
}

static const void *fxRunSub( fxRun_t *run, const fxRunSub_t *sub) {
	const float *src1 = fxFloatSrc( run, sub->src1 );
	const float *src2 = fxFloatSrc( run, sub->src2 );
	float *dst = fxFloatDst( run, sub->dst );
	VectorSubtract( src1, src2, dst );
	return sub + 1;
}

static const void *fxRunRotateAround( fxRun_t *run, const fxRunRotateAround_t *rotateAround) {
	const float *src = fxFloatSrc( run, rotateAround->src );
	const float *dir = fxFloatSrc( run, rotateAround->dir );
	float *dst = fxFloatDst( run, rotateAround->dst );
	float v;
	const void *next;

	next = fxRunMath( run, rotateAround+1, &v );
	RotatePointAroundVector( dst, dir, src, v );
	return next;
}

static const void *fxRunCopy( fxRun_t *run, const fxRunCopy_t *copy) {
	const float *src = fxFloatSrc( run, copy->src );
	float *dst = fxFloatDst( run, copy->dst );

	VectorCopy( src, dst );
	return copy + 1;
}

static const void *fxRunInverse( fxRun_t *run, const fxRunInverse_t *inverse ) {
	const float *src = fxFloatSrc( run, inverse->src );
	float *dst = fxFloatDst( run, inverse->dst );

	dst[0] = -src[0];
	dst[1] = -src[1];
	dst[2] = -src[2];
	return inverse+1;
}

static const void *fxRunNormalize( fxRun_t *run, const fxRunNormalize_t *normalize ) {
	const float *src = fxFloatSrc( run, normalize->src );
	float *dst = fxFloatDst( run, normalize->dst );

	VectorNormalize2( src, dst );
	return normalize+1;
}

static const void *fxRunPerpendicular( fxRun_t *run, const fxRunPerpendicular_t *perpendicular ) {
	const float *src = fxFloatSrc( run, perpendicular->src );
	float *dst = fxFloatDst( run, perpendicular->dst );

	PerpendicularVector( dst, src );
	return perpendicular+1;
}

static const void *fxRunWobble( fxRun_t *run, const fxRunWobble_t *wobble ) {
	const float *src = fxFloatSrc( run, wobble->src );
	float *dst = fxFloatDst( run, wobble->dst );
	float v;
	const void *next;
	float angle, s;
	vec3_t axis[3];

	next = fxRunMath( run, wobble+1, &v );

	/* Check for the unlikely upward/downward vector */

	VectorCopy( src, axis[0] );
	RotateAroundDirection( axis, fxRandom() * 360 ); 
	angle = DEG2RAD( v );
	s = cos( angle );
	VectorScale( axis[0], s, dst );
	s = sin( angle );
	VectorMA( dst, s, axis[2], dst );
	return next;
}

static const void *fxRunMakeAngles( fxRun_t *run, const fxRunMakeAngles_t *makeAngles ) {
	const float *src = fxFloatSrc( run, makeAngles->src );
	float *dst = fxFloatDst( run, makeAngles->dst );

	vectoangles( src, dst );
	return makeAngles + 1;
}

static const void *fxRunRandom( fxRun_t *run, const fxRunRandom_t *random ) {
	float *dst = fxFloatDst( run, random->dst );

	float yaw = fxRandom() * 2 * M_PI;
	float pitch = fxRandom() * 2 * M_PI;
	float cp = cos ( pitch );
	float sp = sin ( pitch );
	float cy = cos ( yaw );
	float sy = sin ( yaw );

	dst[0] = cp*cy;
	dst[1] = cp*sy;
	dst[2] = -sp;
	return random + 1;
}

static const void *fxRunEmitter( fxRun_t *run, const fxRunEmitter_t *emitter ) {
	fxEntity_t	*entity;

	entity = fxAlloc( emitter->allocSize );
	if ( entity ) { 
		float life;

		entity->next = fx.entityNew;
		fx.entityNew = entity;

		entity->startTime = fx.time;
		fxRunMath( run, emitter + 1, &life );
		entity->lifeScale = 1.0f / (life * 1000);
		entity->traceTime = 0;
		entity->moveTime = 0;
		entity->flags = 0;
		VectorCopy( run->origin, entity->origin );
		VectorCopy( run->velocity, entity->velocity );
		entity->emitter = emitter;
		fxMaskInput( emitter->mask, run->velocity + 3, entity + 1 );
	}
	return ((byte *)emitter) + emitter->size;
}

const void *fxRunRepeat( fxRun_t *run, const fxRunRepeat_t *repeat ) {
	int i, count;
	const void *next;
	float v, loopScale;

	next = fxRunMath( run, repeat + 1, &v );
	count = Q_ftol(v);
	loopScale = 1.0 / count;
	for (i = 0; i < count; i++ ) {
		run->loop = i * loopScale;
		fxRun( run, next );
	}
	return ((char *)repeat) + repeat->size;
 }

const void *fxRunIf( fxRun_t *run, const fxRunIf_t *runIf ) {
	int i;
	const void *next;
	float v;

	next = runIf + 1;
	for ( i = 0; i<runIf->testCount;i++) {
		int *blockSize;
		next = fxRunMath( run, next, &v );
		blockSize = (int *)next;
		next = blockSize + 1;
		if ( v ) {
			fxRun( run, next );
			return ((char *)runIf) + runIf->size;
		} else {
			/* Skip the block for this if case */
			next = ((char *)(next)) + blockSize[0];
		}
	}
	if ( runIf->elseStep) {
		return next;
	} else {
		return ((char *)runIf) + runIf->size;
	}
}

static const void *fxRunSkip( fxRun_t *run, const fxRunSkip_t *skip ) {
	return ((byte *)skip) + skip->size;
}

static const void *fxRunDistance( fxRun_t *run, const fxRunDistance_t *distance ) {
	fxActiveDistance_t	*active;
	vec3_t delta, realOrigin;
	float lengthScale;

	if ( run->entity && run->entity->flags & FX_ENT_STATIONARY )
		return ((byte *)distance) + distance->size;
	active = fxGetActive( run->key, (unsigned int)distance, sizeof( fxActiveDistance_t) );
	if ( !active )
		return ((byte *)distance) + distance->size;
	if ( !active->beenUsed ) {
		active->beenUsed = qtrue;
		VectorCopy( run->origin, active->lastOrigin );
		return ((byte *)distance) + distance->size;
	}
	VectorSubtract( run->origin, active->lastOrigin, delta );
	lengthScale = VectorNormalize( delta );
	if ( lengthScale + active->distance <  0 ) {
		return ((byte *)distance) + distance->size;
	}
	active->distance += lengthScale;
	lengthScale = -active->distance;
	VectorCopy( run->origin, realOrigin );
	while ( active->distance >= 0 ) {
		float step;
		const void *mathEnd = fxRunMath( run, distance + 1, &step );
		VectorMA( active->lastOrigin, lengthScale, delta, run->origin );
		lengthScale += step;
		active->distance -= step;
		fxRun( run, mathEnd );
	}
	VectorCopy( realOrigin, active->lastOrigin );
	VectorCopy( realOrigin, run->origin );
	return ((byte *)distance) + distance->size;
}

static const void *fxRunInterval( fxRun_t *run, const fxRunInterval_t *interval ) {
	float i;
	fxActiveInterval_t	*active;
	const void *mathNext;

	active = fxGetActive( run->key, (unsigned int)interval, sizeof( fxActiveInterval_t) );
	if ( !active->beenUsed ) {
		mathNext = fxRunMath( run, interval + 1, &i );
		if ( i <= 0)
			goto returnIt;
		active->beenUsed = qtrue;
		fxRun( run, mathNext);
		active->nextTime = fx.time + i * 1000;
	} else while( active->nextTime <= fx.time ) {
		mathNext = fxRunMath( run, interval + 1, &i );
		if ( i <= 0)
			break;
		fxRun( run, mathNext);
		active->nextTime += i * 1000;
	}
returnIt:
	return ((byte *)interval) + interval->size;
 }

static const void *fxRunOnce( fxRun_t *run, const fxRunOnce_t *once ) {
	fxActiveOnce_t	*active;

	active = fxGetActive( run->key, (unsigned int)once, sizeof( fxActiveOnce_t) );
	if ( !active->beenUsed ) {
		active->beenUsed = qtrue;
		fxRun( run, once + 1);
	}
	return ((byte *)once) + once->size;
 }

static const void *fxRunPushParent( fxRun_t *run, const fxRunPushParent_t *push ) {
	int i;
	float *input = ( float *)(((char *)(run->parent)) + push->offset);
	if ( run->stackUsed + push->count > FX_STACK_SIZE ) {
		Com_Printf( "Overflowing run stack\n" );	
		return push + 1;
	}
	for (i = push->count; i > 0; i--) {
		run->stackData[run->stackUsed++] = *input++;
	}
	return push + 1;
}

static const void *fxRunPush( fxRun_t *run, const fxRunPush_t *push ) {
	int i;
	float *input = ( float *)(((char *)run) + push->offset);
	if ( run->stackUsed + push->count > FX_STACK_SIZE ) {
		Com_Printf( "Overflowing run stack\n" );	
		return push + 1;
	}
	for (i = push->count; i > 0; i--) {
		run->stackData[run->stackUsed++] = *input++;
	}
	return push + 1;
}

static const void *fxRunPop( fxRun_t *run, const fxRunPop_t *pop ) {
	int i;
	float *output = ( float *)(((char *)run) + pop->offset);
	if ( run->stackUsed - pop->count < 0) {
		Com_Printf( "underflowing run stack\n" );	
		return pop + 1;
	}
	for (i = pop->count; i > 0; i--) {
		output[i-1] = run->stackData[--run->stackUsed];	
	}
	return pop + 1;
}

static const void *fxRun( fxRun_t *run, const void *data );

static const void *fxRunScript( fxRun_t *run, const fxRunScript_t *script ) {
	fxRun( run, script->data );
	return script + 1;
}

static const void *fxRun( fxRun_t *run, const void *data ) {
	while ( 1 ) {
		const int *cmdList = (int *)data;
		int cmd = cmdList[0];		
		data = cmdList + 1;
		switch ( cmd ) {
		case fxCmdHalt:
			return data;
		default:
			Com_Error( ERR_DROP, "FX:Unknown command %d\n", cmd );
			break;
		case fxCmdSkip:
			data = fxRunSkip( run, (fxRunSkip_t*)data); 
			break;
		case fxCmdKill:
			if ( run->entity )
				run->entity->flags |= FX_ENT_DEAD;
			return data;
		case fxCmdEmitter:
			data = fxRunEmitter( run, (fxRunEmitter_t*)data);
			break;
		case fxCmdRepeat:
			data = fxRunRepeat( run, (fxRunRepeat_t*)data);
			break;
		case fxCmdDistance:
			data = fxRunDistance( run, (fxRunDistance_t*)data);
			break;
		case fxCmdInterval:
			data = fxRunInterval( run, (fxRunInterval_t*)data);
			break;
		case fxCmdIf:
			data = fxRunIf( run, (fxRunIf_t*)data);
			break;
		case fxCmdOnce:
			data = fxRunOnce( run, (fxRunOnce_t*)data);
			break;

		case fxCmdLight:
			fxRunLight( run );
			break;
		case fxCmdSprite:
			data = fxRunSprite( run, (fxRunRender_t*)data );
			break;
		case fxCmdQuad:
			data = fxRunQuad( run, (fxRunRender_t*)data );
			break;
		case fxCmdSpark:
			data = fxRunSpark( run, (fxRunRender_t*)data );
			break;
		case fxCmdBeam:
			data = fxRunBeam( run, (fxRunRender_t*)data );
			break;
		case fxCmdRings:
			data = fxRunRings( run, (fxRunRender_t*)data );
			break;
		case fxCmdAxisModel:
			data = fxRunAxisModel( run, (fxRunRender_t*)data );
			break;
		case fxCmdAnglesModel:
			data = fxRunAnglesModel( run, (fxRunRender_t*)data );
			break;
		case fxCmdDirModel:
			data = fxRunDirModel( run, (fxRunRender_t*)data );
			break;
		case fxCmdDecal:
			data = fxRunDecal( run, (fxRunDecal_t*)data );
			break;

		case fxCmdTrace:
			fxRunTrace( run );
			break;

		case fxCmdScript:
			data = fxRunScript( run, (fxRunScript_t*)data );
			break;
		case fxCmdColor:
			data = fxRunColor( run, (fxRunColor_t*)data); 
			break;
		case fxCmdColorList:
			data = fxRunColorList( run, (fxRunColorList_t*)data); 
			break;
		case fxCmdColorBlend:
			data = fxRunColorBlend( run, (fxRunColorBlend_t*)data); 
			break;
		case fxCmdColorMath:
			data = fxRunColorMath( run, (fxRunColorMath_t*)data);
			break;
		case fxCmdColorScale:
			data = fxRunColorScale( run, data);
			break;
		case fxCmdColorHue:
			data = fxRunColorHue( run, data);
			break;
		case fxCmdShader:
			data = fxRunShader( run, (fxRunShader_t*)data); 
			break;
		case fxCmdShaderList:
			data = fxRunShaderList( run, (fxRunShaderList_t*)data); 
			break;
		case fxCmdModel:
			data = fxRunModel( run, (fxRunModel_t*)data); 
			break;
		case fxCmdModelList:
			data = fxRunModelList( run, (fxRunModelList_t*)data); 
			break;
		case fxCmdSound:
			data = fxRunSound( run, (fxRunSound_t*)data); 
			break;
		case fxCmdSoundList:
			data = fxRunSoundList( run, (fxRunSoundList_t*)data); 
			break;
		case fxCmdLoopSound:
			data = fxRunLoopSound( run, (fxRunLoopSound_t*)data); 
			break;
		case fxCmdVibrate:
			data = fxRunVibrate( run, (fxRunVibrate_t*)data); 
			break;

		case fxCmdAlphaFade:
			data = fxRunAlphaFade( run, (fxRunAlphaFade_t*)data); 
			break;
		case fxCmdColorFade:
			data = fxRunColorFade( run, (fxRunColorFade_t*)data); 
			break;
		case fxCmdPush:
			data = fxRunPush( run, (fxRunPush_t*)data); 
			break;
		case fxCmdPop:
			data = fxRunPop( run, (fxRunPop_t*)data); 
			break;
		case fxCmdPushParent:
			data = fxRunPushParent( run, (fxRunPushParent_t*)data); 
			break;

		case fxCmdAdd:
			data = fxRunAdd( run, (fxRunAdd_t*)data);
			break;
		case fxCmdSub:
			data = fxRunSub( run, (fxRunSub_t*)data);
			break;
		case fxCmdAddScale:
			data = fxRunAddScale( run, (fxRunAddScale_t*)data);
			break;
		case fxCmdSubScale:
			data = fxRunSubScale( run, (fxRunSubScale_t*)data);
			break;
		case fxCmdRotateAround:
			data = fxRunRotateAround( run, (fxRunRotateAround_t*)data);
			break;
		case fxCmdScale:
			data = fxRunScale( run, (fxRunScale_t*)data);
			break;
		case fxCmdCopy:
			data = fxRunCopy( run, (fxRunCopy_t*)data);
			break;
		case fxCmdRandom:
			data = fxRunRandom( run, (fxRunRandom_t*)data);
			break;
		case fxCmdWobble:
			data = fxRunWobble( run, (fxRunWobble_t*)data);
			break;
		case fxCmdNormalize:
			data = fxRunNormalize( run, (fxRunNormalize_t*)data);
			break;
		case fxCmdPerpendicular:
			data = fxRunPerpendicular( run, (fxRunPerpendicular_t*)data);
			break;
		case fxCmdInverse:
			data = fxRunInverse( run, (fxRunInverse_t*)data);
			break;
		case fxCmdClear:
			data = fxRunClear( run, (fxRunClear_t*)data);
			break;
		case fxCmdValue:
			data = fxRunValue( run, (fxRunValue_t*)data );
			break;
		}
	}
	return data;
}

static fxEntity_t *fxRunEntities( fxEntity_t **nextEntity ) {
	fxEntity_t	*lastEntity;
	fxRun_t		run;

	lastEntity = 0;
	for (; nextEntity[0]; nextEntity = &nextEntity[0]->next ) {
		float deltaTime;
		float moveTime;
		int movePasses;
		const fxRunEmitter_t *emitter;
killSkip:
		run.entity = nextEntity[0];

		fx.last.entityCount++;
		fx.last.entitySize += fxSize( run.entity );

		deltaTime = (fx.time - run.entity->startTime) + fx.timeFraction;
		run.lerp = deltaTime * run.entity->lifeScale;
		if ( run.lerp >= 1.0f ) {
			run.entity->flags |= FX_ENT_DEAD;
		} else if ( deltaTime < 0 ) {
			lastEntity = run.entity;
			continue;
		}
		run.shaderTime = run.entity->startTime;
		run.life = deltaTime * 0.001;
		run.key = (unsigned int)run.entity;
		emitter = run.entity->emitter;
		run.stackUsed = 0;

		if ( !(emitter->flags & FX_EMIT_MOVE ) ) {
			VectorCopy( run.entity->origin, run.origin );
			VectorCopy( run.entity->velocity, run.velocity );
			goto runEntity; 
		} else if ( !(emitter->flags & FX_EMIT_TRACE ) ) {
			VectorMA( run.entity->origin, run.life, run.entity->velocity, run.origin );
			VectorCopy( run.entity->velocity, run.velocity );
		
			run.velocity[2] -= 2 * run.life * emitter->gravity;
			run.origin[2] -= run.life * run.life * emitter->gravity;
			goto runEntity; 
		} else if ( run.entity->flags & FX_ENT_STATIONARY ) {
			VectorCopy( run.entity->origin, run.origin );
			if ( emitter->flags & FX_EMIT_SINK && run.lerp > emitter->sinkDelay ) {
				run.origin[2] -= emitter->sinkDepth * ( run.lerp - emitter->sinkDelay );
			}
			VectorClear( run.velocity );
			goto runEntity; 
		}

		moveTime = run.life - run.entity->moveTime;
		//Still within the valid trace range just move it directly
		if( !moveTime || (moveTime < run.entity->traceTime )) {
traceOkay:
			VectorMA( run.entity->origin, moveTime, run.entity->velocity, run.origin );
			run.origin[2] -= moveTime * moveTime * emitter->gravity;
			VectorCopy( run.entity->velocity, run.velocity );
			run.velocity[2] -= 2 * moveTime * emitter->gravity;
			goto runEntity; 
		}

		//movepasses will go down fast incase stuff fails
		for( movePasses = 2; movePasses >= 0; movePasses-- ) {
			trace_t tr;
			vec3_t oldOrigin, oldVelocity, newOrigin;
			vec3_t traceMin, traceMax;
			float addTime;
	
			//Tracetime holds the time since the last update to the origin/velocity
			//Calculate the previous trace end position
			VectorMA( run.entity->origin, run.entity->traceTime, run.entity->velocity, oldOrigin );
			oldOrigin[2] -= run.entity->traceTime * run.entity->traceTime * emitter->gravity;
			VectorCopy( run.entity->velocity, oldVelocity );
			oldVelocity[2] -= 2 * run.entity->traceTime * emitter->gravity; 

			//Predict how far we can go from this position if there's any time left in the particle's life anyway
			addTime = 0.001 * movePasses * ( 30 + (rand() & 15 ) );

			//If the entity is still moving upwards check when upward speed will hit 0 if that takes longer that 2 milliseconds
			if ( oldVelocity[2] > 0 ) {
				float left = oldVelocity[2] / ( 2 * emitter->gravity );
				if ( left < addTime ) {
					addTime = left;
				}
				traceMin[2] = 0;
				traceMax[2] = addTime * addTime * emitter->gravity;
			} else {
				traceMin[2] = -addTime * addTime * emitter->gravity;
				traceMax[2] = 0;
			}
			traceMin[0] = traceMin[1] = -0.1f; 
			traceMin[2] -= 0.1f;
			traceMax[0] = traceMax[1] = 0.1f;
			traceMax[2] += 0.1f;
			//Check the end position of the trace without the last piece of gravity
			VectorMA( oldOrigin, addTime, oldVelocity, newOrigin );

			CM_BoxTrace( &tr, oldOrigin, newOrigin, traceMin, traceMax, 0, CONTENTS_SOLID, qfalse );
			if ( tr.allsolid || tr.fraction == 0 )
				break;
			if ( tr.fraction < 1 ) {
				addTime *= tr.fraction * 0.99;
			}
			run.entity->traceTime += addTime;
			//Have we moved into safe territory for now
			if ( run.entity->traceTime >= moveTime ) {
				goto traceOkay;
			}
			//Did we only add a very small piece go into regular trace mode
//			if ( addTime > 0.001f )
//				break;
		}

		//Can do a few passes if the particle bounces between a few surfaces
		for( movePasses = 2; movePasses >= 0; movePasses-- ) {
			vec3_t oldOrigin;
			trace_t tr;

			VectorMA( run.entity->origin, run.entity->traceTime, run.entity->velocity, oldOrigin );
			oldOrigin[2] -= run.entity->traceTime * run.entity->traceTime * emitter->gravity;

			VectorCopy( run.entity->origin, run.origin );
			VectorMA( run.entity->origin, moveTime, run.entity->velocity, run.origin );
			run.origin[2] -= moveTime * moveTime * emitter->gravity;

			VectorCopy( run.entity->velocity, run.velocity );
			run.velocity[2] -= 2 * moveTime * emitter->gravity;
	
			//Determine how far we can still go before we hit something
			CM_BoxTrace( &tr, oldOrigin, run.origin, vec3_origin, vec3_origin, 0, CONTENTS_SOLID, qtrue );
			if ( tr.allsolid || (tr.surfaceFlags & SURF_NOIMPACT) ) {
				/* Special case just don't do anything */
				goto killEntity;
			//Full trace succeeded
			} else if ( tr.fraction == 1 ) {
				goto runEntity;
			} else {
				if ( emitter->flags & FX_EMIT_IMPACT ) {
					run.entity->flags |= FX_ENT_DEAD;
					goto runEntity;
				}
				if ( emitter->impactRun && (VectorLengthSquared( run.velocity ) > emitter->impactSpeed )  ) {
					/* Run the optional code for the impact handler */
					VectorCopy( tr.endpos, run.origin );
					VectorCopy( tr.plane.normal, run.dir );
					fxRun( &run, ((char *)emitter) + emitter->impactRun );
				} 
				if ( emitter->bounceFactor > 0 ) {
					float bounceTime;
					float reflect;	
					float fractionTime = ( moveTime - run.entity->traceTime ) * tr.fraction;

					bounceTime = run.entity->traceTime + fractionTime;
					if ( tr.fraction ) {
						//Add back the velocity till the impact time
						run.velocity[2] += 2 * fractionTime * emitter->gravity;
						reflect = -2 * DotProduct( tr.plane.normal, run.velocity );
						VectorMA( run.velocity, reflect, tr.plane.normal, run.velocity );
						VectorScale( run.velocity, emitter->bounceFactor, run.velocity );
						VectorCopy( run.velocity, run.entity->velocity );
					}
					VectorCopy( tr.endpos, run.entity->origin );

					//Stop moving if it's on a vertical slope and speed is low */
					if ( ( tr.plane.normal[2] > 0 && VectorLengthSquared( run.velocity ) < 40 ) ) {
						run.entity->flags |= FX_ENT_STATIONARY;
						VectorClear( run.entity->velocity );
						goto runEntity;
					}
					//Move slightly back in time to prevent negative offsets
					run.entity->moveTime += bounceTime;
					moveTime = run.life - run.entity->moveTime;
					run.entity->traceTime = 0;
					continue;
				}
			}
		}
runEntity:
		fxMaskOutput( emitter->mask, run.entity + 1, run.velocity + 3 );
		if ( run.entity->flags & FX_ENT_DEAD ) {
			if ( emitter->deathRun )
				fxRun( &run, ((char *)emitter) + emitter->deathRun );
			goto killEntity;
		} else {
			fxRun( &run, ((char *)emitter) + emitter->emitRun );
		}
		lastEntity = run.entity;
		continue;
killEntity:
		nextEntity[0] = run.entity->next;
		fxFree( run.entity );
		if ( !nextEntity[0] )
			break;
		goto killSkip;
	}
	return lastEntity;
}

static void *fxGetActive( unsigned int key1, unsigned int key2, int size ) {
	fxActive_t *active;
	unsigned int index;

	index = ((key1 ^ key2) >> FX_ACTIVE_HASH_SHIFT) ^ key1 ^ key2;
	index &= FX_ACTIVE_HASH_MASK;
	active = fx.activeHash[ index ];
	while (1) {
		if (!active) {
			active = fxAlloc( sizeof( fxActive_t ) + size );
			if (!active)
				return 0;
			active->next = fx.activeHash[ index ];
			fx.activeHash[ index ] = active;

			active->key1 = key1;
			active->key2 = key2;
			memset( active + 1, 0, size );
			fx.last.activeCount++;
			fx.last.activeSize += fxSize( active );
			break;
		} else if (active->key1 == key1 && active->key2 == key2) {
			fx.last.activeCount++;
			fx.last.activeSize += fxSize( active );
			break;
		}
		active = active->next;
	}
	active->beenUsed = qtrue;
	return active + 1;
}


static void fxDebugDrawString( int x, int y, const char *line ) {
	int i;
	for (i = 0; i<40;i++) {
		if (!line[i])
			break;
		SCR_DrawSmallChar( x, y, line[i] );
		x += SMALLCHAR_WIDTH;
	}
}

static void fxDebugDraw( void ) {
	char buf[512];
	int x = cls.glconfig.vidWidth * 3 / 4;
	int y = cls.glconfig.vidHeight - SMALLCHAR_HEIGHT * 10 * 1.5;
	
	re.SetColor( colorWhite );

	Com_sprintf( buf, sizeof( buf ), "Entities %d, mem %dkb", fx.last.entityCount, fx.last.entitySize / -1024 );
	fxDebugDrawString( x, y, buf );
	y += SMALLCHAR_HEIGHT * 1.5;
	Com_sprintf( buf, sizeof( buf ), "Active %d, mem %dkb", fx.last.activeCount, fx.last.activeSize / -1024 );
	fxDebugDrawString( x, y, buf );
	y += SMALLCHAR_HEIGHT * 1.5;

	re.SetColor( NULL );
}

void FX_Debug( void ) {
	if ( fx_Debug->integer )
		fxDebugDraw();
}

void FX_Reset( void ) {
	int *lastAlloc;

	memset( fx.activeHash, 0, sizeof( fx.activeHash ));
	fx.entityActive = 0;
	fx.entityNew = 0;
	fx.vibrate.time = 0;
	fx.vibrate.used = 0;
	/* Create the terminator block */
	fx.allocStart[0] = fx.allocSize - sizeof( int );
	fx.allocSearch = fx.allocStart;
	lastAlloc = (int *)(((char *)fx.allocStart) + fx.allocSize - sizeof( int ));
	lastAlloc[0] = 0;
}

void FX_Begin( int time, float timeFraction ) {
	fx.deltaTime = fx.time - fx.oldTime;
	fx.oldTime = fx.time;
	fx.time = time;
	fx.timeFraction = timeFraction;
	if ( fx.deltaTime < 0 || fx.deltaTime > 50) {
		fx.oldTime = fx.time;
		fx.deltaTime = 0;
	}
	fx.seed = time;
	/* Clear some stats */
	fx.last.activeCount = 0;
	fx.last.entityCount = 0;
	fx.last.entitySize = 0;
	fx.last.activeSize = 0;
	/* Run the currently active entities */
	fxRunEntities( &fx.entityActive );
}

void FX_End( void ) {
	int i;
	int loops = 5;
	/* Run all the new entities and loop a few times for recursive entities */
	while ( loops>0 && fx.entityNew ) {
		fxEntity_t *newEntities, *lastEntity;
		newEntities = fx.entityNew;
		fx.entityNew = 0;
		lastEntity = fxRunEntities( &newEntities );
		if ( lastEntity ) {
			lastEntity->next = fx.entityActive;
			fx.entityActive = newEntities;
		}
		loops --;
	}
	/* Go through the active list and set all to false */
	for( i = 0; i < FX_ACTIVE_HASH_SIZE; i++) {
		fxActive_t **activeList = &fx.activeHash[i];
		while ( activeList[0] ) {
			fxActive_t *active = activeList[0];
			if ( active->beenUsed ) {
				active->beenUsed = qfalse;
				if (!active->next)
					break;
				activeList = &active->next;
				continue;
			}
			activeList[0] = active->next;
			fxFree( active );
		}
	}
}

void FX_Run( fxHandle_t handle, const fxParent_t *parent, unsigned int key ) {
	fxRun_t run;
	const fxScript_t *script;

	if (handle <= 0 || handle > fx.scriptCount || !parent )
		return;
	script = fx.scripts[handle];

	//Check for illegal readmasks for undefined values
	if ( fx_Debug->integer ) {
		int debug = fx_Debug->integer;
		char warning[256];
		warning[0] = 0;
		if ( (script->readMask & FX_MASK_ORIGIN) && !(parent->flags & FXP_ORIGIN ) ) {
			Q_strcat( warning, sizeof( warning ), "origin, " );
		}
		if ( (script->readMask & FX_MASK_VELOCITY) && !(parent->flags & FXP_VELOCITY ) ) {
			Q_strcat( warning, sizeof( warning ), "velocity, " );
		}
		if ( (script->readMask & FX_MASK_DIR) && !(parent->flags & FXP_DIR ) ) {
			Q_strcat( warning, sizeof( warning ), "dir, " );
		}
		if ( (script->readMask & FX_MASK_ANGLES) && !(parent->flags & FXP_ANGLES ) ) {
			Q_strcat( warning, sizeof( warning ), "angles, " );
		}
		if ( debug > 1 && (script->readMask & FX_MASK_SIZE) && !(parent->flags & FXP_SIZE ) ) {
			Q_strcat( warning, sizeof( warning ), "size, " );
		}
		if ( (script->readMask & FX_MASK_WIDTH) && !(parent->flags & FXP_WIDTH ) ) {
			Q_strcat( warning, sizeof( warning ), "width, " );
		}
		if ( debug > 1 && (script->readMask & FX_MASK_SHADER) && !(parent->flags & FXP_SHADER ) ) {
			Q_strcat( warning, sizeof( warning ), "shader, " );
		}
		if ( (script->readMask & FX_MASK_MODEL) && !(parent->flags & FXP_MODEL ) ) {
			Q_strcat( warning, sizeof( warning ), "model, " );
		}
		if ( (script->readMask & FX_MASK_AXIS) && !(parent->flags & FXP_AXIS ) ) {
			Q_strcat( warning, sizeof( warning ), "axis, " );
		}
		if ( warning[0] ) {
			Com_Printf("FX_Debug:script '%s' requires %s which aren't supplied by caller\n", script->entry->name, warning );
		}
	}
	VectorCopy( parent->origin, run.origin );
	if ( parent->flags & FXP_VELOCITY)
		VectorCopy( parent->velocity, run.velocity );
	else
		VectorClear( run.velocity );
	if ( parent->flags & FXP_DIR ) 
		VectorCopy( parent->dir, run.dir );
	if ( parent->flags & FXP_ANGLES ) {
		VectorCopy( parent->angles, run.angles );
		run.rotate = run.angles[ROLL];
	} else {
		VectorClear( run.angles );
		run.rotate = 0;
	}
	if ( parent->flags & FXP_AXIS ) 
		AxisCopy( parent->axis, run.axis );

	*(int *)run.color = ( parent->flags & FXP_COLOR ) ? *(int *)parent->color : 0xffffffff;
	run.shader = ( parent->flags & FXP_SHADER ) ? parent->shader : 0;
	run.model = ( parent->flags & FXP_MODEL ) ? parent->model : 0;
	run.size = ( parent->flags & FXP_SIZE ) ? parent->size : 1.0f;
	run.width = ( parent->flags & FXP_WIDTH ) ? parent->width : 1.0f;

	run.parent = parent;
	run.shaderTime = 0;
	run.key = key;
	run.stackUsed = 0;
	run.entity = 0;
	fxRun( &run, script->data );
}


static void fxList( void ) {
	const fxScript_t *script;
	int i;

	if ( fx.scriptCount <= 1) {
		Com_Printf("No fx scripts loaded\n");
		return;
	}
	Com_Printf ( "-----------------------\n" );
	for ( i = 1; i<fx.scriptCount;i++ ) {
		script = fx.scripts[i];
		Com_Printf( "%s", script->entry->name );
		if ( script->remap ) {
			Com_Printf( " remap to %s", script->remap->entry->name );
		}
		Com_Printf( "\n");
	}
	Com_Printf( "%d Total scripts\n", fx.scriptCount - 1);
	Com_Printf ( "-----------------------\n" );
}

static void fxStats( void ) {
	int *alloc = fx.allocStart;
	int allocSize, allocBlocks;
	int freeSize, freeBlocks;

	allocSize = allocBlocks = 0;
	freeSize = freeBlocks = 0;

	while ( 1 ) {
		int size = alloc[0];
		if ( size < 0 ) {
			size = -size;
			allocSize += size;
			allocBlocks++;
		} else if ( size > 0 ) {
			freeSize += size;
			freeBlocks++;
		} else
			break;
		alloc = (int*)(((char *)alloc) + size);
	}

	Com_Printf( "FX Stats:\n" );
	Com_Printf( "Free %d in %d blocks\n", freeSize, freeBlocks );
	Com_Printf( "Allocated %d in %d blocks\n", allocSize, allocBlocks );
}

void FX_Init( void ) {
	Com_Memset( &fx, 0, sizeof( fx ));

	fx_Megs = Cvar_Get( "fx_Megs", "2", CVAR_ARCHIVE | CVAR_LATCH );
	fx_Vibrate = Cvar_Get( "fx_Vibrate", "1", CVAR_ARCHIVE );
	fx_Override = Cvar_Get( "fx_Override", "", CVAR_ARCHIVE );
	fx_Debug = Cvar_Get( "fx_Debug", "0", CVAR_CHEAT );
	fx.allocSize = fx_Megs->integer;
	if ( fx.allocSize < 1)
		fx.allocSize = 1;
	else if (fx.allocSize > 8) {
		fx.allocSize = 8;
	}
	fx.allocSize *= 1024*1024;
	fx.allocStart = Hunk_Alloc( fx.allocSize, h_low );
	FX_Reset( );
	Cmd_AddCommand( "fxStats", fxStats );
	Cmd_AddCommand( "fxList", fxList );
	Cmd_AddCommand( "fxReset", FX_Reset );

	fxInitParser();
}

void FX_Shutdown( void ) {
	Cmd_RemoveCommand( "fxStats" );
	Cmd_RemoveCommand( "fxReset" );
	Cmd_RemoveCommand( "fxRemap" );
	Cmd_RemoveCommand( "fxMath" );
	Cmd_RemoveCommand( "fxReload" );
	if ( fx.alloc.entries )
		fxFreeMemory();
}