def sub_chunk_count(self, sub_chunk_size: int = 16) -> int: cy_min, cy_max = block_coords_to_chunk_coords( self.min_y, self.max_y - 1, sub_chunk_size=sub_chunk_size, ) return (cy_max + 1 - cy_min) * self.chunk_count()
def get_version_block( self, x: int, y: int, z: int, dimension: Dimension, version: VersionIdentifierType, ) -> Tuple[Union[Block, Entity], Optional[BlockEntity]]: """ Get a block at the specified location and convert it to the format of the version specified Note the odd return format. In most cases this will return (Block, None) or (Block, BlockEntity) but in select cases like item frames may return (Entity, None) :param x: The X coordinate of the desired block :param y: The Y coordinate of the desired block :param z: The Z coordinate of the desired block :param dimension: The dimension of the desired block :param version: The version to get the block converted to. :return: The block at the given location converted to the `version` format. Note the odd return format. :raise: Raises ChunkDoesNotExist or ChunkLoadError if the chunk was not loaded. """ cx, cz = block_coords_to_chunk_coords(x, z, chunk_size=self.sub_chunk_size) chunk = self.get_chunk(cx, cz, dimension) offset_x, offset_z = x - 16 * cx, z - 16 * cz output, extra_output, _ = self.translation_manager.get_version( *version).block.from_universal( chunk.get_block(offset_x, y, offset_z), chunk.block_entities.get((x, y, z))) return output, extra_output
def chunk_y_locations(self, sub_chunk_size: int = 16) -> Generator[int, None, None]: """A generator of all the sub-chunk y indexes this box intersects. :param sub_chunk_size: The dimension of the chunk (normally 16) """ cy_min, cy_max = block_coords_to_chunk_coords( self.min_y, self._max_y - 1, sub_chunk_size=sub_chunk_size ) for cy in range(cy_min, cy_max + 1): yield cy
def chunk_count(self, sub_chunk_size: int = 16) -> int: cx_min, cz_min, cx_max, cz_max = block_coords_to_chunk_coords( self.min_x, self.min_z, self.max_x - 1, self.max_z - 1, sub_chunk_size=sub_chunk_size, ) return (cx_max + 1 - cx_min) * (cz_max + 1 - cz_min)
def chunk_y_locations(self, sub_chunk_size: int = 16) -> Iterable[int]: """ An iterable of all the sub-chunk y indexes this box intersects. :param sub_chunk_size: The dimension of a sub-chunk. Default 16. """ cy_min, cy_max = block_coords_to_chunk_coords( self.min_y, self._max_y - 1, sub_chunk_size=sub_chunk_size) for cy in range(cy_min, cy_max + 1): yield cy
def chunk_locations(self, sub_chunk_size: int = 16 ) -> Iterable[ChunkCoordinates]: cx_min, cz_min, cx_max, cz_max = block_coords_to_chunk_coords( self.min_x, self.min_z, self.max_x - 1, self.max_z - 1, sub_chunk_size=sub_chunk_size, ) yield from itertools.product(range(cx_min, cx_max + 1), range(cz_min, cz_max + 1))
def set_version_block( self, x: int, y: int, z: int, dimension: Dimension, version: VersionIdentifierType, block: Block, block_entity: BlockEntity = None, ): """ Convert the block and block_entity from the given version format to the universal format and set at the location. :param x: The X coordinate of the desired block. :param y: The Y coordinate of the desired block. :param z: The Z coordinate of the desired block. :param dimension: The dimension of the desired block. :param version: The version the given ``block`` and ``block_entity`` come from. >>> ("java", (1, 16, 2)) # Java 1.16.2 format >>> ("java", 2578) # Java 1.16.2 format (using the data version) >>> ("bedrock", (1, 16, 210)) # Bedrock 1.16.210 format :param block: The block to set. Must be valid in the specified version. :param block_entity: The block entity to set. Must be valid in the specified version. :return: The block at the given location converted to the `version` format. Note the odd return format. :raises: ChunkLoadError: If the chunk was not able to be loaded. Eg. If the chunk is corrupt or some error occurred when loading. """ cx, cz = block_coords_to_chunk_coords( x, z, sub_chunk_size=self.sub_chunk_size) try: chunk = self.get_chunk(cx, cz, dimension) except ChunkDoesNotExist: chunk = self.create_chunk(cx, cz, dimension) offset_x, offset_z = x - 16 * cx, z - 16 * cz ( universal_block, universal_block_entity, _, ) = self.translation_manager.get_version(*version).block.to_universal( block, block_entity) chunk.set_block(offset_x, y, offset_z, universal_block), if isinstance(universal_block_entity, BlockEntity): chunk.block_entities[(x, y, z)] = universal_block_entity elif (x, y, z) in chunk.block_entities: del chunk.block_entities[(x, y, z)] chunk.changed = True
def chunk_locations( self, sub_chunk_size: int = 16 ) -> Generator[ChunkCoordinates, None, None]: """A generator of chunk locations that this box intersects. :param sub_chunk_size: The dimension of the chunk (normally 16) """ cx_min, cz_min, cx_max, cz_max = block_coords_to_chunk_coords( self.min_x, self.min_z, self.max_x - 1, self.max_z - 1, sub_chunk_size=sub_chunk_size, ) yield from itertools.product( range(cx_min, cx_max + 1), range(cz_min, cz_max + 1) )
def get_block(self, x: int, y: int, z: int, dimension: Dimension) -> Block: """ Gets the universal Block object at the specified coordinates :param x: The X coordinate of the desired block :param y: The Y coordinate of the desired block :param z: The Z coordinate of the desired block :param dimension: The dimension of the desired block :return: The universal Block object representation of the block at that location :raise: Raises ChunkDoesNotExist or ChunkLoadError if the chunk was not loaded. """ cx, cz = block_coords_to_chunk_coords( x, z, sub_chunk_size=self.sub_chunk_size) offset_x, offset_z = x - 16 * cx, z - 16 * cz return self.get_chunk(cx, cz, dimension).get_block(offset_x, y, offset_z)
def set_version_block( self, x: int, y: int, z: int, dimension: Dimension, version: VersionIdentifierType, block: Block, block_entity: BlockEntity = None, ): """ Convert the block and block_entity from the given version format to the universal format and set at the location :param x: The X coordinate of the desired block :param y: The Y coordinate of the desired block :param z: The Z coordinate of the desired block :param dimension: The dimension of the desired block :param version: The version to get the block converted from. :param block: :param block_entity: :return: The block at the given location converted to the `version` format. Note the odd return format. :raise: Raises ChunkLoadError if the chunk was not loaded correctly. """ cx, cz = block_coords_to_chunk_coords( x, z, sub_chunk_size=self.sub_chunk_size) try: chunk = self.get_chunk(cx, cz, dimension) except ChunkDoesNotExist: chunk = self.create_chunk(cx, cz, dimension) offset_x, offset_z = x - 16 * cx, z - 16 * cz ( universal_block, universal_block_entity, _, ) = self.translation_manager.get_version(*version).block.to_universal( block, block_entity) chunk.set_block(offset_x, y, offset_z, universal_block), if isinstance(universal_block_entity, BlockEntity): chunk.block_entities[(x, y, z)] = universal_block_entity elif (x, y, z) in chunk.block_entities: del chunk.block_entities[(x, y, z)]
def get_version_block( self, x: int, y: int, z: int, dimension: Dimension, version: VersionIdentifierType, ) -> Union[Tuple[Block, BlockEntity], Tuple[Entity, None]]: """ Get a block at the specified location and convert it to the format of the version specified Note the odd return format. In most cases this will return (Block, None) or (Block, BlockEntity) if a block entity is present. In select cases (like item frames) it may return (Entity, None) :param x: The X coordinate of the desired block :param y: The Y coordinate of the desired block :param z: The Z coordinate of the desired block :param dimension: The dimension of the desired block :param version: The version to get the block converted to. >>> ("java", (1, 16, 2)) # Java 1.16.2 format >>> ("java", 2578) # Java 1.16.2 format (using the data version) >>> ("bedrock", (1, 16, 210)) # Bedrock 1.16.210 format :return: The block at the given location converted to the `version` format. Note the odd return format. :raises: :class:`~amulet.api.errors.ChunkDoesNotExist`: If the chunk does not exist (was deleted or never created) :class:`~amulet.api.errors.ChunkLoadError`: If the chunk was not able to be loaded. Eg. If the chunk is corrupt or some error occurred when loading. """ cx, cz = block_coords_to_chunk_coords( x, z, sub_chunk_size=self.sub_chunk_size) chunk = self.get_chunk(cx, cz, dimension) offset_x, offset_z = x - 16 * cx, z - 16 * cz output, extra_output, _ = self.translation_manager.get_version( *version).block.from_universal( chunk.get_block(offset_x, y, offset_z), chunk.block_entities.get((x, y, z))) return output, extra_output
def get_block(self, x: int, y: int, z: int, dimension: Dimension) -> Block: """ Gets the universal Block object at the specified coordinates. To get the block in a given format use :meth:`get_version_block` :param x: The X coordinate of the desired block :param y: The Y coordinate of the desired block :param z: The Z coordinate of the desired block :param dimension: The dimension of the desired block :return: The universal Block object representation of the block at that location :raises: :class:`~amulet.api.errors.ChunkDoesNotExist`: If the chunk does not exist (was deleted or never created) :class:`~amulet.api.errors.ChunkLoadError`: If the chunk was not able to be loaded. Eg. If the chunk is corrupt or some error occurred when loading. """ cx, cz = block_coords_to_chunk_coords( x, z, sub_chunk_size=self.sub_chunk_size) offset_x, offset_z = x - 16 * cx, z - 16 * cz return self.get_chunk(cx, cz, dimension).get_block(offset_x, y, offset_z)
# load the level level = amulet.load_level("level") # lets get the block at 9, 98, 24 which in the world I am testing in is a piston head. # Like with the previous level.get_version_block method there # is a helper method to get the universal Block object. # This method also adds a bit of overhead so is not # great at scale but lets show how it works anyway. universal_block = level.get_block(9, 98, 24, "minecraft:overworld") # Block(universal_minecraft:piston_head[facing="up",short="false"]) # Lets look into what level.get_block actually does. # first lets find which chunk the block is in. x, z = 9, 24 cx, cz = block_coords_to_chunk_coords(x, z) # 0, 1 # read in the chunk chunk = level.get_chunk(cx, cz, "minecraft:overworld") # note that level.get_block and level.get_chunk may raise ChunkLoadError or ChunkDoesNotExist # wrap them in a try except block to handle the error. # get the offset within the chunk. offset_x, offset_z = x - 16 * cx, z - 16 * cz # 9, 8 # chunk.blocks is a custom class designed to behave like an infinite height array. # It stores a dictionary mapping sub-chunk location to a numpy array # of size 16x16x16 which can be directly accessed and modified.
def get_moved_coord_slice_box( self, dimension: Dimension, destination_origin: BlockCoordinates, selection: Optional[Union[SelectionGroup, SelectionBox]] = None, destination_sub_chunk_shape: Optional[int] = None, yield_missing_chunks: bool = False, ) -> Generator[Tuple[ChunkCoordinates, Tuple[ slice, slice, slice], SelectionBox, ChunkCoordinates, Tuple[ slice, slice, slice], SelectionBox, ], None, None, ]: """ Iterate over a selection and return slices into the source object and destination object given the origin of the destination. When copying a selection to a new area the slices will only be equal if the offset is a multiple of the chunk size. This will rarely be the case so the slices need to be split up into parts that intersect a chunk in the source and destination. :param dimension: The dimension to iterate over. :param destination_origin: The location where the minimum point of the selection will end up :param selection: An optional selection. The overlap of this and the dimensions bounds will be used :param destination_sub_chunk_shape: the chunk shape of the destination object (defaults to self.sub_chunk_size) :param yield_missing_chunks: Generate empty chunks if the chunk does not exist. :return: """ if destination_sub_chunk_shape is None: destination_sub_chunk_shape = self.sub_chunk_size if selection is None: selection = self.bounds(dimension) else: selection = self.bounds(dimension).intersection(selection) # the offset from self.selection to the destination location offset = numpy.subtract(destination_origin, self.bounds(dimension).min, dtype=int) for (src_cx, src_cz), box in self.get_coord_box( dimension, selection, yield_missing_chunks=yield_missing_chunks): dst_full_box = SelectionBox(offset + box.min, offset + box.max) first_chunk = block_coords_to_chunk_coords( dst_full_box.min_x, dst_full_box.min_z, sub_chunk_size=destination_sub_chunk_shape, ) last_chunk = block_coords_to_chunk_coords( dst_full_box.max_x - 1, dst_full_box.max_z - 1, sub_chunk_size=destination_sub_chunk_shape, ) for dst_cx, dst_cz in itertools.product( range(first_chunk[0], last_chunk[0] + 1), range(first_chunk[1], last_chunk[1] + 1), ): chunk_box = self._chunk_box(dst_cx, dst_cz, destination_sub_chunk_shape) dst_box = chunk_box.intersection(dst_full_box) src_box = SelectionBox(-offset + dst_box.min, -offset + dst_box.max) src_slices = src_box.chunk_slice(src_cx, src_cz, self.sub_chunk_size) dst_slices = dst_box.chunk_slice(dst_cx, dst_cz, self.sub_chunk_size) yield (src_cx, src_cz), src_slices, src_box, ( dst_cx, dst_cz, ), dst_slices, dst_box