Queue: gathering too

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
rope 2026-10-27 23:02:57 +00:00
parent a723558fb2
commit 45a51476a9
3 changed files with 19 additions and 9 deletions

View file

@ -3185,15 +3185,15 @@ def stop(player: dict, reason: str | None = None) -> None:
def queueable(action: dict) -> bool: def queueable(action: dict) -> bool:
"""Crafting: turns materials into something. Only these can be queued.""" """Crafting and gathering: makes or gathers something. Only these can be queued."""
return bool(action["inputs"] and action["outputs"]) and not ( return bool(action["outputs"]) and not (
action.get("enemy") or action.get("special") or action.get("together")) action.get("enemy") or action.get("special") or action.get("together"))
def queue_reason(world: dict, player: dict, action: dict) -> str | None: def queue_reason(world: dict, player: dict, action: dict) -> str | None:
"""Why it can't go in the queue. Materials are checked when its turn comes.""" """Why it can't go in the queue. Materials are checked when its turn comes."""
if not queueable(action): if not queueable(action):
return "Only crafting can be queued" return "Only crafting and gathering can be queued"
if skill_level(player, action["skill"]) < action["level"]: if skill_level(player, action["skill"]) < action["level"]:
return f"Needs level {action['level']}" return f"Needs level {action['level']}"
if action.get("pattern") and action["pattern"] not in player.get("patterns", []): if action.get("pattern") and action["pattern"] not in player.get("patterns", []):
@ -6286,6 +6286,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", "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."),
("balance", "Rebalanced favour values of items"), ("balance", "Rebalanced favour values of items"),
("balance", "Prospecting: 3 ore per try (was 5), 50% gem chance (was 15%)."), ("balance", "Prospecting: 3 ore per try (was 5), 50% gem chance (was 15%)."),

View file

@ -20,7 +20,7 @@ let seenOrders = null; // order ids already known, so only new ones toast
let deliveredSeen = {}; // how much of each order had arrived let deliveredSeen = {}; // how much of each order had arrived
let earnedSeen = {}; // gold each order had paid you for reagents let earnedSeen = {}; // gold each order had paid you for reagents
let pickedClass = null; // registration let pickedClass = null; // registration
let queueOpen = false; // the crafting queue panel under the top bar let queueOpen = false; // the queue panel under the top bar
const tabs = {}; // skill -> selected recipe tab const tabs = {}; // skill -> selected recipe tab
let refreshing = false; let refreshing = false;
let refreshedFor = null; // action (start and length) a completion refresh was sent for let refreshedFor = null; // action (start and length) a completion refresh was sent for
@ -777,7 +777,7 @@ function topbar() {
chips += `<span class="chip muted">Idle${S.me.stopped ? ` · ${esc(S.me.stopped)}` : ""}</span>`; chips += `<span class="chip muted">Idle${S.me.stopped ? ` · ${esc(S.me.stopped)}` : ""}</span>`;
} }
if (S.me?.action?.left || S.me?.queue.length) { if (S.me?.action?.left || S.me?.queue.length) {
chips += `<button class="chip ${queueOpen ? "on" : ""}" data-queue-menu title="Crafting queue">⏳ ${S.me.queue.length}</button>`; chips += `<button class="chip ${queueOpen ? "on" : ""}" data-queue-menu title="Queue">⏳ ${S.me.queue.length}</button>`;
} }
const fires = S.me ? Object.keys(C.fires).map((id) => [id, fireNow(id)]) : []; const fires = S.me ? Object.keys(C.fires).map((id) => [id, fireNow(id)]) : [];
if (fires.some(([, n]) => n.heat >= 0)) { if (fires.some(([, n]) => n.heat >= 0)) {
@ -791,7 +791,7 @@ function topbar() {
return `<button class="menu-btn" data-menu aria-label="Menu">☰</button><h1>${esc(title)}</h1>${chips}${S.me ? trackedBar() : ""}${S.me && queueOpen ? queuePanel() : ""}`; return `<button class="menu-btn" data-menu aria-label="Menu">☰</button><h1>${esc(title)}</h1>${chips}${S.me ? trackedBar() : ""}${S.me && queueOpen ? queuePanel() : ""}`;
} }
// The crafting queue, opened from the ⏳ chip: what's being made, what's next, and in what order. // The queue, opened from the ⏳ chip: what's being made, what's next, and in what order.
function queuePanel() { function queuePanel() {
const me = S.me, q = me.queue, recipe = (id) => C.actions.find((x) => x.id === id); const me = S.me, q = me.queue, recipe = (id) => C.actions.find((x) => x.id === id);
const now = me.action?.left && recipe(me.action.id), back = me.action?.resume && recipe(me.action.resume); const now = me.action?.left && recipe(me.action.id), back = me.action?.resume && recipe(me.action.resume);
@ -805,10 +805,10 @@ function queuePanel() {
<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>⏳ Crafting 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-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 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>
${q.length ? `<button class="btn sm" data-queue-clear>Clear</button>` : ""}</div> ${q.length ? `<button class="btn sm" data-queue-clear>Clear</button>` : ""}</div>
</div>`; </div>`;

View file

@ -103,6 +103,15 @@ class ActionTest(unittest.TestCase):
self.assertEqual(summary["resumed"], "mine_stone") self.assertEqual(summary["resumed"], "mine_stone")
self.assertEqual(p["queue"], []) self.assertEqual(p["queue"], [])
def test_queue_gathering(self):
world, p = self._smith(4)
self.assertIsNone(game.queue_add(world, p, "cut_logs", 3, T0)) # starts it, mining is what comes after
self.assertIsNone(game.queue_add(world, p, "smith_copper_legs", 1, T0))
self.assertIsNone(game.queue_add(world, p, "cut_logs", 2, T0))
game.process_player(world, p, T0 + 3600, random.Random(1))
self.assertEqual((p["bank"].get("logs"), p["bank"].get("copper_legs")), (5, 1))
self.assertEqual((p["action"]["id"], p["queue"]), ("mine_stone", []))
def test_queue_skips_what_you_cant_make(self): def test_queue_skips_what_you_cant_make(self):
world, p = self._smith(4) world, p = self._smith(4)
game.start_action(world, p, "smith_copper_helmet", T0, once=1) game.start_action(world, p, "smith_copper_helmet", T0, once=1)
@ -133,7 +142,7 @@ class ActionTest(unittest.TestCase):
def test_queue_rules(self): def test_queue_rules(self):
world, p = self._smith(4) world, p = self._smith(4)
self.assertEqual(game.queue_add(world, p, "cut_logs", 1, T0), "Only crafting can be queued") self.assertEqual(game.queue_add(world, p, "bury_bones", 1, T0), "Only crafting and gathering can be queued")
self.assertIsNone(game.queue_add(world, p, "smith_copper_helmet", 1, T0)) self.assertIsNone(game.queue_add(world, p, "smith_copper_helmet", 1, T0))
self.assertEqual(p["action"]["id"], "smith_copper_helmet") # nothing being made: it just starts self.assertEqual(p["action"]["id"], "smith_copper_helmet") # nothing being made: it just starts
self.assertEqual(p["action"]["resume"], "mine_stone") self.assertEqual(p["action"]["resume"], "mine_stone")