feat(combat): add saving throws (fort/ref/will, natural 1/20, effect modifiers)
This commit is contained in:
@@ -189,6 +189,16 @@ class AttackResult:
|
||||
base_bonus: int = 0
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class SaveResult:
|
||||
"""Outcome of one saving throw roll."""
|
||||
|
||||
success: bool
|
||||
roll: int
|
||||
total: int
|
||||
dc: int
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class Action:
|
||||
"""What a policy wants a combatant to do this turn.
|
||||
@@ -401,6 +411,22 @@ class CombatEngine:
|
||||
return damage
|
||||
return max(0, damage - dr.amount)
|
||||
|
||||
def resolve_save(
|
||||
self, state: CombatantState, save_type: Literal["fort", "ref", "will"], dc: int
|
||||
) -> SaveResult:
|
||||
"""Roll a saving throw (fort/ref/will) against ``dc``.
|
||||
|
||||
PF1e: natural 1 = automatic failure, natural 20 = automatic success.
|
||||
Effect modifiers (StatModifier with matching target) are applied via
|
||||
``resolve_modifiers`` — same bonus-type stacking rules as attacks.
|
||||
"""
|
||||
roll = self._rng.d20()
|
||||
base = getattr(state.combatant.saves, save_type)
|
||||
bonus = resolve_modifiers(e for e in state.effects if e.target == save_type)
|
||||
total = roll + base + bonus
|
||||
success = roll != _NATURAL_ONE and (roll == _NATURAL_TWENTY or total >= dc)
|
||||
return SaveResult(success=success, roll=roll, total=total, dc=dc)
|
||||
|
||||
def _roll_initiative(self) -> list[CombatantState]:
|
||||
rolls: dict[str, tuple[int, int]] = {}
|
||||
for state in self._states:
|
||||
|
||||
Reference in New Issue
Block a user