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

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

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

See the GNU General Public License for more details.

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

*/
#include "cg_local.h"

/*
==============
CG_TouchJumpPad
==============
*/
void CG_TouchJumpPad( int entNum )
{
	CG_SexedSound( entNum, CHAN_VOICE, va( S_PLAYER_JUMP_1_to_2, (rand()&1)+1 ), cg_volume_players->value );
}

//==============
//CG_StartVoiceTokenEffect
//==============
#ifdef VSAYS
void CG_StartVoiceTokenEffect( int entNum, int type, int vsay ) {
	centity_t	*cent;
	cgs_media_handle_t *sound = NULL;

	if( !cg_voiceChats->integer || cg_volume_voicechats->value <= 0.0f )
		return;

	cent = &cg_entities[entNum];

	// set the icon effect
	cent->localEffects[LOCALEFFECT_VSAY_HEADICON] = vsay;
	cent->localEffects[LOCALEFFECT_VSAY_HEADICON_TIMEOUT] = cg.time + HEADICON_TIMEOUT;

	// play the sound 
	sound = cgs.media.sfxVSaySounds[vsay];

	// played as it was made by the 1st person player
	trap_S_StartSound( NULL, cg.chasedNum+1, CHAN_AUTO, CG_MediaSfx(sound), cg_volume_voicechats->value, ATTN_NONE, 0 );
}
#endif // VSAYS

/*
==============
CG_PlayerMuzzleFlash
==============
*/
void CG_PlayerMuzzleFlash( entity_state_t *ent, int parms )
{
	int			i;
	centity_t	*pl;
	float		radius, volume;
	vec3_t		lightcolor;
	vec3_t		muzzle;
	qboolean	silenced = parms & MZ_SILENCED;
	int			strongmode = parms & ~MZ_SILENCED;
	cgs_media_handle_t *sound = NULL;

	pl = &cg_entities[ent->number];

	if ( silenced ) {
		volume = 0.2f;
		//radius = 100 + (rand()&31);
	} else {
		volume = 1.0f;
		//radius = 200 + (rand()&31);
	}

	if( strongmode )
		radius = 200 + (rand()&31);
	else
		radius = 100 + (rand()&31);

	if( ent->renderfx & RF_FRAMELERP ) {
		VectorCopy( ent->origin, muzzle );
	} else {
		for( i = 0; i < 3; i++ )
			muzzle[i] = pl->prev.origin[i] + cg.lerpfrac * (pl->current.origin[i] - pl->prev.origin[i]);
	}

	switch( ent->weapon ) {

		case WEAP_GUNBLADE:
			if( strongmode ) {
				VectorSet( lightcolor, 1.0f, 0.0f, 0.2f );
				sound = cgs.media.sfxGunbladeStrongShot;
			} else {
				radius = 0;
				sound = cgs.media.sfxGunbladeWeakShot[(int)(random()*3)];
			}
			break;
		case WEAP_RIOTGUN:
			VectorSet( lightcolor, 1.0f, 0.0f, 0.2f );
			sound = strongmode ? cgs.media.sfxRiotgunStrongShot : cgs.media.sfxRiotgunWeakShot;
			break;
		case WEAP_GRENADELAUNCHER:
			VectorSet( lightcolor, 1.0f, 0.0f, 0.2f );
			sound = strongmode ? cgs.media.sfxGrenadeLauncherStrongShot : cgs.media.sfxGrenadeLauncherWeakShot;
			break;
		case WEAP_ROCKETLAUNCHER:
			VectorSet( lightcolor, 1.0f, 0.0f, 0.2f );
			sound = strongmode ? cgs.media.sfxRocketLauncherStrongShot : cgs.media.sfxRocketLauncherWeakShot;
			break;
		case WEAP_PLASMAGUN:
			VectorSet( lightcolor, 0.0f, 1.0f, 0.0f );
			sound = strongmode ? cgs.media.sfxPlasmagunStrongShot[rand()%3] : cgs.media.sfxPlasmagunWeakShot;
			break;
		case WEAP_ELECTROBOLT:
			VectorSet( lightcolor, 0.9f, 0.9f, 1.0f );
			sound = strongmode ? cgs.media.sfxElectroboltStrongShot : cgs.media.sfxElectroboltWeakShot;
			break;
		case WEAP_LASERGUN:
			sound = strongmode ? cgs.media.sfxLasergunStrongShot : cgs.media.sfxLasergunWeakShot;
			radius = 0;
			VectorClear( lightcolor );
			break;

		// default to no light
		default:
			radius = 0;
			VectorClear( lightcolor );
			break;
	}

	// spawn light if not cleared
	if( radius )
		CG_AddLightToScene( muzzle, radius, lightcolor[0], lightcolor[1], lightcolor[2], NULL );
		//CG_AllocDlight( radius, muzzle, lightcolor );

	if ( sound ) {
		if( ent->number == cg.chasedNum + 1 )
			trap_S_StartSound ( NULL, ent->number, CHAN_AUTO, CG_MediaSfx (sound), cg_volume_effects->value, ATTN_NORM, 0 );
		else
			trap_S_StartSound ( ent->origin, 0, CHAN_AUTO, CG_MediaSfx (sound), cg_volume_effects->value, ATTN_NORM, 0 );
	}
}

void CG_WeaponSwitchSound( entity_state_t *ent, int parm )
{
	cgs_media_handle_t *sound = NULL;
	
	if( parm == 1 )
		sound = cgs.media.sfxWeaponUp;
	else if( parm == 2 )
		sound = cgs.media.sfxWeaponUpNoAmmo;
	
	if ( sound ) {
		if( ent->number == cg.chasedNum + 1 )
			trap_S_StartSound ( NULL, ent->number, CHAN_AUTO, CG_MediaSfx (sound), cg_volume_effects->value, ATTN_NORM, 0 );
		else
			trap_S_StartSound ( ent->origin, 0, CHAN_AUTO, CG_MediaSfx (sound), cg_volume_effects->value, ATTN_NORM, 0 );
	}

}

/*
==============
CG_FireLead

Clientside prediction of gunshots.
Must match fire_lead in g_weapon.c
==============
*/
static void CG_FireLead( int self, vec3_t start, vec3_t axis[3], int hspread, int vspread, int *seed, trace_t *trace )
{
	trace_t		tr;
	vec3_t		dir;
	vec3_t		end;
	float		r;
	float		u;
	vec3_t		water_start;
	qboolean	water = qfalse;
	int			content_mask = MASK_SHOT | MASK_WATER;

#if 1
	// circle
	double alpha=M_PI*Q_crandom(seed); // [-PI ..+PI]
	double s=fabs(Q_crandom(seed)); // [0..1]
	r= s*cos(alpha)*hspread;
	u= s*sin(alpha)*vspread;
#else
	// square
	r = Q_crandom (seed) * hspread;
	u = Q_crandom (seed) * vspread;
#endif

	VectorMA( start, 8192, axis[0], end );
	VectorMA( end, r, axis[1], end );
	VectorMA( end, u, axis[2], end );

	if( CG_PointContents( start ) & MASK_WATER ) {
		water = qtrue;
		VectorCopy( start, water_start );
		content_mask &= ~MASK_WATER;
	}

	CG_Trace( &tr, start, vec3_origin, vec3_origin, end, self, content_mask );

	// see if we hit water
	if ( tr.contents & MASK_WATER ) {
		water = qtrue;
		VectorCopy( tr.endpos, water_start );

		if( !VectorCompare( start, tr.endpos ) ) {
			vec3_t forward, right, up;

			if( tr.contents & CONTENTS_WATER )
				CG_ParticleEffect( tr.endpos, tr.plane.normal, 0.47f, 0.48f, 0.8f, 8 );
			else if ( tr.contents & CONTENTS_SLIME )
				CG_ParticleEffect( tr.endpos, tr.plane.normal, 0.0f, 1.0f, 0.0f, 8 );
			else if ( tr.contents & CONTENTS_LAVA )
				CG_ParticleEffect( tr.endpos, tr.plane.normal, 1.0f, 0.67f, 0.0f, 8 );

			// change bullet's course when it enters water
			VectorSubtract( end, start, dir );
			VecToAngles( dir, dir );
			AngleVectors( dir, forward, right, up );
			r = Q_crandom( seed ) * hspread * 2;
			u = Q_crandom( seed ) * vspread * 2;
			VectorMA( water_start, 8192, forward, end );
			VectorMA( end, r, right, end );
			VectorMA( end, u, up, end );
		}

		// re-trace ignoring water this time
		CG_Trace( &tr, water_start, vec3_origin, vec3_origin, end, self, MASK_SHOT );
	}

	// save the final trace
	*trace = tr;

	// if went through water, determine where the end and make a bubble trail
	if ( water ) {
		vec3_t	pos;

		VectorSubtract( tr.endpos, water_start, dir );
		VectorNormalize( dir );
		VectorMA( tr.endpos, -2, dir, pos );

		if( CG_PointContents( pos ) & MASK_WATER )
			VectorCopy( pos, tr.endpos );
		else
			CG_Trace( &tr, pos, vec3_origin, vec3_origin, water_start, tr.ent ? cg_entities[tr.ent].current.number : 0, MASK_WATER );

		VectorAdd( water_start, tr.endpos, pos );
		VectorScale( pos, 0.5, pos );

		CG_BubbleTrail( water_start, tr.endpos, 32 );
	}
}

//==============
//CG_FireBullet
//==============
void CG_FireBullet( int self, vec3_t start, vec3_t forward, int count, int vspread, int hspread, int seed, void (*impact) (trace_t *tr/*, int impactnum*/) )
{
	int i;
	trace_t tr;
	vec3_t dir, axis[3];
	qboolean takedamage;

	// calculate normal vectors
	VecToAngles( forward, dir );
	AngleVectors( dir, axis[0], axis[1], axis[2] );

	for( i = 0; i < count; i++ ) 
	{
		// wsw: fix hspread is the first param for that function
		CG_FireLead( self, start, axis, hspread, vspread, &seed, &tr );

		takedamage = tr.ent && ( cg_entities[tr.ent].current.takedamage );

		if( tr.fraction < 1.0f && !takedamage && !(tr.surfFlags & SURF_NOIMPACT) )
			impact( &tr );
	}
}

/*
==============
CG_BulletImpact
==============
*/
void CG_BulletImpact( trace_t *tr )
{
	// bullet impact
	CG_BulletExplosion( tr->endpos, tr->plane.normal );

	// spawn decal
	CG_SpawnDecal( tr->endpos, tr->plane.normal, random()*360, 8, 1, 1, 1, 1, 8, 1, qfalse, CG_MediaShader (cgs.media.shaderBulletMark) );

	// throw particles on dust
	if( tr->surfFlags & SURF_DUST )
		CG_ParticleEffect( tr->endpos, tr->plane.normal, 0.30f, 0.30f, 0.25f, 20 );

	// impact sound
	trap_S_StartSound( tr->endpos, 0, 0, CG_MediaSfx( cgs.media.sfxRic[rand()&2] ), cg_volume_effects->value, ATTN_NORM, 0 );
}

/*
==============
CG_RiotGunImpactSound
==============
*/
void CG_RiotGunImpactSound( int self, vec3_t start, vec3_t dir, int count )
{
	trace_t	trace;
	vec3_t	end;

	VectorMA( start, 8192, dir, end );
	CG_Trace( &trace, start, vec3_origin, vec3_origin, end, self, MASK_SHOT );
	if( trace.fraction < 1.0f && !(trace.surfFlags & SURF_NOIMPACT) ) {
		if( count > 20 )
			trap_S_StartSound ( trace.endpos, 0, 0, CG_MediaSfx (cgs.media.sfxRiotgunStrongHit), cg_volume_effects->value, ATTN_NORM, 0 );
		else
			trap_S_StartSound ( trace.endpos, 0, 0, CG_MediaSfx (cgs.media.sfxRiotgunWeakHit), cg_volume_effects->value, ATTN_NORM, 0 );
	}
}

/*
==============
CG_RiotgunStrongImpact
==============
*/
void CG_RiotgunStrongImpact( trace_t *tr )//, int impactnum )
{
	// bullet impact
	CG_BulletExplosion ( tr->endpos, tr->plane.normal );

	// throw particles on dust
	if ( tr->surfFlags & SURF_DUST ) {
		CG_ParticleEffect ( tr->endpos, tr->plane.normal, 0.30f, 0.30f, 0.25f, 20 );
	}

	// spawn decal
	CG_SpawnDecal ( tr->endpos, tr->plane.normal, random()*360, 8, 1, 1, 1, 1, 8, 1, qfalse, CG_MediaShader (cgs.media.shaderBulletMark) );

	//if ( !impactnum )
	//	trap_S_StartSound ( tr->endpos, 0, 0, CG_MediaSfx (cgs.media.sfxRiotgunStrongHit), cg_volume_effects->value, ATTN_NORM, 0 );
}




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

//=========================================================
#define CG_MAX_ANNOUNCER_EVENTS	32
#define CG_MAX_ANNOUNCER_EVENTS_MASK ( CG_MAX_ANNOUNCER_EVENTS - 1 )
#define CG_ANNOUNCER_EVENTS_FRAMETIME 1.5f // the announcer will speak each 1.5 seconds
typedef struct cg_announcerevent_s
{
	int		soundindex;
}cg_announcerevent_t;
cg_announcerevent_t cg_announcerEvents[CG_MAX_ANNOUNCER_EVENTS];
static int			cg_announcerEventsCurrent = 0;
static int			cg_announcerEventsHead = 0;
static float		cg_announcerEventsDelay = 0.0f;

//=================
//G_ClearAnnouncerEvents
//=================
void CG_ClearAnnouncerEvents( void ) {
	cg_announcerEventsCurrent = cg_announcerEventsHead = 0;
}

//=================
//G_AddAnnouncerEvent
//=================
void CG_AddAnnouncerEvent( int soundindex ) {
	if( cg_announcerEventsCurrent + CG_MAX_ANNOUNCER_EVENTS >= cg_announcerEventsHead ) {
		// full buffer (we do nothing, just let it overwrite the oldest
	}

	// add it
	cg_announcerEvents[cg_announcerEventsHead&CG_MAX_ANNOUNCER_EVENTS_MASK].soundindex = soundindex;
	cg_announcerEventsHead++;
}

//=================
//G_ReleaseAnnouncerEvents
//=================
void CG_ReleaseAnnouncerEvents( void ) {
	// see if time enough has passed
	cg_announcerEventsDelay -= cg.frameTime;
	if( cg_announcerEventsDelay > 0.0f ) {
		return;
	}

	if( cg_announcerEventsCurrent < cg_announcerEventsHead ) {
		// play the event
		int soundindex = cg_announcerEvents[cg_announcerEventsCurrent&CG_MAX_ANNOUNCER_EVENTS_MASK].soundindex;
		trap_S_StartSound( NULL, cg.chasedNum + 1, CHAN_AUTO, cgs.soundPrecache[soundindex], cg_volume_announcer->value, ATTN_NONE, 0 );
		cg_announcerEventsCurrent++;
		cg_announcerEventsDelay = CG_ANNOUNCER_EVENTS_FRAMETIME; // wait
	} else {
		cg_announcerEventsDelay = 0; // no wait
	}
}

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

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





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

An entity has just been parsed that has an event value
==============
*/
//wsw : jal : fixme (clean up into cg_local.h)
void CG_PlasmaExplosion ( vec3_t pos, vec3_t dir, int fire_mode, float radius );
void CG_GrenadeExplosionMode ( vec3_t pos, vec3_t dir, int fire_mode, float radius );
void CG_RocketExplosionMode ( vec3_t pos, vec3_t dir, int fire_mode, float radius );
void CG_ElectroTrail2( vec3_t start, vec3_t end );
void CG_BoltExplosionMode ( vec3_t pos, vec3_t dir, int fire_mode );
void CG_BladeImpact ( vec3_t pos, vec3_t dir );
void CG_GunBladeBlastImpact ( vec3_t pos, vec3_t dir, float radius );
void CG_StartKickAnglesEffect( vec3_t source, float knockback, float radius, int time );
void CG_StartColorBlendEffect( float r, float g, float b, float a, int time );

struct sfx_s *CG_RegisterSexedSound( int entnum, char *name );

void CG_PModel_SetUpTeleportEffect( vec3_t origin, int entNum, int event );

void CG_EntityEvent( entity_state_t *ent )
{
	static orientation_t projection;
	int choose;//splitmodels
	int i;
	int parm;
	vec3_t dir;

	for( i = 0; i < 2; i++ ) 
	{
		parm = ent->eventParms[i];

		switch( ent->events[i] ) 
		{
			case EV_FALL:
				if( parm == FALL_FAR ) {
					CG_SexedSound( ent->number, CHAN_VOICE, "*fall_2.wav", cg_volume_players->value );
				} else if( parm == FALL_MEDIUM ) {
					CG_SexedSound( ent->number, CHAN_VOICE, "*fall_1.wav", cg_volume_players->value );
				} else
					CG_SexedSound( ent->number, CHAN_VOICE, "*fall_0.wav", cg_volume_players->value );
				//splitmodels jal[start]
#ifndef _ANIM_PRESTEP
				CG_AddPModelAnimation( ent->number, LEGS_JUMP1ST, 0, 0, EVENT_CHANNEL);
#endif			//jal[end]
				break;

			case EV_SEXEDSOUND:
				if( parm == 2 )
					CG_SexedSound( ent->number, CHAN_VOICE, S_PLAYER_GASP, cg_volume_players->value );
				else if( parm == 1 )
					CG_SexedSound( ent->number, CHAN_VOICE, S_PLAYER_DROWN, cg_volume_players->value );
				break;
 
			case EV_PAIN:
				CG_SexedSound( ent->number, CHAN_VOICE, va( S_PLAYER_PAINS, 25*(parm+1) ), cg_volume_players->value );
				//splitmodels	jal[start]
				choose = random();
				if (choose <= 0.33)
					CG_AddPModelAnimation( ent->number, 0, TORSO_PAIN1, 0, EVENT_CHANNEL);
				else if (choose <= 0.66)
					CG_AddPModelAnimation( ent->number, 0, TORSO_PAIN2, 0, EVENT_CHANNEL);
				else
					CG_AddPModelAnimation( ent->number, 0, TORSO_PAIN3, 0, EVENT_CHANNEL);
				//jal[end]
				break;

			case EV_DIE:
				CG_SexedSound( ent->number, CHAN_VOICE, "*death.wav", cg_volume_players->value );
				//splitmodels	jal[start]
				switch( parm )
				{
				case 0:
				default:
					CG_AddPModelAnimation( ent->number, BOTH_DEATH1, BOTH_DEATH1, ANIM_NONE, EVENT_CHANNEL);
					break;
				case 1:
					CG_AddPModelAnimation( ent->number, BOTH_DEATH2, BOTH_DEATH2, ANIM_NONE, EVENT_CHANNEL);
					break;
				case 2:
					CG_AddPModelAnimation( ent->number, BOTH_DEATH3, BOTH_DEATH3, ANIM_NONE, EVENT_CHANNEL);
					break;
				}	//jal[end]
				break;

			case EV_GIB:
				//	trap_S_StartSound( NULL, ent->number, CHAN_VOICE, CG_MediaSfx( cgs.media.sfxGibSound ), cg_volume_effects->value, ATTN_NORM, 0 );
				break;

			case EV_JUMP_PAD:
				CG_TouchJumpPad( ent->ownerNum/*, ent->targetNum*/ );
				break;

			case EV_EXPLOSION1:
				CG_Explosion1( ent->origin );
				break;

			case EV_EXPLOSION2:
				CG_Explosion2( ent->origin );
				break;
				
			case EV_GREEN_LASER:
				CG_GreenLaser( ent->origin, ent->origin2 );
				break;

			case EV_LIGHTNING:
				//CG_AddLightning( ent->ownerNum, ent->targetNum, ent->origin, ent->origin2, CG_MediaModel( cgs.media.modLightning ) );
				//trap_S_StartSound( NULL, ent->ownerNum, CHAN_AUTO, CG_MediaSfx( cgs.media.sfxLightning ), cg_volume_effects->value, ATTN_NORM, 0 );
				break;

			case EV_BLOOD:
				ByteToDir( parm, dir );
				CG_ParticleEffect( ent->origin, dir, 0.61f, 0.1f, 0.0f, 60 );
				break;

			case EV_SPARKS:
				ByteToDir( parm, dir );
				CG_ParticleEffect( ent->origin, dir, 1.0f, 0.67f, 0.0f, 6 );
				break;

			case EV_BULLET_SPARKS:
				ByteToDir( parm, dir );
				CG_BulletExplosion( ent->origin, dir );
				CG_ParticleEffect( ent->origin, dir, 1.0f, 0.67f, 0.0f, 6 );
				trap_S_StartSound( ent->origin, 0, 0, CG_MediaSfx( cgs.media.sfxRic[rand()&2] ), cg_volume_effects->value, ATTN_NORM, 0 );
				break;

			case EV_LASER_SPARKS:
				ByteToDir( parm, dir );
				CG_ParticleEffect2( ent->origin, dir, 
					COLOR_R (ent->colorRGBA) * (1.0 / 255.0), 
					COLOR_G (ent->colorRGBA) * (1.0 / 255.0), 
					COLOR_B (ent->colorRGBA) * (1.0 / 255.0), 
					ent->eventCount );
				break;

			//SPLITMODELS	jal[start]
			case EV_GESTURE:
				CG_SexedSound( ent->number, CHAN_VOICE, "*taunt.wav", cg_volume_players->value );
				CG_AddPModelAnimation( ent->number, 0, TORSO_GESTURE, 0, EVENT_CHANNEL);
				break;
				
			case EV_DROP:
				CG_AddPModelAnimation( ent->number, 0, TORSO_DROP, 0, EVENT_CHANNEL);
				break;

			case EV_WEAPONUP:
				CG_WeaponSwitchSound( ent, parm );
				CG_AddPModelAnimation( ent->number, 0, TORSO_FLIPIN, 0, EVENT_CHANNEL);
				break;

			case EV_SPOG://debrisbounce
				CG_SmallPileOfGibs( ent->origin, parm, ent->origin2 );
				break;
			//jal[end]

//JALNEWWEAPONS : wsw events
//-------------------------------------------------------------------
				case EV_JUMP:
				if( parm == 1 ) {
					CG_SexedSound( ent->number, CHAN_VOICE, va( S_PLAYER_WALLJUMP_1_to_2, (rand()&1)+1 ), cg_volume_players->value );
				} else if( parm == 2 ) {
					CG_SexedSound( ent->number, CHAN_VOICE, va( S_PLAYER_DASH_1_to_2, (rand()&1)+1 ), cg_volume_players->value );
				} else
					CG_SexedSound( ent->number, CHAN_VOICE, va( S_PLAYER_JUMP_1_to_2, (rand()&1)+1 ), cg_volume_players->value );
				break;

			case EV_ITEM_RESPAWN:
				cg_entities[ent->number].respawnTime = cg.time;
				if( parm == POWERUP_QUAD )
					trap_S_StartSound( NULL, ent->number, CHAN_AUTO, CG_MediaSfx (cgs.media.sfxItemQuadRespawn), cg_volume_effects->value, ATTN_IDLE, 0 );
				else
					trap_S_StartSound( NULL, ent->number, CHAN_AUTO, CG_MediaSfx (cgs.media.sfxItemRespawn), cg_volume_effects->value, ATTN_IDLE, 0 );
				break;

			case EV_PLAYER_TELEPORT_IN:
				if( ent->ownerNum == cg.chasedNum + 1 ) {
					trap_S_StartSound( NULL, ent->ownerNum, 0, CG_MediaSfx (cgs.media.sfxTeleportIn),
							cg_volume_effects->value, ATTN_NORM, 0 );
				} else {
					trap_S_StartSound( ent->origin, 0, 0, CG_MediaSfx (cgs.media.sfxTeleportIn),
							cg_volume_effects->value, ATTN_NORM, 0 );
				}
				if( ent->ownerNum && ent->ownerNum < MAX_CLIENTS ) {
					cg_entities[ent->ownerNum].localEffects[LOCALEFFECT_EV_PLAYER_TELEPORT_IN] = (unsigned int)cg.time;
				} else {
					CG_TeleportEffect( ent->origin );
				}
				break;
				
			case EV_PLAYER_TELEPORT_OUT:
				if( ent->ownerNum == cg.chasedNum + 1 ) {
					trap_S_StartSound( NULL, ent->ownerNum, 0, CG_MediaSfx (cgs.media.sfxTeleportOut),
							cg_volume_effects->value, ATTN_NORM, 0 );
				} else {
					trap_S_StartSound( ent->origin, 0, 0, CG_MediaSfx (cgs.media.sfxTeleportOut),
							cg_volume_effects->value, ATTN_NORM, 0 );
				}
				if( ent->ownerNum && ent->ownerNum < MAX_CLIENTS ) {
					cg_entities[ent->ownerNum].localEffects[LOCALEFFECT_EV_PLAYER_TELEPORT_OUT] = (unsigned int)cg.time;
					VectorCopy( ent->origin, cg_entities[ent->ownerNum].teleportedFrom );
				} else {
					CG_TeleportEffect( ent->origin );
				}
				break;

			case EV_MUZZLEFLASH: //parm is fire mode
				CG_PlayerMuzzleFlash( ent, parm );
				CG_PModels_AddFireEffects( ent );
				break;

			case EV_ELECTROTRAIL:
				if( CG_PModel_GetProjectionSource( ent->ownerNum, &projection ) )
					CG_ElectroTrail2( projection.origin, ent->origin2 );
				else
					CG_ElectroTrail2( ent->origin, ent->origin2 );
				break;

			case EV_PLASMA_EXPLOSION:
				ByteToDir( parm, dir );
				if( ent->firemode == FIRE_MODE_WEAK ) {
					CG_PlasmaExplosion( ent->origin, dir, FIRE_MODE_WEAK, (float)ent->weapon * 8.0f );
					trap_S_StartSound( ent->origin, 0, CHAN_AUTO, CG_MediaSfx( cgs.media.sfxPlasmaWeakHit), cg_volume_effects->value, ATTN_NORM, 0 );
				} else {
					CG_PlasmaExplosion( ent->origin, dir, FIRE_MODE_STRONG, (float)ent->weapon * 8.0f );
					trap_S_StartSound( ent->origin, 0, CHAN_AUTO, CG_MediaSfx( cgs.media.sfxPlasmaStrongHit), cg_volume_effects->value, ATTN_NORM, 0 );
				}
				// add kickangles from explosion radius
				if( ent->firemode == FIRE_MODE_STRONG )
					CG_StartKickAnglesEffect( ent->origin, 30, ent->weapon * 8, 50 );
				else
					CG_StartKickAnglesEffect( ent->origin, 20, ent->weapon * 8, 30 );
				break;

			case EV_BOLT_EXPLOSION:
				ByteToDir( parm, dir );
				CG_BoltExplosionMode( ent->origin, dir, ent->firemode );
				CG_BulletExplosion( ent->origin, dir );
				break;

			case EV_GRENADE_EXPLOSION:
				// wsw: pb make grenades using the same explosion effect as RL
				if( parm ) 
				{
					// we have a direction
					ByteToDir( parm, dir );
					CG_GrenadeExplosionMode( ent->origin, dir, ent->firemode, (float)ent->weapon*8.0f );
				}
				else
				{
					// no direction so lets use vec3_origin
					CG_GrenadeExplosionMode( ent->origin, vec3_origin, ent->firemode, (float)ent->weapon*8.0f );
				}

				/* // old code
				if( parm ) {
					ByteToDir( parm, dir );
					CG_GrenadeExplosionMode( ent->origin, dir, ent->firemode, (float)ent->weapon*8.0f );
				} else
					CG_GrenadeExplosionMode( ent->origin, NULL, ent->firemode, (float)ent->weapon*8.0f );
				*/

				if( ent->firemode == FIRE_MODE_WEAK )
					trap_S_StartSound( ent->origin, 0, CHAN_AUTO, CG_MediaSfx( cgs.media.sfxGrenadeWeakExplosion), cg_volume_effects->value, ATTN_NORM, 0 );
				else
					trap_S_StartSound( ent->origin, 0, CHAN_AUTO, CG_MediaSfx( cgs.media.sfxGrenadeStrongExplosion), cg_volume_effects->value, ATTN_NORM, 0 );
				// add kickangles from explosion radius
				if( ent->firemode == FIRE_MODE_STRONG )
					CG_StartKickAnglesEffect( ent->origin, 150, ent->weapon*8, 350 );
				else
					CG_StartKickAnglesEffect( ent->origin, 130, ent->weapon*8, 300 );
				break;

			case EV_ROCKET_EXPLOSION:
				ByteToDir( parm, dir );
				if( ent->firemode == FIRE_MODE_WEAK ) {
					CG_RocketExplosionMode( ent->origin, dir, FIRE_MODE_WEAK, (float)ent->weapon * 8.0f );
					trap_S_StartSound( ent->origin, 0, CHAN_AUTO, CG_MediaSfx( cgs.media.sfxRocketLauncherWeakHit), cg_volume_effects->value, ATTN_NORM, 0 );
				} else {
					CG_RocketExplosionMode( ent->origin, dir, FIRE_MODE_STRONG, (float)ent->weapon * 8.0f );
					trap_S_StartSound( ent->origin, 0, CHAN_AUTO, CG_MediaSfx( cgs.media.sfxRocketLauncherStrongHit), cg_volume_effects->value, ATTN_NORM, 0 );
				}
				// add kickangles from explosion radius
				if( ent->firemode == FIRE_MODE_STRONG )
					CG_StartKickAnglesEffect( ent->origin, 140, ent->weapon * 8, 300 );
				else
					CG_StartKickAnglesEffect( ent->origin, 120, ent->weapon * 8, 250 );
				break;

			case EV_FIRE_RIOTGUN:
				//spreads come inside the free shorts I found: //light = hspread //skinnum = vspread
				CG_FireBullet( ent->ownerNum, ent->origin, ent->origin2, ent->eventCount, ent->skinnum, ent->light, parm, CG_RiotgunStrongImpact );
				//spawn a single sound in the impact
				CG_RiotGunImpactSound( ent->ownerNum, ent->origin, ent->origin2, ent->eventCount );
				break;

			case EV_FIRE_BULLET:
				CG_FireBullet( ent->ownerNum, ent->origin, ent->origin2, 1, DEFAULT_BULLET_VSPREAD, DEFAULT_BULLET_HSPREAD, parm, CG_BulletImpact );
				break;

			case EV_GRENADE_BOUNCE:
				if( parm == FIRE_MODE_STRONG )
					trap_S_StartSound( NULL, ent->number, CHAN_AUTO, CG_MediaSfx (cgs.media.sfxGrenadeStrongBounce[rand()&1]), cg_volume_effects->value, ATTN_NORM, 0 );
				else
					trap_S_StartSound( NULL, ent->number, CHAN_AUTO, CG_MediaSfx (cgs.media.sfxGrenadeWeakBounce[rand()&1]), cg_volume_effects->value, ATTN_NORM, 0 );
				break;

			case EV_BLADE_IMPACT:
				CG_BladeImpact( ent->origin, ent->origin2 );
				break;

			case EV_GUNBLADEBLAST_IMPACT:
				ByteToDir ( parm, dir );
				CG_GunBladeBlastImpact( ent->origin, dir, (float)ent->weapon*8 );
				//sfxGunbladeStrongHit
				if( ent->skinnum > 64 )
					trap_S_StartSound( ent->origin, 0, CHAN_AUTO, CG_MediaSfx (cgs.media.sfxGunbladeStrongHit[2]), cg_volume_effects->value, ATTN_NORM, 0 );
				else if( ent->skinnum > 34 )
					trap_S_StartSound( ent->origin, 0, CHAN_AUTO, CG_MediaSfx (cgs.media.sfxGunbladeStrongHit[1]), cg_volume_effects->value, ATTN_NORM, 0 );
				else 
					trap_S_StartSound( ent->origin, 0, CHAN_AUTO, CG_MediaSfx (cgs.media.sfxGunbladeStrongHit[0]), cg_volume_effects->value, ATTN_NORM, 0 );
				
				// add kickangles from explosion radius
				//ent->skinnum is knockback value
				CG_StartKickAnglesEffect( ent->origin, ent->skinnum*8, ent->weapon*8, 200 );
				break;

			case EV_BLOOD2:
				// wsw: pb: filter own blood trail
				if ( cg_showBloodTrail->integer==2 && (cg.chasedNum+1 == ent->ownerNum))
					return;
				ByteToDir( parm, dir );
				CG_BloodDamageEffect( ent->origin, dir, ent->damage );
				break;

			case EV_BLOOD_SAVED:
				//ByteToDir( parm, dir );
				//CG_BloodDamageEffect( ent->origin, dir, ent->damage );
				break;

#ifdef VSAYS
			case EV_VSAY:
				CG_StartVoiceTokenEffect( ent->ownerNum, EV_VSAY, parm );
				break;
#endif

#ifdef THIS_IS_DISABLED
			case EV_RAILTRAIL:
				//splitmodels	jal[start]
				if( vweap.active && (cg.chasedNum+1 == ent->ownerNum) ) {
					orientation_t projection;
					if( CG_vWeap_CalcProjectionSource( &projection ) )
						CG_RailTrail( projection.origin, ent->origin2 );
					else
						CG_RailTrail( ent->origin, ent->origin2 );
				} else {
					orientation_t projection;
					if( CG_PModel_GetProjectionSource( &cg_entPModels[ent->ownerNum], &projection) )
						CG_RailTrail ( projection.origin, ent->origin2 );
					else
						CG_RailTrail ( ent->origin, ent->origin2 );
				}
				//jal[end]
				trap_S_StartSound( ent->origin2, 0, 0, CG_MediaSfx( cgs.media.sfxRailg ), cg_volume_effects->value, ATTN_NORM, 0 );
				break;

			case EV_BFG_EXPLOSION:
				CG_BFGExplosion( ent->origin );
				break;

			case EV_BFG_BIGEXPLOSION:
				CG_BFGBigExplosion( ent->origin );
				break;
#endif
		}
	}
}

//==================
//CG_FirePlayerStateEvents
//This events are only received by this client, and only affect it.
//==================
void CG_FirePlayerStateEvents( void )
{
	unsigned int	event, parm;

	if( !cg.frame.playerState.event )
		return;

	// first byte is event number, second is parm
	event = cg.frame.playerState.event & 0xFF;
	parm = (cg.frame.playerState.event>>8) & 0xFF;

	//CG_Printf( "GotPSEVENT:%i\n", event );

	switch( event )
	{
	case PSEV_HIT :
		// wsw : pb: hit sound volume
		trap_S_StartSound( cg.refdef.vieworg, 0, 0, CG_MediaSfx( cgs.media.sfxWeaponHit[parm] ), cg_volume_hitsound->value , ATTN_NONE, 0 );
		if( parm == 4 && cg_showhelp->integer ) {
			if( random() <= 0.5f )
				SCR_CenterPrint( "Don't shoot at members of your team!" );
			else
				SCR_CenterPrint( "You are shooting at your team-mates!" );
		}
		break;

	case PSEV_PICKUP :
		if( cg_pickup_flash->integer )
			CG_StartColorBlendEffect( 1.0f, 1.0f, 1.0f, 0.25f, 150 );
		break;

	case PSEV_DAMAGE_BLEND :
		if( cg_damage_blend->integer )
			CG_StartColorBlendEffect( 1.0f, 0.0f, 0.0f, 0.4f, parm * 10 );
		break;

	case PSEV_INDEXEDSOUND:
		trap_S_StartSound( NULL, cg.chasedNum + 1, CHAN_AUTO, cgs.soundPrecache[parm], cg_volume_effects->value, ATTN_NORM, 0 );
		break;

	case PSEV_NOAMMO:
		if( parm == cg.frame.playerState.stats[STAT_WEAPON_ITEM] )
			CG_NoAmmoWeaponChange();
		break;

	case PSEV_ANNOUNCER:
		trap_S_StartSound( NULL, cg.chasedNum + 1, CHAN_AUTO, cgs.soundPrecache[parm], cg_volume_announcer->value, ATTN_NONE, 0 );
		break;

	case PSEV_ANNOUNCER_QUEUED:
		CG_AddAnnouncerEvent( parm );
		break;

	default:
		break;
	}
}





