fix(combat): _spell_combat_actions gates on _has_slot - with exhausted slots the cast is rejected (NO SPELL SLOT) and spell_combat resolves as a bare full attack at -2, strictly worse than the plain full attack; the policy now falls through (mirrors _choose_spell_action's slot gate) + 2 regression tests
This commit is contained in:
@@ -8892,12 +8892,20 @@ def _emit_hex(
|
||||
def _spell_combat_actions(
|
||||
engine: CombatEngine, state: CombatantState, target: CombatantState
|
||||
) -> tuple[Action, ...] | None:
|
||||
"""Misty's spell_combat (main) + inspire_courage (swift), or None if no offensive spell."""
|
||||
"""Misty's spell_combat (main) + inspire_courage (swift), None if not castable.
|
||||
|
||||
Slot gate (mirror of ``_choose_spell_action``): with no slot left for the
|
||||
chosen spell the cast is rejected (``NO SPELL SLOT``), so the executor
|
||||
would run the action as a bare full attack at -2 - strictly worse than the
|
||||
plain full attack the policy would otherwise choose. Fall through instead.
|
||||
"""
|
||||
if state.resources_left.get("bardic_rounds", 0) <= 0:
|
||||
return None
|
||||
offensive = _find_offensive_spell(engine, state.combatant.spells, state, target)
|
||||
if offensive is None:
|
||||
return None
|
||||
if not _has_slot(engine, state, offensive):
|
||||
return None
|
||||
return (
|
||||
Action(kind="inspire_courage", target_id=target.combatant.id),
|
||||
Action(kind="spell_combat", target_id=target.combatant.id, spell_name=offensive),
|
||||
|
||||
Reference in New Issue
Block a user