Skip to main content

World & Blocks

session.levelState is a LevelStateTracker: the client's model of the world. Use it to read blocks and chunks, ray-trace, check collision, and look up block states.

LevelStateTracker​

Reading & writing blocks​

MemberTypeDescription
getBlock(x, y, z, layer?)BlockStateBlock state at a world position. Layer 0 by default.
setBlock(x, y, z, layer, blockState)voidSet a block state client-side only. Nothing is sent to the server.
getBlockEntity(x, y, z)BlockEntity | undefinedBlock entity at a world position, if any.
providerBlockStateProviderThe block-state registry. Look blocks up by name.
const block = ctx.session.levelState.getBlock(100, 64, 100);
console.log(block.name); // "minecraft:stone"
console.log(block.states); // { ... }
console.log(block.meta.isFullBlock); // true

Ray-tracing & collision​

MemberTypeDescription
rayTrace(position, direction, maxDistance){ block, blockPosition, position, face } | nullRay-trace against world blocks. Returns the hit, or null.
checkCollision(position, motion){ verticalCollision, horizontalCollision }Collision flags for a player at position after applying motion.
getCollidingBlocks(aabb, ignoreAtOrAbove?)AABB[]World-space collision boxes that intersect the given AABB. Boxes whose bottom is at or above ignoreAtOrAbove are left out.
const p = ctx.session.entityState.localPlayer;
const hit = ctx.session.levelState.rayTrace(p.eyePosition, /* dir */ p.motion, 5);
if (hit) console.log("looking at", hit.block.name, "face", hit.face);

Chunks & dimension​

MemberTypeDescription
getChunk(chunkX, chunkZ)Chunk | undefinedA loaded chunk, by chunk coordinates.
getSubChunk(chunkX, chunkZ, subChunkY)SubChunk | undefinedA subchunk.
chunkStorageChunkStorageLoaded chunks, by chunk coordinates.
chunkRadiusnumber | nullServer view distance in chunks, if known.
get dimensionDimensionDataData for the current dimension.
dimensionIdnumberIndex of the current dimension.
dimensionListDimensionData[]Known dimensions.
difficultynumber0 = peaceful, 1 = easy, 2 = normal, 3 = hard.
seedbigintWorld seed.

BlockState​

The resolved state of one block. Implements PaletteItem.

MemberTypeDescription
runtimeIdnumberNetwork runtime id of this block state.
hashnumberIdentity hash of this block state.
namestringBlock identifier, e.g. minecraft:stone.
get statesRecord<string, any>Simplified block-state values, e.g. { facing_direction: 2 }.
nbtStatesTags['compound']Raw NBT block-state compound.
tagsSet<string>Block tags.
metaBlockMetaDerived block metadata (below).
get isAirbooleanWhether this is air. Goes by name, not collision: a torch or water has no collision box but isn't air.

BlockMeta​

MemberType
hardnessnumber
frictionnumber
collisionShapeAABB[]
visualShapeAABB
mapColor[number, number, number, number]
requiresCorrectToolForDropsboolean
isFullBlockboolean

BlockStateProvider​

levelState.provider. Looks up any block state by identifier:

const stone = ctx.session.levelState.provider.getBlock("minecraft:stone");
// BlockState | undefined

DimensionData​

MemberTypeDescription
handlestringDimension identifier.
min_heightnumberLowest block Y.
max_heightnumberHighest block Y.

Chunks: Chunk, SubChunk, Palette​

Chunk and SubChunk are type-only exports. Use import type { Chunk, SubChunk } to name them. You can't use them with instanceof.

ChunkStorage​

levelState.chunkStorage holds every loaded chunk, looked up by chunk coordinates. Before 0.5 it was a Map<bigint, Chunk> keyed by chunk.key().

MemberTypeDescription
sizenumberNumber of loaded chunks.
get(x, z)Chunk | undefinedThe chunk at chunk coordinates, if loaded.
has(x, z)booleanWhether a chunk is loaded at chunk coordinates.
values()IterableIterator<Chunk>Every loaded chunk. You can delete while iterating.
set(chunk)voidStore a chunk at its own coordinates, replacing any already there.
delete(x, z)booleanRemove a chunk. Returns whether there was one.
clear()voidRemove every chunk.
const chunks = ctx.session.levelState.chunkStorage;
console.log(chunks.size, "chunks loaded");

for (const chunk of chunks.values()) {
console.log(chunk.x, chunk.z);
}

You'll mostly read from it. set, delete and clear only change the tracker's own copy of the world.

Chunk​

MemberTypeDescription
x / znumberChunk coordinates (block coordinate >> 4).
providerBlockStateProviderResolves runtime ids in this chunk.
subChunksSubChunk[]Subchunks. Index 0 is the bottom (see dimension.min_height).
blockEntitiesBlockEntity[]Block entities tracked in this chunk.
key()bigintX and Z packed into one 64-bit key.
getSubChunk(i)SubChunkSubchunk at vertical index i (0 = bottom).

SubChunk​

A 16×16×16 section. Block positions inside it are packed indices (0–4095).

MemberTypeDescription
providerBlockStateProviderResolves runtime ids.
parentChunkThe chunk this belongs to.
defaultLayerPalette<BlockState>Layer 0, the main block layer.
extraLayersPalette<BlockState>[]Extra layers (e.g. waterlogging). Index 0 here is layer 1.
index(x, y, z)numberPack local coordinates (0–15) into a block index.
fromIndex(index){ x, y, z }Unpack a block index into local coordinates.
getBlock(x, y, z, layer?)BlockStateRead a block at local coordinates.
setBlock(x, y, z, layer, block)voidWrite a block at local coordinates.
hasBlock(block, layer?)booleanWhether the block appears on the layer.
getLayer(layer)Palette<BlockState>The palette for a layer (0 = default).

Palette<T>​

Stores one subchunk layer: the list of distinct entries, plus a lookup for each position.

MemberTypeDescription
paletteT[]The distinct entries present (e.g. block states).
get(index)TEntry at a block index.
set(index, value)voidSet the entry at a block index.
has(item)booleanWhether the entry is present.
positions(item)Generator<number>Every block index holding item.

Finding all stone in a subchunk:

const sub = ctx.session.levelState.getSubChunk(0, 0, 4);
if (sub) {
for (const block of sub.defaultLayer.palette) {
if (block.name === "minecraft:stone") {
for (const idx of sub.defaultLayer.positions(block)) {
const { x, y, z } = sub.fromIndex(idx); // local 0-15
}
}
}
}

Tracking blocks: AbstractBlockLocationTracker​

Subclass AbstractBlockLocationTracker<T> to keep a live set of blocks that stays in sync as chunks load and unload and blocks change. The built-in Chest ESP and Block ESP modules use the same class.

You implement three methods, and the base class handles the chunk and block bookkeeping:

MemberTypeDescription
canTrackBlock(block)booleanYou implement. Return true for blocks you want tracked.
onBlockAdded(entry)voidYou implement. A tracked block appeared.
onBlockRemoved(entry)voidYou implement. A tracked block was removed.
entriesMap<bigint, BlockEntry<T>>All tracked blocks, keyed by packed position.
reset()voidRe-scan all loaded chunks.
bindToSession(session)voidAttach, and scan the chunks already loaded.
unbindFromSession()voidDetach and clear.

BlockEntry<T> extends IVector3 (x/y/z) and adds block: BlockState and an optional extra?: T.

import {
AbstractBlockLocationTracker,
type BlockEntry,
type BlockState,
} from "@protohax/userscript";

class DiamondOreTracker extends AbstractBlockLocationTracker<void> {
canTrackBlock(block: BlockState): boolean {
return block.name === "minecraft:diamond_ore";
}
onBlockAdded(entry: BlockEntry<void>) {
console.log("diamond at", entry.x, entry.y, entry.z);
}
onBlockRemoved(entry: BlockEntry<void>) {
console.log("gone", entry.x, entry.y, entry.z);
}
}

defineModule(
{ name: "DiamondFinder" },
{},
(ctx) => {
const tracker = new DiamondOreTracker();
ctx.onEnable(() => tracker.bindToSession(ctx.session));
ctx.onDisable(() => tracker.unbindFromSession());
},
);
Client-side only

setBlock and tracked state only exist on your client. They change what your client sees, not the server's world.