Update directive utilities plugin
This commit is contained in:
@@ -0,0 +1,102 @@
|
||||
// Copyright (c) 2026 Unreal Directive. Licensed under the MIT License.
|
||||
|
||||
#include "Tasks/DirectiveUtilAsyncActionBase.h"
|
||||
|
||||
#include "Engine/Engine.h"
|
||||
#include "Engine/World.h"
|
||||
|
||||
void UDirectiveUtilAsyncActionBase::RegisterWithGameInstance(const UObject* WorldContextObject)
|
||||
{
|
||||
UnbindWorldCleanup();
|
||||
Super::RegisterWithGameInstance(WorldContextObject);
|
||||
RegisteredWorld = WorldContextObject && GEngine
|
||||
? GEngine->GetWorldFromContextObject(WorldContextObject, EGetWorldErrorMode::ReturnNull)
|
||||
: nullptr;
|
||||
if (RegisteredWorld.IsValid())
|
||||
{
|
||||
WorldCleanupHandle = FWorldDelegates::OnWorldCleanup.AddUObject(
|
||||
this,
|
||||
&UDirectiveUtilAsyncActionBase::HandleWorldCleanup);
|
||||
}
|
||||
}
|
||||
|
||||
void UDirectiveUtilAsyncActionBase::SetReadyToDestroy()
|
||||
{
|
||||
UnbindWorldCleanup();
|
||||
Super::SetReadyToDestroy();
|
||||
}
|
||||
|
||||
void UDirectiveUtilAsyncActionBase::BeginDestroy()
|
||||
{
|
||||
UnbindWorldCleanup();
|
||||
Super::BeginDestroy();
|
||||
}
|
||||
|
||||
void UDirectiveUtilAsyncActionBase::HandleWorldCleanup(
|
||||
UWorld* World,
|
||||
const bool,
|
||||
const bool)
|
||||
{
|
||||
if (World == RegisteredWorld.Get())
|
||||
{
|
||||
SetReadyToDestroy();
|
||||
}
|
||||
}
|
||||
|
||||
void UDirectiveUtilAsyncActionBase::UnbindWorldCleanup()
|
||||
{
|
||||
if (WorldCleanupHandle.IsValid())
|
||||
{
|
||||
FWorldDelegates::OnWorldCleanup.Remove(WorldCleanupHandle);
|
||||
WorldCleanupHandle.Reset();
|
||||
}
|
||||
RegisteredWorld.Reset();
|
||||
}
|
||||
|
||||
void UDirectiveUtilCancellableAsyncAction::RegisterWithGameInstance(const UObject* WorldContextObject)
|
||||
{
|
||||
UnbindWorldCleanup();
|
||||
Super::RegisterWithGameInstance(WorldContextObject);
|
||||
RegisteredWorld = WorldContextObject && GEngine
|
||||
? GEngine->GetWorldFromContextObject(WorldContextObject, EGetWorldErrorMode::ReturnNull)
|
||||
: nullptr;
|
||||
if (RegisteredWorld.IsValid())
|
||||
{
|
||||
WorldCleanupHandle = FWorldDelegates::OnWorldCleanup.AddUObject(
|
||||
this,
|
||||
&UDirectiveUtilCancellableAsyncAction::HandleWorldCleanup);
|
||||
}
|
||||
}
|
||||
|
||||
void UDirectiveUtilCancellableAsyncAction::SetReadyToDestroy()
|
||||
{
|
||||
UnbindWorldCleanup();
|
||||
Super::SetReadyToDestroy();
|
||||
}
|
||||
|
||||
void UDirectiveUtilCancellableAsyncAction::BeginDestroy()
|
||||
{
|
||||
UnbindWorldCleanup();
|
||||
Super::BeginDestroy();
|
||||
}
|
||||
|
||||
void UDirectiveUtilCancellableAsyncAction::HandleWorldCleanup(
|
||||
UWorld* World,
|
||||
const bool,
|
||||
const bool)
|
||||
{
|
||||
if (World == RegisteredWorld.Get())
|
||||
{
|
||||
Cancel();
|
||||
}
|
||||
}
|
||||
|
||||
void UDirectiveUtilCancellableAsyncAction::UnbindWorldCleanup()
|
||||
{
|
||||
if (WorldCleanupHandle.IsValid())
|
||||
{
|
||||
FWorldDelegates::OnWorldCleanup.Remove(WorldCleanupHandle);
|
||||
WorldCleanupHandle.Reset();
|
||||
}
|
||||
RegisteredWorld.Reset();
|
||||
}
|
||||
@@ -52,7 +52,7 @@ void UDirectiveUtilTask_AsyncLoadAsset::Activate()
|
||||
|
||||
void UDirectiveUtilTask_AsyncLoadAsset::OnLoaded()
|
||||
{
|
||||
UObject* LoadedAsset = StreamableHandle.IsValid() ? StreamableHandle->GetLoadedAsset() : nullptr;
|
||||
UObject* LoadedAsset = SoftAsset.Get();
|
||||
if (LoadedAsset)
|
||||
{
|
||||
Completed.Broadcast(LoadedAsset);
|
||||
@@ -120,7 +120,7 @@ void UDirectiveUtilTask_AsyncLoadClass::Activate()
|
||||
|
||||
void UDirectiveUtilTask_AsyncLoadClass::OnLoaded()
|
||||
{
|
||||
UClass* LoadedClass = StreamableHandle.IsValid() ? Cast<UClass>(StreamableHandle->GetLoadedAsset()) : nullptr;
|
||||
UClass* LoadedClass = SoftClass.Get();
|
||||
if (LoadedClass)
|
||||
{
|
||||
Completed.Broadcast(LoadedClass);
|
||||
@@ -157,8 +157,6 @@ UDirectiveUtilTask_AsyncLoadAssets* UDirectiveUtilTask_AsyncLoadAssets::AsyncLoa
|
||||
|
||||
void UDirectiveUtilTask_AsyncLoadAssets::Activate()
|
||||
{
|
||||
// Unset references are filtered out of the request but keep their null slots in the output;
|
||||
// duplicates are requested once and resolved per slot.
|
||||
TArray<FSoftObjectPath> PathsToLoad;
|
||||
for (const TSoftObjectPtr<UObject>& SoftAsset : SoftAssets)
|
||||
{
|
||||
@@ -170,7 +168,6 @@ void UDirectiveUtilTask_AsyncLoadAssets::Activate()
|
||||
|
||||
if (PathsToLoad.Num() == 0)
|
||||
{
|
||||
// Nothing to request; an empty request list is an error path in the streamable manager.
|
||||
OnLoaded();
|
||||
return;
|
||||
}
|
||||
@@ -194,8 +191,6 @@ void UDirectiveUtilTask_AsyncLoadAssets::Activate()
|
||||
return;
|
||||
}
|
||||
|
||||
// Binding fails when the load already finished (all assets were in memory); complete directly,
|
||||
// with the guard keeping the broadcast exactly once.
|
||||
if (!StreamableHandle->BindUpdateDelegate(FStreamableUpdateDelegate::CreateUObject(this, &UDirectiveUtilTask_AsyncLoadAssets::OnUpdate)))
|
||||
{
|
||||
OnLoaded();
|
||||
|
||||
@@ -25,11 +25,28 @@ UDirectiveUtilTask_Delay* UDirectiveUtilTask_Delay::CancellableDelay(UObject* Wo
|
||||
|
||||
void UDirectiveUtilTask_Delay::EndTask()
|
||||
{
|
||||
if (UWorld* World = GEngine ? GEngine->GetWorldFromContextObject(WorldContextObject, EGetWorldErrorMode::ReturnNull) : nullptr)
|
||||
Cancel();
|
||||
}
|
||||
|
||||
void UDirectiveUtilTask_Delay::Cancel()
|
||||
{
|
||||
bFinished = true;
|
||||
if (FTimerManager* TimerManager = GetTimerManager())
|
||||
{
|
||||
World->GetTimerManager().ClearTimer(TimerHandle);
|
||||
TimerManager->ClearTimer(TimerHandle);
|
||||
}
|
||||
SetReadyToDestroy();
|
||||
Completed.Clear();
|
||||
Super::Cancel();
|
||||
}
|
||||
|
||||
bool UDirectiveUtilTask_Delay::IsActive() const
|
||||
{
|
||||
return !bFinished && Super::IsActive();
|
||||
}
|
||||
|
||||
bool UDirectiveUtilTask_Delay::ShouldBroadcastDelegates() const
|
||||
{
|
||||
return !bFinished && Super::ShouldBroadcastDelegates();
|
||||
}
|
||||
|
||||
void UDirectiveUtilTask_Delay::Activate()
|
||||
@@ -40,11 +57,14 @@ void UDirectiveUtilTask_Delay::Activate()
|
||||
if (!World)
|
||||
{
|
||||
UE_LOG(LogDirectiveUtil, Warning, TEXT("Cancellable Delay failed to activate. World is null."));
|
||||
bFinished = true;
|
||||
SetReadyToDestroy();
|
||||
return;
|
||||
}
|
||||
|
||||
const float ClampedDuration = FMath::Max(Duration, KINDA_SMALL_NUMBER);
|
||||
const float ClampedDuration = FMath::IsFinite(Duration)
|
||||
? FMath::Max(Duration, KINDA_SMALL_NUMBER)
|
||||
: KINDA_SMALL_NUMBER;
|
||||
World->GetTimerManager().SetTimer(TimerHandle, this, &UDirectiveUtilTask_Delay::OnDelayComplete, ClampedDuration, false);
|
||||
UE_LOG(LogDirectiveUtil, Verbose, TEXT("Cancellable Delay started for %f seconds."), ClampedDuration);
|
||||
}
|
||||
@@ -52,6 +72,10 @@ void UDirectiveUtilTask_Delay::Activate()
|
||||
void UDirectiveUtilTask_Delay::OnDelayComplete()
|
||||
{
|
||||
UE_LOG(LogDirectiveUtil, Verbose, TEXT("Cancellable Delay completed."));
|
||||
Completed.Broadcast();
|
||||
if (ShouldBroadcastDelegates())
|
||||
{
|
||||
Completed.Broadcast();
|
||||
}
|
||||
bFinished = true;
|
||||
SetReadyToDestroy();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,286 @@
|
||||
// Copyright (c) 2026 Unreal Directive. Licensed under the MIT License.
|
||||
|
||||
#include "Tasks/DirectiveUtilTask_Flow.h"
|
||||
|
||||
#include "DirectiveUtilLogChannels.h"
|
||||
#include "Engine/Engine.h"
|
||||
#include "Engine/World.h"
|
||||
#include "TimerManager.h"
|
||||
|
||||
UDirectiveUtilTask_UpdateForDuration* UDirectiveUtilTask_UpdateForDuration::UpdateForDuration(
|
||||
UObject* WorldContextObject,
|
||||
const float Duration,
|
||||
const float UpdateInterval)
|
||||
{
|
||||
UDirectiveUtilTask_UpdateForDuration* Action = NewObject<UDirectiveUtilTask_UpdateForDuration>();
|
||||
Action->WorldContextObject = WorldContextObject;
|
||||
Action->Duration = Duration;
|
||||
Action->UpdateInterval = UpdateInterval;
|
||||
if (WorldContextObject)
|
||||
{
|
||||
Action->RegisterWithGameInstance(WorldContextObject);
|
||||
}
|
||||
return Action;
|
||||
}
|
||||
|
||||
void UDirectiveUtilTask_UpdateForDuration::Activate()
|
||||
{
|
||||
UWorld* World = WorldContextObject && GEngine
|
||||
? GEngine->GetWorldFromContextObject(WorldContextObject, EGetWorldErrorMode::LogAndReturnNull)
|
||||
: nullptr;
|
||||
if (!World)
|
||||
{
|
||||
UE_LOG(LogDirectiveUtil, Warning, TEXT("Update for Duration failed to activate. World is null."));
|
||||
bFinished = true;
|
||||
SetReadyToDestroy();
|
||||
return;
|
||||
}
|
||||
|
||||
FTimerManager& TimerManager = World->GetTimerManager();
|
||||
if (!FMath::IsFinite(Duration) || Duration <= 0.0f)
|
||||
{
|
||||
CompletionTimerHandle = TimerManager.SetTimerForNextTick(this, &UDirectiveUtilTask_UpdateForDuration::OnComplete);
|
||||
return;
|
||||
}
|
||||
|
||||
TimerManager.SetTimer(
|
||||
CompletionTimerHandle,
|
||||
this,
|
||||
&UDirectiveUtilTask_UpdateForDuration::OnComplete,
|
||||
Duration,
|
||||
false);
|
||||
|
||||
if (!FMath::IsFinite(UpdateInterval) || UpdateInterval <= 0.0f)
|
||||
{
|
||||
UpdateTimerHandle = TimerManager.SetTimerForNextTick(this, &UDirectiveUtilTask_UpdateForDuration::OnUpdate);
|
||||
return;
|
||||
}
|
||||
|
||||
FTimerManagerTimerParameters UpdateParameters;
|
||||
UpdateParameters.bLoop = true;
|
||||
UpdateParameters.bMaxOncePerFrame = true;
|
||||
UpdateParameters.FirstDelay = UpdateInterval;
|
||||
TimerManager.SetTimer(
|
||||
UpdateTimerHandle,
|
||||
this,
|
||||
&UDirectiveUtilTask_UpdateForDuration::OnUpdate,
|
||||
UpdateInterval,
|
||||
UpdateParameters);
|
||||
}
|
||||
|
||||
void UDirectiveUtilTask_UpdateForDuration::Cancel()
|
||||
{
|
||||
bFinished = true;
|
||||
ClearTimers();
|
||||
Updated.Clear();
|
||||
Completed.Clear();
|
||||
Super::Cancel();
|
||||
}
|
||||
|
||||
bool UDirectiveUtilTask_UpdateForDuration::IsActive() const
|
||||
{
|
||||
return !bFinished && Super::IsActive();
|
||||
}
|
||||
|
||||
bool UDirectiveUtilTask_UpdateForDuration::ShouldBroadcastDelegates() const
|
||||
{
|
||||
return !bFinished && Super::ShouldBroadcastDelegates();
|
||||
}
|
||||
|
||||
void UDirectiveUtilTask_UpdateForDuration::OnUpdate()
|
||||
{
|
||||
if (!ShouldBroadcastDelegates())
|
||||
{
|
||||
ClearTimers();
|
||||
return;
|
||||
}
|
||||
|
||||
FTimerManager* TimerManager = GetTimerManager();
|
||||
if (!TimerManager)
|
||||
{
|
||||
Cancel();
|
||||
return;
|
||||
}
|
||||
|
||||
const float RemainingTime = TimerManager->GetTimerRemaining(CompletionTimerHandle);
|
||||
const float ElapsedTime = RemainingTime >= 0.0f
|
||||
? FMath::Clamp(Duration - RemainingTime, 0.0f, Duration)
|
||||
: Duration;
|
||||
BroadcastUpdate(ElapsedTime, ElapsedTime / Duration);
|
||||
if (ShouldBroadcastDelegates() && (!FMath::IsFinite(UpdateInterval) || UpdateInterval <= 0.0f))
|
||||
{
|
||||
UpdateTimerHandle = TimerManager->SetTimerForNextTick(this, &UDirectiveUtilTask_UpdateForDuration::OnUpdate);
|
||||
}
|
||||
}
|
||||
|
||||
void UDirectiveUtilTask_UpdateForDuration::OnComplete()
|
||||
{
|
||||
if (!ShouldBroadcastDelegates())
|
||||
{
|
||||
ClearTimers();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!bHasUpdated || LastElapsedTime < Duration)
|
||||
{
|
||||
const float FinalElapsedTime = FMath::IsFinite(Duration) && Duration > 0.0f ? Duration : 0.0f;
|
||||
BroadcastUpdate(FinalElapsedTime, 1.0f);
|
||||
}
|
||||
if (!ShouldBroadcastDelegates())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ClearTimers();
|
||||
Completed.Broadcast();
|
||||
bFinished = true;
|
||||
SetReadyToDestroy();
|
||||
}
|
||||
|
||||
void UDirectiveUtilTask_UpdateForDuration::BroadcastUpdate(const float ElapsedTime, const float Alpha)
|
||||
{
|
||||
const float DeltaTime = bHasUpdated ? FMath::Max(ElapsedTime - LastElapsedTime, 0.0f) : ElapsedTime;
|
||||
LastElapsedTime = ElapsedTime;
|
||||
bHasUpdated = true;
|
||||
Updated.Broadcast(ElapsedTime, DeltaTime, Alpha);
|
||||
}
|
||||
|
||||
void UDirectiveUtilTask_UpdateForDuration::ClearTimers()
|
||||
{
|
||||
if (FTimerManager* TimerManager = GetTimerManager())
|
||||
{
|
||||
TimerManager->ClearTimer(UpdateTimerHandle);
|
||||
TimerManager->ClearTimer(CompletionTimerHandle);
|
||||
}
|
||||
}
|
||||
|
||||
UDirectiveUtilTask_RepeatWithInterval* UDirectiveUtilTask_RepeatWithInterval::RepeatWithInterval(
|
||||
UObject* WorldContextObject,
|
||||
const int32 Count,
|
||||
const float Interval,
|
||||
const float InitialDelay)
|
||||
{
|
||||
UDirectiveUtilTask_RepeatWithInterval* Action = NewObject<UDirectiveUtilTask_RepeatWithInterval>();
|
||||
Action->WorldContextObject = WorldContextObject;
|
||||
Action->Count = Count;
|
||||
Action->Interval = Interval;
|
||||
Action->InitialDelay = InitialDelay;
|
||||
if (WorldContextObject)
|
||||
{
|
||||
Action->RegisterWithGameInstance(WorldContextObject);
|
||||
}
|
||||
return Action;
|
||||
}
|
||||
|
||||
void UDirectiveUtilTask_RepeatWithInterval::Activate()
|
||||
{
|
||||
UWorld* World = WorldContextObject && GEngine
|
||||
? GEngine->GetWorldFromContextObject(WorldContextObject, EGetWorldErrorMode::LogAndReturnNull)
|
||||
: nullptr;
|
||||
if (!World)
|
||||
{
|
||||
UE_LOG(LogDirectiveUtil, Warning, TEXT("Repeat with Interval failed to activate. World is null."));
|
||||
bFinished = true;
|
||||
SetReadyToDestroy();
|
||||
return;
|
||||
}
|
||||
|
||||
if (Count == 0 || Count < -1)
|
||||
{
|
||||
TimerHandle = World->GetTimerManager().SetTimerForNextTick(this, &UDirectiveUtilTask_RepeatWithInterval::Complete);
|
||||
return;
|
||||
}
|
||||
|
||||
const float SafeInitialDelay = FMath::IsFinite(InitialDelay) && InitialDelay > 0.0f
|
||||
? InitialDelay
|
||||
: 0.0f;
|
||||
Schedule(SafeInitialDelay);
|
||||
}
|
||||
|
||||
void UDirectiveUtilTask_RepeatWithInterval::Cancel()
|
||||
{
|
||||
bFinished = true;
|
||||
ClearTimer();
|
||||
Iteration.Clear();
|
||||
Completed.Clear();
|
||||
Super::Cancel();
|
||||
}
|
||||
|
||||
bool UDirectiveUtilTask_RepeatWithInterval::IsActive() const
|
||||
{
|
||||
return !bFinished && Super::IsActive();
|
||||
}
|
||||
|
||||
bool UDirectiveUtilTask_RepeatWithInterval::ShouldBroadcastDelegates() const
|
||||
{
|
||||
return !bFinished && Super::ShouldBroadcastDelegates();
|
||||
}
|
||||
|
||||
void UDirectiveUtilTask_RepeatWithInterval::Schedule(const float Delay)
|
||||
{
|
||||
FTimerManager* TimerManager = GetTimerManager();
|
||||
if (!TimerManager)
|
||||
{
|
||||
Cancel();
|
||||
return;
|
||||
}
|
||||
|
||||
if (Delay > 0.0f)
|
||||
{
|
||||
TimerManager->SetTimer(TimerHandle, this, &UDirectiveUtilTask_RepeatWithInterval::OnIteration, Delay, false);
|
||||
}
|
||||
else
|
||||
{
|
||||
TimerHandle = TimerManager->SetTimerForNextTick(this, &UDirectiveUtilTask_RepeatWithInterval::OnIteration);
|
||||
}
|
||||
}
|
||||
|
||||
void UDirectiveUtilTask_RepeatWithInterval::OnIteration()
|
||||
{
|
||||
if (!ShouldBroadcastDelegates())
|
||||
{
|
||||
ClearTimer();
|
||||
return;
|
||||
}
|
||||
|
||||
const int32 CurrentIndex = NextIndex;
|
||||
NextIndex = NextIndex == MAX_int32 ? 0 : NextIndex + 1;
|
||||
const int32 Remaining = Count == -1 ? -1 : Count - NextIndex;
|
||||
Iteration.Broadcast(CurrentIndex, Remaining);
|
||||
if (!ShouldBroadcastDelegates())
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (Count != -1 && NextIndex >= Count)
|
||||
{
|
||||
Complete();
|
||||
return;
|
||||
}
|
||||
|
||||
const float SafeInterval = FMath::IsFinite(Interval) && Interval > 0.0f
|
||||
? Interval
|
||||
: 0.0f;
|
||||
Schedule(SafeInterval);
|
||||
}
|
||||
|
||||
void UDirectiveUtilTask_RepeatWithInterval::Complete()
|
||||
{
|
||||
if (!ShouldBroadcastDelegates())
|
||||
{
|
||||
ClearTimer();
|
||||
return;
|
||||
}
|
||||
|
||||
ClearTimer();
|
||||
Completed.Broadcast();
|
||||
bFinished = true;
|
||||
SetReadyToDestroy();
|
||||
}
|
||||
|
||||
void UDirectiveUtilTask_RepeatWithInterval::ClearTimer()
|
||||
{
|
||||
if (FTimerManager* TimerManager = GetTimerManager())
|
||||
{
|
||||
TimerManager->ClearTimer(TimerHandle);
|
||||
}
|
||||
}
|
||||
@@ -6,9 +6,22 @@
|
||||
#include "Blueprint/AIBlueprintHelperLibrary.h"
|
||||
#include "Engine/World.h"
|
||||
#include "DrawDebugHelpers.h"
|
||||
#include "NavigationSystem.h"
|
||||
#include "Navigation/PathFollowingComponent.h"
|
||||
#include "TimerManager.h"
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
UDirectiveUtilTask_MoveToLocation* UDirectiveUtilTask_MoveToLocation::MoveToLocation(
|
||||
UObject* WorldContextObject,
|
||||
@@ -22,9 +35,9 @@ UDirectiveUtilTask_MoveToLocation* UDirectiveUtilTask_MoveToLocation::MoveToLoca
|
||||
UDirectiveUtilTask_MoveToLocation* Action = NewObject<UDirectiveUtilTask_MoveToLocation>();
|
||||
Action->Controller = Controller;
|
||||
Action->Destination = Destination;
|
||||
Action->AcceptanceRadius = AcceptanceRadius;
|
||||
Action->AcceptanceRadius = GetNonNegativeFiniteValue(AcceptanceRadius);
|
||||
Action->bDebugLineTrace = bDebugLineTrace;
|
||||
Action->StuckThreshold = StuckThreshold;
|
||||
Action->StuckThreshold = GetNonNegativeFiniteValue(StuckThreshold);
|
||||
Action->bCheckStuckMovement = bCheckStuckMovement;
|
||||
|
||||
if (WorldContextObject)
|
||||
@@ -38,27 +51,45 @@ UDirectiveUtilTask_MoveToLocation* UDirectiveUtilTask_MoveToLocation::MoveToLoca
|
||||
|
||||
void UDirectiveUtilTask_MoveToLocation::EndTask()
|
||||
{
|
||||
if (IsValid(Controller))
|
||||
{
|
||||
Controller->StopMovement();
|
||||
}
|
||||
ExecuteCompleted(false);
|
||||
}
|
||||
|
||||
void UDirectiveUtilTask_MoveToLocation::Activate()
|
||||
{
|
||||
if(!Controller || !Controller->GetPawn())
|
||||
if (bHasCompleted)
|
||||
{
|
||||
ExecuteCompleted(false);
|
||||
UE_LOG(LogDirectiveUtil, Warning, TEXT("Controller or pawn has been destroyed while moving to location. Aborting."));
|
||||
return;
|
||||
}
|
||||
|
||||
StartLocation = Controller->GetPawn()->GetActorLocation();
|
||||
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))
|
||||
{
|
||||
ExecuteCompleted(false);
|
||||
UE_LOG(LogDirectiveUtil, Verbose, TEXT("Navigation is unavailable while moving to location."));
|
||||
return;
|
||||
}
|
||||
|
||||
TimerWorld = World;
|
||||
StartLocation = Pawn->GetActorLocation();
|
||||
LastCheckedLocation = StartLocation;
|
||||
CurrentLocation = StartLocation;
|
||||
|
||||
Controller->GetWorld()->GetTimerManager().SetTimer(TimerHandle, this, &UDirectiveUtilTask_MoveToLocation::CheckMoveToLocation, 0.1f, true);
|
||||
World->GetTimerManager().SetTimer(TimerHandle, this, &UDirectiveUtilTask_MoveToLocation::CheckMoveToLocation, 0.1f, true);
|
||||
|
||||
if (bCheckStuckMovement)
|
||||
{
|
||||
Controller->GetWorld()->GetTimerManager().SetTimer(StuckTimerHandle, this, &UDirectiveUtilTask_MoveToLocation::CheckStuckMovement, 3.f, true);
|
||||
World->GetTimerManager().SetTimer(StuckTimerHandle, this, &UDirectiveUtilTask_MoveToLocation::CheckStuckMovement, 3.f, true);
|
||||
}
|
||||
|
||||
UAIBlueprintHelperLibrary::SimpleMoveToLocation(Controller, Destination);
|
||||
@@ -67,7 +98,7 @@ void UDirectiveUtilTask_MoveToLocation::Activate()
|
||||
if (bDebugLineTrace)
|
||||
{
|
||||
DrawDebugLine(
|
||||
Controller->GetWorld(),
|
||||
World,
|
||||
Destination + FVector(0, 0, 100),
|
||||
Destination,
|
||||
FColor::Green,
|
||||
@@ -81,18 +112,18 @@ void UDirectiveUtilTask_MoveToLocation::Activate()
|
||||
|
||||
void UDirectiveUtilTask_MoveToLocation::CheckMoveToLocation()
|
||||
{
|
||||
|
||||
if(!Controller || !Controller->GetPawn())
|
||||
APawn* Pawn = IsValid(Controller) ? Controller->GetPawn() : nullptr;
|
||||
if (!IsValid(Pawn) || !TimerWorld.IsValid())
|
||||
{
|
||||
ExecuteCompleted(false);
|
||||
UE_LOG(LogDirectiveUtil, Warning, TEXT("Controller or pawn has been destroyed while moving to location. Aborting."));
|
||||
UE_LOG(LogDirectiveUtil, Warning, TEXT("Controller, pawn, or world is unavailable while moving to location. Aborting."));
|
||||
return;
|
||||
}
|
||||
|
||||
CurrentLocation = Controller->GetPawn()->GetActorLocation();
|
||||
CurrentLocation = Pawn->GetActorLocation();
|
||||
UE_LOG(LogDirectiveUtil, Verbose, TEXT("Controller is moving to location (%s). Current distance: %f."), *Destination.ToString(), FVector::Dist(CurrentLocation, Destination));
|
||||
|
||||
if (FVector::Dist(CurrentLocation, Destination) < AcceptanceRadius)
|
||||
if (IsWithinAcceptanceRadius(CurrentLocation, Destination, AcceptanceRadius))
|
||||
{
|
||||
UE_LOG(LogDirectiveUtil, Verbose, TEXT("Controller has moved to location."));
|
||||
ExecuteCompleted(true);
|
||||
@@ -103,16 +134,17 @@ void UDirectiveUtilTask_MoveToLocation::CheckMoveToLocation()
|
||||
if (!PathFollowing || PathFollowing->GetStatus() == EPathFollowingStatus::Idle)
|
||||
{
|
||||
UE_LOG(LogDirectiveUtil, Verbose, TEXT("Path following has stopped. Completing move to location."));
|
||||
ExecuteCompleted(FVector::Dist(CurrentLocation, Destination) < AcceptanceRadius);
|
||||
ExecuteCompleted(IsWithinAcceptanceRadius(CurrentLocation, Destination, AcceptanceRadius));
|
||||
}
|
||||
}
|
||||
|
||||
void UDirectiveUtilTask_MoveToLocation::CheckStuckMovement()
|
||||
{
|
||||
if(!Controller || !Controller->GetPawn())
|
||||
const APawn* Pawn = IsValid(Controller) ? Controller->GetPawn() : nullptr;
|
||||
if (!IsValid(Pawn) || !TimerWorld.IsValid())
|
||||
{
|
||||
ExecuteCompleted(false);
|
||||
UE_LOG(LogDirectiveUtil, Warning, TEXT("Controller or pawn has been destroyed while moving to location. Aborting."));
|
||||
UE_LOG(LogDirectiveUtil, Warning, TEXT("Controller, pawn, or world is unavailable while moving to location. Aborting."));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -133,13 +165,14 @@ void UDirectiveUtilTask_MoveToLocation::ExecuteCompleted(const bool bSuccess)
|
||||
}
|
||||
bHasCompleted = true;
|
||||
|
||||
UE_LOG(LogDirectiveUtil, Log, TEXT("Movement to location completed. Success: %s."), bSuccess ? TEXT("true") : TEXT("false"));
|
||||
UE_LOG(LogDirectiveUtil, Verbose, TEXT("Movement to location completed. Success: %s."), bSuccess ? TEXT("true") : TEXT("false"));
|
||||
|
||||
if (Controller)
|
||||
if (UWorld* World = TimerWorld.Get())
|
||||
{
|
||||
Controller->GetWorld()->GetTimerManager().ClearTimer(TimerHandle);
|
||||
Controller->GetWorld()->GetTimerManager().ClearTimer(StuckTimerHandle);
|
||||
World->GetTimerManager().ClearTimer(TimerHandle);
|
||||
World->GetTimerManager().ClearTimer(StuckTimerHandle);
|
||||
}
|
||||
TimerWorld.Reset();
|
||||
|
||||
Completed.Broadcast(bSuccess);
|
||||
|
||||
@@ -160,8 +193,8 @@ UDirectiveUtilTask_MoveToActor* UDirectiveUtilTask_MoveToActor::MoveToActor(
|
||||
UDirectiveUtilTask_MoveToActor* Action = NewObject<UDirectiveUtilTask_MoveToActor>();
|
||||
Action->Controller = Controller;
|
||||
Action->Goal = Goal;
|
||||
Action->AcceptanceRadius = AcceptanceRadius;
|
||||
Action->StuckThreshold = StuckThreshold;
|
||||
Action->AcceptanceRadius = GetNonNegativeFiniteValue(AcceptanceRadius);
|
||||
Action->StuckThreshold = GetNonNegativeFiniteValue(StuckThreshold);
|
||||
Action->bCheckStuckMovement = bCheckStuckMovement;
|
||||
|
||||
if (WorldContextObject)
|
||||
@@ -174,27 +207,45 @@ UDirectiveUtilTask_MoveToActor* UDirectiveUtilTask_MoveToActor::MoveToActor(
|
||||
|
||||
void UDirectiveUtilTask_MoveToActor::EndTask()
|
||||
{
|
||||
if (IsValid(Controller))
|
||||
{
|
||||
Controller->StopMovement();
|
||||
}
|
||||
ExecuteCompleted(false);
|
||||
}
|
||||
|
||||
void UDirectiveUtilTask_MoveToActor::Activate()
|
||||
{
|
||||
if(!Controller || !Controller->GetPawn() || !IsValid(Goal))
|
||||
if (bHasCompleted)
|
||||
{
|
||||
ExecuteCompleted(false);
|
||||
UE_LOG(LogDirectiveUtil, Warning, TEXT("Controller, pawn, or goal has been destroyed while moving to actor. Aborting."));
|
||||
return;
|
||||
}
|
||||
|
||||
StartLocation = Controller->GetPawn()->GetActorLocation();
|
||||
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))
|
||||
{
|
||||
ExecuteCompleted(false);
|
||||
UE_LOG(LogDirectiveUtil, Verbose, TEXT("Navigation is unavailable while moving to actor."));
|
||||
return;
|
||||
}
|
||||
|
||||
TimerWorld = World;
|
||||
StartLocation = Pawn->GetActorLocation();
|
||||
LastCheckedLocation = StartLocation;
|
||||
CurrentLocation = StartLocation;
|
||||
|
||||
Controller->GetWorld()->GetTimerManager().SetTimer(TimerHandle, this, &UDirectiveUtilTask_MoveToActor::CheckMoveToActor, 0.1f, true);
|
||||
World->GetTimerManager().SetTimer(TimerHandle, this, &UDirectiveUtilTask_MoveToActor::CheckMoveToActor, 0.1f, true);
|
||||
|
||||
if (bCheckStuckMovement)
|
||||
{
|
||||
Controller->GetWorld()->GetTimerManager().SetTimer(StuckTimerHandle, this, &UDirectiveUtilTask_MoveToActor::CheckStuckMovement, 3.f, true);
|
||||
World->GetTimerManager().SetTimer(StuckTimerHandle, this, &UDirectiveUtilTask_MoveToActor::CheckStuckMovement, 3.f, true);
|
||||
}
|
||||
|
||||
UAIBlueprintHelperLibrary::SimpleMoveToActor(Controller, Goal);
|
||||
@@ -203,10 +254,11 @@ void UDirectiveUtilTask_MoveToActor::Activate()
|
||||
|
||||
void UDirectiveUtilTask_MoveToActor::CheckMoveToActor()
|
||||
{
|
||||
if(!Controller || !Controller->GetPawn())
|
||||
APawn* Pawn = IsValid(Controller) ? Controller->GetPawn() : nullptr;
|
||||
if (!IsValid(Pawn) || !TimerWorld.IsValid())
|
||||
{
|
||||
ExecuteCompleted(false);
|
||||
UE_LOG(LogDirectiveUtil, Warning, TEXT("Controller or pawn has been destroyed while moving to actor. Aborting."));
|
||||
UE_LOG(LogDirectiveUtil, Warning, TEXT("Controller, pawn, or world is unavailable while moving to actor. Aborting."));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -219,10 +271,10 @@ void UDirectiveUtilTask_MoveToActor::CheckMoveToActor()
|
||||
|
||||
// The goal can move, so its location is re-read every poll.
|
||||
const FVector GoalLocation = Goal->GetActorLocation();
|
||||
CurrentLocation = Controller->GetPawn()->GetActorLocation();
|
||||
CurrentLocation = Pawn->GetActorLocation();
|
||||
UE_LOG(LogDirectiveUtil, Verbose, TEXT("Controller is moving to actor (%s). Current distance: %f."), *GetNameSafe(Goal), FVector::Dist(CurrentLocation, GoalLocation));
|
||||
|
||||
if (FVector::Dist(CurrentLocation, GoalLocation) < AcceptanceRadius)
|
||||
if (IsWithinAcceptanceRadius(CurrentLocation, GoalLocation, AcceptanceRadius))
|
||||
{
|
||||
UE_LOG(LogDirectiveUtil, Verbose, TEXT("Controller has moved to actor."));
|
||||
ExecuteCompleted(true);
|
||||
@@ -233,16 +285,17 @@ void UDirectiveUtilTask_MoveToActor::CheckMoveToActor()
|
||||
if (!PathFollowing || PathFollowing->GetStatus() == EPathFollowingStatus::Idle)
|
||||
{
|
||||
UE_LOG(LogDirectiveUtil, Verbose, TEXT("Path following has stopped. Completing move to actor."));
|
||||
ExecuteCompleted(FVector::Dist(CurrentLocation, GoalLocation) < AcceptanceRadius);
|
||||
ExecuteCompleted(IsWithinAcceptanceRadius(CurrentLocation, GoalLocation, AcceptanceRadius));
|
||||
}
|
||||
}
|
||||
|
||||
void UDirectiveUtilTask_MoveToActor::CheckStuckMovement()
|
||||
{
|
||||
if(!Controller || !Controller->GetPawn())
|
||||
const APawn* Pawn = IsValid(Controller) ? Controller->GetPawn() : nullptr;
|
||||
if (!IsValid(Pawn) || !TimerWorld.IsValid())
|
||||
{
|
||||
ExecuteCompleted(false);
|
||||
UE_LOG(LogDirectiveUtil, Warning, TEXT("Controller or pawn has been destroyed while moving to actor. Aborting."));
|
||||
UE_LOG(LogDirectiveUtil, Warning, TEXT("Controller, pawn, or world is unavailable while moving to actor. Aborting."));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -263,13 +316,14 @@ void UDirectiveUtilTask_MoveToActor::ExecuteCompleted(const bool bSuccess)
|
||||
}
|
||||
bHasCompleted = true;
|
||||
|
||||
UE_LOG(LogDirectiveUtil, Log, TEXT("Movement to actor completed. Success: %s."), bSuccess ? TEXT("true") : TEXT("false"));
|
||||
UE_LOG(LogDirectiveUtil, Verbose, TEXT("Movement to actor completed. Success: %s."), bSuccess ? TEXT("true") : TEXT("false"));
|
||||
|
||||
if (Controller)
|
||||
if (UWorld* World = TimerWorld.Get())
|
||||
{
|
||||
Controller->GetWorld()->GetTimerManager().ClearTimer(TimerHandle);
|
||||
Controller->GetWorld()->GetTimerManager().ClearTimer(StuckTimerHandle);
|
||||
World->GetTimerManager().ClearTimer(TimerHandle);
|
||||
World->GetTimerManager().ClearTimer(StuckTimerHandle);
|
||||
}
|
||||
TimerWorld.Reset();
|
||||
|
||||
Completed.Broadcast(bSuccess);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user