Import helper plugin
This commit is contained in:
@@ -0,0 +1,42 @@
|
||||
// Copyright (c) 2026 Unreal Directive. Licensed under the MIT License.
|
||||
|
||||
using UnrealBuildTool;
|
||||
|
||||
public class DirectiveUtilitiesRuntime : ModuleRules
|
||||
{
|
||||
public DirectiveUtilitiesRuntime(ReadOnlyTargetRules Target) : base(Target)
|
||||
{
|
||||
PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs;
|
||||
|
||||
|
||||
PublicDependencyModuleNames.AddRange(
|
||||
new string[]
|
||||
{
|
||||
"Core",
|
||||
"GameplayTags",
|
||||
"NetCore",
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
PrivateDependencyModuleNames.AddRange(
|
||||
new string[]
|
||||
{
|
||||
"CoreUObject",
|
||||
"Engine",
|
||||
"Slate",
|
||||
"SlateCore",
|
||||
"AIModule",
|
||||
"EnhancedInput",
|
||||
"ApplicationCore",
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
DynamicallyLoadedModuleNames.AddRange(
|
||||
new string[]
|
||||
{
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
#include "DirectiveUtilLogChannels.h"
|
||||
|
||||
DEFINE_LOG_CATEGORY(LogDirectiveUtil);
|
||||
DEFINE_LOG_CATEGORY(LogDirectiveUtilEditor);
|
||||
@@ -0,0 +1,13 @@
|
||||
// Copyright (c) 2026 Unreal Directive. Licensed under the MIT License.
|
||||
|
||||
#include "DirectiveUtilitiesRuntime.h"
|
||||
|
||||
void FDirectiveUtilitiesRuntimeModule::StartupModule()
|
||||
{
|
||||
}
|
||||
|
||||
void FDirectiveUtilitiesRuntimeModule::ShutdownModule()
|
||||
{
|
||||
}
|
||||
|
||||
IMPLEMENT_MODULE(FDirectiveUtilitiesRuntimeModule, DirectiveUtilitiesRuntime)
|
||||
@@ -0,0 +1,565 @@
|
||||
// Copyright (c) 2026 Unreal Directive. Licensed under the MIT License.
|
||||
|
||||
|
||||
#include "Libraries/DirectiveUtilArrayFunctionLibrary.h"
|
||||
|
||||
int32 UDirectiveUtilArrayFunctionLibrary::Array_NextIndex(const TArray<int32>& TargetArray, const int32 Index, const bool bLoop)
|
||||
{
|
||||
checkNoEntry();
|
||||
return 0;
|
||||
}
|
||||
|
||||
int32 UDirectiveUtilArrayFunctionLibrary::Array_PreviousIndex(
|
||||
const TArray<int32>& TargetArray,
|
||||
const int32 Index,
|
||||
const bool bLoop)
|
||||
{
|
||||
checkNoEntry();
|
||||
return 0;
|
||||
}
|
||||
|
||||
int32 UDirectiveUtilArrayFunctionLibrary::GenericArray_NextIndex(
|
||||
const void* TargetArray,
|
||||
const FArrayProperty* ArrayProperty,
|
||||
const int32 Index,
|
||||
const bool bLoop)
|
||||
{
|
||||
if (!TargetArray || !ArrayProperty)
|
||||
{
|
||||
return INDEX_NONE;
|
||||
}
|
||||
|
||||
const FScriptArrayHelper ArrayHelper(ArrayProperty, TargetArray);
|
||||
const int32 NextIndex = Index + 1;
|
||||
|
||||
if(ArrayHelper.Num() == 0)
|
||||
{
|
||||
return INDEX_NONE;
|
||||
}
|
||||
|
||||
if(NextIndex < 0)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (NextIndex <= ArrayHelper.Num() - 1)
|
||||
{
|
||||
return NextIndex;
|
||||
}
|
||||
|
||||
if (bLoop)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
return ArrayHelper.Num() - 1;
|
||||
}
|
||||
|
||||
void UDirectiveUtilArrayFunctionLibrary::Array_RemoveDuplicates(const TArray<int32>& TargetArray)
|
||||
{
|
||||
checkNoEntry();
|
||||
}
|
||||
|
||||
void UDirectiveUtilArrayFunctionLibrary::GenericArray_RemoveDuplicates(
|
||||
void* TargetArray,
|
||||
const FArrayProperty* ArrayProperty)
|
||||
{
|
||||
if (!TargetArray || !ArrayProperty)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
FScriptArrayHelper ArrayHelper(ArrayProperty, TargetArray);
|
||||
const FProperty* InnerProp = ArrayProperty->Inner;
|
||||
|
||||
for (int32 OuterIndex = ArrayHelper.Num() - 1; OuterIndex > 0; --OuterIndex)
|
||||
{
|
||||
for (int32 InnerIndex = 0; InnerIndex < OuterIndex; ++InnerIndex)
|
||||
{
|
||||
if (InnerProp->Identical(ArrayHelper.GetElementPtr(OuterIndex), ArrayHelper.GetElementPtr(InnerIndex)))
|
||||
{
|
||||
ArrayHelper.RemoveValues(OuterIndex);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int32 UDirectiveUtilArrayFunctionLibrary::GenericArray_PreviousIndex(
|
||||
const void* TargetArray,
|
||||
const FArrayProperty* ArrayProperty,
|
||||
const int32 Index,
|
||||
const bool bLoop)
|
||||
{
|
||||
if (!TargetArray || !ArrayProperty)
|
||||
{
|
||||
return INDEX_NONE;
|
||||
}
|
||||
|
||||
const FScriptArrayHelper ArrayHelper(ArrayProperty, TargetArray);
|
||||
const int32 PreviousIndex = Index - 1;
|
||||
|
||||
if(ArrayHelper.Num() == 0)
|
||||
{
|
||||
return INDEX_NONE;
|
||||
}
|
||||
|
||||
if(PreviousIndex > ArrayHelper.Num() - 1)
|
||||
{
|
||||
return ArrayHelper.Num() - 1;
|
||||
}
|
||||
|
||||
if (PreviousIndex >= 0)
|
||||
{
|
||||
return PreviousIndex;
|
||||
}
|
||||
|
||||
if (bLoop)
|
||||
{
|
||||
return ArrayHelper.Num() - 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool UDirectiveUtilArrayFunctionLibrary::Array_GetValidFirstItemCopy(const TArray<int32>& TargetArray, int32& OutItem)
|
||||
{
|
||||
checkNoEntry();
|
||||
return false;
|
||||
}
|
||||
|
||||
bool UDirectiveUtilArrayFunctionLibrary::Array_GetValidLastItemCopy(const TArray<int32>& TargetArray, int32& OutItem)
|
||||
{
|
||||
checkNoEntry();
|
||||
return false;
|
||||
}
|
||||
|
||||
bool UDirectiveUtilArrayFunctionLibrary::Array_GetValidItemFromIndexCopy(const TArray<int32>& TargetArray, const int32 Index, int32& OutItem)
|
||||
{
|
||||
checkNoEntry();
|
||||
return false;
|
||||
}
|
||||
|
||||
bool UDirectiveUtilArrayFunctionLibrary::Array_GetRandomItem(const TArray<int32>& TargetArray, int32& OutItem, int32& OutIndex)
|
||||
{
|
||||
checkNoEntry();
|
||||
return false;
|
||||
}
|
||||
|
||||
void UDirectiveUtilArrayFunctionLibrary::Array_LastValue(const TArray<int32>& TargetArray, int32& OutItem)
|
||||
{
|
||||
checkNoEntry();
|
||||
}
|
||||
|
||||
bool UDirectiveUtilArrayFunctionLibrary::Array_Pop(const TArray<int32>& TargetArray, int32& OutItem)
|
||||
{
|
||||
checkNoEntry();
|
||||
return false;
|
||||
}
|
||||
|
||||
bool UDirectiveUtilArrayFunctionLibrary::Array_PopFirst(const TArray<int32>& TargetArray, int32& OutItem)
|
||||
{
|
||||
checkNoEntry();
|
||||
return false;
|
||||
}
|
||||
|
||||
bool UDirectiveUtilArrayFunctionLibrary::Array_RemoveAtSwap(const TArray<int32>& TargetArray, const int32 Index)
|
||||
{
|
||||
checkNoEntry();
|
||||
return false;
|
||||
}
|
||||
|
||||
bool UDirectiveUtilArrayFunctionLibrary::GenericArray_GetItemAtIndex(
|
||||
const void* TargetArray,
|
||||
const FArrayProperty* ArrayProperty,
|
||||
const int32 Index,
|
||||
void* OutItemPtr)
|
||||
{
|
||||
if (!TargetArray || !ArrayProperty)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
FScriptArrayHelper ArrayHelper(ArrayProperty, TargetArray);
|
||||
const FProperty* InnerProp = ArrayProperty->Inner;
|
||||
|
||||
if (!ArrayHelper.IsValidIndex(Index))
|
||||
{
|
||||
if (OutItemPtr)
|
||||
{
|
||||
InnerProp->ClearValue(OutItemPtr);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if (OutItemPtr)
|
||||
{
|
||||
InnerProp->CopyCompleteValueFromScriptVM(OutItemPtr, ArrayHelper.GetRawPtr(Index));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool UDirectiveUtilArrayFunctionLibrary::GenericArray_GetFirstItem(
|
||||
const void* TargetArray,
|
||||
const FArrayProperty* ArrayProperty,
|
||||
void* OutItemPtr)
|
||||
{
|
||||
return GenericArray_GetItemAtIndex(TargetArray, ArrayProperty, 0, OutItemPtr);
|
||||
}
|
||||
|
||||
bool UDirectiveUtilArrayFunctionLibrary::GenericArray_GetLastItem(
|
||||
const void* TargetArray,
|
||||
const FArrayProperty* ArrayProperty,
|
||||
void* OutItemPtr)
|
||||
{
|
||||
if (!TargetArray || !ArrayProperty)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
const FScriptArrayHelper ArrayHelper(ArrayProperty, TargetArray);
|
||||
return GenericArray_GetItemAtIndex(TargetArray, ArrayProperty, ArrayHelper.Num() - 1, OutItemPtr);
|
||||
}
|
||||
|
||||
bool UDirectiveUtilArrayFunctionLibrary::GenericArray_GetRandomItem(
|
||||
const void* TargetArray,
|
||||
const FArrayProperty* ArrayProperty,
|
||||
void* OutItemPtr,
|
||||
int32* OutIndex)
|
||||
{
|
||||
if (OutIndex)
|
||||
{
|
||||
*OutIndex = INDEX_NONE;
|
||||
}
|
||||
|
||||
if (!TargetArray || !ArrayProperty)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
const FScriptArrayHelper ArrayHelper(ArrayProperty, TargetArray);
|
||||
const int32 Num = ArrayHelper.Num();
|
||||
if (Num <= 0)
|
||||
{
|
||||
if (OutItemPtr)
|
||||
{
|
||||
ArrayProperty->Inner->ClearValue(OutItemPtr);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
const int32 Index = FMath::RandRange(0, Num - 1);
|
||||
if (OutIndex)
|
||||
{
|
||||
*OutIndex = Index;
|
||||
}
|
||||
return GenericArray_GetItemAtIndex(TargetArray, ArrayProperty, Index, OutItemPtr);
|
||||
}
|
||||
|
||||
bool UDirectiveUtilArrayFunctionLibrary::GenericArray_Pop(
|
||||
void* TargetArray,
|
||||
const FArrayProperty* ArrayProperty,
|
||||
void* OutItemPtr)
|
||||
{
|
||||
if (!TargetArray || !ArrayProperty)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
FScriptArrayHelper ArrayHelper(ArrayProperty, TargetArray);
|
||||
const FProperty* InnerProp = ArrayProperty->Inner;
|
||||
const int32 LastIndex = ArrayHelper.Num() - 1;
|
||||
|
||||
if (LastIndex < 0)
|
||||
{
|
||||
if (OutItemPtr)
|
||||
{
|
||||
InnerProp->ClearValue(OutItemPtr);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if (OutItemPtr)
|
||||
{
|
||||
InnerProp->CopyCompleteValueFromScriptVM(OutItemPtr, ArrayHelper.GetRawPtr(LastIndex));
|
||||
}
|
||||
ArrayHelper.RemoveValues(LastIndex, 1);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool UDirectiveUtilArrayFunctionLibrary::GenericArray_PopFirst(
|
||||
void* TargetArray,
|
||||
const FArrayProperty* ArrayProperty,
|
||||
void* OutItemPtr)
|
||||
{
|
||||
if (!TargetArray || !ArrayProperty)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
FScriptArrayHelper ArrayHelper(ArrayProperty, TargetArray);
|
||||
const FProperty* InnerProp = ArrayProperty->Inner;
|
||||
|
||||
if (ArrayHelper.Num() <= 0)
|
||||
{
|
||||
if (OutItemPtr)
|
||||
{
|
||||
InnerProp->ClearValue(OutItemPtr);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if (OutItemPtr)
|
||||
{
|
||||
InnerProp->CopyCompleteValueFromScriptVM(OutItemPtr, ArrayHelper.GetRawPtr(0));
|
||||
}
|
||||
ArrayHelper.RemoveValues(0, 1);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool UDirectiveUtilArrayFunctionLibrary::GenericArray_RemoveAtSwap(
|
||||
void* TargetArray,
|
||||
const FArrayProperty* ArrayProperty,
|
||||
const int32 Index)
|
||||
{
|
||||
if (!TargetArray || !ArrayProperty)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
FScriptArrayHelper ArrayHelper(ArrayProperty, TargetArray);
|
||||
if (!ArrayHelper.IsValidIndex(Index))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
const int32 LastIndex = ArrayHelper.Num() - 1;
|
||||
if (Index != LastIndex)
|
||||
{
|
||||
ArrayHelper.SwapValues(Index, LastIndex);
|
||||
}
|
||||
ArrayHelper.RemoveValues(LastIndex, 1);
|
||||
return true;
|
||||
}
|
||||
|
||||
void UDirectiveUtilArrayFunctionLibrary::Array_Slice(const TArray<int32>& TargetArray, const int32 StartIndex, const int32 Count, TArray<int32>& OutArray)
|
||||
{
|
||||
checkNoEntry();
|
||||
}
|
||||
|
||||
void UDirectiveUtilArrayFunctionLibrary::Array_Rotate(const TArray<int32>& TargetArray, const int32 Shift)
|
||||
{
|
||||
checkNoEntry();
|
||||
}
|
||||
|
||||
void UDirectiveUtilArrayFunctionLibrary::Array_GetDistinct(const TArray<int32>& TargetArray, TArray<int32>& OutArray)
|
||||
{
|
||||
checkNoEntry();
|
||||
}
|
||||
|
||||
int32 UDirectiveUtilArrayFunctionLibrary::Array_CountOccurrences(const TArray<int32>& TargetArray, const int32& ItemToCount)
|
||||
{
|
||||
checkNoEntry();
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool UDirectiveUtilArrayFunctionLibrary::Array_GetMostCommon(const TArray<int32>& TargetArray, int32& OutItem, int32& OutCount)
|
||||
{
|
||||
checkNoEntry();
|
||||
return false;
|
||||
}
|
||||
|
||||
void UDirectiveUtilArrayFunctionLibrary::GenericArray_Slice(
|
||||
const void* TargetArray,
|
||||
const FArrayProperty* TargetArrayProperty,
|
||||
const int32 StartIndex,
|
||||
const int32 Count,
|
||||
void* OutArray,
|
||||
const FArrayProperty* OutArrayProperty)
|
||||
{
|
||||
if (!TargetArray || !OutArray || !TargetArrayProperty || !OutArrayProperty)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
FScriptArrayHelper SourceHelper(TargetArrayProperty, TargetArray);
|
||||
FScriptArrayHelper OutHelper(OutArrayProperty, OutArray);
|
||||
OutHelper.EmptyValues();
|
||||
|
||||
const int32 Num = SourceHelper.Num();
|
||||
if (Num == 0 || Count <= 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const FProperty* InnerProp = TargetArrayProperty->Inner;
|
||||
const int32 Start = FMath::Clamp(StartIndex, 0, Num);
|
||||
const int32 NumToCopy = FMath::Min(Count, Num - Start);
|
||||
for (int32 Offset = 0; Offset < NumToCopy; ++Offset)
|
||||
{
|
||||
const int32 OutIndex = OutHelper.AddValue();
|
||||
InnerProp->CopySingleValueToScriptVM(OutHelper.GetRawPtr(OutIndex), SourceHelper.GetRawPtr(Start + Offset));
|
||||
}
|
||||
}
|
||||
|
||||
void UDirectiveUtilArrayFunctionLibrary::GenericArray_Rotate(
|
||||
void* TargetArray,
|
||||
const FArrayProperty* ArrayProperty,
|
||||
const int32 Shift)
|
||||
{
|
||||
if (!TargetArray || !ArrayProperty)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
FScriptArrayHelper ArrayHelper(ArrayProperty, TargetArray);
|
||||
const int32 Num = ArrayHelper.Num();
|
||||
if (Num <= 1)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
int32 Normalized = Shift % Num;
|
||||
if (Normalized < 0)
|
||||
{
|
||||
Normalized += Num;
|
||||
}
|
||||
if (Normalized == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
auto ReverseRange = [&ArrayHelper](int32 Low, int32 High)
|
||||
{
|
||||
while (Low < High)
|
||||
{
|
||||
ArrayHelper.SwapValues(Low, High);
|
||||
++Low;
|
||||
--High;
|
||||
}
|
||||
};
|
||||
ReverseRange(0, Num - 1);
|
||||
ReverseRange(0, Normalized - 1);
|
||||
ReverseRange(Normalized, Num - 1);
|
||||
}
|
||||
|
||||
void UDirectiveUtilArrayFunctionLibrary::GenericArray_GetDistinct(
|
||||
const void* TargetArray,
|
||||
const FArrayProperty* TargetArrayProperty,
|
||||
void* OutArray,
|
||||
const FArrayProperty* OutArrayProperty)
|
||||
{
|
||||
if (!TargetArray || !OutArray || !TargetArrayProperty || !OutArrayProperty)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
FScriptArrayHelper SourceHelper(TargetArrayProperty, TargetArray);
|
||||
FScriptArrayHelper OutHelper(OutArrayProperty, OutArray);
|
||||
OutHelper.EmptyValues();
|
||||
|
||||
const FProperty* InnerProp = TargetArrayProperty->Inner;
|
||||
const int32 Num = SourceHelper.Num();
|
||||
for (int32 SourceIndex = 0; SourceIndex < Num; ++SourceIndex)
|
||||
{
|
||||
const uint8* SourceElement = SourceHelper.GetRawPtr(SourceIndex);
|
||||
|
||||
bool bIsDuplicate = false;
|
||||
for (int32 ExistingIndex = 0; ExistingIndex < OutHelper.Num(); ++ExistingIndex)
|
||||
{
|
||||
if (InnerProp->Identical(SourceElement, OutHelper.GetRawPtr(ExistingIndex)))
|
||||
{
|
||||
bIsDuplicate = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!bIsDuplicate)
|
||||
{
|
||||
const int32 OutIndex = OutHelper.AddValue();
|
||||
InnerProp->CopySingleValueToScriptVM(OutHelper.GetRawPtr(OutIndex), SourceElement);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int32 UDirectiveUtilArrayFunctionLibrary::GenericArray_CountOccurrences(
|
||||
const void* TargetArray,
|
||||
const FArrayProperty* ArrayProperty,
|
||||
const void* ItemToCount)
|
||||
{
|
||||
if (!TargetArray || !ArrayProperty || !ItemToCount)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
FScriptArrayHelper ArrayHelper(ArrayProperty, TargetArray);
|
||||
const FProperty* InnerProp = ArrayProperty->Inner;
|
||||
|
||||
int32 Count = 0;
|
||||
for (int32 Index = 0; Index < ArrayHelper.Num(); ++Index)
|
||||
{
|
||||
if (InnerProp->Identical(ArrayHelper.GetRawPtr(Index), ItemToCount))
|
||||
{
|
||||
++Count;
|
||||
}
|
||||
}
|
||||
return Count;
|
||||
}
|
||||
|
||||
bool UDirectiveUtilArrayFunctionLibrary::GenericArray_GetMostCommon(
|
||||
const void* TargetArray,
|
||||
const FArrayProperty* ArrayProperty,
|
||||
void* OutItemPtr,
|
||||
int32* OutCount)
|
||||
{
|
||||
if (OutCount)
|
||||
{
|
||||
*OutCount = 0;
|
||||
}
|
||||
|
||||
if (!TargetArray || !ArrayProperty)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
FScriptArrayHelper ArrayHelper(ArrayProperty, TargetArray);
|
||||
const FProperty* InnerProp = ArrayProperty->Inner;
|
||||
const int32 Num = ArrayHelper.Num();
|
||||
if (Num == 0)
|
||||
{
|
||||
if (OutItemPtr)
|
||||
{
|
||||
InnerProp->ClearValue(OutItemPtr);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
int32 BestIndex = 0;
|
||||
int32 BestCount = 0;
|
||||
for (int32 Index = 0; Index < Num; ++Index)
|
||||
{
|
||||
int32 CurrentCount = 0;
|
||||
for (int32 CompareIndex = 0; CompareIndex < Num; ++CompareIndex)
|
||||
{
|
||||
if (InnerProp->Identical(ArrayHelper.GetRawPtr(Index), ArrayHelper.GetRawPtr(CompareIndex)))
|
||||
{
|
||||
++CurrentCount;
|
||||
}
|
||||
}
|
||||
if (CurrentCount > BestCount)
|
||||
{
|
||||
BestCount = CurrentCount;
|
||||
BestIndex = Index;
|
||||
}
|
||||
}
|
||||
|
||||
if (OutItemPtr)
|
||||
{
|
||||
InnerProp->CopyCompleteValueFromScriptVM(OutItemPtr, ArrayHelper.GetRawPtr(BestIndex));
|
||||
}
|
||||
if (OutCount)
|
||||
{
|
||||
*OutCount = BestCount;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
// Copyright (c) 2026 Unreal Directive. Licensed under the MIT License.
|
||||
|
||||
|
||||
#include "Libraries/DirectiveUtilFunctionLibrary.h"
|
||||
#include "HAL/PlatformApplicationMisc.h"
|
||||
#include "Misc/CommandLine.h"
|
||||
#include "Misc/ConfigCacheIni.h"
|
||||
|
||||
void UDirectiveUtilFunctionLibrary::GetChildClasses(const UClass* BaseClass, const bool bRecursive, TArray<UClass*>& DerivedClasses)
|
||||
{
|
||||
GetDerivedClasses(BaseClass, DerivedClasses, bRecursive);
|
||||
}
|
||||
|
||||
void UDirectiveUtilFunctionLibrary::CopyTextToClipboard(const FText& Text)
|
||||
{
|
||||
const FString ClipboardText = Text.ToString();
|
||||
FPlatformApplicationMisc::ClipboardCopy(*ClipboardText);
|
||||
}
|
||||
|
||||
void UDirectiveUtilFunctionLibrary::CopyStringToClipboard(const FString& String)
|
||||
{
|
||||
FPlatformApplicationMisc::ClipboardCopy(*String);
|
||||
}
|
||||
|
||||
FText UDirectiveUtilFunctionLibrary::GetTextFromClipboard()
|
||||
{
|
||||
FString ClipboardText;
|
||||
FPlatformApplicationMisc::ClipboardPaste(ClipboardText);
|
||||
return FText::FromString(ClipboardText);
|
||||
}
|
||||
|
||||
FString UDirectiveUtilFunctionLibrary::GetStringFromClipboard()
|
||||
{
|
||||
FString ClipboardText;
|
||||
FPlatformApplicationMisc::ClipboardPaste(ClipboardText);
|
||||
return ClipboardText;
|
||||
}
|
||||
|
||||
void UDirectiveUtilFunctionLibrary::ClearClipboard()
|
||||
{
|
||||
FPlatformApplicationMisc::ClipboardCopy(TEXT(""));
|
||||
}
|
||||
|
||||
FString UDirectiveUtilFunctionLibrary::GetProjectVersion()
|
||||
{
|
||||
FString ProjectVersion;
|
||||
GConfig->GetString(
|
||||
TEXT("/Script/EngineSettings.GeneralProjectSettings"),
|
||||
TEXT("ProjectVersion"),
|
||||
ProjectVersion,
|
||||
GGameIni);
|
||||
return ProjectVersion;
|
||||
}
|
||||
|
||||
bool UDirectiveUtilFunctionLibrary::IsRunningInEditor()
|
||||
{
|
||||
return GIsEditor;
|
||||
}
|
||||
|
||||
bool UDirectiveUtilFunctionLibrary::HasCommandLineSwitch(const FString& Switch)
|
||||
{
|
||||
return HasCommandLineSwitch(FCommandLine::Get(), Switch);
|
||||
}
|
||||
|
||||
bool UDirectiveUtilFunctionLibrary::GetCommandLineOption(const FString& Key, FString& OutValue)
|
||||
{
|
||||
return GetCommandLineOption(FCommandLine::Get(), Key, OutValue);
|
||||
}
|
||||
|
||||
bool UDirectiveUtilFunctionLibrary::HasCommandLineSwitch(const TCHAR* CommandLine, const FString& Switch)
|
||||
{
|
||||
if (Switch.IsEmpty())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return FParse::Param(CommandLine, *Switch);
|
||||
}
|
||||
|
||||
bool UDirectiveUtilFunctionLibrary::GetCommandLineOption(const TCHAR* CommandLine, const FString& Key, FString& OutValue)
|
||||
{
|
||||
OutValue.Reset();
|
||||
if (Key.IsEmpty())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return FParse::Value(CommandLine, *(Key + TEXT("=")), OutValue);
|
||||
}
|
||||
@@ -0,0 +1,164 @@
|
||||
// Copyright (c) 2026 Unreal Directive. Licensed under the MIT License.
|
||||
|
||||
|
||||
#include "Libraries/DirectiveUtilGameplayTagFunctionLibrary.h"
|
||||
#include "GameplayTagsManager.h"
|
||||
|
||||
FGameplayTag UDirectiveUtilGameplayTagFunctionLibrary::GetTagDirectParent(const FGameplayTag& Tag)
|
||||
{
|
||||
if (!Tag.IsValid())
|
||||
{
|
||||
return FGameplayTag();
|
||||
}
|
||||
return Tag.RequestDirectParent();
|
||||
}
|
||||
|
||||
FGameplayTagContainer UDirectiveUtilGameplayTagFunctionLibrary::GetTagParents(const FGameplayTag& Tag)
|
||||
{
|
||||
if (!Tag.IsValid())
|
||||
{
|
||||
return FGameplayTagContainer();
|
||||
}
|
||||
|
||||
FGameplayTagContainer Parents = Tag.GetGameplayTagParents();
|
||||
Parents.RemoveTag(Tag);
|
||||
return Parents;
|
||||
}
|
||||
|
||||
int32 UDirectiveUtilGameplayTagFunctionLibrary::GetTagDepth(const FGameplayTag& Tag)
|
||||
{
|
||||
return GetTagSegments(Tag).Num();
|
||||
}
|
||||
|
||||
FString UDirectiveUtilGameplayTagFunctionLibrary::GetTagLeafName(const FGameplayTag& Tag)
|
||||
{
|
||||
const TArray<FString> Segments = GetTagSegments(Tag);
|
||||
return Segments.Num() > 0 ? Segments.Last() : FString();
|
||||
}
|
||||
|
||||
TArray<FString> UDirectiveUtilGameplayTagFunctionLibrary::GetTagSegments(const FGameplayTag& Tag)
|
||||
{
|
||||
TArray<FString> Segments;
|
||||
if (!Tag.IsValid())
|
||||
{
|
||||
return Segments;
|
||||
}
|
||||
Tag.GetTagName().ToString().ParseIntoArray(Segments, TEXT("."), true);
|
||||
return Segments;
|
||||
}
|
||||
|
||||
FGameplayTagContainer UDirectiveUtilGameplayTagFunctionLibrary::GetTagChildren(const FGameplayTag& Tag)
|
||||
{
|
||||
if (!Tag.IsValid())
|
||||
{
|
||||
return FGameplayTagContainer();
|
||||
}
|
||||
return UGameplayTagsManager::Get().RequestGameplayTagChildren(Tag);
|
||||
}
|
||||
|
||||
FGameplayTagContainer UDirectiveUtilGameplayTagFunctionLibrary::GetTagDirectChildren(const FGameplayTag& Tag)
|
||||
{
|
||||
FGameplayTagContainer DirectChildren;
|
||||
if (!Tag.IsValid())
|
||||
{
|
||||
return DirectChildren;
|
||||
}
|
||||
|
||||
const int32 DirectChildDepth = GetTagDepth(Tag) + 1;
|
||||
for (const FGameplayTag& Child : GetTagChildren(Tag))
|
||||
{
|
||||
if (GetTagDepth(Child) == DirectChildDepth)
|
||||
{
|
||||
DirectChildren.AddTag(Child);
|
||||
}
|
||||
}
|
||||
return DirectChildren;
|
||||
}
|
||||
|
||||
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())
|
||||
{
|
||||
return FGameplayTag();
|
||||
}
|
||||
// A common prefix of two registered tags is itself registered (parents auto-register).
|
||||
return FGameplayTag::RequestGameplayTag(FName(*Prefix), false);
|
||||
}
|
||||
|
||||
FGameplayTag UDirectiveUtilGameplayTagFunctionLibrary::GetTagAtDepth(const FGameplayTag& Tag, const int32 Depth)
|
||||
{
|
||||
if (!Tag.IsValid() || Depth < 1)
|
||||
{
|
||||
return FGameplayTag();
|
||||
}
|
||||
|
||||
const TArray<FString> Segments = GetTagSegments(Tag);
|
||||
if (Depth >= Segments.Num())
|
||||
{
|
||||
return Tag;
|
||||
}
|
||||
|
||||
FString Prefix = Segments[0];
|
||||
for (int32 Index = 1; Index < Depth; ++Index)
|
||||
{
|
||||
Prefix += TEXT(".");
|
||||
Prefix += Segments[Index];
|
||||
}
|
||||
// An ancestor of a registered tag is always registered itself (parents auto-register).
|
||||
return FGameplayTag::RequestGameplayTag(FName(*Prefix), false);
|
||||
}
|
||||
|
||||
FGameplayTagContainer UDirectiveUtilGameplayTagFunctionLibrary::GetTagSiblings(const FGameplayTag& Tag)
|
||||
{
|
||||
const FGameplayTag DirectParent = GetTagDirectParent(Tag);
|
||||
if (!DirectParent.IsValid())
|
||||
{
|
||||
return FGameplayTagContainer();
|
||||
}
|
||||
|
||||
FGameplayTagContainer Siblings = GetTagDirectChildren(DirectParent);
|
||||
Siblings.RemoveTag(Tag);
|
||||
return Siblings;
|
||||
}
|
||||
|
||||
bool UDirectiveUtilGameplayTagFunctionLibrary::IsLeafTag(const FGameplayTag& Tag)
|
||||
{
|
||||
return Tag.IsValid() && GetTagChildren(Tag).IsEmpty();
|
||||
}
|
||||
|
||||
TArray<FGameplayTag> UDirectiveUtilGameplayTagFunctionLibrary::FindRegisteredTags(const FString& Substring)
|
||||
{
|
||||
TArray<FGameplayTag> MatchingTags;
|
||||
if (Substring.IsEmpty())
|
||||
{
|
||||
return MatchingTags;
|
||||
}
|
||||
|
||||
FGameplayTagContainer AllTags;
|
||||
UGameplayTagsManager::Get().RequestAllGameplayTags(AllTags, /*OnlyIncludeDictionaryTags*/ false);
|
||||
for (const FGameplayTag& RegisteredTag : AllTags)
|
||||
{
|
||||
if (RegisteredTag.GetTagName().ToString().Contains(Substring))
|
||||
{
|
||||
MatchingTags.Add(RegisteredTag);
|
||||
}
|
||||
}
|
||||
return MatchingTags;
|
||||
}
|
||||
@@ -0,0 +1,218 @@
|
||||
// Copyright (c) 2026 Unreal Directive. Licensed under the MIT License.
|
||||
|
||||
|
||||
#include "Libraries/DirectiveUtilInputFunctionLibrary.h"
|
||||
#include "EnhancedInputSubsystems.h"
|
||||
#include "InputMappingContext.h"
|
||||
#include "DirectiveUtilLogChannels.h"
|
||||
#include "Logging/StructuredLog.h"
|
||||
#include "GameFramework/Controller.h"
|
||||
#include "GameFramework/PlayerController.h"
|
||||
#include "Engine/LocalPlayer.h"
|
||||
|
||||
bool UDirectiveUtilInputFunctionLibrary::TryGetEnhancedInputSubsystemFromController(
|
||||
AController* PlayerController,
|
||||
UEnhancedInputLocalPlayerSubsystem*& EnhancedInput)
|
||||
{
|
||||
if (!PlayerController)
|
||||
{
|
||||
UE_LOG(LogDirectiveUtil, Warning, TEXT("PlayerController is null. Cannot set input mapping contexts."));
|
||||
return false;
|
||||
}
|
||||
|
||||
const APlayerController* PC = Cast<APlayerController>(PlayerController);
|
||||
if (!PC)
|
||||
{
|
||||
UE_LOG(LogDirectiveUtil, Warning, TEXT("Controller is not a PlayerController. Cannot get enhanced input subsystem."));
|
||||
return false;
|
||||
}
|
||||
|
||||
const ULocalPlayer* LocalPlayer = PC->GetLocalPlayer();
|
||||
if (!LocalPlayer)
|
||||
{
|
||||
UE_LOG(LogDirectiveUtil, Warning, TEXT("LocalPlayer not found. Cannot set input mapping contexts."));
|
||||
return false;
|
||||
}
|
||||
|
||||
EnhancedInput = LocalPlayer->GetSubsystem<UEnhancedInputLocalPlayerSubsystem>();
|
||||
|
||||
if(!EnhancedInput)
|
||||
{
|
||||
UE_LOG(LogDirectiveUtil, Warning, TEXT("EnhancedInput subsystem not found. Cannot remove input mapping contexts."));
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
EDirectiveUtilSuccessStatus UDirectiveUtilInputFunctionLibrary::AddInputMappingContexts(
|
||||
AController* PlayerController,
|
||||
const TArray<FDirectiveUtilEnhancedInputContextData>& Contexts,
|
||||
const bool bClearPrevious)
|
||||
{
|
||||
if (Contexts.IsEmpty()) { return EDirectiveUtilSuccessStatus::Failure; }
|
||||
|
||||
UEnhancedInputLocalPlayerSubsystem* EnhancedInput;
|
||||
const bool bEnhancedInputRetrievedFromController = TryGetEnhancedInputSubsystemFromController(PlayerController, EnhancedInput);
|
||||
|
||||
if(!bEnhancedInputRetrievedFromController)
|
||||
{
|
||||
return EDirectiveUtilSuccessStatus::Failure;
|
||||
}
|
||||
|
||||
TArray<TPair<const UInputMappingContext*, int32>> LoadedContexts;
|
||||
TArray<int32> FailedIndices;
|
||||
for (int32 Index = 0; Index < Contexts.Num(); ++Index)
|
||||
{
|
||||
const auto& [InputContext, Priority] = Contexts[Index];
|
||||
if (const UInputMappingContext* MappingContext = InputContext.LoadSynchronous())
|
||||
{
|
||||
LoadedContexts.Emplace(MappingContext, Priority);
|
||||
}
|
||||
else
|
||||
{
|
||||
FailedIndices.Add(Index);
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
if (LoadedContexts.IsEmpty())
|
||||
{
|
||||
return EDirectiveUtilSuccessStatus::Failure;
|
||||
}
|
||||
|
||||
if (bClearPrevious)
|
||||
{
|
||||
EnhancedInput->ClearAllMappings();
|
||||
}
|
||||
|
||||
for (const auto& [MappingContext, Priority] : LoadedContexts)
|
||||
{
|
||||
EnhancedInput->AddMappingContext(MappingContext, Priority);
|
||||
}
|
||||
|
||||
UE_LOGFMT(LogDirectiveUtil, Verbose, "{MappingCount} Input mapping contexts set successfully.", Contexts.Num() - FailedIndices.Num());
|
||||
return EDirectiveUtilSuccessStatus::Success;
|
||||
}
|
||||
|
||||
EDirectiveUtilSuccessStatus UDirectiveUtilInputFunctionLibrary::RemoveInputMappingContexts(
|
||||
AController* PlayerController,
|
||||
const TArray<TSoftObjectPtr<UInputMappingContext>>& Contexts)
|
||||
{
|
||||
if (Contexts.IsEmpty()) { return EDirectiveUtilSuccessStatus::Failure;; }
|
||||
|
||||
UEnhancedInputLocalPlayerSubsystem* EnhancedInput;
|
||||
const bool bEnhancedInputRetrievedFromController = TryGetEnhancedInputSubsystemFromController(PlayerController, EnhancedInput);
|
||||
|
||||
if(!bEnhancedInputRetrievedFromController)
|
||||
{
|
||||
return EDirectiveUtilSuccessStatus::Failure;
|
||||
}
|
||||
|
||||
TArray<int32> FailedIndices;
|
||||
for (int32 Index = 0; Index < Contexts.Num(); ++Index)
|
||||
{
|
||||
const TSoftObjectPtr<UInputMappingContext>& Context = Contexts[Index];
|
||||
if (const UInputMappingContext* MappingContext = Context.LoadSynchronous())
|
||||
{
|
||||
EnhancedInput->RemoveMappingContext(MappingContext);
|
||||
} else
|
||||
{
|
||||
FailedIndices.Add(Index);
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
if (FailedIndices.Num() == Contexts.Num())
|
||||
{
|
||||
return EDirectiveUtilSuccessStatus::Failure;
|
||||
}
|
||||
|
||||
UE_LOGFMT(LogDirectiveUtil, Verbose, "{MappingCount} Input mapping contexts removed successfully.", Contexts.Num() - FailedIndices.Num());
|
||||
return EDirectiveUtilSuccessStatus::Success;
|
||||
}
|
||||
|
||||
EDirectiveUtilSuccessStatus UDirectiveUtilInputFunctionLibrary::SwapInputMappingContexts(
|
||||
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 int32 TargetPriority = bUsePreviousPriority ? PreviousPriority : Priority;
|
||||
EnhancedInput->RemoveMappingContext(LoadedPreviousMappingContext);
|
||||
EnhancedInput->AddMappingContext(LoadedNewMappingContext, TargetPriority);
|
||||
UE_LOGFMT(LogDirectiveUtil, Verbose, "Input mapping contexts {PreviousContext} and {NewContext} swapped successfully at priority {Priority}.", LoadedPreviousMappingContext->GetName(), LoadedNewMappingContext->GetName(), TargetPriority);
|
||||
}
|
||||
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);
|
||||
}
|
||||
|
||||
return EDirectiveUtilSuccessStatus::Success;
|
||||
}
|
||||
|
||||
UEnhancedInputLocalPlayerSubsystem* UDirectiveUtilInputFunctionLibrary::GetEnhancedInputSubsystem(AController* PlayerController)
|
||||
{
|
||||
UEnhancedInputLocalPlayerSubsystem* EnhancedInput = nullptr;
|
||||
TryGetEnhancedInputSubsystemFromController(PlayerController, EnhancedInput);
|
||||
return EnhancedInput;
|
||||
}
|
||||
|
||||
bool UDirectiveUtilInputFunctionLibrary::IsInputMappingContextActive(AController* PlayerController, TSoftObjectPtr<UInputMappingContext> Context)
|
||||
{
|
||||
const UInputMappingContext* MappingContext = Context.LoadSynchronous();
|
||||
if (!MappingContext)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
UEnhancedInputLocalPlayerSubsystem* EnhancedInput;
|
||||
if (!TryGetEnhancedInputSubsystemFromController(PlayerController, EnhancedInput))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
int32 OutPriority;
|
||||
return EnhancedInput->HasMappingContext(MappingContext, OutPriority);
|
||||
}
|
||||
|
||||
EDirectiveUtilSuccessStatus UDirectiveUtilInputFunctionLibrary::ClearAllInputMappingContexts(AController* PlayerController)
|
||||
{
|
||||
UEnhancedInputLocalPlayerSubsystem* EnhancedInput;
|
||||
if (!TryGetEnhancedInputSubsystemFromController(PlayerController, EnhancedInput))
|
||||
{
|
||||
return EDirectiveUtilSuccessStatus::Failure;
|
||||
}
|
||||
|
||||
EnhancedInput->ClearAllMappings();
|
||||
return EDirectiveUtilSuccessStatus::Success;
|
||||
}
|
||||
@@ -0,0 +1,205 @@
|
||||
// Copyright (c) 2026 Unreal Directive. Licensed under the MIT License.
|
||||
|
||||
|
||||
#include "Libraries/DirectiveUtilMapFunctionLibrary.h"
|
||||
|
||||
void UDirectiveUtilMapFunctionLibrary::Map_FindOrAdd(const TMap<int32, int32>& TargetMap, const int32& Key, int32& Value)
|
||||
{
|
||||
checkNoEntry();
|
||||
}
|
||||
|
||||
void UDirectiveUtilMapFunctionLibrary::Map_ClearValues(const TMap<int32, int32>& TargetMap)
|
||||
{
|
||||
checkNoEntry();
|
||||
}
|
||||
|
||||
void UDirectiveUtilMapFunctionLibrary::Map_GetKeysByValue(const TMap<int32, int32>& TargetMap, const int32& Value, TArray<int32>& Keys)
|
||||
{
|
||||
checkNoEntry();
|
||||
}
|
||||
|
||||
bool UDirectiveUtilMapFunctionLibrary::Map_HasValue(const TMap<int32, int32>& TargetMap, const int32& Value)
|
||||
{
|
||||
checkNoEntry();
|
||||
return false;
|
||||
}
|
||||
|
||||
int32 UDirectiveUtilMapFunctionLibrary::Map_RemoveKeys(const TMap<int32, int32>& TargetMap, const TArray<int32>& Keys)
|
||||
{
|
||||
checkNoEntry();
|
||||
return 0;
|
||||
}
|
||||
|
||||
void UDirectiveUtilMapFunctionLibrary::Map_Append(const TMap<int32, int32>& TargetMap, const TMap<int32, int32>& SourceMap, bool bOverwriteExisting)
|
||||
{
|
||||
checkNoEntry();
|
||||
}
|
||||
|
||||
void UDirectiveUtilMapFunctionLibrary::GenericMap_FindOrAdd(
|
||||
const void* TargetMap,
|
||||
const FMapProperty* MapProperty,
|
||||
const void* KeyPtr,
|
||||
void* ValuePtr)
|
||||
{
|
||||
if (!TargetMap || !MapProperty || !KeyPtr)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
FScriptMapHelper MapHelper(MapProperty, TargetMap);
|
||||
|
||||
void* ValueInMap = MapHelper.FindOrAdd(KeyPtr);
|
||||
if (ValuePtr && ValueInMap)
|
||||
{
|
||||
MapProperty->ValueProp->CopyCompleteValueFromScriptVM(ValuePtr, ValueInMap);
|
||||
}
|
||||
}
|
||||
|
||||
void UDirectiveUtilMapFunctionLibrary::GenericMap_ClearValues(
|
||||
const void* TargetMap,
|
||||
const FMapProperty* MapProperty)
|
||||
{
|
||||
if (!TargetMap || !MapProperty)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
FScriptMapHelper MapHelper(MapProperty, TargetMap);
|
||||
const FProperty* ValueProp = MapProperty->ValueProp;
|
||||
|
||||
const int32 MaxIndex = MapHelper.GetMaxIndex();
|
||||
for (int32 InternalIndex = 0; InternalIndex < MaxIndex; ++InternalIndex)
|
||||
{
|
||||
if (MapHelper.IsValidIndex(InternalIndex))
|
||||
{
|
||||
ValueProp->ClearValue(MapHelper.GetValuePtr(InternalIndex));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void UDirectiveUtilMapFunctionLibrary::GenericMap_GetKeysByValue(
|
||||
const void* TargetMap,
|
||||
const FMapProperty* MapProperty,
|
||||
const void* ValuePtr,
|
||||
const void* TargetArray,
|
||||
const FArrayProperty* ArrayProperty)
|
||||
{
|
||||
if (!TargetArray || !ArrayProperty)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
FScriptArrayHelper ArrayHelper(ArrayProperty, TargetArray);
|
||||
ArrayHelper.EmptyValues();
|
||||
|
||||
if (!TargetMap || !MapProperty || !ValuePtr || !ArrayProperty->Inner->SameType(MapProperty->KeyProp))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
FScriptMapHelper MapHelper(MapProperty, TargetMap);
|
||||
const FProperty* ValueProp = MapProperty->ValueProp;
|
||||
const FProperty* InnerProp = ArrayProperty->Inner;
|
||||
|
||||
const int32 MaxIndex = MapHelper.GetMaxIndex();
|
||||
for (int32 InternalIndex = 0; InternalIndex < MaxIndex; ++InternalIndex)
|
||||
{
|
||||
if (MapHelper.IsValidIndex(InternalIndex) && ValueProp->Identical(MapHelper.GetValuePtr(InternalIndex), ValuePtr))
|
||||
{
|
||||
const int32 LastIndex = ArrayHelper.AddValue();
|
||||
InnerProp->CopySingleValueToScriptVM(ArrayHelper.GetRawPtr(LastIndex), MapHelper.GetKeyPtr(InternalIndex));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool UDirectiveUtilMapFunctionLibrary::GenericMap_HasValue(
|
||||
const void* TargetMap,
|
||||
const FMapProperty* MapProperty,
|
||||
const void* ValuePtr)
|
||||
{
|
||||
if (!TargetMap || !MapProperty || !ValuePtr)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
FScriptMapHelper MapHelper(MapProperty, TargetMap);
|
||||
const FProperty* ValueProp = MapProperty->ValueProp;
|
||||
|
||||
const int32 MaxIndex = MapHelper.GetMaxIndex();
|
||||
for (int32 InternalIndex = 0; InternalIndex < MaxIndex; ++InternalIndex)
|
||||
{
|
||||
if (MapHelper.IsValidIndex(InternalIndex) && ValueProp->Identical(MapHelper.GetValuePtr(InternalIndex), ValuePtr))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
int32 UDirectiveUtilMapFunctionLibrary::GenericMap_RemoveKeys(
|
||||
const void* TargetMap,
|
||||
const FMapProperty* MapProperty,
|
||||
const void* TargetArray,
|
||||
const FArrayProperty* ArrayProperty)
|
||||
{
|
||||
if (!TargetMap || !MapProperty || !TargetArray || !ArrayProperty
|
||||
|| !ArrayProperty->Inner->SameType(MapProperty->KeyProp))
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
FScriptMapHelper MapHelper(MapProperty, TargetMap);
|
||||
FScriptArrayHelper ArrayHelper(ArrayProperty, TargetArray);
|
||||
|
||||
int32 NumRemoved = 0;
|
||||
const int32 Num = ArrayHelper.Num();
|
||||
for (int32 Index = 0; Index < Num; ++Index)
|
||||
{
|
||||
if (MapHelper.RemovePair(ArrayHelper.GetRawPtr(Index)))
|
||||
{
|
||||
++NumRemoved;
|
||||
}
|
||||
}
|
||||
|
||||
return NumRemoved;
|
||||
}
|
||||
|
||||
void UDirectiveUtilMapFunctionLibrary::GenericMap_Append(
|
||||
const void* TargetMap,
|
||||
const FMapProperty* TargetMapProperty,
|
||||
const void* SourceMap,
|
||||
const FMapProperty* SourceMapProperty,
|
||||
const bool bOverwriteExisting)
|
||||
{
|
||||
if (!TargetMap || !TargetMapProperty || !SourceMap || !SourceMapProperty
|
||||
|| !TargetMapProperty->KeyProp->SameType(SourceMapProperty->KeyProp)
|
||||
|| !TargetMapProperty->ValueProp->SameType(SourceMapProperty->ValueProp))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Appending a map onto itself is a no-op; bail before AddPair can reallocate under the source pointers.
|
||||
if (TargetMap == SourceMap)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
FScriptMapHelper TargetHelper(TargetMapProperty, TargetMap);
|
||||
FScriptMapHelper SourceHelper(SourceMapProperty, SourceMap);
|
||||
|
||||
const int32 MaxIndex = SourceHelper.GetMaxIndex();
|
||||
for (int32 InternalIndex = 0; InternalIndex < MaxIndex; ++InternalIndex)
|
||||
{
|
||||
if (!SourceHelper.IsValidIndex(InternalIndex))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
const uint8* KeyPtr = SourceHelper.GetKeyPtr(InternalIndex);
|
||||
if (bOverwriteExisting || !TargetHelper.FindValueFromHash(KeyPtr))
|
||||
{
|
||||
TargetHelper.AddPair(KeyPtr, SourceHelper.GetValuePtr(InternalIndex));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,466 @@
|
||||
// Copyright (c) 2026 Unreal Directive. Licensed under the MIT License.
|
||||
|
||||
|
||||
#include "Libraries/DirectiveUtilMathFunctionLibrary.h"
|
||||
|
||||
namespace
|
||||
{
|
||||
double EaseBackIn(double t)
|
||||
{
|
||||
const double s = 1.70158;
|
||||
return t * t * ((s + 1.0) * t - s);
|
||||
}
|
||||
|
||||
double EaseBackOut(double t)
|
||||
{
|
||||
const double s = 1.70158;
|
||||
t -= 1.0;
|
||||
return t * t * ((s + 1.0) * t + s) + 1.0;
|
||||
}
|
||||
|
||||
double EaseBackInOut(double t)
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
float UDirectiveUtilMathFunctionLibrary::PerlinNoise2D(const FVector2D Position)
|
||||
{
|
||||
return FMath::PerlinNoise2D(Position);
|
||||
}
|
||||
|
||||
float UDirectiveUtilMathFunctionLibrary::PerlinNoise3D(const FVector& Position)
|
||||
{
|
||||
return FMath::PerlinNoise3D(Position);
|
||||
}
|
||||
|
||||
float UDirectiveUtilMathFunctionLibrary::AngleBetweenVectors(const FVector& A, const FVector& B)
|
||||
{
|
||||
return FMath::RadiansToDegrees(FMath::Acos(FMath::Clamp(FVector::DotProduct(A.GetSafeNormal(), B.GetSafeNormal()), -1.0, 1.0)));
|
||||
}
|
||||
|
||||
float UDirectiveUtilMathFunctionLibrary::EaseAlpha(const float Alpha, const EDirectiveUtilEaseType EaseType)
|
||||
{
|
||||
const double t = static_cast<double>(FMath::Clamp(Alpha, 0.0f, 1.0f));
|
||||
double Result = t;
|
||||
switch (EaseType)
|
||||
{
|
||||
case EDirectiveUtilEaseType::BackIn: Result = EaseBackIn(t); break;
|
||||
case EDirectiveUtilEaseType::BackOut: Result = EaseBackOut(t); break;
|
||||
case EDirectiveUtilEaseType::BackInOut: Result = EaseBackInOut(t); break;
|
||||
case EDirectiveUtilEaseType::ElasticIn: Result = EaseElasticIn(t); break;
|
||||
case EDirectiveUtilEaseType::ElasticOut: Result = EaseElasticOut(t); break;
|
||||
case EDirectiveUtilEaseType::ElasticInOut: Result = EaseElasticInOut(t); break;
|
||||
case EDirectiveUtilEaseType::BounceIn: Result = EaseBounceIn(t); break;
|
||||
case EDirectiveUtilEaseType::BounceOut: Result = EaseBounceOut(t); break;
|
||||
case EDirectiveUtilEaseType::BounceInOut: Result = EaseBounceInOut(t); break;
|
||||
}
|
||||
return static_cast<float>(Result);
|
||||
}
|
||||
|
||||
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));
|
||||
}
|
||||
|
||||
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 int64 TotalSeconds = static_cast<int64>(FMath::Abs(static_cast<double>(Seconds)));
|
||||
const bool bNegative = Seconds < 0.0f && TotalSeconds > 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 to whole seconds so clock-adjacent inputs (e.g. Now() + 2 hours) land in the intended 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> Sorted = Values;
|
||||
Sorted.Sort();
|
||||
|
||||
const int32 Middle = Sorted.Num() / 2;
|
||||
if (Sorted.Num() % 2 == 0)
|
||||
{
|
||||
return static_cast<float>((static_cast<double>(Sorted[Middle - 1]) + static_cast<double>(Sorted[Middle])) * 0.5);
|
||||
}
|
||||
return static_cast<float>(Sorted[Middle]);
|
||||
}
|
||||
|
||||
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> Sorted = Values;
|
||||
Sorted.Sort();
|
||||
|
||||
const int32 Middle = Sorted.Num() / 2;
|
||||
if (Sorted.Num() % 2 == 0)
|
||||
{
|
||||
return static_cast<float>((static_cast<double>(Sorted[Middle - 1]) + static_cast<double>(Sorted[Middle])) * 0.5);
|
||||
}
|
||||
return Sorted[Middle];
|
||||
}
|
||||
|
||||
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()));
|
||||
}
|
||||
|
||||
int32 UDirectiveUtilMathFunctionLibrary::GetRandomIndexFromWeights(const TArray<float>& Weights)
|
||||
{
|
||||
float Total = 0.0f;
|
||||
for (const float Weight : Weights)
|
||||
{
|
||||
Total += FMath::Max(0.0f, Weight);
|
||||
}
|
||||
|
||||
if (Total <= 0.0f)
|
||||
{
|
||||
return INDEX_NONE;
|
||||
}
|
||||
|
||||
const float Roll = FMath::FRand() * Total;
|
||||
float Accumulated = 0.0f;
|
||||
int32 LastPositiveIndex = INDEX_NONE;
|
||||
for (int32 Index = 0; Index < Weights.Num(); ++Index)
|
||||
{
|
||||
const float Weight = FMath::Max(0.0f, 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)
|
||||
{
|
||||
float Total = 0.0f;
|
||||
for (const float Weight : Weights)
|
||||
{
|
||||
Total += FMath::Max(0.0f, Weight);
|
||||
}
|
||||
|
||||
if (Total <= 0.0f)
|
||||
{
|
||||
return INDEX_NONE;
|
||||
}
|
||||
|
||||
const float Roll = Stream.FRand() * Total;
|
||||
float Accumulated = 0.0f;
|
||||
int32 LastPositiveIndex = INDEX_NONE;
|
||||
for (int32 Index = 0; Index < Weights.Num(); ++Index)
|
||||
{
|
||||
const float Weight = FMath::Max(0.0f, Weights[Index]);
|
||||
if (Weight <= 0.0f)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
LastPositiveIndex = Index;
|
||||
Accumulated += Weight;
|
||||
if (Roll < Accumulated)
|
||||
{
|
||||
return Index;
|
||||
}
|
||||
}
|
||||
|
||||
return LastPositiveIndex;
|
||||
}
|
||||
@@ -0,0 +1,131 @@
|
||||
// Copyright (c) 2026 Unreal Directive. Licensed under the MIT License.
|
||||
|
||||
|
||||
#include "Libraries/DirectiveUtilRegexFunctionLibrary.h"
|
||||
#include "Internationalization/Regex.h"
|
||||
|
||||
namespace
|
||||
{
|
||||
FRegexPattern MakePattern(const FString& Pattern, const bool bCaseSensitive)
|
||||
{
|
||||
return FRegexPattern(Pattern, bCaseSensitive ? ERegexPatternFlags::None : ERegexPatternFlags::CaseInsensitive);
|
||||
}
|
||||
}
|
||||
|
||||
bool UDirectiveUtilRegexFunctionLibrary::RegexMatches(const FString& Input, const FString& Pattern, const bool bCaseSensitive)
|
||||
{
|
||||
if (Pattern.IsEmpty())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
const FRegexPattern RegexPattern = MakePattern(Pattern, bCaseSensitive);
|
||||
FRegexMatcher Matcher(RegexPattern, Input);
|
||||
return Matcher.FindNext();
|
||||
}
|
||||
|
||||
bool UDirectiveUtilRegexFunctionLibrary::RegexFindFirst(const FString& Input, const FString& Pattern, FString& OutMatch, int32& OutMatchStart, int32& OutMatchEnd, const bool bCaseSensitive)
|
||||
{
|
||||
OutMatch = FString();
|
||||
OutMatchStart = INDEX_NONE;
|
||||
OutMatchEnd = INDEX_NONE;
|
||||
|
||||
if (Pattern.IsEmpty())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
const FRegexPattern RegexPattern = MakePattern(Pattern, bCaseSensitive);
|
||||
FRegexMatcher Matcher(RegexPattern, Input);
|
||||
if (Matcher.FindNext())
|
||||
{
|
||||
OutMatchStart = Matcher.GetMatchBeginning();
|
||||
OutMatchEnd = Matcher.GetMatchEnding();
|
||||
OutMatch = Matcher.GetCaptureGroup(0);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
TArray<FString> UDirectiveUtilRegexFunctionLibrary::RegexFindAll(const FString& Input, const FString& Pattern, const bool bCaseSensitive)
|
||||
{
|
||||
TArray<FString> Matches;
|
||||
if (Pattern.IsEmpty())
|
||||
{
|
||||
return Matches;
|
||||
}
|
||||
|
||||
const FRegexPattern RegexPattern = MakePattern(Pattern, bCaseSensitive);
|
||||
FRegexMatcher Matcher(RegexPattern, Input);
|
||||
while (Matcher.FindNext())
|
||||
{
|
||||
const int32 Begin = Matcher.GetMatchBeginning();
|
||||
const int32 End = Matcher.GetMatchEnding();
|
||||
if (Begin == End)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
Matches.Add(Matcher.GetCaptureGroup(0));
|
||||
}
|
||||
return Matches;
|
||||
}
|
||||
|
||||
FString UDirectiveUtilRegexFunctionLibrary::RegexReplaceAll(const FString& Input, const FString& Pattern, const FString& Replacement, const bool bCaseSensitive)
|
||||
{
|
||||
if (Pattern.IsEmpty())
|
||||
{
|
||||
return Input;
|
||||
}
|
||||
|
||||
const FRegexPattern RegexPattern = MakePattern(Pattern, bCaseSensitive);
|
||||
FRegexMatcher Matcher(RegexPattern, Input);
|
||||
|
||||
FString Result;
|
||||
int32 LastEnd = 0;
|
||||
while (Matcher.FindNext())
|
||||
{
|
||||
const int32 Begin = Matcher.GetMatchBeginning();
|
||||
const int32 End = Matcher.GetMatchEnding();
|
||||
|
||||
if (Begin == End)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (Begin < LastEnd)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
Result.Append(Input.Mid(LastEnd, Begin - LastEnd));
|
||||
Result.Append(Replacement);
|
||||
LastEnd = End;
|
||||
}
|
||||
Result.Append(Input.Mid(LastEnd));
|
||||
return Result;
|
||||
}
|
||||
|
||||
bool UDirectiveUtilRegexFunctionLibrary::RegexGetCaptureGroup(const FString& Input, const FString& Pattern, const int32 GroupIndex, FString& OutGroup, const bool bCaseSensitive)
|
||||
{
|
||||
OutGroup = FString();
|
||||
|
||||
if (Pattern.IsEmpty() || GroupIndex < 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
const FRegexPattern RegexPattern = MakePattern(Pattern, bCaseSensitive);
|
||||
FRegexMatcher Matcher(RegexPattern, Input);
|
||||
if (!Matcher.FindNext())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (Matcher.GetCaptureGroupBeginning(GroupIndex) == INDEX_NONE)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
OutGroup = Matcher.GetCaptureGroup(GroupIndex);
|
||||
return true;
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
// Copyright (c) 2026 Unreal Directive. Licensed under the MIT License.
|
||||
|
||||
|
||||
#include "Libraries/DirectiveUtilSaveGameFunctionLibrary.h"
|
||||
#include "Libraries/DirectiveUtilStringFunctionLibrary.h"
|
||||
#include "Kismet/GameplayStatics.h"
|
||||
#include "GameFramework/SaveGame.h"
|
||||
#include "HAL/FileManager.h"
|
||||
#include "Misc/Paths.h"
|
||||
|
||||
namespace
|
||||
{
|
||||
FString GetSaveGamesDirectory()
|
||||
{
|
||||
return FPaths::ProjectSavedDir() / TEXT("SaveGames");
|
||||
}
|
||||
|
||||
FString GetSaveSlotFilePath(const FString& SlotName)
|
||||
{
|
||||
return GetSaveGamesDirectory() / (SlotName + TEXT(".sav"));
|
||||
}
|
||||
|
||||
bool IsValidSaveSlotName(const FString& SlotName)
|
||||
{
|
||||
return UDirectiveUtilStringFunctionLibrary::IsValidFileName(SlotName);
|
||||
}
|
||||
}
|
||||
|
||||
TArray<FString> UDirectiveUtilSaveGameFunctionLibrary::GetAllSaveSlotNames()
|
||||
{
|
||||
TArray<FString> SlotNames;
|
||||
|
||||
TArray<FString> Files;
|
||||
IFileManager::Get().FindFiles(Files, *(GetSaveGamesDirectory() / TEXT("*.sav")), true, false);
|
||||
|
||||
SlotNames.Reserve(Files.Num());
|
||||
for (const FString& File : Files)
|
||||
{
|
||||
SlotNames.Add(FPaths::GetBaseFilename(File));
|
||||
}
|
||||
return SlotNames;
|
||||
}
|
||||
|
||||
bool UDirectiveUtilSaveGameFunctionLibrary::GetSaveSlotTimestamp(const FString& SlotName, FDateTime& OutTimestamp)
|
||||
{
|
||||
OutTimestamp = FDateTime();
|
||||
if (!IsValidSaveSlotName(SlotName))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
const FDateTime Timestamp = IFileManager::Get().GetTimeStamp(*GetSaveSlotFilePath(SlotName));
|
||||
if (Timestamp == FDateTime::MinValue())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
OutTimestamp = Timestamp + (FDateTime::Now() - FDateTime::UtcNow());
|
||||
return true;
|
||||
}
|
||||
|
||||
bool UDirectiveUtilSaveGameFunctionLibrary::SaveGameToBytes(USaveGame* SaveGameObject, TArray<uint8>& OutBytes)
|
||||
{
|
||||
OutBytes.Reset();
|
||||
if (!SaveGameObject)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return UGameplayStatics::SaveGameToMemory(SaveGameObject, OutBytes);
|
||||
}
|
||||
|
||||
USaveGame* UDirectiveUtilSaveGameFunctionLibrary::LoadGameFromBytes(const TArray<uint8>& SaveData)
|
||||
{
|
||||
if (SaveData.Num() == 0)
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
return UGameplayStatics::LoadGameFromMemory(SaveData);
|
||||
}
|
||||
|
||||
bool UDirectiveUtilSaveGameFunctionLibrary::DoesSaveSlotExist(const FString& SlotName, const int32 UserIndex)
|
||||
{
|
||||
if (!IsValidSaveSlotName(SlotName))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return UGameplayStatics::DoesSaveGameExist(SlotName, UserIndex);
|
||||
}
|
||||
|
||||
bool UDirectiveUtilSaveGameFunctionLibrary::DeleteSaveSlot(const FString& SlotName, const int32 UserIndex)
|
||||
{
|
||||
if (!IsValidSaveSlotName(SlotName))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return UGameplayStatics::DeleteGameInSlot(SlotName, UserIndex);
|
||||
}
|
||||
|
||||
bool UDirectiveUtilSaveGameFunctionLibrary::RenameSaveSlot(const FString& OldSlotName, const FString& NewSlotName, const int32 UserIndex)
|
||||
{
|
||||
if (!IsValidSaveSlotName(OldSlotName) || !IsValidSaveSlotName(NewSlotName) || OldSlotName == NewSlotName)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (!UGameplayStatics::DoesSaveGameExist(OldSlotName, UserIndex) || UGameplayStatics::DoesSaveGameExist(NewSlotName, UserIndex))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
TArray<uint8> SaveData;
|
||||
if (!UGameplayStatics::LoadDataFromSlot(SaveData, OldSlotName, UserIndex))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (!UGameplayStatics::SaveDataToSlot(SaveData, NewSlotName, UserIndex))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
// If this delete fails the new copy is kept alongside the original, so the save is never lost.
|
||||
return UGameplayStatics::DeleteGameInSlot(OldSlotName, UserIndex);
|
||||
}
|
||||
@@ -0,0 +1,422 @@
|
||||
// Copyright (c) 2026 Unreal Directive. Licensed under the MIT License.
|
||||
|
||||
|
||||
#include "Libraries/DirectiveUtilStringFunctionLibrary.h"
|
||||
#include "Misc/Base64.h"
|
||||
#include "Misc/Crc.h"
|
||||
#include "Misc/Paths.h"
|
||||
#include "Misc/SecureHash.h"
|
||||
|
||||
namespace
|
||||
{
|
||||
TArray<uint8> StringToUtf8Bytes(const FString& String)
|
||||
{
|
||||
const FTCHARToUTF8 Converter(*String, String.Len());
|
||||
return TArray<uint8>(reinterpret_cast<const uint8*>(Converter.Get()), Converter.Length());
|
||||
}
|
||||
}
|
||||
|
||||
bool UDirectiveUtilStringFunctionLibrary::ContainsLetters(const FString& String)
|
||||
{
|
||||
for (const TCHAR& Char : String)
|
||||
{
|
||||
if (FChar::IsAlpha(Char))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool UDirectiveUtilStringFunctionLibrary::ContainsNumbers(const FString& String)
|
||||
{
|
||||
for (const TCHAR& Char : String)
|
||||
{
|
||||
if (FChar::IsDigit(Char))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool UDirectiveUtilStringFunctionLibrary::ContainsSpaces(const FString& String)
|
||||
{
|
||||
for (const TCHAR& Char : String)
|
||||
{
|
||||
if (FChar::IsWhitespace(Char))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool UDirectiveUtilStringFunctionLibrary::ContainsSpecialCharacters(const FString& String)
|
||||
{
|
||||
for (const TCHAR& Char : String)
|
||||
{
|
||||
if (FChar::IsPunct(Char))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
FString UDirectiveUtilStringFunctionLibrary::FilterCharacters(
|
||||
const FString& String,
|
||||
const bool bLetters,
|
||||
const bool bNumbers,
|
||||
const bool bSpecialCharacters,
|
||||
const bool bSpaces)
|
||||
{
|
||||
FString NewString;
|
||||
NewString.Reserve(String.Len());
|
||||
|
||||
for (const TCHAR& Char : String)
|
||||
{
|
||||
if (bLetters && FChar::IsAlpha(Char)) continue;
|
||||
if (bNumbers && FChar::IsDigit(Char)) continue;
|
||||
if (bSpecialCharacters && FChar::IsPunct(Char)) continue;
|
||||
if (bSpaces && FChar::IsWhitespace(Char)) continue;
|
||||
NewString.AppendChar(Char);
|
||||
}
|
||||
return NewString;
|
||||
}
|
||||
|
||||
FString UDirectiveUtilStringFunctionLibrary::TruncateString(const FString& String, const int32 MaxLength, const FString& Suffix)
|
||||
{
|
||||
if (String.Len() <= MaxLength)
|
||||
{
|
||||
return String;
|
||||
}
|
||||
return String.Left(FMath::Max(0, MaxLength - Suffix.Len())) + Suffix;
|
||||
}
|
||||
|
||||
FString UDirectiveUtilStringFunctionLibrary::ToTitleCase(const FString& String)
|
||||
{
|
||||
FString Result;
|
||||
Result.Reserve(String.Len());
|
||||
bool bCapitalizeNext = true;
|
||||
|
||||
for (const TCHAR& Char : String)
|
||||
{
|
||||
if (FChar::IsWhitespace(Char))
|
||||
{
|
||||
bCapitalizeNext = true;
|
||||
Result.AppendChar(Char);
|
||||
}
|
||||
else if (bCapitalizeNext && FChar::IsAlpha(Char))
|
||||
{
|
||||
Result.AppendChar(FChar::ToUpper(Char));
|
||||
bCapitalizeNext = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
Result.AppendChar(FChar::ToLower(Char));
|
||||
bCapitalizeNext = false;
|
||||
}
|
||||
}
|
||||
|
||||
return Result;
|
||||
}
|
||||
|
||||
TArray<FString> UDirectiveUtilStringFunctionLibrary::SplitIntoWords(const FString& String)
|
||||
{
|
||||
TArray<FString> Words;
|
||||
FString CurrentWord;
|
||||
|
||||
for (int32 Index = 0; Index < String.Len(); ++Index)
|
||||
{
|
||||
const TCHAR Char = String[Index];
|
||||
if (!FChar::IsAlnum(Char))
|
||||
{
|
||||
if (!CurrentWord.IsEmpty())
|
||||
{
|
||||
Words.Add(MoveTemp(CurrentWord));
|
||||
CurrentWord.Reset();
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!CurrentWord.IsEmpty())
|
||||
{
|
||||
const TCHAR Previous = CurrentWord[CurrentWord.Len() - 1];
|
||||
const bool bNextIsLower = Index + 1 < String.Len() && FChar::IsLower(String[Index + 1]);
|
||||
const bool bBoundary =
|
||||
(FChar::IsLower(Previous) && FChar::IsUpper(Char)) ||
|
||||
(FChar::IsUpper(Previous) && FChar::IsUpper(Char) && bNextIsLower) ||
|
||||
(FChar::IsAlpha(Previous) && FChar::IsDigit(Char)) ||
|
||||
(FChar::IsDigit(Previous) && FChar::IsAlpha(Char));
|
||||
if (bBoundary)
|
||||
{
|
||||
Words.Add(MoveTemp(CurrentWord));
|
||||
CurrentWord.Reset();
|
||||
}
|
||||
}
|
||||
|
||||
CurrentWord.AppendChar(Char);
|
||||
}
|
||||
|
||||
if (!CurrentWord.IsEmpty())
|
||||
{
|
||||
Words.Add(MoveTemp(CurrentWord));
|
||||
}
|
||||
return Words;
|
||||
}
|
||||
|
||||
FString UDirectiveUtilStringFunctionLibrary::ToCamelCase(const FString& String)
|
||||
{
|
||||
const TArray<FString> Words = SplitIntoWords(String);
|
||||
FString Result;
|
||||
for (int32 Index = 0; Index < Words.Num(); ++Index)
|
||||
{
|
||||
FString Word = Words[Index].ToLower();
|
||||
if (Index > 0)
|
||||
{
|
||||
Word[0] = FChar::ToUpper(Word[0]);
|
||||
}
|
||||
Result += Word;
|
||||
}
|
||||
return Result;
|
||||
}
|
||||
|
||||
FString UDirectiveUtilStringFunctionLibrary::ToPascalCase(const FString& String)
|
||||
{
|
||||
FString Result;
|
||||
for (const FString& Word : SplitIntoWords(String))
|
||||
{
|
||||
FString Cased = Word.ToLower();
|
||||
Cased[0] = FChar::ToUpper(Cased[0]);
|
||||
Result += Cased;
|
||||
}
|
||||
return Result;
|
||||
}
|
||||
|
||||
FString UDirectiveUtilStringFunctionLibrary::ToSnakeCase(const FString& String)
|
||||
{
|
||||
return FString::Join(SplitIntoWords(String), TEXT("_")).ToLower();
|
||||
}
|
||||
|
||||
FString UDirectiveUtilStringFunctionLibrary::ToKebabCase(const FString& String)
|
||||
{
|
||||
return FString::Join(SplitIntoWords(String), TEXT("-")).ToLower();
|
||||
}
|
||||
|
||||
TArray<FString> UDirectiveUtilStringFunctionLibrary::SortStringArray(TArray<FString> StringArray)
|
||||
{
|
||||
Algo::Sort(StringArray);
|
||||
return StringArray;
|
||||
}
|
||||
|
||||
TArray<FString> UDirectiveUtilStringFunctionLibrary::GetSortedStringArray(const TArray<FString> StringArray)
|
||||
{
|
||||
TArray<FString> SortedArray = StringArray;
|
||||
Algo::Sort(SortedArray);
|
||||
return SortedArray;
|
||||
}
|
||||
|
||||
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; }
|
||||
|
||||
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];
|
||||
}
|
||||
|
||||
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));
|
||||
}
|
||||
|
||||
bool UDirectiveUtilStringFunctionLibrary::ContainsAny(const FString& Source, const TArray<FString>& SearchTerms, const bool bCaseSensitive)
|
||||
{
|
||||
const ESearchCase::Type SearchCase = bCaseSensitive ? ESearchCase::CaseSensitive : ESearchCase::IgnoreCase;
|
||||
for (const FString& Term : SearchTerms)
|
||||
{
|
||||
if (!Term.IsEmpty() && Source.Contains(Term, SearchCase))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool UDirectiveUtilStringFunctionLibrary::FindFirstOfAny(const FString& Source, const TArray<FString>& SearchTerms, const bool bCaseSensitive, int32& OutFoundIndex, int32& OutTermIndex)
|
||||
{
|
||||
OutFoundIndex = INDEX_NONE;
|
||||
OutTermIndex = INDEX_NONE;
|
||||
|
||||
const ESearchCase::Type SearchCase = bCaseSensitive ? ESearchCase::CaseSensitive : ESearchCase::IgnoreCase;
|
||||
for (int32 TermIndex = 0; TermIndex < SearchTerms.Num(); ++TermIndex)
|
||||
{
|
||||
const FString& Term = SearchTerms[TermIndex];
|
||||
if (Term.IsEmpty())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
const int32 FoundIndex = Source.Find(Term, SearchCase, ESearchDir::FromStart);
|
||||
if (FoundIndex != INDEX_NONE && (OutFoundIndex == INDEX_NONE || FoundIndex < OutFoundIndex))
|
||||
{
|
||||
OutFoundIndex = FoundIndex;
|
||||
OutTermIndex = TermIndex;
|
||||
}
|
||||
}
|
||||
|
||||
return OutFoundIndex != INDEX_NONE;
|
||||
}
|
||||
|
||||
FString UDirectiveUtilStringFunctionLibrary::Base64Encode(const FString& Source)
|
||||
{
|
||||
return FBase64::Encode(Source);
|
||||
}
|
||||
|
||||
bool UDirectiveUtilStringFunctionLibrary::Base64Decode(const FString& Source, FString& OutDecoded)
|
||||
{
|
||||
OutDecoded.Reset();
|
||||
return FBase64::Decode(Source, OutDecoded);
|
||||
}
|
||||
|
||||
FString UDirectiveUtilStringFunctionLibrary::HexEncode(const FString& String)
|
||||
{
|
||||
return HexEncodeBytes(StringToUtf8Bytes(String));
|
||||
}
|
||||
|
||||
bool UDirectiveUtilStringFunctionLibrary::HexDecode(const FString& Hex, FString& OutString)
|
||||
{
|
||||
OutString.Reset();
|
||||
|
||||
TArray<uint8> Bytes;
|
||||
if (!HexDecodeBytes(Hex, Bytes))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
const FUTF8ToTCHAR Converter(reinterpret_cast<const ANSICHAR*>(Bytes.GetData()), Bytes.Num());
|
||||
OutString = FString(Converter.Length(), Converter.Get());
|
||||
return true;
|
||||
}
|
||||
|
||||
FString UDirectiveUtilStringFunctionLibrary::HexEncodeBytes(const TArray<uint8>& Bytes)
|
||||
{
|
||||
return BytesToHexLower(Bytes.GetData(), Bytes.Num());
|
||||
}
|
||||
|
||||
bool UDirectiveUtilStringFunctionLibrary::HexDecodeBytes(const FString& Hex, TArray<uint8>& OutBytes)
|
||||
{
|
||||
OutBytes.Reset();
|
||||
if (Hex.Len() % 2 != 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
OutBytes.Reserve(Hex.Len() / 2);
|
||||
for (int32 Index = 0; Index < Hex.Len(); Index += 2)
|
||||
{
|
||||
const TCHAR High = Hex[Index];
|
||||
const TCHAR Low = Hex[Index + 1];
|
||||
if (!CheckTCharIsHex(High) || !CheckTCharIsHex(Low))
|
||||
{
|
||||
OutBytes.Reset();
|
||||
return false;
|
||||
}
|
||||
OutBytes.Add(static_cast<uint8>((TCharToNibble(High) << 4) | TCharToNibble(Low)));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
FString UDirectiveUtilStringFunctionLibrary::Md5HashString(const FString& String)
|
||||
{
|
||||
return Md5HashBytes(StringToUtf8Bytes(String));
|
||||
}
|
||||
|
||||
FString UDirectiveUtilStringFunctionLibrary::Md5HashBytes(const TArray<uint8>& Bytes)
|
||||
{
|
||||
return FMD5::HashBytes(Bytes.GetData(), Bytes.Num());
|
||||
}
|
||||
|
||||
FString UDirectiveUtilStringFunctionLibrary::Sha1HashString(const FString& String)
|
||||
{
|
||||
return Sha1HashBytes(StringToUtf8Bytes(String));
|
||||
}
|
||||
|
||||
FString UDirectiveUtilStringFunctionLibrary::Sha1HashBytes(const TArray<uint8>& Bytes)
|
||||
{
|
||||
uint8 Digest[20];
|
||||
FSHA1::HashBuffer(Bytes.GetData(), Bytes.Num(), Digest);
|
||||
return BytesToHexLower(Digest, UE_ARRAY_COUNT(Digest));
|
||||
}
|
||||
|
||||
int32 UDirectiveUtilStringFunctionLibrary::Crc32String(const FString& String)
|
||||
{
|
||||
return Crc32Bytes(StringToUtf8Bytes(String));
|
||||
}
|
||||
|
||||
int32 UDirectiveUtilStringFunctionLibrary::Crc32Bytes(const TArray<uint8>& Bytes)
|
||||
{
|
||||
return static_cast<int32>(FCrc::MemCrc32(Bytes.GetData(), Bytes.Num()));
|
||||
}
|
||||
|
||||
bool UDirectiveUtilStringFunctionLibrary::IsValidFileName(const FString& String)
|
||||
{
|
||||
return !String.IsEmpty() && 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]);
|
||||
}
|
||||
|
||||
int32 UDirectiveUtilStringFunctionLibrary::FindBestStringMatch(const FString& Input, const TArray<FString>& Candidates, float& OutSimilarity, const bool bCaseSensitive)
|
||||
{
|
||||
OutSimilarity = 0.0f;
|
||||
int32 BestIndex = INDEX_NONE;
|
||||
|
||||
for (int32 Index = 0; Index < Candidates.Num(); ++Index)
|
||||
{
|
||||
const float Similarity = GetStringSimilarity(Input, Candidates[Index], bCaseSensitive);
|
||||
if (BestIndex == INDEX_NONE || Similarity > OutSimilarity)
|
||||
{
|
||||
BestIndex = Index;
|
||||
OutSimilarity = Similarity;
|
||||
}
|
||||
}
|
||||
return BestIndex;
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
// Copyright (c) 2026 Unreal Directive. Licensed under the MIT License.
|
||||
|
||||
|
||||
#include "Libraries/DirectiveUtilTextFunctionLibrary.h"
|
||||
|
||||
bool UDirectiveUtilTextFunctionLibrary::IsNotEmpty(const FText& Text)
|
||||
{
|
||||
return !Text.IsEmpty();
|
||||
}
|
||||
@@ -0,0 +1,245 @@
|
||||
// Copyright (c) 2026 Unreal Directive. Licensed under the MIT License.
|
||||
|
||||
|
||||
#include "Tasks/DirectiveUtilTask_AsyncLoadAsset.h"
|
||||
#include "DirectiveUtilLogChannels.h"
|
||||
#include "Engine/AssetManager.h"
|
||||
#include "Engine/StreamableManager.h"
|
||||
|
||||
UDirectiveUtilTask_AsyncLoadAsset* UDirectiveUtilTask_AsyncLoadAsset::AsyncLoadAsset(UObject* WorldContextObject, const TSoftObjectPtr<UObject> Asset)
|
||||
{
|
||||
UDirectiveUtilTask_AsyncLoadAsset* Action = NewObject<UDirectiveUtilTask_AsyncLoadAsset>();
|
||||
Action->SoftAsset = Asset;
|
||||
|
||||
if (WorldContextObject)
|
||||
{
|
||||
Action->RegisterWithGameInstance(WorldContextObject);
|
||||
}
|
||||
|
||||
return Action;
|
||||
}
|
||||
|
||||
void UDirectiveUtilTask_AsyncLoadAsset::Activate()
|
||||
{
|
||||
if (SoftAsset.IsNull())
|
||||
{
|
||||
UE_LOG(LogDirectiveUtil, Warning, TEXT("Async Load Asset failed to activate. The soft object reference is null."));
|
||||
Failed.Broadcast(nullptr);
|
||||
SetReadyToDestroy();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!UAssetManager::GetIfInitialized())
|
||||
{
|
||||
UE_LOG(LogDirectiveUtil, Warning, TEXT("Async Load Asset failed to activate. The Asset Manager is not initialized."));
|
||||
Failed.Broadcast(nullptr);
|
||||
SetReadyToDestroy();
|
||||
return;
|
||||
}
|
||||
|
||||
StreamableHandle = UAssetManager::GetStreamableManager().RequestAsyncLoad(
|
||||
SoftAsset.ToSoftObjectPath(),
|
||||
FStreamableDelegate::CreateUObject(this, &UDirectiveUtilTask_AsyncLoadAsset::OnLoaded),
|
||||
FStreamableManager::DefaultAsyncLoadPriority);
|
||||
|
||||
if (!StreamableHandle.IsValid())
|
||||
{
|
||||
UE_LOG(LogDirectiveUtil, Warning, TEXT("Async Load Asset failed to start the load request."));
|
||||
Failed.Broadcast(nullptr);
|
||||
SetReadyToDestroy();
|
||||
}
|
||||
}
|
||||
|
||||
void UDirectiveUtilTask_AsyncLoadAsset::OnLoaded()
|
||||
{
|
||||
UObject* LoadedAsset = StreamableHandle.IsValid() ? StreamableHandle->GetLoadedAsset() : nullptr;
|
||||
if (LoadedAsset)
|
||||
{
|
||||
Completed.Broadcast(LoadedAsset);
|
||||
}
|
||||
else
|
||||
{
|
||||
UE_LOG(LogDirectiveUtil, Warning, TEXT("Async Load Asset completed but the asset could not be resolved."));
|
||||
Failed.Broadcast(nullptr);
|
||||
}
|
||||
SetReadyToDestroy();
|
||||
}
|
||||
|
||||
void UDirectiveUtilTask_AsyncLoadAsset::Cancel()
|
||||
{
|
||||
if (StreamableHandle.IsValid() && StreamableHandle->IsActive())
|
||||
{
|
||||
StreamableHandle->CancelHandle();
|
||||
}
|
||||
SetReadyToDestroy();
|
||||
}
|
||||
|
||||
UDirectiveUtilTask_AsyncLoadClass* UDirectiveUtilTask_AsyncLoadClass::AsyncLoadClass(UObject* WorldContextObject, const TSoftClassPtr<UObject> AssetClass)
|
||||
{
|
||||
UDirectiveUtilTask_AsyncLoadClass* Action = NewObject<UDirectiveUtilTask_AsyncLoadClass>();
|
||||
Action->SoftClass = AssetClass;
|
||||
|
||||
if (WorldContextObject)
|
||||
{
|
||||
Action->RegisterWithGameInstance(WorldContextObject);
|
||||
}
|
||||
|
||||
return Action;
|
||||
}
|
||||
|
||||
void UDirectiveUtilTask_AsyncLoadClass::Activate()
|
||||
{
|
||||
if (SoftClass.IsNull())
|
||||
{
|
||||
UE_LOG(LogDirectiveUtil, Warning, TEXT("Async Load Class failed to activate. The soft class reference is null."));
|
||||
Failed.Broadcast(nullptr);
|
||||
SetReadyToDestroy();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!UAssetManager::GetIfInitialized())
|
||||
{
|
||||
UE_LOG(LogDirectiveUtil, Warning, TEXT("Async Load Class failed to activate. The Asset Manager is not initialized."));
|
||||
Failed.Broadcast(nullptr);
|
||||
SetReadyToDestroy();
|
||||
return;
|
||||
}
|
||||
|
||||
StreamableHandle = UAssetManager::GetStreamableManager().RequestAsyncLoad(
|
||||
SoftClass.ToSoftObjectPath(),
|
||||
FStreamableDelegate::CreateUObject(this, &UDirectiveUtilTask_AsyncLoadClass::OnLoaded),
|
||||
FStreamableManager::DefaultAsyncLoadPriority);
|
||||
|
||||
if (!StreamableHandle.IsValid())
|
||||
{
|
||||
UE_LOG(LogDirectiveUtil, Warning, TEXT("Async Load Class failed to start the load request."));
|
||||
Failed.Broadcast(nullptr);
|
||||
SetReadyToDestroy();
|
||||
}
|
||||
}
|
||||
|
||||
void UDirectiveUtilTask_AsyncLoadClass::OnLoaded()
|
||||
{
|
||||
UClass* LoadedClass = StreamableHandle.IsValid() ? Cast<UClass>(StreamableHandle->GetLoadedAsset()) : nullptr;
|
||||
if (LoadedClass)
|
||||
{
|
||||
Completed.Broadcast(LoadedClass);
|
||||
}
|
||||
else
|
||||
{
|
||||
UE_LOG(LogDirectiveUtil, Warning, TEXT("Async Load Class completed but the class could not be resolved."));
|
||||
Failed.Broadcast(nullptr);
|
||||
}
|
||||
SetReadyToDestroy();
|
||||
}
|
||||
|
||||
void UDirectiveUtilTask_AsyncLoadClass::Cancel()
|
||||
{
|
||||
if (StreamableHandle.IsValid() && StreamableHandle->IsActive())
|
||||
{
|
||||
StreamableHandle->CancelHandle();
|
||||
}
|
||||
SetReadyToDestroy();
|
||||
}
|
||||
|
||||
UDirectiveUtilTask_AsyncLoadAssets* UDirectiveUtilTask_AsyncLoadAssets::AsyncLoadAssets(UObject* WorldContextObject, const TArray<TSoftObjectPtr<UObject>>& Assets)
|
||||
{
|
||||
UDirectiveUtilTask_AsyncLoadAssets* Action = NewObject<UDirectiveUtilTask_AsyncLoadAssets>();
|
||||
Action->SoftAssets = Assets;
|
||||
|
||||
if (WorldContextObject)
|
||||
{
|
||||
Action->RegisterWithGameInstance(WorldContextObject);
|
||||
}
|
||||
|
||||
return Action;
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
if (!SoftAsset.IsNull())
|
||||
{
|
||||
PathsToLoad.AddUnique(SoftAsset.ToSoftObjectPath());
|
||||
}
|
||||
}
|
||||
|
||||
if (PathsToLoad.Num() == 0)
|
||||
{
|
||||
// Nothing to request; an empty request list is an error path in the streamable manager.
|
||||
OnLoaded();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!UAssetManager::GetIfInitialized())
|
||||
{
|
||||
UE_LOG(LogDirectiveUtil, Warning, TEXT("Async Load Assets failed to activate. The Asset Manager is not initialized."));
|
||||
OnLoaded();
|
||||
return;
|
||||
}
|
||||
|
||||
StreamableHandle = UAssetManager::GetStreamableManager().RequestAsyncLoad(
|
||||
MoveTemp(PathsToLoad),
|
||||
FStreamableDelegate::CreateUObject(this, &UDirectiveUtilTask_AsyncLoadAssets::OnLoaded),
|
||||
FStreamableManager::DefaultAsyncLoadPriority);
|
||||
|
||||
if (!StreamableHandle.IsValid())
|
||||
{
|
||||
UE_LOG(LogDirectiveUtil, Warning, TEXT("Async Load Assets failed to start the load request."));
|
||||
OnLoaded();
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
void UDirectiveUtilTask_AsyncLoadAssets::OnLoaded()
|
||||
{
|
||||
if (bHasCompleted)
|
||||
{
|
||||
return;
|
||||
}
|
||||
bHasCompleted = true;
|
||||
|
||||
TArray<UObject*> LoadedAssets;
|
||||
LoadedAssets.Reserve(SoftAssets.Num());
|
||||
for (const TSoftObjectPtr<UObject>& SoftAsset : SoftAssets)
|
||||
{
|
||||
LoadedAssets.Add(SoftAsset.Get());
|
||||
}
|
||||
|
||||
Completed.Broadcast(LoadedAssets);
|
||||
SetReadyToDestroy();
|
||||
}
|
||||
|
||||
void UDirectiveUtilTask_AsyncLoadAssets::OnUpdate(TSharedRef<FStreamableHandle> Handle)
|
||||
{
|
||||
if (bHasCompleted)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
int32 LoadedCount = 0;
|
||||
int32 RequestedCount = 0;
|
||||
Handle->GetLoadedCount(LoadedCount, RequestedCount);
|
||||
Progress.Broadcast(LoadedCount, RequestedCount);
|
||||
}
|
||||
|
||||
void UDirectiveUtilTask_AsyncLoadAssets::Cancel()
|
||||
{
|
||||
bHasCompleted = true;
|
||||
if (StreamableHandle.IsValid() && StreamableHandle->IsActive())
|
||||
{
|
||||
StreamableHandle->CancelHandle();
|
||||
}
|
||||
SetReadyToDestroy();
|
||||
}
|
||||
@@ -0,0 +1,139 @@
|
||||
// Copyright (c) 2026 Unreal Directive. Licensed under the MIT License.
|
||||
|
||||
|
||||
#include "Tasks/DirectiveUtilTask_AsyncTrace.h"
|
||||
#include "DirectiveUtilLogChannels.h"
|
||||
#include "Engine/Engine.h"
|
||||
#include "Engine/World.h"
|
||||
#include "WorldCollision.h"
|
||||
#include "CollisionShape.h"
|
||||
#include "CollisionQueryParams.h"
|
||||
|
||||
UDirectiveUtilTask_AsyncTrace* UDirectiveUtilTask_AsyncTrace::AsyncLineTraceByChannel(UObject* WorldContextObject, const FVector Start, const FVector End, const ETraceTypeQuery TraceChannel, const bool bMultiTrace)
|
||||
{
|
||||
UDirectiveUtilTask_AsyncTrace* Action = NewObject<UDirectiveUtilTask_AsyncTrace>();
|
||||
Action->WorldContextObject = WorldContextObject;
|
||||
Action->Start = Start;
|
||||
Action->End = End;
|
||||
Action->TraceChannel = TraceChannel;
|
||||
Action->bMultiTrace = bMultiTrace;
|
||||
Action->Shape = EDirectiveUtilTraceShape::Line;
|
||||
|
||||
if (WorldContextObject)
|
||||
{
|
||||
Action->RegisterWithGameInstance(WorldContextObject);
|
||||
}
|
||||
|
||||
return Action;
|
||||
}
|
||||
|
||||
UDirectiveUtilTask_AsyncTrace* UDirectiveUtilTask_AsyncTrace::AsyncSphereTraceByChannel(UObject* WorldContextObject, const FVector Start, const FVector End, const float Radius, const ETraceTypeQuery TraceChannel, const bool bMultiTrace)
|
||||
{
|
||||
UDirectiveUtilTask_AsyncTrace* Action = NewObject<UDirectiveUtilTask_AsyncTrace>();
|
||||
Action->WorldContextObject = WorldContextObject;
|
||||
Action->Start = Start;
|
||||
Action->End = End;
|
||||
Action->Radius = Radius;
|
||||
Action->TraceChannel = TraceChannel;
|
||||
Action->bMultiTrace = bMultiTrace;
|
||||
Action->Shape = EDirectiveUtilTraceShape::Sphere;
|
||||
|
||||
if (WorldContextObject)
|
||||
{
|
||||
Action->RegisterWithGameInstance(WorldContextObject);
|
||||
}
|
||||
|
||||
return Action;
|
||||
}
|
||||
|
||||
UDirectiveUtilTask_AsyncTrace* UDirectiveUtilTask_AsyncTrace::AsyncBoxTraceByChannel(UObject* WorldContextObject, const FVector Start, const FVector End, const FVector HalfSize, const FRotator Orientation, const ETraceTypeQuery TraceChannel, const bool bMultiTrace)
|
||||
{
|
||||
UDirectiveUtilTask_AsyncTrace* Action = NewObject<UDirectiveUtilTask_AsyncTrace>();
|
||||
Action->WorldContextObject = WorldContextObject;
|
||||
Action->Start = Start;
|
||||
Action->End = End;
|
||||
Action->HalfSize = HalfSize;
|
||||
Action->Orientation = Orientation.Quaternion();
|
||||
Action->TraceChannel = TraceChannel;
|
||||
Action->bMultiTrace = bMultiTrace;
|
||||
Action->Shape = EDirectiveUtilTraceShape::Box;
|
||||
|
||||
if (WorldContextObject)
|
||||
{
|
||||
Action->RegisterWithGameInstance(WorldContextObject);
|
||||
}
|
||||
|
||||
return Action;
|
||||
}
|
||||
|
||||
UDirectiveUtilTask_AsyncTrace* UDirectiveUtilTask_AsyncTrace::AsyncCapsuleTraceByChannel(UObject* WorldContextObject, const FVector Start, const FVector End, const float Radius, const float HalfHeight, const ETraceTypeQuery TraceChannel, const bool bMultiTrace)
|
||||
{
|
||||
UDirectiveUtilTask_AsyncTrace* Action = NewObject<UDirectiveUtilTask_AsyncTrace>();
|
||||
Action->WorldContextObject = WorldContextObject;
|
||||
Action->Start = Start;
|
||||
Action->End = End;
|
||||
Action->Radius = Radius;
|
||||
Action->HalfHeight = HalfHeight;
|
||||
Action->TraceChannel = TraceChannel;
|
||||
Action->bMultiTrace = bMultiTrace;
|
||||
Action->Shape = EDirectiveUtilTraceShape::Capsule;
|
||||
|
||||
if (WorldContextObject)
|
||||
{
|
||||
Action->RegisterWithGameInstance(WorldContextObject);
|
||||
}
|
||||
|
||||
return Action;
|
||||
}
|
||||
|
||||
void UDirectiveUtilTask_AsyncTrace::Activate()
|
||||
{
|
||||
UWorld* World = WorldContextObject && GEngine
|
||||
? GEngine->GetWorldFromContextObject(WorldContextObject, EGetWorldErrorMode::LogAndReturnNull)
|
||||
: nullptr;
|
||||
if (!World)
|
||||
{
|
||||
UE_LOG(LogDirectiveUtil, Warning, TEXT("Async Trace failed to activate. World is null."));
|
||||
Completed.Broadcast(TArray<FHitResult>());
|
||||
SetReadyToDestroy();
|
||||
return;
|
||||
}
|
||||
|
||||
const ECollisionChannel CollisionChannel = UEngineTypes::ConvertToCollisionChannel(TraceChannel);
|
||||
const EAsyncTraceType AsyncType = bMultiTrace ? EAsyncTraceType::Multi : EAsyncTraceType::Single;
|
||||
|
||||
FTraceDelegate TraceDelegate;
|
||||
TraceDelegate.BindUObject(this, &UDirectiveUtilTask_AsyncTrace::OnTraceComplete);
|
||||
|
||||
FCollisionQueryParams Params(FName(TEXT("DirectiveUtilAsyncTrace")), false);
|
||||
|
||||
if (Shape == EDirectiveUtilTraceShape::Line)
|
||||
{
|
||||
World->AsyncLineTraceByChannel(AsyncType, Start, End, CollisionChannel, Params, FCollisionResponseParams::DefaultResponseParam, &TraceDelegate);
|
||||
}
|
||||
else
|
||||
{
|
||||
FCollisionShape CollisionShape;
|
||||
switch (Shape)
|
||||
{
|
||||
case EDirectiveUtilTraceShape::Sphere:
|
||||
CollisionShape = FCollisionShape::MakeSphere(Radius);
|
||||
break;
|
||||
case EDirectiveUtilTraceShape::Box:
|
||||
CollisionShape = FCollisionShape::MakeBox(HalfSize);
|
||||
break;
|
||||
case EDirectiveUtilTraceShape::Capsule:
|
||||
CollisionShape = FCollisionShape::MakeCapsule(Radius, HalfHeight);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
World->AsyncSweepByChannel(AsyncType, Start, End, Orientation, CollisionChannel, CollisionShape, Params, FCollisionResponseParams::DefaultResponseParam, &TraceDelegate);
|
||||
}
|
||||
}
|
||||
|
||||
void UDirectiveUtilTask_AsyncTrace::OnTraceComplete(const FTraceHandle& Handle, FTraceDatum& Datum)
|
||||
{
|
||||
Completed.Broadcast(Datum.OutHits);
|
||||
SetReadyToDestroy();
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
// Copyright (c) 2026 Unreal Directive. Licensed under the MIT License.
|
||||
|
||||
|
||||
#include "Tasks/DirectiveUtilTask_Delay.h"
|
||||
#include "DirectiveUtilLogChannels.h"
|
||||
#include "Engine/Engine.h"
|
||||
#include "Engine/World.h"
|
||||
#include "TimerManager.h"
|
||||
|
||||
|
||||
UDirectiveUtilTask_Delay* UDirectiveUtilTask_Delay::CancellableDelay(UObject* WorldContextObject, const float Duration)
|
||||
{
|
||||
UDirectiveUtilTask_Delay* Action = NewObject<UDirectiveUtilTask_Delay>();
|
||||
Action->WorldContextObject = WorldContextObject;
|
||||
Action->Duration = Duration;
|
||||
|
||||
if (WorldContextObject)
|
||||
{
|
||||
Action->RegisterWithGameInstance(WorldContextObject);
|
||||
}
|
||||
|
||||
return Action;
|
||||
}
|
||||
|
||||
|
||||
void UDirectiveUtilTask_Delay::EndTask()
|
||||
{
|
||||
if (UWorld* World = GEngine ? GEngine->GetWorldFromContextObject(WorldContextObject, EGetWorldErrorMode::ReturnNull) : nullptr)
|
||||
{
|
||||
World->GetTimerManager().ClearTimer(TimerHandle);
|
||||
}
|
||||
SetReadyToDestroy();
|
||||
}
|
||||
|
||||
void UDirectiveUtilTask_Delay::Activate()
|
||||
{
|
||||
UWorld* World = WorldContextObject && GEngine
|
||||
? GEngine->GetWorldFromContextObject(WorldContextObject, EGetWorldErrorMode::LogAndReturnNull)
|
||||
: nullptr;
|
||||
if (!World)
|
||||
{
|
||||
UE_LOG(LogDirectiveUtil, Warning, TEXT("Cancellable Delay failed to activate. World is null."));
|
||||
SetReadyToDestroy();
|
||||
return;
|
||||
}
|
||||
|
||||
const float ClampedDuration = FMath::Max(Duration, 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);
|
||||
}
|
||||
|
||||
void UDirectiveUtilTask_Delay::OnDelayComplete()
|
||||
{
|
||||
UE_LOG(LogDirectiveUtil, Verbose, TEXT("Cancellable Delay completed."));
|
||||
Completed.Broadcast();
|
||||
SetReadyToDestroy();
|
||||
}
|
||||
@@ -0,0 +1,280 @@
|
||||
// Copyright (c) 2026 Unreal Directive. Licensed under the MIT License.
|
||||
|
||||
|
||||
#include "Tasks/DirectiveUtilTask_MoveToLocation.h"
|
||||
#include "DirectiveUtilLogChannels.h"
|
||||
#include "Blueprint/AIBlueprintHelperLibrary.h"
|
||||
#include "Engine/World.h"
|
||||
#include "DrawDebugHelpers.h"
|
||||
#include "Navigation/PathFollowingComponent.h"
|
||||
#include "TimerManager.h"
|
||||
|
||||
|
||||
UDirectiveUtilTask_MoveToLocation* UDirectiveUtilTask_MoveToLocation::MoveToLocation(
|
||||
UObject* WorldContextObject,
|
||||
AController* Controller,
|
||||
const FVector Destination,
|
||||
const float AcceptanceRadius,
|
||||
const bool bCheckStuckMovement,
|
||||
const float StuckThreshold,
|
||||
const bool bDebugLineTrace)
|
||||
{
|
||||
UDirectiveUtilTask_MoveToLocation* Action = NewObject<UDirectiveUtilTask_MoveToLocation>();
|
||||
Action->Controller = Controller;
|
||||
Action->Destination = Destination;
|
||||
Action->AcceptanceRadius = AcceptanceRadius;
|
||||
Action->bDebugLineTrace = bDebugLineTrace;
|
||||
Action->StuckThreshold = StuckThreshold;
|
||||
Action->bCheckStuckMovement = bCheckStuckMovement;
|
||||
|
||||
if (WorldContextObject)
|
||||
{
|
||||
Action->RegisterWithGameInstance(WorldContextObject);
|
||||
}
|
||||
|
||||
return Action;
|
||||
}
|
||||
|
||||
|
||||
void UDirectiveUtilTask_MoveToLocation::EndTask()
|
||||
{
|
||||
ExecuteCompleted(false);
|
||||
}
|
||||
|
||||
void UDirectiveUtilTask_MoveToLocation::Activate()
|
||||
{
|
||||
if(!Controller || !Controller->GetPawn())
|
||||
{
|
||||
ExecuteCompleted(false);
|
||||
UE_LOG(LogDirectiveUtil, Warning, TEXT("Controller or pawn has been destroyed while moving to location. Aborting."));
|
||||
return;
|
||||
}
|
||||
|
||||
StartLocation = Controller->GetPawn()->GetActorLocation();
|
||||
LastCheckedLocation = StartLocation;
|
||||
CurrentLocation = StartLocation;
|
||||
|
||||
Controller->GetWorld()->GetTimerManager().SetTimer(TimerHandle, this, &UDirectiveUtilTask_MoveToLocation::CheckMoveToLocation, 0.1f, true);
|
||||
|
||||
if (bCheckStuckMovement)
|
||||
{
|
||||
Controller->GetWorld()->GetTimerManager().SetTimer(StuckTimerHandle, this, &UDirectiveUtilTask_MoveToLocation::CheckStuckMovement, 3.f, true);
|
||||
}
|
||||
|
||||
UAIBlueprintHelperLibrary::SimpleMoveToLocation(Controller, Destination);
|
||||
UE_LOG(LogDirectiveUtil, Verbose, TEXT("Moving controller to location (%s)."), *Destination.ToString());
|
||||
|
||||
if (bDebugLineTrace)
|
||||
{
|
||||
DrawDebugLine(
|
||||
Controller->GetWorld(),
|
||||
Destination + FVector(0, 0, 100),
|
||||
Destination,
|
||||
FColor::Green,
|
||||
false,
|
||||
5.0f,
|
||||
0,
|
||||
1.0f
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void UDirectiveUtilTask_MoveToLocation::CheckMoveToLocation()
|
||||
{
|
||||
|
||||
if(!Controller || !Controller->GetPawn())
|
||||
{
|
||||
ExecuteCompleted(false);
|
||||
UE_LOG(LogDirectiveUtil, Warning, TEXT("Controller or pawn has been destroyed while moving to location. Aborting."));
|
||||
return;
|
||||
}
|
||||
|
||||
CurrentLocation = Controller->GetPawn()->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)
|
||||
{
|
||||
UE_LOG(LogDirectiveUtil, Verbose, TEXT("Controller has moved to location."));
|
||||
ExecuteCompleted(true);
|
||||
return;
|
||||
}
|
||||
|
||||
const UPathFollowingComponent* PathFollowing = Controller->FindComponentByClass<UPathFollowingComponent>();
|
||||
if (!PathFollowing || PathFollowing->GetStatus() == EPathFollowingStatus::Idle)
|
||||
{
|
||||
UE_LOG(LogDirectiveUtil, Verbose, TEXT("Path following has stopped. Completing move to location."));
|
||||
ExecuteCompleted(FVector::Dist(CurrentLocation, Destination) < AcceptanceRadius);
|
||||
}
|
||||
}
|
||||
|
||||
void UDirectiveUtilTask_MoveToLocation::CheckStuckMovement()
|
||||
{
|
||||
if(!Controller || !Controller->GetPawn())
|
||||
{
|
||||
ExecuteCompleted(false);
|
||||
UE_LOG(LogDirectiveUtil, Warning, TEXT("Controller or pawn has been destroyed while moving to location. Aborting."));
|
||||
return;
|
||||
}
|
||||
|
||||
if (FVector::Dist(CurrentLocation, LastCheckedLocation) < StuckThreshold)
|
||||
{
|
||||
UE_LOG(LogDirectiveUtil, Warning, TEXT("Controller is stuck while moving to location. Aborting"));
|
||||
ExecuteCompleted(false);
|
||||
}
|
||||
|
||||
LastCheckedLocation = CurrentLocation;
|
||||
}
|
||||
|
||||
void UDirectiveUtilTask_MoveToLocation::ExecuteCompleted(const bool bSuccess)
|
||||
{
|
||||
if (bHasCompleted)
|
||||
{
|
||||
return;
|
||||
}
|
||||
bHasCompleted = true;
|
||||
|
||||
UE_LOG(LogDirectiveUtil, Log, TEXT("Movement to location completed. Success: %s."), bSuccess ? TEXT("true") : TEXT("false"));
|
||||
|
||||
if (Controller)
|
||||
{
|
||||
Controller->GetWorld()->GetTimerManager().ClearTimer(TimerHandle);
|
||||
Controller->GetWorld()->GetTimerManager().ClearTimer(StuckTimerHandle);
|
||||
}
|
||||
|
||||
Completed.Broadcast(bSuccess);
|
||||
|
||||
Controller = nullptr;
|
||||
Destination = FVector::ZeroVector;
|
||||
|
||||
SetReadyToDestroy();
|
||||
}
|
||||
|
||||
UDirectiveUtilTask_MoveToActor* UDirectiveUtilTask_MoveToActor::MoveToActor(
|
||||
UObject* WorldContextObject,
|
||||
AController* Controller,
|
||||
AActor* Goal,
|
||||
const float AcceptanceRadius,
|
||||
const bool bCheckStuckMovement,
|
||||
const float StuckThreshold)
|
||||
{
|
||||
UDirectiveUtilTask_MoveToActor* Action = NewObject<UDirectiveUtilTask_MoveToActor>();
|
||||
Action->Controller = Controller;
|
||||
Action->Goal = Goal;
|
||||
Action->AcceptanceRadius = AcceptanceRadius;
|
||||
Action->StuckThreshold = StuckThreshold;
|
||||
Action->bCheckStuckMovement = bCheckStuckMovement;
|
||||
|
||||
if (WorldContextObject)
|
||||
{
|
||||
Action->RegisterWithGameInstance(WorldContextObject);
|
||||
}
|
||||
|
||||
return Action;
|
||||
}
|
||||
|
||||
void UDirectiveUtilTask_MoveToActor::EndTask()
|
||||
{
|
||||
ExecuteCompleted(false);
|
||||
}
|
||||
|
||||
void UDirectiveUtilTask_MoveToActor::Activate()
|
||||
{
|
||||
if(!Controller || !Controller->GetPawn() || !IsValid(Goal))
|
||||
{
|
||||
ExecuteCompleted(false);
|
||||
UE_LOG(LogDirectiveUtil, Warning, TEXT("Controller, pawn, or goal has been destroyed while moving to actor. Aborting."));
|
||||
return;
|
||||
}
|
||||
|
||||
StartLocation = Controller->GetPawn()->GetActorLocation();
|
||||
LastCheckedLocation = StartLocation;
|
||||
CurrentLocation = StartLocation;
|
||||
|
||||
Controller->GetWorld()->GetTimerManager().SetTimer(TimerHandle, this, &UDirectiveUtilTask_MoveToActor::CheckMoveToActor, 0.1f, true);
|
||||
|
||||
if (bCheckStuckMovement)
|
||||
{
|
||||
Controller->GetWorld()->GetTimerManager().SetTimer(StuckTimerHandle, this, &UDirectiveUtilTask_MoveToActor::CheckStuckMovement, 3.f, true);
|
||||
}
|
||||
|
||||
UAIBlueprintHelperLibrary::SimpleMoveToActor(Controller, Goal);
|
||||
UE_LOG(LogDirectiveUtil, Verbose, TEXT("Moving controller to actor (%s)."), *GetNameSafe(Goal));
|
||||
}
|
||||
|
||||
void UDirectiveUtilTask_MoveToActor::CheckMoveToActor()
|
||||
{
|
||||
if(!Controller || !Controller->GetPawn())
|
||||
{
|
||||
ExecuteCompleted(false);
|
||||
UE_LOG(LogDirectiveUtil, Warning, TEXT("Controller or pawn has been destroyed while moving to actor. Aborting."));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!IsValid(Goal))
|
||||
{
|
||||
ExecuteCompleted(false);
|
||||
UE_LOG(LogDirectiveUtil, Warning, TEXT("Goal actor has been destroyed while moving to actor. Aborting."));
|
||||
return;
|
||||
}
|
||||
|
||||
// The goal can move, so its location is re-read every poll.
|
||||
const FVector GoalLocation = Goal->GetActorLocation();
|
||||
CurrentLocation = Controller->GetPawn()->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)
|
||||
{
|
||||
UE_LOG(LogDirectiveUtil, Verbose, TEXT("Controller has moved to actor."));
|
||||
ExecuteCompleted(true);
|
||||
return;
|
||||
}
|
||||
|
||||
const UPathFollowingComponent* PathFollowing = Controller->FindComponentByClass<UPathFollowingComponent>();
|
||||
if (!PathFollowing || PathFollowing->GetStatus() == EPathFollowingStatus::Idle)
|
||||
{
|
||||
UE_LOG(LogDirectiveUtil, Verbose, TEXT("Path following has stopped. Completing move to actor."));
|
||||
ExecuteCompleted(FVector::Dist(CurrentLocation, GoalLocation) < AcceptanceRadius);
|
||||
}
|
||||
}
|
||||
|
||||
void UDirectiveUtilTask_MoveToActor::CheckStuckMovement()
|
||||
{
|
||||
if(!Controller || !Controller->GetPawn())
|
||||
{
|
||||
ExecuteCompleted(false);
|
||||
UE_LOG(LogDirectiveUtil, Warning, TEXT("Controller or pawn has been destroyed while moving to actor. Aborting."));
|
||||
return;
|
||||
}
|
||||
|
||||
if (FVector::Dist(CurrentLocation, LastCheckedLocation) < StuckThreshold)
|
||||
{
|
||||
UE_LOG(LogDirectiveUtil, Warning, TEXT("Controller is stuck while moving to actor. Aborting"));
|
||||
ExecuteCompleted(false);
|
||||
}
|
||||
|
||||
LastCheckedLocation = CurrentLocation;
|
||||
}
|
||||
|
||||
void UDirectiveUtilTask_MoveToActor::ExecuteCompleted(const bool bSuccess)
|
||||
{
|
||||
if (bHasCompleted)
|
||||
{
|
||||
return;
|
||||
}
|
||||
bHasCompleted = true;
|
||||
|
||||
UE_LOG(LogDirectiveUtil, Log, TEXT("Movement to actor completed. Success: %s."), bSuccess ? TEXT("true") : TEXT("false"));
|
||||
|
||||
if (Controller)
|
||||
{
|
||||
Controller->GetWorld()->GetTimerManager().ClearTimer(TimerHandle);
|
||||
Controller->GetWorld()->GetTimerManager().ClearTimer(StuckTimerHandle);
|
||||
}
|
||||
|
||||
Completed.Broadcast(bSuccess);
|
||||
|
||||
Controller = nullptr;
|
||||
Goal = nullptr;
|
||||
|
||||
SetReadyToDestroy();
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
// Copyright (c) 2026 Unreal Directive. Licensed under the MIT License.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Logging/LogMacros.h"
|
||||
|
||||
DIRECTIVEUTILITIESRUNTIME_API DECLARE_LOG_CATEGORY_EXTERN(LogDirectiveUtil, Log, All);
|
||||
DIRECTIVEUTILITIESRUNTIME_API DECLARE_LOG_CATEGORY_EXTERN(LogDirectiveUtilEditor, Log, All);
|
||||
@@ -0,0 +1,15 @@
|
||||
// Copyright (c) 2026 Unreal Directive. Licensed under the MIT License.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "Modules/ModuleManager.h"
|
||||
|
||||
class FDirectiveUtilitiesRuntimeModule : public IModuleInterface
|
||||
{
|
||||
public:
|
||||
|
||||
/** IModuleInterface implementation */
|
||||
virtual void StartupModule() override;
|
||||
virtual void ShutdownModule() override;
|
||||
};
|
||||
@@ -0,0 +1,716 @@
|
||||
// Copyright (c) 2026 Unreal Directive. Licensed under the MIT License.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "Kismet/BlueprintFunctionLibrary.h"
|
||||
#include "Net/Core/PushModel/PushModel.h"
|
||||
#include "DirectiveUtilArrayFunctionLibrary.generated.h"
|
||||
|
||||
/**
|
||||
* UDirectiveUtilArrayFunctionLibrary
|
||||
* A collection of array utility functions that improve the usability of arrays in Blueprints.
|
||||
*/
|
||||
UCLASS()
|
||||
class DIRECTIVEUTILITIESRUNTIME_API UDirectiveUtilArrayFunctionLibrary : public UBlueprintFunctionLibrary
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
|
||||
/**
|
||||
* Returns the next index in the array.
|
||||
* If the next index is greater than the last array index and bLoop is enabled, the index will loop back to the start of the array.
|
||||
* Otherwise, the last index will be returned.
|
||||
* Returns INDEX_NONE for an empty array.
|
||||
* @param TargetArray - The array to get the next index for.
|
||||
* @param Index - The current index.
|
||||
* @param bLoop - If true, the index will loop back to the beginning of the array when the next index is greater than the last array index.
|
||||
* Otherwise, the last index will be returned.
|
||||
* @returns The next index in the array.
|
||||
*/
|
||||
UFUNCTION(BlueprintPure, CustomThunk, meta=(DisplayName = "Next Index", CompactNodeTitle = "NEXT INDEX", ArrayParm = "TargetArray", BlueprintThreadSafe), Category="Directive Utilities|Array")
|
||||
static int32 Array_NextIndex(const TArray<int32>& TargetArray, const int32 Index, const bool bLoop);
|
||||
|
||||
/**
|
||||
* Returns the previous index in the array.
|
||||
* If the previous index is less than 0 and bLoop is enabled, the index will loop back to the end of the array.
|
||||
* Otherwise, 0 will be returned.
|
||||
* Returns INDEX_NONE for an empty array.
|
||||
* @param TargetArray - The array to get the previous index for.
|
||||
* @param Index - The current index.
|
||||
* @param bLoop - If the next index is greater than the last array index and bLoop is enabled, the index will loop back to the start of the array.
|
||||
* Otherwise, the last index will be returned.
|
||||
* @returns The previous index in the array.
|
||||
*/
|
||||
UFUNCTION(BlueprintPure, CustomThunk, meta=(DisplayName = "Previous Index", CompactNodeTitle = "PREV INDEX", ArrayParm = "TargetArray", BlueprintThreadSafe), Category="Directive Utilities|Array")
|
||||
static int32 Array_PreviousIndex(const TArray<int32>& TargetArray, const int32 Index, const bool bLoop);
|
||||
|
||||
/**
|
||||
* Removes duplicate elements from the array in-place.
|
||||
* @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);
|
||||
|
||||
/**
|
||||
* Returns a copy of the first element of the array.
|
||||
* @param TargetArray - The array to read from.
|
||||
* @param OutItem - [out] A copy of the first element, or the default value if the array is empty.
|
||||
* @returns True if the array contained a valid first element.
|
||||
*/
|
||||
UFUNCTION(BlueprintPure, CustomThunk, meta=(DisplayName = "Get Valid First Array Item (Copy)", ArrayParm = "TargetArray", ArrayTypeDependentParams = "OutItem", BlueprintThreadSafe), Category="Directive Utilities|Array")
|
||||
static bool Array_GetValidFirstItemCopy(const TArray<int32>& TargetArray, int32& OutItem);
|
||||
|
||||
/**
|
||||
* Returns a copy of the last element of the array.
|
||||
* @param TargetArray - The array to read from.
|
||||
* @param OutItem - [out] A copy of the last element, or the default value if the array is empty.
|
||||
* @returns True if the array contained a valid last element.
|
||||
*/
|
||||
UFUNCTION(BlueprintPure, CustomThunk, meta=(DisplayName = "Get Valid Last Array Item (Copy)", ArrayParm = "TargetArray", ArrayTypeDependentParams = "OutItem", BlueprintThreadSafe), Category="Directive Utilities|Array")
|
||||
static bool Array_GetValidLastItemCopy(const TArray<int32>& TargetArray, int32& OutItem);
|
||||
|
||||
/**
|
||||
* Returns a copy of the element at the given index, if the index is valid.
|
||||
* @param TargetArray - The array to read from.
|
||||
* @param Index - The index to read.
|
||||
* @param OutItem - [out] A copy of the element, or the default value if the index is invalid.
|
||||
* @returns True if the index was valid.
|
||||
*/
|
||||
UFUNCTION(BlueprintPure, CustomThunk, meta=(DisplayName = "Get Valid Array Item From Index (Copy)", ArrayParm = "TargetArray", ArrayTypeDependentParams = "OutItem", BlueprintThreadSafe), Category="Directive Utilities|Array")
|
||||
static bool Array_GetValidItemFromIndexCopy(const TArray<int32>& TargetArray, const int32 Index, int32& OutItem);
|
||||
|
||||
/**
|
||||
* Returns a copy of a random element from the array.
|
||||
* @param TargetArray - The array to read from.
|
||||
* @param OutItem - [out] A copy of the randomly selected element, or the default value if the array is empty.
|
||||
* @param OutIndex - [out] The index of the selected element, or INDEX_NONE if the array is empty.
|
||||
* @returns True if a valid element was selected.
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, CustomThunk, meta=(DisplayName = "Get Random Valid Array Item", CompactNodeTitle = "RANDOM", ArrayParm = "TargetArray", ArrayTypeDependentParams = "OutItem"), Category="Directive Utilities|Array")
|
||||
static bool Array_GetRandomItem(const TArray<int32>& TargetArray, int32& OutItem, int32& OutIndex);
|
||||
|
||||
/**
|
||||
* Returns a copy of the last element of the array without removing it.
|
||||
* @param TargetArray - The array to read from.
|
||||
* @param OutItem - [out] A copy of the last element, or the default value if the array is empty.
|
||||
*/
|
||||
UFUNCTION(BlueprintPure, CustomThunk, meta=(DisplayName = "Last Value", CompactNodeTitle = "LAST", ArrayParm = "TargetArray", ArrayTypeDependentParams = "OutItem", BlueprintThreadSafe), Category="Directive Utilities|Array")
|
||||
static void Array_LastValue(const TArray<int32>& TargetArray, int32& OutItem);
|
||||
|
||||
/**
|
||||
* Removes the last element of the array and returns a copy of it.
|
||||
* @param TargetArray - The array to pop from.
|
||||
* @param OutItem - [out] A copy of the removed element, or the default value if the array is empty.
|
||||
* @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);
|
||||
|
||||
/**
|
||||
* Removes the first element of the array and returns a copy of it.
|
||||
* @param TargetArray - The array to pop from.
|
||||
* @param OutItem - [out] A copy of the removed element, or the default value if the array is empty.
|
||||
* @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);
|
||||
|
||||
/**
|
||||
* Removes the element at the given index by swapping it with the last element (does not preserve order).
|
||||
* This is O(1) but changes the position of the previously-last element.
|
||||
* @param TargetArray - The array to remove from.
|
||||
* @param Index - The index to remove.
|
||||
* @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);
|
||||
|
||||
/**
|
||||
* Returns a copy of a contiguous range of the array. The range is clamped to the array bounds.
|
||||
* @param TargetArray - The array to slice.
|
||||
* @param StartIndex - The index to start copying from (clamped to [0, Length]).
|
||||
* @param Count - The number of elements to copy. Values <= 0 produce an empty array.
|
||||
* @param OutArray - [out] The sliced copy.
|
||||
*/
|
||||
UFUNCTION(BlueprintPure, CustomThunk, meta=(DisplayName = "Slice", ArrayParm = "TargetArray,OutArray", ArrayTypeDependentParams = "OutArray", BlueprintThreadSafe), Category="Directive Utilities|Array")
|
||||
static void Array_Slice(const TArray<int32>& TargetArray, const int32 StartIndex, const int32 Count, TArray<int32>& OutArray);
|
||||
|
||||
/**
|
||||
* Cyclically rotates the elements of the array in place.
|
||||
* @param TargetArray - The array to rotate.
|
||||
* @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);
|
||||
|
||||
/**
|
||||
* Returns a copy of the array with duplicates removed, keeping the first occurrence and preserving order.
|
||||
* Unlike Remove Duplicates, this does not modify the input array.
|
||||
* @param TargetArray - The array to read from.
|
||||
* @param OutArray - [out] The de-duplicated copy.
|
||||
*/
|
||||
UFUNCTION(BlueprintPure, CustomThunk, meta=(DisplayName = "Get Distinct (Copy)", ArrayParm = "TargetArray,OutArray", ArrayTypeDependentParams = "OutArray", BlueprintThreadSafe), Category="Directive Utilities|Array")
|
||||
static void Array_GetDistinct(const TArray<int32>& TargetArray, TArray<int32>& OutArray);
|
||||
|
||||
/**
|
||||
* Counts how many times an item appears in the array.
|
||||
* @param TargetArray - The array to search.
|
||||
* @param ItemToCount - The item to count.
|
||||
* @returns The number of occurrences.
|
||||
*/
|
||||
UFUNCTION(BlueprintPure, CustomThunk, meta=(DisplayName = "Count Occurrences", ArrayParm = "TargetArray", ArrayTypeDependentParams = "ItemToCount", AutoCreateRefTerm = "ItemToCount", BlueprintThreadSafe), Category="Directive Utilities|Array")
|
||||
static int32 Array_CountOccurrences(const TArray<int32>& TargetArray, const int32& ItemToCount);
|
||||
|
||||
/**
|
||||
* Returns the most frequently occurring element of the array (ties resolve to the earliest such element).
|
||||
* @param TargetArray - The array to read from.
|
||||
* @param OutItem - [out] A copy of the most common element, or the default value if the array is empty.
|
||||
* @param OutCount - [out] The number of times the most common element occurs.
|
||||
* @returns True if the array was non-empty.
|
||||
*/
|
||||
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);
|
||||
|
||||
|
||||
/*~
|
||||
* Native functions that will be called by the below custom thunk layers, which read off the property address and call the appropriate native handler.
|
||||
* Based off UKismetArrayLibrary implementation
|
||||
~*/
|
||||
|
||||
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 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);
|
||||
static bool GenericArray_GetRandomItem(const void* TargetArray, const FArrayProperty* ArrayProperty, void* OutItemPtr, int32* OutIndex);
|
||||
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 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);
|
||||
|
||||
/*~
|
||||
* Custom thunk layers that read off the property address and call the appropriate native handler.
|
||||
* Based off UKismetArrayLibrary implementation
|
||||
~*/
|
||||
|
||||
DECLARE_FUNCTION(execArray_NextIndex)
|
||||
{
|
||||
Stack.MostRecentProperty = nullptr;
|
||||
Stack.StepCompiledIn<FArrayProperty>(nullptr);
|
||||
const void* ArrayAddr = Stack.MostRecentPropertyAddress;
|
||||
const FArrayProperty* ArrayProperty = CastField<FArrayProperty>(Stack.MostRecentProperty);
|
||||
if (!ArrayProperty)
|
||||
{
|
||||
Stack.bArrayContextFailed = true;
|
||||
return;
|
||||
}
|
||||
P_GET_PROPERTY(FIntProperty, Index);
|
||||
P_GET_UBOOL(bLoop);
|
||||
P_FINISH;
|
||||
P_NATIVE_BEGIN;
|
||||
|
||||
*static_cast<int32*>(RESULT_PARAM) = GenericArray_NextIndex(ArrayAddr, ArrayProperty, Index, bLoop);
|
||||
P_NATIVE_END;
|
||||
}
|
||||
|
||||
DECLARE_FUNCTION(execArray_PreviousIndex)
|
||||
{
|
||||
Stack.MostRecentProperty = nullptr;
|
||||
Stack.StepCompiledIn<FArrayProperty>(nullptr);
|
||||
const void* ArrayAddr = Stack.MostRecentPropertyAddress;
|
||||
const FArrayProperty* ArrayProperty = CastField<FArrayProperty>(Stack.MostRecentProperty);
|
||||
if (!ArrayProperty)
|
||||
{
|
||||
Stack.bArrayContextFailed = true;
|
||||
return;
|
||||
}
|
||||
P_GET_PROPERTY(FIntProperty, Index);
|
||||
P_GET_UBOOL(bLoop);
|
||||
P_FINISH;
|
||||
P_NATIVE_BEGIN;
|
||||
|
||||
*static_cast<int32*>(RESULT_PARAM) = GenericArray_PreviousIndex(ArrayAddr, ArrayProperty, Index, bLoop);
|
||||
P_NATIVE_END;
|
||||
}
|
||||
|
||||
DECLARE_FUNCTION(execArray_RemoveDuplicates)
|
||||
{
|
||||
Stack.MostRecentProperty = nullptr;
|
||||
Stack.StepCompiledIn<FArrayProperty>(nullptr);
|
||||
void* ArrayAddr = Stack.MostRecentPropertyAddress;
|
||||
FArrayProperty* ArrayProperty = CastField<FArrayProperty>(Stack.MostRecentProperty);
|
||||
if (!ArrayProperty)
|
||||
{
|
||||
Stack.bArrayContextFailed = true;
|
||||
return;
|
||||
}
|
||||
P_FINISH;
|
||||
P_NATIVE_BEGIN;
|
||||
MARK_PROPERTY_DIRTY(Stack.Object, ArrayProperty);
|
||||
GenericArray_RemoveDuplicates(ArrayAddr, ArrayProperty);
|
||||
P_NATIVE_END;
|
||||
}
|
||||
|
||||
DECLARE_FUNCTION(execArray_GetValidFirstItemCopy)
|
||||
{
|
||||
Stack.MostRecentProperty = nullptr;
|
||||
Stack.StepCompiledIn<FArrayProperty>(nullptr);
|
||||
const void* ArrayAddr = Stack.MostRecentPropertyAddress;
|
||||
const 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);
|
||||
void* ItemPtr;
|
||||
if (Stack.MostRecentPropertyAddress != nullptr && Stack.MostRecentProperty != nullptr
|
||||
&& PropertySize == Stack.MostRecentProperty->GetElementSize() * Stack.MostRecentProperty->ArrayDim
|
||||
&& (Stack.MostRecentProperty->GetClass()->IsChildOf(InnerProp->GetClass())
|
||||
|| InnerProp->GetClass()->IsChildOf(Stack.MostRecentProperty->GetClass())))
|
||||
{
|
||||
ItemPtr = Stack.MostRecentPropertyAddress;
|
||||
}
|
||||
else
|
||||
{
|
||||
ItemPtr = StorageSpace;
|
||||
}
|
||||
|
||||
P_FINISH;
|
||||
P_NATIVE_BEGIN;
|
||||
*static_cast<bool*>(RESULT_PARAM) = GenericArray_GetFirstItem(ArrayAddr, ArrayProperty, ItemPtr);
|
||||
P_NATIVE_END;
|
||||
InnerProp->DestroyValue(StorageSpace);
|
||||
}
|
||||
|
||||
DECLARE_FUNCTION(execArray_GetValidLastItemCopy)
|
||||
{
|
||||
Stack.MostRecentProperty = nullptr;
|
||||
Stack.StepCompiledIn<FArrayProperty>(nullptr);
|
||||
const void* ArrayAddr = Stack.MostRecentPropertyAddress;
|
||||
const 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);
|
||||
void* ItemPtr;
|
||||
if (Stack.MostRecentPropertyAddress != nullptr && Stack.MostRecentProperty != nullptr
|
||||
&& PropertySize == Stack.MostRecentProperty->GetElementSize() * Stack.MostRecentProperty->ArrayDim
|
||||
&& (Stack.MostRecentProperty->GetClass()->IsChildOf(InnerProp->GetClass())
|
||||
|| InnerProp->GetClass()->IsChildOf(Stack.MostRecentProperty->GetClass())))
|
||||
{
|
||||
ItemPtr = Stack.MostRecentPropertyAddress;
|
||||
}
|
||||
else
|
||||
{
|
||||
ItemPtr = StorageSpace;
|
||||
}
|
||||
|
||||
P_FINISH;
|
||||
P_NATIVE_BEGIN;
|
||||
*static_cast<bool*>(RESULT_PARAM) = GenericArray_GetLastItem(ArrayAddr, ArrayProperty, ItemPtr);
|
||||
P_NATIVE_END;
|
||||
InnerProp->DestroyValue(StorageSpace);
|
||||
}
|
||||
|
||||
DECLARE_FUNCTION(execArray_GetValidItemFromIndexCopy)
|
||||
{
|
||||
Stack.MostRecentProperty = nullptr;
|
||||
Stack.StepCompiledIn<FArrayProperty>(nullptr);
|
||||
const void* ArrayAddr = Stack.MostRecentPropertyAddress;
|
||||
const FArrayProperty* ArrayProperty = CastField<FArrayProperty>(Stack.MostRecentProperty);
|
||||
if (!ArrayProperty)
|
||||
{
|
||||
Stack.bArrayContextFailed = true;
|
||||
return;
|
||||
}
|
||||
P_GET_PROPERTY(FIntProperty, Index);
|
||||
|
||||
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);
|
||||
void* ItemPtr;
|
||||
if (Stack.MostRecentPropertyAddress != nullptr && Stack.MostRecentProperty != nullptr
|
||||
&& PropertySize == Stack.MostRecentProperty->GetElementSize() * Stack.MostRecentProperty->ArrayDim
|
||||
&& (Stack.MostRecentProperty->GetClass()->IsChildOf(InnerProp->GetClass())
|
||||
|| InnerProp->GetClass()->IsChildOf(Stack.MostRecentProperty->GetClass())))
|
||||
{
|
||||
ItemPtr = Stack.MostRecentPropertyAddress;
|
||||
}
|
||||
else
|
||||
{
|
||||
ItemPtr = StorageSpace;
|
||||
}
|
||||
|
||||
P_FINISH;
|
||||
P_NATIVE_BEGIN;
|
||||
*static_cast<bool*>(RESULT_PARAM) = GenericArray_GetItemAtIndex(ArrayAddr, ArrayProperty, Index, ItemPtr);
|
||||
P_NATIVE_END;
|
||||
InnerProp->DestroyValue(StorageSpace);
|
||||
}
|
||||
|
||||
DECLARE_FUNCTION(execArray_GetRandomItem)
|
||||
{
|
||||
Stack.MostRecentProperty = nullptr;
|
||||
Stack.StepCompiledIn<FArrayProperty>(nullptr);
|
||||
const void* ArrayAddr = Stack.MostRecentPropertyAddress;
|
||||
const 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);
|
||||
void* ItemPtr;
|
||||
if (Stack.MostRecentPropertyAddress != nullptr && Stack.MostRecentProperty != nullptr
|
||||
&& PropertySize == Stack.MostRecentProperty->GetElementSize() * Stack.MostRecentProperty->ArrayDim
|
||||
&& (Stack.MostRecentProperty->GetClass()->IsChildOf(InnerProp->GetClass())
|
||||
|| InnerProp->GetClass()->IsChildOf(Stack.MostRecentProperty->GetClass())))
|
||||
{
|
||||
ItemPtr = Stack.MostRecentPropertyAddress;
|
||||
}
|
||||
else
|
||||
{
|
||||
ItemPtr = StorageSpace;
|
||||
}
|
||||
|
||||
Stack.MostRecentProperty = nullptr;
|
||||
Stack.MostRecentPropertyAddress = nullptr;
|
||||
Stack.StepCompiledIn<FProperty>(nullptr);
|
||||
int32* OutIndex = reinterpret_cast<int32*>(Stack.MostRecentPropertyAddress);
|
||||
|
||||
P_FINISH;
|
||||
P_NATIVE_BEGIN;
|
||||
*static_cast<bool*>(RESULT_PARAM) = GenericArray_GetRandomItem(ArrayAddr, ArrayProperty, ItemPtr, OutIndex);
|
||||
P_NATIVE_END;
|
||||
InnerProp->DestroyValue(StorageSpace);
|
||||
}
|
||||
|
||||
DECLARE_FUNCTION(execArray_LastValue)
|
||||
{
|
||||
Stack.MostRecentProperty = nullptr;
|
||||
Stack.StepCompiledIn<FArrayProperty>(nullptr);
|
||||
const void* ArrayAddr = Stack.MostRecentPropertyAddress;
|
||||
const 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);
|
||||
void* ItemPtr;
|
||||
if (Stack.MostRecentPropertyAddress != nullptr && Stack.MostRecentProperty != nullptr
|
||||
&& PropertySize == Stack.MostRecentProperty->GetElementSize() * Stack.MostRecentProperty->ArrayDim
|
||||
&& (Stack.MostRecentProperty->GetClass()->IsChildOf(InnerProp->GetClass())
|
||||
|| InnerProp->GetClass()->IsChildOf(Stack.MostRecentProperty->GetClass())))
|
||||
{
|
||||
ItemPtr = Stack.MostRecentPropertyAddress;
|
||||
}
|
||||
else
|
||||
{
|
||||
ItemPtr = StorageSpace;
|
||||
}
|
||||
|
||||
P_FINISH;
|
||||
P_NATIVE_BEGIN;
|
||||
GenericArray_GetLastItem(ArrayAddr, ArrayProperty, ItemPtr);
|
||||
P_NATIVE_END;
|
||||
InnerProp->DestroyValue(StorageSpace);
|
||||
}
|
||||
|
||||
DECLARE_FUNCTION(execArray_Pop)
|
||||
{
|
||||
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);
|
||||
void* ItemPtr;
|
||||
if (Stack.MostRecentPropertyAddress != nullptr && Stack.MostRecentProperty != nullptr
|
||||
&& PropertySize == Stack.MostRecentProperty->GetElementSize() * Stack.MostRecentProperty->ArrayDim
|
||||
&& (Stack.MostRecentProperty->GetClass()->IsChildOf(InnerProp->GetClass())
|
||||
|| InnerProp->GetClass()->IsChildOf(Stack.MostRecentProperty->GetClass())))
|
||||
{
|
||||
ItemPtr = Stack.MostRecentPropertyAddress;
|
||||
}
|
||||
else
|
||||
{
|
||||
ItemPtr = StorageSpace;
|
||||
}
|
||||
|
||||
P_FINISH;
|
||||
P_NATIVE_BEGIN;
|
||||
MARK_PROPERTY_DIRTY(Stack.Object, ArrayProperty);
|
||||
*static_cast<bool*>(RESULT_PARAM) = GenericArray_Pop(ArrayAddr, ArrayProperty, ItemPtr);
|
||||
P_NATIVE_END;
|
||||
InnerProp->DestroyValue(StorageSpace);
|
||||
}
|
||||
|
||||
DECLARE_FUNCTION(execArray_PopFirst)
|
||||
{
|
||||
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);
|
||||
void* ItemPtr;
|
||||
if (Stack.MostRecentPropertyAddress != nullptr && Stack.MostRecentProperty != nullptr
|
||||
&& PropertySize == Stack.MostRecentProperty->GetElementSize() * Stack.MostRecentProperty->ArrayDim
|
||||
&& (Stack.MostRecentProperty->GetClass()->IsChildOf(InnerProp->GetClass())
|
||||
|| InnerProp->GetClass()->IsChildOf(Stack.MostRecentProperty->GetClass())))
|
||||
{
|
||||
ItemPtr = Stack.MostRecentPropertyAddress;
|
||||
}
|
||||
else
|
||||
{
|
||||
ItemPtr = StorageSpace;
|
||||
}
|
||||
|
||||
P_FINISH;
|
||||
P_NATIVE_BEGIN;
|
||||
MARK_PROPERTY_DIRTY(Stack.Object, ArrayProperty);
|
||||
*static_cast<bool*>(RESULT_PARAM) = GenericArray_PopFirst(ArrayAddr, ArrayProperty, ItemPtr);
|
||||
P_NATIVE_END;
|
||||
InnerProp->DestroyValue(StorageSpace);
|
||||
}
|
||||
|
||||
DECLARE_FUNCTION(execArray_RemoveAtSwap)
|
||||
{
|
||||
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_PROPERTY(FIntProperty, Index);
|
||||
P_FINISH;
|
||||
P_NATIVE_BEGIN;
|
||||
MARK_PROPERTY_DIRTY(Stack.Object, ArrayProperty);
|
||||
*static_cast<bool*>(RESULT_PARAM) = GenericArray_RemoveAtSwap(ArrayAddr, ArrayProperty, Index);
|
||||
P_NATIVE_END;
|
||||
}
|
||||
|
||||
DECLARE_FUNCTION(execArray_Slice)
|
||||
{
|
||||
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, StartIndex);
|
||||
P_GET_PROPERTY(FIntProperty, Count);
|
||||
|
||||
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_Slice(SourceArrayAddr, SourceArrayProperty, StartIndex, Count, OutArrayAddr, OutArrayProperty);
|
||||
P_NATIVE_END;
|
||||
}
|
||||
|
||||
DECLARE_FUNCTION(execArray_Rotate)
|
||||
{
|
||||
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_PROPERTY(FIntProperty, Shift);
|
||||
P_FINISH;
|
||||
P_NATIVE_BEGIN;
|
||||
MARK_PROPERTY_DIRTY(Stack.Object, ArrayProperty);
|
||||
GenericArray_Rotate(ArrayAddr, ArrayProperty, Shift);
|
||||
P_NATIVE_END;
|
||||
}
|
||||
|
||||
DECLARE_FUNCTION(execArray_GetDistinct)
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
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_GetDistinct(SourceArrayAddr, SourceArrayProperty, OutArrayAddr, OutArrayProperty);
|
||||
P_NATIVE_END;
|
||||
}
|
||||
|
||||
DECLARE_FUNCTION(execArray_CountOccurrences)
|
||||
{
|
||||
Stack.MostRecentProperty = nullptr;
|
||||
Stack.StepCompiledIn<FArrayProperty>(nullptr);
|
||||
const void* ArrayAddr = Stack.MostRecentPropertyAddress;
|
||||
const 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;
|
||||
P_NATIVE_BEGIN;
|
||||
*static_cast<int32*>(RESULT_PARAM) = GenericArray_CountOccurrences(ArrayAddr, ArrayProperty, StorageSpace);
|
||||
P_NATIVE_END;
|
||||
InnerProp->DestroyValue(StorageSpace);
|
||||
}
|
||||
|
||||
DECLARE_FUNCTION(execArray_GetMostCommon)
|
||||
{
|
||||
Stack.MostRecentProperty = nullptr;
|
||||
Stack.StepCompiledIn<FArrayProperty>(nullptr);
|
||||
const void* ArrayAddr = Stack.MostRecentPropertyAddress;
|
||||
const 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);
|
||||
void* ItemPtr;
|
||||
if (Stack.MostRecentPropertyAddress != nullptr && Stack.MostRecentProperty != nullptr
|
||||
&& PropertySize == Stack.MostRecentProperty->GetElementSize() * Stack.MostRecentProperty->ArrayDim
|
||||
&& (Stack.MostRecentProperty->GetClass()->IsChildOf(InnerProp->GetClass())
|
||||
|| InnerProp->GetClass()->IsChildOf(Stack.MostRecentProperty->GetClass())))
|
||||
{
|
||||
ItemPtr = Stack.MostRecentPropertyAddress;
|
||||
}
|
||||
else
|
||||
{
|
||||
ItemPtr = StorageSpace;
|
||||
}
|
||||
|
||||
Stack.MostRecentProperty = nullptr;
|
||||
Stack.MostRecentPropertyAddress = nullptr;
|
||||
Stack.StepCompiledIn<FProperty>(nullptr);
|
||||
int32* OutCount = reinterpret_cast<int32*>(Stack.MostRecentPropertyAddress);
|
||||
|
||||
P_FINISH;
|
||||
P_NATIVE_BEGIN;
|
||||
*static_cast<bool*>(RESULT_PARAM) = GenericArray_GetMostCommon(ArrayAddr, ArrayProperty, ItemPtr, OutCount);
|
||||
P_NATIVE_END;
|
||||
InnerProp->DestroyValue(StorageSpace);
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,104 @@
|
||||
// Copyright (c) 2026 Unreal Directive. Licensed under the MIT License.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "Kismet/BlueprintFunctionLibrary.h"
|
||||
#include "DirectiveUtilFunctionLibrary.generated.h"
|
||||
|
||||
/**
|
||||
* UDirectiveUtilFunctionLibrary
|
||||
*
|
||||
* The primary function library for the Directive Utilities plugin.
|
||||
*/
|
||||
UCLASS()
|
||||
class DIRECTIVEUTILITIESRUNTIME_API UDirectiveUtilFunctionLibrary : public UBlueprintFunctionLibrary
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
|
||||
/**
|
||||
* Returns a list of classes derived from the given base class (not limited to Actors).
|
||||
* This exposes the built-in GetDerivedClasses function to blueprints.
|
||||
* @param BaseClass The base class to get the derived classes for.
|
||||
* @param bRecursive Whether to include derived classes of derived classes.
|
||||
* @param DerivedClasses The list of derived classes.
|
||||
*/
|
||||
UFUNCTION(BlueprintPure, Category = "Directive Utilities|Utility")
|
||||
static void GetChildClasses(const UClass* BaseClass, bool bRecursive, TArray<UClass*>& DerivedClasses);
|
||||
|
||||
/**
|
||||
* Copy the provided text to the clipboard.
|
||||
* @param Text - The text to copy to the clipboard.
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "Directive Utilities|Clipboard" )
|
||||
static void CopyTextToClipboard(const FText& Text);
|
||||
|
||||
/**
|
||||
* Copy the provided string to the clipboard.
|
||||
* @param String - The string to copy to the clipboard.
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "Directive Utilities|Clipboard" )
|
||||
static void CopyStringToClipboard(const FString& String);
|
||||
|
||||
/**
|
||||
* Get the content from the clipboard as FText.
|
||||
* @returns The text from the clipboard.
|
||||
*/
|
||||
UFUNCTION(BlueprintPure, 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" )
|
||||
static FString GetStringFromClipboard();
|
||||
|
||||
/**
|
||||
* Clear the clipboard.
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "Directive Utilities|Clipboard")
|
||||
static void ClearClipboard();
|
||||
|
||||
/**
|
||||
* Get the project version as a string.
|
||||
* @returns The project version as a string.
|
||||
*/
|
||||
UFUNCTION(BlueprintPure, Category = "Directive Utilities|Utility")
|
||||
static FString GetProjectVersion();
|
||||
|
||||
/**
|
||||
* Returns true if the game is running in the Unreal Editor.
|
||||
* @note This will return false in packaged/standalone builds.
|
||||
* @returns True if running in the editor.
|
||||
*/
|
||||
UFUNCTION(BlueprintPure, Category = "Directive Utilities|Utility")
|
||||
static bool IsRunningInEditor();
|
||||
|
||||
/**
|
||||
* Checks whether a switch (e.g. "MySwitch" matching "-MySwitch") was passed on the
|
||||
* process command line. Matching is case-insensitive.
|
||||
* @param Switch - The switch name, without the leading dash.
|
||||
* @returns True if the switch is present.
|
||||
*/
|
||||
UFUNCTION(BlueprintPure, Category = "Directive Utilities|Utility")
|
||||
static bool HasCommandLineSwitch(const FString& Switch);
|
||||
|
||||
/**
|
||||
* Reads a key=value option (e.g. "MyKey" matching "-MyKey=Value") from the process
|
||||
* command line. Matching is case-insensitive; quoted values are returned without the quotes.
|
||||
* @param Key - The key name, without the leading dash or trailing equals sign.
|
||||
* @param OutValue - [out] The option's value, or empty if the key is missing.
|
||||
* @returns True if the key was present.
|
||||
*/
|
||||
UFUNCTION(BlueprintPure, Category = "Directive Utilities|Utility")
|
||||
static bool GetCommandLineOption(const FString& Key, FString& OutValue);
|
||||
|
||||
/** Core of Has Command Line Switch that checks an explicit command line. */
|
||||
static bool HasCommandLineSwitch(const TCHAR* CommandLine, const FString& Switch);
|
||||
|
||||
/** Core of Get Command Line Option that reads from an explicit command line. */
|
||||
static bool GetCommandLineOption(const TCHAR* CommandLine, const FString& Key, FString& OutValue);
|
||||
};
|
||||
@@ -0,0 +1,124 @@
|
||||
// Copyright (c) 2026 Unreal Directive. Licensed under the MIT License.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "Kismet/BlueprintFunctionLibrary.h"
|
||||
#include "GameplayTagContainer.h"
|
||||
#include "DirectiveUtilGameplayTagFunctionLibrary.generated.h"
|
||||
|
||||
/**
|
||||
* UDirectiveUtilGameplayTagFunctionLibrary
|
||||
* Hierarchy navigation helpers for Gameplay Tags that the engine does not expose to Blueprints.
|
||||
*/
|
||||
UCLASS()
|
||||
class DIRECTIVEUTILITIESRUNTIME_API UDirectiveUtilGameplayTagFunctionLibrary : public UBlueprintFunctionLibrary
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
|
||||
/**
|
||||
* Returns the direct (immediate) parent of a tag, e.g. "A.B.C" returns "A.B".
|
||||
* @param Tag - The tag to read.
|
||||
* @returns The direct parent tag, or an invalid tag if the tag is a root or invalid.
|
||||
*/
|
||||
UFUNCTION(BlueprintPure, Category = "Directive Utilities|GameplayTags")
|
||||
static FGameplayTag GetTagDirectParent(const FGameplayTag& Tag);
|
||||
|
||||
/**
|
||||
* Returns all ancestor tags of a tag (its parents, grandparents, etc.), excluding the tag itself.
|
||||
* e.g. "A.B.C" returns { "A.B", "A" }.
|
||||
* @param Tag - The tag to read.
|
||||
* @returns A container of the tag's ancestors.
|
||||
*/
|
||||
UFUNCTION(BlueprintPure, Category = "Directive Utilities|GameplayTags", meta = (BlueprintThreadSafe))
|
||||
static FGameplayTagContainer GetTagParents(const FGameplayTag& Tag);
|
||||
|
||||
/**
|
||||
* Returns the number of segments (hierarchy depth) of a tag, e.g. "A.B.C" returns 3.
|
||||
* @param Tag - The tag to read.
|
||||
* @returns The depth, or 0 for an invalid tag.
|
||||
*/
|
||||
UFUNCTION(BlueprintPure, Category = "Directive Utilities|GameplayTags", meta = (BlueprintThreadSafe))
|
||||
static int32 GetTagDepth(const FGameplayTag& Tag);
|
||||
|
||||
/**
|
||||
* Returns the last (leaf) segment of a tag's name, e.g. "A.B.C" returns "C".
|
||||
* @param Tag - The tag to read.
|
||||
* @returns The leaf segment, or an empty string for an invalid tag.
|
||||
*/
|
||||
UFUNCTION(BlueprintPure, Category = "Directive Utilities|GameplayTags", meta = (BlueprintThreadSafe))
|
||||
static FString GetTagLeafName(const FGameplayTag& Tag);
|
||||
|
||||
/**
|
||||
* Splits a tag's name into its individual segments, e.g. "A.B.C" returns [ "A", "B", "C" ].
|
||||
* @param Tag - The tag to read.
|
||||
* @returns The segments in order, or an empty array for an invalid tag.
|
||||
*/
|
||||
UFUNCTION(BlueprintPure, Category = "Directive Utilities|GameplayTags", meta = (BlueprintThreadSafe))
|
||||
static TArray<FString> GetTagSegments(const FGameplayTag& Tag);
|
||||
|
||||
/**
|
||||
* Returns all registered descendant tags of a tag (children, grandchildren, etc.), excluding the tag itself.
|
||||
* e.g. "A" returns { "A.B", "A.B.C" } when those tags are registered.
|
||||
* @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))
|
||||
static FGameplayTagContainer GetTagChildren(const FGameplayTag& Tag);
|
||||
|
||||
/**
|
||||
* Returns only the direct (immediate) registered children of a tag, e.g. "A" returns "A.B" but not "A.B.C".
|
||||
* @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))
|
||||
static FGameplayTagContainer GetTagDirectChildren(const FGameplayTag& Tag);
|
||||
|
||||
/**
|
||||
* Returns the deepest tag that both tags share as an ancestor, e.g. "A.B.C" and "A.B.D" return "A.B".
|
||||
* The result may be one of the inputs when one tag is an ancestor of the other.
|
||||
* @param TagA - The first tag.
|
||||
* @param TagB - The second tag.
|
||||
* @returns The deepest common ancestor tag, or an invalid tag if the tags share none.
|
||||
*/
|
||||
UFUNCTION(BlueprintPure, Category = "Directive Utilities|GameplayTags")
|
||||
static FGameplayTag GetTagCommonAncestor(const FGameplayTag& TagA, const FGameplayTag& TagB);
|
||||
|
||||
/**
|
||||
* Truncates a tag to its first Depth segments, e.g. "A.B.C" at depth 2 returns "A.B".
|
||||
* An ancestor of a registered tag is always registered itself.
|
||||
* @param Tag - The tag to truncate.
|
||||
* @param Depth - The number of leading segments to keep.
|
||||
* @returns The truncated tag, the tag itself when Depth >= its depth, or an invalid tag when Depth < 1 or the tag is invalid.
|
||||
*/
|
||||
UFUNCTION(BlueprintPure, Category = "Directive Utilities|GameplayTags")
|
||||
static FGameplayTag GetTagAtDepth(const FGameplayTag& Tag, int32 Depth);
|
||||
|
||||
/**
|
||||
* Returns the registered tags that share a tag's direct parent, excluding the tag itself.
|
||||
* Enumerating the siblings of a root tag (which has no parent) is not supported and returns an empty container.
|
||||
* @param Tag - The tag to read.
|
||||
* @returns A container of the tag's siblings, or an empty container for an invalid or root tag.
|
||||
*/
|
||||
UFUNCTION(BlueprintPure, Category = "Directive Utilities|GameplayTags")
|
||||
static FGameplayTagContainer GetTagSiblings(const FGameplayTag& Tag);
|
||||
|
||||
/**
|
||||
* Checks whether a tag has no registered children.
|
||||
* @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))
|
||||
static bool IsLeafTag(const FGameplayTag& Tag);
|
||||
|
||||
/**
|
||||
* Finds every registered gameplay tag whose name contains Substring (case-insensitive), in registry order.
|
||||
* Cost scales with the size of the tag registry; intended for tooling and debug use, not per-frame calls.
|
||||
* @param Substring - The text to search for. An empty string returns an empty array.
|
||||
* @returns The matching tags.
|
||||
*/
|
||||
UFUNCTION(BlueprintPure, Category = "Directive Utilities|GameplayTags")
|
||||
static TArray<FGameplayTag> FindRegisteredTags(const FString& Substring);
|
||||
};
|
||||
@@ -0,0 +1,101 @@
|
||||
// Copyright (c) 2026 Unreal Directive. Licensed under the MIT License.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "Kismet/BlueprintFunctionLibrary.h"
|
||||
#include "Types/DirectiveUtilInputTypes.h"
|
||||
#include "Types/DirectiveUtilTypes.h"
|
||||
#include "DirectiveUtilInputFunctionLibrary.generated.h"
|
||||
|
||||
class UEnhancedInputLocalPlayerSubsystem;
|
||||
/**
|
||||
* UDirectiveUtilInputFunctionLibrary
|
||||
* A function library that adds functionality for working with input in Unreal Engine.
|
||||
*/
|
||||
UCLASS()
|
||||
class DIRECTIVEUTILITIESRUNTIME_API UDirectiveUtilInputFunctionLibrary : public UBlueprintFunctionLibrary
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
|
||||
/**
|
||||
* Apply multiple Input Mapping Contexts.
|
||||
* @param PlayerController The player controller to add the contexts to. Will attempt to get the LocalPlayer from the controller.
|
||||
* @param Contexts The contexts to apply.
|
||||
* @param bClearPrevious Whether to clear all previous contexts before applying the new ones.
|
||||
* @returns Returns Success if the contexts were successfully applied.
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "Directive Utilities|Input", meta=(ExpandEnumAsExecs="ReturnValue", DefaultToSelf="PlayerController"))
|
||||
static EDirectiveUtilSuccessStatus AddInputMappingContexts(
|
||||
AController* PlayerController,
|
||||
const TArray<FDirectiveUtilEnhancedInputContextData>& Contexts,
|
||||
bool bClearPrevious);
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* @returns Returns Success if the contexts were successfully removed.
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "Directive Utilities|Input", meta=(ExpandEnumAsExecs="ReturnValue", DefaultToSelf="PlayerController"))
|
||||
static EDirectiveUtilSuccessStatus RemoveInputMappingContexts(
|
||||
AController* PlayerController,
|
||||
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"))
|
||||
static EDirectiveUtilSuccessStatus SwapInputMappingContexts(
|
||||
AController* PlayerController,
|
||||
TSoftObjectPtr<UInputMappingContext> PreviousContext,
|
||||
TSoftObjectPtr<UInputMappingContext> NewContext,
|
||||
int32 Priority,
|
||||
bool bUsePreviousPriority);
|
||||
|
||||
/**
|
||||
* Returns the Enhanced Input local player subsystem for the given controller, if available.
|
||||
* @param PlayerController - The player controller; the Local Player's subsystem is retrieved.
|
||||
* @returns The Enhanced Input subsystem, or null if the controller has no associated local player subsystem.
|
||||
*/
|
||||
UFUNCTION(BlueprintPure, Category = "Directive Utilities|Input", meta=(DefaultToSelf="PlayerController"))
|
||||
static UEnhancedInputLocalPlayerSubsystem* GetEnhancedInputSubsystem(AController* PlayerController);
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* @returns True if the context is currently applied.
|
||||
*/
|
||||
UFUNCTION(BlueprintPure, Category = "Directive Utilities|Input", meta=(DefaultToSelf="PlayerController"))
|
||||
static bool IsInputMappingContextActive(AController* PlayerController, TSoftObjectPtr<UInputMappingContext> Context);
|
||||
|
||||
/**
|
||||
* Removes all input mapping contexts from the controller.
|
||||
* @param PlayerController - The player controller to clear.
|
||||
* @returns Success if the contexts were cleared.
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "Directive Utilities|Input", meta=(ExpandEnumAsExecs="ReturnValue", DefaultToSelf="PlayerController"))
|
||||
static EDirectiveUtilSuccessStatus ClearAllInputMappingContexts(AController* PlayerController);
|
||||
|
||||
protected:
|
||||
/**
|
||||
* Attempt to get the Enhanced Input Subsystem from the provided controller.
|
||||
* @param PlayerController - The player controller to get the subsystem from.
|
||||
* @param EnhancedInput - The Enhanced Input Subsystem to return
|
||||
* @return True if the subsystem was found.
|
||||
*/
|
||||
static bool TryGetEnhancedInputSubsystemFromController(
|
||||
AController* PlayerController,
|
||||
UEnhancedInputLocalPlayerSubsystem*& EnhancedInput);
|
||||
};
|
||||
@@ -0,0 +1,291 @@
|
||||
// Copyright (c) 2026 Unreal Directive. Licensed under the MIT License.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "Kismet/BlueprintFunctionLibrary.h"
|
||||
#include "Net/Core/PushModel/PushModel.h"
|
||||
#include "DirectiveUtilMapFunctionLibrary.generated.h"
|
||||
|
||||
/**
|
||||
* UDirectiveUtilMapFunctionLibrary
|
||||
* A collection of map (TMap) utility functions that improve the usability of maps in Blueprints.
|
||||
*/
|
||||
UCLASS()
|
||||
class DIRECTIVEUTILITIESRUNTIME_API UDirectiveUtilMapFunctionLibrary : public UBlueprintFunctionLibrary
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
|
||||
/**
|
||||
* Finds the value associated with Key, adding a new default-constructed entry if the key is not present.
|
||||
* @param TargetMap - The map to search or add to.
|
||||
* @param Key - The key to look up.
|
||||
* @param Value - [out] A copy of the existing or newly added value.
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, CustomThunk, meta=(DisplayName = "Find Or Add", CompactNodeTitle = "FIND OR ADD", MapParam = "TargetMap", MapKeyParam = "Key", MapValueParam = "Value", AutoCreateRefTerm = "Key, Value"), Category="Directive Utilities|Map")
|
||||
static void Map_FindOrAdd(const TMap<int32, int32>& TargetMap, const int32& Key, int32& Value);
|
||||
|
||||
/**
|
||||
* Resets every value in the map to its default while preserving all keys.
|
||||
* @param TargetMap - The map whose values will be reset.
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, CustomThunk, meta=(DisplayName = "Clear Values", CompactNodeTitle = "CLEAR VALUES", MapParam = "TargetMap"), Category="Directive Utilities|Map")
|
||||
static void Map_ClearValues(const TMap<int32, int32>& TargetMap);
|
||||
|
||||
/**
|
||||
* Gathers every key whose value is identical to Value, in map order.
|
||||
* @param TargetMap - The map to search.
|
||||
* @param Value - The value to look for.
|
||||
* @param Keys - [out] Every key associated with Value.
|
||||
*/
|
||||
UFUNCTION(BlueprintPure, CustomThunk, meta=(DisplayName = "Get Keys By Value", CompactNodeTitle = "KEYS BY VALUE", MapParam = "TargetMap", MapValueParam = "Value", MapKeyParam = "Keys", AutoCreateRefTerm = "Value, Keys"), Category="Directive Utilities|Map")
|
||||
static void Map_GetKeysByValue(const TMap<int32, int32>& TargetMap, const int32& Value, TArray<int32>& Keys);
|
||||
|
||||
/**
|
||||
* Checks whether any value in the map is identical to Value.
|
||||
* @param TargetMap - The map to search.
|
||||
* @param Value - The value to look for.
|
||||
* @returns True if at least one entry holds Value.
|
||||
*/
|
||||
UFUNCTION(BlueprintPure, CustomThunk, meta=(DisplayName = "Has Value", CompactNodeTitle = "HAS VALUE", MapParam = "TargetMap", MapValueParam = "Value", AutoCreateRefTerm = "Value"), Category="Directive Utilities|Map")
|
||||
static bool Map_HasValue(const TMap<int32, int32>& TargetMap, const int32& Value);
|
||||
|
||||
/**
|
||||
* Removes every key in Keys from the map.
|
||||
* @param TargetMap - The map to remove from.
|
||||
* @param Keys - The keys to remove.
|
||||
* @returns The number of entries that were actually removed.
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, CustomThunk, meta=(DisplayName = "Remove Keys", CompactNodeTitle = "REMOVE KEYS", MapParam = "TargetMap", MapKeyParam = "Keys", AutoCreateRefTerm = "Keys"), Category="Directive Utilities|Map")
|
||||
static int32 Map_RemoveKeys(const TMap<int32, int32>& TargetMap, const TArray<int32>& Keys);
|
||||
|
||||
/**
|
||||
* Copies every pair from SourceMap into TargetMap. Both maps must share key and value types.
|
||||
* @param TargetMap - The map to copy into.
|
||||
* @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")
|
||||
static void Map_Append(const TMap<int32, int32>& TargetMap, const TMap<int32, int32>& SourceMap, bool bOverwriteExisting = true);
|
||||
|
||||
/*~
|
||||
* Native functions called by the custom thunk layers below, which read off the property address
|
||||
* and operate on the underlying map. Based off UBlueprintMapLibrary implementation.
|
||||
~*/
|
||||
|
||||
static void GenericMap_FindOrAdd(const void* TargetMap, const FMapProperty* MapProperty, const void* KeyPtr, void* ValuePtr);
|
||||
static void GenericMap_ClearValues(const void* TargetMap, const FMapProperty* MapProperty);
|
||||
static void GenericMap_GetKeysByValue(const void* TargetMap, const FMapProperty* MapProperty, const void* ValuePtr, const void* TargetArray, const FArrayProperty* ArrayProperty);
|
||||
static bool GenericMap_HasValue(const void* TargetMap, const FMapProperty* MapProperty, const void* ValuePtr);
|
||||
static int32 GenericMap_RemoveKeys(const void* TargetMap, const FMapProperty* MapProperty, const void* TargetArray, const FArrayProperty* ArrayProperty);
|
||||
static void GenericMap_Append(const void* TargetMap, const FMapProperty* TargetMapProperty, const void* SourceMap, const FMapProperty* SourceMapProperty, bool bOverwriteExisting);
|
||||
|
||||
/*~
|
||||
* Custom thunk layers that read off the property address and call the appropriate native handler.
|
||||
* Based off UBlueprintMapLibrary implementation.
|
||||
~*/
|
||||
|
||||
DECLARE_FUNCTION(execMap_FindOrAdd)
|
||||
{
|
||||
Stack.MostRecentProperty = nullptr;
|
||||
Stack.StepCompiledIn<FMapProperty>(nullptr);
|
||||
void* MapAddr = Stack.MostRecentPropertyAddress;
|
||||
FMapProperty* MapProperty = CastField<FMapProperty>(Stack.MostRecentProperty);
|
||||
if (!MapProperty)
|
||||
{
|
||||
Stack.bArrayContextFailed = true;
|
||||
return;
|
||||
}
|
||||
|
||||
const FProperty* CurrKeyProp = MapProperty->KeyProp;
|
||||
const int32 KeyPropertySize = CurrKeyProp->GetElementSize() * CurrKeyProp->ArrayDim;
|
||||
void* KeyStorageSpace = FMemory_Alloca(KeyPropertySize);
|
||||
CurrKeyProp->InitializeValue(KeyStorageSpace);
|
||||
|
||||
Stack.MostRecentPropertyAddress = nullptr;
|
||||
Stack.MostRecentPropertyContainer = nullptr;
|
||||
Stack.StepCompiledIn<FProperty>(KeyStorageSpace);
|
||||
|
||||
const FProperty* CurrValueProp = MapProperty->ValueProp;
|
||||
const int32 ValuePropertySize = CurrValueProp->GetElementSize() * CurrValueProp->ArrayDim;
|
||||
void* ValueStorageSpace = FMemory_Alloca(ValuePropertySize);
|
||||
CurrValueProp->InitializeValue(ValueStorageSpace);
|
||||
|
||||
Stack.MostRecentPropertyAddress = nullptr;
|
||||
Stack.MostRecentPropertyContainer = nullptr;
|
||||
Stack.StepCompiledIn<FProperty>(ValueStorageSpace);
|
||||
void* ItemPtr;
|
||||
if (Stack.MostRecentPropertyAddress != nullptr && Stack.MostRecentProperty != nullptr
|
||||
&& ValuePropertySize == Stack.MostRecentProperty->GetElementSize() * Stack.MostRecentProperty->ArrayDim
|
||||
&& (Stack.MostRecentProperty->GetClass()->IsChildOf(CurrValueProp->GetClass())
|
||||
|| CurrValueProp->GetClass()->IsChildOf(Stack.MostRecentProperty->GetClass())))
|
||||
{
|
||||
ItemPtr = Stack.MostRecentPropertyAddress;
|
||||
}
|
||||
else
|
||||
{
|
||||
ItemPtr = ValueStorageSpace;
|
||||
}
|
||||
|
||||
P_FINISH;
|
||||
P_NATIVE_BEGIN;
|
||||
MARK_PROPERTY_DIRTY(Stack.Object, MapProperty);
|
||||
GenericMap_FindOrAdd(MapAddr, MapProperty, KeyStorageSpace, ItemPtr);
|
||||
P_NATIVE_END;
|
||||
|
||||
CurrValueProp->DestroyValue(ValueStorageSpace);
|
||||
CurrKeyProp->DestroyValue(KeyStorageSpace);
|
||||
}
|
||||
|
||||
DECLARE_FUNCTION(execMap_ClearValues)
|
||||
{
|
||||
Stack.MostRecentProperty = nullptr;
|
||||
Stack.StepCompiledIn<FMapProperty>(nullptr);
|
||||
void* MapAddr = Stack.MostRecentPropertyAddress;
|
||||
FMapProperty* MapProperty = CastField<FMapProperty>(Stack.MostRecentProperty);
|
||||
if (!MapProperty)
|
||||
{
|
||||
Stack.bArrayContextFailed = true;
|
||||
return;
|
||||
}
|
||||
|
||||
P_FINISH;
|
||||
P_NATIVE_BEGIN;
|
||||
MARK_PROPERTY_DIRTY(Stack.Object, MapProperty);
|
||||
GenericMap_ClearValues(MapAddr, MapProperty);
|
||||
P_NATIVE_END;
|
||||
}
|
||||
|
||||
DECLARE_FUNCTION(execMap_GetKeysByValue)
|
||||
{
|
||||
Stack.MostRecentProperty = nullptr;
|
||||
Stack.StepCompiledIn<FMapProperty>(nullptr);
|
||||
void* MapAddr = Stack.MostRecentPropertyAddress;
|
||||
FMapProperty* MapProperty = CastField<FMapProperty>(Stack.MostRecentProperty);
|
||||
if (!MapProperty)
|
||||
{
|
||||
Stack.bArrayContextFailed = true;
|
||||
return;
|
||||
}
|
||||
|
||||
const FProperty* CurrValueProp = MapProperty->ValueProp;
|
||||
const int32 ValuePropertySize = CurrValueProp->GetElementSize() * CurrValueProp->ArrayDim;
|
||||
void* ValueStorageSpace = FMemory_Alloca(ValuePropertySize);
|
||||
CurrValueProp->InitializeValue(ValueStorageSpace);
|
||||
|
||||
Stack.MostRecentPropertyAddress = nullptr;
|
||||
Stack.MostRecentPropertyContainer = nullptr;
|
||||
Stack.StepCompiledIn<FProperty>(ValueStorageSpace);
|
||||
|
||||
Stack.MostRecentProperty = nullptr;
|
||||
Stack.StepCompiledIn<FArrayProperty>(nullptr);
|
||||
void* ArrayAddr = Stack.MostRecentPropertyAddress;
|
||||
FArrayProperty* ArrayProperty = CastField<FArrayProperty>(Stack.MostRecentProperty);
|
||||
if (!ArrayProperty || !ArrayProperty->Inner->SameType(MapProperty->KeyProp))
|
||||
{
|
||||
Stack.bArrayContextFailed = true;
|
||||
CurrValueProp->DestroyValue(ValueStorageSpace);
|
||||
return;
|
||||
}
|
||||
|
||||
P_FINISH;
|
||||
P_NATIVE_BEGIN;
|
||||
GenericMap_GetKeysByValue(MapAddr, MapProperty, ValueStorageSpace, ArrayAddr, ArrayProperty);
|
||||
P_NATIVE_END;
|
||||
|
||||
CurrValueProp->DestroyValue(ValueStorageSpace);
|
||||
}
|
||||
|
||||
DECLARE_FUNCTION(execMap_HasValue)
|
||||
{
|
||||
Stack.MostRecentProperty = nullptr;
|
||||
Stack.StepCompiledIn<FMapProperty>(nullptr);
|
||||
void* MapAddr = Stack.MostRecentPropertyAddress;
|
||||
FMapProperty* MapProperty = CastField<FMapProperty>(Stack.MostRecentProperty);
|
||||
if (!MapProperty)
|
||||
{
|
||||
Stack.bArrayContextFailed = true;
|
||||
return;
|
||||
}
|
||||
|
||||
const FProperty* CurrValueProp = MapProperty->ValueProp;
|
||||
const int32 ValuePropertySize = CurrValueProp->GetElementSize() * CurrValueProp->ArrayDim;
|
||||
void* ValueStorageSpace = FMemory_Alloca(ValuePropertySize);
|
||||
CurrValueProp->InitializeValue(ValueStorageSpace);
|
||||
|
||||
Stack.MostRecentPropertyAddress = nullptr;
|
||||
Stack.MostRecentPropertyContainer = nullptr;
|
||||
Stack.StepCompiledIn<FProperty>(ValueStorageSpace);
|
||||
|
||||
P_FINISH;
|
||||
P_NATIVE_BEGIN;
|
||||
*static_cast<bool*>(RESULT_PARAM) = GenericMap_HasValue(MapAddr, MapProperty, ValueStorageSpace);
|
||||
P_NATIVE_END;
|
||||
|
||||
CurrValueProp->DestroyValue(ValueStorageSpace);
|
||||
}
|
||||
|
||||
DECLARE_FUNCTION(execMap_RemoveKeys)
|
||||
{
|
||||
Stack.MostRecentProperty = nullptr;
|
||||
Stack.StepCompiledIn<FMapProperty>(nullptr);
|
||||
void* MapAddr = Stack.MostRecentPropertyAddress;
|
||||
FMapProperty* MapProperty = CastField<FMapProperty>(Stack.MostRecentProperty);
|
||||
if (!MapProperty)
|
||||
{
|
||||
Stack.bArrayContextFailed = true;
|
||||
return;
|
||||
}
|
||||
|
||||
Stack.MostRecentProperty = nullptr;
|
||||
Stack.StepCompiledIn<FArrayProperty>(nullptr);
|
||||
void* ArrayAddr = Stack.MostRecentPropertyAddress;
|
||||
FArrayProperty* ArrayProperty = CastField<FArrayProperty>(Stack.MostRecentProperty);
|
||||
if (!ArrayProperty || !ArrayProperty->Inner->SameType(MapProperty->KeyProp))
|
||||
{
|
||||
Stack.bArrayContextFailed = true;
|
||||
return;
|
||||
}
|
||||
|
||||
P_FINISH;
|
||||
P_NATIVE_BEGIN;
|
||||
MARK_PROPERTY_DIRTY(Stack.Object, MapProperty);
|
||||
*static_cast<int32*>(RESULT_PARAM) = GenericMap_RemoveKeys(MapAddr, MapProperty, ArrayAddr, ArrayProperty);
|
||||
P_NATIVE_END;
|
||||
}
|
||||
|
||||
DECLARE_FUNCTION(execMap_Append)
|
||||
{
|
||||
Stack.MostRecentProperty = nullptr;
|
||||
Stack.StepCompiledIn<FMapProperty>(nullptr);
|
||||
void* TargetMapAddr = Stack.MostRecentPropertyAddress;
|
||||
FMapProperty* TargetMapProperty = CastField<FMapProperty>(Stack.MostRecentProperty);
|
||||
if (!TargetMapProperty)
|
||||
{
|
||||
Stack.bArrayContextFailed = true;
|
||||
return;
|
||||
}
|
||||
|
||||
Stack.MostRecentProperty = nullptr;
|
||||
Stack.StepCompiledIn<FMapProperty>(nullptr);
|
||||
void* SourceMapAddr = Stack.MostRecentPropertyAddress;
|
||||
FMapProperty* SourceMapProperty = CastField<FMapProperty>(Stack.MostRecentProperty);
|
||||
if (!SourceMapProperty
|
||||
|| !SourceMapProperty->KeyProp->SameType(TargetMapProperty->KeyProp)
|
||||
|| !SourceMapProperty->ValueProp->SameType(TargetMapProperty->ValueProp))
|
||||
{
|
||||
Stack.bArrayContextFailed = true;
|
||||
return;
|
||||
}
|
||||
|
||||
P_GET_UBOOL(bOverwriteExisting);
|
||||
|
||||
P_FINISH;
|
||||
P_NATIVE_BEGIN;
|
||||
MARK_PROPERTY_DIRTY(Stack.Object, TargetMapProperty);
|
||||
GenericMap_Append(TargetMapAddr, TargetMapProperty, SourceMapAddr, SourceMapProperty, bOverwriteExisting);
|
||||
P_NATIVE_END;
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,244 @@
|
||||
// Copyright (c) 2026 Unreal Directive. Licensed under the MIT License.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "Kismet/BlueprintFunctionLibrary.h"
|
||||
#include "Types/DirectiveUtilMathTypes.h"
|
||||
#include "DirectiveUtilMathFunctionLibrary.generated.h"
|
||||
|
||||
/**
|
||||
* UDirectiveUtilMathFunctionLibrary
|
||||
*
|
||||
* Contains math functions for the Directive Utilities plugin.
|
||||
*/
|
||||
UCLASS()
|
||||
class DIRECTIVEUTILITIESRUNTIME_API UDirectiveUtilMathFunctionLibrary : public UBlueprintFunctionLibrary
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
|
||||
/**
|
||||
* Returns a perlin noise value between -1 and 1 at the given position.
|
||||
* @note This exposes the built-in PerlinNoise2D function to blueprints.
|
||||
* @param Position - The position to get the noise value for.
|
||||
* @returns The noise value at the given position.
|
||||
*/
|
||||
UFUNCTION(BlueprintPure, meta = (BlueprintThreadSafe), Category = "Directive Utilities|Math|Random")
|
||||
static float PerlinNoise2D(FVector2D Position);
|
||||
|
||||
/**
|
||||
* Returns a perlin noise value between -1 and 1 at the given position.
|
||||
* @note This exposes the built-in PerlinNoise3D function to blueprints.
|
||||
* @param Position - The position to get the noise value for.
|
||||
* @returns The noise value at the given position.
|
||||
*/
|
||||
UFUNCTION(BlueprintPure, meta = (BlueprintThreadSafe), Category = "Directive Utilities|Math|Random")
|
||||
static float PerlinNoise3D(const FVector& Position);
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
UFUNCTION(BlueprintPure, meta = (BlueprintThreadSafe), Category = "Directive Utilities|Math|Vector")
|
||||
static float AngleBetweenVectors(const FVector& A, const FVector& B);
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
UFUNCTION(BlueprintPure, meta = (DisplayName = "Ease Alpha", BlueprintThreadSafe), Category = "Directive Utilities|Math|Easing")
|
||||
static float EaseAlpha(float Alpha, EDirectiveUtilEaseType EaseType);
|
||||
|
||||
/**
|
||||
* Eases a float from A to B using a Back/Elastic/Bounce easing curve.
|
||||
* @param A - The start value (returned at Alpha 0).
|
||||
* @param B - The target value (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 value between A and B.
|
||||
*/
|
||||
UFUNCTION(BlueprintPure, meta = (DisplayName = "Ease (Float)", BlueprintThreadSafe), Category = "Directive Utilities|Math|Easing")
|
||||
static float EaseFloat(float A, float B, float Alpha, EDirectiveUtilEaseType EaseType);
|
||||
|
||||
/**
|
||||
* Eases a vector from A to B using a Back/Elastic/Bounce easing curve.
|
||||
* @param A - The start vector (returned at Alpha 0).
|
||||
* @param B - The target vector (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 vector between A and B.
|
||||
*/
|
||||
UFUNCTION(BlueprintPure, meta = (DisplayName = "Ease (Vector)", BlueprintThreadSafe), Category = "Directive Utilities|Math|Easing")
|
||||
static FVector EaseVector(const FVector& A, const FVector& B, float Alpha, EDirectiveUtilEaseType EaseType);
|
||||
|
||||
/**
|
||||
* Eases a rotator from A to B using a Back/Elastic/Bounce easing curve (shortest-path interpolation).
|
||||
* @param A - The start rotator (returned at Alpha 0).
|
||||
* @param B - The target rotator (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 rotator between A and B.
|
||||
*/
|
||||
UFUNCTION(BlueprintPure, meta = (DisplayName = "Ease (Rotator)", BlueprintThreadSafe), Category = "Directive Utilities|Math|Easing")
|
||||
static FRotator EaseRotator(const FRotator& A, const FRotator& B, float Alpha, EDirectiveUtilEaseType EaseType);
|
||||
|
||||
/**
|
||||
* Eases a color from A to B using a Back/Elastic/Bounce easing curve.
|
||||
* @param A - The start color (returned at Alpha 0).
|
||||
* @param B - The target color (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 color between A and B.
|
||||
*/
|
||||
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);
|
||||
|
||||
/**
|
||||
* Rounds a float to a given number of decimal places. Rounds half away from zero,
|
||||
* matching "Round To Decimals (Text)".
|
||||
* @note Due to floating-point representation the returned value may not display exactly;
|
||||
* use "Round To Decimals (Text)" for clean display.
|
||||
* @param Value - The value to round.
|
||||
* @param Decimals - The number of decimal places to round to. Clamped to the [0, 10] range.
|
||||
* @returns The rounded value.
|
||||
*/
|
||||
UFUNCTION(BlueprintPure, meta = (DisplayName = "Round To Decimals", BlueprintThreadSafe), Category = "Directive Utilities|Math|Float")
|
||||
static float RoundToDecimals(float Value, int32 Decimals);
|
||||
|
||||
/**
|
||||
* Rounds a float to a given number of decimal places and returns it as display text.
|
||||
* Rounds half away from zero, matching "Round To Decimals".
|
||||
* @param Value - The value to round.
|
||||
* @param Decimals - The maximum number of decimal places to display. Clamped to the [0, 10] range.
|
||||
* @returns The rounded value as text, formatted with the current locale.
|
||||
*/
|
||||
UFUNCTION(BlueprintPure, meta = (DisplayName = "Round To Decimals (Text)", BlueprintThreadSafe), Category = "Directive Utilities|Math|Float")
|
||||
static FText RoundToDecimalsAsText(float Value, int32 Decimals);
|
||||
|
||||
/**
|
||||
* Formats a byte count as a human-readable size using binary units (1024): B, KB, MB, GB, TB, PB.
|
||||
* Decimals are applied only from KB up ("532 B", "1.4 MB"). Negative input formats the absolute
|
||||
* value with a leading minus sign. Output is English-only.
|
||||
* @param Bytes - The byte count to format.
|
||||
* @param Decimals - The number of decimal places to show from KB up. Clamped to the [0, 3] range.
|
||||
* @returns The formatted size text.
|
||||
*/
|
||||
UFUNCTION(BlueprintPure, Category = "Directive Utilities|Math|Formatting")
|
||||
static FText FormatBytes(int64 Bytes, int32 Decimals = 1);
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* @param Seconds - The duration in seconds.
|
||||
* @param bIncludeSeconds - Whether to include the seconds unit.
|
||||
* @returns The formatted duration text.
|
||||
*/
|
||||
UFUNCTION(BlueprintPure, Category = "Directive Utilities|Math|Formatting")
|
||||
static FText FormatDuration(float Seconds, bool bIncludeSeconds = true);
|
||||
|
||||
/**
|
||||
* Formats a timestamp relative to the current local time: "just now" (under a minute),
|
||||
* "N minute(s)/hour(s)/day(s) ago", or "in N ..." for future timestamps. Uses local time,
|
||||
* pairing with Get Save Slot Timestamp. Output is English-only.
|
||||
* @note Not pure: reads the current clock each call.
|
||||
* @param Timestamp - The local timestamp to describe.
|
||||
* @returns The formatted relative time text.
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "Directive Utilities|Math|Formatting")
|
||||
static FText FormatRelativeTime(const FDateTime& Timestamp);
|
||||
|
||||
/**
|
||||
* Returns the sum of an integer array as a 64-bit integer, so large arrays cannot overflow int32.
|
||||
* @param Values - The values to sum.
|
||||
* @returns The sum of the values, or 0 if the array is empty.
|
||||
*/
|
||||
UFUNCTION(BlueprintPure, meta = (BlueprintThreadSafe), Category = "Directive Utilities|Math|Array")
|
||||
static int64 GetIntArraySum(const TArray<int32>& Values);
|
||||
|
||||
/**
|
||||
* Returns the arithmetic mean of an integer array.
|
||||
* @param Values - The values to average.
|
||||
* @returns The average of the values, or 0 if the array is empty.
|
||||
*/
|
||||
UFUNCTION(BlueprintPure, meta = (BlueprintThreadSafe), Category = "Directive Utilities|Math|Array")
|
||||
static float GetIntArrayAverage(const TArray<int32>& Values);
|
||||
|
||||
/**
|
||||
* Returns the median of an integer array (computed on a sorted copy; the input is not modified).
|
||||
* For an even count, returns the average of the two middle values.
|
||||
* @param Values - The values to take the median of.
|
||||
* @returns The median of the values, or 0 if the array is empty.
|
||||
*/
|
||||
UFUNCTION(BlueprintPure, meta = (BlueprintThreadSafe), Category = "Directive Utilities|Math|Array")
|
||||
static float GetIntArrayMedian(const TArray<int32>& Values);
|
||||
|
||||
/**
|
||||
* Returns the population standard deviation of an integer array (divides by N, not N-1).
|
||||
* @param Values - The values to measure.
|
||||
* @returns The population standard deviation, or 0 if the array is empty.
|
||||
*/
|
||||
UFUNCTION(BlueprintPure, meta = (BlueprintThreadSafe), Category = "Directive Utilities|Math|Array")
|
||||
static float GetIntArrayStandardDeviation(const TArray<int32>& Values);
|
||||
|
||||
/**
|
||||
* Returns the sum of a float array. Accumulates in double internally for precision.
|
||||
* @param Values - The values to sum.
|
||||
* @returns The sum of the values, or 0 if the array is empty.
|
||||
*/
|
||||
UFUNCTION(BlueprintPure, meta = (BlueprintThreadSafe), Category = "Directive Utilities|Math|Array")
|
||||
static float GetFloatArraySum(const TArray<float>& Values);
|
||||
|
||||
/**
|
||||
* Returns the arithmetic mean of a float array. Accumulates in double internally for precision.
|
||||
* @param Values - The values to average.
|
||||
* @returns The average of the values, or 0 if the array is empty.
|
||||
*/
|
||||
UFUNCTION(BlueprintPure, meta = (BlueprintThreadSafe), Category = "Directive Utilities|Math|Array")
|
||||
static float GetFloatArrayAverage(const TArray<float>& Values);
|
||||
|
||||
/**
|
||||
* Returns the median of a float array (computed on a sorted copy; the input is not modified).
|
||||
* For an even count, returns the average of the two middle values.
|
||||
* @param Values - The values to take the median of.
|
||||
* @returns The median of the values, or 0 if the array is empty.
|
||||
*/
|
||||
UFUNCTION(BlueprintPure, meta = (BlueprintThreadSafe), Category = "Directive Utilities|Math|Array")
|
||||
static float GetFloatArrayMedian(const TArray<float>& Values);
|
||||
|
||||
/**
|
||||
* Returns the population standard deviation of a float array (divides by N, not N-1).
|
||||
* Accumulates in double internally for precision.
|
||||
* @param Values - The values to measure.
|
||||
* @returns The population standard deviation, or 0 if the array is empty.
|
||||
*/
|
||||
UFUNCTION(BlueprintPure, meta = (BlueprintThreadSafe), Category = "Directive Utilities|Math|Array")
|
||||
static float GetFloatArrayStandardDeviation(const TArray<float>& Values);
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* @param Weights - The per-index weights.
|
||||
* @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"), Category = "Directive Utilities|Math|Random")
|
||||
static int32 GetRandomIndexFromWeights(const TArray<float>& Weights);
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* @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);
|
||||
};
|
||||
@@ -0,0 +1,80 @@
|
||||
// Copyright (c) 2026 Unreal Directive. Licensed under the MIT License.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "Kismet/BlueprintFunctionLibrary.h"
|
||||
#include "DirectiveUtilRegexFunctionLibrary.generated.h"
|
||||
|
||||
/**
|
||||
* UDirectiveUtilRegexFunctionLibrary
|
||||
* Exposes the engine's regular-expression matching (FRegexPattern/FRegexMatcher) to Blueprints and Python.
|
||||
* @warning Complex patterns with nested quantifiers can be extremely slow on long inputs
|
||||
* (catastrophic backtracking), and matching runs synchronously on the calling thread.
|
||||
*/
|
||||
UCLASS()
|
||||
class DIRECTIVEUTILITIESRUNTIME_API UDirectiveUtilRegexFunctionLibrary : public UBlueprintFunctionLibrary
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
|
||||
/**
|
||||
* Returns true if the pattern matches anywhere within the input string.
|
||||
* @param Input - The string to search.
|
||||
* @param Pattern - The regular expression pattern.
|
||||
* @param bCaseSensitive - Whether matching is case-sensitive. Defaults to true.
|
||||
* @returns True if at least one match was found.
|
||||
*/
|
||||
UFUNCTION(BlueprintPure, Category = "Directive Utilities|Regex")
|
||||
static bool RegexMatches(const FString& Input, const FString& Pattern, bool bCaseSensitive = true);
|
||||
|
||||
/**
|
||||
* Finds the first match of the pattern within the input string.
|
||||
* @param Input - The string to search.
|
||||
* @param Pattern - The regular expression pattern.
|
||||
* @param OutMatch - [out] The matched substring, or empty if no match.
|
||||
* @param OutMatchStart - [out] The start index of the match, or INDEX_NONE if no match.
|
||||
* @param OutMatchEnd - [out] The index just past the end of the match, or INDEX_NONE if no match.
|
||||
* @param bCaseSensitive - Whether matching is case-sensitive. Defaults to true.
|
||||
* @returns True if a match was found.
|
||||
*/
|
||||
UFUNCTION(BlueprintPure, Category = "Directive Utilities|Regex")
|
||||
static bool RegexFindFirst(const FString& Input, const FString& Pattern, FString& OutMatch, int32& OutMatchStart, int32& OutMatchEnd, bool bCaseSensitive = true);
|
||||
|
||||
/**
|
||||
* Returns every (non-overlapping) match of the pattern within the input string.
|
||||
* @note Zero-width (empty) matches are ignored.
|
||||
* @param Input - The string to search.
|
||||
* @param Pattern - The regular expression pattern.
|
||||
* @param bCaseSensitive - Whether matching is case-sensitive. Defaults to true.
|
||||
* @returns The matched substrings, in order.
|
||||
*/
|
||||
UFUNCTION(BlueprintPure, Category = "Directive Utilities|Regex")
|
||||
static TArray<FString> RegexFindAll(const FString& Input, const FString& Pattern, bool bCaseSensitive = true);
|
||||
|
||||
/**
|
||||
* Replaces every match of the pattern in the input string with a literal replacement.
|
||||
* @note The replacement is literal text; capture-group back-references (e.g. $1) are not expanded.
|
||||
* @note Zero-width (empty) matches are ignored.
|
||||
* @param Input - The string to operate on.
|
||||
* @param Pattern - The regular expression pattern.
|
||||
* @param Replacement - The literal text to substitute for each match.
|
||||
* @param bCaseSensitive - Whether matching is case-sensitive. Defaults to true.
|
||||
* @returns The string with all matches replaced.
|
||||
*/
|
||||
UFUNCTION(BlueprintPure, Category = "Directive Utilities|Regex")
|
||||
static FString RegexReplaceAll(const FString& Input, const FString& Pattern, const FString& Replacement, bool bCaseSensitive = true);
|
||||
|
||||
/**
|
||||
* Returns a specific capture group from the first match of the pattern.
|
||||
* @param Input - The string to search.
|
||||
* @param Pattern - The regular expression pattern.
|
||||
* @param GroupIndex - The capture group to retrieve. 0 is the entire match; 1+ are the parenthesized groups.
|
||||
* @param OutGroup - [out] The captured substring, or empty if the group did not participate in the match.
|
||||
* @param bCaseSensitive - Whether matching is case-sensitive. Defaults to true.
|
||||
* @returns True if a match was found and the requested group participated in it.
|
||||
*/
|
||||
UFUNCTION(BlueprintPure, Category = "Directive Utilities|Regex")
|
||||
static bool RegexGetCaptureGroup(const FString& Input, const FString& Pattern, int32 GroupIndex, FString& OutGroup, bool bCaseSensitive = true);
|
||||
};
|
||||
@@ -0,0 +1,93 @@
|
||||
// Copyright (c) 2026 Unreal Directive. Licensed under the MIT License.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "Kismet/BlueprintFunctionLibrary.h"
|
||||
#include "DirectiveUtilSaveGameFunctionLibrary.generated.h"
|
||||
|
||||
class USaveGame;
|
||||
|
||||
/**
|
||||
* UDirectiveUtilSaveGameFunctionLibrary
|
||||
* 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.
|
||||
*/
|
||||
UCLASS()
|
||||
class DIRECTIVEUTILITIESRUNTIME_API UDirectiveUtilSaveGameFunctionLibrary : public UBlueprintFunctionLibrary
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
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 save slot names (without extension).
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "Directive Utilities|SaveGame")
|
||||
static TArray<FString> GetAllSaveSlotNames();
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* @returns True if the slot exists.
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "Directive Utilities|SaveGame")
|
||||
static bool GetSaveSlotTimestamp(const FString& SlotName, FDateTime& OutTimestamp);
|
||||
|
||||
/**
|
||||
* Serializes a save game object to an in-memory byte array (instead of a slot file).
|
||||
* Useful for custom storage, networking, or cloud saves.
|
||||
* @param SaveGameObject - The save game object to serialize.
|
||||
* @param OutBytes - [out] The serialized bytes.
|
||||
* @returns True if serialization succeeded.
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "Directive Utilities|SaveGame")
|
||||
static bool SaveGameToBytes(USaveGame* SaveGameObject, TArray<uint8>& OutBytes);
|
||||
|
||||
/**
|
||||
* Deserializes a save game object from an in-memory byte array produced by Save Game To Bytes.
|
||||
* @param SaveData - The serialized bytes.
|
||||
* @returns The deserialized save game object, or null on failure.
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "Directive Utilities|SaveGame")
|
||||
static USaveGame* LoadGameFromBytes(const TArray<uint8>& SaveData);
|
||||
|
||||
/**
|
||||
* Checks whether a save slot exists. Goes through the engine's save game system, so unlike
|
||||
* enumeration it also works on platform save backends.
|
||||
* @param SlotName - The save slot name.
|
||||
* @param UserIndex - The platform user index the save belongs to.
|
||||
* @returns True if the slot exists.
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "Directive Utilities|SaveGame")
|
||||
static bool DoesSaveSlotExist(const FString& SlotName, int32 UserIndex = 0);
|
||||
|
||||
/**
|
||||
* Deletes a save slot. Goes through the engine's save game system, so unlike enumeration it
|
||||
* also works on platform save backends.
|
||||
* @param SlotName - The save slot name.
|
||||
* @param UserIndex - The platform user index the save belongs to.
|
||||
* @returns True if a save was actually deleted.
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "Directive Utilities|SaveGame")
|
||||
static bool DeleteSaveSlot(const FString& SlotName, int32 UserIndex = 0);
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* @param OldSlotName - The existing save slot name.
|
||||
* @param NewSlotName - The new save slot name.
|
||||
* @param UserIndex - The platform user index the save belongs to.
|
||||
* @returns True if the slot was renamed.
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "Directive Utilities|SaveGame")
|
||||
static bool RenameSaveSlot(const FString& OldSlotName, const FString& NewSlotName, int32 UserIndex = 0);
|
||||
};
|
||||
@@ -0,0 +1,339 @@
|
||||
// Copyright (c) 2026 Unreal Directive. Licensed under the MIT License.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "Kismet/BlueprintFunctionLibrary.h"
|
||||
#include "Types/DirectiveUtilTypes.h"
|
||||
#include "DirectiveUtilStringFunctionLibrary.generated.h"
|
||||
|
||||
/**
|
||||
* UDirectiveUtilStringFunctionLibrary
|
||||
* A collection of helpful string utility functions that improve the usability of strings in Blueprints.
|
||||
*/
|
||||
UCLASS()
|
||||
class DIRECTIVEUTILITIESRUNTIME_API UDirectiveUtilStringFunctionLibrary : public UBlueprintFunctionLibrary
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
|
||||
/**
|
||||
* Detect if the provided string contains any letters.
|
||||
* @note Operates on ASCII characters only; non-ASCII letters/digits are not classified or cased.
|
||||
* @param String - The string to check.
|
||||
* @returns True if the string contains letters.
|
||||
*/
|
||||
UFUNCTION(BlueprintPure, Category = "Directive Utilities|String" )
|
||||
static bool ContainsLetters(const FString& String);
|
||||
|
||||
/**
|
||||
* Detect if the provided string contains any numbers.
|
||||
* @note Operates on ASCII characters only; non-ASCII letters/digits are not classified or cased.
|
||||
* @param String - The string to check.
|
||||
* @returns True if the string contains numbers.
|
||||
*/
|
||||
UFUNCTION(BlueprintPure, Category = "Directive Utilities|String" )
|
||||
static bool ContainsNumbers(const FString& String);
|
||||
|
||||
/**
|
||||
* Detect if the provided string contains any spaces.
|
||||
* @param String - The string to check.
|
||||
* @returns True if the string contains spaces.
|
||||
*/
|
||||
UFUNCTION(BlueprintPure, Category = "Directive Utilities|String" )
|
||||
static bool ContainsSpaces(const FString& String);
|
||||
|
||||
/**
|
||||
* Detect if the provided string contains any special characters.
|
||||
* @note Operates on ASCII characters only; non-ASCII letters/digits are not classified or cased.
|
||||
* @param String - The string to check.
|
||||
* @returns True if the string contains special characters.
|
||||
*/
|
||||
UFUNCTION(BlueprintPure, Category = "Directive Utilities|String" )
|
||||
static bool ContainsSpecialCharacters(const FString& String);
|
||||
|
||||
/**
|
||||
* Filter out characters types from the string.
|
||||
* @note Operates on ASCII characters only; non-ASCII letters/digits are not classified or cased.
|
||||
* @param String - The string to filter.
|
||||
* @param bLetters - If true, filter out letters.
|
||||
* @param bNumbers - If true, filter out numbers.
|
||||
* @param bSpecialCharacters - If true, filter out special characters.
|
||||
* @param bSpaces - If true, filter out spaces.
|
||||
*/
|
||||
UFUNCTION(BlueprintPure, Category = "Directive Utilities|String" )
|
||||
static FString FilterCharacters(const FString& String, const bool bLetters, const bool bNumbers, const bool bSpecialCharacters, const bool bSpaces);
|
||||
|
||||
/**
|
||||
* Sort a string array alphabetically.
|
||||
* @param StringArray - The string array to sort.
|
||||
*/
|
||||
UFUNCTION(BlueprintPure, Category = "Directive Utilities|String", meta = (DeprecatedFunction, DeprecationMessage = "Use the engine's Sort String Array node (UE 5.6+) instead."))
|
||||
static TArray<FString> SortStringArray(TArray<FString> StringArray);
|
||||
|
||||
/**
|
||||
* Truncates a string to the specified length and appends a suffix.
|
||||
* @param String - The string to truncate.
|
||||
* @param MaxLength - The maximum length of the resulting string including the suffix. If MaxLength is smaller than the suffix length, the full suffix is still returned.
|
||||
* @param Suffix - The suffix to append when truncated.
|
||||
* @returns The truncated string.
|
||||
*/
|
||||
UFUNCTION(BlueprintPure, Category = "Directive Utilities|String")
|
||||
static FString TruncateString(const FString& String, int32 MaxLength, const FString& Suffix = TEXT("..."));
|
||||
|
||||
/**
|
||||
* Converts a string to title case, capitalizing the first letter of each word.
|
||||
* @note Operates on ASCII characters only; non-ASCII letters/digits are not classified or cased.
|
||||
* @param String - The string to convert.
|
||||
* @returns The title-cased string.
|
||||
*/
|
||||
UFUNCTION(BlueprintPure, Category = "Directive Utilities|String")
|
||||
static FString ToTitleCase(const FString& String);
|
||||
|
||||
/**
|
||||
* Splits a string into its component words. Words are delimited by any non-alphanumeric
|
||||
* character (which is consumed), a lower-to-upper transition ("fooBar" -> "foo", "Bar"),
|
||||
* the end of an acronym ("XMLParser" -> "XML", "Parser"), or a letter/digit transition
|
||||
* ("version2Beta" -> "version", "2", "Beta").
|
||||
* @note Operates on ASCII characters only; non-ASCII letters/digits are not classified or cased.
|
||||
* @param String - The string to split.
|
||||
* @returns The words in order, or an empty array for an empty input.
|
||||
*/
|
||||
UFUNCTION(BlueprintPure, Category = "Directive Utilities|String")
|
||||
static TArray<FString> SplitIntoWords(const FString& String);
|
||||
|
||||
/**
|
||||
* Converts a string to camelCase: the first word lowercased, every following word capitalized,
|
||||
* joined without separators ("my var name" -> "myVarName").
|
||||
* @note Operates on ASCII characters only; non-ASCII letters/digits are not classified or cased.
|
||||
* @param String - The string to convert.
|
||||
* @returns The camelCased string.
|
||||
*/
|
||||
UFUNCTION(BlueprintPure, Category = "Directive Utilities|String")
|
||||
static FString ToCamelCase(const FString& String);
|
||||
|
||||
/**
|
||||
* Converts a string to PascalCase: every word capitalized, joined without separators
|
||||
* ("my var name" -> "MyVarName").
|
||||
* @note Operates on ASCII characters only; non-ASCII letters/digits are not classified or cased.
|
||||
* @param String - The string to convert.
|
||||
* @returns The PascalCased string.
|
||||
*/
|
||||
UFUNCTION(BlueprintPure, Category = "Directive Utilities|String")
|
||||
static FString ToPascalCase(const FString& String);
|
||||
|
||||
/**
|
||||
* Converts a string to snake_case: every word lowercased, joined with underscores
|
||||
* ("my var name" -> "my_var_name").
|
||||
* @note Operates on ASCII characters only; non-ASCII letters/digits are not classified or cased.
|
||||
* @param String - The string to convert.
|
||||
* @returns The snake_cased string.
|
||||
*/
|
||||
UFUNCTION(BlueprintPure, Category = "Directive Utilities|String")
|
||||
static FString ToSnakeCase(const FString& String);
|
||||
|
||||
/**
|
||||
* Converts a string to kebab-case: every word lowercased, joined with hyphens
|
||||
* ("my var name" -> "my-var-name").
|
||||
* @note Operates on ASCII characters only; non-ASCII letters/digits are not classified or cased.
|
||||
* @param String - The string to convert.
|
||||
* @returns The kebab-cased string.
|
||||
*/
|
||||
UFUNCTION(BlueprintPure, Category = "Directive Utilities|String")
|
||||
static FString ToKebabCase(const FString& String);
|
||||
|
||||
/**
|
||||
* Returns a sorted copy of the provided string array.
|
||||
* @param StringArray - The array of strings to sort.
|
||||
* @returns A sorted copy of the provided string array.
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "Directive Utilities|String", meta = (DeprecatedFunction, DeprecationMessage = "Use the engine's Sort String Array node (UE 5.6+) instead."))
|
||||
static TArray<FString> GetSortedStringArray(TArray<FString> StringArray);
|
||||
|
||||
/**
|
||||
* Returns the Levenshtein (edit) distance between two strings: the minimum number of single-character
|
||||
* insertions, deletions, or substitutions needed to turn one string into the other.
|
||||
* @param A - The first string.
|
||||
* @param B - The second string.
|
||||
* @param bCaseSensitive - Whether the comparison is case-sensitive. Defaults to true.
|
||||
* @returns The edit distance (0 means identical).
|
||||
*/
|
||||
UFUNCTION(BlueprintPure, Category = "Directive Utilities|String")
|
||||
static int32 GetLevenshteinDistance(const FString& A, const FString& B, bool bCaseSensitive = true);
|
||||
|
||||
/**
|
||||
* Returns a normalized similarity score between two strings, from 0 (completely different) to 1 (identical),
|
||||
* derived from the Levenshtein distance. Two empty strings are considered identical.
|
||||
* @param A - The first string.
|
||||
* @param B - The second string.
|
||||
* @param bCaseSensitive - Whether the comparison is case-sensitive. Defaults to true.
|
||||
* @returns The similarity in the [0, 1] range.
|
||||
*/
|
||||
UFUNCTION(BlueprintPure, Category = "Directive Utilities|String")
|
||||
static float GetStringSimilarity(const FString& A, const FString& B, bool bCaseSensitive = true);
|
||||
|
||||
/**
|
||||
* Returns true if the source string contains any of the provided search terms. Empty terms are ignored.
|
||||
* @param Source - The string to search.
|
||||
* @param SearchTerms - The substrings to look for.
|
||||
* @param bCaseSensitive - Whether the search is case-sensitive. Defaults to true.
|
||||
* @returns True if at least one non-empty term is found.
|
||||
*/
|
||||
UFUNCTION(BlueprintPure, Category = "Directive Utilities|String")
|
||||
static bool ContainsAny(const FString& Source, const TArray<FString>& SearchTerms, bool bCaseSensitive = true);
|
||||
|
||||
/**
|
||||
* Finds the earliest occurrence in the source string of any of the provided search terms.
|
||||
* @param Source - The string to search.
|
||||
* @param SearchTerms - The substrings to look for. Empty terms are ignored.
|
||||
* @param bCaseSensitive - Whether the search is case-sensitive. Defaults to true.
|
||||
* @param OutFoundIndex - [out] The index in Source of the earliest match, or INDEX_NONE if none.
|
||||
* @param OutTermIndex - [out] The index into SearchTerms of the matched term, or INDEX_NONE if none.
|
||||
* @returns True if any term was found.
|
||||
*/
|
||||
UFUNCTION(BlueprintPure, Category = "Directive Utilities|String")
|
||||
static bool FindFirstOfAny(const FString& Source, const TArray<FString>& SearchTerms, bool bCaseSensitive, int32& OutFoundIndex, int32& OutTermIndex);
|
||||
|
||||
/**
|
||||
* Encodes a string to Base64.
|
||||
* @param Source - The string to encode.
|
||||
* @returns The Base64-encoded string.
|
||||
*/
|
||||
UFUNCTION(BlueprintPure, Category = "Directive Utilities|String")
|
||||
static FString Base64Encode(const FString& Source);
|
||||
|
||||
/**
|
||||
* Decodes a Base64 string.
|
||||
* @param Source - The Base64 string to decode.
|
||||
* @param OutDecoded - [out] The decoded string, or empty on failure.
|
||||
* @returns True if the input was valid Base64 and was decoded.
|
||||
*/
|
||||
UFUNCTION(BlueprintPure, Category = "Directive Utilities|String")
|
||||
static bool Base64Decode(const FString& Source, FString& OutDecoded);
|
||||
|
||||
/**
|
||||
* Encodes a string as lowercase hex. The string is converted to UTF-8 bytes first.
|
||||
* @param String - The string to encode.
|
||||
* @returns The lowercase hex encoding of the string's UTF-8 bytes.
|
||||
*/
|
||||
UFUNCTION(BlueprintPure, Category = "Directive Utilities|String")
|
||||
static FString HexEncode(const FString& String);
|
||||
|
||||
/**
|
||||
* Decodes a hex string (either case accepted) into the string its bytes spell in UTF-8.
|
||||
* @param Hex - The hex string to decode.
|
||||
* @param OutString - [out] The decoded string, or empty on failure.
|
||||
* @returns True if the input was valid even-length hex and was decoded.
|
||||
*/
|
||||
UFUNCTION(BlueprintPure, Category = "Directive Utilities|String")
|
||||
static bool HexDecode(const FString& Hex, FString& OutString);
|
||||
|
||||
/**
|
||||
* Encodes a byte array as lowercase hex.
|
||||
* @param Bytes - The bytes to encode.
|
||||
* @returns The lowercase hex encoding of the bytes.
|
||||
*/
|
||||
UFUNCTION(BlueprintPure, Category = "Directive Utilities|String")
|
||||
static FString HexEncodeBytes(const TArray<uint8>& Bytes);
|
||||
|
||||
/**
|
||||
* Decodes a hex string (either case accepted) into a byte array.
|
||||
* @param Hex - The hex string to decode.
|
||||
* @param OutBytes - [out] The decoded bytes, or empty on failure.
|
||||
* @returns True if the input was valid even-length hex and was decoded.
|
||||
*/
|
||||
UFUNCTION(BlueprintPure, Category = "Directive Utilities|String")
|
||||
static bool HexDecodeBytes(const FString& Hex, TArray<uint8>& OutBytes);
|
||||
|
||||
/**
|
||||
* Returns the MD5 digest of the string as 32 lowercase hex characters. The string is
|
||||
* converted to UTF-8 bytes first.
|
||||
* @note For integrity checks and cache keys, not for security or password storage.
|
||||
* @param String - The string to hash.
|
||||
* @returns The MD5 digest as lowercase hex.
|
||||
*/
|
||||
UFUNCTION(BlueprintPure, Category = "Directive Utilities|String")
|
||||
static FString Md5HashString(const FString& String);
|
||||
|
||||
/**
|
||||
* Returns the MD5 digest of a byte array as 32 lowercase hex characters.
|
||||
* @note For integrity checks and cache keys, not for security or password storage.
|
||||
* @param Bytes - The bytes to hash.
|
||||
* @returns The MD5 digest as lowercase hex.
|
||||
*/
|
||||
UFUNCTION(BlueprintPure, Category = "Directive Utilities|String")
|
||||
static FString Md5HashBytes(const TArray<uint8>& Bytes);
|
||||
|
||||
/**
|
||||
* Returns the SHA-1 digest of the string as 40 lowercase hex characters. The string is
|
||||
* converted to UTF-8 bytes first.
|
||||
* @note For integrity checks and cache keys, not for security or password storage.
|
||||
* @param String - The string to hash.
|
||||
* @returns The SHA-1 digest as lowercase hex.
|
||||
*/
|
||||
UFUNCTION(BlueprintPure, Category = "Directive Utilities|String")
|
||||
static FString Sha1HashString(const FString& String);
|
||||
|
||||
/**
|
||||
* Returns the SHA-1 digest of a byte array as 40 lowercase hex characters.
|
||||
* @note For integrity checks and cache keys, not for security or password storage.
|
||||
* @param Bytes - The bytes to hash.
|
||||
* @returns The SHA-1 digest as lowercase hex.
|
||||
*/
|
||||
UFUNCTION(BlueprintPure, Category = "Directive Utilities|String")
|
||||
static FString Sha1HashBytes(const TArray<uint8>& Bytes);
|
||||
|
||||
/**
|
||||
* Returns the CRC32 checksum of the string (seed 0). The string is converted to
|
||||
* UTF-8 bytes first.
|
||||
* @note For integrity checks and cache keys, not for security or password storage.
|
||||
* @param String - The string to checksum.
|
||||
* @returns The CRC32 checksum.
|
||||
*/
|
||||
UFUNCTION(BlueprintPure, Category = "Directive Utilities|String")
|
||||
static int32 Crc32String(const FString& String);
|
||||
|
||||
/**
|
||||
* Returns the CRC32 checksum of a byte array (seed 0).
|
||||
* @note For integrity checks and cache keys, not for security or password storage.
|
||||
* @param Bytes - The bytes to checksum.
|
||||
* @returns The CRC32 checksum.
|
||||
*/
|
||||
UFUNCTION(BlueprintPure, Category = "Directive Utilities|String")
|
||||
static int32 Crc32Bytes(const TArray<uint8>& Bytes);
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* @param String - The string to check.
|
||||
* @returns True if the string is a valid bare file name.
|
||||
*/
|
||||
UFUNCTION(BlueprintPure, Category = "Directive Utilities|String")
|
||||
static bool IsValidFileName(const FString& String);
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* @param String - The string to sanitize.
|
||||
* @param Replacement - Optional single-character replacement for stripped characters.
|
||||
* @returns The sanitized file name.
|
||||
*/
|
||||
UFUNCTION(BlueprintPure, Category = "Directive Utilities|String")
|
||||
static FString SanitizeFileName(const FString& String, const FString& Replacement = TEXT(""));
|
||||
|
||||
/**
|
||||
* Finds the entry in Candidates most similar to Input (by GetStringSimilarity).
|
||||
* Ties resolve to the earliest index. Cost grows with array size and string
|
||||
* lengths (Levenshtein per candidate). Avoid very large arrays per frame.
|
||||
* @note bCaseSensitive defaults to false (matching user input), unlike the pairwise
|
||||
* comparison functions where it defaults to true.
|
||||
* @param Input - The string to match.
|
||||
* @param Candidates - The candidate strings.
|
||||
* @param OutSimilarity - [out] The winning similarity in [0, 1], 0 when empty.
|
||||
* @param bCaseSensitive - Whether comparison is case-sensitive. Defaults to false.
|
||||
* @returns The index of the best match, or -1 if Candidates is empty.
|
||||
*/
|
||||
UFUNCTION(BlueprintPure, Category = "Directive Utilities|String")
|
||||
static int32 FindBestStringMatch(const FString& Input, const TArray<FString>& Candidates, float& OutSimilarity, bool bCaseSensitive = false);
|
||||
};
|
||||
@@ -0,0 +1,26 @@
|
||||
// Copyright (c) 2026 Unreal Directive. Licensed under the MIT License.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "Kismet/BlueprintFunctionLibrary.h"
|
||||
#include "DirectiveUtilTextFunctionLibrary.generated.h"
|
||||
|
||||
/**
|
||||
* DirectiveUtilTextFunctionLibrary
|
||||
* A collection of helpful text utility functions that improve the usability of text in Blueprints.
|
||||
*/
|
||||
UCLASS()
|
||||
class DIRECTIVEUTILITIESRUNTIME_API UDirectiveUtilTextFunctionLibrary : public UBlueprintFunctionLibrary
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
|
||||
/**
|
||||
* Returns true if the provided text is not empty.
|
||||
* @param Text - The text to check.
|
||||
*/
|
||||
UFUNCTION(BlueprintPure, Category = "Directive Utilities|Text" )
|
||||
static bool IsNotEmpty(const FText& Text);
|
||||
};
|
||||
@@ -0,0 +1,181 @@
|
||||
// Copyright (c) 2026 Unreal Directive. Licensed under the MIT License.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "Kismet/BlueprintAsyncActionBase.h"
|
||||
#include "UObject/SoftObjectPtr.h"
|
||||
#include "DirectiveUtilTask_AsyncLoadAsset.generated.h"
|
||||
|
||||
struct FStreamableHandle;
|
||||
|
||||
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnAsyncLoadAssetCompleted, UObject*, LoadedAsset);
|
||||
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnAsyncLoadClassCompleted, UClass*, LoadedClass);
|
||||
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnAsyncLoadAssetsCompleted, const TArray<UObject*>&, LoadedAssets);
|
||||
DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnAsyncLoadAssetsProgress, int32, LoadedCount, int32, TotalCount);
|
||||
|
||||
/**
|
||||
* DirectiveUtilTask_AsyncLoadAsset
|
||||
* 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
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
|
||||
/**
|
||||
* Asynchronously loads the asset referenced by a soft object pointer.
|
||||
* The Completed delegate is called with the loaded asset on success; the Failed delegate is called with null on failure. A manual Cancel() does not broadcast Failed.
|
||||
*
|
||||
* @param WorldContextObject The world context object.
|
||||
* @param Asset The soft object reference to load.
|
||||
*/
|
||||
UFUNCTION(
|
||||
BlueprintCallable,
|
||||
meta=(
|
||||
BlueprintInternalUseOnly = "true",
|
||||
Category = "Directive Utilities|AssetManagement",
|
||||
WorldContext = "WorldContextObject",
|
||||
DisplayName = "Async Load Asset"
|
||||
))
|
||||
static UDirectiveUtilTask_AsyncLoadAsset* AsyncLoadAsset(UObject* WorldContextObject, const TSoftObjectPtr<UObject> Asset);
|
||||
|
||||
/**
|
||||
* Cancels the in-progress load. The Failed delegate is not broadcast for a manual cancel.
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "Directive Utilities|AssetManagement")
|
||||
void Cancel();
|
||||
|
||||
virtual void Activate() override;
|
||||
|
||||
// Called when the asset has finished loading. The loaded asset is valid.
|
||||
UPROPERTY(BlueprintAssignable)
|
||||
FOnAsyncLoadAssetCompleted Completed;
|
||||
|
||||
// Called when the load failed. The loaded asset is null.
|
||||
UPROPERTY(BlueprintAssignable)
|
||||
FOnAsyncLoadAssetCompleted Failed;
|
||||
|
||||
protected:
|
||||
|
||||
TSoftObjectPtr<UObject> SoftAsset;
|
||||
TSharedPtr<FStreamableHandle> StreamableHandle;
|
||||
|
||||
void OnLoaded();
|
||||
};
|
||||
|
||||
/**
|
||||
* DirectiveUtilTask_AsyncLoadClass
|
||||
* 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
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
|
||||
/**
|
||||
* Asynchronously loads the class referenced by a soft class pointer.
|
||||
* The Completed delegate is called with the loaded class on success; the Failed delegate is called with null on failure. A manual Cancel() does not broadcast Failed.
|
||||
*
|
||||
* @param WorldContextObject The world context object.
|
||||
* @param AssetClass The soft class reference to load.
|
||||
*/
|
||||
UFUNCTION(
|
||||
BlueprintCallable,
|
||||
meta=(
|
||||
BlueprintInternalUseOnly = "true",
|
||||
Category = "Directive Utilities|AssetManagement",
|
||||
WorldContext = "WorldContextObject",
|
||||
DisplayName = "Async Load Class"
|
||||
))
|
||||
static UDirectiveUtilTask_AsyncLoadClass* AsyncLoadClass(UObject* WorldContextObject, const TSoftClassPtr<UObject> AssetClass);
|
||||
|
||||
/**
|
||||
* Cancels the in-progress load. The Failed delegate is not broadcast for a manual cancel.
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "Directive Utilities|AssetManagement")
|
||||
void Cancel();
|
||||
|
||||
virtual void Activate() override;
|
||||
|
||||
// Called when the class has finished loading. The loaded class is valid.
|
||||
UPROPERTY(BlueprintAssignable)
|
||||
FOnAsyncLoadClassCompleted Completed;
|
||||
|
||||
// Called when the load failed. The loaded class is null.
|
||||
UPROPERTY(BlueprintAssignable)
|
||||
FOnAsyncLoadClassCompleted Failed;
|
||||
|
||||
protected:
|
||||
|
||||
TSoftClassPtr<UObject> SoftClass;
|
||||
TSharedPtr<FStreamableHandle> StreamableHandle;
|
||||
|
||||
void OnLoaded();
|
||||
};
|
||||
|
||||
/**
|
||||
* DirectiveUtilTask_AsyncLoadAssets
|
||||
* Asynchronously loads a batch of soft object references in a single request and broadcasts the
|
||||
* 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
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
|
||||
/**
|
||||
* Asynchronously loads the assets referenced by an array of soft object pointers in a single request.
|
||||
* The Completed delegate is called exactly once with the loaded assets in input order; entries that were
|
||||
* unset or failed to resolve are null. An empty input array completes immediately with an empty array.
|
||||
* A manual Cancel() does not broadcast Completed.
|
||||
*
|
||||
* @param WorldContextObject The world context object.
|
||||
* @param Assets The soft object references to load.
|
||||
*/
|
||||
UFUNCTION(
|
||||
BlueprintCallable,
|
||||
meta=(
|
||||
BlueprintInternalUseOnly = "true",
|
||||
Category = "Directive Utilities|AssetManagement",
|
||||
WorldContext = "WorldContextObject",
|
||||
DisplayName = "Async Load Assets"
|
||||
))
|
||||
static UDirectiveUtilTask_AsyncLoadAssets* AsyncLoadAssets(UObject* WorldContextObject, const TArray<TSoftObjectPtr<UObject>>& Assets);
|
||||
|
||||
/**
|
||||
* Cancels the in-progress load. The Completed delegate is not broadcast for a manual cancel.
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "Directive Utilities|AssetManagement")
|
||||
void Cancel();
|
||||
|
||||
virtual void Activate() override;
|
||||
|
||||
// Called exactly once when the batch has finished loading. The assets are in input order with null
|
||||
// entries for references that were unset or failed to resolve, and are only guaranteed alive during
|
||||
// this broadcast.
|
||||
UPROPERTY(BlueprintAssignable)
|
||||
FOnAsyncLoadAssetsCompleted Completed;
|
||||
|
||||
// Called as assets arrive, with the number loaded so far and the total requested. Not called when
|
||||
// the request finishes before the first update (e.g. all assets were already in memory).
|
||||
UPROPERTY(BlueprintAssignable)
|
||||
FOnAsyncLoadAssetsProgress Progress;
|
||||
|
||||
protected:
|
||||
|
||||
TArray<TSoftObjectPtr<UObject>> SoftAssets;
|
||||
TSharedPtr<FStreamableHandle> StreamableHandle;
|
||||
|
||||
/* Whether the task has already completed, guarding against a second broadcast. */
|
||||
bool bHasCompleted = false;
|
||||
|
||||
void OnLoaded();
|
||||
void OnUpdate(TSharedRef<FStreamableHandle> Handle);
|
||||
};
|
||||
@@ -0,0 +1,136 @@
|
||||
// Copyright (c) 2026 Unreal Directive. Licensed under the MIT License.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "Kismet/BlueprintAsyncActionBase.h"
|
||||
#include "Engine/EngineTypes.h"
|
||||
#include "Engine/HitResult.h"
|
||||
#include "DirectiveUtilTask_AsyncTrace.generated.h"
|
||||
|
||||
struct FTraceHandle;
|
||||
struct FTraceDatum;
|
||||
|
||||
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnAsyncTraceCompleted, const TArray<FHitResult>&, Hits);
|
||||
|
||||
/** The collision shape used by an async trace. */
|
||||
enum class EDirectiveUtilTraceShape : uint8
|
||||
{
|
||||
Line,
|
||||
Sphere,
|
||||
Box,
|
||||
Capsule,
|
||||
};
|
||||
|
||||
/**
|
||||
* DirectiveUtilTask_AsyncTrace
|
||||
* Queues a collision trace and broadcasts the hit results on the next tick.
|
||||
* The trace cannot be cancelled.
|
||||
*/
|
||||
UCLASS(BlueprintType, meta=(ExposedAsyncProxy = AsyncTask, DisplayName="Async Trace"))
|
||||
class DIRECTIVEUTILITIESRUNTIME_API UDirectiveUtilTask_AsyncTrace : public UBlueprintAsyncActionBase
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
|
||||
/**
|
||||
* Performs an asynchronous line trace against the given trace channel.
|
||||
* @param WorldContextObject The world context object.
|
||||
* @param Start The start of the trace.
|
||||
* @param End The end of the trace.
|
||||
* @param TraceChannel The trace channel to test against.
|
||||
* @param bMultiTrace If true, returns all hits up to and including the first blocking hit; otherwise returns only the first blocking hit.
|
||||
*/
|
||||
UFUNCTION(
|
||||
BlueprintCallable,
|
||||
meta=(
|
||||
BlueprintInternalUseOnly = "true",
|
||||
Category = "Directive Utilities|Collision",
|
||||
WorldContext = "WorldContextObject",
|
||||
DisplayName = "Async Line Trace By Channel"
|
||||
))
|
||||
static UDirectiveUtilTask_AsyncTrace* AsyncLineTraceByChannel(UObject* WorldContextObject, const FVector Start, const FVector End, const ETraceTypeQuery TraceChannel, const bool bMultiTrace = false);
|
||||
|
||||
/**
|
||||
* Performs an asynchronous sphere sweep against the given trace channel.
|
||||
* @param WorldContextObject The world context object.
|
||||
* @param Start The start of the sweep.
|
||||
* @param End The end of the sweep.
|
||||
* @param Radius The radius of the sphere.
|
||||
* @param TraceChannel The trace channel to test against.
|
||||
* @param bMultiTrace If true, returns all hits up to and including the first blocking hit; otherwise returns only the first blocking hit.
|
||||
*/
|
||||
UFUNCTION(
|
||||
BlueprintCallable,
|
||||
meta=(
|
||||
BlueprintInternalUseOnly = "true",
|
||||
Category = "Directive Utilities|Collision",
|
||||
WorldContext = "WorldContextObject",
|
||||
DisplayName = "Async Sphere Trace By Channel"
|
||||
))
|
||||
static UDirectiveUtilTask_AsyncTrace* AsyncSphereTraceByChannel(UObject* WorldContextObject, const FVector Start, const FVector End, const float Radius, const ETraceTypeQuery TraceChannel, const bool bMultiTrace = false);
|
||||
|
||||
/**
|
||||
* Performs an asynchronous box sweep against the given trace channel.
|
||||
* @param WorldContextObject The world context object.
|
||||
* @param Start The start of the sweep.
|
||||
* @param End The end of the sweep.
|
||||
* @param HalfSize The half-extents of the box.
|
||||
* @param Orientation The orientation of the box.
|
||||
* @param TraceChannel The trace channel to test against.
|
||||
* @param bMultiTrace If true, returns all hits up to and including the first blocking hit; otherwise returns only the first blocking hit.
|
||||
*/
|
||||
UFUNCTION(
|
||||
BlueprintCallable,
|
||||
meta=(
|
||||
BlueprintInternalUseOnly = "true",
|
||||
Category = "Directive Utilities|Collision",
|
||||
WorldContext = "WorldContextObject",
|
||||
DisplayName = "Async Box Trace By Channel"
|
||||
))
|
||||
static UDirectiveUtilTask_AsyncTrace* AsyncBoxTraceByChannel(UObject* WorldContextObject, const FVector Start, const FVector End, const FVector HalfSize, const FRotator Orientation, const ETraceTypeQuery TraceChannel, const bool bMultiTrace = false);
|
||||
|
||||
/**
|
||||
* Performs an asynchronous capsule sweep against the given trace channel.
|
||||
* @param WorldContextObject The world context object.
|
||||
* @param Start The start of the sweep.
|
||||
* @param End The end of the sweep.
|
||||
* @param Radius The radius of the capsule.
|
||||
* @param HalfHeight The half-height of the capsule (including the radius).
|
||||
* @param TraceChannel The trace channel to test against.
|
||||
* @param bMultiTrace If true, returns all hits up to and including the first blocking hit; otherwise returns only the first blocking hit.
|
||||
*/
|
||||
UFUNCTION(
|
||||
BlueprintCallable,
|
||||
meta=(
|
||||
BlueprintInternalUseOnly = "true",
|
||||
Category = "Directive Utilities|Collision",
|
||||
WorldContext = "WorldContextObject",
|
||||
DisplayName = "Async Capsule Trace By Channel"
|
||||
))
|
||||
static UDirectiveUtilTask_AsyncTrace* AsyncCapsuleTraceByChannel(UObject* WorldContextObject, const FVector Start, const FVector End, const float Radius, const float HalfHeight, const ETraceTypeQuery TraceChannel, const bool bMultiTrace = false);
|
||||
|
||||
virtual void Activate() override;
|
||||
|
||||
// Called when the trace has completed. Empty if nothing was hit.
|
||||
UPROPERTY(BlueprintAssignable)
|
||||
FOnAsyncTraceCompleted Completed;
|
||||
|
||||
protected:
|
||||
|
||||
UPROPERTY()
|
||||
TObjectPtr<UObject> WorldContextObject;
|
||||
|
||||
FVector Start = FVector::ZeroVector;
|
||||
FVector End = FVector::ZeroVector;
|
||||
ETraceTypeQuery TraceChannel = ETraceTypeQuery::TraceTypeQuery1;
|
||||
bool bMultiTrace = false;
|
||||
EDirectiveUtilTraceShape Shape = EDirectiveUtilTraceShape::Line;
|
||||
float Radius = 0.0f;
|
||||
float HalfHeight = 0.0f;
|
||||
FVector HalfSize = FVector::ZeroVector;
|
||||
FQuat Orientation = FQuat::Identity;
|
||||
|
||||
void OnTraceComplete(const FTraceHandle& Handle, FTraceDatum& Datum);
|
||||
};
|
||||
@@ -0,0 +1,61 @@
|
||||
// Copyright (c) 2026 Unreal Directive. Licensed under the MIT License.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "Kismet/BlueprintAsyncActionBase.h"
|
||||
#include "Engine/TimerHandle.h"
|
||||
#include "DirectiveUtilTask_Delay.generated.h"
|
||||
|
||||
DECLARE_DYNAMIC_MULTICAST_DELEGATE(FOnDelayCompleted);
|
||||
|
||||
/**
|
||||
* DirectiveUtilTask_Delay
|
||||
* A cancellable delay that can be ended early by calling EndTask.
|
||||
*/
|
||||
UCLASS(BlueprintType, meta=(ExposedAsyncProxy = AsyncTask, DisplayName="Cancellable Delay"))
|
||||
class DIRECTIVEUTILITIESRUNTIME_API UDirectiveUtilTask_Delay : public UBlueprintAsyncActionBase
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
|
||||
/**
|
||||
* Starts a cancellable delay.
|
||||
* When the delay has completed, the Completed delegate is called.
|
||||
* Call EndTask to cancel the delay before it completes.
|
||||
*
|
||||
* @param WorldContextObject The world context object.
|
||||
* @param Duration The duration of the delay in seconds.
|
||||
*/
|
||||
UFUNCTION(
|
||||
BlueprintCallable,
|
||||
meta=(
|
||||
BlueprintInternalUseOnly = "true",
|
||||
Category = "Directive Utilities|FlowControl",
|
||||
WorldContext = "WorldContextObject",
|
||||
DisplayName = "Cancellable Delay"
|
||||
))
|
||||
static UDirectiveUtilTask_Delay* CancellableDelay(UObject* WorldContextObject, float Duration);
|
||||
|
||||
/**
|
||||
* Ends the delay early.
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "Directive Utilities|FlowControl")
|
||||
void EndTask();
|
||||
virtual void Activate() override;
|
||||
|
||||
// The delegate called when the delay has completed.
|
||||
UPROPERTY(BlueprintAssignable)
|
||||
FOnDelayCompleted Completed;
|
||||
|
||||
protected:
|
||||
|
||||
UPROPERTY()
|
||||
TObjectPtr<UObject> WorldContextObject;
|
||||
|
||||
float Duration = 0.0f;
|
||||
FTimerHandle TimerHandle;
|
||||
|
||||
void OnDelayComplete();
|
||||
};
|
||||
@@ -0,0 +1,181 @@
|
||||
// Copyright (c) 2026 Unreal Directive. Licensed under the MIT License.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "Kismet/BlueprintAsyncActionBase.h"
|
||||
#include "GameFramework/Controller.h"
|
||||
#include "DirectiveUtilTask_MoveToLocation.generated.h"
|
||||
|
||||
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnAsyncMoveToLocation, bool, bSuccess);
|
||||
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnAsyncMoveToActor, bool, bSuccess);
|
||||
|
||||
/**
|
||||
* DirectiveUtilTask_MoveToLocation
|
||||
* Asynchronously moves an actor to a location.
|
||||
*/
|
||||
UCLASS(BlueprintType, meta=(ExposedAsyncProxy = AsyncTask, DisplayName="Async Move To Location"))
|
||||
class DIRECTIVEUTILITIESRUNTIME_API UDirectiveUtilTask_MoveToLocation : public UBlueprintAsyncActionBase
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
|
||||
/**
|
||||
* Moves the actor to the specified location.
|
||||
* When the movement has succeeded or failed, the Completed delegate is called exactly once with success/failure.
|
||||
* 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 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 bCheckStuckMovement Check if the controller gets stuck while moving.
|
||||
* @param StuckThreshold The distance threshold to consider the controller stuck.
|
||||
* @param bDebugLineTrace Display a line trace to the destination location for a short duration.
|
||||
*/
|
||||
UFUNCTION(
|
||||
BlueprintCallable,
|
||||
meta=(
|
||||
BlueprintInternalUseOnly = "true",
|
||||
Category = "Directive Utilities|Navigation",
|
||||
WorldContext = "WorldContextObject",
|
||||
DisplayName = "Async Move To Location",
|
||||
AdvancedDisplay=6
|
||||
))
|
||||
static UDirectiveUtilTask_MoveToLocation* MoveToLocation(
|
||||
UObject* WorldContextObject,
|
||||
AController* Controller,
|
||||
FVector Destination,
|
||||
float AcceptanceRadius = 100.0f,
|
||||
bool bCheckStuckMovement = true,
|
||||
float StuckThreshold = 1.0f,
|
||||
bool bDebugLineTrace = false);
|
||||
|
||||
/**
|
||||
* Ends the async action.
|
||||
* This must be called manually when the task is no longer necessary.
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "Directive Utilities|Navigation")
|
||||
void EndTask();
|
||||
virtual void Activate() override;
|
||||
|
||||
// The delegate called when the movement has completed regardless of success. Fires exactly once.
|
||||
UPROPERTY(BlueprintAssignable)
|
||||
FOnAsyncMoveToLocation Completed;
|
||||
|
||||
protected:
|
||||
|
||||
UPROPERTY()
|
||||
AController* Controller;
|
||||
|
||||
FVector Destination;
|
||||
FVector StartLocation;
|
||||
FVector CurrentLocation;
|
||||
FVector LastCheckedLocation;
|
||||
float AcceptanceRadius = 10.0f;
|
||||
bool bCheckStuckMovement = true;
|
||||
float StuckThreshold = 1.0f;
|
||||
bool bDebugLineTrace;
|
||||
|
||||
FTimerHandle TimerHandle;
|
||||
|
||||
FTimerHandle StuckTimerHandle;
|
||||
|
||||
bool bHasCompleted = false;
|
||||
|
||||
void CheckMoveToLocation();
|
||||
|
||||
void CheckStuckMovement();
|
||||
|
||||
virtual void ExecuteCompleted(bool bSuccess);
|
||||
};
|
||||
|
||||
/**
|
||||
* DirectiveUtilTask_MoveToActor
|
||||
* Asynchronously moves an actor to another actor.
|
||||
*/
|
||||
UCLASS(BlueprintType, meta=(ExposedAsyncProxy = AsyncTask, DisplayName="Async Move To Actor"))
|
||||
class DIRECTIVEUTILITIESRUNTIME_API UDirectiveUtilTask_MoveToActor : public UBlueprintAsyncActionBase
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
|
||||
/**
|
||||
* Moves the controller's pawn to the goal actor.
|
||||
* When the movement has succeeded or failed, the Completed delegate is called exactly once with success/failure.
|
||||
* bSuccess is true only when the pawn ends within AcceptanceRadius of the goal actor; the task also ends
|
||||
* (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 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 bCheckStuckMovement Check if the controller gets stuck while moving.
|
||||
* @param StuckThreshold The distance threshold to consider the controller stuck.
|
||||
*/
|
||||
UFUNCTION(
|
||||
BlueprintCallable,
|
||||
meta=(
|
||||
BlueprintInternalUseOnly = "true",
|
||||
Category = "Directive Utilities|Navigation",
|
||||
WorldContext = "WorldContextObject",
|
||||
DisplayName = "Async Move To Actor"
|
||||
))
|
||||
static UDirectiveUtilTask_MoveToActor* MoveToActor(
|
||||
UObject* WorldContextObject,
|
||||
AController* Controller,
|
||||
AActor* Goal,
|
||||
float AcceptanceRadius = 100.0f,
|
||||
bool bCheckStuckMovement = true,
|
||||
float StuckThreshold = 1.0f);
|
||||
|
||||
/**
|
||||
* Ends the async action.
|
||||
* This must be called manually when the task is no longer necessary.
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "Directive Utilities|Navigation")
|
||||
void EndTask();
|
||||
virtual void Activate() override;
|
||||
|
||||
// The delegate called when the movement has completed regardless of success. Fires exactly once.
|
||||
UPROPERTY(BlueprintAssignable)
|
||||
FOnAsyncMoveToActor Completed;
|
||||
|
||||
protected:
|
||||
|
||||
UPROPERTY()
|
||||
AController* Controller;
|
||||
|
||||
// The cached goal actor; GC nulls it if the actor is destroyed.
|
||||
UPROPERTY()
|
||||
TObjectPtr<AActor> Goal;
|
||||
|
||||
FVector StartLocation;
|
||||
FVector CurrentLocation;
|
||||
FVector LastCheckedLocation;
|
||||
float AcceptanceRadius = 10.0f;
|
||||
bool bCheckStuckMovement = true;
|
||||
float StuckThreshold = 1.0f;
|
||||
|
||||
FTimerHandle TimerHandle;
|
||||
|
||||
FTimerHandle StuckTimerHandle;
|
||||
|
||||
bool bHasCompleted = false;
|
||||
|
||||
void CheckMoveToActor();
|
||||
|
||||
void CheckStuckMovement();
|
||||
|
||||
virtual void ExecuteCompleted(bool bSuccess);
|
||||
};
|
||||
@@ -0,0 +1,26 @@
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "DirectiveUtilInputTypes.generated.h"
|
||||
|
||||
class UInputMappingContext;
|
||||
|
||||
/** An input mapping context and its application priority. */
|
||||
USTRUCT(BlueprintType)
|
||||
struct FDirectiveUtilEnhancedInputContextData
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
/** The input context to be used. */
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Input")
|
||||
TSoftObjectPtr<UInputMappingContext> InputContext;
|
||||
|
||||
/** The priority of the input context. */
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Input")
|
||||
int32 Priority;
|
||||
|
||||
FDirectiveUtilEnhancedInputContextData()
|
||||
: Priority(0)
|
||||
{
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,23 @@
|
||||
// Copyright (c) 2026 Unreal Directive. Licensed under the MIT License.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "DirectiveUtilMathTypes.generated.h"
|
||||
|
||||
/**
|
||||
* Easing curves not provided by the engine's built-in Ease node (EEasingFunc): the classic Penner Back, Elastic and Bounce curves.
|
||||
*/
|
||||
UENUM(BlueprintType)
|
||||
enum class EDirectiveUtilEaseType : uint8
|
||||
{
|
||||
BackIn UMETA(DisplayName = "Back In", Tooltip="Overshoots slightly at the start before easing in."),
|
||||
BackOut UMETA(DisplayName = "Back Out", Tooltip="Overshoots slightly past the end before settling."),
|
||||
BackInOut UMETA(DisplayName = "Back In Out", Tooltip="Overshoots at both the start and the end."),
|
||||
ElasticIn UMETA(DisplayName = "Elastic In", Tooltip="Oscillates with increasing amplitude before easing in."),
|
||||
ElasticOut UMETA(DisplayName = "Elastic Out", Tooltip="Oscillates with decreasing amplitude after the end."),
|
||||
ElasticInOut UMETA(DisplayName = "Elastic In Out", Tooltip="Oscillates at both the start and the end."),
|
||||
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."),
|
||||
};
|
||||
@@ -0,0 +1,14 @@
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "DirectiveUtilTypes.generated.h"
|
||||
|
||||
/**
|
||||
* Provides a list of success types.
|
||||
*/
|
||||
UENUM(BlueprintType)
|
||||
enum class EDirectiveUtilSuccessStatus : uint8
|
||||
{
|
||||
Success,
|
||||
Failure,
|
||||
};
|
||||
Reference in New Issue
Block a user