sotc-clash-arrow : flèches de clash entre combattants
This commit is contained in:
commit
ad63cafdc6
10 changed files with 2414 additions and 0 deletions
212
sotc-damage-wizard/module/main.js
Normal file
212
sotc-damage-wizard/module/main.js
Normal file
|
|
@ -0,0 +1,212 @@
|
|||
/**
|
||||
* 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.");
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue