fix(rules): scale reposition and drag distance with the margin
PF1e (APG Reposition / Drag): "For every 5 by which your attack exceeds your opponent's CMD, you can move/drag the target an additional 5 feet." Both handlers now walk 1 + margin//5 cells along the chosen (or auto-picked) direction, stopping at the first obstacle or occupied square, mirroring the bull rush scaling. 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:
@@ -382,6 +382,55 @@ def test_steal_nothing_to_steal() -> None:
|
||||
assert "nothing stealable" in engine._transcript[-1]
|
||||
|
||||
|
||||
def test_reposition_distance_scales_with_margin() -> None:
|
||||
# APG Reposition: "For every 5 by which your attack exceeds your
|
||||
# opponent's CMD, you can move the target an additional 5 feet."
|
||||
# a CMB 2 vs b CMD 12: d20 17 -> total 19, margin 7 -> 2 cells.
|
||||
a = make_state(make_combatant("a", attack_bonus=2), "players", (1, 1))
|
||||
b = make_state(make_combatant("b"), "monsters", (1, 2))
|
||||
engine = make_engine([1, 17], [a, b]) # leading 1: AoO auto-miss
|
||||
engine._execute_maneuver(a, b, "reposition", direction=(0, 1))
|
||||
assert b.pos == (1, 4)
|
||||
|
||||
|
||||
def test_reposition_stops_at_obstacle() -> None:
|
||||
# The extra cell is blocked by c: the walk stops on (1, 3), the last
|
||||
# free square ("You cannot move a creature into a square that is
|
||||
# occupied by a solid object or obstacle").
|
||||
a = make_state(make_combatant("a", attack_bonus=2), "players", (1, 1))
|
||||
b = make_state(make_combatant("b"), "monsters", (1, 2))
|
||||
c = make_state(make_combatant("c"), "monsters", (1, 4))
|
||||
engine = make_engine([1, 17], [a, b, c])
|
||||
engine._execute_maneuver(a, b, "reposition", direction=(0, 1))
|
||||
assert b.pos == (1, 3)
|
||||
assert c.pos == (1, 4)
|
||||
|
||||
|
||||
def test_drag_distance_scales_with_margin() -> None:
|
||||
# APG Drag: "For every 5 by which your attack exceeds your opponent's
|
||||
# CMD, you can drag the target back an additional 5 feet." Margin 7 ->
|
||||
# two 5-ft swaps along the straight line: b (1,2)->(1,4), a (1,1)->(1,3).
|
||||
a = make_state(make_combatant("a", attack_bonus=2), "players", (1, 1))
|
||||
b = make_state(make_combatant("b"), "monsters", (1, 2))
|
||||
engine = make_engine([1, 17], [a, b])
|
||||
engine._execute_maneuver(a, b, "drag")
|
||||
assert b.pos == (1, 4)
|
||||
assert a.pos == (1, 3)
|
||||
|
||||
|
||||
def test_drag_stops_at_obstacle() -> None:
|
||||
# Second drag cell (1, 4) is occupied: the drag ends after the first
|
||||
# 5 ft ("If there is another creature in the way of your movement, the
|
||||
# drag ends adjacent to that creature").
|
||||
a = make_state(make_combatant("a", attack_bonus=2), "players", (1, 1))
|
||||
b = make_state(make_combatant("b"), "monsters", (1, 2))
|
||||
c = make_state(make_combatant("c"), "monsters", (1, 4))
|
||||
engine = make_engine([1, 17], [a, b, c])
|
||||
engine._execute_maneuver(a, b, "drag")
|
||||
assert b.pos == (1, 3)
|
||||
assert a.pos == (1, 2)
|
||||
|
||||
|
||||
def test_reposition_moves_to_first_free_neighbor() -> None:
|
||||
a_spec = make_combatant("a", attack_bonus=2)
|
||||
b_spec = make_combatant("b")
|
||||
|
||||
Reference in New Issue
Block a user