sotc-foundry-suite/sotc-damage-wizard/module/ArrowBridge.js

168 lines
6 KiB
JavaScript

/**
* sotc-damage-wizard | ArrowBridge.js
*
* Pont avec sotc-clash-arrow pour lire les flèches et détecter le clash mutuel.
*
* IMPORTANT : sotc-clash-arrow expose son ArrowStore via :
* globalThis.sotcClashArrow.ArrowStore
* et NON via game.modules.get(...).api
*
* Structure d'une flèche ArrowStore :
* {
* id : string "tokenId:dieIndex→tokenId:dieIndex"
* fromToken : string tokenId source
* fromDie : number index du dé source (ordre SIO = tri initiative desc)
* toToken : string tokenId cible
* toDie : number index du dé cible
* mutual : boolean true si clash mutuel
* used : boolean true si dé passé en Used
* }
*/
const SBSA_MODULE = "sotc-battle-show-ability";
const CA_MODULE = "sotc-clash-arrow";
/* ------------------------------------------------------------------ */
/* Accès à ArrowStore */
/* ------------------------------------------------------------------ */
/**
* Retourne l'instance ArrowStore de sotc-clash-arrow, ou null si inactif.
* sotc-clash-arrow expose son store sur globalThis.sotcClashArrow.ArrowStore
*/
function getArrowStore() {
if (!game.modules.get(CA_MODULE)?.active) return null;
return globalThis.sotcClashArrow?.ArrowStore ?? null;
}
/* ------------------------------------------------------------------ */
/* Index SIO d'un combatant */
/* ------------------------------------------------------------------ */
/**
* Calcule le sioMarkerIndex d'un combatant.
* Identique à la logique de SIOBridge.js de clash-arrow :
* tri par initiative décroissante — marker[0] = initiative la plus haute.
* @param {Combatant} combatant
* @returns {number}
*/
function getSIOIndexForCombatant(combatant) {
if (!game.combat) return 0;
const sorted = game.combat.combatants
.filter(c => c.tokenId === combatant.tokenId)
.sort((a, b) => (b.initiative ?? -Infinity) - (a.initiative ?? -Infinity));
const idx = sorted.findIndex(c => c.id === combatant.id);
return idx === -1 ? 0 : idx;
}
/**
* Retourne le combatant correspondant à (tokenId, dieIndex).
* @param {string} tokenId
* @param {number} dieIndex
* @returns {Combatant|null}
*/
function getCombatantForDie(tokenId, dieIndex) {
if (!game.combat) return null;
const sorted = game.combat.combatants
.filter(c => c.tokenId === tokenId)
.sort((a, b) => (b.initiative ?? -Infinity) - (a.initiative ?? -Infinity));
return sorted[dieIndex] ?? null;
}
/* ------------------------------------------------------------------ */
/* API publique */
/* ------------------------------------------------------------------ */
/**
* Retourne la cible et le mode clash pour un combatant attaquant.
*
* Priorité :
* 1. ArrowStore (sotc-clash-arrow actif) — source de vérité
* 2. game.user.targets — fallback FoundryVTT natif
*
* @param {Combatant} attackerCombatant
* @returns {{ defenderCombatant: Combatant|null, isClash: boolean }}
*/
export function getTargetInfo(attackerCombatant) {
const arrowStore = getArrowStore();
if (arrowStore) {
const result = _getTargetFromArrowStore(arrowStore, attackerCombatant);
console.log("sotc-damage-wizard | ArrowBridge (ArrowStore) →", result);
return result;
}
const result = _getTargetFromFoundry(attackerCombatant);
console.log("sotc-damage-wizard | ArrowBridge (targets fallback) →", result);
return result;
}
/* ------------------------------------------------------------------ */
/* Logique ArrowStore */
/* ------------------------------------------------------------------ */
function _getTargetFromArrowStore(arrowStore, attackerCombatant) {
const tokenId = attackerCombatant.tokenId;
const dieIndex = getSIOIndexForCombatant(attackerCombatant);
const arrows = arrowStore.all();
console.log(
`sotc-damage-wizard | ArrowStore: ${arrows.length} fleche(s). Cherche fromToken=${tokenId} fromDie=${dieIndex}`
);
// Trouver la flèche sortante de ce dé précis
const outgoing = arrows.find(
a => a.fromToken === tokenId && a.fromDie === dieIndex
);
if (!outgoing) {
console.log("sotc-damage-wizard | Aucune fleche sortante trouvee.");
return { defenderCombatant: null, isClash: false };
}
console.log("sotc-damage-wizard | Fleche trouvee ->", outgoing);
const defenderCombatant = getCombatantForDie(outgoing.toToken, outgoing.toDie);
if (!defenderCombatant) {
console.log("sotc-damage-wizard | Combatant defenseur introuvable pour", outgoing.toToken, outgoing.toDie);
return { defenderCombatant: null, isClash: false };
}
// Clash mutuel : flèche marquée mutual OU flèche inverse présente
const reverseExists = arrows.some(
a => a.fromToken === outgoing.toToken
&& a.fromDie === outgoing.toDie
&& a.toToken === tokenId
&& a.toDie === dieIndex
);
const isClash = outgoing.mutual || reverseExists;
return { defenderCombatant, isClash };
}
/* ------------------------------------------------------------------ */
/* Fallback game.user.targets */
/* ------------------------------------------------------------------ */
function _getTargetFromFoundry(attackerCombatant) {
const target = game.user.targets.first();
if (!target) {
return { defenderCombatant: null, isClash: false };
}
// Premier combatant du token ciblé (tri initiative desc)
const defenderCombatant = game.combat?.combatants
.filter(c => c.tokenId === target.document.id)
.sort((a, b) => (b.initiative ?? -Infinity) - (a.initiative ?? -Infinity))[0] ?? null;
if (!defenderCombatant) {
return { defenderCombatant: null, isClash: false };
}
// Clash si le défenseur a dieState === 3 (compétence assignée)
const defDieState = defenderCombatant.getFlag(SBSA_MODULE, "dieState") ?? 0;
const isClash = defDieState === 3;
return { defenderCombatant, isClash };
}