// Copyright Epic Games, Inc. All Rights Reserved. #include "K2Node_CallFunction.h" #include "BlueprintCompilationManager.h" #include "BlueprintEditorSettings.h" #include "UObject/UObjectHash.h" #include "UObject/FrameworkObjectVersion.h" #include "UObject/Interface.h" #include "UObject/PropertyPortFlags.h" #include "Kismet/BlueprintFunctionLibrary.h" #include "Engine/BlueprintGeneratedClass.h" #include "Framework/Notifications/NotificationManager.h" #include "GraphEditorSettings.h" #include "EdGraph/EdGraph.h" #include "EdGraphSchema_K2.h" #include "K2Node_Event.h" #include "K2Node_AssignmentStatement.h" #include "K2Node_CallArrayFunction.h" #include "K2Node_CustomEvent.h" #include "K2Node_FunctionEntry.h" #include "K2Node_IfThenElse.h" #include "K2Node_TemporaryVariable.h" #include "K2Node_ExecutionSequence.h" #include "Kismet2/BlueprintEditorUtils.h" #include "Classes/EditorStyleSettings.h" #include "Editor.h" #include "EdGraphUtilities.h" #include "KismetCompiler.h" #include "CallFunctionHandler.h" #include "K2Node_SwitchEnum.h" #include "Kismet/KismetMathLibrary.h" #include "Kismet/KismetArrayLibrary.h" #include "Kismet2/KismetDebugUtilities.h" #include "K2Node_PureAssignmentStatement.h" #include "BlueprintActionFilter.h" #include "FindInBlueprintManager.h" #include "ScopedTransaction.h" #include "ObjectEditorUtils.h" #include "SPinTypeSelector.h" #include "SourceCodeNavigation.h" #include "HAL/FileManager.h" #include "Widgets/Notifications/SNotificationList.h" #include "BlueprintNodeStatics.h" #define LOCTEXT_NAMESPACE "K2Node" /******************************************************************************* * FCustomStructureParamHelper ******************************************************************************/ struct FCustomStructureParamHelper { static void FillCustomStructureParameterNames(const UFunction* Function, TArray& OutNames) { OutNames.Reset(); if (Function) { const FString& MetaDataValue = Function->GetMetaData(FBlueprintMetadata::MD_CustomStructureParam); if (!MetaDataValue.IsEmpty()) { MetaDataValue.ParseIntoArray(OutNames, TEXT(","), true); } } } static void HandleSinglePin(UEdGraphPin* Pin) { if (Pin) { if (Pin->LinkedTo.Num() > 0) { UEdGraphPin* LinkedTo = Pin->LinkedTo[0]; check(LinkedTo); if (UK2Node* Node = Cast(Pin->GetOwningNode())) { ensure( !LinkedTo->PinType.IsContainer() || Node->DoesWildcardPinAcceptContainer(Pin) ); } else { ensure( !LinkedTo->PinType.IsContainer() ); } Pin->PinType = LinkedTo->PinType; } else { // constness and refness are controlled by our declaration // but everything else needs to be reset to default wildcard: const bool bWasRef = Pin->PinType.bIsReference; const bool bWasConst = Pin->PinType.bIsConst; Pin->PinType = FEdGraphPinType(); Pin->PinType.bIsReference = bWasRef; Pin->PinType.bIsConst = bWasConst; Pin->PinType.PinCategory = UEdGraphSchema_K2::PC_Wildcard; Pin->PinType.PinSubCategory = NAME_None; Pin->PinType.PinSubCategoryObject = nullptr; } } } static void UpdateCustomStructurePins(const UFunction* Function, UK2Node* Node, UEdGraphPin* SinglePin = nullptr) { if (Function && Node) { TArray Names; FCustomStructureParamHelper::FillCustomStructureParameterNames(Function, Names); if (SinglePin) { if (Names.Contains(SinglePin->PinName.ToString())) { HandleSinglePin(SinglePin); } } else { for (const FString& Name : Names) { if (UEdGraphPin* Pin = Node->FindPin(Name)) { HandleSinglePin(Pin); } } } } } }; /******************************************************************************* * FDynamicOutputUtils ******************************************************************************/ struct FDynamicOutputHelper { public: FDynamicOutputHelper(UEdGraphPin* InAlteredPin) : MutatingPin(InAlteredPin) {} /** * Attempts to change the output pin's type so that it reflects the class * specified by the input class pin. */ void ConformOutputType() const; /** * Retrieves the class pin that is used to determine the function's output type. * * @return Null if the "DeterminesOutputType" metadata targets an invalid * param (or if the metadata isn't present), otherwise a class picker pin. */ static UEdGraphPin* GetTypePickerPin(const UK2Node_CallFunction* FuncNode); /** * Attempts to pull out class info from the supplied pin. Starts with the * pin's default, and then falls back onto the pin's native type. Will poll * any connections that the pin may have. * * @param Pin The pin you want a class from. * @return A class that the pin represents (could be null if the pin isn't a class pin). */ static UClass* GetPinClass(UEdGraphPin* Pin); /** * Intended to be used by ValidateNodeDuringCompilation(). Will check to * make sure the dynamic output's connections are still valid (they could * become invalid as the the pin's type changes). * * @param FuncNode The node you wish to validate. * @param MessageLog The log to post errors/warnings to. */ static void VerifyNode(const UK2Node_CallFunction* FuncNode, FCompilerResultsLog& MessageLog); private: /** * * * @return */ UK2Node_CallFunction* GetFunctionNode() const; /** * * * @return */ UFunction* GetTargetFunction() const; /** * Checks if the supplied pin is the class picker that governs the * function's output type. * * @param Pin The pin to test. * @return True if the pin corresponds to the param that was flagged by the "DeterminesOutputType" metadata. */ bool IsTypePickerPin(UEdGraphPin* Pin) const; /** * Retrieves the object output pins that are altered as the class input is * changed (favors params flagged by "DynamicOutputParam" metadata). * * @param FuncNode The function node in question * @param OutPins Out array of pins that are flagged with "DynamicOutputParam" metadata */ static void GetDynamicOutPins(const UK2Node_CallFunction* FuncNode, TArray& OutPins); /** * Checks if the specified type is an object type that reflects the picker * pin's class. * * @param TypeToTest The type you want to check. * @return True if the type is likely the output governed by the class picker pin, otherwise false. */ static bool CanConformPinType(const UK2Node_CallFunction* FuncNode, const FEdGraphPinType& TypeToTest); private: UEdGraphPin* MutatingPin; }; void FDynamicOutputHelper::ConformOutputType() const { if (IsTypePickerPin(MutatingPin)) { UClass* PickedClass = GetPinClass(MutatingPin); UK2Node_CallFunction* FuncNode = GetFunctionNode(); // See if there is any dynamic output pins TArray DynamicPins; GetDynamicOutPins(FuncNode, DynamicPins); // Set the pins class for (UEdGraphPin* Pin : DynamicPins) { if (ensure(Pin != nullptr)) { Pin->PinType.PinSubCategoryObject = PickedClass; } } } } UEdGraphPin* FDynamicOutputHelper::GetTypePickerPin(const UK2Node_CallFunction* FuncNode) { UEdGraphPin* TypePickerPin = nullptr; if (UFunction* Function = FuncNode->GetTargetFunction()) { const FString& TypeDeterminingPinName = Function->GetMetaData(FBlueprintMetadata::MD_DynamicOutputType); if (!TypeDeterminingPinName.IsEmpty()) { TypePickerPin = FuncNode->FindPin(TypeDeterminingPinName); } } return TypePickerPin; } UClass* FDynamicOutputHelper::GetPinClass(UEdGraphPin* Pin) { UClass* PinClass = UObject::StaticClass(); bool const bIsClassOrObjectPin = (Pin->PinType.PinCategory == UEdGraphSchema_K2::PC_Class || Pin->PinType.PinCategory == UEdGraphSchema_K2::PC_Object); if (bIsClassOrObjectPin) { if (UClass* DefaultClass = Cast(Pin->DefaultObject)) { PinClass = DefaultClass; } else if (UClass* BaseClass = Cast(Pin->PinType.PinSubCategoryObject.Get())) { PinClass = BaseClass; } if (Pin->LinkedTo.Num() > 0) { UClass* CommonInputClass = nullptr; for (UEdGraphPin* LinkedPin : Pin->LinkedTo) { const FEdGraphPinType& LinkedPinType = LinkedPin->PinType; UClass* LinkClass = Cast(LinkedPinType.PinSubCategoryObject.Get()); if (LinkClass == nullptr && LinkedPinType.PinSubCategory == UEdGraphSchema_K2::PSC_Self) { if (UK2Node* K2Node = Cast(LinkedPin->GetOwningNode())) { LinkClass = K2Node->GetBlueprint()->GeneratedClass; } } if (LinkClass != nullptr) { if (CommonInputClass != nullptr) { while (!LinkClass->IsChildOf(CommonInputClass)) { CommonInputClass = CommonInputClass->GetSuperClass(); } } else { CommonInputClass = LinkClass; } } } PinClass = CommonInputClass; } } return PinClass; } void FDynamicOutputHelper::VerifyNode(const UK2Node_CallFunction* FuncNode, FCompilerResultsLog& MessageLog) { TArray DynamicPins; GetDynamicOutPins(FuncNode, DynamicPins); for (UEdGraphPin* DynamicOutPin : DynamicPins) { if (ensure(DynamicOutPin != nullptr)) { const UEdGraphSchema* Schema = FuncNode->GetSchema(); for (UEdGraphPin* Link : DynamicOutPin->LinkedTo) { if (Schema->CanCreateConnection(DynamicOutPin, Link).Response == CONNECT_RESPONSE_DISALLOW) { FText const ErrorFormat = LOCTEXT("BadConnection", "Invalid pin connection from '@@' to '@@'. You may have changed the type after the connections were made."); MessageLog.Error(*ErrorFormat.ToString(), DynamicOutPin, Link); } } } } } UK2Node_CallFunction* FDynamicOutputHelper::GetFunctionNode() const { return CastChecked(MutatingPin->GetOwningNode()); } UFunction* FDynamicOutputHelper::GetTargetFunction() const { return GetFunctionNode()->GetTargetFunction(); } bool FDynamicOutputHelper::IsTypePickerPin(UEdGraphPin* Pin) const { bool bIsTypeDeterminingPin = false; if (UFunction* Function = GetTargetFunction()) { const FString& TypeDeterminingPinName = Function->GetMetaData(FBlueprintMetadata::MD_DynamicOutputType); if (!TypeDeterminingPinName.IsEmpty()) { bIsTypeDeterminingPin = (Pin->PinName.ToString() == TypeDeterminingPinName); } } bool const bPinIsClassPicker = (Pin->PinType.PinCategory == UEdGraphSchema_K2::PC_Class || Pin->PinType.PinCategory == UEdGraphSchema_K2::PC_SoftClass); bool const bPinIsObjectPicker = (Pin->PinType.PinCategory == UEdGraphSchema_K2::PC_Object || Pin->PinType.PinCategory == UEdGraphSchema_K2::PC_SoftObject); return bIsTypeDeterminingPin && (bPinIsClassPicker || bPinIsObjectPicker) && (Pin->Direction == EGPD_Input); } void FDynamicOutputHelper::GetDynamicOutPins(const UK2Node_CallFunction* FuncNode, TArray& OutPins) { if (UFunction* Function = FuncNode->GetTargetFunction()) { const FString& OutputPinNames = Function->GetMetaData(FBlueprintMetadata::MD_DynamicOutputParam); // There could be more than one dynamic output, so split by comma TArray UserDefinedDynamicProprties; OutputPinNames.ParseIntoArray(UserDefinedDynamicProprties, TEXT(","), true); // trim up the whitespace that a user may have added for (FString& Name : UserDefinedDynamicProprties) { Name = Name.TrimStartAndEnd(); } // Get the schema so we can verify the pin if we find it const UEdGraphSchema_K2* K2Schema = GetDefault(); // Lambda to add a pin to the out pins if it is valid auto AddPinToOutputLambda = [FuncNode, K2Schema](FProperty* TaggedOutputParam, TArray& OutPins) { // Ensure that this is a valid pin to make dynamic FEdGraphPinType PropertyPinType; if (TaggedOutputParam && K2Schema->ConvertPropertyToPinType(TaggedOutputParam, /*out*/PropertyPinType) && CanConformPinType(FuncNode, PropertyPinType)) { UEdGraphPin* DynamicOutPin = FuncNode->FindPin(TaggedOutputParam->GetFName()); if (DynamicOutPin && (DynamicOutPin->Direction == EGPD_Output)) { OutPins.Add(DynamicOutPin); } } }; // we sort through properties, instead of pins, because the pin's type // could already be modified to some other class (for when we check CanConformPinType) for (TFieldIterator ParamIt(Function); ParamIt && (ParamIt->PropertyFlags & CPF_Parm); ++ParamIt) { // If the user defined pins are 0 then assume we are just setting the type of a single output if (UserDefinedDynamicProprties.Num() == 0 && ParamIt->HasAnyPropertyFlags(CPF_ReturnParm)) { AddPinToOutputLambda(*ParamIt, OutPins); } else { // Check against each property that the user has specified for (const FString& OutputPinName : UserDefinedDynamicProprties) { // If this is the return parameter of this function or the pin name matches that which the user has specified if (OutputPinName == ParamIt->GetName()) { AddPinToOutputLambda(*ParamIt, OutPins); break; } } } } } } bool FDynamicOutputHelper::CanConformPinType(const UK2Node_CallFunction* FuncNode, const FEdGraphPinType& TypeToTest) { bool bIsProperType = false; if (UEdGraphPin* TypePickerPin = GetTypePickerPin(FuncNode)) { UClass* BasePickerClass = CastChecked(TypePickerPin->PinType.PinSubCategoryObject.Get()); const FName PinCategory = TypeToTest.PinCategory; if ((PinCategory == UEdGraphSchema_K2::PC_Object) || (PinCategory == UEdGraphSchema_K2::PC_Interface) || (PinCategory == UEdGraphSchema_K2::PC_Class) || (PinCategory == UEdGraphSchema_K2::PC_SoftObject) || (PinCategory == UEdGraphSchema_K2::PC_SoftClass)) { if (UClass* TypeClass = Cast(TypeToTest.PinSubCategoryObject.Get())) { bIsProperType = BasePickerClass->IsChildOf(TypeClass); } } } return bIsProperType; } static bool WantsExecPinsForParams(const UFunction* TargetFunction) { check(TargetFunction); return TargetFunction->HasMetaData(FBlueprintMetadata::MD_ExpandEnumAsExecs) || TargetFunction->HasMetaData(FBlueprintMetadata::MD_ExpandBoolAsExecs); } static FString GetAllExecParams(const UFunction* TargetFunction) { check(TargetFunction); FString Ret = TargetFunction->GetMetaData(FBlueprintMetadata::MD_ExpandEnumAsExecs); const FString& ExpandBools = TargetFunction->GetMetaData(FBlueprintMetadata::MD_ExpandBoolAsExecs); if (!ExpandBools.IsEmpty()) { if(!Ret.IsEmpty()) { Ret += ", " + ExpandBools; } else { Ret = ExpandBools; } } return Ret; } /******************************************************************************* * UK2Node_CallFunction ******************************************************************************/ UK2Node_CallFunction::UK2Node_CallFunction(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer) , bPinTooltipsValid(false) { OrphanedPinSaveMode = ESaveOrphanPinMode::SaveAll; } bool UK2Node_CallFunction::HasDeprecatedReference() const { UFunction* Function = GetTargetFunction(); return (Function && Function->HasMetaData(FBlueprintMetadata::MD_DeprecatedFunction)); } FEdGraphNodeDeprecationResponse UK2Node_CallFunction::GetDeprecationResponse(EEdGraphNodeDeprecationType DeprecationType) const { FEdGraphNodeDeprecationResponse Response = Super::GetDeprecationResponse(DeprecationType); if (DeprecationType == EEdGraphNodeDeprecationType::NodeHasDeprecatedReference) { // TEMP: Do not warn in the case of SpawnActor, as we have a special upgrade path for those nodes if (FunctionReference.GetMemberName() == FName(TEXT("BeginSpawningActorFromBlueprint"))) { Response.MessageType = EEdGraphNodeDeprecationMessageType::None; } else { UFunction* Function = GetTargetFunction(); if (ensureMsgf(Function != nullptr, TEXT("This node should not be able to report having a deprecated reference if the target function cannot be resolved."))) { FString DetailedMessage = Function->GetMetaData(FBlueprintMetadata::MD_DeprecationMessage); Response.MessageText = FBlueprintEditorUtils::GetDeprecatedMemberUsageNodeWarning(GetUserFacingFunctionName(Function), FText::FromString(DetailedMessage)); } } } return Response; } FText UK2Node_CallFunction::GetFunctionContextString() const { FText ContextString; // Don't show 'target is' if no target pin! UEdGraphPin* SelfPin = GetDefault()->FindSelfPin(*this, EGPD_Input); if(SelfPin != NULL && !SelfPin->bHidden) { const UFunction* Function = GetTargetFunction(); UClass* CurrentSelfClass = (Function != NULL) ? Function->GetOwnerClass() : NULL; UClass const* TrueSelfClass = CurrentSelfClass; if (CurrentSelfClass && CurrentSelfClass->ClassGeneratedBy) { TrueSelfClass = CurrentSelfClass->GetAuthoritativeClass(); } const FText TargetText = FBlueprintEditorUtils::GetFriendlyClassDisplayName(TrueSelfClass); FFormatNamedArguments Args; Args.Add(TEXT("TargetName"), TargetText); ContextString = FText::Format(LOCTEXT("CallFunctionOnDifferentContext", "Target is {TargetName}"), Args); } return ContextString; } FText UK2Node_CallFunction::GetNodeTitle(ENodeTitleType::Type TitleType) const { FText FunctionName; FText ContextString; FText RPCString; if (UFunction* Function = GetTargetFunction()) { RPCString = UK2Node_Event::GetLocalizedNetString(Function->FunctionFlags, true); FunctionName = GetUserFacingFunctionName(Function); ContextString = GetFunctionContextString(); } else { FunctionName = FText::FromName(FunctionReference.GetMemberName()); if ((GEditor != NULL) && (GetDefault()->bShowFriendlyNames)) { FunctionName = FText::FromString(FName::NameToDisplayString(FunctionName.ToString(), false)); } } if(TitleType == ENodeTitleType::FullTitle) { FFormatNamedArguments Args; Args.Add(TEXT("FunctionName"), FunctionName); Args.Add(TEXT("ContextString"), ContextString); Args.Add(TEXT("RPCString"), RPCString); if (ContextString.IsEmpty() && RPCString.IsEmpty()) { return FText::Format(LOCTEXT("CallFunction_FullTitle", "{FunctionName}"), Args); } else if (ContextString.IsEmpty()) { return FText::Format(LOCTEXT("CallFunction_FullTitle_WithRPCString", "{FunctionName}\n{RPCString}"), Args); } else if (RPCString.IsEmpty()) { return FText::Format(LOCTEXT("CallFunction_FullTitle_WithContextString", "{FunctionName}\n{ContextString}"), Args); } else { return FText::Format(LOCTEXT("CallFunction_FullTitle_WithContextRPCString", "{FunctionName}\n{ContextString}\n{RPCString}"), Args); } } else { return FunctionName; } } void UK2Node_CallFunction::GetPinHoverText(const UEdGraphPin& Pin, FString& HoverTextOut) const { if (!bPinTooltipsValid) { for (UEdGraphPin* P : Pins) { if (!P->PinToolTip.IsEmpty() && ExpandAsEnumPins.Contains(P)) { continue; } P->PinToolTip.Reset(); GeneratePinTooltip(*P); } bPinTooltipsValid = true; } return UK2Node::GetPinHoverText(Pin, HoverTextOut); } void UK2Node_CallFunction::AllocateDefaultPins() { InvalidatePinTooltips(); UBlueprint* MyBlueprint = GetBlueprint(); UFunction* Function = GetTargetFunction(); // favor the skeleton function if possible (in case the signature has // changed, and not yet compiled). if (!FunctionReference.IsSelfContext()) { UClass* FunctionClass = FunctionReference.GetMemberParentClass(MyBlueprint->GeneratedClass); if (UBlueprintGeneratedClass* BpClassOwner = Cast(FunctionClass)) { // this function could currently only be a part of some skeleton // class (the blueprint has not be compiled with it yet), so let's // check the skeleton class as well, see if we can pull pin data // from there... UBlueprint* FunctionBlueprint = CastChecked(BpClassOwner->ClassGeneratedBy, ECastCheckedType::NullAllowed); if (FunctionBlueprint) { if (UFunction* SkelFunction = FindUField(FunctionBlueprint->SkeletonGeneratedClass, FunctionReference.GetMemberName())) { Function = SkelFunction; } } } } // First try remap table if (Function == NULL) { UClass* ParentClass = FunctionReference.GetMemberParentClass(GetBlueprintClassFromNode()); if (ParentClass != NULL) { if (UFunction* NewFunction = FMemberReference::FindRemappedField(ParentClass, FunctionReference.GetMemberName())) { // Found a remapped property, update the node Function = NewFunction; SetFromFunction(NewFunction); } } } if (Function == NULL) { // The function no longer exists in the stored scope // Try searching inside all function libraries, in case the function got refactored into one of them. for (TObjectIterator ClassIt; ClassIt; ++ClassIt) { UClass* TestClass = *ClassIt; if (TestClass->IsChildOf(UBlueprintFunctionLibrary::StaticClass())) { Function = FindUField(TestClass, FunctionReference.GetMemberName()); if (Function != NULL) { UClass* OldClass = FunctionReference.GetMemberParentClass(GetBlueprintClassFromNode()); Message_Note( FText::Format(LOCTEXT("FixedUpFunctionInLibraryFmt", "UK2Node_CallFunction: Fixed up function '{0}', originally in '{1}', now in library '{2}'."), FText::FromString(FunctionReference.GetMemberName().ToString()), (OldClass != NULL) ? FText::FromString(*OldClass->GetName()) : LOCTEXT("FixedUpFunctionInLibraryNull", "(null)"), FText::FromString(TestClass->GetName()) ).ToString() ); SetFromFunction(Function); break; } } } } // Now create the pins if we ended up with a valid function to call if (Function != NULL) { CreatePinsForFunctionCall(Function); } FCustomStructureParamHelper::UpdateCustomStructurePins(Function, this); Super::AllocateDefaultPins(); } /** Util to find self pin in an array */ UEdGraphPin* FindSelfPin(TArray& Pins) { for(int32 PinIdx=0; PinIdxPinName == UEdGraphSchema_K2::PN_Self) { return Pins[PinIdx]; } } return NULL; } void UK2Node_CallFunction::ReallocatePinsDuringReconstruction(TArray& OldPins) { // BEGIN TEMP // We had a bug where the class was being messed up by copy/paste, but the self pin class was still ok. This code fixes up those cases. UFunction* Function = GetTargetFunction(); if (Function == NULL) { if (UEdGraphPin* SelfPin = FindSelfPin(OldPins)) { if (UClass* SelfPinClass = Cast(SelfPin->PinType.PinSubCategoryObject.Get())) { if (UFunction* NewFunction = FindUField(SelfPinClass, FunctionReference.GetMemberName())) { SetFromFunction(NewFunction); } } } } // END TEMP Super::ReallocatePinsDuringReconstruction(OldPins); // Connect Execute and Then pins for functions, which became pure. ReconnectPureExecPins(OldPins); } UEdGraphPin* UK2Node_CallFunction::CreateSelfPin(const UFunction* Function) { return FBlueprintNodeStatics::CreateSelfPin(this, Function); } void UK2Node_CallFunction::CreateExecPinsForFunctionCall(const UFunction* Function) { bool bCreateSingleExecInputPin = true; bool bCreateThenPin = true; ExpandAsEnumPins.Reset(); // If not pure, create exec pins if (!bIsPureFunc) { // If we want enum->exec expansion, and it is not disabled, do it now if(bWantsEnumToExecExpansion) { TArray EnumNames; GetExpandEnumPinNames(Function, EnumNames); FProperty* PreviousInput = nullptr; for (const FName& EnumParamName : EnumNames) { FProperty* Prop = nullptr; UEnum* Enum = nullptr; if (FByteProperty* ByteProp = FindFProperty(Function, EnumParamName)) { Prop = ByteProp; Enum = ByteProp->Enum; } else if (FEnumProperty* EnumProp = FindFProperty(Function, EnumParamName)) { Prop = EnumProp; Enum = EnumProp->GetEnum(); } else if (FBoolProperty* BoolProp = FindFProperty(Function, EnumParamName)) { Prop = BoolProp; } if (Prop != nullptr) { const bool bIsFunctionInput = !Prop->HasAnyPropertyFlags(CPF_ReturnParm) && (!Prop->HasAnyPropertyFlags(CPF_OutParm) || Prop->HasAnyPropertyFlags(CPF_ReferenceParm)); const EEdGraphPinDirection Direction = bIsFunctionInput ? EGPD_Input : EGPD_Output; if (bIsFunctionInput) { if (PreviousInput) { bHasCompilerMessage = true; ErrorType = EMessageSeverity::Error; ErrorMsg = FString::Printf(TEXT("Parameter '%s' is listed as an ExpandEnumAsExecs input, but %s already was one. Only one is permitted."), *EnumParamName.ToString(), *PreviousInput->GetName()); break; } PreviousInput = Prop; } if (Enum) { // yay, found it! Now create exec pin for each int32 NumExecs = (Enum->NumEnums() - 1); for (int32 ExecIdx = 0; ExecIdx < NumExecs; ExecIdx++) { bool const bShouldBeHidden = Enum->HasMetaData(TEXT("Hidden"), ExecIdx) || Enum->HasMetaData(TEXT("Spacer"), ExecIdx); if (!bShouldBeHidden) { // Can't use Enum->GetNameByIndex here because it doesn't do namespace mangling const FString NameStr = Enum->GetNameStringByIndex(ExecIdx); UEdGraphPin* CreatedPin = nullptr; // todo: really only makes sense if there are multiple outputs if (bIsFunctionInput || EnumNames.Num() == 1) { CreatedPin = CreatePin(Direction, UEdGraphSchema_K2::PC_Exec, *NameStr); } else { CreatedPin = CreatePin(Direction, UEdGraphSchema_K2::PC_Exec, *NameStr); CreatedPin->PinFriendlyName = FText::FromString(FString::Printf(TEXT("(%s) %s"), *Prop->GetDisplayNameText().ToString(), *NameStr)); } ExpandAsEnumPins.Add(CreatedPin); if (Enum->HasMetaData(TEXT("Tooltip"), ExecIdx)) { FString EnumTooltip = Enum->GetMetaData(TEXT("Tooltip"), ExecIdx); if (const UEdGraphSchema_K2* const K2Schema = Cast(GetSchema())) { K2Schema->ConstructBasicPinTooltip(*CreatedPin, FText::FromString(EnumTooltip), CreatedPin->PinToolTip); } else { CreatedPin->PinToolTip = EnumTooltip; } } } } } else { check(Prop->IsA()); // Create a pin for true and false, note that the order here does not match the // numeric order of bool, but it is more natural to put true first (e.g. to match branch node): ExpandAsEnumPins.Add(CreatePin(Direction, UEdGraphSchema_K2::PC_Exec, TEXT("True"))); ExpandAsEnumPins.Add(CreatePin(Direction, UEdGraphSchema_K2::PC_Exec, TEXT("False"))); } if (bIsFunctionInput) { // If using ExpandEnumAsExec for input, don't want to add a input exec pin bCreateSingleExecInputPin = false; } else { // If using ExpandEnumAsExec for output, don't want to add a "then" pin bCreateThenPin = false; } } } } if (bCreateSingleExecInputPin) { // Single input exec pin CreatePin(EGPD_Input, UEdGraphSchema_K2::PC_Exec, UEdGraphSchema_K2::PN_Execute); } if (bCreateThenPin) { UEdGraphPin* OutputExecPin = CreatePin(EGPD_Output, UEdGraphSchema_K2::PC_Exec, UEdGraphSchema_K2::PN_Then); // Use 'completed' name for output pins on latent functions if (Function->HasMetaData(FBlueprintMetadata::MD_Latent)) { OutputExecPin->PinFriendlyName = FText::FromName(UEdGraphSchema_K2::PN_Completed); } } } } FName UK2Node_CallFunction::GetFunctionName() const { return FunctionReference.GetMemberName(); } void UK2Node_CallFunction::DetermineWantsEnumToExecExpansion(const UFunction* Function) { bWantsEnumToExecExpansion = false; if (WantsExecPinsForParams(Function)) { TArray EnumNamesToCheck; GetExpandEnumPinNames(Function, EnumNamesToCheck); for (int32 i = EnumNamesToCheck.Num() - 1; i >= 0; --i) { const FName& EnumParamName = EnumNamesToCheck[i]; FByteProperty* EnumProp = FindFProperty(Function, EnumParamName); if ((EnumProp != NULL && EnumProp->Enum != NULL) || FindFProperty(Function, EnumParamName)) { bWantsEnumToExecExpansion = true; EnumNamesToCheck.RemoveAt(i); } else { FBoolProperty* BoolProp = FindFProperty(Function, EnumParamName); if (BoolProp) { bWantsEnumToExecExpansion = true; EnumNamesToCheck.RemoveAt(i); } } } if (bWantsEnumToExecExpansion && EnumNamesToCheck.Num() > 0 && !bHasCompilerMessage) { bHasCompilerMessage = true; ErrorType = EMessageSeverity::Warning; if (EnumNamesToCheck.Num() == 1) { ErrorMsg = FText::Format(LOCTEXT("EnumToExecExpansionFailedFmt", "Unable to find enum parameter with name '{0}' to expand for @@"), FText::FromName(EnumNamesToCheck[0])).ToString(); } else { FString ParamNames; for (const FName& Name : EnumNamesToCheck) { if (!ParamNames.IsEmpty()) { ParamNames += TEXT(", "); } ParamNames += Name.ToString(); } ErrorMsg = FText::Format(LOCTEXT("EnumToExecExpansionFailedMultipleFmt", "Unable to find enum parameters for names:\n '{{0}}' \nto expand for @@"), FText::FromString(ParamNames)).ToString(); } } } } void UK2Node_CallFunction::GetExpandEnumPinNames(const UFunction* Function, TArray& EnumNamesToCheck) { EnumNamesToCheck.Reset(); // todo: use metadatacache if/when that's accepted. const FString EnumParamString = GetAllExecParams(Function); TArray RawGroupings; EnumParamString.ParseIntoArray(RawGroupings, TEXT(","), false); for (const FString& RawGroup : RawGroupings) { TArray IndividualEntries; RawGroup.ParseIntoArray(IndividualEntries, TEXT("|")); for (const FString& Entry : IndividualEntries) { if (Entry.IsEmpty()) { continue; } EnumNamesToCheck.Add(*Entry); } } } void UK2Node_CallFunction::GeneratePinTooltip(UEdGraphPin& Pin) const { ensure(Pin.GetOwningNode() == this); UEdGraphSchema const* Schema = GetSchema(); check(Schema != NULL); UEdGraphSchema_K2 const* const K2Schema = Cast(Schema); if (K2Schema == NULL) { Schema->ConstructBasicPinTooltip(Pin, FText::GetEmpty(), Pin.PinToolTip); return; } // get the class function object associated with this node UFunction* Function = GetTargetFunction(); if (Function == NULL) { Schema->ConstructBasicPinTooltip(Pin, FText::GetEmpty(), Pin.PinToolTip); return; } GeneratePinTooltipFromFunction(Pin, Function); } bool UK2Node_CallFunction::CreatePinsForFunctionCall(const UFunction* Function) { const UEdGraphSchema_K2* K2Schema = GetDefault(); UClass* FunctionOwnerClass = Function->GetOuterUClass(); bIsInterfaceCall = FunctionOwnerClass->HasAnyClassFlags(CLASS_Interface); bIsPureFunc = (Function->HasAnyFunctionFlags(FUNC_BlueprintPure) != false); bIsConstFunc = (Function->HasAnyFunctionFlags(FUNC_Const) != false); DetermineWantsEnumToExecExpansion(Function); // Create input pins CreateExecPinsForFunctionCall(Function); UEdGraphPin* SelfPin = CreateSelfPin(Function); // Renamed self pin to target SelfPin->PinFriendlyName = LOCTEXT("Target", "Target"); const bool bIsProtectedFunc = Function->GetBoolMetaData(FBlueprintMetadata::MD_Protected); const bool bIsStaticFunc = Function->HasAllFunctionFlags(FUNC_Static); UEdGraph const* const Graph = GetGraph(); UBlueprint* BP = FBlueprintEditorUtils::FindBlueprintForGraph(Graph); ensure(BP); if (BP != nullptr) { const bool bIsFunctionCompatibleWithSelf = BP->SkeletonGeneratedClass->IsChildOf(FunctionOwnerClass); if (bIsStaticFunc) { // For static methods, wire up the self to the CDO of the class if it's not us if (!bIsFunctionCompatibleWithSelf) { UClass* AuthoritativeClass = FunctionOwnerClass->GetAuthoritativeClass(); SelfPin->DefaultObject = AuthoritativeClass->GetDefaultObject(); } // Purity doesn't matter with a static function, we can always hide the self pin since we know how to call the method SelfPin->bHidden = true; } else { if (Function->GetBoolMetaData(FBlueprintMetadata::MD_HideSelfPin)) { SelfPin->bHidden = true; SelfPin->bNotConnectable = true; } else { // Hide the self pin if the function is compatible with the blueprint class and pure (the !bIsConstFunc portion should be going away soon too hopefully) SelfPin->bHidden = (bIsFunctionCompatibleWithSelf && bIsPureFunc && !bIsConstFunc); } } } // Build a list of the pins that should be hidden for this function (ones that are automagically filled in by the K2 compiler) TSet PinsToHide; TSet InternalPins; FBlueprintEditorUtils::GetHiddenPinsForFunction(Graph, Function, PinsToHide, &InternalPins); const bool bShowWorldContextPin = ((PinsToHide.Num() > 0) && BP && BP->ParentClass && BP->ParentClass->HasMetaDataHierarchical(FBlueprintMetadata::MD_ShowWorldContextPin)); // Create the inputs and outputs bool bAllPinsGood = true; for (TFieldIterator PropIt(Function); PropIt && (PropIt->PropertyFlags & CPF_Parm); ++PropIt) { FProperty* Param = *PropIt; const bool bIsFunctionInput = !Param->HasAnyPropertyFlags(CPF_ReturnParm) && (!Param->HasAnyPropertyFlags(CPF_OutParm) || Param->HasAnyPropertyFlags(CPF_ReferenceParm)); const bool bIsRefParam = Param->HasAnyPropertyFlags(CPF_ReferenceParm) && bIsFunctionInput; const EEdGraphPinDirection Direction = bIsFunctionInput ? EGPD_Input : EGPD_Output; UEdGraphNode::FCreatePinParams PinParams; PinParams.bIsReference = bIsRefParam; UEdGraphPin* Pin = CreatePin(Direction, NAME_None, Param->GetFName(), PinParams); const bool bPinGood = (Pin && K2Schema->ConvertPropertyToPinType(Param, /*out*/ Pin->PinType)); if (bPinGood) { // Check for a display name override const FString& PinDisplayName = Param->GetMetaData(FBlueprintMetadata::MD_DisplayName); if (!PinDisplayName.IsEmpty()) { Pin->PinFriendlyName = FText::FromString(PinDisplayName); } else if (Function->GetReturnProperty() == Param && Function->HasMetaData(FBlueprintMetadata::MD_ReturnDisplayName)) { Pin->PinFriendlyName = Function->GetMetaDataText(FBlueprintMetadata::MD_ReturnDisplayName); } //Flag pin as read only for const reference property Pin->bDefaultValueIsIgnored = Param->HasAllPropertyFlags(CPF_ConstParm | CPF_ReferenceParm) && (!Function->HasMetaData(FBlueprintMetadata::MD_AutoCreateRefTerm) || Pin->PinType.IsContainer()); const bool bAdvancedPin = Param->HasAllPropertyFlags(CPF_AdvancedDisplay); Pin->bAdvancedView = bAdvancedPin; if(bAdvancedPin && (ENodeAdvancedPins::NoPins == AdvancedPinDisplay)) { AdvancedPinDisplay = ENodeAdvancedPins::Hidden; } FString ParamValue; if (K2Schema->FindFunctionParameterDefaultValue(Function, Param, ParamValue)) { K2Schema->SetPinAutogeneratedDefaultValue(Pin, ParamValue); } else { K2Schema->SetPinAutogeneratedDefaultValueBasedOnType(Pin); } if (PinsToHide.Contains(Pin->PinName)) { const FString PinNameStr = Pin->PinName.ToString(); const FString& DefaultToSelfMetaValue = Function->GetMetaData(FBlueprintMetadata::MD_DefaultToSelf); const FString& WorldContextMetaValue = Function->GetMetaData(FBlueprintMetadata::MD_WorldContext); bool bIsSelfPin = ((PinNameStr == DefaultToSelfMetaValue) || (PinNameStr == WorldContextMetaValue)); if (!bShowWorldContextPin || !bIsSelfPin) { Pin->bHidden = true; Pin->bNotConnectable = InternalPins.Contains(Pin->PinName); } } PostParameterPinCreated(Pin); } bAllPinsGood = bAllPinsGood && bPinGood; } // If we have 'enum to exec' parameters, set their default value to something valid so we don't get warnings if(bWantsEnumToExecExpansion) { TArray EnumNamesToCheck; GetExpandEnumPinNames(Function, EnumNamesToCheck); for (const FName& Name : EnumNamesToCheck) { UEdGraphPin* EnumParamPin = FindPin(Name); if (UEnum* PinEnum = (EnumParamPin ? Cast(EnumParamPin->PinType.PinSubCategoryObject.Get()) : NULL)) { EnumParamPin->DefaultValue = PinEnum->GetNameStringByIndex(0); } } } return bAllPinsGood; } void UK2Node_CallFunction::PostReconstructNode() { Super::PostReconstructNode(); InvalidatePinTooltips(); // conform pins that are marked as SetParam: ConformContainerPins(); FCustomStructureParamHelper::UpdateCustomStructurePins(GetTargetFunction(), this); // Fixup self node, may have been overridden from old self node UFunction* Function = GetTargetFunction(); const bool bIsStaticFunc = Function ? Function->HasAllFunctionFlags(FUNC_Static) : false; UEdGraphPin* SelfPin = FindPin(UEdGraphSchema_K2::PN_Self); if (bIsStaticFunc && SelfPin) { // Wire up the self to the CDO of the class if it's not us if (UBlueprint* BP = GetBlueprint()) { UClass* FunctionOwnerClass = Function->GetOuterUClass(); if (!BP->SkeletonGeneratedClass->IsChildOf(FunctionOwnerClass)) { SelfPin->DefaultObject = FunctionOwnerClass->GetAuthoritativeClass()->GetDefaultObject(); } else { // In case a non-NULL reference was previously serialized on load, ensure that it's set to NULL here to match what a new node's self pin would be initialized as (see CreatePinsForFunctionCall). SelfPin->DefaultObject = nullptr; } } } if (UEdGraphPin* TypePickerPin = FDynamicOutputHelper::GetTypePickerPin(this)) { FDynamicOutputHelper(TypePickerPin).ConformOutputType(); } if (IsNodePure()) { // Remove any pre-existing breakpoint on this node since pure nodes cannot have breakpoints if (UBreakpoint* ExistingBreakpoint = FKismetDebugUtilities::FindBreakpointForNode(GetBlueprint(), this)) { // Remove the breakpoint FKismetDebugUtilities::StartDeletingBreakpoint(ExistingBreakpoint, GetBlueprint()); } } } void UK2Node_CallFunction::NotifyPinConnectionListChanged(UEdGraphPin* Pin) { Super::NotifyPinConnectionListChanged(Pin); // conform pins that are marked as SetParam: ConformContainerPins(); if (!ensure(Pin)) { return; } FCustomStructureParamHelper::UpdateCustomStructurePins(GetTargetFunction(), this, Pin); // Refresh the node to hide internal-only pins once the [invalid] connection has been broken if (Pin->bHidden && Pin->bNotConnectable && Pin->LinkedTo.Num() == 0) { GetGraph()->NotifyGraphChanged(); } if (bIsBeadFunction) { if (Pin->LinkedTo.Num() == 0) { // Commit suicide; bead functions must always have an input and output connection DestroyNode(); } } InvalidatePinTooltips(); if(!Pin->IsPendingKill()) { FDynamicOutputHelper(Pin).ConformOutputType(); } } void UK2Node_CallFunction::PinDefaultValueChanged(UEdGraphPin* Pin) { Super::PinDefaultValueChanged(Pin); InvalidatePinTooltips(); FDynamicOutputHelper(Pin).ConformOutputType(); } UFunction* UK2Node_CallFunction::GetTargetFunction() const { if(!FBlueprintCompilationManager::IsGeneratedClassLayoutReady()) { // first look in the skeleton class: if(UFunction* SkeletonFn = GetTargetFunctionFromSkeletonClass()) { return SkeletonFn; } } UFunction* Function = FunctionReference.ResolveMember(GetBlueprintClassFromNode()); return Function; } UFunction* UK2Node_CallFunction::GetTargetFunctionFromSkeletonClass() const { UFunction* TargetFunction = nullptr; UClass* ParentClass = FunctionReference.GetMemberParentClass( GetBlueprintClassFromNode() ); UBlueprint* OwningBP = ParentClass ? Cast( ParentClass->ClassGeneratedBy ) : nullptr; if( UClass* SkeletonClass = OwningBP ? OwningBP->SkeletonGeneratedClass : nullptr ) { TargetFunction = SkeletonClass->FindFunctionByName( FunctionReference.GetMemberName() ); } return TargetFunction; } UEdGraphPin* UK2Node_CallFunction::GetThenPin() const { UEdGraphPin* Pin = FindPin(UEdGraphSchema_K2::PN_Then); check(Pin == nullptr || Pin->Direction == EGPD_Output); // If pin exists, it must be output return Pin; } UEdGraphPin* UK2Node_CallFunction::GetReturnValuePin() const { UEdGraphPin* Pin = FindPin(UEdGraphSchema_K2::PN_ReturnValue); check(Pin == nullptr || Pin->Direction == EGPD_Output); // If pin exists, it must be output return Pin; } bool UK2Node_CallFunction::IsLatentFunction() const { if (UFunction* Function = GetTargetFunction()) { if (Function->HasMetaData(FBlueprintMetadata::MD_Latent)) { return true; } } return false; } bool UK2Node_CallFunction::AllowMultipleSelfs(bool bInputAsArray) const { if (UFunction* Function = GetTargetFunction()) { return CanFunctionSupportMultipleTargets(Function); } return Super::AllowMultipleSelfs(bInputAsArray); } bool UK2Node_CallFunction::CanFunctionSupportMultipleTargets(UFunction const* Function) { bool const bIsImpure = !Function->HasAnyFunctionFlags(FUNC_BlueprintPure); bool const bIsLatent = Function->HasMetaData(FBlueprintMetadata::MD_Latent); bool const bHasReturnParam = (Function->GetReturnProperty() != nullptr); return !bHasReturnParam && bIsImpure && !bIsLatent; } bool UK2Node_CallFunction::CanEditorOnlyFunctionBeCalled(const UFunction* InFunction, const UObject* InObject) { if (InFunction && InObject && (IsEditorOnlyObject(InFunction) || InFunction->HasAnyFunctionFlags(FUNC_EditorOnly))) { return IsEditorOnlyObject(InObject); } return true; } bool UK2Node_CallFunction::CanPasteHere(const UEdGraph* TargetGraph) const { // Basic check for graph compatibility, etc. bool bCanPaste = Super::CanPasteHere(TargetGraph); // Cannot paste editor only functions into runtime graphs if (bCanPaste) { UFunction* TargetFunction = GetTargetFunction(); bCanPaste = CanEditorOnlyFunctionBeCalled(TargetFunction, TargetGraph); } // We check function context for placability only in the base class case; derived classes are typically bound to // specific functions that should always be placeable, but may not always be explicitly callable (e.g. InternalUseOnly). if(bCanPaste && GetClass() == StaticClass()) { const UEdGraphSchema_K2* K2Schema = GetDefault(); uint32 AllowedFunctionTypes = UEdGraphSchema_K2::EFunctionType::FT_Pure | UEdGraphSchema_K2::EFunctionType::FT_Const | UEdGraphSchema_K2::EFunctionType::FT_Protected; if(K2Schema->DoesGraphSupportImpureFunctions(TargetGraph)) { AllowedFunctionTypes |= UEdGraphSchema_K2::EFunctionType::FT_Imperative; } UFunction* TargetFunction = GetTargetFunction(); if( !TargetFunction ) { TargetFunction = GetTargetFunctionFromSkeletonClass(); } if (!TargetFunction) { // If the function doesn't exist and it is from self context, then it could be created from a CustomEvent node, that was also pasted (but wasn't compiled yet). bCanPaste = FunctionReference.IsSelfContext(); } else { bCanPaste = K2Schema->CanFunctionBeUsedInGraph(FBlueprintEditorUtils::FindBlueprintForGraphChecked(TargetGraph)->GeneratedClass, TargetFunction, TargetGraph, AllowedFunctionTypes, false); } } return bCanPaste; } bool UK2Node_CallFunction::IsActionFilteredOut(FBlueprintActionFilter const& Filter) { bool bIsFilteredOut = false; for(UEdGraph* TargetGraph : Filter.Context.Graphs) { bIsFilteredOut |= !CanPasteHere(TargetGraph); } if(const UFunction* TargetFunction = GetTargetFunction()) { const bool bIsProtected = (TargetFunction->FunctionFlags & FUNC_Protected) != 0; const bool bIsPrivate = (TargetFunction->FunctionFlags & FUNC_Private) != 0; const UClass* OwningClass = TargetFunction->GetOwnerClass(); if( (bIsProtected || bIsPrivate) && !FBlueprintEditorUtils::IsNativeSignature(TargetFunction) && OwningClass) { OwningClass = OwningClass->GetAuthoritativeClass(); // we can filter private and protected blueprints that are unrelated: bool bAccessibleInAll = true; for (const UBlueprint* Blueprint : Filter.Context.Blueprints) { UClass* AuthoritativeClass = Blueprint->GeneratedClass; if(!AuthoritativeClass) { continue; } if(bIsPrivate) { bAccessibleInAll = bAccessibleInAll && AuthoritativeClass == OwningClass; } else if(bIsProtected) { bAccessibleInAll = bAccessibleInAll && AuthoritativeClass->IsChildOf(OwningClass); } } if(!bAccessibleInAll) { bIsFilteredOut = true; } } } return bIsFilteredOut; } static FLinearColor GetPalletteIconColor(UFunction const* Function) { bool const bIsPure = (Function != nullptr) && Function->HasAnyFunctionFlags(FUNC_BlueprintPure); if (bIsPure) { return GetDefault()->PureFunctionCallNodeTitleColor; } return GetDefault()->FunctionCallNodeTitleColor; } FSlateIcon UK2Node_CallFunction::GetPaletteIconForFunction(UFunction const* Function, FLinearColor& OutColor) { static const FName NativeMakeFunc(TEXT("NativeMakeFunc")); static const FName NativeBrakeFunc(TEXT("NativeBreakFunc")); if (Function && Function->HasMetaData(NativeMakeFunc)) { static FSlateIcon Icon("EditorStyle", "GraphEditor.MakeStruct_16x"); return Icon; } else if (Function && Function->HasMetaData(NativeBrakeFunc)) { static FSlateIcon Icon("EditorStyle", "GraphEditor.BreakStruct_16x"); return Icon; } // Check to see if the function is calling an function that could be an event, display the event icon instead. else if (Function && UEdGraphSchema_K2::FunctionCanBePlacedAsEvent(Function)) { static FSlateIcon Icon("EditorStyle", "GraphEditor.Event_16x"); return Icon; } else { OutColor = GetPalletteIconColor(Function); static FSlateIcon Icon("EditorStyle", "Kismet.AllClasses.FunctionIcon"); return Icon; } } FLinearColor UK2Node_CallFunction::GetNodeTitleColor() const { return GetPalletteIconColor(GetTargetFunction()); } FText UK2Node_CallFunction::GetTooltipText() const { FText Tooltip; UFunction* Function = GetTargetFunction(); if (Function == nullptr) { return FText::Format(LOCTEXT("CallUnknownFunction", "Call unknown function {0}"), FText::FromName(FunctionReference.GetMemberName())); } else if (CachedTooltip.IsOutOfDate(this)) { FText BaseTooltip = FText::FromString(GetDefaultTooltipForFunction(Function)); FFormatNamedArguments Args; Args.Add(TEXT("DefaultTooltip"), BaseTooltip); if (Function->HasAllFunctionFlags(FUNC_BlueprintAuthorityOnly)) { Args.Add( TEXT("ClientString"), NSLOCTEXT("K2Node", "ServerFunction", "Authority Only. This function will only execute on the server.") ); // FText::Format() is slow, so we cache this to save on performance CachedTooltip.SetCachedText(FText::Format(LOCTEXT("CallFunction_SubtitledTooltip", "{DefaultTooltip}\n\n{ClientString}"), Args), this); } else if (Function->HasAllFunctionFlags(FUNC_BlueprintCosmetic)) { Args.Add( TEXT("ClientString"), NSLOCTEXT("K2Node", "ClientFunction", "Cosmetic. This event is only for cosmetic, non-gameplay actions.") ); // FText::Format() is slow, so we cache this to save on performance CachedTooltip.SetCachedText(FText::Format(LOCTEXT("CallFunction_SubtitledTooltip", "{DefaultTooltip}\n\n{ClientString}"), Args), this); } else { CachedTooltip.SetCachedText(BaseTooltip, this); } } return CachedTooltip; } void UK2Node_CallFunction::GeneratePinTooltipFromFunction(UEdGraphPin& Pin, const UFunction* Function) { if (Pin.bWasTrashed) { return; } // figure what tag we should be parsing for (is this a return-val pin, or a parameter?) FString ParamName; FString TagStr = TEXT("@param"); const bool bReturnPin = Pin.PinName == UEdGraphSchema_K2::PN_ReturnValue; if (bReturnPin) { TagStr = TEXT("@return"); } else { ParamName = Pin.PinName.ToString(); } // grab the the function's comment block for us to parse FString FunctionToolTipText = Function->GetToolTipText().ToString(); int32 CurStrPos = INDEX_NONE; int32 FullToolTipLen = FunctionToolTipText.Len(); // parse the full function tooltip text, looking for tag lines do { CurStrPos = FunctionToolTipText.Find(TagStr, ESearchCase::IgnoreCase, ESearchDir::FromStart, CurStrPos); if (CurStrPos == INDEX_NONE) // if the tag wasn't found { break; } // advance past the tag CurStrPos += TagStr.Len(); // handle people having done @returns instead of @return if (bReturnPin && CurStrPos < FullToolTipLen && FunctionToolTipText[CurStrPos] == TEXT('s')) { ++CurStrPos; } // advance past whitespace while(CurStrPos < FullToolTipLen && FChar::IsWhitespace(FunctionToolTipText[CurStrPos])) { ++CurStrPos; } // if this is a parameter pin if (!ParamName.IsEmpty()) { FString TagParamName; // copy the parameter name while (CurStrPos < FullToolTipLen && !FChar::IsWhitespace(FunctionToolTipText[CurStrPos])) { TagParamName.AppendChar(FunctionToolTipText[CurStrPos++]); } // if this @param tag doesn't match the param we're looking for if (TagParamName != ParamName) { continue; } } // advance past whitespace (get to the meat of the comment) // since many doxygen style @param use the format "@param - " we also strip - if it is before we get to any other non-whitespace while(CurStrPos < FullToolTipLen && (FChar::IsWhitespace(FunctionToolTipText[CurStrPos]) || FunctionToolTipText[CurStrPos] == '-')) { ++CurStrPos; } FString ParamDesc; // collect the param/return-val description while (CurStrPos < FullToolTipLen && FunctionToolTipText[CurStrPos] != TEXT('@')) { // advance past newline while(CurStrPos < FullToolTipLen && FChar::IsLinebreak(FunctionToolTipText[CurStrPos])) { ++CurStrPos; // advance past whitespace at the start of a new line while(CurStrPos < FullToolTipLen && FChar::IsWhitespace(FunctionToolTipText[CurStrPos])) { ++CurStrPos; } // replace the newline with a single space if(CurStrPos < FullToolTipLen && !FChar::IsLinebreak(FunctionToolTipText[CurStrPos])) { ParamDesc.AppendChar(TEXT(' ')); } } if (CurStrPos < FullToolTipLen && FunctionToolTipText[CurStrPos] != TEXT('@')) { ParamDesc.AppendChar(FunctionToolTipText[CurStrPos++]); } } // trim any trailing whitespace from the descriptive text ParamDesc.TrimEndInline(); // if we came up with a valid description for the param/return-val if (!ParamDesc.IsEmpty()) { Pin.PinToolTip += ParamDesc; break; // we found a match, so there's no need to continue } } while (CurStrPos < FullToolTipLen); // If we have no parameter or return value descriptions the full description will be relevant in describing the return value: if( bReturnPin && Pin.PinToolTip.IsEmpty() && FunctionToolTipText.Find(TEXT("@param")) == INDEX_NONE && FunctionToolTipText.Find(TEXT("@return")) == INDEX_NONE) { // for the return pin, default to using the function description if no @return tag was provided: Pin.PinToolTip = Function->GetToolTipText().ToString(); } GetDefault()->ConstructBasicPinTooltip(Pin, FText::FromString(Pin.PinToolTip), Pin.PinToolTip); } FText UK2Node_CallFunction::GetUserFacingFunctionName(const UFunction* Function) { FText ReturnDisplayName; if (Function != NULL) { if (GEditor && GetDefault()->bShowFriendlyNames) { ReturnDisplayName = Function->GetDisplayNameText(); } else { static const FString Namespace = TEXT("UObjectDisplayNames"); const FString Key = Function->GetFullGroupName(false); ReturnDisplayName = Function->GetMetaDataText(TEXT("DisplayName"), Namespace, Key); } } return ReturnDisplayName; } FString UK2Node_CallFunction::GetDefaultTooltipForFunction(const UFunction* Function) { FString Tooltip; if (Function != NULL) { Tooltip = Function->GetToolTipText().ToString(); } if (!Tooltip.IsEmpty()) { // Strip off the doxygen nastiness static const FString DoxygenParam(TEXT("@param")); static const FString DoxygenReturn(TEXT("@return")); static const FString DoxygenSee(TEXT("@see")); static const FString TooltipSee(TEXT("See:")); static const FString DoxygenNote(TEXT("@note")); static const FString TooltipNote(TEXT("Note:")); Tooltip.Split(DoxygenParam, &Tooltip, nullptr, ESearchCase::IgnoreCase, ESearchDir::FromStart); Tooltip.Split(DoxygenReturn, &Tooltip, nullptr, ESearchCase::IgnoreCase, ESearchDir::FromStart); Tooltip.ReplaceInline(*DoxygenSee, *TooltipSee); Tooltip.ReplaceInline(*DoxygenNote, *TooltipNote); Tooltip.TrimStartAndEndInline(); UClass* CurrentSelfClass = (Function != NULL) ? Function->GetOwnerClass() : NULL; UClass const* TrueSelfClass = CurrentSelfClass; if (CurrentSelfClass && CurrentSelfClass->ClassGeneratedBy) { TrueSelfClass = CurrentSelfClass->GetAuthoritativeClass(); } FText TargetDisplayText = (TrueSelfClass != NULL) ? TrueSelfClass->GetDisplayNameText() : LOCTEXT("None", "None"); FFormatNamedArguments Args; Args.Add(TEXT("TargetName"), TargetDisplayText); Args.Add(TEXT("Tooltip"), FText::FromString(Tooltip)); return FText::Format(LOCTEXT("CallFunction_Tooltip", "{Tooltip}\n\nTarget is {TargetName}"), Args).ToString(); } else { return GetUserFacingFunctionName(Function).ToString(); } } FText UK2Node_CallFunction::GetDefaultCategoryForFunction(const UFunction* Function, const FText& BaseCategory) { FText NodeCategory = BaseCategory; if( Function->HasMetaData(FBlueprintMetadata::MD_FunctionCategory) ) { FText FuncCategory; // If we are not showing friendly names, return the metadata stored, without localization if( GEditor && !GetDefault()->bShowFriendlyNames ) { FuncCategory = FText::FromString(Function->GetMetaData(FBlueprintMetadata::MD_FunctionCategory)); } else { // Look for localized metadata FuncCategory = FObjectEditorUtils::GetCategoryText(Function); // If the result is culture invariant, force it into a display string if (FuncCategory.IsCultureInvariant()) { FuncCategory = FText::FromString(FName::NameToDisplayString(FuncCategory.ToString(), false)); } } // Combine with the BaseCategory to form the full category, delimited by "|" if (!FuncCategory.IsEmpty() && !NodeCategory.IsEmpty()) { NodeCategory = FText::Format(FText::FromString(TEXT("{0}|{1}")), NodeCategory, FuncCategory); } else if (NodeCategory.IsEmpty()) { NodeCategory = FuncCategory; } } return NodeCategory; } FText UK2Node_CallFunction::GetKeywordsForFunction(const UFunction* Function) { // If the friendly name and real function name do not match add the real function name friendly name as a keyword. FString Keywords; if( Function->GetName() != GetUserFacingFunctionName(Function).ToString() ) { Keywords = Function->GetName(); } if (ShouldDrawCompact(Function)) { Keywords.AppendChar(TEXT(' ')); Keywords += GetCompactNodeTitle(Function); } FText MetadataKeywords = Function->GetMetaDataText(FBlueprintMetadata::MD_FunctionKeywords, TEXT("UObjectKeywords"), Function->GetFullGroupName(false)); FText ResultKeywords; if (!MetadataKeywords.IsEmpty()) { FFormatNamedArguments Args; Args.Add(TEXT("Name"), FText::FromString(Keywords)); Args.Add(TEXT("MetadataKeywords"), MetadataKeywords); ResultKeywords = FText::Format(FText::FromString("{Name} {MetadataKeywords}"), Args); } else { ResultKeywords = FText::FromString(Keywords); } return ResultKeywords; } void UK2Node_CallFunction::SetFromFunction(const UFunction* Function) { if (Function != NULL) { bIsPureFunc = Function->HasAnyFunctionFlags(FUNC_BlueprintPure); bIsConstFunc = Function->HasAnyFunctionFlags(FUNC_Const); DetermineWantsEnumToExecExpansion(Function); FunctionReference.SetFromField(Function, GetBlueprintClassFromNode()); } } FString UK2Node_CallFunction::GetDocumentationLink() const { UClass* ParentClass = NULL; if (FunctionReference.IsSelfContext()) { if (HasValidBlueprint()) { UFunction* Function = FindUField(GetBlueprint()->GeneratedClass, FunctionReference.GetMemberName()); if (Function != NULL) { ParentClass = Function->GetOwnerClass(); } } } else { ParentClass = FunctionReference.GetMemberParentClass(GetBlueprintClassFromNode()); } if (ParentClass != NULL) { return FString::Printf(TEXT("Shared/GraphNodes/Blueprint/%s%s"), ParentClass->GetPrefixCPP(), *ParentClass->GetName()); } return FString("Shared/GraphNodes/Blueprint/UK2Node_CallFunction"); } FString UK2Node_CallFunction::GetDocumentationExcerptName() const { return FunctionReference.GetMemberName().ToString(); } FString UK2Node_CallFunction::GetDescriptiveCompiledName() const { return FString(TEXT("CallFunc_")) + FunctionReference.GetMemberName().ToString(); } bool UK2Node_CallFunction::ShouldDrawCompact(const UFunction* Function) { return (Function != NULL) && Function->HasMetaData(FBlueprintMetadata::MD_CompactNodeTitle); } bool UK2Node_CallFunction::ShouldDrawCompact() const { UFunction* Function = GetTargetFunction(); return ShouldDrawCompact(Function); } bool UK2Node_CallFunction::ShouldDrawAsBead() const { return bIsBeadFunction; } bool UK2Node_CallFunction::ShouldShowNodeProperties() const { // Show node properties if this corresponds to a function graph if (FunctionReference.GetMemberName() != NAME_None && HasValidBlueprint()) { return FindObject(GetBlueprint(), *(FunctionReference.GetMemberName().ToString())) != NULL; } return false; } FString UK2Node_CallFunction::GetCompactNodeTitle(const UFunction* Function) { static const FString ProgrammerMultiplicationSymbol = TEXT("*"); static const FString CommonMultiplicationSymbol = TEXT("\xD7"); static const FString ProgrammerDivisionSymbol = TEXT("/"); static const FString CommonDivisionSymbol = TEXT("\xF7"); static const FString ProgrammerConversionSymbol = TEXT("->"); static const FString CommonConversionSymbol = TEXT("\x2022"); const FString& OperatorTitle = Function->GetMetaData(FBlueprintMetadata::MD_CompactNodeTitle); if (!OperatorTitle.IsEmpty()) { if (OperatorTitle == ProgrammerMultiplicationSymbol) { return CommonMultiplicationSymbol; } else if (OperatorTitle == ProgrammerDivisionSymbol) { return CommonDivisionSymbol; } else if (OperatorTitle == ProgrammerConversionSymbol) { return CommonConversionSymbol; } else { return OperatorTitle; } } return Function->GetName(); } FText UK2Node_CallFunction::GetCompactNodeTitle() const { UFunction* Function = GetTargetFunction(); if (Function != NULL) { return FText::FromString(GetCompactNodeTitle(Function)); } else { return Super::GetCompactNodeTitle(); } } void UK2Node_CallFunction::GetRedirectPinNames(const UEdGraphPin& Pin, TArray& RedirectPinNames) const { Super::GetRedirectPinNames(Pin, RedirectPinNames); if (RedirectPinNames.Num() > 0) { const FString OldPinName = RedirectPinNames[0]; // first add functionname.param RedirectPinNames.Add(FString::Printf(TEXT("%s.%s"), *FunctionReference.GetMemberName().ToString(), *OldPinName)); // if there is class, also add an option for class.functionname.param UClass* FunctionClass = FunctionReference.GetMemberParentClass(GetBlueprintClassFromNode()); while (FunctionClass) { RedirectPinNames.Add(FString::Printf(TEXT("%s.%s.%s"), *FunctionClass->GetName(), *FunctionReference.GetMemberName().ToString(), *OldPinName)); FunctionClass = FunctionClass->GetSuperClass(); } } } void UK2Node_CallFunction::FixupSelfMemberContext() { UBlueprint* Blueprint = FBlueprintEditorUtils::FindBlueprintForNode(this); auto IsBlueprintOfType = [Blueprint](UClass* ClassType)->bool { bool bIsChildOf = Blueprint && (Blueprint->GeneratedClass != nullptr) && Blueprint->GeneratedClass->IsChildOf(ClassType); if (!bIsChildOf && Blueprint && (Blueprint->SkeletonGeneratedClass)) { bIsChildOf = Blueprint->SkeletonGeneratedClass->IsChildOf(ClassType); } return bIsChildOf; }; UClass* MemberClass = FunctionReference.GetMemberParentClass(); if (FunctionReference.IsSelfContext()) { // if there is a function that matches the reference in the new context // and there are no connections to the self pin, we just want to call // that function UEdGraphPin* SelfPin = GetDefault()->FindSelfPin(*this, EGPD_Input); if (!FunctionReference.ResolveMember(Blueprint) || (SelfPin && SelfPin->HasAnyConnections())) { if (MemberClass == nullptr) { // the self pin may have type information stored on it if (SelfPin) { MemberClass = Cast(SelfPin->PinType.PinSubCategoryObject.Get()); } } // if we happened to retain the ParentClass for a self reference // (unlikely), then we know where this node came from... let's keep it // referencing that function if (MemberClass != nullptr) { if (!IsBlueprintOfType(MemberClass)) { FunctionReference.SetExternalMember(FunctionReference.GetMemberName(), MemberClass); } } // else, there is nothing we can do... the node will produce an error later during compilation } } else if (MemberClass != nullptr) { if (IsBlueprintOfType(MemberClass)) { FunctionReference.SetSelfMember(FunctionReference.GetMemberName()); } } } void UK2Node_CallFunction::PostPasteNode() { Super::PostPasteNode(); FixupSelfMemberContext(); if (UFunction* Function = GetTargetFunction()) { if (Pins.Num() > 0) { // After pasting we need to go through and ensure the hidden the self pins is correct in case the source blueprint had different metadata TSet PinsToHide; FBlueprintEditorUtils::GetHiddenPinsForFunction(GetGraph(), Function, PinsToHide); const bool bShowWorldContextPin = ((PinsToHide.Num() > 0) && GetBlueprint()->ParentClass->HasMetaDataHierarchical(FBlueprintMetadata::MD_ShowWorldContextPin)); const FString& DefaultToSelfMetaValue = Function->GetMetaData(FBlueprintMetadata::MD_DefaultToSelf); const FString& WorldContextMetaValue = Function->GetMetaData(FBlueprintMetadata::MD_WorldContext); const UEdGraphSchema_K2* K2Schema = GetDefault(); for (int32 PinIndex = 0; PinIndex < Pins.Num(); ++PinIndex) { UEdGraphPin* Pin = Pins[PinIndex]; const FString PinNameStr = Pin->PinName.ToString(); const bool bIsSelfPin = ((PinNameStr == DefaultToSelfMetaValue) || (PinNameStr == WorldContextMetaValue)); const bool bPinShouldBeHidden = ((Pin->SubPins.Num() > 0) || (PinsToHide.Contains(Pin->PinName) && (!bShowWorldContextPin || !bIsSelfPin))); if (bPinShouldBeHidden && !Pin->bHidden) { Pin->BreakAllPinLinks(); K2Schema->SetPinAutogeneratedDefaultValueBasedOnType(Pin); } Pin->bHidden = bPinShouldBeHidden; } } } } void UK2Node_CallFunction::PostDuplicate(bool bDuplicateForPIE) { Super::PostDuplicate(bDuplicateForPIE); if (!bDuplicateForPIE && (!this->HasAnyFlags(RF_Transient))) { FixupSelfMemberContext(); } } void UK2Node_CallFunction::ValidateNodeDuringCompilation(class FCompilerResultsLog& MessageLog) const { Super::ValidateNodeDuringCompilation(MessageLog); const UBlueprint* Blueprint = GetBlueprint(); UFunction *Function = GetTargetFunction(); if (Function == NULL) { FString OwnerName; if (Blueprint != nullptr) { OwnerName = Blueprint->GetName(); if (UClass* FuncOwnerClass = FunctionReference.GetMemberParentClass(Blueprint->GeneratedClass)) { OwnerName = FuncOwnerClass->GetName(); } } FString const FunctName = FunctionReference.GetMemberName().ToString(); FText const WarningFormat = LOCTEXT("FunctionNotFoundFmt", "Could not find a function named \"{0}\" in '{1}'.\nMake sure '{2}' has been compiled for @@"); MessageLog.Error(*FText::Format(WarningFormat, FText::FromString(FunctName), FText::FromString(OwnerName), FText::FromString(OwnerName)).ToString(), this); } else if (WantsExecPinsForParams(Function) && bWantsEnumToExecExpansion == false) { // will technically not have a properly formatted output for multiple params... but /shrug. const FString EnumParamName = GetAllExecParams(Function); MessageLog.Warning(*FText::Format(LOCTEXT("EnumToExecExpansionFailedFmt", "Unable to find enum parameter with name '{0}' to expand for @@"), FText::FromString(EnumParamName)).ToString(), this); } const UClass* BlueprintClass = Blueprint ? Blueprint->ParentClass : nullptr; const bool bIsEditorOnlyBlueprintBaseClass = !BlueprintClass || IsEditorOnlyObject(BlueprintClass); // This error is disabled while we figure out how we can identify uncooked only // blueprints that want to make use of uncooked only APIs: #if 0 const bool bIsUncookedOnlyFunction = Function && Function->GetOutermost()->HasAllPackagesFlags(PKG_UncookedOnly); if ( bIsUncookedOnlyFunction && // Only allow calls to uncooked only functions from editor only/uncooked only // contexts: !( GetOutermost()->HasAnyPackageFlags(PKG_UncookedOnly|PKG_EditorOnly) || bIsEditorOnlyBlueprintBaseClass )) { MessageLog.Error(*LOCTEXT("UncookedOnlyError", "Attempting to call uncooked only function @@ in runtime blueprint").ToString(), this); } #endif //0 // Ensure that editor module BP exposed UFunctions can only be called in blueprints for which the base class is also part of an editor module // Also check for functions wrapped in WITH_EDITOR if (Function && Blueprint && (IsEditorOnlyObject(Function) || Function->HasAnyFunctionFlags(FUNC_EditorOnly))) { if (!bIsEditorOnlyBlueprintBaseClass) { FString const FunctName = Function->GetName(); FText const WarningFormat = LOCTEXT("EditorFunctionFmt", "Cannot use the editor function \"{0}\" in this runtime Blueprint. Only for use in Editor Utility Blueprints and Blutilities."); MessageLog.Error(*FText::Format(WarningFormat, FText::FromString(FunctName)).ToString(), this); } // enforce UnsafeDuringActorConstruction keyword if (Function->HasMetaData(FBlueprintMetadata::MD_UnsafeForConstructionScripts)) { // emit warning if we are in a construction script UEdGraph const* const Graph = GetGraph(); bool bNodeIsInConstructionScript = UEdGraphSchema_K2::IsConstructionScript(Graph); if (bNodeIsInConstructionScript == false) { // IsConstructionScript() can return false if graph was cloned from the construction script // in that case, check the function entry TArray EntryPoints; Graph->GetNodesOfClass(EntryPoints); if (EntryPoints.Num() == 1) { UK2Node_FunctionEntry const* const Node = EntryPoints[0]; if (Node) { UFunction* const SignatureFunction = Node->FunctionReference.ResolveMember(Node->GetBlueprintClassFromNode()); bNodeIsInConstructionScript = SignatureFunction && (SignatureFunction->GetFName() == UEdGraphSchema_K2::FN_UserConstructionScript); } } } if ( bNodeIsInConstructionScript ) { MessageLog.Warning(*LOCTEXT("FunctionUnsafeDuringConstruction", "Function '@@' is unsafe to call in a construction script.").ToString(), this); } } // enforce WorldContext restrictions const bool bInsideBpFuncLibrary = Blueprint && (BPTYPE_FunctionLibrary == Blueprint->BlueprintType); if (!bInsideBpFuncLibrary && Function->HasMetaData(FBlueprintMetadata::MD_WorldContext) && !Function->HasMetaData(FBlueprintMetadata::MD_CallableWithoutWorldContext)) { check(Blueprint); UClass* ParentClass = Blueprint->ParentClass; check(ParentClass); if (ParentClass && !FBlueprintEditorUtils::ImplementsGetWorld(Blueprint) && !ParentClass->HasMetaDataHierarchical(FBlueprintMetadata::MD_ShowWorldContextPin)) { MessageLog.Warning(*LOCTEXT("FunctionUnsafeInContext", "Function '@@' is unsafe to call from blueprints of class '@@'.").ToString(), this, ParentClass); } } if(Blueprint && !FBlueprintEditorUtils::IsNativeSignature(Function)) { // enforce protected function restriction const bool bCanTreatAsError = Blueprint->GetLinkerCustomVersion(FFrameworkObjectVersion::GUID) >= FFrameworkObjectVersion::EnforceBlueprintFunctionVisibility; const bool bIsProtected = (Function->FunctionFlags & FUNC_Protected) != 0; const bool bFuncBelongsToSubClass = Blueprint->SkeletonGeneratedClass->IsChildOf(Function->GetOuterUClass()); if (bIsProtected && !bFuncBelongsToSubClass) { if(bCanTreatAsError) { MessageLog.Error(*LOCTEXT("FunctionProtectedAccessed", "Function '@@' is protected and can't be accessed outside of its hierarchy.").ToString(), this); } else { MessageLog.Note(*LOCTEXT("FunctionProtectedAccessedNote", "Function '@@' is protected and can't be accessed outside of its hierarchy - this will be an error if the asset is resaved.").ToString(), this); } } // enforce private function restriction const bool bIsPrivate = (Function->FunctionFlags & FUNC_Private) != 0; const bool bFuncBelongsToClass = bFuncBelongsToSubClass && (Blueprint->SkeletonGeneratedClass == Function->GetOuterUClass()); if (bIsPrivate && !bFuncBelongsToClass) { if(bCanTreatAsError) { MessageLog.Error(*LOCTEXT("FunctionPrivateAccessed", "Function '@@' is private and can't be accessed outside of its defined class '@@'.").ToString(), this, Function->GetOuterUClass()); } else { MessageLog.Note(*LOCTEXT("FunctionPrivateAccessedNote", "Function '@@' is private and can't be accessed outside of its defined class '@@' - this will be an error if the asset is resaved.").ToString(), this, Function->GetOuterUClass()); } } } } FDynamicOutputHelper::VerifyNode(this, MessageLog); for (UEdGraphPin* Pin : Pins) { if (Pin && Pin->PinType.bIsWeakPointer && !Pin->PinType.IsContainer()) { const FString ErrorString = FText::Format( LOCTEXT("WeakPtrNotSupportedErrorFmt", "Weak pointers are not supported as function parameters. Pin '{0}' @@"), FText::FromString(Pin->GetName()) ).ToString(); MessageLog.Error(*ErrorString, this); } } } void UK2Node_CallFunction::Serialize(FArchive& Ar) { Super::Serialize(Ar); Ar.UsingCustomVersion(FReleaseObjectVersion::GUID); if (Ar.IsLoading()) { if (Ar.UEVer() < VER_UE4_SWITCH_CALL_NODE_TO_USE_MEMBER_REFERENCE) { UFunction* Function = FindUField(CallFunctionClass_DEPRECATED, CallFunctionName_DEPRECATED); const bool bProbablySelfCall = (CallFunctionClass_DEPRECATED == NULL) || ((Function != NULL) && (Function->GetOuterUClass()->ClassGeneratedBy == GetBlueprint())); FunctionReference.SetDirect(CallFunctionName_DEPRECATED, FGuid(), CallFunctionClass_DEPRECATED, bProbablySelfCall); } if(Ar.UEVer() < VER_UE4_K2NODE_REFERENCEGUIDS) { FGuid FunctionGuid; if (UBlueprint::GetGuidFromClassByFieldName(GetBlueprint()->GeneratedClass, FunctionReference.GetMemberName(), FunctionGuid)) { const bool bSelf = FunctionReference.IsSelfContext(); FunctionReference.SetDirect(FunctionReference.GetMemberName(), FunctionGuid, (bSelf ? NULL : FunctionReference.GetMemberParentClass((UClass*)NULL)), bSelf); } } // Consider the 'CPF_UObjectWrapper' flag on native function call parameters and return values. if (Ar.CustomVer(FReleaseObjectVersion::GUID) < FReleaseObjectVersion::PinTypeIncludesUObjectWrapperFlag) { if (UFunction* TargetFunction = GetTargetFunction()) { if (TargetFunction->IsNative()) { for (TFieldIterator PropIt(TargetFunction); PropIt && (PropIt->PropertyFlags & CPF_Parm); ++PropIt) { if (UEdGraphPin* Pin = FindPin(PropIt->GetFName())) { if (const FMapProperty* MapProperty = CastField(*PropIt)) { if (MapProperty->KeyProp && MapProperty->KeyProp->HasAllPropertyFlags(CPF_UObjectWrapper)) { Pin->PinType.bIsUObjectWrapper = 1; } if (MapProperty->ValueProp && MapProperty->ValueProp->HasAllPropertyFlags(CPF_UObjectWrapper)) { Pin->PinType.PinValueType.bTerminalIsUObjectWrapper = true; } } else if (const FSetProperty* SetProperty = CastField(*PropIt)) { if (SetProperty->ElementProp && SetProperty->ElementProp->HasAllPropertyFlags(CPF_UObjectWrapper)) { Pin->PinType.PinValueType.bTerminalIsUObjectWrapper = true; } } else if(const FArrayProperty* ArrayProperty = CastField(*PropIt)) { if(ArrayProperty->Inner && ArrayProperty->Inner->HasAllPropertyFlags(CPF_UObjectWrapper)) { Pin->PinType.PinValueType.bTerminalIsUObjectWrapper = true; } } else if (PropIt->HasAllPropertyFlags(CPF_UObjectWrapper)) { Pin->PinType.bIsUObjectWrapper = 1; } } } } } } if (!Ar.IsObjectReferenceCollector()) { // Don't validate the enabled state if the user has explicitly set it. Also skip validation if we're just duplicating this node. const bool bIsDuplicating = (Ar.GetPortFlags() & PPF_Duplicate) != 0; if (!bIsDuplicating && !HasUserSetTheEnabledState()) { if (const UFunction* Function = GetTargetFunction()) { // Enable as development-only if specified in metadata. This way existing functions that have the metadata added to them will get their enabled state fixed up on load. if (GetDesiredEnabledState() == ENodeEnabledState::Enabled && Function->HasMetaData(FBlueprintMetadata::MD_DevelopmentOnly)) { SetEnabledState(ENodeEnabledState::DevelopmentOnly, /*bUserAction=*/ false); } // Ensure that if the metadata is removed, we also fix up the enabled state to avoid leaving it set as development-only in that case. else if (GetDesiredEnabledState() == ENodeEnabledState::DevelopmentOnly && !Function->HasMetaData(FBlueprintMetadata::MD_DevelopmentOnly)) { SetEnabledState(ENodeEnabledState::Enabled, /*bUserAction=*/ false); } } } } } } void UK2Node_CallFunction::PostPlacedNewNode() { Super::PostPlacedNewNode(); // Try re-setting the function given our new parent scope, in case it turns an external to an internal, or vis versa FunctionReference.RefreshGivenNewSelfScope(GetBlueprintClassFromNode()); // Set the node to development only if the function specifies that check(!HasUserSetTheEnabledState()); if (const UFunction* Function = GetTargetFunction()) { if (Function->HasMetaData(FBlueprintMetadata::MD_DevelopmentOnly)) { SetEnabledState(ENodeEnabledState::DevelopmentOnly, /*bUserAction=*/ false); } } } FNodeHandlingFunctor* UK2Node_CallFunction::CreateNodeHandler(FKismetCompilerContext& CompilerContext) const { return new FKCHandler_CallFunction(CompilerContext); } void UK2Node_CallFunction::ExpandNode(class FKismetCompilerContext& CompilerContext, UEdGraph* SourceGraph) { Super::ExpandNode(CompilerContext, SourceGraph); const UEdGraphSchema_K2* Schema = CompilerContext.GetSchema(); UFunction* Function = GetTargetFunction(); // connect DefaultToSelf and WorldContext inside static functions to proper 'self' if (SourceGraph && Schema->IsStaticFunctionGraph(SourceGraph) && Function) { TArray EntryPoints; SourceGraph->GetNodesOfClass(EntryPoints); if (1 != EntryPoints.Num()) { CompilerContext.MessageLog.Warning(*FText::Format(LOCTEXT("WrongEntryPointsNumFmt", "{0} entry points found while expanding node @@"), EntryPoints.Num()).ToString(), this); } else if (UEdGraphPin* BetterSelfPin = EntryPoints[0]->GetAutoWorldContextPin()) { const FString& DefaultToSelfMetaValue = Function->GetMetaData(FBlueprintMetadata::MD_DefaultToSelf); const FString& WorldContextMetaValue = Function->GetMetaData(FBlueprintMetadata::MD_WorldContext); struct FStructConnectHelper { static void Connect(const FString& PinName, UK2Node* Node, UEdGraphPin* BetterSelf, const UEdGraphSchema_K2* InSchema, FCompilerResultsLog& MessageLog) { UEdGraphPin* Pin = Node->FindPin(PinName); if (!PinName.IsEmpty() && Pin && !Pin->LinkedTo.Num()) { const bool bConnected = InSchema->TryCreateConnection(Pin, BetterSelf); if (!bConnected) { MessageLog.Warning(*LOCTEXT("DefaultToSelfNotConnected", "DefaultToSelf pin @@ from node @@ cannot be connected to @@").ToString(), Pin, Node, BetterSelf); } } } }; FStructConnectHelper::Connect(DefaultToSelfMetaValue, this, BetterSelfPin, Schema, CompilerContext.MessageLog); if (!Function->HasMetaData(FBlueprintMetadata::MD_CallableWithoutWorldContext)) { FStructConnectHelper::Connect(WorldContextMetaValue, this, BetterSelfPin, Schema, CompilerContext.MessageLog); } } } // If we have an enum param that is expanded, we handle that first if(bWantsEnumToExecExpansion) { if(Function) { TArray EnumNamesToCheck; GetExpandEnumPinNames(Function, EnumNamesToCheck); bool bAlreadyHandleInput = false; UEdGraphPin* OutMainExecutePin = nullptr; UK2Node_ExecutionSequence* SpawnedSequenceNode = nullptr; int32 OutSequenceIndex = 0; const auto LinkIntoOutputChain = [&OutMainExecutePin, &SpawnedSequenceNode, &OutSequenceIndex, &CompilerContext, this, SourceGraph, Schema](UK2Node* Node) { if (!OutMainExecutePin) { // Create normal exec output -- only once though. OutMainExecutePin = CreatePin(EGPD_Output, UEdGraphSchema_K2::PC_Exec, UEdGraphSchema_K2::PN_Then); } else { // set up a sequence so we can call one after another. if (!SpawnedSequenceNode) { SpawnedSequenceNode = CompilerContext.SpawnIntermediateNode(this, SourceGraph); SpawnedSequenceNode->AllocateDefaultPins(); CompilerContext.MovePinLinksToIntermediate(*OutMainExecutePin, *SpawnedSequenceNode->GetThenPinGivenIndex(OutSequenceIndex++)); Schema->TryCreateConnection(OutMainExecutePin, SpawnedSequenceNode->Pins[0]); } } // Hook up execution to the branch node if (!SpawnedSequenceNode) { Schema->TryCreateConnection(OutMainExecutePin, Node->GetExecPin()); } else { UEdGraphPin* SequenceOutput = SpawnedSequenceNode->GetThenPinGivenIndex(OutSequenceIndex); if (!SequenceOutput) { SpawnedSequenceNode->AddInputPin(); SequenceOutput = SpawnedSequenceNode->GetThenPinGivenIndex(OutSequenceIndex); } Schema->TryCreateConnection(SequenceOutput, Node->GetExecPin()); OutSequenceIndex++; } }; for (const FName& EnumParamName : EnumNamesToCheck) { UEnum* Enum = nullptr; if (FByteProperty* ByteProp = FindFProperty(Function, EnumParamName)) { Enum = ByteProp->Enum; } else if (FEnumProperty* EnumProp = FindFProperty(Function, EnumParamName)) { Enum = EnumProp->GetEnum(); } UEdGraphPin* EnumParamPin = FindPin(EnumParamName); if (Enum && EnumParamPin) { // Expanded as input execs pins if (EnumParamPin->Direction == EGPD_Input) { if (bAlreadyHandleInput) { CompilerContext.MessageLog.Error(TEXT("@@ Already provided an input enum parameter for ExpandEnumAsExecs. Only one is permitted."), this); return; } bAlreadyHandleInput = true; // Create normal exec input UEdGraphPin* ExecutePin = CreatePin(EGPD_Input, UEdGraphSchema_K2::PC_Exec, UEdGraphSchema_K2::PN_Execute); // Create temp enum variable UK2Node_TemporaryVariable* TempEnumVarNode = CompilerContext.SpawnIntermediateNode(this, SourceGraph); TempEnumVarNode->VariableType.PinCategory = UEdGraphSchema_K2::PC_Byte; TempEnumVarNode->VariableType.PinSubCategoryObject = Enum; TempEnumVarNode->AllocateDefaultPins(); // Get the output pin UEdGraphPin* TempEnumVarOutput = TempEnumVarNode->GetVariablePin(); // Connect temp enum variable to (hidden) enum pin Schema->TryCreateConnection(TempEnumVarOutput, EnumParamPin); // Now we want to iterate over other exec inputs... for (int32 PinIdx = Pins.Num() - 1; PinIdx >= 0; PinIdx--) { UEdGraphPin* Pin = Pins[PinIdx]; if (Pin != NULL && Pin != ExecutePin && Pin->Direction == EGPD_Input && Pin->PinType.PinCategory == UEdGraphSchema_K2::PC_Exec) { // Create node to set the temp enum var UK2Node_AssignmentStatement* AssignNode = CompilerContext.SpawnIntermediateNode(this, SourceGraph); AssignNode->AllocateDefaultPins(); // Move connections from fake 'enum exec' pint to this assignment node CompilerContext.MovePinLinksToIntermediate(*Pin, *AssignNode->GetExecPin()); // Connect this to out temp enum var Schema->TryCreateConnection(AssignNode->GetVariablePin(), TempEnumVarOutput); // Connect exec output to 'real' exec pin Schema->TryCreateConnection(AssignNode->GetThenPin(), ExecutePin); // set the literal enum value to set to AssignNode->GetValuePin()->DefaultValue = Pin->PinName.ToString(); // Finally remove this 'cosmetic' exec pin Pins[PinIdx]->MarkPendingKill(); Pins.RemoveAt(PinIdx); } } } // Expanded as output execs pins else if (EnumParamPin->Direction == EGPD_Output) { // Create a SwitchEnum node to switch on the output enum UK2Node_SwitchEnum* SwitchEnumNode = CompilerContext.SpawnIntermediateNode(this, SourceGraph); UEnum* EnumObject = Cast(EnumParamPin->PinType.PinSubCategoryObject.Get()); SwitchEnumNode->SetEnum(EnumObject); SwitchEnumNode->AllocateDefaultPins(); LinkIntoOutputChain(SwitchEnumNode); // Connect (hidden) enum pin to switch node's selection pin Schema->TryCreateConnection(EnumParamPin, SwitchEnumNode->GetSelectionPin()); // Now we want to iterate over other exec outputs corresponding to the enum. // the first pins created are the ExpandEnumAsExecs pins, and they're all made at the same time. for (int32 PinIdx = Enum->NumEnums() - 2; PinIdx >= 0; PinIdx--) { UEdGraphPin* Pin = Pins[PinIdx]; if (Pin && Pin != OutMainExecutePin && Pin->Direction == EGPD_Output && Pin->PinType.PinCategory == UEdGraphSchema_K2::PC_Exec) { if (UEdGraphPin* FoundPin = SwitchEnumNode->FindPin(Pin->PinName)) { if (!FoundPin->LinkedTo.Contains(Pin)) { // Move connections from fake 'enum exec' pin to this switch node CompilerContext.MovePinLinksToIntermediate(*Pin, *FoundPin); // Finally remove this 'cosmetic' exec pin Pins[PinIdx]->MarkPendingKill(); Pins.RemoveAt(PinIdx); } } // Have passed the relevant entries... no more work to do here. else { break; } } } } } else if(EnumParamPin && !EnumParamPin->PinType.IsContainer() && EnumParamPin->PinType.PinCategory == UEdGraphSchema_K2::PC_Boolean) { if (EnumParamPin->Direction == EGPD_Input) { // Create normal exec input UEdGraphPin* ExecutePin = CreatePin(EGPD_Input, UEdGraphSchema_K2::PC_Exec, UEdGraphSchema_K2::PN_Execute); // Create temp bool variable UK2Node_TemporaryVariable* TempBoolVarNode = CompilerContext.SpawnIntermediateNode(this, SourceGraph); TempBoolVarNode->VariableType.PinCategory = UEdGraphSchema_K2::PC_Boolean; TempBoolVarNode->AllocateDefaultPins(); // Get the output pin UEdGraphPin* TempBoolVarOutput = TempBoolVarNode->GetVariablePin(); // Connect temp enum variable to (hidden) bool pin Schema->TryCreateConnection(TempBoolVarOutput, EnumParamPin); // create a true entry and a false: const auto CreateAssignNode = [Schema, &CompilerContext, this, SourceGraph, TempBoolVarOutput, ExecutePin](UEdGraphPin* FakePin, const TCHAR* DefaultValue) { UK2Node_AssignmentStatement* AssignNode = CompilerContext.SpawnIntermediateNode(this, SourceGraph); AssignNode->AllocateDefaultPins(); // Move connections from fake 'enum exec' pint to this assignment node CompilerContext.MovePinLinksToIntermediate(*FakePin, *AssignNode->GetExecPin()); // Connect this to out temp enum var Schema->TryCreateConnection(AssignNode->GetVariablePin(), TempBoolVarOutput); // Connect exec output to 'real' exec pin Schema->TryCreateConnection(AssignNode->GetThenPin(), ExecutePin); // set the literal enum value to set to AssignNode->GetValuePin()->DefaultValue = DefaultValue; }; UEdGraphPin* TruePin = FindPinChecked(TEXT("True"), EEdGraphPinDirection::EGPD_Input); UEdGraphPin* FalsePin = FindPinChecked(TEXT("False"), EEdGraphPinDirection::EGPD_Input); CreateAssignNode(TruePin, TEXT("True")); CreateAssignNode(FalsePin, TEXT("False")); // remove fake false/true nodes: RemovePin(TruePin); RemovePin(FalsePin); } else if (EnumParamPin->Direction == EGPD_Output) { // Create a Branch node to switch on the output bool: UK2Node_IfThenElse* IfElseNode = CompilerContext.SpawnIntermediateNode(this, SourceGraph); IfElseNode->AllocateDefaultPins(); LinkIntoOutputChain(IfElseNode); // Connect (hidden) bool pin to branch node Schema->TryCreateConnection(EnumParamPin, IfElseNode->GetConditionPin()); UEdGraphPin* TruePin = FindPinChecked(TEXT("True"), EEdGraphPinDirection::EGPD_Output); UEdGraphPin* FalsePin = FindPinChecked(TEXT("False"), EEdGraphPinDirection::EGPD_Output); // move true connection to branch node: CompilerContext.MovePinLinksToIntermediate(*TruePin, *IfElseNode->GetThenPin()); // move false connection to branch node: CompilerContext.MovePinLinksToIntermediate(*FalsePin, *IfElseNode->GetElsePin()); // remove fake false/true nodes: RemovePin(TruePin); RemovePin(FalsePin); } } } } } // AUTO CREATED REFS { if ( Function ) { TArray AutoCreateRefTermPinNames; const bool bHasAutoCreateRefTerms = Function->HasMetaData(FBlueprintMetadata::MD_AutoCreateRefTerm); if ( bHasAutoCreateRefTerms ) { CompilerContext.GetSchema()->GetAutoEmitTermParameters(Function, AutoCreateRefTermPinNames); } for (UEdGraphPin* Pin : Pins) { const bool bIsRefInputParam = Pin && Pin->PinType.bIsReference && (Pin->Direction == EGPD_Input) && !CompilerContext.GetSchema()->IsMetaPin(*Pin); if (!bIsRefInputParam) { continue; } const bool bHasConnections = Pin->LinkedTo.Num() > 0; const bool bCreateDefaultValRefTerm = bHasAutoCreateRefTerms && !bHasConnections && AutoCreateRefTermPinNames.Contains(Pin->PinName.ToString()); if (bCreateDefaultValRefTerm) { const bool bHasDefaultValue = !Pin->DefaultValue.IsEmpty() || Pin->DefaultObject || !Pin->DefaultTextValue.IsEmpty(); // copy defaults as default values can be reset when the pin is connected const FString DefaultValue = Pin->DefaultValue; UObject* DefaultObject = Pin->DefaultObject; const FText DefaultTextValue = Pin->DefaultTextValue; bool bMatchesDefaults = Pin->DoesDefaultValueMatchAutogenerated(); UEdGraphPin* ValuePin = InnerHandleAutoCreateRef(this, Pin, CompilerContext, SourceGraph, bHasDefaultValue); if ( ValuePin ) { if (bMatchesDefaults) { // Use the latest code to set default value Schema->SetPinAutogeneratedDefaultValueBasedOnType(ValuePin); } else { ValuePin->DefaultValue = DefaultValue; ValuePin->DefaultObject = DefaultObject; ValuePin->DefaultTextValue = DefaultTextValue; } } } // since EX_Self does not produce an addressable (referenceable) FProperty, we need to shim // in a "auto-ref" term in its place (this emulates how UHT generates a local value for // native functions; hence the IsNative() check) else if (bHasConnections && Pin->LinkedTo[0]->PinType.PinSubCategory == UEdGraphSchema_K2::PSC_Self && Pin->PinType.bIsConst && !Function->IsNative()) { InnerHandleAutoCreateRef(this, Pin, CompilerContext, SourceGraph, /*bForceAssignment =*/true); } } } } // Then we go through and expand out array iteration if necessary const bool bAllowMultipleSelfs = AllowMultipleSelfs(true); UEdGraphPin* MultiSelf = Schema->FindSelfPin(*this, EEdGraphPinDirection::EGPD_Input); if(bAllowMultipleSelfs && MultiSelf && !MultiSelf->PinType.IsArray()) { const bool bProperInputToExpandForEach = (1 == MultiSelf->LinkedTo.Num()) && (nullptr != MultiSelf->LinkedTo[0]) && (MultiSelf->LinkedTo[0]->PinType.IsArray()); if(bProperInputToExpandForEach) { CallForEachElementInArrayExpansion(this, MultiSelf, CompilerContext, SourceGraph); } } } UEdGraphPin* UK2Node_CallFunction::InnerHandleAutoCreateRef(UK2Node* Node, UEdGraphPin* Pin, FKismetCompilerContext& CompilerContext, UEdGraph* SourceGraph, bool bForceAssignment) { const bool bAddAssigment = !Pin->PinType.IsContainer() && bForceAssignment; // ADD LOCAL VARIABLE UK2Node_TemporaryVariable* LocalVariable = CompilerContext.SpawnIntermediateNode(Node, SourceGraph); LocalVariable->VariableType = Pin->PinType; LocalVariable->VariableType.bIsReference = false; LocalVariable->AllocateDefaultPins(); if (!bAddAssigment) { if (!CompilerContext.GetSchema()->TryCreateConnection(LocalVariable->GetVariablePin(), Pin)) { CompilerContext.MessageLog.Error(*LOCTEXT("AutoCreateRefTermPin_NotConnected", "AutoCreateRefTerm Expansion: Pin @@ cannot be connected to @@").ToString(), LocalVariable->GetVariablePin(), Pin); return nullptr; } } // ADD ASSIGMENT else { // TODO connect to dest.. UK2Node_PureAssignmentStatement* AssignDefaultValue = CompilerContext.SpawnIntermediateNode(Node, SourceGraph); AssignDefaultValue->AllocateDefaultPins(); const bool bVariableConnected = CompilerContext.GetSchema()->TryCreateConnection(AssignDefaultValue->GetVariablePin(), LocalVariable->GetVariablePin()); UEdGraphPin* AssignInputPit = AssignDefaultValue->GetValuePin(); const bool bPreviousInputSaved = AssignInputPit && CompilerContext.MovePinLinksToIntermediate(*Pin, *AssignInputPit).CanSafeConnect(); const bool bOutputConnected = CompilerContext.GetSchema()->TryCreateConnection(AssignDefaultValue->GetOutputPin(), Pin); if (!bVariableConnected || !bOutputConnected || !bPreviousInputSaved) { CompilerContext.MessageLog.Error(*LOCTEXT("AutoCreateRefTermPin_AssignmentError", "AutoCreateRefTerm Expansion: Assignment Error @@").ToString(), AssignDefaultValue); return nullptr; } CompilerContext.GetSchema()->SetPinAutogeneratedDefaultValueBasedOnType(AssignDefaultValue->GetValuePin()); return AssignInputPit; } return nullptr; } void UK2Node_CallFunction::CallForEachElementInArrayExpansion(UK2Node* Node, UEdGraphPin* MultiSelf, FKismetCompilerContext& CompilerContext, UEdGraph* SourceGraph) { const UEdGraphSchema_K2* Schema = CompilerContext.GetSchema(); check(Node && MultiSelf && SourceGraph && Schema); const bool bProperInputToExpandForEach = (1 == MultiSelf->LinkedTo.Num()) && (NULL != MultiSelf->LinkedTo[0]) && (MultiSelf->LinkedTo[0]->PinType.IsArray()); ensure(bProperInputToExpandForEach); UEdGraphPin* ThenPin = Node->FindPinChecked(UEdGraphSchema_K2::PN_Then); // Create int Iterator UK2Node_TemporaryVariable* IteratorVar = CompilerContext.SpawnIntermediateNode(Node, SourceGraph); IteratorVar->VariableType.PinCategory = UEdGraphSchema_K2::PC_Int; IteratorVar->AllocateDefaultPins(); // Initialize iterator UK2Node_AssignmentStatement* InteratorInitialize = CompilerContext.SpawnIntermediateNode(Node, SourceGraph); InteratorInitialize->AllocateDefaultPins(); InteratorInitialize->GetValuePin()->DefaultValue = TEXT("0"); Schema->TryCreateConnection(IteratorVar->GetVariablePin(), InteratorInitialize->GetVariablePin()); CompilerContext.MovePinLinksToIntermediate(*Node->GetExecPin(), *InteratorInitialize->GetExecPin()); // Do loop branch UK2Node_IfThenElse* Branch = CompilerContext.SpawnIntermediateNode(Node, SourceGraph); Branch->AllocateDefaultPins(); Schema->TryCreateConnection(InteratorInitialize->GetThenPin(), Branch->GetExecPin()); CompilerContext.MovePinLinksToIntermediate(*ThenPin, *Branch->GetElsePin()); // Do loop condition UK2Node_CallFunction* Condition = CompilerContext.SpawnIntermediateNode(Node, SourceGraph); Condition->SetFromFunction(UKismetMathLibrary::StaticClass()->FindFunctionByName(GET_FUNCTION_NAME_CHECKED(UKismetMathLibrary, Less_IntInt))); Condition->AllocateDefaultPins(); Schema->TryCreateConnection(Condition->GetReturnValuePin(), Branch->GetConditionPin()); Schema->TryCreateConnection(Condition->FindPinChecked(TEXT("A")), IteratorVar->GetVariablePin()); // Array size UK2Node_CallArrayFunction* ArrayLength = CompilerContext.SpawnIntermediateNode(Node, SourceGraph); ArrayLength->SetFromFunction(UKismetArrayLibrary::StaticClass()->FindFunctionByName(GET_FUNCTION_NAME_CHECKED(UKismetArrayLibrary, Array_Length))); ArrayLength->AllocateDefaultPins(); CompilerContext.CopyPinLinksToIntermediate(*MultiSelf, *ArrayLength->GetTargetArrayPin()); ArrayLength->PinConnectionListChanged(ArrayLength->GetTargetArrayPin()); Schema->TryCreateConnection(Condition->FindPinChecked(TEXT("B")), ArrayLength->GetReturnValuePin()); // Get Element UK2Node_CallArrayFunction* GetElement = CompilerContext.SpawnIntermediateNode(Node, SourceGraph); GetElement->SetFromFunction(UKismetArrayLibrary::StaticClass()->FindFunctionByName(GET_FUNCTION_NAME_CHECKED(UKismetArrayLibrary, Array_Get))); GetElement->AllocateDefaultPins(); CompilerContext.CopyPinLinksToIntermediate(*MultiSelf, *GetElement->GetTargetArrayPin()); GetElement->PinConnectionListChanged(GetElement->GetTargetArrayPin()); Schema->TryCreateConnection(GetElement->FindPinChecked(TEXT("Index")), IteratorVar->GetVariablePin()); // Iterator increment UK2Node_CallFunction* Increment = CompilerContext.SpawnIntermediateNode(Node, SourceGraph); Increment->SetFromFunction(UKismetMathLibrary::StaticClass()->FindFunctionByName(GET_FUNCTION_NAME_CHECKED(UKismetMathLibrary, Add_IntInt))); Increment->AllocateDefaultPins(); Schema->TryCreateConnection(Increment->FindPinChecked(TEXT("A")), IteratorVar->GetVariablePin()); Increment->FindPinChecked(TEXT("B"))->DefaultValue = TEXT("1"); // Iterator assigned UK2Node_AssignmentStatement* IteratorAssign = CompilerContext.SpawnIntermediateNode(Node, SourceGraph); IteratorAssign->AllocateDefaultPins(); Schema->TryCreateConnection(IteratorAssign->GetVariablePin(), IteratorVar->GetVariablePin()); Schema->TryCreateConnection(IteratorAssign->GetValuePin(), Increment->GetReturnValuePin()); Schema->TryCreateConnection(IteratorAssign->GetThenPin(), Branch->GetExecPin()); // Connect pins from intermediate nodes back in to the original node Schema->TryCreateConnection(Branch->GetThenPin(), Node->GetExecPin()); Schema->TryCreateConnection(ThenPin, IteratorAssign->GetExecPin()); Schema->TryCreateConnection(GetElement->FindPinChecked(TEXT("Item")), MultiSelf); } FName UK2Node_CallFunction::GetCornerIcon() const { if (const UFunction* Function = GetTargetFunction()) { if (Function->HasAllFunctionFlags(FUNC_BlueprintAuthorityOnly)) { return TEXT("Graph.Replication.AuthorityOnly"); } else if (Function->HasAllFunctionFlags(FUNC_BlueprintCosmetic)) { return TEXT("Graph.Replication.ClientEvent"); } else if(Function->HasMetaData(FBlueprintMetadata::MD_Latent)) { return TEXT("Graph.Latent.LatentIcon"); } } return Super::GetCornerIcon(); } FSlateIcon UK2Node_CallFunction::GetIconAndTint(FLinearColor& OutColor) const { return GetPaletteIconForFunction(GetTargetFunction(), OutColor); } bool UK2Node_CallFunction::ReconnectPureExecPins(TArray& OldPins) { if (IsNodePure()) { // look for an old exec pin UEdGraphPin* PinExec = nullptr; for (int32 PinIdx = 0; PinIdx < OldPins.Num(); PinIdx++) { if (OldPins[PinIdx]->PinName == UEdGraphSchema_K2::PN_Execute) { PinExec = OldPins[PinIdx]; break; } } if (PinExec) { PinExec->SetSavePinIfOrphaned(false); // look for old then pin UEdGraphPin* PinThen = nullptr; for (int32 PinIdx = 0; PinIdx < OldPins.Num(); PinIdx++) { if (OldPins[PinIdx]->PinName == UEdGraphSchema_K2::PN_Then) { PinThen = OldPins[PinIdx]; break; } } if (PinThen) { PinThen->SetSavePinIfOrphaned(false); // reconnect all incoming links to old exec pin to the far end of the old then pin. if (PinThen->LinkedTo.Num() > 0) { UEdGraphPin* PinThenLinked = PinThen->LinkedTo[0]; while (PinExec->LinkedTo.Num() > 0) { UEdGraphPin* PinExecLinked = PinExec->LinkedTo[0]; PinExecLinked->BreakLinkTo(PinExec); PinExecLinked->MakeLinkTo(PinThenLinked); } return true; } } } } return false; } void UK2Node_CallFunction::InvalidatePinTooltips() { bPinTooltipsValid = false; } void UK2Node_CallFunction::ConformContainerPins() { // helper functions for type propagation: const auto TryReadTypeToPropagate = [](UEdGraphPin* Pin, bool& bOutPropagated, FEdGraphTerminalType& TypeToPropagete) { if (Pin && !bOutPropagated) { if (Pin->HasAnyConnections() || !Pin->DoesDefaultValueMatchAutogenerated() ) { bOutPropagated = true; if (Pin->LinkedTo.Num() != 0) { TypeToPropagete = Pin->LinkedTo[0]->GetPrimaryTerminalType(); } else { TypeToPropagete = Pin->GetPrimaryTerminalType(); } } } }; const auto TryReadValueTypeToPropagate = [](UEdGraphPin* Pin, bool& bOutPropagated, FEdGraphTerminalType& TypeToPropagete) { if (Pin && !bOutPropagated) { if (Pin->LinkedTo.Num() != 0 || !Pin->DoesDefaultValueMatchAutogenerated()) { bOutPropagated = true; if (Pin->LinkedTo.Num() != 0) { TypeToPropagete = Pin->LinkedTo[0]->PinType.PinValueType; } else { TypeToPropagete = Pin->PinType.PinValueType; } } } }; const UEdGraphSchema_K2* Schema = CastChecked(GetSchema()); const auto TryPropagateType = [Schema](UEdGraphPin* Pin, const FEdGraphTerminalType& TerminalType, bool bTypeIsAvailable) { if(Pin) { if(bTypeIsAvailable) { const FEdGraphTerminalType PrimaryType = Pin->GetPrimaryTerminalType(); if( PrimaryType.TerminalCategory != TerminalType.TerminalCategory || PrimaryType.TerminalSubCategory != TerminalType.TerminalSubCategory || PrimaryType.TerminalSubCategoryObject != TerminalType.TerminalSubCategoryObject) { // terminal type changed: if (Pin->SubPins.Num() > 0 && Pin->PinType.PinCategory != UEdGraphSchema_K2::PC_Wildcard) { Schema->RecombinePin(Pin->SubPins[0]); } Pin->PinType.PinCategory = TerminalType.TerminalCategory; Pin->PinType.PinSubCategory = TerminalType.TerminalSubCategory; Pin->PinType.PinSubCategoryObject = TerminalType.TerminalSubCategoryObject; // Also propagate the CPF_UObjectWrapper flag, which will be set for "wrapped" object ptr types (e.g. TSubclassOf). Pin->PinType.bIsUObjectWrapper = TerminalType.bTerminalIsUObjectWrapper; // Reset default values if (!Schema->IsPinDefaultValid(Pin, Pin->DefaultValue, Pin->DefaultObject, Pin->DefaultTextValue).IsEmpty()) { Schema->ResetPinToAutogeneratedDefaultValue(Pin, false); } } } else { // reset to wildcard: if (Pin->SubPins.Num() > 0) { Schema->RecombinePin(Pin->SubPins[0]); } Pin->PinType.PinCategory = UEdGraphSchema_K2::PC_Wildcard; Pin->PinType.PinSubCategory = NAME_None; Pin->PinType.PinSubCategoryObject = nullptr; Pin->PinType.bIsUObjectWrapper = false; Schema->ResetPinToAutogeneratedDefaultValue(Pin, false); } } }; const auto TryPropagateValueType = [](UEdGraphPin* Pin, const FEdGraphTerminalType& TerminalType, bool bTypeIsAvailable) { if (Pin) { if (bTypeIsAvailable) { Pin->PinType.PinValueType.TerminalCategory = TerminalType.TerminalCategory; Pin->PinType.PinValueType.TerminalSubCategory = TerminalType.TerminalSubCategory; Pin->PinType.PinValueType.TerminalSubCategoryObject = TerminalType.TerminalSubCategoryObject; } else { Pin->PinType.PinValueType.TerminalCategory = UEdGraphSchema_K2::PC_Wildcard; Pin->PinType.PinValueType.TerminalSubCategory = NAME_None; Pin->PinType.PinValueType.TerminalSubCategoryObject = nullptr; } } }; const UFunction* TargetFunction = GetTargetFunction(); if (TargetFunction == nullptr) { return; } // find any pins marked as SetParam const FString& SetPinMetaData = TargetFunction->GetMetaData(FBlueprintMetadata::MD_SetParam); // useless copies/allocates in this code, could be an optimization target... TArray SetParamPinGroups; { SetPinMetaData.ParseIntoArray(SetParamPinGroups, TEXT(","), true); } for (FString& Entry : SetParamPinGroups) { // split the group: TArray GroupEntries; Entry.ParseIntoArray(GroupEntries, TEXT("|"), true); // resolve pins TArray ResolvedPins; for(UEdGraphPin* Pin : Pins) { if (GroupEntries.Contains(Pin->GetName())) { ResolvedPins.Add(Pin); } } // if nothing is connected (or non-default), reset to wildcard // else, find the first type and propagate to everyone else:: bool bReadyToPropagatSetType = false; FEdGraphTerminalType TypeToPropagate; for (UEdGraphPin* Pin : ResolvedPins) { TryReadTypeToPropagate(Pin, bReadyToPropagatSetType, TypeToPropagate); if(bReadyToPropagatSetType) { break; } } for (UEdGraphPin* Pin : ResolvedPins) { TryPropagateType( Pin, TypeToPropagate, bReadyToPropagatSetType ); } } const FString& MapPinMetaData = TargetFunction->GetMetaData(FBlueprintMetadata::MD_MapParam); const FString& MapKeyPinMetaData = TargetFunction->GetMetaData(FBlueprintMetadata::MD_MapKeyParam); const FString& MapValuePinMetaData = TargetFunction->GetMetaData(FBlueprintMetadata::MD_MapValueParam); if(!MapPinMetaData.IsEmpty() || !MapKeyPinMetaData.IsEmpty() || !MapValuePinMetaData.IsEmpty() ) { // if the map pin has a connection infer from that, otherwise use the information on the key param and value param: bool bReadyToPropagateKeyType = false; FEdGraphTerminalType KeyTypeToPropagate; bool bReadyToPropagateValueType = false; FEdGraphTerminalType ValueTypeToPropagate; UEdGraphPin* MapPin = MapPinMetaData.IsEmpty() ? nullptr : FindPin(MapPinMetaData); UEdGraphPin* MapKeyPin = MapKeyPinMetaData.IsEmpty() ? nullptr : FindPin(MapKeyPinMetaData); UEdGraphPin* MapValuePin = MapValuePinMetaData.IsEmpty() ? nullptr : FindPin(MapValuePinMetaData); TryReadTypeToPropagate(MapPin, bReadyToPropagateKeyType, KeyTypeToPropagate); TryReadValueTypeToPropagate(MapPin, bReadyToPropagateValueType, ValueTypeToPropagate); TryReadTypeToPropagate(MapKeyPin, bReadyToPropagateKeyType, KeyTypeToPropagate); TryReadTypeToPropagate(MapValuePin, bReadyToPropagateValueType, ValueTypeToPropagate); TryPropagateType(MapPin, KeyTypeToPropagate, bReadyToPropagateKeyType); TryPropagateType(MapKeyPin, KeyTypeToPropagate, bReadyToPropagateKeyType); TryPropagateValueType(MapPin, ValueTypeToPropagate, bReadyToPropagateValueType); TryPropagateType(MapValuePin, ValueTypeToPropagate, bReadyToPropagateValueType); } } FText UK2Node_CallFunction::GetToolTipHeading() const { FText Heading = Super::GetToolTipHeading(); struct FHeadingBuilder { FHeadingBuilder(FText InitialHeading) : ConstructedHeading(InitialHeading) {} void Append(FText HeadingAddOn) { if (ConstructedHeading.IsEmpty()) { ConstructedHeading = HeadingAddOn; } else { ConstructedHeading = FText::Format(FText::FromString("{0}\n{1}"), HeadingAddOn, ConstructedHeading); } } FText ConstructedHeading; }; FHeadingBuilder HeadingBuilder(Super::GetToolTipHeading()); if (const UFunction* Function = GetTargetFunction()) { if (Function->HasAllFunctionFlags(FUNC_BlueprintAuthorityOnly)) { HeadingBuilder.Append(LOCTEXT("ServerOnlyFunc", "Server Only")); } if (Function->HasAllFunctionFlags(FUNC_BlueprintCosmetic)) { HeadingBuilder.Append(LOCTEXT("ClientOnlyFunc", "Client Only")); } if(Function->HasMetaData(FBlueprintMetadata::MD_Latent)) { HeadingBuilder.Append(LOCTEXT("LatentFunc", "Latent")); } } return HeadingBuilder.ConstructedHeading; } void UK2Node_CallFunction::GetNodeAttributes( TArray>& OutNodeAttributes ) const { UFunction* TargetFunction = GetTargetFunction(); const FString TargetFunctionName = TargetFunction ? TargetFunction->GetName() : TEXT( "InvalidFunction" ); OutNodeAttributes.Add( TKeyValuePair( TEXT( "Type" ), TEXT( "Function" ) )); OutNodeAttributes.Add( TKeyValuePair( TEXT( "Class" ), GetClass()->GetName() )); OutNodeAttributes.Add( TKeyValuePair( TEXT( "Name" ), TargetFunctionName )); } FText UK2Node_CallFunction::GetMenuCategory() const { UFunction* TargetFunction = GetTargetFunction(); if (TargetFunction != nullptr) { return GetDefaultCategoryForFunction(TargetFunction, FText::GetEmpty()); } return FText::GetEmpty(); } bool UK2Node_CallFunction::HasExternalDependencies(TArray* OptionalOutput) const { UFunction* Function = GetTargetFunction(); const UClass* SourceClass = Function ? Function->GetOwnerClass() : nullptr; const UBlueprint* SourceBlueprint = GetBlueprint(); bool bResult = (SourceClass != nullptr) && (SourceClass->ClassGeneratedBy != SourceBlueprint); if (bResult && OptionalOutput) { OptionalOutput->AddUnique(Function); } // All structures, that are required for the BP compilation, should be gathered for (UEdGraphPin* Pin : Pins) { UStruct* DepStruct = Pin ? Cast(Pin->PinType.PinSubCategoryObject.Get()) : nullptr; UClass* DepClass = Cast(DepStruct); if (DepClass && (DepClass->ClassGeneratedBy == SourceBlueprint)) { //Don't include self continue; } if (DepStruct && !DepStruct->IsNative()) { if (OptionalOutput) { OptionalOutput->AddUnique(DepStruct); } bResult = true; } } const bool bSuperResult = Super::HasExternalDependencies(OptionalOutput); return bSuperResult || bResult; } UEdGraph* UK2Node_CallFunction::GetFunctionGraph(const UEdGraphNode*& OutGraphNode) const { OutGraphNode = nullptr; // Search for the Blueprint owner of the function graph, climbing up through the Blueprint hierarchy UClass* MemberParentClass = FunctionReference.GetMemberParentClass(GetBlueprintClassFromNode()); if(MemberParentClass != nullptr) { UBlueprintGeneratedClass* ParentClass = Cast(MemberParentClass); if(ParentClass != nullptr && ParentClass->ClassGeneratedBy != nullptr) { UBlueprint* Blueprint = Cast(ParentClass->ClassGeneratedBy); while(Blueprint != nullptr) { UEdGraph* TargetGraph = nullptr; const FName FunctionName = FunctionReference.GetMemberName(); for (UEdGraph* const Graph : Blueprint->FunctionGraphs) { if (Graph->GetFName() == FunctionName) { TargetGraph = Graph; break; } } if (!TargetGraph) { for (const FBPInterfaceDescription& Interface : Blueprint->ImplementedInterfaces) { for (UEdGraph* const Graph : Interface.Graphs) { if (Graph->GetFName() == FunctionName) { TargetGraph = Graph; break; } } if (TargetGraph) { break; } } } if((TargetGraph != nullptr) && !TargetGraph->HasAnyFlags(RF_Transient)) { // Found the function graph in a Blueprint, return that graph return TargetGraph; } else { // Did not find the function call as a graph, it may be a custom event UK2Node_CustomEvent* CustomEventNode = nullptr; TArray CustomEventNodes; FBlueprintEditorUtils::GetAllNodesOfClass(Blueprint, CustomEventNodes); for (UK2Node_CustomEvent* const CustomEvent : CustomEventNodes) { if(CustomEvent->CustomFunctionName == FunctionReference.GetMemberName()) { OutGraphNode = CustomEvent; return CustomEvent->GetGraph(); } } } ParentClass = Cast(Blueprint->ParentClass); Blueprint = ParentClass != nullptr ? Cast(ParentClass->ClassGeneratedBy) : nullptr; } } } return nullptr; } bool UK2Node_CallFunction::IsStructureWildcardProperty(const UFunction* Function, const FName PropertyName) { if (Function && !PropertyName.IsNone()) { TArray Names; FCustomStructureParamHelper::FillCustomStructureParameterNames(Function, Names); if (Names.Contains(PropertyName.ToString())) { return true; } } return false; } bool UK2Node_CallFunction::IsWildcardProperty(const UFunction* InFunction, const FProperty* InProperty) { if (InProperty) { return FEdGraphUtilities::IsSetParam(InFunction, InProperty->GetFName()) || FEdGraphUtilities::IsMapParam(InFunction, InProperty->GetFName()); } return false; } void UK2Node_CallFunction::AddSearchMetaDataInfo(TArray& OutTaggedMetaData) const { Super::AddSearchMetaDataInfo(OutTaggedMetaData); if (UFunction* TargetFunction = GetTargetFunction()) { OutTaggedMetaData.Add(FSearchTagDataPair(FFindInBlueprintSearchTags::FiB_NativeName, FText::FromString(TargetFunction->GetName()))); } } TSharedPtr UK2Node_CallFunction::CreateNodeImage() const { // For set, map and array functions we have a cool icon. This helps users quickly // identify container types: if (UFunction* TargetFunction = GetTargetFunction()) { UEdGraphPin* NodeImagePin = FEdGraphUtilities::FindArrayParamPin(TargetFunction, this); NodeImagePin = NodeImagePin ? NodeImagePin : FEdGraphUtilities::FindSetParamPin(TargetFunction, this); NodeImagePin = NodeImagePin ? NodeImagePin : FEdGraphUtilities::FindMapParamPin(TargetFunction, this); if(NodeImagePin) { // Find the first array param pin and bind that to our array image: return SPinTypeSelector::ConstructPinTypeImage(NodeImagePin); } } return TSharedPtr(); } UObject* UK2Node_CallFunction::GetJumpTargetForDoubleClick() const { // If there is an event node, jump to it, otherwise jump to the function graph const UEdGraphNode* ResultEventNode = nullptr; UEdGraph* FunctionGraph = GetFunctionGraph(/*out*/ ResultEventNode); if (ResultEventNode != nullptr) { return const_cast(ResultEventNode); } else { return FunctionGraph; } } bool UK2Node_CallFunction::CanJumpToDefinition() const { const UFunction* TargetFunction = GetTargetFunction(); const bool bNativeFunction = (TargetFunction != nullptr) && (TargetFunction->IsNative()); return bNativeFunction || (GetJumpTargetForDoubleClick() != nullptr); } void UK2Node_CallFunction::JumpToDefinition() const { // For native functions, try going to the function definition in C++ if available if (UFunction* TargetFunction = GetTargetFunction()) { if (TargetFunction->IsNative()) { // First try the nice way that will get to the right line number bool bSucceeded = false; const bool bNavigateToNativeFunctions = GetDefault()->bNavigateToNativeFunctionsFromCallNodes; if(bNavigateToNativeFunctions) { if(FSourceCodeNavigation::CanNavigateToFunction(TargetFunction)) { bSucceeded = FSourceCodeNavigation::NavigateToFunction(TargetFunction); } // Failing that, fall back to the older method which will still get the file open assuming it exists if (!bSucceeded) { FString NativeParentClassHeaderPath; const bool bFileFound = FSourceCodeNavigation::FindClassHeaderPath(TargetFunction, NativeParentClassHeaderPath) && (IFileManager::Get().FileSize(*NativeParentClassHeaderPath) != INDEX_NONE); if (bFileFound) { const FString AbsNativeParentClassHeaderPath = FPaths::ConvertRelativePathToFull(NativeParentClassHeaderPath); bSucceeded = FSourceCodeNavigation::OpenSourceFile(AbsNativeParentClassHeaderPath); } } } else { // Inform user that the function is native, give them opportunity to enable navigation to native // functions: FNotificationInfo Info(LOCTEXT("NavigateToNativeDisabled", "Navigation to Native (c++) Functions Disabled")); Info.ExpireDuration = 10.0f; Info.CheckBoxState = bNavigateToNativeFunctions ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; Info.CheckBoxStateChanged = FOnCheckStateChanged::CreateStatic( [](ECheckBoxState NewState) { const FScopedTransaction Transaction(LOCTEXT("ChangeNavigateToNativeFunctionsFromCallNodes", "Change Navigate to Native Functions from Call Nodes Setting")); UBlueprintEditorSettings* MutableEditorSetings = GetMutableDefault(); MutableEditorSetings->Modify(); MutableEditorSetings->bNavigateToNativeFunctionsFromCallNodes = (NewState == ECheckBoxState::Checked) ? true : false; MutableEditorSetings->SaveConfig(); } ); Info.CheckBoxText = LOCTEXT("EnableNavigationToNative", "Navigate to Native Functions from Blueprint Call Nodes?"); FSlateNotificationManager::Get().AddNotification(Info); } return; } } // Otherwise, fall back to the inherited behavior which should go to the function entry node Super::JumpToDefinition(); } FString UK2Node_CallFunction::GetPinMetaData(FName InPinName, FName InKey) { FString MetaData = Super::GetPinMetaData(InPinName, InKey); // If there's no metadata directly on the pin then check for metadata on the function if (MetaData.IsEmpty()) { if (UFunction* Function = GetTargetFunction()) { // Find the corresponding property for the pin and search that first if (FProperty* Property = Function->FindPropertyByName(InPinName)) { MetaData = Property->GetMetaData(InKey); } // Also look for metadata like DefaultToSelf on the function itself if (MetaData.IsEmpty()) { MetaData = Function->GetMetaData(InKey); if (MetaData != InPinName.ToString()) { // Only return if the value matches the pin name as we don't want general function metadata MetaData.Empty(); } } } } return MetaData; } bool UK2Node_CallFunction::IsConnectionDisallowed(const UEdGraphPin* MyPin, const UEdGraphPin* OtherPin, FString& OutReason) const { bool bIsDisallowed = Super::IsConnectionDisallowed(MyPin, OtherPin, OutReason); if (!bIsDisallowed && MyPin != nullptr) { if (MyPin->bNotConnectable) { bIsDisallowed = true; OutReason = LOCTEXT("PinConnectionDisallowed", "This parameter is for internal use only.").ToString(); } else if (UFunction* TargetFunction = GetTargetFunction()) { const bool bIsObjectType = (MyPin->PinType.PinCategory == UEdGraphSchema_K2::PC_Object || MyPin->PinType.PinCategory == UEdGraphSchema_K2::PC_SoftObject) && (OtherPin->PinType.PinCategory == UEdGraphSchema_K2::PC_Object || OtherPin->PinType.PinCategory == UEdGraphSchema_K2::PC_SoftObject); if (// Strictly speaking this first check is not needed, but by not disabling the connection here we get a better reason later: ( OtherPin->PinType.IsContainer() // make sure we don't allow connections of mismatched container types (e.g. maps to arrays) && (OtherPin->PinType.ContainerType != MyPin->PinType.ContainerType) && ( (FEdGraphUtilities::IsSetParam(TargetFunction, MyPin->PinName) && !MyPin->PinType.IsSet()) || (FEdGraphUtilities::IsMapParam(TargetFunction, MyPin->PinName) && !MyPin->PinType.IsMap()) || (FEdGraphUtilities::IsArrayDependentParam(TargetFunction, MyPin->PinName) && !MyPin->PinType.IsArray()) ) ) ) { bIsDisallowed = true; OutReason = LOCTEXT("PinSetConnectionDisallowed", "Containers of containers are not supported - consider wrapping a container in a Structure object").ToString(); } // Do not allow exec pins to be connected to a wildcard if this is a container function else if(MyPin->PinType.PinCategory == UEdGraphSchema_K2::PC_Wildcard && OtherPin->PinType.PinCategory == UEdGraphSchema_K2::PC_Exec) { bIsDisallowed = true; OutReason = LOCTEXT("PinExecConnectionDisallowed", "Cannot create a container of Exec pins.").ToString(); } else if (bIsObjectType && MyPin->Direction == EGPD_Input && MyPin->PinType.IsContainer() && OtherPin->PinType.IsContainer()) { // Check that we can actually connect the dependent pins to this new array const UEdGraphSchema_K2* K2Schema = Cast(GetSchema()); // Gather all pins that would be dependent on on the container type TArray DependentPins; { for (UEdGraphPin* Pin : Pins) { if (Pin->Direction == EGPD_Input && Pin != MyPin && FEdGraphUtilities::IsDynamicContainerParam(TargetFunction, Pin->PinName)) { DependentPins.Add(Pin); } } } for (UEdGraphPin* Pin : DependentPins) { // If the pins are both containers, then ArePinTypesCompatible will fail incorrectly. if (OtherPin->PinType.ContainerType != Pin->PinType.ContainerType) { continue; } UClass* Context = nullptr; UBlueprint* Blueprint = GetBlueprint(); if (Blueprint) { Context = Blueprint->GeneratedClass; } const bool ConnectResponse = K2Schema->ArePinTypesCompatible(Pin->PinType, OtherPin->PinType, Context, /* bIgnoreArray = */ true); if (!ConnectResponse) { // Display the necessary tooltip on the pin hover, and log it if we are compiling FFormatNamedArguments MessageArgs; MessageArgs.Add(TEXT("PinAType"), UEdGraphSchema_K2::TypeToText(Pin->PinType)); MessageArgs.Add(TEXT("PinBType"), UEdGraphSchema_K2::TypeToText(OtherPin->PinType)); UBlueprint* BP = GetBlueprint(); UEdGraph* OwningGraph = GetGraph(); OutReason = FText::Format(LOCTEXT("DefaultPinIncompatibilityMessage", "{PinAType} is not compatible with {PinBType}."), MessageArgs).ToString(); return true; } } } } } return bIsDisallowed; } #undef LOCTEXT_NAMESPACE