176 lines
7.1 KiB
JavaScript
176 lines
7.1 KiB
JavaScript
/**
|
|
* DataModel pour les acteurs de type "npc" (PNJ).
|
|
* Étend foundry.abstract.TypeDataModel.
|
|
*
|
|
* Conforme aux règles des PNJ (regles/regles_pnj.txt) : un PNJ n'a ni
|
|
* caractéristiques chiffrées, ni réserves détaillées, ni liste de compétences.
|
|
* Il est défini uniquement par son Profil (identité) et trois critères :
|
|
* Menace, Expérience et Rôle. Toutes les valeurs utiles en jeu sont dérivées
|
|
* des tableaux de référence (CONFIG.VERMINE.npc*Levels) et exposées dans
|
|
* "system.computed".
|
|
*
|
|
* Note : le champ libre de description des Spécialités est nommé "freeSkills"
|
|
* pour éviter le conflit avec le champ "skills" que le template.json historique
|
|
* définissait comme texte libre pour les PNJ.
|
|
*/
|
|
import {
|
|
woundSchema,
|
|
combatStatusSchema,
|
|
equipmentSchema,
|
|
attributeSchema
|
|
} 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 utilise "freeSkills" pour cette description.
|
|
* @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é (Profil du PNJ)
|
|
identity: new fields.SchemaField({
|
|
name: new fields.StringField({ required: true, nullable: false, initial: "" }),
|
|
age: 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: "" })
|
|
}),
|
|
|
|
// Critères de définition du PNJ (Menace, Expérience, Rôle)
|
|
threat: attributeSchema(1, 1, 4),
|
|
experience: attributeSchema(1, 1, 4),
|
|
role: attributeSchema(1, 1, 4),
|
|
|
|
// Description libre des Spécialités (champ texte PNJ)
|
|
freeSkills: new fields.StringField({ required: true, nullable: false, initial: "" }),
|
|
|
|
// Valeurs calculées (dérivées des niveaux de menace / expérience / rôle)
|
|
computed: new fields.SchemaField({
|
|
attack: new fields.NumberField({ required: true, nullable: false, integer: true, initial: 0 }),
|
|
vigor: new fields.NumberField({ required: true, nullable: false, integer: true, initial: 0 }),
|
|
action: new fields.NumberField({ required: true, nullable: false, integer: true, initial: 3 }),
|
|
specialties: new fields.NumberField({ required: true, nullable: false, integer: true, initial: 4 }),
|
|
rerolls: new fields.NumberField({ required: true, nullable: false, integer: true, initial: 0 }),
|
|
contact: new fields.StringField({ required: true, nullable: false, initial: "7" }),
|
|
reaction: new fields.NumberField({ required: true, nullable: false, integer: true, initial: 3 }),
|
|
reactionBonus: new fields.NumberField({ required: true, nullable: false, integer: true, initial: 0 }),
|
|
pools: new fields.NumberField({ required: true, nullable: false, integer: true, initial: 0 }),
|
|
gear: new fields.NumberField({ required: true, nullable: false, integer: true, initial: 9 }),
|
|
gearHindrance: new fields.NumberField({ required: true, nullable: false, integer: true, initial: 0 }),
|
|
protection: new fields.NumberField({ required: true, nullable: false, integer: true, initial: 1 })
|
|
}),
|
|
|
|
// Équipement
|
|
equipment: equipmentSchema()
|
|
}
|
|
}
|
|
|
|
/** @override */
|
|
prepareDerivedData() {
|
|
super.prepareDerivedData()
|
|
|
|
// 1. Calculer les valeurs dérivées selon les niveaux de menace/expérience/rôle
|
|
this._setNpcComputedValues()
|
|
|
|
// 2. Calculer les seuils de blessures selon le niveau de menace
|
|
this._setNpcWoundThresholds()
|
|
|
|
// 3. Mettre à jour le statut de combat
|
|
this._updateCombatStatus()
|
|
}
|
|
|
|
/**
|
|
* Calcule les valeurs dérivées à partir des niveaux de Menace, Expérience
|
|
* et Rôle. Utilise CONFIG.VERMINE.npcThreatLevels, .npcExperienceLevels
|
|
* et .npcRoleLevels.
|
|
*/
|
|
_setNpcComputedValues() {
|
|
const threatLevel = this.threat?.value || 1
|
|
const expLevel = this.experience?.value || 1
|
|
const roleLevel = this.role?.value || 1
|
|
|
|
const threatConfig = CONFIG.VERMINE.npcThreatLevels[threatLevel] || {}
|
|
const expConfig = CONFIG.VERMINE.npcExperienceLevels[expLevel] || {}
|
|
const roleConfig = CONFIG.VERMINE.npcRoleLevels[roleLevel] || {}
|
|
|
|
// Menace
|
|
this.computed.attack = threatConfig.attack || 0
|
|
this.computed.vigor = threatConfig.vigor || 0
|
|
|
|
// Expérience
|
|
this.computed.action = expConfig.action || 3
|
|
this.computed.specialties = expConfig.specialties || 4
|
|
this.computed.rerolls = expConfig.rerolls || 0
|
|
this.computed.contact = expConfig.contact || "7"
|
|
|
|
// Rôle
|
|
this.computed.reaction = roleConfig.reaction || 0
|
|
this.computed.reactionBonus = roleConfig.reaction_bonus || 0
|
|
this.computed.pools = roleConfig.pools || 0
|
|
this.computed.gear = roleConfig.gear || 9
|
|
this.computed.gearHindrance = roleConfig.gear_hindrance || 0
|
|
this.computed.protection = roleConfig.protection || 1
|
|
}
|
|
|
|
/**
|
|
* Calcule les seuils et le nombre de boîtes de blessures à partir du
|
|
* niveau de menace (tableau "Niveau de Menace"). Utilise
|
|
* CONFIG.VERMINE.npcThreatLevels.
|
|
*/
|
|
_setNpcWoundThresholds() {
|
|
const threatLevel = this.threat?.value || 1
|
|
const threatConfig = CONFIG.VERMINE.npcThreatLevels[threatLevel] || {}
|
|
|
|
this.minorWound.threshold = threatConfig.minorThreshold ?? 1
|
|
this.majorWound.threshold = threatConfig.majorThreshold ?? 4
|
|
this.deadlyWound.threshold = threatConfig.deadlyThreshold ?? 6
|
|
|
|
this.minorWound.max = threatConfig.minorWound || 1
|
|
this.majorWound.max = threatConfig.majorWound || 1
|
|
this.deadlyWound.max = threatConfig.deadlyWound || 1
|
|
}
|
|
|
|
/**
|
|
* 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
|
|
}
|
|
}
|
|
}
|