sotc-damage-wizard : assistant de calcul des dégâts
This commit is contained in:
parent
ad63cafdc6
commit
84aa6b3437
7 changed files with 1532 additions and 0 deletions
45
sotc-battle-show-ability/lang/en.json
Normal file
45
sotc-battle-show-ability/lang/en.json
Normal file
|
|
@ -0,0 +1,45 @@
|
||||||
|
{
|
||||||
|
"SOTC_BSA": {
|
||||||
|
"Panel": {
|
||||||
|
"Title": "Speed Die",
|
||||||
|
"TokenInfo": "Information",
|
||||||
|
"Stats": "Statistics",
|
||||||
|
"Skills": "Skills",
|
||||||
|
"SkillsTab": "Skills",
|
||||||
|
"EgoTab": "E.G.O",
|
||||||
|
"DieState": "Die State",
|
||||||
|
"AssignSkill": "Assign",
|
||||||
|
"SelectedSkill": "Selected Skill",
|
||||||
|
"NoCombat": "No active combat.",
|
||||||
|
"NoCombatant": "This token is not in combat.",
|
||||||
|
"SpeedDie": "Speed Die",
|
||||||
|
"NoSkills": "No skills available.",
|
||||||
|
"NoEgo": "No E.G.O skills available.",
|
||||||
|
"Cost": "Cost",
|
||||||
|
"LightCost": "Light",
|
||||||
|
"EmotionCost": "Emotion",
|
||||||
|
"DicePreview": "Skill Dice",
|
||||||
|
"Tags": "Tags / Modules",
|
||||||
|
"Assign": "Assign to Die",
|
||||||
|
"AssignSuccess": "Skill assigned to Speed Die.",
|
||||||
|
"InsufficientLight": "Insufficient Light (need: {cost}, available: {current}).",
|
||||||
|
"InsufficientEmotion": "Insufficient Emotion (need: {cost}, available: {current}).",
|
||||||
|
"AlreadyAssigned": "This Speed Die already has a skill assigned.",
|
||||||
|
"StateUnused": "Unused",
|
||||||
|
"StateUsed": "Used",
|
||||||
|
"StateSaved": "Saved Defense Die",
|
||||||
|
"StateAssigned": "Assigned Skills",
|
||||||
|
"StateChanged": "Die state updated.",
|
||||||
|
"OpenSheet": "Open sheet",
|
||||||
|
"HP": "HP",
|
||||||
|
"Stagger": "Stagger",
|
||||||
|
"Light": "Light",
|
||||||
|
"Emotion": "Emotion",
|
||||||
|
"AssignedSkill": "Assigned Skill",
|
||||||
|
"None": "None",
|
||||||
|
"Unassign": "Unassign",
|
||||||
|
"Die": "Die",
|
||||||
|
"RemoveArrows": "Remove arrows"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
45
sotc-battle-show-ability/lang/fr.json
Normal file
45
sotc-battle-show-ability/lang/fr.json
Normal file
|
|
@ -0,0 +1,45 @@
|
||||||
|
{
|
||||||
|
"SOTC_BSA": {
|
||||||
|
"Panel": {
|
||||||
|
"Title": "Dé de Vitesse",
|
||||||
|
"TokenInfo": "Informations",
|
||||||
|
"Stats": "Statistiques",
|
||||||
|
"Skills": "Compétences",
|
||||||
|
"SkillsTab": "Compétences",
|
||||||
|
"EgoTab": "E.G.O",
|
||||||
|
"DieState": "État du Dé",
|
||||||
|
"AssignSkill": "Assigner",
|
||||||
|
"SelectedSkill": "Compétence sélectionnée",
|
||||||
|
"NoCombat": "Aucun combat actif.",
|
||||||
|
"NoCombatant": "Ce token n'est pas dans le combat.",
|
||||||
|
"SpeedDie": "Dé de Vitesse",
|
||||||
|
"NoSkills": "Aucune compétence disponible.",
|
||||||
|
"NoEgo": "Aucune compétence E.G.O disponible.",
|
||||||
|
"Cost": "Coût",
|
||||||
|
"LightCost": "Lumière",
|
||||||
|
"EmotionCost": "Émotion",
|
||||||
|
"DicePreviw": "Dés de la compétence",
|
||||||
|
"Tags": "Tags / Modules",
|
||||||
|
"Assign": "Assigner au Dé",
|
||||||
|
"AssignSuccess": "Compétence assignée au Dé de Vitesse.",
|
||||||
|
"InsufficientLight": "Lumière insuffisante (besoin : {cost}, disponible : {current}).",
|
||||||
|
"InsufficientEmotion": "Émotion insuffisante (besoin : {cost}, disponible : {current}).",
|
||||||
|
"AlreadyAssigned": "Ce Dé de Vitesse a déjà une compétence assignée.",
|
||||||
|
"StateUnused": "Non Utilisé",
|
||||||
|
"StateUsed": "Utilisé",
|
||||||
|
"StateSaved": "Dé de Défense Sauvegardé",
|
||||||
|
"StateAssigned": "Compétence Assignée",
|
||||||
|
"StateChanged": "État du Dé mis à jour.",
|
||||||
|
"OpenSheet": "Ouvrir la fiche",
|
||||||
|
"HP": "PV",
|
||||||
|
"Stagger": "Stagger",
|
||||||
|
"Light": "Lumière",
|
||||||
|
"Emotion": "Émotion",
|
||||||
|
"AssignedSkill": "Compétence assignée",
|
||||||
|
"None": "Aucune",
|
||||||
|
"Unassign": "Désassigner",
|
||||||
|
"Die": "Dé",
|
||||||
|
"RemoveArrows": "Retirer les flèches"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
45
sotc-battle-show-ability/module.json
Normal file
45
sotc-battle-show-ability/module.json
Normal file
|
|
@ -0,0 +1,45 @@
|
||||||
|
{
|
||||||
|
"id": "sotc-battle-show-ability",
|
||||||
|
"title": "SotC Battle Show Ability",
|
||||||
|
"description": "Panneau contextuel sur les Dés de Vitesse dans le tracker de combat SotC. Permet d'assigner des compétences (skill/ego) à chaque Dé de Vitesse, avec validation des coûts en Lumière et Émotion, et gestion manuelle des états.",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"compatibility": {
|
||||||
|
"minimum": "11",
|
||||||
|
"verified": "11.315"
|
||||||
|
},
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Auteur"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"esmodules": [
|
||||||
|
"module/main.js"
|
||||||
|
],
|
||||||
|
"styles": [
|
||||||
|
"styles/sotc-bsa.css"
|
||||||
|
],
|
||||||
|
"languages": [
|
||||||
|
{
|
||||||
|
"lang": "fr",
|
||||||
|
"name": "Français",
|
||||||
|
"path": "lang/fr.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"lang": "en",
|
||||||
|
"name": "English",
|
||||||
|
"path": "lang/en.json"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"relationships": {
|
||||||
|
"systems": [
|
||||||
|
{
|
||||||
|
"id": "sotc",
|
||||||
|
"type": "requires",
|
||||||
|
"compatibility": {
|
||||||
|
"minimum": "1.05"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"flags": {}
|
||||||
|
}
|
||||||
401
sotc-battle-show-ability/module/SpeedDiePanelApp.js
Normal file
401
sotc-battle-show-ability/module/SpeedDiePanelApp.js
Normal file
|
|
@ -0,0 +1,401 @@
|
||||||
|
/**
|
||||||
|
* sotc-battle-show-ability — SpeedDiePanelApp
|
||||||
|
* Fenêtre Application FoundryVTT affichant le panneau d'un Dé de Vitesse.
|
||||||
|
*
|
||||||
|
* Architecture SotC :
|
||||||
|
* - 1 token avec N dés de vitesse → N combatants dans le tracker
|
||||||
|
* - Combatant principal : flags.sotc.isSpeedDieClone absent/false
|
||||||
|
* - Clones : flags.sotc.isSpeedDieClone = true, flags.sotc.speedDieIndex = i
|
||||||
|
* - État utilisé : combatant.flags.sotc.used (boolean)
|
||||||
|
* - Compétence assignée : flag module "sotc-battle-show-ability" → "assignedSkillId"
|
||||||
|
*/
|
||||||
|
|
||||||
|
export class SpeedDiePanelApp extends Application {
|
||||||
|
|
||||||
|
constructor(combatantId, options = {}) {
|
||||||
|
super(options);
|
||||||
|
this.combatantId = combatantId;
|
||||||
|
this._activeTab = "skills";
|
||||||
|
this._previewSkillId = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
static get defaultOptions() {
|
||||||
|
return foundry.utils.mergeObject(super.defaultOptions, {
|
||||||
|
id: "sotc-bsa-panel",
|
||||||
|
title: game.i18n.localize("SOTC_BSA.Panel.Title"),
|
||||||
|
template: "modules/sotc-battle-show-ability/templates/speed-die-panel.hbs",
|
||||||
|
classes: ["sotc-bsa-window"],
|
||||||
|
width: 320,
|
||||||
|
height: "auto",
|
||||||
|
resizable: false,
|
||||||
|
popOut: true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ------------------------------------------------------------------ */
|
||||||
|
/* Helpers */
|
||||||
|
/* ------------------------------------------------------------------ */
|
||||||
|
|
||||||
|
get combatant() {
|
||||||
|
return game.combat?.combatants.get(this.combatantId) ?? null;
|
||||||
|
}
|
||||||
|
|
||||||
|
get actor() {
|
||||||
|
return this.combatant?.actor ?? null;
|
||||||
|
}
|
||||||
|
|
||||||
|
get dieNumber() {
|
||||||
|
// Position visuelle 1-based dans l'ordre SIO (tri par initiative décroissante)
|
||||||
|
return this.sioMarkerIndex + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
get dieState() {
|
||||||
|
const c = this.combatant;
|
||||||
|
if (!c) return 0;
|
||||||
|
// Si une compétence est assignée, l'état est toujours forcé à 3 (Assigned)
|
||||||
|
if (this.assignedSkillId) return 3;
|
||||||
|
const state = c.getFlag("sotc-battle-show-ability", "dieState");
|
||||||
|
if (state === undefined || state === null) {
|
||||||
|
return c.getFlag("sotc", "used") ? 1 : 0;
|
||||||
|
}
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
|
||||||
|
get assignedSkillId() {
|
||||||
|
return this.combatant?.getFlag("sotc-battle-show-ability", "assignedSkillId") ?? null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calcule l'index du marqueur SIO pour ce combatant.
|
||||||
|
* SIO trie les combatants par initiative décroissante — le combatant avec
|
||||||
|
* la plus haute initiative est à l'index 0 (marqueur le plus à gauche).
|
||||||
|
* speedDieIndex est fixe à la création et ne reflète pas cet ordre.
|
||||||
|
*/
|
||||||
|
get sioMarkerIndex() {
|
||||||
|
const combatant = this.combatant;
|
||||||
|
if (!combatant) return 0;
|
||||||
|
const tokenId = combatant.tokenId;
|
||||||
|
const sorted = game.combat.combatants
|
||||||
|
.filter(c => c.tokenId === tokenId)
|
||||||
|
.sort((a, b) => (b.initiative ?? -Infinity) - (a.initiative ?? -Infinity));
|
||||||
|
const idx = sorted.findIndex(c => c.id === combatant.id);
|
||||||
|
return idx === -1 ? 0 : idx;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ------------------------------------------------------------------ */
|
||||||
|
/* getData */
|
||||||
|
/* ------------------------------------------------------------------ */
|
||||||
|
|
||||||
|
async getData(options = {}) {
|
||||||
|
const actor = this.actor;
|
||||||
|
const combatant = this.combatant;
|
||||||
|
|
||||||
|
if (!actor || !combatant) return { error: true };
|
||||||
|
|
||||||
|
const sys = actor.system;
|
||||||
|
|
||||||
|
const hpValue = sys.health?.value ?? 0;
|
||||||
|
const hpMax = sys.health?.max ?? 1;
|
||||||
|
const staggerValue = sys.stagger?.value ?? 0;
|
||||||
|
const staggerMax = sys.stagger?.max ?? 1;
|
||||||
|
const lightValue = sys.light?.value ?? 0;
|
||||||
|
const lightMax = sys.light?.max ?? 1;
|
||||||
|
const emotion = sys.emotion ?? 0;
|
||||||
|
|
||||||
|
const assignedId = this.assignedSkillId;
|
||||||
|
|
||||||
|
const mapItem = (item) => {
|
||||||
|
const lc = item.system.light_cost ?? 0;
|
||||||
|
const ec = item.system.emotion_cost ?? 0;
|
||||||
|
return {
|
||||||
|
id: item.id,
|
||||||
|
name: item.name,
|
||||||
|
img: item.img,
|
||||||
|
lightCost: lc,
|
||||||
|
emotionCost: ec,
|
||||||
|
canAffordLight: lightValue >= lc,
|
||||||
|
canAffordEmotion: emotion >= ec,
|
||||||
|
selected: item.id === this._previewSkillId,
|
||||||
|
weight: item.system.weight ?? 0
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const skills = actor.items.filter(i => i.type === "skill").map(mapItem);
|
||||||
|
const egos = actor.items.filter(i => i.type === "ego").map(mapItem);
|
||||||
|
|
||||||
|
let assignedSkill = null;
|
||||||
|
if (assignedId) {
|
||||||
|
const item = actor.items.get(assignedId);
|
||||||
|
if (item) assignedSkill = { id: item.id, name: item.name, img: item.img };
|
||||||
|
}
|
||||||
|
|
||||||
|
let selectedSkillPreview = null;
|
||||||
|
if (this._previewSkillId) {
|
||||||
|
const item = actor.items.get(this._previewSkillId);
|
||||||
|
if (item) {
|
||||||
|
const rawDice = item.system.dice?.die ?? [];
|
||||||
|
const diceArr = Array.isArray(rawDice) ? rawDice : Object.values(rawDice);
|
||||||
|
selectedSkillPreview = {
|
||||||
|
name: item.name,
|
||||||
|
dice: diceArr.map(d => ({
|
||||||
|
type: d.type ?? "slash",
|
||||||
|
formula: d.formula ?? "",
|
||||||
|
mods: Array.isArray(d.mods) ? d.mods.filter(m => m) : Object.values(d.mods ?? {}).filter(m => m)
|
||||||
|
})),
|
||||||
|
modules: item.system.skill_modules?.mods
|
||||||
|
? (Array.isArray(item.system.skill_modules.mods)
|
||||||
|
? item.system.skill_modules.mods.filter(m => m).join(" | ")
|
||||||
|
: String(item.system.skill_modules.mods))
|
||||||
|
: ""
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const arrowStore = globalThis.sotcClashArrow?.ArrowStore ?? null;
|
||||||
|
const sioIdx = this.sioMarkerIndex;
|
||||||
|
const hasArrows = arrowStore && combatant?.tokenId
|
||||||
|
? arrowStore.all().some(a => a.fromToken === combatant.tokenId && a.fromDie === sioIdx)
|
||||||
|
: false;
|
||||||
|
|
||||||
|
return {
|
||||||
|
tokenId: combatant.tokenId,
|
||||||
|
combatantId: this.combatantId,
|
||||||
|
actorId: actor.id,
|
||||||
|
tokenName: combatant.name,
|
||||||
|
tokenImg: actor.img,
|
||||||
|
isClone: !!combatant.getFlag("sotc", "isSpeedDieClone"),
|
||||||
|
dieNumber: this.dieNumber,
|
||||||
|
hpValue, hpMax,
|
||||||
|
hpPercent: Math.max(0, Math.min(100, Math.round((hpValue / hpMax) * 100))),
|
||||||
|
staggerValue, staggerMax,
|
||||||
|
staggerPercent: Math.max(0, Math.min(100, Math.round((staggerValue / staggerMax) * 100))),
|
||||||
|
lightValue, lightMax,
|
||||||
|
emotion,
|
||||||
|
skills,
|
||||||
|
egos,
|
||||||
|
assignedSkill,
|
||||||
|
selectedSkillPreview,
|
||||||
|
dieState: this.dieState,
|
||||||
|
activeTab: this._activeTab,
|
||||||
|
hasArrows,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ------------------------------------------------------------------ */
|
||||||
|
/* Listeners */
|
||||||
|
/* ------------------------------------------------------------------ */
|
||||||
|
|
||||||
|
activateListeners(html) {
|
||||||
|
super.activateListeners(html);
|
||||||
|
|
||||||
|
html.find(".open-sheet").click(ev => {
|
||||||
|
const actorId = ev.currentTarget.dataset.actorId;
|
||||||
|
const actor = game.actors.get(actorId);
|
||||||
|
actor?.sheet?.render(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
html.find(".bsa-tab").click(ev => {
|
||||||
|
const tab = ev.currentTarget.dataset.tab;
|
||||||
|
this._activeTab = tab;
|
||||||
|
html.find(".bsa-tab").removeClass("active");
|
||||||
|
html.find(".bsa-tab-content").removeClass("active");
|
||||||
|
ev.currentTarget.classList.add("active");
|
||||||
|
html.find(`.bsa-tab-${tab}`).addClass("active");
|
||||||
|
});
|
||||||
|
|
||||||
|
html.find(".bsa-skill-row").click(ev => {
|
||||||
|
if (ev.target.closest(".bsa-btn-assign")) return;
|
||||||
|
const skillId = ev.currentTarget.dataset.skillId;
|
||||||
|
this._previewSkillId = (this._previewSkillId === skillId) ? null : skillId;
|
||||||
|
this.render(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
html.find(".bsa-btn-assign").click(ev => {
|
||||||
|
ev.stopPropagation();
|
||||||
|
const skillId = ev.currentTarget.dataset.skillId;
|
||||||
|
this._onAssignSkill(skillId);
|
||||||
|
});
|
||||||
|
|
||||||
|
html.find(".bsa-btn-unassign").click(() => {
|
||||||
|
this._onUnassignSkill();
|
||||||
|
});
|
||||||
|
|
||||||
|
html.find(".bsa-btn-remove-arrows").click(() => {
|
||||||
|
this._onRemoveArrows();
|
||||||
|
});
|
||||||
|
|
||||||
|
html.find(".bsa-state-btn").click(ev => {
|
||||||
|
const state = parseInt(ev.currentTarget.dataset.state);
|
||||||
|
this._onSetDieState(state);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ------------------------------------------------------------------ */
|
||||||
|
/* Actions */
|
||||||
|
/* ------------------------------------------------------------------ */
|
||||||
|
|
||||||
|
// Gérer l'assignation d'une compétence au dé de vitesse
|
||||||
|
async _onAssignSkill(skillId) {
|
||||||
|
const actor = this.actor;
|
||||||
|
const combatant = this.combatant;
|
||||||
|
if (!actor || !combatant) return;
|
||||||
|
|
||||||
|
const item = actor.items.get(skillId);
|
||||||
|
if (!item) return;
|
||||||
|
|
||||||
|
if (this.assignedSkillId) {
|
||||||
|
ui.notifications.warn(game.i18n.localize("SOTC_BSA.Panel.AlreadyAssigned"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const lightCost = item.system.light_cost ?? 0;
|
||||||
|
const emotionCost = item.system.emotion_cost ?? 0;
|
||||||
|
const currentLight = actor.system.light?.value ?? 0;
|
||||||
|
const currentEmotion = actor.system.emotion ?? 0;
|
||||||
|
|
||||||
|
if (lightCost > 0 && currentLight < lightCost) {
|
||||||
|
ui.notifications.error(
|
||||||
|
game.i18n.format("SOTC_BSA.Panel.InsufficientLight", { cost: lightCost, current: currentLight })
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (emotionCost > 0 && currentEmotion < emotionCost) {
|
||||||
|
ui.notifications.error(
|
||||||
|
game.i18n.format("SOTC_BSA.Panel.InsufficientEmotion", { cost: emotionCost, current: currentEmotion })
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const updates = {};
|
||||||
|
if (lightCost > 0) updates["system.light.value"] = Math.max(0, currentLight - lightCost);
|
||||||
|
if (emotionCost > 0) updates["system.emotion"] = Math.max(0, currentEmotion - emotionCost);
|
||||||
|
if (Object.keys(updates).length > 0) await actor.update(updates);
|
||||||
|
|
||||||
|
await combatant.setFlag("sotc-battle-show-ability", "assignedSkillId", skillId);
|
||||||
|
// Toujours forcer l'état à 3 (Assigned) dès qu'une compétence est assignée
|
||||||
|
await combatant.setFlag("sotc-battle-show-ability", "dieState", 3);
|
||||||
|
await combatant.setFlag("sotc", "used", true);
|
||||||
|
|
||||||
|
// Mettre le marqueur SIO à l'état 3 (DiceSlotIcon4) pour indiquer l'assignation
|
||||||
|
const tokenDocAssign = combatant.token;
|
||||||
|
if (tokenDocAssign && game.modules.get("stars-initiative-overlay")?.active) {
|
||||||
|
const dieIndexAssign = this.sioMarkerIndex;
|
||||||
|
const numDiceAssign = combatant.actor?.system?.speed_dice?.num_dice ?? 1;
|
||||||
|
const statesAssign = [...(tokenDocAssign.getFlag("stars-initiative-overlay", "diceStates") || [])];
|
||||||
|
while (statesAssign.length < numDiceAssign) statesAssign.push(0);
|
||||||
|
statesAssign[dieIndexAssign] = 3;
|
||||||
|
await tokenDocAssign.setFlag("stars-initiative-overlay", "diceStates", statesAssign);
|
||||||
|
}
|
||||||
|
|
||||||
|
ui.notifications.info(game.i18n.localize("SOTC_BSA.Panel.AssignSuccess"));
|
||||||
|
this._previewSkillId = null;
|
||||||
|
this.render(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Gérer la désassignation de la compétence du dé de vitesse
|
||||||
|
async _onUnassignSkill() {
|
||||||
|
const combatant = this.combatant;
|
||||||
|
const actor = this.actor;
|
||||||
|
if (!combatant || !actor) return;
|
||||||
|
|
||||||
|
const skillId = this.assignedSkillId;
|
||||||
|
if (skillId) {
|
||||||
|
const item = actor.items.get(skillId);
|
||||||
|
if (item) {
|
||||||
|
const lightCost = item.system.light_cost ?? 0;
|
||||||
|
const emotionCost = item.system.emotion_cost ?? 0;
|
||||||
|
const updates = {};
|
||||||
|
|
||||||
|
if (lightCost > 0) {
|
||||||
|
const currentLight = actor.system.light?.value ?? 0;
|
||||||
|
const maxLight = actor.system.light?.max ?? currentLight;
|
||||||
|
updates["system.light.value"] = Math.min(currentLight + lightCost, maxLight);
|
||||||
|
}
|
||||||
|
if (emotionCost > 0) {
|
||||||
|
const currentEmotion = actor.system.emotion ?? 0;
|
||||||
|
updates["system.emotion"] = currentEmotion + emotionCost;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Object.keys(updates).length > 0) await actor.update(updates);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// On force l'ordre : d'abord on retire l'assignation, puis on met l'état à 0
|
||||||
|
await combatant.unsetFlag("sotc-battle-show-ability", "assignedSkillId");
|
||||||
|
await combatant.setFlag("sotc-battle-show-ability", "dieState", 0);
|
||||||
|
await combatant.setFlag("sotc", "used", false);
|
||||||
|
|
||||||
|
// Synchronisation SIO
|
||||||
|
const tokenDoc = combatant.token;
|
||||||
|
if (tokenDoc && game.modules.get("stars-initiative-overlay")?.active) {
|
||||||
|
const dieIndex = this.sioMarkerIndex;
|
||||||
|
const numDice = combatant.actor?.system?.speed_dice?.num_dice ?? 1;
|
||||||
|
const states = [...(tokenDoc.getFlag("stars-initiative-overlay", "diceStates") || [])];
|
||||||
|
while (states.length < numDice) states.push(0);
|
||||||
|
states[dieIndex] = 0; // on force explicitement à Unused
|
||||||
|
|
||||||
|
await tokenDoc.setFlag("stars-initiative-overlay", "diceStates", states);
|
||||||
|
}
|
||||||
|
|
||||||
|
this._previewSkillId = null;
|
||||||
|
this.render(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Gérer le changement d'état du dé de vitesse (Unused, Used, Saved, Assigned)
|
||||||
|
_onRemoveArrows() {
|
||||||
|
const arrowStore = globalThis.sotcClashArrow?.ArrowStore ?? null;
|
||||||
|
if (!arrowStore) { ui.notifications.warn("sotc-clash-arrow n'est pas actif."); return; }
|
||||||
|
const combatant = this.combatant;
|
||||||
|
if (!combatant) return;
|
||||||
|
const tokenId = combatant.tokenId;
|
||||||
|
const sioIdx = this.sioMarkerIndex;
|
||||||
|
const toRemove = arrowStore.all()
|
||||||
|
.filter(a => a.fromToken === tokenId && a.fromDie === sioIdx)
|
||||||
|
.map(a => a.id);
|
||||||
|
if (toRemove.length === 0) { ui.notifications.info("Aucune flèche à retirer pour ce dé."); return; }
|
||||||
|
for (const id of toRemove) arrowStore.removeArrow(id);
|
||||||
|
ui.notifications.info(`${toRemove.length} flèche(s) retirée(s).`);
|
||||||
|
this.render(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
async _onSetDieState(state) {
|
||||||
|
const combatant = this.combatant;
|
||||||
|
if (!combatant) return;
|
||||||
|
|
||||||
|
// Si une compétence est assignée, l'état est verrouillé à 3
|
||||||
|
const effectiveState = this.assignedSkillId ? 3 : state;
|
||||||
|
|
||||||
|
await combatant.setFlag("sotc-battle-show-ability", "dieState", effectiveState);
|
||||||
|
|
||||||
|
const isUsed = (effectiveState === 1 || effectiveState === 2 || effectiveState === 3);
|
||||||
|
await combatant.setFlag("sotc", "used", isUsed);
|
||||||
|
|
||||||
|
// Synchroniser le flag SIO pour mettre à jour l'icône PIXI
|
||||||
|
const tokenDoc = combatant.token;
|
||||||
|
if (tokenDoc && game.modules.get("stars-initiative-overlay")?.active) {
|
||||||
|
const dieIndex = this.sioMarkerIndex;
|
||||||
|
const numDice = combatant.actor?.system?.speed_dice?.num_dice ?? 1;
|
||||||
|
const currentStates = tokenDoc.getFlag("stars-initiative-overlay", "diceStates") || [];
|
||||||
|
const states = [...currentStates];
|
||||||
|
while (states.length < numDice) states.push(0);
|
||||||
|
states[dieIndex] = effectiveState;
|
||||||
|
await tokenDoc.setFlag("stars-initiative-overlay", "diceStates", states);
|
||||||
|
}
|
||||||
|
|
||||||
|
ui.notifications.info(game.i18n.localize("SOTC_BSA.Panel.StateChanged"));
|
||||||
|
this.render(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ------------------------------------------------------------------ */
|
||||||
|
/* Validité */
|
||||||
|
/* ------------------------------------------------------------------ */
|
||||||
|
|
||||||
|
_checkStillValid() {
|
||||||
|
if (!game.combat || !this.combatant) {
|
||||||
|
this.close();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
263
sotc-battle-show-ability/module/main.js
Normal file
263
sotc-battle-show-ability/module/main.js
Normal file
|
|
@ -0,0 +1,263 @@
|
||||||
|
/**
|
||||||
|
* sotc-battle-show-ability — main.js
|
||||||
|
* Point d'entrée du module.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { SpeedDiePanelApp } from "./SpeedDiePanelApp.js";
|
||||||
|
|
||||||
|
/* ------------------------------------------------------------------ */
|
||||||
|
/* Initialisation */
|
||||||
|
/* ------------------------------------------------------------------ */
|
||||||
|
|
||||||
|
Hooks.once("init", () => {
|
||||||
|
console.log("sotc-battle-show-ability | Initialisation du module");
|
||||||
|
|
||||||
|
loadTemplates([
|
||||||
|
"modules/sotc-battle-show-ability/templates/speed-die-panel.hbs"
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Note : le helper Handlebars "eq" est déjà enregistré par le système SotC.
|
||||||
|
});
|
||||||
|
|
||||||
|
/* ------------------------------------------------------------------ */
|
||||||
|
/* Menu contextuel dans le Combat Tracker */
|
||||||
|
/* ------------------------------------------------------------------ */
|
||||||
|
|
||||||
|
Hooks.on("getCombatTrackerEntryContext", (html, options) => {
|
||||||
|
if (game.system.id !== "sotc") return;
|
||||||
|
|
||||||
|
options.push({
|
||||||
|
name: game.i18n.localize("SOTC_BSA.Panel.Title"),
|
||||||
|
icon: '<i class="fas fa-dice-d6"></i>',
|
||||||
|
condition: (li) => {
|
||||||
|
if (!game.combat) return false;
|
||||||
|
const combatantId = li.data("combatant-id");
|
||||||
|
const combatant = game.combat.combatants.get(combatantId);
|
||||||
|
if (!combatant) return false;
|
||||||
|
return combatant.isOwner || game.user.isGM;
|
||||||
|
},
|
||||||
|
callback: (li) => {
|
||||||
|
const combatantId = li.data("combatant-id");
|
||||||
|
_openSpeedDiePanel(combatantId);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
/* ------------------------------------------------------------------ */
|
||||||
|
/* Ouverture du panneau */
|
||||||
|
/* ------------------------------------------------------------------ */
|
||||||
|
|
||||||
|
function _openSpeedDiePanel(combatantId) {
|
||||||
|
const existing = Object.values(ui.windows).find(
|
||||||
|
w => w instanceof SpeedDiePanelApp && w.combatantId === combatantId
|
||||||
|
);
|
||||||
|
|
||||||
|
if (existing) {
|
||||||
|
existing.render(true);
|
||||||
|
existing.bringToTop();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const panel = new SpeedDiePanelApp(combatantId);
|
||||||
|
|
||||||
|
const combatant = game.combat?.combatants.get(combatantId);
|
||||||
|
if (combatant) {
|
||||||
|
const numDice = combatant.actor?.system?.speed_dice?.num_dice ?? 1;
|
||||||
|
// Index visuel : position dans le tri par initiative décroissante
|
||||||
|
const sorted = game.combat.combatants
|
||||||
|
.filter(c => c.tokenId === combatant.tokenId)
|
||||||
|
.sort((a, b) => (b.initiative ?? -Infinity) - (a.initiative ?? -Infinity));
|
||||||
|
const dieIndex = sorted.findIndex(c => c.id === combatant.id) + 1;
|
||||||
|
panel.options.title = numDice > 1
|
||||||
|
? `${game.i18n.localize("SOTC_BSA.Panel.SpeedDie")} #${dieIndex} — ${combatant.name}`
|
||||||
|
: `${game.i18n.localize("SOTC_BSA.Panel.Title")} — ${combatant.name}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
panel.render(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ------------------------------------------------------------------ */
|
||||||
|
/* Clic droit sur les marqueurs PIXI de SIO */
|
||||||
|
/* ------------------------------------------------------------------ */
|
||||||
|
|
||||||
|
function _installSIORightClickHandler() {
|
||||||
|
const board = document.getElementById("board");
|
||||||
|
if (!board) return;
|
||||||
|
|
||||||
|
if (board._bsaRightClickHandler) {
|
||||||
|
board.removeEventListener("mousedown", board._bsaRightClickHandler);
|
||||||
|
}
|
||||||
|
|
||||||
|
const handler = async (event) => {
|
||||||
|
if (event.button !== 2) return;
|
||||||
|
if (!game.combat?.active) return;
|
||||||
|
|
||||||
|
const rect = board.getBoundingClientRect();
|
||||||
|
const screenX = event.clientX - rect.left;
|
||||||
|
const screenY = event.clientY - rect.top;
|
||||||
|
|
||||||
|
for (const token of canvas.tokens.placeables) {
|
||||||
|
const markers = token._initiativeMarkers;
|
||||||
|
if (!markers?.length) continue;
|
||||||
|
|
||||||
|
for (let i = 0; i < markers.length; i++) {
|
||||||
|
const m = markers[i];
|
||||||
|
const b = m.getBounds();
|
||||||
|
|
||||||
|
const inside =
|
||||||
|
screenX >= b.x &&
|
||||||
|
screenX <= b.x + b.width &&
|
||||||
|
screenY >= b.y &&
|
||||||
|
screenY <= b.y + b.height;
|
||||||
|
|
||||||
|
if (!inside) continue;
|
||||||
|
|
||||||
|
const combatants = game.combat.combatants
|
||||||
|
.filter(c => c.tokenId === token.document.id)
|
||||||
|
.sort((a, b) => (b.initiative ?? -Infinity) - (a.initiative ?? -Infinity));
|
||||||
|
|
||||||
|
const combatant = combatants[i];
|
||||||
|
if (!combatant) return;
|
||||||
|
|
||||||
|
if (!combatant.isOwner && !game.user.isGM) return;
|
||||||
|
|
||||||
|
event.preventDefault();
|
||||||
|
event.stopPropagation();
|
||||||
|
|
||||||
|
_openSpeedDiePanel(combatant.id);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
board._bsaRightClickHandler = handler;
|
||||||
|
board.addEventListener("mousedown", handler);
|
||||||
|
|
||||||
|
if (!board._bsaContextMenuHandler) {
|
||||||
|
board._bsaContextMenuHandler = (event) => {
|
||||||
|
const rect = board.getBoundingClientRect();
|
||||||
|
const screenX = event.clientX - rect.left;
|
||||||
|
const screenY = event.clientY - rect.top;
|
||||||
|
for (const token of canvas.tokens.placeables) {
|
||||||
|
const markers = token._initiativeMarkers;
|
||||||
|
if (!markers?.length) continue;
|
||||||
|
for (const m of markers) {
|
||||||
|
const b = m.getBounds();
|
||||||
|
if (screenX >= b.x && screenX <= b.x + b.width &&
|
||||||
|
screenY >= b.y && screenY <= b.y + b.height) {
|
||||||
|
event.preventDefault();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
board.addEventListener("contextmenu", board._bsaContextMenuHandler);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Hooks.on("canvasReady", () => {
|
||||||
|
_installSIORightClickHandler();
|
||||||
|
});
|
||||||
|
|
||||||
|
/* ------------------------------------------------------------------ */
|
||||||
|
/* Réinitialisation au début d'un nouveau round */
|
||||||
|
/* ------------------------------------------------------------------ */
|
||||||
|
|
||||||
|
async function _resetAllCombatants(combat) {
|
||||||
|
if (!game.user.isGM) return;
|
||||||
|
|
||||||
|
const updates = [];
|
||||||
|
|
||||||
|
for (const combatant of combat.combatants) {
|
||||||
|
const actor = combatant.actor;
|
||||||
|
if (!actor?.system?.speed_dice) continue;
|
||||||
|
|
||||||
|
updates.push({
|
||||||
|
_id: combatant.id,
|
||||||
|
"flags.sotc-battle-show-ability.dieState": 0,
|
||||||
|
"flags.sotc-battle-show-ability.assignedSkillId": null,
|
||||||
|
"flags.sotc.used": false
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (updates.length > 0) {
|
||||||
|
await combat.updateEmbeddedDocuments("Combatant", updates);
|
||||||
|
console.log(`sotc-battle-show-ability | ${updates.length} dé(s) réinitialisé(s)`);
|
||||||
|
}
|
||||||
|
|
||||||
|
_refreshOpenPanels();
|
||||||
|
}
|
||||||
|
|
||||||
|
Hooks.on("combatRound", async (combat, roundData, options) => {
|
||||||
|
if (game.system.id !== "sotc") return;
|
||||||
|
console.log("sotc-battle-show-ability | Nouveau round — réinitialisation des dés de vitesse");
|
||||||
|
await _resetAllCombatants(combat);
|
||||||
|
});
|
||||||
|
|
||||||
|
Hooks.on("deleteCombat", async (combat, options, userId) => {
|
||||||
|
// Fermer les panneaux ouverts
|
||||||
|
for (const win of Object.values(ui.windows)) {
|
||||||
|
if (win instanceof SpeedDiePanelApp) win.close();
|
||||||
|
}
|
||||||
|
// Reset des flags (utile si le combat est supprimé sans être terminé)
|
||||||
|
console.log("sotc-battle-show-ability | Fin du combat — réinitialisation des dés de vitesse");
|
||||||
|
await _resetAllCombatants(combat);
|
||||||
|
});
|
||||||
|
|
||||||
|
/* ------------------------------------------------------------------ */
|
||||||
|
/* Rafraîchissement réactif des panneaux ouverts */
|
||||||
|
/* ------------------------------------------------------------------ */
|
||||||
|
|
||||||
|
function _refreshOpenPanels() {
|
||||||
|
for (const win of Object.values(ui.windows)) {
|
||||||
|
if (win instanceof SpeedDiePanelApp) {
|
||||||
|
if (!win._checkStillValid()) continue;
|
||||||
|
win.render(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Hooks.on("updateCombatant", (combatant, changes, options, userId) => {
|
||||||
|
if (game.system.id !== "sotc") return;
|
||||||
|
|
||||||
|
for (const win of Object.values(ui.windows)) {
|
||||||
|
if (win instanceof SpeedDiePanelApp && win.combatantId === combatant.id) {
|
||||||
|
win.render(false);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Hooks.on("updateActor", (actor, changes, options, userId) => {
|
||||||
|
if (game.system.id !== "sotc") return;
|
||||||
|
|
||||||
|
for (const win of Object.values(ui.windows)) {
|
||||||
|
if (win instanceof SpeedDiePanelApp && win.actor?.id === actor.id) {
|
||||||
|
win.render(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Hooks.on("deleteCombatant", (combatant, options, userId) => {
|
||||||
|
for (const win of Object.values(ui.windows)) {
|
||||||
|
if (win instanceof SpeedDiePanelApp && win.combatantId === combatant.id) {
|
||||||
|
win.close();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/* ------------------------------------------------------------------ */
|
||||||
|
/* API publique */
|
||||||
|
/* ------------------------------------------------------------------ */
|
||||||
|
|
||||||
|
Hooks.once("ready", () => {
|
||||||
|
game.modules.get("sotc-battle-show-ability").api = {
|
||||||
|
openSpeedDiePanel: _openSpeedDiePanel,
|
||||||
|
SpeedDiePanelApp
|
||||||
|
};
|
||||||
|
|
||||||
|
console.log("sotc-battle-show-ability | Module prêt.");
|
||||||
|
});
|
||||||
543
sotc-battle-show-ability/styles/sotc-bsa.css
Normal file
543
sotc-battle-show-ability/styles/sotc-bsa.css
Normal file
|
|
@ -0,0 +1,543 @@
|
||||||
|
/* =========================================================
|
||||||
|
sotc-battle-show-ability — Styles principaux
|
||||||
|
Thème sombre/industriel, cohérent avec SotC
|
||||||
|
========================================================= */
|
||||||
|
|
||||||
|
/* --- Fenêtre Application --- */
|
||||||
|
.sotc-bsa-window .window-content {
|
||||||
|
padding: 0;
|
||||||
|
background: #1a1a1e;
|
||||||
|
color: #d4c9b0;
|
||||||
|
font-family: 'Barlow Condensed', 'Signika', sans-serif;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sotc-bsa-window .window-header {
|
||||||
|
background: #111116;
|
||||||
|
border-bottom: 1px solid #8b0000;
|
||||||
|
color: #e8d5a3;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- Panneau principal --- */
|
||||||
|
.sotc-bsa-panel {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 6px;
|
||||||
|
padding: 8px;
|
||||||
|
min-width: 300px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- Header : token image + nom --- */
|
||||||
|
.bsa-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 6px 4px;
|
||||||
|
border-bottom: 1px solid #3a2a2a;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bsa-token-info {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bsa-token-img {
|
||||||
|
width: 48px;
|
||||||
|
height: 48px;
|
||||||
|
border-radius: 4px;
|
||||||
|
border: 2px solid #8b0000;
|
||||||
|
cursor: pointer;
|
||||||
|
object-fit: cover;
|
||||||
|
transition: border-color 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bsa-token-img:hover {
|
||||||
|
border-color: #e8d5a3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bsa-token-name {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bsa-token-name span:first-child {
|
||||||
|
font-size: 1.1em;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #e8d5a3;
|
||||||
|
letter-spacing: 0.05em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bsa-die-index {
|
||||||
|
font-size: 0.8em;
|
||||||
|
color: #c09060;
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- Stats --- */
|
||||||
|
.bsa-stats {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 4px;
|
||||||
|
padding: 6px 4px;
|
||||||
|
border-bottom: 1px solid #3a2a2a;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bsa-stat-row {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
height: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bsa-stat-icon {
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
object-fit: contain;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bsa-stat-label {
|
||||||
|
width: 55px;
|
||||||
|
font-size: 0.8em;
|
||||||
|
color: #a09080;
|
||||||
|
flex-shrink: 0;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.05em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bsa-bar-wrap {
|
||||||
|
flex: 1;
|
||||||
|
height: 10px;
|
||||||
|
background: #2a2a2e;
|
||||||
|
border-radius: 3px;
|
||||||
|
overflow: hidden;
|
||||||
|
border: 1px solid #3a3a3e;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bsa-bar {
|
||||||
|
height: 100%;
|
||||||
|
border-radius: 3px;
|
||||||
|
transition: width 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bsa-bar-hp {
|
||||||
|
background: linear-gradient(90deg, #8b0000, #cc2200);
|
||||||
|
}
|
||||||
|
|
||||||
|
.bsa-bar-stagger {
|
||||||
|
background: linear-gradient(90deg, #005577, #0088bb);
|
||||||
|
}
|
||||||
|
|
||||||
|
.bsa-stat-value {
|
||||||
|
font-size: 0.8em;
|
||||||
|
color: #d4c9b0;
|
||||||
|
width: 50px;
|
||||||
|
text-align: right;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bsa-stat-simple .bsa-stat-value {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bsa-light-value {
|
||||||
|
color: #f0e060;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bsa-emotion-value {
|
||||||
|
color: #a060c0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- Compétence assignée --- */
|
||||||
|
.bsa-assigned-section {
|
||||||
|
padding: 6px 4px;
|
||||||
|
border-bottom: 1px solid #3a2a2a;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bsa-section-title {
|
||||||
|
font-size: 0.75em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.1em;
|
||||||
|
color: #8b0000;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bsa-assigned-skill {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
background: #252530;
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 4px 6px;
|
||||||
|
border: 1px solid #4a3a3a;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bsa-assigned-img {
|
||||||
|
width: 28px;
|
||||||
|
height: 28px;
|
||||||
|
border-radius: 3px;
|
||||||
|
object-fit: cover;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bsa-assigned-name {
|
||||||
|
flex: 1;
|
||||||
|
font-size: 0.9em;
|
||||||
|
color: #e8d5a3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bsa-assigned-none {
|
||||||
|
font-size: 0.85em;
|
||||||
|
color: #5a5a5a;
|
||||||
|
font-style: italic;
|
||||||
|
text-align: center;
|
||||||
|
padding: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- Onglets --- */
|
||||||
|
.bsa-tabs {
|
||||||
|
display: flex;
|
||||||
|
gap: 2px;
|
||||||
|
padding: 0 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bsa-tab {
|
||||||
|
flex: 1;
|
||||||
|
padding: 5px 4px;
|
||||||
|
background: #252530;
|
||||||
|
border: 1px solid #3a3a4a;
|
||||||
|
border-bottom: none;
|
||||||
|
color: #a09080;
|
||||||
|
font-size: 0.8em;
|
||||||
|
cursor: pointer;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.08em;
|
||||||
|
border-radius: 3px 3px 0 0;
|
||||||
|
transition: background 0.15s, color 0.15s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bsa-tab:hover {
|
||||||
|
background: #30303a;
|
||||||
|
color: #d4c9b0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bsa-tab.active {
|
||||||
|
background: #1a1a2a;
|
||||||
|
color: #e8d5a3;
|
||||||
|
border-color: #8b0000;
|
||||||
|
border-bottom-color: #1a1a2a;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- Contenu d'onglet --- */
|
||||||
|
.bsa-tab-content {
|
||||||
|
display: none;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 3px;
|
||||||
|
max-height: 200px;
|
||||||
|
overflow-y: auto;
|
||||||
|
padding: 4px;
|
||||||
|
background: #1a1a2a;
|
||||||
|
border: 1px solid #8b0000;
|
||||||
|
border-top: none;
|
||||||
|
border-radius: 0 0 3px 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bsa-tab-content.active {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bsa-tab-content::-webkit-scrollbar {
|
||||||
|
width: 4px;
|
||||||
|
}
|
||||||
|
.bsa-tab-content::-webkit-scrollbar-track {
|
||||||
|
background: #1a1a2a;
|
||||||
|
}
|
||||||
|
.bsa-tab-content::-webkit-scrollbar-thumb {
|
||||||
|
background: #8b0000;
|
||||||
|
border-radius: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- Ligne de compétence --- */
|
||||||
|
.bsa-skill-row {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
padding: 4px 6px;
|
||||||
|
border-radius: 3px;
|
||||||
|
background: #22222c;
|
||||||
|
border: 1px solid transparent;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background 0.15s, border-color 0.15s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bsa-skill-row:hover {
|
||||||
|
background: #2e2e3a;
|
||||||
|
border-color: #4a3a3a;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bsa-skill-row.bsa-skill-selected {
|
||||||
|
background: #2a1a1a;
|
||||||
|
border-color: #8b0000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bsa-skill-img {
|
||||||
|
width: 28px;
|
||||||
|
height: 28px;
|
||||||
|
border-radius: 3px;
|
||||||
|
object-fit: cover;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Zone centrale : nom + coûts empilés verticalement */
|
||||||
|
.bsa-skill-info {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 3px;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bsa-skill-name {
|
||||||
|
font-size: 0.85em;
|
||||||
|
color: #d4c9b0;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Coûts sous le nom, pas écrasés par le bouton */
|
||||||
|
.bsa-skill-costs {
|
||||||
|
display: flex;
|
||||||
|
gap: 5px;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bsa-cost {
|
||||||
|
font-size: 0.75em;
|
||||||
|
padding: 1px 5px;
|
||||||
|
border-radius: 2px;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bsa-cost-light {
|
||||||
|
color: #f0e060;
|
||||||
|
background: #2a2800;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bsa-cost-emotion {
|
||||||
|
color: #c080ff;
|
||||||
|
background: #1a0028;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bsa-cost-insufficient {
|
||||||
|
color: #ff4040 !important;
|
||||||
|
background: #280000 !important;
|
||||||
|
text-decoration: line-through;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- Boutons --- */
|
||||||
|
.bsa-btn {
|
||||||
|
padding: 3px 8px;
|
||||||
|
border-radius: 3px;
|
||||||
|
font-size: 0.75em;
|
||||||
|
cursor: pointer;
|
||||||
|
border: 1px solid;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.05em;
|
||||||
|
font-weight: 600;
|
||||||
|
transition: background 0.15s, color 0.15s;
|
||||||
|
white-space: nowrap;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bsa-btn-assign {
|
||||||
|
background: #1a0a0a !important;
|
||||||
|
border-color: #8b0000 !important;
|
||||||
|
color: #e8d5a3 !important;
|
||||||
|
padding: 4px 7px !important;
|
||||||
|
font-size: 0.7em !important;
|
||||||
|
width: auto !important;
|
||||||
|
min-width: unset !important;
|
||||||
|
max-width: 36px !important;
|
||||||
|
flex-shrink: 0 !important;
|
||||||
|
flex-grow: 0 !important;
|
||||||
|
align-self: center !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bsa-btn-assign:hover {
|
||||||
|
background: #8b0000;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bsa-btn-unassign {
|
||||||
|
background: #1a0a0a !important;
|
||||||
|
border-color: #8b0000 !important;
|
||||||
|
color: #e8d5a3 !important;
|
||||||
|
padding: 4px 7px !important;
|
||||||
|
font-size: 0.7em !important;
|
||||||
|
width: auto !important;
|
||||||
|
min-width: unset !important;
|
||||||
|
max-width: 36px !important;
|
||||||
|
flex-shrink: 0 !important;
|
||||||
|
flex-grow: 0 !important;
|
||||||
|
align-self: center !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bsa-btn-unassign:hover {
|
||||||
|
background: #8b0000 !important;
|
||||||
|
color: #fff !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- Aperçu des dés de la compétence --- */
|
||||||
|
.bsa-dice-preview {
|
||||||
|
padding: 6px 4px;
|
||||||
|
border-bottom: 1px solid #3a2a2a;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bsa-dice-list {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bsa-die-entry {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
padding: 3px 6px;
|
||||||
|
background: #22222c;
|
||||||
|
border-radius: 3px;
|
||||||
|
border: 1px solid #3a3a4a;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bsa-die-type-img {
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
object-fit: contain;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bsa-die-formula {
|
||||||
|
font-size: 0.9em;
|
||||||
|
color: #e8d5a3;
|
||||||
|
font-family: monospace;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bsa-die-tags {
|
||||||
|
font-size: 0.75em;
|
||||||
|
color: #a0a0c0;
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bsa-skill-modules {
|
||||||
|
margin-top: 4px;
|
||||||
|
font-size: 0.78em;
|
||||||
|
color: #8090a0;
|
||||||
|
font-style: italic;
|
||||||
|
padding: 2px 6px;
|
||||||
|
border-left: 2px solid #3a3a5a;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- État du dé --- */
|
||||||
|
.bsa-state-section {
|
||||||
|
padding: 6px 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bsa-state-buttons {
|
||||||
|
display: flex;
|
||||||
|
gap: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bsa-state-btn {
|
||||||
|
flex: 1;
|
||||||
|
padding: 5px 4px;
|
||||||
|
border-radius: 3px;
|
||||||
|
font-size: 0.72em;
|
||||||
|
cursor: pointer;
|
||||||
|
border: 1px solid #3a3a4a;
|
||||||
|
background: #22222c;
|
||||||
|
color: #a09080;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.05em;
|
||||||
|
transition: all 0.15s;
|
||||||
|
text-align: center;
|
||||||
|
line-height: 1.2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bsa-state-btn:hover {
|
||||||
|
background: #2e2e3a;
|
||||||
|
color: #d4c9b0;
|
||||||
|
border-color: #5a5a6a;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bsa-state-btn.active {
|
||||||
|
background: #8b0000;
|
||||||
|
color: #fff;
|
||||||
|
border-color: #cc2200;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bsa-state-btn:disabled {
|
||||||
|
opacity: 0.45;
|
||||||
|
cursor: not-allowed;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bsa-state-btn.active:disabled {
|
||||||
|
opacity: 1;
|
||||||
|
background: #1a6b2a;
|
||||||
|
border-color: #2ecc71;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- Vide --- */
|
||||||
|
.bsa-empty {
|
||||||
|
font-size: 0.82em;
|
||||||
|
color: #5a5a5a;
|
||||||
|
font-style: italic;
|
||||||
|
text-align: center;
|
||||||
|
padding: 12px 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- Scrollbar globale du panneau --- */
|
||||||
|
.sotc-bsa-panel::-webkit-scrollbar {
|
||||||
|
width: 4px;
|
||||||
|
}
|
||||||
|
.sotc-bsa-panel::-webkit-scrollbar-track {
|
||||||
|
background: #1a1a1e;
|
||||||
|
}
|
||||||
|
.sotc-bsa-panel::-webkit-scrollbar-thumb {
|
||||||
|
background: #8b0000;
|
||||||
|
border-radius: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── Badge Attack Weight ── */
|
||||||
|
.bsa-cost-weight {
|
||||||
|
background: rgba(180, 140, 20, 0.18);
|
||||||
|
border: 1px solid #b8860b;
|
||||||
|
color: #f0c040;
|
||||||
|
font-size: 10px;
|
||||||
|
font-weight: 700;
|
||||||
|
padding: 1px 5px;
|
||||||
|
border-radius: 3px;
|
||||||
|
letter-spacing: 0.04em;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── Bouton retirer les flèches ── */
|
||||||
|
.bsa-btn-remove-arrows {
|
||||||
|
width: 100%;
|
||||||
|
margin-top: 5px;
|
||||||
|
background: rgba(100, 30, 30, 0.25);
|
||||||
|
border: 1px solid #7a3030;
|
||||||
|
color: #c07070;
|
||||||
|
font-size: 11px;
|
||||||
|
padding: 3px 6px;
|
||||||
|
border-radius: 3px;
|
||||||
|
cursor: pointer;
|
||||||
|
text-align: center;
|
||||||
|
transition: background .15s;
|
||||||
|
}
|
||||||
|
.bsa-btn-remove-arrows:hover {
|
||||||
|
background: rgba(140, 40, 40, 0.4);
|
||||||
|
color: #e08080;
|
||||||
|
}
|
||||||
190
sotc-battle-show-ability/templates/speed-die-panel.hbs
Normal file
190
sotc-battle-show-ability/templates/speed-die-panel.hbs
Normal file
|
|
@ -0,0 +1,190 @@
|
||||||
|
{{!-- templates/speed-die-panel.hbs --}}
|
||||||
|
<div class="sotc-bsa-panel" data-token-id="{{tokenId}}" data-combatant-id="{{combatantId}}">
|
||||||
|
|
||||||
|
{{!-- HEADER : icône + nom du token --}}
|
||||||
|
<div class="bsa-header">
|
||||||
|
<div class="bsa-token-info">
|
||||||
|
<img class="bsa-token-img open-sheet" src="{{tokenImg}}" alt="{{tokenName}}" title="{{localize 'SOTC_BSA.Panel.OpenSheet'}}" data-actor-id="{{actorId}}" />
|
||||||
|
<div class="bsa-token-name">
|
||||||
|
<span>{{tokenName}}</span>
|
||||||
|
{{#if isClone}}
|
||||||
|
<span class="bsa-die-index">Dé {{dieNumber}}</span>
|
||||||
|
{{/if}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{!-- STATS --}}
|
||||||
|
<div class="bsa-stats">
|
||||||
|
{{!-- PV --}}
|
||||||
|
<div class="bsa-stat-row">
|
||||||
|
<img class="bsa-stat-icon" src="systems/sotc/assets/sheets/HPIcon.webp" alt="HP" title="{{localize 'SOTC_BSA.Panel.HP'}}" />
|
||||||
|
<span class="bsa-stat-label">{{localize 'SOTC_BSA.Panel.HP'}}</span>
|
||||||
|
<div class="bsa-bar-wrap">
|
||||||
|
<div class="bsa-bar bsa-bar-hp" style="width: {{hpPercent}}%"></div>
|
||||||
|
</div>
|
||||||
|
<span class="bsa-stat-value">{{hpValue}} / {{hpMax}}</span>
|
||||||
|
</div>
|
||||||
|
{{!-- STAGGER --}}
|
||||||
|
<div class="bsa-stat-row">
|
||||||
|
<img class="bsa-stat-icon" src="systems/sotc/assets/sheets/StaggerIcon.webp" alt="Stagger" title="{{localize 'SOTC_BSA.Panel.Stagger'}}" />
|
||||||
|
<span class="bsa-stat-label">{{localize 'SOTC_BSA.Panel.Stagger'}}</span>
|
||||||
|
<div class="bsa-bar-wrap">
|
||||||
|
<div class="bsa-bar bsa-bar-stagger" style="width: {{staggerPercent}}%"></div>
|
||||||
|
</div>
|
||||||
|
<span class="bsa-stat-value">{{staggerValue}} / {{staggerMax}}</span>
|
||||||
|
</div>
|
||||||
|
{{!-- LUMIERE --}}
|
||||||
|
<div class="bsa-stat-row bsa-stat-simple">
|
||||||
|
<img class="bsa-stat-icon" src="systems/sotc/assets/sheets/LightIcon.webp" alt="Light" title="{{localize 'SOTC_BSA.Panel.Light'}}" />
|
||||||
|
<span class="bsa-stat-label">{{localize 'SOTC_BSA.Panel.Light'}}</span>
|
||||||
|
<span class="bsa-stat-value bsa-light-value">{{lightValue}} / {{lightMax}}</span>
|
||||||
|
</div>
|
||||||
|
{{!-- EMOTION --}}
|
||||||
|
<div class="bsa-stat-row bsa-stat-simple">
|
||||||
|
<img class="bsa-stat-icon" src="systems/sotc/assets/sheets/EmotionIcon.webp" alt="Emotion" title="{{localize 'SOTC_BSA.Panel.Emotion'}}" />
|
||||||
|
<span class="bsa-stat-label">{{localize 'SOTC_BSA.Panel.Emotion'}}</span>
|
||||||
|
<span class="bsa-stat-value bsa-emotion-value">{{emotion}}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{!-- COMPÉTENCE ASSIGNÉE --}}
|
||||||
|
<div class="bsa-assigned-section">
|
||||||
|
<div class="bsa-section-title">{{localize 'SOTC_BSA.Panel.AssignedSkill'}}</div>
|
||||||
|
{{#if assignedSkill}}
|
||||||
|
<div class="bsa-assigned-skill">
|
||||||
|
<img class="bsa-assigned-img" src="{{assignedSkill.img}}" alt="{{assignedSkill.name}}" />
|
||||||
|
<span class="bsa-assigned-name">{{assignedSkill.name}}</span>
|
||||||
|
<button class="bsa-btn bsa-btn-unassign" data-action="unassign">✕</button>
|
||||||
|
</div>
|
||||||
|
{{else}}
|
||||||
|
<div class="bsa-assigned-none">— {{localize 'SOTC_BSA.Panel.None'}} —</div>
|
||||||
|
{{/if}}
|
||||||
|
{{#if hasArrows}}
|
||||||
|
<button class="bsa-btn bsa-btn-remove-arrows" title="{{localize 'SOTC_BSA.Panel.RemoveArrows'}}">
|
||||||
|
🗑 {{localize 'SOTC_BSA.Panel.RemoveArrows'}}
|
||||||
|
</button>
|
||||||
|
{{/if}}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{!-- ONGLETS COMPÉTENCES --}}
|
||||||
|
<div class="bsa-tabs">
|
||||||
|
<button class="bsa-tab active" data-tab="skills">{{localize 'SOTC_BSA.Panel.SkillsTab'}}</button>
|
||||||
|
<button class="bsa-tab" data-tab="ego">{{localize 'SOTC_BSA.Panel.EgoTab'}}</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{!-- LISTE DES COMPÉTENCES --}}
|
||||||
|
<div class="bsa-tab-content bsa-tab-skills active">
|
||||||
|
{{#if skills.length}}
|
||||||
|
{{#each skills}}
|
||||||
|
<div class="bsa-skill-row {{#if this.selected}}bsa-skill-selected{{/if}}" data-skill-id="{{this.id}}">
|
||||||
|
<img class="bsa-skill-img" src="{{this.img}}" alt="{{this.name}}" />
|
||||||
|
<div class="bsa-skill-info">
|
||||||
|
<span class="bsa-skill-name">{{this.name}}</span>
|
||||||
|
<div class="bsa-skill-costs">
|
||||||
|
{{#if this.lightCost}}
|
||||||
|
<span class="bsa-cost bsa-cost-light {{#unless this.canAffordLight}}bsa-cost-insufficient{{/unless}}">
|
||||||
|
✦ {{this.lightCost}}
|
||||||
|
</span>
|
||||||
|
{{/if}}
|
||||||
|
{{#if this.emotionCost}}
|
||||||
|
<span class="bsa-cost bsa-cost-emotion {{#unless this.canAffordEmotion}}bsa-cost-insufficient{{/unless}}">
|
||||||
|
◆ {{this.emotionCost}}
|
||||||
|
</span>
|
||||||
|
{{/if}}
|
||||||
|
{{#if (gt this.weight 1)}}
|
||||||
|
<span class="bsa-cost bsa-cost-weight" title="Attack Weight : {{this.weight}} cibles">
|
||||||
|
⚖ {{this.weight}}
|
||||||
|
</span>
|
||||||
|
{{/if}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<button class="bsa-btn bsa-btn-assign" data-action="assign" data-skill-id="{{this.id}}" title="{{localize 'SOTC_BSA.Panel.Assign'}}">
|
||||||
|
+
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
{{/each}}
|
||||||
|
{{else}}
|
||||||
|
<div class="bsa-empty">{{localize 'SOTC_BSA.Panel.NoSkills'}}</div>
|
||||||
|
{{/if}}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{!-- LISTE EGO --}}
|
||||||
|
<div class="bsa-tab-content bsa-tab-ego">
|
||||||
|
{{#if egos.length}}
|
||||||
|
{{#each egos}}
|
||||||
|
<div class="bsa-skill-row {{#if this.selected}}bsa-skill-selected{{/if}}" data-skill-id="{{this.id}}">
|
||||||
|
<img class="bsa-skill-img" src="{{this.img}}" alt="{{this.name}}" />
|
||||||
|
<div class="bsa-skill-info">
|
||||||
|
<span class="bsa-skill-name">{{this.name}}</span>
|
||||||
|
<div class="bsa-skill-costs">
|
||||||
|
{{#if this.lightCost}}
|
||||||
|
<span class="bsa-cost bsa-cost-light {{#unless this.canAffordLight}}bsa-cost-insufficient{{/unless}}">
|
||||||
|
✦ {{this.lightCost}}
|
||||||
|
</span>
|
||||||
|
{{/if}}
|
||||||
|
{{#if this.emotionCost}}
|
||||||
|
<span class="bsa-cost bsa-cost-emotion {{#unless this.canAffordEmotion}}bsa-cost-insufficient{{/unless}}">
|
||||||
|
◆ {{this.emotionCost}}
|
||||||
|
</span>
|
||||||
|
{{/if}}
|
||||||
|
{{#if (gt this.weight 1)}}
|
||||||
|
<span class="bsa-cost bsa-cost-weight" title="Attack Weight : {{this.weight}} cibles">
|
||||||
|
⚖ {{this.weight}}
|
||||||
|
</span>
|
||||||
|
{{/if}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<button class="bsa-btn bsa-btn-assign" data-action="assign" data-skill-id="{{this.id}}" title="{{localize 'SOTC_BSA.Panel.Assign'}}">
|
||||||
|
+
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
{{/each}}
|
||||||
|
{{else}}
|
||||||
|
<div class="bsa-empty">{{localize 'SOTC_BSA.Panel.NoEgo'}}</div>
|
||||||
|
{{/if}}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{!-- APERÇU DES DÉS DE LA COMPÉTENCE SÉLECTIONNÉE --}}
|
||||||
|
{{#if selectedSkillPreview}}
|
||||||
|
<div class="bsa-dice-preview">
|
||||||
|
<div class="bsa-section-title">{{localize 'SOTC_BSA.Panel.DicePreview'}}</div>
|
||||||
|
{{#if selectedSkillPreview.modules}}
|
||||||
|
<div class="bsa-skill-modules">{{selectedSkillPreview.modules}}</div>
|
||||||
|
{{/if}}
|
||||||
|
<div class="bsa-dice-list">
|
||||||
|
{{#each selectedSkillPreview.dice}}
|
||||||
|
<div class="bsa-die-entry">
|
||||||
|
<img class="bsa-die-type-img" src="systems/sotc/assets/dice types/{{this.type}}.png" alt="{{this.type}}" />
|
||||||
|
<span class="bsa-die-formula">{{this.formula}}</span>
|
||||||
|
{{#if this.mods.length}}
|
||||||
|
<span class="bsa-die-tags">
|
||||||
|
{{#each this.mods}}[{{this}}]{{/each}}
|
||||||
|
</span>
|
||||||
|
{{/if}}
|
||||||
|
</div>
|
||||||
|
{{/each}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{/if}}
|
||||||
|
|
||||||
|
{{!-- GESTION MANUELLE DE L'ÉTAT --}}
|
||||||
|
<div class="bsa-state-section">
|
||||||
|
<div class="bsa-section-title">{{localize 'SOTC_BSA.Panel.DieState'}}</div>
|
||||||
|
<div class="bsa-state-buttons">
|
||||||
|
<button class="bsa-state-btn {{#if (eq dieState 0)}}active{{/if}}" data-state="0" {{#if assignedSkill}}disabled{{/if}}>
|
||||||
|
{{localize 'SOTC_BSA.Panel.StateUnused'}}
|
||||||
|
</button>
|
||||||
|
<button class="bsa-state-btn {{#if (eq dieState 1)}}active{{/if}}" data-state="1" {{#if assignedSkill}}disabled{{/if}}>
|
||||||
|
{{localize 'SOTC_BSA.Panel.StateUsed'}}
|
||||||
|
</button>
|
||||||
|
<button class="bsa-state-btn {{#if (eq dieState 2)}}active{{/if}}" data-state="2" {{#if assignedSkill}}disabled{{/if}}>
|
||||||
|
{{localize 'SOTC_BSA.Panel.StateSaved'}}
|
||||||
|
</button>
|
||||||
|
<button class="bsa-state-btn {{#if (eq dieState 3)}}active{{/if}}" data-state="3" {{#if assignedSkill}}disabled{{/if}}>
|
||||||
|
{{localize 'SOTC_BSA.Panel.StateAssigned'}}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
Loading…
Add table
Add a link
Reference in a new issue