Files
mgt2-compendium-amiral-denisov/scripts/NpcDialog.js
T
uberwald 9d96ec5543 Fix: Localisation des compétences du PNJ rapide dans le chat
- Ajout des helpers Handlebars localizeSkill et joinLocalizedSkills
- Template npc-result.hbs utilise maintenant joinLocalizedSkills pour afficher les compétences en français
- Import de localizeSkill depuis mgt2eSkills.js
- Résout le problème où les compétences s'affichaient en anglais (pilot, guncombat, melee...) au lieu de français (Pilote, Combat Arme, Mêlée...)

Generated by Mistral Vibe.
Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
2026-05-28 01:06:25 +02:00

373 lines
13 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { formatCredits } from './tradeHelper.js';
import { createNpcActor, generateClientMission, generateEncounter, generateQuickNpc } from './npcHelper.js';
import { NPC_RELATIONS } from './data/npcTables.js';
import { generateAndCreateTravellerNpc } from './travellerNpcGenerator.js';
import { generateRandomName } from './data/travellerNpcGenerator.js';
import { localizeSkill } from './mgt2eSkills.js';
import {
CITIZEN_CATEGORY_LIST,
EXPERIENCE_LEVEL_LIST,
ROLE_LIST,
GENDER_LIST,
DEFAULT_OPTIONS,
CITIZEN_CATEGORY_LABELS_FR,
EXPERIENCE_LEVEL_LABELS_FR,
ROLE_LABELS_FR,
GENDER_LABELS_FR
} from './data/travellerNpcGenerator.js';
const { ApplicationV2, HandlebarsApplicationMixin } = foundry.applications.api;
const MODULE_ID = 'mgt2-compendium-amiral-denisov';
export class NpcDialog extends HandlebarsApplicationMixin(ApplicationV2) {
static DEFAULT_OPTIONS = {
id: 'mgt2-npc',
classes: ['mgt2-npc-dialog'],
position: {
width: 720,
height: 'auto',
},
window: {
title: 'PNJ & Rencontres MgT2e',
resizable: true,
},
};
static PARTS = {
main: {
template: `modules/${MODULE_ID}/templates/npc-dialog.hbs`,
root: true,
},
};
constructor(options = {}) {
super(options);
this._activeTab = options.initialTab ?? 'npc';
this._formData = {
npc: {
relation: options.relation ?? 'contact',
experienceBias: 'random',
createActor: false,
actorName: '',
openCreatedActor: true,
},
encounter: {
context: options.context ?? 'starport',
includeFollowUp: true,
},
mission: {},
traveller: {
citizenCategory: DEFAULT_OPTIONS.citizenCategory,
experience: DEFAULT_OPTIONS.experience,
role: DEFAULT_OPTIONS.role,
gender: DEFAULT_OPTIONS.gender,
firstName: '',
surname: '',
useRandomName: true,
createActor: DEFAULT_OPTIONS.createActor,
actorName: '',
openCreatedActor: DEFAULT_OPTIONS.openCreatedActor,
},
};
}
async _prepareContext() {
registerHandlebarsHelpers();
return {
...this._formData,
activeTab: this._activeTab,
relations: Object.entries(NPC_RELATIONS).map(([key, value]) => ({ key, label: value.label })),
citizenCategories: CITIZEN_CATEGORY_LIST.map(c => ({
key: c.key,
label: CITIZEN_CATEGORY_LABELS_FR[c.key] || c.label,
description: c.description
})),
experienceLevels: EXPERIENCE_LEVEL_LIST.map(e => ({
key: e.key,
label: EXPERIENCE_LEVEL_LABELS_FR[e.key] || e.label,
description: e.description
})),
roles: ROLE_LIST.map(r => ({
key: r.key,
label: ROLE_LABELS_FR[r.key] || r.label,
description: r.description
})),
genders: GENDER_LIST.map(g => ({
key: g.key,
label: GENDER_LABELS_FR[g.key] || g.label
})),
};
}
async _onRender(context, options) {
await super._onRender(context, options);
const html = this._getForm();
if (!html?.length) return;
html.addClass('mgt2-npc-form');
this._applyThemeStyles(html);
html.find('[data-action="generate-npc"]').on('click', async (event) => {
event.preventDefault();
this._readForm(html);
await this._handleNpc();
});
html.find('[data-action="generate-encounter"]').on('click', async (event) => {
event.preventDefault();
this._readForm(html);
await this._handleEncounter();
});
html.find('[data-action="generate-mission"]').on('click', async (event) => {
event.preventDefault();
this._readForm(html);
await this._handleMission();
});
html.find('.tabs .item').on('click', (event) => {
event.preventDefault();
this._readForm(html);
this._activateTab($(event.currentTarget).data('tab'));
});
// Gestion des événements pour l'onglet PNJ Détaillé (Traveller)
html.find('[data-action="generate-traveller-npc"]').on('click', async (event) => {
event.preventDefault();
this._readForm(html);
await this._handleTravellerNpc();
});
html.find('[data-action="randomize-name"]').on('click', (event) => {
event.preventDefault();
this._randomizeTravellerName(html);
});
html.find('[name="traveller.useRandomName"]').on('change', (event) => {
const useRandom = event.target.checked;
this._formData.traveller.useRandomName = useRandom;
html.find('.traveller-name-fields').toggleClass('hidden', useRandom);
});
// Initialiser l'affichage des champs de nom pour l'onglet Traveller
html.find('.traveller-name-fields').toggleClass('hidden', this._formData.traveller.useRandomName);
}
_getForm() {
return $(this.element).find('.window-content');
}
_activateTab(tabId) {
const html = this._getForm();
if (!html?.length) return;
this._activeTab = tabId;
html.find('.tabs .item').removeClass('active');
html.find(`.tabs .item[data-tab="${tabId}"]`).addClass('active');
html.find('.tab-content .tab').removeClass('active');
html.find(`.tab-content .tab[data-tab="${tabId}"]`).addClass('active');
this._applyThemeStyles(html);
}
_applyThemeStyles(html) {
html.find('.tabs .item').css({
color: '#d8c79a',
'text-shadow': 'none',
'background-color': '',
'border-bottom-color': 'transparent'
});
html.find('.tabs .item.active').css({
color: '#d9b24c',
'text-shadow': 'none',
'background-color': 'rgba(201, 162, 39, 0.18)',
'border-bottom-color': '#c9a227'
});
html.find('h3').css({
color: '#5f4300',
'border-bottom-color': '#b78f26',
'text-shadow': 'none'
});
html.find('legend').css({
color: '#7a5c00',
'text-shadow': 'none'
});
}
_readForm(html) {
this._formData.npc.relation = html.find('[name="npc.relation"]').val();
this._formData.npc.experienceBias = html.find('[name="npc.experienceBias"]').val();
this._formData.npc.createActor = html.find('[name="npc.createActor"]').is(':checked');
this._formData.npc.actorName = html.find('[name="npc.actorName"]').val();
this._formData.npc.openCreatedActor = html.find('[name="npc.openCreatedActor"]').is(':checked');
this._formData.encounter.context = html.find('[name="encounter.context"]').val();
this._formData.encounter.includeFollowUp = html.find('[name="encounter.includeFollowUp"]').is(':checked');
// Données pour l'onglet PNJ Détaillé (Traveller)
this._formData.traveller.citizenCategory = html.find('[name="traveller.citizenCategory"]').val();
this._formData.traveller.experience = html.find('[name="traveller.experience"]').val();
this._formData.traveller.role = html.find('[name="traveller.role"]').val();
this._formData.traveller.gender = html.find('[name="traveller.gender"]').val();
this._formData.traveller.firstName = html.find('[name="traveller.firstName"]').val();
this._formData.traveller.surname = html.find('[name="traveller.surname"]').val();
this._formData.traveller.useRandomName = html.find('[name="traveller.useRandomName"]').is(':checked');
this._formData.traveller.createActor = html.find('[name="traveller.createActor"]').is(':checked');
this._formData.traveller.actorName = html.find('[name="traveller.actorName"]').val();
this._formData.traveller.openCreatedActor = html.find('[name="traveller.openCreatedActor"]').is(':checked');
}
async _handleNpc() {
const result = await generateQuickNpc(this._formData.npc);
if (this._formData.npc.createActor) {
const actor = await createNpcActor(result, {
name: this._formData.npc.actorName,
openSheet: this._formData.npc.openCreatedActor,
});
result.createdActor = { id: actor.id, name: actor.name };
ui.notifications.info(`Fiche PNJ créée : ${actor.name}`);
}
await this._postToChatResult(result);
}
async _handleEncounter() {
const result = await generateEncounter(this._formData.encounter);
await this._postToChatResult(result);
}
async _handleMission() {
const result = await generateClientMission();
await this._postToChatResult(result);
}
async _handleTravellerNpc() {
const button = $(this.element).find('[data-action="generate-traveller-npc"]');
const originalLabel = button.html();
try {
button.prop('disabled', true).html('<i class="fas fa-spinner fa-spin"></i> Génération...');
const generateOptions = {
citizenCategory: this._formData.traveller.citizenCategory,
experience: this._formData.traveller.experience,
role: this._formData.traveller.role,
gender: this._formData.traveller.gender,
createActor: this._formData.traveller.createActor,
actorName: this._formData.traveller.actorName,
openCreatedActor: this._formData.traveller.openCreatedActor
};
if (!this._formData.traveller.useRandomName && this._formData.traveller.firstName && this._formData.traveller.surname) {
generateOptions.firstName = this._formData.traveller.firstName;
generateOptions.surname = this._formData.traveller.surname;
}
const result = await generateAndCreateTravellerNpc(generateOptions);
if (result.success) {
await this._postToChatResult(result);
if (result.createdActor) {
ui.notifications.info(`Fiche PNJ Traveller créée : ${result.createdActor.name}`);
}
} else {
ui.notifications.error('Erreur lors de la génération du PNJ Traveller');
}
} catch (error) {
console.error(`${MODULE_ID} | Erreur lors de la génération du PNJ Traveller:`, error);
ui.notifications.error(`Erreur: ${error.message}`);
} finally {
button.prop('disabled', false).html(originalLabel);
}
}
_randomizeTravellerName(html) {
const name = generateRandomName(this._formData.traveller.gender);
html.find('[name="traveller.firstName"]').val(name.firstName);
html.find('[name="traveller.surname"]').val(name.surname);
this._formData.traveller.firstName = name.firstName;
this._formData.traveller.surname = name.surname;
this._formData.traveller.useRandomName = false;
html.find('[name="traveller.useRandomName"]').prop('checked', false);
html.find('.traveller-name-fields').removeClass('hidden');
}
async _postToChatResult(data) {
registerHandlebarsHelpers();
// Déterminer quel template utiliser en fonction du type de données
let template = `modules/${MODULE_ID}/templates/npc-result.hbs`;
let resultType = 'npc-result';
if (data.type === 'traveller-npc' || data?.flags?.[MODULE_ID]?.type === 'traveller-npc-result') {
template = `modules/${MODULE_ID}/templates/traveller-npc-result.hbs`;
resultType = 'traveller-npc-result';
}
const html = await foundry.applications.handlebars.renderTemplate(template, data);
await ChatMessage.create({
content: html,
speaker: ChatMessage.getSpeaker(),
flags: { [MODULE_ID]: { type: resultType } },
});
}
}
let helpersRegistered = false;
function registerHandlebarsHelpers() {
if (helpersRegistered) return;
helpersRegistered = true;
// Helpers existants pour NPC
Handlebars.registerHelper('eq', (a, b) => a === b);
Handlebars.registerHelper('join', (arr, sep) => (Array.isArray(arr) ? arr.join(sep) : ''));
Handlebars.registerHelper('formatCredits', (amount) => formatCredits(amount));
Handlebars.registerHelper('contains', (text, search) => String(text ?? '').includes(search));
// Helper pour localiser une compétence (ex: 'pilot' -> 'Pilote')
Handlebars.registerHelper('localizeSkill', (skillFqn) => {
if (!skillFqn) return '';
return localizeSkill(String(skillFqn));
});
// Helper pour joindre un tableau de compétences en les localisant
Handlebars.registerHelper('joinLocalizedSkills', (arr, sep = ', ') => {
if (!Array.isArray(arr)) return '';
return arr.map(skill => localizeSkill(String(skill))).join(sep);
});
// Helpers pour Traveller NPC
Handlebars.registerHelper('gt', (a, b) => a > b);
// Helper pour afficher le niveau de compétence avec un symbole
Handlebars.registerHelper('skillLevelSymbol', (level) => {
if (level === 0) return '';
if (level === 1) return '★';
if (level === 2) return '★★';
if (level === 3) return '★★★';
return `+${level}`;
});
// Helper pour formater le DM (Difficulté Modificateur)
Handlebars.registerHelper('formatDm', (value) => {
const dm = Math.floor((value - 6) / 3);
return dm >= 0 ? `+${dm}` : `${dm}`;
});
// Helper pour obtenir la classe CSS du niveau de compétence
Handlebars.registerHelper('skillLevelClass', (level) => {
if (level === 3) return 'skill-level-3';
if (level === 2) return 'skill-level-2';
if (level === 1) return 'skill-level-1';
return 'skill-level-0';
});
// Helper pour lookup dans un objet
Handlebars.registerHelper('lookup', (obj, key) => {
if (!obj || !key) return '';
return obj[key] !== undefined ? obj[key] : '';
});
}