Trying to add a combo graph editor.

This commit is contained in:
Jamie Greunbaum 2023-09-27 00:21:59 -04:00
parent bf43acf0dc
commit ea4dd54ec9
43 changed files with 6809 additions and 1 deletions

View File

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

View File

@ -0,0 +1,315 @@
// ©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 = UComboActionGraph::StaticClass();
this->EdgeType = UComboActionGraph::StaticClass();
this->bEdgeEnabled = false;
this->GraphGUID = FGuid::NewGuid();
#if WITH_EDITORONLY_DATA
this->EdGraph = nullptr;
this->bCanRenameNode = true;
#endif
}
TArray<FComboActionDecorator> UComboActionGraph::GetGraphDecorators() const
{
TArray<FComboActionDecorator> TempReturn;
TArray<FComboActionDecorator> Return;
for (const FComboActionDecorator &Itr : this->GraphDecorators)
{
if (Itr.DecoratorType != nullptr)
{
TempReturn.AddUnique(Itr);
}
}
/* TODO: Cleanup duplicates
for (auto Itr : TempReturn)
{
}
*/
Return = TempReturn;
return Return;
}
TArray<FComboActionDecorator> UComboActionGraph::GetAllDecorators() const
{
TArray<FComboActionDecorator> TempReturn;
TArray<FComboActionDecorator> Return;
for (const UComboActionGraphNode *Itr : this->AllNodes)
{
if (Itr && Itr->GetNodeDecorators().Num() > 0)
{
TempReturn.Append(Itr->NodeDecorators);
}
}
TempReturn.Append(this->GetGraphDecorators());
return TempReturn;
}
bool UComboActionGraph::CanStartDialogueGraph() const
{
bool bSatisfied = true;
if (this->AllNodes.Num() == 0)
{
return false;
}
for (const auto& Itr : this->AllNodes)
{
if (!Itr)
{
return false;
}
if (Itr->ValidateNodeRuntime() == false)
{
return false;
}
}
const TArray<FComboActionDecorator> &Decorators = this->GetAllDecorators();
if (Decorators.Num() == 0)
{
return bSatisfied;
}
TArray<FText> DecoratorValidations;
for (const FComboActionDecorator &Itr : Decorators)
{
if (Itr.ValidateDecorator(DecoratorValidations) == false) bSatisfied = false;
}
if (DecoratorValidations.Num() > 0)
{
for(auto Itr : DecoratorValidations)
{
UE_LOG(LogComboActionGraph, Error, TEXT("%s"), *Itr.ToString());
}
}
return bSatisfied;
}
void UComboActionGraph::CreateGraph()
{
#if WITH_EDITOR
// We already have existing Graph
if (this->EdGraph != nullptr)
{
return;
}
// We already have existing Start Node
if (this->StartNode != nullptr)
{
return;
}
this->StartNode = ConstructDialogueNode<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->ChildrenNodes.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;
// GRAPH DECORATORS VALIDATION
{
TArray<UComboActionDecoratorBase*> UsedNodeDecorators;
for (int i = 0; i < this->GraphDecorators.Num(); i++)
{
const FComboActionDecorator &Decorator = this->GraphDecorators[i];
if (Decorator.DecoratorType)
{
UsedNodeDecorators.Add(Decorator.DecoratorType);
}
else
{
const FString RichTextReturn =
FString("* ")
.Append( TEXT("<RichTextBlock.Bold>Dialogue Graph</>"))
.Append(": has ")
.Append(TEXT("<RichTextBlock.Bold>invalid</> Node Decorator at Index: "))
.Append(FString::FromInt(i))
.Append(".");
const FString TextReturn =
this->GetName()
.Append(": has ")
.Append(TEXT("INVALID Node Decorator at Index: "))
.Append(FString::FromInt(i))
.Append(".");
ValidationErrors.Add(FText::FromString(RichTextFormat ? RichTextReturn : TextReturn));
bReturnValue = false;
}
}
TMap<UClass*, int32> DuplicatedDecoratorsMap;
for (const auto& Itr : UsedNodeDecorators)
{
int32 ClassAppearance = 1;
for (const auto& Itr2 : UsedNodeDecorators)
{
if (Itr != Itr2 && Itr->GetClass() == Itr2->GetClass())
{
auto A = Itr->GetClass()->GetName();
ClassAppearance++;
}
}
if (ClassAppearance > 1 && DuplicatedDecoratorsMap.Contains(Itr->GetClass()) == false)
{
DuplicatedDecoratorsMap.Add(Itr->GetClass(), ClassAppearance);
}
}
if (DuplicatedDecoratorsMap.Num() > 0)
{
for (const TTuple<UClass*, int32> &Itr : DuplicatedDecoratorsMap)
{
bReturnValue = false;
const FString RichTextReturn =
FString("* ")
.Append(TEXT("<RichTextBlock.Bold>Dialogue Graph</>"))
.Append(": has Node Decorator ")
.Append("<RichTextBlock.Bold>")
.Append(Itr.Key->GetName().LeftChop(2))
.Append("</> ")
.Append(FString::FromInt(Itr.Value))
.Append("x times! Please, avoid duplicates!");
const FString TextReturn =
FString(TEXT("Dialogue Graph: has Node Decorator "))
.Append( Itr.Key->GetName().LeftChop(2))
.Append(" ")
.Append(FString::FromInt(Itr.Value))
.Append("x times! Please, avoid duplicates!");
ValidationErrors.Add(FText::FromString(RichTextFormat ? RichTextReturn : TextReturn));
}
}
}
// GRAPH DECORATORS VALIDATION
for (const FComboActionDecorator &Itr : this->GraphDecorators)
{
TArray<FText> DecoratorErrors;
if (!Itr.ValidateDecorator(DecoratorErrors))
{
for (auto Error : DecoratorErrors)
{
const FString ErrorTextRich =
FString("* ")
.Append(TEXT("<RichTextBlock.Bold>Dialogue Graph</>: "))
.Append(FString(Error.ToString()));
const FString ErrorTextSimple =
this->GetName()
.Append(": ")
.Append(FString(Error.ToString()));
ValidationErrors.Add(FText::FromString(RichTextFormat ? ErrorTextRich : ErrorTextSimple));
bReturnValue = false;
}
}
}
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;
}
#endif
#undef LOCTEXT_NAMESPACE

View File

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

View File

@ -0,0 +1,222 @@
// ©2023 Batty Bovine Productions, LLC. All Rights Reserved.
#include "Data/ComboActionContext.h"
#include "Interfaces/ComboActionInterface.h"
bool UComboActionContext::IsValid() const
{
return ActiveNode != nullptr;// && DialogueParticipant.GetInterface() != nullptr && PlayerDialogueParticipant.GetInterface() != nullptr;
}
void UComboActionContext::SetComboActionContext(UComboActionGraphNode *NewActiveNode, const TArray<UComboActionGraphNode*> NewAllowedChildNodes)
{
//DialogueParticipant = NewParticipant;
//ActiveNode = NewActiveNode;
//AllowedChildNodes = NewAllowedChildNodes;
//if (!DialogueParticipants.Contains(NewParticipant))
//{
// DialogueParticipants.Add(NewParticipant);
//}
}
//void UComboActionContext::UpdateDialogueParticipant(const TScriptInterface<IComboActionParticipantInterface> NewParticipant)
//{
// DialogueParticipant = NewParticipant;
//
// AddDialogueParticipant(NewParticipant);
//}
//
//void UComboActionContext::UpdateActiveDialogueNode(UComboActionGraphNode *NewActiveNode)
//{
// ActiveNode = NewActiveNode;
//}
//
//void UComboActionContext::UpdateAllowedChildrenNodes(const TArray<UComboActionGraphNode*> &NewNodes)
//{
// AllowedChildNodes = NewNodes;
//}
//
//void UComboActionContext::UpdateActiveDialogueRowDataIndex(const int32 NewIndex)
//{
// ActiveDialogueRowDataIndex = NewIndex;
//}
//
//void UComboActionContext::UpdateDialoguePlayerParticipant(const TScriptInterface<IComboActionParticipantInterface> NewParticipant)
//{
// PlayerDialogueParticipant = NewParticipant;
//
// AddDialogueParticipant(NewParticipant);
//}
//
//void UComboActionContext::UpdateActiveDialogueParticipant(const TScriptInterface<IComboActionParticipantInterface> NewParticipant)
//{
// if (NewParticipant != PlayerDialogueParticipant && NewParticipant != DialogueParticipant)
// {
// //TODO: Properly log this
// return;
// }
//
// ActiveDialogueParticipant = NewParticipant;
//}
//
//void UComboActionContext::AddTraversedNode(const UComboActionGraphNode *TraversedNode)
//{
// if (!TraversedNode) return;
//
// // If we have already passed over this Node, then just increase the counter
// if (TraversedPath.Contains(TraversedNode->GetNodeGUID()))
// {
// TraversedPath[TraversedNode->GetNodeGUID()]++;
// }
// else
// {
// TraversedPath.Add(TraversedNode->GetNodeGUID(), 1);
// }
//}
//
//bool UComboActionContext::AddDialogueParticipants(const TArray<TScriptInterface<IComboActionParticipantInterface>>& NewParticipants)
//{
// bool bSatisfied = true;
// for (const auto& Itr : NewParticipants)
// {
// const bool bTempSatisfied = AddDialogueParticipant(Itr);
// bSatisfied = bTempSatisfied ? bSatisfied : bTempSatisfied;
// }
//
// return bSatisfied;
//}
//
//bool UComboActionContext::AddDialogueParticipant(const TScriptInterface<IComboActionParticipantInterface>& NewParticipant)
//{
// if (DialogueParticipants.Contains(NewParticipant))
// {
// return false;
// }
//
// DialogueParticipants.Add(NewParticipant);
// return true;
//}
//
//bool UComboActionContext::RemoveDialogueParticipants(const TArray<TScriptInterface<IComboActionParticipantInterface>>& NewParticipants)
//{
// bool bSatisfied = true;
// for (const auto& Itr : NewParticipants)
// {
// const bool bTempSatisfied = RemoveDialogueParticipant(Itr);
// bSatisfied = bTempSatisfied ? bSatisfied : bTempSatisfied;
// }
//
// return bSatisfied;
//}
//
//bool UComboActionContext::RemoveDialogueParticipant(const TScriptInterface<IComboActionParticipantInterface>& NewParticipant)
//{
// if (DialogueParticipants.Contains(NewParticipant))
// {
// DialogueParticipants.Remove(NewParticipant);
// return true;
// }
//
// return false;
//}
//
//void UComboActionContext::ClearDialogueParticipants()
//{
// DialogueParticipants.Empty();
//}
void UComboActionContext::SetComboActionContextBP(UComboActionGraphNode *NewActiveNode,TArray<UComboActionGraphNode*> NewAllowedChildNodes)
{
//this->SetComboActionContext(NewParticipant, NewActiveNode, NewAllowedChildNodes);
ComboActionContextUpdatedFromBlueprint.Broadcast(this);
}
//void UComboActionContext::UpdateDialogueParticipantBP(const TScriptInterface<IComboActionParticipantInterface> NewParticipant)
//{
// UpdateDialogueParticipant(NewParticipant);
//
// DialogueContextUpdatedFromBlueprint.Broadcast(this);
//}
//
//void UComboActionContext::UpdateActiveDialogueNodeBP(UComboActionGraphNode *NewActiveNode)
//{
// UpdateActiveDialogueNode(NewActiveNode);
//
// DialogueContextUpdatedFromBlueprint.Broadcast(this);
//}
//
//void UComboActionContext::UpdateActiveDialogueRowBP(const FDialogueRow &NewActiveRow)
//{
// UpdateActiveDialogueRow(NewActiveRow);
//
// DialogueContextUpdatedFromBlueprint.Broadcast(this);
//}
//
//void UComboActionContext::UpdateActiveDialogueRowDataIndexBP(const int32 NewIndex)
//{
// UpdateActiveDialogueRowDataIndex(NewIndex);
//
// DialogueContextUpdatedFromBlueprint.Broadcast(this);
//}
//
//void UComboActionContext::UpdateDialoguePlayerParticipantBP(const TScriptInterface<IComboActionParticipantInterface> NewParticipant)
//{
// UpdateDialoguePlayerParticipant(NewParticipant);
//
// DialogueContextUpdatedFromBlueprint.Broadcast(this);
//}
//
//void UComboActionContext::UpdateActiveDialogueParticipantBP(const TScriptInterface<IComboActionParticipantInterface> NewParticipant)
//{
// UpdateActiveDialogueParticipant(NewParticipant);
//
// DialogueContextUpdatedFromBlueprint.Broadcast(this);
//}
//
//bool UComboActionContext::AddDialogueParticipantBP(const TScriptInterface<IComboActionParticipantInterface> &NewParticipant)
//{
// if (AddDialogueParticipant(NewParticipant))
// {
// DialogueContextUpdatedFromBlueprint.Broadcast(this);
// return true;
// }
//
// return false;
//}
//
//bool UComboActionContext::RemoveDialogueParticipantBP(const TScriptInterface<IComboActionParticipantInterface> &NewParticipant)
//{
// if (RemoveDialogueParticipant(NewParticipant))
// {
// DialogueContextUpdatedFromBlueprint.Broadcast(this);
// return true;
// }
//
// return false;
//}
//
//bool UComboActionContext::AddDialogueParticipantsBP(const TArray<TScriptInterface<IComboActionParticipantInterface>> &NewParticipants)
//{
// if (AddDialogueParticipants(NewParticipants))
// {
// DialogueContextUpdatedFromBlueprint.Broadcast(this);
// return true;
// }
//
// return false;
//}
//
//bool UComboActionContext::RemoveDialogueParticipantsBP(const TArray<TScriptInterface<IComboActionParticipantInterface>> &NewParticipants)
//{
// if (RemoveDialogueParticipants(NewParticipants))
// {
// DialogueContextUpdatedFromBlueprint.Broadcast(this);
// return true;
// }
//
// return false;
//}

View File

@ -0,0 +1,55 @@
// ©2023 Batty Bovine Productions, LLC. All Rights Reserved.
#include "Decorators/ComboActionDecoratorBase.h"
#if WITH_EDITOR
#include "Editor.h"
#endif
DEFINE_LOG_CATEGORY(LogComboActionDecoratorBase);
#define LOCTEXT_NAMESPACE "ComboActionDecoratorBase"
bool UComboActionDecoratorBase::ValidateDecorator_Implementation(TArray<FText>& ValidationMessages)
{
bool bSatisfied = true;
const FText Name = FText::FromString(GetName());
// This is to ensure we are not throwing InvalidWorld errors in Editor with no Gameplay.
bool bIsEditorCall = false;
#if WITH_EDITOR
if (GEditor != nullptr)
{
bIsEditorCall = !GEditor->GetPlayInEditorSessionInfo().IsSet();
}
#endif
if (GetOwningWorld() == nullptr && bIsEditorCall == false)
{
const FText TempText = FText::Format(LOCTEXT("ComboActionDecorator_Base_Validation_World", "[{0}]: No valid World!"), Name);
ValidationMessages.Add(TempText);
bSatisfied = false;
}
if (DecoratorState == EComboActionDecoratorState::Uninitialized && bIsEditorCall == false)
{
const FText TempText = FText::Format(LOCTEXT("ComboActionDecorator_Base_Validation_State", "[{0}]: Not Initialized properly!"), Name);
ValidationMessages.Add(TempText);
bSatisfied = false;
}
if (GetOwner() == nullptr)
{
const FText TempText = FText::Format(LOCTEXT("ComboActionDecorator_Base_Validation_Owner", "[{0}]: No valid Owner!"), Name);
ValidationMessages.Add(TempText);
bSatisfied = false;
}
return bSatisfied;
}
#undef LOCTEXT_NAMESPACE

View File

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

View File

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

View File

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

View File

@ -0,0 +1,396 @@
// ©2023 Batty Bovine Productions, LLC. All Rights Reserved.
#include "Nodes/ComboActionGraphNode.h"
#include "ComboActionGraph.h"
#include "Data/ComboActionContext.h"
#include "Decorators/ComboActionDecoratorBase.h"
#include "Interfaces/ComboActionGraphManagerInterface.h"
DEFINE_LOG_CATEGORY(LogComboActionGraphNode);
#define LOCTEXT_NAMESPACE "ComboActionGraphNode"
UComboActionGraphNode::UComboActionGraphNode()
{
NodeGUID = FGuid::NewGuid();
bInheritGraphDecorators = true;
#if WITH_EDITORONLY_DATA
CompatibleGraphType = UComboActionGraph::StaticClass();
BackgroundColor = FLinearColor::Black;
bAllowInputNodes = true;
bAllowOutputNodes = true;
bAllowCopy = true;
bAllowCut = true;
bAllowDelete = true;
bAllowPaste = true;
bAllowManualCreate = true;
NodeTypeName = LOCTEXT("ComboActionGraphNode_InternalName", "ComboActionGraphNode");
NodeTooltipText = LOCTEXT("ComboActionGraphNode_Tooltip", "Combo action graph base node. Child nodes provide more information.");
#endif
bAutoStarts = false;
}
void UComboActionGraphNode::SetNewWorld(UWorld *NewWorld)
{
if (!NewWorld) return;
if (NewWorld == this->OwningWorld) return;
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()
{
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;
}
//UComboActionContext *Context = Manager->GetDialogueContext();
//if (!Context || !UComboActionSystemBFC::IsContextValid(Context))
//{
// Manager->GetDialogueFailedEventHandle().Broadcast(TEXT("[ProcessNode] Invalid Dialogue Context!!"));
// return;
//}
//
//UComboActionSystemBFC::ExecuteDecorators(this, Context);
//
//Manager->GetDialogueNodeStartedEventHandle().Broadcast(Context);
}
TArray<FComboActionDecorator> UComboActionGraphNode::GetNodeDecorators() const
{
TArray<FComboActionDecorator> TempReturn;
TArray<FComboActionDecorator> Return;
for (auto Itr : NodeDecorators)
{
if (Itr.DecoratorType != nullptr)
{
TempReturn.AddUnique(Itr);
}
}
/* TODO: Cleanup duplicates
for (auto Itr : TempReturn)
{
}
*/
Return = TempReturn;
return Return;
}
bool UComboActionGraphNode::CanStartNode() const
{
return this->EvaluateDecorators();
}
bool UComboActionGraphNode::EvaluateDecorators() const
{
if (this->GetGraph() == nullptr)
{
UE_LOG(LogComboActionGraphNode, Error, TEXT("[EvaluateDecorators] Graph is null (invalid)!"))
return false;
}
bool bSatisfied = true;
TArray<FComboActionDecorator> AllDecorators;
if (this->bInheritGraphDecorators)
{
// Add those Decorators rather than asking Graph to evaluate, because Nodes might introduce specific context
AllDecorators.Append(GetGraph()->GetGraphDecorators());
}
AllDecorators.Append(GetNodeDecorators());
if (AllDecorators.Num() == 0) return bSatisfied;
for (auto Itr : AllDecorators)
{
if (Itr.EvaluateDecorator() == false) bSatisfied = false;
}
return bSatisfied;
}
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->ChildrenNodes.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->ChildrenNodes.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));
}
// DECORATORS VALIDATION
{
TArray<UComboActionDecoratorBase*> UsedNodeDecorators;
for (int i = 0; i < this->NodeDecorators.Num(); i++)
{
if (this->NodeDecorators.IsValidIndex(i) && this->NodeDecorators[i].DecoratorType && !UsedNodeDecorators.Contains(this->NodeDecorators[i].DecoratorType))
{
UsedNodeDecorators.Add(NodeDecorators[i].DecoratorType);
}
else
{
const FString RichTextReturn =
FString("* ")
.Append( TEXT("<RichTextBlock.Bold>"))
.Append(GetNodeTitle().ToString())
.Append(TEXT("</>"))
.Append(": has ")
.Append(TEXT("<RichTextBlock.Bold>invalid</> Node Decorator at Index: "))
.Append(FString::FromInt(i))
.Append(".");
FString TextReturn = FString(GetNodeTitle().ToString())
.Append(": has ")
.Append(TEXT("INVALID Node Decorator at Index: "))
.Append(FString::FromInt(i))
.Append(".");
ValidationsMessages.Add(FText::FromString(RichFormat ? RichTextReturn : TextReturn));
bResult = false;
}
}
TMap<UClass*, int32> DuplicatedDecoratorsMap;
for (const UComboActionDecoratorBase *Itr : UsedNodeDecorators)
{
int32 ClassAppearance = 1;
for (const UComboActionDecoratorBase *Itr2 : UsedNodeDecorators)
{
if (Itr != Itr2 && Itr->GetClass() == Itr2->GetClass())
{
auto A = Itr->GetClass()->GetName();
ClassAppearance++;
}
}
if (ClassAppearance > 1 && DuplicatedDecoratorsMap.Contains(Itr->GetClass()) == false)
{
DuplicatedDecoratorsMap.Add(Itr->GetClass(), ClassAppearance);
}
}
if (DuplicatedDecoratorsMap.Num() > 0)
{
for (const TTuple<UClass*, int32> &Itr : DuplicatedDecoratorsMap)
{
bResult = false;
const FString RichTextReturn =
FString("* ")
.Append("<RichTextBlock.Bold>")
.Append(this->NodeTitle.ToString())
.Append("</>")
.Append(": has Node Decorator ")
.Append("<RichTextBlock.Bold>")
.Append(Itr.Key->GetName().LeftChop(2))
.Append("</> ")
.Append(FString::FromInt(Itr.Value))
.Append("x times! Please, avoid duplicates!");
const FString TextReturn =
FString(this->NodeTitle.ToString())
.Append(this->NodeTitle.ToString())
.Append(": has Node Decorator ")
.Append(Itr.Key->GetName().LeftChop(2))
.Append(" ")
.Append(FString::FromInt(Itr.Value))
.Append("x times! Please, avoid duplicates!");
ValidationsMessages.Add(FText::FromString(RichFormat ? RichTextReturn : TextReturn));
}
}
for (const FComboActionDecorator &Itr : this->GetNodeDecorators())
{
TArray<FText> DecoratorErrors;
if (!Itr.ValidateDecorator(DecoratorErrors))
{
for (auto Error : DecoratorErrors)
{
const FString ErrorTextRich =
FString("* ")
.Append("<RichTextBlock.Bold>")
.Append(this->NodeTitle.ToString())
.Append("</>: ")
.Append(FString(Error.ToString()));
const FString ErrorTextSimple =
FString(this->GetClass()->GetDisplayNameText().ToString())
.Append(": ")
.Append(FString(Error.ToString()));
ValidationsMessages.Add(FText::FromString(RichFormat ? ErrorTextRich : ErrorTextSimple));
bResult = false;
}
}
}
}
return bResult;
}
void UComboActionGraphNode::OnPasted()
{
NodeGUID = FGuid::NewGuid();
ParentNodes.Empty();
ChildrenNodes.Empty();
Edges.Empty();
}
FText UComboActionGraphNode::GetDefaultTooltipBody() const
{
const FText InheritsValue = this->bInheritGraphDecorators ? LOCTEXT("True","Yes") : LOCTEXT("False","No");
const FText Inherits = FText::Format(LOCTEXT("UComboActionGraphNode_InheritsTooltip", "Inherits Graph Decorators: {0}"), InheritsValue);
FText ImplementsNumber;
if (this->NodeDecorators.Num() == 0)
{
ImplementsNumber = LOCTEXT("None", "-");
}
else
{
ImplementsNumber = FText::FromString(FString::FromInt(this->NodeDecorators.Num()));
}
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, Inherits, Implements);
}
#endif
#undef LOCTEXT_NAMESPACE

View File

@ -0,0 +1,268 @@
// ©2023 Batty Bovine Productions, LLC. All Rights Reserved.
#include "Nodes/ComboActionGraphNode_ActionNodeBase.h"
//#include "Helpers/ComboActionSystemBFC.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->BackgroundColor = FLinearColor(FColor::Orange);
this->NodeTooltipText = LOCTEXT("ComboActionGraphNode_BaseTooltip", "* Abstract class, should not appear in graph editor.\n* Enhances 'ComboActionGraphNode' Base class with action data.");
#endif
this->bAutoStarts = false;
this->bUseGameplayTags = true;
}
void UComboActionGraphNode_ActionNodeBase::ProcessNode()
{
//if (Manager)
//{
// if (UComboActionContext *Context = Manager->GetDialogueContext())
// {
// this->GetWorld()->GetTimerManager().ClearTimer(Manager->GetDialogueRowTimerHandle());
// const FDialogueRow DialogueRow = UComboActionSystemBFC::GetDialogueRow(Context->ActiveNode);
// if (UComboActionSystemBFC::IsDialogueRowValid(DialogueRow) && DialogueRow.DialogueRowData.Array().IsValidIndex(Context->GetActiveDialogueRowDataIndex()))
// {
// Context->UpdateActiveDialogueRow(DialogueRow);
// Context->UpdateActiveDialogueRowDataIndex(Context->ActiveDialogueRowDataIndex);
// Manager->GetDialogueContextUpdatedEventHande().Broadcast(Context);
// }
// }
//}
Super::ProcessNode();
}
void UComboActionGraphNode_ActionNodeBase::PreProcessNode()
{
//if (bUseGameplayTags)
//{
// // Switch Participants based on Tags
// if (Manager.GetInterface())
// {
// if (const auto TempContext = Manager->GetDialogueContext())
// {
// const TScriptInterface<IComboActionParticipantInterface> BestMatchingParticipant = UComboActionSystemBFC::FindBestMatchingParticipant(Manager.GetObject(), TempContext);
//
// TempContext->UpdateActiveDialogueParticipant(BestMatchingParticipant);
// }
// }
//}
Super::PreProcessNode();
}
UDataTable *UComboActionGraphNode_ActionNodeBase::GetDataTable() const
{
return DataTable;
}
bool UComboActionGraphNode_ActionNodeBase::ValidateNodeRuntime_Implementation() const
{
//if (DataTable == nullptr)
//{
// return false;
//}
//if (RowName.IsNone())
//{
// return false;
//}
//if (MaxChildrenNodes > -1 && ChildrenNodes.Num() > MaxChildrenNodes)
//{
// return false;
//}
//const FString Context;
//const FDialogueRow* SelectedRow = DataTable->FindRow<FDialogueRow>(RowName, Context);
//if (SelectedRow == nullptr)
//{
// return false;
//}
//if (SelectedRow)
//{
// if (SelectedRow->DialogueRowData.Num() == 0)
// {
// return false;
// }
//}
return true;
}
#if WITH_EDITOR
bool UComboActionGraphNode_ActionNodeBase::ValidateNode(TArray<FText>& ValidationsMessages, const bool RichFormat)
{
bool bResult = Super::ValidateNode(ValidationsMessages, RichFormat);
//if (DataTable == nullptr)
//{
// bResult = false;
// const FString RichTextReturn =
// FString("* ").
// Append("<RichTextBlock.Bold>").
// Append(NodeTitle.ToString()).
// Append("</>").
// Append(": Does not contain any Dialogue Data Table!");
// const FString TextReturn =
// FString(NodeTitle.ToString()).
// Append(": Does not contain any Dialogue Data Table!");
//
// ValidationsMessages.Add(FText::FromString(RichFormat ? RichTextReturn : TextReturn));
//}
//if (RowName.IsNone())
//{
// bResult = false;
// const FString RichTextReturn =
// FString("* ").
// Append("<RichTextBlock.Bold>").
// Append(NodeTitle.ToString()).
// Append("</>").
// Append(": Does not contain valid Dialogue Row!");
// const FString TextReturn =
// FString(NodeTitle.ToString()).
// Append(": Does not contain valid Dialogue Row!");
//
// ValidationsMessages.Add(FText::FromString(RichFormat ? RichTextReturn : TextReturn));
//}
//if (MaxChildrenNodes > -1 && ChildrenNodes.Num() > MaxChildrenNodes)
//{
// const FString RichTextReturn =
// FString("* ").
// Append("<RichTextBlock.Bold>").
// Append(NodeTitle.ToString()).
// Append("</>").
// Append(": Has more than ").
// Append("<RichTextBlock.Bold>").
// Append(FString::FromInt(MaxChildrenNodes)).
// Append("</>").
// Append(" Children Nodes!");
// const FString TextReturn =
// FString(NodeTitle.ToString()).
// Append(": Has more than ").Append(FString::FromInt(MaxChildrenNodes)).Append(" Children Nodes!");
//
// ValidationsMessages.Add(FText::FromString(RichFormat ? RichTextReturn : TextReturn));
//}
//const FString Context;
//const FDialogueRow* SelectedRow = DataTable!=nullptr ? DataTable->FindRow<FDialogueRow>(RowName, Context) : nullptr;
//if (SelectedRow == nullptr)
//{
// bResult = false;
// const FString RichTextReturn =
// FString("* ").
// Append("<RichTextBlock.Bold>").
// Append(NodeTitle.ToString()).
// Append("</>").
// Append(": Invalid Selected Row!");
// const FString TextReturn =
// FString(NodeTitle.ToString()).
// Append(": Invalid Selected Row!");
//
// ValidationsMessages.Add(FText::FromString(RichFormat ? RichTextReturn : TextReturn));
//}
//if (SelectedRow)
//{
// if (SelectedRow->DialogueRowData.Num() == 0)
// {
// bResult = false;
// const FString RichTextReturn =
// FString("* ").
// Append("<RichTextBlock.Bold>").
// Append(NodeTitle.ToString()).
// Append("</>").
// Append(": Invalid Selected Row! No Dialogue Data Rows inside!");
// const FString TextReturn =
// FString(NodeTitle.ToString()).
// Append(": Invalid Selected Row! No Dialogue Data Rows inside!");
//
// 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, DataTable))
{
RowName = FName("");
Preview.Empty();
PreviewsUpdated.ExecuteIfBound();
}
if (PropertyChangedEvent.GetPropertyName() == GET_MEMBER_NAME_CHECKED(UComboActionGraphNode_ActionNodeBase, RowName))
{
UpdatePreviews();
PreviewsUpdated.ExecuteIfBound();
}
}
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
{
TArray<FText> ReturnValues;
//const auto Row = UComboActionSystemBFC::GetDialogueRow( this );
//if (UComboActionSystemBFC::IsDialogueRowValid(Row))
//{
// for (auto Itr : Row.DialogueRowData.Array())
// {
// ReturnValues.Add( Itr.RowText );
// }
//}
//else
//{
// ReturnValues.Empty();
//}
return ReturnValues;
}
void UComboActionGraphNode_ActionNodeBase::UpdatePreviews()
{
if (!DataTable) Preview.Empty();
Preview.Empty();
Preview = GetPreviews();
}
#endif
#undef LOCTEXT_NAMESPACE

View File

@ -0,0 +1,81 @@
// ©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, 1, 0, 1);
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
// TODO: Once there are Conditional Decorators, this will be replaced
MaxChildrenNodes = 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 (ChildrenNodes.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));
}
if (ChildrenNodes.Num() > 1)
{
bResult = false;
const FString RichTextReturn =
FString("* ")
.Append("<RichTextBlock.Bold>")
.Append(this->NodeTitle.ToString())
.Append("</>")
.Append(": Has more than 1 child node.");
const FString TextReturn = FString(this->NodeTitle.ToString())
.Append(": Has more than 1 child node.");
ValidationsMessages.Add(FText::FromString(RichFormat ? RichTextReturn : TextReturn));
}
return bResult;
}
#endif
#undef LOCTEXT_NAMESPACE

View File

@ -0,0 +1,172 @@
// ©2023 Batty Bovine Productions, LLC. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "GameplayTagContainer.h"
#include "Decorators/ComboActionDecoratorBase.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:
/**
* The list of decorators for the dialogue graph.
* Decorators are used to add extra functionality or behavior to the nodes in the graph.
* This array should contain an instance of each decorator used in the graph.
* The order of the decorators in this array determines the order in which they will be applied to the nodes.
*/
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category="Combo Input|Action", NoClear, meta=(NoResetToDefault))
TArray<FComboActionDecorator> GraphDecorators;
/**
* A set of gameplay tags associated with this dialogue graph.
*/
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category="Combo Input|Action")
FGameplayTagContainer GraphTags;
/**
* GUID for this Mountea Dialogue Graph.
* Unique identifier for this Dialogue Graph instance.
* 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 dialogue node represented by this instance.
*/
UPROPERTY(BlueprintReadOnly, Category="Combo Input|Action")
TSubclassOf<UComboActionGraphNode> NodeType;
/**
* The class of the dialogue edge represented by this instance.
*/
UPROPERTY(BlueprintReadOnly, Category="Combo Input|Action")
TSubclassOf<UComboActionGraphEdge> EdgeType;
/**
* An array of root nodes in the dialogue 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;
// 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 root nodes of the dialogue graph.
*
* @return An array of all root nodes.
*/
UFUNCTION(BlueprintCallable, Category="Combo Input|Action")
UComboActionGraphNode *GetStartNode() const { return this->StartNode; }
/**
* Returns the array of decorators that are associated with this graph.
*
* @return The array of decorators.
*/
UFUNCTION(BlueprintCallable, BlueprintPure, Category="Combo Input|Action")
TArray<FComboActionDecorator> GetGraphDecorators() const;
/**
* Returns the array of decorators that are associated with this graph and its nodes.
*
* @return The array of decorators.
*/
UFUNCTION(BlueprintCallable, BlueprintPure, Category="Combo Input|Action")
TArray<FComboActionDecorator> GetAllDecorators() const;
/**
* Determines whether the dialogue graph can be started.
*
* @return true if the graph can be started, false otherwise.
*/
bool CanStartDialogueGraph() const;
public:
void CreateGraph();
void ClearGraph();
FORCEINLINE bool IsEdgeEnabled() const { return bEdgeEnabled; }
virtual void PostInitProperties() override;
#pragma endregion
#if WITH_EDITORONLY_DATA
public:
UPROPERTY()
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* ConstructDialogueNode(TSubclassOf<class UComboActionGraphNode> DialogueNodeClass = T::StaticClass())
{
// Set flag to be transactional so it registers with undo system
T *DialogueNode = NewObject<T>(this, DialogueNodeClass, NAME_None, EObjectFlags::RF_Transactional);
DialogueNode->OnCreatedInEditor();
return DialogueNode;
}
#endif
};

View File

@ -0,0 +1,14 @@
// ©2023 Batty Bovine Productions, LLC. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "ComboActionAdditionalData.generated.h"
UCLASS(Abstract, BlueprintType, meta=(UsesHierarchy=true), Blueprintable, ClassGroup="Combo Input|Action")
class COMBOINPUT_API UComboActionAdditionalData : public UObject
{
GENERATED_BODY()
};

View File

@ -0,0 +1,203 @@
// ©2023 Batty Bovine Productions, LLC. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "Data/ComboActionGraphDataTypes.h"
#include "Nodes/ComboActionGraphNode.h"
#include "UObject/Object.h"
#include "ComboActionContext.generated.h"
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FComboActionContextUpdatedFromBlueprint, class UComboActionContext *, Context);
/**
* Dialogue Context.
*
* Contains information needed to successfully start Dialogue.
* Also helps tracking Dialogue Specific data. Is recycled for whole Dialogue Graph.
*
* In Dialogue Manager Component is used as Transient object, which is nullified once Dialogue ends and is never saved.
*/
UCLASS()
class COMBOINPUT_API UComboActionContext : public UObject
{
GENERATED_BODY()
public:
///**
// * Active Dialogue Participant Interface reference.
// *
// * This is the Participant who is Active right now.
// * ❔ Lead Node sets this value to Dialogue Participant.
// * ❔ Answer Node sets this value to Player Participant.
// * ❗ Might be invalid
// */
//UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category="Combo Input|Action")
// TScriptInterface<IComboActionParticipantInterface> ActionParticipant;
///**
// * Player Dialogue Participant Interface reference.
// *
// * This is the Participant who represent the Player.
// * ❗ Might be invalid
// */
//UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category="Combo Input|Action")
// TScriptInterface<class IComboActionParticipantInterface> PlayerDialogueParticipant;
//UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category="Combo Input|Action")
// TScriptInterface<class IComboActionParticipantInterface> DialogueParticipant;
//UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category="Combo Input|Action")
// TArray<TScriptInterface<class IComboActionParticipantInterface>> DialogueParticipants;
/**
* Pointer to the Node which is currently active.
* Might be null
*/
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category="Combo Input|Action")
class UComboActionGraphNode *ActiveNode = nullptr;
/**
* List of Nodes that can be accessed from Active Node.
* Already filtered to contain only those that can be triggered.
*
* Filter is done by 'CanStartNode', which can have its own logic and can be driven by Decorators as well.
* Might be empty
*/
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category="Combo Input|Action")
TArray<class UComboActionGraphNode*> AllowedChildNodes;
/**
* Index of currently used Dialogue Row Data row.
*/
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category="Combo Input|Action")
int32 ActiveDialogueRowDataIndex = 0;
/**
* Contains mapped list of Traversed Nodes by GUIDs.
* Each time Dialogue is updated, like node is selected or starts itself, this Path is updated.
* Updates Participant once Dialogue is done.
*/
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category="Combo Input|Action", meta=(NoResetToDefault))
TMap<FGuid, int32> TraversedPath;
public:
UFUNCTION(BlueprintCallable, BlueprintPure, Category="Combo Input|Action|Debug")
virtual bool IsValid() const;
//TScriptInterface<class IComboActionParticipantInterface> GetDialoguePlayerParticipant() const { return this->PlayerDialogueParticipant; }
//TScriptInterface<class IComboActionParticipantInterface> GetDialogueParticipant() const { return this->DialogueParticipant; }
//TArray<TScriptInterface<class IComboActionParticipantInterface>> GetDialogueParticipants() const { return this->DialogueParticipants; }
/**
* Returns the Active Node object.
* Might be null
*
* @return Active Node if any specified
*/
class UComboActionGraphNode *GetActiveNode() const { return this->ActiveNode; }
/**
* Returns lsit of Children Nodes from Active Node.
* Might be empty
*
* @return List of allowed Children Nodes
*/
TArray<class UComboActionGraphNode*> GetChildrenNodes() const { return this->AllowedChildNodes; }
///**
// *Returns the Active Dialogue Row Data Index.
// *
// * @return Active Row Index
// */
//int32 GetActiveDialogueRowDataIndex() const { return this->ActiveDialogueRowDataIndex; }
/**
* Returns the map of nodes traversed during this dialogue instance.
*
* @return The map of nodes traversed during this dialogue instance.
*/
TMap<FGuid, int32> GetTraversedPath() const { return TraversedPath; }
virtual void SetComboActionContext(class UComboActionGraphNode *NewActiveNode, TArray<class UComboActionGraphNode*> NewAllowedChildNodes);
//virtual void UpdateDialogueParticipant(TScriptInterface<class IComboActionParticipantInterface> NewParticipant);
//virtual void UpdateActiveDialogueNode(class UComboActionGraphNode *NewActiveNode);
//virtual void UpdateAllowedChildrenNodes(const TArray<class UComboActionGraphNode*> &NewNodes);
//virtual void UpdateActiveDialogueRowDataIndex(int32 NewIndex);
//void UpdateDialoguePlayerParticipant(TScriptInterface<class IComboActionParticipantInterface> NewParticipant);
//void UpdateActiveDialogueParticipant(TScriptInterface<class IComboActionParticipantInterface> NewParticipant);
//void AddTraversedNode(const class UComboActionGraphNode* TraversedNode);
//virtual bool AddDialogueParticipants(const TArray<TScriptInterface<class IComboActionParticipantInterface>> &NewParticipants);
//virtual bool AddDialogueParticipant(const TScriptInterface<class IComboActionParticipantInterface> &NewParticipant);
//virtual bool RemoveDialogueParticipants(const TArray<TScriptInterface<class IComboActionParticipantInterface>> &NewParticipants);
//virtual bool RemoveDialogueParticipant(const TScriptInterface<class IComboActionParticipantInterface> &NewParticipant);
//virtual void ClearDialogueParticipants();
/**
* Sets the dialogue context.
*
* @param NewParticipant The new dialogue participant.
* @param NewActiveNode The new active dialogue node.
* @param NewAllowedChildNodes The new allowed child dialogue nodes.
*/
UFUNCTION(BlueprintCallable, Category="Combo Input|Action|Context", meta=(DisplayName="SetDialogueContext"))
virtual void SetComboActionContextBP(class UComboActionGraphNode *NewActiveNode, TArray<class UComboActionGraphNode*> NewAllowedChildNodes);
///**
// * Updates Dialogue Participant.
// *
// * @param NewParticipant - new Dialogue Participant.
// * ❗ Must implement IComboActionParticipantInterface.
// */
//UFUNCTION(BlueprintCallable, Category="Combo Input|Action|Context", meta=(DisplayName="UpdateDialogueParticipant"))
// virtual void UpdateDialogueParticipantBP(TScriptInterface<class IComboActionParticipantInterface> NewParticipant);
/**
* Updates Active Dialogue Node in Context.
*
* @param NewActiveNode - New Active Dialogue Node to update to.
* Must not be Null
*/
//UFUNCTION(BlueprintCallable, Category="Combo Input|Action|Context", meta=(DisplayName="UpdateActiveDialogueNode"))
// virtual void UpdateActiveDialogueNodeBP(class UComboActionGraphNode *NewActiveNode);
/**
* Updates the active dialogue row Data Index.
*
* @param NewIndex - The new active dialogue data row Index.
*/
//UFUNCTION(BlueprintCallable, Category="Combo Input|Action|Context", meta=(DisplayName="UpdateActiveDialogueRowDataIndex"))
// virtual void UpdateActiveDialogueRowDataIndexBP(int32 NewIndex);
///**
// * Updates Dialogue Player Participant.
// *
// * @param NewParticipant - new Dialogue Player Participant.
// * ❗ Must implement IMounteaDialogueParticipantInterface.
// */
//UFUNCTION(BlueprintCallable, Category="Combo Input|Action|Context", meta=(DisplayName="UpdateDialoguePlayerParticipant"))
// void UpdateDialoguePlayerParticipantBP(TScriptInterface<class IComboActionParticipantInterface> NewParticipant);
///**
// * Updates Dialogue Active Participant.
// *
// * @param NewParticipant - new Dialogue Active Participant.
// * ❗ Must implement IMounteaDialogueParticipantInterface.
// */
//UFUNCTION(BlueprintCallable, Category="Combo Input|Action|Context", meta=(DisplayName="UpdateActiveDialogueParticipant"))
// void UpdateActiveDialogueParticipantBP(TScriptInterface<class IComboActionParticipantInterface> NewParticipant);
//UFUNCTION(BlueprintCallable, Category="Combo Input|Action|Context", meta=(DisplayName="AddDialogueParticipant"))
// virtual bool AddDialogueParticipantBP(const TScriptInterface<class IComboActionParticipantInterface>& NewParticipant);
//
//UFUNCTION(BlueprintCallable, Category="Combo Input|Action|Context", meta=(DisplayName="RemoveDialogueParticipant"))
// virtual bool RemoveDialogueParticipantBP(const TScriptInterface<class IComboActionParticipantInterface>& NewParticipant);
//UFUNCTION(BlueprintCallable, Category="Combo Input|Action|Context", meta=(DisplayName="AddDialogueParticipants"))
// virtual bool AddDialogueParticipantsBP(const TArray<TScriptInterface<class IComboActionParticipantInterface>>& NewParticipants);
//
//UFUNCTION(BlueprintCallable, Category="Combo Input|Action|Context", meta=(DisplayName="RemoveDialogueParticipants"))
// virtual bool RemoveDialogueParticipantsBP(const TArray<TScriptInterface<class IComboActionParticipantInterface>>& NewParticipants);
FComboActionContextUpdatedFromBlueprint ComboActionContextUpdatedFromBlueprint;
};

View File

@ -0,0 +1,27 @@
// ©2023 Batty Bovine Productions, LLC. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "ComboActionAdditionalData.h"
#include "GameplayTagContainer.h"
#include "Blueprint/UserWidget.h"
#include "Engine/DataTable.h"
#include "UObject/Object.h"
#include "Fonts/SlateFontInfo.h"
#include "Styling/CoreStyle.h"
#include "ComboActionGraphDataTypes.generated.h"
/**
* Combo Action Manager state
*/
UENUM(BlueprintType)
enum class EComboActionManagerState : uint8
{
Disabled UMETA(DisplayName="Disabled", Tooltip="Disabled. Combo action cannot be used."),
Enabled UMETA(DisplayName="Enabled", Tooltip="Enabled. Combo action can start."),
Active UMETA(DisplayName="Activated", Tooltip="Combo action is activated"),
};

View File

@ -0,0 +1,254 @@
// ©2023 Batty Bovine Productions, LLC. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "Engine/Level.h"
#include "ComboActionDecoratorBase.generated.h"
DECLARE_LOG_CATEGORY_EXTERN(LogComboActionDecoratorBase, Log, All);
#define LOCTEXT_NAMESPACE "NodeDecoratorBase"
UENUM(BlueprintType)
enum class EComboActionDecoratorState : uint8
{
Uninitialized,
Initialized
};
/**
* Combo Action Decorator
*
* Decorators are instanced and exist only as "triggers".
* Could be used to start audio, play animation or do some logic behind the curtains, like triggering Cutscene etc.
*/
UCLASS(Abstract, Blueprintable, BlueprintType, EditInlineNew, ClassGroup=("Combo Input|Action"), AutoExpandCategories=("Combo Input, Action"))
class COMBOINPUT_API UComboActionDecoratorBase : public UObject
{
GENERATED_BODY()
public:
FORCEINLINE ULevel *GetLevel() const { return this->GetTypedOuter<ULevel>(); }
public:
virtual UWorld *GetWorld() const override
{
if (this->OwningWorld)
{
return this->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 (!this->HasAnyFlags(EObjectFlags::RF_ClassDefaultObject) && ensureMsgf(this->GetOuter(), TEXT("Actor: %s has a null OuterPrivate in AActor::GetWorld()"), *this->GetFullName()) &&
!this->GetOuter()->HasAnyFlags(EObjectFlags::RF_BeginDestroyed) && !this->GetOuter()->IsUnreachable())
{
if (ULevel *Level = this->GetLevel())
{
return Level->OwningWorld;
}
}
return nullptr;
}
UFUNCTION(BlueprintNativeEvent, Category="Combo Input|Action|Decorators")
FString GetDecoratorDocumentationLink() const;
virtual FString GetDecoratorDocumentationLink_Implementation() const { return TEXT("This is a place where a link to the documentation will go eventually."); }
public:
/**
* Initializes the Decorator.
* In C++ saves the World for later use.
* In Blueprints should be used to cache values to avoid overhead in 'ExecuteDecorator'.
*/
UFUNCTION(BlueprintNativeEvent, Category="Combo Input|Action|Decorators")
void InitializeDecorator(UWorld *World);
virtual void InitializeDecorator_Implementation(UWorld *World)
{
this->OwningWorld = World;
if (World)
{
this->DecoratorState = EComboActionDecoratorState::Initialized;
}
}
/**
* Cleans up the Decorator.
* In Blueprints should be used to reset cached values to avoid blocking garbage collector.
*/
UFUNCTION(BlueprintNativeEvent, Category="Combo Input|Action|Decorators")
void CleanupDecorator();
virtual void CleanupDecorator_Implementation() { this->DecoratorState = EComboActionDecoratorState::Uninitialized; }
/**
* Validates the Decorator.
* Called for each Node it is attached to.
* Works as safety measure to avoid calling broken Decorators with invalid references.
*
* False value stops Dialogue whatsoever.
* Validation is called before Context is initialized!
*/
UFUNCTION(BlueprintNativeEvent, Category="Combo Input|Action|Decorators")
bool ValidateDecorator(TArray<FText> &ValidationMessages);
virtual bool ValidateDecorator_Implementation(TArray<FText> &ValidationMessages);
/**
* Evaluates the Decorator.
* Called for each Node it is attached to.
* Could enhance Node's 'CanStartNode'. Example would be: BP_RequireItem decorator, which would return true if Player has specific Item in inventory. Otherwise it returns false and its Node is not available in Selection of Answers.
*/
UFUNCTION(BlueprintNativeEvent, Category="Combo Input|Action|Decorators")
bool EvaluateDecorator();
virtual bool EvaluateDecorator_Implementation() { return this->OwningWorld != nullptr; }
/**
* Executes the Decorator.
* Useful for triggering special events per Node, for instance, switching dialogue cameras.
*/
UFUNCTION(BlueprintNativeEvent, Category="Combo Input|Action|Decorators")
void ExecuteDecorator();
virtual bool ExecuteDecorator_Implementation() { return true; }
/**
* Stores reference to World.
* World is needed to perform World affecting tasks.
*/
UFUNCTION(BlueprintCallable, Category="Combo Input|Action|Decorators")
void StoreWorldReference(UWorld *World) { this->OwningWorld = World; }
/**
* Returns Owning World this Decorator belongs to.
*
* Should not return Null, but possibly can.
*/
UFUNCTION(BlueprintCallable, Category="Combo Input|Action|Decorators", meta=(CompactNodeTitle="World"))
UWorld *GetOwningWorld() const { return this->OwningWorld; }
/**
* Returns Owning Node of this Decorator.
*
* Might return Null if this Decorator is owned by Graph!
*/
UFUNCTION(BlueprintCallable, BlueprintPure, Category="Combo Input|Action|Decorators", meta=(CompactNodeTitle="OwningNode"))
class UComboActionGraphNode *GetOwningNode() const { return this->GetTypedOuter<UComboActionGraphNode>(); }
/**
* Returns Owning Graph of this Decorator.
*
* Might return Null if this Decorator is owned by Node!
*/
UFUNCTION(BlueprintCallable, BlueprintPure, Category="Combo Input|Action|Decorators", meta=(CompactNodeTitle="OwningGraph"))
class UComboActionGraph *GetOwningGraph() const { return this->GetTypedOuter<UComboActionGraph>(); }
/**
* Returns Owning Object of this Decorator.
*/
UFUNCTION(BlueprintCallable, BlueprintPure, Category="Combo Input|Action|Decorators", meta=(CompactNodeTitle="Owner"))
UObject *GetOwner() const { return this->GetOuter(); }
FText GetDecoratorName() const
{
#if WITH_EDITORONLY_DATA
return this->GetClass()->GetDisplayNameText();
#else
return FText::FromString(GetName());
#endif
}
private:
UPROPERTY() EComboActionDecoratorState DecoratorState = EComboActionDecoratorState::Uninitialized;
UPROPERTY() UWorld *OwningWorld = nullptr;
};
/**
* Dialogue Decorator Structure.
* Holds reference to its Instanced Decorator.
*/
USTRUCT(BlueprintType)
struct FComboActionDecorator
{
GENERATED_BODY()
FComboActionDecorator() : DecoratorType(nullptr) {};
public:
void InitializeDecorator(UWorld *World) const
{
if (this->DecoratorType)
{
this->DecoratorType->InitializeDecorator(World);
}
else
{
UE_LOG(LogComboActionDecoratorBase, Error, TEXT("[InitializeDecorator] DecoratorType is null (invalid)!"));
}
}
bool ValidateDecorator(TArray<FText>& ValidationMessages) const
{
if (this->DecoratorType)
{
return this->DecoratorType->ValidateDecorator(ValidationMessages);
}
UE_LOG(LogComboActionDecoratorBase, Error, TEXT("[EvaluateDecorator] DecoratorType is null (invalid)!"));
return false;
}
void CleanupDecorator() const
{
if (this->DecoratorType)
{
this->DecoratorType->CleanupDecorator();
}
else
{
UE_LOG(LogComboActionDecoratorBase, Error, TEXT("[CleanupDecorator] DecoratorType is null (invalid)!"));
}
}
bool EvaluateDecorator() const
{
if (this->DecoratorType)
{
return this->DecoratorType->EvaluateDecorator();
}
UE_LOG(LogComboActionDecoratorBase, Error, TEXT("[EvaluateDecorator] DecoratorType is null (invalid)!"));
return false;
}
void ExecuteDecorator() const
{
if (this->DecoratorType)
{
this->DecoratorType->ExecuteDecorator();
}
else
{
UE_LOG(LogComboActionDecoratorBase, Error, TEXT("[ExecuteDecorator] DecoratorType is null (invalid)!"));
}
}
public:
/**
* Decorators can help out with enhancing the Dialogue flow.
* Those Decorators are instanced and exist only as "triggers".
* Could be used to start audio, play animation or do some logic behind the curtains, like triggering Cutscene etc.
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Instanced, Category = "Combo Input|Action", meta=(NoResetToDefault, AllowAbstract = "false", BlueprintBaseOnly = "true"))
UComboActionDecoratorBase *DecoratorType = nullptr;
bool operator==(const FComboActionDecorator &Other) const { return this->DecoratorType == Other.DecoratorType; }
};
#undef LOCTEXT_NAMESPACE

View File

@ -0,0 +1,233 @@
// ©2023 Batty Bovine Productions, LLC. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "Data/ComboActionGraphDataTypes.h"
#include "UObject/Interface.h"
#include "ComboActionGraphManagerInterface.generated.h"
// This class does not need to be modified.
UINTERFACE(MinimalAPI, BlueprintType, Blueprintable)
class UComboActionGraphManagerInterface : public UInterface
{
GENERATED_BODY()
};
//DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FDialogueInitialized, UMounteaDialogueContext*, Context);
//DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FDialogueEvent, UMounteaDialogueContext*, Context);
//
//DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FDialogueContextUpdated, UMounteaDialogueContext*, Context);
//DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FDialogueUserInterfaceChanged, TSubclassOf<UUserWidget>, DialogueWidgetClass, UUserWidget*, DialogueWidget);
//
//DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FDialogueNodeEvent, UMounteaDialogueContext*, Context);
//DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FDialogueRowEvent, UMounteaDialogueContext*, Context);
//
//DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FDialogueFailed, const FString&, ErrorMessage);
//
//DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FDialogueManagerStateChanged, const EDialogueManagerState&, NewState);
//
//DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FDialogueVoiceEvent, class USoundBase*, NewDialogueVoice);
/**
* Mountea Dialogue Manager Interface.
*
* Should attached directly to Player Controller or used for Components that are attached to some Controller.
* Provides options to start and stop dialogue as well as ability to select dialogue options.
*/
class COMBOINPUT_API IComboActionGraphManagerInterface
{
GENERATED_BODY()
public:
/**
* Notifies the Dialogue that a node has been selected.
*
* @param NodeGUID The GUID of the selected node.
*/
//UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Combo Input|Action", meta=(Keywords="select, chosen, option"))
// void CallDialogueNodeSelected(const FGuid &NodeGUID);
/**
* Starts the Dialogue if possible.
*/
//virtual void StartDialogue() = 0;
/**
* Closes the Dialogue if is active.
*/
//virtual void CloseDialogue() = 0;
/**
* Tries to Invoke Dialogue UI.
* This function servers a purpose to try showing Dialogue UI to player.
* If this function fails, Message will be populated with error message explaining what went wrong.
*
* @param Message InMessage to be populated with error message explaining why returns false
* @return true if UI can be added to screen, false if cannot
*/
//virtual bool InvokeDialogueUI(FString& Message) = 0;
/**
* Gets the widget class used to display Dialogue UI.
*
* @return The widget class used to display Dialogue UI.
*/
//virtual TSubclassOf<UUserWidget> GetDialogueWidgetClass() const = 0;
/**
* Sets the widget class for the Dialogue UI.
* This is a pure virtual function that must be implemented in derived classes.
*
* @param NewWidgetClass The new widget class to set.
*/
//virtual void SetDialogueWidgetClass(TSubclassOf<UUserWidget> NewWidgetClass) = 0;
/**
* Returns Dialogue UI pointer.
*
* Could be null
* @return UserWidget pointer to created UI
*/
//virtual UUserWidget *GetDialogueUIPtr() const = 0;
/**
* Sets Dialogue UI pointer.
*
* Using null value resets saved value
* @param DialogueUIPtr UserWidget pointer to be saved as Dialogue UI
*/
//virtual void SetDialogueUIPtr(UUserWidget *DialogueUIPtr) = 0;
/**
* Starts Dialogue Row execution.
* Dialogue Data contain Dialogue Data Rows, which are individual dialogue lines, which can be skipped.
* Once all Dialogue Data Rows are finished, Dialogue Data is finished as well.
*/
//virtual void StartExecuteDialogueRow() = 0;
/**
* Function responsible for cleanup once Dialogue Row is finished.
* Dialogue Data contain Dialogue Data Rows, which are individual dialogue lines, which can be skipped.
* Once all Dialogue Data Rows are finished, Dialogue Data is finished as well.
*/
//virtual void FinishedExecuteDialogueRow() = 0;
/**
* Retrieves the current dialogue context associated with this dialogue instance.
*
* @return The dialogue context object for this instance.
*/
//UFUNCTION(BlueprintCallable, BlueprintNativeEvent, Category="Combo Input|Action")
//UMounteaDialogueContext* GetDialogueContextEvent() const;
//UMounteaDialogueContext* GetDialogueContextEvent_Implementation() const
//{
// return GetDialogueContext();
//}
/**
* Returns the widget used to display the current dialogue.
*
* @return The widget used to display the current dialogue.
*/
//UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Combo Input|Action", meta=(Keywords="UI, Widget"))
//UUserWidget* GetDialogueWidget();
//UUserWidget* GetDialogueWidget_Implementation()
//{
// return GetDialogueWidget();
//};
/**
* Returns the owning actor for this Dialogue Manager Component.
*
* @return The owning actor for this Dialogue Manager Component.
*/
//UFUNCTION(BlueprintCallable, BlueprintNativeEvent, Category="Combo Input|Action")
//AActor* GetOwningActor() const;
//virtual AActor* GetOwningActor_Implementation() const
//{
// return nullptr;
//};
/**
* Prepares the node for execution.
* Asks Active Node to 'PreProcessNode' and then to 'ProcessNode'.
* In this preparation stage, Nodes are asked to process all Decorators.
*/
//UFUNCTION(BlueprintCallable, BlueprintNativeEvent, Category="Combo Input|Action")
//void PrepareNode();
//virtual void PrepareNode_Implementation() {};
/**
* Retrieves current Dialogue Context.
*
* Could be null
* @return DialogueContext Dialogue Context is transient data holder for current dialogue instance.
*/
//virtual UMounteaDialogueContext* GetDialogueContext() const = 0;
/**
* Sets new Dialogue Context.
*
* Null value clears saved data
* @param NewContext Dialogue Context to be set as Dialogue Context
*/
//virtual void SetDialogueContext(UMounteaDialogueContext* NewContext) = 0;
/**
* Interface call.
* Retrieves current Dialogue Manager State.
* State defines whether Manager can start/close dialogue or not.
*
* @return ManagerState Manager state value
*/
//UFUNCTION(BlueprintCallable, BlueprintNativeEvent, Category="Combo Input|Action")
//EDialogueManagerState GetState() const;
//EDialogueManagerState GetState_Implementation() const
//{ return GetDialogueManagerState(); };
/**
* Retrieves current Dialogue Manager State.
* State defines whether Manager can start/close dialogue or not.
*
* @return ManagerState Manager state value
*/
//virtual EDialogueManagerState GetDialogueManagerState() const = 0;
/**
* Sets new Dialogue Manager State.
*
* @param NewState Manager State to be set as Manager State
*/
//virtual void SetDialogueManagerState(const EDialogueManagerState NewState) = 0;
/**
* Retrieves current Default Dialogue Manager State.
* Default Dialogue Manager State sets Dialogue Manager state upon BeginPlay and is used as fallback once Dialogue ends.
*
* @return ManagerState Default Manager state value
*/
//virtual EDialogueManagerState GetDefaultDialogueManagerState() const = 0;
/**
* Sets new Default Dialogue Manager State.
*
* @param NewState Manager State to be set as Default Manager State
*/
//virtual void SetDefaultDialogueManagerState(const EDialogueManagerState NewState) = 0;
//
//virtual FDialogueInitialized& GetDialogueInitializedEventHandle() = 0;
//virtual FDialogueEvent& GetDialogueStartedEventHandle() = 0;
//virtual FDialogueEvent& GetDialogueClosedEventHandle() = 0;
//
//virtual FDialogueContextUpdated& GetDialogueContextUpdatedEventHande() = 0;
//virtual FDialogueUserInterfaceChanged& GetDialogueUserInterfaceChangedEventHandle() = 0;
//virtual FDialogueNodeEvent& GetDialogueNodeSelectedEventHandle() = 0;
//virtual FDialogueNodeEvent& GetDialogueNodeStartedEventHandle() = 0;
//virtual FDialogueNodeEvent& GetDialogueNodeFinishedEventHandle() = 0;
//virtual FDialogueRowEvent& GetDialogueRowStartedEventHandle() = 0;
//virtual FDialogueRowEvent& GetDialogueRowFinishedEventHandle() = 0;
//virtual FDialogueFailed& GetDialogueFailedEventHandle() = 0;
//virtual FDialogueManagerStateChanged& GetDialogueManagerStateChangedEventHandle() = 0;
//virtual FDialogueVoiceEvent& GetDialogueVoiceStartRequestEventHandle() = 0;
//virtual FDialogueVoiceEvent& GetDialogueVoiceSkipRequestEventHandle() = 0;
//virtual FTimerHandle& GetDialogueRowTimerHandle() = 0;
};

View File

@ -0,0 +1,199 @@
// ©2023 Batty Bovine Productions, LLC. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "Data/ComboActionGraphDataTypes.h"
#include "UObject/Interface.h"
#include "ComboActionInterface.generated.h"
UINTERFACE(MinimalAPI, BlueprintType, Blueprintable)
class UComboActionInterface : public UInterface
{
GENERATED_BODY()
};
//DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FDialogueGraphChanged, UMounteaDialogueGraph*, NewGraph);
//DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FDialogueParticipantStateChanged, const EDialogueParticipantState&, NewState);
//DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FDialogueParticipantAudioComponentChanged, const UAudioComponent*, NewAudioComp);
//DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FParticipantStartingNodeSaved, const UMounteaDialogueGraphNode*, NewSavedNode);
/**
* Combo Action Interface
*/
class COMBOINPUT_API IComboActionInterface
{
GENERATED_BODY()
public:
#pragma region EventFunctions
/*
* A way to determine whether the Dialogue can even start.
* It does come with Native C++ implementation, which can be overriden in child C++ classes.
* If you are using Blueprint implementation, don't forget to call Parent Node, which contains all parent implementations.
*/
UFUNCTION(BlueprintCallable, BlueprintNativeEvent, Category="Combo Input|Action")
bool CanStartDialogueEvent() const;
/**
* Returns the owning actor for this Dialogue Participant Component.
*
* @return The owning actor for this Dialogue Participant Component.
*/
UFUNCTION(BlueprintCallable, BlueprintNativeEvent, Category="Combo Input|Action")
AActor *GetOwningActor() const;
/**
* Saves the starting node for this Dialogue Participant Component.
*
* @param NewStartingNode The node to set as the starting node
*/
UFUNCTION(BlueprintCallable, BlueprintNativeEvent, Category="Combo Input|Action")
void SaveStartingNode(class UComboActionGraphNode *NewStartingNode);
/**
* Saves the traversed path for this Dialogue Participant Component.
* This function is called once Dialogue ends and is updated from Dialogue Context.
*
* @param InPath The traversed path of the dialogue graph to be saved.
*/
UFUNCTION(BlueprintCallable, BlueprintNativeEvent, Category="Combo Input|Action")
void SaveTraversedPath(TMap<FGuid,int32> &InPath);
/**
* Interface call.
* Retrieves current Dialogue Participant State.
* State defines whether Participant can start/close dialogue or not.
*
* @return ParticipantState Participant state value
*/
//UFUNCTION(BlueprintCallable, BlueprintNativeEvent, Category="Combo Input|Action")
// EDialogueParticipantState GetState() const;
UFUNCTION(BlueprintCallable, BlueprintNativeEvent, Category="Combo Input|Action")
FGameplayTag GetTag() const;
#pragma endregion
protected:
#pragma region EventFunctions_Implementations
bool CanStartDialogueEvent_Implementation() const { return CanStartDialogue(); }
virtual void SaveStartingNode_Implementation(UComboActionGraphNode *NewStartingNode){}
virtual AActor *GetOwningActor_Implementation() const { return nullptr; }
virtual void SaveTraversedPath_Implementation(TMap<FGuid,int32> &InPath){}
//EDialogueParticipantState GetState_Implementation() const { return GetParticipantState(); }
FGameplayTag GetTag_Implementation() const { return GetParticipantTag(); }
#pragma endregion
public:
#pragma region Functions
/**
* Checks if the Participant can be start Dialogue.
* To enhance this, you can implement 'CanStartDialogueEvent' and add custom checks to that function.
*
* @return Whether the dialogue can be started
*/
virtual bool CanStartDialogue() const = 0;
/**
* Gets the saved starting node for this Dialogue Participant.
* Could be null
*
* @return The saved starting node, or nullptr if there is none
*/
virtual UComboActionGraphNode *GetSavedStartingNode() const = 0;
/**
* Tries to play the specified sound as the voice of this dialogue participant.
*
* @param ParticipantVoice The sound to play as the voice of this dialogue participant
*/
virtual void PlayParticipantVoice(USoundBase *ParticipantVoice) = 0;
/**
* Tries to skip the specified sound this participant is playing as voice.
*
* @param ParticipantVoice The sound to skip this participant is playing as voice.
*/
virtual void SkipParticipantVoice(USoundBase *ParticipantVoice) = 0;
/**
* Returns the dialogue graph assigned to this Participant.
* Could be updated using 'SetDialogueGraph', providing ability to swith Dialogue graphs on fly
* Could be null
*
* @return A pointer to the dialogue graph
*/
virtual class UComboActionGraph *GetDialogueGraph() const = 0;
/**
* Sets new Dialogue graph for this Participant.
* Should not be null
*
* @param NewDialogueGraph A pointer to the dialogue graph to be used
*/
virtual void SetDialogueGraph(class UComboActionGraph *NewDialogueGraph) = 0;
/**
* Returns the current state of the Dialogue Participant.
*/
//virtual EDialogueParticipantState GetParticipantState() const = 0;
/**
* Sets the state of the dialogue participant to the given state.
*
* @param NewState The new state to set the dialogue participant to
*/
//virtual void SetParticipantState(const EDialogueParticipantState NewState) = 0;
/**
* Returns the default state of the Dialogue Participant.
*/
//virtual EDialogueParticipantState GetDefaultParticipantState() const = 0;
/**
* Sets the Default state of the dialogue participant to the given state.
*
* @param NewState The new state to set the dialogue participant to
*/
//virtual void SetDefaultParticipantState(const EDialogueParticipantState NewState) = 0;
/**
* Returns the audio component used to play the participant voices.
* Could be null
*/
virtual UAudioComponent *GetAudioComponent() const = 0;
/**
* Sets the audio component used to play dialogue audio.
*
* @param NewAudioComponent The new audio component to use for dialogue audio.
*/
virtual void SetAudioComponent(UAudioComponent *NewAudioComponent) = 0;
/**
* Returns the map of nodes traversed during the dialogue.
*
* @return The map of nodes traversed during the dialogue.
*/
virtual TMap<FGuid,int32> GetTraversedPath() const = 0;
virtual FGameplayTag GetParticipantTag() const = 0;
#pragma endregion
#pragma region EventHandles
//virtual FDialogueGraphChanged &GetDialogueGraphChangedEventHandle() = 0;
//virtual FDialogueParticipantStateChanged &GetDialogueParticipantStateChangedEventHandle() = 0;
//virtual FDialogueParticipantAudioComponentChanged &GetDialogueParticipantAudioComponentChangedEventHandle() = 0;
//virtual FParticipantStartingNodeSaved &GetParticipantStartingNodeSavedEventHandle() = 0;
#pragma endregion
};

View File

@ -0,0 +1,40 @@
// ©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()
#pragma region Variables
public:
UPROPERTY(VisibleAnywhere, Category="Combo Input|Action")
UComboActionGraph *Graph = nullptr;
UPROPERTY(BlueprintReadOnly, Category="Combo Input|Action")
UComboActionGraphNode *StartNode = nullptr;
UPROPERTY(BlueprintReadOnly, Category="Combo Input|Action")
UComboActionGraphNode *EndNode = nullptr;
#pragma endregion
#pragma region Functions
public:
//UFUNCTION(BlueprintPure, Category="Combo Input|Action")
UComboActionGraph *GetGraph() const { return Graph; }
#pragma endregion
};

View File

@ -0,0 +1,397 @@
// ©2023 Batty Bovine Productions, LLC. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "Decorators/ComboActionDecoratorBase.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"))
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, VisibleAnywhere, Category="Private")
TArray<class UComboActionGraphNode*> ParentNodes;
/**
* The array of child nodes of the current dialogue node.
*/
UPROPERTY(BlueprintReadOnly, VisibleAnywhere, Category="Private")
TArray<class UComboActionGraphNode*> ChildrenNodes;
/**
* 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, VisibleAnywhere, Category="Private")
TMap<class UComboActionGraphNode*, class UComboActionGraphEdge*> Edges;
/**
* Pointer to the parent dialogue graph of this node.
*/
UPROPERTY(BlueprintReadOnly, VisibleAnywhere, Category="Private", meta=(DisplayThumbnail=false))
TObjectPtr<class UComboActionGraph> Graph;
/**
* Temporary NodeIndex.
* This variable will be deleted.
*/
UPROPERTY(BlueprintReadOnly, VisibleAnywhere, 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, VisibleAnywhere, Category="Private")
FGuid NodeGUID;
private:
/**
* 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(VisibleAnywhere, Category="Private")
TObjectPtr<UWorld> OwningWorld;
#pragma endregion
#pragma region Editable
public:
/**
* 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(SaveGame, EditDefaultsOnly, BlueprintReadOnly, Category="Combo Input|Action")
TArray<TSubclassOf<class UComboActionGraphNode>> AllowedInputClasses;
/** Defines whether this Node will start automatically or if requires input.*/
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="Combo Input|Action")
uint8 bAutoStarts : 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 MaxChildrenNodes = -1;
/**
* Indicates whether this node inherits the decorators from its parent Graph.
* If true, the decorators of the parent Graph will be inherited and applied to this node during processing.
* This flag can be used to control the inheritance of decorators for nodes in the dialogue graph.
*/
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="Combo Input|Action")
uint8 bInheritGraphDecorators : 1;
/**
* A list of Decorators that can help out with enhancing the Dialogue flow.
* Those Decorators are instanced and exist only as "triggers".
* Could be used to start audio, play animation or do some logic behind the curtains, like triggering Cutscene etc.
*/
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Combo Input|Action", NoClear, meta=(NoResetToDefault))
TArray<FComboActionDecorator> NodeDecorators;
#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);
/**
* Checks if the node should automatically start when the dialogue is played.
* @return true if the node should automatically start, false otherwise.
*/
UFUNCTION(BlueprintCallable, BlueprintPure, Category="Combo Input|Action")
virtual bool DoesAutoStart() const { return bAutoStarts; }
virtual void PreProcessNode(){}
virtual void ProcessNode();
/**
* Gets the decorators for this Dialogue Graph Node.
* Returns only Valid decorators!
*/
UFUNCTION(BlueprintCallable, BlueprintPure, Category="Combo Input|Action")
TArray<FComboActionDecorator> GetNodeDecorators() const;
/**
* Returns true if the node can be started.
* The implementation of this function is up to the subclass.
* Can be used to validate if a node can be started before attempting to start it.
* This can be further enhanced by Decorators.
* @return True if the node can be started, false otherwise.
*/
UFUNCTION(BlueprintCallable, BlueprintPure, Category="Combo Input|Action")
virtual bool CanStartNode() const;
virtual bool EvaluateDecorators() const;
/**
* Returns whether this node inherits decorators from the dialogue graph.
* If this is set to true, this node will receive all decorators assigned to the graph.
* If it's set to false, the node will only have its own decorators.
*
* @return Whether this node inherits decorators from the graph.
*/
UFUNCTION(BlueprintCallable, BlueprintPure, Category="Combo Input|Action")
bool DoesInheritDecorators() const { return bInheritGraphDecorators; }
/**
* Returns how many Children Nodes this Node allows to have.
* -1 means no limits.
*
* @return MaxChildrenNodes
*/
UFUNCTION(BlueprintCallable, BlueprintPure, Category="Combo Input|Action")
int32 GetMaxChildNodes() const { return MaxChildrenNodes; }
/**
* 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; }
/**
* Gets children Nodes this one has,
* Might be empty
*
* @return Amount of children Nodes
*/
UFUNCTION(BlueprintCallable, BlueprintPure, Category="Combo Input|Action")
FORCEINLINE TArray<UComboActionGraphNode*> GetChildrenNodes() const { return ChildrenNodes; }
/**
* Gets how many parent Nodes point to this one
* Might be empty
*
* @return Amount of how parent Nodes
*/
UFUNCTION(BlueprintCallable, BlueprintPure, Category="Combo Input|Action")
FORCEINLINE TArray<UComboActionGraphNode*> GetParentNodes() const { return ParentNodes; }
/**
* 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, EditDefaultsOnly, Category = "Editor")
bool bAllowInputNodes;
// Defines whether this Node type allows outputs
UPROPERTY(BlueprintReadOnly, EditDefaultsOnly, Category = "Editor")
bool bAllowOutputNodes;
// Defines whether this Node can be copied
UPROPERTY(BlueprintReadOnly, EditDefaultsOnly, Category = "Editor")
bool bAllowCopy;
// Defines whether this Node can be cut
UPROPERTY(BlueprintReadOnly, EditDefaultsOnly, Category = "Editor")
bool bAllowCut;
// Defines whether this Node can be pasted
UPROPERTY(BlueprintReadOnly, EditDefaultsOnly, Category = "Editor")
bool bAllowPaste;
// Defines whether this Node can be deleted
UPROPERTY(BlueprintReadOnly, EditDefaultsOnly, Category = "Editor")
bool bAllowDelete;
// Defines whether this Node can be manually created
UPROPERTY(BlueprintReadOnly, EditDefaultsOnly, Category = "Editor")
bool bAllowManualCreate;
// Display title of the Node
UPROPERTY(BlueprintReadOnly, EditDefaultsOnly, Category = "Editor")
FText NodeTitle;
// Display name of the Node menu category
UPROPERTY(BlueprintReadOnly, EditDefaultsOnly, Category = "Editor")
FText ContextMenuName;
// List of compatible graph types
UPROPERTY(BlueprintReadOnly, VisibleAnywhere, Category = "Editor")
TSubclassOf<UObject> CompatibleGraphType;
// Defines background colour of this Node
UPROPERTY(BlueprintReadOnly, EditDefaultsOnly, Category = "Editor")
FLinearColor BackgroundColor;
// Contains Node Tooltip text
UPROPERTY(BlueprintReadOnly, EditDefaultsOnly, Category = "Editor")
FText NodeTooltipText;
// User friendly node type name
UPROPERTY(BlueprintReadOnly, EditDefaultsOnly, 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 GetBackgroundColor() 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, BlueprintCallable, Category = "Combo Input|Action", meta=(DevelopmentOnly=true))
FText GetDefaultTooltipBody() const;
virtual void OnCreatedInEditor() {};
#endif
};

View File

@ -0,0 +1,126 @@
// ©2023 Batty Bovine Productions, LLC. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "Engine/DataTable.h"
//#include "Helpers/ComboActionGraphHelpers.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() override;
virtual void PreProcessNode() override;
/**
* Returns the Dialogue Data Table for this graph node.
* Might be null
*
* @return The Dialogue Data Table for this graph node.
*/
UFUNCTION(BlueprintCallable, BlueprintPure, Category="Combo Input|Action")
virtual UDataTable *GetDataTable() const;
/**
* Returns the Dialogue Data Row name.
* Might be invalid
*
* @return The Dialogue Data Row name.
*/
UFUNCTION(BlueprintCallable, BlueprintPure, Category="Combo Input|Action")
virtual FName GetRowName() const { return RowName; }
virtual bool ValidateNodeRuntime_Implementation() const override;
public:
#if WITH_EDITORONLY_DATA
/**
* Shows read-only Texts with localization of selected Dialogue Row.
*/
UPROPERTY(Transient, VisibleAnywhere, Category="Base", meta=(MultiLine=true, ShowOnlyInnerProperties))
TArray<FText> Preview;
FSimpleDelegate PreviewsUpdated;
#endif
protected:
/**
* The data table containing the dialogue rows.
* Strongly suggested to use 'DialogueRow' based Data Tables
*/
UPROPERTY(SaveGame, Category="Combo Input|Action", EditAnywhere, BlueprintReadOnly, meta=(DisplayThumbnail=false, NoResetToDefault))
UDataTable *DataTable;
/** Name of row in the table that we want */
UPROPERTY(SaveGame, Category="Combo Input|Action", EditAnywhere, BlueprintReadOnly, meta=(GetOptions ="GetRowNames", NoResetToDefault, EditCondition="DataTable!=nullptr"))
FName RowName;
/**
* Flag defining how the Participant is searched for.
* Default: False
*
* If True:
* * Participant will be found by its Gameplay Tag, compared to Dialogue Row Data.
* * Only exact match is considered success
* * First found is used, so use unique Tags when working with multiple Participants (Player01, Player02, NPC.Andrew etc.)
*
* If False:
* * Participant will be found using Node Type
* * Lead Node will use NPC
* * Answer Node will use Player
* * This system will be deprecated
*
* New feature in version 1.0.5.X.
* Each unique dialogue Participant should be using different Tag, if generic, then use something like `Dialogue.NPC`
*/
UPROPERTY(SaveGame, Category="Combo Input|Action", EditAnywhere, BlueprintReadOnly, meta=(NoResetToDefault))
uint8 bUseGameplayTags : 1;
#if WITH_EDITOR
virtual bool ValidateNode(TArray<FText>& ValidationsMessages, 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();
#endif
private:
UFUNCTION()
TArray<FName> GetRowNames() const
{
if (DataTable)
{
return DataTable->GetRowNames();
}
return TArray<FName>();
}
};

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

@ -27,7 +27,8 @@ public class ComboInputEditor : ModuleRules
"CoreUObject", "CoreUObject",
"AssetTools", "AssetTools",
"SlateCore", "SlateCore",
"Projects" "Projects",
"GraphEditor"
} }
); );

View File

@ -0,0 +1,434 @@
// ©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"
UEdGraphNode *FAssetSchemaAction_ComboActionGraphSchema_NewNode::PerformAction(UEdGraph *ParentGraph, UEdGraphPin *FromPin, const FVector2D Location, bool bSelectNewNode)
{
UEdGraphNode *ResultNode = nullptr;
if (NodeTemplate != nullptr)
{
const FScopedTransaction Transaction(LOCTEXT("ComboActionGraphEditorNewNode", "Combo Action Graph Editor: New Node"));
ParentGraph->Modify();
if (FromPin != nullptr)
FromPin->Modify();
NodeTemplate->Rename(nullptr, ParentGraph);
ParentGraph->AddNode(NodeTemplate, true, bSelectNewNode);
NodeTemplate->CreateNewGuid();
NodeTemplate->PostPlacedNewNode();
NodeTemplate->AllocateDefaultPins();
NodeTemplate->AutowireNewNode(FromPin);
NodeTemplate->NodePosX = Location.X;
NodeTemplate->NodePosY = Location.Y;
NodeTemplate->ComboActionGraphNode->SetFlags(EObjectFlags::RF_Transactional);
NodeTemplate->SetFlags(EObjectFlags::RF_Transactional);
ResultNode = NodeTemplate;
}
return ResultNode;
}
void FAssetSchemaAction_ComboActionGraphSchema_NewNode::AddReferencedObjects(FReferenceCollector &Collector)
{
FEdGraphSchemaAction::AddReferencedObjects(Collector);
Collector.AddReferencedObject(NodeTemplate);
}
UEdGraphNode *FAssetSchemaAction_ComboActionGraphSchema_NewEdge::PerformAction(UEdGraph *ParentGraph, UEdGraphPin *FromPin, const FVector2D Location, bool bSelectNewNode)
{
UEdGraphNode *ResultNode = nullptr;
if (NodeTemplate != nullptr)
{
const FScopedTransaction Transaction(LOCTEXT("ComboActionGraphEditorNewEdge", "Combo Action Graph Editor: New Edge"));
ParentGraph->Modify();
if (FromPin != nullptr)
FromPin->Modify();
NodeTemplate->Rename(nullptr, ParentGraph);
ParentGraph->AddNode(NodeTemplate, true, bSelectNewNode);
NodeTemplate->CreateNewGuid();
NodeTemplate->PostPlacedNewNode();
NodeTemplate->AllocateDefaultPins();
NodeTemplate->AutowireNewNode(FromPin);
NodeTemplate->NodePosX = Location.X;
NodeTemplate->NodePosY = Location.Y;
NodeTemplate->ComboActionGraphEdge->SetFlags(EObjectFlags::RF_Transactional);
NodeTemplate->SetFlags(EObjectFlags::RF_Transactional);
ResultNode = NodeTemplate;
}
return ResultNode;
}
void FAssetSchemaAction_ComboActionGraphSchema_NewEdge::AddReferencedObjects(FReferenceCollector &Collector)
{
FEdGraphSchemaAction::AddReferencedObjects(Collector);
Collector.AddReferencedObject(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);
}
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;
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;
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

@ -0,0 +1,209 @@
// ©2023 Batty Bovine Productions, LLC. All Rights Reserved.
#include "Ed/EdComboActionGraph.h"
#include "Ed/EdComboActionGraphEdge.h"
#include "Ed/EdComboActionGraphNode.h"
#include "ComboActionGraph.h"
#include "Nodes/ComboActionGraphNode.h"
#include "Nodes/ComboActionGraphEdge.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();
Clear();
for (int i = 0; i < Nodes.Num(); ++i)
{
if (UEdComboActionGraphNode *EdNode = Cast<UEdComboActionGraphNode>(Nodes[i]))
{
if (EdNode->ComboActionGraphNode == nullptr)
continue;
UComboActionGraphNode *ComboActionGraphNode = EdNode->ComboActionGraphNode;
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->ChildrenNodes.Add(ChildNode);
ChildNode->ParentNodes.Add(ComboActionGraphNode);
}
else
{
UE_LOG(LogEdComboActionGraph, Error, TEXT("[RebuildComboActionGraph] Can't find child node"));
}
}
}
}
else if (UEdComboActionGraphEdge *EdgeNode = Cast<UEdComboActionGraphEdge>(Nodes[i]))
{
UEdComboActionGraphNode *StartNode = EdgeNode->GetStartNode();
UEdComboActionGraphNode *EndNode = EdgeNode->GetEndNode();
UComboActionGraphEdge *Edge = EdgeNode->ComboActionGraphEdge;
if (StartNode == nullptr || EndNode == nullptr || Edge == nullptr)
{
UE_LOG(LogEdComboActionGraph, Error, TEXT("[RebuildMounteaDialogueGraph] Add edge failed."));
continue;
}
EdgeMap.Add(Edge, EdgeNode);
Edge->Graph = Graph;
Edge->Rename(nullptr, Graph, REN_DontCreateRedirectors | REN_DoNotDirty);
Edge->StartNode = StartNode->ComboActionGraphNode;
Edge->EndNode = EndNode->ComboActionGraphNode;
Edge->StartNode->Edges.Add(Edge->EndNode, Edge);
}
}
for (int i = 0; i < Graph->AllNodes.Num(); ++i)
{
UComboActionGraphNode *Node = Graph->AllNodes[i];
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 = NodeMap[&L];
UEdComboActionGraphNode* EdNode_RNode = 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 (int32 i = 0; i < Nodes.Num(); ++i)
{
Nodes[i]->Modify();
}
return Rtn;
}
void UEdComboActionGraph::PostEditUndo()
{
NotifyGraphChanged();
Super::PostEditUndo();
}
//void UEdComboActionGraph::SetDialogueEditorPtr(TWeakPtr<FAssetEditor_MounteaDialogueGraph> NewPtr)
//{
// DialogueEditorPtr = NewPtr;
//}
//bool UEdComboActionGraph::JumpToNode(const UMounteaDialogueGraphNode* Node)
//{
// return FComboActionGraphEditorUtilities::OpenEditorAndJumpToGraphNode(DialogueEditorPtr, *NodeMap.Find(Node));
//}
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->ChildrenNodes.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->ChildrenNodes.Sort(Comp);
Node->ParentNodes.Sort(Comp);
for (int j = 0; j < Node->ChildrenNodes.Num(); ++j)
{
NextLevelNodes.Add(Node->ChildrenNodes[j]);
}
}
CurrLevelNodes = NextLevelNodes;
NextLevelNodes.Reset();
++Level;
}
}

View File

@ -0,0 +1,49 @@
// ©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 COMBOINPUT_API UEdComboActionGraph : public UEdGraph
{
GENERATED_BODY()
public:
virtual void RebuildComboActionGraph();
UComboActionGraph *GetComboActionGraph() const;
virtual bool Modify(bool bAlwaysMarkDirty) override;
virtual void PostEditUndo() override;
//TWeakPtr<FAssetEditor_ComboActionGraph> GetDialogueEditorPtr() const { return DialogueEditorPtr; }
//void SetDialogueEditorPtr(TWeakPtr<FAssetEditor_ComboActionGraph> NewPtr);
//void ResetDialogueEditorPtr() { DialogueEditorPtr.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 Dialogue editor that owns us */
//TWeakPtr<FAssetEditor_ComboActionGraph> DialogueEditorPtr;
};

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,169 @@
// ©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"
#include "Settings/FComboActionGraphEditorStyle.h"
#define LOCTEXT_NAMESPACE "UEdComboActionGraphNode"
DEFINE_LOG_CATEGORY(LogEdComboActionGraphNode);
UEdComboActionGraphNode::UEdComboActionGraphNode()
{
bCanRenameNode = true;
bAllowCopy = true;
bAllowDelete = true;
bAllowDuplicate = true;
bAllowPaste = true;
}
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();
}
}
}
FLinearColor UEdComboActionGraphNode::GetBackgroundColor() const
{
// Getting Node colour based on the Settings if any found, otherwise use this logic
if (UComboActionGraphEditorSettings *GraphEditorSettings = GetMutableDefault<UComboActionGraphEditorSettings>())
{
FLinearColor ReturnColour;
if (GraphEditorSettings->FindNodeBackgroundColourOverride(this->ComboActionGraphNode->GetClass(), ReturnColour))
{
return ReturnColour;
}
}
return this->ComboActionGraphNode ? this->ComboActionGraphNode->GetBackgroundColor() : FLinearColor::Black;
}
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", "Mountea Dialogue Node");
}
FSlateIcon UEdComboActionGraphNode::GetIconAndTint(FLinearColor& OutColor) const
{
static const FSlateIcon Icon = FSlateIcon(FComboActionGraphEditorStyle::GetAppStyleSetName(), "MDSStyleSet.Node.Icon.small");
OutColor = this->ComboActionGraphNode->GetBackgroundColor();
return Icon;
}
void UEdComboActionGraphNode::PostEditUndo()
{
Super::PostEditUndo();
}
void UEdComboActionGraphNode::PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent)
{
Super::PostEditChangeProperty(PropertyChangedEvent);
}
#undef LOCTEXT_NAMESPACE

View File

@ -0,0 +1,60 @@
// ©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();
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 FLinearColor GetBackgroundColor() const;
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;
virtual FSlateIcon GetIconAndTint(FLinearColor &OutColor) const override;
#if WITH_EDITOR
virtual void PostEditUndo() override;
virtual void PostEditChangeProperty(FPropertyChangedEvent &PropertyChangedEvent) override;
#endif
UPROPERTY(VisibleAnywhere, Instanced, Category="Combo Action Graph")
class UComboActionGraphNode *ComboActionGraphNode;
SEdComboActionGraphNode *SEdNode;
private:
bool bAllowCopy;
bool bAllowDelete;
bool bAllowDuplicate;
bool bAllowPaste;
};

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,89 @@
// ©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;
virtual const FSlateBrush *GetInheritsImageBrush() const;
virtual FSlateColor GetInheritsImageTint() 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;
bool HasGraphDecorators() const;
bool HasNodeDecorators() const;
virtual FText GetDecoratorsText() const;
virtual FText GetNumberOfDecorators() const;
virtual FText GetDecoratorsInheritanceText() const;
EVisibility ShowImplementsOnlySlot_Unified() const;
EVisibility ShowInheritsDecoratorsSlot_Unified() const;
EVisibility ShowImplementsOnlySlot_Stack() const;
EVisibility ShowInheritsDecoratorsSlot_Stack() const;
EVisibility ShowAllDecorators() const;
EVisibility ShowDecoratorsBottomPadding() const;
FSlateColor GetImplementsRowColor() const;
FSlateColor GetBulletPointsImagePointColor() const;
virtual EComboActionDecoratorsInfoStyle GetDecoratorsStyle() const;
EVisibility GetStackVisibility() const;
EVisibility GetUnifiedVisibility() const;
FText GetTooltipText() const;
protected:
TSharedPtr<SBorder> NodeBody;
TSharedPtr<SHorizontalBox> OutputPinBox;
class UComboActionGraphEditorSettings *GraphEditorSettings = nullptr;
FLinearColor NodeInnerColor;
FLinearColor PinsDockColor;
};

View File

@ -0,0 +1,36 @@
// ©2023 Batty Bovine Productions, LLC. All Rights Reserved.
#include "SEdComboActionGraphNodeIndex.h"
#include "Settings/FComboActionGraphEditorStyle.h"
void SEdComboActionGraphNodeIndex::Construct(const FArguments &InArgs)
{
const FSlateBrush* CircleBrush = FComboActionGraphEditorStyle::GetBrush(TEXT("MDSStyleSet.Node.IndexCircle"));
ChildSlot
[
SNew(SOverlay)
+SOverlay::Slot()
.HAlign(HAlign_Fill)
.VAlign(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(HAlign_Fill)
.VAlign(VAlign_Fill)
[
SNew(SBorder)
.BorderImage(CircleBrush)
.BorderBackgroundColor(this, &SEdComboActionGraphNodeIndex::GetBackgroundColor)
.Padding(FMargin(4.0f))
.VAlign(VAlign_Center)
.HAlign(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,208 @@
// ©2023 Batty Bovine Productions, LLC. All Rights Reserved.
#include "FConnectionDrawingPolicy_ComboActionGraph.h"
#include "Ed/EdComboActionGraphEdge.h"
#include "Ed/EdComboActionGraphNode.h"
#include "Settings/ComboActionGraphEditorSettings.h"
#include "Settings/FComboActionGraphEditorStyle.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>())
{
switch (GraphEditorSettings->GetArrowType())
{
case EComboActionArrowType::SimpleArrow:
ArrowImage = FComboActionGraphEditorStyle::GetBrush(TEXT("MDSStyleSet.Graph.SimpleArrow"));
break;
case EComboActionArrowType::HollowArrow:
ArrowImage = FComboActionGraphEditorStyle::GetBrush(TEXT("MDSStyleSet.Graph.HollowArrow"));
break;
case EComboActionArrowType::FancyArrow:
ArrowImage = FComboActionGraphEditorStyle::GetBrush(TEXT("MDSStyleSet.Graph.FancyArrow"));
break;
case EComboActionArrowType::Bubble:
ArrowImage = FComboActionGraphEditorStyle::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,86 @@
// ©2023 Batty Bovine Productions, LLC. All Rights Reserved.
#pragma once
#include "CoreMinimal.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 (DialogueNodeBase->GetDataTable())
{
FString ReturnString;
DialogueNodeBase->GetRowName().ToString(ReturnString);
return FText::FromString(ReturnString);
}
}
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,40 @@
// ©2023 Batty Bovine Productions, LLC. All Rights Reserved.
#include "ComboActionGraphEditorSettings.h"
#define LOCTEXT_NAMESPACE "ComboActionGraphEditorSettings"
UComboActionGraphEditorSettings::UComboActionGraphEditorSettings()
{
NodeType = EComboActionNodeType::SoftCorners;
NodeTheme = EComboActionNodeTheme::DarkTheme;
ArrowType = EComboActionArrowType::HollowArrow;
CategoryName = TEXT("Combo Input");
SectionName = TEXT("Combo Input (Editor)");
AutoLayoutStrategy = EComboActionAutoLayoutStrategyType::Tree;
bFirstPassOnly = false;
bRandomInit = false;
OptimalDistance = 100.f;
MaxIteration = 50;
InitTemperature = 10.f;
CoolDownRate = 10.f;
WireWidth = 0.8f;
//WireStyle = EWiringStyle::EWS_Simple;
//BubbleDrawRule = EBubbleDrawRule::EBDR_OnSelected;
bAllowRenameNodes = true;
bDisplayAutomaticNames = false;
bShowDetailedInfo_InheritsDecorators = true;
bShowDetailedInfo_NumDecorators = true;
DecoratorsInfoStyle = EComboActionDecoratorsInfoStyle::Unified;
bAllowNativeDecoratorsEdit = false;
}
#undef LOCTEXT_NAMESPACE

View File

@ -0,0 +1,292 @@
// ©2023 Batty Bovine Productions, LLC. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "Engine/DeveloperSettings.h"
#include "ComboActionGraphEditorSettings.generated.h"
#pragma region Enums
UENUM(BlueprintType)
enum class EComboActionAutoLayoutStrategyType : uint8
{
Tree UMETA(DisplayName="Tree"),
ForceDirected UMETA(DisplayName="Force Directed"),
Default UMETA(Hidden)
};
UENUM(BlueprintType)
enum class EComboActionWiringStyle : uint8
{
Vanilla UMETA(DisplayName="Vanilla"),
Simple UMETA(DisplayName="90° Angle"),
Complex UMETA(DisplayName="45° Angle"),
Default UMETA(Hidden)
};
UENUM(BlueprintType)
enum class EComboActionBubbleDrawRule : uint8
{
Always UMETA(DisplayName="Always"),
OnSelected UMETA(DisplayName="When Selected")
};
UENUM(BlueprintType)
enum class EComboActionNodeTheme : uint8
{
DarkTheme UMETA(DisplayName="Dark Theme"),
LightTheme UMETA(DisplayName="Light Theme")
};
UENUM(BlueprintType)
enum class EComboActionDecoratorsInfoStyle : uint8
{
Stack UMETA(DisplayName="Stack"),
Unified UMETA(DisplayName="Unified")
};
UENUM(BlueprintType)
enum class EComboActionNodeType : uint8
{
SoftCorners UMETA(DisplayName="Soft Corners"),
HardCorners UMETA(DisplayName="Hard Corners")
};
UENUM(BlueprintType)
enum class EComboActionArrowType : uint8
{
SimpleArrow UMETA(DisplayName="Simple Arrow"),
HollowArrow UMETA(DisplayName="Hollow Arrow"),
FancyArrow UMETA(DisplayName="Fancy Arrow"),
Bubble UMETA(DisplayName="Bubble"),
None UMETA(DisplayName="Nothing")
};
#pragma endregion
/**
* Combo Action Graph global settings.
*/
UCLASS(config = MounteaSettings)
class COMBOINPUT_API UComboActionGraphEditorSettings : public UDeveloperSettings
{
GENERATED_BODY()
public:
UComboActionGraphEditorSettings();
private:
#pragma region GraphNodes
UPROPERTY(config, EditDefaultsOnly, Category = "NodesSettings")
EComboActionNodeType NodeType;
UPROPERTY(config, EditDefaultsOnly, Category = "NodesSettings")
EComboActionNodeTheme NodeTheme;
UPROPERTY(config, EditDefaultsOnly, Category = "NodesSettings|DecoratorsInfo")
uint8 bShowDetailedInfo_NumDecorators : 1;
UPROPERTY(config, EditDefaultsOnly, Category = "NodesSettings|DecoratorsInfo")
uint8 bShowDetailedInfo_InheritsDecorators : 1;
UPROPERTY(config, EditDefaultsOnly, Category = "NodesSettings")
EComboActionDecoratorsInfoStyle DecoratorsInfoStyle;
UPROPERTY(config, EditDefaultsOnly, Category = "NodesSettings")
bool bDisplayAutomaticNames;
UPROPERTY(config, EditDefaultsOnly, Category = "NodesSettings")
bool bAllowRenameNodes;
/**
* Select a Node Class and specify Override Colour for this Node type.
* Only non-abstract classes are allowed!
*/
UPROPERTY(config, EditDefaultsOnly, Category = "NodesSettings", meta=(ShowTreeView))
TMap<TSoftClassPtr<class UComboActionGraphNode>, FLinearColor> OverrideNodeBackgroundColours;
#pragma endregion
#pragma region GraphDecorators
// Enables 'Edit' button for Native Code Decorators
UPROPERTY(config, EditDefaultsOnly, Category = "NodesSettings")
bool bAllowNativeDecoratorsEdit;
#pragma endregion
#pragma region GraphWiring
UPROPERTY(config, EditDefaultsOnly, Category = "NodeWiring", meta=(UIMin=0.1f, ClampMin=0.1f, UIMax=1.5f, ClampMax=1.5f))
float WireWidth;
UPROPERTY(config, EditDefaultsOnly, Category = "NodeWiring") //, meta=(ConfigRestartRequired=true))
EComboActionArrowType ArrowType;
/* Advanced Wiring doesn't work now
UPROPERTY(config, EditDefaultsOnly, Category = "NodeWiring", AdvancedDisplay, meta=(ToolTip="Work in Progress!"))
bool bUseAdvancedWiring;
UPROPERTY(config, EditDefaultsOnly, Category = "NodeWiring", AdvancedDisplay, meta=(ToolTip="Work in Progress!", EditCondition="bUseAdvancedWiring"))
EWiringStyle WireStyle;
UPROPERTY(config, EditDefaultsOnly, Category = "NodeWiring", AdvancedDisplay, meta=(ToolTip="Work in Progress!", EditCondition="bUseAdvancedWiring"))
uint32 HorizontalOffset = 16;
UPROPERTY(config, EditDefaultsOnly, Category = "NodeWiring", AdvancedDisplay, meta=(ToolTip="Work in Progress!", EditCondition="bUseAdvancedWiring"))
EBubbleDrawRule BubbleDrawRule;
UPROPERTY(config, EditDefaultsOnly, Category = "NodeWiring", AdvancedDisplay, meta=(ToolTip="Work in Progress!", EditCondition="bUseAdvancedWiring"))
int32 BubbleZoomThreshold;
// Space between bubbles on the wires. Default: 20.0
UPROPERTY(config, EditDefaultsOnly, Category = "NodeWiring", AdvancedDisplay, meta = (ClampMin = "10.0", ToolTip="Work in Progress!", EditCondition="bUseAdvancedWiring"))
float BubbleSpace = 20.0f;
UPROPERTY(config, EditDefaultsOnly, Category = "NodeWiring", AdvancedDisplay, meta = (ClampMin = "10.0", ToolTip="Work in Progress!", EditCondition="bUseAdvancedWiring"))
float BubbleSize = 2.0f;
UPROPERTY(config, EditDefaultsOnly, Category = "NodeWiring", AdvancedDisplay, meta = (ClampMin = "10.0", ToolTip="Work in Progress!", EditCondition="bUseAdvancedWiring"))
float BubbleSpeed = 2.0f;
// Disable the offset for pins. Default: false
UPROPERTY(config, EditDefaultsOnly, Category = "NodeWiring", AdvancedDisplay, meta=(ToolTip="Work in Progress!", EditCondition="bUseAdvancedWiring"))
bool DisablePinOffset = false;
// Fix default zoomed-out wire displacement. Default: true
UPROPERTY(config, EditDefaultsOnly, Category = "NodeWiring", AdvancedDisplay, meta=(ToolTip="Work in Progress!", EditCondition="bUseAdvancedWiring"))
bool FixZoomDisplacement = true;
*/
#pragma endregion
#pragma region GraphArrange
UPROPERTY(config, EditDefaultsOnly, Category = "AutoArrange")
float OptimalDistance;
UPROPERTY(config, EditDefaultsOnly, AdvancedDisplay, Category = "AutoArrange")
EComboActionAutoLayoutStrategyType AutoLayoutStrategy;
UPROPERTY(config, EditDefaultsOnly, AdvancedDisplay, Category = "AutoArrange")
int32 MaxIteration;
UPROPERTY(config, EditDefaultsOnly, AdvancedDisplay, Category = "AutoArrange")
bool bFirstPassOnly;
UPROPERTY(config, EditDefaultsOnly, AdvancedDisplay, Category = "AutoArrange")
bool bRandomInit;
UPROPERTY(config, EditDefaultsOnly, AdvancedDisplay, Category = "AutoArrange")
float InitTemperature;
UPROPERTY(config, EditDefaultsOnly, AdvancedDisplay, Category = "AutoArrange")
float CoolDownRate;
#pragma endregion
#if WITH_EDITOR
virtual FText GetSectionText() const override
{
return NSLOCTEXT("ComboActionGraphEditorSystem", "ComboActionGraphEditorSection", "Combo Action Graph (Editor)");
}
virtual FText GetSectionDescription() const override
{
return NSLOCTEXT("ComboActionGraphEditorSystem", "ComboActionGraphEditorDescription", "Default values for Mountea Plugins (Editor).");
}
virtual FName GetContainerName() const override
{
return "Project";
}
#endif
public:
#pragma region GraphNodes_Getters
EComboActionNodeTheme GetNodeTheme() const { return NodeTheme; }
EComboActionNodeType GetNodeType() const { return NodeType; }
bool ShowDetailedInfo_NumDecorators() const { return bShowDetailedInfo_NumDecorators; }
bool ShowDetailedInfo_InheritsDecorators() const { return bShowDetailedInfo_InheritsDecorators; }
bool ShowAutomaticNames() const { return bDisplayAutomaticNames; }
EComboActionDecoratorsInfoStyle GetDecoratorsStyle() const { return DecoratorsInfoStyle; }
bool AllowRenameNodes() const { return bAllowRenameNodes; }
bool FindNodeBackgroundColourOverride(const TSoftClassPtr<class UComboActionGraphNode> NodeClass, FLinearColor& BackgroundColour)
{
if (OverrideNodeBackgroundColours.Contains(NodeClass))
{
BackgroundColour = *OverrideNodeBackgroundColours.Find(NodeClass);
return true;
}
return false;
}
#pragma endregion
#pragma region GraphDecorators_Getters
bool IsNativeDecoratorsEditAllowed() const { return bAllowNativeDecoratorsEdit; }
#pragma endregion
#pragma region GraphWiring_Getters
float GetWireWidth() const { return this->WireWidth; }
EComboActionArrowType GetArrowType() const { return this->ArrowType; }
/*
bool AllowAdvancedWiring() const
{ return bUseAdvancedWiring; };
EWiringStyle GetWireStyle() const
{ return WireStyle; };
int32 GetHorizontalOffset() const
{ return HorizontalOffset; };
EBubbleDrawRule GetBubbleDrawRule() const
{ return BubbleDrawRule; };
int32 GetBubbleZoomThreshold() const
{ return BubbleZoomThreshold; };
float GetBubbleSpace() const
{ return BubbleSpace; };
float GetBubbleSpeed() const
{ return BubbleSpeed; };
float GetBubbleSize() const
{ return BubbleSize; };
*/
#pragma endregion
#pragma region GraphArrange_Getters
EComboActionAutoLayoutStrategyType GetAutoLayoutStrategy() const { return this->AutoLayoutStrategy; }
float GetOptimalDistance() const { return this->OptimalDistance; }
int32 GetMaxIteration() const { return this->MaxIteration; }
bool IsFirstPassOnly() const { return this->bFirstPassOnly; }
bool IsRandomInit() const { return this->bRandomInit; }
float GetInitTemperature() const { return this->InitTemperature; }
float GetCoolDownRate() const { return this->CoolDownRate; }
#pragma endregion
};

View File

@ -0,0 +1,138 @@
// Copyright Dominik Pavlicek 2023. All Rights Reserved.
#include "FComboActionGraphEditorStyle.h"
#include "Interfaces/IPluginManager.h"
#include "Misc/Paths.h"
#include "Styling/SlateStyleRegistry.h"
#define IMAGE_BRUSH(RelativePath, ...) FSlateImageBrush(StyleSet->RootToContentDir(RelativePath, TEXT(".png")), __VA_ARGS__)
#define BOX_BRUSH(RelativePath, ...) FSlateBoxBrush(StyleSet->RootToContentDir(RelativePath, TEXT(".png")), __VA_ARGS__)
#define DEFAULT_FONT(...) FCoreStyle::GetDefaultFontStyle(__VA_ARGS__)
TSharedPtr<FSlateStyleSet> FComboActionGraphEditorStyle::StyleSet = nullptr;
void FComboActionGraphEditorStyle::Initialize()
{
if (!StyleSet.IsValid() )
{
Create();
FSlateStyleRegistry::RegisterSlateStyle(*StyleSet.Get());
}
}
void FComboActionGraphEditorStyle::Shutdown()
{
FSlateStyleRegistry::UnRegisterSlateStyle(*StyleSet.Get());
ensure(StyleSet.IsUnique());
StyleSet.Reset();
}
void FComboActionGraphEditorStyle::Create()
{
const FVector2D Icon12x12(12.0f, 12.0f);
const FVector2D Icon16x16(16.0f, 16.0f);
const FVector2D Icon40x40(40.0f, 40.0f);
const FVector2D Icon64x64(64.0f, 64.0f);
const FVector2D Icon128x128(128.f, 128.f);
const FVector2D Icon200x70(200.f, 70.f);
StyleSet = MakeShareable(new FSlateStyleSet(GetAppStyleSetName()));
StyleSet->SetContentRoot(IPluginManager::Get().FindPlugin("ComboInput")->GetBaseDir() / TEXT("Resources"));
StyleSet->SetCoreContentRoot(IPluginManager::Get().FindPlugin("ComboInput")->GetBaseDir() / TEXT("Content"));
StyleSet->Set("MDSStyleSet.AutoArrange.small", new IMAGE_BRUSH(TEXT("AutoArrangeIcon"), Icon16x16));
StyleSet->Set("MDSStyleSet.AutoArrange", new IMAGE_BRUSH(TEXT("AutoArrangeIcon"), Icon40x40));
StyleSet->Set("MDSStyleSet.AutoArrange.large", new IMAGE_BRUSH(TEXT("AutoArrangeIcon"), Icon64x64));
StyleSet->Set("MDSStyleSet.GraphSettings.small", new IMAGE_BRUSH(TEXT("GraphSettings"), Icon16x16));
StyleSet->Set("MDSStyleSet.GraphSettings", new IMAGE_BRUSH(TEXT("GraphSettings"), Icon40x40));
StyleSet->Set("MDSStyleSet.GraphSettings.large", new IMAGE_BRUSH(TEXT("GraphSettings"), Icon64x64));
StyleSet->Set("MDSStyleSet.ValidateGraph.small", new IMAGE_BRUSH(TEXT("ValidateGraph"), Icon16x16));
StyleSet->Set("MDSStyleSet.ValidateGraph", new IMAGE_BRUSH(TEXT("ValidateGraph"), Icon40x40));
StyleSet->Set("MDSStyleSet.ValidateGraph.large", new IMAGE_BRUSH(TEXT("ValidateGraph"), Icon64x64));
StyleSet->Set("MDSStyleSet.Graph.NodeOverlay", new BOX_BRUSH( TEXT("NodeOverlay"), FMargin(8.0f/64.0f, 3.0f/32.0f, 0, 0) ));
StyleSet->Set("MDSStyleSet.Graph.PinDocksOverlay", new BOX_BRUSH( TEXT("PinDocksOverlay"), FMargin(8.0f/64.0f, 3.0f/32.0f, 0, 0) ));
StyleSet->Set("MDSStyleSet.Graph.SimpleArrow", new IMAGE_BRUSH(TEXT("SimpleArrow"), Icon16x16));
StyleSet->Set("MDSStyleSet.Graph.HollowArrow", new IMAGE_BRUSH(TEXT("HollowArrow"), Icon16x16));
StyleSet->Set("MDSStyleSet.Graph.FancyArrow", new IMAGE_BRUSH(TEXT("FancyArrow"), Icon16x16));
StyleSet->Set("MDSStyleSet.Graph.Bubble", new IMAGE_BRUSH(TEXT("Bubble"), Icon16x16));
StyleSet->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) ));
StyleSet->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) ));
StyleSet->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) ));
StyleSet->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) ));
StyleSet->Set("MDSStyleSet.Node.IndexCircle", new IMAGE_BRUSH(TEXT("IndexIcon"), Icon16x16));
StyleSet->Set("MDSStyleSet.Icon.OK", new IMAGE_BRUSH(TEXT("OKIcon"), Icon16x16));
StyleSet->Set("MDSStyleSet.Icon.Error", new IMAGE_BRUSH(TEXT("ErroIcon"), Icon16x16));
StyleSet->Set("MDSStyleSet.Icon.BulletPoint", new IMAGE_BRUSH(TEXT("CircleBox"), Icon16x16));
StyleSet->Set("MDSStyleSet.Graph.CornerImage", new IMAGE_BRUSH(TEXT("Icon128"), Icon128x128));
StyleSet->Set("MDSStyleSet.Icon.Browse", new IMAGE_BRUSH(TEXT("BrowseIcon"), Icon12x12));
StyleSet->Set("MDSStyleSet.Icon.Edit", new IMAGE_BRUSH(TEXT("EditIcon"), Icon12x12));
StyleSet->Set("MDSStyleSet.Buttons.Documentation", new IMAGE_BRUSH(TEXT("Documentation"), Icon200x70));
StyleSet->Set("MDSStyleSet.Buttons.Documentation.small", new IMAGE_BRUSH(TEXT("DocumentationIcon"), Icon12x12));
StyleSet->Set("MDSStyleSet.Node.Icon.large", new IMAGE_BRUSH(TEXT("DialogueNodeIcon"), Icon64x64));
StyleSet->Set("MDSStyleSet.Node.Icon", new IMAGE_BRUSH(TEXT("DialogueNodeIcon"), Icon16x16));
StyleSet->Set("MDSStyleSet.Node.Icon.small", new IMAGE_BRUSH(TEXT("DialogueNodeIcon"), Icon12x12));
StyleSet->Set("MDSStyleSet.Icon.Close", new IMAGE_BRUSH(TEXT("CloseIcon"), Icon12x12));
StyleSet->Set("MDSStyleSet.Icon.SupportDiscord", new IMAGE_BRUSH(TEXT("DiscordIcon"), Icon12x12));
StyleSet->Set("MDSStyleSet.Icon.HeartIcon", new IMAGE_BRUSH(TEXT("HeartIcon"), Icon12x12));
StyleSet->Set("MDSStyleSet.Icon.UBIcon", new IMAGE_BRUSH(TEXT("UnrealBucketIcon"), Icon12x12));
StyleSet->Set("MDSStyleSet.Icon.MoneyIcon", new IMAGE_BRUSH(TEXT("MoneyIcon"), Icon12x12));
const FButtonStyle MounteaButtonStyle = 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)));
StyleSet->Set("MDSStyleSet.Buttons.Style", MounteaButtonStyle);
{
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) );
StyleSet->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 );
StyleSet->Set( "MDSStyleSet.NodeTitleEditableText", NodeTitleEditableText );
StyleSet->Set( "MDSStyleSet.NodeTitleInlineEditableText", FInlineEditableTextBlockStyle()
.SetTextStyle(NodeTitle)
.SetEditableTextBoxStyle(NodeTitleEditableText)
);
}
}
#undef DEFAULT_FONT
#undef BOX_BRUSH
#undef IMAGE_BRUSH

View File

@ -0,0 +1,40 @@
// Copyright Dominik Pavlicek 2023. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "Styling/SlateStyle.h"
class FComboActionGraphEditorStyle
{
public:
static void Create();
static void Initialize();
static void Shutdown();
static ISlateStyle &Get()
{
return *(StyleSet.Get());
}
static const FSlateBrush *GetBrush(FName PropertyName, const ANSICHAR *Specifier = NULL)
{
return StyleSet->GetBrush(PropertyName, Specifier);
};
static const FName &GetAppStyleSetName()
{
static FName StyleSetName(TEXT("ComboActionGraphEditorStyle"));
return StyleSetName;
};
template< class T >
static const T &GetWidgetStyle( FName PropertyName, const ANSICHAR *Specifier = NULL )
{
return StyleSet->GetWidgetStyle< T >( PropertyName, Specifier );
}
private:
static TSharedPtr<FSlateStyleSet> StyleSet;
};