//-----------------------------------------------------------------------------
//
// $Id$
//
//-----------------------------------------------------------------------------
//
// $Log$
// Revision 1.43  2004/01/26 21:26:08  makro
// no message
//
// Revision 1.42  2003/08/02 22:29:07  makro
// Made underwater bubbles 3 times smaller
//
// Revision 1.41  2002/08/25 07:09:20  niceass
// added "life" setting to func_pressure
//
// Revision 1.40  2002/08/21 07:09:54  jbravo
// Removed an extra definition of the vtos call
//
// Revision 1.39  2002/07/22 07:27:21  niceass
// better fog laser support
//
// Revision 1.38  2002/06/23 04:36:27  niceass
// change to foglaser
//
// Revision 1.37  2002/06/21 21:06:56  niceass
// laserfog stuff
//
// Revision 1.36  2002/06/21 04:11:34  niceass
// fog laser
//
// Revision 1.35  2002/06/16 20:06:13  jbravo
// Reindented all the source files with "indent -kr -ut -i8 -l120 -lc120 -sob -bad -bap"
//
// Revision 1.34  2002/06/16 19:12:52  jbravo
// Removed the MISSIONPACK ifdefs and missionpack only code.
//
// Revision 1.33  2002/06/09 05:16:33  niceass
// pressure change
//
// Revision 1.32  2002/06/03 00:39:29  blaze
// dont make a sound when bouncing on the ground
//
// Revision 1.31  2002/05/26 05:16:12  niceass
// pressure
//
// Revision 1.30  2002/05/19 21:27:51  blaze
// added force and buoyancy to breakables
//
// Revision 1.29  2002/04/29 06:14:10  niceass
// pressure
//
// Revision 1.28  2002/04/26 03:39:34  jbravo
// added tkok, fixed players always leaving zcam modes when player thats
// beeing tracked dies
//
// Revision 1.27  2002/04/23 06:10:10  niceass
// some good tabbing
//
// Revision 1.26  2002/04/06 21:43:58  makro
// New surfaceparm system
//
// Revision 1.25  2002/04/03 15:51:01  jbravo
// Small warning fixes
//
// Revision 1.24  2002/04/03 03:13:48  blaze
// NEW BREAKABLE CODE - will break all old breakables(wont appear in maps)
//
// Revision 1.23  2002/03/31 03:31:24  jbravo
// Compiler warning cleanups
//
// Revision 1.22  2002/03/21 02:17:39  blaze
// more func_explosive goodness
//
// Revision 1.21  2002/03/21 00:26:46  blaze
// some fixing of func_explosive
//
// Revision 1.20  2002/03/04 20:50:59  jbravo
// No floating scores over dead bodies, triangles disabled, and no viewing
// names of enemys just of teammates.
//
// Revision 1.19  2002/01/24 14:20:53  jbravo
// Adding func_explosive and a few new surfaceparms
//
// Revision 1.18  2002/01/11 19:48:29  jbravo
// Formatted the source in non DOS format.
//
// Revision 1.17  2001/12/31 16:28:41  jbravo
// I made a Booboo with the Log tag.
//
//
//-----------------------------------------------------------------------------
// Copyright (C) 1999-2000 Id Software, Inc.
//
// cg_effects.c -- these functions generate localentities, usually as a result
// of event processing

#include "cg_local.h"

extern void CG_Particle_Bleed(qhandle_t pshader, vec3_t start, vec3_t dir, int fleshEntityNum, int duration);

/*
===============
CG_ShrapnelSpark

Moved from cg_weapons
Added by Elder
Modified tracer code
I really don't know what's going on half the time here :)
===============
*/

void CG_CreateTracer(int entity, vec3_t start, vec3_t end);

void CG_ShrapnelSpark(vec3_t source, vec3_t dest, float width, float length)
{
	vec3_t forward, right;
//	polyVert_t verts[4];
	vec3_t line;
	float len, begin, end;
//	vec3_t start, finish;

	//vec3_t                midpoint;

	// tracer
	VectorSubtract(dest, source, forward);
	len = VectorNormalize(forward);

	// start at least a little ways from the muzzle
	//if ( len < 10 ) {
	//return;
	//}

	begin = crandom() * 8;
	end = begin + length;
	if (end > len) {
		end = len;
	}
//	VectorMA(source, begin, forward, start);
//	VectorMA(source, end, forward, finish);

	line[0] = DotProduct(forward, cg.refdef.viewaxis[1]);
	line[1] = DotProduct(forward, cg.refdef.viewaxis[2]);

	VectorScale(cg.refdef.viewaxis[1], line[1], right);
	VectorMA(right, -line[0], cg.refdef.viewaxis[2], right);
	VectorNormalize(right);

/*	VectorMA(finish, width, right, verts[0].xyz);
	verts[0].st[0] = 0;
	verts[0].st[1] = 1;
	verts[0].modulate[0] = 255;
	verts[0].modulate[1] = 255;
	verts[0].modulate[2] = 255;
	verts[0].modulate[3] = 255;

	VectorMA(finish, -width, right, verts[1].xyz);
	verts[1].st[0] = 1;
	verts[1].st[1] = 0;
	verts[1].modulate[0] = 255;
	verts[1].modulate[1] = 255;
	verts[1].modulate[2] = 255;
	verts[1].modulate[3] = 255;

	VectorMA(start, -width, right, verts[2].xyz);
	verts[2].st[0] = 1;
	verts[2].st[1] = 1;
	verts[2].modulate[0] = 255;
	verts[2].modulate[1] = 255;
	verts[2].modulate[2] = 255;
	verts[2].modulate[3] = 255;

	VectorMA(start, width, right, verts[3].xyz);
	verts[3].st[0] = 0;
	verts[3].st[1] = 0;
	verts[3].modulate[0] = 255;
	verts[3].modulate[1] = 255;
	verts[3].modulate[2] = 255;
	verts[3].modulate[3] = 255; */

	//trap_R_AddPolyToScene(cgs.media.tracerShader, 4, verts);
	CG_CreateTracer(ENTITYNUM_WORLD, source, dest);

	//midpoint[0] = ( start[0] + finish[0] ) * 0.5;
	//midpoint[1] = ( start[1] + finish[1] ) * 0.5;
	//midpoint[2] = ( start[2] + finish[2] ) * 0.5;

	// add the tracer sound
	//trap_S_StartSound( midpoint, ENTITYNUM_WORLD, CHAN_AUTO, cgs.media.tracerSound );

}

/*
==================
CG_BubbleTrail

Bullets shot underwater
==================
*/
void CG_BubbleTrail(vec3_t start, vec3_t end, float spacing)
{
	vec3_t move;
	vec3_t vec;
	float len;
	int i;

	if (cg_noProjectileTrail.integer) {
		return;
	}

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

	// advance a random amount first
	i = rand() % (int) spacing;
	VectorMA(move, i, vec, move);

	VectorScale(vec, spacing, vec);

	for (; i < len; i += spacing) {
		localEntity_t *le;
		refEntity_t *re;

		le = CG_AllocLocalEntity();
		le->leFlags = LEF_PUFF_DONT_SCALE;
		le->leType = LE_MOVE_SCALE_FADE;
		le->startTime = cg.time;
		le->endTime = cg.time + 1000 + random() * 250;
		le->lifeRate = 1.0 / (le->endTime - le->startTime);

		re = &le->refEntity;
		re->shaderTime = cg.time / 1000.0f;

		re->reType = RT_SPRITE;
		re->rotation = 0;
		//Makro - changed from 3
		re->radius = 1;
		re->customShader = cgs.media.waterBubbleShader;
		re->shaderRGBA[0] = 0xff;
		re->shaderRGBA[1] = 0xff;
		re->shaderRGBA[2] = 0xff;
		re->shaderRGBA[3] = 0xff;

		le->color[3] = 1.0;

		le->pos.trType = TR_LINEAR;
		le->pos.trTime = cg.time;
		VectorCopy(move, le->pos.trBase);
		le->pos.trDelta[0] = crandom() * 5;
		le->pos.trDelta[1] = crandom() * 5;
		le->pos.trDelta[2] = crandom() * 5 + 6;

		VectorAdd(move, vec, move);
	}
}

/*
=====================
CG_SmokePuff

Adds a smoke puff or blood trail localEntity.
=====================
*/
localEntity_t *CG_SmokePuff(const vec3_t p, const vec3_t vel,
			    float radius,
			    float r, float g, float b, float a,
			    float duration, int startTime, int fadeInTime, int leFlags, qhandle_t hShader)
{
	static int seed = 0x92;
	localEntity_t *le;
	refEntity_t *re;

//      int fadeInTime = startTime + duration / 2;

	le = CG_AllocLocalEntity();
	le->leFlags = leFlags;
	le->radius = radius;

	re = &le->refEntity;
	re->rotation = Q_random(&seed) * 360;
	re->radius = radius;
	re->shaderTime = startTime / 1000.0f;

	le->leType = LE_MOVE_SCALE_FADE;
	le->startTime = startTime;
	le->fadeInTime = fadeInTime;
	le->endTime = startTime + duration;
	if (fadeInTime > startTime) {
		le->lifeRate = 1.0 / (le->endTime - le->fadeInTime);
	} else {
		le->lifeRate = 1.0 / (le->endTime - le->startTime);
	}
	le->color[0] = r;
	le->color[1] = g;
	le->color[2] = b;
	le->color[3] = a;

	le->pos.trType = TR_LINEAR;
	le->pos.trTime = startTime;
	VectorCopy(vel, le->pos.trDelta);
	VectorCopy(p, le->pos.trBase);

	VectorCopy(p, re->origin);
	re->customShader = hShader;

	// rage pro can't alpha fade, so use a different shader
	if (cgs.glconfig.hardwareType == GLHW_RAGEPRO) {
		re->customShader = cgs.media.smokePuffRageProShader;
		re->shaderRGBA[0] = 0xff;
		re->shaderRGBA[1] = 0xff;
		re->shaderRGBA[2] = 0xff;
		re->shaderRGBA[3] = 0xff;
	} else {
		re->shaderRGBA[0] = le->color[0] * 0xff;
		re->shaderRGBA[1] = le->color[1] * 0xff;
		re->shaderRGBA[2] = le->color[2] * 0xff;
		re->shaderRGBA[3] = 0xff;
	}

	re->reType = RT_SPRITE;
	re->radius = le->radius;

	return le;
}

/*
==================
CG_ScorePlum
==================
*/
void CG_ScorePlum(int client, vec3_t org, int score)
{
	localEntity_t *le;
	refEntity_t *re;
	vec3_t angles;
	static vec3_t lastPos;

	// only visualize for the client that scored
	if (client != cg.predictedPlayerState.clientNum || cg_scorePlum.integer == 0) {
		return;
	}
// JBravo: and not in team based games
	if (cgs.gametype >= GT_TEAM) {
		return;
	}

	le = CG_AllocLocalEntity();
	le->leFlags = 0;
	le->leType = LE_SCOREPLUM;
	le->startTime = cg.time;
	le->endTime = cg.time + 4000;
	le->lifeRate = 1.0 / (le->endTime - le->startTime);

	le->color[0] = le->color[1] = le->color[2] = le->color[3] = 1.0;
	le->radius = score;

	VectorCopy(org, le->pos.trBase);
	if (org[2] >= lastPos[2] - 20 && org[2] <= lastPos[2] + 20) {
		le->pos.trBase[2] -= 20;
	}
	//CG_Printf( "Plum origin %i %i %i -- %i\n", (int)org[0], (int)org[1], (int)org[2], (int)Distance(org, lastPos));
	VectorCopy(org, lastPos);

	re = &le->refEntity;

	re->reType = RT_SPRITE;
	re->radius = 16;

	VectorClear(angles);
	AnglesToAxis(angles, re->axis);
}

/*
====================
CG_MakeExplosion
====================
*/
localEntity_t *CG_MakeExplosion(vec3_t origin, vec3_t dir,
				qhandle_t hModel, qhandle_t shader, int msec, qboolean isSprite)
{
	float ang;
	localEntity_t *ex;
	int offset;
	vec3_t tmpVec, newOrigin;

	if (msec <= 0) {
		CG_Error("CG_MakeExplosion: msec = %i", msec);
	}
	// skew the time a bit so they aren't all in sync
	offset = rand() & 63;

	ex = CG_AllocLocalEntity();
	if (isSprite) {
		ex->leType = LE_SPRITE_EXPLOSION;

		// randomly rotate sprite orientation
		ex->refEntity.rotation = rand() % 360;
		VectorScale(dir, 16, tmpVec);
		VectorAdd(tmpVec, origin, newOrigin);
	} else {
		ex->leType = LE_EXPLOSION;
		VectorCopy(origin, newOrigin);

		// set axis with random rotate
		if (!dir) {
			AxisClear(ex->refEntity.axis);
		} else {
			ang = rand() % 360;
			VectorCopy(dir, ex->refEntity.axis[0]);
			RotateAroundDirection(ex->refEntity.axis, ang);
		}
	}

	ex->startTime = cg.time - offset;
	ex->endTime = ex->startTime + msec;

	// bias the time so all shader effects start correctly
	ex->refEntity.shaderTime = ex->startTime / 1000.0f;

	ex->refEntity.hModel = hModel;
	ex->refEntity.customShader = shader;

	// set origin
	VectorCopy(newOrigin, ex->refEntity.origin);
	VectorCopy(newOrigin, ex->refEntity.oldorigin);

	ex->color[0] = ex->color[1] = ex->color[2] = 1.0;

	return ex;
}

/*
=================
CG_BleedSpray

Elder: This is a super blood spray for SSG hits
Based on bubble trail code + other stuff
=================
*/
#define MAX_SPRAY_BURSTS	16
void CG_BleedSpray(vec3_t start, vec3_t end, int entityNum, int numBursts)
{
	//vec3_t                dir;
	vec3_t trueEnd;
	vec3_t move;
	vec3_t vec;
	vec3_t velocity;

	localEntity_t *blood;
	float len;
	int i;
	int spacing = 30;
	int bloodCount = 0;
	trace_t tr;

	if (!cg_blood.integer || cg_RQ3_bloodStyle.integer == 0) {
		return;
	}
	//Clamp number so we don't generate too many blood entities
	if (numBursts > MAX_SPRAY_BURSTS)
		numBursts = MAX_SPRAY_BURSTS;

	VectorCopy(end, move);
	VectorSubtract(end, start, vec);

	//Calculate true length via start/end points
	VectorCopy(vec, trueEnd);
	VectorNormalize(trueEnd);

	//VectorScale (trueEnd, 300 + rand() % 100, trueEnd); 
	//VectorAdd (end, trueEnd, trueEnd);
	VectorMA(end, 300 + rand() % 100, trueEnd, trueEnd);

	// Check end point validity so it doesn't go through walls
	// If it does go through wall, take the trace's endpoint
	CG_Trace(&tr, start, NULL, NULL, trueEnd, entityNum, CONTENTS_SOLID);
	if (tr.fraction != 1.0)
		VectorCopy(tr.endpos, trueEnd);

	VectorSubtract(trueEnd, start, vec);

	len = VectorNormalize(vec);

	//Set velocity
	VectorScale(vec, 10, velocity);
	if (cg_RQ3_bloodStyle.integer == 1)
		velocity[2] += 30;
	else
		velocity[2] -= 10;

	// advance a random amount first
	i = rand() % (int) spacing;
	VectorMA(move, i, vec, move);
	VectorScale(vec, spacing, vec);

	for (; i < len; i += spacing) {
		//restrict amount of spurts
		if (bloodCount++ > numBursts)
			break;

		blood = CG_SmokePuff(move, velocity, 8,
				     1, 1, 1, 1,
				     1500 + rand() % 250,
				     cg.time, 0, LEF_TUMBLE | LEF_PUFF_DONT_SCALE, cgs.media.bloodTrailShader);

		blood->refEntity.rotation = rand() % 360;
		//Check blood style
		if (cg_RQ3_bloodStyle.integer == 1) {
			blood->leType = LE_FRAGMENT;
			blood->leMarkType = LEMT_BLOOD;
			blood->pos.trType = TR_GRAVITY;
			blood->bounceFactor = 0.4f;
		}

		VectorAdd(move, vec, move);
	}
}

/*
=================
CG_EjectBloodSplat

Drop a splat
=================
*/

void CG_EjectBloodSplat(vec3_t origin, vec3_t velocity, int amount, int duration)
{
	int i;
	localEntity_t *blood;
	vec3_t bOrigin;
	vec3_t bVelocity;

	if (!cg_blood.integer)
		return;

	for (i = 0; i < amount; i++) {
		VectorCopy(origin, bOrigin);
		VectorCopy(velocity, bVelocity);
		bOrigin[0] += rand() % 6 - 3;
		bOrigin[1] += rand() % 6 - 3;
		bVelocity[0] += rand() % 6 - 3;
		bVelocity[1] += rand() % 6 - 3;

		blood = CG_SmokePuff(bOrigin, bVelocity, 4,
				     1, 1, 1, 0.6f,
				     duration + rand() % 250, cg.time, 0,
				     LEF_TUMBLE | LEF_PUFF_DONT_SCALE, cgs.media.bloodTrailShader);
		blood->refEntity.rotation = rand() % 360;
		blood->leType = LE_FRAGMENT;
		blood->leType = LE_FRAGMENT;
		blood->leMarkType = LEMT_BLOOD;
		blood->pos.trType = TR_GRAVITY;
		blood->bounceFactor = 0.4f;
	}
}

/*
=================
CG_BleedParticleSpray

This is a particle blood spray not unlike Quake 2's
Err, it's not working well right now :/
=================
*/

void CG_BleedParticleSpray(vec3_t start, vec3_t dir, int fleshEntityNum, int amount, int duration)
{
	int i;

	if (!cg_RQ3_bloodStyle.integer || !cg_blood.integer)
		return;

	for (i = 0; i < amount; i++) {
		CG_Particle_Bleed(cgs.media.bloodExplosionShader, start, dir, fleshEntityNum, duration + rand() % 250);
	}

}

/*
=================
CG_Bleed

This is the spurt of blood when a character gets hit
=================
*/
void CG_Bleed(vec3_t origin, int entityNum)
{
	localEntity_t *ex;

	if (!cg_blood.integer) {
		return;
	}

	ex = CG_AllocLocalEntity();
	ex->leType = LE_EXPLOSION;

	ex->startTime = cg.time;
	ex->endTime = ex->startTime + 500;

	VectorCopy(origin, ex->refEntity.origin);
	ex->refEntity.reType = RT_SPRITE;
	ex->refEntity.rotation = rand() % 360;
	ex->refEntity.radius = 24;

	ex->refEntity.customShader = cgs.media.bloodExplosionShader;

	// don't show player's own blood in view
	if (entityNum == cg.snap->ps.clientNum) {
		ex->refEntity.renderfx |= RF_THIRD_PERSON;
	}
}

/*
==================
CG_LaunchGib
==================
*/
void CG_LaunchGib(vec3_t origin, vec3_t velocity, qhandle_t hModel)
{
	localEntity_t *le;
	refEntity_t *re;

	le = CG_AllocLocalEntity();
	re = &le->refEntity;

	le->leType = LE_FRAGMENT;
	le->startTime = cg.time;
	le->endTime = le->startTime + 5000 + random() * 3000;

	VectorCopy(origin, re->origin);
	AxisCopy(axisDefault, re->axis);
	re->hModel = hModel;

	le->pos.trType = TR_GRAVITY;
	VectorCopy(origin, le->pos.trBase);
	VectorCopy(velocity, le->pos.trDelta);
	le->pos.trTime = cg.time;

	le->bounceFactor = 0.6f;

	le->leBounceSoundType = LEBS_BLOOD;
	le->leMarkType = LEMT_BLOOD;
}

/*
===================
CG_GibPlayer

Generated a bunch of gibs launching out from the bodies location
===================
*/
#define	GIB_VELOCITY	250
#define	GIB_JUMP		250
void CG_GibPlayer(vec3_t playerOrigin)
{
	vec3_t origin, velocity;

	if (!cg_blood.integer) {
		return;
	}

	VectorCopy(playerOrigin, origin);
	velocity[0] = crandom() * GIB_VELOCITY;
	velocity[1] = crandom() * GIB_VELOCITY;
	velocity[2] = GIB_JUMP + crandom() * GIB_VELOCITY;
	if (rand() & 1) {
		CG_LaunchGib(origin, velocity, cgs.media.gibSkull);
	} else {
		CG_LaunchGib(origin, velocity, cgs.media.gibBrain);
	}

	// allow gibs to be turned off for speed
	if (!cg_gibs.integer) {
		return;
	}

	VectorCopy(playerOrigin, origin);
	velocity[0] = crandom() * GIB_VELOCITY;
	velocity[1] = crandom() * GIB_VELOCITY;
	velocity[2] = GIB_JUMP + crandom() * GIB_VELOCITY;
	CG_LaunchGib(origin, velocity, cgs.media.gibAbdomen);

	VectorCopy(playerOrigin, origin);
	velocity[0] = crandom() * GIB_VELOCITY;
	velocity[1] = crandom() * GIB_VELOCITY;
	velocity[2] = GIB_JUMP + crandom() * GIB_VELOCITY;
	CG_LaunchGib(origin, velocity, cgs.media.gibArm);

	VectorCopy(playerOrigin, origin);
	velocity[0] = crandom() * GIB_VELOCITY;
	velocity[1] = crandom() * GIB_VELOCITY;
	velocity[2] = GIB_JUMP + crandom() * GIB_VELOCITY;
	CG_LaunchGib(origin, velocity, cgs.media.gibChest);

	VectorCopy(playerOrigin, origin);
	velocity[0] = crandom() * GIB_VELOCITY;
	velocity[1] = crandom() * GIB_VELOCITY;
	velocity[2] = GIB_JUMP + crandom() * GIB_VELOCITY;
	CG_LaunchGib(origin, velocity, cgs.media.gibFist);

	VectorCopy(playerOrigin, origin);
	velocity[0] = crandom() * GIB_VELOCITY;
	velocity[1] = crandom() * GIB_VELOCITY;
	velocity[2] = GIB_JUMP + crandom() * GIB_VELOCITY;
	CG_LaunchGib(origin, velocity, cgs.media.gibFoot);

	VectorCopy(playerOrigin, origin);
	velocity[0] = crandom() * GIB_VELOCITY;
	velocity[1] = crandom() * GIB_VELOCITY;
	velocity[2] = GIB_JUMP + crandom() * GIB_VELOCITY;
	CG_LaunchGib(origin, velocity, cgs.media.gibForearm);

	VectorCopy(playerOrigin, origin);
	velocity[0] = crandom() * GIB_VELOCITY;
	velocity[1] = crandom() * GIB_VELOCITY;
	velocity[2] = GIB_JUMP + crandom() * GIB_VELOCITY;
	CG_LaunchGib(origin, velocity, cgs.media.gibIntestine);

	VectorCopy(playerOrigin, origin);
	velocity[0] = crandom() * GIB_VELOCITY;
	velocity[1] = crandom() * GIB_VELOCITY;
	velocity[2] = GIB_JUMP + crandom() * GIB_VELOCITY;
	CG_LaunchGib(origin, velocity, cgs.media.gibLeg);

	VectorCopy(playerOrigin, origin);
	velocity[0] = crandom() * GIB_VELOCITY;
	velocity[1] = crandom() * GIB_VELOCITY;
	velocity[2] = GIB_JUMP + crandom() * GIB_VELOCITY;
	CG_LaunchGib(origin, velocity, cgs.media.gibLeg);
}

/*
==================
CG_LaunchGib
==================
*/
void CG_LaunchExplode(vec3_t origin, vec3_t velocity, qhandle_t hModel)
{
	localEntity_t *le;
	refEntity_t *re;

	le = CG_AllocLocalEntity();
	re = &le->refEntity;

	le->leType = LE_FRAGMENT;
	le->startTime = cg.time;
	le->endTime = le->startTime + 10000 + random() * 6000;

	VectorCopy(origin, re->origin);
	AxisCopy(axisDefault, re->axis);
	re->hModel = hModel;

	le->pos.trType = TR_GRAVITY;
	VectorCopy(origin, le->pos.trBase);
	VectorCopy(velocity, le->pos.trDelta);
	le->pos.trTime = cg.time;

	le->bounceFactor = 0.1f;

	le->leBounceSoundType = LEBS_BRASS;
	le->leMarkType = LEMT_NONE;
}

#define	EXP_VELOCITY	100
#define	EXP_JUMP		150
/*
===================
CG_GibPlayer

Generated a bunch of gibs launching out from the bodies location
===================
*/
void CG_BigExplode(vec3_t playerOrigin)
{
	vec3_t origin, velocity;

	if (!cg_blood.integer) {
		return;
	}

	VectorCopy(playerOrigin, origin);
	velocity[0] = crandom() * EXP_VELOCITY;
	velocity[1] = crandom() * EXP_VELOCITY;
	velocity[2] = EXP_JUMP + crandom() * EXP_VELOCITY;
	CG_LaunchExplode(origin, velocity, cgs.media.smoke2);

	VectorCopy(playerOrigin, origin);
	velocity[0] = crandom() * EXP_VELOCITY;
	velocity[1] = crandom() * EXP_VELOCITY;
	velocity[2] = EXP_JUMP + crandom() * EXP_VELOCITY;
	CG_LaunchExplode(origin, velocity, cgs.media.smoke2);

	VectorCopy(playerOrigin, origin);
	velocity[0] = crandom() * EXP_VELOCITY * 1.5;
	velocity[1] = crandom() * EXP_VELOCITY * 1.5;
	velocity[2] = EXP_JUMP + crandom() * EXP_VELOCITY;
	CG_LaunchExplode(origin, velocity, cgs.media.smoke2);

	VectorCopy(playerOrigin, origin);
	velocity[0] = crandom() * EXP_VELOCITY * 2.0;
	velocity[1] = crandom() * EXP_VELOCITY * 2.0;
	velocity[2] = EXP_JUMP + crandom() * EXP_VELOCITY;
	CG_LaunchExplode(origin, velocity, cgs.media.smoke2);

	VectorCopy(playerOrigin, origin);
	velocity[0] = crandom() * EXP_VELOCITY * 2.5;
	velocity[1] = crandom() * EXP_VELOCITY * 2.5;
	velocity[2] = EXP_JUMP + crandom() * EXP_VELOCITY;
	CG_LaunchExplode(origin, velocity, cgs.media.smoke2);
}

#define	GLASS_VELOCITY	175
#define	GLASS_JUMP		125
/*
==================
CG_LaunchGlass
==================
*/
void CG_LaunchGlass(vec3_t origin, vec3_t velocity, vec3_t rotation, float bounce, qhandle_t hModel)	//, qhandle_t altSkin )
{
	localEntity_t *le;
	refEntity_t *re;

	le = CG_AllocLocalEntity();
	re = &le->refEntity;

	le->leType = LE_FRAGMENT;
	le->startTime = cg.time;
	le->endTime = le->startTime + (random() * 3000) + cg_RQ3_glasstime.integer;	// + 30000;

	VectorCopy(origin, re->origin);
	AxisCopy(axisDefault, re->axis);
	re->hModel = hModel;

	//Elder: custom shaders for debris?
	//if (altSkin)
	//re->customSkin = altSkin;

	le->pos.trType = TR_GRAVITY;
	VectorCopy(origin, le->pos.trBase);
	VectorCopy(velocity, le->pos.trDelta);
	le->pos.trTime = cg.time;

	//Elder: added
	//VectorCopy( origin, le->angles.trBase );
	VectorCopy(velocity, le->angles.trBase);
	le->angles.trBase[2] = le->angles.trBase[2] - GLASS_JUMP;
	VectorCopy(rotation, le->angles.trDelta);
	le->angles.trTime = cg.time;

	le->bounceFactor = bounce;

	le->leFlags = LEF_TUMBLE;
	le->leBounceSoundType = LEBS_NONE;
	le->leMarkType = LEMT_NONE;
}

/*
===================
CG_BreakGlass

Generated a bunch of glass shards launching out from the glass location
Elder: don't be mislead by the name - this breaks more than glass
===================
*/

void CG_BreakGlass(vec3_t playerOrigin, int glassParm, int number, int type, int isChip)
{
	vec3_t origin, velocity, rotation;
	int value;
	int count;
	int states[] = { 1, 2, 3 };	// Select model variations

	// Get the size of the array
	int numstates = sizeof(states) / sizeof(states[0]);

	// Elder: debris model handles
	qhandle_t debris1;
	qhandle_t debris2;
	qhandle_t debris3;
	float bounceFactor;
	int newParm;
	int id;
	qhandle_t sound;

	id = (glassParm & 63);
	//  Com_Printf("ID is %d\n",id);
	glassParm = glassParm >> 6;
	sound = cgs.media.breakables[id].sound[rand() % 3];
	trap_S_StartSound(NULL, number, CHAN_BODY, sound);
	bounceFactor = (float) 0.3;

	if ((glassParm & RQ3_DEBRIS_MEDIUM) == RQ3_DEBRIS_MEDIUM && (glassParm & RQ3_DEBRIS_HIGH) == RQ3_DEBRIS_HIGH) {
		//Tons
		count = 65 + rand() % 16;
	} else if ((glassParm & RQ3_DEBRIS_HIGH) == RQ3_DEBRIS_HIGH) {
		//Large
		count = 40 + rand() % 11;
	} else if ((glassParm & RQ3_DEBRIS_MEDIUM) == RQ3_DEBRIS_MEDIUM) {
		//Medium
		count = 20 + rand() % 6;
	} else {
		//Small
		count = 8 + rand() % 6;
	}
	//If it's just a chip, dont make so many
	if (isChip == 1) {
		count /= 8;
	}
	//Strip off amount info and revert eParm back to server-side size
	newParm = glassParm & 15;
	glassParm &= ~newParm;
	glassParm = glassParm << (type * 4);

	debris1 = cgs.media.breakables[id].model[0];
	debris2 = cgs.media.breakables[id].model[1];
	debris3 = cgs.media.breakables[id].model[2];

	//launch loop
	while (count--) {
		// Generate the random number every count so every shard is a
		// of the three. If this is placed above it only gets a random
		// number every time a piece of glass is broken.
		value = states[rand() % numstates];

		VectorCopy(playerOrigin, origin);
		velocity[0] = crandom() * 25 * cgs.media.breakables[id].velocity;
		velocity[1] = crandom() * 25 * cgs.media.breakables[id].velocity;
		velocity[2] = 25 * cgs.media.breakables[id].jump + crandom() * 25 * cgs.media.breakables[id].velocity;
		//Elder: added
		rotation[0] = crandom() * GLASS_VELOCITY;
		rotation[1] = crandom() * GLASS_VELOCITY;
		rotation[2] = crandom() * GLASS_VELOCITY;

		switch (value) {
		case 1:
			CG_LaunchGlass(origin, velocity, rotation, bounceFactor, debris1);
			break;
		case 2:
			CG_LaunchGlass(origin, velocity, rotation, bounceFactor, debris2);
			break;
		case 3:
			CG_LaunchGlass(origin, velocity, rotation, bounceFactor, debris3);
			break;
		}
	}
}

// JBravo: For func_explosive
void CG_LaunchBreakableFrag(vec3_t origin, vec3_t velocity, qhandle_t hModel, float bouncyness, float size)
{
	localEntity_t *le;
	refEntity_t *re;

	le = CG_AllocLocalEntity();
	re = &le->refEntity;

	le->leType = LE_FRAGMENT;
	le->startTime = cg.time - (rand() & 63);
	le->endTime = le->startTime + 5000 + random() * 3000;

	VectorCopy(origin, re->origin);
	AxisCopy(axisDefault, re->axis);
	re->hModel = hModel;
//      re->customShader = hShader;

	le->pos.trType = TR_GRAVITY;
	VectorCopy(origin, le->pos.trBase);
	VectorCopy(velocity, le->pos.trDelta);
	le->pos.trTime = cg.time;

	le->size = size;
	VectorScale(re->axis[0], size, re->axis[0]);
	VectorScale(re->axis[1], size, re->axis[1]);
	VectorScale(re->axis[2], size, re->axis[2]);
	re->nonNormalizedAxes = qtrue;

	le->angles.trType = TR_LINEAR;
	le->angles.trTime = cg.time;
	le->angles.trBase[0] = rand() & 63;
	le->angles.trBase[1] = rand() & 63;
	le->angles.trBase[2] = rand() & 63;
	le->angles.trDelta[0] = rand() & 127;
	le->angles.trDelta[1] = rand() & 127;
	le->angles.trDelta[2] = rand() & 127;
	le->leFlags = LEF_TUMBLE;

	le->bounceFactor = bouncyness;

	le->leBounceSoundType = LEBS_NONE;
	le->leMarkType = LEMT_NONE;
}

/*
=============
VectorToString

This is just a convenience function
for printing vectors
=============
*/
/*
 * JBravo: this is unneccesary and causes compiler warnings for two definitions
char *vtos(const vec3_t v)
{
	static int index;
	static char str[8][32];
	char *s;

	// use an array so that multiple vtos won't collide
	s = str[index];
	index = (index + 1) & 7;

	Com_sprintf(s, 32, "(%i %i %i)", (int) v[0], (int) v[1], (int) v[2]);

	return s;
}
*/
// JBravo: also for func_explosive
/*
===================
CG_BreakBreakable

Generated a bunch of gibs launching out from the breakables location
===================
*/
#define BREAK_VELOCITY  550
#define BREAK_JUMP      1500

void CG_BreakBreakable(centity_t * cent, int eParam, int number)
{
	localEntity_t *le;
	vec3_t origin, velocity;
	qhandle_t mod;
	qhandle_t shader;
	vec3_t shrapnelDest;
//	localEntity_t *smokePuff;
	vec3_t puffDir;

	float light;
	vec3_t lightColor;
	int duration;
	int sparkCount;

	int i;

	// JBravo: Unused variable
	//      int             modelbias[10] = { 0, 0, 0, 0, 1, 1, 1, 2, 2 };
	int id;
//	int count;

	id = (eParam & 63);
	eParam = eParam >> 6;

	trap_S_StartSound(NULL, number, CHAN_BODY, cgs.media.breakables[id].exp_sound);

/* JBravo: After all that setting of 'count' it is never used.
	if ((eParam & RQ3_DEBRIS_MEDIUM) == RQ3_DEBRIS_MEDIUM && (eParam & RQ3_DEBRIS_HIGH) == RQ3_DEBRIS_HIGH) {
		//Tons
		count = 65 + rand() % 16;
	} else if ((eParam & RQ3_DEBRIS_HIGH) == RQ3_DEBRIS_HIGH) {
		//Large
		count = 40 + rand() % 11;
	} else if ((eParam & RQ3_DEBRIS_MEDIUM) == RQ3_DEBRIS_MEDIUM) {
		//Medium
		count = 20 + rand() % 6;
	} else {
		//Small
		count = 8 + rand() % 6;
	}
*/
	//if (material) material--;
	VectorCopy(cent->lerpOrigin, origin);
	/*
	   sound = cgs.media.breakables[id].sound;
	   trap_S_StartSound( origin, cent->currentState.number, CHAN_BODY, sound ); */

	// create an explosion
	mod = cgs.media.dishFlashModel;
	shader = cgs.media.breakables[id].shader;

	//Com_Printf("Explosion, %d, breakableshader %d at %s ep %d\n",shader,cgs.media.breakables[id].shader, vtos(origin), eParam);
	light = 550;
	lightColor[0] = 1;
	lightColor[1] = 1;
	lightColor[2] = 0;
	duration = 1000;

	velocity[0] = 1;
	velocity[1] = 1;
	velocity[2] = 1;

	le = CG_MakeExplosion(origin, velocity, mod, shader, duration, qtrue);
	le->light = light;
	VectorCopy(lightColor, le->lightColor);

	sparkCount = 60 + rand() % 10;
	origin[2] += 32;

	for (i = 0; i < sparkCount; i++) {
		VectorScale(velocity, rand() % 200, velocity);
		velocity[0] += rand() % 200 - 100;
		velocity[1] += rand() % 200 - 100;
		if (i % 8 == 7) {
			// Add shrapnel trace effect
			VectorMA(origin, 0.7f, velocity, shrapnelDest);
			CG_ShrapnelSpark(origin, shrapnelDest, 10, 280);
		}
		// Add sparks
		CG_ParticleSparks(origin, velocity, 900 + rand() % 200, 5, 5, -2.5f, 3.5f);
	}

	// Add smoke puff
	puffDir[0] = 0;
	puffDir[1] = 0;
	puffDir[2] = 20;
	origin[2] -= 16;
	CG_SmokePuff(origin, puffDir,
				 rand() % 12 + 100, 1, 1, 1, 0.6f, 3000, cg.time, 0, 0, cgs.media.smokePuffShader);

}

void CG_Pressure(vec3_t origin, vec3_t dir, int type, int speed, int life)
{
	localEntity_t *le;
//	refEntity_t *re;

	le = CG_AllocLocalEntity();
//	re = &le->refEntity;
	le->leType = LE_PRESSURE;

	if (type == 1)
		le->leFlags = LEF_AIR;
	else if (type == 2)
		le->leFlags = LEF_FLAME;
	else if (type == 3)
		le->leFlags = LEF_WATER;
	else
		le->leFlags = LEF_STEAM;

	le->size = (float) speed;	// Size holds the speed.... yes...
	le->life = life;

	VectorCopy(origin, le->pos.trBase);
	VectorCopy(dir, le->pos.trDelta);
	le->startTime = cg.time;
	le->endTime = le->startTime + 10000;
}

static void CG_VisibleLaser( vec3_t start, vec3_t finish ) {
	refEntity_t re;

//	re.shaderTime = cg.time / 1000.0f;
	re.reType = RT_RAIL_CORE;
	re.customShader = cgs.media.railCoreShader;

	VectorCopy( start, re.origin );
	VectorCopy( finish, re.oldorigin );
	
	re.shaderRGBA[0] = 255;
	re.shaderRGBA[1] = 0;
	re.shaderRGBA[2] = 0;
	re.shaderRGBA[3] = 128;

	AxisClear( re.axis );
	
	trap_R_AddRefEntityToScene( &re );
}


void CG_DrawVisibleLaser( vec3_t origin, int clientNum, vec3_t dir) {
	int			sourceContentType, destContentType;
	vec3_t		start, end;
	trace_t		trace;

	if ( !cg_enableLaserFog.integer )
		return;

	// NiceAss: This user does not have a lasersight
	if (!( cg_entities[clientNum].currentState.powerups & (1 << PW_LASERSIGHT) ))
		return;

	VectorCopy(origin, start);

	VectorMA(origin, 8192 * 16, dir, end);
	//trap_CM_BoxTrace(&trace, start, end, NULL, NULL, clientNum, MASK_ALL); //MASK_SHOT);
	CG_Trace(&trace, start, NULL, NULL, end, clientNum, MASK_ALL);
	VectorCopy(trace.endpos, end);

	sourceContentType = trap_CM_PointContents(start, 0);
	destContentType = trap_CM_PointContents(end, 0);

	// do a complete laser if necessary
	if ((sourceContentType == destContentType) && (sourceContentType & CONTENTS_FOG)) {
		CG_VisibleLaser(start, end);
	}
	// laser from fog into air
	else if ((sourceContentType & CONTENTS_FOG)) {
		trap_CM_BoxTrace(&trace, end, start, NULL, NULL, 0, CONTENTS_FOG);
		CG_VisibleLaser(start, trace.endpos);
	}
	// laser from air into fog
	else if ((destContentType & CONTENTS_FOG)) {
		trap_CM_BoxTrace(&trace, start, end, NULL, NULL, 0, CONTENTS_FOG);
		CG_VisibleLaser(trace.endpos, end);
	}
}
