Files
mgt2-compendium-amiral-denisov/scripts/TravellerNpcDialog.js
T
uberwald 4f53d903eb Ajout de la commande /gennpc pour générer des PNJ Traveller
Implémentation complète du générateur de PNJ Traveller basé sur :
https://github.com/carloscasalar/traveller-npc-generator

Fonctionnalités :
- Génération de caractéristiques selon 4 catégories de citoyens
- Distribution des compétences selon 6 niveaux d'expérience
- 14 rôles différents avec priorités de caractéristiques spécifiques
- Génération de noms aléatoires (masculin/féminin/neutre)
- Création de fiche d'acteur mgt2e avec toutes les compétences
- Interface utilisateur avec dialogue Handlebars
- Commande /gennpc dans le chat

Fichiers ajoutés :
- scripts/data/travellerNpcGenerator.js (données et constantes)
- scripts/travellerNpcGenerator.js (logique métier)
- scripts/TravellerNpcDialog.js (interface utilisateur)
- templates/traveller-npc-dialog.hbs (template dialogue)
- templates/traveller-npc-result.hbs (template résultat)
- styles/traveller-npc.css (styles spécifiques)

Fichiers modifiés :
- scripts/npc.js (intégration de la commande)
- module.json (ajout des nouveaux scripts et styles)

Generated by Mistral Vibe.
Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
2026-05-27 23:09:43 +02:00

310 lines
10 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.
/**
* Traveller NPC Generator - Dialogue de génération
*
* Ce fichier contient le dialogue pour générer des PNJ Traveller.
*/
import { generateAndCreateTravellerNpc } from './travellerNpcGenerator.js';
import {
CITIZEN_CATEGORY_LIST,
EXPERIENCE_LEVEL_LIST,
ROLE_LIST,
GENDER_LIST,
DEFAULT_OPTIONS,
CHARACTERISTIC_LIST,
UPP_ORDER
} from './data/travellerNpcGenerator.js';
const { ApplicationV2, HandlebarsApplicationMixin } = foundry.applications.api;
const MODULE_ID = 'mgt2-compendium-amiral-denisov';
export class TravellerNpcDialog extends HandlebarsApplicationMixin(ApplicationV2) {
static DEFAULT_OPTIONS = {
id: 'mgt2-traveller-npc',
classes: ['mgt2-npc-dialog', 'mgt2-traveller-npc-dialog'],
position: {
width: 700,
height: 'auto',
},
window: {
title: 'Générateur de PNJ Traveller MgT2e',
resizable: true,
},
};
static PARTS = {
main: {
template: `modules/${MODULE_ID}/templates/traveller-npc-dialog.hbs`,
root: true,
},
};
constructor(options = {}) {
super(options);
// Form data avec valeurs par défaut
this._formData = {
citizenCategory: options.citizenCategory || DEFAULT_OPTIONS.citizenCategory,
experience: options.experience || DEFAULT_OPTIONS.experience,
role: options.role || DEFAULT_OPTIONS.role,
gender: options.gender || DEFAULT_OPTIONS.gender,
firstName: options.firstName || '',
surname: options.surname || '',
useRandomName: options.useRandomName !== false, // Par défaut, on utilise des noms aléatoires
createActor: options.createActor !== undefined ? options.createActor : DEFAULT_OPTIONS.createActor,
actorName: options.actorName || '',
openCreatedActor: options.openCreatedActor !== undefined ? options.openCreatedActor : DEFAULT_OPTIONS.openCreatedActor,
};
}
async _prepareContext() {
registerHandlebarsHelpers();
return {
...this._formData,
citizenCategories: CITIZEN_CATEGORY_LIST.map(c => ({
key: c.key,
label: c.label,
description: c.description
})),
experienceLevels: EXPERIENCE_LEVEL_LIST.map(e => ({
key: e.key,
label: e.label,
description: e.description
})),
roles: ROLE_LIST.map(r => ({
key: r.key,
label: r.label,
description: r.description
})),
genders: GENDER_LIST.map(g => ({
key: g.key,
label: g.label
}))
};
}
async _onRender(context, options) {
await super._onRender(context, options);
const html = this._getForm();
if (!html?.length) return;
html.addClass('mgt2-traveller-npc-form');
this._applyThemeStyles(html);
// Gestion des événements
html.find('[data-action="generate-traveller-npc"]').on('click', async (event) => {
event.preventDefault();
this._readForm(html);
await this._handleGenerate();
});
html.find('[data-action="randomize-name"]').on('click', (event) => {
event.preventDefault();
this._randomizeName(html);
});
// Gestion du basculement entre nom aléatoire et nom personnalisé
html.find('[name="useRandomName"]').on('change', (event) => {
const useRandom = event.target.checked;
html.find('.name-fields').toggleClass('hidden', useRandom);
});
// Initialiser l'affichage des champs de nom
html.find('.name-fields').toggleClass('hidden', this._formData.useRandomName);
}
_getForm() {
return $(this.element).find('.window-content');
}
_applyThemeStyles(html) {
// Appliquer les styles de thème cohérents avec le dialogue existant
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.citizenCategory = html.find('[name="citizenCategory"]').val();
this._formData.experience = html.find('[name="experience"]').val();
this._formData.role = html.find('[name="role"]').val();
this._formData.gender = html.find('[name="gender"]').val();
this._formData.firstName = html.find('[name="firstName"]').val();
this._formData.surname = html.find('[name="surname"]').val();
this._formData.useRandomName = html.find('[name="useRandomName"]').is(':checked');
this._formData.createActor = html.find('[name="createActor"]').is(':checked');
this._formData.actorName = html.find('[name="actorName"]').val();
this._formData.openCreatedActor = html.find('[name="openCreatedActor"]').is(':checked');
}
_randomizeName(html) {
// Importer dynamiquement pour éviter les dépendances circulaires
import('./data/travellerNpcGenerator.js').then(module => {
const name = module.generateRandomName(this._formData.gender);
html.find('[name="firstName"]').val(name.firstName);
html.find('[name="surname"]').val(name.surname);
this._formData.firstName = name.firstName;
this._formData.surname = name.surname;
this._formData.useRandomName = false;
html.find('[name="useRandomName"]').prop('checked', false);
html.find('.name-fields').removeClass('hidden');
});
}
async _handleGenerate() {
const button = $(this.element).find('[data-action="generate-traveller-npc"]');
const originalLabel = button.html();
try {
// Désactiver le bouton pendant la génération
button.prop('disabled', true).html('<i class="fas fa-spinner fa-spin"></i> Génération...');
// Préparer les options de génération
const generateOptions = {
citizenCategory: this._formData.citizenCategory,
experience: this._formData.experience,
role: this._formData.role,
gender: this._formData.gender,
createActor: this._formData.createActor,
actorName: this._formData.actorName,
openCreatedActor: this._formData.openCreatedActor
};
// Si on n'utilise pas de nom aléatoire, passer le nom personnalisé
if (!this._formData.useRandomName && this._formData.firstName && this._formData.surname) {
generateOptions.firstName = this._formData.firstName;
generateOptions.surname = this._formData.surname;
}
// Générer le PNJ
const result = await generateAndCreateTravellerNpc(generateOptions);
if (result.success) {
// Afficher le résultat dans le chat
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 {
// Réactiver le bouton
button.prop('disabled', false).html(originalLabel);
}
}
async _postToChatResult(data) {
registerHandlebarsHelpers();
const html = await foundry.applications.handlebars.renderTemplate(
`modules/${MODULE_ID}/templates/traveller-npc-result.hbs`,
data
);
await ChatMessage.create({
content: html,
speaker: ChatMessage.getSpeaker(),
flags: { [MODULE_ID]: { type: 'traveller-npc-result' } },
});
}
}
// ============================================================================
// Helper functions
// ============================================================================
let helpersRegistered = false;
function registerHandlebarsHelpers() {
if (helpersRegistered) return;
helpersRegistered = true;
// Helper pour comparer deux valeurs
Handlebars.registerHelper('eq', (a, b) => a === b);
// Helper pour rejoindre un tableau
Handlebars.registerHelper('join', (arr, sep) => (Array.isArray(arr) ? arr.join(sep) : ''));
// Helper pour vérifier si une valeur contient du texte
Handlebars.registerHelper('contains', (text, search) => String(text ?? '').includes(search));
// Helper pour vérifier si a > b
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
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 formater une compétence avec son niveau
Handlebars.registerHelper('formatSkillForDisplay', (name, level) => {
if (level === 0) {
return name;
}
return `${name}-${level}`;
});
// Helper pour créer un objet de libellés de caractéristiques
Handlebars.registerHelper('createCharacteristicLabels', () => {
const labels = {};
CHARACTERISTIC_LIST.forEach(char => {
labels[char.key] = char.label;
});
return labels;
});
// Helper pour lookup dans un objet
Handlebars.registerHelper('lookup', (obj, key) => {
if (!obj || !key) return '';
return obj[key] !== undefined ? obj[key] : '';
});
}
// Exporter pour pouvoir l'ouvrir depuis d'autres modules
export function openTravellerNpcDialog(options = {}) {
new TravellerNpcDialog(options).render({ force: true });
}