feat(combat): area-of-effect spells
This commit is contained in:
+21
-8
@@ -19,6 +19,7 @@ from pf1e_simulator.models import (
|
||||
)
|
||||
from pf1e_simulator.rng import ScriptedRng
|
||||
from pf1e_simulator.spells import (
|
||||
AreaEffect,
|
||||
BuffEffect,
|
||||
ConditionEffect,
|
||||
DamageEffect,
|
||||
@@ -132,9 +133,15 @@ class TestSpellLoading:
|
||||
assert spell.range == "long"
|
||||
assert spell.save == "ref"
|
||||
assert spell.base_dc == 10
|
||||
assert len(spell.effects) == 1
|
||||
assert isinstance(spell.effects[0], DamageEffect)
|
||||
assert spell.effects[0].half_on_save is True
|
||||
assert isinstance(spell.effects[0], AreaEffect)
|
||||
area_eff = spell.effects[0]
|
||||
assert isinstance(area_eff, AreaEffect)
|
||||
assert area_eff.radius_ft == 20
|
||||
assert area_eff.save_for_half is True
|
||||
assert len(area_eff.effects) == 1
|
||||
inner = area_eff.effects[0]
|
||||
assert isinstance(inner, DamageEffect)
|
||||
assert inner.half_on_save is True
|
||||
|
||||
def test_hold_person_has_condition_effect(self) -> None:
|
||||
spell = load_spell(_SPELLS_DIR / "hold_person.json")
|
||||
@@ -211,7 +218,8 @@ class TestFireballSave:
|
||||
def test_failed_save_full_damage(self) -> None:
|
||||
caster = _make_caster()
|
||||
target = _make_target(hp_max=200)
|
||||
engine, c, t = _make_engine(caster, target, [2, 3, 3, 3, 3, 3, 3])
|
||||
# 2 save rolls (2 targets in area) + 6 damage dice
|
||||
engine, c, t = _make_engine(caster, target, [2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3])
|
||||
hp_before = t.hp
|
||||
engine._cast_spell(c, t, "Fireball")
|
||||
assert t.hp == hp_before - (3 + 3 + 3 + 3 + 3 + 3)
|
||||
@@ -219,7 +227,8 @@ class TestFireballSave:
|
||||
def test_successful_save_half_damage(self) -> None:
|
||||
caster = _make_caster()
|
||||
target = _make_target(hp_max=200)
|
||||
engine, c, t = _make_engine(caster, target, [20, 3, 3, 3, 3, 3, 3])
|
||||
# 2 save rolls (2 targets in area) + 6 damage dice
|
||||
engine, c, t = _make_engine(caster, target, [20, 20, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3])
|
||||
hp_before = t.hp
|
||||
engine._cast_spell(c, t, "Fireball")
|
||||
full = 3 + 3 + 3 + 3 + 3 + 3
|
||||
@@ -228,11 +237,13 @@ class TestFireballSave:
|
||||
def test_transcript_shows_saved(self) -> None:
|
||||
caster = _make_caster()
|
||||
target = _make_target()
|
||||
engine, c, t = _make_engine(caster, target, [20, 3, 3, 3, 3, 3, 3])
|
||||
# 2 save rolls (2 targets in area) + 6 damage dice
|
||||
engine, c, t = _make_engine(caster, target, [20, 20, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3])
|
||||
engine._cast_spell(c, t, "Fireball")
|
||||
assert any("SAVED" in line for line in engine._transcript)
|
||||
|
||||
|
||||
|
||||
class TestHoldPerson:
|
||||
"""Given: a caster casting Hold Person (will negates, paralyzed)
|
||||
When: target fails save
|
||||
@@ -375,7 +386,8 @@ class TestSpellDcScalesWithLevel:
|
||||
def test_dc_is_ten_plus_spell_level_plus_ability_mod(self) -> None:
|
||||
caster = _make_caster(level=5) # int 18 -> mod +4
|
||||
target = _make_target()
|
||||
engine, c, t = _make_engine(caster, target, [20, 3, 3, 3, 3, 3, 3])
|
||||
# 2 save rolls (2 targets in area) + 6 damage dice
|
||||
engine, c, t = _make_engine(caster, target, [20, 20, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3])
|
||||
engine._cast_spell(c, t, "Fireball") # fireball is level 3
|
||||
# DC = 10 + spell_level(3) + int_mod(4) = 17
|
||||
assert any("DC17" in line for line in engine._transcript)
|
||||
@@ -393,7 +405,8 @@ class TestSpellDamageWithDr:
|
||||
target_with_dr = target.model_copy(update={
|
||||
"dr": DamageReduction(amount=5, bypass=frozenset()),
|
||||
})
|
||||
engine, c, t = _make_engine(caster, target_with_dr, [2, 3, 3, 3, 3, 3, 3])
|
||||
# 2 save rolls (2 targets in area) + 6 damage dice
|
||||
engine, c, t = _make_engine(caster, target_with_dr, [2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3])
|
||||
hp_before = t.hp
|
||||
engine._cast_spell(c, t, "Fireball")
|
||||
# Save fails (2), 6d6 rolls all 3 = 18 damage, DR 5/- -> 13 damage
|
||||
|
||||
Reference in New Issue
Block a user