Example #1
0
    def _create(
        self,
        overwrite: bool,
        bounds: Union[SelectionGroup,
                      Dict[Dimension, Optional[SelectionGroup]], None] = None,
        **kwargs,
    ):
        if os.path.isdir(self.path):
            if overwrite:
                shutil.rmtree(self.path)
            else:
                raise ObjectWriteError(
                    f"A world already exists at the path {self.path}")
        self._version = self.translation_manager.get_version(
            self.platform, self.version).data_version

        self.root_tag = root = nbt.TAG_Compound()
        root["Data"] = data = nbt.TAG_Compound()
        data["version"] = nbt.TAG_Int(19133)
        data["DataVersion"] = nbt.TAG_Int(self._version)
        data["LastPlayed"] = nbt.TAG_Long(int(time.time() * 1000))
        data["LevelName"] = nbt.TAG_String("World Created By Amulet")

        os.makedirs(self.path, exist_ok=True)
        self.root_tag.save_to(os.path.join(self.path, "level.dat"))
        self._reload_world()
Example #2
0
    def ints_to_block(self, block_id: int, block_data: int) -> "Block":
        if block_id in self._translation_manager.block_registry:
            (
                namespace,
                base_name,
            ) = self._translation_manager.block_registry.private_to_str(block_id).split(
                ":", 1
            )
        elif block_id in self._numerical_block_map:
            namespace, base_name = self._numerical_block_map[block_id]
        else:
            return Block(
                namespace="minecraft",
                base_name="numerical",
                properties={
                    "block_id": amulet_nbt.TAG_Int(block_id),
                    "block_data": amulet_nbt.TAG_Int(block_data),
                },
            )

        return Block(
            namespace=namespace,
            base_name=base_name,
            properties={"block_data": amulet_nbt.TAG_Int(block_data)},
        )
Example #3
0
    def _create(
        self,
        overwrite: bool,
        bounds: Union[SelectionGroup,
                      Dict[Dimension, Optional[SelectionGroup]], None] = None,
        **kwargs,
    ):
        if os.path.isdir(self.path):
            if overwrite:
                shutil.rmtree(self.path)
            else:
                raise ObjectWriteError(
                    f"A world already exists at the path {self.path}")

        version = self.translation_manager.get_version(
            self.platform, self.version).version_number
        self._version = version + (0, ) * (5 - len(version))

        self.root_tag = root = nbt.TAG_Compound()
        root["StorageVersion"] = nbt.TAG_Int(8)
        root["lastOpenedWithVersion"] = nbt.TAG_List(
            [nbt.TAG_Int(i) for i in self._version])
        root["Generator"] = nbt.TAG_Int(1)
        root["LastPlayed"] = nbt.TAG_Long(int(time.time()))
        root["LevelName"] = nbt.TAG_String("World Created By Amulet")

        os.makedirs(self.path, exist_ok=True)
        self.root_tag.save(os.path.join(self.path, "level.dat"))

        db = LevelDB(os.path.join(self.path, "db"), True)
        db.close()

        self._reload_world()
Example #4
0
    def decode(
        self, cx: int, cz: int, section: MCStructureChunk
    ) -> Tuple["Chunk", AnyNDArray]:
        palette = numpy.empty(len(section.palette) + 1, dtype=numpy.object)
        palette[0] = (
            (
                17563649,
                Block(
                    namespace="minecraft",
                    base_name="air",
                    properties={"block_data": amulet_nbt.TAG_Int(0)},
                ),
            ),
        )

        for index, blocks in enumerate(section.palette):
            block_layers: List[Tuple[Optional[int], Block]] = []
            for block in blocks:
                namespace, base_name = block["name"].value.split(":", 1)
                if "version" in block:
                    version: Optional[int] = block["version"].value
                else:
                    version = None

                if "states" in block:  # 1.13 format
                    properties = block["states"].value
                    if version is None:
                        version = 17694720  # 1, 14, 0, 0
                else:
                    properties = {"block_data": amulet_nbt.TAG_Int(block["val"].value)}
                block_layers.append(
                    (
                        version,
                        Block(
                            namespace=namespace,
                            base_name=base_name,
                            properties=properties,
                        ),
                    )
                )
            palette[index + 1] = block_layers

        chunk = Chunk(cx, cz)
        box = section.selection.create_moved_box((cx * 16, 0, cz * 16), subtract=True)
        chunk.blocks[box.slice] = section.blocks + 1
        for b in section.block_entities:
            b = self._decode_block_entity(
                b, self._block_entity_id_type, self._block_entity_coord_type
            )
            if b is not None:
                chunk.block_entities.insert(b)
        for b in section.entities:
            b = self._decode_entity(
                b, self._block_entity_id_type, self._block_entity_coord_type
            )
            if b is not None:
                chunk.entities.append(b)

        return chunk, palette
Example #5
0
 def _init_write(self):
     """data to be written at init in write mode"""
     self._buffer.write(magic_num)
     self._buffer.write(struct.pack(">B", self._format_version))
     if self._format_version == 0:
         self._metadata = amulet_nbt.NBTFile(
             amulet_nbt.TAG_Compound({
                 "created_with":
                 amulet_nbt.TAG_String("amulet_python_wrapper"),
                 "selection_boxes":
                 amulet_nbt.TAG_Int_Array([
                     c for box in self._selection_boxes
                     if len(box) == 6 and all(
                         isinstance(c, int) for c in box) for c in box
                 ]),
                 "section_version":
                 amulet_nbt.TAG_Byte(self._section_version),
                 "export_version":
                 amulet_nbt.TAG_Compound({
                     "edition":
                     amulet_nbt.TAG_String(self._source_edition),
                     "version":
                     amulet_nbt.TAG_List([
                         amulet_nbt.TAG_Int(v) for v in self._source_version
                     ]),
                 }),
             }))
     else:
         raise Exception(
             f"This wrapper doesn't support any construction version higher than {max_format_version}"
         )
Example #6
0
    def _save_subchunks_1(self, blocks: "Blocks",
                          palette: AnyNDArray) -> List[Optional[bytes]]:
        for index, block in enumerate(palette):
            block: Tuple[Tuple[None, Block], ...]
            block_data = block[0][1].properties.get("block_data",
                                                    amulet_nbt.TAG_Int(0))
            if isinstance(block_data, amulet_nbt.TAG_Int):
                block_data = block_data.value
                # if block_data >= 16:
                #     block_data = 0
            else:
                block_data = 0

            palette[index] = amulet_nbt.NBTFile(
                amulet_nbt.TAG_Compound({
                    "name":
                    amulet_nbt.TAG_String(block[0][1].namespaced_name),
                    "val":
                    amulet_nbt.TAG_Short(block_data),
                }))
        chunk = []
        for cy in range(16):
            if cy in blocks:
                palette_index, sub_chunk = fast_unique(
                    blocks.get_sub_chunk(cy))
                sub_chunk_palette = list(palette[palette_index])
                chunk.append(b"\x01" + self._save_palette_subchunk(
                    sub_chunk.ravel(), sub_chunk_palette))
            else:
                chunk.append(None)
        return chunk
Example #7
0
    def _encode_base_entity(
        entity: Union[Entity, BlockEntity], id_type: str, coord_type: str
    ) -> Optional[amulet_nbt.NBTFile]:
        if not isinstance(entity.nbt, amulet_nbt.NBTFile) and isinstance(
            entity.nbt.value, amulet_nbt.TAG_Compound
        ):
            return
        nbt = entity.nbt

        if id_type == "namespace-str-id":
            nbt["id"] = amulet_nbt.TAG_String(entity.namespaced_name)
        elif id_type == "namespace-str-identifier":
            nbt["identifier"] = amulet_nbt.TAG_String(entity.namespaced_name)
        elif id_type == "str-id":
            nbt["id"] = amulet_nbt.TAG_String(entity.base_name)
        elif id_type == "int-id":
            if not entity.base_name.isnumeric():
                return
            nbt["id"] = amulet_nbt.TAG_Int(int(entity.base_name))
        else:
            raise NotImplementedError(f"Entity id type {id_type}")

        if coord_type == "Pos-list-double":
            nbt["Pos"] = amulet_nbt.TAG_List(
                [
                    amulet_nbt.TAG_Double(float(entity.x)),
                    amulet_nbt.TAG_Double(float(entity.y)),
                    amulet_nbt.TAG_Double(float(entity.z)),
                ]
            )
        elif coord_type == "Pos-list-float":
            nbt["Pos"] = amulet_nbt.TAG_List(
                [
                    amulet_nbt.TAG_Float(float(entity.x)),
                    amulet_nbt.TAG_Float(float(entity.y)),
                    amulet_nbt.TAG_Float(float(entity.z)),
                ]
            )
        elif coord_type == "xyz-int":
            nbt["x"] = amulet_nbt.TAG_Int(int(entity.x))
            nbt["y"] = amulet_nbt.TAG_Int(int(entity.y))
            nbt["z"] = amulet_nbt.TAG_Int(int(entity.z))
        else:
            raise NotImplementedError(f"Entity coord type {coord_type}")

        return nbt
Example #8
0
    def __init__(self, path_or_buffer: str, selection: SelectionBox):
        self._path_or_buffer = path_or_buffer
        self._selection = selection

        self._data = amulet_nbt.NBTFile(
            amulet_nbt.TAG_Compound({
                "format_version":
                amulet_nbt.TAG_Int(1),
                "structure_world_origin":
                amulet_nbt.TAG_List([
                    amulet_nbt.TAG_Int(selection.min_x),
                    amulet_nbt.TAG_Int(selection.min_y),
                    amulet_nbt.TAG_Int(selection.min_z),
                ]),
                "size":
                amulet_nbt.TAG_List([
                    amulet_nbt.TAG_Int(selection.size_x),
                    amulet_nbt.TAG_Int(selection.size_y),
                    amulet_nbt.TAG_Int(selection.size_z),
                ]),
            }))

        self._entities = []
        self._block_entities = []
        self._blocks = numpy.zeros(
            selection.shape,
            dtype=numpy.uint32)  # only 12 bits are actually used at most
        self._palette: List[AnyNDArray] = []
        self._palette_len = 0
Example #9
0
 def _serialise_block_entities(
     block_entities: List[BlockEntity], ) -> amulet_nbt.TAG_List:
     return amulet_nbt.TAG_List([
         amulet_nbt.TAG_Compound({
             "namespace":
             amulet_nbt.TAG_String(block_entity.namespace),
             "base_name":
             amulet_nbt.TAG_String(block_entity.base_name),
             "x":
             amulet_nbt.TAG_Int(block_entity.x),
             "y":
             amulet_nbt.TAG_Int(block_entity.y),
             "z":
             amulet_nbt.TAG_Int(block_entity.z),
             "nbt":
             block_entity.nbt.value,
         }) for block_entity in block_entities
     ])
Example #10
0
    def encode(
        self,
        chunk: "Chunk",
        palette: AnyNDArray,
        max_world_version: VersionIdentifierType,
        box: SelectionBox,
    ) -> MCStructureChunk:
        """
        Take a version-specific chunk and encode it to raw data for the format to store.
        :param chunk: The already translated version-specfic chunk to encode.
        :param palette: The block_palette the ids in the chunk correspond to.
        :type palette: numpy.ndarray[Block]
        :param max_world_version: The key to use to find the encoder.
        :param box: The volume of the chunk to pack.
        :return: Raw data to be stored by the Format.
        """
        entities = []
        for e in chunk.entities:
            if e.location in box:
                entities.append(
                    self._encode_entity(e, self._entity_id_type,
                                        self._entity_coord_type).value)
        block_entities = []
        for e in chunk.block_entities:
            if e.location in box:
                block_entities.append(
                    self._encode_block_entity(
                        e, self._block_entity_id_type,
                        self._block_entity_coord_type).value)

        slices = box.create_moved_box((chunk.cx * 16, 0, chunk.cz * 16),
                                      subtract=True).slice

        out_palette = numpy.empty(palette.shape, dtype=object)
        for index, block_layers in enumerate(palette):
            blocks_out = []
            for version, block in block_layers:
                block = amulet_nbt.TAG_Compound({
                    "name":
                    amulet_nbt.TAG_String(
                        f"{block.namespace}:{block.base_name}"),
                    "states":
                    amulet_nbt.TAG_Compound(block.properties),
                })
                if version:
                    block["version"] = amulet_nbt.TAG_Int(version)
                blocks_out.append(block)
            out_palette[index] = blocks_out

        return MCStructureChunk(
            box,
            numpy.asarray(chunk.blocks[slices]),
            out_palette,
            block_entities,
            entities,
        )
Example #11
0
 def _get_interface_key(self,
                        raw_chunk_data: Optional[Any] = None
                        ) -> Tuple[str, int]:
     if raw_chunk_data:
         return (
             self.platform,
             raw_chunk_data.get("DataVersion", nbt.TAG_Int(-1)).value,
         )
     else:
         return self.max_world_version
Example #12
0
    def close(self):
        compact_palette, lut = brute_sort_objects_no_hash(
            numpy.concatenate(self._palette))
        self._blocks = lut[self._blocks].ravel()
        block_palette = []
        block_palette_indices = []
        for block_list in compact_palette:
            indexed_block = [-1] * 2
            for block_layer, block in enumerate(block_list):
                if block_layer >= 2:
                    break
                if block in block_palette:
                    indexed_block[block_layer] = block_palette.index(block)
                else:
                    indexed_block[block_layer] = len(block_palette)
                    block_palette.append(block)
            block_palette_indices.append(indexed_block)

        block_indices = numpy.array(block_palette_indices,
                                    dtype=numpy.int32)[self._blocks].T

        self._data["structure"] = amulet_nbt.TAG_Compound({
            "block_indices":
            amulet_nbt.TAG_List(
                [  # a list of tag ints that index into the block_palette. One list per block layer
                    amulet_nbt.TAG_List(
                        [amulet_nbt.TAG_Int(block) for block in layer])
                    for layer in block_indices
                ]),
            "entities":
            amulet_nbt.TAG_List(self._entities),
            "block_palette":
            amulet_nbt.TAG_Compound({
                "default":
                amulet_nbt.TAG_Compound({
                    "block_palette":
                    amulet_nbt.TAG_List(block_palette),
                    "block_position_data":
                    amulet_nbt.TAG_Compound({
                        str((
                            (block_entity["x"].value - self._selection.min_x) *
                            self._selection.size_y +
                            (block_entity["y"].value -
                             self._selection.min_y)) * self._selection.size_z +
                            block_entity["z"].value - self._selection.min_z):
                        amulet_nbt.TAG_Compound(
                            {"block_entity_data": block_entity})
                        for block_entity in self._block_entities
                    }),
                })
            }),
        })
        self._data.save_to(self._path_or_buffer,
                           compressed=False,
                           little_endian=True)
Example #13
0
    def _encode_base_entity(
        entity: Union[Entity, BlockEntity],
        id_type: EntityIDType,
        coord_type: EntityCoordType,
    ) -> Optional[amulet_nbt.NBTFile]:
        if not isinstance(entity.nbt, amulet_nbt.NBTFile) and isinstance(
                entity.nbt.value, amulet_nbt.TAG_Compound):
            return
        nbt = entity.nbt

        if id_type == EntityIDType.namespace_str_id:
            nbt["id"] = amulet_nbt.TAG_String(entity.namespaced_name)
        elif id_type == EntityIDType.namespace_str_Id:
            nbt["Id"] = amulet_nbt.TAG_String(entity.namespaced_name)
        elif id_type == EntityIDType.namespace_str_identifier:
            nbt["identifier"] = amulet_nbt.TAG_String(entity.namespaced_name)
        elif id_type == EntityIDType.str_id:
            nbt["id"] = amulet_nbt.TAG_String(entity.base_name)
        elif id_type == EntityIDType.int_id:
            if not entity.base_name.isnumeric():
                return
            nbt["id"] = amulet_nbt.TAG_Int(int(entity.base_name))
        else:
            raise NotImplementedError(f"Entity id type {id_type}")

        if coord_type == EntityCoordType.Pos_list_double:
            nbt["Pos"] = amulet_nbt.TAG_List([
                amulet_nbt.TAG_Double(float(entity.x)),
                amulet_nbt.TAG_Double(float(entity.y)),
                amulet_nbt.TAG_Double(float(entity.z)),
            ])
        elif coord_type == EntityCoordType.Pos_list_float:
            nbt["Pos"] = amulet_nbt.TAG_List([
                amulet_nbt.TAG_Float(float(entity.x)),
                amulet_nbt.TAG_Float(float(entity.y)),
                amulet_nbt.TAG_Float(float(entity.z)),
            ])
        elif coord_type == EntityCoordType.Pos_list_int:
            nbt["Pos"] = amulet_nbt.TAG_List([
                amulet_nbt.TAG_Int(int(entity.x)),
                amulet_nbt.TAG_Int(int(entity.y)),
                amulet_nbt.TAG_Int(int(entity.z)),
            ])
        elif coord_type == EntityCoordType.Pos_array_int:
            nbt["Pos"] = amulet_nbt.TAG_Int_Array(
                [int(entity.x), int(entity.y),
                 int(entity.z)])
        elif coord_type == EntityCoordType.xyz_int:
            nbt["x"] = amulet_nbt.TAG_Int(int(entity.x))
            nbt["y"] = amulet_nbt.TAG_Int(int(entity.y))
            nbt["z"] = amulet_nbt.TAG_Int(int(entity.z))
        else:
            raise NotImplementedError(f"Entity coord type {coord_type}")

        return nbt
Example #14
0
 def get_translator(
     self,
     max_world_version: Tuple[str, Union[int, Tuple[int, int, int]]],
     data: amulet_nbt.NBTFile = None,
 ) -> Tuple["Translator", int]:
     if data:
         data_version = data.get("DataVersion",
                                 amulet_nbt.TAG_Int(-1)).value
         key, version = (("java", data_version), data_version)
     else:
         key = max_world_version
         version = max_world_version[1]
     return translators.loader.get(key), version
Example #15
0
        def translate_block(
            input_object: Block,
            get_block_callback: Optional[GetBlockCallback],
            block_location: BlockCoordinates,
        ) -> TranslateBlockCallbackReturn:
            final_block = None
            final_block_entity = None
            final_entities = []
            final_extra = False

            for depth, block in enumerate(input_object.block_tuple):
                (
                    output_object,
                    output_block_entity,
                    extra,
                ) = version.block.from_universal(
                    block,
                    get_block_callback=get_block_callback,
                    block_location=block_location,
                )

                if isinstance(output_object, Block):
                    if __debug__ and output_object.namespace.startswith(
                            "universal"):
                        log.debug(
                            f"Error translating {input_object.full_blockstate} from universal. Got {output_object.full_blockstate}"
                        )
                    if version.data_version > 0:
                        properties = output_object.properties
                        properties["__version__"] = amulet_nbt.TAG_Int(
                            version.data_version)
                        output_object = Block(
                            output_object.namespace,
                            output_object.base_name,
                            properties,
                            output_object.extra_blocks,
                        )
                    if final_block is None:
                        final_block = output_object
                    else:
                        final_block += output_object
                    if depth == 0:
                        final_block_entity = output_block_entity

                elif isinstance(output_object, Entity):
                    final_entities.append(output_object)
                    # TODO: offset entity coords

                final_extra |= extra

            return final_block, final_block_entity, final_entities, final_extra
Example #16
0
    def _unpack_blocks(
        translation_manager: "TranslationManager",
        version_identifier: VersionIdentifierType,
        chunk: Chunk,
        block_palette: AnyNDArray,
    ):
        """
        Unpacks an object array of block data into a numpy object array containing Block objects.
        :param translation_manager:
        :param version_identifier:
        :param chunk:
        :param block_palette:
        :type block_palette: numpy.ndarray[
            Tuple[
                Union[
                    Tuple[None, Tuple[int, int]],
                    Tuple[None, Block],
                    Tuple[int, Block]
                ], ...
            ]
        ]
        :return:
        """
        palette_ = BlockManager()
        for palette_index, entry in enumerate(block_palette):
            entry: BedrockInterfaceBlockType
            block = None
            for version_number, b in entry:
                version_number: Optional[int]
                if isinstance(b, tuple):
                    version = translation_manager.get_version(
                        version_identifier[0], version_number or 17563649)
                    b = version.block.ints_to_block(*b)
                elif isinstance(b, Block):
                    if version_number is not None:
                        properties = b.properties
                        properties["__version__"] = amulet_nbt.TAG_Int(
                            version_number)
                        b = Block(b.namespace, b.base_name, properties,
                                  b.extra_blocks)
                else:
                    raise Exception(f"Unsupported type {b}")
                if block is None:
                    block = b
                else:
                    block += b
            if block is None:
                raise Exception(f"Empty tuple")

            palette_.get_add_block(block)
        chunk._block_palette = palette_
Example #17
0
def generate_block_entry(block: Block, palette_len,
                         extra_blocks) -> amulet_nbt.TAG_Compound:
    return amulet_nbt.TAG_Compound({
        "namespace":
        amulet_nbt.TAG_String(block.namespace),
        "blockname":
        amulet_nbt.TAG_String(block.base_name),
        "properties":
        amulet_nbt.TAG_Compound(block.properties),
        "extra_blocks":
        amulet_nbt.TAG_List([
            amulet_nbt.TAG_Int(palette_len + extra_blocks.index(_extra_block))
            for _extra_block in block.extra_blocks
        ]),
    })
Example #18
0
    def encode(
        self,
        chunk: "Chunk",
        palette: AnyNDArray,
        max_world_version: Tuple[str, Union[int, Tuple[int, int, int]]],
        box: SelectionBox = None,
    ) -> MCStructureChunk:
        entities = []
        for e in chunk.entities:
            if e.location in box:
                entities.append(
                    self._encode_entity(
                        e, self._entity_id_type, self._entity_coord_type
                    ).value
                )
        block_entities = []
        for e in chunk.block_entities:
            if e.location in box:
                block_entities.append(
                    self._encode_block_entity(
                        e, self._block_entity_id_type, self._block_entity_coord_type
                    ).value
                )

        slices = box.create_moved_box(
            (chunk.cx * 16, 0, chunk.cz * 16), subtract=True
        ).slice

        out_palette = numpy.empty(palette.shape, dtype=object)
        for index, block_layers in enumerate(palette):
            blocks_out = []
            for version, block in block_layers:
                block = amulet_nbt.TAG_Compound(
                    {
                        "name": amulet_nbt.TAG_String(
                            f"{block.namespace}:{block.base_name}"
                        ),
                        "states": amulet_nbt.TAG_Compound(block.properties),
                    }
                )
                if version:
                    block["version"] = amulet_nbt.TAG_Int(version)
                blocks_out.append(block)
            out_palette[index] = blocks_out

        return MCStructureChunk(
            box, chunk.blocks[slices], out_palette, block_entities, entities
        )
Example #19
0
    def decode(self, cx: int, cz: int,
               data: List[ConstructionSection]) -> Tuple["Chunk", AnyNDArray]:
        """
        Create an amulet.api.chunk.Chunk object from raw data given by the format
        :param cx: chunk x coordinate
        :param cz: chunk z coordinate
        :param data: Raw chunk data provided by the format.
        :return: Chunk object in version-specific format, along with the block_palette for that chunk.
        """
        chunk = Chunk(cx, cz)
        palette = []
        for section in data:
            if any(s == 0 for s in section.shape):
                continue
            if section.blocks is not None:
                shapex, shapey, shapez = section.shape
                sx = section.sx % 16
                sy = section.sy
                sz = section.sz % 16
                chunk.blocks[sx:sx + shapex, sy:sy + shapey,
                             sz:sz + shapez, ] = section.blocks.astype(
                                 numpy.uint32) + len(palette)
                chunk.block_entities.update(section.block_entities)
            chunk.entities.extend(section.entities)
            palette += section.palette

        np_palette, inverse = numpy.unique(palette, return_inverse=True)
        np_palette = numpy.insert(
            np_palette,
            0,
            Block(
                namespace="minecraft",
                base_name="air",
                properties={"block_data": amulet_nbt.TAG_Int(0)},
            ),
        )
        inverse += 1
        np_palette: AnyNDArray
        inverse: numpy.ndarray
        for cy in chunk.blocks.sub_chunks:
            chunk.blocks.add_sub_chunk(
                cy,
                inverse[chunk.blocks.get_sub_chunk(cy)].astype(numpy.uint32))
        return chunk, np_palette
Example #20
0
    def decode(
        self, cx: int, cz: int, data: List[ConstructionSection]
    ) -> Tuple["Chunk", AnyNDArray]:
        chunk = Chunk(cx, cz)
        palette = []
        for section in data:
            if any(s == 0 for s in section.shape):
                continue
            if section.blocks is not None:
                shapex, shapey, shapez = section.shape
                sx = section.sx - ((section.sx >> 4) << 4)
                sy = section.sy
                sz = section.sz - ((section.sz >> 4) << 4)
                chunk.blocks[
                    sx : sx + shapex, sy : sy + shapey, sz : sz + shapez,
                ] = section.blocks.astype(numpy.uint32) + len(palette)
                chunk.block_entities.update(section.block_entities)
            chunk.entities.extend(section.entities)
            palette += section.palette

        np_palette, inverse = numpy.unique(palette, return_inverse=True)
        np_palette = numpy.insert(
            np_palette,
            0,
            Block(
                namespace="minecraft",
                base_name="air",
                properties={"block_data": amulet_nbt.TAG_Int(0)},
            ),
        )
        inverse += 1
        np_palette: AnyNDArray
        inverse: numpy.ndarray
        for cy in chunk.blocks.sub_chunks:
            chunk.blocks.add_sub_chunk(
                cy, inverse[chunk.blocks.get_sub_chunk(cy)].astype(numpy.uint32)
            )
        return chunk, np_palette
Example #21
0
    def _save_subchunks_8(self, blocks: "Blocks",
                          palette: AnyNDArray) -> List[Optional[bytes]]:
        palette_depth = numpy.array([len(block) for block in palette])
        if palette.size:
            if palette[0][0][0] is None:
                air = amulet_nbt.NBTFile(
                    amulet_nbt.TAG_Compound({
                        "name":
                        amulet_nbt.TAG_String("minecraft:air"),
                        "val":
                        amulet_nbt.TAG_Short(0),
                    }))
            else:
                air = amulet_nbt.NBTFile(
                    amulet_nbt.TAG_Compound({
                        "name":
                        amulet_nbt.TAG_String("minecraft:air"),
                        "states":
                        amulet_nbt.TAG_Compound({}),
                        "version":
                        amulet_nbt.TAG_Int(17_629_184),  # 1, 13, 0, 0
                    }))

            for index, block in enumerate(palette):
                block: Tuple[Tuple[Optional[int], Block], ...]
                full_block = []
                for sub_block_version, sub_block in block:
                    properties = sub_block.properties
                    if sub_block_version is None:
                        block_data = properties.get("block_data",
                                                    amulet_nbt.TAG_Int(0))
                        if isinstance(block_data, amulet_nbt.TAG_Int):
                            block_data = block_data.value
                            # if block_data >= 16:
                            #     block_data = 0
                        else:
                            block_data = 0
                        sub_block_ = amulet_nbt.NBTFile(
                            amulet_nbt.TAG_Compound({
                                "name":
                                amulet_nbt.TAG_String(
                                    sub_block.namespaced_name),
                                "val":
                                amulet_nbt.TAG_Short(block_data),
                            }))
                    else:
                        sub_block_ = amulet_nbt.NBTFile(
                            amulet_nbt.TAG_Compound({
                                "name":
                                amulet_nbt.TAG_String(
                                    sub_block.namespaced_name),
                                "states":
                                amulet_nbt.TAG_Compound({
                                    key: val
                                    for key, val in properties.items()
                                    if isinstance(val, PropertyDataTypes)
                                }),
                                "version":
                                amulet_nbt.TAG_Int(sub_block_version),
                            }))

                    full_block.append(sub_block_)
                palette[index] = tuple(full_block)

            chunk = []
            for cy in range(16):
                if cy in blocks:
                    palette_index, sub_chunk = fast_unique(
                        blocks.get_sub_chunk(cy))
                    sub_chunk_palette = palette[palette_index]
                    sub_chunk_depth = palette_depth[palette_index].max()

                    if (sub_chunk_depth == 1 and len(sub_chunk_palette) == 1
                            and sub_chunk_palette[0][0]["name"].value
                            == "minecraft:air"):
                        chunk.append(None)
                    else:
                        # pad block_palette with air in the extra layers
                        sub_chunk_palette_full = numpy.empty(
                            (sub_chunk_palette.size, sub_chunk_depth),
                            dtype=object)
                        sub_chunk_palette_full.fill(air)

                        for index, block_tuple in enumerate(sub_chunk_palette):
                            for sub_index, block in enumerate(block_tuple):
                                sub_chunk_palette_full[index,
                                                       sub_index] = block
                        # should now be a 2D array with an amulet_nbt.NBTFile in each element

                        sub_chunk_bytes = [b"\x08", bytes([sub_chunk_depth])]
                        for sub_chunk_layer_index in range(sub_chunk_depth):
                            # TODO: sort out a way to do this quicker without brute forcing it.
                            (
                                sub_chunk_layer_palette,
                                sub_chunk_remap,
                            ) = brute_sort_objects_no_hash(
                                sub_chunk_palette_full[:,
                                                       sub_chunk_layer_index])
                            sub_chunk_layer = sub_chunk_remap[
                                sub_chunk.ravel()]

                            # sub_chunk_layer, sub_chunk_layer_palette = sub_chunk, sub_chunk_palette_full[:, sub_chunk_layer_index]
                            sub_chunk_bytes.append(
                                self._save_palette_subchunk(
                                    sub_chunk_layer.reshape(16, 16, 16),
                                    list(sub_chunk_layer_palette.ravel()),
                                ))

                        chunk.append(b"".join(sub_chunk_bytes))
                else:
                    chunk.append(None)
        else:
            chunk = [None] * 16

        return chunk
Example #22
0
    def decode(self, cx: int, cz: int,
               data: MCStructureChunk) -> Tuple["Chunk", AnyNDArray]:
        """
        Create an amulet.api.chunk.Chunk object from raw data given by the format
        :param cx: chunk x coordinate
        :param cz: chunk z coordinate
        :param data: Raw chunk data provided by the format.
        :return: Chunk object in version-specific format, along with the block_palette for that chunk.
        """
        palette = numpy.empty(len(data.palette) + 1, dtype=object)
        palette[0] = ((
            17563649,
            Block(
                namespace="minecraft",
                base_name="air",
                properties={"block_data": amulet_nbt.TAG_Int(0)},
            ),
        ), )

        for index, blocks in enumerate(data.palette):
            block_layers: List[Tuple[Optional[int], Block]] = []
            for block in blocks:
                namespace, base_name = block["name"].value.split(":", 1)
                if "version" in block:
                    version: Optional[int] = block["version"].value
                else:
                    version = None

                if "states" in block:  # 1.13 format
                    properties = block["states"].value
                    if version is None:
                        version = 17694720  # 1, 14, 0, 0
                else:
                    properties = {
                        "block_data": amulet_nbt.TAG_Int(block["val"].value)
                    }
                block_layers.append((
                    version,
                    Block(
                        namespace=namespace,
                        base_name=base_name,
                        properties=properties,
                    ),
                ))
            palette[index + 1] = block_layers

        chunk = Chunk(cx, cz)
        box = data.selection.create_moved_box((cx * 16, 0, cz * 16),
                                              subtract=True)
        chunk.blocks[box.slice] = data.blocks + 1
        for b in data.block_entities:
            b = self._decode_block_entity(b, self._block_entity_id_type,
                                          self._block_entity_coord_type)
            if b is not None:
                chunk.block_entities.insert(b)
        for b in data.entities:
            b = self._decode_entity(b, self._block_entity_id_type,
                                    self._block_entity_coord_type)
            if b is not None:
                chunk.entities.append(b)

        return chunk, palette
Example #23
0
    def _load_subchunks(
        self, subchunks: List[None, bytes]
    ) -> Tuple[Dict[int, SubChunkNDArray], AnyNDArray]:
        """
        Load a list of bytes objects which contain chunk data
        This function should be able to load all sub-chunk formats (technically before it)
        All sub-chunks will almost certainly all have the same sub-chunk version but
        it should be able to handle a case where that is not true.

        As such this function will return a Chunk and a rather complicated block_palette
        The newer formats allow multiple blocks to occupy the same space and the
        newer versions also include a version ber block. So this will also need
        returning for the translator to handle.

        The block_palette will be a numpy array containing tuple objects
            The tuple represents the "block" however can contain more than one Block object.
            Inside the tuple are one or more tuples.
                These include the block version number and the block itself
                    The block version number will be either None if no block version is given
                    or a tuple containing 4 ints.

                    The block will be either a Block class for the newer formats or a tuple of two ints for the older formats
        """
        blocks: Dict[int, SubChunkNDArray] = {}
        palette: List[Tuple[Tuple[Optional[int],
                                  Union[Tuple[int, int], Block], ], ...]] = [((
                                      17563649,
                                      Block(
                                          namespace="minecraft",
                                          base_name="air",
                                          properties={
                                              "block_data":
                                              amulet_nbt.TAG_Int(0)
                                          },
                                      ),
                                  ), )]
        for cy, data in enumerate(subchunks):
            if data is None:
                continue

            if data[0] in [0, 2, 3, 4, 5, 6, 7]:
                block_ids = numpy.frombuffer(data[1:1 + 2**12],
                                             dtype=numpy.uint8).astype(
                                                 numpy.uint16)
                block_data = from_nibble_array(
                    numpy.frombuffer(data[1 + 2**12:1 + 2**12 + 2**11],
                                     dtype=numpy.uint8))
                combined_palette, block_array = fast_unique(
                    numpy.transpose(
                        ((block_ids << 4) + block_data).reshape(16, 16, 16),
                        (0, 2, 1)))
                blocks[cy] = block_array + len(palette)
                for b in numpy.array(
                    [combined_palette >> 4, combined_palette & 15]).T:
                    palette.append(((None, tuple(b)), ))

            elif data[0] in [1, 8]:
                if data[0] == 1:
                    storage_count = 1
                    data = data[1:]
                else:
                    storage_count, data = data[1], data[2:]

                sub_chunk_blocks = numpy.zeros((16, 16, 16, storage_count),
                                               dtype=numpy.uint32)
                sub_chunk_palette: List[List[Tuple[Optional[int], Block]]] = []
                for storage_index in range(storage_count):
                    (
                        sub_chunk_blocks[:, :, :, storage_index],
                        palette_data,
                        data,
                    ) = self._load_palette_blocks(data)
                    palette_data_out: List[Tuple[Optional[int], Block]] = []
                    for block in palette_data:
                        namespace, base_name = block["name"].value.split(
                            ":", 1)
                        if "version" in block:
                            version: Optional[int] = block["version"].value
                        else:
                            version = None

                        if "states" in block:  # 1.13 format
                            properties = block["states"].value
                            if version is None:
                                version = 17694720  # 1, 14, 0, 0
                        else:
                            properties = {
                                "block_data":
                                amulet_nbt.TAG_Int(block["val"].value)
                            }
                        palette_data_out.append((
                            version,
                            Block(
                                namespace=namespace,
                                base_name=base_name,
                                properties=properties,
                            ),
                        ))
                    sub_chunk_palette.append(palette_data_out)

                if storage_count == 1:
                    blocks[cy] = sub_chunk_blocks[:, :, :, 0] + len(palette)
                    palette += [(val, ) for val in sub_chunk_palette[0]]
                elif storage_count > 1:
                    # we have two or more storages so need to find the unique block combinations and merge them together
                    sub_chunk_palette_, sub_chunk_blocks = numpy.unique(
                        sub_chunk_blocks.reshape(-1, storage_count),
                        return_inverse=True,
                        axis=0,
                    )
                    blocks[cy] = sub_chunk_blocks.reshape(16, 16, 16).astype(
                        numpy.uint32) + len(palette)
                    palette += [
                        tuple(
                            sub_chunk_palette[storage_index][index]
                            for storage_index, index in enumerate(
                                palette_indexes)
                            if not (storage_index > 0
                                    and sub_chunk_palette[storage_index][index]
                                    [1].namespaced_name == "minecraft:air"))
                        for palette_indexes in sub_chunk_palette_
                    ]
                else:
                    continue

        # block_palette should now look like this
        # List[
        #   Tuple[
        #       Tuple[version, Block], ...
        #   ]
        # ]

        numpy_palette, lut = brute_sort_objects(palette)
        for cy in blocks.keys():
            blocks[cy] = lut[blocks[cy]]

        return blocks, numpy_palette
Example #24
0
    def save_to(self, f: BinaryIO):
        palette: BlockManager = BlockManager()
        f.write(magic_num)
        f.write(struct.pack(">B", self._format_version))
        if self._format_version == 0:
            metadata = amulet_nbt.NBTFile(
                amulet_nbt.TAG_Compound(
                    {
                        "created_with": amulet_nbt.TAG_String(
                            "amulet_python_wrapper_v2"
                        ),
                        "selection_boxes": amulet_nbt.TAG_Int_Array(
                            [
                                c
                                for box in self._selection.selection_boxes
                                for c in (*box.min, *box.max)
                            ]
                        ),
                        "section_version": amulet_nbt.TAG_Byte(self._section_version),
                        "export_version": amulet_nbt.TAG_Compound(
                            {
                                "edition": amulet_nbt.TAG_String(self._platform),
                                "version": amulet_nbt.TAG_List(
                                    [amulet_nbt.TAG_Int(v) for v in self._version]
                                ),
                            }
                        ),
                    }
                )
            )
            section_index_table: List[
                Tuple[int, int, int, int, int, int, int, int]
            ] = []
            if self._section_version == 0:
                for section_list in self._chunk_to_section.values():
                    for section in section_list:
                        sx, sy, sz = section.location
                        shapex, shapey, shapez = section.shape
                        blocks = section.blocks
                        entities = section.entities
                        block_entities = section.block_entities
                        section_palette = section.palette
                        position = f.tell()

                        _tag = amulet_nbt.TAG_Compound(
                            {"entities": serialise_entities(entities)}
                        )

                        if blocks is None:
                            _tag["blocks_array_type"] = amulet_nbt.TAG_Byte(-1)
                        else:
                            flattened_array = blocks.ravel()
                            index, flattened_array = numpy.unique(
                                flattened_array, return_inverse=True
                            )
                            section_palette = numpy.array(
                                section_palette, dtype=object
                            )[index]
                            lut = numpy.vectorize(palette.get_add_block)(
                                section_palette
                            )
                            flattened_array = lut[flattened_array]
                            array_type = find_fitting_array_type(flattened_array)
                            _tag["blocks_array_type"] = amulet_nbt.TAG_Byte(
                                array_type().tag_id
                            )
                            _tag["blocks"] = array_type(flattened_array)
                            _tag["block_entities"] = serialise_block_entities(
                                block_entities or []
                            )

                        amulet_nbt.NBTFile(_tag).save_to(f)

                        length = f.tell() - position
                        section_index_table.append(
                            (sx, sy, sz, shapex, shapey, shapez, position, length)
                        )
            else:
                raise Exception(
                    f"This wrapper doesn't support any section version higher than {max_section_version}"
                )
            metadata_start = f.tell()
            metadata["section_index_table"] = amulet_nbt.TAG_Byte_Array(
                numpy.array(section_index_table, dtype=SECTION_ENTRY_TYPE).view(
                    numpy.int8
                )
            )
            metadata["block_palette"] = pack_palette(palette)
            metadata.save_to(f)
            f.write(INT_STRUCT.pack(metadata_start))
            f.write(magic_num)
        else:
            raise Exception(
                f"This wrapper doesn't support any construction version higher than {max_format_version}"
            )
Example #25
0
    def encode(self, chunk: "Chunk", palette: AnyNDArray,
               max_world_version: Tuple[str, int]) -> amulet_nbt.NBTFile:
        """
        Encode a version-specific chunk to raw data for the format to store.
        :param chunk: The version-specific chunk to translate and encode.
        :param palette: The block_palette the ids in the chunk correspond to.
        :param max_world_version: The key to use to find the encoder.
        :return: amulet_nbt.NBTFile
        """

        misc = chunk.misc
        data = amulet_nbt.NBTFile(amulet_nbt.TAG_Compound(), "")
        data["Level"] = amulet_nbt.TAG_Compound()
        data["Level"]["xPos"] = amulet_nbt.TAG_Int(chunk.cx)
        data["Level"]["zPos"] = amulet_nbt.TAG_Int(chunk.cz)
        if self.features["data_version"] == "int":
            data["DataVersion"] = amulet_nbt.TAG_Int(max_world_version[1])

        if self.features["last_update"] == "long":
            data["Level"]["LastUpdate"] = amulet_nbt.TAG_Long(
                misc.get("last_update", 0))

        # Order the float value based on the order they would be run. Newer replacements for the same come just after
        # to save back find the next lowest valid value.
        if self.features["status"] in ["j13", "j14"]:
            status = chunk.status.as_type(self.features["status"])
            data["Level"]["Status"] = amulet_nbt.TAG_String(status)

        else:
            status = chunk.status.as_type("float")
            if self.features["terrain_populated"] == "byte":
                data["Level"]["TerrainPopulated"] = amulet_nbt.TAG_Byte(
                    int(status > -0.3))

            if self.features["light_populated"] == "byte":
                data["Level"]["LightPopulated"] = amulet_nbt.TAG_Byte(
                    int(status > -0.2))

        if self.features["V"] == "byte":
            data["Level"]["V"] = amulet_nbt.TAG_Byte(misc.get("V", 1))

        if self.features["inhabited_time"] == "long":
            data["Level"]["InhabitedTime"] = amulet_nbt.TAG_Long(
                misc.get("inhabited_time", 0))

        if self.features["biomes"] == "256BA":
            if chunk.status.value > -0.7:
                data["Level"]["Biomes"] = amulet_nbt.TAG_Byte_Array(
                    chunk.biomes.convert_to_format(256).astype(
                        dtype=numpy.uint8))
        elif self.features["biomes"] == "256IA":
            if chunk.status.value > -0.7:
                data["Level"]["Biomes"] = amulet_nbt.TAG_Int_Array(
                    chunk.biomes.convert_to_format(256).astype(
                        dtype=numpy.uint32))
        elif self.features["biomes"] == "1024IA":
            if chunk.status.value > -0.7:
                data["Level"]["Biomes"] = amulet_nbt.TAG_Int_Array(
                    chunk.biomes.convert_to_format(1024).astype(
                        dtype=numpy.uint32))

        if self.features["height_map"] == "256IA":
            data["Level"]["HeightMap"] = amulet_nbt.TAG_Int_Array(
                misc.get("height_map256IA", numpy.zeros(256,
                                                        dtype=numpy.uint32)))
        elif self.features["height_map"] in [
                "C|36LA|V1",
                "C|36LA|V2",
                "C|36LA|V3",
                "C|36LA|V4",
        ]:
            maps = [
                "WORLD_SURFACE_WG",
                "OCEAN_FLOOR_WG",
                "MOTION_BLOCKING",
                "MOTION_BLOCKING_NO_LEAVES",
                "OCEAN_FLOOR",
            ]
            if self.features["height_map"] == "C|36LA|V1":  # 1466
                maps = ("LIQUID", "SOILD", "LIGHT", "RAIN")
            elif self.features["height_map"] == "C|36LA|V2":  # 1484
                maps.append("LIGHT_BLOCKING")
            elif self.features["height_map"] == "C|36LA|V3":  # 1503
                maps.append("LIGHT_BLOCKING")
                maps.append("WORLD_SURFACE")
            elif self.features["height_map"] == "C|36LA|V4":  # 1908
                maps.append("WORLD_SURFACE")
            else:
                raise Exception
            heightmaps = misc.get("height_mapC|36LA",
                                  amulet_nbt.TAG_Compound())
            for heightmap in maps:
                if heightmap not in heightmaps:
                    heightmaps[heightmap] = amulet_nbt.TAG_Long_Array(
                        numpy.zeros(36, dtype=">i8"))
            data["Level"]["Heightmaps"] = heightmaps

        if self.features["blocks"] in [
                "Sections|(Blocks,Data,Add)",
                "Sections|(BlockStates,Palette)",
        ]:
            data["Level"]["Sections"] = self._encode_blocks(
                chunk.blocks, palette)
        else:
            raise Exception(
                f'Unsupported block format {self.features["blocks"]}')

        if self.features["block_light"] in [
                "Sections|2048BA"
        ] or self.features["sky_light"] in ["Sections|2048BA"]:
            for section in data["Level"]["Sections"]:
                y = section["Y"].value
                if self.features["block_light"] == "Sections|2048BA":
                    block_light = misc.get("block_light", {})
                    if y in block_light:
                        section["BlockLight"] = block_light[y]
                    else:
                        section["BlockLight"] = amulet_nbt.TAG_Byte_Array(
                            numpy.full(2048, 255, dtype=numpy.uint8))
                if self.features["sky_light"] == "Sections|2048BA":
                    sky_light = misc.get("sky_light", {})
                    if y in sky_light:
                        section["SkyLight"] = sky_light[y]
                    else:
                        section["SkyLight"] = amulet_nbt.TAG_Byte_Array(
                            numpy.full(2048, 255, dtype=numpy.uint8))

        if self.features["entities"] == "list":
            if amulet.entity_support:
                data["Level"]["Entities"] = self._encode_entities(
                    chunk.entities)
            else:
                data["Level"]["Entities"] = self._encode_entities(
                    misc.get("java_entities_temp", amulet_nbt.TAG_List()))

        if self.features["block_entities"] == "list":
            data["Level"]["TileEntities"] = self._encode_block_entities(
                chunk.block_entities)

        if self.features["tile_ticks"] in ["list", "list(optional)"]:
            ticks = misc.get("tile_ticks", amulet_nbt.TAG_List())
            if self.features["tile_ticks"] == "list(optional)":
                if len(ticks) > 0:
                    data["Level"]["TileTicks"] = ticks
            elif self.features["tile_ticks"] == "list":
                data["Level"]["TileTicks"] = ticks

        if self.features["liquid_ticks"] == "list":
            data["Level"]["LiquidTicks"] = misc.get("liquid_ticks",
                                                    amulet_nbt.TAG_List())

        if self.features["liquids_to_be_ticked"] == "16list|list":
            data["Level"]["LiquidsToBeTicked"] = misc.get(
                "liquids_to_be_ticked",
                amulet_nbt.TAG_List([amulet_nbt.TAG_List()
                                     for _ in range(16)]),
            )

        if self.features["to_be_ticked"] == "16list|list":
            data["Level"]["ToBeTicked"] = misc.get(
                "to_be_ticked",
                amulet_nbt.TAG_List([amulet_nbt.TAG_List()
                                     for _ in range(16)]),
            )

        if self.features["post_processing"] == "16list|list":
            data["Level"]["PostProcessing"] = misc.get(
                "post_processing",
                amulet_nbt.TAG_List([amulet_nbt.TAG_List()
                                     for _ in range(16)]),
            )

        if self.features["structures"] == "compound":
            data["Level"]["Structures"] = misc.get(
                "structures",
                amulet_nbt.TAG_Compound({
                    "References": amulet_nbt.TAG_Compound(),
                    "Starts": amulet_nbt.TAG_Compound(),
                }),
            )

        return data
Example #26
0
    def save_to(self, f: BinaryIO):
        selection = self._selection.selection_boxes[0]
        data = amulet_nbt.NBTFile(
            amulet_nbt.TAG_Compound(
                {
                    "format_version": amulet_nbt.TAG_Int(1),
                    "structure_world_origin": amulet_nbt.TAG_List(
                        [
                            amulet_nbt.TAG_Int(selection.min_x),
                            amulet_nbt.TAG_Int(selection.min_y),
                            amulet_nbt.TAG_Int(selection.min_z),
                        ]
                    ),
                    "size": amulet_nbt.TAG_List(
                        [
                            amulet_nbt.TAG_Int(selection.size_x),
                            amulet_nbt.TAG_Int(selection.size_y),
                            amulet_nbt.TAG_Int(selection.size_z),
                        ]
                    ),
                }
            )
        )

        entities = []
        block_entities = []
        blocks = numpy.zeros(
            selection.shape, dtype=numpy.uint32
        )  # only 12 bits are actually used at most
        palette: List[AnyNDArray] = []
        palette_len = 0

        for (
            selection_,
            blocks_,
            palette_,
            block_entities_,
            entities_,
        ) in self._chunks.values():
            if selection_.intersects(selection):
                box = selection_.create_moved_box(selection.min, subtract=True)
                blocks[box.slice] = blocks_ + palette_len
                palette.append(palette_)
                palette_len += len(palette_)
                block_entities += block_entities_
                entities += entities_

        compact_palette, lut = brute_sort_objects_no_hash(numpy.concatenate(palette))
        blocks = lut[blocks].ravel()
        block_palette = []
        block_palette_indices = []
        for block_list in compact_palette:
            indexed_block = [-1] * 2
            for block_layer, block in enumerate(block_list):
                if block_layer >= 2:
                    break
                if block in block_palette:
                    indexed_block[block_layer] = block_palette.index(block)
                else:
                    indexed_block[block_layer] = len(block_palette)
                    block_palette.append(block)
            block_palette_indices.append(indexed_block)

        block_indices = numpy.array(block_palette_indices, dtype=numpy.int32)[blocks].T

        data["structure"] = amulet_nbt.TAG_Compound(
            {
                "block_indices": amulet_nbt.TAG_List(
                    [  # a list of tag ints that index into the block_palette. One list per block layer
                        amulet_nbt.TAG_List(
                            [amulet_nbt.TAG_Int(block) for block in layer]
                        )
                        for layer in block_indices
                    ]
                ),
                "entities": amulet_nbt.TAG_List(entities),
                "palette": amulet_nbt.TAG_Compound(
                    {
                        "default": amulet_nbt.TAG_Compound(
                            {
                                "block_palette": amulet_nbt.TAG_List(block_palette),
                                "block_position_data": amulet_nbt.TAG_Compound(
                                    {
                                        str(
                                            (
                                                (
                                                    block_entity["x"].value
                                                    - selection.min_x
                                                )
                                                * selection.size_y
                                                + (
                                                    block_entity["y"].value
                                                    - selection.min_y
                                                )
                                            )
                                            * selection.size_z
                                            + block_entity["z"].value
                                            - selection.min_z
                                        ): amulet_nbt.TAG_Compound(
                                            {"block_entity_data": block_entity}
                                        )
                                        for block_entity in block_entities
                                    }
                                ),
                            }
                        )
                    }
                ),
            }
        )
        data.save_to(f, compressed=False, little_endian=True)
Example #27
0
    def open_from(self, f: BinaryIO):
        mcstructure = amulet_nbt.load(f, little_endian=True)
        if mcstructure["format_version"].value == 1:
            min_point = numpy.array(
                tuple(c.value for c in mcstructure["structure_world_origin"]))
            max_point = min_point + tuple(c.value for c in mcstructure["size"])
            selection = SelectionBox(min_point, max_point)
            self._bounds[self.dimensions[0]] = SelectionGroup(selection)
            translator_version = self.translation_manager.get_version(
                "bedrock", (999, 999, 999))
            self._platform = translator_version.platform
            self._version = translator_version.version_number
            blocks_array: numpy.ndarray = numpy.array(
                [[b.value for b in layer]
                 for layer in mcstructure["structure"]["block_indices"]],
                dtype=numpy.int32,
            ).reshape((len(mcstructure["structure"]["block_indices"]),
                       *selection.shape))

            palette_key = list(mcstructure["structure"]["palette"].keys())[
                0]  # find a way to do this based on user input
            block_palette = list(mcstructure["structure"]["palette"]
                                 [palette_key]["block_palette"])

            if -1 in blocks_array[0]:
                blocks_array[0][blocks_array[0] == -1] = len(block_palette)
                block_palette.append(
                    amulet_nbt.TAG_Compound({
                        "name":
                        amulet_nbt.TAG_String("minecraft:structure_void"),
                        "states":
                        amulet_nbt.TAG_Compound(),
                        "version":
                        amulet_nbt.TAG_Int(17694723),  # 1.13.0
                    }))

            for cx, cz in selection.chunk_locations():
                chunk_box = SelectionBox.create_chunk_box(
                    cx, cz).intersection(selection)
                array_slice = (slice(None), ) + chunk_box.create_moved_box(
                    selection.min, subtract=True).slice
                chunk_blocks_: numpy.ndarray = blocks_array[array_slice]
                chunk_palette_indexes, chunk_blocks = numpy.unique(
                    chunk_blocks_.reshape((chunk_blocks_.shape[0], -1)).T,
                    return_inverse=True,
                    axis=0,
                )
                chunk_blocks = chunk_blocks.reshape(chunk_blocks_.shape[1:])

                chunk_palette = numpy.empty(len(chunk_palette_indexes),
                                            dtype=object)
                for palette_index, indexes in enumerate(chunk_palette_indexes):
                    chunk_palette[palette_index] = tuple(block_palette[index]
                                                         for index in indexes
                                                         if index >= 0)

                self._chunks[(cx, cz)] = (
                    chunk_box,
                    chunk_blocks,
                    chunk_palette,
                    [],
                    [],
                )

            block_entities = {
                int(key): val["block_entity_data"]
                for key, val in mcstructure["structure"]["palette"]
                [palette_key]["block_position_data"].items()
                if "block_entity_data" in val
            }
            for location, block_entity in block_entities.items():
                if all(key in block_entity for key in "xyz"):
                    x, y, z = (
                        block_entity["x"].value,
                        block_entity["y"].value,
                        block_entity["z"].value,
                    )
                    cx, cz = x >> 4, z >> 4
                    if (cx, cz) in self._chunks and (x, y, z) in self._chunks[(
                            cx, cz)][0]:
                        self._chunks[(cx, cz)][3].append(block_entity)

            entities = list(mcstructure["structure"]["entities"])
            for entity in entities:
                if "Pos" in entity:
                    x, y, z = (
                        entity["Pos"][0].value,
                        entity["Pos"][1].value,
                        entity["Pos"][2].value,
                    )
                    cx, cz = numpy.floor([x, z]).astype(int) >> 4
                    if (cx, cz) in self._chunks and (x, y, z) in self._chunks[(
                            cx, cz)][0]:
                        self._chunks[(cx, cz)][4].append(entity)

        else:
            raise Exception(
                f"mcstructure file with format_version=={mcstructure['format_version'].value} cannot be read"
            )
Example #28
0
    def _encode_subchunks(
        self,
        blocks: "Blocks",
        palette: AnyNDArray,
        bounds: Tuple[int, int],
        max_world_version: VersionNumberTuple,
    ) -> Dict[int, Optional[bytes]]:
        # Encode sub-chunk block format 8
        palette_depth = numpy.array([len(block) for block in palette])
        min_y = bounds[0] // 16
        max_y = bounds[1] // 16
        if palette.size:
            if palette[0][0][0] is None:
                air = amulet_nbt.NBTFile(
                    amulet_nbt.TAG_Compound({
                        "name":
                        amulet_nbt.TAG_String("minecraft:air"),
                        "val":
                        amulet_nbt.TAG_Short(0),
                    }))
            else:
                air = amulet_nbt.NBTFile(
                    amulet_nbt.TAG_Compound({
                        "name":
                        amulet_nbt.TAG_String("minecraft:air"),
                        "states":
                        amulet_nbt.TAG_Compound({}),
                        "version":
                        amulet_nbt.TAG_Int(17_629_184),  # 1, 13, 0, 0
                    }))

            for index, block in enumerate(palette):
                block: Tuple[Tuple[Optional[int], Block], ...]
                full_block = []
                for sub_block_version, sub_block in block:
                    properties = sub_block.properties
                    if sub_block_version is None:
                        block_data = properties.get("block_data",
                                                    amulet_nbt.TAG_Int(0))
                        if isinstance(block_data, amulet_nbt.TAG_Int):
                            block_data = block_data.value
                            # if block_data >= 16:
                            #     block_data = 0
                        else:
                            block_data = 0
                        sub_block_ = amulet_nbt.NBTFile(
                            amulet_nbt.TAG_Compound({
                                "name":
                                amulet_nbt.TAG_String(
                                    sub_block.namespaced_name),
                                "val":
                                amulet_nbt.TAG_Short(block_data),
                            }))
                    else:
                        sub_block_ = amulet_nbt.NBTFile(
                            amulet_nbt.TAG_Compound({
                                "name":
                                amulet_nbt.TAG_String(
                                    sub_block.namespaced_name),
                                "states":
                                amulet_nbt.TAG_Compound({
                                    key: val
                                    for key, val in properties.items()
                                    if isinstance(val, PropertyDataTypes)
                                }),
                                "version":
                                amulet_nbt.TAG_Int(sub_block_version),
                            }))

                    full_block.append(sub_block_)
                palette[index] = tuple(full_block)

            chunk = {}
            for cy in range(min_y, max_y):
                if cy in blocks:
                    palette_index, sub_chunk = fast_unique(
                        blocks.get_sub_chunk(cy))
                    sub_chunk_palette = palette[palette_index]
                    sub_chunk_depth = palette_depth[palette_index].max()

                    if (sub_chunk_depth == 1 and len(sub_chunk_palette) == 1
                            and sub_chunk_palette[0][0]["name"].value
                            == "minecraft:air"):
                        chunk[cy] = None
                    else:
                        # pad block_palette with air in the extra layers
                        sub_chunk_palette_full = numpy.empty(
                            (sub_chunk_palette.size, sub_chunk_depth),
                            dtype=object)
                        sub_chunk_palette_full.fill(air)

                        for index, block_tuple in enumerate(sub_chunk_palette):
                            for sub_index, block in enumerate(block_tuple):
                                sub_chunk_palette_full[index,
                                                       sub_index] = block
                        # should now be a 2D array with an amulet_nbt.NBTFile in each element

                        if max_world_version[1] >= (
                                1,
                                17,
                                30,
                        ):  # Why do I need to check against game version and not chunk version
                            sub_chunk_bytes = [
                                b"\x09",
                                bytes([sub_chunk_depth]),
                                struct.pack("b", cy),
                            ]
                        else:
                            sub_chunk_bytes = [
                                b"\x08", bytes([sub_chunk_depth])
                            ]
                        for sub_chunk_layer_index in range(sub_chunk_depth):
                            # TODO: sort out a way to do this quicker without brute forcing it.
                            (
                                sub_chunk_layer_palette,
                                sub_chunk_remap,
                            ) = brute_sort_objects_no_hash(
                                sub_chunk_palette_full[:,
                                                       sub_chunk_layer_index])
                            sub_chunk_layer = sub_chunk_remap[
                                sub_chunk.ravel()]

                            # sub_chunk_layer, sub_chunk_layer_palette = sub_chunk, sub_chunk_palette_full[:, sub_chunk_layer_index]
                            sub_chunk_bytes.append(
                                self._save_palette_subchunk(
                                    sub_chunk_layer.reshape(16, 16, 16),
                                    list(sub_chunk_layer_palette.ravel()),
                                ))

                        chunk[cy] = b"".join(sub_chunk_bytes)
                else:
                    chunk[cy] = None
        else:
            chunk = {i: None for i in range(min_y, max_y)}

        return chunk
 def _get_version(self) -> int:
     return (self.root_tag.get("Data", nbt.TAG_Compound()).get(
         "DataVersion", nbt.TAG_Int(-1)).value)
 def _get_interface_key(self, raw_chunk_data) -> Tuple[str, int]:
     return self.platform, raw_chunk_data.get("DataVersion",
                                              nbt.TAG_Int(-1)).value