def check_version_leveldat( root_tag: nbt.TAG_Compound, _min: int = None, _max: int = None ) -> bool: """ Check the Version tag from the provided level.dat NBT structure :param root_tag: the root level.dat tag :param _min: The lowest acceptable value (optional) :param _max: The highest acceptable value (optional) :return: Whether the version tag falls in the correct range """ version_found: int = root_tag.get("Data", nbt.TAG_Compound()).get( "Version", nbt.TAG_Compound() ).get("Id", nbt.TAG_Int(-1)).value min_qualifies: bool = True if _min is not None: min_qualifies = version_found >= _min max_qualifies: bool = True if _max is not None: max_qualifies = version_found <= _max if __debug__: min_text: str = f"{min} <= " if _min is not None else "" max_text: str = f" <= {max}" if _max is not None else "" print(f"Checking {min_text}{version_found}{max_text}") return min_qualifies and max_qualifies
def generate_palette(nbtfile: nbt.NBTFile, blankschem: nbt.NBTFile, low_state: str, high_state: str): blankpalette = {k: v.value for k, v in blankschem["Palette"].items()} palette = nbt.TAG_Compound(name="Palette") palette.name = "Palette" for name, id in blankpalette.items(): palette.tags.append(nbt.TAG_Int(name=name, value=id)) to_check = max(blankpalette.values()) + 2 # Find id of low state (create new if it is not present in palette) if low_state not in blankpalette: low_id = min(x for x in range(to_check) if x not in blankpalette.values()) palette.tags.append(nbt.TAG_Int(name=low_state, value=low_id)) else: low_id = blankpalette[low_state] # Find id of high state (create new if it is not present in palette) if high_state not in blankpalette: high_id = min(x for x in range(to_check) if x not in blankpalette.values()) palette.tags.append(nbt.TAG_Int(name=high_state, value=high_id)) else: high_id = blankpalette[high_state] nbtfile.tags.append(palette) nbtfile.tags.append(nbt.TAG_Int(name="PaletteMax", value=len(palette))) return nbtfile, low_id, high_id
def update_server_address(self, ip, name='AWScraft'): ''' Updates the server named <name> with new IP address <ip> in servers.dat. Otherwise creates a new server entry named <name> with <ip> ''' server_file = os.path.join(self.data_directory, 'servers.dat') if not os.path.isfile(server_file): return False # Check Server File Exists with open(server_file, 'rb') as binary: server = nbt.NBTFile(buffer=binary) updated = False for entry in server['servers']: if entry['name'] == name: entry['ip'] = ip updated = True if not updated: new_server = nbt.TAG_Compound() new_server.tags.append(nbt.TAG_String(name='name', value=name)) new_server.tags.append(nbt.TAG_String(name='ip', value=ip)) server['servers'].append(new_server) os.remove(server_file) with open(server_file, 'wb') as new_binary: server.write_file(buffer=new_binary) return True
def generate_meta(nbtfile: nbt.NBTFile, blankschem: nbt.NBTFile): nbtfile["Offset"].value = blankschem["Offset"].value metadata = nbt.TAG_Compound() metadata.tags = blankschem["Metadata"].tags.copy() metadata.name = "Metadata" nbtfile.tags.append(metadata) return nbtfile
def norbert_parse_line(line, sep=DEFAULT_SEP): line = line.strip() name, tagtype, value = norbert_split_line(line, sep[2]) # validate user input if tagtype is None: err("Invalid or missing tag type: " + line) raise IOError(exceptions.INVALID_TYPE, "Not a norbert file") elif tagtype != nbt.TAG_COMPOUND and value is None: err("Tag value not found: " + line) raise IOError(exceptions.INVALID_VALUE, "Not a norbert file") # get the list of names/indexes names = norbert_split_name(name, sep) # create the tag if tagtype == nbt.TAG_LIST: listtype = tag_types[value] tag = nbt.TAG_List(type=nbt.TAGLIST[listtype]) elif tagtype == nbt.TAG_COMPOUND: tag = nbt.TAG_Compound() else: tag = nbt.TAGLIST[tagtype]() retval = set_tag(tag, value) if retval != 0: err("Invalid tag value: " + line) raise IOError(retval, "Not a norbert file") return names, tag
def norbert_add_tag(nbtfile, names, newtag): # give the root TAG_Compound the right name nbtfile.name = names[0] names.pop(0) tag = nbtfile for i, name in enumerate(names): testtag = get_tag(tag, str(name)) # tag already exists if testtag is not None: tag = testtag # add leaf node elif i + 1 == len(names): tag = norbert_add_child(tag, name, newtag) # add a list elif isinstance(names[i + 1], int): # list of basic tags if i + 2 == len(names): listtype = newtag.id # list of lists elif isinstance(names[i + 2], int): listtype = nbt.TAG_LIST # list of compounds else: listtype = nbt.TAG_COMPOUND tag = norbert_add_child(tag, name, \ nbt.TAG_List(type=nbt.TAGLIST[listtype]) ) # add a compound else: tag = norbert_add_child(tag, name, nbt.TAG_Compound())
def toNbt(self) -> nbt.TAG_Compound: data = nbt.TAG_Compound() motion = nbt.TAG_List(name="Motion", type=nbt.TAG_Double) motion.append(nbt.TAG_Double(self.velocity[0])) motion.append(nbt.TAG_Double(self.velocity[1])) motion.append(nbt.TAG_Double(self.velocity[2])) data.tags.append(motion) pos = nbt.TAG_List(name="Pos", type=nbt.TAG_Double) pos.append(nbt.TAG_Double(self.pos[0])) pos.append(nbt.TAG_Double(self.pos[1])) pos.append(nbt.TAG_Double(self.pos[2])) data.tags.append(pos) data.tags.append( nbt.TAG_String(name="id", value=f"minecraft:{self.kind.name}")) data.tags.append(nbt.TAG_Float(name="Health", value=self.health)) data.tags.append(nbt.TAG_Byte(name="OnGround", value=self.onGround)) data.tags.append( nbt.TAG_Int(name="PortalCooldown", value=self.portalCooldown)) if self.extra is not None: extra = self.extra.toNbt() data.tags += extra.tags return data
def template_village_file(tick): """ Creates a template villages.dat file that i can modify later on """ cat = nbt.NBTFile() cat2 = cat['data'] = nbt.TAG_Compound() cat2["Villages"] = nbt.TAG_List(Banana) cat2['Tick'] = nbt.TAG_Int(tick) return cat
def toNbt(self, slotIdx=None) -> Optional[nbt.TAG_Compound]: if self.isEmpty(): return None else: result = nbt.TAG_Compound() result.tags.append(nbt.TAG_String(f'minecraft:{self.item}', 'id')) result.tags.append(nbt.TAG_Byte(self.amount, 'Count')) if slotIdx is not None: result.tags.append(nbt.TAG_Byte(slotIdx, 'Slot')) return result
def create_door(tick, x, y, z): """ Generates a door using given coords and tick. """ door = nbt.TAG_Compound() door['TS'] = nbt.TAG_Int(tick) door['X'] = nbt.TAG_Int(x) door['Y'] = nbt.TAG_Int(y) door['Z'] = nbt.TAG_Int(z) return door
def toNbt(self) -> nbt.TAG_Compound: tag = nbt.TAG_Compound() tag.tags.append(nbt.TAG_Short(self.age, 'Age')) tag.tags.append(nbt.TAG_Short(self.pickupDelay, 'PickupDelay')) item = self.stack.toNbt() assert (item is not None) item.name = 'Item' tag.tags.append(item) return tag
def create_chest_block_entity(self, chest_x, chest_y, chest_z, b_type, amount): items = nbt.TAG_List(name="Items", type=nbt.TAG_Compound) # TODO make this variable by using the config file stacks = min(amount // 64, 27) remainder = amount % 64 if stacks < 27 else 0 for i in range(stacks): chest_entry = nbt.TAG_Compound() chest_entry.tags.extend([ nbt.TAG_Byte(name="Count", value=64), nbt.TAG_Byte(name="Slot", value=i), nbt.TAG_String(name="id", value=b_type), ]) items.tags.append(chest_entry) if stacks < 27: chest_entry = nbt.TAG_Compound() chest_entry.tags.extend([ nbt.TAG_Byte(name="Count", value=amount % 64), nbt.TAG_Byte(name="Slot", value=stacks), nbt.TAG_String(name="id", value=b_type), ]) items.tags.append(chest_entry) block_entity = nbt.TAG_Compound() block_entity.tags.extend([ nbt.TAG_String(name="id", value="minecraft:chest"), nbt.TAG_Int(name="x", value=chest_x), nbt.TAG_Int(name="y", value=chest_y), nbt.TAG_Int(name="z", value=chest_z), nbt.TAG_Byte(name="keepPacked", value=0), ]) block_entity.tags.append(items) new_amount = amount - (stacks * 64 + remainder) return block_entity, new_amount
def create_nbt_from_entry( entry: Union[NBTEntry, NBTListEntry, NBTCompoundEntry]) -> nbt.TAG: tag = None entry_type = entry.tag_type if entry_type in _entry_to_tag_map: tag = _entry_to_tag_map[entry_type](value=entry.value) elif isinstance(entry, NBTCompoundEntry): tag = nbt.TAG_Compound() for name, child_entry in entry.items(): tag[name] = create_nbt_from_entry(child_entry) elif isinstance(entry, NBTListEntry): tag = nbt.TAG_List() for child_entry in entry: tag.append(create_nbt_from_entry(child_entry)) return tag
def gendefaultnbt(self): '''returns an nbt object''' nbtfile = nbt.NBTFile() colors = nbt.TAG_Byte_Array(name="colors") colors.value = bytearray(16384) data = nbt.TAG_Compound() data.name = "data" data.tags = [ nbt.TAG_Int(value=0, name="zCenter"), nbt.TAG_Byte(value=1, name="trackingPosition"), nbt.TAG_Short(value=128, name="width"), nbt.TAG_Byte(value=1, name="scale"), nbt.TAG_Byte(value=0, name="dimension"), nbt.TAG_Int(value=64, name="xCenter"), colors, nbt.TAG_Short(value=128, name="height") ] nbtfile.tags.append(data) return nbtfile
def save_to_nbtfile(barray, map_id): nbtfile = nbt.NBTFile() colors = nbt.TAG_Byte_Array(name="colors") colors.value = barray data = nbt.TAG_Compound() data.name = "data" data.tags = [ nbt.TAG_Byte(value=1, name="scale"), #地图缩放 nbt.TAG_Byte(value=0, name="dimension"), #维度 nbt.TAG_Byte(value=0, name="trackingPosition"), #箭头永不显示 nbt.TAG_Byte(value=1, name="locked"), #被锁定 nbt.TAG_Int(value=0, name="xCenter"), nbt.TAG_Int(value=0, name="zCenter"), nbt.TAG_Short(value=128, name="width"), nbt.TAG_Short(value=128, name="height"), colors ] nbtfile.tags.append(data) nbtfile.write_file("server/world/data/map_{}.dat".format(map_id))
def get_chunk(self, chunk_x, chunk_z): key = (chunk_x, chunk_z) if not key in self.chunks: try: self.chunks[key] = self.region.get_chunk(chunk_x, chunk_z) except region.InconceivedChunk: # create the chunk new_chunk = nbt.NBTFile() level_tag = nbt.TAG_Compound() level_tag.name = "Level" level_tag.tags.append( nbt.TAG_Int(name="xPos", value=chunk_x * 32)) level_tag.tags.append( nbt.TAG_Int(name="zPos", value=chunk_z * 32)) level_tag.tags.append( nbt.TAG_List(name="Sections", type=nbt.TAG_Compound)) new_chunk.tags.append(level_tag) self.chunks[key] = new_chunk return self.chunks[key]
def save(self) -> nbt.NBTFile: """ Saves the chunk data to a :class:`NBTFile` Notes ----- Does not contain most data a regular chunk would have, but minecraft stills accept it. """ root = nbt.NBTFile() root.tags.append(nbt.TAG_Int(name='DataVersion', value=self.version)) level = nbt.TAG_Compound() # Needs to be in a separate line because it just gets # ignored if you pass it as a kwarg in the constructor level.name = 'Level' level.tags.extend([ nbt.TAG_List(name='Entities', type=nbt.TAG_Compound), nbt.TAG_List(name='TileEntities', type=nbt.TAG_Compound), nbt.TAG_List(name='LiquidTicks', type=nbt.TAG_Compound), nbt.TAG_Int(name='xPos', value=self.x), nbt.TAG_Int(name='zPos', value=self.z), nbt.TAG_Long(name='LastUpdate', value=0), nbt.TAG_Long(name='InhabitedTime', value=0), nbt.TAG_Byte(name='isLightOn', value=1), nbt.TAG_String(name='Status', value='full') ]) sections = nbt.TAG_List(name='Sections', type=nbt.TAG_Compound) for s in self.sections: if s: p = s.palette() # Minecraft does not save sections that are just air # So we can just skip them if len(p) == 1 and p[0].name() == 'minecraft:air': continue sections.tags.append(s.save()) level.tags.append(sections) root.tags.append(level) return root
def create_village(tick): """ Creates a template village """ village_template = nbt.TAG_Compound() village_template['Doors'] = nbt.TAG_List(Banana) village_template['Players'] = nbt.TAG_List(Banana) village_template['ACX'] = nbt.TAG_Int(0) village_template['ACY'] = nbt.TAG_Int(0) village_template['ACZ'] = nbt.TAG_Int(0) village_template['CX'] = nbt.TAG_Int(0) village_template['CY'] = nbt.TAG_Int(0) village_template['CZ'] = nbt.TAG_Int(0) village_template['Golems'] = nbt.TAG_Int(0) village_template['MTick'] = nbt.TAG_Int(0) village_template['PopSize'] = nbt.TAG_Int(1) village_template['Radius'] = nbt.TAG_Int(32) village_template['Stable'] = nbt.TAG_Int(tick) village_template['Tick'] = nbt.TAG_Int(tick) return Village(village_template)
def toNbt(self) -> nbt.TAG_Compound: tag = super().toNbt() inventory = nbt.TAG_List(type=nbt.TAG_Compound, name='Inventory') for (slotIdx, item) in enumerate(self.inventory): stackTag = item.stack.toNbt(slotIdx) if stackTag is not None: inventory.append(stackTag) tag.tags.append(inventory) gameMode = 1 if self.creative else 0 tag.tags.append(nbt.TAG_Int(gameMode, 'playerGameType')) abilities = nbt.TAG_Compound() abilities.name = 'abilities' abilities.tags.append(nbt.TAG_Byte(int(self.flying), 'flying')) tag.tags.append(abilities) tag.tags.append(nbt.TAG_String(self.dimension, 'dimension')) return tag
def logContainer(container_nbt): tagName = findTag(container_nbt, 'id') if not tagName: return None if tagName.value in ('ItemFrame', 'MinecartHopper', 'MinecartChest', 'Chest', 'Dropper', 'Trap', 'Hopper', 'Furnace'): pos = findTag(container_nbt, 'Pos') if pos: x = int(pos[0].value) y = int(pos[1].value) z = int(pos[2].value) else: x = findTag(container_nbt, 'x').value y = findTag(container_nbt, 'y').value z = findTag(container_nbt, 'z').value inventory = findTag(container_nbt, 'Items') if not inventory and findTag(container_nbt, 'Item'): inventory = nbt.TAG_Compound() inventory.name = 'Items' inventory.tags.append(findTag(container_nbt, 'Item')) return recordInventory('({0},{1},{2})'.format(x, y, z), inventory) else: return None
def create_empty_section(self, section_y): new_section = nbt.TAG_Compound() data = nbt.TAG_Byte_Array(u"Data") skylight = nbt.TAG_Byte_Array(u"SkyLight") blocklight = nbt.TAG_Byte_Array(u"BlockLight") y = nbt.TAG_Byte() blocks = nbt.TAG_Byte_Array(u"Blocks") # TAG_Byte_Array(u'Data'): [2048 byte(s)] # TAG_Byte_Array(u'SkyLight'): [2048 byte(s)] # TAG_Byte_Array(u'BlockLight'): [2048 byte(s)] # TAG_Byte(u'Y'): 0 # TAG_Byte_Array(u'Blocks'): [4096 byte(s)] data.value = bytearray(2048) skylight.value = bytearray(2048) blocklight.value = bytearray(2048) y.name = u"Y" y.value = section_y blocks.value = bytearray(4096) new_section.tags.extend([data, skylight, blocklight, y, blocks]) return new_section
def make_tag_compound(value): tag = nbt.TAG_Compound() for k, v in value.items(): v.name = k.value tag.tags.append(v) return tag
elif isinstance(template, NBTListStructure): if template.default_value: return template.default_value return NBTListEntry() else: return template.create_entry_from_template() template = loader.load_template(entity_id) entry = NBTCompoundEntry(convert_template(template)) return entry if __name__ == "__main__": print(create_entry_from_nbt(nbt.TAG_Byte(value=4))) compound = nbt.TAG_Compound() compound["test1"] = nbt.TAG_Int(value=-100) compound["test2"] = nbt.TAG_String(value="hello!") test1 = create_entry_from_nbt(compound) print(test1) print(create_nbt_from_entry(test1)) print("=" * 16) test2 = create_entry_from_nbt( nbt.TAG_List(value=[ nbt.TAG_String(value="test1"), nbt.TAG_String(value="test2"), nbt.TAG_String(value="test3"), ])) print(test2)
def save_chunk(self, data) -> nbt.NBTFile: """ Saves the chunk data to a :class:`NBTFile` Notes ----- Does not contain most data a regular chunk would have, but minecraft stills accept it. """ root = nbt.NBTFile() root.tags.append(nbt.TAG_Int(name="DataVersion", value=self.version)) level = nbt.TAG_Compound() # Needs to be in a separate line because it just gets # ignored if you pass it as a kwarg in the constructor level.name = "Level" if data: if data.get("Biomes") is not None: level.tags.append(data["Biomes"]) if data.get("Heightmaps") is not None: level.tags.append(data["Heightmaps"]) # level.tags.append(data["CarvingMasks"]) if data.get("Entities") is not None: level.tags.append(data["Entities"]) if data.get("TileEntities") is not None: level.tags.append(data["TileEntities"]) # if data.get("TileTicks") is not None: # level.tags.append(data["TileTicks"]) if data.get("LiquidTicks") is not None: level.tags.append(data["LiquidTicks"]) ######## if data.get("Lights") is not None: level.tags.append(data["Lights"]) if data.get("LiquidsToBeTicked") is not None: level.tags.append(data["LiquidsToBeTicked"]) if data.get("ToBeTicked") is not None: level.tags.append(data["ToBeTicked"]) if data.get("CarvingMasks") is not None: level.tags.append(data["CarvingMasks"]) ########## if data.get("PostProcessing") is not None: level.tags.append(data["PostProcessing"]) if data.get("Structures") is not None: level.tags.append(data["Structures"]) level.tags.extend([ # nbt.TAG_List(name="Entities", type=nbt.TAG_Compound), # nbt.TAG_List(name="TileEntities", type=nbt.TAG_Compound), # nbt.TAG_List(name="LiquidTicks", type=nbt.TAG_Compound), nbt.TAG_Int(name="xPos", value=self.x), nbt.TAG_Int(name="zPos", value=self.z), # nbt.TAG_Long(name="LastUpdate", value=data["LastUpdate"]), nbt.TAG_Long(name="LastUpdate", value=0), # nbt.TAG_Long(name="InhabitedTime", value=data["InhabitedTime"]), nbt.TAG_Long(name="InhabitedTime", value=0), nbt.TAG_Byte(name="isLightOn", value=1), nbt.TAG_String(name="Status", value="full"), ]) # entities = self.add_entities(data["Entities"]) # level.tags.append(entities) # nbt.TAG_List(name="Entities", type=nbt.TAG_Compound) else: level.tags.extend([ # nbt.TAG_List(name="Entities", type=nbt.TAG_Compound), nbt.TAG_List(name="TileEntities", type=nbt.TAG_Compound), nbt.TAG_List(name="LiquidTicks", type=nbt.TAG_Compound), nbt.TAG_Int(name="xPos", value=self.x), nbt.TAG_Int(name="zPos", value=self.z), nbt.TAG_Long(name="LastUpdate", value=0), nbt.TAG_Long(name="InhabitedTime", value=0), nbt.TAG_Byte(name="isLightOn", value=1), nbt.TAG_String(name="Status", value="full"), ]) sections = nbt.TAG_List(name="Sections", type=nbt.TAG_Compound) for s in self.sections: if s: p = s.palette() # Minecraft does not save sections that are just air # So we can just skip them if len(p) == 1 and p[0].name() == "minecraft:air": continue sections.tags.append(s.save()) level.tags.append(sections) root.tags.append(level) return root
def convert(path): if not os.path.isdir(path): sys.stderr.write('Path is not directory or does not exist:' + path) return False max_payload_size = 50 retry_delay = 3 profile_url = 'https://api.mojang.com/profiles/minecraft' #profile_url = 'http://api.goender.net/api/profiles/minecraft' player_data_path = os.path.join(path, 'players') target_data_path = os.path.join(path, 'playerdata') missing_data_path = os.path.join(path, 'players.missing') converted_data_path = os.path.join(path, 'players.converted') invalid_data_path = os.path.join(path, 'players.invalid') player_files = {} if not os.path.isdir(missing_data_path): os.mkdir(missing_data_path) if not os.path.isdir(converted_data_path): os.mkdir(converted_data_path) if not os.path.isdir(invalid_data_path): os.mkdir(invalid_data_path) for player_file in os.listdir(player_data_path): if os.path.isfile( os.path.join(player_data_path, player_file)) and player_file.endswith('.dat'): name = os.path.splitext(os.path.basename(player_file))[0].lower() player_files[name] = os.path.join(player_data_path, player_file) if not player_files: sys.stderr.write('No player data found!\n') return False if not os.path.isdir(target_data_path): os.mkdir(target_data_path) payload = [] current = 0 for name in player_files.keys(): current = current + 1 payload.append(name) if (float(current) % max_payload_size) != 0 and current != len(player_files): continue request = urllib2.Request(profile_url, json.dumps(payload), {'Content-Type': 'application/json'}) retry = False retry_count = 0 while True: try: response = urllib2.urlopen(request) if retry: sys.stderr.write('Retry successful! Number of retries: ' + str(retry_count) + '\n') retry = False retry_count = 0 break except Exception as e: sys.stderr.write( str(e) + " (don't worry, we'll retry until it works!)\n") retry = True time.sleep(retry_delay) retry_count = retry_count + 1 profiles = json.loads(response.read()) if isinstance(profiles, dict): # http://api.goender.net/api/profiles/minecraft data = profiles profiles = [] for name in data.keys(): profiles.append({'id': data[name], 'name': name}) if len(profiles) != len(payload): payload_names = set([name.lower() for name in payload]) response_names = set( [profile['name'].lower() for profile in profiles]) missing_names = list(payload_names - response_names) for name in missing_names: try: src = player_files[name] shutil.move( src, os.path.join(missing_data_path, os.path.basename(src))) except Exception as e: sys.stderr.write('Error moving file file: ' + src + ' (' + str(e) + ')\n') sys.stderr.write('Missing profiles from API response: ' + repr(missing_names) + '\n') payload = [] for profile in profiles: name = profile['name'].lower() if name not in player_files: continue src = player_files[name] dst = os.path.join(target_data_path, str(uuid.UUID(profile['id'])) + '.dat') try: nbtfile = nbt.NBTFile(src, 'rb') except Exception as e: sys.stderr.write('Error reading NBT file: ' + src + ' (' + str(e) + ')\n') try: shutil.move( src, os.path.join(invalid_data_path, os.path.basename(src))) except: pass continue try: bukkit = nbtfile['bukkit'] except KeyError: bukkit = nbt.TAG_Compound() bukkit.name = 'bukkit' nbtfile.tags.append(bukkit) try: lastKnownName = bukkit['lastKnownName'] except KeyError: lastKnownName = nbt.TAG_String(name='lastKnownName') bukkit.tags.append(lastKnownName) lastKnownName.value = profile['name'] nbtfile.write_file(dst) try: shutil.move( src, os.path.join(converted_data_path, os.path.basename(src))) except Exception as e: sys.stderr.write('Error moving file file: ' + src + ' (' + str(e) + ')\n') return True
def toNbt(self) -> nbt.TAG_Compound: tag = nbt.TAG_Compound() tag.tags.append(nbt.TAG_Short(self.fuse, 'Fuse')) return tag
def to_schem(img_path): # Open file and convert to RGB im = Image.open(img_path) rgb_im = im.convert('RGBA') blockjson = json.load(open('block.json')) palette_blocks = [] indices = {} # Creating palette palette = nbt.TAG_Compound() palette.name = 'Palette' # Initializing new NBT genfile = nbt.NBTFile() genfile.name = 'Schematic' # Setting basic NBT values genfile.tags.append(nbt.TAG_Int(name='Version', value=2)) genfile.tags.append(nbt.TAG_Short(name='Width', value=im.size[0])) genfile.tags.append(nbt.TAG_Short(name='Height', value=1)) genfile.tags.append(nbt.TAG_Short(name='Length', value=im.size[1])) genfile.tags.append(nbt.TAG_Int(name='DataVersion', value=2230)) # Creating block data blockdata = nbt.TAG_Byte_Array() blockdata.name = 'BlockData' # Iterating over each coordinate in the image for c in [(x, z) for x in range(im.size[0]) for z in range(im.size[1])]: # Get the color data from the pixel at coord c rgb = rgb_im.getpixel(c) # Getting the block with the closest color to the image pixel and # appending it to the palette list closest = min(blockjson, key=lambda k: math.dist(rgb, blockjson[k])) if closest not in palette_blocks: palette_blocks.append(closest) # The palette holds all the blocks that are used in a schematic. The field name # is the block name and the value is the index. This index is referenced in the # BlockData field to identify which block is present at a given coord palette[f'minecraft:{closest}'] = nbt.TAG_Int( value=palette_blocks.index(closest)) # Index blocks by x + z * Width + y * Width * Length. If we keep the same # order as the image coordinates the image comes out flipped. indices[c[0] + c[1] * im.size[0] + 1 * im.size[0] * im.size[1]] = palette_blocks.index(closest) # Set the palette length to length of the de-duped palette list genfile.tags.append( nbt.TAG_Int(name='PaletteMax', value=len(palette_blocks))) genfile.tags.append(palette) # A list of integers each referencing a block index from the palette is created # by sorting the indices dict. This list is then turned into a byte array as # that is the type needed by the NBT file. This prevents the image from being # flipped. blockdata.value = bytearray([indices[i] for i in sorted(indices)]) genfile.tags.append(blockdata) return genfile