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 _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 _exit_write(self): """data to be written at close in write mode""" if self._format_version == 0: metadata_start = self._buffer.tell() self._metadata["section_index_table"] = amulet_nbt.TAG_Byte_Array( numpy.array(self._section_index_table, dtype=SECTION_ENTRY_TYPE).view(numpy.int8)) self._metadata["block_palette"] = self._pack_palette() self._metadata.save_to(self._buffer) self._buffer.write(INT_STRUCT.pack(metadata_start)) self._buffer.write(magic_num) else: raise Exception( f"This wrapper doesn't support any construction version higher than {max_format_version}" ) self._buffer.close()
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): 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): 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." )