sotc-battle-show-ability : affichage des capacités en combat
This commit is contained in:
parent
84aa6b3437
commit
e35a3996ee
11 changed files with 1363 additions and 0 deletions
135
sotc-clash-arrow/scripts/DieInteraction.js
Normal file
135
sotc-clash-arrow/scripts/DieInteraction.js
Normal 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 });
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue