//=============================================================================
// KFAIController_ZedPatriarch
//=============================================================================
// The Patriarch's AIController
//=============================================================================
// Killing Floor 2
// Copyright (C) 2015 Tripwire Interactive LLC
//=============================================================================

class KFAIController_ZedPatriarch extends KFAIController_ZedBoss;

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

/*********************************************************************************************
* Enemy Handling
**********************************************************************************************/

/** Whether attack evaluation is enabled or not */
var bool bCanEvaluateAttacks;

/** How long the Patriarch should wait to start sprinting after losing sight of his enemy */
var float LostSightSprintDelay;

/** How long to wait before ticking off aggro damage */
var float AggroFalloffWaitTime;

/** How much damage to tick off of accumulated aggro, per second */
var float AggroFalloffPerSecond;

/** The threshold at which aggro from a visible player becomes critical */
var float VisibleAggroDmgThreshold;

/** The threshold at which aggro from a hidden player becomes critical */
var float HiddenAggroDmgThreshold;

/**
 *	Struct to track AI knowledge of visible enemies
 */
struct Patriarch_TrackedEnemyInfo
{
	/** Enemy we're tracking */
	var KFPawn 			TrackedEnemy;
	/** Last time enemy was visible */
	var float 			LastTimeVisible;
	/** Last location enemy was visible */
	var vector 			LastVisibleLocation;
	/** The last time we took damage from this enemy */
	var float 			LastTakeDamageTime;
	/** Accumulated damage towards this enemy's aggro rating */
	var int 			AggroDamage;
};

/** List of tracked enemies that have recently been seen */
var array<Patriarch_TrackedEnemyInfo> RecentlySeenEnemyList;

/** List of tracked enemies that are hidden */
var array<Patriarch_TrackedEnemyInfo> HiddenEnemies;

/** List of enemies that have been minigun attacked */
var array<KFPawn> LastMinigunEnemies;

/** Minimum distance for a minigun attack */
var float MinMinigunRangeSQ;

/** Maximum distance for a minigun attack */
var float MaxMinigunRangeSQ;

/** Maximum distance enemies are allowed to be for a fan fire attack */
var float MaxFanFireRangeSQ;

/** List of enemies that have been charge attacked */
var array<KFPawn> LastChargedPlayers;

/** Min distance an enemy can be for a charge */
var float MinChargeRangeSQ;

/** Are we doing a charge attack? */
var bool bDoingChargeAttack;

/** Last time we executed a charge attack */
var float LastChargeAttackTime;

/** If we were charging a player, but were interrupted by an obstruction */
var bool bWantsToCharge;

/** Cached charge target */
var Pawn CachedChargeTarget;

/** List of enemies that have been tentacle grabbed */
var array<KFPawn> LastGrabbedPlayers;

/** Last time we executed a tentacle grab */
var float LastGrabAttackTime;

/** Minimum distance to be able to use a tentacle attack */
var float MinTentacleRangeSQ;

/** List of enemies that have been missile attacked */
var array<KFPawn> LastMissileEnemies;

/** Last time we executed a missile attack */
var float LastMissileAttackTime;

/** Min distance an enemy can be for a missile attack */
var float MinMissileRangeSQ;

/** Max distance an enemy can be for a missile attack */
var float MaxMissileRangeSQ;

/** Last time we executed a mortar attack */
var float LastMortarAttackTime;

/** Last time we successfully hit an enemy with a melee attack */
var float LastSuccessfulAttackTime;

/** Variable used for determining if the Patriarch should pick a minigun or missile attack */
var bool bHadMinigunAttack;

/* How often to update RecentlySeenEnemyList */
var() float RecentSeenEnemyListUpdateInterval;

/* Last time we updated the RecentlySeenEnemyList*/
var float LastRecentSeenEnemyListUpdateTime;

/** Last time we finished a melee attack */
var float LastAttackMoveFinishTime;

/** How long to wait before evaluating special attacks */
var float NextAttackCheckTime;

/** Last time we activated a battle phase sprint */
var float LastSprintTime;

/** Whether we should sprint until we complete a melee attack */
var bool bSprintUntilAttack;

/** The last time we changed to a new target */
var float LastRetargetTime;

/** How long to wait before attempting to find a new target */
var float RetargetWaitTime;

/*********************************************************************************************
* Paternal Instinct
**********************************************************************************************/

/** True if we've entered into rage mode */
var bool bRaging;

/** Whether we've raged this phase or not */
var bool bRagedThisPhase;

/** Maximum number of attacks to do before ending rage mode */
var int MaxRageAttacks;

/** Current tally of rage attacks done */
var int RageAttackCount;

/** Maximum distance an enemy can be to be considered for a rage attack */
var float MaxRageRangeSQ;

/** Time before forcing rage mode to end */
var float RageTimeOut;

/** Players that have already been attacked in this rage state */
var array<KFPawn> RageAttackedTargets;

/*********************************************************************************************
* Flee And Heal
**********************************************************************************************/

/** The amount of damage to do to bumped humans when fleeing */
var int HumanBumpDamage;

/** How much momentum to apply to humans when bumping them */
var int HumanBumpMomentum;

/** Do we have a pending flee? */
var bool bWantsToFlee;

/** Whether we are currently fleeing or not */
var bool bFleeing;

/** The health threshold at which a flee and heal should be triggered */
var float FleeHealthThreshold;

/** The maximum amount of time that can be spent in the flee state */
var float MaxFleeDuration;

/** The maximum distance allowed to flee before command pops */
var float MaxFleeDistance;

/** The start time of the last flee */
var float FleeStartTime;

/** The total cumulative time spent fleeing */
var float TotalFleeTime;

/** Whether the flee was interrupted by the targeting loop */
var bool bFleeInterrupted;

/*********************************************************************************************
* Initialization, Pawn Possession, and Destruction
********************************************************************************************* */

/** Set MyPatPawn to avoid casting */
event Possess( Pawn inPawn, bool bVehicleTransition )
{
	if( KFPawn_ZedPatriarch(inPawn) != none )
	{
		MyPatPawn = KFPawn_ZedPatriarch( inPawn );
	}
	else
	{
		`warn( GetFuncName()$"() attempting to possess "$inPawn$", but it's not a KFPawn_ZedPatriarchBase class! MyPatPawn variable will not be valid." );
	}

	// Initialize retarget time
	LastRetargetTime = WorldInfo.TimeSeconds;

	// Initialize sprint time
	LastSprintTime = WorldInfo.TimeSeconds;
	LastSuccessfulAttackTime = WorldInfo.TimeSeconds;
	LastGrabAttackTime = WorldInfo.TimeSeconds;
	LastMissileAttackTime = WorldInfo.TimeSeconds;
	LastMortarAttackTime = WorldInfo.TimeSeconds;
	
	// Wait a bit before evaluating special attacks
	NextAttackCheckTime = 2.5f + fRand();

	// Start evaluating on next tick
	bCanEvaluateAttacks = true;

	super.Possess( inPawn, bVehicleTransition );
}

function PawnDied( Pawn InPawn )
{
	if( MyPatPawn != none )
	{
		//`DialogManager.PlayBossDeathDialog( MyHansPawn );
		MyPatPawn = None;
	}
	Super.PawnDied( InPawn );
}

/** Clean up all internal objects and references when the AI is destroyed
	TBD: Look into using Unpossessed() to do this! */
simulated event Destroyed()
{
	if( MyPatPawn != none )
	{
		MyPatPawn.ClearMortarTargets();
	}
	MyPatPawn = None;
	MyKFGameInfo.SpawnManager.StopSummoningBossMinions();

	super.Destroyed();
}

/*********************************************************************************************
* Enemy and Attack Handling
********************************************************************************************* */

/** We can't allow aggro enemy switches during certain special moves */
function bool IsAggroEnemySwitchAllowed()
{
	return super.IsAggroEnemySwitchAllowed()
			&& !MyPatPawn.IsDoingSpecialMove(SM_StandAndShootAttack)
			&& !MyPatPawn.IsDoingSpecialMove(SM_HoseWeaponAttack)
			&& !MyPatPawn.IsDoingSpecialMove(SM_GrappleAttack);
}

/** Whether enemy switch commands can be run */
function bool CanSwitchEnemies()
{
	return !bWantsToFlee
			&& !bFleeing
			&& MyPatPawn != none
			&& !MyPatPawn.bIsCloaking
			&& !MyPatPawn.IsDoingSpecialMove(SM_Heal)
			&& !MyPatPawn.IsDoingSpecialMove(SM_StandAndShootAttack)
			&& !MyPatPawn.IsDoingSpecialMove(SM_SonicAttack)
			&& !MyPatPawn.IsDoingSpecialMove(SM_GrappleAttack);
}

/** Returns an aggro rating on a scale of 0.0f to 1.0f */
function float GetAggroRating( KFPawn KFP )
{
	local int EnemyIndex;
	local float AggroDmg;

	EnemyIndex = RecentlySeenEnemyList.Find( 'TrackedEnemy', KFP );
	if( EnemyIndex != INDEX_NONE )
	{
		AggroDmg = RecentlySeenEnemyList[EnemyIndex].AggroDamage;
		if( AggroDmg > 0.f )
		{
			return fMin( AggroDmg/VisibleAggroDmgThreshold, 1.f );
		}
		else
		{
			return 0.f;
		}
	}
	else
	{
		EnemyIndex = HiddenEnemies.Find( 'TrackedEnemy', KFP );
		if( EnemyIndex != INDEX_NONE )
		{
			AggroDmg = HiddenEnemies[EnemyIndex].AggroDamage;
			if( AggroDmg > 0.f )
			{
				return fMin( AggroDmg/HiddenAggroDmgThreshold, 1.f );
			}
			else
			{
				return 0.f;
			}
		}
		else
		{
			return 0.5f;
		}
	}

	return 1.f;
}

/** Overridden to stop retargeting enemies when fleeing or cloaked */
event bool FindNewEnemy()
{
	if( !CanSwitchEnemies() )
	{
		return false;
	}

	return super.FindNewEnemy();
}

/** Overridden to stop retargeting enemies when fleeing or cloaked */
event bool SetEnemy( Pawn NewEnemy )
{
	if( !CanSwitchEnemies() )
	{
		return false;
	}

	return super.SetEnemy( NewEnemy );
}

/** Forces the enemy to the new pawn, bypassing CanSwitchEnemies() */
function ForceSetEnemy( Pawn NewEnemy )
{
	super.SetEnemy( NewEnemy );
}

/** Overridden to stop retargeting enemies when fleeing or cloaked */
event ChangeEnemy( Pawn NewEnemy, optional bool bCanTaunt = true )
{
	local Pawn OldEnemy;

	if( !CanSwitchEnemies() )
	{
		return;
	}

	OldEnemy = Enemy;

	super.ChangeEnemy( NewEnemy, !MyPatPawn.bIsCloaking && bCanTaunt );

	if( OldEnemy != Enemy )
	{
		LastRetargetTime = WorldInfo.TimeSeconds;
	}
}

/** Sets the enemy to the best target, based on several factors */
function bool SetBestTarget( out array<KFPawn> RecentTargets,
						optional float MinDistSQ=-1.f,
						optional float MaxDistSQ=-1.f,
						optional float ClampFOV=-1.f,
						optional bool bPreferFurtherTargets,
						optional bool bIsWeaponAttack )
{
	local KFPawn_Human KFPH;
	local KFPawn KFP, BestTarget;
	local int i, FoundIndex, BestIndex;
	local float TempRating, BestRating;
	local vector TempDist;

	// Filter out dead/absent players
	for( i = 0; i < RecentTargets.Length; ++i )
	{
		if( RecentTargets[i] == none || !RecentTargets[i].IsAliveAndWell() )
		{
			RecentTargets.Remove( i, 1 );
			i--;
		}
	}

	// If we've had no previously charged players or in solo play, charge enemy
	if( RecentTargets.Length == 0 || WorldInfo.NetMode == NM_StandAlone )
	{
		// Fallback
		if( Enemy == none )
		{
			//if( !bPreferFurtherTargets )
			//{
				ChangeEnemy( GetClosestEnemy(), false );
			//}
		}

		// Can't get an enemy for whatever reason
		if( Enemy == none )
		{
			return false;
		}

		// Initial rating value is based on distance
		TempRating = VSizeSQ( MyPatPawn.Location - Enemy.Location );

		// Early out if we're outside distance limits
		if( (MinDistSQ >= 0.f && TempRating < MinDistSQ) || (MaxDistSQ > 0.f && TempRating > MaxDistSQ) )
		{
			return false;
		}

		// Even if we can see our targets, make sure that our gun has clearance
		if( bIsWeaponAttack && !IsWeaponArmClear(Enemy.Location) )
		{
			return false;
		}

		KFP = KFPawn( Enemy );
		FoundIndex = RecentTargets.Find( KFP );
		if( FoundIndex == INDEX_NONE )
		{
			RecentTargets[RecentTargets.Length] = KFP;
		}

		return true;
	}

	// Try to find a better target
	foreach WorldInfo.AllPawns( class'KFPawn_Human', KFPH )
	{
		if( KFPH != none && KFPH.IsAliveAndWell() )
		{
			// Initial rating value is based on distance
			TempDist = KFPH.Location - MyPatPawn.Location;
			TempRating = VSizeSQ( TempDist );

			// Filter based on distance limits, if we have any
			if( (MinDistSQ >= 0.f && TempRating < MinDistSQ) || (MaxDistSQ > 0.f && TempRating > MaxDistSQ) )
			{
				continue;
			}

			// Filter out pawns not in the clamp FOV, if we have one
			if( vector(Pawn.Rotation) dot Normal(TempDist) < ClampFOV )
			{
				continue;
			}

			// Even if we can see our targets, make sure that our gun has clearance
			if( bIsWeaponAttack && !IsWeaponArmClear(KFPH.Location) )
			{
				return false;
			}

			// Recently attacked players get lowest priority
			FoundIndex = RecentTargets.Find( KFPH );
			if( FoundIndex != INDEX_NONE )
			{
				// Scale priority based on position in list -- the higher the more recent
				TempRating *= bPreferFurtherTargets ? 
							( 2.5f + 2.5f*(FMax(float(FoundIndex),1.f)/RecentTargets.Length) ) :
							( 1.f - 0.5f*(FMax(float(FoundIndex),1.f)/RecentTargets.Length) );
			}

			// Lower priority for players that haven't been seen recently
			if( RecentlySeenEnemyList.Find('TrackedEnemy', KFPH) == INDEX_NONE )
			{
				TempRating *= bPreferFurtherTargets ? 0.5f : 2.0f;
			}

			// Apply aggro rating
			TempRating *= bPreferFurtherTargets ? 0.5f + 0.5f*GetAggroRating(KFPH) : 1.0f - 0.5f*GetAggroRating(KFPH);

			if( BestRating == 0.f || ((bPreferFurtherTargets && TempRating > BestRating)
				|| (!bPreferFurtherTargets && TempRating < BestRating)) )
			{
				BestTarget = KFPH;
				BestRating = TempRating;
				BestIndex = FoundIndex;
			}
		}
	}

	if( BestTarget != none )
	{
		// Set our enemy
		ChangeEnemy( BestTarget, false );

		// Shuffle target out of current spot if chosen from array
		if( BestIndex != INDEX_NONE )
		{
			RecentTargets.Remove( BestIndex, 1 );
		}

		// Place target at end of array
		RecentTargets[RecentTargets.Length] = BestTarget;

		return true;
	}

	return false;
}

/** Override to tick the ranged combat system */
simulated function Tick( FLOAT DeltaTime )
{
	Super.Tick(DeltaTime);

	UpdateRecentlySeenEnemyList();
	EvaluateAttacks( DeltaTime );
}

/** NPC has seen a player - use SeeMonster for similar notifications about seeing any pawns (currently not in use) */
event SeePlayer( Pawn Seen )
{
    local int EnemyListIndex, HiddenEnemyIndex;
    local Patriarch_TrackedEnemyInfo NewTrackedEnemy;
    local KFPawn KFP;

    Super.SeePlayer(Seen);

	// Evaluate sprinting when visibility changes
    EvaluateSprinting();

	// Reject potential enemy if it's invalid or not on our team, or if it's already my current enemy, or if my pawn is dead or invalid
	if( Seen == none || !Seen.IsAliveAndWell() || Pawn.IsSameTeam( Seen ) ||
		Pawn == none || !Pawn.IsAliveAndWell() || !Seen.CanAITargetThisPawn(self) )
	{
		return;
	}

    KFP = KFPawn(Seen);

    // Add or update recently seen enemy in the list for tracking ranged enemies
    if( KFP != none )
    {
        EnemyListIndex = RecentlySeenEnemyList.Find('TrackedEnemy', KFP);
        if( EnemyListIndex == INDEX_NONE )
        {
        	HiddenEnemyIndex = HiddenEnemies.Find('TrackedEnemy', KFP);
        	if( HiddenEnemyIndex != INDEX_NONE )
        	{
        		HiddenEnemies.Remove(HiddenEnemyIndex, 1);
        	}
            NewTrackedEnemy.TrackedEnemy = KFP;
            NewTrackedEnemy.LastTimeVisible = WorldInfo.TimeSeconds;
            NewTrackedEnemy.LastVisibleLocation = Seen.Location;

			// Delay attacks a bit if we are just acquiring an enemy after having not seen anyone
			if( RecentlySeenEnemyList.Length == 0 )
			{
				NextAttackCheckTime = 1.5f + fRand();
			}

            RecentlySeenEnemyList[RecentlySeenEnemyList.Length] = NewTrackedEnemy;
        }
        else
        {
            RecentlySeenEnemyList[EnemyListIndex].LastTimeVisible = WorldInfo.TimeSeconds;
            RecentlySeenEnemyList[EnemyListIndex].LastVisibleLocation = Seen.Location;
        }

        LastEnemySightedTime = WorldInfo.TimeSeconds;
    }
}

/** Update the ranged enemy visibility tracking */
function UpdateRecentlySeenEnemyList()
{
    local int i;
    local bool bWantsNewEnemy;

    if( LastRecentSeenEnemyListUpdateTime == 0 || `TimeSince(LastRecentSeenEnemyListUpdateTime) > RecentSeenEnemyListUpdateInterval )
    {
        LastRecentSeenEnemyListUpdateTime = WorldInfo.TimeSeconds;

    	for( i = RecentlySeenEnemyList.Length-1; i >= 0; i-- )
    	{
    		bWantsNewEnemy = false;

			if( RecentlySeenEnemyList[i].TrackedEnemy == none
				|| !RecentlySeenEnemyList[i].TrackedEnemy.IsAliveAndWell()
				|| !RecentlySeenEnemyList[i].TrackedEnemy.CanAITargetThisPawn(self)
				|| `TimeSince(RecentlySeenEnemyList[i].LastTimeVisible) > 0.5f )
			{
				// If enemy is still valid but out of view, add it to hidden enemies
				if( RecentlySeenEnemyList[i].TrackedEnemy != none && RecentlySeenEnemyList[i].TrackedEnemy.IsAliveAndWell() )
				{
					HiddenEnemies[HiddenEnemies.Length] = RecentlySeenEnemyList[i];
				}

				// Try to get a new enemy
				if( Enemy == RecentlySeenEnemyList[i].TrackedEnemy )
				{
					if( RecentlySeenEnemyList.Length > 1 )
					{
						bWantsNewEnemy = true;
					}
					else
					{
						FindNewEnemy();
					}
				}

				RecentlySeenEnemyList.Remove(i, 1);

				if( bWantsNewEnemy )
				{
					ChangeEnemy( RecentlySeenEnemyList[Rand(RecentlySeenEnemyList.Length)].TrackedEnemy );
				}
			}
			else if( CanSee(RecentlySeenEnemyList[i].TrackedEnemy) )
			{
				if( `TimeSince(RecentlySeenEnemyList[i].LastTakeDamageTime) > AggroFalloffWaitTime )
				{
		    		RecentlySeenEnemyList[i].AggroDamage -= fMax( AggroFalloffPerSecond * (RecentSeenEnemyListUpdateInterval/1.f), 0.f );
		    	}
				RecentlySeenEnemyList[i].LastVisibleLocation = RecentlySeenEnemyList[i].TrackedEnemy.Location;
				RecentlySeenEnemyList[i].LastTimeVisible = WorldInfo.TimeSeconds;
			}
    	}

    	for( i = HiddenEnemies.Length-1; i >= 0; i-- )
    	{
			if( `TimeSince(HiddenEnemies[i].LastTakeDamageTime) > AggroFalloffWaitTime )
			{    		
	    		HiddenEnemies[i].AggroDamage -= fMax( AggroFalloffPerSecond * (RecentSeenEnemyListUpdateInterval/1.f), 0.f );
	    	}
			if( HiddenEnemies[i].TrackedEnemy == none
				|| !HiddenEnemies[i].TrackedEnemy.IsAliveAndWell()
				|| !HiddenEnemies[i].TrackedEnemy.CanAITargetThisPawn(self) )
			{
				HiddenEnemies.Remove(i, 1);
			}
    	}
    }
}

/** Evaluates whether or not the Patriarch can do a special attack */
function EvaluateAttacks( float DeltaTime )
{
	local bool bCanFireMinigun, bCanFireMissile, bCanFireMortar, bShouldFireMortar, bShouldFireMissile, bMortarBarrage;

	if( !bCanEvaluateAttacks )
	{
		return;
	}

    //Don't attack while we're in theatrics
    if (CommandList != none && CommandList.class == class'AICommand_BossTheatrics')
    {
        return;
    }

	NextAttackCheckTime -= DeltaTime;

	if( bWantsToCharge || bWantsToFlee || bFleeing || MyPatPawn == none || MyPatPawn.IsDoingSpecialMove() || NextAttackCheckTime > 0.f )
	{
		return;
	}

	// Evaluate grab attack
	if ( MyPatPawn.CanTentacleGrab()
		&& RecentlySeenEnemyList.Length > 0
		&& `TimeSince(LastGrabAttackTime) > MyPatPawn.TentacleGrabCooldownTime
		&& (!MyPatPawn.bIsCloaking || fRand() < 0.25f) )
	{
		if( SetBestTarget(LastGrabbedPlayers, MinTentacleRangeSQ, Square(class'KFSM_Patriarch_Grapple'.default.MaxRange*0.8f*MyPatPawn.GetAttackRangeScale()), 0.4f, true, true) )
		{
			MyPatPawn.SetCloaked( false );
			class'AICommand_Patriarch_Grab'.static.TentacleGrab( self );
			return;
		}
	}

	// Evaluate charge attack
	if( !MyPatPawn.bIsCloaking
		&& MyPatPawn.CanChargeAttack()
		&& (bHadMinigunAttack || fRand() < 0.75f) 
		&& `TimeSince(LastChargeAttackTime) > MyPatPawn.ChargeAttackCooldownTime )
	{
		// Make sure we have a target
		if( SetBestTarget(LastChargedPlayers, MinChargeRangeSQ) )
		{
			// Cache our charge target
			CachedChargeTarget = Enemy;

			bDoingChargeAttack = true;
			bSprintUntilAttack = true;
			MyPatPawn.SetSprinting( true );
			MyPatPawn.SetCloaked( true );

			// Delay ranged attack checking a bit
			NextAttackCheckTime = 2.5f + fRand();

			SetTimer( 2.f, false, nameOf(Timer_SearchForChargeObstructions) );
			return;
		}
	}

	// Evaluate weapon attack
	bCanFireMinigun = (!MyPatPawn.CanChargeAttack() || fRand() < 0.5f)
						&& (!MyPatPawn.bIsCloaking || fRand() < 0.75f)
						&& RecentlySeenEnemyList.Length > 0 
						&& `TimeSince(LastSuccessfulAttackTime) > MyPatPawn.MinigunAttackCooldownTime;
	bCanFireMissile = (!MyPatPawn.CanChargeAttack() || fRand() < 0.5f)
						&& (!MyPatPawn.bIsCloaking || fRand() < 0.75f)
						&& MyPatPawn.CanMissileAttack() && RecentlySeenEnemyList.Length > 0
						&& `TimeSince(LastMissileAttackTime) > MyPatPawn.MissileAttackCooldownTime;
	bCanFireMortar = !MyPatPawn.bIsCloaking
						&& MyPatPawn.CanMortarAttack()
						&& `TimeSince(LastMortarAttackTime) > MyPatPawn.MortarAttackCooldownTime;

	if( bCanFireMissile || bCanFireMinigun || bCanFireMortar )
	{
		// Decide if we can fire our mortar attack
		bShouldFireMortar = bCanFireMortar
							&& (!bCanFireMissile || fRand() < 0.5f)
							&& (!bCanFireMinigun || fRand() < 0.75f)
							&& IsCeilingClear();

		if( bShouldFireMortar )
		{
			// Random chance to do an area denial
			if( MyPatPawn.CanDoMortarBarrage() && fRand() < 0.2f )
			{
				bMortarBarrage = true;
				bShouldFireMortar = MyPatPawn.CollectMortarTargets( true, true );
			}
			else if( HiddenEnemies.Length > 0 )
			{
				bMortarBarrage = false;
				bShouldFireMortar = SomeEnemiesAreHidden() && MyPatPawn.CollectMortarTargets( true );
			}
			else
			{
				bShouldFireMortar = false;
			}
		}

		// Decide whether we should fire a missile or the minigun
		bShouldFireMissile = !bShouldFireMortar && bCanFireMissile
							&& (((bCanFireMinigun || !bHadMinigunAttack) && fRand() < 0.2f) || fRand() < 0.6f)
							&& SetBestTarget( LastMissileEnemies, MinMissileRangeSQ, MaxMissileRangeSQ*MyPatPawn.GetAttackRangeScale(), 0.5f, true, true );

		// No other attacks, find a minigun target!
		if( bCanFireMinigun && !bShouldFireMissile && !bShouldFireMortar )
		{
			bCanFireMinigun = SetBestTarget( LastMinigunEnemies, MinMinigunRangeSQ, MaxMinigunRangeSQ*MyPatPawn.GetAttackRangeScale(), 0.25f, false, true );
		}

		if( bShouldFireMortar )
		{
			class'AICommand_Patriarch_MortarAttack'.static.FireMortar( self, bMortarBarrage );
			return;
		}
		else if( bShouldFireMissile )
		{
			bHadMinigunAttack = false;
			MyPatPawn.SetCloaked( false );
			class'AICommand_Patriarch_MissileAttack'.static.FireMissiles( self );
			return;
		}
		else if( bCanFireMinigun )
		{
			bHadMinigunAttack = true;
			MyPatPawn.SetCloaked( false );
			class'AICommand_Patriarch_MinigunBarrage'.static.MinigunBarrage( self );
			return;
		}
	}

	// Evaluate attacks every half second
	NextAttackCheckTime = 0.5f;
}

/** Returns true if the path from the weapon arm to the target location is clear */
function bool IsWeaponArmClear( vector EndTrace )
{
	local vector StartTrace;

	MyPatPawn.Mesh.GetSocketWorldLocationAndRotation( 'MissileCenter', StartTrace );

	return MyPatPawn.FastTrace( EndTrace, StartTrace,, true );
}

/**
 * Adjusts aim to always point at the enemy we're targeting
 * @param	W, weapon about to fire
 * @param	StartFireLoc, world location of weapon fire start trace, or projectile spawn loc.
 */
function Rotator GetAdjustedAimFor( Weapon W, vector StartFireLoc )
{
	if( Enemy != none )
	{
		return rotator(Enemy.Location - StartFireLoc);
	}

	return super.GetAdjustedAimFor( W, StartFireLoc );
}

function DoStrike()
{
	local name AttackName;

	if( MyPatPawn != none && MyPatPawn.PawnAnimInfo != none )
	{
		AttackName = MyPatPawn.PawnAnimInfo.Attacks[PendingAnimStrikeIndex].Tag;

		// @todo: figure out a way to make this less hard-coded? (see also KFDialogManager::PlayHansKilledDialog)
		if( AttackName == 'Radial' )
		{
			`DialogManager.PlayPattyWhirlwindDialog( MyPatPawn );
		}
	}

	super.DoStrike();
}

/*********************************************************************************************
* Sprinting
**********************************************************************************************/

/** Evaluate if we should start/stop sprinting, and then set the sprinting flag */
function EvaluateSprinting()
{
    if( MyKFPawn != none && MyKFPawn.IsAliveAndWell() && Enemy != none )
	{
		if( ShouldSprint() )
		{
			MyKFPawn.SetSprinting( true );
		}
		else
		{
			MyKFPawn.SetSprinting( false );
		}
	}
}

/** Timer function called during latent moves that determines whether NPC should sprint or stop sprinting */
function bool ShouldSprint()
{
	if( Enemy != none && MyPatPawn != none && !MyPatPawn.bIsHeadless )
	{
		// Don't allow sprinting in minigun attack
		if( MyPatPawn.IsDoingSpecialMove(SM_HoseWeaponAttack) )
		{
			return false;
		}

		// Always sprint if fleeing
        if( bFleeing )
		{
			//`log(self@GetFuncName()$" bInFleeAndHealMode should sprint!");
            return true;
		}

		// Always sprint if cloaked
		if( MyPatPawn.bIsCloaking )
		{
			//`log(self@GetFuncName()$" bIsCloaking should sprint!");
			return true;
		}

		// Always sprint if raging
        if( bRaging )
		{
			//`log(self@GetFuncName()$" In Paternal Instinct mode, should sprint!");
            return true;
		}

		// EMP check always comes after any forced sprint state
		if( MyPatPawn.bEmpPanicked )
		{
			return false;
		}

		// Sprint until we attack
		if( bSprintUntilAttack )
		{
			//`log(self@GetFuncName()$" sprint until attack, should sprint!");
			return true;
		}

		// Sprint if we can't see our enemy
		if( LastEnemySightedTime == 0 || `TimeSince(LastEnemySightedTime) > LostSightSprintDelay )
		{
			//`log(self@GetFuncName()$" don't see any enemy should sprint = true! LastEnemySightedTime: "$LastEnemySightedTime$" TimeSince(LastEnemySightedTime): "$`TimeSince(LastEnemySightedTime));
            return true;
		}

        //`log(self@GetFuncName()$" Generic should sprint depending on phase: "$MyPatPawn.DesireSprintingInThisPhase());
        if( MyPatPawn.DesireSprintingInThisPhase() && `TimeSince(LastSprintTime) > MyPatPawn.SprintCooldownTime )
        {
        	bSprintUntilAttack = true;
        	return true;
        }
	}
	//`log(self@GetFuncName()$" Generic should sprint = false!");
	return false;
}

/** Disallow sprinting if we're in a minigun */
function bool CanSetSprinting( bool bNewSprintStatus )
{
	return super.CanSetSprinting( bNewSprintStatus ) && ( !bNewSprintStatus || !MyPatPawn.IsDoingSpecialMove(SM_HoseWeaponAttack) );
}

/*********************************************************************************************
* Mortar
**********************************************************************************************/

/** Returns true if the ceiling is clear for a mortar attack */
function bool IsCeilingClear()
{
	local vector TraceStart, TraceEnd, Extent;

	TraceStart = MyPatPawn.Location + vect(0,0,1)*MyPatPawn.GetCollisionHeight();
	TraceEnd = TraceStart + vect(0,0,1)*500.f;
	Extent.X = MyPatPawn.GetCollisionRadius() * 2.f;
	Extent.Y = Extent.X;
	Extent.Z = 1.f;

	return MyPatPawn.FastTrace( TraceEnd, TraceStart, Extent, true );
}

/** Returns true if a certain percentage of enemies are hidden */
function bool SomeEnemiesAreHidden()
{
	local KFPawn KFP;
	local float TargetDist;
	local int i, NumValidHiddenEnemies;

	for( i = 0; i < HiddenEnemies.Length; ++i )
	{
		KFP = HiddenEnemies[i].TrackedEnemy;
		if( !KFP.IsAliveAndWell() || MyPatPawn.IsSameTeam(KFP) )
		{
			continue;
		}

		// Too close or too far
		TargetDist = VSizeSQ(KFP.Location - MyPatPawn.Location);
		if( TargetDist < MyPatPawn.MinMortarRangeSQ || TargetDist > MyPatPawn.MaxMortarRangeSQ )
		{
			continue;
		}

		NumValidHiddenEnemies++;

		if( NumValidHiddenEnemies >= 2
			|| NumValidHiddenEnemies >= (Max(HiddenEnemies.Length + RecentlySeenEnemyList.Length, 1)/2) )
		{
			return true;
		}
	}

	return false;
}

/*********************************************************************************************
* Notifications
**********************************************************************************************/

/** Overloaded to handle door usage in cloaked state */
function NotifyAttackDoor( KFDoorActor Door )
{
    // We need to count up the total flee time so infinite fleeing isn't possible
    if( bFleeing || bWantsToFlee )
    {
    	if( bFleeing )
    	{
	        bFleeInterrupted = true;
	        bFleeing = false;
	        TotalFleeTime = TotalFleeTime + (WorldInfo.TimeSeconds - FleeStartTime);
	        bWantsToFlee = true;
	    }
        
        // Kill our flee and move commands
        AbortCommand( CommandList );

        // Allow melee again
        EnableMeleeRangeEventProbing();

        // Restart default command
        BeginCombatCommand( GetDefaultCommand(), "Restarting default command" );
    }
	else if( MyPatPawn.bIsCloaking )
	{
		MyPatPawn.SetCloaked( false );
		bWantsToCharge = true;
	}

	super.NotifyAttackDoor( Door );
}

/** Overloaded to handle door usage in cloaked state */
function bool DoorFinished()
{
	local bool bSuperFinished;

	bSuperFinished = super.DoorFinished();

	if( bWantsToFlee && !bFleeing )
	{
		if( MyPatPawn.IsDoingSpecialMove() )
		{
			MyPatPawn.EndSpecialMove();
		}
		Flee();
	}
	else if( bWantsToCharge )
	{
		ChangeEnemy( CachedChargeTarget, false );
		bDoingChargeAttack = true;
		bSprintUntilAttack = true;
		bWantsToCharge = false;
		MyPatPawn.SetSprinting( true );
		MyPatPawn.SetCloaked( true );
		SetTimer( 2.f, false, nameOf(Timer_SearchForChargeObstructions) );		

		// Delay ranged attack checking a bit
		NextAttackCheckTime = 2.5f + fRand();
	}

	return bSuperFinished;
}

/** Overloaded to decloak patty and set pending flee */
function NotifyAttackActor( Actor A )
{
	// do nothing for now, causes issues

	/**
	// We need to count up the total flee time so infinite fleeing isn't possible
	if( bFleeing )
	{
		TotalFleeTime = TotalFleeTime + (WorldInfo.TimeSeconds - FleeStartTime);
		bWantsToFlee = true;
		bFleeInterrupted = true;
		bFleeing = false;
		
		// Kill our flee command
		AbortCommand( FindCommandOfClass(class'AICommand_Flee') );

		// Allow melee again
		EnableMeleeRangeEventProbing();
	}
	else if( MyPatPawn.bIsCloaking )
	{
		MyPatPawn.SetCloaked( false );
		bWantsToCharge = true;
	}

	super.NotifyAttackActor( A );**/
}

/** Command finished. Used to catch instances where the flee command is interrupted by another command */
function NotifyCommandFinished( AICommand FinishedCommand )
{
	if( !bWantsToFlee && bFleeing && PendingDoor == none && (ActorEnemy == none || ActorEnemy.bPendingDelete) && AICommand_Flee(FinishedCommand) != none )
	{
		// Add to our total flee time
		TotalFleeTime = TotalFleeTime + (WorldInfo.TimeSeconds - FleeStartTime);

		// Abort the flee command
		AbortCommand( FinishedCommand );

		// Cancel any special moves
		if( MyPatPawn.IsDoingSpecialMove() )
		{
			MyPatPawn.EndSpecialMove();
		}

		// Delay flee by a tiny bit to allow command to finish up
		SetTimer( 0.06f, false, nameOf(Flee) );
	}
}

function NotifySpecialMoveEnded( KFSpecialMove SM )
{
    super.NotifySpecialMoveEnded(SM);

    bFleeInterrupted = false;

    if( !bWantsToFlee )
    {
	  	if( SM.Handle == 'KFSM_DoorMeleeAttack'
	  		|| SM.Handle == 'KFSM_MeleeAttack'
	  		|| SM.Handle == 'KFSM_Patriarch_Grapple'
	  		|| SM.Handle == 'KFSM_Patriarch_MinigunBarrage' )
		{
		    if( PendingDoor == none && bWantsToCharge && CachedChargeTarget != none && CachedChargeTarget.IsAliveAndWell() )
		    {
				ChangeEnemy( CachedChargeTarget, false );
				bDoingChargeAttack = true;
				bSprintUntilAttack = true;
				bWantsToCharge = false;
				MyPatPawn.SetSprinting( true );
				MyPatPawn.SetCloaked( true );
				SetTimer( 2.f, false, nameOf(Timer_SearchForChargeObstructions) );

				// Delay ranged attack checking a bit
				NextAttackCheckTime = 2.5f + fRand();
		    }
		    else if( bRaging )
			{
				RageAttackCount++;
				UpdateRageState();
			}
	        else if( bSprintUntilAttack )
	        {
				if( bDoingChargeAttack )
				{
					LastChargeAttackTime = WorldInfo.TimeSeconds;
				}

				LastSprintTime = WorldInfo.TimeSeconds;
		        bSprintUntilAttack = false;
		        bWantsToCharge = false;
		        CachedChargeTarget = none;
				ClearTimer( nameOf(Timer_SearchForChargeObstructions) );
		    }

		    // Retarget if it's been enough time since we changed targets
		    if( SM.Handle == 'KFSM_MeleeAttack' && `TimeSince(LastRetargetTime) > RetargetWaitTime )
		    {
		    	CheckForEnemiesInFOV( 3000.f, -1.f, 1.f, true );
		    }
		}
		else if( SM.Handle == 'KFSM_Patriarch_Heal' )
		{
			// Get a new enemy
			ChangeEnemy( GetClosestEnemy(), true );

			// Start moving to enemy
			SetEnemyMoveGoal( self, true );

			// Enable attacks again
			NextAttackCheckTime = fRand();
		}		

		// Use special move handlers, AI commands have a slight delay before popping    
		if( SM.Handle == 'KFSM_Patriarch_MinigunBarrage' )
		{
			NextAttackCheckTime = 2.25f+fRand();
			CheckForEnemiesInFOV( 2000.f, -1.f, 1.f );
		}
		else if( SM.Handle == 'KFSM_Patriarch_MissileAttack' )
		{
			LastMissileAttackTime = WorldInfo.TimeSeconds;
			NextAttackCheckTime = 2.25f+fRand();
			CheckForEnemiesInFOV( 2000.f, -1.f, 1.f );
		}
		else if( SM.Handle == 'KFSM_Patriarch_MortarAttack' )
		{
			LastMortarAttackTime = WorldInfo.TimeSeconds;
			NextAttackCheckTime = 2.25f+fRand();
			CheckForEnemiesInFOV( 2000.f, -1.f, 1.f );
		}
		else if( SM.Handle == 'KFSM_Patriarch_Grapple' )
		{
			// We don't want the grab to retarget so we can kick our enemy in the face
			LastSuccessfulAttackTime = WorldInfo.TimeSeconds;
			LastGrabAttackTime = WorldInfo.TimeSeconds;
			NextAttackCheckTime = 2.25f+fRand();
		}

		// Turn ranged attack eval back on
		bCanEvaluateAttacks = true;

	    // Turn melee attack probing back on
	    EnableMeleeRangeEventProbing();
    }
    else if( PendingDoor == none && !bFleeing )
	{
		bSprintUntilAttack = false;
		Flee();
	}

	// Evaluate sprinting whenever we finish a special move so sprinting will be snappy!
    EvaluateSprinting();

    // Reset charge attack state
    bDoingChargeAttack = false;
}

/** Reset minigun timer if we've landed a successful melee attack */
function NotifyMeleeDamageDealt()
{
	super.NotifyMeleeDamageDealt();

	LastSuccessfulAttackTime = WorldInfo.TimeSeconds;
}

/** If a monster other than the Patriarch is killed, and the Patriarch sees it, rage out */
function NotifyKilled( Controller Killer, Controller Killed, pawn KilledPawn, class<DamageType> damageType )
{
	if( GetIsInZedVictoryState() )
	{
		return;
	}

	if( self == Killer && Killed.GetTeamNum() != GetTeamNum() )
	{
		`DialogManager.PlayPattyKilledDialog( MyPatPawn, damageType );
	}
	else if( !bWantsToFlee && !bFleeing && !bRagedThisPhase && MyPatPawn.MaxRageAttacks > 0
		&& !MyPatPawn.IsDoingSpecialMove(SM_Heal) && Killed != self && Killed.GetTeamNum() == GetTeamNum() )
	{
		if( CanSee(KilledPawn) )
		{
			if( Killer.Pawn != none )
			{
				ChangeEnemy( Killer.Pawn, false );
			}
			StartPaternalInstinct();
		}
	}
	else if( Killed.Pawn == Enemy && MyPatPawn.IsDoingSpecialMove(SM_HoseWeaponAttack) )
	{
		// Allow minigun kills to either force a target switch or end the move
		KFSM_Patriarch_MinigunBarrage( MyPatPawn.SpecialMoves[SM_HoseWeaponAttack] ).Timer_SearchForMinigunTargets();
	}

	super.NotifyKilled( Killer, Killed, KilledPawn, damageType );
}

/** Notification from the pawn that it has taken damage */
function NotifyTakeHit( Controller InstigatedBy, vector HitLocation, int Damage, class<DamageType> damageType, vector Momentum )
{
	local KFPawn EnemyPawn;
	local int EnemyIndex;
	local int pawnDmg;
	local vector pawnRotToVec;

	// Aggro system
	if( InstigatedBy.Pawn != none )
	{
		EnemyPawn = KFPawn(InstigatedBy.Pawn);
		if( EnemyPawn != none )
		{
			EnemyIndex = RecentlySeenEnemyList.Find( 'TrackedEnemy', EnemyPawn );
			if( EnemyIndex != INDEX_NONE )
			{
				pawnDmg = Damage;
				pawnRotToVec = vector(MyPatPawn.Rotation);
				pawnRotToVec.Z = 0.f;
				if( pawnRotToVec dot Normal2D(EnemyPawn.Location - MyPatPawn.Location) < -0.25f )
				{
					// Aggro damage scales by 2 if it came from behind
					pawnDmg *= 2;
				}
				RecentlySeenEnemyList[EnemyIndex].AggroDamage += pawnDmg;
				RecentlySeenEnemyList[EnemyIndex].LastTakeDamageTime = WorldInfo.TimeSeconds;
			}
			else
			{
				EnemyIndex = HiddenEnemies.Find( 'TrackedEnemy', EnemyPawn );
				if( EnemyIndex != INDEX_NONE )
				{
					pawnDmg = Damage;
					pawnRotToVec = vector(MyPatPawn.Rotation);
					pawnRotToVec.Z = 0.f;
					if( pawnRotToVec dot Normal2D(EnemyPawn.Location - MyPatPawn.Location) < -0.25f )
					{
						// Aggro damage scales by 2 if it came from behind
						pawnDmg *= 2;
					}
					HiddenEnemies[EnemyIndex].AggroDamage += pawnDmg;
					HiddenEnemies[EnemyIndex].LastTakeDamageTime = WorldInfo.TimeSeconds;
				}
			}
		}
	}

    // When our health gets low, summon zeds and escape from the battle to heal
    if( !bWantsToFlee && !bFleeing && MyPatPawn != None && !MyPatPawn.bHealedThisPhase && MyPatPawn.CanSummonChildren()
    	&& !MyPatPawn.IsDoingSpecialMove(SM_Heal) )
    {
		if( !bSummonedThisPhase && GetHealthPercentage() < FleeHealthThreshold+0.075f )
		{
			bSummonedThisPhase = true;
			MyAIDirector.bForceFrustration = true;
		    MyPatPawn.SummonChildren();

			// Fallback so zeds can't spawn forever
			SetTimer( 30.f, false, nameOf(Timer_StopSummoningZeds) );
		}

	   	if( !MyPatPawn.IsDoingSpecialMove(SM_Taunt) && GetHealthPercentage() < FleeHealthThreshold )
	   	{
	   		// Force any special move to end
	   		if( MyPatPawn.IsDoingSpecialMove() )
	   		{
	   			MyPatPawn.EndSpecialMove();
	   		}

	   		// Prevent timeout from interrupting flee
	   		if( GetActiveCommand().IsA('AICommand_SpecialMove') )
	   		{
	   			AICommand_SpecialMove(GetActiveCommand()).ClearTimeout();
	   		}

	   		TotalFleeTime = 0.f;
	   		bCanEvaluateAttacks = false;
	   		bWantsToFlee = true;
		    EndPanicWander();
			NextBattlePhase();
   			class'AICommand_TauntEnemy'.static.Taunt( self, Enemy, TAUNT_Enraged, class'KFSM_Patriarch_Taunt' );
	       	MyPatPawn.SetFleeAndHealMode( true );
	   	}
	}

    Super.NotifyTakeHit(InstigatedBy, HitLocation, Damage, DamageType, Momentum);
}

/*********************************************************************************************
* Paternal Instinct
**********************************************************************************************/

/** Enter Paternal Instinct mode. Rage out! */
function StartPaternalInstinct()
{
	//local KFPawn MissileEnemy;

	// Play a dialog event
	`DialogManager.PlayPattyChildKilledDialog( MyPatPawn );

	// Let the rest of code know we're raging
	bRaging = true;
	bRagedThisPhase = true;

	// Zero our attack count
	RageAttackCount = 0;

	// Set the maximum number of attacks via phase and difficulty
	MaxRageAttacks = MyPatPawn.MaxRageAttacks + Max(0, MyKFGameInfo.GetModifiedGameDifficulty() - 1);

	// Always sprint when raging
	MyPatPawn.SetSprinting( true );

	// Set our timeout timer
	SetTimer( RageTimeOut, false, nameOf(Timer_RageTimeOut) );

	/*// Do our mortar rage if the ceiling is clear
	if( !MyPatPawn.IsDoingSpecialMove(SM_StandAndShootAttack) && !MyPatPawn.IsDoingSpecialMove(SM_SonicAttack) )
	{
		if( IsCeilingClear() && CollectMortarTargets(true, true) )
		{
			if( MyPatPawn.IsDoingSpecialMove() )
			{
				MyPatPawn.EndSpecialMove();
			}

			class'AICommand_Patriarch_MortarAttack'.static.FireMortar( self, true );
			bCanEvaluateAttacks = false;
		}
		else if( RecentlySeenEnemyList.Length > 0 )
		{
			if( MyPatPawn.IsDoingSpecialMove() )
			{
				MyPatPawn.EndSpecialMove();
			}

			// See if our enemy was visible recently, if not then choose a random visible one
			MissileEnemy = KFPawn(Enemy);
			if( RecentlySeenEnemyList.Find('TrackedEnemy', MissileEnemy) == INDEX_NONE )
			{
				MissileEnemy = RecentlySeenEnemyList[Rand(RecentlySeenEnemyList.Length)].TrackedEnemy;
			}

			ChangeEnemy( MissileEnemy, false );
			class'AICommand_Patriarch_MissileAttack'.static.FireMissiles( self );
			bCanEvaluateAttacks = false;
		}
	}*/
}

/** Determine if rage state should continue, if so try to get a new enemy */
function UpdateRageState()
{
	local float DistSQ, BestDistSQ;
	local KFPawn KFP, BestTarget;
	local int i;

	// Stop raging if we've cleared the attack threshold
	if( RageAttackCount >= MaxRageAttacks )
	{
		bRaging = false;
		bCanEvaluateAttacks = true;
		RageAttackCount = 0;
		RageAttackedTargets.Length = 0;
		ClearTimer( nameOf(Timer_RageTimeOut) );

		// See if we can still sprint or not
		EvaluateSprinting();
	}
	else
	{
		// Find a (hopefully new) enemy to rage out on
		foreach WorldInfo.AllPawns( class'KFPawn', KFP )
		{
			if( !KFP.IsAliveAndWell() || MyPatPawn.IsSameTeam(KFP) )
			{
				continue;
			}

			// Prefer players that haven't been attacked yet
			if( RageAttackedTargets.Find(KFP) != INDEX_NONE )
			{
				continue;
			}

			// Filter out players that are too far
			DistSQ = VSizeSQ( KFP.Location - MyPatPawn.Location );
			if( DistSQ > MaxRageRangeSQ )
			{
				continue;
			}

			// Favor closer targets
			if( BestDistSQ == 0.f || DistSQ < BestDistSQ )
			{
				BestDistSQ = DistSQ;
				BestTarget = KFP;
			}
		}

		if( BestTarget == none )
		{
			// Most recent targets are at the end of the array, start at the top
			for( i = 0; i < RageAttackedTargets.Length; ++i )
			{
				KFP = RageAttackedTargets[i];

				// Remove any enemies that are no longer valid
				if( KFP == none || !KFP.IsAliveAndWell() )
				{
					RageAttackedTargets.Remove( i, 1 );
					--i;
					continue;
				}

				// Get the first target that's within range
				DistSQ = VSizeSQ( KFP.Location - MyPatPawn.Location );
				if( DistSQ > MaxRageRangeSQ )
				{
					continue;
				}

				BestTarget = KFP;
				RageAttackedTargets.Remove(i, 1);
				break;
			}
		}

		if( BestTarget != none )
		{
			RageAttackedTargets[RageAttackedTargets.Length] = BestTarget;
			ChangeEnemy( BestTarget );
		}
		else
		{
			// End rage state if we couldn't get a valid target
			RageAttackCount = MaxRageAttacks;
			UpdateRageState();
		}
	}
}

/** If we've raged for too long, end Paternal Instinct */
function Timer_RageTimeOut()
{
	// End rage state if it's been too long
	RageAttackCount = MaxRageAttacks;
	UpdateRageState();
}

/*********************************************************************************************
* Pathfinding
**********************************************************************************************/

function bool AmIAllowedToSuicideWhenStuck()
{
	return false;
}

/*********************************************************************************************
* Zed Summoning
**********************************************************************************************/

/** Stop summoning children */
function Timer_StopSummoningZeds()
{
	// Allow summoning of children for this phase
	bSummonedThisPhase = false;	

	super.Timer_StopSummoningZeds();
}

/*********************************************************************************************
* Flee And Heal
**********************************************************************************************/

/** Initiate the Patriarch's next battle phase */
function NextBattlePhase()
{
	bRagedThisPhase = false;
 	
 	// Play a dialog event
 	`DialogManager.PlayPattyBattlePhaseDialog( MyPatPawn, MyPatPawn.CurrentBattlePhase );
	
	if( MyPatPawn != None )
	{
		MyPatPawn.IncrementBattlePhase();
	}
}

/** Custom target searching when fleeing -- we only want to attack targets that are blocking our flee path */
function Timer_SearchForFleeObstructions()
{
	local KFPawn ObstructingEnemy;

	if( !bFleeing || bWantsToFlee || MyPatPawn.IsDoingSpecialMove() )
	{
		SetTimer( 0.25f, false, nameOf(Timer_SearchForFleeObstructions) );
		return;
	}

	// See if there's someone blocking us
	ObstructingEnemy = CheckForEnemiesInFOV( AttackRange * 1.1f, 0.4f, 1.f, false, false );
	if( ObstructingEnemy != none )
	{
		// We need to count up the total flee time so infinite fleeing isn't possible
		TotalFleeTime = TotalFleeTime + (WorldInfo.TimeSeconds - FleeStartTime);
		bFleeInterrupted = true;
		bFleeing = false;

		// Temporarily end flee state
		MyPatPawn.SetCloaked( false );

        // Kill our flee and move commands
        AbortCommand( CommandList );

		// Set our new enemy
		ChangeEnemy( ObstructingEnemy, false );

		// Set our pending flee
		bWantsToFlee = true;

		// Sprint to new enemy
		bSprintUntilAttack = true;
		SetEnemyMoveGoal( self, true );
		EnableMeleeRangeEventProbing();

        // Restart default command
        BeginCombatCommand( GetDefaultCommand(), "Restarting default command" );

		// Give the patty a little bit of time to flee after this attack
		SetTimer( 3.0f, false, nameOf(Timer_SearchForFleeObstructions) );
	}
	else
	{
		SetTimer( 0.25f, false, nameOf(Timer_SearchForFleeObstructions) );
	}
}

/** Searches for enemies that are blocking the path to my charge target */
function Timer_SearchForChargeObstructions()
{
	local KFPawn ObstructingEnemy;

	if( bWantsToCharge || MyPatPawn.IsDoingSpecialMove() )
	{
		SetTimer( 0.25f, false, nameOf(Timer_SearchForChargeObstructions) );
		return;
	}

	// See if there's someone blocking us
	ObstructingEnemy = CheckForEnemiesInFOV( AttackRange * 1.1f, 0.4f, 1.f, false, false );
	if( ObstructingEnemy != none )
	{
		// Set our new enemy
		MyPatPawn.SetCloaked( false );
		ChangeEnemy( ObstructingEnemy, false );

		// Set pending charge
		bWantsToCharge = true;
	}
	else
	{
		SetTimer( 0.25f, false, nameOf(Timer_SearchForChargeObstructions) );
	}
}

/** Bump damage for human players */
function DoHeavyBump( Actor Other, vector HitNormal )
{
	local KFPawn_Human KFPH;

	// Only bump human pawns when we're not in the middle of an attack
	if( IsTimerActive(nameOf(Timer_SearchForFleeObstructions)) )
	{
		KFPH = KFPawn_Human( Other );
		if( KFPH != none )
		{
			KFPH.TakeDamage( HumanBumpDamage, self, KFPH.Location, HitNormal*HumanBumpMomentum, MyPatPawn.GetBumpAttackDamageType() );
			return;
		}
	}

	DoHeavyZedBump( Other, HitNormal );
}

/** Effects and damage from a zed sprinting and bumping other monsters */
function bool DoHeavyZedBump( Actor Other, vector HitNormal )
{
	local int BumpEffectDamage;
	local KFPawn_Monster BumpedMonster;

	/** If we bumped into a glass window, break it */
	if( Other.bCanBeDamaged && KFFracturedMeshGlass(Other) != none )
	{
		KFFracturedMeshGlass(Other).BreakOffAllFragments();
		return true;
	}

	/*if( Other.IsA('KFDestructibleActor') && !GetActiveCommand().IsA('AICommand_Melee') && Other.bCollideActors && !MyPatPawn.IsDoingSpecialMove() )
	{
		AIZeroMovementVariables();
		DisableBump(2.f);
		NotifyAttackActor( Other );
		return true;
	}*/

	BumpedMonster = KFPawn_Monster(Other);
	if( BumpedMonster == none || !BumpedMonster.IsAliveAndWell() || BumpedMonster.ZedBumpDamageScale <= 0 )
	{
		return false;
	}

	if( MyPatPawn == none || !MyPatPawn.IsAliveAndWell() )
	{
		return false;
	}

	// Patriarch knocks guys out of the way always if he is in hunt and heal
    if( MyPatPawn.bIsSprinting && !MyKFPawn.IsDoingSpecialMove() )
	{
		BumpEffectDamage = ZedBumpEffectThreshold * BumpedMonster.ZedBumpDamageScale;

		// If the Bumped Zed is near death, play either a knockdown or an immediate obliteration
		if( BumpedMonster.Health - BumpEffectDamage <= 0 )
		{
			// Chance to obliterate if at low health
			if( FRand() < ZedBumpObliterationEffectChance )
			{
				BumpedMonster.TakeDamage(BumpEffectDamage, self, BumpedMonster.Location, vect(0,0,0), MyKFPawn.GetBumpAttackDamageType());
			}
			else
			{
				BumpedMonster.Knockdown( , vect(1,1,1), Pawn.Location, 1000, 100 );
			}
			return true;
		}
		else
		{
			// otherwise deal damage and stumble the zed
			BumpedMonster.TakeDamage(BumpEffectDamage, self, BumpedMonster.Location, vect(0,0,0), MyKFPawn.GetBumpAttackDamageType());
			BumpedMonster.DoSpecialMove(SM_Stumble,,, class'KFSM_Stumble'.static.PackBodyHitSMFlags(BumpedMonster, HitNormal));
			return true;
		}
	}
	return false;
}

/* Starts Flee AICommand, with optional duration and distance */
function DoFleeFrom( actor FleeFrom,
	optional float FleeDuration,
	optional float FleeDistance,
	optional bool bShouldStopAtGoal=false,
	optional bool bFromFear=false )
{
	if( !bFromFear || !MyPatPawn.bInFleeAndHealMode )
	{
        super.DoFleeFrom( FleeFrom, FleeDuration, FleeDistance, bShouldStopAtGoal, bFromFear );
	}
}

/** Sets flee target if there is no enemy, starts flee command */
function Flee()
{
	local Actor FleeFromTarget;
	local float FleeDuration;
	local AICommand_SpecialMove AICSM;

	// Reset flee state
	bFleeing = false;
	bWantsToFlee = false;
	bFleeInterrupted = false;

	// We need a target to flee from
	if( Enemy == None )
	{
		SetEnemy( GetClosestEnemy() );
	}

	// Try to get an enemy, if not just choose a nearby navigation point (always flee!)
	if( Enemy != None )
	{
		FleeFromTarget = Enemy;
	}
	else
	{
		FleeFromTarget = class'NavigationPoint'.static.GetNearestNavToActor( MyPatPawn );
	}

	// If we somehow ended up taking a ton of damage in rage state, turn it off
	if( bRaging )
	{
		RageAttackCount = MaxRageAttacks;
		UpdateRageState();
	}

	// Prevent timeout from interrupting flee
	AICSM = FindCommandOfClass(class'AICommand_SpecialMove');
	if( AICSM != none )
	{
		AICSM.ClearTimeout();
	}

    // Abort all commands
    EndPanicWander();
    AbortCommand( CommandList );

	// Perform flee
	bFleeing = true;
	bCanEvaluateAttacks = false;
	MyPatPawn.SetCloaked( true );
	SetSprintingDisabled( false );
	MyPatPawn.SetSprinting( true );
	DisableMeleeRangeEventProbing();
	
	FleeDuration = fMax( MaxFleeDuration - TotalFleeTime, 6.f );
	//`log("[FLEE] FleeDuration:"@FleeDuration);
	//`log("[FLEE] FleeStartTime:"@WorldInfo.TimeSeconds);
	FleeStartTime = WorldInfo.TimeSeconds;
	DoFleeFrom( FleeFromTarget, FleeDuration, MaxFleeDistance + Rand(MaxFleeDistance * 0.25f), true );
	EvaluateSprinting();

	// Constantly make sure we don't have a player trying to block us
	if( !IsTimerActive(nameOf(Timer_SearchForFleeObstructions)) )
	{
		SetTimer( 2.f, false, nameOf(Timer_SearchForFleeObstructions) );
	}
}

/** We have finished fleeing for one reason or another, notify pawn to heal */
function NotifyFleeFinished( optional bool bAcquireNewEnemy=true )
{
	// If this was not a flee for healing, don't do additional cleanup
	if( !MyPatPawn.bInFleeAndHealMode )
	{
		return;
	}

	if( MyPatPawn != None )
	{
		MyPatPawn.SetCloaked( false );

		// Delay stop summoning, to give paternal instinct a chance to trigger
		if( IsTimerActive(nameOf(Timer_StopSummoningZeds)) )
		{
			SetTimer( 4.f, false, nameOf(Timer_StopSummoningZeds) );
		}

		// Stop searching for targets
		ClearTimer( nameOf(Timer_SearchForFleeObstructions) );

		// Flee debug
		//`log("[HEAL] FleeDuration:"@fMax(MaxFleeDuration - TotalFleeTime, 6.f));
		//`log("[HEAL] FleeEndTime:"@WorldInfo.TimeSeconds);
		//scripttrace();

		// End flee state
		bWantsToFlee = false;
		bFleeing = false;

		// Heal
		MyPatPawn.DoSpecialMove( SM_Heal,,, class'KFSM_Patriarch_Heal'.static.PackSMFlags(MyPatPawn.CurrentBattlePhase-1) );
	}

    // Allow melee again
	EnableMeleeRangeEventProbing();

    // Restart default command
	BeginCombatCommand( GetDefaultCommand(), "Restarting default command" );
}

/** Forces a heal regardless of what state we're in */
function ForceHeal()
{
    if( bFleeing )
    {
        bFleeing = false;
        bWantsToFlee = false;

        // Kill our flee command
        AbortCommand( FindCommandOfClass(class'AICommand_Flee') );
    }
    else
    {
        // Make sure hans doesn't try to flee again
        bWantsToFlee = false;
    }

    // End flee as normal
    NotifyFleeFinished( false );
}

/** Victory */
function EnterZedVictoryState()
{
	bCanEvaluateAttacks = false;
	bRaging = false;
	MyPatPawn.SetCloaked( false );
	bWantsToFlee = false;
	bFleeing = false;
	bFleeInterrupted = false;

	if( IsTimerActive(nameOf(Timer_SearchForFleeObstructions)) )
	{
		ClearTimer( nameOf(Timer_SearchForFleeObstructions) );
	}

	if( IsTimerActive(nameOf(Timer_SearchForChargeObstructions)) )
	{
		ClearTimer( nameOf(Timer_SearchForChargeObstructions) );
	}

	if( IsTimerActive(nameOf(Timer_RageTimeOut)) )
	{
		ClearTimer( nameOf(Timer_RageTimeOut) );
	}

	MyKFGameInfo.SpawnManager.StopSummoningBossMinions();
	super.EnterZedVictoryState();

	KFWeapon(MyPatPawn.Weapon).GotoState( 'Inactive' );
}

state ZedVictory
{
	ignores NotifyTakeHit, NotifyKilled, NotifySpecialMoveEnded, NotifyFleeFinished, SeePlayer, UpdateRageState, CheckForEnemiesInFOV,
			EvaluateSprinting, ChangeEnemy, SetEnemy, FindNewEnemy, EvaluateAttacks, UpdateRecentlySeenEnemyList, Timer_SearchForFleeObstructions,
			Timer_SearchForChargeObstructions;

Begin:
	Sleep(0.1f);
	class'AICommand_BossTheatrics'.static.DoTheatrics( self, THEATRIC_Victory, -1 );
}

/*********************************************************************************************
* Dialog
********************************************************************************************* */

function PlayDamagePlayerDialog( class<DamageType> DmgType )
{
	//`DialogManager.PlayHansDamagePlayerDialog( MyHansPawn, DmgType );
}

/*********************************************************************************************
* Debug
********************************************************************************************* */

simulated function DrawDebug( KFHUDBase HUD, name Category )
{
	local Canvas C;

	super.DrawDebug( HUD, Category );

	if( MyPatPawn == None || Category != 'All' )
	{
		return;
	}

	C = HUD.Canvas;
	// Patriarch info
	C.SetDrawColor( 255, 255, 255, 255 );
	DrawDebugText( HUD, "************************************************************" );
	C.SetDrawColor( 0, 0, 255, 255 );
	DrawDebugText( HUD, "PATRIARCH STATUS" );
	C.SetDrawColor( 0, 255, 0, 255);
	DrawDebugText( HUD, "BattlePhase:"@MyPatPawn.CurrentBattlePhase );
}


`if(`notdefined(ShippingPC))
/** Debug command to advance battle phase */
function DebugNextPhase()
{
	// Force any special move to end
	if( MyPatPawn.IsDoingSpecialMove() )
	{
		MyPatPawn.EndSpecialMove();
	}

	bCanEvaluateAttacks = false;
	bWantsToFlee = true;
	NextBattlePhase();
	//MyPatPawn.DoSpecialMove( SM_Heal,,, class'KFSM_Patriarch_Heal'.static.PackSMFlags(MyPatPawn.CurrentBattlePhase-1) );
	class'AICommand_TauntEnemy'.static.Taunt( self, Enemy, TAUNT_Enraged, class'KFSM_Patriarch_Taunt' );
   	MyPatPawn.SetFleeAndHealMode( true );
}
`endif

DefaultProperties
{
	Steering=none
	DefaultCommandClass=class'KFGameContent.AICommand_Base_Patriarch'
	MeleeCommandClass=class'KFGameContent.AICommand_Base_Patriarch'
    bRepathOnInvalidStrike=true
	LastRecentSeenEnemyListUpdateTime=0.1
	LostSightSprintDelay=5.0
	bSprintUntilAttack=true
	bHadMinigunAttack=false
	bCanDoHeavyBump=true
	EvadeGrenadeChance=1.0f
	RetargetWaitTime=5.f
	AggroFalloffWaitTime=1.f
	AggroFalloffPerSecond=25.f
	VisibleAggroDmgThreshold=260.f
	HiddenAggroDmgThreshold=200.f

    // Run over warning
    bUseRunOverWarning=true
    MinRunOverSpeed=360.f
    MinRunOverWarningAim=0.85f

	// Special attacks
	MinMinigunRangeSQ=160000.f
	MaxMinigunRangeSQ=16000000.f
	MaxFanFireRangeSQ=490000.f
	MinChargeRangeSQ=810000.f
	MinTentacleRangeSQ=90000.f
	MinMissileRangeSQ=360000.f
	MaxMissileRangeSQ=16000000.f

	// Flee
	HumanBumpDamage=10.f
	HumanBumpMomentum=8000.f
	FleeHealthThreshold=0.35f
	MaxFleeDuration=25.f
	MaxFleeDistance=20000.f

	// Rage
	MaxRageRangeSQ=1440000.f
	RageTimeOut=16.f

	// ---------------------------------------------
	// Danger Evasion Settings
	DangerEvadeSettings.Empty

    //Aim Blocks
    DangerEvadeSettings(0)={(ClassName="KFWeap_Rifle_Winchester1894",
                                Cooldowns=(0.0, 0.0, 0.3, 0.2), // Normal, Hard, Suicidal, HoE
                                BlockChances=(0.0, 0.0, 1.0, 1.0))}
    DangerEvadeSettings(1)={(ClassName="KFWeap_Bow_Crossbow",
                                Cooldowns=(0.0, 0.0, 0.3, 0.2), // Normal, Hard, Suicidal, HoE
                                BlockChances=(0.0, 0.0, 1.0, 1.0))}
    DangerEvadeSettings(2)={(ClassName="KFWeap_Rifle_M14EBR",
                                Cooldowns=(0.0, 0.0, 0.3, 0.2), // Normal, Hard, Suicidal, HoE
                                BlockChances=(0.0, 0.0, 1.0, 1.0))}
    DangerEvadeSettings(3)={(ClassName="KFWeap_Rifle_RailGun",
                                Cooldowns=(0.0, 0.0, 0.3, 0.2), // Normal, Hard, Suicidal, HoE
                                BlockChances=(0.0, 0.0, 1.0, 1.0))}	
    DangerEvadeSettings(4)={(ClassName="KFWeap_Bow_CompoundBow",
                                Cooldowns=(0.0, 0.0, 0.3, 0.2), // Normal, Hard, Suicidal, HoE
                                BlockChances=(0.0, 0.0, 1.0, 1.0))}
}