/** * 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 }; }