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).
|
||||
|
||||
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
|
||||
|
||||
@@ -7116,8 +7116,13 @@ class CombatEngine:
|
||||
) -> list[CombatantState]:
|
||||
"""Creatures intersecting the template of an area effect.
|
||||
|
||||
Burst: circle of ``radius_ft`` around the resolved center ``aim``
|
||||
(unchanged behaviour). Cone/line: the template originates at the
|
||||
Burst: circle of ``radius_ft`` around the resolved center ``aim``,
|
||||
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
|
||||
Spell" — see ``areas.py``); a creature is affected when ANY of its
|
||||
occupied cells lies inside the template.
|
||||
@@ -7129,7 +7134,13 @@ class CombatEngine:
|
||||
"""
|
||||
span_cells = effect.radius_ft // _SQUARE_FT
|
||||
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:
|
||||
origin = caster.pos
|
||||
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:
|
||||
"""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)
|
||||
|
||||
Reference in New Issue
Block a user