sotc-clash-arrow : flèches de clash entre combattants
This commit is contained in:
commit
ad63cafdc6
10 changed files with 2414 additions and 0 deletions
168
sotc-damage-wizard/module/ArrowBridge.js
Normal file
168
sotc-damage-wizard/module/ArrowBridge.js
Normal file
|
|
@ -0,0 +1,168 @@
|
|||
/**
|
||||
* 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 };
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue