// Copyright Epic Games, Inc. All Rights Reserved. #include "K2Node_CustomEvent.h" #include "BlueprintActionDatabaseRegistrar.h" #include "BlueprintEventNodeSpawner.h" #include "BlueprintNodeSpawner.h" #include "Containers/EnumAsByte.h" #include "Delegates/Delegate.h" #include "EdGraph/EdGraph.h" #include "EdGraph/EdGraphPin.h" #include "EdGraphSchema_K2.h" #include "Engine/Blueprint.h" #include "Engine/BlueprintGeneratedClass.h" #include "Engine/MemberReference.h" #include "FindInBlueprintManager.h" #include "FindInBlueprints.h" #include "HAL/PlatformCrt.h" #include "Internationalization/Internationalization.h" #include "K2Node.h" #include "K2Node_BaseMCDelegate.h" #include "Kismet2/BlueprintEditorUtils.h" #include "Kismet2/CompilerResultsLog.h" #include "Kismet2/Kismet2NameValidators.h" #include "Misc/AssertionMacros.h" #include "Serialization/Archive.h" #include "Settings/EditorStyleSettings.h" #include "Styling/AppStyle.h" #include "Templates/Casts.h" #include "Templates/SubclassOf.h" #include "UObject/Class.h" #include "UObject/NameTypes.h" #include "UObject/Object.h" #include "UObject/ObjectPtr.h" #include "UObject/Script.h" #include "UObject/UE5MainStreamObjectVersion.h" #include "UObject/UnrealNames.h" #include "UObject/UnrealType.h" struct FLinearColor; #define LOCTEXT_NAMESPACE "K2Node_CustomEvent" /** * Attempts to find a CustomEvent node associated with the specified function. * * @param CustomEventFunc The function you want to find an associated node for. * @return A pointer to the found node (NULL if a corresponding node wasn't found) */ static const UK2Node_CustomEvent* FindCustomEventNodeFromFunction(UFunction* CustomEventFunc) { const UK2Node_CustomEvent* FoundEventNode = nullptr; if (CustomEventFunc != nullptr) { const UObject* const FuncOwner = CustomEventFunc->GetOuter(); check(FuncOwner != nullptr); // if the found function is a NOT a native function (it's user generated) if (FuncOwner->IsA(UBlueprintGeneratedClass::StaticClass())) { const UBlueprintGeneratedClass* FuncClass = Cast(CustomEventFunc->GetOuter()); check(FuncClass != nullptr); const UBlueprint* FuncBlueprint = Cast(FuncClass->ClassGeneratedBy); check(FuncBlueprint != nullptr); TArray BpCustomEvents; FBlueprintEditorUtils::GetAllNodesOfClass(FuncBlueprint, BpCustomEvents); // look to see if the function that this is overriding is a custom-event for (const UK2Node_CustomEvent* const UserEvent : BpCustomEvents) { check(UserEvent); if (UserEvent->CustomFunctionName == CustomEventFunc->GetFName()) { FoundEventNode = UserEvent; break; } } } } return FoundEventNode; } /** * Custom handler for validating CustomEvent renames */ class FCustomEventNameValidator : public FKismetNameValidator { public: FCustomEventNameValidator(UK2Node_CustomEvent const* CustomEventIn) : FKismetNameValidator(CustomEventIn->GetBlueprint(), CustomEventIn->CustomFunctionName) , CustomEvent(CustomEventIn) { check(CustomEvent != nullptr); } // Begin INameValidatorInterface virtual EValidatorResult IsValid(FString const& Name, bool bOriginal = false) override { UBlueprint* Blueprint = CustomEvent->GetBlueprint(); check(Blueprint != nullptr); EValidatorResult NameValidity = FKismetNameValidator::IsValid(Name, bOriginal); if ((NameValidity == EValidatorResult::Ok) || (NameValidity == EValidatorResult::ExistingName)) { UFunction* ParentFunction = FindUField(Blueprint->ParentClass, *Name); // if this custom-event is overriding a function belonging to the blueprint's parent if (ParentFunction != nullptr) { UK2Node_CustomEvent const* OverriddenEvent = FindCustomEventNodeFromFunction(ParentFunction); // if the function that we're overriding isn't another custom event, // then we can't name it this (only allow custom-event to override other custom-events) if (OverriddenEvent == nullptr) { NameValidity = EValidatorResult::AlreadyInUse; } } } else if (NameValidity == EValidatorResult::AlreadyInUse) { auto Predicate_EventGraphs = [Name](const TObjectPtr& InEventGraph) -> bool { return InEventGraph && InEventGraph->HasAnyFlags(RF_Transient) && InEventGraph->GetName() == Name; }; // Allow a transient event subgraph (compiler artifact) that matches the existing name // to pass if there are no other event nodes that would use this name at compile time. // This type of collision is a false positive that won't result in a conflict, because // the custom event node won't enter the Blueprint's namespace until the next compile, // and the compiler will regenerate the transient event subgraphs array on a full pass. if (Blueprint->EventGraphs.FindByPredicate(Predicate_EventGraphs)) { TArray AllEventNodes; FBlueprintEditorUtils::GetAllNodesOfClass(Blueprint, AllEventNodes); UK2Node_Event** MatchingNodePtr = AllEventNodes.FindByPredicate([Name](UK2Node_Event* InEventNode) { if (InEventNode->bOverrideFunction) { return InEventNode->EventReference.GetMemberName().ToString() == Name; } else if (InEventNode->CustomFunctionName != NAME_None) { return InEventNode->CustomFunctionName.ToString() == Name; } return false; }); if (!MatchingNodePtr || *MatchingNodePtr == CustomEvent) { NameValidity = EValidatorResult::Ok; } } } return NameValidity; } // End INameValidatorInterface private: UK2Node_CustomEvent const* CustomEvent; }; UK2Node_CustomEvent::UK2Node_CustomEvent(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer) { bOverrideFunction = false; bIsEditable = true; bCanRenameNode = true; bIsDeprecated = false; bCallInEditor = false; FunctionFlags = (FUNC_BlueprintCallable | FUNC_BlueprintEvent | FUNC_Public); } void UK2Node_CustomEvent::Serialize(FArchive& Ar) { Ar.UsingCustomVersion(FUE5MainStreamObjectVersion::GUID); Super::Serialize(Ar); if (Ar.IsLoading()) { CachedNodeTitle.MarkDirty(); if (Ar.CustomVer(FUE5MainStreamObjectVersion::GUID) < FUE5MainStreamObjectVersion::AccessSpecifiersForCustomEvents) { FunctionFlags |= (FUNC_BlueprintCallable | FUNC_BlueprintEvent | FUNC_Public); } } } FText UK2Node_CustomEvent::GetNodeTitle(ENodeTitleType::Type TitleType) const { if (TitleType != ENodeTitleType::FullTitle) { return FText::FromName(CustomFunctionName); } else if (CachedNodeTitle.IsOutOfDate(this)) { FText RPCString = UK2Node_Event::GetLocalizedNetString(FunctionFlags, false); FFormatNamedArguments Args; Args.Add(TEXT("FunctionName"), FText::FromName(CustomFunctionName)); Args.Add(TEXT("RPCString"), RPCString); // FText::Format() is slow, so we cache this to save on performance CachedNodeTitle.SetCachedText(FText::Format(NSLOCTEXT("K2Node", "CustomEvent_Name", "{FunctionName}{RPCString}\nCustom Event"), Args), this); } return CachedNodeTitle; } bool UK2Node_CustomEvent::CanCreateUserDefinedPin(const FEdGraphPinType& InPinType, EEdGraphPinDirection InDesiredDirection, FText& OutErrorMessage) { if (!IsEditable()) { return false; } // Make sure that if this is an exec node we are allowed one. const UEdGraphSchema_K2* Schema = GetDefault(); if(InDesiredDirection == EGPD_Input) { OutErrorMessage = NSLOCTEXT("K2Node", "AddInputPinError", "Cannot add input pins to custom event node!"); return false; } else if (InPinType.PinCategory == UEdGraphSchema_K2::PC_Exec && !CanModifyExecutionWires()) { OutErrorMessage = LOCTEXT("MultipleExecPinError", "Cannot support more exec pins!"); return false; } else { TArray> TypeTree; Schema->GetVariableTypeTree(TypeTree, ETypeTreeFilter::RootTypesOnly); bool bIsValid = false; for (TSharedPtr& TypeInfo : TypeTree) { FEdGraphPinType CurrentType = TypeInfo->GetPinType(false); // only concerned with the list of categories if (CurrentType.PinCategory == InPinType.PinCategory) { bIsValid = true; break; } } if (!bIsValid) { OutErrorMessage = LOCTEXT("AddInputPinError", "Cannot add pins of this type to custom event node!"); return false; } } return true; } UEdGraphPin* UK2Node_CustomEvent::CreatePinFromUserDefinition(const TSharedPtr NewPinInfo) { UEdGraphPin* NewPin = CreatePin(EGPD_Output, NewPinInfo->PinType, NewPinInfo->PinName); const UEdGraphSchema_K2* K2Schema = GetDefault(); K2Schema->SetPinAutogeneratedDefaultValue(NewPin, NewPinInfo->PinDefaultValue); return NewPin; } bool UK2Node_CustomEvent::ModifyUserDefinedPinDefaultValue(TSharedPtr PinInfo, const FString& NewDefaultValue) { if (Super::ModifyUserDefinedPinDefaultValue(PinInfo, NewDefaultValue)) { const UEdGraphSchema_K2* K2Schema = GetDefault(); K2Schema->HandleParameterDefaultValueChanged(this); return true; } return false; } void UK2Node_CustomEvent::RenameCustomEventCloseToName(int32 StartIndex) { bool bFoundName = false; const FString& BaseName = CustomFunctionName.ToString(); for (int32 NameIndex = StartIndex; !bFoundName; ++NameIndex) { const FString NewName = FString::Printf(TEXT("%s_%d"), *BaseName, NameIndex); if (Rename(*NewName, GetOuter(), REN_Test)) { UBlueprint* Blueprint = GetBlueprint(); CustomFunctionName = FName(NewName.GetCharArray().GetData()); Rename(*NewName, GetOuter(), REN_DontCreateRedirectors); bFoundName = true; } } } void UK2Node_CustomEvent::OnRenameNode(const FString& NewName) { CustomFunctionName = *NewName; CachedNodeTitle.MarkDirty(); FBlueprintEditorUtils::MarkBlueprintAsStructurallyModified(GetBlueprint()); } TSharedPtr UK2Node_CustomEvent::MakeNameValidator() const { return MakeShareable(new FCustomEventNameValidator(this)); } bool UK2Node_CustomEvent::IsOverride() const { UBlueprint* Blueprint = GetBlueprint(); check(Blueprint != NULL); UFunction* ParentFunction = FindUField(Blueprint->ParentClass, CustomFunctionName); UK2Node_CustomEvent const* OverriddenEvent = FindCustomEventNodeFromFunction(ParentFunction); return (OverriddenEvent != NULL); } uint32 UK2Node_CustomEvent::GetNetFlags() const { uint32 NetFlags = (FunctionFlags & FUNC_NetFuncFlags); if (IsOverride()) { UBlueprint* Blueprint = GetBlueprint(); check(Blueprint != NULL); UFunction* ParentFunction = FindUField(Blueprint->ParentClass, CustomFunctionName); check(ParentFunction != NULL); // inherited net flags take precedence NetFlags = (ParentFunction->FunctionFlags & FUNC_NetFuncFlags); } // Sanitize NetFlags, only allow replication flags that can be supported by the online system // This mirrors logic in ProcessFunctionSpecifiers in HeaderParser.cpp. Basically if we want to // replicate a function we need to know whether we're replicating on the client or the server. if (!(NetFlags & FUNC_Net)) { NetFlags = 0; } return NetFlags; } void UK2Node_CustomEvent::ValidateNodeDuringCompilation(class FCompilerResultsLog& MessageLog) const { Super::ValidateNodeDuringCompilation(MessageLog); UBlueprint* Blueprint = GetBlueprint(); check(Blueprint != NULL); UFunction* ParentFunction = FindUField(Blueprint->ParentClass, CustomFunctionName); // if this custom-event is overriding a function belonging to the blueprint's parent if (ParentFunction != NULL) { UObject const* const FuncOwner = ParentFunction->GetOuter(); check(FuncOwner != NULL); // if this custom-event is attempting to override a native function, we can't allow that if (!FuncOwner->IsA(UBlueprintGeneratedClass::StaticClass())) { MessageLog.Error(*FText::Format(LOCTEXT("NativeFunctionConflictFmt", "@@ name conflicts with a native '{0}' function"), FText::FromString(FuncOwner->GetName())).ToString(), this); } else { UK2Node_CustomEvent const* OverriddenEvent = FindCustomEventNodeFromFunction(ParentFunction); // if the function that this is attempting to override is NOT another // custom-event, then we want to error (a custom-event shouldn't override something different) if (OverriddenEvent == NULL) { MessageLog.Error(*FText::Format(LOCTEXT("NonCustomEventOverride", "@@ name conflicts with a '{0}' function"), FText::FromString(FuncOwner->GetName())).ToString(), this); } // else, we assume the user was attempting to override the parent's custom-event // the signatures could still be off, but FKismetCompilerContext::PrecompileFunction() should catch that } } } void UK2Node_CustomEvent::GetMenuActions(FBlueprintActionDatabaseRegistrar& ActionRegistrar) const { // actions get registered under specific object-keys; the idea is that // actions might have to be updated (or deleted) if their object-key is // mutated (or removed)... here we use the node's class (so if the node // type disappears, then the action should go with it) UClass* ActionKey = GetClass(); // to keep from needlessly instantiating a UBlueprintNodeSpawner, first // check to make sure that the registrar is looking for actions of this type // (could be regenerating actions for a specific asset, and therefore the // registrar would only accept actions corresponding to that asset) if (ActionRegistrar.IsOpenForRegistration(ActionKey)) { UBlueprintNodeSpawner* NodeSpawner = UBlueprintEventNodeSpawner::Create(GetClass(), FName()); check(NodeSpawner != nullptr); auto SetupCustomEventNodeLambda = [](UEdGraphNode* NewNode, bool bIsTemplateNode) { UK2Node_CustomEvent* EventNode = CastChecked(NewNode); UBlueprint* Blueprint = EventNode->GetBlueprint(); // in GetNodeTitle(), we use an empty CustomFunctionName to identify a menu entry if (!bIsTemplateNode) { EventNode->CustomFunctionName = FBlueprintEditorUtils::FindUniqueCustomEventName(Blueprint); } EventNode->bIsEditable = true; }; NodeSpawner->CustomizeNodeDelegate = UBlueprintNodeSpawner::FCustomizeNodeDelegate::CreateStatic(SetupCustomEventNodeLambda); ActionRegistrar.AddBlueprintAction(ActionKey, NodeSpawner); } } void UK2Node_CustomEvent::FixupPinStringDataReferences(FArchive* SavingArchive) { Super::FixupPinStringDataReferences(SavingArchive); if (SavingArchive) { UpdateUserDefinedPinDefaultValues(); } } void UK2Node_CustomEvent::ReconstructNode() { CachedNodeTitle.MarkDirty(); const UEdGraphSchema_K2* K2Schema = GetDefault(); const UEdGraphPin* DelegateOutPin = FindPin(DelegateOutputName); const UEdGraphPin* LinkedPin = ( DelegateOutPin && DelegateOutPin->LinkedTo.Num() && DelegateOutPin->LinkedTo[0] ) ? FBlueprintEditorUtils::FindFirstCompilerRelevantLinkedPin(DelegateOutPin->LinkedTo[0]) : nullptr; const UFunction* DelegateSignature = nullptr; if ( LinkedPin ) { if ( const UK2Node_BaseMCDelegate* OtherNode = Cast(LinkedPin->GetOwningNode()) ) { DelegateSignature = OtherNode->GetDelegateSignature(); } else if ( LinkedPin->PinType.PinCategory == UEdGraphSchema_K2::PC_Delegate ) { DelegateSignature = FMemberReference::ResolveSimpleMemberReference(LinkedPin->PinType.PinSubCategoryMemberReference); } } const bool bUseDelegateSignature = (nullptr == FindEventSignatureFunction()) && DelegateSignature; if (bUseDelegateSignature) { SetDelegateSignature(DelegateSignature); } Super::ReconstructNode(); } void UK2Node_CustomEvent::SetDelegateSignature(const UFunction* DelegateSignature) { if (DelegateSignature == nullptr) { return; } const UEdGraphSchema_K2* K2Schema = GetDefault(); TArray < TSharedPtr > OldPins = UserDefinedPins; UserDefinedPins.Empty(); for (TFieldIterator PropIt(DelegateSignature); PropIt && (PropIt->PropertyFlags & CPF_Parm); ++PropIt) { const FProperty* Param = *PropIt; if (!Param->HasAnyPropertyFlags(CPF_OutParm) || Param->HasAnyPropertyFlags(CPF_ReferenceParm)) { FEdGraphPinType PinType; K2Schema->ConvertPropertyToPinType(Param, /*out*/ PinType); FName NewPinName = Param->GetFName(); int32 Index = 1; while ((DelegateOutputName == NewPinName) || (UEdGraphSchema_K2::PN_Then == NewPinName)) { ++Index; NewPinName = *FString::Printf(TEXT("%s%d"), *NewPinName.ToString(), Index); } TSharedPtr NewPinInfo = MakeShareable(new FUserPinInfo()); NewPinInfo->PinName = NewPinName; NewPinInfo->PinType = PinType; NewPinInfo->DesiredPinDirection = EGPD_Output; int32 NewIndex = UserDefinedPins.Num(); // Copy over old default value if type matches if (OldPins.IsValidIndex(NewIndex) && OldPins[NewIndex].IsValid()) { TSharedPtr OldPinInfo = OldPins[NewIndex]; if (NewPinInfo->PinName == OldPinInfo->PinName && NewPinInfo->PinType == OldPinInfo->PinType && NewPinInfo->DesiredPinDirection == OldPinInfo->DesiredPinDirection) { NewPinInfo->PinDefaultValue = OldPinInfo->PinDefaultValue; } } UserDefinedPins.Add(NewPinInfo); } } } UK2Node_CustomEvent* UK2Node_CustomEvent::CreateFromFunction(FVector2D GraphPosition, UEdGraph* ParentGraph, const FString& Name, const UFunction* Function, bool bSelectNewNode/* = true*/) { UK2Node_CustomEvent* CustomEventNode = NULL; if(ParentGraph && Function) { CustomEventNode = NewObject(ParentGraph); CustomEventNode->CustomFunctionName = FName(*Name); CustomEventNode->SetFlags(RF_Transactional); ParentGraph->Modify(); ParentGraph->AddNode(CustomEventNode, true, bSelectNewNode); CustomEventNode->CreateNewGuid(); CustomEventNode->PostPlacedNewNode(); CustomEventNode->AllocateDefaultPins(); const UEdGraphSchema_K2* K2Schema = GetDefault(); for (TFieldIterator PropIt(Function); PropIt && (PropIt->PropertyFlags & CPF_Parm); ++PropIt) { const FProperty* Param = *PropIt; if (!Param->HasAnyPropertyFlags(CPF_OutParm) || Param->HasAnyPropertyFlags(CPF_ReferenceParm)) { FEdGraphPinType PinType; K2Schema->ConvertPropertyToPinType(Param, /*out*/ PinType); CustomEventNode->CreateUserDefinedPin(Param->GetFName(), PinType, EGPD_Output); } } CustomEventNode->NodePosX = static_cast(GraphPosition.X); CustomEventNode->NodePosY = static_cast(GraphPosition.Y); CustomEventNode->SnapToGrid(GetDefault()->GridSnapSize); } return CustomEventNode; } bool UK2Node_CustomEvent::IsEditable() const { const UEdGraphPin* DelegateOutPin = FindPin(DelegateOutputName); if(DelegateOutPin && DelegateOutPin->LinkedTo.Num()) { return false; } return Super::IsEditable(); } bool UK2Node_CustomEvent::IsUsedByAuthorityOnlyDelegate() const { if(const UEdGraphPin* DelegateOutPin = FindPin(DelegateOutputName)) { for(auto PinIter = DelegateOutPin->LinkedTo.CreateConstIterator(); PinIter; ++PinIter) { const UEdGraphPin* LinkedPin = *PinIter; const UK2Node_BaseMCDelegate* Node = LinkedPin ? Cast(LinkedPin->GetOwningNode()) : NULL; if(Node && Node->IsAuthorityOnly()) { return true; } } } return false; } FText UK2Node_CustomEvent::GetTooltipText() const { return LOCTEXT("AddCustomEvent_Tooltip", "An event with customizable name and parameters."); } FString UK2Node_CustomEvent::GetDocumentationLink() const { // Use the main k2 node doc return UK2Node::GetDocumentationLink(); } FString UK2Node_CustomEvent::GetDocumentationExcerptName() const { return TEXT("UK2Node_CustomEvent"); } FSlateIcon UK2Node_CustomEvent::GetIconAndTint(FLinearColor& OutColor) const { return FSlateIcon(FAppStyle::GetAppStyleSetName(), bCallInEditor ? "GraphEditor.CallInEditorEvent_16x" : "GraphEditor.CustomEvent_16x"); } void UK2Node_CustomEvent::AutowireNewNode(UEdGraphPin* FromPin) { Super::AutowireNewNode(FromPin); if (auto DelegateOutPin = FindPin(DelegateOutputName)) { if (DelegateOutPin->LinkedTo.Num()) { ReconstructNode(); } } } void UK2Node_CustomEvent::AddSearchMetaDataInfo(TArray& OutTaggedMetaData) const { Super::AddSearchMetaDataInfo(OutTaggedMetaData); bool bNeedsNameUpdate = true; bool bNeedsNativeNameUpdate = true; for (FSearchTagDataPair& SearchData : OutTaggedMetaData) { // Should always be the first item, but there is no guarantee if (bNeedsNameUpdate && SearchData.Key.CompareTo(FFindInBlueprintSearchTags::FiB_Name) == 0) { SearchData.Value = FText::FromString(FName::NameToDisplayString(CustomFunctionName.ToString(), false)); bNeedsNameUpdate = false; } else if (bNeedsNativeNameUpdate && SearchData.Key.CompareTo(FFindInBlueprintSearchTags::FiB_NativeName) == 0) { SearchData.Value = FText::FromName(CustomFunctionName); bNeedsNativeNameUpdate = false; } // If no more keys need updating, break if (!bNeedsNameUpdate && !bNeedsNativeNameUpdate) { break; } } } FText UK2Node_CustomEvent::GetKeywords() const { FText ParentKeywords = Super::GetKeywords(); FFormatNamedArguments Args; Args.Add(TEXT("ParentKeywords"), ParentKeywords); return FText::Format(LOCTEXT("CustomEventKeywords", "{ParentKeywords} Custom"), Args); } FEdGraphNodeDeprecationResponse UK2Node_CustomEvent::GetDeprecationResponse(EEdGraphNodeDeprecationType DeprecationType) const { FEdGraphNodeDeprecationResponse Response = Super::GetDeprecationResponse(DeprecationType); if (DeprecationType == EEdGraphNodeDeprecationType::NodeHasDeprecatedReference) { // Only warn on override usage. if (IsOverride()) { FText EventName = FText::FromName(GetFunctionName()); FText DetailedMessage = FText::FromString(DeprecationMessage); Response.MessageText = FBlueprintEditorUtils::GetDeprecatedMemberUsageNodeWarning(EventName, DetailedMessage); } else { // Allow the source event to be marked as deprecated in the class that defines it without warning, but use a note to visually indicate that the definition itself has been deprecated. Response.MessageType = EEdGraphNodeDeprecationMessageType::Note; Response.MessageText = LOCTEXT("DeprecatedCustomEventMessage", "@@: This custom event has been marked as deprecated. It can be safely deleted if all references have been replaced or removed."); } } return Response; } bool UK2Node_CustomEvent::HasExternalDependencies(TArray* OptionalOutput) const { bool bResult = false; // We use the dependencies of the linked node instead of the resulting function signature because a globally defined // delegate won't match a dependency check (it has no owner). const UEdGraphPin* DelegateOutPin = FindPin(DelegateOutputName); const UEdGraphPin* LinkedPin = (DelegateOutPin && DelegateOutPin->LinkedTo.Num() && DelegateOutPin->LinkedTo[0]) ? FBlueprintEditorUtils::FindFirstCompilerRelevantLinkedPin(DelegateOutPin->LinkedTo[0]) : nullptr; if (LinkedPin) { if (UK2Node* OtherNode = Cast(LinkedPin->GetOwningNode())) { bResult = OtherNode->HasExternalDependencies(OptionalOutput); } } bResult |= Super::HasExternalDependencies(OptionalOutput); return bResult; } #undef LOCTEXT_NAMESPACE