Actually implement mod code into server

This commit is contained in:
2026-09-25 23:13:46 -03:00
parent 099811670f
commit 9237152aa1
22 changed files with 1359 additions and 457 deletions
+73
View File
@@ -24,6 +24,28 @@ export function setup(ctx: ServerContext) {
on_tick() { throw new Error("broken on purpose"); },
on_click(block, _params, player) { log("click", [block.id, player.name]); },
});
// a box that only takes coal, counts how often it's opened, and gives up its items when broken
ctx.components.register_block("testmod:coal_box", {
on_interact(block, _params, player) {
block.data ??= { container: ctx.containers.create(2).id, opened: 0 };
block.data.opened += 1;
const screen = ctx.ui.open_container(player, {
container: ctx.containers.get(block.data.container)!,
layout: [{ slot: 0, x: 0, y: 0, filter: (item) => item.id === "bworld:coal" }, { slot: 1, x: 1, y: 0 }],
});
screen.set_property("opened", block.data.opened);
screen.on_close(() => log("closed", screen.open));
return true;
},
on_break(block) {
const container = ctx.containers.get(block.data.container)!;
for (let slot = 0; slot < container.size; slot++) {
const item = container.get(slot);
if (item) ctx.world.drop_item(block.x, block.y, block.z, item);
}
ctx.containers.delete(container.id);
},
});
ctx.components.register_item("testmod:wand", {
on_use(item, params: { power: number }, player) { log("use", [item.id, params.power, player.name]); },
});
@@ -84,6 +106,10 @@ function test_mods() {
`${mod}/blocks/broken.json`,
block("testmod:broken", { components: { "testmod:broken": {} } }),
);
Deno.writeTextFileSync(
`${mod}/blocks/coal_box.json`,
block("testmod:coal_box", { interactive: true, components: { "testmod:coal_box": {} } }),
);
Deno.writeTextFileSync(
`${mod}/blocks/lamp.json`,
block("testmod:lamp", {
@@ -227,3 +253,50 @@ Deno.test("/tps reports the loop, and mods can't take engine commands", async ()
assert(error.includes("/tps belongs to the engine"), error);
Deno.removeSync(clash, { recursive: true });
});
Deno.test("container screens: filters, properties, closing, saving, and dropping what's inside", async () => {
const { game, send, take, log, dir } = await setup();
game.set_block(1, 100, 0, "testmod:coal_box");
send(1, { type: "chat", text: "/give bworld:coal 3" });
send(1, { type: "chat", text: "/give bworld:stone 1" });
take(1);
send(1, { type: "use_block", x: 1, y: 100, z: 0, face: "top" });
let messages = take(1);
const opened = messages.find((m) => m.type === "open_screen");
assertEquals(opened.layout, { rows: 1, slots: [{ index: 0, x: 0, y: 0 }, { index: 1, x: 1, y: 0 }], bars: [] });
assertEquals(messages.find((m) => m.type === "screen_properties").properties, { opened: 1 });
// stone doesn't go into the coal slot, but does into the other one. coal goes in
const screen = (messages: { type: string; container?: string; items?: unknown[] }[]) =>
messages.filter((m) => m.type === "container" && m.container === "screen").at(-1)?.items;
send(1, { type: "click", container: "inventory", index: 1, button: 0 });
send(1, { type: "click", container: "screen", index: 0, button: 0 });
send(1, { type: "click", container: "screen", index: 1, button: 0 });
send(1, { type: "click", container: "inventory", index: 0, button: 0 });
send(1, { type: "click", container: "screen", index: 0, button: 0 });
messages = take(1);
assertEquals(screen(messages), [{ id: "bworld:coal", count: 3 }, { id: "bworld:stone", count: 1 }]);
send(1, { type: "close_screen" });
assertEquals(log().closed, [false]);
// the items and the block's data are saved
const loaded = await test_game(dir, game.save());
loaded.join(1, "alice");
loaded.send(1, { type: "move", x: 0.5, y: 100, z: 0.5, yaw: 0, pitch: 0 });
loaded.send(1, { type: "use_block", x: 1, y: 100, z: 0, face: "top" });
messages = loaded.take(1);
assertEquals(screen(messages), [{ id: "bworld:coal", count: 3 }, { id: "bworld:stone", count: 1 }]);
assertEquals(messages.find((m) => m.type === "screen_properties").properties, { opened: 2 });
// breaking it closes the screen, runs on_close, and drops what was inside
loaded.send(1, { type: "break_block", x: 1, y: 100, z: 0 });
assert(loaded.take(1).some((m) => m.type === "close_screen"));
const logged = loaded.game.mods.storage.testmod.log as Record<string, unknown[]>;
assertEquals(logged.closed, [false, false]);
const dropped = loaded.game.item_entities().map((e) => e.item.to_data().id).sort();
assertEquals(dropped, ["bworld:coal", "bworld:stone"]);
assertEquals(loaded.game.containers.size, 0);
Deno.removeSync(dir, { recursive: true });
});