amulet-core 1.9.19__py3-none-any.whl → 1.9.20__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of amulet-core might be problematic. Click here for more details.
- amulet/__init__.py +27 -27
- amulet/__pyinstaller/__init__.py +2 -2
- amulet/__pyinstaller/hook-amulet.py +4 -4
- amulet/_version.py +21 -21
- amulet/api/__init__.py +2 -2
- amulet/api/abstract_base_entity.py +128 -128
- amulet/api/block.py +630 -630
- amulet/api/block_entity.py +71 -71
- amulet/api/cache.py +107 -107
- amulet/api/chunk/__init__.py +6 -6
- amulet/api/chunk/biomes.py +207 -207
- amulet/api/chunk/block_entity_dict.py +175 -175
- amulet/api/chunk/blocks.py +46 -46
- amulet/api/chunk/chunk.py +389 -389
- amulet/api/chunk/entity_list.py +75 -75
- amulet/api/chunk/status.py +167 -167
- amulet/api/data_types/__init__.py +4 -4
- amulet/api/data_types/generic_types.py +4 -4
- amulet/api/data_types/operation_types.py +16 -16
- amulet/api/data_types/world_types.py +49 -49
- amulet/api/data_types/wrapper_types.py +71 -71
- amulet/api/entity.py +74 -74
- amulet/api/errors.py +119 -119
- amulet/api/history/__init__.py +36 -36
- amulet/api/history/base/__init__.py +3 -3
- amulet/api/history/base/base_history.py +26 -26
- amulet/api/history/base/history_manager.py +63 -63
- amulet/api/history/base/revision_manager.py +73 -73
- amulet/api/history/changeable.py +15 -15
- amulet/api/history/data_types.py +7 -7
- amulet/api/history/history_manager/__init__.py +3 -3
- amulet/api/history/history_manager/container.py +102 -102
- amulet/api/history/history_manager/database.py +279 -279
- amulet/api/history/history_manager/meta.py +93 -93
- amulet/api/history/history_manager/object.py +116 -116
- amulet/api/history/revision_manager/__init__.py +2 -2
- amulet/api/history/revision_manager/disk.py +33 -33
- amulet/api/history/revision_manager/ram.py +12 -12
- amulet/api/item.py +75 -75
- amulet/api/level/__init__.py +4 -4
- amulet/api/level/base_level/__init__.py +1 -1
- amulet/api/level/base_level/base_level.py +1035 -1026
- amulet/api/level/base_level/chunk_manager.py +227 -227
- amulet/api/level/base_level/clone.py +389 -389
- amulet/api/level/base_level/player_manager.py +101 -101
- amulet/api/level/immutable_structure/__init__.py +1 -1
- amulet/api/level/immutable_structure/immutable_structure.py +94 -94
- amulet/api/level/immutable_structure/void_format_wrapper.py +117 -117
- amulet/api/level/structure.py +22 -22
- amulet/api/level/world.py +19 -19
- amulet/api/partial_3d_array/__init__.py +2 -2
- amulet/api/partial_3d_array/base_partial_3d_array.py +263 -263
- amulet/api/partial_3d_array/bounded_partial_3d_array.py +528 -528
- amulet/api/partial_3d_array/data_types.py +15 -15
- amulet/api/partial_3d_array/unbounded_partial_3d_array.py +229 -229
- amulet/api/partial_3d_array/util.py +152 -152
- amulet/api/player.py +65 -65
- amulet/api/registry/__init__.py +2 -2
- amulet/api/registry/base_registry.py +34 -34
- amulet/api/registry/biome_manager.py +153 -153
- amulet/api/registry/block_manager.py +156 -156
- amulet/api/selection/__init__.py +2 -2
- amulet/api/selection/abstract_selection.py +315 -315
- amulet/api/selection/box.py +805 -805
- amulet/api/selection/group.py +488 -488
- amulet/api/structure.py +37 -37
- amulet/api/wrapper/__init__.py +8 -8
- amulet/api/wrapper/chunk/interface.py +441 -441
- amulet/api/wrapper/chunk/translator.py +567 -567
- amulet/api/wrapper/format_wrapper.py +772 -772
- amulet/api/wrapper/structure_format_wrapper.py +116 -116
- amulet/api/wrapper/world_format_wrapper.py +63 -63
- amulet/level/__init__.py +1 -1
- amulet/level/formats/anvil_forge_world.py +40 -40
- amulet/level/formats/anvil_world/__init__.py +3 -3
- amulet/level/formats/anvil_world/_sector_manager.py +291 -384
- amulet/level/formats/anvil_world/data_pack/__init__.py +2 -2
- amulet/level/formats/anvil_world/data_pack/data_pack.py +224 -224
- amulet/level/formats/anvil_world/data_pack/data_pack_manager.py +77 -77
- amulet/level/formats/anvil_world/dimension.py +177 -177
- amulet/level/formats/anvil_world/format.py +769 -769
- amulet/level/formats/anvil_world/region.py +384 -384
- amulet/level/formats/construction/__init__.py +3 -3
- amulet/level/formats/construction/format_wrapper.py +515 -515
- amulet/level/formats/construction/interface.py +134 -134
- amulet/level/formats/construction/section.py +60 -60
- amulet/level/formats/construction/util.py +165 -165
- amulet/level/formats/leveldb_world/__init__.py +3 -3
- amulet/level/formats/leveldb_world/chunk.py +33 -33
- amulet/level/formats/leveldb_world/dimension.py +385 -419
- amulet/level/formats/leveldb_world/format.py +659 -641
- amulet/level/formats/leveldb_world/interface/chunk/__init__.py +36 -36
- amulet/level/formats/leveldb_world/interface/chunk/base_leveldb_interface.py +836 -836
- amulet/level/formats/leveldb_world/interface/chunk/generate_interface.py +31 -31
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_0.py +30 -30
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_1.py +12 -12
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_10.py +12 -12
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_11.py +12 -12
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_12.py +12 -12
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_13.py +12 -12
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_14.py +12 -12
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_15.py +12 -12
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_16.py +12 -12
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_17.py +12 -12
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_18.py +12 -12
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_19.py +12 -12
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_2.py +12 -12
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_20.py +12 -12
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_21.py +12 -12
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_22.py +12 -12
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_23.py +10 -10
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_24.py +10 -10
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_25.py +24 -24
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_26.py +10 -10
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_27.py +10 -10
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_28.py +10 -10
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_29.py +33 -33
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_3.py +57 -57
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_30.py +10 -10
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_31.py +10 -10
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_32.py +10 -10
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_33.py +10 -10
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_34.py +10 -10
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_35.py +10 -10
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_36.py +10 -10
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_37.py +10 -10
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_38.py +10 -10
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_39.py +12 -12
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_4.py +12 -12
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_40.py +16 -16
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_5.py +12 -12
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_6.py +12 -12
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_7.py +12 -12
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_8.py +180 -180
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_9.py +18 -18
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_chunk_versions.py +79 -79
- amulet/level/formats/mcstructure/__init__.py +3 -3
- amulet/level/formats/mcstructure/chunk.py +50 -50
- amulet/level/formats/mcstructure/format_wrapper.py +408 -408
- amulet/level/formats/mcstructure/interface.py +175 -175
- amulet/level/formats/schematic/__init__.py +3 -3
- amulet/level/formats/schematic/chunk.py +55 -55
- amulet/level/formats/schematic/data_types.py +4 -4
- amulet/level/formats/schematic/format_wrapper.py +373 -373
- amulet/level/formats/schematic/interface.py +142 -142
- amulet/level/formats/sponge_schem/__init__.py +4 -4
- amulet/level/formats/sponge_schem/chunk.py +62 -62
- amulet/level/formats/sponge_schem/format_wrapper.py +463 -463
- amulet/level/formats/sponge_schem/interface.py +118 -118
- amulet/level/formats/sponge_schem/varint/__init__.py +1 -1
- amulet/level/formats/sponge_schem/varint/varint.py +87 -87
- amulet/level/interfaces/chunk/anvil/anvil_0.py +72 -72
- amulet/level/interfaces/chunk/anvil/anvil_1444.py +336 -336
- amulet/level/interfaces/chunk/anvil/anvil_1466.py +94 -94
- amulet/level/interfaces/chunk/anvil/anvil_1467.py +37 -37
- amulet/level/interfaces/chunk/anvil/anvil_1484.py +20 -20
- amulet/level/interfaces/chunk/anvil/anvil_1503.py +20 -20
- amulet/level/interfaces/chunk/anvil/anvil_1519.py +34 -34
- amulet/level/interfaces/chunk/anvil/anvil_1901.py +20 -20
- amulet/level/interfaces/chunk/anvil/anvil_1908.py +20 -20
- amulet/level/interfaces/chunk/anvil/anvil_1912.py +21 -21
- amulet/level/interfaces/chunk/anvil/anvil_1934.py +20 -20
- amulet/level/interfaces/chunk/anvil/anvil_2203.py +69 -69
- amulet/level/interfaces/chunk/anvil/anvil_2529.py +19 -19
- amulet/level/interfaces/chunk/anvil/anvil_2681.py +76 -76
- amulet/level/interfaces/chunk/anvil/anvil_2709.py +19 -19
- amulet/level/interfaces/chunk/anvil/anvil_2844.py +267 -267
- amulet/level/interfaces/chunk/anvil/anvil_3463.py +19 -19
- amulet/level/interfaces/chunk/anvil/anvil_na.py +607 -607
- amulet/level/interfaces/chunk/anvil/base_anvil_interface.py +326 -326
- amulet/level/load.py +59 -59
- amulet/level/loader.py +95 -95
- amulet/level/translators/chunk/bedrock/__init__.py +267 -267
- amulet/level/translators/chunk/bedrock/bedrock_nbt_blockstate_translator.py +46 -46
- amulet/level/translators/chunk/bedrock/bedrock_numerical_translator.py +39 -39
- amulet/level/translators/chunk/bedrock/bedrock_psudo_numerical_translator.py +37 -37
- amulet/level/translators/chunk/java/java_1_18_translator.py +40 -40
- amulet/level/translators/chunk/java/java_blockstate_translator.py +94 -94
- amulet/level/translators/chunk/java/java_numerical_translator.py +62 -62
- amulet/libs/leveldb/__init__.py +7 -7
- amulet/operations/__init__.py +5 -5
- amulet/operations/clone.py +18 -18
- amulet/operations/delete_chunk.py +32 -32
- amulet/operations/fill.py +30 -30
- amulet/operations/paste.py +65 -65
- amulet/operations/replace.py +58 -58
- amulet/utils/__init__.py +14 -14
- amulet/utils/format_utils.py +41 -41
- amulet/utils/generator.py +15 -15
- amulet/utils/matrix.py +243 -243
- amulet/utils/numpy_helpers.py +46 -46
- amulet/utils/world_utils.py +349 -349
- {amulet_core-1.9.19.dist-info → amulet_core-1.9.20.dist-info}/METADATA +97 -97
- amulet_core-1.9.20.dist-info/RECORD +208 -0
- amulet_core-1.9.19.dist-info/RECORD +0 -208
- {amulet_core-1.9.19.dist-info → amulet_core-1.9.20.dist-info}/WHEEL +0 -0
- {amulet_core-1.9.19.dist-info → amulet_core-1.9.20.dist-info}/entry_points.txt +0 -0
- {amulet_core-1.9.19.dist-info → amulet_core-1.9.20.dist-info}/top_level.txt +0 -0
|
@@ -1,326 +1,326 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
from abc import abstractmethod, ABC
|
|
3
|
-
|
|
4
|
-
from typing import (
|
|
5
|
-
List,
|
|
6
|
-
Tuple,
|
|
7
|
-
Iterable,
|
|
8
|
-
TYPE_CHECKING,
|
|
9
|
-
Any,
|
|
10
|
-
Dict,
|
|
11
|
-
Callable,
|
|
12
|
-
Sequence,
|
|
13
|
-
Union,
|
|
14
|
-
Type,
|
|
15
|
-
)
|
|
16
|
-
import numpy
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
from amulet_nbt import (
|
|
20
|
-
AbstractBaseTag,
|
|
21
|
-
IntTag,
|
|
22
|
-
ListTag,
|
|
23
|
-
CompoundTag,
|
|
24
|
-
NamedTag,
|
|
25
|
-
AnyNBT,
|
|
26
|
-
)
|
|
27
|
-
|
|
28
|
-
from amulet.api.chunk import Chunk, StatusFormats
|
|
29
|
-
from amulet.api.wrapper import Interface
|
|
30
|
-
from amulet.level import loader
|
|
31
|
-
from amulet.api.data_types import AnyNDArray, VersionIdentifierType
|
|
32
|
-
from amulet.api.wrapper import EntityIDType, EntityCoordType
|
|
33
|
-
|
|
34
|
-
if TYPE_CHECKING:
|
|
35
|
-
from amulet.api.wrapper import Translator
|
|
36
|
-
from amulet.api.block_entity import BlockEntity
|
|
37
|
-
from amulet.api.entity import Entity
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
ChunkDataType = Dict[str, NamedTag]
|
|
41
|
-
|
|
42
|
-
ChunkPathType = Tuple[
|
|
43
|
-
str, # The layer name
|
|
44
|
-
Sequence[Tuple[Union[str, int], Type[AbstractBaseTag]]],
|
|
45
|
-
Type[AbstractBaseTag],
|
|
46
|
-
]
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
class BaseDecoderEncoder(ABC):
|
|
50
|
-
def __init__(self):
|
|
51
|
-
self.__decoders = {}
|
|
52
|
-
self.__post_decoders = {}
|
|
53
|
-
self.__encoders = {}
|
|
54
|
-
self.__post_encoders = {}
|
|
55
|
-
|
|
56
|
-
def _register_decoder(self, decoder: Callable):
|
|
57
|
-
"""Register a function that does the decoding"""
|
|
58
|
-
self.__decoders[decoder] = None
|
|
59
|
-
|
|
60
|
-
def _register_post_decoder(self, post_decoder: Callable):
|
|
61
|
-
"""Register a function that runs after the decoding"""
|
|
62
|
-
self.__post_decoders[post_decoder] = None
|
|
63
|
-
|
|
64
|
-
def _unregister_decoder(self, decoder: Callable):
|
|
65
|
-
"""Unregister a function that does the decoding"""
|
|
66
|
-
del self.__decoders[decoder]
|
|
67
|
-
|
|
68
|
-
def _unregister_post_decoder(self, post_decoder: Callable):
|
|
69
|
-
"""Unregister a function that runs after the decoding"""
|
|
70
|
-
del self.__post_decoders[post_decoder]
|
|
71
|
-
|
|
72
|
-
def _do_decode(self, *args, **kwargs):
|
|
73
|
-
for decoder in self.__decoders:
|
|
74
|
-
decoder(*args, **kwargs)
|
|
75
|
-
for post_decoder in self.__post_decoders:
|
|
76
|
-
post_decoder(*args, **kwargs)
|
|
77
|
-
|
|
78
|
-
def _register_encoder(self, encoder: Callable):
|
|
79
|
-
"""Register a function that does the encoding"""
|
|
80
|
-
self.__encoders[encoder] = None
|
|
81
|
-
|
|
82
|
-
def _register_post_encoder(self, post_encoder: Callable):
|
|
83
|
-
"""Register a function that runs after the encoding"""
|
|
84
|
-
self.__post_encoders[post_encoder] = None
|
|
85
|
-
|
|
86
|
-
def _unregister_encoder(self, encoder: Callable):
|
|
87
|
-
"""Unregister a function that does the encoding"""
|
|
88
|
-
del self.__encoders[encoder]
|
|
89
|
-
|
|
90
|
-
def _unregister_post_encoder(self, post_encoder: Callable):
|
|
91
|
-
"""Unregister a function that runs after the encoding"""
|
|
92
|
-
del self.__post_encoders[post_encoder]
|
|
93
|
-
|
|
94
|
-
def _do_encode(self, *args, **kwargs):
|
|
95
|
-
for encoder in self.__encoders:
|
|
96
|
-
encoder(*args, **kwargs)
|
|
97
|
-
for post_encoder in self.__post_encoders:
|
|
98
|
-
post_encoder(*args, **kwargs)
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
class BaseAnvilInterface(Interface, BaseDecoderEncoder):
|
|
102
|
-
# The chunk object, the chunk data, the floor chunk coord, the chunk height (in sub-chunks)
|
|
103
|
-
DecoderType = Callable[[Chunk, ChunkDataType, int, int], None]
|
|
104
|
-
EncoderType = Callable[[Chunk, ChunkDataType, int, int], None]
|
|
105
|
-
_register_decoder: Callable[[DecoderType], None]
|
|
106
|
-
_register_post_decoder: Callable[[DecoderType], None]
|
|
107
|
-
_unregister_decoder: Callable[[DecoderType], None]
|
|
108
|
-
_unregister_post_decoder: Callable[[DecoderType], None]
|
|
109
|
-
_do_decode: DecoderType
|
|
110
|
-
_register_encoder: Callable[[EncoderType], None]
|
|
111
|
-
_register_post_encoder: Callable[[EncoderType], None]
|
|
112
|
-
_unregister_encoder: Callable[[EncoderType], None]
|
|
113
|
-
_unregister_post_encoder: Callable[[EncoderType], None]
|
|
114
|
-
_do_encode: EncoderType
|
|
115
|
-
|
|
116
|
-
def __init__(self):
|
|
117
|
-
BaseDecoderEncoder.__init__(self)
|
|
118
|
-
self._feature_options = {
|
|
119
|
-
"status": StatusFormats,
|
|
120
|
-
"height_map": [
|
|
121
|
-
"256IARequired", # A 256 element Int Array in HeightMap
|
|
122
|
-
"256IA", # A 256 element Int Array in HeightMap
|
|
123
|
-
"C|V1", # A Compound of Long Arrays with these keys "LIQUID", "SOLID", "LIGHT", "RAIN"
|
|
124
|
-
"C|V2", # A Compound of Long Arrays with these keys "WORLD_SURFACE_WG", "OCEAN_FLOOR_WG", "MOTION_BLOCKING", "MOTION_BLOCKING_NO_LEAVES", "OCEAN_FLOOR", "LIGHT_BLOCKING"
|
|
125
|
-
"C|V3", # A Compound of Long Arrays with these keys "WORLD_SURFACE_WG", "OCEAN_FLOOR_WG", "MOTION_BLOCKING", "MOTION_BLOCKING_NO_LEAVES", "OCEAN_FLOOR", "LIGHT_BLOCKING", "WORLD_SURFACE"
|
|
126
|
-
"C|V4", # A Compound of Long Arrays with these keys "WORLD_SURFACE_WG", "OCEAN_FLOOR_WG", "MOTION_BLOCKING", "MOTION_BLOCKING_NO_LEAVES", "OCEAN_FLOOR", "WORLD_SURFACE"
|
|
127
|
-
],
|
|
128
|
-
# 'carving_masks': ['C|?BA'],
|
|
129
|
-
"light_optional": ["false", "true"],
|
|
130
|
-
"block_entity_format": [EntityIDType.namespace_str_id],
|
|
131
|
-
"block_entity_coord_format": [EntityCoordType.xyz_int],
|
|
132
|
-
"entity_format": [EntityIDType.namespace_str_id],
|
|
133
|
-
"entity_coord_format": [EntityCoordType.Pos_list_double],
|
|
134
|
-
# 'lights': [],
|
|
135
|
-
}
|
|
136
|
-
self._features = {key: None for key in self._feature_options.keys()}
|
|
137
|
-
|
|
138
|
-
def _set_feature(self, feature: str, option: Any):
|
|
139
|
-
assert feature in self._feature_options, f"{feature} is not a valid feature."
|
|
140
|
-
assert (
|
|
141
|
-
option is None or option in self._feature_options[feature]
|
|
142
|
-
), f'Invalid option {option} for feature "{feature}"'
|
|
143
|
-
self._features[feature] = option
|
|
144
|
-
|
|
145
|
-
def is_valid(self, key: Tuple) -> bool:
|
|
146
|
-
return key[0] == "java" and self.minor_is_valid(key[1])
|
|
147
|
-
|
|
148
|
-
@staticmethod
|
|
149
|
-
@abstractmethod
|
|
150
|
-
def minor_is_valid(key: int):
|
|
151
|
-
raise NotImplementedError
|
|
152
|
-
|
|
153
|
-
def get_translator(
|
|
154
|
-
self,
|
|
155
|
-
max_world_version: VersionIdentifierType,
|
|
156
|
-
data: ChunkDataType = None,
|
|
157
|
-
) -> Tuple["Translator", int]:
|
|
158
|
-
if data is None:
|
|
159
|
-
key = max_world_version
|
|
160
|
-
version = max_world_version[1]
|
|
161
|
-
else:
|
|
162
|
-
data_version = (
|
|
163
|
-
data.get("region", NamedTag(CompoundTag()))
|
|
164
|
-
.compound.get("DataVersion", IntTag(-1))
|
|
165
|
-
.py_int
|
|
166
|
-
)
|
|
167
|
-
key, version = (("java", data_version), data_version)
|
|
168
|
-
|
|
169
|
-
return loader.Translators.get(key), version
|
|
170
|
-
|
|
171
|
-
def get_layer_obj(
|
|
172
|
-
self,
|
|
173
|
-
obj: ChunkDataType,
|
|
174
|
-
data: Tuple[
|
|
175
|
-
str,
|
|
176
|
-
Sequence[Tuple[Union[str, int], Type[AbstractBaseTag]]],
|
|
177
|
-
Union[None, AnyNBT, Type[AbstractBaseTag]],
|
|
178
|
-
],
|
|
179
|
-
*,
|
|
180
|
-
pop_last=False,
|
|
181
|
-
) -> Any:
|
|
182
|
-
"""
|
|
183
|
-
Get an object from a nested NBT structure layer
|
|
184
|
-
|
|
185
|
-
:param obj: The chunk data object
|
|
186
|
-
:param data: The data layer name, the nbt path and the default
|
|
187
|
-
:param pop_last: If true the last key will be popped
|
|
188
|
-
:return: The found data or the default
|
|
189
|
-
"""
|
|
190
|
-
layer_key, path, default = data
|
|
191
|
-
if layer_key in obj:
|
|
192
|
-
return self.get_nested_obj(
|
|
193
|
-
obj[layer_key].compound, path, default, pop_last=pop_last
|
|
194
|
-
)
|
|
195
|
-
elif default is None or isinstance(default, AbstractBaseTag):
|
|
196
|
-
return default
|
|
197
|
-
elif issubclass(default, AbstractBaseTag):
|
|
198
|
-
return default()
|
|
199
|
-
else:
|
|
200
|
-
raise TypeError("default must be None, an NBT instance or an NBT class.")
|
|
201
|
-
|
|
202
|
-
def set_layer_obj(
|
|
203
|
-
self,
|
|
204
|
-
obj: ChunkDataType,
|
|
205
|
-
data: Tuple[
|
|
206
|
-
str,
|
|
207
|
-
Sequence[Tuple[Union[str, int], Type[AbstractBaseTag]]],
|
|
208
|
-
Union[None, AnyNBT, Type[AbstractBaseTag]],
|
|
209
|
-
],
|
|
210
|
-
default_tag: AnyNBT = None,
|
|
211
|
-
*,
|
|
212
|
-
setdefault=False,
|
|
213
|
-
) -> AnyNBT:
|
|
214
|
-
"""
|
|
215
|
-
Setdefault on a ChunkDataType object
|
|
216
|
-
|
|
217
|
-
:param obj: The ChunkDataType object to use
|
|
218
|
-
:param data: The data to set
|
|
219
|
-
:param setdefault: If True will behave like setdefault. If False will replace existing data.
|
|
220
|
-
:return: The existing data found or the default that was set
|
|
221
|
-
"""
|
|
222
|
-
layer_key, path, default = data
|
|
223
|
-
default = default if default_tag is None else default_tag
|
|
224
|
-
if not path:
|
|
225
|
-
raise ValueError("was not given a path to set")
|
|
226
|
-
tag = obj.setdefault(layer_key, NamedTag()).compound
|
|
227
|
-
*path, (key, dtype) = path
|
|
228
|
-
if path:
|
|
229
|
-
key_path = next(zip(*path))
|
|
230
|
-
else:
|
|
231
|
-
key_path = ()
|
|
232
|
-
return self.set_obj(
|
|
233
|
-
tag, key, dtype, default, path=key_path, setdefault=setdefault
|
|
234
|
-
)
|
|
235
|
-
|
|
236
|
-
@abstractmethod
|
|
237
|
-
def decode(
|
|
238
|
-
self, cx: int, cz: int, data: ChunkDataType, bounds: Tuple[int, int]
|
|
239
|
-
) -> Tuple["Chunk", AnyNDArray]:
|
|
240
|
-
"""
|
|
241
|
-
Create an amulet.api.chunk.Chunk object from raw data.
|
|
242
|
-
:param cx: chunk x coordinate
|
|
243
|
-
:param cz: chunk z coordinate
|
|
244
|
-
:param data: The chunk data
|
|
245
|
-
:param bounds: The minimum and maximum bounds of the chunk. In 1.17 this is required to define where the biome array sits.
|
|
246
|
-
:return: Chunk object in version-specific format, along with the block_palette for that chunk.
|
|
247
|
-
"""
|
|
248
|
-
raise NotImplementedError
|
|
249
|
-
|
|
250
|
-
def _decode_entity_list(self, entities: ListTag) -> List["Entity"]:
|
|
251
|
-
entities_out = []
|
|
252
|
-
if entities.list_data_type == CompoundTag.tag_id:
|
|
253
|
-
for nbt in entities:
|
|
254
|
-
entity = self._decode_entity(
|
|
255
|
-
NamedTag(nbt),
|
|
256
|
-
self._features["entity_format"],
|
|
257
|
-
self._features["entity_coord_format"],
|
|
258
|
-
)
|
|
259
|
-
if entity is not None:
|
|
260
|
-
entities_out.append(entity)
|
|
261
|
-
|
|
262
|
-
return entities_out
|
|
263
|
-
|
|
264
|
-
def _decode_block_entity_list(self, block_entities: ListTag) -> List["BlockEntity"]:
|
|
265
|
-
entities_out = []
|
|
266
|
-
if block_entities.list_data_type == CompoundTag.tag_id:
|
|
267
|
-
for nbt in block_entities:
|
|
268
|
-
if not isinstance(nbt, CompoundTag):
|
|
269
|
-
continue
|
|
270
|
-
entity = self._decode_block_entity(
|
|
271
|
-
NamedTag(nbt),
|
|
272
|
-
self._features["block_entity_format"],
|
|
273
|
-
self._features["block_entity_coord_format"],
|
|
274
|
-
)
|
|
275
|
-
if entity is not None:
|
|
276
|
-
entities_out.append(entity)
|
|
277
|
-
|
|
278
|
-
return entities_out
|
|
279
|
-
|
|
280
|
-
@abstractmethod
|
|
281
|
-
def encode(
|
|
282
|
-
self,
|
|
283
|
-
chunk: "Chunk",
|
|
284
|
-
palette: AnyNDArray,
|
|
285
|
-
max_world_version: Tuple[str, int],
|
|
286
|
-
bounds: Tuple[int, int],
|
|
287
|
-
) -> ChunkDataType:
|
|
288
|
-
"""
|
|
289
|
-
Encode a version-specific chunk to raw data for the format to store.
|
|
290
|
-
|
|
291
|
-
:param chunk: The already translated version-specific chunk to encode.
|
|
292
|
-
:param palette: The block_palette the ids in the chunk correspond to.
|
|
293
|
-
:type palette: numpy.ndarray[Block]
|
|
294
|
-
:param max_world_version: The key to use to find the encoder.
|
|
295
|
-
:param bounds: The minimum and maximum bounds of the chunk. In 1.17 this is required to define where the biome array sits.
|
|
296
|
-
:return: Raw data to be stored by the Format.
|
|
297
|
-
"""
|
|
298
|
-
raise NotImplementedError
|
|
299
|
-
|
|
300
|
-
def _encode_entity_list(self, entities: Iterable["Entity"]) -> ListTag:
|
|
301
|
-
entities_out = []
|
|
302
|
-
for entity in entities:
|
|
303
|
-
nbt = self._encode_entity(
|
|
304
|
-
entity,
|
|
305
|
-
self._features["entity_format"],
|
|
306
|
-
self._features["entity_coord_format"],
|
|
307
|
-
)
|
|
308
|
-
if nbt is not None:
|
|
309
|
-
entities_out.append(nbt.compound)
|
|
310
|
-
|
|
311
|
-
return ListTag(entities_out)
|
|
312
|
-
|
|
313
|
-
def _encode_block_entity_list(
|
|
314
|
-
self, block_entities: Iterable["BlockEntity"]
|
|
315
|
-
) -> ListTag:
|
|
316
|
-
entities_out = []
|
|
317
|
-
for entity in block_entities:
|
|
318
|
-
nbt = self._encode_block_entity(
|
|
319
|
-
entity,
|
|
320
|
-
self._features["block_entity_format"],
|
|
321
|
-
self._features["block_entity_coord_format"],
|
|
322
|
-
)
|
|
323
|
-
if nbt is not None:
|
|
324
|
-
entities_out.append(nbt.compound)
|
|
325
|
-
|
|
326
|
-
return ListTag(entities_out)
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
from abc import abstractmethod, ABC
|
|
3
|
+
|
|
4
|
+
from typing import (
|
|
5
|
+
List,
|
|
6
|
+
Tuple,
|
|
7
|
+
Iterable,
|
|
8
|
+
TYPE_CHECKING,
|
|
9
|
+
Any,
|
|
10
|
+
Dict,
|
|
11
|
+
Callable,
|
|
12
|
+
Sequence,
|
|
13
|
+
Union,
|
|
14
|
+
Type,
|
|
15
|
+
)
|
|
16
|
+
import numpy
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
from amulet_nbt import (
|
|
20
|
+
AbstractBaseTag,
|
|
21
|
+
IntTag,
|
|
22
|
+
ListTag,
|
|
23
|
+
CompoundTag,
|
|
24
|
+
NamedTag,
|
|
25
|
+
AnyNBT,
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
from amulet.api.chunk import Chunk, StatusFormats
|
|
29
|
+
from amulet.api.wrapper import Interface
|
|
30
|
+
from amulet.level import loader
|
|
31
|
+
from amulet.api.data_types import AnyNDArray, VersionIdentifierType
|
|
32
|
+
from amulet.api.wrapper import EntityIDType, EntityCoordType
|
|
33
|
+
|
|
34
|
+
if TYPE_CHECKING:
|
|
35
|
+
from amulet.api.wrapper import Translator
|
|
36
|
+
from amulet.api.block_entity import BlockEntity
|
|
37
|
+
from amulet.api.entity import Entity
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
ChunkDataType = Dict[str, NamedTag]
|
|
41
|
+
|
|
42
|
+
ChunkPathType = Tuple[
|
|
43
|
+
str, # The layer name
|
|
44
|
+
Sequence[Tuple[Union[str, int], Type[AbstractBaseTag]]],
|
|
45
|
+
Type[AbstractBaseTag],
|
|
46
|
+
]
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class BaseDecoderEncoder(ABC):
|
|
50
|
+
def __init__(self):
|
|
51
|
+
self.__decoders = {}
|
|
52
|
+
self.__post_decoders = {}
|
|
53
|
+
self.__encoders = {}
|
|
54
|
+
self.__post_encoders = {}
|
|
55
|
+
|
|
56
|
+
def _register_decoder(self, decoder: Callable):
|
|
57
|
+
"""Register a function that does the decoding"""
|
|
58
|
+
self.__decoders[decoder] = None
|
|
59
|
+
|
|
60
|
+
def _register_post_decoder(self, post_decoder: Callable):
|
|
61
|
+
"""Register a function that runs after the decoding"""
|
|
62
|
+
self.__post_decoders[post_decoder] = None
|
|
63
|
+
|
|
64
|
+
def _unregister_decoder(self, decoder: Callable):
|
|
65
|
+
"""Unregister a function that does the decoding"""
|
|
66
|
+
del self.__decoders[decoder]
|
|
67
|
+
|
|
68
|
+
def _unregister_post_decoder(self, post_decoder: Callable):
|
|
69
|
+
"""Unregister a function that runs after the decoding"""
|
|
70
|
+
del self.__post_decoders[post_decoder]
|
|
71
|
+
|
|
72
|
+
def _do_decode(self, *args, **kwargs):
|
|
73
|
+
for decoder in self.__decoders:
|
|
74
|
+
decoder(*args, **kwargs)
|
|
75
|
+
for post_decoder in self.__post_decoders:
|
|
76
|
+
post_decoder(*args, **kwargs)
|
|
77
|
+
|
|
78
|
+
def _register_encoder(self, encoder: Callable):
|
|
79
|
+
"""Register a function that does the encoding"""
|
|
80
|
+
self.__encoders[encoder] = None
|
|
81
|
+
|
|
82
|
+
def _register_post_encoder(self, post_encoder: Callable):
|
|
83
|
+
"""Register a function that runs after the encoding"""
|
|
84
|
+
self.__post_encoders[post_encoder] = None
|
|
85
|
+
|
|
86
|
+
def _unregister_encoder(self, encoder: Callable):
|
|
87
|
+
"""Unregister a function that does the encoding"""
|
|
88
|
+
del self.__encoders[encoder]
|
|
89
|
+
|
|
90
|
+
def _unregister_post_encoder(self, post_encoder: Callable):
|
|
91
|
+
"""Unregister a function that runs after the encoding"""
|
|
92
|
+
del self.__post_encoders[post_encoder]
|
|
93
|
+
|
|
94
|
+
def _do_encode(self, *args, **kwargs):
|
|
95
|
+
for encoder in self.__encoders:
|
|
96
|
+
encoder(*args, **kwargs)
|
|
97
|
+
for post_encoder in self.__post_encoders:
|
|
98
|
+
post_encoder(*args, **kwargs)
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
class BaseAnvilInterface(Interface, BaseDecoderEncoder):
|
|
102
|
+
# The chunk object, the chunk data, the floor chunk coord, the chunk height (in sub-chunks)
|
|
103
|
+
DecoderType = Callable[[Chunk, ChunkDataType, int, int], None]
|
|
104
|
+
EncoderType = Callable[[Chunk, ChunkDataType, int, int], None]
|
|
105
|
+
_register_decoder: Callable[[DecoderType], None]
|
|
106
|
+
_register_post_decoder: Callable[[DecoderType], None]
|
|
107
|
+
_unregister_decoder: Callable[[DecoderType], None]
|
|
108
|
+
_unregister_post_decoder: Callable[[DecoderType], None]
|
|
109
|
+
_do_decode: DecoderType
|
|
110
|
+
_register_encoder: Callable[[EncoderType], None]
|
|
111
|
+
_register_post_encoder: Callable[[EncoderType], None]
|
|
112
|
+
_unregister_encoder: Callable[[EncoderType], None]
|
|
113
|
+
_unregister_post_encoder: Callable[[EncoderType], None]
|
|
114
|
+
_do_encode: EncoderType
|
|
115
|
+
|
|
116
|
+
def __init__(self):
|
|
117
|
+
BaseDecoderEncoder.__init__(self)
|
|
118
|
+
self._feature_options = {
|
|
119
|
+
"status": StatusFormats,
|
|
120
|
+
"height_map": [
|
|
121
|
+
"256IARequired", # A 256 element Int Array in HeightMap
|
|
122
|
+
"256IA", # A 256 element Int Array in HeightMap
|
|
123
|
+
"C|V1", # A Compound of Long Arrays with these keys "LIQUID", "SOLID", "LIGHT", "RAIN"
|
|
124
|
+
"C|V2", # A Compound of Long Arrays with these keys "WORLD_SURFACE_WG", "OCEAN_FLOOR_WG", "MOTION_BLOCKING", "MOTION_BLOCKING_NO_LEAVES", "OCEAN_FLOOR", "LIGHT_BLOCKING"
|
|
125
|
+
"C|V3", # A Compound of Long Arrays with these keys "WORLD_SURFACE_WG", "OCEAN_FLOOR_WG", "MOTION_BLOCKING", "MOTION_BLOCKING_NO_LEAVES", "OCEAN_FLOOR", "LIGHT_BLOCKING", "WORLD_SURFACE"
|
|
126
|
+
"C|V4", # A Compound of Long Arrays with these keys "WORLD_SURFACE_WG", "OCEAN_FLOOR_WG", "MOTION_BLOCKING", "MOTION_BLOCKING_NO_LEAVES", "OCEAN_FLOOR", "WORLD_SURFACE"
|
|
127
|
+
],
|
|
128
|
+
# 'carving_masks': ['C|?BA'],
|
|
129
|
+
"light_optional": ["false", "true"],
|
|
130
|
+
"block_entity_format": [EntityIDType.namespace_str_id],
|
|
131
|
+
"block_entity_coord_format": [EntityCoordType.xyz_int],
|
|
132
|
+
"entity_format": [EntityIDType.namespace_str_id],
|
|
133
|
+
"entity_coord_format": [EntityCoordType.Pos_list_double],
|
|
134
|
+
# 'lights': [],
|
|
135
|
+
}
|
|
136
|
+
self._features = {key: None for key in self._feature_options.keys()}
|
|
137
|
+
|
|
138
|
+
def _set_feature(self, feature: str, option: Any):
|
|
139
|
+
assert feature in self._feature_options, f"{feature} is not a valid feature."
|
|
140
|
+
assert (
|
|
141
|
+
option is None or option in self._feature_options[feature]
|
|
142
|
+
), f'Invalid option {option} for feature "{feature}"'
|
|
143
|
+
self._features[feature] = option
|
|
144
|
+
|
|
145
|
+
def is_valid(self, key: Tuple) -> bool:
|
|
146
|
+
return key[0] == "java" and self.minor_is_valid(key[1])
|
|
147
|
+
|
|
148
|
+
@staticmethod
|
|
149
|
+
@abstractmethod
|
|
150
|
+
def minor_is_valid(key: int):
|
|
151
|
+
raise NotImplementedError
|
|
152
|
+
|
|
153
|
+
def get_translator(
|
|
154
|
+
self,
|
|
155
|
+
max_world_version: VersionIdentifierType,
|
|
156
|
+
data: ChunkDataType = None,
|
|
157
|
+
) -> Tuple["Translator", int]:
|
|
158
|
+
if data is None:
|
|
159
|
+
key = max_world_version
|
|
160
|
+
version = max_world_version[1]
|
|
161
|
+
else:
|
|
162
|
+
data_version = (
|
|
163
|
+
data.get("region", NamedTag(CompoundTag()))
|
|
164
|
+
.compound.get("DataVersion", IntTag(-1))
|
|
165
|
+
.py_int
|
|
166
|
+
)
|
|
167
|
+
key, version = (("java", data_version), data_version)
|
|
168
|
+
|
|
169
|
+
return loader.Translators.get(key), version
|
|
170
|
+
|
|
171
|
+
def get_layer_obj(
|
|
172
|
+
self,
|
|
173
|
+
obj: ChunkDataType,
|
|
174
|
+
data: Tuple[
|
|
175
|
+
str,
|
|
176
|
+
Sequence[Tuple[Union[str, int], Type[AbstractBaseTag]]],
|
|
177
|
+
Union[None, AnyNBT, Type[AbstractBaseTag]],
|
|
178
|
+
],
|
|
179
|
+
*,
|
|
180
|
+
pop_last=False,
|
|
181
|
+
) -> Any:
|
|
182
|
+
"""
|
|
183
|
+
Get an object from a nested NBT structure layer
|
|
184
|
+
|
|
185
|
+
:param obj: The chunk data object
|
|
186
|
+
:param data: The data layer name, the nbt path and the default
|
|
187
|
+
:param pop_last: If true the last key will be popped
|
|
188
|
+
:return: The found data or the default
|
|
189
|
+
"""
|
|
190
|
+
layer_key, path, default = data
|
|
191
|
+
if layer_key in obj:
|
|
192
|
+
return self.get_nested_obj(
|
|
193
|
+
obj[layer_key].compound, path, default, pop_last=pop_last
|
|
194
|
+
)
|
|
195
|
+
elif default is None or isinstance(default, AbstractBaseTag):
|
|
196
|
+
return default
|
|
197
|
+
elif issubclass(default, AbstractBaseTag):
|
|
198
|
+
return default()
|
|
199
|
+
else:
|
|
200
|
+
raise TypeError("default must be None, an NBT instance or an NBT class.")
|
|
201
|
+
|
|
202
|
+
def set_layer_obj(
|
|
203
|
+
self,
|
|
204
|
+
obj: ChunkDataType,
|
|
205
|
+
data: Tuple[
|
|
206
|
+
str,
|
|
207
|
+
Sequence[Tuple[Union[str, int], Type[AbstractBaseTag]]],
|
|
208
|
+
Union[None, AnyNBT, Type[AbstractBaseTag]],
|
|
209
|
+
],
|
|
210
|
+
default_tag: AnyNBT = None,
|
|
211
|
+
*,
|
|
212
|
+
setdefault=False,
|
|
213
|
+
) -> AnyNBT:
|
|
214
|
+
"""
|
|
215
|
+
Setdefault on a ChunkDataType object
|
|
216
|
+
|
|
217
|
+
:param obj: The ChunkDataType object to use
|
|
218
|
+
:param data: The data to set
|
|
219
|
+
:param setdefault: If True will behave like setdefault. If False will replace existing data.
|
|
220
|
+
:return: The existing data found or the default that was set
|
|
221
|
+
"""
|
|
222
|
+
layer_key, path, default = data
|
|
223
|
+
default = default if default_tag is None else default_tag
|
|
224
|
+
if not path:
|
|
225
|
+
raise ValueError("was not given a path to set")
|
|
226
|
+
tag = obj.setdefault(layer_key, NamedTag()).compound
|
|
227
|
+
*path, (key, dtype) = path
|
|
228
|
+
if path:
|
|
229
|
+
key_path = next(zip(*path))
|
|
230
|
+
else:
|
|
231
|
+
key_path = ()
|
|
232
|
+
return self.set_obj(
|
|
233
|
+
tag, key, dtype, default, path=key_path, setdefault=setdefault
|
|
234
|
+
)
|
|
235
|
+
|
|
236
|
+
@abstractmethod
|
|
237
|
+
def decode(
|
|
238
|
+
self, cx: int, cz: int, data: ChunkDataType, bounds: Tuple[int, int]
|
|
239
|
+
) -> Tuple["Chunk", AnyNDArray]:
|
|
240
|
+
"""
|
|
241
|
+
Create an amulet.api.chunk.Chunk object from raw data.
|
|
242
|
+
:param cx: chunk x coordinate
|
|
243
|
+
:param cz: chunk z coordinate
|
|
244
|
+
:param data: The chunk data
|
|
245
|
+
:param bounds: The minimum and maximum bounds of the chunk. In 1.17 this is required to define where the biome array sits.
|
|
246
|
+
:return: Chunk object in version-specific format, along with the block_palette for that chunk.
|
|
247
|
+
"""
|
|
248
|
+
raise NotImplementedError
|
|
249
|
+
|
|
250
|
+
def _decode_entity_list(self, entities: ListTag) -> List["Entity"]:
|
|
251
|
+
entities_out = []
|
|
252
|
+
if entities.list_data_type == CompoundTag.tag_id:
|
|
253
|
+
for nbt in entities:
|
|
254
|
+
entity = self._decode_entity(
|
|
255
|
+
NamedTag(nbt),
|
|
256
|
+
self._features["entity_format"],
|
|
257
|
+
self._features["entity_coord_format"],
|
|
258
|
+
)
|
|
259
|
+
if entity is not None:
|
|
260
|
+
entities_out.append(entity)
|
|
261
|
+
|
|
262
|
+
return entities_out
|
|
263
|
+
|
|
264
|
+
def _decode_block_entity_list(self, block_entities: ListTag) -> List["BlockEntity"]:
|
|
265
|
+
entities_out = []
|
|
266
|
+
if block_entities.list_data_type == CompoundTag.tag_id:
|
|
267
|
+
for nbt in block_entities:
|
|
268
|
+
if not isinstance(nbt, CompoundTag):
|
|
269
|
+
continue
|
|
270
|
+
entity = self._decode_block_entity(
|
|
271
|
+
NamedTag(nbt),
|
|
272
|
+
self._features["block_entity_format"],
|
|
273
|
+
self._features["block_entity_coord_format"],
|
|
274
|
+
)
|
|
275
|
+
if entity is not None:
|
|
276
|
+
entities_out.append(entity)
|
|
277
|
+
|
|
278
|
+
return entities_out
|
|
279
|
+
|
|
280
|
+
@abstractmethod
|
|
281
|
+
def encode(
|
|
282
|
+
self,
|
|
283
|
+
chunk: "Chunk",
|
|
284
|
+
palette: AnyNDArray,
|
|
285
|
+
max_world_version: Tuple[str, int],
|
|
286
|
+
bounds: Tuple[int, int],
|
|
287
|
+
) -> ChunkDataType:
|
|
288
|
+
"""
|
|
289
|
+
Encode a version-specific chunk to raw data for the format to store.
|
|
290
|
+
|
|
291
|
+
:param chunk: The already translated version-specific chunk to encode.
|
|
292
|
+
:param palette: The block_palette the ids in the chunk correspond to.
|
|
293
|
+
:type palette: numpy.ndarray[Block]
|
|
294
|
+
:param max_world_version: The key to use to find the encoder.
|
|
295
|
+
:param bounds: The minimum and maximum bounds of the chunk. In 1.17 this is required to define where the biome array sits.
|
|
296
|
+
:return: Raw data to be stored by the Format.
|
|
297
|
+
"""
|
|
298
|
+
raise NotImplementedError
|
|
299
|
+
|
|
300
|
+
def _encode_entity_list(self, entities: Iterable["Entity"]) -> ListTag:
|
|
301
|
+
entities_out = []
|
|
302
|
+
for entity in entities:
|
|
303
|
+
nbt = self._encode_entity(
|
|
304
|
+
entity,
|
|
305
|
+
self._features["entity_format"],
|
|
306
|
+
self._features["entity_coord_format"],
|
|
307
|
+
)
|
|
308
|
+
if nbt is not None:
|
|
309
|
+
entities_out.append(nbt.compound)
|
|
310
|
+
|
|
311
|
+
return ListTag(entities_out)
|
|
312
|
+
|
|
313
|
+
def _encode_block_entity_list(
|
|
314
|
+
self, block_entities: Iterable["BlockEntity"]
|
|
315
|
+
) -> ListTag:
|
|
316
|
+
entities_out = []
|
|
317
|
+
for entity in block_entities:
|
|
318
|
+
nbt = self._encode_block_entity(
|
|
319
|
+
entity,
|
|
320
|
+
self._features["block_entity_format"],
|
|
321
|
+
self._features["block_entity_coord_format"],
|
|
322
|
+
)
|
|
323
|
+
if nbt is not None:
|
|
324
|
+
entities_out.append(nbt.compound)
|
|
325
|
+
|
|
326
|
+
return ListTag(entities_out)
|