//-----------------------------------------------------------------------------
//
//  $Logfile:: /Source/cl_sinfx.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:
// Sin special fx stuff
//

#include "client.h"
#include "snd_loc.h"

/*
==============================================================

TRIANGLE MANAGEMENT

==============================================================
*/

#define TRIANGLE_MOVE_ORIGIN  1
#define TRIANGLE_MOVE_ANGLES  2

#define EXPAND_GROW_TIME 0.2
#define EXPAND_HOVER_TIME 0.3
#define EXPAND_SHRINK_TIME 0.2
#define EXPAND_FINISH_TIME 0.5
#define EXPAND_RADIUS 65

#define BOUNCE_HANGAROUND_TIME 15

typedef struct ctriangle_s
{
	struct ctriangle_s *next;

	byte type;
	byte state;
	byte move;
	byte dummy;

	float lastupdatetime;
	float state_time;
	float die_time;

	vec3_t oldorg;
	vec3_t org;
	vec3_t vel;
	vec3_t accel;
	vec3_t oldang;
	vec3_t ang;
	vec3_t angvel;
	vec3_t angaccel;
	float alpha;
	float alphavel;
	int lightstyle;
	triangle_t base;
} ctriangle_t;


#define TRIANGLE_GRAVITY    450
#define  TRIANGLE_UPDATE_TIME 0.1
#define  MAX_GET_TRIANGLES (MAX_TRIANGLES/2)


ctriangle_t *active_triangles, *free_triangles;

ctriangle_t triangles[MAX_TRIANGLES];
int cl_numtriangles = MAX_TRIANGLES;
int cl_numfreetriangles = MAX_TRIANGLES;
triangle_t temp_triangles[MAX_GET_TRIANGLES];
static vec3_t cl_tri_mins = {-2, -2, 0 };
static vec3_t cl_tri_maxs = {2, 2, 4 };

/*
===============
CL_ClearTriangles
===============
*/
void CL_ClearTriangles( void )
{
	int i;

	free_triangles = &triangles[0];
	active_triangles = NULL;
	cl_numfreetriangles = MAX_TRIANGLES;

	for (i=0; i<cl_numtriangles; i++)
		triangles[i].next = &triangles[i+1];
	triangles[cl_numtriangles-1].next = NULL;
}


/*
===============
CL_KillTriangles
===============
*/
static void CL_KillTriangles( int numtokill )
{
	ctriangle_t *p, *next;
	ctriangle_t *active, *tail;

	active = NULL;
	tail = NULL;

	for (p=active_triangles; p; p=next)
	{
		next = p->next;

		if ( ( ( (cl.time - p->die_time) >= -150 ) || (p->state>=250) ) && numtokill)
		{
			p->next = free_triangles;
			free_triangles = p;
			cl_numfreetriangles++;
			numtokill--;
			continue;
		}
		p->next = NULL;
		if (!tail)
			active = tail = p;
		else
		{
			tail->next = p;
			tail = p;
		}
		if (!numtokill)
		{
			p->next = next;
			break;
		}
	}
	active_triangles = active;
}

/*
===============
CL_MakeTriangles
===============
*/
#define VERT_COPY( src,dest ) memcpy((dest),(src),sizeof(float)*TRI_VERT_SIZE );

static void CL_MakeTriangles( triangle_t * src, ctriangle_t * dest, int thickness )
{
	vec3_t ofs;
	vec3_t norm;
	vec3_t edge[2];
	float triverts[4][TRI_VERT_SIZE];   // variable sized (xyz st rgb)

	//
	// copy over all the default stuff
	//
	dest->base = *src;
	if (!thickness || src->alpha < 1.0f)
	{
		// don't set but OR the flag, instead: refresh side
		// (tesselate.c) now initializes the triangle flags.
		dest->base.flags |= TRI_TWOSIDED;
		dest->base.num_tris = 1;
		return;
	}

	// don't reset the triangle flags: the refresh side
	// (tesselate.c) now initializes the triangle flags.
	//dest->base.flags = 0;
	dest->base.num_tris = 4;

	VectorSubtract( src->verts[2], src->verts[0], edge[0] );
	VectorSubtract( src->verts[1], src->verts[0], edge[1] );

	VectorNormalize( edge[0] );
	VectorNormalize( edge[1] );
	CrossProduct( edge[1], edge[0], norm );
	VectorScale( norm, -thickness, ofs );

	VERT_COPY( src->verts[0], triverts[0] );
	VERT_COPY( src->verts[1], triverts[1] );
	VERT_COPY( src->verts[2], triverts[2] );
	VERT_COPY( src->verts[0], triverts[3] );
	triverts[3][0] -= ofs[0];
	triverts[3][1] -= ofs[1];
	triverts[3][2] -= ofs[2];

	VERT_COPY( triverts[0], dest->base.verts[0] );
	VERT_COPY( triverts[1], dest->base.verts[1] );
	VERT_COPY( triverts[2], dest->base.verts[2] );

	// backfacing poly
	VERT_COPY( triverts[3], dest->base.verts[3] );
	VERT_COPY( triverts[2], dest->base.verts[4] );
	VERT_COPY( triverts[1], dest->base.verts[5] );

	// top poly
	VERT_COPY( triverts[3], dest->base.verts[6] );
	VERT_COPY( triverts[0], dest->base.verts[7] );
	VERT_COPY( triverts[2], dest->base.verts[8] );

	// back poly
	VERT_COPY( triverts[3], dest->base.verts[9] );
	VERT_COPY( triverts[1], dest->base.verts[10] );
	VERT_COPY( triverts[0], dest->base.verts[11] );
}


/*
===============
CL_TriangleEffect
===============
*/
void CL_TriangleEffect( int ent, int min_size, int max_size,vec3_t dir, float force, float percentage, int thickness,const vec3_t origin, char * groupname, int type, int lightstyle )
{
	int i, j;
	ctriangle_t *p;
	triangle_t * t;
	int count;
	entity_state_t * e;
	vec3_t mid;
//   float time1, time2;
//   float fourac;
	entity_t entity;
	float radius;
	float mat[3][3];
	clientinfo_t        *ci;

	if (!cl_add_triangles->value)
		return;

	memset( &entity, 0, sizeof( entity_t ) );

	e = &cl_entities[ent].current;
	entity.model = cl.model_draw[e->modelindex];
	VectorCopy( e->angles, entity.angles );
	VectorCopy( e->origin, entity.origin );
	entity.skinnum = e->skinnum;
	entity.anim = e->anim;
	entity.frame = e->frame;
	entity.scale = e->scale;
	entity.flags = e->renderfx;
	entity.lightofs = e->lightofs;
	if (!type)
		type = TESSELATE_FALL;
//   type = TESSELATE_EXPANDANDSHRINK;
	radius = EXPAND_RADIUS;

	//
	// we are creating dynamic triangles, we need to do a trace
	//
	if ( ent == 0 )
	{
		e->alpha = 1.0f;
		entity.skin = (struct image_s *)groupname;
		VectorCopy( dir, entity.angles );
		VectorCopy( origin, entity.origin );
	}

	if ( e->renderfx & RF_CUSTOMSKIN )
	{
		// skinnum is the player number
		ci = &cl.clientinfo[e->skinnum];
		if (ci->skin && ci->damageskin)
		{
			entity.skinnum    = 0;
			entity.model      = ci->model;
			entity.skin       = ci->skin;
			entity.damageskin = ci->damageskin;
		}
		else
		{
			entity.skinnum    = e->skinnum;
			entity.skin       = NULL;
			entity.damageskin = NULL;
		}
	}

	if (e->modelindex || ( ent == 0 ) )
		count = re.TesselateModel( &entity, temp_triangles, MAX_GET_TRIANGLES, min_size, max_size, percentage, origin );
	else
		return;

	VectorScale( dir, force*2, dir );

	i = cl_numfreetriangles - count;
	// try to free some triangles up
	if ( i < 0 )
		CL_KillTriangles( -i );

	AnglesToMat( entity.angles, mat );

	thickness >>= 1;
	for (i=0; i<count; i++)
	{
		if (!free_triangles)
			return;
		p = free_triangles;
		free_triangles = p->next;
		p->next = active_triangles;
		active_triangles = p;
		cl_numfreetriangles--;
		t = &temp_triangles[i];

		if ( type == TESSELATE_EXPANDANDSHRINK )
		{
			int pts;
			vec3_t tverts[3];

			//VectorNegate( mat[1], mat[1] );

			for ( pts=0; pts<3; pts++ )
			{
				MatrixTransformVector( t->verts[pts], mat, tverts[pts] );
				VectorCopy ( tverts[pts], t->verts[pts] );
			}
		}

		VectorAdd( t->verts[0], t->verts[1], mid );
		VectorAdd( mid, t->verts[2], mid );
		VectorScale( mid, 0.33333f, mid );
		VectorCopy( mid, p->org );
		VectorAdd( p->org, e->origin, p->org );
		VectorSubtract( (t->verts[0]), mid, (t->verts[0]));
		VectorSubtract( (t->verts[1]), mid, (t->verts[1]));
		VectorSubtract( (t->verts[2]), mid, (t->verts[2]));
		p->state_time = cl.time - (TRIANGLE_UPDATE_TIME*1000);
		p->lastupdatetime = cl.time - (TRIANGLE_UPDATE_TIME*1000);
		p->type = type;
		p->state = 0;
		p->move = TRIANGLE_MOVE_ORIGIN | TRIANGLE_MOVE_ANGLES;
		p->alphavel = 0;
		p->lightstyle = lightstyle;
		if (e->alpha < 1.0f)
			p->alpha = e->alpha;
		else
			p->alpha = t->alpha;

		if ( type != TESSELATE_EXPANDANDSHRINK )
			VectorCopy( e->angles, p->ang );
		else
			VectorClear( e->angles );

		if ( thickness )
			CL_MakeTriangles( t, p, thickness + (rand()%thickness) );
		else

			CL_MakeTriangles( t, p, 0 );

		if (p->type == TESSELATE_FALL || p->type == TESSELATE_FALLANDTRAIL)
		{
			VectorScale( dir, 2, p->vel );
			for (j=0; j<3; j++)
			{
				p->vel[j] += crand()*2*force;
				p->angvel[j] = crand()*force*3;
				p->angaccel[j] = crand()*force*2;
			}

			p->vel[2] += 40;
			p->accel[0] = crand()*10;
			p->accel[1] = crand()*10;
			p->accel[2] = -TRIANGLE_GRAVITY + crand()*100;
		}
		else if (p->type == TESSELATE_EXPANDANDSHRINK)
		{
			vec3_t dir;
			vec3_t dist;
			vec3_t dest;
			vec3_t centroid;
			int zd, zu;
			const gravityaxis_t *grav;

			grav = &gravity_axis[ EFFECTS_TO_GRAVITYAXIS( e->effects ) ];

			zd = ( 8*((e->solid>>16) & 63) - 128 ) * grav->sign;
			zu = ( ((e->solid>>22) & 511) - 64 ) * grav->sign;

			VectorCopy( e->origin, centroid );
			centroid[ grav->z ] += ( zd + zu ) / 2;

			VectorSubtract( p->org, centroid, dir );
			VectorNormalize( dir );
			VectorScale( dir, radius, dest );
			VectorAdd( dest, e->origin, dest );
			VectorSubtract( dest, p->org, dist );
			VectorScale( dist, 1.0f/EXPAND_GROW_TIME, p->vel );
			VectorClear( p->accel );
		}
		else if (p->type == TESSELATE_IMPLODE)
		{
			vec3_t dist;
			vec3_t centroid;
			int zd, zu;
			const gravityaxis_t *grav;

			grav = &gravity_axis[ EFFECTS_TO_GRAVITYAXIS( e->effects ) ];

			zd = ( 8*((e->solid>>16) & 63) - 128 ) * grav->sign;
			zu = ( ((e->solid>>22) & 511) - 64 ) * grav->sign;

			VectorCopy( e->origin, centroid );
			centroid[ grav->z ] += ( zd + zu ) / 2;

			VectorSubtract( centroid, p->org, dist );
			VectorScale( dist, 1.0f/EXPAND_GROW_TIME, p->vel );
			VectorClear( p->accel );
		}
		p->die_time = cl.time + 15*1000;
	}
}

/*
===============
CL_TriangleTouch
===============
*/
static void CL_TriangleTouch( ctriangle_t * p )
{
	const char * sound;
	struct sfx_s * sfx;
	switch ( (p->base.surftype<<SURF_START_BIT) )
	{
	case SURF_TYPE_GRAVEL:
	case SURF_TYPE_STONE:
	case SURF_TYPE_CONCRETE:
	case SURF_TYPE_DIRT:
		sound = Alias_FindRandom( "debris_rock" );
		break;
	case SURF_TYPE_GLASS:
	case SURF_TYPE_MONITOR:
		sound = Alias_FindRandom( "debris_glass" );
		break;
	case SURF_TYPE_WOOD:
		sound = Alias_FindRandom( "debris_wood" );
		break;
	case SURF_TYPE_FLESH:
		sound = Alias_FindRandom( "debris_flesh" );
		break;
	case SURF_TYPE_METAL:
	case SURF_TYPE_GRILL:
	case SURF_TYPE_DUCT:
		sound = Alias_FindRandom( "debris_metal" );
		break;
	case SURF_TYPE_FABRIC:
	case SURF_TYPE_VEGETATION:
	case SURF_TYPE_WATER:
	case SURF_TYPE_PAPER:
	default:
		sound = Alias_FindRandom( "debris_generic" );
		break;
	}
	if (sound)
	{
		sfx = S_RegisterSound( sound );
		if (sfx)
			S_StartSoundEx ( p->base.origin, 0, CHAN_AUTO, sfx, 0.5, ATTN_NORM, 0, 1.0f, 0, 0, NULL, SOUND_RANDOM_PITCH_20 );
	}
}

void VectorToAngles( vec3_t vec, vec3_t angles )
{
	float forward;
	float yaw, pitch;

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

		forward = ( float )sqrt( vec[0] * vec[0] + vec[1] * vec[1] );
		pitch = ( float )( ( int )( atan2( vec[2], forward ) * 180 / M_PI ) );
		if ( pitch < 0 )
		{
			pitch += 360;
		}
	}

	angles[0] = pitch;
	angles[1] = yaw;
	angles[2] = 0;
}
/*
===============
CL_GetNormalAngles
===============
*/
static void CL_GetNormalAngles( triangle_t * src, vec3_t angles )
{
	vec3_t edge[2];
	vec3_t norm;

	VectorSubtract( src->verts[2], src->verts[0], edge[0] );
	VectorSubtract( src->verts[1], src->verts[0], edge[1] );

	VectorNormalize( edge[0] );
	VectorNormalize( edge[1] );
	CrossProduct( edge[1], edge[0], norm );
	VectorToAngles( norm, angles );
}

/*
===============
CL_BounceTriangle
===============
*/
static void CL_BounceTriangle( ctriangle_t * p )
{
	vec3_t start,end,delta;
	trace_t trace;
	int j;

	if (p->state == 255)
		return;

	if ( cl.time >= p->state_time )
	{
		switch( p->state )
		{
		case 0:
			p->state_time = cl.time + 100*1000;
			break;
		case 250:
			p->die_time = cl.time + 3*1000;
			p->alphavel = -( p->alpha * 1000 / (p->die_time-cl.time) );
			p->state = 255;
			break;
		}
	}

	if (p->state == 255)
		return;

	if (p->move & TRIANGLE_MOVE_ORIGIN)
	{
		VectorCopy( p->org, start );
		VectorCopy( p->org, end );
		for (j=0; j<3; j++)
		{
			p->vel[j] += p->accel[j] * TRIANGLE_UPDATE_TIME;
			end[j] += p->vel[j] * TRIANGLE_UPDATE_TIME;
		}
		trace = CL_WorldTrace ( start, cl_tri_mins, cl_tri_maxs, end, MASK_SHOT );

		VectorSubtract( start, cl.refdef.vieworg, delta );
		if ( DotProduct( delta, delta ) < (50*50) )
			CL_ClipMoveToPlayer ( start, cl_tri_mins, cl_tri_maxs, end, &trace, MASK_SHOT, true );
		if ( trace.fraction < 1.0f )
		{
			vec3_t vel;
			vec3_t accel;
			vec3_t norm;
			float mag;

			VectorCopy( p->accel, accel );
			VectorScale( accel, 0.3f, p->accel );
			p->accel[2] = -TRIANGLE_GRAVITY + 50*crand();
			VectorCopy( p->vel, vel );
			mag = VectorNormalize( vel ) * 0.4f;
			VectorScale( trace.plane.normal, 2.0f, norm );
			VectorAdd( vel, norm, p->vel );
			VectorScale( p->vel, mag, p->vel );
			VectorCopy( trace.endpos, p->org );
			//      Com_Printf("mag = %4.2f\n", mag );
			if ( mag > (TRIANGLE_GRAVITY*1.25*TRIANGLE_UPDATE_TIME) )
			{
				if ( ( ( (rand() & 63) < 16 ) ) )
					CL_TriangleTouch( p );
			}
			else if ( !trace.ent && mag < (TRIANGLE_GRAVITY*TRIANGLE_UPDATE_TIME*0.5f) )
			{
				vec3_t ang2;
				VectorClear( p->accel );
				VectorClear( p->vel );
				VectorClear( p->angaccel );
				VectorClear( p->angvel );
				p->move = 0;
				p->state = 250;
				VectorToAngles( trace.plane.normal, p->ang );
				CL_GetNormalAngles ( &p->base, ang2 );
				VectorSubtract( p->ang, ang2, p->ang );
				p->state_time = cl.time + (BOUNCE_HANGAROUND_TIME*1000);
				p->die_time = cl.time + ((BOUNCE_HANGAROUND_TIME+5)*1000);
			}
//			else if ( (intptr_t)trace.ent == (cl.playernum+1) && mag > (TRIANGLE_GRAVITY*TRIANGLE_UPDATE_TIME*0.5f) )
			else if ( (intptr_t)trace.ent == (cl.playernum+1) )
			{
				float mult;
				float oldmult;

				p->move &= ~TRIANGLE_MOVE_ANGLES;
				mult = SURFACE_DamageMultiplier( p->base.surftype << SURF_START_BIT );
				oldmult = SURFACE_DamageMultiplier( cl.hit_by_debris << SURF_START_BIT );
				if ( mult > oldmult )
					cl.hit_by_debris = p->base.surftype;
			}
		}
		else
		{
			VectorCopy( end, p->org );
		}
	}
	if (p->move & TRIANGLE_MOVE_ANGLES)
	{
		for (j=0; j<3; j++)
		{
			p->angvel[j] += p->angaccel[j] * TRIANGLE_UPDATE_TIME;
			p->ang[j] += p->angvel[j] * TRIANGLE_UPDATE_TIME;
		}
	}
}

/*
===============
CL_ExpandTriangle
===============
*/
static void CL_ExpandTriangle( ctriangle_t * p )
{
	vec3_t start,end;
	trace_t trace;
	int j;
	float factor;

	if (p->state == 255)
		return;

	if ( cl.time >= p->state_time )
	{
		switch( p->state )
		{
		case 0:
			p->alphavel = 0;
			p->state++;
			p->die_time = cl.time + 50*1000;
			p->state_time = cl.time + EXPAND_GROW_TIME*1000;
			break;
		case 1:
			p->state++;
		//	p->state_time = cl.time + EXPAND_HOVER_TIME*1000;
		//	p->move = TRIANGLE_MOVE_ANGLES;
		//	break;
		case 2:
			p->state++;
			p->state_time = cl.time + EXPAND_SHRINK_TIME*1000;
			factor = EXPAND_GROW_TIME / EXPAND_SHRINK_TIME;
			VectorScale( p->vel, -factor, p->vel );
			factor = (EXPAND_GROW_TIME + EXPAND_HOVER_TIME) / EXPAND_SHRINK_TIME;
			VectorScale( p->angvel, -factor, p->angvel );
			p->move = /*TRIANGLE_MOVE_ANGLES |*/ TRIANGLE_MOVE_ORIGIN;
			break;
		case 3:
			p->state = 255;
			p->die_time = cl.time + EXPAND_FINISH_TIME*1000;
			p->alphavel = -( p->alpha * 1000 / (p->die_time-cl.time) );
			p->move = 0;
			break;
		}
	}
	if (p->state == 255)
		return;

	if (p->move & TRIANGLE_MOVE_ORIGIN)
	{
		VectorCopy( p->org, start );
		VectorCopy( p->org, end );
		for (j=0; j<3; j++)
		{
			p->vel[j] += p->accel[j] * TRIANGLE_UPDATE_TIME;
			end[j] += p->vel[j] * TRIANGLE_UPDATE_TIME;
		}
		trace = CL_WorldTrace ( start, cl_tri_mins, cl_tri_maxs, end, MASK_SHOT );
		if ( trace.fraction < 1.0f )
		{
			vec3_t vel;
			vec3_t norm;

			float mag;
			mag = VectorNormalize( vel );
			VectorScale( trace.plane.normal, 2.0f, norm );
			VectorAdd( vel, norm, p->vel );
			VectorScale( p->vel, mag, p->vel );
			VectorCopy( trace.endpos, p->org );
			if ( mag > (TRIANGLE_GRAVITY*3*TRIANGLE_UPDATE_TIME) )
			{
				if ( ( ( (rand() & 63) < 16 ) ) )
					CL_TriangleTouch( p );
			}
		}
		else
		{
			VectorCopy( end, p->org );
		}
	}
	if (p->move & TRIANGLE_MOVE_ANGLES)
	{
		for (j=0; j<3; j++)
		{
			p->angvel[j] += p->angaccel[j] * TRIANGLE_UPDATE_TIME;
			p->ang[j] += p->angvel[j] * TRIANGLE_UPDATE_TIME;
		}
	}
}

/*
==================
CL_ImplodeTriangle
==================
*/
static void CL_ImplodeTriangle( ctriangle_t * p )
{
	vec3_t start,end;
	trace_t trace;
	int j;
	float factor;

	if (p->state == 255)
		return;

	if ( cl.time >= p->state_time )
	{
		switch( p->state )
		{
		case 0:
			p->alphavel = 0;
			p->state++;
			p->die_time = cl.time + 50*1000;
			p->state_time = cl.time + EXPAND_GROW_TIME*1000;
			break;
		case 1:
			p->state++;
			p->state_time = cl.time + EXPAND_SHRINK_TIME*1000;
			factor = EXPAND_GROW_TIME / EXPAND_SHRINK_TIME;
			VectorScale( p->vel, -factor, p->vel );
			factor = (EXPAND_GROW_TIME + EXPAND_HOVER_TIME) / EXPAND_SHRINK_TIME;
			VectorScale( p->angvel, -factor, p->angvel );
			p->move = TRIANGLE_MOVE_ORIGIN;
			break;
		case 2:
			p->state++;
			p->state_time = cl.time + EXPAND_SHRINK_TIME*1000;
			factor = EXPAND_GROW_TIME / EXPAND_SHRINK_TIME;
			VectorScale( p->vel, -factor, p->vel );
			factor = (EXPAND_GROW_TIME + EXPAND_HOVER_TIME) / EXPAND_SHRINK_TIME;
			VectorScale( p->angvel, -factor, p->angvel );
			p->move = TRIANGLE_MOVE_ORIGIN;
			break;
		case 3:
			p->state = 255;
			p->die_time = cl.time + EXPAND_FINISH_TIME*1000;
			p->alphavel = -( p->alpha * 1000 / (p->die_time-cl.time) );
			p->move = 0;
			break;
		}
	}
	if (p->state == 255)
		return;

	if (p->move & TRIANGLE_MOVE_ORIGIN)
	{
		VectorCopy( p->org, start );
		VectorCopy( p->org, end );
		for (j=0; j<3; j++)
		{
			p->vel[j] += p->accel[j] * TRIANGLE_UPDATE_TIME;
			end[j] += p->vel[j] * TRIANGLE_UPDATE_TIME;
		}
		trace = CL_WorldTrace ( start, cl_tri_mins, cl_tri_maxs, end, MASK_SHOT );
		if ( trace.fraction < 1.0f )
		{
			vec3_t vel;
			vec3_t norm;

			float mag;
			mag = VectorNormalize( vel );
			VectorScale( trace.plane.normal, 2.0f, norm );
			VectorAdd( vel, norm, p->vel );
			VectorScale( p->vel, mag, p->vel );
			VectorCopy( trace.endpos, p->org );
			if ( mag > (TRIANGLE_GRAVITY*3*TRIANGLE_UPDATE_TIME) )
			{
				if ( ( ( (rand() & 63) < 16 ) ) )
					CL_TriangleTouch( p );
			}
		}
		else
		{
			VectorCopy( end, p->org );
		}
	}
	if (p->move & TRIANGLE_MOVE_ANGLES)
	{
		for (j=0; j<3; j++)
		{
			p->angvel[j] += p->angaccel[j] * TRIANGLE_UPDATE_TIME;
			p->ang[j] += p->angvel[j] * TRIANGLE_UPDATE_TIME;
		}
	}
}


/*
===============
CL_AddTriangles
===============
*/
void CL_AddTriangles( void )
{
	ctriangle_t *p, *next;
	float updatetime,lerp;
	ctriangle_t *active, *tail;
	int j;
//	vec3_t delta;
//	int leafnum;
//	int cluster;
//	byte *mask;


//	leafnum = CM_PointLeafnum ( cl.refdef.vieworg );
//	cluster = CM_LeafCluster (leafnum);
//	mask = CM_ClusterPVS (cluster);

	active = NULL;
	tail = NULL;

//	Com_Printf("numfreetriangles = %d\n", cl_numfreetriangles );
	for (p=active_triangles; p; p=next)
	{
		next = p->next;

		if (cl.time > p->die_time)
		{       // dead
			p->next = free_triangles;
			free_triangles = p;
			cl_numfreetriangles++;
			continue;
		}

		updatetime = ( (float)cl.time - p->lastupdatetime)*0.001;
		if (updatetime >= TRIANGLE_UPDATE_TIME)
		{
			p->alpha += updatetime*p->alphavel;
			p->base.alpha = p->alpha;
			if (p->base.alpha <= 0)
			{   // faded out
				p->next = free_triangles;
				free_triangles = p;
				cl_numfreetriangles++;
				continue;
			}
			else if (p->base.alpha > 1.0)
				p->base.alpha = 1;
//			Com_Printf("%p updatetime %.2f cl.time %d p->last %.2f\n", p, updatetime, cl.time, p->lastupdatetime );
			p->lastupdatetime = cl.time;
			updatetime = 0;
			VectorCopy( p->org, p->oldorg );
			VectorCopy( p->ang, p->oldang );

			if (p->type == TESSELATE_FALL )
			{
				CL_BounceTriangle( p );
			}
			else if (p->type == TESSELATE_FALLANDTRAIL )
			{
				CL_BounceTriangle( p );
			}
			else if (p->type == TESSELATE_EXPANDANDSHRINK )
			{
				CL_ExpandTriangle( p );
			}
			else if (p->type == TESSELATE_IMPLODE )
			{
				CL_ImplodeTriangle( p );
			}
		}

		p->next = NULL;
		if (!tail)
			active = tail = p;
		else
		{
			tail->next = p;
			tail = p;
		}

		lerp = updatetime/TRIANGLE_UPDATE_TIME;
		if (p->move & TRIANGLE_MOVE_ORIGIN)
		{
			float delta;
			qboolean moving;

			moving = false;
			for (j=0; j<3; j++)
			{
				delta = (p->org[j] - p->oldorg[j]);
				p->base.origin[j] = p->oldorg[j] + lerp * delta;
				if ( delta )
					moving = true;
			}
			if (moving && p->type == TESSELATE_FALLANDTRAIL )
			{
				CL_SinParticleEffect( p->base.origin, vec3_origin, p->lightstyle, 1, false, 0 );
			}
			if ( moving && p->type == TESSELATE_EXPANDANDSHRINK )
			{
				CL_SinParticleEffect( p->base.origin, vec3_origin, p->lightstyle, 1, false, 0 );
			}

		}
		else
		{
			VectorCopy( p->org, p->base.origin );
		}
		if (p->move & TRIANGLE_MOVE_ANGLES)
		{
			for (j=0; j<3; j++)
				p->base.angles[j] = LerpAngle( p->oldang[j], p->ang[j], lerp );
		}
		else
		{
			VectorCopy( p->ang, p->base.angles );
		}
		/*
		leafnum = CM_PointLeafnum (p->base.origin);
		cluster = CM_LeafCluster (leafnum);
		if ( !mask || (mask[cluster>>3] & (1<<(cluster&7)) ) )
			V_AddTriangle ( &p->base );
		VectorSubtract( p->base.origin, cl.refdef.vieworg, delta );
		if ( DotProduct( delta, delta ) < (2000*2000) )
			V_AddTriangle ( &p->base );
		*/
		V_AddTriangle ( &p->base );
	}

	active_triangles = active;
}


/*
 ==============================================================

 RICOHET CODE

 ==============================================================
*/

/*
===============
CL_RicochetSound
===============
*/
void CL_RicochetSound( vec3_t pos, int surfacetype, float timeofs )
{
	const char * sound;
	struct sfx_s * sfx;
	switch ( (surfacetype<<SURF_START_BIT) )
	{
	case SURF_TYPE_GRAVEL:
	case SURF_TYPE_DIRT:
		sound = Alias_FindRandom( "ricochet_dirt" );
		break;
	case SURF_TYPE_GRILL:
	case SURF_TYPE_METAL:
	case SURF_TYPE_DUCT:
		sound = Alias_FindRandom( "ricochet_metal" );
		break;
	case SURF_TYPE_STONE:
	case SURF_TYPE_CONCRETE:
		sound = Alias_FindRandom( "ricochet_rock" );
		break;
	case SURF_TYPE_WOOD:
		sound = Alias_FindRandom( "ricochet_wood" );
		break;
	case SURF_TYPE_WATER:
		sound = Alias_FindRandom( "ricochet_water" );
		break;
	case SURF_TYPE_FLESH:
		sound = Alias_FindRandom( "ricochet_flesh" );
		break;
	case SURF_TYPE_PAPER:
	case SURF_TYPE_FABRIC:
	case SURF_TYPE_VEGETATION:
	default:
		sound = NULL;
		break;
	}
	if (sound)
	{
		sfx = S_RegisterSound( sound );
		if (sfx)
			S_StartSoundEx ( pos, 0, CHAN_AUTO, sfx, 1, ATTN_NORM, timeofs, 1, 0, 0, NULL, SOUND_RANDOM_PITCH_20 );
	}
}

/*
 ==============================================================

 FOOTSTEP CODE

 ==============================================================
*/

void CL_FootStep( centity_t * ent )
{
	trace_t trace;
	vec3_t porigin;
	vec3_t point;
	vec3_t velocity;
	const char * sound;
	struct sfx_s * sfx;
	csurface_t *s;
	vec3_t mins, maxs;
	gravityaxis_t grav;
	int contents, surftype, gravaxis;

	if ( !cl_footsteps->value || ( ent->current.effects & EF_NOFOOTSTEPS ) )
	{
		return;
	}

	if ( current_entity && ( ent->current.number == current_entity->real_entity_number ) )
		gravaxis = current_entity->gravaxis;
	else
		gravaxis = 0;

	grav = gravity_axis[ gravaxis ];

	if (ent->current.solid > 31)
	{       // encoded bbox
		float x, y, zu, zd;

		x = (ent->current.solid & 255);
		y = ((ent->current.solid>>8) & 255);
		zd = ( 8*((ent->current.solid>>16) & 63) - 128 ) * grav.sign;
		zu = ( ((ent->current.solid>>22) & 511) - 64 ) * grav.sign;

		mins[grav.x] = -x;
		maxs[grav.x] = x;
		mins[grav.y] = -y;
		maxs[grav.y] = y;
		mins[grav.z] = zd * grav.sign;
		maxs[grav.z] = zu * grav.sign;
	}
	else
	{
		VectorCopy( vec3_origin, mins );
		VectorCopy( vec3_origin, maxs );
	}

	VectorCopy( ent->current.origin, point );
	point[grav.z] -= 4 * grav.sign;

	VectorAdd( mins, maxs, porigin );
	VectorScale( porigin, 0.5f, porigin );
	VectorAdd( porigin, ent->current.origin, porigin );

	trace = CL_GenericTrace ( porigin, mins, maxs, point, MASK_PLAYERSOLID, false, ent->current.number );
	//trace = CL_WorldTrace ( porigin, mins, maxs, point, MASK_PLAYERSOLID );
	s = trace.surface;
	if (s)
	{
		surftype = s->flags & MASK_SURF_TYPE;
		contents = CL_PMpointcontents ( trace.endpos );
		if ( contents & MASK_WATER )
		{
			contents = CL_PMpointcontents ( porigin );
			if ( contents & MASK_WATER )
			{
				return;
			}
			surftype = SURF_TYPE_WATER;
		}
		// see if we are standing in water
		switch ( surftype )
		{
		case SURF_TYPE_GRAVEL:
			sound = Alias_FindRandom( "footstep_gravel" );
			break;
		case SURF_TYPE_DIRT:
			sound = Alias_FindRandom( "footstep_dirt" );
			break;
		case SURF_TYPE_FABRIC:
		case SURF_TYPE_FLESH:
		case SURF_TYPE_VEGETATION:
			sound = Alias_FindRandom( "footstep_grass" );
			break;
		case SURF_TYPE_GRILL:
			sound = Alias_FindRandom( "footstep_grate" );
			break;
		case SURF_TYPE_METAL:
			sound = Alias_FindRandom( "footstep_metal" );
			break;
		case SURF_TYPE_STONE:
		case SURF_TYPE_WOOD:
		case SURF_TYPE_CONCRETE:
			sound = Alias_FindRandom( "footstep_pave" );
			break;
		case SURF_TYPE_DUCT:
			sound = Alias_FindRandom( "footstep_duct" );
			break;
		case SURF_TYPE_WATER:
			sound = Alias_FindRandom( "footstep_water" );
			break;
		default:
			sound = NULL;
			break;
		}
		if (sound)
		{
			float volume;
			VectorSubtract( ent->current.origin, ent->prev.origin, velocity );

			volume = VectorLength( velocity )/64;
			if (volume > 1.0f)
				volume = 1.0f;
			if ( volume )
			{
				sfx = S_RegisterSound( sound );
				if (sfx)
				{
					S_StartSoundEx ( ent->current.origin, ent->current.number, CHAN_AUTO, sfx, volume, (ATTN_IDLE + ATTN_STATIC)/2, 0, 1, 0, 0, NULL, SOUND_RANDOM_PITCH_20 );
				}
			}
		}
	}
}
/*
 ==============================================================

 STRIKE SURFACE CODE

 ==============================================================
*/

/*
===============
CL_StrikeSurfaceSound
===============
*/
void CL_StrikeSurfaceSound( vec3_t pos, int surfacetype )
{
	const char * sound;
	struct sfx_s * sfx;
	switch ( (surfacetype<<SURF_START_BIT) )
	{
	case SURF_TYPE_GRAVEL:
	case SURF_TYPE_DIRT:
		sound = Alias_FindRandom( "strike_dirt" );
		break;
	case SURF_TYPE_GRILL:
	case SURF_TYPE_METAL:
	case SURF_TYPE_DUCT:
		sound = Alias_FindRandom( "strike_metal" );
		break;
	case SURF_TYPE_STONE:
	case SURF_TYPE_CONCRETE:
		sound = Alias_FindRandom( "strike_rock" );
		break;
	case SURF_TYPE_WOOD:
		sound = Alias_FindRandom( "strike_wood" );
		break;
	case SURF_TYPE_WATER:
		sound = Alias_FindRandom( "strike_water" );
		break;
	case SURF_TYPE_PAPER:
	case SURF_TYPE_FLESH:
	case SURF_TYPE_FABRIC:
	case SURF_TYPE_VEGETATION:
	default:
		sound = Alias_FindRandom( "strike_dirt" );
		break;
	}
	if (sound)
	{
		sfx = S_RegisterSound( sound );
		if (sfx)
			S_StartSoundEx ( pos, 0, CHAN_AUTO, sfx, 1, ATTN_NORM, 0, 1, 0, 0, NULL, SOUND_RANDOM_PITCH_20 );
	}
}

/*
 ==============================================================

 Music Mood Code

 ==============================================================
*/
static char current_soundtrack[ 128 ];
static qboolean music_active=false;
static int music_current_mood;
static int music_fallback_mood;

void CL_UpdateMusic( void )
{
	int currentsong;

	if (!sound_started)
		return;

	if (cls.state != ca_active)
		return;

	if (cl_paused->value)
		return;

	if (!cl.sound_prepped)
		return;

	if (!music_active)
		return;

	if (!s_music->value)
	{
		if (MUSIC_Playing())
		{
			MUSIC_StopAllSongs();
		}
		return;
	}
	if (!MUSIC_Loaded() && music_active && strlen( current_soundtrack ))
	{
		MUSIC_LoadSoundtrackFile( current_soundtrack );
		MUSIC_RegisterSongs();
	}
	currentsong = MusicMood_NameToNum( MUSIC_CurrentSong() );
	if ( music_current_mood != mood_none && music_current_mood != currentsong )
	{
		const char * mood;

		mood = MusicMood_NumToName( music_current_mood );
		if ( MUSIC_SongValid( mood ) )
		{
			Com_DPrintf(DEVELOPER_MSG_SOUND, "Playing %s.\n", mood);
			MUSIC_PlaySong ( mood );
		}
	}
	else if ( music_current_mood == mood_none && MUSIC_Playing() )
	{
		MUSIC_StopAllSongs();
	}
}

void CL_SongEnded( const char * songname )
{
	music_current_mood = music_fallback_mood;
}

void CL_NewSoundtrack( const char * name )
{
	if (!sound_started)
		return;

	if ( Q_stricmp( current_soundtrack, "none" ) )
		music_active = true;

	if ( Q_stricmp( name, current_soundtrack ) )
	{
		strcpy( current_soundtrack, name );
		if ( !Q_stricmp( current_soundtrack, "none" ) )
		{
			music_active = false;
			if (MUSIC_Playing())
			{
				MUSIC_StopAllSongs();
			}
		}
		else
		{
			music_active = true;
			MUSIC_LoadSoundtrackFile( name );
			MUSIC_RegisterSongs();
		}
	}
}

void CL_ResetMusic( void )
{
	music_active = false;
}

void CL_UpdateMood( int current_mood, int fallback_mood )
{
	music_current_mood = current_mood;
	music_fallback_mood = fallback_mood;
}

void CL_RandomLocalSound( const char *alias )
{
	const char * sound;
	struct sfx_s * sfx;

	sound = Alias_FindRandom( alias );

	if (sound)
	{
		sfx = S_RegisterSound( sound );
		if (sfx)
		{
			S_StartSound ( NULL, cl.playernum+1, 0, sfx, 1, 1, 0 );
		}
		else
		{
			Com_DPrintf (DEVELOPER_MSG_SOUND, "CL_LocalRandomSound: can't cache %s\n", sound);
			return;
		}
	}
}


/*
 ==============================================================

 TEMPMODEL MANAGEMENT

 ==============================================================
*/

#define TEMPMODEL_GRAVITY   500
#define  TEMPMODEL_UPDATE_TIME 0.1

static vec3_t cl_tm_mins = {-2, -2, 0 };
static vec3_t cl_tm_maxs = {2, 2, 4 };

#define MAX_TEMPMODELS 128
#define MAX_BOUNDSOUND_LENGTH 32

typedef struct tempmodel_s
{
	struct tempmodel_s  *next;

	struct model_t  *model;

	float last_drawtime;
	float last_updatetime;
	float time;
	float die_time;

	entity_t ent;
	vec3_t vel;
	vec3_t accel;
	float alphavel;
	float scalemax;
	float minscale;
	int flags;
	int num_frames;
	int oldframe;
	int prevframe;
	int lastframe;
	int lastanim;
	char bouncesound[ MAX_BOUNDSOUND_LENGTH ];
} ctempmodel_t;


ctempmodel_t    *active_tempmodels, *free_tempmodels;

ctempmodel_t tempmodels[MAX_TEMPMODELS];
int cl_numtempmodels = MAX_TEMPMODELS;
int cl_numfreetempmodels = MAX_TEMPMODELS;


/*
===============
CL_ClearTempmodels
===============
*/
void CL_ClearTempmodels( void )
{
	int i;

	free_tempmodels = &tempmodels[0];
	active_tempmodels = NULL;
	cl_numfreetempmodels = MAX_TEMPMODELS;

	for (i=0; i<cl_numtempmodels; i++)
		tempmodels[i].next = &tempmodels[i+1];
	tempmodels[cl_numtempmodels-1].next = NULL;
}


/*
===============
CL_CreateTempmodel
===============
*/
static ctempmodel_t * CL_CreateTempmodel( void )
{
	ctempmodel_t * p;

	if (!free_tempmodels)
	{
		return NULL;
	}
	p = free_tempmodels;
	free_tempmodels = p->next;
	p->next = active_tempmodels;
	active_tempmodels = p;
	cl_numfreetempmodels--;

	p->oldframe = 0;
	p->prevframe = 0;

	p->lastframe = -1;
	p->lastanim = -1;
	return p;
}

/*
===============
CL_FreeTempmodel
===============
*/
static void CL_FreeTempmodel( ctempmodel_t * p )
{
	p->next = free_tempmodels;
	free_tempmodels = p;
	cl_numfreetempmodels++;
}

/*
===============
CL_TempModelPhysics
===============
*/
static void CL_TempModelPhysics( ctempmodel_t * p, float updatetime )
{
	vec3_t start,end;
	trace_t trace;
	int j;

	VectorCopy( p->ent.origin, p->ent.oldorigin );
	VectorCopy( p->ent.origin, start );
	VectorCopy( p->ent.origin, end );
	for (j=0; j<3; j++)
	{
		p->vel[j] += p->accel[j] * updatetime;
		end[j] += p->vel[j] * updatetime;
	}
	trace = CL_GenericTrace ( start, cl_tm_mins, cl_tm_maxs, end, MASK_PLAYERSOLID, false, -1 );
	if ( trace.fraction < 1.0f )
	{
		vec3_t vel;
		vec3_t accel;
		vec3_t norm;
		float mag;

		// if it touches it dies
		if ( p->flags & (TEMPMODEL_TOUCH_DIE|TEMPMODEL_TOUCH_DIE_NO_FADE) )
		{
			p->die_time = 0;
			VectorClear( p->vel );
		}
		else if ( p->flags & TEMPMODEL_BOUNCE )
		{
			if ( VectorLength( p->vel ) > ( TEMPMODEL_GRAVITY*TEMPMODEL_UPDATE_TIME*3 ) )
			{
				const char * sound;
				struct sfx_s * sfx;

				if ( p->bouncesound[ 0 ] )
				{
					sound = Alias_FindRandom( p->bouncesound );
					if ( sound )
					{
						sfx = S_RegisterSound( sound );
						if (sfx)
							S_StartSoundEx ( p->ent.origin, 0, CHAN_AUTO, sfx, 0.75, ATTN_NORM, 0, 1.0f, 0, 0, NULL, SOUND_RANDOM_PITCH_20 );
					}
				}
			}
			VectorCopy( p->accel, accel );
			VectorScale( accel, 0.3f, p->accel );
			p->accel[2] = -TEMPMODEL_GRAVITY + 50*crand();
			VectorCopy( p->vel, vel );
			mag = VectorNormalize( vel ) * 0.4f;
			VectorScale( trace.plane.normal, 2.0f, norm );
			VectorAdd( vel, norm, p->vel );
			VectorScale( p->vel, mag, p->vel );
			p->flags |= TEMPMODEL_RANDOM_PITCH;
			p->flags |= TEMPMODEL_RANDOM_YAW;
		}
		else
		{
			VectorClear( p->vel );
		}
	}
	VectorCopy( trace.endpos, p->ent.origin );
	if (
		( trace.fraction < 1 ) &&
		(!trace.ent) &&
		( trace.plane.normal[2] > 0 ) &&
		( VectorLength( p->vel ) < (TEMPMODEL_GRAVITY*TEMPMODEL_UPDATE_TIME*0.5f) )
		)
	{
		VectorClear( p->vel );
		p->flags &= ~TEMPMODEL_USE_PHYSICS;
		VectorCopy( p->ent.origin, p->ent.oldorigin );
		p->flags &= ~TEMPMODEL_RANDOM_ROLL;
		p->flags &= ~TEMPMODEL_RANDOM_PITCH;
		p->flags &= ~TEMPMODEL_RANDOM_YAW;
		if ( !(p->flags & TEMPMODEL_TOUCH_DIE_NO_FADE) )
			p->die_time = cl.time + 3*1000;
		p->alphavel = -( p->ent.alpha * 100 / (p->die_time-cl.time) );
	}
}

/*
===============
CL_AddTempmodels
===============
*/
extern int r_numentities;
extern entity_t r_entities[MAX_ENTITIES];
void CL_AddTempmodels( void )
{
	ctempmodel_t *p, *next;
	float time;
	ctempmodel_t *active, *tail, *at;
	qboolean angles_changed;
	qboolean view_added;
	float updatetime;
	vec3_t saveorigin = {0,0,0};     /* FS: CRT warning */
//	vec3_t delta;
//	int leafnum;
//	int cluster;
//	byte *mask;

	active = NULL;
	tail = NULL;

//	leafnum = CM_PointLeafnum ( cl.refdef.vieworg );
//	cluster = CM_LeafCluster (leafnum);
//	mask = CM_ClusterPVS (cluster);

	for (p=active_tempmodels; p; p=next)
	{
		next = p->next;

		if ( (cl.time > p->die_time) && (p->last_drawtime > 0 ) )
		{       // dead
			CL_FreeTempmodel( p );
			continue;
		}
		time = (cl.time - p->time)*0.001;
//		updatetime = (cl.time - p->last_drawtime)*0.001;
		p->last_drawtime = cl.time;

		p->ent.alpha = p->ent.alpha + time*p->alphavel;

//		p->ent.alpha = p->ent.alpha + updatetime*p->alphavel;

		if (p->ent.alpha < 1.0f)
			p->ent.flags |= RF_TRANSLUCENT;

		if (p->ent.alpha <= 0)
		{   // faded out
			CL_FreeTempmodel( p );
			continue;
		}

		//
		// see if our parent was added this frame
		//
		if (p->ent.parent && p->ent.parent < MAGIC_UNUSED_NUMBER)
		{
			int k;

			for (k=0; k<r_numentities; k++)
			{
				if (r_entities[k].real_entity_number == p->ent.parent)
					break;
			}
			if (k==r_numentities)
			{
				// our parent got killed
				CL_FreeTempmodel( p );
				continue;
			}
		}
		angles_changed = false;

		if (p->ent.alpha > 1.0)
			p->ent.alpha = 1;
		if (p->flags & TEMPMODEL_ANIMATE_SCALE)
		{
			p->ent.scale = p->minscale + ( time * p->scalemax );
		}
		if (p->flags & TEMPMODEL_RANDOM_ROLL)
		{
			p->ent.angles[2] = (180.0f * crand()) + 180.0f;
			angles_changed = true;
		}
		if (p->flags & TEMPMODEL_RANDOM_PITCH)
		{
			p->ent.angles[0] = (180.0f * crand()) + 180.0f;
			angles_changed = true;
		}
		if (p->flags & TEMPMODEL_RANDOM_YAW)
		{
			p->ent.angles[1] = (180.0f * crand()) + 180.0f;
			angles_changed = true;
		}
		if (p->flags & TEMPMODEL_AUTO_ANIMATE)
		{
			if (p->flags & TEMPMODEL_ANIMATE_FAST)
				p->ent.frame = ((int)(time*20))%p->num_frames;
			else
				p->ent.frame = ((int)(time*10))%p->num_frames;

			if (p->ent.frame != p->prevframe)
				p->oldframe = p->prevframe;

			p->prevframe = p->ent.frame;
			p->ent.oldframe = p->oldframe;
			p->ent.oldanim = p->ent.anim;
			p->ent.backlerp = 1.0 - ((time*10)-(int)(time*10));
			VectorCopy ( p->ent.origin, p->ent.oldorigin );
		}
		if (p->flags & TEMPMODEL_ANIMATE_ONCE)
		{
			if (p->flags & TEMPMODEL_ANIMATE_FAST)
				p->ent.frame = (time*20);
			else
				p->ent.frame = (time*10);
			if (p->ent.frame >= p->num_frames)
			{
				// our parent got killed
				CL_FreeTempmodel( p );
				continue;
			}

			if (p->ent.frame != p->prevframe)
				p->oldframe = p->prevframe;

			p->prevframe = p->ent.frame;
			p->ent.oldframe = p->oldframe;
			p->ent.oldanim = p->ent.anim;
			p->ent.backlerp = 1.0 - ((time*10)-(int)(time*10));
			VectorCopy ( p->ent.origin, p->ent.oldorigin );
		}
		if (p->flags & TEMPMODEL_FRAMELERP)
		{
			p->ent.flags |= RF_FRAMELERP;
		}
		if (p->flags & TEMPMODEL_USE_PHYSICS)
		{
			updatetime = (cl.time - p->last_updatetime)*0.001;
			if (updatetime >= TEMPMODEL_UPDATE_TIME)
			{
				p->last_updatetime = cl.time;
				CL_TempModelPhysics( p, updatetime );
				updatetime = 0;
			}

			p->ent.backlerp = 1.0f - (updatetime*10);

			if ( p->flags & TEMPMODEL_LERP )
			{
				int i;
				// interpolate origin
				VectorCopy( p->ent.origin, saveorigin );
				for (i=0; i<3; i++)
				{
					p->ent.origin[i] = p->ent.origin[i] + p->ent.backlerp *
					                   (p->ent.oldorigin[i] - p->ent.origin[i]);
				}
			}
		}
		if (angles_changed)
			AnglesToMat( p->ent.angles, p->ent.mat );

		p->next = NULL;
		if (!tail)
			active = tail = p;
		else
		{
			tail->next = p;
			tail = p;
		}

//		leafnum = CM_PointLeafnum (p->ent.origin);
//		cluster = CM_LeafCluster (leafnum);

/*
		if ( ( p->flags & TEMPMODEL_NOVIS ) || !mask || (mask[cluster>>3] & (1<<(cluster&7)) ) )
		{
			view_added = true;
			V_AddEntity ( &p->ent );
		}
		else
		{
			view_added = false;
		}
		VectorSubtract( p->ent.origin, cl.refdef.vieworg, delta );
		if ( DotProduct( delta, delta ) < (1000*1000) )
		{
			view_added = true;
			V_AddEntity ( &p->ent );
		}
		else
			view_added = false;
 */
		view_added = true;
		V_AddEntity ( &p->ent );

		if ( p->flags & TEMPMODEL_LERP )
		{
			VectorCopy( saveorigin, p->ent.origin );
		}

		at = active_tempmodels;
		// Process the commands
		{
			int handle;
			handle = re.SINMDL_GetDefHandle( p->ent.model );
			if ( view_added && handle >= 0 )
			{
				sinmdl_cmd_t * cmds;

				if ( (p->ent.frame != p->lastframe) || (p->ent.anim != p->lastanim) )
				{
					cmds = SINMDL_Frame_Commands_Client( handle, p->ent.anim, p->ent.frame );
					if ( cmds )
					{
						int j;
						for ( j = 0; j < cmds->num_cmds; j++ )
						{
							current_entity = &p->ent;
							CL_CMDS_ProcessCommands( p->ent.real_entity_number, handle, cmds->cmds[j].num_args, (const char **)cmds->cmds[j].args );
							current_entity = NULL;
						}
					}
					p->lastframe = p->ent.frame;
					p->lastanim = p->ent.anim;
				}
			}
			if ((at != active_tempmodels) && (active == at))
			{
				active = active_tempmodels;
			}
		}
	}
	active_tempmodels = active;
}

/*
===============
CL_TempmodelEffect
===============
*/
void CL_TempmodelEffect( entity_t * ent,float alphavel,float scalemax,float dietime,vec3_t vel,const char * bouncesound,int flags,float minscale )
{
	ctempmodel_t * p;

	p = CL_CreateTempmodel();
	if (!p)
		return;
	p->ent = *ent;
	p->alphavel = alphavel;
	p->scalemax = scalemax;
	p->last_drawtime = -1;
	p->time = cl.time;
	p->die_time = cl.time + (dietime*1000);
	p->flags = flags;
	p->minscale = minscale;
	p->bouncesound[ 0 ] = 0;
	VectorCopy( p->ent.origin, p->ent.oldorigin );
	if ( p->flags & ( TEMPMODEL_USE_PHYSICS ) )
	{
		p->last_updatetime = cl.time - (TEMPMODEL_UPDATE_TIME*1000);
		strncpy( p->bouncesound, bouncesound, MAX_BOUNDSOUND_LENGTH );
		VectorCopy( vel, p->vel );
		if ( p->flags & TEMPMODEL_BOUNCE )
		{
			p->accel[0] = crand()*10;
			p->accel[1] = crand()*10;
			p->accel[2] = -TEMPMODEL_GRAVITY + crand()*100;
		}
		else
		{
			VectorClear( p->accel );
		}
	}
	if (p->flags & (TEMPMODEL_AUTO_ANIMATE|TEMPMODEL_ANIMATE_ONCE))
	{
		//
		// get the number of frames
		//
		p->num_frames = re.GetNumFrames( ent );
	}
	if (p->flags & TEMPMODEL_ALPHAFADE)
	{
		p->alphavel = -1.0f / dietime;
	}
}

/*
 ==============================================================

 DLIGHT MANAGEMENT

 ==============================================================
*/

#define MAX_TEMPDLIGHTS 8

typedef struct tempdlight_s
{
	struct tempdlight_s *next;

	vec3_t origin;
	float r,g,b,radius,decay;
	float last_time;
	float die_time;
} ctempdlight_t;


ctempdlight_t   *active_tempdlights, *free_tempdlights;

ctempdlight_t tempdlights[MAX_TEMPDLIGHTS];
int cl_numtempdlights = MAX_TEMPDLIGHTS;
int cl_numfreetempdlights = MAX_TEMPDLIGHTS;


/*
===============
CL_ClearTempdlights
===============
*/
void CL_ClearTempdlights( void )
{
	int i;

	free_tempdlights = &tempdlights[0];
	active_tempdlights = NULL;
	cl_numfreetempdlights = MAX_TEMPDLIGHTS;

	for (i=0; i<cl_numtempdlights; i++)
		tempdlights[i].next = &tempdlights[i+1];
	tempdlights[cl_numtempdlights-1].next = NULL;
}

/*
===============
CL_CreateTempdlight
===============
*/
static ctempdlight_t * CL_CreateTempdlight( void )
{
	ctempdlight_t * p;

	if (!free_tempdlights)
		return NULL;
	p = free_tempdlights;
	free_tempdlights = p->next;
	p->next = active_tempdlights;
	active_tempdlights = p;
	cl_numfreetempdlights--;
	return p;
}

/*
===============
CL_FreeTempdlight
===============
*/
static void CL_FreeTempdlight( ctempdlight_t * p )
{
	p->next = free_tempdlights;
	free_tempdlights = p;
	cl_numfreetempdlights++;
}

/*
===============
CL_AddTempdlights
===============
*/
void CL_AddTempdlights( void )
{
	ctempdlight_t *p, *next;
	float time;
	int i;
	ctempdlight_t   *active, *tail;

	active = NULL;
	tail = NULL;

	for (p=active_tempdlights; p; p=next)
	{
		next = p->next;

		if ( (cl.time > p->die_time) )
		{       // dead
			CL_FreeTempdlight( p );
			continue;
		}
		time = (cl.time - p->last_time)*0.001;
		time /= 0.05;
		for ( i = 0; i < time; i++ )
		{
			p->last_time += 50.0f;
			p->radius *= p->decay;
		}

		if (p->radius < 1)
		{   // faded out
			CL_FreeTempdlight( p );
			continue;
		}

		p->next = NULL;
		if (!tail)
			active = tail = p;
		else
		{
			tail->next = p;
			tail = p;
		}

		V_AddLight ( p->origin, p->radius, p->r, p->g, p->b );
	}
	active_tempdlights = active;
}

/*
===============
CL_TempdlightEffect
===============
*/
void CL_TempdlightEffect( vec3_t origin,float r,float g,float b,float radius,float time,float decay )
{
	ctempdlight_t * p;

	p = CL_CreateTempdlight();
	if (!p)
		return;
	VectorCopy( origin, p->origin );
	p->r = r;
	p->g = g;
	p->b = b;
	p->radius = radius;
	p->die_time = cl.time + (time*1000);
	p->last_time = cl.time;
	p->decay = decay;
}


// Returns float from 0 to n
float CL_Random( float n )
{
	return ( ( ( float )( rand() & 0x7fff ) ) / ( ( float )0x8000 ) ) * n;
}

// Returns -1 to 1
static float CL_CRandom( void )
{
	return CL_Random( 2 ) - 1;
}


static void TempBulletHole( vec3_t origin,trace_t trace )
{
	entity_t ent;
	int flags=0;
	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 );

	life = 5;

	assert( cl_bullethole );
	ent.model = cl_bullethole;

	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 CL_BulletEffect( trace_t trace, float timeofs )
{
	int surfflags, surftype;

	if ( !trace.surface )
		return;

	if ( trace.surface && ( trace.surface->flags & SURF_SKY ) )
		return;

	surfflags = trace.surface->flags;
	surftype = SURFACETYPE_FROM_FLAGS( surfflags );

	if ( ( (rand() & 0xf) < 3 ) || timeofs )
		CL_RicochetSound( trace.endpos, surftype&0xf, timeofs );


	if ( trace.ent )
	{
		// make sure it is a b-model
		if ( cl_entities[(int)(intptr_t)trace.ent].current.solid == 31 )
			CL_SinParticleEffect ( trace.endpos, trace.plane.normal, 120, 8, false, 0 );
		return;
	}
	else
	{
		CL_SinParticleEffect ( trace.endpos, trace.plane.normal, 120, 4, false, 0 );
	}

	CL_TriangleEffect( 0,
	                   MIN_TRIANGLE_SIZE,
	                   MAX_TRIANGLE_SIZE,
	                   trace.plane.normal,
	                   TRIANGLE_POWER,
	                   TRIANGLE_PERCENTAGE,
	                   TRIANGLE_THICKNESS,
	                   trace.endpos,
	                   trace.surface->groupname,
	                   TESSELATE_FALLANDTRAIL,
	                   125 );

	if ( cl_add_bulletholes->value )
		TempBulletHole( trace.endpos, trace );
}

void CL_ShootBullets( centity_t *ent, int num, vec3_t spread, float distance )
{
	vec3_t start, end, dir, right, up, f_end, f_up, f_right;
	trace_t trace;
	float mat[ 3 ][ 3 ];
	int i;
	const gravityaxis_t *grav;

	// ent is owner of gun, current_entity is gun
	current_entity->angles[PITCH] = -current_entity->angles[PITCH];
	AnglesToMat( current_entity->angles, mat );

	grav = &gravity_axis[ current_entity->gravaxis ];

	VectorCopy( mat[ 0 ], dir );
	VectorCopy( ent->current.origin, start );

	// Adjust the height
	if ( ent->current.effects & EF_CROUCH )
	{
		start[ grav->z ] += CROUCH_EYE_HEIGHT * grav->sign;
	}
	else
	{
		start[ grav->z ] += STAND_EYE_HEIGHT * grav->sign;
	}

	VectorCopy( mat[ 1 ], right );
	VectorCopy( mat[ 2 ], up );
	VectorMA( start, distance, dir, end );

	for ( i=0; i<num; i++)
	{
		float randx, randy;

		randx = CL_CRandom();
		randy = CL_CRandom();

		// Scatter the bullet
		VectorScale( right, randx * spread[ 0 ], f_right );
		VectorScale( up, randy * spread[ 1 ], f_up );
		VectorAdd( end, f_right, f_end );
		VectorAdd( f_end, f_up, f_end );
		trace = CL_GenericTrace( start,vec3_origin, vec3_origin, f_end, MASK_SHOT, true, ent->current.number );
#if 0
		Com_Printf( "Client REFDEF Angles:%0.2f %0.2f %0.2f\n",cl.refdef.viewangles[0],cl.refdef.viewangles[1],cl.refdef.viewangles[2] );
		Com_Printf( "Client Bullet Angles:%0.2f %0.2f %0.2f\n",current_entity->angles[0],current_entity->angles[1],current_entity->angles[2] );
		Com_Printf( "Client Trace Start  :%0.2f %0.2f %0.2f\n",start[0],start[1],start[2] );
		Com_Printf( "Client Trace End    :%0.2f %0.2f %0.2f\n",trace.endpos[0],trace.endpos[1],trace.endpos[2] );
#endif
		// Do the effect
		CL_BulletEffect( trace, 0 );
	}
}

/*
=================
CL_DefInfo
=================
*/
void CL_DefInfo( void )
{
	const char *model;
	int defhandle;

	if (Cmd_Argc() != 2) {
		Com_Printf ( "usage: definfo <model>\n" );
		return;
	}

	model = Cmd_Argv( 1 );
	defhandle = re.SINMDL_GetDefHandle( re.RegisterModel( model ) );
	if ( defhandle < 0 ) {
		Com_Printf( "DefInfo: could not find model %s.\n", model );
		return;
	}
	SINMDL_ModelInfo( defhandle );
}

/*
=================
CL_Detect3DNow
=================
*/
int CL_Detect3DNow( void )
{
#if defined(_MSC_VER) && defined(_M_IX86)
	_asm
	{
		pushfd                  // save EFLAGS to stack.
		pop eax                 // store EFLAGS in EAX.
		mov edx, eax            // save in EBX for testing later.
		xor     eax, 0x200000   // switch bit 21.
		push eax                // copy "changed" value to stack.
		popfd                   // save "changed" EAX to EFLAGS.
		pushfd
		pop eax
		cmp eax, edx            // See if bit changeable.
		jz No3DNow              // CPU doesn't support CPUID instruction.

		mov eax, 0x80000000     // Check for support of extended functions.
		_emit   0x0f            // CPUID
		_emit   0xa2
		cmp eax, 0x80000001     // Make sure function 0x80000001 supported.
		jb No3DNow
		// Now we know extended functions are supported.
		mov eax, 0x80000001     // Get extended features.
		xor     edx, edx        // Clear edx - not really necessary.
		    _emit   0x0f        // CPUID
		_emit   0xa2
		test edx, 0x80000000    // edx contains extended feature flags
		jz No3DNow              // bit 31 = 1 => 3DNow!
	}

	return 1;
No3DNow:
#else
/* FIXME -- add a GCC inline asm version */
#endif
	return 0;
}

//
// Speech synthesis
//

#define MAX_SPEECH_SOUNDS 16
typedef struct speech_s
{
	sfx_t sound;
	int timeout;
} speech_t;

static speech_t speechsounds[ MAX_SPEECH_SOUNDS ];
static qboolean speechactive = false;

/*
=================
CL_FindFreeTalkSound
=================
*/
static speech_t * CL_FindFreeTalkSound( void )
{
	int i;

	for( i = 0; i < MAX_SPEECH_SOUNDS; i++ )
	{
		if ( speechsounds[ i ].sound.cache )
		{
			if ( cls.realtime > speechsounds[ i ].timeout )
				S_FreeSound( &speechsounds[ i ].sound );
		}
		if ( !speechsounds[ i ].sound.cache )
		{
			speechactive = true;
			return &speechsounds[ i ];
		}
	}
	return NULL;
}
/*
=================
CL_FreeTalkSounds
=================
*/
static void CL_FreeTalkSounds( void )
{
	int i;

	for( i = 0; i < MAX_SPEECH_SOUNDS; i++ )
	{
		if ( speechsounds[ i ].sound.cache )
		{
			S_FreeSound( &speechsounds[ i ].sound );
		}
	}
}

/*
=================
CL_InitTalkSounds
=================
*/
void CL_InitTalkSounds( void )
{
	int i;

	if ( speechactive )
	{
		CL_FreeTalkSounds();
	}

	speechactive = false;

	for( i = 0; i < MAX_SPEECH_SOUNDS; i++ )
	{
		speechsounds[ i ].sound.cache = NULL;
		speechsounds[ i ].timeout = 0;
		sprintf( speechsounds[ i ].sound.name, "speech%d", i );
	}
}

#define SPEECH_RATE 8000
#define SPEECH_BITS 16
#define SPEECH_CHANNELS 1
#define SPEECH_GAIN 4

/*
=================
CL_Talk
=================
*/

extern short *SPEECH_Say (float gain, float pitch, float speed, const char *text, int *length);

void CL_Talk( const char * talkstring )
{
	static int lasttalktime = -1;
	int playernumber;
	float pitch;
	int length;
	short *buf;
	const char * text;
	const char * ptr;
	speech_t *speech;

	// don't let anyone spam us with speech
	if ( ( cl_nospeech->value ) || ( cls.realtime < lasttalktime ) )
		return;

	lasttalktime = cls.realtime + cl_speechdelay->value;

	speech = CL_FindFreeTalkSound();
	if ( !speech )
	{
		Com_DPrintf(DEVELOPER_MSG_SOUND, "No free speech voices\n");
		return;
	}
	ptr = COM_GetToken( &talkstring, false );
	playernumber = atol( ptr );

	text = talkstring;

	ptr = COM_GetToken( &talkstring, false );
	pitch = Sinatof( ptr );

	if ( !pitch )
	{
		pitch = 1.0f;
		talkstring = text;
	}

	con.ormask = 128;
	Com_Printf ( "%s:%s", cl.clientinfo[ playernumber - 1 ].name, talkstring );
	con.ormask = 0;

	buf = SPEECH_Say( SPEECH_GAIN, pitch, 1.0f, talkstring, &length );

	if ( !length )
		return;

	speech->sound.cache = S_LoadRaw( SPEECH_RATE, SPEECH_BITS, SPEECH_CHANNELS, length * sizeof( *buf ), ( byte * )buf );

	if ( speech->sound.cache )
	{
		speech->timeout = cls.realtime + ( length * 1000 / SPEECH_RATE ) + 500;
		S_StartSound ( NULL, playernumber, CHAN_VOICE, &speech->sound, 1.0f, ATTN_NORM, 0 );
	}
}
