Concept: Event Flow & Replication

One event type (FCrimsonCombatTextEvent) travels one direction: server to clients. Everything visual happens client-side after arrival. Understanding the pipeline explains every behavior in the plugin.

The pipeline

StepWhereWhat happens
1. BroadcastServerGameplay code calls BroadcastCombatText. Authority is validated; client calls are ignored with a warning.
2. Resolve locationServerIf the caller didn't set WorldLocation (bHasWorldLocation false and location zero), it is resolved from the Target - socket, then offset, then root + 100 cm (How-To: Position Pops).
3. SendServerServerCullDistance = 0: one NetMulticast Unreliable RPC to all clients. Greater than 0: a per-client Client Unreliable RPC to each player whose GetFocalLocation() is in range.
4. ReceiveEach clientThe event reaches UCrimsonCombatTextSubsystem::HandleIncomingEvent - via the GameState component's OnCombatTextReceived delegate (multicast path) or directly (culled path).
5. FilterEach clientUCrimsonCombatTextPlayerSettings::PassesFilter runs, if the component exists: distance cap first (all categories, local-pawn targets exempt), then heals, incoming-damage override, crits-only, own/ally/enemy toggles.
6. DisplayEach clientThe request is queued; the tick dequeues it, resolves a style from the registry, formats the value, and starts a pooled widget.

Bandwidth choices

  • WorldLocation is a FVector_NetQuantize - quantized to 1 cm, plenty for a floating number and much smaller on the wire.
  • PrefixText and CustomText travel with the event; empty strings cost almost nothing, so pay only for what you use.
  • Instigator and Target are serialized as NetGUID references. On a client where the actor is not net-relevant (or not replicated), they resolve to null - the filter then treats the event as third-party.
  • All RPCs are unreliable: pops are cosmetic, so a dropped packet just means one missing number instead of retransmission cost.

Display-only by design

UCrimsonCombatTextSubsystem is never created on dedicated servers or in non-game worlds (editor previews, thumbnails), and every entry point re-checks the net mode lazily - so a PIE world running as a dedicated server also displays nothing. The GameState component self-registers with the local subsystem in BeginPlay (RegisterEventSource), so there is no polling and no manual binding.

WorldLocation is a network fallback, not the display anchor
The server resolves FCrimsonCombatTextEvent::WorldLocation before sending so that a client which cannot see the target actor still has somewhere to put the pop.

It is not where the text ends up. Each client runs its own placement, which prefers the live target actor and falls back to this location only when that actor is not net relevant. Two clients can legitimately place the same event differently - see How-To: Position Pops.
Listen servers and singleplayer
A listen server's local player is a client of the multicast like any other, and in singleplayer the 'server' and the only client are the same process - the one GameState-broadcast path covers every net mode. The subsystem's direct RequestCombatText exists for purely local, no-replication use (offline tools, menus).
Never trust pops as gameplay state
Events are fire-and-forget cosmetics. If a system needs to know damage happened, read it from your authoritative gameplay state - not from a combat text delegate.