/*
Copyright (C) 1996-1997 Id Software, Inc.

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.

*/
// cl_tent.c -- client side temporary entities

#include "quakedef.h"
#include "particles.h"
entity_state_t *CL_FindPacketEntity(int num);

#define R_AddDecals(a)	//disabled for now

#ifdef Q2CLIENT
typedef enum
{
	Q2TE_GUNSHOT,	//0
	Q2TE_BLOOD,
	Q2TE_BLASTER,
	Q2TE_RAILTRAIL,
	Q2TE_SHOTGUN,
	Q2TE_EXPLOSION1,
	Q2TE_EXPLOSION2,
	Q2TE_ROCKET_EXPLOSION,
	Q2TE_GRENADE_EXPLOSION,
	Q2TE_SPARKS,
	Q2TE_SPLASH,	//10
	Q2TE_BUBBLETRAIL,
	Q2TE_SCREEN_SPARKS,
	Q2TE_SHIELD_SPARKS,
	Q2TE_BULLET_SPARKS,
	Q2TE_LASER_SPARKS,
	Q2TE_PARASITE_ATTACK,
	Q2TE_ROCKET_EXPLOSION_WATER,
	Q2TE_GRENADE_EXPLOSION_WATER,
	Q2TE_MEDIC_CABLE_ATTACK,
	Q2TE_BFG_EXPLOSION,	//20
	Q2TE_BFG_BIGEXPLOSION,
	Q2TE_BOSSTPORT,			// used as '22' in a map, so DON'T RENUMBER!!!
	Q2TE_BFG_LASER,
	Q2TE_GRAPPLE_CABLE,
	Q2TE_WELDING_SPARKS,
	Q2TE_GREENBLOOD,
	Q2TE_BLUEHYPERBLASTER,
	Q2TE_PLASMA_EXPLOSION,
	Q2TE_TUNNEL_SPARKS,
//ROGUE
	Q2TE_BLASTER2,	//30
	Q2TE_RAILTRAIL2,
	Q2TE_FLAME,
	Q2TE_LIGHTNING,
	Q2TE_DEBUGTRAIL,
	Q2TE_PLAIN_EXPLOSION,
	Q2TE_FLASHLIGHT,
	Q2TE_FORCEWALL,
	Q2TE_HEATBEAM,
	Q2TE_MONSTER_HEATBEAM,
	Q2TE_STEAM,		//40
	Q2TE_BUBBLETRAIL2,
	Q2TE_MOREBLOOD,
	Q2TE_HEATBEAM_SPARKS,
	Q2TE_HEATBEAM_STEAM,
	Q2TE_CHAINFIST_SMOKE,
	Q2TE_ELECTRIC_SPARKS,
	Q2TE_TRACKER_EXPLOSION,
	Q2TE_TELEPORT_EFFECT,
	Q2TE_DBALL_GOAL,
	Q2TE_WIDOWBEAMOUT,	//50
	Q2TE_NUKEBLAST,
	Q2TE_WIDOWSPLASH,
	Q2TE_EXPLOSION1_BIG,
	Q2TE_EXPLOSION1_NP,
	Q2TE_FLECHETTE,
//ROGUE


//CODERED
	CRTE_LEADERBLASTER,	//56
	CRTE_BLASTER_MUZZLEFLASH,
	CRTE_BLUE_MUZZLEFLASH,
	CRTE_SMART_MUZZLEFLASH,
	CRTE_LEADERFIELD,	//60
	CRTE_DEATHFIELD,
	CRTE_BLASTERBEAM,
	CRTE_STAIN,
	CRTE_FIRE,
	CRTE_CABLEGUT,
	CRTE_SMOKE
//CODERED
} temp_event_t;

#define Q2SPLASH_UNKNOWN		0
#define Q2SPLASH_SPARKS		1
#define Q2SPLASH_BLUE_WATER	2
#define Q2SPLASH_BROWN_WATER	3
#define Q2SPLASH_SLIME		4
#define	Q2SPLASH_LAVA			5
#define Q2SPLASH_BLOOD		6
#endif



	// hexen 2
#define TE_STREAM_CHAIN			25
#define TE_STREAM_SUNSTAFF1		26
#define TE_STREAM_SUNSTAFF2		27
#define TE_STREAM_LIGHTNING		28
#define TE_STREAM_COLORBEAM		29
#define TE_STREAM_ICECHUNKS		30
#define TE_STREAM_GAZE			31
#define TE_STREAM_FAMINE		32


#define	MAX_BEAMS	64
typedef struct
{
	int		entity;
	short	tag;
	qbyte	flags;
	qbyte	type;
	qbyte	skin;
	struct model_s	*model;
	float	endtime;
	float	alpha;
	vec3_t	start, end;
	int		particleeffect;
	trailstate_t *trailstate;
	trailstate_t *emitstate;
} beam_t;

beam_t		cl_beams[MAX_BEAMS];

#define	MAX_EXPLOSIONS	32
typedef struct
{
	vec3_t	origin;
	vec3_t	oldorigin;

	int firstframe;
	int numframes;

	int		type;
	vec3_t	angles;
	int		flags;
	float	alpha;
	float	start;
	float	framerate;
	model_t	*model;
	int skinnum;
} explosion_t;

explosion_t	cl_explosions[MAX_EXPLOSIONS];

static int explosions_running;
static int beams_running;

sfx_t			*cl_sfx_wizhit;
sfx_t			*cl_sfx_knighthit;
sfx_t			*cl_sfx_tink1;
sfx_t			*cl_sfx_ric1;
sfx_t			*cl_sfx_ric2;
sfx_t			*cl_sfx_ric3;
sfx_t			*cl_sfx_r_exp3;

cvar_t	cl_expsprite = SCVAR("cl_expsprite", "0");
cvar_t  r_explosionlight = SCVARC("r_explosionlight", "1", Cvar_Limiter_ZeroToOne_Callback);
cvar_t	cl_truelightning = SCVARF("cl_truelightning", "0",	CVAR_SEMICHEAT);
cvar_t  cl_beam_trace = SCVAR("cl_beam_trace", "0");

typedef struct {
	sfx_t **sfx;
	char *efname;
} tentsfx_t;
tentsfx_t tentsfx[] =
{
	{&cl_sfx_wizhit, "wizard/hit.wav"},
	{&cl_sfx_knighthit, "hknight/hit.wav"},
	{&cl_sfx_tink1, "weapons/tink1.wav"},
	{&cl_sfx_ric1, "weapons/ric1.wav"},
	{&cl_sfx_ric2, "weapons/ric2.wav"},
	{&cl_sfx_ric3, "weapons/ric3.wav"},
	{&cl_sfx_r_exp3, "weapons/r_exp3.wav"}
};

vec3_t playerbeam_end[MAX_SPLITS];
/*
=================
CL_ParseTEnts
=================
*/
void CL_InitTEnts (void)
{
	int i;
	for (i = 0; i < sizeof(tentsfx)/sizeof(tentsfx[0]); i++)
	{
		if (COM_FCheckExists(va("sound/%s", tentsfx[i].efname)))
			*tentsfx[i].sfx = S_PrecacheSound (tentsfx[i].efname);
		else
			*tentsfx[i].sfx = NULL;
	}

	Cvar_Register (&cl_expsprite, "Temporary entity control");
	Cvar_Register (&cl_truelightning, "Temporary entity control");
	Cvar_Register (&cl_beam_trace, "Temporary entity control");
	Cvar_Register (&r_explosionlight, "Temporary entity control");
}

#ifdef Q2CLIENT
enum {
	q2cl_mod_explode,
	q2cl_mod_smoke,
	q2cl_mod_flash,
	q2cl_mod_parasite_segment,
	q2cl_mod_grapple_cable,
	q2cl_mod_parasite_tip,
	q2cl_mod_explo4,
	q2cl_mod_bfg_explo,
	q2cl_mod_powerscreen,
	q2cl_mod_max
};
typedef struct {
	char *modelname;

} tentmodels_t;
tentmodels_t q2tentmodels[q2cl_mod_max] = {
	{"models/objects/explode/tris.md2"},
	{"models/objects/smoke/tris.md2"},
	{"models/objects/flash/tris.md2"},
	{"models/monsters/parasite/segment/tris.md2"},
	{"models/ctf/segment/tris.md2"},
	{"models/monsters/parasite/tip/tris.md2"},
	{"models/objects/r_explode/tris.md2"},
	{"sprites/s_bfg2.sp2"},
	{"models/items/armor/effect/tris.md2"}
};

int CLQ2_RegisterTEntModels (void)
{
//	int i;
//	for (i = 0; i < q2cl_mod_max; i++)
//		if (!CL_CheckOrDownloadFile(q2tentmodels[i].modelname, false))
//			return false;

	return true;
}	
#endif
/*
=================
CL_ClearTEnts
=================
*/
void CL_ClearTEnts (void)
{
	memset (&cl_beams, 0, sizeof(cl_beams));
	memset (&cl_explosions, 0, sizeof(cl_explosions));
}

/*
=================
CL_AllocExplosion
=================
*/
explosion_t *CL_AllocExplosion (void)
{
	int		i;
	float	time;
	int		index;
	
	for (i=0; i < explosions_running; i++)
	{
		if (!cl_explosions[i].model)
		{
			cl_explosions[i].firstframe = -1;
			cl_explosions[i].framerate = 10;
			return &cl_explosions[i];
		}
	}

	if (i == explosions_running && i != MAX_EXPLOSIONS)
	{
		explosions_running++;
		cl_explosions[i].firstframe = -1;
		cl_explosions[i].framerate = 10;
		return &cl_explosions[i];
	}

// find the oldest explosion
	time = cl.time;
	index = 0;

	for (i=0 ; i<MAX_EXPLOSIONS ; i++)
		if (cl_explosions[i].start < time)
		{
			time = cl_explosions[i].start;
			index = i;
		}
	cl_explosions[index].firstframe = -1;
	cl_explosions[index].framerate = 10;
	return &cl_explosions[index];
}

/*
=================
CL_ParseBeam
=================
*/
beam_t	*CL_NewBeam (int entity, int tag)
{
	beam_t	*b;
	int i;

// override any beam with the same entity (unless they used world)
	if (entity)
	{
		for (i=0, b=cl_beams; i < beams_running; i++, b++)
			if (b->entity == entity && b->tag == tag)
			{
				return b;
			}
	}

// find a free beam
	for (i=0, b=cl_beams; i < beams_running; i++, b++)
	{
		if (!b->model)
		{
			return b;
		}
	}

	if (i == beams_running && i != MAX_BEAMS)
	{
		beams_running++;
		return &cl_beams[i];
	}

	return NULL;
}
#define STREAM_ATTACHED			16
#define STREAM_TRANSLUCENT		32
void CL_AddBeam (int tent, int ent, vec3_t start, vec3_t end)	//fixme: use TE_ numbers instead of 0 - 5
{
	beam_t	*b;

	model_t *m;
	int btype, etype;
	int i;
	vec3_t impact, normal;
	vec3_t extra;

	switch(tent)
	{
	case 0:
		if (ent < 0 && ent >= -512)	//a zquake concept. ent between -1 and -maxplayers is to be taken to be a railtrail from a particular player instead of a beam.
		{
			// TODO: add support for those finnicky colored railtrails...
			if (P_ParticleTrail(start, end, rt_railtrail, NULL))
				P_ParticleTrailIndex(start, end, 208, 8, NULL);
			return;
		}
	default:
		m = Mod_ForName("progs/bolt.mdl", false);
		btype = rt_lightning1;
		etype = pt_lightning1_end;
		break;
	case 1:
		if (ent < 0 && ent >= -MAX_CLIENTS)	//based on the railgun concept - this adds a rogue style TE_BEAM effect.
		{
	case 5:
			m = Mod_ForName("progs/beam.mdl", false);	//remember to precache!
			btype = P_FindParticleType("te_beam");
			etype = P_FindParticleType("te_beam_end");
		}
		else
		{
			m = Mod_ForName("progs/bolt2.mdl", false);
			btype = rt_lightning2;
			etype = pt_lightning2_end;
		}
		break;
	case 2:
		m = Mod_ForName("progs/bolt3.mdl", false);
		btype = rt_lightning3;
		etype = pt_lightning3_end;
		break;
#ifdef Q2CLIENT
	case 3:
		m = Mod_ForName(q2tentmodels[q2cl_mod_parasite_segment].modelname, false);
		btype = P_FindParticleType("te_parasite_attack");
		etype = P_FindParticleType("te_parasite_attack_end");
		break;
	case 4:
		m = Mod_ForName(q2tentmodels[q2cl_mod_grapple_cable].modelname, false);
		btype = P_FindParticleType("te_grapple_cable");
		etype = P_FindParticleType("te_grapple_cable_end");
		break;
#endif
	}

	// save end position for truelightning
	if (ent)
	{
		for (i = 0; i < cl.splitclients; i++)
		{
			if (ent == (autocam[i]?spec_track[i]:(cl.playernum[i]+1)))
			{
				VectorCopy(end, playerbeam_end[i]);
				break;
			}
		}
	}

	if (etype >= 0 && cls.state == ca_active && P_TypeIsLoaded(etype))
	{
		if (cl_beam_trace.value)
		{
			VectorSubtract(end, start, normal);
			VectorNormalize(normal);
			VectorMA(end, 4, normal, extra);	//extend the end-point by four
			if (!TraceLineN(start, extra, impact, normal))
				etype = -1;
		}
		else
		{
			VectorCopy(end, impact);
			normal[0] = normal[1] = normal[2] = 0;
		}
	}

	b = CL_NewBeam(ent, -1);
	if (!b)
	{
		Con_Printf ("beam list overflow!\n");	
		return;
	}

	b->entity = ent;
	b->model = m;
	b->tag = -1;
	b->flags |= /*STREAM_ATTACHED|*/1;
	b->endtime = cl.time + 0.2;
	b->alpha = 1;
	b->particleeffect = btype;
	VectorCopy (start, b->start);
	VectorCopy (end, b->end);

	if (etype >= 0)
	{
		P_RunParticleEffectState (impact, normal, 1, etype, &(b->emitstate));
		R_AddDecals(end);
		R_AddStain(end, -10, -10, -10, 20);
	}
}
void CL_ParseBeam (int tent)
{
	int		ent;
	vec3_t	start, end;

	ent = MSG_ReadShort ();
	
	start[0] = MSG_ReadCoord ();
	start[1] = MSG_ReadCoord ();
	start[2] = MSG_ReadCoord ();
	
	end[0] = MSG_ReadCoord ();
	end[1] = MSG_ReadCoord ();
	end[2] = MSG_ReadCoord ();

	CL_AddBeam(tent, ent, start, end);
}
void CL_ParseStream (int type)
{
	int		ent;
	vec3_t	start, end;
	beam_t	*b, *b2;
	int flags;
	int tag;
	float duration;
	int skin;
	
	ent = MSG_ReadShort();
	flags = MSG_ReadByte();
	tag = flags&15;
	flags-=tag;
	duration = (float)MSG_ReadByte()*0.05;
	skin = 0;
	if(type == TE_STREAM_COLORBEAM)
	{
		skin = MSG_ReadByte();
	}
	start[0] = MSG_ReadCoord();
	start[1] = MSG_ReadCoord();
	start[2] = MSG_ReadCoord();
	end[0] = MSG_ReadCoord();
	end[1] = MSG_ReadCoord();
	end[2] = MSG_ReadCoord();

	b = CL_NewBeam(ent, tag);
	if (!b)
	{
		Con_Printf ("beam list overflow!\n");	
		return;
	}

	b->entity = ent;
	b->tag = tag;
	b->flags = flags;
	b->model = NULL;
	b->endtime = cl.time + duration;
	b->alpha = 1;
	VectorCopy (start, b->start);
	VectorCopy (end, b->end);

	switch(type)
	{
	case TE_STREAM_ICECHUNKS:
		b->model = 	Mod_ForName("models/stice.mdl", true);
		b->flags |= 2;
		b->particleeffect = P_AllocateParticleType("te_stream_icechunks");
		R_AddStain(end, -10, -10, 0, 20);
		break;
	case TE_STREAM_SUNSTAFF1:
		b->model = Mod_ForName("models/stsunsf1.mdl", true);
		b->particleeffect = P_AllocateParticleType("te_stream_sunstaff1");
		if (b->particleeffect < 0)
		{
			b2 = CL_NewBeam(ent, tag+128);
			if (b2)
			{
				memcpy(b2, b, sizeof(*b2));
				b2->model = Mod_ForName("models/stsunsf2.mdl", true);
				b2->alpha = 0.5;
			}
		}
		break;
	case TE_STREAM_SUNSTAFF2:
		b->model = 	Mod_ForName("models/stsunsf1.mdl", true);
		b->particleeffect = P_AllocateParticleType("te_stream_sunstaff2");
		R_AddStain(end, -10, -10, -10, 20);
		break;
	}
}

/*
=================
CL_ParseTEnt
=================
*/

#ifdef NQPROT
void CL_ParseTEnt (qboolean nqprot)
#else
void CL_ParseTEnt (void)
#endif
{
#ifndef NQPROT
#define nqprot false	//it's easier
#endif
	int		type;
	vec3_t	pos, pos2;
	dlight_t	*dl;
	int		rnd;
//	explosion_t	*ex;
	int		cnt, colour;

	type = MSG_ReadByte ();
	switch (type)
	{
	case TE_WIZSPIKE:			// spike hitting wall
		pos[0] = MSG_ReadCoord ();
		pos[1] = MSG_ReadCoord ();
		pos[2] = MSG_ReadCoord ();

		R_AddStain(pos, -10, 0, -10, 20);

		if (P_RunParticleEffectType(pos, NULL, 1, pt_wizspike))
			P_RunParticleEffect (pos, vec3_origin, 20, 30);

		S_StartSound (-2, 0, cl_sfx_wizhit, pos, 1, 1);
		break;
		
	case TE_KNIGHTSPIKE:			// spike hitting wall
		pos[0] = MSG_ReadCoord ();
		pos[1] = MSG_ReadCoord ();
		pos[2] = MSG_ReadCoord ();

		R_AddStain(pos, -10, -10, -10, 20);

		if (P_RunParticleEffectType(pos, NULL, 1, pt_knightspike))
			P_RunParticleEffect (pos, vec3_origin, 226, 20);

		S_StartSound (-2, 0, cl_sfx_knighthit, pos, 1, 1);
		break;
		
	case DPTE_SPIKEQUAD:
		pos[0] = MSG_ReadCoord ();
		pos[1] = MSG_ReadCoord ();
		pos[2] = MSG_ReadCoord ();

		R_AddStain(pos, -10, -10, -10, 20);
		R_AddDecals(pos);

		if (P_RunParticleEffectTypeString(pos, NULL, 1, "te_spikequad"))
			if (P_RunParticleEffectType(pos, NULL, 1, pt_spike))
				if (P_RunParticleEffectType(pos, NULL, 10, pt_gunshot))
					P_RunParticleEffect (pos, vec3_origin, 0, 10);

		if ( rand() % 5 )
			S_StartSound (-2, 0, cl_sfx_tink1, pos, 1, 1);
		else
		{
			rnd = rand() & 3;
			if (rnd == 1)
				S_StartSound (-2, 0, cl_sfx_ric1, pos, 1, 1);
			else if (rnd == 2)
				S_StartSound (-2, 0, cl_sfx_ric2, pos, 1, 1);
			else
				S_StartSound (-2, 0, cl_sfx_ric3, pos, 1, 1);
		}
		break;
	case TE_SPIKE:			// spike hitting wall
		pos[0] = MSG_ReadCoord ();
		pos[1] = MSG_ReadCoord ();
		pos[2] = MSG_ReadCoord ();

		R_AddStain(pos, -10, -10, -10, 20);
		R_AddDecals(pos);

		if (P_RunParticleEffectType(pos, NULL, 1, pt_spike))
			if (P_RunParticleEffectType(pos, NULL, 10, pt_gunshot))
				P_RunParticleEffect (pos, vec3_origin, 0, 10);

		if ( rand() % 5 )
			S_StartSound (-2, 0, cl_sfx_tink1, pos, 1, 1);
		else
		{
			rnd = rand() & 3;
			if (rnd == 1)
				S_StartSound (-2, 0, cl_sfx_ric1, pos, 1, 1);
			else if (rnd == 2)
				S_StartSound (-2, 0, cl_sfx_ric2, pos, 1, 1);
			else
				S_StartSound (-2, 0, cl_sfx_ric3, pos, 1, 1);
		}
		break;
	case DPTE_SUPERSPIKEQUAD:			// super spike hitting wall
		pos[0] = MSG_ReadCoord ();
		pos[1] = MSG_ReadCoord ();
		pos[2] = MSG_ReadCoord ();

		R_AddStain(pos, -10, -10, -10, 20);
		R_AddDecals(pos);

		if (P_RunParticleEffectTypeString(pos, NULL, 1, "te_superspikequad"))
			if (P_RunParticleEffectType(pos, NULL, 1, pt_superspike))
				if (P_RunParticleEffectType(pos, NULL, 2, pt_spike))
					if (P_RunParticleEffectType(pos, NULL, 20, pt_gunshot))
						P_RunParticleEffect (pos, vec3_origin, 0, 20);

		if ( rand() % 5 )
			S_StartSound (-2, 0, cl_sfx_tink1, pos, 1, 1);
		else
		{
			rnd = rand() & 3;
			if (rnd == 1)
				S_StartSound (-2, 0, cl_sfx_ric1, pos, 1, 1);
			else if (rnd == 2)
				S_StartSound (-2, 0, cl_sfx_ric2, pos, 1, 1);
			else
				S_StartSound (-2, 0, cl_sfx_ric3, pos, 1, 1);
		}
		break;
	case TE_SUPERSPIKE:			// super spike hitting wall
		pos[0] = MSG_ReadCoord ();
		pos[1] = MSG_ReadCoord ();
		pos[2] = MSG_ReadCoord ();

		R_AddStain(pos, -10, -10, -10, 20);
		R_AddDecals(pos);

		if (P_RunParticleEffectType(pos, NULL, 1, pt_superspike))
			if (P_RunParticleEffectType(pos, NULL, 2, pt_spike))
				if (P_RunParticleEffectType(pos, NULL, 20, pt_gunshot))
					P_RunParticleEffect (pos, vec3_origin, 0, 20);

		if ( rand() % 5 )
			S_StartSound (-2, 0, cl_sfx_tink1, pos, 1, 1);
		else
		{
			rnd = rand() & 3;
			if (rnd == 1)
				S_StartSound (-2, 0, cl_sfx_ric1, pos, 1, 1);
			else if (rnd == 2)
				S_StartSound (-2, 0, cl_sfx_ric2, pos, 1, 1);
			else
				S_StartSound (-2, 0, cl_sfx_ric3, pos, 1, 1);
		}
		break;
	
#ifdef PEXT_TE_BULLET
	case TE_BULLET:
		if (!(cls.fteprotocolextensions & PEXT_TE_BULLET))
			Host_EndGame("Thought PEXT_TE_BULLET was disabled");
		pos[0] = MSG_ReadCoord ();
		pos[1] = MSG_ReadCoord ();
		pos[2] = MSG_ReadCoord ();

		R_AddStain(pos, -10, -10, -10, 20);
		R_AddDecals(pos);

		if (P_RunParticleEffectType(pos, NULL, 1, pt_bullet))
			if (P_RunParticleEffectType(pos, NULL, 10, pt_gunshot))
				P_RunParticleEffect (pos, vec3_origin, 0, 10);

		if ( rand() % 5 )
			S_StartSound (-2, 0, cl_sfx_tink1, pos, 1, 1);
		else
		{
			rnd = rand() & 3;
			if (rnd == 1)
				S_StartSound (-2, 0, cl_sfx_ric1, pos, 1, 1);
			else if (rnd == 2)
				S_StartSound (-2, 0, cl_sfx_ric2, pos, 1, 1);
			else
				S_StartSound (-2, 0, cl_sfx_ric3, pos, 1, 1);
		}
		break;
	case TE_SUPERBULLET:
		pos[0] = MSG_ReadCoord ();
		pos[1] = MSG_ReadCoord ();
		pos[2] = MSG_ReadCoord ();

		R_AddStain(pos, -10, -10, -10, 20);
		R_AddDecals(pos);

		if (P_RunParticleEffectType(pos, NULL, 1, pt_superbullet))
			if (P_RunParticleEffectType(pos, NULL, 2, pt_bullet))
				if (P_RunParticleEffectType(pos, NULL, 20, pt_gunshot))
					P_RunParticleEffect (pos, vec3_origin, 0, 20);

		if ( rand() % 5 )
			S_StartSound (-2, 0, cl_sfx_tink1, pos, 1, 1);
		else
		{
			rnd = rand() & 3;
			if (rnd == 1)
				S_StartSound (-2, 0, cl_sfx_ric1, pos, 1, 1);
			else if (rnd == 2)
				S_StartSound (-2, 0, cl_sfx_ric2, pos, 1, 1);
			else
				S_StartSound (-2, 0, cl_sfx_ric3, pos, 1, 1);
		}		
		break;
#endif

	case DPTE_EXPLOSIONQUAD:			// rocket explosion
	// particles
		pos[0] = MSG_ReadCoord ();
		pos[1] = MSG_ReadCoord ();
		pos[2] = MSG_ReadCoord ();
		if (P_RunParticleEffectTypeString(pos, NULL, 1, "te_explosionquad"))
			if (P_RunParticleEffectType(pos, NULL, 1, pt_explosion))
				P_RunParticleEffect(pos, NULL, 107, 1024); // should be 97-111

		R_AddStain(pos, -1, -1, -1, 100);
		
	// light
		if (r_explosionlight.value)
		{
			dl = CL_AllocDlight (0);
			VectorCopy (pos, dl->origin);
			dl->radius = 150 + r_explosionlight.value*200;
			dl->die = cl.time + 1;
			dl->decay = 300;
		
			dl->color[0] = 0.2;
			dl->color[1] = 0.155;
			dl->color[2] = 0.05;
			dl->channelfade[0] = 0.196;
			dl->channelfade[1] = 0.23;
			dl->channelfade[2] = 0.12;
		}


	// sound
		S_StartSound (-2, 0, cl_sfx_r_exp3, pos, 1, 1);
	
	// sprite		
		if (cl_expsprite.value) // temp hopefully
		{
			explosion_t *ex = CL_AllocExplosion ();
			VectorCopy (pos, ex->origin);
			ex->start = cl.time;
			ex->model = Mod_ForName ("progs/s_explod.spr", true);
		}
		break;
	case TE_EXPLOSION:			// rocket explosion
	// particles
		pos[0] = MSG_ReadCoord ();
		pos[1] = MSG_ReadCoord ();
		pos[2] = MSG_ReadCoord ();
		if (P_RunParticleEffectType(pos, NULL, 1, pt_explosion))
			P_RunParticleEffect(pos, NULL, 107, 1024); // should be 97-111

		R_AddStain(pos, -1, -1, -1, 100);
		
	// light
		if (r_explosionlight.value)
		{
			dl = CL_AllocDlight (0);
			VectorCopy (pos, dl->origin);
			dl->radius = 150 + r_explosionlight.value*200;
			dl->die = cl.time + 1;
			dl->decay = 300;
			
			dl->color[0] = 0.2;
			dl->color[1] = 0.155;
			dl->color[2] = 0.05;
			dl->channelfade[0] = 0.196;
			dl->channelfade[1] = 0.23;
			dl->channelfade[2] = 0.12;
		}


	// sound
		S_StartSound (-2, 0, cl_sfx_r_exp3, pos, 1, 1);
	
	// sprite		
		if (cl_expsprite.value && !nqprot) // temp hopefully
		{
			explosion_t *ex = CL_AllocExplosion ();
			VectorCopy (pos, ex->origin);
			ex->start = cl.time;
			ex->model = Mod_ForName ("progs/s_explod.spr", true);
		}
		break;

	case DPTE_EXPLOSIONRGB:
		pos[0] = MSG_ReadCoord ();
		pos[1] = MSG_ReadCoord ();
		pos[2] = MSG_ReadCoord ();
		if (P_RunParticleEffectType(pos, NULL, 1, pt_explosion))
			P_RunParticleEffect(pos, NULL, 107, 1024); // should be 97-111

		R_AddStain(pos, -1, -1, -1, 100);
		
		
	// light
		if (r_explosionlight.value)
		{
			dl = CL_AllocDlight (0);
			VectorCopy (pos, dl->origin);
			dl->radius = 150 + r_explosionlight.value*200;
			dl->die = cl.time + 0.5;
			dl->decay = 300;
		
			dl->color[0] = 0.4f*MSG_ReadByte()/255.0f;
			dl->color[1] = 0.4f*MSG_ReadByte()/255.0f;
			dl->color[2] = 0.4f*MSG_ReadByte()/255.0f;
			dl->channelfade[0] = 0;
			dl->channelfade[1] = 0;
			dl->channelfade[2] = 0;
		}

		S_StartSound (-2, 0, cl_sfx_r_exp3, pos, 1, 1);
		break;

	case DPTE_TEI_BIGEXPLOSION:
		pos[0] = MSG_ReadCoord ();
		pos[1] = MSG_ReadCoord ();
		pos[2] = MSG_ReadCoord ();
		if (P_RunParticleEffectTypeString(pos, NULL, 1, "te_tei_bigexplosion"))
			if (P_RunParticleEffectType(pos, NULL, 1, pt_explosion))
				P_RunParticleEffect(pos, NULL, 107, 1024); // should be 97-111

		R_AddStain(pos, -1, -1, -1, 100);
		
	// light
		if (r_explosionlight.value)
		{
			dl = CL_AllocDlight (0);
			VectorCopy (pos, dl->origin);
			// no point in doing this the fuh/ez way
			dl->radius = 500*r_explosionlight.value; 
			dl->die = cl.time + 1;
			dl->decay = 500;
			
			dl->color[0] = 0.4f;
			dl->color[1] = 0.3f;
			dl->color[2] = 0.15f;
			dl->channelfade[0] = 0;
			dl->channelfade[1] = 0;
			dl->channelfade[2] = 0;
		}

		S_StartSound (-2, 0, cl_sfx_r_exp3, pos, 1, 1);
		break;
		
	case TE_TAREXPLOSION:			// tarbaby explosion
		pos[0] = MSG_ReadCoord ();
		pos[1] = MSG_ReadCoord ();
		pos[2] = MSG_ReadCoord ();
		P_BlobExplosion (pos);

		S_StartSound (-2, 0, cl_sfx_r_exp3, pos, 1, 1);
		break;

	case TE_LIGHTNING1:				// lightning bolts
	case TE_LIGHTNING2:				// lightning bolts
		CL_ParseBeam (type - TE_LIGHTNING1);
		break;
	case TE_LIGHTNING3:				// lightning bolts
		CL_ParseBeam (2);
		break;
	
	case TE_LAVASPLASH:	
		pos[0] = MSG_ReadCoord ();
		pos[1] = MSG_ReadCoord ();
		pos[2] = MSG_ReadCoord ();
		P_LavaSplash (pos);
		break;
	
	case TE_TELEPORT:
		pos[0] = MSG_ReadCoord ();
		pos[1] = MSG_ReadCoord ();
		pos[2] = MSG_ReadCoord ();
		P_RunParticleEffectType(pos, NULL, 1, pt_teleportsplash);
		break;

	case DPTE_GUNSHOTQUAD:			// bullet hitting wall
		pos[0] = MSG_ReadCoord ();
		pos[1] = MSG_ReadCoord ();
		pos[2] = MSG_ReadCoord ();

		R_AddStain(pos, -10, -10, -10, 20);

		if (P_RunParticleEffectTypeString(pos, NULL, 1, "te_gunshotquad"))
			if (P_RunParticleEffectType(pos, NULL, 1, pt_gunshot))
				P_RunParticleEffect (pos, vec3_origin, 0, 20);

		break;
	case TE_GUNSHOT:			// bullet hitting wall
		if (nqprot)
			cnt = 1;
		else
			cnt = MSG_ReadByte ();
		pos[0] = MSG_ReadCoord ();
		pos[1] = MSG_ReadCoord ();
		pos[2] = MSG_ReadCoord ();

		R_AddStain(pos, -10, -10, -10, 20);

		if (P_RunParticleEffectType(pos, NULL, cnt, pt_gunshot))
			P_RunParticleEffect (pos, vec3_origin, 0, 20*cnt);

		break;
		
	case TE_BLOOD:				// bullets hitting body
		cnt = MSG_ReadByte ();
		pos[0] = MSG_ReadCoord ();
		pos[1] = MSG_ReadCoord ();
		pos[2] = MSG_ReadCoord ();

		R_AddStain(pos, 0, -10, -10, 40);

		if (P_RunParticleEffectType(pos, NULL, cnt, pt_blood))
			P_RunParticleEffect (pos, vec3_origin, 73, 20*cnt);

		break;

	case TE_LIGHTNINGBLOOD:		// lightning hitting body
		pos[0] = MSG_ReadCoord ();
		pos[1] = MSG_ReadCoord ();
		pos[2] = MSG_ReadCoord ();

		R_AddStain(pos, 1, -10, -10, 20);

		if (P_RunParticleEffectType(pos, NULL, 1, pt_lightningblood))
			P_RunParticleEffect (pos, vec3_origin, 225, 50);

		break;

	case TE_RAILTRAIL:
		pos[0] = MSG_ReadCoord ();
		pos[1] = MSG_ReadCoord ();
		pos[2] = MSG_ReadCoord ();

		pos2[0] = MSG_ReadCoord ();
		pos2[1] = MSG_ReadCoord ();
		pos2[2] = MSG_ReadCoord ();

		if (P_ParticleTrail(pos, pos2, rt_railtrail, NULL))
			P_ParticleTrailIndex(pos, pos2, 208, 8, NULL);
		break;

	case TE_STREAM_CHAIN:
	case TE_STREAM_SUNSTAFF1:
	case TE_STREAM_SUNSTAFF2:
	case TE_STREAM_LIGHTNING:
	case TE_STREAM_COLORBEAM:
	case TE_STREAM_ICECHUNKS:
	case TE_STREAM_GAZE:
	case TE_STREAM_FAMINE:
		CL_ParseStream (type);
		break;

	case DPTE_BLOOD:
		pos[0] = MSG_ReadCoord ();
		pos[1] = MSG_ReadCoord ();
		pos[2] = MSG_ReadCoord ();

		pos2[0] = MSG_ReadChar ();
		pos2[1] = MSG_ReadChar ();
		pos2[2] = MSG_ReadChar ();

		cnt = MSG_ReadByte ();

		P_RunParticleEffectType(pos, pos2, cnt, pt_blood);
		break;

	case DPTE_SPARK:
		pos[0] = MSG_ReadCoord ();
		pos[1] = MSG_ReadCoord ();
		pos[2] = MSG_ReadCoord ();

		pos2[0] = MSG_ReadChar ();
		pos2[1] = MSG_ReadChar ();
		pos2[2] = MSG_ReadChar ();

		cnt = MSG_ReadByte ();
		{
			extern int pt_spark;
			P_RunParticleEffectType(pos, pos2, cnt, pt_spark);
		}
		break;

	case DPTE_BLOODSHOWER:
		pos[0] = MSG_ReadCoord ();
		pos[1] = MSG_ReadCoord ();
		pos[2] = MSG_ReadCoord ();

		pos2[0] = MSG_ReadCoord ();
		pos2[1] = MSG_ReadCoord ();
		pos2[2] = MSG_ReadCoord ();

		cnt = MSG_ReadCoord ();	//speed

		cnt = MSG_ReadShort ();

		{
			VectorAdd(pos, pos2, pos);
			VectorScale(pos, 0.5, pos);
			P_RunParticleEffectTypeString(pos, NULL, cnt, "te_bloodshower");
		}
		break;

	case DPTE_SMALLFLASH:
		pos[0] = MSG_ReadCoord ();
		pos[1] = MSG_ReadCoord ();
		pos[2] = MSG_ReadCoord ();

		// light
		dl = CL_AllocDlight (0);
		VectorCopy (pos, dl->origin);
		dl->radius = 200;
		dl->decay = 1000;
		dl->die = cl.time + 0.2;
		dl->color[0] = 0.4;
		dl->color[1] = 0.4;
		dl->color[2] = 0.4;
		break;

	case DPTE_CUSTOMFLASH:
		pos[0] = MSG_ReadCoord ();
		pos[1] = MSG_ReadCoord ();
		pos[2] = MSG_ReadCoord ();

			// light
		dl = CL_AllocDlight (0);
		VectorCopy (pos, dl->origin);
		dl->radius = MSG_ReadByte()*8;
		pos2[0] = (MSG_ReadByte() + 1) * (1.0 / 256.0);
		dl->die = cl.time + pos2[0];
		dl->decay = dl->radius / pos2[0];
	
		// DP's range is 0-2 for lights, FTE is 0-0.4.. 255/637.5 = 0.4
		dl->color[0] = MSG_ReadByte()*(1.0f/637.5f);
		dl->color[1] = MSG_ReadByte()*(1.0f/637.5f);
		dl->color[2] = MSG_ReadByte()*(1.0f/637.5f);
		
		break;

	case DPTE_FLAMEJET:
		// origin
		pos[0] = MSG_ReadCoord ();
		pos[1] = MSG_ReadCoord ();
		pos[2] = MSG_ReadCoord ();

		// velocity
		pos2[0] = MSG_ReadCoord ();
		pos2[1] = MSG_ReadCoord ();
		pos2[2] = MSG_ReadCoord ();

		// count
		cnt = MSG_ReadByte ();

		if (P_RunParticleEffectTypeString(pos, pos2, cnt, "te_flamejet"))
			P_RunParticleEffect (pos, pos2, 232, cnt);
		break;

	case DPTE_PLASMABURN:
		// origin
		pos[0] = MSG_ReadCoord ();
		pos[1] = MSG_ReadCoord ();
		pos[2] = MSG_ReadCoord ();

		// light
		dl = CL_AllocDlight (0);
		VectorCopy (pos, dl->origin);
		dl->radius = 200;
		dl->decay = 1000;
		dl->die = cl.time + 0.2;
		dl->color[0] = 0.2;
		dl->color[1] = 0.2;
		dl->color[2] = 0.2;

		// stain (Hopefully this is close to how DP does it)
		R_AddStain(pos, -10, -10, -10, 30);

		if (P_ParticleTrail(pos, pos2, P_FindParticleType("te_plasmaburn"), NULL))
			P_ParticleTrailIndex(pos, pos2, 15, 0, NULL);
		break;

	case DPTE_TEI_G3:	//nexuiz's nex beam
		pos[0] = MSG_ReadCoord ();
		pos[1] = MSG_ReadCoord ();
		pos[2] = MSG_ReadCoord ();

		pos2[0] = MSG_ReadCoord ();
		pos2[1] = MSG_ReadCoord ();
		pos2[2] = MSG_ReadCoord ();

		//sigh...
		MSG_ReadCoord ();
		MSG_ReadCoord ();
		MSG_ReadCoord ();

		if (P_ParticleTrail(pos, pos2, P_FindParticleType("te_nexbeam"), NULL))
			P_ParticleTrailIndex(pos, pos2, 15, 0, NULL);
		break;

	case DPTE_SMOKE:
		//org
		pos[0] = MSG_ReadCoord ();
		pos[1] = MSG_ReadCoord ();
		pos[2] = MSG_ReadCoord ();

		//dir
		pos2[0] = MSG_ReadCoord ();
		pos2[1] = MSG_ReadCoord ();
		pos2[2] = MSG_ReadCoord ();

		//count
		cnt = MSG_ReadByte ();
		{
			extern int pt_smoke;
			P_RunParticleEffectType(pos, pos2, cnt, pt_smoke);
		}
		break;

	case DPTE_TEI_PLASMAHIT:
		pos[0] = MSG_ReadCoord ();
		pos[1] = MSG_ReadCoord ();
		pos[2] = MSG_ReadCoord ();

		//dir
		pos2[0] = MSG_ReadCoord ();
		pos2[1] = MSG_ReadCoord ();
		pos2[2] = MSG_ReadCoord ();
		cnt = MSG_ReadByte ();

		{
			extern int pt_plasma;
			P_RunParticleEffectType(pos, pos2, cnt, pt_plasma);
		}
		break;

	case DPTE_PARTICLECUBE:
		{
			vec3_t dir;
			int jitter;
			int gravity;

			//min
			pos[0] = MSG_ReadCoord();
			pos[1] = MSG_ReadCoord();
			pos[2] = MSG_ReadCoord();

			//max
			pos2[0] = MSG_ReadCoord();
			pos2[1] = MSG_ReadCoord();
			pos2[2] = MSG_ReadCoord();

			//dir
			dir[0] = MSG_ReadCoord();
			dir[1] = MSG_ReadCoord();
			dir[2] = MSG_ReadCoord();

			cnt = MSG_ReadShort();	//count
			colour = MSG_ReadByte ();	//colour
			gravity = MSG_ReadByte ();	//gravity flag
			jitter = MSG_ReadCoord();	//jitter

			P_RunParticleCube(pos, pos2, dir, cnt, colour, gravity, jitter);
		}
		break;
	case DPTE_PARTICLERAIN:
		{
			vec3_t dir;

			//min
			pos[0] = MSG_ReadCoord();
			pos[1] = MSG_ReadCoord();
			pos[2] = MSG_ReadCoord();

			//max
			pos2[0] = MSG_ReadCoord();
			pos2[1] = MSG_ReadCoord();
			pos2[2] = MSG_ReadCoord();

			//dir
			dir[0] = MSG_ReadCoord();
			dir[1] = MSG_ReadCoord();
			dir[2] = MSG_ReadCoord();

			cnt = MSG_ReadShort();	//count
			colour = MSG_ReadByte ();	//colour

			P_RunParticleWeather(pos, pos2, dir, cnt, colour, "rain");
		}
		break;
	case DPTE_PARTICLESNOW:
		{
			vec3_t dir;

			//min
			pos[0] = MSG_ReadCoord();
			pos[1] = MSG_ReadCoord();
			pos[2] = MSG_ReadCoord();

			//max
			pos2[0] = MSG_ReadCoord();
			pos2[1] = MSG_ReadCoord();
			pos2[2] = MSG_ReadCoord();

			//dir
			dir[0] = MSG_ReadCoord();
			dir[1] = MSG_ReadCoord();
			dir[2] = MSG_ReadCoord();

			cnt = MSG_ReadShort();	//count
			colour = MSG_ReadByte ();	//colour

			P_RunParticleWeather(pos, pos2, dir, cnt, colour, "snow");
		}
		break;

	default:
		Host_EndGame ("CL_ParseTEnt: bad type - %i", type);
	}
}

void MSG_ReadPos (vec3_t pos);
void MSG_ReadDir (vec3_t dir);
typedef struct {
	int netstyle;
	int particleeffecttype;
	char stain[3];
	qbyte radius;
	vec3_t dlightrgb;
	float dlightradius;
	float dlighttime;
	vec3_t dlightcfade;
} clcustomtents_t;

#define CTE_CUSTOMCOUNT		1
#define CTE_CUSTOMDIRECTION	2
#define CTE_STAINS			4
#define CTE_GLOWS			8
#define CTE_CHANNELFADE		16
#define CTE_ISBEAM			128

clcustomtents_t customtenttype[255];	//network based.
void CL_ParseCustomTEnt(void)
{
	int count;
	vec3_t pos;
	vec3_t pos2;
	vec3_t dir;
	char *str;
	clcustomtents_t *t;
	int type = MSG_ReadByte();

	if (type == 255)	//255 is register
	{
		type = MSG_ReadByte();
		if (type == 255)
			Host_EndGame("Custom temp type 255 isn't valid\n");
		t = &customtenttype[type];

		t->netstyle = MSG_ReadByte();
		str = MSG_ReadString();
		t->particleeffecttype = P_AllocateParticleType(str);

		if (t->netstyle & CTE_STAINS)
		{
			t->stain[0] = MSG_ReadChar();
			t->stain[1] = MSG_ReadChar();
			t->stain[2] = MSG_ReadChar();
			t->radius = MSG_ReadByte();
		}
		else
			t->radius = 0;
		if (t->netstyle & CTE_GLOWS)
		{
			t->dlightrgb[0] = MSG_ReadByte()/255.0f;
			t->dlightrgb[1] = MSG_ReadByte()/255.0f;
			t->dlightrgb[2] = MSG_ReadByte()/255.0f;
			t->dlightradius = MSG_ReadByte();
			t->dlighttime = MSG_ReadByte()/16.0f;
			if (t->netstyle & CTE_CHANNELFADE)
			{
				t->dlightcfade[0] = MSG_ReadByte()/64.0f;
				t->dlightcfade[1] = MSG_ReadByte()/64.0f;
				t->dlightcfade[2] = MSG_ReadByte()/64.0f;
			}
		}
		else
			t->dlighttime = 0;
		return;
	}

	t = &customtenttype[type];
	if (t->particleeffecttype < 0)
		Host_EndGame("Custom Temporary entity %i was not registered\n", type);

	if (t->netstyle & CTE_ISBEAM)
	{
		MSG_ReadPos (pos);
		MSG_ReadPos (pos2);
		P_ParticleTrail(pos, pos2, t->particleeffecttype, NULL);
	}
	else
	{
		if (t->netstyle & CTE_CUSTOMCOUNT)
			count = MSG_ReadByte();
		else
			count = 1;

		MSG_ReadPos (pos);
		VectorCopy(pos, pos2);

		if (t->netstyle & CTE_CUSTOMDIRECTION)
		{
			MSG_ReadDir (dir);
			P_RunParticleEffectType(pos, dir, 1, t->particleeffecttype);
		}
		else P_RunParticleEffectType(pos, NULL, 1, t->particleeffecttype);
	}

	if (t->netstyle & CTE_STAINS)
	{	//added at pos2 - end of trail
		R_AddStain(pos2, t->stain[0], t->stain[1], t->stain[2], 40);
	}
	if (t->netstyle & CTE_GLOWS)
	{	//added at pos1 firer's end.
		dlight_t	*dl;
		dl = CL_AllocDlight (0);
		VectorCopy (pos, dl->origin);
		dl->radius = t->dlightradius*4;
		dl->die = cl.time + t->dlighttime;
		dl->decay = t->radius/t->dlighttime;
		
		dl->color[0] = t->dlightrgb[0];
		dl->color[1] = t->dlightrgb[1];
		dl->color[2] = t->dlightrgb[2];

		if (t->netstyle & CTE_CHANNELFADE)
		{
			dl->channelfade[0] = t->dlightcfade[0];
			dl->channelfade[1] = t->dlightcfade[1];
			dl->channelfade[2] = t->dlightcfade[2];
		}

		/*
		if (dl->color[0] < 0)
			dl->channelfade[0] = 0;
		else
			dl->channelfade[0] = dl->color[0]/t->dlighttime;

		if (dl->color[1] < 0)
			dl->channelfade[1] = 0;
		else
			dl->channelfade[1] = dl->color[0]/t->dlighttime;

		if (dl->color[2] < 0)
			dl->channelfade[2] = 0;
		else
			dl->channelfade[2] = dl->color[0]/t->dlighttime;
		*/
	}
}
void CL_ClearCustomTEnts(void)
{
	int i;
	for (i = 0; i < sizeof(customtenttype)/sizeof(customtenttype[0]); i++)
		customtenttype[i].particleeffecttype = -1;
}

void CLNQ_ParseParticleEffect (void)
{
	vec3_t		org, dir;
	int			i, count, msgcount, color;
	
	for (i=0 ; i<3 ; i++)
		org[i] = MSG_ReadCoord ();
	for (i=0 ; i<3 ; i++)
		dir[i] = MSG_ReadChar () * (1.0/16);
	msgcount = MSG_ReadByte ();
	color = MSG_ReadByte ();

	if (msgcount == 255)
		count = 1024;
	else
		count = msgcount;
	
	P_RunParticleEffect (org, dir, color, count);
}
void CL_ParseParticleEffect2 (void)
{
	vec3_t		org, dmin, dmax;
	int			i, msgcount, color, effect;
	
	for (i=0 ; i<3 ; i++)
		org[i] = MSG_ReadCoord ();
	for (i=0 ; i<3 ; i++)
		dmin[i] = MSG_ReadFloat ();
	for (i=0 ; i<3 ; i++)
		dmax[i] = MSG_ReadFloat ();
	color = MSG_ReadShort ();
	msgcount = MSG_ReadByte ();
	effect = MSG_ReadByte ();

	P_RunParticleEffect2 (org, dmin, dmax, color, effect, msgcount);
}
void CL_ParseParticleEffect3 (void)
{
	vec3_t		org, box;
	int			i, msgcount, color, effect;
	
	for (i=0 ; i<3 ; i++)
		org[i] = MSG_ReadCoord ();
	for (i=0 ; i<3 ; i++)
		box[i] = MSG_ReadByte ();
	color = MSG_ReadShort ();
	msgcount = MSG_ReadByte ();
	effect = MSG_ReadByte ();

	P_RunParticleEffect3 (org, box, color, effect, msgcount);
}
void CL_ParseParticleEffect4 (void)
{
	vec3_t		org;
	int			i, msgcount, color, effect;
	float		radius;
	
	for (i=0 ; i<3 ; i++)
		org[i] = MSG_ReadCoord ();
	radius = MSG_ReadByte();
	color = MSG_ReadShort ();
	msgcount = MSG_ReadByte ();
	effect = MSG_ReadByte ();

	P_RunParticleEffect4 (org, radius, color, effect, msgcount);
}

void CL_SpawnSpriteEffect(vec3_t org, model_t *model, int startframe, int framecount, int framerate)
{
	explosion_t	*ex;

	ex = CL_AllocExplosion ();
	VectorCopy (org, ex->origin);
	ex->start = cl.time;
	ex->model = model;
	ex->firstframe = startframe;
	ex->numframes = framecount;
	ex->framerate = framerate;

	ex->angles[0] = 0;
	ex->angles[1] = 0;
	ex->angles[2] = 0;
}

// [vector] org [byte] modelindex [byte] startframe [byte] framecount [byte] framerate
// [vector] org [short] modelindex [short] startframe [byte] framecount [byte] framerate
void CL_ParseEffect (qboolean effect2)
{
	vec3_t org;
	int modelindex;
	int startframe;
	int framecount;
	int framerate;

	org[0] = MSG_ReadCoord();
	org[1] = MSG_ReadCoord();
	org[2] = MSG_ReadCoord();

	if (effect2)
		modelindex = MSG_ReadShort();
	else
		modelindex = MSG_ReadByte();

	if (effect2)
		startframe = MSG_ReadShort();
	else
		startframe = MSG_ReadByte();

	framecount = MSG_ReadByte();
	framerate = MSG_ReadByte();


	CL_SpawnSpriteEffect(org, cl.model_precache[modelindex], startframe, framecount, framerate);
}

#ifdef Q2CLIENT
void CL_SmokeAndFlash(vec3_t origin)
{
	explosion_t	*ex;

	ex = CL_AllocExplosion ();
	VectorCopy (origin, ex->origin);
	VectorClear(ex->angles);
//	ex->type = ex_misc;
	ex->numframes = 4;
	ex->flags = Q2RF_TRANSLUCENT;
	ex->start = cl.time;
	ex->model = Mod_ForName (q2tentmodels[q2cl_mod_smoke].modelname, false);

	ex = CL_AllocExplosion ();
	VectorCopy (origin, ex->origin);
	VectorClear(ex->angles);
//	ex->type = ex_flash;
	ex->flags = Q2RF_FULLBRIGHT;
	ex->numframes = 2;
	ex->start = cl.time;
	ex->model = Mod_ForName (q2tentmodels[q2cl_mod_flash].modelname, false);
}

void CL_Laser (vec3_t start, vec3_t end, int colors)
{
	explosion_t	*ex = CL_AllocExplosion();
	ex->firstframe = 0;
	ex->numframes = 10;
	ex->alpha = 0.33f;
	ex->model = (void*)0xDEAFF00D;	//something not null
	ex->skinnum = (colors >> ((rand() % 4)*8)) & 0xff;
	VectorCopy (start, ex->origin);
	VectorCopy (end, ex->oldorigin);
	ex->flags = Q2RF_TRANSLUCENT | Q2RF_BEAM;
	ex->start = cl.time;
	ex->framerate = 100; // smoother fading
}

static qbyte splash_color[] = {0x00, 0xe0, 0xb0, 0x50, 0xd0, 0xe0, 0xe8};

#define ATTN_NONE	0
#define ATTN_NORM 1
#define ATTN_STATIC 1
void Q2S_StartSound(vec3_t origin, int entnum, int entchannel, sfx_t *sfx, float fvol, float attenuation, float timeofs)
{
	S_StartSoundDelayed(entnum, entchannel, sfx, origin, fvol, attenuation, timeofs);
}
void CLQ2_ParseTEnt (void)
{
	int		type;
	vec3_t	pos, pos2, dir;
	explosion_t	*ex;
	int		cnt;
	int		color;
	int		r;
//	int		ent;
//	int		magnitude;

	type = MSG_ReadByte ();

	switch (type)
	{
	case Q2TE_BLOOD:			// bullet hitting flesh
		MSG_ReadPos (pos);
		MSG_ReadDir (dir);
		if (P_RunParticleEffectType(pos, dir, 1, pt_blood))
			P_RunParticleEffect(pos, dir, 0xe8, 60);
		R_AddStain(pos, 0, -10, -10, 40);
		break;

	case Q2TE_GUNSHOT:			// bullet hitting wall
	case Q2TE_SPARKS:
	case Q2TE_BULLET_SPARKS:
		MSG_ReadPos (pos);
		MSG_ReadDir (dir);
		if (type == Q2TE_GUNSHOT)
			P_RunParticleEffect (pos, dir, 0, 40);
		else
			P_RunParticleEffect (pos, dir, 0xe0, 6);

		R_AddStain(pos, -10, -10, -10, 20);

		if (type != Q2TE_SPARKS)
		{
			CL_SmokeAndFlash(pos);
			
			// impact sound (nope, not the same as Q1...)
			cnt = rand()&15;
			if (cnt == 1)
				Q2S_StartSound (pos, 0, 0, S_PrecacheSound ("world/ric1.wav"), 1, ATTN_NORM, 0);
			else if (cnt == 2)
				Q2S_StartSound (pos, 0, 0, S_PrecacheSound ("world/ric2.wav"), 1, ATTN_NORM, 0);
			else if (cnt == 3)
				Q2S_StartSound (pos, 0, 0, S_PrecacheSound ("world/ric3.wav"), 1, ATTN_NORM, 0);
		}

		break;

	case Q2TE_SCREEN_SPARKS:
	case Q2TE_SHIELD_SPARKS:
		MSG_ReadPos (pos);
		MSG_ReadDir (dir);
		if (type == Q2TE_SCREEN_SPARKS)
			P_RunParticleEffect (pos, dir, 0xd0, 40);
		else
			P_RunParticleEffect (pos, dir, 0xb0, 40);
		//FIXME : replace or remove this sound
		S_StartSound (-2, 0, S_PrecacheSound ("weapons/lashit.wav"), pos, 1, 1);
		break;

	case Q2TE_SHOTGUN:			// bullet hitting wall
		MSG_ReadPos (pos);
		MSG_ReadDir (dir);
		P_RunParticleEffect (pos, dir, 0, 20);
		CL_SmokeAndFlash(pos);
		R_AddStain(pos, -10, -10, -10, 20);
		break;

	case Q2TE_SPLASH:			// bullet hitting water
		cnt = MSG_ReadByte ();
		MSG_ReadPos (pos);
		MSG_ReadDir (dir);
		r = MSG_ReadByte ();
		if (r > 6)
			color = 0x00;
		else
			color = splash_color[r];
		P_RunParticleEffect (pos, dir, color, cnt);

		if (r == Q2SPLASH_SPARKS)
		{
			r = rand() & 3;
			if (r == 1)
				Q2S_StartSound (pos, 0, 0, S_PrecacheSound ("world/spark5.wav"), 1, ATTN_NORM, 0);
			else if (r == 2)
				Q2S_StartSound (pos, 0, 0, S_PrecacheSound ("world/spark6.wav"), 1, ATTN_NORM, 0);
			else
				Q2S_StartSound (pos, 0, 0, S_PrecacheSound ("world/spark7.wav"), 1, ATTN_NORM, 0);

//			if (r == 0)
//				Q2S_StartSound (pos, 0, 0, cl_sfx_spark5, 1, ATTN_STATIC, 0);
//			else if (r == 1)
//				Q2S_StartSound (pos, 0, 0, cl_sfx_spark6, 1, ATTN_STATIC, 0);
//			else
//				Q2S_StartSound (pos, 0, 0, cl_sfx_spark7, 1, ATTN_STATIC, 0);
		}
		break;

	case Q2TE_LASER_SPARKS:
		cnt = MSG_ReadByte ();
		MSG_ReadPos (pos);
		MSG_ReadDir (dir);
		color = MSG_ReadByte ();
		P_RunParticleEffect (pos, dir, color, cnt);
		break;

	// RAFAEL
	case Q2TE_BLUEHYPERBLASTER:
		MSG_ReadPos (pos);
		MSG_ReadPos (dir);
		if (P_RunParticleEffectType(pos, dir, 1, pt_blasterparticles))
			P_RunParticleEffect (pos, dir, 0xe0, 40);
		break;

	case Q2TE_BLASTER:			// blaster hitting wall
		MSG_ReadPos (pos);
		MSG_ReadDir (dir);

		if (P_RunParticleEffectType(pos, dir, 1, pt_blasterparticles))
			P_RunParticleEffect (pos, dir, 0xe0, 40);

		R_AddStain(pos, 0, -5, -10, 20);

		ex = CL_AllocExplosion ();
		VectorCopy (pos, ex->origin);
		ex->start = cl.time;
		ex->model = Mod_ForName (q2tentmodels[q2cl_mod_explode].modelname, false);
		ex->firstframe = 0;
		ex->numframes = 4;
		ex->flags = Q2RF_FULLBRIGHT;
		
		ex->angles[0] = acos(dir[2])/M_PI*180;
	// PMM - fixed to correct for pitch of 0
		if (dir[0])
			ex->angles[1] = atan2(dir[1], dir[0])/M_PI*180;
		else if (dir[1] > 0)
			ex->angles[1] = 90;
		else if (dir[1] < 0)
			ex->angles[1] = 270;
		else
			ex->angles[1] = 0;
		ex->angles[0]*=-1;

		S_StartSound (-2, 0, S_PrecacheSound ("weapons/lashit.wav"), pos, 1, 1);

	// light
		if (r_explosionlight.value)
		{
			dlight_t *dl;
			dl = CL_AllocDlight (0);
			VectorCopy (pos, dl->origin);
			dl->radius = 150 * r_explosionlight.value;
			dl->die = cl.time + 0.4;
			dl->decay = 400;
			dl->color[0] = 0.2;
			dl->color[1] = 0.2;
			dl->color[2] = 0.0;
			dl->channelfade[0] = 0.5;
			dl->channelfade[1] = 0.51;
			dl->channelfade[2] = 0.0;
		}

		break;

	case Q2TE_RAILTRAIL:			// railgun effect
		MSG_ReadPos (pos);
		MSG_ReadPos (pos2);
		if (P_ParticleTrail(pos, pos2, rt_railtrail, NULL))
			P_ParticleTrailIndex(pos, pos2, 0x74, 8, NULL);
		Q2S_StartSound (pos, 0, 0, S_PrecacheSound ("weapons/railgf1a.wav"), 1, ATTN_NORM, 0);
		break;

	case Q2TE_EXPLOSION2:
	case Q2TE_GRENADE_EXPLOSION:
	case Q2TE_GRENADE_EXPLOSION_WATER:
		MSG_ReadPos (pos);

		if (P_RunParticleEffectType(pos, NULL, 1, pt_explosion))
			P_RunParticleEffect(pos, NULL, 0xe0, 256);

		R_AddStain(pos, -1, -1, -1, 100);
			
	// light
		if (r_explosionlight.value)
		{
			dlight_t *dl;
			dl = CL_AllocDlight (0);
			VectorCopy (pos, dl->origin);
			dl->radius = 150 + r_explosionlight.value*200;
			dl->die = cl.time + 0.5;
			dl->decay = 300;
			dl->color[0] = 0.2;
			dl->color[1] = 0.1;
			dl->color[2] = 0.1;
			dl->channelfade[0] = 0.36;
			dl->channelfade[1] = 0.19;
			dl->channelfade[2] = 0.19;
		}
	
	// sound
		if (type == Q2TE_GRENADE_EXPLOSION_WATER)
			S_StartSound (-2, 0, S_PrecacheSound ("weapons/xpld_wat.wav"), pos, 1, 1);
		else
			S_StartSound (-2, 0, S_PrecacheSound ("weapons/grenlx1a.wav"), pos, 1, 1);
	
	// sprite
		/*
		if (!R_ParticleExplosionHeart(pos))
		{
			ex = CL_AllocExplosion ();
			VectorCopy (pos, ex->origin);
			VectorClear(ex->angles);
			ex->start = cl.time;
			ex->model = Mod_ForName (q2tentmodels[q2cl_mod_explo4].modelname, true);
			ex->firstframe = 30;
			ex->numframes = 19;
		}
		*/
		break;
/*
		ex = CL_AllocExplosion ();
		VectorCopy (pos, ex->ent.origin);
		ex->type = ex_poly;
		ex->ent.flags = RF_FULLBRIGHT;
		ex->start = cl.frame.servertime - 100;
		ex->light = 350;
		ex->lightcolor[0] = 1.0;
		ex->lightcolor[1] = 0.5;
		ex->lightcolor[2] = 0.5;
		ex->ent.model = cl_mod_explo4;
		ex->frames = 19;
		ex->baseframe = 30;
		ex->ent.angles[1] = rand() % 360;
		CL_ExplosionParticles (pos);
		if (type == TE_GRENADE_EXPLOSION_WATER)
			Q2S_StartSound (pos, 0, 0, cl_sfx_watrexp, 1, ATTN_NORM, 0);
		else
			Q2S_StartSound (pos, 0, 0, cl_sfx_grenexp, 1, ATTN_NORM, 0);
		break;
*/
	// RAFAEL
	case Q2TE_PLASMA_EXPLOSION:
		MSG_ReadPos (pos);
/*		ex = CL_AllocExplosion ();
		VectorCopy (pos, ex->ent.origin);
		ex->type = ex_poly;
		ex->ent.flags = RF_FULLBRIGHT;
		ex->start = cl.frame.servertime - 100;
		ex->light = 350;
		ex->lightcolor[0] = 1.0; 
		ex->lightcolor[1] = 0.5;
		ex->lightcolor[2] = 0.5;
		ex->ent.angles[1] = rand() % 360;
		ex->ent.model = cl_mod_explo4;
		if (frand() < 0.5)
			ex->baseframe = 15;
		ex->frames = 15;
		CL_ExplosionParticles (pos);
		Q2S_StartSound (pos, 0, 0, cl_sfx_rockexp, 1, ATTN_NORM, 0);
*/		break;

	case Q2TE_EXPLOSION1:
	case Q2TE_EXPLOSION1_BIG:						// PMM
	case Q2TE_ROCKET_EXPLOSION:
	case Q2TE_ROCKET_EXPLOSION_WATER:
	case Q2TE_EXPLOSION1_NP:						// PMM
		MSG_ReadPos (pos);

	// particle effect
		if (type != Q2TE_EXPLOSION1_BIG && type != Q2TE_EXPLOSION1_NP)
		{
			if (P_RunParticleEffectType(pos, NULL, 1, pt_explosion))
				P_RunParticleEffect(pos, NULL, 0xe0, 256);

			R_AddStain(pos, -1, -1, -1, 100);
		}

	// light
		if (r_explosionlight.value)
		{
			dlight_t *dl;
			dl = CL_AllocDlight (0);
			VectorCopy (pos, dl->origin);
			dl->radius = 150 + r_explosionlight.value*200;
			dl->die = cl.time + 0.5;
			dl->decay = 300;
			dl->color[0] = 0.2;
			dl->color[1] = 0.1;
			dl->color[2] = 0.08;
			dl->channelfade[0] = 0.36;
			dl->channelfade[1] = 0.19;
			dl->channelfade[2] = 0.19;
		}

	// sound
		if (type == Q2TE_ROCKET_EXPLOSION_WATER)
			S_StartSound (-2, 0, S_PrecacheSound ("weapons/xpld_wat.wav"), pos, 1, 1);
		else
			S_StartSound (-2, 0, S_PrecacheSound ("weapons/rocklx1a.wav"), pos, 1, 1);

	// sprite		
/*		if (!R_ParticleExplosionHeart(pos))
		{
			ex = CL_AllocExplosion ();
			VectorCopy (pos, ex->origin);
			VectorClear(ex->angles);
			ex->start = cl.time;
			ex->model = Mod_ForName (q2tentmodels[q2cl_mod_explo4].modelname, false);
			if (rand()&1)
				ex->firstframe = 15;
			else
				ex->firstframe = 0;
			ex->numframes = 15;
		}*/
		break;
/*
		ex = CL_AllocExplosion ();
		VectorCopy (pos, ex->ent.origin);
		ex->type = ex_poly;
		ex->ent.flags = RF_FULLBRIGHT;
		ex->start = cl.frame.servertime - 100;
		ex->light = 350;
		ex->lightcolor[0] = 1.0;
		ex->lightcolor[1] = 0.5;
		ex->lightcolor[2] = 0.5;
		ex->ent.angles[1] = rand() % 360;
		if (type != TE_EXPLOSION1_BIG)				// PMM
			ex->ent.model = cl_mod_explo4;			// PMM
		else
			ex->ent.model = cl_mod_explo4_big;
		if (frand() < 0.5)
			ex->baseframe = 15;
		ex->frames = 15;
		if ((type != TE_EXPLOSION1_BIG) && (type != TE_EXPLOSION1_NP))		// PMM
			CL_ExplosionParticles (pos);									// PMM
		if (type == TE_ROCKET_EXPLOSION_WATER)
			Q2S_StartSound (pos, 0, 0, cl_sfx_watrexp, 1, ATTN_NORM, 0);
		else
			Q2S_StartSound (pos, 0, 0, cl_sfx_rockexp, 1, ATTN_NORM, 0);
		break;

*/	case Q2TE_BFG_EXPLOSION:
		MSG_ReadPos (pos);
/*		ex = CL_AllocExplosion ();
		VectorCopy (pos, ex->ent.origin);
		ex->type = ex_poly;
		ex->flags = RF_FULLBRIGHT;
		ex->start = cl.q2frame.servertime - 100;
		ex->light = 350;
		ex->lightcolor[0] = 0.0;
		ex->lightcolor[1] = 1.0;
		ex->lightcolor[2] = 0.0;
		ex->model = cl_mod_bfg_explo;
		ex->flags |= RF_TRANSLUCENT;
		ex->alpha = 0.30;
		ex->frames = 4;
*/		break;

	case Q2TE_BFG_BIGEXPLOSION:
		MSG_ReadPos (pos);
//		CL_BFGExplosionParticles (pos);
		if (P_RunParticleEffectTypeString(pos, dir, 1, "te_bfg_bigexplosion"))
			P_RunParticleEffect(pos, dir, 0xd0, 256); // TODO: x+(r%8) unstead of x&7+(r&7)
		break;

	case Q2TE_BFG_LASER:
		MSG_ReadPos (pos);
		MSG_ReadPos (pos2);
		CL_Laser(pos, pos2, 0xd0d1d2d3);
		break;

	case Q2TE_BUBBLETRAIL:
		MSG_ReadPos (pos);
		MSG_ReadPos (pos2);
		if (P_ParticleTrail(pos, pos2, rt_bubbletrail, NULL))
			P_ParticleTrailIndex(pos, pos2, 4, 8, NULL);
		break;

	case Q2TE_PARASITE_ATTACK:
	case Q2TE_MEDIC_CABLE_ATTACK:
		CL_ParseBeam (3);
		break;

	case Q2TE_BOSSTPORT:			// boss teleporting to station
		MSG_ReadPos (pos);
/*		CL_BigTeleportParticles (pos);
*/		Q2S_StartSound (pos, 0, 0, S_PrecacheSound ("misc/bigtele.wav"), 1, ATTN_NONE, 0);
		break;

	case Q2TE_GRAPPLE_CABLE:
		CL_ParseBeam (4);
		MSG_ReadPos (pos);
		break;

	// RAFAEL
	case Q2TE_WELDING_SPARKS:
		cnt = MSG_ReadByte ();
		MSG_ReadPos (pos);
		MSG_ReadDir (dir);
		color = MSG_ReadByte ();

		// TODO: fix to Q2's standards
		P_RunParticleEffect(pos, dir, color, cnt);
/*		CL_ParticleEffect2 (pos, dir, color, cnt);

		ex = CL_AllocExplosion ();
		VectorCopy (pos, ex->ent.origin);
		ex->type = ex_flash;
		// note to self
		// we need a better no draw flag
		ex->ent.flags = RF_BEAM;
		ex->start = cl.frame.servertime - 0.1;
		ex->light = 100 + (rand()%75);
		ex->lightcolor[0] = 1.0;
		ex->lightcolor[1] = 1.0;
		ex->lightcolor[2] = 0.3;
		ex->ent.model = cl_mod_flash;
		ex->frames = 2;
*/		break;

	case Q2TE_GREENBLOOD:
		MSG_ReadPos (pos);
		MSG_ReadDir (dir);
		if (P_RunParticleEffectTypeString(pos, dir, 1, "te_greenblood"))
			P_RunParticleEffect(pos, dir, 0xdf, 30); // TODO: x+(r%8) unstead of x&7+(r&7)
//		CL_ParticleEffect2 (pos, dir, 0xdf, 30);
		break;

	// RAFAEL
	case Q2TE_TUNNEL_SPARKS:
		cnt = MSG_ReadByte ();
		MSG_ReadPos (pos);
		MSG_ReadDir (dir);
		color = MSG_ReadByte ();
//		CL_ParticleEffect3 (pos, dir, color, cnt);
		break;

//=============
//PGM
		// PMM -following code integrated for flechette (different color)
	case Q2TE_BLASTER2:			// green blaster hitting wall
		MSG_ReadPos (pos);
		MSG_ReadDir (dir);

		if (P_RunParticleEffectTypeString(pos, dir, 1, "te_blaster2"))
			if (P_RunParticleEffectType(pos, dir, 1, pt_blasterparticles))
				P_RunParticleEffect (pos, dir, 0xd0, 40);

		R_AddStain(pos, -10, 0, -10, 20);

		ex = CL_AllocExplosion ();
		VectorCopy (pos, ex->origin);
		ex->start = cl.time;
		ex->model = Mod_ForName (q2tentmodels[q2cl_mod_explode].modelname, false);
		ex->firstframe = 0;
		ex->numframes = 4;
		ex->flags = Q2RF_FULLBRIGHT;
		
		ex->angles[0] = acos(dir[2])/M_PI*180;
	// PMM - fixed to correct for pitch of 0
		if (dir[0])
			ex->angles[1] = atan2(dir[1], dir[0])/M_PI*180;
		else if (dir[1] > 0)
			ex->angles[1] = 90;
		else if (dir[1] < 0)
			ex->angles[1] = 270;
		else
			ex->angles[1] = 0;
		ex->angles[0]*=-1;

		S_StartSound (-2, 0, S_PrecacheSound ("weapons/lashit.wav"), pos, 1, 1);

	// light
		if (r_explosionlight.value)
		{
			dlight_t *dl;
			dl = CL_AllocDlight (0);
			VectorCopy (pos, dl->origin);
			dl->radius = 150 * r_explosionlight.value;
			dl->die = cl.time + 0.4;
			dl->decay = 400;
			dl->color[0] = 0.01;
			dl->color[1] = 0.2;
			dl->color[2] = 0.01;
			dl->channelfade[0] = 0.1;
			dl->channelfade[1] = 0.5;
			dl->channelfade[2] = 0.1;
		}
		break;

	case Q2TE_FLECHETTE:			// blue blaster effect
		MSG_ReadPos (pos);
		MSG_ReadDir (dir);

		if (P_RunParticleEffectTypeString(pos, dir, 1, "te_blaster2"))
			if (P_RunParticleEffectType(pos, dir, 1, pt_blasterparticles))
				P_RunParticleEffect (pos, dir, 0x6f, 40);

		R_AddStain(pos, -10, -2, 0, 20);

		ex = CL_AllocExplosion ();
		VectorCopy (pos, ex->origin);
		ex->start = cl.time;
		ex->model = Mod_ForName (q2tentmodels[q2cl_mod_explode].modelname, false);
		ex->firstframe = 0;
		ex->numframes = 4;
		ex->flags = Q2RF_FULLBRIGHT;
		
		ex->angles[0] = acos(dir[2])/M_PI*180;
	// PMM - fixed to correct for pitch of 0
		if (dir[0])
			ex->angles[1] = atan2(dir[1], dir[0])/M_PI*180;
		else if (dir[1] > 0)
			ex->angles[1] = 90;
		else if (dir[1] < 0)
			ex->angles[1] = 270;
		else
			ex->angles[1] = 0;
		ex->angles[0]*=-1;

		S_StartSound (-2, 0, S_PrecacheSound ("weapons/lashit.wav"), pos, 1, 1);

	// light
		if (r_explosionlight.value)
		{
			dlight_t *dl;
			dl = CL_AllocDlight (0);
			VectorCopy (pos, dl->origin);
			dl->radius = 150 * r_explosionlight.value;
			dl->die = cl.time + 0.4;
			dl->decay = 400;
			dl->color[0] = 0.038;
			dl->color[1] = 0.082;
			dl->color[2] = 0.150;
			dl->channelfade[0] = 0.085;
			dl->channelfade[1] = 0.180;
			dl->channelfade[2] = 0.300;
		}
		break;


	case Q2TE_LIGHTNING:
		CL_ParseBeam(TE_LIGHTNING1);
		Q2S_StartSound (pos, 0, 0, S_PrecacheSound("weapons/tesla.wav"), 1, ATTN_NORM, 0);
		break;

	case Q2TE_DEBUGTRAIL:
		MSG_ReadPos (pos);
		MSG_ReadPos (pos2);
		if (P_ParticleTrail(pos, pos2, P_AllocateParticleType("te_debugtrail"), NULL))
			P_ParticleTrailIndex(pos, pos2, 116, 8, NULL);
		break;

	case Q2TE_PLAIN_EXPLOSION:
		MSG_ReadPos (pos);

		ex = CL_AllocExplosion ();
		VectorCopy (pos, ex->origin);
//		ex->type = ex_poly;
		ex->flags = Q2RF_FULLBRIGHT;
		ex->angles[1] = rand() % 360;
		ex->model = Mod_ForName (q2tentmodels[q2cl_mod_explo4].modelname, false);
		if (rand() < RAND_MAX/2)
			ex->firstframe = 15;
		ex->numframes = 15;
		Q2S_StartSound (pos, 0, 0, S_PrecacheSound("weapons/rocklx1a.wav"), 1, ATTN_NORM, 0);

	// light
		if (r_explosionlight.value)
		{
			dlight_t *dl;
			dl = CL_AllocDlight (0);
			VectorCopy (pos, dl->origin);
			dl->radius = 150 + r_explosionlight.value*200;
			dl->die = cl.time + 0.5;
			dl->decay = 300;
			dl->color[0] = 0.2;
			dl->color[1] = 0.1;
			dl->color[2] = 0.08;
			dl->channelfade[0] = 0.36;
			dl->channelfade[1] = 0.19;
			dl->channelfade[2] = 0.19;
		}

		break;
/*
	case Q2TE_FLASHLIGHT:
		MSG_ReadPos(&net_message, pos);
		ent = MSG_ReadShort(&net_message);
		CL_Flashlight(ent, pos);
		break;

	case Q2TE_FORCEWALL:
		MSG_ReadPos(&net_message, pos);
		MSG_ReadPos(&net_message, pos2);
		color = MSG_ReadByte (&net_message);
		CL_ForceWall(pos, pos2, color);
		break;
*/
	case Q2TE_HEATBEAM:
		MSG_ReadPos(pos);
		MSG_ReadPos(pos2);
//		ent = CL_ParsePlayerBeam (cl_mod_heatbeam);
		break;
/*
	case Q2TE_MONSTER_HEATBEAM:
		ent = CL_ParsePlayerBeam (cl_mod_monster_heatbeam);
		break;

	case Q2TE_HEATBEAM_SPARKS:
//		cnt = MSG_ReadByte (&net_message);
		cnt = 50;
		MSG_ReadPos (&net_message, pos);
		MSG_ReadDir (&net_message, dir);
//		r = MSG_ReadByte (&net_message);
//		magnitude = MSG_ReadShort (&net_message);
		r = 8;
		magnitude = 60;
		color = r & 0xff;
		CL_ParticleSteamEffect (pos, dir, color, cnt, magnitude);
		S_StartSound (pos,  0, 0, cl_sfx_lashit, 1, ATTN_NORM, 0);
		break;
	
	case Q2TE_HEATBEAM_STEAM:
//		cnt = MSG_ReadByte (&net_message);
		cnt = 20;
		MSG_ReadPos (&net_message, pos);
		MSG_ReadDir (&net_message, dir);
//		r = MSG_ReadByte (&net_message);
//		magnitude = MSG_ReadShort (&net_message);
//		color = r & 0xff;
		color = 0xe0;
		magnitude = 60;
		CL_ParticleSteamEffect (pos, dir, color, cnt, magnitude);
		S_StartSound (pos,  0, 0, cl_sfx_lashit, 1, ATTN_NORM, 0);
		break;

	case Q2TE_STEAM:
		CL_ParseSteam();
		break;

	case Q2TE_BUBBLETRAIL2:
//		cnt = MSG_ReadByte (&net_message);
		cnt = 8;
		MSG_ReadPos (&net_message, pos);
		MSG_ReadPos (&net_message, pos2);
		CL_BubbleTrail2 (pos, pos2, cnt);
		S_StartSound (pos,  0, 0, cl_sfx_lashit, 1, ATTN_NORM, 0);
		break;
*/
	case Q2TE_MOREBLOOD:
		MSG_ReadPos (pos);
		MSG_ReadDir (dir);
		if (P_RunParticleEffectTypeString(pos, dir, 1, "te_moreblood"))
			if (P_RunParticleEffectType(pos, dir, 4, pt_blood))
				P_RunParticleEffect(pos, dir, 0xe8, 250);
		break;
/*
	case Q2TE_CHAINFIST_SMOKE:
		dir[0]=0; dir[1]=0; dir[2]=1;
		MSG_ReadPos(&net_message, pos);
		CL_ParticleSmokeEffect (pos, dir, 0, 20, 20);
		break;

	case Q2TE_ELECTRIC_SPARKS:
		MSG_ReadPos (&net_message, pos);
		MSG_ReadDir (&net_message, dir);
//		CL_ParticleEffect (pos, dir, 109, 40);
		CL_ParticleEffect (pos, dir, 0x75, 40);
		//FIXME : replace or remove this sound
		S_StartSound (pos, 0, 0, cl_sfx_lashit, 1, ATTN_NORM, 0);
		break;

*/
	case Q2TE_TRACKER_EXPLOSION:
		MSG_ReadPos (pos);

		// effect
		if (P_RunParticleEffectTypeString(pos, NULL, 1, "te_tracker_explosion"))
			P_RunParticleEffect(pos, NULL, 0, 128); // TODO: needs to be nonrandom instead of 0+r%8

		// light
	// light
		if (r_explosionlight.value)
		{
			dlight_t *dl;
			dl = CL_AllocDlight (0);
			VectorCopy (pos, dl->origin);
			dl->radius = 150 * r_explosionlight.value;
			dl->die = cl.time + 0.1;
			dl->minlight = 250;
			dl->color[0] = -0.2;
			dl->color[1] = -0.2;
			dl->color[2] = -0.2;
		}

		// sound
		Q2S_StartSound (pos, 0, 0, S_PrecacheSound("weapons/disrupthit.wav"), 1, ATTN_NORM, 0);
		break;
	case Q2TE_TELEPORT_EFFECT:
	case Q2TE_DBALL_GOAL:
		MSG_ReadPos (pos);
		if (P_RunParticleEffectType(pos, NULL, 1, pt_teleportsplash))
			P_RunParticleEffect(pos, NULL, 8, 768);
		// This effect won't match ---
		// Color should be 7+(rand()%8)
		// not 8&~7+(rand()%8)
		break;

	case Q2TE_WIDOWBEAMOUT:
		// this one is really annoying, it's supposed to be a random choice
		// between 2*8, 13*8, 21*8, 18*8, and it respreads every frame
		// into a circle but it could be faked well enough, well except for
		// the fact that these effects have ids associated with them
		// sort of how beams have ents associated
		MSG_ReadShort(); // id
		if (P_RunParticleEffectTypeString(pos, NULL, 1, "te_widowbeamout"))
			P_RunParticleEffect(pos, NULL, 13*8, 300); 
		break;

	case Q2TE_NUKEBLAST:
		// same problem as te_widowbeamout, but colors are a bit easier to manage
		// and there's no id to read in
		MSG_ReadPos (pos);
		if (P_RunParticleEffectTypeString(pos, NULL, 1, "te_nukeblast"))
			P_RunParticleEffect(pos, NULL, 110, 700); 
		break;

	case Q2TE_WIDOWSPLASH:
		// there's the color issue like with te_widowbeamout, but the particles
		// are spawned in an immediate circle and not substained, so it's much
		// easier to manage
		MSG_ReadPos (pos);
		if (P_RunParticleEffectTypeString(pos, NULL, 1, "te_widowsplash"))
			P_RunParticleEffect(pos, NULL, 13*8, 256); 
		break;
//PGM
//==============


	case CRTE_LEADERBLASTER:
		Host_EndGame ("CLQ2_ParseTEnt: bad/non-implemented type %i", type);
	case CRTE_BLASTER_MUZZLEFLASH:
		MSG_ReadPos (pos);
		ex = CL_AllocExplosion ();
		VectorCopy (pos, ex->origin);
		ex->flags = Q2RF_FULLBRIGHT;
		ex->start = cl.q2frame.servertime - 100;
		CL_NewDlightRGB(0, pos[0], pos[1], pos[2], 350, 0.5, 0.2, 0.1, 0);
		P_RunParticleEffectTypeString(pos, NULL, 1, "te_muzzleflash");
		break;
	case CRTE_BLUE_MUZZLEFLASH:
		MSG_ReadPos (pos);
		ex = CL_AllocExplosion ();
		VectorCopy (pos, ex->origin);
		ex->flags = Q2RF_FULLBRIGHT;
		ex->start = cl.q2frame.servertime - 100;
		CL_NewDlightRGB(0, pos[0], pos[1], pos[2], 350, 0.5, 0.2, 0.1, 0);
		P_RunParticleEffectTypeString(pos, NULL, 1, "te_blue_muzzleflash");
		break;
	case CRTE_SMART_MUZZLEFLASH:
		MSG_ReadPos (pos);
		ex = CL_AllocExplosion ();
		VectorCopy (pos, ex->origin);
		ex->flags = Q2RF_FULLBRIGHT;
		ex->start = cl.q2frame.servertime - 100;
		CL_NewDlightRGB(0, pos[0], pos[1], pos[2], 350, 0.5, 0.2, 0, 0.2);
		P_RunParticleEffectTypeString(pos, NULL, 1, "te_smart_muzzleflash");
		break;
	case CRTE_LEADERFIELD:
		Host_EndGame ("CLQ2_ParseTEnt: bad/non-implemented type %i", type);
	case CRTE_DEATHFIELD:
		MSG_ReadPos (pos);
		ex = CL_AllocExplosion ();
		VectorCopy (pos, ex->origin);
		ex->flags = Q2RF_FULLBRIGHT;
		ex->start = cl.q2frame.servertime - 100;
		CL_NewDlightRGB(0, pos[0], pos[1], pos[2], 350, 0.5, 0.2, 0, 0.2);
		P_RunParticleEffectTypeString(pos, NULL, 1, "te_deathfield");
		break;
	case CRTE_BLASTERBEAM:
		MSG_ReadPos (pos);
		MSG_ReadPos (pos2);
		CLQ2_BlasterTrail2 (pos, pos2);
		break;
	case CRTE_STAIN:
		Host_EndGame ("CLQ2_ParseTEnt: bad/non-implemented type %i", type);
	case CRTE_FIRE:
		Host_EndGame ("CLQ2_ParseTEnt: bad/non-implemented type %i", type);
	case CRTE_CABLEGUT:
		Host_EndGame ("CLQ2_ParseTEnt: bad/non-implemented type %i", type);
	case CRTE_SMOKE:
		Host_EndGame ("CLQ2_ParseTEnt: bad/non-implemented type %i", type);

	default:
		Host_EndGame ("CLQ2_ParseTEnt: bad/non-implemented type %i", type);
	}
}
#endif


/*
=================
CL_NewTempEntity
=================
*/
entity_t *CL_NewTempEntity (void)
{
	entity_t	*ent;

	if (cl_numvisedicts == MAX_VISEDICTS)
		return NULL;
	ent = &cl_visedicts[cl_numvisedicts];
	cl_numvisedicts++;
	ent->keynum = 0;
	
	memset (ent, 0, sizeof(*ent));

#ifdef SWQUAKE
	ent->palremap = D_IdentityRemap();
#endif
#ifdef PEXT_SCALE
	ent->scale = 1;
#endif
	ent->shaderRGBAf[0] = 1;
	ent->shaderRGBAf[1] = 1;
	ent->shaderRGBAf[2] = 1;
	ent->shaderRGBAf[3] = 1;
	return ent;
}


/*
=================
CL_UpdateBeams
=================
*/
void CL_UpdateBeams (void)
{
	int			i, j;
	beam_t		*b;
	vec3_t		dist, org;
	float		*vieworg;
	float		d;
	entity_t	*ent;
	entity_state_t *st;
	float		yaw, pitch;
	float		forward, offset;
	int lastrunningbeam = -1;

	extern cvar_t cl_truelightning, v_viewheight;

// update lightning
	for (i=0, b=cl_beams; i < beams_running; i++, b++)
	{
		if (!b->model)
			continue;
		
		if (b->endtime < cl.time)
		{
			P_DelinkTrailstate(&b->trailstate);
			P_DelinkTrailstate(&b->emitstate);
			b->model = NULL;
			continue;
		}

		lastrunningbeam = i;

	// if coming from the player, update the start position
		if ((b->flags & 1) && b->entity > 0 && b->entity <= MAX_CLIENTS)
		{
			for (j = 0; j < cl.splitclients; j++)
			{
				if (b->entity == (autocam[j]?spec_track[j]:(cl.playernum[j]+1)))
				{
					player_state_t	*pl;
		//			VectorSubtract(cl.simorg, b->start, org);
		//			VectorAdd(b->end, org, b->end);		//move the end point by simorg-start

					pl = &cl.frames[cl.parsecount&UPDATE_MASK].playerstate[b->entity-1];
					if (pl->messagenum == cl.parsecount)
					{
						vec3_t	fwd, org, ang;
						float	delta, f, len;

						if (cl.spectator && autocam[j])
						{
							vieworg = pl->origin;
						}
						else
							vieworg = cl.simorg[j];

						VectorCopy (vieworg, b->start);
						b->start[2] += cl.crouch[j] + bound(-7, v_viewheight.value, 4);

						f = bound(0, cl_truelightning.value, 1);

						if (!f)
							break;

						VectorSubtract (playerbeam_end[j], vieworg, org);
						len = VectorLength(org);
						org[2] -= 22;		// adjust for view height
						vectoangles (org, ang);

						// lerp pitch
						ang[0] = -ang[0];
						if (ang[0] < -180)
							ang[0] += 360;
						ang[0] += (cl.simangles[j][0] - ang[0]) * f;

						// lerp yaw
						delta = cl.simangles[j][1] - ang[1];
						if (delta > 180)
							delta -= 360;
						if (delta < -180)
							delta += 360;
						ang[1] += delta * f;
						ang[2] = 0;

						AngleVectors (ang, fwd, ang, ang);
						VectorCopy(fwd, ang);
						VectorScale (fwd, len, fwd);
						VectorCopy (cl.simorg[j], org);
						org[2] += 16;
						VectorAdd (org, fwd, b->end);

						if (cl_beam_trace.value)
						{
							vec3_t normal;
							VectorMA(org, len+4, ang, fwd);
							if (TraceLineN(org, fwd, ang, normal))
								VectorCopy (ang, b->end);
						}
						break;
					}
				}
			}
		}
		else if (b->flags & STREAM_ATTACHED)
		{
			player_state_t	*pl;
			st = CL_FindPacketEntity(b->entity);
			if (st)
			{
				VectorCopy(st->origin, b->start);
			}
			else if (b->entity <= MAX_CLIENTS && b->entity > 0)
			{
				pl = &cl.frames[cl.parsecount&UPDATE_MASK].playerstate[b->entity-1];
				VectorCopy(pl->origin, b->start);
				b->start[2]+=16;
			}
		}

	// calculate pitch and yaw
		VectorSubtract (b->end, b->start, dist);

		if (dist[1] == 0 && dist[0] == 0)
		{
			yaw = 0;
			if (dist[2] > 0)
				pitch = 90;
			else
				pitch = 270;
		}
		else
		{
			yaw = (int) (atan2(dist[1], dist[0]) * 180 / M_PI);
			if (yaw < 0)
				yaw += 360;
	
			forward = sqrt (dist[0]*dist[0] + dist[1]*dist[1]);
			pitch = (int) (atan2(dist[2], forward) * 180 / M_PI);
			if (pitch < 0)
				pitch += 360;
		}


/*		if (1)	//cool funky particle mode.
		{
			CL_LightningParticleBeam(b->start, b->end);
			continue;
		}
*/
//		if (part_type[rt_lightning1].loaded)
//		if (!P_ParticleTrail(b->start, b->end, rt_lightning1, NULL))
//			continue;
		if (b->particleeffect >= 0 && !P_ParticleTrail(b->start, b->end, b->particleeffect, &b->trailstate))
			continue;

	// add new entities for the lightning
		VectorCopy (b->start, org);
		d = VectorNormalize(dist);

		if(b->flags & 2)
		{
			offset = (int)(cl.time*40)%30;
			for(i = 0; i < 3; i++)
			{
				org[i] += dist[i]*offset;
			}
		}

		while (d > 0)
		{
			ent = CL_NewTempEntity ();
			if (!ent)
				return;
			VectorCopy (org, ent->origin);
			ent->model = b->model;
			ent->drawflags |= MLS_ABSLIGHT;
			ent->abslight = 192;
			ent->shaderRGBAf[3] = b->alpha;

			ent->angles[0] = -pitch;
			ent->angles[1] = yaw;
			ent->angles[2] = (int)((cl.time*d*1000))%360;	//paused lightning too.
			AngleVectors(ent->angles, ent->axis[0], ent->axis[1], ent->axis[2]);
			ent->angles[0] = pitch;

			for (i=0 ; i<3 ; i++)
				org[i] += dist[i]*30;
			d -= 30;
		}
	}
	
	beams_running = lastrunningbeam+1;
}

/*
=================
CL_UpdateExplosions
=================
*/
void CL_UpdateExplosions (void)
{
	int			i;
	float		f;
	int			of;
	int numframes;
	int firstframe;
	explosion_t	*ex;
	entity_t	*ent;
	int lastrunningexplosion = -1;

	for (i=0, ex=cl_explosions; i < explosions_running; i++, ex++)
	{
		if (!ex->model)
			continue;

		lastrunningexplosion = i;
		f = ex->framerate*(cl.time - ex->start);
		if (ex->firstframe >= 0)
		{
			firstframe = ex->firstframe;
			numframes = ex->numframes;
		}
		else
		{
			firstframe = 0;
			numframes = ex->model->numframes;
		}

		of = (int)f-1;
		if ((int)f >= numframes || (int)f < 0)
		{
			ex->model = NULL;
			continue;
		}
		if (of < 0)
			of = 0;

		ent = CL_NewTempEntity ();
		if (!ent)
			return;
		VectorCopy (ex->origin, ent->origin);
		VectorCopy (ex->oldorigin, ent->oldorigin);
		VectorCopy (ex->angles, ent->angles);
		ent->skinnum = ex->skinnum;
		ent->angles[0]*=-1;
		AngleVectors(ent->angles, ent->axis[0], ent->axis[1], ent->axis[2]);
		VectorInverse(ent->axis[1]);
		ent->model = ex->model;
		ent->frame = (int)f+firstframe;
		ent->oldframe = of+firstframe;
		ent->lerpfrac = 1-(f - (int)f);
		ent->shaderRGBAf[3] = 1.0 - f/(numframes);
		ent->flags = ex->flags;
	}

	explosions_running = lastrunningexplosion + 1;
}

entity_state_t *CL_FindPacketEntity(int num);

/*
=================
CL_UpdateTEnts
=================
*/
void CL_UpdateTEnts (void)
{
	CL_UpdateBeams ();
	CL_UpdateExplosions ();
}
