def _execute_deed(self, state: CombatantState, target: CombatantState | None, deed_name: str | None) -> None: """Execute a gunslinger deed (up_close_and_deadly, quick_clear).""" if deed_name not in ("up_close_and_deadly", "quick_clear"): return if not state.spend_resource("grit", 1): self._log(f"round {self._current_round} {state.combatant.id}: deed failed (no grit)") return if deed_name == "up_close_and_deadly": # Up Close and Deadly: +1d6 damage within 30ft if target is None or not target.active: return dist_ft = self._grid.distance(state.pos, target.pos) * _SQUARE_FT if dist_ft <= 30: # Apply +1d6 damage to next attack this round state.deed_up_close_and_deadly = True self._log( f"round {self._current_round} {state.combatant.id}: " f"deed Up Close and Deadly activated (+1d6 dmg within 30ft)" ) else: self._log(f"round {self._current_round} {state.combatant.id}: deed Up Close and Deadly - target out of range") elif deed_name == "quick_clear": # Quick Clear: swift action, clears misfire self._log( f"round {self._current_round} {state.combatant.id}: " f"deed Quick Clear - firearm cleared" ) # In a full implementation, this would clear the misfire condition def _execute_spell_combat(self, state: CombatantState, target: CombatantState | None, spell_name: str | None) -> None: """Execute Spell Combat (cast spell + full attack in same round).""" if target is None or not target.active: return if not state.spend_resource("arcane_pool", 1): # Arcane pool not tracked yet, allow for now pass # Cast the spell self._cast_spell(state, target, spell_name) # Then make a full attack self._execute_attack(state, target, "full_attack") def _execute_inspire_courage(self, state: CombatantState, target: CombatantState | None) -> None: """Execute Inspire Courage (morale +1 attack/damage, 30ft radius, costs bardic_rounds).""" if not state.spend_resource("bardic_rounds", 1): self._log(f"round {self._current_round} {state.combatant.id}: inspire courage failed (no bardic rounds)") return if target is None: target = state # Apply to all allies within 30ft (6 cells) radius_cells = 30 // _SQUARE_FT center = state.pos affected: list[CombatantState] = [] for state_other in self._states: if not state_other.active: continue if state_other.side == state.side: dist = self._grid.distance(center, state_other.pos) if dist <= radius_cells: affected.append(state_other) if not affected: self._log(f"round {self._current_round} {state.combatant.id}: inspire courage - no allies in range") return # Apply +1 morale bonus to attack and damage for ally in affected: ally.active_buffs.append( ActiveBuff( modifiers=( StatModifier(target="attack", value=1, bonus_type="morale"), StatModifier(target="damage", value=1, bonus_type="morale"), ), rounds_left=1, source=f"inspire courage by {state.combatant.id}", ) ) # Also update old effects field for backwards compatibility ally.effects.extend(( StatModifier(target="attack", value=1, bonus_type="morale"), StatModifier(target="damage", value=1, bonus_type="morale"), )) self._log( f"round {self._current_round} {state.combatant.id}: " f"inspire courage +1 morale attack/damage to {len(affected)} allies" )