//============================================================================= // 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 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 ); } /** Clean up all internal objects and references when the AI is destroyed 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, 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.18f ) //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 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 /*EyeLocation = MyKFPawn.Location + vect(0,0,1) * MyKFPawn.BaseEyeHeight; 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.7f, Canvas.SizeY * 0.25f); 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, optional bool bFromFear=false, optional bool bUseRandomDirection=false ) { if( !bFromFear || !MyHansPawn.bInHuntAndHealMode ) { super.DoFleeFrom( FleeFrom, FleeDuration, FleeDistance, bShouldStopAtGoal, bFromFear, bUseRandomDirection ); } } /** 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.2f, 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.25f), true ); EvaluateSprinting(); // Constantly make sure we don't have a player trying to block us if( !IsTimerActive(nameOf(Timer_SearchForFleeObstructions)) ) { SetTimer( 1.5f, 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.25f, false, nameOf(Timer_SearchForFleeObstructions) ); return; } // See if there's someone blocking us ObstructingEnemy = CheckForEnemiesInFOV( AttackRange * 1.1f, 0.5f, 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.0f, false, nameOf(Timer_SearchForFleeObstructions) ); } else { SetTimer( 0.25f, 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.06f, 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.5f) ) { // 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 CmdClass ) { CmdClass.static.InitCommand( self ); } /********************************************************************************************* * Dialog ********************************************************************************************* */ function NotifyKilled(Controller Killer, Controller Killed, pawn KilledPawn, class damageType) { if( self == Killer ) { `DialogManager.PlayHansKilledDialog( MyHansPawn, damageType ); } } function PlayDamagePlayerDialog( class 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.5f bRepathOnInvalidStrike=true //DefaultBehavior="Hans_BT" //UsedETQQueries[ENQ_EnemySelection]="BaseZedEnemySelection" AggroEnemySwitchWaitTime=7.0f //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.25f),(InVal=2500.0f, OutVal=1.f))) BurstWaitDistanceCurve=(Points=((InVal=0.f,OutVal=4.f),(InVal=2500.0f, 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))} }