Import helper plugin
This commit is contained in:
@@ -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();
|
||||
}
|
||||
Reference in New Issue
Block a user