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,150 @@
/**
* sotc-clash-arrow | ClashArrowLayer.js
*
* En Foundry v11, les CanvasLayer custom ne se montent pas via CONFIG.Canvas.layers.
* Solution : attacher deux PIXI.Graphics directement sur canvas.interface
* (le groupe PIXI qui contient tokens, drawings, etc.).
*
* API publique (identique à l'ancienne classe) :
* ClashArrowLayer.init() – à appeler au canvasReady
* ClashArrowLayer.tearDown() – à appeler au canvasTearDown
* ClashArrowLayer.redraw() – redessine toutes les flèches du store
* ClashArrowLayer.drawPreview() – flèche temporaire pendant le drag
* ClashArrowLayer.clearPreview()
*/
import { ArrowStore } from "./ArrowStore.js";
// ── Constantes visuelles ──────────────────────────────────────────────────────
const COLOR_BLUE = 0x4a9eff;
const COLOR_YELLOW = 0xffd84a;
const COLOR_PREVIEW = 0x888899;
const COLOR_USED = 0x8B4513; // marron "Used"
const ALPHA_USED = 0.35; // légèrement visible
const LINE_WIDTH = 3;
const LINE_ALPHA = 0.92;
const ARROW_HEAD_SIZE = 14;
const BEZIER_CURVE = 0.35;
// ── Helpers géométriques ──────────────────────────────────────────────────────
function bezierControlPoints(x1, y1, x2, y2, curvature = BEZIER_CURVE) {
const mx = (x1 + x2) / 2;
const my = (y1 + y2) / 2;
const dx = x2 - x1;
const dy = y2 - y1;
const nx = -dy * curvature;
const ny = dx * curvature;
return { cp1x: mx + nx, cp1y: my + ny };
}
function bezierPoint(t, p0, p1, p2, p3) {
const mt = 1 - t;
return mt*mt*mt*p0 + 3*mt*mt*t*p1 + 3*mt*t*t*p2 + t*t*t*p3;
}
function drawArrowHead(gfx, tx, ty, dx, dy, size, color, alpha) {
const angle = Math.atan2(dy, dx);
const spread = Math.PI / 6;
const lx = tx - size * Math.cos(angle - spread);
const ly = ty - size * Math.sin(angle - spread);
const rx = tx - size * Math.cos(angle + spread);
const ry = ty - size * Math.sin(angle + spread);
gfx.lineStyle(0);
gfx.beginFill(color, alpha);
gfx.drawPolygon([tx, ty, lx, ly, rx, ry]);
gfx.endFill();
}
// ── Module singleton ──────────────────────────────────────────────────────────
/** @type {PIXI.Graphics|null} */
let _arrowsGfx = null;
/** @type {PIXI.Graphics|null} */
let _previewGfx = null;
function _drawArrow(gfx, x1, y1, x2, y2, color, alpha = LINE_ALPHA) {
const { cp1x, cp1y } = bezierControlPoints(x1, y1, x2, y2);
gfx.lineStyle(LINE_WIDTH, color, alpha);
gfx.moveTo(x1, y1);
gfx.bezierCurveTo(cp1x, cp1y, cp1x, cp1y, x2, y2);
const t = 0.98;
const px = bezierPoint(t, x1, cp1x, cp1x, x2);
const py = bezierPoint(t, y1, cp1y, cp1y, y2);
drawArrowHead(gfx, x2, y2, x2 - px, y2 - py, ARROW_HEAD_SIZE, color, alpha);
}
export const ClashArrowLayer = {
/**
* Initialise les Graphics PIXI et les attache au canvas.
* Appelé au canvasReady depuis Hooks.js.
*/
init() {
// Si déjà initialisé et Graphics toujours valides, juste redessiner
if (_arrowsGfx && !_arrowsGfx.destroyed) {
this.redraw();
return;
}
// Nettoyer si les Graphics sont corrompus
this.tearDown();
const parent = canvas.interface ?? canvas.stage;
_arrowsGfx = parent.addChild(new PIXI.Graphics());
_previewGfx = parent.addChild(new PIXI.Graphics());
_arrowsGfx.zIndex = 900;
_previewGfx.zIndex = 901;
ArrowStore.on("change", () => this.redraw());
// Dessiner les flèches déjà présentes dans le store
this.redraw();
console.log("sotc-clash-arrow | ClashArrowLayer initialisé sur canvas.interface.");
},
/** Nettoie et retire les Graphics du canvas. */
tearDown() {
ArrowStore.off("change");
if (_arrowsGfx) { _arrowsGfx.parent?.removeChild(_arrowsGfx); _arrowsGfx.destroy(); _arrowsGfx = null; }
if (_previewGfx) { _previewGfx.parent?.removeChild(_previewGfx); _previewGfx.destroy(); _previewGfx = null; }
},
/** Redessine toutes les flèches persistantes. */
redraw() {
if (!_arrowsGfx) return;
_arrowsGfx.clear();
for (const arrow of ArrowStore.all()) {
if (arrow.used) {
_drawArrow(_arrowsGfx, arrow.x1, arrow.y1, arrow.x2, arrow.y2, COLOR_USED, ALPHA_USED);
} else {
const color = arrow.mutual ? COLOR_YELLOW : COLOR_BLUE;
_drawArrow(_arrowsGfx, arrow.x1, arrow.y1, arrow.x2, arrow.y2, color);
}
}
},
/**
* Flèche de preview pendant le drag.
* @param {number} x1 @param {number} y1 Origine (coords monde)
* @param {number} x2 @param {number} y2 Position courante (coords monde)
* @param {number} color
*/
drawPreview(x1, y1, x2, y2, color = COLOR_PREVIEW) {
if (!_previewGfx) return;
_previewGfx.clear();
_drawArrow(_previewGfx, x1, y1, x2, y2, color, 0.55);
},
/** Efface la flèche de preview. */
clearPreview() {
_previewGfx?.clear();
},
};