Files
fvtt-celestopol/module/models/npc.mjs
LeRatierBretonnier 54eacf6afc Fix formule de dés : 2d8 de base (règles Célestopol)
Correction majeure de la mécanique de jet selon les règles :

- Formule : 2d8 + Spécialisation + modificateurs (blessures/aspect/manual)
  (vs. ancienne formule erronée : Nd6 pool variable)
- Dé de la Lune : 1d8 narratif optionnel (résultat 1-8 → Triomphe /
  Brio / Contrecoup / Catastrophe) — pas un bonus numérique
- Destin : disponible uniquement jauge pleine (lvl=8), donne 3d8,
  vide la jauge entière après usage
- system.mjs : MOON_DIE_FACES (tableau 1-8) + MOON_RESULT_TYPES
- roll.mjs : logique complète réécrite (2d8, lune séparée, destin reset)
- character/npc.mjs : prefs.rollMoonDie + destGaugeFull
- roll-dialog.hbs : sans grille lune, checkbox dé lune, destin conditionnel
- chat-message.hbs : résultat dé lune narratif (phase + type + desc),
  dés .d8, suppression moonSymbol/moonBonus header
- roll.less : .form-moon-row, .moon-die-result avec couleurs Triomphe/
  Brio/Contrecoup/Catastrophe
- lang/fr.json : Moon.triomphe/brio/contrecoup/catastrophe + Full descs,
  Roll.rollMoonDie/destGaugeFull/destGaugeEmpty/baseDice

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-03-29 16:19:04 +02:00

100 lines
3.6 KiB
JavaScript

import { SYSTEM } from "../config/system.mjs"
export default class CelestopolNPC extends foundry.abstract.TypeDataModel {
static defineSchema() {
const fields = foundry.data.fields
const reqInt = { required: true, nullable: false, integer: true }
const schema = {}
schema.concept = new fields.StringField({ required: true, nullable: false, initial: "" })
schema.initiative = new fields.NumberField({ ...reqInt, initial: 0, min: 0 })
schema.anomaly = new fields.SchemaField({
type: new fields.StringField({ required: true, nullable: false, initial: "none",
choices: Object.keys(SYSTEM.ANOMALY_TYPES) }),
value: new fields.NumberField({ ...reqInt, initial: 0, min: 0, max: 8 }),
})
const skillField = (label) => new fields.SchemaField({
label: new fields.StringField({ required: true, initial: label }),
value: new fields.NumberField({ ...reqInt, initial: 0, min: 0, max: 8 }),
})
const statField = (statId) => {
const skills = SYSTEM.SKILLS[statId]
const skillSchema = {}
for (const [key, skill] of Object.entries(skills)) {
skillSchema[key] = skillField(skill.label)
}
return new fields.SchemaField({
label: new fields.StringField({ required: true, initial: SYSTEM.STATS[statId].label }),
res: new fields.NumberField({ ...reqInt, initial: 0, min: 0, max: 8 }),
actuel: new fields.NumberField({ ...reqInt, initial: 0, min: 0 }), // res + wound malus
...skillSchema,
})
}
schema.stats = new fields.SchemaField({
ame: statField("ame"),
corps: statField("corps"),
coeur: statField("coeur"),
esprit: statField("esprit"),
})
const woundField = (idx) => new fields.SchemaField({
checked: new fields.BooleanField({ required: true, initial: false }),
malus: new fields.NumberField({ ...reqInt, initial: SYSTEM.WOUND_LEVELS[idx]?.malus ?? 0 }),
})
schema.blessures = new fields.SchemaField({
lvl: new fields.NumberField({ ...reqInt, initial: 0, min: 0, max: 8 }),
b1: woundField(1), b2: woundField(2), b3: woundField(3), b4: woundField(4),
b5: woundField(5), b6: woundField(6), b7: woundField(7), b8: woundField(8),
})
schema.prefs = new fields.SchemaField({
rollMoonDie: new fields.BooleanField({ required: true, initial: false }),
difficulty: new fields.StringField({ required: true, nullable: false, initial: "normal" }),
})
schema.description = new fields.HTMLField({ required: true, textSearch: true })
schema.notes = new fields.HTMLField({ required: true, textSearch: true })
return schema
}
static LOCALIZATION_PREFIXES = ["CELESTOPOL.NPC"]
prepareDerivedData() {
super.prepareDerivedData()
const malus = this.getWoundMalus()
this.initiative = Math.max(0, this.stats.corps.res + malus)
for (const stat of Object.values(this.stats)) {
stat.actuel = Math.max(0, stat.res + malus)
}
}
getWoundMalus() {
const lvl = Math.max(0, Math.min(8, this.blessures.lvl))
return SYSTEM.WOUND_LEVELS[lvl]?.malus ?? 0
}
async roll(statId, skillId) {
const { CelestopolRoll } = await import("../documents/roll.mjs")
const skill = this.stats[statId][skillId]
if (!skill) return null
return CelestopolRoll.prompt({
actorId: this.parent.id,
actorName: this.parent.name,
actorImage: this.parent.img,
statId,
skillId,
skillLabel: skill.label,
skillValue: skill.value,
woundMalus: this.getWoundMalus(),
difficulty: this.prefs.difficulty,
rollMoonDie: this.prefs.rollMoonDie ?? false,
})
}
}