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
    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)
    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}",
                        )
Beispiel #4
0
    def _encode_block_section(
        self,
        chunk: Chunk,
        sections: Dict[int, TAG_Compound],
        palette: AnyNDArray,
        cy: int,
    ) -> bool:
        if cy in chunk.blocks:
            block_sub_array = numpy.transpose(chunk.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_block_palette(palette[sub_palette_])
            if (len(sub_palette) == 1
                    and sub_palette[0]["Name"].value == "minecraft:air"):
                return False

            section = sections.setdefault(cy, TAG_Compound())
            section["BlockStates"] = TAG_Long_Array(
                encode_long_array(block_sub_array,
                                  dense=self.LongArrayDense,
                                  min_bits_per_entry=4))
            section["Palette"] = sub_palette
            return True
        return False
Beispiel #5
0
    def _encode_biome_section(
        self,
        chunk: Chunk,
        sections: Dict[int, TAG_Compound],
        cy: int,
    ) -> bool:
        chunk.biomes.convert_to_3d()
        if cy in chunk.biomes:
            biome_sub_array = numpy.transpose(chunk.biomes.get_section(cy),
                                              (1, 2, 0)).ravel()

            sub_palette_, biome_sub_array = numpy.unique(biome_sub_array,
                                                         return_inverse=True)
            sub_palette = self._encode_biome_palette(
                chunk.biome_palette[sub_palette_])
            section = sections.setdefault(cy, TAG_Compound())
            biomes = section["biomes"] = TAG_Compound({"palette": sub_palette})
            if len(sub_palette) != 1:
                biomes["data"] = TAG_Long_Array(
                    encode_long_array(biome_sub_array,
                                      dense=self.LongArrayDense))
            return True
        return False
Beispiel #6
0
 def _encode_height(
     self, chunk: Chunk, level: TAG_Compound, bounds: Tuple[int, int]
 ):
     maps = [
         "WORLD_SURFACE_WG",
         "OCEAN_FLOOR_WG",
         "MOTION_BLOCKING",
         "MOTION_BLOCKING_NO_LEAVES",
         "OCEAN_FLOOR",
     ]
     if self._features["height_map"] == "C|V1":  # 1466
         maps = ("LIQUID", "SOLID", "LIGHT", "RAIN")
     elif self._features["height_map"] == "C|V2":  # 1484
         maps.append("LIGHT_BLOCKING")
     elif self._features["height_map"] == "C|V3":  # 1503
         maps.append("LIGHT_BLOCKING")
         maps.append("WORLD_SURFACE")
     elif self._features["height_map"] == "C|V4":  # 1908
         maps.append("WORLD_SURFACE")
     else:
         raise Exception
     heightmaps_temp: Dict[str, numpy.ndarray] = chunk.misc.get("height_mapC", {})
     heightmaps = TAG_Compound()
     for heightmap in maps:
         if (
             heightmap in heightmaps_temp
             and isinstance(heightmaps_temp[heightmap], numpy.ndarray)
             and heightmaps_temp[heightmap].size == 256
         ):
             heightmaps[heightmap] = TAG_Long_Array(
                 encode_long_array(
                     heightmaps_temp[heightmap].ravel() - bounds[0],
                     (bounds[1] - bounds[0]).bit_length(),
                     self.LongArrayDense,
                 )
             )
     level["Heightmaps"] = heightmaps
Beispiel #7
0
    def _encode_block_section(
        self,
        chunk: Chunk,
        sections: Dict[int, TAG_Compound],
        palette: AnyNDArray,
        cy: int,
    ):
        if cy in chunk.blocks:
            block_sub_array = numpy.transpose(chunk.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_block_palette(palette[sub_palette_])
            section = sections.setdefault(cy, TAG_Compound())
            block_states = section["block_states"] = TAG_Compound(
                {"palette": sub_palette})
            if len(sub_palette) != 1:
                block_states["data"] = TAG_Long_Array(
                    encode_long_array(block_sub_array,
                                      dense=self.LongArrayDense,
                                      min_bits_per_entry=4))
            return True
        return False
Beispiel #8
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":  # TODO: support the optional variant
            if chunk.status.value > -0.7:
                chunk.biomes.convert_to_2d()
                data["Level"]["Biomes"] = amulet_nbt.TAG_Byte_Array(
                    chunk.biomes.astype(dtype=numpy.uint8))
        elif self.features["biomes"] == "256IA":
            if chunk.status.value > -0.7:
                chunk.biomes.convert_to_2d()
                data["Level"]["Biomes"] = amulet_nbt.TAG_Int_Array(
                    chunk.biomes.astype(dtype=numpy.uint32))
        elif self.features["biomes"] == "1024IA":
            if chunk.status.value > -0.7:
                chunk.biomes.convert_to_3d()
                data["Level"]["Biomes"] = amulet_nbt.TAG_Int_Array(
                    numpy.transpose(
                        numpy.asarray(chunk.biomes[:, 0:64, :]).astype(
                            numpy.uint32),
                        (1, 2, 0),
                    ).ravel(),  # YZX -> XYZ
                )

        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_temp: Dict[str,
                                  numpy.ndarray] = misc.get("height_mapC", {})
            heightmaps = amulet_nbt.TAG_Compound()
            heightmap_length = (36 if max_world_version[1] < 2556 else 37
                                )  # this value is probably actually much lower
            for heightmap in maps:
                if heightmap in heightmaps_temp:
                    array = encode_long_array(heightmaps_temp[heightmap],
                                              max_world_version[1] < 2556, 9)
                    assert (
                        array.size == heightmap_length
                    ), f"Expected an array of length {heightmap_length} but got an array of length {array.size}"
                    heightmaps[heightmap] = amulet_nbt.TAG_Long_Array(array)
                else:
                    heightmaps[heightmap] = amulet_nbt.TAG_Long_Array(
                        numpy.zeros(heightmap_length, 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