168 lines
5.6 KiB
TypeScript
168 lines
5.6 KiB
TypeScript
import { copy } from "@std/fs";
|
|
import { createCanvas, loadImage } from "@gfx/canvas-wasm";
|
|
import { ENGINE_TEXTURE_DIR, load_and_check, load_order, LoadedMod } from "./tools/check_mods.ts";
|
|
import { pack_mod } from "./tools/pack_mod.ts";
|
|
import { BMOD_EXTENSION } from "./common/bmod.ts";
|
|
import { ENGINE_TEXTURES_INDEX } from "./server/host.ts";
|
|
|
|
// all overridable so tests can build somewhere else
|
|
const BUILD_FOLDER = Deno.env.get("BUILD_DIR") ?? "build";
|
|
const MODS_FOLDER = Deno.env.get("MODS_DIR") ?? "mods";
|
|
// where the server loads .bmod files from. the build puts every mod in mods/ there, next to any others
|
|
const SERVER_MODS_FOLDER = Deno.env.get("SERVER_MODS_DIR") ?? "server_mods";
|
|
|
|
function clear_folder(folder: string) {
|
|
try {
|
|
Deno.removeSync(folder, { recursive: true });
|
|
} catch (e) {
|
|
if (!(e instanceof Deno.errors.NotFound)) throw e;
|
|
}
|
|
Deno.mkdirSync(folder, { recursive: true });
|
|
}
|
|
|
|
async function build_fonts() {
|
|
// i used https://fonts.varg.dev/ to build the font, its confusing
|
|
const canvas_size = 256;
|
|
|
|
const font = await loadImage("assets/fonts/m6x11.png");
|
|
const canvas = createCanvas(canvas_size, canvas_size);
|
|
const ctx = canvas.getContext("2d");
|
|
|
|
ctx.drawImage(font, 0, 0);
|
|
const image_data = ctx.getImageData(0, 0, canvas_size, canvas_size);
|
|
const data = image_data.data;
|
|
for (let i = 0; i < data.length; i += 4) {
|
|
const r = data[i];
|
|
const g = data[i + 1];
|
|
const b = data[i + 2];
|
|
|
|
if (r === 0 && g === 0 && b === 0) {
|
|
// black pixel, alpha = 0
|
|
data[i + 3] = 0;
|
|
} else {
|
|
// force white white
|
|
data[i] = 255;
|
|
data[i + 1] = 255;
|
|
data[i + 2] = 255;
|
|
data[i + 3] = 255;
|
|
}
|
|
}
|
|
ctx.putImageData(image_data, 0, 0);
|
|
|
|
Deno.mkdirSync(`${BUILD_FOLDER}/assets/fonts`);
|
|
Deno.writeFileSync(`${BUILD_FOLDER}/assets/fonts/m6x11.png`, canvas.toBuffer());
|
|
await copy("assets/fonts/m6x11.fnt", `${BUILD_FOLDER}/assets/fonts/m6x11.fnt`);
|
|
}
|
|
|
|
async function build_sprites() {
|
|
for (const entry of Deno.readDirSync("assets/sprites")) {
|
|
if (entry.name.endsWith(".png")) {
|
|
await copy(`assets/sprites/${entry.name}`, `${BUILD_FOLDER}/assets/sprites/${entry.name}`);
|
|
}
|
|
}
|
|
}
|
|
|
|
// the engine's textures (engine:<file>), with a list of them. clients put them in the texture atlas with the mods'
|
|
function build_engine_textures() {
|
|
const folder = `${BUILD_FOLDER}/assets/textures`;
|
|
Deno.mkdirSync(folder, { recursive: true });
|
|
const names: string[] = [];
|
|
for (const entry of [...Deno.readDirSync(ENGINE_TEXTURE_DIR)].sort((a, b) => a.name.localeCompare(b.name))) {
|
|
if (entry.isFile && entry.name.endsWith(".png")) {
|
|
Deno.copyFileSync(`${ENGINE_TEXTURE_DIR}/${entry.name}`, `${folder}/${entry.name}`);
|
|
names.push(entry.name.replace(/\.png$/, ""));
|
|
}
|
|
}
|
|
Deno.writeTextFileSync(`${BUILD_FOLDER}/${ENGINE_TEXTURES_INDEX}`, JSON.stringify(names));
|
|
}
|
|
|
|
async function build_assets() {
|
|
Deno.mkdirSync(`${BUILD_FOLDER}/assets`, { recursive: true });
|
|
await copy("assets/ASSETS.md", `${BUILD_FOLDER}/assets/ASSETS.md`);
|
|
|
|
await build_fonts();
|
|
Deno.mkdirSync(`${BUILD_FOLDER}/assets/sprites`, { recursive: true });
|
|
await build_sprites();
|
|
build_engine_textures();
|
|
}
|
|
|
|
// every mod in mods/, checked and sorted so dependencies load first. a broken mod fails the whole build,
|
|
// the game never runs with some mods missing
|
|
function load_mods(): LoadedMod[] {
|
|
const mods = load_and_check(MODS_FOLDER);
|
|
const problems = mods.flatMap((mod) => mod.report.errors.map((error) => ` ${mod.id}: ${error}`));
|
|
if (problems.length > 0) {
|
|
throw new Error(`Mods have errors (deno task check-mods for details):\n${problems.join("\n")}`);
|
|
}
|
|
return load_order(mods);
|
|
}
|
|
|
|
// every mod in mods/ into server_mods/<id>.bmod. other .bmod files there are left alone, they're other people's mods
|
|
async function build_mods(mods: LoadedMod[]) {
|
|
Deno.mkdirSync(SERVER_MODS_FOLDER, { recursive: true });
|
|
remove_old_format();
|
|
for (const mod of mods) {
|
|
Deno.writeFileSync(`${SERVER_MODS_FOLDER}/${mod.id}${BMOD_EXTENSION}`, await pack_mod(mod));
|
|
}
|
|
console.log(`Mods: ${mods.map((m) => m.id).join(", ") || "none"}`);
|
|
}
|
|
|
|
// builds before .bmod files put each mod's server script in a folder here with an index.json
|
|
function remove_old_format() {
|
|
for (const entry of Deno.readDirSync(SERVER_MODS_FOLDER)) {
|
|
if (entry.isDirectory || entry.name === "index.json") {
|
|
Deno.removeSync(`${SERVER_MODS_FOLDER}/${entry.name}`, { recursive: true });
|
|
}
|
|
}
|
|
}
|
|
|
|
async function build_client() {
|
|
const _result = await Deno.bundle({
|
|
entrypoints: ["./client/main.ts", "./client/workers/chunk_worker.ts"],
|
|
outputDir: `${BUILD_FOLDER}/client`,
|
|
platform: "browser",
|
|
minify: false,
|
|
keepNames: true,
|
|
});
|
|
await copy("./client/index.html", `${BUILD_FOLDER}/client/index.html`);
|
|
}
|
|
|
|
async function build() {
|
|
try {
|
|
const now = performance.now();
|
|
clear_folder(BUILD_FOLDER);
|
|
// checked first: a mod with errors stops the build before any .bmod is written
|
|
const mods = load_mods();
|
|
await build_assets();
|
|
await build_client();
|
|
await build_mods(mods);
|
|
console.log(`Built in ${(performance.now() - now).toFixed(2)}ms`);
|
|
} catch (e) {
|
|
console.log(e instanceof Error ? e.message : e);
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
let last_build = 0;
|
|
|
|
if (import.meta.main) {
|
|
const ok = await build();
|
|
// deno task build --once, for scripts and ci
|
|
if (Deno.args.includes("--once")) {
|
|
Deno.exit(ok ? 0 : 1);
|
|
}
|
|
const watcher = Deno.watchFs(["assets", "client", "common", MODS_FOLDER], { recursive: true });
|
|
for await (const event of watcher) {
|
|
const now = performance.now();
|
|
if (now - last_build < 500) {
|
|
continue;
|
|
}
|
|
if (["create", "modify", "rename", "remove"].includes(event.kind)) {
|
|
last_build = now;
|
|
console.log("Rebuilding...");
|
|
await build();
|
|
}
|
|
}
|
|
}
|