feat(combat): grant soft cover (+4 AC) to ranged attacks through creatures

This commit is contained in:
2026-08-17 22:49:50 +02:00
parent f67cd017e4
commit bb53d049ee
5 changed files with 129 additions and 19 deletions
+23 -7
View File
@@ -75,10 +75,12 @@ class _LineGrid:
a: Pos,
b: Pos,
blocking: Callable[[TerrainType], bool],
occupied: frozenset[Pos] = frozenset(),
) -> None:
self._grid = grid
self._exclude = frozenset({a, b})
self._blocking = blocking
self._occupied = occupied
def blocked(self, p1: Pt, p2: Pt) -> bool:
min_x = min(p1[0], p2[0])
@@ -90,9 +92,9 @@ class _LineGrid:
pos = (row, col)
if pos in self._exclude or not self._grid.in_bounds(pos):
continue
if self._blocking(self._grid.terrain_at(pos)) and _segment_hits_cell(
p1, p2, row, col
):
if (
pos in self._occupied or self._blocking(self._grid.terrain_at(pos))
) and _segment_hits_cell(p1, p2, row, col):
return True
return False
@@ -113,16 +115,30 @@ def has_line_of_effect(grid: Grid, a: Pos, b: Pos) -> bool:
)
def has_cover(grid: Grid, attacker: Pos, target: Pos, *, ranged: bool) -> bool:
def has_cover(
grid: Grid,
attacker: Pos,
target: Pos,
*,
ranged: bool,
occupied: frozenset[Pos] = frozenset(),
) -> bool:
"""Cover iff every attacker corner has at least one blocked target corner.
The attacker picks their single best corner (PF1e ranged cover rule);
Phase 0 documented deviation: melee reuses the same corner rule.
Phase 0 documented deviation: melee reuses the same corner rule. For
ranged attacks, squares occupied by other creatures also block the
lines (soft cover, CRB); melee ignores creatures.
"""
if attacker == target:
return False
_ = ranged
lines = _LineGrid(grid, attacker, target, _grants_cover)
lines = _LineGrid(
grid,
attacker,
target,
_grants_cover,
occupied if ranged else frozenset(),
)
return not any(
all(not lines.blocked(corner_a, corner_t) for corner_t in _corners(target))
for corner_a in _corners(attacker)