Update directive utilities plugin
This commit is contained in:
@@ -13,6 +13,8 @@ public class DirectiveUtilitiesRuntime : ModuleRules
|
||||
new string[]
|
||||
{
|
||||
"Core",
|
||||
"CoreUObject",
|
||||
"Engine",
|
||||
"GameplayTags",
|
||||
"NetCore",
|
||||
}
|
||||
@@ -22,11 +24,10 @@ public class DirectiveUtilitiesRuntime : ModuleRules
|
||||
PrivateDependencyModuleNames.AddRange(
|
||||
new string[]
|
||||
{
|
||||
"CoreUObject",
|
||||
"Engine",
|
||||
"Slate",
|
||||
"SlateCore",
|
||||
"AIModule",
|
||||
"NavigationSystem",
|
||||
"EnhancedInput",
|
||||
"ApplicationCore",
|
||||
}
|
||||
@@ -39,4 +40,4 @@ public class DirectiveUtilitiesRuntime : ModuleRules
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
#include "DirectiveUtilLogChannels.h"
|
||||
// Copyright (c) 2026 Unreal Directive. Licensed under the MIT License.
|
||||
|
||||
#include "DirectiveUtilLogChannels.h"
|
||||
|
||||
DEFINE_LOG_CATEGORY(LogDirectiveUtil);
|
||||
DEFINE_LOG_CATEGORY(LogDirectiveUtilEditor);
|
||||
DEFINE_LOG_CATEGORY(LogDirectiveUtilEditor);
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -2,9 +2,92 @@
|
||||
|
||||
|
||||
#include "Libraries/DirectiveUtilFunctionLibrary.h"
|
||||
#include "Engine/World.h"
|
||||
#include "HAL/CriticalSection.h"
|
||||
#include "HAL/PlatformApplicationMisc.h"
|
||||
#include "HAL/PlatformTime.h"
|
||||
#include "Misc/App.h"
|
||||
#include "Misc/CommandLine.h"
|
||||
#include "Misc/ConfigCacheIni.h"
|
||||
#include "Misc/ScopeLock.h"
|
||||
|
||||
namespace
|
||||
{
|
||||
struct FDirectiveUtilStopwatchState
|
||||
{
|
||||
FCriticalSection Lock;
|
||||
TMap<FName, double> StartTimesByKey;
|
||||
};
|
||||
|
||||
FDirectiveUtilStopwatchState StopwatchState;
|
||||
|
||||
EDirectiveUtilWorldType ToDirectiveWorldType(const EWorldType::Type WorldType)
|
||||
{
|
||||
switch (WorldType)
|
||||
{
|
||||
case EWorldType::None:
|
||||
return EDirectiveUtilWorldType::None;
|
||||
case EWorldType::Game:
|
||||
return EDirectiveUtilWorldType::Game;
|
||||
case EWorldType::Editor:
|
||||
return EDirectiveUtilWorldType::Editor;
|
||||
case EWorldType::PIE:
|
||||
return EDirectiveUtilWorldType::PlayInEditor;
|
||||
case EWorldType::EditorPreview:
|
||||
return EDirectiveUtilWorldType::EditorPreview;
|
||||
case EWorldType::GamePreview:
|
||||
return EDirectiveUtilWorldType::GamePreview;
|
||||
case EWorldType::GameRPC:
|
||||
return EDirectiveUtilWorldType::GameRPC;
|
||||
case EWorldType::Inactive:
|
||||
return EDirectiveUtilWorldType::Inactive;
|
||||
}
|
||||
|
||||
return EDirectiveUtilWorldType::Unknown;
|
||||
}
|
||||
|
||||
EDirectiveUtilBuildConfiguration ToDirectiveBuildConfiguration(const EBuildConfiguration BuildConfiguration)
|
||||
{
|
||||
switch (BuildConfiguration)
|
||||
{
|
||||
case EBuildConfiguration::Unknown:
|
||||
return EDirectiveUtilBuildConfiguration::Unknown;
|
||||
case EBuildConfiguration::Debug:
|
||||
return EDirectiveUtilBuildConfiguration::Debug;
|
||||
case EBuildConfiguration::DebugGame:
|
||||
return EDirectiveUtilBuildConfiguration::DebugGame;
|
||||
case EBuildConfiguration::Development:
|
||||
return EDirectiveUtilBuildConfiguration::Development;
|
||||
case EBuildConfiguration::Shipping:
|
||||
return EDirectiveUtilBuildConfiguration::Shipping;
|
||||
case EBuildConfiguration::Test:
|
||||
return EDirectiveUtilBuildConfiguration::Test;
|
||||
}
|
||||
|
||||
return EDirectiveUtilBuildConfiguration::Unknown;
|
||||
}
|
||||
|
||||
EDirectiveUtilBuildTargetType ToDirectiveBuildTargetType(const EBuildTargetType BuildTargetType)
|
||||
{
|
||||
switch (BuildTargetType)
|
||||
{
|
||||
case EBuildTargetType::Unknown:
|
||||
return EDirectiveUtilBuildTargetType::Unknown;
|
||||
case EBuildTargetType::Game:
|
||||
return EDirectiveUtilBuildTargetType::Game;
|
||||
case EBuildTargetType::Server:
|
||||
return EDirectiveUtilBuildTargetType::Server;
|
||||
case EBuildTargetType::Client:
|
||||
return EDirectiveUtilBuildTargetType::Client;
|
||||
case EBuildTargetType::Editor:
|
||||
return EDirectiveUtilBuildTargetType::Editor;
|
||||
case EBuildTargetType::Program:
|
||||
return EDirectiveUtilBuildTargetType::Program;
|
||||
}
|
||||
|
||||
return EDirectiveUtilBuildTargetType::Unknown;
|
||||
}
|
||||
}
|
||||
|
||||
void UDirectiveUtilFunctionLibrary::GetChildClasses(const UClass* BaseClass, const bool bRecursive, TArray<UClass*>& DerivedClasses)
|
||||
{
|
||||
@@ -57,6 +140,22 @@ bool UDirectiveUtilFunctionLibrary::IsRunningInEditor()
|
||||
return GIsEditor;
|
||||
}
|
||||
|
||||
EDirectiveUtilWorldType UDirectiveUtilFunctionLibrary::GetWorldType(const UObject* WorldContextObject)
|
||||
{
|
||||
const UWorld* World = WorldContextObject ? WorldContextObject->GetWorld() : nullptr;
|
||||
return World ? ToDirectiveWorldType(World->WorldType) : EDirectiveUtilWorldType::Unknown;
|
||||
}
|
||||
|
||||
EDirectiveUtilBuildConfiguration UDirectiveUtilFunctionLibrary::GetBuildConfigurationType()
|
||||
{
|
||||
return ToDirectiveBuildConfiguration(FApp::GetBuildConfiguration());
|
||||
}
|
||||
|
||||
EDirectiveUtilBuildTargetType UDirectiveUtilFunctionLibrary::GetBuildTargetType()
|
||||
{
|
||||
return ToDirectiveBuildTargetType(FApp::GetBuildTargetType());
|
||||
}
|
||||
|
||||
bool UDirectiveUtilFunctionLibrary::HasCommandLineSwitch(const FString& Switch)
|
||||
{
|
||||
return HasCommandLineSwitch(FCommandLine::Get(), Switch);
|
||||
@@ -67,6 +166,44 @@ bool UDirectiveUtilFunctionLibrary::GetCommandLineOption(const FString& Key, FSt
|
||||
return GetCommandLineOption(FCommandLine::Get(), Key, OutValue);
|
||||
}
|
||||
|
||||
bool UDirectiveUtilFunctionLibrary::StartStopwatch(const FName Key, const bool bRestartIfRunning)
|
||||
{
|
||||
if (Key.IsNone())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
FScopeLock Lock(&StopwatchState.Lock);
|
||||
if (!bRestartIfRunning && StopwatchState.StartTimesByKey.Contains(Key))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
StopwatchState.StartTimesByKey.Add(Key, FPlatformTime::Seconds());
|
||||
return true;
|
||||
}
|
||||
|
||||
bool UDirectiveUtilFunctionLibrary::StopStopwatch(const FName Key, double& ElapsedMilliseconds)
|
||||
{
|
||||
ElapsedMilliseconds = 0.0;
|
||||
if (Key.IsNone())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
double StartTime = 0.0;
|
||||
{
|
||||
FScopeLock Lock(&StopwatchState.Lock);
|
||||
if (!StopwatchState.StartTimesByKey.RemoveAndCopyValue(Key, StartTime))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
ElapsedMilliseconds = FMath::Max((FPlatformTime::Seconds() - StartTime) * 1000.0, 0.0);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool UDirectiveUtilFunctionLibrary::HasCommandLineSwitch(const TCHAR* CommandLine, const FString& Switch)
|
||||
{
|
||||
if (Switch.IsEmpty())
|
||||
|
||||
@@ -27,13 +27,26 @@ FGameplayTagContainer UDirectiveUtilGameplayTagFunctionLibrary::GetTagParents(co
|
||||
|
||||
int32 UDirectiveUtilGameplayTagFunctionLibrary::GetTagDepth(const FGameplayTag& Tag)
|
||||
{
|
||||
return GetTagSegments(Tag).Num();
|
||||
int32 Depth = 0;
|
||||
for (FGameplayTag Current = Tag; Current.IsValid(); Current = Current.RequestDirectParent())
|
||||
{
|
||||
++Depth;
|
||||
}
|
||||
return Depth;
|
||||
}
|
||||
|
||||
FString UDirectiveUtilGameplayTagFunctionLibrary::GetTagLeafName(const FGameplayTag& Tag)
|
||||
{
|
||||
const TArray<FString> Segments = GetTagSegments(Tag);
|
||||
return Segments.Num() > 0 ? Segments.Last() : FString();
|
||||
if (!Tag.IsValid())
|
||||
{
|
||||
return FString();
|
||||
}
|
||||
|
||||
const FString TagString = Tag.GetTagName().ToString();
|
||||
int32 SeparatorIndex = INDEX_NONE;
|
||||
return TagString.FindLastChar(TEXT('.'), SeparatorIndex)
|
||||
? TagString.Mid(SeparatorIndex + 1)
|
||||
: TagString;
|
||||
}
|
||||
|
||||
TArray<FString> UDirectiveUtilGameplayTagFunctionLibrary::GetTagSegments(const FGameplayTag& Tag)
|
||||
@@ -64,10 +77,9 @@ FGameplayTagContainer UDirectiveUtilGameplayTagFunctionLibrary::GetTagDirectChil
|
||||
return DirectChildren;
|
||||
}
|
||||
|
||||
const int32 DirectChildDepth = GetTagDepth(Tag) + 1;
|
||||
for (const FGameplayTag& Child : GetTagChildren(Tag))
|
||||
{
|
||||
if (GetTagDepth(Child) == DirectChildDepth)
|
||||
if (Child.RequestDirectParent() == Tag)
|
||||
{
|
||||
DirectChildren.AddTag(Child);
|
||||
}
|
||||
@@ -77,29 +89,31 @@ FGameplayTagContainer UDirectiveUtilGameplayTagFunctionLibrary::GetTagDirectChil
|
||||
|
||||
FGameplayTag UDirectiveUtilGameplayTagFunctionLibrary::GetTagCommonAncestor(const FGameplayTag& TagA, const FGameplayTag& TagB)
|
||||
{
|
||||
const TArray<FString> SegmentsA = GetTagSegments(TagA);
|
||||
const TArray<FString> SegmentsB = GetTagSegments(TagB);
|
||||
|
||||
FString Prefix;
|
||||
for (int32 Index = 0; Index < SegmentsA.Num() && Index < SegmentsB.Num(); ++Index)
|
||||
{
|
||||
if (!SegmentsA[Index].Equals(SegmentsB[Index]))
|
||||
{
|
||||
break;
|
||||
}
|
||||
if (!Prefix.IsEmpty())
|
||||
{
|
||||
Prefix += TEXT(".");
|
||||
}
|
||||
Prefix += SegmentsA[Index];
|
||||
}
|
||||
|
||||
if (Prefix.IsEmpty())
|
||||
if (!TagA.IsValid() || !TagB.IsValid())
|
||||
{
|
||||
return FGameplayTag();
|
||||
}
|
||||
// A common prefix of two registered tags is itself registered (parents auto-register).
|
||||
return FGameplayTag::RequestGameplayTag(FName(*Prefix), false);
|
||||
|
||||
FGameplayTag AncestorA = TagA;
|
||||
FGameplayTag AncestorB = TagB;
|
||||
int32 DepthA = GetTagDepth(AncestorA);
|
||||
int32 DepthB = GetTagDepth(AncestorB);
|
||||
while (DepthA > DepthB)
|
||||
{
|
||||
AncestorA = AncestorA.RequestDirectParent();
|
||||
--DepthA;
|
||||
}
|
||||
while (DepthB > DepthA)
|
||||
{
|
||||
AncestorB = AncestorB.RequestDirectParent();
|
||||
--DepthB;
|
||||
}
|
||||
while (AncestorA.IsValid() && AncestorB.IsValid() && AncestorA != AncestorB)
|
||||
{
|
||||
AncestorA = AncestorA.RequestDirectParent();
|
||||
AncestorB = AncestorB.RequestDirectParent();
|
||||
}
|
||||
return AncestorA == AncestorB ? AncestorA : FGameplayTag();
|
||||
}
|
||||
|
||||
FGameplayTag UDirectiveUtilGameplayTagFunctionLibrary::GetTagAtDepth(const FGameplayTag& Tag, const int32 Depth)
|
||||
@@ -109,20 +123,18 @@ FGameplayTag UDirectiveUtilGameplayTagFunctionLibrary::GetTagAtDepth(const FGame
|
||||
return FGameplayTag();
|
||||
}
|
||||
|
||||
const TArray<FString> Segments = GetTagSegments(Tag);
|
||||
if (Depth >= Segments.Num())
|
||||
const int32 TagDepth = GetTagDepth(Tag);
|
||||
if (Depth >= TagDepth)
|
||||
{
|
||||
return Tag;
|
||||
}
|
||||
|
||||
FString Prefix = Segments[0];
|
||||
for (int32 Index = 1; Index < Depth; ++Index)
|
||||
FGameplayTag Ancestor = Tag;
|
||||
for (int32 CurrentDepth = TagDepth; CurrentDepth > Depth; --CurrentDepth)
|
||||
{
|
||||
Prefix += TEXT(".");
|
||||
Prefix += Segments[Index];
|
||||
Ancestor = Ancestor.RequestDirectParent();
|
||||
}
|
||||
// An ancestor of a registered tag is always registered itself (parents auto-register).
|
||||
return FGameplayTag::RequestGameplayTag(FName(*Prefix), false);
|
||||
return Ancestor;
|
||||
}
|
||||
|
||||
FGameplayTagContainer UDirectiveUtilGameplayTagFunctionLibrary::GetTagSiblings(const FGameplayTag& Tag)
|
||||
|
||||
@@ -77,8 +77,11 @@ EDirectiveUtilSuccessStatus UDirectiveUtilInputFunctionLibrary::AddInputMappingC
|
||||
|
||||
if (FailedIndices.Num() > 0)
|
||||
{
|
||||
FString FailedIndicesStr = FString::JoinBy(FailedIndices, TEXT(", "), [](const int32 Index) { return FString::Printf(TEXT("%d"), Index); });
|
||||
UE_LOGFMT(LogDirectiveUtil, Warning, "{FailedIndicies} Input Mapping Contexts failed to load and were not added! The failed indexes are [{FailedIndicieIndexes}]", FailedIndices.Num(), FailedIndicesStr);
|
||||
const FString FailedIndicesString = FString::JoinBy(
|
||||
FailedIndices, TEXT(", "), [](const int32 Index) { return FString::FromInt(Index); });
|
||||
UE_LOGFMT(LogDirectiveUtil, Warning,
|
||||
"{FailedContextCount} input mapping contexts could not be loaded. Indexes: [{FailedContextIndexes}]",
|
||||
FailedIndices.Num(), FailedIndicesString);
|
||||
}
|
||||
|
||||
if (LoadedContexts.IsEmpty())
|
||||
@@ -104,7 +107,7 @@ EDirectiveUtilSuccessStatus UDirectiveUtilInputFunctionLibrary::RemoveInputMappi
|
||||
AController* PlayerController,
|
||||
const TArray<TSoftObjectPtr<UInputMappingContext>>& Contexts)
|
||||
{
|
||||
if (Contexts.IsEmpty()) { return EDirectiveUtilSuccessStatus::Failure;; }
|
||||
if (Contexts.IsEmpty()) { return EDirectiveUtilSuccessStatus::Failure; }
|
||||
|
||||
UEnhancedInputLocalPlayerSubsystem* EnhancedInput;
|
||||
const bool bEnhancedInputRetrievedFromController = TryGetEnhancedInputSubsystemFromController(PlayerController, EnhancedInput);
|
||||
@@ -118,10 +121,11 @@ EDirectiveUtilSuccessStatus UDirectiveUtilInputFunctionLibrary::RemoveInputMappi
|
||||
for (int32 Index = 0; Index < Contexts.Num(); ++Index)
|
||||
{
|
||||
const TSoftObjectPtr<UInputMappingContext>& Context = Contexts[Index];
|
||||
if (const UInputMappingContext* MappingContext = Context.LoadSynchronous())
|
||||
if (const UInputMappingContext* MappingContext = Context.Get())
|
||||
{
|
||||
EnhancedInput->RemoveMappingContext(MappingContext);
|
||||
} else
|
||||
}
|
||||
else
|
||||
{
|
||||
FailedIndices.Add(Index);
|
||||
}
|
||||
@@ -129,8 +133,11 @@ EDirectiveUtilSuccessStatus UDirectiveUtilInputFunctionLibrary::RemoveInputMappi
|
||||
|
||||
if (FailedIndices.Num() > 0)
|
||||
{
|
||||
FString FailedIndicesStr = FString::JoinBy(FailedIndices, TEXT(", "), [](const int32 Index) { return FString::Printf(TEXT("%d"), Index); });
|
||||
UE_LOGFMT(LogDirectiveUtil, Warning, "{FailedIndicies} Input Mapping Contexts failed to load and were not removed! The failed indexes are [{FailedIndicieIndexes}]", FailedIndices.Num(), FailedIndicesStr);
|
||||
const FString FailedIndicesString = FString::JoinBy(
|
||||
FailedIndices, TEXT(", "), [](const int32 Index) { return FString::FromInt(Index); });
|
||||
UE_LOGFMT(LogDirectiveUtil, Warning,
|
||||
"{FailedContextCount} input mapping contexts were not loaded and could not be removed. Indexes: [{FailedContextIndexes}]",
|
||||
FailedIndices.Num(), FailedIndicesString);
|
||||
}
|
||||
|
||||
if (FailedIndices.Num() == Contexts.Num())
|
||||
@@ -143,28 +150,27 @@ EDirectiveUtilSuccessStatus UDirectiveUtilInputFunctionLibrary::RemoveInputMappi
|
||||
}
|
||||
|
||||
EDirectiveUtilSuccessStatus UDirectiveUtilInputFunctionLibrary::SwapInputMappingContexts(
|
||||
AController* PlayerController,
|
||||
const TSoftObjectPtr<UInputMappingContext> PreviousContext,
|
||||
const TSoftObjectPtr<UInputMappingContext> NewContext,
|
||||
const int32 Priority,
|
||||
const bool bUsePreviousPriority)
|
||||
AController* PlayerController,
|
||||
const TSoftObjectPtr<UInputMappingContext> PreviousContext,
|
||||
const TSoftObjectPtr<UInputMappingContext> NewContext,
|
||||
const int32 Priority,
|
||||
const bool bUsePreviousPriority)
|
||||
{
|
||||
const UInputMappingContext* LoadedPreviousMappingContext = PreviousContext.LoadSynchronous();
|
||||
const UInputMappingContext* LoadedNewMappingContext = NewContext.LoadSynchronous();
|
||||
|
||||
if (!LoadedPreviousMappingContext || !LoadedNewMappingContext)
|
||||
{
|
||||
UE_LOGFMT(LogDirectiveUtil, Warning, "Both the previous and new input mapping contexts must be valid.");
|
||||
return EDirectiveUtilSuccessStatus::Failure;
|
||||
}
|
||||
|
||||
UEnhancedInputLocalPlayerSubsystem* EnhancedInput;
|
||||
if (!TryGetEnhancedInputSubsystemFromController(PlayerController, EnhancedInput))
|
||||
{
|
||||
return EDirectiveUtilSuccessStatus::Failure;
|
||||
}
|
||||
|
||||
if (int32 PreviousPriority; EnhancedInput->HasMappingContext(LoadedPreviousMappingContext, PreviousPriority))
|
||||
const UInputMappingContext* LoadedNewMappingContext = NewContext.LoadSynchronous();
|
||||
if (!LoadedNewMappingContext)
|
||||
{
|
||||
UE_LOGFMT(LogDirectiveUtil, Warning, "The new input mapping context could not be loaded.");
|
||||
return EDirectiveUtilSuccessStatus::Failure;
|
||||
}
|
||||
const UInputMappingContext* LoadedPreviousMappingContext = PreviousContext.Get();
|
||||
|
||||
if (int32 PreviousPriority; LoadedPreviousMappingContext && EnhancedInput->HasMappingContext(LoadedPreviousMappingContext, PreviousPriority))
|
||||
{
|
||||
const int32 TargetPriority = bUsePreviousPriority ? PreviousPriority : Priority;
|
||||
EnhancedInput->RemoveMappingContext(LoadedPreviousMappingContext);
|
||||
@@ -174,7 +180,7 @@ EDirectiveUtilSuccessStatus UDirectiveUtilInputFunctionLibrary::SwapInputMapping
|
||||
else
|
||||
{
|
||||
EnhancedInput->AddMappingContext(LoadedNewMappingContext, Priority);
|
||||
UE_LOGFMT(LogDirectiveUtil, Warning, "Previous input mapping context {PreviousContext} not found. New context {NewContext} added at priority {BackupPriority}.", LoadedPreviousMappingContext->GetName(), LoadedNewMappingContext->GetName(), Priority);
|
||||
UE_LOGFMT(LogDirectiveUtil, Verbose, "Previous input mapping context was not active. New context {NewContext} added at priority {BackupPriority}.", LoadedNewMappingContext->GetName(), Priority);
|
||||
}
|
||||
|
||||
return EDirectiveUtilSuccessStatus::Success;
|
||||
@@ -189,7 +195,7 @@ UEnhancedInputLocalPlayerSubsystem* UDirectiveUtilInputFunctionLibrary::GetEnhan
|
||||
|
||||
bool UDirectiveUtilInputFunctionLibrary::IsInputMappingContextActive(AController* PlayerController, TSoftObjectPtr<UInputMappingContext> Context)
|
||||
{
|
||||
const UInputMappingContext* MappingContext = Context.LoadSynchronous();
|
||||
const UInputMappingContext* MappingContext = Context.Get();
|
||||
if (!MappingContext)
|
||||
{
|
||||
return false;
|
||||
|
||||
@@ -179,7 +179,6 @@ void UDirectiveUtilMapFunctionLibrary::GenericMap_Append(
|
||||
return;
|
||||
}
|
||||
|
||||
// Appending a map onto itself is a no-op; bail before AddPair can reallocate under the source pointers.
|
||||
if (TargetMap == SourceMap)
|
||||
{
|
||||
return;
|
||||
|
||||
@@ -0,0 +1,214 @@
|
||||
// Copyright (c) 2026 Unreal Directive. Licensed under the MIT License.
|
||||
|
||||
#include "Libraries/DirectiveUtilMathFunctionLibrary.h"
|
||||
|
||||
namespace
|
||||
{
|
||||
double EaseBackIn(double t)
|
||||
{
|
||||
if (t <= 0.0) { return 0.0; }
|
||||
if (t >= 1.0) { return 1.0; }
|
||||
const double s = 1.70158;
|
||||
return t * t * ((s + 1.0) * t - s);
|
||||
}
|
||||
|
||||
double EaseBackOut(double t)
|
||||
{
|
||||
if (t <= 0.0) { return 0.0; }
|
||||
if (t >= 1.0) { return 1.0; }
|
||||
const double s = 1.70158;
|
||||
t -= 1.0;
|
||||
return t * t * ((s + 1.0) * t + s) + 1.0;
|
||||
}
|
||||
|
||||
double EaseBackInOut(double t)
|
||||
{
|
||||
if (t <= 0.0) { return 0.0; }
|
||||
if (t >= 1.0) { return 1.0; }
|
||||
const double s = 1.70158 * 1.525;
|
||||
t *= 2.0;
|
||||
if (t < 1.0)
|
||||
{
|
||||
return 0.5 * (t * t * ((s + 1.0) * t - s));
|
||||
}
|
||||
t -= 2.0;
|
||||
return 0.5 * (t * t * ((s + 1.0) * t + s) + 2.0);
|
||||
}
|
||||
|
||||
double EaseElasticIn(double t)
|
||||
{
|
||||
if (t <= 0.0) { return 0.0; }
|
||||
if (t >= 1.0) { return 1.0; }
|
||||
const double p = 0.3;
|
||||
const double s = p / 4.0;
|
||||
t -= 1.0;
|
||||
return -(FMath::Pow(2.0, 10.0 * t) * FMath::Sin((t - s) * (2.0 * PI) / p));
|
||||
}
|
||||
|
||||
double EaseElasticOut(double t)
|
||||
{
|
||||
if (t <= 0.0) { return 0.0; }
|
||||
if (t >= 1.0) { return 1.0; }
|
||||
const double p = 0.3;
|
||||
const double s = p / 4.0;
|
||||
return FMath::Pow(2.0, -10.0 * t) * FMath::Sin((t - s) * (2.0 * PI) / p) + 1.0;
|
||||
}
|
||||
|
||||
double EaseElasticInOut(double t)
|
||||
{
|
||||
if (t <= 0.0) { return 0.0; }
|
||||
if (t >= 1.0) { return 1.0; }
|
||||
const double p = 0.3 * 1.5;
|
||||
const double s = p / 4.0;
|
||||
t *= 2.0;
|
||||
if (t < 1.0)
|
||||
{
|
||||
t -= 1.0;
|
||||
return -0.5 * (FMath::Pow(2.0, 10.0 * t) * FMath::Sin((t - s) * (2.0 * PI) / p));
|
||||
}
|
||||
t -= 1.0;
|
||||
return FMath::Pow(2.0, -10.0 * t) * FMath::Sin((t - s) * (2.0 * PI) / p) * 0.5 + 1.0;
|
||||
}
|
||||
|
||||
double EaseBounceOut(double t)
|
||||
{
|
||||
const double n1 = 7.5625;
|
||||
const double d1 = 2.75;
|
||||
if (t < 1.0 / d1)
|
||||
{
|
||||
return n1 * t * t;
|
||||
}
|
||||
if (t < 2.0 / d1)
|
||||
{
|
||||
t -= 1.5 / d1;
|
||||
return n1 * t * t + 0.75;
|
||||
}
|
||||
if (t < 2.5 / d1)
|
||||
{
|
||||
t -= 2.25 / d1;
|
||||
return n1 * t * t + 0.9375;
|
||||
}
|
||||
t -= 2.625 / d1;
|
||||
return n1 * t * t + 0.984375;
|
||||
}
|
||||
|
||||
double EaseBounceIn(double t)
|
||||
{
|
||||
return 1.0 - EaseBounceOut(1.0 - t);
|
||||
}
|
||||
|
||||
double EaseBounceInOut(double t)
|
||||
{
|
||||
return t < 0.5
|
||||
? (1.0 - EaseBounceOut(1.0 - 2.0 * t)) * 0.5
|
||||
: (1.0 + EaseBounceOut(2.0 * t - 1.0)) * 0.5;
|
||||
}
|
||||
|
||||
|
||||
FTransform BlendEasedTransforms(const FTransform& A, const FTransform& B, const double Alpha)
|
||||
{
|
||||
FQuat Rotation = FQuat::Slerp(A.GetRotation(), B.GetRotation(), Alpha);
|
||||
Rotation.Normalize();
|
||||
return FTransform(Rotation,
|
||||
FMath::Lerp(A.GetLocation(), B.GetLocation(), Alpha),
|
||||
FMath::Lerp(A.GetScale3D(), B.GetScale3D(), Alpha));
|
||||
}
|
||||
|
||||
template <typename ValueType, typename BlendType>
|
||||
TArray<ValueType> EaseArrays(const TArray<ValueType>& From, const TArray<ValueType>& To, const float Alpha,
|
||||
const EDirectiveUtilEaseType EaseType, const TArray<float>& PerElementAlphas, BlendType Blend)
|
||||
{
|
||||
const bool bPerElement = !PerElementAlphas.IsEmpty();
|
||||
if (From.Num() != To.Num() || !FMath::IsFinite(Alpha)
|
||||
|| (bPerElement && PerElementAlphas.Num() != From.Num()))
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
const float SharedEasedAlpha = UDirectiveUtilMathFunctionLibrary::EaseAlpha(Alpha, EaseType);
|
||||
TArray<ValueType> Result;
|
||||
Result.SetNumUninitialized(From.Num());
|
||||
for (int32 Index = 0; Index < From.Num(); ++Index)
|
||||
{
|
||||
float EasedAlpha = SharedEasedAlpha;
|
||||
if (bPerElement)
|
||||
{
|
||||
if (!FMath::IsFinite(PerElementAlphas[Index]))
|
||||
{
|
||||
return {};
|
||||
}
|
||||
EasedAlpha = UDirectiveUtilMathFunctionLibrary::EaseAlpha(PerElementAlphas[Index], EaseType);
|
||||
}
|
||||
if (From[Index].ContainsNaN() || To[Index].ContainsNaN())
|
||||
{
|
||||
return {};
|
||||
}
|
||||
Result[Index] = Blend(From[Index], To[Index], static_cast<double>(EasedAlpha));
|
||||
}
|
||||
return Result;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
float UDirectiveUtilMathFunctionLibrary::EaseAlpha(const float Alpha, const EDirectiveUtilEaseType EaseType)
|
||||
{
|
||||
const double t = static_cast<double>(FMath::Clamp(Alpha, 0.0f, 1.0f));
|
||||
switch (EaseType)
|
||||
{
|
||||
case EDirectiveUtilEaseType::BackIn: return static_cast<float>(EaseBackIn(t));
|
||||
case EDirectiveUtilEaseType::BackOut: return static_cast<float>(EaseBackOut(t));
|
||||
case EDirectiveUtilEaseType::BackInOut: return static_cast<float>(EaseBackInOut(t));
|
||||
case EDirectiveUtilEaseType::ElasticIn: return static_cast<float>(EaseElasticIn(t));
|
||||
case EDirectiveUtilEaseType::ElasticOut: return static_cast<float>(EaseElasticOut(t));
|
||||
case EDirectiveUtilEaseType::ElasticInOut: return static_cast<float>(EaseElasticInOut(t));
|
||||
case EDirectiveUtilEaseType::BounceIn: return static_cast<float>(EaseBounceIn(t));
|
||||
case EDirectiveUtilEaseType::BounceOut: return static_cast<float>(EaseBounceOut(t));
|
||||
case EDirectiveUtilEaseType::BounceInOut: return static_cast<float>(EaseBounceInOut(t));
|
||||
case EDirectiveUtilEaseType::Linear:
|
||||
default: return static_cast<float>(t);
|
||||
}
|
||||
}
|
||||
|
||||
float UDirectiveUtilMathFunctionLibrary::EaseFloat(const float A, const float B, const float Alpha, const EDirectiveUtilEaseType EaseType)
|
||||
{
|
||||
return FMath::Lerp(A, B, EaseAlpha(Alpha, EaseType));
|
||||
}
|
||||
|
||||
FVector UDirectiveUtilMathFunctionLibrary::EaseVector(const FVector& A, const FVector& B, const float Alpha, const EDirectiveUtilEaseType EaseType)
|
||||
{
|
||||
return FMath::Lerp(A, B, static_cast<double>(EaseAlpha(Alpha, EaseType)));
|
||||
}
|
||||
|
||||
FRotator UDirectiveUtilMathFunctionLibrary::EaseRotator(const FRotator& A, const FRotator& B, const float Alpha, const EDirectiveUtilEaseType EaseType)
|
||||
{
|
||||
return FQuat::Slerp(A.Quaternion(), B.Quaternion(), EaseAlpha(Alpha, EaseType)).Rotator();
|
||||
}
|
||||
|
||||
FLinearColor UDirectiveUtilMathFunctionLibrary::EaseColor(const FLinearColor& A, const FLinearColor& B, const float Alpha, const EDirectiveUtilEaseType EaseType)
|
||||
{
|
||||
return FMath::Lerp(A, B, EaseAlpha(Alpha, EaseType));
|
||||
}
|
||||
|
||||
FTransform UDirectiveUtilMathFunctionLibrary::EaseTransform(const FTransform& A, const FTransform& B,
|
||||
const float Alpha, const EDirectiveUtilEaseType EaseType)
|
||||
{
|
||||
return BlendEasedTransforms(A, B, static_cast<double>(EaseAlpha(Alpha, EaseType)));
|
||||
}
|
||||
|
||||
TArray<FVector> UDirectiveUtilMathFunctionLibrary::EaseLocationArrays(const TArray<FVector>& From,
|
||||
const TArray<FVector>& To, const float Alpha, const EDirectiveUtilEaseType EaseType,
|
||||
const TArray<float>& PerElementAlphas)
|
||||
{
|
||||
return EaseArrays(From, To, Alpha, EaseType, PerElementAlphas,
|
||||
[](const FVector& A, const FVector& B, const double EasedAlpha)
|
||||
{
|
||||
return FMath::Lerp(A, B, EasedAlpha);
|
||||
});
|
||||
}
|
||||
|
||||
TArray<FTransform> UDirectiveUtilMathFunctionLibrary::EaseTransformArrays(const TArray<FTransform>& From,
|
||||
const TArray<FTransform>& To, const float Alpha, const EDirectiveUtilEaseType EaseType,
|
||||
const TArray<float>& PerElementAlphas)
|
||||
{
|
||||
return EaseArrays(From, To, Alpha, EaseType, PerElementAlphas, &BlendEasedTransforms);
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,219 @@
|
||||
// Copyright (c) 2026 Unreal Directive. Licensed under the MIT License.
|
||||
|
||||
#include "Libraries/DirectiveUtilMathFunctionLibrary.h"
|
||||
|
||||
namespace
|
||||
{
|
||||
float GetUsableRandomWeight(const float Weight)
|
||||
{
|
||||
return FMath::IsFinite(Weight) && Weight > 0.0f ? Weight : 0.0f;
|
||||
}
|
||||
|
||||
FVector2D MakeRandomPointInAnnulus(const double InnerRadius, const double OuterRadius,
|
||||
const double AngleSample, const double RadiusSample)
|
||||
{
|
||||
const double Angle = AngleSample * UE_TWO_PI;
|
||||
const double Radius = FMath::Sqrt(FMath::Lerp(
|
||||
InnerRadius * InnerRadius,
|
||||
OuterRadius * OuterRadius,
|
||||
RadiusSample));
|
||||
return FVector2D(FMath::Cos(Angle) * Radius, FMath::Sin(Angle) * Radius);
|
||||
}
|
||||
|
||||
template <typename RandomFractionFunction>
|
||||
FVector MakeRandomPointInSphere(const double Radius, RandomFractionFunction&& RandomFraction)
|
||||
{
|
||||
FVector Point;
|
||||
double SizeSquared;
|
||||
do
|
||||
{
|
||||
const double X = static_cast<double>(RandomFraction()) * 2.0 - 1.0;
|
||||
const double Y = static_cast<double>(RandomFraction()) * 2.0 - 1.0;
|
||||
const double Z = static_cast<double>(RandomFraction()) * 2.0 - 1.0;
|
||||
Point = FVector(X, Y, Z);
|
||||
SizeSquared = Point.SizeSquared();
|
||||
}
|
||||
while (SizeSquared > 1.0);
|
||||
|
||||
return Point * Radius;
|
||||
}
|
||||
}
|
||||
|
||||
int32 UDirectiveUtilMathFunctionLibrary::GetRandomIndexFromWeights(const TArray<float>& Weights)
|
||||
{
|
||||
double Total = 0.0;
|
||||
for (const float Weight : Weights)
|
||||
{
|
||||
Total += GetUsableRandomWeight(Weight);
|
||||
}
|
||||
|
||||
if (Total <= 0.0)
|
||||
{
|
||||
return INDEX_NONE;
|
||||
}
|
||||
|
||||
const double Roll = static_cast<double>(FMath::FRand()) * Total;
|
||||
double Accumulated = 0.0;
|
||||
int32 LastPositiveIndex = INDEX_NONE;
|
||||
for (int32 Index = 0; Index < Weights.Num(); ++Index)
|
||||
{
|
||||
const float Weight = GetUsableRandomWeight(Weights[Index]);
|
||||
if (Weight <= 0.0f)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
LastPositiveIndex = Index;
|
||||
Accumulated += Weight;
|
||||
if (Roll < Accumulated)
|
||||
{
|
||||
return Index;
|
||||
}
|
||||
}
|
||||
|
||||
return LastPositiveIndex;
|
||||
}
|
||||
|
||||
int32 UDirectiveUtilMathFunctionLibrary::GetRandomIndexFromWeightsFromStream(FRandomStream& Stream, const TArray<float>& Weights)
|
||||
{
|
||||
double Total = 0.0;
|
||||
for (const float Weight : Weights)
|
||||
{
|
||||
Total += GetUsableRandomWeight(Weight);
|
||||
}
|
||||
|
||||
if (Total <= 0.0)
|
||||
{
|
||||
return INDEX_NONE;
|
||||
}
|
||||
|
||||
const double Roll = static_cast<double>(Stream.FRand()) * Total;
|
||||
double Accumulated = 0.0;
|
||||
int32 LastPositiveIndex = INDEX_NONE;
|
||||
for (int32 Index = 0; Index < Weights.Num(); ++Index)
|
||||
{
|
||||
const float Weight = GetUsableRandomWeight(Weights[Index]);
|
||||
if (Weight <= 0.0f)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
LastPositiveIndex = Index;
|
||||
Accumulated += Weight;
|
||||
if (Roll < Accumulated)
|
||||
{
|
||||
return Index;
|
||||
}
|
||||
}
|
||||
|
||||
return LastPositiveIndex;
|
||||
}
|
||||
|
||||
FVector2D UDirectiveUtilMathFunctionLibrary::RandomPointInCircle(const float Radius)
|
||||
{
|
||||
if (!FMath::IsFinite(Radius))
|
||||
{
|
||||
return FVector2D::ZeroVector;
|
||||
}
|
||||
|
||||
const double AbsoluteRadius = FMath::Abs(static_cast<double>(Radius));
|
||||
if (AbsoluteRadius == 0.0)
|
||||
{
|
||||
return FVector2D::ZeroVector;
|
||||
}
|
||||
const double AngleSample = FMath::FRand();
|
||||
const double RadiusSample = FMath::FRand();
|
||||
return MakeRandomPointInAnnulus(0.0, AbsoluteRadius, AngleSample, RadiusSample);
|
||||
}
|
||||
|
||||
FVector2D UDirectiveUtilMathFunctionLibrary::RandomPointInCircleFromStream(FRandomStream& Stream, const float Radius)
|
||||
{
|
||||
if (!FMath::IsFinite(Radius))
|
||||
{
|
||||
return FVector2D::ZeroVector;
|
||||
}
|
||||
|
||||
const double AbsoluteRadius = FMath::Abs(static_cast<double>(Radius));
|
||||
if (AbsoluteRadius == 0.0)
|
||||
{
|
||||
return FVector2D::ZeroVector;
|
||||
}
|
||||
const double AngleSample = Stream.FRand();
|
||||
const double RadiusSample = Stream.FRand();
|
||||
return MakeRandomPointInAnnulus(0.0, AbsoluteRadius, AngleSample, RadiusSample);
|
||||
}
|
||||
|
||||
FVector2D UDirectiveUtilMathFunctionLibrary::RandomPointInAnnulus(const float InnerRadius, const float OuterRadius)
|
||||
{
|
||||
if (!FMath::IsFinite(InnerRadius) || !FMath::IsFinite(OuterRadius))
|
||||
{
|
||||
return FVector2D::ZeroVector;
|
||||
}
|
||||
|
||||
const double FirstRadius = FMath::Abs(static_cast<double>(InnerRadius));
|
||||
const double SecondRadius = FMath::Abs(static_cast<double>(OuterRadius));
|
||||
const double Inner = FMath::Min(FirstRadius, SecondRadius);
|
||||
const double Outer = FMath::Max(FirstRadius, SecondRadius);
|
||||
if (Outer == 0.0)
|
||||
{
|
||||
return FVector2D::ZeroVector;
|
||||
}
|
||||
const double AngleSample = FMath::FRand();
|
||||
const double RadiusSample = FMath::FRand();
|
||||
return MakeRandomPointInAnnulus(Inner, Outer, AngleSample, RadiusSample);
|
||||
}
|
||||
|
||||
FVector2D UDirectiveUtilMathFunctionLibrary::RandomPointInAnnulusFromStream(FRandomStream& Stream,
|
||||
const float InnerRadius, const float OuterRadius)
|
||||
{
|
||||
if (!FMath::IsFinite(InnerRadius) || !FMath::IsFinite(OuterRadius))
|
||||
{
|
||||
return FVector2D::ZeroVector;
|
||||
}
|
||||
|
||||
const double FirstRadius = FMath::Abs(static_cast<double>(InnerRadius));
|
||||
const double SecondRadius = FMath::Abs(static_cast<double>(OuterRadius));
|
||||
const double Inner = FMath::Min(FirstRadius, SecondRadius);
|
||||
const double Outer = FMath::Max(FirstRadius, SecondRadius);
|
||||
if (Outer == 0.0)
|
||||
{
|
||||
return FVector2D::ZeroVector;
|
||||
}
|
||||
const double AngleSample = Stream.FRand();
|
||||
const double RadiusSample = Stream.FRand();
|
||||
return MakeRandomPointInAnnulus(Inner, Outer, AngleSample, RadiusSample);
|
||||
}
|
||||
|
||||
FVector UDirectiveUtilMathFunctionLibrary::RandomPointInSphere(const float Radius)
|
||||
{
|
||||
if (!FMath::IsFinite(Radius))
|
||||
{
|
||||
return FVector::ZeroVector;
|
||||
}
|
||||
|
||||
const double AbsoluteRadius = FMath::Abs(static_cast<double>(Radius));
|
||||
if (AbsoluteRadius == 0.0)
|
||||
{
|
||||
return FVector::ZeroVector;
|
||||
}
|
||||
return MakeRandomPointInSphere(AbsoluteRadius, []
|
||||
{
|
||||
return FMath::FRand();
|
||||
});
|
||||
}
|
||||
|
||||
FVector UDirectiveUtilMathFunctionLibrary::RandomPointInSphereFromStream(FRandomStream& Stream, const float Radius)
|
||||
{
|
||||
if (!FMath::IsFinite(Radius))
|
||||
{
|
||||
return FVector::ZeroVector;
|
||||
}
|
||||
|
||||
const double AbsoluteRadius = FMath::Abs(static_cast<double>(Radius));
|
||||
if (AbsoluteRadius == 0.0)
|
||||
{
|
||||
return FVector::ZeroVector;
|
||||
}
|
||||
return MakeRandomPointInSphere(AbsoluteRadius, [&Stream]
|
||||
{
|
||||
return Stream.FRand();
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,576 @@
|
||||
// Copyright (c) 2026 Unreal Directive. Licensed under the MIT License.
|
||||
|
||||
#include "Libraries/DirectiveUtilMathFunctionLibrary.h"
|
||||
|
||||
#include <limits>
|
||||
|
||||
namespace
|
||||
{
|
||||
float GetUsableStatisticsWeight(const float Weight)
|
||||
{
|
||||
return FMath::IsFinite(Weight) && Weight > 0.0f ? Weight : 0.0f;
|
||||
}
|
||||
|
||||
template <typename ValueType>
|
||||
ValueType SelectStatisticsNth(TArray<ValueType>& Values, const int32 NthIndex)
|
||||
{
|
||||
int32 Left = 0;
|
||||
int32 Right = Values.Num() - 1;
|
||||
int32 RemainingDepth = FMath::FloorLog2(static_cast<uint32>(Values.Num())) * 2;
|
||||
while (Left < Right)
|
||||
{
|
||||
if (RemainingDepth-- <= 0)
|
||||
{
|
||||
Values.Sort();
|
||||
return Values[NthIndex];
|
||||
}
|
||||
|
||||
const int32 Middle = Left + (Right - Left) / 2;
|
||||
if (Values[Middle] < Values[Left])
|
||||
{
|
||||
Values.Swap(Middle, Left);
|
||||
}
|
||||
if (Values[Right] < Values[Left])
|
||||
{
|
||||
Values.Swap(Right, Left);
|
||||
}
|
||||
if (Values[Right] < Values[Middle])
|
||||
{
|
||||
Values.Swap(Right, Middle);
|
||||
}
|
||||
const ValueType Pivot = Values[Middle];
|
||||
|
||||
int32 LessEnd = Left;
|
||||
int32 Current = Left;
|
||||
int32 GreaterStart = Right;
|
||||
while (Current <= GreaterStart)
|
||||
{
|
||||
if (Values[Current] < Pivot)
|
||||
{
|
||||
Values.Swap(LessEnd++, Current++);
|
||||
}
|
||||
else if (Pivot < Values[Current])
|
||||
{
|
||||
Values.Swap(Current, GreaterStart--);
|
||||
}
|
||||
else
|
||||
{
|
||||
++Current;
|
||||
}
|
||||
}
|
||||
|
||||
if (NthIndex < LessEnd)
|
||||
{
|
||||
Right = LessEnd - 1;
|
||||
}
|
||||
else if (NthIndex > GreaterStart)
|
||||
{
|
||||
Left = GreaterStart + 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
return Values[NthIndex];
|
||||
}
|
||||
}
|
||||
return Values[Left];
|
||||
}
|
||||
|
||||
template <typename ValueType>
|
||||
double CalculateStatisticsMedian(TArray<ValueType>& Values)
|
||||
{
|
||||
const int32 Middle = Values.Num() / 2;
|
||||
const ValueType UpperMiddle = SelectStatisticsNth(Values, Middle);
|
||||
if (Values.Num() % 2 != 0)
|
||||
{
|
||||
return static_cast<double>(UpperMiddle);
|
||||
}
|
||||
|
||||
ValueType LowerMiddle = Values[0];
|
||||
for (int32 Index = 1; Index < Middle; ++Index)
|
||||
{
|
||||
LowerMiddle = FMath::Max(LowerMiddle, Values[Index]);
|
||||
}
|
||||
return (static_cast<double>(LowerMiddle) + static_cast<double>(UpperMiddle)) * 0.5;
|
||||
}
|
||||
}
|
||||
|
||||
float UDirectiveUtilMathFunctionLibrary::RoundToDecimals(const float Value, int32 Decimals)
|
||||
{
|
||||
Decimals = FMath::Clamp(Decimals, 0, 10);
|
||||
if (Decimals == 0)
|
||||
{
|
||||
return FMath::RoundHalfFromZero(Value);
|
||||
}
|
||||
const double Factor = FMath::Pow(10.0, static_cast<double>(Decimals));
|
||||
return static_cast<float>(FMath::RoundHalfFromZero(static_cast<double>(Value) * Factor) / Factor);
|
||||
}
|
||||
|
||||
FText UDirectiveUtilMathFunctionLibrary::RoundToDecimalsAsText(const float Value, int32 Decimals)
|
||||
{
|
||||
Decimals = FMath::Clamp(Decimals, 0, 10);
|
||||
FNumberFormattingOptions Options;
|
||||
Options.MinimumFractionalDigits = 0;
|
||||
Options.MaximumFractionalDigits = Decimals;
|
||||
Options.RoundingMode = ERoundingMode::HalfFromZero;
|
||||
return FText::AsNumber(Value, &Options);
|
||||
}
|
||||
|
||||
FText UDirectiveUtilMathFunctionLibrary::FormatBytes(const int64 Bytes, int32 Decimals)
|
||||
{
|
||||
Decimals = FMath::Clamp(Decimals, 0, 3);
|
||||
|
||||
static const TCHAR* Suffixes[] = { TEXT("B"), TEXT("KB"), TEXT("MB"), TEXT("GB"), TEXT("TB"), TEXT("PB") };
|
||||
const bool bNegative = Bytes < 0;
|
||||
double Value = FMath::Abs(static_cast<double>(Bytes));
|
||||
int32 SuffixIndex = 0;
|
||||
while (Value >= 1024.0 && SuffixIndex < UE_ARRAY_COUNT(Suffixes) - 1)
|
||||
{
|
||||
Value /= 1024.0;
|
||||
++SuffixIndex;
|
||||
}
|
||||
|
||||
return FText::FromString(FString::Printf(TEXT("%s%.*f %s"),
|
||||
bNegative ? TEXT("-") : TEXT(""), SuffixIndex == 0 ? 0 : Decimals, Value, Suffixes[SuffixIndex]));
|
||||
}
|
||||
|
||||
FText UDirectiveUtilMathFunctionLibrary::FormatDuration(const float Seconds, const bool bIncludeSeconds)
|
||||
{
|
||||
if (!FMath::IsFinite(Seconds))
|
||||
{
|
||||
return FText::FromString(TEXT("0s"));
|
||||
}
|
||||
|
||||
const double AbsoluteSeconds = FMath::Abs(static_cast<double>(Seconds));
|
||||
const int64 TotalSeconds = AbsoluteSeconds >= static_cast<double>(TNumericLimits<int64>::Max())
|
||||
? TNumericLimits<int64>::Max()
|
||||
: static_cast<int64>(AbsoluteSeconds);
|
||||
const int64 VisibleSeconds = bIncludeSeconds ? TotalSeconds : (TotalSeconds / 60) * 60;
|
||||
const bool bNegative = Seconds < 0.0f && VisibleSeconds > 0;
|
||||
|
||||
const int64 UnitValues[] = { TotalSeconds / 86400, (TotalSeconds / 3600) % 24, (TotalSeconds / 60) % 60, TotalSeconds % 60 };
|
||||
static const TCHAR* UnitSuffixes[] = { TEXT("d"), TEXT("h"), TEXT("m"), TEXT("s") };
|
||||
const int32 NumUnits = bIncludeSeconds ? 4 : 3;
|
||||
|
||||
int32 FirstUnit = NumUnits - 1;
|
||||
for (int32 Index = 0; Index < NumUnits; ++Index)
|
||||
{
|
||||
if (UnitValues[Index] != 0)
|
||||
{
|
||||
FirstUnit = Index;
|
||||
break;
|
||||
}
|
||||
}
|
||||
int32 LastUnit = FirstUnit;
|
||||
for (int32 Index = NumUnits - 1; Index >= FirstUnit; --Index)
|
||||
{
|
||||
if (UnitValues[Index] != 0)
|
||||
{
|
||||
LastUnit = Index;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
FString Result = bNegative ? TEXT("-") : TEXT("");
|
||||
for (int32 Index = FirstUnit; Index <= LastUnit; ++Index)
|
||||
{
|
||||
if (Index == FirstUnit)
|
||||
{
|
||||
Result += FString::Printf(TEXT("%lld%s"), UnitValues[Index], UnitSuffixes[Index]);
|
||||
}
|
||||
else
|
||||
{
|
||||
Result += FString::Printf(TEXT(" %02lld%s"), UnitValues[Index], UnitSuffixes[Index]);
|
||||
}
|
||||
}
|
||||
return FText::FromString(Result);
|
||||
}
|
||||
|
||||
FText UDirectiveUtilMathFunctionLibrary::FormatRelativeTime(const FDateTime& Timestamp)
|
||||
{
|
||||
const FTimespan Delta = FDateTime::Now() - Timestamp;
|
||||
const bool bFuture = Delta.GetTicks() < 0;
|
||||
// Round first so timestamps near the current second stay in the expected bucket.
|
||||
const int64 SecondsAbs = static_cast<int64>(FMath::RoundToDouble(FMath::Abs(Delta.GetTotalSeconds())));
|
||||
|
||||
if (SecondsAbs < 60)
|
||||
{
|
||||
return FText::FromString(TEXT("just now"));
|
||||
}
|
||||
|
||||
int64 Count;
|
||||
const TCHAR* Unit;
|
||||
if (SecondsAbs < 3600)
|
||||
{
|
||||
Count = SecondsAbs / 60;
|
||||
Unit = TEXT("minute");
|
||||
}
|
||||
else if (SecondsAbs < 86400)
|
||||
{
|
||||
Count = SecondsAbs / 3600;
|
||||
Unit = TEXT("hour");
|
||||
}
|
||||
else
|
||||
{
|
||||
Count = SecondsAbs / 86400;
|
||||
Unit = TEXT("day");
|
||||
}
|
||||
|
||||
const FString Quantity = FString::Printf(TEXT("%lld %s%s"), Count, Unit, Count == 1 ? TEXT("") : TEXT("s"));
|
||||
return FText::FromString(bFuture
|
||||
? FString::Printf(TEXT("in %s"), *Quantity)
|
||||
: FString::Printf(TEXT("%s ago"), *Quantity));
|
||||
}
|
||||
|
||||
int64 UDirectiveUtilMathFunctionLibrary::GetIntArraySum(const TArray<int32>& Values)
|
||||
{
|
||||
int64 Sum = 0;
|
||||
for (const int32 Value : Values)
|
||||
{
|
||||
Sum += Value;
|
||||
}
|
||||
return Sum;
|
||||
}
|
||||
|
||||
float UDirectiveUtilMathFunctionLibrary::GetIntArrayAverage(const TArray<int32>& Values)
|
||||
{
|
||||
if (Values.IsEmpty())
|
||||
{
|
||||
return 0.0f;
|
||||
}
|
||||
return static_cast<float>(static_cast<double>(GetIntArraySum(Values)) / Values.Num());
|
||||
}
|
||||
|
||||
float UDirectiveUtilMathFunctionLibrary::GetIntArrayMedian(const TArray<int32>& Values)
|
||||
{
|
||||
if (Values.IsEmpty())
|
||||
{
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
TArray<int32> WorkingValues = Values;
|
||||
return static_cast<float>(CalculateStatisticsMedian(WorkingValues));
|
||||
}
|
||||
|
||||
float UDirectiveUtilMathFunctionLibrary::GetIntArrayStandardDeviation(const TArray<int32>& Values)
|
||||
{
|
||||
if (Values.IsEmpty())
|
||||
{
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
const double Mean = static_cast<double>(GetIntArraySum(Values)) / Values.Num();
|
||||
double SquaredDeltaSum = 0.0;
|
||||
for (const int32 Value : Values)
|
||||
{
|
||||
const double Delta = static_cast<double>(Value) - Mean;
|
||||
SquaredDeltaSum += Delta * Delta;
|
||||
}
|
||||
return static_cast<float>(FMath::Sqrt(SquaredDeltaSum / Values.Num()));
|
||||
}
|
||||
|
||||
float UDirectiveUtilMathFunctionLibrary::GetFloatArraySum(const TArray<float>& Values)
|
||||
{
|
||||
double Sum = 0.0;
|
||||
for (const float Value : Values)
|
||||
{
|
||||
Sum += static_cast<double>(Value);
|
||||
}
|
||||
return static_cast<float>(Sum);
|
||||
}
|
||||
|
||||
float UDirectiveUtilMathFunctionLibrary::GetFloatArrayAverage(const TArray<float>& Values)
|
||||
{
|
||||
if (Values.IsEmpty())
|
||||
{
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
double Sum = 0.0;
|
||||
for (const float Value : Values)
|
||||
{
|
||||
Sum += static_cast<double>(Value);
|
||||
}
|
||||
return static_cast<float>(Sum / Values.Num());
|
||||
}
|
||||
|
||||
float UDirectiveUtilMathFunctionLibrary::GetFloatArrayMedian(const TArray<float>& Values)
|
||||
{
|
||||
if (Values.IsEmpty())
|
||||
{
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
TArray<float> WorkingValues = Values;
|
||||
if (WorkingValues.ContainsByPredicate([](const float Value) { return FMath::IsNaN(Value); }))
|
||||
{
|
||||
return std::numeric_limits<float>::quiet_NaN();
|
||||
}
|
||||
return static_cast<float>(CalculateStatisticsMedian(WorkingValues));
|
||||
}
|
||||
|
||||
float UDirectiveUtilMathFunctionLibrary::GetFloatArrayStandardDeviation(const TArray<float>& Values)
|
||||
{
|
||||
if (Values.IsEmpty())
|
||||
{
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
double Sum = 0.0;
|
||||
for (const float Value : Values)
|
||||
{
|
||||
Sum += static_cast<double>(Value);
|
||||
}
|
||||
const double Mean = Sum / Values.Num();
|
||||
|
||||
double SquaredDeltaSum = 0.0;
|
||||
for (const float Value : Values)
|
||||
{
|
||||
const double Delta = static_cast<double>(Value) - Mean;
|
||||
SquaredDeltaSum += Delta * Delta;
|
||||
}
|
||||
return static_cast<float>(FMath::Sqrt(SquaredDeltaSum / Values.Num()));
|
||||
}
|
||||
|
||||
bool UDirectiveUtilMathFunctionLibrary::GetAngleArrayAverage(const TArray<float>& Angles,
|
||||
float& AverageAngle, float& ResultantStrength)
|
||||
{
|
||||
AverageAngle = 0.0f;
|
||||
ResultantStrength = 0.0f;
|
||||
if (Angles.IsEmpty())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
double SineSum = 0.0;
|
||||
double CosineSum = 0.0;
|
||||
for (const float Angle : Angles)
|
||||
{
|
||||
if (!FMath::IsFinite(Angle))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
const double Radians = FMath::DegreesToRadians(FMath::Fmod(static_cast<double>(Angle), 360.0));
|
||||
SineSum += FMath::Sin(Radians);
|
||||
CosineSum += FMath::Cos(Radians);
|
||||
}
|
||||
|
||||
const double Magnitude = FMath::Sqrt(SineSum * SineSum + CosineSum * CosineSum);
|
||||
ResultantStrength = static_cast<float>(FMath::Clamp(Magnitude / Angles.Num(), 0.0, 1.0));
|
||||
if (ResultantStrength <= UE_DOUBLE_SMALL_NUMBER)
|
||||
{
|
||||
ResultantStrength = 0.0f;
|
||||
return false;
|
||||
}
|
||||
|
||||
AverageAngle = static_cast<float>(FMath::RadiansToDegrees(FMath::Atan2(SineSum, CosineSum)));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool UDirectiveUtilMathFunctionLibrary::GetWeightedFloatArrayAverage(const TArray<float>& Values,
|
||||
const TArray<float>& Weights, float& Average)
|
||||
{
|
||||
Average = 0.0f;
|
||||
if (Values.IsEmpty() || Values.Num() != Weights.Num())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
double WeightedSum = 0.0;
|
||||
double WeightSum = 0.0;
|
||||
for (int32 Index = 0; Index < Values.Num(); ++Index)
|
||||
{
|
||||
if (!FMath::IsFinite(Values[Index]))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
const double Weight = GetUsableStatisticsWeight(Weights[Index]);
|
||||
WeightedSum += static_cast<double>(Values[Index]) * Weight;
|
||||
WeightSum += Weight;
|
||||
}
|
||||
|
||||
if (WeightSum <= 0.0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
Average = static_cast<float>(WeightedSum / WeightSum);
|
||||
return FMath::IsFinite(Average);
|
||||
}
|
||||
|
||||
bool UDirectiveUtilMathFunctionLibrary::GetWeightedVectorArrayAverage(const TArray<FVector>& Values,
|
||||
const TArray<float>& Weights, FVector& Average)
|
||||
{
|
||||
Average = FVector::ZeroVector;
|
||||
if (Values.IsEmpty() || Values.Num() != Weights.Num())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
FVector RunningAverage = FVector::ZeroVector;
|
||||
double WeightSum = 0.0;
|
||||
for (int32 Index = 0; Index < Values.Num(); ++Index)
|
||||
{
|
||||
if (Values[Index].ContainsNaN())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
const double Weight = GetUsableStatisticsWeight(Weights[Index]);
|
||||
if (Weight > 0.0)
|
||||
{
|
||||
const double NewWeightSum = WeightSum + Weight;
|
||||
RunningAverage = FMath::LerpStable(RunningAverage, Values[Index], Weight / NewWeightSum);
|
||||
WeightSum = NewWeightSum;
|
||||
}
|
||||
}
|
||||
|
||||
if (WeightSum <= 0.0 || RunningAverage.ContainsNaN())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
Average = RunningAverage;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool UDirectiveUtilMathFunctionLibrary::NormalizeFloatArrayToRange(const TArray<float>& Values,
|
||||
const float OutputMinimum, const float OutputMaximum, TArray<float>& NormalizedValues)
|
||||
{
|
||||
TArray<float> ValuesCopy;
|
||||
const TArray<float>* SourceValues = &Values;
|
||||
if (&Values == &NormalizedValues)
|
||||
{
|
||||
ValuesCopy = Values;
|
||||
SourceValues = &ValuesCopy;
|
||||
}
|
||||
|
||||
NormalizedValues.Reset();
|
||||
if (SourceValues->IsEmpty() || !FMath::IsFinite(OutputMinimum) || !FMath::IsFinite(OutputMaximum))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
float InputMinimum = (*SourceValues)[0];
|
||||
float InputMaximum = (*SourceValues)[0];
|
||||
for (const float Value : *SourceValues)
|
||||
{
|
||||
if (!FMath::IsFinite(Value))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
InputMinimum = FMath::Min(InputMinimum, Value);
|
||||
InputMaximum = FMath::Max(InputMaximum, Value);
|
||||
}
|
||||
|
||||
NormalizedValues.SetNumUninitialized(SourceValues->Num());
|
||||
if (InputMinimum == InputMaximum)
|
||||
{
|
||||
NormalizedValues.Init(OutputMinimum, SourceValues->Num());
|
||||
return true;
|
||||
}
|
||||
|
||||
const double Scale = (static_cast<double>(OutputMaximum) - OutputMinimum)
|
||||
/ (static_cast<double>(InputMaximum) - InputMinimum);
|
||||
for (int32 Index = 0; Index < SourceValues->Num(); ++Index)
|
||||
{
|
||||
NormalizedValues[Index] = static_cast<float>(OutputMinimum
|
||||
+ (static_cast<double>((*SourceValues)[Index]) - InputMinimum) * Scale);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool UDirectiveUtilMathFunctionLibrary::NormalizeWeights(const TArray<float>& Weights,
|
||||
TArray<float>& NormalizedWeights)
|
||||
{
|
||||
TArray<float> WeightsCopy;
|
||||
const TArray<float>* SourceWeights = &Weights;
|
||||
if (&Weights == &NormalizedWeights)
|
||||
{
|
||||
WeightsCopy = Weights;
|
||||
SourceWeights = &WeightsCopy;
|
||||
}
|
||||
|
||||
NormalizedWeights.Reset();
|
||||
if (SourceWeights->IsEmpty())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
double WeightSum = 0.0;
|
||||
for (const float Weight : *SourceWeights)
|
||||
{
|
||||
WeightSum += GetUsableStatisticsWeight(Weight);
|
||||
}
|
||||
if (WeightSum <= 0.0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
NormalizedWeights.SetNumUninitialized(SourceWeights->Num());
|
||||
for (int32 Index = 0; Index < SourceWeights->Num(); ++Index)
|
||||
{
|
||||
NormalizedWeights[Index] = static_cast<float>(GetUsableStatisticsWeight((*SourceWeights)[Index]) / WeightSum);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool UDirectiveUtilMathFunctionLibrary::GetFloatArrayPercentile(const TArray<float>& Values,
|
||||
const float Percentile, float& Value)
|
||||
{
|
||||
Value = 0.0f;
|
||||
if (Values.IsEmpty() || !FMath::IsFinite(Percentile))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
for (const float Candidate : Values)
|
||||
{
|
||||
if (!FMath::IsFinite(Candidate))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
TArray<float> WorkingValues = Values;
|
||||
|
||||
const double Position = FMath::Clamp(static_cast<double>(Percentile), 0.0, 100.0)
|
||||
* 0.01 * (WorkingValues.Num() - 1);
|
||||
const int32 LowerIndex = FMath::FloorToInt(Position);
|
||||
const int32 UpperIndex = FMath::CeilToInt(Position);
|
||||
const float LowerValue = SelectStatisticsNth(WorkingValues, LowerIndex);
|
||||
if (LowerIndex == UpperIndex)
|
||||
{
|
||||
Value = LowerValue;
|
||||
return true;
|
||||
}
|
||||
const float UpperValue = SelectStatisticsNth(WorkingValues, UpperIndex);
|
||||
Value = static_cast<float>(FMath::Lerp(
|
||||
static_cast<double>(LowerValue),
|
||||
static_cast<double>(UpperValue),
|
||||
Position - LowerIndex));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool UDirectiveUtilMathFunctionLibrary::GetFloatArrayRootMeanSquare(const TArray<float>& Values,
|
||||
float& RootMeanSquare)
|
||||
{
|
||||
RootMeanSquare = 0.0f;
|
||||
if (Values.IsEmpty())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
double SquaredSum = 0.0;
|
||||
for (const float Value : Values)
|
||||
{
|
||||
if (!FMath::IsFinite(Value))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
SquaredSum += static_cast<double>(Value) * Value;
|
||||
}
|
||||
|
||||
RootMeanSquare = static_cast<float>(FMath::Sqrt(SquaredSum / Values.Num()));
|
||||
return FMath::IsFinite(RootMeanSquare);
|
||||
}
|
||||
@@ -7,6 +7,10 @@
|
||||
#include "GameFramework/SaveGame.h"
|
||||
#include "HAL/FileManager.h"
|
||||
#include "Misc/Paths.h"
|
||||
#include "PlatformFeatures.h"
|
||||
#include "SaveGameSystem.h"
|
||||
|
||||
#include <ctime>
|
||||
|
||||
namespace
|
||||
{
|
||||
@@ -24,11 +28,51 @@ namespace
|
||||
{
|
||||
return UDirectiveUtilStringFunctionLibrary::IsValidFileName(SlotName);
|
||||
}
|
||||
|
||||
ISaveGameSystem* GetSaveGameSystem()
|
||||
{
|
||||
return IPlatformFeaturesModule::Get().GetSaveGameSystem();
|
||||
}
|
||||
|
||||
FDateTime ConvertUtcFileTimeToLocal(const FDateTime& UtcTimestamp)
|
||||
{
|
||||
const int64 UnixSeconds = UtcTimestamp.ToUnixTimestamp();
|
||||
const int32 Milliseconds = UtcTimestamp.GetMillisecond();
|
||||
const time_t Time = static_cast<time_t>(UnixSeconds);
|
||||
tm LocalTm;
|
||||
#if PLATFORM_WINDOWS
|
||||
if (localtime_s(&LocalTm, &Time) != 0)
|
||||
{
|
||||
return UtcTimestamp;
|
||||
}
|
||||
#else
|
||||
if (localtime_r(&Time, &LocalTm) == nullptr)
|
||||
{
|
||||
return UtcTimestamp;
|
||||
}
|
||||
#endif
|
||||
return FDateTime(
|
||||
LocalTm.tm_year + 1900,
|
||||
LocalTm.tm_mon + 1,
|
||||
LocalTm.tm_mday,
|
||||
LocalTm.tm_hour,
|
||||
LocalTm.tm_min,
|
||||
LocalTm.tm_sec,
|
||||
Milliseconds);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
TArray<FString> UDirectiveUtilSaveGameFunctionLibrary::GetAllSaveSlotNames()
|
||||
{
|
||||
TArray<FString> SlotNames;
|
||||
if (ISaveGameSystem* SaveSystem = GetSaveGameSystem())
|
||||
{
|
||||
if (SaveSystem->GetSaveGameNames(SlotNames, 0))
|
||||
{
|
||||
return SlotNames;
|
||||
}
|
||||
}
|
||||
|
||||
TArray<FString> Files;
|
||||
IFileManager::Get().FindFiles(Files, *(GetSaveGamesDirectory() / TEXT("*.sav")), true, false);
|
||||
@@ -49,13 +93,21 @@ bool UDirectiveUtilSaveGameFunctionLibrary::GetSaveSlotTimestamp(const FString&
|
||||
return false;
|
||||
}
|
||||
|
||||
if (ISaveGameSystem* SaveSystem = GetSaveGameSystem())
|
||||
{
|
||||
if (!SaveSystem->DoesSaveGameExist(*SlotName, 0))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
const FDateTime Timestamp = IFileManager::Get().GetTimeStamp(*GetSaveSlotFilePath(SlotName));
|
||||
if (Timestamp == FDateTime::MinValue())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
OutTimestamp = Timestamp + (FDateTime::Now() - FDateTime::UtcNow());
|
||||
OutTimestamp = ConvertUtcFileTimeToLocal(Timestamp);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -98,10 +150,30 @@ bool UDirectiveUtilSaveGameFunctionLibrary::DeleteSaveSlot(const FString& SlotNa
|
||||
|
||||
bool UDirectiveUtilSaveGameFunctionLibrary::RenameSaveSlot(const FString& OldSlotName, const FString& NewSlotName, const int32 UserIndex)
|
||||
{
|
||||
if (!IsValidSaveSlotName(OldSlotName) || !IsValidSaveSlotName(NewSlotName) || OldSlotName == NewSlotName)
|
||||
if (!IsValidSaveSlotName(OldSlotName) || !IsValidSaveSlotName(NewSlotName)
|
||||
|| OldSlotName.Equals(NewSlotName, ESearchCase::CaseSensitive))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
const bool bCaseOnlyRename = OldSlotName.Equals(NewSlotName, ESearchCase::IgnoreCase);
|
||||
if (bCaseOnlyRename)
|
||||
{
|
||||
TArray<uint8> SaveData;
|
||||
if (!UGameplayStatics::LoadDataFromSlot(SaveData, OldSlotName, UserIndex) || SaveData.Num() == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (!UGameplayStatics::SaveDataToSlot(SaveData, NewSlotName, UserIndex))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
// Slot-name case sensitivity belongs to the active platform backend. Deleting
|
||||
// the old spelling here can delete the newly written slot on a case-insensitive
|
||||
// filesystem, so treat both spellings as the same logical slot and rewrite it.
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!UGameplayStatics::DoesSaveGameExist(OldSlotName, UserIndex) || UGameplayStatics::DoesSaveGameExist(NewSlotName, UserIndex))
|
||||
{
|
||||
return false;
|
||||
|
||||
@@ -14,6 +14,80 @@ namespace
|
||||
const FTCHARToUTF8 Converter(*String, String.Len());
|
||||
return TArray<uint8>(reinterpret_cast<const uint8*>(Converter.Get()), Converter.Length());
|
||||
}
|
||||
|
||||
int32 CalculateLevenshteinDistance(
|
||||
FStringView Left,
|
||||
FStringView Right,
|
||||
TArray<int32>& PreviousRow,
|
||||
TArray<int32>& CurrentRow)
|
||||
{
|
||||
int32 Start = 0;
|
||||
while (Start < Left.Len() && Start < Right.Len() && Left[Start] == Right[Start])
|
||||
{
|
||||
++Start;
|
||||
}
|
||||
|
||||
int32 LeftEnd = Left.Len();
|
||||
int32 RightEnd = Right.Len();
|
||||
while (LeftEnd > Start && RightEnd > Start && Left[LeftEnd - 1] == Right[RightEnd - 1])
|
||||
{
|
||||
--LeftEnd;
|
||||
--RightEnd;
|
||||
}
|
||||
|
||||
int32 LeftLength = LeftEnd - Start;
|
||||
int32 RightLength = RightEnd - Start;
|
||||
int32 LeftStart = Start;
|
||||
int32 RightStart = Start;
|
||||
if (RightLength > LeftLength)
|
||||
{
|
||||
Swap(Left, Right);
|
||||
Swap(LeftLength, RightLength);
|
||||
Swap(LeftStart, RightStart);
|
||||
}
|
||||
|
||||
if (RightLength == 0)
|
||||
{
|
||||
return LeftLength;
|
||||
}
|
||||
|
||||
PreviousRow.SetNumUninitialized(RightLength + 1);
|
||||
CurrentRow.SetNumUninitialized(RightLength + 1);
|
||||
for (int32 ColumnIndex = 0; ColumnIndex <= RightLength; ++ColumnIndex)
|
||||
{
|
||||
PreviousRow[ColumnIndex] = ColumnIndex;
|
||||
}
|
||||
|
||||
for (int32 RowIndex = 1; RowIndex <= LeftLength; ++RowIndex)
|
||||
{
|
||||
CurrentRow[0] = RowIndex;
|
||||
for (int32 ColumnIndex = 1; ColumnIndex <= RightLength; ++ColumnIndex)
|
||||
{
|
||||
const int32 SubstitutionCost = Left[LeftStart + RowIndex - 1] == Right[RightStart + ColumnIndex - 1] ? 0 : 1;
|
||||
CurrentRow[ColumnIndex] = FMath::Min3(
|
||||
PreviousRow[ColumnIndex] + 1,
|
||||
CurrentRow[ColumnIndex - 1] + 1,
|
||||
PreviousRow[ColumnIndex - 1] + SubstitutionCost);
|
||||
}
|
||||
Swap(PreviousRow, CurrentRow);
|
||||
}
|
||||
return PreviousRow[RightLength];
|
||||
}
|
||||
|
||||
float CalculateStringSimilarity(
|
||||
const FStringView Left,
|
||||
const FStringView Right,
|
||||
const int32 MaxLength,
|
||||
TArray<int32>& PreviousRow,
|
||||
TArray<int32>& CurrentRow)
|
||||
{
|
||||
if (MaxLength == 0)
|
||||
{
|
||||
return 1.0f;
|
||||
}
|
||||
const int32 Distance = CalculateLevenshteinDistance(Left, Right, PreviousRow, CurrentRow);
|
||||
return 1.0f - static_cast<float>(Distance) / static_cast<float>(MaxLength);
|
||||
}
|
||||
}
|
||||
|
||||
bool UDirectiveUtilStringFunctionLibrary::ContainsLetters(const FString& String)
|
||||
@@ -219,50 +293,27 @@ TArray<FString> UDirectiveUtilStringFunctionLibrary::GetSortedStringArray(const
|
||||
|
||||
int32 UDirectiveUtilStringFunctionLibrary::GetLevenshteinDistance(const FString& A, const FString& B, const bool bCaseSensitive)
|
||||
{
|
||||
const FString StringA = bCaseSensitive ? A : A.ToLower();
|
||||
const FString StringB = bCaseSensitive ? B : B.ToLower();
|
||||
const int32 LenA = StringA.Len();
|
||||
const int32 LenB = StringB.Len();
|
||||
|
||||
if (LenA == 0) { return LenB; }
|
||||
if (LenB == 0) { return LenA; }
|
||||
|
||||
const FString NormalizedA = bCaseSensitive ? FString() : A.ToLower();
|
||||
const FString NormalizedB = bCaseSensitive ? FString() : B.ToLower();
|
||||
const FStringView ViewA = bCaseSensitive ? FStringView(A) : FStringView(NormalizedA);
|
||||
const FStringView ViewB = bCaseSensitive ? FStringView(B) : FStringView(NormalizedB);
|
||||
TArray<int32> PreviousRow;
|
||||
TArray<int32> CurrentRow;
|
||||
PreviousRow.SetNumUninitialized(LenB + 1);
|
||||
CurrentRow.SetNumUninitialized(LenB + 1);
|
||||
|
||||
for (int32 ColumnIndex = 0; ColumnIndex <= LenB; ++ColumnIndex)
|
||||
{
|
||||
PreviousRow[ColumnIndex] = ColumnIndex;
|
||||
}
|
||||
|
||||
for (int32 RowIndex = 1; RowIndex <= LenA; ++RowIndex)
|
||||
{
|
||||
CurrentRow[0] = RowIndex;
|
||||
for (int32 ColumnIndex = 1; ColumnIndex <= LenB; ++ColumnIndex)
|
||||
{
|
||||
const int32 SubstitutionCost = (StringA[RowIndex - 1] == StringB[ColumnIndex - 1]) ? 0 : 1;
|
||||
CurrentRow[ColumnIndex] = FMath::Min3(
|
||||
PreviousRow[ColumnIndex] + 1,
|
||||
CurrentRow[ColumnIndex - 1] + 1,
|
||||
PreviousRow[ColumnIndex - 1] + SubstitutionCost);
|
||||
}
|
||||
Exchange(PreviousRow, CurrentRow);
|
||||
}
|
||||
|
||||
return PreviousRow[LenB];
|
||||
return CalculateLevenshteinDistance(ViewA, ViewB, PreviousRow, CurrentRow);
|
||||
}
|
||||
|
||||
float UDirectiveUtilStringFunctionLibrary::GetStringSimilarity(const FString& A, const FString& B, const bool bCaseSensitive)
|
||||
{
|
||||
const int32 MaxLength = FMath::Max(A.Len(), B.Len());
|
||||
if (MaxLength == 0)
|
||||
{
|
||||
return 1.0f;
|
||||
}
|
||||
const int32 Distance = GetLevenshteinDistance(A, B, bCaseSensitive);
|
||||
return 1.0f - (static_cast<float>(Distance) / static_cast<float>(MaxLength));
|
||||
const FString NormalizedA = bCaseSensitive ? FString() : A.ToLower();
|
||||
const FString NormalizedB = bCaseSensitive ? FString() : B.ToLower();
|
||||
TArray<int32> PreviousRow;
|
||||
TArray<int32> CurrentRow;
|
||||
return CalculateStringSimilarity(
|
||||
bCaseSensitive ? FStringView(A) : FStringView(NormalizedA),
|
||||
bCaseSensitive ? FStringView(B) : FStringView(NormalizedB),
|
||||
FMath::Max(A.Len(), B.Len()),
|
||||
PreviousRow,
|
||||
CurrentRow);
|
||||
}
|
||||
|
||||
bool UDirectiveUtilStringFunctionLibrary::ContainsAny(const FString& Source, const TArray<FString>& SearchTerms, const bool bCaseSensitive)
|
||||
@@ -394,28 +445,96 @@ int32 UDirectiveUtilStringFunctionLibrary::Crc32Bytes(const TArray<uint8>& Bytes
|
||||
return static_cast<int32>(FCrc::MemCrc32(Bytes.GetData(), Bytes.Num()));
|
||||
}
|
||||
|
||||
namespace
|
||||
{
|
||||
bool IsReservedDeviceFileName(const FString& FileName)
|
||||
{
|
||||
FString Stem = FileName;
|
||||
int32 DotIndex = INDEX_NONE;
|
||||
if (FileName.FindChar(TEXT('.'), DotIndex))
|
||||
{
|
||||
Stem = FileName.Left(DotIndex);
|
||||
}
|
||||
|
||||
while (Stem.EndsWith(TEXT(".")) || Stem.EndsWith(TEXT(" ")))
|
||||
{
|
||||
Stem.LeftChopInline(1);
|
||||
}
|
||||
if (Stem.IsEmpty())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
static const TCHAR* ReservedNames[] = {
|
||||
TEXT("CON"), TEXT("PRN"), TEXT("AUX"), TEXT("CLOCK$"), TEXT("NUL"),
|
||||
TEXT("COM1"), TEXT("COM2"), TEXT("COM3"), TEXT("COM4"), TEXT("COM5"),
|
||||
TEXT("COM6"), TEXT("COM7"), TEXT("COM8"), TEXT("COM9"),
|
||||
TEXT("LPT1"), TEXT("LPT2"), TEXT("LPT3"), TEXT("LPT4"), TEXT("LPT5"),
|
||||
TEXT("LPT6"), TEXT("LPT7"), TEXT("LPT8"), TEXT("LPT9")
|
||||
};
|
||||
for (const TCHAR* ReservedName : ReservedNames)
|
||||
{
|
||||
if (Stem.Equals(ReservedName, ESearchCase::IgnoreCase))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool UDirectiveUtilStringFunctionLibrary::IsValidFileName(const FString& String)
|
||||
{
|
||||
return !String.IsEmpty() && FPaths::GetCleanFilename(String) == String && SanitizeFileName(String) == String;
|
||||
return !String.IsEmpty()
|
||||
&& String != TEXT(".")
|
||||
&& String != TEXT("..")
|
||||
&& FPaths::GetCleanFilename(String) == String
|
||||
&& SanitizeFileName(String) == String;
|
||||
}
|
||||
|
||||
FString UDirectiveUtilStringFunctionLibrary::SanitizeFileName(const FString& String, const FString& Replacement)
|
||||
{
|
||||
return FPaths::MakeValidFileName(String, Replacement.IsEmpty() ? TEXT('\0') : Replacement[0]);
|
||||
FString Result = FPaths::MakeValidFileName(String, Replacement.IsEmpty() ? TEXT('\0') : Replacement[0]);
|
||||
while (Result.EndsWith(TEXT(".")) || Result.EndsWith(TEXT(" ")))
|
||||
{
|
||||
Result.LeftChopInline(1);
|
||||
}
|
||||
if (IsReservedDeviceFileName(Result))
|
||||
{
|
||||
Result = FString(TEXT("_")) + Result;
|
||||
}
|
||||
return Result;
|
||||
}
|
||||
|
||||
int32 UDirectiveUtilStringFunctionLibrary::FindBestStringMatch(const FString& Input, const TArray<FString>& Candidates, float& OutSimilarity, const bool bCaseSensitive)
|
||||
{
|
||||
OutSimilarity = 0.0f;
|
||||
int32 BestIndex = INDEX_NONE;
|
||||
const FString NormalizedInput = bCaseSensitive ? FString() : Input.ToLower();
|
||||
const FStringView InputView = bCaseSensitive ? FStringView(Input) : FStringView(NormalizedInput);
|
||||
TArray<int32> PreviousRow;
|
||||
TArray<int32> CurrentRow;
|
||||
|
||||
for (int32 Index = 0; Index < Candidates.Num(); ++Index)
|
||||
{
|
||||
const float Similarity = GetStringSimilarity(Input, Candidates[Index], bCaseSensitive);
|
||||
const FString NormalizedCandidate = bCaseSensitive ? FString() : Candidates[Index].ToLower();
|
||||
const FStringView CandidateView = bCaseSensitive
|
||||
? FStringView(Candidates[Index])
|
||||
: FStringView(NormalizedCandidate);
|
||||
const float Similarity = CalculateStringSimilarity(
|
||||
InputView,
|
||||
CandidateView,
|
||||
FMath::Max(Input.Len(), Candidates[Index].Len()),
|
||||
PreviousRow,
|
||||
CurrentRow);
|
||||
if (BestIndex == INDEX_NONE || Similarity > OutSimilarity)
|
||||
{
|
||||
BestIndex = Index;
|
||||
OutSimilarity = Similarity;
|
||||
if (Similarity == 1.0f)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return BestIndex;
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -51,7 +51,25 @@ public:
|
||||
* @param TargetArray - The array to remove duplicates from.
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, CustomThunk, meta=(DisplayName = "Remove Duplicates", CompactNodeTitle = "REMOVE DUPLICATES", ArrayParm = "TargetArray"), Category="Directive Utilities|Array")
|
||||
static void Array_RemoveDuplicates(const TArray<int32>& TargetArray);
|
||||
static void Array_RemoveDuplicates(UPARAM(ref) TArray<int32>& TargetArray);
|
||||
|
||||
/**
|
||||
* Appends every source element to the target array.
|
||||
* @param TargetArray - The array to append to.
|
||||
* @param SourceArray - The array to append.
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, CustomThunk, meta=(DisplayName = "Append Array Optimized", CompactNodeTitle = "APPEND", Keywords = "append merge concatenate bulk", ArrayParm = "TargetArray,SourceArray", ArrayTypeDependentParams = "SourceArray"), Category="Directive Utilities|Array")
|
||||
static void Array_AppendOptimized(UPARAM(ref) TArray<int32>& TargetArray, const TArray<int32>& SourceArray);
|
||||
|
||||
/**
|
||||
* Inserts every source element into the target array at the given index.
|
||||
* @param TargetArray - The array to insert into.
|
||||
* @param SourceArray - The array to insert.
|
||||
* @param Index - The index at which to insert the source array.
|
||||
* @returns True if one or more elements were inserted.
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, CustomThunk, meta=(DisplayName = "Insert Array Optimized", CompactNodeTitle = "INSERT ARRAY", Keywords = "insert splice merge bulk", ArrayParm = "TargetArray,SourceArray", ArrayTypeDependentParams = "SourceArray"), Category="Directive Utilities|Array")
|
||||
static bool Array_InsertOptimized(UPARAM(ref) TArray<int32>& TargetArray, const TArray<int32>& SourceArray, const int32 Index);
|
||||
|
||||
/**
|
||||
* Returns a copy of the first element of the array.
|
||||
@@ -106,7 +124,7 @@ public:
|
||||
* @returns True if an element was removed.
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, CustomThunk, meta=(DisplayName = "Pop", ArrayParm = "TargetArray", ArrayTypeDependentParams = "OutItem"), Category="Directive Utilities|Array")
|
||||
static bool Array_Pop(const TArray<int32>& TargetArray, int32& OutItem);
|
||||
static bool Array_Pop(UPARAM(ref) TArray<int32>& TargetArray, int32& OutItem);
|
||||
|
||||
/**
|
||||
* Removes the first element of the array and returns a copy of it.
|
||||
@@ -115,7 +133,7 @@ public:
|
||||
* @returns True if an element was removed.
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, CustomThunk, meta=(DisplayName = "Pop First", ArrayParm = "TargetArray", ArrayTypeDependentParams = "OutItem"), Category="Directive Utilities|Array")
|
||||
static bool Array_PopFirst(const TArray<int32>& TargetArray, int32& OutItem);
|
||||
static bool Array_PopFirst(UPARAM(ref) TArray<int32>& TargetArray, int32& OutItem);
|
||||
|
||||
/**
|
||||
* Removes the element at the given index by swapping it with the last element (does not preserve order).
|
||||
@@ -125,7 +143,26 @@ public:
|
||||
* @returns True if the index was valid and an element was removed.
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, CustomThunk, meta=(DisplayName = "Remove At Swap", ArrayParm = "TargetArray"), Category="Directive Utilities|Array")
|
||||
static bool Array_RemoveAtSwap(const TArray<int32>& TargetArray, const int32 Index);
|
||||
static bool Array_RemoveAtSwap(UPARAM(ref) TArray<int32>& TargetArray, const int32 Index);
|
||||
|
||||
/**
|
||||
* Removes the elements at the given indices while preserving the order of the remaining elements.
|
||||
* Duplicate and invalid indices are ignored.
|
||||
* @param TargetArray - The array to remove from.
|
||||
* @param Indices - The indices to remove.
|
||||
* @returns The number of elements removed.
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, CustomThunk, meta=(DisplayName = "Remove At Indices", CompactNodeTitle = "REMOVE INDICES", Keywords = "remove delete batch multiple", ArrayParm = "TargetArray"), Category="Directive Utilities|Array")
|
||||
static int32 Array_RemoveAtIndices(UPARAM(ref) TArray<int32>& TargetArray, const TArray<int32>& Indices);
|
||||
|
||||
/**
|
||||
* Removes every matching item while preserving the order of the remaining elements.
|
||||
* @param TargetArray - The array to remove matching items from.
|
||||
* @param Item - The item to remove.
|
||||
* @returns True if one or more items were removed.
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, CustomThunk, meta=(DisplayName = "Remove All Occurrences", CompactNodeTitle = "REMOVE ALL", Keywords = "remove item delete matching", ArrayParm = "TargetArray", ArrayTypeDependentParams = "Item", AutoCreateRefTerm = "Item"), Category="Directive Utilities|Array")
|
||||
static bool Array_RemoveAllOccurrences(UPARAM(ref) TArray<int32>& TargetArray, const int32& Item);
|
||||
|
||||
/**
|
||||
* Returns a copy of a contiguous range of the array. The range is clamped to the array bounds.
|
||||
@@ -143,7 +180,7 @@ public:
|
||||
* @param Shift - The number of positions to rotate. Positive rotates toward the end; negative toward the start.
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, CustomThunk, meta=(DisplayName = "Rotate", ArrayParm = "TargetArray"), Category="Directive Utilities|Array")
|
||||
static void Array_Rotate(const TArray<int32>& TargetArray, const int32 Shift);
|
||||
static void Array_Rotate(UPARAM(ref) TArray<int32>& TargetArray, const int32 Shift);
|
||||
|
||||
/**
|
||||
* Returns a copy of the array with duplicates removed, keeping the first occurrence and preserving order.
|
||||
@@ -173,6 +210,72 @@ public:
|
||||
UFUNCTION(BlueprintPure, CustomThunk, meta=(DisplayName = "Get Most Common", ArrayParm = "TargetArray", ArrayTypeDependentParams = "OutItem", BlueprintThreadSafe), Category="Directive Utilities|Array")
|
||||
static bool Array_GetMostCommon(const TArray<int32>& TargetArray, int32& OutItem, int32& OutCount);
|
||||
|
||||
/**
|
||||
* Returns randomly selected elements from the array.
|
||||
* @param TargetArray The array to sample.
|
||||
* @param Count The requested number of elements, up to 1,000,000.
|
||||
* @param bWithReplacement Whether the same source element can be selected more than once.
|
||||
* @param OutArray The sampled elements.
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, CustomThunk, meta=(DisplayName = "Sample Array", ArrayParm = "TargetArray,OutArray", ArrayTypeDependentParams = "OutArray"), Category="Directive Utilities|Array")
|
||||
static void Array_Sample(const TArray<int32>& TargetArray, int32 Count, bool bWithReplacement, TArray<int32>& OutArray);
|
||||
|
||||
/**
|
||||
* Returns randomly selected elements using a random stream.
|
||||
* @param TargetArray The array to sample.
|
||||
* @param Count The requested number of elements, up to 1,000,000.
|
||||
* @param bWithReplacement Whether the same source element can be selected more than once.
|
||||
* @param RandomStream The stream used to select elements.
|
||||
* @param OutArray The sampled elements.
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, CustomThunk, meta=(DisplayName = "Sample Array from Stream", ArrayParm = "TargetArray,OutArray", ArrayTypeDependentParams = "OutArray"), Category="Directive Utilities|Array")
|
||||
static void Array_SampleFromStream(const TArray<int32>& TargetArray, int32 Count, bool bWithReplacement, UPARAM(ref) FRandomStream& RandomStream, TArray<int32>& OutArray);
|
||||
|
||||
/**
|
||||
* Returns randomly selected elements using per-element weights.
|
||||
* @param TargetArray The array to sample.
|
||||
* @param Weights The selection weight for each source element.
|
||||
* @param Count The requested number of elements, up to 1,000,000.
|
||||
* @param bWithReplacement Whether the same source element can be selected more than once.
|
||||
* @param OutArray The sampled elements.
|
||||
* @returns True when the inputs were valid and the sample was produced.
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, CustomThunk, meta=(DisplayName = "Sample Weighted Array", ArrayParm = "TargetArray,OutArray", ArrayTypeDependentParams = "OutArray"), Category="Directive Utilities|Array")
|
||||
static bool Array_SampleWeighted(const TArray<int32>& TargetArray, const TArray<float>& Weights, int32 Count, bool bWithReplacement, TArray<int32>& OutArray);
|
||||
|
||||
/**
|
||||
* Returns randomly selected elements using per-element weights and a random stream.
|
||||
* @param TargetArray The array to sample.
|
||||
* @param Weights The selection weight for each source element.
|
||||
* @param Count The requested number of elements, up to 1,000,000.
|
||||
* @param bWithReplacement Whether the same source element can be selected more than once.
|
||||
* @param RandomStream The stream used to select elements.
|
||||
* @param OutArray The sampled elements.
|
||||
* @returns True when the inputs were valid and the sample was produced.
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, CustomThunk, meta=(DisplayName = "Sample Weighted Array from Stream", ArrayParm = "TargetArray,OutArray", ArrayTypeDependentParams = "OutArray"), Category="Directive Utilities|Array")
|
||||
static bool Array_SampleWeightedFromStream(const TArray<int32>& TargetArray, const TArray<float>& Weights, int32 Count, bool bWithReplacement, UPARAM(ref) FRandomStream& RandomStream, TArray<int32>& OutArray);
|
||||
|
||||
/**
|
||||
* Returns one zero-based page from the array.
|
||||
* @param TargetArray The array to read.
|
||||
* @param PageIndex The zero-based page index.
|
||||
* @param PageSize The maximum number of elements per page.
|
||||
* @param OutArray The requested page.
|
||||
* @param OutPageCount The total number of pages.
|
||||
* @returns True when the page index and size are valid.
|
||||
*/
|
||||
UFUNCTION(BlueprintPure, CustomThunk, meta=(DisplayName = "Get Array Page", ArrayParm = "TargetArray,OutArray", ArrayTypeDependentParams = "OutArray", BlueprintThreadSafe), Category="Directive Utilities|Array")
|
||||
static bool Array_GetPage(const TArray<int32>& TargetArray, int32 PageIndex, int32 PageSize, TArray<int32>& OutArray, int32& OutPageCount);
|
||||
|
||||
/** Sorts strings in natural order so embedded numbers are compared numerically. */
|
||||
UFUNCTION(BlueprintCallable, Category="Directive Utilities|Array")
|
||||
static void NaturalSortStringArray(UPARAM(ref) TArray<FString>& TargetArray, bool bDescending = false);
|
||||
|
||||
/** Sorts names in natural order so embedded numbers are compared numerically. */
|
||||
UFUNCTION(BlueprintCallable, Category="Directive Utilities|Array")
|
||||
static void NaturalSortNameArray(UPARAM(ref) TArray<FName>& TargetArray, bool bDescending = false);
|
||||
|
||||
|
||||
/*~
|
||||
* Native functions that will be called by the below custom thunk layers, which read off the property address and call the appropriate native handler.
|
||||
@@ -182,6 +285,8 @@ public:
|
||||
static int32 GenericArray_NextIndex(const void* TargetArray, const FArrayProperty* ArrayProperty, int32 Index, bool bLoop);
|
||||
static int32 GenericArray_PreviousIndex(const void* TargetArray, const FArrayProperty* ArrayProperty, int32 Index, bool bLoop);
|
||||
static void GenericArray_RemoveDuplicates(void* TargetArray, const FArrayProperty* ArrayProperty);
|
||||
static void GenericArray_AppendOptimized(void* TargetArray, const FArrayProperty* TargetArrayProperty, const void* SourceArray, const FArrayProperty* SourceArrayProperty);
|
||||
static bool GenericArray_InsertOptimized(void* TargetArray, const FArrayProperty* TargetArrayProperty, const void* SourceArray, const FArrayProperty* SourceArrayProperty, int32 Index);
|
||||
static bool GenericArray_GetItemAtIndex(const void* TargetArray, const FArrayProperty* ArrayProperty, int32 Index, void* OutItemPtr);
|
||||
static bool GenericArray_GetFirstItem(const void* TargetArray, const FArrayProperty* ArrayProperty, void* OutItemPtr);
|
||||
static bool GenericArray_GetLastItem(const void* TargetArray, const FArrayProperty* ArrayProperty, void* OutItemPtr);
|
||||
@@ -189,11 +294,16 @@ public:
|
||||
static bool GenericArray_Pop(void* TargetArray, const FArrayProperty* ArrayProperty, void* OutItemPtr);
|
||||
static bool GenericArray_PopFirst(void* TargetArray, const FArrayProperty* ArrayProperty, void* OutItemPtr);
|
||||
static bool GenericArray_RemoveAtSwap(void* TargetArray, const FArrayProperty* ArrayProperty, int32 Index);
|
||||
static int32 GenericArray_RemoveAtIndices(void* TargetArray, const FArrayProperty* ArrayProperty, const TArray<int32>& Indices);
|
||||
static bool GenericArray_RemoveAllOccurrences(void* TargetArray, const FArrayProperty* ArrayProperty, const void* Item);
|
||||
static void GenericArray_Slice(const void* TargetArray, const FArrayProperty* TargetArrayProperty, int32 StartIndex, int32 Count, void* OutArray, const FArrayProperty* OutArrayProperty);
|
||||
static void GenericArray_Rotate(void* TargetArray, const FArrayProperty* ArrayProperty, int32 Shift);
|
||||
static void GenericArray_GetDistinct(const void* TargetArray, const FArrayProperty* TargetArrayProperty, void* OutArray, const FArrayProperty* OutArrayProperty);
|
||||
static int32 GenericArray_CountOccurrences(const void* TargetArray, const FArrayProperty* ArrayProperty, const void* ItemToCount);
|
||||
static bool GenericArray_GetMostCommon(const void* TargetArray, const FArrayProperty* ArrayProperty, void* OutItemPtr, int32* OutCount);
|
||||
static void GenericArray_Sample(const void* TargetArray, const FArrayProperty* TargetArrayProperty, int32 Count, bool bWithReplacement, FRandomStream* RandomStream, void* OutArray, const FArrayProperty* OutArrayProperty);
|
||||
static bool GenericArray_SampleWeighted(const void* TargetArray, const FArrayProperty* TargetArrayProperty, const TArray<float>& Weights, int32 Count, bool bWithReplacement, FRandomStream* RandomStream, void* OutArray, const FArrayProperty* OutArrayProperty);
|
||||
static bool GenericArray_GetPage(const void* TargetArray, const FArrayProperty* TargetArrayProperty, int32 PageIndex, int32 PageSize, void* OutArray, const FArrayProperty* OutArrayProperty, int32* OutPageCount);
|
||||
|
||||
/*~
|
||||
* Custom thunk layers that read off the property address and call the appropriate native handler.
|
||||
@@ -258,6 +368,78 @@ public:
|
||||
P_NATIVE_END;
|
||||
}
|
||||
|
||||
DECLARE_FUNCTION(execArray_AppendOptimized)
|
||||
{
|
||||
Stack.MostRecentProperty = nullptr;
|
||||
Stack.StepCompiledIn<FArrayProperty>(nullptr);
|
||||
void* TargetArrayAddr = Stack.MostRecentPropertyAddress;
|
||||
FArrayProperty* TargetArrayProperty = CastField<FArrayProperty>(Stack.MostRecentProperty);
|
||||
if (!TargetArrayProperty)
|
||||
{
|
||||
Stack.bArrayContextFailed = true;
|
||||
return;
|
||||
}
|
||||
|
||||
Stack.MostRecentProperty = nullptr;
|
||||
Stack.StepCompiledIn<FArrayProperty>(nullptr);
|
||||
const void* SourceArrayAddr = Stack.MostRecentPropertyAddress;
|
||||
const FArrayProperty* SourceArrayProperty = CastField<FArrayProperty>(Stack.MostRecentProperty);
|
||||
if (!SourceArrayProperty)
|
||||
{
|
||||
Stack.bArrayContextFailed = true;
|
||||
return;
|
||||
}
|
||||
|
||||
P_FINISH;
|
||||
P_NATIVE_BEGIN;
|
||||
MARK_PROPERTY_DIRTY(Stack.Object, TargetArrayProperty);
|
||||
GenericArray_AppendOptimized(
|
||||
TargetArrayAddr,
|
||||
TargetArrayProperty,
|
||||
SourceArrayAddr,
|
||||
SourceArrayProperty);
|
||||
P_NATIVE_END;
|
||||
}
|
||||
|
||||
DECLARE_FUNCTION(execArray_InsertOptimized)
|
||||
{
|
||||
Stack.MostRecentProperty = nullptr;
|
||||
Stack.StepCompiledIn<FArrayProperty>(nullptr);
|
||||
void* TargetArrayAddr = Stack.MostRecentPropertyAddress;
|
||||
FArrayProperty* TargetArrayProperty = CastField<FArrayProperty>(Stack.MostRecentProperty);
|
||||
if (!TargetArrayProperty)
|
||||
{
|
||||
Stack.bArrayContextFailed = true;
|
||||
return;
|
||||
}
|
||||
|
||||
Stack.MostRecentProperty = nullptr;
|
||||
Stack.StepCompiledIn<FArrayProperty>(nullptr);
|
||||
const void* SourceArrayAddr = Stack.MostRecentPropertyAddress;
|
||||
const FArrayProperty* SourceArrayProperty = CastField<FArrayProperty>(Stack.MostRecentProperty);
|
||||
if (!SourceArrayProperty)
|
||||
{
|
||||
Stack.bArrayContextFailed = true;
|
||||
return;
|
||||
}
|
||||
|
||||
P_GET_PROPERTY(FIntProperty, Index);
|
||||
P_FINISH;
|
||||
P_NATIVE_BEGIN;
|
||||
const bool bInserted = GenericArray_InsertOptimized(
|
||||
TargetArrayAddr,
|
||||
TargetArrayProperty,
|
||||
SourceArrayAddr,
|
||||
SourceArrayProperty,
|
||||
Index);
|
||||
if (bInserted)
|
||||
{
|
||||
MARK_PROPERTY_DIRTY(Stack.Object, TargetArrayProperty);
|
||||
}
|
||||
*static_cast<bool*>(RESULT_PARAM) = bInserted;
|
||||
P_NATIVE_END;
|
||||
}
|
||||
|
||||
DECLARE_FUNCTION(execArray_GetValidFirstItemCopy)
|
||||
{
|
||||
Stack.MostRecentProperty = nullptr;
|
||||
@@ -565,6 +747,70 @@ public:
|
||||
P_NATIVE_END;
|
||||
}
|
||||
|
||||
DECLARE_FUNCTION(execArray_RemoveAtIndices)
|
||||
{
|
||||
Stack.MostRecentProperty = nullptr;
|
||||
Stack.StepCompiledIn<FArrayProperty>(nullptr);
|
||||
void* ArrayAddr = Stack.MostRecentPropertyAddress;
|
||||
FArrayProperty* ArrayProperty = CastField<FArrayProperty>(Stack.MostRecentProperty);
|
||||
if (!ArrayProperty)
|
||||
{
|
||||
Stack.bArrayContextFailed = true;
|
||||
return;
|
||||
}
|
||||
P_GET_TARRAY_REF(int32, Indices);
|
||||
P_FINISH;
|
||||
P_NATIVE_BEGIN;
|
||||
const int32 RemovedCount = GenericArray_RemoveAtIndices(ArrayAddr, ArrayProperty, Indices);
|
||||
if (RemovedCount > 0)
|
||||
{
|
||||
MARK_PROPERTY_DIRTY(Stack.Object, ArrayProperty);
|
||||
}
|
||||
*static_cast<int32*>(RESULT_PARAM) = RemovedCount;
|
||||
P_NATIVE_END;
|
||||
}
|
||||
|
||||
DECLARE_FUNCTION(execArray_RemoveAllOccurrences)
|
||||
{
|
||||
Stack.MostRecentProperty = nullptr;
|
||||
Stack.StepCompiledIn<FArrayProperty>(nullptr);
|
||||
void* ArrayAddr = Stack.MostRecentPropertyAddress;
|
||||
FArrayProperty* ArrayProperty = CastField<FArrayProperty>(Stack.MostRecentProperty);
|
||||
if (!ArrayProperty)
|
||||
{
|
||||
Stack.bArrayContextFailed = true;
|
||||
return;
|
||||
}
|
||||
|
||||
const FProperty* InnerProp = ArrayProperty->Inner;
|
||||
const int32 PropertySize = InnerProp->GetElementSize() * InnerProp->ArrayDim;
|
||||
void* StorageSpace = FMemory_Alloca(PropertySize);
|
||||
InnerProp->InitializeValue(StorageSpace);
|
||||
|
||||
Stack.MostRecentPropertyAddress = nullptr;
|
||||
Stack.MostRecentPropertyContainer = nullptr;
|
||||
Stack.StepCompiledIn<FProperty>(StorageSpace);
|
||||
|
||||
P_FINISH;
|
||||
|
||||
if (const FBoolProperty* BoolProperty = CastField<const FBoolProperty>(InnerProp))
|
||||
{
|
||||
ensure(PropertySize == sizeof(uint8));
|
||||
BoolProperty->SetPropertyValue(StorageSpace, *static_cast<uint8*>(StorageSpace) != 0);
|
||||
}
|
||||
|
||||
P_NATIVE_BEGIN;
|
||||
const bool bRemoved = GenericArray_RemoveAllOccurrences(ArrayAddr, ArrayProperty, StorageSpace);
|
||||
if (bRemoved)
|
||||
{
|
||||
MARK_PROPERTY_DIRTY(Stack.Object, ArrayProperty);
|
||||
}
|
||||
*static_cast<bool*>(RESULT_PARAM) = bRemoved;
|
||||
P_NATIVE_END;
|
||||
|
||||
InnerProp->DestroyValue(StorageSpace);
|
||||
}
|
||||
|
||||
DECLARE_FUNCTION(execArray_Slice)
|
||||
{
|
||||
Stack.MostRecentProperty = nullptr;
|
||||
@@ -713,4 +959,179 @@ public:
|
||||
P_NATIVE_END;
|
||||
InnerProp->DestroyValue(StorageSpace);
|
||||
}
|
||||
|
||||
DECLARE_FUNCTION(execArray_Sample)
|
||||
{
|
||||
Stack.MostRecentProperty = nullptr;
|
||||
Stack.StepCompiledIn<FArrayProperty>(nullptr);
|
||||
const void* SourceArrayAddr = Stack.MostRecentPropertyAddress;
|
||||
const FArrayProperty* SourceArrayProperty = CastField<FArrayProperty>(Stack.MostRecentProperty);
|
||||
if (!SourceArrayProperty)
|
||||
{
|
||||
Stack.bArrayContextFailed = true;
|
||||
return;
|
||||
}
|
||||
P_GET_PROPERTY(FIntProperty, Count);
|
||||
P_GET_UBOOL(bWithReplacement);
|
||||
|
||||
Stack.MostRecentProperty = nullptr;
|
||||
Stack.StepCompiledIn<FArrayProperty>(nullptr);
|
||||
void* OutArrayAddr = Stack.MostRecentPropertyAddress;
|
||||
const FArrayProperty* OutArrayProperty = CastField<FArrayProperty>(Stack.MostRecentProperty);
|
||||
if (!OutArrayProperty)
|
||||
{
|
||||
Stack.bArrayContextFailed = true;
|
||||
return;
|
||||
}
|
||||
|
||||
P_FINISH;
|
||||
P_NATIVE_BEGIN;
|
||||
GenericArray_Sample(SourceArrayAddr, SourceArrayProperty, Count, bWithReplacement, nullptr, OutArrayAddr, OutArrayProperty);
|
||||
P_NATIVE_END;
|
||||
}
|
||||
|
||||
DECLARE_FUNCTION(execArray_SampleFromStream)
|
||||
{
|
||||
Stack.MostRecentProperty = nullptr;
|
||||
Stack.StepCompiledIn<FArrayProperty>(nullptr);
|
||||
const void* SourceArrayAddr = Stack.MostRecentPropertyAddress;
|
||||
const FArrayProperty* SourceArrayProperty = CastField<FArrayProperty>(Stack.MostRecentProperty);
|
||||
if (!SourceArrayProperty)
|
||||
{
|
||||
Stack.bArrayContextFailed = true;
|
||||
return;
|
||||
}
|
||||
P_GET_PROPERTY(FIntProperty, Count);
|
||||
P_GET_UBOOL(bWithReplacement);
|
||||
P_GET_STRUCT_REF(FRandomStream, RandomStream);
|
||||
|
||||
Stack.MostRecentProperty = nullptr;
|
||||
Stack.StepCompiledIn<FArrayProperty>(nullptr);
|
||||
void* OutArrayAddr = Stack.MostRecentPropertyAddress;
|
||||
const FArrayProperty* OutArrayProperty = CastField<FArrayProperty>(Stack.MostRecentProperty);
|
||||
if (!OutArrayProperty)
|
||||
{
|
||||
Stack.bArrayContextFailed = true;
|
||||
return;
|
||||
}
|
||||
|
||||
P_FINISH;
|
||||
P_NATIVE_BEGIN;
|
||||
GenericArray_Sample(SourceArrayAddr, SourceArrayProperty, Count, bWithReplacement, &RandomStream, OutArrayAddr, OutArrayProperty);
|
||||
P_NATIVE_END;
|
||||
}
|
||||
|
||||
DECLARE_FUNCTION(execArray_SampleWeighted)
|
||||
{
|
||||
Stack.MostRecentProperty = nullptr;
|
||||
Stack.StepCompiledIn<FArrayProperty>(nullptr);
|
||||
const void* SourceArrayAddr = Stack.MostRecentPropertyAddress;
|
||||
const FArrayProperty* SourceArrayProperty = CastField<FArrayProperty>(Stack.MostRecentProperty);
|
||||
if (!SourceArrayProperty)
|
||||
{
|
||||
Stack.bArrayContextFailed = true;
|
||||
return;
|
||||
}
|
||||
P_GET_TARRAY_REF(float, Weights);
|
||||
P_GET_PROPERTY(FIntProperty, Count);
|
||||
P_GET_UBOOL(bWithReplacement);
|
||||
|
||||
Stack.MostRecentProperty = nullptr;
|
||||
Stack.StepCompiledIn<FArrayProperty>(nullptr);
|
||||
void* OutArrayAddr = Stack.MostRecentPropertyAddress;
|
||||
const FArrayProperty* OutArrayProperty = CastField<FArrayProperty>(Stack.MostRecentProperty);
|
||||
if (!OutArrayProperty)
|
||||
{
|
||||
Stack.bArrayContextFailed = true;
|
||||
return;
|
||||
}
|
||||
|
||||
P_FINISH;
|
||||
P_NATIVE_BEGIN;
|
||||
*static_cast<bool*>(RESULT_PARAM) = GenericArray_SampleWeighted(
|
||||
SourceArrayAddr,
|
||||
SourceArrayProperty,
|
||||
Weights,
|
||||
Count,
|
||||
bWithReplacement,
|
||||
nullptr,
|
||||
OutArrayAddr,
|
||||
OutArrayProperty);
|
||||
P_NATIVE_END;
|
||||
}
|
||||
|
||||
DECLARE_FUNCTION(execArray_SampleWeightedFromStream)
|
||||
{
|
||||
Stack.MostRecentProperty = nullptr;
|
||||
Stack.StepCompiledIn<FArrayProperty>(nullptr);
|
||||
const void* SourceArrayAddr = Stack.MostRecentPropertyAddress;
|
||||
const FArrayProperty* SourceArrayProperty = CastField<FArrayProperty>(Stack.MostRecentProperty);
|
||||
if (!SourceArrayProperty)
|
||||
{
|
||||
Stack.bArrayContextFailed = true;
|
||||
return;
|
||||
}
|
||||
P_GET_TARRAY_REF(float, Weights);
|
||||
P_GET_PROPERTY(FIntProperty, Count);
|
||||
P_GET_UBOOL(bWithReplacement);
|
||||
P_GET_STRUCT_REF(FRandomStream, RandomStream);
|
||||
|
||||
Stack.MostRecentProperty = nullptr;
|
||||
Stack.StepCompiledIn<FArrayProperty>(nullptr);
|
||||
void* OutArrayAddr = Stack.MostRecentPropertyAddress;
|
||||
const FArrayProperty* OutArrayProperty = CastField<FArrayProperty>(Stack.MostRecentProperty);
|
||||
if (!OutArrayProperty)
|
||||
{
|
||||
Stack.bArrayContextFailed = true;
|
||||
return;
|
||||
}
|
||||
|
||||
P_FINISH;
|
||||
P_NATIVE_BEGIN;
|
||||
*static_cast<bool*>(RESULT_PARAM) = GenericArray_SampleWeighted(
|
||||
SourceArrayAddr,
|
||||
SourceArrayProperty,
|
||||
Weights,
|
||||
Count,
|
||||
bWithReplacement,
|
||||
&RandomStream,
|
||||
OutArrayAddr,
|
||||
OutArrayProperty);
|
||||
P_NATIVE_END;
|
||||
}
|
||||
|
||||
DECLARE_FUNCTION(execArray_GetPage)
|
||||
{
|
||||
Stack.MostRecentProperty = nullptr;
|
||||
Stack.StepCompiledIn<FArrayProperty>(nullptr);
|
||||
const void* SourceArrayAddr = Stack.MostRecentPropertyAddress;
|
||||
const FArrayProperty* SourceArrayProperty = CastField<FArrayProperty>(Stack.MostRecentProperty);
|
||||
if (!SourceArrayProperty)
|
||||
{
|
||||
Stack.bArrayContextFailed = true;
|
||||
return;
|
||||
}
|
||||
P_GET_PROPERTY(FIntProperty, PageIndex);
|
||||
P_GET_PROPERTY(FIntProperty, PageSize);
|
||||
|
||||
Stack.MostRecentProperty = nullptr;
|
||||
Stack.StepCompiledIn<FArrayProperty>(nullptr);
|
||||
void* OutArrayAddr = Stack.MostRecentPropertyAddress;
|
||||
const FArrayProperty* OutArrayProperty = CastField<FArrayProperty>(Stack.MostRecentProperty);
|
||||
if (!OutArrayProperty)
|
||||
{
|
||||
Stack.bArrayContextFailed = true;
|
||||
return;
|
||||
}
|
||||
|
||||
Stack.MostRecentProperty = nullptr;
|
||||
Stack.MostRecentPropertyAddress = nullptr;
|
||||
Stack.StepCompiledIn<FProperty>(nullptr);
|
||||
int32* OutPageCount = reinterpret_cast<int32*>(Stack.MostRecentPropertyAddress);
|
||||
|
||||
P_FINISH;
|
||||
P_NATIVE_BEGIN;
|
||||
*static_cast<bool*>(RESULT_PARAM) = GenericArray_GetPage(SourceArrayAddr, SourceArrayProperty, PageIndex, PageSize, OutArrayAddr, OutArrayProperty, OutPageCount);
|
||||
P_NATIVE_END;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "Kismet/BlueprintFunctionLibrary.h"
|
||||
#include "Types/DirectiveUtilTypes.h"
|
||||
#include "DirectiveUtilFunctionLibrary.generated.h"
|
||||
|
||||
/**
|
||||
@@ -46,14 +47,14 @@ public:
|
||||
* Get the content from the clipboard as FText.
|
||||
* @returns The text from the clipboard.
|
||||
*/
|
||||
UFUNCTION(BlueprintPure, Category = "Directive Utilities|Clipboard" )
|
||||
UFUNCTION(BlueprintCallable, Category = "Directive Utilities|Clipboard" )
|
||||
static FText GetTextFromClipboard();
|
||||
|
||||
/**
|
||||
* Get the content from the clipboard as an FString.
|
||||
* @returns The content from the clipboard as a string.
|
||||
*/
|
||||
UFUNCTION(BlueprintPure, Category = "Directive Utilities|Clipboard" )
|
||||
UFUNCTION(BlueprintCallable, Category = "Directive Utilities|Clipboard" )
|
||||
static FString GetStringFromClipboard();
|
||||
|
||||
/**
|
||||
@@ -77,6 +78,28 @@ public:
|
||||
UFUNCTION(BlueprintPure, Category = "Directive Utilities|Utility")
|
||||
static bool IsRunningInEditor();
|
||||
|
||||
/**
|
||||
* Gets the type of world associated with the supplied context.
|
||||
* @param WorldContextObject Object used to resolve the current world.
|
||||
* @returns The resolved world type, or Unknown when the context has no world.
|
||||
*/
|
||||
UFUNCTION(BlueprintPure, Category = "Directive Utilities|Utility", meta = (WorldContext = "WorldContextObject"))
|
||||
static EDirectiveUtilWorldType GetWorldType(const UObject* WorldContextObject);
|
||||
|
||||
/**
|
||||
* Gets the build configuration of the running application.
|
||||
* @returns The active build configuration.
|
||||
*/
|
||||
UFUNCTION(BlueprintPure, Category = "Directive Utilities|Utility", meta = (BlueprintThreadSafe))
|
||||
static EDirectiveUtilBuildConfiguration GetBuildConfigurationType();
|
||||
|
||||
/**
|
||||
* Gets the build target type of the running application.
|
||||
* @returns The active build target type.
|
||||
*/
|
||||
UFUNCTION(BlueprintPure, Category = "Directive Utilities|Utility", meta = (BlueprintThreadSafe))
|
||||
static EDirectiveUtilBuildTargetType GetBuildTargetType();
|
||||
|
||||
/**
|
||||
* Checks whether a switch (e.g. "MySwitch" matching "-MySwitch") was passed on the
|
||||
* process command line. Matching is case-insensitive.
|
||||
@@ -96,6 +119,24 @@ public:
|
||||
UFUNCTION(BlueprintPure, Category = "Directive Utilities|Utility")
|
||||
static bool GetCommandLineOption(const FString& Key, FString& OutValue);
|
||||
|
||||
/**
|
||||
* Starts a keyed stopwatch using monotonic real time.
|
||||
* @param Key The name used to stop this stopwatch.
|
||||
* @param bRestartIfRunning Whether to replace an active stopwatch with the same key.
|
||||
* @returns True when the stopwatch was started.
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "Directive Utilities|Utility|Profiling", meta = (DisplayName = "Start Stopwatch", Keywords = "timer profiling benchmark elapsed milliseconds"))
|
||||
static bool StartStopwatch(FName Key, bool bRestartIfRunning = false);
|
||||
|
||||
/**
|
||||
* Stops a keyed stopwatch and returns its elapsed real time.
|
||||
* @param Key The name passed to Start Stopwatch.
|
||||
* @param ElapsedMilliseconds The elapsed time in milliseconds, or zero when the key is not active.
|
||||
* @returns True when an active stopwatch was found.
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "Directive Utilities|Utility|Profiling", meta = (DisplayName = "Stop Stopwatch", Keywords = "timer profiling benchmark elapsed milliseconds"))
|
||||
static bool StopStopwatch(FName Key, double& ElapsedMilliseconds);
|
||||
|
||||
/** Core of Has Command Line Switch that checks an explicit command line. */
|
||||
static bool HasCommandLineSwitch(const TCHAR* CommandLine, const FString& Switch);
|
||||
|
||||
|
||||
@@ -32,7 +32,7 @@ public:
|
||||
* @param Tag - The tag to read.
|
||||
* @returns A container of the tag's ancestors.
|
||||
*/
|
||||
UFUNCTION(BlueprintPure, Category = "Directive Utilities|GameplayTags", meta = (BlueprintThreadSafe))
|
||||
UFUNCTION(BlueprintPure, Category = "Directive Utilities|GameplayTags")
|
||||
static FGameplayTagContainer GetTagParents(const FGameplayTag& Tag);
|
||||
|
||||
/**
|
||||
@@ -65,7 +65,7 @@ public:
|
||||
* @param Tag - The tag to read.
|
||||
* @returns A container of the tag's descendants, or an empty container for an invalid tag.
|
||||
*/
|
||||
UFUNCTION(BlueprintPure, Category = "Directive Utilities|GameplayTags", meta = (BlueprintThreadSafe))
|
||||
UFUNCTION(BlueprintPure, Category = "Directive Utilities|GameplayTags")
|
||||
static FGameplayTagContainer GetTagChildren(const FGameplayTag& Tag);
|
||||
|
||||
/**
|
||||
@@ -73,7 +73,7 @@ public:
|
||||
* @param Tag - The tag to read.
|
||||
* @returns A container of the tag's direct children, or an empty container for an invalid tag.
|
||||
*/
|
||||
UFUNCTION(BlueprintPure, Category = "Directive Utilities|GameplayTags", meta = (BlueprintThreadSafe))
|
||||
UFUNCTION(BlueprintPure, Category = "Directive Utilities|GameplayTags")
|
||||
static FGameplayTagContainer GetTagDirectChildren(const FGameplayTag& Tag);
|
||||
|
||||
/**
|
||||
@@ -110,7 +110,7 @@ public:
|
||||
* @param Tag - The tag to test.
|
||||
* @returns True if the tag is valid and has no registered descendants; false for an invalid tag.
|
||||
*/
|
||||
UFUNCTION(BlueprintPure, Category = "Directive Utilities|GameplayTags", meta = (BlueprintThreadSafe))
|
||||
UFUNCTION(BlueprintPure, Category = "Directive Utilities|GameplayTags")
|
||||
static bool IsLeafTag(const FGameplayTag& Tag);
|
||||
|
||||
/**
|
||||
|
||||
@@ -36,7 +36,7 @@ public:
|
||||
/**
|
||||
* Remove multiple Input Mapping Contexts.
|
||||
* @param PlayerController The player controller to remove the contexts from. Will attempt to get the LocalPlayer from the controller.
|
||||
* @param Contexts The contexts to remove.
|
||||
* @param Contexts Loaded contexts to remove. This function does not load missing assets.
|
||||
* @returns Returns Success if the contexts were successfully removed.
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "Directive Utilities|Input", meta=(ExpandEnumAsExecs="ReturnValue", DefaultToSelf="PlayerController"))
|
||||
@@ -45,17 +45,17 @@ public:
|
||||
const TArray<TSoftObjectPtr<UInputMappingContext>>& Contexts);
|
||||
|
||||
/**
|
||||
* Swap a designated Input Mapping Context with a new one.
|
||||
* If the previous context is found, it will be removed and the new context will be added.
|
||||
* If the previous context is not found, the new context will be added at the specified priority.
|
||||
* @param PlayerController The player controller to swap the contexts on. Will attempt to get the LocalPlayer from the controller.
|
||||
* @param PreviousContext The context to swap out.
|
||||
* @param NewContext The context to swap in.
|
||||
* @param Priority The priority to set the new context to.
|
||||
* @param bUsePreviousPriority Whether to use the previous context's priority when adding the new context.
|
||||
* @returns Returns Success if the contexts were successfully swapped.
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "Directive Utilities|Input", meta=(ExpandEnumAsExecs="ReturnValue", DefaultToSelf="PlayerController"))
|
||||
* Swap a designated Input Mapping Context with a new one.
|
||||
* If the previous context is found, it will be removed and the new context will be added.
|
||||
* If the previous context is not found, the new context will be added at the specified priority.
|
||||
* @param PlayerController The player controller to swap the contexts on. Will attempt to get the LocalPlayer from the controller.
|
||||
* @param PreviousContext The context to swap out.
|
||||
* @param NewContext The context to swap in. This asset is loaded synchronously when needed.
|
||||
* @param Priority The priority to set the new context to.
|
||||
* @param bUsePreviousPriority Whether to use the previous context's priority when adding the new context.
|
||||
* @returns Returns Success if the contexts were successfully swapped.
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "Directive Utilities|Input", meta=(ExpandEnumAsExecs="ReturnValue", DefaultToSelf="PlayerController"))
|
||||
static EDirectiveUtilSuccessStatus SwapInputMappingContexts(
|
||||
AController* PlayerController,
|
||||
TSoftObjectPtr<UInputMappingContext> PreviousContext,
|
||||
@@ -74,7 +74,7 @@ public:
|
||||
/**
|
||||
* Returns whether the given input mapping context is currently active on the controller.
|
||||
* @param PlayerController - The player controller to query.
|
||||
* @param Context - The input mapping context to check.
|
||||
* @param Context - The loaded input mapping context to check. This function does not load missing assets.
|
||||
* @returns True if the context is currently applied.
|
||||
*/
|
||||
UFUNCTION(BlueprintPure, Category = "Directive Utilities|Input", meta=(DefaultToSelf="PlayerController"))
|
||||
|
||||
@@ -67,7 +67,7 @@ public:
|
||||
* @param SourceMap - The map to copy from.
|
||||
* @param bOverwriteExisting - If true, keys already present in TargetMap are overwritten with SourceMap's values.
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, CustomThunk, meta=(DisplayName = "Append", CompactNodeTitle = "APPEND", MapParam = "TargetMap|SourceMap"), Category="Directive Utilities|Map")
|
||||
UFUNCTION(BlueprintCallable, CustomThunk, meta=(BlueprintInternalUseOnly = "true", DisplayName = "Append", CompactNodeTitle = "APPEND", MapParam = "TargetMap|SourceMap"), Category="Directive Utilities|Map")
|
||||
static void Map_Append(const TMap<int32, int32>& TargetMap, const TMap<int32, int32>& SourceMap, bool bOverwriteExisting = true);
|
||||
|
||||
/*~
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "Components/SplineComponent.h"
|
||||
#include "Kismet/BlueprintFunctionLibrary.h"
|
||||
#include "Types/DirectiveUtilMathTypes.h"
|
||||
#include "DirectiveUtilMathFunctionLibrary.generated.h"
|
||||
@@ -18,6 +19,7 @@ class DIRECTIVEUTILITIESRUNTIME_API UDirectiveUtilMathFunctionLibrary : public U
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
static constexpr int32 MaximumGeneratedElementCount = 1000000;
|
||||
|
||||
/**
|
||||
* Returns a perlin noise value between -1 and 1 at the given position.
|
||||
@@ -41,18 +43,507 @@ public:
|
||||
* Returns the angle in degrees between two vectors.
|
||||
* @param A - The first vector.
|
||||
* @param B - The second vector.
|
||||
* @returns The angle between the two vectors in degrees.
|
||||
* @returns The angle between the two vectors in degrees, or 0 if either
|
||||
* vector is zero or non-finite.
|
||||
*/
|
||||
UFUNCTION(BlueprintPure, meta = (BlueprintThreadSafe), Category = "Directive Utilities|Math|Vector")
|
||||
static float AngleBetweenVectors(const FVector& A, const FVector& B);
|
||||
|
||||
/**
|
||||
* Returns the signed angle in degrees from one vector to another around an axis.
|
||||
* The vectors are projected onto the plane perpendicular to the axis before measuring.
|
||||
* @param From - The starting direction.
|
||||
* @param To - The target direction.
|
||||
* @param Axis - The axis that defines the rotation plane and positive direction.
|
||||
* @returns The signed angle in the [-180, 180] range, or 0 if an input cannot define a direction.
|
||||
*/
|
||||
UFUNCTION(BlueprintPure, meta = (DisplayName = "Signed Angle Between Vectors", BlueprintThreadSafe), Category = "Directive Utilities|Math|Vector")
|
||||
static float SignedAngleBetweenVectors(const FVector& From, const FVector& To, const FVector& Axis);
|
||||
|
||||
/**
|
||||
* Returns the shortest signed difference in degrees from one angle to another.
|
||||
* Exactly opposite angles always return +180, regardless of how the inputs are spelled.
|
||||
* @param From - The starting angle in degrees.
|
||||
* @param To - The target angle in degrees.
|
||||
* @returns The signed difference in the (-180, 180] range, or 0 for non-finite input.
|
||||
*/
|
||||
UFUNCTION(BlueprintPure, meta = (DisplayName = "Delta Angle (Degrees)", BlueprintThreadSafe), Category = "Directive Utilities|Math|Float")
|
||||
static float DeltaAngle(float From, float To);
|
||||
|
||||
/**
|
||||
* Interpolates between two angles along the shortest path.
|
||||
* Alpha 0 returns A. Values outside [0, 1] extrapolate along that same
|
||||
* shortest-path direction without wrapping, so a timeline past the end
|
||||
* does not jump the seam.
|
||||
* @param A - The starting angle in degrees.
|
||||
* @param B - The target angle in degrees.
|
||||
* @param Alpha - The interpolation alpha. Values outside [0, 1] extrapolate.
|
||||
* @returns A plus the shortest signed delta to B, scaled by Alpha, or 0 for non-finite input.
|
||||
*/
|
||||
UFUNCTION(BlueprintPure, meta = (DisplayName = "Lerp Angle (Degrees)", BlueprintThreadSafe), Category = "Directive Utilities|Math|Float")
|
||||
static float LerpAngle(float A, float B, float Alpha);
|
||||
|
||||
/**
|
||||
* Repeats a value between two bounds, reversing direction at each bound.
|
||||
* @param Value - The value to repeat.
|
||||
* @param Minimum - One range bound.
|
||||
* @param Maximum - The other range bound.
|
||||
* @returns The ping-ponged value, the shared bound for a zero-sized range, or 0 for non-finite input.
|
||||
*/
|
||||
UFUNCTION(BlueprintPure, meta = (DisplayName = "Ping Pong (Float)", BlueprintThreadSafe), Category = "Directive Utilities|Math|Float")
|
||||
static float PingPong(float Value, float Minimum = 0.0f, float Maximum = 1.0f);
|
||||
|
||||
/**
|
||||
* Applies cubic smoothing to a value between two bounds.
|
||||
* @returns A value in the [0, 1] range, or 0 for non-finite input.
|
||||
*/
|
||||
UFUNCTION(BlueprintPure, meta = (DisplayName = "Smooth Step", BlueprintThreadSafe), Category = "Directive Utilities|Math|Float")
|
||||
static float SmoothStep(float Value, float Minimum = 0.0f, float Maximum = 1.0f);
|
||||
|
||||
/**
|
||||
* Applies quintic smoothing to a value between two bounds.
|
||||
* @returns A value in the [0, 1] range, or 0 for non-finite input.
|
||||
*/
|
||||
UFUNCTION(BlueprintPure, meta = (DisplayName = "Smoother Step", BlueprintThreadSafe), Category = "Directive Utilities|Math|Float")
|
||||
static float SmootherStep(float Value, float Minimum = 0.0f, float Maximum = 1.0f);
|
||||
|
||||
/**
|
||||
* Returns a normalized falloff between an inner and outer radius.
|
||||
* @returns 1 at or inside the inner radius, 0 beyond the outer radius, or 0 for non-finite input.
|
||||
* Equal radii are a step: 1 at or inside the shared radius, 0 outside.
|
||||
*/
|
||||
UFUNCTION(BlueprintPure, meta = (DisplayName = "Range Falloff", BlueprintThreadSafe), Category = "Directive Utilities|Math|Float")
|
||||
static float RangeFalloff(float Distance, float InnerRadius, float OuterRadius, float FalloffExponent = 1.0f);
|
||||
|
||||
/**
|
||||
* Tests whether a direction lies within a cone centered on another direction.
|
||||
* @param Direction - The direction to test.
|
||||
* @param ConeDirection - The center direction of the cone.
|
||||
* @param ConeHalfAngleDegrees - The angle from the cone center to its edge. Clamped to [0, 180].
|
||||
* @returns True when the direction lies inside or on the cone, or false for an invalid direction or angle.
|
||||
*/
|
||||
UFUNCTION(BlueprintPure, meta = (DisplayName = "Is Direction Within Cone", BlueprintThreadSafe), Category = "Directive Utilities|Math|Vector")
|
||||
static bool IsDirectionWithinCone(const FVector& Direction, const FVector& ConeDirection, float ConeHalfAngleDegrees);
|
||||
|
||||
/**
|
||||
* Calculates the normalized direction and distance from one point to another.
|
||||
* @returns False when the points are equal or an input is non-finite.
|
||||
*/
|
||||
UFUNCTION(BlueprintPure, meta = (DisplayName = "Get Direction And Distance", BlueprintThreadSafe), Category = "Directive Utilities|Math|Vector")
|
||||
static bool GetDirectionAndDistance(const FVector& From, const FVector& To, FVector& Direction, double& Distance);
|
||||
|
||||
/**
|
||||
* Rotates a 2D point around a pivot in degrees.
|
||||
* @returns The rotated point, or zero for non-finite input.
|
||||
*/
|
||||
UFUNCTION(BlueprintPure, meta = (DisplayName = "Rotate Point Around Pivot 2D", BlueprintThreadSafe), Category = "Directive Utilities|Math|Vector")
|
||||
static FVector2D RotatePointAroundPivot2D(const FVector2D& Point, const FVector2D& Pivot, float AngleDegrees);
|
||||
|
||||
/**
|
||||
* Calculates the signed distance from a point to a plane.
|
||||
* @returns The signed distance, or 0 when the plane normal is zero or an input is non-finite.
|
||||
*/
|
||||
UFUNCTION(BlueprintPure, meta = (DisplayName = "Signed Distance To Plane", BlueprintThreadSafe), Category = "Directive Utilities|Math|Vector")
|
||||
static double SignedDistanceToPlane(const FVector& Point, const FVector& PlanePoint, const FVector& PlaneNormal);
|
||||
|
||||
/**
|
||||
* Tests whether a point lies within a cone and optional maximum distance.
|
||||
* @returns True when the point lies inside or on the cone and within the distance limit.
|
||||
*/
|
||||
UFUNCTION(BlueprintPure, meta = (DisplayName = "Is Point Within Cone", BlueprintThreadSafe), Category = "Directive Utilities|Math|Vector")
|
||||
static bool IsPointWithinCone(const FVector& Point, const FVector& ConeOrigin, const FVector& ConeDirection,
|
||||
float ConeHalfAngleDegrees, double MaximumDistance = 0.0);
|
||||
|
||||
/**
|
||||
* Samples a location along the polyline through an array, with Alpha 0 at the first point and 1 at the last.
|
||||
* Progress is distance-weighted, so equal alpha steps cover equal distance.
|
||||
* A closed loop adds the segment from the last point back to the first and wraps Alpha instead of clamping it.
|
||||
* @returns The sampled location, or the zero vector for an empty array or non-finite input.
|
||||
*/
|
||||
UFUNCTION(BlueprintPure, meta = (DisplayName = "Sample Location Array", BlueprintThreadSafe), Category = "Directive Utilities|Math|Vector")
|
||||
static FVector SampleLocationArray(const TArray<FVector>& Locations, float Alpha, bool bClosedLoop = false);
|
||||
|
||||
/** Creates one transform per location using a shared rotation and scale. */
|
||||
UFUNCTION(BlueprintPure, meta = (DisplayName = "Locations To Transforms", BlueprintThreadSafe), Category = "Directive Utilities|Math|Transform")
|
||||
static TArray<FTransform> LocationsToTransforms(const TArray<FVector>& Locations,
|
||||
FRotator Rotation = FRotator(0.0, 0.0, 0.0), FVector Scale = FVector(1.0, 1.0, 1.0));
|
||||
|
||||
/**
|
||||
* Creates one transform per location with its local X axis facing toward or away from a target.
|
||||
* A location equal to Target uses Rotation Offset without a facing rotation.
|
||||
*/
|
||||
UFUNCTION(BlueprintPure, meta = (DisplayName = "Locations To Facing Transforms", BlueprintThreadSafe, AdvancedDisplay = "UpDirection,RotationOffset,Scale,bFaceAway"), Category = "Directive Utilities|Math|Transform")
|
||||
static TArray<FTransform> LocationsToFacingTransforms(const TArray<FVector>& Locations,
|
||||
FVector Target, FVector UpDirection = FVector(0.0, 0.0, 1.0),
|
||||
FRotator RotationOffset = FRotator(0.0, 0.0, 0.0), FVector Scale = FVector(1.0, 1.0, 1.0),
|
||||
bool bFaceAway = false);
|
||||
|
||||
/**
|
||||
* Creates transforms from location, rotation, and scale arrays.
|
||||
* Rotation and scale arrays may be empty, contain one value to broadcast, or match the location count.
|
||||
* @returns True when the attribute-array lengths and values are valid.
|
||||
*/
|
||||
UFUNCTION(BlueprintPure, meta = (DisplayName = "Make Transforms From Arrays", AutoCreateRefTerm = "Rotations,Scales", BlueprintThreadSafe), Category = "Directive Utilities|Math|Transform")
|
||||
static bool MakeTransformsFromArrays(const TArray<FVector>& Locations, const TArray<FRotator>& Rotations,
|
||||
const TArray<FVector>& Scales, TArray<FTransform>& Transforms);
|
||||
|
||||
/**
|
||||
* Samples a transform along the path through an array, with Alpha 0 at the first transform and 1 at the last.
|
||||
* Progress is distance-weighted by location. Rotation takes the shortest path and scale interpolates linearly.
|
||||
* A closed loop adds the segment from the last transform back to the first and wraps Alpha instead of clamping it.
|
||||
* @returns The sampled transform, or the identity for an empty array or non-finite input.
|
||||
*/
|
||||
UFUNCTION(BlueprintPure, meta = (DisplayName = "Sample Transform Array", BlueprintThreadSafe), Category = "Directive Utilities|Math|Transform")
|
||||
static FTransform SampleTransformArray(const TArray<FTransform>& Transforms, float Alpha, bool bClosedLoop = false);
|
||||
|
||||
/**
|
||||
* Generates a rectangular grid on the local XY plane.
|
||||
* @param Origin - The first point, or the grid center when Centered is true.
|
||||
* @param Rotation - The grid plane rotation.
|
||||
* @param Dimensions - The number of points along the local X and Y axes.
|
||||
* @param Spacing - The signed center-to-center spacing along the local X and Y axes.
|
||||
* @param bCentered - Whether to center the grid on Origin.
|
||||
* @returns Points ordered by X, then Y, or an empty array for invalid input or an unsupported point count.
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, meta = (DisplayName = "Generate Grid Points 2D"), Category = "Directive Utilities|Math|Point Generation")
|
||||
static TArray<FVector> GenerateGridPoints2D(const FVector& Origin, const FRotator& Rotation,
|
||||
FIntPoint Dimensions, const FVector2D& Spacing, bool bCentered = true);
|
||||
|
||||
/**
|
||||
* Generates a rectangular 3D grid.
|
||||
* @param Origin - The first point, or the grid center when Centered is true.
|
||||
* @param Rotation - The grid rotation.
|
||||
* @param Dimensions - The number of points along the local X, Y, and Z axes.
|
||||
* @param Spacing - The signed center-to-center spacing along the local X, Y, and Z axes.
|
||||
* @param bCentered - Whether to center the grid on Origin.
|
||||
* @returns Points ordered by X, then Y, then Z, or an empty array for invalid input or an unsupported point count.
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, meta = (DisplayName = "Generate Grid Points 3D"), Category = "Directive Utilities|Math|Point Generation")
|
||||
static TArray<FVector> GenerateGridPoints3D(const FVector& Origin, const FRotator& Rotation,
|
||||
FIntVector Dimensions, const FVector& Spacing, bool bCentered = true);
|
||||
|
||||
/**
|
||||
* Generates transforms on a rectangular grid on the local XY plane.
|
||||
* @param Origin - The first location, or the grid center when Centered is true.
|
||||
* @param Rotation - The grid plane rotation.
|
||||
* @param Dimensions - The number of points along the local X and Y axes.
|
||||
* @param Spacing - The signed center-to-center spacing along the local X and Y axes.
|
||||
* @param bCentered - Whether to center the grid on Origin.
|
||||
* @param InstanceRotation - Shared rotation applied to every transform.
|
||||
* @param Scale - Shared scale applied to every transform.
|
||||
* @returns Transforms ordered by X, then Y, or an empty array for invalid input or an unsupported count.
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, meta = (DisplayName = "Generate Grid Transforms 2D", AdvancedDisplay = "InstanceRotation,Scale"), Category = "Directive Utilities|Math|Point Generation")
|
||||
static TArray<FTransform> GenerateGridTransforms2D(const FVector& Origin, const FRotator& Rotation,
|
||||
FIntPoint Dimensions, const FVector2D& Spacing, bool bCentered = true,
|
||||
FRotator InstanceRotation = FRotator(0.0, 0.0, 0.0), FVector Scale = FVector(1.0, 1.0, 1.0));
|
||||
|
||||
/**
|
||||
* Generates transforms on a rectangular 3D grid.
|
||||
* @param Origin - The first location, or the grid center when Centered is true.
|
||||
* @param Rotation - The grid rotation.
|
||||
* @param Dimensions - The number of points along the local X, Y, and Z axes.
|
||||
* @param Spacing - The signed center-to-center spacing along the local X, Y, and Z axes.
|
||||
* @param bCentered - Whether to center the grid on Origin.
|
||||
* @param InstanceRotation - Shared rotation applied to every transform.
|
||||
* @param Scale - Shared scale applied to every transform.
|
||||
* @returns Transforms ordered by X, then Y, then Z, or an empty array for invalid input or an unsupported count.
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, meta = (DisplayName = "Generate Grid Transforms 3D", AdvancedDisplay = "InstanceRotation,Scale"), Category = "Directive Utilities|Math|Point Generation")
|
||||
static TArray<FTransform> GenerateGridTransforms3D(const FVector& Origin, const FRotator& Rotation,
|
||||
FIntVector Dimensions, const FVector& Spacing, bool bCentered = true,
|
||||
FRotator InstanceRotation = FRotator(0.0, 0.0, 0.0), FVector Scale = FVector(1.0, 1.0, 1.0));
|
||||
|
||||
/**
|
||||
* Generates a rectangular hex grid on the rotated local XY plane.
|
||||
* @param Origin - The first cell center, or the grid bounds center when Centered is true.
|
||||
* @param Rotation - The grid plane rotation.
|
||||
* @param Dimensions - The number of columns and rows.
|
||||
* @param CellRadius - The distance from a cell center to a corner. Must be positive.
|
||||
* @param Orientation - Whether the hex cells have pointy or flat tops.
|
||||
* @param Gap - The signed edge-to-edge gap between adjacent cells. Negative values overlap cells.
|
||||
* @param bCentered - Whether to center the grid bounds on Origin.
|
||||
* @returns Points ordered by row, then column, or an empty array for invalid input or an unsupported point count.
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, meta = (DisplayName = "Generate Rectangular Hex Grid"), Category = "Directive Utilities|Math|Hex Grid")
|
||||
static TArray<FVector> GenerateRectangularHexGrid(const FVector& Origin, const FRotator& Rotation,
|
||||
FIntPoint Dimensions, double CellRadius,
|
||||
EDirectiveUtilHexOrientation Orientation = EDirectiveUtilHexOrientation::PointyTop,
|
||||
double Gap = 0.0, bool bCentered = true);
|
||||
|
||||
/**
|
||||
* Generates transforms for a rectangular hex grid with a shared instance rotation and scale.
|
||||
* Cell order matches Generate Rectangular Hex Grid.
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, meta = (DisplayName = "Generate Rectangular Hex Grid Transforms", AdvancedDisplay = "InstanceRotation,Scale"), Category = "Directive Utilities|Math|Hex Grid")
|
||||
static TArray<FTransform> GenerateRectangularHexGridTransforms(const FVector& Origin, const FRotator& Rotation,
|
||||
FIntPoint Dimensions, double CellRadius,
|
||||
EDirectiveUtilHexOrientation Orientation = EDirectiveUtilHexOrientation::PointyTop,
|
||||
double Gap = 0.0, bool bCentered = true,
|
||||
FRotator InstanceRotation = FRotator(0.0, 0.0, 0.0), FVector Scale = FVector(1.0, 1.0, 1.0));
|
||||
|
||||
/**
|
||||
* Returns the axial coordinate of every cell of a rectangular hex grid, in the same cell order as
|
||||
* Generate Rectangular Hex Grid.
|
||||
* @returns The coordinates, or an empty array for invalid input or an unsupported count.
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, meta = (DisplayName = "Get Rectangular Hex Grid Coordinates"), Category = "Directive Utilities|Math|Hex Grid")
|
||||
static TArray<FIntPoint> GetRectangularHexGridCoordinates(FIntPoint Dimensions,
|
||||
EDirectiveUtilHexOrientation Orientation = EDirectiveUtilHexOrientation::PointyTop);
|
||||
|
||||
/**
|
||||
* Generates a hexagon-shaped grid on the rotated local XY plane.
|
||||
* @param Origin - The center cell location.
|
||||
* @param Rotation - The grid plane rotation.
|
||||
* @param GridRadius - The number of cell rings around the center cell.
|
||||
* @param CellRadius - The distance from a cell center to a corner. Must be positive.
|
||||
* @param Orientation - Whether the hex cells have pointy or flat tops.
|
||||
* @param Gap - The signed edge-to-edge gap between adjacent cells. Negative values overlap cells.
|
||||
* @returns Points ordered by axial R, then Q, or an empty array for invalid input or an unsupported point count.
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, meta = (DisplayName = "Generate Hexagonal Hex Grid"), Category = "Directive Utilities|Math|Hex Grid")
|
||||
static TArray<FVector> GenerateHexagonalHexGrid(const FVector& Origin, const FRotator& Rotation,
|
||||
int32 GridRadius, double CellRadius,
|
||||
EDirectiveUtilHexOrientation Orientation = EDirectiveUtilHexOrientation::PointyTop,
|
||||
double Gap = 0.0);
|
||||
|
||||
/**
|
||||
* Generates transforms for a hexagon-shaped grid with a shared instance rotation and scale.
|
||||
* Cell order matches Generate Hexagonal Hex Grid.
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, meta = (DisplayName = "Generate Hexagonal Hex Grid Transforms", AdvancedDisplay = "InstanceRotation,Scale"), Category = "Directive Utilities|Math|Hex Grid")
|
||||
static TArray<FTransform> GenerateHexagonalHexGridTransforms(const FVector& Origin, const FRotator& Rotation,
|
||||
int32 GridRadius, double CellRadius,
|
||||
EDirectiveUtilHexOrientation Orientation = EDirectiveUtilHexOrientation::PointyTop,
|
||||
double Gap = 0.0,
|
||||
FRotator InstanceRotation = FRotator(0.0, 0.0, 0.0), FVector Scale = FVector(1.0, 1.0, 1.0));
|
||||
|
||||
/**
|
||||
* Converts an axial hex coordinate to a location on the rotated local XY plane.
|
||||
* @returns The cell center, or the zero vector for invalid layout input or coordinate overflow.
|
||||
*/
|
||||
UFUNCTION(BlueprintPure, meta = (DisplayName = "Hex Coordinate To Location", BlueprintThreadSafe), Category = "Directive Utilities|Math|Hex Grid")
|
||||
static FVector HexCoordinateToLocation(FIntPoint Coordinate, const FVector& Origin, const FRotator& Rotation,
|
||||
double CellRadius, EDirectiveUtilHexOrientation Orientation = EDirectiveUtilHexOrientation::PointyTop,
|
||||
double Gap = 0.0);
|
||||
|
||||
/**
|
||||
* Finds the axial coordinate of the nearest hex after projecting a location onto the rotated local XY plane.
|
||||
* @returns The nearest axial coordinate, or (0, 0) for invalid layout input or an unrepresentable coordinate.
|
||||
*/
|
||||
UFUNCTION(BlueprintPure, meta = (DisplayName = "Location To Hex Coordinate", BlueprintThreadSafe), Category = "Directive Utilities|Math|Hex Grid")
|
||||
static FIntPoint LocationToHexCoordinate(const FVector& Location, const FVector& Origin,
|
||||
const FRotator& Rotation, double CellRadius,
|
||||
EDirectiveUtilHexOrientation Orientation = EDirectiveUtilHexOrientation::PointyTop,
|
||||
double Gap = 0.0);
|
||||
|
||||
/**
|
||||
* Returns the six adjacent axial coordinates in a stable direction order.
|
||||
* @returns Six neighbors, or an empty array when a neighbor would exceed the FIntPoint range.
|
||||
*/
|
||||
UFUNCTION(BlueprintPure, meta = (DisplayName = "Get Hex Neighbors", BlueprintThreadSafe), Category = "Directive Utilities|Math|Hex Grid")
|
||||
static TArray<FIntPoint> GetHexNeighbors(FIntPoint Coordinate);
|
||||
|
||||
/** Returns the number of hex-grid steps between two axial coordinates. */
|
||||
UFUNCTION(BlueprintPure, meta = (DisplayName = "Get Hex Distance", BlueprintThreadSafe), Category = "Directive Utilities|Math|Hex Grid")
|
||||
static int64 GetHexDistance(FIntPoint A, FIntPoint B);
|
||||
|
||||
/**
|
||||
* Returns every axial coordinate within a number of steps of a center cell, ordered by axial R, then Q.
|
||||
* With a zero center the order matches the cells of Generate Hexagonal Hex Grid.
|
||||
* @returns The coordinates, or an empty array for a negative range, coordinate overflow, or an unsupported count.
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, meta = (DisplayName = "Get Hexes In Range"), Category = "Directive Utilities|Math|Hex Grid")
|
||||
static TArray<FIntPoint> GetHexesInRange(FIntPoint Center, int32 Range);
|
||||
|
||||
/**
|
||||
* Returns the axial coordinates exactly Radius steps from a center cell.
|
||||
* Consecutive entries are adjacent and trace the ring once. A radius of zero returns the center.
|
||||
* @returns The ring coordinates, or an empty array for a negative radius or coordinate overflow.
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, meta = (DisplayName = "Get Hex Ring"), Category = "Directive Utilities|Math|Hex Grid")
|
||||
static TArray<FIntPoint> GetHexRing(FIntPoint Center, int32 Radius);
|
||||
|
||||
/**
|
||||
* Returns the axial coordinates along the straight line between two cells, including both endpoints.
|
||||
* @returns The line coordinates, or an empty array for an unsupported length.
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, meta = (DisplayName = "Get Hex Line"), Category = "Directive Utilities|Math|Hex Grid")
|
||||
static TArray<FIntPoint> GetHexLine(FIntPoint Start, FIntPoint End);
|
||||
|
||||
/**
|
||||
* Returns the six corner locations of a hex cell on the rotated local XY plane, ordered counter-clockwise.
|
||||
* Corners lie at Cell Radius from the cell center; Gap only moves the center.
|
||||
* @returns The corner locations, or an empty array for invalid layout input or coordinate overflow.
|
||||
*/
|
||||
UFUNCTION(BlueprintPure, meta = (DisplayName = "Get Hex Cell Corners", BlueprintThreadSafe), Category = "Directive Utilities|Math|Hex Grid")
|
||||
static TArray<FVector> GetHexCellCorners(FIntPoint Coordinate, const FVector& Origin, const FRotator& Rotation,
|
||||
double CellRadius, EDirectiveUtilHexOrientation Orientation = EDirectiveUtilHexOrientation::PointyTop,
|
||||
double Gap = 0.0);
|
||||
|
||||
/**
|
||||
* Generates points at a fixed spacing along a direction.
|
||||
* @param Origin - The first point, or the formation center when Centered is true.
|
||||
* @param Direction - The direction of travel. Its magnitude is ignored.
|
||||
* @param Count - The number of points to generate.
|
||||
* @param Spacing - The signed center-to-center distance between points.
|
||||
* @param bCentered - Whether to center the formation on Origin.
|
||||
* @returns The generated points, or an empty array for invalid input.
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, meta = (DisplayName = "Generate Points Along Direction"), Category = "Directive Utilities|Math|Point Generation")
|
||||
static TArray<FVector> GeneratePointsAlongDirection(const FVector& Origin, const FVector& Direction,
|
||||
int32 Count, double Spacing, bool bCentered = false);
|
||||
|
||||
/**
|
||||
* Generates evenly spaced points between two locations.
|
||||
* @param Start - The start of the segment.
|
||||
* @param End - The end of the segment.
|
||||
* @param Count - The number of points to generate.
|
||||
* @param bIncludeEndpoints - Whether the generated points include Start and End.
|
||||
* @returns The generated points, or an empty array for invalid input.
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, meta = (DisplayName = "Generate Points Between Locations"), Category = "Directive Utilities|Math|Point Generation")
|
||||
static TArray<FVector> GeneratePointsBetweenLocations(const FVector& Start, const FVector& End,
|
||||
int32 Count, bool bIncludeEndpoints = true);
|
||||
|
||||
/**
|
||||
* Generates points at fixed distances along a spline.
|
||||
* @param Spline - The spline to sample.
|
||||
* @param Spacing - The distance between regular samples. Must be positive.
|
||||
* @param bIncludeEndpoint - Whether to append the exact end of an open sampling range.
|
||||
* @param SpacingMode - Fixed samples every Spacing units. Even shrinks the spacing so the samples divide the range evenly.
|
||||
* @param CoordinateSpace - The space of the returned points.
|
||||
* @param StartDistance - The distance where sampling starts. Clamped to the spline length.
|
||||
* @param EndDistance - The distance where sampling ends. Negative means the end of the spline.
|
||||
* @returns The generated points, or an empty array for invalid input or an unsupported point count.
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, meta = (DisplayName = "Generate Points Along Spline", AdvancedDisplay = "SpacingMode,CoordinateSpace,StartDistance,EndDistance"), Category = "Directive Utilities|Math|Point Generation")
|
||||
static TArray<FVector> GeneratePointsAlongSpline(const USplineComponent* Spline, double Spacing,
|
||||
bool bIncludeEndpoint = true,
|
||||
EDirectiveUtilSplineSpacingMode SpacingMode = EDirectiveUtilSplineSpacingMode::Fixed,
|
||||
ESplineCoordinateSpace::Type CoordinateSpace = ESplineCoordinateSpace::World,
|
||||
double StartDistance = 0.0, double EndDistance = -1.0);
|
||||
|
||||
/**
|
||||
* Generates a fixed number of evenly spaced points along a spline.
|
||||
* A closed loop spreads the points around the loop; a count of one on an open range returns its midpoint.
|
||||
* @param Count - The number of points to generate.
|
||||
* @param bIncludeEndpoints - Whether the points include both ends of an open sampling range.
|
||||
* @param StartDistance - The distance where sampling starts. Clamped to the spline length.
|
||||
* @param EndDistance - The distance where sampling ends. Negative means the end of the spline.
|
||||
* @returns The generated points, or an empty array for invalid input.
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, meta = (DisplayName = "Generate Points Along Spline by Count", AdvancedDisplay = "StartDistance,EndDistance"), Category = "Directive Utilities|Math|Point Generation")
|
||||
static TArray<FVector> GeneratePointsAlongSplineByCount(const USplineComponent* Spline, int32 Count,
|
||||
bool bIncludeEndpoints = true,
|
||||
ESplineCoordinateSpace::Type CoordinateSpace = ESplineCoordinateSpace::World,
|
||||
double StartDistance = 0.0, double EndDistance = -1.0);
|
||||
|
||||
/**
|
||||
* Generates transforms at fixed distances along a spline.
|
||||
* Rotation follows the spline tangent and roll. Scale can include the spline scale before applying Scale Multiplier.
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, meta = (DisplayName = "Generate Transforms Along Spline", AdvancedDisplay = "SpacingMode,CoordinateSpace,bUseSplineScale,RotationOffset,ScaleMultiplier,StartDistance,EndDistance"), Category = "Directive Utilities|Math|Point Generation")
|
||||
static TArray<FTransform> GenerateTransformsAlongSpline(const USplineComponent* Spline, double Spacing,
|
||||
bool bIncludeEndpoint = true,
|
||||
EDirectiveUtilSplineSpacingMode SpacingMode = EDirectiveUtilSplineSpacingMode::Fixed,
|
||||
ESplineCoordinateSpace::Type CoordinateSpace = ESplineCoordinateSpace::World,
|
||||
bool bUseSplineScale = true,
|
||||
FRotator RotationOffset = FRotator(0.0, 0.0, 0.0), FVector ScaleMultiplier = FVector(1.0, 1.0, 1.0),
|
||||
double StartDistance = 0.0, double EndDistance = -1.0);
|
||||
|
||||
/**
|
||||
* Generates a fixed number of evenly spaced transforms along a spline.
|
||||
* Rotation follows the spline tangent and roll. Scale can include the spline scale before applying Scale Multiplier.
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, meta = (DisplayName = "Generate Transforms Along Spline by Count", AdvancedDisplay = "bUseSplineScale,RotationOffset,ScaleMultiplier,StartDistance,EndDistance"), Category = "Directive Utilities|Math|Point Generation")
|
||||
static TArray<FTransform> GenerateTransformsAlongSplineByCount(const USplineComponent* Spline, int32 Count,
|
||||
bool bIncludeEndpoints = true,
|
||||
ESplineCoordinateSpace::Type CoordinateSpace = ESplineCoordinateSpace::World,
|
||||
bool bUseSplineScale = true,
|
||||
FRotator RotationOffset = FRotator(0.0, 0.0, 0.0), FVector ScaleMultiplier = FVector(1.0, 1.0, 1.0),
|
||||
double StartDistance = 0.0, double EndDistance = -1.0);
|
||||
|
||||
/**
|
||||
* Generates evenly spaced points around a circle on the rotated local XY plane.
|
||||
* @returns The generated points without repeating the first point, or an empty array for invalid input.
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, meta = (DisplayName = "Generate Points On Circle"), Category = "Directive Utilities|Math|Point Generation")
|
||||
static TArray<FVector> GeneratePointsOnCircle(const FVector& Center, const FRotator& Rotation,
|
||||
double Radius, int32 Count, double StartAngleDegrees = 0.0);
|
||||
|
||||
/** Generates transforms around a circle with fixed, radial, or path-relative orientation. */
|
||||
UFUNCTION(BlueprintCallable, meta = (DisplayName = "Generate Transforms On Circle", AdvancedDisplay = "RotationOffset,Scale"), Category = "Directive Utilities|Math|Point Generation")
|
||||
static TArray<FTransform> GenerateTransformsOnCircle(const FVector& Center, const FRotator& Rotation,
|
||||
double Radius, int32 Count, double StartAngleDegrees = 0.0,
|
||||
EDirectiveUtilRadialOrientation Orientation = EDirectiveUtilRadialOrientation::FaceCenter,
|
||||
FRotator RotationOffset = FRotator(0.0, 0.0, 0.0), FVector Scale = FVector(1.0, 1.0, 1.0));
|
||||
|
||||
/**
|
||||
* Generates evenly spaced points along an arc on the rotated local XY plane.
|
||||
* @param bIncludeEndpoint - Whether the final point lies at Start Angle plus Arc Angle.
|
||||
* @returns The generated points, or an empty array for invalid input.
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, meta = (DisplayName = "Generate Points On Arc"), Category = "Directive Utilities|Math|Point Generation")
|
||||
static TArray<FVector> GeneratePointsOnArc(const FVector& Center, const FRotator& Rotation,
|
||||
double Radius, int32 Count, double StartAngleDegrees = 0.0, double ArcAngleDegrees = 90.0,
|
||||
bool bIncludeEndpoint = true);
|
||||
|
||||
/** Generates transforms along an arc with fixed, radial, or path-relative orientation. */
|
||||
UFUNCTION(BlueprintCallable, meta = (DisplayName = "Generate Transforms On Arc", AdvancedDisplay = "RotationOffset,Scale"), Category = "Directive Utilities|Math|Point Generation")
|
||||
static TArray<FTransform> GenerateTransformsOnArc(const FVector& Center, const FRotator& Rotation,
|
||||
double Radius, int32 Count, double StartAngleDegrees = 0.0, double ArcAngleDegrees = 90.0,
|
||||
bool bIncludeEndpoint = true,
|
||||
EDirectiveUtilRadialOrientation Orientation = EDirectiveUtilRadialOrientation::FaceCenter,
|
||||
FRotator RotationOffset = FRotator(0.0, 0.0, 0.0), FVector Scale = FVector(1.0, 1.0, 1.0));
|
||||
|
||||
/**
|
||||
* Generates a deterministic sunflower distribution across a disc on the rotated local XY plane.
|
||||
* @returns Approximately even area coverage, or an empty array for invalid input.
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, meta = (DisplayName = "Generate Points On Disc"), Category = "Directive Utilities|Math|Point Generation")
|
||||
static TArray<FVector> GeneratePointsOnDisc(const FVector& Center, const FRotator& Rotation,
|
||||
double Radius, int32 Count, double AngleOffsetDegrees = 0.0);
|
||||
|
||||
/**
|
||||
* Generates a deterministic Fibonacci distribution across a sphere surface.
|
||||
* @returns Approximately even surface coverage, or an empty array for invalid input.
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, meta = (DisplayName = "Generate Points On Sphere"), Category = "Directive Utilities|Math|Point Generation")
|
||||
static TArray<FVector> GeneratePointsOnSphere(const FVector& Center, const FRotator& Rotation,
|
||||
double Radius, int32 Count, double AngleOffsetDegrees = 0.0);
|
||||
|
||||
/**
|
||||
* Offsets each location along a direction by Perlin noise sampled at that location.
|
||||
* The offset varies smoothly between -Amplitude and Amplitude across the noise field.
|
||||
* @param Locations - The locations to offset.
|
||||
* @param NoiseScale - The world-space size of the noise features. Must be positive.
|
||||
* @param Amplitude - The maximum offset distance along the direction.
|
||||
* @param Direction - The offset direction. Its magnitude is ignored.
|
||||
* @param NoiseOffset - World-space shift of the noise field, for varying the pattern between layers.
|
||||
* @returns The offset locations, or an empty array for invalid input.
|
||||
*/
|
||||
UFUNCTION(BlueprintPure, meta = (DisplayName = "Offset Locations By Noise", BlueprintThreadSafe, AdvancedDisplay = "Direction,NoiseOffset"), Category = "Directive Utilities|Math|Point Generation")
|
||||
static TArray<FVector> OffsetLocationsByNoise(const TArray<FVector>& Locations, double NoiseScale,
|
||||
double Amplitude, FVector Direction = FVector(0.0, 0.0, 1.0),
|
||||
FVector NoiseOffset = FVector(0.0, 0.0, 0.0));
|
||||
|
||||
/**
|
||||
* Offsets each transform location along a direction by Perlin noise sampled at that location.
|
||||
* Rotation and scale are unchanged. Behaves like Offset Locations By Noise.
|
||||
*/
|
||||
UFUNCTION(BlueprintPure, meta = (DisplayName = "Offset Transforms By Noise", BlueprintThreadSafe, AdvancedDisplay = "Direction,NoiseOffset"), Category = "Directive Utilities|Math|Point Generation")
|
||||
static TArray<FTransform> OffsetTransformsByNoise(const TArray<FTransform>& Transforms, double NoiseScale,
|
||||
double Amplitude, FVector Direction = FVector(0.0, 0.0, 1.0),
|
||||
FVector NoiseOffset = FVector(0.0, 0.0, 0.0));
|
||||
|
||||
/**
|
||||
* Applies a Back/Elastic/Bounce easing curve to a normalized alpha.
|
||||
* @note These are the Penner easing curves the engine's built-in "Ease" node (EEasingFunc) does not provide.
|
||||
* For Sinusoidal/Exponential/Circular/power easings, use the engine's "Ease" node instead.
|
||||
* @param Alpha - The input alpha. Clamped to the [0, 1] range.
|
||||
* @param EaseType - The easing curve to apply.
|
||||
* @returns The eased alpha. Note that Back and Elastic curves intentionally overshoot the [0, 1] range.
|
||||
* @returns The eased alpha. Endpoints are exact. Back and Elastic curves intentionally overshoot the [0, 1] range between the endpoints.
|
||||
*/
|
||||
UFUNCTION(BlueprintPure, meta = (DisplayName = "Ease Alpha", BlueprintThreadSafe), Category = "Directive Utilities|Math|Easing")
|
||||
static float EaseAlpha(float Alpha, EDirectiveUtilEaseType EaseType);
|
||||
@@ -101,6 +592,39 @@ public:
|
||||
UFUNCTION(BlueprintPure, meta = (DisplayName = "Ease (Color)", BlueprintThreadSafe), Category = "Directive Utilities|Math|Easing")
|
||||
static FLinearColor EaseColor(const FLinearColor& A, const FLinearColor& B, float Alpha, EDirectiveUtilEaseType EaseType);
|
||||
|
||||
/**
|
||||
* Eases a transform from A to B. Rotation takes the shortest path; location and scale interpolate linearly
|
||||
* before the eased alpha is applied.
|
||||
* @param A - The start transform (returned at Alpha 0).
|
||||
* @param B - The target transform (returned at Alpha 1).
|
||||
* @param Alpha - The input alpha. Clamped to the [0, 1] range.
|
||||
* @param EaseType - The easing curve to apply.
|
||||
* @returns The eased transform between A and B.
|
||||
*/
|
||||
UFUNCTION(BlueprintPure, meta = (DisplayName = "Ease (Transform)", BlueprintThreadSafe), Category = "Directive Utilities|Math|Easing")
|
||||
static FTransform EaseTransform(const FTransform& A, const FTransform& B, float Alpha, EDirectiveUtilEaseType EaseType);
|
||||
|
||||
/**
|
||||
* Eases each location in From toward the same index in To. Use with two generated layouts to blend formations.
|
||||
* @param Alpha - The shared input alpha. Clamped to the [0, 1] range.
|
||||
* @param PerElementAlphas - When non-empty, one alpha per element replaces Alpha for staggered blends.
|
||||
* @returns The eased locations, or an empty array for mismatched lengths or non-finite input.
|
||||
*/
|
||||
UFUNCTION(BlueprintPure, meta = (DisplayName = "Ease Location Arrays", AutoCreateRefTerm = "PerElementAlphas", BlueprintThreadSafe, AdvancedDisplay = "PerElementAlphas"), Category = "Directive Utilities|Math|Easing")
|
||||
static TArray<FVector> EaseLocationArrays(const TArray<FVector>& From, const TArray<FVector>& To,
|
||||
float Alpha, EDirectiveUtilEaseType EaseType, const TArray<float>& PerElementAlphas);
|
||||
|
||||
/**
|
||||
* Eases each transform in From toward the same index in To. Use with two generated layouts to blend formations.
|
||||
* Rotation takes the shortest path; location and scale interpolate linearly before the eased alpha is applied.
|
||||
* @param Alpha - The shared input alpha. Clamped to the [0, 1] range.
|
||||
* @param PerElementAlphas - When non-empty, one alpha per element replaces Alpha for staggered blends.
|
||||
* @returns The eased transforms, or an empty array for mismatched lengths or non-finite input.
|
||||
*/
|
||||
UFUNCTION(BlueprintPure, meta = (DisplayName = "Ease Transform Arrays", AutoCreateRefTerm = "PerElementAlphas", BlueprintThreadSafe, AdvancedDisplay = "PerElementAlphas"), Category = "Directive Utilities|Math|Easing")
|
||||
static TArray<FTransform> EaseTransformArrays(const TArray<FTransform>& From, const TArray<FTransform>& To,
|
||||
float Alpha, EDirectiveUtilEaseType EaseType, const TArray<float>& PerElementAlphas);
|
||||
|
||||
/**
|
||||
* Rounds a float to a given number of decimal places. Rounds half away from zero,
|
||||
* matching "Round To Decimals (Text)".
|
||||
@@ -138,7 +662,7 @@ public:
|
||||
* Formats a duration in seconds as d/h/m/s units from the largest nonzero unit down, with
|
||||
* two-digit padding after the first ("1h 03m 05s", "2d 04h", "45s"). With bIncludeSeconds
|
||||
* false the seconds unit is dropped and sub-minute durations return "0m". Negative input gets
|
||||
* a leading minus sign; non-finite input returns "0s". Output is English-only.
|
||||
* a leading minus sign when a nonzero unit remains; non-finite input returns "0s". Output is English-only.
|
||||
* @param Seconds - The duration in seconds.
|
||||
* @param bIncludeSeconds - Whether to include the seconds unit.
|
||||
* @returns The formatted duration text.
|
||||
@@ -224,9 +748,59 @@ public:
|
||||
UFUNCTION(BlueprintPure, meta = (BlueprintThreadSafe), Category = "Directive Utilities|Math|Array")
|
||||
static float GetFloatArrayStandardDeviation(const TArray<float>& Values);
|
||||
|
||||
/**
|
||||
* Calculates the circular mean of an angle array in degrees.
|
||||
* @returns False for an empty array, non-finite input, or an undefined or numerically indeterminate circular mean.
|
||||
*/
|
||||
UFUNCTION(BlueprintPure, meta = (DisplayName = "Get Angle Array Average", BlueprintThreadSafe), Category = "Directive Utilities|Math|Array")
|
||||
static bool GetAngleArrayAverage(const TArray<float>& Angles, float& AverageAngle, float& ResultantStrength);
|
||||
|
||||
/**
|
||||
* Calculates the weighted average of a float array.
|
||||
* @returns False when the arrays differ in size, contain invalid values, or have no positive weight.
|
||||
*/
|
||||
UFUNCTION(BlueprintPure, meta = (DisplayName = "Get Weighted Float Array Average", BlueprintThreadSafe), Category = "Directive Utilities|Math|Array")
|
||||
static bool GetWeightedFloatArrayAverage(const TArray<float>& Values, const TArray<float>& Weights, float& Average);
|
||||
|
||||
/**
|
||||
* Calculates the weighted average of a vector array.
|
||||
* @returns False when the arrays differ in size, contain invalid values, or have no positive weight.
|
||||
*/
|
||||
UFUNCTION(BlueprintPure, meta = (DisplayName = "Get Weighted Vector Array Average", BlueprintThreadSafe), Category = "Directive Utilities|Math|Array")
|
||||
static bool GetWeightedVectorArrayAverage(const TArray<FVector>& Values, const TArray<float>& Weights, FVector& Average);
|
||||
|
||||
/**
|
||||
* Normalizes a float array to an output range.
|
||||
* @returns False for an empty array or non-finite input.
|
||||
*/
|
||||
UFUNCTION(BlueprintPure, meta = (DisplayName = "Normalize Float Array To Range", BlueprintThreadSafe), Category = "Directive Utilities|Math|Array")
|
||||
static bool NormalizeFloatArrayToRange(const TArray<float>& Values, float OutputMinimum, float OutputMaximum,
|
||||
TArray<float>& NormalizedValues);
|
||||
|
||||
/**
|
||||
* Normalizes positive weights so their sum is one. Negative and non-finite weights are treated as zero.
|
||||
* @returns False for an empty array or when no positive weight remains.
|
||||
*/
|
||||
UFUNCTION(BlueprintPure, meta = (DisplayName = "Normalize Weights", BlueprintThreadSafe), Category = "Directive Utilities|Math|Array")
|
||||
static bool NormalizeWeights(const TArray<float>& Weights, TArray<float>& NormalizedWeights);
|
||||
|
||||
/**
|
||||
* Calculates a percentile using the Type 7 linear method without modifying the input array.
|
||||
* @returns False for an empty array or non-finite input.
|
||||
*/
|
||||
UFUNCTION(BlueprintPure, meta = (DisplayName = "Get Float Array Percentile", BlueprintThreadSafe), Category = "Directive Utilities|Math|Array")
|
||||
static bool GetFloatArrayPercentile(const TArray<float>& Values, float Percentile, float& Value);
|
||||
|
||||
/**
|
||||
* Calculates the root mean square of a float array.
|
||||
* @returns False for an empty array or non-finite input.
|
||||
*/
|
||||
UFUNCTION(BlueprintPure, meta = (DisplayName = "Get Float Array Root Mean Square", BlueprintThreadSafe), Category = "Directive Utilities|Math|Array")
|
||||
static bool GetFloatArrayRootMeanSquare(const TArray<float>& Values, float& RootMeanSquare);
|
||||
|
||||
/**
|
||||
* Returns a random index into the Weights array, where each index's probability is proportional to its weight.
|
||||
* Useful for loot tables and weighted spawning. Negative weights are treated as zero.
|
||||
* Useful for loot tables and weighted spawning. Negative and non-finite weights are treated as zero.
|
||||
* @param Weights - The per-index weights.
|
||||
* @returns The selected index, or INDEX_NONE (-1) if the array is empty or all weights are zero.
|
||||
*/
|
||||
@@ -236,9 +810,33 @@ public:
|
||||
/**
|
||||
* Deterministic version of Get Random Index From Weights that draws from (and advances) the provided random stream.
|
||||
* @param Stream - The random stream to draw from.
|
||||
* @param Weights - The per-index weights. Negative weights are treated as zero.
|
||||
* @param Weights - The per-index weights. Negative and non-finite weights are treated as zero.
|
||||
* @returns The selected index, or INDEX_NONE (-1) if the array is empty or all weights are zero.
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, meta = (DisplayName = "Get Random Index From Weights (Stream)"), Category = "Directive Utilities|Math|Random")
|
||||
static int32 GetRandomIndexFromWeightsFromStream(UPARAM(ref) FRandomStream& Stream, const TArray<float>& Weights);
|
||||
|
||||
/** Returns a uniformly distributed random point inside a circle. */
|
||||
UFUNCTION(BlueprintCallable, meta = (DisplayName = "Random Point In Circle"), Category = "Directive Utilities|Math|Random")
|
||||
static FVector2D RandomPointInCircle(float Radius);
|
||||
|
||||
/** Returns a deterministic uniformly distributed random point inside a circle. Invalid or zero radii do not advance the stream. */
|
||||
UFUNCTION(BlueprintCallable, meta = (DisplayName = "Random Point In Circle (Stream)"), Category = "Directive Utilities|Math|Random")
|
||||
static FVector2D RandomPointInCircleFromStream(UPARAM(ref) FRandomStream& Stream, float Radius);
|
||||
|
||||
/** Returns a uniformly distributed random point inside a 2D annulus. */
|
||||
UFUNCTION(BlueprintCallable, meta = (DisplayName = "Random Point In Annulus"), Category = "Directive Utilities|Math|Random")
|
||||
static FVector2D RandomPointInAnnulus(float InnerRadius, float OuterRadius);
|
||||
|
||||
/** Returns a deterministic uniformly distributed random point inside a 2D annulus. Invalid or zero radii do not advance the stream. */
|
||||
UFUNCTION(BlueprintCallable, meta = (DisplayName = "Random Point In Annulus (Stream)"), Category = "Directive Utilities|Math|Random")
|
||||
static FVector2D RandomPointInAnnulusFromStream(UPARAM(ref) FRandomStream& Stream, float InnerRadius, float OuterRadius);
|
||||
|
||||
/** Returns a uniformly distributed random point inside a sphere. */
|
||||
UFUNCTION(BlueprintCallable, meta = (DisplayName = "Random Point In Sphere"), Category = "Directive Utilities|Math|Random")
|
||||
static FVector RandomPointInSphere(float Radius);
|
||||
|
||||
/** Returns a deterministic uniformly distributed random point inside a sphere. Invalid or zero radii do not advance the stream. */
|
||||
UFUNCTION(BlueprintCallable, meta = (DisplayName = "Random Point In Sphere (Stream)"), Category = "Directive Utilities|Math|Random")
|
||||
static FVector RandomPointInSphereFromStream(UPARAM(ref) FRandomStream& Stream, float Radius);
|
||||
};
|
||||
|
||||
@@ -13,6 +13,7 @@ class USaveGame;
|
||||
* Save-slot utilities that fill the gaps left by UGameplayStatics: enumerating slots, reading slot
|
||||
* timestamps, and serializing a save object to/from an in-memory byte array. This is slot/IO QoL only,
|
||||
* not a save framework: use the engine's SaveGameToSlot/LoadGameFromSlot for the actual slot I/O.
|
||||
* Slot operations accept flat file names so they behave consistently across platform save backends.
|
||||
*/
|
||||
UCLASS()
|
||||
class DIRECTIVEUTILITIESRUNTIME_API UDirectiveUtilSaveGameFunctionLibrary : public UBlueprintFunctionLibrary
|
||||
@@ -22,9 +23,10 @@ class DIRECTIVEUTILITIESRUNTIME_API UDirectiveUtilSaveGameFunctionLibrary : publ
|
||||
public:
|
||||
|
||||
/**
|
||||
* Returns the names of all existing save slots in the project's default save directory.
|
||||
* @note This enumerates the engine's default file-based save directory (Saved/SaveGames); it does not
|
||||
* cover platform-specific save systems (e.g. console storage).
|
||||
* Returns the names of all existing save slots known to the engine save game system.
|
||||
* Uses ISaveGameSystem::GetSaveGameNames so the result matches DoesSaveSlotExist /
|
||||
* DeleteSaveSlot / RenameSaveSlot. Falls back to the default Saved/SaveGames directory
|
||||
* only when the active backend cannot enumerate slots.
|
||||
* @returns The save slot names (without extension).
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "Directive Utilities|SaveGame")
|
||||
@@ -34,7 +36,7 @@ public:
|
||||
* Returns the last-modified timestamp of a save slot, if it exists.
|
||||
* @param SlotName - The save slot name.
|
||||
* @param OutTimestamp - [out] The slot's last-modified time (local), or a default time if it does not exist.
|
||||
* Converted from the file system's UTC timestamp to local time.
|
||||
* Converted from the file system's UTC timestamp using the timezone rules for that instant.
|
||||
* @returns True if the slot exists.
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "Directive Utilities|SaveGame")
|
||||
@@ -81,8 +83,9 @@ public:
|
||||
/**
|
||||
* Renames a save slot by copying its data to the new name and then deleting the original.
|
||||
* Fails without mutating anything unless both names are valid, the names differ, the old slot
|
||||
* exists, and the new slot does not. On failure the original slot is never lost. Goes through
|
||||
* the engine's save game system, so unlike enumeration it also works on platform save backends.
|
||||
* exists, and the new slot does not. Case-only renames (Slot -> slot) rewrite the existing
|
||||
* slot instead of reporting a collision. On failure the original slot is never lost. Goes through
|
||||
* the engine's save game system so it stays consistent with DoesSaveSlotExist and GetAllSaveSlotNames.
|
||||
* @param OldSlotName - The existing save slot name.
|
||||
* @param NewSlotName - The new save slot name.
|
||||
* @param UserIndex - The platform user index the save belongs to.
|
||||
|
||||
@@ -305,7 +305,8 @@ public:
|
||||
|
||||
/**
|
||||
* Checks whether the string is safe to use as a bare file name: not empty, no path
|
||||
* separators or relative segments, and no characters invalid in file names.
|
||||
* separators or relative segments, no characters invalid in file names, no trailing
|
||||
* dot or space, and not a reserved device name (CON, PRN, AUX, NUL, COM1-9, LPT1-9).
|
||||
* @param String - The string to check.
|
||||
* @returns True if the string is a valid bare file name.
|
||||
*/
|
||||
@@ -314,7 +315,8 @@ public:
|
||||
|
||||
/**
|
||||
* Returns the string with path separators and characters invalid in file names removed
|
||||
* (or replaced when a replacement character is provided). May return an empty string.
|
||||
* (or replaced when a replacement character is provided). Trailing dots and spaces are
|
||||
* stripped. Reserved device names are prefixed with an underscore. May return an empty string.
|
||||
* @param String - The string to sanitize.
|
||||
* @param Replacement - Optional single-character replacement for stripped characters.
|
||||
* @returns The sanitized file name.
|
||||
|
||||
@@ -21,6 +21,6 @@ public:
|
||||
* Returns true if the provided text is not empty.
|
||||
* @param Text - The text to check.
|
||||
*/
|
||||
UFUNCTION(BlueprintPure, Category = "Directive Utilities|Text" )
|
||||
UFUNCTION(BlueprintPure, meta = (AutoCreateRefTerm = "Text"), Category = "Directive Utilities|Text")
|
||||
static bool IsNotEmpty(const FText& Text);
|
||||
};
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
// Copyright (c) 2026 Unreal Directive. Licensed under the MIT License.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "Engine/CancellableAsyncAction.h"
|
||||
#include "Kismet/BlueprintAsyncActionBase.h"
|
||||
#include "DirectiveUtilAsyncActionBase.generated.h"
|
||||
|
||||
UCLASS(Abstract)
|
||||
class DIRECTIVEUTILITIESRUNTIME_API UDirectiveUtilAsyncActionBase : public UBlueprintAsyncActionBase
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
virtual void RegisterWithGameInstance(const UObject* WorldContextObject) override;
|
||||
virtual void SetReadyToDestroy() override;
|
||||
|
||||
protected:
|
||||
virtual void BeginDestroy() override;
|
||||
|
||||
private:
|
||||
void HandleWorldCleanup(UWorld* World, bool bSessionEnded, bool bCleanupResources);
|
||||
void UnbindWorldCleanup();
|
||||
|
||||
TWeakObjectPtr<UWorld> RegisteredWorld;
|
||||
FDelegateHandle WorldCleanupHandle;
|
||||
};
|
||||
|
||||
UCLASS(Abstract)
|
||||
class DIRECTIVEUTILITIESRUNTIME_API UDirectiveUtilCancellableAsyncAction : public UCancellableAsyncAction
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
virtual void RegisterWithGameInstance(const UObject* WorldContextObject) override;
|
||||
virtual void SetReadyToDestroy() override;
|
||||
|
||||
protected:
|
||||
virtual void BeginDestroy() override;
|
||||
|
||||
private:
|
||||
void HandleWorldCleanup(UWorld* World, bool bSessionEnded, bool bCleanupResources);
|
||||
void UnbindWorldCleanup();
|
||||
|
||||
TWeakObjectPtr<UWorld> RegisteredWorld;
|
||||
FDelegateHandle WorldCleanupHandle;
|
||||
};
|
||||
@@ -3,7 +3,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "Kismet/BlueprintAsyncActionBase.h"
|
||||
#include "Tasks/DirectiveUtilAsyncActionBase.h"
|
||||
#include "UObject/SoftObjectPtr.h"
|
||||
#include "DirectiveUtilTask_AsyncLoadAsset.generated.h"
|
||||
|
||||
@@ -19,7 +19,7 @@ DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnAsyncLoadAssetsProgress, int32,
|
||||
* Asynchronously loads a soft object reference and broadcasts the loaded asset, with a cancel option.
|
||||
*/
|
||||
UCLASS(BlueprintType, meta=(ExposedAsyncProxy = AsyncTask, DisplayName="Async Load Asset"))
|
||||
class DIRECTIVEUTILITIESRUNTIME_API UDirectiveUtilTask_AsyncLoadAsset : public UBlueprintAsyncActionBase
|
||||
class DIRECTIVEUTILITIESRUNTIME_API UDirectiveUtilTask_AsyncLoadAsset : public UDirectiveUtilAsyncActionBase
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
@@ -71,7 +71,7 @@ protected:
|
||||
* Asynchronously loads a soft class reference and broadcasts the loaded class, with a cancel option.
|
||||
*/
|
||||
UCLASS(BlueprintType, meta=(ExposedAsyncProxy = AsyncTask, DisplayName="Async Load Class"))
|
||||
class DIRECTIVEUTILITIESRUNTIME_API UDirectiveUtilTask_AsyncLoadClass : public UBlueprintAsyncActionBase
|
||||
class DIRECTIVEUTILITIESRUNTIME_API UDirectiveUtilTask_AsyncLoadClass : public UDirectiveUtilAsyncActionBase
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
@@ -124,7 +124,7 @@ protected:
|
||||
* loaded assets, with progress updates and a cancel option.
|
||||
*/
|
||||
UCLASS(BlueprintType, meta=(ExposedAsyncProxy = AsyncTask, DisplayName="Async Load Assets"))
|
||||
class DIRECTIVEUTILITIESRUNTIME_API UDirectiveUtilTask_AsyncLoadAssets : public UBlueprintAsyncActionBase
|
||||
class DIRECTIVEUTILITIESRUNTIME_API UDirectiveUtilTask_AsyncLoadAssets : public UDirectiveUtilAsyncActionBase
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "Kismet/BlueprintAsyncActionBase.h"
|
||||
#include "Tasks/DirectiveUtilAsyncActionBase.h"
|
||||
#include "Engine/EngineTypes.h"
|
||||
#include "Engine/HitResult.h"
|
||||
#include "DirectiveUtilTask_AsyncTrace.generated.h"
|
||||
@@ -28,7 +28,7 @@ enum class EDirectiveUtilTraceShape : uint8
|
||||
* The trace cannot be cancelled.
|
||||
*/
|
||||
UCLASS(BlueprintType, meta=(ExposedAsyncProxy = AsyncTask, DisplayName="Async Trace"))
|
||||
class DIRECTIVEUTILITIESRUNTIME_API UDirectiveUtilTask_AsyncTrace : public UBlueprintAsyncActionBase
|
||||
class DIRECTIVEUTILITIESRUNTIME_API UDirectiveUtilTask_AsyncTrace : public UDirectiveUtilAsyncActionBase
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "Kismet/BlueprintAsyncActionBase.h"
|
||||
#include "Tasks/DirectiveUtilAsyncActionBase.h"
|
||||
#include "Engine/TimerHandle.h"
|
||||
#include "DirectiveUtilTask_Delay.generated.h"
|
||||
|
||||
@@ -11,10 +11,10 @@ DECLARE_DYNAMIC_MULTICAST_DELEGATE(FOnDelayCompleted);
|
||||
|
||||
/**
|
||||
* DirectiveUtilTask_Delay
|
||||
* A cancellable delay that can be ended early by calling EndTask.
|
||||
* A cancellable delay.
|
||||
*/
|
||||
UCLASS(BlueprintType, meta=(ExposedAsyncProxy = AsyncTask, DisplayName="Cancellable Delay"))
|
||||
class DIRECTIVEUTILITIESRUNTIME_API UDirectiveUtilTask_Delay : public UBlueprintAsyncActionBase
|
||||
class DIRECTIVEUTILITIESRUNTIME_API UDirectiveUtilTask_Delay : public UDirectiveUtilCancellableAsyncAction
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
@@ -23,10 +23,10 @@ public:
|
||||
/**
|
||||
* Starts a cancellable delay.
|
||||
* When the delay has completed, the Completed delegate is called.
|
||||
* Call EndTask to cancel the delay before it completes.
|
||||
* Call Cancel to stop the delay before it completes.
|
||||
*
|
||||
* @param WorldContextObject The world context object.
|
||||
* @param Duration The duration of the delay in seconds.
|
||||
* @param Duration The duration of the delay in seconds. Non-finite and non-positive values complete on the next timer tick.
|
||||
*/
|
||||
UFUNCTION(
|
||||
BlueprintCallable,
|
||||
@@ -38,14 +38,13 @@ public:
|
||||
))
|
||||
static UDirectiveUtilTask_Delay* CancellableDelay(UObject* WorldContextObject, float Duration);
|
||||
|
||||
/**
|
||||
* Ends the delay early.
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "Directive Utilities|FlowControl")
|
||||
UFUNCTION(BlueprintCallable, meta=(DeprecatedFunction, DeprecationMessage="Use Cancel instead."), Category = "Directive Utilities|FlowControl")
|
||||
void EndTask();
|
||||
virtual void Activate() override;
|
||||
virtual void Cancel() override;
|
||||
virtual bool IsActive() const override;
|
||||
virtual bool ShouldBroadcastDelegates() const override;
|
||||
|
||||
// The delegate called when the delay has completed.
|
||||
UPROPERTY(BlueprintAssignable)
|
||||
FOnDelayCompleted Completed;
|
||||
|
||||
@@ -55,6 +54,7 @@ protected:
|
||||
TObjectPtr<UObject> WorldContextObject;
|
||||
|
||||
float Duration = 0.0f;
|
||||
bool bFinished = false;
|
||||
FTimerHandle TimerHandle;
|
||||
|
||||
void OnDelayComplete();
|
||||
|
||||
@@ -0,0 +1,121 @@
|
||||
// Copyright (c) 2026 Unreal Directive. Licensed under the MIT License.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "Tasks/DirectiveUtilAsyncActionBase.h"
|
||||
#include "Engine/TimerHandle.h"
|
||||
#include "DirectiveUtilTask_Flow.generated.h"
|
||||
|
||||
DECLARE_DYNAMIC_MULTICAST_DELEGATE_ThreeParams(FOnDurationUpdated, float, ElapsedTime, float, DeltaTime, float, Alpha);
|
||||
DECLARE_DYNAMIC_MULTICAST_DELEGATE(FOnDurationCompleted);
|
||||
DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnRepeatIteration, int32, Index, int32, Remaining);
|
||||
DECLARE_DYNAMIC_MULTICAST_DELEGATE(FOnRepeatCompleted);
|
||||
|
||||
UCLASS(BlueprintType, meta=(ExposedAsyncProxy = AsyncTask, DisplayName="Update for Duration"))
|
||||
class DIRECTIVEUTILITIESRUNTIME_API UDirectiveUtilTask_UpdateForDuration : public UDirectiveUtilCancellableAsyncAction
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
UFUNCTION(
|
||||
BlueprintCallable,
|
||||
meta=(
|
||||
BlueprintInternalUseOnly = "true",
|
||||
Category = "Directive Utilities|FlowControl",
|
||||
WorldContext = "WorldContextObject",
|
||||
DisplayName = "Update for Duration",
|
||||
AdvancedDisplay = "UpdateInterval"
|
||||
))
|
||||
static UDirectiveUtilTask_UpdateForDuration* UpdateForDuration(
|
||||
UObject* WorldContextObject,
|
||||
float Duration,
|
||||
float UpdateInterval = 0.0f);
|
||||
|
||||
virtual void Activate() override;
|
||||
virtual void Cancel() override;
|
||||
virtual bool IsActive() const override;
|
||||
virtual bool ShouldBroadcastDelegates() const override;
|
||||
|
||||
UPROPERTY(BlueprintAssignable)
|
||||
FOnDurationUpdated Updated;
|
||||
|
||||
UPROPERTY(BlueprintAssignable)
|
||||
FOnDurationCompleted Completed;
|
||||
|
||||
private:
|
||||
UPROPERTY()
|
||||
TObjectPtr<UObject> WorldContextObject;
|
||||
|
||||
float Duration = 0.0f;
|
||||
float UpdateInterval = 0.0f;
|
||||
float LastElapsedTime = 0.0f;
|
||||
bool bHasUpdated = false;
|
||||
bool bFinished = false;
|
||||
FTimerHandle UpdateTimerHandle;
|
||||
FTimerHandle CompletionTimerHandle;
|
||||
|
||||
void OnUpdate();
|
||||
void OnComplete();
|
||||
void BroadcastUpdate(float ElapsedTime, float Alpha);
|
||||
void ClearTimers();
|
||||
};
|
||||
|
||||
UCLASS(BlueprintType, meta=(ExposedAsyncProxy = AsyncTask, DisplayName="Repeat with Interval"))
|
||||
class DIRECTIVEUTILITIESRUNTIME_API UDirectiveUtilTask_RepeatWithInterval : public UDirectiveUtilCancellableAsyncAction
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
/**
|
||||
* Runs a fixed number of iterations, or forever when Count is -1.
|
||||
* @param Count - Iteration count. Use -1 to repeat until Cancel. Zero and other negative values complete on the next tick with no iterations.
|
||||
* @param Interval - Delay between iterations. Non-positive or non-finite values run on consecutive ticks.
|
||||
* @param InitialDelay - Delay before the first iteration. Non-positive or non-finite values start on the next tick.
|
||||
*/
|
||||
UFUNCTION(
|
||||
BlueprintCallable,
|
||||
meta=(
|
||||
BlueprintInternalUseOnly = "true",
|
||||
Category = "Directive Utilities|FlowControl",
|
||||
WorldContext = "WorldContextObject",
|
||||
DisplayName = "Repeat with Interval",
|
||||
AdvancedDisplay = "InitialDelay"
|
||||
))
|
||||
static UDirectiveUtilTask_RepeatWithInterval* RepeatWithInterval(
|
||||
UObject* WorldContextObject,
|
||||
int32 Count,
|
||||
float Interval,
|
||||
float InitialDelay = 0.0f);
|
||||
|
||||
virtual void Activate() override;
|
||||
virtual void Cancel() override;
|
||||
virtual bool IsActive() const override;
|
||||
virtual bool ShouldBroadcastDelegates() const override;
|
||||
|
||||
#if WITH_DEV_AUTOMATION_TESTS
|
||||
void SetNextIndexForTesting(int32 Index) { NextIndex = Index; }
|
||||
#endif
|
||||
|
||||
UPROPERTY(BlueprintAssignable)
|
||||
FOnRepeatIteration Iteration;
|
||||
|
||||
UPROPERTY(BlueprintAssignable)
|
||||
FOnRepeatCompleted Completed;
|
||||
|
||||
private:
|
||||
UPROPERTY()
|
||||
TObjectPtr<UObject> WorldContextObject;
|
||||
|
||||
int32 Count = 0;
|
||||
int32 NextIndex = 0;
|
||||
float Interval = 0.0f;
|
||||
float InitialDelay = 0.0f;
|
||||
bool bFinished = false;
|
||||
FTimerHandle TimerHandle;
|
||||
|
||||
void Schedule(float Delay);
|
||||
void OnIteration();
|
||||
void Complete();
|
||||
void ClearTimer();
|
||||
};
|
||||
@@ -3,7 +3,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "Kismet/BlueprintAsyncActionBase.h"
|
||||
#include "Tasks/DirectiveUtilAsyncActionBase.h"
|
||||
#include "GameFramework/Controller.h"
|
||||
#include "DirectiveUtilTask_MoveToLocation.generated.h"
|
||||
|
||||
@@ -15,7 +15,7 @@ DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnAsyncMoveToActor, bool, bSuccess)
|
||||
* Asynchronously moves an actor to a location.
|
||||
*/
|
||||
UCLASS(BlueprintType, meta=(ExposedAsyncProxy = AsyncTask, DisplayName="Async Move To Location"))
|
||||
class DIRECTIVEUTILITIESRUNTIME_API UDirectiveUtilTask_MoveToLocation : public UBlueprintAsyncActionBase
|
||||
class DIRECTIVEUTILITIESRUNTIME_API UDirectiveUtilTask_MoveToLocation : public UDirectiveUtilAsyncActionBase
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
@@ -27,15 +27,15 @@ public:
|
||||
* bSuccess is true only when the pawn ends within AcceptanceRadius of Destination; the task also ends
|
||||
* (with the same distance test) when path-following stops for any reason.
|
||||
*
|
||||
* If the controller or pawn is destroyed while moving, the task will automatically end.
|
||||
* If the controller, pawn, or world becomes unavailable while moving, the task will automatically end.
|
||||
* If bCheckStuckMovement is enabled and the controller gets stuck while moving, the task will automatically end.
|
||||
*
|
||||
* @param WorldContextObject The world context object.
|
||||
* @param Controller The controller to move.
|
||||
* @param Destination The vector location to move to.
|
||||
* @param AcceptanceRadius The radius around the destination location that is considered acceptable. Be sure to set this to a reasonable value as the controller may never reach the exact destination.
|
||||
* @param AcceptanceRadius The radius around the destination location that is considered acceptable. Negative and non-finite values are treated as zero.
|
||||
* @param bCheckStuckMovement Check if the controller gets stuck while moving.
|
||||
* @param StuckThreshold The distance threshold to consider the controller stuck.
|
||||
* @param StuckThreshold The distance threshold to consider the controller stuck. Negative and non-finite values are treated as zero.
|
||||
* @param bDebugLineTrace Display a line trace to the destination location for a short duration.
|
||||
*/
|
||||
UFUNCTION(
|
||||
@@ -86,6 +86,8 @@ protected:
|
||||
|
||||
FTimerHandle StuckTimerHandle;
|
||||
|
||||
TWeakObjectPtr<UWorld> TimerWorld;
|
||||
|
||||
bool bHasCompleted = false;
|
||||
|
||||
void CheckMoveToLocation();
|
||||
@@ -100,7 +102,7 @@ protected:
|
||||
* Asynchronously moves an actor to another actor.
|
||||
*/
|
||||
UCLASS(BlueprintType, meta=(ExposedAsyncProxy = AsyncTask, DisplayName="Async Move To Actor"))
|
||||
class DIRECTIVEUTILITIESRUNTIME_API UDirectiveUtilTask_MoveToActor : public UBlueprintAsyncActionBase
|
||||
class DIRECTIVEUTILITIESRUNTIME_API UDirectiveUtilTask_MoveToActor : public UDirectiveUtilAsyncActionBase
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
@@ -113,15 +115,15 @@ public:
|
||||
* (with the same distance test) when path-following stops for any reason. The goal's location is re-read
|
||||
* every poll, so a moving goal is tracked.
|
||||
*
|
||||
* If the controller, pawn, or goal actor is destroyed while moving, the task will automatically end.
|
||||
* If the controller, pawn, goal actor, or world becomes unavailable while moving, the task will automatically end.
|
||||
* If bCheckStuckMovement is enabled and the controller gets stuck while moving, the task will automatically end.
|
||||
*
|
||||
* @param WorldContextObject The world context object.
|
||||
* @param Controller The controller to move.
|
||||
* @param Goal The actor to move to.
|
||||
* @param AcceptanceRadius The radius around the goal actor that is considered acceptable. Be sure to set this to a reasonable value as the controller may never reach the goal's exact location.
|
||||
* @param AcceptanceRadius The radius around the goal actor that is considered acceptable. Negative and non-finite values are treated as zero.
|
||||
* @param bCheckStuckMovement Check if the controller gets stuck while moving.
|
||||
* @param StuckThreshold The distance threshold to consider the controller stuck.
|
||||
* @param StuckThreshold The distance threshold to consider the controller stuck. Negative and non-finite values are treated as zero.
|
||||
*/
|
||||
UFUNCTION(
|
||||
BlueprintCallable,
|
||||
@@ -171,6 +173,8 @@ protected:
|
||||
|
||||
FTimerHandle StuckTimerHandle;
|
||||
|
||||
TWeakObjectPtr<UWorld> TimerWorld;
|
||||
|
||||
bool bHasCompleted = false;
|
||||
|
||||
void CheckMoveToActor();
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
#pragma once
|
||||
// Copyright (c) 2026 Unreal Directive. Licensed under the MIT License.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "DirectiveUtilInputTypes.generated.h"
|
||||
|
||||
@@ -5,8 +5,37 @@
|
||||
#include "CoreMinimal.h"
|
||||
#include "DirectiveUtilMathTypes.generated.h"
|
||||
|
||||
/** Hex tile orientation on the local XY plane. */
|
||||
UENUM(BlueprintType)
|
||||
enum class EDirectiveUtilHexOrientation : uint8
|
||||
{
|
||||
PointyTop UMETA(DisplayName = "Pointy Top"),
|
||||
FlatTop UMETA(DisplayName = "Flat Top"),
|
||||
};
|
||||
|
||||
/** Rotation applied to transforms generated around a center point. */
|
||||
UENUM(BlueprintType)
|
||||
enum class EDirectiveUtilRadialOrientation : uint8
|
||||
{
|
||||
Fixed UMETA(DisplayName = "Fixed"),
|
||||
FaceCenter UMETA(DisplayName = "Face Center"),
|
||||
FaceAwayFromCenter UMETA(DisplayName = "Face Away From Center"),
|
||||
FollowPath UMETA(DisplayName = "Follow Path"),
|
||||
FaceAgainstPath UMETA(DisplayName = "Face Against Path"),
|
||||
};
|
||||
|
||||
/** Spacing behavior for spline sample generation. */
|
||||
UENUM(BlueprintType)
|
||||
enum class EDirectiveUtilSplineSpacingMode : uint8
|
||||
{
|
||||
Fixed UMETA(DisplayName = "Fixed", Tooltip = "Samples every Spacing units. The final interval may be shorter."),
|
||||
Even UMETA(DisplayName = "Even", Tooltip = "Shrinks Spacing so the samples divide the range evenly."),
|
||||
};
|
||||
|
||||
/**
|
||||
* Easing curves not provided by the engine's built-in Ease node (EEasingFunc): the classic Penner Back, Elastic and Bounce curves.
|
||||
* Easing curves not provided by the engine's built-in Ease node (EEasingFunc): the classic Penner Back, Elastic and Bounce curves,
|
||||
* plus Linear for un-eased interpolation.
|
||||
* New values append at the end so existing Blueprint ordinals stay stable.
|
||||
*/
|
||||
UENUM(BlueprintType)
|
||||
enum class EDirectiveUtilEaseType : uint8
|
||||
@@ -20,4 +49,5 @@ enum class EDirectiveUtilEaseType : uint8
|
||||
BounceIn UMETA(DisplayName = "Bounce In", Tooltip="Bounces with increasing energy before easing in."),
|
||||
BounceOut UMETA(DisplayName = "Bounce Out", Tooltip="Bounces with decreasing energy after the end."),
|
||||
BounceInOut UMETA(DisplayName = "Bounce In Out", Tooltip="Bounces at both the start and the end."),
|
||||
Linear UMETA(DisplayName = "Linear", Tooltip="Interpolates at a constant rate without easing."),
|
||||
};
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
#pragma once
|
||||
// Copyright (c) 2026 Unreal Directive. Licensed under the MIT License.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "DirectiveUtilTypes.generated.h"
|
||||
@@ -11,4 +13,40 @@ enum class EDirectiveUtilSuccessStatus : uint8
|
||||
{
|
||||
Success,
|
||||
Failure,
|
||||
};
|
||||
};
|
||||
|
||||
UENUM(BlueprintType)
|
||||
enum class EDirectiveUtilWorldType : uint8
|
||||
{
|
||||
Unknown,
|
||||
None,
|
||||
Game,
|
||||
Editor,
|
||||
PlayInEditor UMETA(DisplayName = "Play In Editor"),
|
||||
EditorPreview UMETA(DisplayName = "Editor Preview"),
|
||||
GamePreview UMETA(DisplayName = "Game Preview"),
|
||||
GameRPC UMETA(DisplayName = "Game RPC"),
|
||||
Inactive,
|
||||
};
|
||||
|
||||
UENUM(BlueprintType)
|
||||
enum class EDirectiveUtilBuildConfiguration : uint8
|
||||
{
|
||||
Unknown,
|
||||
Debug,
|
||||
DebugGame UMETA(DisplayName = "Debug Game"),
|
||||
Development,
|
||||
Shipping,
|
||||
Test,
|
||||
};
|
||||
|
||||
UENUM(BlueprintType)
|
||||
enum class EDirectiveUtilBuildTargetType : uint8
|
||||
{
|
||||
Unknown,
|
||||
Game,
|
||||
Server,
|
||||
Client,
|
||||
Editor,
|
||||
Program,
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user