feat(rules): restrict bless to allies in a 50-ft burst

SRD spells/bless.md: 'The caster and all allies within a 50-ft. burst,
centered on the caster'. Add an AreaEffect.targets filter (all/allies/
enemies) applied where area creatures are collected; bless.json now
declares radius_ft 50 and targets allies (the caster included).
This commit is contained in:
2026-09-16 11:14:46 +02:00
parent 312f9fd6b8
commit e43066e0ef
4 changed files with 87 additions and 12 deletions
+44
View File
@@ -635,6 +635,50 @@ class TestBullsStrength:
assert any("buff" in line for line in engine._transcript)
class TestBlessAlliesOnly:
"""Given: a caster casting Bless (SRD: 50-ft burst, "the caster and all
allies")
When: _cast_spell is called
Then: the caster and allies inside the burst are buffed, enemies inside
the burst are not."""
def _bless_engine(
self,
) -> tuple[CombatEngine, CombatantState, CombatantState, CombatantState]:
caster = _make_caster()
ally = _make_target("ally", hp_max=20)
enemy = _make_target("enemy", hp_max=20)
c = CombatantState(combatant=caster, side="players", pos=(0, 0), hp=caster.hp_max)
a = CombatantState(combatant=ally, side="players", pos=(1, 0), hp=ally.hp_max)
e = CombatantState(combatant=enemy, side="monsters", pos=(2, 0), hp=enemy.hp_max)
legend = {".": TerrainType(type="floor", move_cost=1)}
spec = MapSpec(name="test", terrain=tuple(["." * 12] * 12), legend=legend)
grid = Grid.from_spec(spec)
engine = CombatEngine(
ScriptedRng([]), grid, [c, a, e],
spell_registry=load_spell_registry(_SPELLS_DIR),
)
return engine, c, a, e
def test_allies_buffed_enemy_not(self) -> None:
engine, c, a, e = self._bless_engine()
engine._cast_spell(c, c, "Bless")
caster_buffs = [m for b in c.active_buffs for m in b.modifiers]
ally_buffs = [m for b in a.active_buffs for m in b.modifiers]
assert any(m.target == "attack" and m.value == 1 for m in caster_buffs)
assert any(m.target == "attack" and m.value == 1 for m in ally_buffs)
assert not e.active_buffs
def test_enemy_outside_burst_unaffected(self) -> None:
engine, c, a, e = self._bless_engine()
# Move the enemy beyond the 50-ft burst (10 cells) before casting.
e.pos = (11, 0)
engine._cast_spell(c, c, "Bless")
assert not e.active_buffs
assert any(m.target == "attack" and m.value == 1
for b in c.active_buffs for m in b.modifiers)
class TestOutOfRange:
"""Given: a target beyond the spell's maximum range
When: _cast_spell is called