Ajout dialog employé
This commit is contained in:
@@ -0,0 +1,363 @@
|
||||
/**
|
||||
* Donjon & Cie - Systeme FoundryVTT
|
||||
*
|
||||
* Fenêtre de dialogue pour afficher les employés (PJ)
|
||||
* Structure : 2 onglets racine (Employés / Clients),
|
||||
* avec sous-onglets par PC ou par client.
|
||||
*
|
||||
* @author LeRatierBretonnien
|
||||
* @copyright 2025–2026 LeRatierBretonnien
|
||||
* @license CC BY-NC-SA 4.0
|
||||
*/
|
||||
|
||||
import { DonjonEtCieUtility } from "../donjon-et-cie-utility.mjs";
|
||||
import { DONJON_ET_CIE } from "../donjon-et-cie-config.mjs";
|
||||
|
||||
export class DonjonEtCieEmployesDialog {
|
||||
/**
|
||||
* Ouvre la fenêtre des employés
|
||||
*/
|
||||
static async open() {
|
||||
const pcs = this.#getPlayerCharacters();
|
||||
const clientTokens = this.#getClients();
|
||||
const characteristicKeys = this.#getCharacteristicKeys();
|
||||
|
||||
const pcsData = await Promise.all(
|
||||
pcs.map(async (pc) => this.#preparePcData(pc, characteristicKeys))
|
||||
);
|
||||
|
||||
const templateContext = {
|
||||
pcs: pcsData,
|
||||
clients: clientTokens.map(t => this.#prepareClientData(t)),
|
||||
characteristicKeys
|
||||
};
|
||||
|
||||
const content = await foundry.applications.handlebars.renderTemplate(
|
||||
"systems/fvtt-donjon-et-cie/templates/dialogs/employes-dialog.hbs",
|
||||
templateContext
|
||||
);
|
||||
|
||||
return foundry.applications.api.DialogV2.wait({
|
||||
window: {
|
||||
title: "Employés",
|
||||
icon: "fa-solid fa-hard-hat"
|
||||
},
|
||||
classes: ["fvtt-donjon-et-cie", "dnc-employes-dialog-wrapper"],
|
||||
content,
|
||||
modal: false,
|
||||
buttons: [
|
||||
{
|
||||
action: "close",
|
||||
label: "Fermer",
|
||||
icon: "fa-solid fa-xmark",
|
||||
callback: () => true
|
||||
}
|
||||
],
|
||||
rejectClose: false,
|
||||
render: (event, dialog) => this.#setupTabs(dialog)
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Récupère les personnages joueurs (employés)
|
||||
*/
|
||||
static #getPlayerCharacters() {
|
||||
return game.actors.filter(a => a.type === "character" || a.hasPlayerOwner);
|
||||
}
|
||||
|
||||
/**
|
||||
* Récupère les PNJ clients de la scène courante (retourne les TokenDocuments)
|
||||
*/
|
||||
static #getClients() {
|
||||
const scene = canvas?.scene ?? game.scenes?.current;
|
||||
if (!scene) return [];
|
||||
|
||||
return scene.tokens.filter(token => {
|
||||
const actor = token.actor;
|
||||
return actor &&
|
||||
!actor.hasPlayerOwner &&
|
||||
actor.type === "pnj" &&
|
||||
actor.system.categorie === "Client";
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Récupère les clés des caractéristiques
|
||||
*/
|
||||
static #getCharacteristicKeys() {
|
||||
return Object.entries(DONJON_ET_CIE.characteristics).map(([key, metadata]) => ({
|
||||
key,
|
||||
label: metadata.label,
|
||||
short: metadata.short
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Prépare les données d'un PJ pour l'affichage
|
||||
*/
|
||||
static async #preparePcData(pc, characteristicKeys) {
|
||||
const sys = pc.system || {};
|
||||
const items = pc.items?.contents || [];
|
||||
|
||||
const pvValue = sys.sante?.pv?.value ?? 0;
|
||||
const pvMax = sys.sante?.pv?.max ?? 0;
|
||||
const dv = sys.sante?.dv ?? "1d6";
|
||||
const meleeAttacks = sys.combat?.attaquesCorpsACorps ?? 1;
|
||||
const rangedAttacks = sys.combat?.attaquesDistance ?? 1;
|
||||
|
||||
const magicResources = DonjonEtCieUtility.getMagicResourceContext(pc);
|
||||
|
||||
const weapons = [];
|
||||
const armors = [];
|
||||
const spells = [];
|
||||
const capacities = [];
|
||||
|
||||
for (const item of items) {
|
||||
const itemSys = item.system || {};
|
||||
|
||||
if (item.type === "arme") {
|
||||
const categoryLabel = itemSys.categorie === "distance" ? "Distance" : "Corps à corps";
|
||||
const handsLabel = (itemSys.mains ?? 1) > 1 ? "2 mains" : "1 main";
|
||||
|
||||
weapons.push({
|
||||
name: item.name,
|
||||
categoryLabel,
|
||||
handsLabel,
|
||||
damage: itemSys.degatsEstUsageDe
|
||||
? `${itemSys.degats}(Δ)`
|
||||
: (itemSys.degats || "—"),
|
||||
range: itemSys.portee || "Contact",
|
||||
ammunition: itemSys.munitionsDelta != null
|
||||
? (itemSys.munitionsDelta === 0 ? "Épuisées" : `Δ${itemSys.munitionsDelta}`)
|
||||
: ""
|
||||
});
|
||||
}
|
||||
|
||||
if (item.type === "armure") {
|
||||
armors.push({
|
||||
name: item.name,
|
||||
protectionDie: `Δ${itemSys.delta || 0}`,
|
||||
encumbrance: itemSys.encombrement || "—",
|
||||
remainingProtection: itemSys.resultatProtection || "—"
|
||||
});
|
||||
}
|
||||
|
||||
if (item.type === "sortilege") {
|
||||
spells.push({
|
||||
name: item.name,
|
||||
usageLabel: itemSys.delta > 0 ? DonjonEtCieUtility.formatUsageDie(itemSys.delta) : null
|
||||
});
|
||||
}
|
||||
|
||||
if (item.type === "capacite") {
|
||||
capacities.push({
|
||||
name: item.name,
|
||||
usageLabel: itemSys.delta > 0 ? DonjonEtCieUtility.formatUsageDie(itemSys.delta) : null
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Spread des valeurs de caractéristiques directement sur l'objet pc
|
||||
const characteristics = {};
|
||||
characteristicKeys.forEach(({ key }) => {
|
||||
characteristics[key] = sys.caracteristiques?.[key]?.value ?? 0;
|
||||
});
|
||||
|
||||
return {
|
||||
actorId: pc.id,
|
||||
name: pc.name,
|
||||
concept: pc.system.concept || "",
|
||||
pvValue,
|
||||
pvMax,
|
||||
dv,
|
||||
meleeAttacks,
|
||||
rangedAttacks,
|
||||
weapons,
|
||||
armors,
|
||||
spells,
|
||||
capacities,
|
||||
magicRank: magicResources.rank,
|
||||
focusDisplay: magicResources.focusDisplay,
|
||||
chaosDisplay: magicResources.chaosLabel,
|
||||
...characteristics
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Prépare les données d'un client (PNJ) depuis son TokenDocument
|
||||
*/
|
||||
static #prepareClientData(token) {
|
||||
const client = token.actor;
|
||||
const sys = client.system || {};
|
||||
const items = client.items?.contents || [];
|
||||
|
||||
// Attaques système du modèle PNJ
|
||||
const attaques = (sys.attaques || []).filter(a => a.nom || a.degats);
|
||||
|
||||
// Items du PNJ
|
||||
const weapons = [];
|
||||
const spells = [];
|
||||
const capacities = [];
|
||||
|
||||
for (const item of items) {
|
||||
const itemSys = item.system || {};
|
||||
if (item.type === "arme") {
|
||||
weapons.push({
|
||||
name: item.name,
|
||||
damage: itemSys.degatsEstUsageDe
|
||||
? `${itemSys.degats}(Δ)`
|
||||
: (itemSys.degats || "—"),
|
||||
categoryLabel: itemSys.categorie === "distance" ? "Distance" : "Corps à corps"
|
||||
});
|
||||
}
|
||||
if (item.type === "sortilege") {
|
||||
spells.push({
|
||||
name: item.name,
|
||||
usageLabel: itemSys.delta > 0 ? DonjonEtCieUtility.formatUsageDie(itemSys.delta) : null
|
||||
});
|
||||
}
|
||||
if (item.type === "capacite") {
|
||||
capacities.push({
|
||||
name: item.name,
|
||||
usageLabel: itemSys.delta > 0 ? DonjonEtCieUtility.formatUsageDie(itemSys.delta) : null
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
id: client.id,
|
||||
tokenUuid: token.uuid,
|
||||
name: client.name,
|
||||
species: sys.espece || "",
|
||||
category: sys.categorie || "",
|
||||
role: sys.role || "",
|
||||
summary: sys.resume || "",
|
||||
pvValue: sys.sante?.pv?.value ?? 0,
|
||||
pvMax: sys.sante?.pv?.max ?? 0,
|
||||
dv: sys.sante?.dv || "1d8",
|
||||
armureDelta: sys.defense?.armure?.delta ?? 0,
|
||||
armureProtection: sys.defense?.armure?.resultatProtection ?? 0,
|
||||
courageDelta: sys.defense?.courage?.delta ?? 0,
|
||||
attaques,
|
||||
weapons,
|
||||
spells,
|
||||
capacities,
|
||||
pouvoirsSpeciaux: sys.pouvoirsSpeciaux || "",
|
||||
hasMagie: spells.length > 0 || capacities.length > 0 || !!(sys.pouvoirsSpeciaux || "").trim()
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Configuration des onglets à 2 niveaux
|
||||
* Niveau 1 : Employés / Clients (root tabs)
|
||||
* Niveau 2 : un onglet par PC ou par client (sub-tabs)
|
||||
*/
|
||||
static #setupTabs(dialog) {
|
||||
const root = dialog.element;
|
||||
|
||||
// ---- Onglets racine ----
|
||||
const rootTabs = root.querySelectorAll('.dnc-root-tab');
|
||||
const rootPanels = root.querySelectorAll('.dnc-root-panel');
|
||||
|
||||
const activateRoot = (tabName) => {
|
||||
rootTabs.forEach(btn => btn.classList.toggle('active', btn.dataset.rootTab === tabName));
|
||||
rootPanels.forEach(panel => panel.classList.toggle('active', panel.dataset.rootPanel === tabName));
|
||||
};
|
||||
|
||||
rootTabs.forEach(btn => {
|
||||
btn.addEventListener('click', (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
activateRoot(btn.dataset.rootTab);
|
||||
});
|
||||
});
|
||||
|
||||
// Activer premier onglet racine par défaut
|
||||
if (rootTabs.length > 0) activateRoot(rootTabs[0].dataset.rootTab);
|
||||
|
||||
// ---- Sous-onglets PC ----
|
||||
const pcTabs = root.querySelectorAll('.dnc-pc-tab[data-pc-tab]');
|
||||
const pcPanels = root.querySelectorAll('.dnc-pc-panel[data-pc-panel]');
|
||||
|
||||
const activatePc = (actorId) => {
|
||||
pcTabs.forEach(btn => btn.classList.toggle('active', btn.dataset.pcTab === actorId));
|
||||
pcPanels.forEach(panel => panel.classList.toggle('active', panel.dataset.pcPanel === actorId));
|
||||
};
|
||||
|
||||
pcTabs.forEach(btn => {
|
||||
btn.addEventListener('click', (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
activatePc(btn.dataset.pcTab);
|
||||
});
|
||||
});
|
||||
|
||||
if (pcTabs.length > 0) activatePc(pcTabs[0].dataset.pcTab);
|
||||
|
||||
// ---- Sous-onglets Clients ----
|
||||
const clientTabs = root.querySelectorAll('.dnc-pc-tab[data-client-tab]');
|
||||
const clientPanels = root.querySelectorAll('.dnc-client-panel[data-client-panel]');
|
||||
|
||||
const activateClient = (clientId) => {
|
||||
clientTabs.forEach(btn => btn.classList.toggle('active', btn.dataset.clientTab === clientId));
|
||||
clientPanels.forEach(panel => panel.classList.toggle('active', panel.dataset.clientPanel === clientId));
|
||||
};
|
||||
|
||||
clientTabs.forEach(btn => {
|
||||
btn.addEventListener('click', (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
activateClient(btn.dataset.clientTab);
|
||||
});
|
||||
});
|
||||
|
||||
if (clientTabs.length > 0) activateClient(clientTabs[0].dataset.clientTab);
|
||||
|
||||
// ---- Ouverture des fiches ----
|
||||
root.addEventListener('click', (e) => {
|
||||
const btn = e.target.closest('.dnc-open-sheet-btn');
|
||||
if (!btn) return;
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
const type = btn.dataset.openSheet;
|
||||
if (type === "pc") {
|
||||
const actor = game.actors.get(btn.dataset.actorId);
|
||||
actor?.sheet.render(true);
|
||||
} else if (type === "client") {
|
||||
const tokenDoc = fromUuidSync(btn.dataset.tokenUuid);
|
||||
const actor = tokenDoc?.actor ?? game.actors.get(btn.dataset.actorId);
|
||||
actor?.sheet.render(true);
|
||||
}
|
||||
});
|
||||
|
||||
// ---- Jets de dés depuis la vue clients (même dialogs que la fiche PNJ) ----
|
||||
root.addEventListener('click', async (e) => {
|
||||
const btn = e.target.closest('.dnc-roll-btn, .dnc-carac-rollable');
|
||||
if (!btn) return;
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
// Résolution de l'actor : token UUID pour gérer les tokens non-liés
|
||||
const tokenUuid = btn.dataset.tokenUuid;
|
||||
const actorId = btn.dataset.actorId;
|
||||
let actor = null;
|
||||
if (tokenUuid) {
|
||||
const tokenDoc = fromUuidSync(tokenUuid);
|
||||
actor = tokenDoc?.actor ?? null;
|
||||
}
|
||||
if (!actor && actorId) {
|
||||
actor = game.actors.get(actorId);
|
||||
}
|
||||
if (!actor) return;
|
||||
|
||||
const action = btn.dataset.pnjAction;
|
||||
switch (action) {
|
||||
case "rollArmure": return actor.rollPnjArmor();
|
||||
case "rollCourage": return actor.rollPnjCourage();
|
||||
case "rollAttaque": return actor.rollPnjAttackDamage(btn.dataset.attackIndex ?? 0);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ import { DonjonEtCieItem } from "./donjon-et-cie-item.mjs";
|
||||
import * as models from "./models/index.mjs";
|
||||
import * as sheets from "./applications/sheets/_module.mjs";
|
||||
import { DonjonEtCieRollDialog } from "./applications/donjon-et-cie-roll-dialog.mjs";
|
||||
import { DonjonEtCieEmployesDialog } from "./applications/donjon-et-cie-employes-dialog.mjs";
|
||||
import { DonjonEtCieRolls } from "./donjon-et-cie-rolls.mjs";
|
||||
import { DonjonEtCieMacros } from "./donjon-et-cie-macros.mjs";
|
||||
|
||||
@@ -233,10 +234,62 @@ Hooks.once("init", async () => {
|
||||
}
|
||||
});
|
||||
|
||||
// Gestion de la commande /employes
|
||||
// Enregistrement officiel via ChatLogV2.CHAT_COMMANDS (comme mgt2-compendium-amiral-denisov)
|
||||
function registerEmployesCommand() {
|
||||
const ChatLogV2 = foundry.applications.sidebar.tabs.ChatLog;
|
||||
|
||||
if (ChatLogV2?.CHAT_COMMANDS) {
|
||||
ChatLogV2.CHAT_COMMANDS.employes = {
|
||||
rgx: /^\/employes(?:\s+(.*))?$/i,
|
||||
fn: () => {
|
||||
DonjonEtCieEmployesDialog.open();
|
||||
return false;
|
||||
},
|
||||
};
|
||||
console.log("DNC | Commande /employes enregistrée via ChatLog.CHAT_COMMANDS");
|
||||
} else {
|
||||
console.warn("DNC | ChatLog.CHAT_COMMANDS indisponible, utilisation des hooks de fallback");
|
||||
}
|
||||
}
|
||||
|
||||
Hooks.once("init", () => {
|
||||
registerEmployesCommand();
|
||||
});
|
||||
|
||||
Hooks.once("ready", () => {
|
||||
DonjonEtCieMacros.registerSocketListeners();
|
||||
document.addEventListener("click", onChatActionClick);
|
||||
void maybeCreateWelcomeMessage();
|
||||
|
||||
// Hooks de fallback pour compatibilité
|
||||
Hooks.on("preCreateChatMessage", (message, data, options, userId) => {
|
||||
const content = data.content?.trim()?.toLowerCase();
|
||||
if (content === "/employes" || content?.startsWith("/employes ")) {
|
||||
DonjonEtCieEmployesDialog.open();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
||||
Hooks.on("chatMessage", (...args) => {
|
||||
// Gestion compatibilité v13/v14
|
||||
let message;
|
||||
if (args[0]?.content !== undefined) {
|
||||
message = args[0].content; // v14
|
||||
} else if (typeof args[1] === "string") {
|
||||
message = args[1]; // v13
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
|
||||
const trimmed = message?.trim()?.toLowerCase();
|
||||
if (trimmed === "/employes" || trimmed?.startsWith("/employes ")) {
|
||||
DonjonEtCieEmployesDialog.open();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
});
|
||||
|
||||
Hooks.on("renderActorDirectory", (app, element) => {
|
||||
|
||||
@@ -36,6 +36,7 @@ export class DonjonEtCieUtility {
|
||||
"systems/fvtt-donjon-et-cie/templates/dialogs/usage-roll.hbs",
|
||||
"systems/fvtt-donjon-et-cie/templates/dialogs/mission-pack-dialog.hbs",
|
||||
"systems/fvtt-donjon-et-cie/templates/dialogs/mission-pack-campaign-dialog.hbs",
|
||||
"systems/fvtt-donjon-et-cie/templates/dialogs/employes-dialog.hbs",
|
||||
"systems/fvtt-donjon-et-cie/templates/chat/roll-card.hbs",
|
||||
"systems/fvtt-donjon-et-cie/templates/chat/spell-card.hbs",
|
||||
"systems/fvtt-donjon-et-cie/templates/chat/chaos-card.hbs",
|
||||
|
||||
Reference in New Issue
Block a user