diff --git a/Source/Integration/AnimQueue.cpp b/Source/Integration/AnimQueue.cpp index a354d987..ed7be21b 100644 --- a/Source/Integration/AnimQueue.cpp +++ b/Source/Integration/AnimQueue.cpp @@ -797,3 +797,12 @@ void UlxAnimationStepLibrary::AnimationStepApplySkeletalMesh(const FlxAnimationS } } +FFormatArgumentData UlxAnimationStepLibrary::FormatArgumentDataAnimationStep(const FlxAnimationStep &AutoConvertedValue, const FString &Name) +{ + FFormatArgumentData Result; + Result.ArgumentValueType = EFormatArgumentType::Text; + Result.ArgumentName = Name; + Result.ArgumentValue = FText::FromString(AnimationStepDebugString(AutoConvertedValue)); + return Result; +} + diff --git a/Source/Integration/AnimQueue.h b/Source/Integration/AnimQueue.h index 181eef96..1ac6702d 100644 --- a/Source/Integration/AnimQueue.h +++ b/Source/Integration/AnimQueue.h @@ -149,6 +149,11 @@ public: UFUNCTION(BlueprintCallable, Category = "Luprex|Animation Step") static void AnimationStepApplySkeletalMesh(const FlxAnimationStep& step, bool FallbackToBP, USkeletalMeshComponent* MeshComp, USkeletalMesh* Fallback = nullptr); + + // Allows you to pass an animation step to Format Message. + // + UFUNCTION(BlueprintPure, meta = (BlueprintInternalUseOnly = "true"), Category = "Luprex|Animation Step") + static FFormatArgumentData FormatArgumentDataAnimationStep(const FlxAnimationStep &AutoConvertedValue, const FString &Name); }; //////////////////////////////////////////////////////////// diff --git a/Source/Integration/FormatDataLibrary.cpp b/Source/Integration/FormatDataLibrary.cpp index 158fe834..d9dc01f3 100644 --- a/Source/Integration/FormatDataLibrary.cpp +++ b/Source/Integration/FormatDataLibrary.cpp @@ -1,9 +1,51 @@ #include "FormatDataLibrary.h" -#include "LuaCall.h" -#include "AnimQueue.h" -#include "MovementComponentState.h" +#include "Common.h" #include "Kismet/KismetTextLibrary.h" +#include "UObject/UObjectIterator.h" + +void UlxFormatDataLibrary::Initialize(FSubsystemCollectionBase& Collection) +{ + Super::Initialize(Collection); + ScanForConverters(); +} + +void UlxFormatDataLibrary::ScanForConverters() +{ + Converters.Empty(); + for (TObjectIterator It; It; ++It) + { + UClass* Class = *It; + for (TFieldIterator FuncIt(Class, EFieldIteratorFlags::ExcludeSuper); FuncIt; ++FuncIt) + { + UFunction* Function = *FuncIt; + + // Must have an AutoConvertedValue parameter. + if (Function->FindPropertyByName(TEXT("AutoConvertedValue")) == nullptr) continue; + + // Must have a Name parameter that is a string. + FProperty* NameProp = Function->FindPropertyByName(TEXT("Name")); + if (NameProp == nullptr) continue; + if (CastField(NameProp) == nullptr) continue; + + // Must return FFormatArgumentData. + FStructProperty* ReturnProp = CastField(Function->GetReturnProperty()); + if (ReturnProp == nullptr) continue; + if (ReturnProp->Struct->GetFName() != TEXT("FormatArgumentData")) continue; + + // Must have exactly three properties: AutoConvertedValue, Name, and ReturnValue. + int PropCount = 0; + for (TFieldIterator PropIt(Function); PropIt; ++PropIt) PropCount++; + if (PropCount != 3) continue; + + Converters.Add(Function); + } + } + for (UFunction* Func : Converters) + { + UE_LOG(LogLuprexIntegration, Display, TEXT("FormatData converter: %s::%s"), *Func->GetOuterUClass()->GetName(), *Func->GetName()); + } +} FFormatArgumentData UlxFormatDataLibrary::FormatArgumentDataBool(bool AutoConvertedValue, const FString &Name) { @@ -149,33 +191,6 @@ FFormatArgumentData UlxFormatDataLibrary::FormatArgumentDataTransform(const FTra return Result; } -FFormatArgumentData UlxFormatDataLibrary::FormatArgumentDataLuaValues(const UlxLuaValues *AutoConvertedValue, const FString &Name) -{ - FFormatArgumentData Result; - Result.ArgumentValueType = EFormatArgumentType::Text; - Result.ArgumentName = Name; - Result.ArgumentValue = FText::FromString(AutoConvertedValue->DebugString()); - return Result; -} - -FFormatArgumentData UlxFormatDataLibrary::FormatArgumentDataAnimationStep(const FlxAnimationStep &AutoConvertedValue, const FString &Name) -{ - FFormatArgumentData Result; - Result.ArgumentValueType = EFormatArgumentType::Text; - Result.ArgumentName = Name; - Result.ArgumentValue = FText::FromString(UlxAnimationStepLibrary::AnimationStepDebugString(AutoConvertedValue)); - return Result; -} - -FFormatArgumentData UlxFormatDataLibrary::FormatArgumentDataMovementComponentState(const FlxMovementComponentState &AutoConvertedValue, const FString &Name) -{ - FFormatArgumentData Result; - Result.ArgumentValueType = EFormatArgumentType::Text; - Result.ArgumentName = Name; - Result.ArgumentValue = FText::FromString(UlxMovementComponentStateLibrary::DebugString(AutoConvertedValue)); - return Result; -} - FFormatArgumentData UlxFormatDataLibrary::FormatArgumentDataBlank(const FString &Name) { FFormatArgumentData Result; diff --git a/Source/Integration/FormatDataLibrary.h b/Source/Integration/FormatDataLibrary.h index 6f14e431..257e6455 100644 --- a/Source/Integration/FormatDataLibrary.h +++ b/Source/Integration/FormatDataLibrary.h @@ -2,9 +2,26 @@ // // FormatDataLibrary.h // -// Functions that convert data into -// FFormatArgumentData structs, so that the data -// can be passed to FText::Format. +// The 'Format Message' and 'Format Log Message' nodes can format +// ints, strings, vectors, and more. To support a new data type, +// all you need to do is implement a blueprint-callable conversion +// function like this one: +// +// UFUNCTION(BlueprintPure, meta = (BlueprintInternalUseOnly = "true")) +// static FFormatArgumentData FormatArgumentDataBool(bool AutoConvertedValue, const FString &Name); +// +// This conversion function must have three things: +// +// * It returns FFormatArgumentData +// * It accepts 'AutoConvertedValue', the value you want to format, +// * It accepts 'Name', which must be copied into the FFormatArgumentData. +// +// The parameter names are required: you must use exactly these parameter +// names if you want this to act as a conversion function. You can put a +// conversion function like that anywhere in the system, in any UCLASS. +// This module will find and use it. +// +// This file also contains the built-in converters for standard types. // //////////////////////////////////////////////////////////// @@ -15,31 +32,40 @@ #include "HAL/Platform.h" #include "UObject/ObjectMacros.h" #include "UObject/UObjectGlobals.h" -#include "Kismet/BlueprintFunctionLibrary.h" +#include "EditorSubsystem.h" #include "FormatDataLibrary.generated.h" -class UlxLuaValues; -struct FlxAnimationStep; -struct FlxMovementComponentState; - //////////////////////////////////////////////////////////// // // UlxFormatDataLibrary // -// The FormatLogMessage K2Node scans this library using -// reflection, looking for functions that have a parameter -// named "AutoConvertedValue". It uses the type of that -// parameter to determine which function to call for a given -// pin type. // //////////////////////////////////////////////////////////// UCLASS(MinimalAPI) -class UlxFormatDataLibrary : public UBlueprintFunctionLibrary +class UlxFormatDataLibrary : public UEditorSubsystem { GENERATED_BODY() +public: + virtual void Initialize(FSubsystemCollectionBase& Collection) override; + + // Get all converter functions (functions with an + // "AutoConvertedValue" parameter) found across all + // loaded classes. + // + const TArray& GetConverters() const { return Converters; } + +private: + // Scan all loaded classes for converter functions. + // + void ScanForConverters(); + + // Cached list of converter functions. + // + TArray Converters; + public: UFUNCTION(BlueprintPure, meta = (BlueprintInternalUseOnly = "true"), Category = "Luprex|Utility") static FFormatArgumentData FormatArgumentDataBool(bool AutoConvertedValue, const FString &Name); @@ -89,15 +115,6 @@ public: UFUNCTION(BlueprintPure, meta = (BlueprintInternalUseOnly = "true"), Category = "Luprex|Utility") static FFormatArgumentData FormatArgumentDataTransform(const FTransform &AutoConvertedValue, const FString &Name); - UFUNCTION(BlueprintPure, meta = (BlueprintInternalUseOnly = "true"), Category = "Luprex|Utility") - static FFormatArgumentData FormatArgumentDataLuaValues(const UlxLuaValues *AutoConvertedValue, const FString &Name); - - UFUNCTION(BlueprintPure, meta = (BlueprintInternalUseOnly = "true"), Category = "Luprex|Utility") - static FFormatArgumentData FormatArgumentDataAnimationStep(const FlxAnimationStep &AutoConvertedValue, const FString &Name); - - UFUNCTION(BlueprintPure, meta = (BlueprintInternalUseOnly = "true"), Category = "Luprex|Utility") - static FFormatArgumentData FormatArgumentDataMovementComponentState(const FlxMovementComponentState &AutoConvertedValue, const FString &Name); - // For pins that were never connected. // UFUNCTION(BlueprintPure, meta = (BlueprintInternalUseOnly = "true"), Category = "Luprex|Utility") diff --git a/Source/Integration/FormatMessage.cpp b/Source/Integration/FormatMessage.cpp index e1ea0d0a..7fd66052 100644 --- a/Source/Integration/FormatMessage.cpp +++ b/Source/Integration/FormatMessage.cpp @@ -2,6 +2,7 @@ #include "FormatMessage.h" +#include "Editor.h" #include "Internationalization/TextFormatter.h" #include "BlueprintActionDatabaseRegistrar.h" @@ -305,11 +306,11 @@ UFunction *ToFormatArgumentData(const UEdGraphSchema_K2 *Schema, const FEdGraphP return UlxFormatDataLibrary::StaticClass()->FindFunctionByName(GET_MEMBER_NAME_CHECKED(UlxFormatDataLibrary, FormatArgumentDataBlank)); } - // Try to find a match in the UlxFormatDataLibrary. + // Scan the cached converter list for a matching type. // - for (auto It = TFieldIterator(UlxFormatDataLibrary::StaticClass()); It; ++It) + UlxFormatDataLibrary* FormatDataLib = GEditor->GetEditorSubsystem(); + for (UFunction* Function : FormatDataLib->GetConverters()) { - UFunction* Function = *It; FProperty* ValueProperty = Function->FindPropertyByName(TEXT("AutoConvertedValue")); FEdGraphPinType ValuePinType; bool Convertible = Schema->ConvertPropertyToPinType(ValueProperty, ValuePinType); @@ -317,9 +318,9 @@ UFunction *ToFormatArgumentData(const UEdGraphSchema_K2 *Schema, const FEdGraphP if (!Schema->ArePinTypesEquivalent(PinType, ValuePinType)) continue; return Function; } - + // A general handler for Enums. You can override this for specific enums by - // putting that particular enum into the UlxFormatDataLibrary. + // putting that particular enum into any class with an AutoConvertedValue function. // if ((PinType.PinCategory == UEdGraphSchema_K2::PC_Byte) && (nullptr != Cast(PinType.PinSubCategoryObject))) { @@ -332,7 +333,7 @@ UFunction *ToFormatArgumentData(const UEdGraphSchema_K2 *Schema, const FEdGraphP { return UlxFormatDataLibrary::StaticClass()->FindFunctionByName(GET_MEMBER_NAME_CHECKED(UlxFormatDataLibrary, FormatArgumentDataObject)); } - + // We don't have a match. // return nullptr; diff --git a/Source/Integration/Integration.Build.cs b/Source/Integration/Integration.Build.cs index f8cf152c..573feb98 100644 --- a/Source/Integration/Integration.Build.cs +++ b/Source/Integration/Integration.Build.cs @@ -31,6 +31,7 @@ public class Integration : ModuleRules "BlueprintGraph", "UMG", "UMGEditor", + "EditorSubsystem", }); // Uncomment if you are using Slate UI diff --git a/Source/Integration/LuaCall.cpp b/Source/Integration/LuaCall.cpp index 42dd2b5d..9b96c005 100644 --- a/Source/Integration/LuaCall.cpp +++ b/Source/Integration/LuaCall.cpp @@ -543,5 +543,12 @@ void UlxLuaValues::ReadBoolean(ElxSuccessOrWrongType &Status, bool &Result, bool Result = FlxStreamBuffer(Data[Cursor++]).read_bool(); } - +FFormatArgumentData UlxLuaValues::FormatArgumentDataLuaValues(const UlxLuaValues *AutoConvertedValue, const FString &Name) +{ + FFormatArgumentData Result; + Result.ArgumentValueType = EFormatArgumentType::Text; + Result.ArgumentName = Name; + Result.ArgumentValue = FText::FromString(AutoConvertedValue->DebugString()); + return Result; +} diff --git a/Source/Integration/LuaCall.h b/Source/Integration/LuaCall.h index 0e6c626e..f5127f55 100644 --- a/Source/Integration/LuaCall.h +++ b/Source/Integration/LuaCall.h @@ -277,4 +277,9 @@ public: UFUNCTION(BlueprintCallable, meta = (ExpandEnumAsExecs = "Status"), Category = "Luprex|Lua Value Array") void ReadBoolean(ElxSuccessOrWrongType &Status, bool &Result, bool LogErrorOnWrongType = false); + + // Allows you to pass a LuaValues to Format Message. + // + UFUNCTION(BlueprintPure, meta = (BlueprintInternalUseOnly = "true"), Category = "Luprex|Lua Value Array") + static FFormatArgumentData FormatArgumentDataLuaValues(const UlxLuaValues *AutoConvertedValue, const FString &Name); }; diff --git a/Source/Integration/MovementComponentState.cpp b/Source/Integration/MovementComponentState.cpp index 9db0be3f..1e9ccc9a 100644 --- a/Source/Integration/MovementComponentState.cpp +++ b/Source/Integration/MovementComponentState.cpp @@ -62,3 +62,12 @@ FlxMovementComponentState UlxMovementComponentStateLibrary::SetFakeMovementCompo } return State; } + +FFormatArgumentData UlxMovementComponentStateLibrary::FormatArgumentDataMovementComponentState(const FlxMovementComponentState &AutoConvertedValue, const FString &Name) +{ + FFormatArgumentData Result; + Result.ArgumentValueType = EFormatArgumentType::Text; + Result.ArgumentName = Name; + Result.ArgumentValue = FText::FromString(DebugString(AutoConvertedValue)); + return Result; +} diff --git a/Source/Integration/MovementComponentState.h b/Source/Integration/MovementComponentState.h index 97c746e6..f4f9c8b9 100644 --- a/Source/Integration/MovementComponentState.h +++ b/Source/Integration/MovementComponentState.h @@ -126,4 +126,9 @@ public: // UFUNCTION(BlueprintCallable, Category = "Luprex|Movement Component State", meta = (DefaultToSelf = "Actor", AutoCreateRefTerm = "State")) static FlxMovementComponentState SetFakeMovementComponentState(AActor *Actor, const FlxMovementComponentState &State); + + // Allows you to pass a MovementComponentState to Format Message. + // + UFUNCTION(BlueprintPure, meta = (BlueprintInternalUseOnly = "true"), Category = "Luprex|Movement Component State") + static FFormatArgumentData FormatArgumentDataMovementComponentState(const FlxMovementComponentState &AutoConvertedValue, const FString &Name); };