fix(combat): separate standard attack (single swing) from full attack (iteratives)
This commit is contained in:
@@ -183,11 +183,12 @@ class Action:
|
||||
"""What a policy wants a combatant to do this turn.
|
||||
|
||||
Kinds map to PF1e action types: ``attack`` is a standard action
|
||||
(single attack), ``move`` is a move action, ``full_attack`` is a full-round
|
||||
action (BAB iteratives), ``charge`` is a special full-round action
|
||||
(2x speed, straight line, +2 attack, -2 AC), ``swift`` and ``free`` are
|
||||
minor actions, ``immediate`` is an off-turn reaction (deferred). ``wait``
|
||||
is a no-op. The engine executes a policy-returned sequence per turn.
|
||||
(single attack, highest BAB only — no iteratives), ``move`` is a move
|
||||
action, ``full_attack`` is a full-round action (BAB iteratives), ``charge``
|
||||
is a special full-round action (2x speed, straight line, +2 attack, -2
|
||||
AC), ``swift`` and ``free`` are minor actions, ``immediate`` is an
|
||||
off-turn reaction (deferred). ``wait`` is a no-op. The engine executes a
|
||||
policy-returned sequence per turn.
|
||||
"""
|
||||
|
||||
kind: Literal[
|
||||
@@ -625,7 +626,9 @@ class CombatEngine:
|
||||
weapon = self.weapon_for(state, target)
|
||||
if weapon is None:
|
||||
return
|
||||
if kind == "full_attack":
|
||||
if kind == "attack":
|
||||
self._single_attack(state, target, weapon)
|
||||
elif kind == "full_attack":
|
||||
self._full_attack(state, target, weapon)
|
||||
else:
|
||||
self._attack(state, target, weapon)
|
||||
@@ -698,6 +701,15 @@ class CombatEngine:
|
||||
if self._resolve_swing(attacker, target, weapon, label=label):
|
||||
return
|
||||
|
||||
def _single_attack(
|
||||
self, attacker: CombatantState, target: CombatantState, weapon: AttackSpec
|
||||
) -> None:
|
||||
if not attacker.active:
|
||||
return
|
||||
if self._check_provoked_aoo(attacker, weapon):
|
||||
return
|
||||
self._resolve_swing(attacker, target, weapon, label=weapon.name)
|
||||
|
||||
def _full_attack(
|
||||
self, attacker: CombatantState, target: CombatantState, weapon: AttackSpec
|
||||
) -> None:
|
||||
@@ -870,12 +882,12 @@ Policy = Callable[[CombatEngine, CombatantState], tuple[Action, ...]]
|
||||
|
||||
|
||||
def default_policy(engine: CombatEngine, state: CombatantState) -> tuple[Action, ...]:
|
||||
"""Full-attack the nearest enemy in range; charge if reachable; otherwise approach.
|
||||
"""Standard-attack the nearest enemy in range; charge if reachable; otherwise approach.
|
||||
|
||||
Decision order:
|
||||
1. Full-attack if a weapon is usable against the nearest enemy this turn.
|
||||
1. Full-attack if a weapon is usable against the nearest enemy this turn (no move needed).
|
||||
2. Charge if a straight-line charge path exists (2x speed, +2 attack, -2 AC).
|
||||
3. Move toward the target then full-attack (standard + move economy).
|
||||
3. Move toward the target then single attack (standard + move economy).
|
||||
4. Wait if nothing is possible.
|
||||
"""
|
||||
target = engine.nearest_enemy(state)
|
||||
@@ -888,5 +900,5 @@ def default_policy(engine: CombatEngine, state: CombatantState) -> tuple[Action,
|
||||
return (Action(kind="charge", target_id=target.combatant.id),)
|
||||
return (
|
||||
Action(kind="move", target_id=target.combatant.id),
|
||||
Action(kind="full_attack", target_id=target.combatant.id),
|
||||
Action(kind="attack", target_id=target.combatant.id),
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user