UNPKG

@minecraft/creator-tools

Version:

Minecraft Creator Tools command line and libraries.

321 lines (320 loc) 10.8 kB
/** * ARCHITECTURE: LiveWorldState * * Client-side world model that maintains chunk data received from the server. * This is the browser's "copy" of the nearby world — chunks are loaded/unloaded * as the server sends LevelChunkPackets and NetworkChunkPublisherUpdatePackets. * * DATA FLOW: * Server → proxy (decodes Bedrock protocol) → WebSocket → LiveWorldState * LiveWorldState marks chunks dirty → WorldRenderer reads dirty chunks → * ChunkMeshBuilder converts to Babylon.js meshes * * BLOCK ACCESS: * getBlock(x, y, z) → { runtimeId, name } or undefined * setBlock(x, y, z, runtimeId) → marks chunk dirty * getBlockName(runtimeId) → block identifier string via palette lookup * * CHUNK STORAGE: * Chunks keyed by "chunkX,chunkZ" string in a Map. * Each chunk has up to 24 subchunks (y = -64 to 319 in overworld). * Each subchunk is 16×16×16 blocks stored as a flat Int32Array of runtime IDs. * Storage order is XZY: index = x*256 + z*16 + y (Bedrock SubChunk format). * * BLOCK PALETTE: * Maps runtime IDs (32-bit hashes) → block names (e.g., "minecraft:stone"). * Populated from StartGamePacket.block_palette and block_palette events. * CRITICAL: Chunks loaded before the palette arrives will have zero rendered * blocks. When the palette arrives, all pre-loaded chunks are marked dirty * so they get rebuilt with correct block names. * * DIRTY TRACKING: * _dirtyChunks tracks which chunk keys need mesh rebuilds. * Chunks are dirtied when: new chunk data arrives, blocks are modified, * palette arrives (mass re-dirty), or adjacent chunks load (face-culling * recalculation at chunk boundaries). * * CLEAR ZONE: * Optional filter that replaces non-whitelisted blocks above a Y threshold * with air. Used to clear terrain for building or to isolate structures. * Applied to incoming chunk data as it arrives. * * COORDINATE SYSTEMS: * - World coords: (x, y, z) — absolute block position * - Chunk coords: (chunkX, chunkZ) = (x >> 4, z >> 4) * - Local coords: (x & 15, y & 15, z & 15) within a subchunk * - Subchunk index: (y - minY) >> 4 * * PACKET HANDLERS: * Each handle*() method processes a specific Bedrock protocol packet type. * Packet interfaces are defined at the top of this file. The proxy pre-parses * binary protocol data into JSON objects before sending via WebSocket. * * RELATED FILES: * - WorldRenderer.ts — reads dirty chunks and builds scene meshes * - ChunkMeshBuilder.ts — converts chunk data to Babylon.js meshes * - EntityManager.ts — tracks entity state (separate from world blocks) * - WorldChunk.ts — existing chunk model (used for file-based worlds) * - BlockPalette.ts — block runtime ID → type mapping */ export interface IBlockState { runtimeId: number; name?: string; } export interface ISubChunk { blocks: Int32Array | number[]; dirty: boolean; } export interface IChunkColumn { x: number; z: number; subchunks: (ISubChunk | undefined)[]; dirty: boolean; meshGenerated: boolean; } export interface IBlockPaletteEntry { name: string; states?: Record<string, unknown>; } /** Shared 3D coordinate shape. */ interface IBlockPosition { x?: number; y?: number; z?: number; X?: number; Y?: number; Z?: number; } /** Pre-parsed subchunk data attached to a level_chunk packet by the proxy. */ interface IParsedSubChunkEntry { y?: number; blocks?: number[] | Int32Array; } export interface ILevelChunkPacket { x: number; z: number; sub_chunk_count?: number; /** Pre-parsed subchunk block arrays (provided by the proxy). */ subchunks?: IParsedSubChunkEntry[]; /** Raw payload buffer (legacy path). */ payload?: ArrayBuffer | Uint8Array; } /** A single entry inside a SubChunk response. */ interface ISubChunkResponseEntry { result?: string; blocks?: number[] | Int32Array; dx?: number; dy?: number; dz?: number; yIndex?: number; } export interface ISubChunkPacket { origin?: { x?: number; z?: number; }; entries?: ISubChunkResponseEntry[]; } export interface IUpdateBlockPacket { position?: IBlockPosition; block_position?: IBlockPosition; block_runtime_id?: number; runtime_id?: number; } /** Individual block update within an update_subchunk_blocks batch. */ interface ISubChunkBlockUpdate { block_position?: IBlockPosition; position?: IBlockPosition; block_runtime_id?: number; runtime_id?: number; } export interface IUpdateSubChunkBlocksPacket { blocks?: ISubChunkBlockUpdate[]; standard_blocks?: ISubChunkBlockUpdate[]; extra_blocks?: ISubChunkBlockUpdate[]; } export interface IChunkPublisherUpdatePacket { position?: IBlockPosition; radius?: number; } export interface IStartGameData { player_gamemode?: number; gamemode?: number; world_name?: string; difficulty?: number; dimension?: number; world_spawn?: { x?: number; y?: number; z?: number; }; itemstates?: unknown[]; block_palette?: Array<{ runtime_id?: number; name?: string; states?: Record<string, unknown>; }>; } export default class LiveWorldState { private _chunks; private _blockPalette; private _dirtyChunks; private _worldName; private _gameMode; private _spawnPosition; private _worldTime; private _minY; private _maxY; private _dimensionId; private _difficulty; private _clearZone?; private _loadedChunkCount; get worldName(): string; get gameMode(): number; get spawnPosition(): { x: number; y: number; z: number; }; get worldTime(): number; set worldTime(t: number); get dimensionId(): number; get minY(): number; get loadedChunkCount(): number; get dirtyChunkCount(): number; /** * Return the center position (in world coordinates) of all loaded chunks. * Falls back to spawnPosition if no chunks are loaded. */ getChunkCenter(): { x: number; y: number; z: number; }; /** * Set a clear zone that automatically filters incoming SubChunk data. * Any block above clearAboveY is replaced with air UNLESS it's in keepNames * AND at or below maxKeepY. This prevents SubChunk data races from restoring * natural terrain that was previously cleared. */ setClearZone(clearAboveY: number, maxKeepY: number, keepNames: Set<string>): void; clearClearZone(): void; /** * Initialize from StartGamePacket data. */ initFromStartGame(data: IStartGameData): void; /** * Get a block at world coordinates. */ getBlock(x: number, y: number, z: number): IBlockState | undefined; /** * Set a block at world coordinates (for client-side prediction). */ setBlock(x: number, y: number, z: number, runtimeId: number): void; /** * Check if the chunk containing this position has been loaded. */ isChunkLoaded(x: number, z: number): boolean; /** * Check if a block position is solid (for collision detection). */ isSolid(x: number, y: number, z: number): boolean; /** * Handle block_palette event from the proxy. * This provides the mapping of runtime IDs to block names. */ handleBlockPalette(entries: { rid: number; name: string; }[]): void; /** * Handle a level_chunk packet from the server. * The proxy may have pre-parsed subchunk data for us. */ private _levelChunkLogCount; private _subchunkLogCount; handleLevelChunk(packet: ILevelChunkPacket): void; /** * Handle a subchunk packet (from the proxy's parsed SubChunk response). * The proxy resolves PalettedBlockStorage and sends pre-parsed block arrays. * Origin is in sub-chunk coordinates (chunkX, subchunkY, chunkZ) — already * in chunk coordinate space. Do NOT right-shift by 4 (that would be double-shifting). */ handleSubChunk(packet: ISubChunkPacket): void; /** * Handle update_block packet. */ handleUpdateBlock(packet: IUpdateBlockPacket): void; /** * Handle update_subchunk_blocks — batch block updates within a subchunk. */ handleUpdateSubChunkBlocks(packet: IUpdateSubChunkBlocksPacket): void; /** * Handle network_chunk_publisher_update — defines which chunks should be loaded. */ handleChunkPublisherUpdate(packet: IChunkPublisherUpdatePacket): void; /** * Get chunk at chunk coordinates. */ getChunk(chunkX: number, chunkZ: number): IChunkColumn | undefined; /** * Get all dirty chunks and clear dirty flags. */ consumeDirtyChunks(): IChunkColumn[]; /** * Mark a chunk as dirty so it will be rebuilt on next consumeDirtyChunks call. */ markChunkDirty(chunk: IChunkColumn): void; /** * Get all loaded chunks. */ getAllChunks(): IChunkColumn[]; /** * Read-only access to the chunks map for iteration. * Used by WorldRenderer.ensureNearbyChunkMeshes() to scan for chunks * within render distance that need mesh generation. */ get chunks(): ReadonlyMap<string, IChunkColumn>; /** * Get a chunk by its key string ("chunkX,chunkZ"). */ getChunkByKey(key: string): IChunkColumn | undefined; /** * Add or replace a chunk column directly. * Used by WorldViewer to feed pre-built chunk data from file-based MCWorld. */ setChunkColumn(chunk: IChunkColumn): void; /** * Mark ALL loaded chunks as dirty, forcing a complete mesh rebuild. * Useful after fill commands or other bulk world modifications. */ markAllChunksDirty(): void; /** * Get block palette entry by runtime ID. */ private _lookupLogCount; private _lookupMissCount; private _lookupHitCount; getBlockName(runtimeId: number): string; /** * Unload all chunks (e.g., on dimension change). */ clear(): void; /** * Parse a level_chunk payload into subchunk data. * The payload format depends on the network protocol version, but decoded * packets typically give us the raw subchunk data as a Buffer. */ private _parseLevelChunkPayload; /** * Parse raw subchunk data into an ISubChunk. * Bedrock uses PalettedBlockStorage format per subchunk. */ private _parseSubChunkData; /** * Apply the clear zone filter to a subchunk's block data. * Replaces non-whitelisted blocks above clearAboveY with air (runtime ID 0). */ private _applyClearZoneToSubchunk; } export {};