| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596 |
- import { join } from 'node:path';
- import { app, BrowserWindow, ipcMain, utilityProcess, type UtilityProcess, type WebContents } from 'electron';
- import { readFileSync } from 'node:fs';
- import type { HostResult, MapFiles } from '../preload/api';
- import { DISCOVERY_INTERVAL_MS } from '../shared/constants';
- import { readMapFiles, readMapImage, resolveAssetsDir } from '../server/assets';
- import { DiscoveryListener } from './discovery';
- const assetsDir = app.isPackaged ? join(process.resourcesPath, 'assets') : join(app.getAppPath(), 'assets');
- process.env.GOUTER_ASSETS = assetsDir;
- let server: UtilityProcess | null = null;
- let serverReady: Promise<HostResult> | null = null;
- /** Forks the authoritative server in a utilityProcess (once) and waits for it to listen. */
- function hostServer(name: string): Promise<HostResult> {
- if (serverReady) return serverReady;
- serverReady = new Promise<HostResult>((resolveReady, reject) => {
- const proc = utilityProcess.fork(join(__dirname, 'server.js'), [`--name=${String(name).slice(0, 40)}`], {
- serviceName: 'goutermageddon-server',
- env: { ...process.env, GOUTER_ASSETS: assetsDir },
- stdio: 'inherit',
- });
- server = proc;
- proc.on('message', (msg: { type: string; port?: number; message?: string }) => {
- if (msg.type === 'ready') resolveReady({ port: msg.port! });
- else if (msg.type === 'error') reject(new Error(msg.message));
- });
- proc.on('exit', (code) => {
- server = null;
- serverReady = null;
- reject(new Error(`server exited with code ${code}`));
- });
- });
- return serverReady;
- }
- function createWindow(): void {
- const win = new BrowserWindow({
- width: 1280,
- height: 720,
- title: 'Goûtermageddon',
- backgroundColor: '#fff1d6',
- autoHideMenuBar: true,
- webPreferences: {
- preload: join(__dirname, '../preload/index.js'),
- contextIsolation: true,
- nodeIntegration: false,
- sandbox: true,
- },
- });
- if (process.env.ELECTRON_RENDERER_URL) void win.loadURL(process.env.ELECTRON_RENDERER_URL);
- else void win.loadFile(join(__dirname, '../renderer/index.html'));
- }
- // LAN discovery: one listener for the app, pushed to the renderer that asked for it.
- const discovery = new DiscoveryListener();
- let discoveryTimer: NodeJS.Timeout | null = null;
- function startDiscovery(target: WebContents): void {
- stopDiscovery();
- discovery.start((err) => console.error('[discovery] cannot listen:', err.message));
- const push = (): void => {
- if (target.isDestroyed()) return stopDiscovery();
- target.send('lan:games', discovery.list());
- };
- push();
- discoveryTimer = setInterval(push, DISCOVERY_INTERVAL_MS);
- }
- function stopDiscovery(): void {
- if (discoveryTimer) clearInterval(discoveryTimer);
- discoveryTimer = null;
- discovery.stop();
- }
- ipcMain.handle('lan:host', (_event, name: string) => hostServer(name));
- ipcMain.handle('lan:discover-start', (event) => startDiscovery(event.sender));
- ipcMain.handle('lan:discover-stop', () => stopDiscovery());
- ipcMain.handle('assets:materials', () => JSON.parse(readFileSync(join(resolveAssetsDir(), 'materials', 'materials.json'), 'utf8')));
- ipcMain.handle('assets:map', (_event, mapId: string): MapFiles => {
- const dir = resolveAssetsDir();
- const { definition, terrain } = readMapFiles(dir, mapId);
- return { definition, terrain, background: readMapImage(dir, mapId, 'background.png') };
- });
- void app.whenReady().then(() => {
- createWindow();
- app.on('activate', () => {
- if (BrowserWindow.getAllWindows().length === 0) createWindow();
- });
- });
- app.on('window-all-closed', () => {
- stopDiscovery();
- server?.kill();
- app.quit();
- });
|