From 15de8dac47cfdb624b1c334074edb9b7d75cf658 Mon Sep 17 00:00:00 2001 From: LindyHopperGT <91915878+LindyHopperGT@users.noreply.github.com> Date: Tue, 17 Feb 2026 12:50:04 -0800 Subject: [PATCH 1/3] Switch node --- .../AddOns/FlowNodeAddOn_SwitchCase.cpp | 149 ++++++++++++++++++ Source/Flow/Private/FlowTags.cpp | 1 + .../Interfaces/FlowSwitchCaseInterface.cpp | 20 +++ .../Private/Nodes/Route/FlowNode_Branch.cpp | 45 +++++- .../Private/Nodes/Route/FlowNode_Switch.cpp | 84 ++++++++++ .../Public/AddOns/FlowNodeAddOn_SwitchCase.h | 60 +++++++ Source/Flow/Public/FlowTags.h | 1 + .../Interfaces/FlowSwitchCaseInterface.h | 30 ++++ .../Flow/Public/Nodes/Route/FlowNode_Branch.h | 10 ++ .../Flow/Public/Nodes/Route/FlowNode_Switch.h | 35 ++++ Source/Flow/Public/Types/FlowBranchEnums.h | 17 ++ .../Graph/Nodes/FlowGraphNode_Branch.cpp | 3 +- .../Public/Asset/FlowAssetIndexer.h | 1 + 13 files changed, 453 insertions(+), 3 deletions(-) create mode 100644 Source/Flow/Private/AddOns/FlowNodeAddOn_SwitchCase.cpp create mode 100644 Source/Flow/Private/Interfaces/FlowSwitchCaseInterface.cpp create mode 100644 Source/Flow/Private/Nodes/Route/FlowNode_Switch.cpp create mode 100644 Source/Flow/Public/AddOns/FlowNodeAddOn_SwitchCase.h create mode 100644 Source/Flow/Public/Interfaces/FlowSwitchCaseInterface.h create mode 100644 Source/Flow/Public/Nodes/Route/FlowNode_Switch.h create mode 100644 Source/Flow/Public/Types/FlowBranchEnums.h diff --git a/Source/Flow/Private/AddOns/FlowNodeAddOn_SwitchCase.cpp b/Source/Flow/Private/AddOns/FlowNodeAddOn_SwitchCase.cpp new file mode 100644 index 000000000..f092a2b71 --- /dev/null +++ b/Source/Flow/Private/AddOns/FlowNodeAddOn_SwitchCase.cpp @@ -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 UFlowNodeAddOn_SwitchCase::GetContextOutputs() const +{ + const UFlowNode* FlowNodeOwner = GetFlowNode(); + check(IsValid(FlowNodeOwner)); + + int32 DuplicateCount = 0; + int32 DuplicateCountForThis = 0; + int32 ThisIndex = INDEX_NONE; + + const TArray& OwnerAddOns = FlowNodeOwner->GetFlowNodeAddOnChildren(); + for (int32 Index = 0; Index < OwnerAddOns.Num(); ++Index) + { + const UFlowNodeAddOn_SwitchCase* SwitchCaseAddOn = Cast(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& 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 \ No newline at end of file diff --git a/Source/Flow/Private/FlowTags.cpp b/Source/Flow/Private/FlowTags.cpp index 9ee1342fd..349d2ed36 100644 --- a/Source/Flow/Private/FlowTags.cpp +++ b/Source/Flow/Private/FlowTags.cpp @@ -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"); diff --git a/Source/Flow/Private/Interfaces/FlowSwitchCaseInterface.cpp b/Source/Flow/Private/Interfaces/FlowSwitchCaseInterface.cpp new file mode 100644 index 000000000..0973034db --- /dev/null +++ b/Source/Flow/Private/Interfaces/FlowSwitchCaseInterface.cpp @@ -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; +} diff --git a/Source/Flow/Private/Nodes/Route/FlowNode_Branch.cpp b/Source/Flow/Private/Nodes/Route/FlowNode_Branch.cpp index de5bef971..1ed91e76b 100644 --- a/Source/Flow/Private/Nodes/Route/FlowNode_Branch.cpp +++ b/Source/Flow/Private/Nodes/Route/FlowNode_Branch.cpp @@ -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"); @@ -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()->bUseAdaptiveNodeTitles) + { + return FText::Format(LOCTEXT("BranchTitle", "{0} ({1})"), { Super::K2_GetNodeTitle_Implementation(), UEnum::GetDisplayValueAsText(BranchCombinationRule) }); + } + + return Super::K2_GetNodeTitle_Implementation(); +} + +#undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/Source/Flow/Private/Nodes/Route/FlowNode_Switch.cpp b/Source/Flow/Private/Nodes/Route/FlowNode_Switch.cpp new file mode 100644 index 000000000..dbd75415a --- /dev/null +++ b/Source/Flow/Private/Nodes/Route/FlowNode_Switch.cpp @@ -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& 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( + [&TriggeringCaseCount, this](const UFlowNodeAddOn& SwitchCaseAddOn) + { + const IFlowSwitchCaseInterface* SwitchCaseInterface = CastChecked(&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 \ No newline at end of file diff --git a/Source/Flow/Public/AddOns/FlowNodeAddOn_SwitchCase.h b/Source/Flow/Public/AddOns/FlowNodeAddOn_SwitchCase.h new file mode 100644 index 000000000..43607adad --- /dev/null +++ b/Source/Flow/Public/AddOns/FlowNodeAddOn_SwitchCase.h @@ -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& 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 GetContextOutputs() const override; + // -- +#endif + + // IFlowSwitchCaseInterface + virtual bool TryTriggerForCase_Implementation() const override; + // -- +}; diff --git a/Source/Flow/Public/FlowTags.h b/Source/Flow/Public/FlowTags.h index bc24ac741..829ddba20 100644 --- a/Source/Flow/Public/FlowTags.h +++ b/Source/Flow/Public/FlowTags.h @@ -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); } diff --git a/Source/Flow/Public/Interfaces/FlowSwitchCaseInterface.h b/Source/Flow/Public/Interfaces/FlowSwitchCaseInterface.h new file mode 100644 index 000000000..ec549055d --- /dev/null +++ b/Source/Flow/Public/Interfaces/FlowSwitchCaseInterface.h @@ -0,0 +1,30 @@ +// Copyright https://github.com/MothCocoon/FlowGraph/graphs/contributors + +#pragma once + +#include "UObject/Interface.h" +#include "Templates/SubclassOf.h" + +#include "FlowSwitchCaseInterface.generated.h" + +class UFlowNodeAddOn; + +// A 'Case' addon for a Switch flow node +UINTERFACE(MinimalAPI, BlueprintType, Blueprintable, DisplayName = "Flow Switch Case Interface") +class UFlowSwitchCaseInterface : public UInterface +{ + GENERATED_BODY() +}; + +class FLOW_API IFlowSwitchCaseInterface +{ + GENERATED_BODY() + +public: + + UFUNCTION(BlueprintNativeEvent) + bool TryTriggerForCase() const; + virtual bool TryTriggerForCase_Implementation() const { return true; } + + static bool ImplementsInterfaceSafe(const UFlowNodeAddOn* AddOnTemplate); +}; diff --git a/Source/Flow/Public/Nodes/Route/FlowNode_Branch.h b/Source/Flow/Public/Nodes/Route/FlowNode_Branch.h index 522d6285e..1f952310f 100644 --- a/Source/Flow/Public/Nodes/Route/FlowNode_Branch.h +++ b/Source/Flow/Public/Nodes/Route/FlowNode_Branch.h @@ -2,6 +2,7 @@ #pragma once #include "Nodes/FlowNode.h" +#include "Types/FlowBranchEnums.h" #include "FlowNode_Branch.generated.h" /** @@ -13,10 +14,19 @@ class UFlowNode_Branch : public UFlowNode GENERATED_UCLASS_BODY() public: + + // For root-level predicates on this branch, do we treat them as an "AND" (all must pass) or an "OR" (at least one must pass)? + UPROPERTY(EditAnywhere, Category = "Branch", DisplayName = "Root Combination Rule") + EFlowPredicateCombinationRule BranchCombinationRule = EFlowPredicateCombinationRule::AND; + +public: + // UFlowNodeBase virtual EFlowAddOnAcceptResult AcceptFlowNodeAddOnChild_Implementation(const UFlowNodeAddOn* AddOnTemplate, const TArray& AdditionalAddOnsToAssumeAreChildren) const override; + virtual FText K2_GetNodeTitle_Implementation() const override; // -- + // Event reacting on triggering Input pin virtual void ExecuteInput(const FName& PinName) override; static const FName INPIN_Evaluate; diff --git a/Source/Flow/Public/Nodes/Route/FlowNode_Switch.h b/Source/Flow/Public/Nodes/Route/FlowNode_Switch.h new file mode 100644 index 000000000..411d4d96b --- /dev/null +++ b/Source/Flow/Public/Nodes/Route/FlowNode_Switch.h @@ -0,0 +1,35 @@ +// Copyright https://github.com/MothCocoon/FlowGraph/graphs/contributors + +#pragma once + +#include "Nodes/FlowNode.h" +#include "Types/FlowBranchEnums.h" + +#include "FlowNode_Switch.generated.h" + +// Similar to a Branch flow node, provides a "Switch" style logic (ie, C/C++), +// where cases are evaluated and triggered if their predicates pass. +// By default, only the first passing case is triggered (see bOnlyTriggerFirstPassingCase) +UCLASS(MinimalApi, NotBlueprintable, meta = (DisplayName = "Switch")) +class UFlowNode_Switch : public UFlowNode +{ + GENERATED_UCLASS_BODY() + +public: + + // Only trigger the switch output for the first passing case during a single Evaluate + // (if false, all passing cases will trigger) + UPROPERTY(EditAnywhere, Category = "Switch") + bool bOnlyTriggerFirstPassingCase = true; + + // UFlowNodeBase + virtual EFlowAddOnAcceptResult AcceptFlowNodeAddOnChild_Implementation(const UFlowNodeAddOn* AddOnTemplate, const TArray& AdditionalAddOnsToAssumeAreChildren) const override; + virtual FText K2_GetNodeTitle_Implementation() const override; + // -- + + // Event reacting on triggering Input pin + virtual void ExecuteInput(const FName& PinName) override; + + static const FName INPIN_Evaluate; + static const FName OUTPIN_DefaultCase; +}; diff --git a/Source/Flow/Public/Types/FlowBranchEnums.h b/Source/Flow/Public/Types/FlowBranchEnums.h new file mode 100644 index 000000000..ba6438208 --- /dev/null +++ b/Source/Flow/Public/Types/FlowBranchEnums.h @@ -0,0 +1,17 @@ +// Copyright https://github.com/MothCocoon/FlowGraph/graphs/contributors + +#pragma once + +#include "Types/FlowEnumUtils.h" + +UENUM(BlueprintType) +enum class EFlowPredicateCombinationRule : uint8 +{ + AND UMETA(ToolTip = "Passes if ALL child predicates pass"), + OR UMETA(ToolTip = "Passes if ANY (at least one) child predicates pass"), + + Max UMETA(Hidden), + Invalid UMETA(Hidden), + Min = 0 UMETA(Hidden), +}; +FLOW_ENUM_RANGE_VALUES(EFlowPredicateCombinationRule); diff --git a/Source/FlowEditor/Private/Graph/Nodes/FlowGraphNode_Branch.cpp b/Source/FlowEditor/Private/Graph/Nodes/FlowGraphNode_Branch.cpp index bc9e37d6b..ace07bbb7 100644 --- a/Source/FlowEditor/Private/Graph/Nodes/FlowGraphNode_Branch.cpp +++ b/Source/FlowEditor/Private/Graph/Nodes/FlowGraphNode_Branch.cpp @@ -2,13 +2,14 @@ #include "Graph/Nodes/FlowGraphNode_Branch.h" #include "Nodes/Route/FlowNode_Branch.h" +#include "Nodes/Route/FlowNode_Switch.h" #include "Textures/SlateIcon.h" UFlowGraphNode_Branch::UFlowGraphNode_Branch(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer) { - AssignedNodeClasses = {UFlowNode_Branch::StaticClass() }; + AssignedNodeClasses = { UFlowNode_Branch::StaticClass(), UFlowNode_Switch::StaticClass() }; } FSlateIcon UFlowGraphNode_Branch::GetIconAndTint(FLinearColor& OutColor) const diff --git a/Source/FlowEditor/Public/Asset/FlowAssetIndexer.h b/Source/FlowEditor/Public/Asset/FlowAssetIndexer.h index 695a2bbe5..8c9310754 100644 --- a/Source/FlowEditor/Public/Asset/FlowAssetIndexer.h +++ b/Source/FlowEditor/Public/Asset/FlowAssetIndexer.h @@ -2,6 +2,7 @@ #pragma once #include "IAssetIndexer.h" +#include "UObject/Object.h" class UFlowAsset; class FSearchSerializer; From c9c9aee56211a603c08cd8b8753c304875e5fb08 Mon Sep 17 00:00:00 2001 From: LindyHopperGT <91915878+LindyHopperGT@users.noreply.github.com> Date: Tue, 17 Feb 2026 14:17:50 -0800 Subject: [PATCH 2/3] compile fix for linux build --- Source/FlowDebugger/Public/Debugger/FlowDebuggerTypes.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Source/FlowDebugger/Public/Debugger/FlowDebuggerTypes.h b/Source/FlowDebugger/Public/Debugger/FlowDebuggerTypes.h index 12eb45e1b..d2ed8cd02 100644 --- a/Source/FlowDebugger/Public/Debugger/FlowDebuggerTypes.h +++ b/Source/FlowDebugger/Public/Debugger/FlowDebuggerTypes.h @@ -1,4 +1,4 @@ -// Copyright https://github.com/MothCocoon/FlowGraph/graphs/contributors +// Copyright https://github.com/MothCocoon/FlowGraph/graphs/contributors #pragma once #include "FlowDebuggerTypes.generated.h" @@ -10,7 +10,7 @@ struct FLOWDEBUGGER_API FFlowBreakpoint protected: /* Applies only to node breakpoint. - /* Pin breakpoints are deactivated by removing element from FNodeBreakpoint::PinBreakpoints. */ + * Pin breakpoints are deactivated by removing element from FNodeBreakpoint::PinBreakpoints. */ UPROPERTY() bool bActive; From 477e39b6310edeb9c40569d97c52caf008c8c1bf Mon Sep 17 00:00:00 2001 From: LindyHopperGT <91915878+LindyHopperGT@users.noreply.github.com> Date: Wed, 18 Feb 2026 11:23:52 -0800 Subject: [PATCH 3/3] Restored lost code for Switch/Branch This code was lost during the merge with mainline flow. Restored it to fix an assert with addons in editor --- .../Private/AddOns/FlowNodeAddOn_SwitchCase.cpp | 2 +- Source/Flow/Private/Nodes/FlowNode.cpp | 17 +++++++++++++++++ .../Private/Nodes/Route/FlowNode_Switch.cpp | 2 +- Source/Flow/Public/AddOns/FlowNodeAddOn.h | 4 ++++ Source/Flow/Public/Nodes/FlowNode.h | 6 ++++++ .../Private/Graph/Nodes/FlowGraphNode.cpp | 6 +++++- 6 files changed, 34 insertions(+), 3 deletions(-) diff --git a/Source/Flow/Private/AddOns/FlowNodeAddOn_SwitchCase.cpp b/Source/Flow/Private/AddOns/FlowNodeAddOn_SwitchCase.cpp index f092a2b71..6a0d9c6cc 100644 --- a/Source/Flow/Private/AddOns/FlowNodeAddOn_SwitchCase.cpp +++ b/Source/Flow/Private/AddOns/FlowNodeAddOn_SwitchCase.cpp @@ -130,7 +130,7 @@ bool UFlowNodeAddOn_SwitchCase::TryTriggerForCase_Implementation() const FText UFlowNodeAddOn_SwitchCase::K2_GetNodeTitle_Implementation() const { - if (UFlowSettings::Get()->bUseAdaptiveNodeTitles) + if (GetDefault()->bUseAdaptiveNodeTitles) { FLOW_ASSERT_ENUM_MAX(EFlowPredicateCombinationRule, 2); if (BranchCombinationRule != EFlowPredicateCombinationRule::AND) diff --git a/Source/Flow/Private/Nodes/FlowNode.cpp b/Source/Flow/Private/Nodes/FlowNode.cpp index e24d8f530..ecf2b1d22 100644 --- a/Source/Flow/Private/Nodes/FlowNode.cpp +++ b/Source/Flow/Private/Nodes/FlowNode.cpp @@ -98,6 +98,19 @@ void UFlowNode::ValidateFlowPinArrayIsUnique(const TArray& FlowPins, T } } } + +void UFlowNode::EnsureSetFlowNodeForEditorForAllAddOns() const +{ + UFlowNode* MutableThis = const_cast(this); + + MutableThis->ForEachAddOn( + [MutableThis](UFlowNodeAddOn& AddOn) -> EFlowForEachAddOnFunctionReturnValue + { + AddOn.SetFlowNodeForEditor(MutableThis); + return EFlowForEachAddOnFunctionReturnValue::Continue; + }); +} + #endif void UFlowNode::PostLoad() @@ -315,6 +328,8 @@ bool UFlowNode::SupportsContextPins() const TArray UFlowNode::GetContextInputs() const { + EnsureSetFlowNodeForEditorForAllAddOns(); + TArray ContextOutputs = Super::GetContextInputs(); // Add the Auto-Generated DataPins as GetContextInputs @@ -328,6 +343,8 @@ TArray UFlowNode::GetContextInputs() const TArray UFlowNode::GetContextOutputs() const { + EnsureSetFlowNodeForEditorForAllAddOns(); + TArray ContextOutputs = Super::GetContextOutputs(); // Add the Auto-Generated DataPins as ContextOutputs diff --git a/Source/Flow/Private/Nodes/Route/FlowNode_Switch.cpp b/Source/Flow/Private/Nodes/Route/FlowNode_Switch.cpp index dbd75415a..0e71c3dd3 100644 --- a/Source/Flow/Private/Nodes/Route/FlowNode_Switch.cpp +++ b/Source/Flow/Private/Nodes/Route/FlowNode_Switch.cpp @@ -73,7 +73,7 @@ void UFlowNode_Switch::ExecuteInput(const FName& PinName) FText UFlowNode_Switch::K2_GetNodeTitle_Implementation() const { - if (!bOnlyTriggerFirstPassingCase && UFlowSettings::Get()->bUseAdaptiveNodeTitles) + if (!bOnlyTriggerFirstPassingCase && GetDefault()->bUseAdaptiveNodeTitles) { return FText::Format(LOCTEXT("SwitchTitle", "{0} (All Passing)"), { Super::K2_GetNodeTitle_Implementation() }); } diff --git a/Source/Flow/Public/AddOns/FlowNodeAddOn.h b/Source/Flow/Public/AddOns/FlowNodeAddOn.h index 4ab18c78b..eb2406cd5 100644 --- a/Source/Flow/Public/AddOns/FlowNodeAddOn.h +++ b/Source/Flow/Public/AddOns/FlowNodeAddOn.h @@ -85,6 +85,10 @@ class UFlowNodeAddOn : public UFlowNodeBase // -- FLOW_API void RequestReconstructionOnOwningFlowNode() const; + + // Editor-only method to set the FlowNode for any follow-up operations + // that the addon will need a reliable FlowNode pointer at editor-time + void SetFlowNodeForEditor(UFlowNode* FlowNodeOwner) { FlowNode = FlowNodeOwner; } #endif // WITH_EDITOR protected: diff --git a/Source/Flow/Public/Nodes/FlowNode.h b/Source/Flow/Public/Nodes/FlowNode.h index e19eb1ffe..09717af2a 100644 --- a/Source/Flow/Public/Nodes/FlowNode.h +++ b/Source/Flow/Public/Nodes/FlowNode.h @@ -69,6 +69,12 @@ class FLOW_API UFlowNode : public UFlowNodeBase virtual bool IsSupportedInputPinName(const FName& PinName) const override; // -- +#if WITH_EDITOR + // Called before operations on AddOns in the editor + // where we need the AddOns to have a value FlowNode pointer to use + void EnsureSetFlowNodeForEditorForAllAddOns() const; +#endif + public: // UObject virtual void PostLoad() override; diff --git a/Source/FlowEditor/Private/Graph/Nodes/FlowGraphNode.cpp b/Source/FlowEditor/Private/Graph/Nodes/FlowGraphNode.cpp index b87e6fcca..e2ec48f48 100644 --- a/Source/FlowEditor/Private/Graph/Nodes/FlowGraphNode.cpp +++ b/Source/FlowEditor/Private/Graph/Nodes/FlowGraphNode.cpp @@ -1846,6 +1846,9 @@ bool UFlowGraphNode::TryUpdateNodePins() const return true; } + // Ensure the AddOns for this FlowNode have their FlowNode pointer set + FlowNodeInstance->EnsureSetFlowNodeForEditorForAllAddOns(); + // Attempt to update auto-generated pins // This must be called first, it updates the underlying data for data pins of the Flow Node const bool bAutoDataPinsChanged = FlowNodeInstance->TryUpdateAutoDataPins(); @@ -1873,7 +1876,8 @@ bool UFlowGraphNode::TryUpdateNodePins() const check(IsValid(FlowNodeCDO)); // Fix up old pins on the CDO - UFlowNode* MutableCDO = const_cast(FlowNodeCDO); + UFlowNode* MutableCDO = const_cast(FlowNodeCDO); + MutableCDO->EnsureSetFlowNodeForEditorForAllAddOns(); MutableCDO->FixupDataPinTypes(); // We grab basic built-in input/output pins from the CDO