So I actually think this is an engine bug introduced in 5.5. Not entirely sure, but here’s what I found:
The function that decides whether an ability is “blocked” (among other things) is DoesAbilitySatisfyTagRequirements()
In UE 5.3 this was the code segement that checked the ability tags against the blocked tags. In GameplayAbility.cpp:
...
// Check if any of this ability's tags are currently blocked
if (AbilitySystemComponent.AreAbilityTagsBlocked(AbilityTags))
{
bBlocked = true;
}
...
Which calls this function in AbilitySystemComponent_Abilities.cpp:
bool UAbilitySystemComponent::AreAbilityTagsBlocked(const FGameplayTagContainer& Tags) const
{
// Expand the passed in tags to get parents, not the blocked tags
return Tags.HasAny(BlockedAbilityTags.GetExplicitGameplayTags());
}
Pretty simple. Note that it’s asking if the Ability Tags contain the Blocked Tags. This is important.
Now this is the new code in UE5.5:
auto CheckForBlocked = [&](const FGameplayTagContainer& ContainerA, const FGameplayTagContainer& ContainerB)
{
// Do we not have any tags in common? Then we're not blocked
if (ContainerA.IsEmpty() || ContainerB.IsEmpty() || !ContainerA.HasAny(ContainerB))
{
return;
}
....
bBlocked = true;
};
Which is a lambda function called later in the funciton like so:
CheckForBlocked(AbilitySystemComponent.GetBlockedAbilityTags(), GetAssetTags());
Meaning ContainerA is the Blocked Tags and ContainerB is the AbilityTags (Or AssetTags as they are now called).
Hence this function is asking if the Blocked Tags contain the Ability Tags. Which is backwards!!!
This means parent tags such Ability.Attack won’t block Ability.Attack.Melee when they did previously. Because while Ability.Attack.Melee contains Ability.Attack (UE 5.3), the reverse isn’t true! Ability.Attackdoesn’t contain Ability.Attack.Melee!
The only workaround I can offer is to override DoesAbilitySatisfyTagRequirements() and change this line. Reversing the comparison:
// Do we not have any tags in common? Then we're not blocked
if (ContainerA.IsEmpty() || ContainerB.IsEmpty() || !ContainerB.HasAny(ContainerA)) // <-- FIX!!!