/**
* 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.");
});