Implement main game as a mod
This commit is contained in:
@@ -0,0 +1,87 @@
|
||||
// loads the mods the server lists: registers their data, then runs their client scripts.
|
||||
// see "Delivery to clients" in MODS.md
|
||||
import { AIR } from "$/common/constants.ts";
|
||||
import { BlockRegistry, EverythingRegistry } from "$/common/everything_registry.ts";
|
||||
import type { ClientContext } from "$/common/mod_api/client.ts";
|
||||
import { ModData, ModListing, ModLoadError, register_mod_data } from "$/common/mod_loader.ts";
|
||||
import type { OreJson } from "$/common/mod_data.ts";
|
||||
import { AIR_ID } from "$/common/protocol.ts";
|
||||
import { Position } from "$/common/components/position.ts";
|
||||
import type { ClientWorld } from "./client_world.ts";
|
||||
|
||||
// what the chunk workers need to generate the same world as the server
|
||||
export const worldgen_mods: { scripts: { mod: string; url: string }[]; ores: OreJson[] } = { scripts: [], ores: [] };
|
||||
|
||||
// set once the world exists, mods can't look at it during setup
|
||||
let world: ClientWorld | undefined;
|
||||
|
||||
export function set_mods_world(client_world: ClientWorld) {
|
||||
world = client_world;
|
||||
}
|
||||
|
||||
export async function load_client_mods(listings: ModListing[]) {
|
||||
const site = new URL("/", location.href);
|
||||
|
||||
const datas = await Promise.all(listings.map(async (listing) => {
|
||||
const response = await fetch(new URL(listing.data, site));
|
||||
if (!response.ok) {
|
||||
throw new ModLoadError(listing.id, `couldn't download its data (${response.status})`);
|
||||
}
|
||||
return { id: listing.id, data: await response.json() as ModData };
|
||||
}));
|
||||
const recipes = register_mod_data(datas);
|
||||
|
||||
worldgen_mods.ores = recipes.ores;
|
||||
worldgen_mods.scripts = listings.flatMap((listing) =>
|
||||
listing.worldgen ? [{ mod: listing.id, url: new URL(listing.worldgen, site).href }] : []
|
||||
);
|
||||
|
||||
for (const listing of listings) {
|
||||
if (!listing.client) continue;
|
||||
const module = await import(new URL(listing.client, site).href);
|
||||
if (typeof module.setup !== "function") {
|
||||
throw new ModLoadError(listing.id, "the client script doesn't export a setup function");
|
||||
}
|
||||
await module.setup(client_context(listing));
|
||||
}
|
||||
}
|
||||
|
||||
function client_context(listing: ModListing): ClientContext {
|
||||
const mod = listing.id;
|
||||
const not_yet = (name: string, where: string) =>
|
||||
new Proxy({}, {
|
||||
get: (_, prop) => () => {
|
||||
throw new Error(`[${mod}] ctx.${name}.${String(prop)} isn't implemented yet (${where} in MODS.md)`);
|
||||
},
|
||||
});
|
||||
const need_world = () => {
|
||||
if (!world) throw new Error(`[${mod}] the world isn't there yet during setup`);
|
||||
return world;
|
||||
};
|
||||
|
||||
return {
|
||||
mod: { id: mod, version: listing.version },
|
||||
ui: not_yet("ui", "step 7") as ClientContext["ui"],
|
||||
hud: not_yet("hud", "step 7") as ClientContext["hud"],
|
||||
input: not_yet("input", "step 8") as ClientContext["input"],
|
||||
net: not_yet("net", "step 8") as ClientContext["net"],
|
||||
player: {
|
||||
get name() {
|
||||
return need_world().connection.name;
|
||||
},
|
||||
get position() {
|
||||
const [player] = need_world().get_tag("player")!;
|
||||
const position = player.get(Position)!;
|
||||
return { x: position.x, y: position.y, z: position.z };
|
||||
},
|
||||
},
|
||||
world: {
|
||||
get_block(x, y, z) {
|
||||
const nid = need_world().dimension.get_block(x, y, z);
|
||||
if (nid === AIR) return AIR_ID;
|
||||
return EverythingRegistry.get_by_id<BlockRegistry>("blocks", nid)?.id;
|
||||
},
|
||||
},
|
||||
log: (...args) => console.log(`[${mod}]`, ...args),
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user