diff --git a/game.py b/game.py index 43e473c..0665c35 100644 --- a/game.py +++ b/game.py @@ -2343,7 +2343,7 @@ def draw_fight(world: dict, players: list, action: dict, rng: random.Random, sta for player in players: seconds, eaten = rest_plan(world, player) member = fight_member(world, player) - member.update(eaten=eaten, hp_in=member["top"]) + member.update(eaten=eaten, hp_in=member["top"], rest=seconds) # full after your own rest, then wait rest = max(rest, seconds) members.append(member) fight = {"seed": rng.random(), "pace": speed(world), "rest": rest, "enemy": action["enemy"]["name"], @@ -3349,7 +3349,7 @@ def draw_pull(world: dict, players: list, action: dict, run: dict, rng: random.R for player in players: seconds, eaten = rest_plan(world, player) member = fight_member(world, player) - member.update(eaten=eaten, hp_in=member["top"]) + member.update(eaten=eaten, hp_in=member["top"], rest=seconds) # full after your own rest, then wait rest = max(rest, seconds) members.append(member) fight = {"seed": rng.random(), "pace": speed(world), "rest": rest, "enemy": name, "members": members, @@ -3447,8 +3447,8 @@ def pull_now(world: dict, player: dict, now: float) -> str | None: if rested >= fight["rest"]: return "Already fighting" rested = math.ceil(rested / combat.STEP) * combat.STEP - share = rested / fight["rest"] if fight["rest"] else 1 for member in fight["members"]: + share = min(1, rested / member["rest"]) if member.get("rest") else 1 member["hp_in"] = member["start_hp"] + (member["hp_in"] - member["start_hp"]) * share member["eaten"] = {food: int(count * share) for food, count in member["eaten"].items() if int(count * share)} member["watch"] += fight["rest"] - rested @@ -6071,6 +6071,8 @@ PATCH_NOTES = [ ("fix", "Combat page shows your health potion: drunk on its own, no need to equip. The other card now reads \"No combat potion\"."), ("fix", "Combat page no longer jumps around: cast bars hold their space and the fight log stays 7 lines tall."), ("new", "Enemy casts that can't be interrupted show a 🔒 and a grey bar. Stuns still stop them, except on bosses."), + ("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."), ]}, {"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, " diff --git a/static/app.js b/static/app.js index 8121ed5..baca614 100644 --- a/static/app.js +++ b/static/app.js @@ -1081,8 +1081,8 @@ function animateFight(now) { const before = fightState(f, t - LAG).st; // the HP a moment ago, trailing the bar const mine = f.members.find((m) => m.id === S.uid) || f.members[0]; const me = st[mine.id]; - // Resting: everyone's HP climbs back to full while they eat. - if (resting) for (const m of f.members) st[m.id].hp = m.start_hp + (m.hp_in - m.start_hp) * Math.min(1, t / rest); + // Resting: everyone's HP climbs back to full at their own pace, then waits for the slowest. + if (resting) for (const m of f.members) st[m.id].hp = m.start_hp + (m.hp_in - m.start_hp) * Math.min(1, t * f.pace / (m.rest || f.rest)); const enemies = f.pack.map((_, i) => st[`e${i}`]); const left = enemies.reduce((sum, e) => sum + e.hp, 0), total = enemies.reduce((sum, e) => sum + e.top, 0); for (const el of document.querySelectorAll("[data-progress]")) el.style.width = `${(resting ? 1 : left / total) * 100}%`; diff --git a/tests/test_game.py b/tests/test_game.py index 401ba12..6bb1938 100644 --- a/tests/test_game.py +++ b/tests/test_game.py @@ -966,6 +966,19 @@ class DungeonTest(unittest.TestCase): self.assertLessEqual(fight["rest"], 3) self.assertLess(game.member_of(fight, "kay")["hp_in"], game.max_hp(self.kay) / 2) + def test_a_pull_early_heals_each_at_their_own_pace(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) + self.wil["hp"], self.kay["hp"] = game.max_hp(self.wil) - 5, 1 + self.online(T0 + 0.5) + fight = self.party()["fight"] + wil = game.member_of(fight, "wil") + self.assertLess(wil["rest"], fight["rest"]) # done resting first, waiting on Kay + self.assertIsNone(game.pull_now(self.world, self.wil, T0 + wil["rest"] / fight["pace"] + 1)) + self.assertEqual(wil["hp_in"], game.max_hp(self.wil)) + self.assertLess(game.member_of(fight, "kay")["hp_in"], game.max_hp(self.kay)) + def test_adds_join_halfway_and_never_count_if_they_dont(self): foes = game.DUNGEONS["dungeon_watchtower"]["pulls"][7][1] self.assertTrue(foes[1]["dormant"] and foes[2]["dormant"])