135 lines
5.2 KiB
JavaScript
135 lines
5.2 KiB
JavaScript
/**
|
|
* 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 });
|
|
}
|