feat(rules): SplashSpec data model - data-driven splash marker on thrown weapons, Alchemist's Fire splash 1 fire on oni/tammara (SRD Throw Splash Weapon, Alchemist's fire)
This commit is contained in:
+5
-1
@@ -72,7 +72,11 @@
|
|||||||
"crit_mult": 2,
|
"crit_mult": 2,
|
||||||
"reach_ft": 5,
|
"reach_ft": 5,
|
||||||
"count": 1,
|
"count": 1,
|
||||||
"range_increment_ft": 10
|
"range_increment_ft": 10,
|
||||||
|
"splash": {
|
||||||
|
"damage": 1,
|
||||||
|
"type": "fire"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Attaques naturelles (Shifter Claws / formes fées)",
|
"name": "Attaques naturelles (Shifter Claws / formes fées)",
|
||||||
|
|||||||
@@ -111,7 +111,11 @@
|
|||||||
"crit_mult": 2,
|
"crit_mult": 2,
|
||||||
"reach_ft": 5,
|
"reach_ft": 5,
|
||||||
"count": 1,
|
"count": 1,
|
||||||
"range_increment_ft": 10
|
"range_increment_ft": 10,
|
||||||
|
"splash": {
|
||||||
|
"damage": 1,
|
||||||
|
"type": "fire"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Unarmed Strike",
|
"name": "Unarmed Strike",
|
||||||
|
|||||||
@@ -7,9 +7,9 @@ never sees raw JSON.
|
|||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from typing import Literal
|
from typing import Literal, Self
|
||||||
|
|
||||||
from pydantic import BaseModel, ConfigDict, Field, field_validator
|
from pydantic import BaseModel, ConfigDict, Field, field_validator, model_validator
|
||||||
|
|
||||||
from pf1e_simulator.abilities import AbilitySpec # noqa: TC001
|
from pf1e_simulator.abilities import AbilitySpec # noqa: TC001
|
||||||
from pf1e_simulator.dice import DiceExpr, DiceParseError, parse_dice
|
from pf1e_simulator.dice import DiceExpr, DiceParseError, parse_dice
|
||||||
@@ -232,6 +232,24 @@ class DamageComponent(_FrozenModel):
|
|||||||
return value
|
return value
|
||||||
|
|
||||||
|
|
||||||
|
class SplashSpec(_FrozenModel):
|
||||||
|
"""Splash payload of a thrown splash weapon (SRD "Throw Splash Weapon").
|
||||||
|
|
||||||
|
``damage`` is the FLAT splash damage every creature within 5 feet of the
|
||||||
|
point of impact takes on a direct hit — no attack roll, no saving throw
|
||||||
|
(SRD "Alchemist's fire": "Every creature within 5 feet of the point where
|
||||||
|
the flask hits takes 1 point of fire damage from the splash"). ``type`` is
|
||||||
|
the energy type ("fire", "acid", ...): splash is energy damage, so the
|
||||||
|
component-based energy resistance/immunity path applies and damage
|
||||||
|
reduction does not; the flat amount is never multiplied on a critical hit.
|
||||||
|
On a miss the weapon scatters (1d8 direction, one range increment) and
|
||||||
|
deals only this splash at where it landed.
|
||||||
|
"""
|
||||||
|
|
||||||
|
damage: int = Field(ge=0)
|
||||||
|
type: str = Field(min_length=1)
|
||||||
|
|
||||||
|
|
||||||
_HAFTED_WEAPON_TOKENS: tuple[str, ...] = (
|
_HAFTED_WEAPON_TOKENS: tuple[str, ...] = (
|
||||||
"axe", "spear", "hammer", "mace", "flail", "staff", "pick", "javelin",
|
"axe", "spear", "hammer", "mace", "flail", "staff", "pick", "javelin",
|
||||||
)
|
)
|
||||||
@@ -291,6 +309,23 @@ class AttackSpec(_FrozenModel):
|
|||||||
hardness: int | None = Field(default=None, ge=0)
|
hardness: int | None = Field(default=None, ge=0)
|
||||||
hit_points: int | None = Field(default=None, ge=1)
|
hit_points: int | None = Field(default=None, ge=1)
|
||||||
enhancement: int = Field(default=0, ge=0, le=20)
|
enhancement: int = Field(default=0, ge=0, le=20)
|
||||||
|
# SRD "Throw Splash Weapon": a thrown splash weapon (alchemist's fire,
|
||||||
|
# acid, holy water) is a ranged attack with a splash payload; it must be
|
||||||
|
# thrown (ranged kind or ranged touch with a range increment), so the
|
||||||
|
# validator below rejects splash on melee or increment-less attacks.
|
||||||
|
splash: SplashSpec | None = None
|
||||||
|
|
||||||
|
@model_validator(mode="after")
|
||||||
|
def _splash_needs_thrown_range(self) -> Self:
|
||||||
|
if self.splash is not None and (
|
||||||
|
self.kind == "melee" or self.range_increment_ft is None
|
||||||
|
):
|
||||||
|
msg = (
|
||||||
|
f"attack {self.id!r}: splash requires a thrown weapon "
|
||||||
|
"(kind ranged/touch with range_increment_ft)"
|
||||||
|
)
|
||||||
|
raise ValueError(msg)
|
||||||
|
return self
|
||||||
|
|
||||||
def object_stats(self) -> tuple[int, int]:
|
def object_stats(self) -> tuple[int, int]:
|
||||||
"""Hardness and hit points of the weapon as an OBJECT (Medium item).
|
"""Hardness and hit points of the weapon as an OBJECT (Medium item).
|
||||||
|
|||||||
Reference in New Issue
Block a user