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:
@@ -173,6 +173,10 @@ class ConditionEffect:
|
||||
condition_name: str
|
||||
duration_rounds: int
|
||||
save_negates: bool = True
|
||||
# PF1e partial save (SRD spells/fear.md): on a SUCCESSFUL save the target
|
||||
# is not spared outright but suffers a weaker condition instead. Encoded as
|
||||
# (condition_name, duration_rounds); None = a successful save fully negates.
|
||||
save_partial: tuple[str, int] | None = None
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
@@ -468,6 +472,15 @@ def _parse_recurring(value: Json) -> RecurringDamageSpec:
|
||||
)
|
||||
|
||||
|
||||
def _parse_save_partial(value: Json) -> tuple[str, int]:
|
||||
data = _as_dict(value)
|
||||
duration = _as_int(data["duration_rounds"])
|
||||
if duration < 1:
|
||||
msg = f"on_success duration_rounds must be >= 1, got {duration}"
|
||||
raise ValueError(msg)
|
||||
return (_as_str(data["condition_name"]), duration)
|
||||
|
||||
|
||||
def _parse_effect(data: Json) -> SpellEffect:
|
||||
effect = _as_dict(data)
|
||||
effect_type = _as_str(effect["type"])
|
||||
@@ -502,10 +515,13 @@ def _parse_effect(data: Json) -> SpellEffect:
|
||||
recurring=recurring,
|
||||
)
|
||||
if effect_type == "condition":
|
||||
partial_raw = effect.get("on_success")
|
||||
save_partial = None if partial_raw is None else _parse_save_partial(partial_raw)
|
||||
return ConditionEffect(
|
||||
condition_name=_as_str(effect["condition_name"]),
|
||||
duration_rounds=_as_int(effect["duration_rounds"]),
|
||||
save_negates=bool(effect.get("save_negates", True)),
|
||||
save_partial=save_partial,
|
||||
)
|
||||
if effect_type == "heal":
|
||||
return HealEffect(
|
||||
|
||||
Reference in New Issue
Block a user