Queue: swap #1 with the current make
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
fa960e1d6c
commit
3d93fabbfd
4 changed files with 43 additions and 1 deletions
3
app.py
3
app.py
|
|
@ -376,6 +376,9 @@ class Handler(BaseHTTPRequestHandler):
|
||||||
if path == "/api/queue/move":
|
if path == "/api/queue/move":
|
||||||
index, to = body.get("index"), body.get("to")
|
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"
|
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":
|
if path == "/api/queue/set":
|
||||||
index, qty = body.get("index"), body.get("qty")
|
index, qty = body.get("index"), body.get("qty")
|
||||||
if not isinstance(index, int) or not isinstance(qty, int):
|
if not isinstance(index, int) or not isinstance(qty, int):
|
||||||
|
|
|
||||||
21
game.py
21
game.py
|
|
@ -3252,6 +3252,24 @@ def queue_move(player: dict, index: int, to: int) -> str | None:
|
||||||
return 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:
|
def queue_set(player: dict, index: int, qty: int) -> str | None:
|
||||||
if not 0 <= index < len(player["queue"]):
|
if not 0 <= index < len(player["queue"]):
|
||||||
return "Not in the 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
|
# 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.
|
# line here (new / balance / fix), and a new release a new entry on top.
|
||||||
PATCH_NOTES = [
|
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": [
|
{"date": "2026-10-27", "title": "Prayer", "notes": [
|
||||||
("new", "Gathering can be queued."),
|
("new", "Gathering can be queued."),
|
||||||
("new", "Make X and queue counts go up to 9,999. Queue has no length cap."),
|
("new", "Make X and queue counts go up to 9,999. Queue has no length cap."),
|
||||||
|
|
|
||||||
|
|
@ -800,13 +800,15 @@ function queuePanel() {
|
||||||
return `<li class="qp-row"><span class="qp-pos">${i + 1}</span>${a.icon}
|
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>
|
<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">
|
<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" 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>`;
|
<button class="btn sm danger" data-unqueue="${i}" title="Remove">✕</button></li>`;
|
||||||
}).join("");
|
}).join("");
|
||||||
return `<div class="queue-panel">
|
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>
|
<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>
|
${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>` : ""}
|
<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>`}
|
${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>
|
<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 (d.unqueue) return act("/api/queue/remove", { index: Number(d.unqueue) });
|
||||||
if ("queueMenu" in d) { queueOpen = !queueOpen; return render(); }
|
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.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 ("queueSkip" in d) return act("/api/queue/skip", {});
|
||||||
if ("queueClear" in d) return act("/api/queue/clear", {});
|
if ("queueClear" in d) return act("/api/queue/clear", {});
|
||||||
if (d.join) { tagging = true; return act("/api/join", { id: d.join }); }
|
if (d.join) { tagging = true; return act("/api/join", { id: d.join }); }
|
||||||
|
|
|
||||||
|
|
@ -212,6 +212,21 @@ class ActionTest(unittest.TestCase):
|
||||||
self.assertEqual(p["action"]["id"], "mine_stone")
|
self.assertEqual(p["action"]["id"], "mine_stone")
|
||||||
self.assertEqual(game.skip_make(world, p, T0 + 7), "Not making anything")
|
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):
|
def test_offline_cap(self):
|
||||||
world = world_with(("fred", "commoner"))
|
world = world_with(("fred", "commoner"))
|
||||||
p = world["players"]["fred"]
|
p = world["players"]["fred"]
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue