2026-07-15 19:17:47 +02:00
// Copyright (c) 2026 Unreal Directive. Licensed under the MIT License.
# include "Tasks/DirectiveUtilTask_MoveToLocation.h"
# include "DirectiveUtilLogChannels.h"
# include "Blueprint/AIBlueprintHelperLibrary.h"
# include "Engine/World.h"
# include "DrawDebugHelpers.h"
2026-08-15 21:06:43 +02:00
# include "NavigationSystem.h"
2026-07-15 19:17:47 +02:00
# include "Navigation/PathFollowingComponent.h"
# include "TimerManager.h"
2026-08-15 21:06:43 +02:00
namespace
{
float GetNonNegativeFiniteValue ( const float Value )
{
return FMath : : IsFinite ( Value ) ? FMath : : Max ( Value , 0.0f ) : 0.0f ;
}
bool IsWithinAcceptanceRadius ( const FVector & CurrentLocation , const FVector & TargetLocation , const float AcceptanceRadius )
{
return FVector : : DistSquared2D ( CurrentLocation , TargetLocation ) < = FMath : : Square ( AcceptanceRadius ) ;
}
}
2026-07-15 19:17:47 +02:00
UDirectiveUtilTask_MoveToLocation * UDirectiveUtilTask_MoveToLocation : : MoveToLocation (
UObject * WorldContextObject ,
AController * Controller ,
const FVector Destination ,
const float AcceptanceRadius ,
const bool bCheckStuckMovement ,
const float StuckThreshold ,
const bool bDebugLineTrace )
{
UDirectiveUtilTask_MoveToLocation * Action = NewObject < UDirectiveUtilTask_MoveToLocation > ( ) ;
Action - > Controller = Controller ;
Action - > Destination = Destination ;
2026-08-15 21:06:43 +02:00
Action - > AcceptanceRadius = GetNonNegativeFiniteValue ( AcceptanceRadius ) ;
2026-07-15 19:17:47 +02:00
Action - > bDebugLineTrace = bDebugLineTrace ;
2026-08-15 21:06:43 +02:00
Action - > StuckThreshold = GetNonNegativeFiniteValue ( StuckThreshold ) ;
2026-07-15 19:17:47 +02:00
Action - > bCheckStuckMovement = bCheckStuckMovement ;
if ( WorldContextObject )
{
Action - > RegisterWithGameInstance ( WorldContextObject ) ;
}
return Action ;
}
void UDirectiveUtilTask_MoveToLocation : : EndTask ( )
{
2026-08-15 21:06:43 +02:00
if ( IsValid ( Controller ) )
{
Controller - > StopMovement ( ) ;
}
2026-07-15 19:17:47 +02:00
ExecuteCompleted ( false ) ;
}
void UDirectiveUtilTask_MoveToLocation : : Activate ( )
{
2026-08-15 21:06:43 +02:00
if ( bHasCompleted )
{
return ;
}
APawn * Pawn = IsValid ( Controller ) ? Controller - > GetPawn ( ) : nullptr ;
UWorld * World = IsValid ( Pawn ) ? Controller - > GetWorld ( ) : nullptr ;
if ( ! World )
{
ExecuteCompleted ( false ) ;
UE_LOG ( LogDirectiveUtil , Warning , TEXT ( " Controller, pawn, or world is unavailable while moving to location. Aborting. " ) ) ;
return ;
}
if ( ! FNavigationSystem : : GetCurrent < UNavigationSystemV1 > ( World ) )
2026-07-15 19:17:47 +02:00
{
ExecuteCompleted ( false ) ;
2026-08-15 21:06:43 +02:00
UE_LOG ( LogDirectiveUtil , Verbose , TEXT ( " Navigation is unavailable while moving to location. " ) ) ;
2026-07-15 19:17:47 +02:00
return ;
}
2026-08-15 21:06:43 +02:00
TimerWorld = World ;
StartLocation = Pawn - > GetActorLocation ( ) ;
2026-07-15 19:17:47 +02:00
LastCheckedLocation = StartLocation ;
CurrentLocation = StartLocation ;
2026-08-15 21:06:43 +02:00
World - > GetTimerManager ( ) . SetTimer ( TimerHandle , this , & UDirectiveUtilTask_MoveToLocation : : CheckMoveToLocation , 0.1f , true ) ;
2026-07-15 19:17:47 +02:00
if ( bCheckStuckMovement )
{
2026-08-15 21:06:43 +02:00
World - > GetTimerManager ( ) . SetTimer ( StuckTimerHandle , this , & UDirectiveUtilTask_MoveToLocation : : CheckStuckMovement , 3.f , true ) ;
2026-07-15 19:17:47 +02:00
}
UAIBlueprintHelperLibrary : : SimpleMoveToLocation ( Controller , Destination ) ;
UE_LOG ( LogDirectiveUtil , Verbose , TEXT ( " Moving controller to location (%s). " ) , * Destination . ToString ( ) ) ;
if ( bDebugLineTrace )
{
DrawDebugLine (
2026-08-15 21:06:43 +02:00
World ,
2026-07-15 19:17:47 +02:00
Destination + FVector ( 0 , 0 , 100 ) ,
Destination ,
FColor : : Green ,
false ,
5.0f ,
0 ,
1.0f
) ;
}
}
void UDirectiveUtilTask_MoveToLocation : : CheckMoveToLocation ( )
{
2026-08-15 21:06:43 +02:00
APawn * Pawn = IsValid ( Controller ) ? Controller - > GetPawn ( ) : nullptr ;
if ( ! IsValid ( Pawn ) | | ! TimerWorld . IsValid ( ) )
2026-07-15 19:17:47 +02:00
{
ExecuteCompleted ( false ) ;
2026-08-15 21:06:43 +02:00
UE_LOG ( LogDirectiveUtil , Warning , TEXT ( " Controller, pawn, or world is unavailable while moving to location. Aborting. " ) ) ;
2026-07-15 19:17:47 +02:00
return ;
}
2026-08-15 21:06:43 +02:00
CurrentLocation = Pawn - > GetActorLocation ( ) ;
2026-07-15 19:17:47 +02:00
UE_LOG ( LogDirectiveUtil , Verbose , TEXT ( " Controller is moving to location (%s). Current distance: %f. " ) , * Destination . ToString ( ) , FVector : : Dist ( CurrentLocation , Destination ) ) ;
2026-08-15 21:06:43 +02:00
if ( IsWithinAcceptanceRadius ( CurrentLocation , Destination , AcceptanceRadius ) )
2026-07-15 19:17:47 +02:00
{
UE_LOG ( LogDirectiveUtil , Verbose , TEXT ( " Controller has moved to location. " ) ) ;
ExecuteCompleted ( true ) ;
return ;
}
const UPathFollowingComponent * PathFollowing = Controller - > FindComponentByClass < UPathFollowingComponent > ( ) ;
if ( ! PathFollowing | | PathFollowing - > GetStatus ( ) = = EPathFollowingStatus : : Idle )
{
UE_LOG ( LogDirectiveUtil , Verbose , TEXT ( " Path following has stopped. Completing move to location. " ) ) ;
2026-08-15 21:06:43 +02:00
ExecuteCompleted ( IsWithinAcceptanceRadius ( CurrentLocation , Destination , AcceptanceRadius ) ) ;
2026-07-15 19:17:47 +02:00
}
}
void UDirectiveUtilTask_MoveToLocation : : CheckStuckMovement ( )
{
2026-08-15 21:06:43 +02:00
const APawn * Pawn = IsValid ( Controller ) ? Controller - > GetPawn ( ) : nullptr ;
if ( ! IsValid ( Pawn ) | | ! TimerWorld . IsValid ( ) )
2026-07-15 19:17:47 +02:00
{
ExecuteCompleted ( false ) ;
2026-08-15 21:06:43 +02:00
UE_LOG ( LogDirectiveUtil , Warning , TEXT ( " Controller, pawn, or world is unavailable while moving to location. Aborting. " ) ) ;
2026-07-15 19:17:47 +02:00
return ;
}
if ( FVector : : Dist ( CurrentLocation , LastCheckedLocation ) < StuckThreshold )
{
UE_LOG ( LogDirectiveUtil , Warning , TEXT ( " Controller is stuck while moving to location. Aborting " ) ) ;
ExecuteCompleted ( false ) ;
}
LastCheckedLocation = CurrentLocation ;
}
void UDirectiveUtilTask_MoveToLocation : : ExecuteCompleted ( const bool bSuccess )
{
if ( bHasCompleted )
{
return ;
}
bHasCompleted = true ;
2026-08-15 21:06:43 +02:00
UE_LOG ( LogDirectiveUtil , Verbose , TEXT ( " Movement to location completed. Success: %s. " ) , bSuccess ? TEXT ( " true " ) : TEXT ( " false " ) ) ;
2026-07-15 19:17:47 +02:00
2026-08-15 21:06:43 +02:00
if ( UWorld * World = TimerWorld . Get ( ) )
2026-07-15 19:17:47 +02:00
{
2026-08-15 21:06:43 +02:00
World - > GetTimerManager ( ) . ClearTimer ( TimerHandle ) ;
World - > GetTimerManager ( ) . ClearTimer ( StuckTimerHandle ) ;
2026-07-15 19:17:47 +02:00
}
2026-08-15 21:06:43 +02:00
TimerWorld . Reset ( ) ;
2026-07-15 19:17:47 +02:00
Completed . Broadcast ( bSuccess ) ;
Controller = nullptr ;
Destination = FVector : : ZeroVector ;
SetReadyToDestroy ( ) ;
}
UDirectiveUtilTask_MoveToActor * UDirectiveUtilTask_MoveToActor : : MoveToActor (
UObject * WorldContextObject ,
AController * Controller ,
AActor * Goal ,
const float AcceptanceRadius ,
const bool bCheckStuckMovement ,
const float StuckThreshold )
{
UDirectiveUtilTask_MoveToActor * Action = NewObject < UDirectiveUtilTask_MoveToActor > ( ) ;
Action - > Controller = Controller ;
Action - > Goal = Goal ;
2026-08-15 21:06:43 +02:00
Action - > AcceptanceRadius = GetNonNegativeFiniteValue ( AcceptanceRadius ) ;
Action - > StuckThreshold = GetNonNegativeFiniteValue ( StuckThreshold ) ;
2026-07-15 19:17:47 +02:00
Action - > bCheckStuckMovement = bCheckStuckMovement ;
if ( WorldContextObject )
{
Action - > RegisterWithGameInstance ( WorldContextObject ) ;
}
return Action ;
}
void UDirectiveUtilTask_MoveToActor : : EndTask ( )
{
2026-08-15 21:06:43 +02:00
if ( IsValid ( Controller ) )
{
Controller - > StopMovement ( ) ;
}
2026-07-15 19:17:47 +02:00
ExecuteCompleted ( false ) ;
}
void UDirectiveUtilTask_MoveToActor : : Activate ( )
{
2026-08-15 21:06:43 +02:00
if ( bHasCompleted )
{
return ;
}
APawn * Pawn = IsValid ( Controller ) ? Controller - > GetPawn ( ) : nullptr ;
UWorld * World = IsValid ( Pawn ) ? Controller - > GetWorld ( ) : nullptr ;
if ( ! World | | ! IsValid ( Goal ) )
{
ExecuteCompleted ( false ) ;
UE_LOG ( LogDirectiveUtil , Warning , TEXT ( " Controller, pawn, goal, or world is unavailable while moving to actor. Aborting. " ) ) ;
return ;
}
if ( ! FNavigationSystem : : GetCurrent < UNavigationSystemV1 > ( World ) )
2026-07-15 19:17:47 +02:00
{
ExecuteCompleted ( false ) ;
2026-08-15 21:06:43 +02:00
UE_LOG ( LogDirectiveUtil , Verbose , TEXT ( " Navigation is unavailable while moving to actor. " ) ) ;
2026-07-15 19:17:47 +02:00
return ;
}
2026-08-15 21:06:43 +02:00
TimerWorld = World ;
StartLocation = Pawn - > GetActorLocation ( ) ;
2026-07-15 19:17:47 +02:00
LastCheckedLocation = StartLocation ;
CurrentLocation = StartLocation ;
2026-08-15 21:06:43 +02:00
World - > GetTimerManager ( ) . SetTimer ( TimerHandle , this , & UDirectiveUtilTask_MoveToActor : : CheckMoveToActor , 0.1f , true ) ;
2026-07-15 19:17:47 +02:00
if ( bCheckStuckMovement )
{
2026-08-15 21:06:43 +02:00
World - > GetTimerManager ( ) . SetTimer ( StuckTimerHandle , this , & UDirectiveUtilTask_MoveToActor : : CheckStuckMovement , 3.f , true ) ;
2026-07-15 19:17:47 +02:00
}
UAIBlueprintHelperLibrary : : SimpleMoveToActor ( Controller , Goal ) ;
UE_LOG ( LogDirectiveUtil , Verbose , TEXT ( " Moving controller to actor (%s). " ) , * GetNameSafe ( Goal ) ) ;
}
void UDirectiveUtilTask_MoveToActor : : CheckMoveToActor ( )
{
2026-08-15 21:06:43 +02:00
APawn * Pawn = IsValid ( Controller ) ? Controller - > GetPawn ( ) : nullptr ;
if ( ! IsValid ( Pawn ) | | ! TimerWorld . IsValid ( ) )
2026-07-15 19:17:47 +02:00
{
ExecuteCompleted ( false ) ;
2026-08-15 21:06:43 +02:00
UE_LOG ( LogDirectiveUtil , Warning , TEXT ( " Controller, pawn, or world is unavailable while moving to actor. Aborting. " ) ) ;
2026-07-15 19:17:47 +02:00
return ;
}
if ( ! IsValid ( Goal ) )
{
ExecuteCompleted ( false ) ;
UE_LOG ( LogDirectiveUtil , Warning , TEXT ( " Goal actor has been destroyed while moving to actor. Aborting. " ) ) ;
return ;
}
// The goal can move, so its location is re-read every poll.
const FVector GoalLocation = Goal - > GetActorLocation ( ) ;
2026-08-15 21:06:43 +02:00
CurrentLocation = Pawn - > GetActorLocation ( ) ;
2026-07-15 19:17:47 +02:00
UE_LOG ( LogDirectiveUtil , Verbose , TEXT ( " Controller is moving to actor (%s). Current distance: %f. " ) , * GetNameSafe ( Goal ) , FVector : : Dist ( CurrentLocation , GoalLocation ) ) ;
2026-08-15 21:06:43 +02:00
if ( IsWithinAcceptanceRadius ( CurrentLocation , GoalLocation , AcceptanceRadius ) )
2026-07-15 19:17:47 +02:00
{
UE_LOG ( LogDirectiveUtil , Verbose , TEXT ( " Controller has moved to actor. " ) ) ;
ExecuteCompleted ( true ) ;
return ;
}
const UPathFollowingComponent * PathFollowing = Controller - > FindComponentByClass < UPathFollowingComponent > ( ) ;
if ( ! PathFollowing | | PathFollowing - > GetStatus ( ) = = EPathFollowingStatus : : Idle )
{
UE_LOG ( LogDirectiveUtil , Verbose , TEXT ( " Path following has stopped. Completing move to actor. " ) ) ;
2026-08-15 21:06:43 +02:00
ExecuteCompleted ( IsWithinAcceptanceRadius ( CurrentLocation , GoalLocation , AcceptanceRadius ) ) ;
2026-07-15 19:17:47 +02:00
}
}
void UDirectiveUtilTask_MoveToActor : : CheckStuckMovement ( )
{
2026-08-15 21:06:43 +02:00
const APawn * Pawn = IsValid ( Controller ) ? Controller - > GetPawn ( ) : nullptr ;
if ( ! IsValid ( Pawn ) | | ! TimerWorld . IsValid ( ) )
2026-07-15 19:17:47 +02:00
{
ExecuteCompleted ( false ) ;
2026-08-15 21:06:43 +02:00
UE_LOG ( LogDirectiveUtil , Warning , TEXT ( " Controller, pawn, or world is unavailable while moving to actor. Aborting. " ) ) ;
2026-07-15 19:17:47 +02:00
return ;
}
if ( FVector : : Dist ( CurrentLocation , LastCheckedLocation ) < StuckThreshold )
{
UE_LOG ( LogDirectiveUtil , Warning , TEXT ( " Controller is stuck while moving to actor. Aborting " ) ) ;
ExecuteCompleted ( false ) ;
}
LastCheckedLocation = CurrentLocation ;
}
void UDirectiveUtilTask_MoveToActor : : ExecuteCompleted ( const bool bSuccess )
{
if ( bHasCompleted )
{
return ;
}
bHasCompleted = true ;
2026-08-15 21:06:43 +02:00
UE_LOG ( LogDirectiveUtil , Verbose , TEXT ( " Movement to actor completed. Success: %s. " ) , bSuccess ? TEXT ( " true " ) : TEXT ( " false " ) ) ;
2026-07-15 19:17:47 +02:00
2026-08-15 21:06:43 +02:00
if ( UWorld * World = TimerWorld . Get ( ) )
2026-07-15 19:17:47 +02:00
{
2026-08-15 21:06:43 +02:00
World - > GetTimerManager ( ) . ClearTimer ( TimerHandle ) ;
World - > GetTimerManager ( ) . ClearTimer ( StuckTimerHandle ) ;
2026-07-15 19:17:47 +02:00
}
2026-08-15 21:06:43 +02:00
TimerWorld . Reset ( ) ;
2026-07-15 19:17:47 +02:00
Completed . Broadcast ( bSuccess ) ;
Controller = nullptr ;
Goal = nullptr ;
SetReadyToDestroy ( ) ;
}