Migration vers le système officiel

This commit is contained in:
2026-05-01 00:57:50 +02:00
parent f31f8aba27
commit 386cf89d68
107 changed files with 1212 additions and 463 deletions
+106 -94
View File
@@ -10,31 +10,40 @@ import {
RANDOM_OPPOSITION_TABLE,
ENCOUNTER_CONTEXTS,
} from './data/npcTables.js';
import { localizeSkill, setSkillLevel } from './mgt2eSkills.js';
const MODULE_ID = 'mgt2-compendium-amiral-denisov';
const SKILL_PACK_ID = `${MODULE_ID}.competences`;
const CORE_CHARACTERISTICS = ['strength', 'dexterity', 'endurance', 'intellect', 'education', 'social'];
const MGT2E_CHARACTERISTICS = {
strength: 'STR',
dexterity: 'DEX',
endurance: 'END',
intellect: 'INT',
education: 'EDU',
social: 'SOC',
};
const DEFAULT_PRIORITIES = {
'Non-combattant': ['intellect', 'education', 'social', 'dexterity', 'endurance', 'strength'],
'Combattant': ['dexterity', 'endurance', 'strength', 'education', 'intellect', 'social'],
};
let mgt2eBaseActorSystemPromise = null;
const ROLE_HINTS = [
{ match: /médecin/i, skills: ['Médecine', 'Science'], priorities: ['education', 'intellect', 'dexterity'] },
{ match: /scientifique|chercheur/i, skills: ['Science', 'Électronique'], priorities: ['intellect', 'education', 'dexterity'] },
{ match: /diplomate|ambassadeur|attaché culturel/i, skills: ['Diplomatie', 'Langage', 'Persuader'], priorities: ['social', 'education', 'intellect'] },
{ match: /marchand|franc-marchand|courtier/i, skills: ['Courtier', 'Administration', 'Persuader'], priorities: ['social', 'education', 'intellect'] },
{ match: /mercenaire|seigneur de guerre/i, skills: ['Combat Arme', 'Mêlée', 'Reconnaissance'], priorities: ['dexterity', 'endurance', 'strength'] },
{ match: /officier de marine|amiral|capitaine/i, skills: ['Leadership', 'Marin', 'Tactique'], priorities: ['education', 'social', 'intellect'] },
{ match: /explorateur|éclaireur/i, skills: ['Reconnaissance', 'Survie', 'Navigation'], priorities: ['education', 'dexterity', 'endurance'] },
{ match: /interprète|xéno/i, skills: ['Langage', 'Diplomatie', 'Science'], priorities: ['education', 'intellect', 'social'] },
{ match: /cadre de corpo|agent corpo|administrateur|gouverneur|homme d'état|noble/i, skills: ['Administration', 'Leadership', 'Diplomatie'], priorities: ['social', 'education', 'intellect'] },
{ match: /journaliste|enquêteur|inspecteur|agent impérial/i, skills: ['Enquêter', 'Persuader', 'Combat Arme'], priorities: ['intellect', 'education', 'dexterity'] },
{ match: /conspirateur|criminel|contrebandier/i, skills: ['Duperie', 'Sens de la rue', 'Combat Arme'], priorities: ['social', 'dexterity', 'intellect'] },
{ match: /chef religieux|cultiste/i, skills: ['Persuader', 'Leadership', 'Langage'], priorities: ['social', 'education', 'intellect'] },
{ match: /joueur|playboy/i, skills: ['Flambeur', 'Mondanités', 'Persuader'], priorities: ['social', 'intellect', 'education'] },
{ match: /intelligence artificielle/i, skills: ['Électronique', 'Science', 'Profession'], priorities: ['intellect', 'education', 'social'] },
{ match: /médecin/i, skills: ['medic', 'science'], priorities: ['education', 'intellect', 'dexterity'] },
{ match: /scientifique|chercheur/i, skills: ['science', 'electronics'], priorities: ['intellect', 'education', 'dexterity'] },
{ match: /diplomate|ambassadeur|attaché culturel/i, skills: ['diplomat', 'language', 'persuade'], priorities: ['social', 'education', 'intellect'] },
{ match: /marchand|franc-marchand|courtier/i, skills: ['broker', 'admin', 'persuade'], priorities: ['social', 'education', 'intellect'] },
{ match: /mercenaire|seigneur de guerre/i, skills: ['guncombat', 'melee', 'recon'], priorities: ['dexterity', 'endurance', 'strength'] },
{ match: /officier de marine|amiral|capitaine/i, skills: ['leadership', 'seafarer', 'tactics'], priorities: ['education', 'social', 'intellect'] },
{ match: /explorateur|éclaireur/i, skills: ['recon', 'survival', 'navigation'], priorities: ['education', 'dexterity', 'endurance'] },
{ match: /interprète|xéno/i, skills: ['language', 'diplomat', 'science'], priorities: ['education', 'intellect', 'social'] },
{ match: /cadre de corpo|agent corpo|administrateur|gouverneur|homme d'état|noble/i, skills: ['admin', 'leadership', 'diplomat'], priorities: ['social', 'education', 'intellect'] },
{ match: /journaliste|enquêteur|inspecteur|agent impérial/i, skills: ['investigate', 'persuade', 'guncombat'], priorities: ['intellect', 'education', 'dexterity'] },
{ match: /conspirateur|criminel|contrebandier/i, skills: ['deception', 'streetwise', 'guncombat'], priorities: ['social', 'dexterity', 'intellect'] },
{ match: /chef religieux|cultiste/i, skills: ['persuade', 'leadership', 'language'], priorities: ['social', 'education', 'intellect'] },
{ match: /joueur|playboy/i, skills: ['gambler', 'carouse', 'persuade'], priorities: ['social', 'intellect', 'education'] },
{ match: /intelligence artificielle/i, skills: ['electronics', 'science', 'profession'], priorities: ['intellect', 'education', 'social'] },
];
function getD66Entry(entries, total) {
@@ -126,9 +135,9 @@ async function generateExperience(mode = 'random') {
}
function findRoleHint(roleName, category) {
const hint = ROLE_HINTS.find((entry) => entry.match.test(roleName));
const hint = ROLE_HINTS.find((entry) => entry.match.test(roleName));
return hint ?? {
skills: ['Profession'],
skills: ['profession'],
priorities: DEFAULT_PRIORITIES[category] ?? DEFAULT_PRIORITIES['Non-combattant'],
};
}
@@ -158,41 +167,6 @@ function buildCharacteristicValues(result) {
};
}
function buildCharacteristicsData(values) {
const allKeys = {
strength: { showMax: true },
dexterity: { showMax: true },
endurance: { showMax: true },
intellect: { showMax: false },
education: { showMax: false },
social: { showMax: false },
morale: { showMax: false, value: 0 },
luck: { showMax: false, value: 0 },
sanity: { showMax: false, value: 0 },
charm: { showMax: false, value: 0 },
psionic: { showMax: false, value: 0 },
other: { showMax: false, value: 0 },
};
return Object.fromEntries(Object.entries(allKeys).map(([key, config]) => {
const value = values[key] ?? config.value ?? 0;
return [key, {
value,
max: value,
dm: calculateDm(value),
show: true,
showMax: config.showMax,
}];
}));
}
async function getSkillPackIndex() {
const pack = game.packs.get(SKILL_PACK_ID);
if (!pack) throw new Error(`Pack de compétences introuvable : ${SKILL_PACK_ID}`);
const index = await pack.getIndex();
return { pack, index };
}
function mergeSkillLevels(profileSkills, roleSkills, baseLevel) {
const levels = new Map();
@@ -202,36 +176,69 @@ function mergeSkillLevels(profileSkills, roleSkills, baseLevel) {
return levels;
}
async function buildSkillItems(result) {
const { pack, index } = await getSkillPackIndex();
const { hint } = buildCharacteristicValues(result);
const skillLevels = mergeSkillLevels(result.experience.profile.skills, hint.skills, result.experience.profile.skillLevel);
const items = [];
function buildMgt2eCharacteristics(existingCharacteristics = {}, values) {
const characteristics = foundry.utils.deepClone(existingCharacteristics);
for (const [skillName, level] of skillLevels.entries()) {
const entry = index.contents.find((item) => item.name === skillName);
if (!entry) continue;
const document = await pack.getDocument(entry._id);
const data = document.toObject();
delete data._id;
delete data.folder;
data.system.level = level;
items.push(data);
for (const [legacyKey, targetKey] of Object.entries(MGT2E_CHARACTERISTICS)) {
const value = values[legacyKey] ?? 7;
characteristics[targetKey] = foundry.utils.mergeObject(characteristics[targetKey] ?? {}, {
value,
current: value,
dm: calculateDm(value),
show: true,
default: false,
});
}
return items;
return characteristics;
}
function buildMgt2eSkills(existingSkills = {}, result) {
const skills = foundry.utils.deepClone(existingSkills);
const { hint } = buildCharacteristicValues(result);
const skillLevels = mergeSkillLevels(result.experience.profile.skills, hint.skills, result.experience.profile.skillLevel);
for (const [skillFqn, level] of skillLevels.entries()) {
setSkillLevel(skills, skillFqn, level);
}
return skills;
}
function buildActorDescription(result, actorName, ucp) {
const { hint } = buildCharacteristicValues(result);
const notableSkills = mergeSkillLevels(result.experience.profile.skills, hint.skills, result.experience.profile.skillLevel);
return [
`${actorName}${result.role.entry.text}`,
`Relation : ${result.relation.label}`,
`Particularité : ${result.quirk.entry.text}`,
`Expérience : ${result.experience.profile.label}`,
`UCP : ${ucp}`,
`UPP : ${ucp}`,
`Compétences clés : ${Array.from(notableSkills.keys()).map((skill) => localizeSkill(skill)).join(', ')}`,
].join('\n');
}
async function getMgt2eBaseActorSystem() {
if (!mgt2eBaseActorSystemPromise) {
mgt2eBaseActorSystemPromise = (async () => {
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;
})();
}
const system = await mgt2eBaseActorSystemPromise;
return system ? foundry.utils.deepClone(system) : null;
}
export function formatSigned(value) {
return value >= 0 ? `+${value}` : `${value}`;
}
@@ -254,32 +261,27 @@ export async function generateQuickNpc(params = {}) {
}
export async function createNpcActor(result, options = {}) {
const actorName = options.name?.trim() || `PNJ — ${result.role.entry.text}`;
const requestedName = options.name?.trim();
const { values, ucp } = buildCharacteristicValues(result);
const items = await buildSkillItems(result);
const actor = await Actor.create({
name: actorName,
type: 'character',
img: 'icons/svg/mystery-man.svg',
const baseActorSystem = game.system?.id === 'mgt2e' ? await getMgt2eBaseActorSystem() : null;
const npcData = {
name: requestedName || `PNJ — ${result.role.entry.text}`,
type: 'npc',
img: 'systems/mgt2e/icons/cargo/passenger-middle.svg',
system: {
life: {
value: values.endurance,
max: values.endurance,
},
personal: {
title: result.role.entry.text,
species: '',
speciesText: {},
age: '',
ucp,
traits: [
{ name: result.relation.label },
{ name: result.quirk.entry.text },
],
},
characteristics: buildCharacteristicsData(values),
biography: buildActorDescription(result, actorName, ucp),
notes: buildActorDescription(result, actorName, ucp),
settings: foundry.utils.mergeObject(foundry.utils.deepClone(baseActorSystem?.settings ?? {}), {
hideUntrained: true,
lockCharacteristics: true,
}),
sophont: foundry.utils.mergeObject(foundry.utils.deepClone(baseActorSystem?.sophont ?? {}), {
age: 18,
homeworld: '',
profession: result.role.entry.text,
}),
characteristics: foundry.utils.deepClone(baseActorSystem?.characteristics ?? {}),
hits: foundry.utils.deepClone(baseActorSystem?.hits ?? {}),
skills: foundry.utils.deepClone(baseActorSystem?.skills ?? {}),
description: '',
},
flags: {
[MODULE_ID]: {
@@ -288,12 +290,22 @@ export async function createNpcActor(result, options = {}) {
role: result.role.entry.text,
quirk: result.quirk.entry.text,
experience: result.experience.profile.label,
upp: ucp,
},
},
},
}, { renderSheet: false });
};
npcData.name = requestedName || npcData.name || `PNJ — ${result.role.entry.text}`;
npcData.system.sophont = foundry.utils.mergeObject(npcData.system.sophont ?? {}, {
profession: result.role.entry.text,
});
npcData.system.characteristics = buildMgt2eCharacteristics(npcData.system.characteristics, values);
npcData.system.skills = buildMgt2eSkills(npcData.system.skills, result);
npcData.system.description = buildActorDescription(result, npcData.name, ucp).replace(/\n/g, '<br>');
const actor = await Actor.create(npcData, { renderSheet: false });
if (items.length) await actor.createEmbeddedDocuments('Item', items);
if (options.openSheet !== false) actor.sheet?.render(true);
return actor;
}