diff --git a/Content/Luprex/lxGameMode.uasset b/Content/Luprex/lxGameMode.uasset index 0248630a..8886427d 100644 --- a/Content/Luprex/lxGameMode.uasset +++ b/Content/Luprex/lxGameMode.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:07e6c90d149a324d8ef2bd9317fd018c425363fe84dedb598a8ef1b3c46c3015 -size 137194 +oid sha256:96fef1fc352a124bcd6e59f99eca20f71ba45983cd45291956004d01b511d219 +size 127513 diff --git a/Source/Integration/BlueprintErrors.cpp b/Source/Integration/BlueprintErrors.cpp index ea758844..00dffd89 100644 --- a/Source/Integration/BlueprintErrors.cpp +++ b/Source/Integration/BlueprintErrors.cpp @@ -83,6 +83,15 @@ FFormatArgumentData UlxFormatDataLibrary::FormatArgumentDataBool(bool Value, con return Result; } +FFormatArgumentData UlxFormatDataLibrary::FormatArgumentDataByte(uint8 Value, const FString &Name) +{ + FFormatArgumentData Result; + Result.ArgumentValueType = EFormatArgumentType::Int; + Result.ArgumentName = Name; + Result.ArgumentValueInt = Value; + return Result; +} + FFormatArgumentData UlxFormatDataLibrary::FormatArgumentDataInt(int Value, const FString &Name) { FFormatArgumentData Result; @@ -200,7 +209,7 @@ FFormatArgumentData UlxFormatDataLibrary::FormatArgumentDataTransform(const FTra return Result; } -FFormatArgumentData UlxFormatDataLibrary::FormatArgumentDataBlank(const FString &Name) +FFormatArgumentData UlxBlueprintErrorLibrary::FormatArgumentDataBlank(const FString &Name) { FFormatArgumentData Result; Result.ArgumentValueType = EFormatArgumentType::Text; @@ -209,6 +218,25 @@ FFormatArgumentData UlxFormatDataLibrary::FormatArgumentDataBlank(const FString return Result; } +FFormatArgumentData UlxBlueprintErrorLibrary::FormatArgumentDataEnum(uint8 Value, const FString &Name, const UObject *PinSubCategoryObject) +{ + const UEnum *Enum = Cast(PinSubCategoryObject); + FFormatArgumentData Result; + if (Enum == nullptr) + { + Result.ArgumentValueType = EFormatArgumentType::Int; + Result.ArgumentName = Name; + Result.ArgumentValueInt = Value; + } + else + { + Result.ArgumentValueType = EFormatArgumentType::Text; + Result.ArgumentName = Name; + Result.ArgumentValue = FText::Format(INVTEXT("<{0}>"), Enum->GetDisplayNameTextByValue(Value)); + } + return Result; +} + FlxDebugBlueprintErrorsOutputDevice::FlxDebugBlueprintErrorsOutputDevice(const ElxLogVerbosity &SensitivityRef) : Sensitivity(SensitivityRef) { diff --git a/Source/Integration/BlueprintErrors.h b/Source/Integration/BlueprintErrors.h index e902447d..aad068eb 100644 --- a/Source/Integration/BlueprintErrors.h +++ b/Source/Integration/BlueprintErrors.h @@ -144,6 +144,16 @@ public: UFUNCTION(BlueprintCallable, meta=(WorldContext = "Context", BlueprintInternalUseOnly = "true")) static void FormatErrorInternal(UObject *Context, ElxLogVerbosity Verbosity, ElxErrorDisplayDuration DisplayDuration, const FString &InPattern, TArray InArgs); + // A formatting routine for pins that were never connected. + // + UFUNCTION(BlueprintPure, meta = (BlueprintInternalUseOnly = "true"), Category = "Luprex|Utility") + static FFormatArgumentData FormatArgumentDataBlank(const FString &Name); + + // A specialized formatting routine for pins of enum types. + // + UFUNCTION(BlueprintPure, meta = (BlueprintInternalUseOnly = "true"), Category = "Luprex|Utility") + static FFormatArgumentData FormatArgumentDataEnum(uint8 Value, const FString &Name, const UObject *PinSubCategoryObject); + // Convert an ElxLogVerbosity to an ELogVerbosity::Type // static ELogVerbosity::Type ConvertElxLogVerbosity(ElxLogVerbosity Verbosity); @@ -162,6 +172,9 @@ public: UFUNCTION(BlueprintPure, meta = (BlueprintInternalUseOnly = "true"), Category = "Luprex|Utility") static FFormatArgumentData FormatArgumentDataBool(bool Value, const FString &Name); + UFUNCTION(BlueprintPure, meta = (BlueprintInternalUseOnly = "true"), Category = "Luprex|Utility") + static FFormatArgumentData FormatArgumentDataByte(uint8 Value, const FString &Name); + UFUNCTION(BlueprintPure, meta = (BlueprintInternalUseOnly = "true"), Category = "Luprex|Utility") static FFormatArgumentData FormatArgumentDataInt(int Value, const FString &Name); @@ -200,9 +213,6 @@ public: UFUNCTION(BlueprintPure, meta = (BlueprintInternalUseOnly = "true"), Category = "Luprex|Utility") static FFormatArgumentData FormatArgumentDataTransform(const FTransform &Value, const FString &Name); - - UFUNCTION(BlueprintPure, meta = (BlueprintInternalUseOnly = "true"), Category = "Luprex|Utility") - static FFormatArgumentData FormatArgumentDataBlank(const FString &Name); }; /* Debug Blueprint Errors output device. diff --git a/Source/Integration/FormatError.cpp b/Source/Integration/FormatError.cpp index bba75af2..6227bccb 100644 --- a/Source/Integration/FormatError.cpp +++ b/Source/Integration/FormatError.cpp @@ -37,7 +37,7 @@ #include "UObject/WeakObjectPtr.h" #include "UObject/WeakObjectPtrTemplates.h" -#define LOCTEXT_NAMESPACE "FormatError" +#define LOCTEXT_NAMESPACE "FormatMessage" // All argument pins will have Names that start with "A:" @@ -71,26 +71,19 @@ static bool IsFormatPin(const UEdGraphPin *Pin) { return (Pin->PinName == FormatPinName); } -UK2Node_FormatError::UK2Node_FormatError(const FObjectInitializer& ObjectInitializer) - : Super(ObjectInitializer) -{ - NodeTooltip = LOCTEXT("NodeTooltip", - "Output an error, warning, or informational message to the log file.\n" - "\n" - " \u2022 Use {ArgName} to denote format arguments, giving each argument a different ArgName.\n" - "\n" - "It is often desirable to use this in conjunction with a separate utility that\n" - "pauses the execution of the blueprint whenever an error is logged." - ); +static const FName ResultPinName(TEXT("Result")); +static bool IsResultPin(const UEdGraphPin *Pin) { + return (Pin->PinName == ResultPinName); } -void UK2Node_FormatError::AllocateDefaultPins() + +void UK2Node_FormatMessage::AllocateDefaultPins() { Super::AllocateDefaultPins(); CreateCorrectPins(); } -void UK2Node_FormatError::CreateCorrectPins() +void UK2Node_FormatMessage::CreateCorrectPins() { if (FindPin(UEdGraphSchema_K2::PN_Execute) == nullptr) { CreatePin(EGPD_Input, UEdGraphSchema_K2::PC_Exec, UEdGraphSchema_K2::PN_Execute); @@ -105,19 +98,34 @@ void UK2Node_FormatError::CreateCorrectPins() P->DefaultValue = TEXT("Error Message"); } - if (FindPin(VerbosityPinName, EGPD_Input) == nullptr) { - UEdGraphPin *P = CreatePin(EGPD_Input, UEdGraphSchema_K2::PC_Byte, StaticEnum(), VerbosityPinName); - P->DefaultValue = TEXT("Error"); - P->AutogeneratedDefaultValue = P->DefaultValue; + // If this is a FormatMessage node, create a pin to output the result as text. + // + if (!IsFormatErrorMessage()) + { + if (FindPin(ResultPinName, EGPD_Output) == nullptr) { + CreatePin(EGPD_Output, UEdGraphSchema_K2::PC_Text, ResultPinName); + } } - if (FindPin(DisplayDurationPinName, EGPD_Input) == nullptr) { - UEdGraphPin *P = CreatePin(EGPD_Input, UEdGraphSchema_K2::PC_Byte, StaticEnum(), DisplayDurationPinName); - P->DefaultValue = TEXT("No_Show"); - P->AutogeneratedDefaultValue = P->DefaultValue; + // If this is a FormatErrorMessage node, create pins that control the log verbosity + // + if (IsFormatErrorMessage()) + { + if (FindPin(VerbosityPinName, EGPD_Input) == nullptr) { + UEdGraphPin *P = CreatePin(EGPD_Input, UEdGraphSchema_K2::PC_Byte, StaticEnum(), VerbosityPinName); + P->DefaultValue = TEXT("Error"); + P->AutogeneratedDefaultValue = P->DefaultValue; + } + + if (FindPin(DisplayDurationPinName, EGPD_Input) == nullptr) { + UEdGraphPin *P = CreatePin(EGPD_Input, UEdGraphSchema_K2::PC_Byte, StaticEnum(), DisplayDurationPinName); + P->DefaultValue = TEXT("No_Show"); + P->AutogeneratedDefaultValue = P->DefaultValue; + } } // Transfer all Existing Argument pins to the Old Pins Map. + // TMap OldPins; for (auto It = Pins.CreateIterator(); It; ++It) { @@ -130,6 +138,7 @@ void UK2Node_FormatError::CreateCorrectPins() } // Create Argument pins in the correct order, reusing old pins where possible. + // for (const FString& Name : PinNames) { UEdGraphPin **OldPin = OldPins.Find(Name); @@ -143,6 +152,7 @@ void UK2Node_FormatError::CreateCorrectPins() } // Delete any unused pins. + // for (auto &iter : OldPins) { iter.Value->Modify(); @@ -152,7 +162,7 @@ void UK2Node_FormatError::CreateCorrectPins() } -void UK2Node_FormatError::SynchronizeArgumentPinType(UEdGraphPin* Pin) +void UK2Node_FormatMessage::SynchronizeArgumentPinType(UEdGraphPin* Pin) { if (IsArgumentPin(Pin)) { @@ -196,12 +206,19 @@ void UK2Node_FormatError::SynchronizeArgumentPinType(UEdGraphPin* Pin) } } -FText UK2Node_FormatError::GetNodeTitle(ENodeTitleType::Type TitleType) const +FText UK2Node_FormatMessage::GetNodeTitle(ENodeTitleType::Type TitleType) const { - return LOCTEXT("FormatError_Title", "Format Error Message"); + if (IsFormatErrorMessage()) + { + return LOCTEXT("FormatErrorMessage_Title", "Format Error Message"); + } + else + { + return LOCTEXT("FormatMessage_Title", "Format Message"); + } } -FText UK2Node_FormatError::GetPinDisplayName(const UEdGraphPin* Pin) const +FText UK2Node_FormatMessage::GetPinDisplayName(const UEdGraphPin* Pin) const { // The exec pins don't need labels. if (Pin->PinType.PinCategory == UEdGraphSchema_K2::PC_Exec) @@ -227,10 +244,10 @@ FText UK2Node_FormatError::GetPinDisplayName(const UEdGraphPin* Pin) const return FText::FromName(Pin->PinName); } -void UK2Node_FormatError::PostEditChangeProperty(struct FPropertyChangedEvent& PropertyChangedEvent) +void UK2Node_FormatMessage::PostEditChangeProperty(struct FPropertyChangedEvent& PropertyChangedEvent) { const FName PropertyName = (PropertyChangedEvent.Property ? PropertyChangedEvent.Property->GetFName() : NAME_None); - if (PropertyName == GET_MEMBER_NAME_CHECKED(UK2Node_FormatError, PinNames)) + if (PropertyName == GET_MEMBER_NAME_CHECKED(UK2Node_FormatMessage, PinNames)) { ReconstructNode(); } @@ -238,13 +255,13 @@ void UK2Node_FormatError::PostEditChangeProperty(struct FPropertyChangedEvent& P GetGraph()->NotifyNodeChanged(this); } -void UK2Node_FormatError::PinConnectionListChanged(UEdGraphPin* Pin) +void UK2Node_FormatMessage::PinConnectionListChanged(UEdGraphPin* Pin) { Modify(); SynchronizeArgumentPinType(Pin); } -void UK2Node_FormatError::PinDefaultValueChanged(UEdGraphPin* Pin) +void UK2Node_FormatMessage::PinDefaultValueChanged(UEdGraphPin* Pin) { if(IsFormatPin(Pin)) { @@ -255,19 +272,19 @@ void UK2Node_FormatError::PinDefaultValueChanged(UEdGraphPin* Pin) } } -void UK2Node_FormatError::PinTypeChanged(UEdGraphPin* Pin) +void UK2Node_FormatMessage::PinTypeChanged(UEdGraphPin* Pin) { SynchronizeArgumentPinType(Pin); Super::PinTypeChanged(Pin); } -FText UK2Node_FormatError::GetTooltipText() const +FText UK2Node_FormatMessage::GetTooltipText() const { return NodeTooltip; } -void UK2Node_FormatError::PostReconstructNode() +void UK2Node_FormatMessage::PostReconstructNode() { Super::PostReconstructNode(); @@ -284,16 +301,21 @@ void UK2Node_FormatError::PostReconstructNode() // Get a function that can convert the specified type into a FFormatArgumentData. // +// For example: +// * if you pass in a pin of type 'String', it will return UlxFormatDataLibrary::FormatArgumentDataString +// * if you pass in a pin of type 'Vector', it will return UlxFormatDataLibrary::FormatArgumentDataVector +// and so forth. +// UFunction *ToFormatArgumentData(const UEdGraphSchema_K2 *Schema, const FEdGraphPinType& PinType, bool AllowWild) { // Special case. Wildcard Pins are unconnected pins. // if (PinType.PinCategory == UEdGraphSchema_K2::PC_Wildcard && AllowWild) { - return UlxFormatDataLibrary::StaticClass()->FindFunctionByName(GET_MEMBER_NAME_CHECKED(UlxFormatDataLibrary, FormatArgumentDataBlank)); + return UlxBlueprintErrorLibrary::StaticClass()->FindFunctionByName(GET_MEMBER_NAME_CHECKED(UlxBlueprintErrorLibrary, FormatArgumentDataBlank)); } - // Try to find an exact match in the UlxFormatDataLibrary. + // Try to find a match in the UlxFormatDataLibrary. // for (auto It = TFieldIterator(UlxFormatDataLibrary::StaticClass()); It; ++It) { @@ -305,6 +327,14 @@ 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. + // + if ((PinType.PinCategory == UEdGraphSchema_K2::PC_Byte) && (nullptr != Cast(PinType.PinSubCategoryObject))) + { + return UlxBlueprintErrorLibrary::StaticClass()->FindFunctionByName(GET_MEMBER_NAME_CHECKED(UlxBlueprintErrorLibrary, FormatArgumentDataEnum)); + } // A case for subclasses of 'Object' which are not exactly 'Object' // @@ -320,12 +350,12 @@ UFunction *ToFormatArgumentData(const UEdGraphSchema_K2 *Schema, const FEdGraphP -void UK2Node_FormatError::ExpandNode(class FKismetCompilerContext& CompilerContext, UEdGraph* SourceGraph) +void UK2Node_FormatMessage::ExpandNode(class FKismetCompilerContext& CompilerContext, UEdGraph* SourceGraph) { Super::ExpandNode(CompilerContext, SourceGraph); /** - At the end of this, the UK2Node_FormatError will not be a part of the Blueprint, it merely handles connecting + At the end of this, the UK2Node_FormatMessage will not be a part of the Blueprint, it merely handles connecting the other nodes into the Blueprint. */ @@ -336,9 +366,19 @@ void UK2Node_FormatError::ExpandNode(class FKismetCompilerContext& CompilerConte MakeArrayNode->AllocateDefaultPins(); CompilerContext.MessageLog.NotifyIntermediateObjectCreation(MakeArrayNode, this); + // Decide which formatting function we're going to call. + UFunction *FormatFunction; + if (IsFormatErrorMessage()) + { + FormatFunction = UlxBlueprintErrorLibrary::StaticClass()->FindFunctionByName(GET_MEMBER_NAME_CHECKED(UlxBlueprintErrorLibrary, FormatErrorInternal)); + } + else + { + FormatFunction = UKismetTextLibrary::StaticClass()->FindFunctionByName(GET_MEMBER_NAME_CHECKED(UKismetTextLibrary, Format)); + } + // This is the node that does all the Format work and outputs the message. UK2Node_CallFunction* CallFormatFunction = CompilerContext.SpawnIntermediateNode(this, SourceGraph); - UFunction *FormatFunction = UlxBlueprintErrorLibrary::StaticClass()->FindFunctionByName(GET_MEMBER_NAME_CHECKED(UlxBlueprintErrorLibrary, FormatErrorInternal)); CallFormatFunction->SetFromFunction(FormatFunction); CallFormatFunction->AllocateDefaultPins(); CompilerContext.MessageLog.NotifyIntermediateObjectCreation(CallFormatFunction, this); @@ -372,16 +412,23 @@ void UK2Node_FormatError::ExpandNode(class FKismetCompilerContext& CompilerConte CompilerContext.MessageLog.NotifyIntermediateObjectCreation(ConvertNode, this); UEdGraphPin *ValuePin = ConvertNode->FindPin(TEXT("Value")); UEdGraphPin *NamePin = ConvertNode->FindPinChecked(TEXT("Name")); + UEdGraphPin *SubCategoryObjectPin = ConvertNode->FindPin(TEXT("PinSubCategoryObject")); // Set a value for the 'Name' pin of the converter. ConvertNode->GetSchema()->TrySetDefaultValue(*NamePin, OriginalName); - // Connect the Value pin of the converter. + // Connect the Value pin of the converter, if any. if (ValuePin != nullptr) { CompilerContext.MovePinLinksToIntermediate(*ArgumentPin, *ValuePin); } + // If the converter wants to know the PinSubCategoryObject, pass it in. + if (SubCategoryObjectPin != nullptr) + { + SubCategoryObjectPin->DefaultObject = Cast(ArgumentPin->PinType.PinSubCategoryObject); + } + // The "Make Array" node already has one pin available, so don't create one for ArgIdx == 0 if(ArgIdx > 0) { @@ -396,11 +443,18 @@ void UK2Node_FormatError::ExpandNode(class FKismetCompilerContext& CompilerConte ConvertNode->GetReturnValuePin()->MakeLinkTo(InputPin); } - // Move connection of "Format" pin to the call function's "InPattern" pin + // Connect up other pins to the Formatting node. CompilerContext.MovePinLinksToIntermediate(*FindPinChecked(FormatPinName), *CallFormatFunction->FindPinChecked(TEXT("InPattern"))); - CompilerContext.MovePinLinksToIntermediate(*FindPinChecked(VerbosityPinName), *CallFormatFunction->FindPinChecked(TEXT("Verbosity"))); - CompilerContext.MovePinLinksToIntermediate(*FindPinChecked(DisplayDurationPinName), *CallFormatFunction->FindPinChecked(TEXT("DisplayDuration"))); - + if (IsFormatErrorMessage()) + { + CompilerContext.MovePinLinksToIntermediate(*FindPinChecked(VerbosityPinName), *CallFormatFunction->FindPinChecked(TEXT("Verbosity"))); + CompilerContext.MovePinLinksToIntermediate(*FindPinChecked(DisplayDurationPinName), *CallFormatFunction->FindPinChecked(TEXT("DisplayDuration"))); + } + else + { + CompilerContext.MovePinLinksToIntermediate(*FindPinChecked(ResultPinName), *CallFormatFunction->GetReturnValuePin()); + } + // Link up the Exec pins. CompilerContext.MovePinLinksToIntermediate(*GetExecPin(), *CallFormatFunction->GetExecPin()); CompilerContext.MovePinLinksToIntermediate(*GetThenPin(), *CallFormatFunction->GetThenPin()); @@ -409,7 +463,7 @@ void UK2Node_FormatError::ExpandNode(class FKismetCompilerContext& CompilerConte } -UK2Node::ERedirectType UK2Node_FormatError::DoPinsMatchForReconstruction(const UEdGraphPin* NewPin, int32 NewPinIndex, const UEdGraphPin* OldPin, int32 OldPinIndex) const +UK2Node::ERedirectType UK2Node_FormatMessage::DoPinsMatchForReconstruction(const UEdGraphPin* NewPin, int32 NewPinIndex, const UEdGraphPin* OldPin, int32 OldPinIndex) const { ERedirectType RedirectType = ERedirectType_None; @@ -454,7 +508,7 @@ UK2Node::ERedirectType UK2Node_FormatError::DoPinsMatchForReconstruction(const U return RedirectType; } -bool UK2Node_FormatError::IsConnectionDisallowed(const UEdGraphPin* MyPin, const UEdGraphPin* OtherPin, FString& OutReason) const +bool UK2Node_FormatMessage::IsConnectionDisallowed(const UEdGraphPin* MyPin, const UEdGraphPin* OtherPin, FString& OutReason) const { // The following pins cannot be connected. They are meant to be hardwired constants. if (IsFormatPin(MyPin)) @@ -481,7 +535,7 @@ bool UK2Node_FormatError::IsConnectionDisallowed(const UEdGraphPin* MyPin, const } -void UK2Node_FormatError::GetMenuActions(FBlueprintActionDatabaseRegistrar& ActionRegistrar) const +void UK2Node_FormatMessage::GetMenuActions(FBlueprintActionDatabaseRegistrar& ActionRegistrar) const { // actions get registered under specific object-keys; the idea is that // actions might have to be updated (or deleted) if their object-key is @@ -501,10 +555,33 @@ void UK2Node_FormatError::GetMenuActions(FBlueprintActionDatabaseRegistrar& Acti } } -FText UK2Node_FormatError::GetMenuCategory() const +FText UK2Node_FormatMessage::GetMenuCategory() const { return FEditorCategoryUtils::GetCommonCategory(FCommonEditorCategory::Text); } +UK2Node_FormatMessage::UK2Node_FormatMessage(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +{ + NodeTooltip = LOCTEXT("NodeTooltip", + "Format a message, and output it as Text.\n" + "\n" + " \u2022 Use {ArgName} to denote format arguments, giving each argument a different ArgName.\n" + "\n" + ); +} + +UK2Node_FormatErrorMessage::UK2Node_FormatErrorMessage(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +{ + NodeTooltip = LOCTEXT("NodeTooltip", + "Output an error, warning, or informational message to the log file.\n" + "\n" + " \u2022 Use {ArgName} to denote format arguments, giving each argument a different ArgName.\n" + "\n" + "It is often desirable to use this in conjunction with a separate utility that\n" + "pauses the execution of the blueprint whenever an error is logged." + ); +} #undef LOCTEXT_NAMESPACE diff --git a/Source/Integration/FormatError.h b/Source/Integration/FormatError.h index 6735f652..4d661e8b 100644 --- a/Source/Integration/FormatError.h +++ b/Source/Integration/FormatError.h @@ -22,14 +22,21 @@ class FString; class UEdGraph; class UObject; - - - // -// The Format Error Message K2Node. +// FormatMessage and FormatErrorMessage +// +// This file defines two K2Nodes: FormatMessage, and FormatErrorMessage. The +// only difference between them is that the former outputs the message as an +// output pin. The latter outputs the message to the log instead. +// +// To implement code reuse, we put all the code into FormatMessage, and made +// FormatErrorMessage a derived class of FormatMessage. The derived class +// doesn't override anything - all it does is set a flag, the flag changes +// the behavior of FormatMessage. +// // UCLASS(MinimalAPI) -class UK2Node_FormatError : public UK2Node +class UK2Node_FormatMessage : public UK2Node { GENERATED_UCLASS_BODY() @@ -60,14 +67,17 @@ class UK2Node_FormatError : public UK2Node virtual int32 GetNodeRefreshPriority() const override { return EBaseNodeRefreshPriority::Low_UsesDependentWildcard; } //~ End UK2Node Interface. -private: +protected: /** Create all necessary pins */ void CreateCorrectPins(); /** Synchronize the type of the given argument pin with the type its connected to, or reset it to a wildcard pin if there's no connection */ void SynchronizeArgumentPinType(UEdGraphPin* Pin); -private: + /** Our derived class will set this to true, altering the behavior of this K2Node. **/ + virtual bool IsFormatErrorMessage() const { return false; } + +protected: /** When adding arguments to the node, their names are placed here and are generated as pins during construction */ UPROPERTY() TArray PinNames; @@ -76,3 +86,18 @@ private: FText NodeTooltip; }; + +// +// This derives from FormatMessage. +// +UCLASS(MinimalAPI) +class UK2Node_FormatErrorMessage : public UK2Node_FormatMessage +{ + GENERATED_UCLASS_BODY() + + // Setting this flag alters the behavior of FormatMessage, making it + // output to the log instead of to a pin. + // + virtual bool IsFormatErrorMessage() const override { return true; } +}; +