102 lines
3.3 KiB
TypeScript
102 lines
3.3 KiB
TypeScript
import { SLOT_SIZE } from "$/common/constants.ts";
|
|
import { AssetManager } from "$/client/assets.ts";
|
|
import type { Client } from "$/client/client.ts";
|
|
import type { ClientInventories } from "$/client/inventory.ts";
|
|
import { draw_item, draw_nine_slice } from "$/client/rendering/render_utils.ts";
|
|
import { canvas, draw_rect_stroke, draw_text, measure_text, Texture } from "$/client/renderer/mod.ts";
|
|
import { display_name } from "$/common/utils.ts";
|
|
import { TEXT_HEIGHT, TEXT_SCALE } from "./widgets.ts";
|
|
|
|
const PADDING = 10;
|
|
const CROSSHAIR_SIZE = 8;
|
|
// how long the held item's name shows above the hotbar after it changes, and the last part of that it fades out in
|
|
const HELD_NAME_MS = 2000;
|
|
const HELD_NAME_FADE_MS = 500;
|
|
const HELD_NAME_GAP = 12;
|
|
|
|
// what's drawn over the world while playing, like minecraft's Gui: hotbar, crosshair and chat
|
|
export class Hud {
|
|
// what was held last frame, as slot and item id, and when the held item last changed
|
|
#held = "";
|
|
#held_since = -Infinity;
|
|
|
|
render(client: Client) {
|
|
this.#render_hotbar(client.player.inventories);
|
|
this.#render_held_name(client.player.inventories);
|
|
this.#render_crosshair();
|
|
client.chat.render(false);
|
|
}
|
|
|
|
#render_hotbar(inventories: ClientInventories) {
|
|
const ui = AssetManager.instance.get<Texture>("bworld:ui");
|
|
|
|
const hotbar_width = PADDING * 2 + SLOT_SIZE * 9;
|
|
const hotbar_height = PADDING * 2 + SLOT_SIZE;
|
|
|
|
const x = canvas.width / 2 - hotbar_width / 2;
|
|
const y = canvas.height - hotbar_height;
|
|
|
|
draw_nine_slice(ui, 160, 0, 16, 16, 4, 4, 4, 4, x, y, hotbar_width, hotbar_height);
|
|
|
|
for (let index = 0; index < 9; index += 1) {
|
|
const selected = inventories.hotbar_selected === index;
|
|
draw_nine_slice(
|
|
ui,
|
|
selected ? 19 * 16 : 160 + 32,
|
|
selected ? 16 : 0,
|
|
16,
|
|
16,
|
|
4,
|
|
4,
|
|
4,
|
|
4,
|
|
x + PADDING + index * SLOT_SIZE,
|
|
y + PADDING,
|
|
SLOT_SIZE,
|
|
SLOT_SIZE,
|
|
);
|
|
}
|
|
|
|
for (let index = 0; index < 9; index += 1) {
|
|
const item = inventories.inventory.get_item(index);
|
|
if (item) {
|
|
draw_item(item, x + PADDING + index * SLOT_SIZE, y + PADDING);
|
|
}
|
|
}
|
|
}
|
|
|
|
// like minecraft: switching slots, or a different item ending up in the selected one, shows its name for a bit
|
|
#render_held_name(inventories: ClientInventories) {
|
|
const item = inventories.inventory.get_item(inventories.hotbar_selected);
|
|
const held = item ? `${inventories.hotbar_selected} ${item.type_id}` : "";
|
|
const now = performance.now();
|
|
if (held !== this.#held) {
|
|
this.#held = held;
|
|
this.#held_since = item ? now : -Infinity;
|
|
}
|
|
|
|
const shown_for = now - this.#held_since;
|
|
if (!item || shown_for > HELD_NAME_MS) {
|
|
return;
|
|
}
|
|
const alpha = Math.min(1, (HELD_NAME_MS - shown_for) / HELD_NAME_FADE_MS);
|
|
const name = display_name(item.type_id);
|
|
const hotbar_top = canvas.height - (PADDING * 2 + SLOT_SIZE);
|
|
const x = (canvas.width - measure_text(name, TEXT_SCALE)) / 2;
|
|
const y = hotbar_top - HELD_NAME_GAP - TEXT_HEIGHT * TEXT_SCALE;
|
|
// a shadow so it reads over bright sky and snow
|
|
draw_text(name, x + TEXT_SCALE, y + TEXT_SCALE, TEXT_SCALE, [0.15, 0.15, 0.15, alpha]);
|
|
draw_text(name, x, y, TEXT_SCALE, [1, 1, 1, alpha]);
|
|
}
|
|
|
|
#render_crosshair() {
|
|
draw_rect_stroke(
|
|
(canvas.width - CROSSHAIR_SIZE) / 2,
|
|
(canvas.height - CROSSHAIR_SIZE) / 2,
|
|
CROSSHAIR_SIZE,
|
|
CROSSHAIR_SIZE,
|
|
[0, 0, 0, 0.6],
|
|
);
|
|
}
|
|
}
|