/** * sotc-clash-arrow | Hooks.js * * Enregistrement de tous les hooks FoundryVTT : * - canvasReady : initialisation du layer + interactions * - canvasTearDown : nettoyage * - updateCombat : reset des flèches au début d'un nouveau round * - deleteCombat : reset à la fin du combat * - renderSceneControls (optionnel) : bouton reset pour le GM */ import { ClashArrowLayer } from "./ClashArrowLayer.js"; import { ArrowStore } from "./ArrowStore.js"; import { initDieInteraction } from "./DieInteraction.js"; import { hookSIOMarkers, getSIODieCenter } from "./SIOBridge.js"; const SBSA_MODULE_ID = "sotc-battle-show-ability"; // ── Setting : mode debug (GM uniquement) ───────────────────────────────────── export function registerSettings() { game.settings.register("sotc-clash-arrow", "debugForceState", { name: game.i18n.localize("SOTCCA.Settings.DebugForceState.Name"), hint: game.i18n.localize("SOTCCA.Settings.DebugForceState.Hint"), scope: "world", config: true, type: Boolean, default: false, restricted: true, requiresReload: false, }); } export function registerHooks() { // ── Initialisation globale (une seule fois) ──────────────────────────── Hooks.once("ready", () => { ArrowStore.initSocket(); initDieInteraction(); hookSIOMarkers(); }); // ── Canvas teardown ──────────────────────────────────────────────────── let _currentSceneId = null; Hooks.on("canvasReady", () => { const newSceneId = canvas.scene?.id; if (_currentSceneId && _currentSceneId !== newSceneId) { // Vrai changement de scène → vider le store ArrowStore.clear(); } _currentSceneId = newSceneId; ClashArrowLayer.init(); // Restaurer les flèches persistées (survit au F5) // Léger délai pour laisser SIO finir d'initialiser ses marqueurs PIXI setTimeout(() => { if (game.user.isGM) { // Le GM recharge depuis les flags de scène (source de vérité) ArrowStore.loadFromScene(getSIODieCenter); } else { // Les joueurs demandent au GM de leur envoyer l'état courant ArrowStore.requestSync(); } }, 200); }); Hooks.on("canvasTearDown", () => { // Ne détruire les Graphics que si la scène change (géré dans canvasReady) // On ne touche pas au store ici pour préserver les flèches lors des refreshs ClashArrowLayer.tearDown(); }); // ── Reset au nouveau round ───────────────────────────────────────────── // Aligné sur SBSA : combatRound, GM uniquement Hooks.on("combatRound", (combat, _roundData, _options) => { if (!game.user.isGM) return; ArrowStore.clear(); }); Hooks.on("deleteCombat", () => { ArrowStore.clear(); }); // ── Suppression des flèches quand un dé passe en Used/Unused ───────────── Hooks.on("updateCombatant", (combatant, changes) => { // On ne réagit que si dieState a changé const newState = foundry.utils.getProperty(changes, `flags.${SBSA_MODULE_ID}.dieState`); if (newState === undefined) return; // On ne réagit pas si le dé est dans l'état Applied Skill (3) if (newState === 3) return; // Retrouver le token et l'index du dé via le même tri que _getCombatantForDie const tokenId = combatant.tokenId; const tokenDoc = canvas.scene?.tokens?.get(tokenId); if (!tokenDoc) return; const sortedCombatants = game.combat.combatants .filter(c => c.tokenId === tokenId) .sort((a, b) => (b.initiative ?? -Infinity) - (a.initiative ?? -Infinity)); const dieIndex = sortedCombatants.indexOf(combatant); if (dieIndex === -1) return; // dieState 1 (Used) → marquer les flèches sortantes en marron // dieState 0 (Unused) → supprimer les flèches sortantes // dieState 2 (Saved Defense) → supprimer les flèches sortantes (on pourra en retracer 1) const isUsed = newState === 1; for (const arrow of ArrowStore.all()) { if (arrow.fromToken === tokenId && arrow.fromDie === dieIndex) { if (isUsed) { ArrowStore.markUsed(arrow.id); } else { ArrowStore.removeArrow(arrow.id); } } } }); // ── Bouton de reset manuel (GM uniquement) ───────────────────────────── Hooks.on("getSceneControlButtons", (controls) => { if (!game.user.isGM) return; // Ajouter au groupe "token" (première barre) const tokenControls = controls.find(c => c.name === "token"); if (!tokenControls) return; tokenControls.tools.push({ name : "sotcca-reset", title : game.i18n.localize("SOTCCA.ArrowsReset"), icon : "fas fa-times-circle", button : true, onClick: () => { ArrowStore.clear(); ui.notifications.info(game.i18n.localize("SOTCCA.ArrowsReset")); }, }); }); // ── Bouton debug : forcer l'état du dé (visible seulement si l'option est activée) ── Hooks.on("renderApplication", (app, html) => { // Vérifier que l'option debug est activée if (!game.settings.get("sotc-clash-arrow", "debugForceState")) return; // On cible uniquement le panneau SBSA if (!html[0]?.querySelector(".sotc-bsa-panel")) return; const panel = html[0].querySelector(".sotc-bsa-panel"); const combatantId = panel?.dataset?.combatantId; if (!combatantId) return; const combatant = game.combat?.combatants.get(combatantId); if (!combatant) return; // Afficher sur tous les états (pas seulement quand assigné) // → le bloc debug permet de forcer l'état peu importe la situation // Injecter le bloc debug après la section d'état const stateSection = html[0].querySelector(".bsa-state-section"); if (!stateSection) return; const debugHtml = `