def _init_decode(cx: int, cz: int, data: NBTFile) -> Tuple[Chunk, TAG_Compound]: """Get the decode started by creating a chunk object.""" chunk = Chunk(cx, cz) assert isinstance(data.value, TAG_Compound), "Raw data must be a compound." chunk.misc = { # store the chunk data so that any non-versioned data can get saved back "java_chunk_data": data.value } return chunk, data.value
def put_chunk(self, chunk: Chunk, dimension: Dimension): """ Add a given chunk to the chunk manager. :param chunk: The :class:`Chunk` to add to the chunk manager. It will be added at the location stored in :attr:`Chunk.coordinates` :param dimension: The dimension to add the chunk to. """ chunk.block_palette = self.level.block_palette chunk.biome_palette = self.level.biome_palette self._put_entry((dimension, chunk.cx, chunk.cz), chunk)
def _decode_status(self, chunk: Chunk, compound: TAG_Compound): status = "empty" if self.get_obj(compound, "TerrainPopulated", TAG_Byte): status = "decorated" if self.get_obj(compound, "LightPopulated", TAG_Byte): status = "postprocessed" chunk.status = status
def get_chunk_boxes( self, selection: Union[SelectionGroup, SelectionBox], dimension: Dimension, create_missing_chunks=False, ) -> Generator[Tuple[Chunk, SelectionBox], None, None]: """Given a selection will yield chunks and SubSelectionBoxes into that chunk :param selection: SelectionGroup or SelectionBox into the world :param dimension: The dimension to take effect in (defaults to overworld) :param create_missing_chunks: If a chunk does not exist an empty one will be created (defaults to false) """ if isinstance(selection, SelectionBox): selection = SelectionGroup([selection]) selection: SelectionGroup for (cx, cz), box in selection.sub_sections(self.sub_chunk_size): try: chunk = self.get_chunk(cx, cz, dimension) except ChunkDoesNotExist: if create_missing_chunks: chunk = Chunk(cx, cz) self.put_chunk(chunk, dimension) else: continue except ChunkLoadError: continue yield chunk, box
def _decode_fluid_ticks(self, chunk: Chunk, compound: TAG_Compound, floor_cy: int): chunk.misc.setdefault("fluid_ticks", {}).update( self._decode_ticks(self.get_obj(compound, "LiquidTicks", TAG_List))) chunk.misc["liquids_to_be_ticked"] = self._decode_to_be_ticked( self.get_obj(compound, "LiquidsToBeTicked", TAG_List), floor_cy)
def decode(self, cx: int, cz: int, data: SpongeSchemChunk) -> Tuple["Chunk", AnyNDArray]: """ Create an amulet.api.chunk.Chunk object from raw data given by the format :param cx: chunk x coordinate :param cz: chunk z coordinate :param data: Raw chunk data provided by the format. :return: Chunk object in version-specific format, along with the block_palette for that chunk. """ palette = numpy.empty(len(data.palette) + 1, dtype=object) palette[0] = Block(namespace="minecraft", base_name="air") palette[1:] = data.palette[:] chunk = Chunk(cx, cz) box = data.selection.create_moved_box((cx * 16, 0, cz * 16), subtract=True) chunk.blocks[box.slice] = data.blocks + 1 for b in data.block_entities: b = self._decode_block_entity(b, self._block_entity_id_type, self._block_entity_coord_type) if b is not None: chunk.block_entities.insert(b) for e in data.entities: e = self._decode_entity(e, self._block_entity_id_type, self._block_entity_coord_type) if e is not None: chunk.entities.append(e) return chunk, palette
def _unpack_biomes( translation_manager: "TranslationManager", version_identifier: VersionIdentifierType, chunk: Chunk, ): """ Unpack the version-specific biome_palette into the stringified version where needed. :return: The biome_palette converted to biome objects. """ version = translation_manager.get_version(*version_identifier) biome_int_palette, biome_array = numpy.unique(chunk.biomes, return_inverse=True) chunk.biomes = biome_array chunk._biome_palette = BiomeManager( [version.biome.unpack(biome) for biome in biome_int_palette] )
def _deserialise(self, path: Optional[str]) -> Optional[Chunk]: if path is None: return None else: pickled_bytes = get_cache_db().get(path.encode("utf-8")) return Chunk.unpickle(pickled_bytes, self.world.block_palette, self.world.biome_palette)
def _pack_biomes( translation_manager: "TranslationManager", version_identifier: VersionIdentifierType, chunk: Chunk, ): """ Unpack the version-specific biome_palette into the stringified version where needed. :return: The biome_palette converted to biome objects. """ version = translation_manager.get_version(*version_identifier) biome_palette = numpy.array( [version.biome.pack(biome) for biome in chunk.biome_palette] ) chunk.biomes = biome_palette[chunk.biomes] chunk._biome_palette = BiomeManager()
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 decode(self, cx: int, cz: int, section: SchematicChunk) -> Tuple["Chunk", AnyNDArray]: chunk = Chunk(cx, cz) block_palette, blocks = numpy.unique( (section.blocks << 4) + (section.data & 0xF), return_inverse=True) blocks = blocks.reshape(section.blocks.shape) palette = numpy.empty(len(block_palette) + 1, dtype=numpy.object) palette[0] = (0, 0) for index, block_num in enumerate(block_palette): palette[index + 1] = (block_num >> 4, block_num & 0xF) box = section.selection.create_moved_box((cx * 16, 0, cz * 16), subtract=True) chunk.blocks[box.slice] = blocks + 1 for b in section.block_entities: b = self._decode_block_entity(b, self._block_entity_id_type, self._block_entity_coord_type) if b is not None: chunk.block_entities.insert(b) for b in section.entities: b = self._decode_entity(b, self._block_entity_id_type, self._block_entity_coord_type) if b is not None: chunk.entities.append(b) return chunk, palette
def _decode_biome_sections(self, chunk: Chunk, chunk_sections: Dict[int, TAG_Compound]): biomes: Dict[int, numpy.ndarray] = {} palette = BiomeManager() for cy, section in chunk_sections.items(): data = self._decode_biome_section(section) if data is not None: arr, section_palette = data lut = numpy.array([ palette.get_add_biome(biome) for biome in section_palette ]) biomes[cy] = lut[arr].astype(numpy.uint32) chunk.biomes = biomes chunk.biome_palette = palette
def _deserialise(self, path: Optional[str]) -> Optional[Chunk]: if path is None: return None else: return Chunk.unpickle( path, self.world.block_palette, self.world.biome_palette )
def _convert_to_load( self, chunk: Chunk, translator: "Translator", game_version: VersionNumberAny, dimension: Dimension, recurse: bool = True, ) -> Chunk: # set up a callback that translator can use to get chunk data cx, cz = chunk.cx, chunk.cz if recurse: chunk_cache: Dict[ChunkCoordinates, Chunk] = {} def get_chunk_callback(x: int, z: int) -> Chunk: cx_, cz_ = cx + x, cz + z if (cx_, cz_) not in chunk_cache: chunk_cache[(cx_, cz_)] = self._load_chunk(cx_, cz_, dimension, recurse=False) return chunk_cache[(cx_, cz_)] else: get_chunk_callback = None # translate the data to universal format chunk = translator.to_universal(game_version, self.translation_manager, chunk, get_chunk_callback, recurse) chunk.changed = False return chunk
def _biomes_from_universal(translator_version: "Version", chunk: Chunk): chunk._biome_palette = BiomeManager( [ translator_version.biome.from_universal(biome) for biome in chunk.biome_palette ] )
def decode( self, cx: int, cz: int, section: MCStructureChunk ) -> Tuple["Chunk", AnyNDArray]: palette = numpy.empty(len(section.palette) + 1, dtype=numpy.object) palette[0] = ( ( 17563649, Block( namespace="minecraft", base_name="air", properties={"block_data": amulet_nbt.TAG_Int(0)}, ), ), ) for index, blocks in enumerate(section.palette): block_layers: List[Tuple[Optional[int], Block]] = [] for block in blocks: namespace, base_name = block["name"].value.split(":", 1) if "version" in block: version: Optional[int] = block["version"].value else: version = None if "states" in block: # 1.13 format properties = block["states"].value if version is None: version = 17694720 # 1, 14, 0, 0 else: properties = {"block_data": amulet_nbt.TAG_Int(block["val"].value)} block_layers.append( ( version, Block( namespace=namespace, base_name=base_name, properties=properties, ), ) ) palette[index + 1] = block_layers chunk = Chunk(cx, cz) box = section.selection.create_moved_box((cx * 16, 0, cz * 16), subtract=True) chunk.blocks[box.slice] = section.blocks + 1 for b in section.block_entities: b = self._decode_block_entity( b, self._block_entity_id_type, self._block_entity_coord_type ) if b is not None: chunk.block_entities.insert(b) for b in section.entities: b = self._decode_entity( b, self._block_entity_id_type, self._block_entity_coord_type ) if b is not None: chunk.entities.append(b) return chunk, palette
def create_chunks( world: BaseLevel, dimension: Dimension, selection: SelectionGroup, ): for cx, cz in selection.chunk_locations(): if not world.has_chunk(cx, cz, dimension): world.put_chunk(Chunk(cx, cz), dimension)
def _unpack_biomes( translation_manager: "TranslationManager", version_identifier: VersionIdentifierType, chunk: Chunk, ): """ Unpack the version-specific biome_palette into the stringified version where needed. :return: The biome_palette converted to biome objects. """ version = translation_manager.get_version(*version_identifier) if chunk.biomes.dimension == BiomesShape.Shape2D: biome_int_palette, biome_array = numpy.unique( chunk.biomes, return_inverse=True ) chunk.biomes = biome_array.reshape(chunk.biomes.shape) chunk._biome_palette = BiomeManager( [version.biome.unpack(biome) for biome in biome_int_palette] ) elif chunk.biomes.dimension == BiomesShape.Shape3D: biomes = {} palette = [] palette_length = 0 for sy in chunk.biomes.sections: biome_int_palette, biome_array = numpy.unique( chunk.biomes.get_section(sy), return_inverse=True ) biomes[sy] = ( biome_array.reshape(chunk.biomes.section_shape) + palette_length ) palette_length += len(biome_int_palette) palette.append(biome_int_palette) if palette: chunk_palette, lut = numpy.unique( numpy.concatenate(palette), return_inverse=True ) lut = lut.astype(numpy.uint32) for sy in biomes: biomes[sy] = lut[biomes[sy]] chunk.biomes = biomes chunk._biome_palette = BiomeManager( numpy.vectorize(version.biome.unpack)(chunk_palette) )
def decode(self, cx: int, cz: int, data: List[ConstructionSection]) -> Tuple["Chunk", AnyNDArray]: """ Create an amulet.api.chunk.Chunk object from raw data given by the format :param cx: chunk x coordinate :param cz: chunk z coordinate :param data: Raw chunk data provided by the format. :return: Chunk object in version-specific format, along with the block_palette for that chunk. """ chunk = Chunk(cx, cz) palette = [] for section in data: if any(s == 0 for s in section.shape): continue if section.blocks is not None: shapex, shapey, shapez = section.shape sx = section.sx % 16 sy = section.sy sz = section.sz % 16 chunk.blocks[sx:sx + shapex, sy:sy + shapey, sz:sz + shapez, ] = section.blocks.astype( numpy.uint32) + len(palette) chunk.block_entities.update(section.block_entities) chunk.entities.extend(section.entities) palette += section.palette np_palette, inverse = numpy.unique(palette, return_inverse=True) np_palette = numpy.insert( np_palette, 0, Block( namespace="minecraft", base_name="air", properties={"block_data": amulet_nbt.TAG_Int(0)}, ), ) inverse += 1 np_palette: AnyNDArray inverse: numpy.ndarray for cy in chunk.blocks.sub_chunks: chunk.blocks.add_sub_chunk( cy, inverse[chunk.blocks.get_sub_chunk(cy)].astype(numpy.uint32)) return chunk, np_palette
def initialize_world(world: World): minxcoord = -1000 minzcoord = -1000 maxxcoord = 5000 maxzcoord = 7000 minx, minz = block_coords_to_chunk_coords(minxcoord, minzcoord) maxx, maxz = block_coords_to_chunk_coords(maxxcoord, maxzcoord) for x in range(minx, maxx): for z in range(minz, maxz): world.put_chunk(Chunk(x, z))
def _convert_to_save( self, chunk: Chunk, chunk_version: VersionNumberAny, translator: "Translator", recurse: bool = True, ) -> Chunk: """Convert the Chunk in Universal format to a Chunk in the version specific format.""" # create a new streamlined block block_palette and remap the data palette: List[numpy.ndarray] = [] palette_len = 0 for cy in chunk.blocks.sub_chunks: sub_chunk_palette, sub_chunk = numpy.unique( chunk.blocks.get_sub_chunk(cy), return_inverse=True) chunk.blocks.add_sub_chunk( cy, sub_chunk.astype(numpy.uint32).reshape( (16, 16, 16)) + palette_len) palette_len += len(sub_chunk_palette) palette.append(sub_chunk_palette) if palette: chunk_palette, lut = numpy.unique(numpy.concatenate(palette), return_inverse=True) for cy in chunk.blocks.sub_chunks: chunk.blocks.add_sub_chunk( cy, lut.astype(numpy.uint32)[chunk.blocks.get_sub_chunk(cy)]) chunk._block_palette = BlockManager( numpy.vectorize( chunk.block_palette.__getitem__)(chunk_palette)) else: chunk._block_palette = BlockManager() def get_chunk_callback(_: int, __: int) -> Chunk: # conversion from universal should not require any data outside the block return chunk # translate from universal format to version format return translator.from_universal(chunk_version, self.translation_manager, chunk, get_chunk_callback, recurse)
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. """ chunk._block_palette = BlockManager(block_palette)
def _extract_sections( self, chunk: Chunk, compound: TAG_Compound ) -> Dict[int, TAG_Compound]: # parse sections into a more usable format sections: Dict[int, TAG_Compound] = { section.pop("Y").value: section for section in self.get_obj(compound, self.Sections, TAG_List) if isinstance(section, TAG_Compound) and self.check_type(section, "Y", TAG_Byte) } chunk.misc["java_sections"] = sections return sections
def _pack_biomes( translation_manager: "TranslationManager", version_identifier: VersionIdentifierType, chunk: Chunk, ): """ Unpack the version-specific biome_palette into the stringified version where needed. :return: The biome_palette converted to biome objects. """ version = translation_manager.get_version(*version_identifier) biome_palette = numpy.array( [version.biome.pack(biome) for biome in chunk.biome_palette], numpy.uint32 ) if chunk.biomes.dimension == BiomesShape.Shape2D: chunk.biomes = biome_palette[chunk.biomes] elif chunk.biomes.dimension == BiomesShape.Shape3D: chunk.biomes = { sy: biome_palette[chunk.biomes.get_section(sy)] for sy in chunk.biomes.sections } chunk._biome_palette = BiomeManager()
def _unpack_blocks( translation_manager: "TranslationManager", version_identifier: VersionIdentifierType, chunk: Chunk, block_palette: AnyNDArray, ): """ Unpacks an object array of block data into a numpy object array containing Block objects. :param translation_manager: :param version_identifier: :param chunk: :param block_palette: :type block_palette: numpy.ndarray[ Tuple[ Union[ Tuple[None, Tuple[int, int]], Tuple[None, Block], Tuple[int, Block] ], ... ] ] :return: """ palette_ = BlockManager() for palette_index, entry in enumerate(block_palette): entry: BedrockInterfaceBlockType block = None for version_number, b in entry: version_number: Optional[int] if isinstance(b, tuple): version = translation_manager.get_version( version_identifier[0], version_number or 17563649) b = version.block.ints_to_block(*b) elif isinstance(b, Block): if version_number is not None: properties = b.properties properties["__version__"] = amulet_nbt.TAG_Int( version_number) b = Block(b.namespace, b.base_name, properties, b.extra_blocks) else: raise Exception(f"Unsupported type {b}") if block is None: block = b else: block += b if block is None: raise Exception(f"Empty tuple") palette_.get_add_block(block) chunk._block_palette = palette_
def _decode_blocks( self, chunk: Chunk, chunk_sections: Dict[int, TAG_Compound] ) -> AnyNDArray: blocks: Dict[int, SubChunkNDArray] = {} palette = [] palette_len = 0 for cy, section in chunk_sections.items(): section_blocks = numpy.frombuffer( section.pop("Blocks").value, dtype=numpy.uint8 ) section_data = numpy.frombuffer( section.pop("Data").value, dtype=numpy.uint8 ) section_blocks = section_blocks.reshape((16, 16, 16)) section_blocks = section_blocks.astype(numpy.uint16) section_data = world_utils.from_nibble_array(section_data) section_data = section_data.reshape((16, 16, 16)) if "Add" in section: add_blocks = numpy.frombuffer( section.pop("Add").value, dtype=numpy.uint8 ) add_blocks = world_utils.from_nibble_array(add_blocks) add_blocks = add_blocks.reshape((16, 16, 16)) section_blocks |= add_blocks.astype(numpy.uint16) << 8 # TODO: fix this (section_palette, blocks[cy]) = world_utils.fast_unique( numpy.transpose( (section_blocks << 4) + section_data, (2, 0, 1) ) # YZX -> XYZ ) blocks[cy] += palette_len palette_len += len(section_palette) palette.append(section_palette) if palette: final_palette, lut = numpy.unique( numpy.concatenate(palette), return_inverse=True ) final_palette: numpy.ndarray = numpy.array( [final_palette >> 4, final_palette & 15] ).T for cy in blocks: blocks[cy] = lut[blocks[cy]] else: final_palette = numpy.array([], dtype=object) chunk.blocks = blocks return final_palette
def decode( self, cx: int, cz: int, data: List[ConstructionSection] ) -> Tuple["Chunk", AnyNDArray]: chunk = Chunk(cx, cz) palette = [] for section in data: if any(s == 0 for s in section.shape): continue if section.blocks is not None: shapex, shapey, shapez = section.shape sx = section.sx - ((section.sx >> 4) << 4) sy = section.sy sz = section.sz - ((section.sz >> 4) << 4) chunk.blocks[ sx : sx + shapex, sy : sy + shapey, sz : sz + shapez, ] = section.blocks.astype(numpy.uint32) + len(palette) chunk.block_entities.update(section.block_entities) chunk.entities.extend(section.entities) palette += section.palette np_palette, inverse = numpy.unique(palette, return_inverse=True) np_palette = numpy.insert( np_palette, 0, Block( namespace="minecraft", base_name="air", properties={"block_data": amulet_nbt.TAG_Int(0)}, ), ) inverse += 1 np_palette: AnyNDArray inverse: numpy.ndarray for cy in chunk.blocks.sub_chunks: chunk.blocks.add_sub_chunk( cy, inverse[chunk.blocks.get_sub_chunk(cy)].astype(numpy.uint32) ) return chunk, np_palette
def create_chunk(self, cx: int, cz: int, dimension: Dimension) -> Chunk: """ Create an empty chunk and put it at the given location. If a chunk exists at the given location it will be overwritten. :param cx: The X coordinate of the chunk :param cz: The Z coordinate of the chunk :param dimension: The dimension to put the chunk in. :return: The newly created :class:`Chunk`. """ chunk = Chunk(cx, cz) self.put_chunk(chunk, dimension) return chunk
def _unserialise_chunk( self, dimension: Dimension, cx: int, cz: int, change_num_delta: int, ) -> Union[Chunk, None]: """Load the next save state for a given chunk in a given direction""" chunk_location = (dimension, cx, cz) chunk_index, save_chunk_index = self._chunk_index[chunk_location] chunk_index += change_num_delta self._chunk_index[chunk_location] = (chunk_index, save_chunk_index) chunk_storage = self._chunk_history[chunk_location] assert 0 <= chunk_index < len(chunk_storage), "Chunk index out of bounds" chunk = chunk_storage[chunk_index] if chunk is not None: chunk = Chunk.unpickle(chunk, self._block_palette, self._biome_palette) return chunk
def _unpack_blocks( translation_manager: "TranslationManager", version_identifier: VersionIdentifierType, chunk: Chunk, block_palette: AnyNDArray, ): """ Unpacks an int array of block ids and block data values [[1, 0], [2, 0]] into a numpy array of Block objects. :param version: :param block_palette: :return: """ version = translation_manager.get_version(*version_identifier) chunk._block_palette = BlockManager( [version.block.ints_to_block(*entry) for entry in block_palette])