How-To: Persist Combat State Across Levels

Goal: carry a player's live GAS combat state (active buffs/debuffs/DoTs, cooldowns, attribute values, tags) across a non-seamless ServerTravel so combat continuity survives the transition - and optionally through a disk save/load.

Prerequisites
A player state that owns a UCrimsonAbilitySystemComponent. For the automatic path, CrimsonCore's ACrimsonPlayerState wires capture/restore for you. Standalone projects call the ASC methods themselves on the server.

1. Decide what persists (authoring)

The system ships safe defaults and never drops state silently. Control any effect three ways, resolved in this precedence: per-effect component -> asset tag -> source-granted exclusion -> project default policy. Instant effects are always skipped.

LayerHowUse when
GE ComponentAdd UCrimsonEffectPersistenceComponent to the GameplayEffect; set Mode = Always Persist / Never Persist (default Default defers to the tag + policy layers)You want explicit per-effect control (plus remaining-duration / stacks / SetByCaller toggles)
Asset tagAdd Crimson.Persistence.Persist or Crimson.Persistence.DoNotPersist to the GE's asset tagsA quick, code-free override with no component
Project policyProject Settings -> Crimson -> Ability State PersistenceSet the default for unmarked effects + the meta-attribute exclusion list
Infinite effects are off by default
Unmarked infinite effects are NOT persisted by default - they are usually source-granted passives / equipment auras that the loadout re-establishes on load, so persisting them would double-apply. Opt a genuine standalone infinite buff in with Always Persist or the Persist tag. Unmarked duration effects (and cooldowns) persist by default.

2. Capture and restore

With CrimsonCore this is automatic: ACrimsonPlayerState captures on the pre-travel EndPlay into a GameInstance carrier that survives the travel, then re-applies it after the new level grants the loadout - and embeds the same snapshot in the player save fragment for the disk path. Standalone, drive the ASC on the server:

The standalone capture/restore path is C++-only: CaptureAbilitySystemState (and its FCrimsonAbilityStateCaptureArgs) have no Blueprint nodes, though Restore Ability System State does. In a Blueprint-only project, use the automatic CrimsonCore path (above) instead of driving capture/restore by hand. Both operations are server-authority only.

Server authority
Capture and restore are server-authority only (HasAuthority() / BlueprintAuthorityOnly). Clients never capture or restore - they receive the re-applied state through the ASC's Mixed replication. The GameInstance carrier survives ServerTravel because non-seamless travel tears down the world, not the process. Fully supports Standalone, Listen Server, Dedicated Server, and Client.
Verify
Apply a timed buff + a cooldown in PIE, ServerTravel to another level, and confirm the buff resumes with roughly the right time left and the cooldown keeps ticking. A loadout-granted passive should appear exactly once (not doubled).

3. Inspect decisions live (dev tools)

Open the Ability Dev Tools (Crimson.AbilitySystem.DevTools) and switch to the Persistence tab. For the selected player it lists every active effect with its capture decision (persist / skip), remaining time, stacks, and the exact rule that decided it - the visual half of the no-silent-drop guarantee.

Screenshot pendingImages/CrimsonAbilitySystem/devtools-persistence-tab.png
The Persistence dev-tools tab: per-effect persist / skip decision with the deciding rule and remaining time.
Source-granted skips
The editor preview uses an empty exclusion set, so the source-granted skip (effects the loadout re-grants) is NOT reflected there - the game assembles that handle set at capture time. Component / tag / policy decisions are exact.

See also

  • Concept: Ability System Component - the ASC that hosts capture / restore
  • The plugin README section 8 (Combat-State Persistence) for the full type table and per-net-mode matrix