2020-12-13 15:01:13 +00:00
|
|
|
//=============================================================================
|
|
|
|
// 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;
|
|
|
|
|
2023-09-21 19:31:11 +00:00
|
|
|
var bool bUseRandomDirection;
|
|
|
|
|
2020-12-13 15:01:13 +00:00
|
|
|
/*********************************************************************************************
|
|
|
|
* Initialization
|
|
|
|
********************************************************************************************* */
|
|
|
|
|
|
|
|
static function bool FleeFrom(
|
|
|
|
KFAIController AI,
|
|
|
|
actor inFleeTarget,
|
|
|
|
optional float inFleeDuration,
|
|
|
|
optional float inFleeDistance=5000,
|
2023-09-21 19:31:11 +00:00
|
|
|
optional bool bShouldStopAtGoal=false,
|
|
|
|
optional bool bHasToUseRandomDirection=false )
|
2020-12-13 15:01:13 +00:00
|
|
|
{
|
|
|
|
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;
|
2023-09-21 19:31:11 +00:00
|
|
|
Cmd.bUseRandomDirection = bHasToUseRandomDirection;
|
2020-12-13 15:01:13 +00:00
|
|
|
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();
|
|
|
|
}
|
|
|
|
|
2023-09-21 19:31:11 +00:00
|
|
|
function vector GetRandomFleeVector()
|
|
|
|
{
|
|
|
|
local vector VectorReference, VectorReferenceRotated, RandVector, HitLocation, HitNormal, Destination, DestinationRotated, AxxisRotation;
|
|
|
|
local Actor HitActor;
|
|
|
|
local int i, NumIterations;
|
|
|
|
local float Angle, LengthFlee;
|
|
|
|
local Quat R;
|
|
|
|
|
|
|
|
// Start on the bearing of the FleeTarget, check for collision then explore to the sides rotating that vector
|
|
|
|
|
|
|
|
VectorReference = Normal(Pawn.Location - FleeTarget.Location);
|
|
|
|
VectorReference.z = 0.f;
|
|
|
|
|
|
|
|
RandVector = VectorReference;
|
|
|
|
|
|
|
|
LengthFlee = 300.f;
|
|
|
|
//LengthFlee = FleeDistance;
|
|
|
|
|
|
|
|
Destination = Pawn.Location + VectorReference * LengthFlee;
|
|
|
|
|
|
|
|
HitActor = Trace(HitLocation, HitNormal, Destination, Pawn.Location, true,,, TRACEFLAG_Blocking);
|
|
|
|
if (HitActor != none && KFPawnBlockingVolume(HitActor) != none)
|
|
|
|
{
|
|
|
|
//HitActor.DrawDebugLine(Pawn.Location, Destination, 255, 0, 0, true );
|
|
|
|
|
|
|
|
Angle = 15.f * DegToRad;
|
|
|
|
|
|
|
|
NumIterations = 180.f * DegToRad / Angle;
|
|
|
|
|
|
|
|
AxxisRotation.x = 0.f;
|
|
|
|
AxxisRotation.y = 0.f;
|
|
|
|
AxxisRotation.z = 1.f;
|
|
|
|
|
|
|
|
for (i = 1; i < NumIterations; ++i)
|
|
|
|
{
|
|
|
|
// Left
|
|
|
|
|
|
|
|
R = QuatFromAxisAndAngle(AxxisRotation, i * Angle);
|
|
|
|
VectorReferenceRotated = Normal(QuatRotateVector(R, VectorReference));
|
|
|
|
|
|
|
|
DestinationRotated = Pawn.Location + VectorReferenceRotated * LengthFlee;
|
|
|
|
|
|
|
|
HitActor = Trace(HitLocation, HitNormal, DestinationRotated, Pawn.Location, true,,, TRACEFLAG_Blocking);
|
|
|
|
if (HitActor != none && KFPawnBlockingVolume(HitActor) != none)
|
|
|
|
{
|
|
|
|
//Pawn.DrawDebugLine(Pawn.Location, DestinationRotated, 255, 255, 0, true );
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
RandVector = DestinationRotated;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Right
|
|
|
|
|
|
|
|
R = QuatFromAxisAndAngle(AxxisRotation, - i * Angle);
|
|
|
|
VectorReferenceRotated = Normal(QuatRotateVector(R, VectorReference));
|
|
|
|
|
|
|
|
DestinationRotated = Pawn.Location + VectorReferenceRotated * LengthFlee;
|
|
|
|
|
|
|
|
HitActor = Trace(HitLocation, HitNormal, DestinationRotated, Pawn.Location, true,,, TRACEFLAG_Blocking);
|
|
|
|
if (HitActor != none && KFPawnBlockingVolume(HitActor) != none)
|
|
|
|
{
|
|
|
|
//Pawn.DrawDebugLine(Pawn.Location, DestinationRotated, 0, 255, 255, true );
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
RandVector = DestinationRotated;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//Pawn.DrawDebugLine(Pawn.Location, Pawn.Location + RandVector * LengthFlee, 0, 255, 0, true );
|
|
|
|
|
|
|
|
return Normal(RandVector);
|
|
|
|
}
|
|
|
|
|
2020-12-13 15:01:13 +00:00
|
|
|
/*********************************************************************************************
|
|
|
|
* Hiding state
|
|
|
|
********************************************************************************************* */
|
|
|
|
|
|
|
|
state Fleeing
|
|
|
|
{
|
|
|
|
event BeginState( name PreviousStateName )
|
|
|
|
{
|
|
|
|
super.BeginState( PreviousStateName );
|
|
|
|
|
|
|
|
Enemy = None;
|
|
|
|
}
|
|
|
|
|
|
|
|
function bool CheckRetreat()
|
|
|
|
{
|
|
|
|
local EPathSearchType OldSearchType;
|
2023-09-21 19:31:11 +00:00
|
|
|
local vector RandVector;
|
2020-12-13 15:01:13 +00:00
|
|
|
|
|
|
|
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;
|
2023-09-21 19:31:11 +00:00
|
|
|
|
|
|
|
if (bUseRandomDirection)
|
|
|
|
{
|
|
|
|
MyKFPawn.SetSprinting( true );
|
|
|
|
|
|
|
|
RandVector = GetRandomFleeVector();
|
|
|
|
|
|
|
|
class'Path_AlongLine'.static.AlongLine( Pawn, RandVector );
|
|
|
|
class'Goal_Random'.static.FindRandom( Pawn );
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
class'Path_AlongLine'.static.AlongLine( Pawn, Normal(Pawn.Location - FleeTarget.Location) );
|
|
|
|
class'Goal_AwayFromPosition'.static.FleeFrom( Pawn, FleeTarget.Location, FleeDistance );
|
|
|
|
}
|
2020-12-13 15:01:13 +00:00
|
|
|
|
|
|
|
if( FindPathToward( Pawn ) != None )
|
|
|
|
{
|
|
|
|
//Pawn.DrawDebugLine( RouteGoal.Location, Pawn.Location, 255, 80, 80, true );
|
|
|
|
//Pawn.DrawDebugSphere( RouteGoal.Location, 128, 4, 255, 80, 80, true );
|
2023-09-21 19:31:11 +00:00
|
|
|
|
2020-12-13 15:01:13 +00:00
|
|
|
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
|
2023-09-21 19:31:11 +00:00
|
|
|
bUseRandomDirection=false
|
2020-12-13 15:01:13 +00:00
|
|
|
}
|