1
0
KF2-Dev-Scripts/KFGameContent/Classes/KFAIController_ZedMatriarch.uc
2020-12-13 18:01:13 +03:00

690 lines
17 KiB
Ucode

//=============================================================================
// 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)
}