#
Multiplayer Guide
A deep dive into how AP StatusFX Suite handles replication, authority, and all the edge cases that make multiplayer status effects hard to get right.
If you only read one page
The short version: call mutation methods on the server only, bind delegates on both server and client, and trust the component to handle the rest. This page explains why and how.
#
Architecture at a Glance
┌─────────────────────────────────────────────┐
│ SERVER │
│ │
│ ApplyEffect() ──► StatusFXComponent │
│ RemoveEffect() (authoritative state) │
│ RefreshEffect() │ │
│ GrantImmunity() │ │
│ ▼ │
│ FastArray Delta Replication │
└─────────────────────────────────────────────┘
│
┌───────────────┼───────────────┐
▼ ▼ ▼
Client A Client B Late Join C
(replicated state) (replicated state) (full state)
HasEffect() ✓ HasEffect() ✓ HasEffect() ✓
OnEffectApplied ✓ OnEffectApplied ✓ (no replay)
#
The Golden Rules
Authority Rules — Follow These Exactly
Rule 1: All mutation methods are server-authoritative. Never call ApplyEffect, RemoveEffect, RefreshEffect, GrantImmunity, or RevokeImmunity from a client.
Rule 2: Bind all delegates on both server and client. Do not gate delegate binding behind HasAuthority().
Rule 3: All query methods (HasEffect, GetStackCount, GetRemainingDuration, etc.) read replicated state and are safe to call from any machine.
#
Replication: FastArray Delta Serialization
The active effects array uses Unreal's FFastArraySerializer — the same technique used by Fortnite's item inventory. Only changed effect entries replicate on each net update, not the entire array.
What this means in practice:
#
Late-Join State Reconstruction
When a client joins a server mid-game, Unreal's initial replication will deliver the full current state of the effects array. The component handles this automatically — the late joiner receives every active effect as if they had been there since the start.
No Code Required
Late-join reconstruction is handled internally by the FastArray serialization. You do not need to write any reconnection logic or state-sync RPCs.
#
Applying Effects From a Client
Clients cannot call ApplyEffect directly. Use a Server RPC to route the request through the server:
UFUNCTION(Server, Reliable)
void ServerApplyPoison(AActor* Target, UAP_StatusEffectDefinition* PoisonDef);
void AMyCharacter::ServerApplyPoison_Implementation(
AActor* Target,
UAP_StatusEffectDefinition* PoisonDef)
{
// Now on the server — safe to call ApplyEffect
UAP_StatusFXBlueprintLibrary::ApplyEffectToActor(Target, PoisonDef, this);
}
In Blueprint, use a Server Event (Reliable, Run on Server) with the same pattern.
#
Delegates: Who Gets What
#
Client Removal Reason (v1.0 Limitation)
OnEffectRemoved fires on clients, but the EAP_EffectRemovalReason value will always be RemovedManually regardless of actual cause. The specific reason is server-side state not currently replicated. This is planned for v1.1.
If you need accurate removal reason on clients right now, send a custom Server → Client RPC after removal.
#
Stack Changes on Clients
When a stack count changes on the server, OnEffectStackChanged fires on the server. Clients detect the array change through FastArray and fire OnEffectRefreshed instead. To handle stack display correctly on all machines:
// Bind BOTH of these on clients
StatusFXComponent->OnEffectStackChanged.AddDynamic(this, &UBuffBar::RefreshStackDisplay);
StatusFXComponent->OnEffectRefreshed.AddDynamic(this, &UBuffBar::RefreshStackDisplay);
#
Immunity Across the Network
Immunity state replicates with the effects array. IsImmuneToEffect() reads replicated state and is accurate on both server and client. When immunity is granted:
- Server grants immunity via
GrantImmunity()or an interaction/expiry rule. - The immunity entry replicates to all clients.
- Clients can query
IsImmuneToEffect()immediately — no RPC needed.
#
Tick Effects in Multiplayer
OnEffectTick fires on the server only, driven by TimerManager. Clients do not receive tick callbacks directly.
Handling periodic effects on clients:
Option A — React to gameplay consequences: The server applies the actual damage/healing and those values replicate through your normal game systems (e.g., Health attribute). Clients just display the effect is active.
Option B — UI tick indicator: Read bIsTicking from the snapshot to show a pulsing indicator in your buff bar. The snapshot also provides TotalDuration and ServerTimeApplied so you can compute elapsed time client-side for animated effects.
Option C — Client tick simulation (planned for v1.1): The roadmap includes client-side tick simulation for cosmetic-only periodic callbacks.
#
Dedicated Server vs. Listen Server
The component behaves identically on both:
#
Testing Checklist
Use this checklist before shipping any status effect gameplay:
- Applied an effect on the server and confirmed
HasEffect()returnstrueon a connected client - Confirmed effect state is correct for a client that joined after the effect was applied (late-join test)
- Confirmed
OnEffectAppliedfires on both server and client - Confirmed stack count is accurate on clients after multiple applications (
GetStackCountquery) - Tested effect expiry on a dedicated server — confirmed
HasEffect()returnsfalseon clients after expiry - Confirmed immunity blocks reapplication on both server and client (
IsImmuneToEffectcheck) - Verified interaction rules (e.g., Frozen removes Burning) replicate correctly to clients
- Simulated packet loss / high latency — effect state should still converge correctly
#
Bandwidth Estimation
For rough planning purposes:
Actual sizes depend on your Gameplay Tag length, Instigator reference, and UE serialization overhead. Enable bEnableReplicationLogging in Project Settings during development to see exact traffic in the output log.
#
Related Pages
AfterPrime Systems — Building the Gameplay Foundation