Raid replay plays like a fight; fight-once can be joined

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
rope 2026-10-25 20:54:37 +00:00
parent 4f2d113abd
commit 387f4c201b
4 changed files with 513 additions and 112 deletions

50
game.py
View file

@ -3090,16 +3090,9 @@ def process_player(world: dict, player: dict, now: float, rng: random.Random | N
current["turn"] = 1 - current.get("turn", 0)
if player["action"] is current and "left" in current:
current["left"] -= 1
if current["left"] <= 0:
back = current.get("resume")
if back:
plot2 = current.get("resume_plot2")
player["action"] = current = {"id": back, "start": current["start"]}
if plot2:
current.update(plot2=plot2, turn=0)
summary["resumed"] = back
else:
stop(player)
if current["left"] <= 0 and finish_once(player, current["start"]):
current = player["action"]
summary["resumed"] = current["id"]
if player["action"] is current and "enemy" not in action and not current.get("plot2"):
# Out of materials: stop now, not once the next bar has filled.
reason = can_start(world, player, growing(player))
@ -3117,6 +3110,19 @@ def stop(player: dict, reason: str | None = None) -> None:
player["stopped"] = reason
def finish_once(player: dict, at: float, reason: str | None = None) -> bool:
"""A one-off is done: back to what you were doing before (True), or stop."""
current = player["action"]
back = current.get("resume")
if not back:
stop(player, reason)
return False
player["action"] = {"id": back, "start": at}
if current.get("resume_plot2"):
player["action"].update(plot2=current["resume_plot2"], turn=0)
return True
def drop_out(world: dict, player: dict, now: float, reason: str | None = None) -> None:
"""Stop, stepping out of a shared fight if you're in one."""
current = player["action"]
@ -3180,7 +3186,7 @@ def offline_limit(world: dict, player: dict) -> float:
def joinable(world: dict, player: dict) -> bool:
"""Whether someone's fight is one others can tag into."""
current = player["action"]
if not current or "left" in current or "order" in current:
if not current or "order" in current:
return False
action = ACTION_BY_ID[current["id"]]
if action.get("dungeon"):
@ -3309,9 +3315,9 @@ def process_party(world: dict, pid: str, now: float, rng: random.Random) -> None
if world["parties"].get(pid) is not party:
break # the last of them carries on alone, and picks up when they're back
at = max(party["start"], offline_limit(world, player))
wind = {"wind_used": True} if player["action"].get("wind_used") else {}
keep = {k: v for k, v in player["action"].items() if k in ("wind_used", "left", "resume", "resume_plot2")}
leave_party(world, player, pid, at)
player["action"] = {"id": action["id"], "start": at, **wind}
player["action"] = {"id": action["id"], "start": at, **keep}
continue
if end > now:
break
@ -3336,6 +3342,15 @@ def process_party(world: dict, pid: str, now: float, rng: random.Random) -> None
for other in members:
if other is not player:
together[other["name"]] = together.get(other["name"], 0) + 1
current = player["action"]
if not action.get("dungeon") and "left" in current:
# Fought once: step out, and back to what you were doing. The rest carry on.
current["left"] -= 1
if current["left"] <= 0:
if world["parties"].get(current.get("party")) is party:
leave_party(world, player, pid, end)
if finish_once(player, end):
summary["resumed"] = player["action"]["id"]
if action.get("dungeon") and world["parties"].get(pid) is party:
_advance_run(world, party, action, fight, members, end, rng)
@ -3382,8 +3397,11 @@ def _advance_run(world: dict, party: dict, action: dict, fight: dict, members: l
log(world, now, "party", f"{names}: {reason.lower()} in {spec['name']}")
del world["parties"][party["id"]]
for player in members:
stop(player, reason)
player.setdefault("away", empty_summary())["stopped"] = reason
summary = player.setdefault("away", empty_summary())
if finish_once(player, now, reason):
summary["resumed"] = player["action"]["id"]
else:
summary["stopped"] = reason
def _clear_bonus(world: dict, player: dict, spec: dict, now: float, rng: random.Random) -> None:
@ -6074,6 +6092,8 @@ PATCH_NOTES = [
("fix", "Resting in a group: everyone heals at their own pace, so good food tops you up first. The pull still "
"waits for the slowest, and Pull now sends each in as healed as they really are."),
("new", "DPS meter under the combat log: damage per second and total for each fighter, live. Shows the last fight while resting."),
("new", "Improved raid UI"),
("fix", "Bugfix: Combats started using \"Fight once\" can now be joined."),
]},
{"date": "2026-10-24", "title": "Dungeon loot", "notes": [
("new", "Masterwork patterns: every dungeon boss can drop one of 7 (15% from a mini-boss, 35% from the last boss, "