Import helper plugin

This commit is contained in:
2026-07-15 19:17:47 +02:00
parent fd992725a2
commit 3c084d9669
72 changed files with 11853 additions and 1 deletions

View File

@@ -0,0 +1,716 @@
// Copyright (c) 2026 Unreal Directive. Licensed under the MIT License.
#pragma once
#include "CoreMinimal.h"
#include "Kismet/BlueprintFunctionLibrary.h"
#include "Net/Core/PushModel/PushModel.h"
#include "DirectiveUtilArrayFunctionLibrary.generated.h"
/**
* UDirectiveUtilArrayFunctionLibrary
* A collection of array utility functions that improve the usability of arrays in Blueprints.
*/
UCLASS()
class DIRECTIVEUTILITIESRUNTIME_API UDirectiveUtilArrayFunctionLibrary : public UBlueprintFunctionLibrary
{
GENERATED_BODY()
public:
/**
* Returns the next index in the array.
* If the next index is greater than the last array index and bLoop is enabled, the index will loop back to the start of the array.
* Otherwise, the last index will be returned.
* Returns INDEX_NONE for an empty array.
* @param TargetArray - The array to get the next index for.
* @param Index - The current index.
* @param bLoop - If true, the index will loop back to the beginning of the array when the next index is greater than the last array index.
* Otherwise, the last index will be returned.
* @returns The next index in the array.
*/
UFUNCTION(BlueprintPure, CustomThunk, meta=(DisplayName = "Next Index", CompactNodeTitle = "NEXT INDEX", ArrayParm = "TargetArray", BlueprintThreadSafe), Category="Directive Utilities|Array")
static int32 Array_NextIndex(const TArray<int32>& TargetArray, const int32 Index, const bool bLoop);
/**
* Returns the previous index in the array.
* If the previous index is less than 0 and bLoop is enabled, the index will loop back to the end of the array.
* Otherwise, 0 will be returned.
* Returns INDEX_NONE for an empty array.
* @param TargetArray - The array to get the previous index for.
* @param Index - The current index.
* @param bLoop - If the next index is greater than the last array index and bLoop is enabled, the index will loop back to the start of the array.
* Otherwise, the last index will be returned.
* @returns The previous index in the array.
*/
UFUNCTION(BlueprintPure, CustomThunk, meta=(DisplayName = "Previous Index", CompactNodeTitle = "PREV INDEX", ArrayParm = "TargetArray", BlueprintThreadSafe), Category="Directive Utilities|Array")
static int32 Array_PreviousIndex(const TArray<int32>& TargetArray, const int32 Index, const bool bLoop);
/**
* Removes duplicate elements from the array in-place.
* @param TargetArray - The array to remove duplicates from.
*/
UFUNCTION(BlueprintCallable, CustomThunk, meta=(DisplayName = "Remove Duplicates", CompactNodeTitle = "REMOVE DUPLICATES", ArrayParm = "TargetArray"), Category="Directive Utilities|Array")
static void Array_RemoveDuplicates(const TArray<int32>& TargetArray);
/**
* Returns a copy of the first element of the array.
* @param TargetArray - The array to read from.
* @param OutItem - [out] A copy of the first element, or the default value if the array is empty.
* @returns True if the array contained a valid first element.
*/
UFUNCTION(BlueprintPure, CustomThunk, meta=(DisplayName = "Get Valid First Array Item (Copy)", ArrayParm = "TargetArray", ArrayTypeDependentParams = "OutItem", BlueprintThreadSafe), Category="Directive Utilities|Array")
static bool Array_GetValidFirstItemCopy(const TArray<int32>& TargetArray, int32& OutItem);
/**
* Returns a copy of the last element of the array.
* @param TargetArray - The array to read from.
* @param OutItem - [out] A copy of the last element, or the default value if the array is empty.
* @returns True if the array contained a valid last element.
*/
UFUNCTION(BlueprintPure, CustomThunk, meta=(DisplayName = "Get Valid Last Array Item (Copy)", ArrayParm = "TargetArray", ArrayTypeDependentParams = "OutItem", BlueprintThreadSafe), Category="Directive Utilities|Array")
static bool Array_GetValidLastItemCopy(const TArray<int32>& TargetArray, int32& OutItem);
/**
* Returns a copy of the element at the given index, if the index is valid.
* @param TargetArray - The array to read from.
* @param Index - The index to read.
* @param OutItem - [out] A copy of the element, or the default value if the index is invalid.
* @returns True if the index was valid.
*/
UFUNCTION(BlueprintPure, CustomThunk, meta=(DisplayName = "Get Valid Array Item From Index (Copy)", ArrayParm = "TargetArray", ArrayTypeDependentParams = "OutItem", BlueprintThreadSafe), Category="Directive Utilities|Array")
static bool Array_GetValidItemFromIndexCopy(const TArray<int32>& TargetArray, const int32 Index, int32& OutItem);
/**
* Returns a copy of a random element from the array.
* @param TargetArray - The array to read from.
* @param OutItem - [out] A copy of the randomly selected element, or the default value if the array is empty.
* @param OutIndex - [out] The index of the selected element, or INDEX_NONE if the array is empty.
* @returns True if a valid element was selected.
*/
UFUNCTION(BlueprintCallable, CustomThunk, meta=(DisplayName = "Get Random Valid Array Item", CompactNodeTitle = "RANDOM", ArrayParm = "TargetArray", ArrayTypeDependentParams = "OutItem"), Category="Directive Utilities|Array")
static bool Array_GetRandomItem(const TArray<int32>& TargetArray, int32& OutItem, int32& OutIndex);
/**
* Returns a copy of the last element of the array without removing it.
* @param TargetArray - The array to read from.
* @param OutItem - [out] A copy of the last element, or the default value if the array is empty.
*/
UFUNCTION(BlueprintPure, CustomThunk, meta=(DisplayName = "Last Value", CompactNodeTitle = "LAST", ArrayParm = "TargetArray", ArrayTypeDependentParams = "OutItem", BlueprintThreadSafe), Category="Directive Utilities|Array")
static void Array_LastValue(const TArray<int32>& TargetArray, int32& OutItem);
/**
* Removes the last element of the array and returns a copy of it.
* @param TargetArray - The array to pop from.
* @param OutItem - [out] A copy of the removed element, or the default value if the array is empty.
* @returns True if an element was removed.
*/
UFUNCTION(BlueprintCallable, CustomThunk, meta=(DisplayName = "Pop", ArrayParm = "TargetArray", ArrayTypeDependentParams = "OutItem"), Category="Directive Utilities|Array")
static bool Array_Pop(const TArray<int32>& TargetArray, int32& OutItem);
/**
* Removes the first element of the array and returns a copy of it.
* @param TargetArray - The array to pop from.
* @param OutItem - [out] A copy of the removed element, or the default value if the array is empty.
* @returns True if an element was removed.
*/
UFUNCTION(BlueprintCallable, CustomThunk, meta=(DisplayName = "Pop First", ArrayParm = "TargetArray", ArrayTypeDependentParams = "OutItem"), Category="Directive Utilities|Array")
static bool Array_PopFirst(const TArray<int32>& TargetArray, int32& OutItem);
/**
* Removes the element at the given index by swapping it with the last element (does not preserve order).
* This is O(1) but changes the position of the previously-last element.
* @param TargetArray - The array to remove from.
* @param Index - The index to remove.
* @returns True if the index was valid and an element was removed.
*/
UFUNCTION(BlueprintCallable, CustomThunk, meta=(DisplayName = "Remove At Swap", ArrayParm = "TargetArray"), Category="Directive Utilities|Array")
static bool Array_RemoveAtSwap(const TArray<int32>& TargetArray, const int32 Index);
/**
* Returns a copy of a contiguous range of the array. The range is clamped to the array bounds.
* @param TargetArray - The array to slice.
* @param StartIndex - The index to start copying from (clamped to [0, Length]).
* @param Count - The number of elements to copy. Values <= 0 produce an empty array.
* @param OutArray - [out] The sliced copy.
*/
UFUNCTION(BlueprintPure, CustomThunk, meta=(DisplayName = "Slice", ArrayParm = "TargetArray,OutArray", ArrayTypeDependentParams = "OutArray", BlueprintThreadSafe), Category="Directive Utilities|Array")
static void Array_Slice(const TArray<int32>& TargetArray, const int32 StartIndex, const int32 Count, TArray<int32>& OutArray);
/**
* Cyclically rotates the elements of the array in place.
* @param TargetArray - The array to rotate.
* @param Shift - The number of positions to rotate. Positive rotates toward the end; negative toward the start.
*/
UFUNCTION(BlueprintCallable, CustomThunk, meta=(DisplayName = "Rotate", ArrayParm = "TargetArray"), Category="Directive Utilities|Array")
static void Array_Rotate(const TArray<int32>& TargetArray, const int32 Shift);
/**
* Returns a copy of the array with duplicates removed, keeping the first occurrence and preserving order.
* Unlike Remove Duplicates, this does not modify the input array.
* @param TargetArray - The array to read from.
* @param OutArray - [out] The de-duplicated copy.
*/
UFUNCTION(BlueprintPure, CustomThunk, meta=(DisplayName = "Get Distinct (Copy)", ArrayParm = "TargetArray,OutArray", ArrayTypeDependentParams = "OutArray", BlueprintThreadSafe), Category="Directive Utilities|Array")
static void Array_GetDistinct(const TArray<int32>& TargetArray, TArray<int32>& OutArray);
/**
* Counts how many times an item appears in the array.
* @param TargetArray - The array to search.
* @param ItemToCount - The item to count.
* @returns The number of occurrences.
*/
UFUNCTION(BlueprintPure, CustomThunk, meta=(DisplayName = "Count Occurrences", ArrayParm = "TargetArray", ArrayTypeDependentParams = "ItemToCount", AutoCreateRefTerm = "ItemToCount", BlueprintThreadSafe), Category="Directive Utilities|Array")
static int32 Array_CountOccurrences(const TArray<int32>& TargetArray, const int32& ItemToCount);
/**
* Returns the most frequently occurring element of the array (ties resolve to the earliest such element).
* @param TargetArray - The array to read from.
* @param OutItem - [out] A copy of the most common element, or the default value if the array is empty.
* @param OutCount - [out] The number of times the most common element occurs.
* @returns True if the array was non-empty.
*/
UFUNCTION(BlueprintPure, CustomThunk, meta=(DisplayName = "Get Most Common", ArrayParm = "TargetArray", ArrayTypeDependentParams = "OutItem", BlueprintThreadSafe), Category="Directive Utilities|Array")
static bool Array_GetMostCommon(const TArray<int32>& TargetArray, int32& OutItem, int32& OutCount);
/*~
* Native functions that will be called by the below custom thunk layers, which read off the property address and call the appropriate native handler.
* Based off UKismetArrayLibrary implementation
~*/
static int32 GenericArray_NextIndex(const void* TargetArray, const FArrayProperty* ArrayProperty, int32 Index, bool bLoop);
static int32 GenericArray_PreviousIndex(const void* TargetArray, const FArrayProperty* ArrayProperty, int32 Index, bool bLoop);
static void GenericArray_RemoveDuplicates(void* TargetArray, const FArrayProperty* ArrayProperty);
static bool GenericArray_GetItemAtIndex(const void* TargetArray, const FArrayProperty* ArrayProperty, int32 Index, void* OutItemPtr);
static bool GenericArray_GetFirstItem(const void* TargetArray, const FArrayProperty* ArrayProperty, void* OutItemPtr);
static bool GenericArray_GetLastItem(const void* TargetArray, const FArrayProperty* ArrayProperty, void* OutItemPtr);
static bool GenericArray_GetRandomItem(const void* TargetArray, const FArrayProperty* ArrayProperty, void* OutItemPtr, int32* OutIndex);
static bool GenericArray_Pop(void* TargetArray, const FArrayProperty* ArrayProperty, void* OutItemPtr);
static bool GenericArray_PopFirst(void* TargetArray, const FArrayProperty* ArrayProperty, void* OutItemPtr);
static bool GenericArray_RemoveAtSwap(void* TargetArray, const FArrayProperty* ArrayProperty, int32 Index);
static void GenericArray_Slice(const void* TargetArray, const FArrayProperty* TargetArrayProperty, int32 StartIndex, int32 Count, void* OutArray, const FArrayProperty* OutArrayProperty);
static void GenericArray_Rotate(void* TargetArray, const FArrayProperty* ArrayProperty, int32 Shift);
static void GenericArray_GetDistinct(const void* TargetArray, const FArrayProperty* TargetArrayProperty, void* OutArray, const FArrayProperty* OutArrayProperty);
static int32 GenericArray_CountOccurrences(const void* TargetArray, const FArrayProperty* ArrayProperty, const void* ItemToCount);
static bool GenericArray_GetMostCommon(const void* TargetArray, const FArrayProperty* ArrayProperty, void* OutItemPtr, int32* OutCount);
/*~
* Custom thunk layers that read off the property address and call the appropriate native handler.
* Based off UKismetArrayLibrary implementation
~*/
DECLARE_FUNCTION(execArray_NextIndex)
{
Stack.MostRecentProperty = nullptr;
Stack.StepCompiledIn<FArrayProperty>(nullptr);
const void* ArrayAddr = Stack.MostRecentPropertyAddress;
const FArrayProperty* ArrayProperty = CastField<FArrayProperty>(Stack.MostRecentProperty);
if (!ArrayProperty)
{
Stack.bArrayContextFailed = true;
return;
}
P_GET_PROPERTY(FIntProperty, Index);
P_GET_UBOOL(bLoop);
P_FINISH;
P_NATIVE_BEGIN;
*static_cast<int32*>(RESULT_PARAM) = GenericArray_NextIndex(ArrayAddr, ArrayProperty, Index, bLoop);
P_NATIVE_END;
}
DECLARE_FUNCTION(execArray_PreviousIndex)
{
Stack.MostRecentProperty = nullptr;
Stack.StepCompiledIn<FArrayProperty>(nullptr);
const void* ArrayAddr = Stack.MostRecentPropertyAddress;
const FArrayProperty* ArrayProperty = CastField<FArrayProperty>(Stack.MostRecentProperty);
if (!ArrayProperty)
{
Stack.bArrayContextFailed = true;
return;
}
P_GET_PROPERTY(FIntProperty, Index);
P_GET_UBOOL(bLoop);
P_FINISH;
P_NATIVE_BEGIN;
*static_cast<int32*>(RESULT_PARAM) = GenericArray_PreviousIndex(ArrayAddr, ArrayProperty, Index, bLoop);
P_NATIVE_END;
}
DECLARE_FUNCTION(execArray_RemoveDuplicates)
{
Stack.MostRecentProperty = nullptr;
Stack.StepCompiledIn<FArrayProperty>(nullptr);
void* ArrayAddr = Stack.MostRecentPropertyAddress;
FArrayProperty* ArrayProperty = CastField<FArrayProperty>(Stack.MostRecentProperty);
if (!ArrayProperty)
{
Stack.bArrayContextFailed = true;
return;
}
P_FINISH;
P_NATIVE_BEGIN;
MARK_PROPERTY_DIRTY(Stack.Object, ArrayProperty);
GenericArray_RemoveDuplicates(ArrayAddr, ArrayProperty);
P_NATIVE_END;
}
DECLARE_FUNCTION(execArray_GetValidFirstItemCopy)
{
Stack.MostRecentProperty = nullptr;
Stack.StepCompiledIn<FArrayProperty>(nullptr);
const void* ArrayAddr = Stack.MostRecentPropertyAddress;
const FArrayProperty* ArrayProperty = CastField<FArrayProperty>(Stack.MostRecentProperty);
if (!ArrayProperty)
{
Stack.bArrayContextFailed = true;
return;
}
const FProperty* InnerProp = ArrayProperty->Inner;
const int32 PropertySize = InnerProp->GetElementSize() * InnerProp->ArrayDim;
void* StorageSpace = FMemory_Alloca(PropertySize);
InnerProp->InitializeValue(StorageSpace);
Stack.MostRecentPropertyAddress = nullptr;
Stack.MostRecentPropertyContainer = nullptr;
Stack.StepCompiledIn<FProperty>(StorageSpace);
void* ItemPtr;
if (Stack.MostRecentPropertyAddress != nullptr && Stack.MostRecentProperty != nullptr
&& PropertySize == Stack.MostRecentProperty->GetElementSize() * Stack.MostRecentProperty->ArrayDim
&& (Stack.MostRecentProperty->GetClass()->IsChildOf(InnerProp->GetClass())
|| InnerProp->GetClass()->IsChildOf(Stack.MostRecentProperty->GetClass())))
{
ItemPtr = Stack.MostRecentPropertyAddress;
}
else
{
ItemPtr = StorageSpace;
}
P_FINISH;
P_NATIVE_BEGIN;
*static_cast<bool*>(RESULT_PARAM) = GenericArray_GetFirstItem(ArrayAddr, ArrayProperty, ItemPtr);
P_NATIVE_END;
InnerProp->DestroyValue(StorageSpace);
}
DECLARE_FUNCTION(execArray_GetValidLastItemCopy)
{
Stack.MostRecentProperty = nullptr;
Stack.StepCompiledIn<FArrayProperty>(nullptr);
const void* ArrayAddr = Stack.MostRecentPropertyAddress;
const FArrayProperty* ArrayProperty = CastField<FArrayProperty>(Stack.MostRecentProperty);
if (!ArrayProperty)
{
Stack.bArrayContextFailed = true;
return;
}
const FProperty* InnerProp = ArrayProperty->Inner;
const int32 PropertySize = InnerProp->GetElementSize() * InnerProp->ArrayDim;
void* StorageSpace = FMemory_Alloca(PropertySize);
InnerProp->InitializeValue(StorageSpace);
Stack.MostRecentPropertyAddress = nullptr;
Stack.MostRecentPropertyContainer = nullptr;
Stack.StepCompiledIn<FProperty>(StorageSpace);
void* ItemPtr;
if (Stack.MostRecentPropertyAddress != nullptr && Stack.MostRecentProperty != nullptr
&& PropertySize == Stack.MostRecentProperty->GetElementSize() * Stack.MostRecentProperty->ArrayDim
&& (Stack.MostRecentProperty->GetClass()->IsChildOf(InnerProp->GetClass())
|| InnerProp->GetClass()->IsChildOf(Stack.MostRecentProperty->GetClass())))
{
ItemPtr = Stack.MostRecentPropertyAddress;
}
else
{
ItemPtr = StorageSpace;
}
P_FINISH;
P_NATIVE_BEGIN;
*static_cast<bool*>(RESULT_PARAM) = GenericArray_GetLastItem(ArrayAddr, ArrayProperty, ItemPtr);
P_NATIVE_END;
InnerProp->DestroyValue(StorageSpace);
}
DECLARE_FUNCTION(execArray_GetValidItemFromIndexCopy)
{
Stack.MostRecentProperty = nullptr;
Stack.StepCompiledIn<FArrayProperty>(nullptr);
const void* ArrayAddr = Stack.MostRecentPropertyAddress;
const FArrayProperty* ArrayProperty = CastField<FArrayProperty>(Stack.MostRecentProperty);
if (!ArrayProperty)
{
Stack.bArrayContextFailed = true;
return;
}
P_GET_PROPERTY(FIntProperty, Index);
const FProperty* InnerProp = ArrayProperty->Inner;
const int32 PropertySize = InnerProp->GetElementSize() * InnerProp->ArrayDim;
void* StorageSpace = FMemory_Alloca(PropertySize);
InnerProp->InitializeValue(StorageSpace);
Stack.MostRecentPropertyAddress = nullptr;
Stack.MostRecentPropertyContainer = nullptr;
Stack.StepCompiledIn<FProperty>(StorageSpace);
void* ItemPtr;
if (Stack.MostRecentPropertyAddress != nullptr && Stack.MostRecentProperty != nullptr
&& PropertySize == Stack.MostRecentProperty->GetElementSize() * Stack.MostRecentProperty->ArrayDim
&& (Stack.MostRecentProperty->GetClass()->IsChildOf(InnerProp->GetClass())
|| InnerProp->GetClass()->IsChildOf(Stack.MostRecentProperty->GetClass())))
{
ItemPtr = Stack.MostRecentPropertyAddress;
}
else
{
ItemPtr = StorageSpace;
}
P_FINISH;
P_NATIVE_BEGIN;
*static_cast<bool*>(RESULT_PARAM) = GenericArray_GetItemAtIndex(ArrayAddr, ArrayProperty, Index, ItemPtr);
P_NATIVE_END;
InnerProp->DestroyValue(StorageSpace);
}
DECLARE_FUNCTION(execArray_GetRandomItem)
{
Stack.MostRecentProperty = nullptr;
Stack.StepCompiledIn<FArrayProperty>(nullptr);
const void* ArrayAddr = Stack.MostRecentPropertyAddress;
const FArrayProperty* ArrayProperty = CastField<FArrayProperty>(Stack.MostRecentProperty);
if (!ArrayProperty)
{
Stack.bArrayContextFailed = true;
return;
}
const FProperty* InnerProp = ArrayProperty->Inner;
const int32 PropertySize = InnerProp->GetElementSize() * InnerProp->ArrayDim;
void* StorageSpace = FMemory_Alloca(PropertySize);
InnerProp->InitializeValue(StorageSpace);
Stack.MostRecentPropertyAddress = nullptr;
Stack.MostRecentPropertyContainer = nullptr;
Stack.StepCompiledIn<FProperty>(StorageSpace);
void* ItemPtr;
if (Stack.MostRecentPropertyAddress != nullptr && Stack.MostRecentProperty != nullptr
&& PropertySize == Stack.MostRecentProperty->GetElementSize() * Stack.MostRecentProperty->ArrayDim
&& (Stack.MostRecentProperty->GetClass()->IsChildOf(InnerProp->GetClass())
|| InnerProp->GetClass()->IsChildOf(Stack.MostRecentProperty->GetClass())))
{
ItemPtr = Stack.MostRecentPropertyAddress;
}
else
{
ItemPtr = StorageSpace;
}
Stack.MostRecentProperty = nullptr;
Stack.MostRecentPropertyAddress = nullptr;
Stack.StepCompiledIn<FProperty>(nullptr);
int32* OutIndex = reinterpret_cast<int32*>(Stack.MostRecentPropertyAddress);
P_FINISH;
P_NATIVE_BEGIN;
*static_cast<bool*>(RESULT_PARAM) = GenericArray_GetRandomItem(ArrayAddr, ArrayProperty, ItemPtr, OutIndex);
P_NATIVE_END;
InnerProp->DestroyValue(StorageSpace);
}
DECLARE_FUNCTION(execArray_LastValue)
{
Stack.MostRecentProperty = nullptr;
Stack.StepCompiledIn<FArrayProperty>(nullptr);
const void* ArrayAddr = Stack.MostRecentPropertyAddress;
const FArrayProperty* ArrayProperty = CastField<FArrayProperty>(Stack.MostRecentProperty);
if (!ArrayProperty)
{
Stack.bArrayContextFailed = true;
return;
}
const FProperty* InnerProp = ArrayProperty->Inner;
const int32 PropertySize = InnerProp->GetElementSize() * InnerProp->ArrayDim;
void* StorageSpace = FMemory_Alloca(PropertySize);
InnerProp->InitializeValue(StorageSpace);
Stack.MostRecentPropertyAddress = nullptr;
Stack.MostRecentPropertyContainer = nullptr;
Stack.StepCompiledIn<FProperty>(StorageSpace);
void* ItemPtr;
if (Stack.MostRecentPropertyAddress != nullptr && Stack.MostRecentProperty != nullptr
&& PropertySize == Stack.MostRecentProperty->GetElementSize() * Stack.MostRecentProperty->ArrayDim
&& (Stack.MostRecentProperty->GetClass()->IsChildOf(InnerProp->GetClass())
|| InnerProp->GetClass()->IsChildOf(Stack.MostRecentProperty->GetClass())))
{
ItemPtr = Stack.MostRecentPropertyAddress;
}
else
{
ItemPtr = StorageSpace;
}
P_FINISH;
P_NATIVE_BEGIN;
GenericArray_GetLastItem(ArrayAddr, ArrayProperty, ItemPtr);
P_NATIVE_END;
InnerProp->DestroyValue(StorageSpace);
}
DECLARE_FUNCTION(execArray_Pop)
{
Stack.MostRecentProperty = nullptr;
Stack.StepCompiledIn<FArrayProperty>(nullptr);
void* ArrayAddr = Stack.MostRecentPropertyAddress;
FArrayProperty* ArrayProperty = CastField<FArrayProperty>(Stack.MostRecentProperty);
if (!ArrayProperty)
{
Stack.bArrayContextFailed = true;
return;
}
const FProperty* InnerProp = ArrayProperty->Inner;
const int32 PropertySize = InnerProp->GetElementSize() * InnerProp->ArrayDim;
void* StorageSpace = FMemory_Alloca(PropertySize);
InnerProp->InitializeValue(StorageSpace);
Stack.MostRecentPropertyAddress = nullptr;
Stack.MostRecentPropertyContainer = nullptr;
Stack.StepCompiledIn<FProperty>(StorageSpace);
void* ItemPtr;
if (Stack.MostRecentPropertyAddress != nullptr && Stack.MostRecentProperty != nullptr
&& PropertySize == Stack.MostRecentProperty->GetElementSize() * Stack.MostRecentProperty->ArrayDim
&& (Stack.MostRecentProperty->GetClass()->IsChildOf(InnerProp->GetClass())
|| InnerProp->GetClass()->IsChildOf(Stack.MostRecentProperty->GetClass())))
{
ItemPtr = Stack.MostRecentPropertyAddress;
}
else
{
ItemPtr = StorageSpace;
}
P_FINISH;
P_NATIVE_BEGIN;
MARK_PROPERTY_DIRTY(Stack.Object, ArrayProperty);
*static_cast<bool*>(RESULT_PARAM) = GenericArray_Pop(ArrayAddr, ArrayProperty, ItemPtr);
P_NATIVE_END;
InnerProp->DestroyValue(StorageSpace);
}
DECLARE_FUNCTION(execArray_PopFirst)
{
Stack.MostRecentProperty = nullptr;
Stack.StepCompiledIn<FArrayProperty>(nullptr);
void* ArrayAddr = Stack.MostRecentPropertyAddress;
FArrayProperty* ArrayProperty = CastField<FArrayProperty>(Stack.MostRecentProperty);
if (!ArrayProperty)
{
Stack.bArrayContextFailed = true;
return;
}
const FProperty* InnerProp = ArrayProperty->Inner;
const int32 PropertySize = InnerProp->GetElementSize() * InnerProp->ArrayDim;
void* StorageSpace = FMemory_Alloca(PropertySize);
InnerProp->InitializeValue(StorageSpace);
Stack.MostRecentPropertyAddress = nullptr;
Stack.MostRecentPropertyContainer = nullptr;
Stack.StepCompiledIn<FProperty>(StorageSpace);
void* ItemPtr;
if (Stack.MostRecentPropertyAddress != nullptr && Stack.MostRecentProperty != nullptr
&& PropertySize == Stack.MostRecentProperty->GetElementSize() * Stack.MostRecentProperty->ArrayDim
&& (Stack.MostRecentProperty->GetClass()->IsChildOf(InnerProp->GetClass())
|| InnerProp->GetClass()->IsChildOf(Stack.MostRecentProperty->GetClass())))
{
ItemPtr = Stack.MostRecentPropertyAddress;
}
else
{
ItemPtr = StorageSpace;
}
P_FINISH;
P_NATIVE_BEGIN;
MARK_PROPERTY_DIRTY(Stack.Object, ArrayProperty);
*static_cast<bool*>(RESULT_PARAM) = GenericArray_PopFirst(ArrayAddr, ArrayProperty, ItemPtr);
P_NATIVE_END;
InnerProp->DestroyValue(StorageSpace);
}
DECLARE_FUNCTION(execArray_RemoveAtSwap)
{
Stack.MostRecentProperty = nullptr;
Stack.StepCompiledIn<FArrayProperty>(nullptr);
void* ArrayAddr = Stack.MostRecentPropertyAddress;
FArrayProperty* ArrayProperty = CastField<FArrayProperty>(Stack.MostRecentProperty);
if (!ArrayProperty)
{
Stack.bArrayContextFailed = true;
return;
}
P_GET_PROPERTY(FIntProperty, Index);
P_FINISH;
P_NATIVE_BEGIN;
MARK_PROPERTY_DIRTY(Stack.Object, ArrayProperty);
*static_cast<bool*>(RESULT_PARAM) = GenericArray_RemoveAtSwap(ArrayAddr, ArrayProperty, Index);
P_NATIVE_END;
}
DECLARE_FUNCTION(execArray_Slice)
{
Stack.MostRecentProperty = nullptr;
Stack.StepCompiledIn<FArrayProperty>(nullptr);
const void* SourceArrayAddr = Stack.MostRecentPropertyAddress;
const FArrayProperty* SourceArrayProperty = CastField<FArrayProperty>(Stack.MostRecentProperty);
if (!SourceArrayProperty)
{
Stack.bArrayContextFailed = true;
return;
}
P_GET_PROPERTY(FIntProperty, StartIndex);
P_GET_PROPERTY(FIntProperty, Count);
Stack.MostRecentProperty = nullptr;
Stack.StepCompiledIn<FArrayProperty>(nullptr);
void* OutArrayAddr = Stack.MostRecentPropertyAddress;
const FArrayProperty* OutArrayProperty = CastField<FArrayProperty>(Stack.MostRecentProperty);
if (!OutArrayProperty)
{
Stack.bArrayContextFailed = true;
return;
}
P_FINISH;
P_NATIVE_BEGIN;
GenericArray_Slice(SourceArrayAddr, SourceArrayProperty, StartIndex, Count, OutArrayAddr, OutArrayProperty);
P_NATIVE_END;
}
DECLARE_FUNCTION(execArray_Rotate)
{
Stack.MostRecentProperty = nullptr;
Stack.StepCompiledIn<FArrayProperty>(nullptr);
void* ArrayAddr = Stack.MostRecentPropertyAddress;
FArrayProperty* ArrayProperty = CastField<FArrayProperty>(Stack.MostRecentProperty);
if (!ArrayProperty)
{
Stack.bArrayContextFailed = true;
return;
}
P_GET_PROPERTY(FIntProperty, Shift);
P_FINISH;
P_NATIVE_BEGIN;
MARK_PROPERTY_DIRTY(Stack.Object, ArrayProperty);
GenericArray_Rotate(ArrayAddr, ArrayProperty, Shift);
P_NATIVE_END;
}
DECLARE_FUNCTION(execArray_GetDistinct)
{
Stack.MostRecentProperty = nullptr;
Stack.StepCompiledIn<FArrayProperty>(nullptr);
const void* SourceArrayAddr = Stack.MostRecentPropertyAddress;
const FArrayProperty* SourceArrayProperty = CastField<FArrayProperty>(Stack.MostRecentProperty);
if (!SourceArrayProperty)
{
Stack.bArrayContextFailed = true;
return;
}
Stack.MostRecentProperty = nullptr;
Stack.StepCompiledIn<FArrayProperty>(nullptr);
void* OutArrayAddr = Stack.MostRecentPropertyAddress;
const FArrayProperty* OutArrayProperty = CastField<FArrayProperty>(Stack.MostRecentProperty);
if (!OutArrayProperty)
{
Stack.bArrayContextFailed = true;
return;
}
P_FINISH;
P_NATIVE_BEGIN;
GenericArray_GetDistinct(SourceArrayAddr, SourceArrayProperty, OutArrayAddr, OutArrayProperty);
P_NATIVE_END;
}
DECLARE_FUNCTION(execArray_CountOccurrences)
{
Stack.MostRecentProperty = nullptr;
Stack.StepCompiledIn<FArrayProperty>(nullptr);
const void* ArrayAddr = Stack.MostRecentPropertyAddress;
const FArrayProperty* ArrayProperty = CastField<FArrayProperty>(Stack.MostRecentProperty);
if (!ArrayProperty)
{
Stack.bArrayContextFailed = true;
return;
}
const FProperty* InnerProp = ArrayProperty->Inner;
const int32 PropertySize = InnerProp->GetElementSize() * InnerProp->ArrayDim;
void* StorageSpace = FMemory_Alloca(PropertySize);
InnerProp->InitializeValue(StorageSpace);
Stack.MostRecentPropertyAddress = nullptr;
Stack.MostRecentPropertyContainer = nullptr;
Stack.StepCompiledIn<FProperty>(StorageSpace);
P_FINISH;
P_NATIVE_BEGIN;
*static_cast<int32*>(RESULT_PARAM) = GenericArray_CountOccurrences(ArrayAddr, ArrayProperty, StorageSpace);
P_NATIVE_END;
InnerProp->DestroyValue(StorageSpace);
}
DECLARE_FUNCTION(execArray_GetMostCommon)
{
Stack.MostRecentProperty = nullptr;
Stack.StepCompiledIn<FArrayProperty>(nullptr);
const void* ArrayAddr = Stack.MostRecentPropertyAddress;
const FArrayProperty* ArrayProperty = CastField<FArrayProperty>(Stack.MostRecentProperty);
if (!ArrayProperty)
{
Stack.bArrayContextFailed = true;
return;
}
const FProperty* InnerProp = ArrayProperty->Inner;
const int32 PropertySize = InnerProp->GetElementSize() * InnerProp->ArrayDim;
void* StorageSpace = FMemory_Alloca(PropertySize);
InnerProp->InitializeValue(StorageSpace);
Stack.MostRecentPropertyAddress = nullptr;
Stack.MostRecentPropertyContainer = nullptr;
Stack.StepCompiledIn<FProperty>(StorageSpace);
void* ItemPtr;
if (Stack.MostRecentPropertyAddress != nullptr && Stack.MostRecentProperty != nullptr
&& PropertySize == Stack.MostRecentProperty->GetElementSize() * Stack.MostRecentProperty->ArrayDim
&& (Stack.MostRecentProperty->GetClass()->IsChildOf(InnerProp->GetClass())
|| InnerProp->GetClass()->IsChildOf(Stack.MostRecentProperty->GetClass())))
{
ItemPtr = Stack.MostRecentPropertyAddress;
}
else
{
ItemPtr = StorageSpace;
}
Stack.MostRecentProperty = nullptr;
Stack.MostRecentPropertyAddress = nullptr;
Stack.StepCompiledIn<FProperty>(nullptr);
int32* OutCount = reinterpret_cast<int32*>(Stack.MostRecentPropertyAddress);
P_FINISH;
P_NATIVE_BEGIN;
*static_cast<bool*>(RESULT_PARAM) = GenericArray_GetMostCommon(ArrayAddr, ArrayProperty, ItemPtr, OutCount);
P_NATIVE_END;
InnerProp->DestroyValue(StorageSpace);
}
};

View File

@@ -0,0 +1,104 @@
// Copyright (c) 2026 Unreal Directive. Licensed under the MIT License.
#pragma once
#include "CoreMinimal.h"
#include "Kismet/BlueprintFunctionLibrary.h"
#include "DirectiveUtilFunctionLibrary.generated.h"
/**
* UDirectiveUtilFunctionLibrary
*
* The primary function library for the Directive Utilities plugin.
*/
UCLASS()
class DIRECTIVEUTILITIESRUNTIME_API UDirectiveUtilFunctionLibrary : public UBlueprintFunctionLibrary
{
GENERATED_BODY()
public:
/**
* Returns a list of classes derived from the given base class (not limited to Actors).
* This exposes the built-in GetDerivedClasses function to blueprints.
* @param BaseClass The base class to get the derived classes for.
* @param bRecursive Whether to include derived classes of derived classes.
* @param DerivedClasses The list of derived classes.
*/
UFUNCTION(BlueprintPure, Category = "Directive Utilities|Utility")
static void GetChildClasses(const UClass* BaseClass, bool bRecursive, TArray<UClass*>& DerivedClasses);
/**
* Copy the provided text to the clipboard.
* @param Text - The text to copy to the clipboard.
*/
UFUNCTION(BlueprintCallable, Category = "Directive Utilities|Clipboard" )
static void CopyTextToClipboard(const FText& Text);
/**
* Copy the provided string to the clipboard.
* @param String - The string to copy to the clipboard.
*/
UFUNCTION(BlueprintCallable, Category = "Directive Utilities|Clipboard" )
static void CopyStringToClipboard(const FString& String);
/**
* Get the content from the clipboard as FText.
* @returns The text from the clipboard.
*/
UFUNCTION(BlueprintPure, Category = "Directive Utilities|Clipboard" )
static FText GetTextFromClipboard();
/**
* Get the content from the clipboard as an FString.
* @returns The content from the clipboard as a string.
*/
UFUNCTION(BlueprintPure, Category = "Directive Utilities|Clipboard" )
static FString GetStringFromClipboard();
/**
* Clear the clipboard.
*/
UFUNCTION(BlueprintCallable, Category = "Directive Utilities|Clipboard")
static void ClearClipboard();
/**
* Get the project version as a string.
* @returns The project version as a string.
*/
UFUNCTION(BlueprintPure, Category = "Directive Utilities|Utility")
static FString GetProjectVersion();
/**
* Returns true if the game is running in the Unreal Editor.
* @note This will return false in packaged/standalone builds.
* @returns True if running in the editor.
*/
UFUNCTION(BlueprintPure, Category = "Directive Utilities|Utility")
static bool IsRunningInEditor();
/**
* Checks whether a switch (e.g. "MySwitch" matching "-MySwitch") was passed on the
* process command line. Matching is case-insensitive.
* @param Switch - The switch name, without the leading dash.
* @returns True if the switch is present.
*/
UFUNCTION(BlueprintPure, Category = "Directive Utilities|Utility")
static bool HasCommandLineSwitch(const FString& Switch);
/**
* Reads a key=value option (e.g. "MyKey" matching "-MyKey=Value") from the process
* command line. Matching is case-insensitive; quoted values are returned without the quotes.
* @param Key - The key name, without the leading dash or trailing equals sign.
* @param OutValue - [out] The option's value, or empty if the key is missing.
* @returns True if the key was present.
*/
UFUNCTION(BlueprintPure, Category = "Directive Utilities|Utility")
static bool GetCommandLineOption(const FString& Key, FString& OutValue);
/** Core of Has Command Line Switch that checks an explicit command line. */
static bool HasCommandLineSwitch(const TCHAR* CommandLine, const FString& Switch);
/** Core of Get Command Line Option that reads from an explicit command line. */
static bool GetCommandLineOption(const TCHAR* CommandLine, const FString& Key, FString& OutValue);
};

View File

@@ -0,0 +1,124 @@
// Copyright (c) 2026 Unreal Directive. Licensed under the MIT License.
#pragma once
#include "CoreMinimal.h"
#include "Kismet/BlueprintFunctionLibrary.h"
#include "GameplayTagContainer.h"
#include "DirectiveUtilGameplayTagFunctionLibrary.generated.h"
/**
* UDirectiveUtilGameplayTagFunctionLibrary
* Hierarchy navigation helpers for Gameplay Tags that the engine does not expose to Blueprints.
*/
UCLASS()
class DIRECTIVEUTILITIESRUNTIME_API UDirectiveUtilGameplayTagFunctionLibrary : public UBlueprintFunctionLibrary
{
GENERATED_BODY()
public:
/**
* Returns the direct (immediate) parent of a tag, e.g. "A.B.C" returns "A.B".
* @param Tag - The tag to read.
* @returns The direct parent tag, or an invalid tag if the tag is a root or invalid.
*/
UFUNCTION(BlueprintPure, Category = "Directive Utilities|GameplayTags")
static FGameplayTag GetTagDirectParent(const FGameplayTag& Tag);
/**
* Returns all ancestor tags of a tag (its parents, grandparents, etc.), excluding the tag itself.
* e.g. "A.B.C" returns { "A.B", "A" }.
* @param Tag - The tag to read.
* @returns A container of the tag's ancestors.
*/
UFUNCTION(BlueprintPure, Category = "Directive Utilities|GameplayTags", meta = (BlueprintThreadSafe))
static FGameplayTagContainer GetTagParents(const FGameplayTag& Tag);
/**
* Returns the number of segments (hierarchy depth) of a tag, e.g. "A.B.C" returns 3.
* @param Tag - The tag to read.
* @returns The depth, or 0 for an invalid tag.
*/
UFUNCTION(BlueprintPure, Category = "Directive Utilities|GameplayTags", meta = (BlueprintThreadSafe))
static int32 GetTagDepth(const FGameplayTag& Tag);
/**
* Returns the last (leaf) segment of a tag's name, e.g. "A.B.C" returns "C".
* @param Tag - The tag to read.
* @returns The leaf segment, or an empty string for an invalid tag.
*/
UFUNCTION(BlueprintPure, Category = "Directive Utilities|GameplayTags", meta = (BlueprintThreadSafe))
static FString GetTagLeafName(const FGameplayTag& Tag);
/**
* Splits a tag's name into its individual segments, e.g. "A.B.C" returns [ "A", "B", "C" ].
* @param Tag - The tag to read.
* @returns The segments in order, or an empty array for an invalid tag.
*/
UFUNCTION(BlueprintPure, Category = "Directive Utilities|GameplayTags", meta = (BlueprintThreadSafe))
static TArray<FString> GetTagSegments(const FGameplayTag& Tag);
/**
* Returns all registered descendant tags of a tag (children, grandchildren, etc.), excluding the tag itself.
* e.g. "A" returns { "A.B", "A.B.C" } when those tags are registered.
* @param Tag - The tag to read.
* @returns A container of the tag's descendants, or an empty container for an invalid tag.
*/
UFUNCTION(BlueprintPure, Category = "Directive Utilities|GameplayTags", meta = (BlueprintThreadSafe))
static FGameplayTagContainer GetTagChildren(const FGameplayTag& Tag);
/**
* Returns only the direct (immediate) registered children of a tag, e.g. "A" returns "A.B" but not "A.B.C".
* @param Tag - The tag to read.
* @returns A container of the tag's direct children, or an empty container for an invalid tag.
*/
UFUNCTION(BlueprintPure, Category = "Directive Utilities|GameplayTags", meta = (BlueprintThreadSafe))
static FGameplayTagContainer GetTagDirectChildren(const FGameplayTag& Tag);
/**
* Returns the deepest tag that both tags share as an ancestor, e.g. "A.B.C" and "A.B.D" return "A.B".
* The result may be one of the inputs when one tag is an ancestor of the other.
* @param TagA - The first tag.
* @param TagB - The second tag.
* @returns The deepest common ancestor tag, or an invalid tag if the tags share none.
*/
UFUNCTION(BlueprintPure, Category = "Directive Utilities|GameplayTags")
static FGameplayTag GetTagCommonAncestor(const FGameplayTag& TagA, const FGameplayTag& TagB);
/**
* Truncates a tag to its first Depth segments, e.g. "A.B.C" at depth 2 returns "A.B".
* An ancestor of a registered tag is always registered itself.
* @param Tag - The tag to truncate.
* @param Depth - The number of leading segments to keep.
* @returns The truncated tag, the tag itself when Depth >= its depth, or an invalid tag when Depth < 1 or the tag is invalid.
*/
UFUNCTION(BlueprintPure, Category = "Directive Utilities|GameplayTags")
static FGameplayTag GetTagAtDepth(const FGameplayTag& Tag, int32 Depth);
/**
* Returns the registered tags that share a tag's direct parent, excluding the tag itself.
* Enumerating the siblings of a root tag (which has no parent) is not supported and returns an empty container.
* @param Tag - The tag to read.
* @returns A container of the tag's siblings, or an empty container for an invalid or root tag.
*/
UFUNCTION(BlueprintPure, Category = "Directive Utilities|GameplayTags")
static FGameplayTagContainer GetTagSiblings(const FGameplayTag& Tag);
/**
* Checks whether a tag has no registered children.
* @param Tag - The tag to test.
* @returns True if the tag is valid and has no registered descendants; false for an invalid tag.
*/
UFUNCTION(BlueprintPure, Category = "Directive Utilities|GameplayTags", meta = (BlueprintThreadSafe))
static bool IsLeafTag(const FGameplayTag& Tag);
/**
* Finds every registered gameplay tag whose name contains Substring (case-insensitive), in registry order.
* Cost scales with the size of the tag registry; intended for tooling and debug use, not per-frame calls.
* @param Substring - The text to search for. An empty string returns an empty array.
* @returns The matching tags.
*/
UFUNCTION(BlueprintPure, Category = "Directive Utilities|GameplayTags")
static TArray<FGameplayTag> FindRegisteredTags(const FString& Substring);
};

View File

@@ -0,0 +1,101 @@
// Copyright (c) 2026 Unreal Directive. Licensed under the MIT License.
#pragma once
#include "CoreMinimal.h"
#include "Kismet/BlueprintFunctionLibrary.h"
#include "Types/DirectiveUtilInputTypes.h"
#include "Types/DirectiveUtilTypes.h"
#include "DirectiveUtilInputFunctionLibrary.generated.h"
class UEnhancedInputLocalPlayerSubsystem;
/**
* UDirectiveUtilInputFunctionLibrary
* A function library that adds functionality for working with input in Unreal Engine.
*/
UCLASS()
class DIRECTIVEUTILITIESRUNTIME_API UDirectiveUtilInputFunctionLibrary : public UBlueprintFunctionLibrary
{
GENERATED_BODY()
public:
/**
* Apply multiple Input Mapping Contexts.
* @param PlayerController The player controller to add the contexts to. Will attempt to get the LocalPlayer from the controller.
* @param Contexts The contexts to apply.
* @param bClearPrevious Whether to clear all previous contexts before applying the new ones.
* @returns Returns Success if the contexts were successfully applied.
*/
UFUNCTION(BlueprintCallable, Category = "Directive Utilities|Input", meta=(ExpandEnumAsExecs="ReturnValue", DefaultToSelf="PlayerController"))
static EDirectiveUtilSuccessStatus AddInputMappingContexts(
AController* PlayerController,
const TArray<FDirectiveUtilEnhancedInputContextData>& Contexts,
bool bClearPrevious);
/**
* Remove multiple Input Mapping Contexts.
* @param PlayerController The player controller to remove the contexts from. Will attempt to get the LocalPlayer from the controller.
* @param Contexts The contexts to remove.
* @returns Returns Success if the contexts were successfully removed.
*/
UFUNCTION(BlueprintCallable, Category = "Directive Utilities|Input", meta=(ExpandEnumAsExecs="ReturnValue", DefaultToSelf="PlayerController"))
static EDirectiveUtilSuccessStatus RemoveInputMappingContexts(
AController* PlayerController,
const TArray<TSoftObjectPtr<UInputMappingContext>>& Contexts);
/**
* Swap a designated Input Mapping Context with a new one.
* If the previous context is found, it will be removed and the new context will be added.
* If the previous context is not found, the new context will be added at the specified priority.
* @param PlayerController The player controller to swap the contexts on. Will attempt to get the LocalPlayer from the controller.
* @param PreviousContext The context to swap out.
* @param NewContext The context to swap in.
* @param Priority The priority to set the new context to.
* @param bUsePreviousPriority Whether to use the previous context's priority when adding the new context.
* @returns Returns Success if the contexts were successfully swapped.
*/
UFUNCTION(BlueprintCallable, Category = "Directive Utilities|Input", meta=(ExpandEnumAsExecs="ReturnValue", DefaultToSelf="PlayerController"))
static EDirectiveUtilSuccessStatus SwapInputMappingContexts(
AController* PlayerController,
TSoftObjectPtr<UInputMappingContext> PreviousContext,
TSoftObjectPtr<UInputMappingContext> NewContext,
int32 Priority,
bool bUsePreviousPriority);
/**
* Returns the Enhanced Input local player subsystem for the given controller, if available.
* @param PlayerController - The player controller; the Local Player's subsystem is retrieved.
* @returns The Enhanced Input subsystem, or null if the controller has no associated local player subsystem.
*/
UFUNCTION(BlueprintPure, Category = "Directive Utilities|Input", meta=(DefaultToSelf="PlayerController"))
static UEnhancedInputLocalPlayerSubsystem* GetEnhancedInputSubsystem(AController* PlayerController);
/**
* Returns whether the given input mapping context is currently active on the controller.
* @param PlayerController - The player controller to query.
* @param Context - The input mapping context to check.
* @returns True if the context is currently applied.
*/
UFUNCTION(BlueprintPure, Category = "Directive Utilities|Input", meta=(DefaultToSelf="PlayerController"))
static bool IsInputMappingContextActive(AController* PlayerController, TSoftObjectPtr<UInputMappingContext> Context);
/**
* Removes all input mapping contexts from the controller.
* @param PlayerController - The player controller to clear.
* @returns Success if the contexts were cleared.
*/
UFUNCTION(BlueprintCallable, Category = "Directive Utilities|Input", meta=(ExpandEnumAsExecs="ReturnValue", DefaultToSelf="PlayerController"))
static EDirectiveUtilSuccessStatus ClearAllInputMappingContexts(AController* PlayerController);
protected:
/**
* Attempt to get the Enhanced Input Subsystem from the provided controller.
* @param PlayerController - The player controller to get the subsystem from.
* @param EnhancedInput - The Enhanced Input Subsystem to return
* @return True if the subsystem was found.
*/
static bool TryGetEnhancedInputSubsystemFromController(
AController* PlayerController,
UEnhancedInputLocalPlayerSubsystem*& EnhancedInput);
};

View File

@@ -0,0 +1,291 @@
// Copyright (c) 2026 Unreal Directive. Licensed under the MIT License.
#pragma once
#include "CoreMinimal.h"
#include "Kismet/BlueprintFunctionLibrary.h"
#include "Net/Core/PushModel/PushModel.h"
#include "DirectiveUtilMapFunctionLibrary.generated.h"
/**
* UDirectiveUtilMapFunctionLibrary
* A collection of map (TMap) utility functions that improve the usability of maps in Blueprints.
*/
UCLASS()
class DIRECTIVEUTILITIESRUNTIME_API UDirectiveUtilMapFunctionLibrary : public UBlueprintFunctionLibrary
{
GENERATED_BODY()
public:
/**
* Finds the value associated with Key, adding a new default-constructed entry if the key is not present.
* @param TargetMap - The map to search or add to.
* @param Key - The key to look up.
* @param Value - [out] A copy of the existing or newly added value.
*/
UFUNCTION(BlueprintCallable, CustomThunk, meta=(DisplayName = "Find Or Add", CompactNodeTitle = "FIND OR ADD", MapParam = "TargetMap", MapKeyParam = "Key", MapValueParam = "Value", AutoCreateRefTerm = "Key, Value"), Category="Directive Utilities|Map")
static void Map_FindOrAdd(const TMap<int32, int32>& TargetMap, const int32& Key, int32& Value);
/**
* Resets every value in the map to its default while preserving all keys.
* @param TargetMap - The map whose values will be reset.
*/
UFUNCTION(BlueprintCallable, CustomThunk, meta=(DisplayName = "Clear Values", CompactNodeTitle = "CLEAR VALUES", MapParam = "TargetMap"), Category="Directive Utilities|Map")
static void Map_ClearValues(const TMap<int32, int32>& TargetMap);
/**
* Gathers every key whose value is identical to Value, in map order.
* @param TargetMap - The map to search.
* @param Value - The value to look for.
* @param Keys - [out] Every key associated with Value.
*/
UFUNCTION(BlueprintPure, CustomThunk, meta=(DisplayName = "Get Keys By Value", CompactNodeTitle = "KEYS BY VALUE", MapParam = "TargetMap", MapValueParam = "Value", MapKeyParam = "Keys", AutoCreateRefTerm = "Value, Keys"), Category="Directive Utilities|Map")
static void Map_GetKeysByValue(const TMap<int32, int32>& TargetMap, const int32& Value, TArray<int32>& Keys);
/**
* Checks whether any value in the map is identical to Value.
* @param TargetMap - The map to search.
* @param Value - The value to look for.
* @returns True if at least one entry holds Value.
*/
UFUNCTION(BlueprintPure, CustomThunk, meta=(DisplayName = "Has Value", CompactNodeTitle = "HAS VALUE", MapParam = "TargetMap", MapValueParam = "Value", AutoCreateRefTerm = "Value"), Category="Directive Utilities|Map")
static bool Map_HasValue(const TMap<int32, int32>& TargetMap, const int32& Value);
/**
* Removes every key in Keys from the map.
* @param TargetMap - The map to remove from.
* @param Keys - The keys to remove.
* @returns The number of entries that were actually removed.
*/
UFUNCTION(BlueprintCallable, CustomThunk, meta=(DisplayName = "Remove Keys", CompactNodeTitle = "REMOVE KEYS", MapParam = "TargetMap", MapKeyParam = "Keys", AutoCreateRefTerm = "Keys"), Category="Directive Utilities|Map")
static int32 Map_RemoveKeys(const TMap<int32, int32>& TargetMap, const TArray<int32>& Keys);
/**
* Copies every pair from SourceMap into TargetMap. Both maps must share key and value types.
* @param TargetMap - The map to copy into.
* @param SourceMap - The map to copy from.
* @param bOverwriteExisting - If true, keys already present in TargetMap are overwritten with SourceMap's values.
*/
UFUNCTION(BlueprintCallable, CustomThunk, meta=(DisplayName = "Append", CompactNodeTitle = "APPEND", MapParam = "TargetMap|SourceMap"), Category="Directive Utilities|Map")
static void Map_Append(const TMap<int32, int32>& TargetMap, const TMap<int32, int32>& SourceMap, bool bOverwriteExisting = true);
/*~
* Native functions called by the custom thunk layers below, which read off the property address
* and operate on the underlying map. Based off UBlueprintMapLibrary implementation.
~*/
static void GenericMap_FindOrAdd(const void* TargetMap, const FMapProperty* MapProperty, const void* KeyPtr, void* ValuePtr);
static void GenericMap_ClearValues(const void* TargetMap, const FMapProperty* MapProperty);
static void GenericMap_GetKeysByValue(const void* TargetMap, const FMapProperty* MapProperty, const void* ValuePtr, const void* TargetArray, const FArrayProperty* ArrayProperty);
static bool GenericMap_HasValue(const void* TargetMap, const FMapProperty* MapProperty, const void* ValuePtr);
static int32 GenericMap_RemoveKeys(const void* TargetMap, const FMapProperty* MapProperty, const void* TargetArray, const FArrayProperty* ArrayProperty);
static void GenericMap_Append(const void* TargetMap, const FMapProperty* TargetMapProperty, const void* SourceMap, const FMapProperty* SourceMapProperty, bool bOverwriteExisting);
/*~
* Custom thunk layers that read off the property address and call the appropriate native handler.
* Based off UBlueprintMapLibrary implementation.
~*/
DECLARE_FUNCTION(execMap_FindOrAdd)
{
Stack.MostRecentProperty = nullptr;
Stack.StepCompiledIn<FMapProperty>(nullptr);
void* MapAddr = Stack.MostRecentPropertyAddress;
FMapProperty* MapProperty = CastField<FMapProperty>(Stack.MostRecentProperty);
if (!MapProperty)
{
Stack.bArrayContextFailed = true;
return;
}
const FProperty* CurrKeyProp = MapProperty->KeyProp;
const int32 KeyPropertySize = CurrKeyProp->GetElementSize() * CurrKeyProp->ArrayDim;
void* KeyStorageSpace = FMemory_Alloca(KeyPropertySize);
CurrKeyProp->InitializeValue(KeyStorageSpace);
Stack.MostRecentPropertyAddress = nullptr;
Stack.MostRecentPropertyContainer = nullptr;
Stack.StepCompiledIn<FProperty>(KeyStorageSpace);
const FProperty* CurrValueProp = MapProperty->ValueProp;
const int32 ValuePropertySize = CurrValueProp->GetElementSize() * CurrValueProp->ArrayDim;
void* ValueStorageSpace = FMemory_Alloca(ValuePropertySize);
CurrValueProp->InitializeValue(ValueStorageSpace);
Stack.MostRecentPropertyAddress = nullptr;
Stack.MostRecentPropertyContainer = nullptr;
Stack.StepCompiledIn<FProperty>(ValueStorageSpace);
void* ItemPtr;
if (Stack.MostRecentPropertyAddress != nullptr && Stack.MostRecentProperty != nullptr
&& ValuePropertySize == Stack.MostRecentProperty->GetElementSize() * Stack.MostRecentProperty->ArrayDim
&& (Stack.MostRecentProperty->GetClass()->IsChildOf(CurrValueProp->GetClass())
|| CurrValueProp->GetClass()->IsChildOf(Stack.MostRecentProperty->GetClass())))
{
ItemPtr = Stack.MostRecentPropertyAddress;
}
else
{
ItemPtr = ValueStorageSpace;
}
P_FINISH;
P_NATIVE_BEGIN;
MARK_PROPERTY_DIRTY(Stack.Object, MapProperty);
GenericMap_FindOrAdd(MapAddr, MapProperty, KeyStorageSpace, ItemPtr);
P_NATIVE_END;
CurrValueProp->DestroyValue(ValueStorageSpace);
CurrKeyProp->DestroyValue(KeyStorageSpace);
}
DECLARE_FUNCTION(execMap_ClearValues)
{
Stack.MostRecentProperty = nullptr;
Stack.StepCompiledIn<FMapProperty>(nullptr);
void* MapAddr = Stack.MostRecentPropertyAddress;
FMapProperty* MapProperty = CastField<FMapProperty>(Stack.MostRecentProperty);
if (!MapProperty)
{
Stack.bArrayContextFailed = true;
return;
}
P_FINISH;
P_NATIVE_BEGIN;
MARK_PROPERTY_DIRTY(Stack.Object, MapProperty);
GenericMap_ClearValues(MapAddr, MapProperty);
P_NATIVE_END;
}
DECLARE_FUNCTION(execMap_GetKeysByValue)
{
Stack.MostRecentProperty = nullptr;
Stack.StepCompiledIn<FMapProperty>(nullptr);
void* MapAddr = Stack.MostRecentPropertyAddress;
FMapProperty* MapProperty = CastField<FMapProperty>(Stack.MostRecentProperty);
if (!MapProperty)
{
Stack.bArrayContextFailed = true;
return;
}
const FProperty* CurrValueProp = MapProperty->ValueProp;
const int32 ValuePropertySize = CurrValueProp->GetElementSize() * CurrValueProp->ArrayDim;
void* ValueStorageSpace = FMemory_Alloca(ValuePropertySize);
CurrValueProp->InitializeValue(ValueStorageSpace);
Stack.MostRecentPropertyAddress = nullptr;
Stack.MostRecentPropertyContainer = nullptr;
Stack.StepCompiledIn<FProperty>(ValueStorageSpace);
Stack.MostRecentProperty = nullptr;
Stack.StepCompiledIn<FArrayProperty>(nullptr);
void* ArrayAddr = Stack.MostRecentPropertyAddress;
FArrayProperty* ArrayProperty = CastField<FArrayProperty>(Stack.MostRecentProperty);
if (!ArrayProperty || !ArrayProperty->Inner->SameType(MapProperty->KeyProp))
{
Stack.bArrayContextFailed = true;
CurrValueProp->DestroyValue(ValueStorageSpace);
return;
}
P_FINISH;
P_NATIVE_BEGIN;
GenericMap_GetKeysByValue(MapAddr, MapProperty, ValueStorageSpace, ArrayAddr, ArrayProperty);
P_NATIVE_END;
CurrValueProp->DestroyValue(ValueStorageSpace);
}
DECLARE_FUNCTION(execMap_HasValue)
{
Stack.MostRecentProperty = nullptr;
Stack.StepCompiledIn<FMapProperty>(nullptr);
void* MapAddr = Stack.MostRecentPropertyAddress;
FMapProperty* MapProperty = CastField<FMapProperty>(Stack.MostRecentProperty);
if (!MapProperty)
{
Stack.bArrayContextFailed = true;
return;
}
const FProperty* CurrValueProp = MapProperty->ValueProp;
const int32 ValuePropertySize = CurrValueProp->GetElementSize() * CurrValueProp->ArrayDim;
void* ValueStorageSpace = FMemory_Alloca(ValuePropertySize);
CurrValueProp->InitializeValue(ValueStorageSpace);
Stack.MostRecentPropertyAddress = nullptr;
Stack.MostRecentPropertyContainer = nullptr;
Stack.StepCompiledIn<FProperty>(ValueStorageSpace);
P_FINISH;
P_NATIVE_BEGIN;
*static_cast<bool*>(RESULT_PARAM) = GenericMap_HasValue(MapAddr, MapProperty, ValueStorageSpace);
P_NATIVE_END;
CurrValueProp->DestroyValue(ValueStorageSpace);
}
DECLARE_FUNCTION(execMap_RemoveKeys)
{
Stack.MostRecentProperty = nullptr;
Stack.StepCompiledIn<FMapProperty>(nullptr);
void* MapAddr = Stack.MostRecentPropertyAddress;
FMapProperty* MapProperty = CastField<FMapProperty>(Stack.MostRecentProperty);
if (!MapProperty)
{
Stack.bArrayContextFailed = true;
return;
}
Stack.MostRecentProperty = nullptr;
Stack.StepCompiledIn<FArrayProperty>(nullptr);
void* ArrayAddr = Stack.MostRecentPropertyAddress;
FArrayProperty* ArrayProperty = CastField<FArrayProperty>(Stack.MostRecentProperty);
if (!ArrayProperty || !ArrayProperty->Inner->SameType(MapProperty->KeyProp))
{
Stack.bArrayContextFailed = true;
return;
}
P_FINISH;
P_NATIVE_BEGIN;
MARK_PROPERTY_DIRTY(Stack.Object, MapProperty);
*static_cast<int32*>(RESULT_PARAM) = GenericMap_RemoveKeys(MapAddr, MapProperty, ArrayAddr, ArrayProperty);
P_NATIVE_END;
}
DECLARE_FUNCTION(execMap_Append)
{
Stack.MostRecentProperty = nullptr;
Stack.StepCompiledIn<FMapProperty>(nullptr);
void* TargetMapAddr = Stack.MostRecentPropertyAddress;
FMapProperty* TargetMapProperty = CastField<FMapProperty>(Stack.MostRecentProperty);
if (!TargetMapProperty)
{
Stack.bArrayContextFailed = true;
return;
}
Stack.MostRecentProperty = nullptr;
Stack.StepCompiledIn<FMapProperty>(nullptr);
void* SourceMapAddr = Stack.MostRecentPropertyAddress;
FMapProperty* SourceMapProperty = CastField<FMapProperty>(Stack.MostRecentProperty);
if (!SourceMapProperty
|| !SourceMapProperty->KeyProp->SameType(TargetMapProperty->KeyProp)
|| !SourceMapProperty->ValueProp->SameType(TargetMapProperty->ValueProp))
{
Stack.bArrayContextFailed = true;
return;
}
P_GET_UBOOL(bOverwriteExisting);
P_FINISH;
P_NATIVE_BEGIN;
MARK_PROPERTY_DIRTY(Stack.Object, TargetMapProperty);
GenericMap_Append(TargetMapAddr, TargetMapProperty, SourceMapAddr, SourceMapProperty, bOverwriteExisting);
P_NATIVE_END;
}
};

View File

@@ -0,0 +1,244 @@
// Copyright (c) 2026 Unreal Directive. Licensed under the MIT License.
#pragma once
#include "CoreMinimal.h"
#include "Kismet/BlueprintFunctionLibrary.h"
#include "Types/DirectiveUtilMathTypes.h"
#include "DirectiveUtilMathFunctionLibrary.generated.h"
/**
* UDirectiveUtilMathFunctionLibrary
*
* Contains math functions for the Directive Utilities plugin.
*/
UCLASS()
class DIRECTIVEUTILITIESRUNTIME_API UDirectiveUtilMathFunctionLibrary : public UBlueprintFunctionLibrary
{
GENERATED_BODY()
public:
/**
* Returns a perlin noise value between -1 and 1 at the given position.
* @note This exposes the built-in PerlinNoise2D function to blueprints.
* @param Position - The position to get the noise value for.
* @returns The noise value at the given position.
*/
UFUNCTION(BlueprintPure, meta = (BlueprintThreadSafe), Category = "Directive Utilities|Math|Random")
static float PerlinNoise2D(FVector2D Position);
/**
* Returns a perlin noise value between -1 and 1 at the given position.
* @note This exposes the built-in PerlinNoise3D function to blueprints.
* @param Position - The position to get the noise value for.
* @returns The noise value at the given position.
*/
UFUNCTION(BlueprintPure, meta = (BlueprintThreadSafe), Category = "Directive Utilities|Math|Random")
static float PerlinNoise3D(const FVector& Position);
/**
* Returns the angle in degrees between two vectors.
* @param A - The first vector.
* @param B - The second vector.
* @returns The angle between the two vectors in degrees.
*/
UFUNCTION(BlueprintPure, meta = (BlueprintThreadSafe), Category = "Directive Utilities|Math|Vector")
static float AngleBetweenVectors(const FVector& A, const FVector& B);
/**
* Applies a Back/Elastic/Bounce easing curve to a normalized alpha.
* @note These are the Penner easing curves the engine's built-in "Ease" node (EEasingFunc) does not provide.
* For Sinusoidal/Exponential/Circular/power easings, use the engine's "Ease" node instead.
* @param Alpha - The input alpha. Clamped to the [0, 1] range.
* @param EaseType - The easing curve to apply.
* @returns The eased alpha. Note that Back and Elastic curves intentionally overshoot the [0, 1] range.
*/
UFUNCTION(BlueprintPure, meta = (DisplayName = "Ease Alpha", BlueprintThreadSafe), Category = "Directive Utilities|Math|Easing")
static float EaseAlpha(float Alpha, EDirectiveUtilEaseType EaseType);
/**
* Eases a float from A to B using a Back/Elastic/Bounce easing curve.
* @param A - The start value (returned at Alpha 0).
* @param B - The target value (returned at Alpha 1).
* @param Alpha - The input alpha. Clamped to the [0, 1] range.
* @param EaseType - The easing curve to apply.
* @returns The eased value between A and B.
*/
UFUNCTION(BlueprintPure, meta = (DisplayName = "Ease (Float)", BlueprintThreadSafe), Category = "Directive Utilities|Math|Easing")
static float EaseFloat(float A, float B, float Alpha, EDirectiveUtilEaseType EaseType);
/**
* Eases a vector from A to B using a Back/Elastic/Bounce easing curve.
* @param A - The start vector (returned at Alpha 0).
* @param B - The target vector (returned at Alpha 1).
* @param Alpha - The input alpha. Clamped to the [0, 1] range.
* @param EaseType - The easing curve to apply.
* @returns The eased vector between A and B.
*/
UFUNCTION(BlueprintPure, meta = (DisplayName = "Ease (Vector)", BlueprintThreadSafe), Category = "Directive Utilities|Math|Easing")
static FVector EaseVector(const FVector& A, const FVector& B, float Alpha, EDirectiveUtilEaseType EaseType);
/**
* Eases a rotator from A to B using a Back/Elastic/Bounce easing curve (shortest-path interpolation).
* @param A - The start rotator (returned at Alpha 0).
* @param B - The target rotator (returned at Alpha 1).
* @param Alpha - The input alpha. Clamped to the [0, 1] range.
* @param EaseType - The easing curve to apply.
* @returns The eased rotator between A and B.
*/
UFUNCTION(BlueprintPure, meta = (DisplayName = "Ease (Rotator)", BlueprintThreadSafe), Category = "Directive Utilities|Math|Easing")
static FRotator EaseRotator(const FRotator& A, const FRotator& B, float Alpha, EDirectiveUtilEaseType EaseType);
/**
* Eases a color from A to B using a Back/Elastic/Bounce easing curve.
* @param A - The start color (returned at Alpha 0).
* @param B - The target color (returned at Alpha 1).
* @param Alpha - The input alpha. Clamped to the [0, 1] range.
* @param EaseType - The easing curve to apply.
* @returns The eased color between A and B.
*/
UFUNCTION(BlueprintPure, meta = (DisplayName = "Ease (Color)", BlueprintThreadSafe), Category = "Directive Utilities|Math|Easing")
static FLinearColor EaseColor(const FLinearColor& A, const FLinearColor& B, float Alpha, EDirectiveUtilEaseType EaseType);
/**
* Rounds a float to a given number of decimal places. Rounds half away from zero,
* matching "Round To Decimals (Text)".
* @note Due to floating-point representation the returned value may not display exactly;
* use "Round To Decimals (Text)" for clean display.
* @param Value - The value to round.
* @param Decimals - The number of decimal places to round to. Clamped to the [0, 10] range.
* @returns The rounded value.
*/
UFUNCTION(BlueprintPure, meta = (DisplayName = "Round To Decimals", BlueprintThreadSafe), Category = "Directive Utilities|Math|Float")
static float RoundToDecimals(float Value, int32 Decimals);
/**
* Rounds a float to a given number of decimal places and returns it as display text.
* Rounds half away from zero, matching "Round To Decimals".
* @param Value - The value to round.
* @param Decimals - The maximum number of decimal places to display. Clamped to the [0, 10] range.
* @returns The rounded value as text, formatted with the current locale.
*/
UFUNCTION(BlueprintPure, meta = (DisplayName = "Round To Decimals (Text)", BlueprintThreadSafe), Category = "Directive Utilities|Math|Float")
static FText RoundToDecimalsAsText(float Value, int32 Decimals);
/**
* Formats a byte count as a human-readable size using binary units (1024): B, KB, MB, GB, TB, PB.
* Decimals are applied only from KB up ("532 B", "1.4 MB"). Negative input formats the absolute
* value with a leading minus sign. Output is English-only.
* @param Bytes - The byte count to format.
* @param Decimals - The number of decimal places to show from KB up. Clamped to the [0, 3] range.
* @returns The formatted size text.
*/
UFUNCTION(BlueprintPure, Category = "Directive Utilities|Math|Formatting")
static FText FormatBytes(int64 Bytes, int32 Decimals = 1);
/**
* Formats a duration in seconds as d/h/m/s units from the largest nonzero unit down, with
* two-digit padding after the first ("1h 03m 05s", "2d 04h", "45s"). With bIncludeSeconds
* false the seconds unit is dropped and sub-minute durations return "0m". Negative input gets
* a leading minus sign; non-finite input returns "0s". Output is English-only.
* @param Seconds - The duration in seconds.
* @param bIncludeSeconds - Whether to include the seconds unit.
* @returns The formatted duration text.
*/
UFUNCTION(BlueprintPure, Category = "Directive Utilities|Math|Formatting")
static FText FormatDuration(float Seconds, bool bIncludeSeconds = true);
/**
* Formats a timestamp relative to the current local time: "just now" (under a minute),
* "N minute(s)/hour(s)/day(s) ago", or "in N ..." for future timestamps. Uses local time,
* pairing with Get Save Slot Timestamp. Output is English-only.
* @note Not pure: reads the current clock each call.
* @param Timestamp - The local timestamp to describe.
* @returns The formatted relative time text.
*/
UFUNCTION(BlueprintCallable, Category = "Directive Utilities|Math|Formatting")
static FText FormatRelativeTime(const FDateTime& Timestamp);
/**
* Returns the sum of an integer array as a 64-bit integer, so large arrays cannot overflow int32.
* @param Values - The values to sum.
* @returns The sum of the values, or 0 if the array is empty.
*/
UFUNCTION(BlueprintPure, meta = (BlueprintThreadSafe), Category = "Directive Utilities|Math|Array")
static int64 GetIntArraySum(const TArray<int32>& Values);
/**
* Returns the arithmetic mean of an integer array.
* @param Values - The values to average.
* @returns The average of the values, or 0 if the array is empty.
*/
UFUNCTION(BlueprintPure, meta = (BlueprintThreadSafe), Category = "Directive Utilities|Math|Array")
static float GetIntArrayAverage(const TArray<int32>& Values);
/**
* Returns the median of an integer array (computed on a sorted copy; the input is not modified).
* For an even count, returns the average of the two middle values.
* @param Values - The values to take the median of.
* @returns The median of the values, or 0 if the array is empty.
*/
UFUNCTION(BlueprintPure, meta = (BlueprintThreadSafe), Category = "Directive Utilities|Math|Array")
static float GetIntArrayMedian(const TArray<int32>& Values);
/**
* Returns the population standard deviation of an integer array (divides by N, not N-1).
* @param Values - The values to measure.
* @returns The population standard deviation, or 0 if the array is empty.
*/
UFUNCTION(BlueprintPure, meta = (BlueprintThreadSafe), Category = "Directive Utilities|Math|Array")
static float GetIntArrayStandardDeviation(const TArray<int32>& Values);
/**
* Returns the sum of a float array. Accumulates in double internally for precision.
* @param Values - The values to sum.
* @returns The sum of the values, or 0 if the array is empty.
*/
UFUNCTION(BlueprintPure, meta = (BlueprintThreadSafe), Category = "Directive Utilities|Math|Array")
static float GetFloatArraySum(const TArray<float>& Values);
/**
* Returns the arithmetic mean of a float array. Accumulates in double internally for precision.
* @param Values - The values to average.
* @returns The average of the values, or 0 if the array is empty.
*/
UFUNCTION(BlueprintPure, meta = (BlueprintThreadSafe), Category = "Directive Utilities|Math|Array")
static float GetFloatArrayAverage(const TArray<float>& Values);
/**
* Returns the median of a float array (computed on a sorted copy; the input is not modified).
* For an even count, returns the average of the two middle values.
* @param Values - The values to take the median of.
* @returns The median of the values, or 0 if the array is empty.
*/
UFUNCTION(BlueprintPure, meta = (BlueprintThreadSafe), Category = "Directive Utilities|Math|Array")
static float GetFloatArrayMedian(const TArray<float>& Values);
/**
* Returns the population standard deviation of a float array (divides by N, not N-1).
* Accumulates in double internally for precision.
* @param Values - The values to measure.
* @returns The population standard deviation, or 0 if the array is empty.
*/
UFUNCTION(BlueprintPure, meta = (BlueprintThreadSafe), Category = "Directive Utilities|Math|Array")
static float GetFloatArrayStandardDeviation(const TArray<float>& Values);
/**
* Returns a random index into the Weights array, where each index's probability is proportional to its weight.
* Useful for loot tables and weighted spawning. Negative weights are treated as zero.
* @param Weights - The per-index weights.
* @returns The selected index, or INDEX_NONE (-1) if the array is empty or all weights are zero.
*/
UFUNCTION(BlueprintCallable, meta = (DisplayName = "Get Random Index From Weights"), Category = "Directive Utilities|Math|Random")
static int32 GetRandomIndexFromWeights(const TArray<float>& Weights);
/**
* Deterministic version of Get Random Index From Weights that draws from (and advances) the provided random stream.
* @param Stream - The random stream to draw from.
* @param Weights - The per-index weights. Negative weights are treated as zero.
* @returns The selected index, or INDEX_NONE (-1) if the array is empty or all weights are zero.
*/
UFUNCTION(BlueprintCallable, meta = (DisplayName = "Get Random Index From Weights (Stream)"), Category = "Directive Utilities|Math|Random")
static int32 GetRandomIndexFromWeightsFromStream(UPARAM(ref) FRandomStream& Stream, const TArray<float>& Weights);
};

View File

@@ -0,0 +1,80 @@
// Copyright (c) 2026 Unreal Directive. Licensed under the MIT License.
#pragma once
#include "CoreMinimal.h"
#include "Kismet/BlueprintFunctionLibrary.h"
#include "DirectiveUtilRegexFunctionLibrary.generated.h"
/**
* UDirectiveUtilRegexFunctionLibrary
* Exposes the engine's regular-expression matching (FRegexPattern/FRegexMatcher) to Blueprints and Python.
* @warning Complex patterns with nested quantifiers can be extremely slow on long inputs
* (catastrophic backtracking), and matching runs synchronously on the calling thread.
*/
UCLASS()
class DIRECTIVEUTILITIESRUNTIME_API UDirectiveUtilRegexFunctionLibrary : public UBlueprintFunctionLibrary
{
GENERATED_BODY()
public:
/**
* Returns true if the pattern matches anywhere within the input string.
* @param Input - The string to search.
* @param Pattern - The regular expression pattern.
* @param bCaseSensitive - Whether matching is case-sensitive. Defaults to true.
* @returns True if at least one match was found.
*/
UFUNCTION(BlueprintPure, Category = "Directive Utilities|Regex")
static bool RegexMatches(const FString& Input, const FString& Pattern, bool bCaseSensitive = true);
/**
* Finds the first match of the pattern within the input string.
* @param Input - The string to search.
* @param Pattern - The regular expression pattern.
* @param OutMatch - [out] The matched substring, or empty if no match.
* @param OutMatchStart - [out] The start index of the match, or INDEX_NONE if no match.
* @param OutMatchEnd - [out] The index just past the end of the match, or INDEX_NONE if no match.
* @param bCaseSensitive - Whether matching is case-sensitive. Defaults to true.
* @returns True if a match was found.
*/
UFUNCTION(BlueprintPure, Category = "Directive Utilities|Regex")
static bool RegexFindFirst(const FString& Input, const FString& Pattern, FString& OutMatch, int32& OutMatchStart, int32& OutMatchEnd, bool bCaseSensitive = true);
/**
* Returns every (non-overlapping) match of the pattern within the input string.
* @note Zero-width (empty) matches are ignored.
* @param Input - The string to search.
* @param Pattern - The regular expression pattern.
* @param bCaseSensitive - Whether matching is case-sensitive. Defaults to true.
* @returns The matched substrings, in order.
*/
UFUNCTION(BlueprintPure, Category = "Directive Utilities|Regex")
static TArray<FString> RegexFindAll(const FString& Input, const FString& Pattern, bool bCaseSensitive = true);
/**
* Replaces every match of the pattern in the input string with a literal replacement.
* @note The replacement is literal text; capture-group back-references (e.g. $1) are not expanded.
* @note Zero-width (empty) matches are ignored.
* @param Input - The string to operate on.
* @param Pattern - The regular expression pattern.
* @param Replacement - The literal text to substitute for each match.
* @param bCaseSensitive - Whether matching is case-sensitive. Defaults to true.
* @returns The string with all matches replaced.
*/
UFUNCTION(BlueprintPure, Category = "Directive Utilities|Regex")
static FString RegexReplaceAll(const FString& Input, const FString& Pattern, const FString& Replacement, bool bCaseSensitive = true);
/**
* Returns a specific capture group from the first match of the pattern.
* @param Input - The string to search.
* @param Pattern - The regular expression pattern.
* @param GroupIndex - The capture group to retrieve. 0 is the entire match; 1+ are the parenthesized groups.
* @param OutGroup - [out] The captured substring, or empty if the group did not participate in the match.
* @param bCaseSensitive - Whether matching is case-sensitive. Defaults to true.
* @returns True if a match was found and the requested group participated in it.
*/
UFUNCTION(BlueprintPure, Category = "Directive Utilities|Regex")
static bool RegexGetCaptureGroup(const FString& Input, const FString& Pattern, int32 GroupIndex, FString& OutGroup, bool bCaseSensitive = true);
};

View File

@@ -0,0 +1,93 @@
// Copyright (c) 2026 Unreal Directive. Licensed under the MIT License.
#pragma once
#include "CoreMinimal.h"
#include "Kismet/BlueprintFunctionLibrary.h"
#include "DirectiveUtilSaveGameFunctionLibrary.generated.h"
class USaveGame;
/**
* UDirectiveUtilSaveGameFunctionLibrary
* Save-slot utilities that fill the gaps left by UGameplayStatics: enumerating slots, reading slot
* timestamps, and serializing a save object to/from an in-memory byte array. This is slot/IO QoL only,
* not a save framework: use the engine's SaveGameToSlot/LoadGameFromSlot for the actual slot I/O.
*/
UCLASS()
class DIRECTIVEUTILITIESRUNTIME_API UDirectiveUtilSaveGameFunctionLibrary : public UBlueprintFunctionLibrary
{
GENERATED_BODY()
public:
/**
* Returns the names of all existing save slots in the project's default save directory.
* @note This enumerates the engine's default file-based save directory (Saved/SaveGames); it does not
* cover platform-specific save systems (e.g. console storage).
* @returns The save slot names (without extension).
*/
UFUNCTION(BlueprintCallable, Category = "Directive Utilities|SaveGame")
static TArray<FString> GetAllSaveSlotNames();
/**
* Returns the last-modified timestamp of a save slot, if it exists.
* @param SlotName - The save slot name.
* @param OutTimestamp - [out] The slot's last-modified time (local), or a default time if it does not exist.
* Converted from the file system's UTC timestamp to local time.
* @returns True if the slot exists.
*/
UFUNCTION(BlueprintCallable, Category = "Directive Utilities|SaveGame")
static bool GetSaveSlotTimestamp(const FString& SlotName, FDateTime& OutTimestamp);
/**
* Serializes a save game object to an in-memory byte array (instead of a slot file).
* Useful for custom storage, networking, or cloud saves.
* @param SaveGameObject - The save game object to serialize.
* @param OutBytes - [out] The serialized bytes.
* @returns True if serialization succeeded.
*/
UFUNCTION(BlueprintCallable, Category = "Directive Utilities|SaveGame")
static bool SaveGameToBytes(USaveGame* SaveGameObject, TArray<uint8>& OutBytes);
/**
* Deserializes a save game object from an in-memory byte array produced by Save Game To Bytes.
* @param SaveData - The serialized bytes.
* @returns The deserialized save game object, or null on failure.
*/
UFUNCTION(BlueprintCallable, Category = "Directive Utilities|SaveGame")
static USaveGame* LoadGameFromBytes(const TArray<uint8>& SaveData);
/**
* Checks whether a save slot exists. Goes through the engine's save game system, so unlike
* enumeration it also works on platform save backends.
* @param SlotName - The save slot name.
* @param UserIndex - The platform user index the save belongs to.
* @returns True if the slot exists.
*/
UFUNCTION(BlueprintCallable, Category = "Directive Utilities|SaveGame")
static bool DoesSaveSlotExist(const FString& SlotName, int32 UserIndex = 0);
/**
* Deletes a save slot. Goes through the engine's save game system, so unlike enumeration it
* also works on platform save backends.
* @param SlotName - The save slot name.
* @param UserIndex - The platform user index the save belongs to.
* @returns True if a save was actually deleted.
*/
UFUNCTION(BlueprintCallable, Category = "Directive Utilities|SaveGame")
static bool DeleteSaveSlot(const FString& SlotName, int32 UserIndex = 0);
/**
* Renames a save slot by copying its data to the new name and then deleting the original.
* Fails without mutating anything unless both names are valid, the names differ, the old slot
* exists, and the new slot does not. On failure the original slot is never lost. Goes through
* the engine's save game system, so unlike enumeration it also works on platform save backends.
* @param OldSlotName - The existing save slot name.
* @param NewSlotName - The new save slot name.
* @param UserIndex - The platform user index the save belongs to.
* @returns True if the slot was renamed.
*/
UFUNCTION(BlueprintCallable, Category = "Directive Utilities|SaveGame")
static bool RenameSaveSlot(const FString& OldSlotName, const FString& NewSlotName, int32 UserIndex = 0);
};

View File

@@ -0,0 +1,339 @@
// Copyright (c) 2026 Unreal Directive. Licensed under the MIT License.
#pragma once
#include "CoreMinimal.h"
#include "Kismet/BlueprintFunctionLibrary.h"
#include "Types/DirectiveUtilTypes.h"
#include "DirectiveUtilStringFunctionLibrary.generated.h"
/**
* UDirectiveUtilStringFunctionLibrary
* A collection of helpful string utility functions that improve the usability of strings in Blueprints.
*/
UCLASS()
class DIRECTIVEUTILITIESRUNTIME_API UDirectiveUtilStringFunctionLibrary : public UBlueprintFunctionLibrary
{
GENERATED_BODY()
public:
/**
* Detect if the provided string contains any letters.
* @note Operates on ASCII characters only; non-ASCII letters/digits are not classified or cased.
* @param String - The string to check.
* @returns True if the string contains letters.
*/
UFUNCTION(BlueprintPure, Category = "Directive Utilities|String" )
static bool ContainsLetters(const FString& String);
/**
* Detect if the provided string contains any numbers.
* @note Operates on ASCII characters only; non-ASCII letters/digits are not classified or cased.
* @param String - The string to check.
* @returns True if the string contains numbers.
*/
UFUNCTION(BlueprintPure, Category = "Directive Utilities|String" )
static bool ContainsNumbers(const FString& String);
/**
* Detect if the provided string contains any spaces.
* @param String - The string to check.
* @returns True if the string contains spaces.
*/
UFUNCTION(BlueprintPure, Category = "Directive Utilities|String" )
static bool ContainsSpaces(const FString& String);
/**
* Detect if the provided string contains any special characters.
* @note Operates on ASCII characters only; non-ASCII letters/digits are not classified or cased.
* @param String - The string to check.
* @returns True if the string contains special characters.
*/
UFUNCTION(BlueprintPure, Category = "Directive Utilities|String" )
static bool ContainsSpecialCharacters(const FString& String);
/**
* Filter out characters types from the string.
* @note Operates on ASCII characters only; non-ASCII letters/digits are not classified or cased.
* @param String - The string to filter.
* @param bLetters - If true, filter out letters.
* @param bNumbers - If true, filter out numbers.
* @param bSpecialCharacters - If true, filter out special characters.
* @param bSpaces - If true, filter out spaces.
*/
UFUNCTION(BlueprintPure, Category = "Directive Utilities|String" )
static FString FilterCharacters(const FString& String, const bool bLetters, const bool bNumbers, const bool bSpecialCharacters, const bool bSpaces);
/**
* Sort a string array alphabetically.
* @param StringArray - The string array to sort.
*/
UFUNCTION(BlueprintPure, Category = "Directive Utilities|String", meta = (DeprecatedFunction, DeprecationMessage = "Use the engine's Sort String Array node (UE 5.6+) instead."))
static TArray<FString> SortStringArray(TArray<FString> StringArray);
/**
* Truncates a string to the specified length and appends a suffix.
* @param String - The string to truncate.
* @param MaxLength - The maximum length of the resulting string including the suffix. If MaxLength is smaller than the suffix length, the full suffix is still returned.
* @param Suffix - The suffix to append when truncated.
* @returns The truncated string.
*/
UFUNCTION(BlueprintPure, Category = "Directive Utilities|String")
static FString TruncateString(const FString& String, int32 MaxLength, const FString& Suffix = TEXT("..."));
/**
* Converts a string to title case, capitalizing the first letter of each word.
* @note Operates on ASCII characters only; non-ASCII letters/digits are not classified or cased.
* @param String - The string to convert.
* @returns The title-cased string.
*/
UFUNCTION(BlueprintPure, Category = "Directive Utilities|String")
static FString ToTitleCase(const FString& String);
/**
* Splits a string into its component words. Words are delimited by any non-alphanumeric
* character (which is consumed), a lower-to-upper transition ("fooBar" -> "foo", "Bar"),
* the end of an acronym ("XMLParser" -> "XML", "Parser"), or a letter/digit transition
* ("version2Beta" -> "version", "2", "Beta").
* @note Operates on ASCII characters only; non-ASCII letters/digits are not classified or cased.
* @param String - The string to split.
* @returns The words in order, or an empty array for an empty input.
*/
UFUNCTION(BlueprintPure, Category = "Directive Utilities|String")
static TArray<FString> SplitIntoWords(const FString& String);
/**
* Converts a string to camelCase: the first word lowercased, every following word capitalized,
* joined without separators ("my var name" -> "myVarName").
* @note Operates on ASCII characters only; non-ASCII letters/digits are not classified or cased.
* @param String - The string to convert.
* @returns The camelCased string.
*/
UFUNCTION(BlueprintPure, Category = "Directive Utilities|String")
static FString ToCamelCase(const FString& String);
/**
* Converts a string to PascalCase: every word capitalized, joined without separators
* ("my var name" -> "MyVarName").
* @note Operates on ASCII characters only; non-ASCII letters/digits are not classified or cased.
* @param String - The string to convert.
* @returns The PascalCased string.
*/
UFUNCTION(BlueprintPure, Category = "Directive Utilities|String")
static FString ToPascalCase(const FString& String);
/**
* Converts a string to snake_case: every word lowercased, joined with underscores
* ("my var name" -> "my_var_name").
* @note Operates on ASCII characters only; non-ASCII letters/digits are not classified or cased.
* @param String - The string to convert.
* @returns The snake_cased string.
*/
UFUNCTION(BlueprintPure, Category = "Directive Utilities|String")
static FString ToSnakeCase(const FString& String);
/**
* Converts a string to kebab-case: every word lowercased, joined with hyphens
* ("my var name" -> "my-var-name").
* @note Operates on ASCII characters only; non-ASCII letters/digits are not classified or cased.
* @param String - The string to convert.
* @returns The kebab-cased string.
*/
UFUNCTION(BlueprintPure, Category = "Directive Utilities|String")
static FString ToKebabCase(const FString& String);
/**
* Returns a sorted copy of the provided string array.
* @param StringArray - The array of strings to sort.
* @returns A sorted copy of the provided string array.
*/
UFUNCTION(BlueprintCallable, Category = "Directive Utilities|String", meta = (DeprecatedFunction, DeprecationMessage = "Use the engine's Sort String Array node (UE 5.6+) instead."))
static TArray<FString> GetSortedStringArray(TArray<FString> StringArray);
/**
* Returns the Levenshtein (edit) distance between two strings: the minimum number of single-character
* insertions, deletions, or substitutions needed to turn one string into the other.
* @param A - The first string.
* @param B - The second string.
* @param bCaseSensitive - Whether the comparison is case-sensitive. Defaults to true.
* @returns The edit distance (0 means identical).
*/
UFUNCTION(BlueprintPure, Category = "Directive Utilities|String")
static int32 GetLevenshteinDistance(const FString& A, const FString& B, bool bCaseSensitive = true);
/**
* Returns a normalized similarity score between two strings, from 0 (completely different) to 1 (identical),
* derived from the Levenshtein distance. Two empty strings are considered identical.
* @param A - The first string.
* @param B - The second string.
* @param bCaseSensitive - Whether the comparison is case-sensitive. Defaults to true.
* @returns The similarity in the [0, 1] range.
*/
UFUNCTION(BlueprintPure, Category = "Directive Utilities|String")
static float GetStringSimilarity(const FString& A, const FString& B, bool bCaseSensitive = true);
/**
* Returns true if the source string contains any of the provided search terms. Empty terms are ignored.
* @param Source - The string to search.
* @param SearchTerms - The substrings to look for.
* @param bCaseSensitive - Whether the search is case-sensitive. Defaults to true.
* @returns True if at least one non-empty term is found.
*/
UFUNCTION(BlueprintPure, Category = "Directive Utilities|String")
static bool ContainsAny(const FString& Source, const TArray<FString>& SearchTerms, bool bCaseSensitive = true);
/**
* Finds the earliest occurrence in the source string of any of the provided search terms.
* @param Source - The string to search.
* @param SearchTerms - The substrings to look for. Empty terms are ignored.
* @param bCaseSensitive - Whether the search is case-sensitive. Defaults to true.
* @param OutFoundIndex - [out] The index in Source of the earliest match, or INDEX_NONE if none.
* @param OutTermIndex - [out] The index into SearchTerms of the matched term, or INDEX_NONE if none.
* @returns True if any term was found.
*/
UFUNCTION(BlueprintPure, Category = "Directive Utilities|String")
static bool FindFirstOfAny(const FString& Source, const TArray<FString>& SearchTerms, bool bCaseSensitive, int32& OutFoundIndex, int32& OutTermIndex);
/**
* Encodes a string to Base64.
* @param Source - The string to encode.
* @returns The Base64-encoded string.
*/
UFUNCTION(BlueprintPure, Category = "Directive Utilities|String")
static FString Base64Encode(const FString& Source);
/**
* Decodes a Base64 string.
* @param Source - The Base64 string to decode.
* @param OutDecoded - [out] The decoded string, or empty on failure.
* @returns True if the input was valid Base64 and was decoded.
*/
UFUNCTION(BlueprintPure, Category = "Directive Utilities|String")
static bool Base64Decode(const FString& Source, FString& OutDecoded);
/**
* Encodes a string as lowercase hex. The string is converted to UTF-8 bytes first.
* @param String - The string to encode.
* @returns The lowercase hex encoding of the string's UTF-8 bytes.
*/
UFUNCTION(BlueprintPure, Category = "Directive Utilities|String")
static FString HexEncode(const FString& String);
/**
* Decodes a hex string (either case accepted) into the string its bytes spell in UTF-8.
* @param Hex - The hex string to decode.
* @param OutString - [out] The decoded string, or empty on failure.
* @returns True if the input was valid even-length hex and was decoded.
*/
UFUNCTION(BlueprintPure, Category = "Directive Utilities|String")
static bool HexDecode(const FString& Hex, FString& OutString);
/**
* Encodes a byte array as lowercase hex.
* @param Bytes - The bytes to encode.
* @returns The lowercase hex encoding of the bytes.
*/
UFUNCTION(BlueprintPure, Category = "Directive Utilities|String")
static FString HexEncodeBytes(const TArray<uint8>& Bytes);
/**
* Decodes a hex string (either case accepted) into a byte array.
* @param Hex - The hex string to decode.
* @param OutBytes - [out] The decoded bytes, or empty on failure.
* @returns True if the input was valid even-length hex and was decoded.
*/
UFUNCTION(BlueprintPure, Category = "Directive Utilities|String")
static bool HexDecodeBytes(const FString& Hex, TArray<uint8>& OutBytes);
/**
* Returns the MD5 digest of the string as 32 lowercase hex characters. The string is
* converted to UTF-8 bytes first.
* @note For integrity checks and cache keys, not for security or password storage.
* @param String - The string to hash.
* @returns The MD5 digest as lowercase hex.
*/
UFUNCTION(BlueprintPure, Category = "Directive Utilities|String")
static FString Md5HashString(const FString& String);
/**
* Returns the MD5 digest of a byte array as 32 lowercase hex characters.
* @note For integrity checks and cache keys, not for security or password storage.
* @param Bytes - The bytes to hash.
* @returns The MD5 digest as lowercase hex.
*/
UFUNCTION(BlueprintPure, Category = "Directive Utilities|String")
static FString Md5HashBytes(const TArray<uint8>& Bytes);
/**
* Returns the SHA-1 digest of the string as 40 lowercase hex characters. The string is
* converted to UTF-8 bytes first.
* @note For integrity checks and cache keys, not for security or password storage.
* @param String - The string to hash.
* @returns The SHA-1 digest as lowercase hex.
*/
UFUNCTION(BlueprintPure, Category = "Directive Utilities|String")
static FString Sha1HashString(const FString& String);
/**
* Returns the SHA-1 digest of a byte array as 40 lowercase hex characters.
* @note For integrity checks and cache keys, not for security or password storage.
* @param Bytes - The bytes to hash.
* @returns The SHA-1 digest as lowercase hex.
*/
UFUNCTION(BlueprintPure, Category = "Directive Utilities|String")
static FString Sha1HashBytes(const TArray<uint8>& Bytes);
/**
* Returns the CRC32 checksum of the string (seed 0). The string is converted to
* UTF-8 bytes first.
* @note For integrity checks and cache keys, not for security or password storage.
* @param String - The string to checksum.
* @returns The CRC32 checksum.
*/
UFUNCTION(BlueprintPure, Category = "Directive Utilities|String")
static int32 Crc32String(const FString& String);
/**
* Returns the CRC32 checksum of a byte array (seed 0).
* @note For integrity checks and cache keys, not for security or password storage.
* @param Bytes - The bytes to checksum.
* @returns The CRC32 checksum.
*/
UFUNCTION(BlueprintPure, Category = "Directive Utilities|String")
static int32 Crc32Bytes(const TArray<uint8>& Bytes);
/**
* Checks whether the string is safe to use as a bare file name: not empty, no path
* separators or relative segments, and no characters invalid in file names.
* @param String - The string to check.
* @returns True if the string is a valid bare file name.
*/
UFUNCTION(BlueprintPure, Category = "Directive Utilities|String")
static bool IsValidFileName(const FString& String);
/**
* Returns the string with path separators and characters invalid in file names removed
* (or replaced when a replacement character is provided). May return an empty string.
* @param String - The string to sanitize.
* @param Replacement - Optional single-character replacement for stripped characters.
* @returns The sanitized file name.
*/
UFUNCTION(BlueprintPure, Category = "Directive Utilities|String")
static FString SanitizeFileName(const FString& String, const FString& Replacement = TEXT(""));
/**
* Finds the entry in Candidates most similar to Input (by GetStringSimilarity).
* Ties resolve to the earliest index. Cost grows with array size and string
* lengths (Levenshtein per candidate). Avoid very large arrays per frame.
* @note bCaseSensitive defaults to false (matching user input), unlike the pairwise
* comparison functions where it defaults to true.
* @param Input - The string to match.
* @param Candidates - The candidate strings.
* @param OutSimilarity - [out] The winning similarity in [0, 1], 0 when empty.
* @param bCaseSensitive - Whether comparison is case-sensitive. Defaults to false.
* @returns The index of the best match, or -1 if Candidates is empty.
*/
UFUNCTION(BlueprintPure, Category = "Directive Utilities|String")
static int32 FindBestStringMatch(const FString& Input, const TArray<FString>& Candidates, float& OutSimilarity, bool bCaseSensitive = false);
};

View File

@@ -0,0 +1,26 @@
// Copyright (c) 2026 Unreal Directive. Licensed under the MIT License.
#pragma once
#include "CoreMinimal.h"
#include "Kismet/BlueprintFunctionLibrary.h"
#include "DirectiveUtilTextFunctionLibrary.generated.h"
/**
* DirectiveUtilTextFunctionLibrary
* A collection of helpful text utility functions that improve the usability of text in Blueprints.
*/
UCLASS()
class DIRECTIVEUTILITIESRUNTIME_API UDirectiveUtilTextFunctionLibrary : public UBlueprintFunctionLibrary
{
GENERATED_BODY()
public:
/**
* Returns true if the provided text is not empty.
* @param Text - The text to check.
*/
UFUNCTION(BlueprintPure, Category = "Directive Utilities|Text" )
static bool IsNotEmpty(const FText& Text);
};