Item entities

This commit is contained in:
2026-09-25 16:26:45 -03:00
parent 5fb23cf404
commit a254b96f65
18 changed files with 797 additions and 94 deletions
+87
View File
@@ -0,0 +1,87 @@
import { assert, assertEquals } from "@std/assert";
import { ItemStack } from "$/common/inventory.ts";
import { test_game } from "./helpers.ts";
// a stone floor at y 99 so drops have something to land on, high above the generated terrain
function build_floor(game: Awaited<ReturnType<typeof test_game>>["game"]) {
for (let x = -3; x <= 9; x++) {
for (let z = -4; z <= 4; z++) {
game.set_block(x, 99, z, "bworld:stone");
}
}
}
const inventory_count = (
messages: { type: string; container?: string; items?: ({ id: string; count: number } | null)[] }[],
id: string,
) => (messages.filter((m) => m.type === "container" && m.container === "inventory").at(-1)?.items ?? [])
.reduce((sum, item) => sum + (item?.id === id ? item.count : 0), 0);
Deno.test("breaking a block drops its item, which lands and gets picked up by walking to it", async () => {
const { game, take, send, join } = await test_game("mods");
join(1, "alice");
build_floor(game);
game.set_block(5, 100, 0, "bworld:dirt");
send(1, { type: "move", x: 0.5, y: 100, z: 0.5, yaw: 0, pitch: 0 });
take(1);
send(1, { type: "break_block", x: 5, y: 100, z: 0 });
const messages = take(1);
const added = messages.find((m) => m.type === "add_entity");
assertEquals(added?.entity.kind, "item");
assertEquals(added?.entity.item, { id: "bworld:dirt", count: 1 });
assertEquals(inventory_count(messages, "bworld:dirt"), 0, "it drops instead of going into the inventory");
for (let i = 0; i < 40; i++) game.tick();
const [item] = game.item_entities();
assert(item.on_ground, "it fell onto the floor");
assertEquals(item.y, 100);
assert(Math.abs(item.x - 5.5) < 1.5 && Math.abs(item.z - 0.5) < 1.5, "it stays near the block");
assert(take(1).some((m) => m.type === "move_entity" && m.id === item.id));
send(1, { type: "move", x: item.x, y: 100, z: item.z, yaw: 0, pitch: 0 });
game.tick();
const picked = take(1);
assert(picked.some((m) => m.type === "take_entity" && m.id === item.id));
assertEquals(inventory_count(picked, "bworld:dirt"), 1);
assertEquals(game.item_entities(), []);
});
Deno.test("items that don't fit in the inventory drop at the player's feet and stay there", async () => {
const { game, take, send, join } = await test_game("mods");
join(1, "alice");
build_floor(game);
send(1, { type: "move", x: 0.5, y: 100, z: 0.5, yaw: 0, pitch: 0 });
// a full inventory, then 10 more
send(1, { type: "chat", text: `/give bworld:stone ${64 * 36}` });
assertEquals(game.item_entities(), []);
send(1, { type: "chat", text: "/give bworld:stone 10" });
const [item] = game.item_entities();
assertEquals(item.item.amount, 10);
for (let i = 0; i < 60; i++) game.tick();
assertEquals(game.item_entities().length, 1, "a full inventory can't pick it up");
assert(take(1).some((m) => m.type === "add_entity"));
});
Deno.test("stacks next to each other merge, and items on the ground are saved with the world", async () => {
const { game, join } = await test_game("mods");
join(1, "alice");
build_floor(game);
for (let i = 0; i < 3; i++) {
game.spawn_item(6.5, 100, 0.5, new ItemStack("bworld:dirt", 2));
}
game.tick();
game.tick();
assertEquals(game.item_entities().map((entity) => entity.item.amount), [6]);
const saved = game.save();
const reloaded = await test_game("mods", saved);
assertEquals(reloaded.game.item_entities().map((entity) => [entity.item.type_id, entity.item.amount]), [[
"bworld:dirt",
6,
]]);
const joined = reloaded.join(2, "bob");
assertEquals(joined.entities.map((entity: { item: unknown }) => entity.item), [{ id: "bworld:dirt", count: 6 }]);
});