sotc-battle-show-ability : affichage des capacités en combat
This commit is contained in:
parent
84aa6b3437
commit
e35a3996ee
11 changed files with 1363 additions and 0 deletions
231
sotc-clash-arrow/scripts/Hooks.js
Normal file
231
sotc-clash-arrow/scripts/Hooks.js
Normal file
|
|
@ -0,0 +1,231 @@
|
|||
/**
|
||||
* 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,
|
||||
});
|
||||
});
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue