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}") self._version = self.translation_manager.get_version( self.platform, self.version).data_version self.root_tag = root = nbt.TAG_Compound() root["Data"] = data = nbt.TAG_Compound() data["version"] = nbt.TAG_Int(19133) data["DataVersion"] = nbt.TAG_Int(self._version) data["LastPlayed"] = nbt.TAG_Long(int(time.time() * 1000)) data["LevelName"] = nbt.TAG_String("World Created By Amulet") os.makedirs(self.path, exist_ok=True) self.root_tag.save_to(os.path.join(self.path, "level.dat")) self._reload_world()
def ints_to_block(self, block_id: int, block_data: int) -> "Block": if block_id in self._translation_manager.block_registry: ( namespace, base_name, ) = self._translation_manager.block_registry.private_to_str(block_id).split( ":", 1 ) elif block_id in self._numerical_block_map: namespace, base_name = self._numerical_block_map[block_id] else: return Block( namespace="minecraft", base_name="numerical", properties={ "block_id": amulet_nbt.TAG_Int(block_id), "block_data": amulet_nbt.TAG_Int(block_data), }, ) return Block( namespace=namespace, base_name=base_name, properties={"block_data": amulet_nbt.TAG_Int(block_data)}, )
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()
def decode( self, cx: int, cz: int, section: MCStructureChunk ) -> Tuple["Chunk", AnyNDArray]: palette = numpy.empty(len(section.palette) + 1, dtype=numpy.object) palette[0] = ( ( 17563649, Block( namespace="minecraft", base_name="air", properties={"block_data": amulet_nbt.TAG_Int(0)}, ), ), ) for index, blocks in enumerate(section.palette): block_layers: List[Tuple[Optional[int], Block]] = [] for block in blocks: namespace, base_name = block["name"].value.split(":", 1) if "version" in block: version: Optional[int] = block["version"].value else: version = None if "states" in block: # 1.13 format properties = block["states"].value if version is None: version = 17694720 # 1, 14, 0, 0 else: properties = {"block_data": amulet_nbt.TAG_Int(block["val"].value)} block_layers.append( ( version, Block( namespace=namespace, base_name=base_name, properties=properties, ), ) ) palette[index + 1] = block_layers chunk = Chunk(cx, cz) box = section.selection.create_moved_box((cx * 16, 0, cz * 16), subtract=True) chunk.blocks[box.slice] = section.blocks + 1 for b in section.block_entities: b = self._decode_block_entity( b, self._block_entity_id_type, self._block_entity_coord_type ) if b is not None: chunk.block_entities.insert(b) for b in section.entities: b = self._decode_entity( b, self._block_entity_id_type, self._block_entity_coord_type ) if b is not None: chunk.entities.append(b) return chunk, palette
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}" )
def _save_subchunks_1(self, blocks: "Blocks", palette: AnyNDArray) -> List[Optional[bytes]]: for index, block in enumerate(palette): block: Tuple[Tuple[None, Block], ...] block_data = block[0][1].properties.get("block_data", amulet_nbt.TAG_Int(0)) if isinstance(block_data, amulet_nbt.TAG_Int): block_data = block_data.value # if block_data >= 16: # block_data = 0 else: block_data = 0 palette[index] = amulet_nbt.NBTFile( amulet_nbt.TAG_Compound({ "name": amulet_nbt.TAG_String(block[0][1].namespaced_name), "val": amulet_nbt.TAG_Short(block_data), })) chunk = [] for cy in range(16): if cy in blocks: palette_index, sub_chunk = fast_unique( blocks.get_sub_chunk(cy)) sub_chunk_palette = list(palette[palette_index]) chunk.append(b"\x01" + self._save_palette_subchunk( sub_chunk.ravel(), sub_chunk_palette)) else: chunk.append(None) return chunk
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
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
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 ])
def encode( self, chunk: "Chunk", palette: AnyNDArray, max_world_version: VersionIdentifierType, box: SelectionBox, ) -> MCStructureChunk: """ Take a version-specific chunk and encode it to raw data for the format to store. :param chunk: The already translated version-specfic chunk to encode. :param palette: The block_palette the ids in the chunk correspond to. :type palette: numpy.ndarray[Block] :param max_world_version: The key to use to find the encoder. :param box: The volume of the chunk to pack. :return: Raw data to be stored by the Format. """ entities = [] for e in chunk.entities: if e.location in box: entities.append( self._encode_entity(e, self._entity_id_type, self._entity_coord_type).value) block_entities = [] for e in chunk.block_entities: if e.location in box: block_entities.append( self._encode_block_entity( e, self._block_entity_id_type, self._block_entity_coord_type).value) slices = box.create_moved_box((chunk.cx * 16, 0, chunk.cz * 16), subtract=True).slice out_palette = numpy.empty(palette.shape, dtype=object) for index, block_layers in enumerate(palette): blocks_out = [] for version, block in block_layers: block = amulet_nbt.TAG_Compound({ "name": amulet_nbt.TAG_String( f"{block.namespace}:{block.base_name}"), "states": amulet_nbt.TAG_Compound(block.properties), }) if version: block["version"] = amulet_nbt.TAG_Int(version) blocks_out.append(block) out_palette[index] = blocks_out return MCStructureChunk( box, numpy.asarray(chunk.blocks[slices]), out_palette, block_entities, entities, )
def _get_interface_key(self, raw_chunk_data: Optional[Any] = None ) -> Tuple[str, int]: if raw_chunk_data: return ( self.platform, raw_chunk_data.get("DataVersion", nbt.TAG_Int(-1)).value, ) else: return self.max_world_version
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)
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
def get_translator( self, max_world_version: Tuple[str, Union[int, Tuple[int, int, int]]], data: amulet_nbt.NBTFile = None, ) -> Tuple["Translator", int]: if data: data_version = data.get("DataVersion", amulet_nbt.TAG_Int(-1)).value key, version = (("java", data_version), data_version) else: key = max_world_version version = max_world_version[1] return translators.loader.get(key), version
def translate_block( input_object: Block, get_block_callback: Optional[GetBlockCallback], block_location: BlockCoordinates, ) -> TranslateBlockCallbackReturn: final_block = None final_block_entity = None final_entities = [] final_extra = False for depth, block in enumerate(input_object.block_tuple): ( output_object, output_block_entity, extra, ) = version.block.from_universal( block, get_block_callback=get_block_callback, block_location=block_location, ) if isinstance(output_object, Block): if __debug__ and output_object.namespace.startswith( "universal"): log.debug( f"Error translating {input_object.full_blockstate} from universal. Got {output_object.full_blockstate}" ) if version.data_version > 0: properties = output_object.properties properties["__version__"] = amulet_nbt.TAG_Int( version.data_version) output_object = Block( output_object.namespace, output_object.base_name, properties, output_object.extra_blocks, ) if final_block is None: final_block = output_object else: final_block += output_object if depth == 0: final_block_entity = output_block_entity elif isinstance(output_object, Entity): final_entities.append(output_object) # TODO: offset entity coords final_extra |= extra return final_block, final_block_entity, final_entities, final_extra
def _unpack_blocks( translation_manager: "TranslationManager", version_identifier: VersionIdentifierType, chunk: Chunk, block_palette: AnyNDArray, ): """ Unpacks an object array of block data into a numpy object array containing Block objects. :param translation_manager: :param version_identifier: :param chunk: :param block_palette: :type block_palette: numpy.ndarray[ Tuple[ Union[ Tuple[None, Tuple[int, int]], Tuple[None, Block], Tuple[int, Block] ], ... ] ] :return: """ palette_ = BlockManager() for palette_index, entry in enumerate(block_palette): entry: BedrockInterfaceBlockType block = None for version_number, b in entry: version_number: Optional[int] if isinstance(b, tuple): version = translation_manager.get_version( version_identifier[0], version_number or 17563649) b = version.block.ints_to_block(*b) elif isinstance(b, Block): if version_number is not None: properties = b.properties properties["__version__"] = amulet_nbt.TAG_Int( version_number) b = Block(b.namespace, b.base_name, properties, b.extra_blocks) else: raise Exception(f"Unsupported type {b}") if block is None: block = b else: block += b if block is None: raise Exception(f"Empty tuple") palette_.get_add_block(block) chunk._block_palette = palette_
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 ]), })
def encode( self, chunk: "Chunk", palette: AnyNDArray, max_world_version: Tuple[str, Union[int, Tuple[int, int, int]]], box: SelectionBox = None, ) -> MCStructureChunk: entities = [] for e in chunk.entities: if e.location in box: entities.append( self._encode_entity( e, self._entity_id_type, self._entity_coord_type ).value ) block_entities = [] for e in chunk.block_entities: if e.location in box: block_entities.append( self._encode_block_entity( e, self._block_entity_id_type, self._block_entity_coord_type ).value ) slices = box.create_moved_box( (chunk.cx * 16, 0, chunk.cz * 16), subtract=True ).slice out_palette = numpy.empty(palette.shape, dtype=object) for index, block_layers in enumerate(palette): blocks_out = [] for version, block in block_layers: block = amulet_nbt.TAG_Compound( { "name": amulet_nbt.TAG_String( f"{block.namespace}:{block.base_name}" ), "states": amulet_nbt.TAG_Compound(block.properties), } ) if version: block["version"] = amulet_nbt.TAG_Int(version) blocks_out.append(block) out_palette[index] = blocks_out return MCStructureChunk( box, chunk.blocks[slices], out_palette, block_entities, entities )
def decode(self, cx: int, cz: int, data: List[ConstructionSection]) -> 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. :return: Chunk object in version-specific format, along with the block_palette for that chunk. """ chunk = Chunk(cx, cz) palette = [] for section in data: if any(s == 0 for s in section.shape): continue if section.blocks is not None: shapex, shapey, shapez = section.shape sx = section.sx % 16 sy = section.sy sz = section.sz % 16 chunk.blocks[sx:sx + shapex, sy:sy + shapey, sz:sz + shapez, ] = section.blocks.astype( numpy.uint32) + len(palette) chunk.block_entities.update(section.block_entities) chunk.entities.extend(section.entities) palette += section.palette np_palette, inverse = numpy.unique(palette, return_inverse=True) np_palette = numpy.insert( np_palette, 0, Block( namespace="minecraft", base_name="air", properties={"block_data": amulet_nbt.TAG_Int(0)}, ), ) inverse += 1 np_palette: AnyNDArray inverse: numpy.ndarray for cy in chunk.blocks.sub_chunks: chunk.blocks.add_sub_chunk( cy, inverse[chunk.blocks.get_sub_chunk(cy)].astype(numpy.uint32)) return chunk, np_palette
def decode( self, cx: int, cz: int, data: List[ConstructionSection] ) -> Tuple["Chunk", AnyNDArray]: chunk = Chunk(cx, cz) palette = [] for section in data: if any(s == 0 for s in section.shape): continue if section.blocks is not None: shapex, shapey, shapez = section.shape sx = section.sx - ((section.sx >> 4) << 4) sy = section.sy sz = section.sz - ((section.sz >> 4) << 4) chunk.blocks[ sx : sx + shapex, sy : sy + shapey, sz : sz + shapez, ] = section.blocks.astype(numpy.uint32) + len(palette) chunk.block_entities.update(section.block_entities) chunk.entities.extend(section.entities) palette += section.palette np_palette, inverse = numpy.unique(palette, return_inverse=True) np_palette = numpy.insert( np_palette, 0, Block( namespace="minecraft", base_name="air", properties={"block_data": amulet_nbt.TAG_Int(0)}, ), ) inverse += 1 np_palette: AnyNDArray inverse: numpy.ndarray for cy in chunk.blocks.sub_chunks: chunk.blocks.add_sub_chunk( cy, inverse[chunk.blocks.get_sub_chunk(cy)].astype(numpy.uint32) ) return chunk, np_palette
def _save_subchunks_8(self, blocks: "Blocks", palette: AnyNDArray) -> List[Optional[bytes]]: palette_depth = numpy.array([len(block) for block in palette]) if palette.size: if palette[0][0][0] is None: air = amulet_nbt.NBTFile( amulet_nbt.TAG_Compound({ "name": amulet_nbt.TAG_String("minecraft:air"), "val": amulet_nbt.TAG_Short(0), })) else: air = amulet_nbt.NBTFile( amulet_nbt.TAG_Compound({ "name": amulet_nbt.TAG_String("minecraft:air"), "states": amulet_nbt.TAG_Compound({}), "version": amulet_nbt.TAG_Int(17_629_184), # 1, 13, 0, 0 })) for index, block in enumerate(palette): block: Tuple[Tuple[Optional[int], Block], ...] full_block = [] for sub_block_version, sub_block in block: properties = sub_block.properties if sub_block_version is None: block_data = properties.get("block_data", amulet_nbt.TAG_Int(0)) if isinstance(block_data, amulet_nbt.TAG_Int): block_data = block_data.value # if block_data >= 16: # block_data = 0 else: block_data = 0 sub_block_ = amulet_nbt.NBTFile( amulet_nbt.TAG_Compound({ "name": amulet_nbt.TAG_String( sub_block.namespaced_name), "val": amulet_nbt.TAG_Short(block_data), })) else: sub_block_ = amulet_nbt.NBTFile( amulet_nbt.TAG_Compound({ "name": amulet_nbt.TAG_String( sub_block.namespaced_name), "states": amulet_nbt.TAG_Compound({ key: val for key, val in properties.items() if isinstance(val, PropertyDataTypes) }), "version": amulet_nbt.TAG_Int(sub_block_version), })) full_block.append(sub_block_) palette[index] = tuple(full_block) chunk = [] for cy in range(16): if cy in blocks: palette_index, sub_chunk = fast_unique( blocks.get_sub_chunk(cy)) sub_chunk_palette = palette[palette_index] sub_chunk_depth = palette_depth[palette_index].max() if (sub_chunk_depth == 1 and len(sub_chunk_palette) == 1 and sub_chunk_palette[0][0]["name"].value == "minecraft:air"): chunk.append(None) else: # pad block_palette with air in the extra layers sub_chunk_palette_full = numpy.empty( (sub_chunk_palette.size, sub_chunk_depth), dtype=object) sub_chunk_palette_full.fill(air) for index, block_tuple in enumerate(sub_chunk_palette): for sub_index, block in enumerate(block_tuple): sub_chunk_palette_full[index, sub_index] = block # should now be a 2D array with an amulet_nbt.NBTFile in each element sub_chunk_bytes = [b"\x08", bytes([sub_chunk_depth])] for sub_chunk_layer_index in range(sub_chunk_depth): # TODO: sort out a way to do this quicker without brute forcing it. ( sub_chunk_layer_palette, sub_chunk_remap, ) = brute_sort_objects_no_hash( sub_chunk_palette_full[:, sub_chunk_layer_index]) sub_chunk_layer = sub_chunk_remap[ sub_chunk.ravel()] # sub_chunk_layer, sub_chunk_layer_palette = sub_chunk, sub_chunk_palette_full[:, sub_chunk_layer_index] sub_chunk_bytes.append( self._save_palette_subchunk( sub_chunk_layer.reshape(16, 16, 16), list(sub_chunk_layer_palette.ravel()), )) chunk.append(b"".join(sub_chunk_bytes)) else: chunk.append(None) else: chunk = [None] * 16 return chunk
def decode(self, cx: int, cz: int, data: MCStructureChunk) -> 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. :return: Chunk object in version-specific format, along with the block_palette for that chunk. """ palette = numpy.empty(len(data.palette) + 1, dtype=object) palette[0] = (( 17563649, Block( namespace="minecraft", base_name="air", properties={"block_data": amulet_nbt.TAG_Int(0)}, ), ), ) for index, blocks in enumerate(data.palette): block_layers: List[Tuple[Optional[int], Block]] = [] for block in blocks: namespace, base_name = block["name"].value.split(":", 1) if "version" in block: version: Optional[int] = block["version"].value else: version = None if "states" in block: # 1.13 format properties = block["states"].value if version is None: version = 17694720 # 1, 14, 0, 0 else: properties = { "block_data": amulet_nbt.TAG_Int(block["val"].value) } block_layers.append(( version, Block( namespace=namespace, base_name=base_name, properties=properties, ), )) palette[index + 1] = block_layers chunk = Chunk(cx, cz) box = data.selection.create_moved_box((cx * 16, 0, cz * 16), subtract=True) chunk.blocks[box.slice] = data.blocks + 1 for b in data.block_entities: b = self._decode_block_entity(b, self._block_entity_id_type, self._block_entity_coord_type) if b is not None: chunk.block_entities.insert(b) for b in data.entities: b = self._decode_entity(b, self._block_entity_id_type, self._block_entity_coord_type) if b is not None: chunk.entities.append(b) return chunk, palette
def _load_subchunks( self, subchunks: List[None, bytes] ) -> Tuple[Dict[int, SubChunkNDArray], AnyNDArray]: """ Load a list of bytes objects which contain chunk data This function should be able to load all sub-chunk formats (technically before it) All sub-chunks will almost certainly all have the same sub-chunk version but it should be able to handle a case where that is not true. As such this function will return a Chunk and a rather complicated block_palette The newer formats allow multiple blocks to occupy the same space and the newer versions also include a version ber block. So this will also need returning for the translator to handle. The block_palette will be a numpy array containing tuple objects The tuple represents the "block" however can contain more than one Block object. Inside the tuple are one or more tuples. These include the block version number and the block itself The block version number will be either None if no block version is given or a tuple containing 4 ints. The block will be either a Block class for the newer formats or a tuple of two ints for the older formats """ blocks: Dict[int, SubChunkNDArray] = {} palette: List[Tuple[Tuple[Optional[int], Union[Tuple[int, int], Block], ], ...]] = [(( 17563649, Block( namespace="minecraft", base_name="air", properties={ "block_data": amulet_nbt.TAG_Int(0) }, ), ), )] for cy, data in enumerate(subchunks): if data is None: continue if data[0] in [0, 2, 3, 4, 5, 6, 7]: block_ids = numpy.frombuffer(data[1:1 + 2**12], dtype=numpy.uint8).astype( numpy.uint16) block_data = from_nibble_array( numpy.frombuffer(data[1 + 2**12:1 + 2**12 + 2**11], dtype=numpy.uint8)) combined_palette, block_array = fast_unique( numpy.transpose( ((block_ids << 4) + block_data).reshape(16, 16, 16), (0, 2, 1))) blocks[cy] = block_array + len(palette) for b in numpy.array( [combined_palette >> 4, combined_palette & 15]).T: palette.append(((None, tuple(b)), )) elif data[0] in [1, 8]: if data[0] == 1: storage_count = 1 data = data[1:] else: storage_count, data = data[1], data[2:] sub_chunk_blocks = numpy.zeros((16, 16, 16, storage_count), dtype=numpy.uint32) sub_chunk_palette: List[List[Tuple[Optional[int], Block]]] = [] for storage_index in range(storage_count): ( sub_chunk_blocks[:, :, :, storage_index], palette_data, data, ) = self._load_palette_blocks(data) palette_data_out: List[Tuple[Optional[int], Block]] = [] for block in palette_data: namespace, base_name = block["name"].value.split( ":", 1) if "version" in block: version: Optional[int] = block["version"].value else: version = None if "states" in block: # 1.13 format properties = block["states"].value if version is None: version = 17694720 # 1, 14, 0, 0 else: properties = { "block_data": amulet_nbt.TAG_Int(block["val"].value) } palette_data_out.append(( version, Block( namespace=namespace, base_name=base_name, properties=properties, ), )) sub_chunk_palette.append(palette_data_out) if storage_count == 1: blocks[cy] = sub_chunk_blocks[:, :, :, 0] + len(palette) palette += [(val, ) for val in sub_chunk_palette[0]] elif storage_count > 1: # we have two or more storages so need to find the unique block combinations and merge them together sub_chunk_palette_, sub_chunk_blocks = numpy.unique( sub_chunk_blocks.reshape(-1, storage_count), return_inverse=True, axis=0, ) blocks[cy] = sub_chunk_blocks.reshape(16, 16, 16).astype( numpy.uint32) + len(palette) palette += [ tuple( sub_chunk_palette[storage_index][index] for storage_index, index in enumerate( palette_indexes) if not (storage_index > 0 and sub_chunk_palette[storage_index][index] [1].namespaced_name == "minecraft:air")) for palette_indexes in sub_chunk_palette_ ] else: continue # block_palette should now look like this # List[ # Tuple[ # Tuple[version, Block], ... # ] # ] numpy_palette, lut = brute_sort_objects(palette) for cy in blocks.keys(): blocks[cy] = lut[blocks[cy]] return blocks, numpy_palette
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}" )
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
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)
def open_from(self, f: BinaryIO): mcstructure = amulet_nbt.load(f, little_endian=True) if mcstructure["format_version"].value == 1: min_point = numpy.array( tuple(c.value for c in mcstructure["structure_world_origin"])) max_point = min_point + tuple(c.value for c in mcstructure["size"]) selection = SelectionBox(min_point, max_point) self._bounds[self.dimensions[0]] = SelectionGroup(selection) translator_version = self.translation_manager.get_version( "bedrock", (999, 999, 999)) self._platform = translator_version.platform self._version = translator_version.version_number blocks_array: numpy.ndarray = numpy.array( [[b.value for b in layer] for layer in mcstructure["structure"]["block_indices"]], dtype=numpy.int32, ).reshape((len(mcstructure["structure"]["block_indices"]), *selection.shape)) palette_key = list(mcstructure["structure"]["palette"].keys())[ 0] # find a way to do this based on user input block_palette = list(mcstructure["structure"]["palette"] [palette_key]["block_palette"]) if -1 in blocks_array[0]: blocks_array[0][blocks_array[0] == -1] = len(block_palette) block_palette.append( amulet_nbt.TAG_Compound({ "name": amulet_nbt.TAG_String("minecraft:structure_void"), "states": amulet_nbt.TAG_Compound(), "version": amulet_nbt.TAG_Int(17694723), # 1.13.0 })) for cx, cz in selection.chunk_locations(): chunk_box = SelectionBox.create_chunk_box( cx, cz).intersection(selection) array_slice = (slice(None), ) + 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_.reshape((chunk_blocks_.shape[0], -1)).T, return_inverse=True, axis=0, ) chunk_blocks = chunk_blocks.reshape(chunk_blocks_.shape[1:]) chunk_palette = numpy.empty(len(chunk_palette_indexes), dtype=object) for palette_index, indexes in enumerate(chunk_palette_indexes): chunk_palette[palette_index] = tuple(block_palette[index] for index in indexes if index >= 0) self._chunks[(cx, cz)] = ( chunk_box, chunk_blocks, chunk_palette, [], [], ) block_entities = { int(key): val["block_entity_data"] for key, val in mcstructure["structure"]["palette"] [palette_key]["block_position_data"].items() if "block_entity_data" in val } for location, block_entity in block_entities.items(): if all(key in block_entity for key in "xyz"): x, y, z = ( block_entity["x"].value, block_entity["y"].value, block_entity["z"].value, ) 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) entities = list(mcstructure["structure"]["entities"]) for entity in entities: if "Pos" in entity: x, y, z = ( entity["Pos"][0].value, entity["Pos"][1].value, entity["Pos"][2].value, ) 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 Exception( f"mcstructure file with format_version=={mcstructure['format_version'].value} cannot be read" )
def _encode_subchunks( self, blocks: "Blocks", palette: AnyNDArray, bounds: Tuple[int, int], max_world_version: VersionNumberTuple, ) -> Dict[int, Optional[bytes]]: # Encode sub-chunk block format 8 palette_depth = numpy.array([len(block) for block in palette]) min_y = bounds[0] // 16 max_y = bounds[1] // 16 if palette.size: if palette[0][0][0] is None: air = amulet_nbt.NBTFile( amulet_nbt.TAG_Compound({ "name": amulet_nbt.TAG_String("minecraft:air"), "val": amulet_nbt.TAG_Short(0), })) else: air = amulet_nbt.NBTFile( amulet_nbt.TAG_Compound({ "name": amulet_nbt.TAG_String("minecraft:air"), "states": amulet_nbt.TAG_Compound({}), "version": amulet_nbt.TAG_Int(17_629_184), # 1, 13, 0, 0 })) for index, block in enumerate(palette): block: Tuple[Tuple[Optional[int], Block], ...] full_block = [] for sub_block_version, sub_block in block: properties = sub_block.properties if sub_block_version is None: block_data = properties.get("block_data", amulet_nbt.TAG_Int(0)) if isinstance(block_data, amulet_nbt.TAG_Int): block_data = block_data.value # if block_data >= 16: # block_data = 0 else: block_data = 0 sub_block_ = amulet_nbt.NBTFile( amulet_nbt.TAG_Compound({ "name": amulet_nbt.TAG_String( sub_block.namespaced_name), "val": amulet_nbt.TAG_Short(block_data), })) else: sub_block_ = amulet_nbt.NBTFile( amulet_nbt.TAG_Compound({ "name": amulet_nbt.TAG_String( sub_block.namespaced_name), "states": amulet_nbt.TAG_Compound({ key: val for key, val in properties.items() if isinstance(val, PropertyDataTypes) }), "version": amulet_nbt.TAG_Int(sub_block_version), })) full_block.append(sub_block_) palette[index] = tuple(full_block) chunk = {} for cy in range(min_y, max_y): if cy in blocks: palette_index, sub_chunk = fast_unique( blocks.get_sub_chunk(cy)) sub_chunk_palette = palette[palette_index] sub_chunk_depth = palette_depth[palette_index].max() if (sub_chunk_depth == 1 and len(sub_chunk_palette) == 1 and sub_chunk_palette[0][0]["name"].value == "minecraft:air"): chunk[cy] = None else: # pad block_palette with air in the extra layers sub_chunk_palette_full = numpy.empty( (sub_chunk_palette.size, sub_chunk_depth), dtype=object) sub_chunk_palette_full.fill(air) for index, block_tuple in enumerate(sub_chunk_palette): for sub_index, block in enumerate(block_tuple): sub_chunk_palette_full[index, sub_index] = block # should now be a 2D array with an amulet_nbt.NBTFile in each element if max_world_version[1] >= ( 1, 17, 30, ): # Why do I need to check against game version and not chunk version sub_chunk_bytes = [ b"\x09", bytes([sub_chunk_depth]), struct.pack("b", cy), ] else: sub_chunk_bytes = [ b"\x08", bytes([sub_chunk_depth]) ] for sub_chunk_layer_index in range(sub_chunk_depth): # TODO: sort out a way to do this quicker without brute forcing it. ( sub_chunk_layer_palette, sub_chunk_remap, ) = brute_sort_objects_no_hash( sub_chunk_palette_full[:, sub_chunk_layer_index]) sub_chunk_layer = sub_chunk_remap[ sub_chunk.ravel()] # sub_chunk_layer, sub_chunk_layer_palette = sub_chunk, sub_chunk_palette_full[:, sub_chunk_layer_index] sub_chunk_bytes.append( self._save_palette_subchunk( sub_chunk_layer.reshape(16, 16, 16), list(sub_chunk_layer_palette.ravel()), )) chunk[cy] = b"".join(sub_chunk_bytes) else: chunk[cy] = None else: chunk = {i: None for i in range(min_y, max_y)} return chunk
def _get_version(self) -> int: return (self.root_tag.get("Data", nbt.TAG_Compound()).get( "DataVersion", nbt.TAG_Int(-1)).value)
def _get_interface_key(self, raw_chunk_data) -> Tuple[str, int]: return self.platform, raw_chunk_data.get("DataVersion", nbt.TAG_Int(-1)).value