/** * DataModel pour les acteurs de type "community" (Communauté). * La Communauté est l'extension sédentarisée d'un Groupe : elle englobe le * Groupe de personnages, des Figures et des Habitants répartis en Compagnies. * Le socle couvre l'identité, la géographie, l'histoire, les objectifs, la * taille/l'effectif, les quatre Domaines (8 jauges), le Moral et les réserves. */ import { attributeSchema, levelSchema, combatStatusSchema } from "./_shared.mjs" export default class VermineCommunityData extends foundry.abstract.TypeDataModel { /** @override */ static LOCALIZATION_PREFIXES = ["VERMINE.community"] /** @override */ static defineSchema() { const fields = foundry.data.fields const reqInt = { required: true, nullable: false, integer: true } const gauge = () => attributeSchema(0, 0, 10) return { // Statut de combat (base, peu utilisé pour une Communauté) combatStatus: combatStatusSchema("9"), // Identité identity: new fields.SchemaField({ totem: new fields.StringField({ required: true, nullable: false, initial: "" }), profile: new fields.StringField({ required: true, nullable: false, initial: "" }), origin: new fields.StringField({ required: true, nullable: false, initial: "" }), theme: new fields.StringField({ required: true, nullable: false, initial: "" }), instincts: new fields.StringField({ required: true, nullable: false, initial: "" }), prohibits: new fields.StringField({ required: true, nullable: false, initial: "" }), // Âge de la Communauté : recent | established | old age: new fields.StringField({ required: true, nullable: false, initial: "recent" }), notes: new fields.HTMLField({ required: true, initial: "", textSearch: true }) }), // Géographie / localisation location: new fields.SchemaField({ sector: new fields.StringField({ required: true, nullable: false, initial: "" }), placeType: new fields.StringField({ required: true, nullable: false, initial: "" }), landmarks: new fields.HTMLField({ required: true, initial: "", textSearch: true }), territory: new fields.HTMLField({ required: true, initial: "", textSearch: true }) }), // Histoire history: new fields.SchemaField({ founding: new fields.StringField({ required: true, nullable: false, initial: "" }), events: new fields.HTMLField({ required: true, initial: "", textSearch: true }), relations: new fields.HTMLField({ required: true, initial: "", textSearch: true }) }), // Objectifs : un majeur + des mineurs objectives: new fields.SchemaField({ major: new fields.StringField({ required: true, nullable: false, initial: "" }), minor: new fields.ArrayField(new fields.StringField({ required: true, nullable: false, initial: "" })) }), // Taille (1-3) et effectif size: attributeSchema(1, 1, 3), population: new fields.NumberField({ ...reqInt, initial: 0, min: 0 }), // Domaines : 4 Domaines × 2 sections (jauges en Dés) domains: new fields.SchemaField({ resources: new fields.SchemaField({ vivre: gauge(), supplies: gauge() }), security: new fields.SchemaField({ defense: gauge(), care: gauge() }), dynamism: new fields.SchemaField({ cohesion: gauge(), productivity: gauge() }), relations: new fields.SchemaField({ human: gauge(), ecology: gauge() }) }), // Moral (comme le Groupe) : value 0-7, level dérivé morale: new fields.SchemaField({ level: new fields.StringField({ required: true, nullable: false, initial: "normal" }), value: new fields.NumberField({ ...reqInt, initial: 7, min: 0, max: 7 }), min: new fields.NumberField({ ...reqInt, initial: 0, min: 0 }), max: new fields.NumberField({ ...reqInt, initial: 7, min: 0 }) }), // Réserve de Groupe reserve: attributeSchema(0, 0, 10), // Dés collectifs de la Communauté material: new fields.NumberField({ ...reqInt, initial: 0, min: 0 }), experience: new fields.SchemaField({ dice: new fields.NumberField({ ...reqInt, initial: 0, min: 0 }) }), // Niveau de la Communauté (pour l'expérience des habitants) level: levelSchema(1, 1, 10), // Groupe de personnages rattaché (id d'acteur) groupId: new fields.StringField({ required: true, nullable: false, initial: "" }), // Habitants (phase 2) : Figures et Compagnies figures: new fields.ArrayField(new fields.SchemaField({ name: new fields.StringField({ required: true, nullable: false, initial: "" }), role: new fields.StringField({ required: true, nullable: false, initial: "" }), totem: new fields.StringField({ required: true, nullable: false, initial: "" }), specialty: new fields.SchemaField({ name: new fields.StringField({ required: true, nullable: false, initial: "" }), score: new fields.NumberField({ ...reqInt, initial: 3, min: 0, max: 10 }) }) })), companies: new fields.ArrayField(new fields.SchemaField({ name: new fields.StringField({ required: true, nullable: false, initial: "" }), specialty: new fields.SchemaField({ name: new fields.StringField({ required: true, nullable: false, initial: "" }), score: new fields.NumberField({ ...reqInt, initial: 3, min: 0, max: 10 }) }), level: levelSchema(1, 1, 4), members: new fields.NumberField({ ...reqInt, initial: 1, min: 0 }), figureName: new fields.StringField({ required: true, nullable: false, initial: "" }) })) } } /** @override */ prepareDerivedData() { super.prepareDerivedData() // 1. État (Crise/Bas/Stable/Opulent) de chaque jauge de Domaine this._computeGaugeStates() // 2. Les quatre Domaines sont-ils Bas ? (Moral inactif) this._computeAllDomainsLow() // 3. Mettre à jour le niveau de Moral selon la valeur this._updateMorale() } /** * État d'une jauge selon sa valeur en Dés. * Crise 0D / Bas 1-2D / Stable 3-5D / Opulent 6D+. * @param {number} value * @returns {string} */ static gaugeState(value) { const v = parseInt(value, 10) || 0 if (v >= 6) return "opulent" if (v >= 3) return "stable" if (v >= 1) return "low" return "crisis" } _computeGaugeStates() { for (const domain of Object.values(this.domains)) { for (const gauge of Object.values(domain)) { gauge.state = VermineCommunityData.gaugeState(gauge.value) } } } /** * Un Domaine est "Bas" lorsque ses deux sections sont à 2D ou moins. * Si les quatre Domaines sont Bas, le Bonus de Moral est inactif. */ _computeAllDomainsLow() { const domainLow = (domain) => Object.values(domain).every(g => (parseInt(g.value, 10) || 0) <= 2) this.allDomainsLow = Object.values(this.domains).every(domainLow) } /** * Met à jour le niveau de Moral en fonction de la valeur de dés. * Règles : 7D = Haut, 6-3D = Normal, 2-1D = Bas, 0D = Crise. */ _updateMorale() { const moraleValue = parseInt(this.morale?.value, 10) || 0 if (this.morale.level && this.morale.level !== "normal") return if (moraleValue >= 7) { this.morale.level = "high" } else if (moraleValue >= 3) { this.morale.level = "normal" } else if (moraleValue >= 1) { this.morale.level = "low" } else { this.morale.level = "crisis" } } }