diff --git a/Source/Flow/Flow.Build.cs b/Source/Flow/Flow.Build.cs index cc47240d..c6847ce4 100644 --- a/Source/Flow/Flow.Build.cs +++ b/Source/Flow/Flow.Build.cs @@ -18,7 +18,6 @@ public Flow(ReadOnlyTargetRules target) : base(target) "CoreUObject", "DeveloperSettings", "Engine", - "GameplayAbilities", // for FGameplayTagRequirements "GameplayTags", "MovieScene", "MovieSceneTracks", diff --git a/Source/Flow/Private/Nodes/FlowNodeBase.cpp b/Source/Flow/Private/Nodes/FlowNodeBase.cpp index 50ff3ced..043fa4ba 100644 --- a/Source/Flow/Private/Nodes/FlowNodeBase.cpp +++ b/Source/Flow/Private/Nodes/FlowNodeBase.cpp @@ -990,6 +990,11 @@ EDataValidationResult UFlowNodeBase::ValidateNode() bool UFlowNodeBase::TryAddValueToFormatNamedArguments(const FFlowNamedDataPinProperty& NamedDataPinProperty, FFormatNamedArguments& InOutArguments) const { + if (NamedDataPinProperty.Name.IsNone() || !NamedDataPinProperty.DataPinValue.IsValid()) + { + return false; + } + const FFlowDataPinValue& DataPinValue = NamedDataPinProperty.DataPinValue.Get(); const FFlowPinTypeName PinTypeName = DataPinValue.GetPinTypeName(); diff --git a/Source/Flow/Private/Types/FlowGameplayTagUtils.cpp b/Source/Flow/Private/Types/FlowGameplayTagUtils.cpp new file mode 100644 index 00000000..d030d2bd --- /dev/null +++ b/Source/Flow/Private/Types/FlowGameplayTagUtils.cpp @@ -0,0 +1,81 @@ +// Copyright https://github.com/MothCocoon/FlowGraph/graphs/contributors + +#include "Types/FlowGameplayTagUtils.h" + +#include UE_INLINE_GENERATED_CPP_BY_NAME(FlowGameplayTagUtils) + +bool FFlowGameplayTagRequirements::RequirementsMet(const FGameplayTagContainer& Container) const +{ + const bool bHasRequired = Container.HasAll(RequireTags); + const bool bHasIgnored = Container.HasAny(IgnoreTags); + const bool bMatchQuery = TagQuery.IsEmpty() || TagQuery.Matches(Container); + + return bHasRequired && !bHasIgnored && bMatchQuery; +} + +bool FFlowGameplayTagRequirements::IsEmpty() const +{ + return (RequireTags.Num() == 0 && IgnoreTags.Num() == 0 && TagQuery.IsEmpty()); +} + +FString FFlowGameplayTagRequirements::ToString() const +{ + FString Str; + + if (RequireTags.Num() > 0) + { + Str += FString::Printf(TEXT("require: %s "), *RequireTags.ToStringSimple()); + } + if (IgnoreTags.Num() > 0) + { + Str += FString::Printf(TEXT("ignore: %s "), *IgnoreTags.ToStringSimple()); + } + if (!TagQuery.IsEmpty()) + { + Str += TagQuery.GetDescription(); + } + + return Str; +} + +bool FFlowGameplayTagRequirements::operator==(const FFlowGameplayTagRequirements& Other) const +{ + return RequireTags == Other.RequireTags && IgnoreTags == Other.IgnoreTags && TagQuery == Other.TagQuery; +} + +bool FFlowGameplayTagRequirements::operator!=(const FFlowGameplayTagRequirements& Other) const +{ + return !(*this == Other); +} + +FGameplayTagQuery FFlowGameplayTagRequirements::ConvertTagFieldsToTagQuery() const +{ + const bool bHasRequireTags = !RequireTags.IsEmpty(); + const bool bHasIgnoreTags = !IgnoreTags.IsEmpty(); + + if (!bHasIgnoreTags && !bHasRequireTags) + { + return FGameplayTagQuery{}; + } + + // FGameplayTagContainer::RequirementsMet is HasAll(RequireTags) && !HasAny(IgnoreTags); + FGameplayTagQueryExpression RequiredTagsQueryExpression = FGameplayTagQueryExpression().AllTagsMatch().AddTags(RequireTags); + FGameplayTagQueryExpression IgnoreTagsQueryExpression = FGameplayTagQueryExpression().NoTagsMatch().AddTags(IgnoreTags); + + FGameplayTagQueryExpression RootQueryExpression; + if (bHasRequireTags && bHasIgnoreTags) + { + RootQueryExpression = FGameplayTagQueryExpression().AllExprMatch().AddExpr(RequiredTagsQueryExpression).AddExpr(IgnoreTagsQueryExpression); + } + else if (bHasRequireTags) + { + RootQueryExpression = RequiredTagsQueryExpression; + } + else // bHasIgnoreTags + { + RootQueryExpression = IgnoreTagsQueryExpression; + } + + // Build the expression + return FGameplayTagQuery::BuildQuery(RootQueryExpression); +} diff --git a/Source/Flow/Public/AddOns/FlowNodeAddOn_PredicateRequireGameplayTags.h b/Source/Flow/Public/AddOns/FlowNodeAddOn_PredicateRequireGameplayTags.h index aff9ea73..c38f6c4f 100644 --- a/Source/Flow/Public/AddOns/FlowNodeAddOn_PredicateRequireGameplayTags.h +++ b/Source/Flow/Public/AddOns/FlowNodeAddOn_PredicateRequireGameplayTags.h @@ -1,10 +1,9 @@ // Copyright https://github.com/MothCocoon/FlowGraph/graphs/contributors #pragma once -#include "GameplayEffectTypes.h" - #include "AddOns/FlowNodeAddOn.h" #include "Interfaces/FlowPredicateInterface.h" +#include "Types/FlowGameplayTagUtils.h" #include "FlowNodeAddOn_PredicateRequireGameplayTags.generated.h" @@ -47,5 +46,5 @@ class UFlowNodeAddOn_PredicateRequireGameplayTags /* Requirements to evaluate the Test Tags with. */ UPROPERTY(EditAnywhere, Category = Configuration) - FGameplayTagRequirements Requirements; + FFlowGameplayTagRequirements Requirements; }; diff --git a/Source/Flow/Public/Types/FlowGameplayTagUtils.h b/Source/Flow/Public/Types/FlowGameplayTagUtils.h new file mode 100644 index 00000000..f16f8f5e --- /dev/null +++ b/Source/Flow/Public/Types/FlowGameplayTagUtils.h @@ -0,0 +1,41 @@ +// Copyright https://github.com/MothCocoon/FlowGraph/graphs/contributors +#pragma once + +#include "GameplayTagContainer.h" + +#include "FlowGameplayTagUtils.generated.h" + +/** Encapsulate require and ignore tags + * Adapted from FGameplayTagRequirements, but without the GameplayAbilities module dependency */ +USTRUCT(BlueprintType) +struct FFlowGameplayTagRequirements +{ + GENERATED_BODY() + + /** All of these tags must be present */ + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = GameplayTags, meta = (DisplayName = "Must Have Tags")) + FGameplayTagContainer RequireTags; + + /** None of these tags may be present */ + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = GameplayTags, meta = (DisplayName = "Must Not Have Tags")) + FGameplayTagContainer IgnoreTags; + + /** Build up a more complex query that can't be expressed with RequireTags/IgnoreTags alone */ + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = GameplayTags, meta = (DisplayName = "Query Must Match")) + FGameplayTagQuery TagQuery; + + /** True if all required tags and no ignore tags found */ + FLOW_API bool RequirementsMet(const FGameplayTagContainer& Container) const; + + /** True if neither RequireTags or IgnoreTags has any tags */ + FLOW_API bool IsEmpty() const; + + /** Return debug string */ + FLOW_API FString ToString() const; + + FLOW_API bool operator==(const FFlowGameplayTagRequirements& Other) const; + FLOW_API bool operator!=(const FFlowGameplayTagRequirements& Other) const; + + /** Converts the RequireTags and IgnoreTags fields into an equivalent FGameplayTagQuery */ + [[nodiscard]] FLOW_API FGameplayTagQuery ConvertTagFieldsToTagQuery() const; +};