Files
pf1e-simulator/tests/test_conformance_caster_footprint_origin.py
T

75 lines
2.8 KiB
Python

"""A multi-cell caster's WHOLE footprint is outside its own cone/line.
PF1e SRD "Aiming a Spell" (aiming-a-spell.md): a cone and a line "start from
any corner of your square" -- for a creature with a space larger than one
square, every occupied square is part of the origin space, so none of them is
ever inside the creature's own area (the template "shoots away from you").
The engine used to exclude only the anchor cell, so a Large caster's other
cells fell inside its own cone/line template.
"""
from __future__ import annotations
from pf1e_simulator.areas import cone_cells, line_cells
from pf1e_simulator.combat import CombatEngine
from pf1e_simulator.grid import Grid
from pf1e_simulator.map import MapSpec, TerrainType
from pf1e_simulator.rng import ScriptedRng
from pf1e_simulator.spells import AreaEffect
from tests.test_observation_enriched import make_combatant, make_state
_FLOOR = TerrainType(type="floor", move_cost=1)
def _open_grid() -> Grid:
return Grid.from_spec(
MapSpec(
name="footprint-origin",
terrain=tuple(["." * 8] * 8),
legend={".": _FLOOR},
)
)
# A Large caster anchored at (1,1) occupies the 2x2 block below. Aiming east
# from the anchor, the ray/cone grazes the caster's own cells (1,2) and (2,2).
_FOOTPRINT = frozenset({(1, 1), (1, 2), (2, 1), (2, 2)})
def test_cone_excludes_the_whole_caster_footprint() -> None:
grid = _open_grid()
cells = cone_cells(grid, (1, 1), (1, 6), 4, caster_cells=_FOOTPRINT)
assert not cells & _FOOTPRINT
# The exclusion is the footprint only: the cone keeps everything past it.
assert (1, 3) in cells
assert (2, 3) in cells
def test_line_excludes_the_whole_caster_footprint() -> None:
grid = _open_grid()
cells = line_cells(grid, (1, 1), (1, 6), 6, caster_cells=_FOOTPRINT)
assert (1, 2) not in cells
assert (1, 3) in cells
assert (1, 7) in cells
def test_single_cell_caster_templates_are_unchanged() -> None:
"""1x1 regression: the footprint is just the anchor, which the old code
already dropped -- both templates must be identical to the pre-fix set."""
grid = _open_grid()
old_cone = cone_cells(grid, (1, 1), (1, 6), 4)
new_cone = cone_cells(grid, (1, 1), (1, 6), 4, caster_cells=frozenset({(1, 1)}))
assert old_cone == new_cone
old_line = line_cells(grid, (1, 1), (1, 6), 6)
new_line = line_cells(grid, (1, 1), (1, 6), 6, caster_cells=frozenset({(1, 1)}))
assert old_line == new_line
def test_large_caster_is_not_caught_by_its_own_cone() -> None:
caster_spec = make_combatant("ogre", size="Large", hp=20)
caster = make_state(caster_spec, "players", (1, 1))
engine = CombatEngine(ScriptedRng([]), _open_grid(), [caster])
effect = AreaEffect(shape="cone", radius_ft=30)
affected = engine._area_creatures(effect, caster, (1, 6))
assert affected == []