How-To: Accumulate Damage Over a Stagger Window

A scope accumulates every hit on a target for as long as some state lasts - a stagger, a posture break, an exposed wound - into one running total, then emits a summary when the state ends. It is the difference between forty numbers scrolling past and one number climbing to 951.

text
During State.Stagger: 142 -> 310 -> 588 -> 951 (one climbing number)
On tag removal: [ 951 ] (ScopeSummaryStyle)

1. Declare the scope on a style

Style Registry: a style row with ScopeTag and ScopeSummaryStyle set.
FieldMeaning
ScopeTagWhile the target holds this tag, hits resolving to this style accumulate
ScopeSummaryStyleStyle key for the summary emitted on close. Unset reuses this style

2a. Drive it manually (no GAS required)

Call these on the client when your state begins and ends. This is the path to use if your project does not use the Gameplay Ability System.

cpp
UCrimsonCombatTextSubsystem* Subsystem = GetWorld()->GetSubsystem<UCrimsonCombatTextSubsystem>();
// Stagger begins
Subsystem->BeginCombatTextScope(TargetActor, StaggerTag);
// Stagger ends - retires the running total and emits the summary
Subsystem->EndCombatTextScope(TargetActor, StaggerTag);

In Blueprint, call Begin Combat Text Scope and End Combat Text Scope on the subsystem. Get Combat Text Scope Total reads the running total if you want to drive a meter from it.

Call scopes on clients, never the server
Scopes are local and cosmetic. Call them on each client, not on the server - the server does not display combat text at all.

2b. Or let GAS drive it

If the target has an AbilitySystemComponent, you do not need to call anything. Declare ScopeTag on the style and apply that tag through a GameplayEffect as you normally would; the scope opens on the first hit that finds the tag present, and closes when the tag is removed.

No CrimsonAbilitySystem dependency
This uses Epic's IAbilitySystemInterface only - there is no dependency on CrimsonAbilitySystem. Scopes open lazily on the first hit rather than by watching every actor for the tag being added, so only tag removal needs a callback.

Edge cases already handled

  • Target dies mid-scope - a destroyed actor never fires its removal callback, so dead targets are detected and their scopes flushed.
  • Running total never expires on its own - it is held just short of the fade-out region for as long as the scope is open, then retired on close.
  • Level teardown - every tag callback is unregistered without emitting summaries.

Related: merging without a state

If you want repeat hits merged but have no state tag to hang it on, use AggregationWindow instead. It folds hits on the same target and style that land within N seconds of the previous merge - so a sustained DoT keeps folding into one number rather than starting a fresh one each tick.