fix(rules): apply maneuver AoO damage as a penalty to the maneuver
PF1e (CRB Combat Maneuvers): "If you are hit by the target, you take the damage normally and apply that amount as a penalty to the attack roll to perform the maneuver." Capture the provoked AoO's damage in _execute_maneuver and subtract it from the check total (floored at 0; natural 20 still succeeds, natural 1 still fails; dropping below 0 hp still forfeits). Escape and the Improved-feat exemption are untouched. Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
@@ -946,8 +946,12 @@ Règles modélisées :
|
|||||||
(conformance P2). Conformance P2 : tenter une manœuvre (sauf `escape`)
|
(conformance P2). Conformance P2 : tenter une manœuvre (sauf `escape`)
|
||||||
**provoque une attaque d'opportunité** de la cible, résolue **avant** le jet
|
**provoque une attaque d'opportunité** de la cible, résolue **avant** le jet
|
||||||
de CMB ; le don « Improved <Maneuver> » correspondant (Improved Trip, …)
|
de CMB ; le don « Improved <Maneuver> » correspondant (Improved Trip, …)
|
||||||
annule cette provocation ; si l'AoO fait passer l'attaquant sous 0 pv, la
|
annule cette provocation ; les dégâts infligés par cette AoO sont appliqués
|
||||||
manœuvre est perdue. L'AoO de manœuvre consomme le budget d'AoO du round de
|
comme **pénalité au jet de manœuvre** (conformance A2 — CRB « Combat
|
||||||
|
Maneuvers » : « apply that amount as a penalty to the attack roll to perform
|
||||||
|
the maneuver », total planché à 0, 20 naturel réussit / 1 naturel échoue
|
||||||
|
toujours) ; si l'AoO fait passer l'attaquant sous 0 pv, la manœuvre est
|
||||||
|
perdue. L'AoO de manœuvre consomme le budget d'AoO du round de
|
||||||
la cible (Combat Reflexes inclus). Conformance B16 (CRB « Power Attack ») :
|
la cible (Combat Reflexes inclus). Conformance B16 (CRB « Power Attack ») :
|
||||||
la pénalité −X de Power Attack actif s'applique **au jet de CMB** de la
|
la pénalité −X de Power Attack actif s'applique **au jet de CMB** de la
|
||||||
manœuvre (X = BAB//4 + 1) — la pénalité couvre « les jets d'attaque de mêlée
|
manœuvre (X = BAB//4 + 1) — la pénalité couvre « les jets d'attaque de mêlée
|
||||||
|
|||||||
@@ -3839,10 +3839,16 @@ class CombatEngine:
|
|||||||
|
|
||||||
# PF1e (CRB, Combat Maneuver): attempting the maneuver provokes an
|
# PF1e (CRB, Combat Maneuver): attempting the maneuver provokes an
|
||||||
# attack of opportunity from the target — resolved BEFORE the CMB
|
# attack of opportunity from the target — resolved BEFORE the CMB
|
||||||
# check — unless the attacker holds the Improved feat for it. Being
|
# check — unless the attacker holds the Improved feat for it. Damage
|
||||||
# knocked below 0 hp by that AoO forfeits the maneuver.
|
# taken from that AoO is applied as a penalty to the maneuver check
|
||||||
|
# (CRB: "apply that amount as a penalty to the attack roll to perform
|
||||||
|
# the maneuver"); being knocked below 0 hp by it forfeits the
|
||||||
|
# maneuver.
|
||||||
|
aoo_damage = 0
|
||||||
if not self._has_feat(state, _IMPROVED_MANEUVER_FEATS[maneuver]):
|
if not self._has_feat(state, _IMPROVED_MANEUVER_FEATS[maneuver]):
|
||||||
|
hp_before = state.hp
|
||||||
self._maneuver_provoked_aoo(state, target)
|
self._maneuver_provoked_aoo(state, target)
|
||||||
|
aoo_damage = max(0, hp_before - state.hp)
|
||||||
if not state.active:
|
if not state.active:
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -3855,7 +3861,15 @@ class CombatEngine:
|
|||||||
attacker_cmb = self._maneuver_cmb(state) - pa_amt
|
attacker_cmb = self._maneuver_cmb(state) - pa_amt
|
||||||
target_cmd = self._maneuver_cmd(target)
|
target_cmd = self._maneuver_cmd(target)
|
||||||
roll = self._rng.d20()
|
roll = self._rng.d20()
|
||||||
total = roll + attacker_cmb
|
# PF1e (CRB Combat Maneuvers): the provoked-AoO damage rides the
|
||||||
|
# check total (floored at 0); natural 20/1 keep their rules below.
|
||||||
|
total = max(0, roll + attacker_cmb - aoo_damage)
|
||||||
|
if aoo_damage:
|
||||||
|
self._log(
|
||||||
|
f"round {self._current_round} {state.combatant.id}: "
|
||||||
|
f"maneuver AoO dealt {aoo_damage} damage "
|
||||||
|
f"(-{aoo_damage} on the check)"
|
||||||
|
)
|
||||||
m = _ManeuverRoll(
|
m = _ManeuverRoll(
|
||||||
roll=roll, attacker_cmb=attacker_cmb, total=total,
|
roll=roll, attacker_cmb=attacker_cmb, total=total,
|
||||||
target_cmd=target_cmd,
|
target_cmd=target_cmd,
|
||||||
|
|||||||
@@ -441,6 +441,30 @@ def test_maneuver_aoo_can_drop_attacker_and_forfeit_trip() -> None:
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_maneuver_aoo_damage_penalizes_the_check() -> None:
|
||||||
|
"""A provoked AoO that deals N damage turns a would-be hit into a miss.
|
||||||
|
|
||||||
|
PF1e (CRB Combat Maneuvers): "If you are hit by the target, you take the
|
||||||
|
damage normally and apply that amount as a penalty to the attack roll to
|
||||||
|
perform the maneuver." b's AoO deals 6; the trip roll 15 + CMB 1 = 16
|
||||||
|
would beat CMD 11 by 5 — under the -6 penalty the total is 10 and the
|
||||||
|
trip fails.
|
||||||
|
"""
|
||||||
|
a = _combatant("a", bab=1)
|
||||||
|
b = _combatant("b", bab=1, attacks=(_sword("b", bonus=5),))
|
||||||
|
# b's AoO: d20 15 + 5 = 20 vs AC 10 -> HIT 6 damage (a survives at 94).
|
||||||
|
eng = _engine([a, b], [(1, 1), (1, 2)], ["players", "monsters"], [15, 6, 15])
|
||||||
|
eng._execute_maneuver(eng._states[0], eng._states[1], "trip")
|
||||||
|
assert eng._states[0].hp == 94
|
||||||
|
assert eng._states[0].active is True
|
||||||
|
assert not any(
|
||||||
|
ac.condition.name == "prone" for ac in eng._states[1].active_conditions
|
||||||
|
)
|
||||||
|
assert any("maneuver AoO dealt 6 damage" in line for line in eng._transcript)
|
||||||
|
trip_line = next(line for line in eng._transcript if "trip vs b" in line)
|
||||||
|
assert "d20=15+1=10 CMD 11 -> FAIL" in trip_line
|
||||||
|
|
||||||
|
|
||||||
def test_improved_trip_skips_the_provoking_aoo() -> None:
|
def test_improved_trip_skips_the_provoking_aoo() -> None:
|
||||||
a = _combatant("a", features=("Improved Trip",))
|
a = _combatant("a", features=("Improved Trip",))
|
||||||
b = _combatant("b")
|
b = _combatant("b")
|
||||||
|
|||||||
Reference in New Issue
Block a user