# sotc-clash-arrow — Spécification Technique v1.0 ## Vue d'ensemble Module FoundryVTT v11 permettant de tracer des flèches courbées entre les **Dés de Vitesse** du module *Stars Initiative Overlay* (SIO) de Vani, pour visualiser les clashes dans *Star of the City* (SotC). S'appuie sur le module **SBSA (clash-resolver)** pour lire les compétences assignées aux dés. --- ## Dépendances | Module | Rôle | |---|---| | `stars-initiative-overlay` | Fournit les marqueurs PIXI des Dés de Vitesse | | `clash-resolver` (SBSA) | Fournit les compétences stockées par dé via flags | --- ## Logique des couleurs | Couleur | Condition | |---|---| | **Bleu** `#4a9eff` | A cible B, mais B n'a **pas** de compétence active sur son dé | | **Jaune** `#ffd84a` | A→B **ET** B→A existent simultanément (clash mutuel automatique) | | **Gris** `#888899` | Flèche de preview pendant le drag (avant relâchement) | La détection du clash mutuel est **automatique** : dès que la flèche A→B est créée alors que B→A existe déjà, les **deux** flèches passent en jaune. --- ## Interactions utilisateur ### Tracer une flèche 1. Maintenir **clic gauche** sur un Dé de Vitesse SIO 2. Déplacer la souris vers un autre dé — la flèche preview change de couleur en temps réel selon la cible survolée 3. Relâcher sur le dé cible → la flèche devient persistante **Qui peut tracer ?** Tous les utilisateurs (GM et joueurs), depuis n'importe quel token. ### Supprimer une flèche - **Clic droit** sur un Dé de Vitesse → supprime toutes les flèches depuis/vers ce dé ### Reset global - Automatique au début d'un nouveau round (hook `updateCombat` + hook SIO `sioRoundReset`) - Automatique à la fin du combat (`deleteCombat`) - Manuel via le bouton **GM** dans la barre de contrôles de scène (icône `×`) --- ## Architecture des fichiers ``` sotc-clash-arrow/ ├── module.json ├── styles/ │ └── sotc-clash-arrow.css ├── lang/ │ ├── fr.json │ └── en.json └── scripts/ ├── main.js ← point d'entrée ├── Hooks.js ← enregistrement des hooks FoundryVTT ├── ArrowStore.js ← store centralisé + sync socket ├── ClashArrowLayer.js ← couche PIXI (dessin des flèches) ├── DieInteraction.js ← gestion du drag souris └── SIOBridge.js ← intégration SIO + détection compétences SBSA ``` --- ## Description des modules JS ### `main.js` - Point d'entrée unique chargé par `module.json` - Expose `globalThis.sotcClashArrow` pour débogage console - Vérifie la présence du module SIO au `ready` ### `ArrowStore.js` - Store en mémoire (`Map`) - Détecte automatiquement le clash mutuel à chaque `addArrow()` - Synchronise via `game.socket` (événement `module.sotc-clash-arrow`) pour que tous les clients voient les mêmes flèches - Émet un événement `change` → `ClashArrowLayer` se redessine **Structure d'une flèche :** ```js { id : "tokenA:0→tokenB:1", fromToken : "tokenA_id", fromDie : 0, toToken : "tokenB_id", toDie : 1, x1, y1 : number, // coords canvas du centre du dé source x2, y2 : number, // coords canvas du centre du dé cible mutual : boolean, // true = flèche jaune } ``` ### `ClashArrowLayer.js` - Hérite de `CanvasLayer` (FoundryVTT v11) - `zIndex: 180` → au-dessus des tokens (100), sous l'UI (900+) - Deux `PIXI.Graphics` séparés : flèches persistantes + preview - Courbe de Bézier cubique avec pointe de flèche trigonométrique - `BEZIER_CURVE = 0.35` : facteur de courbure ajustable ### `DieInteraction.js` - Écoute `pointerdown` (via hook interne `sotcca:diePointerDown`) → démarre le drag - `pointermove` → met à jour la preview avec la couleur calculée en temps réel - `pointerup` → crée la flèche dans `ArrowStore` si une cible valide est sous le curseur ### `SIOBridge.js` - `getSIODieCenter()` : lit `token._initiativeMarkers[i].getBounds()` pour les coords canvas - `findDieUnderPoint()` : parcourt tous les tokens/marqueurs, hit-test à ±24px - `tokenHasActiveSkillOnDie()` : combine état SIO (`diceStates[i] !== 0`) ET présence d'une compétence SBSA (`flag "clash-resolver" → "dieSkills"[i]`) - `hookSIOMarkers()` : attache les listeners sur les marqueurs PIXI existants + futurs (via hook `refreshToken`) ### `Hooks.js` - `init` : enregistrement du layer dans `CONFIG.Canvas.layers` - `canvasReady` : init socket + interactions + hook SIO markers - `canvasTearDown` : `ArrowStore.clear()` - `updateCombat` (nouveau round) + `sioRoundReset` + `deleteCombat` → reset - `getSceneControlButtons` : bouton reset GM - `sotcca:mutualClash` : message chat signalant un clash mutuel --- ## Chemins de données supposés (à vérifier) > ⚠️ Ces chemins sont des hypothèses basées sur l'analyse du module SIO et SBSA. > À valider lors des tests avec les modules installés. | Donnée | Chemin supposé | |---|---| | Marqueurs PIXI SIO | `token._initiativeMarkers[]` | | États des dés SIO | `tokenDoc.getFlag("stars-initiative-overlay", "diceStates")` → `int[]` | | Nombre de dés | `actor.system.speed_dice.num_dice` | | Compétences par dé (SBSA) | `tokenDoc.getFlag("clash-resolver", "dieSkills")` → `string[]` | | Hook reset SIO | `"sioRoundReset"` (à confirmer dans la source SIO) | --- ## Synchronisation multi-clients Toutes les modifications du store sont broadcastées via `game.socket.emit("module.sotc-clash-arrow", payload)`. Les autres clients reçoivent le payload et appliquent la même modification localement sans re-broadcaster, pour éviter les boucles. **Actions socket :** - `add` : nouvelle flèche (+ mise à jour éventuelle de la flèche inverse) - `remove` : suppression d'une flèche (+ dés-mutualisation de l'inverse) - `clear` : reset total --- ## Notes d'implémentation - Le layer PIXI est enregistré dans `CONFIG.Canvas.layers` au hook `init`, avant la construction du canvas - Les listeners sur les marqueurs SIO sont attachés avec un délai de 50ms après `refreshToken` pour laisser SIO finir de recréer ses containers PIXI - Un marqueur déjà hookié est marqué `marker._sotcca_hooked = true` pour éviter les doublons - La flèche de preview utilise `alpha = 0.55` pour la distinguer des flèches persistantes (`alpha = 0.92`)