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:
@@ -6010,23 +6010,44 @@ class CombatEngine:
|
||||
spell_name: str,
|
||||
) -> str:
|
||||
if saved and effect.save_negates:
|
||||
# PF1e partial save (SRD spells/fear.md): a successful save does
|
||||
# not always spare the target — it may impose a weaker condition
|
||||
# (Fear: shaken for 1 round instead of panicked).
|
||||
if effect.save_partial is not None:
|
||||
name, duration = effect.save_partial
|
||||
applied = self._impose_condition(target, name, duration, spell_name)
|
||||
if not applied:
|
||||
return ""
|
||||
return f"{name} ({duration}r) (partial save)"
|
||||
return f"{effect.condition_name} negated"
|
||||
cond = CONDITIONS_BY_NAME.get(effect.condition_name)
|
||||
return self._impose_condition(
|
||||
target, effect.condition_name, effect.duration_rounds, spell_name
|
||||
)
|
||||
|
||||
def _impose_condition(
|
||||
self,
|
||||
target: CombatantState,
|
||||
condition_name: str,
|
||||
duration_rounds: int,
|
||||
spell_name: str,
|
||||
) -> str:
|
||||
"""Append a timed condition to the target; handle the bleeding DoT."""
|
||||
cond = CONDITIONS_BY_NAME.get(condition_name)
|
||||
if cond is None:
|
||||
return ""
|
||||
target.active_conditions.append(
|
||||
ActiveCondition(
|
||||
condition=cond,
|
||||
rounds_left=effect.duration_rounds,
|
||||
rounds_left=duration_rounds,
|
||||
source=f"spell: {spell_name}",
|
||||
)
|
||||
)
|
||||
if effect.condition_name == "bleeding":
|
||||
if condition_name == "bleeding":
|
||||
# The bleeding condition's damage-over-time is engine-driven
|
||||
# (CombatantState.bleed_damage, CRB Conditions — bleeding); the
|
||||
# SRD Bleed cantrip (spells/bleed.md) deals 1 bleed damage/round.
|
||||
target.bleed_damage = max(target.bleed_damage, 1)
|
||||
return f"{effect.condition_name} ({effect.duration_rounds}r)"
|
||||
return f"{condition_name} ({duration_rounds}r)"
|
||||
|
||||
def _apply_heal_effect(
|
||||
self,
|
||||
@@ -6222,10 +6243,14 @@ class CombatEngine:
|
||||
return effect
|
||||
if isinstance(effect, ConditionEffect):
|
||||
duration = effect.duration_rounds * 2 if has_extend else effect.duration_rounds
|
||||
partial = effect.save_partial
|
||||
if partial is not None and has_extend:
|
||||
partial = (partial[0], partial[1] * 2)
|
||||
return ConditionEffect(
|
||||
condition_name=effect.condition_name,
|
||||
duration_rounds=duration,
|
||||
save_negates=effect.save_negates,
|
||||
save_partial=partial,
|
||||
)
|
||||
if isinstance(effect, BuffEffect):
|
||||
duration = effect.duration_rounds * 2 if has_extend else effect.duration_rounds
|
||||
|
||||
Reference in New Issue
Block a user