diff --git a/app.py b/app.py index 9f032ce..c9b8904 100644 --- a/app.py +++ b/app.py @@ -376,6 +376,9 @@ class Handler(BaseHTTPRequestHandler): if path == "/api/queue/move": index, to = body.get("index"), body.get("to") return game.queue_move(player, index, to) if isinstance(index, int) and isinstance(to, int) else "Not in the queue" + if path == "/api/queue/now": + index = body.get("index") + return game.queue_now(world, player, index, now) if isinstance(index, int) else "Not in the queue" if path == "/api/queue/set": index, qty = body.get("index"), body.get("qty") if not isinstance(index, int) or not isinstance(qty, int): diff --git a/game.py b/game.py index a613023..c853e29 100644 --- a/game.py +++ b/game.py @@ -3252,6 +3252,24 @@ def queue_move(player: dict, index: int, to: int) -> str | None: return None +def queue_now(world: dict, player: dict, index: int, now: float) -> str | None: + """Start a queued make now: the one running goes to the front of the queue with what it had left.""" + queue, current = player["queue"], player["action"] + if not 0 <= index < len(queue): + return "Not in the queue" + if not current or "left" not in current or "order" in current or not queueable(ACTION_BY_ID[current["id"]]): + return "Not making anything" + action = ACTION_BY_ID.get(queue[index]["id"]) + reason = can_start(world, player, action) if action else "No such action" + if reason: + return reason + entry = queue.pop(index) + queue.insert(0, {"id": current["id"], "n": current["left"]}) + player["action"] = {**{k: v for k, v in current.items() if k in ("resume", "resume_plot2")}, + "id": entry["id"], "start": now, "left": entry["n"]} + return None + + def queue_set(player: dict, index: int, qty: int) -> str | None: if not 0 <= index < len(player["queue"]): return "Not in the queue" @@ -6294,6 +6312,9 @@ ORDER_RECIPES, ORDER_RAW = _order_sources() # What changed, shown on the Patch notes page. Newest first; every shipped change adds a # line here (new / balance / fix), and a new release a new entry on top. PATCH_NOTES = [ + {"date": "2026-10-28", "title": "Queue", "notes": [ + ("new", "Queue: ▲ on #1 swaps it with the current make."), + ]}, {"date": "2026-10-27", "title": "Prayer", "notes": [ ("new", "Gathering can be queued."), ("new", "Make X and queue counts go up to 9,999. Queue has no length cap."), diff --git a/static/app.js b/static/app.js index d08ff5f..0acca65 100644 --- a/static/app.js +++ b/static/app.js @@ -800,13 +800,15 @@ function queuePanel() { return `
  • ${i + 1}${a.icon} ${esc(a.name)}${why ? `${esc(why)}` : ""} - + ${i || !now ? `` + : ``}
  • `; }).join(""); return `
    ⏳ Queue${q.length}
    ${now ? `
    ${now.icon}${esc(now.name)}${fmt(me.action.left)} left +
    ` : ""} ${rows ? `
      ${rows}
    ` : `

    Empty. +Q on a recipe or gathering adds to it.

    `}
    After queue: ${back ? `${esc(back.name)}${now ? `` : ""}` : "idle"} @@ -2921,6 +2923,7 @@ document.addEventListener("click", (e) => { if (d.unqueue) return act("/api/queue/remove", { index: Number(d.unqueue) }); if ("queueMenu" in d) { queueOpen = !queueOpen; return render(); } if (d.queueMove) return act("/api/queue/move", { index: Number(d.queueMove), to: Number(d.to) }); + if (d.queueNow) return act("/api/queue/now", { index: Number(d.queueNow) }); if ("queueSkip" in d) return act("/api/queue/skip", {}); if ("queueClear" in d) return act("/api/queue/clear", {}); if (d.join) { tagging = true; return act("/api/join", { id: d.join }); } diff --git a/tests/test_game.py b/tests/test_game.py index d54205c..5408eac 100644 --- a/tests/test_game.py +++ b/tests/test_game.py @@ -212,6 +212,21 @@ class ActionTest(unittest.TestCase): self.assertEqual(p["action"]["id"], "mine_stone") self.assertEqual(game.skip_make(world, p, T0 + 7), "Not making anything") + def test_queue_swap_with_current(self): + world, p = self._smith(20) + game.start_action(world, p, "smith_copper_helmet", T0, once=3) + game.queue_add(world, p, "smith_copper_legs", 1, T0) + game.queue_add(world, p, "smith_copper_sword", 2, T0) + p["action"]["left"] = 2 + self.assertIsNone(game.queue_now(world, p, 1, T0 + 5)) + self.assertEqual((p["action"]["id"], p["action"]["left"], p["action"]["resume"], p["action"]["start"]), + ("smith_copper_sword", 2, "mine_stone", T0 + 5)) + self.assertEqual([(q["id"], q["n"]) for q in p["queue"]], + [("smith_copper_helmet", 2), ("smith_copper_legs", 1)]) + self.assertEqual(game.queue_now(world, p, 2, T0 + 6), "Not in the queue") + p["action"] = {"id": "mine_stone", "start": T0} + self.assertEqual(game.queue_now(world, p, 0, T0 + 6), "Not making anything") + def test_offline_cap(self): world = world_with(("fred", "commoner")) p = world["players"]["fred"]