fix(rules): disarm drops the wielded weapon (weapon_dropped) instead of a synthetic -4 penalty

This commit is contained in:
2026-09-16 20:08:10 +02:00
parent fdc9127483
commit 2b5920c85c
3 changed files with 112 additions and 19 deletions
+56
View File
@@ -284,6 +284,62 @@ def test_unrestricted_maneuver_ignores_size_limit() -> None:
assert not engine._rng._queue
# --- SRD Disarm: the target DROPS the weapon (no synthetic -4 penalty) --------
def test_disarm_success_drops_target_weapon() -> None:
# SRD Disarm: "If your attack is successful, your target drops one item
# it is carrying." The dropped weapon is unusable until retrieved; the
# old synthetic "disarmed" -4 attack condition is gone.
a = make_state(make_combatant("a", attack_bonus=2), "players", (1, 1))
b = make_state(make_combatant("b"), "monsters", (1, 2))
engine = make_engine([1, 12], [a, b])
engine._execute_maneuver(a, b, "disarm")
assert "b-w" in b.weapon_dropped
assert [w.id for w in b.usable_attacks()] == []
assert engine.weapon_for(b, a) is None
assert not any(c.condition.name == "disarmed" for c in b.active_conditions)
assert "SUCCESS (sword dropped)" in engine._transcript[-1]
def test_retrieve_weapon_picks_dropped_weapon_back_up() -> None:
# Picking the dropped weapon back up is a move action (retrieve_weapon);
# once restored, the weapon is usable again.
a = make_state(make_combatant("a", attack_bonus=2), "players", (1, 1))
b = make_state(make_combatant("b"), "monsters", (1, 2))
engine = make_engine([1, 12], [a, b])
engine._execute_maneuver(a, b, "disarm")
assert "b-w" in b.weapon_dropped
engine._retrieve_weapon(b, "b-w")
assert "b-w" not in b.weapon_dropped
assert [w.id for w in b.usable_attacks()] == ["b-w"]
assert "retrieve b-w (restored)" in engine._transcript[-1]
def test_retrieve_weapon_still_restores_stolen() -> None:
# The steal path keeps working: retrieve removes the weapon from
# weapon_stolen.
b = make_state(make_combatant("b"), "monsters", (1, 2))
b.weapon_stolen.add("b-w")
engine = make_engine([], [b])
engine._retrieve_weapon(b, "b-w")
assert not b.weapon_stolen
assert [w.id for w in b.usable_attacks()] == ["b-w"]
def test_disarm_fail_by_10_drops_attacker_weapon() -> None:
# SRD Disarm: "If your attack fails by 10 or more, you drop the weapon
# that you were using to attempt the disarm." a total 2+2=4 vs CMD 14
# fails by 10 -> a drops its sword; b's weapon is untouched.
a = make_state(make_combatant("a", attack_bonus=2), "players", (1, 1))
b = make_state(make_combatant("b", attack_bonus=20), "monsters", (1, 2))
engine = make_engine([1, 2], [a, b])
engine._execute_maneuver(a, b, "disarm")
assert "a-w" in a.weapon_dropped
assert not b.weapon_dropped
assert any("disarm backfire -> drops sword" in line for line in engine._transcript)
def test_overrun_knocks_prone_and_moves_through() -> None:
# d20=15+2=17 vs CMD 12: margin 5 -> prone AND moved through (CRB
# "Overrun": prone only when the attack exceeds CMD by 5 or more).