fix(spells): apply a shaken effect when a fear save succeeds
ConditionEffect gains save_partial (JSON 'on_success': condition_name + duration_rounds). On a successful save that would otherwise negate, _apply_condition_effect now imposes the weaker condition (SRD fear.md: panicked on a failed Will save, shaken for 1 round on a success). Extended _metamagic_transform_sub to preserve/double it; extracted _impose_condition helper. Updated fear.json, added failed/successful-save and loader tests in test_spells.py, and the README. Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
+29
-3
@@ -172,6 +172,18 @@ class TestSpellLoading:
|
||||
assert isinstance(eff, ConditionEffect)
|
||||
assert eff.condition_name == "paralyzed"
|
||||
assert eff.save_negates is True
|
||||
assert eff.save_partial is None
|
||||
|
||||
def test_fear_condition_has_save_partial(self) -> None:
|
||||
# PF1e (SRD spells/fear.md): a successful Will save imposes shaken 1
|
||||
# round instead of panicked — the partial-save spec.
|
||||
spell = load_spell(_SPELLS_DIR / "fear.json")
|
||||
area = spell.effects[0]
|
||||
assert isinstance(area, AreaEffect)
|
||||
eff = area.effects[0]
|
||||
assert isinstance(eff, ConditionEffect)
|
||||
assert eff.condition_name == "panicked"
|
||||
assert eff.save_partial == ("shaken", 1)
|
||||
|
||||
def test_cure_light_has_heal_effect(self) -> None:
|
||||
spell = load_spell(_SPELLS_DIR / "cure_light_wounds.json")
|
||||
@@ -534,9 +546,9 @@ class TestHoldPerson:
|
||||
|
||||
class TestFear:
|
||||
"""Given: a caster casting Fear (SRD spells/fear.md: panicked unless the
|
||||
Will save succeeds)
|
||||
When: target fails save
|
||||
Then: panicked condition is applied."""
|
||||
Will save succeeds; a successful save makes the creature shaken 1 round)
|
||||
When: target fails / succeeds the save
|
||||
Then: panicked is applied / shaken (partial save) is applied instead."""
|
||||
|
||||
def test_failed_save_applies_panicked(self) -> None:
|
||||
caster = _make_caster()
|
||||
@@ -544,6 +556,20 @@ class TestFear:
|
||||
engine, c, t = _make_engine(caster, target, [2])
|
||||
engine._cast_spell(c, t, "Fear")
|
||||
assert any(ac.condition.name == "panicked" for ac in t.active_conditions)
|
||||
assert not any(ac.condition.name == "shaken" for ac in t.active_conditions)
|
||||
|
||||
def test_successful_save_applies_shaken(self) -> None:
|
||||
"""Given: the target succeeds its Will save (natural 20)
|
||||
When: Fear resolves
|
||||
Then: it is shaken for 1 round (partial save), not panicked."""
|
||||
caster = _make_caster()
|
||||
target = _make_target()
|
||||
engine, c, t = _make_engine(caster, target, [20])
|
||||
engine._cast_spell(c, t, "Fear")
|
||||
shaken = [ac for ac in t.active_conditions if ac.condition.name == "shaken"]
|
||||
assert len(shaken) == 1
|
||||
assert shaken[0].rounds_left == 1
|
||||
assert not any(ac.condition.name == "panicked" for ac in t.active_conditions)
|
||||
|
||||
|
||||
class TestCureLightWounds:
|
||||
|
||||
Reference in New Issue
Block a user