import type { ItemStack } from "./inventory.ts"; export class EverythingRegistry { static #key_to_id = new Map>(); static #id_to_value = new Map(); static register(registry: string, key: string, value: T): T { if (this.#key_to_id.get(registry)?.has(key)) { throw new Error(`${key} is already registered in ${registry}`); } if (!this.#key_to_id.has(registry)) { this.#key_to_id.set(registry, new Map()); this.#id_to_value.set(registry, []); } const key_map = this.#key_to_id.get(registry)!; const values = this.#id_to_value.get(registry)!; const id = values.length + 1; key_map.set(key, id); values[id] = value; return value; } static get(registry: string, key: string): T | undefined { const id = this.#key_to_id.get(registry)?.get(key); if (id === undefined) { return undefined; } return this.#id_to_value.get(registry)?.[id] as T; } static get_id(registry: string, key: string): number | undefined { return this.#key_to_id.get(registry)?.get(key); } static get_full(registry: string, key: string): [number, T] | undefined { const id = this.#key_to_id.get(registry)?.get(key); if (id === undefined) { return undefined; } return [id, this.#id_to_value.get(registry)?.[id] as T]; } static get_by_id(registry: string, id: number): T | undefined { return this.#id_to_value.get(registry)?.[id] as T; } static get_registry(registry: string): T[] { return this.#id_to_value.get(registry) as T[]; } // forget everything, for tests that load mods more than once static clear() { this.#key_to_id.clear(); this.#id_to_value.clear(); } // [key, value] pairs in registration order static entries(registry: string): [string, T][] { const values = this.#id_to_value.get(registry) ?? []; return [...(this.#key_to_id.get(registry) ?? [])].map(([key, id]) => [key, values[id] as T]); } } interface TextureSideTopBottom { top: string; bottom: string; side: string; } interface TextureFront { front: string; side: string; } export interface BlockStateDefinition { name: string; bits: number; default: number; } interface CompiledStateDefinition { name: string; mask: number; shift: number; } interface BlockStateVariant { model: string; y: number; } // solid: fully opaque. cutout: texels are either opaque or see-through (leaves). // translucent: blended and sorted back to front (water, glass) export const RENDER_LAYERS = ["solid", "cutout", "translucent"] as const; export type RenderLayer = typeof RENDER_LAYERS[number]; export interface BlockRegistry { id: string; textures: string | TextureSideTopBottom | TextureFront; // solid when not set. anything else doesn't hide its neighbors' faces render_layer?: RenderLayer; // hide faces between two of this block, like glass and water. defaults to true for translucent blocks cull_same?: boolean; alpha?: number; has_collision: boolean; toughness?: number; requires_tool?: boolean; tool_to_break?: string; drop_table?: string; states?: BlockStateDefinition[]; variants?: Record; // right clicking it does something instead of placing a block, clients don't predict placing against it. // behavior runs on the server, see server/game/blocks.ts interactive?: boolean; // placing a block into it replaces it, like water replaceable?: boolean; // custom components from mods and their params, the server runs them components?: Record; compiled_states?: CompiledStateDefinition[]; } export interface ItemRegistry { texture_id: string | ((item: ItemStack) => string); block_id?: string; tool_type?: string; max_stack?: number; lore?: string; components?: Record; on_create?(item: ItemStack): void; get_lore?(item: ItemStack): string; }