//=============================================================================
// KFAIController_ZedMatriarch
//=============================================================================
// AI controller class for the matriarch.
//=============================================================================
// Killing Floor 2
// Copyright (C) 2016 Tripwire Interactive LLC
// - Dan Weiss
//=============================================================================

class KFAIController_ZedMatriarch extends KFAIController_ZedBoss;

enum EMatriarchAttacksByRange
{
	EMatriarchAttacksByRange_SweepingClaw,
	EMatriarchAttacksByRange_LightningStorm,
	EMatriarchAttacksByRange_WarningSiren,
	EMatriarchAttacksByRange_TeslaBlast,
	EMatriarchAttacksByRange_PlasmaCannon,
	EMatriarchAttacksByRange_ScorpionWhip
};

/** Cached reference to patriarch pawn */
var KFPawn_ZedMatriarch MyMatPawn;

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

/** How long to wait before evaluating special attacks */
var float GlobalCooldownTimer;
var vector2d GlobalCooldownTimeRange_Melee;
var vector2d GlobalCooldownTimeRange_LightningStorm;
var vector2d GlobalCooldownTimeRange_WarningSiren;
var vector2d GlobalCooldownTimeRange_TeslaBlast;
var vector2d GlobalCooldownTimeRange_PlasmaCannon;
var vector2d GlobalCooldownTimeRange_ScorpionWhip;

/*
	Player Targeting
*/

var vector2d ReevaluateEnemiesTimeRange;
var transient float ReevaluateEnemiesTimer;
var array<int> PlayerDamages;
var Pawn CurrentTargetPawn;
var bool bLogTargeting;

/*
	Special Attacks
*/

var float SweepingClawCooldown, LastSweepingClawTime;
var float TeslaBlastCooldown, LastTeslaBlastTime;
var float PlasmaCannonCooldown, LastPlasmaCannonTime;
var float LightningStormCooldown, LastLightningStormTime;
var float WarningSirenCooldown, LastWarningSirenTime;
var float ScorpionWhipCooldown, LastScorpionWhipTime;

var config bool bRandomMoves;

/** Set MyPatPawn to avoid casting */
event Possess( Pawn inPawn, bool bVehicleTransition )
{
	super.Possess( inPawn, bVehicleTransition );

	if (KFPawn_ZedMatriarch(inPawn) != none)
	{
		MyMatPawn = KFPawn_ZedMatriarch(inPawn);
	}
	else
	{
		`warn( GetFuncName()$"() attempting to possess "$inPawn$", but it's not a KFPawn_ZedMatriarch class! MyMatPawn variable will not be valid." );
	}

	// Wait a bit before evaluating special attacks
	GlobalCooldownTimer = 2.5f + fRand();

	// Start evaluating on next tick
	bCanEvaluateAttacks = true;

	if (CommandList == none || CommandList.class != class'AICommand_BossTheatrics')
    {
        MyMatPawn.ActivateShield();
    }

	SetReevaluateEnemiesTimer();
}

simulated function SetReevaluateEnemiesTimer()
{
	ReevaluateEnemiesTimer = RandRange(ReevaluateEnemiesTimeRange.X, ReevaluateEnemiesTimeRange.Y);
	if (bLogTargeting)
	{
		`log(self$"::SetReevaluateEnemiesTimer - ReevaluateEnemiesTimer: "$ReevaluateEnemiesTimer);
	}
}

simulated function ReevaluateEnemies()
{
	local int MaxPlayerDamage, i;
	local KFPlayerController KFPC, MaxKFPC;

	if (bLogTargeting)
	{
		for (i = 0; i < PlayerDamages.Length; ++i)
		{
			`log(self$"::ReevaluateEnemies - PlayerDamages "$i$": "$PlayerDamages[i]);
		}
	}

	MaxPlayerDamage = 0;

	// find most damaging player
	foreach WorldInfo.AllControllers(class'KFPlayerController', KFPC)
	{
		if (KFPawn(KFPC.Pawn).IsAliveAndWell())
		{
			if (PlayerDamages.Length > KFPC.PlayerNum &&
				PlayerDamages[KFPC.PlayerNum] > MaxPlayerDamage)
			{
				MaxPlayerDamage = PlayerDamages[KFPC.PlayerNum];
				MaxKFPC = KFPC;
			}
		}
		else
		{
			// mark invalid player as untargetable
			PlayerDamages[MaxKFPC.PlayerNum] = 0;
		}
	}

	if (bLogTargeting)
	{
		`log(self$"::ReevaluateEnemies - Max damage player: "$MaxKFPC);
	}

	// target most damaging player
	if (MaxKFPC != none)
	{
		CurrentTargetPawn = MaxKFPC.Pawn;
		ChangeEnemy(MaxKFPC.Pawn, false);

		// mark most damaging player as untargetable now
		PlayerDamages[MaxKFPC.PlayerNum] = 0;
	}

	SetReevaluateEnemiesTimer();
}

simulated function Tick(FLOAT DeltaTime)
{
    super.Tick(DeltaTime);
    EvaluateAttacks(DeltaTime);
}

event SeePlayer(Pawn Seen);

event bool FindNewEnemy()
{
	local bool Result;

	Result = super.FindNewEnemy();
	CurrentTargetPawn = Enemy;

	return Result;
}

/** Evaluates whether or not the Matriarch can do a special attack */
function EvaluateAttacks( float DeltaTime )
{
	local float DistToTargetSq;
	local array<EMatriarchAttacksByRange> PossibleMoves;
	local EMatriarchAttacksByRange ChosenMove;

	if (!bCanEvaluateAttacks)
	{
		return;
	}

	ReevaluateEnemiesTimer -= DeltaTime;

	if (MyMatPawn.IsDoingSpecialMove() ||
		(CommandList != none && GetActiveCommand().IsA('AICommand_SpecialMove')))
	{
		return;
	}

	if (MyMatPawn.bShouldTaunt)
	{
		MyMatPawn.bShouldTaunt = false;
		MyMatPawn.DoSpecialMove(SM_Taunt, true,, class'KFSM_Matriarch_Taunt'.static.PackSMFlags(MyMatPawn, TAUNT_Standard));
		return;
	}

	if (ReevaluateEnemiesTimer <= 0.f)
	{
		ReevaluateEnemies();
	}

	GlobalCooldownTimer -= DeltaTime;
	if (GlobalCooldownTimer > 0.f)
	{
		return;
	}

	// Trace from worldinfo, open doors ignore traces from zeds
    if (Enemy != none && WorldInfo.FastTrace(Enemy.Location, Pawn.Location, , true))
    {
        DistToTargetSq = VSizeSq(Enemy.Location - Pawn.Location);
		PossibleMoves = GetPossibleMovesByRange(DistToTargetSq);
		if (PossibleMoves.Length > 0)
		{
			if (!bRandomMoves)
			{
				ChosenMove = PossibleMoves[0];
			}
			else
			{
				ChosenMove = PossibleMoves[Rand(PossibleMoves.Length)];
			}

			switch (ChosenMove)
			{
			//case EMatriarchAttacksByRange_SweepingClaw:
			//	class'AICommand_Matriarch_SweepingClaw'.static.SweepingClaw(self);
			//	break;

			case EMatriarchAttacksByRange_LightningStorm:
				class'AICommand_Matriarch_LightningStorm'.static.LightningStorm(self);
				break;

			case EMatriarchAttacksByRange_WarningSiren:
				class'AICommand_Matriarch_WarningSiren'.static.WarningSiren(self);
				break;

			case EMatriarchAttacksByRange_TeslaBlast:
				class'AICommand_Matriarch_TeslaBlast'.static.TeslaBlast(self);
				break;

			case EMatriarchAttacksByRange_PlasmaCannon:
				class'AICommand_MatriarchPlasmaCannon'.static.PlasmaCannonAttack(self);
				break;

			case EMatriarchAttacksByRange_ScorpionWhip:
				class'AICommand_Matriarch_ScorpionWhip'.static.ScorpionWhip(self);
				break;
			};

			GlobalCooldownTimer = 1000000.f;
		}
		else
		{
			GlobalCooldownTimer = 0.5f;
		}
    }
	else
	{
		GlobalCooldownTimer = 0.5f;
	}
}

function array<EMatriarchAttacksByRange> GetPossibleMovesByRange(float DistToTargetSq)
{
	local array<EMatriarchAttacksByRange> PossibleMoves;

	//if (CanUseSweepingClaw(DistToTargetSq))
	//{
	//	PossibleMoves.AddItem(EMatriarchAttacksByRange_SweepingClaw);
	//}

	if (CanUseLightningStorm(DistToTargetSq))
	{
		PossibleMoves.AddItem(EMatriarchAttacksByRange_LightningStorm);
	}

	if (CanUseWarningSiren(DistToTargetSq))
	{
		PossibleMoves.AddItem(EMatriarchAttacksByRange_WarningSiren);
	}

	if (CanUseTeslaBlast(DistToTargetSq))
	{
		PossibleMoves.AddItem(EMatriarchAttacksByRange_TeslaBlast);
	}

	if (CanUsePlasmaCannon(DistToTargetSq))
	{
		PossibleMoves.AddItem(EMatriarchAttacksByRange_PlasmaCannon);
	}

	if (CanUseScorpionWhip(DistToTargetSq))
	{
		PossibleMoves.AddItem(EMatriarchAttacksByRange_ScorpionWhip);
	}

	return PossibleMoves;
}

//function bool CanUseSweepingClaw(float DistToTargetSq)
//{
//	if (!MyMatPawn.CanUseSweepingClaw())
//	{
//		return false;
//	}
//
//	if (DistToTargetSq > Square(class'KFSM_Matriarch_SweepingClaw'.default.MaxVictimDistance))
//	{
//		return false;
//	}
//
//	if (`TimeSince(LastSweepingClawTime) < SweepingClawCooldown)
//	{
//		return false;
//	}
//
//	return true;
//}

function bool CanUseTeslaBlast(float DistToTargetSq)
{
	if (!MyMatPawn.CanUseTeslaBlast())
	{
		return false;
	}

	if (DistToTargetSq > Square(class'KFSM_Matriarch_TeslaBlast'.default.MaxVictimDistance))
	{
		return false;
	}

	if (`TimeSince(LastTeslaBlastTime) < TeslaBlastCooldown)
	{
		return false;
	}

	return true;
}

function bool CanUsePlasmaCannon(float DistToTargetSq)
{
	if (!MyMatPawn.CanUsePlasmaCannon())
	{
		return false;
	}

	if (DistToTargetSq > Square(class'KFSM_Matriarch_PlasmaCannon'.default.MaxVictimDistance))
	{
		return false;
	}

	if (`TimeSince(LastPlasmaCannonTime) < PlasmaCannonCooldown)
	{
		return false;
	}

	return true;
}

function bool CanUseLightningStorm(float DistToTargetSq)
{
	if (!MyMatPawn.CanUseLightningStorm())
	{
		return false;
	}

	if (DistToTargetSq > Square(class'KFSM_Matriarch_LightningStorm'.default.MaxVictimDistance))
	{
		return false;
	}

	if (`TimeSince(LastLightningStormTime) < LightningStormCooldown)
	{
		return false;
	}

	return true;
}

function bool CanUseWarningSiren(float DistToTargetSq)
{
	if (!MyMatPawn.CanUseWarningSiren())
	{
		return false;
	}

	if (DistToTargetSq > Square(class'KFSM_Matriarch_WarningSiren'.default.MaxVictimDistance))
	{
		return false;
	}

	if (`TimeSince(LastWarningSirenTime) < WarningSirenCooldown)
	{
		return false;
	}

	return true;
}

function bool CanUseScorpionWhip(float DistToTargetSq)
{
	if (!MyMatPawn.CanUseScorpionWhip())
	{
		return false;
	}

	if (DistToTargetSq > Square(class'KFSM_Matriarch_ScorpionWhip'.default.MaxRange))
	{
		return false;
	}

	if (`TimeSince(LastScorpionWhipTime) < ScorpionWhipCooldown)
	{
		return false;
	}

	return true;
}

//function DoStrike()
//{
//	if (CanUseSweepingClaw(0))
//	{
//		class'AICommand_Matriarch_SweepingClaw'.static.SweepingClaw(self);
//	}
//	else
//	{
//		super.DoStrike();
//	}
//}

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

	if (MyMatPawn.Role == ROLE_Authority)
	{
		MyMatPawn.SetShieldUp(false);
		MyMatPawn.SetCloaked(false);
	}
}

/** Special move has ended, whether cleanly or aborted */
function NotifySpecialMoveEnded(KFSpecialMove SM)
{
	super.NotifySpecialMoveEnded(SM);

	switch (SM.Handle)
	{
	case 'KFSM_MeleeAttack':
		GlobalCooldownTimer = RandRange(GlobalCooldownTimeRange_Melee.X, GlobalCooldownTimeRange_Melee.Y);
		break;

	case 'KFSM_Matriarch_TeslaBlast':
		GlobalCooldownTimer = RandRange(GlobalCooldownTimeRange_TeslaBlast.X, GlobalCooldownTimeRange_TeslaBlast.Y);
		break;

	case 'KFSM_Matriarch_PlasmaCannon':
		GlobalCooldownTimer = RandRange(GlobalCooldownTimeRange_PlasmaCannon.X, GlobalCooldownTimeRange_PlasmaCannon.Y);
		break;

	case 'KFSM_Matriarch_LightningStorm':
		GlobalCooldownTimer = RandRange(GlobalCooldownTimeRange_LightningStorm.X, GlobalCooldownTimeRange_LightningStorm.Y);
		break;

	case 'KFSM_Matriarch_WarningSiren':
		GlobalCooldownTimer = RandRange(GlobalCooldownTimeRange_WarningSiren.X, GlobalCooldownTimeRange_WarningSiren.Y);
		break;

	case 'KFSM_Matriarch_ScorpionWhip':
		GlobalCooldownTimer = RandRange(GlobalCooldownTimeRange_ScorpionWhip.X, GlobalCooldownTimeRange_ScorpionWhip.Y);
		break;

	default:
		GlobalCooldownTimer = 0.5f;
		break;
	};

	EvaluateSprinting();

	if (SM.Handle == 'KFSM_Zed_Boss_Theatrics')
	{
		MyMatPawn.ActivateShield();
	}
	else
	{
		MyMatPawn.SetShieldUp(true);
		MyMatPawn.SetCloaked(true);
	}

	if (CurrentTargetPawn != none && Enemy != CurrentTargetPawn)
	{
		ChangeEnemy(CurrentTargetPawn, false);
	}
}

/** Evaluate if we should start/stop sprinting, and then set the sprinting flag */
function EvaluateSprinting()
{
    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()
{
	local float DistToEnemy;

	if (MyKFPawn != none && MyKFPawn.IsAliveAndWell() && Enemy != none && Enemy.IsAliveAndWell())
	{
		DistToEnemy = VSize(Enemy.Location - Pawn.Location);
		if (DistToEnemy > SprintWithinEnemyRange.X && DistToEnemy < SprintWithinEnemyRange.Y)
		{
			return true;
		}
		else if (!FastTrace(Enemy.Location, Pawn.Location,, true))
		{
			return true;
		}
	}

	return false;
}

function NotifyTakeHit(Controller InstigatedBy, vector HitLocation, int Damage, class<DamageType> damageType, vector Momentum)
{
	super.NotifyTakeHit(InstigatedBy, Hitlocation, Damage, damageType, Momentum);

	if (Damage > 0)
	{
		if (PlayerDamages.Length <= InstigatedBy.PlayerNum)
		{
			PlayerDamages[InstigatedBy.PlayerNum] = Damage;
		}
		else
		{
			PlayerDamages[InstigatedBy.PlayerNum] += Damage;
		}

		if (bLogTargeting)
		{
			`log(self$"::NotifyTakeHit - player "$InstigatedBy.PlayerNum$" add "$Damage$" damage ("$PlayerDamages[InstigatedBy.PlayerNum]$")");
		}
	}
}

function NotifyKilled(Controller Killer, Controller Killed, pawn KilledPawn, class<DamageType> damageType)
{
	if (GetIsInZedVictoryState())
	{
		return;
	}

	if (self == Killer && Killed.GetTeamNum() != GetTeamNum())
	{
		`DialogManager.PlayMattyKilledDialog(MyKFPawn);
	}
	else if (Killed.GetTeamNum() == GetTeamNum())
	{
		`DialogManager.PlayMattyMinionKilledDialog(MyKFPawn);
	}

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

function EnterZedVictoryState()
{
	super.EnterZedVictoryState();

	bCanEvaluateAttacks = false;
	KFWeapon(MyMatPawn.Weapon).GotoState( 'Inactive' );

	MyMatPawn.ClearTimer(nameof(MyMatPawn.Timer_TickDialog), MyMatPawn);
}

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

function bool AmIAllowedToSuicideWhenStuck()
{
	return false;
}

/*********************************************************************************************
* Bump
**********************************************************************************************/

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;
	}

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

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

	BumpEffectDamage = ZedBumpEffectThreshold * BumpedMonster.ZedBumpDamageScale * (MyKFPawn.bIsSprinting ? 2 : 1);

	// If the Bumped Zed is near death, play either a knockdown or an immediate obliteration
	if (BumpedMonster.Health - BumpEffectDamage <= 0)
	{
		BumpedMonster.TakeDamage(BumpEffectDamage, self, BumpedMonster.Location, vect(0, 0, 0), MyKFPawn.GetBumpAttackDamageType());
		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;
}

event bool NotifyBump(Actor Other, vector HitNormal)
{
	local KFPawn_Human BumpedHuman;

	BumpedHuman = KFPawn_Human(Other);
	if (BumpedHuman != none &&
		BumpedHuman != CurrentTargetPawn &&
		!MyMatPawn.IsDoingSpecialMove() &&
		!IsZero(MyMatPawn.Velocity) &&
		HitNormal dot vector(Rotation) < -0.7)
	{
		AbortCommand(CommandList);

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

		//SetEnemyMoveGoal(self, true);
		EnableMeleeRangeEventProbing();

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

	return super.NotifyBump(Other, HitNormal);
}

defaultproperties
{
	Steering=none
	DefaultCommandClass=class'KFGameContent.AICommand_Base_Matriarch'
	MeleeCommandClass=class'KFGameContent.AICommand_Base_Matriarch'

	//SweepingClawCooldown=10.f
	TeslaBlastCooldown=5.f
    PlasmaCannonCooldown=7.f
	LightningStormCooldown=8.f
	WarningSirenCooldown=10.f
	ScorpionWhipCooldown=12.f

	SprintWithinEnemyRange=(X=1500.f,Y=1000000000.f)

	ReevaluateEnemiesTimeRange=(X=8.f, Y=10.f)
	bLogTargeting=false

	bCanDoHeavyBump=true

	GlobalCooldownTimeRange_Melee=(X=0.0, Y=0.0)
	GlobalCooldownTimeRange_LightningStorm=(X=2.5, Y=2.5)
	GlobalCooldownTimeRange_WarningSiren=(X=2.5, Y=2.5)
	GlobalCooldownTimeRange_TeslaBlast=(X=2.5, Y=2.5)
	GlobalCooldownTimeRange_PlasmaCannon=(X=2.5, Y=2.5)
	GlobalCooldownTimeRange_ScorpionWhip=(X=0.0, Y=0.0)
}