// Copyright (C) 2001-2002 Raven Software.
//
// cg_weapons.c -- events and effects dealing with weapons

#include "cg_local.h"
#include "../game/bg_weapons.h"

// set up the appropriate ghoul2 info to a refent
void CG_SetGhoul2InfoRef( refEntity_t *ent, refEntity_t	*s1)
{
	ent->ghoul2 = s1->ghoul2;
	VectorCopy( s1->modelScale, ent->modelScale);
	ent->radius = s1->radius;
	VectorCopy( s1->angles, ent->angles);
}

/*
======================
CG_CalcMuzzlePoint
======================
*/
static qboolean	CG_CalcMuzzlePoint( int entityNum, vec3_t muzzlePoint) 
{
	weaponInfo_t	*weaponInfo;
	centity_t		*cent;
	refEntity_t		muzzle;
	int				muzzleBolt;
	attackInfo_t	*attackInfo;
	vec3_t			rootAngles;
	vec3_t			lowerTorsoAngles;
	vec3_t			upperTorsoAngles;
	vec3_t			headAngles;

	cent = CG_GetEntity ( entityNum );
	if ( !cent->currentValid ) 
	{
		return qfalse;
	}

	BG_PlayerAngles( cent->lerpAngles, 
				     NULL, 
						   
						   rootAngles,
						   lowerTorsoAngles,
						   upperTorsoAngles,
						   headAngles,
						   
						   cent->currentState.leanOffset - LEAN_OFFSET,

						   cent->pe.painTime, 
						   cent->pe.painDirection, 
						   cg.time,

						   &cent->pe.torso,
						   &cent->pe.legs,

						   cg.frametime, 
						   cent->lerpVelocity,
						   (cent->currentState.eFlags & EF_DEAD), 
						   cent->currentState.angles2[YAW],
						   cent->ghoul2 );

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

	if (!trap_G2_HaveWeGhoul2Models(cent->ghoul2))
	{	// no player model and/or no gun!
		return qfalse;
	}

	weaponInfo = &cg_weapons[cent->currentState.weapon];

	if ( cent->currentState.eFlags & EF_ALT_FIRING ) 
	{
		attackInfo = &weaponInfo->attack[ATTACK_ALTERNATE];
	}
	else
	{
		attackInfo = &weaponInfo->attack[ATTACK_NORMAL];
	}

	muzzleBolt = attackInfo->muzzleFlashBoltWorld;

	if ( muzzleBolt == -1 )
	{
		return qfalse;
	}

	if ( G2_PositionEntityOnBolt(&muzzle, 
							     cent->ghoul2, cent->pe.weaponModelSpot, muzzleBolt, 
							     cent->lerpOrigin, rootAngles, cent->modelScale))
	{	
		VectorCopy(muzzle.origin, muzzlePoint);
		return qtrue;
	}

	return qfalse;
}

/*
=============
CG_PlayerWeaponEffects

Add any weapon effects like muzzle flash or ejecting brass
=============
*/
void CG_PlayerWeaponEffects ( refEntity_t *parent, centity_t *cent, int team, vec3_t newAngles ) 
{
	weapon_t			weaponNum;
	const weaponInfo_t	*weaponInfo;
	const weaponData_t	*weaponDat;
	const attackInfo_t	*attackInfo;
	const attackData_t	*attackDat;
	refEntity_t			flash;
			
	cent->flashBoltInterface.isValid	= qfalse;
	cent->ejectBoltInterface.isValid	= qfalse;
	weaponNum = cent->currentState.weapon;	

	weaponInfo = &cg_weapons[weaponNum];
	weaponDat  = &weaponData[weaponNum];

	// Dont do this for the player unless they are in 3rd person
	if ( !cg.renderingThirdPerson )
	{
		if ( cent->currentState.number == cg.predictedPlayerState.clientNum )
		{
			return;
		}
	}

	// Make sure the player model is valid to attach to
	if ( !trap_G2_HaveWeGhoul2Models(cent->ghoul2) )
	{	
		return;
	}

	// The muzzle flash attach position needs to be updated for the duration of the effect
	if ( cg.time - cent->muzzleFlashTime > MUZZLE_FLASH_EFFECT_TIME  )
	{
		return;
	}

	// Alt fire or standard fire?
	attackInfo = &weaponInfo->attack[cent->muzzleFlashAttack];
	attackDat = &weaponDat->attack[cent->muzzleFlashAttack];

	// Position the flash entity on the muzzle flash bolt
	memset (&flash, 0, sizeof(flash));
	if ( !G2_PositionEntityOnBolt( &flash, 
								   cent->ghoul2, 
								   cent->pe.weaponModelSpot, 
								   attackInfo->muzzleFlashBoltWorld, 
								   cent->lerpOrigin, 
								   newAngles, 
								   cent->modelScale ) )
	{
		return;
	}

	// Keep the muzzle flash moving
	cent->flashBoltInterface.isValid	= qtrue;
	cent->flashBoltInterface.ghoul2		= cent->ghoul2;
	cent->flashBoltInterface.modelNum	= cent->pe.weaponModelSpot;
	cent->flashBoltInterface.boltNum	= attackInfo->muzzleFlashBoltWorld;
	
	VectorCopy ( flash.origin, cent->flashBoltInterface.origin );
	VectorCopy ( flash.axis[0], cent->flashBoltInterface.dir );
	VectorCopy ( flash.axis[0], cent->flashBoltInterface.forward );
	VectorCopy ( cent->modelScale, cent->flashBoltInterface.scale );

	// No more flash
	if ( cg.time - cent->muzzleFlashTime > MUZZLE_FLASH_TIME )
	{
		return;
	}

	// First frame of the flash?
	if ( cg.time == cent->muzzleFlashTime )
	{	
		// Ejecting brass
		if ( attackInfo->shellEject && cg_shellEjection.integer )
		{
			refEntity_t eject;

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

			// Now position the flash entity on the bolt itself.
			if ( G2_PositionEntityOnBolt( &eject, 
										  cent->ghoul2, 
										  cent->pe.weaponModelSpot, 
										  attackInfo->shellEjectBoltWorld, 
										  cent->lerpOrigin, 
										  newAngles, 
										  cent->modelScale ) )
			{
				vec3_t	angles;

				eject.axis[0][0] = -eject.axis[0][0];
				eject.axis[0][1] = -eject.axis[0][1];
				eject.axis[0][2] = -eject.axis[0][2];

				vectoangles ( eject.axis[0], angles );
				angles[YAW] += 90;
				AnglesToAxis ( angles, eject.axis );
				
				trap_FX_PlayEntityEffectID ( attackInfo->shellEject, eject.origin, eject.axis, -1, -1, -1, -1  );
			}
		}

		// Muzzle Flash
		if ( attackInfo->muzzleEffect )
		{

			if (attackDat->weaponFlags & UNLOCK_MUZZLEFLASH )
			{
				// Muzzle flash not locked to barrel.
				trap_FX_PlayEffectID(attackInfo->muzzleEffectInWorld, flash.origin, flash.axis[0], -1, -1 );
			}
			else
			{				
				trap_FX_PlayBoltedEffectID ( attackInfo->muzzleEffectInWorld, &cent->flashBoltInterface, -1, -1 );
			}
		}
	}

	// Add a dlight to the scene if it has a muzzle effect
	if ( attackInfo->muzzleEffect && weaponNum != WP_MP5 )
	{
		trap_R_AddLightToScene( flash.origin, 200, 0.6f, 0.4f, 0.2f );
	}
}

// FIMXE: This is defined in a C++ header file. 
// We need to break it up or somethin so we don't have mutliple defs.
#define GHOUL2_NORENDER 2

/*
==============
CG_StartViewAnimation

Start a view animation for the given weapon and model index
==============
*/
void CG_StartViewWeaponAnimation ( int weapon, int modelindex, int choice, TAnimInfoWeapon* aIW )
{
	weaponInfo_t	*weaponInfo;	
	int				boneMode;
	
	weaponInfo = &cg_weapons[weapon];
	if ( weaponInfo->viewG2Indexes[modelindex] == -1 )
	{
		return;
	}

	boneMode = BONE_ANIM_OVERRIDE_DEFAULT;

/*
	if (cg_animBlend.integer)
	{
		boneMode |= BONE_ANIM_BLEND;
	}
*/

	trap_G2API_SetBoneAnim( weaponInfo->viewG2Model,
						    weaponInfo->viewG2Indexes[modelindex],
							"model_root",
							aIW->mStartFrame[choice],
							aIW->mStartFrame[choice] + aIW->mNumFrames[choice],
						    boneMode,
						    50.0f / (1000.0f / aIW->mFPS[choice]) * aIW->mSpeed,
						    cg.time,
						    aIW->mStartFrame[choice],
							150);
	
}

/*
==============
CG_AnimateViewWeapon

Animation the view weapon
==============
*/
void CG_AnimateViewWeapon ( playerState_t *ps )
{
	int				i;
	int				flags;
	weaponInfo_t	*weaponInfo;	

	if( ps->pm_type == PM_SPECTATOR )
	{
		return;
	}

	for ( i = 0; i < 5; i ++ )
	{
		if ( !cg.viewWeaponAnim[i] )
		{
			continue;
		}

		CG_StartViewWeaponAnimation ( ps->weapon, i, i==0?ps->weaponAnimIdChoice:0, cg.viewWeaponAnim[i] );
		cg.viewWeaponAnim[i] = NULL;
	}

	// Need to turn on/off anything?
	if(!cg.weaponHideModels)
	{
		return;
	}

	// Ok... turn on/off models.
	weaponInfo=&cg_weapons[ps->weapon];
	for ( i=0; i < 8; i++ )
	{
		if( weaponInfo->viewG2Indexes[i] == -1 )
		{
			continue;
		}

		flags=trap_G2API_GetGhoul2ModelFlagsByIndex(weaponInfo->viewG2Model,weaponInfo->viewG2Indexes[i]);
		if(cg.weaponHideModels&(1<<i))
		{
			trap_G2API_SetGhoul2ModelFlagsByIndex(weaponInfo->viewG2Model,weaponInfo->viewG2Indexes[i],
												  flags|GHOUL2_NORENDER);
		}
		else
		{
			trap_G2API_SetGhoul2ModelFlagsByIndex(weaponInfo->viewG2Model,weaponInfo->viewG2Indexes[i],
												  flags&=~GHOUL2_NORENDER);
		}
	}
}

/*
==============
CG_UpdateViewWeaponSurfaces

Update the on/off bullet suraces of the inview weapon
==============
*/
#define MAX_VIEWWEAPON_BULLETS	6
void CG_UpdateViewWeaponSurfaces ( playerState_t* ps )
{
	int					numBullets;
	int					i;
	const weaponInfo_t	*weaponInfo;

	weaponInfo = &cg_weapons[ps->weapon];

	numBullets = ps->clip[ATTACK_NORMAL][ps->weapon];

	// When reloading just show it at zero, its easier that way
	if ( ps->weaponstate == WEAPON_RELOADING || ps->weaponstate == WEAPON_RELOADING_ALT )
	{
		numBullets = 0;
	}
	// Dont have more than the max bullets
	else if (numBullets > MAX_VIEWWEAPON_BULLETS)
	{
		// toggle all of them
		numBullets = MAX_VIEWWEAPON_BULLETS;
	}

	// Make sure its registered
	if ( !weaponInfo->registered )
	{
		CG_RegisterWeapon ( ps->weapon );
	}

	// No model, nothing to do
	if ( !weaponInfo->viewG2Model )
	{
		return;
	}

	switch ( ps->weapon )
	{
		case WP_RPG7_LAUNCHER:
			trap_G2API_SetSurfaceOnOff(weaponInfo->viewG2Model,weaponInfo->viewG2Indexes[0],"rockethead",numBullets?0:G2SURFACEFLAG_OFF);
			trap_G2API_SetSurfaceOnOff(weaponInfo->viewG2Model,weaponInfo->viewG2Indexes[0],"rockettail",numBullets?0:G2SURFACEFLAG_OFF);
			break;

		case WP_M1911A1_PISTOL:
		case WP_SILVER_TALON:
		case WP_USSOCOM_PISTOL:
		{
			int forward;
			int backward;

			if ( numBullets == 0 )
			{
				forward = G2SURFACEFLAG_OFF;
				backward = 0;
			}
			else
			{
				forward = 0;
				backward = G2SURFACEFLAG_OFF;
			}

			trap_G2API_SetSurfaceOnOff( weaponInfo->viewG2Model, weaponInfo->viewG2Indexes[0], "slide", forward );
			trap_G2API_SetSurfaceOnOff( weaponInfo->viewG2Model, weaponInfo->viewG2Indexes[0], "slidef", forward );
			trap_G2API_SetSurfaceOnOff( weaponInfo->viewG2Model, weaponInfo->viewG2Indexes[0], "slidel", forward );
			trap_G2API_SetSurfaceOnOff( weaponInfo->viewG2Model, weaponInfo->viewG2Indexes[0], "slider", forward );
			trap_G2API_SetSurfaceOnOff( weaponInfo->viewG2Model, weaponInfo->viewG2Indexes[0], "slideb", forward );
			trap_G2API_SetSurfaceOnOff( weaponInfo->viewG2Model, weaponInfo->viewG2Indexes[0], "slide_off", backward );
			trap_G2API_SetSurfaceOnOff( weaponInfo->viewG2Model, weaponInfo->viewG2Indexes[0], "slidef_off", backward );
			trap_G2API_SetSurfaceOnOff( weaponInfo->viewG2Model, weaponInfo->viewG2Indexes[0], "slidel_off", backward );
			trap_G2API_SetSurfaceOnOff( weaponInfo->viewG2Model, weaponInfo->viewG2Indexes[0], "slider_off", backward );
			trap_G2API_SetSurfaceOnOff( weaponInfo->viewG2Model, weaponInfo->viewG2Indexes[0], "slideb_off", backward );

			break;
		}

		case WP_M60_MACHINEGUN:
			// Turn on whats available
			for ( i = 0; i < numBullets; i++ )
			{
				trap_G2API_SetSurfaceOnOff( weaponInfo->viewG2Model, weaponInfo->viewG2Indexes[0], va("bullet%d", i + 1), 0 );
			}
			
			// Turn the rest off
			for (; i < MAX_VIEWWEAPON_BULLETS; i++)
			{
				trap_G2API_SetSurfaceOnOff( weaponInfo->viewG2Model, weaponInfo->viewG2Indexes[0], va("bullet%d", i + 1), G2SURFACEFLAG_OFF );
			}	
			break;
	}
}

/*
==============
CG_AddViewWeapon

Add the weapon, and flash for the player's view
==============
*/
void CG_AddViewWeapon(playerState_t *ps)
{
	refEntity_t			gun;
	refEntity_t			flash;
	centity_t			*cent;
	float				fovOffset;
	const weaponInfo_t	*weaponInfo;
	const weaponData_t	*weaponDat;
	const attackInfo_t	*attackInfo;
	const attackData_t	*attackDat;
	vec3_t				angles;
	int					delta;
	vec3_t				forward;
	vec3_t				vangles;
	int					speed;

	cg.flashBoltInterface.isValid = qfalse;

	if(ps->pm_type == PM_SPECTATOR || ps->persistant[PERS_TEAM] == TEAM_SPECTATOR )
	{
		return;
	}

	if(ps->pm_type == PM_INTERMISSION)
	{
		return;
	}

	// No gun if in third person view or a camera is active
	//if ( cg.renderingThirdPerson || cg.cameraMode) {
	if(cg.renderingThirdPerson)
	{
		return;
	}

	cent = &cg_entities[ps->clientNum]; 

	// allow the gun to be completely removed
	if( !cg_drawGun.integer || (ps->pm_flags & PMF_ZOOMED) )
	{
		return;
	}

	// drop gun lower at higher fov
	if ( cg_fov.integer > 90 )
	{
		fovOffset = -0.2 * ( cg_fov.integer - 90 );
	} 
	else 
	{
		fovOffset = 0;
	}

	CG_RegisterWeapon(ps->weapon);
	weaponInfo = &cg_weapons[ps->weapon];
	weaponDat  = &weaponData[ps->weapon];

	// Add the weapon and hands models.	
	memset(&gun,0,sizeof(gun));
	gun.ghoul2 = weaponInfo->viewG2Model;
	if(!trap_G2_HaveWeGhoul2Models(gun.ghoul2))
	{	
		// No weapon to draw!
		return;
	}

	VectorCopy ( cg.refdef.vieworg, gun.origin );

	if ( cg.predictedPlayerState.stats[STAT_USEWEAPONDROP] )
	{
		VectorMA ( gun.origin, (-20.0f * (float)cg.predictedPlayerState.stats[STAT_USEWEAPONDROP]/300.0f), cg.refdef.viewaxis[0], gun.origin );
		VectorMA ( gun.origin, (-20.0f * (float)cg.predictedPlayerState.stats[STAT_USEWEAPONDROP]/300.0f), cg.refdef.viewaxis[2], gun.origin );
	}

	// Add movement bobbing 
	gun.origin[2] += cg.xyspeed * cg.bobfracsin * 0.0015f;

	// Add landing offset
	delta = cg.time - cg.landTime;
	if ( delta < LAND_DEFLECT_TIME ) 
	{
		gun.origin[2] += cg.landChange * 0.25f * delta / LAND_DEFLECT_TIME;
	} 
	else if ( delta < LAND_DEFLECT_TIME + LAND_RETURN_TIME ) 
	{
		gun.origin[2] += cg.landChange * 0.25f * (LAND_DEFLECT_TIME + LAND_RETURN_TIME - delta) / LAND_RETURN_TIME;
	}

	// Add movement offset
	speed = sqrt( cg.predictedPlayerState.velocity[0] * cg.predictedPlayerState.velocity[0] +
				  cg.predictedPlayerState.velocity[1] * cg.predictedPlayerState.velocity[1] +
				  cg.predictedPlayerState.velocity[2] * cg.predictedPlayerState.velocity[2]);

	vectoangles ( cg.predictedPlayerState.velocity, vangles );
	vangles[1] += (360 - cg.predictedPlayerState.viewangles[1]);
	vangles[2] += (360 - cg.predictedPlayerState.viewangles[2]);
	AngleVectors ( vangles, forward, NULL, NULL);	

	VectorScale ( forward, speed, forward );

	VectorMA( gun.origin, forward[1] * 0.003f, cg.refdef.viewaxis[1], gun.origin );

	// Set model scale	
	VectorSet ( gun.modelScale,weaponParseInfo[ps->weapon].mForeshorten,1.0f,1.0f );

	vectoangles(cg.refdef.viewaxis[0],angles);
	AnglesToAxis(angles,gun.axis);
	CG_ScaleModelAxis(&gun);

	VectorCopy(gun.origin,gun.oldorigin);
	gun.renderfx=RF_DEPTHHACK|RF_FIRST_PERSON|RF_MINLIGHT|RF_NO_FOG;	
	gun.radius=64;

	trap_R_AddRefEntityToScene(&gun);

	// The muzzle flash attach position needs to be updated for the duration of the effect
	if ( cg.time - cent->muzzleFlashTime > MUZZLE_FLASH_EFFECT_TIME )
	{
		return;
	}

	// Grab the proper attack info
	if(cent->muzzleFlashAttack )
	{
		attackInfo = &weaponInfo->attack[ATTACK_ALTERNATE];
		attackDat  = &weaponDat->attack[ATTACK_ALTERNATE];
	}
	else
	{
		attackInfo = &weaponInfo->attack[ATTACK_NORMAL];
		attackDat  = &weaponDat->attack[ATTACK_NORMAL];
	}

	// Update the muzzle flashes origins
	memset(&flash,0,sizeof(flash));
	VectorCopy ( gun.modelScale, flash.modelScale );
	if(!G2_PositionEntityOnBolt( &flash,
								 gun.ghoul2, 
								 1,
								 attackInfo->muzzleFlashBoltView, 
								 gun.origin,
								 angles,
								 flash.modelScale ) )
	{
		return;
	}

	// Keep the muzzle flash origins updated
	cg.flashBoltInterface.isValid	= qtrue;
	cg.flashBoltInterface.ghoul2	= gun.ghoul2;
	cg.flashBoltInterface.modelNum	= 1;
	cg.flashBoltInterface.boltNum	= attackInfo->muzzleFlashBoltView;
	
	VectorCopy(flash.axis[0],cg.flashBoltInterface.dir );
	VectorCopy(flash.axis[0],cg.flashBoltInterface.forward );
	VectorCopy(flash.origin,cg.flashBoltInterface.origin);	
	VectorCopy(flash.modelScale,cg.flashBoltInterface.scale);

	// If no muzzle flash to handle then there is nothing left to do
	if( cg.time - cent->muzzleFlashTime > MUZZLE_FLASH_TIME )
	{
		return;
	}

	// First frame for the muzzle flash is when the effects are added
	if ( cg.time == cent->muzzleFlashTime )
	{
		// Add the muzzle flash
		if( attackInfo->muzzleEffect )
		{
			// Handle locked and unlocked muzzle flashes
			if(attackDat->weaponFlags & UNLOCK_MUZZLEFLASH )
			{
				trap_FX_PlayEffectID(attackInfo->muzzleEffect,flash.origin,flash.axis[0], -1, -1 );
			}
			else
			{
				trap_FX_PlayBoltedEffectID(attackInfo->muzzleEffect,&cg.flashBoltInterface, -1, -1 );
			}
		}

		// Add shell ejection
		if ( attackInfo->shellEject && cg_shellEjection.integer )
		{
			refEntity_t	shellEject;

			// Handle muzzle flashes
			memset(&shellEject,0,sizeof(shellEject));

			if(G2_PositionEntityOnBolt( &shellEject,
									    weaponInfo->viewG2Model,
									    1,
									    attackInfo->shellEjectBoltView,
									    cg.refdef.vieworg,
									    cg.refdef.viewangles,
									    gun.modelScale ) )
			{
				// Flip the forward axis so it goes backwards rather than forwards
				shellEject.axis[0][0] = -shellEject.axis[0][0];
				shellEject.axis[0][1] = -shellEject.axis[0][1];
				shellEject.axis[0][2] = -shellEject.axis[0][2];

				// Play the entity with a full axis
				trap_FX_PlayEntityEffectID(attackInfo->shellEject,shellEject.origin,shellEject.axis, -1, -1, -1, -1  );
			}
		}

		// Add tracers
		if ( attackInfo->tracerEffect && !(attackDat->projectileLifetime & PROJECTILE_FIRE))
		{
			// Lower the amount of tracers
			if ( rand()%100 < (cg_tracerChance.value * 100.0f) )
			{
				vec3_t origin;
				VectorMA ( flash.origin, 500, gun.axis[0], origin );
				trap_FX_PlayEffectID ( attackInfo->tracerEffect, origin, gun.axis[0], -1, -1  );
			}			
		}
	}

	// Add a dlight when there is a muzzle effect
	if ( attackInfo->muzzleEffect && ps->weapon != WP_MP5 )
	{
		trap_R_AddLightToScene( flash.origin, 200, 0.6f, 0.4f, 0.2f );
	}
}

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

WEAPON SELECTION

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

/*
===============
CG_WeaponSelectable
===============
*/
qboolean CG_WeaponSelectable( int i, qboolean allowEmpty ) 
{
	int ammo;

	if ( ! (cg.predictedPlayerState.stats[ STAT_WEAPONS ] & ( 1 << i ) ) ) 
	{
		return qfalse;
	}

	if ( allowEmpty && (cg.predictedPlayerState.pm_flags & PMF_LIMITED_INVENTORY) )
	{
		return qtrue;
	}

	if ( BG_WeaponHasAlternateAmmo ( i ) )
	{
		if ( cg.predictedPlayerState.ammo[ weaponData[i].attack[ATTACK_ALTERNATE].ammoIndex ] ||
			 cg.predictedPlayerState.clip[ATTACK_ALTERNATE][ i ] )
		{
			return qtrue;
		}
	}

	// Start with normal ammo
	ammo = 0;
	ammo += cg.predictedPlayerState.clip[ATTACK_NORMAL][i];
	ammo += cg.predictedPlayerState.ammo[weaponData[i].attack[ATTACK_NORMAL].ammoIndex];

	if ( !ammo )
	{
		return qfalse;
	}

	return qtrue;
}

/*
===============
CG_NextWeapon

selects the next weapon in the players inventory
===============
*/
void CG_NextWeapon ( qboolean allowEmpty, int exclude ) 
{
	int		i;
	int		original;
	int		selected;

	if ( !cg.snap ) 
	{
		return;
	}
	
	if ( cg.predictedPlayerState.stats[STAT_USEWEAPONDROP] )
	{
		return;
	}

	if ( cg.snap->ps.pm_flags & PMF_FOLLOW ) 
	{
		return;
	}

	if ( cg.predictedPlayerState.weaponstate == WEAPON_CHARGING     || 
	     cg.predictedPlayerState.weaponstate == WEAPON_CHARGING_ALT    ) 
	{
		return;
	}

	// When the weapon select menu is up the next and prev move through it
	if ( cg.weaponMenuUp )
	{
		selected = cg.weaponMenuSelect;
	}
	else
	{
		selected = cg.weaponSelect;
	}

	original = selected;

	for ( i = WP_NONE + 1 ; i < WP_NUM_WEAPONS; i++ ) 
	{
		selected++;
		if ( selected == WP_NUM_WEAPONS ) 
		{
			selected = WP_NONE + 1;
		}
		if ( selected != exclude && CG_WeaponSelectable( selected, allowEmpty ) ) 
		{
			break;
		}
	}

	if ( i == WP_NUM_WEAPONS ) 
	{
		selected = original;
	}

	// When the weapon select menu is up the next and prev move through it
	if ( cg.weaponMenuUp )
	{
		cg.weaponMenuSelect = selected;
	}
	else
	{
		cg.weaponSelect = selected;
	}
}

/*
===============
CG_PrevWeapon

Selects the previous weapon in the players inventory
===============
*/
void CG_PrevWeapon ( qboolean allowEmpty, int exclude ) 
{
	int		i;
	int		original;
	int		selected;

	if ( !cg.snap ) 
	{
		return;
	}

	if ( cg.predictedPlayerState.stats[STAT_USEWEAPONDROP] )
	{
		return;
	}

	if ( cg.snap->ps.pm_flags & PMF_FOLLOW ) 
	{
		return;
	}

	if ( cg.predictedPlayerState.weaponstate == WEAPON_CHARGING     || 
	     cg.predictedPlayerState.weaponstate == WEAPON_CHARGING_ALT    ) 
	{
		return;
	}

	// When the weapon select menu is up the next and prev move through it
	if ( cg.weaponMenuUp )
	{
		selected = cg.weaponMenuSelect;
	}
	else
	{
		selected = cg.weaponSelect;
	}

	original = selected;

	for ( i = WP_NONE + 1 ; i < WP_NUM_WEAPONS ; i++ ) 
	{
		selected--;
		if ( selected == WP_NONE ) 
		{
			selected = WP_NUM_WEAPONS-1;
		}
		if ( selected != exclude && CG_WeaponSelectable( selected, allowEmpty ) ) 
		{
			break;
		}
	}

	if ( i == WP_NUM_WEAPONS ) 
	{
		selected = original;
	}

	// When the weapon select menu is up the next and prev move through it
	if ( cg.weaponMenuUp )
	{
		cg.weaponMenuSelect = selected;
	}
	else
	{
		cg.weaponSelect = selected;
	}
}

/*
===============
CG_Weapon_f
===============
*/
void CG_Weapon_f( void ) 
{
	int		num;
	int		category;
	int		last;

	if ( !cg.snap ) 
	{
		return;
	}

	if ( cg.predictedPlayerState.stats[STAT_USEWEAPONDROP] )
	{
		return;
	}

	if ( (cg.snap->ps.pm_flags & PMF_FOLLOW) ) 
	{
		return;
	}

	if ( cg.predictedPlayerState.weaponstate == WEAPON_CHARGING     || 
	     cg.predictedPlayerState.weaponstate == WEAPON_CHARGING_ALT    ) 
	{
		return;
	}

	category = atoi( CG_Argv( 1 ) );

	if (category < 0 || category > CAT_MAX) 
	{
		return;
	}

	if ( !cg_weaponMenuFast.integer )
	{
		if ( !cg.weaponMenuUp )
		{
			cg.weaponMenuSelect = cg.predictedPlayerState.weapon;
			cg.weaponMenuUp = qtrue; // show weapon menu
		}
	}
	else
	{
		cg.weaponMenuSelect = cg.predictedPlayerState.weapon;
	}

	last = -1;
	if (category == weaponData[cg.weaponMenuSelect].category)
	{
		last = cg.weaponMenuSelect;	
	}

	// Find next weapon in currently active category.
	for (num = WP_KNIFE; num < WP_NUM_WEAPONS; num++)
	{
		if(num>last && category==weaponData[num].category && (cg.predictedPlayerState.stats[STAT_WEAPONS]&(1<<num)))
		{
			if ( (cg.predictedPlayerState.stats[ STAT_WEAPONS ] & ( 1 << num ) ) ) 
			{
				break;
			}
		}
	}
	
	// Ok... wrap around.. and try again.
	if (WP_NUM_WEAPONS == num)
	{
		for (num = WP_KNIFE; num < WP_NUM_WEAPONS; num++)
		{
			if(num!=last && category == weaponData[num].category && (cg.snap->ps.stats[STAT_WEAPONS]&(1<<num)))
			{
				if ( (cg.predictedPlayerState.stats[ STAT_WEAPONS ] & ( 1 << num ) ) ) 
				{
					break;
				}
			}
		}
	}

	if (WP_NUM_WEAPONS == num)
	{	
		// Couldn't find one!
		return;
	}

	if ( !cg_weaponMenuFast.integer )
	{
		cg.weaponMenuSelect = num;
	}
	else
	{
		cg.weaponSelect = num;
	}
}

/*
===================
CG_OutOfAmmoChange

The current weapon has just run out of ammo
===================
*/
void CG_OutOfAmmoChange( int weapon ) 
{
	// Get the best weapon thats not a grenade
	cg.weaponSelect = WP_M84_GRENADE;
	CG_PrevWeapon ( qfalse, weapon );
}

/*
================
CG_PredictedBullet

Fires a bullet client side and produces all the effects that would be produced
by the server if it had done it
================
*/
void CG_PredictedBullet ( centity_t* cent, attackType_t attack )
{
	vec3_t			fireAngs;
	float			inaccuracy;
	qboolean		detailed;
	vec3_t			end;
	vec3_t			start;
	trace_t			tr;
	int				i;
	int				pellets;
	entityState_t	*ent;
	int				seed;

	ent = &cent->currentState;

	// Dont bother if antilag is turned off or its a projectile weapon
	if ( cg_antiLag.integer < 1 || cg_synchronousClients.integer || !cg_impactPrediction.integer || (weaponData[ent->weapon].attack[attack].weaponFlags & PROJECTILE_FIRE))
	{
		return;
	}

	// Calculate the muzzle point
	VectorCopy( cg.predictedPlayerState.origin, start );
	start[2] += cg.predictedPlayerState.viewheight;

	CG_UpdateViewWeaponSurfaces ( &cg.predictedPlayerState );

	detailed = qtrue;
	pellets = (weaponData[ent->weapon].attack[attack].pellets / 2) + 1;
	pellets = weaponData[ent->weapon].attack[attack].pellets;

	// Handle leaning
	VectorCopy(cg.predictedPlayerState.viewangles, fireAngs);
	if ( cg.predictedPlayerState.pm_flags & PMF_LEANING )
	{
		BG_ApplyLeanOffset ( &cg.predictedPlayerState, start );
	}

	seed = cg.predictedPlayerState.stats[STAT_SEED];

	// Current inaccuracy
	inaccuracy = (float)cg.predictedPlayerState.inaccuracy / 1000.0f;
	if ( detailed )
	{
		if ( cg.predictedPlayerState.pm_flags & PMF_JUMPING )
		{
			if ( ent->weapon == WP_MSG90A1 && cg_realisticSniper.integer )
			{
				inaccuracy *= JUMP_SNIPER_ACCURACY_MODIFIER;
			}
			else
			{
				inaccuracy *= JUMP_ACCURACY_MODIFIER;
			}
		}
		else if ( VectorLength(cg.predictedPlayerState.velocity) > (cg.predictedPlayerState.speed * 0.75f) )
		{
			if ( ent->weapon == WP_MSG90A1 && cg_realisticSniper.integer )
			{
				inaccuracy *= RUN_SNIPER_ACCURACY_MODIFIER;
			}
		}
		else if ( cg.predictedPlayerState.pm_flags & PMF_DUCKED )
		{
			inaccuracy *= DUCK_ACCURACY_MODIFIER;
		}
	}

	for ( i = 0; i < pellets; i ++ )
	{
		BG_CalculateBulletEndpoint ( start, fireAngs, inaccuracy, weaponData[ent->weapon].attack[attack].rV.range, end, &seed );

		// See if it hit a wall
		CG_Trace ( &tr, start, NULL, NULL, end, cent->currentState.number, MASK_SHOT&~CONTENTS_BODY );

		if ( tr.fraction >= 0.0f && tr.fraction <= 1.0f )
		{
			VectorCopy ( tr.endpos, end );
		}

		CG_PlayerTrace ( &tr, start, end, cent->currentState.number );			

		if ( tr.entityNum >= cgs.maxclients && tr.entityNum != ENTITYNUM_NONE )
		{		
			CG_Bullet( tr.endpos, ent->number, ent->weapon, tr.plane.normal, ENTITYNUM_WORLD, 
					   tr.surfaceFlags & MATERIAL_MASK,
					   attack );
		}			
#ifdef _SOF2_FLESHIMPACTPREDICTION
		else if ( tr.entityNum < cgs.maxclients )
		{
			qboolean blood = qtrue;
			vec3_t	 dir;

			if ( cg_impactPrediction.integer < 2 )
			{
				return;
			}
			// If invulerable then no blood
			if ( cg_entities[tr.entityNum].currentState.eFlags & EF_INVULNERABLE )
			{
				blood = qfalse;
			}
			// If on the same team in friendly fire then no blood
			else if ( cgs.gametypeData->teams && !cgs.friendlyFire && cgs.clientinfo[tr.entityNum].team == cgs.clientinfo[cg.snap->ps.clientNum].team )
			{
				blood = qfalse;
			}

			VectorSubtract ( end, start, dir );
			VectorNormalize ( dir );

			// If no blood then just make a little white puff
			if ( !blood || cg_lockBlood.integer )
			{
				CG_Bullet( tr.endpos, ent->number, ent->weapon, dir, ENTITYNUM_WORLD, 0, attack );
			}
			else
			{
				CG_PredictedProcGore ( ent->weapon, attack, start, end, &cg_entities[tr.entityNum] );

				CG_MissileHitPlayer( ent->weapon, tr.endpos, dir, tr.entityNum, qfalse );
			}
		}
#endif
	}
}

/*
================
CG_FireWeapon

Caused by an EV_FIRE_WEAPON event
================
*/
void CG_FireWeapon( centity_t *cent, attackType_t attack ) 
{
	entityState_t	*ent;
	int				c;
	weaponInfo_t	*weaponInfo;
	attackInfo_t	*attackInfo;

	ent = &cent->currentState;
	
	if ( ent->weapon == WP_NONE ) 
	{
		return;
	}

	if ( ent->weapon >= WP_NUM_WEAPONS ) 
	{
		Com_Error( ERR_FATAL, "CG_FireWeapon: ent->weapon >= WP_NUM_WEAPONS" );
		return;
	}

	weaponInfo = &cg_weapons[ ent->weapon ];
	attackInfo = &weaponInfo->attack[attack];

	// mark the entity as muzzle flashing, so when it is added it will
	// append the flash to the weapon model
	cent->muzzleFlashTime   = cg.time;
	cent->muzzleFlashAttack = attack;

	// play a sound
	for ( c = 0 ; c < 4 ; c++ )
	{
		if ( !attackInfo->flashSound[c] )
		{
			break;
		}
	}

	if ( c > 0 )
	{
		c = rand() % c;
		if ( attackInfo->flashSound[c] )
		{
			int radius = 2000;

			if ( ent->weapon == WP_MP5 && cent->currentState.number != cg.snap->ps.clientNum)
			{
				radius = 1250;
			}

			trap_S_StartSound( NULL, ent->number, CHAN_WEAPON, attackInfo->flashSound[c], -1, radius );
		}
	}

	// Handle dissapearing bullets if this is the main guy firing
	if ( cent->currentState.number == cg.snap->ps.clientNum && cg.hitModel )
	{
		CG_PredictedBullet ( cent, attack );
	}
}

/*
================
CG_ProjectileThink

Create trail fx for projectiles
================
*/
void CG_ProjectileThink( centity_t *cent, int weaponNum )
{
	const weaponInfo_t	*weaponInfo;
	const weaponData_t	*weaponDat;
	vec3_t				forward;
	int					trailEffectId;
	
	weaponInfo = &cg_weapons[weaponNum];
	weaponDat = &weaponData[weaponNum];

	if ( cent->currentState.eFlags & EF_ALT_FIRING )
	{
		trailEffectId = weaponInfo->attack[ATTACK_ALTERNATE].tracerEffect;
	}
	else
	{
		trailEffectId = weaponInfo->attack[ATTACK_NORMAL].tracerEffect;
	}

	// use tracer effect for projectile trails
	if (trailEffectId)
	{	
		// only calc normalize if there IS an effect
		if ( VectorNormalize2( cent->currentState.pos.trDelta, forward ) == 0.0f )
		{
			forward[2] = 1.0f;
		}

		trap_FX_PlayEffectID(trailEffectId, cent->lerpOrigin, forward, -1, -1  );
	}
}

/*
=================
CG_FlashBang

Blinds a player and makes them loose sound for a bit
=================
*/

#define	MAX_FLASHBANG_AFFECT_DISTANCE	1750
#define MAX_FLASHBANG_DISTANCE			3000
#define MAX_FLASHBANG_TIME				11000

void CG_FlashBang ( vec3_t origin, vec3_t dir )
{
	trace_t trace;
	vec3_t	start;
	vec3_t	end;
	vec3_t	delta;
	float	distance;
	vec3_t	fromangles;
	int		angle;
	int		time;

	// Dont flash grenade dead people
	if ( (cg.predictedPlayerState.pm_flags & (PMF_GHOST|PMF_FOLLOW)) || cg.predictedPlayerState.pm_type != PM_NORMAL )
	{
		return;
	}

	VectorCopy ( origin, start );
	start[2] += cg.snap->ps.viewheight;				

	VectorCopy ( cg.refdef.vieworg, end );
	end[2] += cg.snap->ps.viewheight;				

	VectorSubtract( cg.refdef.vieworg, origin, delta );
					
	// distance to the flash bang
	distance = VectorLength( delta );

	// Make sure its not too far away
	if(distance > MAX_FLASHBANG_DISTANCE )
	{
		return;
	}

	// Can the player see the flashbang?
	CG_Trace ( &trace, start, NULL, NULL, end, ENTITYNUM_NONE, MASK_SHOT ); 
	if ( trace.contents & (CONTENTS_SOLID|CONTENTS_TERRAIN) ) 
	{
		return;
	}

	VectorNormalize ( delta );		
	vectoangles ( delta, fromangles );

	angle =  cg.snap->ps.viewangles[1] - fromangles[1];
	angle += 180;

	if(angle < 0)
	{
		angle += 360;
	}

	// add distance if not facing the grenade.. 
	if(!(angle > 300 || angle < 60 )) 
	{
		angle = 120 - abs(angle-180);
		distance += (MAX_FLASHBANG_AFFECT_DISTANCE * angle / 240);
	}

	if(distance > MAX_FLASHBANG_AFFECT_DISTANCE - 50 )
	{
		distance = MAX_FLASHBANG_AFFECT_DISTANCE - 50;				
	}

	distance = MAX_FLASHBANG_AFFECT_DISTANCE - distance;
	time	 = MAX_FLASHBANG_TIME;

	cg.flashbangTime     = cg.time;
	cg.flashbangFadeTime = (MAX_FLASHBANG_TIME * distance / MAX_FLASHBANG_AFFECT_DISTANCE);
	cg.flashbangAlpha    = 1.0f * distance / MAX_FLASHBANG_AFFECT_DISTANCE;

	// If NVG or thermals were on then blast them a bit more
	if ( (cg.predictedPlayerState.pm_flags & PMF_GOGGLES_ON) )
	{
		cg.flashbangFadeTime += (cg.flashbangFadeTime / 4);
	}
}

/*
=================
CG_MissileHitWall

Caused by an EV_MISSILE_MISS event, or directly by local bullet tracing
=================
*/
void CG_MissileHitWall (
 	int			 weapon, 
	vec3_t		 origin, 
	vec3_t		 dir, 
	int			 material, 
	attackType_t attack
	)	 
{
	qhandle_t	impactEffect;
	int			ammoIndex;

	// Flash grenades are handled very differnetly
	if ( weapon == WP_M84_GRENADE )
	{
		CG_FlashBang ( origin, dir );
	}

	// Melee is handled specialol, the standard bullet routines are used
	// to handle hit detection and what not, but the effects are handled 
	// separately.
	if ( weaponData[weapon].attack[attack].melee )
	{
		impactEffect = trap_MAT_GetEffect( (char*)weaponData[weapon].attack[attack].melee, material&MATERIAL_MASK);
		if (impactEffect)
		{
			trap_FX_PlayEffectID( impactEffect, origin, dir, -1, -1  );
		}

		return;
	}

	ammoIndex = weaponData[weapon].attack[attack].ammoIndex;

	switch( ammoIndex )
	{
		default:
		case AMMO_KNIFE:
		case AMMO_045:
		case AMMO_556:
		case AMMO_9  :
		case AMMO_12 :
		case AMMO_762:
			impactEffect = trap_MAT_GetEffect(ammoData[ammoIndex].name, material&MATERIAL_MASK);
			if (impactEffect)
			{
				trap_FX_PlayEffectID( impactEffect, origin, dir, -1, -1  );
			}
			break;

		case AMMO_40:
		case AMMO_M15:
		case AMMO_M84:
		case AMMO_RPG7:
		case AMMO_SMOHG92:
		case AMMO_ANM14:

			impactEffect = trap_MAT_GetEffect(ammoData[ammoIndex].name, material&MATERIAL_MASK);
			if (impactEffect)
			{			
				trap_FX_PlayEffectID( impactEffect, origin, dir, -1, -1  );
			}
		
			// grenade!
			trap_FX_PlayEffectID( cg_weapons[weapon].attack[attack].explosionEffect, origin, dir, -1, 3000  );

			break;
	}
}


/*
=================
CG_MissileHitPlayer
=================
*/
void CG_MissileHitPlayer (
	int			 weapon, 
	vec3_t		 origin, 
	vec3_t		 dir, 
	int			 entityNum,
	attackType_t attack
	) 
{
	qhandle_t	impactEffect = 0;
	int			ammoIndex;
	
	ammoIndex = weaponData[weapon].attack[attack].ammoIndex;

	switch( ammoIndex )
	{
		default:
		case AMMO_KNIFE:
		case AMMO_045:
		case AMMO_556:
		case AMMO_9  :
		case AMMO_12 :
		case AMMO_762:

			if ( entityNum == cg.clientNum )
			{	// The person hit is the local player.
				if (!cg_lockBlood.integer)
				{	// Only play the exit wound effect
					trap_FX_PlayEffectID( cgs.media.playerFleshImpactEffect, origin, dir, -1, -1  );
				}
			}
			else if (cg_lockBlood.integer)
			{	// Only play the generic hit effect.
				trap_FX_PlayEffectID( trap_MAT_GetEffect(ammoData[ammoIndex].name, MATERIAL_NONE), 
									origin, dir, -1, -1  );
			}
			else
			{
				trap_FX_PlayEffectID( cgs.media.mBloodSmall, origin, dir, -1, -1  );
				trap_FX_PlayEffectID( cgs.media.playerFleshImpactEffect, origin, dir, -1, -1  );	// Exit wound too.
			}

			break;

		case AMMO_RPG7:
		case AMMO_40:
		case AMMO_M15:
		case AMMO_M84:
		case AMMO_SMOHG92:
		case AMMO_ANM14:
			if (cg_lockBlood.integer)
			{	// Play the generic hit effect.
				impactEffect = trap_MAT_GetEffect(ammoData[ammoIndex].name, MATERIAL_NONE);
			}
			else
			{
				impactEffect = cgs.media.mBloodSmall; // trap_MAT_GetEffect(ammoData[ammoIndex].name, MATERIAL_FLESH);
			}

			if (impactEffect)
			{
				trap_FX_PlayEffectID( impactEffect, origin, dir, -1, 3000 );
			}

			trap_FX_PlayEffectID( cg_weapons[weapon].attack[attack].explosionEffect, origin, dir, -1, 3000 );
/*
			trap_S_StartSound(origin, ENTITYNUM_WORLD, CHAN_WEAPON, cg_weapons[weapon].attack[attack].explosionSound, -1, -1);
*/

			break;
	}
}

void CG_HandleStickyMissile(centity_t *cent,entityState_t *es,vec3_t dir,int targetEnt)
{
	int				hitLoc;
	qboolean		altFire;
	int				ammoIndex;
	weaponInfo_t	*weaponInfo;
	qboolean		addBoltedMiss;
	int				missileIndex;
	int				boltIndex;

	hitLoc=es->otherEntityNum2;
	altFire=(cent->currentState.eFlags & EF_ALT_FIRING)!=0;

	if ( cent->currentState.eFlags & EF_ALT_FIRING )
	{
		ammoIndex = weaponData[es->weapon].attack[ATTACK_ALTERNATE].ammoIndex;
	}
	else
	{
		ammoIndex = weaponData[es->weapon].attack[ATTACK_NORMAL].ammoIndex;
	}
	
	// Currently the only type of sticky missile is the thrown knife. Others might be added in future though.
	addBoltedMiss=qfalse;
	switch(es->weapon)
	{
		case AMMO_KNIFE:
			if(altFire)
			{
				addBoltedMiss=qtrue;
			}
			break;

		default:
			break;
	}

	if(addBoltedMiss==qtrue)
	{
		centity_t* centTarget;

		weaponInfo=&cg_weapons[es->weapon];
		if(!trap_G2_HaveWeGhoul2Models(weaponInfo->weaponG2Model))
		{
			return;
		}

		centTarget = CG_GetEntity ( targetEnt );

		missileIndex=trap_G2API_CopySpecificGhoul2Model(weaponInfo->weaponG2Model,0,centTarget->ghoul2,-1);
		if(missileIndex!=-1)
		{
//			Com_Printf("Missile index=%i\n",missileIndex);
			boltIndex=trap_G2API_FindBoltIndex(centTarget->ghoul2,0,"rhand");
			if(boltIndex==-1)
			{
				boltIndex=trap_G2API_AddBolt(centTarget->ghoul2,0,"rhand");
			}
			if(boltIndex!=-1)
			{
//				Com_Printf("Bolt index=%i\n",boltIndex);
				if(!trap_G2API_AttachG2Model(centTarget->ghoul2,missileIndex,centTarget->ghoul2,boltIndex,0))
				{
//					Com_Printf("Couldn't attach!\n");
				}
			}
		}
	}
}

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

BULLETS

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


/*
===============
CG_Tracer
===============
*/
void CG_Tracer(int tracerEffectID, vec3_t source, vec3_t dest ) 
{
	vec3_t		forward;
	float		len;
	float		chance;

	if ( !tracerEffectID )
	{
		return;
	}

	// Lower the amount of tracers
	chance = cg_tracerChance.value * 100.0f;
	if ( rand()%100 > chance )
	{
		return;
	}

	VectorSubtract( dest, source, forward );
	len = VectorNormalize( forward );
	if (len > 100)
	{
		VectorMA( source, 100, forward, source );
		trap_FX_PlayEffectID( tracerEffectID, source, forward, -1, -1  );
	}
}

/*
======================
CG_Bullet

Renders bullet effects.
======================
*/
void CG_Bullet( 
	vec3_t		 end, 
	int			 sourceEntityNum, 
	int			 weapon, 
	vec3_t		 normal, 
	int			 fleshEntityNum, 
	int			 material,
	attackType_t attack
	)
{
	trace_t trace;
	int		sourceContentType;
	int		destContentType;
	vec3_t	start;

	// if the shooter is currently valid, calc a source point and possibly
	// do trail effects
	if ( sourceEntityNum >= 0 )
	{
		if ( CG_CalcMuzzlePoint( sourceEntityNum, start ) )
		{
			int	tracerEffectID = cg_weapons[weapon].attack[attack].tracerEffect;;
		
			if (0 != tracerEffectID ) 
			{
				sourceContentType = trap_CM_PointContents( start, 0 );
				destContentType = trap_CM_PointContents( end, 0 );

				// do a complete bubble trail if necessary
				if ( ( sourceContentType == destContentType ) && ( sourceContentType & CONTENTS_WATER ) ) {
					CG_BubbleTrail( start, end, 32 );
				}
				// bubble trail from water into air
				else if ( ( sourceContentType & CONTENTS_WATER ) ) {
					trap_CM_BoxTrace( &trace, end, start, NULL, NULL, 0, CONTENTS_WATER );
					CG_BubbleTrail( start, trace.endpos, 32 );
				}
				// bubble trail from air into water
				else if ( ( destContentType & CONTENTS_WATER ) ) {
					trap_CM_BoxTrace( &trace, start, end, NULL, NULL, 0, CONTENTS_WATER );
					CG_BubbleTrail( trace.endpos, end, 32 );
				}

				// Tracers on enemies only
				if ( sourceEntityNum != cg.snap->ps.clientNum || cg.renderingThirdPerson )
				{
					// MP5's have no tracers
					CG_Tracer(tracerEffectID, start, end );
				}
			}
		}

		if ( sourceEntityNum != cg.snap->ps.clientNum )
		{
			// Melee attacks should not cause the bullet fly by sound
			if ( !weaponData[weapon].attack[attack].melee )
			{
				CG_BulletFlyBySound (start, end);
			}
		}
	}

	// impact splash and mark
	if ( fleshEntityNum != ENTITYNUM_WORLD ) 
	{
		CG_MissileHitPlayer(weapon, end, normal, fleshEntityNum, qfalse); 
	}
	else 
	{
		CG_MissileHitWall( weapon, end, normal, material, attack );
	}
}

/*
===============
CG_StartModelAnims
===============
*/
static void CG_StartModelAnims ( TAnimWeapon *aW, playerState_t *ps )
{
	TAnimInfoWeapon *aIW;

	if(!aW)
	{
		assert(0);
	}

	cg.weaponHideModels=-1;

	// Loop for all models affected by this anim.
	aIW=aW->mInfos;
	while(aIW)
	{
		// Ignore if no anims available.
		if ( !aIW->mNumChoices )
		{
			aIW=aIW->mNext;
			continue;
		}

		if(!Q_stricmp(aIW->mType,"hands"))
		{
			// Hands... now which one?
			if(!Q_stricmp(aIW->mName,"right"))
			{
				// Right.
				cg.viewWeaponAnim[2] = aIW;	
				cg.weaponHideModels&=~(1<<2);
			}
			else if(!Q_stricmp(aIW->mName,"left"))
			{
				// Left.
				cg.viewWeaponAnim[3] = aIW;	
				cg.weaponHideModels&=~(1<<3);
			}
		}
		else if(!Q_stricmp(aIW->mType,"weaponmodel"))
		{
			cg.viewWeaponAnim[0] = aIW;
			cg.weaponHideModels&=~(1<<0);
		}
		else if(!Q_stricmp(aIW->mType,"bolton"))
		{
			// Weapon model.
			cg.viewWeaponAnim[4] = aIW;
			cg.weaponHideModels&=~(1<<4);
		}
		else
		{
			Com_Printf("Anim unknown for model %s\n",aIW->mType);
		}
		
		// Follow link to next model.
		aIW=aIW->mNext;
	}
}

/*
===============
CG_SetWeaponAnim
===============
*/
void CG_SetWeaponAnim(int weaponAction,playerState_t *ps)
{
	TAnimWeapon	*aW;
	aW=BG_GetInviewAnimFromIndex(ps->weapon,weaponAction);
	if(aW)
	{
		CG_StartModelAnims(aW,ps);
	}
}

/*
===============
CG_WeaponCallback
===============
*/
void CG_WeaponCallback ( playerState_t* ps, centity_t* cent, int weapon, int anim, int animChoice, int step )
{
	int				i,j;
	TNoteTrack		*note;
	entityState_t	*ent;	
	weaponInfo_t	*weaponInfo;
	char			*surfName;
	int				surfFlags;

	ent  = &cent->currentState;

	// Make sure the weapon number is valid
	if( weapon <= WP_NONE || weapon >= WP_NUM_WEAPONS )
	{
		Com_Error( ERR_FATAL, "CG_WeaponCallabck: ps->weapon >= WP_NUM_WEAPONS");
		return;
	}

	weaponInfo = &cg_weapons[ weapon ];

	note = BG_GetWeaponNote ( ps, weapon, anim, animChoice, step );
	if ( !note )
	{
		return;
	}

	if(!strcmp(note->mNote,"fire"))
	{
		// Callback is type fire = muzzle flash + sound + shell eject.
		// TODO.
	}
	else if(!strcmp(note->mNote,"altfire"))
	{
		// Callback is type altfire = muzzle flash + sound + shell eject.
		// TODO.
	}
	else
	{
		// Search the sound list... is it a sound callback?
		for(i=0;i<MAX_WEAPON_SOUNDS;i++)
		{
			if(!weaponParseInfo[weapon].mSoundNames[i][0])
			{
				break;
			}

			if(!strcmp(weaponParseInfo[weapon].mSoundNames[i],note->mNote))
			{
				// Play sound.
				// FIXME: randomly select sound??
				trap_S_StartSound(NULL,ent->number,CHAN_AUTO,weaponInfo->otherWeaponSounds[i][0], -1, -1);
				break;
			}
		}

		// Search the surfaces list... is it a surfaces callback?
		for(i=0;i<MAX_SURFACE_CALLBACKS;i++)
		{
			if(!weaponParseInfo[weapon].mSurfaceCallbacks[i].mName[0])
			{
				break;
			}
			if(!strcmp(weaponParseInfo[weapon].mSurfaceCallbacks[i].mName,note->mNote))
			{
				for(j=0;j<MAX_CALLBACK_SURFACES;j++)
				{
					surfName=weaponParseInfo[weapon].mSurfaceCallbacks[i].mOnOffSurfaces[j].mName;
					if(!surfName[0])
					{
						break;
					}
					// Turn Ghoul2 surface on/off.
					surfFlags=weaponParseInfo[weapon].mSurfaceCallbacks[i].mOnOffSurfaces[j].mStatus?0:GHOUL2_NORENDER;
					trap_G2API_SetSurfaceOnOff(weaponInfo->viewG2Model,weaponInfo->viewG2Indexes[0],surfName,surfFlags);
				}
				break;
			}
		}
	}
}
