fix(rules): cap reposition and drag by size category
PF1e (APG Reposition / Drag): "You can only reposition/drag an opponent that is no more than one size category larger than you" — the same cap the SRD puts on trip/bull rush/overrun. reposition and drag join _SIZE_RESTRICTED_MANEUVERS, so a two-categories-larger target is rejected outright (no roll, no AoO). README updated. Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
@@ -1023,12 +1023,14 @@ Règles modélisées :
|
|||||||
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).
|
||||||
(SRD « Trip » / « Bull Rush » / « Overrun ») : « You can only [affect] an
|
(SRD « Trip » / « Bull Rush » / « Overrun » ; APG « Reposition » / « Drag ») :
|
||||||
opponent who is no more than one size category larger than you » — conformance
|
« You can only [affect/reposition/drag] an opponent [who is] no more than
|
||||||
L8 : trip, bull_rush et overrun contre une cible de **deux catégories de
|
one size category larger than you » — conformance
|
||||||
taille ou plus au-dessus** de l'attaquant sont illégaux (tentative refusée,
|
L8 : trip, bull_rush, overrun, reposition et drag contre une cible de
|
||||||
journalisée `rejected (target too large)`, aucun jet, pas d'AoO provoquée) ;
|
**deux catégories de taille ou plus au-dessus** de l'attaquant sont illégaux
|
||||||
les autres manœuvres (disarm, sunder, grapple…) n'ont pas de limite de taille.
|
(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
|
trip → `prone` ; disarm → `disarmed` (−4 attaque) ; bull_rush → pousse la
|
||||||
cible (direction sélectionnable via `Action.direction`) ; conformance L6
|
cible (direction sélectionnable via `Action.direction`) ; conformance L6
|
||||||
(SRD « Bull Rush ») : la poussée vaut 5 ft plus « For every 5 by which
|
(SRD « Bull Rush ») : la poussée vaut 5 ft plus « For every 5 by which
|
||||||
|
|||||||
@@ -284,9 +284,12 @@ _SIZE_ORDER: tuple[str, ...] = (
|
|||||||
"gargantuan", "colossal",
|
"gargantuan", "colossal",
|
||||||
)
|
)
|
||||||
|
|
||||||
# PF1e (SRD Trip / Bull Rush / Overrun): "You can only [affect] an opponent
|
# PF1e (SRD Trip / Bull Rush / Overrun; APG Reposition / Drag): "You can only
|
||||||
# who is no more than one size category larger than you."
|
# [affect/reposition/drag] an opponent [who is] no more than one size category
|
||||||
_SIZE_RESTRICTED_MANEUVERS: frozenset[str] = frozenset({"trip", "bull_rush", "overrun"})
|
# larger than you."
|
||||||
|
_SIZE_RESTRICTED_MANEUVERS: frozenset[str] = frozenset(
|
||||||
|
{"trip", "bull_rush", "overrun", "reposition", "drag"}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def _size_rank(size: str) -> int:
|
def _size_rank(size: str) -> int:
|
||||||
@@ -4001,9 +4004,10 @@ class CombatEngine:
|
|||||||
"overrun", "dirty_trick", "steal", "reposition", "drag",
|
"overrun", "dirty_trick", "steal", "reposition", "drag",
|
||||||
):
|
):
|
||||||
return
|
return
|
||||||
# PF1e (SRD Trip / Bull Rush / Overrun): "You can only [affect] an
|
# PF1e (SRD Trip / Bull Rush / Overrun; APG Reposition / Drag): "You
|
||||||
# opponent who is no more than one size category larger than you."
|
# can only [affect] an opponent who is no more than one size category
|
||||||
# The attempt is illegal outright: no roll, no provoked AoO.
|
# larger than you." The attempt is illegal outright: no roll, no
|
||||||
|
# provoked AoO.
|
||||||
if maneuver in _SIZE_RESTRICTED_MANEUVERS and (
|
if maneuver in _SIZE_RESTRICTED_MANEUVERS and (
|
||||||
_size_rank(target.combatant.size) - _size_rank(state.combatant.size)
|
_size_rank(target.combatant.size) - _size_rank(state.combatant.size)
|
||||||
>= _MANEUVER_MAX_SIZE_DIFF
|
>= _MANEUVER_MAX_SIZE_DIFF
|
||||||
|
|||||||
@@ -249,16 +249,19 @@ def test_overrun_fail_adjacent_attacker_does_not_move() -> None:
|
|||||||
|
|
||||||
|
|
||||||
def test_maneuver_rejects_target_two_sizes_larger() -> None:
|
def test_maneuver_rejects_target_two_sizes_larger() -> None:
|
||||||
# SRD Trip/Bull Rush/Overrun: "You can only [affect] an opponent who is
|
# SRD Trip/Bull Rush/Overrun + APG Reposition/Drag: "You can only
|
||||||
# no more than one size category larger than you." Medium vs Huge: the
|
# [affect/reposition/drag] an opponent who is no more than one size
|
||||||
# attempt is illegal — no d20 consumed, no effect, logged rejection.
|
# category larger than you." Medium vs Huge: the attempt is illegal — no
|
||||||
for maneuver in ("trip", "bull_rush", "overrun"):
|
# 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))
|
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))
|
huge = make_state(_resized(make_combatant("h"), "Huge", 10, 10), "monsters", (1, 2))
|
||||||
engine = make_engine([1, 12], [a, huge])
|
engine = make_engine([1, 12], [a, huge])
|
||||||
engine._execute_maneuver(a, huge, maneuver)
|
engine._execute_maneuver(a, huge, maneuver)
|
||||||
assert len(engine._rng._queue) == 2
|
assert len(engine._rng._queue) == 2
|
||||||
assert not any(c.condition.name == "prone" for c in huge.active_conditions)
|
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]
|
assert f"{maneuver} vs h rejected (target too large)" in engine._transcript[-1]
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user