t1k:unity:dots-combat:modifiers
| Field | Value |
|---|---|
| Module | dots-combat |
| Version | 2.3.8 |
| Effort | medium |
| Tools | — |
Keywords: 4-scope, ActModifiers, booster, consumable, modifier, PhaseModifiers, RunModifiers, status effect, StepModifiers
How to invoke
Section titled “How to invoke”/t1k:unity:dots-combat:modifiersDOTS Modifiers — com.the1studio.dots-modifiers
Section titled “DOTS Modifiers — com.the1studio.dots-modifiers”Namespace: DOTSModifiers.* | Package: com.the1studio.dots-modifiers
Depends on: DOTSCore.*, DOTSCombat.*
When This Skill Triggers
Section titled “When This Skill Triggers”- Implementing run/act/step/phase scoped stat modifiers (wagers, daily modifiers, skill-tree bonuses, set bonuses)
- Adding status effects (Poison, Burn, Freeze, Stun, Slow) that expire over time
- Building a consumable / booster system (one-shot items that push modifiers onto a target)
- Looking for
RequestBufferSingletonHelper— the drain-and-clear singleton-buffer pattern - Using
ModifierKind,ModifierElement,RunModifiers,ActModifiers,StepModifiers,PhaseModifiers - Projecting modifier elements into
StatModifierviaModifierTranslator
4-Scope Architecture
Section titled “4-Scope Architecture”The library uses four DynamicBuffer components, each with a distinct lifetime:
| Buffer | Cleared when | Typical use |
|---|---|---|
RunModifiers | Run reset | Wager choices, hero passives selected at run start |
ActModifiers | Act transition | Daily modifiers, weekly events scoped to one act |
StepModifiers | Encounter step advance | Step-only bonuses, single-encounter cards |
PhaseModifiers | Phase change (consumer-owned) | Short-duration status effects, booster consumables |
All four buffers hold ModifierElement structs. The element carries a ModifierKind discriminator so downstream apply-systems know which feature emitted the entry.
Key Types
Section titled “Key Types”Components
Section titled “Components”| Type | Location | Notes |
|---|---|---|
ModifierElement | Components/ | Generic payload — Kind, SourceId, Tier, ValueA, ValueB, FlagMask, ExpiresAtSeconds |
RunModifiers | Components/ | IBufferElementData on run-scope singleton |
ActModifiers | Components/ | IBufferElementData on act-scope singleton |
StepModifiers | Components/ | IBufferElementData on step-scope singleton |
PhaseModifiers | Components/ | IBufferElementData per target — status effects and consumables write here |
ModifierTargetTag | Components/ | Marks entities eligible for modifier application |
LastAppliedRollupHash | Components/ | FNV-1a gate — skips StatModifier rebuild if buffer unchanged |
Status Effects (DOTSModifiers.StatusEffects)
Section titled “Status Effects (DOTSModifiers.StatusEffects)”| Type | Notes |
|---|---|
StatusEffectVariant | Byte enum: Poison, Burn, Freeze, Stun, Slow |
StatusEffectApplyRequest | Singleton-buffer entry: TargetEntity, Variant, DurationSec, Magnitude |
StatusEffectRequestSingletonTag | Marker tag for the request singleton entity |
StatusEffectActiveState | Per-frame rollup — IsImmobilized, SpeedMultiplier |
StatusEffectApplySystem | Drains request buffer → pushes ModifierElement into PhaseModifiers |
StatusEffectTickSystem | Rewrites StatusEffectActiveState from PhaseModifiers each frame |
Consumables (DOTSModifiers.Consumables)
Section titled “Consumables (DOTSModifiers.Consumables)”| Type | Notes |
|---|---|
ConsumableActivationRequest | Singleton-buffer entry: TargetEntity, pre-resolved Element |
ConsumableRequestSingletonTag | Marker tag for the consumable request singleton entity |
IConsumableRegistry<TId> | Contract for consumer catalogs — IsKnown(id), BuildModifierElement(id, time) |
ConsumableApplySystem | Drains buffer → appends Element to target’s PhaseModifiers |
Systems (DOTSModifiers.Systems)
Section titled “Systems (DOTSModifiers.Systems)”| Type | Notes |
|---|---|
ModifierApplySystem | Projects all four buffer scopes into StatModifier rows via ModifierTranslator |
ModifierExpirySystem | Prunes PhaseModifiers entries when ElapsedTime > ExpiresAtSeconds |
ModifierTranslator | Static: ToStatModifier(ModifierElement) — Tier→StatType, ValueA→value, FlagMask&0x3→ModifierType |
RequestBufferSingletonHelper | Lazy-create pattern for singleton-tag + request-buffer drain pattern |
Coexistence with DOTSCombat.StatusEffect
Section titled “Coexistence with DOTSCombat.StatusEffect”Two distinct status-effect systems coexist by design (Phase 4, Option A):
DOTSCombat.StatusEffect— the legacy combat-pipeline variant for generic live combat (MaxStacks, StackBehavior). Lives inDOTSCombat.*.DOTSModifiers.StatusEffects.StatusEffectVariant— the typed roguelike variant (Poison/Burn/Freeze/Stun/Slow, DurationSec, Magnitude). Lives here.
If both packages are imported, use the fully qualified namespace to resolve the StatusEffectTickSystem ambiguity (see integration defect #6 in the BPC extraction handoff).
Quick How-To
Section titled “Quick How-To”Apply a booster consumable (BPC pattern)
Section titled “Apply a booster consumable (BPC pattern)”// Producer (managed, in UI layer):var registry = this.container.Resolve<IConsumableRegistry<BoosterId>>();var element = registry.BuildModifierElement(boosterId, (float)World.DefaultGameObjectInjectionWorld.Time.ElapsedTime);
var buffer = RequestBufferSingletonHelper.GetOrCreate<ConsumableRequestSingletonTag, ConsumableActivationRequest>(ref state);buffer.Add(new ConsumableActivationRequest { TargetEntity = playerEntity, Element = element });Apply a status effect
Section titled “Apply a status effect”var buffer = RequestBufferSingletonHelper.GetOrCreate<StatusEffectRequestSingletonTag, StatusEffectApplyRequest>(ref state);buffer.Add(new StatusEffectApplyRequest{ TargetEntity = enemyEntity, Variant = StatusEffectVariant.Poison, DurationSec = 5f, Magnitude = 10f, // 10 damage/second});Reference: references/4-scope-guide.md, references/consumable-pattern.md, references/status-effect-coexistence.md
Gotchas
Section titled “Gotchas”ExpiresAtSecondsuses monotonicSystemAPI.Time.ElapsedTime— not per-encounter clock. Run-reset does NOT reset this clock. Use short durations or a custom expiry pass for encounter-scoped effects.RequestBufferSingletonHelperis NOT Burst-compatible — it callstypeof(T).Name(managed). Call it from a non-Burst entry point; access the buffer directly in Burst code.ModifierApplySystemcleans up its own rows usingModifierSource.Auraas the discriminator. Do NOT manually remove rows emitted by this system — the next apply pass overwrites them.StatusEffectTickSystemfully rewritesStatusEffectActiveStateeach frame — it is a derived component, not a persistent source. Do not read it outside of the frame it was written.- Fully qualify when both
DOTSCombatandDOTSModifiersare referenced in the same asmdef —StatusEffectTickSystemexists in both namespaces (integration defect #6). ConsumableApplySystemacquires theEndSimulationEntityCommandBufferSystemsingleton LAZILY — only on the cold (structural-change) branch, never unconditionally atOnUpdatestart. The system has two paths: a hot path (target already has thePhaseModifiersbuffer → inlinebuffer.Add, no ECB) and a cold path (target lacks the buffer →ecb.AddBuffer<PhaseModifiers>+AppendToBuffer). CallingSystemAPI.GetSingleton<EndSimulationEntityCommandBufferSystem.Singleton>()at the top ofOnUpdatemakes the system hard-depend onEndSimulationEntityCommandBufferSystemexisting in every world — it throws in worlds that don’t register the EndSim ECB (minimal / registry-contract test worlds, custom bootstrap worlds), silently failing the entire system update for BOTH paths. Correct pattern: gate the acquire behind the cold-path branch with a one-shot guard.General rule: only acquire an ECB singleton inside the branch that actually performs the structural change — an unconditional acquire couples the system to that ECB system’s presence in every world. SeeEntityCommandBuffer ecb = default;bool ecbAcquired = false;// hot path: inline buffer.Add(), no ECB// cold path:if (!ecbAcquired){ecb = SystemAPI.GetSingleton<EndSimulationEntityCommandBufferSystem.Singleton>().CreateCommandBuffer(state.WorldUnmanaged);ecbAcquired = true;}ecb.AddBuffer<PhaseModifiers>(req.TargetEntity);t1k:unity:dots-core:entity-command-buffer. (Fixed in unity-dots-libraryd42be59.)