feat(combat): AoO and concentration on casting

This commit is contained in:
ctan
2026-08-29 22:47:22 +02:00
parent aaa1ef5661
commit de399b74b8
2 changed files with 59 additions and 11 deletions
+12 -11
View File
@@ -93,7 +93,7 @@ def _make_engine(
queue: list[int],
*,
caster_pos: tuple[int, int] = (0, 0),
target_pos: tuple[int, int] = (1, 1),
target_pos: tuple[int, int] = (3, 0),
spell_registry: dict[str, SpellSpec] | None = None,
) -> tuple[CombatEngine, CombatantState, CombatantState]:
c = CombatantState(combatant=caster, side="players", pos=caster_pos, hp=caster.hp_max)
@@ -228,7 +228,7 @@ class TestFireballSave:
def test_transcript_shows_saved(self) -> None:
caster = _make_caster()
target = _make_target()
engine, c, t = _make_engine(caster, target, [20, 1, 1, 1, 1, 1, 1])
engine, c, t = _make_engine(caster, target, [20, 3, 3, 3, 3, 3, 3])
engine._cast_spell(c, t, "Fireball")
assert any("SAVED" in line for line in engine._transcript)
@@ -281,7 +281,7 @@ class TestCureLightWounds:
def test_heals_damage(self) -> None:
caster = _make_caster()
target = _make_target(hp_max=30)
engine, c, t = _make_engine(caster, target, [20, 5])
engine, c, t = _make_engine(caster, target, [3, 3, 5], target_pos=(1, 0))
t.hp = 10
engine._cast_spell(c, t, "Cure Light Wounds")
assert t.hp == 10 + (5 + 1)
@@ -289,7 +289,7 @@ class TestCureLightWounds:
def test_heal_capped_at_max(self) -> None:
caster = _make_caster()
target = _make_target(hp_max=30)
engine, c, t = _make_engine(caster, target, [20, 6])
engine, c, t = _make_engine(caster, target, [3, 3, 6], target_pos=(1, 0))
t.hp = 28
engine._cast_spell(c, t, "Cure Light Wounds")
assert t.hp == 30
@@ -303,7 +303,7 @@ class TestBullsStrength:
def test_applies_buff_effects(self) -> None:
caster = _make_caster()
target = _make_target()
engine, c, t = _make_engine(caster, target, [20])
engine, c, t = _make_engine(caster, target, [3, 3], target_pos=(1, 0))
engine._cast_spell(c, t, "Bull's Strength")
atk_mods = [e for e in t.effects if e.target == "attack"]
dmg_mods = [e for e in t.effects if e.target == "damage"]
@@ -315,7 +315,7 @@ class TestBullsStrength:
def test_transcript_shows_buff(self) -> None:
caster = _make_caster()
target = _make_target()
engine, c, t = _make_engine(caster, target, [20])
engine, c, t = _make_engine(caster, target, [3, 3], target_pos=(1, 0))
engine._cast_spell(c, t, "Bull's Strength")
assert any("buff" in line for line in engine._transcript)
@@ -375,7 +375,7 @@ 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, 1, 1, 1, 1, 1, 1])
engine, c, t = _make_engine(caster, target, [20, 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)
@@ -389,11 +389,12 @@ class TestSpellDamageWithDr:
def test_dr_reduces_spell_damage(self) -> None:
caster = _make_caster()
target = _make_target(hp_max=200)
# DR 5/- (bypassed by nothing) applies to all damage including fire
target_with_dr = target.model_copy(update={
"dr": DamageReduction(amount=5, bypass=frozenset({"bludgeoning"})),
"dr": DamageReduction(amount=5, bypass=frozenset()),
})
engine, c, t = _make_engine(caster, target_with_dr, [2, 6, 6, 6, 6, 6, 6])
engine, c, t = _make_engine(caster, target_with_dr, [2, 3, 3, 3, 3, 3, 3])
hp_before = t.hp
engine._cast_spell(c, t, "Fireball")
full = 6 * 6
assert t.hp == hp_before - max(0, full - 5)
# Save fails (2), 6d6 rolls all 3 = 18 damage, DR 5/- -> 13 damage
assert t.hp == hp_before - 13