// Copyright Epic Games, Inc. All Rights Reserved. #include "K2Node_PromotableOperator.h" #include "BlueprintEditorSettings.h" #include "BlueprintTypePromotion.h" #include "Containers/EnumAsByte.h" #include "Containers/Set.h" #include "Containers/UnrealString.h" #include "CoreTypes.h" #include "Delegates/Delegate.h" #include "EdGraph/EdGraph.h" #include "EdGraph/EdGraphNode.h" #include "EdGraph/EdGraphNodeUtils.h" #include "EdGraph/EdGraphPin.h" #include "EdGraph/EdGraphSchema.h" #include "EdGraphSchema_K2.h" #include "Engine/MemberReference.h" #include "EngineLogs.h" #include "Framework/Commands/UIAction.h" #include "HAL/PlatformCrt.h" #include "Internationalization/Internationalization.h" #include "K2Node.h" #include "Kismet/BlueprintTypeConversions.h" #include "Kismet2/BlueprintEditorUtils.h" #include "Kismet2/CompilerResultsLog.h" #include "Kismet2/WildcardNodeUtils.h" #include "KismetCompiler.h" #include "Logging/LogCategory.h" #include "Logging/LogMacros.h" #include "Misc/AssertionMacros.h" #include "ScopedTransaction.h" #include "Templates/SubclassOf.h" #include "Templates/UnrealTemplate.h" #include "Textures/SlateIcon.h" #include "ToolMenu.h" #include "ToolMenuDelegates.h" #include "ToolMenuSection.h" #include "Trace/Detail/Channel.h" #include "UObject/Class.h" #include "UObject/UnrealType.h" #include "UObject/WeakObjectPtr.h" #include "UObject/WeakObjectPtrTemplates.h" #include "EdGraphSchema_K2_Actions.h" #define LOCTEXT_NAMESPACE "PromotableOperatorNode" static TAutoConsoleVariable CVarAllowConversionOfComparisonOps( TEXT("BP.bAllowConversionOfComparisonOps"), true, TEXT("If true, then allow the user to convert between comparison operators on the UK2Node_PromotableOperator"), ECVF_Default); /////////////////////////////////////////////////////////// // Pin names for default construction static const FName InputPinA_Name = FName(TEXT("A")); static const FName InputPinB_Name = FName(TEXT("B")); static const FName TolerancePin_Name = FName(TEXT("ErrorTolerance")); static const int32 NumFunctionInputs = 2; /////////////////////////////////////////////////////////// // UK2Node_PromotableOperator UK2Node_PromotableOperator::UK2Node_PromotableOperator(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer) { UpdateOpName(); OrphanedPinSaveMode = ESaveOrphanPinMode::SaveAllButExec; NumAdditionalInputs = 0; } /////////////////////////////////////////////////////////// // UEdGraphNode interface namespace PromotableOpUtils { bool FindTolerancePinType(const UFunction* Func, FEdGraphPinType& OutPinType) { const UEdGraphSchema_K2* Schema = GetDefault(); // Check if there is a tolerance field that we need to add // If UFunction has a third input param int32 InputArgsFound = 0; for (TFieldIterator PropIt(Func); PropIt && (PropIt->PropertyFlags & CPF_Parm); ++PropIt) { const FProperty* Param = *PropIt; // We don't care about the return property, and we are looking for // any additional input param that isn't in the normal function input range if (Param->HasAnyPropertyFlags(CPF_ReturnParm) || (InputArgsFound++ < NumFunctionInputs)) { continue; } // Found the tolerance type! FEdGraphPinType ParamType; if (Schema->ConvertPropertyToPinType(Param, /* out */ ParamType)) { OutPinType = ParamType; return true; } } return false; } } void UK2Node_PromotableOperator::AllocateDefaultPins() { FWildcardNodeUtils::CreateWildcardPin(this, InputPinA_Name, EGPD_Input); FWildcardNodeUtils::CreateWildcardPin(this, InputPinB_Name, EGPD_Input); UEdGraphPin* OutPin = FWildcardNodeUtils::CreateWildcardPin(this, UEdGraphSchema_K2::PN_ReturnValue, EGPD_Output); const UFunction* Func = GetTargetFunction(); // For comparison functions we always want a bool output, so make it visually so if (Func && FTypePromotion::IsComparisonFunc(Func)) { OutPin->PinType.PinCategory = UEdGraphSchema_K2::PC_Boolean; // If we need a tolerance pin then we need to add it here, but not if we are in a wildcard state FEdGraphPinType ToleranceType; if (PromotableOpUtils::FindTolerancePinType(Func, ToleranceType)) { UEdGraphPin* TolerancePin = CreatePin(EGPD_Input, ToleranceType, TolerancePin_Name); } } // Update the op name so that if there is a blank wildcard node left on the graph we // can ensure that it is correct UpdateOpName(); ensureMsgf((OperationName != TEXT("NO_OP")), TEXT("Invalid operation name on Promotable Operator node!")); // Create any additional input pin. Their appropriate type is determined in ReallocatePinsDuringReconstruction // because we cannot get a promoted type with no links to the pin. for (int32 i = NumFunctionInputs; i < (NumAdditionalInputs + NumFunctionInputs); ++i) { AddInputPinImpl(i); } } void UK2Node_PromotableOperator::GetNodeContextMenuActions(UToolMenu* Menu, UGraphNodeContextMenuContext* Context) const { Super::GetNodeContextMenuActions(Menu, Context); static const FName PromotableOperatorNodeName = FName("PromotableOperator"); static const FText PromotableOperatorStr = LOCTEXT("PromotableOperatorNode", "Operator Node"); if (CVarAllowConversionOfComparisonOps.GetValueOnAnyThread() && CanConvertComparisonOperatorNodeType(Context->Node)) { static const FName ConvertComparisonOpName = FName("ConvertComparisonOp"); static const FText ConvertComparisonOpStr = LOCTEXT("ConvertComparisonOp", "Convert Operator"); FToolMenuSection& Section = Menu->AddSection(ConvertComparisonOpName, ConvertComparisonOpStr); for (const FName& PossibleConversionOpName : FTypePromotion::GetComparisonOpNames()) { // Don't display our current operator type if (PossibleConversionOpName == OperationName) { continue; } FFormatNamedArguments Args; Args.Add(TEXT("NewOpName"), FTypePromotion::GetUserFacingOperatorName(PossibleConversionOpName)); Args.Add(TEXT("CurrentOpName"), FTypePromotion::GetUserFacingOperatorName(OperationName)); const FText PinConversionName = FText::Format(LOCTEXT("ConvertOpName_Tooltip", "Convert to {NewOpName}"), Args); Section.AddMenuEntry( FName(PinConversionName.ToString()), PinConversionName, FText::Format(LOCTEXT("ConvertOperator_ToType_Tooltip", "Convert this node operation from '{CurrentOpName}' to '{NewOpName}'"), Args), FSlateIcon(), FUIAction( FExecuteAction::CreateStatic(&UK2Node_PromotableOperator::ConvertComparisonOperatorNode, const_cast(Context->Node.Get()), PossibleConversionOpName) ) ); } } // Add the option to remove a pin via the context menu if (CanRemovePin(Context->Pin)) { FToolMenuSection& Section = Menu->AddSection(PromotableOperatorNodeName, PromotableOperatorStr); Section.AddMenuEntry( "RemovePin", LOCTEXT("RemovePin", "Remove pin"), LOCTEXT("RemovePinTooltip", "Remove this input pin"), FSlateIcon(), FUIAction( FExecuteAction::CreateUObject(const_cast(this), &UK2Node_PromotableOperator::RemoveInputPin, const_cast(Context->Pin)) ) ); } else if (CanAddPin()) { FToolMenuSection& Section = Menu->AddSection(PromotableOperatorNodeName, PromotableOperatorStr); Section.AddMenuEntry( "AddPin", LOCTEXT("AddPin", "Add pin"), LOCTEXT("AddPinTooltip", "Add another input pin"), FSlateIcon(), FUIAction( FExecuteAction::CreateUObject(const_cast(this), &UK2Node_PromotableOperator::AddInputPin) ) ); } // Add the pin conversion sub menu if (CanConvertPinType(Context->Pin)) { static const FName ConvNodeName = FName("PromotableOperatorPinConvs"); static const FText ConvNodeStr = LOCTEXT("PromotableOperatorPinConvs", "Pin Conversions"); FToolMenuSection& ConversionSection = Menu->AddSection(ConvNodeName, ConvNodeStr); // Give the user an option to reset this node to wildcard if (!FWildcardNodeUtils::IsWildcardPin(Context->Pin)) { const FText ResetName = LOCTEXT("ResetFunction", "To Wildcard"); ConversionSection.AddMenuEntry( FName(ResetName.ToString()), ResetName, LOCTEXT("ResetToWildcardTooltip", "Break all connections and reset this node to a wildcard state."), FSlateIcon(), FUIAction( FExecuteAction::CreateUObject(const_cast(this), &UK2Node_PromotableOperator::ConvertPinType, const_cast(Context->Pin), FWildcardNodeUtils::GetDefaultWildcardPinType()) ) ); } CreateConversionMenu(ConversionSection, (UEdGraphPin*)Context->Pin); } } void UK2Node_PromotableOperator::CreateConversionMenu(FToolMenuSection& ConversionSection, UEdGraphPin* PinToConvert) const { check(PinToConvert); // Gather what pin types could possibly be used for a conversion with this operator TArray AvailableFunctions; FTypePromotion::GetAllFuncsForOp(OperationName, AvailableFunctions); TArray PossiblePromos; const UEdGraphSchema_K2* Schema = GetDefault(); // If this is a split pin, then we need to convert the parent pin, not the child. if (PinToConvert->ParentPin != nullptr) { PinToConvert = PinToConvert->ParentPin; } // If we have a pin that matches our current type, then we can use it to see if we can still get a valid function FEdGraphPinType OriginalContextType = PinToConvert->PinType; for (const UFunction* Func : AvailableFunctions) { for (TFieldIterator PropIt(Func); PropIt && (PropIt->PropertyFlags & CPF_Parm); ++PropIt) { const FProperty* Param = *PropIt; FEdGraphPinType ParamType; if (Schema->ConvertPropertyToPinType(Param, /* out */ ParamType)) { if (FWildcardNodeUtils::IsWildcardPin(PinToConvert) || FTypePromotion::IsValidPromotion(ParamType, PinToConvert->PinType) || FTypePromotion::IsValidPromotion(PinToConvert->PinType, ParamType)) { PossiblePromos.AddUnique(ParamType); } } } } // Don't display the conversion menu if there are no valid conversions if (AvailableFunctions.Num() == 0) { return; } // Add the options to the context menu for (const FEdGraphPinType& PinType : PossiblePromos) { FFormatNamedArguments Args; Args.Add(TEXT("NewPinType"), Schema->TypeToText(PinType)); Args.Add(TEXT("CurrentPinType"), Schema->TypeToText(OriginalContextType)); const FText PinConversionName = FText::Format(LOCTEXT("Convert_Pin_To_Type_Tooltip", "To {NewPinType}"), Args); ConversionSection.AddMenuEntry( FName(PinConversionName.ToString()), PinConversionName, FText::Format(LOCTEXT("ConvertPinTypeTooltip", "Convert this pin type from '{CurrentPinType}' to '{NewPinType}'"), Args), FSlateIcon(), FUIAction( FExecuteAction::CreateUObject(const_cast(this), &UK2Node_PromotableOperator::ConvertPinType, const_cast(PinToConvert), PinType) ) ); } } bool UK2Node_PromotableOperator::CanConvertPinType(const UEdGraphPin* Pin) const { // You can convert any pin except for output pins on comparison functions and the tolerance pin return Pin && !(Pin->Direction == EGPD_Output && FTypePromotion::IsComparisonFunc(GetTargetFunction())) && !IsTolerancePin(*Pin); } bool UK2Node_PromotableOperator::CanConvertComparisonOperatorNodeType(const UEdGraphNode* Node) { if (!Node) { return false; } if (const UK2Node_PromotableOperator* OpNode = Cast(Node)) { // Only allow conversions between simple comparison types. If we have a tolerance pin, don't allow conversion // because not all operators for those types have a tolerance. const UEdGraphPin* TolerancePin = OpNode->FindTolerancePin(); const bool bTolerancePinAcceptable = !TolerancePin || TolerancePin->bHidden; const UFunction* TargetFunc = OpNode->GetTargetFunction(); // If the target function is null then it's a wildcard node and we can convert it return (TargetFunc == nullptr || FTypePromotion::IsComparisonFunc(TargetFunc)) && bTolerancePinAcceptable; } return false; } void UK2Node_PromotableOperator::ConvertComparisonOperatorNode(UEdGraphNode* Node, const FName NewOpName) { UK2Node_PromotableOperator* OpNode = Cast(Node); // You can only convert to comparison operators, nothing else, because then we can be sure the number of pins to consider will be correct // when finding the best matching function. Other math ops may behave in undefined ways if we allowed it. if (!OpNode || !FTypePromotion::IsComparisonOpName(NewOpName)) { return; } TArray PinsToConsider; OpNode->GetPinsToConsider(PinsToConsider); // In the case of this node having no pins to consider (it is a wildcard, with no default values or connections) // we will just use the boolean output pin to ensure that we get a valid UFunction to use. if (PinsToConsider.IsEmpty()) { UEdGraphPin* OutPin = OpNode->GetOutputPin(); // Because we only allow this conversion to happen on comparison operators, we can be sure the output pin will be a // simple boolean output and that it is there ensure(OutPin && OutPin->PinType.PinCategory == UEdGraphSchema_K2::PC_Boolean); PinsToConsider.Add(OutPin); } // For nodes with connections we have to find the best function again that matches for them if (const UFunction* BestMatchingFunc = FTypePromotion::FindBestMatchingFunc(NewOpName, PinsToConsider)) { FScopedTransaction Transaction(LOCTEXT("PromotableOperatorComparisonOpConversion", "Convert operator node")); OpNode->Modify(); OpNode->OperationName = NewOpName; // Only allow this with comparison functions ensure(FTypePromotion::IsComparisonFunc(BestMatchingFunc)); UE_LOG(LogBlueprint, Verbose, TEXT("Converting node '%s' from '%s' to '%s'..."), *GetNameSafe(OpNode), *OpNode->OperationName.ToString(), *NewOpName.ToString()); OpNode->SetFromFunction(BestMatchingFunc); OpNode->ReconstructNode(); } else { UE_LOG(LogBlueprint, Error, TEXT("Failed to convert Convert node from '%s' to '%s'"), *OpNode->OperationName.ToString(), *NewOpName.ToString()); } } FText UK2Node_PromotableOperator::GetTooltipText() const { FFormatNamedArguments Args; Args.Add(TEXT("FunctionTooltip"), !HasAnyConnectionsOrDefaults() ? FTypePromotion::GetUserFacingOperatorName(OperationName) : Super::GetTooltipText()); return FText::Format(LOCTEXT("PromotableOpTooltipText", "{FunctionTooltip}\n\nTo go back to old math nodes, uncheck 'Enable Type Promotion' in the Blueprint Editor Settings. Both node types are supported."), Args); } void UK2Node_PromotableOperator::PinDefaultValueChanged(UEdGraphPin* Pin) { Super::PinDefaultValueChanged(Pin); if (bDefaultValueReentranceGuard) { return; } // Re-entrance Guard just in case this function gets called from any notify triggers in the schema // to prevent possible recursive calls from ResetPinToAutogeneratedDefaultValue when breaking // all links to this node TGuardValue ReentranceGuard(bDefaultValueReentranceGuard, true); // If this default value resets to the default one on the pin, and there are no other // connections or default values, then we should just reset the whole node to a wildcard if (!HasAnyConnectionsOrDefaults()) { ResetNodeToWildcard(); } } void UK2Node_PromotableOperator::NodeConnectionListChanged() { Super::NodeConnectionListChanged(); // This will handle the case of dragging off of this node, and connecting to a node via typing // in the context menu. Without updating in this case, our pins would be left as wildcards! if(HasAnyConnectionsOrDefaults()) { UpdateOpName(); UpdateFromBestMatchingFunction(); // Get correct default value boxes GetGraph()->NotifyNodeChanged(this); } } void UK2Node_PromotableOperator::PostPasteNode() { Super::PostPasteNode(); // If we have copied a node with additional pins then we need to make sure // they get reset to wildcard as well, otherwise their type will persist if (!HasAnyConnectionsOrDefaults()) { ResetNodeToWildcard(); } } /////////////////////////////////////////////////////////// // UK2Node interface void UK2Node_PromotableOperator::ExpandNode(FKismetCompilerContext& CompilerContext, UEdGraph* SourceGraph) { Super::ExpandNode(CompilerContext, SourceGraph); const bool bValidOpName = UpdateOpName(); if (!bValidOpName) { UE_LOG(LogBlueprint, Error, TEXT("Could not find matching operation name for this function!")); CompilerContext.MessageLog.Error(TEXT("Could not find matching operation on '@@'!"), this); return; } UEdGraphPin* OriginalOutputPin = GetOutputPin(); TArray OriginalInputPins = GetInputPins(); // Our operator function has been determined on pin connection change UFunction* OpFunction = GetTargetFunction(); if (!OpFunction) { UE_LOG(LogBlueprint, Error, TEXT("Could not find matching op function during expansion!")); CompilerContext.MessageLog.Error(TEXT("Could not find matching op function during expansion on '@@'!"), this); return; } const UEdGraphSchema_K2* Schema = CompilerContext.GetSchema(); /** Helper struct to gather the necessary pins we need to create redirections */ struct FIntermediateCastPinHelper { UEdGraphPin* InputA = nullptr; UEdGraphPin* InputB = nullptr; UEdGraphPin* OutputPin = nullptr; UEdGraphPin* SelfPin = nullptr; UEdGraphPin* TolerancePin = nullptr; explicit FIntermediateCastPinHelper(UK2Node_CallFunction* NewOperator) { check(NewOperator); SelfPin = NewOperator->FindPin(UEdGraphSchema_K2::PN_Self); TolerancePin = NewOperator->FindPin(TolerancePin_Name, EGPD_Input); // Find inputs and outputs for (UEdGraphPin* Pin : NewOperator->Pins) { if (Pin == SelfPin || Pin == TolerancePin) { continue; } if (Pin->Direction == EEdGraphPinDirection::EGPD_Input) { if (!InputA) { InputA = Pin; } else if (!InputB) { InputB = Pin; } } else if (Pin->Direction == EEdGraphPinDirection::EGPD_Output) { OutputPin = Pin; } } } ~FIntermediateCastPinHelper() = default; }; UK2Node_CallFunction* PrevIntermediateNode = nullptr; UEdGraphPin* PrevOutputPin = nullptr; UEdGraphPin* MyTolerancePin = FindTolerancePin(); // Create cast from original 2 inputs to the first intermediate node { UFunction* BestFunc = OpFunction; // Look for a best matching function if we possibly don't have one if(!BestFunc) { TArray PinsToConsider = { OriginalInputPins[0], OriginalInputPins[1], OriginalOutputPin }; if (UFunction* Func = FTypePromotion::FindBestMatchingFunc(OperationName, PinsToConsider)) { BestFunc = Func; } } PrevIntermediateNode = CreateIntermediateNode(this, BestFunc, CompilerContext, SourceGraph); FIntermediateCastPinHelper NewOpHelper(PrevIntermediateNode); PrevOutputPin = PrevIntermediateNode->FindPin(UEdGraphSchema_K2::PN_ReturnValue, EGPD_Output); const bool bPinASuccess = UK2Node_PromotableOperator::CreateIntermediateCast(this, CompilerContext, SourceGraph, OriginalInputPins[0], NewOpHelper.InputA); const bool bPinBSuccess = UK2Node_PromotableOperator::CreateIntermediateCast(this, CompilerContext, SourceGraph, OriginalInputPins[1], NewOpHelper.InputB); // Attempt to connect the tolerance pin if both nodes have one const bool bToleranceSuccess = MyTolerancePin && NewOpHelper.TolerancePin ? UK2Node_PromotableOperator::CreateIntermediateCast(this, CompilerContext, SourceGraph, MyTolerancePin, NewOpHelper.TolerancePin) : true; if (!bPinASuccess || !bPinBSuccess || !bToleranceSuccess) { CompilerContext.MessageLog.Error(TEXT("'@@' could not successfuly expand pins!"), PrevIntermediateNode); } } // Loop through all the additional inputs, create a new node of this function and connecting inputs as necessary for (int32 i = NumFunctionInputs; i < NumAdditionalInputs + NumFunctionInputs; ++i) { check(i > 0 && i < OriginalInputPins.Num()); FIntermediateCastPinHelper PrevNodeHelper(PrevIntermediateNode); // Find the best matching function that this intermediate node should use // so that we can avoid unnecessary conversion nodes and casts UFunction* BestMatchingFunc = OpFunction; { TArray PinsToConsider = { PrevNodeHelper.OutputPin, OriginalInputPins[i], OriginalOutputPin }; if (UFunction* Func = FTypePromotion::FindBestMatchingFunc(OperationName, PinsToConsider)) { BestMatchingFunc = Func; } } UK2Node_CallFunction* NewIntermediateNode = CreateIntermediateNode(PrevIntermediateNode, BestMatchingFunc, CompilerContext, SourceGraph); FIntermediateCastPinHelper NewOpHelper(NewIntermediateNode); // Connect the output pin of the previous intermediate node, to the input of the new one const bool bPinASuccess = CreateIntermediateCast(PrevIntermediateNode, CompilerContext, SourceGraph, NewOpHelper.InputA, PrevOutputPin); // Connect the original node's pin to the newly created intermediate node's B Pin const bool bPinBSuccess = CreateIntermediateCast(this, CompilerContext, SourceGraph, OriginalInputPins[i], NewOpHelper.InputB); // Make a connection to a tolerance pin if both nodes have one const bool bToleranceSuccess = MyTolerancePin && NewOpHelper.TolerancePin ? UK2Node_PromotableOperator::CreateIntermediateCast(this, CompilerContext, SourceGraph, MyTolerancePin, NewOpHelper.TolerancePin) : true; if (!bPinASuccess || !bPinBSuccess || !bToleranceSuccess) { CompilerContext.MessageLog.Error(TEXT("'@@' could not successfuly expand additional pins!"), PrevIntermediateNode); } // Track what the previous node is so that we can connect it's output appropriately PrevOutputPin = NewOpHelper.OutputPin; PrevIntermediateNode = NewIntermediateNode; } // Make the final output connection that we need if (OriginalOutputPin && PrevOutputPin) { CompilerContext.MovePinLinksToIntermediate(*OriginalOutputPin, *PrevOutputPin); // If there is no link to the output pin then the connection response called for a conversion node, // but one was not available. This can occur when there is a connection to the output pin that // is smaller than an input and cannot be casted. We throw a compiler error instead of auto-breaking // pins because that can be confusing to the user. if (PrevOutputPin->LinkedTo.Num() == 0) { CompilerContext.MessageLog.Error( *FText::Format(LOCTEXT("FailedOutputConnection_ErrorFmt", "Output pin type '{1}' is not compatible with input type of '{0}' on '@@'"), Schema->TypeToText(PrevOutputPin->PinType), Schema->TypeToText(OriginalOutputPin->PinType)).ToString(), PrevIntermediateNode ); } } } void UK2Node_PromotableOperator::NotifyPinConnectionListChanged(UEdGraphPin* ChangedPin) { Super::NotifyPinConnectionListChanged(ChangedPin); EvaluatePinsFromChange(ChangedPin); } void UK2Node_PromotableOperator::PostReconstructNode() { Super::PostReconstructNode(); // We only need to set the function if we have connections, otherwise we should stick in a wildcard state if (HasAnyConnectionsOrDefaults()) { if (UEdGraphPin* TolerancePin = FindTolerancePin()) { FEdGraphPinType ToleranceType; TolerancePin->bHidden = !PromotableOpUtils::FindTolerancePinType(GetTargetFunction(), ToleranceType); } // Allocate default pins will have been called before this, which means we are reset to wildcard state // We need to Update the pins to be the proper function again UpdatePinsFromFunction(GetTargetFunction()); for (UEdGraphPin* AddPin : Pins) { if (ensure(AddPin) && IsAdditionalPin(*AddPin) && AddPin->LinkedTo.Num() > 0) { FEdGraphPinType TypeToSet = FTypePromotion::GetPromotedType(AddPin->LinkedTo); AddPin->PinType = TypeToSet; } } } else if (UEdGraphPin* TolerancePin = FindTolerancePin()) { TolerancePin->bHidden = true; } } bool UK2Node_PromotableOperator::IsConnectionDisallowed(const UEdGraphPin* MyPin, const UEdGraphPin* OtherPin, FString& OutReason) const { check(MyPin && OtherPin); const UEdGraphSchema_K2* K2Schema = GetDefault(); // Container types cannot be promotable operators as they have their own special case wildcard propagation if (OtherPin->PinType.IsContainer()) { OutReason = LOCTEXT("NoExecPinsAllowed", "Promotable Operator nodes cannot have containers or references.").ToString(); return true; } // The output pin on comparison operators is always a boolean, and cannot have its type changed // so we need to just treat it normally as a regular K2CallFunction node would else if (MyPin == GetOutputPin() && FTypePromotion::IsComparisonFunc(GetTargetFunction()) && OtherPin->PinType.PinCategory != UEdGraphSchema_K2::PC_Boolean) { return Super::IsConnectionDisallowed(MyPin, OtherPin, OutReason); } // If the pins are the same type then there is no reason to check for a promotion else if (MyPin->PinType == OtherPin->PinType) { return Super::IsConnectionDisallowed(MyPin, OtherPin, OutReason); } // Enums need to be casted to a byte manually before we can do math with them, like in C++ else if (!FWildcardNodeUtils::IsWildcardPin(MyPin) && MyPin->Direction == EGPD_Input && OtherPin->PinType.PinCategory == UEdGraphSchema_K2::PC_Byte && OtherPin->PinType.PinSubCategoryObject != nullptr) { FFormatNamedArguments Args; Args.Add(TEXT("OtherPinType"), K2Schema->TypeToText(OtherPin->PinType)); OutReason = FText::Format(LOCTEXT("NoCompatibleEnumConv", "'{OtherPinType}' must be converted to a numeric type before being connected"), Args).ToString(); return true; } else if (FWildcardNodeUtils::IsWildcardPin(MyPin) && !FWildcardNodeUtils::IsWildcardPin(OtherPin)) { TArray PinsToConsider; PinsToConsider.Add(const_cast(OtherPin)); if (!FTypePromotion::FindBestMatchingFunc(OperationName, PinsToConsider)) { FFormatNamedArguments Args; Args.Add(TEXT("OtherPinType"), K2Schema->TypeToText(OtherPin->PinType)); Args.Add(TEXT("OpName"), FText::FromName(OperationName)); const TSet& DenyList = GetDefault()->TypePromotionPinDenyList; if (DenyList.Contains(OtherPin->PinType.PinCategory)) { OutReason = FText::Format(LOCTEXT("NoCompatibleStructConv_Denied", "No matching '{OpName}' function for '{OtherPinType}'. This type is listed as denied by TypePromotionPinDenyList in the blueprint editor settings."), Args).ToString(); } else { OutReason = FText::Format(LOCTEXT("NoCompatibleStructConv", "No matching '{OpName}' function for '{OtherPinType}'"), Args).ToString(); } return true; } } const bool bHasStructPin = MyPin->PinType.PinCategory == UEdGraphSchema_K2::PC_Struct || OtherPin->PinType.PinCategory == UEdGraphSchema_K2::PC_Struct; // If the other pin can be promoted to my pin type, than allow the connection if (FTypePromotion::IsValidPromotion(OtherPin->PinType, MyPin->PinType)) { if (bHasStructPin) { // Compare the directions const UEdGraphPin* InputPin = nullptr; const UEdGraphPin* OutputPin = nullptr; if (!K2Schema->CategorizePinsByDirection(MyPin, OtherPin, /*out*/ InputPin, /*out*/ OutputPin)) { OutReason = LOCTEXT("DirectionsIncompatible", "Pin directions are not compatible!").ToString(); return true; } if (!FTypePromotion::HasStructConversion(InputPin, OutputPin)) { FFormatNamedArguments Args; Args.Add(TEXT("MyPinType"), K2Schema->TypeToText(MyPin->PinType)); Args.Add(TEXT("OtherPinType"), K2Schema->TypeToText(OtherPin->PinType)); OutReason = FText::Format(LOCTEXT("NoCompatibleOperatorConv", "No compatible operator functions between '{MyPinType}' and '{OtherPinType}'"), Args).ToString(); return true; } } return false; } return Super::IsConnectionDisallowed(MyPin, OtherPin, OutReason); } void UK2Node_PromotableOperator::ReallocatePinsDuringReconstruction(TArray& OldPins) { Super::ReallocatePinsDuringReconstruction(OldPins); // We need to fix up any additional pins that may have been created as a wildcard pin int32 AdditionalPinsFixed = 0; // Additional Pin creation here? Check for orphan pins here and see if we can re-create them for (UEdGraphPin* OldPin : OldPins) { if (ensure(OldPin)) { if (IsAdditionalPin(*OldPin)) { if (UEdGraphPin* AddPin = GetAdditionalPin(AdditionalPinsFixed + NumFunctionInputs)) { AddPin->PinType = OldPin->PinType; AddPin->DefaultValue = OldPin->DefaultValue; ++AdditionalPinsFixed; } } // Copy the default value and pin type for pins without any links to preseve it UEdGraphPin* CurrentPin = FindPin(OldPin->GetFName(), OldPin->Direction); if (CurrentPin && OldPin->LinkedTo.Num() == 0 && !OldPin->DoesDefaultValueMatchAutogenerated()) { CurrentPin->PinType = OldPin->PinType; CurrentPin->DefaultValue = OldPin->DefaultValue; } } } } void UK2Node_PromotableOperator::AutowireNewNode(UEdGraphPin* ChangedPin) { Super::AutowireNewNode(ChangedPin); EvaluatePinsFromChange(ChangedPin); } void UK2Node_PromotableOperator::GetPinHoverText(const UEdGraphPin& Pin, FString& HoverTextOut) const { Super::GetPinHoverText(Pin, HoverTextOut); static const FText RightClickToConv = LOCTEXT("RightClickToConvTooltip", "\n\nRight click this pin to convert its type."); HoverTextOut += RightClickToConv.ToString(); } /////////////////////////////////////////////////////////// // IK2Node_AddPinInterface void UK2Node_PromotableOperator::AddInputPin() { if (CanAddPin()) { FScopedTransaction Transaction(LOCTEXT("AddPinPromotableOperator", "AddPin")); Modify(); AddInputPinImpl(NumFunctionInputs + NumAdditionalInputs); ++NumAdditionalInputs; FBlueprintEditorUtils::MarkBlueprintAsStructurallyModified(GetBlueprint()); } } bool UK2Node_PromotableOperator::CanAddPin() const { return ((NumAdditionalInputs + NumFunctionInputs) < GetMaxInputPinsNum()) && !FTypePromotion::IsComparisonFunc(GetTargetFunction()); } bool UK2Node_PromotableOperator::CanRemovePin(const UEdGraphPin* Pin) const { return ( Pin && Pin->ParentPin == nullptr && NumAdditionalInputs > 0 && INDEX_NONE != Pins.IndexOfByKey(Pin) && Pin->Direction == EEdGraphPinDirection::EGPD_Input ); } void UK2Node_PromotableOperator::RemoveInputPin(UEdGraphPin* Pin) { if (CanRemovePin(Pin)) { FScopedTransaction Transaction(LOCTEXT("RemovePinPromotableOperator", "RemovePin")); Modify(); if (RemovePin(Pin)) { --NumAdditionalInputs; int32 NameIndex = 0; const UEdGraphPin* SelfPin = FindPin(UEdGraphSchema_K2::PN_Self); for (int32 PinIndex = 0; PinIndex < Pins.Num(); ++PinIndex) { UEdGraphPin* LocalPin = Pins[PinIndex]; if (LocalPin && (LocalPin->Direction != EGPD_Output) && (LocalPin != SelfPin) && LocalPin->ParentPin == nullptr) { const FName PinName = GetNameForAdditionalPin(NameIndex); if (PinName != LocalPin->PinName) { LocalPin->Modify(); LocalPin->PinName = PinName; } NameIndex++; } } FBlueprintEditorUtils::MarkBlueprintAsStructurallyModified(GetBlueprint()); } } // If the pin that was removed makes this node empty then we can just reset it // and have no need to reevaluate any pins if (!HasAnyConnectionsOrDefaults()) { ResetNodeToWildcard(); } } UEdGraphPin* UK2Node_PromotableOperator::GetAdditionalPin(int32 PinIndex) const { const FName PinToFind = GetNameForAdditionalPin(PinIndex); for (UEdGraphPin* Pin : Pins) { if (Pin && Pin->PinName == PinToFind) { return Pin; } } return nullptr; } UEdGraphPin* UK2Node_PromotableOperator::FindTolerancePin() const { return FindPin(TolerancePin_Name, EGPD_Input); } /////////////////////////////////////////////////////////// // UK2Node_CallFunction interface void UK2Node_PromotableOperator::SetFromFunction(const UFunction* Function) { if(Function) { OperationName = FTypePromotion::GetOpNameFromFunction(Function); } // During compilation of an Anim BP, if this node gets pruned then the outer will be the /Engine/Transient/ // package which will result in this node not having a valid SelfContext, so there is no need // to set any further information based on this function as it will not be used. if(GetBlueprintClassFromNode()) { Super::SetFromFunction(Function); } } /////////////////////////////////////////////////////////// // UK2Node_PromotableOperator bool UK2Node_PromotableOperator::IsTolerancePin(const UEdGraphPin& Pin) const { return Pin.PinName == TolerancePin_Name && Pin.Direction == EGPD_Input; } UEdGraphPin* UK2Node_PromotableOperator::AddInputPinImpl(int32 PinIndex) { const FName NewPinName = GetNameForAdditionalPin(PinIndex); UEdGraphPin* NewPin = FWildcardNodeUtils::CreateWildcardPin(this, NewPinName, EGPD_Input); check(NewPin); // Determine a default type for this pin if we have other input connections const TArray InputPins = GetInputPins(/* bIncludeLinks = */ true); check(InputPins.Num()); FEdGraphPinType PromotedType = FTypePromotion::GetPromotedType(InputPins); NewPin->PinType = PromotedType; return NewPin; } bool UK2Node_PromotableOperator::IsAdditionalPin(const UEdGraphPin& Pin) const { // Quickly check if this input pin is one of the two default input pins bool bIsDefaultPinA = (Pin.PinName == InputPinA_Name) || (Pin.ParentPin && (Pin.ParentPin->PinName == InputPinA_Name)); bool bIsDefaultPinB = (Pin.PinName == InputPinB_Name) || (Pin.ParentPin && (Pin.ParentPin->PinName == InputPinB_Name)); return (Pin.Direction == EGPD_Input) && !bIsDefaultPinA && !bIsDefaultPinB && !IsTolerancePin(Pin); } bool UK2Node_PromotableOperator::HasAnyConnectionsOrDefaults() const { if(FTypePromotion::IsComparisonOpName(OperationName)) { return HasDeterminingComparisonTypes(); } for (UEdGraphPin* Pin : Pins) { if (Pin->LinkedTo.Num() > 0 || !Pin->DoesDefaultValueMatchAutogenerated()) { return true; } } return false; } bool UK2Node_PromotableOperator::HasDeterminingComparisonTypes() const { if(!FTypePromotion::IsComparisonOpName(OperationName)) { return false; } // For comparison ops, we only want to check the input pins as the output // will always be a bool for (const UEdGraphPin* Pin : Pins) { if (Pin && Pin->Direction == EGPD_Input && (Pin->LinkedTo.Num() > 0 || !Pin->DoesDefaultValueMatchAutogenerated())) { return true; } } return false; } void UK2Node_PromotableOperator::EvaluatePinsFromChange(UEdGraphPin* ChangedPin, const bool bFromConversion /* = false*/) { UpdateOpName(); if (!ensureMsgf(ChangedPin, TEXT("UK2Node_PromotableOperator::EvaluatePinsFromChange failed to evaluate on a null pin!"))) { return; } // True if the pin that has changed now has zero connections const bool bWasAFullDisconnect = (ChangedPin->LinkedTo.Num() == 0) && !HasAnyConnectionsOrDefaults(); const bool bIsComparisonOp = FTypePromotion::IsComparisonOpName(OperationName); // If we have been totally disconnected and don't have any non-default inputs, // than we just reset the node to be a regular wildcard if (!bFromConversion && (bWasAFullDisconnect || (bIsComparisonOp && !HasDeterminingComparisonTypes()))) { ResetNodeToWildcard(); return; } // If the pin that was connected is linked to a wildcard pin, then we should make it a wildcard // and do nothing else. else if (ChangedPin->GetOwningNodeUnchecked() == this && FWildcardNodeUtils::IsLinkedToWildcard(ChangedPin)) { return; } // Changing the tolerance pin doesn't effect the rest of the function signature, so don't attempt to update the func else if (IsTolerancePin(*ChangedPin)) { return; } // If we have connected the output of our comparison operator (which is always a bool) then // there is no need to update the other pins else if (bIsComparisonOp && ChangedPin == GetOutputPin()) { return; } // If the newest connection to this this pin is a different type, // then we need to break all other type connections that are not the same if(ChangedPin->LinkedTo.Num() > 1) { const FEdGraphPinType& MostRecentConnection = ChangedPin->LinkedTo.Last()->PinType; for(int32 i = ChangedPin->LinkedTo.Num() - 1; i >= 0; --i) { UEdGraphPin* Link = ChangedPin->LinkedTo[i]; const bool bIsRealNumberConnection = (Link->PinType.PinCategory == UEdGraphSchema_K2::PC_Real) && (MostRecentConnection.PinCategory == UEdGraphSchema_K2::PC_Real); // Real numbers are an exception to the normal link breaking rules. // Even if the subcategories don't match, they're still valid connections // since we implicitly cast between float and double types. if (bIsRealNumberConnection) { continue; } if( Link->PinType.PinCategory != MostRecentConnection.PinCategory || Link->PinType.PinSubCategory != MostRecentConnection.PinSubCategory || Link->PinType.PinSubCategoryObject != MostRecentConnection.PinSubCategoryObject ) { ChangedPin->BreakLinkTo(Link); } } } // Gather all pins and their links so we can determine the highest type TArray PinsToConsider; GetPinsToConsider(PinsToConsider); if (bFromConversion) { PinsToConsider.AddUnique(ChangedPin); } const UFunction* BestMatchingFunc = FTypePromotion::FindBestMatchingFunc(OperationName, PinsToConsider); // Store these other function options for later so that the user can convert to them later UpdatePinsFromFunction(BestMatchingFunc, ChangedPin, bFromConversion); } bool UK2Node_PromotableOperator::UpdateOpName() { // If the function is null then return false, because we did not successfully update it. // This could be possible during node reconstruction/refresh, and we don't want to set the // op name to "Empty" incorrectly. if (const UFunction* Func = GetTargetFunction()) { OperationName = FTypePromotion::GetOpNameFromFunction(Func); return true; } return false; } UK2Node_CallFunction* UK2Node_PromotableOperator::CreateIntermediateNode(UK2Node_CallFunction* PreviousNode, const UFunction* const OpFunction, FKismetCompilerContext& CompilerContext, UEdGraph* SourceGraph) { // Spawn an intermediate UK2Node_CallFunction node of the function type we need UK2Node_CallFunction* NewOperator = SourceGraph->CreateIntermediateNode(); NewOperator->SetFromFunction(OpFunction); NewOperator->AllocateDefaultPins(); // Move this node next to the thing it was linked to NewOperator->NodePosY = PreviousNode->NodePosY + 50; NewOperator->NodePosX = PreviousNode->NodePosX + 8; CompilerContext.MessageLog.NotifyIntermediateObjectCreation(NewOperator, this); return NewOperator; } bool UK2Node_PromotableOperator::CreateIntermediateCast(UK2Node_CallFunction* SourceNode, FKismetCompilerContext& CompilerContext, UEdGraph* SourceGraph, UEdGraphPin* InputPin, UEdGraphPin* OutputPin) { check(InputPin && OutputPin); const UEdGraphSchema_K2* Schema = CompilerContext.GetSchema(); // If the pin types are the same, than no casts are needed and we can just connect. // This includes real number types that may have a different subcategory, which we implicitly convert. const bool bPinTypesMatch = (InputPin->PinType == OutputPin->PinType) || ((InputPin->PinType.PinCategory == UEdGraphSchema_K2::PC_Real) && (OutputPin->PinType.PinCategory == UEdGraphSchema_K2::PC_Real)); if (bPinTypesMatch) { // If SourceNode is 'this' then we need to move the pin links instead of just // creating the connection because the output is not another new node, but // just the intermediate expansion node. if (SourceNode == this) { return !CompilerContext.MovePinLinksToIntermediate(*InputPin, *OutputPin).IsFatal(); } else { return Schema->TryCreateConnection(InputPin, OutputPin); } } UK2Node* TemplateConversionNode = nullptr; TSubclassOf ConversionNodeClass; if (TOptional AutoCastResults = Schema->SearchForAutocastFunction(InputPin->PinType, OutputPin->PinType)) { // Create a new call function node for the casting operator UK2Node_CallFunction* TemplateNode = SourceGraph->CreateIntermediateNode(); TemplateNode->FunctionReference.SetExternalMember(AutoCastResults->TargetFunction, AutoCastResults->FunctionOwner); TemplateConversionNode = TemplateNode; TemplateNode->AllocateDefaultPins(); CompilerContext.MessageLog.NotifyIntermediateObjectCreation(TemplateNode, this); } else if (TOptional ConversionNodeResults = Schema->FindSpecializedConversionNode(InputPin->PinType, *OutputPin, true)) { FVector2D AverageLocation = UEdGraphSchema_K2::CalculateAveragePositionBetweenNodes(InputPin, OutputPin); TemplateConversionNode = FEdGraphSchemaAction_K2NewNode::SpawnNodeFromTemplate(SourceGraph, ConversionNodeResults->TargetNode, AverageLocation); CompilerContext.MessageLog.NotifyIntermediateObjectCreation(TemplateConversionNode, this); } bool bInputSuccessful = false; bool bOutputSuccessful = false; if (TemplateConversionNode) { UEdGraphPin* ConversionInput = nullptr; UEdGraphPin* ConversionOutput = TemplateConversionNode->FindPin(UEdGraphSchema_K2::PN_ReturnValue, EGPD_Output); for (UEdGraphPin* ConvPin : TemplateConversionNode->Pins) { if (ConvPin) { if (!ConversionInput && ConvPin->Direction == EGPD_Input && ConvPin->PinName != UEdGraphSchema_K2::PSC_Self) { ConversionInput = ConvPin; } else if (!ConversionOutput && ConvPin->Direction == EGPD_Output) { ConversionOutput = ConvPin; } } } ensure(ConversionInput && ConversionOutput); // Connect my input to the conversion node directly if we have links, otherwise we need to move the intermediate version of it if (InputPin->LinkedTo.Num() > 0) { bInputSuccessful = Schema->TryCreateConnection(InputPin->LinkedTo[0], ConversionInput); } else if (InputPin && ConversionInput) { bInputSuccessful = !CompilerContext.MovePinLinksToIntermediate(*InputPin, *ConversionInput).IsFatal(); } // Connect conversion node output to the input of the new operator bOutputSuccessful = Schema->TryCreateConnection(ConversionOutput, OutputPin); // Move this node next to the thing it was linked to TemplateConversionNode->NodePosY = SourceNode->NodePosY; TemplateConversionNode->NodePosX = SourceNode->NodePosX + 4; } else { CompilerContext.MessageLog.Error(*FText::Format(LOCTEXT("NoValidPromotion", "Cannot find appropriate promotion from '{0}' to '{1}' on '@@'"), Schema->TypeToText(InputPin->PinType), Schema->TypeToText(OutputPin->PinType)).ToString(), SourceNode ); } return bInputSuccessful && bOutputSuccessful; } void UK2Node_PromotableOperator::ResetNodeToWildcard() { RecombineAllSplitPins(); // Reset type to wildcard const FEdGraphPinType& WildType = FWildcardNodeUtils::GetDefaultWildcardPinType(); const UEdGraphSchema* Schema = GetSchema(); for (UEdGraphPin* Pin : Pins) { // Ensure this pin is not a split pin if (Pin && Pin->ParentPin == nullptr) { Pin->PinType = WildType; Schema->ResetPinToAutogeneratedDefaultValue(Pin); } } const UFunction* Func = GetTargetFunction(); if(Func && FTypePromotion::IsComparisonFunc(Func)) { // Set output pins to have a bool output flag by default if (UEdGraphPin* OutPin = GetOutputPin()) { OutPin->PinType.PinCategory = UEdGraphSchema_K2::PC_Boolean; } // If we have a tolerance pin, and we reset to wildcard then we should hide it if (UEdGraphPin* TolerancePin = FindTolerancePin()) { TolerancePin->bHidden = true; } } GetGraph()->NotifyNodeChanged(this); } void UK2Node_PromotableOperator::RecombineAllSplitPins() { const UEdGraphSchema_K2* K2Schema = GetDefault(); // Gather what pins need to be recombined from a split pin for (int32 Index = 0; Index < Pins.Num(); ++Index) { if (Pins[Index] && Pins[Index]->SubPins.Num() > 0) { K2Schema->RecombinePin(Pins[Index]); } } } void UK2Node_PromotableOperator::UpdateFromBestMatchingFunction() { // Gather all pins and their links so we can determine the highest type that the user could want TArray PinsToConsider; GetPinsToConsider(PinsToConsider); // We need to update the pins from our function if have a new connection if (const UFunction* BestMatchingFunc = FTypePromotion::FindBestMatchingFunc(OperationName, PinsToConsider)) { UpdatePinsFromFunction(BestMatchingFunc); } } TArray UK2Node_PromotableOperator::GetInputPins(bool bIncludeLinks /** = false */) const { TArray InputPins; for (UEdGraphPin* Pin : Pins) { // Exclude split pins from this if (Pin && Pin->Direction == EEdGraphPinDirection::EGPD_Input && Pin->ParentPin == nullptr) { InputPins.Add(Pin); if (bIncludeLinks) { for (UEdGraphPin* Link : Pin->LinkedTo) { InputPins.Emplace(Link); } } } } return InputPins; } void UK2Node_PromotableOperator::GetPinsToConsider(TArray& OutArray) const { const bool bIsComparisonOp = FTypePromotion::IsComparisonOpName(OperationName); for (UEdGraphPin* Pin : Pins) { if (ensure(Pin)) { // Tolerance pins don't factor into what types we should be matching // for the function, so we should not consider them if (IsTolerancePin(*Pin)) { continue; } // If this is a comparison operator then we don't need to consider the boolean output // pin because every comparison function will have a boolean output! if (bIsComparisonOp && Pin->Direction == EGPD_Output) { continue; } // If this pin has links, then use those instead of the actual pin because we could be process of changing it // which means that it would still have it's old pin type, and could be inaccurate if (Pin->LinkedTo.Num() > 0) { // If this is from a split pin, then we care about the parent type if (Pin->ParentPin != nullptr) { OutArray.Add(Pin->ParentPin); } // as well as all the links to it for (UEdGraphPin* Link : Pin->LinkedTo) { OutArray.Emplace(Link); } } else if (!FWildcardNodeUtils::IsWildcardPin(Pin)) { // If this is from a split pin, then we care about the type of the parent, not this pin if (Pin->ParentPin != nullptr) { OutArray.Add(Pin->ParentPin); } else { OutArray.Add(Pin); } } } } } void UK2Node_PromotableOperator::UpdatePinsFromFunction(const UFunction* Function, UEdGraphPin* ChangedPin /* = nullptr */, bool bIsFromConversion /*= false*/) { if (!Function) { return; } const UEdGraphSchema_K2* Schema = GetDefault(); // Gather the pin types of the properties on the function we want to convert to FEdGraphPinType FunctionReturnType; FEdGraphPinType HighestFuncInputType; FEdGraphPinType ToleranceType; TArray FunctionInputTypes; { for (TFieldIterator PropIt(Function); PropIt && (PropIt->PropertyFlags & CPF_Parm); ++PropIt) { FProperty* Param = *PropIt; FEdGraphPinType ParamType; if (Schema->ConvertPropertyToPinType(Param, /* out */ ParamType)) { if (Param->HasAnyPropertyFlags(CPF_ReturnParm)) { FunctionReturnType = ParamType; } else { // Track the highest input pin type that we have if (FTypePromotion::GetHigherType(HighestFuncInputType, ParamType) == FTypePromotion::ETypeComparisonResult::TypeBHigher) { HighestFuncInputType = ParamType; } FunctionInputTypes.Add(ParamType); } } } } auto ConformPinLambda = [&ChangedPin, bIsFromConversion](const FEdGraphPinType& FunctionPinType, UEdGraphPin* NodePin) { using namespace UE::Kismet::BlueprintTypeConversions; // If the pin types are already equal, then we don't have to do any work // If this is linked to wildcard pins, then we can just ignore it and handle it on expansion if (!NodePin || FWildcardNodeUtils::IsLinkedToWildcard(NodePin)) { return; } // Pins that are underdoing a conversion will have already had their types changed if (NodePin == ChangedPin && bIsFromConversion) { return; } // By default, conform to the type of the function param FEdGraphPinType ConformingType = FunctionPinType; const FEdGraphPinType HighestLinkedType = NodePin->LinkedTo.Num() > 0 ? FTypePromotion::GetPromotedType(NodePin->LinkedTo) : NodePin->PinType; const UScriptStruct* NodePinStruct = Cast(HighestLinkedType.PinSubCategoryObject); const UScriptStruct* FunctionPinStruct = Cast(FunctionPinType.PinSubCategoryObject); const bool bHasImplicitConversionFunction = FStructConversionTable::Get().GetConversionFunction(NodePinStruct, FunctionPinStruct).IsSet(); const bool bDifferingStructs = HighestLinkedType.PinCategory == UEdGraphSchema_K2::PC_Struct && FunctionPinType.PinCategory == UEdGraphSchema_K2::PC_Struct && HighestLinkedType.PinSubCategoryObject != FunctionPinType.PinSubCategoryObject && !bHasImplicitConversionFunction; const bool bHasDeterminingType = NodePin->LinkedTo.Num() > 0 || !NodePin->DoesDefaultValueMatchAutogenerated(); // If the highest type is the same as the function type, just continue on with life if (bHasDeterminingType && (HighestLinkedType.PinCategory != FunctionPinType.PinCategory || bDifferingStructs)) { NodePin->PinType = FunctionPinType; const bool bValidPromo = FTypePromotion::IsValidPromotion(HighestLinkedType, FunctionPinType) || (NodePin->LinkedTo.Num() > 0 && FTypePromotion::HasStructConversion(NodePin, NodePin->LinkedTo[0])); // If the links cannot be promoted to the function type, then we need to break them // We don't want to break the pin if it is the one that the user has dragged on to though, // because that would result in the node breaking connection as soon as the user lets go if ((NodePin != ChangedPin) && !FWildcardNodeUtils::IsWildcardPin(NodePin) && !bValidPromo) { NodePin->BreakAllPinLinks(); } else { ConformingType = HighestLinkedType; } } // Conform the pin type appropriately NodePin->PinType = ConformingType; }; // Check if we need to add a tolerance pin or not with this new function if (FTypePromotion::IsComparisonFunc(Function)) { UEdGraphPin* ExistingTolPin = FindTolerancePin(); const bool bHasTolerancePin = PromotableOpUtils::FindTolerancePinType(Function, ToleranceType); if (!ExistingTolPin) { ExistingTolPin = CreatePin(EGPD_Input, ToleranceType, TolerancePin_Name); } // Set the tolerance pin to visible if it is currently on the node ExistingTolPin->bHidden = !bHasTolerancePin; } int32 CurPinIndex = 0; for (UEdGraphPin* CurPin : Pins) { if (ensure(CurPin)) { // We don't want to try and conform split pin, because we will already have conformed the parent pin if (CurPin->ParentPin != nullptr) { continue; } if (IsAdditionalPin(*CurPin)) { // Conform to the highest input pin on the function ConformPinLambda(HighestFuncInputType, CurPin); } else if (CurPin->Direction == EGPD_Output) { // Match to the output pin ConformPinLambda(FunctionReturnType, CurPin); } // Creation and conformation of the tolerance pin is handled before all others to // ensure that connections are not broken accidentally else if (IsTolerancePin(*CurPin)) { ConformPinLambda(ToleranceType, CurPin); } else { // Match to the appropriate function input type ConformPinLambda(FunctionInputTypes[CurPinIndex], CurPin); ++CurPinIndex; } } } // Update the function reference and the FUNC_BlueprintPure/FUNC_Const appropriately SetFromFunction(Function); // Invalidate the tooltips CachedTooltip.MarkDirty(); // We need to notify the graph that the node has changed to get // the correct default value text boxes on the node GetGraph()->NotifyNodeChanged(this); } UEdGraphPin* UK2Node_PromotableOperator::GetOutputPin() const { for (UEdGraphPin* Pin : Pins) { if (Pin && Pin->Direction == EGPD_Output) { return Pin; } } return nullptr; } void UK2Node_PromotableOperator::ConvertPinType(UEdGraphPin* PinToChange, const FEdGraphPinType NewPinType) { // No work to be done here! if (!PinToChange || PinToChange->PinType == NewPinType) { return; } FScopedTransaction Transaction(LOCTEXT("PromotableOperatorPinConvert", "Convert pin type")); Modify(); if(NewPinType.PinCategory == UEdGraphSchema_K2::PC_Wildcard) { const bool bIsComparison = FTypePromotion::IsComparisonFunc(GetTargetFunction()); // Break all input connections, but only break an output if it is not a comparison // because the bool pin type will not change. for(UEdGraphPin* Pin : Pins) { if(Pin->Direction == EGPD_Input || !bIsComparison) { Pin->BreakAllPinLinks(); } } ResetNodeToWildcard(); return; } // Break any pin links to this node because the type will be different PinToChange->BreakAllPinLinks(); // Recombine any split pins that this type has before changing its type const UEdGraphSchema_K2* Schema = GetDefault(); if (PinToChange->SubPins.Num() > 0) { Schema->RecombinePin(PinToChange); } PinToChange->PinType = NewPinType; EvaluatePinsFromChange(PinToChange, true); InvalidatePinTooltips(); // Reset the default value on pins that have been converted Schema->ResetPinToAutogeneratedDefaultValue(PinToChange, false); } #undef LOCTEXT_NAMESPACE // "PromotableOperatorNode"