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
228
sotc-clash-arrow/scripts/SIOBridge.js
Normal file
228
sotc-clash-arrow/scripts/SIOBridge.js
Normal file
|
|
@ -0,0 +1,228 @@
|
|||
/**
|
||||
* sotc-clash-arrow | SIOBridge.js
|
||||
*
|
||||
* Pont d'intégration avec SIO et SBSA.
|
||||
*
|
||||
* LEÇON CLUE (apprise du module SBSA) :
|
||||
* L'API PIXI events ne fonctionne pas correctement pour ajouter des
|
||||
* listeners supplémentaires sur les marqueurs SIO en Foundry v11.
|
||||
* La solution fiable est d'écouter les events souris NATIFS sur
|
||||
* l'élément HTML #board, exactement comme le fait SBSA.
|
||||
*
|
||||
* Correspondance index → combatant (identique à SIO) :
|
||||
* Les combatants d'un token sont triés par initiative décroissante.
|
||||
* marker[i] correspond à combatants[i] dans ce tri.
|
||||
*
|
||||
* Compétence assignée (flags SBSA réels) :
|
||||
* combatant.getFlag("sotc-battle-show-ability", "assignedSkillId")
|
||||
* combatant.getFlag("sotc-battle-show-ability", "dieState") → 0/1/2/3
|
||||
* dieState === 3 signifie "compétence assignée"
|
||||
*/
|
||||
|
||||
const SIO_FLAG_SCOPE = "stars-initiative-overlay";
|
||||
const SBSA_MODULE_ID = "sotc-battle-show-ability";
|
||||
const HIT_RADIUS = 28; // pixels écran (getBounds retourne des coords écran sur #board)
|
||||
|
||||
// ── API publique ──────────────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Installe le handler mousedown natif sur #board.
|
||||
* Appelé une fois depuis Hooks.js au canvasReady.
|
||||
*/
|
||||
export function hookSIOMarkers() {
|
||||
_installBoardHandler();
|
||||
// Réinstaller si la scène change
|
||||
Hooks.on("canvasReady", () => _installBoardHandler());
|
||||
}
|
||||
|
||||
/**
|
||||
* Retourne les coordonnées MONDE (canvas) du centre d'un marqueur SIO.
|
||||
* Utilisé pour dessiner les flèches dans ClashArrowLayer.
|
||||
* @param {TokenDocument} tokenDoc
|
||||
* @param {number} dieIndex
|
||||
* @returns {{ x: number, y: number }}
|
||||
*/
|
||||
export function getSIODieCenter(tokenDoc, dieIndex) {
|
||||
const token = tokenDoc.object;
|
||||
const marker = token?._initiativeMarkers?.[dieIndex];
|
||||
if (marker) {
|
||||
const b = marker.getBounds();
|
||||
// getBounds() retourne des coordonnées écran (#board) → convertir en coords monde
|
||||
const screenCenter = { x: b.x + b.width / 2, y: b.y + b.height / 2 };
|
||||
return canvas.stage.toLocal(screenCenter);
|
||||
}
|
||||
// Fallback : centre du token en coords monde
|
||||
return { x: token.center.x, y: token.center.y };
|
||||
}
|
||||
|
||||
/**
|
||||
* Cherche quel dé SIO se trouve sous les coordonnées écran (x, y).
|
||||
* Retourne { tokenDoc, dieIndex, combatant } ou null.
|
||||
* @param {number} screenX – coordonnées relatives à #board
|
||||
* @param {number} screenY
|
||||
* @returns {{ tokenDoc: TokenDocument, dieIndex: number, combatant: Combatant }|null}
|
||||
*/
|
||||
export function findDieUnderScreenPoint(screenX, screenY) {
|
||||
for (const token of canvas.tokens.placeables) {
|
||||
const markers = token._initiativeMarkers;
|
||||
if (!markers?.length) continue;
|
||||
|
||||
for (let i = 0; i < markers.length; i++) {
|
||||
const b = markers[i].getBounds();
|
||||
const cx = b.x + b.width / 2;
|
||||
const cy = b.y + b.height / 2;
|
||||
|
||||
if (Math.hypot(screenX - cx, screenY - cy) > HIT_RADIUS) continue;
|
||||
|
||||
const combatant = _getCombatantForDie(token.document, i);
|
||||
if (!combatant) continue;
|
||||
|
||||
return { tokenDoc: token.document, dieIndex: i, combatant };
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retourne le dieState SBSA du dé `dieIndex` d'un token.
|
||||
* @param {TokenDocument} tokenDoc
|
||||
* @param {number} dieIndex
|
||||
* @returns {number} 0=Unused, 1=Used, 2=Saved Defense, 3=Applied Skill
|
||||
*/
|
||||
export function getDieState(tokenDoc, dieIndex) {
|
||||
const combatant = _getCombatantForDie(tokenDoc, dieIndex);
|
||||
if (!combatant) return 0;
|
||||
return combatant.getFlag(SBSA_MODULE_ID, "dieState") ?? 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retourne l'attack_weight de la compétence assignée au dé `dieIndex`.
|
||||
* - Si aucune compétence ou pas d'attack_weight → retourne 1 (1 flèche max)
|
||||
* - Sinon retourne la valeur numérique de attack_weight
|
||||
* @param {TokenDocument} tokenDoc
|
||||
* @param {number} dieIndex
|
||||
* @returns {number}
|
||||
*/
|
||||
export function getAttackWeightForDie(tokenDoc, dieIndex) {
|
||||
const combatant = _getCombatantForDie(tokenDoc, dieIndex);
|
||||
if (!combatant) return 1;
|
||||
|
||||
const skillId = combatant.getFlag(SBSA_MODULE_ID, "assignedSkillId") ?? null;
|
||||
if (!skillId) return 1;
|
||||
|
||||
const actor = tokenDoc.actor;
|
||||
if (!actor) return 1;
|
||||
|
||||
const skill = actor.items.get(skillId);
|
||||
if (!skill) return 1;
|
||||
|
||||
const weight = skill.system?.weight ?? null;
|
||||
if (!weight || weight <= 0) return 1;
|
||||
|
||||
return weight;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retourne true si le dé `dieIndex` d'un token a une compétence assignée.
|
||||
* États autorisés à créer une flèche :
|
||||
* - dieState === 3 (Applied Skill)
|
||||
* - dieState === 2 (Saved Defense Dice) — le dé sauvegardé réutilise sa compétence
|
||||
* @param {TokenDocument} tokenDoc
|
||||
* @param {number} dieIndex
|
||||
* @returns {boolean}
|
||||
*/
|
||||
export function tokenHasActiveSkillOnDie(tokenDoc, dieIndex) {
|
||||
const combatant = _getCombatantForDie(tokenDoc, dieIndex);
|
||||
if (!combatant) return false;
|
||||
const dieState = combatant.getFlag(SBSA_MODULE_ID, "dieState") ?? 0;
|
||||
const assignedId = combatant.getFlag(SBSA_MODULE_ID, "assignedSkillId") ?? null;
|
||||
return (dieState === 3 || dieState === 2) && !!assignedId;
|
||||
}
|
||||
|
||||
// ── Helpers internes ──────────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Retourne le combatant correspondant à marker[dieIndex] selon le tri SIO.
|
||||
* Identique au getter sioMarkerIndex de SpeedDiePanelApp (nouvelle version SBSA) :
|
||||
* tri par initiative décroissante, marker[0] = initiative la plus haute.
|
||||
* @param {TokenDocument} tokenDoc
|
||||
* @param {number} dieIndex
|
||||
* @returns {Combatant|null}
|
||||
*/
|
||||
function _getCombatantForDie(tokenDoc, dieIndex) {
|
||||
if (!game.combat) return null;
|
||||
const combatants = game.combat.combatants
|
||||
.filter(c => c.tokenId === tokenDoc.id)
|
||||
.sort((a, b) => (b.initiative ?? -Infinity) - (a.initiative ?? -Infinity));
|
||||
return combatants[dieIndex] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Installe (ou réinstalle) les listeners natifs sur #board.
|
||||
*/
|
||||
function _installBoardHandler() {
|
||||
const board = document.getElementById("board");
|
||||
if (!board) return;
|
||||
|
||||
// Nettoyer les anciens handlers
|
||||
if (board._sotcca_downHandler) {
|
||||
board.removeEventListener("mousedown", board._sotcca_downHandler);
|
||||
board.removeEventListener("mouseup", board._sotcca_upHandler);
|
||||
board.removeEventListener("mousemove", board._sotcca_moveHandler);
|
||||
}
|
||||
|
||||
board._sotcca_downHandler = _onBoardMouseDown;
|
||||
board._sotcca_upHandler = _onBoardMouseUp;
|
||||
board._sotcca_moveHandler = _onBoardMouseMove;
|
||||
|
||||
board.addEventListener("mousedown", board._sotcca_downHandler);
|
||||
board.addEventListener("mouseup", board._sotcca_upHandler);
|
||||
board.addEventListener("mousemove", board._sotcca_moveHandler, { passive: true });
|
||||
|
||||
// Bloquer le menu contextuel navigateur sur les dés
|
||||
if (!board._sotcca_ctxHandler) {
|
||||
board._sotcca_ctxHandler = (e) => {
|
||||
const rect = board.getBoundingClientRect();
|
||||
const hit = findDieUnderScreenPoint(e.clientX - rect.left, e.clientY - rect.top);
|
||||
if (hit) e.preventDefault();
|
||||
};
|
||||
board.addEventListener("contextmenu", board._sotcca_ctxHandler);
|
||||
}
|
||||
|
||||
console.log("sotc-clash-arrow | Handler mousedown installé sur #board.");
|
||||
}
|
||||
|
||||
// ── Handlers DOM → délèguent via Hooks à DieInteraction ──────────────────────
|
||||
|
||||
function _onBoardMouseDown(event) {
|
||||
const board = document.getElementById("board");
|
||||
const rect = board.getBoundingClientRect();
|
||||
const sx = event.clientX - rect.left;
|
||||
const sy = event.clientY - rect.top;
|
||||
|
||||
const hit = findDieUnderScreenPoint(sx, sy);
|
||||
if (!hit) return;
|
||||
|
||||
if (event.button === 0) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
Hooks.callAll("sotcca:diePointerDown", hit.tokenDoc, hit.dieIndex, sx, sy);
|
||||
} else if (event.button === 2) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
Hooks.callAll("sotcca:dieRightClick", hit.tokenDoc, hit.dieIndex);
|
||||
}
|
||||
}
|
||||
|
||||
function _onBoardMouseMove(event) {
|
||||
const board = document.getElementById("board");
|
||||
const rect = board.getBoundingClientRect();
|
||||
Hooks.callAll("sotcca:boardMouseMove", event.clientX - rect.left, event.clientY - rect.top);
|
||||
}
|
||||
|
||||
function _onBoardMouseUp(event) {
|
||||
if (event.button !== 0) return;
|
||||
const board = document.getElementById("board");
|
||||
const rect = board.getBoundingClientRect();
|
||||
Hooks.callAll("sotcca:boardMouseUp", event.clientX - rect.left, event.clientY - rect.top);
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue