117 lines
4.8 KiB
TypeScript
117 lines
4.8 KiB
TypeScript
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 }]);
|
|
});
|
|
|
|
Deno.test("q throws one of an item the way the player looks, ctrl throws the whole stack", async () => {
|
|
const { game, take, send, join } = await test_game("mods");
|
|
join(1, "alice");
|
|
build_floor(game);
|
|
// looking straight along -z (yaw 0)
|
|
send(1, { type: "move", x: 0.5, y: 100, z: 0.5, yaw: 0, pitch: 0 });
|
|
send(1, { type: "chat", text: "/give bworld:stone 5" });
|
|
take(1);
|
|
|
|
send(1, { type: "drop_item", container: "inventory", index: 0, all: false });
|
|
const [thrown] = game.item_entities();
|
|
assertEquals(thrown.item.amount, 1);
|
|
assert(thrown.vz < -5 && Math.abs(thrown.vx) < 1, "thrown forwards");
|
|
assertEquals(inventory_count(take(1), "bworld:stone"), 4);
|
|
|
|
for (let i = 0; i < 20; i++) game.tick();
|
|
assert(thrown.z < -1, "it flew away from the player");
|
|
assertEquals(game.item_entities().length, 1, "and isn't picked straight back up");
|
|
|
|
send(1, { type: "drop_item", container: "inventory", index: 0, all: true });
|
|
assertEquals(game.item_entities().map((entity) => entity.item.amount).sort(), [1, 4]);
|
|
assertEquals(inventory_count(take(1), "bworld:stone"), 0);
|
|
|
|
// empty slots and the crafting result don't throw anything
|
|
send(1, { type: "drop_item", container: "inventory", index: 0, all: true });
|
|
send(1, { type: "drop_item", container: "crafting", index: 9, all: true });
|
|
assertEquals(game.item_entities().length, 2);
|
|
});
|