/** * sotc-damage-wizard | main.js * * Point d'entrée du module. * * Stratégie d'intégration SBSA : * On utilise le hook "renderApplication" pour détecter quand SBSA rend * son panneau et on injecte dynamiquement le bouton "⚔ Damage Wizard" * dans la section de la compétence assignée. * * Compatibilité sotc-clash-arrow : * clash-arrow écoute aussi renderApplication sur SBSA pour injecter * ses propres boutons debug. On utilise un attribut data pour éviter * les injections en double. * * Condition d'activation du bouton : * - dieState === 3 (compétence assignée) * - Une cible est détectable (ArrowStore ou game.user.targets) */ import { DamageWizardApp, _rollCache } from "./DamageWizardApp.js"; import { getTargetInfo } from "./ArrowBridge.js"; import { getAssignedSkill } from "./DamageEngine.js"; const SBSA_MODULE = "sotc-battle-show-ability"; /* ------------------------------------------------------------------ */ /* Init */ /* ------------------------------------------------------------------ */ Hooks.once("init", () => { console.log("sotc-damage-wizard | Initialisation"); loadTemplates([ "modules/sotc-damage-wizard/templates/damage-wizard.hbs" ]); // "eq" est déjà enregistré par le système SotC — on enregistre "gt" si absent if (!Handlebars.helpers["gt"]) { Handlebars.registerHelper("gt", (a, b) => a > b); } }); /* ------------------------------------------------------------------ */ /* Détection d'un panneau SBSA */ /* ------------------------------------------------------------------ */ /** * Retourne true si l'Application donnée est un SpeedDiePanelApp SBSA. * Trois méthodes de détection pour couvrir tous les cas. */ function _isSBSAPanel(app) { // 1. Instance directe via l'API publique SBSA const sbsaApi = game.modules.get(SBSA_MODULE)?.api; if (sbsaApi?.SpeedDiePanelApp && app instanceof sbsaApi.SpeedDiePanelApp) return true; // 2. L'id de l'Application FoundryVTT if (app.id === "sotc-bsa-panel") return true; // 3. Nom du constructeur (robuste si l'API n'est pas encore montée) if (app.constructor?.name === "SpeedDiePanelApp") return true; return false; } /* ------------------------------------------------------------------ */ /* Injection du bouton dans le panneau SBSA */ /* ------------------------------------------------------------------ */ Hooks.on("renderApplication", (app, html, data) => { if (!_isSBSAPanel(app)) return; _injectDamageButton(app, html); }); /** * Injecte le bouton "⚔ Damage Wizard" dans le panneau SBSA. * N'injecte que si une compétence est assignée (dieState === 3). * Utilise data-dw-injected pour éviter les doublons lors des re-renders. */ function _injectDamageButton(app, html) { const combatantId = app.combatantId; if (!combatantId) return; const combatant = game.combat?.combatants.get(combatantId); if (!combatant) return; // Vérifier qu'une compétence est assignée const skill = getAssignedSkill(combatant); if (!skill) return; // Retirer un ancien bouton si déjà présent (re-render propre) html.find(".dw-open-btn-wrapper").remove(); // Construire le bouton const $wrapper = $(`
`); // Insérer après la section compétence assignée, ou en haut du panneau const $assigned = html.find(".bsa-assigned-section"); if ($assigned.length) { $assigned.after($wrapper); } else { html.find(".sotc-bsa-panel").prepend($wrapper); } // Listener $wrapper.find(".dw-open-btn").on("click", ev => { ev.stopPropagation(); _openDamageWizard(ev.currentTarget.dataset.combatantId); }); } /* ------------------------------------------------------------------ */ /* Ouverture du DamageWizardApp */ /* ------------------------------------------------------------------ */ function _openDamageWizard(attackerCombatantId) { const attackerCombatant = game.combat?.combatants.get(attackerCombatantId); if (!attackerCombatant) { ui.notifications.warn("sotc-damage-wizard : Combatant introuvable."); return; } // Compétence assignée requise const skill = getAssignedSkill(attackerCombatant); if (!skill) { ui.notifications.warn(game.i18n.localize("SOTC_DW.NoSkill")); return; } // Cible via ArrowBridge (ArrowStore ou game.user.targets) const { defenderCombatant, isClash } = getTargetInfo(attackerCombatant); if (!defenderCombatant) { ui.notifications.warn(game.i18n.localize("SOTC_DW.NoTarget")); return; } // Réutiliser une fenêtre existante si possible const existing = Object.values(ui.windows).find( w => w instanceof DamageWizardApp && w.attackerCombatantId === attackerCombatantId && w.defenderCombatantId === defenderCombatant.id ); if (existing) { existing.render(true); existing.bringToTop(); return; } // Titre const title = isClash ? `${game.i18n.localize("SOTC_DW.TitleClash")} — ${attackerCombatant.name} ⚔ ${defenderCombatant.name}` : `${game.i18n.localize("SOTC_DW.Title")} — ${attackerCombatant.name} → ${defenderCombatant.name}`; const wizard = new DamageWizardApp( attackerCombatantId, defenderCombatant.id, isClash, { title } ); wizard.render(true); } /* ------------------------------------------------------------------ */ /* Réactivité : rafraîchir le bouton si le dé change d'état */ /* ------------------------------------------------------------------ */ Hooks.on("updateCombatant", (combatant, changes, options, userId) => { if (game.system.id !== "sotc") return; for (const win of Object.values(ui.windows)) { if (_isSBSAPanel(win) && win.combatantId === combatant.id) { win.render(false); break; } } }); /* ------------------------------------------------------------------ */ /* Nettoyage en fin de combat / nouveau round */ /* ------------------------------------------------------------------ */ Hooks.on("deleteCombat", () => { _rollCache.clear(); for (const win of Object.values(ui.windows)) { if (win instanceof DamageWizardApp) win.close(); } }); Hooks.on("combatRound", () => { _rollCache.clear(); }); /* ------------------------------------------------------------------ */ /* API publique */ /* ------------------------------------------------------------------ */ Hooks.once("ready", () => { game.modules.get("sotc-damage-wizard").api = { DamageWizardApp, openDamageWizard: _openDamageWizard }; console.log("sotc-damage-wizard | Module pret."); });