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("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], ); } }