Implement tiled multiplayer city simulation and scaling stages

This commit is contained in:
mrleo1nid committed 2026-10-30 22:06:56 +03:00
1 parent 3d4b29fedf
commit 3b1fcd996a
53 files changed
+7354 -13

No files matched your search

+43
View File
@@ -0,0 +1,43 @@
import { access, mkdir, readFile, rename, writeFile } from 'node:fs/promises';
import { gzipSync, gunzipSync } from 'node:zlib';
import { resolve } from 'node:path';
import { pathToFileURL } from 'node:url';
import { createHash } from 'node:crypto';
import { tileWorld } from '../../src/city/format';
import type { World } from '../../src/types';
export async function exportCity(world: World, output: string, tileSize = 256) {
let exists = false;
try { await access(resolve(output, 'manifest.json')); exists = true; } catch (error) { if ((error as NodeJS.ErrnoException).code !== 'ENOENT') throw error; }
if (exists) throw new Error('City export is immutable: use a new directory to preserve existing rooms and HTTP caches');
const { manifest, tiles } = tileWorld(world, tileSize);
await mkdir(resolve(output, 'tiles'), { recursive: true });
for (const ref of manifest.tiles) {
const file = resolve(output, ref.url);
const json = JSON.stringify(tiles.get(ref.id));
ref.sha256 = createHash('sha256').update(json).digest('hex');
await writeFile(file + '.tmp', gzipSync(json, { level: 6 }));
await rename(file + '.tmp', file);
}
const navigation = JSON.stringify({formatVersion:1,cityId:world.id,id:'navigation',
roads:world.roads.map(({id,kind,pts})=>({id,kind,pts})),
buildings:world.buildings.map(({id,kind,floors})=>({id,kind,floors:floors.filter(f=>f.level===0).map(f=>({level:0,doors:f.doors.filter(d=>d.rooms.includes('outside'))}))})),
});
manifest.navigation={url:'navigation.json.gz',sha256:createHash('sha256').update(navigation).digest('hex')};
const navFile=resolve(output,manifest.navigation.url);
await writeFile(navFile+'.tmp',gzipSync(navigation,{level:6}));
await rename(navFile+'.tmp',navFile);
const file = resolve(output, 'manifest.json');
await writeFile(file + '.tmp', JSON.stringify(manifest, null, 2));
await rename(file + '.tmp', file);
return manifest;
}
if (process.argv[1] && import.meta.url === pathToFileURL(resolve(process.argv[1])).href) {
const [input, output, size] = process.argv.slice(2);
if (!input || !output) throw new Error('Usage: npm run export-city -- world.json[.gz] output-directory [tile-size-metres]');
const bytes = await readFile(input);
const world = JSON.parse((bytes[0] === 0x1f && bytes[1] === 0x8b ? gunzipSync(bytes) : bytes).toString()) as World;
const manifest = await exportCity(world, output, size === undefined ? 256 : Number(size));
console.log(`${manifest.city.name}: ${manifest.tiles.length} tiles → ${resolve(output, 'manifest.json')}`);
}