// Server entry point. Runs headless (`npm run server`) or inside an Electron utilityProcess. import { DEFAULT_MAP_ID, NET_PORT } from '../shared/constants'; import { loadCharacterStats, loadMap, loadMaterials, resolveAssetsDir } from './assets'; import { Game } from './game'; import { GameServer } from './net'; interface ParentPort { postMessage(message: unknown): void; } /** Set by Electron when this file runs in a utilityProcess. */ const parentPort = (process as { parentPort?: ParentPort }).parentPort; function arg(name: string): string | undefined { const prefix = `--${name}=`; return process.argv.find((a) => a.startsWith(prefix))?.slice(prefix.length); } function main(): void { const assetsDir = resolveAssetsDir(); const mapId = arg('map') ?? DEFAULT_MAP_ID; const port = Number(arg('port') ?? NET_PORT); // The only place the seed is drawn; everything downstream uses the seeded RNG. const seed = Number(arg('seed') ?? Date.now() % 0x7fffffff); const materials = loadMaterials(assetsDir); const { definition, terrain } = loadMap(assetsDir, mapId, materials); const game = new Game({ map: definition, terrain, materials, seed, loadCharacter: (id) => loadCharacterStats(assetsDir, id), }); const server = new GameServer(game, port, '0.0.0.0', () => { console.log(`[server] map "${mapId}" (seed ${seed}) listening on :${port}`); parentPort?.postMessage({ type: 'ready', port }); }); server.start(); const shutdown = (): void => { server.close(); process.exit(0); }; process.on('SIGINT', shutdown); process.on('SIGTERM', shutdown); } try { main(); } catch (err) { console.error('[server] failed to start:', err); parentPort?.postMessage({ type: 'error', message: err instanceof Error ? err.message : String(err) }); process.exit(1); }