[GameplayTags] AnyCountChange delegate does not fire for intermediate count decreases when removing loose gameplay tags

Summary

[GameplayTags] AnyCountChange delegate does not fire for intermediate count decreases when removing loose gameplay tags

What Type of Bug are you experiencing?

Gameplay

Steps to Reproduce

  1. Obtain a UAbilitySystemComponent instance.

  2. Register a gameplay tag event delegate using EGameplayTagEventType::AnyCountChange:

    ASC->RegisterGameplayTagEvent(
    Tag,
    EGameplayTagEventType::AnyCountChange
    );

  3. Call AddLooseGameplayTag(Tag) three times.

  4. Call RemoveLooseGameplayTag(Tag) three times.

  5. Observe the delegate callback invocations and the reported tag counts.

The issue is consistently reproducible with the original implementation.

Expected Result

The AnyCountChange delegate should be triggered every time the gameplay tag count changes, including both increases and decreases.

Expected callback sequence:

Adding:

  • Count: 1
  • Count: 2
  • Count: 3

Removing:

  • Count: 2
  • Count: 1
  • Count: 0

Every count change should trigger the registered delegate.

Observed Result

The delegate fires correctly when adding loose gameplay tags:

  • Count: 1
  • Count: 2
  • Count: 3

However, when removing loose gameplay tags using RemoveLooseGameplayTag(), the delegate only fires when the count reaches zero.

Intermediate count decreases (3 → 2 and 2 → 1) do not trigger the AnyCountChange delegate.

This causes listeners relying on AnyCountChange to receive incomplete tag count change notifications.

Affects Versions

5.8

Platform(s)

Windows

Additional Notes

A fix for this issue has already been submitted as a pull request to the official Unreal Engine repository.

GitHub Pull Request:
https://github.com/EpicGames/UnrealEngine/pull/14880

Commit:
1c4a967

Proposed Fix:

Restructure the scope of the conditional block in UpdateTagMapSingle_Internal() by moving the closing brace before the DeferredTagChangeDelegates execution loop.

This ensures that deferred tag change delegates are executed regardless of whether the tag count change is significant, allowing AnyCountChange delegates to fire for intermediate count decreases.

This follows the existing implementation pattern in UpdateTagMap_Internal(), where delegate execution is already handled outside the corresponding conditional block.

Testing:

  • Manually tested loose gameplay tag add/remove cycles.
  • Verified that intermediate count decreases correctly trigger AnyCountChange callbacks with the proposed fix.
  • Verified that the container-based AddLooseGameplayTags / RemoveLooseGameplayTags path remains unaffected.

The proposed change is a minimal control-flow restructuring without introducing additional gameplay logic.

I am submitting this report to ensure the underlying engine issue is tracked and can be reviewed by the Unreal Engine team.