diff --git a/game.py b/game.py index 2b71f75..0c2ee3d 100644 --- a/game.py +++ b/game.py @@ -1318,6 +1318,10 @@ ACTIONS = [ *[_action(f"tend_{fire}_{log}", "firekeeping", f"Tend the {spec['name'].lower()}", TIER_LEVELS[tier], 30, 0, {log: 1}, special="tend", tend=fire, tier=tier, group=spec["name"]) for fire, spec in FIRES.items() for tier, log in enumerate(FIREWOOD)], + # Tending all: each log goes on the lowest fire with room in your pile. + *[_action(f"tend_all_{log}", "firekeeping", "Tend all fires", TIER_LEVELS[tier], 30, 0, + {log: 1}, special="tend", tend=None, tier=tier, group="All fires") + for tier, log in enumerate(FIREWOOD)], ] # --- dungeon loot -------------------------------------------------------------- @@ -2768,6 +2772,14 @@ def _room(fire: dict, player: dict, tier: int) -> float: return -1 if short > cheaper + 1e-6 else max(0.0, short) +def tend_target(world: dict, player: dict, action: dict) -> str | None: + """The fire a tend feeds, or None if the log won't fit: its own fire, or when tending + all, the one with the least fuel left among those with room in your pile.""" + fires, tier = world["city"]["fires"], action["tier"] + room = [key for key in ([action["tend"]] if action["tend"] else FIRES) if _room(fires[key], player, tier) >= 0] + return min(room, key=lambda key: sum(chunk["left"] for chunk in fires[key]["fuel"]), default=None) + + def feed_fire(world: dict, player: dict, fire_id: str, tier: int) -> float: """Put one log on your pile in a fire, pushing out your cheapest fuel if it's full. Returns the minutes added.""" fire, level = world["city"]["fires"][fire_id], skill_level(player, "firekeeping") @@ -2860,8 +2872,8 @@ def can_start(world: dict, player: dict, action: dict) -> str | None: pooled = order["provided"].get(item, 0) if order else 0 if pooled + player["bank"].get(item, 0) < qty: return f"Out of {ITEMS[item]['name'].lower()}" - if action.get("special") == "tend" and _room(world["city"]["fires"][action["tend"]], player, action["tier"]) < 0: - return f"Your pile in the {FIRES[action['tend']]['name'].lower()} is full" + if action.get("special") == "tend" and not tend_target(world, player, action): + return f"Your pile in the {FIRES[action['tend']]['name'].lower()} is full" if action["tend"] else "Your piles are all full" if action.get("special") == "sabotage": raid = upcoming_raid(world) if not raid or intel_level(world, raid) < 3: @@ -3018,7 +3030,7 @@ def _complete(world: dict, player: dict, action: dict, now: float, rng: random.R xp = DRILL_XP * (gain + boost) * mods["xp"] threat["drilled"] = min(drill_needed(threat), threat.get("drilled", 0) + gain + boost) if action.get("special") == "tend": - xp = TEND_XP[action["tier"]] * feed_fire(world, player, action["tend"], action["tier"]) * mods["xp"] + xp = TEND_XP[action["tier"]] * feed_fire(world, player, tend_target(world, player, action), action["tier"]) * mods["xp"] if warmed: _warm(world, player, action, xp, now) if action.get("special") == "labour": @@ -6258,6 +6270,7 @@ ORDER_RECIPES, ORDER_RAW = _order_sources() # line here (new / balance / fix), and a new release a new entry on top. PATCH_NOTES = [ {"date": "2026-10-27", "title": "Prayer", "notes": [ + ("new", "Firekeeping: Tend all fires. Each log goes on the lowest fire with room in your pile."), ("new", "Added resource dumps for Leatherworking: Guard Jerkins"), ("fix", "+Q button: rounded corners."), ("fix", "Make box: number field sits in front of +Q."), diff --git a/static/app.js b/static/app.js index 6c0c1fe..f566bf6 100644 --- a/static/app.js +++ b/static/app.js @@ -1111,8 +1111,19 @@ function pickFirewood() { return [...open].reverse().find((t) => me.bank[C.firewood[t]]) ?? 0; } +// The fire tending all feeds next: the least fuel left among those with room in your pile. +function tendAllTarget() { + const action = S.me.action && C.actions.find((a) => a.id === S.me.action.id), fk = S.me.firekeeping; + if (!action?.id.startsWith("tend_all_")) return null; + const sum = (fuel) => fuel.reduce((total, c) => total + c.left, 0); + const room = Object.keys(C.fires).map((id) => [id, fireNow(id)]) + .filter(([, n]) => sum(n.mine) + (fk.minutes - fk.bank) * 60 <= sum(n.mine.filter((c) => c.tier < action.tier)) + 1e-6); + return room.reduce((best, f) => (!best || f[1].left < best[1].left ? f : best), null)?.[0] ?? null; +} + function firekeepingPage() { const me = S.me, fk = me.firekeeping, pick = pickFirewood(), speed = Object.values(S.city.fires)[0].speed; + const all = `tend_all_${C.firewood[pick]}`, tendingAll = me.action?.id.startsWith("tend_all_"), allWhy = me.blocked[all]; const perLog = (tier) => +(C.tend_xp[tier] * fk.minutes).toFixed(1); const logs = C.firewood.map((log, tier) => { const level = tendLevel(tier), locked = me.levels.firekeeping < level; @@ -1121,13 +1132,15 @@ function firekeepingPage() { }).join(""); return `