diff --git a/AGENTS.md b/AGENTS.md index 4b71fd7..608a8fe 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -38,15 +38,20 @@ Commands are registered via `ChatLogV2.CHAT_COMMANDS` — not Hooks.on("chatMess ## Testing +Deux fichiers de test, pas de build : + ```sh -# standalone, no npm needed — install jest globally or use npx -npx jest scripts/tests/travellerNpcGenerator.test.js +# Tests Jest (globals importés via @jest/globals, flag ESM requis) +NODE_OPTIONS="--experimental-vm-modules" npx jest scripts/tests/travellerNpcGenerator.test.js + +# Mini-runner maison (Jest rapporte un faux positif « at least one test ») +node scripts/tests/allyEnemyGenerator.test.js ``` -- Single file, 1096 lines, hand-rolled FoundryVTT mocks. -- Covers: utilities, lookups, validation, characteristic/skill generation, skill conversion, full NPC gen, ModuleCache, errors, config validation. -- No CI test step — you must run manually. -- No Jest config file — relies on defaults. +- `travellerNpcGenerator.test.js` : Jest (`describe`/`it`/`expect`), mock FoundryVTT fait main, ~1100 lignes. Couvre : utilitaires, lookups, validation, caractéristiques/compétences, conversion de compétences, génération complète, ModuleCache, erreurs, config validation. +- `allyEnemyGenerator.test.js` : son propre runner (`test()`/`assertEqual`), 39 tests, à lancer avec `node` directement. +- Config Jest minimale dans `jest.config.cjs` (`testMatch` + `transform: {}`). +- Pas de CI test step — à lancer manuellement. ## Versioning diff --git a/README.md b/README.md new file mode 100644 index 0000000..d7ed99d --- /dev/null +++ b/README.md @@ -0,0 +1,147 @@ +# MgT2e — Compendium Amiral Denisov + +Module **FoundryVTT** pour **Mongoose Traveller 2e (MgT2e)**, regroupant des compendiums de référence et une suite d'outils d'aide au MJ (commerce, PNJ, rencontres, missions, cartes stellaires, calendrier impérial). + +**Compatibilité :** FoundryVTT v13 et v14 + +--- + +## ✨ Fonctionnalités + +### Commandes de chat + +| Commande | Rôle | +|---|---| +| `/commerce` | Génération d'offres de commerce (3 onglets) | +| `/pnj` | Génération de PNJ rapide (4 onglets) | +| `/rencontre` | Rencontres aléatoires | +| `/mission` | Contrats / missions aléatoires | +| `/sector` | Carte stellaire interactive (Traveller Map) | +| `/subsector` | Carte d'un sous-secteur | +| `/cal` | Fenêtre du calendrier impérial | + +### Génération de PNJ + +- **PNJ rapide** : nom, carrière, caractéristiques, compétences (localisées en français), relations +- **PNJ détaillé (Traveller)** : génération selon les règles du générateur officiel, création de fiche `npc` MgT2e avec spécialités de compétences natives +- **Alliés & Ennemis** : relations générées via tables D66, modificateurs d'affinité/inimité, caractéristiques spéciales +- **Création d'acteur** : possibilité de créer directement une fiche PNJ et de l'ouvrir + +### Import par lot de PNJ + +- **`scripts/batchNpcCreator.js`** : importe un répertoire d'images (fichiers) comme PNJs, grille de raccourcis dans la barre latérale *Acteurs* (Commerce, PNJ, Rencontre, Mission), bouton *Traveller Map* dans la barre latérale *Scènes* + +### Carte stellaire (Traveller Map) + +- Carte interactive en IFRAME avec recherche de mondes +- Carte de chat complète : UWP (avec détail), importance, économie, culture, population, noblesse, bases, remarques, allégeance, étoiles +- **Synchronisation** : le MJ peut partager la vue actuelle (hex, zoom) avec tous les joueurs via socket +- **Vue joueur** en lecture seule, mise à jour en temps réel +- **Synchronisation du canvas** : le MJ peut aligner la vue des joueurs sur la sienne + +### Calendrier impérial + +- Fenêtre compacte affichant la date impériale (`Wonday, 234-1116 IC ─ 14:30`) +- **Configurable par le MJ** : année, jour (1-365), heure:minute locale, planète +- Boutons rapides `+1 jour` et `+1 heure` (report 24 h → jour, 365 → 1) +- **Conversions calendaires** : Solomani, Vilani, Zhodani (Olympiades), Aslan, K'Kree, Hiver (formules approximatives de [Date_Conversion](https://wiki.travellerrpg.com/Date_Conversion)) +- **Synchronisé pour les joueurs** en temps réel via socket +- **Auto-ouverture** au démarrage (désactivable) et **sauvegarde de la position** de la fenêtre +- **Notes de bord** : bouton crayon → saisie d'une note datée (date impériale + date réelle), ajoutée à un journal de session + +### Compendiums + +14 compendiums organisés en dossiers thématiques : + +| Dossier | Contenu | +|---|---| +| **Équipements** | armures, objets, équipement, ordinateur, contenants, armes | +| **Références** | compétences, maladie/poison/blessure, espèces, talents psioniques, carrières | +| **PNJ & Items** | tables PNJ, PNJ, compétences, maladies, espèces, talents, carrières | +| **Journaux** | journal psioniques | + +--- + +## 📦 Installation + +1. **Copie manuelle** : placer le dossier dans `FoundryVTT/Data/modules/mgt2-compendium-amiral-denisov/` +2. **Manifest** : dans FoundryVTT, *Add-on Modules → Install Module* avec l'URL du manifest + +Activer ensuite le module dans la configuration du monde. + +--- + +## 🎮 Utilisation + +### Commencer une partie + +Tapez une commande dans le chat (ex. `/pnj`) ou utilisez les raccourcis de la barre latérale. Le message d'accueil au lancement rappelle les commandes disponibles. + +### Calendrier impérial + +1. `/cal` (ou auto-ouverture au démarrage) +2. Clic sur la roue dentée **⚙** (MJ) pour configurer la date +3. Clic sur le crayon **✎** (MJ) pour ajouter une note au journal de bord + +### Paramètres du monde + +Dans *Configuration → Réglages → Module Settings* : + +| Réglage | Description | +|---|---| +| **Calendrier impérial : auto-ouverture** | Ouvrir automatiquement la fenêtre au démarrage | +| **Journal de bord** | Nom du journal utilisé pour les notes de session (créé automatiquement) | + +--- + +## 🛠 Développement + +### Structure + +``` +module.json Manifeste du module (source de vérité pour la version) +scripts/ + data/ Tables de données (NPC, commerce, alliés/ennemis, générateur Traveller) + tests/ Tests unitaires (Jest) + *.js Logique des outils + *.css → styles/ Styles +templates/ Templates Handlebars +packs/ Compendiums (niveau de base de données) +``` + +### Prérequis + +- Aucun gestionnaire de paquets, aucun build — JavaScript ESM chargé directement par FoundryVTT +- `package.json` créé uniquement pour les tests (`"type": "module"`) + +### Tests + +Deux styles de tests coexistent (pas de configuration Jest, `package.json` n'existe que pour `"type": "module"` + mock FoundryVTT fait main) : + +- **`allyEnemyGenerator.test.js`** — mini-runner maison (`test()`/`assertEqual`), à lancer avec Node directement : + +```sh +node scripts/tests/allyEnemyGenerator.test.js # "All N tests passed" +``` + +- **`travellerNpcGenerator.test.js`** — Jest (`describe`/`it`), à lancer avec le flag ESM : + +```sh +NODE_OPTIONS="--experimental-vm-modules" npx jest scripts/tests/travellerNpcGenerator.test.js +``` + +### Conventions + +- Tout le contenu (libellés, commentaires, messages de commit) est en **français** +- Dialogs : `ApplicationV2` + `HandlebarsApplicationMixin` +- Compatibilité v13/v14 : certains hooks doivent gérer les API jQuery (v13) et DOM (v14) +- Dés à lancer : toujours `await new Roll(formula).evaluate()` + +--- + +## 📄 Licence & Crédits + +- Contenu Traveller © Mongoose Publishing — sous licence pour *Mongoose Traveller 2e* +- Cartes stellaire via [Traveller Map](https://www.travellermap.com) +- Règles de conversion calendaires d'après [Traveller Wiki](https://wiki.travellerrpg.com/Date_Conversion) +- Module initié par **JdR.Ninja** diff --git a/scripts/data/travellerNpcGenerator.js b/scripts/data/travellerNpcGenerator.js index 58d9acb..7ff16ea 100644 --- a/scripts/data/travellerNpcGenerator.js +++ b/scripts/data/travellerNpcGenerator.js @@ -723,22 +723,30 @@ export function validateOptions(options = {}) { const errors = []; const validated = { ...options }; - if (validated.citizenCategory && !getCitizenCategoryByKey(validated.citizenCategory)) { + // Remplir les valeurs manquantes avec les défauts + for (const key of ['citizenCategory', 'experience', 'role', 'gender']) { + if (!validated[key]) { + validated[key] = DEFAULT_OPTIONS[key]; + } + } + + // Corriger les valeurs invalides (getXByKey renvoie l'objet par défaut si la clé est inconnue) + if (getCitizenCategoryByKey(validated.citizenCategory).key !== validated.citizenCategory) { errors.push(`Catégorie de citoyen invalide: ${validated.citizenCategory}`); validated.citizenCategory = DEFAULT_OPTIONS.citizenCategory; } - if (validated.experience && !getExperienceLevelByKey(validated.experience)) { + if (getExperienceLevelByKey(validated.experience).key !== validated.experience) { errors.push(`Niveau d'expérience invalide: ${validated.experience}`); validated.experience = DEFAULT_OPTIONS.experience; } - if (validated.role && !getRoleByKey(validated.role)) { + if (getRoleByKey(validated.role).key !== validated.role) { errors.push(`Rôle invalide: ${validated.role}`); validated.role = DEFAULT_OPTIONS.role; } - if (validated.gender && !getGenderByKey(validated.gender)) { + if (getGenderByKey(validated.gender).key !== validated.gender) { errors.push(`Genre invalide: ${validated.gender}`); validated.gender = DEFAULT_OPTIONS.gender; } diff --git a/scripts/tests/travellerNpcGenerator.test.js b/scripts/tests/travellerNpcGenerator.test.js index 163d3d6..897c4df 100644 --- a/scripts/tests/travellerNpcGenerator.test.js +++ b/scripts/tests/travellerNpcGenerator.test.js @@ -8,6 +8,9 @@ * un framework de test similaire. */ +// Import explicite des globals Jest (obligatoire en ESM) +import { jest, describe, it, expect, beforeEach } from '@jest/globals'; + // Mock des dépendances Foundry pour l'environnement de test const mockFoundry = { utils: { @@ -85,7 +88,6 @@ import { convertSkillToMgt2eFormat, buildMgt2eCharacteristics, buildMgt2eSkills, - buildMgt2eBaseActorSystem, getMgt2eBaseActorSystem, toHex, calculateDm, @@ -579,13 +581,14 @@ describe('generateSkills', () => { expect(eliteLevel3).toBeGreaterThanOrEqual(regularLevel3); }); - it('devrait supprimer les doublons basés sur la compétence de base', () => { + it('devrait supprimer les doublons exacts de compétence', () => { const skills = generateSkills('pilot', 'regular'); const skillNames = skills.map(s => s.name); - const uniqueBases = new Set(skillNames.map(n => n.split('-')[0])); + const uniqueNames = new Set(skillNames); - // Chaque nom de base devrait apparaître une seule fois - expect(skillNames.length).toBe(uniqueBases.size); + // Le générateur dédoublonne les noms complets mais conserve + // les spécialisations (Pilot-Spacecraft, Pilot-Small Craft, ...) + expect(skillNames.length).toBe(uniqueNames.size); }); it('devrait générer des compétences avec le rôle par défaut pour un rôle invalide', () => { @@ -624,7 +627,7 @@ describe('convertSkillToMgt2eFormat', () => { }); it('devrait gérer les tirets', () => { - expect(convertSkillToMgt2eFormat('Pilot-Small Craft')).toBe('pilot.smallcraft'); + expect(convertSkillToMgt2eFormat('Pilot-Small Craft')).toBe('pilot.smallCraft'); }); }); diff --git a/scripts/travellerNpcGenerator.js b/scripts/travellerNpcGenerator.js index 4aefcd7..da54218 100644 --- a/scripts/travellerNpcGenerator.js +++ b/scripts/travellerNpcGenerator.js @@ -146,7 +146,7 @@ const SKILL_MAPPING = { * @param {string} skillName - Nom de la compétence * @returns {string} - Nom au format mgt2e */ -function convertSkillToMgt2eFormat(skillName) { +export function convertSkillToMgt2eFormat(skillName) { // Vérifier d'abord dans le mapping explicite if (SKILL_MAPPING[skillName]) { return SKILL_MAPPING[skillName]; @@ -192,8 +192,9 @@ export function generateCharacteristics(citizenCategoryKey, roleKey) { const category = getCitizenCategoryByKey(citizenCategoryKey); const priorities = getCharacteristicPrioritiesForRole(roleKey); - // Cloner et mélanger l'array de base de la catégorie - let characteristicArray = shuffleArray([...category.characteristicArray]); + // Le tableau de base de la catégorie est trié par ordre décroissant : + // on l'attribue tel quel dans l'ordre de priorité (high → medium → low) + let characteristicArray = [...category.characteristicArray]; const characteristics = {}; @@ -726,5 +727,9 @@ export { getSkillsForRole, getCharacteristicPrioritiesForRole, validateOptions, - DEFAULT_OPTIONS + DEFAULT_OPTIONS, + popRandomItems } from './data/travellerNpcGenerator.js'; + +// Ré-exporter ERROR_CODES et TravellerNpcError utilisés en interne (proviennent de utils) +export { ERROR_CODES, TravellerNpcError } from './utils/travellerNpcUtils.js';