76870c27bf
Corrections critiques implémentées: - Remplacement du cache global mutable par ModuleCache - Binding des méthodes dans TravellerNpcDialog - Suppression des ré-exports circulaires - Validation complète des options - Correction: Duplicate export de TravellerNpcDialog et openTravellerNpcDialog - Correction: distributeSkillLevels ne supprime plus les spécialisations (ex: Pilot-Spacecraft ET Pilot-Small Craft sont maintenant conservées) Améliorations majeures: - Optimisation de l'algorithme de distribution des compétences (single-pass) - Optimisation de la génération des caractéristiques (priorité-based) - Gestion d'erreur améliorée avec TravellerNpcError - Création de TravellerNpcUtils.js avec classes utilitaires Améliorations mineures: - CSS aligné avec les styles des dialogues /commerce et /pnj - Thème clair cohérent (#f5f0e8 background, #222 text) - Fieldset, onglets, formulaires alignés sur mgt2-npc-form - Boutons et résultats stylisés comme mgt2-npc-result - Suppression des styles inline redondants dans _applyThemeStyles - Design réactif, accessibilité, impression - Tests unitaires complets pour toutes les fonctions Traductions en français: - Ajout de SKILL_LABELS_FR pour toutes les compétences Traveller - Ajout de CHARACTERISTIC_LABELS_FR pour STR, DEX, END, INT, EDU, SOC - Ajout de CITIZEN_CATEGORY_LABELS_FR, EXPERIENCE_LEVEL_LABELS_FR - Ajout de ROLE_LABELS_FR, GENDER_LABELS_FR - Mise à jour de generateTravellerNpc pour utiliser les libellés français - Mise à jour du template traveller-npc-result.hbs pour afficher labelFr - Mise à jour du template traveller-npc-dialog.hbs avec libellés français - Mise à jour de TravellerNpcDialog._prepareContext pour utiliser les libellés FR Vérification des compétences: ✅ Pilot: 13 compétences (avant: 12, perdait Pilot-Small Craft) ✅ Engineer: 13 compétences (avant: 10, perdait 3 spécialisations) ✅ Tous les rôles conservent leurs spécialisations Fichiers ajoutés: - scripts/utils/travellerNpcUtils.js - scripts/tests/travellerNpcGenerator.test.js Fichiers modifiés: - scripts/data/travellerNpcGenerator.js (+ traductions FR) - scripts/travellerNpcGenerator.js (+ fonctions getSkillLabelFr, getCharacteristicLabelFr) - scripts/TravellerNpcDialog.js (libellés FR dans _prepareContext) - scripts/npc.js - styles/traveller-npc.css - templates/traveller-npc-dialog.hbs - templates/traveller-npc-result.hbs - module.json Generated by Mistral Vibe. Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
195 lines
6.0 KiB
JavaScript
195 lines
6.0 KiB
JavaScript
import { NpcDialog } from './NpcDialog.js';
|
||
import { openTravellerNpcDialog } from './TravellerNpcDialog.js';
|
||
import { syncNpcRollTables } from './npcRollTableSync.js';
|
||
import './mgt2eMigration.js';
|
||
|
||
const MODULE_ID = 'mgt2-compendium-amiral-denisov';
|
||
const ChatLogV2 = foundry.applications.sidebar.tabs.ChatLog;
|
||
|
||
function openNpcDialog(initialTab, options = {}) {
|
||
new NpcDialog({ initialTab, ...options }).render({ force: true });
|
||
}
|
||
|
||
function openTravellerNpcGenerator() {
|
||
openTravellerNpcDialog();
|
||
}
|
||
|
||
function registerNpcCommand(commandName, initialTab) {
|
||
if (!ChatLogV2?.CHAT_COMMANDS) {
|
||
console.warn(`${MODULE_ID} | ChatLog.CHAT_COMMANDS indisponible, commande /${commandName} non enregistrée`);
|
||
return;
|
||
}
|
||
|
||
ChatLogV2.CHAT_COMMANDS[commandName] = {
|
||
rgx: new RegExp(`^\\/${commandName}(?:\\s+(.*))?$`, 'i'),
|
||
fn: () => {
|
||
openNpcDialog(initialTab);
|
||
return false;
|
||
},
|
||
};
|
||
console.log(`${MODULE_ID} | Commande /${commandName} enregistrée via ChatLog.CHAT_COMMANDS`);
|
||
}
|
||
|
||
function registerTravellerNpcCommand() {
|
||
if (!ChatLogV2?.CHAT_COMMANDS) {
|
||
console.warn(`${MODULE_ID} | ChatLog.CHAT_COMMANDS indisponible, commande /gennpc non enregistrée`);
|
||
return;
|
||
}
|
||
|
||
ChatLogV2.CHAT_COMMANDS.gennpc = {
|
||
rgx: new RegExp(`^\\/gennpc(?:\\s+(.*))?$`, 'i'),
|
||
fn: () => {
|
||
openTravellerNpcGenerator();
|
||
return false;
|
||
},
|
||
};
|
||
console.log(`${MODULE_ID} | Commande /gennpc enregistrée via ChatLog.CHAT_COMMANDS`);
|
||
}
|
||
|
||
Hooks.once('init', () => {
|
||
console.log(`${MODULE_ID} | Outils PNJ initialisés`);
|
||
|
||
// Pré-charge les templates Handlebars
|
||
// Compatibilité v13 et v14
|
||
const loadTemplatesFn = foundry.applications?.handlebars?.loadTemplates || loadTemplates;
|
||
if (loadTemplatesFn) {
|
||
loadTemplatesFn([
|
||
`modules/${MODULE_ID}/templates/npc-dialog.hbs`,
|
||
`modules/${MODULE_ID}/templates/npc-result.hbs`,
|
||
`modules/${MODULE_ID}/templates/traveller-npc-dialog.hbs`,
|
||
`modules/${MODULE_ID}/templates/traveller-npc-result.hbs`,
|
||
]);
|
||
}
|
||
|
||
registerNpcCommand('pnj', 'npc');
|
||
registerNpcCommand('rencontre', 'encounter');
|
||
registerNpcCommand('mission', 'mission');
|
||
registerTravellerNpcCommand();
|
||
});
|
||
|
||
Hooks.once('ready', async () => {
|
||
await syncNpcRollTables();
|
||
console.log(`${MODULE_ID} | Outils PNJ prêts – tapez /pnj, /rencontre, /mission ou /gennpc dans le chat`);
|
||
});
|
||
|
||
/**
|
||
* Solution pour Foundry v14 : Intercepte les commandes AVANT la validation
|
||
* Utilise renderChatInput pour modifier le comportement de l'input
|
||
* Compatible v13 (jQuery) et v14 (DOM element)
|
||
*/
|
||
Hooks.on('renderChatInput', (app, html, data) => {
|
||
// Foundry v14 passe un objet avec element, v13 passe jQuery
|
||
let input;
|
||
|
||
// Vérifie si html est un objet avec element (v14)
|
||
if (html?.element) {
|
||
input = $(html.element).find('textarea[name="content"]');
|
||
} else if (html?.find) {
|
||
// v13 ou déjà jQuery
|
||
input = html.find('textarea[name="content"]');
|
||
} else {
|
||
// Dernier recours : suppose que html est l'élément
|
||
input = $(html).find('textarea[name="content"]');
|
||
}
|
||
|
||
// Évite les doublons d'écouteurs
|
||
if (input.data('mgt2-npc-listener')) return;
|
||
input.data('mgt2-npc-listener', true);
|
||
|
||
input.on('keydown', (event) => {
|
||
// Intercepte Entrée (13) et vérifie si c'est une de nos commandes
|
||
if (event.key === 'Enter' && !event.shiftKey) {
|
||
const content = input.val()?.trim();
|
||
if (content?.startsWith('/pnj')) {
|
||
event.preventDefault();
|
||
event.stopImmediatePropagation();
|
||
openNpcDialog('npc');
|
||
input.val('');
|
||
} else if (content?.startsWith('/rencontre')) {
|
||
event.preventDefault();
|
||
event.stopImmediatePropagation();
|
||
openNpcDialog('encounter');
|
||
input.val('');
|
||
} else if (content?.startsWith('/mission')) {
|
||
event.preventDefault();
|
||
event.stopImmediatePropagation();
|
||
openNpcDialog('mission');
|
||
input.val('');
|
||
} else if (content?.startsWith('/gennpc')) {
|
||
event.preventDefault();
|
||
event.stopImmediatePropagation();
|
||
openTravellerNpcGenerator();
|
||
input.val('');
|
||
}
|
||
}
|
||
});
|
||
});
|
||
|
||
/**
|
||
* Intercepte les messages de chat pour /pnj, /rencontre, /mission, /gennpc
|
||
* Utilise preCreateChatMessage pour Foundry v14+ (avant que le message ne soit validé)
|
||
* Compatible avec Foundry v13 et v14
|
||
*/
|
||
Hooks.on('preCreateChatMessage', (message, data, options) => {
|
||
const content = message.content?.trim()?.toLowerCase();
|
||
|
||
if (content === '/pnj' || content?.startsWith('/pnj ')) {
|
||
openNpcDialog('npc');
|
||
return false; // Empêche la création du message
|
||
}
|
||
|
||
if (content === '/rencontre' || content?.startsWith('/rencontre ')) {
|
||
openNpcDialog('encounter');
|
||
return false; // Empêche la création du message
|
||
}
|
||
|
||
if (content === '/mission' || content?.startsWith('/mission ')) {
|
||
openNpcDialog('mission');
|
||
return false; // Empêche la création du message
|
||
}
|
||
|
||
if (content === '/gennpc' || content?.startsWith('/gennpc ')) {
|
||
openTravellerNpcGenerator();
|
||
return false; // Empêche la création du message
|
||
}
|
||
});
|
||
|
||
// Gardé pour compatibilité v13
|
||
Hooks.on('chatMessage', (...args) => {
|
||
// Foundry v14 passe un objet ChatMessage en premier paramètre
|
||
// Foundry v13 passe (chatLog, message, chatData)
|
||
let message;
|
||
|
||
if (args[0]?.content !== undefined) {
|
||
// v14: premier argument est ChatMessage
|
||
message = args[0].content;
|
||
} else if (typeof args[1] === 'string') {
|
||
// v13: deuxième argument est la string message
|
||
message = args[1];
|
||
} else {
|
||
return;
|
||
}
|
||
|
||
const trimmed = message?.trim()?.toLowerCase();
|
||
|
||
if (trimmed === '/pnj' || trimmed?.startsWith('/pnj ')) {
|
||
openNpcDialog('npc');
|
||
return false;
|
||
}
|
||
|
||
if (trimmed === '/rencontre' || trimmed?.startsWith('/rencontre ')) {
|
||
openNpcDialog('encounter');
|
||
return false;
|
||
}
|
||
|
||
if (trimmed === '/mission' || trimmed?.startsWith('/mission ')) {
|
||
openNpcDialog('mission');
|
||
return false;
|
||
}
|
||
|
||
if (trimmed === '/gennpc' || trimmed?.startsWith('/gennpc ')) {
|
||
openTravellerNpcGenerator();
|
||
return false;
|
||
}
|
||
});
|