fix(rules): allow a staggered creature to withdraw as a standard action
SRD 'Full-Round Actions - Restricted Withdraw': 'If you are limited to taking only a standard action each round you can withdraw as a standard action. In this case, you may move up to your speed.' _staggered_filter now keeps withdraw as the one move-or-standard action (spending the slot) instead of dropping it, and _flee_path halves the budget to single speed for a staggered/disabled creature (new _is_staggered_limited helper). Filter-level and engine-level tests added. Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
@@ -1641,7 +1641,7 @@ class CombatEngine:
|
||||
)
|
||||
actions = kept
|
||||
state.immediate_used = False
|
||||
if self._is_disabled(state) or self._has_condition_named(state, "staggered"):
|
||||
if self._is_staggered_limited(state):
|
||||
kept = _staggered_filter(actions, cast_time=self._cast_time_of)
|
||||
results.extend(
|
||||
ActionResult(kind=kind, ok=False, reason=_REASON_DOWNGRADED_STAGGERED)
|
||||
@@ -1677,6 +1677,16 @@ class CombatEngine:
|
||||
disabled — staggered, half speed, and a strenuous action costs 1 hp."""
|
||||
return state.hp == 0
|
||||
|
||||
def _is_staggered_limited(self, state: CombatantState) -> bool:
|
||||
"""True when the creature is limited to one move OR one standard
|
||||
action per round (CRB "Conditions — Staggered"; a disabled creature
|
||||
at 0 hp is staggered). Such a creature cannot take full-round actions
|
||||
— except Restricted Withdraw (SRD "Full-Round Actions"), which it may
|
||||
take as a standard action moving up to its speed."""
|
||||
return self._is_disabled(state) or self._has_condition_named(
|
||||
state, "staggered"
|
||||
)
|
||||
|
||||
def _is_helpless(self, state: CombatantState) -> bool:
|
||||
"""PF1e (CRB): helpless = paralyzed, held, bound, sleeping, unconscious.
|
||||
|
||||
@@ -6479,11 +6489,18 @@ class CombatEngine:
|
||||
def _flee_path(self, state: CombatantState, threat: CombatantState) -> list[Pos]:
|
||||
"""Movement path away from ``threat``, up to double speed.
|
||||
|
||||
A staggered/disabled creature taking Restricted Withdraw (SRD "Full-Round
|
||||
Actions": "you can withdraw as a standard action. In this case, you may
|
||||
move up to your speed") gets a single-speed budget instead.
|
||||
|
||||
Uses the Dijkstra cost field from the threat and greedily picks the
|
||||
step that maximizes distance (cost) from the threat each cell.
|
||||
"""
|
||||
speed_mult = self._get_speed_mult(state)
|
||||
max_cells = int(2 * (_speed_ft(state) // _SQUARE_FT) * speed_mult)
|
||||
withdraw_mult = 1 if self._is_staggered_limited(state) else 2
|
||||
max_cells = int(
|
||||
withdraw_mult * (_speed_ft(state) // _SQUARE_FT) * speed_mult
|
||||
)
|
||||
if max_cells <= 0:
|
||||
return []
|
||||
blocked = self._blocked(state)
|
||||
@@ -6537,6 +6554,10 @@ class CombatEngine:
|
||||
def _withdraw(self, state: CombatantState, threat: CombatantState | None) -> None:
|
||||
"""Full-round action: move up to 2x speed away from the nearest enemy.
|
||||
|
||||
Restricted Withdraw (SRD "Full-Round Actions"): a staggered/disabled
|
||||
creature takes this as a standard action and moves up to its speed
|
||||
(single, not double — see ``_flee_path``).
|
||||
|
||||
The starting square is not threatened — no AoO when leaving it.
|
||||
Subsequent squares provoke AoOs normally (step-by-step, as for move).
|
||||
Like every other movement it is mutually exclusive with movement
|
||||
@@ -6821,8 +6842,12 @@ def _staggered_filter(
|
||||
and drops full-round actions plus any SECOND move-or-standard. A leading
|
||||
``full_attack`` (already in range) is downgraded to a single standard
|
||||
``attack`` that consumes the one move-or-standard slot; a further
|
||||
full-round action (charge, withdraw, run, a full-round/one-round cast,
|
||||
spell_combat) is dropped.
|
||||
full-round action (charge, run, a full-round/one-round cast,
|
||||
spell_combat) is dropped. Exception (SRD "Full-Round Actions — Restricted
|
||||
Withdraw"): "If you are limited to taking only a standard action each
|
||||
round you can withdraw as a standard action. In this case, you may move
|
||||
up to your speed" — a ``withdraw`` is kept as the one standard action and
|
||||
``_flee_path`` halves its budget to single speed.
|
||||
"""
|
||||
kept: list[Action] = []
|
||||
slot_used = False # the single move-OR-standard slot is spent
|
||||
@@ -6836,6 +6861,10 @@ def _staggered_filter(
|
||||
continue
|
||||
slot = _effective_slot(action, cast_time)
|
||||
if slot == "full_round":
|
||||
if action.kind == "withdraw" and not slot_used:
|
||||
# Restricted Withdraw: standard action, move up to speed.
|
||||
kept.append(action)
|
||||
slot_used = True
|
||||
continue
|
||||
if slot in ("standard", "move"):
|
||||
if slot_used:
|
||||
|
||||
Reference in New Issue
Block a user