fix(rules): keep swift and free actions for staggered combatants
PF1e (CRB, Staggered): a staggered creature takes a single move OR standard action but can still take free, swift, and immediate actions. _staggered_filter returned only the first non-full-round action, so a disabled combatant with a queued swift bonus (deed/inspire_courage) lost its standard action. It now keeps every swift and free action plus the first move-or-standard, preserving the full_attack -> single attack downgrade. Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
@@ -6054,19 +6054,40 @@ def _staggered_filter(
|
||||
*,
|
||||
cast_time: CastTimeResolver = _default_cast_time,
|
||||
) -> tuple[Action, ...]:
|
||||
"""Staggered: one move OR one standard action per round, no full-round.
|
||||
"""Staggered: one move OR one standard action, plus every swift and free.
|
||||
|
||||
A leading ``full_attack`` (already in range) becomes a single standard
|
||||
``attack``; anything whose effective slot is full-round (charge,
|
||||
withdraw, run, a full-round/one-round cast, spell_combat) is dropped.
|
||||
PF1e (CRB, Conditions — Staggered): "A staggered creature may take a
|
||||
single move action or standard action each round (but not both, nor can
|
||||
he take full-round actions). A staggered creature can still take free,
|
||||
swift, and immediate actions." So the filter walks the sequence in order
|
||||
and keeps every swift and free action, the FIRST move-or-standard action,
|
||||
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.
|
||||
"""
|
||||
kept: list[Action] = []
|
||||
slot_used = False # the single move-OR-standard slot is spent
|
||||
for action in actions:
|
||||
if action.kind == "full_attack":
|
||||
return (Action(kind="attack", target_id=action.target_id),)
|
||||
if _effective_slot(action, cast_time) == "full_round":
|
||||
# A full attack is a full-round action; staggered it collapses to
|
||||
# a single standard attack (the kept move-or-standard action).
|
||||
if not slot_used:
|
||||
kept.append(Action(kind="attack", target_id=action.target_id))
|
||||
slot_used = True
|
||||
continue
|
||||
return (action,)
|
||||
return ()
|
||||
slot = _effective_slot(action, cast_time)
|
||||
if slot == "full_round":
|
||||
continue
|
||||
if slot in ("standard", "move"):
|
||||
if slot_used:
|
||||
continue
|
||||
kept.append(action)
|
||||
slot_used = True
|
||||
else: # swift / free — always allowed while staggered (CRB)
|
||||
kept.append(action)
|
||||
return tuple(kept)
|
||||
|
||||
|
||||
def _nauseated_filter(actions: tuple[Action, ...]) -> tuple[Action, ...]:
|
||||
|
||||
Reference in New Issue
Block a user