// Copyright Epic Games, Inc. All Rights Reserved. #include "K2Node_Tunnel.h" #include "Containers/EnumAsByte.h" #include "EdGraph/EdGraph.h" #include "EdGraph/EdGraphPin.h" #include "EdGraphSchema_K2.h" #include "Engine/Blueprint.h" #include "HAL/IConsoleManager.h" #include "Internationalization/Internationalization.h" #include "K2Node_Composite.h" #include "K2Node_MacroInstance.h" #include "Kismet2/BlueprintEditorUtils.h" #include "Kismet2/WildcardNodeUtils.h" #include "Math/UnrealMathSSE.h" #include "Templates/Casts.h" #include "Templates/Function.h" #include "UObject/WeakObjectPtr.h" #include "UObject/WeakObjectPtrTemplates.h" class FArchive; class FCompilerResultsLog; class UObject; #define LOCTEXT_NAMESPACE "K2Node" // @TODO_BH: Remove the CVar for validity checking when we can get all the errors sorted out in a pre-flight // When we remove this then make sure we have a valid cook happening namespace PinValidityCheck { /** * CVar controls pin validity warning which will throw when a macro graph is silently failing * @see UE-90009 */ static bool bDisplayInvalidPinWarning = true; static FAutoConsoleVariableRef CVarDisplayInvalidPinWarning( TEXT("bp.PinValidityCheck.bDisplayInvalidPinWarning"), bDisplayInvalidPinWarning, TEXT("CVar controls pin validity warning which will throw when a macro graph is silently failing"), ECVF_Default); } UK2Node_Tunnel::UK2Node_Tunnel(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer) { bIsEditable = true; } void UK2Node_Tunnel::DestroyNode() { if (InputSinkNode != nullptr) { InputSinkNode->OutputSourceNode = nullptr; } if (OutputSourceNode != nullptr) { OutputSourceNode->InputSinkNode = nullptr; } //@TODO: Should we remove the pins provided by this node from the twinned node(s)? Super::DestroyNode(); } void UK2Node_Tunnel::PostPasteNode() { Super::PostPasteNode(); //@TODO: The gateway<->gateway node pairing should be unique, so we need to disallow this paste somehow (or just let it be an error later on)! } FText UK2Node_Tunnel::GetTooltipText() const { if (bCanHaveInputs && !bCanHaveOutputs) { return NSLOCTEXT("K2Node", "OutputTunnelTooltip", "Outputs from this graph"); } else if (!bCanHaveInputs && bCanHaveOutputs) { return NSLOCTEXT("K2Node", "InputTunnelTooltip", "Inputs into this graph"); } else { return NSLOCTEXT("K2Node", "TunnelConnectionTooltip", "Tunnel Connection"); } } FText UK2Node_Tunnel::GetNodeTitle(ENodeTitleType::Type TitleType) const { if (bCanHaveInputs && !bCanHaveOutputs) { return NSLOCTEXT("K2Node", "OutputTunnelTitle", "Outputs"); } else if (!bCanHaveInputs && bCanHaveOutputs) { return NSLOCTEXT("K2Node", "InputTunnelTitle", "Inputs"); } else { return NSLOCTEXT("K2Node", "TunnelConnectionTitle", "Tunnel Connection"); } } FName UK2Node_Tunnel::CreateUniquePinName(FName InSourcePinName) const { if (GetClass() == UK2Node_Tunnel::StaticClass()) { // When dealing with a tunnel node that is not a sub class (macro/collapsed graph entry and result), attempt to find the paired node and find a valid name between the two TWeakObjectPtr TunnelEntry; TWeakObjectPtr TunnelResult; FBlueprintEditorUtils::GetEntryAndResultNodes(GetGraph(), TunnelEntry, TunnelResult); if (TunnelEntry.IsValid() && TunnelResult.IsValid()) { FName PinName(InSourcePinName); int32 Index = 1; while (TunnelEntry.Get()->FindPin(PinName) != nullptr || TunnelResult.Get()->FindPin(PinName) != nullptr) { ++Index; PinName = *FString::Printf(TEXT("%s%d"),*InSourcePinName.ToString(), Index); } return PinName; } } return Super::CreateUniquePinName(InSourcePinName); } bool UK2Node_Tunnel::CanUserDeleteNode() const { // Disallow deletion of tunnels that are inside a tunnel graph, but allow it on top level tunnels that have gotten there on accident //@TODO: Remove this code if (UBlueprint* Blueprint = FBlueprintEditorUtils::FindBlueprintForNode(this)) { const bool bIsExactlyTunnel = (GetClass() == UK2Node_Tunnel::StaticClass()); const bool bIsTopLevelGraph = (GetGraph()->GetOuter() == Blueprint); const bool bIsLibraryProject = (Blueprint->BlueprintType == BPTYPE_MacroLibrary); const bool bIsLocalMacro = Blueprint->MacroGraphs.Contains(GetGraph()); if (bIsExactlyTunnel && bIsTopLevelGraph && !bIsLibraryProject && !bIsLocalMacro) { return true; } } return false; } bool UK2Node_Tunnel::CanDuplicateNode() const { return true; } bool UK2Node_Tunnel::IsNodeSafeToIgnore() const { // If there are no connections to anything on this node, it's safe to ignore int32 NumConnections = 0; for (int32 BoundaryPinIndex = 0; BoundaryPinIndex < Pins.Num(); ++BoundaryPinIndex) { UEdGraphPin* BoundaryPin = Pins[BoundaryPinIndex]; NumConnections += BoundaryPin->LinkedTo.Num(); } return NumConnections == 0; } bool UK2Node_Tunnel::DrawNodeAsExit() const { return(bCanHaveInputs && !bCanHaveOutputs); } bool UK2Node_Tunnel::DrawNodeAsEntry() const { return(!bCanHaveInputs && bCanHaveOutputs); } UK2Node_Tunnel* UK2Node_Tunnel::GetInputSink() const { return InputSinkNode; } UK2Node_Tunnel* UK2Node_Tunnel::GetOutputSource() const { return OutputSourceNode; } bool UK2Node_Tunnel::CanCreateUserDefinedPin(const FEdGraphPinType& InPinType, EEdGraphPinDirection InDesiredDirection, FText& OutErrorMessage) { bool bResult = true; // Make sure that if this is an exec node we are allowed one. if (InPinType.PinCategory == UEdGraphSchema_K2::PC_Exec && !CanModifyExecutionWires()) { OutErrorMessage = LOCTEXT("MultipleExecPinError", "Cannot support more exec pins!"); bResult = false; } else if(InDesiredDirection == EGPD_Input && !bCanHaveInputs) { OutErrorMessage = LOCTEXT("AddTunnelInputPinError", "Cannot add input pins to entry node!"); bResult = false; } else if(InDesiredDirection == EGPD_Output && !bCanHaveOutputs) { OutErrorMessage = LOCTEXT("AddTunnelOutputPinError", "Cannot add output pins to entry node!"); bResult = false; } return bResult; } void UK2Node_Tunnel::ClearCachedBlueprintData(UBlueprint* Blueprint) { // Remove data marking graphs as latent, this will be re-cache'd as needed MetaData.HasLatentFunctions = INDEX_NONE; } void UK2Node_Tunnel::ValidateNodeDuringCompilation(FCompilerResultsLog& MessageLog) const { Super::ValidateNodeDuringCompilation(MessageLog); if (PinValidityCheck::bDisplayInvalidPinWarning) { FBlueprintEditorUtils::ValidatePinConnections(this, MessageLog); } } void UK2Node_Tunnel::FixupPinStringDataReferences(FArchive* SavingArchive) { Super::FixupPinStringDataReferences(SavingArchive); if (SavingArchive) { UpdateUserDefinedPinDefaultValues(); } } UEdGraphPin* UK2Node_Tunnel::CreatePinFromUserDefinition(const TSharedPtr NewPinInfo) { // Create the new pin EEdGraphPinDirection Direction = bCanHaveInputs ? EGPD_Input : EGPD_Output; // Let the user pick the pin direction if legal if ( (bCanHaveInputs && NewPinInfo->DesiredPinDirection == EGPD_Input) || (bCanHaveOutputs && NewPinInfo->DesiredPinDirection == EGPD_Output) ) { Direction = NewPinInfo->DesiredPinDirection; } const UEdGraphSchema_K2* const Schema = GetDefault(); UEdGraphPin* Result = CreatePin(Direction, NewPinInfo->PinType, NewPinInfo->PinName); Schema->SetPinAutogeneratedDefaultValue(Result, NewPinInfo->PinDefaultValue); // Make sure it mirrors onto the associated node UEdGraphNode* TargetNode = (InputSinkNode ? InputSinkNode : OutputSourceNode); if (Cast(TargetNode) || Cast(TargetNode)) { UEdGraphPin* HasPinAlready = TargetNode->FindPin(Result->PinName); if (HasPinAlready == nullptr) { TargetNode->CreatePin(UEdGraphPin::GetComplementaryDirection(Direction), NewPinInfo->PinType, NewPinInfo->PinName); } } else if (UK2Node_Tunnel* TunnelNode = Cast((Direction == EGPD_Input) ? InputSinkNode : OutputSourceNode)) { TunnelNode->CreateUserDefinedPin(NewPinInfo->PinName, NewPinInfo->PinType, UEdGraphPin::GetComplementaryDirection(Direction)); } //@TODO: Automatically update loaded macro instances when this node is changed too return Result; } bool UK2Node_Tunnel::ModifyUserDefinedPinDefaultValue(TSharedPtr PinInfo, const FString& NewDefaultValue) { if (Super::ModifyUserDefinedPinDefaultValue(PinInfo, NewDefaultValue)) { const UEdGraphSchema_K2* K2Schema = GetDefault(); K2Schema->HandleParameterDefaultValueChanged(this); return true; } return false; } bool UK2Node_Tunnel::ShouldDoSmartWildcardInference() { static const FBoolConfigValueHelper bUseSimpleWildcardInference(TEXT("Blueprints"), TEXT("bUseSimpleWildcardInference"), GEngineIni); return !bUseSimpleWildcardInference; } bool UK2Node_Tunnel::CanModifyExecutionWires() { return true; } ERenamePinResult UK2Node_Tunnel::RenameUserDefinedPinImpl(const FName OldName, const FName NewName, bool bTest) { const ERenamePinResult ThisNodeResult = Super::RenameUserDefinedPinImpl(OldName, NewName, bTest); if(ERenamePinResult::ERenamePinResult_NameCollision == ThisNodeResult) { return ERenamePinResult::ERenamePinResult_NameCollision; } // And do the same on the twinned pin ERenamePinResult TargetNodeResult = ERenamePinResult::ERenamePinResult_Success; UEdGraphNode* TargetNode = ((InputSinkNode != nullptr) ? InputSinkNode : OutputSourceNode); if (UK2Node_Composite* CompositeNode = Cast(TargetNode)) { TargetNodeResult = CompositeNode->RenameUserDefinedPin(OldName, NewName, bTest); } if(ERenamePinResult::ERenamePinResult_NameCollision == TargetNodeResult) { return ERenamePinResult::ERenamePinResult_NameCollision; } return FMath::Min(ThisNodeResult, TargetNodeResult); } UObject* UK2Node_Tunnel::GetJumpTargetForDoubleClick() const { // Try to select the other side of a tunnel node UEdGraphNode* TargetNode = GetOutputSource(); if (TargetNode == nullptr) { TargetNode = GetInputSink(); } return TargetNode; } void UK2Node_Tunnel::CacheWildcardPins() { WildcardPins.Reset(); const bool bSupportMapWildcards = ShouldDoSmartWildcardInference(); if(bSupportMapWildcards) { for (UEdGraphPin* Pin : Pins) { if (FWildcardNodeUtils::HasAnyWildcards(Pin)) { WildcardPins.Add(Pin); } } } else { for (UEdGraphPin* Pin : Pins) { // for each of the wildcard pins... if (Pin->PinType.PinCategory == UEdGraphSchema_K2::PC_Wildcard) { WildcardPins.Add(Pin); } } } } void UK2Node_Tunnel::ReallocatePinsDuringReconstruction(TArray& OldPins) { Super::ReallocatePinsDuringReconstruction(OldPins); // determine if all wildcard pins are unlinked. // if they are, we should revert them all back to wildcard status bool bAllWildcardsAreUnlinked = true; for (UEdGraphPin* Pin : WildcardPins) { // find it in the old pins array (where it might not be a wildcard) // and see if it's unlinked for (UEdGraphPin* OldPin : OldPins) { if (OldPin->PinName == Pin->PinName) { TFunction IsPinLinked = [&IsPinLinked](UEdGraphPin* InPin) { if (InPin->LinkedTo.Num() > 0) { return true; } for (UEdGraphPin* SubPin : InPin->SubPins) { if (IsPinLinked(SubPin)) { return true; } } return false; }; if (IsPinLinked(OldPin)) { bAllWildcardsAreUnlinked = false; break; } } } if (bAllWildcardsAreUnlinked == false) { break; } } if (bAllWildcardsAreUnlinked == false) { const bool bSupportMapWildcards = ShouldDoSmartWildcardInference(); // Copy pin types from old pins for wildcard pins for (UEdGraphPin* const Pin : WildcardPins) { // Only change the type if it is still a wildcard if ((bSupportMapWildcards && FWildcardNodeUtils::HasAnyWildcards(Pin)) || Pin->PinType.PinCategory == UEdGraphSchema_K2::PC_Wildcard) { // find it in the old pins and copy the type for (UEdGraphPin const* const OldPin : OldPins) { if (OldPin->PinName == Pin->PinName) { Pin->PinType = OldPin->PinType; break; } } } } } PostFixupAllWildcardPins(bAllWildcardsAreUnlinked); } bool UK2Node_Tunnel::IsCompatibleWithGraph(const UEdGraph* InGraph) const { const bool bIsEntryOrExit = bCanHaveInputs != bCanHaveOutputs; // If this is an Entry or Exit, make sure an Entry or exit doesn't already exist in this Graph if (bIsEntryOrExit) { TArray Tunnels; InGraph->GetNodesOfClass(Tunnels); for (UK2Node_Tunnel* Node : Tunnels) { if (Node->bCanHaveInputs == bCanHaveInputs && Node->bCanHaveOutputs == bCanHaveOutputs) { return false; } } } return Super::IsCompatibleWithGraph(InGraph); } #undef LOCTEXT_NAMESPACE