Files
pf1e-simulator/new_functions.py
T
2026-08-30 15:11:08 +02:00

86 lines
4.1 KiB
Python

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"
)