feat(rules): scale healing spells with caster level
PF1e (SRD spells/cure-light-wounds.md): 'cures 1d8 points of damage +1 point per caster level (maximum +5)'; (spells/cure-moderate-wounds.md): 'cures 2d8 points of damage +1 point per caster level (maximum +10)'. A HealEffect now carries the same DamageFlatScaling spec as damage effects (S1b) and resolves it through effective_formula(caster_level) at cast time, so the flat bonus grows with the caster and stops at the stated cap. The engine passes the caster's level into _apply_heal_effect (required keyword: no silent level-0 under-heal); Maximize Spell maximizes the scaled formula. Cure Light/Moderate Wounds ship the SRD values; inflict_light_wounds already carried its scaling_flat. Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
@@ -6202,7 +6202,10 @@ class CombatEngine:
|
||||
if isinstance(effect, ConditionEffect):
|
||||
return self._apply_condition_effect(effect, target, saved=saved, spell_name=spell_name)
|
||||
if isinstance(effect, HealEffect):
|
||||
return self._apply_heal_effect(effect, target, empower=empower, maximize=maximize)
|
||||
return self._apply_heal_effect(
|
||||
effect, target, caster_level=caster.combatant.level,
|
||||
empower=empower, maximize=maximize,
|
||||
)
|
||||
return self._apply_buff_effect(effect, target, spell_name=spell_name)
|
||||
|
||||
def _apply_damage_effect(
|
||||
@@ -6386,12 +6389,14 @@ class CombatEngine:
|
||||
effect: HealEffect,
|
||||
target: CombatantState,
|
||||
*,
|
||||
caster_level: int,
|
||||
empower: bool,
|
||||
maximize: bool,
|
||||
) -> str:
|
||||
heal = effect.formula.roll(self._rng)
|
||||
formula = effect.effective_formula(caster_level)
|
||||
heal = formula.roll(self._rng)
|
||||
if maximize:
|
||||
heal = effect.formula.max_value()
|
||||
heal = formula.max_value()
|
||||
if empower:
|
||||
heal = int(heal * 1.5)
|
||||
old_hp = target.hp
|
||||
|
||||
@@ -182,6 +182,27 @@ class ConditionEffect:
|
||||
@dataclass(frozen=True)
|
||||
class HealEffect:
|
||||
formula: DiceExpr
|
||||
# PF1e caster-level scaling for healing (SRD spells/cure-light-wounds.md:
|
||||
# "cures 1d8 points of damage +1 point per caster level (maximum +5)" and
|
||||
# cure-moderate-wounds.md: "cures 2d8 points of damage +1 point per caster
|
||||
# level (maximum +10)"). Absent = level-independent healing, the pre-S5b
|
||||
# behaviour.
|
||||
scaling_flat: DamageFlatScaling | None = None
|
||||
|
||||
def effective_formula(self, caster_level: int) -> DiceExpr:
|
||||
"""The healing formula resolved for a caster of ``caster_level``.
|
||||
|
||||
PF1e (CRB, Magic — Caster Level): the flat bonus grows with the caster
|
||||
and stops at the stated cap — bonus = min(max, (level // per_levels)
|
||||
* amount), mirroring ``DamageEffect.effective_formula``.
|
||||
"""
|
||||
bonus = self.formula.bonus
|
||||
if self.scaling_flat is not None:
|
||||
bonus += min(
|
||||
self.scaling_flat.max_amount,
|
||||
(caster_level // self.scaling_flat.per_levels) * self.scaling_flat.amount,
|
||||
)
|
||||
return DiceExpr(count=self.formula.count, sides=self.formula.sides, bonus=bonus)
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
@@ -529,8 +550,11 @@ def _parse_effect(data: Json) -> SpellEffect:
|
||||
save_partial=save_partial,
|
||||
)
|
||||
if effect_type == "heal":
|
||||
flat_raw = effect.get("scaling_flat")
|
||||
scaling_flat = None if flat_raw is None else _parse_flat_scaling(flat_raw)
|
||||
return HealEffect(
|
||||
formula=parse_dice(_as_str(effect["formula"])),
|
||||
scaling_flat=scaling_flat,
|
||||
)
|
||||
if effect_type == "buff":
|
||||
return BuffEffect(
|
||||
|
||||
Reference in New Issue
Block a user