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.npcType = new fields.StringField({ required: true, nullable: false, initial: "standard", choices: Object.keys(SYSTEM.NPC_TYPES) }) schema.concept = new fields.StringField({ required: true, nullable: false, initial: "" }) schema.faction = 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 }), }) // PNJs : 4 domaines uniquement (pas de sous-compétences) const domainField = (statId) => 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 }), }) schema.stats = new fields.SchemaField({ ame: domainField("ame"), corps: domainField("corps"), coeur: domainField("coeur"), esprit: domainField("esprit"), }) schema.blessures = new fields.SchemaField({ lvl: new fields.NumberField({ ...reqInt, initial: 0, min: 0, max: 8 }), }) schema.histoire = new fields.HTMLField({ required: true, textSearch: true }) schema.descriptionPhysique = 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() // Initiative PNJ : valeur du Domaine Corps (avec malus blessures) 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) } this.armorMalus = this.getArmorMalus() } getWoundMalus() { const lvl = Math.max(0, Math.min(8, this.blessures.lvl)) return SYSTEM.WOUND_LEVELS[lvl]?.malus ?? 0 } /** Somme des malus des armures équipées (valeur négative ou 0). */ getArmorMalus() { const armures = this.parent?.itemTypes?.armure ?? [] return armures .filter(a => a.system.equipped) .reduce((sum, a) => sum + (a.system.malus ? -Math.abs(a.system.malus) : 0), 0) } /** * Lance un jet sur un domaine (Âme/Corps/Cœur/Esprit). * Le label affiché tient compte du type de PNJ (standard vs antagoniste). */ async roll(statId) { const { CelestopolRoll } = await import("../documents/roll.mjs") const statData = this.stats[statId] if (!statData) return null const isAntagoniste = this.npcType === "antagoniste" const skillLabel = isAntagoniste ? SYSTEM.ANTAGONISTE_STATS[statId]?.label : SYSTEM.STATS[statId]?.label return CelestopolRoll.prompt({ actorId: this.parent.id, actorName: this.parent.name, actorImage: this.parent.img, statId, skillLabel, skillValue: statData.res, woundMalus: this.getWoundMalus(), armorMalus: this.getArmorMalus(), woundLevel: this.blessures.lvl, }) } /** Alias pour compatibilité avec le handler _onRoll (clic sans skillId). */ async rollResistance(statId) { return this.roll(statId) } }