Gameplay Tag Relationships

When dealing with a lot of abilities, the block and cancel tags can get confusing and hard to keep managed. By using a relationship, we can apply block, cancel and activation tags from a more central location. This allows us to define what ability tags block and cancels what abilities, Example below:

This is a relationship for the Ability tag: Gameplay.Action.Player.Reload. Which will block the reload ability, and cancel the players sprint ability.

Inside your ability you would this gameplay tag:

Lets create a new class, which is going to be a DataAsset, and populate it with the required fields

// (C) 2021 InterKaos Games

#pragma once

#include "CoreMinimal.h"
#include "GameplayTagContainer.h"
#include "Engine/DataAsset.h"
#include "KaosAbilityTagRelationship.generated.h"

/*
 * Defines the relationship between different ability tags
 */
USTRUCT()
struct FKaosAbilityTagRelationshipItem
{
	GENERATED_BODY()

	/* The tag that this relationship is about. */
	UPROPERTY(EditAnywhere, Category = Ability)
	FGameplayTag AbilityTag;

	/* This ability tag will block abilities matching these tags */
	UPROPERTY(EditAnywhere, Category = Tags)
	FGameplayTagContainer AbilityTagsToBlock;

	/* This ability tag will cancel abilities matching these tags */
	UPROPERTY(EditAnywhere, Category = Tags)
	FGameplayTagContainer AbilityTagsToCancel;

	/* This ability tag will add these tags to the Activation Required Tags */
	UPROPERTY(EditAnywhere, Category = Tags)
	FGameplayTagContainer ActivationRequiredTags;

	/* This ability tag will add these tags to the Activation Blocked Tags */
	UPROPERTY(EditAnywhere, Category = Tags)
	FGameplayTagContainer ActivationBlockedTags;

};


/*
 * Mapping of how ability tags block or cancel other abilities, and additional activation
 * required and blocked tags. 
 */
UCLASS()
class UKaosAbilityTagRelationship : public UDataAsset
{
	GENERATED_BODY()

public:
	/* Fill out tags to block and cancel matching the AbilityTags passed in */
	void GetAbilityTagsToBlockAndCancel(const FGameplayTagContainer& AbilityTags, FGameplayTagContainer* OutTagsToBlock, FGameplayTagContainer* OutTagsToCancel) const;

	/* Add additional required and blocking tags matching the passed in AbilityTags */
	void GetActivationRequiredAndBlockedTags(const FGameplayTagContainer& AbilityTags, FGameplayTagContainer* OutActivationRequired, FGameplayTagContainer* OutActivationBlocked) const;

private:
	/* The list of relationships between different ability gameplay tags */
	UPROPERTY(EditAnywhere)
	TArray<FKaosAbilityTagRelationshipItem> AbilityTagRelationships;
};

Now we need to create the corresponding cpp file

// (C) 2021 InterKaos Games

#include "KaosAbilityTagRelationship.h"

void UKaosAbilityTagRelationship::GetAbilityTagsToBlockAndCancel(const FGameplayTagContainer& AbilityTags, FGameplayTagContainer* OutTagsToBlock, FGameplayTagContainer* OutTagsToCancel) const
{
	for (const FKaosAbilityTagRelationshipItem& Relationship : AbilityTagRelationships)
	{
		if (AbilityTags.HasTag(Relationship.AbilityTag))
		{
			if (OutTagsToBlock)
			{
				OutTagsToBlock->AppendTags(Relationship.AbilityTagsToBlock);
			}
			
			if (OutTagsToCancel)
			{
				OutTagsToCancel->AppendTags(Relationship.AbilityTagsToCancel);
			}
		}
	}
}

void UKaosAbilityTagRelationship::GetActivationRequiredAndBlockedTags(const FGameplayTagContainer& AbilityTags, FGameplayTagContainer* OutActivationRequired, FGameplayTagContainer* OutActivationBlocked) const
{
	for (const FKaosAbilityTagRelationshipItem& Relationship : AbilityTagRelationships)
	{
		if (AbilityTags.HasTag(Relationship.AbilityTag))
		{
			if (OutActivationRequired)
			{
				OutActivationRequired->AppendTags(Relationship.ActivationRequiredTags);
			}

			if (OutActivationBlocked)
			{
				OutActivationBlocked->AppendTags(Relationship.ActivationBlockedTags);
			}
		}
	}
}

Now we need to add a couple of things to the Ability System Component so we can make use of these relationships

Add the following to your custom ASC

public:
	void GetRelationshipActivationTagRequirements(const FGameplayTagContainer& AbilityTags, FGameplayTagContainer& OutActivationRequired, FGameplayTagContainer& OutActivationBlocked) const;


protected:
	/* Mapping of Ability Tag to block and cancel tags. */
	UPROPERTY(EditDefaultsOnly, Category = "Abilities|GameplayTags")
	UKaosAbilityTagRelationship* AbilityTagRelationship;

and we need to override one function:

	virtual void ApplyAbilityBlockAndCancelTags(const FGameplayTagContainer& AbilityTags, UGameplayAbility* RequestingAbility, bool bEnableBlockTags, const FGameplayTagContainer& BlockTags, bool bExecuteCancelTags, const FGameplayTagContainer& CancelTags) override;

Now we can and implement these functions, and use the tag relationship table

void UKaosAbilitySystemComponent::GetRelationshipActivationTagRequirements(const FGameplayTagContainer& AbilityTags, FGameplayTagContainer& OutActivationRequired, FGameplayTagContainer& OutActivationBlocked) const
{
	if (AbilityTagRelationship)
	{
		AbilityTagRelationship->GetActivationRequiredAndBlockedTags(AbilityTags, &OutActivationRequired, &OutActivationBlocked);
	}
}

void UKaosAbilitySystemComponent::ApplyAbilityBlockAndCancelTags(const FGameplayTagContainer& AbilityTags, UGameplayAbility* RequestingAbility, bool bEnableBlockTags, const FGameplayTagContainer& BlockTags, bool bExecuteCancelTags,
	const FGameplayTagContainer& CancelTags)
{
	FGameplayTagContainer AbilityBlockTags = BlockTags;
	FGameplayTagContainer AbilityCancelTags = CancelTags;
	
	if (AbilityTagRelationship)
	{
		AbilityTagRelationship->GetAbilityTagsToBlockAndCancel(AbilityTags, &AbilityBlockTags, &AbilityCancelTags);
	}

	Super::ApplyAbilityBlockAndCancelTags(AbilityTags, RequestingAbility, bEnableBlockTags, AbilityBlockTags, bExecuteCancelTags, AbilityCancelTags);
}

Now the block and cancel tags will work, but we need to do some stuff in your custom Gameplay Ability to handle the Activation Required and Activation Blocked tags.

Lets override the following function in your custom Gameplay Ability class

	virtual bool DoesAbilitySatisfyTagRequirements(const UAbilitySystemComponent& AbilitySystemComponent, const FGameplayTagContainer* SourceTags, const FGameplayTagContainer* TargetTags, FGameplayTagContainer* OptionalRelevantTags) const override;

and implement the function like this (this is a hard override of the default GameplayAbility function

bool UKaosGameplayAbility::DoesAbilitySatisfyTagRequirements(const UAbilitySystemComponent& AbilitySystemComponent, const FGameplayTagContainer* SourceTags, const FGameplayTagContainer* TargetTags, FGameplayTagContainer* OptionalRelevantTags) const
{
	bool bBlocked = false;
	bool bMissing = false;

	const UAbilitySystemGlobals& AbilitySystemGlobals = UAbilitySystemGlobals::Get();
	const FGameplayTag& BlockedTag = AbilitySystemGlobals.ActivateFailTagsBlockedTag;
	const FGameplayTag& MissingTag = AbilitySystemGlobals.ActivateFailTagsMissingTag;

	// Check if any of this ability's tags are currently blocked
	if (AbilitySystemComponent.AreAbilityTagsBlocked(AbilityTags))
	{
		bBlocked = true;
	}

	/*
	 * Relationship related code
	 */
	
	const UKaosAbilitySystemComponent* KaosASC = Cast<UKaosAbilitySystemComponent>(&AbilitySystemComponent);
	static FGameplayTagContainer AbilityRequiredTags;
	AbilityRequiredTags = ActivationRequiredTags;
	
	static FGameplayTagContainer AbilityBlockedTags; 
	AbilityBlockedTags = ActivationBlockedTags;

	// This gets the additional tags from the ASC's relationship mapping for the abilities tags.
	if (KaosASC)
	{
		KaosASC->GetRelationshipActivationTagRequirements(AbilityTags, AbilityRequiredTags, AbilityBlockedTags);
	}

	/*
	 * End of relationship code
	 */

	// Check to see the required/blocked tags for this ability
	if (AbilityBlockedTags.Num() || AbilityRequiredTags.Num())
	{
		static FGameplayTagContainer AbilitySystemComponentTags;
		
		AbilitySystemComponentTags.Reset();
		AbilitySystemComponent.GetOwnedGameplayTags(AbilitySystemComponentTags);

		if (AbilitySystemComponentTags.HasAny(AbilityBlockedTags))
		{
			bBlocked = true;
		}

		if (!AbilitySystemComponentTags.HasAll(AbilityRequiredTags))
		{
			bMissing = true;
		}
	}

	if (SourceTags != nullptr)
	{
		if (SourceBlockedTags.Num() || SourceRequiredTags.Num())
		{
			if (SourceTags->HasAny(SourceBlockedTags))
			{
				bBlocked = true;
			}

			if (!SourceTags->HasAll(SourceRequiredTags))
			{
				bMissing = true;
			}
		}
	}

	if (TargetTags != nullptr)
	{
		if (TargetBlockedTags.Num() || TargetRequiredTags.Num())
		{
			if (TargetTags->HasAny(TargetBlockedTags))
			{
				bBlocked = true;
			}

			if (!TargetTags->HasAll(TargetRequiredTags))
			{
				bMissing = true;
			}
		}
	}

	if (bBlocked)
	{
		if (OptionalRelevantTags && BlockedTag.IsValid())
		{
			OptionalRelevantTags->AddTag(BlockedTag);
		}
		return false;
	}
	if (bMissing)
	{
		if (OptionalRelevantTags && MissingTag.IsValid())
		{
			OptionalRelevantTags->AddTag(MissingTag);
		}
		return false;
	}

	return true;
}

Hopefully that is everything (not sure if i have missed anything…), but this should allow you to finally get those Gameplay Tags for abilities under a bit more control, especially on large projects with a lot of abilities.