Skip to content

Commit

Permalink
Merge pull request #134 from Amulet-Team/impl-1-17
Browse files Browse the repository at this point in the history
Added support for 1.17 format worlds with optional variable height
  • Loading branch information
gentlegiantJGC committed Jun 7, 2021
2 parents 26be4ff + f53c5ea commit 7eba15e
Show file tree
Hide file tree
Showing 64 changed files with 1,185 additions and 399 deletions.
43 changes: 30 additions & 13 deletions amulet/api/level/base_level/base_level.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import traceback
import numpy
import itertools
import warnings

from amulet import log
from amulet.api.block import Block, UniversalAirBlock
Expand Down Expand Up @@ -108,7 +109,23 @@ def biome_palette(self) -> BiomeManager:
@property
def selection_bounds(self) -> SelectionGroup:
"""The selection(s) that all chunk data must fit within. Usually +/-30M for worlds. The selection for structures."""
return self.level_wrapper.selection
warnings.warn(
"BaseLevel.selection_bounds is depreciated and will be removed in the future. Please use BaseLevel.bounds(dimension) instead",
DeprecationWarning,
)
return self.bounds(self.dimensions[0])

def bounds(self, dimension: Dimension) -> SelectionGroup:
"""
The selection(s) that all chunk data must fit within.
This specifies the volume that can be built in.
Worlds will have a single cuboid volume.
Structures may have one or more cuboid volumes.
:param dimension: The dimension to get the bounds of.
:return: The build volume for the dimension.
"""
return self.level_wrapper.bounds(dimension)

@property
def dimensions(self) -> Tuple[Dimension, ...]:
Expand Down Expand Up @@ -143,14 +160,14 @@ def _chunk_box(self, cx: int, cz: int, sub_chunk_size: Optional[int] = None):
return SelectionBox.create_chunk_box(cx, cz, sub_chunk_size)

def _sanitise_selection(
self, selection: Union[SelectionGroup, SelectionBox, None]
self, selection: Union[SelectionGroup, SelectionBox, None], dimension: Dimension
) -> SelectionGroup:
if isinstance(selection, SelectionBox):
return SelectionGroup(selection)
elif isinstance(selection, SelectionGroup):
return selection
elif selection is None:
return self.selection_bounds
return self.bounds(dimension)
else:
raise ValueError(
f"Expected SelectionBox, SelectionGroup or None. Got {selection}"
Expand All @@ -168,10 +185,10 @@ def get_coord_box(
If not given a selection will use the bounds of the object.
:param dimension: The dimension to take effect in.
:param selection: SelectionGroup or SelectionBox into the level. If None will use :meth:`selection_bounds`
:param selection: SelectionGroup or SelectionBox into the level. If None will use :meth:`bounds` for the dimension.
:param yield_missing_chunks: If a chunk does not exist an empty one will be created (defaults to false). Use this with care.
"""
selection = self._sanitise_selection(selection)
selection = self._sanitise_selection(selection, dimension)
if yield_missing_chunks or selection.footprint_area < 1_000_000:
if yield_missing_chunks:
for coord, box in selection.chunk_boxes(self.sub_chunk_size):
Expand Down Expand Up @@ -206,7 +223,7 @@ def get_chunk_boxes(
If not given a selection will use the bounds of the object.
:param dimension: The dimension to take effect in.
:param selection: SelectionGroup or SelectionBox into the level. If None will use :meth:`selection_bounds`
:param selection: SelectionGroup or SelectionBox into the level. If None will use :meth:`bounds` for the dimension.
:param create_missing_chunks: If a chunk does not exist an empty one will be created (defaults to false). Use this with care.
"""
for (cx, cz), box in self.get_coord_box(
Expand All @@ -232,7 +249,7 @@ def get_chunk_slice_box(
Given a selection will yield :class:`Chunk`, slices, :class:`SelectionBox` for the contents of the selection.
:param dimension: The dimension to take effect in.
:param selection: SelectionGroup or SelectionBox into the level. If None will use :meth:`selection_bounds`
:param selection: SelectionGroup or SelectionBox into the level. If None will use :meth:`bounds` for the dimension.
:param create_missing_chunks: If a chunk does not exist an empty one will be created (defaults to false)
>>> for chunk, slices, box in level.get_chunk_slice_box(selection):
Expand Down Expand Up @@ -270,8 +287,8 @@ def get_moved_coord_slice_box(
so the slices need to be split up into parts that intersect a chunk in the source and destination.
:param dimension: The dimension to iterate over.
:param destination_origin: The location where the minimum point of self.selection_bounds will end up
:param selection: An optional selection. The overlap of this and self.selection_bounds will be used
:param destination_origin: The location where the minimum point of the selection will end up
:param selection: An optional selection. The overlap of this and the dimensions bounds will be used
:param destination_sub_chunk_shape: the chunk shape of the destination object (defaults to self.sub_chunk_size)
:param yield_missing_chunks: Generate empty chunks if the chunk does not exist.
:return:
Expand All @@ -280,12 +297,12 @@ def get_moved_coord_slice_box(
destination_sub_chunk_shape = self.sub_chunk_size

if selection is None:
selection = self.selection_bounds
selection = self.bounds(dimension)
else:
selection = self.selection_bounds.intersection(selection)
selection = self.bounds(dimension).intersection(selection)
# the offset from self.selection to the destination location
offset = numpy.subtract(
destination_origin, self.selection_bounds.min, dtype=int
destination_origin, self.bounds(dimension).min, dtype=int
)
for (src_cx, src_cz), box in self.get_coord_box(
dimension, selection, yield_missing_chunks=yield_missing_chunks
Expand Down Expand Up @@ -694,7 +711,7 @@ def paste_iter(
src_selection,
self,
dst_dimension,
self.selection_bounds,
self.bounds(dst_dimension),
location,
scale,
rotation,
Expand Down
3 changes: 3 additions & 0 deletions amulet/api/level/immutable_structure/immutable_structure.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,9 @@ def __init__(
def selection_bounds(self) -> SelectionGroup:
return self._selection

def bounds(self, dimension: Dimension) -> SelectionGroup:
return self._selection

def undo(self):
pass

Expand Down
13 changes: 12 additions & 1 deletion amulet/api/level/immutable_structure/void_format_wrapper.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
from typing import Any, List, Dict, Tuple, Optional, TYPE_CHECKING, Iterable

from amulet.api.data_types import Dimension, PlatformType, ChunkCoordinates
from amulet.api.data_types import Dimension, PlatformType, ChunkCoordinates, AnyNDArray
from amulet.api.wrapper import FormatWrapper
from amulet.api.errors import ChunkDoesNotExist, PlayerDoesNotExist
from amulet.api.player import Player
from amulet.api.chunk import Chunk
from amulet.api import wrapper as api_wrapper

if TYPE_CHECKING:
from amulet.api.wrapper import Interface
Expand Down Expand Up @@ -44,6 +46,15 @@ def register_dimension(self, dimension_identifier: Any):
def _get_interface(self, raw_chunk_data: Optional[Any] = None) -> "Interface":
raise Exception("If this is called something is wrong")

def _encode(
self,
interface: api_wrapper.Interface,
chunk: Chunk,
dimension: Dimension,
chunk_palette: AnyNDArray,
) -> Any:
raise Exception("If this is called something is wrong")

def _create(self, overwrite: bool, **kwargs):
pass

Expand Down
1 change: 1 addition & 0 deletions amulet/api/wrapper/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
FormatWrapper,
DefaultPlatform,
DefaultVersion,
DefaultSelection,
)
from amulet.api.wrapper.chunk.translator import Translator
from amulet.api.wrapper.chunk.interface import Interface, EntityIDType, EntityCoordType
Expand Down
42 changes: 11 additions & 31 deletions amulet/api/wrapper/chunk/interface.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from __future__ import annotations

import numpy
from abc import ABC, abstractmethod
from typing import Tuple, Any, Union, TYPE_CHECKING, Optional, overload, Type
from enum import Enum

Expand Down Expand Up @@ -38,20 +38,10 @@ class EntityCoordType(Enum):
}


class Interface:
def decode(self, cx: int, cz: int, data: Any) -> Tuple["Chunk", AnyNDArray]:
"""
Create an amulet.api.chunk.Chunk object from raw data given by the format
:param cx: chunk x coordinate
:type cx: int
:param cz: chunk z coordinate
:type cz: int
:param data: Raw chunk data provided by the format.
:type data: Any
:return: Chunk object in version-specific format, along with the block_palette for that chunk.
:rtype: Chunk
"""
raise NotImplementedError()
class Interface(ABC):
@abstractmethod
def decode(self, *args, **kwargs) -> Tuple["Chunk", AnyNDArray]:
raise NotImplementedError

def _decode_entity(
self,
Expand Down Expand Up @@ -186,24 +176,12 @@ def _decode_base_entity(

return namespace, base_name, x, y, z, nbt

def encode(
self,
chunk: "Chunk",
palette: AnyNDArray,
max_world_version: Tuple[str, Union[int, Tuple[int, int, int]]],
) -> Any:
@abstractmethod
def encode(self, *args, **kwargs) -> Any:
"""
Take a version-specific chunk and encode it to raw data for the format to store.
:param chunk: The already translated version-specfic chunk to encode.
:type chunk: Chunk
:param palette: The block_palette the ids in the chunk correspond to.
:type palette: numpy.ndarray[Block]
:param max_world_version: The key to use to find the encoder.
:type max_world_version: Tuple[str, Union[int, Tuple[int, int, int]]]
:return: Raw data to be stored by the Format.
:rtype: Any
"""
raise NotImplementedError()
raise NotImplementedError

def _encode_entity(
self, entity: Entity, id_type: EntityIDType, coord_type: EntityCoordType
Expand Down Expand Up @@ -358,6 +336,7 @@ def set_obj(
obj[key] = default
return obj[key]

@abstractmethod
def get_translator(
self,
max_world_version: Tuple[str, Union[int, Tuple[int, int, int]]],
Expand All @@ -375,6 +354,7 @@ def get_translator(
raise NotImplementedError

@staticmethod
@abstractmethod
def is_valid(key: Tuple) -> bool:
"""
Returns whether this Interface is able to interface with the chunk type with a given identifier key,
Expand All @@ -383,4 +363,4 @@ def is_valid(key: Tuple) -> bool:
:param key: The key who's decodability needs to be checked.
:return: True if this interface can interface with the chunk version associated with the key, False otherwise.
"""
raise NotImplementedError()
raise NotImplementedError
4 changes: 2 additions & 2 deletions amulet/api/wrapper/chunk/translator.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ def _translator_key(
:return: The tuple version key for PyMCTranslate
"""
raise NotImplementedError()
raise NotImplementedError

@staticmethod
def is_valid(key: Tuple) -> bool:
Expand All @@ -55,7 +55,7 @@ def is_valid(key: Tuple) -> bool:
:param key: The key who's decodability needs to be checked.
:return: True if this translator is able to translate the chunk type associated with the key, False otherwise.
"""
raise NotImplementedError()
raise NotImplementedError

@staticmethod
def _translate(
Expand Down

0 comments on commit 7eba15e

Please sign in to comment.