diff --git a/README.md b/README.md index 8e38331..cd7b5da 100644 --- a/README.md +++ b/README.md @@ -1095,9 +1095,12 @@ Règles modélisées : zone (AoE) utilisent le DD d'incantation du jeteur et chaque créature de la zone effectue la sauvegarde listée par le sort (Volonté / Réflexes / Constitution — le sort primaire réutilise son jet déjà effectué) ; un succès - annule ou divise par deux selon ce que l'effet déclare (`save_negates` / - `half_on_save` / `AreaEffect.save_for_half`) — les zones « sauvegarde - annule » (Sleep, Color Spray, Web) sauvent donc aussi. + annule ou divise par deux selon ce que l'effet déclare (`save_negates` / + `half_on_save` / `AreaEffect.save_for_half`) — les zones « sauvegarde + annule » (Sleep, Color Spray, Web) sauvent donc aussi. Une composante de + dégâts peut déclarer `save_negates: false` (conformance D1 — SRD + « Sound Burst » : « Fortitude partial » n'annule que l'étourdissement, + jamais les dés) : la sauvegarde réussie ne réduit alors pas ces dégâts. - Dons : les 8 PJ portent 55 noms de dons uniques ; le chargeur résout chacun d'eux soit vers un effet réel (Power Attack actif, Weapon Focus ciblé par arme, Toughness lié au niveau, dons passifs via `_stat_modifiers`), soit vers un « no-op » tracé en diff --git a/data/spells/sound_burst.json b/data/spells/sound_burst.json index 9e1ef76..ef66867 100644 --- a/data/spells/sound_burst.json +++ b/data/spells/sound_burst.json @@ -10,13 +10,14 @@ { "type": "area", "radius_ft": 10, - "save_for_half": true, + "save_for_half": false, "effects": [ { "type": "damage", "formula": "1d8", "types": ["sonic"], - "half_on_save": true + "half_on_save": false, + "save_negates": false }, { "type": "condition", @@ -27,5 +28,5 @@ ] } ], - "description": "A tremendous cacophony blasts a 10-ft.-radius spread: every creature in the area takes 1d8 points of sonic damage and must succeed on a Fortitude save or be stunned for 1 round (Fortitude partial — modeled as save for half damage and negates the stun). Creatures that cannot hear take the damage but are not stunned (not modeled)." + "description": "A tremendous cacophony blasts a 10-ft.-radius spread: every creature in the area takes 1d8 points of sonic damage and must succeed on a Fortitude save to avoid being stunned for 1 round (SRD spells/sound-burst.md: the save only negates the stun - the damage is full regardless). Creatures that cannot hear take the damage but are not stunned (not modeled)." } diff --git a/src/pf1e_simulator/combat.py b/src/pf1e_simulator/combat.py index eb4af9a..77c90d4 100644 --- a/src/pf1e_simulator/combat.py +++ b/src/pf1e_simulator/combat.py @@ -5534,7 +5534,7 @@ class CombatEngine: raw = effect.formula.max_value() if saved and effect.half_on_save: dmg = max(0, raw // 2) - elif saved: + elif saved and effect.save_negates: dmg = 0 else: dmg = raw diff --git a/src/pf1e_simulator/spells.py b/src/pf1e_simulator/spells.py index 9176473..f5664f3 100644 --- a/src/pf1e_simulator/spells.py +++ b/src/pf1e_simulator/spells.py @@ -39,6 +39,11 @@ class DamageEffect: types: tuple[str, ...] # False = save negates (targeted); True = half damage on save (ray/line/cone/AoE) half_on_save: bool = False + # Only consulted when half_on_save is False. True = a successful save deals + # no damage (save negates, the targeted-spell default); False = the save has + # no effect on this damage at all (SRD Sound Burst: "Fortitude partial" + # negates only the stun — every creature takes the full dice regardless). + save_negates: bool = True @dataclass(frozen=True) @@ -258,6 +263,7 @@ def _parse_effect(data: Json) -> SpellEffect: formula=parse_dice(_as_str(effect["formula"])), types=tuple(_as_str(t) for t in _as_list(effect["types"])), half_on_save=bool(effect.get("half_on_save", False)), + save_negates=bool(effect.get("save_negates", True)), ) if effect_type == "condition": return ConditionEffect( diff --git a/tests/test_spells.py b/tests/test_spells.py index 33cfc1f..2acc74c 100644 --- a/tests/test_spells.py +++ b/tests/test_spells.py @@ -390,7 +390,10 @@ class TestAreaSavesPerSpellSaveType: ) engine._cast_spell(c, t, "Sound Burst") assert any(ac.condition.name == "stunned" for ac in c.active_conditions) - assert t.hp == 200 - 1 # saved: 3 // 2 + # SRD spells/sound-burst.md: the save only avoids the stun — the 1d8 + # sonic damage is full regardless (save_negates: false on the damage). + assert t.hp == 200 - 3 # saved: full damage, no stun + assert not any(ac.condition.name == "stunned" for ac in t.active_conditions) class TestTargetedSpellSaveNegates: