def _decode_blocks(
        self, chunk_sections: amulet_nbt.TAG_List
    ) -> Tuple[Dict[int, SubChunkNDArray], AnyNDArray]:
        blocks: Dict[int, numpy.ndarray] = {}
        palette = [Block(namespace="minecraft", base_name="air")]

        for section in chunk_sections:
            if "Palette" not in section:  # 1.14 makes block_palette/blocks optional.
                continue
            cy = section["Y"].value
            if self.features["long_array_format"] == "compact":
                decoded = decode_long_array(section["BlockStates"].value, 4096)
            elif self.features["long_array_format"] == "1.16":
                decoded = decode_long_array(
                    section["BlockStates"].value, 4096, dense=False
                )
            else:
                raise Exception("long_array_format", self.features["long_array_format"])
            blocks[cy] = numpy.transpose(
                decoded.reshape((16, 16, 16)) + len(palette), (2, 0, 1)
            )

            palette += self._decode_palette(section["Palette"])

        np_palette, inverse = numpy.unique(palette, return_inverse=True)
        np_palette: numpy.ndarray
        inverse: numpy.ndarray
        for cy in blocks:
            blocks[cy] = inverse[blocks[cy]].astype(
                numpy.uint32
            )  # TODO: find a way to make the new blocks format change dtype
        return blocks, np_palette
예제 #2
0
    def test_longarray(self):
        with open(get_data_path("longarraytest.json")) as json_data:
            test_data = json.load(json_data)

        test_ran = False
        for test_entry in test_data["tests"]:
            test_ran = True
            block_array = numpy.asarray(test_entry["block_array"])
            long_array = numpy.asarray(test_entry["long_array"])
            palette_size = test_entry["palette_size"]

            numpy.testing.assert_array_equal(
                block_array,
                decode_long_array(long_array, len(block_array),
                                  (palette_size - 1).bit_length()),
            )

            numpy.testing.assert_array_equal(
                long_array,
                encode_long_array(block_array,
                                  (palette_size - 1).bit_length()),
            )

        # Make sure some test are ran in case the data file failed to load or has a wrong format.
        self.assertTrue(test_ran)
예제 #3
0
    def test_encode_decode(self):
        for signed in (False, True):
            for dense in (False, True):
                for bits_per_entry in range(4, 65):
                    for size in set([2**p for p in range(1, 13)] +
                                    list(range(100, 5000, 100))):
                        if signed:
                            arr = numpy.random.randint(-(2**63),
                                                       2**63,
                                                       size,
                                                       dtype=numpy.int64)
                        else:
                            arr = numpy.random.randint(0,
                                                       2**64,
                                                       size,
                                                       dtype=numpy.uint64)
                        arr >>= 64 - bits_per_entry

                        packed = encode_long_array(arr, bits_per_entry, dense)
                        arr2 = decode_long_array(packed,
                                                 len(arr),
                                                 bits_per_entry,
                                                 dense=dense,
                                                 signed=signed)
                        numpy.testing.assert_array_equal(
                            arr,
                            arr2,
                            f"Long array does not equal. Dense: {dense}, bits per entry: {bits_per_entry}, size: {size}",
                        )
예제 #4
0
 def _decode_block_section(
         self,
         section: TAG_Compound) -> Optional[Tuple[numpy.ndarray, list]]:
     if "Palette" not in section:  # 1.14 makes block_palette/blocks optional.
         return None
     section_palette = self._decode_block_palette(section.pop("Palette"))
     decoded = decode_long_array(
         section.pop("BlockStates").value,
         4096,
         max(4, (len(section_palette) - 1).bit_length()),
         dense=self.LongArrayDense,
     ).astype(numpy.uint32)
     arr = numpy.transpose(decoded.reshape((16, 16, 16)), (2, 0, 1))
     return arr, section_palette
예제 #5
0
 def _decode_height(
     self, chunk: Chunk, compound: TAG_Compound, bounds: Tuple[int, int]
 ):
     heights = self.get_obj(compound, "Heightmaps", TAG_Compound)
     chunk.misc["height_mapC"] = h = {}
     for key, value in heights.items():
         if isinstance(value, TAG_Long_Array):
             try:
                 h[key] = (
                     decode_long_array(
                         value.value,
                         256,
                         (bounds[1] - bounds[0]).bit_length(),
                         dense=self.LongArrayDense,
                     ).reshape((16, 16))
                     + bounds[0]
                 )
             except Exception as e:
                 log.warning(e)
예제 #6
0
 def _decode_block_section(
         self,
         section: TAG_Compound) -> Optional[Tuple[numpy.ndarray, list]]:
     block_states = self.get_obj(section, "block_states", TAG_Compound)
     if (isinstance(block_states, TAG_Compound) and "palette"
             in block_states):  # 1.14 makes block_palette/blocks optional.
         section_palette = self._decode_block_palette(
             block_states.pop("palette"))
         data = block_states.pop("data", None)
         if data is None:
             arr = numpy.zeros((16, 16, 16), numpy.uint32)
         else:
             decoded = decode_long_array(
                 data.value,
                 16**3,
                 max(4, (len(section_palette) - 1).bit_length()),
                 dense=self.LongArrayDense,
             ).astype(numpy.uint32)
             arr = numpy.transpose(decoded.reshape((16, 16, 16)), (2, 0, 1))
         return arr, section_palette
     else:
         return None
예제 #7
0
 def _decode_biome_section(
         self,
         section: TAG_Compound) -> Optional[Tuple[numpy.ndarray, list]]:
     biomes = self.get_obj(section, "biomes", TAG_Compound)
     if isinstance(biomes, TAG_Compound) and "palette" in biomes:
         section_palette = self._decode_biome_palette(biomes.pop("palette"))
         data = biomes.pop("data", None)
         if data is None:
             # TODO: in the new biome system just leave this as the number
             arr = numpy.zeros((4, 4, 4), numpy.uint32)
         else:
             arr = numpy.transpose(
                 decode_long_array(
                     data.value,
                     4**3,
                     (len(section_palette) - 1).bit_length(),
                     dense=self.LongArrayDense,
                 ).astype(numpy.uint32).reshape((4, 4, 4)),
                 (2, 0, 1),
             )
         return arr, section_palette
     else:
         return None
예제 #8
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:
            if "Biomes" in data["Level"]:
                biomes = data["Level"]["Biomes"].value
                if self.features["biomes"] in ["256BA", "256IA"]:
                    chunk.biomes = biomes.astype(numpy.uint32).reshape(
                        (16, 16))
                elif self.features["biomes"] == "1024IA":
                    chunk.biomes = {
                        sy: arr
                        for sy, arr in enumerate(
                            numpy.split(
                                numpy.transpose(
                                    biomes.astype(numpy.uint32).reshape(
                                        64, 4, 4),
                                    (2, 0, 1),
                                ),  # YZX -> XYZ
                                16,
                                1,
                            ))
                    }

        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"] = {
                    key: decode_long_array(value, 256,
                                           len(value) == 36)
                    for key, value in data["Level"]["Heightmaps"].items()
                }

        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