/*
 * Copyright (C) 1997-2001 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 "client.h"

#ifdef QMAX
cparticle_t    *
setupParticle(
    float angle0, float angle1, float angle2,
    float org0, float org1, float org2,
    float vel0, float vel1, float vel2,
    float accel0, float accel1, float accel2,
    float color0, float color1, float color2,
    float colorvel0, float colorvel1, float colorvel2,
    float alpha, float alphavel,
    int blendfunc_src, int blendfunc_dst,
    float size, float sizevel,
    int image,
    int flags,
    void (*think) (cparticle_t * p, vec3_t org, vec3_t angle, float *alpha, float *size, int *image, float *time),
    qboolean thinknext);
#endif

typedef enum {
	ex_free, ex_explosion, ex_misc, ex_flash, ex_mflash, ex_poly, ex_poly2
} exptype_t;

typedef struct {
	exptype_t	type;
	entity_t	ent;

	int		frames;
	float		light;
	vec3_t		lightcolor;
	float		start;
	int		baseframe;
} explosion_t;

#define	MAX_EXPLOSIONS	32
explosion_t	cl_explosions[MAX_EXPLOSIONS];


#define	MAX_BEAMS	32
typedef struct {
	int		entity;
	int		dest_entity;
	struct model_s *model;
	int		endtime;
	vec3_t		offset;
	vec3_t		start  , end;
} beam_t;
beam_t		cl_beams[MAX_BEAMS];

/*
 * PMM - added this for player-linked beams.  Currently only used by the plasma beam
 */
beam_t		cl_playerbeams[MAX_BEAMS];


#define	MAX_LASERS	32
typedef struct {
	entity_t	ent;
	int		endtime;
} laser_t;
laser_t		cl_lasers[MAX_LASERS];

/* ROGUE */
cl_sustain_t	cl_sustains[MAX_SUSTAINS];
/* ROGUE */


#define EXPLOSION_PARTICLES(x) CL_ExplosionParticles((x));

#ifdef QMAX
void CL_BloodHit(vec3_t org, vec3_t dir);
void CL_BlasterParticles(vec3_t org, vec3_t dir, int count);
void CL_BlasterParticlesColor(vec3_t org, vec3_t dir, int count, int red, int green, int blue, 
                               int reddelta, int greendelta, int bluedelta);
void CL_ParticleEffectSparks(vec3_t org, vec3_t dir, vec3_t color, int count);
void CL_ExplosionParticles_Old(vec3_t org);
void CL_ExplosionParticles(vec3_t org, float scale, int water);
void CL_LightningFlare(vec3_t start, int srcEnt, int dstEnt);
void CL_GreenBloodHit(vec3_t org, vec3_t dir);
void CL_ParticleFootPrint(vec3_t org, vec3_t angle, float size, vec3_t color);
void CL_FlameBurst(vec3_t pos, float size);
void CL_LaserStun(vec3_t pos, vec3_t dir, vec3_t color, float size);
void CL_StunBlast(vec3_t pos, vec3_t color, float size);
void CL_Disruptor_Explosion_Particle(vec3_t org, float size);
void CL_Disintegrate(vec3_t pos, int ent);
void CL_WeatherFx(vec3_t org, vec3_t vec, vec3_t color, int type, float size, float time);
void CL_LensFlare(vec3_t pos, vec3_t whatever, float size, float time);
void CL_Explosion_Sparks(vec3_t org, int size);
#endif

#ifndef QMAX
void CL_BlasterParticles(vec3_t org, vec3_t dir);
void CL_ExplosionParticles(vec3_t org);
#endif

void CL_Explosion_Particle(vec3_t org, float scale, qboolean large, qboolean rocket);
void CL_BFGExplosionParticles(vec3_t org);
#ifdef QMAX
void CL_BFGExplosionParticles_2(vec3_t org);
#endif

/* RAFAEL */
void CL_BlueBlasterParticles(vec3_t org, vec3_t dir);

/* PGM */
extern void CL_TeleportParticles(vec3_t org);

/* PGM */
struct sfx_s   *cl_sfx_ric1;
struct sfx_s   *cl_sfx_ric2;
struct sfx_s   *cl_sfx_ric3;
struct sfx_s   *cl_sfx_lashit;
struct sfx_s   *cl_sfx_spark5;
struct sfx_s   *cl_sfx_spark6;
struct sfx_s   *cl_sfx_spark7;
struct sfx_s   *cl_sfx_railg;
struct sfx_s   *cl_sfx_rockexp;
struct sfx_s   *cl_sfx_grenexp;
struct sfx_s   *cl_sfx_watrexp;
struct sfx_s   *cl_sfx_underwater;
/* RAFAEL */

struct sfx_s   *cl_sfx_plasexp;
struct sfx_s   *cl_sfx_footsteps[4];
/* Knightmare- Lazarus footstep sounds */
struct sfx_s	*cl_sfx_metal_footsteps[4];
struct sfx_s	*cl_sfx_dirt_footsteps[4];
struct sfx_s	*cl_sfx_vent_footsteps[4];
struct sfx_s	*cl_sfx_grate_footsteps[4];
struct sfx_s	*cl_sfx_tile_footsteps[4];
struct sfx_s	*cl_sfx_grass_footsteps[4];
struct sfx_s	*cl_sfx_snow_footsteps[4];
struct sfx_s	*cl_sfx_carpet_footsteps[4];
struct sfx_s	*cl_sfx_force_footsteps[4];
struct sfx_s	*cl_sfx_gravel_footsteps[4];
struct sfx_s	*cl_sfx_ice_footsteps[4];
struct sfx_s	*cl_sfx_sand_footsteps[4];
struct sfx_s	*cl_sfx_wood_footsteps[4];
struct sfx_s	*cl_sfx_slosh[4];
struct sfx_s	*cl_sfx_wade[4];
struct sfx_s	*cl_sfx_ladder[4];
/* end Knightmare */

struct model_s *cl_mod_explode;
struct model_s *cl_mod_smoke;
struct model_s *cl_mod_flash;
struct model_s *cl_mod_parasite_segment;
struct model_s *cl_mod_grapple_cable;
struct model_s *cl_mod_parasite_tip;
struct model_s *cl_mod_explo4;
struct model_s *cl_mod_bfg_explo;
struct model_s *cl_mod_powerscreen;

/* RAFAEL */
struct model_s *cl_mod_plasmaexplo;

/* ROGUE */
struct sfx_s   *cl_sfx_lightning;
struct sfx_s   *cl_sfx_disrexp;
struct model_s *cl_mod_lightning;
struct model_s *cl_mod_heatbeam;
struct model_s *cl_mod_monster_heatbeam;
struct model_s *cl_mod_explo4_big;

/* ROGUE */

/*
 * ================= CL_RegisterTEntSounds =================
 */

void 
ReadTextureSurfaceAssignments();

void
CL_RegisterTEntSounds(void)
{
	int		i;
	char		name      [MAX_QPATH];

	/* PMM - version stuff */
	/* Com_Printf ("%s\n", ROGUE_VERSION_STRING); */
	/* PMM */
	cl_sfx_ric1 = S_RegisterSound("world/ric1.wav");
	cl_sfx_ric2 = S_RegisterSound("world/ric2.wav");
	cl_sfx_ric3 = S_RegisterSound("world/ric3.wav");
	cl_sfx_lashit = S_RegisterSound("weapons/lashit.wav");
	cl_sfx_spark5 = S_RegisterSound("world/spark5.wav");
	cl_sfx_spark6 = S_RegisterSound("world/spark6.wav");
	cl_sfx_spark7 = S_RegisterSound("world/spark7.wav");
	cl_sfx_railg = S_RegisterSound("weapons/railgf1a.wav");
	cl_sfx_rockexp = S_RegisterSound("weapons/rocklx1a.wav");
	cl_sfx_grenexp = S_RegisterSound("weapons/grenlx1a.wav");
	cl_sfx_watrexp = S_RegisterSound("weapons/xpld_wat.wav");
	cl_sfx_underwater = S_RegisterSound("world/bubl1.wav");
	/* RAFAEL */
	/* cl_sfx_plasexp = S_RegisterSound ("weapons/plasexpl.wav"); */
	
	
	S_RegisterSound("player/land1.wav");

	S_RegisterSound("player/fall2.wav");
	S_RegisterSound("player/fall1.wav");

	for (i = 0; i < 4; i++) {
		Com_sprintf(name, sizeof(name), "player/step%i.wav", i + 1);
		cl_sfx_footsteps[i] = S_RegisterSound(name);
	}

	/* Knightmare- Lazarus footstep sounds */
	for (i=0 ; i<4 ; i++)
	{
		Com_sprintf (name, sizeof(name), "player/pl_metal%i.wav", i+1);
		cl_sfx_metal_footsteps[i] = S_RegisterSound (name);
	}
	for (i=0 ; i<4 ; i++)
	{
		Com_sprintf (name, sizeof(name), "player/pl_dirt%i.wav", i+1);
		cl_sfx_dirt_footsteps[i] = S_RegisterSound (name);
	}
	for (i=0 ; i<4 ; i++)
	{
		Com_sprintf (name, sizeof(name), "player/pl_duct%i.wav", i+1);
		cl_sfx_vent_footsteps[i] = S_RegisterSound (name);
	}
	for (i=0 ; i<4 ; i++)
	{
		Com_sprintf (name, sizeof(name), "player/pl_grate%i.wav", i+1);
		cl_sfx_grate_footsteps[i] = S_RegisterSound (name);
	}
	for (i=0 ; i<4 ; i++)
	{
		Com_sprintf (name, sizeof(name), "player/pl_tile%i.wav", i+1);
		cl_sfx_tile_footsteps[i] = S_RegisterSound (name);
	}
	for (i=0 ; i<4 ; i++)
	{
		Com_sprintf (name, sizeof(name), "player/pl_grass%i.wav", i+1);
		cl_sfx_grass_footsteps[i] = S_RegisterSound (name);
	}
	for (i=0 ; i<4 ; i++)
	{
		Com_sprintf (name, sizeof(name), "player/pl_snow%i.wav", i+1);
		cl_sfx_snow_footsteps[i] = S_RegisterSound (name);
	}
	for (i=0 ; i<4 ; i++)
	{
		Com_sprintf (name, sizeof(name), "player/pl_carpet%i.wav", i+1);
		cl_sfx_carpet_footsteps[i] = S_RegisterSound (name);
	}
	for (i=0 ; i<4 ; i++)
	{
		Com_sprintf (name, sizeof(name), "player/pl_force%i.wav", i+1);
		cl_sfx_force_footsteps[i] = S_RegisterSound (name);
	}
	for (i=0 ; i<4 ; i++)
	{
		Com_sprintf (name, sizeof(name), "player/pl_gravel%i.wav", i+1);
		cl_sfx_gravel_footsteps[i] = S_RegisterSound (name);
	}
	for (i=0; i<4; i++)
	{
		Com_sprintf (name, sizeof(name), "player/pl_ice%i.wav", i+1);
		cl_sfx_ice_footsteps[i] = S_RegisterSound (name);
	}
	for (i=0; i<4; i++)
	{
		Com_sprintf (name, sizeof(name), "player/pl_sand%i.wav", i+1);
		cl_sfx_sand_footsteps[i] = S_RegisterSound (name);
	}
	for (i=0; i<4; i++)
	{
		Com_sprintf (name, sizeof(name), "player/pl_wood%i.wav", i+1);
		cl_sfx_wood_footsteps[i] = S_RegisterSound (name);
	}
	for (i=0; i<4; i++)
	{
		Com_sprintf (name, sizeof(name), "player/pl_slosh%i.wav", i+1);
		cl_sfx_slosh[i] = S_RegisterSound (name);
	}
	for (i=0; i<4; i++)
	{
		Com_sprintf (name, sizeof(name), "player/pl_wade%i.wav", i+1);
		cl_sfx_wade[i] = S_RegisterSound (name);
	}
	for (i=0; i<4; i++)
	{
		Com_sprintf (name, sizeof(name), "player/pl_ladder%i.wav", i+1);
		cl_sfx_ladder[i] = S_RegisterSound (name);
	}
	if (cl_footsteps_override->value)
		ReadTextureSurfaceAssignments();
	/* end Knightmare */

	/* PGM */
	cl_sfx_lightning = S_RegisterSound("weapons/tesla.wav");
	cl_sfx_disrexp = S_RegisterSound("weapons/disrupthit.wav");
	/* version stuff */
	/* sprintf (name, "weapons/sound%d.wav", ROGUE_VERSION_ID); */
	/* if (name[0] == 'w') */
	/* name[0] = 'W'; */
	/* PGM */
}

/*
 * ================= CL_RegisterTEntModels =================
 */
void
CL_RegisterTEntModels(void)
{
	cl_mod_explode = re.RegisterModel("models/objects/explode/tris.md2");
	cl_mod_smoke = re.RegisterModel("models/objects/smoke/tris.md2");
	cl_mod_flash = re.RegisterModel("models/objects/flash/tris.md2");
	cl_mod_parasite_segment = re.RegisterModel("models/monsters/parasite/segment/tris.md2");
	cl_mod_grapple_cable = re.RegisterModel("models/ctf/segment/tris.md2");
	cl_mod_parasite_tip = re.RegisterModel("models/monsters/parasite/tip/tris.md2");
	cl_mod_explo4 = re.RegisterModel("models/objects/r_explode/tris.md2");
	cl_mod_bfg_explo = re.RegisterModel("sprites/s_bfg2.sp2");
	cl_mod_powerscreen = re.RegisterModel("models/items/armor/effect/tris.md2");

	re.RegisterModel("models/objects/laser/tris.md2");
	re.RegisterModel("models/objects/grenade2/tris.md2");
	re.RegisterModel("models/weapons/v_machn/tris.md2");
	re.RegisterModel("models/weapons/v_handgr/tris.md2");
	re.RegisterModel("models/weapons/v_shotg2/tris.md2");
	re.RegisterModel("models/objects/gibs/bone/tris.md2");
	re.RegisterModel("models/objects/gibs/sm_meat/tris.md2");
	re.RegisterModel("models/objects/gibs/bone2/tris.md2");
	/* RAFAEL */
	/* re.RegisterModel ("models/objects/blaser/tris.md2"); */

	re.RegisterPic("w_machinegun");
	re.RegisterPic("a_bullets");
	re.RegisterPic("i_health");
	re.RegisterPic("a_grenades");

	/* ROGUE */
	cl_mod_explo4_big = re.RegisterModel("models/objects/r_explode2/tris.md2");
	cl_mod_lightning = re.RegisterModel("models/proj/lightning/tris.md2");
	cl_mod_heatbeam = re.RegisterModel("models/proj/beam/tris.md2");
	cl_mod_monster_heatbeam = re.RegisterModel("models/proj/widowbeam/tris.md2");
	/* ROGUE */
}

/*
 * ================= CL_ClearTEnts =================
 */
void
CL_ClearTEnts(void)
{
	memset(cl_beams, 0, sizeof(cl_beams));
	memset(cl_explosions, 0, sizeof(cl_explosions));
	memset(cl_lasers, 0, sizeof(cl_lasers));

	/* ROGUE */
	memset(cl_playerbeams, 0, sizeof(cl_playerbeams));
	memset(cl_sustains, 0, sizeof(cl_sustains));
	/* ROGUE */
}

#ifdef QMAX
#define EXP_DIST 10
void
CL_R_Explode_SP(vec3_t origin)
{
	CL_Explosion_Particle(origin, 0, true, true);
}

void
CL_G_Explode_SP(vec3_t origin)
{
	CL_Explosion_Particle(origin, 0, true, false);
}
#endif

/*
 * ================= CL_AllocExplosion =================
 */
explosion_t    *
CL_AllocExplosion(void)
{
	int		i;
	int		time;
	int		index;

	for (i = 0; i < MAX_EXPLOSIONS; i++) {
		if (cl_explosions[i].type == ex_free) {
			memset(&cl_explosions[i], 0, sizeof(cl_explosions[i]));
			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;
		}
	memset(&cl_explosions[index], 0, sizeof(cl_explosions[index]));
	return &cl_explosions[index];
}

/*
 * ================= CL_SmokeAndFlash =================
 */
void
CL_SmokeAndFlash(vec3_t origin)
{
	explosion_t    *ex;

	ex = CL_AllocExplosion();
	VectorCopy(origin, ex->ent.origin);
	ex->type = ex_misc;
	ex->frames = 4;
	ex->ent.flags = RF_TRANSLUCENT;
	ex->start = cl.frame.servertime - 100;
	ex->ent.model = cl_mod_smoke;

	ex = CL_AllocExplosion();
	VectorCopy(origin, ex->ent.origin);
	ex->type = ex_flash;
	ex->ent.flags = RF_FULLBRIGHT;
	ex->frames = 2;
	ex->start = cl.frame.servertime - 100;
	ex->ent.model = cl_mod_flash;
}

/*
 * ================= CL_ParseParticles =================
 */
void
CL_ParseParticles(void)
{
	int		color     , count;
	vec3_t		pos    , dir;

	MSG_ReadPos(&net_message, pos);
	MSG_ReadDir(&net_message, dir);

	color = MSG_ReadByte(&net_message);

	count = MSG_ReadByte(&net_message);

	CL_ParticleEffect(pos, dir, color, count);
}

/*
 * ================= CL_ParseBeam =================
 */
int
CL_ParseBeam(struct model_s *model)
{
	int		ent;
	vec3_t		start  , end;
	beam_t         *b;
	int		i;

	ent = MSG_ReadShort(&net_message);

	MSG_ReadPos(&net_message, start);
	MSG_ReadPos(&net_message, end);

	/* override any beam with the same entity */
	for (i = 0, b = cl_beams; i < MAX_BEAMS; i++, b++)
		if (b->entity == ent) {
			b->entity = ent;
			b->model = model;
			b->endtime = cl.time + 200;
			VectorCopy(start, b->start);
			VectorCopy(end, b->end);
			VectorClear(b->offset);
			return ent;
		}
	/* find a free beam */
	for (i = 0, b = cl_beams; i < MAX_BEAMS; i++, b++) {
		if (!b->model || b->endtime < cl.time) {
			b->entity = ent;
			b->model = model;
			b->endtime = cl.time + 200;
			VectorCopy(start, b->start);
			VectorCopy(end, b->end);
			VectorClear(b->offset);
			return ent;
		}
	}
	Com_Printf("beam list overflow!\n");
	return ent;
}

/*
 * ================= CL_ParseBeam2 =================
 */
int
CL_ParseBeam2(struct model_s *model)
{
	int		ent;
	vec3_t		start  , end, offset;
	beam_t         *b;
	int		i;

	ent = MSG_ReadShort(&net_message);

	MSG_ReadPos(&net_message, start);
	MSG_ReadPos(&net_message, end);
	MSG_ReadPos(&net_message, offset);

	/* Com_Printf ("end- %f %f %f\n", end[0], end[1], end[2]); */

	/* override any beam with the same entity */

	for (i = 0, b = cl_beams; i < MAX_BEAMS; i++, b++)
		if (b->entity == ent) {
			b->entity = ent;
			b->model = model;
			b->endtime = cl.time + 200;
			VectorCopy(start, b->start);
			VectorCopy(end, b->end);
			VectorCopy(offset, b->offset);
			return ent;
		}
	/* find a free beam */
	for (i = 0, b = cl_beams; i < MAX_BEAMS; i++, b++) {
		if (!b->model || b->endtime < cl.time) {
			b->entity = ent;
			b->model = model;
			b->endtime = cl.time + 200;
			VectorCopy(start, b->start);
			VectorCopy(end, b->end);
			VectorCopy(offset, b->offset);
			return ent;
		}
	}
	Com_Printf("beam list overflow!\n");
	return ent;
}

/* ROGUE */

/*
 * ================= CL_ParsePlayerBeam - adds to the cl_playerbeam array
 * instead of the cl_beams array =================
 */
int
CL_ParsePlayerBeam(struct model_s *model)
{
	int		ent;
	vec3_t		start  , end, offset;
	beam_t         *b;
	int		i;

	ent = MSG_ReadShort(&net_message);

	MSG_ReadPos(&net_message, start);
	MSG_ReadPos(&net_message, end);
	/* PMM - network optimization */
	if (model == cl_mod_heatbeam)
		VectorSet(offset, 2, 7, -3);
	else if (model == cl_mod_monster_heatbeam) {
		model = cl_mod_heatbeam;
		VectorSet(offset, 0, 0, 0);
	} else
		MSG_ReadPos(&net_message, offset);

	/* Com_Printf ("end- %f %f %f\n", end[0], end[1], end[2]); */

	/* override any beam with the same entity */
	/* PMM - For player beams, we only want one per player (entity) so.. */
	for (i = 0, b = cl_playerbeams; i < MAX_BEAMS; i++, b++) {
		if (b->entity == ent) {
			b->entity = ent;
			b->model = model;
			b->endtime = cl.time + 200;
			VectorCopy(start, b->start);
			VectorCopy(end, b->end);
			VectorCopy(offset, b->offset);
			return ent;
		}
	}

	/* find a free beam */
	for (i = 0, b = cl_playerbeams; i < MAX_BEAMS; i++, b++) {
		if (!b->model || b->endtime < cl.time) {
			b->entity = ent;
			b->model = model;
			b->endtime = cl.time + 100;	/* PMM - this needs to
							 * be 100 to prevent
							 * multiple heatbeams */
			VectorCopy(start, b->start);
			VectorCopy(end, b->end);
			VectorCopy(offset, b->offset);
			return ent;
		}
	}
	Com_Printf("beam list overflow!\n");
	return ent;
}

/* rogue */

/*
 * ================= CL_ParseLightning =================
 */
#ifdef QMAX
void
CL_LightningBeam(vec3_t start, vec3_t end, int srcEnt, int dstEnt, float size);
int
CL_ParseLightning(float size)
{

	vec3_t		start  , end;
	int		srcEnt    , dstEnt;

	srcEnt = MSG_ReadShort(&net_message);
	dstEnt = MSG_ReadShort(&net_message);

	MSG_ReadPos(&net_message, start);
	MSG_ReadPos(&net_message, end);

	CL_LightningBeam(start, end, srcEnt, dstEnt, 5);

	return srcEnt;
}
#else
int
CL_ParseLightning(struct model_s *model)
{
	int		srcEnt    , destEnt;
	vec3_t		start  , end;
	beam_t         *b;
	int		i;

	srcEnt = MSG_ReadShort(&net_message);
	destEnt = MSG_ReadShort(&net_message);

	MSG_ReadPos(&net_message, start);
	MSG_ReadPos(&net_message, end);

	/* override any beam with the same source AND destination entities */
	for (i = 0, b = cl_beams; i < MAX_BEAMS; i++, b++)
		if (b->entity == srcEnt && b->dest_entity == destEnt) {

			/*
			 * Com_Printf("%d: OVERRIDE  %d -> %d\n", cl.time,
			 * srcEnt, destEnt);
			 */
			b->entity = srcEnt;
			b->dest_entity = destEnt;
			b->model = model;
			b->endtime = cl.time + 200;
			VectorCopy(start, b->start);
			VectorCopy(end, b->end);
			VectorClear(b->offset);
			return srcEnt;
		}
	/* find a free beam */
	for (i = 0, b = cl_beams; i < MAX_BEAMS; i++, b++) {
		if (!b->model || b->endtime < cl.time) {

			/*
			 * Com_Printf("%d: NORMAL  %d -> %d\n", cl.time,
			 * srcEnt, destEnt);
			 */
			b->entity = srcEnt;
			b->dest_entity = destEnt;
			b->model = model;
			b->endtime = cl.time + 200;
			VectorCopy(start, b->start);
			VectorCopy(end, b->end);
			VectorClear(b->offset);
			return srcEnt;
		}
	}
	Com_Printf("beam list overflow!\n");
	return srcEnt;
}
#endif

/*
 * ================= CL_ParseLaser =================
 */

#if 0 /* was QMAX TODO/FIXME */
void
CL_ParseLaser(int colors)
{
	vec3_t		start  , end;
	vec3_t		move;
	vec3_t		vec;
	vec3_t		point;
	float		len;
	float		dec;

	MSG_ReadPos(&net_message, start);
	MSG_ReadPos(&net_message, end);

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

	VectorCopy(vec, point);
	dec = 15;
	VectorScale(vec, dec, vec);
	VectorCopy(start, move);

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

		setupParticle(
		    point[0], point[1], point[2],
		    move[0], move[1], move[2],
		    0, 0, 0,
		    0, 0, 0,
		    50, 255, 50,
		    0, 0, 0,
		    0.75f, -2.5f,
		    GL_SRC_ALPHA, GL_ONE,
		    dec * TWOTHIRDS, 0,
		    particle_lightning,
		    PART_DIRECTION,
		    NULL, 0);

		VectorAdd(move, vec, move);
	}
}
#else
void
CL_ParseLaser(int colors)
{
	vec3_t		start;
	vec3_t		end;
	laser_t        *l;
	int		i;

	MSG_ReadPos(&net_message, start);
	MSG_ReadPos(&net_message, end);

	for (i = 0, l = cl_lasers; i < MAX_LASERS; i++, l++) {
		if (l->endtime < cl.time) {
			l->ent.flags = RF_TRANSLUCENT | RF_BEAM;
			VectorCopy(start, l->ent.origin);
			VectorCopy(end, l->ent.oldorigin);
			l->ent.alpha = 0.30;
			l->ent.skinnum = (colors >> ((rand() % 4) * 8)) & 0xff;
			l->ent.model = NULL;
			l->ent.frame = 4;
			l->endtime = cl.time + 100;
			return;
		}
	}
}
#endif

#ifdef QMAX
void
CL_GunSmokeEffect(vec3_t org, vec3_t dir, float smokesize)
{
	vec3_t		velocity, origin, direction;
	float		size;

	direction[0] = dir[0] + crand() * 0.2;
	direction[1] = dir[1] + crand() * 0.2;
	direction[2] = dir[2] + crand() * 0.2;

	size = smokesize * 5;
	VectorMA(org, size, dir, origin);
	VectorScale(direction, size, velocity);
	velocity[2] += smokesize * 0.2;
	CL_ParticleSmokeEffect(origin, velocity, size, 0.9);

	size = smokesize * 7.5;
	VectorMA(org, size, dir, origin);
	VectorScale(direction, size * 2, velocity);
	velocity[2] += smokesize * 0.3;
	CL_ParticleSmokeEffect(origin, velocity, size, 0.75);

	size = smokesize * 9;
	VectorMA(org, size, dir, origin);
	VectorScale(direction, size * 3, velocity);
	velocity[2] += smokesize * 0.4;
	CL_ParticleSmokeEffect(origin, velocity, size, 0.6);

	size = smokesize * 10;
	VectorMA(org, size, dir, origin);
	VectorScale(direction, size * 5, velocity);
	velocity[2] += smokesize * 0.5;
	CL_ParticleSmokeEffect(origin, velocity, size, 0.5);
}
#endif

/* ============= */
/* ROGUE */
void
CL_ParseSteam(void)
{
	vec3_t		pos    , dir;
	int		id        , i;
	int		r;
	int		cnt;
	int		color;
	int		magnitude;
	cl_sustain_t   *s, *free_sustain;

	id = MSG_ReadShort(&net_message);	/* an id of -1 is an instant
						 * effect */
	if (id != -1) {		/* sustains */
		/* Com_Printf ("Sustain effect id %d\n", id); */
		free_sustain = NULL;
		for (i = 0, s = cl_sustains; i < MAX_SUSTAINS; i++, s++) {
			if (s->id == 0) {
				free_sustain = s;
				break;
			}
		}
		if (free_sustain) {
			s->id = id;
			s->count = MSG_ReadByte(&net_message);
			MSG_ReadPos(&net_message, s->org);
			MSG_ReadDir(&net_message, s->dir);
			r = MSG_ReadByte(&net_message);
			s->color = r & 0xff;
			s->magnitude = MSG_ReadShort(&net_message);
			s->endtime = cl.time + MSG_ReadLong(&net_message);
			s->think = CL_ParticleSteamEffect2;
			s->thinkinterval = 100;
			s->nextthink = cl.time;
		} else {
			/* Com_Printf ("No free sustains!\n"); */
			/* FIXME - read the stuff anyway */
			cnt = MSG_ReadByte(&net_message);
			MSG_ReadPos(&net_message, pos);
			MSG_ReadDir(&net_message, dir);
			r = MSG_ReadByte(&net_message);
			magnitude = MSG_ReadShort(&net_message);
			magnitude = MSG_ReadLong(&net_message);	/* really interval */
		}
	} else {		/* instant */
		cnt = MSG_ReadByte(&net_message);
		MSG_ReadPos(&net_message, pos);
		MSG_ReadDir(&net_message, dir);
		r = MSG_ReadByte(&net_message);
		magnitude = MSG_ReadShort(&net_message);
		color = r & 0xff;
#ifdef QMAX
		CL_ParticleSteamEffect(pos, dir,
		    color8red(color), color8green(color), color8blue(color),
		    -20, -20, -20, cnt, magnitude);
#else
		CL_ParticleSteamEffect(pos, dir, color, cnt, magnitude);
#endif
		/* S_StartSound (pos,  0, 0, cl_sfx_lashit, 1, ATTN_NORM, 0); */
	}
}

void
CL_ParseWidow(void)
{
	vec3_t		pos;
	int		id        , i;
	cl_sustain_t   *s, *free_sustain;

	id = MSG_ReadShort(&net_message);

	free_sustain = NULL;
	for (i = 0, s = cl_sustains; i < MAX_SUSTAINS; i++, s++) {
		if (s->id == 0) {
			free_sustain = s;
			break;
		}
	}
	if (free_sustain) {
		s->id = id;
		MSG_ReadPos(&net_message, s->org);
		s->endtime = cl.time + 2100;
		s->think = CL_Widowbeamout;
		s->thinkinterval = 1;
		s->nextthink = cl.time;
	} else {		/* no free sustains */
		/* FIXME - read the stuff anyway */
		MSG_ReadPos(&net_message, pos);
	}
}

void
CL_ParseNuke(void)
{
	vec3_t		pos;
	int		i;
	cl_sustain_t   *s, *free_sustain;
#ifdef QMAX
	extern cvar_t	*cl_nukeblast_enh;
#endif	
	free_sustain = NULL;
	for (i = 0, s = cl_sustains; i < MAX_SUSTAINS; i++, s++) {
		if (s->id == 0) {
			free_sustain = s;
			break;
		}
	}
	if (free_sustain) {
		s->id = 21000;
		MSG_ReadPos(&net_message, s->org);
		s->endtime = cl.time + 1000;
#ifdef QMAX
		if (cl_nukeblast_enh->value) {
			s->think = CL_Nukeblast_Opt;
		}
		else 
#endif
		{
			s->think = CL_Nukeblast;
		}
		s->thinkinterval = 1;
		s->nextthink = cl.time;
	} else {		/* no free sustains */
		/* FIXME - read the stuff anyway */
		MSG_ReadPos(&net_message, pos);
	}
}

/* ROGUE */
/* ============= */


/*
 * ================= CL_ParseTEnt =================
 */
 
static byte	
splash_color[] = { 
	0x00, 
	0xe0, 
	0xb0, 
	0x50, 
	0xd0, 
	0xe0, 
	0xe8
};

#ifdef QMAX
void
CL_ParseTEnt(void)
{
	int		type;
	vec3_t		pos    , pos2, dir;
	explosion_t    *ex;
	int		cnt;
	int		color;
	int		r;
	int		ent;
	int		magnitude;
	vec3_t		rgbcolor;
	int 		red, green, blue;

	type = MSG_ReadByte(&net_message);
	
	if (cl.refdef.rdflags & RDF_UNDERWATER)
		S_StartSound(pos, 0, 1, cl_sfx_underwater, 0.9, ATTN_NORM, 0);

	switch (type) {
	case TE_BLOOD:		/* bullet hitting flesh */
		MSG_ReadPos(&net_message, pos);
		MSG_ReadDir(&net_message, dir);
		CL_BloodHit(pos, dir);
		rgbcolor[0] = 0.97f;
		rgbcolor[1] = rgbcolor[2] = 0.51f;
		V_AddStain(pos, rgbcolor, 30);
		break;

	case TE_GUNSHOT:	/* bullet hitting wall */
	case TE_SPARKS:
	case TE_BULLET_SPARKS:
		MSG_ReadPos(&net_message, pos);
		MSG_ReadDir(&net_message, dir);
		rgbcolor[0] = rgbcolor[1] = rgbcolor[2] = 0.89f;
		V_AddStain(pos, rgbcolor, 5);
		if (type == TE_BULLET_SPARKS)
			VectorScale(dir, 2, dir);

		{
			vec3_t		color = {255, 125, 10};

			CL_ParticleEffectSparks(pos, dir, color, (type == TE_GUNSHOT) ? 5 : 10);
		}

		if (type != TE_SPARKS) {
			if (cl_gunsmoke->value) {
				CL_GunSmokeEffect(pos, dir, 0.5);
			}
			re.AddDecal(pos, dir, 0, 0, 0, 1.0, 2 + ((rand() % 21 * 0.05) - 0.5), 1, 0, rand() % 361);
			/* impact sound */
			cnt = rand() & 15;
			if (cnt == 1)
				S_StartSound(pos, 0, 0, cl_sfx_ric1, 1, ATTN_NORM, 0);
			else if (cnt == 2)
				S_StartSound(pos, 0, 0, cl_sfx_ric2, 1, ATTN_NORM, 0);
			else if (cnt == 3)
				S_StartSound(pos, 0, 0, cl_sfx_ric3, 1, ATTN_NORM, 0);
		}
		break;
	case TE_SCREEN_SPARKS:
	case TE_SHIELD_SPARKS:
		MSG_ReadPos(&net_message, pos);
		MSG_ReadDir(&net_message, dir);
		if (type == TE_SCREEN_SPARKS)
			CL_ParticleEffect(pos, dir, 0xd0, 40);
		else
			CL_ParticleEffect(pos, dir, 0xb0, 40);
		/* FIXME : replace or remove this sound */
		S_StartSound(pos, 0, 0, cl_sfx_lashit, 1, ATTN_NORM, 0);
		break;

	case TE_SHOTGUN:	/* bullet hitting wall */
		MSG_ReadPos(&net_message, pos);
		MSG_ReadDir(&net_message, dir);
		rgbcolor[0] = rgbcolor[1] = rgbcolor[2] = 0.89f;
		V_AddStain(pos, rgbcolor, 5);
		if (cl_gunsmoke->value) {
			CL_GunSmokeEffect(pos, dir, 0.5);
		} {
			vec3_t		color = {200, 100, 10};

			CL_ParticleEffectSparks(pos, dir, color, 8);
		}
		re.AddDecal(pos, dir, 0, 0, 0, 1.0, 2 + ((rand() % 21 * 0.05) - 0.5), 1, 0, rand() % 361);
		break;

	case TE_SPLASH:	/* bullet hitting water */
		cnt = MSG_ReadByte(&net_message);
		MSG_ReadPos(&net_message, pos);
		MSG_ReadDir(&net_message, dir);
		r = MSG_ReadByte(&net_message);
		if (r > 6)
			color = 0x00;
		else
			color = splash_color[r];
		if (r == SPLASH_SPARKS) {
			CL_ParticleEffectSplash(pos, dir, color, cnt);

			r = rand() & 3;
			if (r == 0)
				S_StartSound(pos, 0, 0, cl_sfx_spark5, 1, ATTN_STATIC, 0);
			else if (r == 1)
				S_StartSound(pos, 0, 0, cl_sfx_spark6, 1, ATTN_STATIC, 0);
			else
				S_StartSound(pos, 0, 0, cl_sfx_spark7, 1, ATTN_STATIC, 0);
		} else
			CL_ParticleWaterEffectSplash(pos, dir, color, cnt, r);
		break;

	case TE_LASER_SPARKS:
		cnt = MSG_ReadByte(&net_message);
		MSG_ReadPos(&net_message, pos);
		MSG_ReadDir(&net_message, dir);
		color = MSG_ReadByte(&net_message);
		CL_ParticleEffect2(pos, dir, color, cnt);
		break;
	case TE_BLASTER:	/* blaster hitting wall	 */
		MSG_ReadPos(&net_message, pos);
		MSG_ReadDir(&net_message, dir);

		/*
		 * rgbcolor[0] = rgbcolor[1] = 1.0f; rgbcolor[2] = 0.0f;
		 * V_AddStain (pos, rgbcolor, 10);
		 */
		if (cl_blaster_color->value == 1) {
			/* give the bolt a red particle glow */
			CL_BlasterParticlesColor(pos, dir, 40, 235, 50, 50, 0, -90, -30);
		} else if (cl_blaster_color->value == 2) {
			/* give the bolt a green particle glow */
			CL_BlasterParticlesColor(pos, dir, 40, 50, 235, 50, 0, -90, -30);
		} else if (cl_blaster_color->value == 3) {
			/* give the bolt a blue particle glow */
			CL_BlasterParticlesColor(pos, dir, 40, 50, 50, 235, 0, -90, -30);
		} else
			CL_BlasterParticles(pos, dir, 40);


		rgbcolor[0] = rgbcolor[1] = 1.1f;
		rgbcolor[2] = 0.0f;
		V_AddStain(pos, rgbcolor, 10);
		
		re.AddDecal(pos, dir, 0, 0, 0, 1.0, 2 + ((rand() % 21 * 0.05) - 0.5), 1, 0, rand() % 361);

		for (r = 0; r < 3; r++) {
			dir[r] *= 10;
			pos[r] += dir[r];
		}
		S_StartSound(pos, 0, 0, cl_sfx_lashit, 1, ATTN_NORM, 0);
		break;
	case TE_BLUEHYPERBLASTER:
		MSG_ReadPos(&net_message, pos);
		MSG_ReadPos(&net_message, dir);

		/*
		 * rgbcolor[0] = 0.5f; rgbcolor[1] = rgbcolor[2] = 1.0f;
		 * V_AddStain (pos, rgbcolor, 10);
		 */
		CL_BlasterParticles(pos, dir, 40);
		break;
	case TE_RAILTRAIL:	/* railgun effect */
		MSG_ReadPos(&net_message, pos);
		MSG_ReadPos(&net_message, pos2);
		rgbcolor[0] = 0.5f;
		rgbcolor[1] = rgbcolor[2] = 1.0f;
		V_AddStain(pos, rgbcolor, 5);
		CL_RailTrail(pos, pos2);
		S_StartSound(pos2, 0, 0, cl_sfx_railg, 1, ATTN_NORM, 0);
		break;

	case TE_EXPLOSION2:
	case TE_GRENADE_EXPLOSION:
	case TE_GRENADE_EXPLOSION_WATER:
		MSG_ReadPos(&net_message, pos);
		rgbcolor[0] = rgbcolor[1] = rgbcolor[2] = 0.8f;
		V_AddStain(pos, rgbcolor, 35);
		if (cl_explosion->value == 2) {
			CL_G_Explode_SP(pos);
			CL_ExplosionParticles(pos, 1.25, type == TE_GRENADE_EXPLOSION_WATER);
		} else if (cl_explosion->value == 1) {
			CL_G_Explode_SP(pos);
			CL_ExplosionParticles_Old(pos);
		} else {
			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->ent.scale = cl_explosion_scale->value;
			ex->frames = 19;
			ex->baseframe = 30;
			ex->ent.angles[1] = rand() % 360;
		}
		if (type == TE_GRENADE_EXPLOSION_WATER)
			S_StartSound(pos, 0, 0, cl_sfx_watrexp, 1, ATTN_NORM, 0);
		else
			S_StartSound(pos, 0, 0, cl_sfx_grenexp, 1, ATTN_NORM, 0);
		break;

		/* RAFAEL */
	case TE_PLASMA_EXPLOSION:
		MSG_ReadPos(&net_message, pos);
		if (cl_explosion->value == 2) {
			CL_R_Explode_SP(pos);
			CL_ExplosionParticles(pos, 1, 0);
			CL_Explosion_Sparks (pos, 32);
		} else if (cl_explosion->value == 1) {
			CL_R_Explode_SP(pos);
		} else {
			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;
			ex->ent.scale = cl_explosion_scale->value;
			if (frand() < 0.5)
				ex->baseframe = 15;
			ex->frames = 15;
		}
		S_StartSound(pos, 0, 0, cl_sfx_rockexp, 1, ATTN_NORM, 0);
		break;

	case TE_EXPLOSION1_BIG:
		/* PMM */
		MSG_ReadPos(&net_message, pos);

		if (cl_explosion->value == 2) {
			CL_Explosion_Particle(pos, 100, true, true);
			CL_ExplosionParticles(pos, 3, 0);
			CL_Explosion_Sparks(pos, 96);
			{
				vec3_t		color = {200, 100, 10};

				CL_ParticleEffectSparks(pos, dir, color, 8);
			}
		} else if (cl_explosion->value == 1) {
			CL_Explosion_Particle(pos, 100, true, true);
			CL_ExplosionParticles_Old(pos);
		} else {
			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.scale = cl_explosion_scale->value;
			ex->ent.model = cl_mod_explo4_big;
			if (frand() < 0.5)
				ex->baseframe = 15;
			ex->frames = 15;

			CL_ExplosionParticles(pos, 3, 0);
		}
		S_StartSound(pos, 0, 0, cl_sfx_rockexp, 1, ATTN_NORM, 0);
		break;
	case TE_EXPLOSION1_NP:	/* PMM */
		MSG_ReadPos(&net_message, pos);
		rgbcolor[0] = rgbcolor[1] = rgbcolor[2] = 0.8f;
		V_AddStain(pos, rgbcolor, 35);
		if (cl_explosion->value == 2) {
			CL_Explosion_Particle(pos, 50, true, true);
			CL_ExplosionParticles(pos, 0.6666666, 0);
			CL_Explosion_Sparks(pos, 21);
		} else if (cl_explosion->value == 1) {
			CL_Explosion_Particle(pos, 50, true, true);
			CL_ExplosionParticles_Old(pos);
		} else {
			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;
			ex->ent.scale = cl_explosion_scale->value;
			if (frand() < 0.5)
				ex->baseframe = 15;
			ex->frames = 15;

		}
		S_StartSound(pos, 0, 0, cl_sfx_grenexp, 1, ATTN_NORM, 0);
		break;
	case TE_EXPLOSION1:
	case TE_ROCKET_EXPLOSION:
	case TE_ROCKET_EXPLOSION_WATER:
		MSG_ReadPos(&net_message, pos);
		rgbcolor[0] = rgbcolor[1] = rgbcolor[2] = 0.5f;
		V_AddStain(pos, rgbcolor, 40);
		if (cl_explosion->value == 2) {
			CL_R_Explode_SP(pos);
			CL_ExplosionParticles(pos, 1, type == TE_ROCKET_EXPLOSION_WATER);
			CL_Explosion_Sparks(pos, 32);	/* PMM */
		} else if (cl_explosion->value == 1) {
			CL_R_Explode_SP(pos);
			CL_ExplosionParticles_Old(pos);
		} else {
			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;
			ex->ent.scale = cl_explosion_scale->value;
			if (frand() < 0.5)
				ex->baseframe = 15;
			ex->frames = 15;

		}
		if (type == TE_ROCKET_EXPLOSION_WATER)
			S_StartSound(pos, 0, 0, cl_sfx_watrexp, 1, ATTN_NORM, 0);
		else
			S_StartSound(pos, 0, 0, cl_sfx_rockexp, 1, ATTN_NORM, 0);
		break;

	case TE_BFG_EXPLOSION:
		MSG_ReadPos(&net_message, pos);
#if 0
		ex = CL_AllocExplosion();
		VectorCopy(pos, ex->ent.origin);
		ex->type = ex_poly;
		ex->ent.flags = RF_FULLBRIGHT | RF_NOSHADOW;
		ex->start = cl.frame.servertime - 100;
		ex->light = 350;
		ex->lightcolor[0] = 0.0;
		ex->lightcolor[1] = 1.0;
		ex->lightcolor[2] = 0.0;
		ex->ent.model = cl_mod_bfg_explo;
		ex->ent.flags |= RF_TRANSLUCENT | RF_TRANS_ADDITIVE;
		ex->ent.alpha = 0.30;
		ex->frames = 4;
#else
		CL_BFGExplosionParticles_2(pos);
#endif
		break;

	case TE_BFG_BIGEXPLOSION:
		MSG_ReadPos(&net_message, pos);
		CL_BFGExplosionParticles(pos);
		break;

	case TE_BFG_LASER:
		CL_ParseLaser(0xd0d1d2d3);
		break;

	case TE_BUBBLETRAIL:
		MSG_ReadPos(&net_message, pos);
		MSG_ReadPos(&net_message, pos2);
		CL_BubbleTrail(pos, pos2);
		break;

	case TE_PARASITE_ATTACK:
	case TE_MEDIC_CABLE_ATTACK:
		ent = CL_ParseBeam(cl_mod_parasite_segment);
		break;

	case TE_BOSSTPORT:	/* boss teleporting to station */
		MSG_ReadPos(&net_message, pos);
		CL_BigTeleportParticles(pos);
		S_StartSound(pos, 0, 0, S_RegisterSound("misc/bigtele.wav"), 1, ATTN_NONE, 0);
		break;

	case TE_GRAPPLE_CABLE:
		ent = CL_ParseBeam2(cl_mod_grapple_cable);
		break;

		/* RAFAEL */
	case TE_WELDING_SPARKS:
		cnt = MSG_ReadByte(&net_message);
		MSG_ReadPos(&net_message, pos);
		MSG_ReadDir(&net_message, dir);
		color = MSG_ReadByte(&net_message);
		{
			vec3_t		sparkcolor = {color8red(color), color8green(color), color8blue(color)};

			CL_ParticleEffectSparks(pos, dir, sparkcolor, 40);
		}
		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 TE_GREENBLOOD:
		MSG_ReadPos(&net_message, pos);
		MSG_ReadDir(&net_message, dir);
		rgbcolor[0] = rgbcolor[2] = 0.87f;
		rgbcolor[1] = 0.90f;
		V_AddStain(pos, rgbcolor, 30);
		CL_GreenBloodHit(pos, dir);
		break;

		/* RAFAEL */
	case TE_TUNNEL_SPARKS:
		cnt = MSG_ReadByte(&net_message);
		MSG_ReadPos(&net_message, pos);
		MSG_ReadDir(&net_message, dir);
		color = MSG_ReadByte(&net_message);
		CL_ParticleEffect3(pos, dir, color, cnt);
		break;

		/* ============= */
		/* PGM */

		/*
		 * PMM -following code integrated for flechette (different
		 * color)
		 */
	case TE_BLASTER2:	/* green blaster hitting wall */
	case TE_FLECHETTE:	/* flechette */
		MSG_ReadPos(&net_message, pos);
		MSG_ReadDir(&net_message, dir);
		/* PMM */
#if 1
			
		if (type == TE_BLASTER2) {
			CL_BlasterParticlesColor (pos, dir, 40, 50, 235, 50, -10, 0, -10);
			red=50; 
			green=235; 
			blue=50; 
		}
		else if (type == TE_BLUEHYPERBLASTER) {
			CL_BlasterParticlesColor (pos, dir, 40, 50, 50, 235, -10, 0, -10);
			red=50; 
			green=50; 
			blue=235; 
		}
		else {
 			CL_BlasterParticlesColor (pos, dir, 40, 100, 100, 195, -10, 0, -10);
			red=100; 
			green=100; 
			blue=195; 
		}
#else
		CL_BlasterParticlesColor(pos, dir, 40, 50, 50, 235, 0, -90, -30);
#endif
		re.AddDecal(pos, dir, 0, 0, 0, 1.0, 2 + ((rand() % 21 * 0.05) - 0.5), 1, 0, rand() % 361);
		S_StartSound(pos, 0, 0, cl_sfx_lashit, 1, ATTN_NORM, 0);
		break;
	case TE_LIGHTNING:
		ent = CL_ParseLightning(10);
		S_StartSound(NULL, ent, CHAN_WEAPON, cl_sfx_lightning, 1, ATTN_NORM, 0);
		break;

	case TE_LIGHTNINGSIZED:
		ent = CL_ParseLightning(MSG_ReadFloat(&net_message));
		S_StartSound(NULL, ent, CHAN_WEAPON, cl_sfx_lightning, 1, ATTN_NORM, 0);
		break;
	case TE_DEBUGTRAIL:
		MSG_ReadPos(&net_message, pos);
		MSG_ReadPos(&net_message, pos2);
		CL_DebugTrail(pos, pos2);
		break;

	case TE_PLAIN_EXPLOSION:
		MSG_ReadPos(&net_message, pos);
		rgbcolor[0] = rgbcolor[1] = rgbcolor[2] = 0.8f;
		V_AddStain(pos, rgbcolor, 35);
		if (cl_explosion->value == 2) {
			CL_Explosion_Particle(pos, 50, true, true);
			CL_ExplosionParticles(pos, 0.6666666, 0);
			CL_Explosion_Sparks(pos, 21);
		} else if (cl_explosion->value == 1) {
			CL_Explosion_Particle(pos, 50, true, true);
			CL_ExplosionParticles_Old(pos);
		} else {
			ex = CL_AllocExplosion();
			VectorCopy(pos, ex->ent.origin);
			ex->type = ex_poly;
			ex->ent.flags = RF_FULLBRIGHT | RF_NOSHADOW;
			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;
		}
		if (type == TE_ROCKET_EXPLOSION_WATER)
			S_StartSound(pos, 0, 0, cl_sfx_watrexp, 1, ATTN_NORM, 0);
		else
			S_StartSound(pos, 0, 0, cl_sfx_rockexp, 1, ATTN_NORM, 0);
		break;

	case TE_FLASHLIGHT:
		MSG_ReadPos(&net_message, pos);
		ent = MSG_ReadShort(&net_message);
		CL_Flashlight(ent, pos);
		break;

	case TE_FORCEWALL:
		MSG_ReadPos(&net_message, pos);
		MSG_ReadPos(&net_message, pos2);
		color = MSG_ReadByte(&net_message);
		CL_ForceWall(pos, pos2, color);
		break;

	case TE_HEATBEAM:
		ent = CL_ParsePlayerBeam(cl_mod_heatbeam);
		break;

	case TE_MONSTER_HEATBEAM:
		ent = CL_ParsePlayerBeam(cl_mod_monster_heatbeam);
		break;

	case TE_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, 240, 240, 240, -20, -20, -20, cnt, magnitude);
		S_StartSound(pos, 0, 0, cl_sfx_lashit, 1, ATTN_NORM, 0);
		break;

	case TE_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, 255, 150, 50, 0, -90, -30, cnt, magnitude);
		S_StartSound(pos, 0, 0, cl_sfx_lashit, 1, ATTN_NORM, 0);
		break;

	case TE_STEAM:
		CL_ParseSteam();
		break;

	case TE_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 TE_MOREBLOOD:
		MSG_ReadPos(&net_message, pos);
		MSG_ReadDir(&net_message, dir);
		rgbcolor[0] = 1.0f;
		rgbcolor[1] = rgbcolor[2] = 0.0f;
		V_AddStain(pos, rgbcolor, 30);
		CL_BloodHit(pos, dir);
		CL_BloodHit(pos, dir);
		break;

	case TE_CHAINFIST_SMOKE:
		dir[0] = 0;
		dir[1] = 0;
		dir[2] = 1;
		MSG_ReadPos(&net_message, pos);
		CL_ParticleSmokeEffect(pos, dir, 25, 1);
		break;

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

	case TE_TRACKER_EXPLOSION:
		MSG_ReadPos(&net_message, pos);
		rgbcolor[0] = rgbcolor[1] = rgbcolor[2] = 0.85f;
		V_AddStain(pos, rgbcolor, 25);
		CL_ColorFlash(pos, 0, 150, -1, -1, -1);
		CL_Tracker_Explode(pos);
		S_StartSound(pos, 0, 0, cl_sfx_disrexp, 1, ATTN_NORM, 0);
		break;

	case TE_TELEPORT_EFFECT:
	case TE_DBALL_GOAL:
		MSG_ReadPos(&net_message, pos);
		CL_TeleportParticles(pos);
		break;

	case TE_WIDOWBEAMOUT:
		CL_ParseWidow();
		break;

	case TE_NUKEBLAST:
		CL_ParseNuke();
		break;

	case TE_WIDOWSPLASH:
		MSG_ReadPos(&net_message, pos);
		CL_WidowSplash(pos);
		break;
		/* NEW CUSTOM TEMP EVENTS */

	case TE_LIGHTNINGFLARE:/*-psychospaz */
		{
			int		num       , entity;

			entity = MSG_ReadShort(&net_message);
			num = MSG_ReadShort(&net_message);
			MSG_ReadPos(&net_message, pos);

			/* double up for opacity endurance */
			CL_LightningFlare(pos, entity, 2 * num);
			CL_LightningFlare(pos, entity, 2 * num + 1);
		}
		break;
	case TE_SMOKEPUFF:	/* hahaha server declared stains biotch - psychospaz */
		{
			float		size;

			MSG_ReadPos(&net_message, pos);
			MSG_ReadPos(&net_message, dir);
			size = MSG_ReadFloat(&net_message);
			CL_ParticleSmokeEffect(pos, dir, 25, 1);
		}
		break;
	case TE_STAIN:		/* hahaha server declared stains biotch - psychospaz */
		{
			float		intens;
			int		i         , color[3];

			MSG_ReadPos(&net_message, pos);
			intens = MSG_ReadFloat(&net_message);
			for (i = 0; i < 3; i++)
				color[i] = -MSG_ReadByte(&net_message);

			/*
			 * re.AddStain(pos,	intens, color[0], color[1],
			 * color[2]);
			 */
		}
		break;
	case TE_FOOTPRINT:
		{
			float		size;
			vec3_t		color  , angle;

			MSG_ReadPos(&net_message, pos);
			MSG_ReadPos(&net_message, dir);
			MSG_ReadPos(&net_message, color);
			size = MSG_ReadFloat(&net_message);

			angle[0] = anglemod(dir[0] + 180);
			angle[1] = anglemod(dir[1]);
			angle[2] = anglemod(dir[2]);

			CL_ParticleFootPrint(pos, angle, size, color);
		}
		break;
	case TE_FLAMEBURST:
		{
			float		size;

			MSG_ReadPos(&net_message, pos);
			size = MSG_ReadFloat(&net_message);

			CL_FlameBurst(pos, size);
		}
		break;
	case TE_LASERSTUN:
		{
			int		i;
			float		size;
			vec3_t		color;

			MSG_ReadPos(&net_message, pos);
			MSG_ReadPos(&net_message, dir);
			size = MSG_ReadFloat(&net_message);
			for (i = 0; i < 3; i++)
				color[i] = (float)MSG_ReadByte(&net_message);

			CL_LaserStun(pos, dir, color, size);
		}
		break;
	case TE_STUNBLAST:
		{
			int		i;
			float		size;
			vec3_t		color;

			MSG_ReadPos(&net_message, pos);
			size = MSG_ReadFloat(&net_message);
			for (i = 0; i < 3; i++)
				color[i] = (float)MSG_ReadByte(&net_message);

			CL_StunBlast(pos, color, size);
		}
		break;
	case TE_DISRUPTOR_EXPLOSION:
		{
			float		size;

			MSG_ReadPos(&net_message, pos);
			size = MSG_ReadFloat(&net_message);

			CL_Disruptor_Explosion_Particle(pos, size);

			S_StartSound(pos, 0, 0, cl_sfx_rockexp, 1, ATTN_NORM, 0);
		}
		break;
	case TE_DISINTEGRATE:
		{
			ent = MSG_ReadByte(&net_message);
			MSG_ReadPos(&net_message, pos);
			CL_Disintegrate(pos, ent);
		}
		break;
	case TE_LENSFLARE:
		{
			float		size    , time;

			MSG_ReadPos(&net_message, pos);
			MSG_ReadPos(&net_message, dir);
			size = MSG_ReadFloat(&net_message);
			time = MSG_ReadFloat(&net_message);

			CL_LensFlare(pos, dir, size, time);
		}
		break;
	case TE_WEATHERFX:
		{
			vec3_t		color;
			int		type;
			float		size    , time;

			MSG_ReadPos(&net_message, pos);
			MSG_ReadPos(&net_message, dir);
			MSG_ReadPos(&net_message, color);
			type = MSG_ReadByte(&net_message);
			size = MSG_ReadFloat(&net_message);
			time = MSG_ReadFloat(&net_message);

			CL_WeatherFx(pos, dir, color, type, size, time);
		}
		break;
		/* PGM */
		/* ============== */

	default:
		Com_Error(ERR_DROP, "CL_ParseTEnt: bad type");
	}
}

#else
void
CL_ParseTEnt(void)
{
	int		type;
	vec3_t		pos    , pos2, dir;
	explosion_t    *ex;
	int		cnt;
	int		color;
	int		r;
	int		ent;
	int		magnitude;
	vec3_t		rgbcolor;


	type = MSG_ReadByte(&net_message);
	
	if (cl.refdef.rdflags & RDF_UNDERWATER)
		S_StartSound(pos, 0, 1, cl_sfx_underwater, 0.9, ATTN_NORM, 0);

	switch (type) {
	case TE_BLOOD:		/* bullet hitting flesh */
		MSG_ReadPos(&net_message, pos);
		MSG_ReadDir(&net_message, dir);
		CL_ParticleEffect(pos, dir, 0xe8, 60);
		rgbcolor[0] = 0.97f;
		rgbcolor[1] = rgbcolor[2] = 0.51f;
		V_AddStain(pos, rgbcolor, 30);
		break;

	case TE_GUNSHOT:	/* bullet hitting wall */
	case TE_SPARKS:
	case TE_BULLET_SPARKS:
		MSG_ReadPos(&net_message, pos);
		MSG_ReadDir(&net_message, dir);
		rgbcolor[0] = rgbcolor[1] = rgbcolor[2] = 0.89f;
		V_AddStain(pos, rgbcolor, 5);

		if (type == TE_GUNSHOT)
			CL_ParticleEffect(pos, dir, 0, 40);
		else
			CL_ParticleEffect(pos, dir, 0xe0, 6);
		if (type != TE_SPARKS) {

			re.AddDecal(pos, dir, 0, 0, 0, 1.0, 2 + ((rand() % 21 * 0.05) - 0.5), 1, 0, rand() % 361);
			CL_SmokeAndFlash(pos);

			/* impact sound */
			cnt = rand() & 15;
			if (cnt == 1)
				S_StartSound(pos, 0, 0, cl_sfx_ric1, 1, ATTN_NORM, 0);
			else if (cnt == 2)
				S_StartSound(pos, 0, 0, cl_sfx_ric2, 1, ATTN_NORM, 0);
			else if (cnt == 3)
				S_StartSound(pos, 0, 0, cl_sfx_ric3, 1, ATTN_NORM, 0);
		}
		break;
	case TE_SCREEN_SPARKS:
	case TE_SHIELD_SPARKS:
		MSG_ReadPos(&net_message, pos);
		MSG_ReadDir(&net_message, dir);
		if (type == TE_SCREEN_SPARKS)
			CL_ParticleEffect(pos, dir, 0xd0, 40);
		else
			CL_ParticleEffect(pos, dir, 0xb0, 40);
		/* FIXME : replace or remove this sound */
		S_StartSound(pos, 0, 0, cl_sfx_lashit, 1, ATTN_NORM, 0);
		break;


	case TE_SHOTGUN:	/* bullet hitting wall */
		MSG_ReadPos(&net_message, pos);
		MSG_ReadDir(&net_message, dir);

		rgbcolor[0] = rgbcolor[1] = rgbcolor[2] = 0.89f;
		V_AddStain(pos, rgbcolor, 5);
		re.AddDecal(pos, dir, 0, 0, 0, 1.0, 2 + ((rand() % 21 * 0.05) - 0.5), 1, 0, rand() % 361);
		CL_ParticleEffect(pos, dir, 0, 20);
		CL_SmokeAndFlash(pos);
		break;

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

		if (r == SPLASH_SPARKS) {
			r = rand() & 3;
			if (r == 0)
				S_StartSound(pos, 0, 0, cl_sfx_spark5, 1, ATTN_STATIC, 0);
			else if (r == 1)
				S_StartSound(pos, 0, 0, cl_sfx_spark6, 1, ATTN_STATIC, 0);
			else
				S_StartSound(pos, 0, 0, cl_sfx_spark7, 1, ATTN_STATIC, 0);
		}
		break;

	case TE_LASER_SPARKS:
		cnt = MSG_ReadByte(&net_message);
		MSG_ReadPos(&net_message, pos);
		MSG_ReadDir(&net_message, dir);
		color = MSG_ReadByte(&net_message);
		CL_ParticleEffect2(pos, dir, color, cnt);
		break;

		/* RAFAEL */
	case TE_BLUEHYPERBLASTER:
		MSG_ReadPos(&net_message, pos);
		MSG_ReadPos(&net_message, dir);
		rgbcolor[0] = 0.5f;
		rgbcolor[1] = rgbcolor[2] = 1.0f;
		V_AddStain(pos, rgbcolor, 10);
		CL_BlasterParticles(pos, dir);
		break;

	case TE_BLASTER:	/* blaster hitting wall */
		MSG_ReadPos(&net_message, pos);
		MSG_ReadDir(&net_message, dir);
		rgbcolor[0] = rgbcolor[1] = 1.1f;
		rgbcolor[2] = 0.0f;
		V_AddStain(pos, rgbcolor, 10);
		re.AddDecal(pos, dir, 0, 0, 0, 1.0, 2 + ((rand() % 21 * 0.05) - 0.5), 1, 0, rand() % 361);
		CL_BlasterParticles(pos, dir);

		ex = CL_AllocExplosion();
		VectorCopy(pos, ex->ent.origin);
		ex->ent.angles[0] = acos(dir[2]) / M_PI * 180;
		/* PMM - fixed to correct for pitch of 0 */
		if (dir[0])
			ex->ent.angles[1] = atan2(dir[1], dir[0]) / M_PI * 180;
		else if (dir[1] > 0)
			ex->ent.angles[1] = 90;
		else if (dir[1] < 0)
			ex->ent.angles[1] = 270;
		else
			ex->ent.angles[1] = 0;

		ex->type = ex_misc;
		ex->ent.flags = RF_FULLBRIGHT | RF_TRANSLUCENT | RF_NOSHADOW;
		ex->start = cl.frame.servertime - 100;
		ex->light = 150;
		ex->lightcolor[0] = 1;
		ex->lightcolor[1] = 1;
		ex->ent.model = cl_mod_explode;
		ex->frames = 4;
		S_StartSound(pos, 0, 0, cl_sfx_lashit, 1, ATTN_NORM, 0);
		break;

	case TE_RAILTRAIL:	/* railgun effect */
		/* NiceAss: */
		if (cl_railtrail->value == 0 ) {
			MSG_ReadPos(&net_message, pos);
			MSG_ReadPos(&net_message, pos2);
			rgbcolor[0] = 0.5f;
			rgbcolor[1] = rgbcolor[2] = 1.0f;
			V_AddStain(pos, rgbcolor, 5);
			CL_RailTrail(pos, pos2);
		} else {
			int		color;

			if (cl_railtrail_color->value == 0)
				color = 0xe0e1e2e3; 			/* orange */
			else if (cl_railtrail_color->value == 1)
				color = 0xf2f2f0f0; 			/* red */
			else if (cl_railtrail_color->value == 2)
				color = 0xd0d1d2d3; 			/* green */
			else if (cl_railtrail_color->value == 3)
				color = 0xf3f3f1f1; 			/* blue */
			else
				color = 0xe0e1e2e3; 			/* orange */

			CL_ParseLaser(color);
		}
		S_StartSound(pos2, 0, 0, cl_sfx_railg, 1, ATTN_NORM, 0);
		break;

	case TE_EXPLOSION2:
	case TE_GRENADE_EXPLOSION:
	case TE_GRENADE_EXPLOSION_WATER:
		MSG_ReadPos(&net_message, pos);
		rgbcolor[0] = rgbcolor[1] = rgbcolor[2] = 0.8f;
		V_AddStain(pos, rgbcolor, 35);
		ex = CL_AllocExplosion();
		VectorCopy(pos, ex->ent.origin);
		ex->type = ex_poly;
		ex->ent.flags = RF_FULLBRIGHT | RF_NOSHADOW;
		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;
		EXPLOSION_PARTICLES(pos);
		if (type == TE_GRENADE_EXPLOSION_WATER)
			S_StartSound(pos, 0, 0, cl_sfx_watrexp, 1, ATTN_NORM, 0);
		else
			S_StartSound(pos, 0, 0, cl_sfx_grenexp, 1, ATTN_NORM, 0);
		break;

		/* RAFAEL */
	case TE_PLASMA_EXPLOSION:
		MSG_ReadPos(&net_message, pos);
		ex = CL_AllocExplosion();
		VectorCopy(pos, ex->ent.origin);
		ex->type = ex_poly;
		ex->ent.flags = RF_FULLBRIGHT | RF_NOSHADOW;
		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;
		EXPLOSION_PARTICLES(pos);
		S_StartSound(pos, 0, 0, cl_sfx_rockexp, 1, ATTN_NORM, 0);
		break;

	case TE_EXPLOSION1_BIG:
	case TE_EXPLOSION1_NP:	/* PMM */
		MSG_ReadPos(&net_message, pos);

		rgbcolor[0] = rgbcolor[1] = rgbcolor[2] = 0.8f;
		V_AddStain(pos, rgbcolor, 35);


		S_StartSound(pos, 0, 0, cl_sfx_grenexp, 1, ATTN_NORM, 0);
		break;
	case TE_EXPLOSION1:
	case TE_ROCKET_EXPLOSION:
	case TE_ROCKET_EXPLOSION_WATER:
		MSG_ReadPos(&net_message, pos);
		rgbcolor[0] = rgbcolor[1] = rgbcolor[2] = 0.5f;
		V_AddStain(pos, rgbcolor, 40);
		ex = CL_AllocExplosion();
		VectorCopy(pos, ex->ent.origin);
		ex->type = ex_poly;
		ex->ent.flags = RF_FULLBRIGHT | RF_NOSHADOW;
		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 */
			EXPLOSION_PARTICLES(pos);	/* PMM */
		if (type == TE_ROCKET_EXPLOSION_WATER)
			S_StartSound(pos, 0, 0, cl_sfx_watrexp, 1, ATTN_NORM, 0);
		else
			S_StartSound(pos, 0, 0, cl_sfx_rockexp, 1, ATTN_NORM, 0);
		break;

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

	case TE_BFG_BIGEXPLOSION:
		MSG_ReadPos(&net_message, pos);
		CL_BFGExplosionParticles(pos);
		break;

	case TE_BFG_LASER:
		CL_ParseLaser(0xd0d1d2d3);
		break;

	case TE_BUBBLETRAIL:
		MSG_ReadPos(&net_message, pos);
		MSG_ReadPos(&net_message, pos2);
		CL_BubbleTrail(pos, pos2);
		break;

	case TE_PARASITE_ATTACK:
	case TE_MEDIC_CABLE_ATTACK:
		ent = CL_ParseBeam(cl_mod_parasite_segment);
		break;

	case TE_BOSSTPORT:	/* boss teleporting to station */
		MSG_ReadPos(&net_message, pos);
		CL_BigTeleportParticles(pos);
		S_StartSound(pos, 0, 0, S_RegisterSound("misc/bigtele.wav"), 1, ATTN_NONE, 0);
		break;

	case TE_GRAPPLE_CABLE:
		ent = CL_ParseBeam2(cl_mod_grapple_cable);
		break;

		/* RAFAEL */
	case TE_WELDING_SPARKS:
		cnt = MSG_ReadByte(&net_message);
		MSG_ReadPos(&net_message, pos);
		MSG_ReadDir(&net_message, dir);
		color = MSG_ReadByte(&net_message);
		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 TE_GREENBLOOD:
		MSG_ReadPos(&net_message, pos);
		MSG_ReadDir(&net_message, dir);
		rgbcolor[0] = rgbcolor[2] = 0.87f;
		rgbcolor[1] = 0.90f;
		V_AddStain(pos, rgbcolor, 30);
		CL_ParticleEffect2(pos, dir, 0xdf, 30);
		break;

		/* RAFAEL */
	case TE_TUNNEL_SPARKS:
		cnt = MSG_ReadByte(&net_message);
		MSG_ReadPos(&net_message, pos);
		MSG_ReadDir(&net_message, dir);
		color = MSG_ReadByte(&net_message);
		CL_ParticleEffect3(pos, dir, color, cnt);
		break;

		/* ============= */
		/* PGM */

		/*
		 * PMM -following code integrated for flechette (different
		 * color)
		 */
	case TE_BLASTER2:	/* green blaster hitting wall */
	case TE_FLECHETTE:	/* flechette */
		MSG_ReadPos(&net_message, pos);
		MSG_ReadDir(&net_message, dir);

		if (type == TE_BLASTER2) {
			rgbcolor[0] = 0.24f;
			rgbcolor[1] = 0.10f;
			rgbcolor[2] = 0.82f;
		} else {
			rgbcolor[0] = 0.19f;
			rgbcolor[1] = 0.41f;
			rgbcolor[2] = 0.75f;
		}

		re.AddDecal(pos, dir, 0, 0, 0, 1.0, 2 + ((rand() % 21 * 0.05) - 0.5), 1, 0, rand() % 361);
		V_AddStain(pos, rgbcolor, 10);

		/* PMM */
		if (type == TE_BLASTER2)
			CL_BlasterParticles2(pos, dir, 0xd0);
		else
			CL_BlasterParticles2(pos, dir, 0x6f);	/* 75 */

		ex = CL_AllocExplosion();
		VectorCopy(pos, ex->ent.origin);
		ex->ent.angles[0] = acos(dir[2]) / M_PI * 180;
		/* PMM - fixed to correct for pitch of 0 */
		if (dir[0])
			ex->ent.angles[1] = atan2(dir[1], dir[0]) / M_PI * 180;
		else if (dir[1] > 0)
			ex->ent.angles[1] = 90;
		else if (dir[1] < 0)
			ex->ent.angles[1] = 270;
		else
			ex->ent.angles[1] = 0;

		ex->type = ex_misc;
		ex->ent.flags = RF_FULLBRIGHT | RF_TRANSLUCENT | RF_NOSHADOW;

		/* PMM */
		if (type == TE_BLASTER2)
			ex->ent.skinnum = 1;
		else		/* flechette */
			ex->ent.skinnum = 2;

		ex->start = cl.frame.servertime - 100;
		ex->light = 150;
		/* PMM */
		if (type == TE_BLASTER2)
			ex->lightcolor[1] = 1;
		else {		/* flechette */
			ex->lightcolor[0] = 0.19;
			ex->lightcolor[1] = 0.41;
			ex->lightcolor[2] = 0.75;
		}
		ex->ent.model = cl_mod_explode;
		ex->frames = 4;
		S_StartSound(pos, 0, 0, cl_sfx_lashit, 1, ATTN_NORM, 0);
		break;

	case TE_LIGHTNING:
		ent = CL_ParseLightning(cl_mod_lightning);
		S_StartSound(NULL, ent, CHAN_WEAPON, cl_sfx_lightning, 1, ATTN_NORM, 0);
		break;
	case TE_DEBUGTRAIL:
		MSG_ReadPos(&net_message, pos);
		MSG_ReadPos(&net_message, pos2);
		CL_DebugTrail(pos, pos2);
		break;

	case TE_PLAIN_EXPLOSION:
		MSG_ReadPos(&net_message, pos);

		rgbcolor[0] = rgbcolor[1] = rgbcolor[2] = 0.8f;
		V_AddStain(pos, rgbcolor, 35);

		ex = CL_AllocExplosion();
		VectorCopy(pos, ex->ent.origin);
		ex->type = ex_poly;
		ex->ent.flags = RF_FULLBRIGHT | RF_NOSHADOW;
		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;
		if (type == TE_ROCKET_EXPLOSION_WATER)
			S_StartSound(pos, 0, 0, cl_sfx_watrexp, 1, ATTN_NORM, 0);
		else
			S_StartSound(pos, 0, 0, cl_sfx_rockexp, 1, ATTN_NORM, 0);
		break;

	case TE_FLASHLIGHT:
		MSG_ReadPos(&net_message, pos);
		ent = MSG_ReadShort(&net_message);
		CL_Flashlight(ent, pos);
		break;

	case TE_FORCEWALL:
		MSG_ReadPos(&net_message, pos);
		MSG_ReadPos(&net_message, pos2);
		color = MSG_ReadByte(&net_message);
		CL_ForceWall(pos, pos2, color);
		break;

	case TE_HEATBEAM:
		ent = CL_ParsePlayerBeam(cl_mod_heatbeam);
		break;

	case TE_MONSTER_HEATBEAM:
		ent = CL_ParsePlayerBeam(cl_mod_monster_heatbeam);
		break;

	case TE_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 TE_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 TE_STEAM:
		CL_ParseSteam();
		break;

	case TE_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 TE_MOREBLOOD:
		MSG_ReadPos(&net_message, pos);
		MSG_ReadDir(&net_message, dir);

		rgbcolor[0] = 1.0f;
		rgbcolor[1] = rgbcolor[2] = 0.0f;
		V_AddStain(pos, rgbcolor, 30);
		CL_ParticleEffect(pos, dir, 0xe8, 250);

		break;

	case TE_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 TE_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 TE_TRACKER_EXPLOSION:
		MSG_ReadPos(&net_message, pos);

		rgbcolor[0] = rgbcolor[1] = rgbcolor[2] = 0.85f;
		V_AddStain(pos, rgbcolor, 25);

		CL_ColorFlash(pos, 0, 150, -1, -1, -1);
		CL_ColorExplosionParticles(pos, 0, 1);
		/* CL_Tracker_Explode (pos); */

		S_StartSound(pos, 0, 0, cl_sfx_disrexp, 1, ATTN_NORM, 0);
		break;

	case TE_TELEPORT_EFFECT:
	case TE_DBALL_GOAL:
		MSG_ReadPos(&net_message, pos);
		CL_TeleportParticles(pos);
		break;

	case TE_WIDOWBEAMOUT:
		CL_ParseWidow();
		break;

	case TE_NUKEBLAST:
		CL_ParseNuke();
		break;

	case TE_WIDOWSPLASH:
		MSG_ReadPos(&net_message, pos);
		CL_WidowSplash(pos);
		break;
		/* PGM */
		/* ============== */

	default:
		Com_Error(ERR_DROP, "CL_ParseTEnt: bad type");
	}
}

#endif

/*
 * ================= CL_AddBeams =================
 */
extern cvar_t  *hand;

/* Knightmare- backup of client angles */
extern vec3_t	old_viewangles;
void
CL_AddBeams(void)
{
	int		i         , j;
	beam_t         *b;
	vec3_t		dist   , org;
	float		d;
	entity_t	ent;
	float		yaw     , pitch;
	float		forward;
	float		len     , steps;
	float		model_length;

	/* Knightmare added */
	frame_t        *oldframe;
	player_state_t *ps, *ops;
	qboolean	firstperson, chasecam;
	int		handmult;
	vec3_t		thirdp_grapple_offset;
	vec3_t		grapple_offset_dir;

	/* Knightmare- chasecam grapple offset stuff */
	if (hand) {
		if (hand->value == 2)
			handmult = 1;
		else if (hand->value == 1)
			handmult = -1;
		else
			handmult = 1;
	} else
		handmult = 1;
	VectorSet(thirdp_grapple_offset, 6, 16, 16);
	/* end Knightmare */

	/* update beams */
	for (i = 0, b = cl_beams; i < MAX_BEAMS; i++, b++) {
		if (!b->model || b->endtime < cl.time)
			continue;
		/* Knightmare- */
		firstperson = ((b->entity == cl.playernum + 1) && !cl_3dcam->value);
		chasecam = ((b->entity == cl.playernum + 1) && cl_3dcam->value);

		/* if coming from the player, update the start position */

		/*
		 * if (b->entity == cl.playernum+1)	// entity 0 is the
		 * world
		 */
		if (firstperson) {	/* entity 0 is the world */
			VectorCopy(cl.refdef.vieworg, b->start);
			b->start[2] -= 22;	/* adjust for view height */
		} else if (chasecam) {
			vec3_t		f      , r, u;

			ps = &cl.frame.playerstate;
			j = (cl.frame.serverframe - 1) & UPDATE_MASK;
			oldframe = &cl.frames[j];
			if (oldframe->serverframe != cl.frame.serverframe - 1 || !oldframe->valid)
				oldframe = &cl.frame;
			ops = &oldframe->playerstate;

			AngleVectors(old_viewangles, f, r, u);
			VectorClear(grapple_offset_dir);
			for (j = 0; j < 3; j++) {
				grapple_offset_dir[j] += f[j] * thirdp_grapple_offset[1];
				grapple_offset_dir[j] += r[j] * thirdp_grapple_offset[0] * handmult;
				grapple_offset_dir[j] += u[j] * (-thirdp_grapple_offset[2]);
			}

			for (j = 0; j < 3; j++)
				b->start[j] = cl.predicted_origin[j] + ops->viewoffset[j]
				    + cl.lerpfrac * (ps->viewoffset[j] - ops->viewoffset[j])
				    + grapple_offset_dir[j];

			/*
			 * Com_Printf("%f, %f, %f\n", grapple_offset_dir[0],
			 * grapple_offset_dir[1], grapple_offset_dir[2]);
			 */
		}
		if (chasecam)
			VectorCopy(b->start, org);
		else
			VectorAdd(b->start, b->offset, org);

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

		if (dist[1] == 0 && dist[0] == 0) {
			yaw = 0;
			if (dist[2] > 0)
				pitch = 90;
			else
				pitch = 270;
		} else {
			/* PMM - fixed to correct for pitch of 0 */
			if (dist[0])
				yaw = (atan2(dist[1], dist[0]) * 180 / M_PI);
			else if (dist[1] > 0)
				yaw = 90;
			else
				yaw = 270;
			if (yaw < 0)
				yaw += 360;

			forward = sqrt(dist[0] * dist[0] + dist[1] * dist[1]);
			pitch = (atan2(dist[2], forward) * -180.0 / M_PI);
			if (pitch < 0)
				pitch += 360.0;
		}

		/* add new entities for the beams */
		d = VectorNormalize(dist);

		memset(&ent, 0, sizeof(ent));
		if (b->model == cl_mod_lightning) {
			model_length = 35.0;
			d -= 20.0;	/* correction so it doesn't end in
					 * middle of tesla */
		} else {
			model_length = 30.0;
		}
		steps = ceil(d / model_length);
		len = (d - model_length) / (steps - 1);

		/*
		 * PMM - special case for lightning model .. if the real
		 * length is shorter than the model,
		 */

		/*
		 * flip it around & draw it from the end to the start.  This
		 * prevents the model from going
		 */

		/*
		 * through the tesla mine (instead it goes through the
		 * target)
		 */
		if ((b->model == cl_mod_lightning) && (d <= model_length)) {
			/* Com_Printf ("special case\n"); */
			VectorCopy(b->end, ent.origin);

			/*
			 * offset to push beam outside of tesla model
			 * (negative because dist is from end to start
			 */
			/* for this beam) */
			/* for (j=0 ; j<3 ; j++) */
			/* ent.origin[j] -= dist[j]*10.0; */
			ent.model = b->model;
			ent.flags = RF_FULLBRIGHT;
			ent.angles[0] = pitch;
			ent.angles[1] = yaw;
			ent.angles[2] = rand() % 360;
			V_AddEntity(&ent);
			return;
		}
		while (d > 0) {
			VectorCopy(org, ent.origin);
			ent.model = b->model;
			if (b->model == cl_mod_lightning) {
				ent.flags = RF_FULLBRIGHT;
				ent.angles[0] = -pitch;
				ent.angles[1] = yaw + 180.0;
				ent.angles[2] = rand() % 360;
			} else {
				ent.angles[0] = pitch;
				ent.angles[1] = yaw;
				ent.angles[2] = rand() % 360;
			}

			/*
			 * Com_Printf("B: %d -> %d\n", b->entity,
			 * b->dest_entity);
			 */
			ent.flags |= RF_NOSHADOW;
			V_AddEntity(&ent);

			for (j = 0; j < 3; j++)
				org[j] += dist[j] * len;
			d -= model_length;
		}
	}
}

/*
 * ================= ROGUE - draw player locked beams CL_AddPlayerBeams
 * =================
 */
void
CL_AddPlayerBeams(void)
{
	int		i         , j;
	beam_t         *b;
	vec3_t		dist   , org;
	float		d;
	entity_t	ent;
	float		yaw     , pitch;
	float		forward;
	float		len     , steps;
	int		framenum = 0;
	float		model_length;

	float		hand_multiplier;
	frame_t        *oldframe;
	player_state_t *ps, *ops;

	/* framenum = 0; */
	/* Knightmare-  */
	qboolean	firstperson, chasecam;
	int		newhandmult;
	vec3_t		thirdp_pbeam_offset;
	vec3_t		pbeam_offset_dir;

	/* end Knightmare */

	/* PMM */
	if (hand) {
		if (hand->value == 2)
			hand_multiplier = 0;
		else if (hand->value == 1)
			hand_multiplier = -1;
		else
			hand_multiplier = 1;
	} else {
		hand_multiplier = 1;
	}
	/* PMM */
	/* Knightmare- chasecam beam offset stuff */
	newhandmult = hand_multiplier;
	if (newhandmult == 0)
		newhandmult = 1;

	VectorSet(thirdp_pbeam_offset, 6.5, 0, 12);
	/* end Knightmare */

	/* update beams */
	for (i = 0, b = cl_playerbeams; i < MAX_BEAMS; i++, b++) {
		vec3_t		f      , r, u;

		if (!b->model || b->endtime < cl.time)
			continue;

		firstperson = ((b->entity == cl.playernum + 1) && !cl_3dcam->value);
		chasecam = ((b->entity == cl.playernum + 1) && cl_3dcam->value);

		if (cl_mod_heatbeam && (b->model == cl_mod_heatbeam)) {

			/*
			 * if coming from the player, update the start
			 * position
			 */

			/*
			 * if (b->entity == cl.playernum+1)	// entity 0
			 * is the world
			 */
			if (firstperson || chasecam) {
				/* set up gun position */
				/* code straight out of CL_AddViewWeapon */
				ps = &cl.frame.playerstate;
				j = (cl.frame.serverframe - 1) & UPDATE_MASK;
				oldframe = &cl.frames[j];
				if (oldframe->serverframe != cl.frame.serverframe - 1 || !oldframe->valid)
					oldframe = &cl.frame;	/* previous frame was
								 * dropped or involid */
				ops = &oldframe->playerstate;
				/* Knightmare- lerp for chasecam mode */
				if (chasecam) {	/* Knightmare- use player's
						 * original viewangles */
					AngleVectors(old_viewangles, f, r, u);
					VectorClear(pbeam_offset_dir);
					for (j = 0; j < 3; j++) {
						pbeam_offset_dir[j] += f[j] * thirdp_pbeam_offset[1];
						pbeam_offset_dir[j] += r[j] * thirdp_pbeam_offset[0] * newhandmult;
						pbeam_offset_dir[j] += u[j] * (-thirdp_pbeam_offset[2]);
					}
					for (j = 0; j < 3; j++)
						b->start[j] = cl.predicted_origin[j] + ops->viewoffset[j]
						    + cl.lerpfrac * (ps->viewoffset[j] - ops->viewoffset[j])
						    + pbeam_offset_dir[j];

					/*
					 * Com_Printf("%f, %f, %f\n",
					 * pbeam_offset_dir[0],
					 * pbeam_offset_dir[1],
					 * pbeam_offset_dir[2]);
					 */
					VectorMA(b->start, (newhandmult * b->offset[0]), r, org);
					VectorMA(org, b->offset[1], f, org);
					VectorMA(org, b->offset[2], u, org);
				} else {	/* firstperson */
					for (j = 0; j < 3; j++) {
						b->start[j] = cl.refdef.vieworg[j] + ops->gunoffset[j]
						    + cl.lerpfrac * (ps->gunoffset[j] - ops->gunoffset[j]);
					}
					VectorMA(b->start, (hand_multiplier * b->offset[0]), cl.v_right, org);
					VectorMA(org, b->offset[1], cl.v_forward, org);
					VectorMA(org, b->offset[2], cl.v_up, org);
					if ((hand) && (hand->value == 2)) {
						VectorMA(org, -1, cl.v_up, org);
					}
					/* FIXME - take these out when final */
					VectorCopy(cl.v_right, r);
					VectorCopy(cl.v_forward, f);
					VectorCopy(cl.v_up, u);
				}
			} else
				VectorCopy(b->start, org);
		} else {

			/*
			 * if coming from the player, update the start
			 * position
			 */

			/*
			 * if (b->entity == cl.playernum+1)	// entity 0
			 * is the world
			 */
			/* Knightmare- skip for chasecam mode */
			if (firstperson) {
				VectorCopy(cl.refdef.vieworg, b->start);
				b->start[2] -= 22;	/* adjust for view
							 * height */
			}
			VectorAdd(b->start, b->offset, org);
		}

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

		/* PMM */

		/*
		 * if(cl_mod_heatbeam && (b->model == cl_mod_heatbeam) &&
		 * (b->entity == cl.playernum+1))
		 */
		if (cl_mod_heatbeam && (b->model == cl_mod_heatbeam) && (firstperson || chasecam)) {
			vec_t		len;

			len = VectorLength(dist);
			VectorScale(f, len, dist);
			if (chasecam)
				VectorMA(dist, (newhandmult * b->offset[0]), r, dist);
			else
				VectorMA(dist, (hand_multiplier * b->offset[0]), r, dist);
			VectorMA(dist, b->offset[1], f, dist);
			VectorMA(dist, b->offset[2], u, dist);
			if (chasecam) {
				VectorMA(dist, -(newhandmult * thirdp_pbeam_offset[0]), r, dist);
				VectorMA(dist, thirdp_pbeam_offset[1], f, dist);
				VectorMA(dist, thirdp_pbeam_offset[2], u, dist);
			}
			if ((hand) && (hand->value == 2)) {
				VectorMA(org, -1, cl.v_up, org);
			}
		}
		/* PMM */

		if (dist[1] == 0 && dist[0] == 0) {
			yaw = 0;
			if (dist[2] > 0)
				pitch = 90;
			else
				pitch = 270;
		} else {
			/* PMM - fixed to correct for pitch of 0 */
			if (dist[0])
				yaw = (atan2(dist[1], dist[0]) * 180 / M_PI);
			else if (dist[1] > 0)
				yaw = 90;
			else
				yaw = 270;
			if (yaw < 0)
				yaw += 360;

			forward = sqrt(dist[0] * dist[0] + dist[1] * dist[1]);
			pitch = (atan2(dist[2], forward) * -180.0 / M_PI);
			if (pitch < 0)
				pitch += 360.0;
		}

		if (cl_mod_heatbeam && (b->model == cl_mod_heatbeam)) {
			/* if (b->entity != cl.playernum+1) */
			if (!firstperson) {
				framenum = 2;
				/* Com_Printf ("Third person\n"); */
				ent.angles[0] = -pitch;
				ent.angles[1] = yaw + 180.0;
				ent.angles[2] = 0;

				/*
				 * Com_Printf ("%f %f - %f %f %f\n", -pitch,
				 * yaw+180.0, b->offset[0], b->offset[1],
				 * b->offset[2]);
				 */
				if (!chasecam) {
					AngleVectors(ent.angles, f, r, u);

					/*
					 * if it's a non-origin offset, it's
					 * a player, so use the hardcoded
					 * player offset
					 */
					if (!VectorCompare(b->offset, vec3_origin)) {
						VectorMA(org, -(b->offset[0]) + 1, r, org);

						/*
						 * VectorMA (org,
						 * -(b->offset[1]), f, org);
						 */

						/*
						 * VectorMA (org,
						 * -(b->offset[2])-10, u,
						 * org);
						 */
						VectorMA(org, -(b->offset[1]) + 12, f, org);
						VectorMA(org, -(b->offset[2]) - 5, u, org);
					} else {

						/*
						 * if it's a monster, do the
						 * particle effect
						 */
						CL_MonsterPlasma_Shell(b->start);
					}
				}
			} else {
				framenum = 1;
			}
		}
		/* if it's the heatbeam, draw the particle effect */

		/*
		 * if ((cl_mod_heatbeam && (b->model == cl_mod_heatbeam) &&
		 * (b->entity == cl.playernum+1)))
		 */
		if ((cl_mod_heatbeam && (b->model == cl_mod_heatbeam) && (firstperson || chasecam))) {
			CL_Heatbeam(org, dist);
		}
		/* add new entities for the beams */
		d = VectorNormalize(dist);

		memset(&ent, 0, sizeof(ent));
		if (b->model == cl_mod_heatbeam) {
			model_length = 32.0;
		} else if (b->model == cl_mod_lightning) {
			model_length = 35.0;
			d -= 20.0;	/* correction so it doesn't end in
					 * middle of tesla */
		} else {
			model_length = 30.0;
		}
		steps = ceil(d / model_length);
		len = (d - model_length) / (steps - 1);

		/*
		 * PMM - special case for lightning model .. if the real
		 * length is shorter than the model,
		 */

		/*
		 * flip it around & draw it from the end to the start.  This
		 * prevents the model from going
		 */

		/*
		 * through the tesla mine (instead it goes through the
		 * target)
		 */
		if ((b->model == cl_mod_lightning) && (d <= model_length)) {
			/* Com_Printf ("special case\n"); */
			VectorCopy(b->end, ent.origin);

			/*
			 * offset to push beam outside of tesla model
			 * (negative because dist is from end to start
			 */
			/* for this beam) */
			/* for (j=0 ; j<3 ; j++) */
			/* ent.origin[j] -= dist[j]*10.0; */
			ent.model = b->model;
			ent.flags = RF_FULLBRIGHT;
			ent.angles[0] = pitch;
			ent.angles[1] = yaw;
			ent.angles[2] = rand() % 360;
			ent.flags |= RF_NOSHADOW;
			V_AddEntity(&ent);
			return;
		}
		while (d > 0) {
			VectorCopy(org, ent.origin);
			ent.model = b->model;
			if (cl_mod_heatbeam && (b->model == cl_mod_heatbeam)) {
				/* ent.flags = RF_FULLBRIGHT|RF_TRANSLUCENT; */
				/* ent.alpha = 0.3; */
				ent.flags = RF_FULLBRIGHT;
				ent.angles[0] = -pitch;
				ent.angles[1] = yaw + 180.0;
				ent.angles[2] = (cl.time) % 360;
				/* ent.angles[2] = rand()%360; */
				ent.frame = framenum;
			} else if (b->model == cl_mod_lightning) {
				ent.flags = RF_FULLBRIGHT;
				ent.angles[0] = -pitch;
				ent.angles[1] = yaw + 180.0;
				ent.angles[2] = rand() % 360;
			} else {
				ent.angles[0] = pitch;
				ent.angles[1] = yaw;
				ent.angles[2] = rand() % 360;
			}

			/*
			 * Com_Printf("B: %d -> %d\n", b->entity,
			 * b->dest_entity);
			 */
			ent.flags |= RF_NOSHADOW;
			V_AddEntity(&ent);

			for (j = 0; j < 3; j++)
				org[j] += dist[j] * len;
			d -= model_length;
		}
	}
}

/*
 * ================= CL_AddExplosions =================
 */
void
CL_AddExplosions(void)
{
	entity_t       *ent;
	int		i;
	explosion_t    *ex;
	float		frac;
	int		f;

	memset(&ent, 0, sizeof(ent));

	for (i = 0, ex = cl_explosions; i < MAX_EXPLOSIONS; i++, ex++) {
		if (ex->type == ex_free)
			continue;
		frac = (cl.time - ex->start) / 100.0;
		f = floor(frac);

		ent = &ex->ent;

		switch (ex->type) {
		case ex_free:
		case ex_explosion:
		case ex_mflash:
			if (f >= ex->frames - 1)
				ex->type = ex_free;
			break;
		case ex_misc:
			if (f >= ex->frames - 1) {
				ex->type = ex_free;
				break;
			}
			ent->alpha = 1.0 - frac / (ex->frames - 1);
			break;
		case ex_flash:
			if (f >= 1) {
				ex->type = ex_free;
				break;
			}
			ent->alpha = 1.0;
			break;
		case ex_poly:
			if (f >= ex->frames - 1) {
				ex->type = ex_free;
				break;
			}
			ent->alpha = (16.0 - (float)f) / 16.0;

			if (f < 10) {
				ent->skinnum = (f >> 1);
				if (ent->skinnum < 0)
					ent->skinnum = 0;
			} else {
				ent->flags |= RF_TRANSLUCENT;
				if (f < 13)
					ent->skinnum = 5;
				else
					ent->skinnum = 6;
			}
			break;
		case ex_poly2:
			if (f >= ex->frames - 1) {
				ex->type = ex_free;
				break;
			}
			ent->alpha = (5.0 - (float)f) / 5.0;
			ent->skinnum = 0;
			ent->flags |= RF_TRANSLUCENT;
			break;
		default:
			break;
		}

		if (ex->type == ex_free)
			continue;
		if (ex->light) {
			V_AddLight(ent->origin, ex->light * ent->alpha,
			    ex->lightcolor[0], ex->lightcolor[1], ex->lightcolor[2]);
		}
		VectorCopy(ent->origin, ent->oldorigin);

		if (f < 0)
			f = 0;
		ent->frame = ex->baseframe + f + 1;
		ent->oldframe = ex->baseframe + f;
		ent->backlerp = 1.0 - cl.lerpfrac;

		V_AddEntity(ent);
	}
}


/*
 * ================= CL_AddLasers =================
 */
void
CL_AddLasers(void)
{
	laser_t        *l;
	int		i;

	for (i = 0, l = cl_lasers; i < MAX_LASERS; i++, l++) {
		if (l->endtime >= cl.time)
			V_AddEntity(&l->ent);
	}
}

/* PMM - CL_Sustains */
void
CL_ProcessSustain()
{
	cl_sustain_t   *s;
	int		i;

	for (i = 0, s = cl_sustains; i < MAX_SUSTAINS; i++, s++) {
		if (s->id) {
			if ((s->endtime >= cl.time) && (cl.time >= s->nextthink)) {

				/*
				 * Com_Printf ("think %d %d %d\n", cl.time,
				 * s->nextthink, s->thinkinterval);
				 */
				s->think(s);
			} else if (s->endtime < cl.time)
				s->id = 0;
		}
	}
}

/*
 * ================= CL_AddTEnts =================
 */
void
CL_AddTEnts(void)
{
	CL_AddBeams();
	/* PMM - draw plasma beams */
	CL_AddPlayerBeams();
	CL_AddExplosions();
	CL_AddLasers();
	/* PMM - set up sustain */
	CL_ProcessSustain();
}
