/** * sotc-battle-show-ability — main.js * Point d'entrée du module. */ import { SpeedDiePanelApp } from "./SpeedDiePanelApp.js"; /* ------------------------------------------------------------------ */ /* Initialisation */ /* ------------------------------------------------------------------ */ Hooks.once("init", () => { console.log("sotc-battle-show-ability | Initialisation du module"); loadTemplates([ "modules/sotc-battle-show-ability/templates/speed-die-panel.hbs" ]); // Note : le helper Handlebars "eq" est déjà enregistré par le système SotC. }); /* ------------------------------------------------------------------ */ /* Menu contextuel dans le Combat Tracker */ /* ------------------------------------------------------------------ */ Hooks.on("getCombatTrackerEntryContext", (html, options) => { if (game.system.id !== "sotc") return; options.push({ name: game.i18n.localize("SOTC_BSA.Panel.Title"), icon: '', condition: (li) => { if (!game.combat) return false; const combatantId = li.data("combatant-id"); const combatant = game.combat.combatants.get(combatantId); if (!combatant) return false; return combatant.isOwner || game.user.isGM; }, callback: (li) => { const combatantId = li.data("combatant-id"); _openSpeedDiePanel(combatantId); } }); }); /* ------------------------------------------------------------------ */ /* Ouverture du panneau */ /* ------------------------------------------------------------------ */ function _openSpeedDiePanel(combatantId) { const existing = Object.values(ui.windows).find( w => w instanceof SpeedDiePanelApp && w.combatantId === combatantId ); if (existing) { existing.render(true); existing.bringToTop(); return; } const panel = new SpeedDiePanelApp(combatantId); const combatant = game.combat?.combatants.get(combatantId); if (combatant) { const numDice = combatant.actor?.system?.speed_dice?.num_dice ?? 1; // Index visuel : position dans le tri par initiative décroissante const sorted = game.combat.combatants .filter(c => c.tokenId === combatant.tokenId) .sort((a, b) => (b.initiative ?? -Infinity) - (a.initiative ?? -Infinity)); const dieIndex = sorted.findIndex(c => c.id === combatant.id) + 1; panel.options.title = numDice > 1 ? `${game.i18n.localize("SOTC_BSA.Panel.SpeedDie")} #${dieIndex} — ${combatant.name}` : `${game.i18n.localize("SOTC_BSA.Panel.Title")} — ${combatant.name}`; } panel.render(true); } /* ------------------------------------------------------------------ */ /* Clic droit sur les marqueurs PIXI de SIO */ /* ------------------------------------------------------------------ */ function _installSIORightClickHandler() { const board = document.getElementById("board"); if (!board) return; if (board._bsaRightClickHandler) { board.removeEventListener("mousedown", board._bsaRightClickHandler); } const handler = async (event) => { if (event.button !== 2) return; if (!game.combat?.active) return; const rect = board.getBoundingClientRect(); const screenX = event.clientX - rect.left; const screenY = event.clientY - rect.top; for (const token of canvas.tokens.placeables) { const markers = token._initiativeMarkers; if (!markers?.length) continue; for (let i = 0; i < markers.length; i++) { const m = markers[i]; const b = m.getBounds(); const inside = screenX >= b.x && screenX <= b.x + b.width && screenY >= b.y && screenY <= b.y + b.height; if (!inside) continue; const combatants = game.combat.combatants .filter(c => c.tokenId === token.document.id) .sort((a, b) => (b.initiative ?? -Infinity) - (a.initiative ?? -Infinity)); const combatant = combatants[i]; if (!combatant) return; if (!combatant.isOwner && !game.user.isGM) return; event.preventDefault(); event.stopPropagation(); _openSpeedDiePanel(combatant.id); return; } } }; board._bsaRightClickHandler = handler; board.addEventListener("mousedown", handler); if (!board._bsaContextMenuHandler) { board._bsaContextMenuHandler = (event) => { const rect = board.getBoundingClientRect(); const screenX = event.clientX - rect.left; const screenY = event.clientY - rect.top; for (const token of canvas.tokens.placeables) { const markers = token._initiativeMarkers; if (!markers?.length) continue; for (const m of markers) { const b = m.getBounds(); if (screenX >= b.x && screenX <= b.x + b.width && screenY >= b.y && screenY <= b.y + b.height) { event.preventDefault(); return; } } } }; board.addEventListener("contextmenu", board._bsaContextMenuHandler); } } Hooks.on("canvasReady", () => { _installSIORightClickHandler(); }); /* ------------------------------------------------------------------ */ /* Réinitialisation au début d'un nouveau round */ /* ------------------------------------------------------------------ */ async function _resetAllCombatants(combat) { if (!game.user.isGM) return; const updates = []; for (const combatant of combat.combatants) { const actor = combatant.actor; if (!actor?.system?.speed_dice) continue; updates.push({ _id: combatant.id, "flags.sotc-battle-show-ability.dieState": 0, "flags.sotc-battle-show-ability.assignedSkillId": null, "flags.sotc.used": false }); } if (updates.length > 0) { await combat.updateEmbeddedDocuments("Combatant", updates); console.log(`sotc-battle-show-ability | ${updates.length} dé(s) réinitialisé(s)`); } _refreshOpenPanels(); } Hooks.on("combatRound", async (combat, roundData, options) => { if (game.system.id !== "sotc") return; console.log("sotc-battle-show-ability | Nouveau round — réinitialisation des dés de vitesse"); await _resetAllCombatants(combat); }); Hooks.on("deleteCombat", async (combat, options, userId) => { // Fermer les panneaux ouverts for (const win of Object.values(ui.windows)) { if (win instanceof SpeedDiePanelApp) win.close(); } // Reset des flags (utile si le combat est supprimé sans être terminé) console.log("sotc-battle-show-ability | Fin du combat — réinitialisation des dés de vitesse"); await _resetAllCombatants(combat); }); /* ------------------------------------------------------------------ */ /* Rafraîchissement réactif des panneaux ouverts */ /* ------------------------------------------------------------------ */ function _refreshOpenPanels() { for (const win of Object.values(ui.windows)) { if (win instanceof SpeedDiePanelApp) { if (!win._checkStillValid()) continue; win.render(false); } } } Hooks.on("updateCombatant", (combatant, changes, options, userId) => { if (game.system.id !== "sotc") return; for (const win of Object.values(ui.windows)) { if (win instanceof SpeedDiePanelApp && win.combatantId === combatant.id) { win.render(false); break; } } }); Hooks.on("updateActor", (actor, changes, options, userId) => { if (game.system.id !== "sotc") return; for (const win of Object.values(ui.windows)) { if (win instanceof SpeedDiePanelApp && win.actor?.id === actor.id) { win.render(false); } } }); Hooks.on("deleteCombatant", (combatant, options, userId) => { for (const win of Object.values(ui.windows)) { if (win instanceof SpeedDiePanelApp && win.combatantId === combatant.id) { win.close(); break; } } }); /* ------------------------------------------------------------------ */ /* API publique */ /* ------------------------------------------------------------------ */ Hooks.once("ready", () => { game.modules.get("sotc-battle-show-ability").api = { openSpeedDiePanel: _openSpeedDiePanel, SpeedDiePanelApp }; console.log("sotc-battle-show-ability | Module prêt."); });