// Copyright Epic Games, Inc. All Rights Reserved. #include "K2Node_GenericToText.h" #include "BlueprintActionDatabaseRegistrar.h" #include "BlueprintNodeSpawner.h" #include "EdGraph/EdGraph.h" #include "EdGraphSchema_K2.h" #include "EditorCategoryUtils.h" #include "K2Node_CallFunction.h" #include "Kismet/KismetTextLibrary.h" #include "Kismet2/BlueprintEditorUtils.h" #include "Kismet2/CompilerResultsLog.h" #include "KismetCompiler.h" #define LOCTEXT_NAMESPACE "K2Node_GenericToText" ///////////////////////////////////////////////////// // K2Node_GenericConvToText namespace UE::Blueprint::Private { const FLazyName PC_Value = "Value"; const TArrayView GetToTextSupportedScriptStruct() { static const UScriptStruct* List[] = { TBaseStructure::Get() , TBaseStructure::Get() , TBaseStructure::Get() , TBaseStructure::Get() , TBaseStructure::Get() , TBaseStructure::Get() }; return MakeArrayView(List); } bool IsGenericNumericProperty(FName OtherPinCategory) { return OtherPinCategory == UEdGraphSchema_K2::PC_Boolean || OtherPinCategory == UEdGraphSchema_K2::PC_Byte || OtherPinCategory == UEdGraphSchema_K2::PC_Enum || OtherPinCategory == UEdGraphSchema_K2::PC_Int || OtherPinCategory == UEdGraphSchema_K2::PC_Int64 || OtherPinCategory == UEdGraphSchema_K2::PC_Real || OtherPinCategory == UEdGraphSchema_K2::PC_Double || OtherPinCategory == UEdGraphSchema_K2::PC_Float; } } void UK2Node_GenericToText::AllocateDefaultPins() { Super::AllocateDefaultPins(); CreatePin(EGPD_Input, UEdGraphSchema_K2::PC_Wildcard, UE::Blueprint::Private::PC_Value.Resolve()); CreatePin(EGPD_Output, UEdGraphSchema_K2::PC_Text, UEdGraphSchema_K2::PN_ReturnValue); } void UK2Node_GenericToText::SynchronizeArgumentPinType() { bool bPinTypeChanged = false; UEdGraphPin* InputPin = FindPinChecked(UE::Blueprint::Private::PC_Value.Resolve(), EEdGraphPinDirection::EGPD_Input); if (InputPin->LinkedTo.Num() == 0) { static const FEdGraphPinType WildcardPinType = FEdGraphPinType(UEdGraphSchema_K2::PC_Wildcard, NAME_None, nullptr, EPinContainerType::None, false, FEdGraphTerminalType()); // Ensure wildcard if (InputPin->PinType != WildcardPinType) { InputPin->PinType = WildcardPinType; bPinTypeChanged = true; } } else { UEdGraphPin* SourcePin = InputPin->LinkedTo[0]; // Take the type of the connected pin if (InputPin->PinType != SourcePin->PinType) { InputPin->PinType = SourcePin->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_GenericToText::GetNodeTitle(ENodeTitleType::Type TitleType) const { return LOCTEXT("ToText_Title", "To Text"); } void UK2Node_GenericToText::PinConnectionListChanged(UEdGraphPin* Pin) { Modify(); // Potentially update an argument pin type SynchronizeArgumentPinType(); } void UK2Node_GenericToText::PinTypeChanged(UEdGraphPin* Pin) { SynchronizeArgumentPinType(); Super::PinTypeChanged(Pin); } void UK2Node_GenericToText::NodeConnectionListChanged() { SynchronizeArgumentPinType(); Super::NodeConnectionListChanged(); } FText UK2Node_GenericToText::GetTooltipText() const { return LOCTEXT("NodeTooltip", "Converts numeric value, enum, string, name and some basic structure to text.\n \u2022 The basic type are Vector, Vector2D, Rotator, Transform, LinearColor, DateTime."); } void UK2Node_GenericToText::PostReconstructNode() { Super::PostReconstructNode(); if (!IsTemplate()) { // Make sure we're not dealing with a menu node UEdGraph* OuterGraph = GetGraph(); if (OuterGraph && OuterGraph->Schema) { // Potentially update an argument pin type SynchronizeArgumentPinType(); } } } void UK2Node_GenericToText::ExpandNode(class FKismetCompilerContext& CompilerContext, UEdGraph* SourceGraph) { Super::ExpandNode(CompilerContext, SourceGraph); UEdGraphPin* InputPin = FindPinChecked(UE::Blueprint::Private::PC_Value.Resolve(), EEdGraphPinDirection::EGPD_Input); if (InputPin == nullptr) { CompilerContext.MessageLog.Error(*LOCTEXT("Error_BadInputPin", "Invalid input pin.").ToString()); return; } if (InputPin->LinkedTo.Num() != 1 || InputPin->PinType.PinCategory == UEdGraphSchema_K2::PC_Wildcard) { CompilerContext.MessageLog.Error(*LOCTEXT("Error_BadLinkedInputPin", "Input pin is not linked to a value.").ToString()); return; } // Convert to the correct function call auto CreateFunctionCall = [Self = this, &CompilerContext, SourceGraph](FName FunctionName) { UK2Node_CallFunction* CallFunction = CompilerContext.SpawnIntermediateNode(Self, SourceGraph); CallFunction->SetFromFunction(UKismetTextLibrary::StaticClass()->FindFunctionByName(FunctionName)); check(CallFunction->IsNodePure()); CallFunction->AllocateDefaultPins(); CompilerContext.MessageLog.NotifyIntermediateObjectCreation(CallFunction, Self); return CallFunction; }; UK2Node_CallFunction* CallFunction = nullptr; UEdGraphPin* CallFunctionInputValue = nullptr; if (UE::Blueprint::Private::IsGenericNumericProperty(InputPin->PinType.PinCategory)) { CallFunction = CreateFunctionCall(GET_MEMBER_NAME_CHECKED(UKismetTextLibrary, Conv_NumericPropertyToText)); CallFunctionInputValue = CallFunction->FindPinChecked(TEXT("Value")); // Set the generic type CallFunctionInputValue->PinType = InputPin->PinType; } else if (InputPin->PinType.PinCategory == UEdGraphSchema_K2::PC_String) { CallFunction = CreateFunctionCall(GET_MEMBER_NAME_CHECKED(UKismetTextLibrary, Conv_StringToText)); CallFunctionInputValue = CallFunction->FindPinChecked(TEXT("InString")); } else if (InputPin->PinType.PinCategory == UEdGraphSchema_K2::PC_Name) { CallFunction = CreateFunctionCall(GET_MEMBER_NAME_CHECKED(UKismetTextLibrary, Conv_NameToText)); CallFunctionInputValue = CallFunction->FindPinChecked(TEXT("InName")); } else if (InputPin->PinType.PinCategory == UEdGraphSchema_K2::PC_Struct) { const UObject* PinSubCategoryObject = InputPin->PinType.PinSubCategoryObject.Get(); if (PinSubCategoryObject == TBaseStructure::Get()) { CallFunction = CreateFunctionCall(GET_MEMBER_NAME_CHECKED(UKismetTextLibrary, Conv_VectorToText)); CallFunctionInputValue = CallFunction->FindPinChecked(TEXT("InVec")); } else if (PinSubCategoryObject == TBaseStructure::Get()) { CallFunction = CreateFunctionCall(GET_MEMBER_NAME_CHECKED(UKismetTextLibrary, Conv_Vector2dToText)); CallFunctionInputValue = CallFunction->FindPinChecked(TEXT("InVec")); } else if (PinSubCategoryObject == TBaseStructure::Get()) { CallFunction = CreateFunctionCall(GET_MEMBER_NAME_CHECKED(UKismetTextLibrary, Conv_RotatorToText)); CallFunctionInputValue = CallFunction->FindPinChecked(TEXT("InRot")); } else if (PinSubCategoryObject == TBaseStructure::Get()) { CallFunction = CreateFunctionCall(GET_MEMBER_NAME_CHECKED(UKismetTextLibrary, Conv_TransformToText)); CallFunctionInputValue = CallFunction->FindPinChecked(TEXT("InTrans")); } else if (PinSubCategoryObject == TBaseStructure::Get()) { CallFunction = CreateFunctionCall(GET_MEMBER_NAME_CHECKED(UKismetTextLibrary, Conv_ColorToText)); CallFunctionInputValue = CallFunction->FindPinChecked(TEXT("InColor")); } else if (PinSubCategoryObject == TBaseStructure::Get()) { CallFunction = CreateFunctionCall(GET_MEMBER_NAME_CHECKED(UKismetTextLibrary, AsDateTime_DateTime)); CallFunctionInputValue = CallFunction->FindPinChecked(TEXT("In")); } else { check(!UE::Blueprint::Private::GetToTextSupportedScriptStruct().Contains(Cast(PinSubCategoryObject))); } } if (CallFunction == nullptr || CallFunctionInputValue == nullptr) { CompilerContext.MessageLog.Error(*LOCTEXT("Error_BadPinCategory", "The input pin could not generate a valid ToText.").ToString()); return; } // Move connection of ToText's to the generated function. CompilerContext.MovePinLinksToIntermediate(*FindPinChecked(UEdGraphSchema_K2::PN_ReturnValue), *CallFunction->GetReturnValuePin()); CompilerContext.MovePinLinksToIntermediate(*FindPinChecked(TEXT("Value")), *CallFunctionInputValue); BreakAllNodeLinks(); } bool UK2Node_GenericToText::IsConnectionDisallowed(const UEdGraphPin* MyPin, const UEdGraphPin* OtherPin, FString& OutReason) const { UEdGraphPin* InputPin = FindPinChecked(UE::Blueprint::Private::PC_Value.Resolve(), EEdGraphPinDirection::EGPD_Input); if (InputPin == MyPin && MyPin->Direction == EGPD_Input) { const FName OtherPinCategory = OtherPin->PinType.PinCategory; bool bIsValidType = UE::Blueprint::Private::IsGenericNumericProperty(OtherPinCategory) || OtherPinCategory == UEdGraphSchema_K2::PC_String || OtherPinCategory == UEdGraphSchema_K2::PC_Name; if (!bIsValidType) { const UObject* PinSubCategoryObject = OtherPin->PinType.PinSubCategoryObject.Get(); const bool bIsScriptStruct = OtherPinCategory == UEdGraphSchema_K2::PC_Struct && UE::Blueprint::Private::GetToTextSupportedScriptStruct().Contains(Cast(PinSubCategoryObject)); bIsValidType = bIsScriptStruct; } if (!bIsValidType) { OutReason = LOCTEXT("Error_InvalidArgumentType", "To Text arguments may only be Byte, Integer, Int64, Float, Double, Text, String, Name, Boolean, Enum, and basic struct.").ToString(); return true; } } return Super::IsConnectionDisallowed(MyPin, OtherPin, OutReason); } void UK2Node_GenericToText::GetMenuActions(FBlueprintActionDatabaseRegistrar& ActionRegistrar) const { UClass* ActionKey = GetClass(); if (ActionRegistrar.IsOpenForRegistration(ActionKey)) { UBlueprintNodeSpawner* NodeSpawner = UBlueprintNodeSpawner::Create(GetClass()); ActionRegistrar.AddBlueprintAction(ActionKey, NodeSpawner); } } FText UK2Node_GenericToText::GetMenuCategory() const { return FEditorCategoryUtils::GetCommonCategory(FCommonEditorCategory::Text); } #undef LOCTEXT_NAMESPACE