def from_universal( self, entity: "Entity", force_blockstate: bool = False ) -> Union[Tuple[Block, Optional[BlockEntity]], Tuple[Entity, None]]: """ A method to translate a given Entity object from the Universal format to the format of this class instance. :param entity: The entity to translate :param force_blockstate: True to get the blockstate format. False to get the native format (these are sometimes the same) :return: There are two formats that can be returned. The first is a Block and an optional BlockEntity. The second is an Entity and None. """ assert isinstance(entity, Entity), "entity must be an Entity instance" try: input_spec = self._universal_format.entity.get_specification( entity.namespace, entity.base_name ) mapping = self.get_mapping_from_universal( entity.namespace, entity.base_name, force_blockstate ) except KeyError: log.warning( f"Could not find translation information for {self._mode} {entity} from universal in {self._parent_version}. If this is not a vanilla entity ignore this message" ) return copy.deepcopy(entity), None output, extra_output, _, _ = self._translate( copy.deepcopy(entity), input_spec, mapping, self._parent_version, force_blockstate, "from_universal", ) return output, extra_output
def to_universal(self, entity: "Entity", force_blockstate: bool = False) -> Entity: """ A method to translate a given Entity object to the Universal format. :param entity: The entity to translate :param force_blockstate: True to get the blockstate format. False to get the native format (these are sometimes the same) :return: The translated Entity """ assert isinstance(entity, Entity), "entity must be an Entity instance" try: input_spec = self.get_specification( entity.namespace, entity.base_name, force_blockstate ) mapping = self.get_mapping_to_universal( entity.namespace, entity.base_name, force_blockstate ) except KeyError: log.warning( f"Could not find translation information for {self._mode} {entity} to universal in {self._parent_version}. If this is not a vanilla entity ignore this message" ) return copy.deepcopy(entity) output, _, _, _ = self._translate( copy.deepcopy(entity), input_spec, mapping, self._universal_format, True, "to universal", ) return output
def pack(self, biome: str) -> int: """Pack the namespaced string biome value into the raw numerical format This will first use any pre-registered mappings bound using TranslationManager.biome_registry.register If it can't be found there it will fall back to the vanilla ones. If it still can't be found it will fall back to plains""" if biome in self._translation_manager.biome_registry: biome_int = self._translation_manager.biome_registry.private_to_int( biome) elif biome in self._biome_str_to_int: biome_int = self._biome_str_to_int[biome] else: log.warning(f"Error processing biome {biome}. Setting to plains.") biome_int = self.pack( "minecraft:plains" ) # TODO: perhaps find a way to assign default dynamically return biome_int
def unpack(self, biome: int) -> str: """Unpack the raw numerical biome value into the namespaced string format. This will first use any pre-registered mappings bound using TranslationManager.biome_registry.register If it can't be found there it will fall back to the vanilla ones. If it still can't be found it will fall back to plains""" if isinstance(biome, numpy.integer): biome = int(biome) if biome in self._translation_manager.biome_registry: biome_str = self._translation_manager.biome_registry.private_to_str( biome) elif biome in self._biome_int_to_str: biome_str = self._biome_int_to_str[biome] else: log.warning( f"Could not find registered value for biome {biome}. Reverting to plains" ) biome_str = "minecraft:plains" return biome_str
def _warn_once(self, unique, msg_fmt, *args): if unique not in self._error_cache: log.warning(msg_fmt.format(*args)) self._error_cache.add(unique)
def from_universal( self, block: "Block", block_entity: "BlockEntity" = None, force_blockstate: bool = False, block_location: BlockCoordinates = (0, 0, 0), get_block_callback: Callable[ [Tuple[int, int, int]], Tuple[Block, Union[None, BlockEntity]] ] = None, ) -> Union[ Tuple[Block, Optional[BlockEntity], bool], Tuple[Entity, None, bool], ]: """ Translate the given Block object from the Universal format to the parent Version's format. :param block: The block to translate :param block_entity: An optional block entity related to the block input :param force_blockstate: True to get the blockstate format. False to get the native format (these are sometimes the same) :param block_location: The location of the block in the world :param get_block_callback: A callable with relative coordinates that returns a Block and optional BlockEntity :return: There are two formats that can be returned. The first is a Block, optional BlockEntity and a bool. The second is an Entity, None and a bool. The bool specifies if block_location and get_block_callback are required to fully define the output data. """ assert isinstance(block, Block), "block must be a Block instance" cache_key = ("from_universal", force_blockstate) if block_entity is None: if block in self._cache[cache_key]: output, extra_output, extra_needed = self._cache[cache_key][block] if isinstance(output, Entity): output = copy.deepcopy(output) extra_output = copy.deepcopy(extra_output) return output, extra_output, extra_needed else: assert isinstance( block_entity, BlockEntity ), "extra_input must be None or a BlockEntity" block_entity = copy.deepcopy(block_entity) try: input_spec = self._universal_format.block.get_specification( block.namespace, block.base_name ) mapping = self.get_mapping_from_universal( block.namespace, block.base_name, force_blockstate ) except KeyError: if block.namespace == "minecraft" and list(block.properties.keys()) == [ "block_data" ]: log.debug( f"Probably just a quirk block {block} from universal in {self._parent_version}." ) else: log.warning( f"Could not find translation information for {self._mode} {block} from universal in {self._parent_version}. If this is not a vanilla block ignore this message" ) return block, block_entity, False output, extra_output, extra_needed, cacheable = self._translate( block, input_spec, mapping, self._parent_version, force_blockstate, "from_universal", get_block_callback, block_entity, block_location, ) if cacheable: self._cache[cache_key][block] = output, extra_output, extra_needed if isinstance(output, Entity): output = copy.deepcopy(output) extra_output = copy.deepcopy(extra_output) return output, extra_output, extra_needed
def to_universal( self, block: "Block", block_entity: "BlockEntity" = None, force_blockstate: bool = False, block_location: BlockCoordinates = (0, 0, 0), get_block_callback: Callable[ [Tuple[int, int, int]], Tuple[Block, Optional[BlockEntity]] ] = None, ) -> Tuple[Block, Optional[BlockEntity], bool]: """ Translate the given Block object and optional BlockEntity from the parent Version's format to the Universal format. :param block: The block to translate :param block_entity: An optional block entity related to the block input :param force_blockstate: True to get the blockstate format. False to get the native format (these are sometimes the same) :param block_location: The location of the block in the world :param get_block_callback: A callable with relative coordinates that returns a Block and optional BlockEntity :return: A Block, optional BlockEntity and a bool. The bool specifies if block_location and get_block_callback are required to fully define the output data. """ assert isinstance(block, Block), "block must be a Block instance" cache_key = ("to_universal", force_blockstate) if block_entity is None: if block in self._cache[cache_key]: output, extra_output, extra_needed = self._cache[cache_key][block] extra_output = copy.deepcopy(extra_output) return output, extra_output, extra_needed else: assert isinstance( block_entity, BlockEntity ), "extra_input must be None or a BlockEntity" block_entity = copy.deepcopy(block_entity) try: input_spec = self.get_specification( block.namespace, block.base_name, force_blockstate ) mapping = self.get_mapping_to_universal( block.namespace, block.base_name, force_blockstate ) except KeyError: log.warning( f"Could not find translation information for {self._mode} {block} to universal in {self._parent_version}. If this is not a vanilla block ignore this message" ) return block, block_entity, False output, extra_output, extra_needed, cacheable = self._translate( block, input_spec, mapping, self._universal_format, True, "to universal", get_block_callback, block_entity, block_location, ) if cacheable: self._cache[cache_key][block] = output, extra_output, extra_needed return output, copy.deepcopy(extra_output), extra_needed
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