fix(rules): natural weapons get no BAB iteratives, secondary attacks at -5
PF1e (CRB "Natural Attacks"): - Natural weapons (natural=true) get exactly one swing per full attack, regardless of BAB (no BAB iteratives for natural attacks). - Secondary natural attacks (secondary=true) attack at BAB -5; the Multiattack feat reduces this to -2. - Manifested in combat.py (_full_attack_weapon, _resolve_swing), models.py (AttackSpec.natural/secondary), 6 data JSONs, 4 new tests, and README documentation. 1461 passed, ruff clean, basedpyright 0 errors.
This commit is contained in:
@@ -6699,6 +6699,12 @@ class CombatEngine:
|
||||
target_live = self._stats[target.combatant.id]
|
||||
hp_before = target.hp
|
||||
override = bonus_override
|
||||
if weapon.secondary:
|
||||
# PF1e (CRB "Natural Attacks"): secondary natural attacks are at
|
||||
# base attack bonus -5; the Multiattack feat reduces this to -2.
|
||||
penalty = 2 if self._has_feat(attacker, "Multiattack") else 5
|
||||
base = weapon.attack_bonus if override is None else override
|
||||
override = base - penalty
|
||||
if attack_penalty:
|
||||
base = weapon.attack_bonus if override is None else override
|
||||
override = base + attack_penalty
|
||||
@@ -6829,6 +6835,12 @@ class CombatEngine:
|
||||
if weapon.count > 1:
|
||||
self._attack(attacker, target, weapon, attack_penalty=attack_penalty)
|
||||
return
|
||||
if weapon.natural:
|
||||
# PF1e (CRB "Natural Attacks"): "You do not receive additional
|
||||
# natural attacks for a high base attack bonus." One swing per
|
||||
# natural weapon, at its own attack bonus.
|
||||
self._attack(attacker, target, weapon, attack_penalty=attack_penalty)
|
||||
return
|
||||
n = min(
|
||||
_MAX_ITERATIVES,
|
||||
1 + max(0, (attacker.combatant.bab - 1) // _ITERATIVE_PENALTY),
|
||||
|
||||
@@ -242,14 +242,18 @@ class AttackSpec(_FrozenModel):
|
||||
increment and against full AC beyond it. `two_handed` marks a weapon swung
|
||||
in two hands (two-handed weapon, one-handed weapon used two hands, or a
|
||||
primary natural with 1.5x Strength) — PF1e Power Attack rolls +50% damage
|
||||
with such a weapon (CRB "Power Attack"). `reach_weapon` marks a weapon
|
||||
with such a weapon (CRB "Power Attack"). `reach_weapon` marks a weapon
|
||||
with the reach weapon quality (CRB "Big and Little Creatures in Combat"):
|
||||
it doubles the wielder's NATURAL reach but cannot strike at the wielder's
|
||||
natural reach or less — the usable zone is a RING (dead zone = natural
|
||||
reach). Without the flag a melee attack follows the creature's natural
|
||||
reach, which is a DISK: a Large creature's natural attack (its JSON
|
||||
`reach_ft` IS its natural reach, 10 ft) still strikes and threatens
|
||||
adjacent squares.
|
||||
adjacent squares. `natural` marks a natural weapon (bite, claw, etc.):
|
||||
PF1e (CRB "Natural Attacks") grants no BAB iteratives for natural
|
||||
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).
|
||||
"""
|
||||
|
||||
id: str
|
||||
@@ -266,6 +270,8 @@ class AttackSpec(_FrozenModel):
|
||||
count: int = Field(default=1, ge=1)
|
||||
firearm: bool = False
|
||||
two_handed: bool = False
|
||||
natural: bool = False
|
||||
secondary: bool = False
|
||||
|
||||
|
||||
class DamageReduction(_FrozenModel):
|
||||
|
||||
Reference in New Issue
Block a user