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:
@@ -232,6 +232,11 @@ class DamageComponent(_FrozenModel):
|
||||
return value
|
||||
|
||||
|
||||
_HAFTED_WEAPON_TOKENS: tuple[str, ...] = (
|
||||
"axe", "spear", "hammer", "mace", "flail", "staff", "pick", "javelin",
|
||||
)
|
||||
|
||||
|
||||
class AttackSpec(_FrozenModel):
|
||||
"""One attack menu entry (weapon or natural attack).
|
||||
|
||||
@@ -254,6 +259,13 @@ class AttackSpec(_FrozenModel):
|
||||
weapons. `secondary` marks a secondary natural attack (tail, wing, etc.):
|
||||
the engine applies -5 to the attack bonus (reduced to -2 with the
|
||||
Multiattack feat).
|
||||
|
||||
Object statistics (CRB "Breaking and Entering", Table 7-12): `hardness`
|
||||
and `hit_points` are authorable overrides; when absent the engine
|
||||
derives them from the weapon class via `object_stats()`. `enhancement`
|
||||
is the weapon's enhancement bonus (0 = masterwork or plain); each +1
|
||||
adds +2 hardness and +10 hit points (CRB "Magic Armor, Shields, and
|
||||
Weapons").
|
||||
"""
|
||||
|
||||
id: str
|
||||
@@ -272,6 +284,34 @@ class AttackSpec(_FrozenModel):
|
||||
two_handed: bool = False
|
||||
natural: bool = False
|
||||
secondary: bool = False
|
||||
hardness: int | None = Field(default=None, ge=0)
|
||||
hit_points: int | None = Field(default=None, ge=1)
|
||||
enhancement: int = Field(default=0, ge=0, le=20)
|
||||
|
||||
def object_stats(self) -> tuple[int, int]:
|
||||
"""Hardness and hit points of the weapon as an OBJECT (Medium item).
|
||||
|
||||
Defaults from CRB Table 7-12 "Common Armor, Weapon, and Shield
|
||||
Hardness and Hit Points" when not authored: projectile weapons 5/5;
|
||||
hafted weapons (wooden haft: axe, spear, hammer, mace, flail,
|
||||
staff, pick, javelin) hardness 5, 5 hp one-handed / 10 hp
|
||||
two-handed; blades steel hardness 10, 5 hp one-handed / 10 hp
|
||||
two-handed. The light/one-handed granularity is collapsed to
|
||||
one-handed (the model has no weapon-weight field). Enhancement
|
||||
adds +2 hardness and +10 hp per +1 (footnotes 1 and 3).
|
||||
"""
|
||||
if self.kind == "ranged" or self.firearm:
|
||||
base_hardness, base_hp = 5, 5
|
||||
elif any(token in self.name.lower() for token in _HAFTED_WEAPON_TOKENS):
|
||||
base_hardness, base_hp = 5, (10 if self.two_handed else 5)
|
||||
else:
|
||||
base_hardness, base_hp = 10, (10 if self.two_handed else 5)
|
||||
hardness = self.hardness if self.hardness is not None else base_hardness
|
||||
hit_points = self.hit_points if self.hit_points is not None else base_hp
|
||||
return (
|
||||
hardness + 2 * self.enhancement,
|
||||
hit_points + 10 * self.enhancement,
|
||||
)
|
||||
|
||||
|
||||
class DamageReduction(_FrozenModel):
|
||||
|
||||
Reference in New Issue
Block a user