1
0
KF2-Dev-Scripts/KFGame/Classes/AICommand_Flee.uc
2023-09-21 22:31:11 +03:00

382 lines
9.7 KiB
Ucode

//=============================================================================
// 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;
var bool bUseRandomDirection;
/*********************************************************************************************
* Initialization
********************************************************************************************* */
static function bool FleeFrom(
KFAIController AI,
actor inFleeTarget,
optional float inFleeDuration,
optional float inFleeDistance=5000,
optional bool bShouldStopAtGoal=false,
optional bool bHasToUseRandomDirection=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;
Cmd.bUseRandomDirection = bHasToUseRandomDirection;
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();
}
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);
}
/*********************************************************************************************
* Hiding state
********************************************************************************************* */
state Fleeing
{
event BeginState( name PreviousStateName )
{
super.BeginState( PreviousStateName );
Enemy = None;
}
function bool CheckRetreat()
{
local EPathSearchType OldSearchType;
local vector RandVector;
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;
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 );
}
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
bUseRandomDirection=false
}