Consumable host: initialise at activation, dispose at expiry, instantiate consumables

ActivateConsumable runs Initialize on the effect's behaviours; expiry disposes
them before Resolver.Forget, so a consumable's modifiers exist exactly while
its effect is active. ItemManager.CreateInstance returns a ConsumableItem for a
consumable design. StatResolver.DetachModifier drops an entry left with
nothing attached, so ModifierEntryCount returns to baseline. LoadoutTests'
hand-built ConsumableItem goes through CreateInstance. Builds.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Meta Vens 2026-10-09 20:19:22 +02:00
commit aaab561f98
5 changed files with 20 additions and 7 deletions

View file

@ -860,7 +860,13 @@ public abstract class Entity
public void ActivateConsumable(ConsumableItem item)
{
_activeConsumables.Add(new ConsumableItemEffect(item, this));
var effect = new ConsumableItemEffect(item, this);
// The same hook Activate runs over Equipment: a behaviour that computes its targets there (StatModifier)
// would otherwise reach its second tick uninitialised.
foreach (var behavior in effect.Behaviors)
if (behavior is IInitializableBehavior initializable)
initializable.Initialize();
_activeConsumables.Add(effect);
}
public ConsumableItemEffect FindActiveConsumable(ConsumableItemData data)
@ -1453,6 +1459,11 @@ public abstract class Entity
// Cut 2 Gate 1 fix (docs/stats-and-power-cut.md): an expired consumable dropped out of this
// list without ever telling the resolver, leaving its generation/cache/modifier entries
// reachable (keyed by this ConsumableItemEffect instance) for the rest of the process.
// A StatModifier's attachments are keyed by its target items, not by the effect, so Forget alone
// leaves a boost attached to its targets: dispose each behaviour first.
foreach (var behavior in _activeConsumables[i].Behaviors)
if (behavior is IDisposable disposable)
disposable.Dispose();
Resolver.Forget(_activeConsumables[i]);
_activeConsumables.RemoveAt(i--);
}

View file

@ -183,6 +183,9 @@ public class ItemManager
};
}
if (design is ConsumableItemData)
return new ConsumableItem { Data = l.Design, Lot = lot };
return new CompoundCommodity
{
Data = l.Design, Lot = lot

View file

@ -141,6 +141,9 @@ public class StatResolver
{
set.Scale.Remove(modifierKey);
set.Constant.Remove(modifierKey);
// An entry with nothing attached is no entry: a modifier that is gone leaves no trace in the resolver.
if (set.Scale.Count == 0 && set.Constant.Count == 0)
_modifiers.Remove(entry);
}
_cache.Remove(entry);
}