Files
bworld/common/everything_registry.ts
2026-09-25 15:40:09 -03:00

156 lines
4.4 KiB
TypeScript

import type { ItemStack } from "./inventory.ts";
export class EverythingRegistry {
static #key_to_id = new Map<string, Map<string, number>>();
static #id_to_value = new Map<string, unknown[]>();
static register<T>(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<T>(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<T>(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<T>(registry: string, id: number): T | undefined {
return this.#id_to_value.get(registry)?.[id] as T;
}
static get_registry<T>(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<T>(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];
// like minecraft: solid blocks stop light, everything else lets it through unless it says otherwise
export function block_light_opacity(block: BlockRegistry | undefined): number {
if (!block) return 0;
return block.light_opacity ?? ((block.render_layer ?? "solid") === "solid" ? 15 : 0);
}
export function block_light_emission(block: BlockRegistry | undefined): number {
return block?.light_emission ?? 0;
}
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;
// light level 0-15 it gives off
light_emission?: number;
// how much light going through it loses, 0-15. see block_light_opacity for the default
light_opacity?: number;
alpha?: number;
has_collision: boolean;
toughness?: number;
requires_tool?: boolean;
tool_to_break?: string;
drop_table?: string;
states?: BlockStateDefinition[];
variants?: Record<string, BlockStateVariant>;
// 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<string, unknown>;
compiled_states?: CompiledStateDefinition[];
}
export interface ItemRegistry<T = unknown | undefined> {
texture_id: string | ((item: ItemStack<T>) => string);
block_id?: string;
tool_type?: string;
max_stack?: number;
lore?: string;
components?: Record<string, unknown>;
on_create?(item: ItemStack<T>): void;
get_lore?(item: ItemStack<T>): string;
}