2020-12-13 15:01:13 +00:00
//=============================================================================
// KFAIController_Hans
//=============================================================================
// Hans's AIController
//=============================================================================
// Killing Floor 2
// Copyright (C) 2015 Tripwire Interactive LLC
//=============================================================================
class KFAIController _Hans extends KFAIController _ZedBoss
native ( AI ) ;
/ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* Initialization
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * /
/** Cached copy of Hans pawn to avoid casting */
var transient KFPawn _ZedHansBase MyHansPawn ;
/** Destination navigation point Hans wants to throw a grenade from */
//var NavigationPoint RangedAttackMoveGoal;
var vector RangedAttackMoveLoc ;
var bool bFoundGrenadeThrowLocation ;
var bool bWantsToThrowGrenade ;
var vector CalcedGreanadeTossVelForNavMeshLoc ;
var float TimeCalcedGreanadeTossVelWasFound ;
var vector EnemysLocationWhenTossVelWasFound ;
/ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* Sprint behavior
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * /
/** How long Hans should wait to start sprinting after losing sight of his enemy */
var float LostSightSprintDelay ;
/ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* Gun shooting related variables brought over from AICommand _HansGunAttack .
* Put here because the shoot AICommand keeps getting pushed onto the stack and
* resetting these vars
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * /
/** The time a new barrage started */
var float BarrageStartTime ;
/** The last time we started firing our gun */
var float LastFireTime ;
/** The time we started drawing our gun */
var float StartDrawGunsTime ;
/** The time we started this gun attack */
var float StartGunAttackTime ;
/** Cache our last target */
var Actor LastTarget ;
/** The time we first saw our current target we are shooting at */
var float TimeFirstSawTarget ;
/** Last time we changed weapon stances */
var float LastStanceChangeTime ;
/** Can't change stances more often than this */
var ( ) float StanceChangeCooldown ;
/** Last time we finished a melee attack */
var float LastAttackMoveFinishTime ;
/** Wait this long to do a gun attack after doing a melee attack */
var ( ) float PostAttackMoveGunCooldown ;
/ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* Enemy Tracking
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * /
/ * *
* Struct to track AI knowledge of visible enemies
* /
struct native TrackedEnemyInfo
{
/** Enemy we're tracking */
var KFPawn TrackedEnemy ;
/** Enemy we're tracking */
var float LastTimeVisible ;
/** Enemy we're tracking */
var vector LastVisibleLocation ;
/** The last time we fired on this enemy */
var float LastTimeFiredOn ;
/** The last time we tossed a grenade at this enemy */
var float LastTimeGrenadeAttacked ;
/** How often we've chosen this enemy as a target recently */
var int NumTimesEngagedRecently ;
} ;
/** List of tracked enemies that have recently been seen */
var array < TrackedEnemyInfo > RecentlySeenEnemyList ;
/** The last enemy we gunned at that was recently seen */
var KFPawn LastRecentlySeenEnemyGunned ;
/** The last enemy we grenaded that was recently seen */
var KFPawn LastRecentlySeenEnemyGrenaded ;
/** How often to update RecentlySeenEnemyList */
var ( ) float RecentSeenEnemyListUpdateInterval ;
/** Last time we updated the RecentlySeenEnemyList*/
var float LastRecentSeenEnemyListUpdateTime ;
/** The last time we changed to a new target */
var float LastRetargetTime ;
/** How long to wait before attempting to find a new target */
var vector2d RetargetWaitTimeRange ;
/** The actual retarget wait time after last retarget time has been set */
var transient float ActualRetargetWaitTime ;
/ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* Vars for handing the timing of firing , when to shoot , the shooting cadence , etc
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * /
/** Min length of a burst for this weapon when used by AI. Randomly used to calculate the BurstAmount */
var ( Firing ) int MinBurstAmount ;
/** Max length of a burst for this weapon when used by AI. Randomly used to calculate the BurstAmount */
var ( Firing ) int MaxBurstAmount ;
/** How long to wait between bursts when this weapon is used by AI */
var ( Firing ) float BurstWaitTime ;
/** How long to wait between barrages (groups of bursts) when this weapon is used by AI */
var ( Firing ) float BarrageWaitTime ;
/** Interp curve to scale how long Hans waits between barrages for different ranges **/
var ( ) InterpCurveFloat BarrageWaitDistanceCurve ;
/** Interp curve to scale how long Hans waits between bursts for different ranges **/
var ( ) InterpCurveFloat BurstWaitDistanceCurve ;
/** How long a barrage (groups of bursts) will last */
var ( Firing ) float BarrageLength ;
/** Tracks the current burst and what size it is */
var int BurstAmount ;
/** How long to wait to stop firing after losing sight of an enemy */
var ( Firing ) float LostSightStopFireDelay ;
/** The fire mode the last time we fired our gun */
var float LastFireMode ;
/** How long to wait after starting to draw our guns to fire */
var ( Firing ) float DrawGunFireDelay ;
/** How long to wait after starting to see an enemy to fire */
var ( Firing ) float TargetAquisitionDelay ;
/** How long this gun attack should last at the max */
var ( Firing ) float MaxGunAttackLength ;
/** How long to wait after drawing the guns before allowing a grenade toss to interrupt them */
var ( Firing ) float GrenadeGunInterruptDelay ;
/** The range the zed needs to be within to start shooting bullets */
var float StartShootingRange ;
/** The range the zed needs to be outside to disallow shooting bullets */
var float MinShootingRange ;
/** min delay between shooting attacks */
var float ShootingCooldown ;
enum EHansNadeType
{
HNT _None ,
HNT _HEGrenade ,
HNT _NerveGas ,
HNT _Smoke ,
HNT _HEGrenadeBarrage ,
HNT _NerveGasBarrage ,
HNT _SmokeBarrage ,
} ;
/* The grenade attack type we have queued up to do */
var EHansNadeType CurrentNadeAttackType ;
/* How often to check if we want to do a grenade attack */
var ( ) float GrenadeAttackEvalInterval ;
/* Last time we evaluated if we wanted to do a grenade attack*/
var float LastGrenadeAttackEvalTime ;
/** Whether we've already thrown a smoke grenade barrage this hunt and heal phase */
var bool bHasUsedSmokeScreenThisPhase ;
/ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* Fleeing
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * /
/** TRUE if flee is in progress */
var bool bFleeing ;
/** TRUE if flee was interrupted (to attack, etc) */
var bool bWantsToFlee ;
/** Set to true once the flee has been completed */
var bool bHasFledThisPhase ;
/** How long we should flee */
var float MinFleeDuration , MaxFleeDuration ;
/** Maximum flee distance */
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 ;
cpptext
{
/** Overridden to allow grab attack check */
virtual UBOOL TickMeleeCombatDecision ( FLOAT DeltaTime ) ;
}
/ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* Initialization , Pawn Possession , and Destruction
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * /
/** Set MyHansPawn to avoid casting */
event Possess ( Pawn inPawn , bool bVehicleTransition )
{
if ( KFPawn _ZedHansBase ( inPawn ) != none )
{
MyHansPawn = KFPawn _ZedHansBase ( inPawn ) ;
}
else
{
` warn( GetFuncName() $ "() attempting to possess " $ inPawn $ ", but it's not a KFPawn_ZedHansBase class! MyHansPawn variable will not be valid." );
}
// Initialize retarget time
LastRetargetTime = WorldInfo . TimeSeconds ;
ActualRetargetWaitTime = RandRange ( RetargetWaitTimeRange . X , RetargetWaitTimeRange . Y ) ;
super . Possess ( inPawn , bVehicleTransition ) ;
}
function PawnDied ( Pawn InPawn )
{
if ( MyHansPawn != none )
{
` DialogManager.PlayBossDeathDialog( MyHansPawn );
MyHansPawn = 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 ( )
{
MyHansPawn = None ;
super . Destroyed ( ) ;
}
/** Overridden to allow grab attack check */
/ *
function DoMeleeAttack ( optional Pawn NewEnemy , optional Actor InTarget , optional byte AttackFlags )
{
local float DistSq ;
if ( CommandList == None || AICommand ( CommandList ) . bAllowedToAttack )
{
DistSq = VSizeSq ( Enemy . Location - Pawn . Location ) ;
if ( DistSq < 32400. f || DistSq > 10000. f )
{
if ( CanGrabAttack ( ) )
{
// DumpCommandStack();
DoGrabAttack ( ) ;
return ;
}
}
}
super . DoMeleeAttack ( NewEnemy , inTarget , AttackFlags ) ;
}
* /
/** Start the throw grenade command, optionally for the grenade barrage attack */
function DoGrenadeThrow ( optional bool bGrenadeBarrage )
{
if ( MyHansPawn != none )
{
MyHansPawn . SetActiveGrenadeClassGrenade ( ) ;
}
if ( class 'AICommand_ThrowGrenade' . static . ThrowGrenade ( self , bGrenadeBarrage ? 2 : 0 ) )
{
if ( MyHansPawn != none )
{
MyHansPawn . BarrageTossCount = 0 ;
MyHansPawn . bDoingBarrage = bGrenadeBarrage ;
}
}
}
/** Start the throw grenade command with smoke, optionally for the grenade barrage attack */
function DoSmokeGrenadeThrow ( optional bool bGrenadeBarrage , optional bool bIsHuntAndHeal )
{
local bool bThrowSuccess ;
if ( MyHansPawn != none )
{
MyHansPawn . SetActiveGrenadeClassSmoke ( ) ;
}
// Spawn some zeds if we need to
bThrowSuccess = class 'AICommand_ThrowGrenade' . static . ThrowGrenade ( self , bGrenadeBarrage ? 1 : 0 , bIsHuntAndHeal ) ;
if ( bThrowSuccess )
{
if ( MyHansPawn != none )
{
MyHansPawn . bPendingSmokeGrenadeBarrage = false ;
MyHansPawn . BarrageTossCount = 0 ;
MyHansPawn . bDoingBarrage = bGrenadeBarrage ;
}
}
}
/** Notification from the pawn that it has taken damage */
function NotifyTakeHit ( Controller InstigatedBy , vector HitLocation , int Damage , class < DamageType > damageType , vector Momentum )
{
local float HealThreshold , HealthPct ;
super . NotifyTakeHit ( InstigatedBy , HitLocation , Damage , damageType , Momentum ) ;
// When our health gets low, go hunt a player to draw life and enter the next battle phase
if ( ! MyHansPawn . bHealedThisPhase && MyHansPawn . CurrentBattlePhase < 4 )
{
HealThreshold = MyHansPawn . BattlePhases [ MyHansPawn . CurrentBattlePhase - 1 ] . HealThresholds [ WorldInfo . Game . GetModifiedGameDifficulty ( ) ] ;
HealthPct = GetHealthPercentage ( ) ;
// Summon minions if we haven't yet
if ( ! bSummonedThisPhase && HealthPct < HealThreshold + 0.18 f ) //0.16
{
MyHansPawn . SummonMinions ( ) ;
bSummonedThisPhase = true ;
// Fallback so zeds can't spawn forever
SetTimer ( 30. f , false , nameOf ( Timer _StopSummoningZeds ) ) ;
}
// Enter hunt and heal mode if we've dropped below the health threshold
if ( HealthPct < HealThreshold )
{
MyHansPawn . SetHuntAndHealMode ( true ) ;
}
}
}
/** Special move has ended, whether cleanly or aborted */
function NotifySpecialMoveEnded ( KFSpecialMove SM )
{
super . NotifySpecialMoveEnded ( SM ) ;
// Our guns are put away, toss some grenades
if ( ! bWantsToFlee && ! bFleeing && Enemy != None && MyHansPawn != none && MyHansPawn . bPendingSmokeGrenadeBarrage )
{
DoSmokeGrenadeThrow ( true , true ) ;
}
if ( SM . Handle == 'KFSM_MeleeAttack'
|| SM . Handle == 'KFSM_Hans_GrenadeBarrage'
|| SM . Handle == 'KFSM_Hans_GrenadeHalfBarrage'
|| SM . Handle == 'KFSM_Hans_ThrowGrenade'
|| SM . Handle == 'KFSM_Taunt'
|| SM . Handle == 'KFSM_WalkingTaunt' )
{
LastAttackMoveFinishTime = WorldInfo . TimeSeconds ;
}
// Handle flee interruptions
if ( ! bWantsToFlee )
{
// Retarget if it's been enough time since we changed targets
if ( SM . Handle == 'KFSM_MeleeAttack' && ` TimeSince(LastRetargetTime) > ActualRetargetWaitTime )
{
CheckForEnemiesInFOV ( 3000. f , - 1. f , 1. f , true ) ;
}
}
else if ( MyHansPawn . bGunsEquipped )
{
class 'AICommand_Hans_GunStance' . static . SetGunStance ( self , 0 ) ;
}
else if ( PendingDoor == none && ! bFleeing )
{
Flee ( ) ;
// Play dialog indicating the next phase has started
if ( ! bFleeInterrupted )
{
` DialogManager.PlayHansSmokeDialog( MyHansPawn, true );
}
}
// Reset flee interrupted flag in case this was an attack special move
bFleeInterrupted = false ;
// Evaluate sprinting whenever we finish a special move so sprinting will be snappy!
EvaluateSprinting ( ) ;
}
/** Evaluate if we should start/stop sprinting, and then set the sprinting flag */
function EvaluateSprinting ( )
{
if ( MyKFPawn == none || ! MyKFPawn . IsAliveAndWell ( ) )
{
return ;
}
if ( bFleeing || bWantsToFlee )
{
MyKFPawn . SetSprinting ( true ) ;
}
else if ( Enemy != none )
{
if ( ShouldSprint ( ) )
{
MyKFPawn . SetSprinting ( true ) ;
}
else
{
MyKFPawn . SetSprinting ( false ) ;
}
}
}
/** 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 ;
local 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 )
{
NewTrackedEnemy . TrackedEnemy = KFP ;
NewTrackedEnemy . LastTimeVisible = WorldInfo . TimeSeconds ;
NewTrackedEnemy . LastVisibleLocation = Seen . Location ;
RecentlySeenEnemyList [ RecentlySeenEnemyList . Length ] = NewTrackedEnemy ;
}
else
{
RecentlySeenEnemyList [ EnemyListIndex ] . LastTimeVisible = WorldInfo . TimeSeconds ;
RecentlySeenEnemyList [ EnemyListIndex ] . LastVisibleLocation = Seen . Location ;
}
}
// Throw down our smoke screen before going after a player to heal
if ( bHasFledThisPhase && MyHansPawn . bInHuntAndHealMode && ! bHasUsedSmokeScreenThisPhase )
{
bHasUsedSmokeScreenThisPhase = true ;
SetTimer ( 2. f + fRand ( ) , false , nameOf ( Timer _DoHuntAndHealSmokeGrenadeThrow ) ) ;
}
}
/** Throws a smoke grenade barrage after waiting a short time */
function Timer _DoHuntAndHealSmokeGrenadeThrow ( )
{
// Toss some smoke grenades out and summon some minions to help
// while I try and find someont to suck some life from. If Hans
// has his guns out, set the flag to wait til his guns are put
// away before trying to throw the smoke grenade barrage and
// Spawn the minion zeds
if ( ! MyHansPawn . bGunsEquipped && ! MyHansPawn . IsImpaired ( ) && ! MyHansPawn . IsIncapacitated ( ) )
{
DoSmokeGrenadeThrow ( true ) ;
}
else
{
MyHansPawn . bPendingSmokeGrenadeBarrage = true ;
}
}
/** Start the throw grenade command with nerve gas, optionally for the grenade barrage attack */
function DoNerveGasGrenadeThrow ( optional bool bGrenadeBarrage )
{
if ( MyHansPawn != none )
{
MyHansPawn . SetActiveGrenadeClassNerveGas ( ) ;
}
if ( class 'AICommand_ThrowGrenade' . static . ThrowGrenade ( self , bGrenadeBarrage ? 1 : 0 ) )
{
if ( MyHansPawn != none )
{
MyHansPawn . BarrageTossCount = 0 ;
MyHansPawn . bDoingBarrage = bGrenadeBarrage ;
}
}
}
/** Timer function called during latent moves that determines whether NPC should sprint or stop sprinting */
function bool ShouldSprint ( )
{
local float RangeToEnemy ;
if ( Enemy != none && MyHansPawn != none && ! MyHansPawn . bIsHeadless && ! MyHansPawn . bEmpPanicked )
{
// Always sprint if we're trying to track down players to heal from
if ( MyHansPawn . bInHuntAndHealMode )
{
//`log(self@GetFuncName()$" bInHuntAndHealMode 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 ;
}
// No sprinting with the guns out!
if ( MyHansPawn . bGunsEquipped )
{
RangeToEnemy = VSize ( Enemy . Location - Pawn . Location ) ;
// Sprint if we're too close to shoot
if ( RangeToEnemy > StartShootingRange * MyHansPawn . GetAttackRangeScale ( ) || rangeToEnemy < MinShootingRange )
{
//`log(self@GetFuncName()$" guns out but enemy not in gun range. Should sprint = true!");
return true ;
}
else
{
//`log(self@GetFuncName()$" guns out enemy not in gun range. Should sprint = false!");
return false ;
}
}
//`log(self@GetFuncName()$" Generic should sprint depending on phase: "$MyHansPawn.DesireSprintingInThisPhase());
return MyHansPawn . DesireSprintingInThisPhase ( ) ;
}
//`log(self@GetFuncName()$" Generic should sprint = false!");
return false ;
}
/ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* Combat
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * /
/ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* Shooting
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * /
/ * *
* Handle the firing of the pawn ' s weapon
* /
function FireTimer ( )
{
local float UsedBarrageWaitTime , UsedBurstWaitTime ;
// if we no longer have an Enemy or a pawn return
if ( Enemy == none || Pawn == none )
{
// We've got nothing to Fire at
ClearFireTiming ( ) ;
return ;
}
// Lost sight of enemy, stop firing
if ( ! CanSee ( Enemy ) && ( WorldInfo . TimeSeconds - LastEnemySightedTime >= LostSightStopFireDelay ) )
{
//`Log(Self$" "$GetStateName()$" "$GetFuncName()$"() Can't see the enemy, stopping shooting TimeDiff: "$`TimeSince(LastEnemySightedTime)$" LostSightStopFireDelay = "$LostSightStopFireDelay);
ClearFireTiming ( ) ;
TimeFirstSawTarget = 0 ;
// Clear any previous barrage so we start firing again immediately after reacquiring the target
BarrageStartTime = 0 ;
return ;
}
// Don't fire during a Melee attack!
if ( MyKFPawn . IsDoingMeleeAttack ( ) )
{
ClearFireTiming ( ) ;
return ;
}
// if there is a friendly in the way don't fire!
// if( IsFriendlyBlockingFireLine() )
// {
// // `CombatLog(Self$" "$GetStateName()$" "$GetFuncName()$"() Friendly blocking fire line, not shooting");
// SetTimer(0.5, false, 'FireTimer');
// return;
// }
// Store some info so we can time target acquisition
if ( LastTarget == none || TimeFirstSawTarget == 0 )
{
LastTarget = Enemy ;
TimeFirstSawTarget = WorldInfo . TimeSeconds ;
}
// Wait to fire until the TargetAquisitionDelay is over
if ( TimeFirstSawTarget > 0 && ` TimeSince(TimeFirstSawTarget) < TargetAquisitionDelay )
{
//`Log(Self$" "$GetStateName()$" "$GetFuncName()$"() Waiting to aquire target, TimeDiff: "$`TimeSince(TimeFirstSawTarget)$" TargetAquisitionDelay = "$TargetAquisitionDelay);
SetTimer ( TargetAquisitionDelay - ` TimeSince(TimeFirstSawTarget), false, nameof(FireTimer), self);
Pawn . StopFiring ( ) ;
// Clear any previous barrage so we start firing again immediately after reacquiring the target
BarrageStartTime = 0 ;
return ;
}
// Pause between groups of bursts to give the firing a good cadence
if ( BarrageStartTime > 0 && ` TimeSince(BarrageStartTime) > BarrageLength )
{
if ( Enemy != none && Pawn != none )
{
UsedBarrageWaitTime = BarrageWaitTime * EvalInterpCurveFloat ( BarrageWaitDistanceCurve , VSize ( Pawn . Location - Enemy . Location ) ) ;
//`log("Barrage Shooting at enemy that is "$VSize(Pawn.Location - Enemy.Location)/100.0$" meters away BarrageWait is "$UsedBarrageWaitTime$" scale is "$EvalInterpCurveFloat(BarrageWaitDistanceCurve, VSize(Pawn.Location - Enemy.Location)));
}
else
{
UsedBarrageWaitTime = BarrageWaitTime ;
}
SetTimer ( UsedBarrageWaitTime , false , nameof ( FireTimer ) , self ) ;
BarrageStartTime = 0 ;
Pawn . StopFiring ( ) ;
//`Log(Self$" "$GetStateName()$" "$GetFuncName()$"() Pausing between baragges");
return ;
}
else if ( BarrageStartTime == 0 )
{
BarrageStartTime = WorldInfo . TimeSeconds ;
}
// Reset the burst settings when we finish a burst
if ( BurstAmount == 0 )
{
//`Log(Self$" "$GetStateName()$" "$GetFuncName()$"() Burst amount is 0");
BurstAmount = RandRange ( MinBurstAmount , MaxBurstAmount ) ;
// See if we can no longer shoot at our target when a burst ends
if ( ! CanPerformShotAttack ( ) )
{
//`Log(Self$" "$GetStateName()$" "$GetFuncName()$"() BurstAmount hit zero, and CanPerformShotAttack is false so stopping shooting");
ClearFireTiming ( ) ;
// Clear any previous barrage so we start firing again immediately after reacquiring the target
BarrageStartTime = 0 ;
}
// Set up the next burst
else
{
UsedBurstWaitTime = BurstWaitTime + ( ( FRand ( ) * BurstWaitTime * 0.15 ) - ( BurstWaitTime * 0.3 ) ) ;
if ( Enemy != none && Pawn != none )
{
UsedBurstWaitTime = UsedBurstWaitTime * EvalInterpCurveFloat ( BurstWaitDistanceCurve , VSize ( Pawn . Location - Enemy . Location ) ) ;
//`log("Burst Shooting at enemy that is "$VSize(Pawn.Location - Enemy.Location)/100.0$" meters away BurstWaitTime is "$UsedBurstWaitTime$" scale is "$EvalInterpCurveFloat(BurstWaitDistanceCurve, VSize(Pawn.Location - Enemy.Location)));
}
//`Log(Self$" "$GetStateName()$" "$GetFuncName()$"() BurstAmount hit zero, stopping firing. New BurstAmount is: "$BurstAmount);
SetTimer ( UsedBurstWaitTime , false , nameof ( FireTimer ) , self ) ;
Pawn . StopFiring ( ) ;
// Select a new enemy 50% of the time when a burst ends
if ( FRand ( ) < 0.50 )
{
SelectNewGunFireEnemy ( KFPawn ( Enemy ) ) ;
}
}
return ;
}
// if we can see our Enemy and we're facing our Enemy and we've waited long enough to shoot again
if ( ! Pawn . NeedToTurn ( GetAimingLocation ( ) ) && WorldInfo . TimeSeconds - LastFireTime > Pawn . Weapon . FireInterval [ LastFireMode ] )
{
// Shoot at our Enemy and record the Fire Time
Pawn . BotFire ( true ) ;
LastFireMode = 0 ;
// `Log("Time since last fire = "$(WorldInfo.TimeSeconds - LastFireTime));
LastFireTime = WorldInfo . TimeSeconds ;
//`Log(Self$" "$GetStateName()$" "$GetFuncName()$"() Shooting!!!!! BurstAmount is: "$BurstAmount);
SetTimer ( Pawn . Weapon . FireInterval [ LastFireMode ] * BurstAmount , false , nameof ( FireTimer ) , self ) ;
BurstAmount = 0 ;
` DialogManager.PlayBeingShotAtDialog( KFPawn_Human(Enemy), MyKFPawn );
}
else
{
//`Log(Self$" "$GetStateName()$" "$GetFuncName()$"() Not Shooting NeedToTurn = "$Pawn.NeedToTurn(Enemy.Location)$" Last Fire Diff = "$(WorldInfo.TimeSeconds - LastFireTime)$" FireInterval = "$Pawn.Weapon.FireInterval[0]);
SetTimer ( 0.1 , false , nameof ( FireTimer ) , self ) ;
}
}
/ * *
* Clear firing cycle
* /
function ClearFireTiming ( )
{
SetTimer ( 0.0 , false , nameof ( FireTimer ) , self ) ;
SetTimer ( 0.0 , false , nameof ( StartFireTiming ) , self ) ;
if ( Pawn != none )
{
Pawn . StopFiring ( ) ;
}
}
/** Set a new enemy, returns false potential enemy is a player and AI is set to ignore players */
event bool SetEnemy ( Pawn NewEnemy )
{
// Don't let Hans switch enemies in the middle of draining his current enemy,
// unless that enemy is dead
if ( ! CanSwitchEnemies ( )
|| ( MyKFPawn != none && MyKFPawn . IsDoingSpecialMove ( SM _GrappleAttack ) && Enemy != none
&& Enemy . IsAliveAndWell ( ) && Enemy . CanAITargetThisPawn ( self ) ) )
{
return false ;
}
return 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 , bCanTaunt ) ;
if ( OldEnemy != Enemy )
{
LastRetargetTime = WorldInfo . TimeSeconds ;
ActualRetargetWaitTime = RandRange ( RetargetWaitTimeRange . X , RetargetWaitTimeRange . Y ) ;
}
}
/ * *
* Pick a different visible enemy to fire at so we can cycle between enemies
* /
function SelectNewGunFireEnemy ( KFPawn CurrentEnemy )
{
local int RandIdx , i ;
local array < TrackedEnemyInfo > TargetCandidates ;
// local Vector CamLoc;
// local Rotator CamRot;
// local Vector X, Y, Z;
local int EnemyListIndex ;
// Randomly pick another visible enemy to shoot at
if ( RecentlySeenEnemyList . Length > 0 )
{
// Validate targets to shoot
for ( i = RecentlySeenEnemyList . Length - 1 ; i >= 0 ; i -- )
{
if ( RecentlySeenEnemyList [ i ] . TrackedEnemy == none
|| ! RecentlySeenEnemyList [ i ] . TrackedEnemy . IsAliveAndWell ( )
|| ! RecentlySeenEnemyList [ i ] . TrackedEnemy . CanAITargetThisPawn ( self ) )
{
RecentlySeenEnemyList . Remove ( i , 1 ) ;
continue ;
}
else if ( ! MyHansPawn . NeedToTurnEx ( RecentlySeenEnemyList [ i ] . LastVisibleLocation , 0.0 ) )
{
TargetCandidates [ TargetCandidates . Length ] = RecentlySeenEnemyList [ i ] ;
}
}
// Clear out this pawn if it was the last one we fired at and it was engaged recently
for ( i = TargetCandidates . Length - 1 ; i >= 0 ; i -- )
{
if ( TargetCandidates . Length > 1 && TargetCandidates [ i ] . TrackedEnemy == LastRecentlySeenEnemyGunned
&& ` TimeSince(TargetCandidates[i].LastTimeFiredOn) < 3.0 )
{
TargetCandidates . Remove ( i , 1 ) ;
}
}
// Target selection debugging
// Get camera location/rotation
// GetPlayerViewPoint( CamLoc, CamRot );
// GetAxes( CamRot, X, Y, Z );
// FlushPersistentDebugLines();
// DrawDebugCone(CamLoc,X,500.0, Acos(0.0), Acos(0.0),16,MakeColor(0,255,0,255),true);
if ( TargetCandidates . Length > 0 )
{
RandIdx = Rand ( TargetCandidates . Length ) ;
//`log("Selected random gunfire target index "$RandIdx$" Pawn: "$TargetCandidates[RandIdx].TrackedEnemy);
LastRecentlySeenEnemyGunned = TargetCandidates [ RandIdx ] . TrackedEnemy ;
if ( LastRecentlySeenEnemyGunned != none )
{
EnemyListIndex = RecentlySeenEnemyList . Find ( 'TrackedEnemy' , LastRecentlySeenEnemyGunned ) ;
if ( EnemyListIndex != INDEX _NONE )
{
RecentlySeenEnemyList [ EnemyListIndex ] . LastTimeFiredOn = WorldInfo . TimeSeconds ;
}
}
}
}
}
/ * *
* Returns the location this AI should be aiming at to shoot its enemy .
* /
function vector GetAimingLocation ( )
{
local KFPawn KFP ;
local vector AimingLocation ;
local int EnemyListIndex ;
KFP = KFPawn ( Enemy ) ;
// Flag the last guy we selected to fire at and aim at him
if ( LastRecentlySeenEnemyGunned != none )
{
EnemyListIndex = RecentlySeenEnemyList . Find ( 'TrackedEnemy' , LastRecentlySeenEnemyGunned ) ;
if ( EnemyListIndex != INDEX _NONE )
{
RecentlySeenEnemyList [ EnemyListIndex ] . LastTimeFiredOn = WorldInfo . TimeSeconds ;
// We recently selected this guy to fire on
if ( ` TimeSince(RecentlySeenEnemyList[EnemyListIndex].LastTimeFiredOn) < 1.0 )
{
KFP = LastRecentlySeenEnemyGunned ;
}
}
}
if ( KFP == none )
{
if ( Focus != none )
{
AimingLocation = Focus . Location ;
}
else if ( Pawn != none )
{
AimingLocation = Pawn . Location + vector ( Pawn . Rotation ) * 128 ;
}
}
else
{
AimingLocation = KFP . Location + Vect ( 0 , 0 , 1 ) * KFP . BaseEyeHeight * 0.5 ; //KFP.Mesh.GetBoneLocation(KFP.HeadBoneName,0);
}
return AimingLocation ;
}
/ * *
* Start a firing cycle
* /
function StartFireTiming ( )
{
// Check to see if its the first time we have fired, or the first time we've fired this attack.
// If that is the case, fire now, then let the FireTimer handle any subsequent fires
if ( Pawn != none && Pawn . Weapon != none )
{
if ( ` TimeSince( LastFireTime ) > (Pawn.Weapon.FireInterval[0] + BurstWaitTime + BurstWaitTime * 0.3) )
{
//`log(Self$" "$GetStateName()$" "$GetFuncName());
BurstAmount = RandRange ( MinBurstAmount , MaxBurstAmount ) ;
FireTimer ( ) ;
}
else
{
//`log(Self$" "$GetStateName()$" "$GetFuncName()$" ?????????????????????????????? Not starting fire timing!!!");
}
}
}
/ * *
* Validate the next gun shot attack
* /
function bool CanPerformShotAttack ( optional bool bStart )
{
local float rangeToEnemy ;
// No guns when fleeing
if ( bFleeing || bWantsToFlee )
{
return false ;
}
// Don't start shooting if hans hasn't drawn his gun yet, or is already trying to fire
if ( bStart && ( Pawn . IsFiring ( )
|| IsTimerActive ( nameof ( FireTimer ) , self ) || IsTimerActive ( nameof ( StartFireTiming ) , self ) ) )
{
//`log("bStart not shooting because: TimeSince(StartDrawGunsTime): "$`TimeSince(StartDrawGunsTime)$" < DrawGunFireDelay: "$DrawGunFireDelay$" Pawn.IsFiring(): "$Pawn.IsFiring()$" FireTimer Active: "$IsTimerActive(nameof(FireTimer), self)$" StartFireTiming Active: "$IsTimerActive(nameof(StartFireTiming), self));
return false ;
}
// Don't start shooting if Hans just did a melee attack
if ( bStart && ( ` TimeSince(LastAttackMoveFinishTime) < PostAttackMoveGunCooldown) )
{
return false ;
}
// Don't let us shoot if Hans hasn't had his guns out long enough
if ( ` TimeSince(StartDrawGunsTime) < DrawGunFireDelay )
{
return false ;
}
// Don't shoot while sprinting
if ( MyKFPawn != none && MyKFPawn . bIsSprinting )
{
return false ;
}
if ( Enemy != none )
{
if ( bStart && LastShotTime > 0 && ( ` TimeSince(LastShotTime) < ShootingCooldown) )
{
return false ;
}
if ( MyKFPawn . IsImpaired ( ) )
{
return false ;
}
rangeToEnemy = VSize ( Enemy . Location - MyKFPawn . Location ) ;
if ( rangeToEnemy < StartShootingRange * MyHansPawn . GetAttackRangeScale ( ) && rangeToEnemy > MinShootingRange && ! MyKFPawn . IsDoingMeleeAttack ( ) )
{
if ( CanSee ( Enemy ) )
{
return true ;
}
else if ( ( WorldInfo . TimeSeconds - LastEnemySightedTime ) >= LostSightStopFireDelay )
{
//`Log(Self$" "$GetStateName()$" "$GetFuncName()$"() Can't see the enemy, can't shoot TimeDiff: "$`TimeSince(LastEnemySightedTime)$" LostSightStopFireDelay = "$LostSightStopFireDelay);
TimeFirstSawTarget = 0 ;
}
}
}
return false ;
}
/ * *
* Can I do a stance change right now
* /
function bool CanStanceChange ( )
{
return ( LastStanceChangeTime == 0 || ( ` TimeSince(LastStanceChangeTime) > StanceChangeCooldown));
}
/** Override to tick the ranged combat system */
simulated function Tick ( FLOAT DeltaTime )
{
Super . Tick ( DeltaTime ) ;
if ( Role == ROLE _Authority )
{
TickRangedCombatDecision ( ) ;
}
}
/** Tick the ranged combat system */
function TickRangedCombatDecision ( )
{
local int i ;
// Update the ranged enemy visibility tracking
// TODO: update this slower if we aren't doing an attack!
if ( LastRecentSeenEnemyListUpdateTime == 0 || ` TimeSince(LastRecentSeenEnemyListUpdateTime) > RecentSeenEnemyListUpdateInterval )
{
LastRecentSeenEnemyListUpdateTime = WorldInfo . TimeSeconds ;
for ( i = RecentlySeenEnemyList . Length - 1 ; i >= 0 ; i -- )
{
if ( RecentlySeenEnemyList [ i ] . TrackedEnemy == none
|| ! RecentlySeenEnemyList [ i ] . TrackedEnemy . IsAliveAndWell ( )
|| ! RecentlySeenEnemyList [ i ] . TrackedEnemy . CanAITargetThisPawn ( self )
|| ` TimeSince(RecentlySeenEnemyList[i].LastTimeVisible) > 5.0 )
{
RecentlySeenEnemyList . Remove ( i , 1 ) ;
}
else if ( CanSee ( RecentlySeenEnemyList [ i ] . TrackedEnemy ) )
{
RecentlySeenEnemyList [ i ] . LastVisibleLocation = RecentlySeenEnemyList [ i ] . TrackedEnemy . Location ;
RecentlySeenEnemyList [ i ] . LastTimeVisible = WorldInfo . TimeSeconds ;
}
}
}
// No grenades or guns when fleeing
if ( bFleeing || bWantsToFlee )
{
return ;
}
//Don't attack while we're in theatrics
if ( CommandList != none && CommandList . class == class 'AICommand_BossTheatrics' )
{
return ;
}
if ( Enemy != None && ( LastGrenadeAttackEvalTime == 0 || ` TimeSince(LastGrenadeAttackEvalTime) > GrenadeAttackEvalInterval) )
{
LastGrenadeAttackEvalTime = WorldInfo . TimeSeconds ;
if ( SetupGrenadeAttack ( ) )
{
if ( CurrentNadeAttackType == HNT _HEGrenade )
{
DoGrenadeThrow ( false ) ;
}
else if ( CurrentNadeAttackType == HNT _HEGrenadeBarrage )
{
DoGrenadeThrow ( true ) ;
}
else if ( CurrentNadeAttackType == HNT _NerveGas )
{
DoNerveGasGrenadeThrow ( false ) ;
}
else if ( CurrentNadeAttackType == HNT _NerveGasBarrage )
{
DoNerveGasGrenadeThrow ( true ) ;
}
else if ( CurrentNadeAttackType == HNT _Smoke )
{
DoSmokeGrenadeThrow ( false ) ;
}
}
}
TickGunSystem ( ) ;
}
/** Tick the gun attack system which handles drawing/putting away the weapon, starting the shooting cycle, etc */
function TickGunSystem ( )
{
local KFPawn _ZedHansBase HansPawn ;
local KFSpecialMove curMove ;
local float rangeToEnemy ;
//local vector EyeLocation;
local bool bGrenadeAttackInterrupt ;
if ( MyKFPawn . SpecialMoves . Length > MyKFPawn . SpecialMove )
{
curMove = MyKFPawn . SpecialMoves [ MyKFPawn . SpecialMove ] ;
}
else
{
curMove = none ;
}
if ( Pawn . Physics == PHYS _Walking )
{
HansPawn = KFPawn _ZedHansBase ( MyKFPawn ) ;
// Aiming debugging
/ * E y e L o c a t i o n = M y K F P a w n . L o c a t i o n + v e c t ( 0 , 0 , 1 ) * M y K F P a w n . B a s e E y e H e i g h t ;
if ( Focus == none )
{
if ( IsZero ( GetFocalPoint ( ) ) )
{
DrawDebugLine ( EyeLocation , EyeLocation + normal ( GetFocalPoint ( ) ) * 5000. f , 255 , 0 , 0 , false ) ;
}
else
{
DrawDebugLine ( EyeLocation , EyeLocation + normal ( GetFocalPoint ( ) - EyeLocation ) * 5000. f , 0 , 255 , 0 , false ) ;
}
}
else
{
DrawDebugLine ( EyeLocation , Focus . Location , 255 , 255 , 0 , false ) ;
} * /
// Don't shoot if we're performing a special move that prevents it
if ( curMove != none && curMove . bDisablesWeaponFiring )
{
//`log("Not shooting because special move disallows it");
if ( Pawn . IsFiring ( ) || IsTimerActive ( nameof ( FireTimer ) , self ) || IsTimerActive ( nameof ( StartFireTiming ) , self ) )
{
ClearFireTiming ( ) ;
// Clear any previous barrage so we are eligible to firing again right after special move is over
BarrageStartTime = 0 ;
}
return ;
}
// Make sure we're not in the middle of switching stances with the weapon, or doing boss theatrics
if ( HansPawn != none && ( curMove == none || ! MyKFPawn . IsDoingSpecialMove ( SM _ChangeStance ) && ! MyKFPawn . IsDoingSpecialMove ( SM _BossTheatrics ) ) )
{
// If we're firing at our target, aim at that location
if ( Pawn . IsFiring ( ) || IsTimerActive ( nameof ( FireTimer ) , self ) || IsTimerActive ( nameof ( StartFireTiming ) , self ) )
{
Focus = none ;
SetFocalPoint ( GetAimingLocation ( ) ) ;
}
// Switch back to aiming at our enemy if we're not moving to a pathnode
else if ( Focus == none )
{
Focus = Enemy ;
}
if ( Enemy != none )
{
rangeToEnemy = VSize ( Enemy . Location - MyKFPawn . Location ) ;
}
else
{
// If there is not enemy just act like they are out of range
rangeToEnemy = MaxInt ;
}
bGrenadeAttackInterrupt = GrenadeAttackInterruptGuns ( ) ;
// Put the guns away if the enemy is out of range
if ( HansPawn . bGunsEquipped && ( ( rangeToEnemy > StartShootingRange * MyHansPawn . GetAttackRangeScale ( ) )
|| HansPawn . bInHuntAndHealMode || ! HansPawn . CanUseGunsInThisPhase ( ) || HansPawn . bPendingSmokeGrenadeBarrage
|| bGrenadeAttackInterrupt )
&& CanStanceChange ( ) )
{
ClearFireTiming ( ) ;
class 'AICommand_Hans_GunStance' . static . SetGunStance ( self , 0 ) ;
//`log("Putting the guns away because there is an enemy too close");
}
// Draw the guns if the enemy is in range, and we're not waiting for a cooldown
else if ( ! HansPawn . bGunsEquipped && ( LastShotTime == 0 || ( ` TimeSince(LastShotTime) > ShootingCooldown))
&& rangeToEnemy < StartShootingRange * MyHansPawn . GetAttackRangeScale ( ) && ! HansPawn . bInHuntAndHealMode
&& HansPawn . CanUseGunsInThisPhase ( ) && CanStanceChange ( ) && ! bGrenadeAttackInterrupt )
{
class 'AICommand_Hans_GunStance' . static . SetGunStance ( self , 1 ) ;
StartDrawGunsTime = WorldInfo . TimeSeconds ;
//`log("Drawing our guns StartDrawGunsTime = "$StartDrawGunsTime);
// Clear any previous barrage so we are eligible to firing again right after special move is over
BarrageStartTime = 0 ;
// Don't start the cooldown again if we haven't already crossed the cooldown
if ( StartGunAttackTime == 0 || ( ` TimeSince(StartGunAttackTime) > MaxGunAttackLength) )
{
StartGunAttackTime = WorldInfo . TimeSeconds ;
//`log("Setting StartGunAttackTime = "$StartGunAttackTime);
}
}
// Put the guns away if its time to cooldown or we can't use guns in this phase
else if ( StartGunAttackTime > 0 && ( ` TimeSince(StartGunAttackTime) > MaxGunAttackLength//StartDrawGunsTime > 0 && ( ` TimeSince ( StartDrawGunsTime ) > MaxGunAttackLength
|| ! HansPawn . CanUseGunsInThisPhase ( ) ) && CanStanceChange ( ) )
{
//`log("Not shooting because we're in cooldown");
if ( HansPawn . bGunsEquipped )
{
ClearFireTiming ( ) ;
LastShotTime = WorldInfo . TimeSeconds ;
// Reset the draw guns time
StartDrawGunsTime = 0 ;
//`log("Putting our guns away StartDrawGunsTime = "$StartDrawGunsTime);
//`log("Putting Guns Away due to cooldown StartDrawGunsTime = "$StartDrawGunsTime$" LastShotTime = "$LastShotTime);
class 'AICommand_Hans_GunStance' . static . SetGunStance ( self , 0 ) ;
}
}
// All clear, start shooting!
else if ( HansPawn . bGunsEquipped && HansPawn . CanUseGunsInThisPhase ( )
&& CanPerformShotAttack ( true ) )
{
//`DialogManager.PlayBeingShotAtDialog( KFPawn_Human(Enemy), HansPawn );
StartFireTiming ( ) ;
}
}
}
}
/** Shows this KFAIController's RandgedAttack info on screen (see ShowAIInfo cheat) */
function DrawRangedAttackInfo ( HUD Hud )
{
local Canvas Canvas ;
local float UsedShootCooldown ;
local KFSpecialMove curMove ;
local bool bMoveDisablesFiring ;
local float UsedGunAttackCooldown ;
local float UsedGlobalNadeAttackCooldown ;
local float UsedHENadeTossCooldown ;
local float UsedHENadeBarrageCooldown ;
local float UsedNerveGasTossCooldown ;
local float UsedNerveGasBarrageCooldown ;
local float UsedSmokeTossCooldown ;
local float UsedDrawGunsCooldown ;
local float UsedAttackMoveGunsCooldown ;
local float UsedGunTargetAquisitionCooldown ;
local int i ;
local vector EyeLocation ;
if ( MyKFPawn == none )
{
return ;
}
if ( MyKFPawn . SpecialMoves . Length > MyKFPawn . SpecialMove )
{
curMove = MyKFPawn . SpecialMoves [ MyKFPawn . SpecialMove ] ;
}
else
{
curMove = none ;
}
// Don't shoot if we're performing a special move that prevents it
if ( curMove != none && curMove . bDisablesWeaponFiring )
{
bMoveDisablesFiring = true ;
}
Canvas = HUD . Canvas ;
Canvas . Font = class 'Engine' . Static . GetTinyFont ( ) ;
Canvas . SetPos ( Canvas . SizeX * 0.7 f , Canvas . SizeY * 0.25 f ) ;
Canvas . SetDrawColor ( 0 , 200 , 50 ) ;
DrawDebugText ( HUD , "Ranged Combat for " $string ( MyKFPawn . Name ) ) ;
DrawDebugText ( HUD , "--------------------------------------" ) ;
DrawDebugText ( HUD , "Battle Phase: " $MyHansPawn . CurrentBattlePhase ) ;
DrawDebugText ( HUD , "--Guns--" ) ;
DrawDebugText ( HUD , "Guns Equipped: " $MyHansPawn . bGunsEquipped$ " Can Use Guns In This Phase: " $MyHansPawn . CanUseGunsInThisPhase ( ) ) ;
DrawDebugText ( HUD , "Stance Changing: " $MyKFPawn . IsDoingSpecialMove ( SM _ChangeStance ) $ " CurrentMove: " $curMove$ " bDisablesWeaponFiring: " $bMoveDisablesFiring ) ;
if ( ` TimeSince(StartDrawGunsTime) > DrawGunFireDelay)
{
UsedDrawGunsCooldown = 0 ;
}
else
{
UsedDrawGunsCooldown = DrawGunFireDelay - ` TimeSince(StartDrawGunsTime);
}
if ( ` TimeSince(LastAttackMoveFinishTime) > PostAttackMoveGunCooldown)
{
UsedAttackMoveGunsCooldown = 0 ;
}
else
{
UsedAttackMoveGunsCooldown = PostAttackMoveGunCooldown - ` TimeSince(LastAttackMoveFinishTime);
}
if ( ` TimeSince(LastShotTime) > ShootingCooldown)
{
UsedShootCooldown = 0 ;
}
else
{
UsedShootCooldown = ShootingCooldown - ` TimeSince(LastShotTime);
}
if ( ` TimeSince(StartGunAttackTime) > MaxGunAttackLength)
{
UsedGunAttackCooldown = 0 ;
}
else
{
UsedGunAttackCooldown = MaxGunAttackLength - ` TimeSince(StartGunAttackTime);
}
if ( ` TimeSince(TimeFirstSawTarget) > TargetAquisitionDelay)
{
UsedGunTargetAquisitionCooldown = 0 ;
}
else
{
UsedGunTargetAquisitionCooldown = TargetAquisitionDelay - ` TimeSince(TimeFirstSawTarget);
}
DrawDebugText ( HUD , "Time Til We Can Shoot : " $UsedShootCooldown$ " Time Til We Must Stop Shooting: " $UsedGunAttackCooldown , ( UsedShootCooldown <= 0 ) ? MakeColor ( 0 , 255 , 0 , 255 ) : MakeColor ( 255 , 0 , 0 , 255 ) ) ;
DrawDebugText ( HUD , "Draw Guns Delay Til We Can Shoot : " $UsedDrawGunsCooldown , ( UsedDrawGunsCooldown <= 0 ) ? MakeColor ( 0 , 255 , 0 , 255 ) : MakeColor ( 255 , 0 , 0 , 255 ) ) ;
DrawDebugText ( HUD , "Attack Move Delay Til We Can Shoot : " $UsedAttackMoveGunsCooldown , ( UsedAttackMoveGunsCooldown <= 0 ) ? MakeColor ( 0 , 255 , 0 , 255 ) : MakeColor ( 255 , 0 , 0 , 255 ) ) ;
DrawDebugText ( HUD , "Target Aquisition Delay Til We Can Shoot : " $UsedGunTargetAquisitionCooldown , ( UsedGunTargetAquisitionCooldown <= 0 ) ? MakeColor ( 0 , 255 , 0 , 255 ) : MakeColor ( 255 , 0 , 0 , 255 ) ) ;
DrawDebugText ( HUD , "FireTimer : " $GetRemainingTimeForTimer ( nameof ( FireTimer ) ) $ " StartFireTiming: " $GetRemainingTimeForTimer ( nameof ( StartFireTiming ) ) , MakeColor ( 0 , 255 , 0 , 255 ) ) ;
DrawDebugText ( HUD , "--Grenades--" ) ;
DrawDebugText ( HUD , "CurrentNadeAttackType: " $CurrentNadeAttackType$ " IsThrowingGrenade: " $MyHansPawn . IsThrowingGrenade ( ) ) ;
if ( Enemy != none )
{
DrawDebugText ( HUD , "Good Line Of Site: " $CanSeeByPoints ( Pawn . GetPawnViewLocation ( ) , Enemy . Location , Rotator ( Enemy . Location - Pawn . GetPawnViewLocation ( ) ) ) $ " Too Close To Regular Nade: " $IsWithinAttackRange ( ) ) ;
}
else
{
DrawDebugText ( HUD , "Good Line Of Site: No Enemy, Too Close To Regular Nade: " $IsWithinAttackRange ( ) ) ;
}
if ( MyHansPawn . LastOffensiveNadeTime == 0 || ` TimeSince(MyHansPawn.LastOffensiveNadeTime) > MyHansPawn.GlobalOffensiveNadeCooldown )
{
UsedGlobalNadeAttackCooldown = 0 ;
}
else
{
UsedGlobalNadeAttackCooldown = MyHansPawn . GlobalOffensiveNadeCooldown - ` TimeSince(MyHansPawn.LastOffensiveNadeTime);
}
if ( MyHansPawn . LastHENadeTossTime == 0 || ` TimeSince(MyHansPawn.LastHENadeTossTime) > MyHansPawn.HENadeTossCooldown )
{
UsedHENadeTossCooldown = 0 ;
}
else
{
UsedHENadeTossCooldown = MyHansPawn . HENadeTossCooldown - ` TimeSince(MyHansPawn.LastHENadeTossTime);
}
if ( MyHansPawn . LastHENadeBarrageTime == 0 || ` TimeSince(MyHansPawn.LastHENadeBarrageTime) > MyHansPawn.HENadeBarrageCooldown )
{
UsedHENadeBarrageCooldown = 0 ;
}
else
{
UsedHENadeBarrageCooldown = MyHansPawn . HENadeBarrageCooldown - ` TimeSince(MyHansPawn.LastHENadeBarrageTime);
}
if ( MyHansPawn . LastNerveGasTossTime == 0 || ` TimeSince(MyHansPawn.LastNerveGasTossTime) > MyHansPawn.NerveGasTossCooldown )
{
UsedNerveGasTossCooldown = 0 ;
}
else
{
UsedNerveGasTossCooldown = MyHansPawn . NerveGasTossCooldown - ` TimeSince(MyHansPawn.LastNerveGasTossTime);
}
if ( MyHansPawn . LastNerveGasBarrageTime == 0 || ` TimeSince(MyHansPawn.LastNerveGasBarrageTime) > MyHansPawn.NerveGasBarrageCooldown )
{
UsedNerveGasBarrageCooldown = 0 ;
}
else
{
UsedNerveGasBarrageCooldown = MyHansPawn . NerveGasBarrageCooldown - ` TimeSince(MyHansPawn.LastNerveGasBarrageTime);
}
if ( MyHansPawn . LastSmokeTossTime == 0 || ` TimeSince(MyHansPawn.LastSmokeTossTime) > MyHansPawn.SmokeTossCooldown )
{
UsedSmokeTossCooldown = 0 ;
}
else
{
UsedSmokeTossCooldown = MyHansPawn . SmokeTossCooldown - ` TimeSince(MyHansPawn.LastSmokeTossTime);
}
DrawDebugText ( HUD , "Global Nade Cooldown Complete: " $MyHansPawn . OffensiveGrenadeCooldownComplete ( ) $ " GlobalNadeAttackCooldown: " $UsedGlobalNadeAttackCooldown , MyHansPawn . OffensiveGrenadeCooldownComplete ( ) ? MakeColor ( 0 , 255 , 0 , 255 ) : MakeColor ( 255 , 0 , 0 , 255 ) ) ;
DrawDebugText ( HUD , "Can Smoke Toss: " $MyHansPawn . CanSmokeTossInThisPhase ( ) $ " SmokeTossCooldown: " $UsedSmokeTossCooldown , MyHansPawn . CanSmokeTossInThisPhase ( ) ? MakeColor ( 0 , 255 , 0 , 255 ) : MakeColor ( 255 , 0 , 0 , 255 ) ) ;
DrawDebugText ( HUD , "Can Nerve Gas Toss: " $MyHansPawn . CanTossNerveGasInThisPhase ( ) $ " NerveGasTossCooldown: " $UsedNerveGasTossCooldown , MyHansPawn . CanTossNerveGasInThisPhase ( ) ? MakeColor ( 0 , 255 , 0 , 255 ) : MakeColor ( 255 , 0 , 0 , 255 ) ) ;
DrawDebugText ( HUD , "Can HE Grenade Toss: " $MyHansPawn . CanTossGrenadeInThisPhase ( ) $ " HENadeTossCooldown: " $UsedHENadeTossCooldown , MyHansPawn . CanTossGrenadeInThisPhase ( ) ? MakeColor ( 0 , 255 , 0 , 255 ) : MakeColor ( 255 , 0 , 0 , 255 ) ) ;
DrawDebugText ( HUD , "Can Nerve Gas Barrage: " $MyHansPawn . CanBarrageNerveGasInThisPhase ( ) $ " NerveGasBarrageCooldown: " $UsedNerveGasBarrageCooldown , MyHansPawn . CanBarrageNerveGasInThisPhase ( ) ? MakeColor ( 0 , 255 , 0 , 255 ) : MakeColor ( 255 , 0 , 0 , 255 ) ) ;
DrawDebugText ( HUD , "Can HE Grenade Barrage: " $MyHansPawn . CanGrenadeBarrageInThisPhase ( ) $ " HENadeBarrageCooldown: " $UsedHENadeBarrageCooldown , MyHansPawn . CanGrenadeBarrageInThisPhase ( ) ? MakeColor ( 0 , 255 , 0 , 255 ) : MakeColor ( 255 , 0 , 0 , 255 ) ) ;
DrawDebugText ( HUD , "--RecentlySeenEnemyList--" ) ;
for ( i = 0 ; i < RecentlySeenEnemyList . Length ; i ++ )
{
DrawDebugText ( HUD , "TrackedEnemy #" $i$ ": " $RecentlySeenEnemyList [ i ] . TrackedEnemy ) ;
EyeLocation = MyKFPawn . Location + vect ( 0 , 0 , 1 ) * MyKFPawn . BaseEyeHeight ;
DrawDebugLine ( EyeLocation , RecentlySeenEnemyList [ i ] . LastVisibleLocation , 254 , 89 , 18 , false ) ;
}
}
/ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* Grenades
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * /
/** Return true if one of the standard (not smoke) grenade attack cooldowns is reached and we can attempt a nade throw*/
function bool GrenadeAttackInterruptGuns ( )
{
// Don't interrupt the gun attack with the grenade attack if we just pulled the guns
if ( MyHansPawn . bGunsEquipped && ` TimeSince(StartDrawGunsTime) < GrenadeGunInterruptDelay )
{
return false ;
}
if ( MyHansPawn . CanGrenadeBarrageInThisPhase ( ) )
{
return true ;
}
else if ( MyHansPawn . CanBarrageNerveGasInThisPhase ( ) )
{
return true ;
}
else if ( MyHansPawn . CanTossGrenadeInThisPhase ( ) )
{
return true ;
}
else if ( MyHansPawn . CanTossNerveGasInThisPhase ( ) )
{
return true ;
}
return false ;
}
/** Returns true if we can successfully set up a grenade attack */
function bool SetupGrenadeAttack ( )
{
/** See if Hans can/should do a grenade attack right now*/
if ( MyHansPawn != none && Enemy != none && ! MyHansPawn . IsDoingSpecialMove ( SM _ChangeStance )
&& ! MyHansPawn . IsThrowingGrenade ( ) && ! MyHansPawn . bGunsEquipped
&& CanSeeByPoints ( Pawn . GetPawnViewLocation ( ) , Enemy . Location , Rotator ( Enemy . Location - Pawn . GetPawnViewLocation ( ) ) )
&& MyHansPawn . CacheGrenadeThrowLocation ( ) )
{
// Clear the last grenade attack type
CurrentNadeAttackType = HNT _None ;
// Don't throw grenades up close, unless it's a smoke grenade during hunt and heal
if ( ! IsWithinAttackRange ( ) )
{
// Pick what kind of nade attack Hans should do
if ( MyHansPawn . CanGrenadeBarrageInThisPhase ( ) )
{
CurrentNadeAttackType = HNT _HEGrenadeBarrage ;
}
else if ( MyHansPawn . CanBarrageNerveGasInThisPhase ( ) )
{
CurrentNadeAttackType = HNT _NerveGasBarrage ;
}
else if ( MyHansPawn . CanTossGrenadeInThisPhase ( ) )
{
CurrentNadeAttackType = HNT _HEGrenade ;
}
else if ( MyHansPawn . CanTossNerveGasInThisPhase ( ) )
{
CurrentNadeAttackType = HNT _NerveGas ;
}
}
//Hans keeps tossing out smoke grenades occasionally when he's hunting a
// player to heal from
else
{
if ( MyHansPawn . CanSmokeTossInThisPhase ( ) )
{
CurrentNadeAttackType = HNT _Smoke ;
}
}
// Return true if we found a valid nade attack
if ( CurrentNadeAttackType != HNT _None )
{
return true ;
}
else
{
return false ;
}
}
else
{
return false ;
}
}
function DoStrike ( )
{
local name AttackName ;
if ( MyHansPawn != none && MyHansPawn . PawnAnimInfo != none )
{
AttackName = MyHansPawn . PawnAnimInfo . Attacks [ PendingAnimStrikeIndex ] . Tag ;
// @todo: figure out a way to make this less hard-coded? (see also KFDialogManager::PlayHansKilledDialog)
if ( AttackName == 'Frenzy_Lunge' )
{
` DialogManager.PlayHansFrenzyDialog( MyHansPawn );
}
else if ( AttackName == 'AOE' )
{
` DialogManager.PlayHansAOEDialog( MyHansPawn );
}
}
super . DoStrike ( ) ;
}
/ * *
* Used by movement commands to determine if NPC is close enough consider a strike
* Overridden so that Hans will move all the way to grab range and not get stuck .
* /
function bool IsWithinAttackRange ( )
{
local float DistSqToEnemy ;
// Don't try to attack if our enemy isn't visible
if ( MyHansPawn == none || ! bEnemyIsVisible )
{
return false ;
}
// Don't grab players if you aren't in hunt and heal mode or are waiting for the smoke grenade barrage
if ( ! MyHansPawn . bInHuntAndHealMode )
{
return Super . IsWithinAttackRange ( ) ;
}
// Check distance from enemy versus my grab range value.
DistSqToEnemy = VSizeSq ( Enemy . Location - Pawn . Location ) ;
if ( DistSqToEnemy <= Square ( MinDistanceToPerformGrabAttack ) )
{
return true ;
}
return false ;
}
function bool CanAttackDestructibles ( )
{
return ! MyHansPawn . bGunsEquipped && bCanDoHeavyBump ;
}
/ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* Battle - phase switching
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * /
/** Overridden to handle door usage in flee state */
function NotifyAttackDoor ( KFDoorActor Door )
{
// 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 and move commands
AbortCommand ( CommandList ) ;
// Allow melee again
EnableMeleeRangeEventProbing ( ) ;
// Restart default command
BeginCombatCommand ( GetDefaultCommand ( ) , "Restarting default command" ) ;
}
super . NotifyAttackDoor ( Door ) ;
}
/** Overridden to handle door usage in flee state */
function bool DoorFinished ( )
{
local bool bSuperFinished ;
bSuperFinished = super . DoorFinished ( ) ;
if ( bWantsToFlee && ! bFleeing )
{
if ( MyHansPawn . IsDoingSpecialMove ( ) )
{
MyHansPawn . EndSpecialMove ( ) ;
}
Flee ( ) ;
}
return bSuperFinished ;
}
/ * *
* Set Hans to the next phase in the battle
* /
function NextBattlePhase ( )
{
// Set flee flags
bHasFledThisPhase = false ;
MaxFleeDuration = RandRange ( MinFleeDuration , MaxFleeDuration ) ;
bWantsToFlee = true ;
DisableMeleeRangeEventProbing ( ) ;
// Set smoke grenade barrage flag
bHasUsedSmokeScreenThisPhase = false ;
}
/** Whether enemy switch commands can be run */
function bool CanSwitchEnemies ( )
{
return ! bWantsToFlee && ! bFleeing ;
}
/* 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 || ! MyHansPawn . bInHuntAndHealMode )
{
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
Enemy = none ;
CheckForEnemiesInFOV ( 3000. f , 0.2 f , 1. f , true , false ) ;
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 ( MyHansPawn ) ;
}
// Prevent timeout from interrupting flee
AICSM = FindCommandOfClass ( class 'AICommand_SpecialMove' ) ;
if ( AICSM != none )
{
AICSM . ClearTimeout ( ) ;
}
// Abort all commands
AbortCommand ( CommandList ) ;
// Perform flee
bFleeing = true ;
MyHansPawn . 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 ( 1.5 f , false , nameOf ( Timer _SearchForFleeObstructions ) ) ;
}
}
/** 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 || MyHansPawn . IsDoingSpecialMove ( ) )
{
SetTimer ( 0.25 f , false , nameOf ( Timer _SearchForFleeObstructions ) ) ;
return ;
}
// See if there's someone blocking us
ObstructingEnemy = CheckForEnemiesInFOV ( AttackRange * 1.1 f , 0.5 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 ;
// 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
MyHansPawn . SetSprinting ( true ) ;
SetEnemyMoveGoal ( self , true ) ;
EnableMeleeRangeEventProbing ( ) ;
// Restart default command
BeginCombatCommand ( GetDefaultCommand ( ) , "Restarting default command" ) ;
// Give hans a little bit of time to flee after this attack
SetTimer ( 2.0 f , false , nameOf ( Timer _SearchForFleeObstructions ) ) ;
}
else
{
SetTimer ( 0.25 f , false , nameOf ( Timer _SearchForFleeObstructions ) ) ;
}
}
/** 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 ( MyHansPawn . IsDoingSpecialMove ( ) )
{
MyHansPawn . EndSpecialMove ( ) ;
}
// Delay flee by a tiny bit to allow command to finish up
SetTimer ( 0.06 f , false , nameOf ( Flee ) , self ) ;
}
}
/** We have finished fleeing for one reason or another, notify pawn to heal */
function NotifyFleeFinished ( optional bool bAcquireNewEnemy = true )
{
local KFAISpawnManager SpawnManager ;
// Don't do additional cleanup if this wasn't a hunt and heal flee
if ( ! MyHansPawn . bInHuntAndHealMode )
{
return ;
}
// Restore flee flags to default
bFleeing = false ;
bWantsToFlee = false ;
// Stop searching for targets
ClearTimer ( nameOf ( Timer _SearchForFleeObstructions ) ) ;
// Stop summoning minions
SpawnManager = KFGameInfo ( WorldInfo . Game ) . SpawnManager ;
if ( SpawnManager != none )
{
SpawnManager . StopSummoningBossMinions ( ) ;
}
// Prevent infinite fleeing
bHasFledThisPhase = true ;
// Find a new enemy and move to them
if ( bAcquireNewEnemy )
{
ChangeEnemy ( GetClosestEnemy ( ) , true ) ;
}
// Allow melee again
EnableMeleeRangeEventProbing ( ) ;
// Restart default command
BeginCombatCommand ( GetDefaultCommand ( ) , "Restarting default command" ) ;
}
/** Aborts the flee (shield was destroyed, etc) */
function CancelFlee ( optional bool bAcquireNewEnemy = true )
{
if ( bFleeing )
{
bFleeing = false ;
bWantsToFlee = false ;
// Kill our flee command
AbortCommand ( FindCommandOfClass ( class 'AICommand_Flee' ) ) ;
// End flee as normal
NotifyFleeFinished ( bAcquireNewEnemy ) ;
}
else
{
// Make sure hans doesn't try to flee again
bWantsToFlee = false ;
bHasFledThisPhase = true ;
}
}
/ * *
* Hans 's prefers using health drain (grab) on players when he' s wounded . Currently he
* won 't attempt this attack if he' s already at full health
* /
event bool CanGrabAttack ( )
{
local KFPawn KFPawnEnemy ;
local float DistSq ;
local vector HitLocation , HitNormal ;
local Actor HitActor ;
/** Don't try to grab while still fleeing */
if ( bFleeing || bWantsToFlee )
{
return false ;
}
/** Hans shouldn't do this attack if at full health */
if ( Enemy == none || MyKFPawn == none || MyKFPawn . Health <= 0 || GetHealthPercentage ( ) >= 1. f )
{
return false ;
}
// Don't grab players if you aren't in hunt and heal mode or are waiting for the smoke grenade barrage
if ( MyHansPawn != none && ( ! MyHansPawn . bInHuntAndHealMode || MyHansPawn . bPendingSmokeGrenadeBarrage ) )
{
return false ;
}
// 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 . IsDoingSpecialMove ( SM _GrappleVictim )
&& VSizeSq ( MyHansPawn . Location - Enemy . Location ) < Square ( MinDistanceToPerformGrabAttack * 1.5 f ) )
{
// End the move immediately so other zed stops grabbing our target!
KFPawnEnemy . InteractionPawn . EndSpecialMove ( ) ;
}
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 . IsImpaired ( ) || MyKFPawn . IsIncapacitated ( ) )
{
return false ;
}
if ( 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 ) > Pawn . CylinderComponent . CollisionHeight )
{
return false ;
}
DistSq = VSizeSq ( Enemy . Location - Pawn . Location ) ;
if ( DistSq > Square ( MinDistanceToPerformGrabAttack ) )
{
return false ;
}
// Do the same kind of trace we do in KFSM_GrappleStart
HitActor = Trace ( HitLocation , HitNormal , Enemy . Location , Pawn . Location , true ) ;
if ( HitActor != None && HitActor != Enemy )
{
return false ;
}
if ( ! CanTargetBeGrabbed ( KFPawn ( Enemy ) ) )
{
return false ;
}
/** Makes Zed have high desire to grab as initial attack */
if ( ! MyKFPawn . IsDoingMeleeAttack ( ) )
{
return true ;
}
}
` AILog( GetFuncName() $ "() returning FALSE", 'GrabAttack' );
return false ;
}
function bool CanTargetBeGrabbed ( KFPawn TargetKFP )
{
local KFAIController OtherKFAIC ;
// Disallow if target is invalid, already being grabbed, or currently falling
if ( TargetKFP == none || ( TargetKFP . Health <= 0 ) || ( TargetKFP . IsDoingSpecialMove ( SM _GrappleVictim ) ) || TargetKFP . Physics == PHYS _Falling )
{
return false ;
}
// Need to check that no other clots are performing initial grab move on player since SM_GRAB is run without the player in a special move
foreach WorldInfo . AllControllers ( class 'KFAIController' , OtherKFAIC )
{
if ( OtherKFAIC == self )
{
continue ;
}
// If the the other Zed is doing a grab start or grab attack special move, and TargetKFP is also his enemy, disallow
// this grab. Already checked if player is doing a grapplevictim special move above, but the player isn't executing
// the victim special move until the other Zed's initial grab anim is complete (see KFSM_Clot_Grab - this is the move that determines
// whether the clot's grab should miss or succeed based on when the initial sequence is interrupted)... so instead check
// whether the starting-to-grab Zed has the same enemy.
// The grab special move assumes that the grab victim is also the grabber's enemy.
if ( OtherKFAIC . MyKFPawn != none && OtherKFAIC . Enemy == TargetKFP && OtherKFAIC . IsDoingGrabSpecialMove ( ) )
{
if ( VSizeSq ( MyHansPawn . Location - TargetKFP . Location ) < 250000 ) // 5 Meters
{
// End the move immediately so other zed stops grabbing our target!
OtherKFAIC . MyKFPawn . EndSpecialMove ( ) ;
}
}
}
return true ;
}
/** Launch a grab attack */
event DoGrabAttack ( optional Pawn NewEnemy , optional float InPostSpecialMoveSleepTime = 0. f )
{
if ( CommandList == None || AICommand ( CommandList ) . bAllowedToAttack )
{
if ( NewEnemy != None && NewEnemy != Enemy )
{
ChangeEnemy ( NewEnemy , false ) ;
}
/** Abort qany movement commands */
ClearMovementInfo ( ) ;
` AILog( GetFuncName() $ "() Init AICommand_Attack_Grab", 'InitAICommand' );
class 'AICommand_Attack_Grab' . static . Grab ( self , InPostSpecialMoveSleepTime ) ;
}
else if ( CommandList != none && ! AICommand ( CommandList ) . bAllowedToAttack )
{
` AILog( GetFuncName() $ "() not doing grab attack because current command (" $ CommandList $ ") will not allow it", 'GrabAttack' );
}
}
/** 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 ;
}
BumpedMonster = KFPawn _Monster ( Other ) ;
if ( BumpedMonster == none || ! BumpedMonster . IsAliveAndWell ( ) || BumpedMonster . ZedBumpDamageScale <= 0 )
{
return false ;
}
if ( MyKFPawn == none || ! MyKFPawn . IsAliveAndWell ( ) )
{
return false ;
}
// Hans knocks guys out of the way always if he is in hunt and heal
if ( ( MyHansPawn . bInHuntAndHealMode && ! IsZero ( MyKFPawn . Velocity ) ) || ( MyKFPawn . 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 ;
}
function bool CanDoStrike ( )
{
local bool bInGrabRange ;
local float DistSq ;
if ( ! bFleeing && ! bWantsToFlee )
{
/** See if we want to grab an enemy, and they are in grab range */
if ( Enemy != none && MyHansPawn != none && MyHansPawn . bInHuntAndHealMode &&
MyKFPawn != none && MyKFPawn . Health > 0 )
{
DistSq = VSizeSq ( Enemy . Location - Pawn . Location ) ;
if ( DistSq < Square ( MinDistanceToPerformGrabAttack ) )
{
bInGrabRange = true ;
}
}
// Never do melee strikes when in bInHuntAndHealMode unless we can't grab attack, and the enemy is in grab range
if ( MyHansPawn == none || ( MyHansPawn . bInHuntAndHealMode && ( ! bInGrabRange || CanGrabAttack ( ) ) ) )
{
return false ;
}
}
return super . CanDoStrike ( ) ;
}
event EdgeAndPolySubRegionRejectedDueToDist ( vector EdgeCenterRejected , ENavmeshEdgeType EdgeTypeRecjted , Color EdgeTypeColorRejected , vector SubRegionLocation , vector PolyCenterRejected , float Dist )
{
local KFDebugLines KFDL ;
if ( class 'KFGameEngine' . default . bEnableAdvDebugLines )
{
//
KFDL = class 'KFDebugLines' . static . GetDebugLines ( ) ;
//
KFDL . AddDebugSphere ( SubRegionLocation , 32 , 8 , 255 , 0 , 0 , true ) ;
KFDL . AddDebugText3D ( SubRegionLocation + vect ( 0 , 0 , 1 ) * 72. f , "FAILED DUE TO Dist:" $Dist , true , 100 , 100 , 0 , true ) ;
}
}
event EdgeAndPolySubRegionRejectedDueToRating ( vector EdgeCenterRejected , ENavmeshEdgeType EdgeTypeRecjted , Color EdgeTypeColorRejected , vector SubRegionLocation , vector PolyCenterRejected , int Rating )
{
local KFDebugLines KFDL ;
//
if ( class 'KFGameEngine' . default . bEnableAdvDebugLines )
{
KFDL = class 'KFDebugLines' . static . GetDebugLines ( ) ;
//
KFDL . AddDebugSphere ( SubRegionLocation , 32 , 8 , 255 , 255 , 0 , true ) ;
KFDL . AddDebugText3D ( SubRegionLocation + vect ( 0 , 0 , 1 ) * 72. f , "FAILED DUE TO RATING:" $Rating , true , 100 , 200 , 0 , true ) ;
}
}
event EntireEdgeAndPolyRejectedDueToRating ( vector EdgeCenterRejected , ENavmeshEdgeType EdgeTypeRecjted , Color EdgeTypeColorRejected , vector PolyCenterRejected , int Rating )
{
local KFDebugLines KFDL ;
local vector centerOfLineFromEdgeToPolyCenter ;
if ( class 'KFGameEngine' . default . bEnableAdvDebugLines )
{
centerOfLineFromEdgeToPolyCenter = 0.5 * ( PolyCenterRejected - EdgeCenterRejected ) ;
//
KFDL = class 'KFDebugLines' . static . GetDebugLines ( ) ;
//
//KFDL.AddDebugSphere( PossibleGoal.Location, 32, 8, 255, 255, 0, true );
KFDL . AddDebugLine ( EdgeCenterRejected , PolyCenterRejected , 255 , 0 , 0 , true ) ;
KFDL . AddDebugText3D ( EdgeCenterRejected + centerOfLineFromEdgeToPolyCenter + vect ( 0 , 0 , 1 ) * 72. f , "Entire Edge FAILED DUE TO RATING:" $Rating , true , 100 , 200 , 0 , true ) ;
}
}
event EdgeAndPolySubRegionRejectedDueToLOS ( vector EdgeCenterRejected , ENavmeshEdgeType EdgeTypeRecjted , Color EdgeTypeColorRejected , vector SubRegionLocation , vector PolyCenterRejected , vector HitLocation , Actor HitActor )
{
local KFDebugLines KFDL ;
if ( class 'KFGameEngine' . default . bEnableAdvDebugLines )
{
//
KFDL = class 'KFDebugLines' . static . GetDebugLines ( ) ;
//
KFDL . AddDebugSphere ( SubRegionLocation , 32 , 8 , 255 , 0 , 0 , true ) ;
KFDL . AddDebugText3D ( SubRegionLocation + vect ( 0 , 0 , 1 ) * 72. f , "FAILED DUE TO LOS" , true , 100 , 100 , 0 , true ) ;
}
}
event EdgeAndPolySubRegionRejectedDueToAdjustToss ( vector EdgeCenterRejected , ENavmeshEdgeType EdgeTypeRecjted , Color EdgeTypeColorRejected , vector SubRegionLocation , vector PolyCenterRejected , vector HitLocation )
{
local KFDebugLines KFDL ;
if ( class 'KFGameEngine' . default . bEnableAdvDebugLines )
{
//
KFDL = class 'KFDebugLines' . static . GetDebugLines ( ) ;
//
KFDL . AddDebugSphere ( SubRegionLocation , 32 , 8 , 255 , 0 , 0 , true ) ;
KFDL . AddDebugText3D ( SubRegionLocation + vect ( 0 , 0 , 1 ) * 72. f , "FAILED DUE TO AdjustToss" , true , 100 , 100 , 0 , true ) ;
}
}
event EdgeAndPolySubRegionRejectedDueToProximityToTarget ( vector EdgeCenterRejected , ENavmeshEdgeType EdgeTypeRecjted , Color EdgeTypeColorRejected , vector SubRegionLocation , vector PolyCenterRejected , float Range , Actor Target )
{
}
` if( ` notdefined ( ShippingPC ) )
/** Debug command to advance battle phase */
function DebugNextPhase ( )
{
MyHansPawn . SetHuntAndHealMode ( true ) ;
MyHansPawn . Health = MyHansPawn . HealthMax * 0.34 ;
}
` endif
/ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* Pathfinding
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * /
function bool AmIAllowedToSuicideWhenStuck ( )
{
return false ;
}
function InitalizeBaseCommand ( class < AICommand > CmdClass )
{
CmdClass . static . InitCommand ( self ) ;
}
/ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* Dialog
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * /
function NotifyKilled ( Controller Killer , Controller Killed , pawn KilledPawn , class < DamageType > damageType )
{
if ( self == Killer )
{
` DialogManager.PlayHansKilledDialog( MyHansPawn, damageType );
}
}
function PlayDamagePlayerDialog ( class < DamageType > DmgType )
{
` DialogManager.PlayHansDamagePlayerDialog( MyHansPawn, DmgType );
}
DefaultProperties
{
Steering = none
DefaultCommandClass = class 'KFGame.AICommand_Base_Hans'
MeleeCommandClass = class 'KFGame.AICommand_Base_Hans'
MinDistanceToPerformGrabAttack = 350. f
MinTimeBetweenGrabAttacks = 2.5 f
bRepathOnInvalidStrike = true
//DefaultBehavior="Hans_BT"
//UsedETQQueries[ENQ_EnemySelection]="BaseZedEnemySelection"
AggroEnemySwitchWaitTime = 7.0 f //7
RetargetWaitTimeRange = ( X = 4.4 , Y = 5.0 ) //(X=5.0, Y=7.0) (X=5.0, Y=8.0)feels good but he finishs people off to much
bCanDoHeavyBump = true
LostSightSprintDelay = 0.5
StartShootingRange = 4000
MinShootingRange = 300
LostSightStopFireDelay = 1.25
BurstWaitTime = 0.5
MinBurstAmount = 3
MaxBurstAmount = 8
BarrageLength = 3.0
BarrageWaitTime = 1.5
GrenadeGunInterruptDelay = 5.0
BarrageWaitDistanceCurve = ( Points = ( ( InVal = 0. f , OutVal = 1.25 f ) , ( InVal = 2500.0 f , OutVal = 1. f ) ) )
BurstWaitDistanceCurve = ( Points = ( ( InVal = 0. f , OutVal = 4. f ) , ( InVal = 2500.0 f , OutVal = 1. f ) ) )
TargetAquisitionDelay = 0.25
DrawGunFireDelay = 1.0
StanceChangeCooldown = 0.3
PostAttackMoveGunCooldown = 0.3
CurrentNadeAttackType = HNT _None
GrenadeAttackEvalInterval = 0.1
LastRecentSeenEnemyListUpdateTime = 0.1
MinFleeDuration = 5. f //10 //3 //2
MaxFleeDuration = 6. f //15 //5 //3
MaxFleeDistance = 3000. f //10000. //5000
TeleportCooldown = 15.0
HiddenRelocateTeleportThreshold = 12.0
// ---------------------------------------------
// Danger Evasion Settings
DangerEvadeSettings . Empty
DangerEvadeSettings ( 0 ) = { ( ClassName = "KFProj_Bullet_Pellet" ,
Cooldowns = ( 4.0 , 3.0 , 2.0 , 1.0 ) , // Normal, Hard, Suicidal, HoE
EvadeChances = ( 0.1 , 0.3 , 0.85 , 0.95 ) , //0.1, 0.3, 0.5, 0.6
ReactionDelayRanges = { ( ( X = 0.0 , Y = 0.2 ) , ( X = 0.0 , Y = 0.0 ) , ( X = 0.0 , Y = 0.15 ) , ( X = 0.0 , Y = 0.00 ) ) } ,
SoloChanceMultiplier = 1.0 ) }
DangerEvadeSettings ( 1 ) = { ( ClassName = "KFProj_Nail_Nailgun" ,
Cooldowns = ( 4.0 , 3.0 , 2.0 , 1.0 ) , // Normal, Hard, Suicidal, HoE
EvadeChances = ( 0.1 , 0.3 , 0.85 , 0.95 ) ,
ReactionDelayRanges = { ( ( X = 0.0 , Y = 0.2 ) , ( X = 0.0 , Y = 0.0 ) , ( X = 0.0 , Y = 0.15 ) , ( X = 0.0 , Y = 0.00 ) ) } ,
SoloChanceMultiplier = 1.0 ) }
DangerEvadeSettings ( 2 ) = { ( ClassName = "KFProj_Bullet_DragonsBreath" ,
Cooldowns = ( 4.0 , 3.0 , 2.0 , 1.0 ) , // Normal, Hard, Suicidal, HoE
EvadeChances = ( 0.1 , 0.3 , 0.85 , 0.95 ) ,
ReactionDelayRanges = { ( ( X = 0.0 , Y = 0.2 ) , ( X = 0.0 , Y = 0.0 ) , ( X = 0.0 , Y = 0.15 ) , ( X = 0.0 , Y = 0.00 ) ) } ,
SoloChanceMultiplier = 1.0 ) }
DangerEvadeSettings ( 3 ) = { ( ClassName = "KFProj_HighExplosive_M79" ,
Cooldowns = ( 4.0 , 3.0 , 2.0 , 1.0 ) , // Normal, Hard, Suicidal, HoE
EvadeChances = ( 0.1 , 0.3 , 0.5 , 0.6 ) ,
ForcedEvadeChances = { ( ( FL = 0.0 , FR = 0.0 ) , ( FL = 0.5 , FR = 0.5 ) , ( FL = 0.5 , FR = 0.5 ) , ( FL = 0.5 , FR = 0.5 ) ) } ,
ReactionDelayRanges = { ( ( X = 0.0 , Y = 0.2 ) , ( X = 0.0 , Y = 0.0 ) , ( X = 0.0 , Y = 0.15 ) , ( X = 0.0 , Y = 0.05 ) ) } ,
SoloChanceMultiplier = 1.0 ) }
DangerEvadeSettings ( 4 ) = { ( ClassName = "KFProj_HighExplosive_M32" ,
Cooldowns = ( 4.0 , 3.0 , 2.0 , 1.0 ) , // Normal, Hard, Suicidal, HoE
EvadeChances = ( 0.1 , 0.3 , 0.5 , 0.6 ) ,
ForcedEvadeChances = { ( ( FL = 0.0 , FR = 0.0 ) , ( FL = 0.5 , FR = 0.5 ) , ( FL = 0.5 , FR = 0.5 ) , ( FL = 0.5 , FR = 0.5 ) ) } ,
ReactionDelayRanges = { ( ( X = 0.0 , Y = 0.2 ) , ( X = 0.0 , Y = 0.0 ) , ( X = 0.0 , Y = 0.15 ) , ( X = 0.0 , Y = 0.05 ) ) } ,
SoloChanceMultiplier = 1.0 ) }
DangerEvadeSettings ( 5 ) = { ( ClassName = "KFProj_Rocket_RPG7" ,
Cooldowns = ( 4.0 , 3.0 , 2.0 , 1.0 ) , // Normal, Hard, Suicidal, HoE
EvadeChances = ( 0.1 , 0.3 , 0.5 , 0.6 ) ,
ForcedEvadeChances = { ( ( FL = 0.0 , FR = 0.0 ) , ( FL = 0.5 , FR = 0.5 ) , ( FL = 0.5 , FR = 0.5 ) , ( FL = 0.5 , FR = 0.5 ) ) } ,
ReactionDelayRanges = { ( ( X = 0.0 , Y = 0.2 ) , ( X = 0.0 , Y = 0.0 ) , ( X = 0.0 , Y = 0.15 ) , ( X = 0.0 , Y = 0.05 ) ) } ,
SoloChanceMultiplier = 1.0 ) }
DangerEvadeSettings ( 6 ) = { ( ClassName = "KFDT_Explosive_M16M203" ,
Cooldowns = ( 4.0 , 3.0 , 2.0 , 1.0 ) , // Normal, Hard, Suicidal, HoE
EvadeChances = ( 0.1 , 0.3 , 0.5 , 0.6 ) ,
ForcedEvadeChances = { ( ( FL = 0.0 , FR = 0.0 ) , ( FL = 0.5 , FR = 0.5 ) , ( FL = 0.5 , FR = 0.5 ) , ( FL = 0.5 , FR = 0.5 ) ) } ,
ReactionDelayRanges = { ( ( X = 0.0 , Y = 0.2 ) , ( X = 0.0 , Y = 0.0 ) , ( X = 0.0 , Y = 0.15 ) , ( X = 0.0 , Y = 0.05 ) ) } ,
SoloChanceMultiplier = 1.0 ) }
DangerEvadeSettings ( 7 ) = { ( ClassName = "KFDT_Explosive_HRGIncendiaryRifle" ,
Cooldowns = ( 4.0 , 3.0 , 2.0 , 1.0 ) , // Normal, Hard, Suicidal, HoE
EvadeChances = ( 0.1 , 0.3 , 0.5 , 0.6 ) ,
ForcedEvadeChances = { ( ( FL = 0.0 , FR = 0.0 ) , ( FL = 0.5 , FR = 0.5 ) , ( FL = 0.5 , FR = 0.5 ) , ( FL = 0.5 , FR = 0.5 ) ) } ,
ReactionDelayRanges = { ( ( X = 0.0 , Y = 0.2 ) , ( X = 0.0 , Y = 0.0 ) , ( X = 0.0 , Y = 0.15 ) , ( X = 0.0 , Y = 0.05 ) ) } ,
SoloChanceMultiplier = 1.0 ) }
//shooting fire
DangerEvadeSettings ( 8 ) = { ( ClassName = "KFProj_CaulkNBurn_GroundFire" ,
Cooldowns = ( 3.0 , 3.0 , 2.5 , 1.5 ) , // Normal, Hard, Suicidal, HoE
EvadeChances = ( 0.0 , 0.3 , 0.5 , 0.8 ) ,
ReactionDelayRanges = { ( ( X = 0.0 , Y = 0.2 ) , ( X = 0.0 , Y = 0.0 ) , ( X = 0.0 , Y = 0.15 ) , ( X = 0.0 , Y = 0.00 ) ) } ,
SoloChanceMultiplier = 1.0 ) }
DangerEvadeSettings ( 9 ) = { ( ClassName = "KFProj_FlameThrower_GroundFire" ,
Cooldowns = ( 3.0 , 3.0 , 2.5 , 1.5 ) , // Normal, Hard, Suicidal, HoE
EvadeChances = ( 0.0 , 0.3 , 0.5 , 0.8 ) ,
ReactionDelayRanges = { ( ( X = 0.0 , Y = 0.2 ) , ( X = 0.0 , Y = 0.0 ) , ( X = 0.0 , Y = 0.15 ) , ( X = 0.0 , Y = 0.00 ) ) } ,
SoloChanceMultiplier = 1.0 ) }
DangerEvadeSettings ( 10 ) = { ( ClassName = "KFWeap_Flame_CaulkBurn" ,
Cooldowns = ( 3.0 , 3.0 , 2.5 , 1.5 ) , // Normal, Hard, Suicidal, HoE
EvadeChances = ( 0.0 , 0.3 , 0.5 , 0.8 ) ,
ReactionDelayRanges = { ( ( X = 0.0 , Y = 0.2 ) , ( X = 0.0 , Y = 0.0 ) , ( X = 0.0 , Y = 0.15 ) , ( X = 0.0 , Y = 0.00 ) ) } ,
SoloChanceMultiplier = 1.0 ) }
DangerEvadeSettings ( 11 ) = { ( ClassName = "KFWeap_Flame_Flamethrower" ,
Cooldowns = ( 3.0 , 3.0 , 2.5 , 1.5 ) , // Normal, Hard, Suicidal, HoE
EvadeChances = ( 0.1 , 0.3 , 0.5 , 0.8 ) ,
ReactionDelayRanges = { ( ( X = 0.0 , Y = 0.2 ) , ( X = 0.0 , Y = 0.0 ) , ( X = 0.0 , Y = 0.15 ) , ( X = 0.0 , Y = 0.00 ) ) } ,
SoloChanceMultiplier = 1.0 ) }
DangerEvadeSettings ( 12 ) = { ( ClassName = "KFWeap_Beam_Microwave" ,
Cooldowns = ( 3.0 , 3.0 , 2.5 , 1.5 ) , // Normal, Hard, Suicidal, HoE
EvadeChances = ( 0.1 , 0.3 , 0.5 , 0.8 ) ,
ReactionDelayRanges = { ( ( X = 0.0 , Y = 0.2 ) , ( X = 0.0 , Y = 0.0 ) , ( X = 0.0 , Y = 0.15 ) , ( X = 0.0 , Y = 0.00 ) ) } ,
SoloChanceMultiplier = 1.0 ) }
DangerEvadeSettings ( 13 ) = { ( ClassName = "KFProj_Flame_HRGIncendiaryRifle" ,
Cooldowns = ( 3.0 , 3.0 , 2.5 , 1.5 ) , // Normal, Hard, Suicidal, HoE
EvadeChances = ( 0.0 , 0.3 , 0.5 , 0.8 ) ,
ReactionDelayRanges = { ( ( X = 0.0 , Y = 0.2 ) , ( X = 0.0 , Y = 0.0 ) , ( X = 0.0 , Y = 0.15 ) , ( X = 0.0 , Y = 0.00 ) ) } ,
SoloChanceMultiplier = 1.0 ) }
//Aimed weapons it dodges //sharpshooter
DangerEvadeSettings ( 14 ) = { ( ClassName = "KFWeap_Bow_Crossbow" ,
Cooldowns = ( 2.3 , 2.3 , 2.3 , 1.3 ) , // Normal, Hard, Suicidal, HoE
EvadeChances = ( 0.08 , 0.1 , 0.2 , 0.35 ) ,
ForcedEvadeChances = { ( ( FL = 0.5 , FR = 0.5 ) , ( FL = 0.5 , FR = 0.5 ) , ( FL = 0.5 , FR = 0.5 ) , ( FL = 0.5 , FR = 0.5 ) ) } ,
ReactionDelayRanges = { ( ( X = 0.0 , Y = 0.15 ) , ( X = 0.0 , Y = 0.15 ) , ( X = 0.0 , Y = 0.15 ) , ( X = 0.0 , Y = 0.15 ) ) } ,
SoloChanceMultiplier = 1.0 ) }
DangerEvadeSettings ( 15 ) = { ( ClassName = "KFWeap_Bow_CompoundBow" ,
Cooldowns = ( 2.3 , 2.3 , 2.3 , 1.3 ) , // Normal, Hard, Suicidal, HoE
EvadeChances = ( 0.08 , 0.1 , 0.2 , 0.35 ) ,
ForcedEvadeChances = { ( ( FL = 0.5 , FR = 0.5 ) , ( FL = 0.5 , FR = 0.5 ) , ( FL = 0.5 , FR = 0.5 ) , ( FL = 0.5 , FR = 0.5 ) ) } ,
ReactionDelayRanges = { ( ( X = 0.0 , Y = 0.15 ) , ( X = 0.0 , Y = 0.15 ) , ( X = 0.0 , Y = 0.15 ) , ( X = 0.0 , Y = 0.15 ) ) } ,
SoloChanceMultiplier = 1.0 ) }
DangerEvadeSettings ( 16 ) = { ( ClassName = "KFWeap_Rifle_M14EBR" ,
Cooldowns = ( 2.3 , 2.3 , 2.3 , 1.3 ) , // Normal, Hard, Suicidal, HoE
EvadeChances = ( 0.08 , 0.1 , 0.2 , 0.35 ) ,
ForcedEvadeChances = { ( ( FL = 0.5 , FR = 0.5 ) , ( FL = 0.5 , FR = 0.5 ) , ( FL = 0.5 , FR = 0.5 ) , ( FL = 0.5 , FR = 0.5 ) ) } ,
ReactionDelayRanges = { ( ( X = 0.0 , Y = 0.15 ) , ( X = 0.0 , Y = 0.15 ) , ( X = 0.0 , Y = 0.15 ) , ( X = 0.0 , Y = 0.15 ) ) } ,
SoloChanceMultiplier = 1.0 ) }
DangerEvadeSettings ( 17 ) = { ( ClassName = "KFWeap_Rifle_Winchester1894" ,
Cooldowns = ( 2.3 , 2.3 , 2.3 , 1.3 ) , // Normal, Hard, Suicidal, HoE
EvadeChances = ( 0.08 , 0.1 , 0.2 , 0.35 ) ,
ForcedEvadeChances = { ( ( FL = 0.5 , FR = 0.5 ) , ( FL = 0.5 , FR = 0.5 ) , ( FL = 0.5 , FR = 0.5 ) , ( FL = 0.5 , FR = 0.5 ) ) } ,
ReactionDelayRanges = { ( ( X = 0.0 , Y = 0.15 ) , ( X = 0.0 , Y = 0.15 ) , ( X = 0.0 , Y = 0.15 ) , ( X = 0.0 , Y = 0.15 ) ) } ,
SoloChanceMultiplier = 1.0 ) }
DangerEvadeSettings ( 18 ) = { ( ClassName = "KFWeap_Rifle_RailGun" ,
Cooldowns = ( 2.3 , 2.3 , 2.3 , 1.3 ) , // Normal, Hard, Suicidal, HoE
EvadeChances = ( 0.08 , 0.1 , 0.2 , 0.35 ) ,
ForcedEvadeChances = { ( ( FL = 0.5 , FR = 0.5 ) , ( FL = 0.5 , FR = 0.5 ) , ( FL = 0.5 , FR = 0.5 ) , ( FL = 0.5 , FR = 0.5 ) ) } ,
ReactionDelayRanges = { ( ( X = 0.0 , Y = 0.15 ) , ( X = 0.0 , Y = 0.15 ) , ( X = 0.0 , Y = 0.15 ) , ( X = 0.0 , Y = 0.15 ) ) } ,
SoloChanceMultiplier = 1.0 ) }
//Grenades
DangerEvadeSettings ( 19 ) = { ( ClassName = "KFProj_FragGrenade" ,
Cooldowns = ( 6.0 , 5.0 , 3.0 , 2.5 ) , // Normal, Hard, Suicidal, HoE
EvadeChances = ( 0.1 , 0.3 , 0.5 , 0.6 ) ,
ForcedEvadeChances = { ( ( FL = 0.0 , FR = 0.0 ) , ( FL = 0.5 , FR = 0.5 ) , ( FL = 0.5 , FR = 0.5 ) , ( FL = 0.5 , FR = 0.5 ) ) } ,
ReactionDelayRanges = { ( ( X = 0.0 , Y = 0.2 ) , ( X = 0.0 , Y = 0.0 ) , ( X = 0.0 , Y = 0.15 ) , ( X = 0.0 , Y = 0.05 ) ) } ,
SoloChanceMultiplier = 1.0 ) }
DangerEvadeSettings ( 20 ) = { ( ClassName = "KFProj_MolotovGrenade" ,
Cooldowns = ( 6.0 , 5.0 , 3.0 , 2.5 ) , // Normal, Hard, Suicidal, HoE
EvadeChances = ( 0.1 , 0.3 , 0.5 , 0.6 ) ,
ForcedEvadeChances = { ( ( FL = 0.0 , FR = 0.0 ) , ( FL = 0.5 , FR = 0.5 ) , ( FL = 0.5 , FR = 0.5 ) , ( FL = 0.5 , FR = 0.5 ) ) } ,
ReactionDelayRanges = { ( ( X = 0.0 , Y = 0.2 ) , ( X = 0.0 , Y = 0.0 ) , ( X = 0.0 , Y = 0.15 ) , ( X = 0.0 , Y = 0.05 ) ) } ,
SoloChanceMultiplier = 1.0 ) }
DangerEvadeSettings ( 21 ) = { ( ClassName = "KFProj_DynamiteGrenade" ,
Cooldowns = ( 6.0 , 5.0 , 3.0 , 2.5 ) , // Normal, Hard, Suicidal, HoE
EvadeChances = ( 0.1 , 0.3 , 0.5 , 0.6 ) ,
ForcedEvadeChances = { ( ( FL = 0.0 , FR = 0.0 ) , ( FL = 0.5 , FR = 0.5 ) , ( FL = 0.5 , FR = 0.5 ) , ( FL = 0.5 , FR = 0.5 ) ) } ,
ReactionDelayRanges = { ( ( X = 0.0 , Y = 0.2 ) , ( X = 0.0 , Y = 0.0 ) , ( X = 0.0 , Y = 0.15 ) , ( X = 0.0 , Y = 0.05 ) ) } ,
SoloChanceMultiplier = 1.0 ) }
DangerEvadeSettings ( 22 ) = { ( ClassName = "KFProj_NailBombGrenade" ,
Cooldowns = ( 6.0 , 5.0 , 3.0 , 2.5 ) , // Normal, Hard, Suicidal, HoE
EvadeChances = ( 0.1 , 0.3 , 0.5 , 0.6 ) ,
ForcedEvadeChances = { ( ( FL = 0.0 , FR = 0.0 ) , ( FL = 0.5 , FR = 0.5 ) , ( FL = 0.5 , FR = 0.5 ) , ( FL = 0.5 , FR = 0.5 ) ) } ,
ReactionDelayRanges = { ( ( X = 0.0 , Y = 0.2 ) , ( X = 0.0 , Y = 0.0 ) , ( X = 0.0 , Y = 0.15 ) , ( X = 0.0 , Y = 0.05 ) ) } ,
SoloChanceMultiplier = 1.0 ) }
DangerEvadeSettings ( 23 ) = { ( ClassName = "KFProj_HEGrenade" ,
Cooldowns = ( 6.0 , 5.0 , 3.0 , 2.5 ) , // Normal, Hard, Suicidal, HoE
EvadeChances = ( 0.1 , 0.3 , 0.5 , 0.6 ) ,
ForcedEvadeChances = { ( ( FL = 0.0 , FR = 0.0 ) , ( FL = 0.5 , FR = 0.5 ) , ( FL = 0.5 , FR = 0.5 ) , ( FL = 0.5 , FR = 0.5 ) ) } ,
ReactionDelayRanges = { ( ( X = 0.0 , Y = 0.2 ) , ( X = 0.0 , Y = 0.0 ) , ( X = 0.0 , Y = 0.15 ) , ( X = 0.0 , Y = 0.05 ) ) } ,
SoloChanceMultiplier = 1.0 ) }
//Aim Blocks
DangerEvadeSettings ( 24 ) = { ( ClassName = "KFWeap_Rifle_Winchester1894" ,
Cooldowns = ( 0.5 , 0.4 , 0.3 , 0.2 ) , // Normal, Hard, Suicidal, HoE
BlockChances = ( 0.1 , 0.2 , 0.7 , 0.85 ) ) }
DangerEvadeSettings ( 25 ) = { ( ClassName = "KFWeap_Bow_Crossbow" ,
Cooldowns = ( 0.5 , 0.4 , 0.3 , 0.2 ) , // Normal, Hard, Suicidal, HoE
BlockChances = ( 0.1 , 0.2 , 0.7 , 0.85 ) ) }
DangerEvadeSettings ( 26 ) = { ( ClassName = "KFWeap_Rifle_M14EBR" ,
Cooldowns = ( 0.5 , 0.4 , 0.3 , 0.2 ) , // Normal, Hard, Suicidal, HoE
BlockChances = ( 0.1 , 0.2 , 0.7 , 0.85 ) ) }
DangerEvadeSettings ( 27 ) = { ( ClassName = "KFWeap_Rifle_RailGun" ,
Cooldowns = ( 0.5 , 0.4 , 0.3 , 0.2 ) , // Normal, Hard, Suicidal, HoE
BlockChances = ( 0.1 , 0.2 , 0.7 , 0.85 ) ) }
DangerEvadeSettings ( 28 ) = { ( ClassName = "KFWeap_Bow_CompoundBow" ,
Cooldowns = ( 0.5 , 0.4 , 0.3 , 0.2 ) , // Normal, Hard, Suicidal, HoE
BlockChances = ( 0.1 , 0.2 , 0.7 , 0.85 ) ) }
}