2020-12-13 15:01:13 +00:00
//=============================================================================
// 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.5 f + 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 ) ;
}
/ * * C l e a n u p a l l i n t e r n a l o b j e c t s a n d r e f e r e n c e s w h e n t h e A I i s d e s t r o y e d
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.5 f ;
}
}
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.5 f + 2.5 f * ( FMax ( float ( FoundIndex ) , 1. f ) / RecentTargets . Length ) ) :
( 1. f - 0.5 f * ( 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.5 f : 2.0 f ;
}
// Apply aggro rating
TempRating *= bPreferFurtherTargets ? 0.5 f + 0.5 f * GetAggroRating ( KFPH ) : 1.0 f - 0.5 f * 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.5 f + 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.25 f ) )
{
if ( SetBestTarget ( LastGrabbedPlayers , MinTentacleRangeSQ , Square ( class 'KFSM_Patriarch_Grapple' . default . MaxRange * 0.8 f * MyPatPawn . GetAttackRangeScale ( ) ) , 0.4 f , true , true ) )
{
MyPatPawn . SetCloaked ( false ) ;
class 'AICommand_Patriarch_Grab' . static . TentacleGrab ( self ) ;
return ;
}
}
// Evaluate charge attack
if ( ! MyPatPawn . bIsCloaking
&& MyPatPawn . CanChargeAttack ( )
&& ( bHadMinigunAttack || fRand ( ) < 0.75 f )
&& ` 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.5 f + fRand ( ) ;
SetTimer ( 2. f , false , nameOf ( Timer _SearchForChargeObstructions ) ) ;
return ;
}
}
// Evaluate weapon attack
bCanFireMinigun = ( ! MyPatPawn . CanChargeAttack ( ) || fRand ( ) < 0.5 f )
&& ( ! MyPatPawn . bIsCloaking || fRand ( ) < 0.75 f )
&& RecentlySeenEnemyList . Length > 0
&& ` TimeSince(LastSuccessfulAttackTime) > MyPatPawn.MinigunAttackCooldownTime;
bCanFireMissile = ( ! MyPatPawn . CanChargeAttack ( ) || fRand ( ) < 0.5 f )
&& ( ! MyPatPawn . bIsCloaking || fRand ( ) < 0.75 f )
&& 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.5 f )
&& ( ! bCanFireMinigun || fRand ( ) < 0.75 f )
&& IsCeilingClear ( ) ;
if ( bShouldFireMortar )
{
// Random chance to do an area denial
if ( MyPatPawn . CanDoMortarBarrage ( ) && fRand ( ) < 0.2 f )
{
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.2 f ) || fRand ( ) < 0.6 f )
&& SetBestTarget ( LastMissileEnemies , MinMissileRangeSQ , MaxMissileRangeSQ * MyPatPawn . GetAttackRangeScale ( ) , 0.5 f , true , true ) ;
// No other attacks, find a minigun target!
if ( bCanFireMinigun && ! bShouldFireMissile && ! bShouldFireMortar )
{
bCanFireMinigun = SetBestTarget ( LastMinigunEnemies , MinMinigunRangeSQ , MaxMinigunRangeSQ * MyPatPawn . GetAttackRangeScale ( ) , 0.25 f , 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.5 f ;
}
/** 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.5 f + 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.06 f , 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.5 f + 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.25 f + fRand ( ) ;
CheckForEnemiesInFOV ( 2000. f , - 1. f , 1. f ) ;
}
else if ( SM . Handle == 'KFSM_Patriarch_MissileAttack' )
{
LastMissileAttackTime = WorldInfo . TimeSeconds ;
NextAttackCheckTime = 2.25 f + fRand ( ) ;
CheckForEnemiesInFOV ( 2000. f , - 1. f , 1. f ) ;
}
else if ( SM . Handle == 'KFSM_Patriarch_MortarAttack' )
{
LastMortarAttackTime = WorldInfo . TimeSeconds ;
NextAttackCheckTime = 2.25 f + 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.25 f + 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.25 f )
{
// 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.25 f )
{
// 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.075 f )
{
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.25 f , false , nameOf ( Timer _SearchForFleeObstructions ) ) ;
return ;
}
// See if there's someone blocking us
ObstructingEnemy = CheckForEnemiesInFOV ( AttackRange * 1.1 f , 0.4 f , 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.0 f , false , nameOf ( Timer _SearchForFleeObstructions ) ) ;
}
else
{
SetTimer ( 0.25 f , 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.25 f , false , nameOf ( Timer _SearchForChargeObstructions ) ) ;
return ;
}
// See if there's someone blocking us
ObstructingEnemy = CheckForEnemiesInFOV ( AttackRange * 1.1 f , 0.4 f , 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.25 f , 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 ;
}
/ * i f ( O t h e r . I s A ( ' K F D e s t r u c t i b l e A c t o r ' ) & & ! G e t A c t i v e C o m m a n d ( ) . I s A ( ' A I C o m m a n d _ M e l e e ' ) & & O t h e r . b C o l l i d e A c t o r s & & ! M y P a t P a w n . I s D o i n g S p e c i a l M o v e ( ) )
{
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 ,
2023-09-21 19:31:11 +00:00
optional bool bFromFear = false ,
optional bool bUseRandomDirection = false )
2020-12-13 15:01:13 +00:00
{
if ( ! bFromFear || ! MyPatPawn . bInFleeAndHealMode )
{
2023-09-21 19:31:11 +00:00
super . DoFleeFrom ( FleeFrom , FleeDuration , FleeDistance , bShouldStopAtGoal , bFromFear , bUseRandomDirection ) ;
2020-12-13 15:01:13 +00:00
}
}
/** 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.25 f ) , 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.1 f ) ;
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.0 f
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.85 f
// 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.35 f
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 ) ) }
}