Migration vers le système officiel
This commit is contained in:
@@ -8,6 +8,7 @@
|
||||
|
||||
import { calculatePassengers, calculateCargo, findAvailableGoods, calculatePrice, formatCredits } from './tradeHelper.js';
|
||||
import { searchWorlds, fetchWorldDetail, fetchWorldCoordinates, calcParsecs } from './travellerMapApi.js';
|
||||
import { buildActiveActorContext, COMMERCE_SKILLS, getActiveTravellerActor, rollActorSkillEffect, getActorSkillSummary } from './mgt2eSkills.js';
|
||||
|
||||
const MODULE_ID = 'mgt2-compendium-amiral-denisov';
|
||||
|
||||
@@ -33,9 +34,9 @@ export class CommerceDialog extends FormApplication {
|
||||
}
|
||||
|
||||
static get defaultOptions() {
|
||||
return mergeObject(super.defaultOptions, {
|
||||
return foundry.utils.mergeObject(super.defaultOptions, {
|
||||
id: 'mgt2-commerce',
|
||||
title: 'Commerce – MGT2',
|
||||
title: 'Commerce – MgT2e',
|
||||
template: `modules/${MODULE_ID}/templates/commerce-dialog.hbs`,
|
||||
width: 780,
|
||||
height: 'auto',
|
||||
@@ -51,7 +52,10 @@ export class CommerceDialog extends FormApplication {
|
||||
|
||||
getData() {
|
||||
_registerHandlebarsHelpers();
|
||||
return foundry.utils.mergeObject(super.getData(), this._formData);
|
||||
return foundry.utils.mergeObject(super.getData(), {
|
||||
...this._formData,
|
||||
activeActor: buildActiveActorContext(),
|
||||
});
|
||||
}
|
||||
|
||||
activateListeners(html) {
|
||||
@@ -84,6 +88,31 @@ export class CommerceDialog extends FormApplication {
|
||||
await this._handleBuyPrices();
|
||||
});
|
||||
|
||||
html.find('[data-action="load-pax-actor"]').on('click', async (ev) => {
|
||||
ev.preventDefault();
|
||||
this._applyPassengerActorData(html);
|
||||
});
|
||||
|
||||
html.find('[data-action="roll-pax-effect"]').on('click', async (ev) => {
|
||||
ev.preventDefault();
|
||||
await this._rollActorEffectIntoField(html, 'pax.skillEffect', COMMERCE_SKILLS.passengerEffect);
|
||||
});
|
||||
|
||||
html.find('[data-action="load-cargo-actor"]').on('click', async (ev) => {
|
||||
ev.preventDefault();
|
||||
this._applyCargoActorData(html);
|
||||
});
|
||||
|
||||
html.find('[data-action="roll-cargo-effect"]').on('click', async (ev) => {
|
||||
ev.preventDefault();
|
||||
await this._rollActorEffectIntoField(html, 'cargo.skillEffect', COMMERCE_SKILLS.cargoEffect);
|
||||
});
|
||||
|
||||
html.find('[data-action="load-trade-actor"]').on('click', async (ev) => {
|
||||
ev.preventDefault();
|
||||
this._applyTradeActorData(html);
|
||||
});
|
||||
|
||||
// ─── Recherche de monde (Traveller Map API) ───────────────────────────────
|
||||
html.find('.world-search-widget').each((_, widget) => {
|
||||
const $widget = $(widget);
|
||||
@@ -205,6 +234,73 @@ export class CommerceDialog extends FormApplication {
|
||||
this._formData.trade.blackMarket = html.find('[name="trade.blackMarket"]').is(':checked');
|
||||
}
|
||||
|
||||
_getActiveActorOrWarn() {
|
||||
const { actor } = getActiveTravellerActor();
|
||||
if (!actor) {
|
||||
ui.notifications.warn('Aucun token sélectionné ni personnage assigné pour lire les compétences mgt2e.');
|
||||
return null;
|
||||
}
|
||||
return actor;
|
||||
}
|
||||
|
||||
_applyPassengerActorData(html) {
|
||||
const actor = this._getActiveActorOrWarn();
|
||||
if (!actor) return;
|
||||
|
||||
const steward = getActorSkillSummary(actor, COMMERCE_SKILLS.steward);
|
||||
this._setNumericSelectValue(html, 'pax.stewardLevel', steward.value);
|
||||
this._readForm(html);
|
||||
ui.notifications.info(`${actor.name} : Intendant ${steward.value} chargé dans le calcul passagers.`);
|
||||
}
|
||||
|
||||
_applyCargoActorData(html) {
|
||||
const actor = this._getActiveActorOrWarn();
|
||||
if (!actor) return;
|
||||
|
||||
const socMod = Number(actor.system.characteristics?.SOC?.dm ?? 0);
|
||||
this._setNumericSelectValue(html, 'cargo.socMod', socMod);
|
||||
this._readForm(html);
|
||||
ui.notifications.info(`${actor.name} : DM SOC ${socMod >= 0 ? '+' : ''}${socMod} chargé dans le calcul cargaison.`);
|
||||
}
|
||||
|
||||
_applyTradeActorData(html) {
|
||||
const actor = this._getActiveActorOrWarn();
|
||||
if (!actor) return;
|
||||
|
||||
const broker = getActorSkillSummary(actor, COMMERCE_SKILLS.tradeBroker);
|
||||
this._setNumericSelectValue(html, 'trade.brokerSkill', broker.value);
|
||||
this._readForm(html);
|
||||
ui.notifications.info(`${actor.name} : Courtier ${broker.value} chargé pour le commerce spéculatif.`);
|
||||
}
|
||||
|
||||
async _rollActorEffectIntoField(html, fieldName, skillList) {
|
||||
const actor = this._getActiveActorOrWarn();
|
||||
if (!actor) return;
|
||||
|
||||
const rollResult = await rollActorSkillEffect(actor, skillList);
|
||||
if (!rollResult) {
|
||||
ui.notifications.warn(`${actor.name} ne possède aucune des compétences attendues pour ce calcul.`);
|
||||
return;
|
||||
}
|
||||
|
||||
this._setNumericSelectValue(html, fieldName, rollResult.effect);
|
||||
this._readForm(html);
|
||||
ui.notifications.info(`${actor.name} : ${rollResult.label} → 2D6 (${rollResult.diceTotal}) + ${rollResult.totalModifier >= 0 ? '+' : ''}${rollResult.totalModifier} = ${rollResult.total}, effet ${rollResult.effect >= 0 ? '+' : ''}${rollResult.effect}.`);
|
||||
}
|
||||
|
||||
_setNumericSelectValue(html, fieldName, value) {
|
||||
const select = html.find(`[name="${fieldName}"]`);
|
||||
if (!select.length) return;
|
||||
|
||||
const normalized = Number(value ?? 0);
|
||||
if (!select.find(`option[value="${normalized}"]`).length) {
|
||||
const label = normalized > 0 ? `+${normalized}` : `${normalized}`;
|
||||
select.append(`<option value="${normalized}">${label}</option>`);
|
||||
}
|
||||
|
||||
select.val(`${normalized}`);
|
||||
}
|
||||
|
||||
// ─── Passagers ─────────────────────────────────────────────────────────────
|
||||
|
||||
async _handlePassengers() {
|
||||
|
||||
@@ -25,7 +25,7 @@ export class NpcDialog extends FormApplication {
|
||||
static get defaultOptions() {
|
||||
return foundry.utils.mergeObject(super.defaultOptions, {
|
||||
id: 'mgt2-npc',
|
||||
title: 'PNJ & Rencontres – MGT2',
|
||||
title: 'PNJ & Rencontres – MgT2e',
|
||||
template: `modules/${MODULE_ID}/templates/npc-dialog.hbs`,
|
||||
width: 720,
|
||||
height: 'auto',
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
*/
|
||||
|
||||
import { CommerceDialog } from './CommerceDialog.js';
|
||||
import './mgt2eMigration.js';
|
||||
|
||||
const MODULE_ID = 'mgt2-compendium-amiral-denisov';
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ export const EXPERIENCE_PROFILES = [
|
||||
label: 'Non-combattant bleu',
|
||||
skillLevel: 0,
|
||||
characteristicBonuses: [],
|
||||
skills: ['Conduire/aéronef'],
|
||||
skills: ['pilot'],
|
||||
},
|
||||
{
|
||||
key: 'combatant-blue',
|
||||
@@ -34,7 +34,7 @@ export const EXPERIENCE_PROFILES = [
|
||||
label: 'Combattant bleu',
|
||||
skillLevel: 0,
|
||||
characteristicBonuses: [],
|
||||
skills: ['Conduire/aéronef', 'Combat Arme', 'Mêlée'],
|
||||
skills: ['pilot', 'guncombat', 'melee'],
|
||||
},
|
||||
{
|
||||
key: 'noncombatant-average',
|
||||
@@ -43,7 +43,7 @@ export const EXPERIENCE_PROFILES = [
|
||||
label: 'Non-combattant moyen',
|
||||
skillLevel: 1,
|
||||
characteristicBonuses: ['+1'],
|
||||
skills: ['Conduire/aéronef', 'Profession'],
|
||||
skills: ['pilot', 'profession'],
|
||||
},
|
||||
{
|
||||
key: 'combatant-average',
|
||||
@@ -52,7 +52,7 @@ export const EXPERIENCE_PROFILES = [
|
||||
label: 'Combattant moyen',
|
||||
skillLevel: 1,
|
||||
characteristicBonuses: ['+1'],
|
||||
skills: ['Conduire/aéronef', 'Combat Arme', 'Mêlée', 'Reconnaissance'],
|
||||
skills: ['pilot', 'guncombat', 'melee', 'recon'],
|
||||
},
|
||||
{
|
||||
key: 'noncombatant-experienced',
|
||||
@@ -61,7 +61,7 @@ export const EXPERIENCE_PROFILES = [
|
||||
label: 'Non-combattant expérimenté',
|
||||
skillLevel: 2,
|
||||
characteristicBonuses: ['+1', '+2'],
|
||||
skills: ['Administration', 'Conduire/aéronef', 'Profession'],
|
||||
skills: ['admin', 'pilot', 'profession'],
|
||||
},
|
||||
{
|
||||
key: 'combatant-experienced',
|
||||
@@ -70,7 +70,7 @@ export const EXPERIENCE_PROFILES = [
|
||||
label: 'Combattant expérimenté',
|
||||
skillLevel: 2,
|
||||
characteristicBonuses: ['+1', '+2'],
|
||||
skills: ['Conduire/aéronef', 'Combat Arme', 'Armes lourdes', 'Mêlée', 'Reconnaissance'],
|
||||
skills: ['pilot', 'guncombat', 'heavyweapons', 'melee', 'recon'],
|
||||
},
|
||||
{
|
||||
key: 'noncombatant-elite',
|
||||
@@ -79,7 +79,7 @@ export const EXPERIENCE_PROFILES = [
|
||||
label: 'Non-combattant élite',
|
||||
skillLevel: 3,
|
||||
characteristicBonuses: ['+1', '+2', '+3'],
|
||||
skills: ['Administration', 'Conduire/aéronef', 'Enquêter', 'Profession'],
|
||||
skills: ['admin', 'pilot', 'investigate', 'profession'],
|
||||
},
|
||||
{
|
||||
key: 'combatant-elite',
|
||||
@@ -88,7 +88,7 @@ export const EXPERIENCE_PROFILES = [
|
||||
label: 'Combattant élite',
|
||||
skillLevel: 3,
|
||||
characteristicBonuses: ['+1', '+2', '+3'],
|
||||
skills: ['Conduire/aéronef', 'Combat Arme', 'Armes lourdes', 'Mêlée', 'Reconnaissance', 'Tactique'],
|
||||
skills: ['pilot', 'guncombat', 'heavyweapons', 'melee', 'recon', 'tactics'],
|
||||
},
|
||||
];
|
||||
|
||||
|
||||
@@ -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');
|
||||
}
|
||||
@@ -0,0 +1,180 @@
|
||||
const SKILL_LOCALIZATION_PREFIX = 'MGT2.Skills.';
|
||||
|
||||
export const COMMERCE_SKILLS = Object.freeze({
|
||||
passengerEffect: ['carouse', 'broker', 'streetwise'],
|
||||
cargoEffect: ['broker', 'streetwise'],
|
||||
tradeBroker: 'broker',
|
||||
steward: 'steward',
|
||||
});
|
||||
|
||||
export function splitSkillFqn(skillFqn = '') {
|
||||
const [skillId = '', specialityId = ''] = String(skillFqn ?? '').split('.');
|
||||
return { skillId, specialityId };
|
||||
}
|
||||
|
||||
export function localizeSkill(skillFqn, fallback = '') {
|
||||
const { skillId, specialityId } = splitSkillFqn(skillFqn);
|
||||
if (!skillId) return fallback;
|
||||
|
||||
const skillLabel = localizeSkillId(skillId);
|
||||
if (!specialityId) return skillLabel;
|
||||
return `${skillLabel} (${localizeSkillId(specialityId)})`;
|
||||
}
|
||||
|
||||
export function getActiveTravellerActor() {
|
||||
const controlled = canvas?.tokens?.controlled?.find((token) => token.actor?.type === 'traveller' || token.actor?.type === 'npc')?.actor;
|
||||
if (controlled) return { actor: controlled, source: 'token' };
|
||||
|
||||
if (game.user?.character?.type === 'traveller' || game.user?.character?.type === 'npc') {
|
||||
return { actor: game.user.character, source: 'character' };
|
||||
}
|
||||
|
||||
const owned = game.actors?.find((actor) => actor.isOwner && (actor.type === 'traveller' || actor.type === 'npc'));
|
||||
if (owned) return { actor: owned, source: 'owned' };
|
||||
|
||||
return { actor: null, source: null };
|
||||
}
|
||||
|
||||
export function getActorSkillSummary(actor, skillFqn) {
|
||||
if (!actor?.system?.skills) return buildEmptySkillSummary(skillFqn);
|
||||
|
||||
const { skillId, specialityId } = splitSkillFqn(skillFqn);
|
||||
const skill = actor.system.skills[skillId];
|
||||
if (!skill) return buildEmptySkillSummary(skillFqn);
|
||||
|
||||
const speciality = specialityId ? skill.specialities?.[specialityId] ?? null : null;
|
||||
const characteristic = speciality?.default || skill.default || null;
|
||||
const characteristicDm = characteristic ? Number(actor.system.characteristics?.[characteristic]?.dm ?? 0) : 0;
|
||||
const trained = Boolean(skill.trained) || Number(skill.value ?? 0) > 0 || Number(speciality?.value ?? 0) > 0;
|
||||
const value = speciality ? Number(speciality.value ?? 0) : Number(skill.value ?? 0);
|
||||
const jackOfAllTrades = Number(actor.system.skills?.jackofalltrades?.value ?? 0);
|
||||
const rollValue = trained ? value : jackOfAllTrades - 3;
|
||||
|
||||
return {
|
||||
skillFqn,
|
||||
skillId,
|
||||
specialityId,
|
||||
label: localizeSkill(skillFqn, skill.label || skillId),
|
||||
value,
|
||||
rollValue,
|
||||
trained,
|
||||
characteristic,
|
||||
characteristicDm,
|
||||
totalModifier: rollValue + characteristicDm,
|
||||
};
|
||||
}
|
||||
|
||||
export function getActorCharacteristicSummary(actor, characteristicId) {
|
||||
if (!actor?.system?.characteristics?.[characteristicId]) {
|
||||
return { id: characteristicId, value: 0, dm: 0, label: characteristicId };
|
||||
}
|
||||
|
||||
const characteristic = actor.system.characteristics[characteristicId];
|
||||
return {
|
||||
id: characteristicId,
|
||||
value: Number(characteristic.value ?? 0),
|
||||
dm: Number(characteristic.dm ?? 0),
|
||||
label: game.i18n.localize(`MGT2.Characteristics.${characteristicId}`),
|
||||
};
|
||||
}
|
||||
|
||||
export function getBestActorSkillSummary(actor, skillList = []) {
|
||||
const summaries = skillList
|
||||
.map((skillFqn) => getActorSkillSummary(actor, skillFqn))
|
||||
.filter((summary) => summary.skillId);
|
||||
|
||||
if (!summaries.length) return null;
|
||||
|
||||
return summaries.sort((left, right) => {
|
||||
if (right.totalModifier !== left.totalModifier) return right.totalModifier - left.totalModifier;
|
||||
if (right.value !== left.value) return right.value - left.value;
|
||||
return left.label.localeCompare(right.label, 'fr');
|
||||
})[0];
|
||||
}
|
||||
|
||||
export async function rollActorSkillEffect(actor, skillList = [], difficulty = 8) {
|
||||
const summary = getBestActorSkillSummary(actor, skillList);
|
||||
if (!summary) return null;
|
||||
|
||||
const roll = await new Roll('2d6').evaluate();
|
||||
const total = Number(roll.total ?? 0) + summary.totalModifier;
|
||||
return {
|
||||
...summary,
|
||||
diceTotal: Number(roll.total ?? 0),
|
||||
total,
|
||||
effect: total - difficulty,
|
||||
difficulty,
|
||||
};
|
||||
}
|
||||
|
||||
export function setSkillLevel(skills, skillFqn, level) {
|
||||
const { skillId, specialityId } = splitSkillFqn(skillFqn);
|
||||
if (!skillId || !skills?.[skillId]) return skills;
|
||||
|
||||
const numericLevel = Number(level ?? 0);
|
||||
const skill = foundry.utils.mergeObject(skills[skillId], { trained: numericLevel > 0 || skills[skillId].trained });
|
||||
|
||||
if (specialityId && skill.specialities?.[specialityId]) {
|
||||
skill.specialities[specialityId] = foundry.utils.mergeObject(skill.specialities[specialityId], {
|
||||
value: Math.max(Number(skill.specialities[specialityId].value ?? 0), numericLevel),
|
||||
});
|
||||
} else {
|
||||
skill.value = Math.max(Number(skill.value ?? 0), numericLevel);
|
||||
}
|
||||
|
||||
skills[skillId] = skill;
|
||||
return skills;
|
||||
}
|
||||
|
||||
export function buildActiveActorContext() {
|
||||
const { actor, source } = getActiveTravellerActor();
|
||||
if (!actor) return null;
|
||||
|
||||
return {
|
||||
id: actor.id,
|
||||
name: actor.name,
|
||||
source,
|
||||
sourceLabel: source === 'token' ? 'token sélectionné' : source === 'character' ? 'personnage assigné' : 'acteur possédé',
|
||||
broker: getActorSkillSummary(actor, 'broker'),
|
||||
carouse: getActorSkillSummary(actor, 'carouse'),
|
||||
streetwise: getActorSkillSummary(actor, 'streetwise'),
|
||||
steward: getActorSkillSummary(actor, 'steward'),
|
||||
soc: getActorCharacteristicSummary(actor, 'SOC'),
|
||||
};
|
||||
}
|
||||
|
||||
export function inferWeaponSkillFromName(name, melee) {
|
||||
const label = String(name ?? '').toLowerCase();
|
||||
if (melee) {
|
||||
if (/(sabre|épée|epee|lame|poignard|couteau|fleuret|rapiere|rapière)/i.test(label)) return 'melee.blade';
|
||||
if (/(massue|matraque|bâton|baton|gourdin|marteau|masse)/i.test(label)) return 'melee.bludgeon';
|
||||
if (/(griffe|morsure|corne|tentacule|naturel)/i.test(label)) return 'melee.natural';
|
||||
return 'melee.unarmed';
|
||||
}
|
||||
|
||||
if (/(laser|plasma|fusion|particule|meson)/i.test(label)) return 'guncombat.energy';
|
||||
if (/(arc|arbal[eè]te|javelot|lance[- ]?harpon)/i.test(label)) return 'guncombat.archaic';
|
||||
if (/(canon|lance[- ]?grenade|missile|mortier|roquette)/i.test(label)) return 'heavyweapons.portable';
|
||||
return 'guncombat.slug';
|
||||
}
|
||||
|
||||
function localizeSkillId(skillId) {
|
||||
const localized = game.i18n.localize(`${SKILL_LOCALIZATION_PREFIX}${skillId}`);
|
||||
return localized.startsWith(SKILL_LOCALIZATION_PREFIX) ? skillId : localized;
|
||||
}
|
||||
|
||||
function buildEmptySkillSummary(skillFqn) {
|
||||
const { skillId, specialityId } = splitSkillFqn(skillFqn);
|
||||
return {
|
||||
skillFqn,
|
||||
skillId,
|
||||
specialityId,
|
||||
label: localizeSkill(skillFqn, skillId),
|
||||
value: 0,
|
||||
rollValue: -3,
|
||||
trained: false,
|
||||
characteristic: null,
|
||||
characteristicDm: 0,
|
||||
totalModifier: -3,
|
||||
};
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
import { NpcDialog } from './NpcDialog.js';
|
||||
import { syncNpcRollTables } from './npcRollTableSync.js';
|
||||
import './mgt2eMigration.js';
|
||||
|
||||
const MODULE_ID = 'mgt2-compendium-amiral-denisov';
|
||||
|
||||
|
||||
+106
-94
@@ -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;
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ import { NPC_ROLLTABLE_DEFINITIONS } from './data/npcTables.js';
|
||||
|
||||
const MODULE_ID = 'mgt2-compendium-amiral-denisov';
|
||||
const PACK_ID = `${MODULE_ID}.tables-pnj`;
|
||||
const WORLD_FOLDER_NAME = 'MGT2 — Tables PNJ';
|
||||
const WORLD_FOLDER_NAME = 'MgT2e — Tables PNJ';
|
||||
const ROLLTABLES_VERSION = 1;
|
||||
|
||||
function entryText(entry) {
|
||||
|
||||
Reference in New Issue
Block a user