fix(rules): avoid the firing-into-melee penalty at 10 feet
SRD 'Attack - Shooting or Throwing into a Melee': a target at least 10 ft from the nearest friendly character lets you avoid the -4 penalty entirely (0, not -2). The -2 is reserved for targets two size categories larger than the engaged friendlies (0 at three+), which is not modeled (documented). Golden transcript unaffected (shot still misses). Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
@@ -937,8 +937,12 @@ Règles modélisées :
|
|||||||
ou `touch` avec incrément) contre une cible engagée dans une mêlée subit −4 au
|
ou `touch` avec incrément) contre une cible engagée dans une mêlée subit −4 au
|
||||||
jet d'attaque ET au jet de confirmation (la cible est « engagée » quand un
|
jet d'attaque ET au jet de confirmation (la cible est « engagée » quand un
|
||||||
ennemi actif autre que l'attaquant, doté d'une arme de mêlée, menace ses
|
ennemi actif autre que l'attaquant, doté d'une arme de mêlée, menace ses
|
||||||
cases) ; la pénalité tombe à −2 si la cible ou l'adversaire menaçant est à
|
cases) ; la pénalité est **totalement évitée (0)** si la cible ou l'adversaire
|
||||||
≥ 10 ft (2 cases) de l'attaquant. Le don « Precise Shot » annule la pénalité.
|
menaçant est à ≥ 10 ft (2 cases) de l'attaquant (SRD « Attack — Shooting or
|
||||||
|
Throwing into a Melee » : « you can avoid the −4 penalty »). Simplification
|
||||||
|
documentée : la réduction RAW à −2 quand la cible est de deux catégories de
|
||||||
|
taille au-dessus des personnages amis (et 0 à trois ou plus) n'est pas
|
||||||
|
modélisée. Le don « Precise Shot » annule la pénalité.
|
||||||
Les attaques de mêlée ne sont pas concernées.
|
Les attaques de mêlée ne sont pas concernées.
|
||||||
- Couvert : les trois niveaux PF1e sont modélisés via `cover_bonus` sur le
|
- Couvert : les trois niveaux PF1e sont modélisés via `cover_bonus` sur le
|
||||||
terrain — partiel +2, standard +4 (ou `cover: true`), amélioré +8 ; le
|
terrain — partiel +2, standard +4 (ou `cover: true`), amélioré +8 ; le
|
||||||
|
|||||||
@@ -177,8 +177,7 @@ _UP_CLOSE_RANGE_FT = 30 # PF1e: Up Close and Deadly works within 30 ft
|
|||||||
# PF1e: Point-Blank Shot grants +1/+1 within 30 ft with ranged weapons
|
# PF1e: Point-Blank Shot grants +1/+1 within 30 ft with ranged weapons
|
||||||
_POINT_BLANK_RANGE_FT = 30
|
_POINT_BLANK_RANGE_FT = 30
|
||||||
_FIRING_MELEE_PENALTY = -4 # PF1e: ranged attack into melee
|
_FIRING_MELEE_PENALTY = -4 # PF1e: ranged attack into melee
|
||||||
_FIRING_MELEE_REDUCED_PENALTY = -2 # PF1e: -2 when the melee is 10+ ft away
|
_FIRING_MELEE_SAFE_FT = 10 # PF1e: 2 cells (10 ft) = the -4 penalty is avoided
|
||||||
_FIRING_MELEE_SAFE_FT = 10 # PF1e: 2 cells (10 ft) from attacker = reduced penalty
|
|
||||||
_SNEAK_ATTACK_FEATURE = "Sneak Attack" # PF1e: rogue class feature (precision dice)
|
_SNEAK_ATTACK_FEATURE = "Sneak Attack" # PF1e: rogue class feature (precision dice)
|
||||||
_PRECISE_SHOT_FEATURE = "Precise Shot" # PF1e: feat negating firing-into-melee penalty
|
_PRECISE_SHOT_FEATURE = "Precise Shot" # PF1e: feat negating firing-into-melee penalty
|
||||||
_IMPROVED_CRITICAL_FEATURE = "Improved Critical" # PF1e: feat doubling the threat range
|
_IMPROVED_CRITICAL_FEATURE = "Improved Critical" # PF1e: feat doubling the threat range
|
||||||
@@ -2645,8 +2644,18 @@ class CombatEngine:
|
|||||||
self, attacker: CombatantState, target: CombatantState
|
self, attacker: CombatantState, target: CombatantState
|
||||||
) -> int:
|
) -> int:
|
||||||
"""PF1e firing into melee: -4 on a ranged attack whose target is
|
"""PF1e firing into melee: -4 on a ranged attack whose target is
|
||||||
engaged in melee, reduced to -2 when the target or every threatening
|
engaged in melee, avoided entirely (0) when the target or every
|
||||||
opponent stands 10+ ft from the attacker. Precise Shot removes it.
|
threatening opponent stands 10+ ft from the attacker. Precise Shot
|
||||||
|
removes it.
|
||||||
|
|
||||||
|
PF1e (SRD "Attack" — Shooting or Throwing into a Melee): "If your
|
||||||
|
target (or the part of your target you're aiming at, if it's a big
|
||||||
|
target) is at least 10 feet away from the nearest friendly character,
|
||||||
|
you can avoid the -4 penalty" — the 10+ ft clause is therefore 0, not
|
||||||
|
-2. The -2 applies only when the target is two size categories larger
|
||||||
|
than the friendly characters it is engaged with (and 0 at three or
|
||||||
|
more): size-category reduction is NOT modeled (documented
|
||||||
|
simplification).
|
||||||
|
|
||||||
The target is engaged when an active enemy of the target (not the
|
The target is engaged when an active enemy of the target (not the
|
||||||
attacker itself) threatens the target's occupied cells with a melee
|
attacker itself) threatens the target's occupied cells with a melee
|
||||||
@@ -2665,11 +2674,11 @@ class CombatEngine:
|
|||||||
if not threats:
|
if not threats:
|
||||||
return 0
|
return 0
|
||||||
if self.creature_distance(attacker, target) * _SQUARE_FT >= _FIRING_MELEE_SAFE_FT:
|
if self.creature_distance(attacker, target) * _SQUARE_FT >= _FIRING_MELEE_SAFE_FT:
|
||||||
return _FIRING_MELEE_REDUCED_PENALTY
|
return 0
|
||||||
for threat in threats:
|
for threat in threats:
|
||||||
dist = self.creature_distance(attacker, threat) * _SQUARE_FT
|
dist = self.creature_distance(attacker, threat) * _SQUARE_FT
|
||||||
if dist >= _FIRING_MELEE_SAFE_FT:
|
if dist >= _FIRING_MELEE_SAFE_FT:
|
||||||
return _FIRING_MELEE_REDUCED_PENALTY
|
return 0
|
||||||
return _FIRING_MELEE_PENALTY
|
return _FIRING_MELEE_PENALTY
|
||||||
|
|
||||||
def _engaged_in_melee(self, state: CombatantState) -> bool:
|
def _engaged_in_melee(self, state: CombatantState) -> bool:
|
||||||
|
|||||||
@@ -337,7 +337,11 @@ def test_firing_into_melee_minus4() -> None:
|
|||||||
assert t.hp == 100
|
assert t.hp == 100
|
||||||
|
|
||||||
|
|
||||||
def test_firing_into_melee_minus2_when_target_10ft_away() -> None:
|
def test_firing_into_melee_avoided_when_target_10ft_away() -> None:
|
||||||
|
# PF1e (SRD "Attack" — Shooting or Throwing into a Melee): "If your target
|
||||||
|
# [...] is at least 10 feet away from the nearest friendly character, you
|
||||||
|
# can avoid the -4 penalty" — the 10+ ft clause is 0, not -2 (the -2 is
|
||||||
|
# reserved for targets two size categories larger, not modeled).
|
||||||
archer = _combatant("archer", ac=99, attacks=(_bow("archer", attack_bonus=8),))
|
archer = _combatant("archer", ac=99, attacks=(_bow("archer", attack_bonus=8),))
|
||||||
tgt = _combatant("tgt", ac=15, attacks=(_bow("tgt"),))
|
tgt = _combatant("tgt", ac=15, attacks=(_bow("tgt"),))
|
||||||
guard = _combatant("guard", ac=99)
|
guard = _combatant("guard", ac=99)
|
||||||
@@ -349,8 +353,8 @@ def test_firing_into_melee_minus2_when_target_10ft_away() -> None:
|
|||||||
)
|
)
|
||||||
a, t, _g = eng._states
|
a, t, _g = eng._states
|
||||||
result = eng.resolve_attack(a, t, a.combatant.attacks[0])
|
result = eng.resolve_attack(a, t, a.combatant.attacks[0])
|
||||||
assert result.total == 15
|
assert result.total == 17
|
||||||
assert result.melee_penalty == -2
|
assert result.melee_penalty == 0
|
||||||
assert result.hit is True
|
assert result.hit is True
|
||||||
assert result.damage == 3
|
assert result.damage == 3
|
||||||
|
|
||||||
|
|||||||
@@ -816,7 +816,7 @@ def test_ranged_attack_ignores_flanking() -> None:
|
|||||||
"initiative: aly d20=9+0=9",
|
"initiative: aly d20=9+0=9",
|
||||||
"initiative: tgt d20=8+0=8",
|
"initiative: tgt d20=8+0=8",
|
||||||
"round 1 tgt: AoO vs arc d20=15+2=17 AC 13 -> HIT 3 damage (6->3)",
|
"round 1 tgt: AoO vs arc d20=15+2=17 AC 13 -> HIT 3 damage (6->3)",
|
||||||
"round 1 arc: short bow vs tgt d20=12+2-2(melee)=12 AC 15 -> MISS",
|
"round 1 arc: short bow vs tgt d20=12+2=14 AC 15 -> MISS",
|
||||||
"round 1 aly: short sword vs tgt d20=10+0=10 AC 15 -> MISS",
|
"round 1 aly: short sword vs tgt d20=10+0=10 AC 15 -> MISS",
|
||||||
"round 1 tgt: short sword vs arc d20=5+2=7 AC 13 -> MISS",
|
"round 1 tgt: short sword vs arc d20=5+2=7 AC 13 -> MISS",
|
||||||
"battle over: draw after 1 rounds",
|
"battle over: draw after 1 rounds",
|
||||||
|
|||||||
Reference in New Issue
Block a user