diff --git a/game.py b/game.py index 9fcc733..9aed496 100644 --- a/game.py +++ b/game.py @@ -1567,11 +1567,11 @@ def max_hp(player: dict) -> int: return 40 + 4 * skill_level(player, "combat") + int(gear_bonus(player, "hp")) -def regen(world: dict, player: dict, now: float, boost: float = 1) -> None: +def regen(world: dict, player: dict, now: float) -> None: """Heal for the time since HP was last settled.""" if now > player["hp_at"]: top = max_hp(player) - boost *= 2 if fitted(world, "enchanted_hearths") else 1 + boost = 2 if fitted(world, "enchanted_hearths") else 1 boost *= 1 + gear_bonus(player, "regen") / 100 per_second = top * REGEN_PER_HOUR * boost * speed(world) / HOUR player["hp"] = min(top, player["hp"] + (now - player["hp_at"]) * per_second) @@ -1627,6 +1627,7 @@ def _variant_odds(player: dict, foe: dict, blessed: dict, brew: dict) -> dict: kill = foe["hp"] / (hit * _average_hit(me["max_hit"], me["crit"]) / me["interval"]) taken = sum(_taken(me, kind) for kind in foe["attack"]) / len(foe["attack"]) hurt = their_hit * (1 - me["block"]) * _average_hit(foe["max_hit"]) * taken / foe["interval"] * kill + hurt = max(0, hurt - blessed["mend"] * max_hp(player) * kill) return {"hit": hit, "their_hit": their_hit, "max_hit": me["max_hit"], "affinity": me["affinity"], "their_max": max(1, round(foe["max_hit"] * max(_taken(me, kind) for kind in foe["attack"]))), "kill_time": kill, "damage": hurt} @@ -1669,6 +1670,7 @@ def simulate_fight(world: dict, player: dict, action: dict, rng: random.Random) break if mine <= theirs: mine += me["interval"] + hp = min(top, hp + blessed["mend"] * top * me["interval"]) # Rapid Heal mends a little each swing crit = rng.random() < me["crit"] landed = rng.random() < hit dealt = max(1, round(me["max_hit"] * CRIT_MULT if crit else rng.randint(1, me["max_hit"]))) if landed else 0 @@ -1720,10 +1722,12 @@ def simulate_fight(world: dict, player: dict, action: dict, rng: random.Random) # Melvor-style: bones become prayer points, active prayers cost points each # fight and give boosts while they last, and praying in a fight trains Prayer. +MEND = 0.0004 # Rapid Heal: share of max HP mended per second of fighting + PRAYERS = [ {"id": "thick_skin", "name": "Thick Skin", "icon": "🪨", "level": 1, "cost": 1, "reduce": 0.10}, {"id": "burst_of_strength", "name": "Burst of Strength", "icon": "💪", "level": 5, "cost": 1, "power": 5}, - {"id": "rapid_heal", "name": "Rapid Heal", "icon": "💗", "level": 10, "cost": 1, "regen": 2}, + {"id": "rapid_heal", "name": "Rapid Heal", "icon": "💗", "level": 10, "cost": 1, "mend": MEND}, {"id": "steel_skin", "name": "Steel Skin", "icon": "🛡️", "level": 20, "cost": 2, "reduce": 0.20}, {"id": "superhuman_strength", "name": "Superhuman Strength", "icon": "🦾", "level": 30, "cost": 2, "power": 12}, {"id": "second_wind", "name": "Second Wind", "icon": "🌬️", "level": 40, "cost": 1, "wind": True}, @@ -1733,7 +1737,7 @@ PRAYERS = [ PRAYER_BY_ID = {prayer["id"]: prayer for prayer in PRAYERS} MAX_PRAYERS = 2 PRAYER_XP_PER_POINT = 5 -NO_BLESSING = {"cost": 0, "power": 0, "taken": 1.0, "regen": 1, "wind": False} +NO_BLESSING = {"cost": 0, "power": 0, "taken": 1.0, "mend": 0, "wind": False} def describe_prayer(prayer: dict) -> str: @@ -1742,8 +1746,8 @@ def describe_prayer(prayer: dict) -> str: parts.append(f"-{round(prayer['reduce'] * 100)}% damage taken") if prayer.get("power"): parts.append(f"+{prayer['power']} power") - if prayer.get("regen"): - parts.append("HP recovers twice as fast") + if prayer.get("mend"): + parts.append(f"+{prayer['mend'] * 6000:g}% HP / min in fights") if prayer.get("wind"): parts.append("once per outing, revive at half HP") return ", ".join(parts) @@ -1759,7 +1763,7 @@ def blessing(player: dict) -> dict: for prayer in active: taken *= 1 - prayer.get("reduce", 0) return {"cost": cost, "power": sum(p.get("power", 0) for p in active), "taken": taken, - "regen": max([p.get("regen", 1) for p in active]), "wind": any(p.get("wind") for p in active)} + "mend": sum(p.get("mend", 0) for p in active), "wind": any(p.get("wind") for p in active)} def set_prayers(player: dict, ids: list) -> str | None: @@ -2132,7 +2136,7 @@ def process_player(world: dict, player: dict, now: float, rng: random.Random | N action = growing(player) if "enemy" in action and not can_start(world, player, action): if not current.get("fight"): - regen(world, player, current["start"], blessing(player)["regen"]) + regen(world, player, current["start"]) current["fight"] = simulate_fight(world, player, action, rng) duration = current["fight"]["duration"] else: diff --git a/tests/test_game.py b/tests/test_game.py index 6ae5c83..ea48768 100644 --- a/tests/test_game.py +++ b/tests/test_game.py @@ -837,6 +837,18 @@ class PrayerTest(unittest.TestCase): self.assertEqual(summary["second_wind"], 1) self.assertTrue(summary["stopped"].startswith("Retreated")) + def test_rapid_heal_mends_during_fights(self): + world, p = self.priest(level=10, combat=10) + scouts = game.ACTION_BY_ID["fight_scouts"] + p["pp"], p["hp"] = 1000, 50 + plain = game.simulate_fight(world, p, scouts, random.Random(3)) + game.set_prayers(p, ["rapid_heal"]) + mended = game.simulate_fight(world, p, scouts, random.Random(3)) + swings = sum(1 for e in plain["events"] if e[1] == "you") + top = game.max_hp(p) + self.assertAlmostEqual(mended["hp"] - plain["hp"], swings * game.MEND * top * plain["interval"], places=6) + self.assertLess(game.fight_odds(p, scouts)["damage"], game.fight_odds({**p, "prayers": []}, scouts)["damage"]) + def test_offerings_ward_the_city_until_the_raid(self): world, p = self.priest(level=15) world["city"]["buildings"]["temple"]["level"] = 1