Beispiel #1
0
    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
Beispiel #2
0
    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
Beispiel #3
0
 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
Beispiel #5
0
 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)
Beispiel #6
0
    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
Beispiel #7
0
    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
Beispiel #8
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