refactor(combat): replace ac_penalty with effect system
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user