Skip to content

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 coordinate
  • y (int) - Block Y coordinate
  • z (int) - Block Z coordinate
  • use_disk (bool, optional) - If True and the chunk is not loaded in memory, read the block from the saved .mca region 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, with use_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 matches
  • center_x (float) - Search center X coordinate
  • center_y (float) - Search center Y coordinate
  • center_z (float) - Search center Z coordinate
  • radius (int) - Search radius in blocks
  • min_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 as get_block
  • use_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 as get_block
  • use_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 coordinate
  • y (int) - Block Y coordinate
  • z (int) - Block Z coordinate
  • use_disk (bool, optional) - If True and no in-memory data exists, read from the saved .mca file (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:

  • items wins over nbt["Items"]. Container contents in nbt are the chunk-load snapshot and are never refreshed afterwards, while items comes from the container packet and is current.
  • The conversion is lossy, because it is meant for reading: byte, short, int and long all become int; float and double become float; byte_array, int_array and long_array all become list[int]; compounds become dict and lists become list.
# 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) - If True and the chunk is not loaded, read from the saved .mca file (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, with use_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 entity
  • center_x (float) - Search center X coordinate
  • center_z (float) - Search center Z coordinate
  • radius (float) - Horizontal search radius in blocks; the search is a disc, every y is included
  • dimension (str, optional) - Dimension string. Defaults to the bot's current dimension; same semantics as get_block
  • use_disk (bool, optional) - Also search saved chunks that are not loaded (default: False)
  • limit (int, optional) - Return at most this many, nearest first. 0 means 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 coordinate
  • y (int) - Block Y coordinate
  • z (int) - Block Z coordinate
  • face (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 id
  • sneak (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 coordinate
  • y (int) - Block Y coordinate
  • z (int) - Block Z coordinate
  • sneak (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 than AUTO, 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 coordinates
  • sneak (bool, optional) - Use crouching eye height (default: False)
  • face (world.BlockFace, optional) - Check reachability of a specific face only. AUTO checks 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:

  • ValueError if timeout is not positive
  • RuntimeError if 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)
  • TimeoutError if the client did not answer within timeout
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 check
  • sneak (bool, optional) - Use crouching eye height (default: False)
  • face (world.BlockFace, optional) - Check reachability of a specific face only. AUTO checks 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:

  • ValueError if timeout is not positive
  • RuntimeError if 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)
  • TimeoutError if the client did not answer within timeout
# 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 (list or any sequence) - Each entry is either an (x, y, z) tuple or a world.ReachQuery
  • sneak (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. AUTO checks 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:

  • ValueError if an entry is neither a 3-tuple nor a ReachQuery, if a coordinate is not a number, if timeout is not positive, or if the batch is too large to fit in a single request (roughly 210,000 queries)
  • RuntimeError if 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)
  • TimeoutError if the client did not answer within timeout

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 (int or MouseButton, optional) - Mouse button for normal clicks, or hotbar key number (1-9) for SWAP (default: LEFT)
  • click_type (ClickType, optional) - Click type (default: PICKUP)
  • bot_name (str, optional) - Bot name, defaults to current bot
  • silent (bool, optional) - Suppress the per-click confirmation message in the bot console (default: False). Useful in loops that call click_slot repeatedly.

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 at chest_size and hotbar at chest_size + 27, so bot.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) - The id from bot.get_screen(). Ensures the screen hasn't changed since you read the dump.
  • widget_index (int) - The index from a GuiWidget in bot.get_screen().widgets
  • button (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) - The id from bot.get_screen(). Ensures the screen hasn't changed since you read the dump.
  • x (float) - Screen pixel X coordinate
  • y (float) - Screen pixel Y coordinate
  • button (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) - The id from bot.get_screen(). Ensures the screen hasn't changed since you read the dump.
  • text (str) - The text to type
  • bot_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) - The id from bot.get_screen(). Ensures the screen hasn't changed since you read the dump.
  • key_code (int) - GLFW key code - use world.Key.* constants
  • modifiers (int, optional) - GLFW modifier flags - use world.KeyMod.* constants, default 0
  • bot_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 bot
  • since (int | None, optional) - token from a previous call. None returns every currently tracked section
  • dimension (str, optional) - Only report chunks of this dimension (e.g. "minecraft:the_nether"). Empty string reports all dimensions
  • digest (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 moved
  • limit (int, optional) - Cap on how many sections to return. 0 is unlimited. Use it to page: the returned token resumes exactly where the call stopped
  • digest_prefix (bytes, optional) - Domain-separation tag hashed ahead of the section content. Defaults to b"", 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 as since next time
  • .truncated (bool) - limit was hit and more sections are pending
  • .sections (list[SectionChange]) - Iterating the SectionChanges directly iterates these
  • .dropped (list[tuple[int, int, int]]) - Keys (chunk_x, chunk_z, section_y) that changed after since but 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) - True when some drops may be missing from .dropped and .dropped_total: since is 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 since and its chunk unloaded before this call. One you were already given (it changed at or before since) 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=None reports 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 read
  • bot_name (str, optional) - Bot name, defaults to current bot
  • dimension (str, optional) - Only read if the chunk is in this dimension
  • digest_prefix (bytes, optional) - Domain-separation tag for .digest, as in changed_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 digest changed_sections reports when called with the same digest_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 bot
  • dimension (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 coordinate
  • y (float) - Y coordinate
  • z (float) - Z coordinate
  • use_disk (bool, optional) - If True and the chunk is not loaded in memory, read light data from the saved .mca region 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, with use_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 to Direction.UP
  • bot_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 like find_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 each can_reach_block() / can_reach_block_from() costs at least ~50 ms. Scanning hundreds of positions one at a time takes tens of seconds; use can_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.