
enum eEnforcerAnims
{
	anim_Enforcer_Fire1 = anim_weaponFire,
	anim_Enforcer_T1    = anim_weaponAttack1,
	anim_Enforcer_Fire2 = anim_weaponAttack2,
	anim_Enforcer_T2    = anim_weaponAttack3
}

enum eEnforcerFrames
{
	Enforcer_SwapIn1  =   0, Enforcer_SwapIn2  =  43,
	Enforcer_SwapOut1 = 349, Enforcer_SwapOut2 = 365,
	Enforcer_Twiddle1 =  66, Enforcer_Twiddle2 = 149,
	Enforcer_Sway1    = 149, Enforcer_Sway2    = 349,
	Enforcer_Fire1_1  =  43, Enforcer_Fire1_2  =  66,
	Enforcer_T1_1     = 366, Enforcer_T1_2     = 375,
	Enforcer_Fire2_1  = 376, Enforcer_Fire2_2  = 392,
	Enforcer_T2_1     = 393, Enforcer_T2_2     = 406,
	Enforcer_nFrames
}

kStr AkimboEnforcerGVK = "akimbo";
bool HasAkimboEnforcer()
{
	bool bAkimbo;
	GameVariables.GetBool( AkimboEnforcerGVK, bAkimbo );
	return bAkimbo;
}
void SetAkimboEnforcer( bool bOwned )
{
	if ( GameVariables.HasKey( AkimboEnforcerGVK ) )
		GameVariables.SetValue( AkimboEnforcerGVK, bOwned ? '1' : '0' );
	else
		GameVariables.Add( AkimboEnforcerGVK, bOwned ? '1' : '0' );
}

final class TurokPistol : TurokWeapon
{
	float TwiddleProb = 0.1f; // 0.04f
	bool bTwiddle = false, bOtherTwiddle = false;

	float spread;
	int flash = -1;
	float recoilPitch, recoilRoll;

	// when true, left hand is leading, and right hand is "other"
	bool bSwapHands = false;
	int shotTick;
	bool bOtherHand = false; // other hand's turn to fire
	int otherAnim = anim_weaponIdle, otherTick = 0;
	bool bOtherAnimDone = false;

	TurokPistol( kWeapon@ a )
	{
		super( a );
	}

	bool Akimbo()
	{
		return HasAkimboEnforcer();
	}

	bool AltFiring( int animID )
	{
		switch ( animID )
		{
			case anim_Enforcer_T1:
			case anim_Enforcer_Fire2:
				return true;
		}
		return false;
	}

	void AnimProperties( int animID, int&out first, int&out last, float&out rate, bool&out bLoop )
	{
		rate = 60; bLoop = false;
		switch ( animID )
		{
			case anim_weaponSwapIn:   first = Enforcer_SwapIn1;  last = Enforcer_SwapIn2;  break;
			case anim_weaponSwapOut:  first = Enforcer_SwapOut1; last = Enforcer_SwapOut2; break;
			case anim_Enforcer_Fire1: first = Enforcer_Fire1_1;  last = Enforcer_Fire1_2;  break;
			case anim_Enforcer_T1:    first = Enforcer_T1_1;     last = Enforcer_T1_2;     break;
			case anim_Enforcer_Fire2: first = Enforcer_Fire2_1;  last = Enforcer_Fire2_2;  break;
			case anim_Enforcer_T2:    first = Enforcer_T2_1;     last = Enforcer_T2_2;     break;
			default: if ( bTwiddle) { first = Enforcer_Twiddle1; last = Enforcer_Twiddle2; }
			         else           { first = Enforcer_Sway1;    last = Enforcer_Sway2;    } break;
		}
	}

	void AnimProperties( int&out first, int&out last, float&out rate, bool&out bLoop )
	{
		AnimProperties( PlayingID(), first, last, rate, bLoop );
	}

	void Animate()
	{
		TurokWeapon::Animate();

		// sync left swap anims
		switch ( PlayingID() )
		{
			case anim_weaponSwapIn:
			case anim_weaponSwapOut:
//				if ( otherAnim == PlayingID() ) break;
				if ( PlayTime() > 0 ) break;
				PlayOtherAnim( PlayingID() );
		}

		// get anim properties
		int first, last;
		float rate;
		bool bLoop;
		bool b = bTwiddle;
		bTwiddle = bOtherTwiddle;
		AnimProperties( otherAnim, first, last, rate, bLoop );
		bTwiddle = b;

		// compute other hand frame
		int vOther = first + self.GameTicks() - otherTick;
		if ( vOther >= last )
		{
			vOther = last;
			bOtherAnimDone = true;
			OtherAnimEnd();
		}

		// set model variation
		int vLead = self.ModelVariation();
		if ( bSwapHands )
		{
			int swap = vOther;
			vOther = vLead;
			vLead = swap;
		}
		self.ModelVariation() = vLead + Enforcer_nFrames * vOther;
	}

	void PlayOtherAnim( int animID )
	{
		otherAnim = animID;
		otherTick = self.GameTicks();
		bOtherAnimDone = false;
		// set twiddle to false if not idle, so it'll have a chance to play when returning to idle
		// (lead hand is handled in OnTick())
		switch ( animID )
		{
			case anim_weaponWalk: case anim_weaponRun: case anim_weaponIdle: break;
			default: bOtherTwiddle = false;
		}
	}

	void AnimEnd()
	{
		switch ( PlayingID() )
		{
			// if player fired left hand close to right hand finishing, stay in fire state until we're back on tempo
			case anim_Enforcer_Fire1:
				if ( self.GameTicks() - shotTick >= (Enforcer_Fire1_2 - Enforcer_Fire1_1) / 2 )
					TurokWeapon::AnimEnd();
				break;
			// alt fire, or transition back to default pose
			case anim_Enforcer_T1:
			case anim_Enforcer_Fire2:
				if ( bFire2 && HasAmmo() )
				{
					PlayAnim( anim_Enforcer_Fire2 );
					ShootBullet();
				}
				else
					PlayAnim( anim_Enforcer_T2 );
				break;
			case anim_weaponWalk:
			case anim_weaponRun:
			case anim_weaponIdle:
				IdleTick = self.GameTicks();
				bTwiddle = !bTwiddle && Math::RandFloat() < TwiddleProb;
				break;
			default:
				TurokWeapon::AnimEnd();
		}
	}

	void OtherAnimEnd()
	{
		// leading hand is alt firing - don't do anything here - handled in OnFire()
		if ( AltFiring( PlayingID() ) )
			return;
		// other hand is alt firing - transition out
		if ( AltFiring( otherAnim ) )
			PlayOtherAnim( anim_Enforcer_T2 );
		// otherwise go idle
		else if ( otherAnim != anim_weaponSwapOut )
		{
			PlayOtherAnim( anim_weaponIdle );
			bOtherTwiddle = !bOtherTwiddle && Math::RandFloat() < TwiddleProb;
		}
	}

	bool Refire()
	{
		switch ( PlayingID() )
		{
			case anim_Enforcer_Fire1:
			case anim_Enforcer_T2:
				return true;
		}
		return false;
	}

	void OnBeginFire()
	{
		if ( !Akimbo() )
			bSwapHands = bOtherHand = false;
		// make sure the "real" animation is for whichever hand is leading this cycle
		else if ( bOtherHand )
		{
			bSwapHands = !bSwapHands;
			bOtherHand = false;
			// make sure other hand keeps its anim (mainly, don't disrupt its idle anim)
			otherAnim = PlayingID();
			switch ( otherAnim )
			{
				case anim_weaponWalk: case anim_weaponRun: case anim_weaponIdle:
					otherTick = IdleTick;
					break;
				default:
					otherTick = self.GameTicks() - int( PlayTime() * GAME_DELTA_TIME + 0.5f );
			}
			bool b = bTwiddle;
			bTwiddle = bOtherTwiddle;
			bOtherTwiddle = b;
		}

		if ( !bFire2 )
		{
//			spread = 0.2f;
			PlayAnim( anim_Enforcer_Fire1 );
			ShootBullet();
		}
		else
		{
			spread = Akimbo() ? EnforcerSpread2_Akimbo : EnforcerSpread2_Min;
			PlayAnim( anim_Enforcer_T1 );
		}
	}

	void OnFire()
	{
		if ( !Akimbo() || !bFire || !HasAmmo() )
			return;

		// leading hand is alt firing - make sure other hand is in position
		if ( AltFiring( PlayingID() ) )
		{
			switch ( otherAnim )
			{
				// other hand is alt firing - all is well
				case anim_Enforcer_T1: case anim_Enforcer_Fire2:
					break;
				// other hand is not alt firing - start transitioning to alt pose
				// interrupt idle anim
				case anim_weaponWalk: case anim_weaponRun: case anim_weaponIdle:
					PlayOtherAnim( anim_Enforcer_T1 );
					break;
				// wait for other anims to finish
				default:
					if ( bOtherAnimDone )
						PlayOtherAnim( anim_Enforcer_T1 );
			}
		}

		// fire other hand if it's our turn
		if ( !bOtherHand || otherAnim == anim_weaponSwapIn ) return;
		switch ( PlayingID() )
		{
			case anim_Enforcer_Fire1:
				if ( self.GameTicks() - shotTick >= (Enforcer_Fire1_2 - Enforcer_Fire1_1) / 2 )
				{
					PlayOtherAnim( anim_Enforcer_Fire1 );
					ShootBullet();
				}
				break;
			case anim_Enforcer_Fire2:
				if ( self.GameTicks() - shotTick >= (Enforcer_Fire2_2 - Enforcer_Fire2_1) / 2 )
				{
					PlayOtherAnim( anim_Enforcer_Fire2 );
					ShootBullet();
				}
				break;
		}
	}

	void OnTick()
	{
		// lead hand first plays idle automatically, so randomize twiddle beforehand
		// (other hand is handled in PlayOtherAnim())
		// (can't do this in PlayAnim() like other hand, because swap anims get played automatically)
		switch ( PlayingID() )
		{
			case anim_weaponWalk: case anim_weaponRun: case anim_weaponIdle: break;
			default: if ( PlayTime() == 0 ) bTwiddle = Math::RandFloat() < TwiddleProb;
		}

		TurokWeapon::OnTick();

		Recoil();

		bool bAkimbo = Akimbo();
		// show/hide left hand
		self.RenderModel().SetVisibility( 1, bAkimbo );
		if ( !bAkimbo )
		{
			// make sure left hand plays swap-in once acquired
			PlayOtherAnim( anim_weaponSwapIn );
			// make sure idle anim is for right hand
			bSwapHands = false;
		}

		if ( flash >= 0 )
		{
			if ( --flash == -1 )
				MuzzleFlash();
		}
	}

	void Recoil()
	{
		float f = (Enforcer_Fire2_2 - Enforcer_Fire2_1) / 2.0f;
		f = 1 - (self.GameTicks() - shotTick) / f;
		if ( f < 0 ) return;
//		f = Math::Sin( Math::pi * f ) * f;
		f = Math::Sin( Math::pi * f*f );
		OwnerP().RecoilPitch() = f * recoilPitch;
		OwnerP().Roll()        = f * recoilRoll;
	}

	void MuzzleFlash()
	{
		kStr fx = "UT/fx/Enforcer/Flash";
		if ( Akimbo() && bOtherHand == bSwapHands ) fx += "_Left";
		fx += IsPlaying( anim_Enforcer_Fire1 ) ? '1' : '2';
		self.FireProjectile( fx + '_' + (1 + Math::RandMax(5)) + ".kfx", 6, 29.69f, -4, true );
	}

	void ShootBullet()
	{
		int hand = Akimbo() && bOtherHand != bSwapHands ? -1 : 1;
		kVec3 bOffset( 0, 25.6f, 0 ), sOffset;
		kStr fx = "UT/fx/Enforcer/Shell";
		if ( hand < 0 ) fx += "_Left";
		if ( IsPlaying( anim_Enforcer_Fire1 ) )
		{
			fx += "1.kfx";
			sOffset.Set( 11.5f * hand, 25, -3.5f );
			bOffset.x = 5 * hand; bOffset.z = -2;
		}
		else
		{
			fx += "2.kfx";
			sOffset.Set( 4.5f * hand, 27, -5.5f );
			bOffset.x = 2 * hand; bOffset.z = -5;
		}

		self.FireProjectile( fx, sOffset.x, sOffset.y, sOffset.z, true );
		fx = "UT/fx/Enforcer/Bullet";
		if ( UDamage() ) fx += "_Amp";
		SpawnProj( fx + ".kfx", bOffset,
			kVec3(0,1,0).Randomize( CalcSpread() ) * OwnerP().Rotation() );

		flash = 1;
		self.PlaySound( "UT/sounds/Enforcer/Fire.ksnd" );
		self.RunFxEvent( "GunFire" );
		self.Owner().Actor().LoudNoiseAlert();
//		self.Owner().Actor().RecoilPitch() = -0.02094395086169243f;
//		recoilPitch = -0.015f * (1 + Math::RandFloat()) / 2;
//		recoilRoll  = -0.015f * Math::RandFloat() * hand;
		recoilPitch = -0.009f * (1 + Math::RandFloat()) / 2;
//		recoilRoll  = -0.009f * Math::RandFloat() * hand;
		recoilRoll  = -0.012f * Math::RandFloat() * hand;
		UseAmmo( 1 );

		shotTick = self.GameTicks();
		bOtherHand= !bOtherHand;
	}

	float CalcSpread()
	{
		if ( IsPlaying( anim_Enforcer_Fire1 ) )
		{
			if ( !Akimbo() )
				return EnforcerSpread1;

			// check if it's been long enough to recover full accuracy
			int t = self.GameTicks() - shotTick;
			int accurate = Enforcer_Fire1_2 - Enforcer_Fire1_1 - 1;
			if ( t >= accurate )
//				return EnforcerSpread1;
				return spread = EnforcerSpread1;

			spread += (EnforcerSpread1_Akimbo - EnforcerSpread1) / 2;
			if ( spread > EnforcerSpread1_Akimbo ) spread = EnforcerSpread1_Akimbo;

			// check if it's too soon to start recovering any accuracy
			float f = (Enforcer_Fire1_2 - Enforcer_Fire1_1) / 2.0f;
			int inaccurate = int( f );
			if ( f > inaccurate ) inaccurate++;
			if ( t <= inaccurate )
//				return EnforcerSpread1_Akimbo;
				return spread;

			// recovery in progress
			f = float( t - inaccurate ) / (accurate - inaccurate);
//			return Math::Lerp( EnforcerSpread1_Akimbo, EnforcerSpread1, f );
			return spread = Math::Lerp( spread, EnforcerSpread1, f );
		}
		// else alt firing
		float s = spread;
		spread += Akimbo() ? EnforcerSpreadInc_Akimbo : EnforcerSpreadInc;
		if ( spread > EnforcerSpread2_Max )
			spread = EnforcerSpread2_Max;
		return s;
	}
}
