Bank: drink all potions

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
rope 2026-10-27 21:23:00 +00:00
parent bb56d618df
commit 2597a1d7e7
4 changed files with 28 additions and 7 deletions

3
app.py
View file

@ -482,7 +482,8 @@ class Handler(BaseHTTPRequestHandler):
if path == "/api/statue": if path == "/api/statue":
return game.carve_statue(world, player, str(body.get("of")), now) return game.carve_statue(world, player, str(body.get("of")), now)
if path == "/api/potion": if path == "/api/potion":
return game.use_potion(player, str(body.get("item"))) qty = body.get("qty", 1)
return game.use_potion(player, str(body.get("item")), qty if qty == "all" or isinstance(qty, int) else -1)
if path == "/api/prayers": if path == "/api/prayers":
ids = body.get("ids") ids = body.get("ids")
return game.set_prayers(player, ids) if isinstance(ids, list) else "Bad request" return game.set_prayers(player, ids) if isinstance(ids, list) else "Bad request"

17
game.py
View file

@ -2692,18 +2692,24 @@ def potion_effect(player: dict, skill: str) -> dict:
return ITEMS[active["item"]]["potion"] if active else {} return ITEMS[active["item"]]["potion"] if active else {}
def use_potion(player: dict, item: str) -> str | None: def use_potion(player: dict, item: str, qty: int | str = 1) -> str | None:
"""Drink a potion from the bank into its skill's slot (topping up the same potion).""" """Drink potions from the bank into their skill's slot (topping up the same potion)."""
spec = ITEMS.get(item, {}).get("potion") spec = ITEMS.get(item, {}).get("potion")
if not spec: if not spec:
return "That isn't a potion" return "That isn't a potion"
if player["bank"].get(item, 0) < 1: have = player["bank"].get(item, 0)
if have < 1:
return "You don't have one" return "You don't have one"
bank_take(player, item, 1) qty = have if qty == "all" else qty
if not isinstance(qty, int) or qty < 1:
return "Bad amount"
if have < qty:
return "You don't have that many"
bank_take(player, item, qty)
current = player["potions"].get(spec["skill"]) current = player["potions"].get(spec["skill"])
charges = current["charges"] if current and current["item"] == item else 0 charges = current["charges"] if current and current["item"] == item else 0
fresh = round(POTION_CHARGES * (1.25 if player["cls"] == "homesteader" else 1)) fresh = round(POTION_CHARGES * (1.25 if player["cls"] == "homesteader" else 1))
player["potions"][spec["skill"]] = {"item": item, "charges": charges + fresh} player["potions"][spec["skill"]] = {"item": item, "charges": charges + fresh * qty}
return None return None
@ -6270,6 +6276,7 @@ ORDER_RECIPES, ORDER_RAW = _order_sources()
# line here (new / balance / fix), and a new release a new entry on top. # line here (new / balance / fix), and a new release a new entry on top.
PATCH_NOTES = [ PATCH_NOTES = [
{"date": "2026-10-27", "title": "Prayer", "notes": [ {"date": "2026-10-27", "title": "Prayer", "notes": [
("new", "Added \"Drink all\" option to potions in the bank."),
("new", "Firekeeping: Tend all fires. Each log goes on the lowest fire with room in your pile."), ("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"), ("new", "Added resource dumps for Leatherworking: Guard Jerkins"),
("fix", "+Q button: rounded corners."), ("fix", "+Q button: rounded corners."),

View file

@ -1903,6 +1903,7 @@ function itemPanel(id) {
${it.slot ? equipRow(id, it) : ""} ${it.slot ? equipRow(id, it) : ""}
${it.potion ? `<div class="row"><div class="small">${esc(it.potion.text)} for ${C.potion_charges} ${it.potion.skill === "combat" ? "fights" : "actions"}.</div> ${it.potion ? `<div class="row"><div class="small">${esc(it.potion.text)} for ${C.potion_charges} ${it.potion.skill === "combat" ? "fights" : "actions"}.</div>
<button class="btn gold" data-potion="${id}">Drink</button> <button class="btn gold" data-potion="${id}">Drink</button>
${have > 1 ? `<button class="btn sm" data-potion="${id}" data-qty="all">Drink all (${fmt(have)})</button>` : ""}
${S.me.potions[it.potion.skill] && S.me.potions[it.potion.skill].item !== id ? `<div class="small why">Replaces your ${esc(item(S.me.potions[it.potion.skill].item).name.toLowerCase())} (${S.me.potions[it.potion.skill].charges} uses left)</div>` : ""}</div>` : ""} ${S.me.potions[it.potion.skill] && S.me.potions[it.potion.skill].item !== id ? `<div class="small why">Replaces your ${esc(item(S.me.potions[it.potion.skill].item).name.toLowerCase())} (${S.me.potions[it.potion.skill].charges} uses left)</div>` : ""}</div>` : ""}
${it.heal ? `<div class="row"><div class="small muted">Heals ${it.heal} resting between fights.</div> ${it.heal ? `<div class="row"><div class="small muted">Heals ${it.heal} resting between fights.</div>
${S.me.food === id ? `<span class="tag good">Your fighting food</span>` : `<button class="btn sm" data-food="${id}">Eat this first</button>`}</div>` : ""} ${S.me.food === id ? `<span class="tag good">Your fighting food</span>` : `<button class="btn sm" data-food="${id}">Eat this first</button>`}</div>` : ""}
@ -2984,7 +2985,7 @@ document.addEventListener("click", (e) => {
if (d.track) return act("/api/track", { id: d.track }); if (d.track) return act("/api/track", { id: d.track });
if (d.favour) return act("/api/favour", { kind: d.favour }); if (d.favour) return act("/api/favour", { kind: d.favour });
if (d.food) return act("/api/food", { item: d.food === "cheapest" ? null : d.food }); if (d.food) return act("/api/food", { item: d.food === "cheapest" ? null : d.food });
if (d.potion) return act("/api/potion", { item: d.potion }); if (d.potion) return act("/api/potion", { item: d.potion, qty: d.qty === "all" ? "all" : 1 });
if (d.unpost) { if (d.unpost) {
if (!confirm("Delete this message?")) return; if (!confirm("Delete this message?")) return;
return act("/api/board/delete", { id: Number(d.unpost) }); return act("/api/board/delete", { id: Number(d.unpost) });

View file

@ -1636,6 +1636,18 @@ class HerbloreTest(unittest.TestCase):
self.assertEqual(game.ITEMS["mining_potion_6"]["name"], "Mining potion VI") self.assertEqual(game.ITEMS["mining_potion_6"]["name"], "Mining potion VI")
self.assertFalse(any("heal" in p[6] or "double" in p[6] for p in game.POTIONS)) self.assertFalse(any("heal" in p[6] or "double" in p[6] for p in game.POTIONS))
def test_drink_all_potions_stacks_charges(self):
p = self.p
p["bank"] = {"mining_potion_1": 3, "mining_potion_2": 1}
game.use_potion(p, "mining_potion_2")
self.assertIsNone(game.use_potion(p, "mining_potion_1", "all"))
self.assertEqual(p["potions"]["mining"], {"item": "mining_potion_1", "charges": 3 * game.POTION_CHARGES})
self.assertNotIn("mining_potion_1", p["bank"])
self.assertEqual(game.use_potion(p, "mining_potion_1", "all"), "You don't have one")
p["bank"]["mining_potion_1"] = 2
self.assertEqual(game.use_potion(p, "mining_potion_1", 0), "Bad amount")
self.assertEqual(game.use_potion(p, "mining_potion_1", 3), "You don't have that many")
def test_skill_potion_speeds_up_and_uses_charges(self): def test_skill_potion_speeds_up_and_uses_charges(self):
world, p = self.world, self.p world, p = self.world, self.p
plain = game.action_time(world, p, game.ACTION_BY_ID["cut_logs"]) plain = game.action_time(world, p, game.ACTION_BY_ID["cut_logs"])