Queue: swap #1 with the current make

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
rope 2026-10-28 08:32:54 +00:00
parent fa960e1d6c
commit 3d93fabbfd
4 changed files with 43 additions and 1 deletions

3
app.py
View file

@ -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):

21
game.py
View file

@ -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."),

View file

@ -800,13 +800,15 @@ function queuePanel() {
return `<li class="qp-row"><span class="qp-pos">${i + 1}</span>${a.icon}
<span class="qp-name">${esc(a.name)}${why ? `<span class="why small" title="Skipped if still short when its turn comes">${esc(why)}</span>` : ""}</span>
<input type="number" min="1" max="${C.make_max}" value="${e.n}" data-qset="${i}" data-keep="qset-${i}-${e.id}" title="How many">
<button class="btn sm" data-queue-move="${i}" data-to="${i - 1}" ${i ? "" : "disabled"} title="Up">▲</button>
${i || !now ? `<button class="btn sm" data-queue-move="${i}" data-to="${i - 1}" ${i ? "" : "disabled"} title="Up">▲</button>`
: `<button class="btn sm" data-queue-now="0" title="Swap with current">▲</button>`}
<button class="btn sm" data-queue-move="${i}" data-to="${i + 1}" ${i < q.length - 1 ? "" : "disabled"} title="Down">▼</button>
<button class="btn sm danger" data-unqueue="${i}" title="Remove">✕</button></li>`;
}).join("");
return `<div class="queue-panel">
<div class="qp-head"><b>⏳ Queue</b><span class="muted small">${q.length}</span><button class="x" data-queue-menu title="Close">✕</button></div>
${now ? `<div class="qp-row now">${now.icon}<span class="qp-name">${esc(now.name)}<span class="muted small">${fmt(me.action.left)} left</span></span>
<button class="btn sm" data-queue-now="0" ${q.length ? "" : "disabled"} title="Swap with next">▼</button>
<button class="btn sm" data-queue-skip title="Drop this make, on to the next">Skip</button></div>` : ""}
${rows ? `<ol class="qp-list">${rows}</ol>` : `<p class="muted small">Empty. +Q on a recipe or gathering adds to it.</p>`}
<div class="qp-foot"><span class="muted small qp-then">After queue: ${back ? `${esc(back.name)}${now ? `<button class="x" data-queue-then title="Idle after the queue">✕</button>` : ""}` : "idle"}</span>
@ -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 }); }

View file

@ -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"]