Esempio n. 1
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
Esempio n. 2
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
Esempio n. 3
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)
Esempio n. 4
0
 def close(self):
     self._data["Entities"] = amulet_nbt.TAG_List(self._entities)
     self._data["TileEntities"] = amulet_nbt.TAG_List(self._block_entities)
     self._data["Data"] = amulet_nbt.TAG_Byte_Array(
         numpy.transpose(self._block_data, (1, 2, 0))  # XYZ => YZX
     )
     self._data["Blocks"] = amulet_nbt.TAG_Byte_Array(
         numpy.transpose((self._blocks & 0xFF).astype(numpy.uint8),
                         (1, 2, 0)))
     if numpy.max(self._blocks) > 0xFF:
         add_blocks = numpy.transpose(self._blocks & 0xF00, (1, 2, 0)) >> 8
         self._data["AddBlocks"] = amulet_nbt.TAG_Byte_Array(
             add_blocks[::2] + (add_blocks[1::2] << 4))
     self._data.save_to(self._path_or_buffer)
Esempio n. 5
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()
Esempio n. 6
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}"
         )
Esempio n. 7
0
    def __init__(self, path_or_buffer: str, platform: str, selection: SelectionBox):
        self._path_or_buffer = path_or_buffer
        if platform == "java":
            self._materials = "Alpha"
        elif platform == "bedrock":
            self._materials = "Pocket"
        else:
            raise Exception(
                f'"{platform}" is not a supported platform for a schematic file.'
            )
        self._selection = selection

        self._data = amulet_nbt.NBTFile(
            amulet_nbt.TAG_Compound(
                {
                    "TileTicks": amulet_nbt.TAG_List(),
                    "Width": amulet_nbt.TAG_Short(selection.size_x),
                    "Height": amulet_nbt.TAG_Short(selection.size_y),
                    "Length": amulet_nbt.TAG_Short(selection.size_z),
                    "Materials": amulet_nbt.TAG_String(self._materials),
                }
            ),
            "Schematic",
        )

        self._entities = []
        self._block_entities = []
        self._blocks = numpy.zeros(
            selection.shape, dtype=numpy.uint16
        )  # only 12 bits are actually used at most
        self._block_data = numpy.zeros(
            selection.shape, dtype=numpy.uint8
        )  # only 4 bits are used
    def _encode_blocks(
        self, blocks: Blocks, palette: AnyNDArray
    ) -> amulet_nbt.TAG_List:
        sections = amulet_nbt.TAG_List()
        for cy in range(16):
            if cy in blocks:
                block_sub_array = numpy.transpose(
                    blocks.get_sub_chunk(cy), (1, 2, 0)
                ).ravel()

                sub_palette_, block_sub_array = numpy.unique(
                    block_sub_array, return_inverse=True
                )
                sub_palette = self._encode_palette(palette[sub_palette_])
                if (
                    len(sub_palette) == 1
                    and sub_palette[0]["Name"].value == "minecraft:air"
                ):
                    continue

                section = amulet_nbt.TAG_Compound()
                section["Y"] = amulet_nbt.TAG_Byte(cy)
                if self.features["long_array_format"] == "compact":
                    section["BlockStates"] = amulet_nbt.TAG_Long_Array(
                        encode_long_array(block_sub_array)
                    )
                elif self.features["long_array_format"] == "1.16":
                    section["BlockStates"] = amulet_nbt.TAG_Long_Array(
                        encode_long_array(block_sub_array, dense=False)
                    )
                section["Palette"] = sub_palette
                sections.append(section)

        return sections
Esempio n. 9
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
Esempio n. 10
0
 def _encode_palette(blockstates: list) -> amulet_nbt.TAG_List:
     palette = amulet_nbt.TAG_List()
     for block in blockstates:
         entry = amulet_nbt.TAG_Compound()
         entry["Name"] = amulet_nbt.TAG_String(
             f"{block.namespace}:{block.base_name}")
         entry["Properties"] = amulet_nbt.TAG_Compound(block.properties)
         palette.append(entry)
     return palette
Esempio n. 11
0
    def _encode_entities(self,
                         entities: Iterable["Entity"]) -> amulet_nbt.TAG_List:
        entities_out = []
        for entity in entities:
            nbt = self._encode_entity(
                entity,
                self.features["entity_format"],
                self.features["entity_coord_format"],
            )
            if nbt is not None:
                entities_out.append(nbt.value)

        return amulet_nbt.TAG_List(entities_out)
Esempio n. 12
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
        ]),
    })
Esempio n. 13
0
 def _serialise_entities(entities: List[Entity]) -> amulet_nbt.TAG_List:
     return amulet_nbt.TAG_List(
         [
             amulet_nbt.TAG_Compound(
                 {
                     "namespace": amulet_nbt.TAG_String(entity.namespace),
                     "base_name": amulet_nbt.TAG_String(entity.base_name),
                     "x": amulet_nbt.TAG_Double(entity.x),
                     "y": amulet_nbt.TAG_Double(entity.y),
                     "z": amulet_nbt.TAG_Double(entity.z),
                     "nbt": entity.nbt.value,
                 }
             )
             for entity in entities
         ]
     )
Esempio n. 14
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
     ])
Esempio n. 15
0
def pack_palette(palette: BlockManager) -> amulet_nbt.TAG_List:
    block_palette_nbt = amulet_nbt.TAG_List()
    extra_blocks = set()

    for block in palette.blocks():
        if len(block.extra_blocks) > 0:
            extra_blocks.update(block.extra_blocks)

    extra_blocks = list(extra_blocks)

    palette_len = len(palette)
    for block_entry in palette.blocks():
        block_palette_nbt.append(
            generate_block_entry(block_entry, palette_len, extra_blocks))

    for extra_block in extra_blocks:
        block_palette_nbt.append(
            generate_block_entry(extra_block, palette_len, extra_blocks))
    return block_palette_nbt
Esempio n. 16
0
    def _encode_blocks(self, blocks: "Blocks",
                       palette: AnyNDArray) -> nbt.TAG_List:
        sections = nbt.TAG_List()
        for cy in range(16):  # perhaps find a way to do this dynamically
            if cy in blocks:
                block_sub_array = palette[numpy.transpose(
                    blocks.get_sub_chunk(cy), (1, 2, 0)).ravel()]

                data_sub_array = block_sub_array[:, 1]
                block_sub_array = block_sub_array[:, 0]
                if not numpy.any(block_sub_array) and not numpy.any(
                        data_sub_array):
                    continue
                section = nbt.TAG_Compound()
                section["Y"] = nbt.TAG_Byte(cy)
                section["Blocks"] = nbt.TAG_Byte_Array(
                    block_sub_array.astype("uint8"))
                section["Data"] = nbt.TAG_Byte_Array(
                    world_utils.to_nibble_array(data_sub_array))
                sections.append(section)

        return sections
Esempio n. 17
0
    def save_to(self, f: BinaryIO):
        selection = self._bounds[self.dimensions[0]].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)
        palette: List[AnyNDArray] = []
        if self.version < (1, 13, 0):
            raise Exception(
                "Writing to mcstructre files in pre-1.13 format is not currently supported."
            )
        else:
            arr = numpy.empty(1, dtype=object)
            arr[0] = [
                amulet_nbt.TAG_Compound({
                    "name":
                    amulet_nbt.TAG_String("minecraft:air"),
                    "states":
                    amulet_nbt.TAG_Compound(),
                    "version":
                    amulet_nbt.TAG_Int(17694723),
                })
            ]
            palette.append(arr)

        palette_len = 1

        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["name"] != "minecraft:structure_void":
                    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)
Esempio n. 18
0
def fillbarrels(chunk, barrelPositionList, barrelBlock, currentArticle,
                booksPerBarrel, zimFilePath, chunkList, target_pos):
    """Generates all barrels in the chunk and fills them with books/articles"""

    for barrelPos in barrelPositionList:
        books = []
        titles = []

        start = time.perf_counter()

        if booksPerBarrel > 30:
            pool = Pool(
                processes=4
            )  #on my laptop ~4 processes was faster than any amount of threads (4 = logic core count)
        else:
            pool = ThreadPool(
                processes=3
            )  #the article reading is mostly cpu limited, so going high on process count doesnt help
        outputs = pool.map(
            partial(tryGetArticle,
                    zimFilePath=zimFilePath,
                    barrelPositionList=barrelPositionList,
                    booksPerBarrel=booksPerBarrel,
                    chunkList=chunkList,
                    target_pos=target_pos),
            range(currentArticle, currentArticle + booksPerBarrel))
        pool.close()
        #outputs = []
        #for id in range(currentArticle, currentArticle + booksPerBarrel):
        #    outputs.append(tryGetArticle(id, zimFilePath))

        currentArticle += booksPerBarrel
        for output in outputs:
            if output[0] == None:
                continue
            titles.append(output[1])
            books.append(output[0])

        stop = time.perf_counter()
        #print("generating a book", (stop-start)/booksPerBarrel)

        chunk.blocks[barrelPos] = barrelBlock
        barrelEntity = BlockEntity("java", "barrel", barrelPos[0] + chunk.cx * 16, barrelPos[1], barrelPos[2] + chunk.cz * 16,\
            amulet_nbt.NBTFile(\
                value = amulet_nbt.TAG_Compound(\
                {\
                    "utags": amulet_nbt.TAG_Compound(\
                    {\
                        "keepPacked": amulet_nbt.TAG_Byte(0),\
                        "isMovable": amulet_nbt.TAG_Byte(1),\
                        "Findable": amulet_nbt.TAG_Byte(0),\
                        "CustomName": amulet_nbt.TAG_String("{\"text\":\"x:%d y:%d z:%d\"}"%(barrelPos[0] + chunk.cx * 16, barrelPos[1], barrelPos[2] + chunk.cz * 16)),\
                        "Items": amulet_nbt.TAG_List(\
                            value = [
                                amulet_nbt.TAG_Compound(\
                                {\
                                    "Slot": amulet_nbt.TAG_Byte(iBook),\
                                    "Count": amulet_nbt.TAG_Byte(1),\
                                    "id": amulet_nbt.TAG_String("minecraft:written_book"),\
                                    "tag": amulet_nbt.TAG_Compound(\
                                    {
                                        "pages": amulet_nbt.TAG_List(\
                                            value=[amulet_nbt.TAG_String(page) for page in books[iBook]],\
                                            list_data_type = 8\
                                        ),\
                                        "title": amulet_nbt.TAG_String(titles[iBook]),\
                                        "author": amulet_nbt.TAG_String("Pos: x:%d y:%d z:%d, ID: %d"%(barrelPos[0] + chunk.cx * 16, barrelPos[1], barrelPos[2] + chunk.cz * 16, currentArticle + iBook)),
                                    })
                                })
                                for iBook in range(len(books))
                            ], list_data_type = 9\
                        )
                    })\
                })))
        chunk.block_entities.insert(barrelEntity)
Esempio n. 19
0
    def decode(self, cx: int, cz: int,
               data: amulet_nbt.NBTFile) -> 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: amulet_nbt.NBTFile
        :return: Chunk object in version-specific format, along with the block_palette for that chunk.
        """
        misc = {}
        chunk = Chunk(cx, cz)

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

        if self.features["status"] in ["j13", "j14"]:
            chunk.status = data["Level"]["Status"].value
        else:
            status = "empty"
            if (self.features["terrain_populated"] == "byte"
                    and data["Level"].get("TerrainPopulated",
                                          amulet_nbt.TAG_Byte()).value):
                status = "decorated"
            if (self.features["light_populated"] == "byte"
                    and data["Level"].get("LightPopulated",
                                          amulet_nbt.TAG_Byte()).value):
                status = "postprocessed"

            chunk.status = status

        if self.features["V"] == "byte":
            misc["V"] = data["Level"]["V"].value

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

        if self.features["biomes"] is not None:
            biomes = data["Level"].get("Biomes",
                                       amulet_nbt.TAG_Int_Array()).value
            if self.features["biomes"] == "256BA":
                biomes = biomes.astype(numpy.uint8)
            elif self.features["biomes"] in ["256IA", "1024IA"]:
                biomes = biomes.astype(numpy.uint32)

            chunk.biomes = biomes

        if self.features["height_map"] == "256IA":
            misc["height_map256IA"] = data["Level"]["HeightMap"].value
        elif self.features["height_map"] in [
                "C|36LA|V1",
                "C|36LA|V2",
                "C|36LA|V3",
                "C|36LA|V4",
        ]:
            if "Heightmaps" in data["Level"]:
                misc["height_mapC|36LA"] = data["Level"]["Heightmaps"]

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

            if self.features["block_light"] == "Sections|2048BA":
                misc["block_light"] = {
                    section["Y"].value: section["BlockLight"]
                    for section in data["Level"]["Sections"]
                    if "BlockLight" in section
                }

            if self.features["sky_light"] == "Sections|2048BA":
                misc["sky_light"] = {
                    section["Y"].value: section["SkyLight"]
                    for section in data["Level"]["Sections"]
                    if "SkyLight" in section
                }
        else:
            palette = numpy.array(
                [Block(namespace="minecraft", base_name="air")])

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

        if self.features["block_entities"] == "list":
            chunk.block_entities = self._decode_block_entities(
                data["Level"].get("TileEntities", amulet_nbt.TAG_List()))

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

        if self.features["liquid_ticks"] == "list":
            if "LiquidTicks" in data["Level"]:
                misc["liquid_ticks"] = data["Level"]["LiquidTicks"]

        if self.features["liquids_to_be_ticked"] == "16list|list":
            if "LiquidsToBeTicked" in data["Level"]:
                misc["liquids_to_be_ticked"] = data["Level"][
                    "LiquidsToBeTicked"]

        if self.features["to_be_ticked"] == "16list|list":
            if "ToBeTicked" in data["Level"]:
                misc["to_be_ticked"] = data["Level"]["ToBeTicked"]

        if self.features["post_processing"] == "16list|list":
            if "PostProcessing" in data["Level"]:
                misc["post_processing"] = data["Level"]["PostProcessing"]

        if self.features["structures"] == "compound":
            if "Structures" in data["Level"]:
                misc["structures"] = data["Level"]["Structures"]

        chunk.misc = misc

        return chunk, palette
Esempio n. 20
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
Esempio n. 21
0
    def __init__(self, path_or_buffer: PathOrBuffer):
        if isinstance(path_or_buffer, str):
            assert path_or_buffer.endswith(
                ".schematic"), "File selected is not a .schematic file"
            assert os.path.isfile(
                path_or_buffer
            ), f"There is no schematic file at path {path_or_buffer}"
            schematic = amulet_nbt.load(path_or_buffer)
            assert not all(key in schematic for key in (
                "Version", "Data Version",
                "BlockData")), "This file is not a legacy schematic file."
        else:
            assert hasattr(path_or_buffer,
                           "read"), "Object does not have a read method"
            schematic = amulet_nbt.load(buffer=path_or_buffer)

        materials = schematic.get("Materials", amulet_nbt.TAG_String()).value
        if materials == "Alpha":
            self._platform = "java"
        elif materials == "Pocket":
            self._platform = "bedrock"
        else:
            raise Exception(
                f'"{materials}" is not a supported platform for a schematic file.'
            )
        self._chunks: Dict[ChunkCoordinates,
                           Tuple[SelectionBox, BlockArrayType,
                                 BlockDataArrayType, list, list], ] = {}
        self._selection = SelectionBox(
            (0, 0, 0),
            (
                schematic["Width"].value,
                schematic["Height"].value,
                schematic["Length"].value,
            ),
        )
        entities: amulet_nbt.TAG_List = schematic.get("Entities",
                                                      amulet_nbt.TAG_List())
        block_entities: amulet_nbt.TAG_List = schematic.get(
            "TileEntities", amulet_nbt.TAG_List())
        blocks: numpy.ndarray = schematic["Blocks"].value.astype(
            numpy.uint8).astype(numpy.uint16)
        if "AddBlocks" in schematic:
            add_blocks = schematic["AddBlocks"]
            blocks = blocks + (numpy.concatenate([
                [(add_blocks & 0xF0) >> 4], [add_blocks & 0xF]
            ]).T.ravel().astype(numpy.uint16) << 8)[:blocks.size]
        max_point = self._selection.max
        temp_shape = (max_point[1], max_point[2], max_point[0])
        blocks = numpy.transpose(blocks.reshape(temp_shape),
                                 (2, 0, 1))  # YZX => XYZ
        data = numpy.transpose(schematic["Data"].value.reshape(temp_shape),
                               (2, 0, 1))
        for cx, cz in self._selection.chunk_locations():
            box = SelectionBox(
                (cx * 16, 0, cz * 16),
                (
                    min((cx + 1) * 16, self._selection.size_x),
                    self._selection.size_y,
                    min((cz + 1) * 16, self._selection.size_z),
                ),
            )
            self._chunks[(cx, cz)] = (box, blocks[box.slice], data[box.slice],
                                      [], [])
        for e in block_entities:
            if all(key in e for key in ("x", "y", "z")):
                x, y, z = e["x"].value, e["y"].value, e["z"].value
                if (x, y, z) in self._selection:
                    cx = x >> 4
                    cz = z >> 4
                    self._chunks[(cx, cz)][3].append(e)
        for e in entities:
            if "Pos" in e:
                pos: PointCoordinates = tuple(
                    map(lambda t: t.value, e["Pos"].value))
                if pos in self._selection:
                    cx = int(pos[0]) >> 4
                    cz = int(pos[2]) >> 4
                    self._chunks[(cx, cz)][4].append(e)
Esempio n. 22
0
    def open_from(self, f: BinaryIO):
        sponge_schem = amulet_nbt.load(f)
        version = sponge_schem.get("Version")
        if not isinstance(version, amulet_nbt.TAG_Int):
            raise SpongeSchemReadError(
                "Version key must exist and be an integer.")
        if version == 1:
            raise SpongeSchemReadError(
                "Sponge Schematic Version 1 is not supported currently.")
        elif version == 2:
            offset = sponge_schem.get("Offset")
            if isinstance(offset,
                          amulet_nbt.TAG_Int_Array) and len(offset) == 3:
                min_point = numpy.array(offset)
            else:
                min_point = numpy.array([0, 0, 0], dtype=numpy.int32)

            size = []
            for key in ("Width", "Height", "Length"):
                val = sponge_schem.get(key)
                if not isinstance(val, amulet_nbt.TAG_Short):
                    raise SpongeSchemReadError(
                        f"Key {key} must exist and be a TAG_Short.")
                # convert to an unsigned short
                val = val.value
                if val < 0:
                    val += 2**16
                size.append(val)

            max_point = min_point + size
            selection = SelectionBox(min_point, max_point)
            self._bounds[self.dimensions[0]] = SelectionGroup(selection)
            data_version = sponge_schem.get("DataVersion")
            if not isinstance(data_version, amulet_nbt.TAG_Int):
                raise SpongeSchemReadError("DataVersion must be a TAG_Int.")
            translator_version = self.translation_manager.get_version(
                "java", int(data_version))
            self._platform = translator_version.platform
            self._version = translator_version.data_version

            packed_block_data = sponge_schem.get("BlockData")
            if not isinstance(packed_block_data, amulet_nbt.TAG_Byte_Array):
                raise SpongeSchemReadError(
                    "BlockData must be a TAG_Byte_Array")

            unpacked_block_data = decode_byte_array(
                numpy.array(packed_block_data, dtype=numpy.uint8))
            if len(unpacked_block_data) != numpy.prod(size):
                raise SpongeSchemReadError(
                    "The data contained in BlockData does not match the size of the schematic."
                )
            dx, dy, dz = selection.shape
            blocks_array: numpy.ndarray = numpy.transpose(
                numpy.array(
                    unpacked_block_data,
                    dtype=numpy.uint32,
                ).reshape((dy, dz, dx)),
                (2, 0, 1),  # YZX => XYZ
            )

            if "Palette" not in sponge_schem:
                raise SpongeSchemReadError(
                    "Amulet is not able to read Sponge Schem files with no block palette."
                )

            palette_data = sponge_schem.get("Palette")
            if not isinstance(palette_data, amulet_nbt.TAG_Compound):
                raise SpongeSchemReadError("Palette must be a TAG_Compound.")

            block_palette: Dict[int, Block] = {}
            for blockstate, index in palette_data.items():
                if index.value in block_palette:
                    raise SpongeSchemReadError(
                        f"Duplicate block index {index} found in the palette.")
                block_palette[index.value] = Block.from_string_blockstate(
                    blockstate)

            if not numpy.all(numpy.isin(blocks_array, list(block_palette))):
                raise SpongeSchemReadError(
                    "Some values in BlockData were not present in Palette")

            for cx, cz in selection.chunk_locations():
                chunk_box = SelectionBox.create_chunk_box(
                    cx, cz).intersection(selection)
                array_slice = 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_,
                    return_inverse=True,
                )
                chunk_blocks = chunk_blocks.reshape(chunk_blocks_.shape)

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

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

            if "BlockEntities" in sponge_schem:
                block_entities = sponge_schem["BlockEntities"]
                if (not isinstance(block_entities, amulet_nbt.TAG_List)
                        or block_entities.list_data_type !=
                        10  # amulet_nbt.TAG_Compound.tag_id
                    ):
                    raise SpongeSchemReadError(
                        "BlockEntities must be a TAG_List of compound tags.")

                for block_entity in block_entities:
                    if "Pos" in block_entity:
                        pos = block_entity["Pos"]
                        if isinstance(
                                pos,
                                amulet_nbt.TAG_Int_Array) and len(pos) == 3:
                            pos = pos + min_point
                            x, y, z = (
                                pos[0],
                                pos[1],
                                pos[2],
                            )
                            block_entity["Pos"] = amulet_nbt.TAG_Int_Array(pos)
                            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)

            if "Entities" in sponge_schem:
                entities = sponge_schem["Entities"]
                if (not isinstance(entities, amulet_nbt.TAG_List)
                        or entities.list_data_type !=
                        10  # amulet_nbt.TAG_Compound.tag_id
                    ):
                    raise SpongeSchemReadError(
                        "Entities must be a TAG_List of compound tags.")

                for entity in entities:
                    if "Pos" in entity:
                        pos = entity["Pos"]
                        if (isinstance(pos, amulet_nbt.TAG_List)
                                and len(pos) == 3 and pos.list_data_type
                                == 6):  # amulet_nbt.TAG_Double.tag_id:
                            x, y, z = (
                                pos[0].value + offset[0],
                                pos[1].value + offset[0],
                                pos[2].value + offset[0],
                            )
                            entity["Pos"] = amulet_nbt.TAG_List([
                                amulet_nbt.TAG_Int(x),
                                amulet_nbt.TAG_Int(y),
                                amulet_nbt.TAG_Int(z),
                            ])
                            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 SpongeSchemReadError(
                f"Sponge Schematic Version {version.value} is not supported currently."
            )
Esempio n. 23
0
    def save_to(self, f: BinaryIO):
        if self._platform == "java":
            materials = "Alpha"
        elif self._platform == "bedrock":
            materials = "Pocket"
        else:
            raise ObjectWriteError(
                f'"{self._platform}" is not a supported platform for a schematic file.'
            )

        selection = self._selection.selection_boxes[0]

        data = amulet_nbt.NBTFile(
            amulet_nbt.TAG_Compound({
                "TileTicks":
                amulet_nbt.TAG_List(),
                "Width":
                amulet_nbt.TAG_Short(selection.size_x),
                "Height":
                amulet_nbt.TAG_Short(selection.size_y),
                "Length":
                amulet_nbt.TAG_Short(selection.size_z),
                "Materials":
                amulet_nbt.TAG_String(materials),
            }),
            "Schematic",
        )

        entities = []
        block_entities = []
        blocks = numpy.zeros(
            selection.shape,
            dtype=numpy.uint16)  # only 12 bits are actually used at most
        block_data = numpy.zeros(selection.shape,
                                 dtype=numpy.uint8)  # only 4 bits are used

        for (
                selection_,
                blocks_,
                data_,
                block_entities_,
                entities_,
        ) in self._chunks.values():
            if selection_.intersects(selection):
                box = selection_.create_moved_box(selection.min, subtract=True)
                blocks[box.slice] = blocks_
                block_data[box.slice] = data_
                for be in block_entities_:
                    coord_type = be["x"].__class__
                    be["x"] = coord_type(be["x"] - selection.min_x)
                    be["y"] = coord_type(be["y"] - selection.min_y)
                    be["z"] = coord_type(be["z"] - selection.min_z)
                    block_entities.append(be)
                for e in entities_:
                    coord_type = e["Pos"][0].__class__
                    e["Pos"][0] = coord_type(e["Pos"][0] - selection.min_x)
                    e["Pos"][1] = coord_type(e["Pos"][1] - selection.min_y)
                    e["Pos"][2] = coord_type(e["Pos"][2] - selection.min_z)
                    entities.append(e)

        data["Entities"] = amulet_nbt.TAG_List(entities)
        data["TileEntities"] = amulet_nbt.TAG_List(block_entities)
        data["Data"] = amulet_nbt.TAG_Byte_Array(
            numpy.transpose(block_data, (1, 2, 0))  # XYZ => YZX
        )
        data["Blocks"] = amulet_nbt.TAG_Byte_Array(
            numpy.transpose((blocks & 0xFF).astype(numpy.uint8), (1, 2, 0)))
        if numpy.max(blocks) > 0xFF:
            add_blocks = numpy.transpose(blocks & 0xF00, (1, 2, 0)) >> 8
            data["AddBlocks"] = amulet_nbt.TAG_Byte_Array(
                add_blocks[::2] + (add_blocks[1::2] << 4))
        data.save_to(f)
Esempio n. 24
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)
Esempio n. 25
0
 def open_from(self, f: BinaryIO):
     schematic = amulet_nbt.load(f)
     if any(key in schematic
            for key in ("Version", "Data Version", "BlockData")):
         raise ObjectReadError("This file is not a legacy schematic file.")
     materials = schematic.get("Materials", amulet_nbt.TAG_String()).value
     if materials == "Alpha":
         self._platform = "java"
         self._version = (1, 12, 2)
     elif materials == "Pocket":
         self._platform = "bedrock"
         self._version = (1, 12, 0)
     else:
         raise Exception(
             f'"{materials}" is not a supported platform for a schematic file.'
         )
     self._chunks = {}
     selection_box = SelectionBox(
         (0, 0, 0),
         (
             schematic["Width"].value,
             schematic["Height"].value,
             schematic["Length"].value,
         ),
     )
     self._selection = SelectionGroup(selection_box)
     entities: amulet_nbt.TAG_List = schematic.get("Entities",
                                                   amulet_nbt.TAG_List())
     block_entities: amulet_nbt.TAG_List = schematic.get(
         "TileEntities", amulet_nbt.TAG_List())
     blocks: numpy.ndarray = (schematic["Blocks"].value.astype(
         numpy.uint8).astype(numpy.uint16))
     if "AddBlocks" in schematic:
         add_blocks = schematic["AddBlocks"]
         blocks = (blocks + (numpy.concatenate([
             [(add_blocks & 0xF0) >> 4], [add_blocks & 0xF]
         ]).T.ravel().astype(numpy.uint16) << 8)[:blocks.size])
     max_point = selection_box.max
     temp_shape = (max_point[1], max_point[2], max_point[0])
     blocks = numpy.transpose(blocks.reshape(temp_shape),
                              (2, 0, 1))  # YZX => XYZ
     data = numpy.transpose(schematic["Data"].value.reshape(temp_shape),
                            (2, 0, 1)).astype(numpy.uint8)
     for cx, cz in selection_box.chunk_locations():
         box = SelectionBox(
             (cx * self.sub_chunk_size, 0, cz * self.sub_chunk_size),
             (
                 min((cx + 1) * self.sub_chunk_size, selection_box.size_x),
                 selection_box.size_y,
                 min((cz + 1) * self.sub_chunk_size, selection_box.size_z),
             ),
         )
         self._chunks[(cx, cz)] = (box, blocks[box.slice], data[box.slice],
                                   [], [])
     for e in block_entities:
         if all(key in e for key in ("x", "y", "z")):
             x, y, z = e["x"].value, e["y"].value, e["z"].value
             if (x, y, z) in selection_box:
                 cx = x >> 4
                 cz = z >> 4
                 self._chunks[(cx, cz)][3].append(e)
     for e in entities:
         if "Pos" in e:
             pos: PointCoordinates = tuple(
                 map(lambda t: float(t.value), e["Pos"].value))
             if pos in selection_box:
                 cx = int(pos[0]) >> 4
                 cz = int(pos[2]) >> 4
                 self._chunks[(cx, cz)][4].append(e)
Esempio n. 26
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}"
            )
Esempio n. 27
0
    def save_to(self, f: BinaryIO):
        if self._schem_version == 1:
            raise SpongeSchemReadError(
                "Sponge Schematic Version 1 is not supported currently.")
        elif self._schem_version == 2:
            selection = self._bounds[self.dimensions[0]].selection_boxes[0]
            if any(s > 2**16 - 1 for s in selection.shape):
                raise SpongeSchemWriteError(
                    "The structure is too large to be exported to a Sponge Schematic file. It must be 2^16 - 1 at most in each dimension."
                )
            overflowed_shape = [
                s if s < 2**15 else s - 2**16 for s in selection.shape
            ]
            data = amulet_nbt.NBTFile(
                amulet_nbt.TAG_Compound({
                    "Version":
                    amulet_nbt.TAG_Int(2),
                    "DataVersion":
                    amulet_nbt.TAG_Int(self._version),
                    "Width":
                    amulet_nbt.TAG_Short(overflowed_shape[0]),
                    "Height":
                    amulet_nbt.TAG_Short(overflowed_shape[1]),
                    "Length":
                    amulet_nbt.TAG_Short(overflowed_shape[2]),
                    "Offset":
                    amulet_nbt.TAG_Int_Array(selection.min),
                }),
                name="Schematic",
            )

            entities = []
            block_entities = []
            blocks = numpy.zeros(selection.shape, dtype=numpy.uint32)
            palette: List[AnyNDArray] = []
            if self._version < 1500:
                raise Exception(
                    "Writing to Sponge Schematic files in pre-1.13 format is not currently supported."
                )
            else:
                arr = numpy.empty(1, dtype=object)
                arr[0] = Block("minecraft", "air")
                palette.append((arr))

            palette_len = 1

            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_)
                    for be in block_entities_:
                        be = copy.deepcopy(be)
                        be["Pos"] = amulet_nbt.TAG_Int_Array(be["Pos"] -
                                                             selection.min)
                        block_entities.append(be)

                    for e in entities_:
                        e = copy.deepcopy(e)
                        x, y, z = e["Pos"]
                        e["Pos"] = amulet_nbt.TAG_List([
                            amulet_nbt.TAG_Int(x - selection.min_x),
                            amulet_nbt.TAG_Int(y - selection.min_y),
                            amulet_nbt.TAG_Int(z - selection.min_z),
                        ])
                        entities.append(e)

            compact_palette, lut = brute_sort_objects_no_hash(
                numpy.concatenate(palette))
            blocks = numpy.transpose(lut[blocks],
                                     (1, 2, 0)).ravel()  # XYZ => YZX
            block_palette = []
            for index, block in enumerate(compact_palette):
                block: Block
                block_palette.append(block.blockstate)

            data["PaletteMax"] = amulet_nbt.TAG_Int(len(compact_palette))
            data["Palette"] = amulet_nbt.TAG_Compound({
                blockstate: amulet_nbt.TAG_Int(index)
                for index, blockstate in enumerate(block_palette)
            })
            data["BlockData"] = amulet_nbt.TAG_Byte_Array(
                list(encode_array(blocks)))
            if block_entities:
                data["BlockEntities"] = amulet_nbt.TAG_List(block_entities)
            if entities:
                data["Entities"] = amulet_nbt.TAG_List(entities)

            data.save_to(f)
        else:
            raise SpongeSchemReadError(
                f"Sponge Schematic Version {self._schem_version} is not supported currently."
            )