diff --git a/assets/sprites/textures/chest.png b/assets/sprites/textures/chest.png new file mode 100644 index 0000000..7c8da8e Binary files /dev/null and b/assets/sprites/textures/chest.png differ diff --git a/client/gui/gui_chest.ts b/client/gui/gui_chest.ts new file mode 100644 index 0000000..fde25a9 --- /dev/null +++ b/client/gui/gui_chest.ts @@ -0,0 +1,60 @@ +import { Inventory, PlayerInventory } from "../inventory.ts"; +import { add_player_hotbar, add_player_inventory, GuiScreen, Slot } from "./gui_screen.ts"; +import { canvas, draw_rect, Texture } from "$/client/renderer.ts"; +import { AssetManager } from "../assets.ts"; +import { SLOT_SIZE } from "../../common/constants.ts"; +import { draw_nine_slice } from "../systems/rendering/render_utils.ts"; + +const PADDING = 10; + +export class GuiChest extends GuiScreen { + override inventory_width = PADDING * 2 + SLOT_SIZE * 9; + override inventory_height = PADDING * 3 + SLOT_SIZE * 7; + + constructor(inventory: Inventory, player_inventory: PlayerInventory) { + super(inventory, player_inventory, undefined); + + add_player_hotbar(this, player_inventory, PADDING, PADDING); + add_player_inventory(this, player_inventory, PADDING, PADDING * 2 + SLOT_SIZE); + + const chest_x = PADDING; + const chest_y = PADDING * 3 + SLOT_SIZE * 4; + + for (let i = 0; i < 3; ++i) { + for (let l = 0; l < 9; ++l) { + this.slots.push( + new Slot( + this.inventory, + l + i * 9 + 9, + chest_x + l * SLOT_SIZE, + chest_y + i * SLOT_SIZE, + ), + ); + } + } + } + + override on_render(): void { + draw_rect(0, 0, canvas.width, canvas.height, [0, 0, 0, 0.8]); + + const ui = AssetManager.instance.get("bworld:ui"); + + draw_nine_slice( + ui, + 160, + 0, + 16, + 16, + 4, + 4, + 4, + 4, + this.x, + this.y, + this.inventory_width, + this.inventory_height, + ); + + super.on_render(); + } +} diff --git a/client/gui/gui_player_inventory.ts b/client/gui/gui_player_inventory.ts index b465e99..3e55a83 100644 --- a/client/gui/gui_player_inventory.ts +++ b/client/gui/gui_player_inventory.ts @@ -1,10 +1,36 @@ -import { Container, Inventory, PlayerInventory } from "../inventory.ts"; +import { Container, Inventory, ItemStack, PlayerInventory } from "../inventory.ts"; import { add_player_hotbar, add_player_inventory, GuiScreen, Slot } from "./gui_screen.ts"; import { canvas, draw_rect, Texture } from "$/client/renderer.ts"; import { AssetManager } from "../assets.ts"; import { SLOT_SIZE } from "../../common/constants.ts"; import { draw_nine_slice } from "../systems/rendering/render_utils.ts"; +export interface CraftingRecipe { + width: number; + height: number; + pattern: (string | undefined)[]; + result: ItemStack; +} + +const recipes: CraftingRecipe[] = [ + { + width: 3, + height: 3, + pattern: [ + "bworld:log", + "bworld:log", + "bworld:log", + "bworld:log", + undefined, + "bworld:log", + "bworld:log", + "bworld:log", + "bworld:log", + ], + result: new ItemStack("bworld:chest", 1), + }, +]; + const PADDING = 10; export class GuiPlayerInventory extends GuiScreen { @@ -31,6 +57,11 @@ export class GuiPlayerInventory extends GuiScreen { this.slots.push(new Slot(this.inventory, 9, crafting_x + 5 * SLOT_SIZE, crafting_y + 1 * SLOT_SIZE)); } + override on_tick(delta: number): void { + super.on_tick(delta); + this.update_crafting_result(); + } + override on_render(): void { draw_rect(0, 0, canvas.width, canvas.height, [0, 0, 0, 0.8]); @@ -56,11 +87,118 @@ export class GuiPlayerInventory extends GuiScreen { } override on_close(): void { - for (let i = 0; i < this.inventory.container.size; i += 1) { + for (let i = 0; i < 8; i += 1) { const item = this.inventory.container.get_item(i); if (item) { this.player_inventory.container.add_item(item); } } } + + get_crafting_grid(): (string | undefined)[] { + const grid: (string | undefined)[] = []; + + for (let i = 0; i < 9; i++) { + const item = this.inventory.container.get_item(i); + grid.push(item?.type_id); + } + + return grid; + } + + matches_recipe( + grid: (string | undefined)[], + recipe: CraftingRecipe, + ): boolean { + for (let y = 0; y <= 3 - recipe.height; y++) { + for (let x = 0; x <= 3 - recipe.width; x++) { + let match = true; + + for (let ry = 0; ry < recipe.height; ry++) { + for (let rx = 0; rx < recipe.width; rx++) { + const grid_index = (y + ry) * 3 + (x + rx); + const recipe_index = ry * recipe.width + rx; + + if (grid[grid_index] !== recipe.pattern[recipe_index]) { + match = false; + break; + } + } + } + + if (match) return true; + } + } + + return false; + } + + update_crafting_result() { + const grid = this.get_crafting_grid(); + + for (const recipe of recipes) { + if (this.matches_recipe(grid, recipe)) { + this.inventory.container.set_item( + 9, + recipe.result.clone(), + ); + return; + } + } + + this.inventory.container.set_item(9, undefined); + } + + consume_recipe_items() { + for (let i = 0; i < 9; i++) { + const item = this.inventory.container.get_item(i); + if (!item) { + continue; + } + + item.amount -= 1; + + if (item.amount <= 0) { + this.inventory.container.set_item(i, undefined); + } + } + } + + override handle_left_click(slot: Slot): void { + if (slot.inventory === this.inventory && slot.index === 9) { + if (this.inventory.container.get_item(9)) { + this.craft(); + } + } else { + super.handle_left_click(slot); + } + } + + override handle_right_click(slot: Slot): void { + if (slot.inventory === this.inventory && slot.index === 9) { + if (this.inventory.container.get_item(9)) { + this.craft(); + } + } else { + super.handle_right_click(slot); + } + } + + craft() { + const holding_item = this.player_inventory.holding_item; + const item = this.inventory.container.get_item(9)!; + + if (holding_item) { + if (holding_item.type_id === item.type_id) { + const items_left = holding_item.max_amount - holding_item.amount; + if (items_left >= item.amount) { + this.consume_recipe_items(); + holding_item.amount += item.amount; + } + } + } else { + this.player_inventory.holding_item = item; + this.consume_recipe_items(); + } + } } diff --git a/client/inventory.ts b/client/inventory.ts index 43c9f6a..4f1bbe1 100644 --- a/client/inventory.ts +++ b/client/inventory.ts @@ -88,14 +88,14 @@ export class Container { } get_item(slot: number): ItemStack | undefined { - return this.#slots[slot].get_item(); + return this.#slots[slot]?.get_item(); } get_slot(slot: number): ContainerSlot { return this.#slots[slot]; } - set_item(slot: number, item: ItemStack) { + set_item(slot: number, item: ItemStack | undefined) { this.#slots[slot].set_item(item); } } diff --git a/client/main.ts b/client/main.ts index c5e8099..6d5d643 100644 --- a/client/main.ts +++ b/client/main.ts @@ -1,12 +1,9 @@ -import { EverythingRegistry } from "../common/everything_registry.ts"; import { AssetManager } from "./assets.ts"; import { ClientWorld } from "./client_world.ts"; import { InputManager } from "./input_manager.ts"; import { begin_drawing, end_drawing, init_font, init_window } from "./renderer.ts"; -EverythingRegistry.add_registry("tiles"); await import("./tiles/mod.ts"); -EverythingRegistry.add_registry("items"); await import("./items/mod.ts"); export class ClientLoop { diff --git a/client/tiles/chest.ts b/client/tiles/chest.ts new file mode 100644 index 0000000..8234608 --- /dev/null +++ b/client/tiles/chest.ts @@ -0,0 +1,24 @@ +import { EverythingRegistry, TileRegistry } from "$/common/everything_registry.ts"; +import { Container, Inventory } from "../inventory.ts"; +import { PlayerComponent } from "../player.ts"; +import { GuiChest } from "$/client/gui/gui_chest.ts"; + +interface TileChestData { + inventory: Inventory; +} + +EverythingRegistry.register>("tiles", "bworld:chest", { + texture_id: "bworld:chest", + has_collision: false, + + on_interact(world, tile) { + const [player] = world.get_tag("player")!; + const player_component = player.get(PlayerComponent)!; + if (tile.data && tile.data.inventory) { + player_component.screens.push(new GuiChest(tile.data.inventory, player_component.player_inventory)); + } + }, + on_create(_, tile) { + tile.data = { inventory: new Inventory(new Container(9 * 3)) }; + }, +}); diff --git a/client/tiles/crops.ts b/client/tiles/crops.ts index 82c2d0d..b15edf1 100644 --- a/client/tiles/crops.ts +++ b/client/tiles/crops.ts @@ -10,7 +10,6 @@ interface CropsRegistry { regrowable: boolean; } -EverythingRegistry.add_registry("crops"); EverythingRegistry.register("crops", "bworld:carrot", { total_stages: 5, time_to_grow: [60, 60 * 3, 60 * 3, 60 * 3], diff --git a/client/tiles/grass.ts b/client/tiles/grass.ts index 09e973a..4d5c1b8 100644 --- a/client/tiles/grass.ts +++ b/client/tiles/grass.ts @@ -8,9 +8,11 @@ EverythingRegistry.register("tiles", "bworld:grass", { on_interact(world, tile) { const [player] = world.get_tag("player")!; const player_inventory = player.get(PlayerComponent)!.player_inventory; - const maybe_item = player_inventory.container.get_item(player_inventory.hotbar_selected); - if (maybe_item && maybe_item.type_id === "bworld:hoe") { + const item = player_inventory.container.get_item(player_inventory.hotbar_selected); + if (item?.type_id === "bworld:hoe") { tile.id = "bworld:hoed_dirt"; + } else if (item?.type_id === "bworld:chest") { + world.dimension.add_tile(world, { x: tile.x, y: tile.y, id: "bworld:chest" }); } }, }); diff --git a/client/tiles/mod.ts b/client/tiles/mod.ts index ea2e9e9..5820251 100644 --- a/client/tiles/mod.ts +++ b/client/tiles/mod.ts @@ -3,3 +3,4 @@ import "./dirt.ts"; import "./tree.ts"; import "./crops.ts"; import "./water.ts"; +import "./chest.ts"; diff --git a/common/ecs/component.ts b/common/ecs/component.ts index d9d5230..c22cde8 100644 --- a/common/ecs/component.ts +++ b/common/ecs/component.ts @@ -1,7 +1,3 @@ -import { EverythingRegistry } from "../everything_registry.ts"; - -EverythingRegistry.add_registry("components"); - export abstract class Component { __component = true; } diff --git a/common/everything_registry.ts b/common/everything_registry.ts index 7769ed2..8451bdb 100644 --- a/common/everything_registry.ts +++ b/common/everything_registry.ts @@ -3,18 +3,14 @@ import { Tile } from "../client/components/dimension.ts"; import { ItemStack } from "../client/inventory.ts"; export class EverythingRegistry { - static #registries = new Map>(); - - static add_registry(name: string) { - this.#registries.set(name, new Map()); - } + static #registries = new Map(); static register(registry_name: string, key: string, value: T) { - this.#registries.get(registry_name)?.set(key, value); + this.#registries.set(`${registry_name}__${key}`, value); } static get(registry_name: string, key: string): T { - return this.#registries.get(registry_name)?.get(key) as T; + return this.#registries.get(`${registry_name}__${key}`) as T; } }