def __init__(self, objectgroup, world_dir): self.objectgroup = objectgroup self.world_dir = world_dir self.level_name = 'Converted Object Group' self.boundary_block = anvil.Block('minecraft', 'barrier') # If True, convert regions that are small enough to structure blocks self.region_structure_blocks = True # If True, use player heads as playerstart regions instead of structure blocks self.playerstart_to_player_head = True
def get_replacement_block(self, repl_chunk, x, y, z): gold_block = anvil.Block("minecraft", "gold_block") b = gold_block # if repl_chunk: # new_block = repl_chunk.get_block(x, y, z) # # TODO expand is_solid list # if is_solid(new_block.id): # b = new_block # b = blue_wool return b
def load(cls, path: Union[Path, str]) -> "Blueprint": self = cls() with open(path) as f: blueprint = json.load(f) for point_str, block in blueprint.items(): point = [int(p) for i, p in enumerate(point_str.split(","))] self[(point[0], point[1], point[2])] = anvil.Block("minecraft", block["block"], block["attrs"]) return self
def __init__(self, objectgroup, world_dir, resources_pack_path=None): self.objectgroup = objectgroup self.world_dir = world_dir self.level_name = 'Converted Object Group' self.boundary_block = anvil.Block('minecraft', 'barrier') # If Not None, it will convert a MC Dungeon resources pack to a MC resources pack self.resources_pack_path = resources_pack_path # If True, convert regions that are small enough to structure blocks self.region_structure_blocks = True # If True, use player heads as playerstart regions instead of structure blocks self.playerstart_to_player_head = True
def repeater(facing: Direction4, delay: int = 1, powered: bool = False, locked: bool = False): return anvil.Block( "minecraft", "repeater", { "delay": delay, "powered": powered, "facing": facing, "locked": locked }, )
def __init__(self, identifier): self.identifier = identifier self.air = anvil.Block("minecraft", "air") self.stone = anvil.Block("minecraft", "stone") self.glass = anvil.Block("minecraft", "glass") self.torch = anvil.Block("minecraft", "torch") self.r_torch = anvil.Block("minecraft", "redstone_torch") self.rail = anvil.Block("minecraft", "rail") self.powered_rail = anvil.Block("minecraft", "powered_rail") # https://tryolabs.com/blog/2013/07/05/run-time-method-patching-python/ # TODO put this somewhere else? anvil.EmptyChunk.save = save_chunk anvil.EmptyRegion.save = save_region
def place_chests(self, region, new_region, x, y, z, direction): # add_x = 1 # add_z = 0 chest = anvil.Block("minecraft", "chest") chest.properties["waterlogged"] = "false" chest.properties["facing"] = "east" chest.properties["type"] = "single" # if direction == cfg.M_DIR_Z: # add_x = 0 # add_z = 1 # chest.properties["shape"] = "east_west" i = 0 chest_x = x chest_y = y - 1 chest_z = z for item_type in self.item_dict: amount = self.item_dict[item_type] while amount > 0: if direction == cfg.M_DIR_Z: chest_x = x + 1 chest_z = z + i else: chest_x = x + i chest_z = z + 1 new_region.set_block(chest, chest_x, chest_y, chest_z) item = "minecraft:" + str(item_type) block_entity, amount = self.create_chest_block_entity( chest_x, chest_y, chest_z, item, amount) chunk_idx_x = chest_x // cfg.CHUNK_B_X chunk_idx_z = chest_z // cfg.CHUNK_B_Z chunk = region.get_chunk(chunk_idx_x, chunk_idx_z) if chunk.data["TileEntities"].tagID != nbt.TAG_Compound.id: chunk.data["TileEntities"] = nbt.TAG_List( name="TileEntities", type=nbt.TAG_Compound) chunk.data["TileEntities"].tags.append(block_entity) i += 1
def convert(self): """Creates a Java Edition world in the world directory from the object group.""" # TODO: Converting to a Java world should be done one region or maybe even # one sub-region at a time. Right now, all regions are kept # in memory until the conversion process is done, which means the memory # usage can be massive for bigger object groups. # anvil-parser doesn't actually support loading a region from a file and # then editing it and writing it to a file again. Regions loaded from a # file are read-only, and the regions that can be edited start out empty. region_cache = {} block_cache = {} def get_region(rx, rz): if f'{rx}x{rz}' in region_cache: return region_cache[f'{rx}x{rz}'] else: region_cache[f'{rx}x{rz}'] = anvil.EmptyRegion(rx, rz) return region_cache[f'{rx}x{rz}'] structure_block = anvil.Block('minecraft', 'structure_block') player_head = anvil.Block('minecraft', 'player_head') def find_room_for_structure_block(area, get_block): xi = range(area[0]) zi = range(area[1]) # Blocks that will break if a stucture block is placed on top of them breakable_blocks = [0x3c, 0xc6] # Check the area and blocks above it for y in range(49): for x in xi: for z in zi: if get_block(x, y, z) == 0 and not get_block( x, y - 1, z) in breakable_blocks: return (x, y, z) # Check blocks below the area for y in range(-1, -49, -1): for x in xi: for z in zi: if get_block(x, y, z) == 0 and not get_block( x, y - 1, z) in breakable_blocks: return (x, y, z) # No room found :( return None if isinstance(self.objectgroup, dict): og = self.objectgroup else: # If objectgroup is a file path, parse the json file with open(self.objectgroup) as json_file: og = json.load(json_file) for tile_dict in og['objects']: if isinstance(tile_dict, Tile): tile = tile_dict else: tile = Tile.from_dict(tile_dict) zi = range(tile.size[2]) yi = range(min(256, tile.size[1])) # For each slice of the tile along the X axis... for tx in range(tile.size[0]): ax = tx + tile.pos[0] rx = ax // 512 # For each column of the slice along the Z axis... for tz in zi: az = tz + tile.pos[2] rz = az // 512 region = get_region(rx, rz) # For each block in the column along the Y axis... for ty in yi: ay = ty + tile.pos[1] # Skip this block if it's outside of the world bounds if ay < 0 or ay >= 256: continue bidx = tile.get_block_index(tx, ty, tz) # If the block is just air, we don't need to do anything if tile.blocks[bidx] == 0: continue # Get the Java block from the cache if it's there bcid = tile.blocks[bidx] << 4 | tile.block_data[bidx] if bcid in block_cache: java_block = block_cache[bcid] else: # If not, find it and add it to the cache to speed things up later mapped_block = find_dungeons_block( tile.blocks[bidx], tile.block_data[bidx]) if mapped_block is None: print( f'Warning: {tile.blocks[bidx]}:{tile.block_data[bidx]} is not mapped to anything. It will be replaced by air.' ) continue if len(mapped_block['java']) > 1: java_block = anvil.Block( *mapped_block['java'][0].split(':', 1), mapped_block['java'][1]) else: java_block = anvil.Block( *mapped_block['java'][0].split(':', 1)) block_cache[bcid] = java_block # Once we have the Java block, add it to the region region.set_block(java_block, ax, ay, az) # TODO: Block post-processing to fix fences, walls, stairs, and more converter_blocks = [] # Add the tile doors to the world for door in tile.doors: def get_block(x, y, z): tx = x + door.pos[0] ty = y + door.pos[1] tz = z + door.pos[2] if f'{tx},{ty},{tz}' in converter_blocks: return -1 if tx >= 0 and tx < tile.size[ 0] and ty >= 0 and ty < tile.size[ 1] and tz >= 0 and tz < tile.size[2]: return tile.get_block_id(tx, ty, tz) else: return 0 pos = find_room_for_structure_block(door.size[::2], get_block) if pos is None: if hasattr(door, 'name'): print( f'Warning: No room to place structure block for door: {door.name}' ) else: print( f'Warning: No room to place structure block for unnamed door.' ) else: tpos = [p + d for p, d in zip(pos, door.pos)] if tpos[0] >= 0 and tpos[0] < tile.size[0] and tpos[ 1] >= 0 and tpos[1] < tile.size[1] and tpos[ 2] >= 0 and tpos[2] < tile.size[2]: apos = [p + t for p, t in zip(tpos, tile.pos)] region = get_region(apos[0] // 512, apos[2] // 512) region.set_block(structure_block, *apos) metadata = door.dict() metadata.pop('name', None) metadata.pop('pos', None) metadata.pop('size', None) if hasattr(door, 'name'): tile_entity = structure_block_entity( *apos, 'SAVE', f'door:{door.name}', json.dumps(metadata), *[-v for v in pos], *door.size) else: tile_entity = structure_block_entity( *apos, 'SAVE', 'door:', json.dumps(metadata), *[-v for v in pos], *door.size) region.chunks[apos[2] // 16 % 32 * 32 + apos[0] // 16 % 32].tile_entities.append(tile_entity) converter_blocks.append( f'{tpos[0]},{tpos[1]},{tpos[2]}') if self.region_structure_blocks: # Add the tile regions to the world for tile_region in tile.regions: # playerstart regions just use a player head instead of a structure block if self.playerstart_to_player_head and hasattr( tile_region, 'tags') and tile_region.tags == 'playerstart': ax = tile.pos[0] + tile_region.pos[0] ay = tile.pos[1] + tile_region.pos[1] az = tile.pos[2] + tile_region.pos[2] rx = ax // 512 rz = az // 512 region = get_region(rx, rz) region.set_block(player_head, ax, ay, az) tile_entity = TAG_Compound() tile_entity.tags.extend([ TAG_String(name='id', value='minecraft:skull'), TAG_Byte(name='keepPacked', value=0), TAG_Int(name='x', value=ax), TAG_Int(name='y', value=ay), TAG_Int(name='z', value=az) ]) region.chunks[az // 16 % 32 * 32 + ax // 16 % 32].tile_entities.append(tile_entity) converter_blocks.append( f'{tile_region.pos[0]},{tile_region.pos[1]},{tile_region.pos[2]}' ) elif tile_region.size[0] <= 48 and tile_region.size[ 1] <= 48 and tile_region.size[2] <= 48: def get_block(x, y, z): tx = x + tile_region.pos[0] ty = y + tile_region.pos[1] tz = z + tile_region.pos[2] if f'{tx},{ty},{tz}' in converter_blocks: return -1 if tx >= 0 and tx < tile.size[ 0] and ty >= 0 and ty < tile.size[ 1] and tz >= 0 and tz < tile.size[2]: return tile.get_block_id(tx, ty, tz) else: return 0 pos = find_room_for_structure_block( tile_region.size[::2], get_block) if pos is None: if hasattr(tile_region, 'name'): print( f'Warning: No room to place structure block for region: {tile_region.name}' ) else: print( f'Warning: No room to place structure block for unnamed region.' ) else: tpos = [ p + d for p, d in zip(pos, tile_region.pos) ] if tpos[0] >= 0 and tpos[0] < tile.size[ 0] and tpos[1] >= 0 and tpos[ 1] < tile.size[1] and tpos[ 2] >= 0 and tpos[2] < tile.size[2]: apos = [p + t for p, t in zip(tpos, tile.pos)] region = get_region(apos[0] // 512, apos[2] // 512) region.set_block(structure_block, *apos) metadata = tile_region.dict() metadata.pop('name', None) metadata.pop('pos', None) metadata.pop('size', None) if hasattr(tile_region, 'name'): tile_entity = structure_block_entity( *apos, 'SAVE', f'region:{tile_region.name}', json.dumps(metadata), *[-v for v in pos], *tile_region.size) else: tile_entity = structure_block_entity( *apos, 'SAVE', 'region:', json.dumps(metadata), *[-v for v in pos], *tile_region.size) region.chunks[ apos[2] // 16 % 32 * 32 + apos[0] // 16 % 32].tile_entities.append(tile_entity) converter_blocks.append( f'{tpos[0]},{tpos[1]},{tpos[2]}') # Add the tile boundaries to the world for boundary in tile.boundaries: ax = tile.pos[0] + boundary.x az = tile.pos[2] + boundary.z rx = ax // 512 rz = az // 512 region = get_region(rx, rz) for by in range(boundary.h): ay = tile.pos[1] + boundary.y + by region.set_block(self.boundary_block, ax, ay, az) # Write regions to files os.makedirs(os.path.join(self.world_dir, 'region'), exist_ok=True) for k in region_cache: region_cache[k].save( os.path.join( self.world_dir, f'region/r.{region_cache[k].x}.{region_cache[k].z}.mca')) # For convenience, write the object group to objectgroup.json in the world # directory, so JavaWorldToObjectGroup can convert the world back to an # object group without any changes. og_copy = json.loads(json.dumps(og)) # faster than copy.deepcopy for tile in og_copy['objects']: tile.pop('blocks', None) tile.pop('boundaries', None) tile.pop('doors', None) tile.pop('height-plane', None) if self.region_structure_blocks and 'regions' in tile: # Keep only regions that are too big turn into structure blocks tile['regions'] = [ r for r in tile['regions'] if r['size'][0] > 48 or r['size'][1] > 48 or r['size'][2] > 48 ] with open(os.path.join(self.world_dir, 'objectgroup.json'), 'w') as out_file: out_file.write(stringify(og_copy)) # Create level.dat file level = NBTFile('level_template.dat', 'rb') level['Data']['LevelName'].value = self.level_name level['Data']['LastPlayed'].value = int(time.time() * 1000) # Place the player spawn above the center of the first tile. # This could probably be made a bit smarter, since the center of the tile # might still be above the void. For now, this faster solution will have to do. level['Data']['SpawnX'].value = int(og['objects'][0]['pos'][0] + og['objects'][0]['size'][0] * 0.5) level['Data']['SpawnY'].value = min( 255, og['objects'][0]['pos'][1] + og['objects'][0]['size'][1]) level['Data']['SpawnZ'].value = int(og['objects'][0]['pos'][2] + og['objects'][0]['size'][2] * 0.5) level.write_file(os.path.join(self.world_dir, 'level.dat'))
from typing import Dict, Tuple, Union from pathlib import Path import json import anvil # type: ignore region = anvil.EmptyRegion(0, 0) stone = anvil.Block("minecraft", "stone") redstone_wire = anvil.Block("minecraft", "redstone_wire") air = anvil.Block("minecraft", "air") for y in range(4): for z in range(32): for x in range(32): region.set_block(stone, x, y, z) Point = Tuple[int, int, int] class Blueprint(Dict[Point, anvil.Block]): @property def dimensions(self) -> Point: max_x = max(point[0] for point in self) max_y = max(point[1] for point in self) max_z = max(point[2] for point in self) return (max_x + 1, max_y + 1, max_z + 1) def place(self, region: anvil.Region, offset: Point = (0, 0, 0)) -> None: for point, block in self.items():
import _path import anvil from random import choice # Create a new region with the `EmptyRegion` class at 0, 0 (in region coords) region = anvil.EmptyRegion(0, 0) # Create `Block` objects that are used to set blocks stone = anvil.Block('minecraft', 'stone') dirt = anvil.Block('minecraft', 'dirt') # Make a 16x16x16 cube of either stone or dirt blocks for y in range(16): for z in range(16): for x in range(16): region.set_block(choice((stone, dirt)), x, y, z) # Save to a file region.save('r.0.0.mca')
def to_block(name): return anvil.Block('minecraft', name)
#!/usr/bin/env python3 import anvil import pyfastnoisesimd as fns import numpy as np import time def to_block(name): return anvil.Block('minecraft', name) # Usefull blocks type grass = anvil.Block('minecraft', 'grass_block') sand = anvil.Block('minecraft', 'sand') bedrock = anvil.Block('minecraft', 'bedrock') dirt = anvil.Block('minecraft', 'dirt') air = anvil.Block('minecraft', 'air') glass = anvil.Block('minecraft', 'glass') pumpkin = anvil.Block('minecraft', 'pumpkin') diorite = anvil.Block('minecraft', 'diorite') stone = anvil.Block('minecraft', 'stone') oak_log_up = anvil.Block('minecraft', 'oak_log', properties={'axis': 'y'}) oak_leaves = anvil.Block('minecraft', 'oak_leaves', properties={'persistent': True}) coal = anvil.Block('minecraft', 'coal_ore') iron = anvil.Block('minecraft', 'iron_ore') gold = anvil.Block('minecraft', 'gold_ore') diamond = anvil.Block('minecraft', 'diamond_ore') redstone = anvil.Block('minecraft', 'redstone_ore')
def torch(facing: Direction4, lit: bool = True): return anvil.Block("minecraft", "redstone_wall_torch", { "lit": lit, "facing": facing })
def modify(self, chunk, repl_chunk, new_region, chunk_x, chunk_z): x = 0 y = 0 z = 0 # Create `Block` objects that are used to set blocks water = anvil.Block("minecraft", "water") diamond_block = anvil.Block("minecraft", "diamond_block") # gold_block = anvil.Block("minecraft", "gold_block") # blue_wool = anvil.Block("minecraft", "blue_wool") # Iterate all blocks and select write the new block to the new_chunk for block in chunk.stream_chunk(): b = block x_region = chunk_x * cfg.CHUNK_B_X + x z_region = chunk_z * cfg.CHUNK_B_Z + z x_global = new_region.x * cfg.REGION_B_X + x_region z_global = new_region.z * cfg.REGION_B_Z + z_region xyz = self.identifier.identified[x_region, y, z_region] if xyz == cfg.WATERBLOCK: b = water print( f"Found water Block ({x},{y},{z}) in Chunk ({chunk_x}, {chunk_z})" ) print(f"GlobalPos: ({x_global}, {y}, {z_global})") elif xyz == cfg.AIRPOCKET: if repl_chunk: b = self.get_replacement_block(repl_chunk, x, y, z) else: b = self.stone print( f"Found AIRPOCKET Block ({x},{y},{z}) in Chunk ({chunk_x}, {chunk_z})" ) print(f"GlobalPos: ({x_global}, {y}, {z_global})") elif xyz == cfg.SOLIDAREA: if repl_chunk: b = self.get_replacement_block(repl_chunk, x, y, z) # b = self.get_replacement_block(repl_chunk, x, y, z) if repl_chunk: new_block = repl_chunk.get_block(x, y, z) # Replace the block if it is solid but use the original when it is not if is_repl_block(new_block.id): b = new_block # TODO debug version b = diamond_block elif xyz != cfg.UNCHANGED: print(f"Found UNIDENTIFIED Block ({x},{y},{z}) " f"in Chunk ({chunk_x}, {chunk_z}) with {xyz}.") print(f"GlobalPos: ({x_global}, {y}, {z_global})") try: new_region.set_block(b, x_global, y, z_global) except OutOfBoundsCoordinates: print(f"could not set Block ({x},{y},{z})") # TODO if z == 15 and x == 15: y += 1 if x == 15: z = (z + 1) % 16 x = (x + 1) % 16