// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved. #include "BlueprintGraphPrivatePCH.h" #include "Engine/LevelScriptBlueprint.h" #include "Kismet/KismetSystemLibrary.h" #include "Kismet/KismetArrayLibrary.h" #include "GraphEditorActions.h" #include "GraphEditorSettings.h" #include "ScopedTransaction.h" #include "ComponentAssetBroker.h" #include "BlueprintEditorSettings.h" #include "Kismet2/KismetEditorUtilities.h" #include "Kismet2/KismetDebugUtilities.h" #include "KismetCompiler.h" #include "ComponentAssetBroker.h" #include "AssetData.h" #include "Editor/UnrealEd/Public/EdGraphUtilities.h" #include "DefaultValueHelper.h" #include "ObjectEditorUtils.h" #include "ActorEditorUtils.h" #include "ComponentTypeRegistry.h" #include "BlueprintComponentNodeSpawner.h" #include "AssetRegistryModule.h" #include "Blueprint/AIBlueprintHelperLibrary.h" #include "K2Node_CastByteToEnum.h" #include "K2Node_ClassDynamicCast.h" #include "K2Node_GetEnumeratorName.h" #include "K2Node_GetEnumeratorNameAsString.h" #include "K2Node_Tunnel.h" #include "K2Node_SetFieldsInStruct.h" #include "GenericCommands.h" ////////////////////////////////////////////////////////////////////////// // FBlueprintMetadata const FName FBlueprintMetadata::MD_AllowableBlueprintVariableType(TEXT("BlueprintType")); const FName FBlueprintMetadata::MD_NotAllowableBlueprintVariableType(TEXT("NotBlueprintType")); const FName FBlueprintMetadata::MD_BlueprintSpawnableComponent(TEXT("BlueprintSpawnableComponent")); const FName FBlueprintMetadata::MD_IsBlueprintBase(TEXT("IsBlueprintBase")); const FName FBlueprintMetadata::MD_RestrictedToClasses(TEXT("RestrictedToClasses")); const FName FBlueprintMetadata::MD_ChildCanTick(TEXT("ChildCanTick")); const FName FBlueprintMetadata::MD_ChildCannotTick(TEXT("ChildCannotTick")); const FName FBlueprintMetadata::MD_IgnoreCategoryKeywordsInSubclasses(TEXT("IgnoreCategoryKeywordsInSubclasses")); const FName FBlueprintMetadata::MD_Protected(TEXT("BlueprintProtected")); const FName FBlueprintMetadata::MD_Latent(TEXT("Latent")); const FName FBlueprintMetadata::MD_UnsafeForConstructionScripts(TEXT("UnsafeDuringActorConstruction")); const FName FBlueprintMetadata::MD_FunctionCategory(TEXT("Category")); const FName FBlueprintMetadata::MD_DeprecatedFunction(TEXT("DeprecatedFunction")); const FName FBlueprintMetadata::MD_DeprecationMessage(TEXT("DeprecationMessage")); const FName FBlueprintMetadata::MD_CompactNodeTitle(TEXT("CompactNodeTitle")); const FName FBlueprintMetadata::MD_FriendlyName(TEXT("FriendlyName")); const FName FBlueprintMetadata::MD_ExposeOnSpawn(TEXT("ExposeOnSpawn")); const FName FBlueprintMetadata::MD_DefaultToSelf(TEXT("DefaultToSelf")); const FName FBlueprintMetadata::MD_WorldContext(TEXT("WorldContext")); const FName FBlueprintMetadata::MD_CallableWithoutWorldContext(TEXT("CallableWithoutWorldContext")); const FName FBlueprintMetadata::MD_AutoCreateRefTerm(TEXT("AutoCreateRefTerm")); const FName FBlueprintMetadata::MD_ShowWorldContextPin(TEXT("ShowWorldContextPin")); const FName FBlueprintMetadata::MD_Private(TEXT("BlueprintPrivate")); const FName FBlueprintMetadata::MD_BlueprintInternalUseOnly(TEXT("BlueprintInternalUseOnly")); const FName FBlueprintMetadata::MD_NeedsLatentFixup(TEXT("NeedsLatentFixup")); const FName FBlueprintMetadata::MD_LatentCallbackTarget(TEXT("LatentCallbackTarget")); const FName FBlueprintMetadata::MD_AllowPrivateAccess(TEXT("AllowPrivateAccess")); const FName FBlueprintMetadata::MD_ExposeFunctionCategories(TEXT("ExposeFunctionCategories")); const FName FBlueprintMetadata::MD_CannotImplementInterfaceInBlueprint(TEXT("CannotImplementInterfaceInBlueprint")); const FName FBlueprintMetadata::MD_ProhibitedInterfaces(TEXT("ProhibitedInterfaces")); const FName FBlueprintMetadata::MD_FunctionKeywords(TEXT("Keywords")); const FName FBlueprintMetadata::MD_ExpandEnumAsExecs(TEXT("ExpandEnumAsExecs")); const FName FBlueprintMetadata::MD_CommutativeAssociativeBinaryOperator(TEXT("CommutativeAssociativeBinaryOperator")); const FName FBlueprintMetadata::MD_MaterialParameterCollectionFunction(TEXT("MaterialParameterCollectionFunction")); const FName FBlueprintMetadata::MD_Tooltip(TEXT("Tooltip")); const FName FBlueprintMetadata::MD_CallInEditor(TEXT("CallInEditor")); const FName FBlueprintMetadata::MD_DataTablePin(TEXT("DataTablePin")); const FName FBlueprintMetadata::MD_NativeMakeFunction(TEXT("HasNativeMake")); const FName FBlueprintMetadata::MD_NativeBreakFunction(TEXT("HasNativeBreak")); const FName FBlueprintMetadata::MD_DynamicOutputType(TEXT("DeterminesOutputType")); const FName FBlueprintMetadata::MD_DynamicOutputParam(TEXT("DynamicOutputParam")); const FName FBlueprintMetadata::MD_ArrayParam(TEXT("ArrayParm")); const FName FBlueprintMetadata::MD_ArrayDependentParam(TEXT("ArrayTypeDependentParams")); ////////////////////////////////////////////////////////////////////////// #define LOCTEXT_NAMESPACE "KismetSchema" UEdGraphSchema_K2::FPinTypeTreeInfo::FPinTypeTreeInfo(const FText& InFriendlyCategoryName, const FString& CategoryName, const UEdGraphSchema_K2* Schema, const FText& InTooltip, bool bInReadOnly/*=false*/) { Init(InFriendlyCategoryName, CategoryName, Schema, InTooltip, bInReadOnly); } /** Helper class to gather variable types */ class FGatherTypesHelper { private: typedef TSharedPtr FPinTypeTreeInfoPtr; struct FCompareChildren { FORCEINLINE bool operator()(const FPinTypeTreeInfoPtr A, const FPinTypeTreeInfoPtr B) const { return (A->GetDescription().ToString() < B->GetDescription().ToString()); } }; /** Helper function to add an unloaded asset to the children, does no validation on passed data */ static void AddUnloadedAsset(const FAssetData& InAsset, const FString& InCategoryName, TArray& OutChildren) { const FString* TooltipPtr = InAsset.TagsAndValues.Find(TEXT("Tooltip")); const FString Tooltip = (TooltipPtr && !TooltipPtr->IsEmpty()) ? *TooltipPtr : InAsset.ObjectPath.ToString(); FPinTypeTreeInfoPtr TypeTreeInfo = MakeShareable(new UEdGraphSchema_K2::FPinTypeTreeInfo(InCategoryName, InAsset.ToStringReference(), FText::FromString(Tooltip))); TypeTreeInfo->FriendlyName = FText::FromString(FName::NameToDisplayString(InAsset.AssetName.ToString(), false)); OutChildren.Add(TypeTreeInfo); } /** * Gets a list of variable subtypes that are valid for the specified type * * @param Type The type to grab subtypes for * @param SubtypesList (out) Upon return, this will be a list of valid subtype objects for the specified type */ static void GetVariableSubtypes(const FString& Type, TArray& OutSubtypesList) { OutSubtypesList.Empty(); if (Type == UEdGraphSchema_K2::PC_Struct) { // Find script structs marked with "BlueprintType=true" in their metadata, and add to the list for (TObjectIterator StructIt; StructIt; ++StructIt) { UScriptStruct* ScriptStruct = *StructIt; if (UEdGraphSchema_K2::IsAllowableBlueprintVariableType(ScriptStruct)) { OutSubtypesList.Add(ScriptStruct); } } } else if (Type == UEdGraphSchema_K2::PC_Class) { // Generate a list of all potential objects which have "BlueprintType=true" in their metadata for (TObjectIterator ClassIt; ClassIt; ++ClassIt) { UClass* CurrentClass = *ClassIt; if (UEdGraphSchema_K2::IsAllowableBlueprintVariableType(CurrentClass) && !CurrentClass->HasAnyClassFlags(CLASS_Deprecated)) { OutSubtypesList.Add(CurrentClass); } } } else if (Type == UEdGraphSchema_K2::PC_Object) { // Generate a list of all potential objects which have "BlueprintType=true" in their metadata (that aren't interfaces) for (TObjectIterator ClassIt; ClassIt; ++ClassIt) { UClass* CurrentClass = *ClassIt; if (!CurrentClass->IsChildOf(UInterface::StaticClass()) && UEdGraphSchema_K2::IsAllowableBlueprintVariableType(CurrentClass) && !CurrentClass->HasAnyClassFlags(CLASS_Deprecated)) { OutSubtypesList.Add(CurrentClass); } } } else if (Type == UEdGraphSchema_K2::PC_Interface) { // Generate a list of all potential objects which have "BlueprintType=true" in their metadata (only ones that are interfaces) for (TObjectIterator ClassIt; ClassIt; ++ClassIt) { UClass* CurrentClass = *ClassIt; if (CurrentClass->IsChildOf(UInterface::StaticClass()) && UEdGraphSchema_K2::IsAllowableBlueprintVariableType(CurrentClass)) { OutSubtypesList.Add(CurrentClass); } } } else if (Type == UEdGraphSchema_K2::PC_Enum) { // Generate a list of all potential enums which have "BlueprintType=true" in their metadata for (TObjectIterator EnumIt; EnumIt; ++EnumIt) { UEnum* CurrentEnum = *EnumIt; if (UEdGraphSchema_K2::IsAllowableBlueprintVariableType(CurrentEnum)) { OutSubtypesList.Add(CurrentEnum); } } } } public: /** * Gathers all valid sub-types (loaded and unloaded) of a passed category and sorts them alphabetically * @param FriendlyName Friendly name to be used for the tooltip if there is no available data * @param CategoryName Category (type) to find sub-types of * @param Schema Schema to use * @param OutChildren All the gathered children */ static void Gather(const FText& FriendlyName, const FString& CategoryName, const UEdGraphSchema_K2* Schema, TArray& OutChildren) { // Gather any loaded subtype children first TArray LoadedSubTypes; GetVariableSubtypes(CategoryName, LoadedSubTypes); FEdGraphPinType LoadedPinSubtype; LoadedPinSubtype.PinCategory = (CategoryName == UEdGraphSchema_K2::PC_Enum ? UEdGraphSchema_K2::PC_Byte : CategoryName); LoadedPinSubtype.PinSubCategory = TEXT(""); LoadedPinSubtype.PinSubCategoryObject = NULL; // Add any loaded subtypes to the children list for (auto it = LoadedSubTypes.CreateIterator(); it; ++it) { FText SubtypeTooltip; UStruct* Struct = Cast(*it); if(Struct != NULL) { SubtypeTooltip = (Struct ? Struct->GetToolTipText() : FriendlyName); } OutChildren.Add( MakeShareable(new UEdGraphSchema_K2::FPinTypeTreeInfo(LoadedPinSubtype.PinCategory, *it, SubtypeTooltip)) ); } UClass* AssetRegistrySearchClass = nullptr; if(Schema->PC_Struct == CategoryName) { AssetRegistrySearchClass = UUserDefinedStruct::StaticClass(); } else if(Schema->PC_Enum == CategoryName) { AssetRegistrySearchClass = UUserDefinedEnum::StaticClass(); } else if(Schema->PC_Object == CategoryName || Schema->PC_Class == CategoryName || Schema->PC_Interface == CategoryName) { AssetRegistrySearchClass = UBlueprint::StaticClass(); } // Gather all assets of the specified class const FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked(TEXT("AssetRegistry")); TArray AssetData; AssetRegistryModule.Get().GetAssetsByClass(AssetRegistrySearchClass->GetFName(), AssetData); // Search through all the unloaded assets, filtered by certain rules based on struct/enum or object/class/interface if (Schema->PC_Struct == CategoryName || Schema->PC_Enum == CategoryName) { for (int32 AssetIndex = 0; AssetIndex < AssetData.Num(); ++AssetIndex) { const FAssetData& Asset = AssetData[AssetIndex]; if (Asset.IsValid() && !Asset.IsAssetLoaded()) { AddUnloadedAsset(Asset, CategoryName, OutChildren); } } } else if(Schema->PC_Object == CategoryName || Schema->PC_Class == CategoryName || Schema->PC_Interface == CategoryName) { const FString BPTypeAllowed = (Schema->PC_Interface == CategoryName)? TEXT("BPTYPE_Interface") : TEXT("BPTYPE_Normal"); for (int32 AssetIndex = 0; AssetIndex < AssetData.Num(); ++AssetIndex) { const FAssetData& Asset = AssetData[AssetIndex]; if (Asset.IsValid() && !Asset.IsAssetLoaded()) { // Based on the category, only certain Blueprint types are available const FString* BlueprintTypeStr = Asset.TagsAndValues.Find("BlueprintType"); if(BlueprintTypeStr && *BlueprintTypeStr == BPTypeAllowed) { uint32 ClassFlags = 0; const FString* ClassFlagsStr = Asset.TagsAndValues.Find("ClassFlags"); if(ClassFlagsStr) { ClassFlags = FCString::Atoi(**ClassFlagsStr); } // Do not allow deprecated Blueprints to be added if(!(ClassFlags & CLASS_Deprecated)) { AddUnloadedAsset(Asset, CategoryName, OutChildren); } } } } } OutChildren.Sort(FCompareChildren()); } /** Loads an asset based on the AssetReference through the asset registry */ static UObject* LoadAsset(const FStringAssetReference& AssetReference) { if (AssetReference.IsValid()) { const FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked(TEXT("AssetRegistry")); const FAssetData AssetData = AssetRegistryModule.Get().GetAssetByObjectPath(*AssetReference.ToString()); return AssetData.GetAsset(); } return nullptr; } }; const FEdGraphPinType& UEdGraphSchema_K2::FPinTypeTreeInfo::GetPinType(bool bForceLoadedSubCategoryObject) { if (bForceLoadedSubCategoryObject) { // Only attempt to load the sub category object if we need to if ( SubCategoryObjectAssetReference.IsValid() && (!PinType.PinSubCategoryObject.IsValid() || FStringAssetReference(PinType.PinSubCategoryObject.Get()) != SubCategoryObjectAssetReference) ) { UObject* LoadedObject = FGatherTypesHelper::LoadAsset(SubCategoryObjectAssetReference); if(UBlueprint* BlueprintObject = Cast(LoadedObject)) { PinType.PinSubCategoryObject = *BlueprintObject->GeneratedClass; } else { PinType.PinSubCategoryObject = LoadedObject; } } } else { if (SubCategoryObjectAssetReference.IsValid()) { const FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked(TEXT("AssetRegistry")); const FAssetData AssetData = AssetRegistryModule.Get().GetAssetByObjectPath(*SubCategoryObjectAssetReference.ToString()); if(!AssetData.IsAssetLoaded()) { UObject* LoadedObject = FindObject(ANY_PACKAGE, *AssetData.AssetClass.ToString()); // If the unloaded asset is a Blueprint, we need to pull the generated class and assign that if(UBlueprint* BlueprintObject = Cast(LoadedObject)) { PinType.PinSubCategoryObject = *BlueprintObject->GeneratedClass; } else { PinType.PinSubCategoryObject = LoadedObject; } } else { PinType.PinSubCategoryObject = AssetData.GetAsset(); } } } return PinType; } void UEdGraphSchema_K2::FPinTypeTreeInfo::Init(const FText& InFriendlyName, const FString& CategoryName, const UEdGraphSchema_K2* Schema, const FText& InTooltip, bool bInReadOnly) { check( !CategoryName.IsEmpty() ); check( Schema ); FriendlyName = InFriendlyName; Tooltip = InTooltip; PinType.PinCategory = (CategoryName == PC_Enum? PC_Byte : CategoryName); PinType.PinSubCategory = TEXT(""); PinType.PinSubCategoryObject = NULL; bReadOnly = bInReadOnly; if (Schema->DoesTypeHaveSubtypes(CategoryName)) { FGatherTypesHelper::Gather(InFriendlyName, CategoryName, Schema, Children); } } UEdGraphSchema_K2::FPinTypeTreeInfo::FPinTypeTreeInfo(const FString& CategoryName, UObject* SubCategoryObject, const FText& InTooltip, bool bInReadOnly/*=false*/) { check( !CategoryName.IsEmpty() ); check( SubCategoryObject ); Tooltip = InTooltip; PinType.PinCategory = CategoryName; PinType.PinSubCategoryObject = SubCategoryObject; bReadOnly = bInReadOnly; } UEdGraphSchema_K2::FPinTypeTreeInfo::FPinTypeTreeInfo(const FString& CategoryName, const FStringAssetReference& SubCategoryObject, const FText& InTooltip, bool bInReadOnly) { check(!CategoryName.IsEmpty()); check(SubCategoryObject.IsValid()); Tooltip = InTooltip; PinType.PinCategory = CategoryName; SubCategoryObjectAssetReference = SubCategoryObject; PinType.PinSubCategoryObject = SubCategoryObjectAssetReference.ResolveObject(); bReadOnly = bInReadOnly; } FText UEdGraphSchema_K2::FPinTypeTreeInfo::GetDescription() const { if (!FriendlyName.IsEmpty()) { return FriendlyName; } else if (PinType.PinSubCategoryObject.IsValid()) { FText DisplayName; if (UField* SubCategoryField = Cast(PinType.PinSubCategoryObject.Get())) { DisplayName = SubCategoryField->GetDisplayNameText(); } else { DisplayName = FText::FromString(FName::NameToDisplayString(PinType.PinSubCategoryObject->GetName(), PinType.PinCategory == PC_Boolean)); } return DisplayName; } else { return LOCTEXT("PinDescriptionError", "Error!"); } } const FString UEdGraphSchema_K2::PC_Exec(TEXT("exec")); const FString UEdGraphSchema_K2::PC_Boolean(TEXT("bool")); const FString UEdGraphSchema_K2::PC_Byte(TEXT("byte")); const FString UEdGraphSchema_K2::PC_Class(TEXT("class")); const FString UEdGraphSchema_K2::PC_Int(TEXT("int")); const FString UEdGraphSchema_K2::PC_Float(TEXT("float")); const FString UEdGraphSchema_K2::PC_Name(TEXT("name")); const FString UEdGraphSchema_K2::PC_Delegate(TEXT("delegate")); const FString UEdGraphSchema_K2::PC_MCDelegate(TEXT("mcdelegate")); const FString UEdGraphSchema_K2::PC_Object(TEXT("object")); const FString UEdGraphSchema_K2::PC_Interface(TEXT("interface")); const FString UEdGraphSchema_K2::PC_String(TEXT("string")); const FString UEdGraphSchema_K2::PC_Text(TEXT("text")); const FString UEdGraphSchema_K2::PC_Struct(TEXT("struct")); const FString UEdGraphSchema_K2::PC_Wildcard(TEXT("wildcard")); const FString UEdGraphSchema_K2::PC_Enum(TEXT("enum")); const FString UEdGraphSchema_K2::PSC_Self(TEXT("self")); const FString UEdGraphSchema_K2::PSC_Index(TEXT("index")); const FString UEdGraphSchema_K2::PN_Execute(TEXT("execute")); const FString UEdGraphSchema_K2::PN_Then(TEXT("then")); const FString UEdGraphSchema_K2::PN_Completed(TEXT("Completed")); const FString UEdGraphSchema_K2::PN_DelegateEntry(TEXT("delegate")); const FString UEdGraphSchema_K2::PN_EntryPoint(TEXT("EntryPoint")); const FString UEdGraphSchema_K2::PN_Self(TEXT("self")); const FString UEdGraphSchema_K2::PN_Else(TEXT("else")); const FString UEdGraphSchema_K2::PN_Loop(TEXT("loop")); const FString UEdGraphSchema_K2::PN_After(TEXT("after")); const FString UEdGraphSchema_K2::PN_ReturnValue(TEXT("ReturnValue")); const FString UEdGraphSchema_K2::PN_ObjectToCast(TEXT("Object")); const FString UEdGraphSchema_K2::PN_Condition(TEXT("Condition")); const FString UEdGraphSchema_K2::PN_Start(TEXT("Start")); const FString UEdGraphSchema_K2::PN_Stop(TEXT("Stop")); const FString UEdGraphSchema_K2::PN_Index(TEXT("Index")); const FString UEdGraphSchema_K2::PN_Item(TEXT("Item")); const FString UEdGraphSchema_K2::PN_CastSucceeded(TEXT("then")); const FString UEdGraphSchema_K2::PN_CastFailed(TEXT("CastFailed")); const FString UEdGraphSchema_K2::PN_CastedValuePrefix(TEXT("As")); const FString UEdGraphSchema_K2::PN_MatineeFinished(TEXT("Finished")); const FName UEdGraphSchema_K2::FN_UserConstructionScript(TEXT("UserConstructionScript")); const FName UEdGraphSchema_K2::FN_ExecuteUbergraphBase(TEXT("ExecuteUbergraph")); const FName UEdGraphSchema_K2::GN_EventGraph(TEXT("EventGraph")); const FName UEdGraphSchema_K2::GN_AnimGraph(TEXT("AnimGraph")); const FName UEdGraphSchema_K2::VR_DefaultCategory(TEXT("Default")); const int32 UEdGraphSchema_K2::AG_LevelReference = 100; const UScriptStruct* UEdGraphSchema_K2::VectorStruct = nullptr; const UScriptStruct* UEdGraphSchema_K2::RotatorStruct = nullptr; const UScriptStruct* UEdGraphSchema_K2::TransformStruct = nullptr; const UScriptStruct* UEdGraphSchema_K2::LinearColorStruct = nullptr; const UScriptStruct* UEdGraphSchema_K2::ColorStruct = nullptr; bool UEdGraphSchema_K2::bGeneratingDocumentation = false; int32 UEdGraphSchema_K2::CurrentCacheRefreshID = 0; UEdGraphSchema_K2::UEdGraphSchema_K2(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer) { // Initialize cached static references to well-known struct types if (VectorStruct == nullptr) { VectorStruct = GetBaseStructure(TEXT("Vector")); RotatorStruct = GetBaseStructure(TEXT("Rotator")); TransformStruct = GetBaseStructure(TEXT("Transform")); LinearColorStruct = GetBaseStructure(TEXT("LinearColor")); ColorStruct = GetBaseStructure(TEXT("Color")); } } bool UEdGraphSchema_K2::DoesFunctionHaveOutParameters( const UFunction* Function ) const { if ( Function != NULL ) { for ( TFieldIterator PropertyIt(Function); PropertyIt; ++PropertyIt ) { if ( PropertyIt->PropertyFlags & CPF_OutParm ) { return true; } } } return false; } bool UEdGraphSchema_K2::CanFunctionBeUsedInGraph(const UClass* InClass, const UFunction* InFunction, const UEdGraph* InDestGraph, uint32 InAllowedFunctionTypes, bool bInCalledForEach, FText* OutReason) const { if (CanUserKismetCallFunction(InFunction)) { bool bLatentFuncsAllowed = true; bool bIsConstructionScript = false; if(InDestGraph != nullptr) { bLatentFuncsAllowed = (GetGraphType(InDestGraph) == GT_Ubergraph || (GetGraphType(InDestGraph) == GT_Macro)); bIsConstructionScript = IsConstructionScript(InDestGraph); } const bool bIsPureFunc = (InFunction->HasAnyFunctionFlags(FUNC_BlueprintPure) != false); if (bIsPureFunc) { const bool bAllowPureFuncs = (InAllowedFunctionTypes & FT_Pure) != 0; if (!bAllowPureFuncs) { if(OutReason != nullptr) { *OutReason = LOCTEXT("PureFunctionsNotAllowed", "Pure functions are not allowed."); } return false; } } else { const bool bAllowImperativeFuncs = (InAllowedFunctionTypes & FT_Imperative) != 0; if (!bAllowImperativeFuncs) { if(OutReason != nullptr) { *OutReason = LOCTEXT("ImpureFunctionsNotAllowed", "Impure functions are not allowed."); } return false; } } const bool bIsConstFunc = (InFunction->HasAnyFunctionFlags(FUNC_Const) != false); const bool bAllowConstFuncs = (InAllowedFunctionTypes & FT_Const) != 0; if (bIsConstFunc && !bAllowConstFuncs) { if(OutReason != nullptr) { *OutReason = LOCTEXT("ConstFunctionsNotAllowed", "Const functions are not allowed."); } return false; } const bool bIsLatent = InFunction->HasMetaData(FBlueprintMetadata::MD_Latent); if (bIsLatent && !bLatentFuncsAllowed) { if(OutReason != nullptr) { *OutReason = LOCTEXT("LatentFunctionsNotAllowed", "Latent functions cannot be used here."); } return false; } const bool bIsProtected = InFunction->GetBoolMetaData(FBlueprintMetadata::MD_Protected); const bool bFuncBelongsToSubClass = InClass->IsChildOf(InFunction->GetOuterUClass()); if (bIsProtected) { const bool bAllowProtectedFuncs = (InAllowedFunctionTypes & FT_Protected) != 0; if (!bAllowProtectedFuncs) { if(OutReason != nullptr) { *OutReason = LOCTEXT("ProtectedFunctionsNotAllowed", "Protected functions are not allowed."); } return false; } if (!bFuncBelongsToSubClass) { if(OutReason != nullptr) { *OutReason = LOCTEXT("ProtectedFunctionInaccessible", "Function is protected and inaccessible."); } return false; } } const bool bIsPrivate = InFunction->GetBoolMetaData(FBlueprintMetadata::MD_Private); const bool bFuncBelongsToClass = bFuncBelongsToSubClass && (InFunction->GetOuterUClass() == InClass); if (bIsPrivate && !bFuncBelongsToClass) { if(OutReason != nullptr) { *OutReason = LOCTEXT("PrivateFunctionInaccessible", "Function is private and inaccessible."); } return false; } const bool bIsUnsafeForConstruction = InFunction->GetBoolMetaData(FBlueprintMetadata::MD_UnsafeForConstructionScripts); if (bIsUnsafeForConstruction && bIsConstructionScript) { if(OutReason != nullptr) { *OutReason = LOCTEXT("FunctionUnsafeForConstructionScript", "Function cannot be used in a Construction Script."); } return false; } const bool bRequiresWorldContext = InFunction->HasMetaData(FBlueprintMetadata::MD_WorldContext); if (bRequiresWorldContext) { if (InDestGraph && !InFunction->HasMetaData(FBlueprintMetadata::MD_CallableWithoutWorldContext)) { auto BP = FBlueprintEditorUtils::FindBlueprintForGraph(InDestGraph); const bool bIsFunctLib = BP && (EBlueprintType::BPTYPE_FunctionLibrary == BP->BlueprintType); UClass* ParentClass = BP ? BP->ParentClass : NULL; const bool bIncompatibleParrent = ParentClass && (!ParentClass->GetDefaultObject()->ImplementsGetWorld() && !ParentClass->HasMetaData(FBlueprintMetadata::MD_ShowWorldContextPin)); if (!bIsFunctLib && bIncompatibleParrent) { if(OutReason != nullptr) { *OutReason = LOCTEXT("FunctionRequiresWorldContext", "Function requires a world context."); } return false; } } } const bool bFunctionStatic = InFunction->HasAllFunctionFlags(FUNC_Static); const bool bHasReturnParams = (InFunction->GetReturnProperty() != NULL); const bool bHasArrayPointerParms = InFunction->HasMetaData(FBlueprintMetadata::MD_ArrayParam); const bool bAllowForEachCall = !bFunctionStatic && !bIsLatent && !bIsPureFunc && !bIsConstFunc && !bHasReturnParams && !bHasArrayPointerParms; if (bInCalledForEach && !bAllowForEachCall) { if(OutReason != nullptr) { if(bFunctionStatic) { *OutReason = LOCTEXT("StaticFunctionsNotAllowedInForEachContext", "Static functions cannot be used within a ForEach context."); } else if(bIsLatent) { *OutReason = LOCTEXT("LatentFunctionsNotAllowedInForEachContext", "Latent functions cannot be used within a ForEach context."); } else if(bIsPureFunc) { *OutReason = LOCTEXT("PureFunctionsNotAllowedInForEachContext", "Pure functions cannot be used within a ForEach context."); } else if(bIsConstFunc) { *OutReason = LOCTEXT("ConstFunctionsNotAllowedInForEachContext", "Const functions cannot be used within a ForEach context."); } else if(bHasReturnParams) { *OutReason = LOCTEXT("FunctionsWithReturnValueNotAllowedInForEachContext", "Functions that return a value cannot be used within a ForEach context."); } else if(bHasArrayPointerParms) { *OutReason = LOCTEXT("FunctionsWithArrayParmsNotAllowedInForEachContext", "Functions with array parameters cannot be used within a ForEach context."); } else { *OutReason = LOCTEXT("FunctionNotAllowedInForEachContext", "Function cannot be used within a ForEach context."); } } return false; } return true; } if(OutReason != nullptr) { *OutReason = LOCTEXT("FunctionInvalid", "Invalid function."); } return false; } UFunction* UEdGraphSchema_K2::GetCallableParentFunction(UFunction* Function) const { if( Function && Cast(Function->GetOuter()) ) { const FName FunctionName = Function->GetFName(); // Search up the parent scopes UClass* ParentClass = CastChecked(Function->GetOuter())->GetSuperClass(); UFunction* ClassFunction = ParentClass->FindFunctionByName(FunctionName); return ClassFunction; } return NULL; } bool UEdGraphSchema_K2::CanUserKismetCallFunction(const UFunction* Function) { return Function && (Function->HasAllFunctionFlags(FUNC_BlueprintCallable) && !Function->HasAllFunctionFlags(FUNC_Delegate) && !Function->GetBoolMetaData(FBlueprintMetadata::MD_BlueprintInternalUseOnly) && !Function->HasMetaData(FBlueprintMetadata::MD_DeprecatedFunction)); } bool UEdGraphSchema_K2::CanKismetOverrideFunction(const UFunction* Function) { return Function && (Function->HasAllFunctionFlags(FUNC_BlueprintEvent) && !Function->HasAllFunctionFlags(FUNC_Delegate) && !Function->GetBoolMetaData(FBlueprintMetadata::MD_BlueprintInternalUseOnly) && !Function->HasMetaData(FBlueprintMetadata::MD_DeprecatedFunction)); } bool UEdGraphSchema_K2::HasFunctionAnyOutputParameter(const UFunction* InFunction) { check(InFunction); for (TFieldIterator PropIt(InFunction); PropIt && (PropIt->PropertyFlags & CPF_Parm); ++PropIt) { UProperty* FuncParam = *PropIt; if (FuncParam->HasAnyPropertyFlags(CPF_ReturnParm) || (FuncParam->HasAnyPropertyFlags(CPF_OutParm) && !FuncParam->HasAnyPropertyFlags(CPF_ReferenceParm) && !FuncParam->HasAnyPropertyFlags(CPF_ConstParm))) { return true; } } return false; } bool UEdGraphSchema_K2::FunctionCanBePlacedAsEvent(const UFunction* InFunction) { // First check we are override-able, non-static and non-const if (!InFunction || !CanKismetOverrideFunction(InFunction) || InFunction->HasAnyFunctionFlags(FUNC_Static|FUNC_Const)) { return false; } // Then look to see if we have any output, return, or reference params return !HasFunctionAnyOutputParameter(InFunction); } bool UEdGraphSchema_K2::FunctionCanBeUsedInDelegate(const UFunction* InFunction) { if (!InFunction || !CanUserKismetCallFunction(InFunction) || InFunction->HasMetaData(FBlueprintMetadata::MD_Latent) || InFunction->HasAllFunctionFlags(FUNC_BlueprintPure)) { return false; } return true; } FText UEdGraphSchema_K2::GetFriendlySignatureName(const UFunction* Function) { return UK2Node_CallFunction::GetUserFacingFunctionName( Function ); } void UEdGraphSchema_K2::GetAutoEmitTermParameters(const UFunction* Function, TArray& AutoEmitParameterNames) const { AutoEmitParameterNames.Empty(); if( Function->HasMetaData(FBlueprintMetadata::MD_AutoCreateRefTerm) ) { FString MetaData = Function->GetMetaData(FBlueprintMetadata::MD_AutoCreateRefTerm); MetaData.ParseIntoArray(AutoEmitParameterNames, TEXT(","), true); } } bool UEdGraphSchema_K2::FunctionHasParamOfType(const UFunction* InFunction, UEdGraph const* InGraph, const FEdGraphPinType& DesiredPinType, bool bWantOutput) const { TSet HiddenPins; FBlueprintEditorUtils::GetHiddenPinsForFunction(InGraph, InFunction, HiddenPins); // Iterate over all params of function for (TFieldIterator PropIt(InFunction); PropIt && (PropIt->PropertyFlags & CPF_Parm); ++PropIt) { UProperty* FuncParam = *PropIt; // Ensure that this isn't a hidden parameter if (!HiddenPins.Contains(FuncParam->GetName())) { // See if this is the direction we want (input or output) const bool bIsFunctionInput = !FuncParam->HasAnyPropertyFlags(CPF_OutParm) || FuncParam->HasAnyPropertyFlags(CPF_ReferenceParm); if ((!bIsFunctionInput && bWantOutput) || (bIsFunctionInput && !bWantOutput)) { // See if this pin has compatible types FEdGraphPinType ParamPinType; bool bConverted = ConvertPropertyToPinType(FuncParam, ParamPinType); if (bConverted) { if (bIsFunctionInput && ArePinTypesCompatible(DesiredPinType, ParamPinType)) { return true; } else if (!bIsFunctionInput && ArePinTypesCompatible(ParamPinType, DesiredPinType)) { return true; } } } } } // Boo, no pin of this type return false; } void UEdGraphSchema_K2::AddExtraFunctionFlags(const UEdGraph* CurrentGraph, int32 ExtraFlags) const { for (auto It = CurrentGraph->Nodes.CreateConstIterator(); It; ++It) { if (UK2Node_FunctionEntry* Node = Cast(*It)) { Node->ExtraFlags |= ExtraFlags; } } } void UEdGraphSchema_K2::MarkFunctionEntryAsEditable(const UEdGraph* CurrentGraph, bool bNewEditable) const { for (auto It = CurrentGraph->Nodes.CreateConstIterator(); It; ++It) { if (UK2Node_EditablePinBase* Node = Cast(*It)) { Node->bIsEditable = bNewEditable; } } } void UEdGraphSchema_K2::ListFunctionsMatchingSignatureAsDelegates(FGraphContextMenuBuilder& ContextMenuBuilder, const UClass* Class, const UFunction* SignatureToMatch) const { check(Class); for (TFieldIterator FunctionIt(Class, EFieldIteratorFlags::IncludeSuper); FunctionIt; ++FunctionIt) { const UFunction* TrialFunction = *FunctionIt; if (CanUserKismetCallFunction(TrialFunction) && TrialFunction->IsSignatureCompatibleWith(SignatureToMatch)) { FString Description(TrialFunction->GetName()); FString Tooltip = FString::Printf(TEXT("Existing function '%s' as delegate"), *(TrialFunction->GetName())); //@TODO: Need a better tooltip // @TODO } } } bool UEdGraphSchema_K2::IsActorValidForLevelScriptRefs(const AActor* TestActor, const UBlueprint* Blueprint) const { check(Blueprint); return TestActor && FBlueprintEditorUtils::IsLevelScriptBlueprint(Blueprint) && (TestActor->GetLevel() == FBlueprintEditorUtils::GetLevelFromBlueprint(Blueprint)) && FKismetEditorUtilities::IsActorValidForLevelScript(TestActor); } void UEdGraphSchema_K2::ReplaceSelectedNode(UEdGraphNode* SourceNode, AActor* TargetActor) { check(SourceNode); if (TargetActor != NULL) { UK2Node_Literal* LiteralNode = (UK2Node_Literal*)(SourceNode); if (LiteralNode) { const FScopedTransaction Transaction( LOCTEXT("ReplaceSelectedNodeUndoTransaction", "Replace Selected Node") ); LiteralNode->Modify(); LiteralNode->SetObjectRef( TargetActor ); LiteralNode->ReconstructNode(); UBlueprint* Blueprint = FBlueprintEditorUtils::FindBlueprintForGraphChecked(CastChecked(SourceNode->GetOuter())); FBlueprintEditorUtils::MarkBlueprintAsModified(Blueprint); } } } void UEdGraphSchema_K2::AddSelectedReplaceableNodes( UBlueprint* Blueprint, const UEdGraphNode* InGraphNode, FMenuBuilder* MenuBuilder ) const { //Only allow replace object reference functionality for literal nodes const UK2Node_Literal* LiteralNode = Cast(InGraphNode); if (LiteralNode) { USelection* SelectedActors = GEditor->GetSelectedActors(); for(FSelectionIterator Iter(*SelectedActors); Iter; ++Iter) { // We only care about actors that are referenced in the world for literals, and also in the same level as this blueprint AActor* Actor = Cast(*Iter); if( LiteralNode->GetObjectRef() != Actor && IsActorValidForLevelScriptRefs(Actor, Blueprint) ) { FText Description = FText::Format( LOCTEXT("ChangeToActorName", "Change to <{0}>"), FText::FromString( Actor->GetActorLabel() ) ); FText ToolTip = LOCTEXT("ReplaceNodeReferenceToolTip", "Replace node reference"); MenuBuilder->AddMenuEntry( Description, ToolTip, FSlateIcon(), FUIAction( FExecuteAction::CreateUObject((UEdGraphSchema_K2*const)this, &UEdGraphSchema_K2::ReplaceSelectedNode, const_cast< UEdGraphNode* >(InGraphNode), Actor) ) ); } } } } bool UEdGraphSchema_K2::CanUserKismetAccessVariable(const UProperty* Property, const UClass* InClass, EDelegateFilterMode FilterMode) { const bool bIsDelegate = Property->IsA(UMulticastDelegateProperty::StaticClass()); const bool bIsAccessible = Property->HasAllPropertyFlags(CPF_BlueprintVisible); const bool bIsAssignableOrCallable = Property->HasAnyPropertyFlags(CPF_BlueprintAssignable | CPF_BlueprintCallable); const bool bPassesDelegateFilter = (bIsAccessible && !bIsDelegate && (FilterMode != MustBeDelegate)) || (bIsAssignableOrCallable && bIsDelegate && (FilterMode != CannotBeDelegate)); const bool bHidden = FObjectEditorUtils::IsVariableCategoryHiddenFromClass(Property, InClass); return !Property->HasAnyPropertyFlags(CPF_Parm) && bPassesDelegateFilter && !bHidden; } bool UEdGraphSchema_K2::ClassHasBlueprintAccessibleMembers(const UClass* InClass) const { // @TODO Don't show other blueprints yet... UBlueprint* ClassBlueprint = UBlueprint::GetBlueprintFromClass(InClass); if (!InClass->HasAnyClassFlags(CLASS_Deprecated | CLASS_NewerVersionExists) && (ClassBlueprint == NULL)) { // Find functions for (TFieldIterator FunctionIt(InClass, EFieldIteratorFlags::IncludeSuper); FunctionIt; ++FunctionIt) { UFunction* Function = *FunctionIt; const bool bIsBlueprintProtected = Function->GetBoolMetaData(FBlueprintMetadata::MD_Protected); const bool bHidden = FObjectEditorUtils::IsFunctionHiddenFromClass(Function, InClass); if (UEdGraphSchema_K2::CanUserKismetCallFunction(Function) && !bIsBlueprintProtected && !bHidden) { return true; } } // Find vars for (TFieldIterator PropertyIt(InClass, EFieldIteratorFlags::IncludeSuper); PropertyIt; ++PropertyIt) { UProperty* Property = *PropertyIt; if (CanUserKismetAccessVariable(Property, InClass, CannotBeDelegate)) { return true; } } } return false; } bool UEdGraphSchema_K2::IsAllowableBlueprintVariableType(const class UEnum* InEnum) { return InEnum && (InEnum->GetBoolMetaData(FBlueprintMetadata::MD_AllowableBlueprintVariableType) || InEnum->IsA()); } bool UEdGraphSchema_K2::IsAllowableBlueprintVariableType(const class UClass* InClass) { if (InClass) { // No Skeleton classes or reinstancing classes (they would inherit the BlueprintType metadata) if (FKismetEditorUtilities::IsClassABlueprintSkeleton(InClass) || InClass->HasAnyClassFlags(CLASS_NewerVersionExists)) { return false; } // No Blueprint Macro Libraries if (FKismetEditorUtilities::IsClassABlueprintMacroLibrary(InClass)) { return false; } // UObject is an exception, and is always a blueprint-able type if(InClass == UObject::StaticClass()) { return true; } static const FBoolConfigValueHelper NotBlueprintType(TEXT("EditoronlyBP"), TEXT("bBlueprintIsNotBlueprintType")); if (NotBlueprintType && InClass->IsChildOf(UBlueprint::StaticClass())) { return false; } // cannot have level script variables if (InClass->IsChildOf(ALevelScriptActor::StaticClass())) { return false; } const UClass* ParentClass = InClass; while(ParentClass) { // Climb up the class hierarchy and look for "BlueprintType" and "NotBlueprintType" to see if this class is allowed. if(ParentClass->GetBoolMetaData(FBlueprintMetadata::MD_AllowableBlueprintVariableType) || ParentClass->HasMetaData(FBlueprintMetadata::MD_BlueprintSpawnableComponent)) { return true; } else if(ParentClass->GetBoolMetaData(FBlueprintMetadata::MD_NotAllowableBlueprintVariableType)) { return false; } ParentClass = ParentClass->GetSuperClass(); } } return false; } bool UEdGraphSchema_K2::IsAllowableBlueprintVariableType(const class UScriptStruct *InStruct) { if (auto UDStruct = Cast(InStruct)) { if (EUserDefinedStructureStatus::UDSS_UpToDate != UDStruct->Status.GetValue()) { return false; } } return InStruct && (InStruct->GetBoolMetaDataHierarchical(FBlueprintMetadata::MD_AllowableBlueprintVariableType)); } bool UEdGraphSchema_K2::DoesGraphSupportImpureFunctions(const UEdGraph* InGraph) const { const EGraphType GraphType = GetGraphType(InGraph); const bool bAllowImpureFuncs = GraphType != GT_Animation; //@TODO: It's really more nuanced than this (e.g., in a function someone wants to write as pure) return bAllowImpureFuncs; } bool UEdGraphSchema_K2::IsPropertyExposedOnSpawn(const UProperty* Property) { if (Property) { const bool bMeta = Property->HasMetaData(FBlueprintMetadata::MD_ExposeOnSpawn); const bool bFlag = Property->HasAllPropertyFlags(CPF_ExposeOnSpawn); if (bMeta != bFlag) { UE_LOG(LogBlueprint, Warning , TEXT("ExposeOnSpawn ambiguity. Property '%s', MetaData '%s', Flag '%s'") , *Property->GetFullName() , bMeta ? *GTrue.ToString() : *GFalse.ToString() , bFlag ? *GTrue.ToString() : *GFalse.ToString()); } return bMeta || bFlag; } return false; } // if node is a get/set variable and the variable it refers to does not exist static bool IsUsingNonExistantVariable(const UEdGraphNode* InGraphNode, UBlueprint* OwnerBlueprint) { bool bNonExistantVariable = false; const bool bBreakOrMakeStruct = InGraphNode->IsA(UK2Node_BreakStruct::StaticClass()) || InGraphNode->IsA(UK2Node_MakeStruct::StaticClass()); if (!bBreakOrMakeStruct) { if (const UK2Node_Variable* Variable = Cast(InGraphNode)) { if (Variable->VariableReference.IsSelfContext()) { TArray CurrentVars; FBlueprintEditorUtils::GetClassVariableList(OwnerBlueprint, CurrentVars); if ( false == CurrentVars.Contains(Variable->GetVarName()) ) { bNonExistantVariable = true; } } else if(Variable->VariableReference.IsLocalScope()) { // If there is no member scope, or we can't find the local variable in the member scope, then it's non-existant UStruct* MemberScope = Variable->VariableReference.GetMemberScope(Variable->GetBlueprintClassFromNode()); if (MemberScope == nullptr || !FBlueprintEditorUtils::FindLocalVariable(OwnerBlueprint, MemberScope, Variable->GetVarName())) { bNonExistantVariable = true; } } } } return bNonExistantVariable; } bool UEdGraphSchema_K2::PinHasSplittableStructType(const UEdGraphPin* InGraphPin) const { const FEdGraphPinType& PinType = InGraphPin->PinType; bool bCanSplit = (!PinType.bIsArray && PinType.PinCategory == PC_Struct); if (bCanSplit) { UScriptStruct* StructType = CastChecked(InGraphPin->PinType.PinSubCategoryObject.Get()); if (InGraphPin->Direction == EGPD_Input) { bCanSplit = UK2Node_MakeStruct::CanBeMade(StructType); if (!bCanSplit) { const FString& MetaData = StructType->GetMetaData(TEXT("HasNativeMake")); UFunction* Function = FindObject(NULL, *MetaData, true); bCanSplit = (Function != NULL); } } else { bCanSplit = UK2Node_BreakStruct::CanBeBroken(StructType); if (!bCanSplit) { const FString& MetaData = StructType->GetMetaData(TEXT("HasNativeBreak")); UFunction* Function = FindObject(NULL, *MetaData, true); bCanSplit = (Function != NULL); } } } return bCanSplit; } bool UEdGraphSchema_K2::PinDefaultValueIsEditable(const UEdGraphPin& InGraphPin) const { // Array types are not currently assignable without a 'make array' node: if( InGraphPin.PinType.bIsArray ) { return false; } // User defined structures (from code or from data) cannot accept default values: if( InGraphPin.PinType.PinCategory == PC_Struct ) { // Only the built in struct types are editable as 'default' values on a pin. // See FNodeFactory::CreatePinWidget for justification of the above statement! UObject const& SubCategoryObject = *InGraphPin.PinType.PinSubCategoryObject; return &SubCategoryObject == VectorStruct || &SubCategoryObject == RotatorStruct || &SubCategoryObject == TransformStruct || &SubCategoryObject == LinearColorStruct || &SubCategoryObject == ColorStruct || &SubCategoryObject == FCollisionProfileName::StaticStruct(); } return true; } void UEdGraphSchema_K2::GetContextMenuActions(const UEdGraph* CurrentGraph, const UEdGraphNode* InGraphNode, const UEdGraphPin* InGraphPin, FMenuBuilder* MenuBuilder, bool bIsDebugging) const { check(CurrentGraph); UBlueprint* OwnerBlueprint = FBlueprintEditorUtils::FindBlueprintForGraphChecked(CurrentGraph); if (InGraphPin != NULL) { MenuBuilder->BeginSection("EdGraphSchemaPinActions", LOCTEXT("PinActionsMenuHeader", "Pin Actions")); { if (!bIsDebugging) { // Break pin links if (InGraphPin->LinkedTo.Num() > 1) { MenuBuilder->AddMenuEntry( FGraphEditorCommands::Get().BreakPinLinks ); } // Add the change pin type action, if this is a select node if (InGraphNode->IsA(UK2Node_Select::StaticClass())) { MenuBuilder->AddMenuEntry(FGraphEditorCommands::Get().ChangePinType); } // add sub menu for break link to if (InGraphPin->LinkedTo.Num() > 0) { if(InGraphPin->LinkedTo.Num() > 1) { MenuBuilder->AddSubMenu( LOCTEXT("BreakLinkTo", "Break Link To..."), LOCTEXT("BreakSpecificLinks", "Break a specific link..."), FNewMenuDelegate::CreateUObject( (UEdGraphSchema_K2*const)this, &UEdGraphSchema_K2::GetBreakLinkToSubMenuActions, const_cast(InGraphPin))); MenuBuilder->AddSubMenu( LOCTEXT("JumpToConnection", "Jump to Connection..."), LOCTEXT("JumpToSpecificConnection", "Jump to specific connection..."), FNewMenuDelegate::CreateUObject( (UEdGraphSchema_K2*const)this, &UEdGraphSchema_K2::GetJumpToConnectionSubMenuActions, const_cast(InGraphPin))); } else { ((UEdGraphSchema_K2*const)this)->GetBreakLinkToSubMenuActions(*MenuBuilder, const_cast(InGraphPin)); ((UEdGraphSchema_K2*const)this)->GetJumpToConnectionSubMenuActions(*MenuBuilder, const_cast(InGraphPin)); } } // Conditionally add the var promotion pin if this is an output pin and it's not an exec pin if (InGraphPin->PinType.PinCategory != PC_Exec) { MenuBuilder->AddMenuEntry( FGraphEditorCommands::Get().PromoteToVariable ); } if (PinHasSplittableStructType(InGraphPin) && InGraphNode->AllowSplitPins()) { MenuBuilder->AddMenuEntry( FGraphEditorCommands::Get().SplitStructPin ); } if (InGraphPin->ParentPin != NULL) { MenuBuilder->AddMenuEntry( FGraphEditorCommands::Get().RecombineStructPin ); } // Conditionally add the execution path pin removal if this is an execution branching node if( InGraphPin->Direction == EGPD_Output && InGraphPin->GetOwningNode()) { if (CastChecked(InGraphPin->GetOwningNode())->CanEverRemoveExecutionPin()) { MenuBuilder->AddMenuEntry( FGraphEditorCommands::Get().RemoveExecutionPin ); } } if (UK2Node_SetFieldsInStruct::ShowCustomPinActions(InGraphPin, true)) { MenuBuilder->AddMenuEntry(FGraphEditorCommands::Get().RemoveThisStructVarPin); MenuBuilder->AddMenuEntry(FGraphEditorCommands::Get().RemoveOtherStructVarPins); } } } MenuBuilder->EndSection(); // Add the watch pin / unwatch pin menu items MenuBuilder->BeginSection("EdGraphSchemaWatches", LOCTEXT("WatchesHeader", "Watches")); { if (!IsMetaPin(*InGraphPin)) { const UEdGraphPin* WatchedPin = ((InGraphPin->Direction == EGPD_Input) && (InGraphPin->LinkedTo.Num() > 0)) ? InGraphPin->LinkedTo[0] : InGraphPin; if (FKismetDebugUtilities::IsPinBeingWatched(OwnerBlueprint, WatchedPin)) { MenuBuilder->AddMenuEntry( FGraphEditorCommands::Get().StopWatchingPin ); } else { MenuBuilder->AddMenuEntry( FGraphEditorCommands::Get().StartWatchingPin ); } } } MenuBuilder->EndSection(); } else if (InGraphNode != NULL) { if (IsUsingNonExistantVariable(InGraphNode, OwnerBlueprint)) { MenuBuilder->BeginSection("EdGraphSchemaNodeActions", LOCTEXT("NodeActionsMenuHeader", "Node Actions")); { GetNonExistentVariableMenu(InGraphNode,OwnerBlueprint, MenuBuilder); } MenuBuilder->EndSection(); } else { MenuBuilder->BeginSection("EdGraphSchemaNodeActions", LOCTEXT("NodeActionsMenuHeader", "Node Actions")); { if (!bIsDebugging) { // Replaceable node display option AddSelectedReplaceableNodes( OwnerBlueprint, InGraphNode, MenuBuilder ); // Node contextual actions MenuBuilder->AddMenuEntry( FGenericCommands::Get().Delete ); MenuBuilder->AddMenuEntry( FGenericCommands::Get().Cut ); MenuBuilder->AddMenuEntry( FGenericCommands::Get().Copy ); MenuBuilder->AddMenuEntry( FGenericCommands::Get().Duplicate ); MenuBuilder->AddMenuEntry( FGraphEditorCommands::Get().ReconstructNodes ); MenuBuilder->AddMenuEntry( FGraphEditorCommands::Get().BreakNodeLinks ); // Conditionally add the action to add an execution pin, if this is an execution node if( InGraphNode->IsA(UK2Node_ExecutionSequence::StaticClass()) || InGraphNode->IsA(UK2Node_Switch::StaticClass()) ) { MenuBuilder->AddMenuEntry( FGraphEditorCommands::Get().AddExecutionPin ); } // Conditionally add the action to create a super function call node, if this is an event or function entry if( InGraphNode->IsA(UK2Node_Event::StaticClass()) || InGraphNode->IsA(UK2Node_FunctionEntry::StaticClass()) ) { MenuBuilder->AddMenuEntry( FGraphEditorCommands::Get().AddParentNode ); } // Conditionally add the actions to add or remove an option pin, if this is a select node if (InGraphNode->IsA(UK2Node_Select::StaticClass())) { MenuBuilder->AddMenuEntry(FGraphEditorCommands::Get().AddOptionPin); MenuBuilder->AddMenuEntry(FGraphEditorCommands::Get().RemoveOptionPin); } // Conditionally add the action to find instances of the node if it is a custom event if (InGraphNode->IsA(UK2Node_CustomEvent::StaticClass())) { MenuBuilder->AddMenuEntry(FGraphEditorCommands::Get().FindInstancesOfCustomEvent); } // Don't show the "Assign selected Actor" option if more than one actor is selected if (InGraphNode->IsA(UK2Node_ActorBoundEvent::StaticClass()) && GEditor->GetSelectedActorCount() == 1) { MenuBuilder->AddMenuEntry(FGraphEditorCommands::Get().AssignReferencedActor); } // Add the goto source code action for native functions if (InGraphNode->IsA(UK2Node_CallFunction::StaticClass())) { const UEdGraphNode* ResultEventNode= NULL; if(Cast(InGraphNode)->GetFunctionGraph(ResultEventNode)) { MenuBuilder->AddMenuEntry(FGraphEditorCommands::Get().GoToDefinition); } else { MenuBuilder->AddMenuEntry(FGraphEditorCommands::Get().GotoNativeFunctionDefinition); } } // Functions, macros, and composite nodes support going to a definition if (InGraphNode->IsA(UK2Node_MacroInstance::StaticClass()) || InGraphNode->IsA(UK2Node_Composite::StaticClass())) { MenuBuilder->AddMenuEntry(FGraphEditorCommands::Get().GoToDefinition); } // show search for references for variable nodes and goto source code action if (InGraphNode->IsA(UK2Node_Variable::StaticClass())) { MenuBuilder->AddMenuEntry(FGraphEditorCommands::Get().FindVariableReferences); MenuBuilder->AddMenuEntry(FGraphEditorCommands::Get().GotoNativeVariableDefinition); GetReplaceVariableMenu(InGraphNode,OwnerBlueprint, MenuBuilder, true); } if (InGraphNode->IsA(UK2Node_SetFieldsInStruct::StaticClass())) { MenuBuilder->AddMenuEntry(FGraphEditorCommands::Get().RestoreAllStructVarPins); } MenuBuilder->AddMenuEntry(FGenericCommands::Get().Rename, NAME_None, LOCTEXT("Rename", "Rename"), LOCTEXT("Rename_Tooltip", "Renames selected function or variable in blueprint.") ); } // Select referenced actors in the level MenuBuilder->AddMenuEntry( FGraphEditorCommands::Get().SelectReferenceInLevel ); } MenuBuilder->EndSection(); //EdGraphSchemaNodeActions if (!bIsDebugging) { // Collapse/expand nodes MenuBuilder->BeginSection("EdGraphSchemaOrganization", LOCTEXT("OrganizationHeader", "Organization")); { MenuBuilder->AddMenuEntry( FGraphEditorCommands::Get().CollapseNodes ); MenuBuilder->AddMenuEntry( FGraphEditorCommands::Get().CollapseSelectionToFunction ); MenuBuilder->AddMenuEntry( FGraphEditorCommands::Get().CollapseSelectionToMacro ); MenuBuilder->AddMenuEntry( FGraphEditorCommands::Get().ExpandNodes ); if(InGraphNode->IsA(UK2Node_Composite::StaticClass())) { MenuBuilder->AddMenuEntry( FGraphEditorCommands::Get().PromoteSelectionToFunction ); MenuBuilder->AddMenuEntry( FGraphEditorCommands::Get().PromoteSelectionToMacro ); } } MenuBuilder->EndSection(); } // Add breakpoint actions if (const UK2Node* K2Node = Cast(InGraphNode)) { if (!K2Node->IsNodePure()) { MenuBuilder->BeginSection("EdGraphSchemaBreakpoints", LOCTEXT("BreakpointsHeader", "Breakpoints")); { MenuBuilder->AddMenuEntry( FGraphEditorCommands::Get().ToggleBreakpoint ); MenuBuilder->AddMenuEntry( FGraphEditorCommands::Get().AddBreakpoint ); MenuBuilder->AddMenuEntry( FGraphEditorCommands::Get().RemoveBreakpoint ); MenuBuilder->AddMenuEntry( FGraphEditorCommands::Get().EnableBreakpoint ); MenuBuilder->AddMenuEntry( FGraphEditorCommands::Get().DisableBreakpoint ); } MenuBuilder->EndSection(); } } MenuBuilder->BeginSection("EdGraphSchemaDocumentation", LOCTEXT("DocumentationHeader", "Documentation")); { MenuBuilder->AddMenuEntry( FGraphEditorCommands::Get().GoToDocumentation ); } MenuBuilder->EndSection(); } } Super::GetContextMenuActions(CurrentGraph, InGraphNode, InGraphPin, MenuBuilder, bIsDebugging); } void UEdGraphSchema_K2::OnCreateNonExistentVariable( UK2Node_Variable* Variable, UBlueprint* OwnerBlueprint) { if (UEdGraphPin* Pin = Variable->FindPin(Variable->GetVarNameString())) { const FScopedTransaction Transaction( LOCTEXT("CreateMissingVariable", "Create Missing Variable") ); if (FBlueprintEditorUtils::AddMemberVariable(OwnerBlueprint,Variable->GetVarName(), Pin->PinType)) { Variable->VariableReference.SetSelfMember( Variable->GetVarName() ); } } } void UEdGraphSchema_K2::OnCreateNonExistentLocalVariable( UK2Node_Variable* Variable, UBlueprint* OwnerBlueprint) { if (UEdGraphPin* Pin = Variable->FindPin(Variable->GetVarNameString())) { const FScopedTransaction Transaction( LOCTEXT("CreateMissingLocalVariable", "Create Missing Local Variable") ); FName VarName = Variable->GetVarName(); if (FBlueprintEditorUtils::AddLocalVariable(OwnerBlueprint, Variable->GetGraph(), VarName, Pin->PinType)) { FGuid LocalVarGuid = FBlueprintEditorUtils::FindLocalVariableGuidByName(OwnerBlueprint, Variable->GetGraph(), VarName); if (LocalVarGuid.IsValid()) { Variable->VariableReference.SetLocalMember(VarName, Variable->GetGraph()->GetName(), LocalVarGuid); } } } } void UEdGraphSchema_K2::OnReplaceVariableForVariableNode( UK2Node_Variable* Variable, UBlueprint* OwnerBlueprint, FString VariableName, bool bIsSelfMember) { if(UEdGraphPin* Pin = Variable->FindPin(Variable->GetVarNameString())) { const FScopedTransaction Transaction( NSLOCTEXT("UnrealEd", "GraphEd_ReplaceVariable", "Replace Variable") ); Variable->Modify(); Pin->Modify(); if (bIsSelfMember) { Variable->VariableReference.SetSelfMember( FName(*VariableName) ); } else { UEdGraph* FunctionGraph = FBlueprintEditorUtils::GetTopLevelGraph(Variable->GetGraph()); Variable->VariableReference.SetLocalMember( FName(*VariableName), FunctionGraph->GetName(), FBlueprintEditorUtils::FindLocalVariableGuidByName(OwnerBlueprint, FunctionGraph, *VariableName)); } Pin->PinName = VariableName; Variable->ReconstructNode(); } } void UEdGraphSchema_K2::GetReplaceVariableMenu(FMenuBuilder& MenuBuilder, UK2Node_Variable* Variable, UBlueprint* OwnerBlueprint, bool bReplaceExistingVariable/*=false*/) { if (UEdGraphPin* Pin = Variable->FindPin(Variable->GetVarNameString())) { FName ExistingVariableName = bReplaceExistingVariable? Variable->GetVarName() : NAME_None; FText ReplaceVariableWithTooltipFormat; if(!bReplaceExistingVariable) { ReplaceVariableWithTooltipFormat = LOCTEXT("ReplaceNonExistantVarToolTip", "Variable '{OldVariable}' does not exist, replace with matching variable '{AlternateVariable}'?"); } else { ReplaceVariableWithTooltipFormat = LOCTEXT("ReplaceExistantVarToolTip", "Replace Variable '{OldVariable}' with matching variable '{AlternateVariable}'?"); } TArray Variables; FBlueprintEditorUtils::GetNewVariablesOfType(OwnerBlueprint, Pin->PinType, Variables); MenuBuilder.BeginSection(NAME_None, LOCTEXT("Variables", "Variables")); for (TArray::TIterator VarIt(Variables); VarIt; ++VarIt) { if(*VarIt != ExistingVariableName) { const FText AlternativeVar = FText::FromName( *VarIt ); FFormatNamedArguments TooltipArgs; TooltipArgs.Add(TEXT("OldVariable"), Variable->GetVarNameText()); TooltipArgs.Add(TEXT("AlternateVariable"), AlternativeVar); const FText Desc = FText::Format(ReplaceVariableWithTooltipFormat, TooltipArgs); MenuBuilder.AddMenuEntry( AlternativeVar, Desc, FSlateIcon(), FUIAction( FExecuteAction::CreateStatic(&UEdGraphSchema_K2::OnReplaceVariableForVariableNode, const_cast(Variable),OwnerBlueprint, (*VarIt).ToString(), /*bIsSelfMember=*/true ) ) ); } } MenuBuilder.EndSection(); FText ReplaceLocalVariableWithTooltipFormat; if(!bReplaceExistingVariable) { ReplaceLocalVariableWithTooltipFormat = LOCTEXT("ReplaceNonExistantLocalVarToolTip", "Variable '{OldVariable}' does not exist, replace with matching local variable '{AlternateVariable}'?"); } else { ReplaceLocalVariableWithTooltipFormat = LOCTEXT("ReplaceExistantLocalVarToolTip", "Replace Variable '{OldVariable}' with matching local variable '{AlternateVariable}'?"); } TArray LocalVariables; FBlueprintEditorUtils::GetLocalVariablesOfType(Variable->GetGraph(), Pin->PinType, LocalVariables); MenuBuilder.BeginSection(NAME_None, LOCTEXT("LocalVariables", "LocalVariables")); for (TArray::TIterator VarIt(LocalVariables); VarIt; ++VarIt) { if(*VarIt != ExistingVariableName) { const FText AlternativeVar = FText::FromName( *VarIt ); FFormatNamedArguments TooltipArgs; TooltipArgs.Add(TEXT("OldVariable"), Variable->GetVarNameText()); TooltipArgs.Add(TEXT("AlternateVariable"), AlternativeVar); const FText Desc = FText::Format( ReplaceLocalVariableWithTooltipFormat, TooltipArgs ); MenuBuilder.AddMenuEntry( AlternativeVar, Desc, FSlateIcon(), FUIAction( FExecuteAction::CreateStatic(&UEdGraphSchema_K2::OnReplaceVariableForVariableNode, const_cast(Variable),OwnerBlueprint, (*VarIt).ToString(), /*bIsSelfMember=*/false ) ) ); } } MenuBuilder.EndSection(); } } void UEdGraphSchema_K2::GetNonExistentVariableMenu( const UEdGraphNode* InGraphNode, UBlueprint* OwnerBlueprint, FMenuBuilder* MenuBuilder ) const { if (const UK2Node_Variable* Variable = Cast(InGraphNode)) { // Creating missing variables should never occur in a Macro Library or Interface, they do not support variables if(OwnerBlueprint->BlueprintType != BPTYPE_MacroLibrary && OwnerBlueprint->BlueprintType != BPTYPE_Interface ) { // Creating missing member variables should never occur in a Function Library, they do not support variables if(OwnerBlueprint->BlueprintType != BPTYPE_FunctionLibrary) { // create missing variable const FText Label = FText::Format( LOCTEXT("CreateNonExistentVar", "Create variable '{0}'"), Variable->GetVarNameText()); const FText Desc = FText::Format( LOCTEXT("CreateNonExistentVarToolTip", "Variable '{0}' does not exist, create it?"), Variable->GetVarNameText()); MenuBuilder->AddMenuEntry( Label, Desc, FSlateIcon(), FUIAction( FExecuteAction::CreateStatic( &UEdGraphSchema_K2::OnCreateNonExistentVariable, const_cast(Variable),OwnerBlueprint) ) ); } // Only allow creating missing local variables if in a function graph if(InGraphNode->GetGraph()->GetSchema()->GetGraphType(InGraphNode->GetGraph()) == GT_Function) { const FText Label = FText::Format( LOCTEXT("CreateNonExistentLocalVar", "Create local variable '{0}'"), Variable->GetVarNameText()); const FText Desc = FText::Format( LOCTEXT("CreateNonExistentLocalVarToolTip", "Local variable '{0}' does not exist, create it?"), Variable->GetVarNameText()); MenuBuilder->AddMenuEntry( Label, Desc, FSlateIcon(), FUIAction( FExecuteAction::CreateStatic( &UEdGraphSchema_K2::OnCreateNonExistentLocalVariable, const_cast(Variable),OwnerBlueprint) ) ); } } // delete this node { const FText Desc = FText::Format( LOCTEXT("DeleteNonExistentVarToolTip", "Referenced variable '{0}' does not exist, delete this node?"), Variable->GetVarNameText()); MenuBuilder->AddMenuEntry( FGenericCommands::Get().Delete, NAME_None, FGenericCommands::Get().Delete->GetLabel(), Desc); } GetReplaceVariableMenu(InGraphNode, OwnerBlueprint, MenuBuilder); } } void UEdGraphSchema_K2::GetReplaceVariableMenu(const UEdGraphNode* InGraphNode, UBlueprint* InOwnerBlueprint, FMenuBuilder* InMenuBuilder, bool bInReplaceExistingVariable/* = false*/) const { if (const UK2Node_Variable* Variable = Cast(InGraphNode)) { // replace with matching variables if (UEdGraphPin* Pin = Variable->FindPin(Variable->GetVarNameString())) { FName ExistingVariableName = bInReplaceExistingVariable? Variable->GetVarName() : NAME_None; TArray Variables; FBlueprintEditorUtils::GetNewVariablesOfType(InOwnerBlueprint, Pin->PinType, Variables); Variables.RemoveSwap(ExistingVariableName); TArray LocalVariables; FBlueprintEditorUtils::GetLocalVariablesOfType(Variable->GetGraph(), Pin->PinType, LocalVariables); LocalVariables.RemoveSwap(ExistingVariableName); if (Variables.Num() > 0 || LocalVariables.Num() > 0) { FText ReplaceVariableWithTooltip; if(bInReplaceExistingVariable) { ReplaceVariableWithTooltip = LOCTEXT("ReplaceVariableWithToolTip", "Replace Variable '{0}' with another variable?"); } else { ReplaceVariableWithTooltip = LOCTEXT("ReplaceMissingVariableWithToolTip", "Variable '{0}' does not exist, replace with another variable?"); } InMenuBuilder->AddSubMenu( FText::Format( LOCTEXT("ReplaceVariableWith", "Replace variable '{0}' with..."), Variable->GetVarNameText()), FText::Format( ReplaceVariableWithTooltip, Variable->GetVarNameText()), FNewMenuDelegate::CreateStatic( &UEdGraphSchema_K2::GetReplaceVariableMenu, const_cast(Variable),InOwnerBlueprint, bInReplaceExistingVariable)); } } } } void UEdGraphSchema_K2::GetBreakLinkToSubMenuActions( class FMenuBuilder& MenuBuilder, UEdGraphPin* InGraphPin ) { // Make sure we have a unique name for every entry in the list TMap< FString, uint32 > LinkTitleCount; // Add all the links we could break from for(TArray::TConstIterator Links(InGraphPin->LinkedTo); Links; ++Links) { UEdGraphPin* Pin = *Links; FText Title = Pin->GetOwningNode()->GetNodeTitle(ENodeTitleType::ListView); FString TitleString = Title.ToString(); if ( Pin->PinName != TEXT("") ) { TitleString = FString::Printf(TEXT("%s (%s)"), *TitleString, *Pin->GetDisplayName().ToString()); // Add name of connection if possible FFormatNamedArguments Args; Args.Add( TEXT("NodeTitle"), Title ); Args.Add( TEXT("PinName"), Pin->GetDisplayName() ); Title = FText::Format( LOCTEXT("BreakDescPin", "{NodeTitle} ({PinName})"), Args ); } uint32 &Count = LinkTitleCount.FindOrAdd( TitleString ); FText Description; FFormatNamedArguments Args; Args.Add( TEXT("NodeTitle"), Title ); Args.Add( TEXT("NumberOfNodes"), Count ); if ( Count == 0 ) { Description = FText::Format( LOCTEXT("BreakDesc", "Break link to {NodeTitle}"), Args ); } else { Description = FText::Format( LOCTEXT("BreakDescMulti", "Break link to {NodeTitle} ({NumberOfNodes})"), Args ); } ++Count; MenuBuilder.AddMenuEntry( Description, Description, FSlateIcon(), FUIAction( FExecuteAction::CreateUObject(this, &UEdGraphSchema_K2::BreakSinglePinLink, const_cast< UEdGraphPin* >(InGraphPin), *Links))); } } void UEdGraphSchema_K2::GetJumpToConnectionSubMenuActions( class FMenuBuilder& MenuBuilder, UEdGraphPin* InGraphPin ) { // Make sure we have a unique name for every entry in the list TMap< FString, uint32 > LinkTitleCount; // Add all the links we could break from for(auto PinLink : InGraphPin->LinkedTo ) { FText Title = PinLink->GetOwningNode()->GetNodeTitle(ENodeTitleType::ListView); FString TitleString = Title.ToString(); if ( PinLink->PinName != TEXT("") ) { TitleString = FString::Printf(TEXT("%s (%s)"), *TitleString, *PinLink->GetDisplayName().ToString()); // Add name of connection if possible FFormatNamedArguments Args; Args.Add( TEXT("NodeTitle"), Title ); Args.Add( TEXT("PinName"), PinLink->GetDisplayName() ); Title = FText::Format( LOCTEXT("JumpToDescPin", "{NodeTitle} ({PinName})"), Args ); } uint32 &Count = LinkTitleCount.FindOrAdd( TitleString ); FText Description; FFormatNamedArguments Args; Args.Add( TEXT("NodeTitle"), Title ); Args.Add( TEXT("NumberOfNodes"), Count ); if ( Count == 0 ) { Description = FText::Format( LOCTEXT("JumpDesc", "Jump to {NodeTitle}"), Args ); } else { Description = FText::Format( LOCTEXT("JumpDescMulti", "Jump to {NodeTitle} ({NumberOfNodes})"), Args ); } ++Count; MenuBuilder.AddMenuEntry( Description, Description, FSlateIcon(), FUIAction( FExecuteAction::CreateStatic(&FKismetEditorUtilities::BringKismetToFocusAttentionOnObject, Cast(PinLink), false))); } } const FPinConnectionResponse UEdGraphSchema_K2::DetermineConnectionResponseOfCompatibleTypedPins(const UEdGraphPin* PinA, const UEdGraphPin* PinB, const UEdGraphPin* InputPin, const UEdGraphPin* OutputPin) const { // Now check to see if there are already connections and this is an 'exclusive' connection const bool bBreakExistingDueToExecOutput = IsExecPin(*OutputPin) && (OutputPin->LinkedTo.Num() > 0); const bool bBreakExistingDueToDataInput = !IsExecPin(*InputPin) && (InputPin->LinkedTo.Num() > 0); bool bMultipleSelfException = false; const UK2Node* OwningNode = Cast(InputPin->GetOwningNode()); if (bBreakExistingDueToDataInput && IsSelfPin(*InputPin) && OwningNode && OwningNode->AllowMultipleSelfs(false) && !InputPin->PinType.bIsArray && !OutputPin->PinType.bIsArray) { //check if the node wont be expanded as foreach call, if there is a link to an array bool bAnyArrayInput = false; for(int InputLinkIndex = 0; InputLinkIndex < InputPin->LinkedTo.Num(); InputLinkIndex++) { if(const UEdGraphPin* Pin = InputPin->LinkedTo[InputLinkIndex]) { if(Pin->PinType.bIsArray) { bAnyArrayInput = true; break; } } } bMultipleSelfException = !bAnyArrayInput; } if (bBreakExistingDueToExecOutput) { const ECanCreateConnectionResponse ReplyBreakOutputs = (PinA == OutputPin) ? CONNECT_RESPONSE_BREAK_OTHERS_A : CONNECT_RESPONSE_BREAK_OTHERS_B; return FPinConnectionResponse(ReplyBreakOutputs, TEXT("Replace existing output connections")); } else if (bBreakExistingDueToDataInput && !bMultipleSelfException) { const ECanCreateConnectionResponse ReplyBreakInputs = (PinA == InputPin) ? CONNECT_RESPONSE_BREAK_OTHERS_A : CONNECT_RESPONSE_BREAK_OTHERS_B; return FPinConnectionResponse(ReplyBreakInputs, TEXT("Replace existing input connections")); } else { return FPinConnectionResponse(CONNECT_RESPONSE_MAKE, TEXT("")); } } static FText GetPinIncompatibilityMessage(const UEdGraphPin* PinA, const UEdGraphPin* PinB) { const FEdGraphPinType& PinAType = PinA->PinType; const FEdGraphPinType& PinBType = PinB->PinType; FFormatNamedArguments MessageArgs; MessageArgs.Add(TEXT("PinAName"), PinA->GetDisplayName()); MessageArgs.Add(TEXT("PinBName"), PinB->GetDisplayName()); MessageArgs.Add(TEXT("PinAType"), UEdGraphSchema_K2::TypeToText(PinAType)); MessageArgs.Add(TEXT("PinBType"), UEdGraphSchema_K2::TypeToText(PinBType)); const UEdGraphPin* InputPin = (PinA->Direction == EGPD_Input) ? PinA : PinB; const FEdGraphPinType& InputType = InputPin->PinType; const UEdGraphPin* OutputPin = (InputPin == PinA) ? PinB : PinA; const FEdGraphPinType& OutputType = OutputPin->PinType; FText MessageFormat = LOCTEXT("DefaultPinIncompatibilityMessage", "{PinAType} is not compatible with {PinBType}."); if (OutputType.PinCategory == UEdGraphSchema_K2::PC_Struct) { if (InputType.PinCategory == UEdGraphSchema_K2::PC_Struct) { MessageFormat = LOCTEXT("StructsIncompatible", "Only exactly matching structures are considered compatible."); const UStruct* OutStruct = Cast(OutputType.PinSubCategoryObject.Get()); const UStruct* InStruct = Cast(InputType.PinSubCategoryObject.Get()); if ((OutStruct != nullptr) && (InStruct != nullptr) && OutStruct->IsChildOf(InStruct)) { MessageFormat = LOCTEXT("ChildStructIncompatible", "Only exactly matching structures are considered compatible. Derived structures are disallowed."); } } } else if (OutputType.PinCategory == UEdGraphSchema_K2::PC_Class) { if ((InputType.PinCategory == UEdGraphSchema_K2::PC_Object) || (InputType.PinCategory == UEdGraphSchema_K2::PC_Interface)) { MessageArgs.Add(TEXT("OutputName"), OutputPin->GetDisplayName()); MessageArgs.Add(TEXT("InputName"), InputPin->GetDisplayName()); MessageFormat = LOCTEXT("ClassObjectIncompatible", "'{PinAName}' and '{PinBName}' are incompatible ('{OutputName}' is an object type, and '{InputName}' is a reference to an object instance)."); } } else if ((OutputType.PinCategory == UEdGraphSchema_K2::PC_Object) )//|| (OutputType.PinCategory == UEdGraphSchema_K2::PC_Interface)) { if (InputType.PinCategory == UEdGraphSchema_K2::PC_Class) { MessageArgs.Add(TEXT("OutputName"), OutputPin->GetDisplayName()); MessageArgs.Add(TEXT("InputName"), InputPin->GetDisplayName()); MessageArgs.Add(TEXT("InputType"), UEdGraphSchema_K2::TypeToText(InputType)); MessageFormat = LOCTEXT("CannotGetClass", "'{PinAName}' and '{PinBName}' are not inherently compatible ('{InputName}' is an object type, and '{OutputName}' is a reference to an object instance).\nWe cannot use {OutputName}'s class because it is not a child of {InputType}."); } } return FText::Format(MessageFormat, MessageArgs); } const FPinConnectionResponse UEdGraphSchema_K2::CanCreateConnection(const UEdGraphPin* PinA, const UEdGraphPin* PinB) const { const UK2Node* OwningNodeA = Cast(PinA->GetOwningNodeUnchecked()); const UK2Node* OwningNodeB = Cast(PinB->GetOwningNodeUnchecked()); if (!OwningNodeA || !OwningNodeB) { return FPinConnectionResponse(CONNECT_RESPONSE_DISALLOW, TEXT("Invalid nodes")); } // Make sure the pins are not on the same node if (OwningNodeA == OwningNodeB) { return FPinConnectionResponse(CONNECT_RESPONSE_DISALLOW, TEXT("Both are on the same node")); } // node can disallow the connection { FString RespondMessage; if(OwningNodeA && OwningNodeA->IsConnectionDisallowed(PinA, PinB, RespondMessage)) { return FPinConnectionResponse(CONNECT_RESPONSE_DISALLOW, RespondMessage); } if(OwningNodeB && OwningNodeB->IsConnectionDisallowed(PinB, PinA, RespondMessage)) { return FPinConnectionResponse(CONNECT_RESPONSE_DISALLOW, RespondMessage); } } // Compare the directions const UEdGraphPin* InputPin = NULL; const UEdGraphPin* OutputPin = NULL; if (!CategorizePinsByDirection(PinA, PinB, /*out*/ InputPin, /*out*/ OutputPin)) { return FPinConnectionResponse(CONNECT_RESPONSE_DISALLOW, TEXT("Directions are not compatible")); } bool bIgnoreArray = false; if(const UK2Node* OwningNode = Cast(InputPin->GetOwningNode())) { const bool bAllowMultipleSelfs = OwningNode->AllowMultipleSelfs(true); // it applies also to ForEachCall const bool bNotAnArrayFunction = !InputPin->PinType.bIsArray; const bool bSelfPin = IsSelfPin(*InputPin); bIgnoreArray = bAllowMultipleSelfs && bNotAnArrayFunction && bSelfPin; } // Find the calling context in case one of the pins is of type object and has a value of Self UClass* CallingContext = NULL; const UBlueprint* Blueprint = FBlueprintEditorUtils::FindBlueprintForNode(PinA->GetOwningNodeUnchecked()); if (Blueprint) { CallingContext = (Blueprint->GeneratedClass != NULL) ? Blueprint->GeneratedClass : Blueprint->ParentClass; } // Compare the types const bool bTypesMatch = ArePinsCompatible(OutputPin, InputPin, CallingContext, bIgnoreArray); if (bTypesMatch) { return DetermineConnectionResponseOfCompatibleTypedPins(PinA, PinB, InputPin, OutputPin); } else { // Autocasting FName DummyName; UK2Node* DummyNode; const bool bCanAutocast = SearchForAutocastFunction(OutputPin, InputPin, /*out*/ DummyName); const bool bCanAutoConvert = FindSpecializedConversionNode(OutputPin, InputPin, false, /* out */ DummyNode); if (bCanAutocast || bCanAutoConvert) { return FPinConnectionResponse(CONNECT_RESPONSE_MAKE_WITH_CONVERSION_NODE, FString::Printf(TEXT("Convert %s to %s"), *TypeToText(OutputPin->PinType).ToString(), *TypeToText(InputPin->PinType).ToString())); } else { FText IncompatibilityReasonText = GetPinIncompatibilityMessage(PinA, PinB); return FPinConnectionResponse(CONNECT_RESPONSE_DISALLOW, IncompatibilityReasonText.ToString()); } } } bool UEdGraphSchema_K2::TryCreateConnection(UEdGraphPin* PinA, UEdGraphPin* PinB) const { UBlueprint* Blueprint = FBlueprintEditorUtils::FindBlueprintForNodeChecked(PinA->GetOwningNode()); bool bModified = UEdGraphSchema::TryCreateConnection(PinA, PinB); if (bModified && !PinA->HasAnyFlags(RF_Transient)) { FBlueprintEditorUtils::MarkBlueprintAsModified(Blueprint); } return bModified; } bool UEdGraphSchema_K2::SearchForAutocastFunction(const UEdGraphPin* OutputPin, const UEdGraphPin* InputPin, /*out*/ FName& TargetFunction) const { // NOTE: Under no circumstances should anyone *ever* add a questionable cast to this function. // If it could be at all confusing why a function is provided, to even a novice user, err on the side of do not cast!!! // This includes things like string->int (does it do length, atoi, or what?) that would be autocasts in a traditional scripting language TargetFunction = NAME_None; const UScriptStruct* InputStructType = Cast(InputPin->PinType.PinSubCategoryObject.Get()); const UScriptStruct* OutputStructType = Cast(OutputPin->PinType.PinSubCategoryObject.Get()); if (OutputPin->PinType.bIsArray != InputPin->PinType.bIsArray) { // We don't autoconvert between arrays and non-arrays. Those are handled by specialized conversions } else if (OutputPin->PinType.PinCategory == PC_Int) { if (InputPin->PinType.PinCategory == PC_Float) { TargetFunction = TEXT("Conv_IntToFloat"); } else if (InputPin->PinType.PinCategory == PC_String) { TargetFunction = TEXT("Conv_IntToString"); } else if ((InputPin->PinType.PinCategory == PC_Byte) && (InputPin->PinType.PinSubCategoryObject == NULL)) { TargetFunction = TEXT("Conv_IntToByte"); } else if (InputPin->PinType.PinCategory == PC_Boolean) { TargetFunction = TEXT("Conv_IntToBool"); } else if(InputPin->PinType.PinCategory == PC_Text) { TargetFunction = TEXT("Conv_IntToText"); } } else if (OutputPin->PinType.PinCategory == PC_Float) { if (InputPin->PinType.PinCategory == PC_Int) { TargetFunction = TEXT("FTrunc"); } else if ((InputPin->PinType.PinCategory == PC_Struct) && (InputStructType == VectorStruct)) { TargetFunction = TEXT("Conv_FloatToVector"); } else if (InputPin->PinType.PinCategory == PC_String) { TargetFunction = TEXT("Conv_FloatToString"); } else if ((InputPin->PinType.PinCategory == PC_Struct) && (InputStructType == LinearColorStruct)) { TargetFunction = TEXT("Conv_FloatToLinearColor"); } else if(InputPin->PinType.PinCategory == PC_Text) { TargetFunction = TEXT("Conv_FloatToText"); } } else if (OutputPin->PinType.PinCategory == PC_Struct) { if (OutputStructType == VectorStruct) { if ((InputPin->PinType.PinCategory == PC_Struct) && (InputStructType == TransformStruct)) { TargetFunction = TEXT("Conv_VectorToTransform"); } else if ((InputPin->PinType.PinCategory == PC_Struct) && (InputStructType == LinearColorStruct)) { TargetFunction = TEXT("Conv_VectorToLinearColor"); } else if ((InputPin->PinType.PinCategory == PC_Struct) && (InputStructType == RotatorStruct)) { TargetFunction = TEXT("Conv_VectorToRotator"); } else if (InputPin->PinType.PinCategory == PC_String) { TargetFunction = TEXT("Conv_VectorToString"); } // NOTE: Did you see the note above about unsafe and unclear casts? } else if(OutputStructType == RotatorStruct) { if ((InputPin->PinType.PinCategory == PC_Struct) && (InputStructType == TransformStruct)) { TargetFunction = TEXT("MakeTransform"); } else if ((InputPin->PinType.PinCategory == PC_Struct) && (InputStructType == VectorStruct)) { TargetFunction = TEXT("Conv_RotatorToVector"); } else if (InputPin->PinType.PinCategory == PC_String) { TargetFunction = TEXT("Conv_RotatorToString"); } // NOTE: Did you see the note above about unsafe and unclear casts? } else if(OutputStructType == LinearColorStruct) { if ((InputPin->PinType.PinCategory == PC_Struct) && (InputStructType == ColorStruct)) { TargetFunction = TEXT("Conv_LinearColorToColor"); } else if (InputPin->PinType.PinCategory == PC_String) { TargetFunction = TEXT("Conv_ColorToString"); } else if ((InputPin->PinType.PinCategory == PC_Struct) && (InputStructType == VectorStruct)) { TargetFunction = TEXT("Conv_LinearColorToVector"); } } else if(OutputStructType == ColorStruct) { if ((InputPin->PinType.PinCategory == PC_Struct) && (InputStructType == LinearColorStruct)) { TargetFunction = TEXT("Conv_ColorToLinearColor"); } } } else if (OutputPin->PinType.PinCategory == PC_Boolean) { if (InputPin->PinType.PinCategory == PC_String) { TargetFunction = TEXT("Conv_BoolToString"); } else if (InputPin->PinType.PinCategory == PC_Int) { TargetFunction = TEXT("Conv_BoolToInt"); } else if (InputPin->PinType.PinCategory == PC_Float) { TargetFunction = TEXT("Conv_BoolToFloat"); } else if ((InputPin->PinType.PinCategory == PC_Byte) && (InputPin->PinType.PinSubCategoryObject == NULL)) { TargetFunction = TEXT("Conv_BoolToByte"); } else if ( InputPin->PinType.PinCategory == PC_Text ) { TargetFunction = TEXT("Conv_BoolToText"); } } else if (OutputPin->PinType.PinCategory == PC_Byte && (OutputPin->PinType.PinSubCategoryObject == NULL || !OutputPin->PinType.PinSubCategoryObject->IsA(UEnum::StaticClass()))) { if (InputPin->PinType.PinCategory == PC_String) { TargetFunction = TEXT("Conv_ByteToString"); } else if (InputPin->PinType.PinCategory == PC_Int) { TargetFunction = TEXT("Conv_ByteToInt"); } else if (InputPin->PinType.PinCategory == PC_Float) { TargetFunction = TEXT("Conv_ByteToFloat"); } else if ( InputPin->PinType.PinCategory == PC_Text ) { TargetFunction = TEXT("Conv_ByteToText"); } } else if (OutputPin->PinType.PinCategory == PC_Name) { if (InputPin->PinType.PinCategory == PC_String) { TargetFunction = TEXT("Conv_NameToString"); } else if (InputPin->PinType.PinCategory == PC_Text) { TargetFunction = TEXT("Conv_NameToText"); } } else if (OutputPin->PinType.PinCategory == PC_String) { if (InputPin->PinType.PinCategory == PC_Name) { TargetFunction = TEXT("Conv_StringToName"); } else if (InputPin->PinType.PinCategory == PC_Int) { TargetFunction = TEXT("Conv_StringToInt"); } else if (InputPin->PinType.PinCategory == PC_Float) { TargetFunction = TEXT("Conv_StringToFloat"); } else if (InputPin->PinType.PinCategory == PC_Text) { TargetFunction = TEXT("Conv_StringToText"); } else { // NOTE: Did you see the note above about unsafe and unclear casts? } } else if (OutputPin->PinType.PinCategory == PC_Text) { if(InputPin->PinType.PinCategory == PC_String) { TargetFunction = TEXT("Conv_TextToString"); } } else if ((OutputPin->PinType.PinCategory == PC_Interface) && (InputPin->PinType.PinCategory == PC_Object)) { UClass const* InputClass = Cast(InputPin->PinType.PinSubCategoryObject.Get()); bool const bInputIsUObject = ((InputClass != NULL) && (InputClass == UObject::StaticClass())); if (bInputIsUObject) { TargetFunction = TEXT("Conv_InterfaceToObject"); } } else if (OutputPin->PinType.PinCategory == PC_Object) { UClass const* OutputClass = Cast(OutputPin->PinType.PinSubCategoryObject.Get()); if (InputPin->PinType.PinCategory == PC_Class) { UClass const* InputClass = Cast(InputPin->PinType.PinSubCategoryObject.Get()); if ((OutputClass != nullptr) && (InputClass != nullptr) && OutputClass->IsChildOf(InputClass)) { TargetFunction = TEXT("GetObjectClass"); } } else if (InputPin->PinType.PinCategory == PC_String) { TargetFunction = TEXT("GetDisplayName"); } } return TargetFunction != NAME_None; } bool UEdGraphSchema_K2::FindSpecializedConversionNode(const UEdGraphPin* OutputPin, const UEdGraphPin* InputPin, bool bCreateNode, /*out*/ UK2Node*& TargetNode) const { bool bCanConvert = false; TargetNode = NULL; // Conversion for scalar -> array if( (!OutputPin->PinType.bIsArray && InputPin->PinType.bIsArray) && ArePinTypesCompatible(OutputPin->PinType, InputPin->PinType, NULL, true) ) { bCanConvert = true; if(bCreateNode) { TargetNode = NewObject(); } } // If connecting an object to a 'call function' self pin, and not currently compatible, see if there is a property we can call a function on else if (InputPin->GetOwningNode()->IsA(UK2Node_CallFunction::StaticClass()) && IsSelfPin(*InputPin) && ((OutputPin->PinType.PinCategory == PC_Object) || (OutputPin->PinType.PinCategory == PC_Interface))) { UK2Node_CallFunction* CallFunctionNode = (UK2Node_CallFunction*)(InputPin->GetOwningNode()); UClass* OutputPinClass = Cast(OutputPin->PinType.PinSubCategoryObject.Get()); UClass* FunctionClass = CallFunctionNode->FunctionReference.GetMemberParentClass(CallFunctionNode->GetBlueprintClassFromNode()); if(FunctionClass != NULL && OutputPinClass != NULL) { // Iterate over object properties.. for (TFieldIterator PropIt(OutputPinClass, EFieldIteratorFlags::IncludeSuper); PropIt; ++PropIt) { UObjectProperty* ObjProp = *PropIt; // .. if we have a blueprint visible var, and is of the type which contains this function.. if(ObjProp->HasAllPropertyFlags(CPF_BlueprintVisible) && ObjProp->PropertyClass->IsChildOf(FunctionClass)) { // say we can convert bCanConvert = true; // Create 'get variable' node if(bCreateNode) { UK2Node_VariableGet* GetNode = NewObject(); GetNode->VariableReference.SetFromField(ObjProp, false); TargetNode = GetNode; } } } } } if(!bCanConvert) { // CHECK ENUM TO NAME CAST const bool bInoputMatch = InputPin && !InputPin->PinType.bIsArray && ((PC_Name == InputPin->PinType.PinCategory) || (PC_String == InputPin->PinType.PinCategory)); const bool bOutputMatch = OutputPin && !OutputPin->PinType.bIsArray && (PC_Byte == OutputPin->PinType.PinCategory) && (NULL != Cast(OutputPin->PinType.PinSubCategoryObject.Get())); if(bOutputMatch && bInoputMatch) { bCanConvert = true; if(bCreateNode) { check(NULL == TargetNode); if(PC_Name == InputPin->PinType.PinCategory) { TargetNode = NewObject(); } else if(PC_String == InputPin->PinType.PinCategory) { TargetNode = NewObject(); } } } } if (!bCanConvert && InputPin && OutputPin) { FEdGraphPinType const& InputType = InputPin->PinType; FEdGraphPinType const& OutputType = OutputPin->PinType; // CHECK BYTE TO ENUM CAST UEnum* Enum = Cast(InputType.PinSubCategoryObject.Get()); const bool bInputIsEnum = !InputType.bIsArray && (PC_Byte == InputType.PinCategory) && Enum; const bool bOutputIsByte = !OutputType.bIsArray && (PC_Byte == OutputType.PinCategory); if (bInputIsEnum && bOutputIsByte) { bCanConvert = true; if(bCreateNode) { auto CastByteToEnum = NewObject(); CastByteToEnum->Enum = Enum; CastByteToEnum->bSafe = true; TargetNode = CastByteToEnum; } } else { UClass* InputClass = Cast(InputType.PinSubCategoryObject.Get()); UClass* OutputClass = Cast(InputType.PinSubCategoryObject.Get()); if ((OutputType.PinCategory == PC_Interface) && (InputType.PinCategory == PC_Object)) { bCanConvert = (InputClass && OutputClass) && (InputClass->ImplementsInterface(OutputClass) || OutputClass->IsChildOf(InputClass)); } else if (OutputType.PinCategory == PC_Object) { UBlueprintEditorSettings const* BlueprintSettings = GetDefault(); if ((InputType.PinCategory == PC_Object) && BlueprintSettings->bAutoCastObjectConnections) { bCanConvert = (InputClass && OutputClass) && InputClass->IsChildOf(OutputClass); } } if (bCanConvert && bCreateNode) { UK2Node_DynamicCast* DynCastNode = NewObject(); DynCastNode->TargetType = InputClass; DynCastNode->SetPurity(true); TargetNode = DynCastNode; } } } return bCanConvert; } void UEdGraphSchema_K2::AutowireConversionNode(UEdGraphPin* InputPin, UEdGraphPin* OutputPin, UEdGraphNode* ConversionNode) const { bool bAllowInputConnections = true; bool bAllowOutputConnections = true; for (int32 PinIndex = 0; PinIndex < ConversionNode->Pins.Num(); ++PinIndex) { UEdGraphPin* TestPin = ConversionNode->Pins[PinIndex]; if ((TestPin->Direction == EGPD_Input) && (ArePinTypesCompatible(OutputPin->PinType, TestPin->PinType))) { if(bAllowOutputConnections && TryCreateConnection(TestPin, OutputPin)) { // Successful connection, do not allow more output connections bAllowOutputConnections = false; } } else if ((TestPin->Direction == EGPD_Output) && (ArePinTypesCompatible(TestPin->PinType, InputPin->PinType))) { if(bAllowInputConnections && TryCreateConnection(TestPin, InputPin)) { // Successful connection, do not allow more input connections bAllowInputConnections = false; } } } } bool UEdGraphSchema_K2::CreateAutomaticConversionNodeAndConnections(UEdGraphPin* PinA, UEdGraphPin* PinB) const { // Determine which pin is an input and which pin is an output UEdGraphPin* InputPin = NULL; UEdGraphPin* OutputPin = NULL; if (!CategorizePinsByDirection(PinA, PinB, /*out*/ InputPin, /*out*/ OutputPin)) { return false; } FName TargetFunctionName; TSubclassOf ConversionNodeClass; UK2Node* TemplateConversionNode = NULL; if (SearchForAutocastFunction(OutputPin, InputPin, /*out*/ TargetFunctionName)) { // Create a new call function node for the casting operator UClass* ClassContainingConversionFunction = NULL; //@TODO: Should probably return this from the search function too UK2Node_CallFunction* TemplateNode = NewObject(); TemplateNode->FunctionReference.SetExternalMember(TargetFunctionName, ClassContainingConversionFunction); //TemplateNode->bIsBeadFunction = true; TemplateConversionNode = TemplateNode; } else { FindSpecializedConversionNode(OutputPin, InputPin, true, /*out*/ TemplateConversionNode); } if (TemplateConversionNode != NULL) { // Determine where to position the new node (assuming it isn't going to get beaded) FVector2D AverageLocation = CalculateAveragePositionBetweenNodes(InputPin, OutputPin); UK2Node* ConversionNode = FEdGraphSchemaAction_K2NewNode::SpawnNodeFromTemplate(InputPin->GetOwningNode()->GetGraph(), TemplateConversionNode, AverageLocation); // Connect the cast node up to the output/input pins AutowireConversionNode(InputPin, OutputPin, ConversionNode); return true; } return false; } FString UEdGraphSchema_K2::IsPinDefaultValid(const UEdGraphPin* Pin, const FString& NewDefaultValue, UObject* NewDefaultObject, const FText& InNewDefaultText) const { const UBlueprint* OwningBP = FBlueprintEditorUtils::FindBlueprintForNodeChecked(Pin->GetOwningNode()); const bool bIsArray = Pin->PinType.bIsArray; const bool bIsReference = Pin->PinType.bIsReference; const bool bIsAutoCreateRefTerm = IsAutoCreateRefTerm(Pin); FFormatNamedArguments MessageArgs; MessageArgs.Add(TEXT("PinName"), Pin->GetDisplayName()); if (OwningBP->BlueprintType != BPTYPE_Interface) { if( !bIsAutoCreateRefTerm ) { if( bIsArray ) { FText MsgFormat = LOCTEXT("BadArrayDefaultVal", "Array inputs (like '{PinName}') must have an input wired into them (try connecting a MakeArray node)."); return FText::Format(MsgFormat, MessageArgs).ToString(); } else if( bIsReference ) { FText MsgFormat = LOCTEXT("BadRefDefaultVal", "'{PinName}' must have an input wired into it (\"by ref\" params expect a valid input to operate on)."); return FText::Format(MsgFormat, MessageArgs).ToString(); } } } FString ReturnMsg; DefaultValueSimpleValidation(Pin->PinType, Pin->PinName, NewDefaultValue, NewDefaultObject, InNewDefaultText, &ReturnMsg); return ReturnMsg; } bool UEdGraphSchema_K2::DoesSupportPinWatching() const { return true; } bool UEdGraphSchema_K2::IsPinBeingWatched(UEdGraphPin const* Pin) const { // Note: If you crash here; it is likely that you forgot to call Blueprint->OnBlueprintChanged.Broadcast(Blueprint) to invalidate the cached UI state UBlueprint* Blueprint = FBlueprintEditorUtils::FindBlueprintForNodeChecked(Pin->GetOwningNode()); return FKismetDebugUtilities::IsPinBeingWatched(Blueprint, Pin); } void UEdGraphSchema_K2::ClearPinWatch(UEdGraphPin const* Pin) const { UBlueprint* Blueprint = FBlueprintEditorUtils::FindBlueprintForNodeChecked(Pin->GetOwningNode()); FKismetDebugUtilities::RemovePinWatch(Blueprint, Pin); } bool UEdGraphSchema_K2::DefaultValueSimpleValidation(const FEdGraphPinType& PinType, const FString& PinName, const FString& NewDefaultValue, UObject* NewDefaultObject, const FText& InNewDefaultText, FString* OutMsg /*= NULL*/) const { #ifdef DVSV_RETURN_MSG static_assert(false, "Macro redefinition."); #endif #define DVSV_RETURN_MSG(Str) if(NULL != OutMsg) { *OutMsg = Str; } return false; const FString& PinCategory = PinType.PinCategory; const FString& PinSubCategory = PinType.PinSubCategory; const UObject* PinSubCategoryObject = PinType.PinSubCategoryObject.Get(); if (PinType.bIsArray) { // arrays are validated separately } //@TODO: FCString::Atoi, FCString::Atof, and appStringToBool will 'accept' any input, but we should probably catch and warn // about invalid input (non numeric for int/byte/float, and non 0/1 or yes/no/true/false for bool) else if(PinCategory == PC_Boolean) { // All input is acceptable to some degree } else if (PinCategory == PC_Byte) { const UEnum* EnumPtr = Cast(PinSubCategoryObject); if (EnumPtr) { if (EnumPtr->FindEnumIndex(*NewDefaultValue) == INDEX_NONE) { DVSV_RETURN_MSG( FString::Printf( TEXT("'%s' is not a valid enumerant of '<%s>'"), *NewDefaultValue, *(EnumPtr->GetName( )) ) ); } } else if( !NewDefaultValue.IsEmpty() ) { int32 Value; if (!FDefaultValueHelper::ParseInt(NewDefaultValue, Value)) { DVSV_RETURN_MSG( TEXT("Expected a valid unsigned number for a byte property") ); } if ((Value < 0) || (Value > 255)) { DVSV_RETURN_MSG( TEXT("Expected a value between 0 and 255 for a byte property") ); } } } else if (PinCategory == PC_Class) { // Should have an object set but no string if(!NewDefaultValue.IsEmpty()) { DVSV_RETURN_MSG( FString::Printf(TEXT("String NewDefaultValue '%s' specified on class pin '%s'"), *NewDefaultValue, *(PinName)) ); } if (NewDefaultObject == NULL) { // Valid self-reference or empty reference } else { // Otherwise, we expect to be able to resolve the type at least const UClass* DefaultClassType = Cast(NewDefaultObject); if (DefaultClassType == NULL) { DVSV_RETURN_MSG( FString::Printf(TEXT("Literal on pin %s is not a class."), *(PinName)) ); } else { // @TODO support PinSubCategory == 'self' const UClass* PinClassType = Cast(PinSubCategoryObject); if (PinClassType == NULL) { DVSV_RETURN_MSG( FString::Printf(TEXT("Failed to find class for pin %s"), *(PinName)) ); } else { // Have both types, make sure the specified type is a valid subtype if (!DefaultClassType->IsChildOf(PinClassType)) { DVSV_RETURN_MSG( FString::Printf(TEXT("%s isn't a valid subclass of %s (specified on pin %s)"), *NewDefaultObject->GetPathName(), *PinClassType->GetName(), *(PinName)) ); } } } } } else if (PinCategory == PC_Float) { if(!NewDefaultValue.IsEmpty()) { if (!FDefaultValueHelper::IsStringValidFloat(NewDefaultValue)) { DVSV_RETURN_MSG( TEXT("Expected a valid number for an float property") ); } } } else if (PinCategory == PC_Int) { if(!NewDefaultValue.IsEmpty()) { if (!FDefaultValueHelper::IsStringValidInteger(NewDefaultValue)) { DVSV_RETURN_MSG( TEXT("Expected a valid number for an integer property") ); } } } else if (PinCategory == PC_Name) { // Anything is allowed } else if ((PinCategory == PC_Object) || (PinCategory == PC_Interface)) { if(PinSubCategoryObject == NULL && PinSubCategory != PSC_Self) { DVSV_RETURN_MSG( FString::Printf(TEXT("PinSubCategoryObject on pin '%s' is NULL and PinSubCategory is '%s' not 'self'"), *(PinName), *PinSubCategory) ); } if(PinSubCategoryObject != NULL && PinSubCategory != TEXT("")) { DVSV_RETURN_MSG( FString::Printf(TEXT("PinSubCategoryObject on pin '%s' is non-NULL but PinSubCategory is '%s', should be empty"), *(PinName), *PinSubCategory) ); } // Should have an object set but no string - 'self' is not a valid NewDefaultValue for PC_Object pins if(!NewDefaultValue.IsEmpty()) { DVSV_RETURN_MSG( FString::Printf(TEXT("String NewDefaultValue '%s' specified on object pin '%s'"), *NewDefaultValue, *(PinName)) ); } // Check that the object that is set is of the correct class const UClass* ObjectClass = Cast(PinSubCategoryObject); if(NewDefaultObject != NULL && ObjectClass != NULL && !NewDefaultObject->IsA(ObjectClass)) { DVSV_RETURN_MSG( FString::Printf(TEXT("%s isn't a %s (specified on pin %s)"), *NewDefaultObject->GetPathName(), *ObjectClass->GetName(), *(PinName)) ); } } else if (PinCategory == PC_String) { // All strings are valid } else if (PinCategory == PC_Text) { // Neither of these should ever be true if( InNewDefaultText.IsTransient() ) { DVSV_RETURN_MSG( TEXT("Invalid text literal, text is transient!") ); } } else if (PinCategory == PC_Struct) { if(PinSubCategory != TEXT("")) { DVSV_RETURN_MSG( FString::Printf(TEXT("Invalid PinSubCategory value '%s' (it should be empty)"), *PinSubCategory) ); } // Only FRotator and FVector properties are currently allowed to have a valid default value const UScriptStruct* StructType = Cast(PinSubCategoryObject); if (StructType == NULL) { //@TODO: MessageLog.Error(*FString::Printf(TEXT("Failed to find struct named %s (passed thru @@)"), *PinSubCategory), SourceObject); DVSV_RETURN_MSG( FString::Printf(TEXT("No struct specified for pin '%s'"), *(PinName)) ); } else if(!NewDefaultValue.IsEmpty()) { if (StructType == VectorStruct) { if (!FDefaultValueHelper::IsStringValidVector(NewDefaultValue)) { DVSV_RETURN_MSG( TEXT("Invalid value for an FVector") ); } } else if (StructType == RotatorStruct) { FRotator Rot; if (!FDefaultValueHelper::IsStringValidRotator(NewDefaultValue)) { DVSV_RETURN_MSG( TEXT("Invalid value for an FRotator") ); } } else if (StructType == TransformStruct) { FTransform Transform; if ( !Transform.InitFromString(NewDefaultValue)) { DVSV_RETURN_MSG( TEXT("Invalid value for an FTransform") ); } } else if (StructType == LinearColorStruct) { FLinearColor Color; // Color form: "(R=%f,G=%f,B=%f,A=%f)" if (!Color.InitFromString(NewDefaultValue)) { DVSV_RETURN_MSG( TEXT("Invalid value for an FLinearColor") ); } } else { // Structs must pass validation at this point, because we need a UStructProperty to run ImportText // They'll be verified in FKCHandler_CallFunction::CreateFunctionCallStatement() } } } else if (PinCategory == TEXT("CommentType")) { // Anything is allowed } else { //@TODO: MessageLog.Error(*FString::Printf(TEXT("Unsupported type %s on @@"), *UEdGraphSchema_K2::TypeToText(Type).ToString()), SourceObject); DVSV_RETURN_MSG( FString::Printf(TEXT("Unsupported type %s on pin %s"), *UEdGraphSchema_K2::TypeToText(PinType).ToString(), *(PinName)) ); } #undef DVSV_RETURN_MSG return true; } FLinearColor UEdGraphSchema_K2::GetPinTypeColor(const FEdGraphPinType& PinType) const { const FString& TypeString = PinType.PinCategory; const UGraphEditorSettings* Settings = GetDefault(); if (TypeString == PC_Exec) { return Settings->ExecutionPinTypeColor; } else if (TypeString == PC_Object) { return Settings->ObjectPinTypeColor; } else if (TypeString == PC_Interface) { return Settings->InterfacePinTypeColor; } else if (TypeString == PC_Float) { return Settings->FloatPinTypeColor; } else if (TypeString == PC_Boolean) { return Settings->BooleanPinTypeColor; } else if (TypeString == PC_Byte) { return Settings->BytePinTypeColor; } else if (TypeString == PC_Int) { return Settings->IntPinTypeColor; } else if (TypeString == PC_Struct) { if (PinType.PinSubCategoryObject == VectorStruct) { // vector return Settings->VectorPinTypeColor; } else if (PinType.PinSubCategoryObject == RotatorStruct) { // rotator return Settings->RotatorPinTypeColor; } else if (PinType.PinSubCategoryObject == TransformStruct) { // transform return Settings->TransformPinTypeColor; } else { return Settings->StructPinTypeColor; } } else if (TypeString == PC_String) { return Settings->StringPinTypeColor; } else if (TypeString == PC_Text) { return Settings->TextPinTypeColor; } else if (TypeString == PC_Wildcard) { if (PinType.PinSubCategory == PSC_Index) { return Settings->IndexPinTypeColor; } else { return Settings->WildcardPinTypeColor; } } else if (TypeString == PC_Name) { return Settings->NamePinTypeColor; } else if (TypeString == PC_Delegate) { return Settings->DelegatePinTypeColor; } else if (TypeString == PC_Class) { return Settings->ClassPinTypeColor; } // Type does not have a defined color! return Settings->DefaultPinTypeColor; } FText UEdGraphSchema_K2::GetPinDisplayName(const UEdGraphPin* Pin) const { FText DisplayName = FText::GetEmpty(); if (Pin != NULL) { UEdGraphNode* Node = Pin->GetOwningNode(); if (Node->ShouldOverridePinNames()) { DisplayName = Node->GetPinNameOverride(*Pin); } else { DisplayName = Super::GetPinDisplayName(Pin); // bit of a hack to hide 'execute' and 'then' pin names if ((Pin->PinType.PinCategory == PC_Exec) && ((DisplayName.ToString() == PN_Execute) || (DisplayName.ToString() == PN_Then))) { DisplayName = FText::GetEmpty(); } } if( GEditor && GetDefault()->bShowFriendlyNames ) { DisplayName = FText::FromString(FName::NameToDisplayString(DisplayName.ToString(), Pin->PinType.PinCategory == PC_Boolean)); } } return DisplayName; } void UEdGraphSchema_K2::ConstructBasicPinTooltip(const UEdGraphPin& Pin, const FText& PinDescription, FString& TooltipOut) const { if (bGeneratingDocumentation) { TooltipOut = PinDescription.ToString(); } else { FFormatNamedArguments Args; Args.Add(TEXT("PinType"), TypeToText(Pin.PinType)); if (UEdGraphNode* PinNode = Pin.GetOwningNode()) { UEdGraphSchema_K2 const* const K2Schema = Cast(PinNode->GetSchema()); if (ensure(K2Schema != NULL)) // ensure that this node belongs to this schema { Args.Add(TEXT("DisplayName"), GetPinDisplayName(&Pin)); Args.Add(TEXT("LineFeed1"), FText::FromString(TEXT("\n"))); } } else { Args.Add(TEXT("DisplayName"), FText::GetEmpty()); Args.Add(TEXT("LineFeed1"), FText::GetEmpty()); } if (!PinDescription.IsEmpty()) { Args.Add(TEXT("Description"), PinDescription); Args.Add(TEXT("LineFeed2"), FText::FromString(TEXT("\n\n"))); } else { Args.Add(TEXT("Description"), FText::GetEmpty()); Args.Add(TEXT("LineFeed2"), FText::GetEmpty()); } TooltipOut = FText::Format(LOCTEXT("PinTooltip", "{DisplayName}{LineFeed1}{PinType}{LineFeed2}{Description}"), Args).ToString(); } } EGraphType UEdGraphSchema_K2::GetGraphType(const UEdGraph* TestEdGraph) const { if (TestEdGraph) { //@TODO: Should there be a GT_Subgraph type? UEdGraph* GraphToTest = const_cast(TestEdGraph); for (UObject* TestOuter = GraphToTest; TestOuter; TestOuter = TestOuter->GetOuter()) { // reached up to the blueprint for the graph if (UBlueprint* Blueprint = Cast(TestOuter)) { if (Blueprint->BlueprintType == BPTYPE_MacroLibrary || Blueprint->MacroGraphs.Contains(GraphToTest)) { return GT_Macro; } else if (Blueprint->UbergraphPages.Contains(GraphToTest)) { return GT_Ubergraph; } else if (Blueprint->FunctionGraphs.Contains(GraphToTest)) { return GT_Function; } } else { GraphToTest = Cast(TestOuter); } } } return Super::GetGraphType(TestEdGraph); } bool UEdGraphSchema_K2::IsTitleBarPin(const UEdGraphPin& Pin) const { return IsExecPin(Pin); } void UEdGraphSchema_K2::CreateMacroGraphTerminators(UEdGraph& Graph, UClass* Class) const { const FName GraphName = Graph.GetFName(); UBlueprint* Blueprint = FBlueprintEditorUtils::FindBlueprintForGraphChecked(&Graph); // Create the entry/exit tunnels { FGraphNodeCreator EntryNodeCreator(Graph); UK2Node_Tunnel* EntryNode = EntryNodeCreator.CreateNode(); EntryNode->bCanHaveOutputs = true; EntryNodeCreator.Finalize(); SetNodeMetaData(EntryNode, FNodeMetadata::DefaultGraphNode); } { FGraphNodeCreator ExitNodeCreator(Graph); UK2Node_Tunnel* ExitNode = ExitNodeCreator.CreateNode(); ExitNode->bCanHaveInputs = true; ExitNode->NodePosX = 240; ExitNodeCreator.Finalize(); SetNodeMetaData(ExitNode, FNodeMetadata::DefaultGraphNode); } } void UEdGraphSchema_K2::LinkDataPinFromOutputToInput(UEdGraphNode* InOutputNode, UEdGraphNode* InInputNode) const { for (auto PinIter = InOutputNode->Pins.CreateIterator(); PinIter; ++PinIter) { UEdGraphPin* const OutputPin = *PinIter; if ((OutputPin->Direction == EGPD_Output) && (!IsExecPin(*OutputPin))) { UEdGraphPin* const InputPin = InInputNode->FindPinChecked(OutputPin->PinName); OutputPin->MakeLinkTo(InputPin); } } } void UEdGraphSchema_K2::CreateFunctionGraphTerminators(UEdGraph& Graph, UClass* Class) const { const FName GraphName = Graph.GetFName(); UBlueprint* Blueprint = FBlueprintEditorUtils::FindBlueprintForGraphChecked(&Graph); check(Blueprint->BlueprintType != BPTYPE_MacroLibrary); // Create a function entry node FGraphNodeCreator FunctionEntryCreator(Graph); UK2Node_FunctionEntry* EntryNode = FunctionEntryCreator.CreateNode(); EntryNode->SignatureClass = Class; EntryNode->SignatureName = GraphName; FunctionEntryCreator.Finalize(); SetNodeMetaData(EntryNode, FNodeMetadata::DefaultGraphNode); // See if we need to implement a return node UFunction* InterfaceToImplement = FindField(Class, GraphName); if (InterfaceToImplement) { // Add modifier flags from the declaration EntryNode->ExtraFlags |= InterfaceToImplement->FunctionFlags & (FUNC_Const | FUNC_Static | FUNC_BlueprintPure); UK2Node* NextNode = EntryNode; UEdGraphPin* NextExec = FindExecutionPin(*EntryNode, EGPD_Output); bool bHasParentNode = false; // Create node for call parent function if (((Class->GetClassFlags() & CLASS_Interface) == 0) && (InterfaceToImplement->FunctionFlags & FUNC_BlueprintCallable)) { FGraphNodeCreator FunctionParentCreator(Graph); UK2Node_CallParentFunction* ParentNode = FunctionParentCreator.CreateNode(); ParentNode->SetFromFunction(InterfaceToImplement); ParentNode->NodePosX = EntryNode->NodePosX + EntryNode->NodeWidth + 256; ParentNode->NodePosY = EntryNode->NodePosY; FunctionParentCreator.Finalize(); UEdGraphPin* ParentNodeExec = FindExecutionPin(*ParentNode, EGPD_Input); // If the parent node has an execution pin, then we should as well (we're overriding them, after all) // but perhaps this assumption is not valid in the case where a function becomes pure after being // initially declared impure - for that reason I'm checking for validity on both ParentNodeExec and NextExec if (ParentNodeExec && NextExec) { NextExec->MakeLinkTo(ParentNodeExec); NextExec = FindExecutionPin(*ParentNode, EGPD_Output); } NextNode = ParentNode; bHasParentNode = true; } // See if any function params are marked as out bool bHasOutParam = false; for( TFieldIterator It(InterfaceToImplement); It && (It->PropertyFlags & CPF_Parm); ++It ) { if( It->PropertyFlags & CPF_OutParm ) { bHasOutParam = true; break; } } if( bHasOutParam ) { FGraphNodeCreator NodeCreator(Graph); UK2Node_FunctionResult* ReturnNode = NodeCreator.CreateNode(); ReturnNode->SignatureClass = Class; ReturnNode->SignatureName = GraphName; ReturnNode->NodePosX = NextNode->NodePosX + NextNode->NodeWidth + 256; ReturnNode->NodePosY = EntryNode->NodePosY; NodeCreator.Finalize(); SetNodeMetaData(ReturnNode, FNodeMetadata::DefaultGraphNode); // Auto-connect the pins for entry and exit, so that by default the signature is properly generated UEdGraphPin* ResultNodeExec = FindExecutionPin(*ReturnNode, EGPD_Input); if (ResultNodeExec && NextExec) { NextExec->MakeLinkTo(ResultNodeExec); } if (bHasParentNode) { LinkDataPinFromOutputToInput(NextNode, ReturnNode); } } } } void UEdGraphSchema_K2::CreateFunctionGraphTerminators(UEdGraph& Graph, UFunction* FunctionSignature) const { const FName GraphName = Graph.GetFName(); UBlueprint* Blueprint = FBlueprintEditorUtils::FindBlueprintForGraphChecked(&Graph); check(Blueprint->BlueprintType != BPTYPE_MacroLibrary); // Create a function entry node FGraphNodeCreator FunctionEntryCreator(Graph); UK2Node_FunctionEntry* EntryNode = FunctionEntryCreator.CreateNode(); EntryNode->SignatureClass = NULL; EntryNode->SignatureName = GraphName; FunctionEntryCreator.Finalize(); SetNodeMetaData(EntryNode, FNodeMetadata::DefaultGraphNode); // We don't have a signature class to base this on permanently, because it's not an override function. // so we need to define the pins as user defined so that they are serialized. EntryNode->CreateUserDefinedPinsForFunctionEntryExit(FunctionSignature, /*bIsFunctionEntry=*/ true); // See if any function params are marked as out bool bHasOutParam = false; for ( TFieldIterator It(FunctionSignature); It && ( It->PropertyFlags & CPF_Parm ); ++It ) { if ( It->PropertyFlags & CPF_OutParm ) { bHasOutParam = true; break; } } if ( bHasOutParam ) { FGraphNodeCreator NodeCreator(Graph); UK2Node_FunctionResult* ReturnNode = NodeCreator.CreateNode(); ReturnNode->SignatureClass = NULL; ReturnNode->SignatureName = GraphName; ReturnNode->NodePosX = EntryNode->NodePosX + EntryNode->NodeWidth + 256; ReturnNode->NodePosY = EntryNode->NodePosY; NodeCreator.Finalize(); SetNodeMetaData(ReturnNode, FNodeMetadata::DefaultGraphNode); ReturnNode->CreateUserDefinedPinsForFunctionEntryExit(FunctionSignature, /*bIsFunctionEntry=*/ false); // Auto-connect the pins for entry and exit, so that by default the signature is properly generated UEdGraphPin* EntryNodeExec = FindExecutionPin(*EntryNode, EGPD_Output); UEdGraphPin* ResultNodeExec = FindExecutionPin(*ReturnNode, EGPD_Input); EntryNodeExec->MakeLinkTo(ResultNodeExec); } } bool UEdGraphSchema_K2::ConvertPropertyToPinType(const UProperty* Property, /*out*/ FEdGraphPinType& TypeOut) const { if (Property == NULL) { TypeOut.PinCategory = TEXT("bad_type"); return false; } TypeOut.PinSubCategory = TEXT(""); // Handle whether or not this is an array property const UArrayProperty* ArrayProperty = Cast(Property); const UProperty* TestProperty = ArrayProperty ? ArrayProperty->Inner : Property; TypeOut.bIsArray = (ArrayProperty != NULL); TypeOut.bIsReference = Property->HasAllPropertyFlags(CPF_OutParm|CPF_ReferenceParm); TypeOut.bIsConst = Property->HasAllPropertyFlags(CPF_ConstParm); // Check to see if this is the wildcard property for the target array type UFunction* Function = Cast(Property->GetOuter()); if( UK2Node_CallArrayFunction::IsWildcardProperty(Function, Property) || UK2Node_CallFunction::IsStructureWildcardProperty(Function, Property->GetName())) { TypeOut.PinCategory = PC_Wildcard; } else if (const UInterfaceProperty* InterfaceProperty = Cast(TestProperty)) { TypeOut.PinCategory = PC_Interface; TypeOut.PinSubCategoryObject = InterfaceProperty->InterfaceClass; } else if (const UClassProperty* ClassProperty = Cast(TestProperty)) { TypeOut.PinCategory = PC_Class; TypeOut.PinSubCategoryObject = ClassProperty->MetaClass; } else if (const UAssetClassProperty* AssetClassProperty = Cast(TestProperty)) { TypeOut.PinCategory = PC_Class; TypeOut.PinSubCategoryObject = AssetClassProperty->MetaClass; } else if (const UObjectPropertyBase* ObjectProperty = Cast(TestProperty)) { TypeOut.PinCategory = PC_Object; TypeOut.PinSubCategoryObject = ObjectProperty->PropertyClass; TypeOut.bIsWeakPointer = TestProperty->IsA(UWeakObjectProperty::StaticClass()); } else if (const UStructProperty* StructProperty = Cast(TestProperty)) { TypeOut.PinCategory = PC_Struct; TypeOut.PinSubCategoryObject = StructProperty->Struct; } else if (Cast(TestProperty) != NULL) { TypeOut.PinCategory = PC_Float; } else if (Cast(TestProperty) != NULL) { TypeOut.PinCategory = PC_Int; } else if (const UByteProperty* ByteProperty = Cast(TestProperty)) { TypeOut.PinCategory = PC_Byte; TypeOut.PinSubCategoryObject = ByteProperty->Enum; } else if (Cast(TestProperty) != NULL) { TypeOut.PinCategory = PC_Name; } else if (Cast(TestProperty) != NULL) { TypeOut.PinCategory = PC_Boolean; } else if (Cast(TestProperty) != NULL) { TypeOut.PinCategory = PC_String; } else if (Cast(TestProperty) != NULL) { TypeOut.PinCategory = PC_Text; } else if (const UMulticastDelegateProperty* MulticastDelegateProperty = Cast(TestProperty)) { TypeOut.PinCategory = PC_MCDelegate; FMemberReference::FillSimpleMemberReference(MulticastDelegateProperty->SignatureFunction, TypeOut.PinSubCategoryMemberReference); } else if (const UDelegateProperty* DelegateProperty = Cast(TestProperty)) { TypeOut.PinCategory = PC_Delegate; FMemberReference::FillSimpleMemberReference(DelegateProperty->SignatureFunction, TypeOut.PinSubCategoryMemberReference); } else { TypeOut.PinCategory = TEXT("bad_type"); return false; } return true; } FString UEdGraphSchema_K2::TypeToString(const FEdGraphPinType& Type) { return TypeToText(Type).ToString(); } FString UEdGraphSchema_K2::TypeToString(UProperty* const Property) { return TypeToText(Property).ToString(); } FText UEdGraphSchema_K2::TypeToText(UProperty* const Property) { if (UStructProperty* Struct = Cast(Property)) { if (Struct->Struct) { FEdGraphPinType PinType; PinType.PinCategory = PC_Struct; PinType.PinSubCategoryObject = Struct->Struct; return TypeToText(PinType); } } else if (UClassProperty* Class = Cast(Property)) { if (Class->MetaClass) { FEdGraphPinType PinType; PinType.PinCategory = PC_Class; PinType.PinSubCategoryObject = Class->MetaClass; return TypeToText(PinType); } } else if (UInterfaceProperty* Interface = Cast(Property)) { if (Interface->InterfaceClass != nullptr) { FEdGraphPinType PinType; PinType.PinCategory = PC_Interface; PinType.PinSubCategoryObject = Interface->InterfaceClass; return TypeToText(PinType); } } else if (UObjectPropertyBase* Obj = Cast(Property)) { if( Obj->PropertyClass ) { FEdGraphPinType PinType; PinType.PinCategory = PC_Object; PinType.PinSubCategoryObject = Obj->PropertyClass; PinType.bIsWeakPointer = Property->IsA(UWeakObjectProperty::StaticClass()); return TypeToText(PinType); } return FText::GetEmpty(); } else if (UArrayProperty* Array = Cast(Property)) { if (Array->Inner) { FFormatNamedArguments Args; Args.Add(TEXT("ArrayType"), TypeToText(Array->Inner)); return FText::Format(LOCTEXT("ArrayPropertyText", "Array of {ArrayType}"), Args); } } return FText::FromString(Property->GetClass()->GetName()); } FText UEdGraphSchema_K2::GetCategoryText(const FString& Category, const bool bForMenu) { if (Category.IsEmpty()) { return FText::GetEmpty(); } static TMap CategoryDescriptions; if (CategoryDescriptions.Num() == 0) { CategoryDescriptions.Add(PC_Exec, LOCTEXT("Exec", "Exec")); CategoryDescriptions.Add(PC_Boolean, LOCTEXT("BoolCategory","Boolean")); CategoryDescriptions.Add(PC_Byte, LOCTEXT("ByteCategory","Byte")); CategoryDescriptions.Add(PC_Class, LOCTEXT("ClassCategory","Class")); CategoryDescriptions.Add(PC_Int, LOCTEXT("IntCategory","Integer")); CategoryDescriptions.Add(PC_Float, LOCTEXT("FloatCategory","Float")); CategoryDescriptions.Add(PC_Name, LOCTEXT("NameCategory","Name")); CategoryDescriptions.Add(PC_Delegate, LOCTEXT("DelegateCategory","Delegate")); CategoryDescriptions.Add(PC_MCDelegate, LOCTEXT("MulticastDelegateCategory","Multicast Delegate")); CategoryDescriptions.Add(PC_Object, LOCTEXT("ObjectCategory","Reference")); CategoryDescriptions.Add(PC_Interface, LOCTEXT("InterfaceCategory","Interface")); CategoryDescriptions.Add(PC_String, LOCTEXT("StringCategory","String")); CategoryDescriptions.Add(PC_Text, LOCTEXT("TextCategory","Text")); CategoryDescriptions.Add(PC_Struct, LOCTEXT("StructCategory","Structure")); CategoryDescriptions.Add(PC_Wildcard, LOCTEXT("WildcardCategory","Wildcard")); CategoryDescriptions.Add(PC_Enum, LOCTEXT("EnumCategory","Enum")); } if (bForMenu) { if (Category == PC_Object) { return LOCTEXT("ObjectCategoryForMenu", "Object Reference"); } } if (FText const* TypeDesc = CategoryDescriptions.Find(Category)) { return *TypeDesc; } else { return FText::FromString(Category); } } FText UEdGraphSchema_K2::TypeToText(const FEdGraphPinType& Type) { FText PropertyText; if (Type.PinSubCategoryObject != NULL) { const UEdGraphSchema_K2* Schema = GetDefault(); if (Type.PinCategory == Schema->PC_Byte) { FFormatNamedArguments Args; Args.Add(TEXT("EnumName"), FText::FromString(Type.PinSubCategoryObject->GetName())); PropertyText = FText::Format(LOCTEXT("EnumAsText", "{EnumName} Enum"), Args); } else { UObject* SubCategoryObj = Type.PinSubCategoryObject.Get(); FString SubCategoryObjName = SubCategoryObj->GetName(); if (UField* SubCategoryField = Cast(SubCategoryObj)) { SubCategoryObjName = SubCategoryField->GetDisplayNameText().ToString(); } if( !Type.bIsWeakPointer ) { UClass* PSCOAsClass = Cast(Type.PinSubCategoryObject.Get()); const bool bIsInterface = PSCOAsClass && PSCOAsClass->HasAnyClassFlags(CLASS_Interface); FFormatNamedArguments Args; // Don't display the category for "well-known" struct types if (Type.PinCategory == PC_Struct && (Type.PinSubCategoryObject == VectorStruct || Type.PinSubCategoryObject == RotatorStruct || Type.PinSubCategoryObject == TransformStruct)) { Args.Add(TEXT("Category"), FText::GetEmpty()); } else { Args.Add(TEXT("Category"), (!bIsInterface ? GetCategoryText(Type.PinCategory) : GetCategoryText(PC_Interface))); } Args.Add(TEXT("ObjectName"), FText::FromString(FName::NameToDisplayString(SubCategoryObjName, /*bIsBool =*/false))); PropertyText = FText::Format(LOCTEXT("ObjectAsText", "{ObjectName} {Category}"), Args); } else { FFormatNamedArguments Args; Args.Add(TEXT("Category"), FText::FromString(Type.PinCategory)); Args.Add(TEXT("ObjectName"), FText::FromString(SubCategoryObjName)); PropertyText = FText::Format(LOCTEXT("WeakPtrAsText", "{ObjectName} Weak {Category}"), Args); } } } else if (Type.PinSubCategory != TEXT("")) { FFormatNamedArguments Args; Args.Add(TEXT("Category"), GetCategoryText(Type.PinCategory)); Args.Add(TEXT("ObjectName"), FText::FromString(FName::NameToDisplayString(Type.PinSubCategory, false))); PropertyText = FText::Format(LOCTEXT("ObjectAsText", "{ObjectName} {Category}"), Args); } else { PropertyText = GetCategoryText(Type.PinCategory); } if (Type.bIsArray) { FFormatNamedArguments Args; Args.Add(TEXT("PropertyTitle"), PropertyText); PropertyText = FText::Format(LOCTEXT("ArrayAsText", "Array of {PropertyTitle}s"), Args); } else if (Type.bIsReference) { FFormatNamedArguments Args; Args.Add(TEXT("PropertyTitle"), PropertyText); PropertyText = FText::Format(LOCTEXT("PropertyByRef", "{PropertyTitle} (by ref)"), Args); } return PropertyText; } void UEdGraphSchema_K2::GetVariableTypeTree( TArray< TSharedPtr >& TypeTree, bool bAllowExec, bool bAllowWildCard ) const { // Clear the list TypeTree.Empty(); if( bAllowExec ) { TypeTree.Add( MakeShareable( new FPinTypeTreeInfo(GetCategoryText(PC_Exec, true), PC_Exec, this, LOCTEXT("ExecType", "Execution pin")) ) ); } TypeTree.Add( MakeShareable( new FPinTypeTreeInfo(GetCategoryText(PC_Boolean, true), PC_Boolean, this, LOCTEXT("BooleanType", "True or false value")) ) ); TypeTree.Add( MakeShareable( new FPinTypeTreeInfo(GetCategoryText(PC_Byte, true), PC_Byte, this, LOCTEXT("ByteType", "8 bit number")) ) ); TypeTree.Add( MakeShareable( new FPinTypeTreeInfo(GetCategoryText(PC_Int, true), PC_Int, this, LOCTEXT("IntegerType", "Integer number")) ) ); TypeTree.Add( MakeShareable( new FPinTypeTreeInfo(GetCategoryText(PC_Float, true), PC_Float, this, LOCTEXT("FloatType", "Floating point number")) ) ); TypeTree.Add( MakeShareable( new FPinTypeTreeInfo(GetCategoryText(PC_Name, true), PC_Name, this, LOCTEXT("NameType", "A text name")) ) ); TypeTree.Add( MakeShareable( new FPinTypeTreeInfo(GetCategoryText(PC_String, true), PC_String, this, LOCTEXT("StringType", "A text string")) ) ); TypeTree.Add( MakeShareable( new FPinTypeTreeInfo(GetCategoryText(PC_Text, true), PC_Text, this, LOCTEXT("TextType", "A localizable text string")) ) ); // Add in special first-class struct types TypeTree.Add( MakeShareable( new FPinTypeTreeInfo(PC_Struct, VectorStruct, LOCTEXT("VectorType", "A 3D vector")) ) ); TypeTree.Add( MakeShareable( new FPinTypeTreeInfo(PC_Struct, RotatorStruct, LOCTEXT("RotatorType", "A 3D rotation")) ) ); TypeTree.Add( MakeShareable( new FPinTypeTreeInfo(PC_Struct, TransformStruct, LOCTEXT("TransformType", "A 3D transformation, including translation, rotation and 3D scale.")) ) ); // Add wildcard type if (bAllowWildCard) { TypeTree.Add( MakeShareable( new FPinTypeTreeInfo(GetCategoryText(PC_Wildcard, true), PC_Wildcard, this, LOCTEXT("WildcardType", "Wildcard type (unspecified).")) ) ); } // Add the types that have subtrees TypeTree.Add( MakeShareable( new FPinTypeTreeInfo(GetCategoryText(PC_Struct, true), PC_Struct, this, LOCTEXT("StructType", "Struct (value) types."), true) ) ); TypeTree.Add( MakeShareable( new FPinTypeTreeInfo(GetCategoryText(PC_Object, true), PC_Object, this, LOCTEXT("ObjectType", "Object pointer."), true) ) ); TypeTree.Add( MakeShareable( new FPinTypeTreeInfo(GetCategoryText(PC_Interface, true), PC_Interface, this, LOCTEXT("InterfaceType", "Interface pointer."), true) ) ); TypeTree.Add( MakeShareable( new FPinTypeTreeInfo(GetCategoryText(PC_Class, true), PC_Class, this, LOCTEXT("ClassType", "Class pointers."), true) ) ); TypeTree.Add( MakeShareable( new FPinTypeTreeInfo(GetCategoryText(PC_Enum, true), PC_Enum, this, LOCTEXT("EnumType", "Enumeration types."), true) ) ); } void UEdGraphSchema_K2::GetVariableIndexTypeTree( TArray< TSharedPtr >& TypeTree, bool bAllowExec, bool bAllowWildcard ) const { // Clear the list TypeTree.Empty(); if( bAllowExec ) { TypeTree.Add( MakeShareable( new FPinTypeTreeInfo(GetCategoryText(PC_Exec, true), PC_Exec, this, LOCTEXT("ExecIndexType", "Execution pin")) ) ); } TypeTree.Add( MakeShareable( new FPinTypeTreeInfo(GetCategoryText(PC_Boolean, true), PC_Boolean, this, LOCTEXT("BooleanIndexType", "True or false value")) ) ); TypeTree.Add( MakeShareable( new FPinTypeTreeInfo(GetCategoryText(PC_Byte, true), PC_Byte, this, LOCTEXT("ByteIndexType", "8 bit number")) ) ); TypeTree.Add( MakeShareable( new FPinTypeTreeInfo(GetCategoryText(PC_Int, true), PC_Int, this, LOCTEXT("IntegerIndexType", "Integer number")) ) ); // Add wildcard type if (bAllowWildcard) { TypeTree.Add( MakeShareable( new FPinTypeTreeInfo(GetCategoryText(PC_Wildcard, true), PC_Wildcard, this, LOCTEXT("WildcardIndexType", "Wildcard type (unspecified).")) ) ); } // Add the types that have subtrees TypeTree.Add( MakeShareable( new FPinTypeTreeInfo(GetCategoryText(PC_Enum, true), PC_Enum, this, LOCTEXT("EnumIndexType", "Enumeration types."), true) ) ); } bool UEdGraphSchema_K2::DoesTypeHaveSubtypes(const FString& Category) const { return (Category == PC_Struct) || (Category == PC_Object) || (Category == PC_Interface) || (Category == PC_Class) || (Category == PC_Enum); } struct FWildcardArrayPinHelper { static bool CheckArrayCompatibility(const UEdGraphPin* OutputPin, const UEdGraphPin* InputPin, bool bIgnoreArray) { if (bIgnoreArray) { return true; } const UK2Node* OwningNode = InputPin ? Cast(InputPin->GetOwningNode()) : NULL; const bool bInputWildcardPinAcceptsArray = !OwningNode || OwningNode->DoesInputWildcardPinAcceptArray(InputPin); if (bInputWildcardPinAcceptsArray) { return true; } const bool bCheckInputPin = (InputPin->PinType.PinCategory == GetDefault()->PC_Wildcard) && !InputPin->PinType.bIsArray; const bool bArrayOutputPin = OutputPin && OutputPin->PinType.bIsArray; return !(bCheckInputPin && bArrayOutputPin); } }; bool UEdGraphSchema_K2::ArePinsCompatible(const UEdGraphPin* PinA, const UEdGraphPin* PinB, const UClass* CallingContext, bool bIgnoreArray /*= false*/) const { if ((PinA->Direction == EGPD_Input) && (PinB->Direction == EGPD_Output)) { return FWildcardArrayPinHelper::CheckArrayCompatibility(PinB, PinA, bIgnoreArray) && ArePinTypesCompatible(PinB->PinType, PinA->PinType, CallingContext, bIgnoreArray); } else if ((PinB->Direction == EGPD_Input) && (PinA->Direction == EGPD_Output)) { return FWildcardArrayPinHelper::CheckArrayCompatibility(PinA, PinB, bIgnoreArray) && ArePinTypesCompatible(PinA->PinType, PinB->PinType, CallingContext, bIgnoreArray); } else { return false; } } bool UEdGraphSchema_K2::ArePinTypesCompatible(const FEdGraphPinType& Output, const FEdGraphPinType& Input, const UClass* CallingContext, bool bIgnoreArray /*= false*/) const { if( !bIgnoreArray && (Output.bIsArray != Input.bIsArray) && (Input.PinCategory != PC_Wildcard || Input.bIsArray) ) { return false; } else if (Output.PinCategory == Input.PinCategory) { if ((Output.PinSubCategory == Input.PinSubCategory) && (Output.PinSubCategoryObject == Input.PinSubCategoryObject) && (Output.PinSubCategoryMemberReference == Input.PinSubCategoryMemberReference)) { return true; } else if (Output.PinCategory == PC_Interface) { UClass const* OutputClass = Cast(Output.PinSubCategoryObject.Get()); check(OutputClass && OutputClass->IsChildOf(UInterface::StaticClass())); UClass const* InputClass = Cast(Input.PinSubCategoryObject.Get()); check(InputClass && InputClass->IsChildOf(UInterface::StaticClass())); return OutputClass->IsChildOf(InputClass); } else if ((Output.PinCategory == PC_Object) || (Output.PinCategory == PC_Struct) || (Output.PinCategory == PC_Class)) { // Subcategory mismatch, but the two could be castable // Only allow a match if the input is a superclass of the output UStruct const* OutputObject = (Output.PinSubCategory == PSC_Self) ? CallingContext : Cast(Output.PinSubCategoryObject.Get()); UStruct const* InputObject = (Input.PinSubCategory == PSC_Self) ? CallingContext : Cast(Input.PinSubCategoryObject.Get()); if ((OutputObject != NULL) && (InputObject != NULL)) { if (Output.PinCategory == PC_Struct) { return OutputObject->IsChildOf(InputObject) && FStructUtils::TheSameLayout(OutputObject, InputObject); } // Special Case: Cannot mix interface and non-interface calls, because the pointer size is different under the hood const bool bInputIsInterface = InputObject->IsChildOf(UInterface::StaticClass()); const bool bOutputIsInterface = OutputObject->IsChildOf(UInterface::StaticClass()); if (bInputIsInterface != bOutputIsInterface) { UClass const* OutputClass = Cast(OutputObject); UClass const* InputClass = Cast(InputObject); if (bInputIsInterface && (OutputClass != NULL)) { return OutputClass->ImplementsInterface(InputClass); } else if (bOutputIsInterface && (InputClass != NULL)) { return InputClass->ImplementsInterface(OutputClass); } } return OutputObject->IsChildOf(InputObject) && (bInputIsInterface == bOutputIsInterface); } } else if ((Output.PinCategory == PC_Byte) && (Output.PinSubCategory == Input.PinSubCategory)) { // NOTE: This allows enums to be converted to bytes. Long-term we don't want to allow that, but we need it // for now until we have == for enums in order to be able to compare them. if (Input.PinSubCategoryObject == NULL) { return true; } } else if (PC_Delegate == Output.PinCategory || PC_MCDelegate == Output.PinCategory) { auto CanUseFunction = [](const UFunction* Func) -> bool { return Func && (Func->HasAllFlags(RF_LoadCompleted) || !Func->HasAnyFlags(RF_NeedLoad | RF_WasLoaded)); }; const UFunction* OutFunction = FMemberReference::ResolveSimpleMemberReference(Output.PinSubCategoryMemberReference); if (!CanUseFunction(OutFunction)) { OutFunction = NULL; } if (!OutFunction && Output.PinSubCategoryMemberReference.GetMemberParentClass()) { const UClass* ParentClass = Output.PinSubCategoryMemberReference.GetMemberParentClass(); const UBlueprint* BPOwner = Cast(ParentClass->ClassGeneratedBy); if (BPOwner && BPOwner->SkeletonGeneratedClass && (BPOwner->SkeletonGeneratedClass != ParentClass)) { OutFunction = BPOwner->SkeletonGeneratedClass->FindFunctionByName(Output.PinSubCategoryMemberReference.MemberName); } } const UFunction* InFunction = FMemberReference::ResolveSimpleMemberReference(Input.PinSubCategoryMemberReference); if (!CanUseFunction(InFunction)) { InFunction = NULL; } if (!InFunction && Input.PinSubCategoryMemberReference.GetMemberParentClass()) { const UClass* ParentClass = Input.PinSubCategoryMemberReference.GetMemberParentClass(); const UBlueprint* BPOwner = Cast(ParentClass->ClassGeneratedBy); if (BPOwner && BPOwner->SkeletonGeneratedClass && (BPOwner->SkeletonGeneratedClass != ParentClass)) { InFunction = BPOwner->SkeletonGeneratedClass->FindFunctionByName(Input.PinSubCategoryMemberReference.MemberName); } } return !OutFunction || !InFunction || OutFunction->IsSignatureCompatibleWith(InFunction); } } else if (Output.PinCategory == PC_Wildcard || Input.PinCategory == PC_Wildcard) { // If this is an Index Wildcard we have to check compatibility for indexing types if (Output.PinSubCategory == PSC_Index) { return IsIndexWildcardCompatible(Input); } else if (Input.PinSubCategory == PSC_Index) { return IsIndexWildcardCompatible(Output); } return true; } else if ((Output.PinCategory == PC_Object) && (Input.PinCategory == PC_Interface)) { UClass const* OutputClass = Cast(Output.PinSubCategoryObject.Get()); UClass const* InterfaceClass = Cast(Input.PinSubCategoryObject.Get()); if ((OutputClass == nullptr) && (Output.PinSubCategory == PSC_Self)) { OutputClass = CallingContext; } return OutputClass && (OutputClass->ImplementsInterface(InterfaceClass) || OutputClass->IsChildOf(InterfaceClass)); } // Pins representing BLueprint objects and subclass of UObject can match when EditoronlyBP.bAllowClassAndBlueprintPinMatching=true (BaseEngine.ini) // It's required for converting all UBlueprint references into UClass. struct FObjClassAndBlueprintHelper { private: bool bAllow; public: FObjClassAndBlueprintHelper() : bAllow(false) { GConfig->GetBool(TEXT("EditoronlyBP"), TEXT("bAllowClassAndBlueprintPinMatching"), bAllow, GEditorIni); } bool Match(const FEdGraphPinType& A, const FEdGraphPinType& B, const UEdGraphSchema_K2& Schema) const { if (bAllow && (B.PinCategory == Schema.PC_Object) && (A.PinCategory == Schema.PC_Class)) { const bool bAIsObjectClass = (UObject::StaticClass() == A.PinSubCategoryObject.Get()); const UClass* BClass = Cast(B.PinSubCategoryObject.Get()); const bool bBIsBlueprintObj = BClass && BClass->IsChildOf(UBlueprint::StaticClass()); return bAIsObjectClass && bBIsBlueprintObj; } return false; } }; static FObjClassAndBlueprintHelper MatchHelper; if (MatchHelper.Match(Input, Output, *this) || MatchHelper.Match(Output, Input, *this)) { return true; } return false; } void UEdGraphSchema_K2::BreakNodeLinks(UEdGraphNode& TargetNode) const { UBlueprint* Blueprint = FBlueprintEditorUtils::FindBlueprintForNodeChecked(&TargetNode); Super::BreakNodeLinks(TargetNode); FBlueprintEditorUtils::MarkBlueprintAsModified(Blueprint); } void UEdGraphSchema_K2::BreakPinLinks(UEdGraphPin& TargetPin, bool bSendsNodeNotifcation) const { const FScopedTransaction Transaction( NSLOCTEXT("UnrealEd", "GraphEd_BreakPinLinks", "Break Pin Links") ); // cache this here, as BreakPinLinks can trigger a node reconstruction invalidating the TargetPin referenceS UBlueprint* const Blueprint = FBlueprintEditorUtils::FindBlueprintForNodeChecked(TargetPin.GetOwningNode()); Super::BreakPinLinks(TargetPin, bSendsNodeNotifcation); FBlueprintEditorUtils::MarkBlueprintAsModified(Blueprint); } void UEdGraphSchema_K2::BreakSinglePinLink(UEdGraphPin* SourcePin, UEdGraphPin* TargetPin) { const FScopedTransaction Transaction( NSLOCTEXT("UnrealEd", "GraphEd_BreakSinglePinLink", "Break Pin Link") ); UBlueprint* Blueprint = FBlueprintEditorUtils::FindBlueprintForNodeChecked(TargetPin->GetOwningNode()); Super::BreakSinglePinLink(SourcePin, TargetPin); FBlueprintEditorUtils::MarkBlueprintAsModified(Blueprint); } void UEdGraphSchema_K2::ReconstructNode(UEdGraphNode& TargetNode, bool bIsBatchRequest/*=false*/) const { { TArray NodeChildren; GetObjectsWithOuter(&TargetNode, NodeChildren, false); for (int32 Iter = 0; Iter < NodeChildren.Num(); ++Iter) { UEdGraphPin* Pin = Cast(NodeChildren[Iter]); const bool bIsValidPin = !Pin || (Pin->HasAllFlags(RF_PendingKill) && !Pin->LinkedTo.Num()) || TargetNode.Pins.Contains(Pin); if (!bIsValidPin) { UE_LOG(LogBlueprint, Error, TEXT("Broken Node: %s keeps removed/invalid pin: %s. Try refresh all nodes."), *TargetNode.GetFullName(), *Pin->GetFullName()); } } } Super::ReconstructNode(TargetNode, bIsBatchRequest); // If the reconstruction is being handled by something doing a batch (i.e. the blueprint autoregenerating itself), defer marking the blueprint as modified to prevent multiple recompiles if (!bIsBatchRequest) { const UK2Node* K2Node = Cast(&TargetNode); if (K2Node && K2Node->NodeCausesStructuralBlueprintChange()) { UBlueprint* Blueprint = FBlueprintEditorUtils::FindBlueprintForNodeChecked(&TargetNode); FBlueprintEditorUtils::MarkBlueprintAsStructurallyModified(Blueprint); } } } bool UEdGraphSchema_K2::CanEncapuslateNode(UEdGraphNode const& TestNode) const { // Can't encapsulate entry points (may relax this restriction in the future, but it makes sense for now) return !TestNode.IsA(UK2Node_FunctionTerminator::StaticClass()) && TestNode.GetClass() != UK2Node_Tunnel::StaticClass(); //Tunnel nodes getting sucked into collapsed graphs fails badly, want to allow derived types though(composite node/Macroinstances) } void UEdGraphSchema_K2::HandleGraphBeingDeleted(UEdGraph& GraphBeingRemoved) const { if (UBlueprint* Blueprint = FBlueprintEditorUtils::FindBlueprintForGraph(&GraphBeingRemoved)) { // Look for collapsed graph nodes that reference this graph TArray CompositeNodes; FBlueprintEditorUtils::GetAllNodesOfClass(Blueprint, /*out*/ CompositeNodes); TSet NodesToDelete; for (int32 i = 0; i < CompositeNodes.Num(); ++i) { UK2Node_Composite* CompositeNode = CompositeNodes[i]; if (CompositeNode->BoundGraph == &GraphBeingRemoved) { NodesToDelete.Add(CompositeNode); } } // Delete the node that owns us ensure(NodesToDelete.Num() <= 1); for (TSet::TIterator It(NodesToDelete); It; ++It) { UK2Node_Composite* NodeToDelete = *It; // Prevent re-entrancy here NodeToDelete->BoundGraph = NULL; NodeToDelete->Modify(); NodeToDelete->DestroyNode(); } } } void UEdGraphSchema_K2::TrySetDefaultValue(UEdGraphPin& Pin, const FString& NewDefaultValue) const { FString UseDefaultValue; UObject* UseDefaultObject = NULL; FText UseDefaultText; if ((Pin.PinType.PinCategory == PC_Object) || (Pin.PinType.PinCategory == PC_Class) || (Pin.PinType.PinCategory == PC_Interface)) { UseDefaultObject = FindObject(ANY_PACKAGE, *NewDefaultValue); UseDefaultValue = NULL; } else if(Pin.PinType.PinCategory == PC_Text) { // Set Text pins by string as if it were text. TrySetDefaultText(Pin, FText::FromString(NewDefaultValue)); UseDefaultObject = nullptr; UseDefaultValue.Empty(); return; } else { UseDefaultObject = NULL; UseDefaultValue = NewDefaultValue; } // Check the default value and make it an error if it's bogus if (IsPinDefaultValid(&Pin, UseDefaultValue, UseDefaultObject, UseDefaultText) == TEXT("")) { Pin.DefaultObject = UseDefaultObject; Pin.DefaultValue = UseDefaultValue; Pin.DefaultTextValue = UseDefaultText; } UEdGraphNode* Node = Pin.GetOwningNode(); check(Node); Node->PinDefaultValueChanged(&Pin); UBlueprint* Blueprint = FBlueprintEditorUtils::FindBlueprintForNodeChecked(Node); FBlueprintEditorUtils::MarkBlueprintAsModified(Blueprint); } void UEdGraphSchema_K2::TrySetDefaultObject(UEdGraphPin& Pin, UObject* NewDefaultObject) const { FText UseDefaultText; // Check the default value and make it an error if it's bogus if (IsPinDefaultValid(&Pin, FString(TEXT("")), NewDefaultObject, UseDefaultText) == TEXT("")) { Pin.DefaultObject = NewDefaultObject; Pin.DefaultValue = NULL; Pin.DefaultTextValue = UseDefaultText; } UEdGraphNode* Node = Pin.GetOwningNode(); check(Node); Node->PinDefaultValueChanged(&Pin); UBlueprint* Blueprint = FBlueprintEditorUtils::FindBlueprintForNodeChecked(Node); FBlueprintEditorUtils::MarkBlueprintAsModified(Blueprint); } void UEdGraphSchema_K2::TrySetDefaultText(UEdGraphPin& InPin, const FText& InNewDefaultText) const { // No reason to set the FText if it is not a PC_Text. if(InPin.PinType.PinCategory == PC_Text) { // Check the default value and make it an error if it's bogus if (IsPinDefaultValid(&InPin, TEXT(""), NULL, InNewDefaultText) == TEXT("")) { InPin.DefaultObject = NULL; InPin.DefaultValue = NULL; if(InNewDefaultText.IsEmpty()) { InPin.DefaultTextValue = InNewDefaultText; } else { if(InNewDefaultText.IsCultureInvariant()) { InPin.DefaultTextValue = InNewDefaultText; } else { InPin.DefaultTextValue = FText::ChangeKey(TEXT(""), InPin.GetOwningNode()->NodeGuid.ToString() + TEXT("_") + InPin.PinName + FString::FromInt(InPin.GetOwningNode()->Pins.Find(&InPin)), InNewDefaultText); } } } UEdGraphNode* Node = InPin.GetOwningNode(); check(Node); Node->PinDefaultValueChanged(&InPin); UBlueprint* Blueprint = FBlueprintEditorUtils::FindBlueprintForNodeChecked(Node); FBlueprintEditorUtils::MarkBlueprintAsModified(Blueprint); } } bool UEdGraphSchema_K2::IsAutoCreateRefTerm(const UEdGraphPin* Pin) const { check(Pin != NULL); bool bIsAutoCreateRefTerm = false; UEdGraphNode* OwningNode = Pin->GetOwningNode(); UK2Node_CallFunction* FuncNode = Cast(OwningNode); if (FuncNode) { UFunction* TargetFunction = FuncNode->GetTargetFunction(); if (TargetFunction && !Pin->PinName.IsEmpty()) { TArray AutoCreateParameterNames; FString MetaData = TargetFunction->GetMetaData(FBlueprintMetadata::MD_AutoCreateRefTerm); MetaData.ParseIntoArray(AutoCreateParameterNames, TEXT(","), true); bIsAutoCreateRefTerm = AutoCreateParameterNames.Contains(Pin->PinName); } } return bIsAutoCreateRefTerm; } bool UEdGraphSchema_K2::ShouldHidePinDefaultValue(UEdGraphPin* Pin) const { check(Pin != NULL); if (Pin->bDefaultValueIsIgnored || Pin->PinType.bIsArray || (Pin->PinName == PN_Self && Pin->LinkedTo.Num() > 0) || (Pin->PinType.PinCategory == PC_Exec) || (Pin->PinType.bIsReference && !IsAutoCreateRefTerm(Pin))) { return true; } return false; } bool UEdGraphSchema_K2::ShouldShowAssetPickerForPin(UEdGraphPin* Pin) const { bool bShow = true; if (Pin->PinType.PinCategory == PC_Object) { UClass* ObjectClass = Cast(Pin->PinType.PinSubCategoryObject.Get()); if (ObjectClass) { // Don't show literal buttons for component type objects bShow = !ObjectClass->IsChildOf(UActorComponent::StaticClass()); if (bShow && ObjectClass->IsChildOf(AActor::StaticClass())) { // Only show the picker for Actor classes if the class is placeable and we are in the level script bShow = !ObjectClass->HasAllClassFlags(CLASS_NotPlaceable) && FBlueprintEditorUtils::IsLevelScriptBlueprint(FBlueprintEditorUtils::FindBlueprintForNode(Pin->GetOwningNode())); } if (bShow) { if (UK2Node_CallFunction* CallFunctionNode = Cast(Pin->GetOwningNode())) { if ( UFunction* FunctionRef = CallFunctionNode->GetTargetFunction() ) { const UEdGraphPin* WorldContextPin = CallFunctionNode->FindPin(FunctionRef->GetMetaData(FBlueprintMetadata::MD_WorldContext)); bShow = ( WorldContextPin != Pin ); } } } } } return bShow; } void UEdGraphSchema_K2::SetPinDefaultValue(UEdGraphPin* Pin, const UFunction* Function, const UProperty* Param) const { if ((Function != nullptr) && (Param != nullptr)) { bool bHasAutomaticValue = false; const FString MetadataDefaultValue = Function->GetMetaData(*Param->GetName()); if (!MetadataDefaultValue.IsEmpty()) { // Specified default value in the metadata Pin->AutogeneratedDefaultValue = MetadataDefaultValue; bHasAutomaticValue = true; } else { const FName MetadataCppDefaultValueKey( *(FString(TEXT("CPP_Default_")) + Param->GetName()) ); const FString MetadataCppDefaultValue = Function->GetMetaData(MetadataCppDefaultValueKey); if (!MetadataCppDefaultValue.IsEmpty()) { Pin->AutogeneratedDefaultValue = MetadataCppDefaultValue; bHasAutomaticValue = true; } } if (bHasAutomaticValue) { if (Pin->PinType.PinCategory == PC_Text) { Pin->DefaultTextValue = FText::AsCultureInvariant(Pin->AutogeneratedDefaultValue); } else { Pin->DefaultValue = Pin->AutogeneratedDefaultValue; } } } if (Pin->DefaultValue.Len() == 0) { // Set the default value to (T)0 SetPinDefaultValueBasedOnType(Pin); } } void UEdGraphSchema_K2::SetPinDefaultValueBasedOnType(UEdGraphPin* Pin) const { // Create a useful default value based on the pin type if(Pin->PinType.bIsArray) { Pin->AutogeneratedDefaultValue = TEXT(""); } else if (Pin->PinType.PinCategory == PC_Int) { Pin->AutogeneratedDefaultValue = TEXT("0"); } else if(Pin->PinType.PinCategory == PC_Byte) { UEnum* EnumPtr = Cast(Pin->PinType.PinSubCategoryObject.Get()); if(EnumPtr) { // First element of enum can change. If the enum is { A, B, C } and the default value is A, // the defult value should not change when enum will be changed into { N, A, B, C } Pin->AutogeneratedDefaultValue = TEXT(""); Pin->DefaultValue = EnumPtr->GetEnumName(0); return; } else { Pin->AutogeneratedDefaultValue = TEXT("0"); } } else if (Pin->PinType.PinCategory == PC_Float) { Pin->AutogeneratedDefaultValue = TEXT("0.0"); } else if (Pin->PinType.PinCategory == PC_Boolean) { Pin->AutogeneratedDefaultValue = TEXT("false"); } else if (Pin->PinType.PinCategory == PC_Name) { Pin->AutogeneratedDefaultValue = TEXT("None"); } else if ((Pin->PinType.PinCategory == PC_Struct) && ((Pin->PinType.PinSubCategoryObject == VectorStruct) || (Pin->PinType.PinSubCategoryObject == RotatorStruct))) { Pin->AutogeneratedDefaultValue = TEXT("0, 0, 0"); } else { Pin->AutogeneratedDefaultValue = TEXT(""); } Pin->DefaultValue = Pin->AutogeneratedDefaultValue; } void UEdGraphSchema_K2::ValidateExistingConnections(UEdGraphPin* Pin) { const UEdGraphSchema_K2* K2Schema = GetDefault(); // Break any newly invalid links TArray BrokenLinks; for (int32 Index = 0; Index < Pin->LinkedTo.Num(); ) { UEdGraphPin* OtherPin = Pin->LinkedTo[Index]; if (K2Schema->ArePinsCompatible(Pin, OtherPin)) { ++Index; } else { OtherPin->LinkedTo.Remove(Pin); Pin->LinkedTo.RemoveAtSwap(Index); BrokenLinks.Add(OtherPin); } } // Cascade the check for changed pin types for (TArray::TIterator PinIt(BrokenLinks); PinIt; ++PinIt) { UEdGraphPin* OtherPin = *PinIt; OtherPin->GetOwningNode()->PinConnectionListChanged(OtherPin); } } UFunction* UEdGraphSchema_K2::FindSetVariableByNameFunction(const FEdGraphPinType& PinType) { //!!!! Keep this function synced with FExposeOnSpawnValidator::IsSupported !!!! struct FIsCustomStructureParamHelper { static bool Is(const UObject* Obj) { static const FName BlueprintTypeName(TEXT("BlueprintType")); const auto Struct = Cast(Obj); return Struct ? Struct->GetBoolMetaData(BlueprintTypeName) : false; } }; const UEdGraphSchema_K2* K2Schema = GetDefault(); FName SetFunctionName = NAME_None; if(PinType.PinCategory == K2Schema->PC_Int) { static FName SetIntName(GET_FUNCTION_NAME_CHECKED(UKismetSystemLibrary, SetIntPropertyByName)); SetFunctionName = SetIntName; } else if(PinType.PinCategory == K2Schema->PC_Byte) { static FName SetByteName(GET_FUNCTION_NAME_CHECKED(UKismetSystemLibrary, SetBytePropertyByName)); SetFunctionName = SetByteName; } else if(PinType.PinCategory == K2Schema->PC_Float) { static FName SetFloatName(GET_FUNCTION_NAME_CHECKED(UKismetSystemLibrary, SetFloatPropertyByName)); SetFunctionName = SetFloatName; } else if(PinType.PinCategory == K2Schema->PC_Boolean) { static FName SetBoolName(GET_FUNCTION_NAME_CHECKED(UKismetSystemLibrary, SetBoolPropertyByName)); SetFunctionName = SetBoolName; } else if(PinType.PinCategory == K2Schema->PC_Object) { static FName SetObjectName(GET_FUNCTION_NAME_CHECKED(UKismetSystemLibrary, SetObjectPropertyByName)); SetFunctionName = SetObjectName; } else if (PinType.PinCategory == K2Schema->PC_Class) { static FName SetObjectName(GET_FUNCTION_NAME_CHECKED(UKismetSystemLibrary, SetClassPropertyByName)); SetFunctionName = SetObjectName; } else if(PinType.PinCategory == K2Schema->PC_Interface) { static FName SetObjectName(GET_FUNCTION_NAME_CHECKED(UKismetSystemLibrary, SetObjectPropertyByName)); SetFunctionName = SetObjectName; } else if(PinType.PinCategory == K2Schema->PC_String) { static FName SetStringName(GET_FUNCTION_NAME_CHECKED(UKismetSystemLibrary, SetStringPropertyByName)); SetFunctionName = SetStringName; } else if ( PinType.PinCategory == K2Schema->PC_Text) { static FName SetTextName(GET_FUNCTION_NAME_CHECKED(UKismetSystemLibrary, SetTextPropertyByName)); SetFunctionName = SetTextName; } else if(PinType.PinCategory == K2Schema->PC_Name) { static FName SetNameName(GET_FUNCTION_NAME_CHECKED(UKismetSystemLibrary, SetNamePropertyByName)); SetFunctionName = SetNameName; } else if(PinType.PinCategory == K2Schema->PC_Struct && PinType.PinSubCategoryObject == VectorStruct) { static FName SetVectorName(GET_FUNCTION_NAME_CHECKED(UKismetSystemLibrary, SetVectorPropertyByName)); SetFunctionName = SetVectorName; } else if(PinType.PinCategory == K2Schema->PC_Struct && PinType.PinSubCategoryObject == RotatorStruct) { static FName SetRotatorName(GET_FUNCTION_NAME_CHECKED(UKismetSystemLibrary, SetRotatorPropertyByName)); SetFunctionName = SetRotatorName; } else if(PinType.PinCategory == K2Schema->PC_Struct && PinType.PinSubCategoryObject == ColorStruct) { static FName SetLinearColorName(GET_FUNCTION_NAME_CHECKED(UKismetSystemLibrary, SetLinearColorPropertyByName)); SetFunctionName = SetLinearColorName; } else if(PinType.PinCategory == K2Schema->PC_Struct && PinType.PinSubCategoryObject == TransformStruct) { static FName SetTransformName(GET_FUNCTION_NAME_CHECKED(UKismetSystemLibrary, SetTransformPropertyByName)); SetFunctionName = SetTransformName; } else if (PinType.PinCategory == K2Schema->PC_Struct && PinType.PinSubCategoryObject == FCollisionProfileName::StaticStruct()) { static FName SetStructureName(GET_FUNCTION_NAME_CHECKED(UKismetSystemLibrary, SetCollisionProfileNameProperty)); SetFunctionName = SetStructureName; } else if (PinType.PinCategory == K2Schema->PC_Struct && FIsCustomStructureParamHelper::Is(PinType.PinSubCategoryObject.Get())) { static FName SetStructureName(GET_FUNCTION_NAME_CHECKED(UKismetSystemLibrary, SetStructurePropertyByName)); SetFunctionName = SetStructureName; } UFunction* Function = NULL; if(SetFunctionName != NAME_None) { if(PinType.bIsArray) { static FName SetArrayName(GET_FUNCTION_NAME_CHECKED(UKismetArrayLibrary, SetArrayPropertyByName)); Function = FindField(UKismetArrayLibrary::StaticClass(), SetArrayName); } else { Function = FindField(UKismetSystemLibrary::StaticClass(), SetFunctionName); } } return Function; } bool UEdGraphSchema_K2::CanPromotePinToVariable( const UEdGraphPin& Pin ) const { const FEdGraphPinType& PinType = Pin.PinType; bool bCanPromote = (PinType.PinCategory != PC_Wildcard && PinType.PinCategory != PC_Exec ) ? true : false; const UK2Node* Node = Cast(Pin.GetOwningNode()); const UBlueprint* OwningBlueprint = Node->GetBlueprint(); if (!OwningBlueprint || (OwningBlueprint->BlueprintType == BPTYPE_MacroLibrary) || (OwningBlueprint->BlueprintType == BPTYPE_FunctionLibrary) || IsStaticFunctionGraph(Node->GetGraph())) { // Never allow promotion in macros, because there's not a scope to define them in bCanPromote = false; } else { if (PinType.PinCategory == PC_Delegate) { bCanPromote = false; } else if ((PinType.PinCategory == PC_Object) || (PinType.PinCategory == PC_Interface)) { if (PinType.PinSubCategoryObject != NULL) { if (UClass* Class = Cast(PinType.PinSubCategoryObject.Get())) { bCanPromote = UEdGraphSchema_K2::IsAllowableBlueprintVariableType(Class); } } } else if ((PinType.PinCategory == PC_Struct) && (PinType.PinSubCategoryObject != NULL)) { if (UScriptStruct* Struct = Cast(PinType.PinSubCategoryObject.Get())) { bCanPromote = UEdGraphSchema_K2::IsAllowableBlueprintVariableType(Struct); } } } return bCanPromote; } bool UEdGraphSchema_K2::CanSplitStructPin( const UEdGraphPin& Pin ) const { return (Pin.LinkedTo.Num() == 0 && PinHasSplittableStructType(&Pin) && Pin.GetOwningNode()->AllowSplitPins()); } bool UEdGraphSchema_K2::CanRecombineStructPin( const UEdGraphPin& Pin ) const { bool bCanRecombine = (Pin.ParentPin != NULL && Pin.LinkedTo.Num() == 0); if (bCanRecombine) { // Go through all the other subpins and ensure they also are not connected to anything TArray PinsToExamine = Pin.ParentPin->SubPins; int32 PinIndex = 0; while (bCanRecombine && PinIndex < PinsToExamine.Num()) { UEdGraphPin* SubPin = PinsToExamine[PinIndex]; if (SubPin->LinkedTo.Num() > 0) { bCanRecombine = false; } else if (SubPin->SubPins.Num() > 0) { PinsToExamine.Append(SubPin->SubPins); } ++PinIndex; } } return bCanRecombine; } void UEdGraphSchema_K2::GetGraphDisplayInformation(const UEdGraph& Graph, /*out*/ FGraphDisplayInfo& DisplayInfo) const { DisplayInfo.DocLink = TEXT("Shared/Editors/BlueprintEditor/GraphTypes"); DisplayInfo.PlainName = FText::FromString( Graph.GetName() ); // Fallback is graph name UFunction* Function = NULL; UBlueprint* Blueprint = FBlueprintEditorUtils::FindBlueprintForGraph(&Graph); if (Blueprint) { Function = Blueprint->SkeletonGeneratedClass->FindFunctionByName(Graph.GetFName()); } const EGraphType GraphType = GetGraphType(&Graph); if (GraphType == GT_Ubergraph) { DisplayInfo.DocExcerptName = TEXT("EventGraph"); if (Graph.GetFName() == GN_EventGraph) { // localized name for the first event graph DisplayInfo.PlainName = LOCTEXT("GraphDisplayName_EventGraph", "EventGraph"); DisplayInfo.Tooltip = DisplayInfo.PlainName.ToString(); } else { DisplayInfo.Tooltip = Graph.GetName(); } } else if (GraphType == GT_Function) { if ( Graph.GetFName() == FN_UserConstructionScript ) { DisplayInfo.PlainName = LOCTEXT("GraphDisplayName_ConstructionScript", "ConstructionScript"); DisplayInfo.Tooltip = LOCTEXT("GraphTooltip_ConstructionScript", "Function executed when Blueprint is placed or modified.").ToString(); DisplayInfo.DocExcerptName = TEXT("ConstructionScript"); } else { // If we found a function from this graph.. if (Function) { DisplayInfo.PlainName = FText::FromString(Function->GetName()); DisplayInfo.Tooltip = UK2Node_CallFunction::GetDefaultTooltipForFunction(Function); // grab its tooltip } else { DisplayInfo.Tooltip = Graph.GetName(); } DisplayInfo.DocExcerptName = TEXT("FunctionGraph"); } } else if (GraphType == GT_Macro) { // Show macro description if set FKismetUserDeclaredFunctionMetadata* MetaData = UK2Node_MacroInstance::GetAssociatedGraphMetadata(&Graph); DisplayInfo.Tooltip = (MetaData && MetaData->ToolTip.Len() > 0) ? MetaData->ToolTip : Graph.GetName(); DisplayInfo.DocExcerptName = TEXT("MacroGraph"); } else if (GraphType == GT_Animation) { DisplayInfo.PlainName = LOCTEXT("GraphDisplayName_AnimGraph", "AnimGraph"); DisplayInfo.Tooltip = LOCTEXT("GraphTooltip_AnimGraph", "Graph used to blend together different animations.").ToString(); DisplayInfo.DocExcerptName = TEXT("AnimGraph"); } else if (GraphType == GT_StateMachine) { DisplayInfo.Tooltip = Graph.GetName(); DisplayInfo.DocExcerptName = TEXT("StateMachine"); } // Add pure/static/const to notes if set if (Function) { if(Function->HasAnyFunctionFlags(FUNC_BlueprintPure)) { DisplayInfo.Notes.Add(TEXT("pure")); } // since 'static' is implied in a function library, not going to display it (to be consistent with previous behavior) if(Function->HasAnyFunctionFlags(FUNC_Static) && Blueprint->BlueprintType != BPTYPE_FunctionLibrary) { DisplayInfo.Notes.Add(TEXT("static")); } else if(Function->HasAnyFunctionFlags(FUNC_Const)) { DisplayInfo.Notes.Add(TEXT("const")); } } // Mark transient graphs as obviously so if (Graph.HasAllFlags(RF_Transient)) { DisplayInfo.PlainName = FText::FromString( FString::Printf(TEXT("$$ %s $$"), *DisplayInfo.PlainName.ToString()) ); DisplayInfo.Notes.Add(TEXT("intermediate build product")); } if( GEditor && GetDefault()->bShowFriendlyNames ) { DisplayInfo.DisplayName = FText::FromString(FName::NameToDisplayString( DisplayInfo.PlainName.ToString(), false )); } else { DisplayInfo.DisplayName = DisplayInfo.PlainName; } } bool UEdGraphSchema_K2::IsSelfPin(const UEdGraphPin& Pin) const { return (Pin.PinName == PN_Self); } bool UEdGraphSchema_K2::IsDelegateCategory(const FString& Category) const { return (Category == PC_Delegate); } FVector2D UEdGraphSchema_K2::CalculateAveragePositionBetweenNodes(UEdGraphPin* InputPin, UEdGraphPin* OutputPin) { UEdGraphNode* InputNode = InputPin->GetOwningNode(); UEdGraphNode* OutputNode = OutputPin->GetOwningNode(); const FVector2D InputCorner(InputNode->NodePosX, InputNode->NodePosY); const FVector2D OutputCorner(OutputNode->NodePosX, OutputNode->NodePosY); return (InputCorner + OutputCorner) * 0.5f; } bool UEdGraphSchema_K2::IsConstructionScript(const UEdGraph* TestEdGraph) const { TArray EntryNodes; TestEdGraph->GetNodesOfClass(EntryNodes); bool bIsConstructionScript = false; if (EntryNodes.Num() > 0) { UK2Node_FunctionEntry const* const EntryNode = EntryNodes[0]; bIsConstructionScript = (EntryNode->SignatureName == FN_UserConstructionScript); } return bIsConstructionScript; } bool UEdGraphSchema_K2::IsCompositeGraph( const UEdGraph* TestEdGraph ) const { check(TestEdGraph); const EGraphType GraphType = GetGraphType(TestEdGraph); if(GraphType == GT_Function) { //Find the Tunnel node for composite graph and see if its output is a composite node for(auto I = TestEdGraph->Nodes.CreateConstIterator();I;++I) { UEdGraphNode* Node = *I; if(auto Tunnel = Cast(Node)) { if(auto OutNode = Tunnel->OutputSourceNode) { if(Cast(OutNode)) { return true; } } } } } return false; } bool UEdGraphSchema_K2::IsConstFunctionGraph( const UEdGraph* TestEdGraph, bool* bOutIsEnforcingConstCorrectness ) const { check(TestEdGraph); const EGraphType GraphType = GetGraphType(TestEdGraph); if(GraphType == GT_Function) { // Find the entry node for the function graph and see if the 'const' flag is set for(auto I = TestEdGraph->Nodes.CreateConstIterator(); I; ++I) { UEdGraphNode* Node = *I; if(auto EntryNode = Cast(Node)) { if(bOutIsEnforcingConstCorrectness != nullptr) { *bOutIsEnforcingConstCorrectness = EntryNode->bEnforceConstCorrectness; } return (EntryNode->ExtraFlags & FUNC_Const) != 0; } } } if(bOutIsEnforcingConstCorrectness != nullptr) { *bOutIsEnforcingConstCorrectness = false; } return false; } bool UEdGraphSchema_K2::IsStaticFunctionGraph( const UEdGraph* TestEdGraph ) const { check(TestEdGraph); const auto Blueprint = FBlueprintEditorUtils::FindBlueprintForGraph(TestEdGraph); if (Blueprint && (EBlueprintType::BPTYPE_FunctionLibrary == Blueprint->BlueprintType)) { return true; } const EGraphType GraphType = GetGraphType(TestEdGraph); if(GraphType == GT_Function) { // Find the entry node for the function graph and see if the 'static' flag is set for(auto I = TestEdGraph->Nodes.CreateConstIterator(); I; ++I) { UEdGraphNode* Node = *I; if(auto EntryNode = Cast(Node)) { return (EntryNode->ExtraFlags & FUNC_Static) != 0; } } } return false; } void UEdGraphSchema_K2::DroppedAssetsOnGraph(const TArray& Assets, const FVector2D& GraphPosition, UEdGraph* Graph) const { UBlueprint* Blueprint = FBlueprintEditorUtils::FindBlueprintForGraph(Graph); if ((Blueprint != NULL) && FBlueprintEditorUtils::IsActorBased(Blueprint)) { float XOffset = 0.0f; for(int32 AssetIdx=0; AssetIdx < Assets.Num(); AssetIdx++) { FVector2D Position = GraphPosition + (AssetIdx * FVector2D(XOffset, 0.0f)); UObject* Asset = Assets[AssetIdx].GetAsset(); UClass* AssetClass = Asset->GetClass(); if (UBlueprint* BlueprintAsset = Cast(Asset)) { AssetClass = BlueprintAsset->GeneratedClass; } TSubclassOf DestinationComponentType = FComponentAssetBrokerage::GetPrimaryComponentForAsset(AssetClass); if ((DestinationComponentType == NULL) && AssetClass->IsChildOf(AActor::StaticClass())) { DestinationComponentType = UChildActorComponent::StaticClass(); } // Make sure we have an asset type that's registered with the component list if (DestinationComponentType != NULL) { UEdGraph* TempOuter = NewObject((UObject*)Blueprint); TempOuter->SetFlags(RF_Transient); FComponentTypeEntry ComponentType = { FString(), FString(), DestinationComponentType }; IBlueprintNodeBinder::FBindingSet Bindings; Bindings.Add(Asset); UBlueprintComponentNodeSpawner::Create(ComponentType)->Invoke(Graph, Bindings, GraphPosition); } } } } void UEdGraphSchema_K2::DroppedAssetsOnNode(const TArray& Assets, const FVector2D& GraphPosition, UEdGraphNode* Node) const { // @TODO: Should dropping on component node change the component? } void UEdGraphSchema_K2::DroppedAssetsOnPin(const TArray& Assets, const FVector2D& GraphPosition, UEdGraphPin* Pin) const { // If dropping onto an 'object' pin, try and set the literal if ((Pin->PinType.PinCategory == PC_Object) || (Pin->PinType.PinCategory == PC_Interface)) { UClass* PinClass = Cast(Pin->PinType.PinSubCategoryObject.Get()); if(PinClass != NULL) { // Find first asset of type of the pin UObject* Asset = FAssetData::GetFirstAssetDataOfClass(Assets, PinClass).GetAsset(); if(Asset != NULL) { TrySetDefaultObject(*Pin, Asset); } } } } void UEdGraphSchema_K2::GetAssetsNodeHoverMessage(const TArray& Assets, const UEdGraphNode* HoverNode, FString& OutTooltipText, bool& OutOkIcon) const { // No comment at the moment because this doesn't do anything OutTooltipText = TEXT(""); OutOkIcon = false; } void UEdGraphSchema_K2::GetAssetsPinHoverMessage(const TArray& Assets, const UEdGraphPin* HoverPin, FString& OutTooltipText, bool& OutOkIcon) const { OutTooltipText = TEXT(""); OutOkIcon = false; // If dropping onto an 'object' pin, try and set the literal if ((HoverPin->PinType.PinCategory == PC_Object) || (HoverPin->PinType.PinCategory == PC_Interface)) { UClass* PinClass = Cast(HoverPin->PinType.PinSubCategoryObject.Get()); if(PinClass != NULL) { // Find first asset of type of the pin FAssetData AssetData = FAssetData::GetFirstAssetDataOfClass(Assets, PinClass); if(AssetData.IsValid()) { OutOkIcon = true; OutTooltipText = FString::Printf(TEXT("Assign %s to this pin"), *(AssetData.AssetName.ToString())); } else { OutOkIcon = false; OutTooltipText = FString::Printf(TEXT("Not compatible with this pin")); } } } } bool UEdGraphSchema_K2::FadeNodeWhenDraggingOffPin(const UEdGraphNode* Node, const UEdGraphPin* Pin) const { if(Node && Pin && (PC_Delegate == Pin->PinType.PinCategory) && (EGPD_Input == Pin->Direction)) { //When dragging off a delegate pin, we should duck the alpha of all nodes except the Custom Event nodes that are compatible with the delegate signature //This would help reinforce the connection between delegates and their matching events, and make it easier to see at a glance what could be matched up. if(const UK2Node_Event* EventNode = Cast(Node)) { const UEdGraphPin* DelegateOutPin = EventNode->FindPin(UK2Node_Event::DelegateOutputName); if ((NULL != DelegateOutPin) && (ECanCreateConnectionResponse::CONNECT_RESPONSE_DISALLOW != CanCreateConnection(DelegateOutPin, Pin).Response)) { return false; } } if(const UK2Node_CreateDelegate* CreateDelegateNode = Cast(Node)) { const UEdGraphPin* DelegateOutPin = CreateDelegateNode->GetDelegateOutPin(); if ((NULL != DelegateOutPin) && (ECanCreateConnectionResponse::CONNECT_RESPONSE_DISALLOW != CanCreateConnection(DelegateOutPin, Pin).Response)) { return false; } } return true; } return false; } struct FBackwardCompatibilityConversionHelper { static bool ConvertNode( UK2Node* OldNode, const FString& BlueprintPinName, UK2Node* NewNode, const FString& ClassPinName, const UEdGraphSchema_K2& Schema, bool bOnlyWithDefaultBlueprint) { check(OldNode && NewNode); const auto Blueprint = OldNode->GetBlueprint(); auto Graph = OldNode->GetGraph(); if (!Graph) { UE_LOG(LogBlueprint, Warning, TEXT("BackwardCompatibilityNodeConversion Error bp: '%s' node: '%s'. No graph containing the node."), Blueprint ? *Blueprint->GetName() : TEXT("Unknown"), *OldNode->GetName(), *BlueprintPinName); return false; } auto OldBlueprintPin = OldNode->FindPin(BlueprintPinName); if (!OldBlueprintPin) { UE_LOG(LogBlueprint, Warning, TEXT("BackwardCompatibilityNodeConversion Error bp: '%s' node: '%s'. No bp pin found '%s'"), Blueprint ? *Blueprint->GetName() : TEXT("Unknown"), *OldNode->GetName(), *BlueprintPinName); return false; } const bool bNondefaultBPConnected = (OldBlueprintPin->LinkedTo.Num() > 0); const bool bTryConvert = !bNondefaultBPConnected || !bOnlyWithDefaultBlueprint; if (bTryConvert) { // CREATE NEW NODE NewNode->SetFlags(RF_Transactional); Graph->AddNode(NewNode, false, false); NewNode->CreateNewGuid(); NewNode->PostPlacedNewNode(); NewNode->AllocateDefaultPins(); NewNode->NodePosX = OldNode->NodePosX; NewNode->NodePosY = OldNode->NodePosY; const auto ClassPin = NewNode->FindPin(ClassPinName); if (!ClassPin) { UE_LOG(LogBlueprint, Warning, TEXT("BackwardCompatibilityNodeConversion Error bp: '%s' node: '%s'. No class pin found '%s'"), Blueprint ? *Blueprint->GetName() : TEXT("Unknown"), *NewNode->GetName(), *ClassPinName); return false; } auto TargetClass = Cast(ClassPin->PinType.PinSubCategoryObject.Get()); if (!TargetClass) { UE_LOG(LogBlueprint, Warning, TEXT("BackwardCompatibilityNodeConversion Error bp: '%s' node: '%s'. No class found '%s'"), Blueprint ? *Blueprint->GetName() : TEXT("Unknown"), *NewNode->GetName(), *ClassPinName); return false; } // REPLACE BLUEPRINT WITH CLASS if (!bNondefaultBPConnected) { // DEFAULT VALUE const auto UsedBlueprint = Cast(OldBlueprintPin->DefaultObject); ensure(!OldBlueprintPin->DefaultObject || UsedBlueprint); ensure(!UsedBlueprint || *UsedBlueprint->GeneratedClass); UClass* UsedClass = UsedBlueprint ? *UsedBlueprint->GeneratedClass : NULL; Schema.TrySetDefaultObject(*ClassPin, UsedClass); if (ClassPin->DefaultObject != UsedClass) { auto ErrorStr = Schema.IsPinDefaultValid(ClassPin, FString(), UsedClass, FText()); UE_LOG(LogBlueprint, Warning, TEXT("BackwardCompatibilityNodeConversion Error 'cannot set class' in blueprint: %s node: '%s' actor bp: %s, reason: %s"), Blueprint ? *Blueprint->GetName() : TEXT("Unknown"), *OldNode->GetName(), UsedBlueprint ? *UsedBlueprint->GetName() : TEXT("Unknown"), ErrorStr.IsEmpty() ? TEXT("Unknown") : *ErrorStr); return false; } } else { // LINK auto CastNode = NewObject(Graph); CastNode->SetFlags(RF_Transactional); CastNode->TargetType = TargetClass; Graph->AddNode(CastNode, false, false); CastNode->CreateNewGuid(); CastNode->PostPlacedNewNode(); CastNode->AllocateDefaultPins(); const int32 OffsetOnGraph = 200; CastNode->NodePosX = OldNode->NodePosX - OffsetOnGraph; CastNode->NodePosY = OldNode->NodePosY; auto ExecPin = OldNode->GetExecPin(); auto ExecCastPin = CastNode->GetExecPin(); check(ExecCastPin); if (!ExecPin || !Schema.MovePinLinks(*ExecPin, *ExecCastPin).CanSafeConnect()) { UE_LOG(LogBlueprint, Warning, TEXT("BackwardCompatibilityNodeConversion Error 'cannot connect' in blueprint: %s, pin: %s"), Blueprint ? *Blueprint->GetName() : TEXT("Unknown"), *ExecCastPin->PinName); return false; } auto ValidCastPin = CastNode->GetValidCastPin(); check(ValidCastPin); if (!Schema.TryCreateConnection(ValidCastPin, ExecPin)) { UE_LOG(LogBlueprint, Warning, TEXT("BackwardCompatibilityNodeConversion Error 'cannot connect' in blueprint: %s, pin: %s"), Blueprint ? *Blueprint->GetName() : TEXT("Unknown"), *ValidCastPin->PinName); return false; } auto InValidCastPin = CastNode->GetInvalidCastPin(); check(InValidCastPin); if (!Schema.TryCreateConnection(InValidCastPin, ExecPin)) { UE_LOG(LogBlueprint, Warning, TEXT("BackwardCompatibilityNodeConversion Error 'cannot connect' in blueprint: %s, pin: %s"), Blueprint ? *Blueprint->GetName() : TEXT("Unknown"), *InValidCastPin->PinName); return false; } auto CastSourcePin = CastNode->GetCastSourcePin(); check(CastSourcePin); if (!Schema.MovePinLinks(*OldBlueprintPin, *CastSourcePin).CanSafeConnect()) { UE_LOG(LogBlueprint, Warning, TEXT("BackwardCompatibilityNodeConversion Error 'cannot connect' in blueprint: %s, pin: %s"), Blueprint ? *Blueprint->GetName() : TEXT("Unknown"), *CastSourcePin->PinName); return false; } auto CastResultPin = CastNode->GetCastResultPin(); check(CastResultPin); if (!Schema.TryCreateConnection(CastResultPin, ClassPin)) { UE_LOG(LogBlueprint, Warning, TEXT("BackwardCompatibilityNodeConversion Error 'cannot connect' in blueprint: %s, pin: %s"), Blueprint ? *Blueprint->GetName() : TEXT("Unknown"), *CastResultPin->PinName); return false; } } // MOVE OTHER PINS TArray OldPins; OldPins.Add(OldBlueprintPin); for (auto PinIter = NewNode->Pins.CreateIterator(); PinIter; ++PinIter) { UEdGraphPin* const Pin = *PinIter; check(Pin); if (ClassPin != Pin) { const auto OldPin = OldNode->FindPin(Pin->PinName); if(NULL != OldPin) { OldPins.Add(OldPin); if (!Schema.MovePinLinks(*OldPin, *Pin).CanSafeConnect()) { UE_LOG(LogBlueprint, Warning, TEXT("BackwardCompatibilityNodeConversion Error 'cannot connect' in blueprint: %s, pin: %s"), Blueprint ? *Blueprint->GetName() : TEXT("Unknown"), *Pin->PinName); } } else { UE_LOG(LogBlueprint, Warning, TEXT("BackwardCompatibilityNodeConversion Error 'missing old pin' in blueprint: %s"), Blueprint ? *Blueprint->GetName() : TEXT("Unknown"), Pin ? *Pin->PinName : TEXT("Unknown")); } } } OldNode->BreakAllNodeLinks(); for (auto PinIter = OldNode->Pins.CreateIterator(); PinIter; ++PinIter) { if(!OldPins.Contains(*PinIter)) { UEdGraphPin* Pin = *PinIter; UE_LOG(LogBlueprint, Warning, TEXT("BackwardCompatibilityNodeConversion Error 'missing new pin' in blueprint: %s"), Blueprint ? *Blueprint->GetName() : TEXT("Unknown"), Pin ? *Pin->PinName : TEXT("Unknown")); } } Graph->RemoveNode(OldNode); return true; } return false; } struct FFunctionCallParams { const FName OldFuncName; const FName NewFuncName; const FString& BlueprintPinName; const FString& ClassPinName; const UClass* FuncScope; FFunctionCallParams(FName InOldFunc, FName InNewFunc, const FString& InBlueprintPinName, const FString& InClassPinName, const UClass* InFuncScope) : OldFuncName(InOldFunc), NewFuncName(InNewFunc), BlueprintPinName(InBlueprintPinName), ClassPinName(InClassPinName), FuncScope(InFuncScope) { check(FuncScope); } FFunctionCallParams(const FBlueprintCallableFunctionRedirect& FunctionRedirect) : OldFuncName(*FunctionRedirect.OldFunctionName) , NewFuncName(*FunctionRedirect.NewFunctionName) , BlueprintPinName(FunctionRedirect.BlueprintParamName) , ClassPinName(FunctionRedirect.ClassParamName) , FuncScope(NULL) { FuncScope = FindObject(ANY_PACKAGE, *FunctionRedirect.ClassName); } }; static void ConvertFunctionCallNodes(const FFunctionCallParams& ConversionParams, TArray& Nodes, UEdGraph* Graph, const UEdGraphSchema_K2& Schema, bool bOnlyWithDefaultBlueprint) { if (ConversionParams.FuncScope) { const UFunction* OldFunc = ConversionParams.FuncScope->FindFunctionByName(ConversionParams.OldFuncName); check(OldFunc); const UFunction* NewFunc = ConversionParams.FuncScope->FindFunctionByName(ConversionParams.NewFuncName); check(NewFunc); for (auto It = Nodes.CreateIterator(); It; ++It) { if (OldFunc == (*It)->GetTargetFunction()) { auto NewNode = NewObject(Graph); NewNode->SetFromFunction(NewFunc); ConvertNode(*It, ConversionParams.BlueprintPinName, NewNode, ConversionParams.ClassPinName, Schema, bOnlyWithDefaultBlueprint); } } } } }; void UEdGraphSchema_K2::BackwardCompatibilityNodeConversion(UEdGraph* Graph, bool bOnlySafeChanges) const { if (Graph) { { static const FString BlueprintPinName(TEXT("Blueprint")); static const FString ClassPinName(TEXT("Class")); TArray SpawnActorNodes; Graph->GetNodesOfClass(SpawnActorNodes); for (auto It = SpawnActorNodes.CreateIterator(); It; ++It) { FBackwardCompatibilityConversionHelper::ConvertNode( *It, BlueprintPinName, NewObject(Graph), ClassPinName, *this, bOnlySafeChanges); } } { auto Blueprint = FBlueprintEditorUtils::FindBlueprintForGraph(Graph); if (Blueprint && *Blueprint->SkeletonGeneratedClass) { TArray Nodes; Graph->GetNodesOfClass(Nodes); for (const auto& FunctionRedirect : EditoronlyBPFunctionRedirects) { FBackwardCompatibilityConversionHelper::ConvertFunctionCallNodes( FBackwardCompatibilityConversionHelper::FFunctionCallParams(FunctionRedirect), Nodes, Graph, *this, bOnlySafeChanges); } } else { UE_LOG(LogBlueprint, Log, TEXT("BackwardCompatibilityNodeConversion: Blueprint '%s' cannot be fully converted. It has no skeleton class!"), Blueprint ? *Blueprint->GetName() : TEXT("Unknown")); } } /** Fix the old Make/Break Vector, Make/Break Vector 2D, and Make/Break Rotator nodes to use the native function call versions */ TArray MakeStructNodes; Graph->GetNodesOfClass(MakeStructNodes); for(auto It = MakeStructNodes.CreateIterator(); It; ++It) { UK2Node_MakeStruct* OldMakeStructNode = *It; check(NULL != OldMakeStructNode); // user may have since deleted the struct type if (OldMakeStructNode->StructType == NULL) { continue; } // Check to see if the struct has a native make/break that we should try to convert to. if (OldMakeStructNode->StructType && OldMakeStructNode->StructType->HasMetaData(TEXT("HasNativeMake"))) { UFunction* MakeNodeFunction = NULL; // If any pins need to change their names during the conversion, add them to the map. TMap OldPinToNewPinMap; if(OldMakeStructNode->StructType->GetName() == TEXT("Rotator")) { MakeNodeFunction = FindObject(ANY_PACKAGE, TEXT("KismetMathLibrary"))->FindFunctionByName(TEXT("MakeRot")); OldPinToNewPinMap.Add(TEXT("Rotator"), TEXT("ReturnValue")); } else if(OldMakeStructNode->StructType->GetName() == TEXT("Vector")) { MakeNodeFunction = FindObject(ANY_PACKAGE, TEXT("KismetMathLibrary"))->FindFunctionByName(TEXT("MakeVector")); OldPinToNewPinMap.Add(TEXT("Vector"), TEXT("ReturnValue")); } else if(OldMakeStructNode->StructType->GetName() == TEXT("Vector2D")) { MakeNodeFunction = FindObject(ANY_PACKAGE, TEXT("KismetMathLibrary"))->FindFunctionByName(TEXT("MakeVector2D")); OldPinToNewPinMap.Add(TEXT("Vector2D"), TEXT("ReturnValue")); } if(MakeNodeFunction) { UK2Node_CallFunction* CallFunctionNode = NewObject(Graph); check(CallFunctionNode); CallFunctionNode->SetFlags(RF_Transactional); Graph->AddNode(CallFunctionNode, false, false); CallFunctionNode->SetFromFunction(MakeNodeFunction); CallFunctionNode->CreateNewGuid(); CallFunctionNode->PostPlacedNewNode(); CallFunctionNode->AllocateDefaultPins(); CallFunctionNode->NodePosX = OldMakeStructNode->NodePosX; CallFunctionNode->NodePosY = OldMakeStructNode->NodePosY; for(int32 PinIdx = 0; PinIdx < OldMakeStructNode->Pins.Num(); ++PinIdx) { UEdGraphPin* OldPin = OldMakeStructNode->Pins[PinIdx]; UEdGraphPin* NewPin = NULL; // Check to see if the pin name is mapped to a new one, if it is use it, otherwise just search for the pin under the old name FString* NewPinNamePtr = OldPinToNewPinMap.Find(OldPin->PinName); if(NewPinNamePtr) { NewPin = CallFunctionNode->FindPin(*NewPinNamePtr); } else { NewPin = CallFunctionNode->FindPin(OldPin->PinName); } check(NewPin); if(!Graph->GetSchema()->MovePinLinks(*OldPin, *NewPin).CanSafeConnect()) { const UBlueprint* Blueprint = OldMakeStructNode->GetBlueprint(); UE_LOG(LogBlueprint, Warning, TEXT("BackwardCompatibilityNodeConversion Error 'cannot safetly move pin %s to %s' in blueprint: %s"), *OldPin->PinName, *NewPin->PinName, Blueprint ? *Blueprint->GetName() : TEXT("Unknown")); } } OldMakeStructNode->DestroyNode(); } } } TArray BreakStructNodes; Graph->GetNodesOfClass(BreakStructNodes); for(auto It = BreakStructNodes.CreateIterator(); It; ++It) { UK2Node_BreakStruct* OldBreakStructNode = *It; check(NULL != OldBreakStructNode); // user may have since deleted the struct type if (OldBreakStructNode->StructType == NULL) { continue; } // Check to see if the struct has a native make/break that we should try to convert to. if (OldBreakStructNode->StructType && OldBreakStructNode->StructType->HasMetaData(TEXT("HasNativeBreak"))) { UFunction* BreakNodeFunction = NULL; // If any pins need to change their names during the conversion, add them to the map. TMap OldPinToNewPinMap; if(OldBreakStructNode->StructType->GetName() == TEXT("Rotator")) { BreakNodeFunction = FindObject(ANY_PACKAGE, TEXT("KismetMathLibrary"))->FindFunctionByName(TEXT("BreakRot")); OldPinToNewPinMap.Add(TEXT("Rotator"), TEXT("InRot")); } else if(OldBreakStructNode->StructType->GetName() == TEXT("Vector")) { BreakNodeFunction = FindObject(ANY_PACKAGE, TEXT("KismetMathLibrary"))->FindFunctionByName(TEXT("BreakVector")); OldPinToNewPinMap.Add(TEXT("Vector"), TEXT("InVec")); } else if(OldBreakStructNode->StructType->GetName() == TEXT("Vector2D")) { BreakNodeFunction = FindObject(ANY_PACKAGE, TEXT("KismetMathLibrary"))->FindFunctionByName(TEXT("BreakVector2D")); OldPinToNewPinMap.Add(TEXT("Vector2D"), TEXT("InVec")); } if(BreakNodeFunction) { UK2Node_CallFunction* CallFunctionNode = NewObject(Graph); check(CallFunctionNode); CallFunctionNode->SetFlags(RF_Transactional); Graph->AddNode(CallFunctionNode, false, false); CallFunctionNode->SetFromFunction(BreakNodeFunction); CallFunctionNode->CreateNewGuid(); CallFunctionNode->PostPlacedNewNode(); CallFunctionNode->AllocateDefaultPins(); CallFunctionNode->NodePosX = OldBreakStructNode->NodePosX; CallFunctionNode->NodePosY = OldBreakStructNode->NodePosY; for(int32 PinIdx = 0; PinIdx < OldBreakStructNode->Pins.Num(); ++PinIdx) { UEdGraphPin* OldPin = OldBreakStructNode->Pins[PinIdx]; UEdGraphPin* NewPin = NULL; // Check to see if the pin name is mapped to a new one, if it is use it, otherwise just search for the pin under the old name FString* NewPinNamePtr = OldPinToNewPinMap.Find(OldPin->PinName); if(NewPinNamePtr) { NewPin = CallFunctionNode->FindPin(*NewPinNamePtr); } else { NewPin = CallFunctionNode->FindPin(OldPin->PinName); } check(NewPin); if(!Graph->GetSchema()->MovePinLinks(*OldPin, *NewPin).CanSafeConnect()) { const UBlueprint* Blueprint = OldBreakStructNode->GetBlueprint(); UE_LOG(LogBlueprint, Warning, TEXT("BackwardCompatibilityNodeConversion Error 'cannot safetly move pin %s to %s' in blueprint: %s"), *OldPin->PinName, *NewPin->PinName, Blueprint ? *Blueprint->GetName() : TEXT("Unknown")); } } OldBreakStructNode->DestroyNode(); } } } } } UEdGraphNode* UEdGraphSchema_K2::CreateSubstituteNode(UEdGraphNode* Node, const UEdGraph* Graph, FObjectInstancingGraph* InstanceGraph) const { // If this is an event node, create a unique custom event node as a substitute UK2Node_Event* EventNode = Cast(Node); if(EventNode) { if(!Graph) { // Use the node's graph (outer) if an explicit graph was not specified Graph = Node->GetGraph(); } // Can only place events in ubergraphs if (GetGraphType(Graph) != EGraphType::GT_Ubergraph) { return NULL; } // Find the Blueprint that owns the graph UBlueprint* Blueprint = Graph ? FBlueprintEditorUtils::FindBlueprintForGraph(Graph) : NULL; if(Blueprint && Blueprint->SkeletonGeneratedClass) { // Gather all names in use by the Blueprint class TArray ExistingNamesInUse; FBlueprintEditorUtils::GetFunctionNameList(Blueprint, ExistingNamesInUse); FBlueprintEditorUtils::GetClassVariableList(Blueprint, ExistingNamesInUse); const ERenameFlags RenameFlags = (Blueprint->bIsRegeneratingOnLoad ? REN_ForceNoResetLoaders : 0); // Allow the old object name to be used in the graph FName ObjName = EventNode->GetFName(); UObject* Found = FindObject(EventNode->GetOuter(), *ObjName.ToString()); if(Found) { Found->Rename(NULL, NULL, REN_DontCreateRedirectors | RenameFlags); } // Create a custom event node to replace the original event node imported from text UK2Node_CustomEvent* CustomEventNode = NewObject(EventNode->GetOuter(), ObjName, EventNode->GetFlags(), nullptr, true, InstanceGraph); // Ensure that it is editable CustomEventNode->bIsEditable = true; // Set grid position to match that of the target node CustomEventNode->NodePosX = EventNode->NodePosX; CustomEventNode->NodePosY = EventNode->NodePosY; // Build a function name that is appropriate for the event we're replacing FString FunctionName; const UK2Node_ActorBoundEvent* ActorBoundEventNode = Cast(EventNode); const UK2Node_ComponentBoundEvent* CompBoundEventNode = Cast(EventNode); if(ActorBoundEventNode) { FString TargetName = TEXT("None"); if(ActorBoundEventNode->EventOwner) { TargetName = ActorBoundEventNode->EventOwner->GetActorLabel(); } FunctionName = FString::Printf(TEXT("%s_%s"), *ActorBoundEventNode->DelegatePropertyName.ToString(), *TargetName); } else if(CompBoundEventNode) { FunctionName = FString::Printf(TEXT("%s_%s"), *CompBoundEventNode->DelegatePropertyName.ToString(), *CompBoundEventNode->ComponentPropertyName.ToString()); } else if(EventNode->CustomFunctionName != NAME_None) { FunctionName = EventNode->CustomFunctionName.ToString(); } else if(EventNode->bOverrideFunction) { FunctionName = EventNode->EventReference.GetMemberName().ToString(); } else { FunctionName = CustomEventNode->GetName().Replace(TEXT("K2Node_"), TEXT(""), ESearchCase::CaseSensitive); } // Ensure that the new event name doesn't already exist as a variable or function name if(InstanceGraph) { FunctionName += TEXT("_Copy"); CustomEventNode->CustomFunctionName = FName(*FunctionName, FNAME_Find); if(CustomEventNode->CustomFunctionName != NAME_None && ExistingNamesInUse.Contains(CustomEventNode->CustomFunctionName)) { int32 i = 0; FString TempFuncName; do { TempFuncName = FString::Printf(TEXT("%s_%d"), *FunctionName, ++i); CustomEventNode->CustomFunctionName = FName(*TempFuncName, FNAME_Find); } while(CustomEventNode->CustomFunctionName != NAME_None && ExistingNamesInUse.Contains(CustomEventNode->CustomFunctionName)); FunctionName = TempFuncName; } } // Should be a unique name now, go ahead and assign it CustomEventNode->CustomFunctionName = FName(*FunctionName); // Copy the pins from the old node to the new one that's replacing it CustomEventNode->Pins = EventNode->Pins; CustomEventNode->UserDefinedPins = EventNode->UserDefinedPins; // Clear out the pins from the old node so that links aren't broken later when it's destroyed EventNode->Pins.Empty(); EventNode->UserDefinedPins.Empty(); bool bOriginalWasCustomEvent = Cast(Node) != nullptr; // Fixup pins for(int32 PinIndex = 0; PinIndex < CustomEventNode->Pins.Num(); ++PinIndex) { UEdGraphPin* Pin = CustomEventNode->Pins[PinIndex]; check(Pin); // Reparent the pin to the new custom event node Pin->Rename(*Pin->GetName(), CustomEventNode, RenameFlags); // Don't include execution or delegate output pins as user-defined pins if(!bOriginalWasCustomEvent && !IsExecPin(*Pin) && !IsDelegateCategory(Pin->PinType.PinCategory)) { // Check to see if this pin already exists as a user-defined pin on the custom event node bool bFoundUserDefinedPin = false; for(int32 UserDefinedPinIndex = 0; UserDefinedPinIndex < CustomEventNode->UserDefinedPins.Num() && !bFoundUserDefinedPin; ++UserDefinedPinIndex) { const FUserPinInfo& UserDefinedPinInfo = *CustomEventNode->UserDefinedPins[UserDefinedPinIndex].Get(); bFoundUserDefinedPin = Pin->PinName == UserDefinedPinInfo.PinName && Pin->PinType == UserDefinedPinInfo.PinType; } if(!bFoundUserDefinedPin) { // Add a new entry into the user-defined pin array for the custom event node TSharedPtr UserPinInfo = MakeShareable(new FUserPinInfo()); UserPinInfo->PinName = Pin->PinName; UserPinInfo->PinType = Pin->PinType; CustomEventNode->UserDefinedPins.Add(UserPinInfo); } } } // Return the new custom event node that we just created as a substitute for the original event node return CustomEventNode; } } // Use the default logic in all other cases return UEdGraphSchema::CreateSubstituteNode(Node, Graph, InstanceGraph); } int32 UEdGraphSchema_K2::GetNodeSelectionCount(const UEdGraph* Graph) const { UBlueprint* Blueprint = FBlueprintEditorUtils::FindBlueprintForGraph(Graph); int32 SelectionCount = 0; if( Blueprint ) { SelectionCount = FKismetEditorUtilities::GetNumberOfSelectedNodes(Blueprint); } return SelectionCount; } TSharedPtr UEdGraphSchema_K2::GetCreateCommentAction() const { return TSharedPtr(static_cast(new FEdGraphSchemaAction_K2AddComment)); } TSharedPtr UEdGraphSchema_K2::GetCreateDocumentNodeAction() const { return TSharedPtr(static_cast(new FEdGraphSchemaAction_K2AddDocumentation)); } bool UEdGraphSchema_K2::CanDuplicateGraph(UEdGraph* InSourceGraph) const { if(GetGraphType(InSourceGraph) == GT_Function) { UBlueprint* SourceBP = FBlueprintEditorUtils::FindBlueprintForGraph(InSourceGraph); // Do not duplicate graphs in Blueprint Interfaces if(SourceBP->BlueprintType == BPTYPE_Interface) { return false; } // Do not duplicate functions from implemented interfaces if( FBlueprintEditorUtils::FindFunctionInImplementedInterfaces(SourceBP, InSourceGraph->GetFName()) ) { return false; } // Do not duplicate inherited functions if( FindField(SourceBP->ParentClass, InSourceGraph->GetFName()) ) { return false; } } return true; } UEdGraph* UEdGraphSchema_K2::DuplicateGraph(UEdGraph* GraphToDuplicate) const { UEdGraph* NewGraph = NULL; if (CanDuplicateGraph(GraphToDuplicate)) { UBlueprint* Blueprint = FBlueprintEditorUtils::FindBlueprintForGraph(GraphToDuplicate); NewGraph = FEdGraphUtilities::CloneGraph(GraphToDuplicate, Blueprint); if (NewGraph) { FName NewGraphName = FBlueprintEditorUtils::FindUniqueKismetName(Blueprint, GraphToDuplicate->GetFName().GetPlainNameString()); FEdGraphUtilities::RenameGraphCloseToName(NewGraph,NewGraphName.ToString()); // can't have two graphs with the same guid... that'd be silly! NewGraph->GraphGuid = FGuid::NewGuid(); //Rename the entry node or any further renames will not update the entry node, also fixes a duplicate node issue on compile for (int32 NodeIndex = 0; NodeIndex < NewGraph->Nodes.Num(); ++NodeIndex) { UEdGraphNode* Node = NewGraph->Nodes[NodeIndex]; if (UK2Node_FunctionEntry* EntryNode = Cast(Node)) { if (EntryNode->SignatureName == GraphToDuplicate->GetFName()) { EntryNode->Modify(); EntryNode->SignatureName = NewGraph->GetFName(); break; } } // Rename any custom events to be unique else if (Node->GetClass()->GetFName() == TEXT("K2Node_CustomEvent")) { UK2Node_CustomEvent* CustomEvent = Cast(Node); CustomEvent->RenameCustomEventCloseToName(); } } } } return NewGraph; } /** * Attempts to best-guess the height of the node. This is necessary because we don't know the actual * size of the node until the next Slate tick * * @param Node The node to guess the height of * @return The estimated height of the specified node */ float UEdGraphSchema_K2::EstimateNodeHeight( UEdGraphNode* Node ) { float HeightEstimate = 0.0f; if ( Node != NULL ) { float BaseNodeHeight = 48.0f; bool bConsiderNodePins = false; float HeightPerPin = 18.0f; if ( Node->IsA( UK2Node_CallFunction::StaticClass() ) ) { BaseNodeHeight = 80.0f; bConsiderNodePins = true; HeightPerPin = 18.0f; } else if ( Node->IsA( UK2Node_Event::StaticClass() ) ) { BaseNodeHeight = 48.0f; bConsiderNodePins = true; HeightPerPin = 16.0f; } HeightEstimate = BaseNodeHeight; if ( bConsiderNodePins ) { int32 NumInputPins = 0; int32 NumOutputPins = 0; for ( int32 PinIndex = 0; PinIndex < Node->Pins.Num(); PinIndex++ ) { UEdGraphPin* CurrentPin = Node->Pins[PinIndex]; if ( CurrentPin != NULL && !CurrentPin->bHidden ) { switch ( CurrentPin->Direction ) { case EGPD_Input: { NumInputPins++; } break; case EGPD_Output: { NumOutputPins++; } break; } } } float MaxNumPins = float(FMath::Max( NumInputPins, NumOutputPins )); HeightEstimate += MaxNumPins * HeightPerPin; } } return HeightEstimate; } bool UEdGraphSchema_K2::CollapseGatewayNode(UK2Node* InNode, UEdGraphNode* InEntryNode, UEdGraphNode* InResultNode, FKismetCompilerContext* CompilerContext) const { bool bSuccessful = true; // We iterate the array in reverse so we can both remove the subpins safely after we've read them and // so we have split nested structs we combine them back together in the right order for (int32 BoundaryPinIndex = InNode->Pins.Num() - 1; BoundaryPinIndex >= 0; --BoundaryPinIndex) { UEdGraphPin* const BoundaryPin = InNode->Pins[BoundaryPinIndex]; bool bFunctionNode = InNode->IsA(UK2Node_CallFunction::StaticClass()); // For each pin in the gateway node, find the associated pin in the entry or result node. UEdGraphNode* const GatewayNode = (BoundaryPin->Direction == EGPD_Input) ? InEntryNode : InResultNode; UEdGraphPin* GatewayPin = NULL; if (GatewayNode) { // First handle struct combining if necessary if (BoundaryPin->SubPins.Num() > 0) { InNode->ExpandSplitPin(CompilerContext, InNode->GetGraph(), BoundaryPin); } for (int32 PinIdx = GatewayNode->Pins.Num() - 1; PinIdx >= 0; --PinIdx) { UEdGraphPin* const Pin = GatewayNode->Pins[PinIdx]; // Expand any gateway pins as needed if (Pin->SubPins.Num() > 0) { InNode->ExpandSplitPin(CompilerContext, GatewayNode->GetGraph(), Pin); } // Function graphs have a single exec path through them, so only one exec pin for input and another for output. In this fashion, they must not be handled by name. if(InNode->GetClass() == UK2Node_CallFunction::StaticClass() && Pin->PinType.PinCategory == PC_Exec && BoundaryPin->PinType.PinCategory == PC_Exec && (Pin->Direction != BoundaryPin->Direction)) { GatewayPin = Pin; break; } else if ((Pin->PinName == BoundaryPin->PinName) && (Pin->Direction != BoundaryPin->Direction)) { GatewayPin = Pin; break; } } } if (GatewayPin) { CombineTwoPinNetsAndRemoveOldPins(BoundaryPin, GatewayPin); } else { if (BoundaryPin->LinkedTo.Num() > 0 && BoundaryPin->ParentPin == NULL) { UBlueprint* OwningBP = InNode->GetBlueprint(); if( OwningBP ) { // We had an input/output with a connection that wasn't twinned bSuccessful = false; OwningBP->Message_Warn( FString::Printf(*NSLOCTEXT("K2Node", "PinOnBoundryNode_Warning", "Warning: Pin '%s' on boundary node '%s' could not be found in the composite node '%s'").ToString(), *(BoundaryPin->PinName), (GatewayNode != NULL) ? *(GatewayNode->GetName()) : TEXT("(null)"), *(GetName())) ); } else { UE_LOG(LogBlueprint, Warning, TEXT("%s"), *FString::Printf(*NSLOCTEXT("K2Node", "PinOnBoundryNode_Warning", "Warning: Pin '%s' on boundary node '%s' could not be found in the composite node '%s'").ToString(), *(BoundaryPin->PinName), (GatewayNode != NULL) ? *(GatewayNode->GetName()) : TEXT("(null)"), *(GetName())) ); } } else { // Associated pin was not found but there were no links on this side either, so no harm no foul } } } return bSuccessful; } void UEdGraphSchema_K2::CombineTwoPinNetsAndRemoveOldPins(UEdGraphPin* InPinA, UEdGraphPin* InPinB) const { check(InPinA != NULL); check(InPinB != NULL); ensure(InPinA->Direction != InPinB->Direction); if ((InPinA->LinkedTo.Num() == 0) && (InPinA->Direction == EGPD_Input)) { // Push the literal value of A to InPinB's connections for (int32 IndexB = 0; IndexB < InPinB->LinkedTo.Num(); ++IndexB) { UEdGraphPin* FarB = InPinB->LinkedTo[IndexB]; // TODO: Michael N. says this if check should be unnecessary once the underlying issue is fixed. // (Probably should use a check() instead once it's removed though. See additional cases below. if (FarB != NULL) { FarB->DefaultValue = InPinA->DefaultValue; FarB->DefaultObject = InPinA->DefaultObject; FarB->DefaultTextValue = InPinA->DefaultTextValue; } } } else if ((InPinB->LinkedTo.Num() == 0) && (InPinB->Direction == EGPD_Input)) { // Push the literal value of B to InPinA's connections for (int32 IndexA = 0; IndexA < InPinA->LinkedTo.Num(); ++IndexA) { UEdGraphPin* FarA = InPinA->LinkedTo[IndexA]; // TODO: Michael N. says this if check should be unnecessary once the underlying issue is fixed. // (Probably should use a check() instead once it's removed though. See additional cases above and below. if (FarA != NULL) { FarA->DefaultValue = InPinB->DefaultValue; FarA->DefaultObject = InPinB->DefaultObject; FarA->DefaultTextValue = InPinB->DefaultTextValue; } } } else { // Make direct connections between the things that connect to A or B, removing A and B from the picture for (int32 IndexA = 0; IndexA < InPinA->LinkedTo.Num(); ++IndexA) { UEdGraphPin* FarA = InPinA->LinkedTo[IndexA]; // TODO: Michael N. says this if check should be unnecessary once the underlying issue is fixed. // (Probably should use a check() instead once it's removed though. See additional cases above. if (FarA != NULL) { for (int32 IndexB = 0; IndexB < InPinB->LinkedTo.Num(); ++IndexB) { UEdGraphPin* FarB = InPinB->LinkedTo[IndexB]; FarA->Modify(); FarB->Modify(); FarA->MakeLinkTo(FarB); } } } } InPinA->BreakAllPinLinks(); InPinB->BreakAllPinLinks(); } UK2Node* UEdGraphSchema_K2::CreateSplitPinNode(UEdGraphPin* Pin, FKismetCompilerContext* CompilerContext, UEdGraph* SourceGraph) const { UEdGraphNode* GraphNode = Pin->GetOwningNode(); UEdGraph* Graph = GraphNode->GetGraph(); UScriptStruct* StructType = CastChecked(Pin->PinType.PinSubCategoryObject.Get(), ECastCheckedType::NullAllowed); if (!StructType) { if (CompilerContext) { CompilerContext->MessageLog.Error(TEXT("No structure in SubCategoryObject in pin @@"), Pin); } StructType = GetFallbackStruct(); } UK2Node* SplitPinNode = NULL; if (Pin->Direction == EGPD_Input) { if (UK2Node_MakeStruct::CanBeMade(StructType)) { UK2Node_MakeStruct* MakeStructNode = (CompilerContext ? CompilerContext->SpawnIntermediateNode(GraphNode, SourceGraph) : NewObject(Graph)); MakeStructNode->StructType = StructType; SplitPinNode = MakeStructNode; } else { const FString& MetaData = StructType->GetMetaData(TEXT("HasNativeMake")); const UFunction* Function = FindObject(NULL, *MetaData, true); UK2Node_CallFunction* CallFunctionNode = (CompilerContext ? CompilerContext->SpawnIntermediateNode(GraphNode, SourceGraph) : NewObject(Graph)); CallFunctionNode->SetFromFunction(Function); SplitPinNode = CallFunctionNode; } } else { if (UK2Node_BreakStruct::CanBeBroken(StructType)) { UK2Node_BreakStruct* BreakStructNode = (CompilerContext ? CompilerContext->SpawnIntermediateNode(GraphNode, SourceGraph) : NewObject(Graph)); BreakStructNode->StructType = StructType; SplitPinNode = BreakStructNode; } else { const FString& MetaData = StructType->GetMetaData(TEXT("HasNativeBreak")); const UFunction* Function = FindObject(NULL, *MetaData, true); UK2Node_CallFunction* CallFunctionNode = (CompilerContext ? CompilerContext->SpawnIntermediateNode(GraphNode, SourceGraph) : NewObject(Graph)); CallFunctionNode->SetFromFunction(Function); SplitPinNode = CallFunctionNode; } } SplitPinNode->AllocateDefaultPins(); return SplitPinNode; } void UEdGraphSchema_K2::SplitPin(UEdGraphPin* Pin) const { // Under some circumstances we can get here when PinSubCategoryObject is not set, so we just can't split the pin in that case UScriptStruct* StructType = Cast(Pin->PinType.PinSubCategoryObject.Get()); if (StructType == nullptr) { return; } UEdGraphNode* GraphNode = Pin->GetOwningNode(); UK2Node* K2Node = Cast(GraphNode); UEdGraph* Graph = CastChecked(GraphNode->GetOuter()); GraphNode->Modify(); Pin->Modify(); Pin->bHidden = true; UK2Node* ProtoExpandNode = CreateSplitPinNode(Pin); for (UEdGraphPin* ProtoPin : ProtoExpandNode->Pins) { if (ProtoPin->Direction == Pin->Direction && !ProtoPin->bHidden) { const FString PinName = FString::Printf(TEXT("%s_%s"), *Pin->PinName, *ProtoPin->PinName); const FEdGraphPinType& ProtoPinType = ProtoPin->PinType; UEdGraphPin* SubPin = GraphNode->CreatePin(Pin->Direction, ProtoPinType.PinCategory, ProtoPinType.PinSubCategory, ProtoPinType.PinSubCategoryObject.Get(), ProtoPinType.bIsArray, false, PinName); if (K2Node != nullptr && K2Node->ShouldDrawCompact() && !Pin->ParentPin) { SubPin->PinFriendlyName = ProtoPin->GetDisplayName(); } else { FFormatNamedArguments Arguments; Arguments.Add(TEXT("PinDisplayName"), Pin->GetDisplayName()); Arguments.Add(TEXT("ProtoPinDisplayName"), ProtoPin->GetDisplayName()); SubPin->PinFriendlyName = FText::Format(LOCTEXT("SplitPinFriendlyNameFormat", "{PinDisplayName} {ProtoPinDisplayName}"), Arguments); } SubPin->DefaultValue = ProtoPin->DefaultValue; SubPin->AutogeneratedDefaultValue = ProtoPin->AutogeneratedDefaultValue; SubPin->ParentPin = Pin; // CreatePin puts the Pin in the array, but we are going to insert it later, so pop it back out GraphNode->Pins.Pop(/*bAllowShrinking=*/ false); Pin->SubPins.Add(SubPin); } } ProtoExpandNode->DestroyNode(); if (Pin->Direction == EGPD_Input) { TArray OriginalDefaults; if ( StructType == GetBaseStructure(TEXT("Vector")) || StructType == GetBaseStructure(TEXT("Rotator"))) { Pin->DefaultValue.ParseIntoArray(OriginalDefaults, TEXT(","), false); for (FString& Default : OriginalDefaults) { Default = FString::SanitizeFloat(FCString::Atof(*Default)); } // In some cases (particularly wildcards) the default value may not accurately reflect the normal component elements while (OriginalDefaults.Num() < 3) { OriginalDefaults.Add(TEXT("0.0")); } } else if (StructType == GetBaseStructure(TEXT("Vector2D"))) { FVector2D V2D; V2D.InitFromString(Pin->DefaultValue); OriginalDefaults.Add(FString::SanitizeFloat(V2D.X)); OriginalDefaults.Add(FString::SanitizeFloat(V2D.Y)); } else if (StructType == GetBaseStructure(TEXT("LinearColor"))) { FLinearColor LC; LC.InitFromString(Pin->DefaultValue); OriginalDefaults.Add(FString::SanitizeFloat(LC.R)); OriginalDefaults.Add(FString::SanitizeFloat(LC.G)); OriginalDefaults.Add(FString::SanitizeFloat(LC.B)); OriginalDefaults.Add(FString::SanitizeFloat(LC.A)); } check(OriginalDefaults.Num() == 0 || OriginalDefaults.Num() == Pin->SubPins.Num()); for (int32 SubPinIndex = 0; SubPinIndex < OriginalDefaults.Num(); ++SubPinIndex) { UEdGraphPin* SubPin = Pin->SubPins[SubPinIndex]; SubPin->DefaultValue = OriginalDefaults[SubPinIndex]; } } GraphNode->Pins.Insert(Pin->SubPins, GraphNode->Pins.Find(Pin) + 1); Graph->NotifyGraphChanged(); UBlueprint* Blueprint = FBlueprintEditorUtils::FindBlueprintForGraphChecked(Graph); FBlueprintEditorUtils::MarkBlueprintAsModified(Blueprint); } void UEdGraphSchema_K2::RecombinePin(UEdGraphPin* Pin) const { UEdGraphNode* GraphNode = Pin->GetOwningNode(); UEdGraphPin* ParentPin = Pin->ParentPin; GraphNode->Modify(); ParentPin->Modify(); ParentPin->bHidden = false; for (int32 SubPinIndex = 0; SubPinIndex < ParentPin->SubPins.Num(); ++SubPinIndex) { UEdGraphPin* SubPin = ParentPin->SubPins[SubPinIndex]; if (SubPin->SubPins.Num() > 0) { RecombinePin(SubPin->SubPins[0]); } GraphNode->Pins.Remove(SubPin); } if (Pin->Direction == EGPD_Input) { UScriptStruct* StructType = CastChecked(ParentPin->PinType.PinSubCategoryObject.Get()); TArray OriginalDefaults; if ( StructType == GetBaseStructure(TEXT("Vector")) || StructType == GetBaseStructure(TEXT("Rotator"))) { ParentPin->DefaultValue = ParentPin->SubPins[0]->DefaultValue + TEXT(",") + ParentPin->SubPins[1]->DefaultValue + TEXT(",") + ParentPin->SubPins[2]->DefaultValue; } else if (StructType == GetBaseStructure(TEXT("Vector2D"))) { FVector2D V2D; V2D.X = FCString::Atof(*ParentPin->SubPins[0]->DefaultValue); V2D.Y = FCString::Atof(*ParentPin->SubPins[1]->DefaultValue); ParentPin->DefaultValue = V2D.ToString(); } else if (StructType == GetBaseStructure(TEXT("LinearColor"))) { FLinearColor LC; LC.R = FCString::Atof(*ParentPin->SubPins[0]->DefaultValue); LC.G = FCString::Atof(*ParentPin->SubPins[1]->DefaultValue); LC.B = FCString::Atof(*ParentPin->SubPins[2]->DefaultValue); LC.A = FCString::Atof(*ParentPin->SubPins[3]->DefaultValue); ParentPin->DefaultValue = LC.ToString(); } } ParentPin->SubPins.Empty(); UEdGraph* Graph = CastChecked(GraphNode->GetOuter()); Graph->NotifyGraphChanged(); UBlueprint* Blueprint = FBlueprintEditorUtils::FindBlueprintForGraphChecked(Graph); FBlueprintEditorUtils::MarkBlueprintAsModified(Blueprint); } void UEdGraphSchema_K2::OnPinConnectionDoubleCicked(UEdGraphPin* PinA, UEdGraphPin* PinB, const FVector2D& GraphPosition) const { const FScopedTransaction Transaction(LOCTEXT("CreateRerouteNodeOnWire", "Create Reroute Node")); //@TODO: This constant is duplicated from inside of SGraphNodeKnot const FVector2D NodeSpacerSize(42.0f, 24.0f); const FVector2D KnotTopLeft = GraphPosition - (NodeSpacerSize * 0.5f); // Create a new knot UEdGraph* ParentGraph = PinA->GetOwningNode()->GetGraph(); UK2Node_Knot* NewKnot = FEdGraphSchemaAction_K2NewNode::SpawnNodeFromTemplate(ParentGraph, NewObject(), KnotTopLeft); // Move the connections across (only notifying the knot, as the other two didn't really change) PinA->BreakLinkTo(PinB); PinA->MakeLinkTo((PinA->Direction == EGPD_Output) ? NewKnot->GetInputPin() : NewKnot->GetOutputPin()); PinB->MakeLinkTo((PinB->Direction == EGPD_Output) ? NewKnot->GetInputPin() : NewKnot->GetOutputPin()); NewKnot->PostReconstructNode(); // Dirty the blueprint UBlueprint* Blueprint = FBlueprintEditorUtils::FindBlueprintForGraphChecked(CastChecked(ParentGraph)); FBlueprintEditorUtils::MarkBlueprintAsModified(Blueprint); } void UEdGraphSchema_K2::ConfigureVarNode(UK2Node_Variable* InVarNode, FName InVariableName, UStruct* InVariableSource, UBlueprint* InTargetBlueprint) { // See if this is a 'self context' (ie. blueprint class is owner (or child of owner) of dropped var class) if ((InVariableSource == NULL) || InTargetBlueprint->SkeletonGeneratedClass->IsChildOf(InVariableSource)) { InVarNode->VariableReference.SetSelfMember(InVariableName); } else if (InVariableSource->IsA(UClass::StaticClass())) { InVarNode->VariableReference.SetExternalMember(InVariableName, CastChecked(InVariableSource)); } else { FGuid LocalVarGuid = FBlueprintEditorUtils::FindLocalVariableGuidByName(InTargetBlueprint, InVariableSource, InVariableName); if (LocalVarGuid.IsValid()) { InVarNode->VariableReference.SetLocalMember(InVariableName, InVariableSource, LocalVarGuid); } } } UK2Node_VariableGet* UEdGraphSchema_K2::SpawnVariableGetNode(const FVector2D GraphPosition, class UEdGraph* ParentGraph, FName VariableName, UStruct* Source) const { UK2Node_VariableGet* NodeTemplate = NewObject(); UBlueprint* Blueprint = FBlueprintEditorUtils::FindBlueprintForGraph(ParentGraph); UEdGraphSchema_K2::ConfigureVarNode(NodeTemplate, VariableName, Source, Blueprint); return FEdGraphSchemaAction_K2NewNode::SpawnNodeFromTemplate(ParentGraph, NodeTemplate, GraphPosition); } UK2Node_VariableSet* UEdGraphSchema_K2::SpawnVariableSetNode(const FVector2D GraphPosition, class UEdGraph* ParentGraph, FName VariableName, UStruct* Source) const { UK2Node_VariableSet* NodeTemplate = NewObject(); UBlueprint* Blueprint = FBlueprintEditorUtils::FindBlueprintForGraph(ParentGraph); UEdGraphSchema_K2::ConfigureVarNode(NodeTemplate, VariableName, Source, Blueprint); return FEdGraphSchemaAction_K2NewNode::SpawnNodeFromTemplate(ParentGraph, NodeTemplate, GraphPosition); } UEdGraphPin* UEdGraphSchema_K2::DropPinOnNode(UEdGraphNode* InTargetNode, const FString& InSourcePinName, const FEdGraphPinType& InSourcePinType, EEdGraphPinDirection InSourcePinDirection) const { UEdGraphPin* ResultPin = nullptr; if (UK2Node_EditablePinBase* EditablePinNode = Cast(InTargetNode)) { EditablePinNode->Modify(); if (InSourcePinDirection == EGPD_Output && Cast(InTargetNode)) { if (UK2Node_EditablePinBase* ResultNode = FBlueprintEditorUtils::FindOrCreateFunctionResultNode(EditablePinNode)) { EditablePinNode = ResultNode; } else { // If we did not successfully find or create a result node, just fail out return nullptr; } } else if (InSourcePinDirection == EGPD_Input && Cast(InTargetNode)) { TArray FunctionEntryNode; InTargetNode->GetGraph()->GetNodesOfClass(FunctionEntryNode); if (FunctionEntryNode.Num() == 1) { EditablePinNode = FunctionEntryNode[0]; } else { // If we did not successfully find the entry node, just fail out return nullptr; } } FString NewPinName = InSourcePinName; ResultPin = EditablePinNode->CreateUserDefinedPin(NewPinName, InSourcePinType, (InSourcePinDirection == EGPD_Input)? EGPD_Output : EGPD_Input); FParamsChangedHelper ParamsChangedHelper; ParamsChangedHelper.ModifiedBlueprints.Add(FBlueprintEditorUtils::FindBlueprintForNode(InTargetNode)); FBlueprintEditorUtils::MarkBlueprintAsStructurallyModified(FBlueprintEditorUtils::FindBlueprintForNode(InTargetNode)); ParamsChangedHelper.Broadcast(FBlueprintEditorUtils::FindBlueprintForNode(InTargetNode), EditablePinNode, InTargetNode->GetGraph()); for (auto GraphIt = ParamsChangedHelper.ModifiedGraphs.CreateIterator(); GraphIt; ++GraphIt) { if(UEdGraph* ModifiedGraph = *GraphIt) { ModifiedGraph->NotifyGraphChanged(); } } // Now update all the blueprints that got modified for (auto BlueprintIt = ParamsChangedHelper.ModifiedBlueprints.CreateIterator(); BlueprintIt; ++BlueprintIt) { if(UBlueprint* Blueprint = *BlueprintIt) { FBlueprintEditorUtils::MarkBlueprintAsModified(Blueprint); Blueprint->BroadcastChanged(); } } } return ResultPin; } bool UEdGraphSchema_K2::SupportsDropPinOnNode(UEdGraphNode* InTargetNode, const FEdGraphPinType& InSourcePinType, EEdGraphPinDirection InSourcePinDirection, FText& OutErrorMessage) const { bool bIsSupported = false; if (UK2Node_EditablePinBase* EditablePinNode = Cast(InTargetNode)) { if (InSourcePinDirection == EGPD_Output && Cast(InTargetNode)) { // Just check with the Function Entry and see if it's legal, we'll create/use a result node if the user drops bIsSupported = EditablePinNode->CanCreateUserDefinedPin(InSourcePinType, InSourcePinDirection, OutErrorMessage); if (bIsSupported) { OutErrorMessage = LOCTEXT("AddConnectResultNode", "Add Pin to Result Node"); } } else if (InSourcePinDirection == EGPD_Input && Cast(InTargetNode)) { // Just check with the Function Result and see if it's legal, we'll create/use a result node if the user drops bIsSupported = EditablePinNode->CanCreateUserDefinedPin(InSourcePinType, InSourcePinDirection, OutErrorMessage); if (bIsSupported) { OutErrorMessage = LOCTEXT("AddPinEntryNode", "Add Pin to Entry Node"); } } else { bIsSupported = EditablePinNode->CanCreateUserDefinedPin(InSourcePinType, (InSourcePinDirection == EGPD_Input)? EGPD_Output : EGPD_Input, OutErrorMessage); if (bIsSupported) { OutErrorMessage = LOCTEXT("AddPinToNode", "Add Pin to Node"); } } } return bIsSupported; } bool UEdGraphSchema_K2::IsCacheVisualizationOutOfDate(int32 InVisualizationCacheID) const { return CurrentCacheRefreshID != InVisualizationCacheID; } int32 UEdGraphSchema_K2::GetCurrentVisualizationCacheID() const { return CurrentCacheRefreshID; } void UEdGraphSchema_K2::ForceVisualizationCacheClear() const { ++CurrentCacheRefreshID; } ///////////////////////////////////////////////////// #undef LOCTEXT_NAMESPACE