Appearance
How to Build: Consumables
In an auto-battler, the player isn't clicking frantically during combat — the game fights for them. But that doesn't mean there are no decisions. Consumables are where player strategy meets automation: you configure when potions should fire, then the system handles the rest. Set the threshold too high and you waste potions. Set it too low and you might die before one triggers.
For the prototype, there's only one consumable: HP potions. But the pattern we'll build is clean enough to extend later.
The ConsumableSettings trait
The player entity carries a ConsumableSettings trait that controls auto-use behavior.
typescript
type ConsumableSettings = {
enabled: boolean // master toggle for auto-use
hpThreshold: number // use when HP drops below this percentage (0.0 - 1.0)
}Default values: enabled: true, hpThreshold: 0.4 (40%). This means out of the box, the player auto-drinks a potion whenever their HP drops below 40% during combat.
Potion count itself lives on the Inventory trait (from the inventory system) — Inventory.potions is a simple integer.
The tick system
tickConsumables runs every game tick, slotted right after the combat tick in the Phase 3 execution order. This placement is deliberate: potions respond to damage taken this tick, before death checking runs.
typescript
function tickConsumables(world: World): voidThe system queries for the player entity with Health, Inventory, ConsumableSettings, and CombatState. Then it checks a chain of conditions:
- Is the player in combat? (No point using potions while walking.)
- Is auto-use enabled in settings?
- Is the player's HP percentage below the threshold?
- Does the player have potions in inventory?
If all four conditions pass, a potion is consumed: the inventory count decrements by one and heal is called on the player for the potion's healing amount.
Notice how this runs every tick, not just when damage is taken. This is important because the player might have been below the threshold for several ticks but had zero potions. The moment they somehow gain a potion (unlikely in the prototype, but the system shouldn't assume), it fires immediately.
The settings User Interface
The player needs a way to configure their consumable behavior. This lives in the skills/inventory menu panel as two controls:
- A toggle for enabling or disabling auto-use entirely
- A slider (or numeric input) for the HP threshold percentage
When the player changes these values, the UI writes directly to the ConsumableSettings trait on the player entity. The tick system picks up the changes on the next tick — no event bus, no callbacks, just ECS data flow.
This is a great example of why ECS-driven UI works well for configuration. The "settings" aren't stored in React state or a separate config object — they're a trait on the player entity, saved and loaded with everything else.
Why auto-use instead of manual
In a traditional RPG, the player clicks a potion during combat. In an auto-battler where combat happens automatically at 4 ticks per second, manual potion use would either be too slow (you can't react in 250 milliseconds) or require pausing the game. Auto-use with a configurable threshold gives the player strategic control without requiring twitch reflexes.
The threshold choice is genuinely interesting: setting it to 80% means you stay healthy but burn through potions fast. Setting it to 20% is efficient but risky — a big hit might kill you before the potion fires. Finding the sweet spot for your current content level is a real decision.
Relevant files
src/core/systems/tick-consumables.ts — Auto-use potion check each tickWith consumables in place, we have every Phase 3 combat system covered: health and regen, enemy spawning, the combat tick, aggro and leashing, death and resets, gold drops, and now consumables. The player can walk through a generated level, fight enemies automatically, earn gold and experience, use potions to survive, and respawn into a fresh world if they fall. That's a playable game loop.