fix(rules): total defense and fighting defensively cannot be combined (SRD Total Defense) - no stacking, second declaration rejected

This commit is contained in:
2026-09-17 23:13:22 +02:00
parent 980d53d80b
commit a144423522
2 changed files with 64 additions and 3 deletions
+19 -3
View File
@@ -1064,7 +1064,7 @@ ACTION_RESULT_REASONS: frozenset[str] = frozenset({
"downgraded:surprise",
"delayed", "already_delayed", "ready",
"not_prone", "already_prone", "unreachable_destination",
"weapon_unavailable", "invalid_destination",
"weapon_unavailable", "invalid_destination", "cannot_combine",
})
_REASON_OK = "ok"
_REASON_UNSUPPORTED_KIND = "unsupported_kind"
@@ -1086,6 +1086,7 @@ _REASON_INVALID_DESTINATION = "invalid_destination"
_REASON_NO_WEAPON = "no_weapon"
_REASON_OUT_OF_RANGE = "out_of_range"
_REASON_NO_LINE_OF_EFFECT = "no_line_of_effect"
_REASON_CANNOT_COMBINE = "cannot_combine"
# Action kinds that are declared but have no engine dispatch (U1 surfaces
# them as explicit rejections instead of silent no-ops).
@@ -4115,10 +4116,14 @@ class CombatEngine:
+4 dodge bonus to AC until the start of the combatant's next turn.
PF1e (SRD "Total Defense"): "You can't make attacks of opportunity
while using total defense" — the flag drops the AoO allowance to 0
(see ``_aoo_allowance``).
(see ``_aoo_allowance``) — and "You can't combine total defense with
fighting defensively": the bonus is SET (never stacked on a prior
+2) and a pending fighting-defensively declaration is voided (under
total defense no attack is made, so its -4 is moot).
"""
state.total_defense = True
state.defensive_ac_bonus += _TOTAL_DEFENSE_AC_BONUS
state.fight_defensively = False
state.defensive_ac_bonus = _TOTAL_DEFENSE_AC_BONUS
self._log(
f"round {self._current_round} {state.combatant.id}: "
f"total defense (+{_TOTAL_DEFENSE_AC_BONUS} AC)"
@@ -4129,7 +4134,18 @@ class CombatEngine:
"""Execute the fighting defensively action (PF1e): standard action
granting a +2 dodge bonus to AC until the start of the combatant's
next turn, at the cost of -4 on attack and crit confirmation rolls.
PF1e (SRD "Total Defense"): "You can't combine total defense with
fighting defensively" — the declaration is rejected while total
defense is active.
"""
if state.total_defense:
self._log(
f"round {self._current_round} {state.combatant.id}: "
f"fight_defensively rejected ({_REASON_CANNOT_COMBINE})"
)
return ActionResult(
kind="fight_defensively", ok=False, reason=_REASON_CANNOT_COMBINE
)
state.fight_defensively = True
state.defensive_ac_bonus += _FIGHT_DEFENSIVELY_AC_BONUS
self._log(