/*
Copyright (C) 2002-2003, Dr Labman, A. Nourai

This program 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.

This program 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 this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.

*/
// gl_rpart.c

#include "quakedef.h"

//#define	DEFAULT_NUM_PARTICLES		8192
#define	ABSOLUTE_MIN_PARTICLES		256
#define	ABSOLUTE_MAX_PARTICLES		32768

typedef	byte	col_t[4];

typedef	enum 
{
	p_spark, 
	p_smoke, 
	p_fire, 
	p_fire2, 
	p_bubble, 
	p_lavasplash, 
	p_gunblast, 
	p_chunk, 
	p_shockwave,
	p_inferno_flame, 
	p_inferno_trail, 
	p_sparkray, 
	p_staticbubble, 
	p_trailpart,
	p_dpsmoke, 
	p_dpfire, 
	p_teleflare, 
	p_blood1, 
	p_blood2, 
	p_blood3,
	p_bloodcloud,
	p_flame, 
	p_lavatrail, 
	p_bubble2, 
	p_rain,
	p_streak, 
	p_streaktrail, 
	p_streakwave, 
	p_lightningbeam,
	p_glow, 
	p_alphatrail,//R00k
	p_torch_flame,
	p_flare,
	p_dot,
	p_muzzleflash,
	p_muzzleflash8,
	p_fly,
	num_particletypes
} part_type_t;

typedef	enum 
{
	pm_static, 
	pm_normal,
	pm_bounce,
	pm_die,
	pm_nophysics,
	pm_float,
	pm_rain,
	pm_streak,
	pm_streakwave,
} part_move_t;

typedef	enum 
{
	ptex_none,
	ptex_smoke,
	ptex_bubble,
	ptex_generic,
	ptex_dpsmoke,
	ptex_lava,
	ptex_blueflare,
	ptex_blood1,
	ptex_blood2,
	ptex_blood3,
	ptex_lightning,
	ptex_flame,
	ptex_muzzleflash,
	ptex_muzzleflash8,
	ptex_bloodcloud,
	ptex_fly,
	num_particletextures
} part_tex_t;

typedef	enum 
{
	pd_spark,
	pd_sparkray,
	pd_billboard,
	pd_hide,
	pd_beam,
} part_draw_t;

typedef struct particle_s 
{
	struct	particle_s	*next;
	vec3_t				org, endorg;
	col_t				color;
	float				growth;
	vec3_t				vel;
	float				rotangle;
	float				rotspeed;
	float				size;
	float				start;
	float				die;
	byte				hit;
	byte				texindex;
	byte				bounces;
} particle_t;

typedef	struct particle_type_s 
{
	particle_t			*start;		
	part_type_t			id;
	part_draw_t			drawtype;
	int					SrcBlend;
	int					DstBlend;
	part_tex_t			texture;
	float				startalpha;
	float				grav;
	float				accel;
	part_move_t 		move;
	float				custom;	
} particle_type_t;

#define	MAX_PTEX_COMPONENTS		8

typedef struct particle_texture_s 
{
	int		texnum;
	int		components;
	float	coords[MAX_PTEX_COMPONENTS][4];
} particle_texture_t;

static	float	sint[7] = {0.000000, 0.781832, 0.974928, 0.433884, -0.433884, -0.974928, -0.781832};
static	float	cost[7] = {1.000000, 0.623490, -0.222521, -0.900969, -0.900969, -0.222521, 0.623490};

static			particle_t			*particles, *free_particles;
static			particle_type_t		particle_types[num_particletypes];//R00k
static	int		particle_type_index[num_particletypes];	
static			particle_texture_t	particle_textures[num_particletextures];

float			varray_vertex[16];//Vult
void			R_CalcBeamVerts (float *vert, vec3_t org1, vec3_t org2, float width);//Vult

vec3_t	NULLVEC = {0,0,0};//r00k

static	int		r_numparticles;		
static	vec3_t	zerodir = {22, 22, 22};
static	int		particle_count = 0;
static	float	particle_time;
qboolean		qmb_initialized = false;
int				particle_mode = 0;	// 0: classic (default), 1: QMB, 2: mixed

qboolean OnChange_gl_particle_count (cvar_t *var, char *string)
{
	float	f;

	f = bound(ABSOLUTE_MIN_PARTICLES, (atoi(string)), ABSOLUTE_MAX_PARTICLES);
	Cvar_SetValue("gl_particle_count", f);

	QMB_ClearParticles ();		// also re-allocc particles
	
	return true;
}

extern cvar_t	cl_gun_offset;

cvar_t	gl_particle_count	= {"gl_particle_count", "8192", true, false, OnChange_gl_particle_count};
cvar_t	gl_bounceparticles	= {"gl_bounceparticles", "1",true};
cvar_t	gl_flametype		= {"gl_flametype", "0",true};

void R_CalcBeamVerts (float *vert, vec3_t org1, vec3_t org2, float width);

qboolean TraceLineN (vec3_t start, vec3_t end, vec3_t impact, vec3_t normal)
{
	trace_t	trace;

	memset (&trace, 0, sizeof(trace));
	trace.fraction = 1;
	if (SV_RecursiveHullCheck(cl.worldmodel->hulls, 0, 0, 1, start, end, &trace))
	{
		return false;
	}
	VectorCopy (trace.endpos, impact);

	if (normal)
		VectorCopy (trace.plane.normal, normal);

	return true;
}

//Decals -- START
//R00k decals, round 2....
/*
Penta:
Adds decal support...
Decals are similar to particles currently...
The code to generate them is based on the article "Applying decals to arbitrary surfaces"
by "Eric Lengyel" in "Game Programming Gems 2"
*/

#define RandomMinMax(MIN,MAX) ((rand() & 32767) * (((MAX)-(MIN)) * (1.0f / 32767.0f)) + (MIN))
#define DEFAULT_NUM_DECALS		"1024"
#define ABSOLUTE_MIN_DECALS		256
#define ABSOLUTE_MAX_DECALS		32768
#define MAX_DECAL_VERTICES		128
#define MAX_DECAL_TRIANGLES		64

extern void R_ClearDecals (void);

qboolean OnChange_gl_decals (cvar_t * var, char *value);
cvar_t  gl_decals			= {"gl_decals", "1", true, false, OnChange_gl_decals };
qboolean OnChange_gl_decal_count (cvar_t *var, char *string);
cvar_t  gl_decal_count		= {"gl_decal_count", DEFAULT_NUM_DECALS, true, false, OnChange_gl_decal_count};
cvar_t	gl_decal_blood		= {"gl_decal_blood", "1",true};
cvar_t	gl_decal_bullets	= {"gl_decal_bullets","1",true};
cvar_t	gl_decal_sparks		= {"gl_decal_sparks","1",true};
cvar_t	gl_decal_explosions	= {"gl_decal_explosions","1",true};
cvar_t	gl_decal_quality   	= {"gl_decal_quality","0",true};

cvar_t	gl_decaltime		= {"gl_decaltime", "30",true};
cvar_t	gl_decal_viewdistance = {"gl_decal_viewdistance","1280",true};
int		decals_enabled		= 0;

qboolean OnChange_gl_decal_count (cvar_t *var, char *string)
{
	float	f;

	f = bound(ABSOLUTE_MIN_DECALS, (atof(string)), ABSOLUTE_MAX_DECALS);
	Cvar_SetValue("gl_decal_count", f);

	R_ClearDecals ();	
	
	return true;
}

qboolean OnChange_gl_decals (cvar_t * var, char *value) 
{
    float val = Q_atof (value);

	decals_enabled = val;

	Cvar_SetValue ("gl_decal_bullets", val);
	Cvar_SetValue ("gl_decal_explosions", val);
	Cvar_SetValue ("gl_decal_sparks", val);
	Cvar_SetValue ("gl_decal_blood", val);
    
	return true;
}

typedef struct decal_s
{
	float		contents;
	vec3_t		origin;
	vec3_t		normal;
	vec3_t		tangent;
	float		radius;

	struct decal_s	*next;
	float		die;
	float		starttime;

	int			srcblend;
	int			dstblend;

	int			texture;
	float		alpha;

	//geometry of decal
	int			vertexCount, triangleCount;
	vec3_t		vertexArray[MAX_DECAL_VERTICES];
	float		texcoordArray[MAX_DECAL_VERTICES][2];
	int			triangleArray[MAX_DECAL_TRIANGLES][3];
} decal_t;

decal_t	*active_decals, *free_decals;
decal_t	*decals;
int		r_max_decals;

void DecalClipLeaf(decal_t *dec, mleaf_t *leaf);
void DecalWalkBsp_R(decal_t *dec, mnode_t *node);
int DecalClipPolygonAgainstPlane(plane_t *plane, int vertexCount, vec3_t *vertex, vec3_t *newVertex);
int DecalClipPolygon(int vertexCount, vec3_t *vertices, vec3_t *newVertex);

void R_AllocDecals (void) 
{
	extern cvar_t gl_decal_count;

	r_max_decals = bound(ABSOLUTE_MIN_DECALS, gl_decal_count.value, ABSOLUTE_MAX_DECALS);

	if (decals || r_max_decals < 1)
		Sys_Error("R_AllocDecals: internal error");

	decals = (decal_t *) Q_malloc (r_max_decals * sizeof(decal_t));
	GL_RegisterVerts (r_max_decals);
}

void R_InitDecals (void)
{
	Cvar_RegisterVariable (&gl_decals);
	Cvar_RegisterVariable (&gl_decaltime);
	Cvar_RegisterVariable (&gl_decal_count);
	Cvar_RegisterVariable (&gl_decal_blood);	
	Cvar_RegisterVariable (&gl_decal_bullets);
	Cvar_RegisterVariable (&gl_decal_sparks);
	Cvar_RegisterVariable (&gl_decal_explosions);
	Cvar_RegisterVariable (&gl_decal_viewdistance);
	Cvar_RegisterVariable (&gl_decal_quality);

	R_AllocDecals();
}

void R_ClearDecals (void)
{
	int		i;

	if (!qmb_initialized)
		return;

	Q_free (decals);	// free
	R_AllocDecals ();	// and alloc again

	free_decals = &decals[0];
	active_decals = NULL;

	for (i=0 ;i + 1 < r_max_decals ; i++)
		decals[i].next = &decals[i + 1];

	decals[r_max_decals - 1].next = NULL;	
}

//--------------------------------------------------------------------------------------------->
static plane_t leftPlane, rightPlane, bottomPlane, topPlane, backPlane, frontPlane;

void R_SpawnDecal(vec3_t center, vec3_t normal, vec3_t tangent, int tex, int size, float dtime, float dalpha)
{
	float	width, height, depth, d, one_over_w, one_over_h;
	vec3_t	binormal, decaldist, test = {0.5, 0.5, 0.5};
	decal_t	*dec, *kill;
	int		a, i;
	
	if (!qmb_initialized)
		return;

	if ((!decals_enabled) || (!particle_mode))
		return;

	//allocate decal
	if (!free_decals)
	{
		return;
	}

	if (tex != decal_mark)//Only draw bulletholes underwater...
	{
		if (ISUNDERWATER(TruePointContents(center)))
			return;
	}

	//R00k added distance checking, default 1024			
	VectorSubtract(r_refdef.vieworg, center, decaldist);		

	if (VectorLength(decaldist) > gl_decal_viewdistance.value)
		return;

//R00k: remove oldest decal from list if max decal limit hit

	i = 1;
	kill = active_decals;

	while ((i < r_max_decals - 1)&&(kill))
	{
		i++;
		kill = kill->next;		
	}
	
	if (kill && (i == r_max_decals - 1))
	{
		kill->die = cl.time - 1;
	}

	dec = free_decals;
	free_decals = dec->next;
	dec->next = active_decals;
	active_decals = dec;

	VectorNormalize(test);
	CrossProduct(normal,test,tangent);

	VectorCopy(center, dec->origin);
	VectorCopy(tangent, dec->tangent);
	VectorCopy(normal, dec->normal);
	
	VectorNormalize(tangent);
	VectorNormalize(normal);
	CrossProduct(normal, tangent, binormal);
	VectorNormalize(binormal);	
	
	//R00k: if negative size then it's fixed, otherwise its random between 50%-100%
	if (size >= 1)
		width = RandomMinMax(size*0.5, size);
	else
		width = size / -1.0;

	height = width;
	depth = width*0.5;
	dec->radius = max(max(width,height),depth);
	dec->starttime = cl.time;
	
	if (dtime)
		dec->die = cl.time + dtime;
	else
		dec->die = cl.time + gl_decaltime.value;

	dec->texture = tex;
	dec->alpha = dalpha;

	// Calculate boundary planes
	d = DotProduct(center, tangent);
	VectorCopy(tangent,leftPlane.normal);
	leftPlane.dist = -(width * 0.5 - d);
	VectorInverse(tangent);
	VectorCopy(tangent,rightPlane.normal);
	VectorInverse(tangent);
	rightPlane.dist = -(width * 0.5 + d);
	
	d = DotProduct(center, binormal);
	VectorCopy(binormal,bottomPlane.normal);
	bottomPlane.dist = -(height * 0.5 - d);
	VectorInverse(binormal);
	VectorCopy(binormal,topPlane.normal);
	VectorInverse(binormal);
	topPlane.dist = -(height * 0.5 + d);
	
	d = DotProduct(center, normal);
	VectorCopy(normal,backPlane.normal);
	backPlane.dist = -(depth - d);
	VectorInverse(normal);
	VectorCopy(normal,frontPlane.normal);
	VectorInverse(normal);
	frontPlane.dist = -(depth + d);

	// Begin with empty mesh
	dec->vertexCount = 0;
	dec->triangleCount = 0;
	
	//Clip decal to bsp
	DecalWalkBsp_R(dec, cl.worldmodel->nodes);

	//This happens when a decal is to far from any surface or the surface is to steeply sloped
	if (dec->triangleCount == 0) 
	{
		//deallocate decal
		active_decals = dec->next;
		dec->next = free_decals;
		free_decals = dec;	
		return;
	}

	//Assign texture mapping coordinates
	one_over_w  = 1.0F / width;
	one_over_h = 1.0F / height;
	for (a = 0; a < dec->vertexCount; a++)
	{
		float s, t;
		vec3_t v;
		VectorSubtract(dec->vertexArray[a],center,v);
		s = DotProduct(v, tangent) * one_over_w + 0.5F;
		t = DotProduct(v, binormal) * one_over_h + 0.5F;
		dec->texcoordArray[a][0] = s;
		dec->texcoordArray[a][1] = t;
	}
}

void R_SpawnDecalStatic(vec3_t org, int tex, int size)
{
	int i;
	float bestfrac, bestorg[3], bestnormal[3];
	float frac, v[3], normal[3], org2[3];
	vec3_t tangent;

	if (!qmb_initialized)
		return;

	if ((!decals_enabled) || (!particle_mode))
		return;

	if (!free_decals)
	{
		return;
	}	

	if (tex != decal_mark)//Only draw bulletholes underwater...
	{
		if (ISUNDERWATER(TruePointContents(org)))
			return;
	}

	bestorg[0] = bestorg[1] = bestorg[2] = 0;
	bestnormal[0] = bestnormal[1] = bestnormal[2] = 0;

	bestfrac = 10;

	for (i = 0;i < 32;i++)
	{
		VectorRandom(org2);
		VectorMA(org, 20, org2, org2);
		TraceLineN(org, org2, v, normal);
		frac = 0.5;

		if (bestfrac > frac)
		{
			bestfrac = frac;
			VectorCopy(v, bestorg);
			VectorCopy(normal, bestnormal);
			CrossProduct(normal,bestnormal,tangent);
		}

		if (bestnormal && bestorg)
			continue;
		else
		{
			VectorNegate(org2, org2);
			VectorMA(org, 20, org2, org2);
			TraceLineN(org, org2, v, normal);
			VectorCopy(v, bestorg);
			VectorCopy(normal, bestnormal);
			CrossProduct(normal,bestnormal,tangent);
		} 
		if (bestnormal && bestorg)
			continue;
	}

	if (bestfrac < 1)
	{
		R_SpawnDecal(bestorg, bestnormal, tangent, tex, size, 0, 0);
	}
}

void DecalWalkBsp_R(decal_t *dec, mnode_t *node)
{
	mplane_t	*plane;
	float		dist;
	mleaf_t		*leaf;

	if (node->contents < 0) 
	{
		//we are in a leaf
		leaf = (mleaf_t *)node;
		DecalClipLeaf(dec, leaf);
		return;
	}

	plane = node->plane;
	dist = DotProduct (dec->origin, plane->normal) - plane->dist;
	
	if (dist > dec->radius)
	{
		DecalWalkBsp_R (dec, node->children[0]);
		return;
	}
	if (dist < -dec->radius)
	{
		DecalWalkBsp_R (dec, node->children[1]);
		return;
	}

	DecalWalkBsp_R (dec, node->children[0]);
	DecalWalkBsp_R (dec, node->children[1]);
}

qboolean DecalAddPolygon(decal_t *dec, int vertcount, vec3_t *vertices)
{
	int *triangle;
	int count;
	int a, b;

	//Con_Printf("AddPolygon %i %i\n",vertcount, dec->vertexCount);
	count = dec->vertexCount;
	if (count + vertcount >= MAX_DECAL_VERTICES)
		return false;
	
	if (dec->triangleCount + vertcount-2 >= MAX_DECAL_TRIANGLES)
		return false;

	// Add polygon as a triangle fan
	triangle = &dec->triangleArray[dec->triangleCount][0];
	for (a = 2; a < vertcount; a++)
	{
		dec->triangleArray[dec->triangleCount][0] = count;
		dec->triangleArray[dec->triangleCount][1] = (count + a - 1);
		dec->triangleArray[dec->triangleCount][2] = (count + a );
		dec->triangleCount++;
	}
	
	// Assign vertex colors
	for (b = 0; b < vertcount; b++)
	{
		VectorCopy(vertices[b],dec->vertexArray[count]);
		count++;
	}
	
	dec->vertexCount = count;
	return true;
}

const double decalEpsilon = 0.001f;

void DecalClipLeaf(decal_t *dec, mleaf_t *leaf)
{
	vec3_t		newVertex[64], t3;
	int	c;
	msurface_t **surf;

	//Con_Printf("Clipleaf\n");
	c = leaf->nummarksurfaces;
	surf = leaf->firstmarksurface;

	//for all surfaces in the leaf
	for (c=0; c<leaf->nummarksurfaces; c++, surf++) 
	{
		glpoly_t *poly;
		int	i,count;
		float *v;
		poly = (*surf)->polys;

		v = poly->verts[0]; //Omikron - holyshit took a while to figure this one out
	
		for (i=0 ; i<poly->numverts ; i++, v+= VERTEXSIZE)
		{
			newVertex[i][0] = v[0];
			newVertex[i][1] = v[1];
			newVertex[i][2] = v[2];
		}

		VectorCopy((*surf)->plane->normal,t3);		
		
		if ((*surf)->flags & SURF_PLANEBACK) 
		{
			VectorInverse(t3);
		}
		
		if ((*surf)->flags & SURF_DRAWSKY)//R00k dont draw decals on sky
		{
			dec->die = cl.time - 1;
		}
		
		if ((*surf)->texinfo->texture->name[0] =='+')//R00k: Dont draw on animated textures.
		{
			dec->die = cl.time - 1;
		}

		if ((*surf)->flags & SURF_DRAWTURB)
		{
			dec->die = cl.time - 1;
		}

		//avoid backfacing and orthogonal facing faces to recieve decal parts
		if (DotProduct(dec->normal, t3) > decalEpsilon)
		{
			count = DecalClipPolygon(poly->numverts, newVertex, newVertex);
			if ((count != 0) && (!DecalAddPolygon(dec, count, newVertex))) break;
		}
	}
}

int DecalClipPolygon(int vertexCount, vec3_t *vertices, vec3_t *newVertex)
{
	vec3_t		tempVertex[64];
	
	// Clip against all six planes
	int count = DecalClipPolygonAgainstPlane(&leftPlane, vertexCount, vertices, tempVertex);
	if (count != 0)
	{
		count = DecalClipPolygonAgainstPlane(&rightPlane, count, tempVertex, newVertex);
		if (count != 0)
		{
			count = DecalClipPolygonAgainstPlane(&bottomPlane, count, newVertex, tempVertex);
			if (count != 0)
			{
				count = DecalClipPolygonAgainstPlane(&topPlane, count, tempVertex, newVertex);
				if (count != 0)
				{
					count = DecalClipPolygonAgainstPlane(&backPlane, count, newVertex, tempVertex);
					if (count != 0)
					{
						count = DecalClipPolygonAgainstPlane(&frontPlane, count, tempVertex, newVertex);
					}
				}
			}
		}
	}
	
	return count;
}

int DecalClipPolygonAgainstPlane(plane_t *plane, int vertexCount, vec3_t *vertex, vec3_t *newVertex)
{
	qboolean	negative[65];
	int	a, count, b, c;
	float t;
	vec3_t v1, v2;

	// Classify vertices
	int negativeCount = 0;
	
	for (a = 0; a < vertexCount; a++)
	{
		qboolean neg = ((DotProduct(plane->normal, vertex[a]) - plane->dist) < 0.0);
		negative[a] = neg;
		negativeCount += neg;
	}
	
	// Discard this polygon if it's completely culled
	if (negativeCount == vertexCount)
	{
		return (0);
	}

	count = 0;
	
	for (b = 0; b < vertexCount; b++)
	{
		// c is the index of the previous vertex
		c = (b != 0) ? b - 1 : vertexCount - 1;
		
		if (negative[b])
		{
			if (!negative[c])
			{
				// Current vertex is on negative side of plane,
				// but previous vertex is on positive side.
				VectorCopy(vertex[c],v1);
				VectorCopy(vertex[b],v2);

				t = (DotProduct(plane->normal, v1) - plane->dist) / (plane->normal[0] * (v1[0] - v2[0])	+ plane->normal[1] * (v1[1] - v2[1]) + plane->normal[2] * (v1[2] - v2[2]));
				
				VectorScale(v1,(1.0 - t),newVertex[count]);
				VectorMA(newVertex[count],t,v2,newVertex[count]);
				
				count++;
			}
		}
		else
		{
			if (negative[c])
			{
				// Current vertex is on positive side of plane,
				// but previous vertex is on negative side.
				VectorCopy(vertex[b],v1);
				VectorCopy(vertex[c],v2);

				t = (DotProduct(plane->normal, v1) - plane->dist) / (plane->normal[0] * (v1[0] - v2[0])+ plane->normal[1] * (v1[1] - v2[1])+ plane->normal[2] * (v1[2] - v2[2]));
				
				VectorScale(v1,(1.0 - t),newVertex[count]);
				VectorMA(newVertex[count],t,v2,newVertex[count]);
				
				count++;
			}
			
			// Include current vertex
			VectorCopy(vertex[b],newVertex[count]);
			count++;
		}
	}
	
	// Return number of vertices in clipped polygon
	return count;
}

extern	cvar_t	sv_gravity;

void R_DrawDecals (void)
{
	decal_t		*p, *kill;
	int			i;
	vec3_t		decaldist;
	float		dcolor, scale;

	if (!qmb_initialized)
		return;

	if ((!decals_enabled) || (!particle_mode))
		return;

	if (gl_fogenable.value)
		glDisable(GL_FOG);

	glEnable (GL_BLEND);
	glEnable (GL_ALPHA_TEST);
	glAlphaFunc(GL_GREATER,0.000f);
	glBlendFunc(GL_ZERO, GL_ONE_MINUS_SRC_COLOR);
	glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
	
	if (gl_decal_quality.value)
	{
		glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
		glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);	  
	}

	glDepthMask(GL_FALSE);
	
	vaEnableVertexArray (3);

	vaEnableTexCoordArray (GL_TEXTURE0, VA_TEXTURE0, 2);
		
	GL_PolygonOffset (-3);

	for ( ;; ) 
	{
		kill = active_decals;

		if (kill && kill->die < cl.time)
		{
			active_decals = kill->next;
			kill->next = free_decals;
			free_decals = kill;
			continue;
		}
		break;
	}

	for (p=active_decals ; p ; p=p->next)
	{
		for ( ;; )
		{
			kill = p->next;
			//XYZ
			if (kill && (kill->die < cl.time))
			{
				p->next = kill->next;
				kill->next = free_decals;
				free_decals = kill;
				continue;
			}
			break;
		}

		//R00k added distance checking, default 1024			
		VectorSubtract(r_refdef.vieworg,p->origin, decaldist);		

		if (VectorLength(decaldist) > gl_decal_viewdistance.value)
			continue;
		
		GL_Bind(p->texture);
	
		dcolor = (1 - (VectorLength(decaldist) / gl_decal_viewdistance.value));		

		if (p->alpha)
			dcolor *= p->alpha;

		if (r_novis.value > 0)
		{
			if ((TruePointContents(p->origin)) != (TruePointContents(r_refdef.vieworg)))//R00k: different waterlevels of the 2 points..
				dcolor *= (1 - r_wateralpha.value);
		}

		scale = p->die - cl.time;

//		glPushMatrix ();

		if ((p->die - cl.time) < ((p->die/2)))
		{
			glColor4f((dcolor*scale), (dcolor*scale), (dcolor*scale), scale);
		} 
		else 
		{	
			if ((p->texture == decal_blood1)||(p->texture == decal_blood2)||(p->texture == decal_blood3))
				glColor4f(dcolor,dcolor,dcolor,0.8);
			else
				glColor4f(dcolor,dcolor,dcolor,dcolor);
		}

		//Con_Printf("Drawdec %i\n",p->triangleCount);
		for (i=0; i<p->triangleCount; i++) 
		{
			int i1, i2, i3;
			i1 = p->triangleArray[i][0];
			i2 = p->triangleArray[i][1];
			i3 = p->triangleArray[i][2];

			vaBegin(GL_TRIANGLES);
			vaTexCoord2fv(&p->texcoordArray[i1][0]);
			vaVertex3fv(&p->vertexArray[i1][0]);

			vaTexCoord2fv(&p->texcoordArray[i2][0]);
			vaVertex3fv(&p->vertexArray[i2][0]);

			vaTexCoord2fv(&p->texcoordArray[i3][0]);
			vaVertex3fv(&p->vertexArray[i3][0]);
			vaEnd();
		}				
//		glPopMatrix ();
	}
	vaDisableArrays();

	GL_PolygonOffset (0);
	
	glEnable(GL_DEPTH_TEST);
	glDepthMask(GL_TRUE);
	glDisable (GL_BLEND);
	glDisable(GL_ALPHA_TEST);
	glAlphaFunc(GL_GREATER,0.666f);
	glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
	glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

	if (gl_fogenable.value)
	{
		glEnable(GL_FOG);
	}
}

//Decals -- END

static byte *ColorForParticle (part_type_t type)
{
	static	col_t	color;

	switch (type)
	{
	case p_spark:
		color[0] = 224 + (rand() & 31);
		color[1] = 100 + (rand() & 31);
		color[2] = 0;
		break;

	case p_torch_flame:
	case p_glow:
		color[0] = color[1] = color[2] = 255;
		break;

	case p_smoke:
		color[0] = color[1] = color[2] = 128;
		color[3] = 128;
		break;

	case p_fire:
	case p_fire2:
		color[0] = 255;
		color[1] = 142;
		color[2] = 62;
		break;

	case p_bubble:
	case p_bubble2:
	case p_staticbubble:
		color[0] = color[1] = color[2] = 192 + (rand() & 63);
		break;

	case p_teleflare:
	case p_lavasplash:
		color[0] = color[1] = color[2] = 128 + (rand() & 127);
		break;

	case p_gunblast:
		color[0]= 224 + (rand() & 31);
		color[1] = 170 + (rand() & 31);
		color[2] = 0;
		break;

	case p_chunk:
		color[0] = color[1] = color[2] = (32 + (rand() & 127));
		break;

	case p_shockwave:
		color[0] = color[1] = color[2] = 64 + (rand() & 31);
		break;

	case p_inferno_flame:
	case p_inferno_trail:
		color[0] = 255;
		color[1] = 77;
		color[2] = 13;
		break;

	case p_sparkray:
		color[0] = 255;
		color[1] = 102;
		color[2] = 25;
		break;

	case p_dpsmoke:
		color[0] = color[1] = color[2] = 48 + (((rand() & 0xFF) * 48) >> 8);
		break;

	case p_dpfire:
		color[0] = 160 + (((rand() & 0xFF) * 48) >> 8);
		color[1] = 16 + (((rand() & 0xFF) * 148) >> 8);
		color[2] = 16 + (((rand() & 0xFF) * 16) >> 8);
		break;

	case p_blood1:
	case p_blood2:
		color[0] = color[1] = color[2] = 180 + (rand() & 63);
		color[3] = 200;
		break;

	case p_blood3:
		color[0] = (50 + (rand() & 31));
		color[1] = color[2] = 0;
		color[3] = 200;
		break;
	
	case p_flame:
		color[0] = 255;
		color[1] = 100;
		color[2] = 25;
		color[3] = 128;
		break;

	case p_lavatrail:
		color[0] = 255;
		color[1] = 102;
		color[2] = 25;
		color[3] = 255;
		break;

	default:
		color[0] = color[1] = color[2] = 255;//White
		break;
	}

	return color;
}


#define ADD_PARTICLE_TEXTURE(_ptex, _texnum, _texindex, _components, _s1, _t1, _s2, _t2)\
do {											\
	particle_textures[_ptex].texnum = _texnum;					\
	particle_textures[_ptex].components = _components;				\
	particle_textures[_ptex].coords[_texindex][0] = (_s1 + 1) / 256.0;		\
	particle_textures[_ptex].coords[_texindex][1] = (_t1 + 1) / 256.0;		\
	particle_textures[_ptex].coords[_texindex][2] = (_s2 - 1) / 256.0;		\
	particle_textures[_ptex].coords[_texindex][3] = (_t2 - 1) / 256.0;		\
} while(0)

#define ADD_PARTICLE_TYPE(_id, _drawtype, _SrcBlend, _DstBlend, _texture, _startalpha, _grav, _accel, _move, _custom)\
do {\
	particle_types[count].id = (_id);\
	particle_types[count].drawtype = (_drawtype);\
	particle_types[count].SrcBlend = (_SrcBlend);\
	particle_types[count].DstBlend = (_DstBlend);\
	particle_types[count].texture = (_texture);\
	particle_types[count].startalpha = (_startalpha);\
	particle_types[count].grav = 9.8 * (_grav);\
	particle_types[count].accel = (_accel);\
	particle_types[count].move = (_move);\
	particle_types[count].custom = (_custom);\
	particle_type_index[_id] = count;\
	count++;\
} while(0)

void QMB_AllocParticles (void) 
{
	extern cvar_t gl_particle_count;

	r_numparticles = bound(ABSOLUTE_MIN_PARTICLES, gl_particle_count.value, ABSOLUTE_MAX_PARTICLES);

	if (particles || r_numparticles < 1)
		Sys_Error("QMB_AllocParticles: internal error");

	// can't alloc on Hunk, using native memory
	particles = (particle_t *) Q_malloc (r_numparticles * sizeof(particle_t));
	GL_RegisterVerts (r_numparticles);
}
void QMB_InitParticles (void)
{
	int	i, count = 0, particleimage;

	Cvar_RegisterVariable (&gl_bounceparticles);
	Cvar_RegisterVariable (&gl_flametype);
	Cvar_RegisterVariable (&gl_particle_count);

	QMB_AllocParticles ();

	if (!(particleimage = GL_LoadTextureImage("textures/particles/particlefont", "qmb:particlefont", 256, 256, TEX_ALPHA | TEX_COMPLAIN)))
		return;

	ADD_PARTICLE_TEXTURE(ptex_none, 0, 0, 1, 0, 0, 0, 0);
//	ADD_PARTICLE_TEXTURE(ptex_blood1, particleimage, 0, 1, 0, 0, 64, 64);
//	ADD_PARTICLE_TEXTURE(ptex_blood2, particleimage, 0, 1, 64, 0, 128, 64);
	ADD_PARTICLE_TEXTURE(ptex_lava, particleimage, 0, 1, 128, 0, 192, 64);
	ADD_PARTICLE_TEXTURE(ptex_blueflare, particleimage, 0, 1, 192, 0, 256, 64);
	ADD_PARTICLE_TEXTURE(ptex_generic, particleimage, 0, 1, 0, 96, 96, 192);
	ADD_PARTICLE_TEXTURE(ptex_smoke, particleimage, 0, 1, 96, 96, 192, 192);
	ADD_PARTICLE_TEXTURE(ptex_blood3, particleimage, 0, 1, 192, 96, 256, 160);
	ADD_PARTICLE_TEXTURE(ptex_bubble, particleimage, 0, 1, 192, 160, 224, 192);
	
	for (i=0 ; i<8 ; i++)
		ADD_PARTICLE_TEXTURE(ptex_dpsmoke, particleimage, i, 8, i * 32, 64, (i + 1) * 32, 96);

	// load the rest of the images
	if (!(particleimage = GL_LoadTextureImage("textures/particles/inferno", "qmb:inferno", 0, 0, TEX_ALPHA | TEX_COMPLAIN)))
		return;
	ADD_PARTICLE_TEXTURE(ptex_flame, particleimage, 0, 1, 0, 0, 256, 256);

	if (!(particleimage = GL_LoadTextureImage("textures/particles/zing1", "qmb:lightning", 0, 0, TEX_ALPHA | TEX_COMPLAIN)))
		return;	
	ADD_PARTICLE_TEXTURE(ptex_lightning, particleimage, 0, 1, 0, 0, 256, 128);//R00k changed
	
	if (!(particleimage = GL_LoadTextureImage("textures/muzzleflash", "qmb:muzzleflash", 128, 128, TEX_ALPHA | TEX_COMPLAIN)))
		return;	
	ADD_PARTICLE_TEXTURE(ptex_muzzleflash, particleimage, 0, 1, 0, 0, 256, 256);

	if (!(particleimage = GL_LoadTextureImage("textures/muzzleflash8", "qmb:muzzleflash8", 128, 128, TEX_ALPHA | TEX_COMPLAIN)))
		return;	
	ADD_PARTICLE_TEXTURE(ptex_muzzleflash8, particleimage, 0, 1, 0, 0, 256, 256);

	if (!(particleimage = GL_LoadTextureImage("textures/particles/bloodcloud", "qmb:bloodcloud", 128, 128, TEX_ALPHA | TEX_COMPLAIN)))
		return;	
	ADD_PARTICLE_TEXTURE(ptex_bloodcloud, particleimage, 0, 1, 0, 0, 256, 256);

	if (!(particleimage = GL_LoadTextureImage("textures/particles/blood1", "qmb:blood1", 0, 0, TEX_ALPHA | TEX_COMPLAIN)))
		return;	
	ADD_PARTICLE_TEXTURE(ptex_blood1, particleimage, 0, 1, 0, 0, 256, 256);

	if (!(particleimage = GL_LoadTextureImage("textures/particles/blood2", "qmb:blood2", 0, 0, TEX_ALPHA | TEX_COMPLAIN)))
		return;	
	ADD_PARTICLE_TEXTURE(ptex_blood2, particleimage, 0, 1, 0, 0, 256, 256);

	if (!(particleimage = GL_LoadTextureImage("textures/particles/fly", "qmb:fly", 0, 0, TEX_ALPHA | TEX_COMPLAIN)))
		return;	
	ADD_PARTICLE_TEXTURE(ptex_fly, particleimage, 0, 1, 0, 0, 256, 256);
	
	ADD_PARTICLE_TYPE(p_spark,			pd_spark,			GL_SRC_ALPHA,	GL_ONE,					ptex_none,		255, -32, 0,	pm_bounce,	1.3);	
	ADD_PARTICLE_TYPE(p_gunblast,		pd_spark,			GL_SRC_ALPHA,	GL_ONE,					ptex_none,		255, -16, 0,	pm_bounce,	1.3);	
	ADD_PARTICLE_TYPE(p_sparkray,		pd_sparkray,		GL_SRC_ALPHA,	GL_ONE,					ptex_none,		255, -0, 0,		pm_nophysics, 0);
	ADD_PARTICLE_TYPE(p_fire,			pd_billboard,		GL_SRC_ALPHA,	GL_ONE,					ptex_generic,	204, 0, -2.95,	pm_die,		0);
	ADD_PARTICLE_TYPE(p_fire2,			pd_billboard,		GL_SRC_ALPHA,	GL_ONE,					ptex_dpsmoke,	204, 0, -2.95,	pm_die,		0);
	ADD_PARTICLE_TYPE(p_chunk,			pd_billboard,		GL_SRC_ALPHA,	GL_ONE,					ptex_generic,	255, -16, 0,	pm_bounce,	1.475);
	ADD_PARTICLE_TYPE(p_shockwave,		pd_billboard,		GL_SRC_ALPHA,	GL_ONE,					ptex_generic,	255, 0, -4.85,	pm_nophysics, 0);
	ADD_PARTICLE_TYPE(p_inferno_flame,	pd_billboard,		GL_SRC_ALPHA,	GL_ONE,					ptex_generic,	153, 0, 0,		pm_static,	0);
	ADD_PARTICLE_TYPE(p_inferno_trail,	pd_billboard,		GL_SRC_ALPHA,	GL_ONE,					ptex_generic,	204, 0, 0,		pm_die,		0);
	ADD_PARTICLE_TYPE(p_trailpart,		pd_billboard,		GL_SRC_ALPHA,	GL_ONE,					ptex_generic,	230, 0, 0,		pm_static,	0);
	ADD_PARTICLE_TYPE(p_smoke,			pd_billboard,		GL_SRC_ALPHA,	GL_ONE,					ptex_smoke,		140, 3, 0,		pm_normal,	0);
	ADD_PARTICLE_TYPE(p_dpfire,			pd_billboard,		GL_SRC_ALPHA,	GL_ONE,					ptex_dpsmoke,	144, 0, 0,		pm_die,		0);
	ADD_PARTICLE_TYPE(p_dpsmoke,		pd_billboard,		GL_SRC_ALPHA,	GL_ONE,					ptex_dpsmoke,	85, 3, 0,		pm_die,		0);
	ADD_PARTICLE_TYPE(p_teleflare,		pd_billboard,		GL_ONE,			GL_ONE,					ptex_blueflare, 255, 0, 0,		pm_die,		0);
	ADD_PARTICLE_TYPE(p_flare,			pd_billboard,		GL_ONE,			GL_ONE,					ptex_bubble,	255, 0, 0,		pm_die,		0);
	ADD_PARTICLE_TYPE(p_fly,			pd_billboard,		GL_ZERO,		GL_ONE_MINUS_SRC_COLOR,	ptex_fly,		255, 0, 0,		pm_die,		0);
	ADD_PARTICLE_TYPE(p_dot,			pd_billboard,		GL_SRC_ALPHA,	GL_ONE,					ptex_generic,	255, 0, 0,		pm_static,	0);
	ADD_PARTICLE_TYPE(p_blood1,			pd_billboard,		GL_SRC_ALPHA,	GL_ONE_MINUS_SRC_ALPHA,	ptex_blood1,	255, -20, 0,	pm_die,		0);
	ADD_PARTICLE_TYPE(p_blood2,			pd_billboard,		GL_SRC_ALPHA,	GL_ONE_MINUS_SRC_ALPHA,	ptex_blood2,	255, -20, 0,	pm_die,		0.018);
	ADD_PARTICLE_TYPE(p_blood3,			pd_billboard,		GL_SRC_ALPHA,	GL_ONE_MINUS_SRC_ALPHA, ptex_blood3,	255, -30, 0,	pm_normal,		0);
	ADD_PARTICLE_TYPE(p_lavasplash,		pd_billboard,		GL_SRC_ALPHA,	GL_ONE_MINUS_SRC_ALPHA, ptex_lava,		170, 0, 0,		pm_nophysics, 0);
	ADD_PARTICLE_TYPE(p_bubble,			pd_billboard,		GL_SRC_ALPHA,	GL_ONE_MINUS_SRC_ALPHA, ptex_bubble,	204, 8, 0,		pm_float,	0);
	ADD_PARTICLE_TYPE(p_staticbubble,	pd_billboard,		GL_SRC_ALPHA,	GL_ONE_MINUS_SRC_ALPHA, ptex_bubble,	204, 0, 0,		pm_static,	0);
	ADD_PARTICLE_TYPE(p_flame,			pd_billboard,		GL_SRC_ALPHA,	GL_ONE,					ptex_generic,	200, 10, 0,		pm_die,		0);
	ADD_PARTICLE_TYPE(p_lavatrail,		pd_billboard,		GL_SRC_ALPHA,	GL_ONE,					ptex_dpsmoke,	255, 3, 0,		pm_normal,	0);//R00k
	ADD_PARTICLE_TYPE(p_bubble2,		pd_billboard,		GL_SRC_ALPHA,	GL_ONE_MINUS_SRC_ALPHA, ptex_bubble,	204, 1, 0,		pm_float,	0);
	ADD_PARTICLE_TYPE(p_glow,			pd_billboard,		GL_SRC_ALPHA,	GL_ONE,					ptex_generic,	204, 0, 0,		pm_die,		0);
	ADD_PARTICLE_TYPE(p_alphatrail,		pd_billboard,		GL_SRC_ALPHA,	GL_ONE,					ptex_generic,	100, 0, 0,		pm_static,	0);
	ADD_PARTICLE_TYPE(p_torch_flame,	pd_billboard,		GL_SRC_ALPHA,	GL_ONE,					ptex_flame,		255, 12, 0,		pm_die,		0);
	ADD_PARTICLE_TYPE(p_streak,			pd_hide,			GL_SRC_ALPHA,	GL_ONE,					ptex_none,		255, -64, 0,	pm_streak,	1.5);
	ADD_PARTICLE_TYPE(p_streakwave,		pd_hide,			GL_SRC_ALPHA,	GL_ONE,					ptex_none,		255, 0, 0,		pm_streakwave, 0);
	ADD_PARTICLE_TYPE(p_streaktrail,	pd_beam,			GL_SRC_ALPHA,	GL_ONE,					ptex_none,		255, 0, 0,		pm_die,		0);
	ADD_PARTICLE_TYPE(p_lightningbeam,	pd_beam,			GL_SRC_ALPHA,	GL_ONE,					ptex_lightning, 255, 0, 0,		pm_die,		0);
	ADD_PARTICLE_TYPE(p_muzzleflash,	pd_billboard,		GL_SRC_ALPHA,	GL_ONE,					ptex_muzzleflash,255, 0, 0,		pm_static,	0);
	ADD_PARTICLE_TYPE(p_muzzleflash8,	pd_billboard,		GL_SRC_ALPHA,	GL_ONE,					ptex_muzzleflash8,220, 0, 0,	pm_static,	0);
	ADD_PARTICLE_TYPE(p_rain,			pd_billboard,		GL_SRC_ALPHA,	GL_ONE,					ptex_generic,	255, -16, 0,	pm_rain,	0);
	ADD_PARTICLE_TYPE(p_bloodcloud,		pd_billboard,		GL_SRC_ALPHA,	GL_ONE_MINUS_SRC_ALPHA,	ptex_bloodcloud,255, -10, 0,	pm_normal,	0);
	qmb_initialized = true;
}

#define	INIT_NEW_PARTICLE(_pt, _p, _color, _size, _time)	\
	_p = free_particles;					\
	free_particles = _p->next;				\
	_p->next = _pt->start;					\
	_pt->start = _p;					\
	_p->size = _size;					\
	_p->hit = 0;						\
	_p->start = cl.time;					\
	_p->die = _p->start + _time;				\
	_p->growth = 0;						\
	_p->rotspeed = 0;					\
	_p->texindex = (rand() % particle_textures[_pt->texture].components);	\
	_p->bounces = 0;					\
	VectorCopy(_color, _p->color);

__inline static void AddParticle (part_type_t type, vec3_t org, int count, float size, float time, col_t col, vec3_t dir)
{
	byte			*color;
	int				i, j;
	float			tempSize;
	particle_t		*p;
	particle_type_t	*pt;

	if (!qmb_initialized)
		Sys_Error ("QMB particle added without initialization");

	//assert (size > 0 && time > 0);
	if (size < 0) 
	{
		Sys_Error ("AddParticle: Invalid size. part_type(%d)", type);
	}
	
	if (time < 0)
	{
		Sys_Error ("AddParticle: Invalid time. part_type(%d)", type);
	}

	if (type < 0 || type >= num_particletypes)
		Sys_Error ("AddParticle: Invalid type (%d)", type);

	pt = &particle_types[particle_type_index[type]];

	for (i=0 ; i < count && free_particles ; i++)
	{
		color = col ? col : ColorForParticle (type);

		INIT_NEW_PARTICLE(pt, p, color, size, time);
				
		switch (type)
		{
		case p_spark:
			p->size = 1.175;
			VectorCopy (org, p->org);
			tempSize = size * 2;
			p->vel[0] = (rand() % (int)tempSize) - ((int)tempSize / 2);
			p->vel[1] = (rand() % (int)tempSize) - ((int)tempSize / 2);
			p->vel[2] = (rand() % (int)tempSize) - ((int)tempSize / 3);
			break;

		case p_smoke:		
			for (j=0 ; j<3 ; j++)
				p->org[j] = org[j] + ((rand() & 31) - 16) / 2.0;
			for (j=0 ; j<3 ; j++)
				p->vel[j] = ((rand() % 10) - 5) / 20.0;
			p->growth = 4.5;
			break;

		case p_fire:
		case p_fire2:
			VectorCopy (org, p->org);
			for (j=0 ; j<3 ; j++)
				p->vel[j] = ((rand() % 160) - 80) * (size / 25.0);
			break;

		case p_bubble:
			p->start += (rand() & 15) / 36.0;
			p->org[0] = org[0] + ((rand() & 31) - 16);
			p->org[1] = org[1] + ((rand() & 31) - 16);
			p->org[2] = org[2] + ((rand() & 63) - 32);
			VectorClear (p->vel);
			break;

		case p_lavasplash:
			VectorCopy (org, p->org);
			VectorCopy (dir, p->vel);
			break;

		case p_gunblast:
			p->size = 1;
			VectorCopy (org, p->org);
			p->vel[0] = (rand() & 127) - 64;
			p->vel[1] = (rand() & 127) - 64;
			p->vel[2] = (rand() & 127) - 10;
			break;

		case p_chunk:
			VectorCopy (org, p->org);
			p->vel[0] = (rand() % 40) - 20;
			p->vel[1] = (rand() % 40) - 20;
			p->vel[2] = (rand() % 40) - 5;			
			break;

		case p_rain:
			VectorCopy(org, p->org);
			p->vel[0] = (rand() % 180) - 90;
			p->vel[1] = (rand() % 180) - 90;
			p->vel[2] = ((rand() % -100) - 1200);
			break;
/*
		case p_snow:
			VectorCopy(org, p->org);
			p->vel[0] = (rand() % 180) - 90;
			p->vel[1] = (rand() % 180) - 90;
			p->vel[2] = ((rand() % -10));
			break;
*/
		case p_streak:
		case p_streakwave:
		case p_shockwave:
			VectorCopy (org, p->org);
			VectorCopy (dir, p->vel);	
			break;

		case p_inferno_trail:
			for (j=0 ; j<3 ; j++)
				p->org[j] = org[j] + (rand() & 15) - 8;
			for (j=0 ; j<3 ; j++)
				p->vel[j] = (rand() & 3) - 2;
			p->growth = -1.5;
			break;

		case p_inferno_flame:
			VectorCopy (org, p->org);
			VectorClear (p->vel);
			p->growth = -30;
			break;

		case p_sparkray:
			VectorCopy (org, p->endorg);
			VectorCopy (dir, p->org);	
			for (j=0 ; j<3 ; j++)
				p->vel[j] = (rand() & 127) - 64;
			p->growth = -16;
			break;

		case p_bloodcloud:
		case p_staticbubble:
			VectorCopy (org, p->org);
			VectorClear (p->vel);
			break;

		case p_muzzleflash:
		case p_muzzleflash8:		
			VectorCopy (org, p->org);			
			p->rotspeed = (rand() & 45) - 90;
			p->size = size * (rand() % 6) / 4;//r00k
			break;

		case p_teleflare:
		case p_flare:
		case p_fly:
			VectorCopy (org, p->org);
			VectorCopy (dir, p->vel);	
			p->growth = 1.75;
			break;
	
		case p_blood1:
		case p_blood2:
			p->size = size * (rand() % 2) + 0.50;//r00k
			for (j=0 ; j<3 ; j++)
				p->org[j] = org[j] + (rand() & 15) - 8;
			for (j=0 ; j<3 ; j++)
				p->vel[j] = (rand() & 63) - 32;
			break;		

		case p_blood3:
			p->size = size * (rand() % 20) / 5.0;
			VectorCopy (org, p->org);
			for (j=0 ; j<3 ; j++)
				p->vel[j] = (rand() % 40) - 20;
			break;

		case p_flame:
			VectorCopy (org, p->org);
			p->growth = -p->size / 2;
			VectorClear (p->vel);
			for (j=0 ; j<2 ; j++)
				p->vel[j] = (rand() % 6) - 3;
			break;

		case p_torch_flame:
			for (j=0 ; j<3 ; j++)
				p->org[j] = org[j] + (rand() & 3) - 2;
			p->vel[0] = rand() % 15 - 8;
			p->vel[1] = rand() % 15 - 8;
			p->vel[2] = rand() % 15;
			p->rotspeed = (rand() & 31) + 32;
			break;

		case p_dot:
		case p_glow:
			VectorCopy (org, p->org);
			VectorCopy (dir, p->vel);	
			p->growth = -1.5;
			break;

		case p_streaktrail:
		case p_lightningbeam:
			VectorCopy(org, p->org);			
			VectorCopy(dir, p->endorg);
			VectorClear(p->vel);
			p->growth = -p->size/time;
			p->bounces = color[3];
			break;

		default:
			assert (!"AddParticle: unexpected type");
			break;
		}
	}
}

__inline static void AddParticleTrail (part_type_t type, vec3_t start, vec3_t end, float size, float time, col_t col)
{
	byte		*color;
	int		i, j, num_particles;
	float		count, length;
	vec3_t		point, delta;
	particle_t	*p;
	particle_type_t	*pt;
	static	float	rotangle = 0;

	if (!qmb_initialized)
		Sys_Error ("QMB particle added without initialization");

		//assert (size > 0 && time > 0);
	if (size < 0) 
	{
		Sys_Error ("AddParticle: Invalid size. part_type(%d)", type);
	}
	
	if (time < 0)
	{
		Sys_Error ("AddParticle: Invalid time. part_type(%d)", type);
	}


	if (type < 0 || type >= num_particletypes)
		Sys_Error ("AddParticle: Invalid type (%d)", type);

	pt = &particle_types[particle_type_index[type]];

	VectorCopy(start, point);
	VectorSubtract(end, start, delta);
	if (!(length = VectorLength(delta)))
		return;

	switch (type)
	{
	case p_alphatrail:
	case p_trailpart:
	case p_lavatrail:
		count = length / 1.1;
		break;

	case p_blood3:
		count = length / 8;
		break;

	case p_bubble:
	case p_bubble2:
		count = length / 8;
		break;

	case p_smoke:
		count = length / 3.8;
		break;

	case p_dpsmoke:
		count = length / 2.5;
		break;

	case p_dpfire:
		count = length / 2.8;
		break;

	default:
		assert (!"AddParticleTrail: unexpected type");
		break;
	}

	if (!(num_particles = (int)count))
		num_particles = 1;

	VectorScale(delta, 1.0 / num_particles, delta);

	for (i=0 ; i < num_particles && free_particles ; i++)
	{
		color = col ? col : ColorForParticle (type);
		INIT_NEW_PARTICLE(pt, p, color, size, time);

		switch (type)
		{
		case p_alphatrail:
		case p_trailpart:
			VectorCopy (point, p->org);
			VectorClear (p->vel);
			p->growth = -size / time;
			break;

		case p_blood3:
			VectorCopy (point, p->org);
			for (j=0 ; j<3 ; j++)
				p->org[j] += ((rand() & 15) - 8) / 8.0;
			for (j=0 ; j<3 ; j++)
				p->vel[j] = ((rand() & 15) - 8) / 2.0;
			p->size = size * (rand() % 20) / 10.0;
			p->growth = 6;
			break;

		case p_bubble2:
			VectorCopy(point, p->org);
			for (j=0 ; j<3 ; j++)
				p->vel[j] = (rand() % 10) - 5;
			break;

		//R00k added
		case p_bubble:
			VectorCopy (point, p->org);
			
			for (j=0 ; j<3 ; j++)
				p->org[j] += ((rand() & 15) - 8) / 8.0;
			
			for (j=0 ; j<3 ; j++)
				p->vel[j] = ((rand() & 15) - 8) / 2.0;

			p->size = size * (rand() % 20) / 10.0;			
			p->growth = 1;						
			break;

		case p_smoke:
			VectorCopy (point, p->org);
			for (j=0 ; j<3 ; j++)
				p->org[j] += ((rand() & 7) - 4) / 8.0;
			p->vel[0] = p->vel[1] = 0;
			p->vel[2] = rand() & 3;
			p->growth = 4.5;
			p->rotspeed = (rand() & 63) + 96;
			break;

		case p_dpsmoke:
			VectorCopy (point, p->org);
			for (j=0 ; j<3 ; j++)
				p->vel[j] = (rand() % 10) - 5;
			p->growth = 3;
			p->rotspeed = (rand() & 63) + 96;
			break;

		case p_dpfire:
			VectorCopy (point, p->org);
			for (j=0 ; j<3 ; j++)
				p->vel[j] = (rand() % 40) - 20;
			break;

		case p_lavatrail:
			VectorCopy (point, p->org);
			for (j=0 ; j<3 ; j++)
				p->org[j] += ((rand() & 7) - 4);
			p->vel[0] = p->vel[1] = 0;
			p->vel[2] = rand() & 3;
			break;

		default:
			assert (!"AddParticleTrail: unexpected type");
			break;
		}

		VectorAdd(point, delta, point);
	}
}

void QMB_ClearParticles (void)
{
	int	i;

	if (!qmb_initialized)
		return;

	Q_free (particles);		// free
	QMB_AllocParticles ();	// and alloc again
	particle_count = 0;
	memset (particles, 0, r_numparticles * sizeof(particle_t));
	free_particles = &particles[0];

	for (i=0 ; i+1 < r_numparticles ; i++)
		particles[i].next = &particles[i + 1];
	particles[r_numparticles-1].next = NULL;

	for (i=0 ; i < num_particletypes ; i++)
		particle_types[i].start = NULL;
}

_inline static void QMB_UpdateParticles(void)
{
	int		i, c;
	float	grav, bounce, frametime, distance[3];
	vec3_t	oldorg, stop, normal;
	particle_type_t	*pt;
	particle_t	*p, *kill;

	if (!qmb_initialized)
		return;

	particle_count = 0;
	frametime = fabs(cl.ctime - cl.oldtime);
	grav = sv_gravity.value / 800.0;

	for (i=0 ; i<num_particletypes ; i++)
	{
		pt = &particle_types[i];

		if (pt->start)
		{
			p = pt->start;
			while (p && p->next)
			{
				kill = p->next;
				if (kill->die <= particle_time)
				{
					p->next = kill->next;
					kill->next = free_particles;
					free_particles = kill;
				}
				else
				{
					p = p->next;
				}
			}
			if (pt->start->die <= particle_time)
			{
				kill = pt->start;
				pt->start = kill->next;
				kill->next = free_particles;
				free_particles = kill;
			}
		}

		for (p = pt->start ; (p); p = p->next)
		{
			if (particle_time < p->start)
				continue;
			
			particle_count++;
			
			p->size += p->growth * frametime;
			
			if (p->size <= 0)
			{
				p->die = 0;
				continue;
			}

			VectorCopy (p->org, oldorg);
			VectorSubtract(r_refdef.vieworg,oldorg, distance);		

			if (VectorLength(distance) >= r_farclip.value)
				p->die = 0;
			
			switch (pt->id)
			{
				case p_streaktrail://R00k
				case p_lightningbeam:
					p->color[3] = p->bounces * ((p->die - particle_time) / (p->die - p->start));
					break;

				default:
					p->color[3] = pt->startalpha * ((p->die - particle_time) / (p->die - p->start));
					break;
			}

			p->rotangle += p->rotspeed * frametime;

			if (p->hit)
				continue;
			
			p->vel[2] += pt->grav * grav * frametime;
			
			VectorScale (p->vel, 1 + pt->accel * frametime, p->vel);

			switch (pt->move)
			{
			case pm_static:
				break;

			case pm_normal:
				VectorCopy (p->org, oldorg);
				VectorMA (p->org, frametime, p->vel, p->org);

				if (CONTENTS_SOLID == TruePointContents(p->org))
				{
					p->hit = 1;	
										
					if ((pt->id == p_blood3)&&(gl_decal_blood.value) && (decals_enabled) && (particle_mode))
					{
						TraceLineN(oldorg, p->org, stop, normal);

						if ((stop != p->org)&&(VectorLength(stop)!=0))
						{
							vec3_t tangent;
							VectorCopy(stop, p->org);
							VectorCopy(normal, p->vel);						
							CrossProduct(normal,p->vel,tangent);	
							R_SpawnDecal(p->org, normal, tangent, decal_blood3, 16, 0, 0);
						}		
						p->die = 0;
					}
					VectorCopy (oldorg, p->org);
					VectorClear (p->vel);
				}			
			break;

			case pm_float:
				VectorMA (p->org, frametime, p->vel, p->org);
				p->org[2] += p->size + 1;		
				if (!ISUNDERWATER(TruePointContents(p->org)))
					p->die = 0;
				p->org[2] -= p->size + 1;
				break;

			case pm_nophysics:
				VectorMA (p->org, frametime, p->vel, p->org);
				break;

			case pm_die:
				VectorCopy (p->org, oldorg);
				VectorMA (p->org, frametime, p->vel, p->org);				
				if (CONTENTS_SOLID == TruePointContents(p->org))
				{		
					if ((decals_enabled) && (particle_mode))
					{
						TraceLineN(oldorg, p->org, stop, normal);

						if ((stop != p->org)&&(VectorLength(stop)!=0))
						{
							vec3_t tangent;							
							
							VectorCopy(stop, p->org);
							VectorCopy(normal, p->vel);						
							
							CrossProduct(p->org,p->vel,tangent);	
							if (pt->id == p_fire2)
							{
								if (gl_decal_explosions.value)
								{							
									R_SpawnDecal(p->org, normal, tangent, decal_burn, 50, 0, 0);
								}
							}
							else
							{
								if (pt->id == p_blood1)
								{					
									if (gl_decal_blood.value)
										R_SpawnDecal(p->org, normal, tangent, decal_blood1, 16, 0, 0);								
								}
								else
								{
									if (pt->id == p_blood2)
									{					
										if (gl_decal_blood.value)
											R_SpawnDecal(p->org, normal, tangent, decal_blood2, 12, 0, 0);									
									}
								}
							}
						}					
					}					
					VectorCopy (oldorg, p->org);
					p->die = 0;
				}
				break;

			case pm_bounce:
				if (!gl_bounceparticles.value || p->bounces)
				{
					VectorMA(p->org, frametime, p->vel, p->org);
					if (CONTENTS_SOLID == TruePointContents(p->org))
					{
						p->die = 0;
					}
				}
				else
				{
					VectorCopy (p->org, oldorg);
					VectorMA (p->org, frametime, p->vel, p->org);
					if (CONTENTS_SOLID == TruePointContents(p->org))
					{
						if (TraceLineN(oldorg, p->org, stop, normal))
						{
							VectorCopy (stop, p->org);
							bounce = -pt->custom * DotProduct(p->vel, normal);
							VectorMA(p->vel, bounce, normal, p->vel);
							p->bounces++;
						}
					}
				}
				break;

			case pm_streak:
				VectorCopy(p->org, oldorg);
				VectorMA(p->org, frametime, p->vel, p->org);
				if (CONTENTS_SOLID == TruePointContents(p->org))
				{
					if (TraceLineN(oldorg, p->org, stop, normal)) 
					{
						VectorCopy(stop, p->org);
						bounce = -pt->custom * DotProduct(p->vel, normal);
						VectorMA(p->vel, bounce, normal, p->vel);
					}
				}

				AddParticle (p_streaktrail, oldorg, 1, p->size, 0.2, p->color, p->org);
				
				if (!VectorLength(p->vel))
					p->die = 0;
				break;

			case pm_rain:
				VectorCopy(p->org, oldorg);
				VectorMA(p->org, frametime, p->vel, p->org);

				VectorSubtract(r_refdef.vieworg,oldorg, distance);		

				if (VectorLength(distance) < r_farclip.value)
				{
					c = TruePointContents(p->org);
	
					if ((CONTENTS_SOLID == c) || (ISUNDERWATER(c)))
					{		
						VectorClear (p->vel);
						p->die = 0;
					}
					else
						AddParticle (p_streaktrail, oldorg, 1, 1, (rand()%1 + 0.03), p->color, p->org);
				}
				break;

			case pm_streakwave:
				VectorCopy(p->org, oldorg);
				VectorMA(p->org, frametime, p->vel, p->org);
				AddParticle (p_streaktrail, oldorg, 1, p->size, 0.5, p->color, p->org);
				p->vel[0] = 19 * p->vel[0] / 20;
				p->vel[1] = 19 * p->vel[1] / 20;
				p->vel[2] = 19 * p->vel[2] / 20;
				break;

			default:
				assert (!"QMB_UpdateParticles: unexpected pt->move");
				break;
			}			
		}
	}
}

//from darkplaces engine - finds which corner of a particle goes where, so I don't have to :D
void R_CalcBeamVerts (float *vert, vec3_t org1, vec3_t org2, float width)
{
	vec3_t right1, right2, diff, normal;

	VectorSubtract (org2, org1, normal);
	VectorNormalize (normal);

	//width = width / 2;
	// calculate 'right' vector for start
	VectorSubtract (r_origin, org1, diff);
	VectorNormalize (diff);
	CrossProduct (normal, diff, right1);

	// calculate 'right' vector for end
	VectorSubtract (r_origin, org2, diff);
	VectorNormalize (diff);
	CrossProduct (normal, diff, right2);

	vert[ 0] = org1[0] + width * right1[0];
	vert[ 1] = org1[1] + width * right1[1];
	vert[ 2] = org1[2] + width * right1[2];
	vert[ 4] = org1[0] - width * right1[0];
	vert[ 5] = org1[1] - width * right1[1];
	vert[ 6] = org1[2] - width * right1[2];
	vert[ 8] = org2[0] - width * right2[0];
	vert[ 9] = org2[1] - width * right2[1];
	vert[10] = org2[2] - width * right2[2];
	vert[12] = org2[0] + width * right2[0];
	vert[13] = org2[1] + width * right2[1];
	vert[14] = org2[2] + width * right2[2];
}

void QMB_DrawParticles (void)
{
	int		i, j, k;
//	vec3_t		v, up, right, billboard[4], velcoord[4], neworg;
	vec3_t		v, billboard[4], neworg;
	particle_t		*p;
	particle_type_t		*pt;
	particle_texture_t	*ptex;

	float	varray_vertex[16];
	vec3_t	distance;
	if (!qmb_initialized)
		return;

	particle_time = cl.time;

	if (!cl.paused)
		QMB_UpdateParticles ();
	
	VectorAdd (vup, vright, billboard[2]);
	VectorSubtract (vright, vup, billboard[3]);
	VectorNegate (billboard[2], billboard[0]);
	VectorNegate (billboard[3], billboard[1]);

	glDepthMask (GL_FALSE);
	glEnable (GL_BLEND);
	glTexEnvf (GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
	glShadeModel (GL_SMOOTH);

	for (i = 0 ; i < num_particletypes ; i++)
	{
		pt = &particle_types[i];
		
		if (!pt->start)
			continue;

		glBlendFunc (pt->SrcBlend, pt->DstBlend);

		switch (pt->drawtype)
		{
			case pd_hide:
				break;
			case pd_beam:
				ptex = &particle_textures[pt->texture];
				GL_Bind (ptex->texnum);
				for (p = pt->start ; p ; p = p->next)
				{
					if (particle_time < p->start || particle_time >= p->die)
						continue;

					VectorSubtract(r_refdef.vieworg, p->org, distance);			
					if (VectorLength(distance) > r_farclip.value)
						continue;

					glColor4ubv (p->color);
					R_CalcBeamVerts (varray_vertex, p->org, p->endorg, p->size / 3.0);
					glBegin (GL_QUADS);
					glTexCoord2f (1, 0);
					glVertex3f (varray_vertex[0], varray_vertex[1], varray_vertex[2]);
					glTexCoord2f (1, 1);
					glVertex3f (varray_vertex[4], varray_vertex[5], varray_vertex[6]);
					glTexCoord2f (0, 1);
					glVertex3f (varray_vertex[8], varray_vertex[9], varray_vertex[10]);
					glTexCoord2f (0, 0);
					glVertex3f (varray_vertex[12], varray_vertex[13], varray_vertex[14]);
					glEnd ();
				}
				break;

			case pd_spark:
				glDisable (GL_TEXTURE_2D);
				for (p = pt->start ; p ; p = p->next)
				{
					if (particle_time < p->start || particle_time >= p->die)
						continue;

					VectorSubtract(r_refdef.vieworg, p->org, distance);			
					if (VectorLength(distance) > r_farclip.value)
						continue;

					glBegin (GL_TRIANGLE_FAN);
					glColor4ubv (p->color);
					glVertex3fv (p->org);
					glColor4ub (p->color[0] >> 1, p->color[1] >> 1, p->color[2] >> 1, 0);
					for (j=7 ; j>=0 ; j--)
					{
						for (k=0 ; k<3 ; k++)
							v[k] = p->org[k] - p->vel[k] / 8 + vright[k] * cost[j%7] * p->size + vup[k] * sint[j%7] * p->size;
						glVertex3fv (v);
					}
					glEnd ();
				}
				glEnable (GL_TEXTURE_2D);
				break;

			case pd_sparkray:
				glDisable (GL_TEXTURE_2D);
				for (p = pt->start ; p ; p = p->next) 
				{
					if (particle_time < p->start || particle_time >= p->die)
						continue;

					VectorSubtract(r_refdef.vieworg, p->org, distance);			
					if (VectorLength(distance) > r_farclip.value)
						continue;

					if (!TraceLineN(p->endorg, p->org, neworg, NULLVEC))
						VectorCopy(p->org, neworg);

					glBegin (GL_TRIANGLE_FAN);
					//R00k added -start-
					glEnable (GL_BLEND);
					p->color[3] = bound(0, 0.3, 1) * 255;
					//R00k added -end-
					glColor4ubv (p->color);
					glVertex3fv (p->endorg);
					glColor4ub (p->color[0] >> 1, p->color[1] >> 1, p->color[2] >> 1, 0);
					for (j=7 ; j>=0 ; j--)
					{
						for (k=0 ; k<3 ; k++)
							v[k] = neworg[k] + vright[k] * cost[j%7] * p->size + vup[k] * sint[j%7] * p->size;
						glVertex3fv (v);
					}
					glDisable (GL_BLEND);//R00k
					glEnd ();
				}
				glEnable (GL_TEXTURE_2D);
				break;

			case pd_billboard:
				ptex = &particle_textures[pt->texture];
				
				GL_Bind (ptex->texnum);

				vaEnableVertexArray (3);
				vaEnableTexCoordArray (GL_TEXTURE0, VA_TEXTURE0, 2);				
				
				for (p = pt->start ; p ; p = p->next)
				{
					if (particle_time < p->start || particle_time >= p->die)
						continue;

					VectorSubtract(r_refdef.vieworg, p->org, distance);			
					if (VectorLength(distance) > r_farclip.value)
						continue;

					glPushMatrix ();
									
					glTranslatef (p->org[0], p->org[1], p->org[2]);				
					glScalef (p->size, p->size, p->size);					
					if (p->rotspeed)								
						glRotatef (p->rotangle, vpn[0], vpn[1], vpn[2]);			
															
					glColor4ubv (p->color);																						
					vaBegin (GL_QUADS);	
						vaTexCoord2f (ptex->coords[p->texindex][0], ptex->coords[p->texindex][3]); vaVertex3fv(billboard[0]);	
						vaTexCoord2f (ptex->coords[p->texindex][0], ptex->coords[p->texindex][1]); vaVertex3fv(billboard[1]);	
						vaTexCoord2f (ptex->coords[p->texindex][2], ptex->coords[p->texindex][1]); vaVertex3fv(billboard[2]);	
						vaTexCoord2f (ptex->coords[p->texindex][2], ptex->coords[p->texindex][3]); vaVertex3fv(billboard[3]);	
					vaEnd ();																								
					glPopMatrix ();
				}
				vaDisableArrays();
			break;
			default:
				assert (!"QMB_DrawParticles: unexpected drawtype");
				break;
		}
	}

	glDepthMask (GL_TRUE);
	glDisable (GL_BLEND);
	glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
	glTexEnvf (GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
	glShadeModel (GL_FLAT);
}

void QMB_Shockwave_Splash(vec3_t org, int radius)
{
	float	theta;
	vec3_t	angle;

	angle[2] = 0;

  //for (theta = 0; theta < 2 * M_PI; theta += 2 * M_PI / 90) 
	for (theta = 0; theta < 6.283185307179586476925286766559; theta += 0.069813170079773183076947630739545)
	{
		angle[0] = cos(theta) * radius;
		angle[1] = sin(theta) * radius;		
		AddParticle(p_shockwave, org, 1, 2, 0.625f, NULL, angle);
	}
}

void QMB_LetItRain(void)
{
	int				i;
	msurface_t		*surf;
	glpoly_t		*p;
	col_t			color;
	float			distance[3], point[3];
	float			frametime = fabs(cl.ctime - cl.oldtime);
	extern sfx_t	*cl_sfx_thunder;

	color[0] =  color[1] = color[2] = + (rand() % 25 + 15);
	color[2] += 5;

	if (!qmb_initialized)
		return;

	if (!particle_mode)
		return;

	if (cl.time < cl.rain_time)	
	{
		return;
	}

	cl.rain_time = cl.time + 0.025;

	if (gl_fogenable.value)
	{
		gl_fogred.value = gl_foggreen.value = gl_fogblue.value = 0.2;
	}

	for (i = 0, surf = cl.worldmodel->surfaces; i < cl.worldmodel->numsurfaces; i++, surf++)
	{
		if (!(surf->flags & SURF_DRAWSKY))
			continue;

		for (p = surf->polys;  p; p = p->next)
		{	
			VectorSubtract(r_refdef.vieworg,p->midpoint, distance);		

			if ((VectorLength(distance) > r_farclip.value))
				continue;

			//R00k let's make some thunder...
			if (cl.time > cl.thundertime)
			{
				S_StartSound (-1, 0, cl_sfx_thunder, p->midpoint, 1, 0);
				cl.thundertime = cl.time + (rand()% 60 + 1);
			}

			point[0] = p->midpoint[0] + (rand() % 80 - 40);
			point[1] = p->midpoint[1] + (rand() % 80 - 40);
			point[2] = p->midpoint[2] + p->fxofs;
			
			AddParticle(p_rain, point, 1, 0.035, 5, color, zerodir);		
		}		
	}
};

//R00k: revamped to coincide with classic particle style...

void QMB_ParticleExplosion (vec3_t org)
{
	if (r_explosiontype.value == 2)//no explosion what so ever
		return;

	if (ISUNDERWATER(TruePointContents(org)))
	{
		AddParticle (p_fire, org, 8, 8, 0.8, NULL, zerodir);
		AddParticle (p_bubble, org, 50, 2.0, 0.825f + ((rand() % 10) - 5) / 40.0, NULL, zerodir);
		AddParticle (p_bubble, org, 25, 1.0, 0.825f + ((rand() % 10) - 5) / 40.0, NULL, zerodir);
	}
	else
	{
		if (r_explosiontype.value != 3)		
		{
			if (gl_flametype.value < 1)//R00k: default 0
			{
				AddParticle (p_fire2, org, 16, 18, 1, NULL, zerodir);			
			}
			else
			{
				AddParticle (p_fire, org, 16, 18, 1, NULL, zerodir);
			}
		}

		if ((r_explosiontype.value == 0) || (r_explosiontype.value == 1) || (r_explosiontype.value == 3))
		{
			AddParticle (p_spark, org, 25, 150, 0.825f, NULL, zerodir);
		}
	}
}

void d8to24col (col_t colourv, int colour)
{
	byte	*colourByte;

	colourByte = (byte *)&d_8to24table[colour];
	colourv[0] = colourByte[0];
	colourv[1] = colourByte[1];
	colourv[2] = colourByte[2];
}

__inline static void AddColoredParticle (part_type_t type, vec3_t org, int count, float size, float time, int colorStart, int colorLength, vec3_t dir)
{
	col_t		color;
	int		i, j, colorMod = 0;
	float		tempSize;
	particle_t	*p;
	particle_type_t	*pt;


	if (!qmb_initialized)
		Sys_Error ("QMB particle added without initialization");

	//assert (size > 0 && time > 0);
	if (size < 0) 
	{
		Sys_Error ("AddParticle: Invalid size. part_type(%d)", type);
	}
	
	if (time < 0)
	{
		Sys_Error ("AddParticle: Invalid time. part_type(%d)", type);
	}

	if (type < 0 || type >= num_particletypes)
		Sys_Error ("AddColoredParticle: Invalid type (%d)", type);

	pt = &particle_types[particle_type_index[type]];

	for (i=0 ; i < count && free_particles ; i++)
	{
		d8to24col (color, colorStart + (colorMod % colorLength));
		colorMod++;
		INIT_NEW_PARTICLE(pt, p, color, size, time);

		switch (type)
		{
		case p_spark:
			p->size = 1.175;
			VectorCopy (org, p->org);
			tempSize = size * 2;
			p->vel[0] = (rand() % (int)tempSize) - ((int)tempSize / 4);
			p->vel[1] = (rand() % (int)tempSize) - ((int)tempSize / 4);
			p->vel[2] = (rand() % (int)tempSize) - ((int)tempSize / 6);
			break;

		case p_fire:
			VectorCopy (org, p->org);
			for (j=0 ; j<3 ; j++)
				p->vel[j] = ((rand() % 160) - 80) * (size / 25.0);
			break;

		default:
			assert (!"AddColoredParticle: unexpected type");
			break;
		}
	}
}

void QMB_ColorMappedExplosion (vec3_t org, int colorStart, int colorLength)
{
	if (ISUNDERWATER(TruePointContents(org)))
	{
		//AddColoredParticle (p_fire, org, 16, 18, 1, colorStart, colorLength, zerodir);
		AddParticle (p_bubble, org, 6, 3.0, 2.5, NULL, zerodir);
		AddParticle (p_bubble, org, 4, 2.35, 2.5, NULL, zerodir);
		if (r_explosiontype.value != 2)
		{
			AddColoredParticle (p_spark, org, 50, 100, 0.5, colorStart, colorLength, zerodir);
			AddColoredParticle (p_spark, org, 25, 60, 0.5, colorStart, colorLength, zerodir);
		}
	}
	else
	{
		if (gl_flametype.value < 1)//R00k
		{
				AddColoredParticle (p_fire2, org, 16, 18, 1, colorStart, colorLength, zerodir);
		}
		else
		{
				AddColoredParticle (p_fire, org, 16, 18, 1, colorStart, colorLength, zerodir);
		}	
	
		if (r_explosiontype.value < 2)
		{			
			AddColoredParticle (p_spark, org, 50, 250, 0.625f, colorStart, colorLength, zerodir);
			if (r_explosiontype.value < 1)
				AddColoredParticle (p_spark, org, 25, 150, 0.625f, colorStart, colorLength, zerodir);
		}
	}
}

void QMB_Blood_Splat(part_type_t type, vec3_t org) //R00k :)
{
	int			j;
	col_t		color;
	vec3_t		neworg, angle;
	
	VectorClear (angle);

	color[0]=100;
	color[1]=0;
	color[2]=0;
	color[3]=255;

	AddParticle(p_bloodcloud, org, 1, 6, 0.5, color, zerodir);

	for (j=0 ; j<4 ; j++)
	{
		AngleVectors (angle, NULLVEC, NULLVEC, neworg);
		VectorMA (org, 70, neworg, neworg);

		AddParticle (type, org, 5, 1, 2, color, neworg);
		angle[1] += 360 / 4;
	}
} 

void QMB_RunParticleEffect (vec3_t org, vec3_t dir, int col, int count)
{
	col_t	color;
	vec3_t	neworg, newdir;
	int		i, j, particlecount;
	int		contents;//R00k Added	
	
	if (col == 73)
	{			
		QMB_Blood_Splat(p_blood1, org);
		QMB_Blood_Splat(p_blood2, org);
		return;
	}
	else if (col == 225)
	{		
		QMB_Blood_Splat(p_blood3, org);
		return;
	}
	else if (col == 20 && count == 30)
	{
		color[0] = color[2] = 51;
		color[1] = 255;
		AddParticle (p_chunk, org, 1, 1, 0.75, color, zerodir);
		//AddParticle (p_spark, org, 1, 70, 0.2, NULL, zerodir);					
		return;
	}
	else if (col == 226 && count == 20)
	{
		color[0] = 230;
		color[1] = 204;
		color[2] = 26;
		AddParticle (p_chunk, org, 1, 1, 0.75, color, zerodir);  
		//AddParticle (p_spark, org, 1, 70, 0.2, NULL, zerodir);			
		return;
	}

	switch (count)
	{
	case 9:
	case 10://nailgun
		if (nehahra && count == 10)	// ventillation's wind
		{
			for (i=0 ; i<count ; i++)
			{
				for (j=0 ; j<3 ; j++)
				{
					neworg[j] = org[j] + ((rand() % 24) - 12);
					newdir[j] = dir[j] * (10 + (rand() % 5));
				}
				d8to24col (color, (col & ~7) + (rand() & 7));
				AddParticle (p_glow, neworg, 1, 3, 0.3 + 0.1 * (rand() % 3), color, newdir);
			}
		}
		else
		{
			color[0] = 200;	color[1] = 200;	color[2] = 125;			

			AddParticle (p_spark, org, 1, 70, 0.2, color, zerodir);			
			AddParticle (p_chunk, org, 3, 1, 0.75, NULL, zerodir);

			contents = TruePointContents (org);//R00k Added
			if (ISUNDERWATER(contents))//R00k
			{							
				AddParticle (p_bubble, org, 1, 2, 0.825f + ((rand() % 10) - 5) / 40.0, NULL, zerodir);
			}
			else
			{	
				AddParticle (p_smoke, org, 1, 4, 0.825f + ((rand() % 10) - 5) / 40.0, NULL, zerodir);
			}
		}
		break;

	case 20://super nailgun
		color[0] = 200;	color[1] = 200;	color[2] = 125;		
		AddParticle (p_spark, org, 2, 85, 0.4, color, zerodir);
		AddParticle (p_chunk, org, 6, 1, 0.75, NULL, zerodir);			

		contents = TruePointContents (org);//R00k Added
		if (ISUNDERWATER(contents))//R00k
		{							
			AddParticle (p_bubble, org, 1, 2, 0.825f + ((rand() % 10) - 5) / 40.0, NULL, zerodir);
		}
		else
		{	
			AddParticle (p_smoke, org, 1, 4, 0.825f + ((rand() % 10) - 5) / 40.0, NULL, zerodir);
		}
		break;

	case 24:// gunshot

		particlecount = count >> 1;
		//AddParticle (p_gunblast, org, 1, 1.04, 0.47, NULL, zerodir);
		for (i=0 ; i<particlecount ; i++)
		{			
			for (j=0 ; j<3 ; j++)
				neworg[j] = org[j] + ((rand() & 15) - 8);
			
			contents = TruePointContents (neworg);//R00k Added

			if (ISUNDERWATER(contents))//R00k
			{							
				AddParticle (p_bubble, neworg, 1, 2, 0.825f + ((rand() % 10) - 5) / 40.0, NULL, zerodir);
			}
			else
			{	
				AddParticle (p_smoke, neworg, 1, 4, 0.825f + ((rand() % 10) - 5) / 40.0, NULL, zerodir);
			}
			
			if ((i % particlecount) == 0)
			{
				AddParticle (p_chunk, neworg, 1, 0.75, 3.75, NULL, zerodir);
			}
		}
		break;

	case 30:
		AddParticle (p_chunk, org, 10, 1, 4, NULL, zerodir);
		AddParticle (p_spark, org, 1, 70, 0.2, NULL, zerodir);			
		//AddParticle (p_spark, org, 8, 105, 0.9, NULL, zerodir);

		break;

	case 128:	// electric sparks (R00k added from QMB)
		color[2] = 1.0f;
		for (i=0; i<count; i++)
		{
			color[0] = color[1] = 0.4 + ((rand()%90)/255.0);
			AddParticle (p_spark, org, 1, 100, 1.0f,  color, zerodir);//modified
		}		
		break;

	default:
		if (hipnotic)
		{
			switch (count)
			{
			case 1:		//particlefields
			case 2:
			case 3:
			case 4:
				d8to24col (color, (col & ~7) + (rand() & 7));
				AddParticle (p_gunblast, org, 15, 5, 0.1 * (rand() % 5), color, zerodir);
				return;
			}
		}
		else if (nehahra)
		{
			switch (count)
			{
			case 25:	// slime barrel chunks
				return;

			case 244:	// sewer
				for (i=0 ; i<(count>>3) ; i++)
				{
					for (j=0 ; j<3 ; j++)
						neworg[j] = org[j] + ((rand() % 24) - 12);
					newdir[0] = dir[0] * (10 + (rand() % 5));
					newdir[1] = dir[1] * (10 + (rand() % 5));
					newdir[2] = dir[2] * 15;
					d8to24col (color, (col & ~7) + (rand() & 7));
					AddParticle (p_glow, neworg, 1, 3.5, 0.5 + 0.1 * (rand() % 3), color, newdir);
				}
				return;
			}
		}

		particlecount = max(1, count>>1);
		for (i=0 ; i<particlecount ; i++)
		{
			for (j=0 ; j<3 ; j++)
			{
				neworg[j] = org[j] + ((rand() % 20) - 10);
				newdir[j] = dir[j] * (10 + (rand() % 5));
			}
			d8to24col (color, (col & ~7) + (rand() & 7));
			AddParticle (p_glow, neworg, 1, 3, 0.2 + 0.1 * (rand() % 4), color, newdir);
		}
		break;
	}
}

//R00k added particle muzzleflashes
void QMB_MuzzleFlash(vec3_t org)
{
	if (!qmb_initialized)
		return;

	if (!particle_mode)
		return;

	if (!(ISUNDERWATER(TruePointContents(org))))
	{
		AddParticle (p_muzzleflash, org, 1, 8, 0.04, NULL, zerodir);
	}
}

void QMB_MuzzleFlashLG(vec3_t org)
{
	float	frametime = fabs(cl.ctime - cl.oldtime);
	col_t	color;

	color[0] = color[1] = 20;
	color[2] = 200;

	if (!qmb_initialized)
		return;

	if (!particle_mode)
		return;

	if (!(ISUNDERWATER(TruePointContents(org))))
	{
		AddParticle (p_muzzleflash8, org, 1, 8, 0.4 * frametime, color, zerodir);
	}
}

void QMB_RocketTrail (vec3_t start, vec3_t end, trail_type_t type)
{
	col_t		color;

	switch (type)
	{
	case GRENADE_TRAIL://r00K mODIFIED
		if (ISUNDERWATER(TruePointContents(start)))
		{
			AddParticleTrail (p_bubble, start, end, 1, 0.30, NULL);							
		}
		else
		{				
			if (gl_part_trails.value > 1)
			{			
				color[0] = 15; color[1] = 15; color[2] = 10;
				AddParticleTrail (p_alphatrail, start, end, 8, 1, color);				
			}
			else
				AddParticleTrail (p_smoke, start, end, 1.45, 0.825, NULL);
		}
		break;

	case BLOOD_TRAIL:
	case SLIGHT_BLOOD_TRAIL:
		AddParticleTrail (p_blood3, start, end, type == BLOOD_TRAIL ? 1.35 : 2.4, 2, NULL);
		break;

	case TRACER1_TRAIL:
		color[0] = color[2] = 0;
		color[1] = 124;
		AddParticleTrail (p_trailpart, start, end, 3.75, 0.5, color);
		break;

	case TRACER2_TRAIL:
		color[0] = 255;
		color[1] = 77;
		color[2] = 0;
		AddParticleTrail (p_trailpart, start, end, 1.75, 0.2, color);
		break;

	case VOOR_TRAIL:
		color[0] = 77;
		color[1] = 0;
		color[2] = 255;
		AddParticleTrail (p_trailpart, start, end, 3.75, 0.5, color);
		break;

	case ALT_ROCKET_TRAIL:
		if (ISUNDERWATER(TruePointContents(start)))
		{
			AddParticleTrail (p_bubble, start, end, 1, 2.5, NULL);								
		}
		else
		{
			if (gl_part_trails.value > 1)
			{			
				color[0] = 15; color[1] = 15; color[2] = 10;
				AddParticleTrail (p_alphatrail, start, end, 8, 1, color);				
			}
			else
			{			
				AddParticleTrail (p_dpfire, start, end, 3, 0.26, NULL);
				AddParticleTrail (p_dpsmoke, start, end, 3, 0.825, NULL);
			}
		}
		break;

	case LAVA_TRAIL:
		AddParticleTrail (p_lavatrail, start, end, 5, 0.25, NULL);		
		AddParticleTrail (p_dpsmoke, start, end, 5, 0.825, NULL);
		break;

	case BUBBLE_TRAIL:
		if (ISUNDERWATER(TruePointContents(start)))
			AddParticleTrail (p_bubble2, start, end, 1.5, 0.825, NULL);
		break;

	case NEHAHRA_SMOKE:
		AddParticleTrail (p_smoke, start, end, 0.8, 0.825, NULL);
		break;

	case ROCKET_TRAIL:
	default:
		color[0] = 255;
		color[1] = 56;
		color[2] = 9;
//		AddParticleTrail (p_trailpart, start, end, 6.2, 0.31, color);
		if (ISUNDERWATER(TruePointContents(start)))
		{
			AddParticleTrail (p_trailpart, start, end, 1, 0.30, color);				
			AddParticleTrail (p_bubble, start, end, 1, 2.5, NULL);				
		}
		else
		{
			if (gl_part_trails.value > 1)
			{			
				color[0] = 15; color[1] = 15; color[2] = 10;
				AddParticleTrail (p_alphatrail, start, end, 8, 3, color);				
			}
			else
			{			
				AddParticleTrail (p_trailpart, start, end, 6.2, 0.31, color);
				AddParticleTrail (p_smoke, start, end, 1.8, 0.825, NULL);
			}
		}
		break;
	case NAIL_TRAIL://R00k added
		if (ISUNDERWATER(TruePointContents(start)))
		{
			AddParticleTrail (p_bubble, start, end, 0.25, 0.50, NULL);
		}
		else
		{			
			color[0] = 15; color[1] = 15; color[2] = 15;
			AddParticleTrail (p_alphatrail, start, end, 1, 0.25, color);				
		}
		break;
	}
}
/*
void QMB_AxeSwoosh(vec3_t start)
{
		col_t		color;
		vec3_t		end, mid;

		mid[0] = 15;mid[1] = 15;mid[2] = 15;
		VectorSubtract(start,mid,end);
		
		if (ISUNDERWATER(TruePointContents(start)))
		{
			AddParticleTrail (p_bubble, start, end, 0.25, 0.50, NULL);
		}
		else
		{			
			color[0] = 255; color[1] = 255; color[2] = 255;
			AddParticleTrail (p_alphatrail, start, end, 50, 0.5, color);				
		//	AddParticle (p_spark, start, 50, 250, 0.925f, NULL, zerodir);
		}
};
*/

void QMB_BlobExplosion (vec3_t org)
{
	float	theta;
	col_t	color;
	vec3_t	neworg, vel;

	color[0] = 60;
	color[1] = 100;
	color[2] = 240;
	AddParticle (p_spark, org, 44, 250, 1.15, color, zerodir);

	color[0] = 90;
	color[1] = 47;
	color[2] = 207;
	AddParticle (p_fire, org, 15, 30, 1.4, color, zerodir);

	vel[2] = 0;
	for (theta = 0 ; theta < 6.28318530717958647692528676655901 ; theta += 0.0897597901025655210989326680937001)
	{
		color[0] = (60 + (rand() & 15));
		color[1] = (65 + (rand() & 15));
		color[2] = (200 + (rand() & 15));

		vel[0] = cos(theta) * 125;
		vel[1] = sin(theta) * 125;
		neworg[0] = org[0] + cos(theta) * 6;
		neworg[1] = org[1] + sin(theta) * 6;
		neworg[2] = org[2] + 0 - 10;
		AddParticle (p_shockwave, neworg, 1, 4, 0.8, color, vel);
		neworg[2] = org[2] + 0 + 10;
		AddParticle (p_shockwave, neworg, 1, 4, 0.8, color, vel);

		vel[0] *= 1.15;
		vel[1] *= 1.15;
		neworg[0] = org[0] + cos(theta) * 13;
		neworg[1] = org[1] + sin(theta) * 13;
		neworg[2] = org[2] + 0;
		AddParticle (p_shockwave, neworg, 1, 6, 1.0, color, vel);
	}
}

void QMB_LavaSplash (vec3_t org)
{
	int	i, j;
	float	vel;
	vec3_t	dir, neworg;

	for (i=-16 ; i<16; i++)
	{
		for (j=-16 ; j<16 ; j++)
		{
			dir[0] = j * 8 + (rand() & 7);
			dir[1] = i * 8 + (rand() & 7);
			dir[2] = 256;

			neworg[0] = org[0] + dir[0];
			neworg[1] = org[1] + dir[1];
			neworg[2] = org[2] + (rand() & 63);

			VectorNormalizeFast (dir);
			vel = 50 + (rand() & 63);
			VectorScale (dir, vel, dir);

			AddParticle (p_lavasplash, neworg, 1, 4.5, 2.6 + (rand() & 31) * 0.02, NULL, dir);
		}
	}
}

void QMB_TeleportSplash (vec3_t org)
{
	int		i, j, k;
	vec3_t		neworg, angle;
	col_t		color;

	QMB_Shockwave_Splash(org, 120);
	for (i=-12 ; i<=12 ; i+=6)
	{
		for (j=-12 ; j<=12 ; j+=6)
		{
			for (k=-24 ; k<=32 ; k+=8)
			{
				neworg[0] = org[0] + i + (rand() & 3) - 1;
				neworg[1] = org[1] + j + (rand() & 3) - 1;
				neworg[2] = org[2] + k + (rand() & 3) - 1;
				angle[0] = (rand() & 15) - 7;
				angle[1] = (rand() & 15) - 7;
				angle[2] = (rand() % 160) - 80;
				AddParticle (p_teleflare, neworg, 1, 1.8, 0.30 + (rand() & 7) * 0.02, NULL, angle);
			}
		}
	}

	VectorSet (color, 140, 140, 255);
	VectorClear (angle);
	for (i=0 ; i<5 ; i++)
	{
		angle[2] = 0;
		for (j=0 ; j<5 ; j++)
		{
			AngleVectors (angle, NULLVEC, NULLVEC, neworg);
			VectorMA (org, 70, neworg, neworg);
			AddParticle (p_sparkray, org, 1, 6 + (i & 3), 5, color, neworg);
			angle[2] += 360 / 5;
		}
		angle[0] += 180 / 5;
	}
}
/*
void QMB_InfernoFlame (vec3_t org)
{
	float	frametime = fabs(cl.ctime - cl.oldtime);
	
	if (ISUNDERWATER(TruePointContents(org)))
		return;

	if (frametime)
	{
		if (gl_flametype.value < 1)
		{
			AddParticle (p_torch_flame, org, 1, 5, 0.5, NULL, zerodir);//R00k
		}
		else
		{
			AddParticle (p_inferno_flame, org, 1, 30, 13.125 * frametime, NULL, zerodir);
			AddParticle (p_inferno_trail, org, 2, 1.75, 45.0 * frametime, NULL, zerodir);
			AddParticle (p_inferno_trail, org, 2, 1.0, 52.5 * frametime, NULL, zerodir);
		}
	}
}
*/

void QMB_StaticBubble (entity_t *ent)
{
	AddParticle (p_staticbubble, ent->origin, 1, ent->frame == 1 ? 1.85 : 2.9, 0.001, NULL, zerodir);
}

void QMB_StaticMarker (entity_t *ent)
{
	col_t		color;

	color[0] = 255;color[1] = 255;color[2] = 000;color[3] = 0;
				
	AddParticle (p_dot, ent->origin, 1, 1, 0.01, color, zerodir);//pinpoint on wall
}

void QMB_TorchFlame (vec3_t org) 
{
	float	frametime = fabs (cl.ctime - cl.oldtime);

	if (frametime > (1/cl_maxfps.value))
	{
		if (gl_flametype.value < 1)
		{
			AddParticle (p_torch_flame, org, 2, 2.5, 0.5, NULL, zerodir);		
		}
		else
		{
			AddParticle (p_flame, org, 1, 7, 0.8, NULL, zerodir);
		}
	}
}

void QMB_BigTorchFlame (vec3_t org) 
{
	float	frametime = fabs (cl.ctime - cl.oldtime);

	if (frametime > 0.001)
	{
		if (gl_flametype.value < 1)
		{
			AddParticle (p_torch_flame, org, 2, 7, 0.5, NULL, zerodir);//R00k
		}
		else
		{
			AddParticle (p_flame, org, 1, 12, 1, NULL, zerodir);
		}		
	}
}

void QMB_ShamblerCharge (vec3_t org)
{
	vec3_t	pos, vec, dir;
	col_t	col = {60, 100, 240, 128};
	float	time, len;
	int	i;

	for (i=0 ; i<5 ; i++)
	{
		VectorClear(vec);
		VectorClear(dir);

		VectorCopy(org, pos);
		pos[0] += (rand() % 200) - 100;
		pos[1] += (rand() % 200) - 100;
		pos[2] += (rand() % 200) - 100;
	
		VectorSubtract(pos, org, vec);
		len = VectorLength (vec);
		VectorNormalize (vec);
		VectorMA(dir, -200, vec, dir);
		time = len / 200;

		AddParticle (p_streakwave, pos, 1, 3, time, col, dir);
	}
}
//R00k: this is the old style like HalfLife 1...not used
/*
void QMB_FlashLight (void)
{	
	float		frametime	= fabs(cl.time - cl.oldtime);
	float		size;

	extern cvar_t	v_viewheight;
	extern cvar_t	cl_gun_offset;
	
	vec3_t	dest, start, forward, right,up, sz;
	trace_t	trace;

	if (frametime > 0.001)
	{	
		if (qmb_initialized)
		{
			AngleVectors (r_refdef.viewangles,  forward, right, up);
			VectorCopy(cl_entities[cl.viewentity].origin, start);

			start[2] +=  16;
			start[2] += cl.crouch + bound(-7, v_viewheight.value, 4);

			VectorMA (start, 1024, forward, dest);					
					
			memset (&trace, 0, sizeof(trace_t));
			trace.fraction = 1;
			
			start[2]+=cl.crouch;

			SV_RecursiveHullCheck(cl.worldmodel->hulls, 0, 0, 1, start, dest, &trace);
			
			VectorSubtract(dest, start, sz);
			
			size = VectorLength(sz);
			size = bound(0,size,100);

			if (trace.fraction != 1)
			{					
				CL_NewDlight (0, trace.endpos, size, 0.02, lt_default);
			}
		} 
	}
}
*/
void QMB_LaserSight (void)
{	
	float		frametime	= fabs(cl.time - cl.oldtime);
	col_t		color;
	int			c;
	float		fpstime;

	extern cvar_t	gl_laserpoint;
	extern cvar_t	chase_right;
	extern cvar_t	v_viewheight;
	extern cvar_t	cl_gun_offset;
	extern cvar_t	cl_maxfps;
	extern cvar_t	host_maxfps;
	
	vec3_t	dest, start, forward, right,up;
	trace_t	trace;

	
	if (!particle_mode)
		return;

	if (cl.viewent.model->name == NULL)//R00k: dont show laserpoint when observer!
	{
		return;
	}
	
	if (sv.active)
		fpstime = (1.0f / host_maxfps.value);
	else
		fpstime = (1.0f / cl_maxfps.value);

	if (frametime > fpstime)
	{	
		if (qmb_initialized)
		{
			if (cl.time > cl.laser_point_time)
			{
				AngleVectors (cl.viewangles,  forward, right, up);
				VectorCopy(cl_entities[cl.viewentity].origin, start);

				start[2] +=  16;//QuakeC uses + '0 0 16' for gun aim.
				start[2] += cl.crouch + bound(-7, v_viewheight.value, 4);

				if (cl_gun_offset.value == 2)//Right-Handed
				{
					VectorMA (start, 2, right, start);	
				}
				else if (cl_gun_offset.value == 1)//Left-Handed
				{
					VectorMA (start, -2, right, start);	
				}

				VectorMA (start, 4096, forward, dest);					
						
				c = lt_default;

				switch ((int)gl_laserpoint.value)
				{
					case 1:
						color[0] = color[0] = 000;color[1] = 000;color[2] = 255;color[3] = 30;//B
						c = lt_blue;
						break;
					case 2:
						color[0] = 255;color[1] = 000;color[2] = 000;color[3] = 30;//R
						c = lt_red;
						break;
					case 3:
						color[0] = 255;color[1] = 255;color[2] = 000;color[3] = 30;//Y
						c = lt_yellow;
						break;
					case 4:					
						color[0] = 000;color[1] = 255;color[2] = 000;color[3] = 30;//G
						c = lt_green;
						break;
				}			
				
				memset (&trace, 0, sizeof(trace_t));
				trace.fraction = 1;
				SV_RecursiveHullCheck(cl.worldmodel->hulls, 0, 0, 1, start, dest, &trace);
				
				start[2]+=cl.crouch;
				AddParticle (p_streaktrail, start, 1, 1, 0.02, color, trace.endpos);// draw the line 
				
				
				if (trace.fraction != 1)
				{					
					color[3] = 200;
					AddParticle (p_dot, trace.endpos, 1, 4, fpstime, color, zerodir);//pinpoint on wall
					if (cl.maxclients < 2)
					{					
						CL_NewDlight (0, trace.endpos, (rand() % 10 + 30), fpstime + 0.005, c);
					}
				}
				cl.laser_point_time = cl.time + fpstime;
			}
		} 
	}
}

void QMB_CullTraceHightlight (vec3_t start, vec3_t end, int c)
{	
	float		frametime	= fabs(cl.time - cl.oldtime);
	col_t		color;

	if (!particle_mode)
		return;

	if (frametime > 0.001)
	{	
		if (qmb_initialized)
		{
			switch (c)
			{
				case 13:
					color[0] = color[0] = 000;color[1] = 000;color[2] = 255;color[3] = 30;//B
					break;
				case 4:
					color[0] = 255;color[1] = 000;color[2] = 000;color[3] = 30;//R
					break;
				case 12:
					color[0] = 255;color[1] = 255;color[2] = 000;color[3] = 30;//Y
					break;
				case 3:					
					color[0] = 000;color[1] = 255;color[2] = 000;color[3] = 30;//G
					break;
			}			

			AddParticle (p_streaktrail, start, 1, 1, 0.2, color, end);// draw the line 
		} 
	}
}

void QMB_Lightning_Splash(vec3_t org)
{
	int      i, j;
	vec3_t   neworg, angle;
	col_t   color;   
	col_t	col2 = {200, 100, 100, 255};

	VectorSet (color, 40, 40, 128);
	VectorClear (angle);

	for (i=0 ; i<5 ; i++)
	{
	  angle[2] = 0;
	  for (j=0 ; j<5 ; j++)
	  {
			AngleVectors (angle, NULLVEC, NULLVEC, neworg);
			VectorMA (org, 20, neworg, neworg);
			AddParticle (p_spark, org, 2, 42, 0.05f, NULL, zerodir);
			AddParticle (p_spark, org, 2, 50, 0.1f, col2, neworg);
			angle[2] += 360 / 5;
	  }
	  angle[0] += 180 / 5;
	}   
	color[0] = 224 + (rand() & 31);
	color[1] = 100 + (rand() & 31);
	color[2] = 0;

	AddParticle (p_spark, org, 1, 35, 0.2, color, zerodir);			
} 

void QMB_LightningBeam (vec3_t start, vec3_t end)
{	
	float		frametime	= fabs(cl.time - cl.oldtime);
	col_t		color		= {255,255,255,255};
	trace_t		trace;
	
	if (frametime > 0.001)
	{	
		if (qmb_initialized && gl_part_lightning.value )
		{
			if (qmb_initialized && gl_part_sparks.value)
			{
				memset (&trace, 0, sizeof(trace_t));
				if (!SV_RecursiveHullCheck(cl.worldmodel->hulls, 0, 0, 1, start, end, &trace))
				{
					if (trace.fraction < 1)
					{
						VectorCopy (trace.endpos, end);
						if ((gl_decal_sparks.value) && (particle_mode) && (decals_enabled))
						{							
							R_SpawnDecalStatic(end, decal_glow, 10);
						}
						QMB_Lightning_Splash (end);						
					}
				}			
			}

			//R00k v1.84 moved down here 
			AddParticle(p_lightningbeam, start, 1, 80, host_frametime * 2, color, end);
		} 
	}
}

extern	float	r_avertexnormals[NUMVERTEXNORMALS][3];
vec3_t		avelocities[NUMVERTEXNORMALS];

/*
===============
R_EntityParticles
===============
*/
void QMB_EntityParticles (entity_t *ent)
{
	int			i;
	float		angle, dist, sr, sp, sy, cr, cp, cy;
	vec3_t		forward, org;
	col_t		color = {255,255,0,100};
	
	dist = 64;

	if (!avelocities[0][0])
		for (i=0 ; i<NUMVERTEXNORMALS*3 ; i++)
			avelocities[0][i] = (rand() & 255) * 0.01;

	for (i=0 ; i<NUMVERTEXNORMALS ; i++)
	{
		angle = cl.time * avelocities[i][0];
		sy = sin(angle);
		cy = cos(angle);
		angle = cl.time * avelocities[i][1];
		sp = sin(angle);
		cp = cos(angle);
		angle = cl.time * avelocities[i][2];
		sr = sin(angle);
		cr = cos(angle);
	
		forward[0] = cp*cy;
		forward[1] = cp*sy;
		forward[2] = -sp;

		org[0] = ent->origin[0] + r_avertexnormals[i][0]*dist + forward[0]*16;
		org[1] = ent->origin[1] + r_avertexnormals[i][1]*dist + forward[1]*16;
		org[2] = ent->origin[2] + r_avertexnormals[i][2]*dist + forward[2]*16;
		AddParticle (p_flare, org, 1, 2,0.005, color, forward);
	}
}

//Modified from Quake2
void QMB_FlyParticles (vec3_t origin, int count)
{
	float		frametime	= fabs(cl.time - cl.oldtime);
    int         i;
    float       angle, sr, sp, sy, cr, cp, cy;
    vec3_t      forward, org;
    float       dist = 64;
	col_t		color = {255,255,255,100};

	if (frametime > 0.001)
	{
		if (count > NUMVERTEXNORMALS)
			count = NUMVERTEXNORMALS;

		if (!avelocities[0][0])
		{
			for (i=0 ; i<NUMVERTEXNORMALS*3 ; i++)
				avelocities[0][i] = (rand()&255) * 0.01;
		}

		for (i=0 ; i<count ; i+=2)
		{
			angle = cl.time * avelocities[i][0];
			sy = sin(angle);
			cy = cos(angle);
			angle = cl.time * avelocities[i][1];
			sp = sin(angle);
			cp = cos(angle);
			angle = cl.time * avelocities[i][2];
			sr = sin(angle);
			cr = cos(angle);
    
			forward[0] = cp*cy;
			forward[1] = cp*sy;
			forward[2] = -sp;

			dist = sin(cl.time + i)*64;
			org[0] = origin[0] + r_avertexnormals[i][0]*dist + forward[0]*32;
			org[1] = origin[1] + r_avertexnormals[i][1]*dist + forward[1]*32;
			org[2] = origin[2] + r_avertexnormals[i][2]*dist + forward[2]*32;

			AddParticle (p_fly, org, 1, 1,0.01, color, forward);
		}
	}
}

void CheckParticles (void)
{
	if (!qmb_initialized)//something is wrong fallback to classic
	{
		particle_mode = 0;
		return;
	}

	if	(
			!gl_part_explosions.value && 
			!gl_part_trails.value && 
			!gl_part_sparks.value && 
			!gl_part_gunshots.value && 
			!gl_part_blood.value && 
			!gl_part_telesplash.value && 
			!gl_part_blobs.value && 
			!gl_part_lavasplash.value && 
			!gl_part_flames.value && 
			!gl_part_lightning.value &&
			!gl_part_flies.value 
		)
		particle_mode = 0;
	else 
	if	(
			gl_part_explosions.value && 
			gl_part_trails.value && 
			gl_part_sparks.value && 
			gl_part_gunshots.value && 
			gl_part_blood.value && 
			gl_part_telesplash.value && 
			gl_part_blobs.value && 
			gl_part_lavasplash.value && 
			gl_part_flames.value && 
			gl_part_lightning.value &&
			gl_part_flies.value 
		)
		particle_mode = 1;
	else
		particle_mode = 2;
}

void R_SetParticles (int val)
{
	particle_mode = val;

	Cvar_SetValue ("gl_part_explosions", particle_mode);
	Cvar_SetValue ("gl_part_trails", particle_mode);
	Cvar_SetValue ("gl_part_sparks", particle_mode);
	Cvar_SetValue ("gl_part_gunshots", particle_mode);
	Cvar_SetValue ("gl_part_blood", particle_mode);
	Cvar_SetValue ("gl_part_telesplash", particle_mode);
	Cvar_SetValue ("gl_part_blobs", particle_mode);
	Cvar_SetValue ("gl_part_lavasplash", particle_mode);
	Cvar_SetValue ("gl_part_flames", particle_mode);
	Cvar_SetValue ("gl_part_lightning", particle_mode);
	Cvar_SetValue ("gl_part_flies", particle_mode);
}

/*
===============
R_ToggleParticles_f

function that toggles between classic and QMB particles - by joe
===============
*/
void R_ToggleParticles_f (void)
{
	if (cmd_source != src_command)
		return;
	CheckParticles();
	R_SetParticles (!particle_mode);
	Con_Printf ("Using %s particles\n", !particle_mode ? "Classic" : "QMB");
}

void CheckDecals (void)
{
	if	(!gl_decal_bullets.value && !gl_decal_explosions.value && !gl_decal_sparks.value && !gl_decal_blood.value)
		decals_enabled = 0;
	else	
		if	(gl_decal_bullets.value && gl_decal_explosions.value && gl_decal_sparks.value && gl_decal_blood.value)
		decals_enabled = 1;
		else
			decals_enabled = 2;
}

void R_SetDecals (int val)
{	
	decals_enabled = val;
	Cvar_SetValue ("gl_decal_bullets", val);
	Cvar_SetValue ("gl_decal_explosions", val);
	Cvar_SetValue ("gl_decal_sparks", val);
	Cvar_SetValue ("gl_decal_blood", val);
}

void R_ToggleDecals_f (void)
{
	if (cmd_source != src_command)
		return;
	CheckDecals ();
	R_SetDecals (!(decals_enabled));	
	Con_Printf ("All Decals are %s.\n", !decals_enabled ? "disabled" : "enabled");
}

