import { resolve, toFileUrl } from "@std/path"; import { EverythingRegistry } from "$/common/everything_registry.ts"; import { load_and_check, load_order } from "$/tools/check_mods.ts"; import type { ServerModSource } from "$/server/game/load_mods.ts"; import { GameServer } from "$/server/game/game_server.ts"; import { start_game } from "$/server/game/load_mods.ts"; import { PROTOCOL_VERSION } from "$/common/protocol.ts"; // mods straight from their source folders, skipping the build: scripts are imported as typescript export function mod_sources(mods_dir: string): ServerModSource[] { const mods = load_and_check(mods_dir); const errors = mods.flatMap((mod) => mod.report.errors.map((e) => `${mod.id}: ${e}`)); if (errors.length) throw new Error(errors.join("\n")); return load_order(mods).map((mod) => { const scripts = (mod.manifest?.scripts ?? {}) as Record; const url = (path?: string) => path ? toFileUrl(resolve(mod.dir, path)).href : undefined; return { listing: { id: mod.id, name: String(mod.manifest?.name), version: String(mod.manifest?.version), hash: "source", data: `mods/${mod.id}/source/data.json`, sha256: { data: "" }, }, data: { blocks: mod.blocks.map((b) => b.json), items: mod.items.map((i) => i.json), recipes: mod.recipes.map((r) => r.json), ores: mod.ores.map((o) => o.json), }, server_url: url(scripts.server), worldgen_url: url(scripts.worldgen), }; }); } // a game with an in memory host that remembers what it sent to each connection export async function test_game(mods_dir: string, save?: string, seed = "test-seed") { EverythingRegistry.clear(); const outbox = new Map(); const closed = new Set(); const host = { send: (conn: number, data: string) => { if (!outbox.has(conn)) outbox.set(conn, []); outbox.get(conn)!.push(JSON.parse(data)); }, close(conn: number) { closed.add(conn); }, }; const atlas = { png: "atlas.png", json: "atlas.json", sha256: { png: "", json: "" } }; const game: GameServer = await start_game(host, save, seed, atlas, mod_sources(mods_dir)); // deno-lint-ignore no-explicit-any const take = (conn: number): any[] => { const messages = outbox.get(conn) ?? []; outbox.set(conn, []); return messages; }; const send = (conn: number, message: unknown) => game.on_message(conn, JSON.stringify(message)); // the whole handshake: hello, welcome, ready, join. returns join const join = (conn: number, name: string) => { game.on_connect(conn); send(conn, { type: "hello", name, protocol: PROTOCOL_VERSION }); send(conn, { type: "ready" }); return take(conn).find((m) => m.type === "join"); }; return { game, take, send, join, closed }; } export function copy_dir(from: string, to: string) { if (Deno.statSync(from).isDirectory) { Deno.mkdirSync(to, { recursive: true }); for (const entry of Deno.readDirSync(from)) copy_dir(`${from}/${entry.name}`, `${to}/${entry.name}`); } else { Deno.copyFileSync(from, to); } }