ComboInput/Source/ComboInputNodes/Private/K2Node_ComboAction.cpp

303 lines
12 KiB
C++

// ©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<void(EComboActionTriggerEvent Event, FName PinName)> PinLambda)
{
UEnum *EventEnum = StaticEnum<EComboActionTriggerEvent>();
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<UEdGraphSchema_K2>();
this->AdvancedPinDisplay = ENodeAdvancedPins::Hidden;
ForEachEventPinName([this](EComboActionTriggerEvent Event, FName PinName)
{
static const UEnum *EventEnum = StaticEnum<EComboActionTriggerEvent>();
UEdGraphPin *NewPin = this->CreatePin(EGPD_Output, UEdGraphSchema_K2::PC_Exec, PinName);
NewPin->PinToolTip = EventEnum->GetToolTipTextByIndex(EventEnum->GetIndexByValue(static_cast<uint8>(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<UObject *>(Cast<UObject>(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<UGraphEditorSettings>()->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<UEdGraphSchema_K2>(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<UObject *>(Cast<UObject>(this->ComboAction));
}
void UK2Node_ComboAction::JumpToDefinition() const
{
GEditor->GetEditorSubsystem<UAssetEditorSubsystem>()->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<UK2Node_TemporaryVariable>(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<ActivePinData> 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<UK2Node_ComboActionEvent>(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<UK2Node_AssignmentStatement>(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<const UComboAction> Action)
{
UK2Node_ComboAction *ComboActionNode = CastChecked<UK2Node_ComboAction>(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<FAssetRegistryModule>(TEXT("AssetRegistry")).Get();
static bool bRegisterOnce = true;
if (bRegisterOnce)
{
bRegisterOnce = false;
if (AssetRegistry.IsLoadingAssets())
{
AssetRegistry.OnFilesLoaded().AddLambda([]()
{
FBlueprintActionDatabase::Get().RefreshClassActions(StaticClass());
});
}
}
TArray<FAssetData> 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<const UComboAction>(ActionAsset.GetAsset()))
{
NodeSpawner->CustomizeNodeDelegate = UBlueprintNodeSpawner::FCustomizeNodeDelegate::CreateStatic(CustomizeComboActionNodeLambda, TWeakObjectPtr<const UComboAction>(Action));
ActionRegistrar.AddBlueprintAction(Action, NodeSpawner);
}
}
}
}
else if (const UComboAction *Action = Cast<const UComboAction>(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<const UComboAction>(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<FEdGraphSchemaAction> UK2Node_ComboAction::GetEventNodeAction(const FText &ActionCategory)
{
// TODO: Custom EdGraphSchemaAction required?
TSharedPtr<FEdGraphSchemaAction_K2InputAction> EventNodeAction = MakeShareable(new FEdGraphSchemaAction_K2InputAction(ActionCategory, GetNodeTitle(ENodeTitleType::EditableTitle), GetTooltipText(), 0));
EventNodeAction->NodeTemplate = this;
return EventNodeAction;
}
#undef LOCTEXT_NAMESPACE