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
+406
View File
@@ -0,0 +1,406 @@
import { inferWeaponSkillFromName } from './mgt2eSkills.js';
const MODULE_ID = 'mgt2-compendium-amiral-denisov';
const MIGRATION_SETTING = 'mgt2eMigrationVersion';
const MIGRATION_VERSION = 2;
const ITEM_PACKS = [
'armures',
'competences',
'maladie-poison-and-blessure',
'objet',
'equipement',
'ordinateur',
'contenant-sac-coffre',
'espece',
'armes',
'talents-psioniques',
'carrieres',
];
Hooks.once('init', () => {
game.settings.register(MODULE_ID, MIGRATION_SETTING, {
scope: 'world',
config: false,
type: Number,
default: 0,
});
});
Hooks.once('ready', async () => {
if (game.system?.id !== 'mgt2e' || !game.user?.isGM) return;
const currentVersion = game.settings.get(MODULE_ID, MIGRATION_SETTING) ?? 0;
if (currentVersion >= MIGRATION_VERSION) return;
ui.notifications.info('Migration mgt2e du module en cours...');
try {
for (const packName of ITEM_PACKS) {
await migrateItemPack(packName);
}
await game.settings.set(MODULE_ID, MIGRATION_SETTING, MIGRATION_VERSION);
ui.notifications.info('Migration mgt2e du module terminée.');
} catch (error) {
console.error(`${MODULE_ID} | Migration mgt2e impossible`, error);
ui.notifications.error(`Migration mgt2e inachevee : ${error.message}`);
}
});
async function migrateItemPack(packName) {
const collection = `${MODULE_ID}.${packName}`;
const pack = game.packs.get(collection);
if (!pack || pack.documentName !== 'Item') return;
const originalLocked = pack.locked;
if (originalLocked) {
await pack.configure({ locked: false });
}
try {
const documents = await pack.getDocuments();
const folderNames = buildFolderNameMap(pack);
const failures = [];
for (const document of documents) {
const folderId = document.folder?.id ?? document.folder ?? null;
const update = buildMigratedItemData(document, packName, folderNames.get(folderId));
if (!update) continue;
try {
await document.update(update, { diff: false, recursive: false });
} catch (error) {
failures.push(`${document.name}: ${error.message}`);
}
}
if (failures.length) {
throw new Error(`${packName} (${failures.length} erreurs) — ${failures.slice(0, 5).join(' | ')}`);
}
} finally {
if (originalLocked) {
await pack.configure({ locked: true });
}
}
}
function buildFolderNameMap(pack) {
const folders = new Map();
for (const folder of pack.folders ?? []) {
folders.set(folder.id, folder.name ?? '');
}
return folders;
}
function buildMigratedItemData(document, packName, folderName = '') {
const legacyType = document.type;
const folderId = document.folder?.id ?? document.folder ?? null;
const common = {
name: document.name,
img: document.img,
flags: foundry.utils.mergeObject(document.flags ?? {}, {
[MODULE_ID]: {
legacyType,
migratedToMgt2e: MIGRATION_VERSION,
},
}),
folder: folderId,
sort: document.sort,
ownership: document.ownership,
};
switch (legacyType) {
case 'armor':
return {
...common,
type: 'armour',
system: buildArmourSystem(document),
};
case 'weapon':
return {
...common,
type: 'weapon',
system: buildWeaponSystem(document),
};
case 'equipment':
return {
...common,
type: isAugmentFolder(folderName) ? 'augment' : 'item',
system: isAugmentFolder(folderName) ? buildAugmentSystem(document) : buildSimpleItemSystem(document),
};
case 'computer':
return {
...common,
type: 'hardware',
system: buildHardwareSystem(document),
};
case 'item':
if (document.system?.subType === 'software') {
return {
...common,
type: 'software',
system: buildSoftwareSystem(document),
};
}
return {
...common,
type: 'item',
system: buildSimpleItemSystem(document),
};
case 'career':
return {
...common,
type: 'item',
system: buildReferenceItemSystem(document, 'Carriere', packName),
};
case 'talent':
return {
...common,
type: 'item',
system: buildReferenceItemSystem(document, document.system?.subType === 'psionic' ? 'Talent psionique' : 'Competence', packName),
};
case 'disease':
return {
...common,
type: 'item',
system: buildDiseaseItemSystem(document),
};
default:
return {
...common,
type: 'item',
system: buildReferenceItemSystem(document, legacyType, packName),
};
}
}
function buildWeaponSystem(document) {
const system = document.system ?? {};
const traits = normalizeTraits(system.traits);
const melee = Boolean(system.range?.isMelee);
return {
tl: normalizeTl(system.tl),
weight: toNumber(system.weight),
cost: toNumber(system.cost),
notes: traits.notes,
active: Boolean(system.equipped),
quantity: toNumber(system.quantity, 1),
status: system.trash ? 'broken' : null,
legality: 9,
weapon: {
scale: 'traveller',
range: melee ? 0 : toNumber(system.range?.value),
minRange: 0,
damage: normalizeDamage(system.damage),
magazine: toNumber(system.magazine),
ammo: toNumber(system.magazine),
magazineCost: toNumber(system.magazineCost),
characteristic: melee ? 'STR' : 'DEX',
skill: inferWeaponSkillFromName(document.name, melee),
parryBonus: 0,
damageBonus: melee ? 'STR' : '',
damageType: 'standard',
attackBonus: 0,
traits: traits.names,
},
description: formatDescription(system.description),
};
}
function buildArmourSystem(document) {
const system = document.system ?? {};
return {
tl: normalizeTl(system.tl),
weight: toNumber(system.weight),
cost: toNumber(system.cost),
notes: buildLines([
system.requireSkillLevel != null ? `Combi requise: ${system.requireSkillLevel}` : '',
]),
active: Boolean(system.equipped),
quantity: toNumber(system.quantity, 1),
status: system.trash ? 'broken' : null,
legality: 9,
armour: {
protection: toNumber(String(system.protection ?? '').replace(/[^\d-]/g, '')),
otherProtection: 0,
otherTypes: '',
rad: toNumber(system.radiations),
archaic: 0,
skill: toNumber(system.requireSkillLevel) > 0 ? 'vaccsuit' : '',
duration: 0,
slots: 0,
form: 'standard',
layered: 0,
ablat: 0,
powered: Boolean(system.powered) ? 1 : 0,
psi: 0,
worn: Boolean(system.equipped) ? 1 : 0,
},
description: formatDescription(system.description),
};
}
function buildSimpleItemSystem(document) {
const system = document.system ?? {};
return {
tl: normalizeTl(system.tl),
weight: toNumber(system.weight),
cost: toNumber(system.cost),
notes: '',
active: Boolean(system.equipped),
quantity: toNumber(system.quantity, 1),
status: system.trash ? 'broken' : null,
legality: 9,
description: formatDescription(system.description),
};
}
function buildAugmentSystem(document) {
const system = buildSimpleItemSystem(document);
return system;
}
function buildHardwareSystem(document) {
const system = document.system ?? {};
return {
tl: normalizeTl(system.tl),
weight: toNumber(system.weight),
cost: toNumber(system.cost),
notes: buildLines([
system.overload ? 'Surcharge: oui' : '',
system.processing != null ? `Traitement: ${system.processing}` : '',
system.processingUsed != null ? `Traitement utilise: ${system.processingUsed}` : '',
]),
active: false,
quantity: toNumber(system.quantity, 1),
status: system.trash ? 'broken' : null,
legality: 9,
hardware: {
system: 'computer',
tons: 0,
power: toNumber(system.processing),
rating: inferRating(document.name),
variables: {
max: 0,
tl: normalizeTl(system.tl),
cost: toNumber(system.cost),
},
},
description: formatDescription(system.description),
};
}
function buildSoftwareSystem(document) {
const system = document.system ?? {};
return {
quantity: toNumber(system.quantity, 1),
tl: normalizeTl(system.tl),
software: {
bandwidth: toNumber(system.software?.bandwidth),
},
cost: toNumber(system.cost),
description: formatDescription(system.description),
};
}
function buildReferenceItemSystem(document, category, packName) {
const system = document.system ?? {};
const isSkillReference = packName === 'competences';
return {
tl: normalizeTl(system.tl),
weight: toNumber(system.weight),
cost: toNumber(system.cost),
notes: buildLines([
isSkillReference ? 'Référence documentaire uniquement — les compétences de jeu utilisent désormais actor.system.skills et la localisation MGT2.Skills.*.' : '',
category ? `Categorie legacy: ${category}` : '',
system.subType ? `Sous-type legacy: ${system.subType}` : '',
system.level != null ? `Niveau legacy: ${system.level}` : '',
system.assignment ? `Affectations: ${system.assignment}` : '',
]),
active: false,
quantity: toNumber(system.quantity, 1),
status: null,
legality: 9,
description: isSkillReference
? formatDescription([system.description, '<p><strong>Note mgt2e :</strong> cette entrée sert de référence documentaire. Les compétences jouables sont natives à la fiche de personnage `mgt2e`.</p>'].filter(Boolean).join('\n\n'))
: formatDescription(system.description),
};
}
function buildDiseaseItemSystem(document) {
const system = document.system ?? {};
return {
tl: '0',
weight: 0,
cost: 0,
notes: buildLines([
system.subType ? `Sous-type: ${system.subType}` : '',
system.difficulty ? `Difficulte: ${system.difficulty}` : '',
system.damage ? `Degats: ${system.damage}` : '',
system.interval ? `Intervalle: ${system.interval}` : '',
]),
active: false,
quantity: 1,
status: null,
legality: 9,
description: formatDescription(system.description),
};
}
function isAugmentFolder(folderName) {
return /augmentation/i.test(folderName ?? '');
}
function normalizeTl(value) {
const match = String(value ?? '').match(/(\d+)/);
return match ? match[1] : '0';
}
function normalizeDamage(value) {
return String(value ?? '1D6').replace(/d/g, 'D');
}
function toNumber(value, fallback = 0) {
const parsed = Number.parseInt(String(value ?? ''), 10);
return Number.isFinite(parsed) ? parsed : fallback;
}
function inferRating(name) {
const match = String(name ?? '').match(/\/(\d+)/);
return match ? Number.parseInt(match[1], 10) : 0;
}
function normalizeTraits(traits) {
if (!Array.isArray(traits) || !traits.length) return { names: '', notes: '' };
const names = traits
.map((trait) => trait?.name ?? trait)
.filter(Boolean)
.join(', ');
const notes = traits
.filter((trait) => trait?.description)
.map((trait) => `${trait.name}: ${trait.description}`)
.join('\n');
return { names, notes };
}
function formatDescription(value) {
const text = String(value ?? '').trim();
if (!text) return '';
if (text.includes('<')) return text;
const paragraphs = text
.split(/\n{2,}/)
.map((part) => part.trim())
.filter(Boolean)
.map((part) => `<p>${part.replace(/\n/g, '<br>')}</p>`);
return paragraphs.join('');
}
function buildLines(lines) {
return lines.filter(Boolean).join('\n');
}