/*
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);

#ifdef Q2CLIENT
static const char	*q2efnames[] =
{
	"TEQ2_GUNSHOT",
	"TEQ2_BLOOD",
	"TEQ2_BLASTER",
	"TEQ2_RAILTRAIL",
	"TEQ2_SHOTGUN",
	"TEQ2_EXPLOSION1",
	"TEQ2_EXPLOSION2",
	"TEQ2_ROCKET_EXPLOSION",
	"TEQ2_GRENADE_EXPLOSION",
	"TEQ2_SPARKS",
	NULL,//"TEQ2_SPLASH",
	"TEQ2_BUBBLETRAIL",
	"TEQ2_SCREEN_SPARKS",
	"TEQ2_SHIELD_SPARKS",
	"TEQ2_BULLET_SPARKS",
	NULL,//"TEQ2_LASER_SPARKS",
	NULL,//"TEQ2_PARASITE_ATTACK",
	"TEQ2_ROCKET_EXPLOSION_WATER",
	"TEQ2_GRENADE_EXPLOSION_WATER",
	NULL,//"TEQ2_MEDIC_CABLE_ATTACK",
	"TEQ2_BFG_EXPLOSION",
	"TEQ2_BFG_BIGEXPLOSION",
	"TEQ2_BOSSTPORT",
	NULL,//"TEQ2_BFG_LASER",
	NULL,//"TEQ2_GRAPPLE_CABLE",
	"TEQ2_WELDING_SPARKS",
	"TEQ2_GREENBLOOD",
	"TEQ2_BLUEHYPERBLASTER",
	"TEQ2_PLASMA_EXPLOSION",
	"TEQ2_TUNNEL_SPARKS",
	"TEQ2_BLASTER2",
	"TEQ2_RAILTRAIL2",	//not implemented in vanilla
	NULL,//"TEQ2_FLAME",	//not implemented in vanilla
	NULL,//"TEQ2_LIGHTNING",
	"TEQ2_DEBUGTRAIL",
	"TEQ2_PLAIN_EXPLOSION",
	"TEQ2_FLASHLIGHT",
	"TEQ2_FORCEWALL",
	NULL,//"TEQ2_HEATBEAM",
	NULL,//"TEQ2_MONSTER_HEATBEAM",
	NULL,//"TEQ2_STEAM",
	"TEQ2_BUBBLETRAIL2",
	"TEQ2_MOREBLOOD",
	"TEQ2_HEATBEAM_SPARKS",
	"TEQ2_HEATBEAM_STEAM",
	"TEQ2_CHAINFIST_SMOKE",
	"TEQ2_ELECTRIC_SPARKS",
	"TEQ2_TRACKER_EXPLOSION",
	"TEQ2_TELEPORT_EFFECT",
	"TEQ2_DBALL_GOAL",
	NULL,//"TEQ2_WIDOWBEAMOUT",
	NULL,//"TEQ2_NUKEBLAST",
	"TEQ2_WIDOWSPLASH",
	"TEQ2_EXPLOSION1_BIG",
	"TEQ2_EXPLOSION1_NP",
	"TEQ2_FLECHETTE",


	NULL,//"TEQ2_CR_LEADERBLASTER",
	NULL,//"TEQ2_CR_BLASTER_MUZZLEFLASH",
	NULL,//"TEQ2_CR_BLUE_MUZZLEFLASH",
	NULL,//"TEQ2_CR_SMART_MUZZLEFLASH",
	NULL,//"TEQ2_CR_LEADERFIELD",
	NULL,//"TEQ2_CR_DEATHFIELD",
	NULL,//"TEQ2_CR_BLASTERBEAM",
	NULL,//"TEQ2_CR_STAIN",
	NULL,//"TEQ2_CR_FIRE",
	NULL,//"TEQ2_CR_CABLEGUT",
	NULL,//"TEQ2_CR_SMOKE",

	//the rest have no specific value meanings

	//slashes block
	"te_splashunknown",
	"te_splashsparks",
	"te_splashbluewater",
	"te_splashbrownwater",
	"te_splashslime",
	"te_splashlava",
	"te_splashblood",

	"TR_BLASTERTRAIL",
	"TR_BLASTERTRAIL2",
	"TRQ2_GIB",
	"TRQ2_GREENGIB",
	"TRQ2_ROCKET",
	"TRQ2_GRENADE",

	"TR_TRAP",
	"TR_FLAG1",
	"TR_FLAG2",
	"TR_TAGTRAIL",
	"TR_TRACKER",
	"TR_IONRIPPER",
	"TR_PLASMA",

	"EF_BFGPARTICLES",
	"EF_FLIES",
	"EF_TRAP",
	"EF_TRACKERSHELL",

	
	"ev_item_respawn",
	"ev_player_teleport",
	"ev_footstep",
};
int pt_q2[sizeof(q2efnames)/sizeof(q2efnames[0])];
#endif

int
	pt_muzzleflash=P_INVALID,
	pt_gunshot=P_INVALID,
	ptdp_gunshotquad=P_INVALID,
	pt_spike=P_INVALID,
	ptdp_spikequad=P_INVALID,
	pt_superspike=P_INVALID,
	ptdp_superspikequad=P_INVALID,
	pt_wizspike=P_INVALID,
	pt_knightspike=P_INVALID,
	pt_explosion=P_INVALID,
	ptdp_explosionquad=P_INVALID,
	pt_tarexplosion=P_INVALID,
	pt_teleportsplash=P_INVALID,
	pt_lavasplash=P_INVALID,
	ptdp_smallflash=P_INVALID,
	ptdp_flamejet=P_INVALID,
	ptdp_flame=P_INVALID,
	ptdp_blood=P_INVALID,
	ptdp_spark=P_INVALID,
	ptdp_plasmaburn=P_INVALID,
	ptdp_tei_g3=P_INVALID,
	ptdp_tei_smoke=P_INVALID,
	ptdp_tei_bigexplosion=P_INVALID,
	ptdp_tei_plasmahit=P_INVALID,
	ptdp_stardust=P_INVALID,
	rt_rocket=P_INVALID,
	rt_grenade=P_INVALID,
	rt_blood=P_INVALID,
	rt_wizspike=P_INVALID,
	rt_slightblood=P_INVALID,
	rt_knightspike=P_INVALID,
	rt_vorespike=P_INVALID,
	rtdp_nexuizplasma=P_INVALID,
	rtdp_glowtrail=P_INVALID,

	ptqw_blood=P_INVALID,
	ptqw_lightningblood=P_INVALID,

	rtqw_railtrail=P_INVALID,
	ptfte_bullet=P_INVALID,
	ptfte_superbullet=P_INVALID;

typedef struct {
	/*static stuff*/
	char *modelname;
	char *beamparticles;
	char *beamimpactparticle;
	int bflags;

	/*cached stuff*/
	model_t *model;
	int ef_beam;
	int ef_impact;
} tentmodels_t;

struct beam_s {
	tentmodels_t *info;
	int		entity;
	short	tag;
//	short	pad;
//	qbyte	pad;
	qbyte	bflags;
	qbyte	type;
	qbyte	skin;
	unsigned int rflags;

	float	endtime;
	float	alpha;
	vec3_t	start, end;
	vec3_t	offset;	//when attached, this is the offset from the owning entity. probably only z is meaningful.
//	int		particlecolour;	//some effects have specific colours. which is weird.
	trailstate_t *trailstate;
	trailstate_t *emitstate;
};

beam_t		*cl_beams;
int			cl_beams_max;

typedef struct
{
	vec3_t	origin;
	vec3_t	oldorigin;
	vec3_t	velocity;

	int firstframe;
	int numframes;

	int		type;
	vec3_t	angles;
	vec3_t	avel;
	int		flags;
	float	gravity;
	float	startalpha;
	float	endalpha;
	float	scale;
	float	start;
	float	framerate;
	model_t	*model;
	int skinnum;
	int		traileffect;
	trailstate_t *trailstate;
} explosion_t;

explosion_t	*cl_explosions;
int			cl_explosions_max;

static int explosions_running;
static int beams_running;

static tentmodels_t beamtypes[] =
{
	{"progs/bolt.mdl",							"TE_LIGHTNING1",				"TE_LIGHTNING1_END"},
	{"progs/bolt2.mdl",							"TE_LIGHTNING2",				"TE_LIGHTNING2_END"},
	{"progs/bolt3.mdl",							"TE_LIGHTNING3",				"TE_LIGHTNING3_END"},
	{"progs/beam.mdl",							"te_beam",						"te_beam_end"},	//a CTF addition, but has other potential uses, sadly.

	{"models/monsters/parasite/segment/tris.md2","te_parasite_attack",			"te_parasite_attack_end"},
	{"models/ctf/segment/tris.md2",				"te_grapple_cable",				"te_grapple_cable_end"},
	{"models/proj/beam/tris.md2",				"te_heatbeam",					"te_heatbeam_end"},
	{"models/proj/lightning/tris.md2",			"TE_LIGHTNING2",				"TE_LIGHTNING2_END"},

	{"models/stltng2.mdl",						"te_stream_lightning_small",	NULL},
	{"models/stchain.mdl",						"te_stream_chain",				NULL},
	{"models/stsunsf1.mdl",						"te_stream_sunstaff1",			NULL},
	{"models/stsunsf2.mdl",						NULL,							NULL},
	{"models/stsunsf1.mdl",						"te_stream_sunstaff2",			NULL},
	{"models/stlghtng.mdl",						"te_stream_lightning",			NULL},
	{"models/stclrbm.mdl",						"te_stream_colorbeam",			NULL},
	{"models/stice.mdl",						"te_stream_icechunks",			NULL},
	{"models/stmedgaz.mdl",						"te_stream_gaze",				NULL},
	{"models/fambeam.mdl",						"te_stream_famine",				NULL},
};


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 = CVARFD("cl_expsprite", "0", CVAR_ARCHIVE, "Display a central sprite in explosion effects. QuakeWorld typically does so, NQ mods should not (which is problematic when played with the qw protocol).");
cvar_t  r_explosionlight = CVARFC("r_explosionlight", "1", CVAR_ARCHIVE, Cvar_Limiter_ZeroToOne_Callback);
cvar_t	cl_truelightning = CVARF("cl_truelightning", "0",	CVAR_SEMICHEAT);
cvar_t  cl_beam_trace = CVAR("cl_beam_trace", "0");
cvar_t	cl_legacystains = CVARD("cl_legacystains", "1", "WARNING: this cvar will default to 0 and later removed at some point");	//FIXME: do as the description says!
cvar_t	cl_shaftlight = {"gl_shaftlight", "0.8"};

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];

typedef struct associatedeffect_s
{
	struct associatedeffect_s *next;
	char mname[MAX_QPATH];
	char pname[MAX_QPATH];
	enum
	{
		AE_TRAIL,
		AE_EMIT,
	} type;
	unsigned int meflags;
} associatedeffect_t;
associatedeffect_t *associatedeffect;
void CL_AssociateEffect_f(void)
{
	char *modelname = Cmd_Argv(1);
	char *effectname = Cmd_Argv(2);
	int type, i;
	unsigned int flags = 0;
	struct associatedeffect_s *ae;
	if (!strcmp(Cmd_Argv(0), "r_trail"))
		type = AE_TRAIL;
	else
	{
		type = AE_EMIT;

		for (i = 3; i < Cmd_Argc(); i++)
		{
			const char *fn = Cmd_Argv(i);
			if (!strcmp(fn, "replace") || !strcmp(fn, "1"))
				flags |= MDLF_EMITREPLACE;
			else if (!strcmp(fn, "forwards") || !strcmp(fn, "forward"))
				flags |= MDLF_EMITFORWARDS;
			else if (!strcmp(fn, "0"))
				;	//1 or 0 are legacy, meaning replace or not
			else
				Con_DPrintf("%s %s: unknown flag %s\n", Cmd_Argv(0), modelname, fn);
		}
	}

	if (
		strstr(modelname, "player") ||
		strstr(modelname, "eyes") ||
		strstr(modelname, "flag") ||
		strstr(modelname, "tf_stan") ||
		strstr(modelname, ".bsp") ||
		strstr(modelname, "turr"))
	{
		Con_Printf("Sorry: Not allowed to attach effects to model \"%s\"\n", modelname);
		return;
	}

	if (strlen (modelname) >= MAX_QPATH || strlen(effectname) >= MAX_QPATH)
		return;

	/*replace the old one if it exists*/
	for(ae = associatedeffect; ae; ae = ae->next)
	{
		if (!strcmp(ae->mname, modelname))
			if ((ae->type==AE_TRAIL) == (type==AE_TRAIL))
				break;
	}
	if (!ae)
	{
		ae = Z_Malloc(sizeof(*ae));
		strcpy(ae->mname, modelname);
		ae->next = associatedeffect;
		associatedeffect = ae;
	}
	ae->type = type;
	ae->meflags = flags;
	strcpy(ae->pname, effectname);

	if (pe)
		CL_RegisterParticles();
}

void CL_InitTEntSounds (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;
	}
}

/*
=================
CL_ParseTEnts
=================
*/
void CL_InitTEnts (void)
{
	int i;
	for (i = 0; i < sizeof(tentsfx)/sizeof(tentsfx[0]); i++)
		*tentsfx[i].sfx = NULL;

	Cmd_AddCommand("r_effect", CL_AssociateEffect_f);
	Cmd_AddCommand("r_trail", CL_AssociateEffect_f);

	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");
	Cvar_Register (&cl_legacystains, "Temporary entity control");
	Cvar_Register (&cl_shaftlight, "Temporary entity control");
}

void CL_ShutdownTEnts (void)
{
	struct associatedeffect_s *ae;
	while(associatedeffect)
	{
		ae = associatedeffect;
		associatedeffect = ae->next;
		BZ_Free(ae);
	}
}

void CL_ClearTEntParticleState (void)
{
	int i;
	for (i = 0; i < cl_beams_max; i++)
	{
		if (cl_beams[i].trailstate)
			P_DelinkTrailstate(&(cl_beams[i].trailstate));
		if (cl_beams[i].emitstate)
			P_DelinkTrailstate(&(cl_beams[i].emitstate));
	}
}

void P_LoadedModel(model_t *mod)
{
	struct associatedeffect_s *ae;

	mod->particleeffect = P_INVALID;
	mod->particletrail = P_INVALID;
	mod->engineflags &= ~(MDLF_EMITREPLACE|MDLF_EMITFORWARDS);
	mod->engineflags |= MDLF_RECALCULATERAIN;
	for(ae = associatedeffect; ae; ae = ae->next)
	{
		if (!strcmp(ae->mname, mod->name))
		{
			switch(ae->type)
			{
			case AE_TRAIL:
				mod->particletrail = P_FindParticleType(ae->pname);
				break;
			case AE_EMIT:
				mod->particleeffect = P_FindParticleType(ae->pname);
				mod->engineflags |= ae->meflags;
				break;
			}
		}
	}
	if (mod->particletrail == P_INVALID)
		P_DefaultTrail(0, mod->flags, &mod->particletrail, &mod->traildefaultindex);
}

void CL_RefreshCustomTEnts(void);
void CL_RegisterParticles(void)
{
	model_t *mod;
	extern model_t	*mod_known;
	extern int		mod_numknown;
	int i;
	for (i=0 , mod=mod_known ; i<mod_numknown ; i++, mod++)
	{
		if (mod->loadstate == MLS_LOADED)
		{
			P_LoadedModel(mod);
		}
	}

	pt_muzzleflash			= P_FindParticleType("TE_MUZZLEFLASH");
	pt_gunshot				= P_FindParticleType("TE_GUNSHOT");	/*shotgun*/
	ptdp_gunshotquad		= P_FindParticleType("TE_GUNSHOTQUAD");	/*DP: quadded shotgun*/
	pt_spike				= P_FindParticleType("TE_SPIKE");	/*nailgun*/
	ptdp_spikequad			= P_FindParticleType("TE_SPIKEQUAD");	/*DP: quadded nailgun*/
	pt_superspike			= P_FindParticleType("TE_SUPERSPIKE");	/*nailgun*/
	ptdp_superspikequad		= P_FindParticleType("TE_SUPERSPIKEQUAD");	/*DP: quadded nailgun*/
	pt_wizspike				= P_FindParticleType("TE_WIZSPIKE");	//scrag missile impact
	pt_knightspike			= P_FindParticleType("TE_KNIGHTSPIKE"); //hellknight missile impact
	pt_explosion			= P_FindParticleType("TE_EXPLOSION");/*rocket/grenade launcher impacts/far too many things*/
	ptdp_explosionquad		= P_FindParticleType("TE_EXPLOSIONQUAD");	/*nailgun*/
	pt_tarexplosion			= P_FindParticleType("TE_TAREXPLOSION");//tarbaby/spawn dying.
	pt_teleportsplash		= P_FindParticleType("TE_TELEPORT");/*teleporters*/
	pt_lavasplash			= P_FindParticleType("TE_LAVASPLASH");	//e1m7 boss dying.
	ptdp_smallflash			= P_FindParticleType("TE_SMALLFLASH");	//DP:
	ptdp_flamejet			= P_FindParticleType("TE_FLAMEJET");	//DP:
	ptdp_flame				= P_FindParticleType("EF_FLAME");	//DP:
	ptdp_blood				= P_FindParticleType("TE_BLOOD"); /*when you hit something with the shotgun/axe/nailgun - nq uses the general particle builtin*/
	ptdp_spark				= P_FindParticleType("TE_SPARK");//DPTE_SPARK
	ptdp_plasmaburn			= P_FindParticleType("TE_PLASMABURN");
	ptdp_tei_g3				= P_FindParticleType("TE_TEI_G3");
	ptdp_tei_smoke			= P_FindParticleType("TE_TEI_SMOKE");
	ptdp_tei_bigexplosion	= P_FindParticleType("TE_TEI_BIGEXPLOSION");
	ptdp_tei_plasmahit		= P_FindParticleType("TE_TEI_PLASMAHIT");
	ptdp_stardust			= P_FindParticleType("EF_STARDUST");
	rt_rocket				= P_FindParticleType("TR_ROCKET");	/*rocket trail*/
	rt_grenade				= P_FindParticleType("TR_GRENADE");	/*grenade trail*/
	rt_blood				= P_FindParticleType("TR_BLOOD");	/*blood trail*/
	rt_wizspike				= P_FindParticleType("TR_WIZSPIKE");
	rt_slightblood			= P_FindParticleType("TR_SLIGHTBLOOD");
	rt_knightspike			= P_FindParticleType("TR_KNIGHTSPIKE");
	rt_vorespike			= P_FindParticleType("TR_VORESPIKE");
	//rtdp_neharasmoke		= P_FindParticleType("TR_NEHAHRASMOKE");
	rtdp_nexuizplasma		= P_FindParticleType("TR_NEXUIZPLASMA");
	rtdp_glowtrail			= P_FindParticleType("TR_GLOWTRAIL");
	/*internal to psystem*/   P_FindParticleType("SVC_PARTICLE");

	ptqw_blood				= P_FindParticleType("TE_BLOOD");
	ptqw_lightningblood		= P_FindParticleType("TE_LIGHTNINGBLOOD");

#ifdef Q2CLIENT
	if (cls.protocol == CP_QUAKE2)
	{
		for (i = 0; i < sizeof(pt_q2)/sizeof(pt_q2[0]); i++)
		{
			if (!q2efnames[i])
			{
				pt_q2[i] = P_INVALID;
				continue;
			}
			pt_q2[i] = P_FindParticleType(va("q2part.%s", q2efnames[i]));

#ifdef _DEBUG
			if (pt_q2[i] == P_INVALID && pt_q2[0] != P_INVALID)
				Con_Printf("effect q2part.%s was not declared\n", q2efnames[i]);
#endif
		}

		/*ptq2_blood				= P_FindParticleType("q2part.TEQ2_BLOOD");
		rtq2_railtrail			= P_FindParticleType("q2part.TR_RAILTRAIL");
		rtq2_blastertrail		= P_FindParticleType("q2part.TR_BLASTERTRAIL");
		ptq2_blasterparticles	= P_FindParticleType("q2part.TE_BLASTERPARTICLES");
		rtq2_bubbletrail		= P_FindParticleType("q2part.TE_BUBBLETRAIL");
		rtq2_gib				= P_FindParticleType("q2part.TR_GIB");
		rtq2_rocket				= P_FindParticleType("q2part.TR_ROCKET");
		rtq2_grenade			= P_FindParticleType("q2part.TR_GRENADE");
		ptq2_bfgparticles		= P_FindParticleType("q2part.TR_BFGPARTICLES");
		ptq2_flies				= P_FindParticleType("q2part.TR_FLIES");
		ptq2_trap				= P_FindParticleType("q2part.TR_TRAP");
		ptq2_trackershell		= P_FindParticleType("q2part.TR_TRACKERSHELL");*/
	}
	else
	{
		for (i = 0; i < sizeof(pt_q2)/sizeof(pt_q2[0]); i++)
			pt_q2[i] = P_INVALID;
	}
#endif

	rtqw_railtrail			= P_FindParticleType("TE_RAILTRAIL");
	ptfte_bullet			= P_FindParticleType("TE_BULLET");
	ptfte_superbullet		= P_FindParticleType("TE_SUPERBULLET");

	CL_RefreshCustomTEnts();

	for (i = 0; i < countof(beamtypes); i++)
	{
		//we can normally expect the server to have precache_modeled these models, so any lookups should be just a lookup, and thus relatively cheap.
		beamtypes[i].model = NULL;
		beamtypes[i].ef_beam = beamtypes[i].beamparticles?P_FindParticleType(beamtypes[i].beamparticles):P_INVALID;
		beamtypes[i].ef_impact = beamtypes[i].beamimpactparticle?P_FindParticleType(beamtypes[i].beamimpactparticle):P_INVALID;
	}

	//FIXME
	for (i = 0; i < cl_explosions_max; i++)
		cl_explosions[i].model = NULL;
}

#ifdef Q2CLIENT
enum {
	q2cl_mod_explode,
	q2cl_mod_smoke,
	q2cl_mod_flash,
	q2cl_mod_parasite_tip,
	q2cl_mod_explo4,
	q2cl_mod_bfg_explo,
	q2cl_mod_powerscreen,
	q2cl_mod_max
};
tentmodels_t q2tentmodels[q2cl_mod_max] = {
	{"models/objects/explode/tris.md2"},
	{"models/objects/smoke/tris.md2"},
	{"models/objects/flash/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

static void CL_ClearExplosion(explosion_t *exp, vec3_t org)
{
	exp->endalpha = 0;
	exp->startalpha = 1;
	exp->scale = 1;
	exp->gravity = 0;
	exp->flags = 0;
	exp->model = NULL;
	exp->firstframe = -1;
	exp->framerate = 10;
	VectorClear(exp->velocity);
	VectorClear(exp->angles);
	VectorClear(exp->avel);
	if (pe)
		P_DelinkTrailstate(&(exp->trailstate));
	exp->traileffect = P_INVALID;
	VectorCopy(org, exp->origin);
	VectorCopy(org, exp->oldorigin);
}

/*
=================
CL_ClearTEnts
=================
*/
void CL_ClearTEnts (void)
{
	int i;
	CL_ClearTEntParticleState();
	CL_ShutdownTEnts();

	cl_beams_max = 0;
	BZ_Free(cl_beams);
	cl_beams = NULL;
	beams_running = 0;

	for (i = 0; i < cl_explosions_max; i++)
		CL_ClearExplosion(cl_explosions+i, vec3_origin);
	cl_explosions_max = 0;
	BZ_Free(cl_explosions);
	cl_explosions = NULL;
	explosions_running = 0;
}

/*
=================
CL_AllocExplosion
=================
*/
explosion_t *CL_AllocExplosion (vec3_t org)
{
	int		i;
	float	time;
	int		index;

	for (i=0; i < explosions_running; i++)
	{
		if (!cl_explosions[i].model)
		{
			CL_ClearExplosion(&cl_explosions[i], org);
			return &cl_explosions[i];
		}
	}

//	if (i == explosions_running && i < cl_maxexplosions.ival)
	{
		if (i == cl_explosions_max)
		{
			cl_explosions_max = (i+1)*2;
			cl_explosions = BZ_Realloc(cl_explosions, sizeof(*cl_explosions)*cl_explosions_max);
			memset(cl_explosions + i, 0, sizeof(*cl_explosions)*(cl_explosions_max-i));
		}

		explosions_running++;
		CL_ClearExplosion(&cl_explosions[i], org);
		return &cl_explosions[i];
	}

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

	for (i=0 ; i<cl_explosions_max ; i++)
		if (cl_explosions[i].start < time)
		{
			time = cl_explosions[i].start;
			index = i;
		}
	CL_ClearExplosion(&cl_explosions[index], org);
	return &cl_explosions[index];
}

/*
=================
CL_ParseBeam
=================
*/
beam_t	*CL_NewBeam (int entity, int tag, tentmodels_t *btype)
{
	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)
			{
				b->info = btype;
				return b;
			}
	}

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

//	if (i == beams_running && i < cl_maxbeams.ival)
	{
		if (i == cl_beams_max)
		{
			int nm = (i+1)*2;
			CL_ClearTEntParticleState();

			cl_beams = BZ_Realloc(cl_beams, nm*sizeof(*cl_beams));
			memset(cl_beams + cl_beams_max, 0, sizeof(*cl_beams)*(nm-cl_beams_max));
			cl_beams_max = nm;
		}

		beams_running++;
		cl_beams[i].info = btype;
		return &cl_beams[i];
	}

	return NULL;
}
#define STREAM_ATTACHED			16
#define STREAM_TRANSLUCENT		32
beam_t *CL_AddBeam (enum beamtype_e 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;

	//zquake compat requires some parsing weirdness.
	switch(tent)
	{
	case BT_Q1LIGHTNING1:
		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, rtqw_railtrail, -ent, NULL, NULL))
				P_ParticleTrailIndex(start, end, P_INVALID, 208, 8, NULL);
			return NULL;
		}
		break;
	case BT_Q1LIGHTNING2:
		if (ent < 0 && ent >= -MAX_CLIENTS)	//based on the railgun concept - this adds a rogue style TE_BEAM effect.
			tent = BT_Q1BEAM;
		break;
	default:
		break;
	}

	btype = beamtypes[tent].ef_beam;
	etype = beamtypes[tent].ef_impact;

	/*don't bother loading the model if we have a particle effect for it instead*/
	if (ruleset_allow_particle_lightning.ival && btype >= 0)
		m = NULL;
	else
	{
		m = beamtypes[tent].model;
		if (!m)
			m = beamtypes[tent].model = Mod_ForName(beamtypes[tent].modelname, MLV_WARN);
	}

	if (m && m->loadstate != MLS_LOADED)
		CL_CheckOrEnqueDownloadFile(m->name, NULL, 0);

	// save end position for truelightning
	if (ent)
	{
		for (i = 0; i < cl.splitclients; i++)
		{
			playerview_t *pv = &cl.playerview[i];
			if (ent == ((pv->cam_state == CAM_EYECAM)?(pv->cam_spec_track+1):(pv->playernum+1)))
			{
				VectorCopy(end, playerbeam_end[i]);
				break;
			}
		}
	}

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

	b = CL_NewBeam(ent, -1, &beamtypes[tent]);
	if (!b)
	{
		Con_Printf ("beam list overflow!\n");
		return NULL;
	}

	b->rflags = RF_NOSHADOW;
	b->entity = ent;
	b->info = &beamtypes[tent];
	b->tag = -1;
	b->bflags |= /*STREAM_ATTACHED|*/1;
	b->endtime = cl.time + 0.2;
	b->alpha = 1;
	VectorCopy (start, b->start);
	VectorCopy (end, b->end);

	if (etype >= 0)
	{
		P_RunParticleEffectState (impact, normal, 1, etype, &(b->emitstate));
		if (cl_legacystains.ival) Surf_AddStain(end, -10, -10, -10, 20);
	}
	return b;
}
void CL_ParseBeamOffset (enum beamtype_e tent)
{
	int		ent;
	vec3_t	start, end, offset;
	beam_t *b;

	ent = MSGCL_ReadEntity ();

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

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

	MSG_ReadPos(offset);

	b = CL_AddBeam(tent, ent, start, end);
	if (b)
		VectorCopy(offset, b->offset);
}
beam_t *CL_ParseBeam (enum beamtype_e tent)
{
	int		ent;
	vec3_t	start, end;

	ent = MSGCL_ReadEntity ();

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

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

	return CL_AddBeam(tent, ent, start, end);
}

//finds the latest non-lerped position
float *CL_FindLatestEntityOrigin(int entnum)
{
	int i;
	packet_entities_t *pe;
	int framenum = cl.validsequence & UPDATE_MASK;
	pe = &cl.inframes[framenum].packet_entities;
	for (i = 0; i < pe->num_entities; i++)
	{
		if (pe->entities[i].number == entnum)
			return pe->entities[i].origin;
	}
	if (entnum > 0 && entnum <= MAX_CLIENTS)
	{
		entnum--;
		if (cl.inframes[framenum].playerstate[entnum].messagenum == cl.parsecount)
			return cl.inframes[framenum].playerstate[entnum].origin;
	}
	return NULL;
}

void CL_ParseStream (int type)
{
	int		ent;
	vec3_t	start, end;
	beam_t	*b, *b2;
	int flags;
	int tag;
	float duration;
	int skin;
	tentmodels_t *info;

	ent = MSGCL_ReadEntity();
	flags = MSG_ReadByte();
	tag = flags&15;
	flags-=tag;
	duration = (float)MSG_ReadByte()*0.05;
	skin = 0;
	if(type == TEH2_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();

	switch(type)
	{
	case TEH2_STREAM_LIGHTNING_SMALL:
		info = &beamtypes[BT_H2LIGHTNING_SMALL];
		flags |= 2;
		break;
	case TEH2_STREAM_LIGHTNING:
		info = &beamtypes[BT_H2LIGHTNING];
		flags |= 2;
		break;
	case TEH2_STREAM_ICECHUNKS:
		info = &beamtypes[BT_H2ICECHUNKS];
		flags |= 2;
		if (cl_legacystains.ival) Surf_AddStain(end, -10, -10, 0, 20);
		break;
	case TEH2_STREAM_SUNSTAFF1:
		info = &beamtypes[BT_H2SUNSTAFF1];
		break;
	case TEH2_STREAM_SUNSTAFF2:
		info = &beamtypes[BT_H2SUNSTAFF2];
		if (cl_legacystains.ival) Surf_AddStain(end, -10, -10, -10, 20);
		break;
	case TEH2_STREAM_COLORBEAM:
		info = &beamtypes[BT_H2COLORBEAM];
		break;
	case TEH2_STREAM_GAZE:
		info = &beamtypes[BT_H2GAZE];
		break;
	case TEH2_STREAM_FAMINE:
		info = &beamtypes[BT_H2FAMINE];
		break;
	case TEH2_STREAM_CHAIN:
		info = &beamtypes[BT_H2CHAIN];
		break;
	default:
		Con_Printf("CL_ParseStream: type %i\n", type);
		info = &beamtypes[BT_H2LIGHTNING];
		break;
	}

	b = CL_NewBeam(ent, tag, info);
	if (!b)
	{
		Con_Printf ("beam list overflow!\n");
		return;
	}
		
	b->rflags = RF_NOSHADOW;
	b->entity = ent;
	b->tag = tag;
	b->bflags = flags;
	b->endtime = cl.time + duration;
	b->alpha = 1;
	b->skin = skin;
	VectorCopy (start, b->start);
	VectorCopy (end, b->end);

	if (b->bflags & STREAM_ATTACHED)
	{
		float *entorg = CL_FindLatestEntityOrigin(ent);
		if (!entorg)
			b->bflags &= ~STREAM_ATTACHED;	//not found, attached isn't valid.
		else
		{
			VectorSubtract(b->start, entorg, b->offset);
		}
	}

	//special handling...
	switch(type)
	{
	case TEH2_STREAM_SUNSTAFF1:
		if (info->ef_beam == P_INVALID)
		{
			b2 = CL_NewBeam(ent, tag+128, &beamtypes[BT_H2SUNSTAFF1_SUB]);
			if (b2)
			{
				P_DelinkTrailstate(&b2->trailstate);
				P_DelinkTrailstate(&b2->emitstate);
				memcpy(b2, b, sizeof(*b2));
				b2->trailstate = NULL;
				b2->emitstate = NULL;
				b2->alpha = 0.5;
				b2->rflags = RF_TRANSLUCENT|RF_NOSHADOW;
			}
		}
		//FIXME: we don't add the blob corners+smoke
		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;

#ifdef CSQC_DAT
	//I know I'm going to regret this.
	if (CSQC_ParseTempEntity())
		return;
#endif

	type = MSG_ReadByte ();

	if (nqprot)
	{
		//easiest way to handle these
		//should probably also do qwgunshot ones with nq protocols or something
		switch(type)
		{
		case TENQ_EXPLOSION2:
			type = TEQW_EXPLOSION2;
			break;
		case TENQ_BEAM:
			type = TEQW_BEAM;
			break;
		case TENQ_EXPLOSION_SPRITE:
			type = TE_EXPLOSION;
			break;
		case TE_EXPLOSION:
			type = TEQW_EXPLOSION_NOSPRITE;
			break;
		case TE_GUNSHOT:
			type = TE_GUNSHOT_NQCOMPAT;
			break;
		case TE_GUNSHOT_NQCOMPAT:
			type = TE_GUNSHOT;
			break;
		default:
			break;
		}
	}

	//right, nq vs qw doesn't matter now, supposedly.
	
	if (cl_shownet.ival >= 2)
	{
		static char *te_names[] = {
			"spike", "superspike", "qwgunshot", "qwexplosion",
			"tarexplosion", "lightning1", "lightning2", "wizspike",
			"knightspike", "lightning3", "lavasplash", "teleport",
			"blood", "lightningblood", "bullet", "superbullet",	//bullets deprecated
			"railtrail", "beam", "explosion2", "nqexplosion",
			"nqgunshot", "?", "?", "?",
#ifdef HEXEN2
			"h2lightsml", "h2chain", "h2sunstf1", "h2sunstf2",
			"h2light", "h2cb", "h2ic", "h2gaze",
			"h2famine", "h2partexp"
#endif
		};

		if (type < countof(te_names))
			Con_Printf("  te_%s\n", te_names[type]);
		else
			Con_Printf("  te_%i\n", type);
	}

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

		if (cl_legacystains.ival) Surf_AddStain(pos, -10, 0, -10, 20);

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

		S_StartSound (0, 0, cl_sfx_wizhit, pos, NULL, 1, 1, 0, 0, 0);
		break;

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

		if (cl_legacystains.ival) Surf_AddStain(pos, -10, -10, -10, 20);

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

		S_StartSound (0, 0, cl_sfx_knighthit, pos, NULL, 1, 1, 0, 0, 0);
		break;

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

		if (cl_legacystains.ival) Surf_AddStain(pos, -10, -10, -10, 20);

		if (P_RunParticleEffectType(pos, NULL, 1, ptdp_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 (0, 0, cl_sfx_tink1, pos, NULL, 1, 1, 0, 0, 0);
		else
		{
			rnd = rand() & 3;
			if (rnd == 1)
				S_StartSound (0, 0, cl_sfx_ric1, pos, NULL, 1, 1, 0, 0, 0);
			else if (rnd == 2)
				S_StartSound (0, 0, cl_sfx_ric2, pos, NULL, 1, 1, 0, 0, 0);
			else
				S_StartSound (0, 0, cl_sfx_ric3, pos, NULL, 1, 1, 0, 0, 0);
		}
		break;
	case TE_SPIKE:			// spike hitting wall
		pos[0] = MSG_ReadCoord ();
		pos[1] = MSG_ReadCoord ();
		pos[2] = MSG_ReadCoord ();

		if (cl_legacystains.ival) Surf_AddStain(pos, -10, -10, -10, 20);

		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 (0, 0, cl_sfx_tink1, pos, NULL, 1, 1, 0, 0, 0);
		else
		{
			rnd = rand() & 3;
			if (rnd == 1)
				S_StartSound (0, 0, cl_sfx_ric1, pos, NULL, 1, 1, 0, 0, 0);
			else if (rnd == 2)
				S_StartSound (0, 0, cl_sfx_ric2, pos, NULL, 1, 1, 0, 0, 0);
			else
				S_StartSound (0, 0, cl_sfx_ric3, pos, NULL, 1, 1, 0, 0, 0);
		}
		break;
	case TEDP_SUPERSPIKEQUAD:			// super spike hitting wall
		pos[0] = MSG_ReadCoord ();
		pos[1] = MSG_ReadCoord ();
		pos[2] = MSG_ReadCoord ();

		if (cl_legacystains.ival) Surf_AddStain(pos, -10, -10, -10, 20);

		if (P_RunParticleEffectType(pos, NULL, 1, ptdp_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 (0, 0, cl_sfx_tink1, pos, NULL, 1, 1, 0, 0, 0);
		else
		{
			rnd = rand() & 3;
			if (rnd == 1)
				S_StartSound (0, 0, cl_sfx_ric1, pos, NULL, 1, 1, 0, 0, 0);
			else if (rnd == 2)
				S_StartSound (0, 0, cl_sfx_ric2, pos, NULL, 1, 1, 0, 0, 0);
			else
				S_StartSound (0, 0, cl_sfx_ric3, pos, NULL, 1, 1, 0, 0, 0);
		}
		break;
	case TE_SUPERSPIKE:			// super spike hitting wall
		pos[0] = MSG_ReadCoord ();
		pos[1] = MSG_ReadCoord ();
		pos[2] = MSG_ReadCoord ();

		if (cl_legacystains.ival) Surf_AddStain(pos, -10, -10, -10, 20);

		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 (0, 0, cl_sfx_tink1, pos, NULL, 1, 1, 0, 0, 0);
		else
		{
			rnd = rand() & 3;
			if (rnd == 1)
				S_StartSound (0, 0, cl_sfx_ric1, pos, NULL, 1, 1, 0, 0, 0);
			else if (rnd == 2)
				S_StartSound (0, 0, cl_sfx_ric2, pos, NULL, 1, 1, 0, 0, 0);
			else
				S_StartSound (0, 0, cl_sfx_ric3, pos, NULL, 1, 1, 0, 0, 0);
		}
		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 ();

		if (cl_legacystains.ival) Surf_AddStain(pos, -10, -10, -10, 20);

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

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

		if (cl_legacystains.ival) Surf_AddStain(pos, -10, -10, -10, 20);

		if (P_RunParticleEffectType(pos, NULL, 1, ptfte_superbullet))
			if (P_RunParticleEffectType(pos, NULL, 2, ptfte_bullet))
				if (P_RunParticleEffectType(pos, NULL, 20, pt_gunshot))
					P_RunParticleEffect (pos, vec3_origin, 0, 20);

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

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

		if (cl_legacystains.ival) Surf_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] = 4.0;
			dl->color[1] = 2.0;
			dl->color[2] = 0.5;
			dl->channelfade[0] = 0.196;
			dl->channelfade[1] = 0.23;
			dl->channelfade[2] = 0.12;
		}


	// sound
		S_StartSound (0, 0, cl_sfx_r_exp3, pos, NULL, 1, 1, 0, 0, 0);

	// sprite
		if (cl_expsprite.ival) // temp hopefully
		{
			explosion_t *ex = CL_AllocExplosion (pos);
			ex->start = cl.time;
			ex->model = Mod_ForName ("progs/s_explod.spr", MLV_WARN);
			ex->endalpha = ex->startalpha;	//don't fade out
		}
		break;
	case TEQW_EXPLOSION_NOSPRITE:	//nq-style, no sprite
	case TE_EXPLOSION:				//qw-style, with (optional) sprite
	// 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

		if (cl_legacystains.ival) Surf_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.75;
			dl->decay = dl->radius*2;

			dl->color[0] = 4.0;
			dl->color[1] = 2.0;
			dl->color[2] = 0.5;
			dl->channelfade[0] = 0.784;
			dl->channelfade[1] = 0.92;
			dl->channelfade[2] = 0.48;
		}


	// sound
		S_StartSound (0, 0, cl_sfx_r_exp3, pos, NULL, 1, 1, 0, 0, 0);

	// sprite
		if (type == TE_EXPLOSION && cl_expsprite.ival) // temp hopefully
		{
			explosion_t *ex = CL_AllocExplosion (pos);
			ex->start = cl.time;
			ex->model = Mod_ForName ("progs/s_explod.spr", MLV_WARN);
			ex->endalpha = ex->startalpha;	//don't fade out
		}
		break;

	case TEQW_EXPLOSION2:
		{
			int colorStart;
			int colorLength;
			int ef;
			pos[0] = MSG_ReadCoord ();
			pos[1] = MSG_ReadCoord ();
			pos[2] = MSG_ReadCoord ();
			colorStart = MSG_ReadByte ();
			colorLength = MSG_ReadByte ();

			ef = P_FindParticleType(va("TE_EXPLOSION2_%i_%i", colorStart, colorLength));
			if (ef == P_INVALID)
				ef = pt_explosion;
			P_RunParticleEffectType(pos, NULL, 1, ef);
			if (r_explosionlight.value)
			{
				dl = CL_AllocDlight (0);
				VectorCopy (pos, dl->origin);
				dl->radius = 350;
				dl->die = cl.time + 0.5;
				dl->decay = 300;
			}
			S_StartSound (0, 0, cl_sfx_r_exp3, pos, NULL, 1, 1, 0, 0, 0);
		}
		break;

	case TEDP_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

		if (cl_legacystains.ival) Surf_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 (0, 0, cl_sfx_r_exp3, pos, NULL, 1, 1, 0, 0, 0);
		break;

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

		if (cl_legacystains.ival) Surf_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] = 2.0f;
			dl->color[1] = 1.5f;
			dl->color[2] = 0.75f;
			dl->channelfade[0] = 0;
			dl->channelfade[1] = 0;
			dl->channelfade[2] = 0;
		}

		S_StartSound (0, 0, cl_sfx_r_exp3, pos, NULL, 1, 1, 0, 0, 0);
		break;

	case TE_TAREXPLOSION:			// tarbaby explosion
		pos[0] = MSG_ReadCoord ();
		pos[1] = MSG_ReadCoord ();
		pos[2] = MSG_ReadCoord ();
		P_RunParticleEffectType(pos, NULL, 1, pt_tarexplosion);

		S_StartSound (0, 0, cl_sfx_r_exp3, pos, NULL, 1, 1, 0, 0, 0);
		break;

	case TE_LIGHTNING1:				// lightning bolts
		CL_ParseBeam (BT_Q1LIGHTNING1);
		break;
	case TE_LIGHTNING2:				// lightning bolts
		CL_ParseBeam (BT_Q1LIGHTNING2);
		break;
	case TE_LIGHTNING3:				// lightning bolts
		CL_ParseBeam (BT_Q1LIGHTNING3);
		break;

	case TE_LAVASPLASH:
		pos[0] = MSG_ReadCoord ();
		pos[1] = MSG_ReadCoord ();
		pos[2] = MSG_ReadCoord ();
		P_RunParticleEffectType(pos, NULL, 1, pt_lavasplash);
		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 TEDP_GUNSHOTQUAD:			// bullet hitting wall
		pos[0] = MSG_ReadCoord ();
		pos[1] = MSG_ReadCoord ();
		pos[2] = MSG_ReadCoord ();

		if (cl_legacystains.ival) Surf_AddStain(pos, -10, -10, -10, 20);

		if (P_RunParticleEffectType(pos, NULL, 1, ptdp_gunshotquad))
			if (P_RunParticleEffectType(pos, NULL, 1, pt_gunshot))
				P_RunParticleEffect (pos, vec3_origin, 0, 20);

		break;

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

		if (cl_legacystains.ival) Surf_AddStain(pos, -10, -10, -10, 20);

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

		break;

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

		if (cl_legacystains.ival) Surf_AddStain(pos, 0, -10, -10, 40);

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

		break;

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

		if (cl_legacystains.ival) Surf_AddStain(pos, 1, -10, -10, 20);

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

		break;

	case TEQW_BEAM:
		CL_ParseBeam (BT_Q1BEAM);
		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, rtqw_railtrail, 0, NULL, NULL))
			P_ParticleTrailIndex(pos, pos2, P_INVALID, 208, 8, NULL);
		break;

	case TEH2_STREAM_LIGHTNING_SMALL:
	case TEH2_STREAM_CHAIN:
	case TEH2_STREAM_SUNSTAFF1:
	case TEH2_STREAM_SUNSTAFF2:
	case TEH2_STREAM_LIGHTNING:
	case TEH2_STREAM_COLORBEAM:
	case TEH2_STREAM_ICECHUNKS:
	case TEH2_STREAM_GAZE:
	case TEH2_STREAM_FAMINE:
		CL_ParseStream (type);
		break;

	case TEDP_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, ptdp_blood);
		break;

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

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

		cnt = MSG_ReadByte ();
		{
			P_RunParticleEffectType(pos, pos2, cnt, ptdp_spark);
		}
		break;

	case TEDP_BLOODSHOWER:
		{
			vec3_t vel = {0,0,0};
			pos[0] = MSG_ReadCoord ();
			pos[1] = MSG_ReadCoord ();
			pos[2] = MSG_ReadCoord ();

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

			vel[2] = -MSG_ReadCoord ();

			cnt = MSG_ReadShort ();

			P_RunParticleCube(P_FindParticleType("te_bloodshower"), pos, pos2, vel, vel, cnt, 0, false, 0);
		}
		break;

	case TEDP_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] = 2.0;
		dl->color[1] = 2.0;
		dl->color[2] = 2.0;
		break;

	case TEDP_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];

		dl->color[0] = MSG_ReadByte()*(1.0f/127.0f);
		dl->color[1] = MSG_ReadByte()*(1.0f/127.5f);
		dl->color[2] = MSG_ReadByte()*(1.0f/127.0f);

		break;

	case TEDP_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_RunParticleEffectType(pos, pos2, cnt, ptdp_flamejet))
			P_RunParticleEffect (pos, pos2, 232, cnt);
		break;

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

		// stain (Hopefully this is close to how DP does it)
		if (cl_legacystains.ival) Surf_AddStain(pos, -10, -10, -10, 30);

		if (P_RunParticleEffectType(pos, NULL, 1, P_FindParticleType("te_plasmaburn")))
			P_RunParticleEffect(pos, vec3_origin, 15, 50);
		break;

	case TEDP_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"), 0, NULL, NULL))
			P_ParticleTrailIndex(pos, pos2, P_INVALID, 15, 0, NULL);
		break;

	case TEDP_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 ();
		{
			P_RunParticleEffectType(pos, pos2, cnt, ptdp_tei_smoke);
		}
		break;

	case TEDP_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 ();

		{
			P_RunParticleEffectType(pos, pos2, cnt, ptdp_tei_plasmahit);
		}
		break;

	case TEDP_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(P_INVALID, pos, pos2, dir, dir, cnt, colour, gravity, jitter);
		}
		break;
	case TEDP_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 = (unsigned short)MSG_ReadShort();	//count
			colour = MSG_ReadByte ();	//colour

			P_RunParticleWeather(pos, pos2, dir, cnt, colour, "rain");
		}
		break;
	case TEDP_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 = (unsigned short)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 {
	char name[64];
	int netstyle;
	int particleeffecttype;
	char stain[3];
	qbyte radius;
	vec3_t dlightrgb;
	float dlightradius;
	float dlighttime;
	vec3_t dlightcfade;
} clcustomtents_t;

typedef struct custtentinst_s
{
	struct custtentinst_s *next;
	clcustomtents_t *type;
	int id;
	vec3_t pos;
	vec3_t pos2;
	vec3_t dir;
	int count;
} custtentinst_t;
custtentinst_t *activepcusttents;

void CL_SpawnCustomTEnt(custtentinst_t *info)
{
	clcustomtents_t *t = info->type;
	qboolean failed;
	if (t->netstyle & CTE_ISBEAM)
	{
		if (t->netstyle & (CTE_CUSTOMVELOCITY|CTE_CUSTOMDIRECTION))
		{
			vec3_t org;
			int i, j;
			//FIXME: pvs cull
			if (t->particleeffecttype == -1)
				failed = true;
			else
			{
				failed = false;
				for (i=0 ; i<info->count ; i++)
				{
					for (j=0 ; j<3 ; j++)
					{
						org[j] = info->pos[j] + (info->pos2[j] - info->pos[j])*frandom();
					}

					failed |= P_RunParticleEffectType(org, info->dir, 1, t->particleeffecttype);
				}
			}
		}
		else
			failed = P_ParticleTrail(info->pos, info->pos2, t->particleeffecttype, 0, NULL, NULL);
	}
	else
	{
		if (t->netstyle & (CTE_CUSTOMVELOCITY|CTE_CUSTOMDIRECTION))
			failed = P_RunParticleEffectType(info->pos, info->dir, info->count, t->particleeffecttype);
		else
			failed = P_RunParticleEffectType(info->pos, NULL, info->count, t->particleeffecttype);
	}

	if (failed)
		Con_DPrintf("Failed to create effect %s\n", t->name);

	if (t->netstyle & CTE_STAINS)
	{	//added at pos2 - end of trail
		Surf_AddStain(info->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 (info->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_RunPCustomTEnts(void)
{
	custtentinst_t *ef;
	static float lasttime;
	float since = cl.time - lasttime;
	if (since < 0)
		lasttime = cl.time;
	else if (since < 1/60.0)
		return;
	lasttime = cl.time;
	for (ef = activepcusttents; ef; ef = ef->next)
	{
		CL_SpawnCustomTEnt(ef);
	}
}

clcustomtents_t customtenttype[255];	//network based.

qboolean CL_WriteCustomTEnt(sizebuf_t *buf, int id)
{
	clcustomtents_t *t;
	if ((unsigned)id >= 255u)
		return false;
	t = &customtenttype[id];
	if (*t->name)
	{
		MSG_WriteByte(buf, svcfte_customtempent);
		MSG_WriteByte(buf, 255);
		MSG_WriteByte(buf, id);
		MSG_WriteByte(buf, t->netstyle);
		MSG_WriteString(buf, t->name);

		if (t->netstyle & CTE_STAINS)
		{
			MSG_WriteChar(buf, t->stain[0]);
			MSG_WriteChar(buf, t->stain[1]);
			MSG_WriteChar(buf, t->stain[2]);
			MSG_WriteByte(buf, t->radius);
		}
		if (t->netstyle & CTE_GLOWS)
		{
			MSG_WriteByte(buf, t->dlightrgb[0]*255);
			MSG_WriteByte(buf, t->dlightrgb[1]*255);
			MSG_WriteByte(buf, t->dlightrgb[2]*255);
			MSG_WriteByte(buf, t->dlightradius);
			MSG_WriteByte(buf, t->dlighttime*16);
			if (t->netstyle & CTE_CHANNELFADE)
			{
				MSG_WriteByte(buf, t->dlightcfade[0]*64);
				MSG_WriteByte(buf, t->dlightcfade[1]*64);
				MSG_WriteByte(buf, t->dlightcfade[2]*64);
			}
		}
	}
	return true;
}

void CL_ParseCustomTEnt(void)
{
	char *str;
	clcustomtents_t *t;
	int type = MSG_ReadByte();
	custtentinst_t info;

	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();
		Q_strncpyz(t->name, str, sizeof(t->name));
		t->particleeffecttype = P_FindParticleType(str);
		if (cl_shownet.ival >= 3)
			Con_Printf("\tdefine \"%s\" (%x)\n", t->name, t->netstyle);

		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 (cl_shownet.ival >= 3)
		Con_Printf("\tspawn \"%s\" (%x)\n", t->name, t->netstyle);

	info.type = t;
	if (t->netstyle & CTE_PERSISTANT)
	{
		info.id = MSG_ReadShort();
	}
	else
		info.id = 0;

	if (info.id & 0x8000)
	{
		VectorClear(info.pos);
		VectorClear(info.pos2);
		info.count = 0;
		VectorClear(info.dir);
	}
	else
	{
		MSG_ReadPos (info.pos);

		if (t->netstyle & CTE_ISBEAM)
			MSG_ReadPos (info.pos2);
		else
			VectorCopy(info.pos, info.pos2);

		if (t->netstyle & CTE_CUSTOMCOUNT)
			info.count = MSG_ReadByte();
		else
			info.count = 1;

		if (t->netstyle & CTE_CUSTOMVELOCITY)
		{
			info.dir[0] = MSG_ReadCoord();
			info.dir[1] = MSG_ReadCoord();
			info.dir[2] = MSG_ReadCoord();
		}
		else if (t->netstyle & CTE_CUSTOMDIRECTION)
			MSG_ReadDir (info.dir);
		else
			VectorClear(info.dir);
	}

	if (t->netstyle & CTE_PERSISTANT)
	{
		if (info.id & 0x8000)
		{
			custtentinst_t **link, *o;
			for (link = &activepcusttents; *link; )
			{
				o = *link;
				if (o->id == info.id)
				{
					*link = o->next;
					Z_Free(o);
				}
				else
					link = &(*link)->next;
			}
		}
		else
		{
			//heap fragmentation is going to suck here.
			custtentinst_t *n = Z_Malloc(sizeof(*n));
			info.next = activepcusttents;
			*n = info;
			activepcusttents = n;
		}
	}
	else
		CL_SpawnCustomTEnt(&info);
}
void CL_RefreshCustomTEnts(void)
{
	int i;
	for (i = 0; i < sizeof(customtenttype)/sizeof(customtenttype[0]); i++)
	{
		customtenttype[i].particleeffecttype = (!*customtenttype[i].name)?-1:P_FindParticleType(customtenttype[i].name);

//		if (customtenttype[i].particleeffecttype == P_INVALID && *customtenttype[i].name)
//			Con_DPrintf("%s was not loaded\n", customtenttype[i].name);
	}

	if (cl.particle_ssprecaches)
	{
		for (i = 0; i < MAX_SSPARTICLESPRE; i++)
		{
			if (cl.particle_ssname[i])
				cl.particle_ssprecache[i] = P_FindParticleType(cl.particle_ssname[i]);
			else
				cl.particle_ssprecache[i] = P_INVALID;
		}
	}
	if (cl.particle_csprecaches)
	{
		for (i = 0; i < MAX_CSPARTICLESPRE; i++)
		{
			if (cl.particle_csname[i])
				cl.particle_csprecache[i] = P_FindParticleType(cl.particle_csname[i]);
			else
				cl.particle_csprecache[i] = P_INVALID;
		}
	}
#ifdef CSQC_DAT
	CSQC_ResetTrails();
#endif
}
void CL_ClearCustomTEnts(void)
{
	int i;
	custtentinst_t *p;
	while(activepcusttents)
	{
		p = activepcusttents;
		activepcusttents = p->next;
		Z_Free(p);
	}
	for (i = 0; i < sizeof(customtenttype)/sizeof(customtenttype[0]); i++)
	{
		*customtenttype[i].name = 0;
		customtenttype[i].particleeffecttype = -1;
	}
}

int CL_TranslateParticleFromServer(int qceffect)
{
	if (cl.particle_ssprecaches && qceffect >= 0 && qceffect < MAX_SSPARTICLESPRE)
	{
		/*proper precaches*/
		return cl.particle_ssprecache[qceffect];
	}
	else if (-qceffect >= 0 && -qceffect < MAX_CSPARTICLESPRE)
	{
		qceffect = -qceffect;
		return cl.particle_csprecache[qceffect];
	}
	else
		return P_INVALID;
}

void CL_ParseTrailParticles(void)
{
	int entityindex;
	int effectindex;
	vec3_t start, end;
	trailstate_t **ts;

	entityindex = MSGCL_ReadEntity();
	effectindex = (unsigned short)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();

	effectindex = CL_TranslateParticleFromServer(effectindex);

	if (entityindex && (unsigned int)entityindex < MAX_EDICTS)
		ts = &cl.lerpents[entityindex].trailstate;
	else
		ts = NULL;

	if (P_ParticleTrail(start, end, effectindex, entityindex, NULL, ts))
		P_ParticleTrail(start, end, rt_blood, entityindex, NULL, ts);
}

void CL_ParsePointParticles(qboolean compact)
{
	vec3_t		org, dir;
	unsigned int count, effectindex;

	effectindex = (unsigned short)MSG_ReadShort();
	org[0] = MSG_ReadCoord();
	org[1] = MSG_ReadCoord();
	org[2] = MSG_ReadCoord();
	if (compact)
	{
		dir[0] = dir[1] = dir[2] = 0;
		count = 1;
	}
	else
	{
		dir[0] = MSG_ReadCoord();
		dir[1] = MSG_ReadCoord();
		dir[2] = MSG_ReadCoord();
		count = (unsigned short)MSG_ReadShort();
	}

	effectindex = CL_TranslateParticleFromServer(effectindex);

	if (P_RunParticleEffectType(org, dir, count, effectindex))
		P_RunParticleEffect (org, dir, 15, 15);
}

void CLNQ_ParseParticleEffect (void)
{
	vec3_t		org, dir;
	int			i, 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)
	{
		// treat as spriteless explosion (qtest/some mods require this)
		if (P_RunParticleEffectType(org, NULL, 1, pt_explosion))
			P_RunParticleEffect(org, NULL, 107, 1024); // should be 97-111
	}
	else
		P_RunParticleEffect (org, dir, color, msgcount);
}
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, vec3_t dir, vec3_t orientationup, model_t *model, int startframe, int framecount, float framerate, float alpha, float scale, float randspin, float gravity, int traileffect, unsigned int renderflags, int skinnum)
{
	explosion_t	*ex;

	ex = CL_AllocExplosion (org);
	ex->start = cl.time;
	ex->model = model;
	ex->firstframe = startframe;
	ex->numframes = framecount;
	ex->framerate = framerate;
	ex->skinnum = skinnum;
	ex->traileffect = traileffect;
	ex->scale = scale;

	ex->flags |= renderflags;

	//sprites always use a fixed alpha. models can too if the alpha is < 0
	if (model->type == mod_sprite || alpha < 0)
		ex->endalpha = fabs(alpha);
	ex->startalpha = fabs(alpha);

	if (ex->endalpha < 1 || ex->startalpha < 1)
		ex->flags |= RF_TRANSLUCENT;

	if (randspin)
	{
		ex->angles[0] = frandom()*360;
		ex->angles[1] = frandom()*360;
		ex->angles[2] = frandom()*360;

		ex->avel[0] = crandom()*randspin;
		ex->avel[1] = crandom()*randspin;
		ex->avel[2] = crandom()*randspin;
	}
	ex->gravity = gravity;

	if (orientationup)
	{
		ex->angles[0] = acos(orientationup[2])/M_PI*180;
		if (orientationup[0])
			ex->angles[1] = atan2(orientationup[1], orientationup[0])/M_PI*180;
		else if (orientationup[1] > 0)
			ex->angles[1] = 90;
		else if (orientationup[1] < 0)
			ex->angles[1] = 270;
		else
			ex->angles[1] = 0;
		ex->angles[0]*=r_meshpitch.value;
	}


	if (dir)
	{
//		vec3_t spos;
//		float dlen;
//		dlen = -10/VectorLength(dir);
//		VectorMA(ex->origin, dlen, dir, spos);
//		TraceLineN(spos, org, ex->origin, NULL);

		VectorCopy(dir, ex->velocity);
	}
	else
		VectorClear(ex->velocity);
}

// [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;
	model_t *mod;

	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();

	mod = cl.model_precache[modelindex];
	CL_SpawnSpriteEffect(org, NULL, NULL, mod, startframe, framecount, framerate, mod->type==mod_sprite?-1:1, 1, 0, 0, P_INVALID, 0, 0);
}

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

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

	ex = CL_AllocExplosion (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, MLV_WARN);
}

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

void CLQ2_ParseSteam(void)
{
	vec3_t pos, dir;
	/*qbyte colour;
	short magnitude;
	unsigned int duration;*/
	signed int id = MSG_ReadShort();
	/*qbyte count =*/ MSG_ReadByte();
	MSG_ReadPos(pos);
	MSG_ReadDir(dir);
	/*colour =*/ MSG_ReadByte();
	/*magnitude =*/ MSG_ReadShort();

	if (id != -1)
		/*duration =*/ MSG_ReadLong();
	else
		/*duration = 0;*/

	Con_Printf("FIXME: CLQ2_ParseSteam: stub\n");
}

void CLQ2_ParseTEnt (void)
{
	beam_t *b;
	q2particleeffects_t		type;
	int		pt;
	vec3_t	pos, pos2, dir;
	int		cnt;
	int		color;
	int		r;
	int		ent;
//	int		magnitude;
	explosion_t	*ex;

	type = MSG_ReadByte ();

	if (type <= Q2TE_MAX)
		pt = pt_q2[type];
	else
		pt = P_INVALID;
	switch (type)
	{
	case Q2TE_GUNSHOT:	//grey tall thing with smoke+sparks
	case Q2TE_BLOOD:		//red tall thing
	case Q2TE_SPARKS:		//orange tall thing (with not many particles)
	case Q2TE_BLASTER:	//regular blaster
	case Q2TE_SHOTGUN:	//gunshot with less particles
	case Q2TE_SCREEN_SPARKS://green+grey tall
	case Q2TE_SHIELD_SPARKS://blue+grey tall
	case Q2TE_BULLET_SPARKS://orange+grey tall+smoke
	case Q2TE_GREENBLOOD:	//yellow...
	case Q2TE_BLASTER2:	//green version of te_blaster
	case Q2TE_MOREBLOOD:	//te_blood*2
	case Q2TE_HEATBEAM_SPARKS://white outwards puffs
	case Q2TE_HEATBEAM_STEAM://orange outwards puffs
	case Q2TE_ELECTRIC_SPARKS://blue tall
	case Q2TE_FLECHETTE:	//grey version of te_blaster
		MSG_ReadPos (pos);
		MSG_ReadDir (dir);
		P_RunParticleEffectType(pos, dir, 1, pt);
		break;
	case Q2TE_BFG_LASER:
		MSG_ReadPos (pos);
		MSG_ReadPos (pos2);
		CL_Laser(pos, pos2, 0xd0d1d2d3);
		break;
	case Q2TE_RAILTRAIL:	//blue spiral, grey particles
	case Q2TE_BUBBLETRAIL:	//grey sparse trail, slow riser
//	case Q2TE_BFG_LASER:	//green laser
	case Q2TE_DEBUGTRAIL:	//long lived blue trail
	case Q2TE_BUBBLETRAIL2:	//grey rising trail
	case Q2TE_BLUEHYPERBLASTER:	//TE_BLASTER without model+light
		MSG_ReadPos (pos);
		MSG_ReadPos (pos2);
		P_ParticleTrail(pos, pos2, pt, 0, NULL, NULL);
		break;
	case Q2TE_EXPLOSION1:	//column
	case Q2TE_EXPLOSION2:	//splits
	case Q2TE_ROCKET_EXPLOSION://top blob/column
	case Q2TE_GRENADE_EXPLOSION://indistinguishable from TE_EXPLOSION2
	case Q2TE_ROCKET_EXPLOSION_WATER://rocket but with different sound
	case Q2TE_GRENADE_EXPLOSION_WATER://different sound
	case Q2TE_BFG_EXPLOSION://green light+sprite
	case Q2TE_BFG_BIGEXPLOSION://green+white fast particles
	case Q2TE_BOSSTPORT://splitting+merging+upwards particles.
	case Q2TE_PLASMA_EXPLOSION://looks like rocket explosion to me
	case Q2TE_PLAIN_EXPLOSION://looks like rocket explosion to me
	case Q2TE_CHAINFIST_SMOKE://small smoke
	case Q2TE_TRACKER_EXPLOSION://black light, slow particles
	case Q2TE_TELEPORT_EFFECT://q1-style teleport
	case Q2TE_DBALL_GOAL://q1-style teleport
	case Q2TE_NUKEBLAST://dome expansion (blue/white particles)
	case Q2TE_WIDOWSPLASH://dome (orange+gravity)
	case Q2TE_EXPLOSION1_BIG://buggy model
	case Q2TE_EXPLOSION1_NP://looks like a rocket explosion to me
		MSG_ReadPos (pos);
		P_RunParticleEffectType(pos, NULL, 1, pt);
		break;
	case Q2TE_SPLASH:
		cnt = MSG_ReadByte ();
		MSG_ReadPos (pos);
		MSG_ReadDir (dir);
		r = MSG_ReadByte () + Q2SPLASH_UNKNOWN;
		if (r > Q2SPLASH_MAX)
			r = Q2SPLASH_UNKNOWN;
		pt = pt_q2[r];
		P_RunParticleEffectType(pos, NULL, 1, pt);
		break;

	case Q2TE_PARASITE_ATTACK:
	case Q2TE_MEDIC_CABLE_ATTACK:
		CL_ParseBeam (BT_Q2PARASITE);
		break;
	case Q2TE_HEATBEAM:
		b = CL_ParseBeam (BT_Q2HEATBEAM);	//2, 7, -3
		if (b)
		{
			b->bflags |= STREAM_ATTACHED;
			VectorSet(b->offset, 2, 7, -3);
		}
		break;
	case Q2TE_MONSTER_HEATBEAM:
		b = CL_ParseBeam (BT_Q2HEATBEAM);
		if (b)
			b->bflags |= STREAM_ATTACHED;
		break;
	case Q2TE_GRAPPLE_CABLE:
		CL_ParseBeamOffset (BT_Q2GRAPPLE);
		break;

	case Q2TE_LIGHTNING:
		ent = MSGCL_ReadEntity ();
		/*toent =*/ MSGCL_ReadEntity ();	//ident only.
		pos[0] = MSG_ReadCoord ();
		pos[1] = MSG_ReadCoord ();
		pos[2] = MSG_ReadCoord ();
		pos2[0] = MSG_ReadCoord ();
		pos2[1] = MSG_ReadCoord ();
		pos2[2] = MSG_ReadCoord ();
		CL_AddBeam(BT_Q2LIGHTNING, ent, pos, pos2);
		break;

	case Q2TE_LASER_SPARKS:
	case Q2TE_WELDING_SPARKS:
	case Q2TE_TUNNEL_SPARKS:
		cnt = MSG_ReadByte ();
		MSG_ReadPos (pos);
		MSG_ReadDir (dir);
		color = MSG_ReadByte ();
		P_RunParticleEffectPalette(va("q2part.%s", q2efnames[type]), pos, dir, color, cnt);
		break;

	case Q2TE_STEAM:
		CLQ2_ParseSteam();
		break;

	case Q2TE_FORCEWALL:
		MSG_ReadPos (pos);
		MSG_ReadPos (pos2);
		color = MSG_ReadByte ();
		P_ParticleTrailIndex(pos, pos2, pt, color, 0, NULL);
		break;

	case Q2TE_FLASHLIGHT:	//white 400-radius dlight
		MSG_ReadPos(pos);
		ent = MSG_ReadShort();
		P_ParticleTrail(pos, pos, pt, ent, NULL, NULL);
		break;
	case Q2TE_WIDOWBEAMOUT:		/*requires state tracking to keep it splurting constantly for 2.1 secs*/
		ent = MSG_ReadShort();
		MSG_ReadPos(pos);
		Con_Printf("FIXME: Q2TE_WIDOWBEAMOUT not implemented\n");
		break;

	case Q2TE_RAILTRAIL2:		/*not implemented in vanilla*/
	case Q2TE_FLAME:			/*not implemented in vanilla*/
		Host_EndGame ("CLQ2_ParseTEnt: bad/non-implemented type %i", type);
		break;




	//My old attempt at running AlienArena years ago. probably not enough now. Other engines will have other effects.
	case CRTE_LEADERBLASTER:
		Host_EndGame ("CLQ2_ParseTEnt: bad/non-implemented type %i", type);
	case CRTE_BLASTER_MUZZLEFLASH:
		MSG_ReadPos (pos);
		ex = CL_AllocExplosion (pos);
		ex->flags = Q2RF_FULLBRIGHT|RF_NOSHADOW;
		ex->start = cl.q2frame.servertime - 100;
		CL_NewDlight(0, pos, 350, 0.5, 0.2*5, 0.1*5, 0*5);
		P_RunParticleEffectTypeString(pos, NULL, 1, "te_muzzleflash");
		break;
	case CRTE_BLUE_MUZZLEFLASH:
		MSG_ReadPos (pos);
		ex = CL_AllocExplosion (pos);
		ex->flags = Q2RF_FULLBRIGHT|RF_NOSHADOW;
		ex->start = cl.q2frame.servertime - 100;
		CL_NewDlight(0, pos, 350, 0.5, 0.2*5, 0.1*5, 0*5);
		P_RunParticleEffectTypeString(pos, NULL, 1, "te_blue_muzzleflash");
		break;
	case CRTE_SMART_MUZZLEFLASH:
		MSG_ReadPos (pos);
		ex = CL_AllocExplosion (pos);
		ex->flags = Q2RF_FULLBRIGHT|RF_NOSHADOW;
		ex->start = cl.q2frame.servertime - 100;
		CL_NewDlight(0, pos, 350, 0.5, 0.2*5, 0*5, 0.2*5);
		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 (pos);
		VectorCopy (pos, ex->origin);
		ex->flags = Q2RF_FULLBRIGHT|RF_NOSHADOW;
		ex->start = cl.q2frame.servertime - 100;
		CL_NewDlight(0, pos, 350, 0.5, 0.2*5, 0*5, 0.2*5);
		P_RunParticleEffectTypeString(pos, NULL, 1, "te_deathfield");
		break;
	case CRTE_BLASTERBEAM:
		MSG_ReadPos (pos);
		MSG_ReadPos (pos2);
		P_ParticleTrail(pos, pos2, P_FindParticleType("q2part.TR_BLASTERTRAIL2"), 0, NULL, NULL);
		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);
		break;
	}
}
#endif


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

	if (cl_numvisedicts == cl_maxvisedicts)
		return NULL;
	ent = &cl_visedicts[cl_numvisedicts];
	cl_numvisedicts++;
	ent->keynum = 0;

	memset (ent, 0, sizeof(*ent));
	ent->playerindex = -1;
	ent->topcolour = TOP_DEFAULT;
	ent->bottomcolour = BOTTOM_DEFAULT;

#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;
}

void CSQC_GetEntityOrigin(unsigned int csqcent, float *out);

/*
=================
CL_UpdateBeams
=================
*/
void CL_UpdateBeams (void)
{
	int bnum;
	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;
	tentmodels_t *type;

	extern cvar_t cl_truelightning, v_viewheight;

// update lightning
	for (bnum=0, b=cl_beams; bnum < beams_running; bnum++, b++)
	{
		type = b->info;
		if (!type)
			continue;

		if (b->endtime < cl.time)
		{
			if (!cl.paused)
			{	/*don't let lightning decay while paused*/
				P_DelinkTrailstate(&b->trailstate);
				P_DelinkTrailstate(&b->emitstate);
				b->info = NULL;
				continue;
			}
		}

		lastrunningbeam = bnum;

	// if coming from the player, update the start position
		if ((b->bflags & 1) && b->entity > 0 && b->entity <= cl.allocated_client_slots)
		{
			for (j = 0; j < cl.splitclients; j++)
			{
				playerview_t *pv = &cl.playerview[j];
				if (b->entity == pv->viewentity)
				{
//					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.inframes[cl.parsecount&UPDATE_MASK].playerstate[b->entity-1];
//					if (pl->messagenum == cl.parsecount || cls.protocol == CP_NETQUAKE)
					{
						vec3_t	fwd, org, ang, viewang;
						float	delta, f, len;

//						if (cl.spectator && pv->cam_auto)
//						{	//if we're tracking someone, use their origin explicitly.
//							vieworg = pl->origin;
//						}
//						else
#ifdef Q2CLIENT
						if (cls.protocol == CP_QUAKE2)
							vieworg = pv->predicted_origin;
						else
#endif
							vieworg = pv->simorg;

						if (cl_truelightning.ival > 1 && cl.movesequence > cl_truelightning.ival)
						{
							outframe_t *frame = &cl.outframes[(cl.movesequence-cl_truelightning.ival)&UPDATE_MASK];
							viewang[0] = SHORT2ANGLE(frame->cmd[j].angles[0]);
							viewang[1] = SHORT2ANGLE(frame->cmd[j].angles[1]);
							viewang[2] = SHORT2ANGLE(frame->cmd[j].angles[2]);
						}
						else
							VectorCopy(pv->simangles, viewang);

						VectorCopy (vieworg, b->start);
						b->start[2] += pv->crouch + 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
						VectorAngles (org, NULL, ang, false);

						// lerp pitch
						if (ang[0] < -180)
							ang[0] += 360;
						ang[0] += (viewang[0] - ang[0]) * f;

						// lerp yaw
						delta = anglemod(viewang[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 (vieworg, org);
						org[2] += 16;
						VectorAdd (org, fwd, b->end);

						if (cl_beam_trace.ival)
						{
							vec3_t normal;
							VectorMA(org, len+4, ang, fwd);
							if (CL_TraceLine(org, fwd, ang, normal, NULL) < 1)
								VectorCopy (ang, b->end);
						}
						break;
					}
				}
			}
			VectorCopy (b->start, org);
		}
#ifdef CSQC_DAT
		else if ((b->bflags & 1) && b->entity > MAX_EDICTS)
		{
			CSQC_GetEntityOrigin(b->entity-MAX_EDICTS, org);
			VectorCopy (b->start, org);
		}
#endif
		else if (b->bflags & STREAM_ATTACHED)
		{
			player_state_t	*pl;
			st = CL_FindPacketEntity(b->entity);
			if (st)
			{
				VectorCopy(st->origin, b->start);
			}
			else if (b->entity <= cl.allocated_client_slots && b->entity > 0)
			{
				pl = &cl.inframes[cl.parsecount&UPDATE_MASK].playerstate[b->entity-1];
				VectorCopy(pl->origin, b->start);
			}
	
			VectorCopy (b->start, org);
		}
		else
			VectorCopy (b->start, org);
		VectorAdd(org, 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
		{
			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 (ruleset_allow_particle_lightning.ival || !type->modelname)
			if (type->ef_beam >= 0 && !P_ParticleTrail(org, b->end, type->ef_beam, b->entity, NULL, &b->trailstate))
				continue;
		if (!type->model)
		{
			type->model = type->modelname?Mod_ForName(type->modelname, MLV_WARN):NULL;
			if (!type->model)
				continue;
		}

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

		if(b->bflags & 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 = type->model;
#ifdef HEXEN2
			ent->drawflags |= MLS_ABSLIGHT;
			ent->abslight = 64 + 128 * bound(0, cl_shaftlight.value, 1);
#endif
			ent->shaderRGBAf[3] = b->alpha;
			ent->flags = b->rflags;

			ent->angles[0] = -pitch;
			ent->angles[1] = yaw;
			ent->angles[2] = rand()%360;
			AngleVectors(ent->angles, ent->axis[0], ent->axis[1], ent->axis[2]);
			ent->angles[0] = pitch;
			ent->framestate.g[FS_REG].lerpweight[0] = 1;
			ent->framestate.g[FS_REG].frame[0] = 0;
			ent->framestate.g[FS_REG].frametime[0] = cl.time - (b->endtime - 0.2);

			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;
	vec3_t pos, norm;
	static float oldtime;
	float		scale;
	float frametime = cl.time - oldtime;
	if (frametime < 0 || frametime > 100)
		frametime = 0;
	oldtime = cl.time;

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

		lastrunningexplosion = i;
		if (ex->model)
		{
			if (ex->model->loadstate == MLS_LOADING)
				continue;
			if (ex->model->loadstate != MLS_LOADED)
			{
				ex->model = NULL;
				ex->flags = 0;
				P_DelinkTrailstate(&(ex->trailstate));
				continue;
			}
		}

		f = ex->framerate*(cl.time - ex->start);

		if (ex->firstframe >= 0)
		{
			firstframe = ex->firstframe;
			numframes = ex->numframes;

			if (!numframes)
				numframes = ex->model->numframes - firstframe;
		}
		else
		{
			firstframe = 0;
			numframes = ex->model->numframes;
		}

		of = (int)f-1;
		if (ex->endalpha && (int)f == numframes)
		{
			scale = 1-(f-(int)f);	//if we have endalpha not 0, then there is a final 'bonus' frame where the model scales down to 0. use numframes+framerate to control how fast it fades.
			f = of;	//clamp it to the old frame
		}
		else if ((int)f >= numframes || (int)f < 0)
		{
			ex->model = NULL;
			ex->flags = 0;
			P_DelinkTrailstate(&(ex->trailstate));
			continue;
		}
		else
			scale = 1;
		if (of < 0)
			of = 0;

		ent = CL_NewTempEntity ();
		if (!ent)
			return;

		if (ex->gravity)
		{
			VectorMA(ex->origin, frametime, ex->velocity, pos);
			if (ex->velocity[0] || ex->velocity[1] || ex->velocity[2])
			{
				VectorClear(norm);
				if (CL_TraceLine(ex->origin, pos, ent->origin, norm, NULL) < 1)
				{
					float sc = DotProduct(ex->velocity, norm) * -1.5;
					VectorMA(ex->velocity, sc, norm, ex->velocity);
					VectorScale(ex->velocity, 0.9, ex->velocity);
					if (norm[2] > 0.7 && DotProduct(ex->velocity, ex->velocity) < 100)
					{
						VectorClear(ex->velocity);
						VectorClear(ex->avel);
					}
				}
				else
					ex->velocity[2] -= ex->gravity * frametime;
				VectorCopy(ent->origin, ex->origin);
			}
			else
				VectorCopy(ex->origin, ent->origin);
		}
		else
		{
			VectorMA (ex->origin, f, ex->velocity, ent->origin);
		}
		VectorMA(ex->angles, frametime, ex->avel, ex->angles);

		VectorCopy (ex->oldorigin, ent->oldorigin);
		VectorCopy (ex->angles, ent->angles);
		ent->skinnum = ex->skinnum;
		ent->angles[0]*=r_meshpitch.value;
		AngleVectors(ent->angles, ent->axis[0], ent->axis[1], ent->axis[2]);
		VectorInverse(ent->axis[1]);
		ent->model = ex->model;
		ent->framestate.g[FS_REG].frame[1] = (int)f+firstframe;
		ent->framestate.g[FS_REG].frame[0] = of+firstframe;
		ent->framestate.g[FS_REG].lerpweight[1] = (f - (int)f);
		ent->framestate.g[FS_REG].lerpweight[0] = 1-ent->framestate.g[FS_REG].lerpweight[1];
		ent->shaderRGBAf[3] = (1.0 - f/(numframes))*(ex->startalpha-ex->endalpha) + ex->endalpha;
		ent->flags = ex->flags;
		ent->scale = scale*ex->scale;
#ifdef HEXEN2
		ent->drawflags = SCALE_ORIGIN_ORIGIN;
#endif

		if (ex->traileffect != P_INVALID)
			pe->ParticleTrail(ent->oldorigin, ent->origin, ex->traileffect, 0, ent->axis, &(ex->trailstate));
		if (!(ex->flags & Q2RF_BEAM))
			VectorCopy(ent->origin, ex->oldorigin);	//don't corrupt q2 beams
		if (ex->flags & Q2RF_BEAM)
		{
			ent->rtype = RT_BEAM;
			ent->shaderRGBAf[0] = ((d_8to24rgbtable[ex->skinnum & 0xFF] >>  0) & 0xFF)/255.0;
			ent->shaderRGBAf[1] = ((d_8to24rgbtable[ex->skinnum & 0xFF] >>  8) & 0xFF)/255.0;
			ent->shaderRGBAf[2] = ((d_8to24rgbtable[ex->skinnum & 0xFF] >> 16) & 0xFF)/255.0;
		}
		else if (ex->skinnum < 0)
		{
			ent->skinnum = 7*f/(numframes);
		}
	}

	explosions_running = lastrunningexplosion + 1;
}

entity_state_t *CL_FindPacketEntity(int num);

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