refactor(combat): replace ac_penalty with effect system

This commit is contained in:
2026-08-17 22:49:50 +02:00
parent 8ac6f9eabe
commit 88f11de40f
2 changed files with 13 additions and 9 deletions
+11 -7
View File
@@ -51,8 +51,8 @@ Phase 0 documented deviations from PF1e (conventions):
- Charge: a full-round action that moves in a straight line (Bresenham) up to
2x speed toward the closest square from which the charger can melee the
target, then makes a single melee attack at +2. The charger takes -2 AC
until the start of their next turn (tracked in ``ac_penalty``, cleared in
``_take_turn``). The straight line must be clear of difficult terrain,
until the start of their next turn (tracked as a StatModifier in
``effects``, cleared in ``_take_turn``). The straight line must be clear of difficult terrain,
obstacles, and creatures; minimum 2 cells (10 ft). ``default_policy``
chooses a charge when the enemy is out of reach but a valid charge path
exists.
@@ -76,9 +76,11 @@ Phase 0 documented deviations from PF1e (conventions):
from __future__ import annotations
from collections.abc import Callable
from dataclasses import asdict, dataclass
from dataclasses import asdict, dataclass, field
from typing import TYPE_CHECKING
from pf1e_simulator.effects import StatModifier, resolve_modifiers
if TYPE_CHECKING:
from typing import Literal
@@ -156,7 +158,7 @@ class CombatantState:
side: str
pos: Pos
hp: int
ac_penalty: int = 0
effects: list[StatModifier] = field(default_factory=list)
moved_this_turn: bool = False
@property
@@ -250,7 +252,7 @@ class CombatEngine:
def _take_turn(self, state: CombatantState) -> bool:
"""Execute one combatant's full turn; return True if the battle is over."""
state.ac_penalty = 0
state.effects.clear()
state.moved_this_turn = False
actions = self._policy(self, state)
before = len(self._transcript)
@@ -358,7 +360,9 @@ class CombatEngine:
flank = self._flanking_bonus(attacker, target) if weapon.kind == "melee" else 0
base_bonus = bonus_override if bonus_override is not None else weapon.attack_bonus
total = roll + base_bonus + penalty + flank
ac = target.combatant.ac.total - target.ac_penalty
ac = target.combatant.ac.total + resolve_modifiers(
e for e in target.effects if e.target == "ac"
)
occupied = frozenset(
s.pos for s in self._states if s.active and s is not attacker and s is not target
)
@@ -620,7 +624,7 @@ class CombatEngine:
)
if not attacker.active:
return
attacker.ac_penalty = _CHARGE_AC_PENALTY
attacker.effects.append(StatModifier(target="ac", value=-_CHARGE_AC_PENALTY))
bonus = weapon.attack_bonus + _CHARGE_ATTACK_BONUS
self._resolve_swing(
attacker, target, weapon, label="charge", bonus_override=bonus
+2 -2
View File
@@ -18,7 +18,7 @@ from dataclasses import dataclass
from typing import TYPE_CHECKING, Literal
if TYPE_CHECKING:
from collections.abc import Sequence
from collections.abc import Iterable
BonusType = Literal[
"", # untyped — stacks with everything
@@ -65,7 +65,7 @@ class StatModifier:
weapon_filter: str | None = None
def resolve_modifiers(modifiers: Sequence[StatModifier]) -> int:
def resolve_modifiers(modifiers: Iterable[StatModifier]) -> int:
"""Sum stat modifiers applying PF1e stacking rules.
Stacking bonus types (dodge, racial, trait, untyped) all sum together.