fix(rules): Up Close and Deadly deed requires a firearm attack (Ultimate Combat)
This commit is contained in:
@@ -1419,7 +1419,9 @@ Règles modélisées :
|
||||
demi-niveau du canaliseur + modificateur de Charisme` pour demi-dégâts
|
||||
— CRB Channel Energy ; le mode soin n'accorde pas de sauvegarde), sortilège `evil_eye` (nairda), deed `up_close_and_deadly`
|
||||
(tammara, action bonus +1d6 à ≤ 30 ft — consommé sur la première attaque
|
||||
résolue : si elle est à distance ET à ≤ 30 ft, +1d6 s'ajoute aux dégâts ;
|
||||
résolue : si elle est une attaque **d'arme à feu** à distance ET à ≤ 30 ft
|
||||
(Ultimate Combat « Deeds — Up Close and Deadly » : « a successful attack
|
||||
with a firearm against a target within 30 feet »), +1d6 s'ajoute aux dégâts ;
|
||||
sinon le bonus est perdu ; le drapeau est réinitialisé dans tous les cas),
|
||||
deed `quick_clear` (S5a — conformance implémentée, Ultimate Combat « Deeds —
|
||||
Quick Clear » : « As a standard action, you can spend 1 point from your grit
|
||||
|
||||
@@ -3096,13 +3096,21 @@ class CombatEngine:
|
||||
) -> int:
|
||||
"""Consume the Up Close and Deadly deed on the first attack resolved.
|
||||
|
||||
The deed applies +1d6 to a ranged attack within 30 ft; the flag is
|
||||
reset after the first attack in all cases (the benefit is lost if the
|
||||
attack is out of range or not ranged).
|
||||
The deed applies +1d6 to a FIREARM attack that hits a target within
|
||||
30 ft (Ultimate Combat "Deeds - Up Close and Deadly": "makes a
|
||||
successful attack with a firearm against a target within 30 feet");
|
||||
the flag is reset after the first attack in all cases (the benefit is
|
||||
lost if the attack misses, is not a firearm, is out of range or is
|
||||
not ranged at all).
|
||||
"""
|
||||
if not attacker.deed_up_close_and_deadly:
|
||||
return damage
|
||||
if hit and self._is_ranged_attack(weapon) and dist_ft <= _UP_CLOSE_RANGE_FT:
|
||||
if (
|
||||
hit
|
||||
and weapon.firearm
|
||||
and self._is_ranged_attack(weapon)
|
||||
and dist_ft <= _UP_CLOSE_RANGE_FT
|
||||
):
|
||||
bonus = self._rng.roll_die(6)
|
||||
damage += bonus
|
||||
target.hp -= bonus
|
||||
|
||||
@@ -5,8 +5,9 @@ Covers:
|
||||
action when the combatant has the Power Attack feat, and is a logged no-op
|
||||
otherwise.
|
||||
- ``deed_up_close_and_deadly`` consumption: the +1d6 bonus applies to the
|
||||
first ranged attack within 30 ft and the flag is reset; the flag is reset
|
||||
with no bonus when the first attack is out of range or not ranged.
|
||||
first firearm attack within 30 ft and the flag is reset; the flag is reset
|
||||
with no bonus when the first attack is out of range, not ranged, or not a
|
||||
firearm.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
@@ -33,6 +34,7 @@ def _combatant(
|
||||
kind: str = "melee",
|
||||
range_increment_ft: int | None = None,
|
||||
features: list | None = None,
|
||||
firearm: bool = False,
|
||||
) -> Combatant:
|
||||
attack = AttackSpec(
|
||||
id=f"{cid}-w",
|
||||
@@ -41,6 +43,7 @@ def _combatant(
|
||||
attack_bonus=5,
|
||||
damage=[DamageComponent(formula=parse_dice("1d8"), types=["slashing"])],
|
||||
range_increment_ft=range_increment_ft,
|
||||
firearm=firearm,
|
||||
)
|
||||
return Combatant(
|
||||
id=cid,
|
||||
@@ -124,11 +127,15 @@ class TestActivatePowerAttack:
|
||||
class TestDeedUpCloseAndDeadly:
|
||||
"""Given: an attacker with deed_up_close_and_deadly set
|
||||
When: an attack is resolved
|
||||
Then: the first ranged attack within 30 ft gains +1d6 and the flag resets;
|
||||
otherwise the flag resets with no bonus."""
|
||||
Then: the first firearm attack within 30 ft gains +1d6 and the flag
|
||||
resets; otherwise the flag resets with no bonus (Ultimate Combat deed:
|
||||
"makes a successful attack with a firearm against a target within 30
|
||||
feet")."""
|
||||
|
||||
def test_ranged_within_30ft_adds_d6_and_resets(self) -> None:
|
||||
attacker = _combatant("gunner", kind="ranged", range_increment_ft=60)
|
||||
def test_firearm_within_30ft_adds_d6_and_resets(self) -> None:
|
||||
attacker = _combatant(
|
||||
"gunner", kind="ranged", range_increment_ft=60, firearm=True
|
||||
)
|
||||
target = _foe()
|
||||
engine, a, t = _engine(attacker, target, [15, 4, 3])
|
||||
a.deed_up_close_and_deadly = True
|
||||
@@ -137,8 +144,22 @@ class TestDeedUpCloseAndDeadly:
|
||||
assert result.damage == 4 + 3 # 1d8(4) + 1d6(3)
|
||||
assert a.deed_up_close_and_deadly is False
|
||||
|
||||
def test_non_firearm_ranged_gets_no_bonus(self) -> None:
|
||||
# A bow is a ranged attack within 30 ft but NOT a firearm: the deed
|
||||
# gives no bonus and is consumed on the resolved attack.
|
||||
attacker = _combatant("archer", kind="ranged", range_increment_ft=60)
|
||||
target = _foe()
|
||||
engine, a, t = _engine(attacker, target, [15, 4])
|
||||
a.deed_up_close_and_deadly = True
|
||||
result = engine.resolve_attack(a, t, attacker.attacks[0])
|
||||
assert result.hit is True
|
||||
assert result.damage == 4 # no +1d6 (not a firearm)
|
||||
assert a.deed_up_close_and_deadly is False
|
||||
|
||||
def test_out_of_range_resets_without_bonus(self) -> None:
|
||||
attacker = _combatant("gunner", kind="ranged", range_increment_ft=60)
|
||||
attacker = _combatant(
|
||||
"gunner", kind="ranged", range_increment_ft=60, firearm=True
|
||||
)
|
||||
target = _foe()
|
||||
engine, a, t = _engine(attacker, target, [15, 4])
|
||||
a.deed_up_close_and_deadly = True
|
||||
|
||||
Reference in New Issue
Block a user