1
0
KF2-Dev-Scripts/KFGameContent/Classes/KFAIController_ZedFleshpoundKing.uc
2021-11-16 20:03:42 +03:00

293 lines
7.8 KiB
Ucode

//=============================================================================
// KFAIController_ZedFleshpoundKing
//=============================================================================
// AI Controller for the Fleshpound King
//=============================================================================
// Killing Floor 2
// Copyright (C) 2017 Tripwire Interactive LLC
// - Matt 'Squirrlz' Farber
//=============================================================================
class KFAIController_ZedFleshpoundKing extends KFAIController_ZedFleshpound;
const MaxPhases = 4;
/** Cached pawn reference */
var KFPawn_ZedFleshpoundKing MyPawn;
/** Next time we should attempt to do a beam attack */
var protected float NextBeamCheckTime;
/** Last time we ticked the beam attack check code */
var transient protected float LastBeamTickTime;
/** How long to wait after the first time seeing a player before we should attempt to check for a beam attack */
var const vector2d BeamAttackWaitTimeRange;
/** Minimum angle a target must be from the fleshpound's rotation when starting the chest beam attack */
var const float MinChestBeamTargetAngle;
/** Maximum height offset a target can be from the fleshpound to start the chest beam attack*/
var const float MaxChestBeamHeightOffset;
/** Current battle phase, 0 = start, indexes into PhaseThresholds*/
var int CurrentPhase;
/** Health percentage cutoffs for the current fight phase */
var const float PhaseThresholds[MaxPhases];
/** Phase at which the chest beam can start */
var const int ChestBeamMinPhase;
event Possess(Pawn inPawn, bool bVehicleTransition)
{
local KFGameReplicationInfo KFGRI;
super.Possess(inPawn, bVehicleTransition);
// Play entrance animation
KFGRI = KFGameReplicationInfo(WorldInfo.GRI);
if (KFGRI != none && KFGRI.WaveNum >= KFGRI.WaveMax)
{
class'AICommand_BossTheatrics'.static.DoTheatrics(self, THEATRIC_Entrance);
}
MyPawn = KFPawn_ZedFleshpoundKing(inPawn);
}
function bool AmIAllowedToSuicideWhenStuck()
{
return false;
}
/** We have seen a player -- start checking for beam attack */
event SeePlayer( Pawn Seen )
{
super.SeePlayer( Seen );
if( NextBeamCheckTime == 0.f )
{
NextBeamCheckTime = WorldInfo.TimeSeconds + RandRange( BeamAttackWaitTimeRange.X, BeamAttackWaitTimeRange.Y );
}
}
/** Tick our chest beam attack */
simulated event Tick( float DeltaTime )
{
super.Tick( DeltaTime );
//Don't attack while we're in theatrics
if (CommandList != none && CommandList.class == class'AICommand_BossTheatrics')
{
return;
}
if( NextBeamCheckTime > 0.f && `TimeSince(LastBeamTickTime) > 0.2f && WorldInfo.TimeSeconds >= NextBeamCheckTime )
{
LastBeamTickTime = WorldInfo.TimeSeconds;
CheckForBeamAttack();
}
}
/** Decides if we should attempt a beam attack */
function CheckForBeamAttack()
{
local Pawn P, BestTarget;
local float MaxBeamTargetDistSQ, Dist, DotAngle, Rating, BestRating;
local vector Projection, PawnDir;
// Don't allow beam attack during special moves
if( MyKFPawn.IsDoingSpecialMove() )
{
return;
}
// Don't allow beam when not in the correct phase
if (CurrentPhase < ChestBeamMinPhase)
{
return;
}
// Don't allow beam while enraged
if (RagePlugin.bIsEnraged)
{
return;
}
// Subtract 15% from the max beam length when choosing targets due to wind up time
MaxBeamTargetDistSQ = Square( class'KFSM_FleshpoundKing_ChestBeam'.default.MaxBeamLength * 0.85f );
PawnDir = vector( MyKFPawn.Rotation );
BestRating = -1.f;
foreach WorldInfo.AllPawns( class'Pawn', P )
{
// Only target enemies
if( P.GetTeamNum() == MyKFPawn.GetTeamNum() || !P.IsAliveAndWell() )
{
continue;
}
// Only target things that are targetable
if (!P.CanAITargetThisPawn(MyKFPawn.Controller))
{
continue;
}
Projection = P.Location - MyKFPawn.Location;
Dist = VSizeSQ( Projection );
// Distance check
if( Dist > MaxBeamTargetDistSQ )
{
continue;
}
// Within field of view and not behind geometry and within allowed height offset
DotAngle = PawnDir dot Normal(Projection);
if( DotAngle > MinChestBeamTargetAngle && MyKFPawn.FastTrace(P.Location, MyKFPawn.Location) && Abs(P.Location.Z - MyKFPawn.Location.Z) < MaxChestBeamHeightOffset )
{
// Lower ratings are better (smaller distance and closer to center of pawn's rotation)
Rating = Dist * ( 1.f - DotAngle );
if( BestRating == -1.f || Rating < BestRating )
{
BestRating = Rating;
BestTarget = P;
}
}
}
// Target check
if( BestTarget != none )
{
if( BestTarget != Enemy )
{
ChangeEnemy( BestTarget, false );
}
// Start blasting!
NextBeamCheckTime = 0.f;
class'AICommand_FleshpoundKing_ChestBeamAttack'.static.ChestBeamAttack( self );
}
}
function NotifyTakeHit(Controller InstigatedBy, vector HitLocation, int Damage, class<DamageType> damageType, vector Momentum)
{
local float CurrentHealth;
super.NotifyTakeHit(InstigatedBy, HitLocation, Damage, damageType, Momentum);
//Can still transition
if (MyPawn != none && CurrentPhase + 1 < MaxPhases)
{
CurrentHealth = float(MyPawn.Health) / float(MyPawn.HealthMax);
if (CurrentHealth < PhaseThresholds[CurrentPhase + 1])
{
TransitionToPhase(CurrentPhase + 1);
}
}
}
/** Handle any updates caused by a phase transition */
function TransitionToPhase(int NewPhase)
{
CurrentPhase = NewPhase;
MyPawn.CurrentPhase = NewPhase;
MyPawn.SpawnSubWave();
switch (NewPhase)
{
case 3:
MyPawn.ActivateShield();
break;
}
}
function NotifySpecialMoveEnded( KFSpecialMove SM )
{
super.NotifySpecialMoveEnded( SM );
if (SM.Handle == 'KFSM_Zed_Boss_Theatrics')
{
//Start initial phase if theatrics just finished
TransitionToPhase(0);
}
// This ensures that we don't attempt to do a beam attack immediately after a special move ends
if( NextBeamCheckTime < WorldInfo.TimeSeconds + 2.0f )
{
NextBeamCheckTime = WorldInfo.TimeSeconds + 2.0f + fRand();
}
}
/** Whether enemy switch commands can be run */
function bool CanSwitchEnemies()
{
return (MyKFPawn != none && MyKFPawn.SpecialMove != SM_HoseWeaponAttack);
}
/** Overridden to stop retargeting enemies when firing the chest beam */
event bool FindNewEnemy()
{
if( !CanSwitchEnemies() )
{
return false;
}
return super.FindNewEnemy();
}
/** Overridden to stop retargeting enemies when firing the chest beam */
event bool SetEnemy( Pawn NewEnemy )
{
if( !CanSwitchEnemies() )
{
return false;
}
return super.SetEnemy( NewEnemy );
}
/** Overridden to stop retargeting enemies when firing the chest beam */
event ChangeEnemy( Pawn NewEnemy, optional bool bCanTaunt=true )
{
if( !CanSwitchEnemies() )
{
return;
}
super.ChangeEnemy( NewEnemy, bCanTaunt );
}
/** Forces a target change, bypasses our custom ChangeEnemy() */
function ForceTargetChange( Pawn NewEnemy )
{
super.ChangeEnemy( NewEnemy, false );
}
state ZedVictory
{
Begin:
Sleep(0.1f);
if (MyKFPawn != none)
{
class'AICommand_BossTheatrics'.static.DoTheatrics(self, THEATRIC_Victory, -1);
}
}
DefaultProperties
{
BeamAttackWaitTimeRange=(X=1.5f, Y=3.0f)
MinChestBeamTargetAngle=0.77f
MaxChestBeamHeightOffset=64.f
PhaseThresholds[0]=1
PhaseThresholds[1]=0.75 //0.65 //0.75
PhaseThresholds[2]=0.6 //0.5
PhaseThresholds[3]=0.3 //0.2 //0.25 //0.1
ChestBeamMinPhase=2
SpawnRagedChance(`DIFFICULTY_NORMAL)=1.f
SpawnRagedChance(`DIFFICULTY_HARD)= 1.f
SpawnRagedChance(`DIFFICULTY_SUICIDAL)= 1.f
SpawnRagedChance(`DIFFICULTY_HELLONEARTH)= 1.f
}