fix(combat): _swift_bonus_actions emits up_close_and_deadly only when the turn will actually fire a firearm attack within 30 ft - the deed burns 1 grit at execution and its +1d6 rides only a firearm attack, so emitting it alongside a melee swing or a spell cast wasted grit for a lost bonus; + 2 policy tests, deed test now uses a firearm-only tammara
This commit is contained in:
@@ -34,14 +34,27 @@ def _combatant(
|
||||
spell_slots: dict[str, int] | None = None,
|
||||
hp_max: int = 20,
|
||||
creature_type: Literal["living", "undead", "construct"] = "living",
|
||||
firearm: bool = False,
|
||||
) -> Combatant:
|
||||
attack = AttackSpec(
|
||||
id=f"{cid}-w",
|
||||
name="sword",
|
||||
kind="melee",
|
||||
attack_bonus=3,
|
||||
damage=[DamageComponent(formula=parse_dice("1d6"), types=["slashing"])],
|
||||
)
|
||||
if firearm:
|
||||
# Tammara-style: a firearm (pistol) as her ONLY weapon.
|
||||
attack = AttackSpec(
|
||||
id=f"{cid}-w",
|
||||
name="pistol",
|
||||
kind="touch",
|
||||
attack_bonus=3,
|
||||
damage=[DamageComponent(formula=parse_dice("1d6"), types=["piercing"])],
|
||||
range_increment_ft=20,
|
||||
firearm=True,
|
||||
)
|
||||
else:
|
||||
attack = AttackSpec(
|
||||
id=f"{cid}-w",
|
||||
name="sword",
|
||||
kind="melee",
|
||||
attack_bonus=3,
|
||||
damage=[DamageComponent(formula=parse_dice("1d6"), types=["slashing"])],
|
||||
)
|
||||
return Combatant(
|
||||
id=cid,
|
||||
name=cid,
|
||||
@@ -373,7 +386,7 @@ def test_slumber_saved_leaves_target_awake() -> None:
|
||||
|
||||
|
||||
def test_tammara_emits_deed_alongside_attack() -> None:
|
||||
tammara = _combatant("tammara", resources={"grit": 3})
|
||||
tammara = _combatant("tammara", resources={"grit": 3}, firearm=True)
|
||||
goblin = _combatant("goblin", hp_max=6)
|
||||
engine, me, _ = _engine(tammara, [goblin])
|
||||
|
||||
@@ -383,6 +396,39 @@ def test_tammara_emits_deed_alongside_attack() -> None:
|
||||
assert any(a.kind in {"full_attack", "attack"} for a in actions)
|
||||
|
||||
|
||||
def test_tammara_no_deed_when_followup_attack_is_melee() -> None:
|
||||
"""Regression: Up Close and Deadly spends 1 grit at execution and its
|
||||
+1d6 rides only a firearm attack within 30 ft; emitted alongside a melee
|
||||
full attack the bonus is lost and the grit burned. The policy must skip
|
||||
the deed when the weapon the turn will fire is not a firearm."""
|
||||
tammara = _combatant("tammara", resources={"grit": 3}) # sword only
|
||||
goblin = _combatant("goblin", hp_max=6)
|
||||
engine, me, _ = _engine(tammara, [goblin])
|
||||
|
||||
actions = default_policy(engine, me)
|
||||
|
||||
assert not any(a.kind == "deed" for a in actions)
|
||||
assert any(a.kind == "full_attack" for a in actions)
|
||||
|
||||
|
||||
def test_tammara_no_deed_when_turn_casts_instead_of_attacking() -> None:
|
||||
"""Even with a firearm selected, a turn whose normal action is a spell
|
||||
cast resolves no weapon attack: the deed flag would burn grit unused."""
|
||||
tammara = _combatant(
|
||||
"tammara", resources={"grit": 3}, firearm=True,
|
||||
spells=["Divine Favor"], spell_slots={1: 1},
|
||||
)
|
||||
goblin = _combatant("goblin", hp_max=6)
|
||||
engine, me, _ = _engine(
|
||||
tammara, [goblin], spell_registry=load_spell_registry(SPELLS_DIR)
|
||||
)
|
||||
|
||||
actions = default_policy(engine, me)
|
||||
|
||||
assert any(a.kind == "cast_spell" for a in actions)
|
||||
assert not any(a.kind == "deed" for a in actions)
|
||||
|
||||
|
||||
def test_tammara_silent_when_grit_exhausted() -> None:
|
||||
tammara = _combatant("tammara", resources={"grit": 0})
|
||||
goblin = _combatant("goblin", hp_max=6)
|
||||
|
||||
Reference in New Issue
Block a user