Compare commits

...

35 Commits

Author SHA1 Message Date
Jamie Greunbaum
26b506eb0e Merge pull request 'Created the necessary changes for Unreal Engine 5.4' (#1) from update-to-5.4 into main
Reviewed-on: BattyBovine/ComboInput#1
2024-06-28 17:28:10 -04:00
Jamie Greunbaum
67c0126322 Created the necessary changes for Unreal Engine 5.4 2024-06-28 17:26:45 -04:00
Jamie Greunbaum
a9a841911a Fixed a bug that can cause buttons not mapped to combo inputs to block later combo inputs when they aren't part of a multi-press. 2023-10-11 01:20:29 -04:00
Jamie Greunbaum
b77a9bd2ed - Release triggers only fire if one is available in the current node's children.
- Crashes related to PreviousNode occasionally becoming null have been fixed.
2023-10-07 19:41:23 -04:00
Jamie Greunbaum
08b89b9d84 Fixed some grammar issues with the variable names that were annoying me. 2023-10-07 18:58:26 -04:00
Jamie Greunbaum
b7d299f8b1 Display a validation window when validating a graph, and also a message stating that auto-arrange does not currently work. 2023-10-05 13:13:37 -04:00
Jamie Greunbaum
6261a7eddc Added a popup class for graph validation. 2023-10-05 13:13:03 -04:00
Jamie Greunbaum
9dec4a1c91 Disabled verification on save. 2023-10-04 23:32:07 -04:00
Jamie Greunbaum
0feb21fefc Components were reworked to no longer need a separate list of valid inputs to listen for. 2023-10-04 18:52:53 -04:00
Jamie Greunbaum
3d0fedabf6 Combo graph code now compiles and functions properly in shipping builds. 2023-10-04 13:05:53 -04:00
Jamie Greunbaum
c50c9c6bf7 Components now use InitializeComponent instead of BeginPlay. 2023-10-04 13:05:15 -04:00
Jamie Greunbaum
13172b3b60 - ComboManagerComponent accepts a list of actions to unlock.
- Removed more unused functions.
2023-10-03 22:52:48 -04:00
Jamie Greunbaum
64b2c8e934 Fixed up some description text. 2023-10-03 01:54:42 -04:00
Jamie Greunbaum
7505a70575 Added the ability to disable combo action nodes. Enabling them will come later. 2023-10-03 01:47:24 -04:00
Jamie Greunbaum
dcaac98f50 Node colours are now driven by combo input assets, and overridden by the combo action if a colour is set there. 2023-10-03 00:23:30 -04:00
Jamie Greunbaum
510ccb936a ComboManagerComponent has been modified to use the combo graph nodes instead of the placeholder ComboSequenceNode class. 2023-10-02 18:48:33 -04:00
Jamie Greunbaum
49ffadd4f3 Node title now takes the name of the assigned combo action. 2023-10-02 12:33:21 -04:00
Jamie Greunbaum
780409b060 Hide ContextMenuName variable from graph editor. 2023-10-02 12:32:11 -04:00
Jamie Greunbaum
3d06c5723e Added UComboAction class setting to the action node base class. 2023-10-02 00:57:44 -04:00
Jamie Greunbaum
a8786b87fc Hid a few more configuration options from the editor. 2023-10-02 00:56:54 -04:00
Jamie Greunbaum
390e144c33 Corrected a layout issue that made input pins look very stupid. 2023-10-01 21:17:33 -04:00
Jamie Greunbaum
61a8857105 Removed some more unnecessary leftover functions and variables. 2023-10-01 21:16:54 -04:00
Jamie Greunbaum
fde46f4a91 Default max child nodes for combo actions is set to -1 now, so that there is no limit. 2023-10-01 18:13:42 -04:00
Jamie Greunbaum
68397f74e6 Fixed up the layout of the combo graph nodes, so that they arrange horizontally instead of vertically. 2023-10-01 17:40:40 -04:00
Jamie Greunbaum
3d14af4625 Combo Action Graph is basically finished. The hard part, anyway. 2023-10-01 14:07:52 -04:00
Jamie Greunbaum
af2b70227d Adding nodes finally works correctly. Now beginning work on nodes that fit our actual development needs. 2023-09-30 18:32:45 -04:00
Jamie Greunbaum
102927a690 Added a bunch more stuff. Too much to care to mention. Still more to do though. 2023-09-30 01:25:51 -04:00
Jamie Greunbaum
cfe285b94d Did a shitload more work. To what end? Not much. 2023-09-29 15:11:48 -04:00
Jamie Greunbaum
de9865758a ComboActionGraph classes can now be added in the content browser. No asset editor yet though. 2023-09-29 01:33:09 -04:00
Jamie Greunbaum
8b6cfafc45 Code compiles now, at least. That's a start. 2023-09-27 12:21:35 -04:00
Jamie Greunbaum
ea4dd54ec9 Trying to add a combo graph editor. 2023-09-27 00:21:59 -04:00
Jamie Greunbaum
bf43acf0dc Global settings renamed to something that does not reference the old subsystem. 2023-09-24 13:03:16 -04:00
Jamie Greunbaum
aa81438bc7 Fixed various crashes and infinite loops when encountering unhandled or null combo input assets. 2023-09-22 14:24:15 -04:00
Jamie Greunbaum
a4913db597 Input buffer changed from a subsystem to an extension of the input component. 2023-09-21 20:32:29 -04:00
Jamie Greunbaum
74411baaea Locking inputs now happens without being tied to lists in combo input assets. 2023-09-21 16:02:27 -04:00
115 changed files with 8648 additions and 396 deletions

View File

@ -6,7 +6,7 @@
"Description": "A set of components and classes for capturing Enhanced Input actions for buffering actions and stringing them into complex combos.",
"Category": "Input",
"CreatedBy": "Jamie Greunbaum",
"EngineVersion": "5.3.0",
"EngineVersion": "5.4.0",
"CanContainContent": true,
"IsBetaVersion": true,
"IsExperimentalVersion": true,

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 295 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 180 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 518 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 382 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 124 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 138 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

View File

@ -44,6 +44,7 @@ public class ComboInput : ModuleRules
"Core",
"CoreUObject",
"Engine",
"GameplayTags",
"Slate",
"SlateCore",
"DeveloperSettings",

View File

@ -0,0 +1,118 @@
// ©2023 Batty Bovine Productions, LLC. All Rights Reserved.
#include "ComboActionGraph.h"
#include "Nodes/ComboActionGraphEdge.h"
#include "Nodes/ComboActionGraphNode.h"
#include "Nodes/ComboActionGraphNode_StartNode.h"
#define LOCTEXT_NAMESPACE "ComboActionGraph"
DEFINE_LOG_CATEGORY(LogComboActionGraph);
UComboActionGraph::UComboActionGraph()
{
this->NodeType = UComboActionGraphNode::StaticClass();
this->EdgeType = UComboActionGraphEdge::StaticClass();
this->bEdgeEnabled = false;
this->GraphGUID = FGuid::NewGuid();
#if WITH_EDITORONLY_DATA
this->EdGraph = nullptr;
this->bCanRenameNode = true;
#endif
}
void UComboActionGraph::CreateGraph()
{
#if WITH_EDITOR
// We already have an existing graph or start node
if (this->EdGraph != nullptr || this->StartNode != nullptr)
{
return;
}
this->StartNode = this->ConstructActionNode<UComboActionGraphNode_StartNode>();
if (this->StartNode != nullptr )
{
this->StartNode->Graph = this;
this->RootNodes.Add(this->StartNode);
this->AllNodes.Add(this->StartNode);
}
#endif
}
void UComboActionGraph::ClearGraph()
{
for (UComboActionGraphNode *Node : this->AllNodes)
{
Node->ParentNodes.Empty();
Node->ChildNodes.Empty();
Node->Edges.Empty();
}
this->AllNodes.Empty();
this->RootNodes.Empty();
}
void UComboActionGraph::PostInitProperties()
{
UObject::PostInitProperties();
// Ignore these cases
if (this->HasAnyFlags(EObjectFlags::RF_ClassDefaultObject | EObjectFlags::RF_NeedLoad))
{
return;
}
#if WITH_EDITOR
this->CreateGraph();
#endif
}
#if WITH_EDITOR
bool UComboActionGraph::ValidateGraph(TArray<FText> &ValidationErrors, bool RichTextFormat)
{
bool bReturnValue = true;
if (this->StartNode == nullptr)
{
const FString RichTextReturn =
FString("* ")
.Append(TEXT("<RichTextBlock.Bold>Dialogue Graph</>"))
.Append(": Has no Start Node!");
const FString TextReturn =
this->GetName()
.Append(": Has no Start Node!");
ValidationErrors.Add(FText::FromString(RichTextFormat ? RichTextReturn : TextReturn));
bReturnValue = false;
}
for (UComboActionGraphNode *Itr : this->AllNodes)
{
if (Itr != nullptr && !Itr->ValidateNode(ValidationErrors, RichTextFormat))
{
bReturnValue = false;
}
}
return bReturnValue;
}
EDataValidationResult UComboActionGraph::IsDataValid(TArray<FText> &ValidationErrors)
{
//return this->ValidateGraph(ValidationErrors, false)
// ? EDataValidationResult::Valid
// : EDataValidationResult::Invalid;
return EDataValidationResult::NotValidated;
}
#endif
#undef LOCTEXT_NAMESPACE

View File

@ -2,38 +2,45 @@
#include "Components/ComboManagerComponent.h"
#include "ComboActionGraph.h"
#include "ComboInputAssets.h"
#include "EnhancedInputComponent.h"
#include "InputBufferLocalPlayerSubsystem.h"
#include "Components/InputBufferComponent.h"
#include "Engine/LocalPlayer.h"
#include "GameFramework/Character.h"
#include "Kismet/GameplayStatics.h"
#include "Nodes/ComboActionGraphNode_ActionNode.h"
DEFINE_LOG_CATEGORY(LogComboManagerComponent);
UComboManagerComponent::UComboManagerComponent()
{
PrimaryComponentTick.bStartWithTickEnabled = false;
PrimaryComponentTick.bTickEvenWhenPaused = false;
PrimaryComponentTick.bCanEverTick = false;
this->PrimaryComponentTick.bStartWithTickEnabled = false;
this->PrimaryComponentTick.bTickEvenWhenPaused = false;
this->PrimaryComponentTick.bCanEverTick = false;
this->bWantsInitializeComponent = true;
}
void UComboManagerComponent::BeginPlay()
void UComboManagerComponent::InitializeComponent()
{
Super::BeginPlay();
Super::InitializeComponent();
// If this component's owner is the player character or controller, attach it to the subsystem.
APlayerController *PlayerController = UGameplayStatics::GetPlayerController(this, 0);
if (this->GetOwner() == PlayerController)
{
UEnhancedInputComponent *InputComponent = Cast<UEnhancedInputComponent>(PlayerController->InputComponent);
checkf(InputComponent, TEXT("Discovered player input component is not of type %s."), *UEnhancedInputComponent::StaticClass()->GetName());
checkf(this->ComboGraph, TEXT("No combo graph is set for %s in actor %s"), *UComboManagerComponent::StaticClass()->GetName(), *this->GetOwner()->GetName());
this->PreviousNode = this->ActiveNode = this->ComboGraph->StartNode;
}
UInputBufferLocalPlayerSubsystem *InputBufferSubsystem = PlayerController->GetLocalPlayer()->GetSubsystem<UInputBufferLocalPlayerSubsystem>();
InputBufferSubsystem->AttachComboManager(this, InputComponent);
}
void UComboManagerComponent::SetComboGraph(const UComboActionGraph *Graph)
{
checkf(Graph, TEXT("Attempting to set a null combo graph."));
this->ComboGraph = Graph;
this->FoundInputsCache.Empty();
this->FindAllUsedInputs();
this->ResetCombo();
}
@ -76,50 +83,85 @@ void UComboManagerComponent::ActivateComboAction(const UComboInputAsset *Input)
return;
}
const TObjectPtr<const UComboSequenceNode> CurrentNode = (this->ActiveNode ? this->ActiveNode : this->DefaultStartNode);
checkf(CurrentNode, TEXT("No combo sequence nodes available."));
const FComboSequenceAction *ActionData = CurrentNode->ComboBranch.Find(Input);
// If this node has an action we can activate, then activate it.
if (ActionData && ActionData->ComboAction)
// Find the next node in the graph.
const UComboAction *ComboAction = nullptr;
const UComboActionGraphNode *NextNode = this->FindActiveNodeData(this->ActiveNode, Input, EComboActionTriggerEvent::Activated, ComboAction);
if (!NextNode)
{
this->BeginNodeTransition(ActionData->NextNode);
this->BroadcastDelegates(ActionData->ComboAction, EComboActionTriggerEvent::Activated);
UE_LOG(LogComboManagerComponent, Verbose, TEXT("%s activated"), *ActionData->ComboAction->ActionName.ToString());
// If there is no next active node, then return to the start node.
NextNode = this->FindActiveNodeData(this->ComboGraph->StartNode, Input, EComboActionTriggerEvent::Activated, ComboAction);
}
checkf(NextNode, TEXT("No combo sequence nodes available."));
// If this node has an action we can activate, then activate it.
if (ComboAction)
{
this->BeginNodeTransition(NextNode);
this->BroadcastDelegates(ComboAction, EComboActionTriggerEvent::Activated);
UE_LOG(LogComboManagerComponent, Verbose, TEXT("%s activated"), *ComboAction->ActionName.ToString());
}
// Otherwise, see if we have a fallback we can use.
else if (const TObjectPtr<const UComboAction> *ComboAction = this->FallbackActions.Find(Input))
else if (const TObjectPtr<const UComboAction> *FallbackAction = this->FallbackActions.Find(Input))
{
this->ResetCombo();
this->BroadcastDelegates(*ComboAction, EComboActionTriggerEvent::Activated);
UE_LOG(LogComboManagerComponent, Verbose, TEXT("Fallback action %s activated"), *(*ComboAction)->ActionName.ToString());
this->BroadcastDelegates(*FallbackAction, EComboActionTriggerEvent::Activated);
UE_LOG(LogComboManagerComponent, Verbose, TEXT("Fallback action %s activated"), *(*FallbackAction)->ActionName.ToString());
}
// If we haven't found an action to perform, reset the combo and activate using the default start node.
// If we haven't found an action to perform, end here.
else
{
this->ActiveNode = nullptr;
this->ActivateComboAction(Input);
return;
UE_LOG(LogComboManagerComponent, Verbose, TEXT("No action found for %s"), *Input->ComboInputName.ToString());
}
}
void UComboManagerComponent::DEBUG__UnlockAction(TObjectPtr<const UComboInputAsset> Unlock)
{
APlayerController *Controller = UGameplayStatics::GetPlayerController(this, 0);
UInputBufferLocalPlayerSubsystem *Subsystem = Controller->GetLocalPlayer()->GetSubsystem<UInputBufferLocalPlayerSubsystem>();
Subsystem->UnlockComboInput(Unlock);
if (UInputBufferComponent *InputComponent = Cast<UInputBufferComponent>(Controller->InputComponent))
{
InputComponent->UnlockComboInput(Unlock);
}
}
void UComboManagerComponent::ReleaseComboAction(const class UComboInputAsset *Input)
{
if (this->LastComboAction)
if (this->LastComboAction && this->ActiveNode)
{
this->BroadcastDelegates(this->LastComboAction, EComboActionTriggerEvent::Released);
this->LastComboAction = nullptr;
// See if we have a fallback we can release.
const TObjectPtr<const UComboAction> *FallbackAction = this->FallbackActions.Find(Input);
if (FallbackAction && *FallbackAction == this->LastComboAction)
{
this->ResetCombo();
this->BroadcastDelegates(*FallbackAction, EComboActionTriggerEvent::Released);
UE_LOG(LogComboManagerComponent, Verbose, TEXT("Fallback action %s released"), *(*FallbackAction)->ActionName.ToString());
}
else
{
// Find a node that matches the release action.
const UComboAction *ComboAction = nullptr;
for (const UComboActionGraphNode *Node : this->ActiveNode->ChildNodes)
{
if (const UComboActionGraphNode_ActionNode *ActionNode = StaticCast<const UComboActionGraphNode_ActionNode *>(Node))
{
if (ActionNode->GetTriggerEvent() == EComboActionTriggerEvent::Released)
{
ComboAction = ActionNode->GetComboAction();
break;
}
}
}
if (ComboAction)
{
this->BroadcastDelegates(ComboAction, EComboActionTriggerEvent::Released);
UE_LOG(LogComboManagerComponent, Verbose, TEXT("%s released"), *ComboAction->ActionName.ToString());
}
}
}
this->LastComboAction = nullptr;
}
void UComboManagerComponent::BeginNodeTransition(const UComboSequenceNode *NextNode)
void UComboManagerComponent::BeginNodeTransition(const UComboActionGraphNode *NextNode)
{
this->PreviousNode = this->ActiveNode;
this->ActiveNode = NextNode;
@ -136,7 +178,80 @@ void UComboManagerComponent::FinishTransition()
void UComboManagerComponent::ResetCombo()
{
this->GetWorld()->GetTimerManager().ClearTimer(this->DEBUG__ResetComboTimer);
this->ActiveNode = this->PreviousNode = nullptr;
this->PreviousNode = this->ActiveNode = this->ComboGraph->StartNode;
APlayerController *Controller = UGameplayStatics::GetPlayerController(this, 0);
if (UInputBufferComponent *InputComponent = Cast<UInputBufferComponent>(Controller->InputComponent))
{
for (const UComboInputAsset *Unlock : this->FoundInputsCache)
{
InputComponent->UnlockComboInput(Unlock);
}
}
}
TSet<const UComboInputAsset *> &UComboManagerComponent::FindAllUsedInputs()
{
if (this->FoundInputsCache.IsEmpty())
{
// First check the graph for inputs to respond to.
this->FindAllUsedInputs_RecurseGraph(this->ComboGraph->StartNode, this->FoundInputsCache);
// Next check the fallback actions for supplementary inputs to respond to.
TArray<TObjectPtr<const UComboInputAsset>> FallbackInputs;
this->FallbackActions.GetKeys(FallbackInputs);
for (const TObjectPtr<const UComboInputAsset> &FallbackInput : FallbackInputs)
{
this->FoundInputsCache.Add(FallbackInput);
}
// Finally check the offset action for the final input to respond to.
if (this->OffsetMap.ComboInput)
{
this->FoundInputsCache.Add(this->OffsetMap.ComboInput);
}
}
return this->FoundInputsCache;
}
void UComboManagerComponent::FindAllUsedInputs_RecurseGraph(const UComboActionGraphNode *CurrentNode, TSet<const UComboInputAsset *> &FoundInputs)
{
for (const UComboActionGraphNode *NextNode : CurrentNode->ChildNodes)
{
// It should be safe to assume all of our nodes are action nodes. If at some point
// this becomes less reliable, this should be changed. But just for the sake of
// saving a bit of performance on start-up, we're doing this right now.
checkf(NextNode->GetClass() == UComboActionGraphNode_ActionNode::StaticClass(), TEXT("This graph contains non-action nodes. The cast in this function will fail."));
FoundInputs.Add(StaticCast<const UComboActionGraphNode_ActionNode *>(NextNode)->GetComboInput());
this->FindAllUsedInputs_RecurseGraph(NextNode, FoundInputs);
}
}
const UComboActionGraphNode *UComboManagerComponent::FindActiveNodeData(const UComboActionGraphNode *CurrentNode, const UComboInputAsset *Input, const EComboActionTriggerEvent TriggerEvent, const UComboAction *&ComboAction)
{
checkf(CurrentNode, TEXT("Attempting to find an active node from a null node."));
// Find a node that matches both the combo input and the trigger action.
const UComboActionGraphNode *NextNode = nullptr;
for (const UComboActionGraphNode *GraphNode : CurrentNode->ChildNodes)
{
if (const UComboActionGraphNode_ActionNode *ActionNode = Cast<UComboActionGraphNode_ActionNode>(GraphNode))
{
if (ActionNode->GetComboInput() == Input && ActionNode->GetTriggerEvent() == EComboActionTriggerEvent::Activated)
{
// If we found the right node, only acknowledge it if it's enabled.
const UComboAction *CheckAction = ActionNode->GetComboAction();
if (ActionNode->bEnabled || this->ComboGraph->UnlockedActions.Contains(CheckAction))
{
ComboAction = CheckAction;
NextNode = ActionNode;
}
break;
}
}
}
return NextNode;
}

View File

@ -0,0 +1,250 @@
// ©2022 Batty Bovine Productions, LLC. All Rights Reserved.
#include "Components/InputBufferComponent.h"
#include "ComboInputAssets.h"
#include "EnhancedInputComponent.h"
#include "Kismet/GameplayStatics.h"
#include "Components/ComboManagerComponent.h"
#include "GlobalSettings/InputBufferGlobalSettings.h"
DEFINE_LOG_CATEGORY(LogInputBufferComponent);
UInputBufferComponent::UInputBufferComponent()
{
this->bWantsInitializeComponent = true;
}
void UInputBufferComponent::InitializeComponent()
{
Super::InitializeComponent();
if (APlayerController *PlayerController = UGameplayStatics::GetPlayerController(this, 0))
{
if (UComboManagerComponent *ComboManager = PlayerController->GetComponentByClass<UComboManagerComponent>())
{
// Get the player character and try to connect to its combo manager.
this->OnNewComboInput.BindUObject(ComboManager, &UComboManagerComponent::HandleComboInput);
const TSet<const UComboInputAsset *> &ComboInputs = ComboManager->FindAllUsedInputs();
// Get all unique EnhancedInput actions bound to combo input actions.
TSet<const UInputAction *> InputActionsToBind;
for (const UComboInputAsset *ComboInput : ComboInputs)
{
if (ComboInput)
{
this->ComboInputList.Emplace(ComboInput);
for (const UInputAction *InputAction : ComboInput->ActionGroup)
{
InputActionsToBind.Add(InputAction);
}
}
else
{
UE_LOG(LogInputBufferComponent, Verbose, TEXT("Invalid combo input found"));
}
}
for (const UInputAction *InputAction : InputActionsToBind)
{
this->BindAction(InputAction, ETriggerEvent::Started, this, &UInputBufferComponent::AddActionToBuffer, InputAction);
this->BindAction(InputAction, ETriggerEvent::Completed, this, &UInputBufferComponent::ExpireAction, InputAction);
}
}
}
}
void UInputBufferComponent::AddActionToBuffer(const FInputActionValue &Value, const class UInputAction *Action)
{
this->MultiPressActions.Add(Action);
this->ExpiringActions.Remove(Action);
// Find any combo input that matches this action, plus buffered actions.
bool bComboInputFound = false;
for (TObjectPtr<const UComboInputAsset> ComboInput : this->ComboInputList)
{
if (ComboInput->MatchesInputActions(this->MultiPressActions))
{
this->ActivateComboInput(ComboInput.Get());
bComboInputFound = true;
break;
}
}
// If we haven't found any matching inputs, check if anything was somehow unhandled.
// This can happen if, for example, a jump input can combine with an attack input to
// create a combo action, but jump by itself has no action on its own.
if (!bComboInputFound)
{
if (!this->UnhandledActions.IsEmpty())
{
TSet<const UInputAction*> HandledActions = this->MultiPressActions;
for (const UInputAction *UnhandledAction : this->UnhandledActions)
{
HandledActions.Remove(UnhandledAction);
}
for (TObjectPtr<const UComboInputAsset> ComboInput : this->ComboInputList)
{
if (ComboInput->MatchesInputActions(HandledActions))
{
this->ActivateComboInput(ComboInput);
bComboInputFound = true;
break;
}
}
}
this->UnhandledActions.Add(Action);
}
const UInputBufferGlobalSettings *Settings = GetDefault<UInputBufferGlobalSettings>();
this->GetWorld()->GetTimerManager().SetTimer(this->MultiPressTimerHandle, this, &UInputBufferComponent::ClearMultiPresses, Settings->MultiPressTimerLength);
}
void UInputBufferComponent::ClearMultiPresses()
{
#if WITH_EDITOR
TArray<FString> ActionNames;
for (const UInputAction *Action : this->MultiPressActions)
{
ActionNames.Add(Action->GetName());
}
UE_LOG(LogInputBufferComponent, Verbose, TEXT("Multi-press buffer cleared (%s)"), *FString::Join(ActionNames, TEXT(" | ")));
#endif
this->MultiPressActions.Empty();
this->UnhandledActions.Empty();
}
void UInputBufferComponent::ActivateComboInput(const UComboInputAsset *ComboInput)
{
checkf(ComboInput, TEXT("Invalid %s."), *UComboInputAsset::StaticClass()->GetName());
const bool bNewInputLocked = this->LockedComboInputs.Contains(ComboInput);
const bool bNewSupercedesActive = (this->InputBufferActive && this->InputBufferActive->ContainsOneOf(ComboInput->ActionGroup) && !bNewInputLocked);
// Make this combo input active if it isn't being locked, or if we are
// overwriting a previous combo input with a multi-press combo input.
if (bNewSupercedesActive || !bNewInputLocked)
{
// Set the combo input as active.
this->InputBufferActive = ComboInput;
this->InputBufferHold = nullptr;
const UInputBufferGlobalSettings *Settings = GetDefault<UInputBufferGlobalSettings>();
for (const TObjectPtr<const UComboInputAsset> &LockedAsset : this->ComboInputList)
{
this->LockComboInput(LockedAsset);
}
this->OnNewComboInput.Execute(ComboInput, EComboActionTriggerEvent::Activated);
// If the incoming combo input contains an expiring action,
// we know it's been released, so send that signal too.
if (ComboInput->ContainsOneOf(this->ExpiringActions))
{
this->OnNewComboInput.Execute(ComboInput, EComboActionTriggerEvent::Released);
}
UE_LOG(LogInputBufferComponent, Verbose, TEXT("%s is active."), *ComboInput->ComboInputName.ToString());
}
else
{
this->HoldComboInput(ComboInput);
}
}
void UInputBufferComponent::HoldComboInput(const UComboInputAsset *ComboInput)
{
this->InputBufferHold = ComboInput;
UE_SUPPRESS(LogInputBufferComponent, Verbose,
{
if (this->LockedComboInputs.Contains(ComboInput))
{
UE_LOG(LogInputBufferComponent, Verbose, TEXT("%s is locked and won't be activated yet."), *ComboInput->ComboInputName.ToString());
}
else
{
UE_LOG(LogInputBufferComponent, Verbose, TEXT("%s added to buffer."), *ComboInput->ComboInputName.ToString());
}
}
);
}
void UInputBufferComponent::LockComboInput(const UComboInputAsset *Input)
{
this->LockedComboInputs.Emplace(Input);
}
void UInputBufferComponent::UnlockComboInput(const UComboInputAsset *Unlocked)
{
// Remove the newly-unlocked asset from the locked combo inputs.
UE_CLOG(this->LockedComboInputs.Contains(Unlocked), LogInputBufferComponent, Verbose, TEXT("%s has unlocked."), *Unlocked->ComboInputName.ToString());
this->LockedComboInputs.Remove(Unlocked);
// Check if the newly unlocked combo input is in the hold.
if (Unlocked == this->InputBufferHold)
{
const UComboInputAsset *OriginalActive = this->InputBufferActive;
// Activate the held combo input.
const UComboInputAsset *HeldAsset = this->InputBufferHold;
this->InputBufferHold = nullptr;
if (HeldAsset)
{
this->ActivateComboInput(HeldAsset);
}
UE_LOG(LogInputBufferComponent, Verbose, TEXT("%s has expired."), *OriginalActive->ComboInputName.ToString());
}
}
void UInputBufferComponent::ExpireAction(const FInputActionValue &Value, const class UInputAction *Action)
{
// Only send a release event if we haven't already, i.e. if the combo input associated
// with the action is not already buffered for another future activation.
if (this->InputBufferActive && this->InputBufferActive != this->InputBufferHold && this->InputBufferActive->MatchesInputAction(Action))
{
this->OnNewComboInput.Execute(this->InputBufferActive, EComboActionTriggerEvent::Released);
}
// Prepare to expire any buffered combo inputs
this->ExpiringActions.Add(Action);
const UInputBufferGlobalSettings *Settings = GetDefault<UInputBufferGlobalSettings>();
this->GetWorld()->GetTimerManager().SetTimer(this->InputReleaseExpirationTimerHandle, this, &UInputBufferComponent::ExpireBufferedActions, Settings->InputReleaseExpirationTimerLength);
}
void UInputBufferComponent::ExpireBufferedActions()
{
// Only bother dealing with this if there's something to deal with in the first place.
if (!this->ExpiringActions.IsEmpty())
{
UE_SUPPRESS(LogInputBufferComponent, Verbose,
{
TArray<FString> ActionNames;
for (const UInputAction *Action : this->ExpiringActions)
{
ActionNames.Add(Action->GetName());
}
UE_LOG(LogInputBufferComponent, Verbose, TEXT("Released actions expired (%s)"), *FString::Join(ActionNames, TEXT(" | ")));
}
);
// If there is an action in the hold, check if it's related
// to our current released buttons, and if so cancel it.
if (this->InputBufferHold)
{
for (const UInputAction *Action : this->ExpiringActions)
{
if (this->InputBufferHold->MatchesInputAction(Action))
{
UE_LOG(LogInputBufferComponent, Verbose, TEXT("%s has been cancelled."), *this->InputBufferHold->ComboInputName.ToString());
this->InputBufferHold = nullptr;
break;
}
}
}
this->ExpiringActions.Empty();
}
}

View File

@ -1,6 +1,6 @@
// ©2023 Batty Bovine Productions, LLC. All Rights Reserved.
#include "GlobalSettings/InputBufferSubsystemGlobalSettings.h"
#include "GlobalSettings/InputBufferGlobalSettings.h"

View File

@ -1,204 +0,0 @@
// ©2022 Batty Bovine Productions, LLC. All Rights Reserved.
#include "InputBufferLocalPlayerSubsystem.h"
#include "ComboInputAssets.h"
#include "EnhancedInputComponent.h"
#include "Components/ComboManagerComponent.h"
#include "GlobalSettings/InputBufferSubsystemGlobalSettings.h"
DEFINE_LOG_CATEGORY(LogInputBufferLocalPlayerSubsystem);
void UInputBufferLocalPlayerSubsystem::Initialize(FSubsystemCollectionBase &Collection)
{
Super::Initialize(Collection);
}
void UInputBufferLocalPlayerSubsystem::AttachComboManager(UComboManagerComponent *ComboManager, UEnhancedInputComponent *InputComponent)
{
// Get the player character and try to connect to its combo manager.
this->OnNewComboInput.BindUObject(ComboManager, &UComboManagerComponent::HandleComboInput);
// Get all unique EnhancedInput actions bound to combo input actions.
const UInputBufferSubsystemGlobalSettings *Settings = GetDefault<UInputBufferSubsystemGlobalSettings>();
TSet<const UInputAction*> InputActionsToBind;
for (TSoftObjectPtr<const UComboInputAsset> ComboInput : Settings->ComboActions)
{
for (const UInputAction *InputAction : ComboInput->ActionGroup)
{
InputActionsToBind.Add(InputAction);
}
}
for (const UInputAction *InputAction : InputActionsToBind)
{
InputComponent->BindAction(InputAction, ETriggerEvent::Started, this, &UInputBufferLocalPlayerSubsystem::AddActionToBuffer, InputAction);
InputComponent->BindAction(InputAction, ETriggerEvent::Completed, this, &UInputBufferLocalPlayerSubsystem::ExpireAction, InputAction);
}
}
void UInputBufferLocalPlayerSubsystem::AddActionToBuffer(const FInputActionValue &Value, const class UInputAction *Action)
{
this->MostRecentActions.Add(Action);
this->ExpiringActions.Remove(Action);
// Find any combo input that matches this action, plus buffered actions.
const UInputBufferSubsystemGlobalSettings *Settings = GetDefault<UInputBufferSubsystemGlobalSettings>();
for (TSoftObjectPtr<const UComboInputAsset> ComboInput : Settings->ComboActions)
{
if (ComboInput->MatchesInputActions(this->MostRecentActions))
{
this->ActivateComboInput(ComboInput.Get());
break;
}
}
this->GetWorld()->GetTimerManager().SetTimer(this->MultiPressTimerHandle, this, &UInputBufferLocalPlayerSubsystem::ClearMultiPresses, Settings->MultiPressTimerLength);
}
void UInputBufferLocalPlayerSubsystem::ClearMultiPresses()
{
#if WITH_EDITOR
TArray<FString> ActionNames;
for (const UInputAction *Action : this->MostRecentActions)
{
ActionNames.Add(Action->GetName());
}
UE_LOG(LogInputBufferLocalPlayerSubsystem, Verbose, TEXT("Multi-press buffer cleared (%s)"), *FString::Join(ActionNames, TEXT(" | ")));
#endif
this->MostRecentActions.Empty();
}
void UInputBufferLocalPlayerSubsystem::ActivateComboInput(const UComboInputAsset *ComboInput)
{
checkf(ComboInput, TEXT("Invalid %s."), *UComboInputAsset::StaticClass()->GetName());
const bool bNewInputLocked = this->LockedComboInputs.Contains(ComboInput);
const bool bNewSupercedesActive = (this->InputBufferActive && this->InputBufferActive->ContainsOneOf(ComboInput->ActionGroup) && !bNewInputLocked);
// Make this combo input active if it isn't being locked, or if we are
// overwriting a previous combo input with a multi-press combo input.
if (bNewSupercedesActive || !bNewInputLocked)
{
// Set the combo input as active.
this->InputBufferActive = ComboInput;
// If we have inputs to lock, prepare the buffer.
if (!ComboInput->LockedComboInputs.IsEmpty())
{
this->LockedComboInputs = ComboInput->LockedComboInputs;
this->InputBufferHold = nullptr;
UE_LOG(LogInputBufferLocalPlayerSubsystem, Verbose, TEXT("%s is active."), *ComboInput->ComboInputName.ToString());
}
else
{
UE_LOG(LogInputBufferLocalPlayerSubsystem, Verbose, TEXT("%s is active and won't lock inputs."), *ComboInput->ComboInputName.ToString());
}
this->OnNewComboInput.Execute(ComboInput, EComboActionTriggerEvent::Activated);
// If the incoming combo input contains an expiring action,
// we know it's been released, so send that signal too.
if (ComboInput->ContainsOneOf(this->ExpiringActions))
{
this->OnNewComboInput.Execute(ComboInput, EComboActionTriggerEvent::Released);
}
}
else
{
this->HoldComboInput(ComboInput);
}
}
void UInputBufferLocalPlayerSubsystem::HoldComboInput(const UComboInputAsset *ComboInput)
{
this->InputBufferHold = ComboInput;
UE_SUPPRESS(LogInputBufferLocalPlayerSubsystem, Verbose,
{
if (this->LockedComboInputs.Contains(ComboInput))
{
UE_LOG(LogInputBufferLocalPlayerSubsystem, Verbose, TEXT("%s is locked and won't be activated yet."), *ComboInput->ComboInputName.ToString());
}
else
{
UE_LOG(LogInputBufferLocalPlayerSubsystem, Verbose, TEXT("%s added to buffer."), *ComboInput->ComboInputName.ToString());
}
}
);
}
void UInputBufferLocalPlayerSubsystem::UnlockComboInput(const UComboInputAsset *Unlocked)
{
// Remove the newly-unlocked asset from the locked combo inputs.
UE_CLOG(this->LockedComboInputs.Contains(Unlocked), LogInputBufferLocalPlayerSubsystem, Verbose, TEXT("%s has unlocked."), *Unlocked->ComboInputName.ToString());
this->LockedComboInputs.Remove(Unlocked);
// Check if the newly unlocked combo input is in the hold.
if (Unlocked == this->InputBufferHold)
{
const UComboInputAsset *OriginalActive = this->InputBufferActive;
// Activate the held combo input.
const UComboInputAsset *HeldAsset = this->InputBufferHold;
this->InputBufferHold = nullptr;
if (HeldAsset)
{
this->ActivateComboInput(HeldAsset);
}
UE_LOG(LogInputBufferLocalPlayerSubsystem, Verbose, TEXT("%s has expired."), *OriginalActive->ComboInputName.ToString());
}
}
void UInputBufferLocalPlayerSubsystem::ExpireAction(const FInputActionValue &Value, const class UInputAction *Action)
{
// Only send a release event if we haven't already, i.e. if the combo input associated
// with the action is not already buffered for another future activation.
if (this->InputBufferActive && this->InputBufferActive != this->InputBufferHold && this->InputBufferActive->MatchesInputAction(Action))
{
this->OnNewComboInput.Execute(this->InputBufferActive, EComboActionTriggerEvent::Released);
}
// Prepare to expire any buffered combo inputs
this->ExpiringActions.Add(Action);
const UInputBufferSubsystemGlobalSettings *Settings = GetDefault<UInputBufferSubsystemGlobalSettings>();
this->GetWorld()->GetTimerManager().SetTimer(this->InputReleaseExpirationTimerHandle, this, &UInputBufferLocalPlayerSubsystem::ExpireBufferedActions, Settings->InputReleaseExpirationTimerLength);
}
void UInputBufferLocalPlayerSubsystem::ExpireBufferedActions()
{
// Only bother dealing with this if there's something to deal with in the first place.
if (!this->ExpiringActions.IsEmpty())
{
UE_SUPPRESS(LogInputBufferLocalPlayerSubsystem, Verbose,
{
TArray<FString> ActionNames;
for (const UInputAction *Action : this->ExpiringActions)
{
ActionNames.Add(Action->GetName());
}
UE_LOG(LogInputBufferLocalPlayerSubsystem, Verbose, TEXT("Released actions expired (%s)"), *FString::Join(ActionNames, TEXT(" | ")));
}
);
// If there is an action in the hold, check if it's related
// to our current released buttons, and if so cancel it.
if (this->InputBufferHold)
{
for (const UInputAction *Action : this->ExpiringActions)
{
if (this->InputBufferHold->MatchesInputAction(Action))
{
UE_LOG(LogInputBufferLocalPlayerSubsystem, Verbose, TEXT("%s has been cancelled."), *this->InputBufferHold->ComboInputName.ToString());
this->InputBufferHold = nullptr;
break;
}
}
}
this->ExpiringActions.Empty();
}
}

View File

@ -0,0 +1,6 @@
// ©2023 Batty Bovine Productions, LLC. All Rights Reserved.
#include "Nodes/ComboActionGraphEdge.h"

View File

@ -0,0 +1,203 @@
// ©2023 Batty Bovine Productions, LLC. All Rights Reserved.
#include "Nodes/ComboActionGraphNode.h"
#include "ComboActionGraph.h"
#include "ComboInputAssets.h"
#include "ComboInputTriggers.h"
DEFINE_LOG_CATEGORY(LogComboActionGraphNode);
#define LOCTEXT_NAMESPACE "ComboActionGraphNode"
UComboActionGraphNode::UComboActionGraphNode()
{
this->NodeGUID = FGuid::NewGuid();
this->bEnabled = true;
#if WITH_EDITORONLY_DATA
this->CompatibleGraphType = UComboActionGraph::StaticClass();
this->BackgroundColor = FLinearColor::Black;
this->bAllowInputNodes = true;
this->bAllowOutputNodes = true;
this->bAllowCopy = true;
this->bAllowCut = true;
this->bAllowDelete = true;
this->bAllowPaste = true;
this->bAllowManualCreate = true;
this->NodeTypeName = LOCTEXT("ComboActionGraphNode_InternalName", "ComboActionGraphNode");
this->NodeTooltipText = LOCTEXT("ComboActionGraphNode_Tooltip", "Combo action graph base node. Child nodes provide more information.");
#endif
}
void UComboActionGraphNode::SetNewWorld(UWorld *NewWorld)
{
if (NewWorld)
{
this->OwningWorld = NewWorld;
}
}
void UComboActionGraphNode::InitializeNode_Implementation(UWorld *InWorld)
{
this->SetNewWorld(InWorld);
if (this->Graph)
{
this->SetNodeIndex(this->Graph->AllNodes.Find(this));
}
}
void UComboActionGraphNode::ProcessNode(const TScriptInterface<IComboActionGraphManagerInterface> &Manager)
{
if (!this->GetWorld())
{
UE_LOG(LogComboActionGraphNode, Error, TEXT("[ProcessNode] Cannot find World!"));
return;
}
if (!this->GetGraph())
{
UE_LOG(LogComboActionGraphNode, Error, TEXT("[ProcessNode] Invalid owning Graph!!"));
return;
}
}
void UComboActionGraphNode::SetNodeIndex(const int32 NewIndex)
{
check(NewIndex>INDEX_NONE);
this->NodeIndex = NewIndex;
}
#if WITH_EDITOR
FText UComboActionGraphNode::GetDescription_Implementation() const
{
return LOCTEXT("NodeDesc", "Mountea Dialogue Graph Node");
}
FText UComboActionGraphNode::GetNodeCategory_Implementation() const
{
return LOCTEXT("NodeCategory", "Mountea Dialogue Tree Node");
}
FString UComboActionGraphNode::GetNodeDocumentationLink_Implementation() const
{
return TEXT("A link to the documentation for this plugin will go here.");
}
FText UComboActionGraphNode::GetNodeTooltipText_Implementation() const
{
return FText::Format(LOCTEXT("ComboActionGraphNode_FinalTooltip", "{0}\n\n{1}"), this->GetDefaultTooltipBody(), this->NodeTooltipText);
}
bool UComboActionGraphNode::CanCreateConnection(UComboActionGraphNode *Other, enum EEdGraphPinDirection Direction, FText &ErrorMessage)
{
if (Other == nullptr)
{
ErrorMessage = FText::FromString("Invalid Other Node!");
}
if (Other->GetMaxChildNodes() > -1 && Other->ChildNodes.Num() >= Other->GetMaxChildNodes())
{
const FString TextReturn =
FString(Other->GetNodeTitle().ToString()).
Append(": Cannot have more than ").Append(FString::FromInt(Other->GetMaxChildNodes())).Append(" Children Nodes!");
ErrorMessage = FText::FromString(TextReturn);
return false;
}
if (Direction == EEdGraphPinDirection::EGPD_Output)
{
// Fast checking for native classes
if ( AllowedInputClasses.Contains(Other->GetClass()) )
{
return true;
}
// Slower iterative checking for child classes
for (auto Itr : AllowedInputClasses)
{
if (Other->GetClass()->IsChildOf(Itr))
{
return true;
}
}
ErrorMessage = FText::FromString("Invalid Node Connection!");
return false;
}
return true;
}
bool UComboActionGraphNode::ValidateNode(TArray<FText> &ValidationsMessages, const bool RichFormat)
{
bool bResult = true;
if (this->ParentNodes.Num() == 0 && this->ChildNodes.Num() == 0)
{
bResult = false;
const FString RichTextReturn =
FString("* ")
.Append("<RichTextBlock.Bold>")
.Append(this->NodeTitle.ToString())
.Append("</>")
.Append(": This Node has no Connections!");
const FString TextReturn =
FString(this->NodeTitle.ToString())
.Append(": This Node has no Connections!");
ValidationsMessages.Add(FText::FromString(RichFormat ? RichTextReturn : TextReturn));
}
if (this->bAllowInputNodes && this->ParentNodes.Num() == 0)
{
bResult = false;
const FString RichTextReturn =
FString("* ")
.Append("<RichTextBlock.Bold>")
.Append(this->NodeTitle.ToString())
.Append("</>")
.Append(": This node requires inputs, however none are found!");
const FString TextReturn =
FString(this->NodeTitle.ToString())
.Append(": This node requires inputs, however none are found!");
ValidationsMessages.Add(FText::FromString(RichFormat ? RichTextReturn : TextReturn));
}
return bResult;
}
void UComboActionGraphNode::OnPasted()
{
this->NodeGUID = FGuid::NewGuid();
this->ParentNodes.Empty();
this->ChildNodes.Empty();
this->Edges.Empty();
}
FText UComboActionGraphNode::GetDefaultTooltipBody() const
{
//const FText Inherits = FText::Format(LOCTEXT("UComboActionGraphNode_InheritsTooltip", "Inherits Graph Decorators: {0}"), InheritsValue);
//const FText Implements = FText::Format(LOCTEXT("UComboActionGraphNode_ImplementsTooltip", "Implements Decorators: {0}"), ImplementsNumber);
return FText::Format(LOCTEXT("UComboActionGraphNode_BaseTooltip", "{0}\n\n{1}\n{2}"), this->NodeTypeName, this->NodeTypeName, this->NodeTypeName);
}
#endif
#undef LOCTEXT_NAMESPACE

View File

@ -0,0 +1,49 @@
// ©2023 Batty Bovine Productions, LLC. All Rights Reserved.
#include "Nodes/ComboActionGraphNode_ActionNode.h"
#include "Nodes/ComboActionGraphNode_StartNode.h"
#define LOCTEXT_NAMESPACE "ComboActionGraphNode_ActionNode"
UComboActionGraphNode_ActionNode::UComboActionGraphNode_ActionNode()
{
#if WITH_EDITORONLY_DATA
this->NodeTitle = LOCTEXT("ComboActionGraphNode_ActionNodeTitle", "Action Node");
this->NodeTypeName = LOCTEXT("ComboActionGraphNode_ActionNodeInternalTitle", "Action Node");
this->ContextMenuName = LOCTEXT("ComboActionGraphNode_ActionNodeContextMenuName", "Action Node");
this->NodeTooltipText = LOCTEXT("ComboActionGraphNode_ActionTooltip", "Action node is a node which contains combo actions based on inputs.");
#endif
this->AllowedInputClasses.Add(UComboActionGraphNode_StartNode::StaticClass());
this->AllowedInputClasses.Add(UComboActionGraphNode_ActionNode::StaticClass());
this->MaxChildNodes = -1;
}
void UComboActionGraphNode_ActionNode::PreProcessNode(const TScriptInterface<IComboActionGraphManagerInterface> &Manager)
{
Super::PreProcessNode(Manager);
}
void UComboActionGraphNode_ActionNode::ProcessNode(const TScriptInterface<IComboActionGraphManagerInterface> &Manager)
{
Super::ProcessNode(Manager);
}
#if WITH_EDITOR
FText UComboActionGraphNode_ActionNode::GetDescription_Implementation() const
{
return LOCTEXT("ComboActionGraphNode_ActionNodeDescription", "Action node is a node which contains combo actions based on inputs.");
}
FText UComboActionGraphNode_ActionNode::GetNodeCategory_Implementation() const
{
return LOCTEXT("ComboActionGraphNode_ActionNodeCategory", "Combo Action Branch Nodes");
}
#endif
#undef LOCTEXT_NAMESPACE

View File

@ -0,0 +1,181 @@
// ©2023 Batty Bovine Productions, LLC. All Rights Reserved.
#include "Nodes/ComboActionGraphNode_ActionNodeBase.h"
#include "ComboInputAssets.h"
#define LOCTEXT_NAMESPACE "ComboActionGraphNode_ActionNodeBase"
UComboActionGraphNode_ActionNodeBase::UComboActionGraphNode_ActionNodeBase()
{
#if WITH_EDITORONLY_DATA
this->NodeTitle = LOCTEXT("ComboActionGraphNode_ActionNodeBaseTitle", "Action Node Base");
this->NodeTypeName = LOCTEXT("ComboActionGraphNode_ActionNodeBaseInternalTitle", "Action Node Base");
this->ContextMenuName = LOCTEXT("ComboActionGraphNode_ActionNodeBaseContextMenu", "Action Node");
this->NodeTooltipText = LOCTEXT("ComboActionGraphNode_BaseTooltip", "* Abstract class, should not appear in graph editor.\n* Enhances 'ComboActionGraphNode' Base class with action data.");
#endif
}
void UComboActionGraphNode_ActionNodeBase::ProcessNode(const TScriptInterface<IComboActionGraphManagerInterface> &Manager)
{
Super::ProcessNode(Manager);
}
void UComboActionGraphNode_ActionNodeBase::PreProcessNode(const TScriptInterface<IComboActionGraphManagerInterface> &Manager)
{
Super::PreProcessNode(Manager);
}
bool UComboActionGraphNode_ActionNodeBase::ValidateNodeRuntime_Implementation() const
{
if (!this->ComboInput)
{
return false;
}
if (this->TriggerEvent == EComboActionTriggerEvent::None)
{
return false;
}
if (!this->ComboAction)
{
return false;
}
if (this->MaxChildNodes > -1 && this->ChildNodes.Num() > this->MaxChildNodes)
{
return false;
}
return true;
}
#if WITH_EDITOR
bool UComboActionGraphNode_ActionNodeBase::ValidateNode(TArray<FText> &ValidationsMessages, const bool RichFormat)
{
bool bResult = Super::ValidateNode(ValidationsMessages, RichFormat);
if (!this->ComboInput)
{
bResult = false;
const FString RichTextReturn =
FString("* ").
Append("<RichTextBlock.Bold>").
Append(NodeTitle.ToString()).
Append("</>").
Append(": Does not reference a valid combo input!");
const FString TextReturn =
FString(NodeTitle.ToString()).
Append(": Does not reference a valid combo input!");
ValidationsMessages.Add(FText::FromString(RichFormat ? RichTextReturn : TextReturn));
}
if (this->TriggerEvent == EComboActionTriggerEvent::None)
{
bResult = false;
const FString RichTextReturn =
FString("* ").
Append("<RichTextBlock.Bold>").
Append(NodeTitle.ToString()).
Append("</>").
Append(": Does not reference a valid trigger event!");
const FString TextReturn =
FString(NodeTitle.ToString()).
Append(": Does not reference a valid trigger event!");
ValidationsMessages.Add(FText::FromString(RichFormat ? RichTextReturn : TextReturn));
}
if (!this->ComboAction)
{
bResult = false;
const FString RichTextReturn =
FString("* ").
Append("<RichTextBlock.Bold>").
Append(NodeTitle.ToString()).
Append("</>").
Append(": Does not reference a valid combo action!");
const FString TextReturn =
FString(NodeTitle.ToString()).
Append(": Does not reference a valid combo action!");
ValidationsMessages.Add(FText::FromString(RichFormat ? RichTextReturn : TextReturn));
}
if (this->MaxChildNodes > -1 && this->ChildNodes.Num() > this->MaxChildNodes)
{
const FString RichTextReturn = FString("* ")
.Append("<RichTextBlock.Bold>")
.Append(this->NodeTitle.ToString())
.Append("</>")
.Append(": Has more than ")
.Append("<RichTextBlock.Bold>")
.Append(FString::FromInt(this->MaxChildNodes))
.Append("</>")
.Append(" child nodes!");
const FString TextReturn = FString(this->NodeTitle.ToString())
.Append(": Has more than ").Append(FString::FromInt(this->MaxChildNodes)).Append(" child nodes!");
ValidationsMessages.Add(FText::FromString(RichFormat ? RichTextReturn : TextReturn));
}
return bResult;
}
void UComboActionGraphNode_ActionNodeBase::PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent)
{
Super::PostEditChangeProperty(PropertyChangedEvent);
if (PropertyChangedEvent.GetPropertyName() == GET_MEMBER_NAME_CHECKED(UComboActionGraphNode_ActionNodeBase, ComboInput))
{
this->Preview.Empty();
this->PreviewsUpdated.ExecuteIfBound();
}
if (PropertyChangedEvent.GetPropertyName() == GET_MEMBER_NAME_CHECKED(UComboActionGraphNode_ActionNodeBase, TriggerEvent))
{
this->UpdatePreviews();
this->PreviewsUpdated.ExecuteIfBound();
}
}
#if WITH_EDITOR
FLinearColor UComboActionGraphNode_ActionNodeBase::GetBackgroundColour() const
{
if (this->ComboAction && this->ComboAction->NodeColor != FLinearColor())
{
return this->ComboAction->NodeColor;
}
else if (this->ComboInput)
{
return this->ComboInput->NodeColor;
}
return Super::GetBackgroundColour();
}
#endif
FText UComboActionGraphNode_ActionNodeBase::GetDescription_Implementation() const
{
return LOCTEXT("ComboActionGraphNode_BaseDescription", "Action base node has no logic tied to itself.");
}
TArray<FText> UComboActionGraphNode_ActionNodeBase::GetPreviews() const
{
return TArray<FText>();
}
#endif
#undef LOCTEXT_NAMESPACE

View File

@ -0,0 +1,63 @@
// ©2023 Batty Bovine Productions, LLC. All Rights Reserved.
#include "Nodes/ComboActionGraphNode_StartNode.h"
#define LOCTEXT_NAMESPACE "ComboActionGraphNode_StartNode"
UComboActionGraphNode_StartNode::UComboActionGraphNode_StartNode()
{
#if WITH_EDITORONLY_DATA
this->bAllowInputNodes = false;
this->NodeTitle = LOCTEXT("ComboActionGraphNode_StartNodeTitle", "Start Action");
this->NodeTypeName = LOCTEXT("ComboActionGraphNode_StartNodeInternalTitle", "Start Action");
this->ContextMenuName = LOCTEXT("ComboActionGraphNode_StartNodeContextMenuName", "Start Action");
this->BackgroundColor = FLinearColor(0.0f, 1.0f, 0.0f, 1.0f);
this->bAllowCopy = false;
this->bAllowCut = false;
this->bAllowPaste = false;
this->bAllowDelete = false;
this->bAllowManualCreate = false;
this->NodeTooltipText = LOCTEXT("ComboActionGraphNode_CompleteTooltip", "* This Node will be added to the graph automatically.\n* This Node cannot be created manually.\n* This Node cannot be deleted.\n* Does not implement any logic.");
#endif
this->MaxChildNodes = -1;
}
#if WITH_EDITOR
FText UComboActionGraphNode_StartNode::GetDescription_Implementation() const
{
return LOCTEXT("ComboActionGraphNode_StartNodeDescription", "Start node is automatically placed and cannot be deleted.");
}
bool UComboActionGraphNode_StartNode::ValidateNode(TArray<FText>& ValidationsMessages, const bool RichFormat)
{
bool bResult = Super::ValidateNode(ValidationsMessages, RichFormat);
if (this->ChildNodes.Num() == 0)
{
bResult = false;
const FString RichTextReturn =
FString("* ")
.Append("<RichTextBlock.Bold>")
.Append(this->NodeTitle.ToString())
.Append("</>")
.Append(": Does not have any child nodes!");
const FString TextReturn =
FString(this->NodeTitle.ToString())
.Append(": Does not have any child nodes!");
ValidationsMessages.Add(FText::FromString(RichFormat ? RichTextReturn : TextReturn));
}
return bResult;
}
#endif
#undef LOCTEXT_NAMESPACE

View File

@ -0,0 +1,144 @@
// ©2023 Batty Bovine Productions, LLC. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "Nodes/ComboActionGraphEdge.h"
#include "ComboActionGraph.generated.h"
DECLARE_LOG_CATEGORY_EXTERN(LogComboActionGraph, Log, All);
/**
* Combo Action graph.
*
* Can be manually created from Content Browser, using Combo Action category.
* Comes with a node editor, which provides easy to follow visual way to create combo strings.
*/
UCLASS(BlueprintType, ClassGroup=("Combo Input|Action"), DisplayName="Combo Action Tree", HideCategories=("Hidden", "Private", "Base"), AutoExpandCategories=("Combo Input", "Action"))
class COMBOINPUT_API UComboActionGraph : public UObject
{
GENERATED_BODY()
public:
UComboActionGraph();
#pragma region Variables
protected:
/**
* Unique GUID for this graph.
* Can be used for debugging and tracing purposes.
*/
UPROPERTY(BlueprintReadOnly, Category="Combo Input")
FGuid GraphGUID;
public:
/**
* Pointer to the starting node of the dialogue graph.
*/
UPROPERTY(BlueprintReadOnly, Category="Combo Input|Action")
class UComboActionGraphNode *StartNode = nullptr;
/**
* The class of the action node represented by this instance.
*/
UPROPERTY(BlueprintReadOnly, Category="Combo Input|Action")
TSubclassOf<UComboActionGraphNode> NodeType;
/**
* The class of the action edge represented by this instance.
*/
UPROPERTY(BlueprintReadOnly, Category="Combo Input|Action")
TSubclassOf<UComboActionGraphEdge> EdgeType;
/**
* An array of root nodes in the action graph. These are the nodes that do not have any incoming connections.
*/
UPROPERTY(BlueprintReadOnly, Category="Combo Input|Action")
TArray<UComboActionGraphNode*> RootNodes;
/**
* Array containing all the nodes in the graph, including both root nodes and child nodes.
*/
UPROPERTY(BlueprintReadOnly, Category="Combo Input|Action")
TArray<UComboActionGraphNode*> AllNodes;
/**
* Set containing actions to be unlocked if their associated nodes are currently locked.
*/
UPROPERTY(BlueprintReadWrite, Category="Combo Input|Action")
TSet<const class UComboAction*> UnlockedActions;
// Flag indicating whether an edge is enabled
UPROPERTY(BlueprintReadOnly, Category="Combo Input|Action")
bool bEdgeEnabled;
#pragma endregion
#pragma region Functions
public:
/**
* Returns the GUID of the graph.
*
* @return The GUID of the graph.
*/
UFUNCTION(BlueprintCallable, Category="Combo Input|Action")
FGuid GetGraphGUID() const { return this->GraphGUID; }
/**
* Returns an array containing all nodes in the dialogue graph.
* @return An array of all nodes in the dialogue graph.
*/
UFUNCTION(BlueprintCallable, Category="Combo Input|Action")
TArray<UComboActionGraphNode*> GetAllNodes() const { return this->AllNodes; }
UFUNCTION(BlueprintCallable, Category="Combo Input|Action")
TArray<UComboActionGraphNode*> GetRootNodes() const { return this->RootNodes; }
/**
* Returns the first node in the graph.
*
* @return The start node of this graph.
*/
UFUNCTION(BlueprintCallable, Category="Combo Input|Action")
UComboActionGraphNode *GetStartNode() const { return this->StartNode; }
public:
void CreateGraph();
void ClearGraph();
FORCEINLINE bool IsEdgeEnabled() const { return bEdgeEnabled; }
virtual void PostInitProperties() override;
#pragma endregion
#if WITH_EDITORONLY_DATA
public:
UPROPERTY()
TObjectPtr<class UEdGraph> EdGraph;
UPROPERTY(BlueprintReadOnly, Category="Combo Input|Action|Editor")
bool bCanRenameNode;
#endif
#if WITH_EDITOR
virtual bool ValidateGraph(TArray<FText> &ValidationErrors, bool RichTextFormat);
virtual EDataValidationResult IsDataValid(TArray<FText> &ValidationErrors) override;
public:
// Construct and initialize a node within this Dialogue.
template<class T>
T* ConstructActionNode(TSubclassOf<class UComboActionGraphNode> DialogueNodeClass = T::StaticClass())
{
// Set flag to be transactional so it registers with undo system
T *ActionNode = NewObject<T>(this, DialogueNodeClass, NAME_None, EObjectFlags::RF_Transactional);
ActionNode->OnCreatedInEditor();
return ActionNode;
}
#endif
};

View File

@ -9,24 +9,6 @@
#include "ComboInputAssets.generated.h"
/**
* Struct that is used as the value for a combo branch. ComboAction is the action to be
* executed, and NextNode is the node that will be activated next in the sequence.
*/
USTRUCT(BlueprintType)
struct COMBOINPUT_API FComboSequenceAction
{
GENERATED_BODY()
public:
// Action to perform when the associated combo sequence node is activated.
UPROPERTY(BlueprintReadOnly, EditAnywhere)
TObjectPtr<const class UComboAction> ComboAction;
// Sequence node to switch to once this action is complete.
UPROPERTY(BlueprintReadOnly, EditAnywhere)
TObjectPtr<const class UComboSequenceNode> NextNode;
};
/**
* An action that can be executed as part of a combo sequence. This is essentially a
* representation of an attack, and can be sent to the Animation Graph to play an
@ -41,21 +23,10 @@ public:
// Human-readable name of this combo action.
UPROPERTY(BlueprintReadOnly, EditAnywhere)
FName ActionName;
};
/**
* This represents a node in the combo graph, with each key in the ComboBranch being
* an input this node can react to, and each value containing the action to be executed
* next, and the node to activate after the action is complete.
*/
UCLASS(BlueprintType)
class COMBOINPUT_API UComboSequenceNode : public UDataAsset
{
GENERATED_BODY()
public:
// Sets the colour of the node this action is tied to. Will override any other colours.
UPROPERTY(BlueprintReadOnly, EditAnywhere)
TMap<const class UComboInputAsset *, struct FComboSequenceAction> ComboBranch;
FLinearColor NodeColor;
};
/**
@ -121,10 +92,7 @@ public:
UPROPERTY(BlueprintReadWrite, EditAnywhere)
TSet<const class UInputAction*> ActionGroup;
// Combo inputs that should be prevented from occurring during this
// action. These will be locked when the action is broadcast, and
// should be unlocked by the receiving actor by sending an unlock
// event.
UPROPERTY(BlueprintReadWrite, EditAnywhere)
TSet<TObjectPtr<const class UComboInputAsset>> LockedComboInputs;
// Sets the colour of the node this action is tied to. Can be overridden by UComboAction.
UPROPERTY(BlueprintReadOnly, EditAnywhere)
FLinearColor NodeColor;
};

View File

@ -42,7 +42,10 @@ class COMBOINPUT_API UComboManagerComponent : public UActorComponent
public:
UComboManagerComponent();
virtual void BeginPlay() override;
virtual void InitializeComponent() override;
UFUNCTION(BlueprintCallable)
void SetComboGraph(const class UComboActionGraph *Graph);
UFUNCTION(BlueprintCallable)
void HandleComboInput(const class UComboInputAsset *Input, const EComboActionTriggerEvent &TriggerEvent);
@ -55,9 +58,12 @@ public:
this->ComboActionEventBindings.Emplace(Key.Key, MoveTemp(Delegate));
}
// Recursively search the graph for all inputs used by the graph.
TSet<const UComboInputAsset *> &FindAllUsedInputs();
protected:
UPROPERTY(BlueprintReadOnly, EditDefaultsOnly)
TObjectPtr<const class UComboSequenceNode> DefaultStartNode;
TObjectPtr<const class UComboActionGraph> ComboGraph;
// This input will be recognised as an offset input, which cancels a node transition
// if activated early enough in the transition. This locks the current place in the
@ -81,22 +87,28 @@ private:
void ActivateComboAction(const class UComboInputAsset *Input);
void ReleaseComboAction(const class UComboInputAsset *Input);
void BeginNodeTransition(const class UComboSequenceNode *NextNode);
void BeginNodeTransition(const class UComboActionGraphNode *NextNode);
void FinishTransition();
void ResetCombo();
const UComboActionGraphNode *FindActiveNodeData(const UComboActionGraphNode *CurrentNode, const UComboInputAsset *Input, const EComboActionTriggerEvent TriggerEvent, const UComboAction *&ComboAction);
void FindAllUsedInputs_RecurseGraph(const UComboActionGraphNode *CurrentNode, TSet<const UComboInputAsset *> &FoundInputs);
void BroadcastDelegates(const class UComboAction *ComboAction, const EComboActionTriggerEvent &TriggerEvent);
void DEBUG__UnlockAction(TObjectPtr<const class UComboInputAsset> Unlock);
TObjectPtr<const class UComboSequenceNode> ActiveNode = nullptr;
TObjectPtr<const class UComboSequenceNode> PreviousNode = nullptr;
TObjectPtr<const class UComboActionGraphNode> ActiveNode = nullptr;
TObjectPtr<const class UComboActionGraphNode> PreviousNode = nullptr;
TObjectPtr<const class UComboAction> LastComboAction = nullptr;
TMap<FName, FComboActionHandlerDynamicSignature> ComboActionEventBindings;
TObjectPtr<class UInputBufferComponent> AttachedInputBuffer;
// Cache of combo inputs found in the current graph.
TSet<const UComboInputAsset *> FoundInputsCache;
FTimerHandle FinishTransitionTimer;
FTimerHandle DEBUG__ResetComboTimer;

View File

@ -3,11 +3,11 @@
#pragma once
#include "CoreMinimal.h"
#include "EnhancedInputSubsystems.h"
#include "EnhancedInputComponent.h"
#include "InputBufferLocalPlayerSubsystem.generated.h"
#include "InputBufferComponent.generated.h"
DECLARE_LOG_CATEGORY_EXTERN(LogInputBufferLocalPlayerSubsystem, Log, All);
DECLARE_LOG_CATEGORY_EXTERN(LogInputBufferComponent, Log, All);
DECLARE_DELEGATE_TwoParams(FNewComboInput, const class UComboInputAsset*, const EComboActionTriggerEvent &);
@ -16,15 +16,16 @@ DECLARE_DELEGATE_TwoParams(FNewComboInput, const class UComboInputAsset*, const
* Subsystem that handles input buffering, and passing the resulting actions to the player's ComboManagerComponent.
*/
UCLASS()
class COMBOINPUT_API UInputBufferLocalPlayerSubsystem : public UEnhancedInputLocalPlayerSubsystem
class COMBOINPUT_API UInputBufferComponent : public UEnhancedInputComponent
{
GENERATED_BODY()
public:
virtual void Initialize(FSubsystemCollectionBase &Collection) override;
void AttachComboManager(class UComboManagerComponent *ComboManager, class UEnhancedInputComponent *InputComponent);
UInputBufferComponent();
virtual void InitializeComponent() override;
UFUNCTION(BlueprintCallable)
void LockComboInput(const class UComboInputAsset *Input);
UFUNCTION(BlueprintCallable)
void UnlockComboInput(const class UComboInputAsset *Unlocked);
@ -49,7 +50,11 @@ private:
// Set of currently locked actions; will not be activated until an unlock signal is received.
TSet<TObjectPtr<const class UComboInputAsset>> LockedComboInputs;
TSet<const class UInputAction*> MostRecentActions;
// A local backup of the global setting containing the list of combo inputs to respond to.
TSet<TObjectPtr<const UComboInputAsset>> ComboInputList;
TSet<const class UInputAction*> MultiPressActions;
TSet<const class UInputAction*> UnhandledActions;
TSet<const class UInputAction*> ExpiringActions;
FNewComboInput OnNewComboInput;

View File

@ -4,24 +4,18 @@
#include "Engine/DeveloperSettingsBackedByCVars.h"
#include "InputBufferSubsystemGlobalSettings.generated.h"
#include "InputBufferGlobalSettings.generated.h"
/**
* Global settings for the input buffer subsystem
* Global settings for the input buffer
*/
UCLASS(Config=Game, defaultconfig, meta=(DisplayName="Input Buffer Subsystem"))
class COMBOINPUT_API UInputBufferSubsystemGlobalSettings : public UDeveloperSettingsBackedByCVars
UCLASS(Config=Game, defaultconfig, meta=(DisplayName="Input Buffer"))
class COMBOINPUT_API UInputBufferGlobalSettings : public UDeveloperSettingsBackedByCVars
{
GENERATED_BODY()
public:
// List of possible combo inputs that can be taken. A combo input is selected from this list
// either if an action is made while the current combo is inactive, or when the previous
// action expires.
UPROPERTY(Config, BlueprintReadOnly, EditDefaultsOnly)
TSet<TSoftObjectPtr<const class UComboInputAsset>> ComboActions;
// Length of time after releasing an input to keep the associated combo action buffered before clearing it.
UPROPERTY(Config, BlueprintReadOnly, EditDefaultsOnly, meta=(UIMin="0.0", UIMax="0.5", ClampMin="0.0", ClampMax="0.5"))
float InputReleaseExpirationTimerLength = 0.15f;

View File

@ -0,0 +1,42 @@
// ©2023 Batty Bovine Productions, LLC. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "ComboActionGraphEdge.generated.h"
/**
* Combo Action edges.
*
* Currently those edges are just connecting objects with no advanced logic.
*/
UCLASS(ClassGroup=("Combo Input|Action"), NotBlueprintType)
class COMBOINPUT_API UComboActionGraphEdge : public UObject
{
GENERATED_BODY()
public:
UFUNCTION(BlueprintCallable, Category="Combo Input|Action")
void SetGraph(class UComboActionGraph *InGraph) { this->Graph = InGraph; }
UFUNCTION(BlueprintCallable, Category="Combo Input|Action")
void SetStartNode(class UComboActionGraphNode *InNode) { this->StartNode = InNode; }
UFUNCTION(BlueprintCallable, Category="Combo Input|Action")
void SetEndNode(class UComboActionGraphNode *InNode) { this->EndNode = InNode; }
UFUNCTION(BlueprintPure, Category="Combo Input|Action")
class UComboActionGraph *GetGraph() const { return this->Graph; }
UFUNCTION(BlueprintPure, Category="Combo Input|Action")
class UComboActionGraphNode *GetStartNode() const { return this->StartNode; }
UFUNCTION(BlueprintPure, Category="Combo Input|Action")
class UComboActionGraphNode *GetEndNode() const { return this->EndNode; }
private:
UPROPERTY(VisibleAnywhere, Category="Combo Input|Action")
class UComboActionGraph *Graph = nullptr;
UPROPERTY(BlueprintReadOnly, Category="Combo Input|Action", meta=(AllowPrivateAccess="true"))
class UComboActionGraphNode *StartNode = nullptr;
UPROPERTY(BlueprintReadOnly, Category="Combo Input|Action", meta=(AllowPrivateAccess="true"))
class UComboActionGraphNode *EndNode = nullptr;
};

View File

@ -0,0 +1,337 @@
// ©2023 Batty Bovine Productions, LLC. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "ComboActionGraphNode.generated.h"
DECLARE_LOG_CATEGORY_EXTERN(LogComboActionGraphNode, Log, All);
/**
* Combo Action graph Node abstract base class.
*
* Provides generic functionality to be enhanced or overriden by Child Classes.
* Does come with ability to define Colours, Name, Description and Title.
* Contains information about Parent and Children Nodes.
*/
UCLASS(Abstract, BlueprintType, ClassGroup=("Combo Input|Action"), AutoExpandCategories=("Combo Input", "Action", "Combo Input|Action"), HideCategories=("Private"))
class COMBOINPUT_API UComboActionGraphNode : public UObject
{
GENERATED_BODY()
public:
UComboActionGraphNode();
#pragma region Variables
#pragma region ReadOnly
public:
/**
* Array of parent nodes for the current active node in the combo string.
* Parent nodes are nodes that have a directed edge pointing to the current active node.
*/
UPROPERTY(BlueprintReadOnly, Category = "Private")
TArray<class UComboActionGraphNode *> ParentNodes;
/**
* The array of child nodes of the current dialogue node.
*/
UPROPERTY(BlueprintReadOnly, Category = "Private")
TArray<class UComboActionGraphNode *> ChildNodes;
/**
* Map of edges connecting this Node in the Combo Action graph.
* The key of the map is the source node, and the value is the edge connecting it to its target node.
* Can be used to traverse the graph, get information about node connections...
*/
UPROPERTY(BlueprintReadOnly, Category="Private")
TMap<class UComboActionGraphNode*, class UComboActionGraphEdge*> Edges;
/**
* Pointer to the parent dialogue graph of this node.
*/
UPROPERTY(BlueprintReadOnly, Category="Private", meta=(DisplayThumbnail=false))
TObjectPtr<class UComboActionGraph> Graph;
/**
* Temporary NodeIndex.
* This variable will be deleted.
*/
UPROPERTY(BlueprintReadOnly, Category="Private")
int32 NodeIndex = INDEX_NONE;
protected:
/**
* The unique identifier for this Dialogue Graph Node.
* This is used to differentiate between nodes, and must be unique within the graph.
* Can be used for debugging and tracing purposes.
*/
UPROPERTY(BlueprintReadOnly, Category="Private")
FGuid NodeGUID;
/**
* The world that owns this Dialogue Graph Node.
* This is the world in which this Dialogue Graph Node is currently running.
* Can be used for accessing world-related functionality.
*/
UPROPERTY(BlueprintReadOnly, Category="Private")
TObjectPtr<UWorld> OwningWorld;
#pragma endregion
#pragma region Editable
public:
/**
* Whether the node is enabled by default. Disabled nodes will be ignored by the
* combo manager until they are enabled again programmatically. This allows actions
* to be unlockable during gameplay.
*/
UPROPERTY(BlueprintReadOnly, EditDefaultsOnly, Category="Combo Input|Action")
uint8 bEnabled : 1;
/**
* The maximum number of children nodes that this node can have.
* If this value is -1, then there is no limit on the number of children nodes.
* Can be used to enforce a maximum number of connections for certain types of nodes.
*/
UPROPERTY(BlueprintReadOnly, EditDefaultsOnly, Category="Combo Input|Action")
int32 MaxChildNodes = -1;
/**
* The array of allowed input classes for this Dialogue Node.
* Only nodes with classes from this array can be connected as inputs to this node.
* Can be used to restrict the types of inputs this node can accept.
*/
UPROPERTY(BlueprintReadOnly, Category="Combo Input|Action")
TArray<TSubclassOf<class UComboActionGraphNode>> AllowedInputClasses;
#pragma endregion
#pragma endregion
#pragma region Functions
public:
/**
* Initializes the node with the given world.
*
* @param InWorld The world to use for initialization.
*/
UFUNCTION(BlueprintNativeEvent, Category = "Combo Input|Action")
void InitializeNode(UWorld *InWorld);
virtual void InitializeNode_Implementation(UWorld *InWorld);
virtual void PreProcessNode(const TScriptInterface<class IComboActionGraphManagerInterface> &Manager){}
virtual void ProcessNode(const TScriptInterface<class IComboActionGraphManagerInterface> &Manager);
/**
* Returns how many Children Nodes this Node allows to have.
* -1 means no limits.
*
* @return Max child nodes
*/
UFUNCTION(BlueprintCallable, BlueprintPure, Category="Combo Input|Action")
int32 GetMaxChildNodes() const { return this->MaxChildNodes; }
/**
* Gets the index of the node within the dialogue graph.
*
* @return The index of the node.
*/
UFUNCTION(BlueprintCallable, BlueprintPure, Category="Combo Input|Action")
FORCEINLINE int32 GetNodeIndex() const { return NodeIndex; }
/**
* Sets the index of this dialogue node in the dialogue graph.
*
* @param NewIndex The new index to set.
*/
void SetNodeIndex(const int32 NewIndex);
/**
* Gets the GUID of the node.
*
* @return The GUID of the node.
*/
UFUNCTION(BlueprintCallable, BlueprintPure, Category="Combo Input|Action")
FORCEINLINE FGuid GetNodeGUID() const { return NodeGUID; }
/**
* Gets the owning Graph of the node.
* Might be null
*
* @return The owning Graph of the node.
*/
UFUNCTION(BlueprintCallable, Category = "Combo Input|Action")
class UComboActionGraph *GetGraph() const { return this->Graph; }
/**
* Serves purpose of validating Node before Dialogue gets Started.
* Any broken Node results in non-starting Dialogue to avoid crashes.
*/
UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Combo Input|Action")
bool ValidateNodeRuntime() const;
virtual bool ValidateNodeRuntime_Implementation() const { return true; }
public:
FORCEINLINE ULevel* GetLevel() const { return GetTypedOuter<ULevel>(); }
/**
* Provides a way to update Node's owning World.
* Useful for Loading sub-levels.
*/
UFUNCTION(BlueprintCallable, Category="Combo Input|Action")
virtual void SetNewWorld(UWorld* NewWorld);
virtual UWorld* GetWorld() const override
{
if (OwningWorld) return OwningWorld;
// CDO objects do not belong to a world
// If the actors outer is destroyed or unreachable we are shutting down and the world should be nullptr
if (
!HasAnyFlags(RF_ClassDefaultObject) && ensureMsgf(GetOuter(), TEXT("Actor: %s has a null OuterPrivate in AActor::GetWorld()"), *GetFullName())
&& !GetOuter()->HasAnyFlags(RF_BeginDestroyed) && !GetOuter()->IsUnreachable()
)
{
if (ULevel* Level = GetLevel())
{
return Level->OwningWorld;
}
}
return nullptr;
}
#pragma endregion
#if WITH_EDITORONLY_DATA
// Defines whether this Node type allows inputs
UPROPERTY(BlueprintReadOnly, Category = "Editor")
bool bAllowInputNodes;
// Defines whether this Node type allows outputs
UPROPERTY(BlueprintReadOnly, Category = "Editor")
bool bAllowOutputNodes;
// Defines whether this Node can be copied
UPROPERTY(BlueprintReadOnly, Category = "Editor")
bool bAllowCopy;
// Defines whether this Node can be cut
UPROPERTY(BlueprintReadOnly, Category = "Editor")
bool bAllowCut;
// Defines whether this Node can be pasted
UPROPERTY(BlueprintReadOnly, Category = "Editor")
bool bAllowPaste;
// Defines whether this Node can be deleted
UPROPERTY(BlueprintReadOnly, Category = "Editor")
bool bAllowDelete;
// Defines whether this Node can be manually created
UPROPERTY(BlueprintReadOnly, Category = "Editor")
bool bAllowManualCreate;
// Display title of the Node
UPROPERTY(BlueprintReadOnly, Category = "Editor")
FText NodeTitle;
// Display name of the Node menu category
UPROPERTY(BlueprintReadOnly, Category = "Editor")
FText ContextMenuName;
// List of compatible graph types
UPROPERTY(BlueprintReadOnly, Category = "Editor")
TSubclassOf<UObject> CompatibleGraphType;
// Defines background colour of this Node
UPROPERTY(BlueprintReadOnly, EditDefaultsOnly, Category = "Editor")
FLinearColor BackgroundColor;
// Contains Node Tooltip text
UPROPERTY(BlueprintReadOnly, Category = "Editor")
FText NodeTooltipText;
// User friendly node type name
UPROPERTY(BlueprintReadOnly, Category = "Editor")
FText NodeTypeName;
#endif
#if WITH_EDITOR
/**
* Returns the tooltip text for this graph node.
*
* @return The tooltip text for this node.
*/
UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category = "Combo Input|Action", meta=(DevelopmentOnly=true))
FText GetNodeTooltipText() const;
virtual FText GetNodeTooltipText_Implementation() const;
/**
* Returns the Title text for this graph node.
*
* @return The Title text for this node.
*/
UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category = "Combo Input|Action", meta=(DevelopmentOnly=true))
FText GetNodeTitle() const;
virtual FText GetNodeTitle_Implementation() const { return this->NodeTitle; }
/**
* Returns the Description text for this graph node.
*
* @return The Description text for this node.
*/
UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category = "Combo Input|Action", meta=(DevelopmentOnly=true))
FText GetDescription() const;
virtual FText GetDescription_Implementation() const;
/**
* Returns the Node Category text for this graph node.
*
* @return The Node Category text for this node.
*/
UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category = "Combo Input|Action", meta=(DevelopmentOnly=true))
FText GetNodeCategory() const;
virtual FText GetNodeCategory_Implementation() const;
/**
* Returns the Documentation Link for this graph node.
*
* @return The Documentation Link for this node.
*/
UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category = "Combo Input|Action", meta=(DevelopmentOnly=true))
FString GetNodeDocumentationLink() const;
virtual FString GetNodeDocumentationLink_Implementation() const;
/**
* Returns the background colour for this graph node.
*
* @return The background colour for this node.
*/
virtual FLinearColor GetBackgroundColour() const { return this->BackgroundColor; }
FText GetInternalName() const { return this->NodeTypeName; }
// Allows setting up the Node Title
virtual void SetNodeTitle(const FText &NewTitle) { this->NodeTitle = NewTitle; }
// Allows advanced filtering if Node can be connected from other Node
virtual bool CanCreateConnection(UComboActionGraphNode *Other, enum EEdGraphPinDirection Direction, FText& ErrorMessage);
// Validation function responsible for generating user friendly validation messages
virtual bool ValidateNode(TArray<FText> &ValidationsMessages, const bool RichFormat);
// Once Node is pasted, this function is called
virtual void OnPasted();
// Generates default Tooltip body text used for all Nodes
UFUNCTION(BlueprintPure, Category="Combo Input|Action", meta=(DevelopmentOnly=true))
FText GetDefaultTooltipBody() const;
virtual void OnCreatedInEditor() {};
#endif
};

View File

@ -0,0 +1,33 @@
// ©2023 Batty Bovine Productions, LLC. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "Nodes/ComboActionGraphNode_ActionNodeBase.h"
#include "UObject/Object.h"
#include "ComboActionGraphNode_ActionNode.generated.h"
/**
* Combo Action node.
*
* This Node represents Player's combo actions.
*/
UCLASS(ClassGroup=("Combo Input|Action"), DisplayName="Action Node", meta=(ToolTip="Combo Action Tree: Action Node"))
class COMBOINPUT_API UComboActionGraphNode_ActionNode : public UComboActionGraphNode_ActionNodeBase
{
GENERATED_BODY()
UComboActionGraphNode_ActionNode();
public:
virtual void PreProcessNode(const TScriptInterface<class IComboActionGraphManagerInterface> &Manager) override;
virtual void ProcessNode(const TScriptInterface<class IComboActionGraphManagerInterface> &Manager) override;
#if WITH_EDITOR
virtual FText GetDescription_Implementation() const override;
virtual FText GetNodeCategory_Implementation() const override;
#endif
};

View File

@ -0,0 +1,87 @@
// ©2023 Batty Bovine Productions, LLC. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "ComboInputTriggers.h"
#include "Engine/DataTable.h"
#include "Nodes/ComboActionGraphNode.h"
#include "UObject/Object.h"
#include "ComboActionGraphNode_ActionNodeBase.generated.h"
/**
* Combo Action Graph Node abstract Base class.
*
* Enhances 'ComboActionGraphNode' Base class with action data.
*/
UCLASS(Abstract, ClassGroup=("Combo Input|Action"), AutoExpandCategories=("Combo Input", "Action", "Combo Input|Action"))
class COMBOINPUT_API UComboActionGraphNode_ActionNodeBase : public UComboActionGraphNode
{
GENERATED_BODY()
public:
UComboActionGraphNode_ActionNodeBase();
virtual void ProcessNode(const TScriptInterface<class IComboActionGraphManagerInterface> &Manager) override;
virtual void PreProcessNode(const TScriptInterface<class IComboActionGraphManagerInterface> &Manager) override;
UFUNCTION(BlueprintCallable, BlueprintPure, Category="Combo Input|Action")
class UComboInputAsset *GetComboInput() const { return this->ComboInput; }
UFUNCTION(BlueprintCallable, BlueprintPure, Category="Combo Input|Action")
enum EComboActionTriggerEvent GetTriggerEvent() const { return this->TriggerEvent; }
UFUNCTION(BlueprintCallable, BlueprintPure, Category="Combo Input|Action")
class UComboAction *GetComboAction() const { return this->ComboAction; }
virtual bool ValidateNodeRuntime_Implementation() const override;
#if WITH_EDITOR
virtual FLinearColor GetBackgroundColour() const override;
#endif
#if WITH_EDITORONLY_DATA
/**
* Shows read-only Texts with localization of selected row.
*/
UPROPERTY(Transient, VisibleAnywhere, Category="Base", meta=(MultiLine=true, ShowOnlyInnerProperties))
TArray<FText> Preview;
FSimpleDelegate PreviewsUpdated;
#endif
protected:
// Combo input to respond to in order to reach this action.
UPROPERTY(BlueprintReadOnly, EditAnywhere, Category="Combo Input|Action")
TObjectPtr<class UComboInputAsset> ComboInput;
// Trigger event to respond to in order to reach this action.
UPROPERTY(BlueprintReadOnly, EditAnywhere, Category="Combo Input|Action")
EComboActionTriggerEvent TriggerEvent = EComboActionTriggerEvent::Activated;
// Combo action to fire upon this node's activation.
UPROPERTY(BlueprintReadOnly, EditAnywhere, Category = "Combo Input|Action")
TObjectPtr<class UComboAction> ComboAction;
#if WITH_EDITOR
protected:
virtual bool ValidateNode(TArray<FText> &ValidationMessages, const bool RichFormat) override;
virtual void PostEditChangeProperty(FPropertyChangedEvent &PropertyChangedEvent) override;
virtual FText GetDescription_Implementation() const override;
public:
TArray<FText> GetPreviews() const;
#endif
#if WITH_EDITORONLY_DATA
public:
virtual void UpdatePreviews() { this->Preview = this->GetPreviews(); }
#endif
};

View File

@ -0,0 +1,35 @@
// ©2023 Batty Bovine Productions, LLC. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "Nodes/ComboActionGraphNode.h"
#include "UObject/Object.h"
#include "ComboActionGraphNode_StartNode.generated.h"
/**
* Combo Action Start Node.
*
* This Node will be added to the graph automatically when created.
* This node cannot be created manually.
* This node cannot be deleted.
* Does not implement any logic.
*/
UCLASS(ClassGroup=("Combo Input|Action"), DisplayName="Start Node", meta=(ToolTip="Combo Action Tree: Start Node"))
class COMBOINPUT_API UComboActionGraphNode_StartNode : public UComboActionGraphNode
{
GENERATED_BODY()
public:
UComboActionGraphNode_StartNode();
#if WITH_EDITOR
virtual FText GetDescription_Implementation() const override;
virtual bool ValidateNode(TArray<FText>& ValidationsMessages, const bool RichFormat);
#endif
};

View File

@ -25,9 +25,19 @@ public class ComboInputEditor : ModuleRules
{
"Core",
"CoreUObject",
"Engine",
"UnrealEd",
"ApplicationCore",
"AssetTools",
"BlueprintGraph",
"DeveloperSettings",
"GraphEditor",
"InputCore",
"Projects",
"Slate",
"SlateCore",
"Projects"
"ToolMenus",
}
);

View File

@ -0,0 +1,437 @@
// ©2023 Batty Bovine Productions, LLC. All Rights Reserved.
#include "ComboActionGraphSchema.h"
#include "ComboActionGraph.h"
//#include "FConnectionDrawingPolicy_ComboActionGraph.h"
#include "GraphEditorActions.h"
#include "Ed/EdComboActionGraphEdge.h"
#include "Ed/EdComboActionGraphNode.h"
#include "Framework/Commands/GenericCommands.h"
#include "Nodes/ComboActionGraphEdge.h"
#include "Nodes/ComboActionGraphNode.h"
#define LOCTEXT_NAMESPACE "ComboActionGraph"
int32 UComboActionGraphSchema::CurrentCacheRefreshID = 0;
UEdGraphNode *FAssetSchemaAction_ComboActionGraphSchema_NewNode::PerformAction(UEdGraph *ParentGraph, UEdGraphPin *FromPin, const FVector2D Location, bool bSelectNewNode)
{
UEdGraphNode *ResultNode = nullptr;
if (this->NodeTemplate != nullptr)
{
const FScopedTransaction Transaction(LOCTEXT("ComboActionGraphEditorNewNode", "Combo Action Graph Editor: New Node"));
ParentGraph->Modify();
if (FromPin != nullptr)
FromPin->Modify();
this->NodeTemplate->Rename(nullptr, ParentGraph);
ParentGraph->AddNode(this->NodeTemplate, true, bSelectNewNode);
this->NodeTemplate->CreateNewGuid();
this->NodeTemplate->PostPlacedNewNode();
this->NodeTemplate->AllocateDefaultPins();
this->NodeTemplate->AutowireNewNode(FromPin);
this->NodeTemplate->NodePosX = Location.X;
this->NodeTemplate->NodePosY = Location.Y;
this->NodeTemplate->ComboActionGraphNode->SetFlags(EObjectFlags::RF_Transactional);
this->NodeTemplate->SetFlags(EObjectFlags::RF_Transactional);
ResultNode = this->NodeTemplate;
}
return ResultNode;
}
void FAssetSchemaAction_ComboActionGraphSchema_NewNode::AddReferencedObjects(FReferenceCollector &Collector)
{
FEdGraphSchemaAction::AddReferencedObjects(Collector);
Collector.AddReferencedObject(this->NodeTemplate);
}
UEdGraphNode *FAssetSchemaAction_ComboActionGraphSchema_NewEdge::PerformAction(UEdGraph *ParentGraph, UEdGraphPin *FromPin, const FVector2D Location, bool bSelectNewNode)
{
UEdGraphNode *ResultNode = nullptr;
if (this->NodeTemplate != nullptr)
{
const FScopedTransaction Transaction(LOCTEXT("ComboActionGraphEditorNewEdge", "Combo Action Graph Editor: New Edge"));
ParentGraph->Modify();
if (FromPin != nullptr)
FromPin->Modify();
this->NodeTemplate->Rename(nullptr, ParentGraph);
ParentGraph->AddNode(this->NodeTemplate, true, bSelectNewNode);
this->NodeTemplate->CreateNewGuid();
this->NodeTemplate->PostPlacedNewNode();
this->NodeTemplate->AllocateDefaultPins();
this->NodeTemplate->AutowireNewNode(FromPin);
this->NodeTemplate->NodePosX = Location.X;
this->NodeTemplate->NodePosY = Location.Y;
this->NodeTemplate->ComboActionGraphEdge->SetFlags(EObjectFlags::RF_Transactional);
this->NodeTemplate->SetFlags(EObjectFlags::RF_Transactional);
ResultNode = this->NodeTemplate;
}
return ResultNode;
}
void FAssetSchemaAction_ComboActionGraphSchema_NewEdge::AddReferencedObjects(FReferenceCollector &Collector)
{
FEdGraphSchemaAction::AddReferencedObjects(Collector);
Collector.AddReferencedObject(this->NodeTemplate);
}
void UComboActionGraphSchema::GetBreakLinkToSubMenuActions(UToolMenu *Menu, UEdGraphPin *InGraphPin)
{
// Make sure we have a unique name for every entry in the list
TMap<FString, uint32> LinkTitleCount;
FToolMenuSection &Section = Menu->FindOrAddSection("ComboActionGraphAssetGraphSchemaPinActions");
// Add all the links we could break from
for (TArray<class UEdGraphPin*>::TConstIterator Links(InGraphPin->LinkedTo); Links; ++Links)
{
UEdGraphPin* Pin = *Links;
FString TitleString = Pin->GetOwningNode()->GetNodeTitle(ENodeTitleType::ListView).ToString();
FText Title = FText::FromString(TitleString);
if (Pin->PinName != TEXT(""))
{
TitleString = FString::Printf(TEXT("%s (%s)"), *TitleString, *Pin->PinName.ToString());
// Add name of connection if possible
FFormatNamedArguments Args;
Args.Add(TEXT("NodeTitle"), Title);
Args.Add(TEXT("PinName"), Pin->GetDisplayName());
Title = FText::Format(LOCTEXT("BreakDescPin", "{NodeTitle} ({PinName})"), Args);
}
uint32& Count = LinkTitleCount.FindOrAdd(TitleString);
FText Description;
FFormatNamedArguments Args;
Args.Add(TEXT("NodeTitle"), Title);
Args.Add(TEXT("NumberOfNodes"), Count);
if (Count == 0)
{
Description = FText::Format(LOCTEXT("BreakDesc", "Break link to {NodeTitle}"), Args);
}
else
{
Description = FText::Format(LOCTEXT("BreakDescMulti", "Break link to {NodeTitle} ({NumberOfNodes})"), Args);
}
++Count;
Section.AddMenuEntry(NAME_None, Description, Description, FSlateIcon(), FUIAction(
FExecuteAction::CreateUObject(this, &UComboActionGraphSchema::BreakSinglePinLink, const_cast<UEdGraphPin*>(InGraphPin), *Links)));
}
}
void UComboActionGraphSchema::GetGraphContextActions(FGraphContextMenuBuilder &ContextMenuBuilder) const
{
UComboActionGraph *Graph = CastChecked<UComboActionGraph>(ContextMenuBuilder.CurrentGraph->GetOuter());
if (Graph->NodeType == nullptr)
{
return;
}
const bool bNoParent = (ContextMenuBuilder.FromPin == NULL);
FText AddToolTip = LOCTEXT("NewMoutneaDialogueGraphNodeTooltip", "Add Dialogue Node here");
TSet<TSubclassOf<UComboActionGraphNode>> Visited;
FText Desc = Graph->NodeType.GetDefaultObject()->ContextMenuName;
FText NodeCategory = Graph->NodeType.GetDefaultObject()->GetNodeCategory();
if (Desc.IsEmpty())
{
FString Title = Graph->NodeType->GetName();
Title.RemoveFromEnd("_C");
Desc = FText::FromString(Title);
}
if (!Graph->NodeType->HasAnyClassFlags(CLASS_Abstract))
{
TSharedPtr<FAssetSchemaAction_ComboActionGraphSchema_NewNode> NewNodeAction(new FAssetSchemaAction_ComboActionGraphSchema_NewNode(NodeCategory, Desc, AddToolTip, 0));
NewNodeAction->NodeTemplate = NewObject<UEdComboActionGraphNode>(ContextMenuBuilder.OwnerOfTemporaries);
NewNodeAction->NodeTemplate->ComboActionGraphNode = NewObject<UComboActionGraphNode>(NewNodeAction->NodeTemplate, Graph->NodeType);
NewNodeAction->NodeTemplate->ComboActionGraphNode->Graph = Graph;
ContextMenuBuilder.AddAction(NewNodeAction);
Visited.Add(Graph->NodeType);
}
for (TObjectIterator<UClass> It; It; ++It)
{
if (It->IsChildOf(Graph->NodeType) && !It->HasAnyClassFlags(CLASS_Abstract) && !Visited.Contains(*It))
{
TSubclassOf<UComboActionGraphNode> NodeType = *It;
if (It->GetName().StartsWith("REINST") || It->GetName().StartsWith("SKEL"))
continue;
if (!Graph->GetClass()->IsChildOf(NodeType.GetDefaultObject()->CompatibleGraphType))
continue;
if (!NodeType.GetDefaultObject()->bAllowManualCreate)
continue;
Desc = NodeType.GetDefaultObject()->ContextMenuName;
AddToolTip = NodeType.GetDefaultObject()->GetDescription();
NodeCategory = NodeType.GetDefaultObject()->GetNodeCategory();
if (Desc.IsEmpty())
{
FString Title = NodeType->GetName();
Title.RemoveFromEnd("_C");
Desc = FText::FromString(Title);
}
TSharedPtr<FAssetSchemaAction_ComboActionGraphSchema_NewNode> Action(new FAssetSchemaAction_ComboActionGraphSchema_NewNode(NodeCategory, Desc, AddToolTip, 0));
Action->NodeTemplate = NewObject<UEdComboActionGraphNode>(ContextMenuBuilder.OwnerOfTemporaries);
Action->NodeTemplate->ComboActionGraphNode = NewObject<UComboActionGraphNode>(Action->NodeTemplate, NodeType);
Action->NodeTemplate->ComboActionGraphNode->Graph = Graph;
ContextMenuBuilder.AddAction(Action);
Visited.Add(NodeType);
}
}
}
void UComboActionGraphSchema::GetContextMenuActions(UToolMenu *Menu, UGraphNodeContextMenuContext *Context) const
{
if (Context->Pin)
{
{
FToolMenuSection& Section = Menu->AddSection("ComboActionGraphAssetGraphSchemaNodeActions", LOCTEXT("PinActionsMenuHeader", "Pin Actions"));
// Only display the 'Break Links' option if there is a link to break!
if (Context->Pin->LinkedTo.Num() > 0)
{
Section.AddMenuEntry(FGraphEditorCommands::Get().BreakPinLinks);
// add sub menu for break link to
if (Context->Pin->LinkedTo.Num() > 1)
{
Section.AddSubMenu(
"BreakLinkTo",
LOCTEXT("BreakLinkTo", "Break Link To..."),
LOCTEXT("BreakSpecificLinks", "Break a specific link..."),
FNewToolMenuDelegate::CreateUObject((UComboActionGraphSchema *const)this, &UComboActionGraphSchema::GetBreakLinkToSubMenuActions, const_cast<UEdGraphPin*>(Context->Pin)));
}
else
{
((UComboActionGraphSchema *const)this)->GetBreakLinkToSubMenuActions(Menu, const_cast<UEdGraphPin*>(Context->Pin));
}
}
}
}
else if (Context->Node)
{
{
FToolMenuSection& Section = Menu->AddSection("ComboActionGraphAssetGraphSchemaNodeActions", LOCTEXT("ClassActionsMenuHeader", "Node Actions"));
Section.AddSeparator(FName(TEXT("Main Options")));
Section.AddMenuEntry(FGenericCommands::Get().Rename);
Section.AddSeparator(FName(TEXT("Other Options")));
Section.AddMenuEntry(FGenericCommands::Get().Delete);
Section.AddMenuEntry(FGenericCommands::Get().Cut);
Section.AddMenuEntry(FGenericCommands::Get().Copy);
Section.AddMenuEntry(FGenericCommands::Get().Duplicate);
Section.AddMenuEntry(FGraphEditorCommands::Get().BreakNodeLinks);
}
}
Super::GetContextMenuActions(Menu, Context);
}
const FPinConnectionResponse UComboActionGraphSchema::CanCreateConnection(const UEdGraphPin *A, const UEdGraphPin *B) const
{
// Make sure the pins are not on the same node
if (A->GetOwningNode() == B->GetOwningNode())
{
return FPinConnectionResponse(CONNECT_RESPONSE_DISALLOW, LOCTEXT("PinErrorSameNode", "Both are on the same node"));
}
// Compare the directions
if ((A->Direction == EGPD_Input) && (B->Direction == EGPD_Input))
{
return FPinConnectionResponse(CONNECT_RESPONSE_DISALLOW, LOCTEXT("PinErrorInput", "Can't connect input node to input node"));
}
else if ((A->Direction == EGPD_Output) && (B->Direction == EGPD_Output))
{
return FPinConnectionResponse(CONNECT_RESPONSE_DISALLOW, LOCTEXT("PinErrorOutput", "Can't connect output node to output node"));
}
// check for cycles
FComboActionNodeVisitorCycleChecker CycleChecker;
if (!CycleChecker.CheckForLoop(A->GetOwningNode(), B->GetOwningNode()))
{
return FPinConnectionResponse(CONNECT_RESPONSE_DISALLOW, LOCTEXT("PinErrorCycle", "Can't create a graph cycle"));
}
if (!CycleChecker.CheckForLoop(B->GetOwningNode(), A->GetOwningNode()))
{
return FPinConnectionResponse(CONNECT_RESPONSE_DISALLOW, LOCTEXT("PinErrorCycle", "Can't create a graph cycle"));
}
UEdComboActionGraphNode* EdNode_A = Cast<UEdComboActionGraphNode>(A->GetOwningNode());
UEdComboActionGraphNode* EdNode_B = Cast<UEdComboActionGraphNode>(B->GetOwningNode());
if (EdNode_A == nullptr || EdNode_B == nullptr)
{
return FPinConnectionResponse(CONNECT_RESPONSE_DISALLOW, LOCTEXT("PinError", "Not a valid UComboActionGraphEdNode"));
}
FText ErrorMessage;
if (A->Direction == EGPD_Input)
{
if (!EdNode_A->ComboActionGraphNode->CanCreateConnection(EdNode_B->ComboActionGraphNode, EGPD_Input, ErrorMessage))
{
return FPinConnectionResponse(CONNECT_RESPONSE_DISALLOW, ErrorMessage);
}
}
else
{
if (!EdNode_B->ComboActionGraphNode->CanCreateConnection(EdNode_A->ComboActionGraphNode, EGPD_Output, ErrorMessage))
{
return FPinConnectionResponse(CONNECT_RESPONSE_DISALLOW, ErrorMessage);
}
}
if (EdNode_A->ComboActionGraphNode->GetGraph()->bEdgeEnabled)
{
return FPinConnectionResponse(CONNECT_RESPONSE_MAKE_WITH_CONVERSION_NODE, LOCTEXT("PinConnect", "Connect nodes with edge"));
}
else
{
return FPinConnectionResponse(CONNECT_RESPONSE_MAKE, LOCTEXT("PinConnect", "Connect nodes"));
}
}
bool UComboActionGraphSchema::CreateAutomaticConversionNodeAndConnections(UEdGraphPin *A, UEdGraphPin *B) const
{
UEdComboActionGraphNode *NodeA = Cast<UEdComboActionGraphNode>(A->GetOwningNode());
UEdComboActionGraphNode *NodeB = Cast<UEdComboActionGraphNode>(B->GetOwningNode());
if (NodeA == nullptr || NodeB == nullptr)
return false;
if (NodeA->GetInputPin() == nullptr || NodeA->GetOutputPin() == nullptr || NodeB->GetInputPin() == nullptr || NodeB->GetOutputPin() == nullptr)
return false;
UComboActionGraph* Graph = NodeA->ComboActionGraphNode->GetGraph();
FVector2D InitPos((NodeA->NodePosX + NodeB->NodePosX) / 2, (NodeA->NodePosY + NodeB->NodePosY) / 2);
FAssetSchemaAction_ComboActionGraphSchema_NewEdge Action;
Action.NodeTemplate = NewObject<UEdComboActionGraphEdge>(NodeA->GetGraph());
Action.NodeTemplate->SetEdge(NewObject<UComboActionGraphEdge>(Action.NodeTemplate, Graph->EdgeType));
UEdComboActionGraphEdge *EdgeNode = Cast<UEdComboActionGraphEdge>(Action.PerformAction(NodeA->GetGraph(), nullptr, InitPos, false));
if (A->Direction == EEdGraphPinDirection::EGPD_Output)
{
EdgeNode->CreateConnections(NodeA, NodeB);
}
else
{
EdgeNode->CreateConnections(NodeB, NodeA);
}
return true;
}
FConnectionDrawingPolicy *UComboActionGraphSchema::CreateConnectionDrawingPolicy(int32 InBackLayerID, int32 InFrontLayerID, float InZoomFactor, const FSlateRect& InClippingRect, FSlateWindowElementList& InDrawElements, UEdGraph* InGraphObj) const
{
/*
if (const UMounteaDialogueGraphEditorSettings* MounteaDialogueGraphEditorSettings = GetMutableDefault<UMounteaDialogueGraphEditorSettings>())
{
if (MounteaDialogueGraphEditorSettings->AllowAdvancedWiring())
{
return new FConnectionDrawingPolicy_AdvancedMounteaDialogueGraph(InBackLayerID, InFrontLayerID, InZoomFactor, InClippingRect, InDrawElements, InGraphObj);
}
}
*/
//return new FConnectionDrawingPolicy_ComboActionGraph(InBackLayerID, InFrontLayerID, InZoomFactor, InClippingRect, InDrawElements, InGraphObj);
return nullptr;
}
FLinearColor UComboActionGraphSchema::GetPinTypeColor(const FEdGraphPinType &PinType) const
{
return FColor::White;
}
void UComboActionGraphSchema::BreakNodeLinks(UEdGraphNode &TargetNode) const
{
const FScopedTransaction Transaction(NSLOCTEXT("UnrealEd", "GraphEd_BreakNodeLinks", "Break Node Links"));
Super::BreakNodeLinks(TargetNode);
}
void UComboActionGraphSchema::BreakPinLinks(UEdGraphPin &TargetPin, bool bSendsNodeNotifcation) const
{
const FScopedTransaction Transaction(NSLOCTEXT("UnrealEd", "GraphEd_BreakPinLinks", "Break Pin Links"));
Super::BreakPinLinks(TargetPin, bSendsNodeNotifcation);
}
void UComboActionGraphSchema::BreakSinglePinLink(UEdGraphPin *SourcePin, UEdGraphPin *TargetPin) const
{
const FScopedTransaction Transaction(NSLOCTEXT("UnrealEd", "GraphEd_BreakSinglePinLink", "Break Pin Link"));
Super::BreakSinglePinLink(SourcePin, TargetPin);
}
UEdGraphPin *UComboActionGraphSchema::DropPinOnNode(UEdGraphNode *InTargetNode, const FName &InSourcePinName, const FEdGraphPinType &InSourcePinType, EEdGraphPinDirection InSourcePinDirection) const
{
UEdComboActionGraphNode *EdNode = Cast<UEdComboActionGraphNode>(InTargetNode);
switch (InSourcePinDirection)
{
case EEdGraphPinDirection::EGPD_Input:
return EdNode->GetOutputPin();
case EEdGraphPinDirection::EGPD_Output:
return EdNode->GetInputPin();
default:
return nullptr;
}
}
bool UComboActionGraphSchema::SupportsDropPinOnNode(UEdGraphNode *InTargetNode, const FEdGraphPinType &InSourcePinType, EEdGraphPinDirection InSourcePinDirection, FText &OutErrorMessage) const
{
return Cast<UEdComboActionGraphNode>(InTargetNode) != nullptr;
}
bool UComboActionGraphSchema::IsCacheVisualizationOutOfDate(int32 InVisualizationCacheID) const
{
return CurrentCacheRefreshID != InVisualizationCacheID;
}
int32 UComboActionGraphSchema::GetCurrentVisualizationCacheID() const
{
return CurrentCacheRefreshID;
}
void UComboActionGraphSchema::ForceVisualizationCacheClear() const
{
CurrentCacheRefreshID++;
}
void UComboActionGraphSchema::CreateDefaultNodesForGraph(UEdGraph &Graph) const
{
Super::CreateDefaultNodesForGraph(Graph);
}
#undef LOCTEXT_NAMESPACE

View File

@ -0,0 +1,164 @@
// ©2023 Batty Bovine Productions, LLC. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "ComboActionGraphSchema.generated.h"
class FComboActionNodeVisitorCycleChecker
{
public:
/** Check whether a loop in the graph would be caused by linking the passed-in nodes */
bool CheckForLoop(UEdGraphNode *StartNode, UEdGraphNode *EndNode)
{
VisitedNodes.Add(StartNode);
if (!QuiCheckDirectConnections(StartNode, EndNode))
{
return false;
}
return TraverseInputNodesToRoot(EndNode);
}
private:
bool TraverseInputNodesToRoot(UEdGraphNode *Node)
{
VisitedNodes.Add(Node);
for (int32 PinIndex = 0; PinIndex < Node->Pins.Num(); ++PinIndex)
{
UEdGraphPin *MyPin = Node->Pins[PinIndex];
if (MyPin->Direction == EGPD_Output)
{
for (int32 LinkedPinIndex = 0; LinkedPinIndex < MyPin->LinkedTo.Num(); ++LinkedPinIndex)
{
UEdGraphPin *OtherPin = MyPin->LinkedTo[LinkedPinIndex];
if (OtherPin)
{
UEdGraphNode *OtherNode = OtherPin->GetOwningNode();
if (VisitedNodes.Contains(OtherNode))
{
return false;
}
else
{
return TraverseInputNodesToRoot(OtherNode);
}
}
}
}
}
return true;
}
bool QuiCheckDirectConnections(UEdGraphNode *A, UEdGraphNode *B)
{
for (auto Itr : B->Pins)
{
if (A->Pins.Contains(Itr))
{
return false;
}
}
for (auto Itr : A->Pins)
{
if (B->Pins.Contains(Itr))
{
return false;
}
}
return true;
}
TSet<UEdGraphNode *> VisitedNodes;
};
/** Action to add a node to the graph */
USTRUCT()
struct FAssetSchemaAction_ComboActionGraphSchema_NewNode : public FEdGraphSchemaAction
{
GENERATED_BODY();
public:
FAssetSchemaAction_ComboActionGraphSchema_NewNode() : NodeTemplate(nullptr){}
FAssetSchemaAction_ComboActionGraphSchema_NewNode(const FText &InNodeCategory, const FText &InMenuDesc, const FText &InToolTip, const int32 InGrouping)
: FEdGraphSchemaAction(InNodeCategory, InMenuDesc, InToolTip, InGrouping), NodeTemplate(nullptr){}
virtual UEdGraphNode *PerformAction(class UEdGraph *ParentGraph, UEdGraphPin *FromPin, const FVector2D Location, bool bSelectNewNode = true) override;
virtual void AddReferencedObjects(FReferenceCollector &Collector) override;
TObjectPtr<class UEdComboActionGraphNode> NodeTemplate;
};
USTRUCT()
struct FAssetSchemaAction_ComboActionGraphSchema_NewEdge : public FEdGraphSchemaAction
{
GENERATED_BODY();
public:
FAssetSchemaAction_ComboActionGraphSchema_NewEdge() : NodeTemplate(nullptr)
{}
FAssetSchemaAction_ComboActionGraphSchema_NewEdge(const FText &InNodeCategory, const FText &InMenuDesc, const FText &InToolTip, const int32 InGrouping)
: FEdGraphSchemaAction(InNodeCategory, InMenuDesc, InToolTip, InGrouping), NodeTemplate(nullptr)
{}
virtual UEdGraphNode *PerformAction(class UEdGraph *ParentGraph, UEdGraphPin *FromPin, const FVector2D Location, bool bSelectNewNode = true) override;
virtual void AddReferencedObjects(FReferenceCollector &Collector) override;
TObjectPtr<class UEdComboActionGraphEdge> NodeTemplate;
};
UCLASS(MinimalAPI)
class UComboActionGraphSchema : public UEdGraphSchema
{
GENERATED_BODY()
public:
void GetBreakLinkToSubMenuActions(class UToolMenu* Menu, class UEdGraphPin* InGraphPin);
virtual EGraphType GetGraphType(const UEdGraph* TestEdGraph) const override { return EGraphType::GT_StateMachine; }
virtual void GetGraphContextActions(FGraphContextMenuBuilder& ContextMenuBuilder) const override;
virtual void GetContextMenuActions(class UToolMenu* Menu, class UGraphNodeContextMenuContext* Context) const override;
virtual const FPinConnectionResponse CanCreateConnection(const UEdGraphPin* A, const UEdGraphPin* B) const override;
virtual bool CreateAutomaticConversionNodeAndConnections(UEdGraphPin* A, UEdGraphPin* B) const override;
virtual class FConnectionDrawingPolicy* CreateConnectionDrawingPolicy(int32 InBackLayerID, int32 InFrontLayerID, float InZoomFactor, const FSlateRect& InClippingRect, class FSlateWindowElementList& InDrawElements, class UEdGraph* InGraphObj) const override;
virtual FLinearColor GetPinTypeColor(const FEdGraphPinType& PinType) const override;
virtual void BreakNodeLinks(UEdGraphNode& TargetNode) const override;
virtual void BreakPinLinks(UEdGraphPin& TargetPin, bool bSendsNodeNotifcation) const override;
virtual void BreakSinglePinLink(UEdGraphPin* SourcePin, UEdGraphPin* TargetPin) const override;
virtual UEdGraphPin* DropPinOnNode(UEdGraphNode* InTargetNode, const FName& InSourcePinName, const FEdGraphPinType& InSourcePinType, EEdGraphPinDirection InSourcePinDirection) const override;
virtual bool SupportsDropPinOnNode(UEdGraphNode* InTargetNode, const FEdGraphPinType& InSourcePinType, EEdGraphPinDirection InSourcePinDirection, FText& OutErrorMessage) const override;
virtual bool IsCacheVisualizationOutOfDate(int32 InVisualizationCacheID) const override;
virtual int32 GetCurrentVisualizationCacheID() const override;
virtual void ForceVisualizationCacheClear() const override;
virtual void CreateDefaultNodesForGraph(UEdGraph& Graph) const override;
private:
static int32 CurrentCacheRefreshID;
};

View File

@ -2,10 +2,17 @@
#include "ComboInputEditor.h"
#include "ComboActionGraph.h"
#include "ComboInputAssets.h"
#include "ToolMenuSection.h"
#include "AssetTypeActions/AssetTypeActions_DataAsset.h"
#include "Ed/AssetEditor_ComboActionGraph.h"
#include "Ed/EdComboActionGraphEdge.h"
#include "Ed/EdComboActionGraphNode.h"
#include "Ed/FComboActionGraphEditorCommands.h"
#include "Ed/SEdComboActionGraphEdge.h"
#include "Ed/SEdComboActionGraphNode.h"
#include "Interfaces/IPluginManager.h"
#include "Styling/SlateStyle.h"
#include "Styling/SlateStyleMacros.h"
@ -16,6 +23,7 @@
#define LOCTEXT_NAMESPACE "FComboInputEditorModule"
EAssetTypeCategories::Type FComboInputEditorModule::ComboAssetsCategory;
EAssetTypeCategories::Type FComboInputEditorModule::ComboActionGraphCategory;
class FAssetTypeActions_ComboAction : public FAssetTypeActions_DataAsset
@ -28,68 +36,76 @@ public:
virtual UClass *GetSupportedClass() const override { return UComboAction::StaticClass(); }
};
class FAssetTypeActions_ComboSequenceNode : public FAssetTypeActions_DataAsset
{
public:
virtual FText GetName() const override { return NSLOCTEXT("AssetTypeActions", "AssetTypeActions_ComboSequenceNode", "Combo Sequence Node"); }
virtual uint32 GetCategories() override { return FComboInputEditorModule::GetInputAssetsCategory(); }
virtual FColor GetTypeColor() const override { return FColor(255, 127, 255); }
virtual FText GetAssetDescription(const FAssetData &AssetData) const override { return NSLOCTEXT("AssetTypeActions", "AssetTypeActions_ComboSequenceNodeDesc", "This represents a node in the combo graph, with each key in the ComboBranch being an input this node can react to, and each value containing the action to be executed next, and the node to activate after the action is complete."); }
virtual UClass *GetSupportedClass() const override { return UComboSequenceNode::StaticClass(); }
};
class FAssetTypeActions_ComboInputAsset : public FAssetTypeActions_DataAsset
{
public:
virtual FText GetName() const override { return NSLOCTEXT("AssetTypeActions", "AssetTypeActions_ComboInputAsset", "Combo Input Asset"); }
virtual uint32 GetCategories() override { return FComboInputEditorModule::GetInputAssetsCategory(); }
virtual FColor GetTypeColor() const override { return FColor(255, 127, 255); }
virtual FText GetAssetDescription(const FAssetData &AssetData) const override { return NSLOCTEXT("AssetTypeActions", "AssetTypeActions_ComboInputAssetDesc", "This maps a sequence of button inputs from EnhancedInput to a combo action that can be used to execute a sequence of moves.This gets sent from the input buffer subsystem to the player controller's ComboManagerComponent, which executes the associated action in the current ComboSequenceNode."); }
virtual FText GetAssetDescription(const FAssetData &AssetData) const override { return NSLOCTEXT("AssetTypeActions", "AssetTypeActions_ComboInputAssetDesc", "This maps a sequence of button inputs from EnhancedInput to a combo action that can be used to execute a sequence of moves. This gets sent from the input buffer subsystem to the player controller's ComboManagerComponent, which executes the associated action in the current ComboActionGraph."); }
virtual UClass *GetSupportedClass() const override { return UComboInputAsset::StaticClass(); }
};
class FAssetTypeActions_ComboActionGraph : public FAssetTypeActions_Base
{
public:
virtual FText GetName() const override { return NSLOCTEXT("AssetTypeActions", "AssetTypeActions_ComboActionGraph", "Combo Action Graph"); }
virtual uint32 GetCategories() override { return FComboInputEditorModule::GetInputAssetsCategory(); }
virtual FColor GetTypeColor() const override { return FColor(255, 127, 255); }
virtual FText GetAssetDescription(const FAssetData &AssetData) const override { return NSLOCTEXT("AssetTypeActions", "AssetTypeActions_ComboActionGraphDesc", "A node graph containing a sequence of combo actions. Each node can respond to any number of combo inputs, and when activated will execute the associated event."); }
virtual UClass *GetSupportedClass() const override { return UComboActionGraph::StaticClass(); }
virtual void OpenAssetEditor(const TArray<UObject*> &InObjects, TSharedPtr<class IToolkitHost> EditWithinLevelEditor = TSharedPtr<IToolkitHost>()) override
{
const EToolkitMode::Type Mode = EditWithinLevelEditor.IsValid() ? EToolkitMode::WorldCentric : EToolkitMode::Standalone;
for (auto ObjIt = InObjects.CreateConstIterator(); ObjIt; ObjIt++)
{
if (UComboActionGraph *Graph = Cast<UComboActionGraph>(*ObjIt))
{
TSharedRef<FAssetEditor_ComboActionGraph> NewGraphEditor(new FAssetEditor_ComboActionGraph());
NewGraphEditor->InitComboActionGraphAssetEditor(Mode, EditWithinLevelEditor, Graph);
}
}
}
};
class FComboActionGraphPanelNodeFactory : public FGraphPanelNodeFactory
{
public:
virtual TSharedPtr<class SGraphNode> CreateNode(UEdGraphNode *Node) const override
{
if (UEdComboActionGraphNode *EdGraphNode = Cast<UEdComboActionGraphNode>(Node))
{
return SNew(SEdComboActionGraphNode, EdGraphNode);
}
else if (UEdComboActionGraphEdge *EdGraphEdge = Cast<UEdComboActionGraphEdge>(Node))
{
return SNew(SEdComboActionGraphEdge, EdGraphEdge);
}
return nullptr;
}
};
UComboAction_Factory::UComboAction_Factory(const FObjectInitializer &ObjectInitializer)
: Super(ObjectInitializer)
{
SupportedClass = UComboAction::StaticClass();
bEditAfterNew = true;
bCreateNew = true;
this->SupportedClass = UComboAction::StaticClass();
this->bEditAfterNew = true;
this->bCreateNew = true;
}
UObject *UComboAction_Factory::FactoryCreateNew(UClass *Class, UObject *InParent, FName Name, EObjectFlags Flags, UObject *Context, FFeedbackContext *Warn)
{
if (this->ComboActionClass != nullptr)
{
return NewObject<UComboAction>(InParent, this->ComboActionClass, Name, Flags | RF_Transactional, Context);
return NewObject<UComboAction>(InParent, this->ComboActionClass, Name, Flags | EObjectFlags::RF_Transactional, Context);
}
else
{
check(Class->IsChildOf(UComboAction::StaticClass()));
return NewObject<UComboAction>(InParent, Class, Name, Flags | RF_Transactional, Context);
}
}
UComboSequenceNode_Factory::UComboSequenceNode_Factory(const FObjectInitializer &ObjectInitializer)
: Super(ObjectInitializer)
{
SupportedClass = UComboSequenceNode::StaticClass();
bEditAfterNew = true;
bCreateNew = true;
}
UObject *UComboSequenceNode_Factory::FactoryCreateNew(UClass *Class, UObject *InParent, FName Name, EObjectFlags Flags, UObject *Context, FFeedbackContext *Warn)
{
if (this->ComboSequenceNodeClass != nullptr)
{
return NewObject<UComboSequenceNode>(InParent, this->ComboSequenceNodeClass, Name, Flags | RF_Transactional, Context);
}
else
{
check(Class->IsChildOf(UComboSequenceNode::StaticClass()));
return NewObject<UComboSequenceNode>(InParent, Class, Name, Flags | RF_Transactional, Context);
return NewObject<UComboAction>(InParent, Class, Name, Flags | EObjectFlags::RF_Transactional, Context);
}
}
@ -97,76 +113,199 @@ UObject *UComboSequenceNode_Factory::FactoryCreateNew(UClass *Class, UObject *In
UComboInputAsset_Factory::UComboInputAsset_Factory(const FObjectInitializer &ObjectInitializer)
: Super(ObjectInitializer)
{
SupportedClass = UComboInputAsset::StaticClass();
bEditAfterNew = true;
bCreateNew = true;
this->SupportedClass = UComboInputAsset::StaticClass();
this->bEditAfterNew = true;
this->bCreateNew = true;
}
UObject *UComboInputAsset_Factory::FactoryCreateNew(UClass *Class, UObject *InParent, FName Name, EObjectFlags Flags, UObject *Context, FFeedbackContext *Warn)
{
if (this->ComboInputAssetClass != nullptr)
{
return NewObject<UComboInputAsset>(InParent, this->ComboInputAssetClass, Name, Flags | RF_Transactional, Context);
return NewObject<UComboInputAsset>(InParent, this->ComboInputAssetClass, Name, Flags | EObjectFlags::RF_Transactional, Context);
}
else
{
check(Class->IsChildOf(UComboInputAsset::StaticClass()));
return NewObject<UComboInputAsset>(InParent, Class, Name, Flags | RF_Transactional, Context);
return NewObject<UComboInputAsset>(InParent, Class, Name, Flags | EObjectFlags::RF_Transactional, Context);
}
}
class FComboInputSlateStyle final : public FSlateStyleSet
UComboActionGraph_Factory::UComboActionGraph_Factory(const FObjectInitializer &ObjectInitializer)
: Super(ObjectInitializer)
{
public:
FComboInputSlateStyle() : FSlateStyleSet("ComboInputEditor")
this->SupportedClass = UComboActionGraph::StaticClass();
this->bEditAfterNew = true;
this->bCreateNew = true;
}
UObject *UComboActionGraph_Factory::FactoryCreateNew(UClass *Class, UObject *InParent, FName Name, EObjectFlags Flags, UObject *Context, FFeedbackContext *Warn)
{
if (this->ComboActionGraphClass != nullptr)
{
this->SetParentStyleName(FAppStyle::GetAppStyleSetName());
const FString &PluginSlateDir = IPluginManager::Get().FindPlugin("ComboInput")->GetBaseDir();
this->SetContentRoot(PluginSlateDir / TEXT("Resources/Slate"));
this->SetCoreContentRoot(FPaths::EngineContentDir() / TEXT("Slate"));
// Combo Input Editor icons
static const FVector2D Icon16 = FVector2D(16.0f, 16.0f);
static const FVector2D Icon64 = FVector2D(64.0f, 64.0f);
this->Set("ComboInputIcon_Small", new IMAGE_BRUSH_SVG("Icons/ComboInput_16", Icon16));
this->Set("ComboInputIcon_Large", new IMAGE_BRUSH_SVG("Icons/ComboInput_64", Icon64));
this->Set("ClassIcon.ComboAction", new IMAGE_BRUSH_SVG("Icons/ComboAction_16", Icon16));
this->Set("ClassThumbnail.ComboAction", new IMAGE_BRUSH_SVG("Icons/ComboAction_64", Icon64));
this->Set("ClassIcon.ComboSequenceNode", new IMAGE_BRUSH_SVG("Icons/ComboSequenceNode_16", Icon16));
this->Set("ClassThumbnail.ComboSequenceNode", new IMAGE_BRUSH_SVG("Icons/ComboSequenceNode_64", Icon64));
this->Set("ClassIcon.ComboInputAsset", new IMAGE_BRUSH_SVG("Icons/ComboInputAsset_16", Icon16));
this->Set("ClassThumbnail.ComboInputAsset", new IMAGE_BRUSH_SVG("Icons/ComboInputAsset_64", Icon64));
return NewObject<UComboActionGraph>(InParent, this->ComboActionGraphClass, Name, Flags | EObjectFlags::RF_Transactional, Context);
}
};
else
{
check(Class->IsChildOf(UComboActionGraph::StaticClass()));
return NewObject<UComboActionGraph>(InParent, Class, Name, Flags | EObjectFlags::RF_Transactional, Context);
}
}
FComboInputSlateStyle::FComboInputSlateStyle() : FSlateStyleSet("ComboInputEditor")
{
this->SetParentStyleName(FAppStyle::GetAppStyleSetName());
const FString &PluginSlateDir = IPluginManager::Get().FindPlugin("ComboInput")->GetBaseDir();
this->SetContentRoot(PluginSlateDir / TEXT("Resources/Slate"));
this->SetCoreContentRoot(FPaths::EngineContentDir() / TEXT("Slate"));
// Combo Input Editor icons
static const FVector2D Icon12(12.0f, 12.0f);
static const FVector2D Icon16(16.0f, 16.0f);
static const FVector2D Icon40(40.0f, 40.0f);
static const FVector2D Icon64(64.0f, 64.0f);
this->Set("ComboInputIcon_Small", new IMAGE_BRUSH_SVG("Icons/ComboInput_16", Icon16));
this->Set("ComboInputIcon_Large", new IMAGE_BRUSH_SVG("Icons/ComboInput_64", Icon64));
this->Set("ClassIcon.ComboAction", new IMAGE_BRUSH_SVG("Icons/ComboAction_16", Icon16));
this->Set("ClassThumbnail.ComboAction", new IMAGE_BRUSH_SVG("Icons/ComboAction_64", Icon64));
this->Set("ClassIcon.ComboSequenceNode", new IMAGE_BRUSH_SVG("Icons/ComboSequenceNode_16", Icon16));
this->Set("ClassThumbnail.ComboSequenceNode", new IMAGE_BRUSH_SVG("Icons/ComboSequenceNode_64", Icon64));
this->Set("ClassIcon.ComboInputAsset", new IMAGE_BRUSH_SVG("Icons/ComboInputAsset_16", Icon16));
this->Set("ClassThumbnail.ComboInputAsset", new IMAGE_BRUSH_SVG("Icons/ComboInputAsset_64", Icon64));
this->Set("ClassIcon.ComboActionGraph", new IMAGE_BRUSH_SVG("Icons/ComboSequenceNode_16", Icon16));
this->Set("ClassThumbnail.ComboActionGraph", new IMAGE_BRUSH_SVG("Icons/ComboSequenceNode_64", Icon64));
// Combo Action Graph editor brushes
{
this->SetContentRoot(PluginSlateDir / TEXT("Resources/Slate/Editor"));
this->SetCoreContentRoot(FPaths::EngineContentDir() / TEXT("Slate/Editor"));
this->Set("MDSStyleSet.AutoArrange.small", new IMAGE_BRUSH(TEXT("AutoArrangeIcon"), Icon16));
this->Set("MDSStyleSet.AutoArrange", new IMAGE_BRUSH(TEXT("AutoArrangeIcon"), Icon40));
this->Set("MDSStyleSet.AutoArrange.large", new IMAGE_BRUSH(TEXT("AutoArrangeIcon"), Icon64));
this->Set("MDSStyleSet.GraphSettings.small", new IMAGE_BRUSH(TEXT("GraphSettings"), Icon16));
this->Set("MDSStyleSet.GraphSettings", new IMAGE_BRUSH(TEXT("GraphSettings"), Icon40));
this->Set("MDSStyleSet.GraphSettings.large", new IMAGE_BRUSH(TEXT("GraphSettings"), Icon64));
this->Set("MDSStyleSet.ValidateGraph.small", new IMAGE_BRUSH(TEXT("ValidateGraph"), Icon16));
this->Set("MDSStyleSet.ValidateGraph", new IMAGE_BRUSH(TEXT("ValidateGraph"), Icon40));
this->Set("MDSStyleSet.ValidateGraph.large", new IMAGE_BRUSH(TEXT("ValidateGraph"), Icon64));
this->Set("MDSStyleSet.Graph.NodeOverlay", new BOX_BRUSH(TEXT("NodeOverlay"), FMargin(8.0f / 64.0f, 3.0f / 32.0f, 0, 0)));
this->Set("MDSStyleSet.Graph.PinDocksOverlay", new BOX_BRUSH(TEXT("PinDocksOverlay"), FMargin(8.0f / 64.0f, 3.0f / 32.0f, 0, 0)));
this->Set("MDSStyleSet.Graph.SimpleArrow", new IMAGE_BRUSH(TEXT("SimpleArrow"), Icon16));
this->Set("MDSStyleSet.Graph.HollowArrow", new IMAGE_BRUSH(TEXT("HollowArrow"), Icon16));
this->Set("MDSStyleSet.Graph.FancyArrow", new IMAGE_BRUSH(TEXT("FancyArrow"), Icon16));
this->Set("MDSStyleSet.Graph.Bubble", new IMAGE_BRUSH(TEXT("Bubble"), Icon16));
this->Set("MDSStyleSet.Node.SoftEdges", new BOX_BRUSH(TEXT("NodeSoftCorners"), FMargin(16.f / 64.f, 25.f / 64.f, 16.f / 64.f, 16.f / 64.f)));
this->Set("MDSStyleSet.Node.HardEdges", new BOX_BRUSH(TEXT("NodeHardCorners"), FMargin(16.f / 64.f, 25.f / 64.f, 16.f / 64.f, 16.f / 64.f)));
this->Set("MDSStyleSet.Node.TextSoftEdges", new BOX_BRUSH(TEXT("TextSoftCorners"), FMargin(16.f / 64.f, 25.f / 64.f, 16.f / 64.f, 16.f / 64.f)));
this->Set("MDSStyleSet.Node.TextHardEdges", new BOX_BRUSH(TEXT("TextHardCorners"), FMargin(16.f / 64.f, 25.f / 64.f, 16.f / 64.f, 16.f / 64.f)));
this->Set("MDSStyleSet.Node.IndexCircle", new IMAGE_BRUSH(TEXT("IndexIcon"), Icon16));
this->Set("MDSStyleSet.Icon.OK", new IMAGE_BRUSH(TEXT("OKIcon"), Icon16));
this->Set("MDSStyleSet.Icon.Error", new IMAGE_BRUSH(TEXT("ErroIcon"), Icon16));
this->Set("MDSStyleSet.Icon.BulletPoint", new IMAGE_BRUSH(TEXT("CircleBox"), Icon16));
this->Set("MDSStyleSet.Graph.CornerImage", new IMAGE_BRUSH(TEXT("Icon128"), FVector2D(128.0f, 128.0f)));
this->Set("MDSStyleSet.Icon.Browse", new IMAGE_BRUSH(TEXT("BrowseIcon"), Icon12));
this->Set("MDSStyleSet.Icon.Edit", new IMAGE_BRUSH(TEXT("EditIcon"), Icon12));
this->Set("MDSStyleSet.Buttons.Documentation", new IMAGE_BRUSH(TEXT("Documentation"), FVector2D(200.0f, 70.0f)));
this->Set("MDSStyleSet.Buttons.Documentation.small", new IMAGE_BRUSH(TEXT("DocumentationIcon"), Icon12));
this->Set("MDSStyleSet.Node.Icon.large", new IMAGE_BRUSH(TEXT("DialogueNodeIcon"), Icon64));
this->Set("MDSStyleSet.Node.Icon", new IMAGE_BRUSH(TEXT("DialogueNodeIcon"), Icon16));
this->Set("MDSStyleSet.Node.Icon.small", new IMAGE_BRUSH(TEXT("DialogueNodeIcon"), Icon12));
this->Set("MDSStyleSet.Icon.Close", new IMAGE_BRUSH(TEXT("CloseIcon"), Icon12));
this->Set("MDSStyleSet.Icon.HeartIcon", new IMAGE_BRUSH(TEXT("HeartIcon"), Icon12));
this->Set("MDSStyleSet.Icon.UBIcon", new IMAGE_BRUSH(TEXT("UnrealBucketIcon"), Icon12));
this->Set("MDSStyleSet.Icon.MoneyIcon", new IMAGE_BRUSH(TEXT("MoneyIcon"), Icon12));
this->Set("MDSStyleSet.Buttons.Style", FButtonStyle()
.SetNormal(BOX_BRUSH("RoundedSelection_16x", 4.0f / 16.0f, FLinearColor(1, 1, 1, 0.1f)))
.SetHovered(BOX_BRUSH("RoundedSelection_16x", 4.0f / 16.0f, FLinearColor(1, .55f, 0, 0.2f)))
.SetPressed(BOX_BRUSH("RoundedSelection_16x", 4.0f / 16.0f, FLinearColor(1, .55f, 0, 0.4f))));
const FScrollBarStyle ScrollBar = GetWidgetStyle<FScrollBarStyle>("ScrollBar");
FTextBlockStyle NormalText = FTextBlockStyle()
.SetFont(DEFAULT_FONT("Regular", FCoreStyle::RegularTextSize))
.SetColorAndOpacity(FSlateColor::UseForeground())
.SetShadowOffset(FVector2D::ZeroVector)
.SetShadowColorAndOpacity(FLinearColor::Black)
.SetHighlightColor(FLinearColor(0.02f, 0.3f, 0.0f))
.SetHighlightShape(BOX_BRUSH("TextBlockHighlightShape", FMargin(3.f / 8.f)));
FTextBlockStyle NodeTitle = FTextBlockStyle(NormalText)
.SetFont(DEFAULT_FONT("Bold", 14))
.SetColorAndOpacity(FLinearColor(230.0f / 255.0f, 230.0f / 255.0f, 230.0f / 255.0f))
.SetShadowOffset(FVector2D(2, 2))
.SetShadowColorAndOpacity(FLinearColor(0.f, 0.f, 0.f, 0.7f));
this->Set("MDSStyleSet.NodeTitle", NodeTitle);
FEditableTextBoxStyle NodeTitleEditableText = FEditableTextBoxStyle()
.SetFont(NormalText.Font)
.SetBackgroundImageNormal(BOX_BRUSH("TextBox", FMargin(4.0f / 16.0f)))
.SetBackgroundImageHovered(BOX_BRUSH("TextBox_Hovered", FMargin(4.0f / 16.0f)))
.SetBackgroundImageFocused(BOX_BRUSH("TextBox_Hovered", FMargin(4.0f / 16.0f)))
.SetBackgroundImageReadOnly(BOX_BRUSH("TextBox_ReadOnly", FMargin(4.0f / 16.0f)))
.SetScrollBarStyle(ScrollBar);
this->Set("MDSStyleSet.NodeTitleEditableText", NodeTitleEditableText);
this->Set("MDSStyleSet.NodeTitleInlineEditableText", FInlineEditableTextBlockStyle()
.SetTextStyle(NodeTitle)
.SetEditableTextBoxStyle(NodeTitleEditableText)
);
}
}
void FComboInputEditorModule::StartupModule()
{
// Register combo action asset
IAssetTools &AssetTools = FModuleManager::LoadModuleChecked<FAssetToolsModule>("AssetTools").Get();
FComboInputEditorModule::ComboAssetsCategory = AssetTools.RegisterAdvancedAssetCategory(FName(TEXT("Input")), LOCTEXT("InputAssetsCategory", "Input"));
FComboInputEditorModule::ComboAssetsCategory = AssetTools.RegisterAdvancedAssetCategory(FName(TEXT("Combo Input")), LOCTEXT("ComboInputAssetsCategory", "Combo Input"));
this->RegisterAssetTypeActions(AssetTools, MakeShareable(new FAssetTypeActions_ComboAction));
this->RegisterAssetTypeActions(AssetTools, MakeShareable(new FAssetTypeActions_ComboSequenceNode));
this->RegisterAssetTypeActions(AssetTools, MakeShareable(new FAssetTypeActions_ComboInputAsset));
// Make a new style set for Combo Input, which will register any custom icons for the types in this plugin
StyleSet = MakeShared<FComboInputSlateStyle>();
FSlateStyleRegistry::RegisterSlateStyle(*StyleSet.Get());
this->ComboInputEditorStyleSet = MakeShared<FComboInputSlateStyle>();
FSlateStyleRegistry::RegisterSlateStyle(*this->ComboInputEditorStyleSet.Get());
// Combo Action Graph
{
this->ComboActionGraphPanelNodeFactory = MakeShareable(new FComboActionGraphPanelNodeFactory());
FEdGraphUtilities::RegisterVisualNodeFactory(this->ComboActionGraphPanelNodeFactory);
// Register asset actions
this->ComboActionGraphAssetActions = MakeShared<FAssetTypeActions_ComboActionGraph>();
this->RegisterAssetTypeActions(AssetTools, this->ComboActionGraphAssetActions.ToSharedRef());
}
FComboActionGraphEditorCommands::Register();
}
void FComboInputEditorModule::ShutdownModule()
{
FComboActionGraphEditorCommands::Unregister();
}
#undef LOCTEXT_NAMESPACE
IMPLEMENT_MODULE(FComboInputEditorModule, ComboInputEditorModule)
IMPLEMENT_MODULE(FComboInputEditorModule, ComboInputEditorModule)

View File

@ -0,0 +1,922 @@
// ©2023 Batty Bovine Productions, LLC. All Rights Reserved.
#include "AssetEditor_ComboActionGraph.h"
#include "ComboActionGraph.h"
#include "ComboActionGraphSchema.h"
#include "ComboInputEditor.h"
#include "EdGraphUtilities.h"
#include "GraphEditorActions.h"
#include "Ed/EdComboActionGraph.h"
#include "Ed/EdComboActionGraphEdge.h"
#include "Ed/EdComboActionGraphNode.h"
#include "Ed/FAssetEditorToolbarComboActionGraph.h"
#include "Ed/FComboActionGraphEditorCommands.h"
#include "Framework/Commands/GenericCommands.h"
#include "HAL/PlatformApplicationMisc.h"
#include "Kismet2/BlueprintEditorUtils.h"
#include "Layout/AssetEditorTabs.h"
#include "Layout/ComboActionGraphLayoutStrategy.h"
#include "Layout/ComboActionForceDirectedSolveLayoutStrategy.h"
#include "Layout/ComboActionTreeSolveLayoutStrategy.h"
#include "Nodes/ComboActionGraphNode.h"
#include "Popups/ComboInputPopup_GraphValidation.h"
#include "Search/ComboActionSearchUtils.h"
#include "Search/SComboActionSearch.h"
#include "Settings/ComboActionGraphEditorSettings.h"
#include "UObject/ObjectSaveContext.h"
#define LOCTEXT_NAMESPACE "AssetEditor_ComboActionGraph"
DEFINE_LOG_CATEGORY(LogAssetEditorComboActionGraph);
#pragma region ConstantNames
const FName ComboActionGraphEditorAppName = FName(TEXT("ComboActionGraphEditorApp"));
const FName FAssetEditorTabs_ComboActionGraph::ComboActionGraphPropertyID(TEXT("ComboActionGraphProperty"));
const FName FAssetEditorTabs_ComboActionGraph::ViewportID(TEXT("Viewport"));
const FName FAssetEditorTabs_ComboActionGraph::SearchToolbarID(TEXT("Search"));
#pragma endregion
FAssetEditor_ComboActionGraph::FAssetEditor_ComboActionGraph()
{
this->EditingGraph = nullptr;
this->ComboActionGraphEditorSettings = GetMutableDefault<UComboActionGraphEditorSettings>();
this->OnPackageSavedDelegateHandle = UPackage::PackageSavedWithContextEvent.AddRaw(this, &FAssetEditor_ComboActionGraph::OnPackageSaved);
}
FAssetEditor_ComboActionGraph::~FAssetEditor_ComboActionGraph()
{
this->EditingGraph = nullptr;
UPackage::PackageSavedWithContextEvent.Remove(this->OnPackageSavedDelegateHandle);
this->ToolbarBuilder.Reset();
}
void FAssetEditor_ComboActionGraph::OnPackageSaved(const FString &String, UPackage *Package, FObjectPostSaveContext ObjectPostSaveContext)
{
this->RebuildComboActionGraph();
}
void FAssetEditor_ComboActionGraph::InitComboActionGraphAssetEditor(const EToolkitMode::Type Mode, const TSharedPtr<IToolkitHost>& InitToolkitHost, UComboActionGraph *Graph)
{
this->EditingGraph = Graph;
this->CreateEdGraph();
if (!this->ToolbarBuilder.IsValid())
{
this->ToolbarBuilder = MakeShareable(new FAssetEditorToolbarComboActionGraph(SharedThis(this)));
}
this->BindCommands();
this->CreateInternalWidgets();
TSharedPtr<FExtender> ToolbarExtender = MakeShareable(new FExtender);
this->ToolbarBuilder->AddComboActionGraphToolbar(ToolbarExtender);
// Layout
const TSharedRef<FTabManager::FLayout> StandaloneDefaultLayout = FTabManager::NewLayout("Standalone_ComboActionGraphEditor_LayoutV0.3")
->AddArea
(
FTabManager::NewPrimaryArea()->SetOrientation(EOrientation::Orient_Vertical)
->Split
(
FTabManager::NewSplitter()->SetOrientation(EOrientation::Orient_Horizontal)->SetSizeCoefficient(0.9f)
->Split
(
FTabManager::NewStack()
->SetSizeCoefficient(3.f)
->AddTab(FAssetEditorTabs_ComboActionGraph::ViewportID, ETabState::OpenedTab)->SetHideTabWell(true)
)
->Split
(
FTabManager::NewSplitter()->SetOrientation(EOrientation::Orient_Vertical)
->Split
(
FTabManager::NewSplitter()->SetOrientation(EOrientation::Orient_Vertical)
->Split
(
FTabManager::NewStack()
->SetSizeCoefficient(0.9f)
->AddTab(FAssetEditorTabs_ComboActionGraph::ComboActionGraphPropertyID, ETabState::OpenedTab)->SetHideTabWell(true)
)
->Split
(
FTabManager::NewStack()
->SetSizeCoefficient(0.3f)
->AddTab(FAssetEditorTabs_ComboActionGraph::SearchToolbarID, ETabState::OpenedTab)
)
)
)
)
);
const bool bCreateDefaultStandaloneMenu = true;
const bool bCreateDefaultToolbar = true;
FAssetEditorToolkit::InitAssetEditor
(
Mode,
InitToolkitHost,
ComboActionGraphEditorAppName,
StandaloneDefaultLayout,
bCreateDefaultStandaloneMenu,
bCreateDefaultToolbar,
EditingGraph,
false
);
this->RegenerateMenusAndToolbars();
}
UComboActionGraphEditorSettings *FAssetEditor_ComboActionGraph::GetSettings() const
{
return this->ComboActionGraphEditorSettings;
}
void FAssetEditor_ComboActionGraph::RegisterTabSpawners(const TSharedRef<FTabManager> &InTabManager)
{
this->WorkspaceMenuCategory = InTabManager->AddLocalWorkspaceMenuCategory(LOCTEXT("WorkspaceMenu_ComboActionTreeEditor", "Combo Action Tree Editor"));
auto WorkspaceMenuCategoryRef = this->WorkspaceMenuCategory.ToSharedRef();
FAssetEditorToolkit::RegisterTabSpawners(InTabManager);
InTabManager->RegisterTabSpawner(FAssetEditorTabs_ComboActionGraph::ViewportID, FOnSpawnTab::CreateSP(this, &FAssetEditor_ComboActionGraph::SpawnTab_Viewport))
.SetDisplayName(LOCTEXT("GraphCanvasTab", "Viewport"))
.SetGroup(WorkspaceMenuCategoryRef)
.SetIcon(FSlateIcon(FAppStyle::GetAppStyleSetName(), "GraphEditor.EventGraph_16x"));
InTabManager->RegisterTabSpawner(FAssetEditorTabs_ComboActionGraph::ComboActionGraphPropertyID, FOnSpawnTab::CreateSP(this, &FAssetEditor_ComboActionGraph::SpawnTab_Details))
.SetDisplayName(LOCTEXT("DetailsTab", "Property"))
.SetGroup(WorkspaceMenuCategoryRef)
.SetIcon(FSlateIcon(FAppStyle::GetAppStyleSetName(), "LevelEditor.Tabs.Details"));
InTabManager->RegisterTabSpawner(FAssetEditorTabs_ComboActionGraph::SearchToolbarID, FOnSpawnTab::CreateSP(this, &FAssetEditor_ComboActionGraph::SpawnTab_Search))
.SetDisplayName(LOCTEXT("SearchTab", "Search"))
.SetGroup(WorkspaceMenuCategoryRef)
.SetIcon(FSlateIcon(FAppStyle::GetAppStyleSetName(), "Kismet.Tabs.FindResults"));
}
void FAssetEditor_ComboActionGraph::UnregisterTabSpawners(const TSharedRef<FTabManager> &InTabManager)
{
FAssetEditorToolkit::UnregisterTabSpawners(InTabManager);
InTabManager->UnregisterTabSpawner(FAssetEditorTabs_ComboActionGraph::ViewportID);
InTabManager->UnregisterTabSpawner(FAssetEditorTabs_ComboActionGraph::ComboActionGraphPropertyID);
InTabManager->UnregisterTabSpawner(FAssetEditorTabs_ComboActionGraph::SearchToolbarID);
}
bool FAssetEditor_ComboActionGraph::CloseWindow(EAssetEditorCloseReason InCloseReason)
{
const bool bSatisfied = FAssetEditorToolkit::CloseWindow(InCloseReason);
if (this->EditingGraph)
{
if (this->EditingGraph->EdGraph)
{
UEdComboActionGraph *EdGraph = Cast<UEdComboActionGraph>(this->EditingGraph->EdGraph);
if (EdGraph->GetComboActionEditorPtr().HasSameObject(this)) EdGraph->ResetDialogueEditorPtr();
}
}
return bSatisfied;
}
FName FAssetEditor_ComboActionGraph::GetToolkitFName() const
{
return FName("FComboActionGraphEditor");
}
FText FAssetEditor_ComboActionGraph::GetBaseToolkitName() const
{
return LOCTEXT("ComboActionGraphEditorAppLabel", "Combo Action Graph Editor");
}
FText FAssetEditor_ComboActionGraph::GetToolkitName() const
{
const bool bDirtyState = this->EditingGraph->GetOutermost()->IsDirty();
FFormatNamedArguments Args;
Args.Add(TEXT("ComboActionGraphName"), FText::FromString(this->EditingGraph->GetName()));
Args.Add(TEXT("DirtyState"), bDirtyState ? FText::FromString(TEXT("*")) : FText::GetEmpty());
return FText::Format(LOCTEXT("ComboActionGraphEditorToolkitName", "{ComboActionGraphName}{DirtyState}"), Args);
}
FText FAssetEditor_ComboActionGraph::GetToolkitToolTipText() const
{
return FAssetEditorToolkit::GetToolTipTextForObject(EditingGraph);
}
FLinearColor FAssetEditor_ComboActionGraph::GetWorldCentricTabColorScale() const
{
return FLinearColor::Gray;
}
FString FAssetEditor_ComboActionGraph::GetWorldCentricTabPrefix() const
{
return TEXT("ComboActionGraphEditor");
}
FString FAssetEditor_ComboActionGraph::GetDocumentationLink() const
{
return TEXT("There should be a link to the documentation here someday.");
}
void FAssetEditor_ComboActionGraph::SaveAsset_Execute()
{
if (this->EditingGraph != nullptr)
{
this->RebuildComboActionGraph();
}
FAssetEditorToolkit::SaveAsset_Execute();
}
void FAssetEditor_ComboActionGraph::UpdateToolbar()
{
// TODO
}
void FAssetEditor_ComboActionGraph::RegisterToolbarTab(const TSharedRef<FTabManager> &InTabManager)
{
FAssetEditorToolkit::RegisterTabSpawners(InTabManager);
}
void FAssetEditor_ComboActionGraph::AddReferencedObjects(FReferenceCollector &Collector)
{
Collector.AddReferencedObject(this->EditingGraph);
Collector.AddReferencedObject(this->EditingGraph->EdGraph);
}
FString FAssetEditor_ComboActionGraph::GetReferencerName() const
{
return TEXT("FAssetEditor_ComboActionGraph");
}
void FAssetEditor_ComboActionGraph::SetDialogueBeingEdited(UComboActionGraph *NewDialogue)
{
if (NewDialogue == nullptr || NewDialogue == this->EditingGraph)
{
return;
}
UComboActionGraph *Previous = this->EditingGraph;
this->EditingGraph = NewDialogue;
this->RemoveEditingObject(Previous);
this->AddEditingObject(NewDialogue);
}
void FAssetEditor_ComboActionGraph::CreateInternalWidgets()
{
this->ViewportWidget = this->CreateViewportWidget();
FDetailsViewArgs Args;
Args.bUpdatesFromSelection = false;
Args.bLockable = false;
Args.bAllowSearch = true;
Args.NameAreaSettings = FDetailsViewArgs::HideNameArea;
Args.bHideSelectionTip = false;
Args.bShowObjectLabel = false;
FPropertyEditorModule &PropertyModule = FModuleManager::LoadModuleChecked<FPropertyEditorModule>("PropertyEditor");
this->PropertyWidget = PropertyModule.CreateDetailView(Args);
this->PropertyWidget->SetObject(this->EditingGraph);
this->PropertyWidget->OnFinishedChangingProperties().AddSP(this, &FAssetEditor_ComboActionGraph::OnFinishedChangingProperties);
this->FindResultsView = SNew(SComboActionSearch, SharedThis(this));
}
TSharedRef<SGraphEditor> FAssetEditor_ComboActionGraph::CreateViewportWidget()
{
const FComboInputEditorModule &Module = FModuleManager::GetModuleChecked<FComboInputEditorModule>("ComboInputEditor");
FGraphAppearanceInfo AppearanceInfo;
AppearanceInfo.CornerText = LOCTEXT("AppearanceCornerText_ComboActionGraph", "Combo Action Tree");
AppearanceInfo.CornerImage = Module.Get().GetComboInputEditorStyleSet()->GetBrush(TEXT("MDSStyleSet.Graph.CornerImage"));
AppearanceInfo.InstructionText = LOCTEXT("InstructionText_ComboActionGraph", "Place action nodes by right clicking.");
this->CreateCommandList();
SGraphEditor::FGraphEditorEvents InEvents;
InEvents.OnSelectionChanged = SGraphEditor::FOnSelectionChanged::CreateSP(this, &FAssetEditor_ComboActionGraph::OnSelectedNodesChanged);
InEvents.OnNodeDoubleClicked = FSingleNodeEvent::CreateSP(this, &FAssetEditor_ComboActionGraph::OnNodeDoubleClicked);
return SNew(SGraphEditor)
.AdditionalCommands(GraphEditorCommands)
.IsEditable(true)
.Appearance(AppearanceInfo)
.GraphToEdit(EditingGraph->EdGraph)
.GraphEvents(InEvents)
.AutoExpandActionMenu(true)
.ShowGraphStateOverlay(false);
}
void FAssetEditor_ComboActionGraph::BindCommands()
{
this->ToolkitCommands->MapAction
(
FComboActionGraphEditorCommands::Get().AutoArrange,
FExecuteAction::CreateSP(this, &FAssetEditor_ComboActionGraph::AutoArrange),
FCanExecuteAction::CreateSP(this, &FAssetEditor_ComboActionGraph::CanAutoArrange)
);
this->ToolkitCommands->MapAction
(
FComboActionGraphEditorCommands::Get().ValidateGraph,
FExecuteAction::CreateSP(this, &FAssetEditor_ComboActionGraph::ValidateGraph),
FCanExecuteAction::CreateSP(this, &FAssetEditor_ComboActionGraph::CanValidateGraph)
);
}
void FAssetEditor_ComboActionGraph::CreateEdGraph()
{
if (this->EditingGraph->EdGraph == nullptr)
{
this->EditingGraph->EdGraph = CastChecked<UEdComboActionGraph>(FBlueprintEditorUtils::CreateNewGraph(EditingGraph, NAME_None, UEdComboActionGraph::StaticClass(), UComboActionGraphSchema::StaticClass()));
this->EditingGraph->EdGraph->bAllowDeletion = false;
// Give the schema a chance to fill out any required nodes (like the results node)
const UEdGraphSchema *Schema = this->EditingGraph->EdGraph->GetSchema();
Schema->CreateDefaultNodesForGraph(*this->EditingGraph->EdGraph);
UEdComboActionGraph *ComboActionGraph = Cast<UEdComboActionGraph>(this->EditingGraph->EdGraph);
UEdComboActionGraphNode *NewNode = ComboActionGraph->CreateIntermediateNode<UEdComboActionGraphNode>();
NewNode->SetComboActionGraphNode(this->EditingGraph->StartNode);
NewNode->CreateNewGuid();
NewNode->PostPlacedNewNode();
NewNode->AllocateDefaultPins();
NewNode->AutowireNewNode(nullptr);
NewNode->NodePosX = 0;
NewNode->NodePosY = 0;
NewNode->ComboActionGraphNode->SetFlags(EObjectFlags::RF_Transactional);
NewNode->SetFlags(EObjectFlags::RF_Transactional);
ComboActionGraph->RebuildComboActionGraph();
}
if (UEdComboActionGraph *EdComboActionGraph = Cast<UEdComboActionGraph>(this->EditingGraph->EdGraph))
{
EdComboActionGraph->SetDialogueEditorPtr(SharedThis(this));
EdComboActionGraph->RebuildComboActionGraph();
}
}
void FAssetEditor_ComboActionGraph::CreateCommandList()
{
if (this->GraphEditorCommands.IsValid())
{
return;
}
this->GraphEditorCommands = MakeShareable(new FUICommandList);
this->GraphEditorCommands->MapAction(FComboActionGraphEditorCommands::Get().AutoArrange,
FExecuteAction::CreateRaw(this, &FAssetEditor_ComboActionGraph::AutoArrange),
FCanExecuteAction::CreateRaw(this, &FAssetEditor_ComboActionGraph::CanAutoArrange));
this->GraphEditorCommands->MapAction(FComboActionGraphEditorCommands::Get().ValidateGraph,
FExecuteAction::CreateRaw(this, &FAssetEditor_ComboActionGraph::ValidateGraph),
FCanExecuteAction::CreateRaw(this, &FAssetEditor_ComboActionGraph::CanValidateGraph));
this->GraphEditorCommands->MapAction(FGenericCommands::Get().SelectAll,
FExecuteAction::CreateRaw(this, &FAssetEditor_ComboActionGraph::SelectAllNodes),
FCanExecuteAction::CreateRaw(this, &FAssetEditor_ComboActionGraph::CanSelectAllNodes)
);
this->GraphEditorCommands->MapAction(FGenericCommands::Get().Delete,
FExecuteAction::CreateRaw(this, &FAssetEditor_ComboActionGraph::DeleteSelectedNodes),
FCanExecuteAction::CreateRaw(this, &FAssetEditor_ComboActionGraph::CanDeleteNodes)
);
this->GraphEditorCommands->MapAction(FGenericCommands::Get().Copy,
FExecuteAction::CreateRaw(this, &FAssetEditor_ComboActionGraph::CopySelectedNodes),
FCanExecuteAction::CreateRaw(this, &FAssetEditor_ComboActionGraph::CanCopyNodes)
);
this->GraphEditorCommands->MapAction(FGenericCommands::Get().Cut,
FExecuteAction::CreateRaw(this, &FAssetEditor_ComboActionGraph::CutSelectedNodes),
FCanExecuteAction::CreateRaw(this, &FAssetEditor_ComboActionGraph::CanCutNodes)
);
this->GraphEditorCommands->MapAction(FGenericCommands::Get().Paste,
FExecuteAction::CreateRaw(this, &FAssetEditor_ComboActionGraph::PasteNodes),
FCanExecuteAction::CreateRaw(this, &FAssetEditor_ComboActionGraph::CanPasteNodes)
);
this->GraphEditorCommands->MapAction(FGenericCommands::Get().Duplicate,
FExecuteAction::CreateRaw(this, &FAssetEditor_ComboActionGraph::DuplicateNodes),
FCanExecuteAction::CreateRaw(this, &FAssetEditor_ComboActionGraph::CanDuplicateNodes)
);
this->GraphEditorCommands->MapAction(FGenericCommands::Get().Rename,
FExecuteAction::CreateSP(this, &FAssetEditor_ComboActionGraph::OnRenameNode),
FCanExecuteAction::CreateSP(this, &FAssetEditor_ComboActionGraph::CanRenameNodes)
);
}
TSharedPtr<SGraphEditor> FAssetEditor_ComboActionGraph::GetCurrentGraphEditor() const
{
return this->ViewportWidget;
}
FGraphPanelSelectionSet FAssetEditor_ComboActionGraph::GetSelectedNodes() const
{
FGraphPanelSelectionSet CurrentSelection;
TSharedPtr<SGraphEditor> FocusedGraphEd = this->GetCurrentGraphEditor();
if (FocusedGraphEd.IsValid())
{
CurrentSelection = FocusedGraphEd->GetSelectedNodes();
}
return CurrentSelection;
}
void FAssetEditor_ComboActionGraph::RebuildComboActionGraph()
{
if (this->EditingGraph == nullptr)
{
UE_LOG(LogAssetEditorComboActionGraph, Warning, TEXT("[RebuildComboActionGraph] EditingGraph is nullptr"));
return;
}
UEdComboActionGraph *EdGraph = Cast<UEdComboActionGraph>(this->EditingGraph->EdGraph);
check(EdGraph != nullptr);
EdGraph->RebuildComboActionGraph();
}
void FAssetEditor_ComboActionGraph::SelectAllNodes()
{
TSharedPtr<SGraphEditor> CurrentGraphEditor = this->GetCurrentGraphEditor();
if (CurrentGraphEditor.IsValid())
{
CurrentGraphEditor->SelectAllNodes();
}
}
void FAssetEditor_ComboActionGraph::DeleteSelectedNodes()
{
TSharedPtr<SGraphEditor> CurrentGraphEditor = this->GetCurrentGraphEditor();
if (!CurrentGraphEditor.IsValid())
{
return;
}
const FScopedTransaction Transaction(FGenericCommands::Get().Delete->GetDescription());
CurrentGraphEditor->GetCurrentGraph()->Modify();
const FGraphPanelSelectionSet SelectedNodes = CurrentGraphEditor->GetSelectedNodes();
CurrentGraphEditor->ClearSelectionSet();
for (FGraphPanelSelectionSet::TConstIterator NodeIt(SelectedNodes); NodeIt; ++NodeIt)
{
UEdGraphNode* EdNode = Cast<UEdGraphNode>(*NodeIt);
if (EdNode == nullptr || !EdNode->CanUserDeleteNode())
continue;;
if (UEdComboActionGraphNode *EdNode_Node = Cast<UEdComboActionGraphNode>(EdNode))
{
EdNode_Node->Modify();
const UEdGraphSchema *Schema = EdNode_Node->GetSchema();
if (Schema != nullptr)
{
Schema->BreakNodeLinks(*EdNode_Node);
}
EdNode_Node->DestroyNode();
}
else
{
EdNode->Modify();
EdNode->DestroyNode();
}
}
// Update UI
CurrentGraphEditor->NotifyGraphChanged();
UEdGraph *EdGraph = CurrentGraphEditor->GetCurrentGraph();
UObject *GraphOwner = EdGraph->GetOuter();
if (GraphOwner)
{
GraphOwner->PostEditChange();
GraphOwner->MarkPackageDirty();
}
this->RebuildComboActionGraph();
}
bool FAssetEditor_ComboActionGraph::CanDeleteNodes() const
{
// If any of the nodes can be deleted then we should allow deleting
const FGraphPanelSelectionSet SelectedNodes = this->GetSelectedNodes();
for (FGraphPanelSelectionSet::TConstIterator SelectedIter(SelectedNodes); SelectedIter; ++SelectedIter)
{
UEdGraphNode* Node = Cast<UEdGraphNode>(*SelectedIter);
if (Node != nullptr && Node->CanUserDeleteNode())
{
return true;
}
}
return false;
}
void FAssetEditor_ComboActionGraph::DeleteSelectedDuplicatableNodes()
{
TSharedPtr<SGraphEditor> CurrentGraphEditor = this->GetCurrentGraphEditor();
if (!CurrentGraphEditor.IsValid())
{
return;
}
const FGraphPanelSelectionSet OldSelectedNodes = CurrentGraphEditor->GetSelectedNodes();
CurrentGraphEditor->ClearSelectionSet();
for (FGraphPanelSelectionSet::TConstIterator SelectedIter(OldSelectedNodes); SelectedIter; ++SelectedIter)
{
UEdGraphNode *Node = Cast<UEdGraphNode>(*SelectedIter);
if (Node && Node->CanDuplicateNode())
{
CurrentGraphEditor->SetNodeSelection(Node, true);
}
}
// Delete the duplicatable nodes
this->DeleteSelectedNodes();
CurrentGraphEditor->ClearSelectionSet();
for (FGraphPanelSelectionSet::TConstIterator SelectedIter(OldSelectedNodes); SelectedIter; ++SelectedIter)
{
if (UEdGraphNode* Node = Cast<UEdGraphNode>(*SelectedIter))
{
CurrentGraphEditor->SetNodeSelection(Node, true);
}
}
}
void FAssetEditor_ComboActionGraph::CutSelectedNodes()
{
this->CopySelectedNodes();
this->DeleteSelectedDuplicatableNodes();
}
void FAssetEditor_ComboActionGraph::CopySelectedNodes()
{
// Export the selected nodes and place the text on the clipboard
FGraphPanelSelectionSet SelectedNodes = this->GetSelectedNodes();
FString ExportedText;
for (FGraphPanelSelectionSet::TIterator SelectedIter(SelectedNodes); SelectedIter; ++SelectedIter)
{
UEdGraphNode *Node = Cast<UEdGraphNode>(*SelectedIter);
if (Node == nullptr)
{
SelectedIter.RemoveCurrent();
continue;
}
if (UEdComboActionGraphEdge *EdNode_Edge = Cast<UEdComboActionGraphEdge>(*SelectedIter))
{
UEdComboActionGraphNode *StartNode = EdNode_Edge->GetStartNode();
UEdComboActionGraphNode *EndNode = EdNode_Edge->GetEndNode();
if (!SelectedNodes.Contains(StartNode) || !SelectedNodes.Contains(EndNode))
{
SelectedIter.RemoveCurrent();
continue;
}
}
Node->PrepareForCopying();
}
FEdGraphUtilities::ExportNodesToText(SelectedNodes, ExportedText);
FPlatformApplicationMisc::ClipboardCopy(*ExportedText);
}
bool FAssetEditor_ComboActionGraph::CanCopyNodes() const
{
// If any of the nodes can be duplicated then we should allow copying
const FGraphPanelSelectionSet SelectedNodes = this->GetSelectedNodes();
for (FGraphPanelSelectionSet::TConstIterator SelectedIter(SelectedNodes); SelectedIter; ++SelectedIter)
{
UEdGraphNode *Node = Cast<UEdGraphNode>(*SelectedIter);
if (Node && Node->CanDuplicateNode() == false)
{
return false;
}
}
return true;
}
void FAssetEditor_ComboActionGraph::PasteNodes()
{
TSharedPtr<SGraphEditor> CurrentGraphEditor = this->GetCurrentGraphEditor();
if (CurrentGraphEditor.IsValid())
{
this->PasteNodesHere(CurrentGraphEditor->GetPasteLocation());
}
}
void FAssetEditor_ComboActionGraph::PasteNodesHere(const FVector2D &Location)
{
// Find the graph editor with focus
TSharedPtr<SGraphEditor> CurrentGraphEditor = this->GetCurrentGraphEditor();
if (!CurrentGraphEditor.IsValid())
{
return;
}
// Select the newly pasted stuff
UEdGraph *EdGraph = CurrentGraphEditor->GetCurrentGraph();
{
const FScopedTransaction Transaction(FGenericCommands::Get().Paste->GetDescription());
EdGraph->Modify();
// Clear the selection set (newly pasted stuff will be selected)
CurrentGraphEditor->ClearSelectionSet();
// Grab the text to paste from the clipboard.
FString TextToImport;
FPlatformApplicationMisc::ClipboardPaste(TextToImport);
// Import the nodes
TSet<UEdGraphNode*> PastedNodes;
FEdGraphUtilities::ImportNodesFromText(EdGraph, TextToImport, PastedNodes);
//Average position of nodes so we can move them while still maintaining relative distances to each other
FVector2D AvgNodePosition(0.0f, 0.0f);
for (TSet<UEdGraphNode*>::TIterator It(PastedNodes); It; ++It)
{
UEdGraphNode* Node = *It;
AvgNodePosition.X += Node->NodePosX;
AvgNodePosition.Y += Node->NodePosY;
}
float InvNumNodes = 1.0f / float(PastedNodes.Num());
AvgNodePosition.X *= InvNumNodes;
AvgNodePosition.Y *= InvNumNodes;
// 0 is always Start Node!
int32 SharedIndex = EditingGraph->GetAllNodes().Num();
for (TSet<UEdGraphNode*>::TIterator It(PastedNodes); It; ++It)
{
UEdGraphNode* Node = *It;
CurrentGraphEditor->SetNodeSelection(Node, true);
Node->NodePosX = (Node->NodePosX - AvgNodePosition.X) + Location.X;
Node->NodePosY = (Node->NodePosY - AvgNodePosition.Y) + Location.Y;
Node->SnapToGrid(16);
// Give new node a different Guid from the old one
Node->CreateNewGuid();
if (UEdComboActionGraphNode *MounteaNode = Cast<UEdComboActionGraphNode>(Node))
{
if (MounteaNode->ComboActionGraphNode)
{
MounteaNode->ComboActionGraphNode->OnPasted();
//MounteaNode->SetDialogueNodeIndex(SharedIndex);
}
SharedIndex++;
}
}
}
// Update UI
CurrentGraphEditor->NotifyGraphChanged();
UObject *GraphOwner = EdGraph->GetOuter();
if (GraphOwner)
{
GraphOwner->PostEditChange();
GraphOwner->MarkPackageDirty();
}
this->RebuildComboActionGraph();
}
bool FAssetEditor_ComboActionGraph::CanPasteNodes() const
{
const FGraphPanelSelectionSet SelectedNodes = this->GetSelectedNodes();
for (FGraphPanelSelectionSet::TConstIterator SelectedIter(SelectedNodes); SelectedIter; ++SelectedIter)
{
const UEdComboActionGraphNode *Node = Cast<UEdComboActionGraphNode>(*SelectedIter);
if (Node && Node->CanUserPasteNodes() == false)
{
return false;
}
}
TSharedPtr<SGraphEditor> CurrentGraphEditor = this->GetCurrentGraphEditor();
if (!CurrentGraphEditor.IsValid())
{
return false;
}
FString ClipboardContent;
FPlatformApplicationMisc::ClipboardPaste(ClipboardContent);
return FEdGraphUtilities::CanImportNodesFromText(CurrentGraphEditor->GetCurrentGraph(), ClipboardContent);
}
void FAssetEditor_ComboActionGraph::AutoArrange()
{
// For now, auto-arrange is broken. Just in case it can be activated by accident,
// show a popup saying as much if an auto-arrange is attempted.
TArray<FText> ValidationMessages;
ValidationMessages.Add(LOCTEXT("AssetEditor_ComboActionGraph_AutoArrangeDisabled", "Auto-arrange is currently broken."));
ComboInputPopup_GraphValidation::Open(ValidationMessages);
//UEdComboActionGraph *EdGraph = Cast<UEdComboActionGraph>(this->EditingGraph->EdGraph);
//check(EdGraph != nullptr);
//const FScopedTransaction Transaction(LOCTEXT("ComboActionGraphEditorAutoArrange", "Combo Action Graph Editor: Auto Arrange all Nodes"));
//EdGraph->Modify(true);
//UComboActionGraphLayoutStrategy *LayoutStrategy = nullptr;
//switch (this->ComboActionGraphEditorSettings->GetAutoLayoutStrategy())
//{
// case EComboActionAutoLayoutStrategyType::Tree:
// LayoutStrategy = NewObject<UComboActionGraphLayoutStrategy>(EdGraph, UComboActionTreeSolveLayoutStrategy::StaticClass());
// break;
// case EComboActionAutoLayoutStrategyType::ForceDirected:
// LayoutStrategy = NewObject<UComboActionGraphLayoutStrategy>(EdGraph, UComboActionForceDirectedSolveLayoutStrategy::StaticClass());
// break;
// default:
// break;
//}
//if (LayoutStrategy != nullptr)
//{
// LayoutStrategy->Layout(EdGraph);
// LayoutStrategy->ConditionalBeginDestroy();
//}
//else
//{
// UE_LOG(LogAssetEditorComboActionGraph, Error, TEXT("[AutoArrange] LayoutStrategy is null."));
//}
}
bool FAssetEditor_ComboActionGraph::CanAutoArrange() const
{
return this->EditingGraph != nullptr && Cast<UEdComboActionGraph>(this->EditingGraph->EdGraph) != nullptr;
}
void FAssetEditor_ComboActionGraph::ValidateGraph()
{
if (this->ValidationWindow.IsValid())
{
this->ValidationWindow->RequestDestroyWindow();
}
UEdComboActionGraph *EdGraph = Cast<UEdComboActionGraph>(this->EditingGraph->EdGraph);
check(EdGraph != nullptr);
const FScopedTransaction Transaction(LOCTEXT("ComboActionGraphEditorValidateGraph", "Combo Action Graph Editor: Validate Graph."));
UComboActionGraph *ComboActionGraph = EdGraph->GetComboActionGraph();
check(ComboActionGraph != nullptr);
this->RebuildComboActionGraph();
TArray<FText> ValidationMessages;
if (ComboActionGraph->ValidateGraph(ValidationMessages, true) == false)
{
this->ValidationWindow = ComboInputPopup_GraphValidation::Open(ValidationMessages);
}
else
{
ValidationMessages.Empty();
this->ValidationWindow = ComboInputPopup_GraphValidation::Open(ValidationMessages);
}
}
void FAssetEditor_ComboActionGraph::OnRenameNode()
{
TSharedPtr<SGraphEditor> CurrentGraphEditor = this->GetCurrentGraphEditor();
if (CurrentGraphEditor.IsValid())
{
const FGraphPanelSelectionSet SelectedNodes = GetSelectedNodes();
for (FGraphPanelSelectionSet::TConstIterator NodeIt(SelectedNodes); NodeIt; ++NodeIt)
{
UEdGraphNode *SelectedNode = Cast<UEdGraphNode>(*NodeIt);
if (SelectedNode != nullptr && SelectedNode->bCanRenameNode)
{
CurrentGraphEditor->IsNodeTitleVisible(SelectedNode, true);
break;
}
}
}
}
bool FAssetEditor_ComboActionGraph::CanRenameNodes() const
{
check(this->GetSettings() != nullptr);
return this->GetSettings()->AllowRenameNodes() == true && this->GetSelectedNodes().Num() == 1;
}
void FAssetEditor_ComboActionGraph::OnSelectedNodesChanged(const TSet<UObject*> &NewSelection)
{
TArray<UObject*> Selection;
for (UObject* SelectionEntry : NewSelection)
{
Selection.Add(SelectionEntry);
}
if (Selection.Num() == 1)
{
// When just one node is selected, add it to PropertyWidget
this->PropertyWidget->SetObjects(Selection);
this->PropertyWidget->ShowAllAdvancedProperties();
//UComboActionEditorBFC::TriggerPreviewRefresh(Selection);
}
else
{
this->PropertyWidget->SetObject(this->EditingGraph);
}
this->RebuildComboActionGraph();
}
void FAssetEditor_ComboActionGraph::OnNodeDoubleClicked(UEdGraphNode *Node)
{
this->GraphEditorCommands->TryExecuteAction(FGenericCommands::Get().Rename.ToSharedRef());
}
void FAssetEditor_ComboActionGraph::OnFinishedChangingProperties(const FPropertyChangedEvent& PropertyChangedEvent)
{
if (this->EditingGraph == nullptr)
return;
this->EditingGraph->EdGraph->GetSchema()->ForceVisualizationCacheClear();
this->RebuildComboActionGraph();
}
TSharedRef<SDockTab> FAssetEditor_ComboActionGraph::SpawnTab_Viewport(const FSpawnTabArgs &Args)
{
check(Args.GetTabId() == FAssetEditorTabs_ComboActionGraph::ViewportID);
TSharedRef<SDockTab> SpawnedTab = SNew(SDockTab)
.Label(LOCTEXT("ViewportTab_Title", "Viewport"));
if (this->ViewportWidget.IsValid())
{
SpawnedTab->SetContent(this->ViewportWidget.ToSharedRef());
}
return SpawnedTab;
}
TSharedRef<SDockTab> FAssetEditor_ComboActionGraph::SpawnTab_Details(const FSpawnTabArgs& Args)
{
check(Args.GetTabId() == FAssetEditorTabs_ComboActionGraph::ComboActionGraphPropertyID);
auto DockTab = SNew(SDockTab)
.Label(LOCTEXT("Details_Title", "Property"))
[
this->PropertyWidget.ToSharedRef()
];
DockTab->SetTabIcon(FAppStyle::GetBrush("LevelEditor.Tabs.Details"));
return DockTab;
}
TSharedRef<SDockTab> FAssetEditor_ComboActionGraph::SpawnTab_Search(const FSpawnTabArgs &Args)
{
check(Args.GetTabId() == FAssetEditorTabs_ComboActionGraph::SearchToolbarID);
auto DockTab = SNew(SDockTab)
.Label(LOCTEXT("Search_Title", "Search"))
[
this->FindResultsView.ToSharedRef()
];
DockTab->SetTabIcon(FAppStyle::GetBrush("Kismet.Tabs.FindResults"));
return DockTab;
}
#undef LOCTEXT_NAMESPACE

View File

@ -0,0 +1,167 @@
// ©2023 Batty Bovine Productions, LLC. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "Toolkits/AssetEditorToolkit.h"
DECLARE_LOG_CATEGORY_EXTERN(LogAssetEditorComboActionGraph, Log, All);
class COMBOINPUTEDITOR_API FAssetEditor_ComboActionGraph : public FAssetEditorToolkit, public FNotifyHook, public FGCObject
{
public:
FAssetEditor_ComboActionGraph();
virtual ~FAssetEditor_ComboActionGraph() override;
void OnPackageSaved(const FString &String, UPackage *Package, FObjectPostSaveContext ObjectPostSaveContext);
void InitComboActionGraphAssetEditor(const EToolkitMode::Type Mode, const TSharedPtr<IToolkitHost> &InitToolkitHost, class UComboActionGraph *Graph);
class UComboActionGraphEditorSettings *GetSettings() const;
#pragma region ToolkitInterface
public:
// IToolkit interface
virtual void RegisterTabSpawners(const TSharedRef<FTabManager> &InTabManager) override;
virtual void UnregisterTabSpawners(const TSharedRef<FTabManager> &InTabManager) override;
virtual bool CloseWindow(EAssetEditorCloseReason InCloseReason) override;
// End of IToolkit interface
#pragma endregion
#pragma region AssetEditorToolkit
virtual FName GetToolkitFName() const override;
virtual FText GetBaseToolkitName() const override;
virtual FText GetToolkitName() const override;
virtual FText GetToolkitToolTipText() const override;
virtual FLinearColor GetWorldCentricTabColorScale() const override;
virtual FString GetWorldCentricTabPrefix() const override;
virtual FString GetDocumentationLink() const override;
virtual void SaveAsset_Execute() override;
#pragma endregion
#pragma region Toolbar
void UpdateToolbar();
TSharedPtr<class FAssetEditorToolbarComboActionGraph> GetToolbarBuilder() { return this->ToolbarBuilder; }
void RegisterToolbarTab(const TSharedRef<class FTabManager> &InTabManager);
#pragma endregion
#pragma region SerializableObjectInterface
virtual void AddReferencedObjects(FReferenceCollector &Collector) override;
#pragma endregion
#pragma region FGCObject
virtual FString GetReferencerName() const override;
#pragma endregion
// Gets/Sets the dialogue being edited
class UComboActionGraph *GetEditingGraphSafe()
{
check(EditingGraph);
return EditingGraph;
}
void SetDialogueBeingEdited(class UComboActionGraph *NewDialogue);
virtual void JumpToNode(const class UEdGraphNode *JumpToMe, bool bRequestRename = false, bool bSelectNode = true)
{
if (this->ViewportWidget.IsValid())
{
this->ViewportWidget->JumpToNode(JumpToMe, bRequestRename, bSelectNode);
}
}
private:
void CreateInternalWidgets();
TSharedRef<SGraphEditor> CreateViewportWidget();
void BindCommands();
void CreateEdGraph();
void CreateCommandList();
TSharedPtr<SGraphEditor> GetCurrentGraphEditor() const;
FGraphPanelSelectionSet GetSelectedNodes() const;
void RebuildComboActionGraph();
#pragma region GraphEditorCommands
void SelectAllNodes();
bool CanSelectAllNodes() const { return true; }
void DeleteSelectedNodes();
bool CanDeleteNodes() const;
void DeleteSelectedDuplicatableNodes();
void CutSelectedNodes();
bool CanCutNodes() const { return this->CanCopyNodes() && this->CanDeleteNodes(); }
void CopySelectedNodes();
bool CanCopyNodes() const;
void PasteNodes();
void PasteNodesHere(const FVector2D &Location);
bool CanPasteNodes() const;
void DuplicateNodes() { this->CopySelectedNodes(); this->PasteNodes(); }
bool CanDuplicateNodes() { return this->CanCopyNodes(); }
void AutoArrange();
bool CanAutoArrange() const;
void ValidateGraph();
bool CanValidateGraph() const { return true; }
void OnRenameNode();
bool CanRenameNodes() const;
#pragma endregion
#pragma region GraphEditorEvents
void OnSelectedNodesChanged(const TSet<class UObject*> &NewSelection);
void OnNodeDoubleClicked(UEdGraphNode *Node);
void OnFinishedChangingProperties(const FPropertyChangedEvent &PropertyChangedEvent);
void OnPackageSaved(const FString &PackageFileName, UObject *Outer);
#pragma endregion
#pragma region Variables
private:
TSharedRef<SDockTab> SpawnTab_Viewport(const FSpawnTabArgs &Args);
TSharedRef<SDockTab> SpawnTab_Details(const FSpawnTabArgs &Args);
TSharedRef<SDockTab> SpawnTab_Search(const FSpawnTabArgs &Args);
class UComboActionGraphEditorSettings *ComboActionGraphEditorSettings;
TObjectPtr<class UComboActionGraph> EditingGraph;
//Toolbar
TSharedPtr<FAssetEditorToolbarComboActionGraph> ToolbarBuilder;
/** Handle to the registered OnPackageSave delegate */
FDelegateHandle OnPackageSavedDelegateHandle;
TSharedPtr<SGraphEditor> ViewportWidget;
TSharedPtr<class IDetailsView> PropertyWidget;
TSharedPtr<class SComboActionSearch> FindResultsView;
/** The command list for this editor */
TSharedPtr<FUICommandList> GraphEditorCommands;
TSharedPtr<SWindow> ValidationWindow;
#pragma endregion
};

View File

@ -0,0 +1,210 @@
// ©2023 Batty Bovine Productions, LLC. All Rights Reserved.
#include "Ed/EdComboActionGraph.h"
#include "ComboActionGraph.h"
#include "Ed/AssetEditor_ComboActionGraph.h"
#include "Ed/EdComboActionGraphEdge.h"
#include "Ed/EdComboActionGraphNode.h"
#include "Nodes/ComboActionGraphEdge.h"
#include "Nodes/ComboActionGraphNode.h"
//#include "Helpers/MounteaDialogueGraphEditorHelpers.h"
//#include "Helpers/MounteaDialogueGraphEditorUtilities.h"
//#include "Helpers/MounteaDialogueSystemEditorBFC.h"
DEFINE_LOG_CATEGORY(LogEdComboActionGraph);
void UEdComboActionGraph::RebuildComboActionGraph()
{
UE_LOG(LogEdComboActionGraph, Warning, TEXT("UEdComboActionGraph::RebuildComboActionGraph has been called"));
UComboActionGraph *Graph = GetComboActionGraph();
this->Clear();
for (const TObjectPtr<UEdGraphNode> &Node : this->Nodes)
{
if (UEdComboActionGraphNode *EdNode = Cast<UEdComboActionGraphNode>(Node))
{
if (EdNode->ComboActionGraphNode == nullptr)
continue;
UComboActionGraphNode *ComboActionGraphNode = EdNode->ComboActionGraphNode;
this->NodeMap.Add(ComboActionGraphNode, EdNode);
Graph->AllNodes.Add(ComboActionGraphNode);
//EdNode->SetDialogueNodeIndex( Graph->AllNodes.Find(EdNode->ComboActionGraphNode) );
for (int PinIdx = 0; PinIdx < EdNode->Pins.Num(); ++PinIdx)
{
UEdGraphPin* Pin = EdNode->Pins[PinIdx];
if (Pin->Direction != EEdGraphPinDirection::EGPD_Output)
continue;
for (int LinkToIdx = 0; LinkToIdx < Pin->LinkedTo.Num(); ++LinkToIdx)
{
UComboActionGraphNode *ChildNode = nullptr;
if (UEdComboActionGraphNode *EdNode_Child = Cast<UEdComboActionGraphNode>(Pin->LinkedTo[LinkToIdx]->GetOwningNode()))
{
ChildNode = EdNode_Child->ComboActionGraphNode;
}
else if (UEdComboActionGraphEdge *EdNode_Edge = Cast<UEdComboActionGraphEdge>(Pin->LinkedTo[LinkToIdx]->GetOwningNode()))
{
UEdComboActionGraphNode *Child = EdNode_Edge->GetEndNode();;
if (Child != nullptr)
{
ChildNode = Child->ComboActionGraphNode;
}
}
if (ChildNode != nullptr)
{
ComboActionGraphNode->ChildNodes.Add(ChildNode);
ChildNode->ParentNodes.Add(ComboActionGraphNode);
}
else
{
UE_LOG(LogEdComboActionGraph, Error, TEXT("[RebuildComboActionGraph] Can't find child node"));
}
}
}
}
else if (UEdComboActionGraphEdge *EdgeNode = Cast<UEdComboActionGraphEdge>(Node))
{
UEdComboActionGraphNode *StartNode = EdgeNode->GetStartNode();
UEdComboActionGraphNode *EndNode = EdgeNode->GetEndNode();
UComboActionGraphEdge *Edge = EdgeNode->ComboActionGraphEdge;
if (StartNode == nullptr || EndNode == nullptr || Edge == nullptr)
{
UE_LOG(LogEdComboActionGraph, Error, TEXT("[RebuildComboActionGraph] Add edge failed."));
continue;
}
this->EdgeMap.Add(Edge, EdgeNode);
Edge->SetGraph(Graph);
Edge->Rename(nullptr, Graph, REN_DontCreateRedirectors | REN_DoNotDirty);
StartNode->ComboActionGraphNode->Edges.Add(EndNode->ComboActionGraphNode, Edge);
Edge->SetStartNode(StartNode->ComboActionGraphNode);
Edge->SetEndNode(EndNode->ComboActionGraphNode);
}
}
for (UComboActionGraphNode *Node : Graph->AllNodes)
{
if (Node->ParentNodes.Num() == 0)
{
Graph->RootNodes.Add(Node);
SortNodes(Node);
}
Node->Graph = Graph;
Node->Rename(nullptr, Graph, REN_DontCreateRedirectors | REN_DoNotDirty);
}
Graph->RootNodes.Sort([&](const UComboActionGraphNode &L, const UComboActionGraphNode &R)
{
UEdComboActionGraphNode *EdNode_LNode = this->NodeMap[&L];
UEdComboActionGraphNode *EdNode_RNode = this->NodeMap[&R];
return EdNode_LNode->NodePosX < EdNode_RNode->NodePosX;
});
}
UComboActionGraph *UEdComboActionGraph::GetComboActionGraph() const
{
return CastChecked<UComboActionGraph>(GetOuter());
}
bool UEdComboActionGraph::Modify(bool bAlwaysMarkDirty)
{
bool Rtn = Super::Modify(bAlwaysMarkDirty);
this->GetComboActionGraph()->Modify();
for (TObjectPtr<UEdGraphNode> &Node : this->Nodes)
{
Node->Modify();
}
return Rtn;
}
void UEdComboActionGraph::PostEditUndo()
{
this->NotifyGraphChanged();
Super::PostEditUndo();
}
void UEdComboActionGraph::SetDialogueEditorPtr(TWeakPtr<FAssetEditor_ComboActionGraph> NewPtr)
{
this->ComboActionEditorPtr = NewPtr;
}
bool UEdComboActionGraph::JumpToNode(const UComboActionGraphNode *Node)
{
//return FComboActionGraphEditorUtilities::OpenEditorAndJumpToGraphNode(this->ComboActionEditorPtr, *NodeMap.Find(Node));
return false;
}
void UEdComboActionGraph::Clear()
{
UComboActionGraph *Graph = this->GetComboActionGraph();
Graph->ClearGraph();
this->NodeMap.Reset();
this->EdgeMap.Reset();
for (int i = 0; i < this->Nodes.Num(); ++i)
{
if (UEdComboActionGraphNode *EdNode = Cast<UEdComboActionGraphNode>(Nodes[i]))
{
UComboActionGraphNode *MounteaDialogueGraphNode = EdNode->ComboActionGraphNode;
MounteaDialogueGraphNode->ParentNodes.Reset();
MounteaDialogueGraphNode->ChildNodes.Reset();
MounteaDialogueGraphNode->Edges.Reset();
}
}
}
void UEdComboActionGraph::SortNodes(UComboActionGraphNode *RootNode)
{
int Level = 0;
TArray<UComboActionGraphNode*> CurrLevelNodes = { RootNode };
TArray<UComboActionGraphNode*> NextLevelNodes;
while (CurrLevelNodes.Num() != 0)
{
int32 LevelWidth = 0;
for (int i = 0; i < CurrLevelNodes.Num(); ++i)
{
UComboActionGraphNode *Node = CurrLevelNodes[i];
auto Comp = [&](const UComboActionGraphNode &L, const UComboActionGraphNode &R)
{
UEdComboActionGraphNode *EdNode_LNode = NodeMap[&L];
UEdComboActionGraphNode *EdNode_RNode = NodeMap[&R];
return EdNode_LNode->NodePosX < EdNode_RNode->NodePosX;
};
Node->ChildNodes.Sort(Comp);
Node->ParentNodes.Sort(Comp);
for (int j = 0; j < Node->ChildNodes.Num(); ++j)
{
NextLevelNodes.Add(Node->ChildNodes[j]);
}
}
CurrLevelNodes = NextLevelNodes;
NextLevelNodes.Reset();
++Level;
}
}

View File

@ -0,0 +1,46 @@
// ©2023 Batty Bovine Productions, LLC. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "EdGraph/EdGraph.h"
#include "EdComboActionGraph.generated.h"
DECLARE_LOG_CATEGORY_EXTERN(LogEdComboActionGraph, Log, All);
UCLASS()
class COMBOINPUTEDITOR_API UEdComboActionGraph : public UEdGraph
{
GENERATED_BODY()
public:
virtual void RebuildComboActionGraph();
UComboActionGraph *GetComboActionGraph() const;
virtual bool Modify(bool bAlwaysMarkDirty) override;
virtual void PostEditUndo() override;
TWeakPtr<class FAssetEditor_ComboActionGraph> GetComboActionEditorPtr() const { return this->ComboActionEditorPtr; }
void SetDialogueEditorPtr(TWeakPtr<class FAssetEditor_ComboActionGraph> NewPtr);
void ResetDialogueEditorPtr() { this->ComboActionEditorPtr.Reset(); }
bool JumpToNode(const class UComboActionGraphNode *Node);
public:
UPROPERTY(Transient)
TMap<class UComboActionGraphNode*, class UEdComboActionGraphNode*> NodeMap;
UPROPERTY(Transient)
TMap<class UComboActionGraphEdge*, class UEdComboActionGraphEdge*> EdgeMap;
protected:
void Clear();
void SortNodes(UComboActionGraphNode *RootNode);
private:
/** Pointer back to the combo action editor that owns us */
TWeakPtr<FAssetEditor_ComboActionGraph> ComboActionEditorPtr;
};

View File

@ -0,0 +1,83 @@
// ©2023 Batty Bovine Productions, LLC. All Rights Reserved.
#include "EdComboActionGraphEdge.h"
#include "EdComboActionGraphNode.h"
#include "Nodes/ComboActionGraphEdge.h"
void UEdComboActionGraphEdge::SetEdge(UComboActionGraphEdge *Edge)
{
this->ComboActionGraphEdge = Edge;
}
void UEdComboActionGraphEdge::AllocateDefaultPins()
{
UEdGraphPin* Inputs = this->CreatePin(EGPD_Input, TEXT("Edge"), FName(), TEXT("In"));
Inputs->bHidden = true;
UEdGraphPin* Outputs = this->CreatePin(EGPD_Output, TEXT("Edge"), FName(), TEXT("Out"));
Outputs->bHidden = true;
}
FText UEdComboActionGraphEdge::GetNodeTitle(ENodeTitleType::Type TitleType) const
{
return Super::GetNodeTitle(TitleType);
}
void UEdComboActionGraphEdge::PinConnectionListChanged(UEdGraphPin *Pin)
{
if (Pin->LinkedTo.Num() == 0)
{
// Commit suicide; transitions must always have an input and output connection
Modify();
// Our parent graph will have our graph in SubGraphs so needs to be modified to record that.
if (UEdGraph *ParentGraph = GetGraph())
{
ParentGraph->Modify();
}
DestroyNode();
}
}
void UEdComboActionGraphEdge::PrepareForCopying()
{
this->ComboActionGraphEdge->Rename(nullptr, this, REN_DontCreateRedirectors | REN_DoNotDirty);
}
void UEdComboActionGraphEdge::CreateConnections(UEdComboActionGraphNode *Start, UEdComboActionGraphNode *End)
{
Pins[0]->Modify();
Pins[0]->LinkedTo.Empty();
Start->GetOutputPin()->Modify();
Pins[0]->MakeLinkTo(Start->GetOutputPin());
// This to next
Pins[1]->Modify();
Pins[1]->LinkedTo.Empty();
End->GetInputPin()->Modify();
Pins[1]->MakeLinkTo(End->GetInputPin());
}
UEdComboActionGraphNode *UEdComboActionGraphEdge::GetStartNode()
{
if (Pins[0]->LinkedTo.Num() > 0)
{
return Cast<UEdComboActionGraphNode>(Pins[0]->LinkedTo[0]->GetOwningNode());
}
return nullptr;
}
UEdComboActionGraphNode *UEdComboActionGraphEdge::GetEndNode()
{
if (Pins[1]->LinkedTo.Num() > 0)
{
return Cast<UEdComboActionGraphNode>(Pins[1]->LinkedTo[0]->GetOwningNode());
}
return nullptr;
}

View File

@ -0,0 +1,45 @@
// ©2023 Batty Bovine Productions, LLC. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "EdGraph/EdGraphNode.h"
#include "EdComboActionGraphEdge.generated.h"
/**
*
*/
UCLASS(MinimalAPI)
class UEdComboActionGraphEdge : public UEdGraphNode
{
GENERATED_BODY()
public:
UPROPERTY()
class UEdGraph *Graph;
UPROPERTY(VisibleAnywhere, Instanced, Category="Combo Action Graph")
class UComboActionGraphEdge *ComboActionGraphEdge;
public:
void SetEdge(class UComboActionGraphEdge *Edge);
virtual void AllocateDefaultPins() override;
virtual FText GetNodeTitle(ENodeTitleType::Type TitleType) const override;
virtual void PinConnectionListChanged(UEdGraphPin *Pin) override;
virtual void PrepareForCopying() override;
virtual UEdGraphPin *GetInputPin() const { return Pins[0]; }
virtual UEdGraphPin *GetOutputPin() const { return Pins[1]; }
void CreateConnections(class UEdComboActionGraphNode *Start, class UEdComboActionGraphNode* End);
class UEdComboActionGraphNode *GetStartNode();
class UEdComboActionGraphNode *GetEndNode();
};

View File

@ -0,0 +1,173 @@
// ©2023 Batty Bovine Productions, LLC. All Rights Reserved.
#include "EdComboActionGraphNode.h"
#include "Ed/EdComboActionGraph.h"
#include "Helpers/ComboActionEditorBFC.h"
#include "Nodes/ComboActionGraphNode.h"
#include "Settings/ComboActionGraphEditorSettings.h"
#define LOCTEXT_NAMESPACE "UEdComboActionGraphNode"
DEFINE_LOG_CATEGORY(LogEdComboActionGraphNode);
UEdComboActionGraphNode::UEdComboActionGraphNode()
{
bCanRenameNode = true;
bAllowCopy = true;
bAllowDelete = true;
bAllowDuplicate = true;
bAllowPaste = true;
}
void UEdComboActionGraphNode::SetComboActionGraphNode(UComboActionGraphNode *NewNode)
{
this->ComboActionGraphNode = NewNode;
if (this->ComboActionGraphNode)
{
this->bAllowCopy = this->ComboActionGraphNode->bAllowCopy;
this->bAllowDelete = this->ComboActionGraphNode->bAllowDelete;
this->bAllowDuplicate = this->ComboActionGraphNode->bAllowPaste;
this->bAllowPaste = this->ComboActionGraphNode->bAllowPaste;
}
}
UEdComboActionGraph *UEdComboActionGraphNode::GetEdComboActionGraph() const
{
return Cast<UEdComboActionGraph>(this->GetGraph());
};
void UEdComboActionGraphNode::AllocateDefaultPins()
{
if (this->ComboActionGraphNode == nullptr)
{
UE_LOG(LogEdComboActionGraphNode, Error, TEXT("[AllocateDefaultPins] Cannot find Owning Graph Node!"));
return;
}
if (this->ComboActionGraphNode->bAllowInputNodes)
{
this->CreatePin(EGPD_Input, "MultipleNodes", FName(), TEXT("In"));
}
if (this->ComboActionGraphNode->bAllowOutputNodes)
{
this->CreatePin(EGPD_Output, "MultipleNodes", FName(), TEXT("Out"));
}
}
FText UEdComboActionGraphNode::GetNodeTitle(ENodeTitleType::Type TitleType) const
{
return UComboActionEditorBFC::GetNodeTitle(this->ComboActionGraphNode);
}
void UEdComboActionGraphNode::PrepareForCopying()
{
this->ComboActionGraphNode->Rename(nullptr, this, REN_DontCreateRedirectors | REN_DoNotDirty);
}
void UEdComboActionGraphNode::AutowireNewNode(UEdGraphPin* FromPin)
{
Super::AutowireNewNode(FromPin);
if (FromPin != nullptr)
{
if (GetSchema()->TryCreateConnection(FromPin, GetInputPin()))
{
FromPin->GetOwningNode()->NodeConnectionListChanged();
}
}
}
UEdGraphPin* UEdComboActionGraphNode::GetInputPin() const
{
return Pins[0];
}
UEdGraphPin* UEdComboActionGraphNode::GetOutputPin() const
{
if (Pins.IsValidIndex(1))
{
return Pins[1];
}
return Pins[0];
}
bool UEdComboActionGraphNode::CanUserDeleteNode() const
{
if(!Super::CanUserDeleteNode())
{
return false;
}
if (this->ComboActionGraphNode)
{
return this->ComboActionGraphNode->bAllowDelete;
}
return bAllowDelete;
}
bool UEdComboActionGraphNode::CanDuplicateNode() const
{
if(!Super::CanUserDeleteNode())
{
return false;
}
if (this->ComboActionGraphNode)
{
return this->ComboActionGraphNode->bAllowCopy;
}
return bAllowCopy;
}
bool UEdComboActionGraphNode::CanUserPasteNodes() const
{
if (this->ComboActionGraphNode)
{
return this->ComboActionGraphNode->bAllowPaste;
}
return bAllowPaste;
}
FText UEdComboActionGraphNode::GetTooltipText() const
{
if (this->ComboActionGraphNode)
{
return this->ComboActionGraphNode->GetNodeTooltipText();
}
return NSLOCTEXT("UEdComboActionGraphNode", "DefaultToolTip", "Combo Action Node");
}
#if WITH_EDITOR
FLinearColor UEdComboActionGraphNode::GetBackgroundColor() const
{
return this->ComboActionGraphNode ? this->ComboActionGraphNode->GetBackgroundColour() : FLinearColor::Black;
}
FSlateIcon UEdComboActionGraphNode::GetIconAndTint(FLinearColor& OutColor) const
{
static const FSlateIcon Icon = FSlateIcon(FComboInputSlateStyle::GetAppStyleSetName(), "MDSStyleSet.Node.Icon.small");
OutColor = this->ComboActionGraphNode->GetBackgroundColour();
return Icon;
}
#endif
void UEdComboActionGraphNode::PostEditUndo()
{
Super::PostEditUndo();
}
void UEdComboActionGraphNode::PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent)
{
Super::PostEditChangeProperty(PropertyChangedEvent);
}
#undef LOCTEXT_NAMESPACE

View File

@ -0,0 +1,62 @@
// ©2023 Batty Bovine Productions, LLC. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "SEdComboActionGraphNode.h"
#include "EdGraph/EdGraphNode.h"
#include "EdComboActionGraphNode.generated.h"
DECLARE_LOG_CATEGORY_EXTERN(LogEdComboActionGraphNode, Log, All);
/**
*
*/
UCLASS(MinimalAPI)
class UEdComboActionGraphNode : public UEdGraphNode
{
GENERATED_BODY()
public:
UEdComboActionGraphNode();
void SetComboActionGraphNode(UComboActionGraphNode *NewNode);
class UEdComboActionGraph *GetEdComboActionGraph() const;
virtual void AllocateDefaultPins() override;
virtual FText GetNodeTitle(ENodeTitleType::Type TitleType) const override;
virtual void PrepareForCopying() override;
virtual void AutowireNewNode(UEdGraphPin *FromPin) override;
virtual UEdGraphPin *GetInputPin() const;
virtual UEdGraphPin *GetOutputPin() const;
virtual bool CanUserDeleteNode() const override;
virtual bool CanDuplicateNode() const override;
virtual bool CanUserPasteNodes() const;
virtual FText GetTooltipText() const override;
#if WITH_EDITOR
virtual void PostEditUndo() override;
virtual void PostEditChangeProperty(FPropertyChangedEvent &PropertyChangedEvent) override;
virtual FLinearColor GetBackgroundColor() const;
virtual FSlateIcon GetIconAndTint(FLinearColor &OutColor) const override;
#endif
UPROPERTY(VisibleAnywhere, Instanced, Category="Combo Action Graph")
class UComboActionGraphNode *ComboActionGraphNode;
SEdComboActionGraphNode *SEdNode;
private:
bool bAllowCopy;
bool bAllowDelete;
bool bAllowDuplicate;
bool bAllowPaste;
};

View File

@ -0,0 +1,44 @@
// ©2023 Batty Bovine Productions, LLC. All Rights Reserved.
#include "FAssetEditorToolbarComboActionGraph.h"
#include "ComboInputEditor.h"
#include "Ed/AssetEditor_ComboActionGraph.h"
#include "Ed/FComboActionGraphEditorCommands.h"
#define LOCTEXT_NAMESPACE "AssetEditorToolbarComboActionGraph"
void FAssetEditorToolbarComboActionGraph::AddComboActionGraphToolbar(TSharedPtr<FExtender> Extender)
{
check(this->ComboActionGraphEditor.IsValid());
TSharedPtr<FAssetEditor_ComboActionGraph> ComboActionGraphEditorPtr = this->ComboActionGraphEditor.Pin();
TSharedPtr<FExtender> ToolbarExtender = MakeShareable(new FExtender);
ToolbarExtender->AddToolBarExtension("Asset", EExtensionHook::After, ComboActionGraphEditorPtr->GetToolkitCommands(), FToolBarExtensionDelegate::CreateSP( this, &FAssetEditorToolbarComboActionGraph::FillComboActionGraphToolbar ));
ComboActionGraphEditorPtr->AddToolbarExtender(ToolbarExtender);
}
void FAssetEditorToolbarComboActionGraph::FillComboActionGraphToolbar(FToolBarBuilder &ToolbarBuilder)
{
check(this->ComboActionGraphEditor.IsValid());
TSharedPtr<FAssetEditor_ComboActionGraph> MounteaDialogueGraphEditorPtr = this->ComboActionGraphEditor.Pin();
ToolbarBuilder.BeginSection("Util");
{
ToolbarBuilder.AddToolBarButton(FComboActionGraphEditorCommands::Get().AutoArrange,
NAME_None,
LOCTEXT("AutoArrange_Label", "Auto Arrange"),
LOCTEXT("AutoArrange_ToolTip", "ALPHA version!\n\nTries its best to arrange Graph Nodes. Don't judge too harshly please."),
FSlateIcon(FComboInputSlateStyle::GetAppStyleSetName(), "MDSStyleSet.AutoArrange"));
ToolbarBuilder.AddToolBarButton(FComboActionGraphEditorCommands::Get().ValidateGraph,
NAME_None,
LOCTEXT("ValidateGraph_Label", "Validate Graph"),
LOCTEXT("ValidateGraph_ToolTip", "Validates Graph if there are any invalid connections or broken data."),
FSlateIcon(FComboInputSlateStyle::GetAppStyleSetName(), "MDSStyleSet.ValidateGraph"));
}
ToolbarBuilder.EndSection();
}
#undef LOCTEXT_NAMESPACE

View File

@ -0,0 +1,22 @@
// ©2023 Batty Bovine Productions, LLC. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
class FAssetEditorToolbarComboActionGraph : public TSharedFromThis<FAssetEditorToolbarComboActionGraph>
{
public:
FAssetEditorToolbarComboActionGraph(TSharedPtr<FAssetEditor_ComboActionGraph> InComboActionGraphEditor)
: ComboActionGraphEditor(InComboActionGraphEditor) {}
void AddComboActionGraphToolbar(TSharedPtr<FExtender> Extender);
private:
void FillComboActionGraphToolbar(FToolBarBuilder &ToolbarBuilder);
protected:
TWeakPtr<FAssetEditor_ComboActionGraph> ComboActionGraphEditor;
};

View File

@ -0,0 +1,30 @@
// ©2023 Batty Bovine Productions, LLC. All Rights Reserved.
#include "FComboActionGraphEditorCommands.h"
#define LOCTEXT_NAMESPACE "ComboActionGraphEditorCommands"
void FComboActionGraphEditorCommands::RegisterCommands()
{
UI_COMMAND
(
AutoArrange,
"Auto Arrange",
"Auto Arrange",
EUserInterfaceActionType::Button,
FInputChord()
);
UI_COMMAND
(
ValidateGraph,
"Validate Graph",
"Validate Graph",
EUserInterfaceActionType::Button,
FInputChord()
);
}
#undef LOCTEXT_NAMESPACE

View File

@ -0,0 +1,19 @@
// ©2023 Batty Bovine Productions, LLC. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
class FComboActionGraphEditorCommands : public TCommands<FComboActionGraphEditorCommands>
{
public:
FComboActionGraphEditorCommands()
: TCommands<FComboActionGraphEditorCommands>("ComboActionGraphEditor", NSLOCTEXT("Contexts", "ComboActionGraphEditor", "Combo Action Graph Editor"), NAME_None, FAppStyle::GetAppStyleSetName())
{
}
TSharedPtr<FUICommandInfo> AutoArrange;
TSharedPtr<FUICommandInfo> ValidateGraph;
virtual void RegisterCommands() override;
};

View File

@ -0,0 +1,209 @@
// ©2023 Batty Bovine Productions, LLC. All Rights Reserved.
#include "FConnectionDrawingPolicy_ComboActionGraph.h"
#include "ComboInputEditor.h"
#include "Ed/EdComboActionGraphEdge.h"
#include "Ed/EdComboActionGraphNode.h"
#include "Settings/ComboActionGraphEditorSettings.h"
DEFINE_LOG_CATEGORY(LogConnectionDrawingPolicy_ComboActionGraph);
FConnectionDrawingPolicy_ComboActionGraph::FConnectionDrawingPolicy_ComboActionGraph(int32 InBackLayerID, int32 InFrontLayerID, float ZoomFactor, const FSlateRect &InClippingRect, FSlateWindowElementList &InDrawElements, UEdGraph *InGraphObj)
: FKismetConnectionDrawingPolicy(InBackLayerID, InFrontLayerID, ZoomFactor, InClippingRect, InDrawElements, InGraphObj)
, GraphObj(InGraphObj)
{
if (const UComboActionGraphEditorSettings* GraphEditorSettings = GetMutableDefault<UComboActionGraphEditorSettings>())
{
FComboInputEditorModule &ComboInputEditorModule = FComboInputEditorModule::Get();
switch (GraphEditorSettings->GetArrowType())
{
case EComboActionArrowType::SimpleArrow:
ArrowImage = ComboInputEditorModule.GetComboInputEditorStyleSet()->GetBrush(TEXT("MDSStyleSet.Graph.SimpleArrow"));
break;
case EComboActionArrowType::HollowArrow:
ArrowImage = ComboInputEditorModule.GetComboInputEditorStyleSet()->GetBrush(TEXT("MDSStyleSet.Graph.HollowArrow"));
break;
case EComboActionArrowType::FancyArrow:
ArrowImage = ComboInputEditorModule.GetComboInputEditorStyleSet()->GetBrush(TEXT("MDSStyleSet.Graph.FancyArrow"));
break;
case EComboActionArrowType::Bubble:
ArrowImage = ComboInputEditorModule.GetComboInputEditorStyleSet()->GetBrush(TEXT("MDSStyleSet.Graph.Bubble"));
break;
case EComboActionArrowType::None:
default:
ArrowImage = nullptr;
}
}
else
{
ArrowImage = FAppStyle::GetBrush( TEXT("GenericPlay") );
}
ArrowRadius = ArrowImage ? ArrowImage->ImageSize * ZoomFactor * 0.5f : FVector2D(0.f);
MidpointImage = nullptr;
MidpointRadius = FVector2D::ZeroVector;
HoverDeemphasisDarkFraction = 0.8f;
BubbleImage = FAppStyle::GetBrush( TEXT("Graph.Arrow") );
}
void FConnectionDrawingPolicy_ComboActionGraph::DetermineWiringStyle(UEdGraphPin *OutputPin, UEdGraphPin *InputPin, FConnectionParams &Params)
{
Params.AssociatedPin1 = OutputPin;
Params.AssociatedPin2 = InputPin;
const UComboActionGraphEditorSettings *MounteaDialogueGraphEditorSettings = GetDefault<UComboActionGraphEditorSettings>();
if (MounteaDialogueGraphEditorSettings)
{
Params.WireThickness = MounteaDialogueGraphEditorSettings->GetWireWidth();
}
else
{
Params.WireThickness = 1.f;
}
const bool bDeemphasizeUnhoveredPins = HoveredPins.Num() > 0;
if (bDeemphasizeUnhoveredPins)
{
ApplyHoverDeemphasis(OutputPin, InputPin, /*inout*/ Params.WireThickness, /*inout*/ Params.WireColor);
}
}
void FConnectionDrawingPolicy_ComboActionGraph::Draw(TMap<TSharedRef<SWidget>, FArrangedWidget> &InPinGeometries, FArrangedChildren &ArrangedNodes)
{
// Build an acceleration structure to quickly find geometry for the nodes
NodeWidgetMap.Empty();
for (int32 NodeIndex = 0; NodeIndex < ArrangedNodes.Num(); ++NodeIndex)
{
FArrangedWidget& CurWidget = ArrangedNodes[NodeIndex];
TSharedRef<SGraphNode> ChildNode = StaticCastSharedRef<SGraphNode>(CurWidget.Widget);
NodeWidgetMap.Add(ChildNode->GetNodeObj(), NodeIndex);
}
// Now draw
FConnectionDrawingPolicy::Draw(InPinGeometries, ArrangedNodes);
}
void FConnectionDrawingPolicy_ComboActionGraph::DrawSplineWithArrow(const FGeometry &StartGeom, const FGeometry &EndGeom, const FConnectionParams &Params)
{
// Get a reasonable seed point (halfway between the boxes)
const FVector2D StartCenter = FGeometryHelper::CenterOf(StartGeom);
const FVector2D EndCenter = FGeometryHelper::CenterOf(EndGeom);
const FVector2D SeedPoint = (StartCenter + EndCenter) * 0.5f;
// Find the (approximate) closest points between the two boxes
const FVector2D StartAnchorPoint = FGeometryHelper::FindClosestPointOnGeom(StartGeom, SeedPoint);
const FVector2D EndAnchorPoint = FGeometryHelper::FindClosestPointOnGeom(EndGeom, SeedPoint);
this->DrawSplineWithArrow(StartAnchorPoint, EndAnchorPoint, Params);
}
void FConnectionDrawingPolicy_ComboActionGraph::DrawSplineWithArrow(const FVector2D &StartPoint, const FVector2D &EndPoint, const FConnectionParams &Params)
{
// bUserFlag1 indicates that we need to reverse the direction of connection (used by debugger)
const FVector2D &P0 = Params.bUserFlag1 ? EndPoint : StartPoint;
const FVector2D &P1 = Params.bUserFlag1 ? StartPoint : EndPoint;
UE_LOG(LogConnectionDrawingPolicy_ComboActionGraph, Verbose, TEXT("%s I %s"), *P0.ToString(), *P1.ToString());
FConnectionParams NewParams = Params;
//NewParams.bDrawBubbles = true;
if (const UComboActionGraphEditorSettings *MounteaDialogueGraphEditorSettings = GetMutableDefault<UComboActionGraphEditorSettings>())
{
NewParams.WireThickness = MounteaDialogueGraphEditorSettings->GetWireWidth();
}
Internal_DrawLineWithArrow(P0, P1, NewParams);
}
void FConnectionDrawingPolicy_ComboActionGraph::DrawPreviewConnector(const FGeometry &PinGeometry, const FVector2D &StartPoint, const FVector2D &EndPoint, UEdGraphPin* Pin)
{
FConnectionParams Params;
DetermineWiringStyle(Pin, nullptr, /*inout*/ Params);
if (Pin->Direction == EEdGraphPinDirection::EGPD_Output)
{
DrawSplineWithArrow(FGeometryHelper::FindClosestPointOnGeom(PinGeometry, EndPoint), EndPoint, Params);
}
else
{
DrawSplineWithArrow(FGeometryHelper::FindClosestPointOnGeom(PinGeometry, StartPoint), StartPoint, Params);
}
}
FVector2D FConnectionDrawingPolicy_ComboActionGraph::ComputeSplineTangent(const FVector2D &Start, const FVector2D &End) const
{
const FVector2D Delta = End - Start;
const FVector2D NormDelta = Delta.GetSafeNormal();
return NormDelta;
}
void FConnectionDrawingPolicy_ComboActionGraph::DetermineLinkGeometry(FArrangedChildren &ArrangedNodes, TSharedRef<SWidget> &OutputPinWidget, UEdGraphPin *OutputPin, UEdGraphPin *InputPin, FArrangedWidget *&StartWidgetGeometry, FArrangedWidget *&EndWidgetGeometry)
{
if (UEdComboActionGraphEdge *EdgeNode = Cast<UEdComboActionGraphEdge>(InputPin->GetOwningNode()))
{
UEdComboActionGraphNode* Start = EdgeNode->GetStartNode();
UEdComboActionGraphNode* End = EdgeNode->GetEndNode();
if (Start != nullptr && End != nullptr)
{
int32* StartNodeIndex = NodeWidgetMap.Find(Start);
int32* EndNodeIndex = NodeWidgetMap.Find(End);
if (StartNodeIndex != nullptr && EndNodeIndex != nullptr)
{
StartWidgetGeometry = &(ArrangedNodes[*StartNodeIndex]);
EndWidgetGeometry = &(ArrangedNodes[*EndNodeIndex]);
}
}
}
else
{
StartWidgetGeometry = PinGeometries->Find(OutputPinWidget);
if (TSharedPtr<SGraphPin>* pTargetWidget = PinToPinWidgetMap.Find(InputPin))
{
TSharedRef<SGraphPin> InputWidget = (*pTargetWidget).ToSharedRef();
EndWidgetGeometry = PinGeometries->Find(InputWidget);
}
}
}
void FConnectionDrawingPolicy_ComboActionGraph::Internal_DrawLineWithArrow(const FVector2D &StartAnchorPoint, const FVector2D &EndAnchorPoint, const FConnectionParams &Params)
{
const float LineSeparationAmount = 4.5f;
const FVector2D DeltaPos = EndAnchorPoint - StartAnchorPoint;
const FVector2D UnitDelta = DeltaPos.GetSafeNormal();
const FVector2D Normal = FVector2D(DeltaPos.Y, -DeltaPos.X).GetSafeNormal();
// Come up with the final start/end points
const FVector2D DirectionBias = Normal * LineSeparationAmount;
const FVector2D LengthBias = ArrowRadius.X * UnitDelta;
const FVector2D StartPoint = StartAnchorPoint + DirectionBias + LengthBias;
const FVector2D EndPoint = EndAnchorPoint + DirectionBias - LengthBias;
// Draw a line/spline
DrawConnection(WireLayerID, StartPoint, EndPoint, Params);
// Draw the arrow
if (ArrowImage)
{
const FVector2D ArrowDrawPos = EndPoint - ArrowRadius;
const float AngleInRadians = FMath::Atan2(DeltaPos.Y, DeltaPos.X);
FSlateDrawElement::MakeRotatedBox(
DrawElementsList,
ArrowLayerID,
FPaintGeometry(ArrowDrawPos, ArrowImage->ImageSize * ZoomFactor, ZoomFactor),
ArrowImage,
ESlateDrawEffect::None,
AngleInRadians,
TOptional<FVector2D>(),
FSlateDrawElement::RelativeToElement,
Params.WireColor
);
}
}

View File

@ -0,0 +1,36 @@
// ©2023 Batty Bovine Productions, LLC. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "BlueprintConnectionDrawingPolicy.h"
#include "ConnectionDrawingPolicy.h"
DECLARE_LOG_CATEGORY_EXTERN(LogConnectionDrawingPolicy_ComboActionGraph, Log, All);
class FConnectionDrawingPolicy_ComboActionGraph : public FKismetConnectionDrawingPolicy
{
public:
FConnectionDrawingPolicy_ComboActionGraph(int32 InBackLayerID, int32 InFrontLayerID, float ZoomFactor, const FSlateRect& InClippingRect, FSlateWindowElementList& InDrawElements, UEdGraph* InGraphObj);
virtual ~FConnectionDrawingPolicy_ComboActionGraph() {};
// FConnectionDrawingPolicy interface
virtual void DetermineWiringStyle(UEdGraphPin* OutputPin, UEdGraphPin* InputPin, /*inout*/ FConnectionParams& Params) override;
virtual void Draw(TMap<TSharedRef<SWidget>, FArrangedWidget>& InPinGeometries, FArrangedChildren& ArrangedNodes) override;
virtual void DrawSplineWithArrow(const FGeometry& StartGeom, const FGeometry& EndGeom, const FConnectionParams& Params) override;
virtual void DrawSplineWithArrow(const FVector2D& StartPoint, const FVector2D& EndPoint, const FConnectionParams& Params) override;
virtual void DrawPreviewConnector(const FGeometry& PinGeometry, const FVector2D& StartPoint, const FVector2D& EndPoint, UEdGraphPin* Pin) override;
virtual FVector2D ComputeSplineTangent(const FVector2D& Start, const FVector2D& End) const override;
virtual void DetermineLinkGeometry(FArrangedChildren& ArrangedNodes, TSharedRef<SWidget>& OutputPinWidget, UEdGraphPin* OutputPin, UEdGraphPin* InputPin, FArrangedWidget*& StartWidgetGeometry, FArrangedWidget*& EndWidgetGeometry) override;
// End of FConnectionDrawingPolicy interface
protected:
void Internal_DrawLineWithArrow(const FVector2D& StartAnchorPoint, const FVector2D& EndAnchorPoint, const FConnectionParams& Params);
protected:
UEdGraph *GraphObj;
TMap<UEdGraphNode*, int32> NodeWidgetMap;
};

View File

@ -0,0 +1,131 @@
#include "SEdComboActionGraphEdge.h"
#include "ConnectionDrawingPolicy.h"
#include "SGraphPanel.h"
#include "Ed/EdComboActionGraphEdge.h"
#include "Ed/EdComboActionGraphNode.h"
#include "Widgets/Images/SImage.h"
#define LOCTEXT_NAMESPACE "SEdComboActionGraphEdge"
void SEdComboActionGraphEdge::Construct(const FArguments &InArgs, UEdComboActionGraphEdge *InNode)
{
this->GraphNode = InNode;
this->UpdateGraphNode();
}
bool SEdComboActionGraphEdge::RequiresSecondPassLayout() const
{
return true;
}
void SEdComboActionGraphEdge::PerformSecondPassLayout(const TMap<UObject*, TSharedRef<SNode>>& NodeToWidgetLookup) const
{
UEdComboActionGraphEdge *EdgeNode = CastChecked<UEdComboActionGraphEdge>(GraphNode);
FGeometry StartGeom;
FGeometry EndGeom;
UEdComboActionGraphNode *Start = EdgeNode->GetStartNode();
UEdComboActionGraphNode *End = EdgeNode->GetEndNode();
if (Start != nullptr && End != nullptr)
{
const TSharedRef<SNode> *pFromWidget = NodeToWidgetLookup.Find(Start);
const TSharedRef<SNode> *pToWidget = NodeToWidgetLookup.Find(End);
if (pFromWidget != nullptr && pToWidget != nullptr)
{
const TSharedRef<SNode>& FromWidget = *pFromWidget;
const TSharedRef<SNode>& ToWidget = *pToWidget;
StartGeom = FGeometry(FVector2D(Start->NodePosX, Start->NodePosY), FVector2D::ZeroVector, FromWidget->GetDesiredSize(), 1.0f);
EndGeom = FGeometry(FVector2D(End->NodePosX, End->NodePosY), FVector2D::ZeroVector, ToWidget->GetDesiredSize(), 1.0f);
}
}
PositionBetweenTwoNodesWithOffset(StartGeom, EndGeom, 0, 1);
}
void SEdComboActionGraphEdge::UpdateGraphNode()
{
InputPins.Empty();
OutputPins.Empty();
RightNodeBox.Reset();
LeftNodeBox.Reset();
this->ContentScale.Bind( this, &SGraphNode::GetContentScale );
this->GetOrAddSlot( ENodeZone::Center )
.HAlign(HAlign_Center)
.VAlign(VAlign_Center)
[
SNew(SOverlay)
/*+ SOverlay::Slot()
[
SNew(SImage)
.Image(FAppStyle::GetBrush("Graph.TransitionNode.ColorSpill"))
.ColorAndOpacity(this, &SEdComboActionGraphEdge::GetEdgeColor)
]*/
+ SOverlay::Slot()
[
SNew(SImage)
.Image(FAppStyle::GetBrush("GraphEditor.RefPinIcon"))
.ColorAndOpacity(this, &SEdComboActionGraphEdge::GetEdgeColor)
]
];
}
void SEdComboActionGraphEdge::PositionBetweenTwoNodesWithOffset(const FGeometry& StartGeom, const FGeometry& EndGeom, int32 NodeIndex, int32 MaxNodes) const
{
// Get a reasonable seed point (halfway between the boxes)
const FVector2D StartCenter = FGeometryHelper::CenterOf(StartGeom);
const FVector2D EndCenter = FGeometryHelper::CenterOf(EndGeom);
const FVector2D SeedPoint = (StartCenter + EndCenter) * 0.5f;
// Find the (approximate) closest points between the two boxes
const FVector2D StartAnchorPoint = FGeometryHelper::FindClosestPointOnGeom(StartGeom, SeedPoint);
const FVector2D EndAnchorPoint = FGeometryHelper::FindClosestPointOnGeom(EndGeom, SeedPoint);
// Position ourselves halfway along the connecting line between the nodes, elevated away perpendicular to the direction of the line
const float Height = 30.0f;
const FVector2D DesiredNodeSize = GetDesiredSize();
FVector2D DeltaPos(EndAnchorPoint - StartAnchorPoint);
if (DeltaPos.IsNearlyZero())
{
DeltaPos = FVector2D(10.0f, 0.0f);
}
const FVector2D Normal = FVector2D(DeltaPos.Y, -DeltaPos.X).GetSafeNormal();
const FVector2D NewCenter = StartAnchorPoint + (0.5f * DeltaPos) + (Height * Normal);
FVector2D DeltaNormal = DeltaPos.GetSafeNormal();
// Calculate node offset in the case of multiple transitions between the same two nodes
// MultiNodeOffset: the offset where 0 is the centre of the transition, -1 is 1 <size of node>
// towards the PrevStateNode and +1 is 1 <size of node> towards the NextStateNode.
const float MutliNodeSpace = 0.2f; // Space between multiple transition nodes (in units of <size of node> )
const float MultiNodeStep = (1.f + MutliNodeSpace); //Step between node centres (Size of node + size of node spacer)
const float MultiNodeStart = -((MaxNodes - 1) * MultiNodeStep) / 2.f;
const float MultiNodeOffset = MultiNodeStart + (NodeIndex * MultiNodeStep);
// Now we need to adjust the new center by the node size, zoom factor and multi node offset
const FVector2D NewCorner = NewCenter - (0.5f * DesiredNodeSize) + (DeltaNormal * MultiNodeOffset * DesiredNodeSize.Size());
GraphNode->NodePosX = NewCorner.X;
GraphNode->NodePosY = NewCorner.Y;
}
FSlateColor SEdComboActionGraphEdge::GetEdgeColor() const
{
return FLinearColor(0.9f, 0.9f, 0.9f, 1.0f);
}
#undef LOCTEXT_NAMESPACE

View File

@ -0,0 +1,33 @@
#pragma once
#include "CoreMinimal.h"
#include "Styling/SlateColor.h"
#include "Widgets/DeclarativeSyntaxSupport.h"
#include "Widgets/SWidget.h"
#include "SNodePanel.h"
#include "SGraphNode.h"
class SEdComboActionGraphEdge : public SGraphNode
{
public:
SLATE_BEGIN_ARGS(SEdComboActionGraphEdge){}
SLATE_END_ARGS()
void Construct(const FArguments &InArgs, class UEdComboActionGraphEdge *InNode);
virtual bool RequiresSecondPassLayout() const override;
virtual void PerformSecondPassLayout(const TMap<UObject*, TSharedRef<SNode> > &NodeToWidgetLookup) const override;
virtual void UpdateGraphNode() override;
// Calculate position for multiple nodes to be placed between a start and end point, by providing this nodes index and max expected nodes
void PositionBetweenTwoNodesWithOffset(const FGeometry &StartGeom, const FGeometry &EndGeom, int32 NodeIndex, int32 MaxNodes) const;
protected:
FSlateColor GetEdgeColor() const;
private:
TSharedPtr<STextEntryPopup> TextEntryWidget;
};

View File

@ -0,0 +1,879 @@
// ©2023 Batty Bovine Productions, LLC. All Rights Reserved.
#include "SEdComboActionGraphNode.h"
#include "ComboActionGraph.h"
#include "ComboInputAssets.h"
#include "ComboInputEditor.h"
#include "GraphEditorSettings.h"
#include "SCommentBubble.h"
#include "SGraphPin.h"
#include "SlateOptMacros.h"
#include "Blueprint/UserWidget.h"
#include "Ed/SEdComboActionGraphNodeIndex.h"
#include "Ed/EdComboActionGraphNode.h"
#include "Helpers/ComboActionGraphColors.h"
#include "Nodes/ComboActionGraphNode_ActionNodeBase.h"
#include "Nodes/ComboActionGraphNode_StartNode.h"
#include "Settings/ComboActionGraphEditorSettings.h"
#include "Widgets/Layout/SGridPanel.h"
#include "Widgets/Layout/SScaleBox.h"
#include "Widgets/Text/SInlineEditableTextBlock.h"
#define LOCTEXT_NAMESPACE "EdComboActionGraph"
#pragma region Pin
class SComboActionGraphPin : public SGraphPin
{
public:
SLATE_BEGIN_ARGS(SComboActionGraphPin) {}
SLATE_END_ARGS()
void Construct(const FArguments &InArgs, UEdGraphPin *InPin)
{
this->SetCursor(EMouseCursor::Default);
this->bShowLabel = true;
this->GraphPinObj = InPin;
check(this->GraphPinObj != nullptr);
const UEdGraphSchema *Schema = this->GraphPinObj->GetSchema();
check(Schema);
// Pins Out/In Border
SBorder::Construct(SBorder::FArguments()
.BorderImage(this, &SComboActionGraphPin::GetPinBorder)
.BorderBackgroundColor(this, &SComboActionGraphPin::GetPinColor)
.OnMouseButtonDown(this, &SComboActionGraphPin::OnPinMouseDown)
.Cursor(this, &SComboActionGraphPin::GetPinCursor)
.Padding(FMargin(5.0f))
);
}
protected:
virtual FSlateColor GetPinColor() const override
{
if (const UComboActionGraphEditorSettings *GraphEditorSettings = GetMutableDefault<UComboActionGraphEditorSettings>())
{
switch (GraphEditorSettings->GetNodeTheme())
{
case EComboActionNodeTheme::DarkTheme:
return ComboActionGraphColors::PinsDock::LightTheme;
case EComboActionNodeTheme::LightTheme:
return ComboActionGraphColors::PinsDock::DarkTheme;
default:
return ComboActionGraphColors::PinsDock::LightTheme;
}
}
return ComboActionGraphColors::Pin::Default;
}
virtual TSharedRef<SWidget> GetDefaultValueWidget() override
{
return SNew(STextBlock);
}
const FSlateBrush *GetPinBorder() const
{
FComboInputEditorModule &ComboInputEditorModule = FComboInputEditorModule::Get();
if (const UComboActionGraphEditorSettings *GraphEditorSettings = GetMutableDefault<UComboActionGraphEditorSettings>())
{
switch (GraphEditorSettings->GetNodeType())
{
case EComboActionNodeType::SoftCorners:
return ComboInputEditorModule.GetComboInputEditorStyleSet()->GetBrush("MDSStyleSet.Node.TextSoftEdges");
case EComboActionNodeType::HardCorners:
return ComboInputEditorModule.GetComboInputEditorStyleSet()->GetBrush("MDSStyleSet.Node.TextHardEdges");
}
}
return ComboInputEditorModule.GetComboInputEditorStyleSet()->GetBrush(TEXT("MDSStyleSet.Graph.PinDocksOverlay"));
}
};
#pragma endregion
void SEdComboActionGraphNode::Construct(const FArguments &InArgs, UEdComboActionGraphNode *InNode)
{
this->GraphNode = InNode;
this->UpdateGraphNode();
InNode->SEdNode = this;
//bIsHovered = false;
this->GraphEditorSettings = GetMutableDefault<UComboActionGraphEditorSettings>();
}
void SEdComboActionGraphNode::OnMouseEnter(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent)
{
//bIsHovered = true;
this->SetToolTipText(GetTooltipText());
this->OnVisualizeTooltip(GetToolTip()->AsWidget());
SGraphNode::OnMouseEnter(MyGeometry, MouseEvent);
}
void SEdComboActionGraphNode::OnMouseLeave(const FPointerEvent& MouseEvent)
{
//bIsHovered = false;
this->SetToolTipText(FText::GetEmpty());
this->OnToolTipClosing();
SGraphNode::OnMouseLeave(MouseEvent);
}
const FSlateBrush *SEdComboActionGraphNode::GetIndexBrush() const
{
FComboInputEditorModule &ComboInputEditorModule = FComboInputEditorModule::Get();
return ComboInputEditorModule.GetComboInputEditorStyleSet()->GetBrush("MDSStyleSet.Node.TextSoftEdges");
}
BEGIN_SLATE_FUNCTION_BUILD_OPTIMIZATION
void SEdComboActionGraphNode::UpdateGraphNode()
{
const FMargin NodePadding = FMargin(2.0f);
const FMargin UnifiedRowsPadding = FMargin(0.f, 1.15f, 0.f, 0.f);
const FSlateColor DefaultFontColor = ComboActionGraphColors::TextColors::Normal;
this->InputPins.Empty();
this->OutputPins.Empty();
// Reset variables that are going to be exposed, in case we are refreshing an already setup node.
this->RightNodeBox.Reset();
this->LeftNodeBox.Reset();
this->OutputPinBox.Reset();
// Set this here so that it isn't null.
SAssignNew(this->InlineEditableText, SInlineEditableTextBlock);
TSharedPtr<SErrorText> ErrorText;
TSharedPtr<SNodeTitle> NodeTitle = SNew(SNodeTitle, this->GraphNode);
TSharedPtr<SVerticalBox> StackBox;
TSharedPtr<SVerticalBox> UniformBox;
this->ContentScale.Bind(this, &SGraphNode::GetContentScale);
FComboInputEditorModule &ComboInputEditorModule = FComboInputEditorModule::Get();
const FSlateBrush *CircleBrush = ComboInputEditorModule.GetComboInputEditorStyleSet()->GetBrush(TEXT("MDSStyleSet.Node.IndexCircle"));
this->GetOrAddSlot(ENodeZone::Left)
.SlotOffset(TAttribute<FVector2D>(this, &SEdComboActionGraphNode::GetIndexSlotOffset))
.SlotSize(TAttribute<FVector2D>(this, &SEdComboActionGraphNode::GetIndexSlotSize))
[
SNew(SOverlay)
+SOverlay::Slot()
.HAlign(EHorizontalAlignment::HAlign_Fill)
.VAlign(EVerticalAlignment::VAlign_Fill)
[
SNew(SBox)
.WidthOverride(CircleBrush->ImageSize.X)
.HeightOverride(CircleBrush->ImageSize.Y)
]
+SOverlay::Slot()
.HAlign(EHorizontalAlignment::HAlign_Fill)
.VAlign(EVerticalAlignment::VAlign_Fill)
[
SNew(SBorder)
.BorderImage(CircleBrush)
.BorderBackgroundColor(FLinearColor::Gray)
.Padding(FMargin(4.0f))
.HAlign(EHorizontalAlignment::HAlign_Center)
.VAlign(EVerticalAlignment::VAlign_Center)
[
SNew(STextBlock)
.Font(FCoreStyle::GetDefaultFontStyle("Regular", 8))
.Text(this, &SEdComboActionGraphNode::GetIndexText)
.Visibility(this, &SEdComboActionGraphNode::GetIndexSlotVisibility)
]
]
];
this->GetOrAddSlot(ENodeZone::Center)
.HAlign(EHorizontalAlignment::HAlign_Fill)
.VAlign(EVerticalAlignment::VAlign_Fill)
[
SNew(SBox)
[
// OUTER STYLE
SNew(SBorder)
.BorderImage(this, &SEdComboActionGraphNode::GetNodeTypeBrush)
.Padding(3.0f)
.BorderBackgroundColor(this, &SEdComboActionGraphNode::GetBorderBackgroundColor)
[
SNew(SOverlay)
// Adding some colours so its not so boring
+ SOverlay::Slot()
.HAlign(EHorizontalAlignment::HAlign_Fill)
.VAlign(EVerticalAlignment::VAlign_Fill)
[
// INNER STYLE
SNew(SBorder)
.BorderImage(this, &SEdComboActionGraphNode::GetNodeTypeBrush)
.HAlign(EHorizontalAlignment::HAlign_Fill)
.VAlign(EVerticalAlignment::VAlign_Center)
.Visibility(EVisibility::SelfHitTestInvisible)
.BorderBackgroundColor(this, &SEdComboActionGraphNode::GetBorderFrontColor)
]
// Pins and node details
+ SOverlay::Slot()
.HAlign(EHorizontalAlignment::HAlign_Center)
.VAlign(EVerticalAlignment::VAlign_Fill)
[
SNew(SHorizontalBox)
// INPUT PIN AREA
+ SHorizontalBox::Slot()
.AutoWidth()
[
SNew(SVerticalBox)
+ SVerticalBox::Slot()
.VAlign(EVerticalAlignment::VAlign_Fill)
+ SVerticalBox::Slot()
.VAlign(EVerticalAlignment::VAlign_Center)
[
SAssignNew(this->LeftNodeBox, SVerticalBox)
]
+ SVerticalBox::Slot()
.VAlign(EVerticalAlignment::VAlign_Fill)
]
+ SHorizontalBox::Slot()
.Padding(FMargin(NodePadding.Left, 0.0f, NodePadding.Right, 0.0f))
.VAlign(EVerticalAlignment::VAlign_Fill)
[
SNew(SVerticalBox)
+ SVerticalBox::Slot()
.AutoHeight()
[
SAssignNew(this->NodeBody, SBorder)
.BorderImage(this, &SEdComboActionGraphNode::GetTextNodeTypeBrush)
.BorderBackgroundColor(this, &SEdComboActionGraphNode::GetNodeTitleBackgroundColor)
.HAlign(EHorizontalAlignment::HAlign_Fill)
.VAlign(EVerticalAlignment::VAlign_Center)
.Visibility(EVisibility::SelfHitTestInvisible)
[
SNew(SBox)
//.MinDesiredWidth(FOptionalSize(145.f))
[
SNew(SVerticalBox)
+ SVerticalBox::Slot()
.AutoHeight()
[
SNew(SOverlay)
+ SOverlay::Slot()
.HAlign(EHorizontalAlignment::HAlign_Center)
.VAlign(EVerticalAlignment::VAlign_Fill)
[
SNew(SVerticalBox)
#pragma region NameSlot
// NAME SLOT
+ SVerticalBox::Slot()
.AutoHeight()
.HAlign(HAlign_Center)
[
SNew(SHorizontalBox)
+ SHorizontalBox::Slot()
.AutoWidth()
[
// POPUP ERROR MESSAGE
SAssignNew(ErrorText, SErrorText)
.BackgroundColor(
this,
&SEdComboActionGraphNode::GetErrorColor)
.ToolTipText(
this,
&SEdComboActionGraphNode::GetErrorMsgToolTip)
]
+ SHorizontalBox::Slot()
.AutoWidth()
.HAlign(HAlign_Center)
[
SNew(SHorizontalBox)
+ SHorizontalBox::Slot()
.Padding(FMargin(4.0f, 0.0f, 4.0f, 0.0f))
[
SNew(SVerticalBox)
+ SVerticalBox::Slot()
.HAlign(EHorizontalAlignment::HAlign_Center)
.AutoHeight()
[
SNew(STextBlock)
.Text(this, &SEdComboActionGraphNode::GetComboActionName)
.Justification(ETextJustify::Center)
.Visibility(EVisibility::Visible)
.Font(FCoreStyle::GetDefaultFontStyle("Bold", 14))
.ColorAndOpacity(FSlateColor(FLinearColor(230.0f / 255.0f, 230.0f / 255.0f, 230.0f / 255.0f)))
.ShadowOffset(FVector2D(2, 2))
.ShadowColorAndOpacity(FLinearColor(0.f, 0.f, 0.f, 0.7f))
]
+ SVerticalBox::Slot()
.AutoHeight()
[
NodeTitle.ToSharedRef()
]
]
]
]
#pragma endregion
]
]
#pragma region Unified
+ SVerticalBox::Slot()
.VAlign(EVerticalAlignment::VAlign_Fill)
[
SNew(SBox)
.Visibility(this, &SEdComboActionGraphNode::GetResponseStackVisibility)
.HAlign(EHorizontalAlignment::HAlign_Fill)
[
SNew(SVerticalBox)
//+ SVerticalBox::Slot()
//[
// SNew(SBox)
// .Visibility(this, &SEdComboActionGraphNode::ShowDecoratorsBottomPadding)
// [
// SNew(SSpacer)
// .Size(FVector2D(0.f, 2.5f))
// ]
//]
#pragma region ResponseStack
// RESPONSE STACK
+ SVerticalBox::Slot()
.AutoHeight()
.HAlign(EHorizontalAlignment::HAlign_Fill)
.VAlign(EVerticalAlignment::VAlign_Fill)
.Padding(FMargin(8.0f, 0.0f, 8.0f, 0.0f))
[
SNew(SBox)
.Visibility(EVisibility::Visible)
.MaxDesiredWidth(FOptionalSize(130.0f))
.HAlign(EHorizontalAlignment::HAlign_Fill)
[
SNew(SHorizontalBox)
.Visibility(EVisibility::HitTestInvisible)
#pragma region Title
+ SHorizontalBox::Slot()
.HAlign(EHorizontalAlignment::HAlign_Center)
[
SNew(STextBlock)
.Text(LOCTEXT("A", "Info:"))
.Font(FCoreStyle::GetDefaultFontStyle("Bold", 8))
.ColorAndOpacity(DefaultFontColor)
]
]
#pragma endregion
#pragma region Title
[
SNew(SVerticalBox)
.Visibility(EVisibility::HitTestInvisible)
+ SVerticalBox::Slot()
.HAlign(EHorizontalAlignment::HAlign_Left)
[
SNew(STextBlock)
.Text(this, &SEdComboActionGraphNode::GetComboInputName)
.Font(FCoreStyle::GetDefaultFontStyle("Bold", 8))
.ColorAndOpacity(DefaultFontColor)
]
+ SVerticalBox::Slot()
.HAlign(EHorizontalAlignment::HAlign_Left)
[
SNew(STextBlock)
.Text(this, &SEdComboActionGraphNode::GetComboInputTriggerActionName)
.Font(FCoreStyle::GetDefaultFontStyle("Bold", 8))
.ColorAndOpacity(DefaultFontColor)
]
#pragma endregion
]
]
#pragma endregion
+ SVerticalBox::Slot()
.HAlign(EHorizontalAlignment::HAlign_Center)
.VAlign(EVerticalAlignment::VAlign_Fill)
[
SNew(SSpacer)
.Size(FVector2D(0.f, 2.5f))
]
]
]
#pragma endregion
]
]
]
]
+ SHorizontalBox::Slot()
.AutoWidth()
[
SNew(SSpacer)
.Size(FVector2D(0.0f, 10.0f))
]
// OUTPUT PIN AREA
+ SHorizontalBox::Slot()
.AutoWidth()
.Padding(20.0f, 0.0f)
[
SNew(SVerticalBox)
+ SVerticalBox::Slot()
[
SAssignNew(this->RightNodeBox, SVerticalBox)
+ SVerticalBox::Slot()
.HAlign(EHorizontalAlignment::HAlign_Fill)
.VAlign(EVerticalAlignment::VAlign_Center)
[
SAssignNew(this->OutputPinBox, SHorizontalBox)
]
]
]
]
]
]
];
// Create comment bubble
TSharedPtr<SCommentBubble> CommentBubble;
const FSlateColor CommentColor = GetDefault<UGraphEditorSettings>()->DefaultCommentNodeTitleColor;
SAssignNew(CommentBubble, SCommentBubble)
.GraphNode(this->GraphNode)
.Text(this, &SGraphNode::GetNodeComment)
.OnTextCommitted(this, &SGraphNode::OnCommentTextCommitted)
.ColorAndOpacity(CommentColor)
.AllowPinning(true)
.EnableTitleBarBubble(true)
.EnableBubbleCtrls(true)
.GraphLOD(this, &SGraphNode::GetCurrentLOD)
.IsGraphNodeHovered(this, &SGraphNode::IsHovered);
GetOrAddSlot(ENodeZone::TopCenter)
.SlotOffset(TAttribute<FVector2D>(CommentBubble.Get(), &SCommentBubble::GetOffset))
.SlotSize(TAttribute<FVector2D>(CommentBubble.Get(), &SCommentBubble::GetSize))
.AllowScaling(TAttribute<bool>(CommentBubble.Get(), &SCommentBubble::IsScalingAllowed))
.VAlign(EVerticalAlignment::VAlign_Top)
[
CommentBubble.ToSharedRef()
];
ErrorReporting = ErrorText;
ErrorReporting->SetError(ErrorMsg);
CreatePinWidgets();
}
void SEdComboActionGraphNode::CreatePinWidgets()
{
UEdComboActionGraphNode *StateNode = CastChecked<UEdComboActionGraphNode>(this->GraphNode);
for (int32 PinIdx = 0; PinIdx < StateNode->Pins.Num(); PinIdx++)
{
UEdGraphPin* MyPin = StateNode->Pins[PinIdx];
if (!MyPin->bHidden)
{
TSharedPtr<SGraphPin> NewPin = SNew(SComboActionGraphPin, MyPin);
AddPin(NewPin.ToSharedRef());
}
}
}
void SEdComboActionGraphNode::AddPin(const TSharedRef<SGraphPin>& PinToAdd)
{
PinToAdd->SetOwner(SharedThis(this));
const UEdGraphPin* PinObj = PinToAdd->GetPinObj();
const bool bAdvancedParameter = PinObj && PinObj->bAdvancedView;
if (bAdvancedParameter)
{
PinToAdd->SetVisibility( TAttribute<EVisibility>(PinToAdd, &SGraphPin::IsPinVisibleAsAdvanced) );
}
if (PinToAdd->GetDirection() == EEdGraphPinDirection::EGPD_Input)
{
this->LeftNodeBox->AddSlot()
.HAlign(HAlign_Fill)
.VAlign(VAlign_Fill)
.FillHeight(1.0f)
.Padding(20.0f,0.0f)
[
PinToAdd
];
this->InputPins.Add(PinToAdd);
}
else // Direction == EEdGraphPinDirection::EGPD_Output
{
this->OutputPinBox->AddSlot()
.HAlign(HAlign_Fill)
.VAlign(VAlign_Fill)
.FillWidth(1.0f)
[
PinToAdd
];
this->OutputPins.Add(PinToAdd);
}
}
bool SEdComboActionGraphNode::IsNameReadOnly() const
{
UEdComboActionGraphNode *EdNode_Node = Cast<UEdComboActionGraphNode>(this->GraphNode);
check(EdNode_Node != nullptr);
UComboActionGraph *ComboActionGraphNode = EdNode_Node->ComboActionGraphNode->Graph;
check(this->GraphNode != nullptr);
return !this->GraphNode->bCanRenameNode || SGraphNode::IsNameReadOnly();
}
END_SLATE_FUNCTION_BUILD_OPTIMIZATION
void SEdComboActionGraphNode::OnNameTextCommitted(const FText& InText, ETextCommit::Type CommitInfo)
{
if (InText.IsEmpty()) return;
SGraphNode::OnNameTextCommited(InText, CommitInfo);
UEdComboActionGraphNode *MyNode = CastChecked<UEdComboActionGraphNode>(this->GraphNode);
if (MyNode != nullptr && MyNode->ComboActionGraphNode != nullptr)
{
const FScopedTransaction Transaction(LOCTEXT("ComboActionGraphEditorRenameNode", "Combo Action Editor: Rename Node"));
MyNode->Modify();
MyNode->ComboActionGraphNode->Modify();
MyNode->ComboActionGraphNode->SetNodeTitle(InText);
UpdateGraphNode();
}
}
const FSlateBrush *SEdComboActionGraphNode::GetNodeTypeBrush() const
{
FComboInputEditorModule &ComboInputEditorModule = FComboInputEditorModule::Get();
if (this->GraphEditorSettings)
{
switch (this->GraphEditorSettings->GetNodeType())
{
case EComboActionNodeType::SoftCorners:
return ComboInputEditorModule.GetComboInputEditorStyleSet()->GetBrush("MDSStyleSet.Node.SoftEdges");
case EComboActionNodeType::HardCorners:
return ComboInputEditorModule.GetComboInputEditorStyleSet()->GetBrush("MDSStyleSet.Node.HardEdges");
}
}
return ComboInputEditorModule.GetComboInputEditorStyleSet()->GetBrush("MDSStyleSet.Node.SoftEdges");
}
const FSlateBrush *SEdComboActionGraphNode::GetTextNodeTypeBrush() const
{
FComboInputEditorModule &ComboInputEditorModule = FComboInputEditorModule::Get();
if (this->GraphEditorSettings)
{
switch (this->GraphEditorSettings->GetNodeType())
{
case EComboActionNodeType::SoftCorners:
return ComboInputEditorModule.GetComboInputEditorStyleSet()->GetBrush("MDSStyleSet.Node.TextSoftEdges");
case EComboActionNodeType::HardCorners:
return ComboInputEditorModule.GetComboInputEditorStyleSet()->GetBrush("MDSStyleSet.Node.TextHardEdges");
}
}
return ComboInputEditorModule.GetComboInputEditorStyleSet()->GetBrush("MDSStyleSet.Node.TextSoftEdges");
}
FSlateColor SEdComboActionGraphNode::GetBorderBackgroundColor() const
{
UEdComboActionGraphNode *MyNode = CastChecked<UEdComboActionGraphNode>(this->GraphNode);
return MyNode ? MyNode->GetBackgroundColor() : ComboActionGraphColors::NodeBorder::HighlightAbortRange0;
}
FSlateColor SEdComboActionGraphNode::GetBorderFrontColor() const
{
if (this->GraphEditorSettings)
{
switch (this->GraphEditorSettings->GetNodeTheme())
{
case EComboActionNodeTheme::DarkTheme:
return ComboActionGraphColors::Overlay::DarkTheme;
case EComboActionNodeTheme::LightTheme:
return ComboActionGraphColors::Overlay::LightTheme;
}
}
return ComboActionGraphColors::Overlay::DarkTheme;
}
FSlateColor SEdComboActionGraphNode::GetNodeTitleBackgroundColor() const
{
return ComboActionGraphColors::NodeBody::Default;
}
FSlateColor SEdComboActionGraphNode::GetDecoratorsBackgroundColor() const
{
return ComboActionGraphColors::DecoratorsBody::Default;
}
FSlateColor SEdComboActionGraphNode::GetPinsDockColor() const
{
if (this->GraphEditorSettings)
{
switch (this->GraphEditorSettings->GetNodeTheme())
{
case EComboActionNodeTheme::DarkTheme:
return ComboActionGraphColors::PinsDock::DarkTheme;
case EComboActionNodeTheme::LightTheme:
return ComboActionGraphColors::PinsDock::LightTheme;
}
}
return ComboActionGraphColors::Overlay::DarkTheme;
}
EVisibility SEdComboActionGraphNode::GetDragOverMarkerVisibility() const
{
return EVisibility::Visible;
}
const FSlateBrush*SEdComboActionGraphNode::GetNameIcon() const
{
return FAppStyle::GetBrush(TEXT("BTEditor.Graph.BTNode.Icon"));
}
const FSlateBrush*SEdComboActionGraphNode::GetBulletPointImageBrush() const
{
FComboInputEditorModule &ComboInputEditorModule = FComboInputEditorModule::Get();
return ComboInputEditorModule.GetComboInputEditorStyleSet()->GetBrush( "MDSStyleSet.Icon.BulletPoint" );
}
FText SEdComboActionGraphNode::GetIndexOverlayTooltipText() const
{
return LOCTEXT("NodeIndexTooltip", "Node index");
}
FText SEdComboActionGraphNode::GetIndexText() const
{
if (const UEdComboActionGraphNode *EdParentNode = Cast<UEdComboActionGraphNode>(GraphNode))
{
if (const auto Node = EdParentNode->ComboActionGraphNode)
{
if (const auto Graph = Node->Graph)
{
const int32 Index = Graph->AllNodes.Find(Node);
return FText::AsNumber(Index);
}
return FText::AsNumber(EdParentNode->ComboActionGraphNode->GetNodeIndex());
}
}
return FText::AsNumber(INDEX_NONE);
}
EVisibility SEdComboActionGraphNode::GetIndexSlotVisibility() const
{
if (this->IsHovered())
{
return EVisibility::SelfHitTestInvisible;
}
return EVisibility::Collapsed;
}
FVector2D SEdComboActionGraphNode::GetIndexSlotOffset() const
{
if (this->IsHovered())
{
return FVector2D(-20.f);
}
return FVector2D(-15.f);
}
FVector2D SEdComboActionGraphNode::GetIndexSlotSize() const
{
if (this->IsHovered())
{
return FVector2D(24.f);
}
return FVector2D(12.f);
}
void SEdComboActionGraphNode::OnIndexHoverStateChanged(bool bArg) const
{
// TODO something
}
FSlateColor SEdComboActionGraphNode::GetOverlayWidgetBackgroundColor(bool bArg) const
{
return bArg ? ComboActionGraphColors::IndexBorder::HoveredState : ComboActionGraphColors::IndexBorder::NormalState;
}
EVisibility SEdComboActionGraphNode::ShowImplementsOnlySlot_Unified() const
{
if (this->GraphEditorSettings)
{
if (this->GraphEditorSettings->ShowDetailedInfo_NumDecorators() && !this->GraphEditorSettings->ShowDetailedInfo_InheritsDecorators())
{
return EVisibility::SelfHitTestInvisible; //return HasGraphDecorators() ? EVisibility::Visible : EVisibility::Collapsed;
}
}
return EVisibility::Collapsed;
}
EVisibility SEdComboActionGraphNode::ShowImplementsOnlySlot_Stack() const
{
if (this->GraphEditorSettings)
{
if (this->GraphEditorSettings->GetDecoratorsStyle() == EComboActionDecoratorsInfoStyle::Stack)
{
if (this->GraphEditorSettings->ShowDetailedInfo_NumDecorators())
{
return EVisibility::SelfHitTestInvisible; //return HasGraphDecorators() ? EVisibility::Visible : EVisibility::Collapsed;
}
}
}
return EVisibility::Collapsed;
}
EVisibility SEdComboActionGraphNode::ShowInheritsDecoratorsSlot_Stack() const
{
if (this->GraphEditorSettings)
{
if (this->GraphEditorSettings->GetDecoratorsStyle() == EComboActionDecoratorsInfoStyle::Stack)
{
if (this->GraphEditorSettings->ShowDetailedInfo_InheritsDecorators())
{
return EVisibility::SelfHitTestInvisible; //return HasGraphDecorators() ? EVisibility::Visible : EVisibility::Collapsed;
}
}
}
return EVisibility::Collapsed;
}
EVisibility SEdComboActionGraphNode::ShowAllDecorators() const
{
if (this->GraphEditorSettings)
{
if (this->GraphEditorSettings->GetDecoratorsStyle() == EComboActionDecoratorsInfoStyle::Unified)
{
if (this->GraphEditorSettings->ShowDetailedInfo_InheritsDecorators() && this->GraphEditorSettings->ShowDetailedInfo_NumDecorators())
{
return EVisibility::SelfHitTestInvisible; //return HasGraphDecorators() ? EVisibility::Visible : EVisibility::Collapsed;
}
}
}
return EVisibility::Collapsed;
}
EVisibility SEdComboActionGraphNode::ShowDecoratorsBottomPadding() const
{
if (this->GraphEditorSettings)
{
if (this->GraphEditorSettings->ShowDetailedInfo_InheritsDecorators() || this->GraphEditorSettings->ShowDetailedInfo_NumDecorators())
{
return EVisibility::SelfHitTestInvisible;
}
}
return EVisibility::Collapsed;
}
EComboActionDecoratorsInfoStyle SEdComboActionGraphNode::GetDecoratorsStyle() const
{
if (this->GraphEditorSettings)
{
return this->GraphEditorSettings->GetDecoratorsStyle();
}
if (const UComboActionGraphEditorSettings *TempSettings = GetMutableDefault<UComboActionGraphEditorSettings>())
{
return TempSettings->GetDecoratorsStyle();
}
return EComboActionDecoratorsInfoStyle::Stack;
}
EVisibility SEdComboActionGraphNode::GetResponseStackVisibility() const
{
if (const UEdComboActionGraphNode *EdParentNode = Cast<UEdComboActionGraphNode>(this->GraphNode))
{
if (const UComboActionGraphNode_ActionNodeBase *ActionNode = Cast<UComboActionGraphNode_ActionNodeBase>(EdParentNode->ComboActionGraphNode))
{
return EVisibility::SelfHitTestInvisible;
}
}
return EVisibility::Collapsed;
}
FText SEdComboActionGraphNode::GetTooltipText() const
{
if (const UEdComboActionGraphNode *EdParentNode = Cast<UEdComboActionGraphNode>(this->GraphNode))
{
return EdParentNode->GetTooltipText();
}
return LOCTEXT("SEdComboActionGraphNode_Tooltip", "invalid node selected");
}
FText SEdComboActionGraphNode::GetComboActionName() const
{
if (const UEdComboActionGraphNode *EdParentNode = Cast<UEdComboActionGraphNode>(this->GraphNode))
{
if (const UComboActionGraphNode_ActionNodeBase *ActionNode = Cast<UComboActionGraphNode_ActionNodeBase>(EdParentNode->ComboActionGraphNode))
{
if (UComboAction *ComboAction = ActionNode->GetComboAction())
{
return FText::FromName(ComboAction->ActionName);
}
}
else if (const UComboActionGraphNode_StartNode *StartNode = Cast<UComboActionGraphNode_StartNode>(EdParentNode->ComboActionGraphNode))
{
return LOCTEXT("SEdComboActionGraphNode_StartName", "Start");
}
}
return LOCTEXT("SEdComboActionGraphNode_ComboActionNameNotSet", "NOT CONFIGURED");
}
FText SEdComboActionGraphNode::GetComboInputName() const
{
if (const UEdComboActionGraphNode *EdParentNode = Cast<UEdComboActionGraphNode>(this->GraphNode))
{
if (const UComboActionGraphNode_ActionNodeBase *ActionNode = Cast<UComboActionGraphNode_ActionNodeBase>(EdParentNode->ComboActionGraphNode))
{
if (UComboInputAsset *ComboInput = ActionNode->GetComboInput())
{
return FText::Format(LOCTEXT("SEdComboActionGraphNode_ComboInputName", "When {0}"), FText::FromName(ComboInput->ComboInputName));
}
}
}
return LOCTEXT("SEdComboActionGraphNode_ComboInputNameNotSet", "No combo input");
}
FText SEdComboActionGraphNode::GetComboInputTriggerActionName() const
{
if (const UEdComboActionGraphNode *EdParentNode = Cast<UEdComboActionGraphNode>(this->GraphNode))
{
if (const UComboActionGraphNode_ActionNodeBase *ActionNode = Cast<UComboActionGraphNode_ActionNodeBase>(EdParentNode->ComboActionGraphNode))
{
const FString &EnumNameString = StaticEnum<EComboActionTriggerEvent>()->GetNameByValue((uint8)ActionNode->GetTriggerEvent()).ToString();
FString EnumType, EnumValueName;
EnumNameString.Split("::", &EnumType, &EnumValueName);
return FText::Format(LOCTEXT("SEdComboActionGraphNode_TriggerActionName", "is {0}"), FText::FromString(EnumValueName));
}
}
return LOCTEXT("SEdComboActionGraphNode_TriggerActionNameNotSet", "No trigger event");
}
#undef LOCTEXT_NAMESPACE

View File

@ -0,0 +1,78 @@
// ©2023 Batty Bovine Productions, LLC. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "SGraphNode.h"
#include "Settings\ComboActionGraphEditorSettings.h"
class SEdComboActionGraphNode : public SGraphNode
{
public:
SLATE_BEGIN_ARGS(SEdComboActionGraphNode) {}
SLATE_END_ARGS()
void Construct(const FArguments &InArgs, class UEdComboActionGraphNode *InNode);
virtual void OnMouseEnter(const FGeometry &MyGeometry, const FPointerEvent &MouseEvent) override;
virtual void OnMouseLeave(const FPointerEvent &MouseEvent) override;
const FSlateBrush *GetIndexBrush() const;
virtual void UpdateGraphNode() override;
virtual void CreatePinWidgets() override;
virtual void AddPin(const TSharedRef<SGraphPin> &PinToAdd) override;
virtual bool IsNameReadOnly() const override;
void OnNameTextCommitted(const FText &InText, ETextCommit::Type CommitInfo);
virtual const FSlateBrush *GetNodeTypeBrush () const;
virtual const FSlateBrush *GetTextNodeTypeBrush () const;
virtual FSlateColor GetBorderBackgroundColor() const;
virtual FSlateColor GetBorderFrontColor() const;
virtual FSlateColor GetNodeTitleBackgroundColor() const;
virtual FSlateColor GetDecoratorsBackgroundColor() const;
virtual FSlateColor GetPinsDockColor() const;
virtual EVisibility GetDragOverMarkerVisibility() const;
virtual const FSlateBrush *GetNameIcon() const;
const FSlateBrush *GetBulletPointImageBrush() const;
virtual FText GetIndexOverlayTooltipText() const;
virtual FText GetIndexText() const;
EVisibility GetIndexSlotVisibility() const;
FVector2D GetIndexSlotOffset() const;
FVector2D GetIndexSlotSize() const;
virtual void OnIndexHoverStateChanged(bool bArg) const;
virtual FSlateColor GetOverlayWidgetBackgroundColor(bool bArg) const;
EVisibility ShowImplementsOnlySlot_Unified() const;
EVisibility ShowImplementsOnlySlot_Stack() const;
EVisibility ShowInheritsDecoratorsSlot_Stack() const;
EVisibility ShowAllDecorators() const;
EVisibility ShowDecoratorsBottomPadding() const;
virtual EComboActionDecoratorsInfoStyle GetDecoratorsStyle() const;
EVisibility GetResponseStackVisibility() const;
FText GetTooltipText() const;
FText GetComboActionName() const;
FText GetComboInputName() const;
FText GetComboInputTriggerActionName() const;
protected:
TSharedPtr<SBorder> NodeBody;
TSharedPtr<SHorizontalBox> OutputPinBox;
class UComboActionGraphEditorSettings *GraphEditorSettings = nullptr;
FLinearColor NodeInnerColor;
FLinearColor PinsDockColor;
};

View File

@ -0,0 +1,37 @@
// ©2023 Batty Bovine Productions, LLC. All Rights Reserved.
#include "SEdComboActionGraphNodeIndex.h"
#include "ComboInputEditor.h"
void SEdComboActionGraphNodeIndex::Construct(const FArguments &InArgs)
{
FComboInputEditorModule &ComboInputEditorModule = FComboInputEditorModule::Get();
const FSlateBrush* CircleBrush = ComboInputEditorModule.GetComboInputEditorStyleSet()->GetBrush(TEXT("MDSStyleSet.Node.IndexCircle"));
ChildSlot
[
SNew(SOverlay)
+SOverlay::Slot()
.HAlign(EHorizontalAlignment::HAlign_Fill)
.VAlign(EVerticalAlignment::VAlign_Fill)
[
// Add a dummy box here to make sure the widget doesnt get smaller than the brush
SNew(SBox)
.WidthOverride(CircleBrush->ImageSize.X)
.HeightOverride(CircleBrush->ImageSize.Y)
]
+SOverlay::Slot()
.HAlign(EHorizontalAlignment::HAlign_Fill)
.VAlign(EVerticalAlignment::VAlign_Fill)
[
SNew(SBorder)
.BorderImage(CircleBrush)
.BorderBackgroundColor(this, &SEdComboActionGraphNodeIndex::GetBackgroundColor)
.Padding(FMargin(4.0f))
.VAlign(EVerticalAlignment::VAlign_Center)
.HAlign(EHorizontalAlignment::HAlign_Center)
]
];
}

View File

@ -0,0 +1,33 @@
// ©2023 Batty Bovine Productions, LLC. All Rights Reserved.
#pragma once
#include "Widgets/DeclarativeSyntaxSupport.h"
#include "Widgets/SCompoundWidget.h"
class SEdComboActionGraphNodeIndex : public SCompoundWidget
{
public:
/** Delegate event fired when the hover state of this widget changes */
DECLARE_DELEGATE_OneParam(FOnHoverStateChanged, bool /* bHovered */);
/** Delegate used to receive the background color of the node, depending on hover state and state of other siblings */
DECLARE_DELEGATE_RetVal_OneParam(FSlateColor, FOnGetBackgroundColor, bool /* bHovered */);
SLATE_BEGIN_ARGS(SEdComboActionGraphNodeIndex) {}
SLATE_ATTRIBUTE(TSharedPtr<SWidget>, OverlayBody)
// Events
SLATE_EVENT(FOnHoverStateChanged, OnHoverStateChanged)
SLATE_EVENT(FOnGetBackgroundColor, OnGetBackgroundColor)
SLATE_END_ARGS()
void Construct(const FArguments &InArgs);
/** Get the color we use to display the rounded border */
FSlateColor GetBackgroundColor() const { return FSlateColor::UseForeground(); }
private:
/** The OverlayBody used for this widget*/
TSharedPtr<SWidget> OverlayBody;
};

View File

@ -0,0 +1,87 @@
// ©2023 Batty Bovine Productions, LLC. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "ComboInputAssets.h"
#include "Ed/EdComboActionGraphNode.h"
#include "Kismet/BlueprintFunctionLibrary.h"
#include "Nodes/ComboActionGraphNode.h"
#include "Nodes/ComboActionGraphNode_ActionNodeBase.h"
#include "Settings/ComboActionGraphEditorSettings.h"
#include "ComboActionEditorBFC.generated.h"
/**
* Editor Only helper functions.
*/
UCLASS()
class UComboActionEditorBFC : public UBlueprintFunctionLibrary
{
GENERATED_BODY()
public:
#if WITH_EDITOR
static FText GetNodeTitle(UComboActionGraphNode *Node)
{
if (!Node)
{
return FText::FromString("Invalid Node");
}
const UComboActionGraphEditorSettings *Settings = GetDefault<UComboActionGraphEditorSettings>();
if (Settings)
{
if (Settings->ShowAutomaticNames())
{
if (const UComboActionGraphNode_ActionNodeBase *DialogueNodeBase = Cast<UComboActionGraphNode_ActionNodeBase>(Node))
{
if (const UComboInputAsset *ComboInput = DialogueNodeBase->GetComboInput())
{
return FText::FromString(ComboInput->GetName());
}
}
return Node->GetInternalName();
}
}
return Node->GetNodeTitle();
}
static EComboActionNodeTheme GetNodeTheme()
{
const UComboActionGraphEditorSettings *Settings = GetDefault<UComboActionGraphEditorSettings>();
if (Settings != nullptr)
{
return Settings->GetNodeTheme();
}
return EComboActionNodeTheme::DarkTheme;
}
static void TriggerPreviewRefresh(TArray<UObject*> NodeObjects)
{
for (auto Itr : NodeObjects)
{
UEdComboActionGraphNode *SelectedNode = Cast<UEdComboActionGraphNode>(Itr);
if (!SelectedNode || !SelectedNode->ComboActionGraphNode)
{
continue;
}
UComboActionGraphNode_ActionNodeBase *DialogueNodeBase = Cast<UComboActionGraphNode_ActionNodeBase>(SelectedNode->ComboActionGraphNode);
if (!DialogueNodeBase)
{
continue;
}
DialogueNodeBase->UpdatePreviews();
}
}
#endif
};

View File

@ -0,0 +1,92 @@
#pragma once
#include "CoreMinimal.h"
namespace ComboActionGraphColors
{
namespace NodeBody
{
constexpr FLinearColor Default(0.1f, 0.1f, 0.1f);
constexpr FLinearColor Root(0.5f, 0.5f, 0.5f, 0.1f);
constexpr FLinearColor Error(1.0f, 0.0f, 0.0f);
}
namespace DecoratorsBody
{
constexpr FLinearColor Default(0.1f, 0.1f, 0.1f);
}
namespace NodeBorder
{
constexpr FLinearColor Inactive(0.08f, 0.08f, 0.08f);
constexpr FLinearColor Root(0.2f, 0.2f, 0.2f, 0.2f);
constexpr FLinearColor Selected(1.00f, 0.08f, 0.08f);
constexpr FLinearColor ActiveDebugging(1.0f, 1.0f, 0.0f);
constexpr FLinearColor InactiveDebugging(0.4f, 0.4f, 0.0f);
constexpr FLinearColor HighlightAbortRange0(0.0f, 0.22f, 0.4f);
constexpr FLinearColor HighlightAbortRange1(0.0f, 0.4f, 0.22f);
constexpr FLinearColor Disconnected(0.f, 0.f, 0.f);
constexpr FLinearColor BrokenWithParent(1.f, 0.f, 1.f);
constexpr FLinearColor QuickFind(0.f, 0.8f, 0.f);
}
namespace Pin
{
constexpr FLinearColor Diff(0.9f, 0.2f, 0.15f);
constexpr FLinearColor Hover(1.0f, 0.7f, 0.0f);
constexpr FLinearColor Default(0.02f, 0.02f, 0.02f);
constexpr FLinearColor SingleNode(0.02f, 0.02f, 0.02f);
}
namespace Connection
{
constexpr FLinearColor Default(1.0f, 1.0f, 1.0f);
}
namespace Action
{
constexpr FLinearColor DragMarker(1.0f, 1.0f, 0.2f);
}
namespace Overlay
{
constexpr FLinearColor LightTheme(1.f, 1.f, 1.f, 0.45f);
constexpr FLinearColor DarkTheme(0.f, 0.f, 0.f, 0.9f);
}
namespace PinsDock
{
constexpr FLinearColor DarkTheme(0.075f, 0.075f, 0.075f, 1.f);
constexpr FLinearColor LightTheme(0.075f, 0.075f, 0.075f, 1.f);
}
namespace ValidationGraph
{
constexpr FLinearColor LightTheme(0.1f, 0.1f, 0.1f, 1.f);
constexpr FLinearColor DarkTheme(0.05f, 0.05f, 0.05f, 1.f);
}
namespace IndexBorder
{
constexpr FLinearColor NormalState(0.05f, 0.05f, 0.05f, 1.f);
constexpr FLinearColor HoveredState(0.05f, 0.05f, 0.05f, 1.f);
}
namespace BulletPointsColors
{
constexpr FLinearColor Normal(0.8f, 0.8f, 0.8f, 1.f);
constexpr FLinearColor Disabled(0.8f, 0.8f, 0.8f, 0.2f);
}
namespace TextColors
{
constexpr FLinearColor Normal(0.8f, 0.8f, 0.8f, 1.f);
constexpr FLinearColor Disabled(0.8f, 0.8f, 0.8f, 0.2f);
}
namespace Previews
{
constexpr FLinearColor Normal(0.8f, 0.8f, 0.8f, 0.05f);
constexpr FLinearColor Invalid(0.8f, 0.8f, 0.8f, 0.2f);
}
}

View File

@ -0,0 +1,257 @@
// ©2023 Batty Bovine Productions, LLC. All Rights Reserved.
#include "ComboActionGraphEditorUtilities.h"
#include "ComboActionGraph.h"
#include "K2Node_Event.h"
#include "Ed/AssetEditor_ComboActionGraph.h"
#include "Ed/EdComboActionGraph.h"
#include "Ed/EdComboActionGraphNode.h"
#include "Kismet2/BlueprintEditorUtils.h"
#include "Kismet2/KismetEditorUtilities.h"
#include "Kismet2/SClassPickerDialog.h"
#include "Layout/AssetEditorTabs.h"
bool FComboActionGraphEditorUtilities::PickChildrenOfClass(const FText &TitleText, UClass *&OutChosenClass, UClass *Class)
{
// Create filter
TSharedPtr<FComboActionClassViewerFilter> Filter = MakeShareable(new FComboActionClassViewerFilter);
Filter->AllowedChildrenOfClasses.Add(Class);
// Fill in options
FClassViewerInitializationOptions Options;
Options.Mode = EClassViewerMode::ClassPicker;
Options.bShowUnloadedBlueprints = true;
Options.ClassFilters.Add(Filter.ToSharedRef());
Options.DisplayMode = EClassViewerDisplayMode::TreeView;
Options.bShowNoneOption = false;
Options.InitiallySelectedClass = Class;
Options.bExpandRootNodes = true;
Options.NameTypeToDisplay = EClassViewerNameTypeToDisplay::DisplayName;
return SClassPickerDialog::PickClass(TitleText, Options, OutChosenClass, Class);
}
bool FComboActionGraphEditorUtilities::OpenBlueprintEditor(UBlueprint *Blueprint, EComboActionBlueprintOpenType OpenType, FName FunctionNameToOpen, bool bForceFullEditor, bool bAddBlueprintFunctionIfItDoesNotExist)
{
if (!Blueprint)
{
return false;
}
Blueprint->bForceFullEditor = bForceFullEditor;
// Find Function Graph
UObject *ObjectToFocusOn = nullptr;
if (OpenType != EComboActionBlueprintOpenType::None && FunctionNameToOpen != NAME_None)
{
UClass* Class = Blueprint->GeneratedClass;
check(Class);
if (OpenType == EComboActionBlueprintOpenType::Function)
{
ObjectToFocusOn = bAddBlueprintFunctionIfItDoesNotExist
? FComboActionGraphEditorUtilities::BlueprintGetOrAddFunction(Blueprint, FunctionNameToOpen, Class)
: FComboActionGraphEditorUtilities::BlueprintGetFunction(Blueprint, FunctionNameToOpen, Class);
}
else if (OpenType == EComboActionBlueprintOpenType::Event)
{
ObjectToFocusOn = bAddBlueprintFunctionIfItDoesNotExist
? FComboActionGraphEditorUtilities::BlueprintGetOrAddEvent(Blueprint, FunctionNameToOpen, Class)
: FComboActionGraphEditorUtilities::BlueprintGetEvent(Blueprint, FunctionNameToOpen, Class);
}
}
// Default to the last uber graph
if (ObjectToFocusOn == nullptr)
{
ObjectToFocusOn = Blueprint->GetLastEditedUberGraph();
}
if (ObjectToFocusOn)
{
FKismetEditorUtilities::BringKismetToFocusAttentionOnObject(ObjectToFocusOn);
return true;
}
return FComboActionGraphEditorUtilities::OpenEditorForAsset(Blueprint);
}
UEdGraph *FComboActionGraphEditorUtilities::BlueprintGetOrAddFunction(UBlueprint *Blueprint, FName FunctionName, UClass *FunctionClassSignature)
{
if (!Blueprint || Blueprint->BlueprintType != BPTYPE_Normal)
{
return nullptr;
}
// Find existing function
if (UEdGraph *GraphFunction = BlueprintGetFunction(Blueprint, FunctionName, FunctionClassSignature))
{
return GraphFunction;
}
// Create a new function
UEdGraph *NewGraph = FBlueprintEditorUtils::CreateNewGraph(Blueprint, FunctionName, UEdGraph::StaticClass(), UEdGraphSchema_K2::StaticClass());
FBlueprintEditorUtils::AddFunctionGraph(Blueprint, NewGraph, /*bIsUserCreated=*/ false, FunctionClassSignature);
Blueprint->LastEditedDocuments.Add(NewGraph);
return NewGraph;
}
UEdGraph *FComboActionGraphEditorUtilities::BlueprintGetFunction(UBlueprint *Blueprint, FName FunctionName, UClass *FunctionClassSignature)
{
if (!Blueprint || Blueprint->BlueprintType != BPTYPE_Normal)
{
return nullptr;
}
// Find existing function
for (UEdGraph *GraphFunction : Blueprint->FunctionGraphs)
{
if (FunctionName == GraphFunction->GetFName())
{
return GraphFunction;
}
}
// Find in the implemented Interfaces Graphs
for (const FBPInterfaceDescription &Interface : Blueprint->ImplementedInterfaces)
{
for (UEdGraph *GraphFunction : Interface.Graphs)
{
if (FunctionName == GraphFunction->GetFName())
{
return GraphFunction;
}
}
}
return nullptr;
}
UK2Node_Event *FComboActionGraphEditorUtilities::BlueprintGetOrAddEvent(UBlueprint *Blueprint, FName EventName, UClass *EventClassSignature)
{
if (!Blueprint || Blueprint->BlueprintType != EBlueprintType::BPTYPE_Normal)
{
return nullptr;
}
// Find existing event
if (UK2Node_Event *EventNode = FComboActionGraphEditorUtilities::BlueprintGetEvent(Blueprint, EventName, EventClassSignature))
{
return EventNode;
}
// Create a New Event
if (Blueprint->UbergraphPages.Num())
{
int32 NodePositionY = 0;
UK2Node_Event* NodeEvent = FKismetEditorUtilities::AddDefaultEventNode(
Blueprint,
Blueprint->UbergraphPages[0],
EventName,
EventClassSignature,
NodePositionY
);
NodeEvent->SetEnabledState(ENodeEnabledState::Enabled);
NodeEvent->NodeComment = "";
NodeEvent->bCommentBubbleVisible = false;
return NodeEvent;
}
return nullptr;
}
UK2Node_Event *FComboActionGraphEditorUtilities::BlueprintGetEvent(UBlueprint *Blueprint, FName EventName, UClass *EventClassSignature)
{
if (!Blueprint || Blueprint->BlueprintType != EBlueprintType::BPTYPE_Normal)
{
return nullptr;
}
TArray<UK2Node_Event*> AllEvents;
FBlueprintEditorUtils::GetAllNodesOfClass<UK2Node_Event>(Blueprint, AllEvents);
for (UK2Node_Event *EventNode : AllEvents)
{
if (EventNode->bOverrideFunction && EventNode->EventReference.GetMemberName() == EventName)
{
return EventNode;
}
}
return nullptr;
}
bool FComboActionGraphEditorUtilities::OpenEditorForAsset(const UObject* Asset)
{
if (!IsValid(Asset) || !GEditor)
{
return false;
}
return GEditor->GetEditorSubsystem<UAssetEditorSubsystem>()->OpenEditorForAsset(const_cast<UObject*>(Asset));
}
bool FComboActionGraphEditorUtilities::OpenEditorAndJumpToGraphNode(TWeakPtr<FAssetEditor_ComboActionGraph> DialogueEditorPtr, const UEdGraphNode *GraphNode, bool bFocusIfOpen)
{
if (!IsValid(GraphNode))
{
return false;
}
if (!DialogueEditorPtr.IsValid())
{
return false;
}
// Open if not already.
UComboActionGraph *Dialogue = FComboActionGraphEditorUtilities::GetActionFromGraphNode(GraphNode);
if (!FComboActionGraphEditorUtilities::OpenEditorForAsset(Dialogue))
{
return false;
}
// Could still fail focus on the graph node
if (IAssetEditorInstance *EditorInstance = FindEditorForAsset(Dialogue, bFocusIfOpen))
{
EditorInstance->FocusWindow(const_cast<UEdGraphNode*>(GraphNode));
UEdComboActionGraph *GraphEditor = Cast<UEdComboActionGraph>(Dialogue->EdGraph);
if (GraphEditor)
{
TSet<const UEdGraphNode*> SelectedNodes;
SelectedNodes.Add(GraphNode);
GraphEditor->SelectNodeSet(SelectedNodes);
DialogueEditorPtr.Pin()->JumpToNode(GraphNode);
}
return true;
}
return false;
}
UComboActionGraph *FComboActionGraphEditorUtilities::GetActionFromGraphNode(const UEdGraphNode *GraphNode)
{
if (const UEdComboActionGraphNode *ComboActionBaseNode = Cast<UEdComboActionGraphNode>(GraphNode))
{
return ComboActionBaseNode->GetEdComboActionGraph()->GetComboActionGraph();
}
if (const UEdComboActionGraph *ComboActionGraph = Cast<UEdComboActionGraph>(GraphNode->GetGraph()))
{
return Cast<UComboActionGraph>(ComboActionGraph->GetComboActionGraph());
}
return nullptr;
}
IAssetEditorInstance *FComboActionGraphEditorUtilities::FindEditorForAsset(UObject* Asset, bool bFocusIfOpen)
{
if (!IsValid(Asset) || !GEditor)
{
return nullptr;
}
return GEditor->GetEditorSubsystem<UAssetEditorSubsystem>()->FindEditorForAsset(Asset, bFocusIfOpen);
}

View File

@ -0,0 +1,103 @@
// ©2023 Batty Bovine Productions, LLC. All Rights Reserved.
#pragma once
#include "ClassViewerFilter.h"
enum class EComboActionBlueprintOpenType : uint8
{
None = 0,
Function,
Event
};
class FComboActionClassViewerFilter : public IClassViewerFilter
{
public:
// All children of these classes will be included unless filtered out by another setting.
TSet<const UClass*> AllowedChildrenOfClasses;
virtual bool IsClassAllowed(const FClassViewerInitializationOptions &InInitOptions, const UClass *InClass, TSharedRef<FClassViewerFilterFuncs> InFilterFuncs) override
{
return !InClass->HasAnyClassFlags(this->DisallowedClassFlags)
&& InFilterFuncs->IfInChildOfClassesSet(this->AllowedChildrenOfClasses, InClass) != EFilterReturn::Failed;
}
virtual bool IsUnloadedClassAllowed(const FClassViewerInitializationOptions &InInitOptions, const TSharedRef<const IUnloadedBlueprintData> InUnloadedClassData, TSharedRef<FClassViewerFilterFuncs> InFilterFuncs) override
{
return !InUnloadedClassData->HasAnyClassFlags(this->DisallowedClassFlags)
&& InFilterFuncs->IfInChildOfClassesSet(this->AllowedChildrenOfClasses, InUnloadedClassData) != EFilterReturn::Failed;
}
private:
// Disallowed class flags.
EClassFlags DisallowedClassFlags = CLASS_Deprecated;
};
class FComboActionGraphEditorUtilities
{
public:
static bool PickChildrenOfClass(const FText &TitleText, UClass *&OutChosenClass, UClass *Class);
static bool OpenBlueprintEditor
(
UBlueprint *Blueprint,
EComboActionBlueprintOpenType OpenType = EComboActionBlueprintOpenType::None,
FName FunctionNameToOpen = NAME_None,
bool bForceFullEditor = true,
bool bAddBlueprintFunctionIfItDoesNotExist = false
);
static UEdGraph *BlueprintGetOrAddFunction(UBlueprint *Blueprint, FName FunctionName, UClass *FunctionClassSignature);
static UEdGraph *BlueprintGetFunction(UBlueprint *Blueprint, FName FunctionName, UClass *FunctionClassSignature);
static class UK2Node_Event *BlueprintGetOrAddEvent(UBlueprint *Blueprint, FName EventName, UClass *EventClassSignature);
static class UK2Node_Event *BlueprintGetEvent(UBlueprint *Blueprint, FName EventName, UClass *EventClassSignature);
static bool OpenEditorForAsset(const UObject *Asset);
static bool IsABlueprintClass(const UClass *Class) { return Cast<UBlueprintGeneratedClass>(Class) != nullptr; }
static bool GetAllChildClassesOf(const UClass *ParentClass, TArray<UClass*> &OutNativeClasses, TArray<UClass*> &OutBlueprintClasses)
{
// Iterate over UClass, this might be heavy on performance
for (TObjectIterator<UClass> It; It; ++It)
{
UClass *ChildClass = *It;
if (!ChildClass->IsChildOf(ParentClass))
{
continue;
}
// It is a child of the Parent Class
// make sure we don't include our parent class in the array
if (ChildClass == ParentClass)
{
continue;
}
if (FComboActionGraphEditorUtilities::IsABlueprintClass(ChildClass))
{
// Blueprint
OutBlueprintClasses.Add(ChildClass);
}
else
{
// Native
OutNativeClasses.Add(ChildClass);
}
}
return OutNativeClasses.Num() > 0 || OutBlueprintClasses.Num() > 0;
}
static bool OpenEditorAndJumpToGraphNode(TWeakPtr<class FAssetEditor_ComboActionGraph> DialogueEditorPtr, const UEdGraphNode *GraphNode, bool bFocusIfOpen = false);
static class UComboActionGraph *GetActionFromGraphNode(const UEdGraphNode *GraphNode);
static IAssetEditorInstance *FindEditorForAsset(UObject *Asset, bool bFocusIfOpen);
};

View File

@ -0,0 +1,11 @@
// ©2023 Batty Bovine Productions, LLC. All Rights Reserved.
#pragma once
struct FAssetEditorTabs_ComboActionGraph
{
// Tab identifiers
static const FName ComboActionGraphPropertyID;
static const FName ViewportID;
static const FName SearchToolbarID;
};

View File

@ -0,0 +1,166 @@
// All rights reserved Dominik Pavlicek 2023
#include "Layout/ComboActionForceDirectedSolveLayoutStrategy.h"
#include "ComboActionGraph.h"
#include "Ed/EdComboActionGraph.h"
#include "Ed/EdComboActionGraphNode.h"
#include "Nodes/ComboActionGraphNode.h"
#include "Settings/ComboActionGraphEditorSettings.h"
UComboActionForceDirectedSolveLayoutStrategy::UComboActionForceDirectedSolveLayoutStrategy()
{
this->bRandomInit = false;
this->CoolDownRate = 10;
this->InitTemperature = 10.f;
}
static inline float CoolDown(float Temp, float CoolDownRate)
{
if (Temp < .01) return .01;
return Temp - (Temp / CoolDownRate);
}
static inline float GetAttractForce(float X, float K)
{
return (X * X) / K;
}
static inline float GetRepulseForce(float X, float k)
{
return X != 0 ? k * k / X : TNumericLimits<float>::Max();
}
void UComboActionForceDirectedSolveLayoutStrategy::Layout(UEdGraph *InEdGraph)
{
this->EdGraph = Cast<UEdComboActionGraph>(InEdGraph);
check(this->EdGraph != nullptr);
this->EdGraph->RebuildComboActionGraph();
this->Graph = this->EdGraph->GetComboActionGraph();
check(this->Graph != nullptr);
if (this->Settings != nullptr)
{
this->OptimalDistance = Settings->GetOptimalDistance();
this->MaxIteration = Settings->GetMaxIteration();
this->bRandomInit = Settings->IsRandomInit();
}
FBox2D PreTreeBound(ForceInitToZero);
for (int32 i = 0; i < Graph->RootNodes.Num(); ++i)
{
PreTreeBound = this->LayoutOneTree(Graph->RootNodes[i], PreTreeBound);
}
}
FBox2D UComboActionForceDirectedSolveLayoutStrategy::LayoutOneTree(UComboActionGraphNode *RootNode, const FBox2D &PreTreeBound)
{
float Temp = this->InitTemperature;
FBox2D TreeBound = GetActualBounds(RootNode);
TreeBound.Min.X += PreTreeBound.Max.X + this->OptimalDistance;
TreeBound.Max.X += PreTreeBound.Max.X + this->OptimalDistance;
if (this->bRandomInit)
{
this->RandomLayoutOneTree(RootNode, TreeBound);
}
float RepulseForce, AttractForce, Distance;
FVector2D Diff;
TMap<UEdGraphNode*, FVector2D> NodeToDisplacement;
for (const TObjectPtr<UEdGraphNode> &EdNode : this->EdGraph->Nodes)
{
NodeToDisplacement.Add(EdNode, FVector2D(0.0f, 0.0f));
}
for (int32 IterationNum = 0; IterationNum < this->MaxIteration; IterationNum++)
{
// Calculate the repulsive forces.
for (int32 i = 0; i < this->EdGraph->Nodes.Num(); i++)
{
for (int32 j = 0; j < this->EdGraph->Nodes.Num(); j++)
{
if (i == j)
{
continue;
}
Diff.X = this->EdGraph->Nodes[i]->NodePosX - this->EdGraph->Nodes[j]->NodePosX;
Diff.Y = this->EdGraph->Nodes[i]->NodePosY - this->EdGraph->Nodes[j]->NodePosY;
Distance = Diff.Size();
Diff.Normalize();
RepulseForce = Distance > 2 * this->OptimalDistance ? 0 : GetRepulseForce(Distance, this->OptimalDistance);
NodeToDisplacement[this->EdGraph->Nodes[i]] += Diff * RepulseForce;
}
}
// Calculate the attractive forces.
int Level = 0;
TArray<UComboActionGraphNode*> CurrLevelNodes = { RootNode };
TArray<UComboActionGraphNode*> NextLevelNodes;
while (CurrLevelNodes.Num() != 0)
{
for (int32 i = 0; i < CurrLevelNodes.Num(); i++)
{
UComboActionGraphNode *Node = CurrLevelNodes[i];
check(Node != nullptr);
UEdComboActionGraphNode *EdNode_ParentNode = this->EdGraph->NodeMap[Node];
for (int32 j = 0; j < Node->ChildNodes.Num(); j++)
{
NextLevelNodes.Add(Node->ChildNodes[j]);
UEdComboActionGraphNode *EdNode_ChildNode = this->EdGraph->NodeMap[Node->ChildNodes[j]];
Diff.X = EdNode_ChildNode->NodePosX - EdNode_ParentNode->NodePosY;
Diff.Y = EdNode_ChildNode->NodePosY - EdNode_ParentNode->NodePosY;
Distance = Diff.Size();
Diff.Normalize();
AttractForce = GetAttractForce(Distance, this->OptimalDistance);
NodeToDisplacement[EdNode_ParentNode] += Distance * Diff;
NodeToDisplacement[EdNode_ChildNode] -= Distance * Diff;
}
}
CurrLevelNodes = NextLevelNodes;
NextLevelNodes.Reset();
Level++;
}
for (UEdGraphNode *EdNode : this->EdGraph->Nodes)
{
Distance = NodeToDisplacement[EdNode].Size();
NodeToDisplacement[EdNode].Normalize();
float Minimum = Distance < Temp ? Distance : Temp;
EdNode->NodePosX += NodeToDisplacement[EdNode].X * Minimum;
EdNode->NodePosY += NodeToDisplacement[EdNode].Y * Minimum;
}
Temp = CoolDown(Temp, this->CoolDownRate);
}
FBox2D ActualBound = GetActualBounds(RootNode);
FVector2D Center = ActualBound.GetCenter();
FVector2D TreeCenter = TreeBound.GetCenter();
FVector2D Scale = (TreeBound.Max - TreeBound.Min) / (ActualBound.Max - ActualBound.Min);
for (UEdGraphNode *EdNode : EdGraph->Nodes)
{
EdNode->NodePosX = TreeCenter.X + Scale.X * (EdNode->NodePosX - Center.X);
EdNode->NodePosY = TreeCenter.Y + Scale.Y * (EdNode->NodePosY - Center.Y);
}
return TreeBound;
}

View File

@ -0,0 +1,30 @@
// All rights reserved Dominik Pavlicek 2023
#pragma once
#include "CoreMinimal.h"
#include "Layout/ComboActionGraphLayoutStrategy.h"
#include "UObject/Object.h"
#include "ComboActionForceDirectedSolveLayoutStrategy.generated.h"
/**
*
*/
UCLASS()
class COMBOINPUTEDITOR_API UComboActionForceDirectedSolveLayoutStrategy : public UComboActionGraphLayoutStrategy
{
GENERATED_BODY()
UComboActionForceDirectedSolveLayoutStrategy();
virtual void Layout(UEdGraph *EdGraph) override;
protected:
virtual FBox2D LayoutOneTree(UComboActionGraphNode* RootNode, const FBox2D &PreTreeBound);
protected:
bool bRandomInit;
float InitTemperature;
float CoolDownRate;
};

View File

@ -0,0 +1,95 @@
// ©2023 Batty Bovine Productions, LLC. All Rights Reserved.
#include "ComboActionGraphLayoutStrategy.h"
#include "Ed/EdComboActionGraph.h"
#include "Ed/EdComboActionGraphNode.h"
#include "Ed/SEdComboActionGraphNode.h"
#include "Nodes/ComboActionGraphNode.h"
#include "Kismet/KismetMathLibrary.h"
UComboActionGraphLayoutStrategy::UComboActionGraphLayoutStrategy()
{
Settings = nullptr;
MaxIteration = 50;
OptimalDistance = 150;
}
int32 UComboActionGraphLayoutStrategy::GetNodeWidth(UEdComboActionGraphNode *EdNode)
{
return EdNode->SEdNode->GetCachedGeometry().GetLocalSize().X;
}
int32 UComboActionGraphLayoutStrategy::GetNodeHeight(UEdComboActionGraphNode *EdNode)
{
return EdNode->SEdNode->GetCachedGeometry().GetLocalSize().Y;
}
FBox2D UComboActionGraphLayoutStrategy::GetNodeBound(UEdGraphNode* EdNode)
{
int32 NodeWidth = GetNodeWidth(Cast<UEdComboActionGraphNode>(EdNode));
int32 NodeHeight = GetNodeHeight(Cast<UEdComboActionGraphNode>(EdNode));
FVector2D Min(EdNode->NodePosX, EdNode->NodePosY);
FVector2D Max(EdNode->NodePosX + NodeWidth, EdNode->NodePosY + NodeHeight);
return FBox2D(Min, Max);
}
FBox2D UComboActionGraphLayoutStrategy::GetActualBounds(UComboActionGraphNode *RootNode)
{
int Level = 0;
TArray<UComboActionGraphNode*> CurrLevelNodes = { RootNode };
TArray<UComboActionGraphNode*> NextLevelNodes;
FBox2D Rtn = GetNodeBound(this->EdGraph->NodeMap[RootNode]);
while (CurrLevelNodes.Num() != 0)
{
for (const UComboActionGraphNode *Node : CurrLevelNodes)
{
check(Node != nullptr);
Rtn += GetNodeBound(this->EdGraph->NodeMap[Node]);
for (UComboActionGraphNode *ChildNode : Node->ChildNodes)
{
NextLevelNodes.Add(ChildNode);
}
}
CurrLevelNodes = NextLevelNodes;
NextLevelNodes.Reset();
++Level;
}
return Rtn;
}
void UComboActionGraphLayoutStrategy::RandomLayoutOneTree(UComboActionGraphNode *RootNode, const FBox2D &Bound)
{
int Level = 0;
TArray<UComboActionGraphNode*> CurrLevelNodes = { RootNode };
TArray<UComboActionGraphNode*> NextLevelNodes;
while (CurrLevelNodes.Num() != 0)
{
for (const UComboActionGraphNode *Node : CurrLevelNodes)
{
check(Node != nullptr);
UEdComboActionGraphNode *EdNode_Node = this->EdGraph->NodeMap[Node];
EdNode_Node->NodePosX = UKismetMathLibrary::RandomFloatInRange(Bound.Min.X, Bound.Max.X);
EdNode_Node->NodePosY = UKismetMathLibrary::RandomFloatInRange(Bound.Min.Y, Bound.Max.Y);
for (UComboActionGraphNode *ChildNode : Node->ChildNodes)
{
NextLevelNodes.Add(ChildNode);
}
}
CurrLevelNodes = NextLevelNodes;
NextLevelNodes.Reset();
++Level;
}
}

View File

@ -0,0 +1,42 @@
// ©2023 Batty Bovine Productions, LLC. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "UObject/Object.h"
#include "ComboActionGraphLayoutStrategy.generated.h"
/**
*
*/
UCLASS(Abstract)
class COMBOINPUTEDITOR_API UComboActionGraphLayoutStrategy : public UObject
{
GENERATED_BODY()
public:
UComboActionGraphLayoutStrategy();
virtual void Layout(UEdGraph *G) {};
UComboActionGraphEditorSettings *Settings;
protected:
int32 GetNodeWidth(UEdComboActionGraphNode *EdNode);
int32 GetNodeHeight(UEdComboActionGraphNode *EdNode);
FBox2D GetNodeBound(UEdGraphNode *EdNode);
FBox2D GetActualBounds(UComboActionGraphNode *RootNode);
virtual void RandomLayoutOneTree(UComboActionGraphNode *RootNode, const FBox2D &Bound);
protected:
UComboActionGraph *Graph;
UEdComboActionGraph *EdGraph;
int32 MaxIteration;
int32 OptimalDistance;
};

View File

@ -0,0 +1,241 @@
// ©2023 Batty Bovine Productions, LLC. All Rights Reserved.
#include "ComboActionTreeSolveLayoutStrategy.h"
#include "ComboActionGraph.h"
#include "Ed/EdComboActionGraph.h"
#include "Ed/EdComboActionGraphNode.h"
#include "Nodes/ComboActionGraphNode.h"
#include "Settings/ComboActionGraphEditorSettings.h"
void UComboActionTreeSolveLayoutStrategy::Layout(UEdGraph *InEdGraph)
{
this->EdGraph = Cast<UEdComboActionGraph>(InEdGraph);
check(this->EdGraph != nullptr);
this->EdGraph->RebuildComboActionGraph();
this->Graph = this->EdGraph->GetComboActionGraph();
check(this->Graph != nullptr);
bool bFirstPassOnly = false;
if (this->Settings == nullptr)
{
this->Settings = GetMutableDefault<UComboActionGraphEditorSettings>();
}
if (this->Settings != nullptr)
{
this->OptimalDistance = this->Settings->GetOptimalDistance();
this->MaxIteration = this->Settings->GetMaxIteration();
bFirstPassOnly = this->Settings->IsFirstPassOnly();
}
FVector2D Anchor(0.f, 0.f);
for (UComboActionGraphNode *RootNode : this->Graph->RootNodes)
{
this->InitPass(RootNode, Anchor);
if (!bFirstPassOnly)
{
for (int32 j = 0; j < this->MaxIteration; ++j)
{
bool HasConflict = this->ResolveConflictPass(RootNode);
if (!HasConflict)
{
break;
}
}
}
}
for (int32 i = 0; i < Graph->RootNodes.Num(); i++)
{
for (int32 j = 0; j < i; j++)
{
this->ResolveConflict(Graph->RootNodes[j], Graph->RootNodes[i]);
}
}
}
void UComboActionTreeSolveLayoutStrategy::InitPass(UComboActionGraphNode *RootNode, const FVector2D &Anchor)
{
UEdComboActionGraphNode *EdNode_RootNode = this->EdGraph->NodeMap[RootNode];
FVector2D ChildAnchor(FVector2D(0.0f, this->GetNodeHeight(EdNode_RootNode) + this->OptimalDistance + Anchor.Y));
for (int32 i = 0; i < RootNode->ChildNodes.Num(); i++)
{
UComboActionGraphNode *Child = RootNode->ChildNodes[i];
UEdComboActionGraphNode *EdNode_ChildNode = this->EdGraph->NodeMap[Child];
if (i > 0)
{
UComboActionGraphNode *PreChild = RootNode->ChildNodes[i - 1];
UEdComboActionGraphNode *EdNode_PreChildNode = this->EdGraph->NodeMap[PreChild];
ChildAnchor.X += this->OptimalDistance + this->GetNodeWidth(EdNode_PreChildNode) / 2;
}
ChildAnchor.X += this->GetNodeWidth(EdNode_ChildNode) / 2;
this->InitPass(Child, ChildAnchor);
}
float NodeWidth = this->GetNodeWidth(EdNode_RootNode);
EdNode_RootNode->NodePosY = Anchor.Y;
if (RootNode->ChildNodes.Num() == 0)
{
EdNode_RootNode->NodePosX = Anchor.X - NodeWidth / 2;
}
else
{
this->UpdateParentNodePosition(RootNode);
}
}
bool UComboActionTreeSolveLayoutStrategy::ResolveConflictPass(UComboActionGraphNode *Node)
{
bool HasConflict = false;
for (int32 i = 0; i < Node->ChildNodes.Num(); ++i)
{
UComboActionGraphNode *Child = Node->ChildNodes[i];
if (this->ResolveConflictPass(Child))
{
HasConflict = true;
}
}
for (const UComboActionGraphNode *ParentNode : Node->ParentNodes)
{
for (UComboActionGraphNode *LeftSibling : ParentNode->ChildNodes)
{
if (LeftSibling == Node)
{
break;
}
if (this->ResolveConflict(LeftSibling, Node))
{
HasConflict = true;
}
}
}
return HasConflict;
}
bool UComboActionTreeSolveLayoutStrategy::ResolveConflict(UComboActionGraphNode *LRoot, UComboActionGraphNode *RRoot)
{
TArray<UEdComboActionGraphNode*> RightContour, LeftContour;
this->GetRightContour(LRoot, 0, RightContour);
this->GetLeftContour(RRoot, 0, LeftContour);
int32 MaxOverlapDistance = 0;
int32 Num = FMath::Min(LeftContour.Num(), RightContour.Num());
for (int32 i = 0; i < Num; ++i)
{
if (RightContour.Contains(LeftContour[i]) || LeftContour.Contains(RightContour[i]))
break;
int32 RightBound = RightContour[i]->NodePosX + this->GetNodeWidth(RightContour[i]);
int32 LeftBound = LeftContour[i]->NodePosX;
int32 Distance = RightBound + OptimalDistance - LeftBound;
if (Distance > MaxOverlapDistance)
{
MaxOverlapDistance = Distance;
}
}
if (MaxOverlapDistance > 0)
{
this->ShiftSubTree(RRoot, FVector2D(MaxOverlapDistance, 0.f));
TArray<UComboActionGraphNode*> ParentNodes = RRoot->ParentNodes;
TArray<UComboActionGraphNode*> NextParentNodes;
while (ParentNodes.Num() != 0)
{
for (int32 i = 0; i < ParentNodes.Num(); ++i)
{
UpdateParentNodePosition(ParentNodes[i]);
NextParentNodes.Append(ParentNodes[i]->ParentNodes);
}
ParentNodes = NextParentNodes;
NextParentNodes.Reset();
}
return true;
}
return false;
}
void UComboActionTreeSolveLayoutStrategy::GetLeftContour(UComboActionGraphNode *RootNode, int32 Level, TArray<UEdComboActionGraphNode*> &Contour)
{
UEdComboActionGraphNode *EdNode_Node = this->EdGraph->NodeMap[RootNode];
if (Level >= Contour.Num())
{
Contour.Add(EdNode_Node);
}
else if (EdNode_Node->NodePosX < Contour[Level]->NodePosX)
{
Contour[Level] = EdNode_Node;
}
for (UComboActionGraphNode *Child : RootNode->ChildNodes)
{
this->GetLeftContour(Child, Level + 1, Contour);
}
}
void UComboActionTreeSolveLayoutStrategy::GetRightContour(UComboActionGraphNode *RootNode, int32 Level, TArray<UEdComboActionGraphNode*> &Contour)
{
UEdComboActionGraphNode *EdNode_Node = this->EdGraph->NodeMap[RootNode];
if (Level >= Contour.Num())
{
Contour.Add(EdNode_Node);
}
else if (EdNode_Node->NodePosX + this->GetNodeWidth(EdNode_Node) > Contour[Level]->NodePosX + this->GetNodeWidth(Contour[Level]))
{
Contour[Level] = EdNode_Node;
}
for (UComboActionGraphNode *Child : RootNode->ChildNodes)
{
this->GetRightContour(Child, Level + 1, Contour);
}
}
void UComboActionTreeSolveLayoutStrategy::ShiftSubTree(UComboActionGraphNode *RootNode, const FVector2D &Offset)
{
UEdComboActionGraphNode *EdNode_Node = this->EdGraph->NodeMap[RootNode];
EdNode_Node->NodePosX += Offset.X;
EdNode_Node->NodePosY += Offset.Y;
for (UComboActionGraphNode *Child : RootNode->ChildNodes)
{
if (Child->ParentNodes[0] == RootNode)
{
this->ShiftSubTree(Child, Offset);
}
}
}
void UComboActionTreeSolveLayoutStrategy::UpdateParentNodePosition(UComboActionGraphNode *RootNode)
{
UEdComboActionGraphNode *EdNode_ParentNode = this->EdGraph->NodeMap[RootNode];
if (RootNode->ChildNodes.Num() % 2 == 0)
{
UEdComboActionGraphNode *FirstChild = this->EdGraph->NodeMap[RootNode->ChildNodes[0]];
UEdComboActionGraphNode *LastChild = this->EdGraph->NodeMap[RootNode->ChildNodes.Last()];
float LeftBound = FirstChild->NodePosX;
float RightBound = LastChild->NodePosX + this->GetNodeWidth(LastChild);
EdNode_ParentNode->NodePosX = (LeftBound + RightBound) / 2 - this->GetNodeWidth(EdNode_ParentNode) / 2;
}
else
{
UEdComboActionGraphNode *MidChild = this->EdGraph->NodeMap[RootNode->ChildNodes[RootNode->ChildNodes.Num() / 2]];
EdNode_ParentNode->NodePosX = MidChild->NodePosX + this->GetNodeWidth(MidChild) / 2 - this->GetNodeWidth(EdNode_ParentNode) / 2;
}
}

Some files were not shown because too many files have changed in this diff Show More