def test_hash(self): stone = Block.from_string_blockstate("minecraft:stone") water = Block.from_string_blockstate("minecraft:water[level=1]") granite = Block.from_string_blockstate("minecraft:granite") dirt = Block.from_string_blockstate("minecraft:dirt") conglomerate_1 = stone + water + dirt conglomerate_2 = stone + dirt + water self.assertNotEqual(conglomerate_1, conglomerate_2) self.assertNotEqual(hash(conglomerate_1), hash(conglomerate_2)) conglomerate_3 = dirt + water + dirt conglomerate_4 = dirt + dirt + water self.assertNotEqual(conglomerate_3, conglomerate_4) self.assertNotEqual(hash(conglomerate_3), hash(conglomerate_4)) conglomerate_5 = conglomerate_1 + granite conglomerate_6 = conglomerate_3 + granite self.assertNotEqual(conglomerate_1, conglomerate_5) self.assertNotEqual(conglomerate_3, conglomerate_6) self.assertNotEqual(conglomerate_5, conglomerate_6) self.assertNotEqual(hash(conglomerate_5), hash(conglomerate_6))
def test_replace_single_block(self): subbox1 = SelectionBox((1, 70, 3), (5, 71, 6)) box1 = SelectionGroup((subbox1, )) self.assertEqual( "universal_minecraft:stone", self.world.get_block(1, 70, 3, OVERWORLD).blockstate, ) self.assertEqual( "universal_minecraft:granite[polished=false]", self.world.get_block(1, 70, 5, OVERWORLD).blockstate, ) replace( self.world, OVERWORLD, box1, { "original_blocks": [ Block.from_string_blockstate( "universal_minecraft:granite[polished=false]") ], "replacement_blocks": [ Block.from_string_blockstate( "universal_minecraft:stone") ], }, ) self.world.create_undo_point() self.assertEqual( "universal_minecraft:stone", self.world.get_block(1, 70, 3, OVERWORLD).blockstate, ) self.assertEqual( "universal_minecraft:stone", self.world.get_block(1, 70, 5, OVERWORLD).blockstate, ) self.world.undo() self.assertEqual( "universal_minecraft:stone", self.world.get_block(1, 70, 3, OVERWORLD).blockstate, ) self.assertEqual( "universal_minecraft:granite[polished=false]", self.world.get_block(1, 70, 5, OVERWORLD).blockstate, ) self.world.redo() self.assertEqual( "universal_minecraft:stone", self.world.get_block(1, 70, 3, OVERWORLD).blockstate, ) self.assertEqual( "universal_minecraft:stone", self.world.get_block(1, 70, 5, OVERWORLD).blockstate, )
def paste_iter( dst: "BaseLevel", dst_dimension: Dimension, src: "BaseLevel", src_dimension: Dimension, location: BlockCoordinates, scale: FloatTriplet, rotation: FloatTriplet, copy_air=True, copy_water=True, copy_lava=True, ): yield from dst.paste_iter( src, src_dimension, src.bounds(src_dimension), dst_dimension, location, scale, rotation, True, True, tuple(UniversalAirLikeBlocks) * bool(not copy_air) + (Block("universal_minecraft", "water"), ) * bool(not copy_water) + (Block("universal_minecraft", "lava"), ) * bool(not copy_lava), True, )
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 test_extra_blocks_immutable(self): stone = Block.from_string_blockstate("minecraft:stone") dirt = Block.from_string_blockstate("minecraft:dirt") stone2 = stone self.assertIs(stone, stone2) stone2 += dirt self.assertIsNot(stone, stone2) stone3 = stone2 self.assertIs(stone2, stone3) stone3 -= dirt self.assertIsNot(stone, stone3)
def test_get_block_from_manager(self): dirt = Block.from_string_blockstate("minecraft:dirt") stone = Block.from_string_blockstate("minecraft:stone") granite = Block.from_string_blockstate("minecraft:granite") water = Block.from_string_blockstate("minecraft:water") dirt_water = dirt + water self.assertEqual(dirt, self.manager[0]) self.assertEqual(stone, self.manager[1]) self.assertEqual(granite, self.manager[2]) self.assertEqual(dirt_water, self.manager[3]) with self.assertRaises(KeyError): brain_coral = Block.from_string_blockstate("minecraft:brain_coral") internal_id = self.manager[brain_coral]
def setUp(self): self.manager = BlockManager() initial_dirt = Block.from_string_blockstate("minecraft:dirt") initial_stone = Block.from_string_blockstate("minecraft:stone") initial_granite = Block.from_string_blockstate("minecraft:granite") initial_dirt_water = initial_dirt + Block.from_string_blockstate( "minecraft:water" ) # Partially populate the manager self.manager.get_add_block(initial_dirt) self.manager.get_add_block(initial_stone) self.manager.get_add_block(initial_granite) self.manager.get_add_block(initial_dirt_water)
def _pack_block_palette( self, version: "Version", palette: BlockNDArray ) -> AnyNDArray: """ Packs a numpy array of Block objects into an object array containing one more more pairs of version number and Block objects. :param version: :param palette: :return: numpy.ndarray[Tuple[Tuple[Optional[VersionNumber], Block], ...]] """ palette_ = numpy.empty(len(palette), dtype=object) for palette_index, block in enumerate(palette): block: "Block" # TODO: perhaps check that all properties are NBT objects user interaction if not blocks = [] for b in block.block_tuple: if "__version__" in b.properties: properties = b.properties version_number = properties.pop("__version__").value b = Block(b.namespace, b.base_name, properties, b.extra_blocks) else: version_number = None blocks.append((version_number, b)) palette_[palette_index] = tuple(blocks) return palette_
def __init__(self, directory: str, world_wrapper: "WorldFormatWrapper", temp_dir: str = None): self._directory = directory if temp_dir is None: self._temp_directory = get_temp_dir(self._directory) else: self._temp_directory = temp_dir self._world_wrapper = world_wrapper self._world_wrapper.open() self._block_palette = BlockManager() self._block_palette.get_add_block( Block(namespace="universal_minecraft", base_name="air")) # ensure that index 0 is always air self._biome_palette = BiomeManager() self._biome_palette.get_add_biome("universal_minecraft:plains") self._history_manager = MetaHistoryManager() self._chunks: ChunkManager = ChunkManager( os.path.join(self._temp_directory, "chunks"), self) self._history_manager.register(self._chunks, True)
def _decode_blocks( self, chunk_sections: amulet_nbt.TAG_List ) -> Tuple[Dict[int, SubChunkNDArray], AnyNDArray]: blocks: Dict[int, numpy.ndarray] = {} palette = [Block(namespace="minecraft", base_name="air")] for section in chunk_sections: if "Palette" not in section: # 1.14 makes block_palette/blocks optional. continue cy = section["Y"].value if self.features["long_array_format"] == "compact": decoded = decode_long_array(section["BlockStates"].value, 4096) elif self.features["long_array_format"] == "1.16": decoded = decode_long_array( section["BlockStates"].value, 4096, dense=False ) else: raise Exception("long_array_format", self.features["long_array_format"]) blocks[cy] = numpy.transpose( decoded.reshape((16, 16, 16)) + len(palette), (2, 0, 1) ) palette += self._decode_palette(section["Palette"]) np_palette, inverse = numpy.unique(palette, return_inverse=True) np_palette: numpy.ndarray inverse: numpy.ndarray for cy in blocks: blocks[cy] = inverse[blocks[cy]].astype( numpy.uint32 ) # TODO: find a way to make the new blocks format change dtype return blocks, np_palette
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_palette(raw_palette: amulet_nbt.TAG_List) -> List[Block]: block_palette = [] extra_block_map = {} for block_index, block_nbt in enumerate(raw_palette): block_nbt: amulet_nbt.TAG_Compound block_namespace = block_nbt["namespace"].value block_basename = block_nbt["blockname"].value block = Block( namespace=block_namespace, base_name=block_basename, properties=block_nbt["properties"].value, ) if block_nbt["extra_blocks"].value: extra_block_map[block_index] = block_nbt["extra_blocks"].value block_palette.append(block) for block_index, extra_blocks in extra_block_map.items(): extra_block_objects = [block_palette[i.value] for i in extra_blocks] resulting_block = block_palette[block_index] for extra_block in extra_block_objects: resulting_block = resulting_block + extra_block block_palette[block_index] = resulting_block return block_palette
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 delete(world: "World", dimension: Dimension, selection: SelectionGroup): yield from fill( world, dimension, selection, { "fill_block": world.translation_manager.get_version( 'java', (1, 15, 2)).block.to_universal(Block("minecraft", "air"))[0] })
def block(self) -> Block: if self._wildcard: raise Exception('block property cannot be used when BlockDefine is in wildcard mode') else: return Block( self.namespace, self.base_name, {key: amulet_nbt.from_snbt(value) for key, value in self.properties.items()} )
def _decode_palette(palette: amulet_nbt.TAG_List) -> list: blockstates = [] for entry in palette: namespace, base_name = entry["Name"].value.split(":", 1) properties = entry.get("Properties", amulet_nbt.TAG_Compound({})).value block = Block( namespace=namespace, base_name=base_name, properties=properties ) blockstates.append(block) return blockstates
def test_get_index_from_manager(self): dirt = Block.from_string_blockstate("minecraft:dirt") stone = Block.from_string_blockstate("minecraft:stone") granite = Block.from_string_blockstate("minecraft:granite") self.assertEqual(0, self.manager[dirt]) self.assertEqual(1, self.manager[stone]) self.assertEqual(2, self.manager[granite]) water = Block.from_string_blockstate("minecraft:water") dirt_water = dirt + water self.assertNotEqual(dirt, dirt_water) self.assertIsNot(dirt, dirt_water) self.assertEqual(3, self.manager[dirt_water]) with self.assertRaises(KeyError): random_block = self.manager[10000]
def test_extra_blocks(self): stone = Block.from_string_blockstate("minecraft:stone") water = Block.from_string_blockstate("minecraft:water[level=1]") granite = Block.from_string_blockstate("minecraft:granite") dirt = Block.from_string_blockstate("minecraft:dirt") conglomerate_1 = stone + water + dirt self.assertIsInstance(conglomerate_1, Block) self.assertEqual("minecraft", conglomerate_1.namespace) self.assertEqual("stone", conglomerate_1.base_name) self.assertEqual({}, conglomerate_1.properties) self.assertEqual(2, len(conglomerate_1.extra_blocks)) for block_1, block_2 in zip(conglomerate_1.extra_blocks, (water, dirt)): self.assertEqual(block_1, block_2) self.assertEqual(0, len(block_1.extra_blocks)) conglomerate_2 = conglomerate_1 + granite self.assertIsInstance(conglomerate_2, Block) self.assertEqual("minecraft", conglomerate_2.namespace) self.assertEqual("stone", conglomerate_2.base_name) self.assertEqual({}, conglomerate_2.properties) self.assertEqual(3, len(conglomerate_2.extra_blocks)) for block_1, block_2 in zip( conglomerate_2.extra_blocks, (water, dirt, granite) ): self.assertEqual(block_1, block_2) self.assertEqual(0, len(block_1.extra_blocks)) self.assertNotEqual(conglomerate_1, conglomerate_2) conglomerate_3 = conglomerate_2 - dirt self.assertIsInstance(conglomerate_3, Block) self.assertEqual("minecraft", conglomerate_3.namespace) self.assertEqual("stone", conglomerate_3.base_name) self.assertEqual({}, conglomerate_3.properties) self.assertEqual(2, len(conglomerate_3.extra_blocks)) for block_1, block_2 in zip(conglomerate_3.extra_blocks, (water, granite)): self.assertEqual(block_1, block_2) self.assertEqual(0, len(block_1.extra_blocks)) self.assertRaises(TypeError, lambda: stone + 1) self.assertRaises(TypeError, lambda: stone - 1)
def delete( world: "World", dimension: Dimension, selection: SelectionGroup ) -> OperationReturnType: yield from fill( world, dimension, selection, world.translation_manager.get_version("java", (1, 15, 2)).block.to_universal( Block("minecraft", "air") )[0], )
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 _blockstates( specification: dict, namespace_str: str, base_name: str ) -> Generator[Block, None, None]: properties = specification.get("properties", {}) if properties: keys, values = zip(*properties.items()) else: keys, values = (), () values = tuple([amulet_nbt.from_snbt(val) for val in prop] for prop in values) for spec_ in itertools.product(*values): spec = dict(zip(keys, spec_)) yield Block(namespace=namespace_str, base_name=base_name, properties=spec)
def test_fill_operation(self): subbox_1 = SelectionBox((1, 70, 3), (5, 71, 5)) selection = SelectionGroup((subbox_1, )) # Start sanity check self.assertEqual( "universal_minecraft:stone", self.world.get_block(1, 70, 3, OVERWORLD).blockstate, ) self.assertEqual( "universal_minecraft:granite[polished=false]", self.world.get_block(1, 70, 5, OVERWORLD).blockstate, ) # End sanity check generator_unpacker( fill( self.world, OVERWORLD, selection, Block.from_string_blockstate("universal_minecraft:stone"), )) self.world.create_undo_point() for x, y, z in selection.blocks: self.assertEqual( "universal_minecraft:stone", self.world.get_block(x, y, z, OVERWORLD).blockstate, f"Failed at coordinate ({x},{y},{z})", ) self.world.undo() self.assertEqual( "universal_minecraft:stone", self.world.get_block(1, 70, 3, OVERWORLD).blockstate, ) self.assertEqual( "universal_minecraft:granite[polished=false]", self.world.get_block(1, 70, 5, OVERWORLD).blockstate, ) self.world.redo() for x, y, z in selection.blocks: self.assertEqual( "universal_minecraft:stone", self.world.get_block(x, y, z, OVERWORLD).blockstate, f"Failed at coordinate ({x},{y},{z})", )
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 translate_block( input_object: Block, get_block_callback: Optional[GetBlockCallback], block_location: BlockCoordinates, ) -> TranslateBlockCallbackReturn: final_block = None final_block_entity = None final_entities = [] final_extra = False for depth, block in enumerate(input_object.block_tuple): ( output_object, output_block_entity, extra, ) = version.block.from_universal( block, get_block_callback=get_block_callback, block_location=block_location, ) if isinstance(output_object, Block): if __debug__ and output_object.namespace.startswith( "universal"): log.debug( f"Error translating {input_object.full_blockstate} from universal. Got {output_object.full_blockstate}" ) if version.data_version > 0: properties = output_object.properties properties["__version__"] = amulet_nbt.TAG_Int( version.data_version) output_object = Block( output_object.namespace, output_object.base_name, properties, output_object.extra_blocks, ) if final_block is None: final_block = output_object else: final_block += output_object if depth == 0: final_block_entity = output_block_entity elif isinstance(output_object, Entity): final_entities.append(output_object) # TODO: offset entity coords final_extra |= extra return final_block, final_block_entity, final_entities, final_extra
def decode(self, cx: int, cz: int, data: List[ConstructionSection]) -> Tuple["Chunk", AnyNDArray]: """ Create an amulet.api.chunk.Chunk object from raw data given by the format :param cx: chunk x coordinate :param cz: chunk z coordinate :param data: Raw chunk data provided by the format. :return: Chunk object in version-specific format, along with the block_palette for that chunk. """ chunk = Chunk(cx, cz) palette = [] for section in data: if any(s == 0 for s in section.shape): continue if section.blocks is not None: shapex, shapey, shapez = section.shape sx = section.sx % 16 sy = section.sy sz = section.sz % 16 chunk.blocks[sx:sx + shapex, sy:sy + shapey, sz:sz + shapez, ] = section.blocks.astype( numpy.uint32) + len(palette) chunk.block_entities.update(section.block_entities) chunk.entities.extend(section.entities) palette += section.palette np_palette, inverse = numpy.unique(palette, return_inverse=True) np_palette = numpy.insert( np_palette, 0, Block( namespace="minecraft", base_name="air", properties={"block_data": amulet_nbt.TAG_Int(0)}, ), ) inverse += 1 np_palette: AnyNDArray inverse: numpy.ndarray for cy in chunk.blocks.sub_chunks: chunk.blocks.add_sub_chunk( cy, inverse[chunk.blocks.get_sub_chunk(cy)].astype(numpy.uint32)) return chunk, np_palette
def _decode_blocks(self, chunk: Chunk, chunk_sections: Dict[int, TAG_Compound]) -> AnyNDArray: blocks: Dict[int, numpy.ndarray] = {} palette = [Block(namespace="minecraft", base_name="air")] for cy, section in chunk_sections.items(): data = self._decode_block_section(section) if data is not None: arr, section_palette = data blocks[cy] = arr + len(palette) palette += section_palette np_palette, inverse = numpy.unique(palette, return_inverse=True) np_palette: numpy.ndarray inverse: numpy.ndarray inverse = inverse.astype(numpy.uint32) for cy in blocks: blocks[cy] = inverse[blocks[cy]] chunk.blocks = blocks return np_palette
def copy_selection( world: "World", dimension: Dimension, selection: SelectionGroup ): structure = Structure.from_world( world, selection, dimension ) structure_buffer.append(structure) yield from fill( world, dimension, selection, { "fill_block": world.translation_manager.get_version( 'java', (1, 15, 2) ).block.to_universal( Block("minecraft", "air") )[0] } )
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 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