Implement main game as a mod

This commit is contained in:
2026-09-24 23:49:34 -03:00
parent bb42dd662e
commit 6458bc0440
131 changed files with 1810 additions and 786 deletions
+89
View File
@@ -0,0 +1,89 @@
// registers mods' data, the same way on the server and on clients
import { BlockRegistry, EverythingRegistry, ItemRegistry } from "./everything_registry.ts";
import {
block_from_json,
BlockJson,
grid_recipe_from_json,
GridRecipe,
item_from_json,
ItemJson,
OreJson,
RecipeJson,
} from "./mod_data.ts";
import { register_block_item } from "./utils.ts";
// everything a mod's json files hold, merged into one file by the build (build/mods/<id>/<hash>/data.json)
export interface ModData {
blocks: BlockJson[];
items: ItemJson[];
recipes: RecipeJson[];
ores: OreJson[];
}
// a mod as the server lists it to clients, in load order. paths are relative to the site root
export interface ModListing {
id: string;
name: string;
version: string;
hash: string;
data: string;
client?: string;
worldgen?: string;
}
export class RecipeBook {
shaped: GridRecipe[] = [];
furnace = new Map<string, { output: { id: string; count: number }; cook_time: number }>();
fuel = new Map<string, number>();
ores: OreJson[] = [];
}
export class ModLoadError extends Error {
constructor(mod: string, message: string) {
super(`${mod}: ${message}`);
this.name = "ModLoadError";
}
}
// registers every mod's blocks, items and recipes, in the order given (dependencies first)
export function register_mod_data(mods: { id: string; data: ModData }[]): RecipeBook {
const recipes = new RecipeBook();
for (const { id, data } of mods) {
const fail = (message: string): never => {
throw new ModLoadError(id, message);
};
try {
for (const json of data.blocks) {
const { block, has_item } = block_from_json(json);
EverythingRegistry.register<BlockRegistry>("blocks", block.id, block);
if (has_item) {
register_block_item(block);
}
}
for (const json of data.items) {
EverythingRegistry.register<ItemRegistry>("items", json.id, item_from_json(json));
}
} catch (e) {
fail((e as Error).message);
}
for (const recipe of data.recipes) {
switch (recipe.type) {
case "shaped":
recipes.shaped.push(grid_recipe_from_json(recipe));
break;
case "furnace":
if (recipes.furnace.has(recipe.input)) fail(`two furnace recipes for ${recipe.input}`);
recipes.furnace.set(recipe.input, { output: recipe.output, cook_time: recipe.cook_time });
break;
case "fuel":
recipes.fuel.set(recipe.item, recipe.burn_time);
break;
}
}
recipes.ores.push(...data.ores);
}
return recipes;
}