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 _unpack_blocks( translation_manager: "TranslationManager", version_identifier: VersionIdentifierType, chunk: Chunk, block_palette: AnyNDArray, ): """ Unpack the version-specific block_palette into the stringified version where needed. :return: The block_palette converted to block objects. """ version = translation_manager.get_version(*version_identifier) for index, block in enumerate(block_palette): block: Block if version.block.is_waterloggable(block.namespaced_name): properties = block.properties if "waterlogged" in properties: waterlogged = properties["waterlogged"] del properties["waterlogged"] block = Block( namespace=block.namespace, base_name=block.base_name, properties=properties, ) else: waterlogged = amulet_nbt.TAG_String("false") if waterlogged == amulet_nbt.TAG_String("true"): block_palette[index] = block + water else: block_palette[index] = block elif version.block.is_waterloggable(block.namespaced_name, True): block_palette[index] = block + water chunk._block_palette = BlockManager(block_palette)
def __init__(self, namespace: str, base_name: str, metadata: dict = None): super().__init__( { "namespace": amulet_nbt.TAG_String(namespace), "base_name": amulet_nbt.TAG_String(base_name), "metadata": amulet_nbt.TAG_Compound(metadata or {}), } )
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 parse_state_val(val) -> list: """Convert the json block state format into a consistent format.""" if isinstance(val, str): return [amulet_nbt.TAG_String(v) for v in val.split("|")] elif isinstance(val, bool): return [ amulet_nbt.TAG_String("true") if val else amulet_nbt.TAG_String("false") ] else: raise Exception(f"Could not parse state val {val}")
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 _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 _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 _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 _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 generateSignEntity(x, y, z, direction): """Generates the entity to make the sign display its position""" return BlockEntity("java", "acacia_wall_sign", x, y, z,\ amulet_nbt.NBTFile(\ value = amulet_nbt.TAG_Compound(\ {\ "utags": amulet_nbt.TAG_Compound(\ {\ "keepPacked": amulet_nbt.TAG_Byte(0),\ "Text4": amulet_nbt.TAG_String("{\"text\":\"\"}"),\ "Text3": amulet_nbt.TAG_String("{\"text\":\"\"}"),\ "Text2": amulet_nbt.TAG_String("{\"text\":\"%d - %d\"}"%(z + direction, z + direction * 6)), \ "Text1": amulet_nbt.TAG_String("{\"text\":\"%d\"}"%x)\ }),\ "Color": amulet_nbt.TAG_String("black")\ })))
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_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 _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_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( 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 parse_blockstate_string( blockstate: str, snbt: bool = False) -> Tuple[str, str, PropertyType]: """ Parse a Java or SNBT blockstate string and return the raw data. To parse the blockstate and return a :class:`Block` instance use :func:`from_string_blockstate` or :func:`from_snbt_blockstate` :param blockstate: The blockstate to parse :param snbt: Are the property values in SNBT format. If false all values must be an instance of :class:`~amulet_nbt.TAG_String` :return: namespace, block_name, properties """ if snbt: match = Block.snbt_blockstate_regex.match(blockstate) else: match = Block.blockstate_regex.match(blockstate) namespace = match.group("namespace") or "minecraft" base_name = match.group("base_name") if match.group("property_name") is not None: properties = { match.group("property_name"): match.group("property_value") } properties_string = match.group("properties") if properties_string is not None: if snbt: for match in Block.snbt_properties_regex.finditer( properties_string): properties[match.group("name")] = match.group("value") else: for match in Block.properties_regex.finditer( properties_string): properties[match.group("name")] = match.group("value") else: properties = {} if snbt: properties_dict = { k: amulet_nbt.from_snbt(v) for k, v in sorted(properties.items()) } else: properties_dict = { k: amulet_nbt.TAG_String(v) for k, v in sorted(properties.items()) } return ( namespace, base_name, properties_dict, )
def set_block_property(block, key, val): p = block.properties.copy() nbt_val = None if isinstance(val, str): nbt_val = amulet_nbt.TAG_String(val) #TODO: can blocks actually have non-string properties? # Why are things like glass panes facings stored as strings? else: assert False, "Bad Property" p[key] = nbt_val block2 = Block(block.namespace, block.base_name, p) return block2
def _pack_block_palette(self, version: "Version", palette: BlockNDArray) -> AnyNDArray: """ Translate the list of block objects into a version-specific block_palette. :return: The block_palette converted into version-specific blocks (ie id, data tuples for 1.12) """ for index, block in enumerate(palette): block: Block if version.block.is_waterloggable(block.namespaced_name): properties = block.properties extra_blocks = block.extra_blocks if (extra_blocks and extra_blocks[0].namespaced_name == water.namespaced_name): properties["waterlogged"] = amulet_nbt.TAG_String("true") else: properties["waterlogged"] = amulet_nbt.TAG_String("false") palette[index] = Block( namespace=block.namespace, base_name=block.base_name, properties=properties, ) return palette
def createBlocks(world): """generates all needed Block objects""" barrel = getBlock( world, Block( "minecraft", "barrel", { "facing": amulet_nbt.TAG_String("up"), "open": amulet_nbt.TAG_String("false") })) wool = getBlock(world, Block("minecraft", "red_wool")) air = getBlock(world, Block("minecraft", "air")) stone = getBlock(world, Block("minecraft", "stone")) glowstone = getBlock(world, Block("minecraft", "glowstone")) lantern = getBlock( world, Block("minecraft", "lantern", {"hanging": amulet_nbt.TAG_String("false")})) sign_north = getBlock( world, Block("minecraft", "acacia_wall_sign", {"facing": amulet_nbt.TAG_String("north")})) sign_south = getBlock( world, Block("minecraft", "acacia_wall_sign", {"facing": amulet_nbt.TAG_String("south")})) return [ barrel, wool, glowstone, sign_north, sign_south, air, stone, lantern ]
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 __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 world_name(self, value: str): self.root_tag["LevelName"] = nbt.TAG_String(value)
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_model(self, block: Block) -> BlockMesh: """Find the model paths for a given block state and load them.""" if (block.namespace, block.base_name) in self._blockstate_files: blockstate: dict = self._blockstate_files[(block.namespace, block.base_name)] if "variants" in blockstate: for variant in blockstate["variants"]: if variant == "": try: return self._load_blockstate_model( block, blockstate["variants"][variant]) except Exception as e: log.error( f"Failed to load block model for {blockstate['variants'][variant]}\n{e}" ) else: properties_match = Block.properties_regex.finditer( f",{variant}") if all( block.properties.get( match.group("name"), amulet_nbt.TAG_String(match.group( "value")), ).value == match.group("value") for match in properties_match): try: return self._load_blockstate_model( block, blockstate["variants"][variant]) except Exception as e: log.error( f"Failed to load block model for {blockstate['variants'][variant]}\n{e}" ) elif "multipart" in blockstate: models = [] for case in blockstate["multipart"]: try: if "when" in case: if "OR" in case["when"]: if not any( all( block.properties.get(prop, None) in self.parse_state_val(val) for prop, val in prop_match.items()) for prop_match in case["when"]["OR"]): continue elif not all( block.properties.get(prop, None) in self.parse_state_val(val) for prop, val in case["when"].items()): continue if "apply" in case: try: models.append( self._load_blockstate_model( block, case["apply"])) except: pass except: pass
def level_name(self, value: str): self.root_tag["Data"]["LevelName"] = nbt.TAG_String(value)
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 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 _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_base_entity( nbt: amulet_nbt.NBTFile, id_type: EntityIDType, coord_type: EntityCoordType ) -> Optional[Tuple[str, str, Union[int, float], Union[int, float], Union[ int, float], amulet_nbt.NBTFile, ]]: if not (isinstance(nbt, amulet_nbt.NBTFile) and isinstance(nbt.value, amulet_nbt.TAG_Compound)): return if id_type == EntityIDType.namespace_str_id: entity_id = nbt.pop("id", amulet_nbt.TAG_String("")) if (not isinstance(entity_id, amulet_nbt.TAG_String) or entity_id.value == "" or ":" not in entity_id.value): return namespace, base_name = entity_id.value.split(":", 1) elif id_type == EntityIDType.namespace_str_Id: entity_id = nbt.pop("Id", amulet_nbt.TAG_String("")) if (not isinstance(entity_id, amulet_nbt.TAG_String) or entity_id.value == "" or ":" not in entity_id.value): return namespace, base_name = entity_id.value.split(":", 1) elif id_type == EntityIDType.str_id: entity_id = nbt.pop("id", amulet_nbt.TAG_String("")) if (not isinstance(entity_id, amulet_nbt.TAG_String) or entity_id.value == ""): return namespace = "" base_name = entity_id.value elif id_type in [ EntityIDType.namespace_str_identifier, EntityIDType.int_id ]: if "identifier" in nbt: entity_id = nbt.pop("identifier") if (not isinstance(entity_id, amulet_nbt.TAG_String) or entity_id.value == "" or ":" not in entity_id.value): return namespace, base_name = entity_id.value.split(":", 1) elif "id" in nbt: entity_id = nbt.pop("id") if not isinstance(entity_id, amulet_nbt.TAG_Int): return namespace = "" base_name = str(entity_id.value) else: return else: raise NotImplementedError(f"Entity id type {id_type}") if coord_type in [ EntityCoordType.Pos_list_double, EntityCoordType.Pos_list_float, EntityCoordType.Pos_list_int, ]: if "Pos" not in nbt: return pos = nbt.pop("Pos") pos: amulet_nbt.TAG_List if (not isinstance(pos, amulet_nbt.TAG_List) or len(pos) != 3 or PosTypeMap.get(pos.list_data_type) != coord_type): return x, y, z = [c.value for c in pos] elif coord_type == EntityCoordType.Pos_array_int: if "Pos" not in nbt: return pos = nbt.pop("Pos") pos: amulet_nbt.TAG_Int_Array if not isinstance(pos, amulet_nbt.TAG_Int_Array) or len(pos) != 3: return x, y, z = pos elif coord_type == EntityCoordType.xyz_int: if not all(c in nbt and isinstance(nbt[c], amulet_nbt.TAG_Int) for c in ("x", "y", "z")): return x, y, z = [nbt.pop(c).value for c in ("x", "y", "z")] else: raise NotImplementedError(f"Entity coord type {coord_type}") return namespace, base_name, x, y, z, nbt