Skip to main content

Status System

GCS ยท Runtime

How status stacks live on units at runtime, when they tick and decay, and which events keep your UI honest.

Use this when a status ticks at the wrong time, stacks show stale numbers in your UI, or a damage hook never fires.

Statuses are authored as GameStatus assets and live on units as runtime stack counts. A status can display an icon, track stacks, decay over time, contribute hooks, and run FlowGraph behavior. The Unit tab of the Game Card Monitor shows every unit's current stacks live.

Game Card Monitor Unit tab showing player and enemy units with HP, armor, and active status stacks

Authored status fieldsโ€‹

FieldRuntime meaning
DisplayNameUser-facing name.
DescriptionPlayer-facing description with optional tokens.
IconUI icon.
IsDebuffMarks the status as a debuff for filtering and presentation.
StackRuleHow new stacks combine with existing stacks: Additive adds them, Max keeps the higher count, Replace overwrites.
MaxStacksMaximum stack count; negative means no practical limit.
DecayRuleWhen stacks decay: PerTurn loses one stack at the end of the owner's turn, Never waits for explicit removal.
ShowStackCountWhether UI should show stack numbers.
TagsAuthoring and FlowGraph filters.
BehaviorFlowGraph that reacts to status lifecycle, hooks, and timings.

The Behavior graph is where a status earns its keep. The demo's Poison, for example, deals 1 damage per stack at the start of the owner's turn, ignores armor, then loses one stack:

Poison status FlowGraph in the FlowGraph editor: turn-start entry dealing damage per stack, then reducing stacks


Runtime storageโ€‹

Each UnitState stores status data:

Runtime dataMeaning
StatusesDictionary from GameStatus to stack count.
StatusAppliersDictionary from GameStatus to the unit that applied it.
ActiveHooksHook entries contributed by status and card behavior.

Use these public helpers instead of reading the dictionaries directly:

NeedAPI
Stack countGCSApi.GetStatusStacks(unit, status)
Has at least N stacksGCSApi.HasStatus(unit, status, minStacks)
Who applied the statusGCSApi.GetStatusApplier(unit, status)
All statuses on a unitGCSApi.StatusesOn(unit)

StatusesOn returns an empty map when the unit is null or has no statuses.


Applying and changing statusesโ€‹

TaskAPI or node
Apply stacksGCSApi.ApplyStatus(target, status, stacks, source) or the Change Status node.
Remove stacksGCSApi.RemoveStatus(target, status, stacks) or Remove Statuses / Change Status.
Add or subtract by deltaGCSApi.ModifyStatusStacks(target, status, delta)
Re-run behavior without changing stacksThe Reapply Status node or the controller's RetriggerStatus.

Status changes run through runtime rules and raise events. Avoid manually editing UnitState.Statuses.


Hooks versus lifecycle entriesโ€‹

Status behavior can do two different jobs.

Behavior kindUse forExamples
Lifecycle entriesReact after something happened.On Status Received, On Status Removed, On Turn Start.
HooksChange a value before it is committed.Damage Taken Hook, Card Cost Hook, Draw Count Hook.

A hook that intends to change the value should end with a Write Hook node. Lifecycle entries use normal action nodes.


Decay and turn timingโ€‹

Status decay and ticks are driven by the battle state machine. A status with PerTurn decay follows one loop from application to expiry:

TimingWhat can happen
PlayerTurnStartPlayer armor decay, player status start-turn behavior, delayed triggers.
PlayerTurnEndDiscard phase, player status end-turn behavior, status tick/decay, hook ticking.
EnemyPhase, per enemyEnemy status start-turn behavior, enemy action, enemy status end-turn behavior, status tick/decay.
BattleEndBattle-end status behavior on all units.

Use StatusTickedEventArgs when custom UI needs to animate a tick or decay. It carries the stacks and HP both before and after the tick.


Status eventsโ€‹

EventPayloadUse for
OnStatusChangedStatusChangedEventArgsRefresh a unit's status widget after stack changes.
OnStatusTickedStatusTickedEventArgsAnimate damage, healing, or stack changes from a tick.
OnStatusInflictedStatusTransitionEventArgsReact to incoming status application.
OnStatusExpiredStatusTransitionEventArgsAnimate natural expiration.
OnStatusRemovedStatusTransitionEventArgsAnimate cleanse or manual removal.

StatusChangedEventArgs is the most useful UI refresh signal. The transition events are better for specialized effects and gameplay integrations.


Damage and status attributionโ€‹

Damage caused by a status carries source attribution through runtime hooks and damage events. When a status applies damage, DamageEventArgs tells subscribers:

FieldMeaning
SourceUnitIdUnit responsible for the damage, when known.
TargetUnitIdUnit receiving the damage.
AmountFinal amount after runtime rules.
LethalWhether the target died from the damage.

Use this for UI labels, statistics, and combat log entries. Do not infer lethal damage by comparing stale HP values.


UI display rulesโ€‹

For status widgets:

  1. Listen to OnStatusChanged, OnStatusTicked, OnUnitHpChanged, and OnUnitDied as needed.
  2. Refresh the unit's current status map from GCSApi.StatusesOn(unit).
  3. Use GameStatus.Icon, DisplayName, IsDebuff, ShowStackCount, and the stack count for display.
  4. Hide statuses with zero stacks.

The demo UnitView also listens to status events to drive procedural status visuals. Custom UI can use the same event channel without depending on the demo view components.


Common mistakesโ€‹

MistakeBetter approach
Editing UnitState.Statuses directly.Use ApplyStatus, RemoveStatus, or ModifyStatusStacks.
Using a lifecycle entry to modify damage before it happens.Use the matching hook.
Showing stack counts taken from an event alone.Refresh from GCSApi.StatusesOn(unit).
Assuming all statuses are debuffs.Check GameStatus.IsDebuff or tags.
Treating removed and expired as the same event.Use OnStatusRemoved for manual removal and OnStatusExpired for natural expiration.