fix(rules): bursts cannot affect creatures in total cover from the point of origin (SRD aiming-a-spell.md: a burst "can't affect creatures with total cover from its point of origin (in other words, its effects don't extend around corners)"; Line of Effect: a burst affects only what it has LoE to from its origin) - _area_creatures skips creatures whose occupied cells have no line of effect from the burst center
This commit is contained in:
@@ -29,7 +29,8 @@ PF1e (CRB, Magic — Aiming a Spell):
|
|||||||
wall between them shields everything behind it).
|
wall between them shields everything behind it).
|
||||||
|
|
||||||
Bursts need no template here: they are circles around a center cell and are
|
Bursts need no template here: they are circles around a center cell and are
|
||||||
resolved by ``CombatEngine._creatures_in_range`` (unchanged).
|
resolved by ``CombatEngine._area_creatures`` (which additionally drops
|
||||||
|
creatures without line of effect from the burst's center).
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|||||||
@@ -7116,8 +7116,13 @@ class CombatEngine:
|
|||||||
) -> list[CombatantState]:
|
) -> list[CombatantState]:
|
||||||
"""Creatures intersecting the template of an area effect.
|
"""Creatures intersecting the template of an area effect.
|
||||||
|
|
||||||
Burst: circle of ``radius_ft`` around the resolved center ``aim``
|
Burst: circle of ``radius_ft`` around the resolved center ``aim``,
|
||||||
(unchanged behaviour). Cone/line: the template originates at the
|
minus every creature that has no line of effect from that center
|
||||||
|
(PF1e CRB "Aiming a Spell" — Burst: a burst "can't affect creatures
|
||||||
|
with total cover from its point of origin (in other words, its
|
||||||
|
effects don't extend around corners)"; CRB "Line of Effect": a burst
|
||||||
|
"affects only an area, creature, or object to which it has line of
|
||||||
|
effect from its origin"). Cone/line: the template originates at the
|
||||||
caster's anchor cell and opens toward ``aim`` (PF1e CRB "Aiming a
|
caster's anchor cell and opens toward ``aim`` (PF1e CRB "Aiming a
|
||||||
Spell" — see ``areas.py``); a creature is affected when ANY of its
|
Spell" — see ``areas.py``); a creature is affected when ANY of its
|
||||||
occupied cells lies inside the template.
|
occupied cells lies inside the template.
|
||||||
@@ -7129,7 +7134,13 @@ class CombatEngine:
|
|||||||
"""
|
"""
|
||||||
span_cells = effect.radius_ft // _SQUARE_FT
|
span_cells = effect.radius_ft // _SQUARE_FT
|
||||||
if effect.shape == "burst":
|
if effect.shape == "burst":
|
||||||
affected = self._creatures_in_range(aim, span_cells)
|
affected = [
|
||||||
|
state
|
||||||
|
for state in self._creatures_in_range(aim, span_cells)
|
||||||
|
if has_line_of_effect_cells(
|
||||||
|
self._grid, frozenset({aim}), state.occupied_cells()
|
||||||
|
)
|
||||||
|
]
|
||||||
else:
|
else:
|
||||||
origin = caster.pos
|
origin = caster.pos
|
||||||
if effect.shape == "cone":
|
if effect.shape == "cone":
|
||||||
|
|||||||
@@ -327,10 +327,54 @@ def test_line_behind_a_wall_does_not_hit_sheltered_creatures() -> None:
|
|||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
# burst: unchanged circle around the resolved center
|
# burst: circle around the center, minus creatures in total cover of it
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
|
def test_burst_does_not_extend_around_a_solid_barrier() -> None:
|
||||||
|
"""Given: a 20-ft burst point-cast at center (6,3); wall cells at col 5
|
||||||
|
(rows 5-7); foe_open (4,3) inside the radius with clear LoE, foe_behind
|
||||||
|
(6,7) inside the radius but behind the wall (total cover from the
|
||||||
|
center)
|
||||||
|
When: the burst resolves (caster at (6,2) is in the radius too)
|
||||||
|
Then: the caster and foe_open take damage (two 1d6 consumed); foe_behind
|
||||||
|
is untouched - a burst "can't affect creatures with total cover from its
|
||||||
|
point of origin" (its effects don't extend around corners)."""
|
||||||
|
caster = _caster()
|
||||||
|
walls = frozenset({(5, 5), (6, 5), (7, 5)})
|
||||||
|
foe_open = make_state(make_combatant("foe_open", hp=100), "monsters", (4, 3))
|
||||||
|
foe_behind = make_state(make_combatant("foe_behind", hp=100), "monsters", (6, 7))
|
||||||
|
eng = CombatEngine(
|
||||||
|
ScriptedRng([4, 4]), _walled_grid(walls), [caster, foe_open, foe_behind],
|
||||||
|
spell_registry=_REGISTRY,
|
||||||
|
)
|
||||||
|
result = eng._execute(
|
||||||
|
caster, Action(kind="cast_spell", spell_name="Burst Sparks", target_pos=(6, 3))
|
||||||
|
)
|
||||||
|
assert result.ok
|
||||||
|
assert caster.hp == 100 - 4
|
||||||
|
assert foe_open.hp == 100 - 4
|
||||||
|
assert foe_behind.hp == 100
|
||||||
|
|
||||||
|
|
||||||
|
def test_burst_open_field_control_hits_creatures_behind_the_center() -> None:
|
||||||
|
"""Given: the same layout on an OPEN grid (no walls)
|
||||||
|
When: the 20-ft burst point-cast at (6,3) resolves
|
||||||
|
Then: all three creatures (caster, foe_open, foe_behind at distance 4)
|
||||||
|
are inside the radius and affected - the LoE filter drops nothing when
|
||||||
|
there is no barrier."""
|
||||||
|
caster = _caster()
|
||||||
|
foe_open = make_state(make_combatant("foe_open", hp=100), "monsters", (4, 3))
|
||||||
|
foe_behind = make_state(make_combatant("foe_behind", hp=100), "monsters", (6, 7))
|
||||||
|
eng = _engine([caster, foe_open, foe_behind], [4, 4, 4])
|
||||||
|
eng._execute(
|
||||||
|
caster, Action(kind="cast_spell", spell_name="Burst Sparks", target_pos=(6, 3))
|
||||||
|
)
|
||||||
|
assert caster.hp == 100 - 4
|
||||||
|
assert foe_open.hp == 100 - 4
|
||||||
|
assert foe_behind.hp == 100 - 4
|
||||||
|
|
||||||
|
|
||||||
def test_burst_still_centers_on_the_target_and_ignores_the_caster_side() -> None:
|
def test_burst_still_centers_on_the_target_and_ignores_the_caster_side() -> None:
|
||||||
"""Given: caster (6,2); foe_in (4,4) and foe_out (2,0) around a burst
|
"""Given: caster (6,2); foe_in (4,4) and foe_out (2,0) around a burst
|
||||||
centered on target_id=foe_in (radius 20 ft = 4 cells)
|
centered on target_id=foe_in (radius 20 ft = 4 cells)
|
||||||
|
|||||||
Reference in New Issue
Block a user