def _unpack_biomes( translation_manager: "TranslationManager", version_identifier: VersionIdentifierType, chunk: Chunk, ): """ Unpack the version-specific biome_palette into the stringified version where needed. :return: The biome_palette converted to biome objects. """ version = translation_manager.get_version(*version_identifier) if chunk.biomes.dimension == BiomesShape.Shape2D: biome_int_palette, biome_array = numpy.unique( chunk.biomes, return_inverse=True ) chunk.biomes = biome_array.reshape(chunk.biomes.shape) chunk._biome_palette = BiomeManager( [version.biome.unpack(biome) for biome in biome_int_palette] ) elif chunk.biomes.dimension == BiomesShape.Shape3D: biomes = {} palette = [] palette_length = 0 for sy in chunk.biomes.sections: biome_int_palette, biome_array = numpy.unique( chunk.biomes.get_section(sy), return_inverse=True ) biomes[sy] = ( biome_array.reshape(chunk.biomes.section_shape) + palette_length ) palette_length += len(biome_int_palette) palette.append(biome_int_palette) if palette: chunk_palette, lut = numpy.unique( numpy.concatenate(palette), return_inverse=True ) lut = lut.astype(numpy.uint32) for sy in biomes: biomes[sy] = lut[biomes[sy]] chunk.biomes = biomes chunk._biome_palette = BiomeManager( numpy.vectorize(version.biome.unpack)(chunk_palette) )
def _pack_biomes( translation_manager: "TranslationManager", version_identifier: VersionIdentifierType, chunk: Chunk, ): """ Unpack the version-specific biome_palette into the stringified version where needed. :return: The biome_palette converted to biome objects. """ version = translation_manager.get_version(*version_identifier) biome_palette = numpy.array( [version.biome.pack(biome) for biome in chunk.biome_palette], numpy.uint32 ) if chunk.biomes.dimension == BiomesShape.Shape2D: chunk.biomes = biome_palette[chunk.biomes] elif chunk.biomes.dimension == BiomesShape.Shape3D: chunk.biomes = { sy: biome_palette[chunk.biomes.get_section(sy)] for sy in chunk.biomes.sections } chunk._biome_palette = BiomeManager()
def _pack_biomes( translation_manager: "TranslationManager", version_identifier: VersionIdentifierType, chunk: Chunk, ): """ Unpack the version-specific biome_palette into the stringified version where needed. :return: The biome_palette converted to biome objects. """ version = translation_manager.get_version(*version_identifier) biome_palette = numpy.array( [version.biome.pack(biome) for biome in chunk.biome_palette] ) chunk.biomes = biome_palette[chunk.biomes] chunk._biome_palette = BiomeManager()
def _unpack_biomes( translation_manager: "TranslationManager", version_identifier: VersionIdentifierType, chunk: Chunk, ): """ Unpack the version-specific biome_palette into the stringified version where needed. :return: The biome_palette converted to biome objects. """ version = translation_manager.get_version(*version_identifier) biome_int_palette, biome_array = numpy.unique(chunk.biomes, return_inverse=True) chunk.biomes = biome_array chunk._biome_palette = BiomeManager( [version.biome.unpack(biome) for biome in biome_int_palette] )
def _decode_biome_sections(self, chunk: Chunk, chunk_sections: Dict[int, TAG_Compound]): biomes: Dict[int, numpy.ndarray] = {} palette = BiomeManager() for cy, section in chunk_sections.items(): data = self._decode_biome_section(section) if data is not None: arr, section_palette = data lut = numpy.array([ palette.get_add_biome(biome) for biome in section_palette ]) biomes[cy] = lut[arr].astype(numpy.uint32) chunk.biomes = biomes chunk.biome_palette = palette
def _decode_biomes(self, chunk: Chunk, compound: TAG_Compound, floor_cy: int): biomes = compound.pop("Biomes", None) if isinstance(biomes, TAG_Int_Array): if (len(biomes) / 16) % 4: log.error( f"The biome array size must be 4x4x4xN but got an array of size {biomes.value.size}" ) else: arr = numpy.transpose( biomes.astype(numpy.uint32).reshape((-1, 4, 4)), (2, 0, 1), ) # YZX -> XYZ chunk.biomes = { sy + floor_cy: arr for sy, arr in enumerate( numpy.split( arr, arr.shape[1] // 4, 1, ) ) }
def decode(self, cx: int, cz: int, data: Dict[bytes, bytes]) -> Tuple[Chunk, AnyNDArray]: # chunk_key_base = struct.pack("<ii", cx, cz) chunk = Chunk(cx, cz) chunk_palette = numpy.empty(0, dtype=object) if self.features["terrain"].startswith( "2f"): # ["2farray", "2f1palette", "2fnpalette"] subchunks = [ data.get(b"\x2F" + bytes([i]), None) for i in range(16) ] chunk.blocks, chunk_palette = self._load_subchunks(subchunks) elif self.features["terrain"] == "30array": chunk_data = data.get(b"\x30", None) if chunk_data is not None: block_ids = numpy.frombuffer(chunk_data[:2**15], dtype=numpy.uint8).astype( numpy.uint16) block_data = from_nibble_array( numpy.frombuffer(chunk_data[2**15:2**15 + 2**14], dtype=numpy.uint8)) # there is other data here but we are going to skip over it combined_palette, block_array = fast_unique( numpy.transpose( ((block_ids << 4) + block_data).reshape(16, 16, 128), (0, 2, 1))) chunk.blocks = { i: block_array[:, i * 16:(i + 1) * 16, :] for i in range(8) } palette: AnyNDArray = numpy.array( [combined_palette >> 4, combined_palette & 15]).T chunk_palette = numpy.empty(len(palette), dtype=object) for i, b in enumerate(palette): chunk_palette[i] = ((None, tuple(b)), ) else: raise Exception if self.features["finalised_state"] == "int0-2": if b"\x36" in data: val = struct.unpack("<i", data[b"\x36"])[0] else: val = 2 chunk.status = val if self.features["data_2d"] in [ "height512|biome256", "unused_height512|biome256", ]: d2d = data.get(b"\x2D", b"\x00" * 768) height, biome = d2d[:512], d2d[512:] if self.features["data_2d"] == "height512|biome256": pass # TODO: put this data somewhere chunk.biomes = numpy.frombuffer(biome, dtype="uint8").reshape(16, 16) # TODO: impliment key support # \x2D heightmap and biomes # \x31 block entity # \x32 entity # \x33 ticks # \x34 block extra data # \x35 biome state # \x39 7 ints and an end (03)? Honestly don't know what this is # \x3A fire tick? # \x2E 2d legacy # \x30 legacy terrain # unpack block entities and entities if self.features["block_entities"] == "31list": block_entities = self._unpack_nbt_list(data.get(b"\x31", b"")) chunk.block_entities = self._decode_block_entities(block_entities) if self.features["entities"] == "32list" and amulet.entity_support: entities = self._unpack_nbt_list(data.get(b"\x32", b"")) chunk.entities = self._decode_entities(entities) return chunk, chunk_palette
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
def decode(self, cx: int, cz: int, data: Dict[bytes, bytes], bounds: Tuple[int, int]) -> Tuple[Chunk, AnyNDArray]: """ Create an amulet.api.chunk.Chunk object from raw data given by the format :param cx: chunk x coordinate :param cz: chunk z coordinate :param data: Raw chunk data provided by the format. :param bounds: The minimum and maximum height of the chunk. :return: Chunk object in version-specific format, along with the block_palette for that chunk. """ chunk = Chunk(cx, cz) chunk_palette = numpy.empty(0, dtype=object) chunk.misc = {"bedrock_chunk_data": data} data.pop(b"v", None) data.pop(b",", None) if self._features["terrain"].startswith( "2f"): # ["2farray", "2f1palette", "2fnpalette"] subchunks = {} for key in data.copy().keys(): if len(key) == 2 and key[0:1] == b"\x2F": cy = struct.unpack("b", key[1:2])[0] subchunks[self._chunk_key_to_sub_chunk( cy, bounds[0] >> 4)] = data.pop(key) chunk.blocks, chunk_palette = self._load_subchunks(subchunks) elif self._features["terrain"] == "30array": chunk_data = data.pop(b"\x30", None) if chunk_data is not None: block_ids = numpy.frombuffer(chunk_data[:2**15], dtype=numpy.uint8).astype( numpy.uint16) block_data = from_nibble_array( numpy.frombuffer(chunk_data[2**15:2**15 + 2**14], dtype=numpy.uint8)) # there is other data here but we are going to skip over it combined_palette, block_array = fast_unique( numpy.transpose( ((block_ids << 4) + block_data).reshape(16, 16, 128), (0, 2, 1))) chunk.blocks = { i: block_array[:, i * 16:(i + 1) * 16, :] for i in range(8) } palette: AnyNDArray = numpy.array( [combined_palette >> 4, combined_palette & 15]).T chunk_palette = numpy.empty(len(palette), dtype=object) for i, b in enumerate(palette): chunk_palette[i] = ((None, tuple(b)), ) else: raise Exception if self._features["finalised_state"] == "int0-2": state = data.pop(b"\x36", None) val = 2 if isinstance(state, bytes): if len(state) == 1: # old versions of the game store this as a byte val = struct.unpack("b", state)[0] elif len(state) == 4: # newer versions store it as an int val = struct.unpack("<i", state)[0] chunk.status = val if b"+" in data: height, biome = self._decode_height_3d_biomes( data[b"+"], bounds[0] >> 4) chunk.misc["height"] = height chunk.biomes = biome elif b"\x2D" in data: d2d = data[b"\x2D"] height, biome = ( numpy.frombuffer(d2d[:512], "<i2").reshape((16, 16)), d2d[512:], ) chunk.misc["height"] = height chunk.biomes = numpy.frombuffer(biome, dtype="uint8").reshape(16, 16).T # TODO: implement key support # \x2D heightmap and biomes # \x31 block entity # \x32 entity # \x33 ticks # \x34 block extra data # \x35 biome state # \x39 7 ints and an end (03)? Honestly don't know what this is # \x3A fire tick? # \x2E 2d legacy # \x30 legacy terrain # unpack block entities and entities if self._features["block_entities"] == "31list": block_entities = self._unpack_nbt_list(data.pop(b"\x31", b"")) chunk.block_entities = self._decode_block_entity_list( block_entities) if self._features["entities"] == "32list" and amulet.entity_support: entities = self._unpack_nbt_list(data.pop(b"\x32", b"")) chunk.entities = self._decode_entity_list(entities) return chunk, chunk_palette
def _decode_biomes(self, chunk: Chunk, compound: TAG_Compound, floor_cy: int): biomes = compound.pop("Biomes", None) if isinstance(biomes, TAG_Byte_Array) and biomes.value.size == 256: chunk.biomes = biomes.astype(numpy.uint32).reshape((16, 16))