fix(rules): stop damage reduction from eating energy damage
PF1e (CRB Damage Reduction): "Damage reduction does not negate ... energy damage dealt along with an attack." _resolve_hit_damage now splits the post-crit total and applies DR only to the non-energy share (physical dice + flat bonuses + precision dice); energy components are reduced solely by the per-component energy resistance/immunity step. DR-bypass semantics (multi-component "and" requires every component) are unchanged. Pure-physical weapons keep bit-identical arithmetic and the golden transcripts are untouched. Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
@@ -36,7 +36,10 @@ Phase 0 documented deviations from PF1e (conventions):
|
||||
natural weapons in a single full attack are not modeled.
|
||||
- Natural 1 always misses; natural 20 always hits and threatens a crit.
|
||||
- A confirmed crit multiplies the total damage (dice + flat bonus) by crit_mult.
|
||||
- DR applies once, after crit multiplication; a single bypass type defeats
|
||||
- DR applies once, after crit multiplication, to the PHYSICAL portion of the
|
||||
damage only (bludgeoning/piercing/slashing dice, flat bonuses and precision
|
||||
dice) — energy damage dealt along with an attack is never reduced by DR
|
||||
(CRB "Damage Reduction"); a single bypass type defeats
|
||||
DR; a multi-component "and" bypass (e.g. "bludgeoning and magic") requires
|
||||
EVERY component (PF1e "DR X/Y and Z" notation).
|
||||
- Death when negative hp reaches the Constitution score (hp <= -CON); PF1e
|
||||
@@ -182,6 +185,9 @@ _FIGHT_DEFENSIVELY_AC_BONUS = 2 # PF1e: fighting defensively grants +2 dodge AC
|
||||
_FIGHT_DEFENSIVELY_ATTACK_PENALTY = -4 # PF1e: and -4 on attacks / crit confirms
|
||||
_SPELL_COMBAT_ATTACK_PENALTY = -2 # PF1e: magus Spell Combat melee attacks at -2
|
||||
_TOTAL_CONCEALMENT_PCT = 50 # PF1e: a blinded attacker's targets have total concealment
|
||||
# PF1e (CRB Damage Reduction): DR does not negate energy damage dealt along
|
||||
# with an attack — only these weapon damage types ride the DR path.
|
||||
_PHYSICAL_DAMAGE_TYPES = frozenset({"bludgeoning", "piercing", "slashing"})
|
||||
# PF1e confused d% table: 01-25 act normally, 26-50 babble, 51-75 hit self,
|
||||
# 76-100 attack the nearest creature
|
||||
_CONFUSED_ACT_NORMAL_MAX = 25
|
||||
@@ -2442,7 +2448,19 @@ class CombatEngine:
|
||||
if crit:
|
||||
damage *= weapon.crit_mult
|
||||
damage += sneak_total
|
||||
damage = self._apply_dr(damage, weapon, target)
|
||||
# PF1e (CRB Damage Reduction): "Damage reduction does not negate ...
|
||||
# energy damage dealt along with an attack" — DR reduces only the
|
||||
# non-energy share of the damage (physical dice, flat bonuses and
|
||||
# precision dice); energy components are cut solely by energy
|
||||
# resistance/immunity in the per-component step below.
|
||||
mult = weapon.crit_mult if crit else 1
|
||||
energy_share = sum(
|
||||
roll * mult
|
||||
for comp, roll in zip(weapon.damage, rolls, strict=True)
|
||||
if not _PHYSICAL_DAMAGE_TYPES.issuperset(comp.types)
|
||||
)
|
||||
physical = max(0, damage - energy_share)
|
||||
damage = self._apply_dr(physical, weapon, target) + energy_share
|
||||
return crit, False, self._apply_energy_resistance_per_component(
|
||||
damage, weapon, rolls, crit=crit, target=target
|
||||
)
|
||||
@@ -2633,10 +2651,11 @@ class CombatEngine:
|
||||
A component carrying several energy types uses the largest matching
|
||||
resistance (documented simplification: one die roll cannot be split
|
||||
across the listed types). Physical-only components match nothing in
|
||||
the target's resistance/immunity maps and stay on the DR path, which
|
||||
was applied to the lumped total just before this step (unchanged);
|
||||
single-component weapons keep the exact arithmetic of the old
|
||||
lumped-total path.
|
||||
the target's resistance/immunity maps — they rode the DR path just
|
||||
before this step, which reduces only the non-energy share of the
|
||||
damage (CRB: DR does not negate energy damage dealt along with an
|
||||
attack); single-component weapons keep the exact arithmetic of the
|
||||
old lumped-total path.
|
||||
"""
|
||||
mult = weapon.crit_mult if crit else 1
|
||||
for comp, roll in zip(weapon.damage, rolls, strict=True):
|
||||
|
||||
Reference in New Issue
Block a user