Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
149 changes: 149 additions & 0 deletions Source/Flow/Private/AddOns/FlowNodeAddOn_SwitchCase.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
// Copyright https://github.com/MothCocoon/FlowGraph/graphs/contributors

#include "AddOns/FlowNodeAddOn_SwitchCase.h"
#include "AddOns/FlowNodeAddOn_PredicateAND.h"
#include "AddOns/FlowNodeAddOn_PredicateOR.h"
#include "FlowSettings.h"
#include "Nodes/FlowNode.h"

#include UE_INLINE_GENERATED_CPP_BY_NAME(FlowNodeAddOn_SwitchCase)

#define LOCTEXT_NAMESPACE "FlowNodeAddOn_SwitchCase"

const FName UFlowNodeAddOn_SwitchCase::DefaultCaseName = "Case";

UFlowNodeAddOn_SwitchCase::UFlowNodeAddOn_SwitchCase()
: Super()
, CaseName(DefaultCaseName)
, OutputPinName(DefaultCaseName)
{
#if WITH_EDITOR
NodeDisplayStyle = FlowNodeStyle::AddOn_SwitchCase;
Category = TEXT("Switch");
#endif
}

#if WITH_EDITOR

void UFlowNodeAddOn_SwitchCase::PostEditChangeProperty(struct FPropertyChangedEvent& PropertyChangedEvent)
{
Super::PostEditChangeProperty(PropertyChangedEvent);

if (PropertyChangedEvent.GetMemberPropertyName() == GET_MEMBER_NAME_CHECKED(ThisClass, CaseName))
{
if (CaseName.IsNone())
{
CaseName = DefaultCaseName;
}

RequestReconstructionOnOwningFlowNode();
}
}

TArray<FFlowPin> UFlowNodeAddOn_SwitchCase::GetContextOutputs() const
{
const UFlowNode* FlowNodeOwner = GetFlowNode();
check(IsValid(FlowNodeOwner));

int32 DuplicateCount = 0;
int32 DuplicateCountForThis = 0;
int32 ThisIndex = INDEX_NONE;

const TArray<UFlowNodeAddOn*>& OwnerAddOns = FlowNodeOwner->GetFlowNodeAddOnChildren();
for (int32 Index = 0; Index < OwnerAddOns.Num(); ++Index)
{
const UFlowNodeAddOn_SwitchCase* SwitchCaseAddOn = Cast<UFlowNodeAddOn_SwitchCase>(OwnerAddOns[Index]);
if (!IsValid(SwitchCaseAddOn))
{
continue;
}

const bool bIsThisAddOn = SwitchCaseAddOn == this;

if (CaseName == SwitchCaseAddOn->CaseName)
{
++DuplicateCount;
}

if (bIsThisAddOn)
{
ThisIndex = Index;

DuplicateCountForThis = DuplicateCount;
}
}

check(ThisIndex != INDEX_NONE);

if (DuplicateCount > 1)
{
OutputPinName = FName(FString::Printf(TEXT("%s (%d)"), *CaseName.ToString(), DuplicateCountForThis));
}
else
{
OutputPinName = FName(FString::Printf(TEXT("%s"), *CaseName.ToString()));
}

return { FFlowPin(OutputPinName) };
}
#endif

EFlowAddOnAcceptResult UFlowNodeAddOn_SwitchCase::AcceptFlowNodeAddOnChild_Implementation(
const UFlowNodeAddOn* AddOnTemplate,
const TArray<UFlowNodeAddOn*>& AdditionalAddOnsToAssumeAreChildren) const
{
if (IFlowPredicateInterface::ImplementsInterfaceSafe(AddOnTemplate))
{
return EFlowAddOnAcceptResult::TentativeAccept;
}
else
{
// All AddOn children MUST implement IFlowPredicateInterface
// (so do not return Super's implementation which will return Undetermined)
return EFlowAddOnAcceptResult::Reject;
}
}

bool UFlowNodeAddOn_SwitchCase::TryTriggerForCase_Implementation() const
{
bool bResult = false;
FLOW_ASSERT_ENUM_MAX(EFlowPredicateCombinationRule, 2);
if (BranchCombinationRule == EFlowPredicateCombinationRule::AND)
{
bResult = UFlowNodeAddOn_PredicateAND::EvaluatePredicateAND(AddOns);
}
else
{
check(BranchCombinationRule == EFlowPredicateCombinationRule::OR);

bResult = UFlowNodeAddOn_PredicateOR::EvaluatePredicateOR(AddOns);
}

if (bResult)
{
constexpr bool bFinish = true;
GetFlowNode()->TriggerOutput(OutputPinName, bFinish);
}

return bResult;
}

FText UFlowNodeAddOn_SwitchCase::K2_GetNodeTitle_Implementation() const
{
if (UFlowSettings::Get()->bUseAdaptiveNodeTitles)
{
FLOW_ASSERT_ENUM_MAX(EFlowPredicateCombinationRule, 2);
if (BranchCombinationRule != EFlowPredicateCombinationRule::AND)
{
return FText::Format(LOCTEXT("SwitchCaseTitle", "{0} ({1})"), { FText::FromName(OutputPinName), UEnum::GetDisplayValueAsText(BranchCombinationRule) });
}
else
{
return FText::Format(LOCTEXT("SwitchCaseTitle", "{0}"), { FText::FromName(OutputPinName) });
}
}

return Super::K2_GetNodeTitle_Implementation();
}

#undef LOCTEXT_NAMESPACE
1 change: 1 addition & 0 deletions Source/Flow/Private/FlowTags.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,4 @@ UE_DEFINE_GAMEPLAY_TAG(FlowNodeStyle::AddOn, "Flow.NodeStyle.AddOn");
UE_DEFINE_GAMEPLAY_TAG(FlowNodeStyle::AddOn_PerSpawnedActor, "Flow.NodeStyle.AddOn.PerSpawnedActor");
UE_DEFINE_GAMEPLAY_TAG(FlowNodeStyle::AddOn_Predicate, "Flow.NodeStyle.AddOn.Predicate");
UE_DEFINE_GAMEPLAY_TAG(FlowNodeStyle::AddOn_Predicate_Composite, "Flow.NodeStyle.AddOn.Predicate.Composite");
UE_DEFINE_GAMEPLAY_TAG(FlowNodeStyle::AddOn_SwitchCase, "Flow.NodeStyle.AddOn.SwitchCase");
20 changes: 20 additions & 0 deletions Source/Flow/Private/Interfaces/FlowSwitchCaseInterface.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// Copyright https://github.com/MothCocoon/FlowGraph/graphs/contributors

#include "Interfaces/FlowSwitchCaseInterface.h"
#include "AddOns/FlowNodeAddOn.h"

bool IFlowSwitchCaseInterface::ImplementsInterfaceSafe(const UFlowNodeAddOn* AddOnTemplate)
{
if (!IsValid(AddOnTemplate))
{
return false;
}

UClass* AddOnClass = AddOnTemplate->GetClass();
if (AddOnClass->ImplementsInterface(UFlowSwitchCaseInterface::StaticClass()))
{
return true;
}

return false;
}
45 changes: 43 additions & 2 deletions Source/Flow/Private/Nodes/Route/FlowNode_Branch.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,13 @@

#include "Nodes/Route/FlowNode_Branch.h"
#include "AddOns/FlowNodeAddOn_PredicateAND.h"
#include "AddOns/FlowNodeAddOn_PredicateOR.h"
#include "FlowSettings.h"

#include UE_INLINE_GENERATED_CPP_BY_NAME(FlowNode_Branch)

#define LOCTEXT_NAMESPACE "FlowNode_Branch"

const FName UFlowNode_Branch::INPIN_Evaluate = TEXT("Evaluate");
const FName UFlowNode_Branch::OUTPIN_True = TEXT("True");
const FName UFlowNode_Branch::OUTPIN_False = TEXT("False");
Expand Down Expand Up @@ -38,6 +42,43 @@ EFlowAddOnAcceptResult UFlowNode_Branch::AcceptFlowNodeAddOnChild_Implementation

void UFlowNode_Branch::ExecuteInput(const FName& PinName)
{
const bool bResult = UFlowNodeAddOn_PredicateAND::EvaluatePredicateAND(AddOns);
TriggerOutput(bResult ? OUTPIN_True : OUTPIN_False, true);
bool bPassedRootPredicates = false;
FName ResultPinName = OUTPIN_False;

// Test the root-level IFlowPredicateInterface addons
FLOW_ASSERT_ENUM_MAX(EFlowPredicateCombinationRule, 2);
if (BranchCombinationRule == EFlowPredicateCombinationRule::AND)
{
bPassedRootPredicates = UFlowNodeAddOn_PredicateAND::EvaluatePredicateAND(AddOns);
}
else
{
check(BranchCombinationRule == EFlowPredicateCombinationRule::OR);

bPassedRootPredicates = UFlowNodeAddOn_PredicateOR::EvaluatePredicateOR(AddOns);
}

constexpr bool bFinish = true;
if (bPassedRootPredicates)
{
TriggerOutput(OUTPIN_True, bFinish);
}
else
{
TriggerOutput(OUTPIN_False, bFinish);
}
}

FText UFlowNode_Branch::K2_GetNodeTitle_Implementation() const
{
FLOW_ASSERT_ENUM_MAX(EFlowPredicateCombinationRule, 2);
if (BranchCombinationRule != EFlowPredicateCombinationRule::AND &&
GetDefault<UFlowSettings>()->bUseAdaptiveNodeTitles)
{
return FText::Format(LOCTEXT("BranchTitle", "{0} ({1})"), { Super::K2_GetNodeTitle_Implementation(), UEnum::GetDisplayValueAsText(BranchCombinationRule) });
}

return Super::K2_GetNodeTitle_Implementation();
}

#undef LOCTEXT_NAMESPACE
84 changes: 84 additions & 0 deletions Source/Flow/Private/Nodes/Route/FlowNode_Switch.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
// Copyright https://github.com/MothCocoon/FlowGraph/graphs/contributors

#include "Nodes/Route/FlowNode_Switch.h"
#include "AddOns/FlowNodeAddOn.h"
#include "FlowSettings.h"
#include "Interfaces/FlowSwitchCaseInterface.h"

#include UE_INLINE_GENERATED_CPP_BY_NAME(FlowNode_Switch)

#define LOCTEXT_NAMESPACE "FlowNode_Switch"

const FName UFlowNode_Switch::INPIN_Evaluate = TEXT("Evaluate");
const FName UFlowNode_Switch::OUTPIN_DefaultCase = TEXT("None Passed");

UFlowNode_Switch::UFlowNode_Switch(const FObjectInitializer& ObjectInitializer)
: Super(ObjectInitializer)
{
#if WITH_EDITOR
Category = TEXT("Route|Logic");
NodeDisplayStyle = FlowNodeStyle::Logic;
#endif

InputPins.Reset();
InputPins.Add(FFlowPin(INPIN_Evaluate));

OutputPins.Reset();
OutputPins.Add(FFlowPin(OUTPIN_DefaultCase.ToString(), FString(TEXT("Triggered when no cases pass (during a Switch Evaluate)"))));

AllowedSignalModes = {EFlowSignalMode::Enabled, EFlowSignalMode::Disabled};
}

EFlowAddOnAcceptResult UFlowNode_Switch::AcceptFlowNodeAddOnChild_Implementation(const UFlowNodeAddOn* AddOnTemplate, const TArray<UFlowNodeAddOn*>& AdditionalAddOnsToAssumeAreChildren) const
{
if (IFlowSwitchCaseInterface::ImplementsInterfaceSafe(AddOnTemplate))
{
return EFlowAddOnAcceptResult::TentativeAccept;
}

return Super::AcceptFlowNodeAddOnChild_Implementation(AddOnTemplate, AdditionalAddOnsToAssumeAreChildren);
}

void UFlowNode_Switch::ExecuteInput(const FName& PinName)
{
int32 TriggeringCaseCount = 0;

// Trigger the IFlowSwitchCaseInterface addons that pass
const EFlowForEachAddOnFunctionReturnValue SwitchCaseResult =
ForEachAddOnForClassConst<UFlowSwitchCaseInterface>(
[&TriggeringCaseCount, this](const UFlowNodeAddOn& SwitchCaseAddOn)
{
const IFlowSwitchCaseInterface* SwitchCaseInterface = CastChecked<IFlowSwitchCaseInterface>(&SwitchCaseAddOn);

if (IFlowSwitchCaseInterface::Execute_TryTriggerForCase(&SwitchCaseAddOn))
{
++TriggeringCaseCount;

if (bOnlyTriggerFirstPassingCase)
{
return EFlowForEachAddOnFunctionReturnValue::BreakWithSuccess;
}
}

return EFlowForEachAddOnFunctionReturnValue::Continue;
});

if (TriggeringCaseCount == 0)
{
// Trigger the default case if none of the cases passed
constexpr bool bFinish = true;
TriggerOutput(OUTPIN_DefaultCase, bFinish);
}
}

FText UFlowNode_Switch::K2_GetNodeTitle_Implementation() const
{
if (!bOnlyTriggerFirstPassingCase && UFlowSettings::Get()->bUseAdaptiveNodeTitles)
{
return FText::Format(LOCTEXT("SwitchTitle", "{0} (All Passing)"), { Super::K2_GetNodeTitle_Implementation() });
}

return Super::K2_GetNodeTitle_Implementation();
}

#undef LOCTEXT_NAMESPACE
60 changes: 60 additions & 0 deletions Source/Flow/Public/AddOns/FlowNodeAddOn_SwitchCase.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
// Copyright https://github.com/MothCocoon/FlowGraph/graphs/contributors

#pragma once

#include "AddOns/FlowNodeAddOn.h"
#include "Interfaces/FlowSwitchCaseInterface.h"
#include "Types/FlowBranchEnums.h"

#include "FlowNodeAddOn_SwitchCase.generated.h"

// Forward Declarations
class UFlowNode;

UCLASS(MinimalApi, Blueprintable, meta = (DisplayName = "Case"))
class UFlowNodeAddOn_SwitchCase
: public UFlowNodeAddOn
, public IFlowSwitchCaseInterface
{
GENERATED_BODY()

public:

// The output pin for this Switch Case, if it passes
UPROPERTY(EditAnywhere, Category = "Switch")
FName CaseName;

// The output pin for this Switch Case, if it passes
UPROPERTY()
mutable FName OutputPinName;

// For root-level predicates on this Switch Case, do we treat them as an "AND" (all must pass) or an "OR" (at least one must pass)?
UPROPERTY(EditAnywhere, Category = "Switch", DisplayName = "Root Combination Rule")
EFlowPredicateCombinationRule BranchCombinationRule = EFlowPredicateCombinationRule::AND;

// The base PinName for the Switch Case output(s)
static const FName DefaultCaseName;

public:
UFlowNodeAddOn_SwitchCase();

// UFlowNodeBase
virtual EFlowAddOnAcceptResult AcceptFlowNodeAddOnChild_Implementation(const UFlowNodeAddOn* AddOnTemplate, const TArray<UFlowNodeAddOn*>& AdditionalAddOnsToAssumeAreChildren) const override;
virtual FText K2_GetNodeTitle_Implementation() const override;
// --

#if WITH_EDITOR
// UObject
virtual void PostEditChangeProperty(struct FPropertyChangedEvent& PropertyChangedEvent) override;
// --

// IFlowContextPinSupplierInterface
virtual bool SupportsContextPins() const override { return true; }
virtual TArray<FFlowPin> GetContextOutputs() const override;
// --
#endif

// IFlowSwitchCaseInterface
virtual bool TryTriggerForCase_Implementation() const override;
// --
};
1 change: 1 addition & 0 deletions Source/Flow/Public/FlowTags.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,5 @@ namespace FlowNodeStyle
FLOW_API UE_DECLARE_GAMEPLAY_TAG_EXTERN(AddOn_PerSpawnedActor);
FLOW_API UE_DECLARE_GAMEPLAY_TAG_EXTERN(AddOn_Predicate);
FLOW_API UE_DECLARE_GAMEPLAY_TAG_EXTERN(AddOn_Predicate_Composite);
FLOW_API UE_DECLARE_GAMEPLAY_TAG_EXTERN(AddOn_SwitchCase);
}
Loading