feat(combat): add direction field to Action for directional maneuvers
This commit is contained in:
@@ -366,6 +366,7 @@ class Action:
|
||||
deed_name: str | None = None # "up_close_and_deadly", "quick_clear"
|
||||
class_ability: str | None = None # "deed"|"spell_combat"|"inspire_courage"|"hex"|"channel"
|
||||
channel_mode: Literal["heal", "harm"] | None = None # channel energy polarity for this use
|
||||
direction: tuple[int, int] | None = None # steering for directional maneuvers
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
@@ -1467,7 +1468,7 @@ class CombatEngine:
|
||||
if kind == "cast_spell":
|
||||
self._execute_cast_spell(state, target, action.spell_name)
|
||||
elif kind == "maneuver":
|
||||
self._execute_maneuver(state, target, action.maneuver)
|
||||
self._execute_maneuver(state, target, action.maneuver, direction=action.direction)
|
||||
elif kind in ("channel_energy", "hex", "deed", "spell_combat", "inspire_courage"):
|
||||
self._execute_class_ability(state, target, action)
|
||||
elif kind == "charge":
|
||||
@@ -1565,7 +1566,12 @@ class CombatEngine:
|
||||
self._cast_spell(state, target, spell_name)
|
||||
|
||||
def _execute_maneuver(
|
||||
self, state: CombatantState, target: CombatantState | None, maneuver: str | None
|
||||
self,
|
||||
state: CombatantState,
|
||||
target: CombatantState | None,
|
||||
maneuver: str | None,
|
||||
*,
|
||||
direction: tuple[int, int] | None = None,
|
||||
) -> None:
|
||||
"""Execute a combat maneuver (trip, disarm, bull_rush, sunder, grapple,
|
||||
escape, overrun, dirty_trick, steal, reposition, drag)."""
|
||||
@@ -1595,14 +1601,14 @@ class CombatEngine:
|
||||
handlers: dict[str, Callable[[], None]] = {
|
||||
"trip": lambda: self._maneuver_trip(state, target, m),
|
||||
"disarm": lambda: self._maneuver_disarm(state, target, m),
|
||||
"bull_rush": lambda: self._maneuver_bull_rush(state, target, m),
|
||||
"bull_rush": lambda: self._maneuver_bull_rush(state, target, m, direction=direction),
|
||||
"sunder": lambda: self._maneuver_sunder(state, target, m),
|
||||
"grapple": lambda: self._maneuver_grapple(state, target, m),
|
||||
"overrun": lambda: self._maneuver_overrun(state, target, m),
|
||||
"dirty_trick": lambda: self._maneuver_dirty_trick(state, target, m),
|
||||
"steal": lambda: self._maneuver_steal(state, target, m),
|
||||
"reposition": lambda: self._maneuver_reposition(state, target, m),
|
||||
"drag": lambda: self._maneuver_drag(state, target, m),
|
||||
"reposition": lambda: self._maneuver_reposition(state, target, m, direction=direction),
|
||||
"drag": lambda: self._maneuver_drag(state, target, m, direction=direction),
|
||||
}
|
||||
handlers[maneuver]()
|
||||
|
||||
@@ -1658,12 +1664,23 @@ class CombatEngine:
|
||||
)
|
||||
|
||||
def _maneuver_bull_rush(
|
||||
self, state: CombatantState, target: CombatantState, m: _ManeuverRoll
|
||||
self,
|
||||
state: CombatantState,
|
||||
target: CombatantState,
|
||||
m: _ManeuverRoll,
|
||||
*,
|
||||
direction: tuple[int, int] | None = None,
|
||||
) -> None:
|
||||
if not m.success:
|
||||
self._maneuver_fail(state, target, "bull_rush", m)
|
||||
return
|
||||
pushed = self._bull_rush_push(state, target)
|
||||
if direction is not None:
|
||||
new_pos = (target.pos[0] + direction[0], target.pos[1] + direction[1])
|
||||
pushed = self._cells_free(self._footprint_at(new_pos, target), target)
|
||||
if pushed:
|
||||
target.pos = new_pos
|
||||
else:
|
||||
pushed = self._bull_rush_push(state, target)
|
||||
outcome = "SUCCESS (pushed)" if pushed else "SUCCESS (no space)"
|
||||
self._log(
|
||||
f"round {self._current_round} {state.combatant.id}: bull rush vs "
|
||||
@@ -1805,21 +1822,35 @@ class CombatEngine:
|
||||
)
|
||||
|
||||
def _maneuver_reposition(
|
||||
self, state: CombatantState, target: CombatantState, m: _ManeuverRoll
|
||||
self,
|
||||
state: CombatantState,
|
||||
target: CombatantState,
|
||||
m: _ManeuverRoll,
|
||||
*,
|
||||
direction: tuple[int, int] | None = None,
|
||||
) -> None:
|
||||
if not m.success:
|
||||
self._maneuver_fail(state, target, "reposition", m)
|
||||
return
|
||||
new_pos: Pos | None = None
|
||||
for dr, dc in _NEIGHBOR_OFFSETS:
|
||||
pos = (target.pos[0] + dr, target.pos[1] + dc)
|
||||
if pos == state.pos:
|
||||
continue
|
||||
if not (0 <= pos[0] < self._grid.height and 0 <= pos[1] < self._grid.width):
|
||||
continue
|
||||
if self._cells_free(self._footprint_at(pos, target), target):
|
||||
if direction is not None:
|
||||
pos = (target.pos[0] + direction[0], target.pos[1] + direction[1])
|
||||
if (
|
||||
0 <= pos[0] < self._grid.height
|
||||
and 0 <= pos[1] < self._grid.width
|
||||
and self._cells_free(self._footprint_at(pos, target), target)
|
||||
):
|
||||
new_pos = pos
|
||||
break
|
||||
else:
|
||||
for dr, dc in _NEIGHBOR_OFFSETS:
|
||||
pos = (target.pos[0] + dr, target.pos[1] + dc)
|
||||
if pos == state.pos:
|
||||
continue
|
||||
if not (0 <= pos[0] < self._grid.height and 0 <= pos[1] < self._grid.width):
|
||||
continue
|
||||
if self._cells_free(self._footprint_at(pos, target), target):
|
||||
new_pos = pos
|
||||
break
|
||||
if new_pos is not None:
|
||||
target.pos = new_pos
|
||||
outcome = f"SUCCESS (moved to {new_pos})"
|
||||
@@ -1832,7 +1863,12 @@ class CombatEngine:
|
||||
)
|
||||
|
||||
def _maneuver_drag(
|
||||
self, state: CombatantState, target: CombatantState, m: _ManeuverRoll
|
||||
self,
|
||||
state: CombatantState,
|
||||
target: CombatantState,
|
||||
m: _ManeuverRoll,
|
||||
*,
|
||||
direction: tuple[int, int] | None = None,
|
||||
) -> None:
|
||||
if not m.success:
|
||||
self._maneuver_fail(state, target, "drag", m)
|
||||
@@ -1840,7 +1876,10 @@ class CombatEngine:
|
||||
ar, ac = state.pos
|
||||
tr, tc = target.pos
|
||||
adjacent = max(abs(tr - ar), abs(tc - ac)) == 1
|
||||
beyond = self._beyond_cell(state.pos, target.pos)
|
||||
if direction is not None:
|
||||
beyond = (tr + direction[0], tc + direction[1])
|
||||
else:
|
||||
beyond = self._beyond_cell(state.pos, target.pos)
|
||||
if adjacent and self._cells_free(self._footprint_at(beyond, target), target):
|
||||
target_old = target.pos
|
||||
target.pos = beyond
|
||||
|
||||
Reference in New Issue
Block a user