diff --git a/src/pf1e_simulator/combat.py b/src/pf1e_simulator/combat.py index afb0a06..e3892bd 100644 --- a/src/pf1e_simulator/combat.py +++ b/src/pf1e_simulator/combat.py @@ -4159,7 +4159,15 @@ class CombatEngine: def _maneuver_cmd(self, target: CombatantState) -> int: if target.combatant.cmd: return target.combatant.cmd - target_cmb = self._maneuver_cmb(target) + # PF1e (SRD Combat Maneuvers): "CMD = 10 + Base attack bonus + + # Strength modifier + Dexterity modifier + special size modifier" — + # the Tiny-or-smaller Dex-for-Str substitution is CMB-only, so the + # ability component is computed here instead of via _maneuver_cmb. + target_base = target.combatant.cmb or ( + target.combatant.bab + + target.combatant.abilities.str_mod + + self._size_mod(target) + ) # PF1e (SRD Combat Maneuver Defense): "A creature can also add any # circumstance, deflection, dodge, insight, luck, morale, profane, # and sacred bonuses to AC to its CMD. [...] A flat-footed creature @@ -4177,7 +4185,7 @@ class CombatEngine: # bonus and aid_ac_bonus a circumstance bonus to AC — both types the # SRD lets a creature add to its CMD. return ( - 10 + target_cmb + dex_mod + ac_bonuses + 10 + target_base + dex_mod + ac_bonuses + target.defensive_ac_bonus + target.aid_ac_bonus ) diff --git a/tests/test_maneuvers.py b/tests/test_maneuvers.py index 66e8aee..ce1600a 100644 --- a/tests/test_maneuvers.py +++ b/tests/test_maneuvers.py @@ -461,6 +461,18 @@ def test_small_or_larger_cmb_uses_strength() -> None: assert engine._maneuver_cmb(medium) == 2 - 1 + 0 # bab + str + size +def test_tiny_or_smaller_cmd_uses_strength_and_dexterity() -> None: + # SRD Combat Maneuvers: "CMD = 10 + Base attack bonus + Strength modifier + # + Dexterity modifier + special size modifier" — the Tiny-or-smaller + # Dex-for-Str substitution is CMB-only. Tiny: str -1, dex +4, bab 2, + # size -2 -> 10 + 2 - 1 + 4 - 2 = 13 (not 10 + 2 + 4 - 2 + 4 = 18). + engine = make_engine([], []) + for size in ("Tiny", "Diminutive", "Fine"): + tiny = make_state(_resized(make_combatant("t"), size, 8, 18), "p", (0, 0)) + expected = 10 + 2 - 1 + 4 + _SIZE_MODIFIERS[size.lower()] + assert engine._maneuver_cmd(tiny) == expected + + def _buffed(spec: Combatant, mods: tuple[StatModifier, ...]) -> CombatantState: state = make_state(spec, "p", (0, 0)) state.active_buffs.append(