


#include "../Game_local.h"
#include "../Weapon.h"
#include "../client/ClientEffect.h"
#include "../Projectile.h"

// Available drum speeds
const int NAILGUN_DRUMSPEED_STOPPED		= 0;
const int NAILGUN_DRUMSPEED_SLOW		= 1;
const int NAILGUN_DRUMSPEED_FAST		= 2;

// Spinup and spindown times
const int NAILGUN_SPINDOWN_TIME			= 1000;
const int NAILGUN_SPINUP_TIME			= 1000;

// Nailgun shader parms
const int NAILGUN_SPARM_PLAYLEADIN		= 7;

// Nailgun weapon modifications
const int NAILGUN_MOD_ROF_AMMO			= BIT(0);	// power mod combines Rate of fire and ammoo
const int NAILGUN_MOD_SEEK				= BIT(1);
//const int NAILGUN_MOD_AMMO				= BIT(2);

const int NAILGUN_SPIN_SNDCHANNEL		= SND_CHANNEL_BODY2;

class rvWeaponNailgun : public rvWeapon {
public:

	CLASS_PROTOTYPE( rvWeaponNailgun );

	rvWeaponNailgun ( void );
	~rvWeaponNailgun ( void );

	virtual void		Spawn					( void );
	virtual void		Think					( void );
	void				Save				( idSaveGame *savefile ) const;
	void				Restore				( idRestoreGame *savefile );
	void				PreSave					( void );
	void				PostSave				( void );

protected:

	idEntityPtr<idEntity>				guideEnt;
	int									guideTime;
	int									guideStartTime;
	rvClientEntityPtr<rvClientEffect>	guideEffect;
	bool								guideLocked;
	float								guideRange;
	int									guideHoldTime;
	int									guideAquireTime;

	jointHandle_t						jointDrumView;
	jointHandle_t						jointPinsView;
	jointHandle_t						jointSteamRightView;
	jointHandle_t						jointSteamLeftView;

	jointHandle_t						jointGuideEnt;
	
	int									drumSpeed;
	int									drumSpeedIdeal;
	float								drumMultiplier;

	virtual void		OnLaunchProjectile		( idProjectile* proj );
	
private:

	void				UpdateGuideStatus		( float range = 0.0f );
	void				CancelGuide				( void );	
	bool				DrumSpin				( int speed, int blendFrames );

	stateResult_t		State_Idle				( const stateParms_t& parms );
	stateResult_t		State_Fire				( const stateParms_t& parms );
	stateResult_t		State_Reload			( const stateParms_t& parms );
	stateResult_t		State_Raise				( const stateParms_t& parms );
	stateResult_t		State_Lower				( const stateParms_t& parms );
	
	stateResult_t		State_DrumSpinDown		( const stateParms_t& parms );
	stateResult_t		State_DrumSpinUp		( const stateParms_t& parms );

	stateResult_t		Frame_ClaspOpen			( const stateParms_t& parms );
	stateResult_t		Frame_ClaspClose		( const stateParms_t& parms );	
	
	CLASS_STATES_PROTOTYPE ( rvWeaponNailgun );
};

CLASS_DECLARATION( rvWeapon, rvWeaponNailgun )
END_CLASS

/*
================
rvWeaponNailgun::rvWeaponNailgun
================
*/
rvWeaponNailgun::rvWeaponNailgun ( void ) {
}

/*
================
rvWeaponNailgun::~rvWeaponNailgun
================
*/
rvWeaponNailgun::~rvWeaponNailgun ( void ) {
	if ( guideEffect ) {
		guideEffect->Stop();
	}
	if( viewModel ) {
		viewModel->StopSound( NAILGUN_SPIN_SNDCHANNEL, false );
	}
}


/*
================
rvWeaponNailgun::Spawn
================
*/
void rvWeaponNailgun::Spawn ( void ) {
	spawnArgs.GetFloat ( "lockRange", "1000", guideRange );
	guideHoldTime = SEC2MS ( spawnArgs.GetFloat ( "lockHoldTime", "10" ) );
	guideAquireTime = SEC2MS ( spawnArgs.GetFloat ( "lockAquireTime", ".1" ) );
	
	jointDrumView		= viewAnimator->GetJointHandle ( spawnArgs.GetString ( "joint_view_drum" ) );
	jointPinsView		= viewAnimator->GetJointHandle ( spawnArgs.GetString ( "joint_view_pins" ) );
	jointSteamRightView = viewAnimator->GetJointHandle ( spawnArgs.GetString ( "joint_view_steamRight" ) );
	jointSteamLeftView  = viewAnimator->GetJointHandle ( spawnArgs.GetString ( "joint_view_steamLeft" ) );

	jointGuideEnt		= INVALID_JOINT;
	
	drumSpeed		= NAILGUN_DRUMSPEED_STOPPED;
	drumSpeedIdeal	= drumSpeed;
	drumMultiplier	= spawnArgs.GetFloat ( "drumSpeed" );
	
	ExecuteState ( "ClaspClose" );	
	SetState ( "Raise", 0 );	
}

/*
================
rvWeaponNailgun::Save
================
*/
void rvWeaponNailgun::Save ( idSaveGame *savefile ) const {
	guideEnt.Save( savefile );
	savefile->WriteInt( guideTime );
	savefile->WriteInt( guideStartTime );
	guideEffect.Save( savefile );
	savefile->WriteBool ( guideLocked );
	savefile->WriteFloat ( guideRange );
	savefile->WriteInt ( guideHoldTime );
	savefile->WriteInt ( guideAquireTime );

	savefile->WriteJoint ( jointDrumView );
	savefile->WriteJoint ( jointPinsView );
	savefile->WriteJoint ( jointSteamRightView );
	savefile->WriteJoint ( jointSteamLeftView );
	savefile->WriteJoint ( jointGuideEnt );
	
	savefile->WriteInt ( drumSpeed );
	savefile->WriteInt ( drumSpeedIdeal );
	savefile->WriteFloat ( drumMultiplier );
}

/*
================
rvWeaponNailgun::Restore
================
*/
void rvWeaponNailgun::Restore ( idRestoreGame *savefile ) {
	guideEnt.Restore( savefile );
	savefile->ReadInt( guideTime );
	savefile->ReadInt( guideStartTime );
	guideEffect.Restore( savefile );
	savefile->ReadBool ( guideLocked );
	savefile->ReadFloat ( guideRange );
	savefile->ReadInt ( guideHoldTime );
	savefile->ReadInt ( guideAquireTime );

	savefile->ReadJoint ( jointDrumView );
	savefile->ReadJoint ( jointPinsView );
	savefile->ReadJoint ( jointSteamRightView );
	savefile->ReadJoint ( jointSteamLeftView );
	savefile->ReadJoint ( jointGuideEnt );

	
	savefile->ReadInt ( drumSpeed );
	savefile->ReadInt ( drumSpeedIdeal );
	savefile->ReadFloat ( drumMultiplier );
}

/*
================
rvWeaponNailgun::PreSave
================
*/
void rvWeaponNailgun::PreSave ( void ) {

	//disable sounds
	StopSound( SND_CHANNEL_ANY, false);

	//remove the guide gui cursor if there is one.
	if ( guideEffect ) {
		guideEffect->Stop();
		guideEffect->Event_Remove();
		guideEffect = NULL;
	}
}

/*
================
rvWeaponNailgun::PostSave
================
*/
void rvWeaponNailgun::PostSave ( void ) {

	//restore sounds-- but which one?
	if( drumSpeed == NAILGUN_DRUMSPEED_FAST )	{
		viewModel->StartSound ( "snd_spinfast", NAILGUN_SPIN_SNDCHANNEL, 0, false, NULL );		
	} else 	if( drumSpeed == NAILGUN_DRUMSPEED_SLOW )	{
		viewModel->StartSound ( "snd_spinslow", NAILGUN_SPIN_SNDCHANNEL, 0, false, NULL );		
	}

	//the guide gui effect will restore itself naturally
}

/*
================
rvWeaponNailgun::CancelGuide
================
*/
void rvWeaponNailgun::CancelGuide ( void ) {
	if ( zoomGui && guideEnt ) {
		zoomGui->HandleNamedEvent ( "lockStop" );
	}

	guideEnt = NULL;
	guideLocked = false;
	jointGuideEnt = INVALID_JOINT;
	UpdateGuideStatus ( );	
}

/*
================
rvWeaponNailgun::UpdateGuideStatus
================
*/
void rvWeaponNailgun::UpdateGuideStatus ( float range ) {
	// Update the zoom GUI variables
	if ( zoomGui ) {
		float lockStatus;
		if ( guideEnt ) {
			if ( guideLocked ) {
				lockStatus = 1.0f;
			} else {
				lockStatus = Min((float)(gameLocal.time - guideStartTime)/(float)guideTime, 1.0f);
			}
		} else {
			lockStatus = 0.0f;
		}
		
		if ( owner == gameLocal.GetLocalPlayer( ) ) {
			zoomGui->SetStateFloat ( "lockStatus", lockStatus );		
			zoomGui->SetStateFloat ( "playerYaw", playerViewAxis.ToAngles().yaw );
		}		

		if ( guideEnt ) {		
			idVec3 diff;
			diff = guideEnt->GetPhysics()->GetOrigin ( ) - playerViewOrigin;
			diff.NormalizeFast ( );
			zoomGui->SetStateFloat ( "lockYaw", idMath::AngleDelta ( diff.ToAngles ( ).yaw, playerViewAxis.ToAngles().yaw ) );
			zoomGui->SetStateString ( "lockRange", va("%4.2f", range) );
			zoomGui->SetStateString ( "lockTime", va("%02d", (int)MS2SEC(guideStartTime + guideTime - gameLocal.time) + 1) );
		}
	}
	
	// Update the guide effect
	if ( guideEnt ) {
		idVec3 eyePos = static_cast<idActor *>(guideEnt.GetEntity())->GetEyePosition();
		if( jointGuideEnt == INVALID_JOINT )	{
			eyePos += guideEnt->GetPhysics()->GetAbsBounds().GetCenter ( );
		} else {
			idMat3	jointAxis;
			idVec3	jointLoc;
			static_cast<idAnimatedEntity *>(guideEnt.GetEntity())->GetJointWorldTransform( jointGuideEnt, gameLocal.GetTime(),jointLoc,jointAxis );
			eyePos += jointLoc;
		}
		eyePos *= 0.5f;
		if ( guideEffect ) {			
			guideEffect->SetOrigin ( eyePos );
			guideEffect->SetAxis ( playerViewAxis.Transpose() );
		} else {
			guideEffect = gameLocal.PlayEffect ( 
									gameLocal.GetEffect( spawnArgs, guideLocked ? "fx_guide" : "fx_guidestart" ), 
									eyePos, playerViewAxis.Transpose(), true, vec3_origin, false );
			if ( guideEffect ) {
				guideEffect->GetRenderEffect()->weaponDepthHackInViewID = owner->entityNumber + 1;
				guideEffect->GetRenderEffect()->allowSurfaceInViewID = owner->entityNumber + 1;
			}
		}
	} else {
		if ( guideEffect ) {
			guideEffect->Stop();
			guideEffect = NULL;
		}
	}
}

/*
================
rvWeaponNailgun::Think
================
*/
void rvWeaponNailgun::Think ( void ) {
	idEntity* ent;
	trace_t	  tr;

	// Let the real weapon think first
	rvWeapon::Think ( );

	// If no guide range is set then we dont have the mod yet
	if ( !guideRange ) {
		return;
	}

	// If the zoom button isnt down then dont update the lock
	if ( !wsfl.zoom || IsReloading ( ) || IsHolstered ( ) ) {
		CancelGuide ( );
		return;
	}

	// Dont update the target if the current target is still alive, unhidden and we have already locked
	if ( guideEnt && guideEnt->health > 0 && !guideEnt->IsHidden() && guideLocked ) {
		float	range;
		range = (guideEnt->GetPhysics()->GetOrigin() - playerViewOrigin).LengthFast();
		if ( range > guideRange || gameLocal.time > guideStartTime + guideTime ) {
			CancelGuide ( );
		} else {
			UpdateGuideStatus ( range );
			return;
		}
	}

	// Cast a ray out to the lock range
// RAVEN BEGIN
// ddynerman: multiple clip worlds
	gameLocal.TracePoint(	owner, tr, 
							playerViewOrigin, 
							playerViewOrigin + playerViewAxis[0] * guideRange, 
							MASK_SHOT_BOUNDINGBOX, owner );
// RAVEN END
	
	if ( tr.fraction >= 1.0f ) {
		CancelGuide( );
		return;
	}
	
	ent = gameLocal.entities[tr.c.entityNum];
	
	//if we're using a target nailable...
	if( ent->IsType ( rvTarget_Nailable::GetClassType()	 )	)	{
		const char* jointName = ent->spawnArgs.GetString("lock_joint");
		ent = ent->targets[ 0 ].GetEntity();
		if( !ent)	{
			CancelGuide( );
			return;
		}

		if( ent->GetAnimator() )	{
			jointGuideEnt = ent->GetAnimator()->GetJointHandle( jointName );
		}
	}


	if ( !ent->IsType ( idActor::GetClassType() ) ) {
		CancelGuide( );
		return;
	}
	if ( gameLocal.GetLocalPlayer() && static_cast<idActor *>(ent)->team == gameLocal.GetLocalPlayer()->team ) {
		CancelGuide( );
		return;
	}
	
	if ( guideEnt != ent ) {
		guideStartTime = gameLocal.time;		
		guideTime = guideAquireTime;
		guideEnt = ent;
		if ( zoomGui ) {
			zoomGui->HandleNamedEvent ( "lockStart" );
		}
	} else if ( gameLocal.time > guideStartTime + guideTime ) {
		// Stop the guide effect since it was just the guide_Start effect
		if ( guideEffect ) {
			guideEffect->Stop();
			guideEffect = NULL;
		}
		guideLocked = true;
		guideTime = guideHoldTime;
		guideStartTime = gameLocal.time;
		if ( zoomGui ) {
			zoomGui->HandleNamedEvent ( "lockAquired" );
		}
	}
	
	UpdateGuideStatus ( (ent->GetPhysics()->GetOrigin() - playerViewOrigin).LengthFast() );
}

/*
================
rvWeaponNailgun::OnLaunchProjectile
================
*/
void rvWeaponNailgun::OnLaunchProjectile ( idProjectile* proj ) {
	rvWeapon::OnLaunchProjectile(proj);

	idGuidedProjectile* guided;
	guided = dynamic_cast<idGuidedProjectile*>(proj);
	if ( guided ) {
		guided->GuideTo ( guideEnt, jointGuideEnt );
	}
}

/*
================
rvWeaponNailgun::DrumSpin

Set the drum spin speed 
================
*/
bool rvWeaponNailgun::DrumSpin ( int speed, int blendFrames ) {
	// Dont bother if the drum is already spinning at the desired speed
	if ( drumSpeedIdeal == speed ) {
		return false;
	}

	drumSpeedIdeal = speed;

	switch ( speed ) {
		case NAILGUN_DRUMSPEED_STOPPED:
			viewModel->StopSound ( NAILGUN_SPIN_SNDCHANNEL, false );
			viewAnimator->SetJointAngularVelocity ( jointDrumView, idAngles(0,0,0), gameLocal.time, 250 );
			viewAnimator->SetJointAngularVelocity ( jointPinsView, idAngles(0,0,0), gameLocal.time, 250 );
		
			// Spin the barrel down if we were spinning fast
			if ( drumSpeed == NAILGUN_DRUMSPEED_FAST ) {
				PostState ( "DrumSpinDown", blendFrames );
				return true;
			}
			break;
			
		case NAILGUN_DRUMSPEED_SLOW:
			viewAnimator->SetJointAngularVelocity ( jointDrumView, idAngles(0,0,45), gameLocal.time, NAILGUN_SPINDOWN_TIME );
			viewAnimator->SetJointAngularVelocity ( jointPinsView, idAngles(0,0,-35), gameLocal.time, NAILGUN_SPINDOWN_TIME );		
			viewModel->StopSound ( NAILGUN_SPIN_SNDCHANNEL, false );
			viewModel->StartSound ( "snd_spinslow", NAILGUN_SPIN_SNDCHANNEL, 0, false, NULL );		

			// Spin the barrel down if we were spinning fast		
			if ( drumSpeed == NAILGUN_DRUMSPEED_FAST ) {
				PostState ( "DrumSpinDown", blendFrames );
				return true;
			}
			break;
		
		case NAILGUN_DRUMSPEED_FAST:
			// Start the barrel spinning faster
			viewAnimator->SetJointAngularVelocity ( jointDrumView, idAngles(0,0,360.0f * drumMultiplier), gameLocal.time, NAILGUN_SPINUP_TIME );
			viewAnimator->SetJointAngularVelocity ( jointPinsView, idAngles(0,0,-300.0f * drumMultiplier), gameLocal.time, NAILGUN_SPINUP_TIME );

			PostState ( "DrumSpinUp", blendFrames );
			return true;
	}
	
	drumSpeed = drumSpeedIdeal;
	
	return false;
}

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

	States 

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

CLASS_STATES_DECLARATION ( rvWeaponNailgun )
	STATE ( "Raise",						rvWeaponNailgun::State_Raise )
	STATE ( "Lower",						rvWeaponNailgun::State_Lower )
	STATE ( "Idle",							rvWeaponNailgun::State_Idle)
	STATE ( "Fire",							rvWeaponNailgun::State_Fire )
	STATE ( "Reload",						rvWeaponNailgun::State_Reload )
	STATE ( "DrumSpinDown",					rvWeaponNailgun::State_DrumSpinDown )
	STATE ( "DrumSpinUp",					rvWeaponNailgun::State_DrumSpinUp )
	
	STATE ( "ClaspOpen",					rvWeaponNailgun::Frame_ClaspOpen )
	STATE ( "ClaspClose",					rvWeaponNailgun::Frame_ClaspClose )
END_CLASS_STATES

/*
================
rvWeaponNailgun::State_Raise

Raise the weapon
================
*/
stateResult_t rvWeaponNailgun::State_Raise ( const stateParms_t& parms ) {
	enum {
		STAGE_INIT,
		STAGE_WAIT,
	};	
	switch ( parms.stage ) {
		// Start the weapon raising
		case STAGE_INIT:
			SetStatus ( WP_RISING );
			PlayAnim( ANIMCHANNEL_LEGS, "raise", 0 );
			DrumSpin ( NAILGUN_DRUMSPEED_SLOW, 0 );
			return SRESULT_STAGE ( STAGE_WAIT );
			
		case STAGE_WAIT:
			if ( AnimDone ( ANIMCHANNEL_LEGS, 4 ) ) {
				SetState ( "Idle", 4 );
				return SRESULT_DONE;
			}
			if ( wsfl.lowerWeapon ) {
				SetState ( "Lower", 4 );
				return SRESULT_DONE;
			}
			return SRESULT_WAIT;
	}
	return SRESULT_ERROR;
}

/*
================
rvWeaponNailgun::State_Lower

Lower the weapon
================
*/
stateResult_t rvWeaponNailgun::State_Lower ( const stateParms_t& parms ) {	
	enum {
		STAGE_INIT,
		STAGE_WAIT,
		STAGE_WAITRAISE
	};	
	switch ( parms.stage ) {
		case STAGE_INIT:
			// Stop any looping sounds
			viewModel->StopSound ( NAILGUN_SPIN_SNDCHANNEL, false );
			
			SetStatus ( WP_LOWERING );
			PlayAnim ( ANIMCHANNEL_LEGS, "putaway", parms.blendFrames );
			return SRESULT_STAGE(STAGE_WAIT);
			
		case STAGE_WAIT:
			if ( AnimDone ( ANIMCHANNEL_LEGS, 0 ) ) {
				SetStatus ( WP_HOLSTERED );
				return SRESULT_STAGE(STAGE_WAITRAISE);
			}
			return SRESULT_WAIT;
		
		case STAGE_WAITRAISE:
			if ( wsfl.raiseWeapon ) {
				SetState ( "Raise", 0 );
				return SRESULT_DONE;
			}
			return SRESULT_WAIT;
	}
	return SRESULT_ERROR;
}

/*
================
rvWeaponNailgun::State_Idle

Manage the idle state of the weapon
================
*/
stateResult_t rvWeaponNailgun::State_Idle( const stateParms_t& parms ) {
	enum {
		STAGE_INIT,
		STAGE_WAIT,
	};	
	switch ( parms.stage ) {
		case STAGE_INIT:
			if ( !AmmoInClip ( ) ) {
				SetStatus ( WP_OUTOFAMMO );
			} else {
				SetStatus ( WP_READY );
			}			
			// Do we need to spin the drum down?
			if ( DrumSpin ( NAILGUN_DRUMSPEED_SLOW, parms.blendFrames ) ) {
				PostState ( "Idle", parms.blendFrames );
				return SRESULT_DONE;
			}
				
			PlayCycle( ANIMCHANNEL_LEGS, "idle", parms.blendFrames );
			return SRESULT_STAGE ( STAGE_WAIT );

		case STAGE_WAIT:
			if ( wsfl.lowerWeapon ) {
				SetState ( "Lower", 4 );
				return SRESULT_DONE;
			}
		
			if ( !ClipSize() ) {
				if ( gameLocal.time > nextAttackTime && wsfl.attack && AmmoAvailable ( ) ) {
					SetState ( "Fire", 0 );
					return SRESULT_DONE;
				}
			} else {
				if ( gameLocal.time > nextAttackTime && wsfl.attack && AmmoInClip ( ) ) {
					SetState ( "Fire", 0 );
					return SRESULT_DONE;
				}  
				if ( wsfl.attack && AutoReload() && !AmmoInClip ( ) && AmmoAvailable () ) {
					SetState ( "Reload", 4 );
					return SRESULT_DONE;			
				}
				if ( wsfl.netReload || (wsfl.reload && AmmoInClip() < ClipSize() && AmmoAvailable()>AmmoInClip()) ) {
					SetState ( "Reload", 4 );
					return SRESULT_DONE;			
				}				
			}
			return SRESULT_WAIT;
	}
	return SRESULT_ERROR;
}

/*
================
rvWeaponNailgun::State_Fire

Fire the weapon
================
*/
stateResult_t rvWeaponNailgun::State_Fire( const stateParms_t& parms ) {
	enum {
		STAGE_INIT,
		STAGE_FIRE,
		STAGE_FIREWAIT,
		STAGE_DONE,
		STAGE_SPINEMPTY,		
	};	
	switch ( parms.stage ) {
		case STAGE_INIT:
			if ( !wsfl.attack ) {
				SetState ( "Idle", parms.blendFrames );				
				return SRESULT_DONE;
			}
			if ( DrumSpin ( NAILGUN_DRUMSPEED_FAST, 2 ) ) {
				PostState ( "Fire", 2 );
				return SRESULT_DONE;
			}
			nextAttackTime = gameLocal.time;
			
			return SRESULT_STAGE ( STAGE_FIRE );
			
		case STAGE_FIRE:
			if ( !wsfl.attack || wsfl.reload || wsfl.lowerWeapon || AmmoInClip ( ) <= 0 ) {
				return SRESULT_STAGE ( STAGE_DONE );
			}
			if ( mods & NAILGUN_MOD_ROF_AMMO ) {
				PlayCycle ( ANIMCHANNEL_LEGS, "fire_fast", 4 );
			} else {
				PlayCycle ( ANIMCHANNEL_LEGS, "fire_slow", 4 );
			}

			if ( wsfl.zoom ) {				
				Attack ( true, 1, spread, 0.0f, 1.0f );
				nextAttackTime = gameLocal.time + (altFireRate * owner->PowerUpModifier ( PMOD_FIRERATE ));
			} else {
				Attack ( false, 1, spread, 0.0f, 1.0f );
				nextAttackTime = gameLocal.time + (fireRate * owner->PowerUpModifier ( PMOD_FIRERATE ));
			}
			
			// Play the exhaust effects
			viewModel->PlayEffect ( "fx_exhaust", jointSteamRightView, false );
			viewModel->PlayEffect ( "fx_exhaust", jointSteamLeftView, false );

			viewModel->StartSound ( "snd_fire", SND_CHANNEL_WEAPON,	0, false, NULL );
			viewModel->StartSound ( "snd_fireStereo", SND_CHANNEL_ITEM, 0, false, NULL ); 
					
			return SRESULT_STAGE ( STAGE_FIREWAIT );

		case STAGE_FIREWAIT:
			if ( !wsfl.attack || wsfl.reload || wsfl.lowerWeapon || AmmoInClip ( ) <= 0 ) {
				return SRESULT_STAGE ( STAGE_DONE );
			}
			if ( gameLocal.time >= nextAttackTime ) {
				return SRESULT_STAGE ( STAGE_FIRE );
			}
			return SRESULT_WAIT;
			
		case STAGE_DONE:
			if ( ClipSize() && wsfl.attack && !wsfl.lowerWeapon && !wsfl.reload ) {
				PlayCycle ( ANIMCHANNEL_LEGS, "spinempty", 4 );
				return SRESULT_STAGE ( STAGE_SPINEMPTY );
			}
			DrumSpin ( NAILGUN_DRUMSPEED_SLOW, 4 );
			if ( !wsfl.attack && !AmmoInClip() && AmmoAvailable() && AutoReload ( ) && !wsfl.lowerWeapon ) {
				PostState ( "Reload", 4 );
			} else {
				PostState ( "Idle", 4 );
			}
			return SRESULT_DONE;
			
		case STAGE_SPINEMPTY:
			if ( !wsfl.attack || wsfl.reload || wsfl.lowerWeapon ) {
				return SRESULT_STAGE ( STAGE_DONE );
			}
			return SRESULT_WAIT;
	}
	return SRESULT_ERROR;
}

/*
================
rvWeaponNailgun::State_Reload
================
*/
stateResult_t rvWeaponNailgun::State_Reload ( const stateParms_t& parms ) {
	enum {
		STAGE_INIT,
		STAGE_RELOAD,
		STAGE_RELOADWAIT,
		STAGE_RELOADLEFT,
		STAGE_RELOADLEFTWAIT,
		STAGE_RELOADRIGHT,
		STAGE_RELOADRIGHTWAIT,
		STAGE_RELOADDONE,
		STAGE_RELOADDONEWAIT,		
	};	
	switch ( parms.stage ) {
		case STAGE_INIT:
			if ( DrumSpin ( NAILGUN_DRUMSPEED_STOPPED, parms.blendFrames ) ) {
				PostState ( "reload", parms.blendFrames );
				return SRESULT_DONE;
			}

			SetStatus ( WP_RELOAD );

			if ( wsfl.netReload ) {
				wsfl.netReload = false;
			} else {
				NetReload ( );
			}

			if ( mods & NAILGUN_MOD_ROF_AMMO ) {
				return SRESULT_STAGE( STAGE_RELOADLEFT );
			}						
			return SRESULT_STAGE( STAGE_RELOAD );
			
		case STAGE_RELOAD:
			PlayAnim( ANIMCHANNEL_LEGS, "reload", parms.blendFrames );
			return SRESULT_STAGE( STAGE_RELOADWAIT );
			
		case STAGE_RELOADWAIT:
			if ( AnimDone ( ANIMCHANNEL_LEGS, 4 ) ) {
				AddToClip( ClipSize ( ) );
				SetState ( "Idle", 4 );
				return SRESULT_DONE;
			}
			if ( wsfl.lowerWeapon ) {
				SetState ( "Lower", 4 );
				return SRESULT_DONE;
			}			
			return SRESULT_WAIT;
		
		case STAGE_RELOADLEFT:
			PlayAnim ( ANIMCHANNEL_LEGS, "reload_clip1hold", parms.blendFrames );
			return SRESULT_STAGE ( STAGE_RELOADLEFTWAIT );
			
		case STAGE_RELOADLEFTWAIT:
			if ( AnimDone ( ANIMCHANNEL_LEGS, 0 ) ) {
				AddToClip ( ClipSize() / 2 );				
				return SRESULT_STAGE ( STAGE_RELOADRIGHT );
			}
			if ( wsfl.lowerWeapon ) {
				SetState ( "Lower", 4 );
				return SRESULT_DONE;
			}			
			return SRESULT_WAIT;

		case STAGE_RELOADRIGHT:
			if ( wsfl.attack || AmmoInClip() >= ClipSize() || !AmmoAvailable() ) {
				return SRESULT_STAGE ( STAGE_RELOADDONE );
			}
			PlayAnim ( ANIMCHANNEL_LEGS, "reload_clip2", 0 );
			return SRESULT_STAGE ( STAGE_RELOADRIGHTWAIT );
			
		case STAGE_RELOADRIGHTWAIT:
			if ( AnimDone ( ANIMCHANNEL_LEGS, 4 ) ) {
				AddToClip( ClipSize() / 2 );
				SetState ( "Idle", 4 );
				return SRESULT_DONE;
			}
			if ( wsfl.lowerWeapon ) {
				SetState ( "Lower", 4 );
				return SRESULT_DONE;
			}			
			return SRESULT_WAIT;
		
		case STAGE_RELOADDONE:
			PlayAnim ( ANIMCHANNEL_LEGS, "reload_clip1finish", 0 );
			return SRESULT_STAGE ( STAGE_RELOADDONEWAIT );
		
		case STAGE_RELOADDONEWAIT:
			if ( AnimDone ( ANIMCHANNEL_LEGS, 4 ) ) {
				SetState ( "Idle", 4 );
				return SRESULT_DONE;
			}
			if ( wsfl.lowerWeapon ) {
				SetState ( "Lower", 4 );
				return SRESULT_DONE;
			}			
			return SRESULT_WAIT;
	}
	return SRESULT_ERROR;
}

/*
================
rvWeaponNailgun::State_DrumSpinUp

Spin the drum from a slow speed to a fast speed
================
*/
stateResult_t rvWeaponNailgun::State_DrumSpinUp ( const stateParms_t& parms ) {
	enum {
		STAGE_INIT,
		STAGE_WAIT,
	};	
	switch ( parms.stage ) {
		case STAGE_INIT:
			viewModel->StartSound ( "snd_spinup", NAILGUN_SPIN_SNDCHANNEL, 0, false, NULL);
			PlayAnim ( ANIMCHANNEL_LEGS, "spinup", 4 );
			return SRESULT_STAGE(STAGE_WAIT);
		
		case STAGE_WAIT:
			if ( AnimDone ( ANIMCHANNEL_LEGS, 0 ) ) {
				viewModel->StartSound ( "snd_spinfast", NAILGUN_SPIN_SNDCHANNEL, 0, false, NULL );
				drumSpeed = drumSpeedIdeal;
				return SRESULT_DONE;
			}
			if ( !wsfl.attack ) {
				int oldSpeed = drumSpeed;
				drumSpeed = drumSpeedIdeal;
				DrumSpin ( oldSpeed, 0 );
				return SRESULT_DONE;
			}
			return SRESULT_WAIT;
	}
	return SRESULT_ERROR;
}

/*
================
rvWeaponNailgun::State_DrumSpinDown

Spin the drum down from a faster speed
================
*/
stateResult_t rvWeaponNailgun::State_DrumSpinDown ( const stateParms_t& parms ) {
	enum {
		STAGE_INIT,
		STAGE_WAIT,
	};	
	switch ( parms.stage ) {
		case STAGE_INIT:
			viewModel->StartSound ( "snd_spindown", SND_CHANNEL_ANY, 0, false, 0 );

			// Spin down animation	
			PlayAnim( ANIMCHANNEL_LEGS, "spindown", parms.blendFrames );
			return SRESULT_STAGE ( STAGE_WAIT );
			
		case STAGE_WAIT:
			if ( AnimDone ( ANIMCHANNEL_LEGS, 4 ) ) {
				drumSpeed = drumSpeedIdeal;
				return SRESULT_DONE;
			}
			if ( wsfl.lowerWeapon ) {
				viewModel->StopSound ( NAILGUN_SPIN_SNDCHANNEL, false );
				SetState ( "Lower", 4 );
				return SRESULT_DONE;
			}
			if ( wsfl.attack && AmmoInClip ( ) ) {
				viewModel->StopSound ( NAILGUN_SPIN_SNDCHANNEL, false );
				SetState ( "Fire", 4 );
				return SRESULT_DONE;
			}
			if ( wsfl.netReload || (wsfl.reload && AmmoAvailable() > AmmoInClip() && AmmoInClip() < ClipSize()) ) {
				SetState ( "Reload", 4 );
				return SRESULT_DONE;
			}
			return SRESULT_WAIT;
	}
	return SRESULT_ERROR;
}

/*
================
rvWeaponNailgun::Frame_ClaspOpen

Open the clasp that holds in the clips
================
*/
stateResult_t rvWeaponNailgun::Frame_ClaspOpen ( const stateParms_t& parms ) {
	PlayAnim ( ANIMCHANNEL_TORSO, "clasp_open", 0 );
	return SRESULT_OK;
}

/*
================
rvWeaponNailgun::Frame_ClaspOpen

Close the clasp that holds in the clips and make sure to use the
correct positioning depending on whether you have one or two clips
in the gun.
================
*/
stateResult_t rvWeaponNailgun::Frame_ClaspClose ( const stateParms_t& parms ) {
	if ( mods & NAILGUN_MOD_ROF_AMMO ) {
		PlayAnim( ANIMCHANNEL_TORSO, "clasp_2clip", 0 );
	} else {
		PlayAnim( ANIMCHANNEL_TORSO, "clasp_1clip", 0 );
	}
	return SRESULT_OK;
}
