fix(rules): any concealment denies sneak attack (SRD rogue: 'a creature with concealment')
This commit is contained in:
@@ -1149,10 +1149,10 @@ Règles modélisées :
|
||||
cible en porte une, après la touche et la confirmation de critique, le moteur
|
||||
tire un d100 : si `d100 ≤ miss_chance`, l'attaque rate complètement (aucun
|
||||
dégât, pas de critique, deed non consommé — journalisé « concealment »). Une
|
||||
cible avec concealment **total** (`miss_chance ≥ 50 %`) ne peut pas être la
|
||||
victime d'une attaque sournoise ; une dissimulation partielle (flou 20 %) ne
|
||||
bloque plus la sournoise (conformance A7 — CRB rogue « Sneak Attack » :
|
||||
« cannot sneak attack while striking a creature with total concealment »).
|
||||
cible avec **n'importe quelle dissimulation** (`miss_chance > 0` — flou 20 %
|
||||
comme dissimulation totale 50 %) ne peut pas être la victime d'une attaque
|
||||
sournoise (conformance A7 — SRD rogue « Sneak Attack » :
|
||||
« A rogue cannot sneak attack while striking a creature with concealment »).
|
||||
Attaquant aveugle (V5a) : un attaquant portant `blinded` considère tous ses
|
||||
adversaires comme ayant un concealment total — le même jet unique de d100 est
|
||||
tiré avec `miss_chance = max(concealment de la cible, 50)` et journalisé
|
||||
@@ -1189,8 +1189,9 @@ Règles modélisées :
|
||||
attaque à distance à ≤ 30 ft). Les dés sont jetés avec le RNG du moteur et
|
||||
ajoutés APRÈS le multiplicateur de critique (la précision n'est jamais
|
||||
multipliée) et AVANT la RD et les résistances énergétiques ; jamais contre une
|
||||
cible à dissimulation **totale** (`miss_chance ≥ 50 %` ; conformance A7 — une
|
||||
dissimulation partielle comme le flou 20 % n'empêche plus la sournoise).
|
||||
cible qui a une dissimulation, même partielle (`miss_chance > 0` ;
|
||||
conformance A7 — SRD rogue : « A rogue cannot sneak attack while striking
|
||||
a creature with concealment »).
|
||||
Conformance P2 : les morts-vivants (`undead`) et les
|
||||
constructeurs (`construct`) sont immunisés aux dégâts de précision — la cible
|
||||
ne peut pas être la victime d'une attaque sournoise ; les autres immunités RAW
|
||||
|
||||
@@ -2999,14 +2999,15 @@ class CombatEngine:
|
||||
|
||||
PF1e: precision damage when the target is flanked (melee only, melee
|
||||
touch included) or denied its Dex bonus to AC (melee, or ranged within
|
||||
30 ft); dice = (level + 1) // 2. Only TOTAL concealment (miss chance
|
||||
>= 50%) denies the sneak attack; partial concealment (blurred 20%)
|
||||
does not (CRB rogue "Sneak Attack": "cannot sneak attack while
|
||||
striking a creature with total concealment"). A BLINDED attacker faces
|
||||
total concealment against ALL its opponents (CRB "Conditions —
|
||||
Blinded": "All opponents are considered to have total concealment
|
||||
(50% miss chance) against the blinded character"), so it cannot sneak
|
||||
attack either — same max(target, 50) convention as _resolve_hit_damage.
|
||||
30 ft); dice = (level + 1) // 2. ANY concealment (miss chance > 0)
|
||||
denies the sneak attack (SRD rogue "Sneak Attack": "A rogue cannot
|
||||
sneak attack while striking a creature with concealment" — the rogue
|
||||
must see the target well enough to pick a vital spot). A BLINDED
|
||||
attacker faces total concealment against ALL its opponents (CRB
|
||||
"Conditions — Blinded": "All opponents are considered to have total
|
||||
concealment (50% miss chance) against the blinded character"), so it
|
||||
cannot sneak attack either — same max(target, 50) convention as
|
||||
_resolve_hit_damage.
|
||||
PF1e (CRB): undead and constructs are immune to precision damage —
|
||||
ooze/plant immunity is not modeled (no such creature types exist in
|
||||
the data model).
|
||||
@@ -3015,7 +3016,7 @@ class CombatEngine:
|
||||
if self._has_condition_named(attacker, "blinded"):
|
||||
effective_concealment = max(concealment, _TOTAL_CONCEALMENT_PCT)
|
||||
if (
|
||||
effective_concealment >= _TOTAL_CONCEALMENT_PCT
|
||||
effective_concealment > 0
|
||||
or not self._has_feat(attacker, _SNEAK_ATTACK_FEATURE)
|
||||
):
|
||||
return 0
|
||||
|
||||
@@ -158,26 +158,27 @@ def test_total_concealment_denies_sneak_attack() -> None:
|
||||
assert not any("sneak" in line for line in eng._transcript)
|
||||
|
||||
|
||||
def test_partial_concealment_still_allows_sneak_attack() -> None:
|
||||
"""(A7) a blurred (20%) target CAN be sneak attacked — CRB rogue text is
|
||||
"cannot sneak attack while striking a creature with total concealment",
|
||||
so partial concealment no longer denies the precision damage."""
|
||||
def test_partial_concealment_denies_sneak_attack() -> None:
|
||||
"""(A7) a blurred (20%) target CANNOT be sneak attacked — SRD rogue text
|
||||
is "A rogue cannot sneak attack while striking a creature with
|
||||
concealment": ANY concealment denies the precision damage (the rogue
|
||||
must see the target well enough to pick out a vital spot)."""
|
||||
sneak = _combatant("atk", level=3, features=("Sneak Attack",))
|
||||
eng = _engine(
|
||||
[sneak, _combatant("tgt")],
|
||||
[(0, 0), (0, 1)],
|
||||
["players", "monsters"],
|
||||
[15, 90, 6, 6, 4],
|
||||
[15, 90, 6],
|
||||
)
|
||||
a, t = eng._states
|
||||
_add_condition(t, "flat_footed")
|
||||
_add_condition(t, "blurred")
|
||||
result = eng.resolve_attack(a, t, a.combatant.attacks[0])
|
||||
# d20=15 hits, d100=90 > 20% lands; 2d6 sneak (6+6=12) + 1d6 weapon (4)
|
||||
# = 16 precision-inclusive damage.
|
||||
assert result.damage == 16
|
||||
assert t.hp == 84
|
||||
assert any("sneak attack" in line and "2d6" in line for line in eng._transcript)
|
||||
# d20=15 hits, d100=90 > 20% lands; sneak denied by the 20% concealment,
|
||||
# so only the 1d6 weapon die (6) lands.
|
||||
assert result.damage == 6
|
||||
assert t.hp == 94
|
||||
assert not any("sneak" in line for line in eng._transcript)
|
||||
|
||||
|
||||
def test_concealment_miss_does_not_consume_deed() -> None:
|
||||
|
||||
Reference in New Issue
Block a user