163 lines
5.3 KiB
JavaScript
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
|
|
}
|
|
}
|
|
}
|