/*
===========================================================================
Copyright (C) 1999 - 2005, Id Software, Inc.
Copyright (C) 2000 - 2013, Raven Software, Inc.
Copyright (C) 2001 - 2013, Activision, Inc.
Copyright (C) 2013 - 2015, OpenJK contributors

This file is part of the OpenJK source code.

OpenJK is free software; you can redistribute it and/or modify it
under the terms of the GNU General Public License version 2 as
published by the Free Software Foundation.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, see <http://www.gnu.org/licenses/>.
===========================================================================
*/

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

#include "cg_headers.h"

#include "cg_media.h"
#include "FxScheduler.h"

#include "../game/anims.h"

extern qboolean CG_TryPlayCustomSound( vec3_t origin, int entityNum, soundChannel_t channel, const char *soundName, int customSoundSet );
extern void FX_KothosBeam( vec3_t start, vec3_t end );

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

qboolean CG_IsFemale( const char *infostring ) {
	const char		*sex;

	sex = Info_ValueForKey( infostring, "s" );
	if (sex[0] == 'f' || sex[0] == 'F')
		return qtrue;
	return qfalse;
}

const char	*CG_PlaceString( int rank ) {
	static char	str[64];
	char	*s, *t;

	if ( rank & RANK_TIED_FLAG ) {
		rank &= ~RANK_TIED_FLAG;
		t = "Tied for ";
	} else {
		t = "";
	}

	if ( rank == 1 ) {
		s = "\03341st\0337";		// draw in blue
	} else if ( rank == 2 ) {
		s = "\03312nd\0337";		// draw in red
	} else if ( rank == 3 ) {
		s = "\03333rd\0337";		// draw in yellow
	} else if ( rank == 11 ) {
		s = "11th";
	} else if ( rank == 12 ) {
		s = "12th";
	} else if ( rank == 13 ) {
		s = "13th";
	} else if ( rank % 10 == 1 ) {
		s = va("%ist", rank);
	} else if ( rank % 10 == 2 ) {
		s = va("%ind", rank);
	} else if ( rank % 10 == 3 ) {
		s = va("%ird", rank);
	} else {
		s = va("%ith", rank);
	}

	Com_sprintf( str, sizeof( str ), "%s%s", t, s );
	return str;
}


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

A new item was picked up this frame
================
*/
void CG_ItemPickup( int itemNum, qboolean bHadItem ) {
	cg.itemPickup = itemNum;
	cg.itemPickupTime = cg.time;
	cg.itemPickupBlendTime = cg.time;

	if (bg_itemlist[itemNum].classname && bg_itemlist[itemNum].classname[0])
	{

		char text[1024], data[1024];
		if (cgi_SP_GetStringTextString("SP_INGAME_PICKUPLINE",text, sizeof(text)) )
		{
			if ( cgi_SP_GetStringTextString( va("SP_INGAME_%s",bg_itemlist[itemNum].classname ), data, sizeof( data )))
			{
//				Com_Printf("%s %s\n", text, data );
				cgi_Cvar_Set( "cg_WeaponPickupText", va("%s %s\n", text, data));
				cg.weaponPickupTextTime	= cg.time + 5000;
			}
		}
	}

	// see if it should be the grabbed weapon
	if ( bg_itemlist[itemNum].giType == IT_WEAPON )
	{
		const int nCurWpn = cg.predicted_player_state.weapon;
		const int nNewWpn = bg_itemlist[itemNum].giTag;

		if ( nCurWpn == WP_SABER || bHadItem)
		{//never switch away from the saber!
			return;
		}

		// kef -- check cg_autoswitch...
		//
		// 0 == no switching
		// 1 == automatically switch to best SAFE weapon
		// 2 == automatically switch to best weapon, safe or otherwise
		//
		// NOTE: automatically switching to any weapon you pick up is stupid and annoying and we won't do it.
		//

		if ( nNewWpn == WP_SABER )
		{//always switch to saber
			SetWeaponSelectTime();
			cg.weaponSelect = nNewWpn;
		}
		else if (0 == cg_autoswitch.integer)
		{
			// don't switch
		}
		else if (1 == cg_autoswitch.integer)
		{
			// safe switching
			if (	(nNewWpn > nCurWpn) &&
					!(nNewWpn == WP_DET_PACK) &&
					!(nNewWpn == WP_TRIP_MINE) &&
					!(nNewWpn == WP_THERMAL) &&
					!(nNewWpn == WP_ROCKET_LAUNCHER) &&
					!(nNewWpn == WP_CONCUSSION) )
			{
				// switch to new wpn
//				cg.weaponSelectTime = cg.time;
				SetWeaponSelectTime();
				cg.weaponSelect = nNewWpn;
			}
		}
		else if (2 == cg_autoswitch.integer)
		{
			// best
			if (nNewWpn > nCurWpn)
			{
				// switch to new wpn
//				cg.weaponSelectTime = cg.time;
				SetWeaponSelectTime();
				cg.weaponSelect = nNewWpn;
			}
		}
	}
}


/*
===============
UseItem
===============
*/
extern void CG_ToggleBinoculars( void );
extern void CG_ToggleLAGoggles( void );

void UseItem(int itemNum)
{
	centity_t		*cent;

	cent = &cg_entities[cg.snap->ps.clientNum];

	switch ( itemNum )
	{
	case INV_ELECTROBINOCULARS:
		CG_ToggleBinoculars();
		break;
	case INV_LIGHTAMP_GOGGLES:
		CG_ToggleLAGoggles();
		break;
	case INV_GOODIE_KEY:
		if (cent->gent->client->ps.inventory[INV_GOODIE_KEY])
		{
			cent->gent->client->ps.inventory[INV_GOODIE_KEY]--;
		}
		break;
	case INV_SECURITY_KEY:
		if (cent->gent->client->ps.inventory[INV_SECURITY_KEY])
		{
			cent->gent->client->ps.inventory[INV_SECURITY_KEY]--;
		}
		break;
	}
}

/*
===============
CG_UseForce
===============
*/
static void CG_UseForce( centity_t *cent )
{
	//FIXME: sound or graphic change or something?
	//actual force power action is on game/pm side
}

/*
===============
CG_UseItem
===============
*/
static void CG_UseItem( centity_t *cent )
{
	int			itemNum;
	entityState_t *es;

	es = &cent->currentState;

	itemNum = cg.inventorySelect;
	if ( itemNum < 0 || itemNum > INV_MAX )
	{
		itemNum = 0;
	}

	// print a message if the local player
	if ( es->number == cg.snap->ps.clientNum )
	{
		if ( !itemNum )
		{
//			CG_CenterPrint( "No item to use", SCREEN_HEIGHT * 0.30, BIGCHAR_WIDTH );
		}
		else
		{
//			item = BG_FindItemForHoldable( itemNum );
//			CG_CenterPrint( va("Use %s", item->pickup_name), SCREEN_HEIGHT * 0.30, BIGCHAR_WIDTH );
		}
	}

	UseItem(itemNum);

}


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

An entity has an event value
==============
*/
#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			axis[3];
	const char		*s, *s2;
	int				clientNum;
	//clientInfo_t	*ci;

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

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

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

	if ( !cent->gent )//|| !cent->gent->client )
	{
		return;
	}

	//ci = &cent->gent->client->clientInfo;
	clientNum = cent->gent->s.number;

	switch ( event ) {
	//
	// movement generated events
	//
	case EV_FOOTSPLASH:
		DEBUGNAME("EV_FOOTSPLASH");
		if (cg_footsteps.integer) {
			cgi_S_StartSound (NULL, es->number, CHAN_BODY, cgs.media.footsteps[ FOOTSTEP_SPLASH ][rand()&3] );
		}
		break;
	case EV_FOOTWADE:
		DEBUGNAME("EV_FOOTWADE");
		if (cg_footsteps.integer) {
			cgi_S_StartSound (NULL, es->number, CHAN_BODY, cgs.media.footsteps[ FOOTSTEP_WADE ][rand()&3] );
		}
		break;
	case EV_SWIM:
		DEBUGNAME("EV_SWIM");
		if (cg_footsteps.integer) {
			cgi_S_StartSound (NULL, es->number, CHAN_BODY, cgs.media.footsteps[ FOOTSTEP_SWIM ][rand()&3] );
		}
		break;


	case EV_FALL_SHORT:
		DEBUGNAME("EV_FALL_SHORT");
		cgi_S_StartSound( NULL, es->number, CHAN_AUTO, cgs.media.landSound  );
		if ( clientNum == cg.predicted_player_state.clientNum ) {
			// smooth landing z changes
			cg.landChange = -8;
			cg.landTime = cg.time;
		}
		//FIXME: maybe kick up some dust?
		break;
	case EV_FALL_MEDIUM:
		DEBUGNAME("EV_FALL_MEDIUM");
		// use normal pain sound -
		if ( g_entities[es->number].health <= 0 )
		{//dead
			cgi_S_StartSound( NULL, es->number, CHAN_AUTO, cgs.media.landSound  );
		}
		else if ( g_entities[es->number].s.weapon == WP_SABER
			|| (g_entities[es->number].client && (g_entities[es->number].client->ps.forcePowersKnown&(1<<FP_LEVITATION))) )
		{//jedi or someone who has force jump (so probably took no damage)
			CG_TryPlayCustomSound( NULL, es->number, CHAN_BODY, "*land1.wav", CS_BASIC );
		}
		else
		{//still alive
			CG_TryPlayCustomSound( NULL, es->number, CHAN_BODY, "*pain100.wav", CS_BASIC );
		}
		if ( clientNum == cg.predicted_player_state.clientNum ) {
			// smooth landing z changes
			cg.landChange = -16;
			cg.landTime = cg.time;
		}
		//FIXME: maybe kick up some dust?
		break;
	case EV_FALL_FAR:
		DEBUGNAME("EV_FALL_FAR");
		CG_TryPlayCustomSound( NULL, es->number, CHAN_BODY, "*land1.wav", CS_BASIC );
		cgi_S_StartSound( NULL, es->number, CHAN_AUTO, cgs.media.landSound  );
		cent->pe.painTime = cg.time;	// don't play a pain sound right after this
		if ( clientNum == cg.predicted_player_state.clientNum ) {
			// smooth landing z changes
			cg.landChange = -24;
			cg.landTime = cg.time;
		}
		//FIXME: maybe kick up some dust?
		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.predicted_player_state.clientNum ) {
			break;
		}
		// if we are interpolating, we don't need to smooth steps
		if ( cg_timescale.value >= 1.0f )
		{
			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");
		CG_TryPlayCustomSound(NULL, es->number, CHAN_AUTO, "*jump1.wav", CS_BASIC );//CHAN_VOICE
		break;

	case EV_ROLL:
		DEBUGNAME("EV_ROLL");
		CG_TryPlayCustomSound(NULL, es->number, CHAN_AUTO, "*jump1.wav", CS_BASIC );//CHAN_VOICE
		cgi_S_StartSound( NULL, es->number, CHAN_BODY, cgs.media.rollSound  );//CHAN_AUTO
		//FIXME: need some sort of body impact on ground sound and maybe kick up some dust?
		break;

	case EV_LAVA_TOUCH:
		DEBUGNAME("EV_LAVA_TOUCH");
		cgi_S_StartSound (NULL, es->number, CHAN_AUTO, cgs.media.lavaInSound );
		break;

	case EV_LAVA_LEAVE:
		DEBUGNAME("EV_LAVA_LEAVE");
		cgi_S_StartSound (NULL, es->number, CHAN_AUTO, cgs.media.lavaOutSound );
		break;

	case EV_LAVA_UNDER:
		DEBUGNAME("EV_LAVA_UNDER");
		cgi_S_StartSound (NULL, es->number, CHAN_AUTO, cgs.media.lavaUnSound );
		break;

	case EV_WATER_TOUCH:
		DEBUGNAME("EV_WATER_TOUCH");
		cgi_S_StartSound (NULL, es->number, CHAN_AUTO, cgs.media.watrInSound );
		break;

	case EV_WATER_LEAVE:
		DEBUGNAME("EV_WATER_LEAVE");
		cgi_S_StartSound (NULL, es->number, CHAN_AUTO, cgs.media.watrOutSound );
		break;

	case EV_WATER_UNDER:
		DEBUGNAME("EV_WATER_UNDER");
		cgi_S_StartSound (NULL, es->number, CHAN_AUTO, cgs.media.watrUnSound );
		break;

	case EV_WATER_CLEAR:
		DEBUGNAME("EV_WATER_CLEAR");
		CG_TryPlayCustomSound(NULL, es->number, CHAN_AUTO, "*gasp.wav", CS_BASIC );
		break;

	case EV_WATER_GURP1:
	case EV_WATER_GURP2:
		DEBUGNAME("EV_WATER_GURPx");
		CG_TryPlayCustomSound(NULL, es->number, CHAN_AUTO, va("*gurp%d.wav",event-EV_WATER_GURP1+1), CS_BASIC );
		break;

	case EV_WATER_DROWN:
		DEBUGNAME("EV_WATER_DROWN");
		CG_TryPlayCustomSound(NULL, es->number, CHAN_AUTO, "*drown.wav", CS_BASIC );
		break;

	case EV_ITEM_PICKUP:
		DEBUGNAME("EV_ITEM_PICKUP");
		{
			gitem_t	*item;
			int		index;
			qboolean bHadItem = qfalse;

			index = es->eventParm;		// player predicted

			if ( (char)index < 0 )
			{
				index = -(char)index;
				bHadItem = qtrue;
			}

			if ( index >= bg_numItems ) {
				break;
			}
			item = &bg_itemlist[ index ];
			cgi_S_StartSound (NULL, es->number, CHAN_AUTO,	cgi_S_RegisterSound( item->pickup_sound ) );

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

	//
	// weapon events
	//
	case EV_NOAMMO:
		DEBUGNAME("EV_NOAMMO");
		//cgi_S_StartSound (NULL, es->number, CHAN_AUTO, cgs.media.noAmmoSound );
		if ( es->number == cg.snap->ps.clientNum ) {
			CG_OutOfAmmoChange();
		}
		break;
	case EV_CHANGE_WEAPON:
		DEBUGNAME("EV_CHANGE_WEAPON");
		if ( es->weapon == WP_SABER )
		{
			/*
			if ( !cent->gent || !cent->gent->client || (cent->currentState.saberInFlight == qfalse && cent->currentState.saberActive == qtrue) )
			{
				cgi_S_StartSound (NULL, es->number, CHAN_AUTO, cgi_S_RegisterSound( "sound/weapons/saber/saberoffquick.wav" ) );
			}
			*/
			if ( cent->gent && cent->gent->client )
			{
				//if ( cent->gent->client->ps.saberInFlight )
				{//if it's not in flight or lying around, turn it off!
					cent->currentState.saberActive = qfalse;
				}
			}
		}

		// FIXME: if it happens that you don't want the saber to play the switch sounds, feel free to modify this bit.
		if ( weaponData[cg.weaponSelect].selectSnd[0] )
		{
			// custom select sound
			cgi_S_StartSound (NULL, es->number, CHAN_AUTO, cgi_S_RegisterSound( weaponData[cg.weaponSelect].selectSnd ));
		}
		else
		{
			// generic sound
			cgi_S_StartSound (NULL, es->number, CHAN_AUTO, cgs.media.selectSound );
		}
		break;

	case EV_FIRE_WEAPON:
		DEBUGNAME("EV_FIRE_WEAPON");
		CG_FireWeapon( cent, qfalse );
		break;

	case EV_ALT_FIRE:
		DEBUGNAME("EV_ALT_FIRE");
		CG_FireWeapon( cent, qtrue );
		break;

	case EV_DISRUPTOR_MAIN_SHOT:
		DEBUGNAME("EV_DISRUPTOR_MAIN_SHOT");
		FX_DisruptorMainShot( cent->currentState.origin2, cent->lerpOrigin );
		break;

	case EV_DISRUPTOR_SNIPER_SHOT:
		DEBUGNAME("EV_DISRUPTOR_SNIPER_SHOT");
		FX_DisruptorAltShot( cent->currentState.origin2, cent->lerpOrigin, cent->gent->alt_fire );
		break;

	case EV_DISRUPTOR_SNIPER_MISS:
		DEBUGNAME("EV_DISRUPTOR_SNIPER_MISS");
		FX_DisruptorAltMiss( cent->lerpOrigin, cent->gent->pos1 );
		break;

	case EV_DEMP2_ALT_IMPACT:
		FX_DEMP2_AltDetonate( cent->lerpOrigin, es->eventParm );
		break;

	case EV_CONC_ALT_SHOT:
		DEBUGNAME("EV_CONC_ALT_SHOT");
		FX_ConcAltShot( cent->currentState.origin2, cent->lerpOrigin );
		break;

	case EV_CONC_ALT_MISS:
		DEBUGNAME("EV_CONC_ALT_MISS");
		FX_ConcAltMiss( cent->lerpOrigin, cent->gent->pos1 );
		break;

//	case EV_POWERUP_SEEKER_FIRE:
//		DEBUGNAME("EV_POWERUP_SEEKER_FIRE");
//		CG_FireSeeker( cent );
//		break;

	case EV_POWERUP_BATTLESUIT:
		DEBUGNAME("EV_POWERUP_BATTLESUIT");
		if ( es->number == cg.snap->ps.clientNum ) {
			cg.powerupActive = PW_BATTLESUIT;
			cg.powerupTime = cg.time;
		}
		//cgi_S_StartSound (NULL, es->number, CHAN_ITEM, cgs.media.invulnoProtectSound );
		break;

	case EV_KOTHOS_BEAM:
		DEBUGNAME("EV_KOTHOS_BEAM");
		if ( Q_irand( 0, 1 ) )
		{
			FX_KothosBeam( cg_entities[cent->currentState.otherEntityNum].gent->client->renderInfo.handRPoint, cg_entities[cent->currentState.otherEntityNum2].lerpOrigin );
		}
		else
		{
			FX_KothosBeam( cg_entities[cent->currentState.otherEntityNum].gent->client->renderInfo.handLPoint, cg_entities[cent->currentState.otherEntityNum2].lerpOrigin );
		}
		break;
	//=================================================================

	//
	// other events
	//
	case EV_REPLICATOR:
		DEBUGNAME("EV_REPLICATOR");
//		FX_Replicator( cent, position );
		break;

	case EV_BATTERIES_CHARGED:
		cg.batteryChargeTime = cg.time + 3000;
		cgi_S_StartSound( vec3_origin, es->number, CHAN_AUTO, cgs.media.batteryChargeSound );
		break;

	case EV_DISINTEGRATION:
		{
			DEBUGNAME("EV_DISINTEGRATION");
			qboolean makeNotSolid = qfalse;
			int disintPW = es->eventParm;
			int disintEffect = 0;
			int disintLength = 0;
			qhandle_t disintSound1 = NULL_HANDLE;
			qhandle_t disintSound2 = NULL_HANDLE;
			//qhandle_t disintSound3 = NULL_HANDLE;

			switch( disintPW )
			{
			case PW_DISRUPTION:// sniper rifle
				disintEffect = EF_DISINTEGRATION;//ef_
				disintSound1 = cgs.media.disintegrateSound;//with scream
				disintSound2 = cgs.media.disintegrate2Sound;//no scream
				//disintSound3 = cgs.media.disintegrate3Sound;//with inhuman scream
				disintLength = 2000;
				makeNotSolid = qtrue;
				break;
/*			case PW_SHOCKED:// arc welder
				disintEffect = EF_DISINT_1;//ef_
				disintSound1 = NULL;//with scream
				disintSound2 = NULL;//no scream
				disintSound3 = NULL;//with inhuman scream
				disintLength = 4000;
				break;
*/
			default:
				return;
				break;
			}

			if ( cent->gent->owner )
			{
				cent->gent->owner->fx_time = cg.time;
				if ( cent->gent->owner->client )
				{
					if ( disintSound1 && disintSound2 )
					{//play an extra sound
						// listed all the non-humanoids, because there's a lot more humanoids
						class_t	npc_class = cent->gent->owner->client->NPC_class;
						if( npc_class != CLASS_ATST && npc_class != CLASS_GONK &&
							npc_class != CLASS_INTERROGATOR && npc_class != CLASS_MARK1 &&
							npc_class != CLASS_MARK2 && npc_class != CLASS_MOUSE &&
							npc_class != CLASS_PROBE && npc_class != CLASS_PROTOCOL &&
							npc_class != CLASS_R2D2 && npc_class != CLASS_R5D2 &&
							npc_class != CLASS_SEEKER && npc_class != CLASS_SENTRY)
						{//Only the humanoids scream
							cgi_S_StartSound ( NULL, cent->gent->owner->s.number, CHAN_VOICE, disintSound1 );
						}
						// no more forge or 8472
					//	else if ( cent->gent->owner->client->playerTeam == TEAM_FORGE ||
					//			cent->gent->owner->client->playerTeam == TEAM_8472 )
					//	{
					//		cgi_S_StartSound ( NULL, cent->gent->s.number, CHAN_VOICE, disintSound3 );
					//	}
						else
						{
							cgi_S_StartSound ( NULL, cent->gent->s.number, CHAN_AUTO, disintSound2 );
						}
					}
					cent->gent->owner->s.powerups |= ( 1 << disintPW );
					cent->gent->owner->client->ps.powerups[disintPW] = cg.time + disintLength;

					// Things that are being disintegrated should probably not be solid...
					if ( makeNotSolid && cent->gent->owner->client->playerTeam != TEAM_NEUTRAL )
					{
						cent->gent->contents = CONTENTS_NONE;
					}
				}
				else
				{
					cent->gent->owner->s.eFlags = disintEffect;//FIXME: |= ?
					cent->gent->owner->delay = cg.time + disintLength;
				}
			}
		}
		break;

	// This does not necessarily have to be from a grenade...
	case EV_GRENADE_BOUNCE:
		DEBUGNAME("EV_GRENADE_BOUNCE");
		CG_BounceEffect( cent, es->weapon, position, cent->gent->pos1 );
		break;

	//
	// missile impacts
	//

	case EV_MISSILE_STICK:
		DEBUGNAME("EV_MISSILE_STICK");
		CG_MissileStick( cent, es->weapon, position );
		break;

	case EV_MISSILE_HIT:
		DEBUGNAME("EV_MISSILE_HIT");
		if ( CG_VehicleWeaponImpact( cent ) )
		{
		}
		else
		{
			CG_MissileHitPlayer( cent, es->weapon, position, cent->gent->pos1, cent->gent->alt_fire );
		}
		break;

	case EV_MISSILE_MISS:
		DEBUGNAME("EV_MISSILE_MISS");
		if ( CG_VehicleWeaponImpact( cent ) )
		{
		}
		else
		{
			CG_MissileHitWall( cent, es->weapon, position, cent->gent->pos1, cent->gent->alt_fire );
		}
		break;

	case EV_BMODEL_SOUND:
		DEBUGNAME("EV_BMODEL_SOUND");
		cgi_S_StartSound( NULL, es->number, CHAN_AUTO, es->eventParm );
		break;

	case EV_GENERAL_SOUND:
		DEBUGNAME("EV_GENERAL_SOUND");
		if ( cgs.sound_precache[ es->eventParm ] )
		{
			cgi_S_StartSound (NULL, es->number, CHAN_AUTO, cgs.sound_precache[ es->eventParm ] );
		}
		else
		{
			s = CG_ConfigString( CS_SOUNDS + es->eventParm );
			CG_TryPlayCustomSound(NULL, es->number, CHAN_AUTO, s, CS_BASIC );
		}
		break;

	case EV_GLOBAL_SOUND:	// play from the player's head so it never diminishes
		DEBUGNAME("EV_GLOBAL_SOUND");
		if ( cgs.sound_precache[ es->eventParm ] ) {
			cgi_S_StartSound (NULL, cg.snap->ps.clientNum, CHAN_AUTO, cgs.sound_precache[ es->eventParm ] );
		} else {
			s = CG_ConfigString( CS_SOUNDS + es->eventParm );
			CG_TryPlayCustomSound( NULL, cg.snap->ps.clientNum, CHAN_AUTO, s, CS_BASIC );
		}
		break;

	case EV_DRUGGED:
		DEBUGNAME("EV_DRUGGED");
		if ( cent->gent && cent->gent->owner && cent->gent->owner->s.number == 0 )
		{
			// Only allow setting up the wonky vision on the player..do it for 10 seconds...must be synchronized with calcs done in cg_view.  Just search for cg.wonkyTime to find 'em.
			cg.wonkyTime = cg.time + 10000;
		}
		break;

	case EV_PAIN:
		{
		char	*snd;
		const int health = es->eventParm;

		if ( cent->gent && cent->gent->NPC && (cent->gent->NPC->aiFlags & NPCAI_DIE_ON_IMPACT) )
		{
			return;
		}
		//FIXME: don't do this if we're falling to our deaths...
		DEBUGNAME("EV_PAIN");
		// don't do more than two pain sounds a second
		if ( cg.time - cent->pe.painTime < 500 ) {
			return;
		}

		if ( health < 25 ) {
			snd = "*pain100.wav";
		} else if ( health < 50 ) {
			snd = "*pain75.wav";
		} else if ( health < 75 ) {
			snd = "*pain50.wav";
		} else {
			snd = "*pain25.wav";
		}
		CG_TryPlayCustomSound( NULL, es->number, CHAN_VOICE, snd, CS_BASIC );

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

	case EV_DEATH1:
	case EV_DEATH2:
	case EV_DEATH3:
		DEBUGNAME("EV_DEATHx");
		/*
		if ( cent->gent && cent->gent->NPC && (cent->gent->NPC->aiFlags & NPCAI_DIE_ON_IMPACT) )
		{
			return;
		}
		*/
		CG_TryPlayCustomSound( NULL, es->number, CHAN_VOICE, va("*death%i.wav", event - EV_DEATH1 + 1), CS_BASIC );
		break;

	// Called by the FxRunner entity...usually for Environmental FX Events
	case EV_PLAY_EFFECT:
		DEBUGNAME("EV_PLAY_EFFECT");
		{
			const bool portalEnt = !!es->isPortalEnt; //the fxrunner spawning this effect is within a skyportal, so only render this effect within that portal.

			s = CG_ConfigString( CS_EFFECTS + es->eventParm );
	// Ghoul2 Insert Start
			if (es->boltInfo != 0)
			{
				const bool isRelative = !!es->weapon;
				theFxScheduler.PlayEffect( s, cent->lerpOrigin, axis, es->boltInfo, -1, portalEnt, es->loopSound, isRelative );	//loopSound 0 = not looping, 1 for infinite, else duration
			}
			else
			{
				VectorCopy( cent->gent->pos3, axis[0] );
				VectorCopy( cent->gent->pos4, axis[1] );
				CrossProduct( axis[0], axis[1], axis[2] );

				// the entNum the effect may be attached to
				if ( es->otherEntityNum )
				{
					theFxScheduler.PlayEffect( s, cent->lerpOrigin, axis, -1, es->otherEntityNum, portalEnt );
				}
				else
				{
					theFxScheduler.PlayEffect( s, cent->lerpOrigin, axis, -1, -1, portalEnt );
				}
			}
		}
// Ghoul2 Insert End
		break;

		// play an effect bolted onto a muzzle
	case EV_PLAY_MUZZLE_EFFECT:
		DEBUGNAME("EV_PLAY_MUZZLE_EFFECT");
		s = CG_ConfigString( CS_EFFECTS + es->eventParm );

		theFxScheduler.PlayEffect( s, es->otherEntityNum );
		break;

	case EV_STOP_EFFECT:
		DEBUGNAME("EV_STOP_EFFECT");
		{
			bool portalEnt = false;

			if ( es->isPortalEnt )
			{ //the fxrunner spawning this effect is within a skyportal, so only render this effect within that portal.
				portalEnt = true;
			}

			s = CG_ConfigString( CS_EFFECTS + es->eventParm );
			if ( es->boltInfo != 0 )
			{
				theFxScheduler.StopEffect( s, es->boltInfo, portalEnt );
			}
		}
		break;

	case EV_TARGET_BEAM_DRAW:
		DEBUGNAME("EV_TARGET_BEAM_DRAW");
		if ( cent->gent )
		{
			s = CG_ConfigString( CS_EFFECTS + es->eventParm );

			if ( s && s[0] )
			{
				if ( cent->gent->delay )
				{
					s2 = CG_ConfigString( CS_EFFECTS + cent->gent->delay );
				}
				else
				{
					s2 = NULL;
				}

				CG_DrawTargetBeam( cent->lerpOrigin, cent->gent->s.origin2, cent->gent->pos1, s, s2 );
			}
/*			else
			{
				int gack = 0; // this is bad if it get's here
			}
*/
		}
		break;

	case EV_ANGER1:	//Say when acquire an enemy when didn't have one before
	case EV_ANGER2:
	case EV_ANGER3:
		DEBUGNAME("EV_ANGERx");
		CG_TryPlayCustomSound( NULL, es->number, CHAN_VOICE, va("*anger%i.wav", event - EV_ANGER1 + 1), CS_COMBAT );
		break;

	case EV_VICTORY1:	//Say when killed an enemy
	case EV_VICTORY2:
	case EV_VICTORY3:
		DEBUGNAME("EV_VICTORYx");
		CG_TryPlayCustomSound( NULL, es->number, CHAN_VOICE, va("*victory%i.wav", event - EV_VICTORY1 + 1), CS_COMBAT );
		break;

	case EV_CONFUSE1:	//Say when confused
	case EV_CONFUSE2:
	case EV_CONFUSE3:
		DEBUGNAME("EV_CONFUSEDx");
		CG_TryPlayCustomSound( NULL, es->number, CHAN_VOICE, va("*confuse%i.wav", event - EV_CONFUSE1 + 1), CS_COMBAT );
		break;

	case EV_PUSHED1:	//Say when pushed
	case EV_PUSHED2:
	case EV_PUSHED3:
		DEBUGNAME("EV_PUSHEDx");
		CG_TryPlayCustomSound( NULL, es->number, CHAN_VOICE, va("*pushed%i.wav", event - EV_PUSHED1 + 1), CS_COMBAT );
		break;

	case EV_CHOKE1:	//Say when choking
	case EV_CHOKE2:
	case EV_CHOKE3:
		DEBUGNAME("EV_CHOKEx");
		CG_TryPlayCustomSound( NULL, es->number, CHAN_VOICE, va("*choke%i.wav", event - EV_CHOKE1 + 1), CS_COMBAT );
		break;

	case EV_FFWARN:	//Warn ally to stop shooting you
		DEBUGNAME("EV_FFWARN");
		CG_TryPlayCustomSound( NULL, es->number, CHAN_VOICE, "*ffwarn.wav", CS_COMBAT );
		break;

	case EV_FFTURN:	//Turn on ally after being shot by them
		DEBUGNAME("EV_FFTURN");
		CG_TryPlayCustomSound( NULL, es->number, CHAN_VOICE, "*ffturn.wav", CS_COMBAT );
		break;

	//extra sounds for ST
	case EV_CHASE1:
	case EV_CHASE2:
	case EV_CHASE3:
		DEBUGNAME("EV_CHASEx");
		CG_TryPlayCustomSound( NULL, es->number, CHAN_VOICE, va("*chase%i.wav", event - EV_CHASE1 + 1), CS_EXTRA );
		break;
	case EV_COVER1:
	case EV_COVER2:
	case EV_COVER3:
	case EV_COVER4:
	case EV_COVER5:
		DEBUGNAME("EV_COVERx");
		CG_TryPlayCustomSound( NULL, es->number, CHAN_VOICE, va("*cover%i.wav", event - EV_COVER1 + 1), CS_EXTRA );
		break;
	case EV_DETECTED1:
	case EV_DETECTED2:
	case EV_DETECTED3:
	case EV_DETECTED4:
	case EV_DETECTED5:
		DEBUGNAME("EV_DETECTEDx");
		CG_TryPlayCustomSound( NULL, es->number, CHAN_VOICE, va("*detected%i.wav", event - EV_DETECTED1 + 1), CS_EXTRA );
		break;
	case EV_GIVEUP1:
	case EV_GIVEUP2:
	case EV_GIVEUP3:
	case EV_GIVEUP4:
		DEBUGNAME("EV_GIVEUPx");
		CG_TryPlayCustomSound( NULL, es->number, CHAN_VOICE, va("*giveup%i.wav", event - EV_GIVEUP1 + 1), CS_EXTRA );
		break;
	case EV_LOOK1:
	case EV_LOOK2:
		DEBUGNAME("EV_LOOKx");
		CG_TryPlayCustomSound( NULL, es->number, CHAN_VOICE, va("*look%i.wav", event - EV_LOOK1 + 1), CS_EXTRA );
		break;
	case EV_LOST1:
		DEBUGNAME("EV_LOST1");
		CG_TryPlayCustomSound( NULL, es->number, CHAN_VOICE, "*lost1.wav", CS_EXTRA );
		break;
	case EV_OUTFLANK1:
	case EV_OUTFLANK2:
		DEBUGNAME("EV_OUTFLANKx");
		CG_TryPlayCustomSound( NULL, es->number, CHAN_VOICE, va("*outflank%i.wav", event - EV_OUTFLANK1 + 1), CS_EXTRA );
		break;
	case EV_ESCAPING1:
	case EV_ESCAPING2:
	case EV_ESCAPING3:
		DEBUGNAME("EV_ESCAPINGx");
		CG_TryPlayCustomSound( NULL, es->number, CHAN_VOICE, va("*escaping%i.wav", event - EV_ESCAPING1 + 1), CS_EXTRA );
		break;
	case EV_SIGHT1:
	case EV_SIGHT2:
	case EV_SIGHT3:
		DEBUGNAME("EV_SIGHTx");
		CG_TryPlayCustomSound( NULL, es->number, CHAN_VOICE, va("*sight%i.wav", event - EV_SIGHT1 + 1), CS_EXTRA );
		break;
	case EV_SOUND1:
	case EV_SOUND2:
	case EV_SOUND3:
		DEBUGNAME("EV_SOUNDx");
		CG_TryPlayCustomSound( NULL, es->number, CHAN_VOICE, va("*sound%i.wav", event - EV_SOUND1 + 1), CS_EXTRA );
		break;
	case EV_SUSPICIOUS1:
	case EV_SUSPICIOUS2:
	case EV_SUSPICIOUS3:
	case EV_SUSPICIOUS4:
	case EV_SUSPICIOUS5:
		DEBUGNAME("EV_SUSPICIOUSx");
		CG_TryPlayCustomSound( NULL, es->number, CHAN_VOICE, va("*suspicious%i.wav", event - EV_SUSPICIOUS1 + 1), CS_EXTRA );
		break;
	//extra sounds for Jedi
	case EV_COMBAT1:
	case EV_COMBAT2:
	case EV_COMBAT3:
		DEBUGNAME("EV_COMBATx");
		CG_TryPlayCustomSound( NULL, es->number, CHAN_VOICE, va("*combat%i.wav", event - EV_COMBAT1 + 1), CS_JEDI );
		break;
	case EV_JDETECTED1:
	case EV_JDETECTED2:
	case EV_JDETECTED3:
		DEBUGNAME("EV_JDETECTEDx");
		CG_TryPlayCustomSound( NULL, es->number, CHAN_VOICE, va("*jdetected%i.wav", event - EV_JDETECTED1 + 1), CS_JEDI );
		break;
	case EV_TAUNT1:
	case EV_TAUNT2:
	case EV_TAUNT3:
		DEBUGNAME("EV_TAUNTx");
		CG_TryPlayCustomSound( NULL, es->number, CHAN_VOICE, va("*taunt%i.wav", event - EV_TAUNT1 + 1), CS_JEDI );
		break;
	case EV_JCHASE1:
	case EV_JCHASE2:
	case EV_JCHASE3:
		DEBUGNAME("EV_JCHASEx");
		CG_TryPlayCustomSound( NULL, es->number, CHAN_VOICE, va("*jchase%i.wav", event - EV_JCHASE1 + 1), CS_JEDI );
		break;
	case EV_JLOST1:
	case EV_JLOST2:
	case EV_JLOST3:
		DEBUGNAME("EV_JLOSTx");
		CG_TryPlayCustomSound( NULL, es->number, CHAN_VOICE, va("*jlost%i.wav", event - EV_JLOST1 + 1), CS_JEDI );
		break;
	case EV_DEFLECT1:
	case EV_DEFLECT2:
	case EV_DEFLECT3:
		DEBUGNAME("EV_DEFLECTx");
		CG_TryPlayCustomSound( NULL, es->number, CHAN_VOICE, va("*deflect%i.wav", event - EV_DEFLECT1 + 1), CS_JEDI );
		break;
	case EV_GLOAT1:
	case EV_GLOAT2:
	case EV_GLOAT3:
		DEBUGNAME("EV_GLOATx");
		CG_TryPlayCustomSound( NULL, es->number, CHAN_VOICE, va("*gloat%i.wav", event - EV_GLOAT1 + 1), CS_JEDI );
		break;
	case EV_PUSHFAIL:
		DEBUGNAME("EV_PUSHFAIL");
		CG_TryPlayCustomSound( NULL, es->number, CHAN_VOICE, "*pushfail.wav", CS_JEDI );
		break;

	case EV_USE_FORCE:
		DEBUGNAME("EV_USE_FORCEITEM");
		CG_UseForce( cent );
		break;

	case EV_USE_ITEM:
		DEBUGNAME("EV_USE_ITEM");
		CG_UseItem( cent );
		break;

	case EV_USE_INV_BINOCULARS:
		DEBUGNAME("EV_USE_INV_BINOCULARS");
		UseItem(INV_ELECTROBINOCULARS );
		break;

	case EV_USE_INV_BACTA:
		DEBUGNAME("EV_USE_INV_BACTA");
		UseItem(INV_BACTA_CANISTER );
		break;

	case EV_USE_INV_SEEKER:
		DEBUGNAME("EV_USE_INV_SEEKER");
		UseItem(INV_SEEKER );
		break;

	case EV_USE_INV_LIGHTAMP_GOGGLES:
		DEBUGNAME("EV_USE_INV_LIGHTAMP_GOGGLES");
		UseItem(INV_LIGHTAMP_GOGGLES );
		break;

	case EV_USE_INV_SENTRY:
		DEBUGNAME("EV_USE_INV_SENTRY");
		UseItem(INV_SENTRY );
		break;

	case EV_DEBUG_LINE:
		DEBUGNAME("EV_DEBUG_LINE");
		CG_TestLine(position, es->origin2, es->time, (unsigned int)(es->time2), es->weapon);
		break;

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

}


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

==============
*/
void CG_CheckEvents( centity_t *cent ) {
	// check for event-only entities
	if ( cent->currentState.eType > ET_EVENTS ) {
		if ( cent->previousEvent ) {
			return;	// already fired
		}
		cent->previousEvent = 1;

		cent->currentState.event = cent->currentState.eType - ET_EVENTS;
	} else {
		// check for events riding with another entity
		if ( cent->currentState.event == cent->previousEvent ) {
			return;
		}
		cent->previousEvent = cent->currentState.event;
		if ( ( cent->currentState.event & ~EV_EVENT_BITS ) == 0 ) {
			return;
		}
	}

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

	CG_EntityEvent( cent, cent->lerpOrigin );
}

