import fs from 'node:fs'; import path from 'node:path'; import { pipeline } from 'node:stream/promises'; import { Readable } from 'node:stream'; import { AO_BIN_DUMPS_BASE, GAMEDATA_DIR } from '../config.js'; /** Files pulled from ao-data/ao-bin-dumps. Add zone/world files in the travel phase. */ export const GAMEDATA_FILES = [ { remote: 'items.json', local: 'items.json' }, { remote: 'formatted/items.json', local: 'items_localized.json' }, ] as const; export function gamedataFilePath(local: string): string { return path.join(GAMEDATA_DIR, local); } async function downloadFile(remote: string, dest: string): Promise { const url = `${AO_BIN_DUMPS_BASE}/${remote}`; const res = await fetch(url); if (!res.ok || !res.body) { throw new Error(`Download failed for ${url}: HTTP ${res.status}`); } fs.mkdirSync(path.dirname(dest), { recursive: true }); const tmp = `${dest}.download`; await pipeline(Readable.fromWeb(res.body as import('node:stream/web').ReadableStream), fs.createWriteStream(tmp)); fs.renameSync(tmp, dest); } export interface DownloadResult { file: string; status: 'downloaded' | 'cached'; bytes: number; } /** * Ensure all game data files exist locally. With force=true, re-download everything * (used by the "refresh game data" action after a game patch). */ export async function downloadGameData(force = false): Promise { const results: DownloadResult[] = []; for (const f of GAMEDATA_FILES) { const dest = gamedataFilePath(f.local); if (!force && fs.existsSync(dest)) { results.push({ file: f.local, status: 'cached', bytes: fs.statSync(dest).size }); continue; } await downloadFile(f.remote, dest); results.push({ file: f.local, status: 'downloaded', bytes: fs.statSync(dest).size }); } return results; }