// ©2023 Batty Bovine Productions, LLC. All Rights Reserved. #include "K2Node_ComboAction.h" #include "AssetRegistry/AssetRegistryModule.h" #include "BlueprintActionDatabaseRegistrar.h" #include "BlueprintEditorSettings.h" #include "BlueprintNodeSpawner.h" #include "ComboInputAssets.h" #include "ComboInputTriggers.h" #include "EdGraphSchema_K2_Actions.h" #include "Editor.h" #include "EditorCategoryUtils.h" #include "GraphEditorSettings.h" #include "K2Node_AssignmentStatement.h" #include "K2Node_ComboActionEvent.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 UE_INLINE_GENERATED_CPP_BY_NAME(K2Node_ComboAction) #define LOCTEXT_NAMESPACE "K2Node_ComboAction" #define COMBO_ACTION_OBJECT_PIN_NAME TEXT("ComboAction") void ForEachEventPinName(TFunctionRef PinLambda) { UEnum *EventEnum = StaticEnum(); for (int32 i = 0; i < EventEnum->NumEnums() - 1; ++i) { if (!EventEnum->HasMetaData(TEXT("Hidden"), i)) { PinLambda(EComboActionTriggerEvent(EventEnum->GetValueByIndex(i)), *EventEnum->GetNameStringByIndex(i)); } } } void UK2Node_ComboAction::AllocateDefaultPins() { const UEdGraphSchema_K2 *Schema = GetDefault(); this->AdvancedPinDisplay = ENodeAdvancedPins::Hidden; ForEachEventPinName([this](EComboActionTriggerEvent Event, FName PinName) { static const UEnum *EventEnum = StaticEnum(); UEdGraphPin *NewPin = this->CreatePin(EGPD_Output, UEdGraphSchema_K2::PC_Exec, PinName); NewPin->PinToolTip = EventEnum->GetToolTipTextByIndex(EventEnum->GetIndexByValue(static_cast(Event))).ToString(); NewPin->bAdvancedView = (Event > EComboActionTriggerEvent::Activated); }); UEdGraphPin *ComboActionPin = this->CreatePin(EGPD_Output, UEdGraphSchema_K2::PC_Object, UComboAction::StaticClass(), COMBO_ACTION_OBJECT_PIN_NAME); ComboActionPin->bAdvancedView = true; Schema->SetPinAutogeneratedDefaultValueBasedOnType(ComboActionPin); if (this->ComboAction) { ComboActionPin->DefaultObject = const_cast(Cast(this->ComboAction)); ComboActionPin->DefaultValue = this->ComboAction->GetName(); Schema->ConstructBasicPinTooltip(*ComboActionPin, LOCTEXT("ComboActionPinDescription", "The combo action that caused this event to fire"), ComboActionPin->PinToolTip); } Super::AllocateDefaultPins(); } FLinearColor UK2Node_ComboAction::GetNodeTitleColor() const { return GetDefault()->EventNodeTitleColor; } FName UK2Node_ComboAction::GetActionName() const { return this->ComboAction ? this->ComboAction->ActionName : FName(); } FText UK2Node_ComboAction::GetNodeTitle(ENodeTitleType::Type TitleType) const { if (TitleType == ENodeTitleType::MenuTitle) { return FText::FromName(this->GetActionName()); } else if (this->CachedNodeTitle.IsOutOfDate(this)) { FFormatNamedArguments Args; Args.Add(TEXT("ComboActionName"), FText::FromName(this->GetActionName())); FText LocFormat = LOCTEXT("ComboAction_Name", "ComboAction {ComboActionName}"); // FText::Format() is slow, so we cache this to save on performance this->CachedNodeTitle.SetCachedText(FText::Format(LocFormat, Args), this); } return this->CachedNodeTitle; } FText UK2Node_ComboAction::GetTooltipText() const { if (CachedTooltip.IsOutOfDate(this)) { // FText::Format() is slow, so we cache this to save on performance CachedTooltip.SetCachedText(FText::Format(NSLOCTEXT("K2Node", "ComboAction_Tooltip", "Event for when the combo action {0} is activated or released.\nBoth execution pins might fire on the same frame if an action\nwas buffered during a release action, but \"Activated\" will\nalways happen before \"Released\"."), FText::FromName(this->ComboAction->GetFName())), this); } return CachedTooltip; } FSlateIcon UK2Node_ComboAction::GetIconAndTint(FLinearColor &OutColor) const { static FSlateIcon Icon(FAppStyle::GetAppStyleSetName(), "GraphEditor.Event_16x"); return Icon; } bool UK2Node_ComboAction::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_ComboAction::GetJumpTargetForDoubleClick() const { return const_cast(Cast(this->ComboAction)); } void UK2Node_ComboAction::JumpToDefinition() const { GEditor->GetEditorSubsystem()->OpenEditorForAsset(this->GetJumpTargetForDoubleClick()); } void UK2Node_ComboAction::ValidateNodeDuringCompilation(class FCompilerResultsLog &MessageLog) const { Super::ValidateNodeDuringCompilation(MessageLog); if (!this->ComboAction) { MessageLog.Error(*LOCTEXT("ComboAction_ErrorFmt", "ComboActionEvent references invalid 'null' action for @@").ToString(), this); return; } } void UK2Node_ComboAction::ExpandNode(FKismetCompilerContext &CompilerContext, UEdGraph *SourceGraph) { Super::ExpandNode(CompilerContext, SourceGraph); const UEdGraphSchema_K2 *Schema = CompilerContext.GetSchema(); // Create a temporary variable to copy Key in to UK2Node_TemporaryVariable *ComboActionObjectVar = CompilerContext.SpawnIntermediateNode(this, SourceGraph); ComboActionObjectVar->VariableType.PinCategory = UEdGraphSchema_K2::PC_Object; ComboActionObjectVar->VariableType.PinSubCategoryObject = UComboAction::StaticClass(); ComboActionObjectVar->AllocateDefaultPins(); // Establish active pins struct ActivePinData { ActivePinData(UEdGraphPin *InPin, EComboActionTriggerEvent InTriggerEvent) : Pin(InPin), TriggerEvent(InTriggerEvent){} UEdGraphPin *Pin; EComboActionTriggerEvent TriggerEvent; }; TArray ActivePins; ForEachEventPinName([this, &ActivePins, &CompilerContext](EComboActionTriggerEvent Event, FName PinName) { UEdGraphPin *InputActionPin = this->FindPin(PinName, EEdGraphPinDirection::EGPD_Output); if (InputActionPin && InputActionPin->LinkedTo.Num() > 0) { ActivePins.Add(ActivePinData(InputActionPin, Event)); } }); for(const ActivePinData &PinData : ActivePins) { UEdGraphPin *ComboActionPin = PinData.Pin; if (ComboActionPin->LinkedTo.Num() > 0) { // Create the combo action event UK2Node_ComboActionEvent *ComboActionEvent = CompilerContext.SpawnIntermediateEventNode(this, ComboActionPin, SourceGraph); ComboActionEvent->CustomFunctionName = FName(*FString::Printf(TEXT("ComboActionEvent_%s_%s"), *this->GetActionName().ToString(), *ComboActionEvent->GetName())); ComboActionEvent->ComboAction = this->ComboAction; ComboActionEvent->TriggerEvent = PinData.TriggerEvent; ComboActionEvent->EventReference.SetExternalDelegateMember(FName(TEXT("ComboActionHandlerDynamicSignature__DelegateSignature"))); ComboActionEvent->bInternalEvent = true; ComboActionEvent->AllocateDefaultPins(); // Create assignment nodes to assign the key UK2Node_AssignmentStatement *ComboActionObjectInitialize = CompilerContext.SpawnIntermediateNode(this, SourceGraph); ComboActionObjectInitialize->AllocateDefaultPins(); Schema->TryCreateConnection(ComboActionObjectVar->GetVariablePin(), ComboActionObjectInitialize->GetVariablePin()); Schema->TryCreateConnection(ComboActionObjectInitialize->GetValuePin(), ComboActionEvent->FindPinChecked(COMBO_ACTION_OBJECT_PIN_NAME)); // Connect the events to the assign key nodes Schema->TryCreateConnection(Schema->FindExecutionPin(*ComboActionEvent, EGPD_Output), ComboActionObjectInitialize->GetExecPin()); // Move the original event connections to the then pin of the key assign CompilerContext.MovePinLinksToIntermediate(*ComboActionPin, *ComboActionObjectInitialize->GetThenPin()); // Move the original event variable connections to the intermediate nodes CompilerContext.MovePinLinksToIntermediate(*this->FindPin(COMBO_ACTION_OBJECT_PIN_NAME), *ComboActionObjectVar->GetVariablePin()); } } } void UK2Node_ComboAction::GetMenuActions(FBlueprintActionDatabaseRegistrar &ActionRegistrar) const { auto CustomizeComboActionNodeLambda = [](UEdGraphNode *NewNode, bool bIsTemplateNode, TWeakObjectPtr Action) { UK2Node_ComboAction *ComboActionNode = CastChecked(NewNode); ComboActionNode->ComboAction = 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(UComboAction::StaticClass()->GetClassPathName(), ActionAssets, true); for (const FAssetData &ActionAsset : ActionAssets) { UBlueprintNodeSpawner *NodeSpawner = UBlueprintNodeSpawner::Create(GetClass()); check(NodeSpawner != nullptr); if (FPackageName::GetPackageMountPoint(ActionAsset.PackageName.ToString()) != NAME_None) { if (const UComboAction *Action = Cast(ActionAsset.GetAsset())) { NodeSpawner->CustomizeNodeDelegate = UBlueprintNodeSpawner::FCustomizeNodeDelegate::CreateStatic(CustomizeComboActionNodeLambda, TWeakObjectPtr(Action)); ActionRegistrar.AddBlueprintAction(Action, NodeSpawner); } } } } else if (const UComboAction *Action = Cast(ActionRegistrar.GetActionKeyFilter())) { // If this is a specific UComboAction asset update it. UBlueprintNodeSpawner *NodeSpawner = UBlueprintNodeSpawner::Create(GetClass()); check(NodeSpawner != nullptr); NodeSpawner->CustomizeNodeDelegate = UBlueprintNodeSpawner::FCustomizeNodeDelegate::CreateStatic(CustomizeComboActionNodeLambda, TWeakObjectPtr(Action)); ActionRegistrar.AddBlueprintAction(Action, NodeSpawner); } } FText UK2Node_ComboAction::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", "Combo Action Events")), this); // TODO: Rename Action Events once old action system is removed } return CachedCategory; } FBlueprintNodeSignature UK2Node_ComboAction::GetSignature() const { FBlueprintNodeSignature NodeSignature = Super::GetSignature(); NodeSignature.AddKeyValue(GetActionName().ToString()); return NodeSignature; } TSharedPtr UK2Node_ComboAction::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; } #undef LOCTEXT_NAMESPACE