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

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  

See the GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.

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

#include "cg_local.h"

#define	MAX_BEAMS			32
#define	MAX_LOCAL_ENTITIES	512

#define MAX_BEAMENTS		32
#define NUM_BEAM_SEGS		6

//splitmodels debrisbounce[start]
static vec3_t debris_maxs = { 4, 4, 8 };
static vec3_t debris_mins = { -4, -4, 0 };
//[end]

typedef enum
{
	LE_FREE, 
	LE_NO_FADE,
	LE_RGB_FADE,
	LE_ALPHA_FADE,
	LE_SCALE_ALPHA_FADE,
	LE_LASER
} letype_t;

typedef struct lentity_s
{
	struct lentity_s *prev, *next;

	letype_t	type;

	entity_t	ent;
	vec4_t		color;

	float		start;

	float		light;
	vec3_t		lightcolor;

	vec3_t		velocity;
	vec3_t		accel;
//splitmodels[start]
	int			bounce;	//is activator and bounceability value at once (debrisbounce)
//[end]
	int			frames;
} lentity_t;

typedef struct
{
	int			entity;
	int			dest_entity;
	struct model_s	*model;
	unsigned int	endtime;
	vec3_t		offset;
	vec3_t		start, end;

	//wsw : jal (so electrotrail model isn't rotated)
	qboolean	random_rotation;
	float		model_length;
	
	// wsw : jal : custom shaders
	struct shader_s *shader;

} beam_t;

typedef struct
{
	vec3_t			verts[NUM_BEAM_SEGS][4];
	vec2_t			stcoords[NUM_BEAM_SEGS][4];
	byte_vec4_t		colors[NUM_BEAM_SEGS][4];
} cg_beament_t;

lentity_t		cg_localents[MAX_LOCAL_ENTITIES];
lentity_t		cg_localents_headnode, *cg_free_lents;

beam_t			cg_beams[MAX_BEAMS];

cg_beament_t	cg_beamEnts[MAX_BEAMENTS];
int				cg_numBeamEnts;

//=================
//CG_ClearLocalEntities
//=================
void CG_ClearLocalEntities( void )
{
	int i;

	memset( cg_beams, 0, sizeof( cg_beams ) );
	memset( cg_localents, 0, sizeof( cg_localents ) );

	// link local entities
	cg_free_lents = cg_localents;
	cg_localents_headnode.prev = &cg_localents_headnode;
	cg_localents_headnode.next = &cg_localents_headnode;
	for( i = 0; i < MAX_LOCAL_ENTITIES - 1; i++ )
		cg_localents[i].next = &cg_localents[i+1];
}

//=================
//CG_AllocLocalEntity
//=================
lentity_t *CG_AllocLocalEntity( int type, float r, float g, float b, float a )
{
	lentity_t *le;

	if ( cg_free_lents ) {	// take a free decal if possible
		le = cg_free_lents;
		cg_free_lents = le->next;
	} else {				// grab the oldest one otherwise
		le = cg_localents_headnode.prev;
		le->prev->next = le->next;
		le->next->prev = le->prev;
	}

	memset( le, 0, sizeof (*le) );
	le->type = type;
	le->start = cg.time;
	le->color[0] = r;
	le->color[1] = g;
	le->color[2] = b;
	le->color[3] = a;

	switch( le->type ) {
		case LE_NO_FADE:
			break;
		case LE_RGB_FADE:
			le->ent.color[3] = ( qbyte )( 255 * a );
			break;
		case LE_SCALE_ALPHA_FADE:
		case LE_ALPHA_FADE:
			le->ent.color[0] = ( qbyte )( 255 * r );
			le->ent.color[1] = ( qbyte )( 255 * g );
			le->ent.color[2] = ( qbyte )( 255 * b );
			break;
		default:
			break;
	}

	// put the decal at the start of the list
	le->prev = &cg_localents_headnode;
	le->next = cg_localents_headnode.next;
	le->next->prev = le;
	le->prev->next = le;

	return le;
}

//=================
//CG_FreeLocalEntity
//=================
static void CG_FreeLocalEntity( lentity_t *le )
{
	// remove from linked active list
	le->prev->next = le->next;
	le->next->prev = le->prev;

	// insert into linked free list
	le->next = cg_free_lents;
	cg_free_lents = le;
}

//=================
//CG_AllocModel
//=================
lentity_t *CG_AllocModel( letype_t type, const vec3_t origin, const vec3_t angles, int frames,
				float r, float g, float b, float a, float light, float lr, float lg, float lb, struct model_s *model, struct shader_s *shader  )
{
	lentity_t	*le;

	le = CG_AllocLocalEntity( type, r, g, b, a );
	le->frames = frames;
	le->light = light;
	le->lightcolor[0] = lr;
	le->lightcolor[1] = lg;
	le->lightcolor[2] = lb;

	le->ent.rtype = RT_MODEL;
	le->ent.flags = RF_NOSHADOW;
	le->ent.model = model;
	le->ent.customShader = shader;
	le->ent.shaderTime = cg.time * 0.001f;
	le->ent.scale = 1.0f;

	AnglesToAxis( angles, le->ent.axis );
	VectorCopy( origin, le->ent.origin );

	return le;
}

//=================
//CG_AllocSprite
//=================
lentity_t *CG_AllocSprite( letype_t type, vec3_t origin, float radius, int frames,
				float r, float g, float b, float a, float light, float lr, float lg, float lb, struct shader_s *shader  )
{
	lentity_t	*le;

	le = CG_AllocLocalEntity( type, r, g, b, a );
	le->frames = frames;
	le->light = light;
	le->lightcolor[0] = lr;
	le->lightcolor[1] = lg;
	le->lightcolor[2] = lb;

	le->ent.rtype = RT_SPRITE;
	le->ent.flags = RF_NOSHADOW;
	le->ent.radius = radius;
	le->ent.customShader = shader;
	le->ent.shaderTime = cg.time * 0.001f;
	le->ent.scale = 1.0f;

	Matrix_Identity( le->ent.axis );
	VectorCopy( origin, le->ent.origin );

	return le;
}

//=================
//CG_AllocLaser
//=================
lentity_t *CG_AllocLaser( vec3_t start, vec3_t end, float radius, int frames, 
						 float r, float g, float b, float a, struct shader_s *shader )
{
	lentity_t	*le;

	le = CG_AllocLocalEntity( LE_LASER, 1, 1, 1, 1 );
	le->frames = frames;

	le->ent.radius = radius;
	le->ent.customShader = shader;
	le->ent.skinnum = COLOR_RGBA( (int)(r * 255), (int)(g * 255), (int)(b * 255), (int)(a * 255) );

	VectorCopy( start, le->ent.origin );
	VectorCopy( end, le->ent.oldorigin );

	return le;
}

//=================
//CG_AddLaser
//=================
void CG_AddLaser( vec3_t start, vec3_t end, float radius, int colors, struct shader_s *shader )
{
	int				i;
	vec3_t			perpvec;
	vec3_t			direction, normalized_direction;
	vec3_t			start_points[NUM_BEAM_SEGS], end_points[NUM_BEAM_SEGS];
	vec3_t			oldorigin, origin;
	poly_t			poly;
	cg_beament_t	*beamEnt;

	if( cg_numBeamEnts >= MAX_BEAMENTS )
		return;

	VectorCopy( start, origin );
	VectorCopy( end, oldorigin );
	VectorSubtract( oldorigin, origin, direction );

	if( VectorNormalize2( direction, normalized_direction ) == 0 )
		return;

	PerpendicularVector( perpvec, normalized_direction );
	VectorScale( perpvec, radius, perpvec );

	for( i = 0; i < NUM_BEAM_SEGS; i++ ) {
		RotatePointAroundVector( start_points[i], normalized_direction, perpvec, (360.0/NUM_BEAM_SEGS)*i );
		VectorAdd( start_points[i], origin, start_points[i] );
		VectorAdd( start_points[i], direction, end_points[i] );
	}

	beamEnt = &cg_beamEnts[cg_numBeamEnts++];

	memset( &poly, 0, sizeof(poly) );
	poly.numverts = 4;
	poly.shader = shader;

	for( i = 0; i < NUM_BEAM_SEGS; i++ ) {
		poly.colors = beamEnt->colors[i];
		poly.stcoords = beamEnt->stcoords[i];
		poly.verts = beamEnt->verts[i];

		VectorCopy( start_points[i], poly.verts[0] );
		poly.stcoords[0][0] = 0;
		poly.stcoords[0][1] = 0;
		*(int *)poly.colors[0] = colors;

		VectorCopy( end_points[i], poly.verts[1] );
		poly.stcoords[1][0] = 0;
		poly.stcoords[1][1] = 1;
		*(int *)poly.colors[1] = colors;

		VectorCopy( end_points[(i+1)%NUM_BEAM_SEGS], poly.verts[2] );
		poly.stcoords[2][0] = 1;
		poly.stcoords[2][1] = 1;
		*(int *)poly.colors[2] = colors;

		VectorCopy( start_points[(i+1)%NUM_BEAM_SEGS], poly.verts[3] );
		poly.stcoords[3][0] = 1;
		poly.stcoords[3][1] = 0;
		*(int *)poly.colors[3] = colors;

		trap_R_AddPolyToScene( &poly );
	}
}

//=================
//CG_AdjustImpactToWall
//
//  Helper for impacts positioning. Checks if inside solid and tries to move the origin out
//=================
void CG_AdjustImpactToWall( vec3_t pos, vec3_t dir )
{
	/*
	trace_t trace;
	vec3_t	end;

	if( !(CG_PointContents( pos ) & CONTENTS_SOLID) ) {
		CG_Printf( "Start VOID\n" );

		VectorMA( pos, -16, dir, end );
		//start from void to solid
		CG_Trace( &trace, pos, vec3_origin, vec3_origin, end, cgs.playerNum, CONTENTS_SOLID );
		if( trace.fraction < 1.0f ) {
			CG_Printf( "Adjusted\n" );
			VectorCopy( trace.endpos, pos );
			VectorCopy( trace.plane.normal, dir );
		}
		else CG_Printf( "Failed to adjust\n" );

		return;
	}

	CG_Printf( "Start SOLID\n" );

	// find void and trace to solid then
	
	//VectorNormalizeFast(dir);
	VectorMA( pos, 16, dir, end );
	if( !(CG_PointContents( end ) & CONTENTS_SOLID) ) {
		CG_Trace( &trace, end, vec3_origin, vec3_origin, pos, cgs.playerNum, CONTENTS_SOLID );
		if( trace.fraction < 1.0f ) {
			CG_Printf( "Adjusted\n" );
			VectorCopy( trace.endpos, pos );
			VectorCopy( trace.plane.normal, dir );
		}
		else CG_Printf( "Failed to adjust\n" );
	}
	*/
}

//=================
//CG_ElectroEffect
//=================
void CG_ElectroIonsTrail( vec3_t start, vec3_t end );
void CG_ElectroTrail2( vec3_t start, vec3_t end )
{
	CG_ElectroPolyBeam( start, end );
	CG_ElectroIonsTrail( start, end );
}

//=================
//CG_BulletExplosion_QF (backup)
//=================
void CG_BulletExplosion_QF( vec3_t origin, vec3_t dir )
{
	vec3_t		v;
	lentity_t	*le;

	le = CG_AllocModel( LE_NO_FADE, origin, vec3_origin, 6, 
		1, 1, 1, 1, 
		0, 0, 0, 0, 
		CG_MediaModel( cgs.media.modBulletExplode ), 
		NULL /*CG_MediaShader( cgs.media.shaderBulletExplosion )*/ ); //wsw

	le->ent.scale = 8.0f;

	if( !dir || VectorCompare( dir, vec3_origin ) ) {
		Matrix_Identity( le->ent.axis );
		return;
	}

	VectorMA( le->ent.origin, -8, dir, le->ent.origin );
	VectorCopy( dir, le->ent.axis[0] );
	PerpendicularVector( v, le->ent.axis[0] );
	RotatePointAroundVector( le->ent.axis[1], le->ent.axis[0], v, rand() % 360 );
	CrossProduct( le->ent.axis[0], le->ent.axis[1], le->ent.axis[2] );
}

//=================
//CG_ImpactSmokePuff
//=================
void CG_ImpactSmokePuff( vec3_t origin, vec3_t dir, float radius, float alpha, int time, int speed )
{
#define SMOKEPUFF_MAXVIEWDIST 700
	lentity_t	*le;
	struct shader_s *shader = CG_MediaShader( cgs.media.shaderSmokePuff );

	if( CG_PointContents(origin) & MASK_WATER ) {
		return;
	}

	if( DistanceFast( origin, cg.refdef.vieworg ) * cg.view_fracDistFOV > SMOKEPUFF_MAXVIEWDIST )
		return;

	if( !VectorLength(dir) ) {
		VectorCopy( cg.v_forward, dir );
		VectorInverse( dir ); 
	}
	VectorNormalize( dir );
	//offset the origin by half of the radius
	VectorMA( origin, radius*0.5f, dir, origin );

	le = CG_AllocSprite( LE_SCALE_ALPHA_FADE, origin, radius + crandom(), time,
		1, 1, 1, alpha, 0, 0, 0, 0, shader );

	le->ent.rotation = rand () % 360;
	VectorScale( dir, speed, le->velocity );
}

//=================
//CG_BulletExplosion
//=================
void CG_BulletExplosion( vec3_t pos, vec3_t dir )
{
	lentity_t	*le;
	vec3_t		angles;
	vec3_t		end;
	trace_t		trace;

	//find what are we hitting
	VectorMA( pos, -1.0, dir, end );
	CG_Trace( &trace, pos, vec3_origin, vec3_origin, end, cgs.playerNum+1, MASK_SHOT );
	if( trace.fraction == 1.0 )
		return;

	VecToAngles( dir, angles );

	if( trace.surfFlags & SURF_FLESH || 
		(trace.ent > 0 && cg_entities[trace.ent].current.type == ET_PLAYER) )
	{
		le = CG_AllocModel ( LE_ALPHA_FADE, pos, angles, 3, //3 frames for weak
		1, 1, 1, 1,				//full white no inducted alpha
		0, 0, 0, 0,				//dlight
		CG_MediaModel(cgs.media.modBulletExplode),
		NULL  );
		le->ent.rotation = rand () % 360;
		le->ent.scale = 1.0f;

	} else if( trace.surfFlags & SURF_DUST ) {

		// throw particles on dust
		CG_ImpactSmokePuff( trace.endpos, trace.plane.normal, 4, 0.6f, 6, 8 );

	} else {

		le = CG_AllocModel ( LE_ALPHA_FADE, pos, angles, 3, //3 frames for weak
		1, 1, 1, 1,				//full white no inducted alpha
		0, 0, 0, 0,				//dlight
		CG_MediaModel(cgs.media.modBulletExplode),
		NULL  );
		le->ent.rotation = rand () % 360;
		le->ent.scale = 1.0f;

		CG_ImpactSmokePuff( trace.endpos, trace.plane.normal, 2, 0.6f, 6, 8 );

		if( !(trace.surfFlags & SURF_NOMARKS) )
			CG_SpawnDecal ( pos, dir, random()*360, 8, 1, 1, 1, 1, 10, 1, qfalse, CG_MediaShader (cgs.media.shaderBulletMark) );
	}
}

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

	if( !model )
		return;

// override any beam with the same entity
	for( i = 0, b = cg_beams; i < MAX_BEAMS; i++, b++ ) {
		if( b->entity != ent )
			continue;

		b->entity = ent;
		b->model = model;
		b->shader = shader;
		b->endtime = cg.time + 100;
		VectorCopy( start, b->start );
		VectorCopy( end, b->end );
		VectorCopy( offset, b->offset );
		return;
	}

	// find a free beam
	for( i = 0, b = cg_beams; i < MAX_BEAMS; i++, b++ ) {
		if( b->model || b->endtime >= cg.time )
			continue;

		b->entity = ent;
		b->model = model;
		b->shader = shader;
		b->endtime = cg.time + 100;
		VectorCopy( start, b->start );
		VectorCopy( end, b->end );
		VectorCopy( offset, b->offset );
		return;
	}
}

//=================
//CG_AddLightning
//=================
void CG_AddLightning( int srcEnt, int destEnt, vec3_t start, vec3_t end, struct model_s *model )
{
	int		i;
	beam_t	*b;

	if( !model )
		return;

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

		b->entity = srcEnt;
		b->dest_entity = destEnt;
		b->model = model;
		b->endtime = cg.time + (4 * cg.frameTime);
		VectorCopy( start, b->start );
		VectorCopy( end, b->end );
		VectorClear( b->offset );
		return;
	}

	// find a free beam
	for( i = 0, b = cg_beams; i < MAX_BEAMS; i++, b++ ) {
		if( b->model || b->endtime >= cg.time )
			continue;

		b->entity = srcEnt;
		b->dest_entity = destEnt;
		b->model = model;
		b->endtime = cg.time + (8 * cg.frameTime);
		VectorCopy( start, b->start );
		VectorCopy( end, b->end );
		VectorClear( b->offset );
		return;
	}
}

//===============
//CG_BubbleTrail
//===============
void CG_BubbleTrail( vec3_t start, vec3_t end, int dist )
{
	int			i;
	float		len;
	vec3_t		move, vec;
	lentity_t	*le;
	struct shader_s *shader;

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

	VectorScale( vec, dist, vec );
	shader = CG_MediaShader( cgs.media.shaderWaterBubble );

	for( i = 0; i < len; i += dist ) {
		le = CG_AllocSprite( LE_ALPHA_FADE, move, 3, 10, 
			1, 1, 1, 1,
			0, 0, 0, 0, 
			shader );
		VectorSet( le->velocity, crandom()*5, crandom()*5, crandom()*5 + 6 );
		VectorAdd( move, vec, move );
	}
}

//===============
//CG_PlasmaExplosion
//===============
void CG_PlasmaExplosion( vec3_t pos, vec3_t dir, int fire_mode, float radius )
{
	lentity_t	*le;
	vec3_t		angles;
	float		model_radius = PLASMA_EXPLOSION_MODEL_RADIUS;

	VecToAngles( dir, angles );

	if( fire_mode == FIRE_MODE_STRONG )
	{
		le = CG_AllocModel( LE_ALPHA_FADE, pos, angles, 4,  
			1, 1, 1, 1,
			150, 0, 1, 0, 
			CG_MediaModel(cgs.media.modPlasmaExplosion), 
			NULL );
		//le->ent.scale = 3.0f;
		le->ent.scale = radius/model_radius;
		//trap_S_StartSound ( pos, 0, 0, CG_MediaSfx (cgs.media.sfxPlasmaStrongHit), cg_volume_effects->value, ATTN_NORM, 0 );
		
	} else {

		le = CG_AllocModel( LE_ALPHA_FADE, pos, angles, 4,  
			1, 1, 1, 1,
			80, 0, 1, 0, 
			CG_MediaModel(cgs.media.modPlasmaExplosion), 
			NULL );
		le->ent.scale = radius/model_radius;
		//trap_S_StartSound ( pos, 0, 0, CG_MediaSfx (cgs.media.sfxPlasmaWeakHit), cg_volume_effects->value, ATTN_NORM, 0 );
	}

	le->ent.rotation = rand () % 360;
	
	CG_SpawnDecal ( pos, dir, random()*360,
		8, 1, 1, 1,
		1, 10, 1, qfalse,
		CG_MediaShader (cgs.media.shaderPlasmaMark) );
}

//===============
//CG_BoltExplosionMode
//===============
void CG_BoltExplosionMode( vec3_t pos, vec3_t dir, int fire_mode )
{
	lentity_t	*le;
	vec3_t		angles;

	CG_AdjustImpactToWall( pos, dir );
	VecToAngles( dir, angles );

	le = CG_AllocModel ( LE_ALPHA_FADE, pos, angles, 6, // 6 is time
		1, 1, 1, 1,				//full white no inducted alpha
		250, 1, 1, 1,		//white dlight
		CG_MediaModel(cgs.media.modBladeWallHit), cgs.shaderWhite  );

	le->ent.rotation = rand () % 360;

	if( fire_mode == FIRE_MODE_STRONG ) {
		le->ent.scale = 1.5f;
		// add white energy particles on the impact
		CG_ImpactPufParticles( pos, dir, 8, 1.25f, 1, 1, 1, 1, trap_R_RegisterPic("additiveParticleShine") );
	} else {
		le->ent.scale = 1.0f;
		CG_ImpactPufParticles( pos, dir, 8, 1.0f, 1, 1, 1, 1, NULL );
	}

	//FIXME: need a proper shader
	CG_SpawnDecal( pos, dir, random()*360, 8, 1, 1, 1, 1, 10, 1, qfalse, CG_MediaShader (cgs.media.shaderBulletMark) );
}

//===============
//CG_RocketExplosionMode
//===============
void CG_RocketExplosionMode( vec3_t pos, vec3_t dir, int fire_mode, float radius )
{
	lentity_t	*le;
	vec3_t		angles, vec;
	vec3_t		origin;

	if( cg_explosionsAlpha->value < 0.1f )
		trap_Cvar_SetValue( "cg_explosionsAlpha", 0.1f );

	VecToAngles( dir, angles );

	if( fire_mode == FIRE_MODE_STRONG )
	{
		//trap_S_StartSound ( pos, 0, 0, CG_MediaSfx (cgs.media.sfxRocketLauncherStrongHit), cg_volume_effects->value, ATTN_NORM, 0 );
		CG_SpawnDecal ( pos, dir, random()*360, 64, 1, 1, 1, 1, 10, 1, qfalse, CG_MediaShader (cgs.media.shaderExplosionMark) );

	} else {
		//trap_S_StartSound ( pos, 0, 0, CG_MediaSfx (cgs.media.sfxRocketLauncherWeakHit), cg_volume_effects->value, ATTN_NORM, 0 );
		CG_SpawnDecal ( pos, dir, random()*360, 32, 1, 1, 1, 1, 10, 1, qfalse, CG_MediaShader(cgs.media.shaderExplosionMark) );
	}

	// animmap shader of the explosion
	VectorMA( pos, radius*0.25f, dir, origin );
	le = CG_AllocSprite( LE_ALPHA_FADE, origin, radius * 0.5f, 8,
		1, 1, 1, cg_explosionsAlpha->value,
		radius*4, 1, 0.80f, 0, // yellow dlight
		CG_MediaShader(cgs.media.shaderRocketExplosion) );

	VectorScale( dir, crandom()*6, vec );
	VectorSet( le->velocity, -vec[0] * 5 + crandom()*5, -vec[1] * 5 + crandom()*5, -vec[2] * 5 + crandom()*5 + 4 );
	le->ent.rotation = rand () % 360;

	if( cg_explosionsRingAlpha->value ) {
		// explosion ring sprite
		VectorMA( pos, radius*0.5f, dir, origin );
		le = CG_AllocSprite( LE_ALPHA_FADE, origin, radius, 3,
			1, 1, 1, cg_explosionsRingAlpha->value,
			0, 0, 0, 0, // no dlight
			CG_MediaShader(cgs.media.shaderRocketExplosionRing) );

		le->ent.rotation = rand () % 360;
	}


	//jalfixme: add sound at water?
}

//===============
//CG_BladeImpact
//===============
void CG_BladeImpact( vec3_t pos, vec3_t dir )
{
	lentity_t	*le;
	vec3_t		angles;
	vec3_t		end;
	trace_t		trace;

	//find what are we hitting
	VectorMA( pos, -1.0, dir, end );
	CG_Trace( &trace, pos, vec3_origin, vec3_origin, end, cgs.playerNum+1, MASK_SHOT );
	if( trace.fraction == 1.0 )
		return;

	VecToAngles( dir, angles );

	if( trace.surfFlags & SURF_FLESH || 
		(trace.ent > 0 && cg_entities[trace.ent].current.type == ET_PLAYER) )
	{
		le = CG_AllocModel ( LE_ALPHA_FADE, pos, angles, 3, //3 frames for weak
		1, 1, 1, 1,				//full white no inducted alpha
		0, 0, 0, 0,				//dlight
		CG_MediaModel(cgs.media.modBladeWallHit), NULL  );
		le->ent.rotation = rand () % 360;
		le->ent.scale = 1.0f;

		trap_S_StartSound ( pos, 0, CHAN_AUTO, CG_MediaSfx (cgs.media.sfxBladeFleshHit[(int)(random()*3)]), cg_volume_effects->value, ATTN_NORM, 0 );

	} else if( trace.surfFlags & SURF_DUST ) {

		// throw particles on dust
		if( trace.surfFlags & SURF_DUST )
			CG_ParticleEffect( trace.endpos, trace.plane.normal, 0.30f, 0.30f, 0.25f, 30 );

		//fixme? would need a dust sound
		trap_S_StartSound ( pos, 0, CHAN_AUTO, CG_MediaSfx (cgs.media.sfxBladeWallHit[(int)(random()*2)]), cg_volume_effects->value, ATTN_NORM, 0 );

	} else {

		le = CG_AllocModel ( LE_ALPHA_FADE, pos, angles, 3, //3 frames for weak
		1, 1, 1, 1,				//full white no inducted alpha
		0, 0, 0, 0,				//dlight
		CG_MediaModel(cgs.media.modBladeWallHit), NULL  );
		le->ent.rotation = rand () % 360;
		le->ent.scale = 1.0f;

		CG_ParticleEffect( trace.endpos, trace.plane.normal, 0.30f, 0.30f, 0.25f, 15 );

		trap_S_StartSound ( pos, 0, CHAN_AUTO, CG_MediaSfx (cgs.media.sfxBladeWallHit[(int)(random()*2)]), cg_volume_effects->value, ATTN_NORM, 0 );
		if( !(trace.surfFlags & SURF_NOMARKS) )
			CG_SpawnDecal ( pos, dir, random()*360, 8, 1, 1, 1, 1, 10, 1, qfalse, CG_MediaShader (cgs.media.shaderBulletMark) );
	}
}

//===============
//CG_GunBladeBlastImpact
//===============
void CG_GunBladeBlastImpact( vec3_t pos, vec3_t dir, float radius )
{
	lentity_t	*le;
	lentity_t	*le_explo;
	vec3_t		angles;
	float		model_radius = GUNBLADEBLAST_EXPLOSION_MODEL_RADIUS;

	CG_AdjustImpactToWall( pos, dir );
	VecToAngles( dir, angles );
	
	le = CG_AllocModel ( LE_ALPHA_FADE, pos, angles, 2, //3 frames
		1, 1, 1, 1,				//full white no inducted alpha
		0, 0, 0, 0,				//dlight
		CG_MediaModel( cgs.media.modBladeWallHit ),
		//"models/weapon_hits/gunblade/hit_blast.md3"
		NULL  );
	le->ent.rotation = rand () % 360;
	le->ent.scale = 1.0f; // this is the small bullet impact


	le_explo = CG_AllocModel ( LE_ALPHA_FADE, pos, angles, 2 + (radius/16.1f),
		1, 1, 1, 1,				//full white no inducted alpha
		0, 0, 0, 0,				//dlight
		CG_MediaModel( cgs.media.modBladeWallExplo ),
		NULL  );
	le_explo->ent.rotation = rand () % 360;
	le_explo->ent.scale = radius/model_radius;

	CG_SpawnDecal( pos, dir, random()*360, 3+(radius*0.5f), 1, 1, 1, 1, 10, 1, qfalse, CG_MediaShader(cgs.media.shaderExplosionMark) );
}

//===============
//CG_NewGrenadeTrail
//===============
void CG_NewGrenadeTrail( centity_t *cent )
{
	lentity_t	*le;
	float		len;
	vec3_t		vec;
	int			contents;
	int			trailTime;
	float		radius = 4, alpha = cg_grenadeTrailAlpha->value;
	struct shader_s *shader = CG_MediaShader( cgs.media.shaderGrenadeTrailSmokePuff );

	if( !cg_grenadeTrail->integer )
		return;

	// didn't move
	VectorSubtract( cent->ent.origin, cent->trailOrigin, vec );
	len = VectorNormalize( vec );
	if( !len )
		return;

	// density is found by quantity per second
	trailTime = (int)(1000.0f / cg_grenadeTrail->value );
	if( trailTime < 1 ) trailTime = 1;

	// we don't add more than one sprite each frame. If frame
	// ratio is too slow, people will prefer having less sprites on screen
	if( cent->localEffects[LOCALEFFECT_TRAIL_LAST_DROP] + trailTime < cg.time ) {
		cent->localEffects[LOCALEFFECT_TRAIL_LAST_DROP] = cg.time;

		contents = ( CG_PointContents( cent->trailOrigin ) & CG_PointContents( cent->ent.origin ) );
		if( contents & MASK_WATER ) {
			shader = CG_MediaShader( cgs.media.shaderWaterBubble );
			radius = 3 + crandom();
			alpha = 1.0f;
		}

		clamp( alpha, 0.0f, 1.0f );
		le = CG_AllocSprite( LE_SCALE_ALPHA_FADE, cent->trailOrigin, radius, 10, 
			1.0f, 1.0f, 1.0f, alpha,
			0, 0, 0, 0, 
			shader );
		VectorSet( le->velocity, -vec[0] * 5 + crandom()*5, -vec[1] * 5 + crandom()*5, -vec[2] * 5 + crandom()*5 + 3 );
		le->ent.rotation = rand () % 360;
	}
}

//===============
//CG_NewRocketTrail
//===============
void CG_NewRocketTrail( centity_t *cent )
{
	lentity_t	*le;
	float		len;
	vec3_t		vec;
	int			contents;
	int			trailTime;
	float		radius = 4, alpha = cg_rocketTrailAlpha->value;
	struct shader_s *shader = CG_MediaShader( cgs.media.shaderSmokePuff );

	if( !cg_rocketTrail->integer )
		return;

	// didn't move
	VectorSubtract( cent->ent.origin, cent->trailOrigin, vec );
	len = VectorNormalize( vec );
	if( !len )
		return;

	// density is found by quantity per second
	trailTime = (int)(1000.0f / cg_rocketTrail->value );
	if( trailTime < 1 ) trailTime = 1;

	// we don't add more than one sprite each frame. If frame
	// ratio is too slow, people will prefer having less sprites on screen
	if( cent->localEffects[LOCALEFFECT_TRAIL_LAST_DROP] + trailTime < cg.time ) {
		cent->localEffects[LOCALEFFECT_TRAIL_LAST_DROP] = cg.time;

		contents = ( CG_PointContents( cent->trailOrigin ) & CG_PointContents( cent->ent.origin ) );
		if( contents & MASK_WATER ) {
			shader = CG_MediaShader( cgs.media.shaderWaterBubble );
			radius = 3 + crandom();
			alpha = 1.0f;
		}

		clamp( alpha, 0.0f, 1.0f );
		le = CG_AllocSprite( LE_SCALE_ALPHA_FADE, cent->trailOrigin, radius, 10, 
			1.0f, 1.0f, 1.0f, alpha,
			0, 0, 0, 0, 
			shader );
		VectorSet( le->velocity, -vec[0] * 5 + crandom()*5, -vec[1] * 5 + crandom()*5, -vec[2] * 5 + crandom()*5 + 3 );
		le->ent.rotation = rand () % 360;
	}
}

//===============
//CG_NewElectroBeamPuff
//===============
void CG_NewElectroBeamPuff( centity_t *cent, vec3_t origin, vec3_t dir )
{
	float		len;
	vec3_t		vec;
	int			trailTime;

	if( !cg_particles->integer )
		return;

	// didn't move
	VectorSubtract( cent->ent.origin, cent->trailOrigin, vec );
	len = VectorNormalize( vec );
	if( !len )
		return;

	// density is found by quantity per second
	trailTime = (int)(1000.0f / 20.0f );
	if( trailTime < 1 ) trailTime = 1;

	// we don't add more than one sprite each frame. If frame
	// ratio is too slow, people will prefer having less sprites on screen
	if( cent->localEffects[LOCALEFFECT_TRAIL_LAST_DROP] + trailTime < cg.time ) {
		cent->localEffects[LOCALEFFECT_TRAIL_LAST_DROP] = cg.time;

		CG_ImpactSmokePuff( origin, dir, 3, 1.0f, 8, 12 ); 
	}
}

//===============
//CG_NewBloodTrail
//===============
void CG_NewBloodTrail( centity_t *cent )
{
	lentity_t	*le;
	float		len;
	vec3_t		vec;
	int			contents;
	int			trailTime;
	float		radius = 2.5f, alpha = cg_bloodTrailAlpha->value;
	struct shader_s *shader = CG_MediaShader( cgs.media.shaderBloodTrailPuff );

	if( !cg_showBloodTrail->integer)
		return;

	if(!cg_bloodTrail->integer)
		return;

	// didn't move
	VectorSubtract( cent->ent.origin, cent->trailOrigin, vec );
	len = VectorNormalize( vec );
	if( !len )
		return;

	// density is found by quantity per second
	trailTime = (int)(1000.0f / cg_bloodTrail->value );
	if( trailTime < 1 ) trailTime = 1;

	// we don't add more than one sprite each frame. If frame
	// ratio is too slow, people will prefer having less sprites on screen
	if( cent->localEffects[LOCALEFFECT_TRAIL_LAST_DROP] + trailTime < cg.time ) {
		cent->localEffects[LOCALEFFECT_TRAIL_LAST_DROP] = cg.time;

		contents = ( CG_PointContents( cent->trailOrigin ) & CG_PointContents( cent->ent.origin ) );
		if( contents & MASK_WATER ) {
			shader = CG_MediaShader( cgs.media.shaderBloodTrailLiquidPuff );
			radius = 4 + crandom();
			alpha = 0.5f * cg_bloodTrailAlpha->value;
		}

		clamp( alpha, 0.0f, 1.0f );
		le = CG_AllocSprite( LE_SCALE_ALPHA_FADE, cent->trailOrigin, radius, 8, 
			1.0f, 1.0f, 1.0f, alpha,
			0, 0, 0, 0, 
			shader );
		VectorSet( le->velocity, -vec[0] * 5 + crandom()*5, -vec[1] * 5 + crandom()*5, -vec[2] * 5 + crandom()*5 + 3 );
		le->ent.rotation = rand () % 360;
	}
}

//===============
//CG_BloodDamageEffect
//===============
void CG_BloodDamageEffect( vec3_t origin, vec3_t dir, int damage )
{
	lentity_t	*le;
	int		count, i;
	float	radius = 3.0f, alpha = cg_bloodTrailAlpha->value;
	int		time = 8;
	struct shader_s *shader = CG_MediaShader( cgs.media.shaderBloodImpactPuff );

	if( !cg_showBloodTrail->integer)
		return;

	if( !cg_bloodTrail->integer )
		return;

	count = (int)(damage * 0.25f);
	clamp( count, 1, 10 );

	if( CG_PointContents(origin) & MASK_WATER ) {
		shader = CG_MediaShader( cgs.media.shaderBloodTrailLiquidPuff );
		radius += (1 + crandom());
		alpha = 0.5f * cg_bloodTrailAlpha->value;
	}
	
	if( !VectorLength(dir) ) {
		VectorCopy( cg.v_forward, dir );
		VectorInverse( dir ); 
	}
	VectorNormalize( dir );

	for( i = 0; i < count; i++ ) {
		le = CG_AllocSprite( LE_SCALE_ALPHA_FADE, origin, radius + crandom(), time,
			1, 1, 1, alpha, 0, 0, 0, 0, shader );

		le->ent.rotation = rand () % 360;

		// randomize dir
		VectorSet( le->velocity,
			-dir[0] * 5 + crandom()*5,
			-dir[1] * 5 + crandom()*5,
			-dir[2] * 5 + crandom()*5 + 3 );
		VectorMA( dir, min(6, count), le->velocity, le->velocity );
	}
}

//===============
//CG_PModel_SpawnTeleportEffect
//===============
void CG_PModel_SpawnTeleportEffect( centity_t *cent )
{
	// the thing is, we must have a built skeleton, so we
	// can run all bones and spawn a polygon at their origin
	int		i, j;
	bonepose_t	*bonepose;
	cgs_skeleton_t	*skel;
	orientation_t	orient, ref;
	float			radius = 5;
	lentity_t		*le;
	vec3_t			vec, teleportOrigin;

	skel = CG_SkeletonForModel( cent->ent.model );
	if( !skel || !cent->ent.boneposes )
		return;

	for( j = LOCALEFFECT_EV_PLAYER_TELEPORT_IN; j <= LOCALEFFECT_EV_PLAYER_TELEPORT_OUT; j++ ) 
	{
		if( cent->localEffects[j] ) 
		{
			cent->localEffects[j] = 0;

			if( j == LOCALEFFECT_EV_PLAYER_TELEPORT_OUT )
				VectorCopy( cent->teleportedFrom, teleportOrigin );
			else
				VectorCopy( cent->ent.origin, teleportOrigin );

			for( i = 0; i < skel->numBones; i++ ) {
				bonepose = cent->ent.boneposes + i;
				Quat_Matrix( bonepose->quat, orient.axis );
				orient.origin[0] = bonepose->origin[0];
				orient.origin[1] = bonepose->origin[1];
				orient.origin[2] = bonepose->origin[2];

				VectorCopy( vec3_origin, ref.origin );
				Matrix_Copy( axis_identity, ref.axis );
				CG_MoveToTag( ref.origin, ref.axis,
					teleportOrigin, cent->ent.axis,
					orient.origin, orient.axis );

				VectorSet( vec, 0.1f, 0.1f, 0.1f );

				// spawn a sprite at each bone
				le = CG_AllocSprite( LE_SCALE_ALPHA_FADE, ref.origin, radius, 15 + crandom()*5, 
					1, 1, 1, 0.5f,
					0, 0, 0, 0, 
					CG_MediaShader( cgs.media.shaderTeleporterSmokePuff ) );
				VectorSet( le->velocity, -vec[0] * 5 + crandom()*5, -vec[1] * 5 + crandom()*5, -vec[2] * 5 + crandom()*5 + 3 );
				le->ent.rotation = rand () % 360;

				CG_ParticleEffect( ref.origin, ref.axis[2], 0.9f, 0.9f, 0.9f, 2 );
			}
		}
	}
}

//===============
//CG_GrenadeExplosionMode
//===============
//wsw
void CG_GrenadeExplosionMode( vec3_t pos, vec3_t dir, int fire_mode, float radius )
{
	lentity_t	*le;
	vec3_t		angles;
	vec3_t		decaldir;
	//float		model_radius = GRENADE_EXPLOSION_MODEL_RADIUS;
	vec3_t  origin,vec;

	if( cg_explosionsAlpha->value < 0.1f )
		trap_Cvar_SetValue( "cg_explosionsAlpha", 0.1f );

	/*
	if( !dir ) 
	{
	angles[0] = random()*360;
	angles[1] = random()*360;
	angles[2] = 0;
	AngleVectors(angles, NULL, NULL, decaldir);
	} else 
	{*/
	VectorCopy( dir, decaldir );
	VecToAngles( dir, angles );
	//}

	//if( CG_PointContents( pos ) & MASK_WATER )
	//jalfixme: (shouldn't we do the water sound variation?)

	if( fire_mode == FIRE_MODE_STRONG )
	{
		/*
		le = CG_AllocModel( LE_ALPHA_FADE, pos, angles, 5,  
		1.0f, 1.0f, 1.0f, 0.6f, //jal: suposed to be white transparent
		250, 1.0f, 1.0f, 0.0f,   //jal: supposed to be yellow dlight
		CG_MediaModel(cgs.media.modGrenadeExplosion), 
		NULL );

		//le->ent.scale = 4.0f;
		le->ent.scale = radius / model_radius;

		//trap_S_StartSound ( pos, 0, 0, CG_MediaSfx (cgs.media.sfxGrenadeStrongExplosion), cg_volume_effects->value, ATTN_NORM, 0 );
		*/
		CG_SpawnDecal( pos, decaldir, random()*360, 64, 1, 1, 1, 1, 10, 1, qfalse, CG_MediaShader( cgs.media.shaderExplosionMark ) );

	} else {
		/*
		le = CG_AllocModel( LE_ALPHA_FADE, pos, angles, 3,  
		1.0, 1.0, 1.0, 1.0, //jal: suposed to be white opaque
		100, 1.0, 1.0, 0,   //jal: supposed to be yellow dlight
		CG_MediaModel(cgs.media.modGrenadeExplosion), 
		NULL );

		//le->ent.scale = 2.0f;
		le->ent.scale = radius / model_radius;

		//trap_S_StartSound ( pos, 0, 0, CG_MediaSfx (cgs.media.sfxGrenadeWeakExplosion), cg_volume_effects->value, ATTN_NORM, 0 );
		*/
		CG_SpawnDecal( pos, decaldir, random()*360, 32, 1, 1, 1, 1, 10, 1, qfalse, CG_MediaShader( cgs.media.shaderExplosionMark ) );

	}

	//le->ent.rotation = rand () % 360;
	// animmap shader of the explosion
	VectorMA( pos, radius*0.25f, dir, origin );
	le = CG_AllocSprite( LE_ALPHA_FADE, origin, radius * 0.5f, 8,
		1, 1, 1, cg_explosionsAlpha->value,
		radius*4, 1, 0.80f, 0, // yellow dlight
		CG_MediaShader(cgs.media.shaderRocketExplosion) );

	VectorScale( dir, crandom()*6, vec );
	VectorSet( le->velocity, -vec[0] * 5 + crandom()*5, -vec[1] * 5 + crandom()*5, -vec[2] * 5 + crandom()*5 + 4 );
	le->ent.rotation = rand () % 360;

	// explosion ring sprite
	if( cg_explosionsRingAlpha->value ) {
		VectorMA( pos, radius*0.5f, dir, origin );
		le = CG_AllocSprite( LE_ALPHA_FADE, origin, radius, 3,
			1, 1, 1, cg_explosionsRingAlpha->value,
			0, 0, 0, 0, // no dlight
			CG_MediaShader(cgs.media.shaderRocketExplosionRing) );

		le->ent.rotation = rand () % 360;
	}
}

//=================
//CG_FlagFlareTrail
//=================
void CG_FlagTrail( vec3_t origin, vec3_t start, vec3_t end, float r, float g, float b )
{
	lentity_t	*le;
	float		len, mass = 20;
	vec3_t		dir;

	VectorSubtract( end, start, dir );
	len = VectorNormalize( dir );
	if( !len )
		return;

	le = CG_AllocSprite( LE_SCALE_ALPHA_FADE, origin, 8, 50 + 50*random(), 
		r, g, b, 0.7f,
		0, 0, 0, 0, 
		CG_MediaShader( cgs.media.shaderTeleporterSmokePuff ) );
	VectorSet( le->velocity, -dir[0] * 5 + crandom()*5, -dir[1] * 5 + crandom()*5, -dir[2] * 5 + crandom()*5 + 3 );
	le->ent.rotation = rand () % 360;

	//friction and gravity
	VectorSet( le->accel, -0.2f, -0.2f, -9.8f * mass);
	le->bounce = 20;
}

//===============
//CG_Explosion1
//===============
void CG_Explosion1( vec3_t pos )
{
	CG_RocketExplosionMode( pos, vec3_origin, FIRE_MODE_STRONG, 120 );
}

//===============
//CG_Explosion2
//===============
void CG_Explosion2( vec3_t pos )
{
	CG_GrenadeExplosionMode( pos, vec3_origin, FIRE_MODE_STRONG, 120 );
}

//===============
//CG_TeleportEffect
//===============
void CG_TeleportEffect( vec3_t org )
{
	lentity_t *le;

	le = CG_AllocModel( LE_RGB_FADE, org, vec3_origin, 5, 
		1, 1, 1, 1,
		0, 0, 0, 0, 
		CG_MediaModel( cgs.media.modTeleportEffect ), 
		CG_MediaShader( cgs.media.shaderTeleportEffect ) );
	le->ent.origin[2] -= 24;
}

//===============
//CG_GreenLaser
//===============
void CG_GreenLaser( vec3_t start, vec3_t end )
{
	lentity_t *le;

	le = CG_AllocLaser( start, end, 2.0f, 2.0f, 0.0f, 0.85f, 0.0f, 0.3f, CG_MediaShader( cgs.media.shaderLaser ) );
}

//=================
//CG_SmallPileOfGibs - SPLITMODELS (debrisbounce)
// velocity can be NULL
//=================
void CG_SmallPileOfGibs( vec3_t origin, int	count, vec3_t velocity )
{
	lentity_t	*le;
	int			i;
	float		mass = 60;
	vec3_t		angles;

	if( !cg_gibs->integer )
		return;

	for( i = 0; i < count; i++ ) 
	{
		le = CG_AllocModel( LE_NO_FADE, origin, vec3_origin, 200 + 200*random(), 
			1, 1, 1, 1, 
			0, 0, 0, 0, 
			CG_MediaModel( cgs.media.modMeatyGibs[(int)(random()*(MAX_MEATY_GIBS-1))] ), 
			NULL );

		//random rotation and scale variations
		VectorSet( angles, crandom()*360, crandom()*360, crandom()*360 );
		AnglesToAxis ( angles, le->ent.axis );
		le->ent.scale = 1.0 - (crandom()*0.25);
		le->ent.flags = RF_FULLBRIGHT; //jalfixme: needs to fix ligthing
		
		if( velocity ) {
			//velocity brought by game + random variations
			le->velocity[0] = velocity[0] + crandom()*75;
			le->velocity[1] = velocity[1] + crandom()*75;
			le->velocity[2] = velocity[2] + crandom()*75;
		} else {
			vec3_t		dir;
			float		v;
			//pure random dir & velocity
			VectorSet( dir, crandom()*0.5, crandom()*0.5, random() );
			v = 100 + random() * 100;
			VectorSet( le->velocity, dir[0]*v, dir[1]*v, dir[2]*v );
		}

		//friction and gravity
		VectorSet( le->accel, -0.2f, -0.2f, -9.8f * mass);
		
		le->bounce = 35;
	}
}

//=================
//CG_EjectBrass - SPLITMODELS //eject brass-debris
//=================
void CG_EjectBrass( vec3_t origin, int	count, struct model_s *model )
{
	lentity_t	*le;
	int			i;
	float		mass = 40;
	vec3_t		angles;
	vec3_t		dir;
	float		v;

	if(!cg_ejectBrass->integer)
		return;

	for( i = 0; i < count; i++ ) 
	{
		le = CG_AllocModel( LE_NO_FADE, origin, vec3_origin, 50 + 50*random(), 
			1, 1, 1, 1, 
			0, 0, 0, 0, 
			model, 
			NULL );

		//random rotation
		VectorSet( angles, crandom()*360, crandom()*360, crandom()*360 );
		AnglesToAxis ( angles, le->ent.axis );
		
		//pure random dir & velocity
		VectorSet( dir, crandom()*0.25, crandom()*0.25, random() );
		v = 100 + random() * 25;
		VectorSet( le->velocity, dir[0]*v, dir[1]*v, dir[2]*v );
		

		//friction and gravity
		VectorSet( le->accel, -0.2f, -0.2f, -9.8f * mass);
		
		le->bounce = 60;
	}
}

//=================
//CG_AddBeams
//=================
void CG_AddBeams( void )
{
	int			i;
	beam_t		*b;
	vec3_t		dist, org, angles, angles2;
	float		d;
	entity_t	ent;
	float		len, steps;
	float		model_length;
	
// update beams
	for( i = 0, b = cg_beams; i < MAX_BEAMS; i++, b++ ) {
		if( !b->model || b->endtime < cg.time )
			continue;

		// if coming from the player, update the start position
		//if( b->entity == cgs.playerNum + 1 ) {	// entity 0 is the world
		//	VectorCopy( cg.refdef.vieworg, b->start );
		//	b->start[2] -= 22;				// adjust for view height
		//}
		VectorAdd( b->start, b->offset, org );

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

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

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

		ent.scale = 1.0f;
		ent.color[0] = ent.color[1] = ent.color[2] = ent.color[3] = 255;

		if( b->model == CG_MediaModel( cgs.media.modLightning ) ) {
			model_length = 35.0;
			d -= 20.0;  // correction so it doesn't end in middle of tesla
		} else {
			//wsw : jal : custom model leghts
			if( b->model_length )
				model_length = b->model_length;
			else
				model_length = 30.0;
		}

		steps = ceil( d / model_length);
		len = (d - model_length) / (steps - 1);

		// PMM - special case for lightning model .. if the real length is shorter than the model,
		// flip it around & draw it from the end to the start.  This prevents the model from going
		// through the tesla mine (instead it goes through the target)
		if( (b->model == CG_MediaModel( cgs.media.modLightning )) && (d <= model_length) ) {
			VectorCopy( b->end, ent.origin );
			VectorCopy( b->end, ent.lightingOrigin );
			VectorCopy( b->end, ent.oldorigin );

			// offset to push beam outside of tesla model
			// (negative because dist is from end to start for this beam)
			ent.rtype = RT_MODEL;
			ent.model = b->model;
			ent.flags = RF_FULLBRIGHT|RF_NOSHADOW;
			angles[0] = angles2[0];
			angles[1] = angles2[1];
			angles[2] = rand()%360;
			AnglesToAxis( angles, ent.axis );
			CG_AddEntityToScene( &ent );	// skelmod		
			return;
		}

		ent.rtype = RT_MODEL;
		ent.flags = RF_NOSHADOW;
		ent.model = b->model;
		ent.customShader = b->shader; // wsw : jal : allow custom shaders

		while( d > 0 ) {
			VectorCopy( org, ent.origin );
			VectorCopy( org, ent.lightingOrigin );
			VectorCopy( org, ent.oldorigin );

			if( b->model == CG_MediaModel( cgs.media.modLightning ) ) {
				angles[0] = -angles2[0];
				angles[1] = angles2[1] + 180.0;
				angles[2] = rand()%360;
			} else {
				//wsw : jal (add rotation angle)
				if( b->random_rotation ) {
					angles[0] = angles2[0];
					angles[1] = angles2[1];
					angles[2] = rand()%360;
				} else {
					angles[0] = angles2[0];
					angles[1] = angles2[1];
					angles[2] = angles2[2];
				}
			}
			
			AnglesToAxis( angles, ent.axis );
			CG_AddEntityToScene( &ent );	// skelmod

			VectorMA( org, len, dist, org );
			d -= model_length;
		}
	}
}

//=================
//CG_AddLocalEntities
//=================
void CG_AddLocalEntities( void )
{
	int			f;
	lentity_t	*le, *next, *hnode;
	entity_t	*ent;
	float		scale, frac, fade, time;
	float		backlerp;

	time = cg.frameTime;
	backlerp = 1.0f - cg.lerpfrac;

	hnode = &cg_localents_headnode;
	for( le = hnode->next; le != hnode; le = next ) {
		next = le->next;

		frac = (cg.time - le->start) * 0.01f;
		f = ( int )floor( frac );
		f = max( f, 0 );

		// it's time to DIE
		if( f >= le->frames - 1 ) {
			le->type = LE_FREE;
			CG_FreeLocalEntity( le );
			continue;
		}

		if( le->frames > 1 ) {
			scale = 1.0f - frac / (le->frames - 1);
			scale = bound( 0.0f, scale, 1.0f );
			fade = scale * 255.0f;
		} else {
			scale = 1.0f;
			fade = 255.0f;
		}

		ent = &le->ent;

		if( le->light && scale )
			CG_AddLightToScene( ent->origin, le->light * scale, le->lightcolor[0], le->lightcolor[1], le->lightcolor[2], NULL );

		if( le->type == LE_LASER ) {
			CG_AddLaser( ent->origin, ent->oldorigin, ent->radius, ent->skinnum, ent->customShader );
			continue;
		}

		switch( le->type ) {
			case LE_NO_FADE:
				break;
			case LE_RGB_FADE:
				ent->color[0] = ( qbyte )( fade * le->color[0] );
				ent->color[1] = ( qbyte )( fade * le->color[1] );
				ent->color[2] = ( qbyte )( fade * le->color[2] );
				break;
			case LE_SCALE_ALPHA_FADE:
				ent->scale = 1.0f + 1.0f / scale;
				ent->scale = min( ent->scale, 5.0f );
			case LE_ALPHA_FADE:
				ent->color[3] = ( qbyte )( fade * le->color[3] );
				break;
			default:
				break;
		}

		ent->backlerp = backlerp;

		//splitmodels debrisbounce[start]
		if( le->bounce )
		{
			trace_t	trace;
			vec3_t	next_origin;

			VectorMA( ent->origin, time, le->velocity, next_origin );

			CG_Trace ( &trace, ent->origin, debris_mins, debris_maxs, next_origin, 0, MASK_SOLID );
			if ( trace.fraction != 1.0 ) //found solid
			{
				float	dot;
				vec3_t	vel;
				float	xzyspeed;

				// Reflect velocity
				VectorSubtract( next_origin, ent->origin, vel );
				dot = -2 * DotProduct( vel, trace.plane.normal );
				VectorMA( vel, dot, trace.plane.normal, le->velocity );
				//put new origin in the impact point, but move it out a bit along the normal
				VectorMA( trace.endpos, 1, trace.plane.normal, ent->origin );

				//the entity has not speed enough. Stop checks
				xzyspeed = sqrt(le->velocity[0]*le->velocity[0] + le->velocity[1]*le->velocity[1] + le->velocity[2]*le->velocity[2]);
				if( xzyspeed * time < 1.0f) {
					trace_t traceground;
					vec3_t	ground_origin;
					//see if we have ground
					VectorCopy(ent->origin, ground_origin);
					ground_origin[2] += (debris_mins[2] - 4);
					CG_Trace ( &traceground, ent->origin, debris_mins, debris_maxs, ground_origin, 0, MASK_SOLID );
					if( traceground.fraction != 1.0) {
						le->bounce = qfalse;
						VectorClear(le->velocity);
						VectorClear(le->accel);
					}
				} else 
					VectorScale( le->velocity, le->bounce * time, le->velocity );

			} else {

				VectorCopy( ent->origin, ent->oldorigin );
				VectorCopy( next_origin, ent->origin );
			}
		} else {
			
			VectorCopy( ent->origin, ent->oldorigin );
			VectorMA( ent->origin, time, le->velocity, ent->origin );
		}
		//[end]
		VectorCopy( ent->origin, ent->lightingOrigin );
		VectorMA( le->velocity, time, le->accel, le->velocity );

		CG_AddEntityToScene( ent );		// skelmod
	}
}


// =======================================================
//			Unused code below
// =======================================================


#ifdef THIS_IS_DISABLED
//===============
//CG_BlasterExplosion
//===============
void CG_BlasterExplosion( vec3_t pos, vec3_t dir )
{
	CG_BlasterParticles( pos, dir );
	trap_S_StartSound( pos,  0, 0, CG_MediaSfx( cgs.media.sfxLashit ), cg_volume_effects->value, ATTN_NORM, 0 );
	CG_SpawnDecal( pos, dir, random()*360, 16, 1, 0.8, 0, 1, 8, 2, qtrue, CG_MediaShader( cgs.media.shaderEnergyMark ) );
}

//===============
//CG_BFGExplosion
//===============
void CG_BFGExplosion( vec3_t pos )
{
	lentity_t *le;

	le = CG_AllocModel( LE_NO_FADE, pos, vec3_origin, 4, 
		1, 1, 1, 1, 
		350, 0, 1.0, 0, 
		CG_MediaModel( cgs.media.modBfgExplo ), 
		NULL );
}

//===============
//CG_BFGBigExplosion
//===============
void CG_BFGBigExplosion( vec3_t pos )
{
	lentity_t *le;

	le = CG_AllocModel( LE_NO_FADE, pos, vec3_origin, 6,  
		1, 1, 1, 1,
		700, 0, 1.0, 0, 
		CG_MediaModel( cgs.media.modBfgBigExplo ), 
		NULL );

	CG_BFGExplosionParticles( pos );
}
#endif

