feat(combat): support multi-cell occupancy for large and larger creatures
This commit is contained in:
+49
-13
@@ -67,18 +67,17 @@ def _segment_hits_cell(p1: Pt, p2: Pt, row: int, col: int) -> bool:
|
||||
|
||||
|
||||
class _LineGrid:
|
||||
"""Bound view for corner-line checks between two squares of one grid."""
|
||||
"""Bound view for corner-line checks between two sets of squares of one grid."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
grid: Grid,
|
||||
a: Pos,
|
||||
b: Pos,
|
||||
exclude: frozenset[Pos],
|
||||
blocking: Callable[[TerrainType], bool],
|
||||
occupied: frozenset[Pos] = frozenset(),
|
||||
) -> None:
|
||||
self._grid = grid
|
||||
self._exclude = frozenset({a, b})
|
||||
self._exclude = exclude
|
||||
self._blocking = blocking
|
||||
self._occupied = occupied
|
||||
|
||||
@@ -105,13 +104,23 @@ def _grants_cover(terrain: TerrainType) -> bool:
|
||||
|
||||
def has_line_of_effect(grid: Grid, a: Pos, b: Pos) -> bool:
|
||||
"""LoE iff some corner of a reaches some corner of b unblocked."""
|
||||
if a == b:
|
||||
return has_line_of_effect_cells(grid, frozenset({a}), frozenset({b}))
|
||||
|
||||
|
||||
def has_line_of_effect_cells(
|
||||
grid: Grid, a_cells: frozenset[Pos], b_cells: frozenset[Pos]
|
||||
) -> bool:
|
||||
"""LoE iff some corner of some cell of ``a_cells`` reaches some corner of
|
||||
some cell of ``b_cells`` unblocked."""
|
||||
if a_cells & b_cells:
|
||||
return True
|
||||
lines = _LineGrid(grid, a, b, lambda terrain: terrain.blocks_los)
|
||||
lines = _LineGrid(grid, a_cells | b_cells, lambda terrain: terrain.blocks_los)
|
||||
return any(
|
||||
not lines.blocked(corner_a, corner_b)
|
||||
for corner_a in _corners(a)
|
||||
for corner_b in _corners(b)
|
||||
for cell_a in a_cells
|
||||
for corner_a in _corners(cell_a)
|
||||
for cell_b in b_cells
|
||||
for corner_b in _corners(cell_b)
|
||||
)
|
||||
|
||||
|
||||
@@ -130,16 +139,43 @@ def has_cover(
|
||||
ranged attacks, squares occupied by other creatures also block the
|
||||
lines (soft cover, CRB); melee ignores creatures.
|
||||
"""
|
||||
if attacker == target:
|
||||
return has_cover_cells(
|
||||
grid,
|
||||
frozenset({attacker}),
|
||||
frozenset({target}),
|
||||
ranged=ranged,
|
||||
occupied=occupied,
|
||||
)
|
||||
|
||||
|
||||
def has_cover_cells(
|
||||
grid: Grid,
|
||||
attacker_cells: frozenset[Pos],
|
||||
target_cells: frozenset[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 cell (and best corner within it);
|
||||
the target's corners are the union over all its cells. For 1x1 creatures
|
||||
this is exactly the single-cell corner rule.
|
||||
"""
|
||||
if attacker_cells & target_cells:
|
||||
return False
|
||||
lines = _LineGrid(
|
||||
grid,
|
||||
attacker,
|
||||
target,
|
||||
attacker_cells | target_cells,
|
||||
_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)
|
||||
all(
|
||||
not lines.blocked(corner_a, corner_t)
|
||||
for cell_t in target_cells
|
||||
for corner_t in _corners(cell_t)
|
||||
)
|
||||
for cell_a in attacker_cells
|
||||
for corner_a in _corners(cell_a)
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user