示例#1
0
    def decode(self, cx: int, cz: int,
               data: Dict[bytes, bytes]) -> Tuple[Chunk, AnyNDArray]:
        # chunk_key_base = struct.pack("<ii", cx, cz)

        chunk = Chunk(cx, cz)
        chunk_palette = numpy.empty(0, dtype=object)

        if self.features["terrain"].startswith(
                "2f"):  # ["2farray", "2f1palette", "2fnpalette"]
            subchunks = [
                data.get(b"\x2F" + bytes([i]), None) for i in range(16)
            ]
            chunk.blocks, chunk_palette = self._load_subchunks(subchunks)
        elif self.features["terrain"] == "30array":
            chunk_data = data.get(b"\x30", None)
            if chunk_data is not None:
                block_ids = numpy.frombuffer(chunk_data[:2**15],
                                             dtype=numpy.uint8).astype(
                                                 numpy.uint16)
                block_data = from_nibble_array(
                    numpy.frombuffer(chunk_data[2**15:2**15 + 2**14],
                                     dtype=numpy.uint8))

                # there is other data here but we are going to skip over it
                combined_palette, block_array = fast_unique(
                    numpy.transpose(
                        ((block_ids << 4) + block_data).reshape(16, 16, 128),
                        (0, 2, 1)))
                chunk.blocks = {
                    i: block_array[:, i * 16:(i + 1) * 16, :]
                    for i in range(8)
                }
                palette: AnyNDArray = numpy.array(
                    [combined_palette >> 4, combined_palette & 15]).T
                chunk_palette = numpy.empty(len(palette), dtype=object)
                for i, b in enumerate(palette):
                    chunk_palette[i] = ((None, tuple(b)), )

        else:
            raise Exception

        if self.features["finalised_state"] == "int0-2":
            if b"\x36" in data:
                val = struct.unpack("<i", data[b"\x36"])[0]
            else:
                val = 2
            chunk.status = val

        if self.features["data_2d"] in [
                "height512|biome256",
                "unused_height512|biome256",
        ]:
            d2d = data.get(b"\x2D", b"\x00" * 768)
            height, biome = d2d[:512], d2d[512:]
            if self.features["data_2d"] == "height512|biome256":
                pass  # TODO: put this data somewhere
            chunk.biomes = numpy.frombuffer(biome,
                                            dtype="uint8").reshape(16, 16)

        # TODO: impliment key support
        # \x2D  heightmap and biomes
        # \x31  block entity
        # \x32  entity
        # \x33  ticks
        # \x34  block extra data
        # \x35  biome state
        # \x39  7 ints and an end (03)? Honestly don't know what this is
        # \x3A  fire tick?

        # \x2E  2d legacy
        # \x30  legacy terrain

        # unpack block entities and entities
        if self.features["block_entities"] == "31list":
            block_entities = self._unpack_nbt_list(data.get(b"\x31", b""))
            chunk.block_entities = self._decode_block_entities(block_entities)

        if self.features["entities"] == "32list" and amulet.entity_support:
            entities = self._unpack_nbt_list(data.get(b"\x32", b""))
            chunk.entities = self._decode_entities(entities)

        return chunk, chunk_palette
示例#2
0
    def _translate(
        chunk: Chunk,
        get_chunk_callback: Optional[GetChunkCallback],
        translate_block: TranslateBlockCallback,
        translate_entity: TranslateEntityCallback,
        full_translate: bool,
    ):
        if full_translate:
            todo = []
            output_block_entities = []
            output_entities = []
            finished = BlockManager()
            palette_mappings = {}

            # translate each block without using the callback
            for i, input_block in enumerate(chunk.block_palette):
                input_block: BlockType
                (
                    output_block,
                    output_block_entity,
                    output_entity,
                    extra,
                ) = translate_block(input_block, None, (0, 0, 0))
                if extra and get_chunk_callback:
                    todo.append(i)
                elif output_block is not None:
                    palette_mappings[i] = finished.get_add_block(output_block)
                    if output_block_entity is not None:
                        for cy in chunk.blocks.sub_chunks:
                            for x, y, z in zip(
                                *numpy.where(chunk.blocks.get_sub_chunk(cy) == i)
                            ):
                                output_block_entities.append(
                                    output_block_entity.new_at_location(
                                        x + chunk.cx * 16,
                                        y + cy * 16,
                                        z + chunk.cz * 16,
                                    )
                                )
                else:
                    # TODO: this should only happen if the object is an entity, set the block to air
                    pass

                if output_entity and entity_support:
                    for cy in chunk.blocks.sub_chunks:
                        for x, y, z in zip(
                            *numpy.where(chunk.blocks.get_sub_chunk(cy) == i)
                        ):
                            x += chunk.cx * 16
                            y += cy * 16
                            z += chunk.cz * 16
                            for entity in output_entity:
                                e = copy.deepcopy(entity)
                                e.location += (x, y, z)
                                output_entities.append(e)

            # re-translate the blocks that require extra information
            block_mappings = {}
            for index in todo:
                for cy in chunk.blocks.sub_chunks:
                    for x, y, z in zip(
                        *numpy.where(chunk.blocks.get_sub_chunk(cy) == index)
                    ):
                        y += cy * 16

                        def get_block_at(
                            pos: Tuple[int, int, int]
                        ) -> Tuple[Block, Optional[BlockEntity]]:
                            """Get a block at a location relative to the current block"""
                            nonlocal x, y, z, chunk, cy

                            # calculate position relative to chunk base
                            dx, dy, dz = pos
                            dx += x
                            dy += y
                            dz += z

                            abs_x = dx + chunk.cx * 16
                            abs_y = dy
                            abs_z = dz + chunk.cz * 16

                            # calculate relative chunk position
                            cx = dx // 16
                            cz = dz // 16
                            if cx == 0 and cz == 0:
                                # if it is the current chunk
                                block = chunk.block_palette[chunk.blocks[dx, dy, dz]]
                                return (
                                    block,
                                    chunk.block_entities.get((abs_x, abs_y, abs_z)),
                                )

                            # if it is in a different chunk
                            local_chunk = get_chunk_callback(cx, cz)
                            block = local_chunk.block_palette[
                                local_chunk.blocks[dx % 16, dy, dz % 16]
                            ]
                            return (
                                block,
                                local_chunk.block_entities.get((abs_x, abs_y, abs_z)),
                            )

                        input_block = chunk.block_palette[chunk.blocks[x, y, z]]
                        (
                            output_block,
                            output_block_entity,
                            output_entity,
                            _,
                        ) = translate_block(
                            input_block,
                            get_block_at,
                            (x + chunk.cx * 16, y, z + chunk.cz * 16),
                        )
                        if output_block is not None:
                            block_mappings[(x, y, z)] = finished.get_add_block(
                                output_block
                            )
                            if output_block_entity is not None:
                                output_block_entities.append(
                                    output_block_entity.new_at_location(
                                        x + chunk.cx * 16, y, z + chunk.cz * 16
                                    )
                                )
                        else:
                            # TODO: set the block to air
                            pass

                        if output_entity and entity_support:
                            for entity in output_entity:
                                e = copy.deepcopy(entity)
                                e.location += (x, y, z)
                                output_entities.append(e)

            if entity_support:
                for entity in chunk.entities:
                    output_block, output_block_entity, output_entity = translate_entity(
                        entity
                    )
                    if output_block is not None:
                        block_location = (
                            int(math.floor(entity.x)),
                            int(math.floor(entity.y)),
                            int(math.floor(entity.z)),
                        )
                        block_mappings[block_location] = output_block
                        if output_block_entity:
                            output_block_entities.append(
                                output_block_entity.new_at_location(*block_location)
                            )
                    if output_entity:
                        for e in output_entity:
                            e.location = entity.location
                            output_entities.append(e)

            for cy in chunk.blocks.sub_chunks:
                old_blocks = chunk.blocks.get_sub_chunk(cy)
                new_blocks = numpy.zeros(old_blocks.shape, dtype=old_blocks.dtype)
                for old, new in palette_mappings.items():
                    new_blocks[old_blocks == old] = new
                chunk.blocks.add_sub_chunk(cy, new_blocks)
            for (x, y, z), new in block_mappings.items():
                chunk.blocks[x, y, z] = new
            chunk.block_entities = output_block_entities
            chunk.entities = output_entities
            chunk._block_palette = finished
    def decode(self, cx: int, cz: int, data: Dict[bytes, bytes],
               bounds: Tuple[int, int]) -> Tuple[Chunk, AnyNDArray]:
        """
        Create an amulet.api.chunk.Chunk object from raw data given by the format
        :param cx: chunk x coordinate
        :param cz: chunk z coordinate
        :param data: Raw chunk data provided by the format.
        :param bounds: The minimum and maximum height of the chunk.
        :return: Chunk object in version-specific format, along with the block_palette for that chunk.
        """
        chunk = Chunk(cx, cz)
        chunk_palette = numpy.empty(0, dtype=object)
        chunk.misc = {"bedrock_chunk_data": data}

        data.pop(b"v", None)
        data.pop(b",", None)

        if self._features["terrain"].startswith(
                "2f"):  # ["2farray", "2f1palette", "2fnpalette"]
            subchunks = {}
            for key in data.copy().keys():
                if len(key) == 2 and key[0:1] == b"\x2F":
                    cy = struct.unpack("b", key[1:2])[0]
                    subchunks[self._chunk_key_to_sub_chunk(
                        cy, bounds[0] >> 4)] = data.pop(key)
            chunk.blocks, chunk_palette = self._load_subchunks(subchunks)
        elif self._features["terrain"] == "30array":
            chunk_data = data.pop(b"\x30", None)
            if chunk_data is not None:
                block_ids = numpy.frombuffer(chunk_data[:2**15],
                                             dtype=numpy.uint8).astype(
                                                 numpy.uint16)
                block_data = from_nibble_array(
                    numpy.frombuffer(chunk_data[2**15:2**15 + 2**14],
                                     dtype=numpy.uint8))

                # there is other data here but we are going to skip over it
                combined_palette, block_array = fast_unique(
                    numpy.transpose(
                        ((block_ids << 4) + block_data).reshape(16, 16, 128),
                        (0, 2, 1)))
                chunk.blocks = {
                    i: block_array[:, i * 16:(i + 1) * 16, :]
                    for i in range(8)
                }
                palette: AnyNDArray = numpy.array(
                    [combined_palette >> 4, combined_palette & 15]).T
                chunk_palette = numpy.empty(len(palette), dtype=object)
                for i, b in enumerate(palette):
                    chunk_palette[i] = ((None, tuple(b)), )

        else:
            raise Exception

        if self._features["finalised_state"] == "int0-2":
            state = data.pop(b"\x36", None)
            val = 2
            if isinstance(state, bytes):
                if len(state) == 1:
                    # old versions of the game store this as a byte
                    val = struct.unpack("b", state)[0]
                elif len(state) == 4:
                    # newer versions store it as an int
                    val = struct.unpack("<i", state)[0]
            chunk.status = val

        if b"+" in data:
            height, biome = self._decode_height_3d_biomes(
                data[b"+"], bounds[0] >> 4)
            chunk.misc["height"] = height
            chunk.biomes = biome
        elif b"\x2D" in data:
            d2d = data[b"\x2D"]
            height, biome = (
                numpy.frombuffer(d2d[:512], "<i2").reshape((16, 16)),
                d2d[512:],
            )
            chunk.misc["height"] = height
            chunk.biomes = numpy.frombuffer(biome,
                                            dtype="uint8").reshape(16, 16).T

        # TODO: implement key support
        # \x2D  heightmap and biomes
        # \x31  block entity
        # \x32  entity
        # \x33  ticks
        # \x34  block extra data
        # \x35  biome state
        # \x39  7 ints and an end (03)? Honestly don't know what this is
        # \x3A  fire tick?

        # \x2E  2d legacy
        # \x30  legacy terrain

        # unpack block entities and entities
        if self._features["block_entities"] == "31list":
            block_entities = self._unpack_nbt_list(data.pop(b"\x31", b""))
            chunk.block_entities = self._decode_block_entity_list(
                block_entities)

        if self._features["entities"] == "32list" and amulet.entity_support:
            entities = self._unpack_nbt_list(data.pop(b"\x32", b""))
            chunk.entities = self._decode_entity_list(entities)

        return chunk, chunk_palette
示例#4
0
    def decode(self, cx: int, cz: int,
               data: amulet_nbt.NBTFile) -> Tuple["Chunk", AnyNDArray]:
        """
        Create an amulet.api.chunk.Chunk object from raw data given by the format.
        :param cx: chunk x coordinate
        :param cz: chunk z coordinate
        :param data: amulet_nbt.NBTFile
        :return: Chunk object in version-specific format, along with the block_palette for that chunk.
        """
        misc = {}
        chunk = Chunk(cx, cz)

        if self.features["last_update"] == "long":
            misc["last_update"] = (data["Level"].get(
                "LastUpdate", amulet_nbt.TAG_Long(0)).value)

        if self.features["status"] in ["j13", "j14"]:
            chunk.status = data["Level"]["Status"].value
        else:
            status = "empty"
            if (self.features["terrain_populated"] == "byte"
                    and data["Level"].get("TerrainPopulated",
                                          amulet_nbt.TAG_Byte()).value):
                status = "decorated"
            if (self.features["light_populated"] == "byte"
                    and data["Level"].get("LightPopulated",
                                          amulet_nbt.TAG_Byte()).value):
                status = "postprocessed"

            chunk.status = status

        if self.features["V"] == "byte":
            misc["V"] = data["Level"]["V"].value

        if self.features["inhabited_time"] == "long":
            misc["inhabited_time"] = (data["Level"].get(
                "InhabitedTime", amulet_nbt.TAG_Long(0)).value)

        if self.features["biomes"] is not None:
            biomes = data["Level"].get("Biomes",
                                       amulet_nbt.TAG_Int_Array()).value
            if self.features["biomes"] == "256BA":
                biomes = biomes.astype(numpy.uint8)
            elif self.features["biomes"] in ["256IA", "1024IA"]:
                biomes = biomes.astype(numpy.uint32)

            chunk.biomes = biomes

        if self.features["height_map"] == "256IA":
            misc["height_map256IA"] = data["Level"]["HeightMap"].value
        elif self.features["height_map"] in [
                "C|36LA|V1",
                "C|36LA|V2",
                "C|36LA|V3",
                "C|36LA|V4",
        ]:
            if "Heightmaps" in data["Level"]:
                misc["height_mapC|36LA"] = data["Level"]["Heightmaps"]

        if "Sections" in data["Level"]:
            if self.features["blocks"] in [
                    "Sections|(Blocks,Data,Add)",
                    "Sections|(BlockStates,Palette)",
            ]:
                chunk.blocks, palette = self._decode_blocks(
                    data["Level"]["Sections"])
            else:
                raise Exception(
                    f'Unsupported block format {self.features["blocks"]}')

            if self.features["block_light"] == "Sections|2048BA":
                misc["block_light"] = {
                    section["Y"].value: section["BlockLight"]
                    for section in data["Level"]["Sections"]
                    if "BlockLight" in section
                }

            if self.features["sky_light"] == "Sections|2048BA":
                misc["sky_light"] = {
                    section["Y"].value: section["SkyLight"]
                    for section in data["Level"]["Sections"]
                    if "SkyLight" in section
                }
        else:
            palette = numpy.array(
                [Block(namespace="minecraft", base_name="air")])

        if self.features["entities"] == "list":
            if amulet.entity_support:
                chunk.entities = self._decode_entities(data["Level"].get(
                    "Entities", amulet_nbt.TAG_List()))
            else:
                misc["java_entities_temp"] = self._decode_entities(
                    data["Level"].get("Entities", amulet_nbt.TAG_List()))

        if self.features["block_entities"] == "list":
            chunk.block_entities = self._decode_block_entities(
                data["Level"].get("TileEntities", amulet_nbt.TAG_List()))

        if self.features["tile_ticks"] == "list":
            misc["tile_ticks"] = data["Level"].get("TileTicks",
                                                   amulet_nbt.TAG_List())

        if self.features["liquid_ticks"] == "list":
            if "LiquidTicks" in data["Level"]:
                misc["liquid_ticks"] = data["Level"]["LiquidTicks"]

        if self.features["liquids_to_be_ticked"] == "16list|list":
            if "LiquidsToBeTicked" in data["Level"]:
                misc["liquids_to_be_ticked"] = data["Level"][
                    "LiquidsToBeTicked"]

        if self.features["to_be_ticked"] == "16list|list":
            if "ToBeTicked" in data["Level"]:
                misc["to_be_ticked"] = data["Level"]["ToBeTicked"]

        if self.features["post_processing"] == "16list|list":
            if "PostProcessing" in data["Level"]:
                misc["post_processing"] = data["Level"]["PostProcessing"]

        if self.features["structures"] == "compound":
            if "Structures" in data["Level"]:
                misc["structures"] = data["Level"]["Structures"]

        chunk.misc = misc

        return chunk, palette
示例#5
0
 def _decode_block_entities(self, chunk: Chunk, compound: TAG_Compound):
     chunk.block_entities = self._decode_block_entity_list(
         self.get_obj(compound, self.BlockEntities, TAG_List)
     )