feat: fiche PNJ (profil + niveaux) et passage du véhicule en acteur
Release Creation / build (release) Failing after 1m17s

This commit is contained in:
2026-08-04 20:34:20 +02:00
parent ea3322e843
commit cf905cdcfb
36 changed files with 1288 additions and 707 deletions
+75 -62
View File
@@ -2,17 +2,22 @@
* 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.
* 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,
abilitiesSchema,
skillCategoriesSchema,
skillsSchema
attributeSchema
} from "./_shared.mjs"
export default class VermineNpcData extends foundry.abstract.TypeDataModel {
@@ -23,8 +28,7 @@ export default class VermineNpcData extends foundry.abstract.TypeDataModel {
/**
* 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.
* 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
*/
@@ -48,9 +52,10 @@ export default class VermineNpcData extends foundry.abstract.TypeDataModel {
// Statut de combat (base, difficulté par défaut 9 pour PNJ)
combatStatus: combatStatusSchema("9"),
// Identité
// 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: "" }),
@@ -58,30 +63,29 @@ export default class VermineNpcData extends foundry.abstract.TypeDataModel {
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)
// 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),
// Compétences (les 30 compétences individuelles)
skills: skillsSchema(),
// Description libre des compétences (champ texte PNJ)
// Description libre des Spécialités (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(),
// 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()
@@ -92,58 +96,67 @@ export default class VermineNpcData extends foundry.abstract.TypeDataModel {
prepareDerivedData() {
super.prepareDerivedData()
// 1. Calculer les seuils de blessures selon le niveau de menace
// 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()
// 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
// 3. Mettre à jour le statut de combat
this._updateCombatStatus()
}
/**
* Calcule les seuils de blessures à partir du niveau de menace.
* Utilise CONFIG.VERMINE.npcThreatLevels.
* 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 health = this.abilities?.health?.value || 1
const threatLevel = this.threat?.value || 1
const threatConfig = CONFIG.VERMINE.npcThreatLevels[threatLevel] || {}
this.minorWound.threshold = health
this.majorWound.threshold = health + 3
this.deadlyWound.threshold = Math.min(health + 7, 10)
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
}
/**
* 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é.
*/
+34 -10
View File
@@ -1,43 +1,67 @@
import { baseItemSchema } from "./_shared.mjs"
/**
* DataModel pour les items de type "vehicle" (véhicules et montures).
* Caractéristiques : Rareté, Fiabilité, Distance, Vitesse (croisière/pointe),
* Entretien (compétences) et Réserve d'Effort (montures).
* @augments {foundry.abstract.TypeDataModel}
* DataModel pour les acteurs de type "vehicle" (véhicules et montures).
* Un véhicule est désormais un acteur (et non un objet) : il possède sa propre
* fiche, peut être posé sur la scène comme un jeton et appartient à un groupe
* ou un personnage via `ownerId`.
*
* Caractéristiques (regles_vehicules.txt) : Rareté, Fiabilité (véhicules),
* Réserve d'Effort (montures), Distance, Vitesse (croisière/pointe) et Entretien.
* Pas de statistiques de combat (les règles ne définissent pas de points de vie).
*/
import { raritySchema } from "./_shared.mjs"
export default class VermineVehicleData extends foundry.abstract.TypeDataModel {
/** @override */
static LOCALIZATION_PREFIXES = ["VERMINE.item.vehicle"]
static LOCALIZATION_PREFIXES = ["VERMINE.vehicle"]
/** @override */
static defineSchema() {
const fields = foundry.data.fields
const reqInt = { required: true, nullable: false, integer: true }
return {
...baseItemSchema(),
// Identité
identity: new fields.SchemaField({
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: "" }),
notes: new fields.HTMLField({ required: true, initial: "", textSearch: true })
}),
// Rareté (valeur 1-10 + handicap)
rarity: raritySchema(),
// Fiabilité étendue (1-10) pour les véhicules
reliability: new fields.NumberField({ ...reqInt, initial: 5, min: 1, max: 10 }),
// Type : "vehicle" (véhicule) ou "mount" (monture)
kind: new fields.StringField({ required: true, nullable: false, initial: "vehicle" }),
// Distance : km parcourus avec un "plein"
distance: new fields.NumberField({ ...reqInt, initial: 0, min: 0 }),
// Vitesse : croisière / pointe (durée de la pointe)
speed: new fields.SchemaField({
cruise: new fields.NumberField({ ...reqInt, initial: 0, min: 0 }),
top: new fields.NumberField({ ...reqInt, initial: 0, min: 0 }),
topDuration: new fields.StringField({ required: true, nullable: false, initial: "10 min" })
}),
// Entretien : compétences nécessaires (clés de CONFIG.VERMINE.skills)
maintenance: new fields.ArrayField(new fields.StringField({ required: true, nullable: false, initial: "" })),
// Réserve d'Effort (montures)
effortReserve: new fields.SchemaField({
value: new fields.NumberField({ ...reqInt, initial: 0, min: 0 }),
min: new fields.NumberField({ ...reqInt, initial: 0, min: 0 }),
max: new fields.NumberField({ ...reqInt, initial: 0, min: 0 })
}),
// Mobilité (héritée)
mobility: new fields.NumberField({ ...reqInt, initial: 3, min: 0 })
// Mobilité
mobility: new fields.NumberField({ ...reqInt, initial: 3, min: 0 }),
// Propriétaire (id d'acteur : groupe ou personnage)
ownerId: new fields.StringField({ required: true, nullable: false, initial: "" })
}
}
}