Files
fvtt-celestopol/module/models/character.mjs
LeRatierBretonnier ad85ecf4bf Refonte complète : dialogues de jet + messages de chat
- Nouveau layout roll-dialog.hbs : header, grille lune 3×3,
  sélecteur visibility, ligne Destin avec compteur disponible,
  preview formule améliorée
- Fix JS : data-moon-bonus sur chaque option, formule − si négatif
- Fix roll.mjs : passe destActuel au dialog, déduit automatiquement
  1 point de Destin (system.destin.lvl++) après chaque jet avec Destin
- character.mjs : passe destActuel = 8 - destin.lvl au prompt()
- chat-message.hbs : icônes résultat (✦✦/✖✖ pour critiques), label
  Formule, margin-badge right-aligned vs difficulté left-aligned
- roll.less : refonte complète couleurs (#0c4c0c/#e07b00), grille lune,
  destin row, visibility subtil, bandeau critiques améliorés
- lang/fr.json : clés Roll.visibility*, destinAvailable, destinNone,
  destinBonus, formula

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-03-29 00:34:27 +01:00

196 lines
8.2 KiB
JavaScript

import { SYSTEM } from "../config/system.mjs"
export default class CelestopolCharacter extends foundry.abstract.TypeDataModel {
static defineSchema() {
const fields = foundry.data.fields
const reqInt = { required: true, nullable: false, integer: true }
const schema = {}
// Concept du personnage
schema.concept = new fields.StringField({ required: true, nullable: false, initial: "" })
// Initiative (calculée mais stockée pour affichage)
schema.initiative = new fields.NumberField({ ...reqInt, initial: 0, min: 0 })
// Anomalie du personnage
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 }),
})
// Les 4 stats avec leurs domaines
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 }),
level1: new fields.BooleanField({ required: true, initial: false }),
level2: new fields.BooleanField({ required: true, initial: false }),
level3: new fields.BooleanField({ required: true, initial: false }),
level4: new fields.BooleanField({ required: true, initial: false }),
level5: new fields.BooleanField({ required: true, initial: false }),
level6: new fields.BooleanField({ required: true, initial: false }),
level7: new fields.BooleanField({ required: true, initial: false }),
level8: new fields.BooleanField({ required: true, initial: false }),
})
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 }),
...skillSchema,
})
}
schema.stats = new fields.SchemaField({
ame: statField("ame"),
corps: statField("corps"),
coeur: statField("coeur"),
esprit: statField("esprit"),
})
// Blessures (8 cases)
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),
})
// Destin (8 cases)
const destField = () => new fields.SchemaField({
checked: new fields.BooleanField({ required: true, initial: false }),
})
schema.destin = new fields.SchemaField({
lvl: new fields.NumberField({ ...reqInt, initial: 0, min: 0, max: 8 }),
d1: destField(), d2: destField(), d3: destField(), d4: destField(),
d5: destField(), d6: destField(), d7: destField(), d8: destField(),
})
// Spleen (8 cases)
schema.spleen = new fields.SchemaField({
lvl: new fields.NumberField({ ...reqInt, initial: 0, min: 0, max: 8 }),
s1: destField(), s2: destField(), s3: destField(), s4: destField(),
s5: destField(), s6: destField(), s7: destField(), s8: destField(),
})
// Attributs de personnage (Entregent, Fortune, Rêve, Vision)
const persoAttrField = () => new fields.SchemaField({
value: new fields.NumberField({ ...reqInt, initial: 0, min: 0 }),
max: new fields.NumberField({ ...reqInt, initial: 0, min: 0 }),
})
schema.attributs = new fields.SchemaField({
entregent: persoAttrField(),
fortune: persoAttrField(),
reve: persoAttrField(),
vision: persoAttrField(),
})
// Factions - 9 checkboxes per faction (like wound tracks)
const factionField = () => new fields.SchemaField({
value: new fields.NumberField({ ...reqInt, initial: 0 }),
level1: new fields.BooleanField({ required: true, initial: false }),
level2: new fields.BooleanField({ required: true, initial: false }),
level3: new fields.BooleanField({ required: true, initial: false }),
level4: new fields.BooleanField({ required: true, initial: false }),
level5: new fields.BooleanField({ required: true, initial: false }),
level6: new fields.BooleanField({ required: true, initial: false }),
level7: new fields.BooleanField({ required: true, initial: false }),
level8: new fields.BooleanField({ required: true, initial: false }),
level9: new fields.BooleanField({ required: true, initial: false }),
})
schema.factions = new fields.SchemaField({
pinkerton: factionField(),
police: factionField(),
okhrana: factionField(),
lunanovatek: factionField(),
oto: factionField(),
syndicats: factionField(),
vorovskoymir: factionField(),
cour: factionField(),
perso1: new fields.SchemaField({
label: new fields.StringField({ required: true, nullable: false, initial: "" }),
value: new fields.NumberField({ ...reqInt, initial: 0 }),
}),
perso2: new fields.SchemaField({
label: new fields.StringField({ required: true, nullable: false, initial: "" }),
value: new fields.NumberField({ ...reqInt, initial: 0 }),
}),
})
// Préférences de jet (mémorisé entre sessions)
schema.prefs = new fields.SchemaField({
moonPhase: new fields.StringField({ required: true, nullable: false, initial: "none" }),
difficulty: new fields.StringField({ required: true, nullable: false, initial: "normal" }),
})
// Description & notes
schema.description = new fields.HTMLField({ required: true, textSearch: true })
schema.notes = new fields.HTMLField({ required: true, textSearch: true })
// Données biographiques
schema.biodata = new fields.SchemaField({
age: new fields.StringField({ required: true, nullable: false, initial: "" }),
genre: new fields.StringField({ required: true, nullable: false, initial: "" }),
taille: new fields.StringField({ required: true, nullable: false, initial: "" }),
yeux: new fields.StringField({ required: true, nullable: false, initial: "" }),
naissance: new fields.StringField({ required: true, nullable: false, initial: "" }),
cheveux: new fields.StringField({ required: true, nullable: false, initial: "" }),
origine: new fields.StringField({ required: true, nullable: false, initial: "" }),
})
return schema
}
static LOCALIZATION_PREFIXES = ["CELESTOPOL.Character"]
prepareDerivedData() {
super.prepareDerivedData()
// L'initiative est basée sur la résistance Corps
this.initiative = this.stats.corps.res
}
/**
* Calcule le malus de blessures actif.
* @returns {number}
*/
getWoundMalus() {
const lvl = Math.max(0, Math.min(8, this.blessures.lvl))
return SYSTEM.WOUND_LEVELS[lvl]?.malus ?? 0
}
/**
* Lance les dés pour un domaine donné.
* @param {string} statId - Id de la stat (ame, corps, coeur, esprit)
* @param {string} skillId - Id du domaine
*/
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,
statLabel: SYSTEM.STATS[statId]?.label,
skillLabel: skill.label,
skillValue: skill.value,
woundMalus: this.getWoundMalus(),
woundLevel: this.blessures.lvl,
moonPhase: this.prefs.moonPhase,
difficulty: this.prefs.difficulty,
destActuel: Math.max(0, 8 - (this.destin.lvl ?? 0)),
})
}
}