feat(rules): sunder deals real weapon damage vs object hardness/hp; broken weapons usable at -2 with restricted crits
SRD Sunder + CRB 'Smashing an Object' / 'Conditions - Broken': weapons carry Table 7-12 object stats (AttackSpec.hardness/hit_points/enhancement, defaults by weapon class); a successful sunder deals the attacker's actual physical weapon damage minus the target weapon's hardness, subtracted from its hp. At <= half hp the weapon gains the broken condition - still usable at -2 attack/damage, crits only on a natural 20 at x2. At <= 0 it is destroyed (weapon_destroyed, unusable). The old flat str_mod+2 / durability 10 / synthetic 'disarmed' condition is gone. Firearm misfire carve-out kept (broken firearms stay unusable until Quick Clear - misfire escalation not modeled, documented).
This commit is contained in:
@@ -2,10 +2,12 @@
|
||||
|
||||
``attack`` and ``full_attack`` accept ``Action.weapon_id`` — the id of one of
|
||||
the attacker's weapons. With it the engine strikes with THAT weapon instead of
|
||||
the ``weapon_for`` auto-selection; a choice that is unknown, broken, stolen,
|
||||
or unable to strike the target (out of reach / no line of effect) is rejected
|
||||
with ``weapon_unavailable`` and no attack is made. Without it, the
|
||||
auto-selection and the full-attack mechanics are byte-identical to before.
|
||||
the ``weapon_for`` auto-selection; a choice that is unknown, destroyed,
|
||||
stolen, dropped, or unable to strike the target (out of reach / no line of
|
||||
effect) is rejected with ``weapon_unavailable`` and no attack is made (a
|
||||
BROKEN melee weapon stays selectable — CRB "Broken" makes it usable at -2).
|
||||
Without it, the auto-selection and the full-attack mechanics are
|
||||
byte-identical to before.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
@@ -133,15 +135,31 @@ def test_full_attack_without_choice_still_swings_all_melee_weapons() -> None:
|
||||
assert "shortsword vs foe d20=10+2=12" in joined
|
||||
|
||||
|
||||
# ── (b) broken weapon → weapon_unavailable ───────────────────────────────────
|
||||
# ── (b) broken / destroyed weapon selection ──────────────────────────────────
|
||||
|
||||
|
||||
def test_attack_with_broken_weapon_is_rejected() -> None:
|
||||
eng, hero, foe = _duel([]) # empty script: any roll would raise
|
||||
def test_attack_with_broken_weapon_is_usable() -> None:
|
||||
# CRB "Broken": a broken weapon is still usable — at -2 on attack and
|
||||
# damage. The explicit choice is accepted: d20 10 + 2 - 2 = 10 vs AC 10
|
||||
# -> HIT for 3 - 2 = 1 damage.
|
||||
eng, hero, foe = _duel([10, 3])
|
||||
hero.weapon_broken.add("hero-short")
|
||||
result = eng._execute(
|
||||
hero, Action(kind="attack", target_id="foe", weapon_id="hero-short")
|
||||
)
|
||||
assert result == ActionResult(kind="attack", ok=True, reason="ok")
|
||||
assert "shortsword vs foe d20=10+2=10 AC 10 -> HIT 1 damage" in eng._transcript[-1]
|
||||
assert foe.hp == 99
|
||||
|
||||
|
||||
def test_attack_with_destroyed_weapon_is_rejected() -> None:
|
||||
# A destroyed weapon (hp <= 0, CRB "Smashing an Object": "ruined") is
|
||||
# unusable: the explicit choice is rejected, no roll consumed.
|
||||
eng, hero, foe = _duel([]) # empty script: any roll would raise
|
||||
hero.weapon_destroyed.add("hero-short")
|
||||
result = eng._execute(
|
||||
hero, Action(kind="attack", target_id="foe", weapon_id="hero-short")
|
||||
)
|
||||
assert result == ActionResult(
|
||||
kind="attack", ok=False, reason="weapon_unavailable"
|
||||
)
|
||||
@@ -149,9 +167,9 @@ def test_attack_with_broken_weapon_is_rejected() -> None:
|
||||
assert foe.hp == 100
|
||||
|
||||
|
||||
def test_full_attack_with_broken_weapon_is_rejected() -> None:
|
||||
def test_full_attack_with_destroyed_weapon_is_rejected() -> None:
|
||||
eng, hero, foe = _duel([])
|
||||
hero.weapon_broken.add("hero-short")
|
||||
hero.weapon_destroyed.add("hero-short")
|
||||
result = eng._execute(
|
||||
hero, Action(kind="full_attack", target_id="foe", weapon_id="hero-short")
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user