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

Wolfenstein: Enemy Territory GPL Source Code
Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. 

This file is part of the Wolfenstein: Enemy Territory GPL Source Code (Wolf ET Source Code).  

Wolf ET Source Code 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 3 of the License, or
(at your option) any later version.

Wolf ET Source Code 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 Wolf ET Source Code.  If not, see <http://www.gnu.org/licenses/>.

In addition, the Wolf: ET Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Wolf ET Source Code.  If not, please request a copy in writing from id Software at the address below.

If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA.

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

// cg_event.c -- handle entity events at snapshot or playerstate transitions

#include "cg_local.h"

extern void CG_StartShakeCamera( float param );
extern void CG_Tracer( vec3_t source, vec3_t dest, int sparks );
//==========================================================================

/*
=============
CG_Obituary
=============
*/
static void CG_Obituary( entityState_t *ent ) {
	int mod;
	int target, attacker;
	char        *message;
	char        *message2;
	char targetName[32];
	char attackerName[32];
	clientInfo_t    *ci, *ca; // JPW NERVE ca = attacker
	qhandle_t deathShader = cgs.media.pmImages[PM_DEATH];

	target = ent->otherEntityNum;
	attacker = ent->otherEntityNum2;
	mod = ent->eventParm;

	if ( target < 0 || target >= MAX_CLIENTS ) {
		CG_Error( "CG_Obituary: target out of range" );
	}
	ci = &cgs.clientinfo[target];

	if ( attacker < 0 || attacker >= MAX_CLIENTS ) {
		attacker = ENTITYNUM_WORLD;
		ca = NULL;
	} else {
		ca = &cgs.clientinfo[attacker];
	}

	Q_strncpyz( targetName, ci->name, sizeof( targetName ) - 2 );
	strcat( targetName, S_COLOR_WHITE );

	message2 = "";

	// check for single client messages
	switch ( mod ) {
	case MOD_SUICIDE:
		message = "committed suicide";
		break;
	case MOD_FALLING:
		message = "fell to his death";
		break;
	case MOD_CRUSH:
		message = "was crushed";
		break;
	case MOD_WATER:
		message = "drowned";
		break;
	case MOD_SLIME:
		message = "died by toxic materials";
		break;
	case MOD_TRIGGER_HURT:
	case MOD_TELEFRAG: // rain - added TELEFRAG and TARGET_LASER, just in case
	case MOD_TARGET_LASER:
		message = "was killed";
		break;
	case MOD_CRUSH_CONSTRUCTIONDEATH_NOATTACKER:
		message = "got buried under a pile of rubble";
		break;
	case MOD_LAVA: // rain
		message = "was incinerated";
		break;
	default:
		message = NULL;
		break;
	}

	if ( attacker == target ) {
		switch ( mod ) {
		case MOD_DYNAMITE:
			message = "dynamited himself to pieces";
			break;
		case MOD_GRENADE_LAUNCHER:
		case MOD_GRENADE_PINEAPPLE: // rain - added PINEAPPLE
			message = "dove on his own grenade";
			break;
		case MOD_PANZERFAUST:
			message = "vaporized himself";
			break;
		case MOD_FLAMETHROWER: // rain
			message = "played with fire";
			break;
		case MOD_AIRSTRIKE:
			message = "obliterated himself";
			break;
		case MOD_ARTY:
			message = "fired-for-effect on himself";
			break;
		case MOD_EXPLOSIVE:
			message = "died in his own explosion";
			break;
			// rain - everything from this point on is sorted by MOD, didn't
			// resort existing messages to avoid differences between pre
			// and post-patch code (for source patching)
		case MOD_GPG40:
		case MOD_M7: // rain
			//bani - more amusing, less wordy
			message = "ate his own rifle grenade";
			break;
		case MOD_LANDMINE: // rain
			//bani - slightly more amusing
			message = "failed to spot his own landmine";
			break;
		case MOD_SATCHEL: // rain
			message = "embraced his own satchel explosion";
			break;
		case MOD_TRIPMINE: // rain - dormant code
			message = "forgot where his tripmine was";
			break;
		case MOD_CRUSH_CONSTRUCTION: // rain
			message = "engineered himself into oblivion";
			break;
		case MOD_CRUSH_CONSTRUCTIONDEATH: // rain
			message = "buried himself alive";
			break;
		case MOD_MORTAR: // rain
			message = "never saw his own mortar round coming";
			break;
		case MOD_SMOKEGRENADE: // rain
			// bani - more amusing
			message = "danced on his airstrike marker";
			break;
			// no obituary message if changing teams
		case MOD_SWITCHTEAM:
			return;
		default:
			message = "killed himself";
			break;
		}
	}

	if ( message ) {
		message = CG_TranslateString( message );
		CG_AddPMItem( PM_DEATH, va( "%s %s.", targetName, message ), deathShader );
		return;
	}

	// check for kill messages from the current clientNum
	if ( attacker == cg.snap->ps.clientNum ) {
		char    *s;

		if ( ci->team == ca->team ) {
			if ( mod == MOD_SWAP_PLACES ) {
				s = va( "%s %s", CG_TranslateString( "You swapped places with" ), targetName );
			} else {
				s = va( "%s %s", CG_TranslateString( "You killed ^1TEAMMATE^7" ), targetName );
			}
		} else {
			s = va( "%s %s", CG_TranslateString( "You killed" ), targetName );
		}
		CG_PriorityCenterPrint( s, SCREEN_HEIGHT * 0.75, BIGCHAR_WIDTH * 0.6, 1 );
		// print the text message as well
	}

	// check for double client messages
	if ( !ca ) {
		strcpy( attackerName, "noname" );
	} else {
		Q_strncpyz( attackerName, ca->name, sizeof( attackerName ) - 2 );
		strcat( attackerName, S_COLOR_WHITE );

		// check for kill messages about the current clientNum
		if ( target == cg.snap->ps.clientNum ) {
			Q_strncpyz( cg.killerName, attackerName, sizeof( cg.killerName ) );
		}
	}

	if ( ca ) {
		switch ( mod ) {
		case MOD_KNIFE:
			message = "was stabbed by";
			message2 = "'s knife";
			// OSP - goat luvin
//			if( attacker == cg.snap->ps.clientNum || target == cg.snap->ps.clientNum ) {
//				trap_S_StartSound( cg.snap->ps.origin, cg.snap->ps.clientNum, CHAN_AUTO, cgs.media.goatAxis );
//			}
			break;

		case MOD_AKIMBO_COLT:
		case MOD_AKIMBO_SILENCEDCOLT:
			message = "was killed by";
			message2 = "'s Akimbo .45ACP 1911s";
			break;

		case MOD_AKIMBO_LUGER:
		case MOD_AKIMBO_SILENCEDLUGER:
			message = "was killed by";
			message2 = "'s Akimbo Luger 9mms";
			break;

		case MOD_SILENCER:
		case MOD_LUGER:
			message = "was killed by";
			message2 = "'s Luger 9mm";
			break;

		case MOD_SILENCED_COLT:
		case MOD_COLT:
			message = "was killed by";
			message2 = "'s .45ACP 1911";
			break;

		case MOD_MP40:
			message = "was killed by";
			message2 = "'s MP40";
			break;

		case MOD_THOMPSON:
			message = "was killed by";
			message2 = "'s Thompson";
			break;

		case MOD_STEN:
			message = "was killed by";
			message2 = "'s Sten";
			break;

		case MOD_DYNAMITE:
			message = "was blasted by";
			message2 = "'s dynamite";
			break;

		case MOD_PANZERFAUST:
			message = "was blasted by";
			message2 = "'s Panzerfaust";
			break;

		case MOD_GRENADE_LAUNCHER:
		case MOD_GRENADE_PINEAPPLE:
			message = "was exploded by";
			message2 = "'s grenade";
			break;

		case MOD_FLAMETHROWER:
			message = "was cooked by";
			message2 = "'s flamethrower";
			break;

		case MOD_MORTAR:
			message = "never saw";
			message2 = "'s mortar round coming";
			break;

		case MOD_MACHINEGUN:
			message = "was perforated by";
			message2 = "'s crew-served MG";
			break;

		case MOD_BROWNING:
			message = "was perforated by";
			message2 = "'s tank-mounted browning 30cal";
			break;

		case MOD_MG42:
			message = "was perforated by";
			message2 = "'s tank-mounted MG42";
			break;

		case MOD_AIRSTRIKE:
			message = "was blasted by";
			message2 = "'s support fire";
			break;

		case MOD_ARTY:
			message = "was shelled by";
			message2 = "'s artillery support";
			break;

		case MOD_SWAP_PLACES:
			message = "^2swapped places with^7";
			message2 = "";
			break;

		case MOD_KAR98: // same weapon really
		case MOD_K43:
			message = "was killed by";
			message2 = "'s K43";
			break;

		case MOD_CARBINE: // same weapon really
		case MOD_GARAND:
			message = "was killed by";
			message2 = "'s Garand";
			break;

		case MOD_GPG40:
		case MOD_M7:
			message = "was killed by";
			message2 = "'s rifle grenade";
			break;

		case MOD_LANDMINE:
			message = "failed to spot";
			message2 = "'s Landmine";
			break;

		case MOD_CRUSH_CONSTRUCTION:
			message = "got caught in";
			message2 = "'s construction madness";
			break;

		case MOD_CRUSH_CONSTRUCTIONDEATH:
			message = "got burried under";
			message2 = "'s rubble";
			break;

		case MOD_MOBILE_MG42:
			message = "was mown down by";
			message2 = "'s Mobile MG42";
			break;

		case MOD_GARAND_SCOPE:
			message = "was silenced by";
			message2 = "'s Garand";
			break;

		case MOD_K43_SCOPE:
			message = "was silenced by";
			message2 = "'s K43";
			break;

		case MOD_FG42:
			message = "was killed by";
			message2 = "'s FG42";
			break;

		case MOD_FG42SCOPE:
			message = "was sniped by";
			message2 = "'s FG42";
			break;

		case MOD_SATCHEL:
			message = "was blasted by";
			message2 = "'s Satchel Charge";
			break;

		case MOD_TRIPMINE: // rain - dormant code
			message = "was detonated by";
			message2 = "'s trip mine";
			break;

		case MOD_SMOKEGRENADE: // rain
			message = "stood on";
			message2 = "'s airstrike marker";
			break;

		default:
			message = "was killed by";
			break;
		}

		if ( ci->team == ca->team ) {
			message = "^1WAS KILLED BY TEAMMATE^7";
			message2 = "";
		}

		if ( message ) {
			message = CG_TranslateString( message );
			if ( message2 ) {
				message2 = CG_TranslateString( message2 );
				CG_AddPMItem( PM_DEATH, va( "%s %s %s%s", targetName, message, attackerName, message2 ), deathShader );
//				CG_Printf( "[cgnotify]%s %s %s%s\n", targetName, message, attackerName, message2 );
			}
			return;
		}
	}

	// we don't know what it was
	switch ( mod ) {
	default:
		CG_AddPMItem( PM_DEATH, va( "%s died.", targetName ), deathShader );
		break;
	}
}

//==========================================================================

// from cg_weapons.c
extern int CG_WeaponIndex( int weapnum, int *bank, int *cycle );


/*
================
CG_ItemPickup

A new item was picked up this frame
================
*/
static void CG_ItemPickup( int itemNum ) {
	int itemid;
	int wpbank_cur, wpbank_pickup;

	itemid = bg_itemlist[itemNum].giTag;

	CG_AddPMItem( PM_MESSAGE, va( "Picked up %s", CG_PickupItemText( itemNum ) ), cgs.media.pmImages[PM_MESSAGE] );

//	cg.itemPickup			= itemNum;
//	cg.itemPickupTime		= cg.time;
//	cg.itemPickupBlendTime	= cg.time;

	// see if it should be the grabbed weapon
	if ( bg_itemlist[itemNum].giType == IT_WEAPON ) {

		if ( cg_autoswitch.integer && cg.predictedPlayerState.weaponstate != WEAPON_RELOADING ) {

			//	0 - "Off"
			//	1 - "Always Switch"
			//	2 - "If New"
			//	3 - "If Better"
			//	4 - "New or Better"

			// don't ever autoswitch to secondary fire weapons
			// Gordon: Leave autoswitch to secondary kar/carbine as they use alt ammo and arent zoomed: Note, not that it would do this anyway as it isnt in a bank....
			if ( itemid != WP_FG42SCOPE && itemid != WP_GARAND_SCOPE && itemid != WP_K43_SCOPE && itemid != WP_AMMO ) {   //----(SA)	modified
				// no weap currently selected, always just select the new one
				if ( !cg.weaponSelect ) {
					cg.weaponSelectTime = cg.time;
					cg.weaponSelect     = itemid;
				}
				// 1 - always switch to new weap (Q3A default)
				else if ( cg_autoswitch.integer == 1 ) {
					cg.weaponSelectTime = cg.time;
					cg.weaponSelect     = itemid;
				} else {

					// 2 - switch to weap if it's not already in the player's inventory (Wolf default)
					// 4 - both 2 and 3

					// FIXME:	this works fine for predicted pickups (when you walk over the weapon), but not for
					//			manual pickups (activate item)
					if ( cg_autoswitch.integer == 2 || cg_autoswitch.integer == 4 ) {
						if ( !COM_BitCheck( cg.snap->ps.weapons, itemid ) ) {
							cg.weaponSelectTime = cg.time;
							cg.weaponSelect     = itemid;
						}
					}   // end 2

					// 3 - switch to weap if it's in a bank greater than the current weap
					// 4 - both 2 and 3
					if ( cg_autoswitch.integer == 3 || cg_autoswitch.integer == 4 ) {
						// switch away only if a primary weapon is selected (read: don't switch away if current weap is a secondary mode)
						if ( CG_WeaponIndex( cg.weaponSelect, &wpbank_cur, NULL ) ) {
							if ( CG_WeaponIndex( itemid, &wpbank_pickup, NULL ) ) {
								if ( wpbank_pickup > wpbank_cur ) {
									cg.weaponSelectTime = cg.time;
									cg.weaponSelect     = itemid;
								}
							}
						}
					}   // end 3

				}   // end cg_autoswitch.integer != 1

			}

		}   // end cg_autoswitch.integer

	}   // end bg_itemlist[itemNum].giType == IT_WEAPON
}


/*
================
CG_PainEvent

Also called by playerstate transition
================
*/
typedef struct {
	char *tag;
	int refEntOfs;
	int anim;
} painAnimForTag_t;

#define PEFOFS( x ) ( (int)&( ( (playerEntity_t *)0 )->x ) )

void CG_PainEvent( centity_t *cent, int health, qboolean crouching ) {
	char    *snd;

	// don't do more than two pain sounds a second
	if ( cg.time - cent->pe.painTime < 500 ) {
		return;
	}

	if ( health < 25 ) {
		snd = "*pain25_1.wav";
	} else if ( health < 50 ) {
		snd = "*pain50_1.wav";
	} else if ( health < 75 ) {
		snd = "*pain75_1.wav";
	} else {
		snd = "*pain100_1.wav";
	}
	trap_S_StartSound( NULL, cent->currentState.number, CHAN_VOICE,
					   CG_CustomSound( cent->currentState.number, snd ) );

	// save pain time for programitic twitch animation
	cent->pe.painTime = cg.time;
	cent->pe.painDirection ^= 1;
}





/*
==============
CG_Explode


	if (cent->currentState.angles2[0] || cent->currentState.angles2[1] || cent->currentState.angles2[2])

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

#define POSSIBLE_PIECES 6

typedef struct fxSound_s {
	int max;
	qhandle_t sound[3];
	const char *soundfile[3];
} fxSound_t;

static fxSound_t fxSounds[POSSIBLE_PIECES] = {
	// wood
	{ 1, { -1, -1, -1 }, { "sound/world/boardbreak.wav", NULL, NULL } },
	// glass
	{ 3, { -1, -1, -1 }, { "sound/world/glassbreak1.wav", "sound/world/glassbreak2.wav", "sound/world/glassbreak3.wav" } },
	// metal
	{ 1, { -1, -1, -1 }, { "sound/world/metalbreak.wav", NULL, NULL } },
	// gibs
	{ 1, { -1, -1, -1 }, { "sound/world/gibsplit1.wav", NULL, NULL } },
	// brick
	{ 1, { -1, -1, -1 }, { "sound/world/debris1.wav", NULL, NULL } },
	// stone
	{ 1, { -1, -1, -1 }, { "sound/world/stonefall.wav", NULL, NULL } }
};

void CG_PrecacheFXSounds( void ) {
	int i, j;

	for ( i = 0; i < POSSIBLE_PIECES; i++ ) {
		for ( j = 0; j < fxSounds[i].max; j++ ) {
			fxSounds[i].sound[j] = trap_S_RegisterSound( fxSounds[i].soundfile[j], qfalse );
		}
	}
}

void CG_Explodef( vec3_t origin, vec3_t dir, int mass, int type, qhandle_t sound, int forceLowGrav, qhandle_t shader );
void CG_RubbleFx( vec3_t origin, vec3_t dir, int mass, int type, qhandle_t sound, int forceLowGrav, qhandle_t shader, float speedscale, float sizescale );

/*
==============
CG_Explode
	the old cent-based explode calls will still work with this pass-through
==============
*/
void CG_Explode( centity_t *cent, vec3_t origin, vec3_t dir, qhandle_t shader ) {

	qhandle_t inheritmodel = 0;

	// inherit shader
	// (SA) FIXME: do this at spawn time rather than explode time so any new necessary shaders are created earlier
	if ( cent->currentState.eFlags & EF_INHERITSHADER ) {
		if ( !shader ) {
//			inheritmodel = cent->currentState.modelindex;
			inheritmodel = cgs.inlineDrawModel[cent->currentState.modelindex];  // okay, this should be better.
			if ( inheritmodel ) {
				shader = trap_R_GetShaderFromModel( inheritmodel, 0, 0 );
			}
		}
	}

	if ( !cent->currentState.dl_intensity ) {
		sfxHandle_t sound;

		sound = random() * fxSounds[cent->currentState.frame].max;

		if ( fxSounds[cent->currentState.frame].sound[sound] == -1 ) {
			fxSounds[cent->currentState.frame].sound[sound] = trap_S_RegisterSound( fxSounds[cent->currentState.frame].soundfile[sound], qfalse );
		}

		sound = fxSounds[cent->currentState.frame].sound[sound];

		CG_Explodef(    origin,
						dir,
						cent->currentState.density,         // mass
						cent->currentState.frame,           // type
						sound,                              // sound
						cent->currentState.weapon,          // forceLowGrav
						shader
						);
	} else {
		sfxHandle_t sound;

		if ( cent->currentState.dl_intensity == -1 ) {
			sound = 0;
		} else {
			sound = cgs.gameSounds[cent->currentState.dl_intensity];
		}

		CG_Explodef(    origin,
						dir,
						cent->currentState.density,         // mass
						cent->currentState.frame,           // type
						sound,                              // sound
						cent->currentState.weapon,          // forceLowGrav
						shader
						);
	}

}

/*
==============
CG_Explode
	the old cent-based explode calls will still work with this pass-through
==============
*/
void CG_Rubble( centity_t *cent, vec3_t origin, vec3_t dir, qhandle_t shader ) {

	qhandle_t inheritmodel = 0;

	// inherit shader
	// (SA) FIXME: do this at spawn time rather than explode time so any new necessary shaders are created earlier
	if ( cent->currentState.eFlags & EF_INHERITSHADER ) {
		if ( !shader ) {
//			inheritmodel = cent->currentState.modelindex;
			inheritmodel = cgs.inlineDrawModel[cent->currentState.modelindex];  // okay, this should be better.
			if ( inheritmodel ) {
				shader = trap_R_GetShaderFromModel( inheritmodel, 0, 0 );
			}
		}
	}

	if ( !cent->currentState.dl_intensity ) {
		sfxHandle_t sound;

		sound = random() * fxSounds[cent->currentState.frame].max;

		if ( fxSounds[cent->currentState.frame].sound[sound] == -1 ) {
			fxSounds[cent->currentState.frame].sound[sound] = trap_S_RegisterSound( fxSounds[cent->currentState.frame].soundfile[sound], qfalse );
		}

		sound = fxSounds[cent->currentState.frame].sound[sound];

		CG_RubbleFx(    origin,
						dir,
						cent->currentState.density,         // mass
						cent->currentState.frame,           // type
						sound,                              // sound
						cent->currentState.weapon,          // forceLowGrav
						shader,
						cent->currentState.angles2[0],
						cent->currentState.angles2[1]
						);
	} else {
		sfxHandle_t sound;

		if ( cent->currentState.dl_intensity == -1 ) {
			sound = 0;
		} else {
			sound = cgs.gameSounds[cent->currentState.dl_intensity];
		}

		CG_RubbleFx(    origin,
						dir,
						cent->currentState.density,         // mass
						cent->currentState.frame,           // type
						sound,                              // sound
						cent->currentState.weapon,          // forceLowGrav
						shader,
						cent->currentState.angles2[0],
						cent->currentState.angles2[1]
						);
	}
}


/*
==============
CG_RubbleFx
==============
*/
void CG_RubbleFx( vec3_t origin, vec3_t dir, int mass, int type, sfxHandle_t sound, int forceLowGrav, qhandle_t shader, float speedscale, float sizescale ) {
	int i;
	localEntity_t   *le;
	refEntity_t     *re;
	int howmany, total, totalsounds;
	int pieces[6];              // how many of each piece
	qhandle_t modelshader = 0;
	float materialmul = 1;              // multiplier for different types

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

	pieces[5]   = (int)( mass / 250.0f );
	pieces[4]   = (int)( mass / 76.0f );
	pieces[3]   = (int)( mass / 37.0f );  // so 2 per 75
	pieces[2]   = (int)( mass / 15.0f );
	pieces[1]   = (int)( mass / 10.0f );
	pieces[0]   = (int)( mass / 5.0f );

	if ( pieces[0] > 20 ) {
		pieces[0] = 20;                 // cap some of the smaller bits so they don't get out of control
	}
	if ( pieces[1] > 15 ) {
		pieces[1] = 15;
	}
	if ( pieces[2] > 10 ) {
		pieces[2] = 10;
	}

	if ( type == 0 ) {    // cap wood even more since it's often grouped, and the small splinters can add up
		if ( pieces[0] > 10 ) {
			pieces[0] = 10;
		}
		if ( pieces[1] > 10 ) {
			pieces[1] = 10;
		}
		if ( pieces[2] > 10 ) {
			pieces[2] = 10;
		}
	}

	totalsounds = 0;
	total = pieces[5] + pieces[4] + pieces[3] + pieces[2] + pieces[1] + pieces[0];

	if ( sound ) {
		trap_S_StartSound( origin, -1, CHAN_AUTO, sound );
	}

	if ( shader ) { // shader passed in to use
		modelshader = shader;
	}

	for ( i = 0; i < POSSIBLE_PIECES; i++ ) {
		leBounceSoundType_t snd = LEBS_NONE;
		int hmodel = 0;
		float scale;
		int endtime;
		for ( howmany = 0; howmany < pieces[i]; howmany++ ) {

			scale = 1.0f;
			endtime = 0;    // set endtime offset for faster/slower fadeouts

			switch ( type ) {
			case 0: // "wood"
				snd = LEBS_WOOD;
				hmodel = cgs.media.debWood[i];

				if ( i == 0 ) {
					scale = 0.5f;
				} else if ( i == 1 ) {
					scale = 0.6f;
				} else if ( i == 2 )                           {
					scale = 0.7f;
				} else if ( i == 3 )                                                               {
					scale = 0.5f;
				}
				//					else goto pass;

				if ( i < 3 ) {
					endtime = -3000;    // small bits live 3 sec shorter than normal
				}
				break;

			case 1: // "glass"
				snd = LEBS_NONE;
				if ( i == 5 ) {
					hmodel = cgs.media.shardGlass1;
				} else if ( i == 4 ) {
					hmodel = cgs.media.shardGlass2;
				} else if ( i == 2 )                                             {
					hmodel = cgs.media.shardGlass2;
				} else if ( i == 1 )                                                                                                   {
					hmodel = cgs.media.shardGlass2;
					scale = 0.5f;
				} else {goto pass;}
				break;

			case 2: // "metal"
				snd = LEBS_METAL;
				if ( i == 5 ) {
					hmodel = cgs.media.shardMetal1;
				} else if ( i == 4 ) {
					hmodel = cgs.media.shardMetal2;
				} else if ( i == 2 )                                             {
					hmodel = cgs.media.shardMetal2;
				} else if ( i == 1 )                                                                                                   {
					hmodel = cgs.media.shardMetal2;
					scale = 0.5f;
				} else {goto pass;}
				break;

			case 3: // "gibs"
				snd = LEBS_BLOOD;
				if ( i == 5 ) {
					hmodel = cgs.media.gibIntestine;
				} else if ( i == 4 ) {
					hmodel = cgs.media.gibLeg;
				} else if ( i == 2 )                                        {
					hmodel = cgs.media.gibChest;
				} else { goto pass;}
				break;

			case 4: // "brick"
				snd = LEBS_ROCK;
				hmodel = cgs.media.debBlock[i];
				break;

			case 5: // "rock"
				snd = LEBS_ROCK;
				if ( i == 5 ) {
					hmodel = cgs.media.debRock[2];                  // temporarily use the next smallest rock piece
				} else if ( i == 4 )                                                              {
					hmodel = cgs.media.debRock[2];
				} else if ( i == 3 )                                                                                                                   {
					hmodel = cgs.media.debRock[1];
				} else if ( i == 2 )                                                                                                                                                                        {
					hmodel = cgs.media.debRock[0];
				} else if ( i == 1 )                                                                                                                                                                                                                             {
					hmodel = cgs.media.debBlock[1];                 // temporarily use the small block pieces
				} else { hmodel = cgs.media.debBlock[0];            // temporarily use the small block pieces
				}
				if ( i <= 2 ) {
					endtime = -2000;    // small bits live 2 sec shorter than normal
				}
				break;

			case 6: // "fabric"
				if ( i == 5 ) {
					hmodel = cgs.media.debFabric[0];
				} else if ( i == 4 ) {
					hmodel = cgs.media.debFabric[1];
				} else if ( i == 2 )                                              {
					hmodel = cgs.media.debFabric[2];
				} else if ( i == 1 )                                                                                                     {
					hmodel = cgs.media.debFabric[2];
					scale = 0.5;
				} else {goto pass;  // (only do 5, 4, 2 and 1)
				}
				break;
			}

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

			le->leType              = LE_FRAGMENT;
			le->startTime           = cg.time;

			le->endTime             = ( le->startTime + 5000 + random() * 5000 ) + endtime;

			// as it turns out, i'm not sure if setting the re->axis here will actually do anything
			//			AxisClear(re->axis);
			//			re->axis[0][0] =
			//			re->axis[1][1] =
			//			re->axis[2][2] = scale;
			//
			//			if(scale != 1.0)
			//				re->nonNormalizedAxes = qtrue;

			le->sizeScale = scale * sizescale;

			if ( type == 1 ) { // glass
				// Rafael added this because glass looks funky when it fades out
				// TBD: need to look into this so that they fade out correctly
				re->fadeStartTime       = le->endTime;
				re->fadeEndTime         = le->endTime;
			} else {
				re->fadeStartTime       = le->endTime - 4000;
				re->fadeEndTime         = le->endTime;
			}

			if ( total > 5 ) {
				if ( totalsounds > 5 || ( howmany % 8 ) != 0 ) {
					snd = LEBS_NONE;
				} else {
					totalsounds++;
				}
			}

			le->lifeRate    = 1.0 / ( le->endTime - le->startTime );
			le->leFlags     = LEF_TUMBLE;
			le->leMarkType  = 0;

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

			le->leBounceSoundType = snd;
			re->hModel = hmodel;

			// inherit shader
			if ( modelshader ) {
				re->customShader = modelshader;
			}

			re->radius = 1000;

			// trying to make this a little more interesting
			if ( type == 6 ) { // "fabric"
				le->pos.trType = TR_GRAVITY_FLOAT;  // the fabric stuff will change to use something that looks better
			} else {
				if ( !forceLowGrav && rand() & 1 ) {    // if low gravity is not forced and die roll goes our way use regular grav
					le->pos.trType = TR_GRAVITY;
				} else {
					le->pos.trType = TR_GRAVITY_LOW;
				}
			}

			switch ( type ) {
			case 6: // fabric
				le->bounceFactor    = 0.0;
				materialmul         = 0.3;  // rotation speed
				break;
			default:
				le->bounceFactor    = 0.4;
				break;
			}


			// rotation
			le->angles.trType = TR_LINEAR;
			le->angles.trTime = cg.time;
			le->angles.trBase[0] = rand() & 31;
			le->angles.trBase[1] = rand() & 31;
			le->angles.trBase[2] = rand() & 31;
			le->angles.trDelta[0] = ( ( 100 + ( rand() & 500 ) ) - 300 ) * materialmul;
			le->angles.trDelta[1] = ( ( 100 + ( rand() & 500 ) ) - 300 ) * materialmul;
			le->angles.trDelta[2] = ( ( 100 + ( rand() & 500 ) ) - 300 ) * materialmul;


			//			if(type == 6)	// fabric
			//				materialmul = 1;		// translation speed


			VectorCopy( origin, le->pos.trBase );
			VectorNormalize( dir );
			le->pos.trTime = cg.time;

			// (SA) hoping that was just intended to represent randomness
			//			if (cent->currentState.angles2[0] || cent->currentState.angles2[1] || cent->currentState.angles2[2])
			if ( le->angles.trBase[0] == 1 || le->angles.trBase[1] == 1 || le->angles.trBase[2] == 1 ) {
				le->pos.trType = TR_GRAVITY;
				VectorScale( dir, 10 * 8, le->pos.trDelta );
				le->pos.trDelta[0] += ( ( random() * 400 ) - 200 ) * speedscale;
				le->pos.trDelta[1] += ( ( random() * 400 ) - 200 ) * speedscale;
				le->pos.trDelta[2] = ( ( random() * 400 ) + 400 ) * speedscale;

			} else {
				// location
				VectorScale( dir, 200 + mass, le->pos.trDelta );
				le->pos.trDelta[0] += ( ( random() * 200 ) - 100 );
				le->pos.trDelta[1] += ( ( random() * 200 ) - 100 );

				if ( dir[2] ) {
					le->pos.trDelta[2] = random() * 200 * materialmul;  // randomize sort of a lot so they don't all land together
				} else {
					le->pos.trDelta[2] = random() * 20;
				}
			}
		}
pass:
		continue;
	}

}

/*
==============
CG_Explodef
	made this more generic for spawning hits and breaks without needing a *cent
==============
*/
void CG_Explodef( vec3_t origin, vec3_t dir, int mass, int type, qhandle_t sound, int forceLowGrav, qhandle_t shader ) {
	int i;
	localEntity_t   *le;
	refEntity_t     *re;
	int howmany, total, totalsounds;
	int pieces[6];              // how many of each piece
	qhandle_t modelshader = 0;
	float materialmul = 1;              // multiplier for different types

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

	pieces[5]   = (int)( mass / 250.0f );
	pieces[4]   = (int)( mass / 76.0f );
	pieces[3]   = (int)( mass / 37.0f );  // so 2 per 75
	pieces[2]   = (int)( mass / 15.0f );
	pieces[1]   = (int)( mass / 10.0f );
	pieces[0]   = (int)( mass / 5.0f );

	if ( pieces[0] > 20 ) {
		pieces[0] = 20;                 // cap some of the smaller bits so they don't get out of control
	}
	if ( pieces[1] > 15 ) {
		pieces[1] = 15;
	}
	if ( pieces[2] > 10 ) {
		pieces[2] = 10;
	}

	if ( type == 0 ) {    // cap wood even more since it's often grouped, and the small splinters can add up
		if ( pieces[0] > 10 ) {
			pieces[0] = 10;
		}
		if ( pieces[1] > 10 ) {
			pieces[1] = 10;
		}
		if ( pieces[2] > 10 ) {
			pieces[2] = 10;
		}
	}

	total = pieces[5] + pieces[4] + pieces[3] + pieces[2] + pieces[1] + pieces[0];
	totalsounds = 0;

	if ( sound ) {
		trap_S_StartSound( origin, -1, CHAN_AUTO, sound );
	}

	if ( shader ) { // shader passed in to use
		modelshader = shader;
	}

	for ( i = 0; i < POSSIBLE_PIECES; i++ ) {
		leBounceSoundType_t snd = LEBS_NONE;
		int hmodel = 0;
		float scale;
		int endtime;
		for ( howmany = 0; howmany < pieces[i]; howmany++ ) {

			scale = 1.0f;
			endtime = 0;    // set endtime offset for faster/slower fadeouts

			switch ( type ) {
			case 0: // "wood"
				snd = LEBS_WOOD;
				hmodel = cgs.media.debWood[i];

				if ( i == 0 ) {
					scale = 0.5f;
				} else if ( i == 1 ) {
					scale = 0.6f;
				} else if ( i == 2 )                           {
					scale = 0.7f;
				} else if ( i == 3 )                                                               {
					scale = 0.5f;
				}
				//					else goto pass;

				if ( i < 3 ) {
					endtime = -3000;    // small bits live 3 sec shorter than normal
				}
				break;

			case 1: // "glass"
				snd = LEBS_NONE;
				if ( i == 5 ) {
					hmodel = cgs.media.shardGlass1;
				} else if ( i == 4 ) {
					hmodel = cgs.media.shardGlass2;
				} else if ( i == 2 )                                             {
					hmodel = cgs.media.shardGlass2;
				} else if ( i == 1 )                                                                                                   {
					hmodel = cgs.media.shardGlass2;
					scale = 0.5f;
				} else {goto pass;}
				break;

			case 2: // "metal"
				snd = LEBS_BRASS;
				if ( i == 5 ) {
					hmodel = cgs.media.shardMetal1;
				} else if ( i == 4 ) {
					hmodel = cgs.media.shardMetal2;
				} else if ( i == 2 )                                             {
					hmodel = cgs.media.shardMetal2;
				} else if ( i == 1 )                                                                                                   {
					hmodel = cgs.media.shardMetal2;
					scale = 0.5f;
				} else {goto pass;}
				break;

			case 3: // "gibs"
				snd = LEBS_BLOOD;
				if ( i == 5 ) {
					hmodel = cgs.media.gibIntestine;
				} else if ( i == 4 ) {
					hmodel = cgs.media.gibLeg;
				} else if ( i == 2 )                                        {
					hmodel = cgs.media.gibChest;
				} else { goto pass;}
				break;

			case 4: // "brick"
				snd = LEBS_ROCK;
				hmodel = cgs.media.debBlock[i];
				break;

			case 5: // "rock"
				snd = LEBS_ROCK;
				if ( i == 5 ) {
					hmodel = cgs.media.debRock[2];                  // temporarily use the next smallest rock piece
				} else if ( i == 4 )                                                              {
					hmodel = cgs.media.debRock[2];
				} else if ( i == 3 )                                                                                                                   {
					hmodel = cgs.media.debRock[1];
				} else if ( i == 2 )                                                                                                                                                                        {
					hmodel = cgs.media.debRock[0];
				} else if ( i == 1 )                                                                                                                                                                                                                             {
					hmodel = cgs.media.debBlock[1];                 // temporarily use the small block pieces
				} else { hmodel = cgs.media.debBlock[0];            // temporarily use the small block pieces
				}
				if ( i <= 2 ) {
					endtime = -2000;    // small bits live 2 sec shorter than normal
				}
				break;

			case 6: // "fabric"
				if ( i == 5 ) {
					hmodel = cgs.media.debFabric[0];
				} else if ( i == 4 ) {
					hmodel = cgs.media.debFabric[1];
				} else if ( i == 2 )                                              {
					hmodel = cgs.media.debFabric[2];
				} else if ( i == 1 )                                                                                                     {
					hmodel = cgs.media.debFabric[2];
					scale = 0.5;
				} else {goto pass;  // (only do 5, 4, 2 and 1)
				}
				break;
			}

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

			le->leType              = LE_FRAGMENT;
			le->startTime           = cg.time;

			le->endTime             = ( le->startTime + 5000 + random() * 5000 ) + endtime;

			// as it turns out, i'm not sure if setting the re->axis here will actually do anything
			//			AxisClear(re->axis);
			//			re->axis[0][0] =
			//			re->axis[1][1] =
			//			re->axis[2][2] = scale;
			//
			//			if(scale != 1.0)
			//				re->nonNormalizedAxes = qtrue;

			le->sizeScale = scale;

			if ( type == 1 ) { // glass
				// Rafael added this because glass looks funky when it fades out
				// TBD: need to look into this so that they fade out correctly
				re->fadeStartTime       = le->endTime;
				re->fadeEndTime         = le->endTime;
			} else {
				re->fadeStartTime       = le->endTime - 4000;
				re->fadeEndTime         = le->endTime;
			}

			if ( total > 5 ) {
				if ( totalsounds > 5 || ( howmany % 8 ) != 0 ) {
					snd = LEBS_NONE;
				} else {
					totalsounds++;
				}
			}

			le->lifeRate    = 1.0 / ( le->endTime - le->startTime );
			le->leFlags     = LEF_TUMBLE;
			le->leMarkType  = 0;

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

			le->leBounceSoundType = snd;
			re->hModel = hmodel;

			// inherit shader
			if ( modelshader ) {
				re->customShader = modelshader;
			}

			re->radius = 1000;

			// trying to make this a little more interesting
			if ( type == 6 ) { // "fabric"
				le->pos.trType = TR_GRAVITY_FLOAT;  // the fabric stuff will change to use something that looks better
			} else {
				if ( !forceLowGrav && rand() & 1 ) {    // if low gravity is not forced and die roll goes our way use regular grav
					le->pos.trType = TR_GRAVITY;
				} else {
					le->pos.trType = TR_GRAVITY_LOW;
				}
			}

			switch ( type ) {
			case 6: // fabric
				le->bounceFactor    = 0.0;
				materialmul         = 0.3;  // rotation speed
				break;
			default:
				le->bounceFactor    = 0.4;
				break;
			}


			// rotation
			le->angles.trType = TR_LINEAR;
			le->angles.trTime = cg.time;
			le->angles.trBase[0] = rand() & 31;
			le->angles.trBase[1] = rand() & 31;
			le->angles.trBase[2] = rand() & 31;
			le->angles.trDelta[0] = ( ( 100 + ( rand() & 500 ) ) - 300 ) * materialmul;
			le->angles.trDelta[1] = ( ( 100 + ( rand() & 500 ) ) - 300 ) * materialmul;
			le->angles.trDelta[2] = ( ( 100 + ( rand() & 500 ) ) - 300 ) * materialmul;


			//			if(type == 6)	// fabric
			//				materialmul = 1;		// translation speed


			VectorCopy( origin, le->pos.trBase );
			VectorNormalize( dir );
			le->pos.trTime = cg.time;

			// (SA) hoping that was just intended to represent randomness
			//			if (cent->currentState.angles2[0] || cent->currentState.angles2[1] || cent->currentState.angles2[2])
			if ( le->angles.trBase[0] == 1 || le->angles.trBase[1] == 1 || le->angles.trBase[2] == 1 ) {
				le->pos.trType = TR_GRAVITY;
				VectorScale( dir, 10 * 8, le->pos.trDelta );
				le->pos.trDelta[0] += ( ( random() * 100 ) - 50 );
				le->pos.trDelta[1] += ( ( random() * 100 ) - 50 );
				le->pos.trDelta[2] = ( random() * 200 ) + 200;

			} else {
				// location
				VectorScale( dir, 200 + mass, le->pos.trDelta );
				le->pos.trDelta[0] += ( ( random() * 100 ) - 50 );
				le->pos.trDelta[1] += ( ( random() * 100 ) - 50 );

				if ( dir[2] ) {
					le->pos.trDelta[2] = random() * 200 * materialmul;  // randomize sort of a lot so they don't all land together
				} else {
					le->pos.trDelta[2] = random() * 20;
				}
			}
		}
pass:
		continue;
	}

}


/*
==============
CG_Effect
	Quake ed -> target_effect (0 .5 .8) (-6 -6 -6) (6 6 6) fire explode smoke debris gore lowgrav
==============
*/
void CG_Effect( centity_t *cent, vec3_t origin, vec3_t dir ) {
	localEntity_t   *le;
	refEntity_t     *re;
//	int				howmany;
	int mass;
//	int				large, small;
	vec4_t projection, color;


	VectorSet( dir, 0, 0, 1 );    // straight up.

	mass = cent->currentState.density;

//		1 large per 100, 1 small per 24
//	large	= (int)(mass / 100);
//	small	= (int)(mass / 24) + 1;

	if ( cent->currentState.eventParm & 1 ) {  // fire
		CG_MissileHitWall( WP_DYNAMITE, 0, origin, dir, 0 );
		return;
	}

	// (SA) right now force smoke on any explosions
//	if(cent->currentState.eventParm & 4)	// smoke
	if ( cent->currentState.eventParm & 7 ) {
		int i, j;
		vec3_t sprVel, sprOrg;
		// explosion sprite animation
		VectorScale( dir, 16, sprVel );
		for ( i = 0; i < 5; i++ ) {
			for ( j = 0; j < 3; j++ )
				sprOrg[j] = origin[j] + 64 * dir[j] + 24 * crandom();
			sprVel[2] += rand() % 50;
//			CG_ParticleExplosion( 2, sprOrg, sprVel, 1000+rand()%250, 20, 40+rand()%60 );
			CG_ParticleExplosion( "blacksmokeanim", sprOrg, sprVel, 3500 + rand() % 250, 10, 250 + rand() % 60, qfalse ); // JPW NERVE was smokeanimb
		}
	}


	if ( cent->currentState.eventParm & 2 ) {  // explode
		vec3_t sprVel, sprOrg;
		trap_S_StartSound( origin, -1, CHAN_AUTO, cgs.media.sfx_rockexp );

		// new explode	(from rl)
		VectorMA( origin, 16, dir, sprOrg );
		VectorScale( dir, 100, sprVel );
		CG_ParticleExplosion( "explode1", sprOrg, sprVel, 500, 20, 160, qtrue );
		//CG_ParticleExplosion( "blueexp", sprOrg, sprVel, 1200, 9, 300 );

		// (SA) this is done only if the level designer has it marked in the entity.
		//		(see "cent->currentState.eventParm & 64" below)

		// RF, throw some debris
//		CG_AddDebris( origin, dir,
//						280,	// speed
//						1400,	// duration
//						// 15 + rand()%5 );	// count
//						7 + rand()%2 );	// count

		//%	CG_ImpactMark( cgs.media.burnMarkShader, origin, dir, random()*360, 1,1,1,1, qfalse, 64, qfalse, 0xffffffff );
		VectorSet( projection, 0, 0, -1 );
		projection[ 3 ] = 64.0f;
		Vector4Set( color, 1.0f, 1.0f, 1.0f, 1.0f );
		trap_R_ProjectDecal( cgs.media.burnMarkShader, 1, (vec3_t*) origin, projection, color, cg_markTime.integer, ( cg_markTime.integer >> 4 ) );
	}


	if ( cent->currentState.eventParm & 8 ) { // rubble
		// share the cg_explode code with func_explosives
		const char *s;
		qhandle_t sh = 0;   // shader handle

		vec3_t newdir = {0, 0, 0};

		if ( cent->currentState.angles2[0] || cent->currentState.angles2[1] || cent->currentState.angles2[2] ) {
			VectorCopy( cent->currentState.angles2, newdir );
		}

		s = CG_ConfigString( CS_TARGETEFFECT ); // see if ent has a shader specified
		if ( s && strlen( s ) > 0 ) {
			sh = trap_R_RegisterShader( va( "textures/%s", s ) );    // FIXME: don't do this here.  only for testing

		}
		cent->currentState.eFlags &= ~EF_INHERITSHADER; // don't try to inherit shader
		cent->currentState.dl_intensity = 0;        // no sound
		CG_Explode( cent, origin, newdir, sh );
	}


	if ( cent->currentState.eventParm & 16 ) { // gore
		le = CG_AllocLocalEntity();
		re = &le->refEntity;

		le->leType = LE_FRAGMENT;
		le->startTime = cg.time;
		le->endTime = le->startTime + 5000 + random() * 3000;
//----(SA)	fading out
		re->fadeStartTime       = le->endTime - 4000;
		re->fadeEndTime         = le->endTime;
//----(SA)	end

		VectorCopy( origin, re->origin );
		AxisCopy( axisDefault, re->axis );
		//	re->hModel = hModel;
		re->hModel = cgs.media.gibIntestine;
		le->pos.trType = TR_GRAVITY;
		VectorCopy( origin, le->pos.trBase );

		//	VectorCopy( velocity, le->pos.trDelta );
		VectorNormalize( dir );
		VectorMA( dir, 200, dir, le->pos.trDelta );

		le->pos.trTime = cg.time;

		le->bounceFactor = 0.3;

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


	if ( cent->currentState.eventParm & 64 ) { // debris trails (the black strip that Ryan did)
		CG_AddDebris( origin, dir,
					  280,      // speed
					  1400,     // duration
					  // 15 + rand()%5 );	// count
					  7 + rand() % 2 ); // count
	}
}






/*
CG_Shard

	We should keep this separate since there will be considerable differences
	in the physical properties of shard vrs debris. not to mention the fact
	there is no way we can quantify what type of effects the designers will
	potentially desire. If it is still possible to merge the functionality of
	cg_shard into cg_explode at a latter time I would have no problem with that
	but for now I want to keep it separate
*/
void CG_Shard( centity_t *cent, vec3_t origin, vec3_t dir ) {
	localEntity_t   *le;
	refEntity_t     *re;
	int type;
	int howmany;
	int i;
	int rval;

	qboolean isflyingdebris = qfalse;

	type = cent->currentState.density;
	howmany = cent->currentState.frame;

	for ( i = 0; i < howmany; i++ )
	{
		le = CG_AllocLocalEntity();
		re = &le->refEntity;

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

//----(SA)	fading out
		re->fadeStartTime       = le->endTime - 1000;
		re->fadeEndTime         = le->endTime;
//----(SA)	end

		if ( type == 999 ) {
			le->startTime           = cg.time;
			le->endTime             = le->startTime + 100;
			re->fadeStartTime       = le->endTime - 100;
			re->fadeEndTime         = le->endTime;
			type = 1;

			isflyingdebris = qtrue;
		}


		le->lifeRate            = 1.0 / ( le->endTime - le->startTime );
		le->leFlags             = LEF_TUMBLE;
		le->bounceFactor        = 0.4;
		// le->leBounceSoundType	= LEBS_WOOD;
		le->leMarkType          = 0;

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

		if ( type == FXTYPE_GLASS ) { // glass
			rval = rand() % 2;

			if ( rval ) {
				re->hModel = cgs.media.shardGlass1;
			} else {
				re->hModel = cgs.media.shardGlass2;
			}
		} else if ( type == FXTYPE_WOOD )     { // wood
			rval = rand() % 2;

			if ( rval ) {
				re->hModel = cgs.media.shardWood1;
			} else {
				re->hModel = cgs.media.shardWood2;
			}
		} else if ( type == FXTYPE_METAL )     { // metal
			rval = rand() % 2;

			if ( rval ) {
				re->hModel = cgs.media.shardMetal1;
			} else {
				re->hModel = cgs.media.shardMetal2;
			}
		}
		/*else if (type == 3) // ceramic
		{
			rval = rand()%2;

			if (rval)
				re->hModel = cgs.media.shardCeramic1;
			else
				re->hModel = cgs.media.shardCeramic2;
		}*/
		else if ( type == FXTYPE_BRICK || type == FXTYPE_STONE ) { // rubble
			rval = rand() % 3;

			if ( rval == 1 ) {
				re->hModel = cgs.media.shardRubble1;
			} else if ( rval == 2 ) {
				re->hModel = cgs.media.shardRubble2;
			} else {
				re->hModel = cgs.media.shardRubble3;
			}

		} else {
			CG_Printf( "CG_Debris has an unknown type\n" );
		}

		// location
		if ( isflyingdebris ) {
			le->pos.trType = TR_GRAVITY_LOW;
		} else {
			le->pos.trType = TR_GRAVITY;
		}

		VectorCopy( origin, le->pos.trBase );
		VectorNormalize( dir );
		VectorScale( dir, 10 * howmany, le->pos.trDelta );
		le->pos.trTime = cg.time;
		le->pos.trDelta[0] += ( ( random() * 100 ) - 50 );
		le->pos.trDelta[1] += ( ( random() * 100 ) - 50 );
		if ( type ) {
			le->pos.trDelta[2] = ( random() * 200 ) + 100;    // randomize sort of a lot so they don't all land together
		} else { // glass
			le->pos.trDelta[2] = ( random() * 100 ) + 50; // randomize sort of a lot so they don't all land together

		}
		// rotation
		le->angles.trType = TR_LINEAR;
		le->angles.trTime = cg.time;
		le->angles.trBase[0] = rand() & 31;
		le->angles.trBase[1] = rand() & 31;
		le->angles.trBase[2] = rand() & 31;
		le->angles.trDelta[0] = ( 100 + ( rand() & 500 ) ) - 300;
		le->angles.trDelta[1] = ( 100 + ( rand() & 500 ) ) - 300;
		le->angles.trDelta[2] = ( 100 + ( rand() & 500 ) ) - 300;

	}

}


void CG_ShardJunk( centity_t *cent, vec3_t origin, vec3_t dir ) {
	localEntity_t   *le;
	refEntity_t     *re;
	int type;

	type = cent->currentState.density;

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

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

	re->fadeStartTime       = le->endTime - 1000;
	re->fadeEndTime         = le->endTime;

	le->lifeRate            = 1.0 / ( le->endTime - le->startTime );
	le->leFlags             = LEF_TUMBLE;
	le->bounceFactor        = 0.4;
	le->leMarkType          = 0;

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

	re->hModel = cgs.media.shardJunk[rand() % MAX_LOCKER_DEBRIS];

	le->pos.trType = TR_GRAVITY;

	VectorCopy( origin, le->pos.trBase );
	VectorNormalize( dir );
	VectorScale( dir, 10 * 8, le->pos.trDelta );
	le->pos.trTime = cg.time;
	le->pos.trDelta[0] += ( ( random() * 100 ) - 50 );
	le->pos.trDelta[1] += ( ( random() * 100 ) - 50 );

	le->pos.trDelta[2] = ( random() * 100 ) + 50; // randomize sort of a lot so they don't all land together

	// rotation
	le->angles.trType = TR_LINEAR;
	le->angles.trTime = cg.time;
	//le->angles.trBase[0] = rand()&31;
	//le->angles.trBase[1] = rand()&31;
	le->angles.trBase[2] = rand() & 31;

	//le->angles.trDelta[0] = (100 + (rand()&500)) - 300;
	//le->angles.trDelta[1] = (100 + (rand()&500)) - 300;
	le->angles.trDelta[2] = ( 100 + ( rand() & 500 ) ) - 300;

}

// Gordon: debris test
void CG_Debris( centity_t *cent, vec3_t origin, vec3_t dir ) {
	localEntity_t   *le;
	refEntity_t     *re;
	int type;

	type = cent->currentState.density;

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

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

	re->fadeStartTime       = le->endTime - 1000;
	re->fadeEndTime         = le->endTime;

	le->lifeRate            = 1.0 / ( le->endTime - le->startTime );
	le->leFlags             = LEF_TUMBLE | LEF_TUMBLE_SLOW;
	le->bounceFactor        = 0.4;
	le->leMarkType          = 0;
	le->breakCount          = 1;
	le->sizeScale           = 0.5;

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

	re->hModel = cgs.inlineDrawModel[cent->currentState.modelindex];

	le->pos.trType = TR_GRAVITY;

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

	// rotation
	le->angles.trType = TR_LINEAR;
	le->angles.trTime = cg.time;
	le->angles.trBase[2] = rand() & 31;

	le->angles.trDelta[2] = ( 100 + ( rand() & 500 ) ) - 300;
	le->angles.trDelta[2] = ( 50 + ( rand() & 400 ) ) - 100;
	le->angles.trDelta[2] = ( 50 + ( rand() & 400 ) ) - 100;
}
// ===================

//void CG_BatDeath( centity_t *cent )
//{
//	CG_ParticleExplosion( "blood", cent->lerpOrigin, vec3_origin, 400, 20, 30, qfalse );
//}

void CG_MortarImpact( centity_t *cent, vec3_t origin, int sfx, qboolean dist ) {
	if ( sfx >= 0 ) {
		trap_S_StartSound( origin, -1, CHAN_AUTO, cgs.media.sfx_mortarexp[sfx] );
	}

	if ( dist ) {
		vec3_t gorg, norm;
		float gdist;

		VectorSubtract( origin, cg.refdef_current->vieworg, norm );
		gdist = VectorNormalize( norm );
		if ( gdist > 1200 && gdist < 8000 ) {  // 1200 is max cam shakey dist (2*600) use gorg as the new sound origin
			VectorMA( cg.refdef_current->vieworg, 800, norm, gorg ); // non-distance falloff makes more sense; sfx2range was gdist*0.2
			// sfx2range is variable to give us minimum volume control different explosion sizes (see mortar, panzerfaust, and grenade)
			trap_S_StartSoundEx( gorg, -1, CHAN_WEAPON, cgs.media.sfx_mortarexpDist, SND_NOCUT );
		}

		if ( cent->currentState.clientNum == cg.snap->ps.clientNum && cg.mortarImpactTime != -2 ) {
			VectorCopy( origin, cg.mortarImpactPos );
			cg.mortarImpactTime = cg.time;
			cg.mortarImpactOutOfMap = qfalse;
		}
	}
}

void CG_MortarMiss( centity_t *cent, vec3_t origin ) {
	if ( cent->currentState.clientNum == cg.snap->ps.clientNum && cg.mortarImpactTime != -2 ) {
		VectorCopy( origin, cg.mortarImpactPos );
		cg.mortarImpactTime = cg.time;
		if ( cent->currentState.density ) {
			cg.mortarImpactOutOfMap = qtrue;
		} else {
			cg.mortarImpactOutOfMap = qfalse;
		}
	}
}

// a convenience function for all footstep sound playing
static void CG_StartFootStepSound( bg_playerclass_t* classInfo, entityState_t *es, sfxHandle_t sfx ) {
	if ( cg_footsteps.integer ) {
		trap_S_StartSound( NULL, es->number, CHAN_BODY, sfx );
	}
}

/*
==============
CG_EntityEvent

An entity has an event value
also called by CG_CheckPlayerstateEvents
==============
*/
extern void CG_AddBulletParticles( vec3_t origin, vec3_t dir, int speed, int duration, int count, float randScale );
// JPW NERVE
void CG_MachineGunEjectBrass( centity_t *cent );
void CG_MachineGunEjectBrassNew( centity_t *cent );
// jpw
#define DEBUGNAME( x ) if ( cg_debugEvents.integer ) {CG_Printf( x "\n" );}
void CG_EntityEvent( centity_t *cent, vec3_t position ) {
	entityState_t       *es;
	int event;
	vec3_t dir;
	const char          *s;
	int clientNum;
	clientInfo_t        *ci;
	char tempStr[MAX_QPATH];
	bg_playerclass_t    *classInfo;
	bg_character_t      *character;

// JPW NERVE copied here for mg42 SFX event
	vec3_t porg, gorg, norm;                // player/gun origin
	float gdist;
// jpw

	static int footstepcnt = 0;
	static int splashfootstepcnt = 0;

	es = &cent->currentState;
	event = es->event & ~EV_EVENT_BITS;

	if ( cg_debugEvents.integer ) {
		CG_Printf( "time:%i ent:%3i  event:%3i ", cg.time, es->number, event );
	}

	if ( !event ) {
		DEBUGNAME( "ZEROEVENT" );
		return;
	}

	clientNum = es->clientNum;
	if ( clientNum < 0 || clientNum >= MAX_CLIENTS ) {
		clientNum = 0;
	}
	ci = &cgs.clientinfo[ clientNum ];
	classInfo = CG_PlayerClassForClientinfo( ci, cent );
	character = CG_CharacterForClientinfo( ci, cent );

	switch ( event ) {
		//
		// movement generated events
		//
	case EV_FOOTSTEP:
		DEBUGNAME( "EV_FOOTSTEP" );
		if ( es->eventParm != FOOTSTEP_TOTAL ) {
			if ( es->eventParm ) {
				CG_StartFootStepSound( classInfo, es, cgs.media.footsteps[ es->eventParm ][footstepcnt] );
			} else {
				CG_StartFootStepSound( classInfo, es, cgs.media.footsteps[ character->animModelInfo->footsteps ][footstepcnt] );
			}
		}
		break;
	case EV_FOOTSPLASH:
		DEBUGNAME( "EV_FOOTSPLASH" );
		CG_StartFootStepSound( classInfo, es, cgs.media.footsteps[ FOOTSTEP_SPLASH ][splashfootstepcnt] );
		break;
	case EV_FOOTWADE:
		DEBUGNAME( "EV_FOOTWADE" );
		CG_StartFootStepSound( classInfo, es, cgs.media.footsteps[ FOOTSTEP_SPLASH ][splashfootstepcnt] );
		break;
	case EV_SWIM:
		DEBUGNAME( "EV_SWIM" );
		CG_StartFootStepSound( classInfo, es, cgs.media.footsteps[ FOOTSTEP_SPLASH ][footstepcnt] );
		break;

	case EV_FALL_SHORT:
		DEBUGNAME( "EV_FALL_SHORT" );
		if ( es->eventParm != FOOTSTEP_TOTAL ) {
			if ( es->eventParm ) {
				trap_S_StartSound( NULL, es->number, CHAN_AUTO, cgs.media.landSound[ es->eventParm ] );
			} else {
				trap_S_StartSound( NULL, es->number, CHAN_AUTO, cgs.media.landSound[ character->animModelInfo->footsteps ] );
			}
		}
		if ( clientNum == cg.predictedPlayerState.clientNum ) {
			// smooth landing z changes
			cg.landChange = -8;
			cg.landTime = cg.time;
		}
		break;

	case EV_FALL_DMG_10:
		DEBUGNAME( "EV_FALL_DMG_10" );
		if ( es->eventParm != FOOTSTEP_TOTAL ) {
			if ( es->eventParm ) {
				trap_S_StartSound( NULL, es->number, CHAN_AUTO, cgs.media.landSound[ es->eventParm ] );
			} else {
				trap_S_StartSound( NULL, es->number, CHAN_AUTO, cgs.media.landSound[ character->animModelInfo->footsteps ] );
			}
		}
		trap_S_StartSound( NULL, es->number, CHAN_AUTO, cgs.media.landHurt );
		cent->pe.painTime = cg.time;    // don't play a pain sound right after this
		if ( clientNum == cg.predictedPlayerState.clientNum ) {
			// smooth landing z changes
			cg.landChange = -16;
			cg.landTime = cg.time;
		}
		break;
	case EV_FALL_DMG_15:
		DEBUGNAME( "EV_FALL_DMG_15" );
		if ( es->eventParm != FOOTSTEP_TOTAL ) {
			if ( es->eventParm ) {
				trap_S_StartSound( NULL, es->number, CHAN_AUTO, cgs.media.landSound[ es->eventParm ] );
			} else {
				trap_S_StartSound( NULL, es->number, CHAN_AUTO, cgs.media.landSound[ character->animModelInfo->footsteps ] );
			}
		}
		trap_S_StartSound( NULL, es->number, CHAN_AUTO, cgs.media.landHurt );
		cent->pe.painTime = cg.time;    // don't play a pain sound right after this
		if ( clientNum == cg.predictedPlayerState.clientNum ) {
			// smooth landing z changes
			cg.landChange = -16;
			cg.landTime = cg.time;
		}
		break;
	case EV_FALL_DMG_25:
		DEBUGNAME( "EV_FALL_DMG_25" );
		if ( es->eventParm != FOOTSTEP_TOTAL ) {
			if ( es->eventParm ) {
				trap_S_StartSound( NULL, es->number, CHAN_AUTO, cgs.media.landSound[ es->eventParm ] );
			} else {
				trap_S_StartSound( NULL, es->number, CHAN_AUTO, cgs.media.landSound[ character->animModelInfo->footsteps ] );
			}
		}
		trap_S_StartSound( NULL, es->number, CHAN_AUTO, cgs.media.landHurt );
		cent->pe.painTime = cg.time;    // don't play a pain sound right after this
		if ( clientNum == cg.predictedPlayerState.clientNum ) {
			// smooth landing z changes
			cg.landChange = -24;
			cg.landTime = cg.time;
		}
		break;
	case EV_FALL_DMG_50:
		DEBUGNAME( "EV_FALL_DMG_50" );
		if ( es->eventParm != FOOTSTEP_TOTAL ) {
			if ( es->eventParm ) {
				trap_S_StartSound( NULL, es->number, CHAN_AUTO, cgs.media.landSound[ es->eventParm ] );
			} else {
				trap_S_StartSound( NULL, es->number, CHAN_AUTO, cgs.media.landSound[ character->animModelInfo->footsteps ] );
			}
		}
		trap_S_StartSound( NULL, es->number, CHAN_AUTO, cgs.media.landHurt );
		cent->pe.painTime = cg.time;    // don't play a pain sound right after this
		if ( clientNum == cg.predictedPlayerState.clientNum ) {
			// smooth landing z changes
			cg.landChange = -24;
			cg.landTime = cg.time;
		}
		break;
	case EV_FALL_NDIE:
		DEBUGNAME( "EV_FALL_NDIE" );
		if ( es->eventParm != FOOTSTEP_TOTAL ) {
			if ( es->eventParm ) {
				trap_S_StartSound( NULL, es->number, CHAN_AUTO, cgs.media.landSound[ es->eventParm ] );
			} else {
				trap_S_StartSound( NULL, es->number, CHAN_AUTO, cgs.media.landSound[ character->animModelInfo->footsteps ] );
			}
		}
		trap_S_StartSound( NULL, es->number, CHAN_AUTO, cgs.media.landHurt );
		cent->pe.painTime = cg.time;    // don't play a pain sound right after this
		// splat
		break;

	case EV_EXERT1:
		DEBUGNAME( "EV_EXERT1" );
		trap_S_StartSound( NULL, es->number, CHAN_VOICE, CG_CustomSound( es->number, "*exert1.wav" ) );
		break;
	case EV_EXERT2:
		DEBUGNAME( "EV_EXERT2" );
		trap_S_StartSound( NULL, es->number, CHAN_VOICE, CG_CustomSound( es->number, "*exert2.wav" ) );
		break;
	case EV_EXERT3:
		DEBUGNAME( "EV_EXERT3" );
		trap_S_StartSound( NULL, es->number, CHAN_VOICE, CG_CustomSound( es->number, "*exert3.wav" ) );
		break;

	case EV_STEP_4:
	case EV_STEP_8:
	case EV_STEP_12:
	case EV_STEP_16:        // smooth out step up transitions
		DEBUGNAME( "EV_STEP" );
		{
			float oldStep;
			int delta;
			int step;

			if ( clientNum != cg.predictedPlayerState.clientNum ) {
				break;
			}
			// if we are interpolating, we don't need to smooth steps
			if ( cg.demoPlayback || ( cg.snap->ps.pm_flags & PMF_FOLLOW ) ||
				 cg_nopredict.integer
#ifdef ALLOW_GSYNC
				 || cg_synchronousClients.integer
#endif // ALLOW_GSYNC
				 ) {
				break;
			}
			// check for stepping up before a previous step is completed
			delta = cg.time - cg.stepTime;
			if ( delta < STEP_TIME ) {
				oldStep = cg.stepChange * ( STEP_TIME - delta ) / STEP_TIME;
			} else {
				oldStep = 0;
			}

			// add this amount
			step = 4 * ( event - EV_STEP_4 + 1 );
			cg.stepChange = oldStep + step;
			if ( cg.stepChange > MAX_STEP_CHANGE ) {
				cg.stepChange = MAX_STEP_CHANGE;
			}
			cg.stepTime = cg.time;
			break;
		}

	case EV_JUMP:
		DEBUGNAME( "EV_JUMP" );
		trap_S_StartSound( NULL, es->number, CHAN_VOICE, CG_CustomSound( es->number, "*jump1.wav" ) );
		break;
	case EV_TAUNT:
		DEBUGNAME( "EV_TAUNT" );
		trap_S_StartSound( NULL, es->number, CHAN_VOICE, CG_CustomSound( es->number, "*taunt.wav" ) );
		break;
	case EV_WATER_TOUCH:
		DEBUGNAME( "EV_WATER_TOUCH" );
		trap_S_StartSound( NULL, es->number, CHAN_AUTO, cgs.media.watrInSound );
		break;
	case EV_WATER_LEAVE:
		DEBUGNAME( "EV_WATER_LEAVE" );
		trap_S_StartSound( NULL, es->number, CHAN_AUTO, cgs.media.watrOutSound );
		break;
	case EV_WATER_UNDER:
		DEBUGNAME( "EV_WATER_UNDER" );
		trap_S_StartSound( NULL, es->number, CHAN_AUTO, cgs.media.watrUnSound );
		if ( cg.clientNum == es->number ) {
			cg.waterundertime = cg.time + HOLDBREATHTIME;
		}

//----(SA)	this fog stuff for underwater is really just a test for feasibility of creating the under-water effect that way.
//----(SA)	the related issues of load/savegames, death underwater, etc. are not handled at all.
//----(SA)	the actual problem, of course, is doing underwater stuff when the water is very turbulant and you can't simply
//----(SA)	do things based on the players head being above/below the water brushes top surface. (since the waves can potentially be /way/ above/below that)

		// DHM - Nerve :: causes problems in multiplayer...
		break;
	case EV_WATER_CLEAR:
		DEBUGNAME( "EV_WATER_CLEAR" );
		//trap_S_StartSound (NULL, es->number, CHAN_AUTO, CG_CustomSound( es->number, "*gasp.wav" ) );
		trap_S_StartSound( NULL, es->number, CHAN_AUTO, cgs.media.watrOutSound );
		if ( es->eventParm ) {
			trap_S_StartSound( NULL, es->number, CHAN_AUTO, cgs.media.watrGaspSound );
		}
		break;

	case EV_ITEM_PICKUP:
	case EV_ITEM_PICKUP_QUIET:
		DEBUGNAME( "EV_ITEM_PICKUP" );
		{
			gitem_t *item;
			int index;

			index = es->eventParm;      // player predicted

			if ( index < 1 || index >= bg_numItems ) {
				break;
			}
			item = &bg_itemlist[ index ];

			if ( event == EV_ITEM_PICKUP ) { // not quiet
				// powerups and team items will have a separate global sound, this one
				// will be played at prediction time
				if ( item->giType == IT_TEAM ) {
					trap_S_StartSound( NULL, es->number, CHAN_AUTO, trap_S_RegisterSound( "sound/misc/w_pkup.wav", qfalse ) );
				} else {
					trap_S_StartSound( NULL, es->number, CHAN_AUTO, trap_S_RegisterSound( item->pickup_sound, qfalse ) );
				}
			}

			// show icon and name on status bar
			if ( es->number == cg.snap->ps.clientNum ) {
				CG_ItemPickup( index );
			}

//----(SA)	draw the HUD items for a sec since this is a special item
/*			if ( item->giType == IT_KEY)
				cg.itemFadeTime = cg.time + 1000;*/

		}
		break;

	case EV_GLOBAL_ITEM_PICKUP:
		DEBUGNAME( "EV_GLOBAL_ITEM_PICKUP" );
		{
			gitem_t *item;
			int index;

			index = es->eventParm;      // player predicted

			if ( index < 1 || index >= bg_numItems ) {
				break;
			}
			item = &bg_itemlist[ index ];
			if ( *item->pickup_sound ) {
				// powerup pickups are global
				trap_S_StartSound( NULL, cg.snap->ps.clientNum, CHAN_AUTO, trap_S_RegisterSound( item->pickup_sound, qfalse ) );    // FIXME: precache
			}

			// show icon and name on status bar
			if ( es->number == cg.snap->ps.clientNum ) {
				CG_ItemPickup( index );
			}
		}
		break;

		//
		// weapon events
		//
	case EV_VENOM:
		DEBUGNAME( "EV_VENOM" );
//		CG_VenomFire( es, qfalse );
		break;

	case EV_WEAP_OVERHEAT:
		DEBUGNAME( "EV_WEAP_OVERHEAT" );

		// start weapon idle animation
		if ( es->number == cg.snap->ps.clientNum ) {
			cg.predictedPlayerState.weapAnim = ( ( cg.predictedPlayerState.weapAnim & ANIM_TOGGLEBIT ) ^ ANIM_TOGGLEBIT ) | PM_IdleAnimForWeapon( cg.snap->ps.weapon );
			cent->overheatTime = cg.time;   // used to make the barrels smoke when overheated
		}

		if ( BG_PlayerMounted( es->eFlags ) ) {
			trap_S_StartSoundVControl( NULL, es->number, CHAN_AUTO, cgs.media.hWeaponHeatSnd, 255 );
		} else if ( es->eFlags & EF_MOUNTEDTANK ) {
			if ( cg_entities[cg_entities[cg_entities[ es->number ].tagParent].tankparent].currentState.density & 8 ) {
				trap_S_StartSoundVControl( NULL, es->number, CHAN_AUTO, cgs.media.hWeaponHeatSnd_2, 255 );
			} else {
				trap_S_StartSoundVControl( NULL, es->number, CHAN_AUTO, cgs.media.hWeaponHeatSnd, 255 );
			}
		} else if ( cg_weapons[es->weapon].overheatSound ) {
			trap_S_StartSound( NULL, es->number, CHAN_AUTO, cg_weapons[es->weapon].overheatSound );
		}
		break;

// JPW NERVE
	case EV_SPINUP:
		DEBUGNAME( "EV_SPINUP" );
		trap_S_StartSound( NULL, es->number, CHAN_AUTO, cg_weapons[es->weapon].spinupSound );
		break;
// jpw
	case EV_EMPTYCLIP:
		DEBUGNAME( "EV_EMPTYCLIP" );
		break;

	case EV_FILL_CLIP:
		DEBUGNAME( "EV_FILL_CLIP" );
		if ( cgs.clientinfo[cg.clientNum].skill[SK_LIGHT_WEAPONS] >= 2 && BG_isLightWeaponSupportingFastReload( es->weapon ) && cg_weapons[es->weapon].reloadFastSound ) {
			trap_S_StartSound( NULL, es->number, CHAN_WEAPON, cg_weapons[es->weapon].reloadFastSound );
		} else if ( cg_weapons[es->weapon].reloadSound ) {
			trap_S_StartSound( NULL, es->number, CHAN_WEAPON, cg_weapons[es->weapon].reloadSound ); // JPW NERVE following sherman's SP fix, should allow killing reload sound when player dies
		}
		break;

// JPW NERVE play a sound when engineer fixes MG42
	case EV_MG42_FIXED:
		DEBUGNAME( "EV_MG42_FIXED" );
		//trap_S_StartSound(NULL,es->number,CHAN_WEAPON,cg_weapons[WP_MAUSER].reloadSound); // Arnout: needs updating
		break;
// jpw

	case EV_NOAMMO:
	case EV_WEAPONSWITCHED:
		DEBUGNAME( "EV_NOAMMO" );
		if ( ( es->weapon != WP_GRENADE_LAUNCHER ) &&
			 ( es->weapon != WP_GRENADE_PINEAPPLE ) &&
			 ( es->weapon != WP_DYNAMITE ) &&
			 ( es->weapon != WP_LANDMINE ) &&
			 ( es->weapon != WP_SATCHEL ) &&
			 ( es->weapon != WP_SATCHEL_DET ) &&
			 ( es->weapon != WP_TRIPMINE ) &&
			 ( es->weapon != WP_SMOKE_BOMB ) &&
			 ( es->weapon != WP_AMMO ) &&
			 ( es->weapon != WP_MEDKIT ) ) {
			trap_S_StartSound( NULL, es->number, CHAN_AUTO, cgs.media.noAmmoSound );
		}

		if ( es->number == cg.snap->ps.clientNum && (
				 ( cg_noAmmoAutoSwitch.integer > 0 && !CG_WeaponSelectable( cg.weaponSelect ) ) ||
				 es->weapon == WP_MORTAR_SET ||
				 es->weapon == WP_MOBILE_MG42_SET ||
				 es->weapon == WP_GRENADE_LAUNCHER ||
				 es->weapon == WP_GRENADE_PINEAPPLE ||
				 es->weapon == WP_DYNAMITE ||
				 es->weapon == WP_SMOKE_MARKER ||
				 es->weapon == WP_PANZERFAUST ||
				 es->weapon == WP_ARTY ||
				 es->weapon == WP_LANDMINE ||
				 es->weapon == WP_SATCHEL ||
				 es->weapon == WP_SATCHEL_DET ||
				 es->weapon == WP_TRIPMINE ||
				 es->weapon == WP_SMOKE_BOMB ||
				 es->weapon == WP_AMMO ||
				 es->weapon == WP_MEDKIT ) ) {
			CG_OutOfAmmoChange( event == EV_WEAPONSWITCHED ? qfalse : qtrue );
		}
		break;
	case EV_CHANGE_WEAPON:
		DEBUGNAME( "EV_CHANGE_WEAPON" );
		trap_S_StartSound( NULL, es->number, CHAN_AUTO, cgs.media.selectSound );
		break;
	case EV_CHANGE_WEAPON_2:
		DEBUGNAME( "EV_CHANGE_WEAPON" );

		trap_S_StartSound( NULL, es->number, CHAN_AUTO, cgs.media.selectSound );

		if ( es->number == cg.snap->ps.clientNum ) {
			int newweap = 0;

			// client will get this message if reloading while using an alternate weapon
			// client should voluntarily switch back to primary at that point
			switch ( es->weapon ) {
			case WP_FG42SCOPE:
				newweap = WP_FG42;
				break;
			case WP_GARAND_SCOPE:
				newweap = WP_GARAND;
				break;
			case WP_K43_SCOPE:
				newweap = WP_K43;
				break;
			default:
				break;
			}

			if ( newweap ) {
				CG_FinishWeaponChange( es->weapon, newweap );
			}
		}
		break;

	case EV_FIRE_WEAPON_MOUNTEDMG42:
	case EV_FIRE_WEAPON_MG42:
		VectorCopy( cent->currentState.pos.trBase, gorg );
		VectorCopy( cg.refdef_current->vieworg, porg );
		VectorSubtract( gorg, porg, norm );
		gdist = VectorNormalize( norm );
		if ( gdist > 512 && gdist < 4096 ) {
			VectorMA( cg.refdef_current->vieworg, 64, norm, gorg );
			if ( cg_entities[cg_entities[cg_entities[ cent->currentState.number ].tagParent].tankparent].currentState.density & 8 ) { // should we use a browning?
				trap_S_StartSoundEx( gorg, cent->currentState.number, CHAN_WEAPON, cgs.media.hWeaponEchoSnd_2, SND_NOCUT );
			} else {
				trap_S_StartSoundEx( gorg, cent->currentState.number, CHAN_WEAPON, cgs.media.hWeaponEchoSnd, SND_NOCUT );
			}
		}
		DEBUGNAME( "EV_FIRE_WEAPON_MG42" );
		CG_FireWeapon( cent );
		break;
	case EV_FIRE_WEAPON_AAGUN:
		DEBUGNAME( "EV_FIRE_WEAPON_AAGUN" );
		CG_FireWeapon( cent );
		break;
	case EV_FIRE_WEAPON:
	case EV_FIRE_WEAPONB:
		DEBUGNAME( "EV_FIRE_WEAPON" );
		if ( cent->currentState.clientNum == cg.snap->ps.clientNum && cg.snap->ps.eFlags & EF_ZOOMING ) { // to stop airstrike sfx
			break;
		}
		CG_FireWeapon( cent );
		if ( event == EV_FIRE_WEAPONB ) { // akimbo firing
			cent->akimboFire = qtrue;
		} else {
			cent->akimboFire = qfalse;
		}
		break;
	case EV_FIRE_WEAPON_LASTSHOT:
		DEBUGNAME( "EV_FIRE_WEAPON_LASTSHOT" );
		CG_FireWeapon( cent );
		break;

	case EV_NOFIRE_UNDERWATER:
		DEBUGNAME( "EV_NOFIRE_UNDERWATER" );
		if ( cgs.media.noFireUnderwater ) {
			trap_S_StartSound( NULL, es->number, CHAN_WEAPON, cgs.media.noFireUnderwater );
		}
		break;

	case EV_PLAYER_TELEPORT_IN:
		break;

	case EV_PLAYER_TELEPORT_OUT:
		break;

	case EV_ITEM_POP:
		break;
	case EV_ITEM_RESPAWN:
		break;

	case EV_GRENADE_BOUNCE:
		DEBUGNAME( "EV_GRENADE_BOUNCE" );

		// DYNAMITE // Gordon: or LANDMINE FIXME: change this? (mebe a metallic sound)
		if ( es->weapon == WP_SATCHEL ) {
			trap_S_StartSound( NULL, es->number, CHAN_AUTO, cgs.media.satchelbounce1 );
		} else if ( es->weapon == WP_DYNAMITE ) {
			trap_S_StartSound( NULL, es->number, CHAN_AUTO, cgs.media.dynamitebounce1 );
		} else if ( es->weapon == WP_LANDMINE ) {
			trap_S_StartSound( NULL, es->number, CHAN_AUTO, cgs.media.landminebounce1 );
		} else {
			// GRENADES
			if ( es->eventParm != FOOTSTEP_TOTAL ) {
				if ( rand() & 1 ) {
					trap_S_StartSound( NULL, es->number, CHAN_AUTO, cgs.media.grenadebounce[ es->eventParm ][0] );
				} else {
					trap_S_StartSound( NULL, es->number, CHAN_AUTO,  cgs.media.grenadebounce[ es->eventParm ][1] );
				}
			}
		}
		break;

/*	case EV_FLAMEBARREL_BOUNCE:
		DEBUGNAME("EV_FLAMEBARREL_BOUNCE");
		if ( rand() & 1 ) {
			trap_S_StartSound (NULL, es->number, CHAN_AUTO, cgs.media.fbarrelexp1 );
		} else {
			trap_S_StartSound (NULL, es->number, CHAN_AUTO,  cgs.media.fbarrelexp2 );
		}
		break;*/

	case EV_RAILTRAIL:
		CG_RailTrail( &cgs.clientinfo[ es->otherEntityNum2 ], es->origin2, es->pos.trBase, es->dmgFlags );   //----(SA)	added 'type' field
		break;

		//
		// missile impacts
		//
	case EV_MISSILE_HIT:
		DEBUGNAME( "EV_MISSILE_HIT" );
		ByteToDir( es->eventParm, dir );
		CG_MissileHitPlayer( cent, es->weapon, position, dir, es->otherEntityNum );
		if ( es->weapon == WP_MORTAR_SET ) {
			if ( !es->legsAnim ) {
				CG_MortarImpact( cent, position, 3, qtrue );
			} else {
				CG_MortarImpact( cent, position, -1, qtrue );
			}
		}
		break;

	case EV_MISSILE_MISS_SMALL:
		DEBUGNAME( "EV_MISSILE_MISS" );
		ByteToDir( es->eventParm, dir );
		CG_MissileHitWallSmall( es->weapon, 0, position, dir );
		break;

	case EV_MISSILE_MISS:
		DEBUGNAME( "EV_MISSILE_MISS" );
		ByteToDir( es->eventParm, dir );
		CG_MissileHitWall( es->weapon, 0, position, dir, 0 );   // (SA) modified to send missilehitwall surface parameters
		if ( es->weapon == WP_MORTAR_SET ) {
			if ( !es->legsAnim ) {
				CG_MortarImpact( cent, position, 3, qtrue );
			} else {
				CG_MortarImpact( cent, position, -1, qtrue );
			}
		}
		break;

	case EV_MISSILE_MISS_LARGE:
		DEBUGNAME( "EV_MISSILE_MISS_LARGE" );
		ByteToDir( es->eventParm, dir );
		if ( es->weapon == WP_ARTY || es->weapon == WP_SMOKE_MARKER ) {
			CG_MissileHitWall( es->weapon, 0, position, dir, 0 );    // (SA) modified to send missilehitwall surface parameters
		} else {
			CG_MissileHitWall( VERYBIGEXPLOSION, 0, position, dir, 0 );  // (SA) modified to send missilehitwall surface parameters
		}
		break;

	case EV_MORTAR_IMPACT:
		DEBUGNAME( "EV_MORTAR_IMPACT" );
		CG_MortarImpact( cent, position, rand() % 3, qfalse );
		break;
	case EV_MORTAR_MISS:
		DEBUGNAME( "EV_MORTAR_MISS" );
		CG_MortarMiss( cent, position );
		break;

	case EV_MG42BULLET_HIT_WALL:
		DEBUGNAME( "EV_MG42BULLET_HIT_WALL" );
		ByteToDir( es->eventParm, dir );
		CG_Bullet( es->pos.trBase, es->otherEntityNum, dir, qfalse, ENTITYNUM_WORLD, es->otherEntityNum2, es->origin2[0], es->effect1Time );
		break;

	case EV_MG42BULLET_HIT_FLESH:
		DEBUGNAME( "EV_MG42BULLET_HIT_FLESH" );
		CG_Bullet( es->pos.trBase, es->otherEntityNum, dir, qtrue, es->eventParm, es->otherEntityNum2, 0, es->effect1Time );
		break;


	case EV_BULLET_HIT_WALL:
		DEBUGNAME( "EV_BULLET_HIT_WALL" );
		ByteToDir( es->eventParm, dir );
		CG_Bullet( es->pos.trBase, es->otherEntityNum, dir, qfalse, ENTITYNUM_WORLD, es->otherEntityNum2, es->origin2[0], 0 );
		break;

	case EV_BULLET_HIT_FLESH:
		DEBUGNAME( "EV_BULLET_HIT_FLESH" );
		CG_Bullet( es->pos.trBase, es->otherEntityNum, dir, qtrue, es->eventParm, es->otherEntityNum2, 0, 0 );
		break;

	case EV_POPUPBOOK:
	case EV_POPUP:
	case EV_GIVEPAGE:
		break;

	case EV_GENERAL_SOUND:
		DEBUGNAME( "EV_GENERAL_SOUND" );
		// Ridah, check for a sound script
		s = CG_ConfigString( CS_SOUNDS + es->eventParm );
		if ( !strstr( s, ".wav" ) ) {
			if ( CG_SoundPlaySoundScript( s, NULL, es->number, qfalse ) ) {
				break;
			}
			// try with .wav
			Q_strncpyz( tempStr, s, sizeof( tempStr ) );
			Q_strcat( tempStr, sizeof( tempStr ), ".wav" );
			s = tempStr;
		}

		// done.
		if ( cgs.gameSounds[ es->eventParm ] ) {
			// xkan, 10/31/2002 - crank up the volume
			trap_S_StartSoundVControl( NULL, es->number, CHAN_VOICE, cgs.gameSounds[ es->eventParm ], 255 );
		} else {
			s = CG_ConfigString( CS_SOUNDS + es->eventParm );
			// xkan, 10/31/2002 - crank up the volume
			trap_S_StartSoundVControl( NULL, es->number, CHAN_VOICE, CG_CustomSound( es->number, s ), 255 );
		}
		break;

	case EV_FX_SOUND:
	{
		sfxHandle_t sound;

		DEBUGNAME( "EV_FX_SOUND" );

		sound = random() * fxSounds[ es->eventParm ].max;

		if ( fxSounds[ es->eventParm ].sound[ sound ] == -1 ) {
			fxSounds[ es->eventParm ].sound[ sound ] = trap_S_RegisterSound( fxSounds[ es->eventParm ].soundfile[ sound ], qfalse );
		}

		sound = fxSounds[ es->eventParm ].sound[ sound ];

		trap_S_StartSoundVControl( NULL, es->number, CHAN_VOICE, sound, 255 );
	}
	break;
	case EV_GENERAL_SOUND_VOLUME:
	{
		int sound = es->eventParm;
		int volume = es->onFireStart;

		DEBUGNAME( "EV_GENERAL_SOUND_VOLUME" );
		// Ridah, check for a sound script
		s = CG_ConfigString( CS_SOUNDS + sound );
		if ( !strstr( s, ".wav" ) ) {
			if ( CG_SoundPlaySoundScript( s, NULL, es->number, qfalse ) ) {
				break;
			}
			// try with .wav
			Q_strncpyz( tempStr, s, sizeof( tempStr ) );
			Q_strcat( tempStr, sizeof( tempStr ), ".wav" );
			s = tempStr;
		}
		// done.
		if ( cgs.gameSounds[ sound ] ) {
			trap_S_StartSoundVControl( NULL, es->number, CHAN_VOICE, cgs.gameSounds[ sound ], volume );
		} else {
			s = CG_ConfigString( CS_SOUNDS + sound );
			trap_S_StartSoundVControl( NULL, es->number, CHAN_VOICE, CG_CustomSound( es->number, s ), volume );
		}
	}
	break;


	case EV_GLOBAL_TEAM_SOUND:
		DEBUGNAME( "EV_GLOBAL_TEAM_SOUND" );
		if ( cgs.clientinfo[ cg.snap->ps.clientNum ].team != es->teamNum ) {
			break;
		}
	case EV_GLOBAL_SOUND:   // play from the player's head so it never diminishes
		DEBUGNAME( "EV_GLOBAL_SOUND" );
		// Ridah, check for a sound script
		s = CG_ConfigString( CS_SOUNDS + es->eventParm );
		if ( !strstr( s, ".wav" ) ) {
			if ( CG_SoundPlaySoundScript( s, NULL, -1, qtrue ) ) {
				break;
			}

			// try with .wav
			Q_strncpyz( tempStr, s, sizeof( tempStr ) );
			Q_strcat( tempStr, sizeof( tempStr ), ".wav" );
			s = tempStr;
		}

		if ( cgs.gameSounds[ es->eventParm ] ) {
			trap_S_StartSound( NULL, cg.snap->ps.clientNum, CHAN_AUTO, cgs.gameSounds[ es->eventParm ] );
		} else {
			s = CG_ConfigString( CS_SOUNDS + es->eventParm );
			trap_S_StartSound( NULL, cg.snap->ps.clientNum, CHAN_AUTO, CG_CustomSound( es->number, s ) );
		}
		break;

		// DHM - Nerve
	case EV_GLOBAL_CLIENT_SOUND:
		DEBUGNAME( "EV_GLOBAL_CLIENT_SOUND" );

		if ( cg.snap->ps.clientNum == es->teamNum ) {
			s = CG_ConfigString( CS_SOUNDS + es->eventParm );
			if ( !strstr( s, ".wav" ) ) {
				if ( CG_SoundPlaySoundScript( s, NULL, -1, ( es->effect1Time ? qfalse : qtrue ) ) ) {
					break;
				}
				// try with .wav
				Q_strncpyz( tempStr, s, sizeof( tempStr ) );
				Q_strcat( tempStr, sizeof( tempStr ), ".wav" );
				s = tempStr;
			}
			// done.
			if ( cgs.gameSounds[ es->eventParm ] ) {
				trap_S_StartSound( NULL, cg.snap->ps.clientNum, CHAN_AUTO, cgs.gameSounds[ es->eventParm ] );
			} else {
				s = CG_ConfigString( CS_SOUNDS + es->eventParm );
				trap_S_StartSound( NULL, cg.snap->ps.clientNum, CHAN_AUTO, CG_CustomSound( es->number, s ) );
			}
		}

		break;
		// dhm - end

	case EV_PAIN:
		// local player sounds are triggered in CG_CheckLocalSounds,
		// so ignore events on the player
		DEBUGNAME( "EV_PAIN" );
		if ( cent->currentState.number != cg.snap->ps.clientNum ) {
			CG_PainEvent( cent, es->eventParm, qfalse );
		}
		break;

	case EV_CROUCH_PAIN:
		// local player sounds are triggered in CG_CheckLocalSounds,
		// so ignore events on the player
		DEBUGNAME( "EV_PAIN" );
		if ( cent->currentState.number != cg.snap->ps.clientNum ) {
			CG_PainEvent( cent, es->eventParm, qtrue );
		}
		break;

	case EV_DEATH1:
	case EV_DEATH2:
	case EV_DEATH3:
		DEBUGNAME( "EV_DEATHx" );
		trap_S_StartSound( NULL, es->number, CHAN_VOICE,
						   CG_CustomSound( es->number, va( "*death%i.wav", event - EV_DEATH1 + 1 ) ) );
		break;


	case EV_OBITUARY:
		DEBUGNAME( "EV_OBITUARY" );
		CG_Obituary( es );
		break;

		// JPW NERVE -- swiped from SP/Sherman
	case EV_STOPSTREAMINGSOUND:
		DEBUGNAME( "EV_STOPLOOPINGSOUND" );
//		trap_S_StopStreamingSound( es->number );
		trap_S_StartSoundEx( NULL, es->number, CHAN_WEAPON, 0, SND_CUTOFF_ALL );    // kill weapon sound (could be reloading)
		break;

	case EV_LOSE_HAT:
		DEBUGNAME( "EV_LOSE_HAT" );
		ByteToDir( es->eventParm, dir );
		CG_LoseHat( cent, dir );
		break;

	case EV_GIB_PLAYER:
		DEBUGNAME( "EV_GIB_PLAYER" );
		trap_S_StartSound( es->pos.trBase, -1, CHAN_AUTO, cgs.media.gibSound );
		ByteToDir( es->eventParm, dir );
		CG_GibPlayer( cent, cent->lerpOrigin, dir );
		break;

	case EV_STOPLOOPINGSOUND:
		DEBUGNAME( "EV_STOPLOOPINGSOUND" );
		es->loopSound = 0;
		break;

	case EV_DEBUG_LINE:
		DEBUGNAME( "EV_DEBUG_LINE" );
		CG_Beam( cent );
		break;

		// Rafael particles
	case EV_SMOKE:
		DEBUGNAME( "EV_SMOKE" );
		if ( cent->currentState.density == 3 ) {
			CG_ParticleSmoke( cgs.media.smokePuffShaderdirty, cent );
		} else if ( !( cent->currentState.density ) ) {
			CG_ParticleSmoke( cgs.media.smokePuffShader, cent );
		} else {
			CG_ParticleSmoke( cgs.media.smokePuffShader, cent );
		}
		break;

	case EV_FLAMETHROWER_EFFECT:
		CG_FireFlameChunks( cent, cent->currentState.origin, cent->currentState.apos.trBase, 0.6, 2 );
		break;

	case EV_DUST:
		CG_ParticleDust( cent, cent->currentState.origin, cent->currentState.angles );
		break;

	case EV_RUMBLE_EFX:
	{
		float pitch, yaw;
		pitch = cent->currentState.angles[0];
		yaw = cent->currentState.angles[1];
		CG_RumbleEfx( pitch, yaw );
	}
	break;

	case EV_CONCUSSIVE:
		CG_Concussive( cent );
		break;

	case EV_EMITTER:
	{
		localEntity_t   *le;
		le = CG_AllocLocalEntity();
		le->leType = LE_EMITTER;
		le->startTime = cg.time;
		le->endTime = le->startTime + 20000;
		le->pos.trType = TR_STATIONARY;
		VectorCopy( cent->currentState.origin, le->pos.trBase );
		VectorCopy( cent->currentState.origin2, le->angles.trBase );
		le->ownerNum = 0;
	}
	break;

	case EV_OILPARTICLES:
		CG_Particle_OilParticle( cgs.media.oilParticle, cent->currentState.origin, cent->currentState.origin2, cent->currentState.time, cent->currentState.density );
		break;
	case EV_OILSLICK:
		CG_Particle_OilSlick( cgs.media.oilSlick, cent );
		break;
	case EV_OILSLICKREMOVE:
		CG_OilSlickRemove( cent );
		break;

	case EV_MG42EFX:
		CG_MG42EFX( cent );
		break;

	case EV_SPARKS_ELECTRIC:
	case EV_SPARKS:
	{
		int numsparks;
		int i;
		int duration;
		float x,y;
		float speed;
		vec3_t source, dest;

		if ( !( cent->currentState.density ) ) {
			cent->currentState.density = 1;
		}
		numsparks = rand() % cent->currentState.density;
		duration = cent->currentState.frame;
		x = cent->currentState.angles2[0];
		y = cent->currentState.angles2[1];
		speed = cent->currentState.angles2[2];

		if ( !numsparks ) {
			numsparks = 1;
		}
		for ( i = 0; i < numsparks; i++ )
		{

			if ( event == EV_SPARKS_ELECTRIC ) {
				VectorCopy( cent->currentState.origin, source );

				VectorCopy( source, dest );
				dest[0] += ( ( rand() & 31 ) - 16 );
				dest[1] += ( ( rand() & 31 ) - 16 );
				dest[2] += ( ( rand() & 31 ) - 16 );

				CG_Tracer( source, dest, 1 );
			} else {
				CG_ParticleSparks( cent->currentState.origin, cent->currentState.angles, duration, x, y, speed );
			}

		}

	}
	break;

	case EV_GUNSPARKS:
	{
		int numsparks;
		int speed;
		//int	count;

		numsparks = cent->currentState.density;
		speed = cent->currentState.angles2[2];

		CG_AddBulletParticles( cent->currentState.origin, cent->currentState.angles, speed, 800, numsparks, 1.0f );

	}
	break;

		// Rafael snow pvs check
	case EV_SNOW_ON:
		CG_SnowLink( cent, qtrue );
		break;

	case EV_SNOW_OFF:
		CG_SnowLink( cent, qfalse );
		break;


	case EV_SNOWFLURRY:
		CG_ParticleSnowFlurry( cgs.media.snowShader, cent );
		break;

		// for func_exploding
	case EV_EXPLODE:
		DEBUGNAME( "EV_EXPLODE" );
		ByteToDir( es->eventParm, dir );
		CG_Explode( cent, position, dir, 0 );
		break;

	case EV_RUBBLE:
		DEBUGNAME( "EV_RUBBLE" );
		ByteToDir( es->eventParm, dir );
		CG_Rubble( cent, position, dir, 0 );
		break;

		// for target_effect
	case EV_EFFECT:
		DEBUGNAME( "EV_EFFECT" );
		ByteToDir( es->eventParm, dir );
		CG_Effect( cent, position, dir );
		break;

	case EV_MORTAREFX:  // mortar firing
		DEBUGNAME( "EV_MORTAREFX" );
		CG_MortarEFX( cent );
		break;

	case EV_SHARD:
		ByteToDir( es->eventParm, dir );
		CG_Shard( cent, position, dir );
		break;

	case EV_JUNK:
		ByteToDir( es->eventParm, dir );
		{
			int i;
			int rval;

			rval = rand() % 3 + 3;

			for ( i = 0; i < rval; i++ )
				CG_ShardJunk( cent, position, dir );
		}
		break;

	case EV_DISGUISE_SOUND:
		trap_S_StartSound( NULL, cent->currentState.number, CHAN_WEAPON, cgs.media.uniformPickup );
		break;
	case EV_BUILDDECAYED_SOUND:
		trap_S_StartSound( cent->lerpOrigin, cent->currentState.number, CHAN_AUTO, cgs.media.buildDecayedSound );
		break;

		// Gordon: debris test
	case EV_DEBRIS:
		CG_Debris( cent, position, cent->currentState.origin2 );
		break;
		// ===================

	case EV_SHAKE:
	{
		vec3_t v;
		float len;

		DEBUGNAME( "EV_SHAKE" );

		VectorSubtract( cg.snap->ps.origin, cent->lerpOrigin, v );
		len = VectorLength( v );

		if ( len > cent->currentState.onFireStart ) {
			break;
		}

		len = 1.0f - ( len / (float)cent->currentState.onFireStart );
		len = min( 1.f, len );

		CG_StartShakeCamera( len );
	}

	break;

	case EV_ALERT_SPEAKER:
		DEBUGNAME( "EV_ALERT_SPEAKER" );
		switch ( cent->currentState.otherEntityNum2 )
		{
		case 1:     CG_UnsetActiveOnScriptSpeaker( cent->currentState.otherEntityNum ); break;
		case 2:     CG_SetActiveOnScriptSpeaker( cent->currentState.otherEntityNum );   break;
		case 0:
		default:    CG_ToggleActiveOnScriptSpeaker( cent->currentState.otherEntityNum );    break;
		}
		break;

	case EV_POPUPMESSAGE:
	{
		const char* str = CG_GetPMItemText( cent );
		qhandle_t shader = CG_GetPMItemIcon( cent );
		if ( str ) {
			CG_AddPMItem( cent->currentState.effect1Time, str, shader );
		}
		CG_PlayPMItemSound( cent );
	}
	break;

	case EV_AIRSTRIKEMESSAGE:
	{
		const char* wav = NULL;

		switch ( cent->currentState.density ) {
		case 0:         // too many called
			if ( cgs.clientinfo[ cg.snap->ps.clientNum ].team == TEAM_AXIS ) {
				wav = "axis_hq_airstrike_denied";
			} else {
				wav = "allies_hq_airstrike_denied";
			}
			break;
		case 1:         // aborting can't see target
			if ( cgs.clientinfo[ cg.snap->ps.clientNum ].team == TEAM_AXIS ) {
				wav = "axis_hq_airstrike_abort";
			} else {
				wav = "allies_hq_airstrike_abort";
			}
			break;
		case 2:         // firing for effect
			if ( cgs.clientinfo[ cg.snap->ps.clientNum ].team == TEAM_AXIS ) {
				wav = "axis_hq_airstrike";
			} else {
				wav = "allies_hq_airstrike";
			}
			break;
		}

		if ( wav ) {
			CG_SoundPlaySoundScript( wav, NULL, -1, ( es->effect1Time ? qfalse : qtrue ) );
		}
	}
	break;

	case EV_ARTYMESSAGE:
	{
		const char* wav = NULL;

		switch ( cent->currentState.density ) {
		case 0:         // too many called
			if ( cgs.clientinfo[ cg.snap->ps.clientNum ].team == TEAM_AXIS ) {
				wav = "axis_hq_ffe_denied";
			} else {
				wav = "allies_hq_ffe_denied";
			}
			break;
		case 1:         // aborting can't see target
			if ( cgs.clientinfo[ cg.snap->ps.clientNum ].team == TEAM_AXIS ) {
				wav = "axis_hq_ffe_abort";
			} else {
				wav = "allies_hq_ffe_abort";
			}
			break;
		case 2:         // firing for effect
			if ( cgs.clientinfo[ cg.snap->ps.clientNum ].team == TEAM_AXIS ) {
				wav = "axis_hq_ffe";
			} else {
				wav = "allies_hq_ffe";
			}
			break;
		}

		if ( wav ) {
			CG_SoundPlaySoundScript( wav, NULL, -1, ( es->effect1Time ? qfalse : qtrue ) );
		}
	}
	break;

	case EV_MEDIC_CALL:
		switch ( cgs.clientinfo[ cent->currentState.number ].team ) {
		case TEAM_AXIS:
			trap_S_StartSound( NULL, cent->currentState.number, CHAN_AUTO, cgs.media.sndMedicCall[0] );
			break;
		case TEAM_ALLIES:
			trap_S_StartSound( NULL, cent->currentState.number, CHAN_AUTO, cgs.media.sndMedicCall[1] );
			break;
		default:     // shouldn't happen
			break;
		}

		break;

	default:
		DEBUGNAME( "UNKNOWN" );
		CG_Error( "Unknown event: %i", event );
		break;
	}


	{
		int rval;

		rval = rand() & 3;

		if ( splashfootstepcnt != rval ) {
			splashfootstepcnt = rval;
		} else {
			splashfootstepcnt++;
		}

		if ( splashfootstepcnt > 3 ) {
			splashfootstepcnt = 0;
		}


		if ( footstepcnt != rval ) {
			footstepcnt = rval;
		} else {
			footstepcnt++;
		}

		if ( footstepcnt > 3 ) {
			footstepcnt = 0;
		}
	}
}


/*
==============
CG_CheckEvents

==============
*/
void CG_CheckEvents( centity_t *cent ) {
	int i, event;

	// calculate the position at exactly the frame time
	BG_EvaluateTrajectory( &cent->currentState.pos, cg.snap->serverTime, cent->lerpOrigin, qfalse, cent->currentState.effect2Time );
	CG_SetEntitySoundPosition( cent );

	// check for event-only entities
	if ( cent->currentState.eType > ET_EVENTS ) {
		if ( cent->previousEvent ) {
			//goto skipEvent;
			return; // already fired
		}
		// if this is a player event set the entity number of the client entity number
//(SA) note: EF_PLAYER_EVENT never set
//		if ( cent->currentState.eFlags & EF_PLAYER_EVENT ) {
//			cent->currentState.number = cent->currentState.otherEntityNum;
//		}

		cent->previousEvent = 1;

		cent->currentState.event = cent->currentState.eType - ET_EVENTS;
	} else {

		// DHM - Nerve :: Entities that make it here are Not TempEntities.
		//		As far as we could tell, for all non-TempEntities, the
		//		circular 'events' list contains the valid events.  So we
		//		skip processing the single 'event' field and go straight
		//		to the circular list.

		goto skipEvent;
		/*
		// check for events riding with another entity
		if ( cent->currentState.event == cent->previousEvent ) {
			goto skipEvent;
			//return;
		}
		cent->previousEvent = cent->currentState.event;
		if ( ( cent->currentState.event & ~EV_EVENT_BITS ) == 0 ) {
			goto skipEvent;
			//return;
		}
		*/
		// dhm - end
	}

	CG_EntityEvent( cent, cent->lerpOrigin );
	// DHM - Nerve :: Temp ents return after processing
	return;

skipEvent:

	// check the sequencial list
	// if we've added more events than can fit into the list, make sure we only add them once
	if ( cent->currentState.eventSequence < cent->previousEventSequence ) {
		cent->previousEventSequence -= ( 1 << 8 );    // eventSequence is sent as an 8-bit through network stream
	}
	if ( cent->currentState.eventSequence - cent->previousEventSequence > MAX_EVENTS ) {
		cent->previousEventSequence = cent->currentState.eventSequence - MAX_EVENTS;
	}
	for ( i = cent->previousEventSequence ; i != cent->currentState.eventSequence; i++ ) {
		event = cent->currentState.events[ i & ( MAX_EVENTS - 1 ) ];

		cent->currentState.event = event;
		cent->currentState.eventParm = cent->currentState.eventParms[ i & ( MAX_EVENTS - 1 ) ];
		CG_EntityEvent( cent, cent->lerpOrigin );
	}
	cent->previousEventSequence = cent->currentState.eventSequence;

	// set the event back so we don't think it's changed next frame (unless it really has)
	cent->currentState.event = cent->previousEvent;
}
