feat(combat): add stand up and drop prone
This commit is contained in:
@@ -537,7 +537,10 @@ class Action:
|
||||
``ReactionEvent``; the readied action is then consumed.
|
||||
``total_defense`` and ``fight_defensively`` are standard defensive actions
|
||||
granting a temporary dodge AC bonus until the combatant's next turn;
|
||||
``fight_defensively`` also takes -4 on its attacks.
|
||||
``fight_defensively`` also takes -4 on its attacks. ``stand_up`` is a move
|
||||
action ending the prone condition (it provokes an AoO from each enemy
|
||||
threatening the combatant); ``drop_prone`` is a free action going prone
|
||||
until the combatant stands up.
|
||||
"""
|
||||
|
||||
kind: Literal[
|
||||
@@ -546,7 +549,7 @@ class Action:
|
||||
"maneuver", "channel_energy", "hex", "deed", "spell_combat",
|
||||
"inspire_courage", "retrieve_weapon", "repair_weapon",
|
||||
"activate_power_attack", "delay", "ready",
|
||||
"total_defense", "fight_defensively",
|
||||
"total_defense", "fight_defensively", "stand_up", "drop_prone",
|
||||
]
|
||||
target_id: str | None = None
|
||||
spell_name: str | None = None
|
||||
@@ -567,11 +570,13 @@ class Action:
|
||||
# no_line_of_effect, no_slot and insufficient_resource are declared here for
|
||||
# the deeper execution-feedback package (U6b). U3 adds delayed /
|
||||
# already_delayed for the delay action. U4 adds ready for the ready action.
|
||||
# U5b adds not_prone / already_prone for the prone actions.
|
||||
ACTION_RESULT_REASONS: frozenset[str] = frozenset({
|
||||
"ok", "unsupported_kind", "missing_target", "no_weapon", "out_of_range",
|
||||
"no_line_of_effect", "no_slot", "insufficient_resource",
|
||||
"downgraded:staggered", "downgraded:nauseated",
|
||||
"delayed", "already_delayed", "ready",
|
||||
"not_prone", "already_prone",
|
||||
})
|
||||
_REASON_OK = "ok"
|
||||
_REASON_UNSUPPORTED_KIND = "unsupported_kind"
|
||||
@@ -582,6 +587,8 @@ _REASON_DOWNGRADED_IMMEDIATE_SWIFT = "downgraded:immediate_swift"
|
||||
_REASON_DELAYED = "delayed"
|
||||
_REASON_ALREADY_DELAYED = "already_delayed"
|
||||
_REASON_READY = "ready"
|
||||
_REASON_NOT_PRONE = "not_prone"
|
||||
_REASON_ALREADY_PRONE = "already_prone"
|
||||
|
||||
# Action kinds that are declared but have no engine dispatch (U1 surfaces
|
||||
# them as explicit rejections instead of silent no-ops).
|
||||
@@ -2433,8 +2440,8 @@ class CombatEngine:
|
||||
return self._execute_delay(state, target, action)
|
||||
if kind == "ready":
|
||||
return self._execute_ready(state, action)
|
||||
if kind in ("total_defense", "fight_defensively"):
|
||||
return self._execute_defensive(state, kind)
|
||||
if kind in ("total_defense", "fight_defensively", "stand_up", "drop_prone"):
|
||||
return self._execute_self_action(state, kind)
|
||||
self._dispatch_action(state, target, action)
|
||||
return ActionResult(kind=kind, ok=True, reason=_REASON_OK)
|
||||
|
||||
@@ -2506,11 +2513,15 @@ class CombatEngine:
|
||||
)
|
||||
return ActionResult(kind=kind, ok=True, reason=_REASON_READY)
|
||||
|
||||
def _execute_defensive(self, state: CombatantState, kind: str) -> ActionResult:
|
||||
"""Dispatch a target-free defensive standard action to its helper."""
|
||||
def _execute_self_action(self, state: CombatantState, kind: str) -> ActionResult:
|
||||
"""Dispatch a target-free self-affecting action to its helper."""
|
||||
if kind == "total_defense":
|
||||
return self._execute_total_defense(state)
|
||||
return self._execute_fight_defensively(state)
|
||||
if kind == "fight_defensively":
|
||||
return self._execute_fight_defensively(state)
|
||||
if kind == "stand_up":
|
||||
return self._execute_stand_up(state)
|
||||
return self._execute_drop_prone(state)
|
||||
|
||||
def _execute_total_defense(self, state: CombatantState) -> ActionResult:
|
||||
"""Execute the total defense action (PF1e): standard action granting a
|
||||
@@ -2537,6 +2548,53 @@ class CombatEngine:
|
||||
)
|
||||
return ActionResult(kind="fight_defensively", ok=True, reason=_REASON_OK)
|
||||
|
||||
def _execute_stand_up(self, state: CombatantState) -> ActionResult:
|
||||
"""Execute the stand up action (PF1e): move action that ends prone.
|
||||
|
||||
Standing up provokes an AoO from each enemy threatening the
|
||||
combatant's squares (``_check_move_aoo``, the movement machinery);
|
||||
the AoOs resolve while the combatant is still prone (-4 AC). The
|
||||
prone condition is then removed from both representations: the
|
||||
duration-tracked ``active_conditions`` (trip/overrun) and the
|
||||
duration-less ``conditions`` list (drop_prone). If a provoked AoO
|
||||
takes the combatant out of the fight, the removal is a formality —
|
||||
inactive combatants do not act.
|
||||
"""
|
||||
kind = "stand_up"
|
||||
if not self._has_condition_named(state, "prone"):
|
||||
self._log(
|
||||
f"round {self._current_round} {state.combatant.id}: "
|
||||
f"{kind} rejected ({_REASON_NOT_PRONE})"
|
||||
)
|
||||
return ActionResult(kind=kind, ok=False, reason=_REASON_NOT_PRONE)
|
||||
self._check_move_aoo(state)
|
||||
state.active_conditions = [
|
||||
ac for ac in state.active_conditions if ac.condition.name != "prone"
|
||||
]
|
||||
state.conditions = [c for c in state.conditions if c.name != "prone"]
|
||||
self._log(f"round {self._current_round} {state.combatant.id}: stands up")
|
||||
return ActionResult(kind=kind, ok=True, reason=_REASON_OK)
|
||||
|
||||
def _execute_drop_prone(self, state: CombatantState) -> ActionResult:
|
||||
"""Execute the drop prone action (PF1e): free action that goes prone.
|
||||
|
||||
The prone condition is appended to the duration-less ``conditions``
|
||||
list (trip/overrun use the duration-tracked ``active_conditions``
|
||||
instead), so it persists until the combatant stands up. Both lists
|
||||
are read by ``_has_condition_named``, so the prone modifiers
|
||||
(-4 AC, -4 ranged attacks) apply immediately.
|
||||
"""
|
||||
kind = "drop_prone"
|
||||
if self._has_condition_named(state, "prone"):
|
||||
self._log(
|
||||
f"round {self._current_round} {state.combatant.id}: "
|
||||
f"{kind} rejected ({_REASON_ALREADY_PRONE})"
|
||||
)
|
||||
return ActionResult(kind=kind, ok=False, reason=_REASON_ALREADY_PRONE)
|
||||
state.conditions.append(CONDITIONS_BY_NAME["prone"])
|
||||
self._log(f"round {self._current_round} {state.combatant.id}: drops prone")
|
||||
return ActionResult(kind=kind, ok=True, reason=_REASON_OK)
|
||||
|
||||
def _dispatch_action(
|
||||
self, state: CombatantState, target: CombatantState | None, action: Action
|
||||
) -> None:
|
||||
|
||||
Reference in New Issue
Block a user