Dungeons: run on while anyone watches
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
13371ec269
commit
25ecb885f5
2 changed files with 29 additions and 12 deletions
20
game.py
20
game.py
|
|
@ -1008,7 +1008,7 @@ COMBAT_ACTIONS = [
|
||||||
# cast what has to be interrupted, hit what has to be braced for, call in help, and lose patience.
|
# cast what has to be interrupted, hit what has to be braced for, call in help, and lose patience.
|
||||||
DUNGEON_SIZE = 4 # the most in a run
|
DUNGEON_SIZE = 4 # the most in a run
|
||||||
DUNGEON_WIPES = 3 # on one pull, and the run is over
|
DUNGEON_WIPES = 3 # on one pull, and the run is over
|
||||||
DUNGEON_AWAY = 90 # real seconds without looking before a run waits for you
|
DUNGEON_AWAY = 90 # real seconds without looking before you count as away (a run waits if everyone is)
|
||||||
TRASH = (1.5, 1.5) # a pull's HP (and XP) and hits against the open world's
|
TRASH = (1.5, 1.5) # a pull's HP (and XP) and hits against the open world's
|
||||||
MINI_BOSS = 5.0 # a mini-boss's HP (and XP, and loot odds) against its race's elite
|
MINI_BOSS = 5.0 # a mini-boss's HP (and XP, and loot odds) against its race's elite
|
||||||
FINAL_BOSS = (2.5, 1.3) # the area boss's (and its adds') HP and hits against the open-world one
|
FINAL_BOSS = (2.5, 1.3) # the area boss's (and its adds') HP and hits against the open-world one
|
||||||
|
|
@ -3476,9 +3476,9 @@ def process_party(world: dict, pid: str, now: float, rng: random.Random) -> None
|
||||||
break
|
break
|
||||||
members = [world["players"][uid] for uid in party["members"]]
|
members = [world["players"][uid] for uid in party["members"]]
|
||||||
if action.get("dungeon"):
|
if action.get("dungeon"):
|
||||||
# A run waits for its leader to start it, then for anyone who's looked away: it's played together.
|
# A run waits for its leader to start it, then for someone to be watching. Whoever's
|
||||||
away = [p for p in members if p["last_active"] < party["start"] - DUNGEON_AWAY]
|
# looked away fights on auto.
|
||||||
if "run" not in party or away:
|
if "run" not in party or run_away(world, party):
|
||||||
party["start"] = max(party["start"], now)
|
party["start"] = max(party["start"], now)
|
||||||
for player in members:
|
for player in members:
|
||||||
player["action"]["start"] = party["start"]
|
player["action"]["start"] = party["start"]
|
||||||
|
|
@ -3593,6 +3593,12 @@ def _clear_bonus(world: dict, player: dict, spec: dict, now: float, rng: random.
|
||||||
summary["items"][item] = summary["items"].get(item, 0) + kept
|
summary["items"][item] = summary["items"].get(item, 0) + kept
|
||||||
|
|
||||||
|
|
||||||
|
def run_away(world: dict, party: dict) -> list:
|
||||||
|
"""Everyone in the run, if no one's been looking: it waits for one of them to come back."""
|
||||||
|
members = [world["players"][uid] for uid in party["members"]]
|
||||||
|
return members if all(p["last_active"] < party["start"] - DUNGEON_AWAY for p in members) else []
|
||||||
|
|
||||||
|
|
||||||
def run_of(world: dict, player: dict) -> dict | None:
|
def run_of(world: dict, player: dict) -> dict | None:
|
||||||
"""Where your group's dungeon run stands, if you're in one."""
|
"""Where your group's dungeon run stands, if you're in one."""
|
||||||
party = party_of(world, player)
|
party = party_of(world, player)
|
||||||
|
|
@ -3600,10 +3606,9 @@ def run_of(world: dict, player: dict) -> dict | None:
|
||||||
return None
|
return None
|
||||||
spec = DUNGEONS[ACTION_BY_ID[party["action"]]["dungeon"]]
|
spec = DUNGEONS[ACTION_BY_ID[party["action"]]["dungeon"]]
|
||||||
run = party.get("run", {"pull": 0, "wipes": 0})
|
run = party.get("run", {"pull": 0, "wipes": 0})
|
||||||
away = [world["players"][uid]["name"] for uid in party["members"]
|
|
||||||
if world["players"][uid]["last_active"] < party["start"] - DUNGEON_AWAY]
|
|
||||||
return {"dungeon": spec["name"], "pull": run["pull"] + 1, "of": len(spec["pulls"]), "wipes": run["wipes"],
|
return {"dungeon": spec["name"], "pull": run["pull"] + 1, "of": len(spec["pulls"]), "wipes": run["wipes"],
|
||||||
"next": spec["pulls"][min(run["pull"], len(spec["pulls"]) - 1)][0], "away": away,
|
"next": spec["pulls"][min(run["pull"], len(spec["pulls"]) - 1)][0],
|
||||||
|
"away": [p["name"] for p in run_away(world, party)],
|
||||||
"leader": party["members"][0], "size": len(party["members"]), "started": "run" in party}
|
"leader": party["members"][0], "size": len(party["members"]), "started": "run" in party}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -6275,6 +6280,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", "Dungeons: runs go on while anyone is watching. Members who look away fight on auto."),
|
||||||
("new", "Group stays in lobby after finishing dungeon"),
|
("new", "Group stays in lobby after finishing dungeon"),
|
||||||
("new", "Added \"Drink all\" option to potions in the bank."),
|
("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", "Firekeeping: Tend all fires. Each log goes on the lowest fire with room in your pile."),
|
||||||
|
|
|
||||||
|
|
@ -1083,15 +1083,26 @@ class DungeonTest(unittest.TestCase):
|
||||||
game.start_action(self.world, self.wil, "dungeon_watchtower", T0)
|
game.start_action(self.world, self.wil, "dungeon_watchtower", T0)
|
||||||
self.assertEqual(game.start_run(self.world, self.wil, T0), "Not in a dungeon")
|
self.assertEqual(game.start_run(self.world, self.wil, T0), "Not in a dungeon")
|
||||||
|
|
||||||
def test_a_run_waits_for_whoever_looked_away(self):
|
def test_a_run_goes_on_while_someone_watches(self):
|
||||||
game.start_action(self.world, self.wil, "dungeon_watchtower", T0)
|
game.start_action(self.world, self.wil, "dungeon_watchtower", T0)
|
||||||
game.join_fight(self.world, self.kay, "wil", T0)
|
game.join_fight(self.world, self.kay, "wil", T0)
|
||||||
game.start_run(self.world, self.wil, T0)
|
game.start_run(self.world, self.wil, T0)
|
||||||
self.online(T0 + 300, [self.wil]) # kay went to make tea
|
now = T0
|
||||||
self.online(T0 + 1200, [self.wil])
|
while self.party()["run"]["pull"] < 2: # kay went to make tea: on auto, wil plays on
|
||||||
|
now += 2
|
||||||
|
self.online(now, [self.wil])
|
||||||
|
self.assertLess(now, T0 + 3600)
|
||||||
|
self.assertEqual(game.run_of(self.world, self.wil)["away"], [])
|
||||||
|
self.assertIn("kay", [m["id"] for m in self.party()["fight"]["members"]])
|
||||||
|
|
||||||
|
def test_a_run_waits_when_no_one_watches(self):
|
||||||
|
game.start_action(self.world, self.wil, "dungeon_watchtower", T0)
|
||||||
|
game.join_fight(self.world, self.kay, "wil", T0)
|
||||||
|
game.start_run(self.world, self.wil, T0)
|
||||||
|
game.catch_up(self.world, T0 + 1200, random.Random(1)) # both went to make tea
|
||||||
self.assertIsNone(self.party()["fight"])
|
self.assertIsNone(self.party()["fight"])
|
||||||
self.assertEqual(game.run_of(self.world, self.wil)["away"], ["Kay"])
|
self.assertEqual(game.run_of(self.world, self.wil)["away"], ["Wil", "Kay"])
|
||||||
self.online(T0 + 1210)
|
self.online(T0 + 1210, [self.kay])
|
||||||
self.assertIsNotNone(self.party()["fight"])
|
self.assertIsNotNone(self.party()["fight"])
|
||||||
|
|
||||||
def test_three_wipes_on_a_pull_end_the_run(self):
|
def test_three_wipes_on_a_pull_end_the_run(self):
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue