//=============================================================================
// KFSM_Patriarch_MinigunBarrage
//=============================================================================
// Patriarch's minigun barrage attack
//=============================================================================
// Killing Floor 2
// Copyright (C) 2016 Tripwire Interactive LLC
//=============================================================================

class KFSM_Patriarch_MinigunBarrage extends KFSM_PlaySingleAnim;

/** Cached reference to Patriarch pawn */
var KFPawn_ZedPatriarch MyPatPawn;

/** Cached reference to Patriarch AI controller */
var KFAIController_ZedPatriarch MyPatController;

/** Name of the aim offset profile to use with this special move */
var Name AimOffsetProfileName;

/** Wind Up animation name */
var Name WindUpAnimName;

/** Wind Down animation name */
var Name WindDownAnimName;

/** Alternate fan attack animation names */
var array<Name> FanAnimNames;

/** Set according to special move flags */
var bool bIsFanFire;

/** Rotation rate to use when focus firing */
var rotator FocusFireRotationRate;

/** Minigun sounds */
var AkEvent MinigunLoop;
var AkEvent MinigunLoopEnd;

/** Time to check before firing if an enemy is still visible */
var float VisibilityCheckTime;

/** Set to true if AI command interrupted move early */
var bool bInterrupted;

/** Set to true if fire LOS was obstructed mid-move */
var bool bObstructed;

/** Pack the animation type we'll be using (fan vs. focus fire) */
static function byte PackSMFlags( bool bIsFanFireAttack )
{
	return ( bIsFanFireAttack ? 1 : 0 ) + ( Rand(default.FanAnimNames.Length) << 4 );
}

/** Notification called when Special Move starts */
function SpecialMoveStarted( bool bForced, Name PrevMove )
{
	local byte Type, Variant;

	MyPatPawn = KFPawn_ZedPatriarch( KFPOwner );

	// Set anim type by flag
	Type = MyPatPawn.SpecialMoveFlags & 15; //0x0f
	Variant = MyPatPawn.SpecialMoveFlags >> 4;

	// Set the anim according to the type of barrage we'll be doing
	if( Type > 0 )
	{
		bDisableMovement = true;
		MyPatPawn.bSprayingFire = true;
		bIsFanFire = true;
		AnimName = FanAnimNames[Variant];
	}
	else
	{
		bDisableMovement = !MyPatPawn.CanMoveWhenMinigunning();
		MyPatPawn.bSprayingFire = false;
		if( MyPatPawn.Role == ROLE_Authority )
		{
			MyPatPawn.SetGunTracking( true );
		}

		AnimName = default.AnimName;
	}

	Super.SpecialMoveStarted( bForced,PrevMove );

	MyPatPawn.SetCloaked( false );
	MyPatPawn.SetSprinting( false );
	MyPatPawn.ZeroMovementVariables();
	MyPatPawn.PlayMinigunWarnDialog();

	MyPatController	= KFAIController_ZedPatriarch( MyPatPawn.Controller );

	PlayWindUpAnimation();

	MyPatPawn.SetAimOffsetNodesProfile( AimOffsetProfileName );
	MyPatPawn.bEnableAimOffset = true;

	if( MyPatPawn.Role == ROLE_Authority && !MyPatPawn.IsHumanControlled() )
	{
		MyPatPawn.SetTimer( VisibilityCheckTime, false, nameOf(Timer_CheckEnemyLOS), self );
	}

	bObstructed = false;
	bInterrupted = false;
}

/** Returns anim stance based on whether we're allowed to move when minigunning or not */
function EAnimSlotStance GetAnimStance()
{
	return bDisableMovement ? EAS_FullBody : EAS_UpperBody;
}

/** Overridden to do nothing */
function PlayAnimation() {}

/** Plays our wind up anim, starts the barrel spin skel controller */
function PlayWindUpAnimation()
{
	if( MyPatPawn.bSprayingFire )
	{
		bUseRootMotion = true;
		AnimStance = EAS_FullBody;
		EnableRootMotion();
	}
	else
	{
		AnimStance = EAS_UpperBody;	
	}
	PlaySpecialMoveAnim( WindUpAnimName, AnimStance, BlendInTime, BlendOutTime, 1.f );
	MyPatPawn.SpinMinigunBarrels( true );

	// Play our looping minigun sound
	MyPatPawn.PostAkEventOnBone( MinigunLoop, 'BarrelSpinner', true, true );
}

/** Make sure we have a clear path to enemy */
function Timer_CheckEnemyLOS()
{
	local vector StartTrace, EndTrace;

	if( MyPatPawn != none && MyPatPawn.Controller != none )
	{
		MyPatPawn.Mesh.GetSocketWorldLocationAndRotation( 'MissileCenter', StartTrace );
		EndTrace = StartTrace + (Normal(MyPatPawn.Controller.Enemy.Location - StartTrace) * 300.f);
		if( !MyPatPawn.FastTrace(EndTrace, StartTrace,, true) )
		{
			MyPatPawn.DoSpecialMove( SM_HoseWeaponAttack, true,, 128 );
		}
	}
}

/** Play the wind down if we have to */
function SpecialMoveFlagsUpdated()
{
	switch( MyPatPawn.SpecialMoveFlags )
	{
		// No more valid targets
		case 64:
        	bInterrupted = true;
        	MyPatPawn.StopBodyAnim( AnimStance, 0.1f );
       		PlayWindDownAnim();
       		break;

	    // Obstructed
	    case 128:
	    	bObstructed = true;
	    	MyPatPawn.StopBodyAnim( AnimStance, 0.1f );
	        PlayWindDownAnim();
	        break;
	}
}

/** Plays our fire animation, starts weapon fire */
function PlayFireAnim()
{
	if( bInterrupted || bObstructed )
	{
		return;
	}

	// Play dialog
	MyPatPawn.PlayMinigunAttackDialog();

	if( bIsFanFire )
	{
		MyPatPawn.bDisableTurnInPlace = true;

		bUseRootMotion = true;
		AnimStance = EAS_FullBody;
		EnableRootMotion();

		PlaySpecialMoveAnim( AnimName, AnimStance, 0.1f, 0.2f );
	}
	else
	{
		MyPatPawn.RotationRate = FocusFireRotationRate;
		MyPatPawn.bDisableTurnInPlace = false;

		bUseRootMotion = false;
		AnimStance = EAS_UpperBody;
		DisableRootMotion();

		PlaySpecialMoveAnim( AnimName, AnimStance, BlendInTime, BlendOutTime, 1.f );
	}

	// Zero movement
	MyPatPawn.ZeroMovementVariables();

	// Start firing
	if( MyPatPawn.Role == ROLE_Authority || MyPatPawn.IsLocallyControlled() )
	{
		MyPatPawn.Weapon.StartFire( 0 );
	}

	// Set a timer
	if( MyPatPawn.Role == ROLE_Authority && !bIsFanFire && !MyPatPawn.IsHumanControlled() )
	{
		MyPatPawn.SetTimer( 0.1f, true, nameOf(Timer_CheckIfFireAllowed), self );
		MyPatPawn.SetTimer( 2.0f, true, nameOf(Timer_SearchForMinigunTargets), self );
	}
}

/** Starts and stops minigun fire depending on whether pawn is directly facing enemy */
function Timer_CheckIfFireAllowed()
{
	local KFPawn KFP;
	local vector PawnDir, Projection, OtherProjection, PawnRot2D;
	local float DistSQ;

	if( MyPatController.Enemy == none
		|| !MyPatController.Enemy.IsAliveAndWell()
		|| !MyPatPawn.FastTrace(MyPatController.Enemy.Location, MyPatPawn.Location,, true) )
	{
		Timer_SearchForMinigunTargets();
	}

	// If for whatever reason we have no enemy, or enemy is dead, stop firing
	if( MyPatController.Enemy == none || !MyPatController.Enemy.IsAliveAndWell() )
	{
		MyPatPawn.DoSpecialMove( SM_HoseWeaponAttack, true,, 64 );
		return;
	}

	PawnDir = vector( MyPatPawn.Rotation );
	Projection = MyPatController.Enemy.Location - MyPatPawn.Location;
	DistSQ = VSizeSQ( Projection );

	// Search for enemies obstructing us and our enemy
	foreach MyPatPawn.WorldInfo.AllPawns( class'KFPawn', KFP )
	{
		OtherProjection = KFP.Location - MyPatPawn.Location;

		if( KFP != MyPatController.Enemy
			&& KFP.IsAliveAndWell() 
			&& KFP.GetTeamNum() != MyPatPawn.GetTeamNum()
			&& VSizeSQ(OtherProjection) < DistSQ
			&& PawnDir dot Normal(OtherProjection) >= 0.8f
			&& MyPatPawn.FastTrace(KFP.Location, MyPatPawn.Location,, true) 
			&& KFP.CanAITargetThisPawn(MyPatController))
		{
			MyPatController.ChangeEnemy( KFP, false );
			MyPatPawn.SetTimer( 2.0f, true, nameOf(Timer_SearchForMinigunTargets), self );
			MyPatPawn.SetGunTracking( true );
			if( !MyPatPawn.IsFiring() )
			{
				MyPatPawn.Weapon.StartFire( 0 );
			}
			return;
		}
	}

	if( MyPatPawn.Controller != none && MyPatController.Enemy != none )
	{
		PawnRot2D = PawnDir;
		PawnRot2D.Z = 0.f;
		if( PawnRot2D dot Normal2D(Projection) >= 0.5f )
		{
			if( !MyPatPawn.IsFiring() )
			{
				MyPatPawn.Weapon.StartFire( 0 );
			}
			return;
		}
	}

	if( MyPatPawn.IsFiring() )
	{
		MyPatPawn.StopFiring();
	}
}

/** Searches for new minigun targets */
function Timer_SearchForMinigunTargets()
{
	if( bIsFanFire || MyPatController == none )
	{
		return;
	}

	// Try to find a new enemy
	if( MyPatController.CheckForEnemiesInFOV(4000.f, 0.25f, 1.f, true, false) != none )
	{
		MyPatPawn.SetGunTracking( true );
		return;
	}

	// If we have no valid enemy, or no valid LOS to enemy, end barrage
	if ( MyPatController.Enemy == none || !MyPatPawn.FastTrace(MyPatController.Enemy.Location, MyPatPawn.Location,, true) )
	{
		MyPatPawn.DoSpecialMove( SM_HoseWeaponAttack, true,, 64 );
	}
}

/** Plays our wind down animation, stops firing, disables barrel spin skel controller */
function PlayWindDownAnim()
{
	if( bObstructed )
	{
		MyPatPawn.StopBodyAnim( AnimStance, 0.33f );
	}

	// Clear our fire rotation check timer
	MyPatPawn.ClearTimer( nameOf(Timer_CheckIfFireAllowed), self );

	// Clear our target switching timer
	MyPatPawn.ClearTimer( nameOf(Timer_SearchForMinigunTargets), self );

	// Sync weapon state
	if( MyPatPawn.Weapon != none && !MyPatPawn.Weapon.IsInState('Active') )
	{
		MyPatPawn.StopFiring();
		MyPatPawn.Weapon.GotoState('Active');
	}

	// Zero movement
	MyPatPawn.ZeroMovementVariables();

	if( bDisableMovement )
	{
		bUseRootMotion = true;
		AnimStance = EAS_FullBody;
		EnableRootMotion();
	}
	MyPatPawn.SpinMinigunBarrels( false );
	MyPatPawn.SetGunTracking( false );
	AnimStance = GetAnimStance();
	PlaySpecialMoveAnim( WindDownAnimName, AnimStance, BlendInTime, BlendOutTime, 1.f );

	// Play our minigun loop end sound
	MyPatPawn.PostAkEventOnBone( MinigunLoopEnd, 'BarrelSpinner', true, true );
}

/** Plays subsequent animations in the barrage */
function AnimEndNotify(AnimNodeSequence SeqNode, float PlayedTime, float ExcessTime)
{
	switch( DeferredSeqName )
	{
		case WindUpAnimName:
			PlayFireAnim();
			break;

		case AnimName:
			PlayWindDownAnim();
			break;

		case WindDownAnimName:
			KFPOwner.EndSpecialMove();
			break;
	}
}

function SpecialMoveEnded( Name PrevMove, Name NextMove )
{
	if( MyPatPawn != none )
	{
	    if( !bObstructed )
	    {
	    	MyPatPawn.StartWeaponCooldown();
	    }

	    // Stop body anims if this move was interrupted
		if( MyPatPawn.IsFiring() )
		{
			// If still playing an upperbody or fullbody animation allow it to be interrupted
		 	if( KFPOwner.BodyStanceNodes[AnimStance].bIsPlayingCustomAnim )
		 	{
				KFPOwner.StopBodyAnim( AnimStance, AbortBlendOutTime );
			}
		}

		// Sync weapon state
		if( MyPatPawn.Weapon != none && !MyPatPawn.Weapon.IsInState('Active') )
		{
			MyPatPawn.StopFiring();
			MyPatPawn.Weapon.GotoState('Active');
		}

		// Play our minigun loop end sound
		if( MyPatPawn.bSpinBarrels )
		{
			MyPatPawn.PostAkEventOnBone( MinigunLoopEnd, 'BarrelSpinner', true, true );
		}

		MyPatPawn.bEnableAimOffset = false;
		MyPatPawn.SetDefaultAimOffsetNodesProfile();
		MyPatPawn.ClearTimer( nameOf(Timer_SearchForMinigunTargets), self );
		MyPatPawn.ClearTimer( nameOf(Timer_CheckIfFireAllowed), self );
		MyPatPawn.ClearTimer( nameOf(Timer_CheckEnemyLOS), self );
		MyPatPawn.SetGunTracking( false );
		MyPatPawn.SpinMinigunBarrels( false );
		MyPatPawn.bSprayingFire = false;
		MyPatPawn.RotationRate = MyPatPawn.default.RotationRate;
		MyPatPawn.bDisableTurnInPlace = MyPatPawn.default.bDisableTurnInPlace;
		MyPatPawn = none;
	}

	super.SpecialMoveEnded( PrevMove, NextMove );
}

DefaultProperties
{
	// SpecialMove
	Handle=KFSM_Patriarch_MinigunBarrage
	bDisableSteering=false
	bDisableMovement=true
   	bCanBeInterrupted=false
	bAllowFireAnims=true
	FocusFireRotationRate=(Pitch=30000,Yaw=30000,Roll=30000)
	VisibilityCheckTime=1.0f
    bShouldDeferToPostTick=true

	// Sound
	MinigunLoop=AkEvent'WW_ZED_Patriarch.Play_Mini_Gun_Start'
	MinigunLoopEnd=AkEvent'WW_ZED_Patriarch.Play_Mini_Gun_Stop'

   	// Animation
	WindUpAnimName=Gun_TO_Load
	WindDownAnimName=Gun_TO_Idle
	AnimName=Gun_Idle
	FanAnimNames(0)=Gun_Shoot_Fan_V1
	FanAnimNames(1)=Gun_Shoot_Fan_V3
	AimOffsetProfileName=Minigun
		
	BlendInTime=0.15f
	BlendOutTime=0.1f
	AbortBlendOutTime=0.1f
}