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 _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 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 close(self): self._data["Entities"] = amulet_nbt.TAG_List(self._entities) self._data["TileEntities"] = amulet_nbt.TAG_List(self._block_entities) self._data["Data"] = amulet_nbt.TAG_Byte_Array( numpy.transpose(self._block_data, (1, 2, 0)) # XYZ => YZX ) self._data["Blocks"] = amulet_nbt.TAG_Byte_Array( numpy.transpose((self._blocks & 0xFF).astype(numpy.uint8), (1, 2, 0))) if numpy.max(self._blocks) > 0xFF: add_blocks = numpy.transpose(self._blocks & 0xF00, (1, 2, 0)) >> 8 self._data["AddBlocks"] = amulet_nbt.TAG_Byte_Array( add_blocks[::2] + (add_blocks[1::2] << 4)) self._data.save_to(self._path_or_buffer)
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 _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 __init__(self, path_or_buffer: str, platform: str, selection: SelectionBox): self._path_or_buffer = path_or_buffer if platform == "java": self._materials = "Alpha" elif platform == "bedrock": self._materials = "Pocket" else: raise Exception( f'"{platform}" is not a supported platform for a schematic file.' ) self._selection = selection self._data = amulet_nbt.NBTFile( amulet_nbt.TAG_Compound( { "TileTicks": amulet_nbt.TAG_List(), "Width": amulet_nbt.TAG_Short(selection.size_x), "Height": amulet_nbt.TAG_Short(selection.size_y), "Length": amulet_nbt.TAG_Short(selection.size_z), "Materials": amulet_nbt.TAG_String(self._materials), } ), "Schematic", ) self._entities = [] self._block_entities = [] self._blocks = numpy.zeros( selection.shape, dtype=numpy.uint16 ) # only 12 bits are actually used at most self._block_data = numpy.zeros( selection.shape, dtype=numpy.uint8 ) # only 4 bits are used
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 _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 _encode_palette(blockstates: list) -> amulet_nbt.TAG_List: palette = amulet_nbt.TAG_List() for block in blockstates: entry = amulet_nbt.TAG_Compound() entry["Name"] = amulet_nbt.TAG_String( f"{block.namespace}:{block.base_name}") entry["Properties"] = amulet_nbt.TAG_Compound(block.properties) palette.append(entry) return palette
def _encode_entities(self, entities: Iterable["Entity"]) -> amulet_nbt.TAG_List: entities_out = [] for entity in entities: nbt = self._encode_entity( entity, self.features["entity_format"], self.features["entity_coord_format"], ) if nbt is not None: entities_out.append(nbt.value) return amulet_nbt.TAG_List(entities_out)
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 _serialise_entities(entities: List[Entity]) -> amulet_nbt.TAG_List: return amulet_nbt.TAG_List( [ amulet_nbt.TAG_Compound( { "namespace": amulet_nbt.TAG_String(entity.namespace), "base_name": amulet_nbt.TAG_String(entity.base_name), "x": amulet_nbt.TAG_Double(entity.x), "y": amulet_nbt.TAG_Double(entity.y), "z": amulet_nbt.TAG_Double(entity.z), "nbt": entity.nbt.value, } ) for entity in entities ] )
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 pack_palette(palette: BlockManager) -> amulet_nbt.TAG_List: block_palette_nbt = amulet_nbt.TAG_List() extra_blocks = set() for block in palette.blocks(): if len(block.extra_blocks) > 0: extra_blocks.update(block.extra_blocks) extra_blocks = list(extra_blocks) palette_len = len(palette) for block_entry in palette.blocks(): block_palette_nbt.append( generate_block_entry(block_entry, palette_len, extra_blocks)) for extra_block in extra_blocks: block_palette_nbt.append( generate_block_entry(extra_block, palette_len, extra_blocks)) return block_palette_nbt
def _encode_blocks(self, blocks: "Blocks", palette: AnyNDArray) -> nbt.TAG_List: sections = nbt.TAG_List() for cy in range(16): # perhaps find a way to do this dynamically if cy in blocks: block_sub_array = palette[numpy.transpose( blocks.get_sub_chunk(cy), (1, 2, 0)).ravel()] data_sub_array = block_sub_array[:, 1] block_sub_array = block_sub_array[:, 0] if not numpy.any(block_sub_array) and not numpy.any( data_sub_array): continue section = nbt.TAG_Compound() section["Y"] = nbt.TAG_Byte(cy) section["Blocks"] = nbt.TAG_Byte_Array( block_sub_array.astype("uint8")) section["Data"] = nbt.TAG_Byte_Array( world_utils.to_nibble_array(data_sub_array)) sections.append(section) return sections
def save_to(self, f: BinaryIO): selection = self._bounds[self.dimensions[0]].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) palette: List[AnyNDArray] = [] if self.version < (1, 13, 0): raise Exception( "Writing to mcstructre files in pre-1.13 format is not currently supported." ) else: arr = numpy.empty(1, dtype=object) arr[0] = [ amulet_nbt.TAG_Compound({ "name": amulet_nbt.TAG_String("minecraft:air"), "states": amulet_nbt.TAG_Compound(), "version": amulet_nbt.TAG_Int(17694723), }) ] palette.append(arr) palette_len = 1 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["name"] != "minecraft:structure_void": 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 fillbarrels(chunk, barrelPositionList, barrelBlock, currentArticle, booksPerBarrel, zimFilePath, chunkList, target_pos): """Generates all barrels in the chunk and fills them with books/articles""" for barrelPos in barrelPositionList: books = [] titles = [] start = time.perf_counter() if booksPerBarrel > 30: pool = Pool( processes=4 ) #on my laptop ~4 processes was faster than any amount of threads (4 = logic core count) else: pool = ThreadPool( processes=3 ) #the article reading is mostly cpu limited, so going high on process count doesnt help outputs = pool.map( partial(tryGetArticle, zimFilePath=zimFilePath, barrelPositionList=barrelPositionList, booksPerBarrel=booksPerBarrel, chunkList=chunkList, target_pos=target_pos), range(currentArticle, currentArticle + booksPerBarrel)) pool.close() #outputs = [] #for id in range(currentArticle, currentArticle + booksPerBarrel): # outputs.append(tryGetArticle(id, zimFilePath)) currentArticle += booksPerBarrel for output in outputs: if output[0] == None: continue titles.append(output[1]) books.append(output[0]) stop = time.perf_counter() #print("generating a book", (stop-start)/booksPerBarrel) chunk.blocks[barrelPos] = barrelBlock barrelEntity = BlockEntity("java", "barrel", barrelPos[0] + chunk.cx * 16, barrelPos[1], barrelPos[2] + chunk.cz * 16,\ amulet_nbt.NBTFile(\ value = amulet_nbt.TAG_Compound(\ {\ "utags": amulet_nbt.TAG_Compound(\ {\ "keepPacked": amulet_nbt.TAG_Byte(0),\ "isMovable": amulet_nbt.TAG_Byte(1),\ "Findable": amulet_nbt.TAG_Byte(0),\ "CustomName": amulet_nbt.TAG_String("{\"text\":\"x:%d y:%d z:%d\"}"%(barrelPos[0] + chunk.cx * 16, barrelPos[1], barrelPos[2] + chunk.cz * 16)),\ "Items": amulet_nbt.TAG_List(\ value = [ amulet_nbt.TAG_Compound(\ {\ "Slot": amulet_nbt.TAG_Byte(iBook),\ "Count": amulet_nbt.TAG_Byte(1),\ "id": amulet_nbt.TAG_String("minecraft:written_book"),\ "tag": amulet_nbt.TAG_Compound(\ { "pages": amulet_nbt.TAG_List(\ value=[amulet_nbt.TAG_String(page) for page in books[iBook]],\ list_data_type = 8\ ),\ "title": amulet_nbt.TAG_String(titles[iBook]),\ "author": amulet_nbt.TAG_String("Pos: x:%d y:%d z:%d, ID: %d"%(barrelPos[0] + chunk.cx * 16, barrelPos[1], barrelPos[2] + chunk.cz * 16, currentArticle + iBook)), }) }) for iBook in range(len(books)) ], list_data_type = 9\ ) })\ }))) chunk.block_entities.insert(barrelEntity)
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 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 __init__(self, path_or_buffer: PathOrBuffer): if isinstance(path_or_buffer, str): assert path_or_buffer.endswith( ".schematic"), "File selected is not a .schematic file" assert os.path.isfile( path_or_buffer ), f"There is no schematic file at path {path_or_buffer}" schematic = amulet_nbt.load(path_or_buffer) assert not all(key in schematic for key in ( "Version", "Data Version", "BlockData")), "This file is not a legacy schematic file." else: assert hasattr(path_or_buffer, "read"), "Object does not have a read method" schematic = amulet_nbt.load(buffer=path_or_buffer) materials = schematic.get("Materials", amulet_nbt.TAG_String()).value if materials == "Alpha": self._platform = "java" elif materials == "Pocket": self._platform = "bedrock" else: raise Exception( f'"{materials}" is not a supported platform for a schematic file.' ) self._chunks: Dict[ChunkCoordinates, Tuple[SelectionBox, BlockArrayType, BlockDataArrayType, list, list], ] = {} self._selection = SelectionBox( (0, 0, 0), ( schematic["Width"].value, schematic["Height"].value, schematic["Length"].value, ), ) entities: amulet_nbt.TAG_List = schematic.get("Entities", amulet_nbt.TAG_List()) block_entities: amulet_nbt.TAG_List = schematic.get( "TileEntities", amulet_nbt.TAG_List()) blocks: numpy.ndarray = schematic["Blocks"].value.astype( numpy.uint8).astype(numpy.uint16) if "AddBlocks" in schematic: add_blocks = schematic["AddBlocks"] blocks = blocks + (numpy.concatenate([ [(add_blocks & 0xF0) >> 4], [add_blocks & 0xF] ]).T.ravel().astype(numpy.uint16) << 8)[:blocks.size] max_point = self._selection.max temp_shape = (max_point[1], max_point[2], max_point[0]) blocks = numpy.transpose(blocks.reshape(temp_shape), (2, 0, 1)) # YZX => XYZ data = numpy.transpose(schematic["Data"].value.reshape(temp_shape), (2, 0, 1)) for cx, cz in self._selection.chunk_locations(): box = SelectionBox( (cx * 16, 0, cz * 16), ( min((cx + 1) * 16, self._selection.size_x), self._selection.size_y, min((cz + 1) * 16, self._selection.size_z), ), ) self._chunks[(cx, cz)] = (box, blocks[box.slice], data[box.slice], [], []) for e in block_entities: if all(key in e for key in ("x", "y", "z")): x, y, z = e["x"].value, e["y"].value, e["z"].value if (x, y, z) in self._selection: cx = x >> 4 cz = z >> 4 self._chunks[(cx, cz)][3].append(e) for e in entities: if "Pos" in e: pos: PointCoordinates = tuple( map(lambda t: t.value, e["Pos"].value)) if pos in self._selection: cx = int(pos[0]) >> 4 cz = int(pos[2]) >> 4 self._chunks[(cx, cz)][4].append(e)
def open_from(self, f: BinaryIO): sponge_schem = amulet_nbt.load(f) version = sponge_schem.get("Version") if not isinstance(version, amulet_nbt.TAG_Int): raise SpongeSchemReadError( "Version key must exist and be an integer.") if version == 1: raise SpongeSchemReadError( "Sponge Schematic Version 1 is not supported currently.") elif version == 2: offset = sponge_schem.get("Offset") if isinstance(offset, amulet_nbt.TAG_Int_Array) and len(offset) == 3: min_point = numpy.array(offset) else: min_point = numpy.array([0, 0, 0], dtype=numpy.int32) size = [] for key in ("Width", "Height", "Length"): val = sponge_schem.get(key) if not isinstance(val, amulet_nbt.TAG_Short): raise SpongeSchemReadError( f"Key {key} must exist and be a TAG_Short.") # convert to an unsigned short val = val.value if val < 0: val += 2**16 size.append(val) max_point = min_point + size selection = SelectionBox(min_point, max_point) self._bounds[self.dimensions[0]] = SelectionGroup(selection) data_version = sponge_schem.get("DataVersion") if not isinstance(data_version, amulet_nbt.TAG_Int): raise SpongeSchemReadError("DataVersion must be a TAG_Int.") translator_version = self.translation_manager.get_version( "java", int(data_version)) self._platform = translator_version.platform self._version = translator_version.data_version packed_block_data = sponge_schem.get("BlockData") if not isinstance(packed_block_data, amulet_nbt.TAG_Byte_Array): raise SpongeSchemReadError( "BlockData must be a TAG_Byte_Array") unpacked_block_data = decode_byte_array( numpy.array(packed_block_data, dtype=numpy.uint8)) if len(unpacked_block_data) != numpy.prod(size): raise SpongeSchemReadError( "The data contained in BlockData does not match the size of the schematic." ) dx, dy, dz = selection.shape blocks_array: numpy.ndarray = numpy.transpose( numpy.array( unpacked_block_data, dtype=numpy.uint32, ).reshape((dy, dz, dx)), (2, 0, 1), # YZX => XYZ ) if "Palette" not in sponge_schem: raise SpongeSchemReadError( "Amulet is not able to read Sponge Schem files with no block palette." ) palette_data = sponge_schem.get("Palette") if not isinstance(palette_data, amulet_nbt.TAG_Compound): raise SpongeSchemReadError("Palette must be a TAG_Compound.") block_palette: Dict[int, Block] = {} for blockstate, index in palette_data.items(): if index.value in block_palette: raise SpongeSchemReadError( f"Duplicate block index {index} found in the palette.") block_palette[index.value] = Block.from_string_blockstate( blockstate) if not numpy.all(numpy.isin(blocks_array, list(block_palette))): raise SpongeSchemReadError( "Some values in BlockData were not present in Palette") for cx, cz in selection.chunk_locations(): chunk_box = SelectionBox.create_chunk_box( cx, cz).intersection(selection) array_slice = 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_, return_inverse=True, ) chunk_blocks = chunk_blocks.reshape(chunk_blocks_.shape) chunk_palette = numpy.empty(len(chunk_palette_indexes), dtype=object) for palette_index, index in enumerate(chunk_palette_indexes): chunk_palette[palette_index] = block_palette[index] self._chunks[(cx, cz)] = ( chunk_box, chunk_blocks, chunk_palette, [], [], ) if "BlockEntities" in sponge_schem: block_entities = sponge_schem["BlockEntities"] if (not isinstance(block_entities, amulet_nbt.TAG_List) or block_entities.list_data_type != 10 # amulet_nbt.TAG_Compound.tag_id ): raise SpongeSchemReadError( "BlockEntities must be a TAG_List of compound tags.") for block_entity in block_entities: if "Pos" in block_entity: pos = block_entity["Pos"] if isinstance( pos, amulet_nbt.TAG_Int_Array) and len(pos) == 3: pos = pos + min_point x, y, z = ( pos[0], pos[1], pos[2], ) block_entity["Pos"] = amulet_nbt.TAG_Int_Array(pos) 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) if "Entities" in sponge_schem: entities = sponge_schem["Entities"] if (not isinstance(entities, amulet_nbt.TAG_List) or entities.list_data_type != 10 # amulet_nbt.TAG_Compound.tag_id ): raise SpongeSchemReadError( "Entities must be a TAG_List of compound tags.") for entity in entities: if "Pos" in entity: pos = entity["Pos"] if (isinstance(pos, amulet_nbt.TAG_List) and len(pos) == 3 and pos.list_data_type == 6): # amulet_nbt.TAG_Double.tag_id: x, y, z = ( pos[0].value + offset[0], pos[1].value + offset[0], pos[2].value + offset[0], ) entity["Pos"] = amulet_nbt.TAG_List([ amulet_nbt.TAG_Int(x), amulet_nbt.TAG_Int(y), amulet_nbt.TAG_Int(z), ]) 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 SpongeSchemReadError( f"Sponge Schematic Version {version.value} is not supported currently." )
def save_to(self, f: BinaryIO): if self._platform == "java": materials = "Alpha" elif self._platform == "bedrock": materials = "Pocket" else: raise ObjectWriteError( f'"{self._platform}" is not a supported platform for a schematic file.' ) selection = self._selection.selection_boxes[0] data = amulet_nbt.NBTFile( amulet_nbt.TAG_Compound({ "TileTicks": amulet_nbt.TAG_List(), "Width": amulet_nbt.TAG_Short(selection.size_x), "Height": amulet_nbt.TAG_Short(selection.size_y), "Length": amulet_nbt.TAG_Short(selection.size_z), "Materials": amulet_nbt.TAG_String(materials), }), "Schematic", ) entities = [] block_entities = [] blocks = numpy.zeros( selection.shape, dtype=numpy.uint16) # only 12 bits are actually used at most block_data = numpy.zeros(selection.shape, dtype=numpy.uint8) # only 4 bits are used for ( selection_, blocks_, data_, block_entities_, entities_, ) in self._chunks.values(): if selection_.intersects(selection): box = selection_.create_moved_box(selection.min, subtract=True) blocks[box.slice] = blocks_ block_data[box.slice] = data_ for be in block_entities_: coord_type = be["x"].__class__ be["x"] = coord_type(be["x"] - selection.min_x) be["y"] = coord_type(be["y"] - selection.min_y) be["z"] = coord_type(be["z"] - selection.min_z) block_entities.append(be) for e in entities_: coord_type = e["Pos"][0].__class__ e["Pos"][0] = coord_type(e["Pos"][0] - selection.min_x) e["Pos"][1] = coord_type(e["Pos"][1] - selection.min_y) e["Pos"][2] = coord_type(e["Pos"][2] - selection.min_z) entities.append(e) data["Entities"] = amulet_nbt.TAG_List(entities) data["TileEntities"] = amulet_nbt.TAG_List(block_entities) data["Data"] = amulet_nbt.TAG_Byte_Array( numpy.transpose(block_data, (1, 2, 0)) # XYZ => YZX ) data["Blocks"] = amulet_nbt.TAG_Byte_Array( numpy.transpose((blocks & 0xFF).astype(numpy.uint8), (1, 2, 0))) if numpy.max(blocks) > 0xFF: add_blocks = numpy.transpose(blocks & 0xF00, (1, 2, 0)) >> 8 data["AddBlocks"] = amulet_nbt.TAG_Byte_Array( add_blocks[::2] + (add_blocks[1::2] << 4)) data.save_to(f)
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): schematic = amulet_nbt.load(f) if any(key in schematic for key in ("Version", "Data Version", "BlockData")): raise ObjectReadError("This file is not a legacy schematic file.") materials = schematic.get("Materials", amulet_nbt.TAG_String()).value if materials == "Alpha": self._platform = "java" self._version = (1, 12, 2) elif materials == "Pocket": self._platform = "bedrock" self._version = (1, 12, 0) else: raise Exception( f'"{materials}" is not a supported platform for a schematic file.' ) self._chunks = {} selection_box = SelectionBox( (0, 0, 0), ( schematic["Width"].value, schematic["Height"].value, schematic["Length"].value, ), ) self._selection = SelectionGroup(selection_box) entities: amulet_nbt.TAG_List = schematic.get("Entities", amulet_nbt.TAG_List()) block_entities: amulet_nbt.TAG_List = schematic.get( "TileEntities", amulet_nbt.TAG_List()) blocks: numpy.ndarray = (schematic["Blocks"].value.astype( numpy.uint8).astype(numpy.uint16)) if "AddBlocks" in schematic: add_blocks = schematic["AddBlocks"] blocks = (blocks + (numpy.concatenate([ [(add_blocks & 0xF0) >> 4], [add_blocks & 0xF] ]).T.ravel().astype(numpy.uint16) << 8)[:blocks.size]) max_point = selection_box.max temp_shape = (max_point[1], max_point[2], max_point[0]) blocks = numpy.transpose(blocks.reshape(temp_shape), (2, 0, 1)) # YZX => XYZ data = numpy.transpose(schematic["Data"].value.reshape(temp_shape), (2, 0, 1)).astype(numpy.uint8) for cx, cz in selection_box.chunk_locations(): box = SelectionBox( (cx * self.sub_chunk_size, 0, cz * self.sub_chunk_size), ( min((cx + 1) * self.sub_chunk_size, selection_box.size_x), selection_box.size_y, min((cz + 1) * self.sub_chunk_size, selection_box.size_z), ), ) self._chunks[(cx, cz)] = (box, blocks[box.slice], data[box.slice], [], []) for e in block_entities: if all(key in e for key in ("x", "y", "z")): x, y, z = e["x"].value, e["y"].value, e["z"].value if (x, y, z) in selection_box: cx = x >> 4 cz = z >> 4 self._chunks[(cx, cz)][3].append(e) for e in entities: if "Pos" in e: pos: PointCoordinates = tuple( map(lambda t: float(t.value), e["Pos"].value)) if pos in selection_box: cx = int(pos[0]) >> 4 cz = int(pos[2]) >> 4 self._chunks[(cx, cz)][4].append(e)
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 save_to(self, f: BinaryIO): if self._schem_version == 1: raise SpongeSchemReadError( "Sponge Schematic Version 1 is not supported currently.") elif self._schem_version == 2: selection = self._bounds[self.dimensions[0]].selection_boxes[0] if any(s > 2**16 - 1 for s in selection.shape): raise SpongeSchemWriteError( "The structure is too large to be exported to a Sponge Schematic file. It must be 2^16 - 1 at most in each dimension." ) overflowed_shape = [ s if s < 2**15 else s - 2**16 for s in selection.shape ] data = amulet_nbt.NBTFile( amulet_nbt.TAG_Compound({ "Version": amulet_nbt.TAG_Int(2), "DataVersion": amulet_nbt.TAG_Int(self._version), "Width": amulet_nbt.TAG_Short(overflowed_shape[0]), "Height": amulet_nbt.TAG_Short(overflowed_shape[1]), "Length": amulet_nbt.TAG_Short(overflowed_shape[2]), "Offset": amulet_nbt.TAG_Int_Array(selection.min), }), name="Schematic", ) entities = [] block_entities = [] blocks = numpy.zeros(selection.shape, dtype=numpy.uint32) palette: List[AnyNDArray] = [] if self._version < 1500: raise Exception( "Writing to Sponge Schematic files in pre-1.13 format is not currently supported." ) else: arr = numpy.empty(1, dtype=object) arr[0] = Block("minecraft", "air") palette.append((arr)) palette_len = 1 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_) for be in block_entities_: be = copy.deepcopy(be) be["Pos"] = amulet_nbt.TAG_Int_Array(be["Pos"] - selection.min) block_entities.append(be) for e in entities_: e = copy.deepcopy(e) x, y, z = e["Pos"] e["Pos"] = amulet_nbt.TAG_List([ amulet_nbt.TAG_Int(x - selection.min_x), amulet_nbt.TAG_Int(y - selection.min_y), amulet_nbt.TAG_Int(z - selection.min_z), ]) entities.append(e) compact_palette, lut = brute_sort_objects_no_hash( numpy.concatenate(palette)) blocks = numpy.transpose(lut[blocks], (1, 2, 0)).ravel() # XYZ => YZX block_palette = [] for index, block in enumerate(compact_palette): block: Block block_palette.append(block.blockstate) data["PaletteMax"] = amulet_nbt.TAG_Int(len(compact_palette)) data["Palette"] = amulet_nbt.TAG_Compound({ blockstate: amulet_nbt.TAG_Int(index) for index, blockstate in enumerate(block_palette) }) data["BlockData"] = amulet_nbt.TAG_Byte_Array( list(encode_array(blocks))) if block_entities: data["BlockEntities"] = amulet_nbt.TAG_List(block_entities) if entities: data["Entities"] = amulet_nbt.TAG_List(entities) data.save_to(f) else: raise SpongeSchemReadError( f"Sponge Schematic Version {self._schem_version} is not supported currently." )