world module
World data queries and block interaction.
The world module provides access to chunk data collected from the Minecraft client, enabling block queries and world interaction. Chunks are automatically synchronized and cached in memory.
Saved world
Every query that takes use_disk can also read the world the manager saves to disk (the .mca
autosave under the world save directory, one save per server). Memory comes first: a chunk that is
loaded in memory for the requested dimension is answered from memory only, and disk is consulted
for the chunks that are not. Nothing is reported twice. When the save path does not exist, saving
is disabled, or a chunk was never saved, the disk side contributes nothing - no error is raised.
The area searches (find_blocks, find_nearest, find_block_entities) scan by region rather
than by chunk: the region files covering the search disc are opened once each, split across worker
threads, and only the saved chunks inside the disc that are not in memory are decoded. Only the
parts of a chunk the query needs are parsed (the palette of a section is checked before its block
data), so a search over a few hundred regions takes well under a second to a few seconds,
depending on disk and cores, and never runs on the manager's UI thread. dimension follows get_block: it defaults to the bot's current dimension, memory is read
when the loaded chunk belongs to that dimension, and the save's region, DIM-1/region,
DIM1/region (or dimensions/... on 26.1+) directory is read for the rest.
Block Queries
get_block(x, y, z, use_disk=False, dimension="", bot_name="")
Get the block state at the specified coordinates.
Parameters:
x(int) - Block X coordinatey(int) - Block Y coordinatez(int) - Block Z coordinateuse_disk(bool, optional) - IfTrueand the chunk is not loaded in memory, read the block from the saved.mcaregion file on disk (default:False)dimension(str, optional) - Dimension string (e.g."minecraft:overworld","minecraft:the_nether"). Defaults to the bot's current dimension. Memory is read when the loaded chunk at that position belongs to this dimension; otherwise, withuse_disk=True, the saved world is read instead.bot_name(str, optional) - Bot name, defaults to current bot
Returns: str - Block state string (e.g., "minecraft:stone", "minecraft:chest[facing=north]"), or None if chunk is not loaded (and not on disk when use_disk=True) or bot is offline
Raises: RuntimeError if bot not found or not online
Note: Disk reads require world saving to be enabled. Returns None if the world save path is not available or the chunk has never been saved.
# Get block at specific position
block = world.get_block(100, 64, 200)
if block:
print(f"Block at (100, 64, 200): {block}")
# Check block type
if "chest" in block:
print("Found a chest!")
# Read a block from a chunk that isn't currently loaded
block = world.get_block(5000, 64, 5000, use_disk=True)
if block:
print(f"Saved block: {block}")
# Read a block from a different dimension
block = world.get_block(100, 64, 100, use_disk=True, dimension="minecraft:the_nether")
find_blocks(block_type, center_x, center_y, center_z, radius, min_block_light=0, max_block_light=15, min_sky_light=0, max_sky_light=15, dimension="", use_disk=False, bot_name="")
Find all blocks of a specific type within a spherical radius, with optional light level filters.
By default only loaded chunks are searched. With use_disk=True, chunks inside the radius that are
not loaded in memory are read from the saved world.
Parameters:
block_type(str) - Block type to search for (e.g.,"minecraft:diamond_ore"). A[state]suffix is ignored; every state of the block matchescenter_x(float) - Search center X coordinatecenter_y(float) - Search center Y coordinatecenter_z(float) - Search center Z coordinateradius(int) - Search radius in blocksmin_block_light(int, optional) - Minimum block light level, inclusive (default: 0)max_block_light(int, optional) - Maximum block light level, inclusive (default: 15)min_sky_light(int, optional) - Minimum sky light level, inclusive (default: 0)max_sky_light(int, optional) - Maximum sky light level, inclusive (default: 15)dimension(str, optional) - Dimension string. Defaults to the bot's current dimension; same semantics asget_blockuse_disk(bool, optional) - Also search saved chunks that are not loaded (default:False)bot_name(str, optional) - Bot name, defaults to current bot
Returns: list[tuple] - Block positions as (x, y, z) tuples of floats, nearest to the center first
Note: Returns float coordinates. Convert to int for block operations: int(x), int(y), int(z)
Raises: RuntimeError if bot not found or not online
# Find all diamond ore within 64 blocks of bot
pos = bot.position()
diamonds = world.find_blocks("minecraft:diamond_ore",
pos["x"], pos["y"], pos["z"],
radius=64)
print(f"Found {len(diamonds)} diamond ore blocks")
for x, y, z in diamonds:
print(f" Diamond at ({x}, {y}, {z})")
# Find all chests near spawn
chests = world.find_blocks("minecraft:chest", 0, 64, 0, radius=100)
# Find air blocks with no block light and no sky access (fully dark, unlit caves)
pos = bot.position()
dark_air = world.find_blocks("minecraft:air",
pos["x"], pos["y"], pos["z"],
radius=128,
max_block_light=0,
max_sky_light=0)
# Every obsidian block within 2000 blocks of the nether origin, loaded or saved
obsidian = world.find_blocks("minecraft:obsidian", 0, 64, 0, radius=2000,
dimension="minecraft:the_nether", use_disk=True)
find_nearest(block_types, max_distance=128, dimension="", use_disk=False, bot_name="")
Find the nearest block matching any of the specified types.
This function searches from the bot's current position. By default only loaded chunks are
searched; with use_disk=True, chunks within max_distance that are not loaded are read from the
saved world as well.
Parameters:
block_types(list[str]) - List of block types to search for (a[state]suffix is ignored)max_distance(int, optional) - Maximum search distance in blocks (default: 128)dimension(str, optional) - Dimension string. Defaults to the bot's current dimension; same semantics asget_blockuse_disk(bool, optional) - Also search saved chunks that are not loaded (default:False)bot_name(str, optional) - Bot name, defaults to current bot
Returns: tuple - Position as (x, y, z) tuple of floats, or None if no matching block found
Note: Returns float coordinates. Convert to int for block operations: int(x), int(y), int(z)
Raises: RuntimeError if bot not found or not online
import time
# Find nearest chest or barrel
container = world.find_nearest([
"minecraft:chest",
"minecraft:barrel",
"minecraft:trapped_chest"
])
if container:
x, y, z = container
print(f"Found container at ({int(x)}, {int(y)}, {int(z)})")
# Find a valid standing position around the container
# Check adjacent blocks (not diagonals)
offsets = [(1, 0, 0), (-1, 0, 0), (0, 0, 1), (0, 0, -1)]
standing_pos = None
for dx, dy, dz in offsets:
check_x, check_y, check_z = int(x) + dx, int(y) + dy, int(z) + dz
# Check if position is air (can stand there)
block_at_pos = world.get_block(check_x, check_y, check_z)
block_above = world.get_block(check_x, check_y + 1, check_z)
if block_at_pos and block_above and "air" in block_at_pos and "air" in block_above:
standing_pos = (check_x, check_y, check_z)
break
if standing_pos:
sx, sy, sz = standing_pos
print(f"Going to standing position ({sx}, {sy}, {sz})")
baritone.goto(sx, sy, sz)
# Wait for bot to arrive
while True:
pos = bot.position()
if abs(pos["x"] - sx) < 1.5 and abs(pos["y"] - sy) < 1.5 and abs(pos["z"] - sz) < 1.5:
break
time.sleep(0.2)
# To interact, convert to int:
world.interact_block(int(x), int(y), int(z))
else:
print("No valid standing position found near container")
else:
print("No containers found nearby")
# Find nearest ore
ore = world.find_nearest([
"minecraft:diamond_ore",
"minecraft:iron_ore",
"minecraft:coal_ore"
], max_distance=50)
if ore:
x, y, z = ore
print(f"Found ore at ({int(x)}, {int(y)}, {int(z)})")
block = world.get_block(int(x), int(y), int(z))
print(f"Ore type: {block}")
else:
print("No ore found within 50 blocks")
# Nearest saved ender chest, even if the chunk is not loaded
echest = world.find_nearest(["minecraft:ender_chest"], max_distance=2000, use_disk=True)
Block Entities
Block entities are blocks with attached data: chests, furnaces, signs, shulker boxes, etc. The bot tracks block entities for chunks it has loaded this session. With use_disk=True, entities from saved but currently unloaded chunks can also be queried.
get_block_entity(x, y, z, use_disk=False, dimension="", bot_name="")
Get the block entity at the specified position.
Parameters:
x(int) - Block X coordinatey(int) - Block Y coordinatez(int) - Block Z coordinateuse_disk(bool, optional) - IfTrueand no in-memory data exists, read from the saved.mcafile (default:False)dimension(str, optional) - Dimension string. Defaults to the bot's current dimension. Block entities are stored per dimension, so an explicit dimension reads memory directly.bot_name(str, optional) - Bot name, defaults to current bot
Returns: BlockEntity or None if no block entity exists at that position
BlockEntity attributes:
| Attribute | Type | Description |
|---|---|---|
type |
str |
Block entity id, e.g. "minecraft:chest" |
x, y, z |
int |
Block position |
items |
list |
Container contents (item dicts); empty when the container has never been opened |
front_text |
list[str] |
Signs: the four lines of the front face, in order. Empty for anything else |
back_text |
list[str] |
Signs: the four lines of the back face, in order. Empty for anything else |
is_waxed |
bool |
Signs: True when the sign has been waxed with honeycomb |
nbt |
dict or None |
The whole NBT compound, parsed on first access. See below |
This used to be a plain dict and still behaves like one - be["type"], be.get("items", []) and
"front_text" in be all work, with the same keys the dict had, so existing scripts keep running.
New code should use the attributes: they are typed in the editor and always present, so
be.front_text on a chest is [] rather than a KeyError.
Note on items: The Minecraft server only sends container contents when a container is opened,
so items is empty unless the container was opened at some point. Memory always takes priority: if
items are already in memory (opened this session), they are returned even when use_disk=True. Disk
is only consulted when items are absent from memory - in that case, items may be present on disk if
the container was opened in a previous session that was saved.
Note on nbt: the block entity's full NBT compound, exactly as the server sent it with the
chunk, converted to plain Python. Use it for anything this API does not expose directly - spawner
mob, beacon effects, banner patterns, lectern books, skull owners, decorated pot sherds, modded
block entities. It is parsed on first access and cached, so a chunk scan that never touches nbt
costs nothing, and it is None when there is no NBT for that block entity.
Two things to know about it:
itemswins overnbt["Items"]. Container contents innbtare the chunk-load snapshot and are never refreshed afterwards, whileitemscomes from the container packet and is current.- The conversion is lossy, because it is meant for reading:
byte,short,intandlongall becomeint;floatanddoublebecomefloat;byte_array,int_arrayandlong_arrayall becomelist[int]; compounds becomedictand lists becomelist.
# items is empty unless the container was opened
be = world.get_block_entity(cx, cy, cz)
if be and be.items:
for item in be.items:
if item['item_id'] != 'minecraft:air':
utils.log(f" {item['count']}x {item['item_id']}")
# check a saved but unloaded chunk
be = world.get_block_entity(5000, 64, 5000, use_disk=True)
if be and be.type == 'minecraft:chest':
utils.log("Found a chest in saved data")
# anything not exposed as an attribute is in nbt
be = world.get_block_entity(sx, sy, sz)
if be and be.type == 'minecraft:trial_spawner':
utils.log(be.nbt.get('normal_config'))
Note on sign text: front_text / back_text come from the block entity data the server sends
with the chunk, so they are available for any sign in a chunk the bot has loaded (or from disk with
use_disk=True).
They are normalized here rather than left to nbt because the encoding differs by Minecraft
version. Text components are flattened to plain strings, so styling and colour are dropped and an
empty line is "".
Freshness: block entity data arrives with the chunk and is then kept current incrementally. The client forwards every block entity update packet the server sends, plus any block entity that shows up on a block update, and drops one whose block is broken - so a sign edited or a banner re-dyed next to the bot is reflected immediately. The block entities that push updates are signs, banners, skulls, spawners, trial spawners, vaults, beacons, campfires, decorated pots, conduits, beds, brushable blocks, end gateways, creaking hearts, jigsaws and structure blocks.
Chests, barrels, furnaces, hoppers, brewing stands, lecterns and beehives send no update packet at
all - Minecraft has none for them - so their contents and state still only arrive by opening the
container (items, or the container_update event, which also
carries furnace burn and cook progress) or when the chunk is loaded again.
# Find the chest under the sign that says "eChests"
for be in world.get_block_entities_in_chunk(cx, cz):
if "eChests" in be.front_text:
world.interact_block(be.x, be.y - 1, be.z)
break
get_block_entities_in_chunk(chunk_x, chunk_z, use_disk=False, dimension="", bot_name="")
Get all block entities in a chunk.
Parameters:
chunk_x(int) - Chunk X coordinate (block X divided by 16, rounded down)chunk_z(int) - Chunk Z coordinate (block Z divided by 16, rounded down)use_disk(bool, optional) - IfTrueand the chunk is not loaded, read from the saved.mcafile (default:False)dimension(str, optional) - Dimension string (e.g."minecraft:overworld","minecraft:the_nether"). Defaults to the bot's current dimension. Memory is read when the loaded chunk at that position belongs to this dimension; otherwise, withuse_disk=True, the saved world is read instead.bot_name(str, optional) - Bot name, defaults to current bot
Returns: list[BlockEntity] - same objects as get_block_entity returns
Memory always takes priority: if the chunk is loaded, memory data is returned regardless of use_disk. Disk is only read when the chunk is not loaded. items is filled for any container that was opened (either this session from memory, or a previous session from disk).
import math
# Get all block entities in the chunk under the bot
pos = bot.position()
cx = math.floor(pos['x'] / 16)
cz = math.floor(pos['z'] / 16)
entities = world.get_block_entities_in_chunk(cx, cz)
for be in entities:
utils.log(f"{be.type} at ({be.x}, {be.y}, {be.z})")
# Scan a saved chunk for chests
entities = world.get_block_entities_in_chunk(312, -5, use_disk=True)
chests = [be for be in entities if be.type == 'minecraft:chest']
utils.log(f"Found {len(chests)} chests in saved chunk (312, -5)")
# Scan a chunk in the nether from disk
nether_ents = world.get_block_entities_in_chunk(10, 10,
use_disk=True, dimension="minecraft:the_nether")
find_block_entities(types, center_x, center_z, radius, dimension="", use_disk=False, limit=0, bot_name="")
Find block entities of the given types within a horizontal radius of a point, at any y.
By default only loaded chunks are searched. With use_disk=True, chunks inside the radius that are
not loaded in memory are read from the saved world, so one call answers "every ender
chest within 5000 blocks of here" without loading anything.
Parameters:
types(list[str]) - Block entity ids to look for, e.g.["minecraft:ender_chest"]or["minecraft:chest", "minecraft:barrel"]. An empty list matches every block entitycenter_x(float) - Search center X coordinatecenter_z(float) - Search center Z coordinateradius(float) - Horizontal search radius in blocks; the search is a disc, every y is includeddimension(str, optional) - Dimension string. Defaults to the bot's current dimension; same semantics asget_blockuse_disk(bool, optional) - Also search saved chunks that are not loaded (default:False)limit(int, optional) - Return at most this many, nearest first.0means no limit (default)bot_name(str, optional) - Bot name, defaults to current bot
Returns: list[BlockEntity] - the same objects get_block_entity returns, sorted by horizontal distance from (center_x, center_z) ascending. items is filled where it is known (opened this session from memory, or saved with the chunk)
Raises: RuntimeError if bot not found or not online
import math
# Ender chests in the saved overworld within 5000 blocks of spawn, farthest first
hits = world.find_block_entities(["minecraft:ender_chest"], 0, 0, 5000,
dimension="minecraft:overworld", use_disk=True)
hits.sort(key=lambda be: -math.hypot(be.x, be.z))
for be in hits[:10]:
utils.log(f"ender chest at {be.x} {be.y} {be.z}")
# The five nearest containers of any kind, loaded or saved
pos = bot.position()
nearby = world.find_block_entities(["minecraft:chest", "minecraft:trapped_chest", "minecraft:barrel"],
pos["x"], pos["z"], 500, use_disk=True, limit=5)
# Everything with a block entity in the loaded nether chunks around the bot
everything = world.find_block_entities([], pos["x"], pos["z"], 200,
dimension="minecraft:the_nether")
Block Interaction
look_at(x, y, z, face=world.BlockFace.AUTO, sneak=False, bot_name="")
Rotate the bot to look at a block. With face=AUTO the handler raycasts each face and picks the first visible one. When a specific face is given it uses candidate points on that face (center + near-corners for UP/DOWN, top/bottom for horizontal faces) and picks the first visible candidate. If sneak=False and no candidate is visible standing up, it automatically retries with the crouched eye position.
Parameters:
x(int) - Block X coordinatey(int) - Block Y coordinatez(int) - Block Z coordinateface(world.BlockFace, optional) - Which face to target (default:world.BlockFace.AUTO)sneak(bool, optional) - Use crouching eye height for raytrace; when False, tries standing first then sneaking (default: False)bot_name(str, optional) - Bot name, defaults to current bot
Raises: RuntimeError if bot not found or not online
# Look at the top face of a block before placing on it
world.look_at(bx, by, bz, face=world.BlockFace.UP)
world.interact_block(bx, by, bz, sneak=True, look_at_block=False, face=world.BlockFace.UP)
# Auto-find best visible face
world.look_at(bx, by, bz)
look_at_entity(entity_id, sneak=False, bot_name="")
Rotate the bot to look at an entity's eyes. Entity ids come from world.entities() and world.find_entities_near() (the entity_id field). There is no visibility check; the bot turns toward the entity even through walls.
Parameters:
entity_id(int) - Entity idsneak(bool, optional) - Aim from the crouching eye height (default: False)bot_name(str, optional) - Bot name, defaults to current bot
Raises: RuntimeError if bot not found or not online. An unknown entity id is reported by the client in the bot console (Entity <id> not found); nothing is raised.
pos = bot.position()
zombies = world.find_entities_near(pos["x"], pos["y"], pos["z"], 8, type="minecraft:zombie")
if zombies:
world.look_at_entity(zombies[0]["entity_id"])
bot.hold_attack(True, duration_ticks=10)
interact_block(x, y, z, sneak=False, look_at_block=True, face=world.BlockFace.AUTO, bot_name="")
Interact with (right-click) a block at the specified position.
This is used to open containers, press buttons, use beds, place blocks against a neighbour face, etc.
Parameters:
x(int) - Block X coordinatey(int) - Block Y coordinatez(int) - Block Z coordinatesneak(bool, optional) - Whether to sneak while interacting (default: False)look_at_block(bool, optional) - Whether to rotate and look at the block before interacting (default: True)face(world.BlockFace, optional) - Which face of the block to interact with. When set to anything other thanAUTO, bypasses the automatic raytrace and clicks the specified face directly regardless of bot position (default:world.BlockFace.AUTO)bot_name(str, optional) - Bot name, defaults to current bot
Raises: RuntimeError if bot not found or not online
# Open a chest (bot will look at it)
world.interact_block(100, 64, 200)
# Open a chest while sneaking (places block instead if holding one)
world.interact_block(100, 64, 200, sneak=True)
# Place a block against the west face of a neighbour
world.interact_block(nx, ny, nz, sneak=True, face=world.BlockFace.WEST)
# Use a bed
pos = world.find_nearest(["minecraft:white_bed"])
if pos:
x, y, z = pos
world.interact_block(int(x), int(y), int(z))
can_reach_block(x, y, z, sneak=False, face=world.BlockFace.AUTO, timeout=3.0, bot_name="")
Check if a block is reachable (visible via raytrace) from the bot's current eye position.
Parameters:
x, y, z(int) - Block coordinatessneak(bool, optional) - Use crouching eye height (default: False)face(world.BlockFace, optional) - Check reachability of a specific face only.AUTOchecks all faces (default:world.BlockFace.AUTO)timeout(float, optional) - Seconds to wait for the client's reply (default: 3.0)bot_name(str, optional) - Bot name (default: active bot)
Returns: bool - True if the block (or specified face) is reachable
Raises:
ValueErroriftimeoutis not positiveRuntimeErrorif the bot is not found or not online, if the message cannot be sent, or if the client could not evaluate the query (e.g. bot not in a world)TimeoutErrorif the client did not answer withintimeout
if world.can_reach_block(x, y, z):
world.interact_block(x, y, z)
# Check a specific face before placing
if world.can_reach_block(x, y, z, face=world.BlockFace.UP):
world.interact_block(x, y, z, face=world.BlockFace.UP)
can_reach_block_from(from_x, from_y, from_z, x, y, z, sneak=False, face=world.BlockFace.AUTO, timeout=3.0, bot_name="")
Check if a block is reachable from a hypothetical standing position without moving the bot. Useful for pre-validating candidate positions before navigating.
Parameters:
from_x, from_y, from_z(int) - Hypothetical foot position (eye height is added automatically)x, y, z(int) - Block coordinates to checksneak(bool, optional) - Use crouching eye height (default: False)face(world.BlockFace, optional) - Check reachability of a specific face only.AUTOchecks all faces (default:world.BlockFace.AUTO)timeout(float, optional) - Seconds to wait for the client's reply (default: 3.0)bot_name(str, optional) - Bot name (default: active bot)
Returns: bool - True if the block (or specified face) would be reachable from that position
Raises:
ValueErroriftimeoutis not positiveRuntimeErrorif the bot is not found or not online, if the message cannot be sent, or if the client could not evaluate the query (e.g. bot not in a world)TimeoutErrorif the client did not answer withintimeout
# Find a standable position from which the UP face is reachable
if world.can_reach_block_from(cx, cy, cz, bx, by, bz, face=world.BlockFace.UP):
baritone.goto(cx, cy, cz)
can_reach_blocks(queries, sneak=False, face=world.BlockFace.AUTO, timeout=5.0, bot_name="")
Check many positions in a single round trip. Equivalent to calling can_reach_block once per query, except the whole list goes to the client as one message, so a scan costs one game tick instead of one tick per query.
Parameters:
queries(listor any sequence) - Each entry is either an(x, y, z)tuple or aworld.ReachQuerysneak(bool, optional) - Crouching eye height, for every entry that does not override it (default: False)face(world.BlockFace, optional) - Face to check, for every entry that does not override it.AUTOchecks all faces (default:world.BlockFace.AUTO)timeout(float, optional) - Seconds to wait for the client's reply (default: 5.0)bot_name(str, optional) - Bot name (default: active bot)
Returns: list[bool] - One entry per query, in the same order
Raises:
ValueErrorif an entry is neither a 3-tuple nor aReachQuery, if a coordinate is not a number, iftimeoutis not positive, or if the batch is too large to fit in a single request (roughly 210,000 queries)RuntimeErrorif the bot is not found or not online, if the message cannot be sent, or if the client could not evaluate the batch (e.g. bot not in a world)TimeoutErrorif the client did not answer withintimeout
Note: The client does the whole batch within one game tick, so every query is evaluated against the same bot position and the results are consistent with each other even if the bot is moving. The flip side is that a very large batch may briefly freeze the client.
# Which of these chests can the bot reach right now?
chests = world.find_blocks("minecraft:chest", px, py, pz, 8)
positions = [(int(x), int(y), int(z)) for x, y, z in chests]
for (x, y, z), ok in zip(positions, world.can_reach_blocks(positions)):
if ok:
world.interact_block(x, y, z)
break
# Score candidate standing positions against a target, one round trip for the whole scan
queries = [world.ReachQuery(bx, by, bz, from_pos=(cx, cy, cz)) for cx, cy, cz in candidates]
for (cx, cy, cz), ok in zip(candidates, world.can_reach_blocks(queries)):
if ok:
baritone.goto(cx, cy, cz)
break
# Which faces of a block are exposed?
faces = [world.BlockFace.UP, world.BlockFace.DOWN, world.BlockFace.NORTH,
world.BlockFace.SOUTH, world.BlockFace.WEST, world.BlockFace.EAST]
reachable = world.can_reach_blocks([world.ReachQuery(x, y, z, face=f) for f in faces])
usable = [f for f, ok in zip(faces, reachable) if ok]
ReachQuery
One entry of a can_reach_blocks batch, for when a query needs its own sneak, face, or standing position rather than the batch-wide defaults. Plain (x, y, z) tuples are accepted in the same list, so only the entries that need overrides have to use this.
world.ReachQuery(x, y, z, from_pos=None, sneak=None, face=None)
| Field | Type | Description |
|---|---|---|
x, y, z |
int |
Coordinates of the block to check |
from_pos |
tuple[int, int, int] or None |
Foot position to trace from, eye height added automatically. None uses the bot's current position |
sneak |
bool or None |
None inherits the sneak passed to can_reach_blocks |
face |
world.BlockFace or None |
None inherits the face passed to can_reach_blocks |
All three optional fields default to None, meaning "inherit from the call". That is what makes mixing tuples and ReachQuery objects predictable: with can_reach_blocks([...], sneak=True), a bare ReachQuery(x, y, z) crouches just like a bare tuple would, and only an explicit sneak=False opts out.
Fields are readable and writable after construction.
world.can_reach_blocks([
(x1, y1, z1), # batch defaults
world.ReachQuery(x2, y2, z2, sneak=True), # this one crouches
world.ReachQuery(x3, y3, z3, face=world.BlockFace.UP), # top face only
world.ReachQuery(x4, y4, z4, from_pos=(cx, cy, cz)), # from a hypothetical position
])
BlockFace enum
Used with look_at, interact_block, can_reach_block, can_reach_block_from, can_reach_blocks, and world.ReachQuery to specify a block face.
| Value | Direction |
|---|---|
world.BlockFace.AUTO |
Auto-detect via raytrace from bot eye position (default) |
world.BlockFace.DOWN |
Bottom face (-Y) |
world.BlockFace.UP |
Top face (+Y) |
world.BlockFace.NORTH |
North face (-Z) |
world.BlockFace.SOUTH |
South face (+Z) |
world.BlockFace.WEST |
West face (-X) |
world.BlockFace.EAST |
East face (+X) |
Container Interaction
get_container(bot_name="")
Get the currently open container. Returns None if no container is open or bot is offline.
Returns: dict with container info, or None
| Key | Type | Description |
|---|---|---|
id |
int |
Container ID |
type |
ContainerType |
Container type enum value |
position |
tuple[int, int, int] |
Block position of the container. Only present when the client could attribute the open screen to a block (the block was opened by interacting with it shortly before), so use container.get('position'). |
items |
list |
Item dicts for the filled slots only, in slot order. Empty slots are not listed, so check slot rather than the list index. |
Note: Only works for external containers (chests, barrels, etc.). For the player's own inventory use bot.inventory().
Compare position against the block you interacted with before clicking slots: it tells you whether the screen that came up belongs to the container you asked for.
import world, time
world.interact_block(cx, cy, cz)
time.sleep(0.3) # wait for server to open container
container = world.get_container()
if container:
if container.get('position') not in (None, (cx, cy, cz)):
raise RuntimeError(f"Opened the wrong block: {container['position']}")
print(f"Container type: {container['type']}")
for item in container['items']:
print(f" Slot {item['slot']}: {item['count']}x {item['item_id']}")
click_slot(slot_index, button=world.MouseButton.LEFT, click_type=world.ClickType.PICKUP, bot_name="", silent=False)
Click a slot in the currently open container (or player inventory).
Parameters:
slot_index(int) - Slot index using InventoryMenu numbering (see note below)button(intorMouseButton, optional) - Mouse button for normal clicks, or hotbar key number (1-9) forSWAP(default:LEFT)click_type(ClickType, optional) - Click type (default:PICKUP)bot_name(str, optional) - Bot name, defaults to current botsilent(bool, optional) - Suppress the per-click confirmation message in the bot console (default:False). Useful in loops that callclick_slotrepeatedly.
Raises: RuntimeError if bot not found or not online
Slot numbering depends on what is open. Container slots always start at 0, with player inventory slots appended after.
Single chest (27 slots):
| Range | Contents |
|---|---|
| 0–26 | Chest |
| 27–53 | Player main inventory |
| 54–62 | Player hotbar |
Double chest (54 slots):
| Range | Contents |
|---|---|
| 0–53 | Chest |
| 54–80 | Player main inventory |
| 81–89 | Player hotbar |
Player inventory screen (no external container):
| Range | Contents |
|---|---|
| 0 | Crafting output |
| 1–4 | Crafting grid |
| 5–8 | Armor |
| 9–35 | Main inventory |
| 36–44 | Hotbar |
| 45 | Offhand |
Note:
bot.inventory()numbers hotbar as 0–8 and main as 9–35. When a chest is open, player main starts atchest_sizeand hotbar atchest_size + 27, sobot.inventory()slot numbers cannot be used directly - calculate the offset based on container size.
import world
# Shift-click slot 0 of a chest into inventory
world.click_slot(0, click_type=world.ClickType.QUICK_MOVE)
# Swap chest slot 0 with hotbar slot 8 (button = slot index + 1)
world.click_slot(0, button=9, click_type=world.ClickType.SWAP)
# Right-click a slot (split stack)
world.click_slot(5, button=world.MouseButton.RIGHT)
click_widget(screen_id, widget_index, button=world.MouseButton.LEFT, bot_name="")
Click a widget (button, etc.) on the currently open screen.
Parameters:
screen_id(str) - Theidfrombot.get_screen(). Ensures the screen hasn't changed since you read the dump.widget_index(int) - Theindexfrom aGuiWidgetinbot.get_screen().widgetsbutton(MouseButton, optional) - Mouse button (default:LEFT)bot_name(str, optional) - Bot name, defaults to current bot
Raises: RuntimeError if bot not found, not online, or screen_id doesn't match the currently open screen
Note: Only use for non-slot widgets (buttons, checkboxes, etc.). For container slots, use
click_slot().
screen = bot.get_screen()
if screen is not None:
for w in screen.widgets:
if w.text == "Accept" and w.active:
world.click_widget(screen.id, w.index)
break
click_screen(screen_id, x, y, button=world.MouseButton.LEFT, bot_name="")
Click at specific pixel coordinates on the currently open screen. Useful for sliders and other widgets where you need to control the exact click position.
Parameters:
screen_id(str) - Theidfrombot.get_screen(). Ensures the screen hasn't changed since you read the dump.x(float) - Screen pixel X coordinatey(float) - Screen pixel Y coordinatebutton(MouseButton, optional) - Mouse button (default:LEFT)bot_name(str, optional) - Bot name, defaults to current bot
Raises: RuntimeError if bot not found, not online, or screen_id doesn't match the currently open screen
screen = bot.get_screen()
if screen is not None:
for w in screen.widgets:
if w.text.startswith("Max Framerate"):
# Slider range: 10-260 fps
fraction = (90 - 10) / (260 - 10)
click_x = w.x + fraction * w.width
world.click_screen(screen.id, click_x, w.y + w.height / 2)
break
type_text(screen_id, text, bot_name="")
Type text into the currently focused element on the open screen (sign lines, chat, edit boxes). Sends individual charTyped events for each character.
Parameters:
screen_id(str) - Theidfrombot.get_screen(). Ensures the screen hasn't changed since you read the dump.text(str) - The text to typebot_name(str, optional) - Bot name, defaults to current bot
Raises: RuntimeError if bot not found or not online
screen = bot.get_screen()
if screen and "SignEditScreen" in screen.screen_class:
world.type_text(screen.id, "Hello world")
world.press_key(screen.id, world.Key.ENTER)
press_key(screen_id, key_code, modifiers=0, bot_name="")
Press a key on the currently open screen (sends keyPressed + keyReleased).
Parameters:
screen_id(str) - Theidfrombot.get_screen(). Ensures the screen hasn't changed since you read the dump.key_code(int) - GLFW key code - useworld.Key.*constantsmodifiers(int, optional) - GLFW modifier flags - useworld.KeyMod.*constants, default0bot_name(str, optional) - Bot name, defaults to current bot
Raises: RuntimeError if bot not found or not online
world.Key constants:
| Group | Constants |
|---|---|
| Navigation | UP, DOWN, LEFT, RIGHT, HOME, END, PAGE_UP, PAGE_DOWN, INSERT, DELETE |
| Control | ESCAPE, ENTER, TAB, BACKSPACE, SPACE, CAPS_LOCK, SCROLL_LOCK, NUM_LOCK, PRINT_SCREEN, PAUSE, MENU |
| Letters | A-Z |
| Digits | NUM_0-NUM_9 |
| Punctuation | APOSTROPHE, COMMA, MINUS, PERIOD, SLASH, SEMICOLON, EQUAL, LEFT_BRACKET, BACKSLASH, RIGHT_BRACKET, GRAVE_ACCENT |
| Function | F1-F12 |
| Keypad | KP_0-KP_9, KP_DECIMAL, KP_DIVIDE, KP_MULTIPLY, KP_SUBTRACT, KP_ADD, KP_ENTER, KP_EQUAL |
| Modifiers | LEFT_SHIFT, LEFT_CONTROL, LEFT_ALT, LEFT_SUPER, RIGHT_SHIFT, RIGHT_CONTROL, RIGHT_ALT, RIGHT_SUPER |
world.KeyMod constants: SHIFT, CONTROL, ALT, SUPER, CAPS_LOCK, NUM_LOCK
screen = bot.get_screen()
if screen is not None:
# Select all text and delete it
world.press_key(screen.id, world.Key.A, world.KeyMod.CONTROL)
world.press_key(screen.id, world.Key.DELETE)
close_container(bot_name="")
Close the currently open container. The client reports the close as soon as it processes the command, rather than leaving it to be noticed when the screen next changes, so get_container() clears within a tick. The call itself does not wait for that - it returns once the command has been sent - so a get_container() immediately afterwards can still answer with the container that was open.
Raises: RuntimeError if bot not found or not online
world.close_container()
open_inventory(bot_name="")
Open the player's own inventory screen. If a container is open it is closed first, so click_slot() afterwards uses the player inventory's slot numbering. Like close_container, the call returns once the command has been sent and not when the screen is up.
Raises: RuntimeError if bot not found or not online
world.open_inventory()
Enums
world.MouseButton
| Value | Description |
|---|---|
LEFT |
Left mouse button |
RIGHT |
Right mouse button |
MIDDLE |
Middle mouse button |
world.ClickType
| Value | Description |
|---|---|
PICKUP |
Pick up / place item (left or right click) |
QUICK_MOVE |
Shift-click (move to/from inventory) |
SWAP |
Swap with hotbar slot (button = hotbar index 0–8) |
CLONE |
Clone item (creative mode middle-click) |
THROW |
Throw item out of inventory |
QUICK_CRAFT |
Quick craft drag operation |
PICKUP_ALL |
Double-click to collect all matching items |
world.ContainerType
| Value | |
|---|---|
PLAYER_INVENTORY |
CRAFTING_TABLE |
CHEST |
ENCHANTING_TABLE |
ENDER_CHEST |
ANVIL |
SHULKER_BOX |
BREWING_STAND |
FURNACE |
VILLAGER_TRADE |
BLAST_FURNACE |
HORSE_INVENTORY |
SMOKER |
HOPPER |
DISPENSER |
DROPPER |
BEACON |
OTHER |
Chunk Information
loaded_chunk_count(bot_name="")
Get the number of chunks currently loaded in memory.
Parameters:
bot_name(str, optional) - Bot name, defaults to current bot
Returns: int - Number of loaded chunks
Raises: RuntimeError if bot not found or not online
count = world.loaded_chunk_count()
print(f"Loaded chunks: {count}")
loaded_chunks(bot_name="")
Get list of all loaded chunk positions.
Parameters:
bot_name(str, optional) - Bot name, defaults to current bot
Returns: list[tuple] - List of chunk positions as (chunk_x, chunk_z) tuples
Raises: RuntimeError if bot not found or not online
chunks = world.loaded_chunks()
print(f"Loaded {len(chunks)} chunks:")
for cx, cz in chunks:
print(f" Chunk ({cx}, {cz}) - blocks ({cx*16}, {cz*16}) to ({cx*16+15}, {cz*16+15})")
memory_usage(bot_name="")
Get the total memory used by world data storage.
Parameters:
bot_name(str, optional) - Bot name, defaults to current bot
Returns: int - Memory usage in bytes
Raises: RuntimeError if bot not found or not online
memory = world.memory_usage()
print(f"World data memory usage: {memory / 1024 / 1024:.2f} MB")
Section Observation
Bulk primitives for scripts that need to know what part of the world changed without walking every block themselves - incremental mirroring to an external service, minimap or render invalidation, re-scanning only what moved.
A section is a 16x16x16 block cube, addressed by
(chunk_x, chunk_z, section_y) where section_y is the absolute section index
(block_y >> 4, so y=118 is section 7 and y=-56 is section -4).
changed_sections(bot_name="", since=None, dimension="", digest=False, limit=0, digest_prefix=b"")
Get the chunk sections whose content changed since a previous call.
Change tracking is fed by chunk loads, block updates and chunk unloads. A freshly loaded chunk counts as changed in all its sections; light, biome and block entity changes do not count.
Parameters:
bot_name(str, optional) - Bot name, defaults to current botsince(int | None, optional) -tokenfrom a previous call.Nonereturns every currently tracked sectiondimension(str, optional) - Only report chunks of this dimension (e.g."minecraft:the_nether"). Empty string reports all dimensionsdigest(bool, optional) - Also compute a content digest per section. This hashes every returned section, so leave it off when you only need to know which sections movedlimit(int, optional) - Cap on how many sections to return.0is unlimited. Use it to page: the returnedtokenresumes exactly where the call stoppeddigest_prefix(bytes, optional) - Domain-separation tag hashed ahead of the section content. Defaults tob"", the bare content hash. Digests only compare equal when computed with the same prefix, so pass one value consistently - and give each distinct consumer or format its own tag if their encodings could ever share a content-addressed store
Returns: SectionChanges with:
.token(int) - Pass assincenext time.truncated(bool) -limitwas hit and more sections are pending.sections(list[SectionChange]) - Iterating theSectionChangesdirectly iterates these.dropped(list[tuple[int, int, int]]) - Keys(chunk_x, chunk_z, section_y)that changed aftersincebut whose chunk unloaded before they could be listed (or, in practice never, that the manager could not encode), so this caller will never see them unless the chunk loads again. Each key appears once however often it changed. At most 4096 keys; see Token semantics.dropped_total(int) - How many keys were dropped, uncapped.dropped_incomplete(bool) -Truewhen some drops may be missing from.droppedand.dropped_total:sinceis older than the manager's record of dropped sections still reaches, or it is not a token from this bot
Each SectionChange has .chunk_x, .chunk_z, .section_y, .key (the three
as a tuple, ready to pass to export_sections) and .digest - 32 bytes when
digest=True, otherwise None.
The digest is BLAKE2b-256 over digest_prefix followed by the section's
canonical palette+indices encoding, covering block states only (light, biomes
and block entities are excluded). No
coordinate goes into it, so byte-identical terrain digests identically wherever
it occurs - same content at a different height, column or dimension is the same
digest, and palette ordering never matters. That is what lets a content-addressed
store keep one copy, and what makes the digest usable as a cache key for content
rather than for a place.
The work runs with the GIL released and the hashing is spread over worker threads, so a script that polls several bots from its own threads, one per bot, polls them in parallel.
Raises: RuntimeError if bot not found or not online
token = None
while True:
changes = world.changed_sections("MyBot", since=token, limit=512)
token = changes.token
for section in changes:
rescan(section.chunk_x, section.chunk_z, section.section_y)
if changes.dropped_total or changes.dropped_incomplete:
utils.log(f"missed {changes.dropped_total} sections, e.g. {changes.dropped[:3]}")
if not changes.truncated:
time.sleep(1)
Token semantics
Tokens are per-bot. Passing one bot's token to another bot returns a full
snapshot rather than a wrong delta, and so does a token from a bot that was
removed and re-added (with .dropped_incomplete set, since there is no telling
what it missed). Tokens stay valid across a reconnect: the world clears, the
sections still pending at the disconnect come back in .dropped, and the
sections re-report as chunks load again.
Reading never consumes, so any number of independent pollers can each hold their
own token without affecting each other - each gets its own .sections and its
own .dropped.
The report is deliberately conservative in one direction only: it can tell you a
section changed when the content happens to be identical (a chunk reload
re-reports every section), so compare digests if that matters. It will not miss
a change to a section that is still loaded. A section whose chunk unloads before
you poll cannot be listed - its content is gone from memory - so it is reported
in .dropped instead, and reloading that chunk marks all of it again. So a
poller that falls behind learns exactly what it missed:
- A section is dropped for you only if it changed after your
sinceand its chunk unloaded before this call. One you were already given (it changed at or beforesince) is never reported as dropped, even when its chunk unloads later. - A section that changed again (a block update) before its chunk unloaded is reported once.
- A chunk that unloaded and then loaded again before you polled is not dropped:
its sections are in
.sections. - With
limit, drops belong to the call whose token range covers them, so paging never loses or repeats one. - With
dimension, only that dimension's drops are reported. since=Nonereports no unloaded sections: there is no earlier poll to have missed anything since.
The manager keeps the drops of roughly the last 32,000 sections per bot, which
at a flying bot's rate is about 15 seconds. A poller whose token is older than
that gets .dropped_incomplete = True: the count it gets is a lower bound.
get_section(chunk_x, chunk_z, section_y, bot_name="", dimension="", digest_prefix=b"")
Get one section's blocks in canonical form.
Parameters:
chunk_x,chunk_z,section_y(int) - Section to readbot_name(str, optional) - Bot name, defaults to current botdimension(str, optional) - Only read if the chunk is in this dimensiondigest_prefix(bytes, optional) - Domain-separation tag for.digest, as inchanged_sections
Returns: Section | None - None when the section is not loaded (or fails
the dimension filter). Fields:
.palette(list[str]) - Block state strings, sorted and containing exactly the states the indices reference.indices(bytes) - 4096 unsigned 16-bit little-endian palette indices in YZX order (index = y*256 + z*16 + x).dimension(str),.chunk_x,.chunk_z,.section_y(int).digest(bytes) - The same 32-byte content digestchanged_sectionsreports when called with the samedigest_prefix
section = world.get_section(10, -3, 4)
if section:
indices = struct.unpack("<4096H", section.indices)
block = section.palette[indices[y * 256 + z * 16 + x]]
export_sections(keys, bot_name="", dimension="")
Serialize chunk sections into a single upload payload.
The payload is the manager's bulk section framing: a little-endian frame count
followed by one frame per section (dimension string, section key, and the
section's canonical palette+indices blob). It is uncompressed and
self-describing; a receiver that hashes a frame's blob under its own
digest_prefix gets exactly what changed_sections reported for the same
content and prefix.
get_section() gives you the same content without the framing.
Parameters:
keys(list[tuple]) - Sections to export, as(chunk_x, chunk_z, section_y)bot_name(str, optional) - Bot name, defaults to current botdimension(str, optional) - Only export chunks of this dimension. Empty string exports all
Returns: bytes - The framed payload. Sections that are no longer loaded
(or fail the dimension filter) are silently omitted, so the frame count can be
lower than len(keys); callers that need exactness should compare counts
Raises: RuntimeError if bot not found or not online, if a key is not a
3-tuple, or if more than 4096 keys are passed in one call (batch instead - one
section is about 8.3 KB, so the cap is roughly a 34 MB payload)
changes = world.changed_sections("MyBot", digest=True, limit=4096)
payload = world.export_sections([s.key for s in changes], bot_name="MyBot")
# POST payload wherever it needs to go
Usage Examples
Mining Helper
import time
# Check if bot has a pickaxe
inventory = bot.inventory()
has_pickaxe = False
if inventory:
for item in inventory:
if item and "pickaxe" in item["item_id"].lower():
has_pickaxe = True
print(f"Found {item['display_name']} in slot {item['slot']}")
break
if not has_pickaxe:
print("No pickaxe found in inventory! Cannot mine.")
else:
# Find diamond ore
pos = bot.position()
diamonds = world.find_blocks("minecraft:diamond_ore",
pos["x"], pos["y"], pos["z"],
radius=64)
if diamonds:
print(f"Found {len(diamonds)} diamond ore blocks")
# Sort by distance
diamonds.sort(key=lambda p: (p[0]-pos["x"])**2 + (p[1]-pos["y"])**2 + (p[2]-pos["z"])**2)
# Mine each one
for x, y, z in diamonds:
print(f"Mining diamond at ({int(x)}, {int(y)}, {int(z)})")
# Go near the block
baritone.goto(x, y, z)
# Wait for arrival
while True:
p = bot.position()
if abs(p["x"] - x) < 3 and abs(p["z"] - z) < 3:
break
time.sleep(0.5)
# Break the block using sel cleararea command
# Set selection for the single block
baritone.command(f"sel pos1 {int(x)} {int(y)} {int(z)}")
baritone.command(f"sel pos2 {int(x)} {int(y)} {int(z)}")
baritone.command("sel cleararea")
# Wait for block to be broken
while True:
block = world.get_block(int(x), int(y), int(z))
if block and "air" in block:
break
time.sleep(0.5)
# Clear selections for next iteration
baritone.command("sel clear")
else:
print("No diamonds found nearby")
Container Finder
# Find all chests
pos = bot.position()
chests = world.find_blocks("minecraft:chest",
pos["x"], pos["y"], pos["z"],
radius=100)
print(f"Found {len(chests)} chests")
# Go to nearest chest and open it
if chests:
nearest = min(chests, key=lambda p: (p[0]-pos["x"])**2 + (p[1]-pos["y"])**2 + (p[2]-pos["z"])**2)
x, y, z = nearest
baritone.goto(x, y+1, z)
# Wait for arrival...
world.interact_block(int(x), int(y), int(z))
print("Opened chest!")
Block Scanner
# Scan area and categorize blocks
pos = bot.position()
radius = 32
ores = []
containers = []
fluids = []
chunks = world.loaded_chunks()
print(f"Scanning {len(chunks)} loaded chunks...")
# Count different block types
for cx, cz in chunks:
# Check if chunk is in range
chunk_center_x = cx * 16 + 8
chunk_center_z = cz * 16 + 8
dist = ((chunk_center_x - pos["x"])**2 + (chunk_center_z - pos["z"])**2)**0.5
if dist > radius:
continue
# Scan blocks in chunk
for x in range(cx * 16, cx * 16 + 16):
for z in range(cz * 16, cz * 16 + 16):
for y in range(-64, 320):
block = world.get_block(x, y, z)
if not block:
continue
if "ore" in block:
ores.append((x, y, z, block))
elif any(c in block for c in ["chest", "barrel", "shulker"]):
containers.append((x, y, z, block))
elif any(f in block for f in ["water", "lava"]):
fluids.append((x, y, z, block))
print(f"Found {len(ores)} ore blocks")
print(f"Found {len(containers)} containers")
print(f"Found {len(fluids)} fluid blocks")
Recipe Registry
get_recipe(recipe_id, bot_name="")
Get recipe data by its exact recipe ID.
Parameters:
recipe_id(str) - Exact recipe ID (e.g.,"minecraft:gold_ingot_from_gold_block")bot_name(str, optional) - Bot name, defaults to current bot
Returns: dict with recipe data, or None if not found
recipe = world.get_recipe("minecraft:stick")
if recipe:
print(f"Makes {recipe['result_count']}x {recipe['result_item']}")
for ing in recipe['ingredients']:
print(f" Slot {ing['slot']}: {ing['items']} x{ing['count']}")
Recipe dict fields:
| Field | Type | Description |
|---|---|---|
recipe_id |
str |
The recipe's unique ID |
type |
str |
Recipe type (e.g., "minecraft:crafting_shaped") |
result_item |
str |
Output item ID |
result_count |
int |
Number of items produced |
is_shapeless |
bool |
Whether the recipe is shapeless |
ingredients |
list |
List of ingredient dicts (see below) |
experience |
float |
XP granted (smelting recipes only) |
cooking_time |
int |
Ticks to smelt (smelting recipes only) |
Each ingredient dict:
| Field | Type | Description |
|---|---|---|
slot |
int |
Grid slot index (1-9 for 3×3, 1-4 for 2×2) |
count |
int |
Amount required |
items |
list[str] |
Accepted item IDs (e.g., any log type) |
get_recipes_for(item_id, bot_name="")
Get all recipes that produce a given item.
Parameters:
item_id(str) - Item ID to look up (e.g.,"minecraft:gold_ingot")bot_name(str, optional) - Bot name, defaults to current bot
Returns: list[dict] - List of recipe dicts (same format as get_recipe()). Empty list if no recipes found.
recipes = world.get_recipes_for("minecraft:gold_ingot")
print(f"{len(recipes)} way(s) to craft gold ingot:")
for r in recipes:
print(f" {r['recipe_id']}")
# e.g.:
# minecraft:gold_ingot_from_nuggets
# minecraft:gold_ingot_from_gold_block
get_item_info(item_id, bot_name="")
Get item metadata from the item registry.
Parameters:
item_id(str) - Item ID (e.g.,"minecraft:diamond_sword")bot_name(str, optional) - Bot name, defaults to current bot
Returns: dict or None if item not found
| Field | Type | Description |
|---|---|---|
item_id |
str |
Item ID |
display_name |
str |
Human-readable item name (e.g., "Diamond Pickaxe") |
max_stack_size |
int |
Maximum stack size (usually 1, 16, or 64) |
max_damage |
int |
Maximum durability (0 for non-damageable items) |
info = world.get_item_info("minecraft:diamond_pickaxe")
if info:
print(f"Name: {info['display_name']}")
print(f"Max durability: {info['max_damage']}")
print(f"Stack size: {info['max_stack_size']}")
get_all_recipes(bot_name="")
Get a list of all known recipe IDs.
Parameters:
bot_name(str, optional) - Bot name, defaults to current bot
Returns: list[str] - All recipe IDs
recipes = world.get_all_recipes()
# Filter to just pickaxe recipes
pickaxe_recipes = [r for r in recipes if "pickaxe" in r]
plan_recursive_craft(item_id, count=1, bot_name="")
Plan the full crafting tree for an item, including all intermediate steps, based on the bot's current inventory.
Parameters:
item_id(str) - Item to craft (e.g.,"minecraft:piston")count(int, optional) - How many to craft (default: 1)bot_name(str, optional) - Bot name, defaults to current bot
Returns: dict
| Field | Type | Description |
|---|---|---|
success |
bool |
Whether a complete plan was found |
error |
str |
Error message if success is False |
steps |
list |
Ordered crafting steps (see below) |
raw_materials |
dict |
Items needed that cannot be crafted: {item_id: count} |
leftovers |
dict |
Items left over after all steps: {item_id: count} |
Each step dict:
| Field | Type | Description |
|---|---|---|
recipe_id |
str |
Recipe to use for this step |
output_item |
str |
Item produced |
output_count |
int |
Items produced per craft |
times |
int |
How many times to run this recipe |
inputs |
dict |
Items consumed: {item_id: count} |
plan = world.plan_recursive_craft("minecraft:piston", 4)
if not plan['success']:
utils.error(f"Can't plan: {plan['error']}")
else:
utils.log(f"Raw materials needed: {dict(plan['raw_materials'])}")
for step in plan['steps']:
utils.log(f"Craft {step['output_item']} x{step['times']} (recipe: {step['recipe_id']})")
Note
Pass step['recipe_id'] to get_recipe() when executing plan steps - do not re-look up by item ID, as there may be multiple recipes for the same output.
Entity Tracking
Live entity data is pushed from the Minecraft client every tick (only changed/new/removed entities).
Entity dict schema
| Key | Type | Present when |
|---|---|---|
entity_id |
int |
always |
uuid |
str |
always |
type |
str |
always (e.g. "minecraft:item", "minecraft:zombie") |
x, y, z |
float |
always |
yaw, pitch |
float |
always - yaw is normalised to -180–180 |
vel_x, vel_y, vel_z |
float |
always |
health |
float |
living entities |
max_health |
float |
living entities |
item |
item dict | type == "minecraft:item" (dropped item entities) |
player_name |
str |
player entities |
owner_entity_id |
int |
projectiles - the owner's entity id, when the server named one |
owner_uuid |
str |
projectiles - when this client has resolved that id |
The item sub-dict follows the standard item dict schema.
Projectile ownership
Either field can be absent:
owner_entity_id- the server named no owner, usually because the owner was offline when the projectile last entered range. It fills in the next time that chunk is re-sent while the owner is online.owner_uuid- this client has never had the owner in range. Not the same as "no owner".
Entity ids are reassigned on every login, so match them on the spot rather than storing them. On 1.21.4 the server resolves an owner only within the owner's own dimension, so a cross-dimension owner sends no id at all; 1.21.5 and later, including 26.1, are dimension agnostic.
To test against the current bot, compare with bot.entity_id():
import bot, world
mine = bot.entity_id()
pearls = world.find_entities_near(px, py, pz, 2, type="minecraft:ender_pearl")
if any(p.get("owner_entity_id") == mine for p in pearls):
world.interact_block(tx, ty, tz)
To name any of the manager's bots, build the mapping yourself - rebuild it per use, since the ids change on reconnect:
import bot
owners = {eid: b["name"] for b in bot.list_all()
if (eid := bot.entity_id(b["name"])) is not None}
name = owners.get(pearl.get("owner_entity_id"), "someone else")
world.entities(bot_name="")
Returns a list of all currently tracked entity dicts.
Holds what the bot can currently see: entities in loaded chunks of the dimension it is
in, one entry each. Empty (not stale) when the bot is attached to the manager but not on
a server. uuid is stable for the life of the world; entity_id is reassigned whenever
the entity's chunk reloads, so key on uuid for anything you keep.
import world
for e in world.entities():
print(e['type'], e['x'], e['y'], e['z'])
world.find_entities_near(x, y, z, radius, type="", bot_name="")
Returns entities within radius blocks of (x, y, z). Optional type is a prefix filter on the entity type string (e.g. "minecraft:item" or "minecraft:" for all vanilla entities).
import bot, world
pos = bot.position()
items = world.find_entities_near(pos['x'], pos['y'], pos['z'], 8, type='minecraft:item')
for item_ent in items:
print(item_ent['item']['item_id'], 'at', item_ent['x'], item_ent['y'], item_ent['z'])
Wait for a specific item to drop after breaking a block
import bot, world, time
MINE_ITEMS = {'minecraft:diamond', 'minecraft:ancient_debris'}
def wait_for_drops(bx, by, bz, timeout=3.0):
deadline = time.time() + timeout
while time.time() < deadline:
items = world.find_entities_near(bx, by, bz, 4, type='minecraft:item')
if any(i['item']['item_id'] in MINE_ITEMS for i in items):
return items
time.sleep(0.1)
return []
Check nearby mob health
import bot, world
pos = bot.position()
mobs = [e for e in world.find_entities_near(pos['x'], pos['y'], pos['z'], 16)
if 'health' in e and e['type'] != 'minecraft:player']
for mob in mobs:
print(mob['type'], f"{mob['health']:.1f}/{mob['max_health']:.1f} HP")
Update strategy
The Java client sends EntityUpdate messages only when entities change - new arrivals, position/rotation changes, and removals.
On server disconnect, all entity data is cleared so stale entities never persist across reconnections.
Light
get_light(x, y, z, use_disk=False, dimension="", bot_name="")
Get the light levels at the specified block position.
Parameters:
x(float) - X coordinatey(float) - Y coordinatez(float) - Z coordinateuse_disk(bool, optional) - IfTrueand the chunk is not loaded in memory, read light data from the saved.mcaregion file on disk (default:False)dimension(str, optional) - Dimension string (e.g."minecraft:overworld","minecraft:the_nether"). Defaults to the bot's current dimension. Memory is read when the loaded chunk at that position belongs to this dimension; otherwise, withuse_disk=True, the saved world is read instead.bot_name(str, optional) - Bot name, defaults to current bot
Raises: RuntimeError if bot not found or not online
Returns: dict or None if the chunk is not loaded (and not on disk when use_disk=True)
| Key | Type | Description |
|---|---|---|
block |
int |
Block light level (0-15, from torches, lava, etc.) |
sky |
int |
Raw sky light level (0-15, always 0 in nether/end) |
Light data is captured on chunk load and updated as the server sends light updates (e.g. from placing or removing light sources).
Note: sky is the raw stored sky light, not the internal sky light used for mob spawning calculations. Internal sky light also depends on time of day and weather, which are not factored in here.
light = world.get_light(x, y, z)
if light:
utils.log(f"Block light: {light['block']}, Sky light: {light['sky']}")
# A position with no block light and no sky access is fully dark
if light['block'] == 0 and light['sky'] == 0:
utils.log("No light sources reach this block")
# Read light from a saved but unloaded chunk
light = world.get_light(5000, 64, 5000, use_disk=True)
Block Solidity
is_solid(block_state, face=Direction.UP, bot_name="")
Check if a block state has a fully solid face in the given direction. Useful for finding mob spawn surfaces, attachment points, and pathfinding.
Parameters:
block_state(str) - Block state string (e.g."minecraft:stone"or"minecraft:oak_slab[type=top,waterlogged=false]")face(Direction, optional) - Face direction to check, defaults toDirection.UPbot_name(str, optional) - Bot name, defaults to current bot
Returns: bool, or None if the block registry is not loaded
Direction
Enum for block face directions, matching Minecraft's internal direction ordering.
| Value | Description |
|---|---|
Direction.DOWN |
Bottom face |
Direction.UP |
Top face |
Direction.NORTH |
North face (-Z) |
Direction.SOUTH |
South face (+Z) |
Direction.WEST |
West face (-X) |
Direction.EAST |
East face (+X) |
# Check if a block has a solid top face (mob spawn surface)
block = world.get_block(x, y, z)
if world.is_solid(block):
utils.log(f"{block} has a solid top face")
# Check a specific face
if world.is_solid("minecraft:oak_stairs[facing=north,half=bottom,shape=straight]", world.Direction.NORTH):
utils.log("North face is solid")
# Find all spawnable locations in a radius
pos = bot.position()
cx, cy, cz = int(pos['x']), int(pos['y']), int(pos['z'])
dark_air = world.find_blocks("minecraft:air", cx, cy, cz, 64,
min_block_light=0, max_block_light=0)
spawnable = []
for (x, y, z) in dark_air:
surface = world.get_block(x, y - 1, z)
head = world.get_block(x, y + 1, z)
if surface and world.is_solid(surface) and head == "minecraft:air":
spawnable.append((x, y, z))
utils.log(f"Found {len(spawnable)} spawnable locations")
Weather
get_weather(bot_name="")
Get the current weather state.
Parameters:
bot_name(str, optional) - Bot name, defaults to current bot
Returns: dict or None if bot is offline
| Key | Type | Description |
|---|---|---|
is_raining |
bool |
Whether it is raining |
is_thundering |
bool |
Whether there is a thunderstorm |
rain_level |
float |
Rain intensity (0.0-1.0) |
thunder_level |
float |
Thunder intensity (0.0-1.0) |
Note: Weather only applies to the overworld. In the nether or end, values will show no rain since those dimensions have no weather system.
weather = world.get_weather()
if weather and weather['is_raining']:
utils.log(f"It's raining (intensity: {weather['rain_level']:.2f})")
if weather['is_thundering']:
utils.log("Thunderstorm!")
Notes
- Chunk Loading: Blocks can only be queried in loaded chunks. The client automatically loads chunks as the bot moves around.
- Performance: Block queries are O(1) for
get_block(). Searches likefind_blocks()are optimized but still need to scan blocks, so prefer smaller radii when possible. - Round trips:
get_block()and the other block queries read the manager's own world copy and are cheap. Reach checks are not: they ask the game client, which only processes messages once per tick, so eachcan_reach_block()/can_reach_block_from()costs at least ~50 ms. Scanning hundreds of positions one at a time takes tens of seconds; usecan_reach_blocks()to send the whole set at once and get it back in a few ticks. - Block State Format: Block states are returned as strings in the format
"minecraft:block_name[property=value,...]". Use Python string operations to check block types.