Complex Set Block ExampleΒΆ

import amulet
from amulet.api.block import Block
from amulet.utils.world_utils import block_coords_to_chunk_coords
from amulet_nbt import StringTag, IntTag

# load the level
level = amulet.load_level("level")

# lets set the block at 15, 70, 17 and 15, 72, 17 to be green glazed
# terracotta using the Java and Bedrock formats respectively.

# Note that the format you define the block in does not need to match the world you are writing the block to.
# The translator will handle block translating it to the correct format.

# First we need to define the Block object for each of the formats.
# The translator holds a specification for each block for each version which the UI is built off.
# You can also look at the Minecraft wiki to find the blockstate format.
java_block = Block(
    "minecraft", "green_glazed_terracotta", {"facing": StringTag("south")}
)
bedrock_block = Block(
    "minecraft", "green_glazed_terracotta", {"facing_direction": IntTag(3)}
)

# Then we can translate the block to the universal format.
(
    universal_block_1,
    universal_block_entity_1,
    universal_extra_1,
) = level.translation_manager.get_version("java", (1, 16, 5)).block.to_universal(
    java_block
    # if your block has a block entity you can give it as a second input here.
)
# Block(universal_minecraft:glazed_terracotta[color="green",facing="south"]), None, False

# Use the translator to convert the block to the universal format.
(
    universal_block_2,
    universal_block_entity_2,
    universal_extra_2,
) = level.translation_manager.get_version("bedrock", (1, 16, 210)).block.to_universal(
    bedrock_block
    # if your block has a block entity you can give it as a second input here.
)
# Block(universal_minecraft:glazed_terracotta[color="green",facing="south"]), None, False

# The boolean output at the end can for the most part be ignored but lets explain the use of it.
# There are certain cases that require more than just the block state to fully define the block.
# If the translator is not able to fully define the translation it will return that value as True.
# The calling code can then give it more data to fully define the translation.

# Blocks with a block entity is a simple example.
# Doing the translation without the block entity will use the default NBT data and return the value as True.
# The calling code can then call the translator again with the block entity.

# Another example is multi-block structures such as doors in older versions of the game.
# Doors used to split the state data over the bottom and top block.
# The translator has the ability to reach back into the world to inspect neighbour blocks to get this extra data.
# This is done through a callback function given to the translator.

# Now we now have the universal block state converted from both Bedrock and Java. Lets set the data in the level.

# First we need to register the Block objects with the block palette to get the runtime block id.
block_id_1 = level.block_palette.get_add_block(universal_block_1)
block_id_2 = level.block_palette.get_add_block(universal_block_2)
# In this case the two translations gave the same universal block object so the block ids are the same.

# This code was documented in the previous example so I won't do it again.
x, z = 15, 17
cx, cz = block_coords_to_chunk_coords(x, z)
chunk = level.get_chunk(cx, cz, "minecraft:overworld")
offset_x, offset_z = x - 16 * cx, z - 16 * cz

# set the blocks into the world.
chunk.blocks[offset_x, 70, offset_z] = block_id_1
chunk.blocks[offset_x, 72, offset_z] = block_id_2

# Set the block entities.
for block_entity, location in (
    (universal_block_entity_1, (15, 70, 17)),
    (universal_block_entity_2, (15, 72, 17)),
):
    if block_entity is not None:
        # if there is a block entity to go along with the block we just set,
        # add it to the chunks block entities.
        chunk.block_entities[location] = block_entity
    elif location in chunk.block_entities:
        # if there is no new block entity and there was one present from the previous block, remove it.
        del chunk.block_entities[location]

# we have changed the chunk data so we need to set this value otherwise it won't get saved.
chunk.changed = True

# save and close the world
level.save()
level.close()