Files
vermine2047/module/models/creature.mjs
T
uberwald 9d36b92fa9 feat: nouveaux apprentissages (Capacités de Totem, Historique, Réputation) et expérience de Groupe
- Dialogue d'apprentissage personnage : 4 nouveaux types d'achat.
  - Capacité de Totem : options filtrées (mode de jeu, niveau de
    groupe, totem, non-apprises), difficulté = valeur d'Apprentissage,
    limites 3×N1 / 1×N2-N3 / max 5, marque system.learned de l'item
    (convention type character) et learned.ability du personnage.
  - Élément d'Historique : difficulté 9, handicap 1 si astérisque
    (background.asterisk), marque learned.background + item.
  - Test de Réputation (réduire/augmenter) : réduire = difficulté
    10 − score visé (−1 pt), augmenter = difficulté = réputation
    actuelle (+1 par succès), bornes 0-10, marque learned.reputation.
  - Évolution : option Dé de Totem Adapté ×2 quand Adapté > Humain
    (1D remplacé par un dé adapté compté ×2 via VermineUtils.roll).
- Correction règles : les Dés d'Expérience sont dépensés même en cas
  d'échec du jet d'apprentissage (décompte hors #applySuccess).
- Expérience de Groupe : nouveau dialogue d'apprentissage de Groupe
  (niveau, réserve, capacité de totem), réserve d'expérience et phase
  dans system.experience, boutons sur la fiche Groupe, reset des
  limites par phase (level/reserve/ability).
- Phase d'expérience : filtre par groupe sélectionné, associe chaque
  personnage à son premier groupe, reset des limites de tous les
  nouveaux types d'apprentissage.
- Corrections : l'attaque de créature n'ajoute plus la Réaction du Rôle
  (regles_creatures.txt), suppression de la case XP des en-têtes PJ
  (legacy et AppV2).
- Divers : .eslintrc.js (règles inexistantes/obsolètes retirées),
  libellé jaune de dialogue en couleur chitine, i18n FR/EN, chips
  « appris » sur l'onglet expérience du personnage.
2026-08-05 11:56:59 +02:00

208 lines
8.3 KiB
JavaScript

/**
* DataModel pour les acteurs de type "creature" (créature).
* Étend foundry.abstract.TypeDataModel.
*/
import {
woundSchema,
combatStatusSchema,
equipmentSchema,
attributeSchema
} from "./_shared.mjs"
export default class VermineCreatureData extends foundry.abstract.TypeDataModel {
/** @override */
static LOCALIZATION_PREFIXES = ["VERMINE.creature"]
/** @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)
combatStatus: combatStatusSchema(),
// 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: "" }),
biography: new fields.HTMLField({ required: true, initial: "" })
}),
// Compétences (description libre)
skills: new fields.StringField({ required: true, nullable: false, initial: "" }),
// Modes de jeu actifs
modes: new fields.SchemaField({
survival: new fields.BooleanField({ required: true, initial: true }),
nightmare: new fields.BooleanField({ required: true, initial: true }),
apocalypse: new fields.BooleanField({ required: true, initial: false })
}),
// Niveaux de créature (patron, taille, rôle, meute)
pattern: attributeSchema(1, 1, 4),
size: attributeSchema(0, 0, 3),
role: attributeSchema(1, 1, 4),
pack: attributeSchema(0, 0, 3),
// Valeurs calculées (dérivées de pattern/size/role/pack)
computed: new fields.SchemaField({
attack: new fields.NumberField({ required: true, nullable: false, integer: true, initial: 0 }),
damage: new fields.NumberField({ required: true, nullable: false, integer: true, initial: 0 }),
vigor: new fields.NumberField({ required: true, nullable: false, integer: true, initial: 0 }),
reaction: new fields.NumberField({ required: true, nullable: false, integer: true, initial: 0 }),
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 (attaque, dégâts, vigueur, etc.)
this._calculateCreatureComputedValues()
// 2. Calculer les seuils de blessures
this._calculateCreatureWoundThresholds()
// 3. Mettre à jour le statut de combat
this._updateCombatStatus()
}
/**
* Calcule les valeurs dérivées à partir des niveaux de patron, taille, rôle et meute.
* Utilise les configs CONFIG.VERMINE.creaturePatternLevels, .creatureSizeLevels,
* .creatureRoleLevels, .creaturePackLevels.
*
* Règles :
* - Attaque = pattern.attack + size.attack + pack.attack
* - Dégâts = pattern.damage + size.vigor + pack.damage
* - Vigueur = size.vigor + pack.damage
* - Réaction = role.reaction + role.reaction_bonus
*/
_calculateCreatureComputedValues() {
const patternLevel = this.pattern?.value || 1
const sizeLevel = this.size?.value || 0
const roleLevel = this.role?.value || 1
const packLevel = this.pack?.value || 0
const patternConfig = CONFIG.VERMINE.creaturePatternLevels[patternLevel] || {}
const roleConfig = CONFIG.VERMINE.creatureRoleLevels[roleLevel] || {}
const packConfig = CONFIG.VERMINE.creaturePackLevels[packLevel] || {}
// Les modificateurs de Taille se cumulent : une créature de Taille N applique
// les modificateurs de chaque Taille de 1 à N (Taille 0 = insecte, aucun).
const sizeLevels = CONFIG.VERMINE.creatureSizeLevels || {}
let sizeAttack = 0
let sizeVigor = 0
for (let i = 1; i <= sizeLevel; i++) {
const cfg = sizeLevels[i] || {}
sizeAttack += cfg.attack || 0
sizeVigor += cfg.vigor || 0
}
// Attaque : patron + taille + meute.
// NB : le Rôle ne donne que la Réaction (dés + réussites automatiques),
// il n'ajoute pas de dés d'attaque (regles_creatures.txt).
this.computed.attack = (patternConfig.attack || 0)
+ sizeAttack
+ (packConfig.attack || 0)
// Dégâts : patron + vigueur de taille + meute
this.computed.damage = (patternConfig.damage || 0)
+ sizeVigor
+ (packConfig.damage || 0)
// Vigueur : taille + meute
this.computed.vigor = sizeVigor + (packConfig.damage || 0)
// Réaction : rôle (le bonus de rôle est compté séparément et ajouté
// comme réussites automatiques par le NpcRollDialog — cf. modèle PNJ).
this.computed.reaction = roleConfig.reaction || 0
this.computed.reactionBonus = roleConfig.reaction_bonus || 0
// Réserves
this.computed.pools = roleConfig.pools || 0
// Équipement et handicap
this.computed.gear = roleConfig.gear || 9
this.computed.gearHindrance = roleConfig.gear_hindrance || 0
// Protection
this.computed.protection = roleConfig.protection || 1
}
/**
* Calcule les seuils et les cercles de blessures à partir du patron, de la
* taille et de la meute.
*
* Règles (regles_creatures.txt) :
* - Seuil = contribution du Gabarit + contributions de Taille (cumulées).
* Le Groupe n'ajoute PAS de seuil (tableau « Groupe » : cercles seuls).
* - Cercles = cercles du Gabarit + cercles de Taille (cumulés) + cercles du Groupe.
*/
_calculateCreatureWoundThresholds() {
const patternLevel = this.pattern?.value || 1
const sizeLevel = this.size?.value || 0
const packLevel = this.pack?.value || 0
const patternConfig = CONFIG.VERMINE.creaturePatternLevels[patternLevel] || {}
const packConfig = CONFIG.VERMINE.creaturePackLevels[packLevel] || {}
// Les modificateurs de Taille se cumulent (Taille N = somme des tailles 1..N).
const sizeLevels = CONFIG.VERMINE.creatureSizeLevels || {}
const sizeThresholds = { minor: 0, major: 0, deadly: 0 }
const sizeWounds = { minor: 0, major: 0, deadly: 0 }
for (let i = 1; i <= sizeLevel; i++) {
const cfg = sizeLevels[i] || {}
sizeThresholds.minor += cfg.minorThreshold || 0
sizeThresholds.major += cfg.majorThreshold || 0
sizeThresholds.deadly += cfg.deadlyThreshold || 0
sizeWounds.minor += cfg.minorWound || 0
sizeWounds.major += cfg.majorWound || 0
sizeWounds.deadly += cfg.deadlyWound || 0
}
// Seuils : gabarit + taille cumulée (pas de contribution du groupe).
this.minorWound.threshold = (patternConfig.minorThreshold ?? 0) + sizeThresholds.minor
this.majorWound.threshold = (patternConfig.majorThreshold ?? 0) + sizeThresholds.major
this.deadlyWound.threshold = (patternConfig.deadlyThreshold ?? 0) + sizeThresholds.deadly
// Cercles : gabarit + taille cumulée + groupe.
this.minorWound.max = (patternConfig.minorWound ?? 0) + sizeWounds.minor + (packConfig.minorWound ?? 0)
this.majorWound.max = (patternConfig.majorWound ?? 0) + sizeWounds.major + (packConfig.majorWound ?? 0)
this.deadlyWound.max = (patternConfig.deadlyWound ?? 0) + sizeWounds.deadly + (packConfig.deadlyWound ?? 0)
}
/**
* 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
}
}
}