231 lines
8.6 KiB
JavaScript
231 lines
8.6 KiB
JavaScript
/**
|
|
* 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 = `
|
|
<div class="sotcca-debug-section" style="
|
|
margin-top: 6px;
|
|
padding: 4px 6px;
|
|
border: 1px dashed #888;
|
|
border-radius: 4px;
|
|
opacity: 0.75;
|
|
">
|
|
<div style="font-size: 10px; color: #aaa; margin-bottom: 4px;">
|
|
🛠 ${game.i18n.localize("SOTCCA.Debug.Title")}
|
|
</div>
|
|
<div style="display: flex; gap: 4px; flex-wrap: wrap;">
|
|
<button class="sotcca-debug-state-btn" data-state="0" style="flex:1; font-size:11px; padding:2px 4px;">
|
|
${game.i18n.localize("SOTC_BSA.Panel.StateUnused")}
|
|
</button>
|
|
<button class="sotcca-debug-state-btn" data-state="1" style="flex:1; font-size:11px; padding:2px 4px;">
|
|
${game.i18n.localize("SOTC_BSA.Panel.StateUsed")}
|
|
</button>
|
|
<button class="sotcca-debug-state-btn" data-state="2" style="flex:1; font-size:11px; padding:2px 4px;">
|
|
${game.i18n.localize("SOTC_BSA.Panel.StateSaved")}
|
|
</button>
|
|
<button class="sotcca-debug-state-btn" data-state="3" style="flex:1; font-size:11px; padding:2px 4px;">
|
|
${game.i18n.localize("SOTC_BSA.Panel.StateAssigned")}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
`;
|
|
|
|
stateSection.insertAdjacentHTML("afterend", debugHtml);
|
|
|
|
// Brancher les listeners sur les nouveaux boutons
|
|
html[0].querySelectorAll(".sotcca-debug-state-btn").forEach(btn => {
|
|
btn.addEventListener("click", async (e) => {
|
|
e.preventDefault();
|
|
e.stopPropagation();
|
|
const state = parseInt(btn.dataset.state);
|
|
await combatant.setFlag(SBSA_MODULE_ID, "dieState", state);
|
|
ui.notifications.info(
|
|
game.i18n.format("SOTCCA.Debug.StateForced", { state })
|
|
);
|
|
});
|
|
});
|
|
});
|
|
|
|
ArrowStore.on?.("change", () => {
|
|
// Détecter les nouvelles flèches qui viennent de passer en mutuel
|
|
// (géré dans ArrowStore.addArrow → on écoute ici pour le message chat)
|
|
});
|
|
|
|
// Hook interne émis par ArrowStore pour les nouveaux clashes mutuels
|
|
Hooks.on("sotcca:mutualClash", (arrow) => {
|
|
const attacker = canvas.scene.tokens.get(arrow.fromToken)?.name ?? arrow.fromToken;
|
|
const defender = canvas.scene.tokens.get(arrow.toToken)?.name ?? arrow.toToken;
|
|
|
|
ChatMessage.create({
|
|
content: `<div class="sotcca-mutual-clash-msg">
|
|
⚔️ ${game.i18n.format("SOTCCA.MutualClash", { attacker, defender })}
|
|
</div>`,
|
|
type: CONST.CHAT_MESSAGE_TYPES.OTHER,
|
|
});
|
|
});
|
|
}
|