fix(rules): apply AC penalties to CMD
PF1e (SRD Combat Maneuvers): "Any penalties to a creature's AC also apply to its CMD." _maneuver_cmd now folds negative AC modifiers into the sum regardless of bonus type (grappled -2, other untyped penalties), and — a maneuver being a melee attack roll — the prone -4 melee AC penalty. Positive modifiers keep the eight SRD-listed types. The test that pinned the old ignore-penalties convention is updated; README documents the rule. Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
@@ -1022,7 +1022,10 @@ Règles modélisées :
|
|||||||
les bonus de CA des huit types autorisés (« A creature can also add any
|
les bonus de CA des huit types autorisés (« A creature can also add any
|
||||||
circumstance, deflection, dodge, insight, luck, morale, profane, and
|
circumstance, deflection, dodge, insight, luck, morale, profane, and
|
||||||
sacred bonuses to AC to its CMD » — bonus issus des buffs/conditions/
|
sacred bonuses to AC to its CMD » — bonus issus des buffs/conditions/
|
||||||
dons, défense totale et combat défensif inclus) et une créature au
|
dons, défense totale et combat défensif inclus), **toutes les pénalités de CA
|
||||||
|
(« Any penalties to a creature's AC also apply to its CMD » — pénalités sans
|
||||||
|
type comme grapplé −2 ou à plat ventre −4 contre la mêlée, la manœuvre
|
||||||
|
étant un jet d'attaque de mêlée)** et une créature au
|
||||||
contact pris de dos ne ajoute pas son bonus de Dextérité (« A flat-footed
|
contact pris de dos ne ajoute pas son bonus de Dextérité (« A flat-footed
|
||||||
creature does not add its Dexterity bonus to its CMD » — la pénalité de
|
creature does not add its Dexterity bonus to its CMD » — la pénalité de
|
||||||
Dextérité, qui n'est pas un bonus, reste appliquée).
|
Dextérité, qui n'est pas un bonus, reste appliquée).
|
||||||
|
|||||||
@@ -4192,11 +4192,20 @@ class CombatEngine:
|
|||||||
dex_mod = target.combatant.abilities.dex_mod
|
dex_mod = target.combatant.abilities.dex_mod
|
||||||
if self.has_condition_flag(target, "denies_dex_to_ac"):
|
if self.has_condition_flag(target, "denies_dex_to_ac"):
|
||||||
dex_mod = min(0, dex_mod)
|
dex_mod = min(0, dex_mod)
|
||||||
ac_bonuses = resolve_modifiers(
|
# PF1e (SRD Combat Maneuvers): "Any penalties to a creature's AC also
|
||||||
|
# apply to its CMD." — negative AC modifiers ride the CMD whatever
|
||||||
|
# their bonus type; only positive ones are restricted to the eight
|
||||||
|
# SRD-listed types.
|
||||||
|
ac_mods = [
|
||||||
m
|
m
|
||||||
for m in self._stat_modifiers(target, "ac")
|
for m in self._stat_modifiers(target, "ac")
|
||||||
if m.bonus_type in _CMD_AC_BONUS_TYPES
|
if m.bonus_type in _CMD_AC_BONUS_TYPES or m.value < 0
|
||||||
)
|
]
|
||||||
|
# A combat maneuver is a melee attack roll (SRD: "Combat maneuvers are
|
||||||
|
# attack rolls"), so the prone defender's -4 melee AC penalty applies.
|
||||||
|
if self._has_condition_named(target, "prone"):
|
||||||
|
ac_mods.append(StatModifier(target="ac", value=-4))
|
||||||
|
ac_bonuses = resolve_modifiers(ac_mods)
|
||||||
# defensive_ac_bonus (total defense / fight defensively) is a dodge
|
# defensive_ac_bonus (total defense / fight defensively) is a dodge
|
||||||
# bonus and aid_ac_bonus a circumstance bonus to AC — both types the
|
# bonus and aid_ac_bonus a circumstance bonus to AC — both types the
|
||||||
# SRD lets a creature add to its CMD.
|
# SRD lets a creature add to its CMD.
|
||||||
|
|||||||
+26
-4
@@ -504,9 +504,10 @@ def test_cmd_adds_eligible_ac_bonus_types() -> None:
|
|||||||
assert engine._maneuver_cmd(buffed) == 12 + 4 + 2
|
assert engine._maneuver_cmd(buffed) == 12 + 4 + 2
|
||||||
|
|
||||||
|
|
||||||
def test_cmd_ignores_non_ac_bonus_types() -> None:
|
def test_cmd_ignores_non_ac_bonus_types_keeps_penalties() -> None:
|
||||||
# Enhancement/natural armor bonuses and untyped penalties are NOT added
|
# Enhancement bonuses are NOT among the eight SRD-listed types — ignored.
|
||||||
# to CMD (only the eight SRD-listed AC bonus types are).
|
# But "Any penalties to a creature's AC also apply to its CMD" (SRD
|
||||||
|
# Combat Maneuvers): the untyped -4 penalty rides the CMD.
|
||||||
engine = make_engine([], [])
|
engine = make_engine([], [])
|
||||||
state = _buffed(
|
state = _buffed(
|
||||||
make_combatant("b"),
|
make_combatant("b"),
|
||||||
@@ -515,7 +516,28 @@ def test_cmd_ignores_non_ac_bonus_types() -> None:
|
|||||||
StatModifier(target="ac", value=-4),
|
StatModifier(target="ac", value=-4),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
assert engine._maneuver_cmd(state) == 12
|
assert engine._maneuver_cmd(state) == 12 - 4
|
||||||
|
|
||||||
|
|
||||||
|
def test_cmd_includes_prone_and_grappled_penalties() -> None:
|
||||||
|
# A maneuver is a melee attack roll, so the prone -4 melee AC penalty and
|
||||||
|
# the grappled -2 AC penalty both apply to CMD (SRD: "Any penalties to a
|
||||||
|
# creature's AC also apply to its CMD").
|
||||||
|
engine = make_engine([], [])
|
||||||
|
prone = make_state(make_combatant("p"), "p", (0, 0))
|
||||||
|
prone.active_conditions.append(
|
||||||
|
ActiveCondition(
|
||||||
|
condition=CONDITIONS_BY_NAME["prone"], rounds_left=1, source="test"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
assert engine._maneuver_cmd(prone) == 12 - 4
|
||||||
|
grappled = make_state(make_combatant("g"), "p", (0, 1))
|
||||||
|
grappled.active_conditions.append(
|
||||||
|
ActiveCondition(
|
||||||
|
condition=CONDITIONS_BY_NAME["grappled"], rounds_left=1, source="test"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
assert engine._maneuver_cmd(grappled) == 12 - 2
|
||||||
|
|
||||||
|
|
||||||
def test_cmd_flat_footed_drops_dex_bonus_keeps_penalty() -> None:
|
def test_cmd_flat_footed_drops_dex_bonus_keeps_penalty() -> None:
|
||||||
|
|||||||
Reference in New Issue
Block a user