1
0
KF2-Dev-Scripts/KFGame/Classes/AICommand_MoveToEnemy.uc

474 lines
16 KiB
Ucode
Raw Normal View History

2020-12-13 15:01:13 +00:00
//=============================================================================
// AICommand_MoveToEnemy
//=============================================================================
// Base command for moving to enemy - will build path and call MoveToGoal
// command to start the movement.
//=============================================================================
// Killing Floor 2
// Copyright (C) 2015 Tripwire Interactive LLC
//=============================================================================
class AICommand_MoveToEnemy extends AICommand
within KFAIController;
var Actor Path, Find;
var float Radius;
/** Number of times remaining we can fail the MoveToGoal before we give up */
var int MoveTriesRemaining;
/** If this is true, we will move all the way to the enemy instead of just to the first goal */
var bool bCompleteMove;
/** How close to get to the enemy (only valid of bCompleteMove is true) */
var float GoalDistance;
/** If enemy gets this far from us we should abandon the move */
var float AbandonDistance;
/** Last location of enemy... if they've moved too much, redo pathing */
var BasedPosition LastEnemyLocation;
/** Are we allowed to fire during this move? */
var bool bAllowedToAttackDuringMove;
/** If the enemy moves more than this distance away from where it was when we pathed, repath */
var float RepathDistance;
/** override for movement focus */
var Actor OverrideFocus;
/*********************************************************************************************
* Push/Pause/Pop/Init
********************************************************************************************* */
/** Simple constructor that pushes a new instance of the command for the AI */
static function bool MoveToEnemy( KFAIController AI, bool bInCompleteMove, float InGoalDistance, float InAbandonDistance, optional bool bInAllowedToAttackDuringMove=true, optional actor InFocusOverride=none )
{
local AICommand_MoveToEnemy Cmd;
if( AI != None && AI.Pawn != None )
{
Cmd = new(AI) class'AICommand_MoveToEnemy';
if( Cmd != None )
{
Cmd.bCompleteMove = bInCompleteMove;
Cmd.GoalDistance = InGoalDistance;
Cmd.AbandonDistance = InAbandonDistance;
Cmd.bAllowedToAttackDuringMove = bInAllowedToAttackDuringMove;
Cmd.OverrideFocus = InFocusOverride;
AI.PushCommand( Cmd );
return true;
}
}
return false;
}
function Pushed()
{
`AILog( self@GetFuncName()$"() current dist to enemy: "$VSize(Enemy.Location - Pawn.Location), 'Command_MoveToEnemy' );
Super.Pushed();
bMovingToEnemy = true;
SprintTimer();
SetTimer( 1.5f + FRand(), false, nameof( SprintTimer ), self );
GotoState( 'Moving' );
}
function Popped()
{
if( Pawn != none && Enemy != none )
{
`AILog( self@GetFuncName()$"() current dist to enemy: "$VSize(Enemy.Location - Pawn.Location), 'Command_MoveToEnemy' );
}
Super.Popped();
`AILog( self@GetFuncName()$"()", 'Command_MoveToEnemy' );
ClearTimer( 'CheckReachedEnemy', self );
ClearTimer( nameof(CheckEnemyMoved), self );
ClearTimer( nameof(self.DirectMoveTimeout),self );
bFailedToMoveToEnemy = ( Status != 'Success' );
bMovingToEnemy = false;
if( bFailedToMoveToEnemy )
{
if( Pawn != none )
UpdateHistoryString( "Popped but failed to move to enemy at "$Pawn.Location$" Time: "$WorldInfo.TimeSeconds );
}
}
function Paused( GameAICommand NewCommand )
{
Super.Paused( NewCommand );
if( Enemy != none )
{
`AILog( GetFuncName()$" "$self$" Paused, replaced by "$NewCommand$" [and dist to enemy: "$VSize(Enemy.Location - Pawn.Location)$"]", 'Command_MoveToEnemy' );
}
PauseTimer( true, 'CheckReachedEnemy', self );
PauseTimer( true, nameof(CheckEnemyMoved), self );
PauseTimer( true, nameof(self.DirectMoveTimeout),self );
}
function Resumed( name OldCommandName )
{
Super.Resumed( OldCommandName );
if( Enemy != none )
{
`AILog( GetFuncName()$" "$self$" Resumed, OldCommand: "$OldCommandName$" [and dist to enemy: "$VSize(Enemy.Location - Pawn.Location)$"]", 'Command_MoveToEnemy' );
}
PauseTimer( false, 'CheckReachedEnemy', self );
PauseTimer( false, nameof(CheckEnemyMoved), self );
PauseTimer( false, nameof(self.DirectMoveTimeout),self );
if( !bCompleteMove )
{
`AILog( GetFuncName()$"() popping command because !bCompleteMove on resume", 'Command_MoveToEnemy' );
Status = ChildStatus;
PopCommand( self );
}
}
/*********************************************************************************************
* Combat related
********************************************************************************************* */
function bool NotifyPlayerBecameVisible( Pawn VisiblePlayer )
{
if( CachedChildCommand != None )
{
`AILog( GetFuncName()$"() Seen: "$VisiblePlayer$" notifying "$CachedChildCommand$" and letting it handle the event.", 'SeePlayer' );
return CachedChildCommand.NotifyPlayerBecameVisible( VisiblePlayer );
}
`AILog( GetFuncName()$" "$VisiblePlayer$" ignoring this event", 'SeePlayer' );
return false;
}
/*********************************************************************************************
* Movement & Pathfinding
********************************************************************************************* */
function SprintTimer()
{
if( MyKFPawn != none && MyKFPawn.IsAliveAndWell() && Enemy != none )
{
if( VSize( Pawn.Velocity ) > 0.f )
{
UpdateSprintFrustration();
if( ShouldSprint() || MyKFPawn.bSprintOverride)
{
MyKFPawn.SetSprinting( true );
}
else
{
MyKFPawn.SetSprinting( false );
}
}
}
SetTimer( 1.5f + FRand(), false, nameof( SprintTimer ), self ); // ToDo: Disable
}
function CheckReachedEnemy()
{
local float DistToEnemySq;
// only abort if we don't have a child, or our child is a move command.. otherwise let it slide
if( ChildCommand == none || (ChildCommand.IsA('AICommand_MoveToGoal') && ChildCommand.ChildCommand == none) )
{
if( Enemy != None || !Enemy.IsAliveAndWell() )
{
DistToEnemySq = VSizeSq(Enemy.Location-Pawn.Location);
`AIlog( GetFuncName()$"() Dist: "$VSize(Enemy.Location-Pawn.Location)$" GoalDistance: "$GoalDistance$" checking IsWithinAttackRange()", 'ReachedEnemy');
if( IsDoingAttackSpecialMove() )
{
`AILog( GetFuncName()$"() IsWithinAttackRange(): I've reached enemy (Dist:"$Sqrt(DistToEnemySq)$") calling AbortCommand", 'ReachedEnemy' );
// if( !MyKFPawn.IsPawnMovingAwayFromMe( Enemy ) )
// {
AbortCommand( self );
// }
}
else if( AbandonDistance > 0.f && DistToEnemySq >= AbandonDistance*AbandonDistance )
{
`AILog( self$" Move took us outside abandon dist...", 'ReachedEnemy' );
AbortCommand( self );
}
}
else
{
`AILog( "Enemy is no longer valid, aborting command", 'ReachedEnemy' );
AbortCommand( self );
}
}
else
{
if( ChildCommand == none )
{
`AILog( GetFuncName()$"() skipping reached evaluation because ChildCommand is NONE", 'ReachedEnemy' );
}
else if( ChildCommand.IsA('AICommand_MoveToGoal') && ChildCommand.ChildCommand == none )
{
`AILog( GetFuncName()$"() skipping reached evaluation because ChildCommand is a MoveToGoal command and has no child of its own", 'ReachedEnemy' );
}
}
}
function CheckEnemyMoved()
{
local float DistSq;
`AILog( GetFuncName(), 'Command_MoveToEnemy' );
if(Find != none)
{
DistSq = VSizeSq( Find.Location - BP2Vect(LastEnemyLocation) );
if( DistSq > RepathDistance*RepathDistance && !IsValidDirectMoveGoal(Find) )
{
if(ChildCommand!=none)
{
// if( !MyKFPawn.IsPawnMovingAwayFromMe( Enemy ) )
// {
// MoveTriesRemaining=default.MoveTriesRemaining+1;
// AbortCommand(ChildCommand);
// }
}
}
}
}
function DirectMoveTimeout()
{
`AILog( GetFuncName()$"() - not doing anything, though.", 'Command_MoveToEnemy' );
// if( !MyKFPawn.IsPawnMovingAwayFromMe( Enemy ) )
// {
// `AILog("Was moving directly to enemy, but it took too long and we were bCompleteMove=false, aborting");
// AbortCommand(self);
// }
}
/*********************************************************************************************
* Debug
********************************************************************************************* */
/** Build debug string */
event String GetDumpString()
{
return Super.GetDumpString()@Enemy@GoalDistance@bCompleteMove;
}
/*********************************************************************************************
* Moving to Enemy State
********************************************************************************************* */
function JumpToTarget()
{
local Vector Suggested;
if( Enemy != none )
{
MyKFPawn.SuggestJumpVelocity( Suggested, Enemy.Location, Pawn.Location );
MyKFPawn.Velocity = Suggested;
MoveTarget = Enemy;
//MyKFPawn.Acceleration = vect( 0,0,0 );
MyKFPawn.SetPhysics( PHYS_Falling );
}
}
state Moving `DEBUGSTATE
{
Begin:
if( MyKFPawn.Physics == PHYS_Falling )
{
WaitForLanding();
}
`AILog( "[Begin Label]"@GetSTatename(), 'Command_MoveToEnemy');
// UnMark failure to move
bFailedToMoveToEnemy = false;
if( Enemy == None )
{
SelectEnemy();
}
if( Enemy != none && !IsDoingAttackSpecialMove() && MoveIsInterruptable() )
{
`AILog( "Found valid enemy: "$Enemy, 'Command_MoveToEnemy' );
bReachedMoveGoal = false;
Radius = Pawn.GetCollisionRadius() + Enemy.GetCollisionRadius() + Pawn.MeleeRange + 1.0;
//[NEW] Commented out 10/10
Radius = FMax( 0.f, GoalDistance - Radius ); // subtract because collision radius already considered by ReachedDestination()
Find = Enemy;
Loop:
// If enemy directly reachable
`AILog( "Loop Label "$find, 'Command_MoveToEnemy' );
SetBasedPosition( LastEnemyLocation, Find.Location );
// Is Pawn closer than maximum 'basic' attack tag range?
if( IsDoingAttackSpecialMove() )
{
`AIlog( self$" [Cleaning up and returning Success status] IsWithinAttackRange returned TRUE DIST: "$VSize( Enemy.Location - Pawn.Location )$" <= MeleeAttackHelper range ("$MyKFPawn.MeleeAttackHelper.GetMeleeRange()$")", 'Command_MoveToEnemy' );
ClearTimer( 'CheckReachedEnemy', self );
ClearTimer( nameof(self.DirectMoveTimeout ),self);
ClearTimer( nameof(CheckEnemyMoved ), self);
Status = 'Success';
PopCommand( self );
Stop;
}
else if( IsValidDirectMoveGoal( Find ) )
{
// WIP - Disabled until ready
/*
if( Pawn.bCrawler )
{
MyKFPawn.SetSprinting( true );
MyKFPawn.bIsSprinting = true;
}
if( Pawn.Physics == PHYS_Spider )
{
JumpToTarget();
if( Pawn.Physics == PHYS_Falling )
{
WaitForLanding();
Goto( 'Begin' );
}
}
*/
`AILog( "Enemy "$Find$" - IsValidDirectMoveGoal() returned true, trying direct move...", 'Command_MoveToEnemy' );
SetTimer( 0.1f, true, nameof(CheckEnemyMoved), self);
SetTimer( 0.1f, true, nameof(self.CheckReachedEnemy), self );
if( !bUseNavMesh || !bUsePluginsForMovement )
{
`AILog( self$" Calling SetMoveGoal() for enemy, using offset "$Radius$", with bCanPathFind = false", 'Command_MoveToEnemy' );
SetMoveGoal( Find,, true, Radius,, false,, bAllowedToAttackDuringMove );
}
if( Enemy != none )
{
`AILog( self$" Done moving to goal, distance is now "$VSize(Enemy.Location - Pawn.Location), 'Command_MoveToEnemy' );
}
if( ChildStatus != 'Success' )
{
if( Enemy != none )
{
`AILog( "Child status was not successful, MoveTriesRemaining: "$MoveTriesRemaining$" Enemy Dist: "$VSize(Enemy.Location - Pawn.Location) );
}
if( --MoveTriesRemaining <= 0)
{
`AILog( "Child failed or aborted, and we're out of move tries.. failing", 'Command_MoveToEnemy' );
UpdateHistoryString( "Child failed or aborted, and we're out of move tries at "$WorldInfo.TimeSeconds$"]" );
GotoState( 'DelayFailure' );
}
else
{
Goto( 'Loop' );
}
}
ClearTimer( 'CheckReachedEnemy', self );
ClearTimer( nameof(self.DirectMoveTimeout ),self );
ClearTimer( nameof(CheckEnemyMoved), self );
`AILog( "Finished moving to enemy", 'Command_MoveToEnemy' );
Status = 'Success';
Sleep(0.f);
PopCommand( self );
}
else
{
if( Enemy != none )
{
`AILog( "Trying to build path to enemy who is currently "$VSize( Enemy.Location - Pawn.Location )$" units away", 'Command_MoveToEnemy' );
}
if( Pawn.Anchor == none )
{
SetBestAnchor();
}
// Try to find path to enemy
Path = GeneratePathTo( Find,, true );
// If no path available
if( Path == None )
{
`AILog( "Attempt to build path failed, going to DelayFailure state and calling SetFailedPathToEnemy()", 'PathWarning' );
if( MyKFPawn.bCrawler && MyKFPawn.Physics == PHYS_Spider )
{
MyKFPawn.SetPhysics(PHYS_Falling);
WaitForLanding();
}
// Update failed path time
SetFailedPathToEnemy( Enemy );
GotoState( 'DelayFailure' );
}
else
{
if( VSize(Find.Location - Pawn.Location) <= StrikeRange )
{
GotoState( 'DelayFailure' );
}
else if( !bCompleteMove )
{
`AILog( "Built path to enemy, bComplete move was false.. Pawn.Anchor: "$Pawn.Anchor$" Path: "$Path$" AnchorDist: "$VSize(Pawn.Location - Pawn.Anchor.Location), 'Command_MoveToEnemy' );
//if( RouteCache.Length > 0 )
//{
// `log( "Pawn.RouteCache[0]: "$RouteCache[0] );
// `log( "Pawn.RouteCache[1]: "$RouteCache[1] );
// `log( "Pawn.RouteCache[2]: "$RouteCache[2] );
//}
// Move to first path...
// will research for new path when reached first one
// make sure we don't try to move to our anchor
if( Path == Pawn.Anchor )
{
`AILog( "Preventing myself from moving to my anchor ("$Pawn.Anchor$") as my first path goal", 'Command_MoveToEnemy' );
Path = RouteCache[1];
// clip off the end of the routecache
if(RouteCache.length > 2)
{
RouteCache_RemoveIndex(2,RouteCache.length-2);
}
}
else
{
if(RouteCache.length > 1)
{
RouteCache_RemoveIndex(1,RouteCache.length-1);
}
}
}
else
{
Path = Find;
}
SetTimer( 0.5, true, nameof(CheckEnemyMoved), self );
SetTimer( 0.5f, true, nameof(CheckReachedEnemy), self );
if( !bUseNavMesh || !bUsePluginsForMovement )
{
// Have bValidCache true first time, but false after that
`AILog( "Starting move to goal "$Path$" which is currently "$VSize( Path.Location - Pawn.Location )$" units away", 'Command_MoveToEnemy' );
SetMoveGoal( Path,, true,, true,,,bAllowedToAttackDuringMove );
}
// (untested) SetMoveGoal( Path,, true, StrikeRange, true,,,bAllowedToAttackDuringMove );
`AILog( "Finished move to goal "$Path$" which is now "$VSize( Path.Location - Pawn.Location )$" units away", 'Command_MoveToEnemy' );
ClearTimer( 'CheckReachedEnemy', self );
ClearTimer( 'CheckEnemyMoved', self );
if( ChildStatus != 'Success' && --MoveTriesRemaining <= 0 )
{
`AILog( "Going to DelayedFailure state because ChildStatus is failed and MoveTriesRemaining: "$MoveTriesRemaining, 'Command_MoveToENemy' );
GotoState( 'DelayFailure' );
}
Sleep(0.0);
if( Enemy == none )
{
`AILog( "Lost enemy or enemy became invalid, aborting", 'Command_MoveToEnemy' );
AbortCommand( self );
}
Goto( 'Loop' );
}
}
}
else
{
`AILog( "No valid enemy, or not allowed to move.. bailing ValidEnemy?:"@Enemy@"Allowed to move?"@AllowedToMove()@"Move Interruptable?"@MoveIsInterruptable(), 'Command_MoveToEnemy' );
GotoState( 'DelayFailure' );
}
}
defaultproperties
{
MoveTriesRemaining=3
bAllowedToAttack=false
RepathDistance=1224.f
}