168 lines
6 KiB
JavaScript
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 };
|
|
}
|