Firekeeping: tend all fires
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
6e48c72d95
commit
fd85580466
3 changed files with 45 additions and 7 deletions
19
game.py
19
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."),
|
||||
|
|
|
|||
|
|
@ -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 `<div class="panel fire-tools"><div class="logs">${logs}</div>
|
||||
<span class="tag" title="Fire and XP per log">${glyph("stopwatch", "#98a1b4")} ${dur(fk.minutes * 60 / speed)} · ${perLog(pick)} XP</span>
|
||||
<span class="tag" title="Most fuel you can bank in a fire">${glyph("campfire", "#98a1b4")} ${dur(fk.bank * 60 / speed)}</span></div>
|
||||
<div class="fire-grid">${Object.keys(C.fires).map((id) => fireCard(id, pick)).join("")}</div>`;
|
||||
<span class="tag" title="Most fuel you can bank in a fire">${glyph("campfire", "#98a1b4")} ${dur(fk.bank * 60 / speed)}</span>
|
||||
${tendingAll ? `<button class="btn danger" data-stop>Stop</button>`
|
||||
: `<button class="btn primary" data-start="${all}" ${allWhy ? "disabled" : ""} title="${esc(allWhy || "Each log goes on the lowest fire with room in your pile")}">🔥 Tend all</button>`}</div>
|
||||
<div class="fire-grid">${Object.keys(C.fires).map((id) => fireCard(id, pick, tendAllTarget())).join("")}</div>`;
|
||||
}
|
||||
|
||||
function fireCard(id, pick) {
|
||||
function fireCard(id, pick, target) {
|
||||
const me = S.me, spec = C.fires[id], f = S.city.fires[id], n = fireNow(id), skill = skillOf(spec.skill);
|
||||
const mine = me.action?.id.startsWith(`tend_${id}_`);
|
||||
const mine = me.action?.id.startsWith(`tend_${id}_`) || target === id;
|
||||
const start = `tend_${id}_${C.firewood[pick]}`, why = mine ? "" : me.blocked[start];
|
||||
const crafting = f.users.filter((u) => !u.action.startsWith("tend_"));
|
||||
const doing = (u) => C.actions.find((a) => a.id === u.action);
|
||||
|
|
|
|||
|
|
@ -2785,6 +2785,18 @@ class FirekeepingTest(unittest.TestCase):
|
|||
self.assertEqual(game.heat(self.world, "kitchen"), -1)
|
||||
self.assertAlmostEqual(self.kai["xp"]["firekeeping"], 3 * game.TEND_XP[0] * game.fire_minutes(level))
|
||||
|
||||
def test_tending_all_feeds_the_lowest_fire_with_room(self):
|
||||
self.fires["forge"].update(fuel=[{"tier": 0, "left": 600, "by": "sam"}], at=T0)
|
||||
self.fires["kitchen"].update(fuel=[{"tier": 0, "left": game.fire_bank(1) * 60, "by": "kai"}], at=T0) # kai's pile is full
|
||||
self.fires["kiln"].update(fuel=[{"tier": 0, "left": 300, "by": "sam"}], at=T0)
|
||||
self.kai["bank"] = {"logs": 1}
|
||||
self.tend(self.kai, "all", "logs", T0, 35)
|
||||
self.assertEqual([chunk["by"] for chunk in self.fires["still"]["fuel"]], ["kai"]) # out, so lowest
|
||||
self.kai["bank"] = {"logs": 100}
|
||||
for key in ("forge", "kiln", "still"):
|
||||
self.fires[key].update(fuel=[{"tier": 0, "left": game.fire_bank(1) * 60, "by": "kai"}], at=T0 + 35)
|
||||
self.assertEqual(game.start_action(self.world, self.kai, "tend_all_logs", T0 + 35), "Your piles are all full")
|
||||
|
||||
def test_better_logs_need_the_level(self):
|
||||
self.kai["bank"] = {"maple_logs": 3}
|
||||
self.assertEqual(game.start_action(self.world, self.kai, "tend_forge_maple_logs", T0), "Needs level 45")
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue