def verify_snbt(val): nbt_val = amulet_nbt.from_snbt(val) if isinstance(nbt_val, amulet_nbt.TAG_String): assert val[0] == val[ -1] == '"', f'TAG_String {val} not in the strict format' elif isinstance(nbt_val, (amulet_nbt.TAG_Compound, amulet_nbt.TAG_List)): raise Exception('This function does not work with nested data types')
def parse_blockstate_string( blockstate: str, ) -> Tuple[str, str, PropertyType]: 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") } else: properties = {} properties_string = match.group("properties") if properties_string is not None: properties_match = Block.parameters_regex.finditer( properties_string) for match in properties_match: properties[match.group("name")] = match.group("value") return ( namespace, base_name, { k: amulet_nbt.from_snbt(v) for k, v in sorted(properties.items()) }, )
def check_specification_format(data: dict): assert isinstance(data, dict), 'Specification must be a dictionary' properties = data.get('properties', {}) defaults = data.get('defaults', {}) assert isinstance(properties, dict), '"properties" must be a dictionary' assert isinstance(defaults, dict), '"defaults" must be a dictionary' assert sorted(properties.keys()) == sorted( defaults.keys()), 'The keys in "properties" and "defaults" must match' for key, val in properties.items(): assert isinstance(key, str), 'Property names must be strings' verify_string(key) assert isinstance(val, list), 'Property options must be a list of strings' assert all(isinstance(prop, str) for prop in val), 'All property options must be strings' remove_list_duplicates(val) assert isinstance(defaults[key], str), 'All default property values must be strings' assert defaults[ key] in val, f'Default property value ({defaults[key]}) must be in the property list ({val})' for val_ in val: verify_snbt(val_) # verify that the snbt is valid if 'snbt' in data: assert isinstance(data['snbt'], str), 'Specification "snbt" must be a string' while '\n\t' in data['snbt'] or '\n ' in data['snbt']: data['snbt'] = data['snbt'].replace('\n\t', '\n').replace('\n ', '\n') data['snbt'] = data['snbt'].replace('\n', '') try: amulet_nbt.from_snbt(data['snbt']) except: raise Exception(f'Error in snbt') assert 'nbt_identifier' in data and isinstance( data['nbt_identifier'], list ) and len(data['nbt_identifier']) == 2 and all( isinstance(a, str) for a in data['nbt_identifier'] ), 'if "snbt" is defined then "nbt_identifier" must be defined and be [namespace, base_name]' else: assert 'nbt_identifier' not in data, '"nbt_identifier" should only be defined if "snbt" is defined' for key in data.keys(): if key not in ('properties', 'defaults', 'snbt', "nbt_identifier"): log_to_file(f'Extra key "{key}" found')
def properties(self) -> PropertyType: if self.wildcard_mode: raise Exception( "Accessing the properties attribute is invalid in wildcard mode" ) return { key: amulet_nbt.from_snbt(val) for key, val in self.str_properties.items() }
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 properties(self) -> Dict[str, SNBTType]: properties = {} for name, value in self._properties.values(): try: nbt = amulet_nbt.from_snbt(value.GetValue()) except: continue if name.GetValue() and isinstance(nbt, PropertyDataTypes): properties[name.GetValue()] = nbt.to_snbt() return properties
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 merge_primitive_specification(obj1: dict, obj2: dict) -> dict: assert isinstance(obj1, dict) and isinstance(obj2, dict) # { # "properties": { # "prop": [ # "SNBT" # ] # }, # "defaults": { # "prop": "SNBT" # }, # "snbt": "snbt_str", # "nbt_identifier": ["namespace", "base_name"] # } if 'properties' in obj2: obj1.setdefault("properties", {}) obj1.setdefault("defaults", {}) for prop, values in obj2['properties'].items(): obj1['properties'][prop] = obj1['properties'].get(prop, []) + [ val for val in values if val not in obj1['properties'].get(prop, []) ] obj1['defaults'][prop] = obj2['defaults'][prop] if 'snbt' in obj2: if 'snbt' in obj1: obj1['snbt'] = merge_nbt(amulet_nbt.from_snbt(obj1['snbt']), amulet_nbt.from_snbt( obj2['snbt'])).to_snbt() else: obj1['snbt'] = obj2['snbt'] if 'nbt_identifier' in obj2: if 'nbt_identifier' in obj1: assert obj1['nbt_identifier'] == obj2[ 'nbt_identifier'], f'nbt identifiers do not match {obj1["nbt_identifier"]}, {obj2["nbt_identifier"]}' else: obj1['nbt_identifier'] = obj2['nbt_identifier'] return obj1
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 _change_value(self, snbt: SNBTType, snbt_text: wx.StaticText): try: nbt = amulet_nbt.from_snbt(snbt) except: snbt_text.SetLabel("Invalid SNBT") snbt_text.SetBackgroundColour((255, 200, 200)) else: if isinstance(nbt, PropertyDataTypes): snbt_text.SetLabel(nbt.to_snbt()) snbt_text.SetBackgroundColour(wx.NullColour) else: snbt_text.SetLabel(f"{nbt.__class__.__name__} not valid") snbt_text.SetBackgroundColour((255, 200, 200)) self.Layout()
def get_blockstates(version, namespace_str, base_name): block_specification = version.block.get_specification( namespace_str, base_name, True) properties = block_specification.get("properties", {}) if len(properties) > 0: keys, values = zip(*properties.items()) else: keys, values = (), () values = tuple([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 main(path): for dump_file_path in glob.glob(os.path.join(path, '**', 'blocks.txt'), recursive=True): print(dump_file_path) insane_map = {} insane_map_diff = {} with open(dump_file_path) as f: for line in f.readlines(): line = line.strip() block = amulet_nbt.from_snbt(line) if 'Block' not in block: continue sane = block['Block']['name'].value insane = block['Name'].value insane_map[sane] = insane if sane != insane: insane_map_diff[sane] = insane with open(os.path.join(os.path.dirname(dump_file_path), 'block_id_to_item_id.json'), 'w') as f: json.dump(insane_map, f, indent='\t') with open(os.path.join(os.path.dirname(dump_file_path), 'block_id_to_item_id_diff.json'), 'w') as f: json.dump(insane_map_diff, f, indent='\t')
def properties(self, properties: Dict[str, SNBTType]): self.Freeze() self._properties.clear() self._property_sizer.Clear(True) spec_properties: Dict[str, List[str]] = self._specification.get( "properties", {}) spec_defaults = self._specification.get("defaults", {}) for name, choices in spec_properties.items(): label = wx.StaticText(self, label=name) self._property_sizer.Add(label, 0, wx.ALIGN_CENTER) if self._wildcard_mode: choices = ["*"] + choices choice = wx.Choice(self, choices=choices) self._property_sizer.Add(choice, 0, wx.EXPAND) choice.Bind( wx.EVT_CHOICE, lambda evt: wx.PostEvent( self, PropertiesChangeEvent(self.GetId(), properties=self.properties), ), ) val = spec_defaults[name] if name in properties and val != "*": try: snbt = amulet_nbt.from_snbt(properties[name]).to_snbt() except: pass else: if snbt in choices: val = snbt if val in choices: choice.SetSelection(choices.index(val)) self._properties[name] = choice self.Thaw() self.Fit() self.Layout()
def _translate( block_input: Union[Block, None], nbt_input: Union[NBTFile, None], mappings: List[dict], get_block_callback: Callable = None, absolute_location: BlockCoordinates = (0, 0, 0), relative_location: BlockCoordinates = (0, 0, 0), nbt_path: Tuple[str, str, List[Tuple[Union[str, int], str]]] = None, inherited_data: Tuple[Union[str, None], Union[str, None], dict, bool, bool] = None, ) -> Tuple[Union[str, None], Union[str, None], dict, bool, bool]: """ :param block_input: :param nbt_input: :param mappings: :param get_block_callback: :param absolute_location: :param relative_location: :param inherited_data: :return: output_name - string of the object being output output_type - string of the type output name is (should be 'block' or 'entity') new - a dictionary that looks like this {'properties': {}, 'nbt': []} extra_needed - bool. Specifies if more data is needed (ie if the block callback needs to be given to do a full map) cacheable - bool. Specifies if the input object is cachable. Only true for simple Blocks without BlockEntities """ output_name: Optional[str] if inherited_data is not None: output_name, output_type, new_data, extra_needed, cacheable = inherited_data else: output_name = None output_type = None new_data = { "properties": {}, "nbt": [], } # There could be multiple 'new_block' functions in the mappings so new properties are put in here and merged at the very end new_data["nbt"]: List[ Tuple[ str, str, List[Union[str, int], str], Union[str, int], Union[ TAG_Byte, TAG_Short, TAG_Int, TAG_Long, TAG_Float, TAG_Double, TAG_Byte_Array, TAG_String, TAG_List, TAG_Compound, TAG_Int_Array, TAG_Long_Array, ], ] ] """ new['nbt'] = [ [ [ [path0, type0], [path1, type1], ... ], path_n, NBT ], ... ] """ extra_needed = False # used to determine if extra data is required (and thus to do block by block) cacheable = True # cacheable until proven otherwise for translate_function in mappings: function_name = translate_function["function"] if "new_block" == function_name: # { # "function": "new_block", # "options": "<namespace>:<base_name>" # } output_name = translate_function["options"] output_type = "block" elif "new_entity" == function_name: # { # "function": "new_entity", # "options": "<namespace>:<base_name>" # } output_name = translate_function["options"] output_type = "entity" elif "new_properties" == function_name: # { # "function": "new_properties", # "options": { # "<property_name>": "<SNBT>", # eg "val", "54b" # } # } for key, val in translate_function["options"].items(): new_data["properties"][key] = amulet_nbt.from_snbt(val) elif "carry_properties" == function_name: # { # "function": "carry_properties", # "options": { # "<property_name>": ["<property_value"], # "<nbt_property_name>": ['<SNBT>'] # } # } assert isinstance(block_input, Block), "The block input is not a block" for key in translate_function["options"]: if key in block_input.properties: val = block_input.properties[key] if isinstance(val, amulet_nbt.BaseValueType): hash_val = val.to_snbt() else: continue if hash_val in translate_function["options"][key]: new_data["properties"][key] = val elif "map_properties" == function_name: # { # "function": "map_properties", # "options": { # "<property_name>": { # "<SNBT>": [ # <functions> # ] # } # } # } assert isinstance(block_input, Block), "The block input is not a block" for key in translate_function["options"]: if key in block_input.properties: val = block_input.properties[key] if isinstance(val, amulet_nbt.BaseValueType): val = val.to_snbt() else: continue if val in translate_function["options"][key]: ( output_name, output_type, new_data, extra_needed, cacheable, ) = _translate( block_input, nbt_input, translate_function["options"][key][val], get_block_callback, absolute_location, relative_location, nbt_path, ( output_name, output_type, new_data, extra_needed, cacheable, ), ) elif "multiblock" == function_name: # { # "function": "multiblock", # "options": [ # { # "coords": [dx, dy, dz], # "functions": <functions> # } # ] # } cacheable = False if get_block_callback is None: extra_needed = True else: multiblocks = translate_function["options"] if isinstance(multiblocks, dict): multiblocks = [multiblocks] for multiblock in multiblocks: new_location = ( relative_location[0] + multiblock["coords"][0], relative_location[1] + multiblock["coords"][1], relative_location[2] + multiblock["coords"][2], ) new_absolute_location = ( absolute_location[0] + multiblock["coords"][0], absolute_location[1] + multiblock["coords"][1], absolute_location[2] + multiblock["coords"][2], ) try: block_input_, nbt_input_ = get_block_callback(new_location) if nbt_input_ is not None: nbt_input_ = nbt_input_.nbt ( output_name, output_type, new_data, extra_needed, cacheable, ) = _translate( block_input_, nbt_input_, multiblock["functions"], get_block_callback, new_absolute_location, new_location, nbt_path, ( output_name, output_type, new_data, extra_needed, cacheable, ), ) except ChunkLoadError: continue elif "map_block_name" == function_name: # { # "function": "map_block_name", # "options": { # "<namespace>:<base_name>": [ # <functions> # ] # } # } assert isinstance( block_input, Block ), f"The block input {block_input} is not a block" block_name = f"{block_input.namespace}:{block_input.base_name}" if block_name in translate_function["options"]: ( output_name, output_type, new_data, extra_needed, cacheable, ) = _translate( block_input, nbt_input, translate_function["options"][block_name], get_block_callback, absolute_location, relative_location, nbt_path, (output_name, output_type, new_data, extra_needed, cacheable), ) elif "walk_input_nbt" == function_name: # This is a special function unlike the others. See _convert_walk_input_nbt for more information # { # "function": "walk_input_nbt", # "outer_name": "", # defaults to this if undefined # "options": { # "type": "<nbt type>", # check that the nbt is of this type # "self_default": [], # if the type is different run these functions : defaults to [{"function": "carry_nbt"}] which carries everything # "functions": [], # functions to run if defined # # "keys": { # only for compound type # str: {nested options format} # }, # "index": { # only for list or array types # str(<int>): {nested options format} (type should not be defined for nested array types) # }, # "nested_default": [] # only for compound, list or array types. # If nested key/index is not in respective dictionary run these functions on them. # If undefined defaults to [{"function": "carry_nbt"}] which carries everything # } # } cacheable = False if nbt_input is None: extra_needed = True else: custom_nbt_path = translate_function.get("path", []) if custom_nbt_path: nbt_temp = index_nbt(nbt_input, ("", "compound", custom_nbt_path)) if nbt_temp is None: log.error(f"Expected nbt data at {custom_nbt_path}") elif not isinstance( nbt_temp, datatype_to_nbt(custom_nbt_path[-1][-1]) ): log.error( f"Expected nbt data at {custom_nbt_path} to be an {custom_nbt_path[-1][-1]} tag but got {nbt_temp.__class__.__name__}" ) else: ( output_name, output_type, new_data, extra_needed, cacheable, ) = _convert_walk_input_nbt( block_input, nbt_input, translate_function["options"], get_block_callback, absolute_location, relative_location, ("", "compound", custom_nbt_path), ( output_name, output_type, new_data, extra_needed, cacheable, ), ) else: ( output_name, output_type, new_data, extra_needed, cacheable, ) = _convert_walk_input_nbt( block_input, nbt_input, translate_function["options"], get_block_callback, absolute_location, relative_location, nbt_path, (output_name, output_type, new_data, extra_needed, cacheable), ) elif "new_nbt" == function_name: # when used outside walk_input_nbt # { # "function": "new_nbt", # "options": [ # { # "outer_name": "", # defaults to this if undefined # "outer_type": "compound", # defaults to this if undefined # "path": [ # optional. Defaults to the root # [ < path1 >: Union[str, int], < datatype1 >: str] # ] # "key": <key>: str or int, # "value": "<SNBT>" # } # ] # } # when used inside walk_input_nbt # { # "function": "new_nbt", # "options": [ # { # "outer_name": "", # defaults to this if undefined # "outer_type": "compound", # defaults to this if undefined # "path": [ # optional. [] to be the root, undefined to be the input path # [ < path1 >: Union[str, int], < datatype1 >: str] # ] # "key": <key>: Union[str, int], # "value": "<SNBT>" # } # ] # } new_nbts = translate_function["options"] if isinstance(new_nbts, dict): new_nbts = [new_nbts] for new_nbt in new_nbts: if "path" in new_nbt: path = new_nbt["path"] elif nbt_path is None: path = [] else: path = nbt_path[2] outer_name = new_nbt.get("outer_name", "") outer_type = new_nbt.get("outer_type", "compound") new_data["nbt"].append( ( outer_name, outer_type, path, new_nbt["key"], amulet_nbt.from_snbt(new_nbt["value"]), ) ) elif "carry_nbt" == function_name: # only works within walk_input_nbt # { # "function": "carry_nbt", # "options": { # "outer_name": "", # defaults to this if undefined # "outer_type": "compound", # defaults to this if undefined # "path": [ # [] to be the root, undefined to be the input path # [ <path1>: Union[str, int], <datatype1>: str], # ... # ], # "key": <key>: Union[str, int] # undefined to remain under the same key/index # "type": <type>: str # undefined to remain as the input type # } # } cacheable = False if nbt_input is None: extra_needed = True elif nbt_path is not None: nbt = index_nbt(nbt_input, nbt_path) if nbt is None: raise Exception( "This code should not be run because it should be caught by other code before it gets here." ) val = nbt.value options = translate_function.get("options", {}) outer_name = options.get("outer_name", "") outer_type = options.get("outer_type", "compound") path = options.get("path", nbt_path[2][:-1]) key = options.get("key", nbt_path[2][-1][0]) nbt_type = options.get("type", nbt_path[2][-1][1]) # TODO: some kind of check to make sure that the input data type nbt_path[-1][1] can be cast to nbt_type # perhaps this should be done in the compiler rather than at runtime new_data["nbt"].append( (outer_name, outer_type, path, key, datatype_to_nbt(nbt_type)(val)) ) elif "map_nbt" == function_name: # { # "function": "map_nbt", # "options": { # based on the input nbt value at path (should only be used with end stringable datatypes) # "cases": {}, # if the data is in here then do the nested functions # "default": [] # if the data is not in cases or cases is not defined then do these functions # } # } cacheable = False if nbt_input is None: extra_needed = True elif nbt_path is not None: run_default = True if "cases" in translate_function["options"]: nbt = index_nbt(nbt_input, nbt_path) nbt_hash = nbt.to_snbt() if nbt_hash in translate_function["options"]["cases"]: ( output_name, output_type, new_data, extra_needed, cacheable, ) = _translate( block_input, nbt_input, translate_function["options"]["cases"][nbt_hash], get_block_callback, absolute_location, relative_location, nbt_path, ( output_name, output_type, new_data, extra_needed, cacheable, ), ) run_default = False if run_default and "default" in translate_function["options"]: ( output_name, output_type, new_data, extra_needed, cacheable, ) = _translate( block_input, nbt_input, translate_function["options"]["default"], get_block_callback, absolute_location, relative_location, nbt_path, (output_name, output_type, new_data, extra_needed, cacheable), ) elif "code" == function_name: # { # "function": "code", # when all the other functions fail you this should do what you need. Use as sparingly as possible # "options": { # "input": ["namespace", "base_name", "properties", "nbt"], # all of these inputs and output are optional. Change these lists to modify # "output": ["output_name", "output_type", "new_properties", "new_nbt"], # "function": "function_name" # this links to a lua funciton in the lua directory with the file name function_name.lua # } # } # this function was originally designed to be lua code but I have now switched it to python because lua is hard :( # Might swap back one day # this would be in function_name.py # def main(namespace, base_name, properties, nbt) # return "minecraft:air", "block", {"property_name": "property_name"}, [] # usage examples: # splitting and merging strings in signs cacheable = False options = translate_function["options"] function_inputs = [] for inp in options.get("input", []): if inp == "namspace": function_inputs.append(block_input.namespace) elif inp == "base_name": function_inputs.append(block_input.base_name) elif inp == "properties": function_inputs.append(block_input.properties) elif inp == "nbt": if nbt_input is None: extra_needed = True function_inputs.append(["compound", {}]) else: function_inputs.append(objectify_nbt(nbt_input)) elif inp == "location": function_inputs.append(absolute_location) function_output = code_functions.run(options["function"], function_inputs) if not isinstance(function_output, tuple): function_output = (function_output,) for out, out_name in zip(function_output, options["output"]): if out_name == "output_name": assert isinstance(out, str) output_name = out elif out_name == "output_type": assert isinstance(out, str) output_type = out elif out_name == "new_properties": assert isinstance(out, dict) for key, val in out.items(): new_data["properties"][key] = amulet_nbt.from_snbt(val) elif out_name == "new_nbt": assert isinstance(out, list) for val in out: assert len(val) == 5 new_data["nbt"].append( tuple(val[:4]) + (unobjectify_nbt(val[4]),) ) return output_name, output_type, new_data, extra_needed, cacheable
def nbt_from_list( outer_name: str, outer_type: str, nbt_list: List[ Tuple[ str, str, List[Tuple[Union[str, int], str]], Union[str, int], Union[ TAG_Byte, TAG_Short, TAG_Int, TAG_Long, TAG_Float, TAG_Double, TAG_Byte_Array, TAG_String, TAG_List, TAG_Compound, TAG_Int_Array, TAG_Long_Array, ], ] ], default_template: str = None, ) -> NBTFile: if default_template is not None: nbt_object = amulet_nbt.from_snbt(default_template) else: nbt_object = datatype_to_nbt(outer_type)() for nbt_entry in nbt_list: outer_name_, outer_type_, nbt_path, data_path, data = nbt_entry if outer_name == outer_name_ and outer_type == outer_type_: nbt_temp: Union[ TAG_Byte, TAG_Short, TAG_Int, TAG_Long, TAG_Float, TAG_Double, TAG_Byte_Array, TAG_String, TAG_List, TAG_Compound, TAG_Int_Array, TAG_Long_Array, ] = nbt_object for path, nbt_type in nbt_path: # if the nested NBT object does not exist then create it if isinstance(nbt_temp, TAG_Compound): if ( path not in nbt_temp or nbt_to_datatype(nbt_temp[path]) != nbt_type ): nbt_temp[path] = datatype_to_nbt(nbt_type)() elif isinstance(nbt_temp, TAG_List): # if the list is a different type to nbt_type replace it with nbt_type if ( _int_to_datatype[int(nbt_temp.list_data_type)] != nbt_type and len(nbt_temp) > 0 ): nbt_temp.list_data_type = datatype_to_nbt(nbt_type).tag_id for index in range(len(nbt_temp)): nbt_temp[index] = datatype_to_nbt(nbt_type)() # pad out the list to the length of path if path + 1 > len(nbt_temp): # pad out the list to the length of the index for _ in range(path + 1 - len(nbt_temp)): nbt_temp.insert(datatype_to_nbt(nbt_type)()) # we now should have a TAG_List of the same type as nbt_type and length as path nbt_temp = nbt_temp[path] if isinstance(nbt_temp, TAG_Compound): nbt_temp[data_path] = data elif isinstance(nbt_temp, TAG_List): # if the list is a different type to data replace it with type(data) if nbt_temp.list_data_type != data.tag_id and len(nbt_temp) > 0: nbt_temp.list_data_type = data.tag_id for index in range(len(nbt_temp)): nbt_temp[index] = data.__class__() # pad out the list to the length of path if data_path + 1 > len(nbt_temp): # pad out the list to the length of the index for _ in range(data_path + 1 - len(nbt_temp)): nbt_temp.append(data.__class__()) # we now should have a TAG_List of the same type as nbt_type and length as data_path nbt_temp[data_path] = data # TODO: # elif isinstance(nbt_temp, TAG_Byte_Array) and isinstance(data, TAG_Byte): # # pad to the length of data_path if less than the current length # # nbt_temp[data_path] = data.value # elif isinstance(nbt_temp, TAG_Int_Array) and isinstance(data, TAG_Int): # elif isinstance(nbt_temp, TAG_Long_Array) and isinstance(data, TAG_Long): return NBTFile(nbt_object, outer_name)
}] }, } if len(block["properties"]) > 0: primitive["to_universal"].append({ "function": "map_properties", "options": { prop: { snbt_val: [{ "function": "new_properties", "options": { (property_name_remap[prop][0] if prop in property_name_remap else prop): (amulet_nbt.from_snbt( property_name_remap[prop][1][val] if prop in property_name_remap and val in property_name_remap[prop][1] else val). to_snbt()) }, }] for snbt_val, val in block["properties"][prop] } for prop in block["properties"].keys() }, }) primitive["from_universal"][ f"universal_{namespace}:{base_name}"].append({ "function": "map_properties", "options": {(property_name_remap[prop][0]
def translate( object_input: Union[Block, Entity], input_spec: dict, mappings: List[dict], output_version: "Version", force_blockstate: bool, get_block_callback: Callable[ [BlockCoordinates], Tuple[Block, Union[None, BlockEntity]] ] = None, extra_input: BlockEntity = None, pre_populate_defaults: bool = True, block_location: Optional[BlockCoordinates] = None, ) -> Tuple[Union[Block, Entity], Union[BlockEntity, None], bool, bool]: """ A function to translate the object input to the output version :param object_input: the Block or Entity object to be converted :param input_spec: the specification for the object_input from the input block_format :param mappings: the mapping file for the input_object :param output_version: A way for the function to look at the specification being converted to. (used to load default properties) :param force_blockstate: True to get the blockstate format. False to get the native format (these are sometimes the same) :param get_block_callback: A callable with relative coordinates that returns a Block and optional BlockEntity :param extra_input: secondary to the object_input a block entity can be given. This should only be used in the select block tool or plugins. Not compatible with location :param pre_populate_defaults: should the nbt structure (if exists) be populated with the default values :param block_location: optional coordinate of where the block is in the world. Used in very few situations. :return: output, extra_output, extra_needed, cacheable extra_needed: a bool to specify if more data is needed beyond the object_input cacheable: a bool to specify if the result can be cached to reduce future processing Block, None, bool, bool Block, BlockEntity, bool, bool Entity, None, bool, bool """ if block_location is not None: block_location = ( # force block locations to be ints int(block_location[0]), int(block_location[1]), int(block_location[2]), ) # set up for the _translate function which does the actual conversion if isinstance(object_input, Block): block_input = object_input if ( extra_input is None and "snbt" in input_spec and get_block_callback is not None ): # if the callback function is defined then load the BlockEntity from the world extra_input = get_block_callback((0, 0, 0))[1] if extra_input is None: # if BlockEntity is still None create it based off the specification namespace, base_name = input_spec["nbt_identifier"] extra_input = BlockEntity( namespace, base_name, 0, 0, 0, NBTFile(amulet_nbt.from_snbt(input_spec["snbt"])), ) nbt_input = extra_input.nbt elif extra_input is not None: # if the BlockEntity is already defined in extra_input continue with that assert isinstance(extra_input, BlockEntity) nbt_input = extra_input.nbt else: # if callback and extra_input are both None then continue with the mapping as normal but without the BlockEntity. # The mappings will do as much as it can and will return the extra_needed flag as True telling the caller to run with callback if possible nbt_input = None elif isinstance(object_input, Entity): assert ( extra_input is None ), "When an Entity is the first input the extra input must be None" block_input = None nbt_input = object_input.nbt else: raise Exception # run the conversion output_name, output_type, new_data, extra_needed, cacheable = _translate( block_input, nbt_input, mappings, get_block_callback, block_location ) # sort out the outputs from the _translate function extra_output = None if output_type == "block": # we should have a block output # create the block object based on output_name and new['properties'] namespace, base_name = output_name.split(":", 1) spec = output_version.block.get_specification( namespace, base_name, force_blockstate ) properties = spec.get("defaults", {}) # cast to NBT properties = { prop: amulet_nbt.from_snbt(val) for prop, val in properties.items() } for key, val in new_data["properties"].items(): properties[key] = val output = Block(namespace, base_name, properties) if "snbt" in spec: namespace, base_name = spec.get("nbt_identifier", ["unknown", "unknown"]) if pre_populate_defaults: nbt = nbt_from_list( spec.get("outer_name", ""), spec.get("outer_type", "compound"), new_data["nbt"], spec.get("snbt", "{}"), ) else: nbt = nbt_from_list( spec.get("outer_name", ""), spec.get("outer_type", "compound"), new_data["nbt"], ) extra_output = BlockEntity(namespace, base_name, 0, 0, 0, nbt) # not quite sure how to handle coordinates here. # it makes sense to me to have the wrapper program set the coordinates so none are missed. elif new_data["nbt"]: log.warning( f"New nbt present but no output block entity\nin:{object_input.blockstate}\nout:{output.blockstate}" ) elif output_type == "entity": # we should have an entity output # create the entity object based on output_name and new['nbt'] namespace, base_name = output_name.split(":", 1) spec = output_version.entity.get_specification( namespace, base_name, force_blockstate ) if pre_populate_defaults: nbt = nbt_from_list( spec.get("outer_name", ""), spec.get("outer_type", "compound"), new_data["nbt"], spec.get("snbt", "{}"), ) else: nbt = nbt_from_list( spec.get("outer_name", ""), spec.get("outer_type", "compound"), new_data["nbt"], ) output = Entity(namespace, base_name, 0.0, 0.0, 0.0, nbt) else: raise Exception("No output object given.") return output, extra_output, extra_needed, cacheable