Let's talk

Ability fires twice, or not at all

It works for the host. On a client it fires twice, spawns two projectiles, or does nothing at all. Seven causes explain almost every case, and a side-by-side dump of the server and client tells you which one you have.

First, get a reason

"Nothing happens" is not a symptom you can debug. Make the activation call tell you why it failed. The free GAS Helpers library has TryActivateAbilityByTag, which returns one of four results:

ResultMeaning
NoASCNo Ability System Component was found on the actor, its PlayerState, controller or owner
NotGrantedThe ASC exists but has no ability with that tag
BlockedThe ability is granted but could not activate: blocking tags, missing required tags, cost, cooldown or net policy
ActivatedIt started, or was sent to the server to start
C++
const ERSActivateResult Result =
    URSAbilitySystemLibrary::TryActivateAbilityByTag(this, TAG_Ability_Fire);

if (Result != ERSActivateResult::Activated)
{
    UE_LOG(LogTemp, Warning, TEXT("Fire: %s\n%s"),
        *UEnum::GetValueAsString(Result),
        *URSAbilitySystemLibrary::DumpAbilitySystemState(this));
}

DumpAbilitySystemState prints the net role, owner, avatar, owned tags, granted abilities, active effects and attributes. Print it on the server and on the client at the same moment and compare. The difference is your bug.

Fires twice: four causes

1. The ability was granted twice

TryActivateAbilitiesByTag activates every granted ability that matches. If a respawn path calls GiveAbility again on a PlayerState ASC, the second life has two specs and fires both. The dump shows the ability listed twice. Fix: check FindAbilitySpecFromClass before granting, as in the PlayerState setup.

2. Two activation paths

Input activates the ability, and so does a gameplay event trigger in the ability's AbilityTriggers. Or an Enhanced Input action is bound to both Started and Triggered. Each path is correct alone. Keep one.

3. Manual server RPC on a predicted ability

With NetExecutionPolicy = LocalPredicted, the ASC already sends the activation to the server. Code that also calls a custom Server_Fire RPC, which then activates the ability on the server, gives the server two activations. Remove the RPC and let the ASC do the round trip.

4. Side effects run on both machines

The ability activates once on the client (predicted) and once on the server (authoritative). That is correct. But if ActivateAbility spawns a replicated projectile on both, the owner sees its predicted projectile plus the server's replicated one.

C++
// Spawn replicated actors on the server only.
if (HasAuthority(&ActivationInfo))
{
    SpawnProjectile();
}
// Optional: a cosmetic, non-replicated stand-in on the owning client.
else if (IsLocallyControlled())
{
    SpawnLocalFakeProjectile();
}

Never fires: three causes

5. The client ASC has no avatar

The ASC lives on the PlayerState and InitAbilityActorInfo only ran on the server. Predicted abilities need the avatar on the client. The dump on the client shows avatar None. Fix: call it in OnRep_PlayerState too.

6. Granted on the client

GiveAbility called from client code does nothing useful. The server owns the ability list and replicates it to the owner. The dump on the server shows no such ability. Fix: grant on the server.

7. Net policy does not match the caller

A ServerOnly or ServerInitiated ability activated from the client needs bAllowRemoteActivation = true (the default). A LocalOnly ability activated on the server never runs on the client. Match the policy to who calls it: player input is almost always LocalPredicted.

A known engine bug

If your ability uses UAbilityTask_NetworkSyncPoint and a predicted gameplay effect is applied twice on the client, check UE-172641. A change made the task generate a second prediction key, so the server could not remove the predicted effect. Epic lists it as fixed; confirm your engine version includes the fix before you rewrite your ability.

Test it the way players play

Most of these bugs hide in a host-player PIE session. Test with:

  • Net Mode: Play as Client, so no player is the server
  • 2 or more players
  • Network emulation on at 150 ms. The real latency tutorial has profiles and a launcher for packaged builds.

Useful console commands while you test: showdebug abilitysystem (cycle categories with AbilitySystem.Debug.NextCategory) and log LogAbilitySystem Verbose.

When it is not one of the seven

If the dump matches on both sides and the ability still misbehaves only in packaged builds at latency, the cause is usually ownership or prediction spread across the project: a custom movement mode, a montage task outside the prediction window, a state machine that bypasses the ASC. That is exactly what the Diagnostic finds in two days.

Get the code

TryActivateAbilityByTag and DumpAbilitySystemState are in GAS Helpers v0.1.0. See the implementation, free under the MIT license.

Still desyncing in packaged builds?

Free code fixes one symptom. A Diagnostic finds the cause across your project in two days.