fix(combat): _choose_spell_action heal branch gates on spell range + line of effect - a touch-range Cure on a distant ally was rejected by _cast_spell (OUT OF RANGE / NO LINE OF EFFECT) and wasted the caster's whole turn; extracted the offensive branch's reach check as _spell_reaches and reused it (the duplicated below-half check in the branch was a no-op copy of _find_ally_needing_heal, dropped) + 3 policy tests

This commit is contained in:
2026-09-19 18:48:58 +02:00
parent 31d47aee79
commit bdf15aa8fe
2 changed files with 115 additions and 8 deletions
+25 -7
View File
@@ -8319,11 +8319,27 @@ def _find_ally_needing_heal(engine: CombatEngine, caster: CombatantState) -> Com
return None
def _spell_reaches(
engine: CombatEngine,
caster: CombatantState,
target: CombatantState,
spell: SpellSpec,
) -> bool:
"""True if the spell's range covers caster -> target with line of effect
(the same gate `_cast_spell` applies; a proposed cast failing it wastes
the whole turn on an OUT OF RANGE / NO LINE OF EFFECT rejection)."""
dist_ft = engine.creature_distance(caster, target) * _SQUARE_FT
return dist_ft <= spell_range_ft(spell, caster.combatant.level) and (
has_line_of_effect_cells(
engine.grid, caster.occupied_cells(), target.occupied_cells()
)
)
def _find_offensive_spell(
engine: CombatEngine, spells_known: list[str], caster: CombatantState, target: CombatantState
) -> str | None:
"""Find an offensive spell that can reach the target."""
dist_ft = engine.creature_distance(caster, target) * _SQUARE_FT
for name in spells_known:
spell = engine.spell_registry.get(name)
if not spell:
@@ -8332,10 +8348,7 @@ def _find_offensive_spell(
has_damage = any(isinstance(e, DamageEffect) for e in spell.effects)
if not has_damage:
continue
max_range = spell_range_ft(spell, caster.combatant.level)
if dist_ft <= max_range and has_line_of_effect_cells(
engine.grid, caster.occupied_cells(), target.occupied_cells()
):
if _spell_reaches(engine, caster, target, spell):
return name
return None
@@ -8375,7 +8388,9 @@ def _choose_spell_action(
if not spells_known:
return None
# 1. Heal spell if ally below 50% HP
# 1. Heal spell if ally below 50% HP (within range and line of effect:
# a touch-range Cure on a distant ally is rejected by _cast_spell and
# would waste the whole turn).
heal_spell = _find_heal_spell(engine, spells_known)
if (
heal_spell
@@ -8383,7 +8398,10 @@ def _choose_spell_action(
and _has_slot(engine, caster, heal_spell)
):
ally = _find_ally_needing_heal(engine, caster)
if ally and ally.hp < ally.combatant.hp_max // 2:
spell = engine.spell_registry.get(heal_spell)
if ally is not None and spell is not None and _spell_reaches(
engine, caster, ally, spell
):
return (Action(kind="cast_spell", target_id=ally.combatant.id, spell_name=heal_spell),)
# 2. Offensive spell if enemy in range