fix(rules): shield of faith grants its SRD deflection AC buff (+2, +1 per six caster levels, max +5) via a new BuffEffect flat scaling

This commit is contained in:
2026-09-17 22:36:18 +02:00
parent 27f962920f
commit 47f563e11a
5 changed files with 154 additions and 31 deletions
+38 -5
View File
@@ -6461,7 +6461,10 @@ class CombatEngine:
effect, target, caster=caster, saved=saved,
empower=empower, maximize=maximize, spell_name=spell_name,
)
return self._apply_buff_effect(effect, target, spell_name=spell_name)
return self._apply_buff_effect(
effect, target, spell_name=spell_name,
caster_level=caster.combatant.level,
)
def _apply_damage_effect(
self,
@@ -6678,16 +6681,42 @@ class CombatEngine:
return f"+{actual} HP"
def _apply_buff_effect(
self, effect: BuffEffect, target: CombatantState, *, spell_name: str
self,
effect: BuffEffect,
target: CombatantState,
*,
spell_name: str,
caster_level: int,
) -> str:
# PF1e caster-level scaling on buff values (SRD shield of faith
# "+2 deflection, +1 per six caster levels (maximum +5)"): the flat
# extra is added to EVERY modifier of the buff.
modifiers = effect.modifiers
if effect.scaling_flat is not None:
extra = min(
effect.scaling_flat.max_amount,
(caster_level // effect.scaling_flat.per_levels)
* effect.scaling_flat.amount,
)
if extra > 0:
modifiers = tuple(
StatModifier(
target=mod.target,
value=mod.value + extra,
bonus_type=mod.bonus_type,
condition=mod.condition,
weapon_filter=mod.weapon_filter,
)
for mod in modifiers
)
target.active_buffs.append(
ActiveBuff(
modifiers=effect.modifiers,
modifiers=modifiers,
rounds_left=effect.duration_rounds,
source=f"spell: {spell_name}",
)
)
return f"buff {len(effect.modifiers)} mods ({effect.duration_rounds}r)"
return f"buff {len(modifiers)} mods ({effect.duration_rounds}r)"
def _apply_penalty_effect(
self,
@@ -6912,7 +6941,11 @@ class CombatEngine:
)
if isinstance(effect, BuffEffect):
duration = effect.duration_rounds * 2 if has_extend else effect.duration_rounds
return BuffEffect(modifiers=effect.modifiers, duration_rounds=duration)
return BuffEffect(
modifiers=effect.modifiers,
duration_rounds=duration,
scaling_flat=effect.scaling_flat,
)
if isinstance(effect, PenaltyEffect):
duration = (
effect.duration_rounds_per_level * 2
+12
View File
@@ -213,8 +213,17 @@ class HealEffect:
@dataclass(frozen=True)
class BuffEffect:
"""Transient stat modifiers (PF1e buff spells).
``scaling_flat`` (SRD shield of faith "+2 deflection, +1 per six caster
levels (maximum +5)", divine favor "+1 per three caster levels (maximum
+3)") adds the resolved flat bonus to EVERY modifier's value at cast
time, mirroring ``DamageEffect.effective_formula``'s flat scaling.
"""
modifiers: tuple[StatModifier, ...]
duration_rounds: int
scaling_flat: DamageFlatScaling | None = None
@dataclass(frozen=True)
@@ -611,9 +620,12 @@ def _parse_heal_effect(effect: dict[str, Json]) -> HealEffect:
def _parse_buff_effect(effect: dict[str, Json]) -> BuffEffect:
flat_raw = effect.get("scaling_flat")
scaling_flat = None if flat_raw is None else _parse_flat_scaling(flat_raw)
return BuffEffect(
modifiers=tuple(_parse_modifier(m) for m in _as_list(effect["modifiers"])),
duration_rounds=_as_int(effect.get("duration_rounds", 0)),
scaling_flat=scaling_flat,
)