1792 lines
63 KiB
Ucode
1792 lines
63 KiB
Ucode
|
//=============================================================================
|
||
|
// AICommand_MoveToGoal
|
||
|
//=============================================================================
|
||
|
// Main movement/navigation AICommand (based on GOW2-GOW3)
|
||
|
//=============================================================================
|
||
|
// Killing Floor 2
|
||
|
// Copyright (C) 2015 Tripwire Interactive LLC
|
||
|
//=============================================================================
|
||
|
class AICommand_MoveToGoal extends AICommand
|
||
|
within KFAIController
|
||
|
native(AI);
|
||
|
|
||
|
`include(KFGame\KFGameAnalytics.uci);
|
||
|
|
||
|
/*********************************************************************************************
|
||
|
* Pathfinding
|
||
|
**********************************************************************************************/
|
||
|
/** if false, abort if we need to pathfind (i.e, if the current RouteCache becomes invalid) */
|
||
|
var bool bCanPathfind;
|
||
|
/** Current RouteCache for controller is valid, no need to path find again */
|
||
|
var bool bValidRouteCache;
|
||
|
/** Allow any path generation to return best guess */
|
||
|
var bool bAllowPartialPath;
|
||
|
/** Intermediate move goal used when pathfinding to MoveGoal */
|
||
|
var Actor IntermediateMoveGoal;
|
||
|
/** Can pawn perform attacks during this move? **/
|
||
|
var bool bAllowedToAttackDuringMove;
|
||
|
/** IgnoreStepAside is saved in case temporarily overridden */
|
||
|
var bool bSavedIgnoreStepAside;
|
||
|
/** Cached last goal location if goal is a pawn (and might move) */
|
||
|
var BasedPosition LastPawnTargetPathLocation;
|
||
|
/** Current actor destination */
|
||
|
var actor MoveToActor;
|
||
|
|
||
|
/*********************************************************************************************
|
||
|
* Failed move recovery
|
||
|
**********************************************************************************************/
|
||
|
/** counter for handling some cases of pawn getting stuck */
|
||
|
var int NumTimesGetNextMoveGoalReturnedSameNode;
|
||
|
/** Number of times this oommand has attempted to retry moving after failure */
|
||
|
var int Retries;
|
||
|
/** counter for handling some cases of pawn getting stuck */
|
||
|
var int LoopFailSafeCounter;
|
||
|
/** fail-safe vars used to determine when a move has failed and needs to be started over */
|
||
|
var vector LastMovePoint;
|
||
|
/** Seconds before timing out this move */
|
||
|
var float TimeOutTime;
|
||
|
/** Actor my pawn should rotate toward before move */
|
||
|
var actor TurnFocus;
|
||
|
/** Location my pawn should rotate toward before move */
|
||
|
var vector TurnFocalPoint;
|
||
|
/** Track # of recent collisions with non-pawn obstructions */
|
||
|
var int WallHitCount;
|
||
|
|
||
|
/*********************************************************************************************
|
||
|
* SkipAhead (evaluate and skip unnecessary nodes in a path)
|
||
|
**********************************************************************************************/
|
||
|
/** The last position we tried to skip ahead in the path from */
|
||
|
var BasedPosition SkipAheadLastUpdatePos;
|
||
|
/** if we've moved SkipAheadUpdateThreshold units away from SkipAheadlastupdatePos, a new skipahead update will be run */
|
||
|
var float SkipAheadUpdateThreshold;
|
||
|
/** maximum number of nodes in a path to skip ahead */
|
||
|
var int SkipAheadMaxNodes;
|
||
|
/** maximum path distance to skip */
|
||
|
var float SkipAheadMaxDist;
|
||
|
/** distance interval to check for pits when determining if it's safe to skip ahead */
|
||
|
var float SkipAheadPitCheckInterval;
|
||
|
/** distance downward to trace for pit checks */
|
||
|
var float SkipAheadPitCheckHeight;
|
||
|
/** disable/enable skipahead */
|
||
|
var bool bEnableSkipAheadChecks;
|
||
|
/** internal use only, number of pending line check results we're waiting on */
|
||
|
var const int SkipAheadNumActiveTests;
|
||
|
/** internal use only, whether any of the last batch of tests failed */
|
||
|
var const bool bSkipAheadFail;
|
||
|
/** internal use only, the index into the route cache of the current node we're testing for skip ahead */
|
||
|
var int SkipAheadCurrentTestingIndex;
|
||
|
/** indicates that the our current routecache has changed since the last time GetNnextMoveTarget was called */
|
||
|
var bool bGoalChangedDueToSkipAhead;
|
||
|
|
||
|
/** list of nav points which skipahead testing is not allowed to skip past (e.g. for when we're following a set route) */
|
||
|
var native const transient Map{ANavigationPoint*,UBOOL} NonSkippableWaypoints;
|
||
|
|
||
|
const bUseAsyncRaycastsForSkipAhead = 0;
|
||
|
var bool bGoalSurrounded;
|
||
|
|
||
|
cpptext
|
||
|
{
|
||
|
UBOOL AreSkipAheadTestsPending();
|
||
|
void QueueSkipAheadTests(const FVector& Start, ANavigationPoint* NodeToTest, FLOAT CollisionRadius);
|
||
|
virtual void TickCommand(FLOAT DeltaTime);
|
||
|
void UpdateSubGoal(FLOAT DeltaTime);
|
||
|
void SkipToSubGoal(AKFAIController* GAI, INT Index);
|
||
|
};
|
||
|
|
||
|
/** Removes all navpts from the non-skippable waypoint list */
|
||
|
function native ClearNonSkippableWayPoints();
|
||
|
/** Adds a nav point to the non-skippable waypoint list*/
|
||
|
function native AddNonSkippableWayPoint( NavigationPoint Point );
|
||
|
|
||
|
/*********************************************************************************************
|
||
|
* Push/Pause/Pop/Init
|
||
|
********************************************************************************************* */
|
||
|
|
||
|
/** Init the MoveToGoal command - my pawn's goal will be an actor (usually a player) */
|
||
|
static function bool MoveToGoal( KFAIController AI, Actor NewMoveGoal, optional Actor NewMoveFocus, optional float NewMoveOffset,
|
||
|
optional bool bIsValidCache,
|
||
|
optional bool bInCanPathfind = true,
|
||
|
optional bool bInAllowedToAttackDuringMove = true,
|
||
|
optional bool bInAllowPartialPath = true )
|
||
|
{
|
||
|
local AICommand_MoveToGoal Cmd;
|
||
|
|
||
|
if( AI != none && NewMoveGoal != none )
|
||
|
{
|
||
|
Cmd = new(AI) class'AICommand_MoveToGoal';
|
||
|
if( Cmd != none )
|
||
|
{
|
||
|
// Never actually want to move to a controller, substitute pawn instead
|
||
|
if( Controller(NewMoveGoal) != none )
|
||
|
{
|
||
|
NewMoveGoal = Controller(NewMoveGoal).Pawn;
|
||
|
}
|
||
|
AI.MoveGoal = NewMoveGoal;
|
||
|
AI.MoveFocus = NewMoveFocus;
|
||
|
AI.MoveOffset = NewMoveOffset;
|
||
|
Cmd.MoveToActor = NewMoveGoal;
|
||
|
Cmd.bValidRouteCache = bIsValidCache;
|
||
|
Cmd.bCanPathfind = bInCanPathfind;
|
||
|
Cmd.bAllowedToAttack = bInAllowedToAttackDuringMove;
|
||
|
Cmd.bAllowPartialPath = bInAllowPartialPath;
|
||
|
|
||
|
AI.SetBasedPosition( AI.MovePosition, vect(0,0,0) );
|
||
|
AI.PushCommand( Cmd );
|
||
|
return true;
|
||
|
}
|
||
|
}
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
/** Init the MoveToGoal command - my pawn's goal will be a location instead of an actor */
|
||
|
static function bool MoveToPoint( KFAIController AI, Vector NewMovePoint, optional Actor NewMoveFocus, optional float NewMoveOffset,
|
||
|
optional bool bIsValidCache,
|
||
|
optional bool bInCanPathfind = true,
|
||
|
optional bool bInAllowedToAttackDuringMove = true,
|
||
|
optional bool bInAllowPartialPath = true )
|
||
|
{
|
||
|
local AICommand_MoveToGoal Cmd;
|
||
|
|
||
|
if( AI != none && NewMovePoint != vect(0,0,0) )
|
||
|
{
|
||
|
Cmd = new(AI) class'AICommand_MoveToGoal';
|
||
|
if( Cmd != none )
|
||
|
{
|
||
|
AI.MoveFocus = NewMoveFocus;
|
||
|
AI.MoveOffset = NewMoveOffset;
|
||
|
AI.MoveGoal = none;
|
||
|
Cmd.MoveToActor = none;
|
||
|
|
||
|
Cmd.bValidRouteCache = bIsValidCache;
|
||
|
Cmd.bCanPathfind = bInCanPathfind;
|
||
|
Cmd.bAllowedToAttackDuringMove = bInAllowedToAttackDuringMove;
|
||
|
Cmd.bAllowPartialPath = bInAllowPartialPath;
|
||
|
|
||
|
AI.SetBasedPosition( AI.MovePosition, NewMovePoint );
|
||
|
|
||
|
AI.PushCommand( Cmd );
|
||
|
return true;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
function Pushed()
|
||
|
{
|
||
|
if( Steering != none )
|
||
|
{
|
||
|
// Steering.EnableDefaultAcceleration();
|
||
|
}
|
||
|
|
||
|
Super.Pushed();
|
||
|
|
||
|
StartingMovement();
|
||
|
bSavedIgnoreStepAside = bIgnoreStepAside;
|
||
|
// Reset flags in the controller
|
||
|
bMovingToGoal = true;
|
||
|
bReachedMoveGoal = false;
|
||
|
bReevaluatePath = false;
|
||
|
MoveTarget = none;
|
||
|
LastMovePoint = vect(0,0,0);
|
||
|
SetDestinationPosition( Pawn.Location, true );
|
||
|
ClearNonSkippableWayPoints();
|
||
|
// Start the move
|
||
|
GotoState( 'MovingToGoal' );
|
||
|
}
|
||
|
|
||
|
function Popped()
|
||
|
{
|
||
|
if( Steering != none )
|
||
|
{
|
||
|
Steering.DisableDefaultAcceleration();
|
||
|
}
|
||
|
Super.Popped();
|
||
|
|
||
|
bDirectMoveToGoal = false;
|
||
|
bMovingToGoal = false;
|
||
|
bReachedMoveGoal = (Status == 'Success');
|
||
|
MoveTarget = none;
|
||
|
Retries = 0;
|
||
|
bIgnoreStepAside = bSavedIgnoreStepAside;
|
||
|
LastNavGoalReached = none;
|
||
|
bReachedLatentMoveGoal = false;
|
||
|
|
||
|
StoppingMovement();
|
||
|
// Clear the route cache to make sure any intermediate claims are destroyed
|
||
|
RouteCache_Empty();
|
||
|
if( Pawn != none )
|
||
|
{
|
||
|
Pawn.ZeroMovementVariables(); //[Just Added 7/15/14]
|
||
|
Pawn.DestinationOffset = 0.f;
|
||
|
}
|
||
|
ClearTimer( nameOf(MoveToGoalTimedOut), self );
|
||
|
// Make sure that if we are bailing we notify the Controller we're done trying to move
|
||
|
ReachedMoveGoal();
|
||
|
}
|
||
|
|
||
|
function Paused( GameAICommand NewCommand )
|
||
|
{
|
||
|
local vector OldVelocity;
|
||
|
local bool bWasFalling;
|
||
|
|
||
|
Super.Paused( NewCommand );
|
||
|
Retries = 0;
|
||
|
// Pause move timeout timer
|
||
|
PauseTimer( true, nameOf(MoveToGoalTimedOut), self );
|
||
|
if( Steering != none )
|
||
|
{
|
||
|
// Steering.DisableDefaultAcceleration();
|
||
|
}
|
||
|
|
||
|
if( Pawn != none && Pawn.Physics == PHYS_Falling )
|
||
|
{
|
||
|
OldVelocity = Pawn.Velocity;
|
||
|
bWasFalling = true;
|
||
|
}
|
||
|
|
||
|
Pawn.ZeroMovementVariables();
|
||
|
|
||
|
// Set our old velocity if we zeroed our movement while falling (probably from being pushed by weapon physics)
|
||
|
if( Pawn != none && bWasFalling )
|
||
|
{
|
||
|
Pawn.Velocity = OldVelocity;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
function Resumed( Name OldCommandName )
|
||
|
{
|
||
|
`AILog( self$" Resumed (oldCommand: "$OldCommandName$")", 'Command_MoveToGoal' );
|
||
|
Retries = 0;
|
||
|
Super.Resumed( OldCommandName );
|
||
|
if( Steering != none )
|
||
|
{
|
||
|
// Steering.EnableDefaultAcceleration();
|
||
|
}
|
||
|
NumTimesGetNextMoveGoalReturnedSameNode=0;
|
||
|
Retries = 0;
|
||
|
|
||
|
if( ChildStatus == 'Success' || ( Enemy != none && MyKFPawn.IsPawnMovingAwayFromMe( Enemy ) && MyKFPawn.bIsSprinting ) )
|
||
|
{
|
||
|
// Unpause move timeout timer
|
||
|
PauseTimer( false, nameOf(MoveToGoalTimedOut), self );
|
||
|
|
||
|
bMovingToGoal = true;
|
||
|
bReachedMoveGoal = false;
|
||
|
if( OldCommandName != 'AICommand_Crawler_LeapToWall' ) // (Temp - move to crawler move to goal)
|
||
|
{
|
||
|
// reset intermediate move goal so GetMoveGoal doesn't freak out when the current goal hasn't changed
|
||
|
IntermediateMoveGoal = none;
|
||
|
//ForceSkipAheadUpdate();
|
||
|
}
|
||
|
// Testing
|
||
|
if( RouteCache.Length > 0 && Enemy != none ) //&& CanDirectlyReach(Enemy) )
|
||
|
{
|
||
|
bReevaluatePath = true;
|
||
|
ReEvaluatePath();
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
UpdateHistoryString( "Failure!" );
|
||
|
// See if this still happens after adding || check above
|
||
|
Status = 'Failure';
|
||
|
AbortCommand( self );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/*********************************************************************************************
|
||
|
* Movement and Pathfinding
|
||
|
********************************************************************************************* */
|
||
|
|
||
|
event bool EnemyIsSurrounded()
|
||
|
{
|
||
|
// local KFAIDirector KFAID;
|
||
|
// local KFPawn NewEnemy;
|
||
|
// local bool bReachable, bCanSee;
|
||
|
//
|
||
|
// bReachable = ActorReachable( Enemy );
|
||
|
// bCanSee = CanSee( Enemy );
|
||
|
// `AILog( MyKFPawn$" EnemyIsSurrounded() - Reachable: "$bReachable$" Dist: "$VSize(Enemy.Location - Location)$" LOS? "$bCanSee$" $bDirectMoveToGoal: "$bDirectMoveToGoal, 'PathWarning' );
|
||
|
// KFAID = KFGameInfo(WorldInfo.Game).GetAIDirector();
|
||
|
// if( KFAID != none )
|
||
|
// {
|
||
|
// NewEnemy = KFPawn( KFAID.FindEnemyFor( Outer ) );
|
||
|
// if( NewEnemy != none )
|
||
|
// {
|
||
|
// if( SetEnemy (NewEnemy ) )
|
||
|
// {
|
||
|
// `AIlog( MyKFPawn$" EnemyIsSurrounded() - Set new enemy to "$NewEnemy, 'PathWarning' );
|
||
|
// return true;
|
||
|
// }
|
||
|
// }
|
||
|
// }
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
function ReEvaluatePath()
|
||
|
{
|
||
|
local NavigationPoint BestAnchor;
|
||
|
local float Dist;
|
||
|
|
||
|
`AILog( GetFuncName()$"() bReEvaluatePath: "$bReevaluatePath, 'Command_MoveToGoal' );
|
||
|
WallHitCount = 0;
|
||
|
LastHitWall = none;
|
||
|
// TODO: Skip below if goal is directly reachable?
|
||
|
if( bReevaluatePath && Pawn != none && (MoveGoalIsValid() || MovePointIsValid()) )
|
||
|
{
|
||
|
`AILog( self$" "$GetFuncName()$"() ... Goal"@MoveGoal@"Anchor"@Pawn.Anchor, 'Command_MoveToGoal' );
|
||
|
RouteCache_Empty(); //[NEW]
|
||
|
if( !Pawn.ValidAnchor() && !bDirectMoveToGoal )
|
||
|
{
|
||
|
BestAnchor = Pawn.GetBestAnchor( Pawn, Pawn.Location, true, false, Dist );
|
||
|
`AILog( GetFuncName()$"- ValidAnchor() returned false, new anchor:"@BestAnchor, 'Command_MoveToGoal' );
|
||
|
if( BestAnchor == none )
|
||
|
{
|
||
|
// this is baaad, teleport to the nearest node
|
||
|
BestAnchor = class'NavigationPoint'.static.GetNearestNavToActor( MyKFPawn );
|
||
|
`AILog( "-dd teleport anchor:"@BestAnchor, 'Command_MoveToGoal' );
|
||
|
if( BestAnchor != none && BestAnchor.IsUsableAnchorFor(MyKFPawn) )
|
||
|
{
|
||
|
// Only teleport if the anchor is really usable
|
||
|
Pawn.SetLocation(BestAnchor.Location);
|
||
|
}
|
||
|
}
|
||
|
if( BestAnchor != none )
|
||
|
{
|
||
|
Pawn.SetAnchor(BestAnchor);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
RouteCache_Empty();
|
||
|
`AILog( GetFuncName()@"disabling bReevaluatePath and restarting the move", 'Command_MoveToGoal' );
|
||
|
bReevaluatePath = false;
|
||
|
bValidRouteCache = false;
|
||
|
|
||
|
// Restart the movement state
|
||
|
GotoState( 'MovingToGoal', 'Begin' );
|
||
|
}
|
||
|
else if( HasReachedMoveGoal() )
|
||
|
{
|
||
|
Status = 'Success';
|
||
|
PopCommand( self );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/** wrapper for Controller::FindPathToward() that sets the appopriate path search type first */
|
||
|
function Actor FindPathToward( Actor anActor )
|
||
|
{
|
||
|
local EPathSearchType OldSearchType;
|
||
|
local Actor Result;
|
||
|
local int i;
|
||
|
|
||
|
//if( PendingJumpSpot != none )
|
||
|
//{
|
||
|
// if( PendingJumpSpot.bNotifyWhenClear )
|
||
|
// {
|
||
|
// PendingJumpSpot.NotifyClear();
|
||
|
// PendingJumpSpot = none;
|
||
|
// }
|
||
|
//}
|
||
|
|
||
|
// if (!bCanPathfind)
|
||
|
// {
|
||
|
// `AILog( "Not allowed to pathfind - aborting move", 'Command_MoveToGoal' );
|
||
|
// //msg( "Not allowed to pathfind - aborting move" );
|
||
|
// UpdateHistoryString( "bPathFind was false, need to repath" );
|
||
|
// Status = 'Failure';
|
||
|
// AbortCommand(self);
|
||
|
// }
|
||
|
// else
|
||
|
// {
|
||
|
OldSearchType = Pawn.PathSearchType;
|
||
|
Pawn.PathSearchType = PST_Constraint;
|
||
|
`AILog( "Generating path toward "$anActor$" Partial allowed? "$bAllowPartialPath, 'Command_MoveToGoal' );
|
||
|
for( i = 0; i < BlockedPathList.Length; i++ )
|
||
|
{
|
||
|
if( (BlockedPathList[i].BlockedTime > 0.f) && `TimeSince(BlockedPathList[i].BlockedTime) > MaxBlockedPathDuration )
|
||
|
{
|
||
|
BlockedPathList.RemoveItem(BlockedPathList[i]);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
NavigationPoint( BlockedPathList[i].BlockedReachSpec.End.Actor ).bBlocked = true;
|
||
|
}
|
||
|
}
|
||
|
Result = GeneratePathTo( anActor,, bAllowPartialPath );
|
||
|
for( i = 0; i < BlockedPathList.Length; i++ )
|
||
|
{
|
||
|
NavigationPoint( BlockedPathList[i].BlockedReachSpec.End.Actor ).bBlocked = false;
|
||
|
}
|
||
|
|
||
|
Pawn.PathSearchType = OldSearchType;
|
||
|
// }
|
||
|
if( Result == none )
|
||
|
{
|
||
|
AIActionStatus = "Failed Path toward "$anActor;
|
||
|
CurrentMovementPhase = MOVEMENT_PHASE_TYPE_PATHNODE_FAILED_MOVE;
|
||
|
`AILog( "** FAILED TO BUILD PATH TO "$AnActor, 'PathWarning' );
|
||
|
if( Pawn.Anchor != none )
|
||
|
{
|
||
|
`RecordAIPathFailure( Outer, Pawn.Anchor, MoveGoal, "MoveToGoal" );
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
`RecordAIPathFailure( Outer, none, MoveGoal, "MoveToGoal" );
|
||
|
}
|
||
|
}
|
||
|
else if( bDebug_DrawPath )
|
||
|
{
|
||
|
KFDebug_DrawMyPath();
|
||
|
}
|
||
|
|
||
|
// if( BlockedPath != none )
|
||
|
// {
|
||
|
// BlockedPath.bBlocked = false;
|
||
|
// BlockedPath = none;
|
||
|
// }
|
||
|
return Result;
|
||
|
}
|
||
|
/** Wrapper for Controller::FindPathTo() that sets the appopriate path search type first */
|
||
|
function Actor FindPathTo(vector aPoint)
|
||
|
{
|
||
|
local EPathSearchType OldSearchType;
|
||
|
local Actor Result;
|
||
|
|
||
|
if( !bCanPathfind )
|
||
|
{
|
||
|
`AILog( "Not allowed to pathfind - aborting move", 'Command_MoveToGoal' );
|
||
|
Status = 'Failure';
|
||
|
UpdateHistoryString( "Failure- !bCanPathfind" );
|
||
|
AbortCommand( self );
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
OldSearchType = Pawn.PathSearchType;
|
||
|
Pawn.PathSearchType = PST_NewBestPathTo;
|
||
|
Result = Outer.FindPathTo(aPoint);
|
||
|
Pawn.PathSearchType = OldSearchType;
|
||
|
|
||
|
`AILog( "Finding path to location "$aPoint$" Partial allowed? "$bAllowPartialPath, 'Command_MoveToGoal' );
|
||
|
}
|
||
|
if( Result == none )
|
||
|
{
|
||
|
AIActionStatus = "Failed Path to "$aPoint;
|
||
|
}
|
||
|
// if( BlockedPath != none )
|
||
|
// {
|
||
|
// BlockedPath.bBlocked = false;
|
||
|
// BlockedPath = none;
|
||
|
// }
|
||
|
return Result;
|
||
|
}
|
||
|
|
||
|
function RouteCache_RemoveIndex( int Idx )
|
||
|
{
|
||
|
//`AILog( GetFuncName()@" -- Resetting skipahead index!", 'Command_MoveToGoal' );
|
||
|
SkipAheadCurrentTestingIndex = 0;
|
||
|
outer.RouteCache_RemoveIndex( Idx );
|
||
|
}
|
||
|
|
||
|
function MoveToGoalTimedOut()
|
||
|
{
|
||
|
`AILog( GetFuncName(), 'Command_MoveToGoal' );
|
||
|
AIActionStatus = "MoveToGoal TimedOut";
|
||
|
if( Enemy != none && Enemy.IsAliveAndWell() )
|
||
|
{
|
||
|
if( NumberOfZedsTargetingPawn(Enemy, true, 350.f ) > 2 && CanSee(Enemy) )
|
||
|
{
|
||
|
Status='Success';
|
||
|
PopCommand(self);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
`AILog( GetFuncName()@self$" Timing out, but since my enemy is still alive I'm assuming I should keep trying? Anchor: "$Pawn.Anchor$" MoveGoal: "$MoveGoal, 'Command_MoveToGoal' );
|
||
|
return;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
`AILog( GetFuncName()$"() Anchor: "$Pawn.Anchor$" MoveGoal: "$MoveGoal, 'Command_MoveToGoal' );
|
||
|
Pawn.SetAnchor( GetFallbackAnchor() );
|
||
|
AbortCommand( self );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/** Check if given goal is valid */
|
||
|
function bool MoveGoalIsValid( optional Actor Goal )
|
||
|
{
|
||
|
if( Goal == none )
|
||
|
{
|
||
|
Goal = MoveGoal;
|
||
|
}
|
||
|
return (Goal != none);
|
||
|
}
|
||
|
|
||
|
/** Check if given point is valid */
|
||
|
function bool MovePointIsValid( optional Vector Point )
|
||
|
{
|
||
|
if( Point == vect(0,0,0) )
|
||
|
{
|
||
|
Point = GetBasedPosition( MovePosition );
|
||
|
}
|
||
|
return (Point != vect(0,0,0));
|
||
|
}
|
||
|
|
||
|
/** Check if AI has successfully reached the given goal */
|
||
|
function bool HasReachedMoveGoal( optional Actor Goal )
|
||
|
{
|
||
|
local bool bReached;
|
||
|
local float TempDist;
|
||
|
local bool bEnemyCheck;
|
||
|
|
||
|
// Retries = 0;
|
||
|
if( Pawn == none )
|
||
|
{
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
if( KFPawn(Goal) != none && KFPawn(Goal) == Enemy )
|
||
|
{
|
||
|
`AILog( GetFuncName()$"() for ENEMY goal, current distance: "$VSize(Enemy.Location - Pawn.Location)$" MoveOffset: "$MoveOffset, 'Command_MoveToGoal' );
|
||
|
bEnemyCheck = true;
|
||
|
}
|
||
|
|
||
|
if( Goal == none )
|
||
|
{
|
||
|
Goal = MoveGoal;
|
||
|
}
|
||
|
|
||
|
Pawn.DestinationOffset = MoveOffset;
|
||
|
if( MoveGoalIsValid( Goal ) )
|
||
|
{
|
||
|
if( Pawn.Anchor == Goal )
|
||
|
{
|
||
|
return true;
|
||
|
}
|
||
|
if( DynamicAnchor(Pawn.Anchor) != none )
|
||
|
{
|
||
|
// don't allow dynamic anchors to be valid for reach tests
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
|
||
|
bReached = (bReachedLatentMoveGoal && (NavigationPoint(Goal) != none && LastNavGoalReached == NavigationPoint(Goal))) || Pawn.ReachedDestination( Goal );
|
||
|
`AIlog( self$" HasReachedMoveGoal for goal: "$Goal$" Dist: "$VSize(Goal.Location - pawn.Location)$" bReachedLatentMoveGoal: "$bReachedLatentMoveGoal$" LastNavGoalReached: "$LastNavGoalReached, 'Command_MoveToGoal' );
|
||
|
|
||
|
TempDist = VSize( Goal.Location - Pawn.Location );
|
||
|
if( !bReached )
|
||
|
{
|
||
|
`AILog( "NOT REACHED "$Goal$" (Dist: "$TempDist$")", 'Command_MoveToGoal' );
|
||
|
}
|
||
|
return bReached;
|
||
|
}
|
||
|
else if( GetBasedPosition( MovePosition ) != vect(0,0,0) )
|
||
|
{
|
||
|
if( bEnemyCheck )
|
||
|
{
|
||
|
`AILog( GetFuncName()$"() "$VSize(GetBasedPosition(MovePosition)-Pawn.Location)$" versus MoveOffset "$MoveOffset, 'Command_MoveToGoal' );
|
||
|
}
|
||
|
if( VSize(GetBasedPosition(MovePosition)-Pawn.Location) < MoveOffset )
|
||
|
{
|
||
|
if( bEnemyCheck )
|
||
|
{
|
||
|
`AILog( GetFuncName()$"() Returning TRUE, position < MoveOffset", 'Command_MoveToGoal' );
|
||
|
}
|
||
|
return true;
|
||
|
}
|
||
|
bReached = Pawn.ReachedPoint( GetBasedPosition(MovePosition), none );
|
||
|
if( bEnemyCheck )
|
||
|
{
|
||
|
`AILog( GetFuncName()$"() ReachedPoint reaturned "$bReached, 'Command_MoveToGoal' );
|
||
|
}
|
||
|
if( bReached )
|
||
|
{
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
return bReached;
|
||
|
}
|
||
|
if( bEnemyCheck )
|
||
|
{
|
||
|
`AILog( GetFuncName()$"() Returning false", 'Command_MoveToGoal' );
|
||
|
}
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
function bool MoveUnreachable( Vector AttemptedDest, Actor AttemptedTarget )
|
||
|
{
|
||
|
if( AttemptedTarget != none )
|
||
|
{
|
||
|
`RecordAIMoveFailure( Outer, Pawn.Location, Pawn.Rotation, AttemptedTarget, "2 MoveUnreachable to "$AttemptedTarget);
|
||
|
`AILog( self$" MoveUnreachable() while moving to goal! Target: "$AttemptedTarget$" Dest: "$AttemptedDest, 'Command_MoveToGoal' );
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
`RecordAIMoveFailure( Outer, Pawn.Location, Pawn.Rotation, MoveTarget, "3 MoveUnreachable to "$MoveTarget);
|
||
|
`AILog( self$" MoveUnreachable() while moving to goal! Dest: "$AttemptedDest, 'Command_MoveToGoal' );
|
||
|
}
|
||
|
UpdateHistoryString( "[F] MoveUnreachable "$Pawn.Location );
|
||
|
Status = 'Failure';
|
||
|
if( AttemptedTarget == none )
|
||
|
{
|
||
|
AIActionStatus = "Move Unreachable: "$AttemptedDest;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
AIActionStatus = "Move Unreachable: "$AttemptedTarget;
|
||
|
}
|
||
|
bValidRouteCache = false;
|
||
|
bReevaluatePath = true;
|
||
|
NotifyNeedRepath();
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
/** Called from movetogoal when we arrive at our destination */
|
||
|
function ReachedMoveGoal()
|
||
|
{
|
||
|
if( MoveGoal != none && Pawn != none )
|
||
|
{
|
||
|
AIActionStatus = "Reached MoveGoal Dist: "$VSize( MoveGoal.Location - Pawn.Location )$" MoveGoal: "$MoveGoal;
|
||
|
`AILog( self$" Reached MoveGoal: "$MoveGoal$" Dist: "$VSize( MoveGoal.Location - Pawn.Location ), 'Command_MoveToGoal' );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
function ReachedIntermediateMoveGoal()
|
||
|
{
|
||
|
local KFDoorMarker DM;
|
||
|
|
||
|
if( !bUseNavMesh )
|
||
|
{
|
||
|
//Retries = 0;
|
||
|
AIActionStatus = "Reached Intermediate Goal "$IntermediateMoveGoal;
|
||
|
`AILog( self$" ReachedIntermediateMoveGoal: "$IntermediateMoveGoal, 'PathWarning' );
|
||
|
|
||
|
DM = KFDoorMarker(IntermediateMoveGoal);
|
||
|
|
||
|
if( DM != none && DM.MyKFDoor != none && !DM.MyKFDoor.IsCompletelyOpen() )
|
||
|
{
|
||
|
// Allow pawn to handle door bump events first
|
||
|
if( MyKFPawn.HandleAIDoorBump(DM.MyKFDoor) )
|
||
|
{
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
`AILog( GetFuncName()$" Reached IntermediateMoveGoal DoorMarker "$DM$" for closed door Dist: "$VSize(DM.Location - Pawn.Location), 'Doors' );
|
||
|
if( DM.MyKFDoor.WeldIntegrity <= 0 )
|
||
|
{
|
||
|
AIActionStatus = "Waiting for "$DM.MyKFDoor$" to open";
|
||
|
`AILog( GetFuncName()$" Calling WaitForDoor and UseDoor", 'Doors' );
|
||
|
// Stop, open the door, and wait for notification from it once it's open.
|
||
|
WaitForDoor( DM.MyKFDoor );
|
||
|
DM.MyKFDoor.UseDoor( Pawn );
|
||
|
return;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
`AILog( GetFuncName()$" calling NotifyAttackDoor for "$DM.MyKFDoor$" with weld integrity "$DM.MyKFDoor.WeldIntegrity, 'Doors' );
|
||
|
AIActionStatus = "Wants to attack door "$DM.MyKFDoor;
|
||
|
NotifyAttackDoor( DM.MyKFDoor );
|
||
|
return;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// See if there's a better enemy blocking the path to our current one
|
||
|
CheckForIntermediateEnemies();
|
||
|
|
||
|
// Tell the AI controller we've arrived
|
||
|
NotifyReachedLatentMoveGoal();
|
||
|
}
|
||
|
|
||
|
/** Attempts to find a better enemy than our current one */
|
||
|
function CheckForIntermediateEnemies()
|
||
|
{
|
||
|
local Pawn EnemyBlocker;
|
||
|
|
||
|
// See if we can find a better enemy
|
||
|
if( MyKFPawn != none && Enemy != none && !MyKFPawn.IsDoingSpecialMove() && VSizeSQ(Enemy.Location - MyKFPawn.Location) > 490000.f )
|
||
|
{
|
||
|
EnemyBlocker = GetPawnBlockingPathTo( Enemy, true, true );
|
||
|
if( EnemyBlocker != none && EnemyBlocker.CanAITargetThisPawn(Outer) )
|
||
|
{
|
||
|
ChangeEnemy( EnemyBlocker );
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/** Called when blocked by bBlocksNavigation actors (see ShouldIgnoreNavigationBlockingFor) */
|
||
|
function bool HandlePathObstruction( Actor BlockedBy )
|
||
|
{
|
||
|
local Pawn BlockPawn;
|
||
|
|
||
|
AIActionStatus = "Path obstructed by "$BlockedBy;
|
||
|
`AILog( GetFuncName()$" - Blocked by : "$BlockedBy$" Time since previous obstruction: "$`TimeSince(LastObstructionTime), 'HandlePathObstruction' );
|
||
|
|
||
|
LastObstructionTime = WorldInfo.TimeSeconds;
|
||
|
BlockPawn = Pawn(BlockedBy);
|
||
|
if( BlockPawn != none )
|
||
|
{
|
||
|
`AILog("- blocked anchor:"@BlockPawn.Anchor@BlockPawn.ReachedDestination(MoveGoal));
|
||
|
|
||
|
// if they're touching our goal
|
||
|
if( BlockPawn.ReachedDestination( MoveGoal ) )
|
||
|
{
|
||
|
`AILog( "- they're touching my goal, finishing the move", 'HandlePathObstruction' );
|
||
|
|
||
|
// then just pretend we made it
|
||
|
bReachedMoveGoal = true;
|
||
|
|
||
|
|
||
|
Status = 'Success';
|
||
|
PopCommand( self );
|
||
|
return true;
|
||
|
}
|
||
|
}
|
||
|
else if( KFDestructibleActor(BlockedBy) != none )
|
||
|
{
|
||
|
bValidRouteCache = false;
|
||
|
bReevaluatePath = true;
|
||
|
StopAllLatentMovement();
|
||
|
`AILog( GetFuncName()$"() calling ZeroMovementVariables", 'HandlePathObstruction' );
|
||
|
Pawn.ZeroMovementVariables();
|
||
|
|
||
|
if( NavigationPoint(IntermediateMoveGoal) != none )
|
||
|
{
|
||
|
// Path is failing - if I just ran into a non-pawn obstruction, temporarily block IntermediateMoveGoal to let me
|
||
|
// generate another path that doesn't require it.
|
||
|
if( CreateTemporaryBlockedPath(NavigationPoint(IntermediateMoveGoal)) )
|
||
|
{
|
||
|
//`RecordAIRedirectedPath( Outer, IntermediateMoveGoal, "[HPO]Path:"$IntermediateMoveGoal$" and "$RouteCache[1] );
|
||
|
}
|
||
|
}
|
||
|
Obstruction_Repath( BlockedBy );
|
||
|
|
||
|
NotifyNeedRepath();
|
||
|
return false;
|
||
|
}
|
||
|
Obstruction_Fail( BlockedBy );
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
function Obstruction_Repath( actor Obstruction )
|
||
|
{
|
||
|
`RecordAIPathObstruction( `StatID(AI_PATHOBSTRUCTION_REPATH), Outer, Obstruction, "MoveTarget:"$MoveTarget );
|
||
|
}
|
||
|
|
||
|
function Obstruction_Fail( actor Obstruction )
|
||
|
{
|
||
|
`RecordAIPathObstruction( `StatID(AI_PATHOBSTRUCTION_FAIL), Outer, Obstruction, "MoveTarget:"$MoveTarget );
|
||
|
}
|
||
|
|
||
|
/** Called after the type of move has been determined. */
|
||
|
function StartingMove( bool bDirectMove, float Distance, int PathLength, Actor Dest)
|
||
|
{
|
||
|
if( MyKFPawn.IsDoingMeleeAttack() )
|
||
|
{
|
||
|
StopAllLatentMovement();
|
||
|
GotoState( 'MovingToGoal', 'ReachedGoal' );
|
||
|
return;
|
||
|
}
|
||
|
if( (Enemy != none && Dest != none && Enemy == Dest) )
|
||
|
{
|
||
|
if( VSize( Dest.Location - Pawn.Location ) <= Pawn.MeleeRange ) // TODO: Use MyKFPawn.PawnAnimInfo.DesiredMeleeRange() instead?
|
||
|
{
|
||
|
StopAllLatentMovement();
|
||
|
GotoState( 'MovingToGoal', 'ReachedGoal' );
|
||
|
return;
|
||
|
}
|
||
|
}
|
||
|
if( bDirectMove )
|
||
|
{
|
||
|
`AILog( self$" Starting *DIRECT* move, Distance: "$Distance$" PathLength: "$PathLength$" Dest: "$Dest, 'Command_MoveToGoal' );
|
||
|
bDirectMoveToGoal = true;
|
||
|
RouteCache_Empty();
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
`AILog( self$" Starting *PATHFINDING* move, Distance: "$Distance$" PathLength: "$PathLength$" Dest: "$Dest, 'Command_MoveToGoal' );
|
||
|
bDirectMoveToGoal = false;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/*********************************************************************************************
|
||
|
* Combat related
|
||
|
********************************************************************************************* */
|
||
|
|
||
|
function bool NotifyPlayerBecameVisible( Pawn VisiblePlayer )
|
||
|
{
|
||
|
`AILog( GetFuncName()$" "$VisiblePlayer, 'SeePlayer' );
|
||
|
|
||
|
if( Enemy != none && bMovingToGoal && !bDirectMoveToGoal && InLatentExecution( LATENT_MOVETOWARD )) //&& (VisiblePlayer == MoveGoal || VisiblePlayer == IntermediateMoveGoal) )
|
||
|
{
|
||
|
// This player happens to be my next movetarget, so break off of path if directly reachable
|
||
|
if( VisiblePlayer == MoveGoal || VisiblePlayer == IntermediateMoveGoal )
|
||
|
{
|
||
|
if( ActorReachable( VisiblePlayer) )
|
||
|
{
|
||
|
`AILog( GetFuncName()$" My MoveGoal ("$MoveGoal$") is visible and reachable - calling stop latent execution and re-evaluate path", 'Move_DirectPath' );
|
||
|
bReEvaluatePath = true;
|
||
|
bDirectMoveToGoal = true;
|
||
|
SetDestinationPosition( Enemy.Location );
|
||
|
ReEvaluatePath();
|
||
|
StopLatentExecution();
|
||
|
return true;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
// final function ForceSkipAheadUpdate()
|
||
|
// {
|
||
|
// //`AILog( GetFuncName()$"() Pawn Location: "$Pawn.Location$" SkipAheadLastUpdatePos: "$SkipAheadLastUpdatePos.Position$" "$Pawn.Location + vect(1.f,0,0) * 2.f * SkipAheadUpdateThreshold, 'Command_MoveToGoal' );
|
||
|
//
|
||
|
// //SetBasedPosition( SkipAheadLastUpdatePos, Pawn.Location + vect(1.f,0,0) * 2.0f * SkipAheadUpdateThreshold );
|
||
|
// //DrawDebugSphere( GetBasedPosition(SkipAheadLastupdatepos), 32, 8, 0, 255, 0, TRUE );
|
||
|
// }
|
||
|
|
||
|
/*********************************************************************************************
|
||
|
* Debugging
|
||
|
********************************************************************************************* */
|
||
|
|
||
|
/** Build debug string */
|
||
|
event String GetDumpString()
|
||
|
{
|
||
|
if( GetBasedPosition( MovePosition ) != vect(0,0,0) )
|
||
|
{
|
||
|
return Super.GetDumpString()@"Point:"@GetBasedPosition(MovePosition)@"MoveFocus:"@MoveFocus@"Offset:"@MoveOffset@"IMG:"@IntermediateMoveGoal@"Focus:"@Focus@"MoveTarget:"@MoveTarget@"MySpeed:"@VSize(Pawn.Velocity);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
return Super.GetDumpString()@"Destination:"@MoveGoal@"MoveFocus:"@MoveFocus@"Offset:"@MoveOffset@"IMG:"@IntermediateMoveGoal@"Focus:"@Focus@"MoveTarget:"@MoveTarget@"MySpeed:"@VSize(Pawn.Velocity);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/** Notification that I should stop moving and build a new path to my goal */
|
||
|
function NotifyNeedRepath()
|
||
|
{
|
||
|
`AILog( GetFuncName()$"() MoveTarget: "$(MoveTarget!=none?string(MoveTarget):"none")$" Dist: "$(MoveTarget!=none?VSize(MoveTarget.Location-Pawn.Location):0.f), 'PathWarning' );
|
||
|
bReEvaluatePath = true;
|
||
|
StopLatentExecution();
|
||
|
ReEvaluatePath();
|
||
|
}
|
||
|
|
||
|
function CheckForStuckPath()
|
||
|
{
|
||
|
local NavigationPoint NavPoint;
|
||
|
|
||
|
// Failed to find a valid next move target
|
||
|
if( IntermediateMoveGoal == none || (NumTimesGetNextMoveGoalReturnedSameNode > 20 && VSizeSQ(MyKFPawn.Velocity) < 600.f) )
|
||
|
{
|
||
|
`AILog("Failed to get valid movetarget, Got Same result "$NumTimesGetNextMoveGoalReturnedSameNode$" time(s). has reached move goal? "@HasReachedMoveGoal(), 'PathWarning');
|
||
|
//`RecordAIGetNextMoveGoalFailure( Outer,Pawn.Location,Pawn.Rotation,MoveGoal,"GNML Fail, Int. = "$IntermediateMoveGoal );
|
||
|
|
||
|
bValidRouteCache = false;
|
||
|
bReevaluatePath = true;
|
||
|
NumTimesGetNextMoveGoalReturnedSameNode = 0;
|
||
|
|
||
|
// Path is failing - if I just ran into a non-pawn obstruction, temporarily block IntermediateMoveGoal to let me
|
||
|
// generate another path that doesn't require it.
|
||
|
if( Pawn.Anchor != none && (LastHitWall != none && !LastHitWall.IsA('Pawn')) )
|
||
|
{
|
||
|
NavPoint = NavigationPoint( IntermediateMoveGoal );
|
||
|
if( CurrentPath != none && CreateTemporaryBlockedReach(NavPoint, CurrentPath) )
|
||
|
{
|
||
|
//`RecordAIRedirectedPath( Outer, IntermediateMoveGoal, "[HPO]Path:"$IntermediateMoveGoal$" and "$RouteCache[1] );
|
||
|
}
|
||
|
else if( NavPoint != none && CreateTemporaryBlockedPath(NavPoint) )
|
||
|
{
|
||
|
//`RecordAIGetNextMoveGoalFailure( Outer,Pawn.Location,Pawn.Rotation,MoveGoal,"GNML Fail, Int. = "$IntermediateMoveGoal );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
UpdateHistoryString( "[F] [GNM] "$IntermediateMoveGoal$" "$Pawn.Location );
|
||
|
GotoState( 'MovingToGoal', 'FailedMove' );
|
||
|
IntermediateMoveGoal = none;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
`AILog( "NextMoveTarget"@IntermediateMoveGoal@"MoveGoal:"@MoveGoal, 'Move_Path' );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/*********************************************************************************************
|
||
|
* Base movement state
|
||
|
********************************************************************************************* */
|
||
|
state MovingToGoal
|
||
|
{
|
||
|
function bool NotifyHitWall( vector HitNormal, actor Wall )
|
||
|
{
|
||
|
local KFDoorActor Door;
|
||
|
|
||
|
DisableNotifyHitWall( 0.2f );
|
||
|
`AILog( "NotifyHitWall() while in MoveToGoal, HitNormal: "$HitNormal$" Wall: "$Wall$" LastHitWall: "$LastHitWall$" WallHitCount: "$WallHitCount$" MoveTarget: "$MoveTarget, 'PathWarning' );
|
||
|
AIActionStatus = "Received NotifyHitWall event";
|
||
|
|
||
|
if( !Wall.bStatic )
|
||
|
{
|
||
|
Door = KFDoorActor( Wall );
|
||
|
if( Door == none )
|
||
|
{
|
||
|
`AILog( GetFuncName()$"() Wall: "$Wall$" HitNormal: "$HitNormal, 'HitWall' );
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// Allow pawn to handle door bump events first
|
||
|
if( MyKFPawn.HandleAIDoorBump(Door) )
|
||
|
{
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
if( Door.WeldIntegrity <= 0 && KFDoorMarker(Door.MyMarker) != none && !Door.IsCompletelyOpen() )
|
||
|
{
|
||
|
DisableNotifyHitWall(0.25f);
|
||
|
WaitForDoor( Door );
|
||
|
`AILog( "NotifyHitWall() while in MoveToGoal, Wall: "$Wall$" Using door and waiting for it to open", 'Doors' );
|
||
|
Door.UseDoor( Pawn );
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
// NOTE: Unless returning true, if the Wall is a closed door, SuggestMovePreparation event will be called on the associated KFDoorMarker
|
||
|
`AILog( GetFuncName()$"() Wall: "$Wall$" HitNormal: "$HitNormal$" ran into a door!", 'Doors' );
|
||
|
if( !Door.IsCompletelyOpen() && Door.WeldIntegrity > 0 && (Pawn.Anchor == Door.MyMarker || (DoorEnemy != none && (DoorEnemy == Door || PendingDoor == Door))) )
|
||
|
{
|
||
|
DisableNotifyHitWall(0.25f);
|
||
|
`AILog( GetFuncName()$"() calling NotifyAttackDoor for "$Wall, 'Doors' );
|
||
|
NotifyAttackDoor( Door );
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
}
|
||
|
if( Pawn.Physics == PHYS_Falling )
|
||
|
{
|
||
|
return true;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if( LastHitWall != none && Wall != LastHitWall )
|
||
|
{
|
||
|
LastHitWall = none;
|
||
|
WallHitCount = 0;
|
||
|
}
|
||
|
LastHitWall = Wall;
|
||
|
//WallHitCount++; // TODO: ENABLE
|
||
|
if( WallHitCount > 26 ) //26
|
||
|
{
|
||
|
if( IntermediateMoveGoal.IsA( 'KFPathnode' ) )
|
||
|
{
|
||
|
//UNCOMMENT //NumTimesGetNextMoveGoalReturnedSameNode = 0; // new
|
||
|
`AILog( GetFuncName()$"() Wall: "$Wall$" WallHitCount: "$WallHitCount$" Hit Wall > 3 times, setting "$IntermediateMoveGoal$" to be a blocked path and forcing a repath!", 'PathWarning' );
|
||
|
WallHitCount++;
|
||
|
LastHitWall = Wall;
|
||
|
`RecordAIWall( `StatID(AI_FAILEDADJUSTFROMWALL), outer, Pawn.Location, Pawn.Rotation, Wall, "SuperSpeed: "$MyKFPawn.IsUsingSuperSpeed() );
|
||
|
ForcePauseAndRepath(Wall);
|
||
|
WallHitCount = 0;
|
||
|
DisableNotifyHitWall(3.f);
|
||
|
return true;
|
||
|
}
|
||
|
}
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
function bool ShouldDelayMove()
|
||
|
{
|
||
|
//local NavigationPoint Nav;
|
||
|
//local bool bResult;
|
||
|
//local KFAIDirector KFAID;
|
||
|
//
|
||
|
if( bPreparingMove || MyKFPawn.IsDoingMeleeAttack() )
|
||
|
{
|
||
|
bGoalSurrounded = false;
|
||
|
`AILog( "ShouldDelayMove returning TRUE because bPreparingMove", 'Command_MoveToGoal' );
|
||
|
AIActionStatus = "Delaying Move";
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
// if( KFPawn(Enemy) != none && DoorEnemy == none && (Pawn.Anchor == none || !Pawn.Anchor.IsA('KFDoorActor')) )
|
||
|
// {
|
||
|
// if( KFPawn(Enemy).IsSurrounded( false, 4, 350.f ) && VSize(Enemy.Location - Pawn.Location) < 700.f )
|
||
|
// {
|
||
|
// `AILog( "Delaying move because "$Enemy$" is currently surrounded! Reachable? "$ActorReachable(Enemy), 'PathWarning' );
|
||
|
// if( WorldInfo.Game.GetNumPlayers() > 1 )
|
||
|
// {
|
||
|
// KFAID = KFGameInfo( WorldInfo.Game ).GetAIDirector();
|
||
|
// KFAID.FindEnemyFor( Outer, true );
|
||
|
// }
|
||
|
// AIActionStatus = "Delaying move because enemy surrounded";
|
||
|
// bGoalSurrounded = true;
|
||
|
// return true;
|
||
|
// }
|
||
|
// }
|
||
|
// bGoalSurrounded = false;
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
/** Notification from controller that my enemy has changed (Enemy = my new enemy, OldEnemy = previous) */
|
||
|
function NotifyEnemyChanged( optional Pawn OldEnemy )
|
||
|
{
|
||
|
// Notify child command first.
|
||
|
if( CachedChildCommand != none )
|
||
|
{
|
||
|
CachedChildCommand.NotifyEnemyChanged( OldEnemy );
|
||
|
}
|
||
|
|
||
|
// If I'm pathfinding to my previous enemy, change my movegoal to the new enemy and build a new path
|
||
|
if( MoveGoal != none && OldEnemy == MoveGoal || (IntermediateMoveGoal == none && IntermediateMoveGoal == OldEnemy) )
|
||
|
{
|
||
|
`AILog( GetFuncName()$"() Stopping latent execution, resetting destination position to new enemy location, and calling ReEvaluatePath()", 'SetEnemy' );
|
||
|
MoveGoal = Enemy;
|
||
|
StopLatentExecution();
|
||
|
SetDestinationPosition( Enemy.Location );
|
||
|
ReEvaluatePath();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
function NotifyDoorOpened()
|
||
|
{
|
||
|
// Notify child command first.
|
||
|
if( CachedChildCommand != none )
|
||
|
{
|
||
|
CachedChildCommand.NotifyDoorOpened();
|
||
|
}
|
||
|
|
||
|
// Only allow movement if we're not in a special move that disables movement
|
||
|
if( !MyKFPawn.IsDoingSpecialMove() || !MyKFPawn.SpecialMoves[MyKFPawn.SpecialMove].bDisableMovement )
|
||
|
{
|
||
|
bPreparingMove = false;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/** Checks to see if my pawn can directly move to my enemy (directly reachable), rather than pathfinding */
|
||
|
function FindDirectPath()
|
||
|
{
|
||
|
if( Enemy != none && !bDirectMoveToGoal && Pawn.Physics != PHYS_Spider )
|
||
|
{
|
||
|
`AILog( GetFuncName()$" event received, checking for direct reachability to enemy "$Enemy, 'Move_DirectPath' );
|
||
|
// TODO: Look into not requiring enemy in this check, changing to MoveGoal should be safe
|
||
|
if( InLatentExecution( LATENT_MOVETOWARD ) && MyKFPawn.Physics != PHYS_Falling /*&& MoveGoal == Enemy && Outer.MoveTarget != none*/ && ActorReachable( Enemy ) )
|
||
|
{
|
||
|
`AILog( GetFuncName()$" can directly reach enemy, stopping and resetting MoveTimer and resetting my move", 'Move_DirectPath' );
|
||
|
// Stop pathfinding and move directly to my goal
|
||
|
bDirectMoveToGoal = true;
|
||
|
bReEvaluatePath = true;
|
||
|
StopLatentExecution();
|
||
|
SetDestinationPosition( Enemy.Location );
|
||
|
ReEvaluatePath();
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/** Returns the next valid MoveTarget (usually a NavigationPoint) in my path */
|
||
|
final protected function bool GetNextMoveTarget( out Actor CurrentMoveTarget )
|
||
|
{
|
||
|
local bool bDone, bReachedDynamicAnchor;
|
||
|
local Actor OldMoveTarget;
|
||
|
|
||
|
`AILog(GetFuncName()@"current movetarget:"@CurrentMoveTarget@"routecache length:"@RouteCache.Length@"anchor"@Pawn.Anchor, 'Command_MoveToGoal');
|
||
|
bGoalChangedDueToSkipAhead = false;
|
||
|
OldMoveTarget = CurrentMoveTarget;
|
||
|
while( !bDone && RouteCache.Length > 0 && RouteCache[0] != none )
|
||
|
{
|
||
|
/*
|
||
|
[7/6/14]
|
||
|
Need to check the date of this comment in source control history to see how long this code has been in use, but it's been awhile and I haven't
|
||
|
seen the issue since that time. Prior to adding "LastNavGoalReached" related code, the most frequent movement failure was caused by this function
|
||
|
returning the same MoveTarget repeatedly in some situations. The cause is detailed in the old comment below. It might be worth another look to
|
||
|
make sure te ReachedDestination() result conflict described below isn't causing any other problems. The fix seems to have worked, however it's
|
||
|
not fixing the root of the problem. I know there was/is an almost identical NavMesh issue with GetNextMoveLocation() returning the same destination
|
||
|
repeatedly, if so it's either coincidental or the cause is probably in whatever reached checks are shared by the 2 systems.
|
||
|
https://udn.unrealengine.com/questions/120379/navmesh-path-advancement-issues.html
|
||
|
--------------------
|
||
|
APawn::MoveToward (used by Crawler) and most likely KFPawn::MoveToward (used by non-crawlers) can sometimes consider a nav destination reached,
|
||
|
while GetNextMoveTarget() considers it not reached. MoveToward calls the overloaded ReachedDestination, while script calls the native function which
|
||
|
only accepts an actor rather than a position. When this happens, they are using slightly different destination positions to check for. Somewhere along
|
||
|
the way from the start of the move, Dest becomes modified (probably from a call to SetDestinationPosition()) and used in MoveToward's ReachDestination()
|
||
|
check while GetNextMoveTarget() most likely uses the node's location. Whatever the case, the 2 vectors are different and lead to MoveToward()
|
||
|
considering the IntermediateMoveGoal to be reached, and GetNextMoveTarget() considering it not reached and attempting to use it again - then, 5 attempts later,
|
||
|
the command fails. Need to make sure that MoveToward is really correct. So far it has been for the Crawler, but the Crawler's not a typical Pawn.
|
||
|
|
||
|
Search for bReachedLatentMoveGoal and LastNavGoalReached for the code involved in fixing this.
|
||
|
|
||
|
Good way to reproduce in KF-BurningParis:
|
||
|
BugItGo -3518.6257 2585.0835 -551.9000 0 -213344 0
|
||
|
SpawnDebugAI Crawler
|
||
|
AIPathTo MetroTop3 (currently KFPathNode_298 - the problem occurs when Crawler is moving up wall to KFPathnode_267.. right before transition to ceiling, crawler stops due to
|
||
|
GetNextMoveTarget() failure.
|
||
|
*/
|
||
|
if( (bReachedLatentMoveGoal && LastNavGoalReached == RouteCache[0]) || Pawn.ReachedDestination( RouteCache[0] ) )
|
||
|
{
|
||
|
bReachedLatentMoveGoal = false;
|
||
|
`AILog( "Pawn has Reached route cache 0:"@RouteCache[0]$" DIST: "$VSize(RouteCache[0].Location - Pawn.Location), 'Command_MoveToGoal' );
|
||
|
// If our move goal has been reached
|
||
|
if( RouteCache[0] == MoveGoal )
|
||
|
{
|
||
|
// Clear move target and exit
|
||
|
bDone = true;
|
||
|
CurrentMoveTarget = none;
|
||
|
}
|
||
|
|
||
|
// MAKE SURE ANCHOR IS UPDATED -- this is cause of NO CURRENT PATH bug
|
||
|
`AILog( "Setting anchor to RouteCache 0 ("$RouteCache[0]$")", 'Command_MoveToGoal' );
|
||
|
Pawn.SetAnchor( RouteCache[0] );
|
||
|
// If we reached a dynamic anchor, return true so we don't abort the move
|
||
|
bReachedDynamicAnchor = ( DynamicAnchor(RouteCache[0]) != none );
|
||
|
`AILog( "Remove from route:"@RouteCache[0]@bReachedDynamicAnchor, 'Command_MoveToGoal' );
|
||
|
RouteCache_RemoveIndex( 0 );
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
`AILog( "I Did NOT reach route cache 0:"@RouteCache[0]$" Dist: "$VSize(RouteCache[0].Location - Pawn.Location), 'Command_MoveToGoal' );
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if( !bDone )
|
||
|
{
|
||
|
LastNavGoalReached = none;
|
||
|
bReachedLatentMoveGoal = false;
|
||
|
|
||
|
if( RouteCache.Length > 0 )
|
||
|
{
|
||
|
CurrentMoveTarget = RouteCache[0];
|
||
|
`AILog( GetFuncName()$" setting CurrentMoveTarget to "$CurrentMoveTarget$" (RouteCache[0] ) OldMoveTarget was "$OldMoveTarget, 'Command_MoveToGoal' );
|
||
|
}
|
||
|
// Otherwise, if not moving directly to movegoal and movegoal is not a nav point, try moving directly to it
|
||
|
else if( MoveGoal != none && CurrentMoveTarget != MoveGoal && NavigationPoint(MoveGoal) == none && ActorReachable(MoveGoal) )
|
||
|
{
|
||
|
CurrentMoveTarget = MoveGoal;
|
||
|
`AILog( GetFuncName()$" Set CurrentMoveTarget to MoveGoal "$MoveGoal$" because move goal is now directly reachable. OldMoveTarget was "$OldMoveTarget, 'Command_MoveToGoal' );
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
`AILog( GetFuncName()$" setting CurrentMoveTarget to none!", 'Command_MoveToGoal' );
|
||
|
CurrentMoveTarget = none;
|
||
|
}
|
||
|
}
|
||
|
return bReachedDynamicAnchor || (OldMoveTarget != CurrentMoveTarget);
|
||
|
}
|
||
|
|
||
|
/** True if my pawn should rotate to face position */
|
||
|
function bool ShouldTurnToGoal( vector TurnToLocation )
|
||
|
{
|
||
|
if( Pawn == none || !Pawn.IsAliveAndWell() )
|
||
|
{
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
return Pawn.NeedToTurn( TurnToLocation );
|
||
|
}
|
||
|
|
||
|
function bool CanDirectlyReach( actor Goal )
|
||
|
{
|
||
|
return ActorReachable( Goal );
|
||
|
}
|
||
|
|
||
|
function SetBestAnchor()
|
||
|
{
|
||
|
// local NavigationPoint BestAnchor;
|
||
|
// local float Dist;
|
||
|
//
|
||
|
// BestAnchor = Pawn.GetBestAnchor( Pawn, Pawn.Location, true, false, Dist );
|
||
|
// if( BestAnchor != none )
|
||
|
// {
|
||
|
// Pawn.SetAnchor( BestAnchor );
|
||
|
// }
|
||
|
}
|
||
|
|
||
|
/** Execution is often sent to CheckMove label to confirm goal is reached and finish command if so */
|
||
|
CheckMove:
|
||
|
ClearTimer( 'MoveToGoalTimedOut', self );
|
||
|
if( HasReachedMoveGoal() )
|
||
|
{
|
||
|
`AILog( "CheckMove label, I've reached my goal ", 'Command_MoveToGoal' );
|
||
|
Goto( 'ReachedGoal' );
|
||
|
}
|
||
|
|
||
|
/** Initial label when state begins */
|
||
|
Begin:
|
||
|
Sleep(0.f);
|
||
|
CurrentMovementPhase = MOVEMENT_PHASE_TYPE_NONE;
|
||
|
|
||
|
if( MyKFPawn.Physics == PHYS_Falling )
|
||
|
{
|
||
|
/** My Pawn is falling, so stop executing state code until the pawn lands */
|
||
|
WaitForLanding();
|
||
|
}
|
||
|
/** Reset for upcoming move */
|
||
|
bReachedLatentMoveGoal = false;
|
||
|
if( IsTimerActive(nameof(Timer_DelayMoveTimeOut), self) )
|
||
|
{
|
||
|
ClearTimer( nameof(Timer_DelayMoveTimeOut), self );
|
||
|
}
|
||
|
/** Abort move commands if Pawn's invalid or dead */
|
||
|
if( Pawn == none || Pawn.Health <= 0 )
|
||
|
{
|
||
|
Status = 'Aborted';
|
||
|
//AbortCommand();
|
||
|
AbortMovementCommands( true, "Dead!" );
|
||
|
AbortMovementPlugIns(true, "Dead" );
|
||
|
/* Stop executing any more state code */
|
||
|
Stop;
|
||
|
}
|
||
|
|
||
|
`AILog( "Attempting move to goal "$MoveGoal$" Valid? "$MoveGoalIsValid()$" MovePosition: "$MovePosition.Position$" BasedPosition: "$GetBasedPosition( MovePosition )$" MovePointIsValid? "$MovePointIsvalid()$" bValidRouteCache? "$bValidRouteCache, 'Command_MoveToGoal' );
|
||
|
if( MoveGoalIsValid() )
|
||
|
{
|
||
|
/*********************************************************************************************
|
||
|
* Moving toward an Actor (command was started with MoveToGoal())
|
||
|
********************************************************************************************* */
|
||
|
if( !bValidRouteCache && CanDirectlyReach(MoveGoal) && (!Pawn.bCrawler || (Abs(Pawn.Location.Z - MoveGoal.Location.Z) < 700.f)) ) // 6.6.14 Move crawler check to CanDirectlyReach()
|
||
|
{
|
||
|
DirectMoveToActor:
|
||
|
CurrentMovementPhase = MOVEMENT_PHASE_TYPE_EMT_LOS_MOVE_TOWARDS;
|
||
|
AIActionStatus = "Moving Direct Path to "$MoveGoal;
|
||
|
// MoveGoal is directly reachable, so start moving.
|
||
|
StartingMove( true, VSize(Pawn.Location - MoveGoal.Location), 0, MoveGoal );
|
||
|
// Estimate a reasonable TimeOut based on move distance
|
||
|
TimeOutTime = GetMoveTimeOutDuration( MoveGoal.GetDestination(outer), false );
|
||
|
SetTimer( TimeOutTime, false, nameof(self.MoveToGoalTimedOut), self );
|
||
|
|
||
|
// This while loop helps catch cases interruption when a state is pushed
|
||
|
LoopFailSafeCounter = 0;
|
||
|
do
|
||
|
{
|
||
|
`AILog( "Moving directly to move goal:"@MoveGoal@"from"@Pawn.Anchor@"Focus"@MoveFocus@"Offset"@MoveOffset@CurrentPath, 'Move_DirectPath' );
|
||
|
IntermediateMoveGoal = MoveGoal;
|
||
|
/** Optionally make Pawn rotate to my IntermediateMoveGoal prior to moving */
|
||
|
if( Pawn.Physics != PHYS_Falling && !bMovingToGoal && !IsDoingLatentMove() && MoveGoal != none )
|
||
|
{
|
||
|
/** ...Only if enough time has passed since my last move (LastMoveFinishTime is set by AKFPawn::moveToward() when dest is reached */
|
||
|
if( `TimeSince(LastMoveFinishTime) > 5.f && ShouldTurnToGoal(IntermediateMoveGoal.Location) )
|
||
|
{
|
||
|
TurnFocus = IntermediateMoveGoal;
|
||
|
/** Push the RotateToFocus state, which will rotate my pawn and return here after my pawn is finished rotating */
|
||
|
PushState( 'RotateToFocus' );
|
||
|
}
|
||
|
}
|
||
|
// bPreciseDestination = true;
|
||
|
AIActionStatus = "Moving directly toward "$IntermediateMoveGoal;
|
||
|
/** Begin latent move, state code execution will resume after the move is finished/interrupted/etc. */
|
||
|
MoveToward( IntermediateMoveGoal, MoveFocus, MoveOffset, false, false );
|
||
|
if( (bReachedLatentMoveGoal && LastNavGoalReached == IntermediateMoveGoal ) || Pawn.ReachedDestination( IntermediateMoveGoal ) )
|
||
|
{
|
||
|
ReachedIntermediateMoveGoal();
|
||
|
}
|
||
|
/** Move finished, wait until my pawn has landed if the move managed to make it fall */
|
||
|
if( MyKFPawn.Physics == PHYS_Falling )
|
||
|
{
|
||
|
WaitForLanding();
|
||
|
}
|
||
|
} until( HasReachedMoveGoal() || !ActorReachable(MoveGoal) || LoopFailSafeCounter++ > 50 );
|
||
|
CurrentMovementPhase = MOVEMENT_PHASE_TYPE_NONE;
|
||
|
// Go to the CheckMove label which will decide what to do next.
|
||
|
Goto( 'CheckMove' );
|
||
|
}
|
||
|
else if( bValidRouteCache && RouteCache.Length > 0 )
|
||
|
{
|
||
|
// I can't directly reach MoveGoal, and I already have a valid path, so skip generating a new path.
|
||
|
IntermediateMoveGoal = (RouteCache.Length > 0) ? RouteCache[0] : none;
|
||
|
bValidRouteCache = false;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// I can't directly reach MoveGoal, and I need to generate a new path to it.
|
||
|
bValidRouteCache = false;
|
||
|
IntermediateMoveGoal = FindPathToward( MoveGoal );
|
||
|
// Invalidate anchor if path generation fails
|
||
|
if( IntermediateMoveGoal == none )
|
||
|
{
|
||
|
InvalidateAnchor( Pawn.Anchor );
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
else if( MovePointIsValid() )
|
||
|
{
|
||
|
/*********************************************************************************************
|
||
|
* Moving to a location (command was started with MoveToPoint())
|
||
|
********************************************************************************************* */
|
||
|
|
||
|
if( PointReachable(GetBasedPosition(MovePosition)) )
|
||
|
{
|
||
|
DirectMoveToPosition:
|
||
|
CurrentMovementPhase = MOVEMENT_PHASE_TYPE_PATHNODE_POINT_MOVE_TO;
|
||
|
// MovePosition is a directly reachable location, no need to pathfind
|
||
|
StartingMove( true, VSize(Pawn.Location - GetBasedPosition(MovePosition)), 0, none );
|
||
|
|
||
|
// Move directly to it (this while loop helps catch cases interruption when a state is pushed)
|
||
|
TimeOutTime = GetMoveTimeOutDuration( GetBasedPosition(MovePosition), false );
|
||
|
SetTimer( TimeOutTime, false, nameof(self.MoveToGoalTimedOut), self );
|
||
|
do
|
||
|
{
|
||
|
`AILog( "Moving directly to move point:"@GetBasedPosition( MovePosition )@"from"@Pawn.Anchor@"Focus"@MoveFocus@Pawn.Location, 'Move_Path' );
|
||
|
EnableSeePlayer();
|
||
|
if( `TimeSince(LastMoveFinishTime) > 5.f && ShouldTurnToGoal(GetBasedPosition( MovePosition )) )
|
||
|
{
|
||
|
TurnFocalPoint = GetBasedPosition( MovePosition );
|
||
|
PushState( 'RotateToFocus' );
|
||
|
}
|
||
|
/** Begin latent movement and stop executing state code until move completes */
|
||
|
MoveTo( GetBasedPosition( MovePosition ), MoveFocus,, false );
|
||
|
/** Move finished, wait until my pawn has landed if the move managed to make it fall */
|
||
|
if( MyKFPawn.Physics == PHYS_Falling )
|
||
|
{
|
||
|
WaitForLanding();
|
||
|
}
|
||
|
} until( HasReachedMoveGoal() || !PointReachable(GetBasedPosition(MovePosition)) );
|
||
|
CurrentMovementPhase = MOVEMENT_PHASE_TYPE_NONE;
|
||
|
// Go to the CheckMove label which will decide what to do next.
|
||
|
Goto( 'CheckMove' );
|
||
|
}
|
||
|
else if( bValidRouteCache )
|
||
|
{
|
||
|
// I can't directly reach MovePosition, and I already have a valid path, so skip generating a new path.
|
||
|
IntermediateMoveGoal = (RouteCache.Length > 0) ? RouteCache[0] : none;
|
||
|
bValidRouteCache = false;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// I can't directly reach MovePosition, and I need to generate a new path to it.
|
||
|
IntermediateMoveGoal = FindPathTo( GetBasedPosition( MovePosition ) );
|
||
|
if( IntermediateMoveGoal == none )
|
||
|
{
|
||
|
`AILog( self$" IntermediateMoveGoal is null, so calling InvalidateAnchor for nav "$Pawn.Anchor, 'PathWarning' );
|
||
|
InvalidateAnchor( Pawn.Anchor );
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
FollowingPath:
|
||
|
if( IntermediateMoveGoal != none )
|
||
|
{
|
||
|
if( MoveGoal != none ) { `AILog("Following path to move goal:"@MoveGoal@"from"@Pawn.Anchor, 'Move_Path' ); }
|
||
|
else { `AILog("Following path to move point:"@GetBasedPosition(MovePosition)@"from"@Pawn.Anchor, 'Move_Path' ); }
|
||
|
|
||
|
GetNextMoveTarget( IntermediateMoveGoal );
|
||
|
// If first navigation point is my pawn's anchor, and the next navigation point is directly reachable...
|
||
|
if( IntermediateMoveGoal == Pawn.Anchor && RouteCache.Length > 1 && ActorReachable(RouteCache[1]) )
|
||
|
{
|
||
|
`AILog( "Already at anchor, move to next..."@Pawn.Anchor@RouteCache[1], 'Move_Path' );
|
||
|
// Remove the anchor from my route and try to use the next navigation point
|
||
|
RouteCache_RemoveIndex( 0 );
|
||
|
GetNextMoveTarget( IntermediateMoveGoal );
|
||
|
}
|
||
|
if( IntermediateMoveGoal == none )
|
||
|
{
|
||
|
`AILog( "Failed to acquire move target, sleeping for 0.5 seconds and going to CheckMove label", 'Move_Path' );
|
||
|
Sleep( 0.5f );
|
||
|
// Go to the CheckMove label which will decide what to do next.
|
||
|
Goto( 'CheckMove' );
|
||
|
}
|
||
|
// Retries = 0; // New, check!
|
||
|
ClearTimer( 'MoveToGoalTimedOut', self );
|
||
|
while( IntermediateMoveGoal != none )
|
||
|
{
|
||
|
`AILog( "Still moving to"@IntermediateMoveGoal$" which is "$VSize( Pawn.Location - IntermediateMoveGoal.Location )$" units away", 'Move_Path' );
|
||
|
// Check for any global interrupts (enemy with melee range)
|
||
|
CheckInterruptCombatTransitions();
|
||
|
// If Pawn cannot strafe, then face destination before moving
|
||
|
if( !Pawn.bCanStrafe )
|
||
|
{
|
||
|
`AILog( "Pushing RotateToFocus state", 'Move_Path' );
|
||
|
PushState( 'RotateToFocus' );
|
||
|
}
|
||
|
|
||
|
// Check if we should delay our move a little
|
||
|
LastDetourCheckTime = WorldInfo.TimeSeconds;
|
||
|
if( ShouldDelayMove() )
|
||
|
{
|
||
|
SetTimer( 20.f, false, nameof(Timer_DelayMoveTimeOut), self );
|
||
|
}
|
||
|
DelayMove:
|
||
|
while( ShouldDelayMove() )
|
||
|
{
|
||
|
CurrentMovementPhase = MOVEMENT_PHASE_TYPE_PATHNODE_DELAY_MOVE;
|
||
|
AIActionStatus = "Delaying move";
|
||
|
bDirectMoveToGoal = false;
|
||
|
`AILog( "Delaying move, LastDetourCheckTime: "$`TimeSince(LastDetourCheckTime), 'Move_Path' );
|
||
|
AIZeroMovementVariables();
|
||
|
// If it's been a while since our last detour check
|
||
|
if( `TimeSince( LastDetourCheckTime ) > 5.0f )
|
||
|
{
|
||
|
`AILog( "Attempting to restart move after delaying", 'Move_Path' );
|
||
|
// Restart the move
|
||
|
LastDetourCheckTime = WorldInfo.TimeSeconds;
|
||
|
bValidRouteCache = false;
|
||
|
// Go to the CheckMove label which will decide what to do next.
|
||
|
Goto( 'CheckMove' );
|
||
|
}
|
||
|
if( Pawn != none && Pawn.Physics != PHYS_Spider )
|
||
|
{
|
||
|
Focus = IntermediateMoveGoal;
|
||
|
}
|
||
|
if( PendingDoor != none && bPreparingMove && PendingDoor.WeldIntegrity > 0 && !PendingDoor.IsCompletelyOpen() )
|
||
|
{
|
||
|
if( `FastTracePhysX(PendingDoor.Location, Pawn.Location) )
|
||
|
{
|
||
|
AIActionStatus = "Wants to attack door "$PendingDoor;
|
||
|
NotifyAttackDoor( PendingDoor );
|
||
|
}
|
||
|
}
|
||
|
Sleep( 0.1f );
|
||
|
}
|
||
|
CurrentMovementPhase = MOVEMENT_PHASE_TYPE_NONE;
|
||
|
PathingTowardActor:
|
||
|
CurrentMovementPhase = MOVEMENT_PHASE_TYPE_PATHNODE_NORMAL_MOVE_TO;
|
||
|
ClearTimer( nameof(Timer_DelayMoveTimeOut), self );
|
||
|
// Setup the next move
|
||
|
StartingMove( false, GetRouteCacheDistance(), RouteCache.Length, IntermediateMoveGoal);
|
||
|
// if the goal has been changed in between that last time we called getnextmovegoal and now, we need to go to the new goal!
|
||
|
if( bGoalChangedDueToSkipAhead )
|
||
|
{
|
||
|
`AILog( "RouteCache changed out from under us.. calling GetNextMoveTarget again!", 'Move_Path' );
|
||
|
if( !GetNextMoveTarget(IntermediateMoveGoal) )
|
||
|
{
|
||
|
`AILog( "GetNextMoveTarget FAILED after skipahead changed the route cache.. Aborting move", 'PathWarning' );
|
||
|
// issue with movetarget not changing
|
||
|
IntermediateMoveGoal = none;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
Outer.SetDirectPathCheckTime();
|
||
|
EnableSeePlayer();
|
||
|
if( Pawn.Physics != PHYS_Falling && !bMovingToGoal && !IsDoingLatentMove() && MoveGoal != none )
|
||
|
{
|
||
|
if( `TimeSince(LastMoveFinishTime) > 5.f && ShouldTurnToGoal(IntermediateMoveGoal.Location) )
|
||
|
{
|
||
|
TurnFocus = IntermediateMoveGoal;
|
||
|
PushState( 'RotateToFocus' );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// bPreciseDestination = true;
|
||
|
AIActionStatus = "Moving toward "$IntermediateMoveGoal;
|
||
|
/** Begin latent movement and stop executing state code until move completes */
|
||
|
MoveToward( IntermediateMoveGoal, MoveFocus, (IntermediateMoveGoal == MoveGoal) ? MoveOffset : 0.f, false, false );
|
||
|
AIActionStatus = "Finished moving toward "$IntermediateMoveGoal;
|
||
|
CurrentMovementPhase = MOVEMENT_PHASE_TYPE_NONE;
|
||
|
if( (bReachedLatentMoveGoal && LastNavGoalReached == IntermediateMoveGoal) || Pawn.ReachedDestination(IntermediateMoveGoal) )
|
||
|
{
|
||
|
`AILog( "MoveToward finished and I reached my intermediate goal ("$IntermediateMoveGoal$") which is "$VSize(IntermediateMoveGoal.Location - Pawn.Location)$" units away", 'Move_Path' );
|
||
|
ReachedIntermediateMoveGoal();
|
||
|
}
|
||
|
/** Move finished, wait until my pawn has landed if the move managed to make it fall */
|
||
|
if( MyKFPawn.Physics == PHYS_Falling )
|
||
|
{
|
||
|
WaitForLanding();
|
||
|
bReevaluatePath = true;
|
||
|
}
|
||
|
if( bReevaluatePath )
|
||
|
{
|
||
|
ReEvaluatePath();
|
||
|
}
|
||
|
else if( bGoalChangedDueToSkipAhead )
|
||
|
{
|
||
|
`AILog( "path was changed during movetoward", 'Move_Path' );
|
||
|
IntermediateMoveGoal = none;
|
||
|
}
|
||
|
|
||
|
/** If we are moving towards a Pawn, repath every time we successfully reach the next node as that Pawn's movement
|
||
|
may cause the best path to change */
|
||
|
if( Pawn(MoveGoal) != none && Pawn.ReachedDestination(IntermediateMoveGoal)
|
||
|
&& VSizeSq(MoveGoal.Location - GetBasedPosition(LastPawnTargetPathLocation)) > 589824.f )
|
||
|
{
|
||
|
ReachedIntermediateMoveGoal();
|
||
|
SetBasedPosition( LastPawnTargetPathLocation, MoveGoal.Location );
|
||
|
`AILog( "Repathing because MoveGoal is a Pawn:"@MoveGoal, 'Command_MoveToGoal' );
|
||
|
// Go to the CheckMove label which will decide what to do next.
|
||
|
Goto( 'CheckMove' );
|
||
|
}
|
||
|
else if( IntermediateMoveGoal == MoveGoal && HasReachedMoveGoal() )
|
||
|
{
|
||
|
// Reached my MoveGoal, go to the CheckMove label which will decide what to do next.
|
||
|
Goto( 'CheckMove' );
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
if( GetNextMoveTarget(IntermediateMoveGoal) )
|
||
|
{
|
||
|
// Reset counter if I've gotten a valid next move target
|
||
|
NumTimesGetNextMoveGoalReturnedSameNode = 0;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// GetNextMoveTarget()'s result wasn't valid, so count it, reset my pawn's anchor, and delay.
|
||
|
NumTimesGetNextMoveGoalReturnedSameNode++;
|
||
|
SetBestAnchor();
|
||
|
Sleep( 0.1f );
|
||
|
}
|
||
|
|
||
|
CheckForStuckPath();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if( GetBasedPosition(MovePosition) != vect(0,0,0) )
|
||
|
{
|
||
|
/*********************************************************************************************
|
||
|
* Moving to a location (command was started with MoveToPoint())
|
||
|
********************************************************************************************* */
|
||
|
DirectMoveToPositionTwo:
|
||
|
while( !HasReachedMoveGoal() )
|
||
|
{
|
||
|
CurrentMovementPhase = MOVEMENT_PHASE_TYPE_PATHNODE_POINT_MOVE_TO;
|
||
|
if( !PointReachable( GetBasedPosition(MovePosition)) )
|
||
|
{
|
||
|
UpdateHistoryString( "[F] [!POINTREACHABLE] "$Pawn.Location );
|
||
|
Sleep( 0.25f );
|
||
|
/** Give up and go to the FailedMove label */
|
||
|
Goto( 'FailedMove' );
|
||
|
}
|
||
|
|
||
|
// Finish up direct move
|
||
|
`AILog( "Finishing direct move, calling MoveTo "$GetBasedPosition(MovePosition), 'Move_DirectPath' );
|
||
|
if( `TimeSince(LastMoveFinishTime) > 5.f && ShouldTurnToGoal(GetBasedPosition(MovePosition)) )
|
||
|
{
|
||
|
TurnFocalPoint = GetBasedPosition( MovePosition );
|
||
|
PushState( 'RotateToFocus' );
|
||
|
}
|
||
|
/** Begin latent movement and stop executing state code until move completes */
|
||
|
MoveTo( GetBasedPosition(MovePosition), MoveFocus,, false );
|
||
|
CurrentMovementPhase = MOVEMENT_PHASE_TYPE_NONE;
|
||
|
/** Move finished, wait until my pawn has landed if the move managed to make it fall */
|
||
|
if( MyKFPawn.Physics == PHYS_Falling )
|
||
|
{
|
||
|
WaitForLanding();
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
else if( !HasReachedMoveGoal() ) // Otherwise, if haven't reached move goal
|
||
|
{
|
||
|
UpdateHistoryString( "[F] [NO PATH TO "$MOVEGOAL$"] "$Pawn.Location );
|
||
|
if( MoveGoal != none ) { `AILog( "Failed to find path to:"@MoveGoal, 'Command_MoveToGoal' ); }
|
||
|
else { `AILog( "Failed to find path to:"@GetBasedPosition(MovePosition), 'PathWarning' ); }
|
||
|
|
||
|
Sleep( 0.25f );
|
||
|
/** Give up and go to the FailedMove label */
|
||
|
Goto( 'FailedMove' );
|
||
|
}
|
||
|
/** Go to the CheckMove label which will decide what to do next */
|
||
|
Goto( 'CheckMove' );
|
||
|
|
||
|
FailedMove:
|
||
|
`AILog( "FailedMove Label", 'PathWarning' );
|
||
|
UpdateHistoryString( "Failure - FailedMove Label" );
|
||
|
if( Pawn.bCrawler && Pawn.Floor != vect(0,0,1) && Pawn.Physics == PHYS_Spider )
|
||
|
{
|
||
|
// if( Retries < 5 && IntermediateMoveGoal != none && CurrentPath != none && WallReachSpec(CurrentPath) != none && WallReachSpec(CurrentPath).IsWallToCeiling() )
|
||
|
// {
|
||
|
// bValidRouteCache = false;
|
||
|
// if( bPreparingMove && PendingDoor == none )
|
||
|
// {
|
||
|
// bPreparingMove = false;
|
||
|
// }
|
||
|
// bReevaluatePath = true;
|
||
|
// Sleep( 0.f );
|
||
|
// IntermediateMoveGoal = none;
|
||
|
// Retries++;
|
||
|
// // SetBestAnchor();
|
||
|
// ReEvaluatePath();
|
||
|
// }
|
||
|
// else
|
||
|
if( Retries < 5 )
|
||
|
{
|
||
|
Pawn.SetPhysics( PHYS_Falling );
|
||
|
WaitForLanding();
|
||
|
bValidRouteCache = false;
|
||
|
if( bPreparingMove && PendingDoor == none && (!MyKFPawn.IsDoingSpecialMove() || !MyKFPawn.SpecialMoves[MyKFPawn.SpecialMove].bDisableMovement) )
|
||
|
{
|
||
|
bPreparingMove = false;
|
||
|
}
|
||
|
bReevaluatePath = true;
|
||
|
Sleep( 0.f );
|
||
|
IntermediateMoveGoal = none;
|
||
|
Retries++;
|
||
|
// SetBestAnchor();
|
||
|
ReEvaluatePath();
|
||
|
}
|
||
|
}
|
||
|
else if( Retries < 5 )
|
||
|
{
|
||
|
bValidRouteCache = false;
|
||
|
if( bPreparingMove && PendingDoor == none && (!MyKFPawn.IsDoingSpecialMove() || !MyKFPawn.SpecialMoves[MyKFPawn.SpecialMove].bDisableMovement) )
|
||
|
{
|
||
|
bPreparingMove = false;
|
||
|
}
|
||
|
bReevaluatePath = true;
|
||
|
Sleep( 0.f );
|
||
|
IntermediateMoveGoal = none;
|
||
|
Retries++;
|
||
|
// SetBestAnchor();
|
||
|
ReEvaluatePath();
|
||
|
Sleep( 0.f );
|
||
|
}
|
||
|
else if( Retries >= 5 && Retries < 10 )
|
||
|
{
|
||
|
bValidRouteCache = false;
|
||
|
if( bPreparingMove && PendingDoor == none && (!MyKFPawn.IsDoingSpecialMove() || !MyKFPawn.SpecialMoves[MyKFPawn.SpecialMove].bDisableMovement) )
|
||
|
{
|
||
|
bPreparingMove = false;
|
||
|
}
|
||
|
bReevaluatePath = true;
|
||
|
Sleep( 0.f );
|
||
|
if( IntermediateMoveGoal != none && NavigationPoint(IntermediateMoveGoal) != none )
|
||
|
{
|
||
|
// Path is failing - if I just ran into a non-pawn obstruction, temporarily block IntermediateMoveGoal to let me
|
||
|
// generate another path that doesn't require it.
|
||
|
if( CreateTemporaryBlockedReach(NavigationPoint(IntermediateMoveGoal), CurrentPath) )
|
||
|
{
|
||
|
IntermediateMoveGoal = none;
|
||
|
Retries++;
|
||
|
// SetBestAnchor();
|
||
|
ReEvaluatePath();
|
||
|
Sleep( 0.f );
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
Retries = 0;
|
||
|
Status = 'Failure';
|
||
|
GotoState('DelayFailure');
|
||
|
Stop;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
Retries = 0;
|
||
|
Status = 'Failure';
|
||
|
GotoState('DelayFailure');
|
||
|
Stop;
|
||
|
}
|
||
|
|
||
|
ReachedGoal:
|
||
|
if( MoveGoal != none ) { `AILog("Reached move goal:"@MoveGoal@VSize(Pawn.Location-MoveGoal.Location), 'Command_MoveToGoal'); }
|
||
|
else { `AILog("Reached move point:"@GetBasedPosition(MovePosition)@VSize(Pawn.Location-GetBasedPosition(MovePosition)), 'Command_MoveToGoal'); }
|
||
|
|
||
|
Status = 'Success';
|
||
|
PopCommand( self );
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Command has failed but delay pop to avoid infinite recursion
|
||
|
*/
|
||
|
state DelayFailure
|
||
|
{
|
||
|
Begin:
|
||
|
CurrentMovementPhase = MOVEMENT_PHASE_TYPE_PATHNODE_FAILED_MOVE;
|
||
|
Sleep( 0.5f );
|
||
|
|
||
|
Status = 'Failure';
|
||
|
CurrentMovementPhase = MOVEMENT_PHASE_TYPE_NONE;
|
||
|
PopCommand( self );
|
||
|
}
|
||
|
|
||
|
state RotateToFocus
|
||
|
{
|
||
|
Begin:
|
||
|
CurrentMovementPhase = MOVEMENT_PHASE_TYPE_PATHNODE_ROTATE_TO_FOCUS;
|
||
|
if( Pawn.Physics == PHYS_Falling )
|
||
|
{
|
||
|
WaitForLanding();
|
||
|
}
|
||
|
|
||
|
if( TurnFocalPoint != vect(0,0,0) )
|
||
|
{
|
||
|
Focus = none;
|
||
|
SetFocalPoint( TurnFocalPoint );
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
SetFocalPoint( vect(0,0,0) );
|
||
|
Focus = TurnFocus;
|
||
|
}
|
||
|
FinishRotation();
|
||
|
MyKFPawn.ResetDesiredRotation();
|
||
|
TurnFocalPoint = vect(0,0,0);
|
||
|
CurrentMovementPhase = MOVEMENT_PHASE_TYPE_NONE;
|
||
|
PopState();
|
||
|
}
|
||
|
|
||
|
function Timer_DelayMoveTimeOut()
|
||
|
{
|
||
|
if( MyKFPawn != none && MyKFPawn.IsAliveAndWell() )
|
||
|
{
|
||
|
`AILog( "************* "$MyKFPawn$" DELAYING MOVE FOR OVER 20 SECONDS" );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
native function bool AdjustAround( Actor Other, vector HitNormal );
|
||
|
|
||
|
function bool NotifyBump( Actor Other, Vector HitNormal )
|
||
|
{
|
||
|
local KFPawn KFP;
|
||
|
//local KFAIController OtherKFAIC;
|
||
|
|
||
|
if( IsDoingLatentMove() && MyKFPawn.Physics == PHYS_Walking )
|
||
|
{
|
||
|
KFP = KFPawn(Other);
|
||
|
|
||
|
if( KFP != none )
|
||
|
{
|
||
|
`AILog( "NotifyBump in MoveToGoal, Other: "$Other$" HitNormal: "$HitNormal, 'BumpEvent' );
|
||
|
DisableBump( 0.12f );
|
||
|
if ( (!KFP.IsAliveAndWell() || !Pawn.IsSameTeam( KFP )) ) //|| (P.Controller == none)
|
||
|
{
|
||
|
return false;
|
||
|
}
|
||
|
if( KFDoorMarker(MoveTarget) != none )
|
||
|
{
|
||
|
if( PendingDoor == none && !KFDoorMarker(MoveTarget).MyKFDoor.IsCompletelyOpen() && VSizeSQ(MoveTarget.Location - MyKFPawn.Location) < 122500.f )
|
||
|
{
|
||
|
WaitForDoor( KFDoorMarker(MoveTarget).MyKFDoor );
|
||
|
return true;
|
||
|
}
|
||
|
}
|
||
|
// Skip if closer than 400uu to enemy and bumped Zed's enemy matches mine
|
||
|
if( KFP.MyKFAIC != none && KFP.MyKFAIC.Enemy != none && Enemy != none && KFP.MyKFAIC.Enemy == Enemy
|
||
|
&& KFP.MyKFAIC.IsDoingLatentMove() && VSizeSQ(Enemy.Location - KFP.Location) < 160000.f )
|
||
|
{
|
||
|
return false;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Don't adjust around a non-walking pawn, causes odd results
|
||
|
if( Other.Physics == PHYS_Walking )
|
||
|
{
|
||
|
AdjustAround( Other, HitNormal );
|
||
|
}
|
||
|
return true;
|
||
|
}
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
function bool NotifyLatentPostPhysWalking()
|
||
|
{
|
||
|
if( !bPreparingMove && MoveTimer > 1.f && Pawn.Velocity == vect(0,0,0) && Pawn.Acceleration != vect(0,0,0) )
|
||
|
{
|
||
|
++NumFailedLatentWalkMoves;
|
||
|
if( NumFailedLatentWalkMoves > 3 )
|
||
|
{
|
||
|
MoveTimer = -1;
|
||
|
NumFailedLatentWalkMoves = 0;
|
||
|
return true;
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
NumFailedLatentWalkMoves = 0;
|
||
|
}
|
||
|
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
defaultproperties
|
||
|
{
|
||
|
SkipAheadUpdateThreshold=500.f
|
||
|
SkipAheadMaxNodes=15
|
||
|
SkipAheadMaxDist=4096.f
|
||
|
SkipAheadPitCheckInterval=300.f
|
||
|
SkipAheadPitCheckHeight=250.f
|
||
|
SkipAheadCurrentTestingIndex=0
|
||
|
bCanPathfind=true
|
||
|
bEnableSkipAheadChecks=false
|
||
|
}
|