sotc-battle-show-ability : affichage des capacités en combat

This commit is contained in:
Yasser Boulayoune 2026-05-01 16:26:00 +02:00
parent 84aa6b3437
commit e35a3996ee
11 changed files with 1363 additions and 0 deletions

View file

@ -0,0 +1,135 @@
/**
* sotc-clash-arrow | DieInteraction.js
*
* Gère la logique de drag entre dés de vitesse.
* Reçoit les événements depuis SIOBridge (mousedown/move/up natifs sur #board).
*
* Coordonnées :
* - Les hooks sotcca:* reçoivent des coords ÉCRAN (relatives à #board)
* - ClashArrowLayer.drawPreview() et ArrowStore travaillent en coords MONDE
* - getSIODieCenter() retourne des coords monde
* - _screenToWorld() convertit écran → monde
*/
import { ArrowStore } from "./ArrowStore.js";
import { getSIODieCenter, findDieUnderScreenPoint,
tokenHasActiveSkillOnDie, getAttackWeightForDie,
getDieState } from "./SIOBridge.js";
import { ClashArrowLayer } from "./ClashArrowLayer.js";
// ── Couleurs ──────────────────────────────────────────────────────────────────
const COLOR_BLUE = 0x4a9eff; // attaque directe
const COLOR_YELLOW = 0xffd84a; // clash mutuel potentiel
const COLOR_PREVIEW = 0x888899; // drag en cours, cible indéfinie
// ── État du drag ──────────────────────────────────────────────────────────────
let _dragging = false;
let _srcToken = null; // TokenDocument source
let _srcDieIdx = null; // index du dé source
let _srcWorldX = 0; // coords monde du centre du dé source
let _srcWorldY = 0;
// ── Init ──────────────────────────────────────────────────────────────────────
export function initDieInteraction() {
Hooks.on("sotcca:diePointerDown", _onDieDown);
Hooks.on("sotcca:boardMouseMove", _onMove);
Hooks.on("sotcca:boardMouseUp", _onUp);
Hooks.on("sotcca:dieRightClick", _onRightClick);
}
// ── Handlers ──────────────────────────────────────────────────────────────────
function _onDieDown(tokenDoc, dieIndex, screenX, screenY) {
// Bloquer si le dé source n'a pas de compétence assignée
if (!tokenHasActiveSkillOnDie(tokenDoc, dieIndex)) {
ui.notifications.warn(game.i18n.localize("SOTCCA.NoSkillOnDie"));
return;
}
_dragging = true;
_srcToken = tokenDoc;
_srcDieIdx = dieIndex;
const world = getSIODieCenter(tokenDoc, dieIndex);
_srcWorldX = world.x;
_srcWorldY = world.y;
}
function _onMove(screenX, screenY) {
if (!_dragging) return;
const worldPos = _screenToWorld(screenX, screenY);
const hit = findDieUnderScreenPoint(screenX, screenY);
const color = _previewColor(hit?.tokenDoc, hit?.dieIndex);
ClashArrowLayer.drawPreview(_srcWorldX, _srcWorldY, worldPos.x, worldPos.y, color);
}
function _onUp(screenX, screenY) {
if (!_dragging) return;
_dragging = false;
ClashArrowLayer.clearPreview();
const hit = findDieUnderScreenPoint(screenX, screenY);
if (!hit) return;
const { tokenDoc: tgtToken, dieIndex: tgtDie } = hit;
if (tgtToken.id === _srcToken.id) {
ui.notifications.warn(game.i18n.localize("SOTCCA.CannotTargetSelf"));
return;
}
// Vérifier la limite d'attack weight
// En état Saved Defense Dice (2) : toujours limité à 1 flèche
const isSavedDefense = getDieState(_srcToken, _srcDieIdx) === 2;
const maxArrows = isSavedDefense ? 1 : getAttackWeightForDie(_srcToken, _srcDieIdx);
const existingArrows = ArrowStore.all().filter(
a => a.fromToken === _srcToken.id && a.fromDie === _srcDieIdx
);
if (existingArrows.length >= maxArrows) {
ui.notifications.warn(
game.i18n.format("SOTCCA.AttackWeightExceeded", { max: maxArrows })
);
return;
}
const src = getSIODieCenter(_srcToken, _srcDieIdx);
const tgt = getSIODieCenter(tgtToken, tgtDie);
ArrowStore.addArrow({
fromToken: _srcToken.id,
fromDie : _srcDieIdx,
toToken : tgtToken.id,
toDie : tgtDie,
x1: src.x, y1: src.y,
x2: tgt.x, y2: tgt.y,
});
}
function _onRightClick(_tokenDoc, _dieIndex) {
// Suppression par clic droit désactivée.
// Les flèches sont supprimées uniquement via le changement de dieState (Used/Unused).
}
// ── Helpers ───────────────────────────────────────────────────────────────────
/**
* Détermine la couleur de la flèche preview en fonction de la cible survolée.
*/
function _previewColor(targetTokenDoc, targetDieIdx) {
if (!targetTokenDoc || targetTokenDoc.id === _srcToken?.id) return COLOR_PREVIEW;
if (tokenHasActiveSkillOnDie(targetTokenDoc, targetDieIdx)) return COLOR_YELLOW;
return COLOR_BLUE;
}
/**
* Convertit des coordonnées écran (#board) en coordonnées monde (canvas).
* @param {number} sx
* @param {number} sy
* @returns {{ x: number, y: number }}
*/
function _screenToWorld(sx, sy) {
return canvas.stage.toLocal({ x: sx, y: sy });
}