From 70be493558255d302be9a6611c3393a0a7c7101e Mon Sep 17 00:00:00 2001 From: rope Date: Wed, 28 Oct 2026 12:14:18 +0000 Subject: [PATCH] Work orders keep the crafting queue Co-Authored-By: Claude Opus 5.5 --- app.py | 2 +- game.py | 38 +++++++++++++++++++++++++++----------- static/app.js | 2 +- tests/test_game.py | 23 +++++++++++++++++++++++ 4 files changed, 52 insertions(+), 13 deletions(-) diff --git a/app.py b/app.py index c32643f..433d3a2 100644 --- a/app.py +++ b/app.py @@ -419,7 +419,7 @@ class Handler(BaseHTTPRequestHandler): if path == "/api/order/work": return game.work_order(world, player, body.get("id"), now) if path == "/api/order/drop": - return game.claim_order(world, player, body.get("id"), claim=False) + return game.drop_order(world, player, body.get("id"), now) if path == "/api/order/deliver": return game.deliver_order(world, player, body.get("id"), now) if path == "/api/seen": diff --git a/game.py b/game.py index b2ea052..bde367c 100644 --- a/game.py +++ b/game.py @@ -3253,7 +3253,8 @@ def process_player(world: dict, player: dict, now: float, rng: random.Random | N burn_fires(world, current["start"] + duration) reason = can_start(world, player, action) if reason: - if next_queued(world, player, current["start"], summary, skipped=current): + if next_queued(world, player, current["start"], summary, skipped=current) or \ + "order" in current and finish_once(world, player, current["start"], reason, summary): current = player["action"] continue stop(player, reason) @@ -3277,7 +3278,8 @@ def process_player(world: dict, player: dict, now: float, rng: random.Random | N # Out of materials: stop now, not once the next bar has filled. reason = can_start(world, player, growing(player)) if reason: - if next_queued(world, player, current["start"], summary, skipped=current): + if next_queued(world, player, current["start"], summary, skipped=current) or \ + "order" in current and finish_once(world, player, current["start"], reason, summary): current = player["action"] continue stop(player, reason) @@ -3322,7 +3324,7 @@ def queue_add(world: dict, player: dict, action_id: str, qty: int, now: float) - reason = queue_reason(world, player, action) if reason: return reason - if not (player["action"] and "left" in player["action"]): + if not (player["action"] and ("left" in player["action"] or "order" in player["action"])): return start_action(world, player, action_id, now, once=qty) player["queue"].append({"id": action_id, "n": qty}) return None @@ -3985,16 +3987,10 @@ def remove_orders(world: dict, player: dict, order_id=None) -> str | None: return None -def claim_order(world: dict, player: dict, order_id, claim: bool = True) -> str | None: +def claim_order(world: dict, player: dict, order_id) -> str | None: order = order_by_id(world, order_id) if not order or order["status"] != "open" or order["kind"] != "craft": return "No such order" - if not claim: - if order["claimed"] == player["id"]: - order["claimed"] = None - if (player["action"] or {}).get("order") == order["id"]: - stop(player) - return None if order["by"] == player["id"]: return "That's your own order" if order["claimed"] not in (None, player["id"]): @@ -4005,8 +4001,21 @@ def claim_order(world: dict, player: dict, order_id, claim: bool = True) -> str return None +def drop_order(world: dict, player: dict, order_id, now: float) -> str | None: + """Let go of an order you'd claimed. If you were crafting it: on with the queue, or back to what you were doing.""" + order = order_by_id(world, order_id) + if not order or order["status"] != "open" or order["kind"] != "craft": + return "No such order" + if order["claimed"] == player["id"]: + order["claimed"] = None + if (player["action"] or {}).get("order") == order["id"]: + finish_once(world, player, now) + return None + + def work_order(world: dict, player: dict, order_id, now: float) -> str | None: - """Claim a crafting order and start making it.""" + """Claim a crafting order and start making it. A make that was running goes back to the + front of the queue with what it had left; anything else is what comes after.""" order = order_by_id(world, order_id) had = order and order["claimed"] == player["id"] error = claim_order(world, player, order_id) @@ -4022,6 +4031,12 @@ def work_order(world: dict, player: dict, order_id, now: float) -> str | None: return reason if previous and previous.get("party") in world["parties"]: leave_party(world, player, previous["party"], now) + if previous and "order" not in previous: + if "left" in previous and queueable(ACTION_BY_ID[previous["id"]]): + player["queue"].insert(0, {"id": previous["id"], "n": previous["left"]}) + elif "left" not in previous: + previous = {"resume": previous["id"], "resume_plot2": previous.get("plot2")} + player["action"].update({k: v for k, v in (previous or {}).items() if k in ("resume", "resume_plot2") and v}) player["stopped"] = None return None @@ -6576,6 +6591,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-28", "title": "Queue", "notes": [ + ("new", "Work orders no longer erase your crafting queue"), ("new", "Items link to where they're made: click to jump to the recipe."), ("new", "Queue: ▲ on #1 swaps it with the current make."), ("new", "Updated Guard kit, added recipes for crafting professions to contribute to defence of town without needing combat-related loot."), diff --git a/static/app.js b/static/app.js index f0127b5..b252c9e 100644 --- a/static/app.js +++ b/static/app.js @@ -2489,7 +2489,7 @@ function orderCard(o) { && !S.me.blocked[o.action].startsWith("Out of") ? S.me.blocked[o.action] : ""; buttons = `${o.claimed === S.uid ? `` : ""} ${have ? `` : ""} - ${working ? `Crafting` : ``}`; + ${working ? `Crafting` : ``}`; } const reward = o.kind === "gather" ? `${fmt(Math.round(o.reward / o.qty))} each` : fmt(o.reward); return `
diff --git a/tests/test_game.py b/tests/test_game.py index b1ed066..42a6822 100644 --- a/tests/test_game.py +++ b/tests/test_game.py @@ -2610,6 +2610,29 @@ class WorkOrderTest(unittest.TestCase): self.assertNotIn("oak_plank", self.fred["bank"]) # the crafter brings what wasn't provided self.assertIsNone(self.fred["action"]) + def test_an_order_mid_queue_puts_the_make_back_in_line(self): + self.post_hammer() + order = self.world["orders"][0] + self.fred["bank"] = {"oak_plank": 1, "copper_bar": 8} + game.start_action(self.world, self.fred, "mine_stone", T0) + game.start_action(self.world, self.fred, "smith_copper_helmet", T0, once=2) + game.queue_add(self.world, self.fred, "smith_copper_legs", 1, T0) + self.assertIsNone(game.work_order(self.world, self.fred, order["id"], T0)) + self.assertEqual(self.fred["queue"], [{"id": "smith_copper_helmet", "n": 2}, {"id": "smith_copper_legs", "n": 1}]) + summary = game.process_player(self.world, self.fred, T0 + 3600, random.Random(1)) + self.assertEqual(order["status"], "done") + self.assertEqual((self.fred["bank"].get("copper_helmet"), self.fred["bank"].get("copper_legs")), (2, 1)) + self.assertEqual((self.fred["action"]["id"], self.fred["queue"], summary["stopped"]), ("mine_stone", [], None)) + + def test_an_order_goes_back_to_what_you_were_doing(self): + self.post_hammer() + order = self.world["orders"][0] + self.fred["bank"] = {"oak_plank": 1} + game.start_action(self.world, self.fred, "mine_stone", T0) + self.assertIsNone(game.work_order(self.world, self.fred, order["id"], T0)) + self.assertIsNone(game.drop_order(self.world, self.fred, order["id"], T0)) + self.assertEqual((self.fred["action"]["id"], order["claimed"]), ("mine_stone", None)) + def test_cancelling_refunds_what_is_left(self): self.post_hammer() order = self.world["orders"][0]