sotc-foundry-suite/sotc-battle-show-ability/module/main.js

263 lines
8.3 KiB
JavaScript

/**
* 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: '<i class="fas fa-dice-d6"></i>',
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.");
});