fix(rules): charge and run walks enforce the diagonal corner rule (SRD measuring-distance.md: 'You can't move diagonally past a corner') - _charge_path_clear and _run_line_path never checked diagonal_allowed, so a charge/run could cut diagonally through a wall corner; both walks now stop/reject a diagonal step whose flanks are not both passable, same predicate the Dijkstra descent uses
This commit is contained in:
@@ -3988,13 +3988,33 @@ class CombatEngine:
|
||||
def _charge_path_clear(
|
||||
self, path: list[Pos], state: CombatantState
|
||||
) -> bool:
|
||||
"""True if the footprint fits at every anchor on the charge path."""
|
||||
"""True if the footprint fits at every anchor on the charge path.
|
||||
|
||||
PF1e (SRD "Measuring Distance"): "You can't move diagonally past a
|
||||
corner" - a diagonal step whose two flanking squares are not both
|
||||
passable is illegal, so a charge may never cut a wall corner (the
|
||||
same corner rule the Dijkstra descent enforces in
|
||||
``_best_move_step``). PF1e (SRD "Charge"): "If any line from your
|
||||
starting space to the ending space passes through a square that
|
||||
blocks movement, slows movement, or contains a creature (even an
|
||||
ally), you can't charge" - the straight Bresenham anchor line is the
|
||||
engine's model of that path, walked under the full-footprint gates
|
||||
below.
|
||||
"""
|
||||
prev = state.pos
|
||||
for pos in path:
|
||||
if not self._cells_free(self._footprint_at(pos, state), state):
|
||||
return False
|
||||
terrain = self._grid.terrain_at(pos)
|
||||
if terrain.move_cost is not None and terrain.move_cost > 1:
|
||||
return False
|
||||
if (
|
||||
prev[0] != pos[0]
|
||||
and prev[1] != pos[1]
|
||||
and not self._grid.diagonal_allowed(prev, pos)
|
||||
):
|
||||
return False
|
||||
prev = pos
|
||||
return True
|
||||
|
||||
def _charge(
|
||||
@@ -7860,7 +7880,9 @@ class CombatEngine:
|
||||
speed budget and stopped at the first obstacle, mirroring the charge's
|
||||
straight-line clear-path logic (CRB "Charge"): impassable terrain, a
|
||||
difficult-terrain cell (CRB "Difficult Terrain": "You can't run or
|
||||
charge across difficult terrain"), or a blocking creature. A hostile
|
||||
charge across difficult terrain"), a diagonal step past a blocked
|
||||
corner (SRD "Measuring Distance": "You can't move diagonally past a
|
||||
corner"), or a blocking creature. A hostile
|
||||
creature (including the target itself) halts the walk one cell short,
|
||||
so an auto-directed run ends adjacent to the target — except one the
|
||||
mover may pass through (three size categories apart, SRD "Moving
|
||||
@@ -7879,6 +7901,16 @@ class CombatEngine:
|
||||
current = state.pos
|
||||
diagonals_used = 0
|
||||
for nxt in _bresenham_line(state.pos, goal):
|
||||
# PF1e (SRD "Measuring Distance"): "You can't move diagonally
|
||||
# past a corner" - a diagonal step with a blocked flank is
|
||||
# illegal, so a run stops at the corner just like an obstacle
|
||||
# (same rule _best_move_step enforces for the Dijkstra descent).
|
||||
if (
|
||||
current[0] != nxt[0]
|
||||
and current[1] != nxt[1]
|
||||
and not self._grid.diagonal_allowed(current, nxt)
|
||||
):
|
||||
break
|
||||
footprint = self._footprint_at(nxt, state)
|
||||
if self._footprint_touches_difficult(footprint):
|
||||
break
|
||||
|
||||
Reference in New Issue
Block a user