Skip to content

✨ Using the Status Effects System

The Status Effects system is a modular, deterministic buff/debuff/CC pipeline with stacking rules, dispels, cleanses, potency, resistance, tagging, ScriptableObject authoring, UI support, and optional server‑authoritative multiplayer gating via IStatusAuthority.

It is entirely interface‑driven, runtime‑safe, and netcode‑agnostic.

Important: Status Effects does not define gameplay rules or balance. It provides a predictable runtime pipeline for applying them.


📦 Overview

Attach StatusEffectController to any GameObject to manage:

  • Damage‑over‑time / Heal‑over‑time (PoisonStatus, BurnStatus, RegenStatus)
  • Multipliers (SlowStatus, HasteStatus, VulnerabilityStatus)
  • Utility (StunStatus, ThornsStatus, ShieldStatus)
  • Context attribution (StatusContext: instigator, source, tags)
  • Potency & resistance scaling
  • Dispels & cleanses (tier‑based or tag‑based)
  • Optional UI buff bars (icons, stacks, radial timers)
  • Optional authority gating via IStatusAuthority

Responsibility Boundaries

Clear ownership keeps the system predictable and debuggable:

  • StatusEffectController owns lifecycle, stacking, time, and authority
  • Effects own behaviour only (what happens on apply / tick / remove)
  • Receiver‑side modifiers influence incoming math, never effect state
  • UI mirrors controller state; it never drives gameplay

🚀 Quick Start

  1. Add StatusEffectController to a target
  2. (Optional) Add receiver‑side modifiers:
  3. IStatusImmunity
  4. IStatusResistance
  5. IStatusPotency
  6. Create a ScriptableObject definition or build an effect directly
  7. Apply effects using controller APIs

Minimal usage

ctrl.ApplyStatus(
    new PoisonStatus(5f, 10f),
    StatusContext.FromAbility("firebolt")
);

// Preferred: centralized refresh/stack handling
ctrl.ApplyOrRefresh(
    () => new PoisonStatus(5f, 10f),
    StatusContext.FromAbility("firebolt")
);

🧩 Composition Cheat Sheet

Layer Component Purpose Required
Controller StatusEffectController Core runtime manager Yes
Receiver Modifiers IStatusImmunity, IStatusResistance, IStatusPotency Modify incoming effects No
Effects IStatusEffect, TimedStatusEffect Behaviour implementations No
Definitions StatusEffectDefinitionBase ScriptableObject authoring No
Authority IStatusAuthority Multiplayer gating No
UI StatusBuffBar, StatusIconView Visual display No
Integration Movement / Health / Shields Gameplay bridges No

Only the controller is mandatory. All other layers are opt‑in.


🧮 Public API

All public methods on StatusEffectController are runtime‑safe and may be called by gameplay code, AI, abilities, or netcode — provided authority rules allow mutation.

Apply & Refresh

void ApplyStatus(IStatusEffect effect);
void ApplyStatus(IStatusEffect effect, StatusContext ctx);
void ApplyOrRefresh(Func<IStatusEffect> build, StatusContext ctx);

Remove / Cleanse

void RemoveStatus(StatusId id);
void RemoveStatusAt(int index);
void ClearAll();

int Dispel(DispelType type, int minTier = 0);
int CleanseByTag(StatusTag mask);

Queries

bool HasStatus(StatusId id);
int GetStackCount(StatusId id);

IReadOnlyList<IStatusEffect> Active { get; }

bool TryGetFirst(StatusId id, out IStatusEffect e);
IStatusEffect GetFirstOrNull(StatusId id);

bool TryGetFirstContext(StatusId id, out StatusContext ctx);
StatusContext GetContext(IStatusEffect e);

int TryGetAll(StatusId id, List<IStatusEffect> buffer);

Stack Policy

ctrl.SetStackCap(StatusId.Poison, 3);
ctrl.ClearStackCap(StatusId.Poison);

ctrl.SetPerSourceStacks(StatusId.Poison, true);

Potency / Time

ctrl.RecomputePotencyForAll();
ctrl.SetTimeMode(StatusTimeMode.Custom, customTimeSource);

🔔 Events

Context‑aware C# events

  • OnStatusAppliedCtx
  • OnStatusRefreshedCtx
  • OnStatusRemovedCtx
  • OnStatusExpiredCtx

UnityEvents

  • OnStatusApplied
  • OnStatusRefreshed
  • OnStatusRemoved
  • OnStatusExpired

🧱 Stacking & Caps

Policy Behaviour
Replace Remove old → add new
Refresh Reset or extend timer
Stack Multiple concurrent instances

Supports global caps and optional per‑source stacking semantics.


⚖️ Potency & Resistance

Modifier Interface Affects
Potency IStatusPotency Magnitude (DPS / HPS / multipliers)
Resistance IStatusResistance Duration scaling (0–1)

After stat or composition changes:

ctrl.RecomputePotencyForAll();

Updates any effect implementing IAdjustableMagnitude.


🎨 UI

buffBar.controller  = ctrl;
buffBar.iconLibrary = iconLibrary;
buffBar.iconPrefab  = iconPrefab;

Components:

  • StatusBuffBar
  • StatusIconView
  • StatusIconLibrary

UI is fully decoupled from runtime logic and may be removed or replaced freely.


🌐 Authority (Multiplayer)

When requireAuthority = true:

  • Mutations and ticks resolve via IStatusAuthority
  • Typical flow:
  • Client requests
  • Server validates
  • Server mutates
  • Netcode replicates state

If authority is denied or no binder resolves:

No partial behaviour occurs — no ticking, refresh, expiry, or mutation.

This is intentional and prevents desync.

Example binder:

public class MyStatusAuthority : MonoBehaviour, IStatusAuthority
{
    public bool HasAuthority(StatusEffectController ctrl)
        => IsServer; // or IsOwner / HasStateAuthority / etc.
}

Multiplayer Wiring Checklist

  1. Add a scene‑level IStatusAuthority binder
  2. Enable requireAuthority on server‑owned controllers
  3. Tick and mutate statuses server‑side only
  4. Replicate status state via your netcode
  5. Disable global FX on server (enableGlobalFx = false)
  6. Do not mutate effect state directly on clients

🎆 FX

Status effects expose an optional global FX seam via StatusFx.

  • Register FX handlers on clients only
  • Or disable FX entirely on server controllers

FX handlers must never mutate gameplay state.


⚠️ Gotchas

  • TimedStatusEffect.Apply() resets time; use Refresh() to extend
  • Effects must never be reused or cached — always create fresh instances
  • Do not modify TimeRemaining directly
  • UI jitter is coalesced via destroy‑grace in StatusBuffBar
  • Legacy shield bridges may clear all shields (prefer IShieldTicketPool)
  • Potency scaling is absolute from base magnitude
  • Authority required + no binder = frozen effects (by design)

🧩 Extending

Effects

  • Subclass TimedStatusEffect
  • Implement IStatusEffect for passive/custom logic
  • Avoid querying other effects directly — use controller queries

Definitions

  • ScriptableObject authoring via StatusEffectDefinitionBase

Receiver‑side modifiers

  • IStatusPotency, IStatusResistance, IStatusImmunity

Integration

  • Movement, cooldowns, damage, shields, custom sinks

Authority

  • Custom binders implementing IStatusAuthority

Registry

  • Optional registration via StatusRegistry

📘 See Also

  • Abstractions
  • Core
  • Effects
  • Integration
  • Teaching (Editor Panels & Guides)
  • UI
  • Samples