Files
vermine2047/module/models/npc.mjs
T
2026-06-06 22:37:29 +02:00

163 lines
5.3 KiB
JavaScript

/**
* DataModel pour les acteurs de type "npc" (PNJ).
* Étend foundry.abstract.TypeDataModel.
*
* Note : le champ libre de compétences (texte descriptif) est nommé "freeSkills"
* pour éviter le conflit avec le SchemaField "skills" qui contient les 30 compétences.
*/
import {
woundSchema,
combatStatusSchema,
equipmentSchema,
attributeSchema,
abilitiesSchema,
skillCategoriesSchema,
skillsSchema
} from "./_shared.mjs"
export default class VermineNpcData extends foundry.abstract.TypeDataModel {
/** @override */
static LOCALIZATION_PREFIXES = ["VERMINE.npc"]
/**
* Migration des données avant traitement par le schéma.
* Avant DataModel, template.json définissait "skills" comme un champ texte libre
* pour les PNJ. Le DataModel réserve "skills" pour les 30 compétences individuelles
* (SchemaField) et utilise "freeSkills" pour le texte libre.
* @param {Object} source Données brutes avant validation du schéma
* @returns {Object} Données migrées
*/
static migrateData(source) {
if (typeof source.skills === "string") {
source.freeSkills = source.skills
}
return super.migrateData(source)
}
/** @override */
static defineSchema() {
const fields = foundry.data.fields
return {
// Blessures (base)
minorWound: new fields.SchemaField(woundSchema(1, 5)),
majorWound: new fields.SchemaField(woundSchema(4, 4)),
deadlyWound: new fields.SchemaField(woundSchema(8, 2)),
// Statut de combat (base, difficulté par défaut 9 pour PNJ)
combatStatus: combatStatusSchema("9"),
// Identité
identity: new fields.SchemaField({
name: 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: "" }),
totem: new fields.StringField({ required: true, nullable: false, initial: "" }),
theme: new fields.StringField({ required: true, nullable: false, initial: "" }),
notes: new fields.HTMLField({ required: true, initial: "" })
}),
// Attributs (XP, réputation, sang-froid, effort)
attributes: new fields.SchemaField({
xp: attributeSchema(0, 0, 10),
reputation: attributeSchema(0, 0, 10),
self_control: attributeSchema(0, 0, 5),
effort: attributeSchema(0, 0, 5)
}),
// Niveaux PNJ (menace, expérience, rôle)
threat: attributeSchema(1, 1, 4),
experience: attributeSchema(1, 1, 4),
role: attributeSchema(1, 1, 4),
// Compétences (les 30 compétences individuelles)
skills: skillsSchema(),
// Description libre des compétences (champ texte PNJ)
freeSkills: new fields.StringField({ required: true, nullable: false, initial: "" }),
// Catégories de compétences
skill_categories: skillCategoriesSchema(),
// Caractéristiques (8)
abilities: abilitiesSchema(),
// Équipement
equipment: equipmentSchema()
}
}
/** @override */
prepareDerivedData() {
super.prepareDerivedData()
// 1. Calculer les seuils de blessures selon le niveau de menace
this._setNpcWoundThresholds()
// 2. Calculer les réserves selon le niveau de rôle
this._setNpcAttributes()
// 3. Définir les libellés des caractéristiques
this._setAbilityLabels()
// 4. Mettre à jour le statut de combat
this._updateCombatStatus()
}
/**
* Calcule les seuils de blessures à partir du niveau de menace.
* Utilise CONFIG.VERMINE.npcThreatLevels.
*/
_setNpcWoundThresholds() {
const health = this.abilities?.health?.value || 1
const threatLevel = this.threat?.value || 1
const threatConfig = CONFIG.VERMINE.npcThreatLevels[threatLevel] || {}
this.minorWound.threshold = threatConfig.minorWound || health
this.majorWound.threshold = threatConfig.majorWound || (health + 3)
this.deadlyWound.threshold = threatConfig.deadlyWound || (health + 7 < 11 ? health + 7 : 10)
this.minorWound.max = threatConfig.minorWound || 4
this.majorWound.max = threatConfig.majorWound || 3
this.deadlyWound.max = threatConfig.deadlyWound || 2
}
/**
* Définit les attributs dérivés (effort, sang-froid) selon le niveau de rôle.
* Utilise CONFIG.VERMINE.npcRoleLevels.
*/
_setNpcAttributes() {
const roleLevel = this.role?.value || 1
const roleConfig = CONFIG.VERMINE.npcRoleLevels[roleLevel] || {}
this.attributes.effort.max = roleConfig.pools || 0
this.attributes.self_control.max = roleConfig.reaction_bonus || 0
}
/**
* Définit les libellés localisés des caractéristiques.
*/
_setAbilityLabels() {
for (const [k, v] of Object.entries(this.abilities)) {
v.label = game.i18n.localize(CONFIG.VERMINE.abilities[k]) ?? k
}
}
/**
* Met à jour le label du statut de combat en fonction de la difficulté.
*/
_updateCombatStatus() {
const difficulty = parseInt(this.combatStatus.difficulty) || 9
let newLabel = "Passif"
switch (difficulty) {
case 5: newLabel = "Offensif"; break
case 7: newLabel = "Actif"; break
case 9: newLabel = "Passif"; break
}
if (this.combatStatus.label !== newLabel) {
this.combatStatus.label = newLabel
}
}
}