/** * Helpers partagés pour les Traits (regles/regles_traits.txt). * * Centralise la lecture des Traits sur les items (présence, valeur) et les * calculs liés aux objets équipés d'un acteur (Lourd, Mobilité, Fétiche…). * Consommé par les dialogues de jet (combat, action) et les cartes de chat. */ export class VermineTraits { /** * Présence d'un Trait sur un item. * @param {Item|null} item * @param {string} key clé du Trait (ex. "lourd") * @returns {boolean} */ static hasTrait(item, key) { return Boolean(item?.system?.traits?.[key]); } /** * Valeur (n) d'un Trait sur un item (0 si absent ou sans valeur). * @param {Item|null} item * @param {string} key clé du Trait * @returns {number} */ static traitValue(item, key) { if (!item?.system?.traits?.[key]) return 0; return parseInt(item.system.traits[key]?.value ?? 0, 10) || 0; } /** * Items équipés d'un acteur (armes + protections + objets génériques) * possédant un Trait donné. * @param {Actor} actor * @param {string} traitKey * @returns {Item[]} */ static equippedItems(actor, traitKey) { if (!actor?.itemTypes) return []; return [...actor.itemTypes.weapon || [], ...actor.itemTypes.defense || [], ...actor.itemTypes.item || []] .filter(i => i.system?.equipped && this.hasTrait(i, traitKey)); } /** * Vigueur de l'acteur : caractéristique pour le personnage, tables pour * PNJ (Menace) et créature (computed). * @param {Actor} actor * @returns {number} */ static vigor(actor) { if (!actor?.system) return 0; if (actor.type === 'npc') { const threat = actor.system?.threat?.value || 1; return CONFIG.VERMINE.npcThreatLevels?.[threat]?.vigor || 0; } if (actor.type === 'creature') return actor.system?.computed?.vigor || 0; return actor.system?.abilities?.vigor?.value || 0; } /** * Malus cumulés des objets Lourd (n) équipés dont la Vigueur du porteur * est inférieure à (n). Les Malus se cumulent entre objets (règles p. …). * @param {Actor} actor * @returns {number} */ static lourdMalus(actor) { const vigor = this.vigor(actor); return this.equippedItems(actor, 'lourd').reduce((sum, item) => { const n = this.traitValue(item, 'lourd'); return sum + (n > 0 && vigor < n ? 1 : 0); }, 0); } /** * Handicap de Mobilité total : somme des handicaps des protections équipées * qui ne sont pas Maniables (le Trait Maniable annule le Handicap). * @param {Actor} actor * @returns {number} */ static mobilityHandicap(actor) { const defenses = (actor?.itemTypes?.defense || []).filter(d => d.system?.equipped); return defenses.reduce((sum, d) => sum + (this.hasTrait(d, 'maniable') ? 0 : (parseInt(d.system?.mobility ?? 0, 10) || 0)), 0); } /** * Note de rappel sur la Mobilité (protections équipées non Maniables), * ou null si aucun Handicap de Mobilité. * @param {Actor} actor * @returns {string|null} */ static mobilityNote(actor) { const handicap = this.mobilityHandicap(actor); if (handicap <= 0) return null; return game.i18n.format('VERMINE.traits_mobility_note', { handicap }); } /** * Premier item équipé possédant le Trait Fétiche, ou null. * @param {Actor} actor * @returns {Item|null} */ static feticheItem(actor) { return this.equippedItems(actor, 'fetiche')[0] ?? null; } /** * Nombre d'items équipés avec le Trait Fétiche. * @param {Actor} actor * @returns {number} */ static feticheCount(actor) { return this.equippedItems(actor, 'fetiche').length; } }