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>
This commit is contained in:
@@ -0,0 +1,663 @@
|
||||
/**
|
||||
* Traveller NPC Generator - Logique métier
|
||||
* Basé sur : https://github.com/carloscasalar/traveller-npc-generator
|
||||
*
|
||||
* Ce fichier contient la logique de génération des PNJ Traveller.
|
||||
*/
|
||||
|
||||
import {
|
||||
CITIZEN_CATEGORY,
|
||||
CITIZEN_CATEGORY_LIST,
|
||||
EXPERIENCE_LEVEL,
|
||||
EXPERIENCE_LEVEL_LIST,
|
||||
ROLE,
|
||||
ROLE_LIST,
|
||||
ROLE_SKILLS,
|
||||
CHARACTERISTIC_PRIORITIES,
|
||||
GENDER,
|
||||
GENDER_LIST,
|
||||
CHARACTERISTIC,
|
||||
CHARACTERISTIC_LIST,
|
||||
UPP_ORDER,
|
||||
NAME_CATALOGS,
|
||||
toHex,
|
||||
calculateDm,
|
||||
pickRandomItem,
|
||||
popRandomItems,
|
||||
shuffleArray,
|
||||
getRoleByKey,
|
||||
getCitizenCategoryByKey,
|
||||
getExperienceLevelByKey,
|
||||
getGenderByKey,
|
||||
getSkillsForRole,
|
||||
getCharacteristicPrioritiesForRole,
|
||||
DEFAULT_OPTIONS
|
||||
} from './data/travellerNpcGenerator.js';
|
||||
|
||||
import { setSkillLevel, localizeSkill } from './mgt2eSkills.js';
|
||||
|
||||
const MODULE_ID = 'mgt2-compendium-amiral-denisov';
|
||||
|
||||
// Cache pour le système de base des acteurs mgt2e
|
||||
let mgt2eBaseActorSystemPromise = null;
|
||||
|
||||
// ============================================================================
|
||||
// Génération des caractéristiques
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Génère les caractéristiques d'un PNJ en fonction de sa catégorie et de son rôle
|
||||
*
|
||||
* @param {string} citizenCategoryKey - Clé de la catégorie de citoyen
|
||||
* @param {string} roleKey - Clé du rôle
|
||||
* @returns {Object} - Objet contenant les caractéristiques et l'UPP
|
||||
*/
|
||||
export function generateCharacteristics(citizenCategoryKey, roleKey) {
|
||||
const category = getCitizenCategoryByKey(citizenCategoryKey);
|
||||
const priorities = getCharacteristicPrioritiesForRole(roleKey);
|
||||
|
||||
// On commence avec l'array de base de la catégorie
|
||||
let characteristicArray = [...category.characteristicArray];
|
||||
|
||||
// On attribue les valeurs aux caractéristiques selon les priorités du rôle
|
||||
const characteristics = {};
|
||||
|
||||
// 1. Attribuer les valeurs les plus élevées aux caractéristiques High priority
|
||||
const [highValues, remaining1] = popRandomItems(characteristicArray, priorities.high.length);
|
||||
priorities.high.forEach((charKey, index) => {
|
||||
characteristics[charKey] = highValues[index] || 7;
|
||||
});
|
||||
|
||||
// 2. Attribuer les valeurs moyennes aux caractéristiques Medium priority
|
||||
const [mediumValues, remaining2] = popRandomItems(remaining1, priorities.medium.length);
|
||||
priorities.medium.forEach((charKey, index) => {
|
||||
characteristics[charKey] = mediumValues[index] || 7;
|
||||
});
|
||||
|
||||
// 3. Attribuer les valeurs restantes aux caractéristiques Low priority
|
||||
const [lowValues] = popRandomItems(remaining2, priorities.low.length);
|
||||
priorities.low.forEach((charKey, index) => {
|
||||
characteristics[charKey] = lowValues[index] || 7;
|
||||
});
|
||||
|
||||
// S'assurer que toutes les caractéristiques sont définies
|
||||
UPP_ORDER.forEach(charKey => {
|
||||
if (characteristics[charKey] === undefined) {
|
||||
characteristics[charKey] = 7;
|
||||
}
|
||||
});
|
||||
|
||||
// Construire l'UPP
|
||||
const upp = UPP_ORDER.map(charKey => toHex(characteristics[charKey])).join('');
|
||||
|
||||
return {
|
||||
characteristics,
|
||||
upp,
|
||||
category
|
||||
};
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Génération des compétences
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Distribue les niveaux de compétence sur une liste de compétences
|
||||
*
|
||||
* @param {string[]} roleSkills - Liste des compétences du rôle
|
||||
* @param {Object} distribution - Distribution des niveaux (level0, level1, level2, level3)
|
||||
* @returns {Map<string, number>} - Map des compétences avec leurs niveaux
|
||||
*/
|
||||
function distributeSkillLevels(roleSkills, distribution) {
|
||||
const skillLevels = new Map();
|
||||
const maxLevelBySkill = new Map();
|
||||
|
||||
// 1. On commence par les compétences de niveau 3 (les plus rares)
|
||||
let remainingSkills = [...roleSkills];
|
||||
|
||||
// Supprimer les doublons (spécialisations)
|
||||
const uniqueSkills = [];
|
||||
const seenSkills = new Set();
|
||||
for (const skill of remainingSkills) {
|
||||
const baseSkill = skill.split('-')[0];
|
||||
if (!seenSkills.has(baseSkill)) {
|
||||
seenSkills.add(baseSkill);
|
||||
uniqueSkills.push(skill);
|
||||
}
|
||||
}
|
||||
remainingSkills = uniqueSkills;
|
||||
|
||||
// Level 3
|
||||
if (distribution.level3 > 0) {
|
||||
const [level3Skills, remaining] = popRandomItems(remainingSkills, distribution.level3);
|
||||
level3Skills.forEach(skill => {
|
||||
skillLevels.set(skill, 3);
|
||||
maxLevelBySkill.set(skill, 3);
|
||||
});
|
||||
remainingSkills = remaining;
|
||||
}
|
||||
|
||||
// Level 2
|
||||
if (distribution.level2 > 0) {
|
||||
const [level2Skills, remaining] = popRandomItems(remainingSkills, distribution.level2);
|
||||
level2Skills.forEach(skill => {
|
||||
const currentLevel = skillLevels.get(skill) || 0;
|
||||
const newLevel = Math.max(currentLevel, 2);
|
||||
skillLevels.set(skill, newLevel);
|
||||
if (newLevel > (maxLevelBySkill.get(skill) || 0)) {
|
||||
maxLevelBySkill.set(skill, newLevel);
|
||||
}
|
||||
});
|
||||
remainingSkills = remaining;
|
||||
}
|
||||
|
||||
// Level 1
|
||||
if (distribution.level1 > 0) {
|
||||
const [level1Skills, remaining] = popRandomItems(remainingSkills, distribution.level1);
|
||||
level1Skills.forEach(skill => {
|
||||
const currentLevel = skillLevels.get(skill) || 0;
|
||||
const newLevel = Math.max(currentLevel, 1);
|
||||
skillLevels.set(skill, newLevel);
|
||||
if (newLevel > (maxLevelBySkill.get(skill) || 0)) {
|
||||
maxLevelBySkill.set(skill, newLevel);
|
||||
}
|
||||
});
|
||||
remainingSkills = remaining;
|
||||
}
|
||||
|
||||
// Level 0 - les compétences restantes
|
||||
if (distribution.level0 > 0) {
|
||||
const [level0Skills] = popRandomItems(remainingSkills, distribution.level0);
|
||||
level0Skills.forEach(skill => {
|
||||
const currentLevel = skillLevels.get(skill) || 0;
|
||||
if (currentLevel === 0) {
|
||||
skillLevels.set(skill, 0);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Pour les compétences restantes non sélectionnées, leur donner niveau 0
|
||||
for (const skill of roleSkills) {
|
||||
if (!skillLevels.has(skill)) {
|
||||
skillLevels.set(skill, 0);
|
||||
}
|
||||
}
|
||||
|
||||
return skillLevels;
|
||||
}
|
||||
|
||||
/**
|
||||
* Génère les compétences d'un PNJ en fonction de son rôle et de son expérience
|
||||
*
|
||||
* @param {string} roleKey - Clé du rôle
|
||||
* @param {string} experienceKey - Clé du niveau d'expérience
|
||||
* @returns {Array<{name: string, level: number}>} - Liste des compétences avec niveaux
|
||||
*/
|
||||
export function generateSkills(roleKey, experienceKey) {
|
||||
const roleSkills = getSkillsForRole(roleKey);
|
||||
const experience = getExperienceLevelByKey(experienceKey);
|
||||
const distribution = experience.skillDistribution;
|
||||
|
||||
const skillLevels = distributeSkillLevels(roleSkills, distribution);
|
||||
|
||||
// Convertir en tableau trié par niveau (descendant) puis par nom
|
||||
const skills = Array.from(skillLevels.entries())
|
||||
.sort((a, b) => {
|
||||
// D'abord par niveau (descendant)
|
||||
if (b[1] !== a[1]) {
|
||||
return b[1] - a[1];
|
||||
}
|
||||
// Puis par nom (ascendant)
|
||||
return a[0].localeCompare(b[0]);
|
||||
})
|
||||
.map(([name, level]) => ({ name, level }));
|
||||
|
||||
return skills;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Génération du nom
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Génère un nom aléatoire
|
||||
*
|
||||
* @param {string} genderKey - Clé du genre
|
||||
* @returns {{firstName: string, surname: string, fullName: string}}
|
||||
*/
|
||||
export function generateName(genderKey) {
|
||||
return pickRandomItem(NAME_CATALOGS.nonGenderedNames) + ' ' + pickRandomItem(NAME_CATALOGS.surnames);
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Génération complète du PNJ
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Convertit un nom de compétence du format Traveller vers le format mgt2e
|
||||
* @param {string} skillName - Nom de la compétence (ex: "Pilot-Spacecraft")
|
||||
* @returns {string} - Nom de la compétence au format mgt2e (ex: "pilot.spacecraft")
|
||||
*/
|
||||
function convertSkillToMgt2eFormat(skillName) {
|
||||
// Remplacer les tirets par des points
|
||||
// Note: Certaines compétences ont des spécialisations comme "Pilot-Small Craft" -> "pilot.smallcraft"
|
||||
const mapping = {
|
||||
'Pilot-Spacecraft': 'pilot.spacecraft',
|
||||
'Pilot-Small Craft': 'pilot.smallcraft',
|
||||
'Pilot': 'pilot',
|
||||
'Astrogation': 'astrogation',
|
||||
'Electronics-Sensors': 'electronics.sensors',
|
||||
'Electronics-Communications': 'electronics.communications',
|
||||
'Electronics-Computers': 'electronics.computers',
|
||||
'Electronics': 'electronics',
|
||||
'Gunner-Turrets': 'gunner.turrets',
|
||||
'Gunner-Screens': 'gunner.screens',
|
||||
'Gunner': 'gunner',
|
||||
'Mechanic': 'mechanic',
|
||||
'Engineer-MDrive': 'engineer.mdrive',
|
||||
'Engineer-Power': 'engineer.power',
|
||||
'Engineer-JDrive': 'engineer.jdrive',
|
||||
'Engineer-Life Support': 'engineer.lifesupport',
|
||||
'Engineer': 'engineer',
|
||||
'Steward': 'steward',
|
||||
'Carouse': 'carouse',
|
||||
'Persuade': 'persuade',
|
||||
'Broker': 'broker',
|
||||
'Admin': 'admin',
|
||||
'Computers': 'electronics.computers',
|
||||
'Language': 'language',
|
||||
'Advocate': 'advocate',
|
||||
'Leadership': 'leadership',
|
||||
'Medic': 'medic',
|
||||
'Streetwise': 'streetwise',
|
||||
'Diplomat': 'diplomat',
|
||||
'Science-Biology': 'science.biology',
|
||||
'Science-Chemistry': 'science.chemistry',
|
||||
'Science': 'science',
|
||||
'Deception': 'deception',
|
||||
'Investigate': 'investigate',
|
||||
'Gun Combat': 'guncombat',
|
||||
'GunCombat': 'guncombat',
|
||||
'Heavy Weapons': 'heavyweapons',
|
||||
'HeavyWeapons': 'heavyweapons',
|
||||
'Melee-Unarmed': 'melee.unarmed',
|
||||
'Melee-Blade': 'melee.blade',
|
||||
'Melee': 'melee',
|
||||
'Athletics-Strength': 'athletics.strength',
|
||||
'Athletics-Dexterity': 'athletics.dexterity',
|
||||
'Athletics': 'athletics',
|
||||
'Tactics': 'tactics',
|
||||
'Recon': 'recon',
|
||||
'Survival': 'survival',
|
||||
'Navigation': 'navigation',
|
||||
'Stealth': 'stealth',
|
||||
'Explosives': 'explosives',
|
||||
'Communications': 'electronics.communications',
|
||||
'Drive-Grav': 'drive.grav',
|
||||
'Drive': 'drive',
|
||||
'Vacc Suit': 'vaccsuit',
|
||||
'VaccSuit': 'vaccsuit',
|
||||
'Flyer': 'flyer',
|
||||
'Art-Acting': 'art.acting',
|
||||
'Art-Instrument': 'art.instrument',
|
||||
'Art': 'art',
|
||||
'Flyer': 'flyer',
|
||||
'Engineer-Manoeuvre Drive': 'engineer.manoeuvredrive',
|
||||
'Engineer-Manoeuvre': 'engineer.manoeuvredrive'
|
||||
};
|
||||
|
||||
return mapping[skillName] || skillName.toLowerCase().replace(/-| /g, '.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Génère un PNJ Traveller complet
|
||||
*
|
||||
* @param {Object} options - Options de génération
|
||||
* @param {string} [options.citizenCategory='average'] - Catégorie de citoyen
|
||||
* @param {string} [options.experience='regular'] - Niveau d'expérience
|
||||
* @param {string} [options.role='pilot'] - Rôle
|
||||
* @param {string} [options.gender='unspecified'] - Genre
|
||||
* @param {string} [options.firstName] - Prénom forcé
|
||||
* @param {string} [options.surname] - Nom de famille forcé
|
||||
* @returns {Object} - PNJ généré
|
||||
*/
|
||||
export function generateTravellerNpc(options = {}) {
|
||||
// Fusionner avec les options par défaut
|
||||
const opts = {
|
||||
...DEFAULT_OPTIONS,
|
||||
...options
|
||||
};
|
||||
|
||||
// Générer le nom
|
||||
let name;
|
||||
if (opts.firstName && opts.surname) {
|
||||
name = {
|
||||
firstName: opts.firstName,
|
||||
surname: opts.surname,
|
||||
fullName: `${opts.firstName} ${opts.surname}`
|
||||
};
|
||||
} else {
|
||||
name = {
|
||||
firstName: pickRandomItem(
|
||||
opts.gender === 'female' ? NAME_CATALOGS.femaleNames :
|
||||
opts.gender === 'male' ? NAME_CATALOGS.maleNames :
|
||||
NAME_CATALOGS.nonGenderedNames
|
||||
),
|
||||
surname: pickRandomItem(NAME_CATALOGS.surnames),
|
||||
fullName: ''
|
||||
};
|
||||
name.fullName = `${name.firstName} ${name.surname}`;
|
||||
}
|
||||
|
||||
// Générer les caractéristiques
|
||||
const { characteristics, upp, category } = generateCharacteristics(
|
||||
opts.citizenCategory,
|
||||
opts.role
|
||||
);
|
||||
|
||||
// Générer les compétences
|
||||
const skills = generateSkills(opts.role, opts.experience);
|
||||
|
||||
// Convertir les compétences au format mgt2e pour la création de fiche
|
||||
const skillsForActor = skills.map(s => ({
|
||||
name: convertSkillToMgt2eFormat(s.name),
|
||||
level: s.level
|
||||
}));
|
||||
|
||||
// Récupérer les objets complets pour les références
|
||||
const citizenCategory = getCitizenCategoryByKey(opts.citizenCategory);
|
||||
const experience = getExperienceLevelByKey(opts.experience);
|
||||
const role = getRoleByKey(opts.role);
|
||||
const gender = getGenderByKey(opts.gender);
|
||||
|
||||
// Libellés des caractéristiques pour l'affichage
|
||||
const characteristicLabels = {};
|
||||
CHARACTERISTIC_LIST.forEach(char => {
|
||||
characteristicLabels[char.key] = char.label;
|
||||
});
|
||||
|
||||
return {
|
||||
success: true,
|
||||
type: 'traveller-npc',
|
||||
name,
|
||||
role,
|
||||
citizenCategory,
|
||||
experience,
|
||||
gender,
|
||||
characteristics,
|
||||
upp,
|
||||
skills,
|
||||
skillsForActor, // Compétences au format mgt2e
|
||||
MODULE_ID,
|
||||
UPP_ORDER,
|
||||
// Métadonnées pour l'affichage
|
||||
display: {
|
||||
roleLabel: role.label,
|
||||
categoryLabel: citizenCategory.label,
|
||||
experienceLabel: experience.label,
|
||||
genderLabel: gender.label,
|
||||
characteristicLabels
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Création de la fiche d'acteur
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Récupère le système de base des acteurs mgt2e
|
||||
* @returns {Promise<Object|null>} - Système de base ou null
|
||||
*/
|
||||
async function getMgt2eBaseActorSystem() {
|
||||
if (!mgt2eBaseActorSystemPromise) {
|
||||
mgt2eBaseActorSystemPromise = (async () => {
|
||||
try {
|
||||
const pack = game.packs.get('mgt2e.base-actors');
|
||||
if (!pack) return null;
|
||||
|
||||
const index = Array.from(await pack.getIndex({ fields: ['name', 'type'] }));
|
||||
const entry = index.find((document) => document.name === 'DEFAULT TRAVELLER')
|
||||
?? index.find((document) => document.type === 'traveller')
|
||||
?? index[0];
|
||||
if (!entry?._id) return null;
|
||||
|
||||
const document = await pack.getDocument(entry._id);
|
||||
return document?.toObject()?.system ?? null;
|
||||
} catch (error) {
|
||||
console.warn(`${MODULE_ID} | Erreur lors de la récupération du système de base mgt2e:`, error);
|
||||
return null;
|
||||
}
|
||||
})();
|
||||
}
|
||||
|
||||
const system = await mgt2eBaseActorSystemPromise;
|
||||
return system ? foundry.utils.deepClone(system) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Construit les caractéristiques au format mgt2e
|
||||
*
|
||||
* @param {Object} existingCharacteristics - Caractéristiques existantes (optionnel)
|
||||
* @param {Object} characteristics - Caractéristiques générées
|
||||
* @returns {Object} - Caractéristiques au format mgt2e
|
||||
*/
|
||||
export function buildMgt2eCharacteristics(existingCharacteristics = {}, characteristics) {
|
||||
const result = foundry.utils.deepClone(existingCharacteristics);
|
||||
|
||||
for (const [key, char] of Object.entries(CHARACTERISTIC)) {
|
||||
const value = characteristics[char.key] || 7;
|
||||
result[char.mgt2eKey] = foundry.utils.mergeObject(result[char.mgt2eKey] ?? {}, {
|
||||
value,
|
||||
current: value,
|
||||
dm: calculateDm(value),
|
||||
show: true,
|
||||
default: false,
|
||||
});
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Construit les compétences au format mgt2e
|
||||
*
|
||||
* @param {Object} existingSkills - Compétences existantes (optionnel)
|
||||
* @param {Array<{name: string, level: number}>} skills - Compétences générées
|
||||
* @returns {Object} - Compétences au format mgt2e
|
||||
*/
|
||||
export function buildMgt2eSkills(existingSkills = {}, skills, useMgt2eFormat = false) {
|
||||
const result = foundry.utils.deepClone(existingSkills);
|
||||
|
||||
for (const { name, level } of skills) {
|
||||
// Si useMgt2eFormat est vrai, on utilise directement le nom
|
||||
// Sinon, on convertit
|
||||
const skillName = useMgt2eFormat ? name : convertSkillToMgt2eFormat(name);
|
||||
setSkillLevel(result, skillName, level);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Construit la description de l'acteur
|
||||
*
|
||||
* @param {Object} npcData - Données du PNJ généré
|
||||
* @param {string} actorName - Nom de l'acteur
|
||||
* @returns {string} - Description formatée
|
||||
*/
|
||||
function buildActorDescription(npcData, actorName) {
|
||||
const notableSkills = npcData.skills
|
||||
.filter(s => s.level > 0)
|
||||
.map(s => {
|
||||
// Essayer de localiser la compétence
|
||||
try {
|
||||
return localizeSkill(s.name);
|
||||
} catch (e) {
|
||||
return s.name;
|
||||
}
|
||||
})
|
||||
.join(', ');
|
||||
|
||||
return [
|
||||
`${actorName} — ${npcData.role.label}`,
|
||||
`Catégorie : ${npcData.citizenCategory.label}`,
|
||||
`Expérience : ${npcData.experience.label}`,
|
||||
`UPP : ${npcData.upp}`,
|
||||
`Genre : ${npcData.gender.label}`,
|
||||
notableSkills ? `Compétences : ${notableSkills}` : ''
|
||||
].filter(line => line).join('\n');
|
||||
}
|
||||
|
||||
/**
|
||||
* Crée une fiche d'acteur pour un PNJ Traveller
|
||||
*
|
||||
* @param {Object} npcData - Données du PNJ généré
|
||||
* @param {Object} options - Options de création
|
||||
* @param {string} [options.name] - Nom de l'acteur
|
||||
* @param {boolean} [options.openSheet=true] - Ouvrir la fiche après création
|
||||
* @returns {Promise<Actor|null>} - Acteur créé ou null
|
||||
*/
|
||||
export async function createTravellerNpcActor(npcData, options = {}) {
|
||||
try {
|
||||
const requestedName = options.name?.trim();
|
||||
const baseActorSystem = game.system?.id === 'mgt2e' ? await getMgt2eBaseActorSystem() : null;
|
||||
|
||||
const actorName = requestedName || npcData.name.fullName || `PNJ — ${npcData.role.label}`;
|
||||
|
||||
const actorData = {
|
||||
name: actorName,
|
||||
type: 'npc',
|
||||
img: 'systems/mgt2e/icons/cargo/passenger-middle.svg',
|
||||
system: {
|
||||
settings: foundry.utils.mergeObject(
|
||||
foundry.utils.deepClone(baseActorSystem?.settings ?? {}),
|
||||
{
|
||||
hideUntrained: true,
|
||||
lockCharacteristics: true,
|
||||
}
|
||||
),
|
||||
sophont: foundry.utils.mergeObject(
|
||||
foundry.utils.deepClone(baseActorSystem?.sophont ?? {}),
|
||||
{
|
||||
age: 18 + Math.floor(Math.random() * 40), // Âge entre 18 et 58 ans
|
||||
homeworld: '',
|
||||
profession: npcData.role.label,
|
||||
}
|
||||
),
|
||||
characteristics: buildMgt2eCharacteristics(
|
||||
foundry.utils.deepClone(baseActorSystem?.characteristics ?? {}),
|
||||
npcData.characteristics
|
||||
),
|
||||
hits: foundry.utils.deepClone(baseActorSystem?.hits ?? {}),
|
||||
skills: buildMgt2eSkills(
|
||||
foundry.utils.deepClone(baseActorSystem?.skills ?? {}),
|
||||
npcData.skillsForActor,
|
||||
true // Utiliser le format mgt2e directement
|
||||
),
|
||||
description: buildActorDescription(npcData, actorName),
|
||||
},
|
||||
flags: {
|
||||
[MODULE_ID]: {
|
||||
generatedTravellerNpc: {
|
||||
version: 1,
|
||||
role: npcData.role.key,
|
||||
citizenCategory: npcData.citizenCategory.key,
|
||||
experience: npcData.experience.key,
|
||||
gender: npcData.gender.key,
|
||||
upp: npcData.upp,
|
||||
generatedAt: new Date().toISOString()
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
// S'assurer que le nom est défini
|
||||
actorData.name = actorName;
|
||||
actorData.system.sophont = foundry.utils.mergeObject(
|
||||
actorData.system.sophont ?? {},
|
||||
{
|
||||
profession: npcData.role.label,
|
||||
}
|
||||
);
|
||||
|
||||
// Remplacer les sauts de ligne par des <br> pour HTML
|
||||
actorData.system.description = actorData.system.description.replace(/\n/g, '<br>');
|
||||
|
||||
const actor = await Actor.create(actorData, { renderSheet: false });
|
||||
|
||||
if (options.openSheet !== false) {
|
||||
actor.sheet?.render(true);
|
||||
}
|
||||
|
||||
return actor;
|
||||
} catch (error) {
|
||||
console.error(`${MODULE_ID} | Erreur lors de la création de l'acteur Traveller NPC:`, error);
|
||||
ui.notifications.error(`Erreur lors de la création de la fiche PNJ: ${error.message}`);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Fonction principale exportée
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Fonction principale pour générer un PNJ Traveller
|
||||
* Peut créer une fiche d'acteur si demandé
|
||||
*
|
||||
* @param {Object} options - Options de génération
|
||||
* @param {string} [options.citizenCategory] - Catégorie de citoyen
|
||||
* @param {string} [options.experience] - Niveau d'expérience
|
||||
* @param {string} [options.role] - Rôle
|
||||
* @param {string} [options.gender] - Genre
|
||||
* @param {boolean} [options.createActor] - Créer une fiche d'acteur
|
||||
* @param {string} [options.actorName] - Nom de la fiche
|
||||
* @param {boolean} [options.openCreatedActor] - Ouvrir la fiche créée
|
||||
* @returns {Promise<Object>} - Résultat avec le PNJ généré et éventuellement l'acteur
|
||||
*/
|
||||
export async function generateAndCreateTravellerNpc(options = {}) {
|
||||
const npcData = generateTravellerNpc(options);
|
||||
|
||||
let actor = null;
|
||||
if (options.createActor) {
|
||||
actor = await createTravellerNpcActor(npcData, {
|
||||
name: options.actorName,
|
||||
openSheet: options.openCreatedActor !== false
|
||||
});
|
||||
|
||||
if (actor) {
|
||||
npcData.createdActor = {
|
||||
id: actor.id,
|
||||
name: actor.name
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return npcData;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Export des fonctions utilitaires pour les tests
|
||||
// ============================================================================
|
||||
|
||||
export {
|
||||
generateCharacteristics,
|
||||
generateSkills,
|
||||
generateName,
|
||||
buildMgt2eCharacteristics,
|
||||
buildMgt2eSkills,
|
||||
// Ré-exporter les données
|
||||
CITIZEN_CATEGORY,
|
||||
CITIZEN_CATEGORY_LIST,
|
||||
EXPERIENCE_LEVEL,
|
||||
EXPERIENCE_LEVEL_LIST,
|
||||
ROLE,
|
||||
ROLE_LIST,
|
||||
GENDER,
|
||||
GENDER_LIST,
|
||||
CHARACTERISTIC,
|
||||
CHARACTERISTIC_LIST,
|
||||
UPP_ORDER,
|
||||
NAME_CATALOGS
|
||||
};
|
||||
Reference in New Issue
Block a user