In UAuraAbilitySystemComponent::AbilityActorInfoSet(), bind to OnActivationFailed.
AbilityFailedCallbacks.AddUObject(this, &UAuraAbilitySystemComponent::OnActivationFailed);```
This passes in the failed ability and tags describing the reason:
```c++
void UAuraAbilitySystemComponent::OnActivationFailed(const UGameplayAbility* FailedAbility, const FGameplayTagContainer& GameplayTags)
{
UE_LOGFMT(LogHerb64, Warning, "{0}: Ability Activation failed for {1}, Tags: {2}", __FUNCTION__, FailedAbility->GetName(), GameplayTags.ToStringSimple(true));
if (GameplayTags.HasTag(FAuraGameplayTags::Get().AbilityFail_Cost))
{
if (UAuraDamageGameplayAbility* Ability = const_cast<UAuraDamageGameplayAbility*>(Cast<UAuraDamageGameplayAbility>(FailedAbility)))
{
FScopedAbilityListLock ActiveScopeLock(*this);
for (FGameplayAbilitySpec& AbilitySpec : GetActivatableAbilities())
{
if (AbilitySpec.Ability == FailedAbility)
{
AbilitySpec.DynamicAbilityTags.AddTag(FAuraGameplayTags::Get().AbilityFail_Cost);
break;
}
}
if (Ability->ActivationFailSound) UGameplayStatics::PlaySound2D(this, Ability->ActivationFailSound);
}
}
}```
Note that I also add a dynamic tag to the spec. This is used to avoid multiple triggers in AbilityInputTagHeld().