diff --git a/README.md b/README.md index e231366..d1fc5b8 100644 --- a/README.md +++ b/README.md @@ -1023,12 +1023,14 @@ Règles modélisées : 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 Dextérité, qui n'est pas un bonus, reste appliquée). - (SRD « Trip » / « Bull Rush » / « Overrun ») : « You can only [affect] an - opponent who is no more than one size category larger than you » — conformance - L8 : trip, bull_rush et overrun contre une cible de **deux catégories de - taille ou plus au-dessus** de l'attaquant sont illégaux (tentative refusée, - journalisée `rejected (target too large)`, aucun jet, pas d'AoO provoquée) ; - les autres manœuvres (disarm, sunder, grapple…) n'ont pas de limite de taille. + (SRD « Trip » / « Bull Rush » / « Overrun » ; APG « Reposition » / « Drag ») : + « You can only [affect/reposition/drag] an opponent [who is] no more than + one size category larger than you » — conformance + L8 : trip, bull_rush, overrun, reposition et drag contre une cible de + **deux catégories de taille ou plus au-dessus** de l'attaquant sont illégaux + (tentative refusée, + journalisée `rejected (target too large)`, aucun jet, pas d'AoO provoquée) ; + les autres manœuvres (disarm, sunder, grapple…) n'ont pas de limite de taille. trip → `prone` ; disarm → `disarmed` (−4 attaque) ; bull_rush → pousse la cible (direction sélectionnable via `Action.direction`) ; conformance L6 (SRD « Bull Rush ») : la poussée vaut 5 ft plus « For every 5 by which diff --git a/src/pf1e_simulator/combat.py b/src/pf1e_simulator/combat.py index e3892bd..f098fe5 100644 --- a/src/pf1e_simulator/combat.py +++ b/src/pf1e_simulator/combat.py @@ -284,9 +284,12 @@ _SIZE_ORDER: tuple[str, ...] = ( "gargantuan", "colossal", ) -# PF1e (SRD Trip / Bull Rush / Overrun): "You can only [affect] an opponent -# who is no more than one size category larger than you." -_SIZE_RESTRICTED_MANEUVERS: frozenset[str] = frozenset({"trip", "bull_rush", "overrun"}) +# PF1e (SRD Trip / Bull Rush / Overrun; APG Reposition / Drag): "You can only +# [affect/reposition/drag] an opponent [who is] no more than one size category +# larger than you." +_SIZE_RESTRICTED_MANEUVERS: frozenset[str] = frozenset( + {"trip", "bull_rush", "overrun", "reposition", "drag"} +) def _size_rank(size: str) -> int: @@ -4001,9 +4004,10 @@ class CombatEngine: "overrun", "dirty_trick", "steal", "reposition", "drag", ): return - # PF1e (SRD Trip / Bull Rush / Overrun): "You can only [affect] an - # opponent who is no more than one size category larger than you." - # The attempt is illegal outright: no roll, no provoked AoO. + # PF1e (SRD Trip / Bull Rush / Overrun; APG Reposition / Drag): "You + # can only [affect] an opponent who is no more than one size category + # larger than you." The attempt is illegal outright: no roll, no + # provoked AoO. if maneuver in _SIZE_RESTRICTED_MANEUVERS and ( _size_rank(target.combatant.size) - _size_rank(state.combatant.size) >= _MANEUVER_MAX_SIZE_DIFF diff --git a/tests/test_maneuvers.py b/tests/test_maneuvers.py index ce1600a..093c6b4 100644 --- a/tests/test_maneuvers.py +++ b/tests/test_maneuvers.py @@ -249,16 +249,19 @@ def test_overrun_fail_adjacent_attacker_does_not_move() -> None: def test_maneuver_rejects_target_two_sizes_larger() -> None: - # SRD Trip/Bull Rush/Overrun: "You can only [affect] an opponent who is - # no more than one size category larger than you." Medium vs Huge: the - # attempt is illegal — no d20 consumed, no effect, logged rejection. - for maneuver in ("trip", "bull_rush", "overrun"): + # SRD Trip/Bull Rush/Overrun + APG Reposition/Drag: "You can only + # [affect/reposition/drag] an opponent who is no more than one size + # category larger than you." Medium vs Huge: the attempt is illegal — no + # d20 consumed, no effect, logged rejection. + for maneuver in ("trip", "bull_rush", "overrun", "reposition", "drag"): a = make_state(make_combatant("a", attack_bonus=2), "players", (1, 1)) huge = make_state(_resized(make_combatant("h"), "Huge", 10, 10), "monsters", (1, 2)) engine = make_engine([1, 12], [a, huge]) engine._execute_maneuver(a, huge, maneuver) assert len(engine._rng._queue) == 2 assert not any(c.condition.name == "prone" for c in huge.active_conditions) + assert huge.pos == (1, 2) # reposition/drag must not move the target + assert a.pos == (1, 1) # drag must not displace the attacker either assert f"{maneuver} vs h rejected (target too large)" in engine._transcript[-1]