feat(combat): add withdraw action (double move with AoO protection on first square)
This commit is contained in:
@@ -1145,3 +1145,60 @@ def test_charge_too_close_full_attacks_instead() -> None:
|
||||
"round 1 orc: short sword vs gob d20=3+0=3 AC 15 -> MISS",
|
||||
"battle over: draw after 1 rounds",
|
||||
)
|
||||
|
||||
|
||||
# --------------------------------------------------------------------------- #
|
||||
# Withdraw (CRB: double move, first square protected, subsequent provoke) #
|
||||
# --------------------------------------------------------------------------- #
|
||||
|
||||
|
||||
def _withdraw_from_foe(
|
||||
engine: CombatEngine, state: CombatantState
|
||||
) -> tuple[Action, ...]:
|
||||
return (Action(kind="withdraw", target_id="foe"),)
|
||||
|
||||
|
||||
def test_withdraw_protects_first_square() -> None:
|
||||
"""Withdraw moves 2x speed away; the starting square does not provoke AoO."""
|
||||
flee = make_combatant("flee", hp=20, ac=15, attack_bonus=0, speed=30)
|
||||
foe = make_combatant("foe", hp=20, ac=15, attack_bonus=5, speed=0)
|
||||
states = [make_state(flee, "players", (4, 1)), make_state(foe, "monsters", (3, 1))]
|
||||
engine = CombatEngine(
|
||||
ScriptedRng([10, 9, 15, 3]),
|
||||
make_grid(), states, round_cap=1, policy=_withdraw_from_foe,
|
||||
)
|
||||
result = engine.run()
|
||||
assert result.transcript == (
|
||||
"initiative: flee d20=10+0=10",
|
||||
"initiative: foe d20=9+0=9",
|
||||
"round 1 flee: withdraw (4,1)->(5,0)->(6,0)->(7,0)",
|
||||
"round 1 foe: wait",
|
||||
"battle over: draw after 1 rounds",
|
||||
)
|
||||
|
||||
|
||||
def test_withdraw_provokes_after_first_square() -> None:
|
||||
"""Subsequent squares during withdraw provoke AoOs from other enemies."""
|
||||
flee = make_combatant("flee", hp=20, ac=12, attack_bonus=0, speed=30)
|
||||
foe = make_combatant("foe", hp=20, ac=15, attack_bonus=5, speed=0)
|
||||
foe2 = make_combatant("foe2", hp=20, ac=15, attack_bonus=5, speed=0)
|
||||
states = [
|
||||
make_state(flee, "players", (4, 1)),
|
||||
make_state(foe, "monsters", (3, 1)),
|
||||
make_state(foe2, "monsters", (6, 1)),
|
||||
]
|
||||
engine = CombatEngine(
|
||||
ScriptedRng([10, 9, 8, 15, 3, 5]),
|
||||
make_grid(), states, round_cap=1, policy=_withdraw_from_foe,
|
||||
)
|
||||
result = engine.run()
|
||||
assert result.transcript == (
|
||||
"initiative: flee d20=10+0=10",
|
||||
"initiative: foe d20=9+0=9",
|
||||
"initiative: foe2 d20=8+0=8",
|
||||
"round 1 foe2: AoO vs flee d20=15+5=20 AC 12 -> HIT 3 damage (20->17)",
|
||||
"round 1 flee: withdraw (4,1)->(5,0)->(6,0)->(7,1)",
|
||||
"round 1 foe: wait",
|
||||
"round 1 foe2: wait",
|
||||
"battle over: draw after 1 rounds",
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user