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({ rollMoonDie: new fields.BooleanField({ required: true, initial: false }), 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() // Calcul automatique de la valeur de chaque domaine = nombre de cases cochées for (const stat of Object.values(this.stats)) { for (const skill of Object.values(stat)) { if (typeof skill !== "object" || !("level1" in skill)) continue skill.value = [1,2,3,4,5,6,7,8].filter(i => skill[`level${i}`]).length } } // Calcul automatique de la valeur de chaque faction = nombre de cases cochées for (const faction of Object.values(this.factions)) { if (typeof faction !== "object" || !("level1" in faction)) continue faction.value = [1,2,3,4,5,6,7,8,9].filter(i => faction[`level${i}`]).length } // Initiative PJ : 4 + Mobilité (Corps) + Inspiration (Cœur) [après calcul des domaines] this.initiative = 4 + (this.stats.corps.mobilite?.value ?? 0) + (this.stats.coeur.inspiration?.value ?? 0) } /** * 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, difficulty: this.prefs.difficulty, rollMoonDie: this.prefs.rollMoonDie ?? false, destGaugeFull: this.destin.lvl >= 8, }) } }