//=============================================================================
// KFAIController_Monster.uc
//=============================================================================
// Base AIController for KF2's Zeds
//=============================================================================
// Killing Floor 2
// Copyright (C) 2015 Tripwire Interactive LLC
//=============================================================================

class KFAIController_Monster extends KFAIController
	dependson(KFAIController)
	abstract
	native(AI);

`include(KFGame\KFGameAnalytics.uci);

/** Zeds who can grab prefer to use a grab as their initial attack - if true, they've already done this */
var bool		bCompletedInitialGrabAttack;
/** Clot won't perform grab until closer than this distance. TODO: If we keep this, change it to a % scale of MaxGrabDistance in KFSM_Clot_Grab */
var float		MinDistanceToPerformGrabAttack;
/** Time frequency for grab attacks */
var float		MinTimeBetweenGrabAttacks;
/** Last time a grab attack was performed */
var float		LastAttackTime_Grab;
var bool		bPathAroundDestructiblesICantBreak;
/** Determines if a zed should try to force a repath if they cannot execute a valid strike */
var bool 		bRepathOnInvalidStrike;

/*********************************************************************************************
* RunOverWarning (warns Zeds nearby that my pawn's about to run into them)
********************************************************************************************* */
/** Zed will transmit ReceiveRunOverWarning events to other Zeds if its about to run them over */
var bool		bUseRunOverWarning;
/** Speed must be greater than this to transmit run over warning (if bUseRunOverWarning=true) */
var float		MinRunOverSpeed;
/** Last time checked for pawns to transmit RunOverWarning to (if bUseRunOverWarning=true) */
var	float		LastRunOverWarningTime;
/** Minimum angle to victim required to transmit RunOverWarning (if bUseRunOverWarning=true) */
var	float		MinRunOverWarningAim;
/** When TRUE, this Zed will attempt to evade when warned of being run over */
var bool 		bEvadeOnRunOverWarning;
/** Scales the delay from the initial warning notification. Increase for zeds that are fast evaders, decrease for slow evaders */
var float 		RunOverEvadeDelayScale;

cpptext
{
	UBOOL Tick( FLOAT DeltaTime, enum ELevelTick TickType );

	// Called by native Tick() to evaluate if Zed in melee range of target - if so, will call InMeleeRange() event
	virtual UBOOL TickMeleeCombatDecision( FLOAT DeltaTime );
	// Supports "run over" warning notification to other NPCs - mainly for larger Zeds to use
	// to give nearby Zeds a chance to get out of the way.
	virtual void TickRunOverWarning( FLOAT DeltaSeconds );
}

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

/** Only spawning a PRI for gameplayevents! */
function InitPlayerReplicationInfo()
{
	local KFGameInfo KFGI;
	local string NPCName;

	KFGI = KFGameInfo(WorldInfo.Game);
	if( KFGI != none && KFGI.bEnableGameAnalytics )
	{
		PlayerReplicationInfo = Spawn(class'KFDummyReplicationInfo', self,, vect(0,0,0),rot(0,0,0));
		if ( Pawn != none )
		{
			NPCName = string(Pawn.name);
			NPCName = Repl(NPCName,"KFPawn_Zed","",false);
		}
		else
		{
			NPCName = string(self.name);
			NPCName = Repl(NPCName,"KFAIController_Zed","",false);
		}

		PlayerReplicationInfo.PlayerName = NPCName;

		/* __TW_ANALYTICS_ */
		`RecordZedSpawn(self);
		// don't call SetPlayerName() as that will broadcast entry messages but the GameInfo hasn't had a chance
		// to potentionally apply a player/bot name yet
		//PlayerReplicationInfo.PlayerName = class'GameInfo'.default.DefaultPlayerName;
	}
}

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

	super.Possess( inPawn, bVehicleTransition );

	SetPawnDefaults();
}

function SetPawnDefaults()
{
	local float SprintChance;
	local float SprintDamagedChance;
	local float HiddenSpeedMod;
	local float GameDifficulty;
	local KFGameDifficultyInfo DifficultyInfo;
	local KFGameInfo KFGI;

	KFGI = KFGameInfo( WorldInfo.Game );

    GameDifficulty = KFGI.GetModifiedGameDifficulty();
    DifficultyInfo = KFGI.DifficultyInfo;

	SprintChance = DifficultyInfo.GetCharSprintChanceByDifficulty( MyKFPawn, GameDifficulty );
	SprintDamagedChance = DifficultyInfo.GetCharSprintWhenDamagedChanceByDifficulty( MyKFPawn, GameDifficulty );
	HiddenSpeedMod = DifficultyInfo.GetAIHiddenSpeedModifier( KFGI.GetLivingPlayerCount() );
	MyKFPawn.HiddenGroundSpeed = MyKFPawn.default.HiddenGroundSpeed * HiddenSpeedMod;

	if ( MyKFPawn.PawnAnimInfo != none )
	{
		MyKFPawn.PawnAnimInfo.SetDifficultyValues( DifficultyInfo );
	}

	// Each zed has a chance he will sprint at a certain difficulty
	// NOTE: Some zeds now bypass this check because they need to sprint under certain conditions regardless of
	//	difficulty!  Search the code for bIsSprinting = true.  Evil, yes, but necessary
	SetCanSprint( FRand() <= SprintChance );
	SetCanSprintWhenDamaged( FRand() <= SprintDamagedChance );

	bDefaultCanSprint = bCanSprint;

	if( KFGI.BaseMutator != None )
	{
		KFGI.BaseMutator.ModifyAI( Pawn );
	}
}

/*********************************************************************************************
* Notifications & Events
********************************************************************************************* */

/** Re-Enables notifications from TickMeleeCombatDecision() */
function Timer_EnableMeleeRangeEventProbing()
{
	if( !MyKFPawn.IsDoingSpecialMove() )
	{
		EnableMeleeRangeEventProbing();
	}
	else
	{
		// Re-Enable timer once at a time (added 7/2014)
		SetTimer( 0.12f, false, nameof(Timer_EnableMeleeRangeEventProbing), self );
	}
}

/** Notification that we have passed all our basic melee checks and are ready to attempt a melee attack */
event ReadyToMelee()
{
	// Check script to see if a strike is allowed
	if( CanDoStrike() )
	{
		// Update our next pending strike
		UpdatePendingStrike();
		LastGetStrikeTime = WorldInfo.TimeSeconds;

		// Perform strike if we have a valid animation
		if( PendingAnimStrikeIndex != 255 )
		{
			DoStrike();
			return;
		}
	}

	// Attempt to find another path to enemy
	if( bRepathOnInvalidStrike && (bFailedToMoveToEnemy || (!bMovingToGoal && !bMovingToEnemy)) )
	{
		SetEnemyMoveGoal(self, true,,, true);
	}
	// If we can't attack, and are close to the enemy, do a taunt so we don't just stand there
	else if( !CheckOverallCooldownTimer() && Enemy != none && Pawn != none && Pawn.IsAliveAndWell() )
	{
        if( VSize(Enemy.Location - Pawn.Location) < MyKFPawn.CylinderComponent.CollisionRadius * 3.0 )
		{
			if( MyKFPawn.CanDoSpecialMove(SM_Taunt) && `TimeSince(LastTauntTime) > 2.f )
			{
				`AILog( GetFuncName()$" starting taunt command", 'CantMelee' );
				class'AICommand_TauntEnemy'.static.Taunt( self, KFPawn(Enemy), TAUNT_Standard );
			}
		}
	}
}

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

/** Set up path constraints and attempt to build a path to Goal actor. Distance is an optional offset. */
event Actor GeneratePathTo( Actor Goal, optional float Distance, optional bool bAllowPartialPath )
{
	local actor PathResult;
	local int i;

	if( bDisablePartialPaths )
	{
		bAllowPartialPath = false;
	}

	AddBasePathConstraints();

	class'Path_TowardGoal'.static.TowardGoal( Pawn, Goal );

	if( bPathAroundDestructiblesICantBreak )
	{
		/** NPC will build path around destructible objects not configured to accept bump damage */
		class'Path_AroundDestructibles'.static.AvoidDestructibles( Pawn, true, true );
		class'Goal_Null'.static.GoUntilBust( Pawn, 2024 );
	}
	else
	{
		class'Goal_AtActor'.static.AtActor( Pawn, Goal, Distance, bAllowPartialPath );
	}
	// Attempt to build the path.
	PathResult = FindPathToward( Goal );
	Pawn.ClearConstraints();

	if( PathResult == None )
	{
		`AILog( GetFuncName()$"() failed to build a path to "$Goal$", offset distance was "$Distance$", bAllowPartialPath was "$bAllowPartialPath, 'PathWarning' );
	}

	if( bShowMovePointsDebugInfo )
	{
		for( i = 0; i < RouteCache.Length; i++ )
		{
			DrawDebugStar( RouteCache[i].Location, PathNodeShowRouteCacheCrossSize, PathNodeShowRouteCacheColor.R, PathNodeShowRouteCacheColor.G, PathNodeShowRouteCacheColor.B, true);
			DrawDebugString( RouteCache[i].Location + vect(0,0,5), string(i), , PathNodeShowRouteCacheColor, PathNodeShowRouteCacheNumberLabelDuration);

			if( i > 0 )
			{
				DrawDebugLine( RouteCache[i].Location, RouteCache[i-1].Location, PathNodeShowRouteCacheColor.R, PathNodeShowRouteCacheColor.G, PathNodeShowRouteCacheColor.B, true);
			}
		}
	}

	return PathResult;
}

/*********************************************************************************************
* Combat
**********************************************************************************************/

/** Can this pawn perform a grab attack? */
event bool CanGrabAttack()
{
	local KFPawn_Human KFPH;
	local KFPerk EnemyPerk;
	local KFPawn KFPawnEnemy;
	local float DistSq;
	local vector Extent, HitLocation, HitNormal;
	local Actor HitActor;

	// If I'm dead, incapable of grabbing, or have no enemy, or my enemy is a player, or I'm busy doing a melee attack, refuse.
	if( (MyKFPawn == none || !MyKFPawn.bCanGrabAttack || MyKFPawn.Health <= 0) || (Enemy == none) || (Enemy != none && Pawn.IsSameTeam(Enemy)) )
	{
		return false;
	}

	KFPawnEnemy = KFPawn( Enemy );
	if( KFPawnEnemy == none || !KFPawnEnemy.CanBeGrabbed(MyKFPawn) )
	{
		return false;
	}

	// If I'm crippled, falling, busy doing an attack, or incapacitated, refuse.
	if( MyKFPawn.bIsHeadless || (MyKFPawn.Physics == PHYS_Falling) || IsDoingAttackSpecialMove() || !MyKFPawn.IsCombatCapable() )
	{
		return false;
	}

	// Check for fakeout perk
	KFPH = KFPawn_Human(Enemy);
	if ( KFPH != none )
	{
		EnemyPerk = KFPH.GetPerk();
		if ( EnemyPerk != none && EnemyPerk.CanNotBeGrabbed() )
		{
			return false;
		}
	}

	if( !bCompletedInitialGrabAttack || (LastAttackTime_Grab == 0.f || (`TimeSince(LastAttackTime_Grab) > MinTimeBetweenGrabAttacks)) )
	{
        // Make sure the enemy's center of mass (location) is within my collision cylinder
		if( Abs(Enemy.Location.Z - Pawn.Location.Z) > class'KFSM_GrappleCombined'.default.MaxVictimZOffset )
		{
			return false;
		}

		DistSq = VSizeSq(Enemy.Location - Pawn.Location);
		if( DistSq > MinDistanceToPerformGrabAttack * MinDistanceToPerformGrabAttack || MyKFPawn.IsPawnMovingAwayFromMe(Enemy, 300.f) )
		{
			return false;
		}

		// Set our extent
		Extent.X = Pawn.GetCollisionRadius() * 0.5f;
		Extent.Y = Extent.X;
		Extent.Z = Pawn.GetCollisionHeight() * 0.5f;

        // Do the same kind of trace we do in KFSM_GrappleStart
		HitActor = Trace(HitLocation, HitNormal, Enemy.Location, Pawn.Location, true, Extent);
		if ( HitActor != None && HitActor != Enemy )
		{
            return false;
		}
		if( !CanTargetBeGrabbed(KFPawnEnemy) )
		{
			return false;
		}
		/** Makes Zed have high desire to grab as initial attack */
		if( !MyKFPawn.IsDoingMeleeAttack() && (!bCompletedInitialGrabAttack || (FRand() < MyKFPawn.GrabAttackFrequency)) ) //&& !MyKFPawn.IsPawnMovingAwayFromMe(Enemy, 250.f) )
		{
			return true;
		}
	}
	`AILog( GetFuncName()$"() returning FALSE", 'GrabAttack' );
	return false;
}

function bool CanDoStrike()
{
	local actor HitActor;
	local vector TraceStepLocation;

	// Used by KFPawnAnimInfo to determine if an attack can be performed if legs are blocked (lunges, etc)
	bIsBodyBlocked = false;

	// Check if a wall or another Zed is blocking my pawn from performing a melee attack, ignore zed collision if bCanStrikeThroughEnemies is true,
	TraceStepLocation = Pawn.Location + (vect(0,0,-1) * (Pawn.CylinderComponent.CollisionHeight * 0.5f));
	HitActor = ActorBlockTest( Pawn, Enemy.Location, TraceStepLocation,, !bCanStrikeThroughEnemies );
	if( HitActor != none && HitActor != Enemy )
	{
		if( HitActor.bWorldGeometry )
		{
			// Set the body blocked flag so the anim info can check it
			bIsBodyBlocked = true;
		}

		// Try again at eyeheight
		HitActor = ActorBlockTest( Pawn, Enemy.Location + (vect(0,0,1) * Enemy.BaseEyeHeight), Pawn.Location + (vect(0,0,1) * Pawn.BaseEyeHeight),, !bCanStrikeThroughEnemies );
		if( HitActor != None && HitActor != Enemy && (!bCanStrikeThroughEnemies || HitActor.bWorldGeometry) )
		{
			return false;
		}
	}

	return true;
}

function DoStrike()
{
	local byte StrikeFlags;

	if( MyKFPawn != none && MyKFPawn.PawnAnimInfo != none )
	{
		StrikeFlags = MyKFPawn.PawnAnimInfo.GetStrikeFlags(PendingAnimStrikeIndex);
		if( StrikeFlags != 255 )
		{
			`AILog( GetFuncName()$"() "$VSize(MyKFPawn.Location - Enemy.Location)$" units from enemy and I DO HAVE AN available attack!", 'Command_Attack_Melee' );
			class'AICommand_Attack_Melee'.static.Melee( self, Enemy, StrikeFlags );

			MyKFPawn.PawnAnimInfo.UpdateAttackCooldown(self, PendingAnimStrikeIndex);

			UpdatePendingStrike();
		}
		else
		{
			`AILog( GetFuncName()$"() "$VSize(MyKFPawn.Location - Enemy.Location)$" units from enemy and I have no available attack!", 'Command_Attack_Melee' );
		}
	}
}

/** Perform a melee attack AICommand.. InTarget is optional actor to attack (door, etc.) */
function DoMeleeAttack( optional Pawn NewEnemy, optional Actor InTarget, optional byte AttackFlags )
{
	/*
	local AICommand AIC;

	if( MyKFPawn != none && (!MyKFPawn.bIsHeadless && !MyKFPawn.bEmpPanicked && !IsMeleeRangeEventProbingEnabled()) || (MyKFPawn.IsDoingSpecialMove() && !MyKFPawn.IsDoingSpecialMove(SM_ChargeRun)) )
	{
		`AILog( GetFuncName()$"() skipping melee attack because "$Pawn$" is already busy.", 'Command_Attack_Melee' );
		return;
	}
	AIC = AICommand( GetActiveCommand() );
	if( AIC != none )
	{
		if( !AIC.bAllowedToAttack )
		{
			`AILog( GetFuncName()$"() refusing to do melee attack because "$AIC$" bAllowedToAttack is FALSE", 'Command_Attack_Melee' );
			return;
		}
		if( AICommand_Pause(AIC) != none )
		{
			return;
		}
		if( AICommand_TauntEnemy(AIC) != none )
		{
			return;
		}
	}
	if( MyKFPawn != none && MyKFPawn.PawnAnimInfo != none )
	{
		// Only Pack flags if 255 was initially passed in
		if( AttackFlags == 255 )
		{
			AttackFlags = ChooseStrikeAnimation();
		}

		if( AttackFlags != 255 )
		{
			`AILog( GetFuncName()$"() Aborting movement commands and starting melee attack command", 'Command_Attack_Melee' );
			class'AICommand_Attack_Melee'.static.Melee( self, InTarget, AttackFlags );
		}
		return;
	}

	if( !AICommand(CommandList).bAllowedToAttack )
	{
		`AILog( GetFuncName()$"() refusing to do melee attack because "$CommandList$" bAllowedToAttack is FALSE", 'Command_Attack_Melee' );
	}
	*/
}

/** Called when in melee range but enemy is blocked from me, probably by another Zed */
function bool HandleZedBlockedPath()
{
	local actor HitActor;
	local KFPawn_Monster HitMonster;

	HitActor = ActorBlockTest( Pawn, Enemy.Location + vect(0,0,1) * (Enemy.BaseEyeHeight * 0.5f), MyKFPawn.Location + vect(0,0,1) * (MyKFPawn.BaseEyeHeight * 0.5f), MyKFPawn.GetCollisionExtent() * vect(0.2f,0.2f,0.2f), true );
	if( HitActor == none || HitActor == Enemy )
	{
		return false;
	}

	// If we hit a monster check HandleEnemyBlocked, otherwise we're good to strike
	HitMonster = KFPawn_Monster(HitActor);
	if( HitMonster != none && HitMonster.Health > 0 )
	{
		if( MyKFPawn == none || MyKFPawn.Health <= 0 || MyKFPawn.IsDoingSpecialMove() )
		{
			return true;
		}

		`AILog( GetFuncName()$" ENEMY IS BLOCKED", 'ReachedEnemy' );
		DisableMeleeRangeEventProbing();
		SetTimer( 1.5f + (2.f*FRand()), false, nameof(Timer_EnableMeleeRangeEventProbing), self );
		if( FindNewEnemy() )
		{
			ForcePauseAndRepath();
			return true;
		}

		if( VSize(Enemy.Location - Pawn.Location) < AttackRange && bDirectMoveToGoal )
		{
			if( MyKFPawn.CanDoSpecialMove(SM_Taunt) && FRand() < 0.32 && `TimeSince(LastTauntTime) > 2.f )
			{
				`AILog( GetFuncName()$" starting taunt command", 'ReachedEnemy' );
				class'AICommand_TauntEnemy'.static.Taunt( self, KFPawn(Enemy), TAUNT_Standard );
			}
 			else
 			{
				`AILog( GetFuncName()$" starting pauseAI command", 'ReachedEnemy' );
				DoPauseAI( 1.f + (3.f * FRand()), true );
			}
			return true;
		}
	}
	return false;
}

/** Notification I'm about to be run into by a Zed which has bUseRunOverWarning set to true */
event RunOverWarning( KFPawn IncomingKFP, float IncomingSpeedSquared, vector RunOverPoint )
{
	local float Delay;

	if( bEvadeOnRunOverWarning && CanEvade(true) )
	{
		Delay = ( VSize(IncomingKFP.Location - MyKFPawn.Location) / Sqrt(IncomingSpeedSquared) ) * RunOverEvadeDelayScale;
		DoEvade( GetBestEvadeDir(RunOverPoint,, false), IncomingKFP,, Delay, true );
	}
}

DefaultProperties
{
	// ---------------------------------------------
	// Combat
	MeleeCommandClass=class'AICommand_Base_Zed'
	DoorMeleeDistance=200.f
	MinTimeBetweenGrabAttacks=5.f
	MinDistanceToPerformGrabAttack=188.f

	// ---------------------------------------------
	// AI / Navigation
	DefaultCommandClass=class'AICommand_Base_Zed'
	SightCounterInterval=0.35f
	bEvadeOnRunOverWarning=false
	RunOverEvadeDelayScale=0.25f

	bIsPlayer=false
}