diff --git a/game.py b/game.py index c95b496..32eeff6 100644 --- a/game.py +++ b/game.py @@ -3097,7 +3097,7 @@ def gain_xp(world: dict, player: dict, skill: str, xp: float, now: float, summar def empty_summary() -> dict: return {"done": 0, "items": {}, "overflow": {}, "xp": {}, "levels": [], "eaten": {}, "used": {}, "damage": 0, - "gold": 0, "wins": 0, "losses": 0, "lucky": 0, "stopped": None, "resumed": None, "skipped": [], + "gold": 0, "wins": 0, "losses": 0, "lucky": 0, "stopped": None, "resumed": None, "run_over": None, "skipped": [], "sold": {}, "sold_gold": 0, "pp_spent": 0, "pp_gained": 0, "prayer_out": False, "second_wind": 0, "kept": 0, "potions_drunk": {}, "potion_out": [], "labour": 0} @@ -3574,11 +3574,10 @@ def _advance_run(world: dict, party: dict, action: dict, fight: dict, members: l return reason = f"Wiped {DUNGEON_WIPES} times at {spec['pulls'][run['pull']][0]}" log(world, now, "party", f"{names}: {reason.lower()} in {spec['name']}") - del world["parties"][party["id"]] + # Back to the lobby, together: the leader can take the group in again, and others can join. + del party["run"] for player in members: - summary = player.setdefault("away", empty_summary()) - if not finish_once(world, player, now, reason, summary): - summary["stopped"] = reason + player.setdefault("away", empty_summary())["run_over"] = reason def _clear_bonus(world: dict, player: dict, spec: dict, now: float, rng: random.Random) -> None: @@ -6276,6 +6275,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-27", "title": "Prayer", "notes": [ + ("new", "Group stays in lobby after finishing dungeon"), ("new", "Added \"Drink all\" option to potions in the bank."), ("new", "Firekeeping: Tend all fires. Each log goes on the lowest fire with room in your pile."), ("new", "Added resource dumps for Leatherworking: Guard Jerkins"), diff --git a/static/app.js b/static/app.js index adfe9ac..d4512b3 100644 --- a/static/app.js +++ b/static/app.js @@ -190,6 +190,7 @@ function showSummary(sum) { if (sold.length) toast(`๐Ÿ“ฆ Bank full: sold ${sold.map(([id, q]) => `${fmt(q)} ${item(id).name.toLowerCase()}`).join(", ")} for ${fmt(sum.sold_gold)} gold`); for (const [skill, level] of sum.levels) toast(`๐ŸŽ‰ ${skillOf(skill).name} level ${level}!`, "level"); if (sum.stopped) toast(`Stopped: ${sum.stopped}`, "bad"); + if (sum.run_over) toast(`๐Ÿ ${sum.run_over}`, sum.run_over.startsWith("Cleared") ? "level" : "bad"); if (sum.skipped?.length) toast(`โญ๏ธ Skipped: ${skippedNames(sum)}`, "bad"); for (const id of sum.potion_out || []) toast(`๐Ÿงช Your ${item(id).name.toLowerCase()} ran out`, "bad"); for (const id of Object.keys(sum.potions_drunk || {})) toast(`๐Ÿงช Drank another ${item(id).name.toLowerCase()}`); @@ -223,6 +224,7 @@ function awayModal(sum) { ${spilled ? `

Bank full, sent to the stockpile:

` : ""} ${sum.skipped?.length ? `

โญ๏ธ Skipped: ${esc(skippedNames(sum))}

` : ""} ${sum.stopped ? `

Stopped: ${esc(sum.stopped)}

` : ""} + ${sum.run_over ? `

๐Ÿ ${esc(sum.run_over)}

` : ""}

`); } diff --git a/tests/test_game.py b/tests/test_game.py index 4d903ae..d74c88f 100644 --- a/tests/test_game.py +++ b/tests/test_game.py @@ -1036,7 +1036,7 @@ class DungeonTest(unittest.TestCase): self.assertIsNone(game.start_run(self.world, self.wil, T0 + 600)) names = [] now = T0 + 600 - while game.party_of(self.world, self.wil): + while "run" in self.party(): fight = self.party()["fight"] or {} if fight.get("stage"): names.append(fight["enemy"]) @@ -1046,10 +1046,16 @@ class DungeonTest(unittest.TestCase): pulls = game.ACTION_BY_ID["dungeon_watchtower"]["pulls"] self.assertEqual(list(dict.fromkeys(names)), pulls) for p in (self.wil, self.kay): - self.assertIsNone(p["action"]) - self.assertEqual(p["stopped"], "Cleared Watchtower Depths") + self.assertEqual(p["away"]["run_over"], "Cleared Watchtower Depths") self.assertGreaterEqual(p["gold"], game.DUNGEONS["dungeon_watchtower"]["bonus_gold"]) self.assertIn("cleared Watchtower Depths", self.world["ledger"][-1]["text"]) + # Back in the lobby together: waiting on the leader, open to join, ready to go again. + self.online(now + 600) + self.assertIs(game.party_of(self.world, self.kay), self.party()) + self.assertIsNone(self.party()["fight"]) + self.assertFalse(game.run_of(self.world, self.wil)["started"]) + self.assertTrue(game.joinable(self.world, self.wil)) + self.assertIsNone(game.start_run(self.world, self.wil, now + 600)) def test_a_group_waits_for_its_leader_and_holds_four(self): game.start_action(self.world, self.wil, "dungeon_watchtower", T0) @@ -1096,11 +1102,12 @@ class DungeonTest(unittest.TestCase): p["xp"]["combat"] = game.XP_TABLE[20] self.party()["run"] = {"pull": 7, "wipes": 0} # the ogre, naked now = T0 - while game.party_of(self.world, self.wil): + while "run" in self.party(): now += 30 self.online(now) self.assertLess(now, T0 + 3 * 3600) - self.assertEqual(self.wil["stopped"], "Wiped 3 times at Watchtower ogre") + self.assertEqual(self.wil["away"]["run_over"], "Wiped 3 times at Watchtower ogre") + self.assertEqual(self.party()["members"], ["wil", "kay"]) def test_the_leader_can_pull_early(self): game.start_action(self.world, self.wil, "dungeon_watchtower", T0)