From a1b1c29ca356eccc28d2aed8e928fa3fa16120ff Mon Sep 17 00:00:00 2001 From: ctan Date: Sun, 20 Sep 2026 19:23:15 +0200 Subject: [PATCH] feat(rules): expose splash in observation attack reference + AttackResult.splash, document splash weapon rule and JSON schema in README (SRD Throw Splash Weapon) --- README.md | 42 ++++++++++++++++++++++++++++++++++++ src/pf1e_simulator/combat.py | 5 +++++ 2 files changed, 47 insertions(+) diff --git a/README.md b/README.md index 44b7177..7f15021 100644 --- a/README.md +++ b/README.md @@ -301,6 +301,13 @@ double l'allonge naturelle du porteur mais ne frappe pas à cette allonge naturelle ou moins — anneau avec dead zone (conformance R1, voir « Mêlée »). Le `reach_ft` d'une attaque naturelle (morsure, sabots) n'est **pas** une arme à allonge : il encode l'allonge naturelle de la créature, qui reste un disque. +L'objet `splash` (optionnel, défaut `null`) marque une arme de jet à +éclaboussures (SRD « Throw Splash Weapon ») : `{"damage": N, "type": "fire"}` +— dégâts d'éclaboussure plats (type énergétique, comme dans `damage`) infligés sans jet ni sauvegarde à toute créature +à 5 pieds du point d'impact (touche directe) ou d'atterrissage (dispersion au +raté, voir « Armes de jet à éclaboussures »). Une arme `splash` doit être +lançable (`kind: ranged`, ou `kind: touch` avec `range_increment_ft`) — le +schéma rejette `splash` sur une attaque de mêlée ou sans incrément. `spell_resistance` (entier), `resistances` (dict type → montant) et `immunities` (liste de types) sont optionnels. Le tableau `carried_items` (optionnel, défaut vide) énumère les **objets @@ -1093,6 +1100,40 @@ Règles modélisées : touch attacks ») s'applique à **la touche** du contact de mêlée (pénalité −X au jet d'attaque et de confirmation) mais son bonus de dégâts reste réservé aux armes de mêlée (le contact ne gagne pas le +2X/+3X). +- Armes de jet à éclaboussures (SRD « Throw Splash Weapon », KB + `rules/prpg-core-rulebook/throw-splash-weapon.md` ; SRD « Alchemist's fire », + « Acid », « Holy water ») : une attaque dont l'`AttackSpec` porte un marqueur + `splash` (`{"damage": 1, "type": "fire"}` dans le JSON — Alchemist's Fire sur + `oni.json` et `tammara.json` : 1 point d'éclaboussure de feu) se lance en + attaque de contact à distance (incréments de portée ; la maîtrise est + automatique en RAW — le moteur ne modélise aucune pénalité de maîtrise). + **Touche directe** : la cible subit les dégâts indiqués (1d6 feu) et + **toute créature à moins de 5 pieds de la case d'impact** (case adjacente ou + occupée — créatures mourantes/inconscientes incluses, cadavres exclus) subit + l'éclaboussure plate, sans jet d'attaque ni sauvegarde ; la cible elle-même + ne subit **pas** d'éclaboussure supplémentaire (SRD : « A hit deals direct + hit damage to the target and splash damage to all creatures within 5 feet of + the target »). **Échec** (jet raté ou dissimulation) : la fiole **disperse** + — d8 de direction (1 = trop court, en ligne vers le lanceur ; 2 à 8 en + rotation horaire autour de la cible), puis un nombre de cases égal à + l'**incrément de portée du jet** (Alchemist's Fire : 10 pieds = 2 cases) ; + la case d'atterrissage **et ses cases adjacentes** subissent **seulement** + l'éclaboussure — la cible d'origine comprise si elle est dans la zone. + L'éclaboussure est un dégât **énergétique** : résistance/immunité + énergétiques s'appliquent, la **RD non** ; le montant plat n'est **jamais** + multiplié au critique, et une arme à éclaboussures ne peut pas porter de + dégâts de précision — SRD : « Splash weapons cannot deal precision-based + damage (such as sneak attack) ». Le d8 de dispersion n'est jeté **que** pour + une arme à éclaboussures : le flux RNG de toute autre attaque est + bit-identique. `AttackResult.splash` (additif) rapporte le total + d'éclaboussure infligé, distinct de `damage` (cible uniquement) ; le + snapshot d'observation expose `splash` (`{damage, type}` ou `None`) par + arme. Simplifications et hors périmètre documentés : le ciblage d'une + intersection de grille (CA 5) n'est pas modélisé ; l'éclaboussure asperge + les cases adjacentes sans vérification de ligne d'effet ; le point d'impact + d'une cible multi-cases est sa case d'ancrage (le choix RAW d'une case n'est + pas offert) ; les dégâts persistants d'Alchemist's Fire (1d6 feu au round + suivant, extinction par Réflexes DD 15) ne sont **pas** modélisés. - Jets de sauvegarde : `resolve_save(state, save_type, dc)` — fort/ref/will. 1 naturel = échec automatique, 20 naturel = succès automatique. Les modificateurs d'effets (`StatModifier` avec `target` correspondant) sont @@ -1816,6 +1857,7 @@ Règles modélisées : touch, flat_footed}, saves {fort, ref, will}, cmb, cmd, attacks (référentiel complet : id, name, kind, attack_bonus, damage, damage_bonus, crit_range, crit_mult, reach_ft, range_increment_ft, count, firearm, + splash ({damage, type} ou None — arme de jet à éclaboussures), hardness, hit_points (stats d'objet effectives, Table 7-12) — y compris les armes broken/destroyed/stolen), features (noms des capacités), feature_details (C4 : tuple de {name, category, source_detail, effects: diff --git a/src/pf1e_simulator/combat.py b/src/pf1e_simulator/combat.py index aed3e38..0f5461f 100644 --- a/src/pf1e_simulator/combat.py +++ b/src/pf1e_simulator/combat.py @@ -1932,6 +1932,11 @@ class CombatEngine: "range_increment_ft": attack.range_increment_ft, "count": attack.count, "firearm": attack.firearm, + "splash": ( + {"damage": attack.splash.damage, "type": attack.splash.type} + if attack.splash is not None + else None + ), "hardness": attack.object_stats()[0], "hit_points": attack.object_stats()[1], }