@minecraft/creator-tools
Version:
Minecraft Creator Tools command line and libraries.
321 lines (320 loc) • 10.8 kB
TypeScript
/**
* 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 {};