// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. #include "K2Node.h" #include "BlueprintCompilationManager.h" #include "UObject/UnrealType.h" #include "UObject/CoreRedirects.h" #include "EdGraph/EdGraphPin.h" #include "UObject/Interface.h" #include "Engine/Blueprint.h" #include "Engine/MemberReference.h" #include "GraphEditorSettings.h" #include "EdGraph/EdGraphSchema.h" #include "EdGraphSchema_K2.h" #include "K2Node_CallFunction.h" #include "K2Node_MacroInstance.h" #include "Kismet2/BlueprintEditorUtils.h" #include "Editor/EditorEngine.h" #include "Misc/OutputDeviceNull.h" #include "Engine/Breakpoint.h" #include "Kismet2/KismetDebugUtilities.h" #include "KismetCompiler.h" #include "PropertyCustomizationHelpers.h" #include "ObjectEditorUtils.h" #include "UObject/FrameworkObjectVersion.h" #define LOCTEXT_NAMESPACE "K2Node" // File-Scoped Globals static const uint32 MaxArrayPinTooltipLineCount = 10; ///////////////////////////////////////////////////// // UK2Node UK2Node::UK2Node(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer) { bAllowSplitPins_DEPRECATED = true; OrphanedPinSaveMode = ESaveOrphanPinMode::SaveAllButExec; } void UK2Node::PostLoad() { #if WITH_EDITORONLY_DATA // Clean up win watches for any deprecated pins we are about to remove in Super::PostLoad if (DeprecatedPins.Num() && HasValidBlueprint()) { UBlueprint* BP = GetBlueprint(); check(BP); // patch DeprecatedPinWatches to WatchedPins: for (int32 WatchIdx = BP->DeprecatedPinWatches.Num() - 1; WatchIdx >= 0; --WatchIdx) { UEdGraphPin_Deprecated* WatchedPin = BP->DeprecatedPinWatches[WatchIdx]; if (DeprecatedPins.Contains(WatchedPin)) { if (UEdGraphPin* NewPin = UEdGraphPin::FindPinCreatedFromDeprecatedPin(WatchedPin)) { BP->WatchedPins.Add(NewPin); } BP->DeprecatedPinWatches.RemoveAt(WatchIdx); } } } #endif // WITH_EDITORONLY_DATA Super::PostLoad(); } void UK2Node::Serialize(FArchive& Ar) { Ar.UsingCustomVersion(FFrameworkObjectVersion::GUID); if (Ar.IsSaving()) { for (UEdGraphPin* Pin : Pins) { if (!Pin->bDefaultValueIsIgnored && !Pin->DefaultValue.IsEmpty() ) { // If looking for references during save, expand any default values on the pins // This is only reliable when saving in the editor, the cook case is handled below if (Ar.IsObjectReferenceCollector() && Pin->PinType.PinCategory == UEdGraphSchema_K2::PC_Struct && Pin->PinType.PinSubCategoryObject.IsValid()) { UScriptStruct* Struct = Cast(Pin->PinType.PinSubCategoryObject.Get()); if (Struct) { TSharedPtr StructData = MakeShareable(new FStructOnScope(Struct)); // Import the literal text to a dummy struct and then serialize that. Hard object references will not properly import, this is only useful for soft references! FOutputDeviceNull NullOutput; Struct->ImportText(*Pin->DefaultValue, StructData->GetStructMemory(), nullptr, PPF_SerializedAsImportText, &NullOutput, Pin->PinName.ToString()); Struct->SerializeItem(Ar, StructData->GetStructMemory(), nullptr); } } if (Pin->PinType.PinCategory == UEdGraphSchema_K2::PC_SoftObject || Pin->PinType.PinCategory == UEdGraphSchema_K2::PC_SoftClass) { FSoftObjectPath TempRef(Pin->DefaultValue); // Serialize the asset reference, this will do the save fixup. It won't actually serialize the string if this is a real archive like linkersave FSoftObjectPathSerializationScope DisableSerialize(NAME_None, NAME_None, ESoftObjectPathCollectType::AlwaysCollect, ESoftObjectPathSerializeType::SkipSerializeIfArchiveHasSize); Ar << TempRef; Pin->DefaultValue = TempRef.ToString(); } } } } Super::Serialize(Ar); if (Ar.IsLoading()) { // Fix up pin default values, must be done before post load FixupPinDefaultValues(); } } void UK2Node::FixupPinDefaultValues() { const int32 LinkerUE4Version = GetLinkerUE4Version(); const int32 LinkerFrameworkVersion = GetLinkerCustomVersion(FFrameworkObjectVersion::GUID); const UEdGraphSchema_K2* K2Schema = GetDefault(); // Swap "new" default error tolerance value with zero on vector/rotator equality nodes, in order to preserve current behavior in existing blueprints. if(LinkerUE4Version < VER_UE4_BP_MATH_VECTOR_EQUALITY_USES_EPSILON) { static const FString VectorsEqualFunctionEpsilonPinName = TEXT("KismetMathLibrary.EqualEqual_VectorVector.ErrorTolerance"); static const FString VectorsNotEqualFunctionEpsilonPinName = TEXT("KismetMathLibrary.NotEqual_VectorVector.ErrorTolerance"); static const FString RotatorsEqualFunctionEpsilonPinName = TEXT("KismetMathLibrary.EqualEqual_RotatorRotator.ErrorTolerance"); static const FString RotatorsNotEqualFunctionEpsilonPinName = TEXT("KismetMathLibrary.NotEqual_RotatorRotator.ErrorTolerance"); bool bFoundPin = false; for(int32 i = 0; i < Pins.Num() && !bFoundPin; ++i) { UEdGraphPin* Pin = Pins[i]; check(Pin); TArray RedirectPinNames; GetRedirectPinNames(*Pin, RedirectPinNames); for (const FString& PinName : RedirectPinNames) { if((Pin->DefaultValue == Pin->AutogeneratedDefaultValue) && (PinName == VectorsEqualFunctionEpsilonPinName || PinName == VectorsNotEqualFunctionEpsilonPinName || PinName == RotatorsEqualFunctionEpsilonPinName || PinName == RotatorsNotEqualFunctionEpsilonPinName)) { bFoundPin = true; K2Schema->TrySetDefaultValue(*Pin, TEXT("0.0")); break; } } } } // Fix soft object ptr pins if (GIsEditor || LinkerFrameworkVersion < FFrameworkObjectVersion::ChangeAssetPinsToString) { FSoftObjectPathSerializationScope SetPackage(GetOutermost()->GetFName(), NAME_None, ESoftObjectPathCollectType::AlwaysCollect, ESoftObjectPathSerializeType::SkipSerializeIfArchiveHasSize); for (int32 i = 0; i < Pins.Num(); ++i) { UEdGraphPin* Pin = Pins[i]; if (Pin->PinType.PinCategory == UEdGraphSchema_K2::PC_SoftObject || Pin->PinType.PinCategory == UEdGraphSchema_K2::PC_SoftClass) { // Fix old assetptr pins if (LinkerFrameworkVersion < FFrameworkObjectVersion::ChangeAssetPinsToString) { if (Pin->DefaultObject && Pin->DefaultValue.IsEmpty()) { Pin->DefaultValue = Pin->DefaultObject->GetPathName(); Pin->DefaultObject = nullptr; } } // In editor, fixup soft object ptrs on load on to handle redirects and finding refs for cooking // We're not handling soft object ptrs inside FStructs because it's a rare edge case and would be a performance hit on load if (GIsEditor && !Pin->DefaultValue.IsEmpty()) { FSoftObjectPath TempRef(Pin->DefaultValue); TempRef.PostLoadPath(GetLinker()); TempRef.PreSavePath(); Pin->DefaultValue = TempRef.ToString(); } } } } } FText UK2Node::GetToolTipHeading() const { UBlueprint* Blueprint = FBlueprintEditorUtils::FindBlueprintForNode(this); FText Heading = FText::GetEmpty(); if (Blueprint) { if (UBreakpoint* ExistingBreakpoint = FKismetDebugUtilities::FindBreakpointForNode(Blueprint, this)) { if (ExistingBreakpoint->IsEnabled()) { Heading = LOCTEXT("EnabledBreakpoint", "Active Breakpoint - Execution will break at this location."); } else { Heading = LOCTEXT("DisabledBreakpoint", "Disabled Breakpoint"); } } } return Heading; } bool UK2Node::CreatePinsForFunctionEntryExit(const UFunction* Function, bool bForFunctionEntry) { const UEdGraphSchema_K2* K2Schema = GetDefault(); // if the generated class is not up to date, use the skeleton's class function to create pins: Function = FBlueprintEditorUtils::GetMostUpToDateFunction(Function); // Create the inputs and outputs bool bAllPinsGood = true; for (TFieldIterator PropIt(Function); PropIt && (PropIt->PropertyFlags & CPF_Parm); ++PropIt) { UProperty* Param = *PropIt; const bool bIsFunctionInput = !Param->HasAnyPropertyFlags(CPF_OutParm) || Param->HasAnyPropertyFlags(CPF_ReferenceParm); if (bIsFunctionInput == bForFunctionEntry) { const EEdGraphPinDirection Direction = bForFunctionEntry ? EGPD_Output : EGPD_Input; UEdGraphPin* Pin = CreatePin(Direction, NAME_None, Param->GetFName()); const bool bPinGood = K2Schema->ConvertPropertyToPinType(Param, /*out*/ Pin->PinType); K2Schema->SetPinAutogeneratedDefaultValueBasedOnType(Pin); UK2Node_CallFunction::GeneratePinTooltipFromFunction(*Pin, Function); bAllPinsGood = bAllPinsGood && bPinGood; } } return bAllPinsGood; } void UK2Node::AutowireNewNode(UEdGraphPin* FromPin) { const UEdGraphSchema_K2* K2Schema = CastChecked(GetSchema()); // Do some auto-connection if (FromPin) { TSet NodeList; // sometimes we don't always find an ideal connection, but we want to exhaust // all our options first... this stores a secondary less-ideal pin to connect to, if nothing better was found UEdGraphPin* BackupConnection = NULL; // If not dragging an exec pin, auto-connect from dragged pin to first compatible pin on the new node for (int32 i=0; i