diff --git a/data/monsters/lich.json b/data/monsters/lich.json index 5623725..5ba2f39 100644 --- a/data/monsters/lich.json +++ b/data/monsters/lich.json @@ -6,24 +6,24 @@ "abilities": { "str_score": 12, "dex_score": 14, - "con_score": 14, + "con_score": 10, "int_score": 20, "wis_score": 14, "cha_score": 16 }, "hp_max": 100, "ac": { - "total": 25, + "total": 23, "touch": 14, - "flat_footed": 23 + "flat_footed": 21 }, - "bab": 8, + "bab": 5, "initiative_mod": 6, "speed_land_ft": 30, "saves": { - "fort": 10, - "ref": 10, - "will": 14 + "fort": 6, + "ref": 7, + "will": 11 }, "casting_ability": "int", "spell_slots": { @@ -39,14 +39,15 @@ "id": "lich:touch", "name": "Paralyzing Touch", "kind": "touch", - "attack_bonus": 10, + "attack_bonus": 5, + "natural": true, "damage": [ { "formula": "1d8+5", "types": ["negative"] } ], - "damage_bonus": 5, + "damage_bonus": 0, "crit_range": 20, "crit_mult": 2, "reach_ft": 5, diff --git a/tests/test_lich.py b/tests/test_lich.py index 465c23e..2d79a55 100644 --- a/tests/test_lich.py +++ b/tests/test_lich.py @@ -36,23 +36,24 @@ def _make_lich() -> Combatant: size="Medium", creature_type="undead", abilities=AbilityScores( - str_score=12, dex_score=14, con_score=14, + str_score=12, dex_score=14, con_score=10, int_score=20, wis_score=14, cha_score=16, ), hp_max=100, - ac=ACProfile(total=25, touch=14, flat_footed=23), - bab=8, + ac=ACProfile(total=23, touch=14, flat_footed=21), + bab=5, initiative_mod=6, speed_land_ft=30, - saves=Saves(fort=10, ref=10, will=14), + saves=Saves(fort=6, ref=7, will=11), attacks=[ AttackSpec( id="lich:touch", name="Paralyzing Touch", kind="touch", - attack_bonus=10, + attack_bonus=5, + natural=True, damage=[DamageComponent(formula=parse_dice("1d8+5"), types=["negative"])], - damage_bonus=5, + damage_bonus=0, crit_range=20, crit_mult=2, reach_ft=5, @@ -225,3 +226,30 @@ class TestLichJsonIntegrity: assert not any("spell_slots_" in k for k in data) assert len(data["casting_ability"]) > 0 assert data["spell_slots"]["6"] == 3 + + def test_json_matches_srd_stat_block(self) -> None: + """Given: the lich bestiary JSON + When: checked against the KB stat block (Bestiary "Lich": + "Base Atk +5", "Melee touch +5 (1d8+5 plus paralyzing touch)", + "AC 23, touch 14, flat-footed 21", "Fort +6, Ref +7, Will +11", + and the template's "Being undead, a lich has no Constitution score") + Then: BAB, the natural touch attack, AC, saves and CON all match.""" + data = json.loads(_DATA.read_text(encoding="utf-8")) + assert data["bab"] == 5 + assert data["ac"] == {"total": 23, "touch": 14, "flat_footed": 21} + assert data["saves"] == {"fort": 6, "ref": 7, "will": 11} + touch = data["attacks"][0] + assert touch["attack_bonus"] == 5 + # SRD lich template: "A lich has a touch attack that it can use once + # per round as a natural weapon [...] treated as a primary natural + # weapon" - no BAB iteratives (CRB "Natural Attacks"). + assert touch["natural"] is True + assert touch["damage"][0]["formula"] == "1d8+5" + # The +5 (1 point per 2 HD, SRD "Damage") is already in the formula; + # damage_bonus would be added on top (double counting). + assert touch["damage_bonus"] == 0 + # Undead have no CON score: encoded as the neutral 10 (modifier +0); + # the engine destroys undead at hp <= 0 (SRD Undead traits), so the + # score is never used for the dying band. + assert data["creature_type"] == "undead" + assert data["abilities"]["con_score"] == 10