fix(rules): combat maneuvers auto-succeed (natural 20) vs incapacitated targets
This commit is contained in:
@@ -4641,6 +4641,15 @@ class CombatEngine:
|
|||||||
attacker_cmb = self._maneuver_cmb(state) - pa_amt - grapple_penalty
|
attacker_cmb = self._maneuver_cmb(state) - pa_amt - grapple_penalty
|
||||||
target_cmd = self._maneuver_cmd(target)
|
target_cmd = self._maneuver_cmd(target)
|
||||||
roll = self._rng.d20()
|
roll = self._rng.d20()
|
||||||
|
# PF1e (SRD Combat Maneuvers): "If your target is immobilized,
|
||||||
|
# unconscious, or otherwise incapacitated, your maneuver
|
||||||
|
# automatically succeeds (treat as if you rolled a natural 20 on
|
||||||
|
# the attack roll)." The engine's incapacitated notion is the
|
||||||
|
# helpless set (helpless/paralyzed/asleep/unconscious, CRB
|
||||||
|
# "Conditions"); the natural 20 also feeds the margin-based
|
||||||
|
# outcomes (bull rush / overrun / reposition / drag / dirty trick).
|
||||||
|
if self._is_helpless(target):
|
||||||
|
roll = _NATURAL_TWENTY
|
||||||
# PF1e (CRB Combat Maneuvers): the provoked-AoO damage rides the
|
# PF1e (CRB Combat Maneuvers): the provoked-AoO damage rides the
|
||||||
# check total (floored at 0); natural 20/1 keep their rules below.
|
# check total (floored at 0); natural 20/1 keep their rules below.
|
||||||
total = max(0, roll + attacker_cmb - aoo_damage)
|
total = max(0, roll + attacker_cmb - aoo_damage)
|
||||||
|
|||||||
@@ -340,6 +340,44 @@ def test_disarm_fail_by_10_drops_attacker_weapon() -> None:
|
|||||||
assert any("disarm backfire -> drops sword" in line for line in engine._transcript)
|
assert any("disarm backfire -> drops sword" in line for line in engine._transcript)
|
||||||
|
|
||||||
|
|
||||||
|
# --- SRD Combat Maneuvers: auto-success against an incapacitated target -------
|
||||||
|
|
||||||
|
|
||||||
|
def test_maneuver_auto_succeeds_vs_helpless_target() -> None:
|
||||||
|
# SRD Combat Maneuvers: "If your target is immobilized, unconscious, or
|
||||||
|
# otherwise incapacitated, your maneuver automatically succeeds (treat
|
||||||
|
# as if you rolled a natural 20 on the attack roll)." b (bab 20) has CMD
|
||||||
|
# 30: a natural 1 would normally auto-fail, but the helpless target
|
||||||
|
# forces the natural-20 treatment -> trip succeeds.
|
||||||
|
a = make_state(make_combatant("a", attack_bonus=2), "players", (1, 1))
|
||||||
|
b = make_state(make_combatant("b", attack_bonus=20), "monsters", (1, 2))
|
||||||
|
_conditioned(b, "paralyzed")
|
||||||
|
engine = make_engine([1, 1], [a, b])
|
||||||
|
engine._execute_maneuver(a, b, "trip")
|
||||||
|
assert any(c.condition.name == "prone" for c in b.active_conditions)
|
||||||
|
assert "d20=20" in engine._transcript[-1]
|
||||||
|
|
||||||
|
|
||||||
|
def test_maneuver_auto_succeeds_vs_asleep_target_with_margin() -> None:
|
||||||
|
# The natural-20 treatment feeds the margin outcomes: a CMB 2 vs CMD 12,
|
||||||
|
# roll forced to 20 -> total 22, margin 10 -> bull rush pushes 3 cells.
|
||||||
|
a = make_state(make_combatant("a", attack_bonus=2), "players", (1, 1))
|
||||||
|
b = make_state(make_combatant("b"), "monsters", (1, 2))
|
||||||
|
_conditioned(b, "asleep")
|
||||||
|
engine = make_engine([1, 5], [a, b])
|
||||||
|
engine._execute_maneuver(a, b, "bull_rush")
|
||||||
|
assert b.pos == (1, 5)
|
||||||
|
|
||||||
|
|
||||||
|
def test_maneuver_normal_roll_still_fails_vs_awake_target() -> None:
|
||||||
|
# Control: the same natural 1 against a non-helpless target auto-fails.
|
||||||
|
a = make_state(make_combatant("a", attack_bonus=2), "players", (1, 1))
|
||||||
|
b = make_state(make_combatant("b"), "monsters", (1, 2))
|
||||||
|
engine = make_engine([1, 1], [a, b])
|
||||||
|
engine._execute_maneuver(a, b, "trip")
|
||||||
|
assert not any(c.condition.name == "prone" for c in b.active_conditions)
|
||||||
|
|
||||||
|
|
||||||
def test_overrun_knocks_prone_and_moves_through() -> None:
|
def test_overrun_knocks_prone_and_moves_through() -> None:
|
||||||
# d20=15+2=17 vs CMD 12: margin 5 -> prone AND moved through (CRB
|
# d20=15+2=17 vs CMD 12: margin 5 -> prone AND moved through (CRB
|
||||||
# "Overrun": prone only when the attack exceeds CMD by 5 or more).
|
# "Overrun": prone only when the attack exceeds CMD by 5 or more).
|
||||||
|
|||||||
Reference in New Issue
Block a user