// Copyright Epic Games, Inc. All Rights Reserved. #include "FormatError.h" #include "BlueprintActionDatabaseRegistrar.h" #include "BlueprintNodeSpawner.h" #include "Containers/EnumAsByte.h" #include "Containers/UnrealString.h" #include "EdGraph/EdGraph.h" #include "EdGraph/EdGraphSchema.h" #include "EdGraphSchema_K2.h" #include "EdGraphSchema_K2_Actions.h" #include "EditorCategoryUtils.h" #include "Engine/Blueprint.h" #include "HAL/PlatformCrt.h" #include "Internationalization/Internationalization.h" #include "Internationalization/TextFormatter.h" #include "K2Node_CallFunction.h" #include "K2Node_MakeArray.h" #include "K2Node_MakeStruct.h" #include "Kismet/KismetMathLibrary.h" #include "Kismet/KismetSystemLibrary.h" #include "Kismet/KismetTextLibrary.h" #include "Kismet2/BlueprintEditorUtils.h" #include "Kismet2/CompilerResultsLog.h" #include "KismetCompiler.h" #include "Math/Vector2D.h" #include "Misc/AssertionMacros.h" #include "Misc/CString.h" #include "ScopedTransaction.h" #include "Templates/Casts.h" #include "Templates/SubclassOf.h" #include "UObject/Class.h" #include "UObject/ObjectPtr.h" #include "UObject/Package.h" #include "UObject/UnrealNames.h" #include "UObject/UnrealType.h" #include "UObject/WeakObjectPtr.h" #include "UObject/WeakObjectPtrTemplates.h" #define LOCTEXT_NAMESPACE "FormatError" // All argument pins will have Names that start with "A:" static bool IsArgumentPin(const UEdGraphPin *Pin) { TCHAR pname[FName::StringBufferSize]; Pin->PinName.ToString(pname); return pname[0] == 'A' && pname[1] == ':'; } static FName ArgumentNameAddPrefix(const FString &name) { FString Prefixed = FString("A:") + name; return FName(*Prefixed); } static FString ArgumentNameRemovePrefix(const FName &name) { return name.ToString().Mid(2, FName::StringBufferSize); } static const FName VerbosityPinName(TEXT("Verbosity")); static bool IsVerbosityPin(const UEdGraphPin *Pin) { return (Pin->PinName == VerbosityPinName); } static const FName DisplayDurationPinName(TEXT("DisplayDuration")); static bool IsDisplayDurationPin(const UEdGraphPin *Pin) { return (Pin->PinName == DisplayDurationPinName); } static const FName FormatPinName(TEXT("Format")); 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" " \u2022 Arguments may be Byte, Integer, Float, Text, String, Name, Boolean, Object or ETextGender.\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." ); } void UK2Node_FormatError::AllocateDefaultPins() { Super::AllocateDefaultPins(); CreateCorrectPins(); } void UK2Node_FormatError::CreateCorrectPins() { if (FindPin(UEdGraphSchema_K2::PN_Execute) == nullptr) { CreatePin(EGPD_Input, UEdGraphSchema_K2::PC_Exec, UEdGraphSchema_K2::PN_Execute); } if (FindPin(UEdGraphSchema_K2::PN_Then) == nullptr) { CreatePin(EGPD_Output, UEdGraphSchema_K2::PC_Exec, UEdGraphSchema_K2::PN_Then); } if (FindPin(FormatPinName, EGPD_Input) == nullptr) { UEdGraphPin *P = CreatePin(EGPD_Input, UEdGraphSchema_K2::PC_Text, FormatPinName); P->DefaultTextValue = LOCTEXT("FormatErrorMessage_DefaultMessage", "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 (FindPin(DisplayDurationPinName, EGPD_Input) == nullptr) { UEdGraphPin *P = CreatePin(EGPD_Input, UEdGraphSchema_K2::PC_Byte, StaticEnum(), DisplayDurationPinName); P->DefaultValue = TEXT("No_Display"); P->AutogeneratedDefaultValue = P->DefaultValue; } // Transfer all Existing Argument pins to the Old Pins Map. TMap OldPins; for (auto It = Pins.CreateIterator(); It; ++It) { UEdGraphPin* CheckPin = *It; if (IsArgumentPin(CheckPin)) { OldPins.Add(ArgumentNameRemovePrefix(CheckPin->PinName), CheckPin); It.RemoveCurrent(); } } // Create Argument pins in the correct order, reusing old pins where possible. for (const FString& Name : PinNames) { UEdGraphPin **OldPin = OldPins.Find(Name); if (OldPin != nullptr) { Pins.Emplace(*OldPin); OldPins.Remove(Name); } else { FName PrefixedName = ArgumentNameAddPrefix(Name); CreatePin(EGPD_Input, UEdGraphSchema_K2::PC_Wildcard, PrefixedName); } } // Delete any unused pins. for (auto &iter : OldPins) { iter.Value->Modify(); iter.Value->MarkAsGarbage(); } OldPins.Empty(); } void UK2Node_FormatError::SynchronizeArgumentPinType(UEdGraphPin* Pin) { if (IsArgumentPin(Pin)) { const UEdGraphSchema_K2* K2Schema = Cast(GetSchema()); bool bPinTypeChanged = false; if (Pin->LinkedTo.Num() == 0) { static const FEdGraphPinType WildcardPinType = FEdGraphPinType(UEdGraphSchema_K2::PC_Wildcard, NAME_None, nullptr, EPinContainerType::None, false, FEdGraphTerminalType()); // Ensure wildcard if (Pin->PinType != WildcardPinType) { Pin->PinType = WildcardPinType; bPinTypeChanged = true; } } else { UEdGraphPin* ArgumentSourcePin = Pin->LinkedTo[0]; // Take the type of the connected pin if (Pin->PinType != ArgumentSourcePin->PinType) { Pin->PinType = ArgumentSourcePin->PinType; bPinTypeChanged = true; } } if (bPinTypeChanged) { // Let the graph know to refresh GetGraph()->NotifyNodeChanged(this); UBlueprint* Blueprint = GetBlueprint(); if (!Blueprint->bBeingCompiled) { FBlueprintEditorUtils::MarkBlueprintAsModified(Blueprint); } } } } FText UK2Node_FormatError::GetNodeTitle(ENodeTitleType::Type TitleType) const { return LOCTEXT("FormatError_Title", "Format Error Message"); } FText UK2Node_FormatError::GetPinDisplayName(const UEdGraphPin* Pin) const { // The exec pins don't need labels. if (Pin->PinType.PinCategory == UEdGraphSchema_K2::PC_Exec) { return FText::GetEmpty(); } // Many pins can go unlabeled if they have default values. if (IsFormatPin(Pin) || IsVerbosityPin(Pin) || IsDisplayDurationPin(Pin)) { if (Pin->LinkedTo.Num() == 0) { return FText::GetEmpty(); } } // For argument pins, we must strip off the Argument Pin Prefix. if (IsArgumentPin(Pin)) { return FText::FromString(ArgumentNameRemovePrefix(Pin->PinName)); } // Otherwise, just return the Pin Name the normal way. return FText::FromName(Pin->PinName); } void UK2Node_FormatError::PostEditChangeProperty(struct FPropertyChangedEvent& PropertyChangedEvent) { const FName PropertyName = (PropertyChangedEvent.Property ? PropertyChangedEvent.Property->GetFName() : NAME_None); if (PropertyName == GET_MEMBER_NAME_CHECKED(UK2Node_FormatError, PinNames)) { ReconstructNode(); } Super::PostEditChangeProperty(PropertyChangedEvent); GetGraph()->NotifyNodeChanged(this); } void UK2Node_FormatError::PinConnectionListChanged(UEdGraphPin* Pin) { Modify(); SynchronizeArgumentPinType(Pin); } void UK2Node_FormatError::PinDefaultValueChanged(UEdGraphPin* Pin) { if(IsFormatPin(Pin)) { PinNames.Empty(); FText::GetFormatPatternParameters(Pin->DefaultTextValue, PinNames); CreateCorrectPins(); GetGraph()->NotifyNodeChanged(this); } } void UK2Node_FormatError::PinTypeChanged(UEdGraphPin* Pin) { SynchronizeArgumentPinType(Pin); Super::PinTypeChanged(Pin); } FText UK2Node_FormatError::GetTooltipText() const { return NodeTooltip; } UEdGraphPin* FindOutputStructPinChecked(UEdGraphNode* Node) { check(NULL != Node); UEdGraphPin* OutputPin = NULL; for (int32 PinIndex = 0; PinIndex < Node->Pins.Num(); ++PinIndex) { UEdGraphPin* Pin = Node->Pins[PinIndex]; if (Pin && (EGPD_Output == Pin->Direction)) { OutputPin = Pin; break; } } check(NULL != OutputPin); return OutputPin; } void UK2Node_FormatError::PostReconstructNode() { Super::PostReconstructNode(); UEdGraph* OuterGraph = GetGraph(); if (!IsTemplate() && OuterGraph && OuterGraph->Schema) { for (UEdGraphPin* CurrentPin : Pins) { // Potentially update an argument pin type SynchronizeArgumentPinType(CurrentPin); } } } void UK2Node_FormatError::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 the other nodes into the Blueprint. */ const UEdGraphSchema_K2* Schema = CompilerContext.GetSchema(); // Create a "Make Array" node to compile the list of arguments into an array for the Format function being called UK2Node_MakeArray* MakeArrayNode = CompilerContext.SpawnIntermediateNode(this, SourceGraph); MakeArrayNode->AllocateDefaultPins(); CompilerContext.MessageLog.NotifyIntermediateObjectCreation(MakeArrayNode, this); // This is the node that does all the Format work and outputs the message. UK2Node_CallFunction* CallFormatFunction = CompilerContext.SpawnIntermediateNode(this, SourceGraph); UFunction *FormatFunction = UlxFormatErrorLibrary::StaticClass()->FindFunctionByName(GET_MEMBER_NAME_CHECKED(UlxFormatErrorLibrary, FormatErrorInternal)); CallFormatFunction->SetFromFunction(FormatFunction); CallFormatFunction->AllocateDefaultPins(); CompilerContext.MessageLog.NotifyIntermediateObjectCreation(CallFormatFunction, this); // Connect the output of the "Make Array" pin to the function's "InArgs" pin UEdGraphPin* ArrayOut = MakeArrayNode->GetOutputPin(); ArrayOut->MakeLinkTo(CallFormatFunction->FindPinChecked(TEXT("InArgs"))); // This will set the "Make Array" node's type, only works if one pin is connected. MakeArrayNode->PinConnectionListChanged(ArrayOut); // For each argument, we will need to add in a "Make Struct" node. for(int32 ArgIdx = 0; ArgIdx < PinNames.Num(); ++ArgIdx) { FString OriginalName = PinNames[ArgIdx]; UEdGraphPin* ArgumentPin = FindPin(ArgumentNameAddPrefix(OriginalName), EGPD_Input); static UScriptStruct* FormatArgumentDataStruct = FindObjectChecked(FindObjectChecked(nullptr, TEXT("/Script/Engine")), TEXT("FormatArgumentData")); // Spawn a "Make Struct" node to create the struct needed for formatting the text. UK2Node_MakeStruct* MakeFormatArgumentDataStruct = CompilerContext.SpawnIntermediateNode(this, SourceGraph); MakeFormatArgumentDataStruct->StructType = FormatArgumentDataStruct; MakeFormatArgumentDataStruct->AllocateDefaultPins(); MakeFormatArgumentDataStruct->bMadeAfterOverridePinRemoval = true; CompilerContext.MessageLog.NotifyIntermediateObjectCreation(MakeFormatArgumentDataStruct, this); // Set the struct's "ArgumentName" pin literal to be the argument pin's name. MakeFormatArgumentDataStruct->GetSchema()->TrySetDefaultValue(*MakeFormatArgumentDataStruct->FindPinChecked(GET_MEMBER_NAME_STRING_CHECKED(FFormatArgumentData, ArgumentName)), OriginalName); UEdGraphPin* ArgumentTypePin = MakeFormatArgumentDataStruct->FindPinChecked(GET_MEMBER_NAME_STRING_CHECKED(FFormatArgumentData, ArgumentValueType)); // Move the connection of the argument pin to the correct argument value pin, and also set the correct argument type based on the pin that was hooked up. if (ArgumentPin->LinkedTo.Num() > 0) { const FName& ArgumentPinCategory = ArgumentPin->PinType.PinCategory; // Adds an implicit conversion node to this argument based on its function and pin name auto AddConversionNode = [&](const FName FuncName, const TCHAR* PinName) { // Set the default value if there was something passed in, or default to "Text" MakeFormatArgumentDataStruct->GetSchema()->TrySetDefaultValue(*ArgumentTypePin, TEXT("Text")); // Spawn conversion node based on the given function name UK2Node_CallFunction* ToTextFunction = CompilerContext.SpawnIntermediateNode(this, SourceGraph); ToTextFunction->SetFromFunction(UKismetTextLibrary::StaticClass()->FindFunctionByName(FuncName)); ToTextFunction->AllocateDefaultPins(); CompilerContext.MessageLog.NotifyIntermediateObjectCreation(ToTextFunction, this); CompilerContext.MovePinLinksToIntermediate(*ArgumentPin, *ToTextFunction->FindPinChecked(PinName)); ToTextFunction->FindPinChecked(UEdGraphSchema_K2::PN_ReturnValue)->MakeLinkTo(MakeFormatArgumentDataStruct->FindPinChecked(GET_MEMBER_NAME_STRING_CHECKED(FFormatArgumentData, ArgumentValue))); }; if (ArgumentPinCategory == UEdGraphSchema_K2::PC_Int) { MakeFormatArgumentDataStruct->GetSchema()->TrySetDefaultValue(*ArgumentTypePin, TEXT("Int")); // Need a manual cast from int -> int64 UK2Node_CallFunction* CallFloatToDoubleFunction = CompilerContext.SpawnIntermediateNode(this, SourceGraph); CallFloatToDoubleFunction->SetFromFunction(UKismetMathLibrary::StaticClass()->FindFunctionByName(GET_MEMBER_NAME_CHECKED(UKismetMathLibrary, Conv_IntToInt64))); CallFloatToDoubleFunction->AllocateDefaultPins(); CompilerContext.MessageLog.NotifyIntermediateObjectCreation(CallFloatToDoubleFunction, this); // Move the byte output pin to the input pin of the conversion node CompilerContext.MovePinLinksToIntermediate(*ArgumentPin, *CallFloatToDoubleFunction->FindPinChecked(TEXT("InInt"))); // Connect the int output pin to the argument value CallFloatToDoubleFunction->FindPinChecked(UEdGraphSchema_K2::PN_ReturnValue)->MakeLinkTo(MakeFormatArgumentDataStruct->FindPinChecked(GET_MEMBER_NAME_STRING_CHECKED(FFormatArgumentData, ArgumentValueInt))); } else if (ArgumentPinCategory == UEdGraphSchema_K2::PC_Real) { if (ArgumentPin->PinType.PinSubCategory == UEdGraphSchema_K2::PC_Float) { MakeFormatArgumentDataStruct->GetSchema()->TrySetDefaultValue(*ArgumentTypePin, TEXT("Float")); CompilerContext.MovePinLinksToIntermediate(*ArgumentPin, *MakeFormatArgumentDataStruct->FindPinChecked(GET_MEMBER_NAME_STRING_CHECKED(FFormatArgumentData, ArgumentValueFloat))); } else if (ArgumentPin->PinType.PinSubCategory == UEdGraphSchema_K2::PC_Double) { MakeFormatArgumentDataStruct->GetSchema()->TrySetDefaultValue(*ArgumentTypePin, TEXT("Double")); CompilerContext.MovePinLinksToIntermediate(*ArgumentPin, *MakeFormatArgumentDataStruct->FindPinChecked(GET_MEMBER_NAME_STRING_CHECKED(FFormatArgumentData, ArgumentValueDouble))); } else { check(false); } } else if (ArgumentPinCategory == UEdGraphSchema_K2::PC_Int64) { MakeFormatArgumentDataStruct->GetSchema()->TrySetDefaultValue(*ArgumentTypePin, TEXT("Int64")); CompilerContext.MovePinLinksToIntermediate(*ArgumentPin, *MakeFormatArgumentDataStruct->FindPinChecked(GET_MEMBER_NAME_STRING_CHECKED(FFormatArgumentData, ArgumentValueInt))); } else if (ArgumentPinCategory == UEdGraphSchema_K2::PC_Text) { MakeFormatArgumentDataStruct->GetSchema()->TrySetDefaultValue(*ArgumentTypePin, TEXT("Text")); CompilerContext.MovePinLinksToIntermediate(*ArgumentPin, *MakeFormatArgumentDataStruct->FindPinChecked(GET_MEMBER_NAME_STRING_CHECKED(FFormatArgumentData, ArgumentValue))); } else if (ArgumentPinCategory == UEdGraphSchema_K2::PC_Byte && !ArgumentPin->PinType.PinSubCategoryObject.IsValid()) { MakeFormatArgumentDataStruct->GetSchema()->TrySetDefaultValue(*ArgumentTypePin, TEXT("Int")); // Need a manual cast from byte -> int UK2Node_CallFunction* CallByteToIntFunction = CompilerContext.SpawnIntermediateNode(this, SourceGraph); CallByteToIntFunction->SetFromFunction(UKismetMathLibrary::StaticClass()->FindFunctionByName(GET_MEMBER_NAME_CHECKED(UKismetMathLibrary, Conv_ByteToInt64))); CallByteToIntFunction->AllocateDefaultPins(); CompilerContext.MessageLog.NotifyIntermediateObjectCreation(CallByteToIntFunction, this); // Move the byte output pin to the input pin of the conversion node CompilerContext.MovePinLinksToIntermediate(*ArgumentPin, *CallByteToIntFunction->FindPinChecked(TEXT("InByte"))); // Connect the int output pin to the argument value CallByteToIntFunction->FindPinChecked(UEdGraphSchema_K2::PN_ReturnValue)->MakeLinkTo(MakeFormatArgumentDataStruct->FindPinChecked(GET_MEMBER_NAME_STRING_CHECKED(FFormatArgumentData, ArgumentValueInt))); } else if (ArgumentPinCategory == UEdGraphSchema_K2::PC_Byte || ArgumentPinCategory == UEdGraphSchema_K2::PC_Enum) { static UEnum* TextGenderEnum = FindObjectChecked(nullptr, TEXT("/Script/Engine.ETextGender"), /*ExactClass*/true); if (ArgumentPin->PinType.PinSubCategoryObject == TextGenderEnum) { MakeFormatArgumentDataStruct->GetSchema()->TrySetDefaultValue(*ArgumentTypePin, TEXT("Gender")); CompilerContext.MovePinLinksToIntermediate(*ArgumentPin, *MakeFormatArgumentDataStruct->FindPinChecked(GET_MEMBER_NAME_STRING_CHECKED(FFormatArgumentData, ArgumentValueGender))); } } else if (ArgumentPinCategory == UEdGraphSchema_K2::PC_Boolean) { AddConversionNode(GET_MEMBER_NAME_CHECKED(UKismetTextLibrary, Conv_BoolToText), TEXT("InBool")); } else if (ArgumentPinCategory == UEdGraphSchema_K2::PC_Name) { AddConversionNode(GET_MEMBER_NAME_CHECKED(UKismetTextLibrary, Conv_NameToText), TEXT("InName")); } else if (ArgumentPinCategory == UEdGraphSchema_K2::PC_String) { AddConversionNode(GET_MEMBER_NAME_CHECKED(UKismetTextLibrary, Conv_StringToText), TEXT("InString")); } else if (ArgumentPinCategory == UEdGraphSchema_K2::PC_Object) { AddConversionNode(GET_MEMBER_NAME_CHECKED(UKismetTextLibrary, Conv_ObjectToText), TEXT("InObj")); } else { // Unexpected pin type! CompilerContext.MessageLog.Error(*FText::Format(LOCTEXT("Error_UnexpectedPinType", "Pin '{0}' has an unexpected type: {1}"), FText::FromString(OriginalName), FText::FromName(ArgumentPinCategory)).ToString()); } } else { // No connected pin - just default to an empty text MakeFormatArgumentDataStruct->GetSchema()->TrySetDefaultValue(*ArgumentTypePin, TEXT("Text")); MakeFormatArgumentDataStruct->GetSchema()->TrySetDefaultText(*MakeFormatArgumentDataStruct->FindPinChecked(GET_MEMBER_NAME_STRING_CHECKED(FFormatArgumentData, ArgumentValue)), FText::GetEmpty()); } // The "Make Array" node already has one pin available, so don't create one for ArgIdx == 0 if(ArgIdx > 0) { MakeArrayNode->AddInputPin(); } // Find the input pin on the "Make Array" node by index. const FString PinName = FString::Printf(TEXT("[%d]"), ArgIdx); UEdGraphPin* InputPin = MakeArrayNode->FindPinChecked(PinName); // Find the output for the pin's "Make Struct" node and link it to the corresponding pin on the "Make Array" node. FindOutputStructPinChecked(MakeFormatArgumentDataStruct)->MakeLinkTo(InputPin); } // Move connection of "Format" pin to the call function's "InPattern" pin CompilerContext.MovePinLinksToIntermediate(*FindPinChecked(FormatPinName), *CallFormatFunction->FindPinChecked(TEXT("InPattern"))); CompilerContext.MovePinLinksToIntermediate(*FindPinChecked(VerbosityPinName), *CallFormatFunction->FindPinChecked(TEXT("Verbosity"))); CompilerContext.MovePinLinksToIntermediate(*FindPinChecked(DisplayDurationPinName), *CallFormatFunction->FindPinChecked(TEXT("DisplayDuration"))); // Link up the Exec pins. CompilerContext.MovePinLinksToIntermediate(*GetExecPin(), *CallFormatFunction->GetExecPin()); CompilerContext.MovePinLinksToIntermediate(*GetThenPin(), *CallFormatFunction->GetThenPin()); BreakAllNodeLinks(); } UK2Node::ERedirectType UK2Node_FormatError::DoPinsMatchForReconstruction(const UEdGraphPin* NewPin, int32 NewPinIndex, const UEdGraphPin* OldPin, int32 OldPinIndex) const { ERedirectType RedirectType = ERedirectType_None; // if the pin names do match if (NewPin->PinName.ToString().Equals(OldPin->PinName.ToString(), ESearchCase::CaseSensitive)) { // Make sure we're not dealing with a menu node UEdGraph* OuterGraph = GetGraph(); if( OuterGraph && OuterGraph->Schema ) { const UEdGraphSchema_K2* K2Schema = Cast(GetSchema()); if( !K2Schema || K2Schema->IsSelfPin(*NewPin) || K2Schema->ArePinTypesCompatible(OldPin->PinType, NewPin->PinType) ) { RedirectType = ERedirectType_Name; } else { RedirectType = ERedirectType_None; } } } else { // try looking for a redirect if it's a K2 node if (UK2Node* Node = Cast(NewPin->GetOwningNode())) { // if you don't have matching pin, now check if there is any redirect param set TArray OldPinNames; GetRedirectPinNames(*OldPin, OldPinNames); FName NewPinName; RedirectType = ShouldRedirectParam(OldPinNames, /*out*/ NewPinName, Node); // make sure they match if ((RedirectType != ERedirectType_None) && (!NewPin->PinName.ToString().Equals(NewPinName.ToString(), ESearchCase::CaseSensitive))) { RedirectType = ERedirectType_None; } } } return RedirectType; } bool UK2Node_FormatError::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)) { OutReason = LOCTEXT("Error_FormatStringMustBeHardwired", "Format string must be a hardwired constant.").ToString(); return true; } // Argument input pins may only be connected to Byte, Integer, Float, Text, and ETextGender pins... if (IsArgumentPin(MyPin)) { const UEdGraphSchema_K2* K2Schema = Cast(GetSchema()); const FName& OtherPinCategory = OtherPin->PinType.PinCategory; bool bIsValidType = false; if (OtherPinCategory == UEdGraphSchema_K2::PC_Int || OtherPinCategory == UEdGraphSchema_K2::PC_Real || OtherPinCategory == UEdGraphSchema_K2::PC_Text || (OtherPinCategory == UEdGraphSchema_K2::PC_Byte && !OtherPin->PinType.PinSubCategoryObject.IsValid()) || OtherPinCategory == UEdGraphSchema_K2::PC_Boolean || OtherPinCategory == UEdGraphSchema_K2::PC_String || OtherPinCategory == UEdGraphSchema_K2::PC_Name || OtherPinCategory == UEdGraphSchema_K2::PC_Object || OtherPinCategory == UEdGraphSchema_K2::PC_Wildcard || OtherPinCategory == UEdGraphSchema_K2::PC_Int64) { bIsValidType = true; } else if (OtherPinCategory == UEdGraphSchema_K2::PC_Byte || OtherPinCategory == UEdGraphSchema_K2::PC_Enum) { static UEnum* TextGenderEnum = FindObjectChecked(nullptr, TEXT("/Script/Engine.ETextGender"), /*ExactClass*/true); if (OtherPin->PinType.PinSubCategoryObject == TextGenderEnum) { bIsValidType = true; } } if (!bIsValidType) { OutReason = LOCTEXT("Error_InvalidArgumentType", "Format arguments may only be Byte, Integer, Int64, Float, Double, Text, String, Name, Boolean, Object, Wildcard or ETextGender.").ToString(); return true; } } return Super::IsConnectionDisallowed(MyPin, OtherPin, OutReason); } void UK2Node_FormatError::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 // mutated (or removed)... here we use the node's class (so if the node // type disappears, then the action should go with it) UClass* ActionKey = GetClass(); // to keep from needlessly instantiating a UBlueprintNodeSpawner, first // check to make sure that the registrar is looking for actions of this type // (could be regenerating actions for a specific asset, and therefore the // registrar would only accept actions corresponding to that asset) if (ActionRegistrar.IsOpenForRegistration(ActionKey)) { UBlueprintNodeSpawner* NodeSpawner = UBlueprintNodeSpawner::Create(GetClass()); check(NodeSpawner != nullptr); ActionRegistrar.AddBlueprintAction(ActionKey, NodeSpawner); } } FText UK2Node_FormatError::GetMenuCategory() const { return FEditorCategoryUtils::GetCommonCategory(FCommonEditorCategory::Text); } void UlxFormatErrorLibrary::FormatErrorInternal(UObject *Context, ElxLogVerbosity Verbosity, ElxDisplayDuration DisplayDuration, FText InPattern, TArray InArgs) { // Generate the formatted string. // FText Message = FTextFormatter::Format(MoveTemp(InPattern), MoveTemp(InArgs), false, false); FString MessageString = Message.ToString(); // Convert the DisplayDuration enum into a number of seconds. // int Seconds = int(DisplayDuration); if (Seconds > 100) Seconds = (Seconds - 100) * 60; // Choose a color appropriate to the verbosity level. // FLinearColor Color; switch (Verbosity) { case ElxLogVerbosity::Fatal : Color = FLinearColor(1.0, 0.6, 0.6); break; case ElxLogVerbosity::Error : Color = FLinearColor(1.0, 0.6, 0.6); break; case ElxLogVerbosity::Warning : Color = FLinearColor(0.9, 0.9, 0.6); break; default: Color = FLinearColor(0.8, 0.8, 0.8); break; } // Convert verbosity to an internal value. // ELogVerbosity::Type VerbosityValue; switch (Verbosity) { case ElxLogVerbosity::Error: VerbosityValue = ELogVerbosity::Error; break; case ElxLogVerbosity::Warning: VerbosityValue = ELogVerbosity::Warning; break; case ElxLogVerbosity::Display: VerbosityValue = ELogVerbosity::Display; break; case ElxLogVerbosity::Log: VerbosityValue = ELogVerbosity::Log; break; case ElxLogVerbosity::Verbose: VerbosityValue = ELogVerbosity::Verbose; break; case ElxLogVerbosity::VeryVerbose: VerbosityValue = ELogVerbosity::VeryVerbose; break; case ElxLogVerbosity::Fatal: VerbosityValue = ELogVerbosity::Fatal; break; } // Get the blueprint name. // // Normally, the log function expects you to pass in a filename, and a log // category name. We use the blueprint name for both. // // Using the blueprint name as a log category name is not technically // correct. However, there is no correct way to create log categories // from inside of blueprints. Doing it this way at least produces a reasonable // message inside the log. What doesn't work correctly is the log message // suppression system. Ie, console commands like 'log verbose' // don't have any effect here. The design of the log message suppression // system is such that there just is no reasonable way to hook into it from // inside of blueprints. // FString BlueprintNameString = Context->GetClass()->GetName(); auto BlueprintNameAnsi = StringCast(*BlueprintNameString); FLogCategoryName BlueprintNameLogCategory(Context->GetClass()->GetFName()); // Output to Screen, if requested. // if (Seconds != 0) { UKismetSystemLibrary::PrintText(NULL, Message, true, false, Color, Seconds, NAME_None); } // Output to Log // if (VerbosityValue <= ELogVerbosity::COMPILED_IN_MINIMUM_VERBOSITY) { FMsg::Logf(BlueprintNameAnsi.Get(), 0, BlueprintNameLogCategory, VerbosityValue, TEXT("%s"), *MessageString); } } #undef LOCTEXT_NAMESPACE