fix(spells): correct the sound burst save
PF1e (SRD spells/sound-burst.md): "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" — the Fortitude partial negates the stun only, never the dice. The damage component declared half_on_save: true, halving the sonic damage on a successful save. DamageEffect gains a save_negates flag (default True, existing behavior unchanged); sound_burst.json declares half_on_save: false + save_negates: false so the 1d8 is full regardless while the condition keeps save_negates. README updated. Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
@@ -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 (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 /
|
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
|
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` /
|
annule ou divise par deux selon ce que l'effet déclare (`save_negates` /
|
||||||
`half_on_save` / `AreaEffect.save_for_half`) — les zones « sauvegarde
|
`half_on_save` / `AreaEffect.save_for_half`) — les zones « sauvegarde
|
||||||
annule » (Sleep, Color Spray, Web) sauvent donc aussi.
|
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
|
- 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
|
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
|
lié au niveau, dons passifs via `_stat_modifiers`), soit vers un « no-op » tracé en
|
||||||
|
|||||||
@@ -10,13 +10,14 @@
|
|||||||
{
|
{
|
||||||
"type": "area",
|
"type": "area",
|
||||||
"radius_ft": 10,
|
"radius_ft": 10,
|
||||||
"save_for_half": true,
|
"save_for_half": false,
|
||||||
"effects": [
|
"effects": [
|
||||||
{
|
{
|
||||||
"type": "damage",
|
"type": "damage",
|
||||||
"formula": "1d8",
|
"formula": "1d8",
|
||||||
"types": ["sonic"],
|
"types": ["sonic"],
|
||||||
"half_on_save": true
|
"half_on_save": false,
|
||||||
|
"save_negates": false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "condition",
|
"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)."
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5534,7 +5534,7 @@ class CombatEngine:
|
|||||||
raw = effect.formula.max_value()
|
raw = effect.formula.max_value()
|
||||||
if saved and effect.half_on_save:
|
if saved and effect.half_on_save:
|
||||||
dmg = max(0, raw // 2)
|
dmg = max(0, raw // 2)
|
||||||
elif saved:
|
elif saved and effect.save_negates:
|
||||||
dmg = 0
|
dmg = 0
|
||||||
else:
|
else:
|
||||||
dmg = raw
|
dmg = raw
|
||||||
|
|||||||
@@ -39,6 +39,11 @@ class DamageEffect:
|
|||||||
types: tuple[str, ...]
|
types: tuple[str, ...]
|
||||||
# False = save negates (targeted); True = half damage on save (ray/line/cone/AoE)
|
# False = save negates (targeted); True = half damage on save (ray/line/cone/AoE)
|
||||||
half_on_save: bool = False
|
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)
|
@dataclass(frozen=True)
|
||||||
@@ -258,6 +263,7 @@ def _parse_effect(data: Json) -> SpellEffect:
|
|||||||
formula=parse_dice(_as_str(effect["formula"])),
|
formula=parse_dice(_as_str(effect["formula"])),
|
||||||
types=tuple(_as_str(t) for t in _as_list(effect["types"])),
|
types=tuple(_as_str(t) for t in _as_list(effect["types"])),
|
||||||
half_on_save=bool(effect.get("half_on_save", False)),
|
half_on_save=bool(effect.get("half_on_save", False)),
|
||||||
|
save_negates=bool(effect.get("save_negates", True)),
|
||||||
)
|
)
|
||||||
if effect_type == "condition":
|
if effect_type == "condition":
|
||||||
return ConditionEffect(
|
return ConditionEffect(
|
||||||
|
|||||||
@@ -390,7 +390,10 @@ class TestAreaSavesPerSpellSaveType:
|
|||||||
)
|
)
|
||||||
engine._cast_spell(c, t, "Sound Burst")
|
engine._cast_spell(c, t, "Sound Burst")
|
||||||
assert any(ac.condition.name == "stunned" for ac in c.active_conditions)
|
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:
|
class TestTargetedSpellSaveNegates:
|
||||||
|
|||||||
Reference in New Issue
Block a user