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
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}", )
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
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)
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
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
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