using Unity.Entities; namespace ProjectM.Simulation { /// /// SERVER-ONLY expiry tracker paired with a by . It is NOT a /// [GhostField] and lives in a SEPARATE buffer so the replicated layout stays /// byte-identical — adding ANY member (even non-ghost) to a [GhostField] buffer element regenerates its /// serializer/stride/hash = an effective ghost re-bake. To grant a TIMED buff, append both a StatModifier and a /// TimedModifier sharing one unique SourceId; TimedModifierExpirySystem removes the matching StatModifier /// when elapses, and that removal replicates for free via the StatModifier [GhostField] /// buffer (OwnerSendType.All), so StatRecomputeSystem reverts the effective stat on both worlds with no change. /// public struct TimedModifier : IBufferElementData { /// Matches the this row governs. public uint SourceId; /// Server tick at which the paired StatModifier expires (0 = no expiry / inert; schedule via TickUtil.NonZero). public uint UntilTick; } /// Pure helpers for removing modifiers by provenance (clear-by-type / timed expiry). Deterministic, no RNG/wall-clock. public static class TimedModifierUtil { /// Remove every row whose SourceId matches (RemoveAtSwapBack). Returns the count removed. public static int RemoveBySourceId(DynamicBuffer mods, uint sourceId) { int removed = 0; for (int j = mods.Length - 1; j >= 0; j--) if (mods[j].SourceId == sourceId) { mods.RemoveAtSwapBack(j); removed++; } return removed; } /// Remove every row whose SourceId lies in [lo, hiExclusive) — the /// run-scoped BOON strip (per-pick distinct ids share the band; one call clears the whole run's boons while /// class/meta/equip bands survive untouched). Idempotent; RemoveAtSwapBack. Returns the count removed. public static int RemoveBySourceIdRange(DynamicBuffer mods, uint lo, uint hiExclusive) { int removed = 0; for (int j = mods.Length - 1; j >= 0; j--) if (mods[j].SourceId >= lo && mods[j].SourceId < hiExclusive) { mods.RemoveAtSwapBack(j); removed++; } return removed; } } }