diff --git a/modules/applications/sheets/base-actor-sheet.mjs b/modules/applications/sheets/base-actor-sheet.mjs index c5ca952..885214a 100644 --- a/modules/applications/sheets/base-actor-sheet.mjs +++ b/modules/applications/sheets/base-actor-sheet.mjs @@ -333,7 +333,7 @@ export default class MournbladeCYD2ActorSheetV2 extends HandlebarsApplicationMix // Créer les données par défaut pour un nouvel effet const defaultEffectData = { name: game.i18n.localize("MOURNBLADECYD2.EFFECT.new") || "Nouvel Effet", - icon: "systems/fvtt-mournblade-cyd-2-0/assets/icons/effect.webp", + icon: "systems/fvtt-mournblade-cyd-2-0/assets/icons/capacite.webp", description: "", changes: [], disabled: false, diff --git a/modules/applications/sheets/base-item-sheet.mjs b/modules/applications/sheets/base-item-sheet.mjs index 73afcfb..094d4f8 100644 --- a/modules/applications/sheets/base-item-sheet.mjs +++ b/modules/applications/sheets/base-item-sheet.mjs @@ -194,7 +194,7 @@ export default class MournbladeCYD2ItemSheetV2 extends HandlebarsApplicationMixi // Créer les données par défaut pour un nouvel effet const defaultEffectData = { name: game.i18n.localize("MOURNBLADECYD2.EFFECT.new") || "Nouvel Effet", - icon: "systems/fvtt-mournblade-cyd-2-0/assets/icons/effect.webp", + icon: "systems/fvtt-mournblade-cyd-2-0/assets/icons/capacite.webp", description: "", changes: [], disabled: false, diff --git a/modules/mournblade-cyd2-effects.js b/modules/mournblade-cyd2-effects.js index bba6b7b..c952399 100644 --- a/modules/mournblade-cyd2-effects.js +++ b/modules/mournblade-cyd2-effects.js @@ -23,6 +23,29 @@ export class MournbladeCYD2Effects { }); } + /** + * Parse une valeur d'effet en nombre + * Gère les strings comme "+2", "-3", "5" + * @param {string|number} value - Valeur à parser + * @returns {number} - Valeur numérique + * @private + */ + static _parseEffectValue(value) { + if (typeof value === 'number') return value; + if (typeof value !== 'string') return 0; + + const trimmed = value.trim(); + if (!trimmed) return 0; + + if (trimmed.startsWith('+')) { + return parseFloat(trimmed.substring(1)) || 0; + } else if (trimmed.startsWith('-')) { + return -(parseFloat(trimmed.substring(1)) || 0); + } + + return parseFloat(trimmed) || 0; + } + /** * Hook appelé lorsqu'un effet est appliqué * Permet de personnaliser le calcul des modifications @@ -30,10 +53,15 @@ export class MournbladeCYD2Effects { */ static _onApplyActiveEffect(effect, change, current, delta, changes) { // Pour Mournblade, nous voulons gérer les valeurs string (ex: "+1", "-2") - // Convertir en nombre si nécessaire - if (typeof current === "number" && typeof delta === "number") { - return current + delta; + // Convertir delta en nombre si nécessaire + const numericDelta = this._parseEffectValue(delta); + const numericCurrent = current != null ? Number(current) : 0; + + if (!isNaN(numericDelta) && !isNaN(numericCurrent)) { + return numericCurrent + numericDelta; } + + // Si on ne peut pas calculer, retourner delta tel quel return delta; } @@ -43,9 +71,14 @@ export class MournbladeCYD2Effects { */ static _onRemoveActiveEffect(effect, change, current, delta, changes) { // Logique inverse de l'application - if (typeof current === "number" && typeof delta === "number") { - return current - delta; + // Foundry gère déjà la suppression, ce hook est pour des calculs personnalisés + const numericDelta = this._parseEffectValue(delta); + const numericCurrent = current != null ? Number(current) : 0; + + if (!isNaN(numericDelta) && !isNaN(numericCurrent)) { + return numericCurrent - numericDelta; } + return current; } @@ -59,32 +92,48 @@ export class MournbladeCYD2Effects { * @param {string} attribute - Attribut cible (adr, pui, cla, pre, tre, vigueur, etc.) * @param {number|string} value - Valeur du bonus/malus * @param {object} options - Options supplémentaires - * @returns {Object} - Données de l'effet + * @returns {Object|null} - Données de l'effet ou null */ static createSimpleEffect(name, attribute, value, options = {}) { + // Validation des paramètres + if (!name || typeof name !== "string") { + console.warn("MournbladeCYD2 | Effect name must be a non-empty string"); + return null; + } + + if (value == null) { + console.warn("MournbladeCYD2 | Effect value cannot be null or undefined"); + return null; + } + const attributeKey = this.getAttributeKey(attribute); if (!attributeKey) { console.warn(`MournbladeCYD2 | Unknown attribute: ${attribute}`); return null; } + // Normaliser la valeur en string + const valueString = String(value).trim(); + return { - name: name, - icon: options.icon || "systems/fvtt-mournblade-cyd-2-0/assets/icons/effect.webp", - description: options.description || "", + name: name.trim(), + icon: options.icon || "systems/fvtt-mournblade-cyd-2-0/assets/icons/capacite.webp", + description: (options.description || "").trim(), changes: [ { key: attributeKey, mode: CONST.ActiveEffect.MODES.ADD, - value: value.toString(), - priority: options.priority || 0 + value: valueString, + priority: options.priority ?? 0 } ], - disabled: options.disabled || false, + disabled: Boolean(options.disabled), duration: options.duration || {}, origin: options.origin || null, tint: options.tint || "", - transfer: options.transfer !== false + transfer: options.transfer !== false, + statuses: options.statuses || [], + flags: options.flags || {} }; } @@ -128,7 +177,7 @@ export class MournbladeCYD2Effects { static createMultiEffect(name, changes, options = {}) { return { name: name, - icon: options.icon || "systems/fvtt-mournblade-cyd-2-0/assets/icons/effect.webp", + icon: options.icon || "systems/fvtt-mournblade-cyd-2-0/assets/icons/capacite.webp", description: options.description || "", changes: changes.map(c => ({ key: c.key, @@ -155,17 +204,26 @@ export class MournbladeCYD2Effects { * @returns {Promise} - L'effet créé ou null */ static async applyEffectToActor(actor, effectData) { - if (!actor || !actor.isOwner) return null; + if (!actor || !actor.canUserModify(game.user, "update")) return null; - const effect = effectData instanceof foundry.documents.ActiveEffect - ? effectData - : new CONFIG.ActiveEffect.documentClass(effectData); + let effect; + if (effectData instanceof foundry.documents.ActiveEffect) { + effect = effectData; + } else if (effectData?.toObject) { + effect = effectData; + } else { + effect = new CONFIG.ActiveEffect.documentClass(effectData); + } try { const createdEffects = await actor.createEmbeddedDocuments("ActiveEffect", [effect.toObject()]); return createdEffects[0]; } catch (error) { console.error("MournbladeCYD2 | Failed to apply effect:", error); + ui.notifications?.error( + game.i18n?.localize("MOURNBLADECYD2.EFFECT.applyError") || + `Erreur: Impossible d'appliquer l'effet (${error.message})` + ); return null; } } @@ -178,11 +236,13 @@ export class MournbladeCYD2Effects { */ static async applyItemEffectsToActor(item, actor) { if (!item?.effects?.length || !actor) return []; + if (!actor.canUserModify(game.user, "update")) return []; const effectsToApply = []; for (const effectData of item.effects) { - // Vérifier si l'effet doit être appliqué automatiquement - if (effectData.getFlag("mournblade-cyd2", "autoApply") !== false) { + // Par défaut, appliquer automatiquement SAUF si explicitement désactivé + const autoApply = effectData.getFlag("mournblade-cyd2", "autoApply"); + if (autoApply !== false) { effectsToApply.push({ ...effectData.toObject(), origin: item.uuid, @@ -193,8 +253,17 @@ export class MournbladeCYD2Effects { if (effectsToApply.length === 0) return []; - const createdEffects = await actor.createEmbeddedDocuments("ActiveEffect", effectsToApply); - return createdEffects; + try { + const createdEffects = await actor.createEmbeddedDocuments("ActiveEffect", effectsToApply); + return createdEffects; + } catch (error) { + console.error("MournbladeCYD2 | Failed to apply item effects:", error); + ui.notifications?.error( + game.i18n?.localize("MOURNBLADECYD2.EFFECT.applyItemError") || + `Erreur: Impossible d'appliquer les effets de l'item` + ); + return []; + } } /* -------------------------------------------- */ @@ -208,12 +277,13 @@ export class MournbladeCYD2Effects { * @returns {Promise} - L'effet désactivé */ static async disableEffect(owner, effectId) { + if (!owner?.canUserModify(game.user, "update")) return null; + const effect = owner.effects.get(effectId); - if (effect) { - await effect.update({ disabled: true }); - return effect; - } - return null; + if (!effect) return null; + + await effect.update({ disabled: true }); + return effect; } /** @@ -223,12 +293,13 @@ export class MournbladeCYD2Effects { * @returns {Promise} - L'effet activé */ static async enableEffect(owner, effectId) { + if (!owner?.canUserModify(game.user, "update")) return null; + const effect = owner.effects.get(effectId); - if (effect) { - await effect.update({ disabled: false }); - return effect; - } - return null; + if (!effect) return null; + + await effect.update({ disabled: false }); + return effect; } /** @@ -238,12 +309,13 @@ export class MournbladeCYD2Effects { * @returns {Promise} - L'effet togglé */ static async toggleEffect(owner, effectId) { + if (!owner?.canUserModify(game.user, "update")) return null; + const effect = owner.effects.get(effectId); - if (effect) { - await effect.update({ disabled: !effect.disabled }); - return effect; - } - return null; + if (!effect) return null; + + await effect.update({ disabled: !effect.disabled }); + return effect; } /** @@ -253,12 +325,13 @@ export class MournbladeCYD2Effects { * @returns {Promise} - L'effet supprimé */ static async deleteEffect(owner, effectId) { + if (!owner?.canUserModify(game.user, "delete")) return null; + const effect = owner.effects.get(effectId); - if (effect) { - await owner.deleteEmbeddedDocuments("ActiveEffect", [effectId]); - return effect; - } - return null; + if (!effect) return null; + + await owner.deleteEmbeddedDocuments("ActiveEffect", [effectId]); + return effect; } /* -------------------------------------------- */ @@ -271,10 +344,14 @@ export class MournbladeCYD2Effects { * @returns {string|null} - Clé complète ou null */ static getAttributeKey(attribute) { + if (!attribute) return null; + const config = game.system.mournbladecyd2?.config; if (!config?.effectAttributeKeys) return null; - return config.effectAttributeKeys[attribute] || null; + // Normaliser en minuscules pour correspondre à la config + const normalizedAttribute = attribute.toLowerCase().trim(); + return config.effectAttributeKeys[normalizedAttribute] || null; } /** @@ -593,16 +670,18 @@ export class MournbladeCYD2Effects { /** * Crée un effet d'adversité bleue * @param {number} value - Nombre d'adversités - * @returns {Object} - Données de l'effet + * @returns {Object|null} - Données de l'effet ou null */ static createAdversiteBleueEffect(value) { + if (value == null) return null; + return this.createSimpleEffect( `Adversité Bleue: +${value}`, "adversite.bleue", value, { icon: "systems/fvtt-mournblade-cyd-2-0/assets/icons/gemme_bleue.webp", - type: "temp", + duration: { type: "rounds", value: 1 }, statuses: ["adversite-bleue"] } ); @@ -611,16 +690,18 @@ export class MournbladeCYD2Effects { /** * Crée un effet d'adversité rouge * @param {number} value - Nombre d'adversités - * @returns {Object} - Données de l'effet + * @returns {Object|null} - Données de l'effet ou null */ static createAdversiteRougeEffect(value) { + if (value == null) return null; + return this.createSimpleEffect( `Adversité Rouge: +${value}`, "adversite.rouge", value, { icon: "systems/fvtt-mournblade-cyd-2-0/assets/icons/gemme_rouge.webp", - type: "temp", + duration: { type: "rounds", value: 1 }, statuses: ["adversite-rouge"] } ); @@ -629,16 +710,18 @@ export class MournbladeCYD2Effects { /** * Crée un effet d'adversité noire * @param {number} value - Nombre d'adversités - * @returns {Object} - Données de l'effet + * @returns {Object|null} - Données de l'effet ou null */ static createAdversiteNoireEffect(value) { + if (value == null) return null; + return this.createSimpleEffect( `Adversité Noire: +${value}`, "adversite.noire", value, { icon: "systems/fvtt-mournblade-cyd-2-0/assets/icons/gemme_noire.webp", - type: "temp", + duration: { type: "rounds", value: 1 }, statuses: ["adversite-noire"] } ); @@ -652,19 +735,24 @@ export class MournbladeCYD2Effects { * Crée un effet de Rune prononcée * @param {Object} rune - Données de la rune * @param {number} pointsAme - Points de pouvoir dépensés - * @returns {Object} - Données de l'effet + * @returns {Object|null} - Données de l'effet ou null */ static createRunePrononceeEffect(rune, pointsAme) { + if (!rune || !rune.name || pointsAme == null) return null; + + // Utiliser une icône par défaut si l'image de la rune est l'image par défaut + const icon = rune.img?.includes('/blank.png') || !rune.img + ? "systems/fvtt-mournblade-cyd-2-0/assets/icons/rune.webp" + : rune.img; + return { name: `Rune: ${rune.name} (Prononcée)`, - icon: rune.img || "systems/fvtt-mournblade-cyd-2-0/assets/icons/rune.webp", - description: rune.system.description || "", - changes: [ - // Ajouter ici les modifications spécifiques de la rune - ], + icon: icon, + description: rune.system?.description || "", + changes: [], // Les modifications spécifiques peuvent être ajoutées par les appels disabled: false, duration: { type: "rounds", value: Math.ceil(pointsAme / 3) }, - origin: rune.uuid, + origin: rune.uuid || null, tint: "#00ff00", transfer: true, flags: { @@ -681,17 +769,24 @@ export class MournbladeCYD2Effects { * Crée un effet de Rune tracée * @param {Object} rune - Données de la rune * @param {number} pointsAme - Points de pouvoir dépensés - * @returns {Object} - Données de l'effet + * @returns {Object|null} - Données de l'effet ou null */ static createRuneTraceeEffect(rune, pointsAme) { + if (!rune || !rune.name || pointsAme == null) return null; + + // Utiliser une icône par défaut si l'image de la rune est l'image par défaut + const icon = rune.img?.includes('/blank.png') || !rune.img + ? "systems/fvtt-mournblade-cyd-2-0/assets/icons/rune.webp" + : rune.img; + return { name: `Rune: ${rune.name} (Tracée)`, - icon: rune.img || "systems/fvtt-mournblade-cyd-2-0/assets/icons/rune.webp", - description: rune.system.description || "", - changes: [], + icon: icon, + description: rune.system?.description || "", + changes: [], // Les modifications spécifiques peuvent être ajoutées par les appels disabled: false, duration: { type: "rounds", value: Math.ceil(pointsAme / 3) * 2 }, - origin: rune.uuid, + origin: rune.uuid || null, tint: "#0000ff", transfer: true, flags: { @@ -707,6 +802,6 @@ export class MournbladeCYD2Effects { } // Initialisation automatique -table Hooks.once("init", () => { +Hooks.once("init", () => { MournbladeCYD2Effects.init(); }); diff --git a/templates/partial-active-effects.hbs b/templates/partial-active-effects.hbs index 6a1f4d4..cc61db1 100644 --- a/templates/partial-active-effects.hbs +++ b/templates/partial-active-effects.hbs @@ -27,7 +27,7 @@ {{#each actor.effects as |effect|}}
  • {{!-- Icône de l'effet --}} - + {{!-- Nom et description de l'effet --}}
    diff --git a/templates/partial-item-effects.hbs b/templates/partial-item-effects.hbs index 65e9c41..090ccb1 100644 --- a/templates/partial-item-effects.hbs +++ b/templates/partial-item-effects.hbs @@ -27,7 +27,7 @@ {{#each item.effects as |effect|}}
  • {{!-- Icône de l'effet --}} - + {{!-- Nom et description de l'effet --}}
    diff --git a/test-templates.js b/test-templates.js index eeb7bc4..26c7a71 100644 --- a/test-templates.js +++ b/test-templates.js @@ -135,6 +135,36 @@ if (!deprecatedFound) { console.log(' ✅ Aucun appel à ActiveEffectDialog (API dépréciée) trouvé'); } +// 6. Vérification des références à l'icône manquante effect.webp +console.log('\n6. Vérification des références à effect.webp (icône manquante) :'); +const effectWebpPattern = /effect\.webp/g; +const allJsFiles = [ + 'modules/applications/sheets/base-actor-sheet.mjs', + 'modules/applications/sheets/base-item-sheet.mjs', + 'modules/mournblade-cyd2-effects.js' +]; +const hbsFiles = [ + 'templates/partial-active-effects.hbs', + 'templates/partial-item-effects.hbs' +]; + +let effectWebpFound = false; +[...allJsFiles, ...hbsFiles].forEach(file => { + try { + const content = fs.readFileSync(path.join(__dirname, file), 'utf8'); + if (effectWebpPattern.test(content)) { + console.log(` ❌ Fichier ${file} contient une référence à effect.webp`); + effectWebpFound = true; + } + } catch (err) { + // Fichier introuvable, ignorer + } +}); + +if (!effectWebpFound) { + console.log(' ✅ Aucune référence à effect.webp (icône manquante) trouvée'); +} + // Résumé console.log('\n=== Résumé ==='); console.log(`Templates préchargés: ${loaded.length}`); @@ -142,8 +172,9 @@ console.log(`Partials utilisés: ${usedPartials.length}`); console.log(`Partials manquants: ${missingPartials.length}`); console.log(`Fichier JSON valide: ${errors.length === 0 ? 'Oui' : 'Non'}`); console.log(`API dépréciée utilisée: ${deprecatedFound ? 'Oui' : 'Non'}`); +console.log(`Référence à effect.webp: ${effectWebpFound ? 'Oui' : 'Non'}`); -if (errors.length === 0 && missingPartials.length === 0 && !deprecatedFound) { +if (errors.length === 0 && missingPartials.length === 0 && !deprecatedFound && !effectWebpFound) { console.log('\n✅ Toutes les vérifications ont réussi !'); process.exit(0); } else {