176 lines
4.7 KiB
TypeScript
176 lines
4.7 KiB
TypeScript
import { SLOT_SIZE } from "$/common/constants.ts";
|
|
import { click_slot, Container } from "$/common/inventory.ts";
|
|
import { ClientMessage, ContainerKey, CRAFTING_RESULT_SLOT } from "$/common/protocol.ts";
|
|
import { point_inside_rec } from "../../common/utils.ts";
|
|
import { AssetManager } from "../assets.ts";
|
|
import { InputManager } from "../input_manager.ts";
|
|
import { ClientInventories } from "../inventory.ts";
|
|
import { canvas, Texture } from "../renderer/mod.ts";
|
|
import { draw_item, draw_nine_slice } from "../rendering/render_utils.ts";
|
|
|
|
export class Slot {
|
|
container: ContainerKey;
|
|
index: number;
|
|
// relative to screen top left
|
|
x: number;
|
|
y: number;
|
|
// can only be taken from, like the crafting or furnace result
|
|
output: boolean;
|
|
|
|
constructor(container: ContainerKey, index: number, x: number, y: number, output = false) {
|
|
this.container = container;
|
|
this.index = index;
|
|
this.x = x;
|
|
this.y = y;
|
|
this.output = output;
|
|
}
|
|
}
|
|
|
|
export abstract class GuiScreen {
|
|
x: number = 0;
|
|
y: number = 0;
|
|
|
|
abstract on_tick(_delta: number): void;
|
|
abstract on_render(): void;
|
|
abstract on_close(): void;
|
|
}
|
|
|
|
// a screen of slots. the server owns the contents: clicks are sent to it and guessed locally
|
|
// so they feel instant, then whatever the server syncs back wins
|
|
export class GuiInventoryScreen extends GuiScreen {
|
|
inventories: ClientInventories;
|
|
send: (message: ClientMessage) => void;
|
|
|
|
slots: Slot[] = [];
|
|
hovering: Slot | undefined;
|
|
inventory_width: number = 500;
|
|
inventory_height: number = 500;
|
|
|
|
constructor(inventories: ClientInventories, send: (message: ClientMessage) => void) {
|
|
super();
|
|
this.inventories = inventories;
|
|
this.send = send;
|
|
}
|
|
|
|
get_container(key: ContainerKey): Container | undefined {
|
|
switch (key) {
|
|
case "inventory":
|
|
return this.inventories.inventory;
|
|
case "crafting":
|
|
return this.inventories.crafting;
|
|
case "screen":
|
|
return this.inventories.screen;
|
|
}
|
|
}
|
|
|
|
on_tick(_delta: number): void {
|
|
this.x = canvas.width / 2 - (this.inventory_width / 2);
|
|
this.y = canvas.height / 2 - (this.inventory_height / 2);
|
|
|
|
this.handle_interaction();
|
|
}
|
|
|
|
on_render(): void {
|
|
const ui = AssetManager.instance.get<Texture>("bworld:ui");
|
|
|
|
for (const slot of this.slots) {
|
|
const hovered = slot === this.hovering;
|
|
draw_nine_slice(
|
|
ui,
|
|
hovered ? 19 * 16 : 160 + 32,
|
|
hovered ? 16 : 0,
|
|
16,
|
|
16,
|
|
4,
|
|
4,
|
|
4,
|
|
4,
|
|
this.x + slot.x,
|
|
this.y + slot.y,
|
|
SLOT_SIZE,
|
|
SLOT_SIZE,
|
|
);
|
|
}
|
|
|
|
for (const slot of this.slots) {
|
|
const item = this.get_container(slot.container)?.get_item(slot.index);
|
|
if (item) {
|
|
draw_item(item, this.x + slot.x, this.y + slot.y);
|
|
}
|
|
}
|
|
|
|
if (this.inventories.cursor.item) {
|
|
const mouse = InputManager.get_mouse_position();
|
|
draw_item(this.inventories.cursor.item, mouse.x, mouse.y);
|
|
}
|
|
}
|
|
|
|
on_close(): void {
|
|
this.send({ type: "close_screen" });
|
|
|
|
// the server puts these back in the inventory, show it right away
|
|
const { inventory, crafting, cursor } = this.inventories;
|
|
for (let i = 0; i < CRAFTING_RESULT_SLOT; i++) {
|
|
const item = crafting.get_item(i);
|
|
if (item) {
|
|
inventory.add_item(item);
|
|
crafting.set_item(i, undefined);
|
|
}
|
|
}
|
|
crafting.set_item(CRAFTING_RESULT_SLOT, undefined);
|
|
if (cursor.item) {
|
|
inventory.add_item(cursor.item);
|
|
cursor.item = undefined;
|
|
}
|
|
}
|
|
|
|
handle_interaction() {
|
|
this.hovering = undefined;
|
|
const mouse = InputManager.get_mouse_position();
|
|
|
|
for (const slot of this.slots) {
|
|
const hovering = point_inside_rec(mouse.x, mouse.y, this.x + slot.x, this.y + slot.y, SLOT_SIZE, SLOT_SIZE);
|
|
if (!hovering) {
|
|
continue;
|
|
}
|
|
this.hovering = slot;
|
|
|
|
for (const button of [0, 2]) {
|
|
if (InputManager.is_mouse_pressed(button)) {
|
|
InputManager.consume_mouse(button);
|
|
this.click(slot, button);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
click(slot: Slot, button: number) {
|
|
this.send({ type: "click", container: slot.container, index: slot.index, button });
|
|
|
|
// output slots depend on server side things like recipes, just wait for the server
|
|
const container = this.get_container(slot.container);
|
|
if (container && !slot.output) {
|
|
click_slot(container, slot.index, this.inventories.cursor, button);
|
|
}
|
|
}
|
|
}
|
|
|
|
// generic methods that is often needed
|
|
|
|
export function add_player_inventory(handler: GuiInventoryScreen, offset_x: number, offset_y: number) {
|
|
for (let i = 0; i < 3; ++i) {
|
|
for (let l = 0; l < 9; ++l) {
|
|
handler.slots.push(
|
|
new Slot("inventory", l + i * 9 + 9, offset_x + l * SLOT_SIZE, offset_y + i * SLOT_SIZE),
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
export function add_player_hotbar(handler: GuiInventoryScreen, offset_x: number, offset_y: number) {
|
|
for (let i = 0; i < 9; ++i) {
|
|
handler.slots.push(new Slot("inventory", i, offset_x + i * SLOT_SIZE, offset_y));
|
|
}
|
|
}
|