//============================================================================= // AICommand_Flee //============================================================================= // Directs the AI away from a target for a specified duration and distance //============================================================================= // Killing Floor 2 // Copyright (C) 2015 Tripwire Interactive LLC //============================================================================= class AICommand_Flee extends AICommand within KFAIController; /* Tells the command to stop when it reaches the goal */ var bool bStopAtGoal; /* Actor to flee away from */ var Actor FleeTarget; /* Dot product of projection between pawn and goal location */ var float Dot; /** Cached value of the sprinting state before fleeing, to restore upon command pop */ var bool bWasSprinting; /* How many unsuccessful path attempts have been made since command was pushed */ var int PathAttempts; /* The maximum duration that the flee command can run */ var float FleeDuration; /** The desired distance to attempt to flee. Note that this is not based on direct projection, but visited weight. */ var float FleeDistance; /** Returns true if we've gotten a goal and want to continue towards it */ var bool bHaveGoal; /** A randomized distance outside of the target goal to stop at */ var float GoalOffset; /********************************************************************************************* * Initialization ********************************************************************************************* */ static function bool FleeFrom( KFAIController AI, actor inFleeTarget, optional float inFleeDuration, optional float inFleeDistance=5000, optional bool bShouldStopAtGoal=false ) { local AICommand_Flee Cmd; if( AI != None ) { Cmd = new(AI) Default.Class; if( Cmd != None ) { Cmd.FleeTarget = inFleeTarget; Cmd.FleeDistance = inFleeDistance; Cmd.FleeDuration = inFleeDuration; Cmd.bStopAtGoal = bShouldStopAtGoal; AI.PushCommand(Cmd); return true; } } return false; } function Pushed() { super.Pushed(); bWasSprinting = MyKFPawn.bIsSprinting; DisableMeleeRangeEventProbing(); MoveTimer = -1.0; Pawn.ZeroMovementVariables(); StopAllLatentMovement(); RouteGoal = None; bHaveGoal = false; GoalOffset = Square(384.f + (fRand()*128.f)); if( FleeDuration > 0.f ) { SetTimer( FleeDuration, false, nameof(Timer_FleeDurationExpired), self ); } StartFleeing(); } function Timer_FleeDurationExpired() { Status = 'Success'; NotifyFleeFinished(); PopCommand( self ); } function StartFleeing() { GotoState('Fleeing', 'Begin'); } function Popped() { super.Popped(); if( MyKFPawn != none ) { MyKFPawn.bIsSprinting = bWasSprinting; MyKFPawn.ClearHeadTrackTarget( FleeTarget ); } ClearTimer( nameOf(Timer_FleeDurationExpired), self ); EnableMeleeRangeEventProbing(); } /********************************************************************************************* * Hiding state ********************************************************************************************* */ state Fleeing { event BeginState( name PreviousStateName ) { super.BeginState( PreviousStateName ); Enemy = None; } function bool CheckRetreat() { local EPathSearchType OldSearchType; if( RouteGoal != none && bHaveGoal ) { AIActionStatus = "Attempting to flee from ["$FleeTarget$"] at ["$RouteGoal$"]"; return true; } else { bHaveGoal = false; } // Make sure we always have something to flee from if( FleeTarget == none ) { Class'NavigationPoint'.static.GetNearestNavToActor( Pawn ); } // Cache off our previous path search type OldSearchType = Pawn.PathSearchType; if( FleeTarget != none ) { AIActionStatus = "Searching for navigable path from ["$FleeTarget$"]"; Pawn.PathSearchType = PST_Constraint; class'Path_AlongLine'.static.AlongLine( Pawn, Normal(Pawn.Location - FleeTarget.Location) ); class'Goal_AwayFromPosition'.static.FleeFrom( Pawn, FleeTarget.Location, FleeDistance ); if( FindPathToward( Pawn ) != None ) { //Pawn.DrawDebugLine( RouteGoal.Location, Pawn.Location, 255, 80, 80, true ); //Pawn.DrawDebugSphere( RouteGoal.Location, 128, 4, 255, 80, 80, true ); bHaveGoal = true; AIActionStatus = "Attempting to flee from ["$FleeTarget$"] at ["$RouteGoal$"]"; Focus = none; PathAttempts = 0; // Restore previous path search type Pawn.PathSearchType = OldSearchType; return true; } } // Restore previous path search type Pawn.PathSearchType = OldSearchType; PathAttempts += 1; return false; } Begin: // Make sure we have a pawn and it's alive if( MyKFPawn == none || !MyKFPawn.IsAliveAndWell() ) { Status = 'Failure'; `AILog( "No pawn, aborting", 'Command_Flee' ); NotifyFleeFinished(); PopCommand( self ); } else { // Don't try to path when falling if( MyKFPawn.Physics == PHYS_Falling ) { AIActionStatus = "Fleeing, waiting for landing..."; WaitForLanding(); } // Make sure our pawn didn't die from falling! if( MyKFPawn == none || !MyKFPawn.IsAliveAndWell() ) { Status = 'Failure'; `AILog( "No pawn, or pawn died from falling", 'Command_Flee' ); NotifyFleeFinished(); PopCommand( self ); } else { // Keep focus clear Focus = None; if( CheckRetreat() ) { // Always sprint when fleeing MyKFPawn.SetSprinting( true ); // Continually move us to our goal SetMoveGoal( RouteGoal, none, false, 256.f, true, true, true, false, false ); // Stop at goal if we are close enough if( !bStopAtGoal || VSizeSQ(Pawn.Location - RouteGoal.Location) > GoalOffset ) { Sleep( 0.f ); Goto( 'Begin' ); } // Rotate to direction Dot = Normal(Location - FleeTarget.Location) dot vector(FleeTarget.Rotation); if( Dot > 0.78f ) { PushState( 'RotateToFocus' ); Status = 'Success'; Sleep( 0.f ); NotifyFleeFinished(); PopCommand( self ); } else { Status = 'Success'; Sleep( 0.f ); NotifyFleeFinished(); PopCommand( self ); } } else if( PathAttempts > 30 ) { // Don't get stuck, pop the command so the notify triggers Status = 'Failure'; `AILog( "Failed to find flee path from ["$FleeTarget$"] after 30 attempts, aborting...", 'Command_Flee' ); NotifyFleeFinished(); PopCommand( self ); } else { Sleep( 0.f ); Goto( 'Begin' ); } } } FleeComplete: Status = 'Success'; NotifyFleeFinished(); PopCommand( self ); } state RotateToFocus `DEBUGSTATE { Begin: AIActionStatus = "Rotating to focus"; SetFocalPoint( vect(0,0,0) ); Focus = FleeTarget; FinishRotation(); PopState(); } DefaultProperties { bAllowedToAttack=false }