Ability activation tags empty on client

Hello, (UE5.7) using HandleGameplayEvent to activate an ability. Having a problem where the ability on client machines will have empty activation tags despite them being set in the constructor. Works reliably on server and I can see that the tag is available globally on the client. Anyone have any thoughts?

[/Script/GameplayAbilities.AbilitySystemGlobals]

ReplicateActivationOwnedTags=True

// STEPPED THROUGH THIS, ABILITYTAGS IS ALWAYS EMPTY ON CLIENT
for (FGameplayAbilitySpec AS : ASC->GetActivatableAbilities())
{
    for (FGameplayTag T : AS.Ability->AbilityTags)
    {
        UE_LOG(RTSRuntime, Warning, TEXT("%s"), *T.GetTagName().ToString());
    }
}
// Ability Constructor
UGameplayAbility_PurchaseUnit::UGameplayAbility_PurchaseUnit()
{
    NetExecutionPolicy = EGameplayAbilityNetExecutionPolicy::LocalPredicted;
    ReplicationPolicy = EGameplayAbilityReplicationPolicy::ReplicateYes;
    InstancingPolicy = EGameplayAbilityInstancingPolicy::InstancedPerExecution;
    NetSecurityPolicy = EGameplayAbilityNetSecurityPolicy::ClientOrServer;

    bWasCancelled = true;
    bReplicateEndAbility = true;
    
    // Grants tags to actor using this ability while this ability is active
    FGameplayTag RequestedAbilityTag = FGameplayTag::RequestGameplayTag(FName("GASCore.Event.PurchaseUnit"), true);

    if (RequestedAbilityTag.IsValid())
    {  
        FAbilityTriggerData TriggerData;
        TriggerData.TriggerTag = RequestedAbilityTag;
        TriggerData.TriggerSource = EGameplayAbilityTriggerSource::GameplayEvent;
        AbilityTriggers.Add(TriggerData);
    }
    else
    {
        UE_LOG(RTSRuntime, Warning, TEXT("Failed to add tag to ability trigger"));
    }
}
// Calling the ability
        FGameplayEventData EventData;
        EventData.Instigator = this;
        EventData.Target = this; //shouldnt need target
        EventData.TargetData = FGameplayAbilityTargetDataHandle(TargetDataPtr);
        EventData.OptionalObject = UnitData;

        // Fire the gameplay event to the Ability System Component to trigger abilities listening to this event tag
        FGameplayTag RequestedAbilityTag = FGameplayTag::RequestGameplayTag(FName("GASCore.Event.PurchaseUnit"), true);
        if (RequestedAbilityTag.IsValid())
        {
            if (ASC->HandleGameplayEvent(RequestedAbilityTag, &EventData))
            {
                UE_LOG(RTSRuntime, Display, TEXT("Successfully activated events from tag %s"), *RequestedAbilityTag.GetTagName().ToString());
            }
            else 
            {
                UE_LOG(RTSRuntime, Warning, TEXT("Was unable to activate any events from tag %s"), *RequestedAbilityTag.GetTagName().ToString());
            }
        }
        else
        {
            UE_LOG(RTSRuntime, Warning, TEXT("RequestedAbilityTag is NOT valid."));
        }
    }

Hey @xHesher how are you?

I’m not a programmer so I can’t give you the specific code, but here is my take on this after some research:

AbilityTriggers and AbilityTags live on the CDO and are set in the constructor, but FGameplayAbilitySpec replicates a reference to the ability class, and the client reconstructs it. The tags should be there, but your iteration is likely hitting a timing or pointer issue.

Probably, HandleGameplayEvent is being called on the client before GiveAbility has replicated the spec. The server grants the ability, but the RPC or gameplay event fires before the replicated spec arrives.

FGameplayAbilitySpec::Ability is the CDO pointer. FGameplayAbilitySpec::GetPrimaryInstance() is the instanced one. Since you’re using InstancedPerExecution, there may be no primary instance between executions, leaving GetPrimaryInstance() null — but the CDO pointer should still have the tags.

Knowing this, you can try to ensure the ability is granted before the client calls HandleGameplayEvent with something like this:

  // In your component, bind to the OnGiveAbility delegate
  ASC->OnGiveAbility.AddUObject(this, &UYourClass::OnAbilityGranted);

Or verify if you are reading from the CDO with thiss, if you want to do it using tags:

for (FGameplayAbilitySpec& Spec : ASC->GetActivatableAbilities())
{
    if (!Spec.Ability) 
    {
        UE_LOG(RTSRuntime, Warning, TEXT("Spec.Ability is NULL"));
        continue;
    }

    // This reads from the CDO - tags set in constructor should be here
    UE_LOG(RTSRuntime, Warning, TEXT("Ability class: %s"), 
        *Spec.Ability->GetClass()->GetName());
    UE_LOG(RTSRuntime, Warning, TEXT("AbilityTags count: %d"), 
        Spec.Ability->AbilityTags.Num());
    
    // Also check the spec-level dynamic tags
    UE_LOG(RTSRuntime, Warning, TEXT("Spec DynamicAbilityTags count: %d"), 
        Spec.DynamicAbilityTags.Num());
}

Sorry again if my code isn’t clear but I hope this helps you at least think about the problem from another perspective!

Let me know if it worked!

Hey there, you were right that I was having a sort of race condition, but in this instance it had to do with my PlayerState (which had the ASC attached to it) not being replicated when given the ability. I moved InitAbilitySystem to OnRep_PlayerState and that fixed it

1 Like