// Copyright Epic Games, Inc. All Rights Reserved. #include "K2Node_EnhancedInputAction.h" #include "AssetRegistry/AssetRegistryModule.h" #include "BlueprintActionDatabaseRegistrar.h" #include "BlueprintEditorSettings.h" #include "BlueprintNodeSpawner.h" #include "EdGraphSchema_K2_Actions.h" #include "Editor.h" #include "EditorCategoryUtils.h" #include "GraphEditorSettings.h" #include "InputAction.h" #include "K2Node_AssignmentStatement.h" #include "K2Node_EnhancedInputActionEvent.h" #include "K2Node_GetInputActionValue.h" #include "K2Node_TemporaryVariable.h" #include "Kismet2/BlueprintEditorUtils.h" #include "KismetCompiler.h" #include "Misc/PackageName.h" #include "Subsystems/AssetEditorSubsystem.h" #include "Modules/ModuleManager.h" #include "Styling/AppStyle.h" #include "EnhancedInputEditorSettings.h" #include "WidgetBlueprint.h" #include "Blueprint/UserWidget.h" #include "BlueprintNodeTemplateCache.h" #include UE_INLINE_GENERATED_CPP_BY_NAME(K2Node_EnhancedInputAction) #define LOCTEXT_NAMESPACE "K2Node_EnhancedInputAction" static const FName InputActionPinName = TEXT("InputAction"); static const FName ElapsedSecondsPinName = TEXT("ElapsedSeconds"); static const FName TriggeredSecondsPinName = TEXT("TriggeredSeconds"); static const FName ActionValuePinName = TEXT("ActionValue"); namespace UE::Input { static bool bShouldWarnOnUnsupportedInputPin = false; static FAutoConsoleVariableRef CVarShouldWarnOnUnsupportedInputPin( TEXT("enhancedInput.bp.bShouldWarnOnUnsupportedInputPin"), bShouldWarnOnUnsupportedInputPin, TEXT("Should the Enhanced Input event node throw a warning if a \"Unsuported\" pin has a connection?"), ECVF_Default); } UK2Node_EnhancedInputAction::UK2Node_EnhancedInputAction(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer) { } void ForEachEventPinName(TFunctionRef PinLambda) { UEnum* EventEnum = StaticEnum(); for (int32 i = 0; i < EventEnum->NumEnums() - 1; ++i) { if (!EventEnum->HasMetaData(TEXT("Hidden"), i)) { if (!PinLambda(ETriggerEvent(EventEnum->GetValueByIndex(i)), *EventEnum->GetNameStringByIndex(i))) { break; } } } } UInputActionEventNodeSpawner* UInputActionEventNodeSpawner::Create(TSubclassOf const NodeClass, TObjectPtr InAction) { check(NodeClass != nullptr); check(NodeClass->IsChildOf()); check(InAction != nullptr); UInputActionEventNodeSpawner* NodeSpawner = NewObject(GetTransientPackage()); NodeSpawner->NodeClass = NodeClass; NodeSpawner->WeakActionPtr = InAction; return NodeSpawner; } UEdGraphNode* UInputActionEventNodeSpawner::Invoke(UEdGraph* ParentGraph, FBindingSet const& Bindings, FVector2D const Location) const { check(ParentGraph != nullptr); UBlueprint* Blueprint = FBlueprintEditorUtils::FindBlueprintForGraphChecked(ParentGraph); if (!FBlueprintNodeTemplateCache::IsTemplateOuter(ParentGraph)) { // Look to see if a node for this input action already exists. If it does, just return that // which will jump the focus to it. if (UK2Node* PreExistingNode = FindExistingNode(Blueprint)) { return PreExistingNode; } } return Super::Invoke(ParentGraph, Bindings, Location); } UK2Node* UInputActionEventNodeSpawner::FindExistingNode(const UBlueprint* Blueprint) const { // We don't want references to node spawners to be keeping any input action assets from GC // if you unload a plugin for example, so we keep it as a weak pointer. TStrongObjectPtr ActionPtr = WeakActionPtr.Pin(); if (!ActionPtr) { return nullptr; } TArray AllInputActionNodes; FBlueprintEditorUtils::GetAllNodesOfClass(Blueprint, AllInputActionNodes); for (UK2Node_EnhancedInputAction* Node : AllInputActionNodes) { if (Node->InputAction == ActionPtr) { return Node; } } return nullptr; } void UK2Node_EnhancedInputAction::AllocateDefaultPins() { PreloadObject((UObject*)InputAction); const ETriggerEventsSupported SupportedTriggerEvents = GetDefault()->bEnableInputTriggerSupportWarnings && InputAction ? InputAction->GetSupportedTriggerEvents() : ETriggerEventsSupported::All; ForEachEventPinName([this, SupportedTriggerEvents](ETriggerEvent Event, FName PinName) { static const UEnum* EventEnum = StaticEnum(); UEdGraphPin* NewPin = CreatePin(EGPD_Output, UEdGraphSchema_K2::PC_Exec, PinName); // Mark all triggering exec pins as advanced view except for the triggered pin. Most of the time, triggered is what users should be using. // More advanced input set ups can use the more advanced pins when they want to! NewPin->bAdvancedView = !(GetDefault()->VisibleEventPinsByDefault & static_cast(Event)); NewPin->PinToolTip = EventEnum->GetToolTipTextByIndex(EventEnum->GetIndexByValue(static_cast(Event))).ToString(); // Add a special tooltip and display name for pins that are unsupported if (UE::Input::bShouldWarnOnUnsupportedInputPin && !UInputTrigger::IsSupportedTriggerEvent(SupportedTriggerEvents, Event)) { static const FText UnsuportedTooltip = LOCTEXT("UnsupportedTooltip", "\n\nThis trigger event is not supported by the action! Add a supported trigger to enable this pin."); NewPin->PinToolTip += UnsuportedTooltip.ToString(); NewPin->PinFriendlyName = FText::Format(LOCTEXT("UnsupportedPinFriendlyName", "(Unsupported) {0}"), FText::FromName(NewPin->GetFName())); } // Continue iterating return true; }); HideEventPins(nullptr); const UEdGraphSchema_K2* Schema = GetDefault(); AdvancedPinDisplay = ENodeAdvancedPins::Hidden; UEdGraphPin* ValuePin = CreatePin(EGPD_Output, UK2Node_GetInputActionValue::GetValueCategory(InputAction), UK2Node_GetInputActionValue::GetValueSubCategory(InputAction), UK2Node_GetInputActionValue::GetValueSubCategoryObject(InputAction), ActionValuePinName); Schema->SetPinAutogeneratedDefaultValueBasedOnType(ValuePin); CreatePin(EGPD_Output, UEdGraphSchema_K2::PC_Real, UEdGraphSchema_K2::PC_Double, ElapsedSecondsPinName)->bAdvancedView = true; CreatePin(EGPD_Output, UEdGraphSchema_K2::PC_Real, UEdGraphSchema_K2::PC_Double, TriggeredSecondsPinName)->bAdvancedView = true; if(InputAction) { UEdGraphPin* ActionPin = CreatePin(EGPD_Output, UEdGraphSchema_K2::PC_Object, InputAction->GetClass(), InputActionPinName); ActionPin->DefaultObject = const_cast(Cast(InputAction)); ActionPin->DefaultValue = InputAction->GetName(); ActionPin->bAdvancedView = true; Schema->ConstructBasicPinTooltip(*ActionPin, LOCTEXT("InputActionPinDescription", "The input action that caused this event to fire"), ActionPin->PinToolTip); } Super::AllocateDefaultPins(); } void UK2Node_EnhancedInputAction::HideEventPins(UEdGraphPin* RetainPin) { // Gather pins const ETriggerEventsSupported SupportedTriggerEvents = GetDefault()->bEnableInputTriggerSupportWarnings && InputAction ? InputAction->GetSupportedTriggerEvents() : ETriggerEventsSupported::All; // Hide any event pins that are not supported by this Action's triggers in the advanced view ForEachEventPinName([this, SupportedTriggerEvents](ETriggerEvent Event, FName PinName) { if (UEdGraphPin* Pin = FindPin(PinName)) { const bool bIsSupported = UInputTrigger::IsSupportedTriggerEvent(SupportedTriggerEvents, Event); Pin->bAdvancedView = !(GetDefault()->VisibleEventPinsByDefault & static_cast(Event)) || !bIsSupported; } // Continue iterating return true; }); } const ETriggerEvent UK2Node_EnhancedInputAction::GetTriggerTypeFromExecPin(const UEdGraphPin* ExecPin) const { static const UEnum* EventEnum = StaticEnum(); if(ensure(ExecPin && ExecPin->PinType.PinCategory == UEdGraphSchema_K2::PC_Exec)) { return (ETriggerEvent)(EventEnum->GetValueByName(ExecPin->PinName)); } return ETriggerEvent::None; } void UK2Node_EnhancedInputAction::PostReconstructNode() { Super::PostReconstructNode(); HideEventPins(nullptr); } void UK2Node_EnhancedInputAction::PinConnectionListChanged(UEdGraphPin* Pin) { Super::PinConnectionListChanged(Pin); HideEventPins(Pin); } bool UK2Node_EnhancedInputAction::IsConnectionDisallowed(const UEdGraphPin* MyPin, const UEdGraphPin* OtherPin, FString& OutReason) const { if(MyPin->PinType.PinCategory == UEdGraphSchema_K2::PC_Exec && InputAction) { const ETriggerEvent Event = GetTriggerTypeFromExecPin(MyPin); const ETriggerEventsSupported SupportedEvents = GetDefault()->bEnableInputTriggerSupportWarnings ? InputAction->GetSupportedTriggerEvents() : ETriggerEventsSupported::All; if(!UInputTrigger::IsSupportedTriggerEvent(SupportedEvents, Event)) { FFormatNamedArguments Args; Args.Add(TEXT("ActionName"), FText::FromName(InputAction->GetFName())); Args.Add(TEXT("PinName"), FText::FromName(MyPin->PinName)); OutReason = FText::Format(LOCTEXT("UnsupportedEventType_DragTooltip", "WARNING: '{ActionName}' does not support the '{PinName}' trigger event."), Args).ToString(); } } return Super::IsConnectionDisallowed(MyPin, OtherPin, OutReason); } FLinearColor UK2Node_EnhancedInputAction::GetNodeTitleColor() const { return GetDefault()->EventNodeTitleColor; } FName UK2Node_EnhancedInputAction::GetActionName() const { return InputAction ? InputAction->GetFName() : FName(); } FText UK2Node_EnhancedInputAction::GetNodeTitle(ENodeTitleType::Type TitleType) const { // TODO: Is Using InputAction->GetFName okay here? Full Asset path would be better for disambiguation. if (TitleType == ENodeTitleType::MenuTitle) { return FText::FromName(GetActionName()); } else if (CachedNodeTitle.IsOutOfDate(this)) { FFormatNamedArguments Args; Args.Add(TEXT("InputActionName"), FText::FromName(GetActionName())); FText LocFormat = LOCTEXT("EnhancedInputAction_Name", "EnhancedInputAction {InputActionName}"); // FText::Format() is slow, so we cache this to save on performance CachedNodeTitle.SetCachedText(FText::Format(LocFormat, Args), this); } return CachedNodeTitle; } FText UK2Node_EnhancedInputAction::GetTooltipText() const { if (CachedTooltip.IsOutOfDate(this)) { // FText::Format() is slow, so we cache this to save on performance FString ActionPath = InputAction ? InputAction->GetFullName() : TEXT(""); CachedTooltip.SetCachedText( FText::Format( LOCTEXT("EnhancedInputAction_Tooltip", "Event for when '{0}' triggers.\n\nNote: This is not guaranteed to fire every frame, only when the Action is triggered and the current Input Mode includes 'Game'.\n\n{1}\n\n{2}"), FText::FromString(ActionPath), LOCTEXT("EnhancedInputAction_Node_Tooltip_Tip", "Tip: Use the 'showdebug enhancedinput' command while playing to see debug information about Enhanced Input."), LOCTEXT("EnhancedInputAction_Node_SettingsTooltip", "You can change what execution pins are visible by default in the Enhanced Input Editor Preferences.")), this); } return CachedTooltip; } FSlateIcon UK2Node_EnhancedInputAction::GetIconAndTint(FLinearColor& OutColor) const { static FSlateIcon Icon(FAppStyle::GetAppStyleSetName(), "GraphEditor.Event_16x"); return Icon; } bool UK2Node_EnhancedInputAction::IsCompatibleWithGraph(UEdGraph const* Graph) const { // This node expands into event nodes and must be placed in a Ubergraph EGraphType const GraphType = Graph->GetSchema()->GetGraphType(Graph); bool bIsCompatible = (GraphType == EGraphType::GT_Ubergraph); if (bIsCompatible) { UBlueprint* Blueprint = FBlueprintEditorUtils::FindBlueprintForGraph(Graph); UEdGraphSchema_K2 const* K2Schema = Cast(Graph->GetSchema()); bool const bIsConstructionScript = (K2Schema != nullptr) ? UEdGraphSchema_K2::IsConstructionScript(Graph) : false; bIsCompatible = (Blueprint != nullptr) && Blueprint->SupportsInputEvents() && !bIsConstructionScript && Super::IsCompatibleWithGraph(Graph); } return bIsCompatible; } UObject* UK2Node_EnhancedInputAction::GetJumpTargetForDoubleClick() const { return const_cast(Cast(InputAction)); } void UK2Node_EnhancedInputAction::JumpToDefinition() const { GEditor->GetEditorSubsystem()->OpenEditorForAsset(GetJumpTargetForDoubleClick()); } void UK2Node_EnhancedInputAction::ValidateNodeDuringCompilation(class FCompilerResultsLog& MessageLog) const { Super::ValidateNodeDuringCompilation(MessageLog); if (!InputAction) { MessageLog.Error(*LOCTEXT("EnhancedInputAction_ErrorFmt", "EnhancedInputActionEvent references invalid 'null' action for @@").ToString(), this); return; } // There are no supported triggers on this action, we should put a note down // This would only be the case if the user has added a custom UInputTrigger that uses ETriggeredEventsSupported::None if(InputAction->GetSupportedTriggerEvents() == ETriggerEventsSupported::None) { MessageLog.Warning( *LOCTEXT("EnhancedInputAction_NoTriggersOnAction", "@@ may not be triggered. There are no triggers supported on this action! Add a trigger to this action to resolve this warning.").ToString(), this); } } void UK2Node_EnhancedInputAction::ExpandNode(FKismetCompilerContext& CompilerContext, UEdGraph* SourceGraph) { Super::ExpandNode(CompilerContext, SourceGraph); if(!InputAction) { static const FText InvalidActionWarning = LOCTEXT("InvalidInputActionDuringExpansion", "@@ does not have a valid Input Action asset!!"); CompilerContext.MessageLog.Warning(*InvalidActionWarning.ToString(), this); return; } // Establish active pins struct ActivePinData { ActivePinData(UEdGraphPin* InPin, ETriggerEvent InTriggerEvent) : Pin(InPin), TriggerEvent(InTriggerEvent) {} UEdGraphPin* Pin; ETriggerEvent TriggerEvent; }; const ETriggerEventsSupported SupportedTriggerEvents = GetDefault()->bEnableInputTriggerSupportWarnings ? InputAction->GetSupportedTriggerEvents() : ETriggerEventsSupported::All; TArray ActivePins; ForEachActiveEventPin([this, &ActivePins, &SupportedTriggerEvents, &CompilerContext](ETriggerEvent Event, UEdGraphPin& InputActionPin) { ActivePins.Add(ActivePinData(&InputActionPin, Event)); // Check if this exec pin is supported! if (UE::Input::bShouldWarnOnUnsupportedInputPin && !UInputTrigger::IsSupportedTriggerEvent(SupportedTriggerEvents, Event)) { CompilerContext.MessageLog.Warning(*FText::Format(LOCTEXT("UnsuportedEventTypeOnAction", "'{0}'on @@ may not be executed because it is not a supported trigger on this action!"), InputActionPin.GetDisplayName()).ToString(), this); } // Continue iterating return true; }); if (ActivePins.Num() == 0) { return; } // Bind all active pins to their action delegate const UEdGraphSchema_K2* Schema = CompilerContext.GetSchema(); auto CreateInputActionEvent = [this, &CompilerContext, &SourceGraph](UEdGraphPin* Pin, ETriggerEvent TriggerEvent) -> UK2Node_EnhancedInputActionEvent* { if (!InputAction) { return nullptr; } UK2Node_EnhancedInputActionEvent* InputActionEvent = CompilerContext.SpawnIntermediateNode(this, SourceGraph); InputActionEvent->CustomFunctionName = FName(*FString::Printf(TEXT("InpActEvt_%s_%s"), *GetActionName().ToString(), *InputActionEvent->GetName())); InputActionEvent->InputAction = InputAction; InputActionEvent->TriggerEvent = TriggerEvent; InputActionEvent->EventReference.SetExternalDelegateMember(FName(TEXT("EnhancedInputActionHandlerDynamicSignature__DelegateSignature"))); InputActionEvent->AllocateDefaultPins(); return InputActionEvent; }; // Widget blueprints require the bAutomaticallyRegisterInputOnConstruction to be set to true in order to receive callbacks if (GetBlueprint()->IsA()) { CompilerContext.AddPostCDOCompiledStep([](const UObject::FPostCDOCompiledContext& Context, UObject* NewCDO) { UUserWidget* Widget = CastChecked(NewCDO); Widget->bAutomaticallyRegisterInputOnConstruction = true; }); } // Create temporary variables to copy ActionValue and ElapsedSeconds into UK2Node_TemporaryVariable* ActionValueVar = CompilerContext.SpawnIntermediateNode(this, SourceGraph); ActionValueVar->VariableType.PinCategory = UK2Node_GetInputActionValue::GetValueCategory(InputAction); ActionValueVar->VariableType.PinSubCategory = UK2Node_GetInputActionValue::GetValueSubCategory(InputAction); ActionValueVar->VariableType.PinSubCategoryObject = UK2Node_GetInputActionValue::GetValueSubCategoryObject(InputAction); ActionValueVar->AllocateDefaultPins(); UK2Node_TemporaryVariable* ElapsedSecondsVar = CompilerContext.SpawnIntermediateNode(this, SourceGraph); ElapsedSecondsVar->VariableType.PinCategory = UEdGraphSchema_K2::PC_Real; ElapsedSecondsVar->VariableType.PinSubCategory = UEdGraphSchema_K2::PC_Double; ElapsedSecondsVar->AllocateDefaultPins(); UK2Node_TemporaryVariable* TriggeredSecondsVar = CompilerContext.SpawnIntermediateNode(this, SourceGraph); TriggeredSecondsVar->VariableType.PinCategory = UEdGraphSchema_K2::PC_Real; TriggeredSecondsVar->VariableType.PinSubCategory = UEdGraphSchema_K2::PC_Double; TriggeredSecondsVar->AllocateDefaultPins(); UK2Node_TemporaryVariable* InputActionVar = CompilerContext.SpawnIntermediateNode(this, SourceGraph); InputActionVar->VariableType.PinCategory = UEdGraphSchema_K2::PC_Object; InputActionVar->VariableType.PinSubCategoryObject = InputAction->GetClass(); InputActionVar->AllocateDefaultPins(); for (ActivePinData& PinData : ActivePins) { UEdGraphPin* EachPin = PinData.Pin; UK2Node_EnhancedInputActionEvent* InputActionEvent = CreateInputActionEvent(EachPin, PinData.TriggerEvent); if (!InputActionEvent) { continue; } // Create assignment nodes to assign the action value UK2Node_AssignmentStatement* ActionValueInitialize = CompilerContext.SpawnIntermediateNode(this, SourceGraph); ActionValueInitialize->AllocateDefaultPins(); Schema->TryCreateConnection(ActionValueVar->GetVariablePin(), ActionValueInitialize->GetVariablePin()); Schema->TryCreateConnection(ActionValueInitialize->GetValuePin(), InputActionEvent->FindPinChecked(ActionValuePinName)); // Connect the events to the assign location nodes Schema->TryCreateConnection(Schema->FindExecutionPin(*InputActionEvent, EGPD_Output), ActionValueInitialize->GetExecPin()); // Create assignment nodes to assign the elapsed timers and input action UK2Node_AssignmentStatement* ElapsedSecondsInitialize = CompilerContext.SpawnIntermediateNode(this, SourceGraph); ElapsedSecondsInitialize->AllocateDefaultPins(); Schema->TryCreateConnection(ElapsedSecondsVar->GetVariablePin(), ElapsedSecondsInitialize->GetVariablePin()); Schema->TryCreateConnection(ElapsedSecondsInitialize->GetValuePin(), InputActionEvent->FindPinChecked(TEXT("ElapsedTime"))); UK2Node_AssignmentStatement* TriggeredSecondsInitialize = CompilerContext.SpawnIntermediateNode(this, SourceGraph); TriggeredSecondsInitialize->AllocateDefaultPins(); Schema->TryCreateConnection(TriggeredSecondsVar->GetVariablePin(), TriggeredSecondsInitialize->GetVariablePin()); Schema->TryCreateConnection(TriggeredSecondsInitialize->GetValuePin(), InputActionEvent->FindPinChecked(TEXT("TriggeredTime"))); UK2Node_AssignmentStatement* InputActionInitialize = CompilerContext.SpawnIntermediateNode(this, SourceGraph); InputActionInitialize->AllocateDefaultPins(); Schema->TryCreateConnection(InputActionVar->GetVariablePin(), InputActionInitialize->GetVariablePin()); Schema->TryCreateConnection(InputActionInitialize->GetValuePin(), InputActionEvent->FindPinChecked(TEXT("SourceAction"))); // Connect the assign location to the assign elapsed time nodes Schema->TryCreateConnection(ActionValueInitialize->GetThenPin(), ElapsedSecondsInitialize->GetExecPin()); Schema->TryCreateConnection(ElapsedSecondsInitialize->GetThenPin(), TriggeredSecondsInitialize->GetExecPin()); Schema->TryCreateConnection(TriggeredSecondsInitialize->GetThenPin(), InputActionInitialize->GetExecPin()); // Move the original event connections to the then pin of the Input Action assign CompilerContext.MovePinLinksToIntermediate(*EachPin, *InputActionInitialize->GetThenPin()); // Move the original event variable connections to the intermediate nodes CompilerContext.MovePinLinksToIntermediate(*FindPin(ActionValuePinName), *ActionValueVar->GetVariablePin()); CompilerContext.MovePinLinksToIntermediate(*FindPin(ElapsedSecondsPinName), *ElapsedSecondsVar->GetVariablePin()); CompilerContext.MovePinLinksToIntermediate(*FindPin(TriggeredSecondsPinName), *TriggeredSecondsVar->GetVariablePin()); CompilerContext.MovePinLinksToIntermediate(*FindPin(InputActionPinName), *InputActionVar->GetVariablePin()); } } void UK2Node_EnhancedInputAction::GetMenuActions(FBlueprintActionDatabaseRegistrar& ActionRegistrar) const { auto CustomizeInputNodeLambda = [](UEdGraphNode* NewNode, bool bIsTemplateNode, TWeakObjectPtr Action) { UK2Node_EnhancedInputAction* InputNode = CastChecked(NewNode); InputNode->InputAction = Action.Get(); }; // Do a first time registration using the node's class to pull in all existing actions if (ActionRegistrar.IsOpenForRegistration(GetClass())) { IAssetRegistry& AssetRegistry = FModuleManager::LoadModuleChecked(TEXT("AssetRegistry")).Get(); static bool bRegisterOnce = true; if (bRegisterOnce) { bRegisterOnce = false; if (AssetRegistry.IsLoadingAssets()) { AssetRegistry.OnFilesLoaded().AddLambda([]() { FBlueprintActionDatabase::Get().RefreshClassActions(StaticClass()); }); } } TArray ActionAssets; AssetRegistry.GetAssetsByClass(UInputAction::StaticClass()->GetClassPathName(), ActionAssets, true); for (const FAssetData& ActionAsset : ActionAssets) { if (FPackageName::GetPackageMountPoint(ActionAsset.PackageName.ToString()) != NAME_None) { if (const UInputAction* Action = Cast(ActionAsset.GetAsset())) { UBlueprintNodeSpawner* NodeSpawner = UInputActionEventNodeSpawner::Create(GetClass(), Action); check(NodeSpawner != nullptr); NodeSpawner->CustomizeNodeDelegate = UBlueprintNodeSpawner::FCustomizeNodeDelegate::CreateStatic(CustomizeInputNodeLambda, TWeakObjectPtr(Action)); ActionRegistrar.AddBlueprintAction(Action, NodeSpawner); } } } } else if (const UInputAction* Action = Cast(ActionRegistrar.GetActionKeyFilter())) { // If this is a specific UInputAction asset update it. UBlueprintNodeSpawner* NodeSpawner = UBlueprintNodeSpawner::Create(GetClass()); check(NodeSpawner != nullptr); NodeSpawner->CustomizeNodeDelegate = UBlueprintNodeSpawner::FCustomizeNodeDelegate::CreateStatic(CustomizeInputNodeLambda, TWeakObjectPtr(Action)); ActionRegistrar.AddBlueprintAction(Action, NodeSpawner); } } FText UK2Node_EnhancedInputAction::GetMenuCategory() const { static FNodeTextCache CachedCategory; if (CachedCategory.IsOutOfDate(this)) { // FText::Format() is slow, so we cache this to save on performance CachedCategory.SetCachedText(FEditorCategoryUtils::BuildCategoryString(FCommonEditorCategory::Input, LOCTEXT("ActionMenuCategory", "Enhanced Action Events")), this); // TODO: Rename Action Events once old action system is removed } return CachedCategory; } FBlueprintNodeSignature UK2Node_EnhancedInputAction::GetSignature() const { FBlueprintNodeSignature NodeSignature = Super::GetSignature(); NodeSignature.AddKeyValue(GetActionName().ToString()); return NodeSignature; } TSharedPtr UK2Node_EnhancedInputAction::GetEventNodeAction(const FText& ActionCategory) { // TODO: Custom EdGraphSchemaAction required? TSharedPtr EventNodeAction = MakeShareable(new FEdGraphSchemaAction_K2InputAction(ActionCategory, GetNodeTitle(ENodeTitleType::EditableTitle), GetTooltipText(), 0)); EventNodeAction->NodeTemplate = this; return EventNodeAction; } bool UK2Node_EnhancedInputAction::HasAnyConnectedEventPins() const { bool bHasAnyConnectedEventPins = false; ForEachActiveEventPin([&bHasAnyConnectedEventPins](ETriggerEvent, UEdGraphPin& Pin) { // Stop iterating on the first active pin bHasAnyConnectedEventPins = true; return false; }); return bHasAnyConnectedEventPins; } void UK2Node_EnhancedInputAction::ForEachActiveEventPin(TFunctionRef Predicate) const { ForEachEventPinName([this, Predicate](ETriggerEvent Event, FName PinName) { UEdGraphPin* InputActionPin = FindPin(PinName, EEdGraphPinDirection::EGPD_Output); if (InputActionPin && InputActionPin->LinkedTo.Num() > 0) { return Predicate(Event, *InputActionPin); } return true; }); } #undef LOCTEXT_NAMESPACE