//-----------------------------------------------------------------------------
//
//  $Logfile:: /Source/cl_tent.c                                              $
// $Revision:: 4                                                              $
//   $Author:: Burgermac                                                      $
//     $Date:: 8/8/01 7:37 PM                                                 $
//
// Copyright (C) 1998 by Ritual Entertainment, Inc.
// All rights reserved.
//
// This source is may not be distributed and/or modified without
// expressly written permission by Ritual Entertainment, Inc.
//
// DESCRIPTION:
// client side temporary entities
//

#include "client.h"

#define	MAX_BEAMS	32
typedef struct
{
	int		entity;
	struct	model_t	*model;
	int		endtime;
	vec3_t	start, end;
	vec3_t	current_start, current_end;
	vec3_t	prev_start, prev_end;
	float	alpha;
	float	scale;
	int		anim;
	int		flags;
	int		num_frames;
	int		frame_start[MAX_BEAMS];
	int		oldframe[MAX_BEAMS];
	int		prevframe[MAX_BEAMS];
	int		time;
	int		parent;
	float	max_offset;
	float	min_offset;
	int		overlap;
} beam_t;
beam_t		cl_beams[MAX_BEAMS];

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

struct model_t	*cl_shockwave;
struct model_t	*cl_explosion1;
struct model_t	*cl_explosion2;
struct model_t	*cl_smokering;
struct model_t	*cl_bullethole;
struct model_t	*cl_lightning;
struct model_t	*cl_pulsesphere;
struct model_t	*cl_pulseexplode1;
struct model_t	*cl_pulseexplode2;

/*
=================
CL_RegisterTEntSounds
=================
*/
void CL_RegisterTEntSounds (void)
{
}

/*
=================
CL_RegisterTEntModels
=================
*/
void CL_RegisterTEntModels (void)
{
	cl_shockwave  = re.RegisterModel( "sprites/shockwave.spr" );
	cl_explosion1 = re.RegisterModel( "models/sphere.def" );
	cl_explosion2 = re.RegisterModel( "sprites/explode.spr" );
	cl_smokering  = re.RegisterModel( "sprites/smokering.spr" );
	cl_bullethole = re.RegisterModel( "sprites/bullethole.spr" );
	cl_lightning  = re.RegisterModel( "models/lightning.def" );
	cl_pulsesphere = re.RegisterModel( "models/sphere2.def" );
	cl_pulseexplode1 = re.RegisterModel( "sprites/pulseexplode1.spr" );
	cl_pulseexplode2 = re.RegisterModel( "sprites/pulseexplode2.spr" );
}

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

/*
=================
CL_ParseParticles
=================
*/
#if 0
static 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);
}
#endif

void CL_CreateBeam (vec3_t start,vec3_t dir,centity_t *owner,struct model_t *model,float alpha,float scale,int flags,float length,float life,qboolean create,qboolean endpoint,vec3_t endpointvec,int min_offset,int max_offset,int overlap)
{
	int i;
	beam_t *b;
	vec3_t end;
	trace_t trace;

	if ( endpointvec )
		VectorCopy( endpointvec, end );
	else
	{
		VectorMA( start, length, dir, end );
		trace = CL_GenericTrace( start, vec3_origin, vec3_origin, end, MASK_SHOT, true, owner->current.number );
		VectorCopy( trace.endpos, end );
	}

	if ( !create )
	{
		// Search for this beam already being used
		for (i=0, b=cl_beams; i< MAX_BEAMS; i++, b++)
			if (b->entity == owner->current.number)
			{
				b->endtime  = cl.time + life;
				b->model    = model;
				b->alpha    = alpha;
				b->scale    = scale;
				b->anim     = 0;
				b->flags    = flags;
				b->time     = cl.time;
				b->overlap  = overlap;
				b->min_offset = min_offset;
				b->max_offset = max_offset;
				VectorCopy ( b->current_start, b->prev_start );
				VectorCopy ( start, b->current_start );
				VectorCopy ( b->current_end, b->prev_end );
				VectorCopy ( end, b->current_end );
				return;
			}
	}

	// find a free beam
	for (i=0, b=cl_beams; i< MAX_BEAMS; i++, b++)
	{
		if (!b->model || b->endtime < cl.time)
		{
			b->entity      = owner->current.number;
			b->endtime     = cl.time + life;
			b->model       = model;
			b->alpha       = alpha;
			b->scale       = scale;
			b->anim        = 0;
			b->flags       = flags;
			b->time        = cl.time;
			b->overlap     = overlap;
			b->min_offset  = min_offset;
			b->max_offset  = max_offset;
			VectorCopy ( start, b->current_start );
			VectorCopy ( end, b->current_end );
			VectorCopy ( start, b->prev_start );
			VectorCopy ( end, b->prev_end );
			return;
		}
	}

	Com_Printf ( "beam list overflow!\n" );
	return;
}

/*
=================
CL_ParseBeam
=================
*/
static void CL_ParseBeam ( void )
{
	vec3_t	start={0,0,0}, end = {0,0,0};
	beam_t	*b;
	int		i;
	int		modelindex;
	int		sendflags;
	float	alpha = 1.0f;
	int		anim  = 0;
	int		flags = 0;
	float	life  = 30;

	sendflags = MSG_ReadByte( &net_message );

	MSG_ReadPos( &net_message, start );
	MSG_ReadPos( &net_message, end );
	modelindex = MSG_ReadShort( &net_message );

	if ( sendflags & BM_ALPHA )
		alpha = MSG_ReadByte( &net_message ) / 255.0f;
	if ( sendflags & BM_FLAGS )
		flags = MSG_ReadByte( &net_message );
	if ( sendflags & BM_LIFE )
		life = MSG_ReadShort( &net_message );

	// find a free beam
	for (i=0, b=cl_beams; i< MAX_BEAMS; i++, b++)
	{
		if (!b->model || b->endtime < cl.time)
		{
			b->endtime     = cl.time + life;
			b->model       = cl.model_draw[modelindex];
			b->alpha       = alpha;
			b->anim        = anim;
			b->flags       = flags;
			b->time        = cl.time;
			VectorCopy ( start, b->current_start );
			VectorCopy ( end, b->current_end );
			VectorCopy ( start, b->prev_start );
			VectorCopy ( end, b->prev_end );
			return;
		}
	}

	Com_Printf ( "beam list overflow!\n" );
	return;
}

/*
=================
CL_ChipSurface
=================
*/
static void CL_ChipSurface (int min_size,int max_size,int thickness,int power,float percentage,vec3_t origin,vec3_t dir,qboolean fullsurface,int tess_type)
{
	vec3_t end;
	vec3_t negdir;
	trace_t trace;

	VectorNegate( dir, negdir );
	VectorMA( origin, 64, dir, end );
	trace = CL_GenericTrace( origin, vec3_origin, vec3_origin, end, MASK_PLAYERSOLID, false, 0 );
	// Make sure there is a surface here.
	if ( !trace.surface )
		return;

	if (fullsurface)
		CL_TriangleEffect( (int)(intptr_t)trace.ent, min_size, max_size, negdir, power, percentage, thickness, vec3_origin, trace.surface->groupname, tess_type, 125 );
	else
		CL_TriangleEffect( (int)(intptr_t)trace.ent, min_size, max_size, negdir, power, percentage, thickness, origin, trace.surface->groupname, tess_type, 125 );
}

/*
==============
CL_BulletSpray
==============
*/
static void CL_BulletSpray (vec3_t pos,vec3_t dir,vec3_t right,vec3_t up,int count,int lightstyle)
{
	trace_t trace;
	vec3_t end;

	VectorMA( pos, 8192, dir, end );
	trace = CL_WorldTrace( pos, vec3_origin, vec3_origin, end, MASK_SHOT );
	if ( !trace.surface )
		return;

	CL_SinParticleEffect ( trace.endpos, trace.plane.normal, lightstyle, 4, false, 0 );
}

/*
=================
CL_ParseTEnt
=================
*/
void CL_ParseTEnt( void )
{
	int type;
	vec3_t pos, dir;
	int count;
	int surftype;
	int lightstyle;
	byte flags=0;

	type = MSG_ReadByte ( &net_message );

	switch (type)
	{
	case TE_PARTICLES: // general particles
		MSG_ReadPos ( &net_message, pos );
		MSG_ReadDir ( &net_message, dir );
		lightstyle = MSG_ReadByte( &net_message );
		count = MSG_ReadByte( &net_message );
		flags = MSG_ReadByte( &net_message );
		CL_SinParticleEffect ( pos, dir, lightstyle, count, false, flags );
		break;

	case TE_RANDOM_PARTICLES: // general particles (randomly selected from lightstyle)
		MSG_ReadPos ( &net_message, pos );
		MSG_ReadDir ( &net_message, dir );
		lightstyle = MSG_ReadByte( &net_message );
		count = MSG_ReadByte( &net_message );
		flags = MSG_ReadByte( &net_message );
		CL_SinParticleEffect2 ( pos, dir, lightstyle, count, false, flags );
		break;

	case TE_ROCKET_EXPLOSION:
		MSG_ReadPos ( &net_message, pos );
		TempExplosion( pos, 1.0f );
		CL_ExplosionParticles ( pos,123,1 );
		break;

	case TE_SCALED_EXPLOSION:
		MSG_ReadPos ( &net_message, pos );
		// get scale
		count = MSG_ReadByte( &net_message );
		TempExplosion( pos, (float)count / 64.0f );
		CL_ExplosionParticles ( pos,123, (float)count/64.0f );
		break;

	case TE_PULSE_EXPLOSION:
		MSG_ReadPos ( &net_message, pos );
		// get scale
		TempPulseExplosion( pos );
		break;

	case TE_GUNSHOT: // bullet hitting wall
	{
		vec3_t end;
		vec3_t negdir;
		trace_t trace;
		float timeofs;

		MSG_ReadPos ( &net_message, pos );
		MSG_ReadDir ( &net_message, dir );
		timeofs = ( float )MSG_ReadByte( &net_message ) / 0xf;
		VectorNegate( dir, negdir );
		VectorMA( pos, 32, negdir, end );
		trace = CL_GenericTrace( pos, vec3_origin, vec3_origin, end, MASK_SHOT, true, -1 );
		// Do the effect
		CL_BulletEffect( trace, timeofs );
	}
	break;

	case TE_DAMAGE_WALL:
	{
		vec3_t negdir;
		MSG_ReadPos ( &net_message, pos );
		MSG_ReadDir ( &net_message, dir );
		lightstyle = MSG_ReadByte( &net_message );
		CL_SinParticleEffect ( pos, dir, lightstyle, 4, false, 0 );
		VectorNegate( dir, negdir );
		CL_ChipSurface ( MIN_TRIANGLE_SIZE,
		                 MAX_TRIANGLE_SIZE,
		                 TRIANGLE_THICKNESS,
		                 TRIANGLE_POWER,
		                 TRIANGLE_PERCENTAGE,
		                 pos,
		                 negdir,
		                 false,
		                 TESSELATE_FALLANDTRAIL );
	}
	break;

	case TE_LASER:
		Com_Error ( ERR_DROP, "CL_ParseTEnt: TE_LASER no longer supported.\n" );
		break;

	case TE_TESSELATE:
	{
		int sendflags, min_size, max_size, thickness, entnum;
		vec3_t origin, dir;
		float power, percentage;
		int type;

		sendflags = MSG_ReadShort( &net_message );

		if (sendflags & TESS_ORIGIN)
			MSG_ReadPos( &net_message, origin );
		else
			VectorCopy( vec3_origin, origin );

		if (sendflags & TESS_DIR)
			MSG_ReadDir( &net_message, dir );
		else
			VectorCopy( vec3_origin, dir );

		if (sendflags & TESS_ENTNUM)
			entnum = MSG_ReadShort( &net_message );
		else
			entnum = 0;

		if (sendflags & TESS_MINSIZE)
			min_size = MSG_ReadByte( &net_message );
		else
			min_size = TESS_DEFAULT_MIN_SIZE;

		if (sendflags & TESS_MAXSIZE)
			max_size = MSG_ReadByte( &net_message );
		else
			max_size = TESS_DEFAULT_MAX_SIZE;

		if (sendflags & TESS_POWER)
			power = MSG_ReadByte( &net_message );
		else
			power = TESS_DEFAULT_POWER;

		if (sendflags & TESS_PERCENT)
			percentage = MSG_ReadByte( &net_message );
		else
			percentage = TESS_DEFAULT_PERCENT * 255.0f;

		if (sendflags & TESS_THICK)
			thickness = MSG_ReadByte( &net_message );
		else
			thickness = min_size;

		if (sendflags & TESS_LIGHTSTYLE)
			lightstyle = MSG_ReadByte( &net_message );
		else
			lightstyle = TESS_DEFAULT_LIGHTSTYLE;

		if (sendflags & TESS_TYPE)
			type = MSG_ReadByte( &net_message );
		else
			type = TESSELATE_FALL;

		if ( entnum )
		{
			CL_TriangleEffect( entnum, min_size, max_size, dir, power, percentage, thickness, origin, NULL, type, lightstyle );
		}
		else
		{
			CL_ChipSurface ( min_size, max_size, thickness, power, percentage, origin, dir, true, 0 );
		}
	}
	break;

	case TE_BURNWALL: // burn the wall
		MSG_ReadPos ( &net_message, pos );
		MSG_ReadPos ( &net_message, dir );
		count = (signed char)MSG_ReadByte( &net_message );
		VectorSubtract( dir, pos, dir );
		VectorNormalize( dir );
		re.BurnLightmap( pos, dir, count );
		break;

	case TE_TEMPMODEL: // temporary model
	{
		entity_t ent;
		/*int owner;*/
		int modelindex;
		int flags;
		float life;
		int sendflags;

		memset( &ent, 0, sizeof( entity_t ) );
		ent.scale = 1.0f;
		ent.alpha = 1.0f;

		sendflags = MSG_ReadByte( &net_message );
		//
		// read the stuff in
		//
		if (sendflags & TM_ORIGIN)
			MSG_ReadPos( &net_message, ent.origin );
		if (sendflags & TM_ANGLES)
			MSG_ReadPos( &net_message, ent.angles );
		if (sendflags & TM_ANIM)
			ent.anim = MSG_ReadByte( &net_message );
		if (sendflags & TM_SCALE)
			ent.scale = (float)MSG_ReadShort( &net_message ) / 256.0f;
		if (sendflags & TM_ALPHA)
			ent.alpha = (float)MSG_ReadByte( &net_message ) / 255.0f;
		if (sendflags & TM_FLAGS)
			flags = MSG_ReadByte( &net_message );
		else	flags = 0;
		if (sendflags & TM_OWNER)
			/*owner =*/ MSG_ReadShort( &net_message );
		if (sendflags & TM_LIFE)
			life = (float)MSG_ReadShort( &net_message ) / 1000.0f;
		else	life = 0;
		modelindex = MSG_ReadShort( &net_message );
		ent.model = cl.model_draw[modelindex];
		if (!ent.model)
		{
			Com_DPrintf(DEVELOPER_MSG_CLIENT, "Temporary Model: Model not found \n");
			return;
		}
		AnglesToMat( ent.angles, ent.mat );
		ent.real_entity_number = MAGIC_UNUSED_NUMBER;
		CL_TempmodelEffect( &ent, 0, ent.scale, life, NULL, NULL, flags, 0 );
	}
	break;

	case TE_STRIKE: // bullet hitting wall
		MSG_ReadPos ( &net_message, pos );
		MSG_ReadDir ( &net_message, dir );
		lightstyle = MSG_ReadByte( &net_message );
		surftype = MSG_ReadByte( &net_message );
		CL_StrikeSurfaceSound( pos, surftype );
		CL_SinParticleEffect ( pos, dir, lightstyle, 10, false, 0 );
		break;

	case TE_DLIGHT: // dynamic light
	{
		float r,g,b,radius,decay,life;
		MSG_ReadPos ( &net_message, pos );
		r = ( float )MSG_ReadByte( &net_message ) / 255.0f;
		g = ( float )MSG_ReadByte( &net_message ) / 255.0f;
		b = ( float )MSG_ReadByte( &net_message ) / 255.0f;
		radius = MSG_ReadByte( &net_message ) * 4;
		decay = ( float )MSG_ReadByte( &net_message ) / 255.f;
		life = ( float )MSG_ReadByte( &net_message ) / 25.5f;
		CL_TempdlightEffect( pos, r, g, b, radius, life, decay );
	}
	break;

	case TE_BEAM:
		CL_ParseBeam();
		break;

	case TE_TELEPORT_EFFECT:
		MSG_ReadPos( &net_message, pos );
		lightstyle = MSG_ReadByte( &net_message );
		CL_TeleportParticles( pos, lightstyle );
		break;

	case TE_BULLET_SPRAY: // Multiple bullets hitting wall
	{
		vec3_t right, up;
		int i;

		MSG_ReadPos( &net_message, pos );
		MSG_ReadDir( &net_message, dir );
		MSG_ReadDir( &net_message, right );
		MSG_ReadDir( &net_message, up );
		count = MSG_ReadByte( &net_message );

		for (i=0; i<count; i++)
		{
			CL_BulletSpray( pos, dir, right, up, count, 120 );
		}
		break;
	}
	// 2015 code
	// added flame type particles for flamethrower
	case TE_FLAMETHROWER:
	{
		int x, y, z;
		vec3_t dest;

		MSG_ReadPos ( &net_message, pos );
		x = MSG_ReadByte( &net_message );
		y = MSG_ReadByte( &net_message );
		z = MSG_ReadByte( &net_message );
		if(!x && !y && !z)
		{
			dest[0] = pos[0];
			dest[1] = pos[1];
			dest[2] = pos[2];
		}
		else
		{
			dest[0] = (x - 128)*2 + pos[0];
			dest[1] = (y - 128)*2 + pos[1];
			dest[2] = (z - 128)*2 + pos[2];
		}
		CL_ThrowerParticleEffect( pos, dest );
		break;
	}
	// added flame type particles for rows of flamethrower flames
	case TE_FLAMETHROWERROW:
	{
		int x, y, z;
		vec3_t dest, pos2, dest2;

		// read in initial starting position
		MSG_ReadPos ( &net_message, pos );

		// read in first destination position
		x = MSG_ReadByte( &net_message );
		y = MSG_ReadByte( &net_message );
		z = MSG_ReadByte( &net_message );
		if(!x && !y && !z)
		{
			dest[0] = pos[0];
			dest[1] = pos[1];
			dest[2] = pos[2];
		}
		else
		{
			dest[0] = (x - 128)*2 + pos[0];
			dest[1] = (y - 128)*2 + pos[1];
			dest[2] = (z - 128)*2 + pos[2];
		}

		// read in second starting position
		x = MSG_ReadByte( &net_message );
		y = MSG_ReadByte( &net_message );
		z = MSG_ReadByte( &net_message );
		if(!x && !y && !z)
		{
			pos2[0] = pos[0];
			pos2[1] = pos[1];
			pos2[2] = pos[2];
		}
		else
		{
			pos2[0] = (x - 128)*2 + pos[0];
			pos2[1] = (y - 128)*2 + pos[1];
			pos2[2] = (z - 128)*2 + pos[2];
		}

		// read in second destination position
		x = MSG_ReadByte( &net_message );
		y = MSG_ReadByte( &net_message );
		z = MSG_ReadByte( &net_message );
		if(!x && !y && !z)
		{
			dest2[0] = pos2[0];
			dest2[1] = pos2[1];
			dest2[2] = pos2[2];
		}
		else
		{
			dest2[0] = (x - 128)*2 + pos2[0];
			dest2[1] = (y - 128)*2 + pos2[1];
			dest2[2] = (z - 128)*2 + pos2[2];
		}

		// make the row of flamethrower flames
		CL_ThrowerRowParticleEffect( pos, dest, pos2, dest2 );
		break;
	}
	// added flame type particles for flamethrower hit flames
	case TE_FLAMETHROWERHIT:
		MSG_ReadPos( &net_message, pos );
		MSG_ReadDir( &net_message, dir );
		CL_ThrowerHitParticleEffect( pos, dir );
		break;
	// added general purpose flame type particles
	case TE_FLAME:
	{
		vec3_t dest, dirb;
		int speed, seperation;
		byte size;
		signed char growrate;

		MSG_ReadPos ( &net_message, pos );
		dest[0] = (MSG_ReadByte( &net_message ) - 128)*2 + pos[0];
		dest[1] = (MSG_ReadByte( &net_message ) - 128)*2 + pos[1];
		dest[2] = (MSG_ReadByte( &net_message ) - 128)*2 + pos[2];
		MSG_ReadDir( &net_message, dir );
		speed = MSG_ReadByte( &net_message )*2;
		VectorScale( dir, speed, dir );
		MSG_ReadDir( &net_message, dirb );
		speed = MSG_ReadByte( &net_message )*2;
		VectorScale( dirb, speed, dirb );
		lightstyle = MSG_ReadByte( &net_message );
		count = MSG_ReadByte( &net_message );
		size = MSG_ReadByte( &net_message );
		// calc seperation distance
		seperation = size*0.3 + 1;
		growrate = size*(-0.25);
		CL_FlameParticleEffect( pos, dir, dest, dirb, seperation, count, lightstyle, 0, size, growrate );
		break;
	}
	// added nuke explosion
	case TE_NUKE_EXPLOSION:
		MSG_ReadPos ( &net_message, pos );
		count = MSG_ReadByte( &net_message );
		count *= 8;
		lightstyle = MSG_ReadByte( &net_message );
		CL_NukeEffect( pos, count, lightstyle );
		break;
	// added nuke explosion
	case TE_BOW_EXPLOSION:
	{
		float tmpflt;

		MSG_ReadPos ( &net_message, pos );
		count = MSG_ReadByte( &net_message );
		if(!count)
		{
			CL_PlasmaExplosion( pos, 16 );
//			CL_ExplosionParticles(pos, 120, 0.3);
			CL_TempdlightEffect( pos, 0.25, 1, 0.25, 100, 0.5, 0.9f );
		}
		else
		{
			count *= 8;
			if(count < 32)
				count = 32;
//			CL_PlasmaExplosion(pos, (float)count/70.0f);
//			CL_NukeEffect(pos, count, 155);
			CL_PlasmaExplosion( pos, count );
			tmpflt = (float)(count - 32) * 0.5 + 32;
//			CL_NukeEffect(pos, (int)tmpflt, 155);
			CL_PlasmaExplosion( pos, tmpflt );
			CL_ExplosionParticles( pos, 120, 0.2 + (float)count/70.0f );
			tmpflt = (float)count;
			tmpflt *= 0.005;
			if(tmpflt > 1)
				tmpflt = 1;
//			CL_TempdlightEffect( pos, 0, 1, 0, count + 256, 1.5, 0.95f );
			CL_TempdlightEffect( pos, tmpflt, 1, tmpflt, count + 256, 1, 0.95f );
		}
		break;
	}
	// added vertical booster effect for hoverbikes
	case TE_HOVERBOOSTER:
		MSG_ReadPos( &net_message, pos );
		count = MSG_ReadByte( &net_message )*2;
		CL_HoverBoostEffect( pos, count );
		break;
	// sizable general particles
	case TE_SIZED_PARTICLES:
	{
		byte size;
		signed char growrate;

		MSG_ReadPos ( &net_message, pos );
		MSG_ReadDir ( &net_message, dir );
		lightstyle = MSG_ReadByte( &net_message );
		count = MSG_ReadByte( &net_message );
		flags = MSG_ReadByte( &net_message );
		size = MSG_ReadByte( &net_message );
		growrate = MSG_ReadByte( &net_message ) - 128;
		CL_SinParticleEffectSized( pos, dir, lightstyle, count, false, flags, size, growrate );
		break;
	}
	// sized general particles (randomly selected from lightstyle)
	case TE_RANDOM_SIZED_PARTICLES:
	{
		byte size;
		signed char growrate;

		MSG_ReadPos ( &net_message, pos );
		MSG_ReadDir ( &net_message, dir );
		lightstyle = MSG_ReadByte( &net_message );
		count = MSG_ReadByte( &net_message );
		flags = MSG_ReadByte( &net_message );
		size = MSG_ReadByte( &net_message );
		growrate = MSG_ReadByte( &net_message ) - 128;
		CL_SinParticleEffect2Sized( pos, dir, lightstyle, count, false, flags, size, growrate );
		break;
	}
	// fully controllable general particles
	case TE_PARTICLES_FULL:
	{
		byte size;
		signed char growrate;
		int speed, spread;
		vec3_t accel;

		MSG_ReadPos ( &net_message, pos );
		MSG_ReadDir ( &net_message, dir );
		lightstyle = MSG_ReadByte( &net_message );
		count = MSG_ReadByte( &net_message );
		flags = MSG_ReadByte( &net_message );
		size = MSG_ReadByte( &net_message );
		growrate = MSG_ReadByte( &net_message ) - 128;
		speed = MSG_ReadByte( &net_message ) * 2;
		spread = MSG_ReadByte( &net_message ) * 2;
		accel[0] = MSG_ReadByte( &net_message ) - 128;
		accel[1] = MSG_ReadByte( &net_message ) - 128;
		accel[2] = MSG_ReadByte( &net_message ) - 128;
		CL_SinParticleEffectFull( pos, dir, lightstyle, count, false, flags, size, growrate, speed, spread, accel );
		break;
	}
	default:
		Com_Error ( ERR_DROP, "CL_ParseTEnt: bad type" );
	}
}

void CL_Shockwave (vec3_t origin,int life,int scale,float alpha)
{
	int flags;
	entity_t ent;

	memset( &ent, 0, sizeof( entity_t ) );
	ent.scale = scale;
	ent.alpha = 1.0f;
	VectorCopy( origin, ent.origin );
	flags = (TEMPMODEL_ANIMATE_SCALE | TEMPMODEL_ALPHAFADE);

	assert( cl_shockwave );
	ent.model = cl_shockwave;
	if (!ent.model)
	{
		Com_DPrintf(DEVELOPER_MSG_CLIENT, "Temporary Model: Model not found \n");
		return;
	}

	ent.real_entity_number = MAGIC_UNUSED_NUMBER;
	CL_TempmodelEffect( &ent, 0, ent.scale, life, NULL, NULL, flags, 0 );
}

void TempShockwave (vec3_t origin,trace_t trace)
{
	entity_t ent;
	int flags;
	float life;
	vec3_t norm;

	memset( &ent, 0, sizeof( entity_t ) );
	ent.scale = 1.0f;
	ent.alpha = 1.0f;

	VectorCopy( origin, ent.origin );

	VectorCopy( trace.plane.normal, norm );
	norm[0] = -norm[0];
	norm[1] = -norm[1];

	VectorToAngles( norm, ent.angles );

	flags = (TEMPMODEL_ANIMATE_SCALE | TEMPMODEL_ALPHAFADE);
	life = 8;

	assert( cl_shockwave );
	ent.model = cl_shockwave;
	if (!ent.model)
	{
		Com_DPrintf(DEVELOPER_MSG_CLIENT, "Temporary Model: Model not found \n");
		return;
	}

	ent.real_entity_number = MAGIC_UNUSED_NUMBER;

	AnglesToMat( ent.angles, ent.mat );
	CL_TempmodelEffect( &ent, 0, ent.scale, life, NULL, NULL, flags, 0 );
}

void TempExplosion (vec3_t origin,float scale)
{
	entity_t ent1,ent2,ent3,ent4;
	vec3_t v1;

	assert( cl_explosion1 );
	assert( cl_explosion2 );
	assert( cl_smokering );

	memset( &ent1, 0, sizeof( entity_t ) );
	memset( &ent2, 0, sizeof( entity_t ) );
	memset( &ent3, 0, sizeof( entity_t ) );
	memset( &ent4, 0, sizeof( entity_t ) );

	ent1.real_entity_number = MAGIC_UNUSED_NUMBER;
	ent2.real_entity_number = MAGIC_UNUSED_NUMBER;
	ent3.real_entity_number = MAGIC_UNUSED_NUMBER;
	ent4.real_entity_number = MAGIC_UNUSED_NUMBER;

	// Explosion 3d model portion
	if ( cl_explosion1 )
	{
		ent1.model = cl_explosion1;
		ent1.scale = 15.0f * scale;
		ent1.alpha = 1.0f;
		// 2015 code
		// added a heat signature to the explosions
		if ( cl.frame.playerstate.stats[STAT_NIGHTVISION] )
		{
			ent1.flags |= RF_ENVMAPPED;
			ent1.lightofs = 1;
		}

		AnglesToMat( ent1.angles, ent1.mat );
		VectorCopy( origin, ent1.origin );
		CL_TempmodelEffect( &ent1, 0, ent1.scale, 16, NULL, NULL, (TEMPMODEL_ANIMATE_SCALE | TEMPMODEL_ALPHAFADE), 0 );
	}

	if ( cl_explosion2 )
	{
		// Explosion sprite
		ent2.model = cl_explosion2;
		ent2.scale = 3.0f * scale;
		ent2.alpha = 1.0f;
		AnglesToMat( ent2.angles, ent2.mat );
		VectorCopy( origin, ent2.origin );
		CL_TempmodelEffect( &ent2, 0, ent2.scale, 10, NULL, NULL, (TEMPMODEL_ANIMATE_ONCE | TEMPMODEL_ANIMATE_SCALE | TEMPMODEL_ALPHAFADE), 0 );
	}

	if ( cl_smokering )
	{
		// Smoke ring 1
		ent3.model = cl_smokering;
		ent3.scale = 10.0f * scale;
		ent3.alpha = 1.0f;
		v1[0] = 0;
		v1[1] = 0;
		v1[2] = rand() % 360;
		VectorCopy( v1, ent3.angles );
		AnglesToMat( ent3.angles, ent3.mat );
		VectorCopy( origin, ent3.origin );
		CL_TempmodelEffect( &ent3, 0, ent3.scale, 3, NULL, NULL, (TEMPMODEL_ANIMATE_SCALE | TEMPMODEL_ALPHAFADE), 0 );

		// Smoke ring 2
		ent3.model = cl_smokering;
		ent3.scale = 6.0f * scale;
		ent3.alpha = 1.0f;
		v1[0] = 0;
		v1[1] = 0;
		v1[2] = rand() % 360;
		VectorCopy( v1, ent3.angles );
		AnglesToMat( ent3.angles, ent3.mat );
		VectorCopy( origin, ent3.origin );
		CL_TempmodelEffect( &ent3, 0, ent3.scale, 3, NULL, NULL, (TEMPMODEL_ANIMATE_SCALE | TEMPMODEL_ALPHAFADE), 0 );
		CL_TempdlightEffect( origin, 1, 0.4f, 0, 240*scale, 1, 0.95f );
	}
}

void TempPulseExplosion (vec3_t origin)
{
	entity_t ent1,ent2,ent3;
	vec3_t v1;

	assert( cl_pulsesphere );
	assert( cl_pulseexplode1 );
	assert( cl_pulseexplode2 );

	memset( &ent1, 0, sizeof( entity_t ) );
	memset( &ent2, 0, sizeof( entity_t ) );
	memset( &ent3, 0, sizeof( entity_t ) );

	ent1.real_entity_number = MAGIC_UNUSED_NUMBER;
	ent2.real_entity_number = MAGIC_UNUSED_NUMBER;
	ent3.real_entity_number = MAGIC_UNUSED_NUMBER;

	// Explosion 3d model portion
	if ( cl_pulsesphere )
	{
		ent1.model = cl_pulsesphere;
		ent1.scale = 15.0f;
		ent1.alpha = 1.0f;
		// 2015 code
		if ( cl.frame.playerstate.stats[STAT_NIGHTVISION] )
		{
			ent1.flags |= RF_ENVMAPPED;
			ent1.lightofs = 1;
		}
		AnglesToMat( ent1.angles, ent1.mat );
		VectorCopy( origin, ent1.origin );
		CL_TempmodelEffect( &ent1, 0, ent1.scale, 5, NULL, NULL, (TEMPMODEL_ANIMATE_SCALE | TEMPMODEL_ALPHAFADE), 0 );
	}

	if ( cl_pulseexplode1 )
	{
		ent2.model = cl_pulseexplode1;
		ent2.scale = 3.0f;
		ent2.alpha = 1.0f;
		AnglesToMat( ent2.angles, ent2.mat );
		VectorCopy( origin, ent2.origin );
		CL_TempmodelEffect( &ent2, 0, ent2.scale, 1, NULL, NULL, (TEMPMODEL_ANIMATE_ONCE | TEMPMODEL_ANIMATE_SCALE | TEMPMODEL_ALPHAFADE), 0 );
	}

	if ( cl_pulseexplode2 )
	{
		ent3.model = cl_pulseexplode2;
		ent3.scale = 3.0f;
		ent3.alpha = 1.0f;
		v1[0] = 0;
		v1[1] = 0;
		v1[2] = rand() % 360;
		VectorCopy( v1, ent3.angles );
		AnglesToMat( ent3.angles, ent3.mat );
		VectorCopy( origin, ent3.origin );
		CL_TempmodelEffect( &ent3, 0, ent3.scale, 1, NULL, NULL, (TEMPMODEL_ANIMATE_ONCE | TEMPMODEL_ANIMATE_SCALE | TEMPMODEL_ALPHAFADE), 0 );
	}
}

static void CalculatePitchAndYaw (vec3_t dist, float *pitch, float *yaw)
{
	float forward;

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

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

/*
=================
CL_AddBeams
=================
*/
void CL_AddBeams (void)
{
	int			i,j,count;
	beam_t		*b;
	vec3_t		dist, org, dir;
	entity_t	ent;
	float		yaw, pitch;
	float		time;
	int			offset = 0;
	float		single_beam_length;
	int			defhandle;
	vec3_t		mins,maxs;
	float		total_length;
	vec3_t		ndir;
	float		factor[3];
	vec3_t		forward, right, up;
	vec3_t		angles;
	float		t;

	b = cl_beams;
	for (i=0; i<MAX_BEAMS; i++, b++)
	{
		if (!b->model || b->endtime < cl.time) {
			continue;
		}
		time = (cl.time - b->time)*0.001;

		for (j=0; j<3; j++) {
			b->start[j] = b->prev_start[j] + cl.lerpfrac * (b->current_start[j] - b->prev_start[j]);
			b->end[j] = b->prev_end[j] + cl.lerpfrac * (b->current_end[j] - b->prev_end[j]);
		}

		VectorSubtract( b->start, b->end, dir );
		VectorNormalize( dir );

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

		CalculatePitchAndYaw( dist, &pitch, &yaw );
		angles[PITCH] = pitch;
		angles[YAW]   = yaw;
		angles[ROLL]  = 0;

		// Get the perpendicular vectors to this vector
		AngleVectors( angles, forward, right, up );

		// Set the starting point
		VectorCopy ( b->start, org );

		// Calculate the entire beam length
		total_length = VectorNormalize( dist );

		// Keep a copy of the normalized dir of this centerline vector
		VectorCopy( dist, ndir );

		// Find the length of a single beam
		defhandle = re.SINMDL_GetDefHandle( b->model );

		if (defhandle == -1) {
			Com_Error ( ERR_DROP, "CL_AddBeam: defhandle not found.\n" );
		}
		// Calculate the bounds of the model to get it's length
		SINMDL_CalculateBounds( defhandle, 1.0, mins, maxs );
		single_beam_length = maxs[0] - mins[0];

		// Create the beam entities
		memset ( &ent, 0, sizeof(ent));
		count = 0;

		// Initialize the factors
		for ( j=0; j<3; j++) {
			factor[j] = 0.3f * crand();
		}
		t = 0;

		// Randomize the alpha for this beam
		if ( b->flags & BEAM_RANDOM_ALPHA ) {
			b->alpha = frand();
		}

		while ( t >= 0.0f && t < 1.0f ) {
			float dot;
			vec3_t pdir;
			float delta;
			vec3_t distance_point;

			count++;

			// Set the origin of the current beam using the last calculated org
			VectorCopy ( org, ent.origin );

			// Advance the org one beam length in the new direction ( dist is the newly calculated direction )
			for (j=0; j<3; j++) {
				org[j] += dist[j] * (single_beam_length - b->overlap);
			}
			// Offset the org by a random amount to simulate lightning
			if ( b->flags & BEAM_LIGHTNING_EFFECT ) {
				VectorMA( org, single_beam_length * factor[2], up, org );
				VectorMA( org, single_beam_length * factor[1], right, org );
			}

			// Calculate (t) - how far this new point is along the overall distance
			VectorSubtract( org, b->start, pdir );
			dot = DotProduct( pdir, ndir );
			t = dot/total_length;

			// Calculate point at current distance along center beam
			VectorMA( b->start, total_length * t, ndir, distance_point );

			// Allow any variations
			if ( t > 0.1 && t < 0.9 ) {
				for ( j=0; j<3; j++ ) {
					delta = org[j] - distance_point[j];
					if ( delta > b->max_offset ) {
						org[j] = distance_point[j] + b->max_offset;
						factor[j] = -0.3 * crand();
					} else if ( delta < -b->max_offset ) {
						org[j] = distance_point[j] - b->max_offset;
						factor[j] = 0.3 * crand();
					} else {
						factor[j] = 0.3 * crand();
					}
				}
			} else { // Clamp to mins
				for ( j=0; j<3; j++ ) {
					delta = org[j] - distance_point[j];
					if ( delta > b->min_offset ) {
						org[j] -= 0.4 * single_beam_length;
						factor[j] = -0.2;
					} else if ( delta < -b->min_offset ) {
						org[j] += 0.4 * single_beam_length;
						factor[j] = 0.2;
					} else {
						factor[j] = 0;
					}
				}
			}
			// Calculate the new dist vector so we can get pitch and yaw for this beam
			VectorSubtract ( org, ent.origin, dist );

			// Normalize the vector and get the length
			VectorNormalize( dist );

			// Set the pitch and the yaw based off this new vector
			CalculatePitchAndYaw( dist, &pitch, &yaw );

			// Fill in the ent fields
			ent.model               = b->model;
			ent.real_entity_number  = MAGIC_UNUSED_NUMBER;
			ent.scale               = b->scale;
			ent.angles[PITCH]       = pitch;
			ent.angles[YAW]         = yaw;
			ent.alpha               = b->alpha;
			VectorCopy( ent.origin, ent.oldorigin );
			if (ent.alpha < 1.0f) {
				ent.flags |= RF_TRANSLUCENT;
			}
			// Set the number of frames and starting frames for random
			// starts
			if ((!b->num_frames) && (b->flags & BEAM_AUTO_ANIMATE)) {
				int c;
				b->num_frames = re.GetNumFrames( &ent );
				for (c=0; c<MAX_BEAMS; c++) {
					b->frame_start[c] = rand()%b->num_frames;
					b->oldframe[c] = 0;
					b->prevframe[c] = 0;
				}
			}
			// Roll baby Roll
			if (b->flags & BEAM_ROLL) {
				ent.angles[ROLL] = (int)(time*4000)%360;
			}
			if (b->flags & BEAM_AUTO_ANIMATE) {
				offset = 0;
				// Randomly select a starting frame
				if (b->flags & BEAM_ANIMATE_RANDOM_START) {
					offset = b->frame_start[count%MAX_BEAMS];
				}
				if (b->flags & BEAM_ANIMATE_FAST) {
					ent.frame = (offset + (int)(time*20))%b->num_frames;
				} else {
					ent.frame = (offset + (int)(time*10))%b->num_frames;
				}
				if (ent.frame != b->prevframe[count%MAX_BEAMS]) {
					b->oldframe[count%MAX_BEAMS] = b->prevframe[count%MAX_BEAMS];
				}
				b->prevframe[count%MAX_BEAMS] = ent.frame;
				ent.oldframe = b->oldframe[count%MAX_BEAMS];
				ent.backlerp = 1.0 - cl.lerpfrac;
			}
			// Add in this beam to the ref
			AnglesToMat( ent.angles,ent.mat );
			V_AddEntity ( &ent );
		}
	}
}

/*
=================
CL_AddTEnts
=================
*/
void CL_AddTEnts (void)
{
}
