equipment.ts•6.69 kB
import { join } from 'path';
import { readXML } from './xml.js';
import { readDVPLString } from '../dvpl/reader.js';
import { getString } from './strings.js';
import type { Config } from '../config.js';
import type { Equipment, Consumable, Provision } from '../data/types.js';
interface ScriptObject {
'#text'?: string;
cooldown?: number;
duration?: number;
bonusValues?: Record<string, number>;
}
type DisplayParamValue = number | Record<string, number>;
interface OptionalDeviceEntry {
id: number;
userString: string;
description: string;
icon: string;
script: string | ScriptObject;
display_params?: Record<string, DisplayParamValue>;
}
interface OptionalDevicesXML {
root: Record<string, OptionalDeviceEntry | number>;
}
interface ConsumableEntry {
id: number;
userString: string;
description: string;
icon: string;
category: string;
tags: string;
script: string | ScriptObject;
display_params?: Record<string, DisplayParamValue>;
}
interface ConsumablesXML {
root: Record<string, ConsumableEntry>;
}
interface ProvisionEntry {
id: number;
userString: string;
description: string;
icon: string;
category: string;
tags: string;
script: string | ScriptObject;
display_params?: Record<string, DisplayParamValue>;
}
interface ProvisionsXML {
root: Record<string, ProvisionEntry>;
}
let equipmentCache: Equipment[] | null = null;
let consumablesCache: Consumable[] | null = null;
let provisionsCache: Provision[] | null = null;
function getScriptCooldown(script: string | ScriptObject): number {
if (typeof script === 'string') return 0;
return script.cooldown ?? 0;
}
function getScriptDuration(script: string | ScriptObject): number | undefined {
if (typeof script === 'string') return undefined;
return script.duration;
}
function getScriptCrewBonus(script: string | ScriptObject): number | undefined {
if (typeof script === 'string') return undefined;
return script.bonusValues?.crewLevelIncrease;
}
function getScriptBonusValues(script: string | ScriptObject): Record<string, number> | undefined {
if (typeof script === 'string') return undefined;
if (!script.bonusValues) return undefined;
const result: Record<string, number> = {};
for (const [key, value] of Object.entries(script.bonusValues)) {
if (typeof value === 'number') {
result[key] = value;
}
}
return Object.keys(result).length > 0 ? result : undefined;
}
export async function loadEquipment(config: Config): Promise<void> {
if (equipmentCache) return;
const path = join(config.paths.common, 'optional_devices.xml');
const data = await readXML<OptionalDevicesXML>(path);
equipmentCache = [];
for (const [key, entry] of Object.entries(data.root)) {
if (key === 'nextAvailableId' || typeof entry === 'number') continue;
const nameKey = entry.userString.startsWith('#artefacts:')
? entry.userString
: entry.userString.replace('#', '');
const descKey = entry.description.startsWith('#artefacts:')
? entry.description
: entry.description.replace('#', '');
equipmentCache.push({
id: entry.id,
name: getString(nameKey),
description: getString(descKey),
effects: entry.display_params,
});
}
}
async function loadConsumableFile(path: string): Promise<ConsumableEntry[]> {
const data = await readXML<ConsumablesXML>(path);
return Object.entries(data.root)
.filter(([key, entry]) => key !== 'nextAvailableId' && typeof entry === 'object' && entry.id)
.map(([, entry]) => entry as ConsumableEntry);
}
export async function loadConsumables(config: Config): Promise<void> {
if (consumablesCache) return;
const consumablesPath = join(config.paths.common, 'consumables');
const listPath = join(consumablesPath, 'list.xml');
const listContent = await readDVPLString(listPath);
const fileMatches = listContent.matchAll(/<items path="(.+)\.xml"\/>/g);
const entries: ConsumableEntry[] = [];
for (const match of fileMatches) {
const fileName = match[1];
if (!fileName || fileName === 'prototypes') continue;
const filePath = join(consumablesPath, `${fileName}.xml`);
const fileEntries = await loadConsumableFile(filePath);
entries.push(...fileEntries);
}
consumablesCache = entries.map(entry => {
const nameKey = entry.userString.startsWith('#artefacts:')
? entry.userString
: entry.userString.replace('#', '');
const descKey = entry.description.startsWith('#artefacts:')
? entry.description
: entry.description.replace('#', '');
const effects = entry.display_params ?? getScriptBonusValues(entry.script);
return {
id: entry.id,
name: getString(nameKey),
description: getString(descKey),
cooldown: getScriptCooldown(entry.script),
duration: getScriptDuration(entry.script),
effects,
};
});
}
async function loadProvisionFile(path: string): Promise<ProvisionEntry[]> {
const data = await readXML<ProvisionsXML>(path);
return Object.entries(data.root)
.filter(([key, entry]) => key !== 'nextAvailableId' && typeof entry === 'object' && entry.id)
.map(([, entry]) => entry as ProvisionEntry);
}
export async function loadProvisions(config: Config): Promise<void> {
if (provisionsCache) return;
const provisionsPath = join(config.paths.common, 'provisions');
const listPath = join(provisionsPath, 'list.xml');
const listContent = await readDVPLString(listPath);
const fileMatches = listContent.matchAll(/<items path="(.+)\.xml"\/>/g);
const entries: ProvisionEntry[] = [];
for (const match of fileMatches) {
const fileName = match[1];
if (!fileName || fileName === 'prototypes') continue;
const filePath = join(provisionsPath, `${fileName}.xml`);
const fileEntries = await loadProvisionFile(filePath);
entries.push(...fileEntries);
}
provisionsCache = entries.map(entry => {
const nameKey = entry.userString.startsWith('#artefacts:')
? entry.userString
: entry.userString.replace('#', '');
const descKey = entry.description.startsWith('#artefacts:')
? entry.description
: entry.description.replace('#', '');
const effects = entry.display_params ?? getScriptBonusValues(entry.script);
return {
id: entry.id,
name: getString(nameKey),
description: getString(descKey),
crewBonus: getScriptCrewBonus(entry.script),
effects,
};
});
}
export function getEquipment(): Equipment[] {
return equipmentCache ?? [];
}
export function getConsumables(): Consumable[] {
return consumablesCache ?? [];
}
export function getProvisions(): Provision[] {
return provisionsCache ?? [];
}