/** * 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 + role.reaction * - 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 + réaction du rôle this.computed.attack = (patternConfig.attack || 0) + sizeAttack + (packConfig.attack || 0) + (roleConfig.reaction || 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 } } }