//-----------------------------------------------------------------------------
//
//  $Logfile:: /Source/cl_fx.c                                                $
// $Revision:: 3                                                              $
//   $Author:: Burgermac                                                      $
//     $Date:: 8/8/01 4:48 PM                                                 $
//
// Copyright (C) 1998 by Ritual Entertainment, Inc.
// All rights reserved.
//
// This source is may not be distributed and/or modified without
// expressly written permission by Ritual Entertainment, Inc.
//
// DESCRIPTION:
// entity effects parsing and management
//

#include "client.h"
#include "ltstyle.h"

void CL_ExplosionParticles (vec3_t org, int lightstyle, float scale);

void CL_LogoutEffect (vec3_t org, int type);
void CL_ItemRespawnParticles (vec3_t org);

/*
==============================================================

LIGHT STYLE MANAGEMENT

==============================================================
*/

#define MAX_LIGHTSTYLE_LENGTH 64

typedef struct
{
	qboolean	lerp;
	qboolean	hasalpha;
	float		value[4];
	float		prevvalue[4];
	int			length;
	int			index;
	byte		map[MAX_LIGHTSTYLE_LENGTH][4];
} clightstyle_t;

clightstyle_t	cl_lightstyle[MAX_LIGHTSTYLES];
int				lastofs;

/*
================
CL_ClearLightStyles
================
*/
static void CL_ClearLightStyles (void)
{
	memset(cl_lightstyle, 0, sizeof(cl_lightstyle));
	lastofs = -1;
}

/*
================
CL_LightStyleColor
================
*/
qboolean CL_LightStyleColor(int style, int time, float color[4])
{
	clightstyle_t	*ls;
	int				i;
	byte		*f;

	time /= 100;
	color[0] = color[1] = color[2] = color[3] = 0;
	if (style < 0 || style >= MAX_LIGHTSTYLES)
	{
		Com_Printf ("CL_LightStyleColor: style out of range.\n");
		return true;
	}
	ls = &cl_lightstyle[style];
	if (time >= ls->length)
		return true;

	f = ls->map[time];

	for (i = 0; i < 4; i++)
		color[i] = (float)f[i]/255.0f;

	if (!ls->hasalpha)
		color[3] = 1;

	return false;
}

/*
================
CL_RunLightStyles
================
*/
void CL_RunLightStyles (void)
{
	int				ofs;
	int				i;
	clightstyle_t	*ls;
	byte				*f;

	ofs = cl.time / 100;
	if (ofs == lastofs)
		return;
	lastofs = ofs;

	for (i = 0, ls = cl_lightstyle; i < MAX_LIGHTSTYLES; i++, ls++)
	{
		ls->prevvalue[0] = ls->value[0];
		ls->prevvalue[1] = ls->value[1];
		ls->prevvalue[2] = ls->value[2];
		ls->prevvalue[3] = ls->value[3];
		if (!ls->length)
		{
			ls->value[0] = ls->value[1] = ls->value[2] = 1.0;
			ls->value[3] = -1;
			continue;
		}
		if (ls->length == 1)
		{
			ls->value[0] = (float)ls->map[0][0]/127.5f;
			ls->value[1] = (float)ls->map[0][1]/127.5f;
			ls->value[2] = (float)ls->map[0][2]/127.5f;
			ls->value[3] = (float)ls->map[0][3]/127.5f;
		}
		else
		{
			f = ls->map[ls->index];
			ls->index++;
			if (ls->index >= ls->length)
				ls->index = 0;
			ls->value[0] = (float)f[0]/127.5f;
			ls->value[1] = (float)f[1]/127.5f;
			ls->value[2] = (float)f[2]/127.5f;
			ls->value[3] = (float)f[3]/127.5f;
		}
	}
}

void CL_SetLightstyle (int i)
{
	char	*s, *r, *g, *b, *a;
	int	j, k, n, len;
	float	t;

	s = cl.configstrings[i+CS_LIGHTS];
	len = (int)strlen(s);

	if (!Q_stricmp(&s[len - 4], ".tga"))
		s = createLightstyleFromTGA(s);
	if (!s)
		return;

	cl_lightstyle[i].index = 0;
	if (*s == 'L')
	{
		cl_lightstyle[i].lerp = true;
		s++;
	}
	else
	{
		cl_lightstyle[i].lerp = false;
	}

	r = strchr(s, 'R');
	g = strchr(s, 'G');
	b = strchr(s, 'B');
	a = strchr(s, 'A');

	n = 0;
	if (r) n++;
	if (g) n++;
	if (b) n++;
	if (a) n++;
	if (!n) n++;

	if (!a)
		cl_lightstyle[i].hasalpha = false;
	else
		cl_lightstyle[i].hasalpha = true;

	j = (int)strlen (s) / n;
	if (j >= MAX_LIGHTSTYLE_LENGTH)
	{
		Com_DPrintf(DEVELOPER_MSG_CLIENT, "svc_lightstyle length=%i", j);
		j = MAX_LIGHTSTYLE_LENGTH - 1;
	}

	if (!r && !g && !b && !a)
	{
		cl_lightstyle[i].length = j;
		for (k = 0; k < j; k++)
		{
			t = (float)(s[k] - 'a') * 127.5f / 12.5;
			if (t > 255)
				t = 255.0f;
			cl_lightstyle[i].map[k][0] = t;
			cl_lightstyle[i].map[k][1] = t;
			cl_lightstyle[i].map[k][2] = t;
			cl_lightstyle[i].map[k][3] = 1;
		}
	}
	else
	{
		n = 0;
		// red
		k = 0;
		t = 0;
		if (r)
		{
			r++;
			while (r[k] >= 'a' && r[k] <= 'z')
			{
				t = (float)(r[k] - 'a') * 127.5f / 12.5;
				if (t > 255)
					t = 255.0f;
				cl_lightstyle[i].map[k][0] = t;
				k++;
			}
			n = k;
		}
		while (k < MAX_LIGHTSTYLE_LENGTH)
		{
			cl_lightstyle[i].map[k++][0] = t;
		}

		// green
		k = 0;
		t = 0;
		if (g)
		{
			g++;
			while (g[k] >= 'a' && g[k] <= 'z')
			{
				t = (float)(g[k] - 'a') * 127.5f / 12.5;
				if (t > 255)
					t = 255.0f;
				cl_lightstyle[i].map[k][1] = t;
				k++;
			}
			n = max(n, k);
		}
		while (k < MAX_LIGHTSTYLE_LENGTH)
		{
			cl_lightstyle[i].map[k++][1] = t;
		}

		// blue
		k = 0;
		t = 0;
		if (b)
		{
			b++;
			while (b[k] >= 'a' && b[k] <= 'z')
			{
				t = (float)(b[k] - 'a') * 127.5f / 12.5;
				if (t > 255)
					t = 255.0f;
				cl_lightstyle[i].map[k][2] = t;
				k++;
			}
			n = max(n, k);
		}
		while (k < MAX_LIGHTSTYLE_LENGTH)
		{
			cl_lightstyle[i].map[k++][2] = t;
		}

		// alpha
		k = 0;
		t = 255;
		if (a)
		{
			a++;
			while (a[k] >= 'a' && a[k] <= 'z')
			{
				t = (float)(a[k] - 'a') * 127.5f / 25;
				if (t > 255)
					t = 255.0f;
				cl_lightstyle[i].map[k][3] = t;
				k++;
			}
			n = max(n, k);
		}
		while (k < MAX_LIGHTSTYLE_LENGTH)
		{
			cl_lightstyle[i].map[k++][3] = t;
		}

		cl_lightstyle[i].length = n;
	}
}

/*
================
CL_AddLightStyles
================
*/
void CL_AddLightStyles (void)
{
	int				i;
	clightstyle_t	*ls;
	float				value[4];

	for (i=0,ls=cl_lightstyle ; i<MAX_LIGHTSTYLES ; i++, ls++)
	{
		if (ls->lerp)
		{
			value[0] = ls->prevvalue[0] + cl.lerpfrac * (ls->value[0] - ls->prevvalue[0]);
			value[1] = ls->prevvalue[1] + cl.lerpfrac * (ls->value[1] - ls->prevvalue[1]);
			value[2] = ls->prevvalue[2] + cl.lerpfrac * (ls->value[2] - ls->prevvalue[2]);
			value[3] = ls->prevvalue[3] + cl.lerpfrac * (ls->value[3] - ls->prevvalue[3]);
			V_AddLightStyle(i, value[0], value[1], value[2], value[3]);
		}
		else
		{
			V_AddLightStyle(i, ls->value[0], ls->value[1], ls->value[2], ls->value[3]);
		}
	}
}

/*
==============================================================

DLIGHT MANAGEMENT

==============================================================
*/

cdlight_t		cl_dlights[MAX_DLIGHTS];

/*
================
CL_ClearDlights
================
*/
static void CL_ClearDlights (void)
{
	memset (cl_dlights, 0, sizeof(cl_dlights));
}

/*
===============
CL_AllocDlight

===============
*/
cdlight_t *CL_AllocDlight (int key)
{
	int		i;
	cdlight_t	*dl;

// first look for an exact key match
	if (key)
	{
		dl = cl_dlights;
		for (i=0 ; i<MAX_DLIGHTS ; i++, dl++)
		{
			if (dl->key == key)
			{
				memset(dl, 0, sizeof(*dl));
				dl->key = key;
				return dl;
			}
		}
	}

// then look for anything else
	dl = cl_dlights;
	for (i=0 ; i<MAX_DLIGHTS ; i++, dl++)
	{
		if (dl->die < cl.time)
		{
			memset(dl, 0, sizeof(*dl));
			dl->key = key;
			return dl;
		}
	}

	dl = &cl_dlights[0];
	memset(dl, 0, sizeof(*dl));
	dl->key = key;
	return dl;
}

/*
===============
CL_NewDlight
===============
*/
#if 0
void CL_NewDlight (int key, float x, float y, float z, float radius, float time)
{
	cdlight_t	*dl;

	dl = CL_AllocDlight (key);
	dl->origin[0] = x;
	dl->origin[1] = y;
	dl->origin[2] = z;
	dl->radius = radius;
	dl->die = cl.time + time;
}
#endif

/*
===============
CL_RunDLights

===============
*/
void CL_RunDLights (void)
{
	int			i;
	cdlight_t	*dl;

	dl = cl_dlights;
	for (i=0 ; i<MAX_DLIGHTS ; i++, dl++)
	{
		if (!dl->radius)
			continue;
		
		if (dl->die < cl.time)
		{
			dl->radius = 0;
			return;
		}
		dl->radius -= cls.renderFrametime*dl->decay;
		if (dl->radius < 0)
			dl->radius = 0;
	}
}

/*
===============
CL_AddDLights

===============
*/
void CL_AddDLights (void)
{
	int			i;
	cdlight_t	*dl;

	dl = cl_dlights;
	for (i=0 ; i<MAX_DLIGHTS ; i++, dl++)
	{
		if (!dl->radius)
			continue;
		V_AddLight (dl->origin, dl->radius,
			dl->color[0], dl->color[1], dl->color[2]);
	}
}


/*
==============================================================

PARTICLE MANAGEMENT

==============================================================
*/

#define	PARTICLE_GRAVITY	40

cparticle_t	*active_particles, *free_particles;

cparticle_t	particles[MAX_PARTICLES];
int			cl_numparticles = MAX_PARTICLES;


/*
===============
CL_ClearParticles
===============
*/
static void CL_ClearParticles (void)
{
	int		i;
	
	free_particles = &particles[0];
	active_particles = NULL;

	for (i=0 ;i<cl_numparticles ; i++)
	{
		particles[i].index = -1;
		particles[i].next = &particles[i+1];
		particles[i].size = 0;
		particles[i].growrate = 0;
	}
	particles[cl_numparticles-1].next = NULL;
}

#if 0 /* not used */
static void MakeNormalVectors (vec3_t forward, vec3_t right, vec3_t up)
{
	float		d;

	// this rotate and negat guarantees a vector
	// not colinear with the original
	right[1] = -forward[0];
	right[2] = forward[1];
	right[0] = forward[2];

	d = DotProduct (right, forward);
	VectorMA (right, -d, forward, right);
	VectorNormalize (right);
	CrossProduct (right, forward, up);
}
#endif

/*
=======================
CL_ParticleStreamEffect
=======================
*/
void CL_ParticleStreamEffect (vec3_t start, vec3_t end, int lightstyle, float density, float velocity, float randoffset, float gravity, int flags)
{
	CL_ParticleStreamEffectSized (start, end, lightstyle, density, velocity, randoffset, gravity, flags, 1, 0);
}

void CL_ParticleStreamEffectSized (vec3_t start, vec3_t end, int lightstyle, float density, float velocity, float randoffset, float gravity, int flags, byte size, signed char growrate)
{
	vec3_t		move;
	vec3_t		vec;
	float		len;
	int			j;
	cparticle_t	*p;
	float		dec;

	VectorCopy (start, move);
	VectorSubtract (end, start, vec);
	len = VectorNormalize (vec);
	dec = density;
	VectorScale (vec, dec, vec);
	VectorCopy (start, move);

	while (len > 0)
	{
		len -= dec;

		if (!free_particles)
			return;
		p = free_particles;
		free_particles = p->next;
		p->next = active_particles;
		active_particles = p;

		VectorClear (p->accel);
		p->time        = cl.time;
		p->lightstyle  = lightstyle;
		p->flags       = flags;

		for (j = 0; j < 3; j++)
		{
			p->org[j] = move[j] + crand() * randoffset;
			p->vel[j] = crand() * velocity;
		}
		p->accel[2] = gravity;

		p->size = size;
		p->growrate = growrate;

		VectorAdd (move, vec, move);
	}
}

#if 0 /* not used */
static void CL_GetBarrelPosition (entity_t * ent, vec3_t pos)
{
	int defhandle;
	vec3_t	trans[3];
	vec3_t	orient;
	vec3_t	offset;
	int		groupindex;
	int		tri_num;
	int		anim;

	if (!ent || !ent->model)
		return;

	defhandle = re.SINMDL_GetDefHandle(ent->model);
	if (defhandle < 0)
		return;

	anim = SINMDL_Anim_Random(defhandle, "fire");
	if (anim < 0)
		return;

	// get the bone information
	if (SINMDL_GetBoneInfo(defhandle, "barrel", &groupindex, &tri_num, orient))
	{
		if (SINMDL_GetBoneTransform(defhandle, groupindex, tri_num, orient, anim, 0, ent->scale, trans, offset))
		{
			// we scale the pos by 0.3 because of RF_DEPTHHACK
			VectorScale(offset, 0.3f, pos);
		}
	}
	else
	{
		Com_DPrintf("CL_GetBarrelPosition: barrel bone not found\n");
	}
}
#endif

void CL_ParticleStream (centity_t *ent, int lightstyle, float density, float velocity, float randoffset, float gravity, int flags, qboolean shockwave)
{
	vec3_t		start, end, offset, orientation;
	trace_t		trace;
	int			i, defhandle;
	vec3_t		trans[3];
	vec3_t		f, u, r;
	int			group_num, tri_num;

	// ent is owner of gun, current_entity is gun

	// make sure this is valid
	if (!current_entity)
		return;

	defhandle = re.SINMDL_GetDefHandle(current_entity->model);
	if (defhandle < 0)
	{
		Com_DPrintf(DEVELOPER_MSG_CLIENT, "CL_ParticleStream: trying to get handle of non-sinmdl model.\n");
		return;
	}

	if (!SINMDL_GetBoneInfo(defhandle,"barrel",&group_num,&tri_num,orientation))
	{
		Com_DPrintf(DEVELOPER_MSG_CLIENT, "CL_ParticleStream: GetBoneInfo failed.\n");
		return;
	}

	if (!SINMDL_GetBoneTransform(defhandle, group_num, tri_num, orientation, current_entity->anim, current_entity->frame, current_entity->scale, trans, offset))
	{
		Com_DPrintf(DEVELOPER_MSG_CLIENT, "CL_ParticleStream: GetBoneTransform failed.\n");
		return;
	}

	for (i = 0; i < 3; i++)
	{
		start[i] = current_entity->oldorigin[i] + cl.lerpfrac * 
								(current_entity->origin[i] - current_entity->oldorigin[i]);
	}

	VectorCopy(current_entity->mat[0], f);
	VectorCopy(current_entity->mat[1], r);
	VectorCopy(current_entity->mat[2], u);

	if (current_entity->flags & RF_WEAPONMODEL)
	{
		VectorScale(offset, 0.3f, offset);
		switch((int)hand->value)
		{
		case 1:
			VectorNegate(r, r);
			break;
		case 2:
			return;
		}
	}

	for (i = 0; i < 3; i++)
	{
		start[i] += (f[i] * offset[0]) + (r[i] * offset[1]) + (u[i] * offset[2]);
	}

	VectorMA(start, 8192, f, end);
	trace = CL_GenericTrace(start, vec3_origin, vec3_origin, end, MASK_SHOT, false, ent->current.number);
	// Do the effect
	CL_ParticleStreamEffect(start, trace.endpos, lightstyle, density, velocity, randoffset, gravity, flags);

	if (shockwave)
	{
		TempShockwave(trace.endpos, trace);
	}
}

void CL_LensFlareParticles (vec3_t org)
{
	int			i, j;
	cparticle_t	*p;

	for (i = 0; i < 5; i++)
	{
		if (!free_particles)
			return;
		p = free_particles;
		free_particles = p->next;
		p->next = active_particles;
		active_particles = p;

		p->time = cl.time;
		p->lightstyle = 124;

		for (j = 0; j < 3; j++)
		{
			p->org[j] = org[j] + ((rand()%8)-4);
			p->vel[j] = (rand()%200)-100;
		}

		p->accel[0] = p->accel[1] = 0;
		p->accel[2] = -PARTICLE_GRAVITY;

		p->size = 1;
		p->growrate = 0;
	}
}

/*
===============
CL_SinParticleEffect
===============
*/
void CL_SinParticleEffect(const vec3_t org,const vec3_t dir, int lightstyle, int count, qboolean scatter, byte flags)
{
	CL_SinParticleEffectSized(org, dir, lightstyle, count, scatter, flags, 1, 0);
}

void CL_SinParticleEffectSized(const vec3_t org,const vec3_t dir, int lightstyle, int count, qboolean scatter, byte flags, byte size, signed char growrate)
{
	int			i, j;
	cparticle_t	*p;
	float		d;

	for (i=0 ; i<count ; i++)
	{
		if (!free_particles)
			return;
		p = free_particles;
		free_particles = p->next;
		p->next = active_particles;
		active_particles = p;

		p->time = cl.time;
		p->lightstyle = lightstyle;
		p->flags = flags;

		if (scatter)
		{
			d = rand()&31;
			for (j = 0; j < 3; j++)
			{
				p->org[j] = org[j] + ((rand()&7)-4) + d*dir[j];
				p->vel[j] = crand()*20;
			}
		}
		else
		{
			d = rand()&15;
			for (j=0 ; j<3 ; j++)
			{
				p->org[j] = org[j];
				p->vel[j] = (20+d)*dir[j] + crand()*d;
			}
		}

		p->accel[0] = p->accel[1] = 0;
		p->accel[2] = -PARTICLE_GRAVITY;

		p->size = size;
		p->growrate = growrate;
	}
}

/*
===============
CL_SinParticleEffect2
===============
*/
void CL_SinParticleEffect2 (vec3_t org, vec3_t dir, int lightstyle, int count, qboolean scatter, byte flags)
{
	CL_SinParticleEffect2Sized(org, dir, lightstyle, count, scatter, flags, 1, 0);
}

void CL_SinParticleEffect2Sized (vec3_t org, vec3_t dir, int lightstyle, int count, qboolean scatter, byte flags, byte size, signed char growrate)
{
	int			i, j;
	cparticle_t	*p;
	float		d;

	for (i=0 ; i<count ; i++)
	{
		if (!free_particles)
			return;
		p = free_particles;
		free_particles = p->next;
		p->next = active_particles;
		active_particles = p;

		p->time = cl.time;
		p->lightstyle = lightstyle;
		p->flags = flags;

		if (scatter)
		{
			d = rand()&31;
			for (j = 0; j < 3; j++)
			{
				p->org[j] = org[j] + ((rand()&7)-4) + d*dir[j];
				p->vel[j] = crand()*20;
			}
		}
		else
		{
			d = rand()&15;
			for (j = 0; j < 3; j++)
			{
				p->org[j] = org[j];
				p->vel[j] = (20+d)*dir[j] + crand()*d;
			}
		}

		p->accel[0] = p->accel[1] = 0;
		p->accel[2] = -PARTICLE_GRAVITY;

		p->size = size;
		p->growrate = growrate;
	}
}


/*
===================
CL_DiminishingTrail
===================
*/
void CL_DiminishingTrail (vec3_t start, vec3_t end, centity_t *old, int flags)
{
	vec3_t		move;
	vec3_t		vec;
	float		len;
	int			j;
	cparticle_t	*p;
	float		dec;
	float		orgscale;
	float		velscale;

	VectorCopy (start, move);
	VectorSubtract (end, start, vec);
	len = VectorNormalize (vec);

	dec = 0.5;
	VectorScale (vec, dec, vec);

	if (old->trailcount > 900)
	{
		orgscale = 4;
		velscale = 15;
	}
	else if (old->trailcount > 800)
	{
		orgscale = 2;
		velscale = 10;
	}
	else
	{
		orgscale = 1;
		velscale = 5;
	}

	while (len > 0)
	{
		len -= dec;

		if (!free_particles)
			return;

		// drop less particles as it flies
		if ((rand()&1023) < old->trailcount)
		{
			p = free_particles;
			free_particles = p->next;
			p->next = active_particles;
			active_particles = p;
			VectorClear (p->accel);

			p->time = cl.time;
			if (flags & EF_GIB)
			{
				p->lightstyle = 127;
				p->flags |= PARTICLE_RANDOM;
				for (j=0 ; j<3 ; j++)
				{
					p->org[j] = move[j] + crand()*orgscale;
					p->vel[j] = crand()*velscale;
					p->accel[j] = 0;
				}
				p->vel[2] -= PARTICLE_GRAVITY;
			}
			else if (flags & EF_ROCKET)
			{
				p->lightstyle = 120;
				for (j=0 ; j<3 ; j++)
				{
					p->org[j] = move[j] + crand()*orgscale;
					p->vel[j] = crand()*velscale;
					p->accel[j] = 0;
				}
				p->vel[2] -= PARTICLE_GRAVITY;
			}
		}
		old->trailcount -= 5;
		if (old->trailcount < 100)
			old->trailcount = 100;
		VectorAdd (move, vec, move);
	}
}

/*
==============
CL_RocketTrail
==============
*/
void CL_RocketTrail (vec3_t start, vec3_t end, centity_t *old)
{
	vec3_t		move;
	vec3_t		vec;
	float		len;
	int			j;
	cparticle_t	*p;
	float		dec;

	// smoke
	CL_DiminishingTrail (start, end, old, EF_ROCKET);

	// fire
	VectorCopy (start, move);
	VectorSubtract (end, start, vec);
	len = VectorNormalize (vec);

	dec = 1;
	VectorScale (vec, dec, vec);

	while (len > 0)
	{
		len -= dec;

		if (!free_particles)
			return;

		if ((rand()&7) == 0)
		{
			p = free_particles;
			free_particles = p->next;
			p->next = active_particles;
			active_particles = p;

			VectorClear (p->accel);
			p->time = cl.time;

			p->lightstyle = 123;
			for (j=0 ; j<3 ; j++)
			{
				p->org[j] = move[j] + crand()*5;
				p->vel[j] = crand()*20;
			}
			p->accel[2] = -PARTICLE_GRAVITY;
		}
		VectorAdd (move, vec, move);
	}
}


/*
================
CL_LaserParicles
================
*/
void CL_LaserParticles (vec3_t org, int lightstyle, int count)
{
	int			i, j;
	cparticle_t	*p;

	for (i=0 ; i<count ; i++)
	{
		if (!free_particles)
			return;
		p = free_particles;
		free_particles = p->next;
		p->next = active_particles;
		active_particles = p;

		p->time = cl.time;
		p->lightstyle = lightstyle;

		for (j=0 ; j<3 ; j++)
		{
			p->org[j] = org[j];
			p->vel[j] = (rand()%100)-50;
		}

		p->accel[0] = p->accel[1] = 0;
		p->accel[2] = -PARTICLE_GRAVITY;
	}
}


/*
===============
CL_ExplosionParticles
===============
*/
void CL_ExplosionParticles (vec3_t org, int lightstyle, float scale)
{
	int			i, j, count;
	cparticle_t	*p;

	count = 256*scale;
	for (i=0 ; i<count ; i++)
	{
		if (!free_particles)
			return;
		p = free_particles;
		free_particles = p->next;
		p->next = active_particles;
		active_particles = p;

		p->time = cl.time;
		p->lightstyle = lightstyle;

		for (j=0 ; j<3 ; j++)
		{
			p->org[j] = org[j] + (scale * ((rand() % 32) - 16));
			p->vel[j] = (scale * ((rand() % 384) - 192));
		}

		p->accel[0] = p->accel[1] = 0;
		p->accel[2] = -PARTICLE_GRAVITY;

		p->size = 1;
		p->growrate = 0;
	}
}


/*
===============
CL_TeleportParticles

===============
*/
void CL_TeleportParticles (vec3_t org, int lightstyle)
{
	int			i, j, k;
	cparticle_t	*p;
	float		vel;
	vec3_t		dir;

	for (i = -16; i <= 16; i += 4)
	{
		for (j = -16; j <= 16; j += 4)
		{
			for (k = -16; k <= 32; k += 4)
			{
				if (!free_particles)
					return;
				p = free_particles;
				free_particles = p->next;
				p->next = active_particles;
				active_particles = p;

				p->time = cl.time;
				p->lightstyle = lightstyle;
				
				p->org[0] = org[0] + i + (rand()&3);
				p->org[1] = org[1] + j + (rand()&3);
				p->org[2] = org[2] + k + (rand()&3);
	
				dir[0] = j*8;
				dir[1] = i*8;
				dir[2] = k*8;

				VectorNormalize (dir);
				vel = 50 + (rand()&63);
				VectorScale (dir, vel, p->vel);

				p->accel[0] = p->accel[1] = 0;
				p->accel[2] = -PARTICLE_GRAVITY;
			}
		}
	}
}


/*
==============
CL_AnimeRocketTrail
==============
*/
void CL_AnimeRocketTrail (vec3_t start, vec3_t end)
{
	vec3_t		move;
	vec3_t		vec;
	float		len;
	int			j;
	cparticle_t	*p;
	float		dec;

	VectorCopy (start, move);
	VectorSubtract (end, start, vec);
	len = VectorNormalize (vec);

//	dec = 1;
	dec = 2;
	VectorScale (vec, dec, vec);

	while (len > 0)
	{
		len -= dec;

		if (!free_particles)
			return;

		if ((rand()&6) == 0)
		{
			p = free_particles;
			free_particles = p->next;
			p->next = active_particles;
			active_particles = p;
			
			VectorClear (p->accel);
			p->time = cl.time;

			p->lightstyle = 160;
			for (j=0 ; j<3 ; j++)
			{
//				p->org[j] = move[j] + crand()*1;
				p->org[j] = move[j];
				p->vel[j] = crand();
			}
//			p->accel[2] = -PARTICLE_GRAVITY;
//			p->accel[2] = 0;

			p->flags = 0;
			p->size = 6;
			p->growrate = -4;
		}
		VectorAdd (move, vec, move);
	}
}

/*
===============
CL_ThrowerParticleEffect
===============
*/
void CL_ThrowerParticleEffect(vec3_t org, vec3_t dest)
{
	int i;
	cparticle_t *p;
	vec3_t move, vec, right, up;
	float len, distmoved;
	int vel, style;
	float d, size;

	VectorSubtract(dest, org, vec);
	len = VectorNormalize(vec);

	// get right direction vector
	VectorToAngles(vec, move);
	AngleVectors(move, NULL, right, up);

	VectorCopy (org, move);

	size = 5;
	distmoved = 0;

	// randomly offset the starting place of flame placement
	// to make it look more realistic and... random.
	vel = frand()*size*0.75;
	VectorMA (move, vel, vec, move);
	len -= vel;

	while(len > 0)
	{
		if(!free_particles)
			return;

		p = free_particles;
		free_particles = p->next;
		p->next = active_particles;
		active_particles = p;

		p->time = cl.time;

		if(distmoved < 32)
		{
			style = 146;
			size++;
		}
		else if(distmoved < 64)
		{
			style = 147;
			size++;
		}
		else if(distmoved < 128)
		{
			style = 148;
			size *= 1.1;
		}
		else if(distmoved < 192)
		{
			style = 149;
			size *= 1.2;
		}
		else
		{
			style = 150;
			size++;
		}

		p->lightstyle = style;

		for(i = 0; i < 3; i++)
			p->org[i] = move[i] + crand()*size*0.1;

		d = size*0.1;
		p->vel[0] = crand()*d;
		p->vel[1] = crand()*d;
		p->vel[2] = frand()*d;

		p->accel[0] = p->accel[1] = 0;
		p->accel[2] = frand()*6;

		p->flags = PARTICLE_FIRE;
		p->size = size;
		p->growrate = -(size*0.75);

		// sometimes add some random looking particles too
		if(!(rand() % 5))
		{
			if(!free_particles)
				return;

			p = free_particles;
			free_particles = p->next;
			p->next = active_particles;
			active_particles = p;

			p->time = cl.time;

			if(distmoved < 128)
				p->lightstyle = 149;
			else
				p->lightstyle = 151;

			d = frand()*size*0.5 + size*0.25;
			if(rand()%2)
				d *= -1;

//			p->org[0] = move[0] + crand()*size*0.5;
//			p->org[1] = move[1] + crand()*size*0.5;
//			p->org[2] = move[2] + crand()*size*0.5;
			p->org[0] = move[0];
			p->org[1] = move[1];
			p->org[2] = move[2];

			p->vel[0] = right[0]*d*2.5;
			p->vel[1] = right[1]*d*2.5;
//			p->vel[2] = frand()*size*0.2;
			p->vel[2] = crand()*size;

			p->accel[0] = p->accel[1] = 0;
			p->accel[2] = 10 + frand()*5;

			p->flags = PARTICLE_FIRE;
			p->size = (size*0.75);
			p->growrate = -size;
		}

		d = size*0.55;
		len -= d;
		distmoved += d;
		VectorMA (move, d, vec, move);
	}
}

/*
===============
CL_ThrowerRowParticleEffect
===============
*/
#define FLAMETHROWER_MOVETOLERANCE 24

void CL_ThrowerRowParticleEffect(vec3_t org, vec3_t dest, vec3_t org2, vec3_t dest2)
{
	vec3_t tmppos, tmpdest;
	float length, poslength;
	vec3_t posmove, destmove;
	vec3_t tmpvec;

	VectorSubtract(dest2, dest, tmpvec);
	length = VectorLength(tmpvec);

	// just incase difference is too small
	if(length < FLAMETHROWER_MOVETOLERANCE)
	{
		VectorAdd(org, org2, tmppos);
		VectorScale(tmppos, 0.5, tmppos);
		VectorAdd(dest, dest2, tmpdest);
		VectorScale(tmpdest, 0.5, tmpdest);

		CL_ThrowerParticleEffect(tmppos, tmpdest);

		return;
	}

//	tmpvec = org2 - org;
//	poslength = tmpvec.length();
//	length /= FLAMETHROWER_MOVETOLERANCE;
//	poslength /= length;
	VectorSubtract(org2, org, tmpvec);
	poslength = VectorLength(tmpvec);
	if(poslength < 1)
		poslength = 1;
	length /= FLAMETHROWER_MOVETOLERANCE;
	poslength /= length;
	
//	posmove = org2 - org;
//	posmove.normalize();
	VectorSubtract(org2, org, posmove);
	VectorNormalize(posmove);
	
//	destmove = dest2 - dest;
//	destmove.normalize();
//	destmove *= FLAMETHROWER_MOVETOLERANCE;
	VectorSubtract(dest2, dest, destmove);
	VectorNormalize(destmove);
	VectorScale(destmove, FLAMETHROWER_MOVETOLERANCE, destmove);
	
//	tmppos = org;
//	tmpdest = dest;
	VectorCopy(org, tmppos);
	VectorCopy(dest, tmpdest);

	// the while loop misses the first position
	CL_ThrowerParticleEffect(org, dest);

	while(length >= 1)
	{
//		tmppos += posmove*poslength;
//		tmpdest += destmove;
		VectorMA(tmppos, poslength, posmove, tmppos);
		VectorAdd(tmpdest, destmove, tmpdest);

		CL_ThrowerParticleEffect(tmppos, tmpdest);

		length--;
	}
}

/*
===============
CL_ThrowerHitParticleEffect
===============
*/

void CL_ThrowerHitParticleEffect(vec3_t org, vec3_t dir)
{
	vec3_t tmpvec, forward, right;
	vec3_t posa, posb, dira, dirb;

	VectorToAngles(dir, tmpvec);
//	tmpvec[0] *= -1;
	tmpvec[0] = 0;
	tmpvec[1] += 180;
	tmpvec[2] = 0;
	if(tmpvec[1] > 360)
		tmpvec[1] -= 360;
	AngleVectors(tmpvec, forward, right, NULL);

	VectorMA(org, 4, right, posa);
	VectorMA(org, -4, right, posb);

	VectorScale(forward, 24, dira);
	VectorMA(dira, -40, right, dirb);
	VectorMA(dira, 40, right, dira);

//	CL_FlameParticleEffect(posa, dira, posb, dirb, 1, 1, 151, 0, 20, -15);
	CL_FlameParticleEffect(posa, dira, posa, dira, 1, 1, 151, 0, 20, -15);
	CL_FlameParticleEffect(posb, dirb, posb, dirb, 1, 1, 151, 0, 20, -15);
}

/*
===============
CL_FlameParticleEffect
===============
*/
void CL_FlameParticleEffect(vec3_t org, vec3_t dira, vec3_t dest, vec3_t dirb, int seperation, int count, int lightstyle, byte flags, byte size, signed char growrate)
{
	int i, j;
	cparticle_t *p;
	float d;
	vec3_t move;
	vec3_t vec;
	vec3_t normdir;
	vec3_t dir, dirvec;
	float len;
	float dec;
	int vel;

	VectorCopy (org, move);
	VectorSubtract(dest, org, vec);
	len = VectorNormalize(vec);

//	if(lightstyle == 150) // flamethrower
//		dec = 6;
//	else if(lightstyle == 154) // hoverbike thruster
//		dec = 3;
//	else
//		dec = 12;
	dec = seperation;

	if(VectorCompare(org, dest) || (len < 1)) // start and finish are very close
	{
//		VectorCopy (org, move);
		VectorClear(vec);
		len = 1;

		for(i = 0; i < 3; i++)
			dir[i] = (dira[i] + dirb[i])*0.5;
	}
	else
	{
		// randomly offset the starting place of flame placement
		// to make it look more realistic and... random.
		vel = frand()*dec*0.9;
		VectorMA (move, vel, vec, move);
		len -= vel;

		VectorScale (vec, dec, vec);

		VectorSubtract(dirb, dira, dirvec);
		VectorNormalize(dirvec);
		VectorScale (dirvec, dec, dirvec);
		VectorCopy(dira, dir);
	}

	while(len > 0)
	{
		vel = VectorNormalize2(dir, normdir)*0.1;

		for(i = 0; i < count; i++)
		{
			if(!free_particles)
				return;
			p = free_particles;
			free_particles = p->next;
			p->next = active_particles;
			active_particles = p;

			p->time = cl.time;
			p->lightstyle = lightstyle; //flame light style

			d = rand()&vel;

			for(j = 0; j < 3; j++)
			{
				p->org[j] = move[j] + crand();
				p->vel[j] = dir[j] + normdir[j]*d + crand()*5;
			}

			if(lightstyle == 153 || lightstyle == 155) // nuke & crossbow
			{
				for(j = 0; j < 3; j++)
				{
					p->accel[j] = p->vel[j] * (-0.1);
				}
			}
			else
			{
				p->accel[0] = p->vel[0] * (-0.1);
				p->accel[1] = p->vel[1] * (-0.1);
				p->accel[2] = frand()*8;
			}

			p->flags = flags | PARTICLE_FIRE;
			p->size = size;
			p->growrate = growrate;
		}

		len -= dec;
		VectorAdd (move, vec, move);

		VectorAdd(dir, dirvec, dir);
	}
}

/*
==============
CL_FlameTrail
==============
*/
void CL_FlameTrail(vec3_t start, vec3_t end)
{
	vec3_t dir;

	dir[0] = 0;
	dir[1] = 0;
	dir[2] = 24;
	CL_FlameParticleEffect(start, dir, end, dir, 12, 1, 151, 0, 25, -10);
}

/*
==============
CL_DeathFlames
==============
*/
void CL_DeathFlames(vec3_t start, vec3_t end, centity_t *old)
{
	vec3_t dir, tmpvec;
	float len;

	// if it wasn't on before, init the counter
	if (!(old->prev.effects & EF_DEATHFLAMES))
	{
		old->trailcount = 1024;
	}

	//Com_DPrintf("old->trailcount: %i\n", old->trailcount);

	if(old->trailcount <= 0)
		return;

	if(old->trailcount < 75)
	{
		dir[0] = 0;
		dir[1] = 0;
		dir[2] = 8;

		VectorSubtract(start, end, tmpvec);
		len = VectorLength(tmpvec);

		if(len < 1)
		{
			tmpvec[0] = end[0];
			tmpvec[1] = end[1];
			tmpvec[2] = end[2] + 2;
		}
		else
		{
			VectorCopy(end, tmpvec);
		}

		CL_ParticleStreamEffectSized(start, tmpvec, 160, 6, 0, 8, 20, 0, 3, 5);
	}
	else if(old->trailcount < 150)
	{
		dir[0] = 0;
		dir[1] = 0;
		dir[2] = 12;

		VectorSubtract(start, end, tmpvec);
		len = VectorLength(tmpvec);

		if(len < 1)
		{
			tmpvec[0] = end[0];
			tmpvec[1] = end[1];
			tmpvec[2] = end[2] + 2;
		}
		else
		{
			VectorCopy(end, tmpvec);
		}

		CL_ParticleStreamEffectSized(start, tmpvec, 160, 6, 0, 8, 30, 0, 4, 10);
	}
	else if(old->trailcount < 450)
	{
		dir[0] = 0;
		dir[1] = 0;
		dir[2] = 16;

		VectorSubtract(start, end, tmpvec);
		len = VectorLength(tmpvec);

		if(len < 1)
		{
			tmpvec[0] = end[0];
			tmpvec[1] = end[1];
			tmpvec[2] = end[2] + 2;
		}
		else
		{
			VectorCopy(end, tmpvec);
		}

		CL_ParticleStreamEffectSized(start, tmpvec, 160, 6, 0, 8, 40, 0, 5, 15);
	}
	else if(old->trailcount < 600)
	{
		dir[0] = 0;
		dir[1] = 0;
		dir[2] = 8;
		CL_FlameParticleEffect(start, dir, end, dir, 12, 1, 151, 0, 18, -8);

		dir[0] = 0;
		dir[1] = 0;
		dir[2] = 12;

		VectorSubtract(start, end, tmpvec);
		len = VectorLength(tmpvec);

		if(len < 1)
		{
			tmpvec[0] = end[0];
			tmpvec[1] = end[1];
			tmpvec[2] = end[2] + 2;
		}
		else
		{
			VectorCopy(end, tmpvec);
		}

		CL_ParticleStreamEffectSized(start, tmpvec, 160, 6, 0, 8, 30, 0, 4, 10);
	}
	else if(old->trailcount < 750)
	{
		dir[0] = 0;
		dir[1] = 0;
		dir[2] = 16;
		CL_FlameParticleEffect(start, dir, end, dir, 12, 1, 151, 0, 22, -10);

		dir[0] = 0;
		dir[1] = 0;
		dir[2] = 8;

		VectorSubtract(start, end, tmpvec);
		len = VectorLength(tmpvec);

		if(len < 1)
		{
			tmpvec[0] = end[0];
			tmpvec[1] = end[1];
			tmpvec[2] = end[2] + 2;
		}
		else
		{
			VectorCopy(end, tmpvec);
		}

		CL_ParticleStreamEffectSized(start, tmpvec, 160, 6, 0, 8, 20, 0, 3, 6);
	}
	else
	{
		dir[0] = 0;
		dir[1] = 0;
		dir[2] = 24;
		CL_FlameParticleEffect(start, dir, end, dir, 12, 1, 151, 0, 25, -10);
	}

	old->trailcount -= 3;
	if(old->trailcount < 0)
		old->trailcount = 0;
}

/*
===============
CL_NukeEffect
===============
*/
void CL_NukeEffect(vec3_t org, int size, int lightstyle)
{
	int p, prate, y, yrate, yofs, seperation;
	vec3_t pos, oldpos;
	vec3_t dir, olddir, dirang;
	int radius;
	byte particlesize;
	signed char growrate;
	int plimit;

	if(size < 130)
	{
		if(size < 16)
			size = 16;

//		prate = 12;
//		yrate = 15;
//		seperation = 12;
		plimit = 70;
		prate = 15;
		yrate = 20;
		seperation = 20;
	}
	else if(size < 256)
	{
//		prate = 12;
//		yrate = 15;
//		seperation = 12;
		plimit = 75;
		prate = 15;
		yrate = 20;
		seperation = 20;
	}
	else if(size < 512)
	{
//		prate = 8;
//		yrate = 12;
//		seperation = 10;
		plimit = 80;
		prate = 10;
		yrate = 15;
		seperation = 15;
	}
	else if(size < 1024)
	{
//		prate = 5;
//		yrate = 8;
//		seperation = 10;
		plimit = 85;
		prate = 8;
		yrate = 12;
		seperation = 12;
	}
	else
	{
//		prate = 3;
//		yrate = 5;
//		seperation = 10;
		plimit = 85;
		prate = 5;
		yrate = 10;
		seperation = 10;
	}

	radius = size * 0.35 + 10;
	if(radius > 255)
		particlesize = 255;
	else
		particlesize = radius;
	particlesize *= 0.65;

	// do differently for crossbow
	if(lightstyle == 155)
	{
		prate = 10;
		seperation = 15;
		particlesize *= 0.3;
	}

	radius *= 0.45;
	if(radius > 127)
		growrate = 127;
	else
		growrate = radius;
	growrate *= 0.95;

	radius = size * 0.5;

	for(p = 0 - plimit; p < plimit; p += prate)
	{
		VectorClear(oldpos);
		yofs = rand()%yrate;
		for(y = 0; y < (360 + yrate); y += yrate)
		{
			dirang[0] = p;
			dirang[1] = y + yofs;
			if(dirang[1] > 360)
				dirang[1] -= 360;
			dirang[2] = 0;

			AngleVectors(dirang, dir, NULL, NULL);

			VectorMA(org, radius, dir, pos);

			// make plasma bow explosions expand faster
			if(lightstyle == 155)
				VectorScale(dir, size*(3 + rand()%3), dir);
			else
				VectorScale(dir, size, dir);

			if(oldpos[0] || oldpos[1] || oldpos[2])
				CL_FlameParticleEffect(oldpos, olddir, pos, dir, seperation, 1, lightstyle, PARTICLE_HITSOLID|PARTICLE_HITREMOVE, particlesize, growrate);

			VectorCopy(pos, oldpos);
			VectorCopy(dir, olddir);
		}
	}
}

/*
===============
CL_HoverBoostEffect
===============
*/
void CL_HoverBoostEffect(vec3_t org, int yawofs)
{
	float y;
	vec3_t pos, dir2, dirang;

	yawofs += 45;
	if(yawofs > 360)
		yawofs -= 360;

	dirang[0] = 0;
	dirang[2] = 0;

	for(y = 0; y < 360; y += 90)
	{
		dirang[1] = yawofs + y;
		if(dirang[1] > 360)
			dirang[1] -= 360;
		AngleVectors(dirang, dir2, NULL, NULL);
		VectorMA(org, 8, dir2, pos);
		dir2[2] -= 0.25;
		VectorScale(dir2, 1.5, dir2);
//		CL_SinParticleEffectSized(pos, dir2, 123, 15, false, 0, 3, -2);
		CL_SinParticleEffect(pos, dir2, 123, 15, false, 0);

		dirang[1] = yawofs + y + 8;
		if(dirang[1] > 360)
			dirang[1] -= 360;
		AngleVectors(dirang, dir2, NULL, NULL);
		VectorMA(org, 8, dir2, pos);
		dir2[2] -= 0.4;
		CL_SinParticleEffect(pos, dir2, 123, 15, false, 0);

		dirang[1] = yawofs + y - 8;
		if(dirang[1] > 360)
			dirang[1] -= 360;
		AngleVectors(dirang, dir2, NULL, NULL);
		VectorMA(org, 8, dir2, pos);
		dir2[2] -= 0.4;
		CL_SinParticleEffect(pos, dir2, 123, 15, false, 0);
	}
}

/*
==============
CL_HoverParticleEffect
==============
Called by CL_HoverEffect
*/

static void CL_HoverParticleEffect(vec3_t org, vec3_t norm, csurface_t *surface, float dist)
{
	float y, yofs, tmpflt;
	vec3_t pos, dir2, dirang;
	int lightstyle, count;
//	int surfflags;
//	int surftype;

	if(!surface)
	{
		lightstyle = 165;
		count = 2;
	}
	else
	{
//		surfflags = surface->flags;
//		surftype = SURFACETYPE_FROM_FLAGS(surfflags);

		// set the type of particles that should be made
		switch(surface->flags & MASK_SURF_TYPE)
		{
			// lots of gray dust
		case SURF_TYPE_GRAVEL:
			lightstyle = 161;
			count = 2;
			break;
			// gray dust
		case SURF_TYPE_STONE:
		case SURF_TYPE_CONCRETE:
			lightstyle = 161;
			count = 1;
			break;
			// lots of brown dust
		case SURF_TYPE_DIRT:
			lightstyle = 162;
			count = 2;
			break;
			// slight gray dust
		case SURF_TYPE_GLASS:
		case SURF_TYPE_MONITOR:
			lightstyle = 163;
			count = 1;
			break;
			// brown/gray dust
		case SURF_TYPE_WOOD:
			lightstyle = 162;
			count = 1;
			break;
			// slight gray dust
		case SURF_TYPE_METAL:
		case SURF_TYPE_GRILL:
			lightstyle = 163;
			count = 1;
			break;
			// lots of bluish particles
		case SURF_TYPE_WATER:
			lightstyle = 165;
			count = 2;
			break;
			// red particles (used for lava)
		case SURF_TYPE_FLESH:
			lightstyle = 166;
			count = 2;
			break;
		case SURF_TYPE_FABRIC:
		case SURF_TYPE_VEGETATION:
		case SURF_TYPE_PAPER:
			// dunno, just toss some gray dust
		default:
			//Com_DPrintf("default\n");
			lightstyle = 163;
			count = 1;
			break;
		}
	}

	// set the yaw incriment to use
	if(count == 2)
		count = 20;
	else
		count = 60;

	if(dist < 512)
		count += 50;
	else if(dist < 1024)
		count += 90;
	else if(dist < 1536)
		count += 130;
	else if(dist < 2048)
		count += 180;
	else
		count += 360;

	count += rand()%20;
	yofs = rand()%count;

	// have to handle perfectly horizontal
	// surfaces slightly differently
	// than sloped surfaces
	if(norm[2] == 1)
	{
		dirang[0] = 0;
		dirang[2] = 0;

		for(y = 0; y < 360; y += count)
		{
			dirang[1] = y + yofs;
			if(dirang[1] > 360)
				dirang[1] -= 360;
			AngleVectors(dirang, dir2, NULL, NULL);
			tmpflt = 8 + rand()%12;
			VectorMA(org, tmpflt, dir2, pos);
			dir2[2] += 0.3;
			VectorScale(dir2, 1.75, dir2);
//			CL_SinParticleEffectSized(pos, dir2, lightstyle, 1, false, 0, 1, -1);
			CL_SinParticleEffect(pos, dir2, lightstyle, 1, false, 0);
		}
	}
	else if(norm[2] == -1)
	{
		dirang[0] = 0;
		dirang[2] = 0;

		for(y = 0; y < 360; y += count)
		{
			dirang[1] = y + yofs;
			if(dirang[1] > 360)
				dirang[1] -= 360;
			AngleVectors(dirang, dir2, NULL, NULL);
			tmpflt = 8 + rand()%12;
			VectorMA(org, tmpflt, dir2, pos);
			dir2[2] -= 0.3;
			VectorScale(dir2, 1.75, dir2);
//			CL_SinParticleEffectSized(pos, dir2, lightstyle, 1, false, 0, 1, -1);
			CL_SinParticleEffect(pos, dir2, lightstyle, 1, false, 0);
		}
	}
	else
	{
		dir2[0] = -norm[0];
		dir2[1] = -norm[1];
		dir2[2] = norm[2];
		VectorToAngles(dir2, dirang);

		for(y = 0; y < 360; y += count)
		{
			dirang[2] = y + yofs;
			if(dirang[2] > 360)
				dirang[2] -= 360;
			AngleVectors(dirang, NULL, NULL, dir2);
			tmpflt = 8 + rand()%12;
			VectorMA(org, tmpflt, dir2, pos);
			VectorMA(dir2, 0.3, norm, dir2);
			VectorScale(dir2, 1.75, dir2);
//			CL_SinParticleEffectSized(pos, dir2, lightstyle, 1, false, 0, 1, -1);
			CL_SinParticleEffect(pos, dir2, lightstyle, 1, false, 0);
		}
	}
}

/*
==============
CL_HoverEffect
==============
*/
void CL_HoverEffect(entity_t *ent)
{
	vec3_t pos, posofs, end;
	vec3_t mins, maxs;
	trace_t trace;
	int mask, i, j;
	char bonename[32];
	vec3_t trans2[3], entmat[3];

	int defhandle;
	vec3_t trans[3];
	vec3_t orient;
	vec3_t offset;
	int groupindex;
	int tri_num;
	int anim;

	float dist;
	
	if(!ent || !ent->model)
		return;

//	return; //***

	// don't bother with this effect if too far away
	VectorSubtract(ent->origin, cl.refdef.vieworg, offset);
	dist = VectorLength(offset) * (cl.refdef.fov_x / 110);
	if(dist > 2560)
		return;

	defhandle = re.SINMDL_GetDefHandle(ent->model);
	if(defhandle < 0)
		return;

//	anim = SINMDL_Anim_Random(defhandle, "idle");
//	if(anim < 0)
//		return;
	anim = 0;

	VectorSet(mins, -4, -4, -4);
	VectorSet(maxs, 4, 4, 4);
	mask = MASK_SOLID|MASK_WATER;

	for(i = 0; i < 3; i++)
	{
		switch(i)
		{
		case 0:
			strcpy(bonename, "pad1");
			break;
		case 1:
			strcpy(bonename, "pad2");
			break;
		case 2:
			strcpy(bonename, "pad3");
			break;
		}

		if(SINMDL_GetBoneInfo(defhandle, bonename, &groupindex, &tri_num, orient))
		{
			if(SINMDL_GetBoneTransform(defhandle, groupindex, tri_num, orient, anim, 0, ent->scale, trans, offset))
			{
//				AnglesToMat(ent->angles, entmat);
				if(ent->parent)
				{
					entity_t *parent;
					
					parent = V_FindEntity(ent->parent);
					if(parent)
						AnglesToMat(parent->angles, entmat);
					else
						AnglesToMat(ent->angles, entmat);
				}
				else
				{
					AnglesToMat(ent->angles, entmat);
				}
				VectorClear(posofs);
				for(j = 0; j < 3; j++)
				{
					VectorMA(posofs, offset[j], entmat[j], posofs);
				}
				VectorAdd(ent->origin, posofs, pos);

				R_ConcatRotations(trans, entmat, trans2);
				VectorMA(pos, 64, trans2[0], end);

				trace = CL_GenericTrace(pos, mins,maxs, end, mask, false, 0);
				if(trace.fraction < 1)
				{
					CL_HoverParticleEffect(trace.endpos, trace.plane.normal, trace.surface, dist);
				}
			}
		}
	}
}

/*
==============
CL_HoverThrust
==============
*/
void CL_HoverThrust(vec3_t prevorg, entity_t *ent)
{
	vec3_t diff, dir;
	vec3_t pos1, pos2, posofs;
	vec3_t trans2[3], entmat[3];
	int i;

	int defhandle;
	vec3_t trans[3];
	vec3_t orient;
	vec3_t offset;
	int groupindex;
	int tri_num;
	int anim;
	
	if(!ent || !ent->model)
		return;

//	return; //***

	defhandle = re.SINMDL_GetDefHandle(ent->model);
	if(defhandle < 0)
		return;

//	anim = SINMDL_Anim_Random(defhandle, "idle");
//	if(anim < 0)
//		return;
	anim = 0;

	VectorSubtract(prevorg, ent->origin, diff);
	VectorScale(diff, 5, diff);

	if(SINMDL_GetBoneInfo(defhandle, "thruster", &groupindex, &tri_num, orient))
	{
		if(SINMDL_GetBoneTransform(defhandle, groupindex, tri_num, orient, anim, 0, ent->scale, trans, offset))
		{
			if(ent->parent)
			{
				entity_t *parent;

				parent = V_FindEntity(ent->parent);
				if(parent)
					AnglesToMat(parent->angles, entmat);
				else
					AnglesToMat(ent->angles, entmat);
			}
			else
			{
				AnglesToMat(ent->angles, entmat);
			}
			VectorClear(posofs);
			for(i = 0; i < 3; i++)
			{
				VectorMA(posofs, offset[i], entmat[i], posofs);
			}
			VectorAdd(prevorg, posofs, pos1);
			VectorAdd(ent->origin, posofs, pos2);

			R_ConcatRotations(trans, entmat, trans2);

			VectorMA(diff, -75, entmat[0], dir);

			CL_FlameParticleEffect(pos1, dir, pos2, dir, 4, 1, 152, 0, 15, -10);
		}
	}
}

/*
==============
CL_HoverTurbo
==============
*/
void CL_HoverTurbo(vec3_t prevorg, entity_t *ent)
{
	vec3_t diff, dir;
	vec3_t pos1, pos2, posofs;
	vec3_t trans2[3], entmat[3];
	int i;

	int defhandle;
	vec3_t trans[3];
	vec3_t orient;
	vec3_t offset;
	int groupindex;
	int tri_num;
	int anim;
	
	if(!ent || !ent->model)
		return;

//	return; //***

	defhandle = re.SINMDL_GetDefHandle(ent->model);
	if(defhandle < 0)
		return;

//	anim = SINMDL_Anim_Random(defhandle, "idle");
//	if(anim < 0)
//		return;
	anim = 0;

	VectorSubtract(prevorg, ent->origin, diff);
	VectorScale(diff, 5, diff);
	if(ent->parent)
	{
		entity_t *parent;

		parent = V_FindEntity(ent->parent);
		if(parent)
			AnglesToMat(parent->angles, entmat);
		else
			AnglesToMat(ent->angles, entmat);
	}
	else
	{
		AnglesToMat(ent->angles, entmat);
	}

	if(SINMDL_GetBoneInfo(defhandle, "turbo1", &groupindex, &tri_num, orient))
	{
		if(SINMDL_GetBoneTransform(defhandle, groupindex, tri_num, orient, anim, 0, ent->scale, trans, offset))
		{
			VectorClear(posofs);
			for(i = 0; i < 3; i++)
			{
				VectorMA(posofs, offset[i], entmat[i], posofs);
			}
			VectorAdd(prevorg, posofs, pos1);
			VectorAdd(ent->origin, posofs, pos2);

			R_ConcatRotations(trans, entmat, trans2);

			VectorMA(diff, -75, entmat[0], dir);

			CL_FlameParticleEffect(pos1, dir, pos2, dir, 5, 1, 154, 0, 6, 15);
		}
	}

	if(SINMDL_GetBoneInfo(defhandle, "turbo2", &groupindex, &tri_num, orient))
	{
		if(SINMDL_GetBoneTransform(defhandle, groupindex, tri_num, orient, anim, 0, ent->scale, trans, offset))
		{
			VectorClear(posofs);
			for(i = 0; i < 3; i++)
			{
				VectorMA(posofs, offset[i], entmat[i], posofs);
			}
			VectorAdd(prevorg, posofs, pos1);
			VectorAdd(ent->origin, posofs, pos2);

			R_ConcatRotations(trans, entmat, trans2);

			VectorMA(diff, -75, entmat[0], dir);

			CL_FlameParticleEffect(pos1, dir, pos2, dir, 5, 1, 154, 0, 6, 15);
		}
	}
}


/*
==============
CL_SinParticleEffectFull
==============
*/
void CL_SinParticleEffectFull (vec3_t org, vec3_t dir, int lightstyle, int count, qboolean scatter, byte flags, byte size, signed char growrate, int speed, int spread, vec3_t accel)
{
	int			i, j;
	cparticle_t	*p;
	float		d;

	for (i=0 ; i<count ; i++)
	{
		if (!free_particles)
			return;
		p = free_particles;
		free_particles = p->next;
		p->next = active_particles;
		active_particles = p;

		p->time = cl.time;
		p->lightstyle = lightstyle;
		p->flags = flags;

		if (scatter)
		{
//			d = rand()&31;
			d = rand()&(spread*2);
			for (j=0 ; j<3 ; j++)
			{
				p->org[j] = org[j] + ((rand()&7)-4) + d*dir[j];
//				p->vel[j] = crand()*20;
				p->vel[j] = crand()*speed;
				p->accel[j] = accel[j];
			}
		}
		else
		{
//			d = rand()&15;
//			d = rand()&((int)(speed*0.5));
			d = rand()&spread;
			for (j=0 ; j<3 ; j++)
			{
				p->org[j] = org[j];
//				p->vel[j] = (20+d)*dir[j] + crand()*d;
				p->vel[j] = (speed+d)*dir[j] + crand()*d;
				p->accel[j] = accel[j];
			}
		}

//		p->accel[0] = p->accel[1] = 0;
//		p->accel[2] = -PARTICLE_GRAVITY;

		p->size = size;
		p->growrate = growrate;
	}
}

/*
==============
CL_PlasmaTrail
==============
*/
void CL_PlasmaTrail(vec3_t start, vec3_t end, centity_t *owner)
{
	CL_ParticleStreamEffectSized(start, end, 156, 1.5, 0, 0, 0, PARTICLE_ADDITIVE, 2, -5);
}

/*
==============
CL_PlasmaTrail2
==============
*/
void CL_PlasmaTrail2(vec3_t start, vec3_t end)
{
	CL_ParticleStreamEffectSized(start, end, 152, 2, 4, 1, 8, PARTICLE_FIRE, 3, 6);
//	vec3_t dir;

//	dir[0] = 0;
//	dir[1] = 0;
//	dir[2] = 24;
//	CL_FlameParticleEffect(start, dir, end, dir, 5, 1, 155, 0, 5, 10);
}

/*
==============
CL_PlasmaExplosion
==============
*/
void CL_PlasmaExplosion(vec3_t org, float scale)
{
	int p, prate, y, yrate, yofs, seperation;
	vec3_t pos, oldpos;
	vec3_t dir, olddir, dirang;
	int radius;
	byte particlesize;
	signed char growrate;
	int plimit;

	plimit = 75;
	prate = 15;
	yrate = 25;
	seperation = 15;

	radius = scale * 0.35 + 10;
	if(radius > 255)
		particlesize = 255;
	else
		particlesize = radius;
//	particlesize *= 0.75;

	growrate = 0 - particlesize*2;
//	radius *= 0.45;
//	if(radius > 127)
//		growrate = -127;
//	else
//		growrate = 0 - radius;

	radius = scale * 0.5;

	for(p = 0 - plimit; p < plimit; p += prate)
	{
		VectorClear(oldpos);
		yofs = rand()%yrate;
		for(y = 0; y < (360 + yrate); y += yrate)
		{
			dirang[0] = p;
			dirang[1] = y + yofs;
			if(dirang[1] > 360)
				dirang[1] -= 360;
			dirang[2] = 0;

			AngleVectors(dirang, dir, NULL, NULL);

			VectorMA(org, radius, dir, pos);

			VectorScale(dir, scale*(2 + rand()%3), dir);

			if(oldpos[0] || oldpos[1] || oldpos[2])
			{
				if(scale < 32)
					CL_FlameParticleEffect(oldpos, olddir, pos, dir, seperation, 1, 152, 0, particlesize, growrate);
				else
					CL_FlameParticleEffect(oldpos, olddir, pos, dir, seperation, 1, 155, 0, particlesize, growrate);
			}

			VectorCopy(pos, oldpos);
			VectorCopy(dir, olddir);
		}
	}
}

void CL_NukeTrailParticles (vec3_t org)
{
	int			i, j;
	cparticle_t	*p;

	for (i=0 ; i<5 ; i++)
	{
		if (!free_particles)
			return;
		p = free_particles;
		free_particles = p->next;
		p->next = active_particles;
		active_particles = p;
		
		p->time = cl.time;
		p->lightstyle = 151;
		
		for (j=0 ; j<3 ; j++)
		{
			p->org[j] = org[j] + ((rand()%8)-4);
			p->vel[j] = (rand()%200)-100;
		}
		
		p->accel[0] = p->accel[1] = 0;
		p->accel[2] = -PARTICLE_GRAVITY;
		
		p->size = 1;
		p->growrate = 0;
		p->flags = PARTICLE_ADDITIVE;
	}
}

void V_AddSinParticle (vec3_t org, float color[4]);

/*
===============
CL_AddParticles
===============
*/
void CL_AddParticles (void)
{
	cparticle_t		*p, *next;
	float			pcolor[4];
	float			time, time2;
	vec3_t			org;
	cparticle_t		*active, *tail;
	float size;

	active = NULL;
	tail = NULL;

	for (p=active_particles ; p ; p=next)
	{
		next = p->next;

		// Random colored particles
		if (p->flags & PARTICLE_RANDOM)
		{
			clightstyle_t	*ls;
			float			alpha;
			byte		*f;
			int			i;

			ls = &cl_lightstyle[p->lightstyle];

			// If the index is -1, then the color has not been picked yet
			if (p->index == -1)
				p->index = (int)(frand() * ls->length);

			// Grab the color from the lightstyle
			f = ls->map[p->index];

			for (i = 0; i < 4; i++)
				pcolor[i] = (float)f[i]/255.0f;

			// Calculate the alpha based on time.
			time = (cl.time - p->time)*0.001f;

			if (!ls->hasalpha)
				pcolor[3] = 1;

			alpha = pcolor[3] + time * (-1.0f / 3.0f);
			if (alpha <= 0)
			{ // dead
				p->next = free_particles;
				free_particles = p;
				p->flags	= 0;
				p->index	= -1;
				p->size		= 0;
				p->growrate	= 0;
				continue;
			}
			if (alpha > 1.0f)
				alpha = 1.0f;

			pcolor[3] = alpha;
		}
		else
		{
			if (CL_LightStyleColor(p->lightstyle, (cl.time - p->time), pcolor))
			{	// dead
				p->next = free_particles;
				p->flags = 0;
				p->index = -1;
				p->size		= 0;
				p->growrate	= 0;
				free_particles = p;
				continue;
			}
		}
		time = (cl.time - p->time)*0.001;
		time2 = time*time;

		org[0] = p->org[0] + p->vel[0]*time + p->accel[0]*time2;
		org[1] = p->org[1] + p->vel[1]*time + p->accel[1]*time2;
		org[2] = p->org[2] + p->vel[2]*time + p->accel[2]*time2;

		// calc particle's current size
		if(!p->growrate && p->size <= 1)
		{
			// size of 0 is intrepreted as a particle
			// that's always regular size by the ref_gl.dll
			size = 0;
		}
		else
		{
			size = (float)p->size + ((float)p->growrate)*time;
			if(size < 0.25) // kill it if too small
			{
				// dead
				p->next = free_particles;
				p->flags = 0;
				p->size		= 0;
				p->growrate	= 0;
				p->index = -1;
				free_particles = p;
				continue;
			}
		}

		//check if collision detecting
		if(p->flags & (PARTICLE_HITSOLID | PARTICLE_HITENTS))
		{
			// only check if particle is moving
			if(p->accel[2])
			{
				int mask;

				mask = 0;
				if(p->flags & PARTICLE_HITSOLID)
					mask |= MASK_SOLID;
				if(p->flags & PARTICLE_HITENTS)
					mask |= CONTENTS_MONSTER;

				// collided with something
				if(CM_PointContents(org, 0) & mask)
				{
					if(p->flags & PARTICLE_HITREMOVE)
					{
						// dead
						p->next = free_particles;
						p->flags = 0;
						p->size		= 0;
						p->growrate	= 0;
						p->index = -1;
						free_particles = p;
						continue;
					}

					// stop in place
					time = cls.renderFrametime*2;
					time2 = time*time;

					p->org[0] = org[0] -= (p->vel[0]*time + p->accel[0]*time2);
					p->org[1] = org[1] -= (p->vel[1]*time + p->accel[1]*time2);
					p->org[2] = org[2] -= (p->vel[2]*time + p->accel[2]*time2);

					p->vel[0] = p->vel[1] = p->vel[2] = 0;
					p->accel[0] = p->accel[1] = p->accel[2] = 0;
				}
			}
		}

		// had to move this down to here because of other particle removing checks
		p->next = NULL;

		if (!tail)
		{
			active = tail = p;
		}
		else
		{
			tail->next = p;
			tail = p;
		}
		V_AddParticle(org, pcolor, p->flags, size);
	}

	active_particles = active;
}


/*
==============
CL_EntityEvent

An entity has just been parsed that has an event value
==============
*/
#if 0
void CL_EntityEvent (entity_state_t *ent)
{
}
#endif

/*
==============
CL_ClearEffects

==============
*/
void CL_ClearEffects (void)
{
	CL_ClearParticles ();
	CL_ClearDlights ();
	CL_ClearLightStyles ();
	CONSOLE_ClearConsoles();
	CL_ClearTriangles();
	CL_ClearTempmodels();
	CL_ClearTempdlights();
}
