212 lines
6.9 KiB
JavaScript
212 lines
6.9 KiB
JavaScript
/**
|
|
* 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 = $(`
|
|
<div class="dw-open-btn-wrapper" data-dw-injected="true"
|
|
style="padding: 4px 8px;">
|
|
<button class="dw-open-btn" data-combatant-id="${combatantId}"
|
|
title="${game.i18n.localize('SOTC_DW.Title')}">
|
|
⚔ ${game.i18n.localize("SOTC_DW.Title")}
|
|
</button>
|
|
</div>
|
|
`);
|
|
|
|
// 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.");
|
|
});
|