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.

Files changed (198) hide show
  1. amulet/__init__.py +27 -27
  2. amulet/__pyinstaller/__init__.py +2 -2
  3. amulet/__pyinstaller/hook-amulet.py +4 -4
  4. amulet/_version.py +21 -21
  5. amulet/api/__init__.py +2 -2
  6. amulet/api/abstract_base_entity.py +128 -128
  7. amulet/api/block.py +630 -630
  8. amulet/api/block_entity.py +71 -71
  9. amulet/api/cache.py +107 -107
  10. amulet/api/chunk/__init__.py +6 -6
  11. amulet/api/chunk/biomes.py +207 -207
  12. amulet/api/chunk/block_entity_dict.py +175 -175
  13. amulet/api/chunk/blocks.py +46 -46
  14. amulet/api/chunk/chunk.py +389 -389
  15. amulet/api/chunk/entity_list.py +75 -75
  16. amulet/api/chunk/status.py +167 -167
  17. amulet/api/data_types/__init__.py +4 -4
  18. amulet/api/data_types/generic_types.py +4 -4
  19. amulet/api/data_types/operation_types.py +16 -16
  20. amulet/api/data_types/world_types.py +49 -49
  21. amulet/api/data_types/wrapper_types.py +71 -71
  22. amulet/api/entity.py +74 -74
  23. amulet/api/errors.py +119 -119
  24. amulet/api/history/__init__.py +36 -36
  25. amulet/api/history/base/__init__.py +3 -3
  26. amulet/api/history/base/base_history.py +26 -26
  27. amulet/api/history/base/history_manager.py +63 -63
  28. amulet/api/history/base/revision_manager.py +73 -73
  29. amulet/api/history/changeable.py +15 -15
  30. amulet/api/history/data_types.py +7 -7
  31. amulet/api/history/history_manager/__init__.py +3 -3
  32. amulet/api/history/history_manager/container.py +102 -102
  33. amulet/api/history/history_manager/database.py +279 -279
  34. amulet/api/history/history_manager/meta.py +93 -93
  35. amulet/api/history/history_manager/object.py +116 -116
  36. amulet/api/history/revision_manager/__init__.py +2 -2
  37. amulet/api/history/revision_manager/disk.py +33 -33
  38. amulet/api/history/revision_manager/ram.py +12 -12
  39. amulet/api/item.py +75 -75
  40. amulet/api/level/__init__.py +4 -4
  41. amulet/api/level/base_level/__init__.py +1 -1
  42. amulet/api/level/base_level/base_level.py +1035 -1026
  43. amulet/api/level/base_level/chunk_manager.py +227 -227
  44. amulet/api/level/base_level/clone.py +389 -389
  45. amulet/api/level/base_level/player_manager.py +101 -101
  46. amulet/api/level/immutable_structure/__init__.py +1 -1
  47. amulet/api/level/immutable_structure/immutable_structure.py +94 -94
  48. amulet/api/level/immutable_structure/void_format_wrapper.py +117 -117
  49. amulet/api/level/structure.py +22 -22
  50. amulet/api/level/world.py +19 -19
  51. amulet/api/partial_3d_array/__init__.py +2 -2
  52. amulet/api/partial_3d_array/base_partial_3d_array.py +263 -263
  53. amulet/api/partial_3d_array/bounded_partial_3d_array.py +528 -528
  54. amulet/api/partial_3d_array/data_types.py +15 -15
  55. amulet/api/partial_3d_array/unbounded_partial_3d_array.py +229 -229
  56. amulet/api/partial_3d_array/util.py +152 -152
  57. amulet/api/player.py +65 -65
  58. amulet/api/registry/__init__.py +2 -2
  59. amulet/api/registry/base_registry.py +34 -34
  60. amulet/api/registry/biome_manager.py +153 -153
  61. amulet/api/registry/block_manager.py +156 -156
  62. amulet/api/selection/__init__.py +2 -2
  63. amulet/api/selection/abstract_selection.py +315 -315
  64. amulet/api/selection/box.py +805 -805
  65. amulet/api/selection/group.py +488 -488
  66. amulet/api/structure.py +37 -37
  67. amulet/api/wrapper/__init__.py +8 -8
  68. amulet/api/wrapper/chunk/interface.py +441 -441
  69. amulet/api/wrapper/chunk/translator.py +567 -567
  70. amulet/api/wrapper/format_wrapper.py +772 -772
  71. amulet/api/wrapper/structure_format_wrapper.py +116 -116
  72. amulet/api/wrapper/world_format_wrapper.py +63 -63
  73. amulet/level/__init__.py +1 -1
  74. amulet/level/formats/anvil_forge_world.py +40 -40
  75. amulet/level/formats/anvil_world/__init__.py +3 -3
  76. amulet/level/formats/anvil_world/_sector_manager.py +291 -384
  77. amulet/level/formats/anvil_world/data_pack/__init__.py +2 -2
  78. amulet/level/formats/anvil_world/data_pack/data_pack.py +224 -224
  79. amulet/level/formats/anvil_world/data_pack/data_pack_manager.py +77 -77
  80. amulet/level/formats/anvil_world/dimension.py +177 -177
  81. amulet/level/formats/anvil_world/format.py +769 -769
  82. amulet/level/formats/anvil_world/region.py +384 -384
  83. amulet/level/formats/construction/__init__.py +3 -3
  84. amulet/level/formats/construction/format_wrapper.py +515 -515
  85. amulet/level/formats/construction/interface.py +134 -134
  86. amulet/level/formats/construction/section.py +60 -60
  87. amulet/level/formats/construction/util.py +165 -165
  88. amulet/level/formats/leveldb_world/__init__.py +3 -3
  89. amulet/level/formats/leveldb_world/chunk.py +33 -33
  90. amulet/level/formats/leveldb_world/dimension.py +385 -419
  91. amulet/level/formats/leveldb_world/format.py +659 -641
  92. amulet/level/formats/leveldb_world/interface/chunk/__init__.py +36 -36
  93. amulet/level/formats/leveldb_world/interface/chunk/base_leveldb_interface.py +836 -836
  94. amulet/level/formats/leveldb_world/interface/chunk/generate_interface.py +31 -31
  95. amulet/level/formats/leveldb_world/interface/chunk/leveldb_0.py +30 -30
  96. amulet/level/formats/leveldb_world/interface/chunk/leveldb_1.py +12 -12
  97. amulet/level/formats/leveldb_world/interface/chunk/leveldb_10.py +12 -12
  98. amulet/level/formats/leveldb_world/interface/chunk/leveldb_11.py +12 -12
  99. amulet/level/formats/leveldb_world/interface/chunk/leveldb_12.py +12 -12
  100. amulet/level/formats/leveldb_world/interface/chunk/leveldb_13.py +12 -12
  101. amulet/level/formats/leveldb_world/interface/chunk/leveldb_14.py +12 -12
  102. amulet/level/formats/leveldb_world/interface/chunk/leveldb_15.py +12 -12
  103. amulet/level/formats/leveldb_world/interface/chunk/leveldb_16.py +12 -12
  104. amulet/level/formats/leveldb_world/interface/chunk/leveldb_17.py +12 -12
  105. amulet/level/formats/leveldb_world/interface/chunk/leveldb_18.py +12 -12
  106. amulet/level/formats/leveldb_world/interface/chunk/leveldb_19.py +12 -12
  107. amulet/level/formats/leveldb_world/interface/chunk/leveldb_2.py +12 -12
  108. amulet/level/formats/leveldb_world/interface/chunk/leveldb_20.py +12 -12
  109. amulet/level/formats/leveldb_world/interface/chunk/leveldb_21.py +12 -12
  110. amulet/level/formats/leveldb_world/interface/chunk/leveldb_22.py +12 -12
  111. amulet/level/formats/leveldb_world/interface/chunk/leveldb_23.py +10 -10
  112. amulet/level/formats/leveldb_world/interface/chunk/leveldb_24.py +10 -10
  113. amulet/level/formats/leveldb_world/interface/chunk/leveldb_25.py +24 -24
  114. amulet/level/formats/leveldb_world/interface/chunk/leveldb_26.py +10 -10
  115. amulet/level/formats/leveldb_world/interface/chunk/leveldb_27.py +10 -10
  116. amulet/level/formats/leveldb_world/interface/chunk/leveldb_28.py +10 -10
  117. amulet/level/formats/leveldb_world/interface/chunk/leveldb_29.py +33 -33
  118. amulet/level/formats/leveldb_world/interface/chunk/leveldb_3.py +57 -57
  119. amulet/level/formats/leveldb_world/interface/chunk/leveldb_30.py +10 -10
  120. amulet/level/formats/leveldb_world/interface/chunk/leveldb_31.py +10 -10
  121. amulet/level/formats/leveldb_world/interface/chunk/leveldb_32.py +10 -10
  122. amulet/level/formats/leveldb_world/interface/chunk/leveldb_33.py +10 -10
  123. amulet/level/formats/leveldb_world/interface/chunk/leveldb_34.py +10 -10
  124. amulet/level/formats/leveldb_world/interface/chunk/leveldb_35.py +10 -10
  125. amulet/level/formats/leveldb_world/interface/chunk/leveldb_36.py +10 -10
  126. amulet/level/formats/leveldb_world/interface/chunk/leveldb_37.py +10 -10
  127. amulet/level/formats/leveldb_world/interface/chunk/leveldb_38.py +10 -10
  128. amulet/level/formats/leveldb_world/interface/chunk/leveldb_39.py +12 -12
  129. amulet/level/formats/leveldb_world/interface/chunk/leveldb_4.py +12 -12
  130. amulet/level/formats/leveldb_world/interface/chunk/leveldb_40.py +16 -16
  131. amulet/level/formats/leveldb_world/interface/chunk/leveldb_5.py +12 -12
  132. amulet/level/formats/leveldb_world/interface/chunk/leveldb_6.py +12 -12
  133. amulet/level/formats/leveldb_world/interface/chunk/leveldb_7.py +12 -12
  134. amulet/level/formats/leveldb_world/interface/chunk/leveldb_8.py +180 -180
  135. amulet/level/formats/leveldb_world/interface/chunk/leveldb_9.py +18 -18
  136. amulet/level/formats/leveldb_world/interface/chunk/leveldb_chunk_versions.py +79 -79
  137. amulet/level/formats/mcstructure/__init__.py +3 -3
  138. amulet/level/formats/mcstructure/chunk.py +50 -50
  139. amulet/level/formats/mcstructure/format_wrapper.py +408 -408
  140. amulet/level/formats/mcstructure/interface.py +175 -175
  141. amulet/level/formats/schematic/__init__.py +3 -3
  142. amulet/level/formats/schematic/chunk.py +55 -55
  143. amulet/level/formats/schematic/data_types.py +4 -4
  144. amulet/level/formats/schematic/format_wrapper.py +373 -373
  145. amulet/level/formats/schematic/interface.py +142 -142
  146. amulet/level/formats/sponge_schem/__init__.py +4 -4
  147. amulet/level/formats/sponge_schem/chunk.py +62 -62
  148. amulet/level/formats/sponge_schem/format_wrapper.py +463 -463
  149. amulet/level/formats/sponge_schem/interface.py +118 -118
  150. amulet/level/formats/sponge_schem/varint/__init__.py +1 -1
  151. amulet/level/formats/sponge_schem/varint/varint.py +87 -87
  152. amulet/level/interfaces/chunk/anvil/anvil_0.py +72 -72
  153. amulet/level/interfaces/chunk/anvil/anvil_1444.py +336 -336
  154. amulet/level/interfaces/chunk/anvil/anvil_1466.py +94 -94
  155. amulet/level/interfaces/chunk/anvil/anvil_1467.py +37 -37
  156. amulet/level/interfaces/chunk/anvil/anvil_1484.py +20 -20
  157. amulet/level/interfaces/chunk/anvil/anvil_1503.py +20 -20
  158. amulet/level/interfaces/chunk/anvil/anvil_1519.py +34 -34
  159. amulet/level/interfaces/chunk/anvil/anvil_1901.py +20 -20
  160. amulet/level/interfaces/chunk/anvil/anvil_1908.py +20 -20
  161. amulet/level/interfaces/chunk/anvil/anvil_1912.py +21 -21
  162. amulet/level/interfaces/chunk/anvil/anvil_1934.py +20 -20
  163. amulet/level/interfaces/chunk/anvil/anvil_2203.py +69 -69
  164. amulet/level/interfaces/chunk/anvil/anvil_2529.py +19 -19
  165. amulet/level/interfaces/chunk/anvil/anvil_2681.py +76 -76
  166. amulet/level/interfaces/chunk/anvil/anvil_2709.py +19 -19
  167. amulet/level/interfaces/chunk/anvil/anvil_2844.py +267 -267
  168. amulet/level/interfaces/chunk/anvil/anvil_3463.py +19 -19
  169. amulet/level/interfaces/chunk/anvil/anvil_na.py +607 -607
  170. amulet/level/interfaces/chunk/anvil/base_anvil_interface.py +326 -326
  171. amulet/level/load.py +59 -59
  172. amulet/level/loader.py +95 -95
  173. amulet/level/translators/chunk/bedrock/__init__.py +267 -267
  174. amulet/level/translators/chunk/bedrock/bedrock_nbt_blockstate_translator.py +46 -46
  175. amulet/level/translators/chunk/bedrock/bedrock_numerical_translator.py +39 -39
  176. amulet/level/translators/chunk/bedrock/bedrock_psudo_numerical_translator.py +37 -37
  177. amulet/level/translators/chunk/java/java_1_18_translator.py +40 -40
  178. amulet/level/translators/chunk/java/java_blockstate_translator.py +94 -94
  179. amulet/level/translators/chunk/java/java_numerical_translator.py +62 -62
  180. amulet/libs/leveldb/__init__.py +7 -7
  181. amulet/operations/__init__.py +5 -5
  182. amulet/operations/clone.py +18 -18
  183. amulet/operations/delete_chunk.py +32 -32
  184. amulet/operations/fill.py +30 -30
  185. amulet/operations/paste.py +65 -65
  186. amulet/operations/replace.py +58 -58
  187. amulet/utils/__init__.py +14 -14
  188. amulet/utils/format_utils.py +41 -41
  189. amulet/utils/generator.py +15 -15
  190. amulet/utils/matrix.py +243 -243
  191. amulet/utils/numpy_helpers.py +46 -46
  192. amulet/utils/world_utils.py +349 -349
  193. {amulet_core-1.9.19.dist-info → amulet_core-1.9.20.dist-info}/METADATA +97 -97
  194. amulet_core-1.9.20.dist-info/RECORD +208 -0
  195. amulet_core-1.9.19.dist-info/RECORD +0 -208
  196. {amulet_core-1.9.19.dist-info → amulet_core-1.9.20.dist-info}/WHEEL +0 -0
  197. {amulet_core-1.9.19.dist-info → amulet_core-1.9.20.dist-info}/entry_points.txt +0 -0
  198. {amulet_core-1.9.19.dist-info → amulet_core-1.9.20.dist-info}/top_level.txt +0 -0
@@ -1,607 +1,607 @@
1
- from __future__ import annotations
2
-
3
- import logging
4
- from typing import Tuple, Dict, Iterator, TYPE_CHECKING
5
-
6
- import numpy
7
- from amulet_nbt import (
8
- ByteTag,
9
- IntTag,
10
- LongTag,
11
- StringTag,
12
- ListTag,
13
- CompoundTag,
14
- ByteArrayTag,
15
- IntArrayTag,
16
- AbstractBaseArrayTag,
17
- NamedTag,
18
- )
19
-
20
- import amulet
21
- from amulet.api.data_types import SubChunkNDArray, AnyNDArray, BlockCoordinates
22
- from amulet.utils import world_utils
23
- from amulet.api.wrapper import EntityIDType, EntityCoordType
24
- from amulet.api.chunk import Chunk, StatusFormats
25
- from .base_anvil_interface import (
26
- BaseAnvilInterface,
27
- ChunkDataType,
28
- ChunkPathType,
29
- )
30
-
31
- if TYPE_CHECKING:
32
- from amulet.api.chunk import Chunk
33
-
34
- log = logging.getLogger(__name__)
35
-
36
-
37
- class AnvilNAInterface(BaseAnvilInterface):
38
- Level: ChunkPathType = ("region", [("Level", CompoundTag)], CompoundTag)
39
- Sections: ChunkPathType = (
40
- "region",
41
- [("Level", CompoundTag), ("Sections", ListTag)],
42
- ListTag,
43
- )
44
-
45
- BlockEntities: ChunkPathType = (
46
- "region",
47
- [("Level", CompoundTag), ("TileEntities", ListTag)],
48
- ListTag,
49
- )
50
- Entities: ChunkPathType = (
51
- "region",
52
- [("Level", CompoundTag), ("Entities", ListTag)],
53
- ListTag,
54
- )
55
- InhabitedTime: ChunkPathType = (
56
- "region",
57
- [("Level", CompoundTag), ("InhabitedTime", LongTag)],
58
- LongTag,
59
- )
60
- LastUpdate: ChunkPathType = (
61
- "region",
62
- [("Level", CompoundTag), ("LastUpdate", LongTag)],
63
- LongTag,
64
- )
65
- HeightMap: ChunkPathType = (
66
- "region",
67
- [("Level", CompoundTag), ("HeightMap", IntArrayTag)],
68
- IntArrayTag,
69
- )
70
- TerrainPopulated: ChunkPathType = (
71
- "region",
72
- [("Level", CompoundTag), ("TerrainPopulated", ByteTag)],
73
- ByteTag,
74
- )
75
- LightPopulated: ChunkPathType = (
76
- "region",
77
- [("Level", CompoundTag), ("LightPopulated", ByteTag)],
78
- ByteTag,
79
- )
80
- V: ChunkPathType = (
81
- "region",
82
- [("Level", CompoundTag), ("V", ByteTag)],
83
- ByteTag(1),
84
- )
85
- BlockTicks: ChunkPathType = (
86
- "region",
87
- [("Level", CompoundTag), ("TileTicks", ListTag)],
88
- ListTag,
89
- )
90
- Biomes: ChunkPathType = (
91
- "region",
92
- [("Level", CompoundTag), ("Biomes", ByteArrayTag)],
93
- None,
94
- )
95
- xPos: ChunkPathType = (
96
- "region",
97
- [("Level", CompoundTag), ("xPos", IntTag)],
98
- IntTag,
99
- )
100
- zPos: ChunkPathType = (
101
- "region",
102
- [("Level", CompoundTag), ("zPos", IntTag)],
103
- IntTag,
104
- )
105
-
106
- def __init__(self):
107
- super().__init__()
108
- self._set_feature("height_map", "256IA")
109
-
110
- self._set_feature("light_optional", "false")
111
-
112
- self._set_feature("block_entity_format", EntityIDType.namespace_str_id)
113
- self._set_feature("block_entity_coord_format", EntityCoordType.xyz_int)
114
-
115
- self._set_feature("entity_format", EntityIDType.namespace_str_id)
116
- self._set_feature("entity_coord_format", EntityCoordType.Pos_list_double)
117
-
118
- self._register_decoder(self._decode_coords)
119
- self._register_decoder(self._decode_last_update)
120
- self._register_decoder(self._decode_status)
121
- self._register_decoder(self._decode_v_tag)
122
- self._register_decoder(self._decode_inhabited_time)
123
- self._register_decoder(self._decode_biomes)
124
- self._register_decoder(self._decode_height)
125
- self._register_decoder(self._decode_entities)
126
- self._register_decoder(self._decode_blocks)
127
- self._register_decoder(self._decode_block_entities)
128
- self._register_decoder(self._decode_block_ticks)
129
- self._register_decoder(self._decode_block_light)
130
- self._register_decoder(self._decode_sky_light)
131
-
132
- self._register_encoder(self._encode_coords)
133
- self._register_encoder(self._encode_last_update)
134
- self._register_encoder(self._encode_status)
135
- self._register_encoder(self._encode_v_tag)
136
- self._register_encoder(self._encode_inhabited_time)
137
- self._register_encoder(self._encode_biomes)
138
- self._register_encoder(self._encode_height)
139
- self._register_encoder(self._encode_entities)
140
- self._register_encoder(self._encode_blocks)
141
- self._register_encoder(self._encode_block_entities)
142
- self._register_encoder(self._encode_block_ticks)
143
- self._register_encoder(self._encode_block_light)
144
- self._register_encoder(self._encode_sky_light)
145
-
146
- self._register_post_encoder(self._post_encode_sections)
147
-
148
- @staticmethod
149
- def minor_is_valid(key: int):
150
- return key == -1
151
-
152
- def decode(
153
- self, cx: int, cz: int, data: ChunkDataType, bounds: Tuple[int, int]
154
- ) -> Tuple["Chunk", AnyNDArray]:
155
- chunk = self._init_decode(cx, cz, data)
156
- floor_cy = self._get_floor_cy(data)
157
- height_cy = (bounds[1] - bounds[0]) >> 4
158
- self._do_decode(chunk, data, floor_cy, height_cy)
159
- block_palette = chunk.misc.pop("block_palette")
160
- return chunk, block_palette
161
-
162
- def _get_floor_cy(self, data: ChunkDataType):
163
- return 0
164
-
165
- @staticmethod
166
- def _init_decode(cx: int, cz: int, data: ChunkDataType) -> Chunk:
167
- """Get the decode started by creating a chunk object."""
168
- chunk = Chunk(cx, cz)
169
- chunk.misc = {
170
- # store the chunk data so that any non-versioned data can get saved back
171
- "_java_chunk_data_layers": data
172
- }
173
- return chunk
174
-
175
- def _get_level(self, data: ChunkDataType) -> CompoundTag:
176
- """
177
- Get the level data container
178
- For older levels this is region:Level but newer worlds it is in region root
179
- :param data: The raw chunk data
180
- :return: The level data compound
181
- """
182
- return self.get_layer_obj(data, self.Level)
183
-
184
- def _decode_coords(
185
- self, chunk: Chunk, data: ChunkDataType, floor_cy: int, height_cy: int
186
- ):
187
- self.get_layer_obj(data, self.xPos, pop_last=True)
188
- self.get_layer_obj(data, self.zPos, pop_last=True)
189
-
190
- def _decode_last_update(
191
- self, chunk: Chunk, data: ChunkDataType, floor_cy: int, height_cy: int
192
- ):
193
- chunk.misc["last_update"] = self.get_layer_obj(
194
- data, self.LastUpdate, pop_last=True
195
- ).py_int
196
-
197
- def _decode_status(
198
- self, chunk: Chunk, data: ChunkDataType, floor_cy: int, height_cy: int
199
- ):
200
- status = "empty"
201
- if self.get_layer_obj(data, self.TerrainPopulated, pop_last=True):
202
- status = "decorated"
203
- if self.get_layer_obj(data, self.LightPopulated, pop_last=True):
204
- status = "postprocessed"
205
- chunk.status = status
206
-
207
- def _decode_v_tag(
208
- self, chunk: Chunk, data: ChunkDataType, floor_cy: int, height_cy: int
209
- ):
210
- chunk.misc["V"] = self.get_layer_obj(data, self.V, pop_last=True).py_int
211
-
212
- def _decode_inhabited_time(
213
- self, chunk: Chunk, data: ChunkDataType, floor_cy: int, height_cy: int
214
- ):
215
- chunk.misc["inhabited_time"] = self.get_layer_obj(
216
- data, self.InhabitedTime, pop_last=True
217
- ).py_int
218
-
219
- def _decode_biomes(
220
- self, chunk: Chunk, data: ChunkDataType, floor_cy: int, height_cy: int
221
- ):
222
- biomes = self.get_layer_obj(data, self.Biomes, pop_last=True)
223
- if isinstance(biomes, AbstractBaseArrayTag) and biomes.np_array.size == 256:
224
- chunk.biomes = biomes.np_array.astype(numpy.uint32).reshape((16, 16))
225
-
226
- def _decode_height(
227
- self, chunk: Chunk, data: ChunkDataType, floor_cy: int, height_cy: int
228
- ):
229
- height = self.get_layer_obj(data, self.HeightMap, pop_last=True).np_array
230
- if isinstance(height, numpy.ndarray) and height.size == 256:
231
- chunk.misc["height_map256IA"] = height.reshape((16, 16))
232
-
233
- def _iter_sections(self, data: ChunkDataType) -> Iterator[Tuple[int, CompoundTag]]:
234
- sections: ListTag = self.get_layer_obj(data, self.Sections)
235
- for section in sections:
236
- yield section["Y"].py_int, section
237
-
238
- def _decode_blocks(
239
- self, chunk: Chunk, data: ChunkDataType, floor_cy: int, height_cy: int
240
- ):
241
- blocks: Dict[int, SubChunkNDArray] = {}
242
- palette = []
243
- palette_len = 0
244
- for cy, section in self._iter_sections(data):
245
- block_tag = section.pop("Blocks", None)
246
- data_tag = section.pop("Data", None)
247
- if not isinstance(block_tag, AbstractBaseArrayTag) or not isinstance(
248
- data_tag, AbstractBaseArrayTag
249
- ):
250
- continue
251
- section_blocks = numpy.asarray(block_tag, dtype=numpy.uint8)
252
- section_data = numpy.asarray(data_tag, dtype=numpy.uint8)
253
- section_blocks = section_blocks.reshape((16, 16, 16))
254
- section_blocks = section_blocks.astype(numpy.uint16)
255
-
256
- section_data = world_utils.from_nibble_array(section_data)
257
- section_data = section_data.reshape((16, 16, 16))
258
-
259
- add_tag = section.pop("Add", None)
260
- if isinstance(add_tag, AbstractBaseArrayTag):
261
- add_blocks = numpy.asarray(add_tag, dtype=numpy.uint8)
262
- add_blocks = world_utils.from_nibble_array(add_blocks)
263
- add_blocks = add_blocks.reshape((16, 16, 16))
264
-
265
- section_blocks |= add_blocks.astype(numpy.uint16) << 8
266
- # TODO: fix this
267
-
268
- (section_palette, blocks[cy]) = world_utils.fast_unique(
269
- numpy.transpose(
270
- (section_blocks << 4) + section_data, (2, 0, 1)
271
- ) # YZX -> XYZ
272
- )
273
- blocks[cy] += palette_len
274
- palette_len += len(section_palette)
275
- palette.append(section_palette)
276
-
277
- if palette:
278
- final_palette, lut = numpy.unique(
279
- numpy.concatenate(palette), return_inverse=True
280
- )
281
- final_palette: numpy.ndarray = numpy.array(
282
- [final_palette >> 4, final_palette & 15]
283
- ).T
284
- for cy in blocks:
285
- blocks[cy] = lut[blocks[cy]]
286
- else:
287
- final_palette = numpy.array([], dtype=object)
288
- chunk.blocks = blocks
289
- chunk.misc["block_palette"] = final_palette
290
-
291
- def _unpack_light(
292
- self, data: ChunkDataType, section_key: str
293
- ) -> Dict[int, numpy.ndarray]:
294
- light_container = {}
295
- for cy, section in self._iter_sections(data):
296
- if self.check_type(section, section_key, ByteArrayTag):
297
- light: numpy.ndarray = section.pop(section_key).np_array
298
- if light.size == 2048:
299
- # TODO: check if this needs transposing or if the values are the other way around
300
- light_container[cy] = (
301
- (
302
- light.reshape(-1, 1)
303
- & numpy.array([0xF, 0xF0], dtype=numpy.uint8)
304
- )
305
- >> numpy.array([0, 4], dtype=numpy.uint8)
306
- ).reshape((16, 16, 16))
307
- return light_container
308
-
309
- def _decode_block_light(
310
- self, chunk: Chunk, data: ChunkDataType, floor_cy: int, height_cy: int
311
- ):
312
- chunk.misc["block_light"] = self._unpack_light(data, "BlockLight")
313
-
314
- def _decode_sky_light(
315
- self, chunk: Chunk, data: ChunkDataType, floor_cy: int, height_cy: int
316
- ):
317
- chunk.misc["sky_light"] = self._unpack_light(data, "SkyLight")
318
-
319
- def _decode_entities(
320
- self, chunk: Chunk, data: ChunkDataType, floor_cy: int, height_cy: int
321
- ):
322
- ents = self._decode_entity_list(
323
- self.get_layer_obj(data, self.Entities, pop_last=True)
324
- )
325
- if amulet.entity_support:
326
- chunk.entities = ents
327
- else:
328
- chunk._native_entities.extend(ents)
329
- chunk._native_version = ("java", -1)
330
-
331
- def _decode_block_entities(
332
- self, chunk: Chunk, data: ChunkDataType, floor_cy: int, height_cy: int
333
- ):
334
- chunk.block_entities = self._decode_block_entity_list(
335
- self.get_layer_obj(data, self.BlockEntities, pop_last=True)
336
- )
337
-
338
- @staticmethod
339
- def _decode_ticks(ticks: ListTag) -> Dict[BlockCoordinates, Tuple[str, int, int]]:
340
- return {
341
- (tick["x"].py_data, tick["y"].py_data, tick["z"].py_data): (
342
- tick["i"].py_data,
343
- tick["t"].py_data,
344
- tick["p"].py_data,
345
- )
346
- for tick in ticks
347
- if all(c in tick and isinstance(tick[c], IntTag) for c in "xyztp")
348
- and "i" in tick
349
- and isinstance(tick["i"], StringTag)
350
- }
351
-
352
- def _decode_block_ticks(
353
- self, chunk: Chunk, data: ChunkDataType, floor_cy: int, height_cy: int
354
- ):
355
- chunk.misc.setdefault("block_ticks", {}).update(
356
- self._decode_ticks(self.get_layer_obj(data, self.BlockTicks, pop_last=True))
357
- )
358
-
359
- def encode(
360
- self,
361
- chunk: "Chunk",
362
- palette: AnyNDArray,
363
- max_world_version: Tuple[str, int],
364
- bounds: Tuple[int, int],
365
- ) -> ChunkDataType:
366
- floor_cy = bounds[0] >> 4
367
- height_cy = (bounds[1] - bounds[0]) >> 4
368
- data = self._init_encode(chunk, max_world_version, floor_cy, height_cy)
369
- chunk.misc["block_palette"] = palette
370
- self._do_encode(chunk, data, floor_cy, height_cy)
371
- return data
372
-
373
- def _init_encode(
374
- self,
375
- chunk: "Chunk",
376
- max_world_version: Tuple[str, int],
377
- floor_cy: int,
378
- height_cy: int,
379
- ) -> ChunkDataType:
380
- """Get or create the root data."""
381
- data = chunk.misc.get("_java_chunk_data_layers", None)
382
- if not isinstance(data, dict):
383
- data = {}
384
- return {
385
- key: value
386
- for key, value in data.items()
387
- if isinstance(key, str) and isinstance(value, NamedTag)
388
- }
389
-
390
- def _get_encode_sections(
391
- self,
392
- data: ChunkDataType,
393
- floor_cy: int,
394
- height_cy: int,
395
- ) -> Dict[int, CompoundTag]:
396
- """Get or create the section array populating all valid sections"""
397
- sections: ListTag = self.set_layer_obj(data, self.Sections, setdefault=True)
398
- section_map: Dict[int, CompoundTag] = {}
399
- section: CompoundTag
400
- for section_index in range(len(sections) - 1, -1, -1):
401
- section = sections[section_index]
402
- cy = section.get("Y", None)
403
- if isinstance(cy, ByteTag):
404
- section_map[cy.py_int] = section
405
- else:
406
- sections.pop(section_index)
407
- for cy in range(floor_cy, floor_cy + height_cy):
408
- if cy not in section_map:
409
- section = section_map[cy] = CompoundTag({"Y": ByteTag(cy)})
410
- sections.append(section)
411
- return section_map
412
-
413
- def _encode_block_section(
414
- self,
415
- chunk: Chunk,
416
- sections: Dict[int, CompoundTag],
417
- palette: AnyNDArray,
418
- cy: int,
419
- ):
420
- block_sub_array = palette[
421
- numpy.transpose(
422
- chunk.blocks.get_sub_chunk(cy), (1, 2, 0)
423
- ).ravel() # XYZ -> YZX
424
- ]
425
-
426
- data_sub_array = block_sub_array[:, 1]
427
- block_sub_array = block_sub_array[:, 0]
428
- # if not numpy.any(block_sub_array) and not numpy.any(data_sub_array):
429
- # return False
430
- sections[cy]["Blocks"] = ByteArrayTag(block_sub_array.astype("uint8"))
431
- sections[cy]["Data"] = ByteArrayTag(world_utils.to_nibble_array(data_sub_array))
432
-
433
- def _encode_blocks(
434
- self, chunk: Chunk, data: ChunkDataType, floor_cy: int, height_cy: int
435
- ):
436
- sections = self._get_encode_sections(data, floor_cy, height_cy)
437
- block_palette = chunk.misc.pop("block_palette")
438
- ceil_cy = floor_cy + height_cy
439
- for cy in chunk.blocks.sub_chunks:
440
- if floor_cy <= cy < ceil_cy:
441
- self._encode_block_section(chunk, sections, block_palette, cy)
442
-
443
- def _encode_coords(
444
- self, chunk: Chunk, data: ChunkDataType, floor_cy: int, height_cy: int
445
- ):
446
- self.set_layer_obj(data, self.xPos, IntTag(chunk.cx))
447
- self.set_layer_obj(data, self.zPos, IntTag(chunk.cz))
448
-
449
- def _encode_last_update(
450
- self, chunk: Chunk, data: ChunkDataType, floor_cy: int, height_cy: int
451
- ):
452
- self.set_layer_obj(
453
- data, self.LastUpdate, LongTag(chunk.misc.get("last_update", 0))
454
- )
455
-
456
- def _encode_status(
457
- self, chunk: Chunk, data: ChunkDataType, floor_cy: int, height_cy: int
458
- ):
459
- status = chunk.status.as_type(StatusFormats.Raw)
460
- self.set_layer_obj(data, self.TerrainPopulated, ByteTag(int(status > -0.3)))
461
- self.set_layer_obj(data, self.LightPopulated, ByteTag(int(status > -0.2)))
462
-
463
- def _encode_v_tag(
464
- self, chunk: Chunk, data: ChunkDataType, floor_cy: int, height_cy: int
465
- ):
466
- self.set_layer_obj(data, self.V, ByteTag(chunk.misc.get("V", 1)))
467
-
468
- def _encode_inhabited_time(
469
- self, chunk: Chunk, data: ChunkDataType, floor_cy: int, height_cy: int
470
- ):
471
- self.set_layer_obj(
472
- data, self.InhabitedTime, LongTag(chunk.misc.get("inhabited_time", 0))
473
- )
474
-
475
- def _encode_biomes(
476
- self, chunk: Chunk, data: ChunkDataType, floor_cy: int, height_cy: int
477
- ):
478
- chunk.biomes.convert_to_2d()
479
- self.set_layer_obj(
480
- data,
481
- self.Biomes,
482
- ByteArrayTag(chunk.biomes.astype(dtype=numpy.uint8).ravel()),
483
- )
484
-
485
- def _encode_height(
486
- self, chunk: Chunk, data: ChunkDataType, floor_cy: int, height_cy: int
487
- ):
488
- height = chunk.misc.get("height_map256IA", None)
489
- if (
490
- isinstance(height, numpy.ndarray)
491
- and numpy.issubdtype(height.dtype, numpy.integer)
492
- and height.shape == (16, 16)
493
- ):
494
- self.set_layer_obj(
495
- data,
496
- self.HeightMap,
497
- IntArrayTag(numpy.zeros(256, dtype=numpy.uint32)),
498
- )
499
- elif self._features["height_map"] == "256IARequired":
500
- self.set_layer_obj(data, self.HeightMap, IntArrayTag(height.ravel()))
501
-
502
- def _encode_entities(
503
- self, chunk: Chunk, data: ChunkDataType, floor_cy: int, height_cy: int
504
- ):
505
- if amulet.entity_support:
506
- entities = chunk.entities
507
- else:
508
- entities = chunk._native_entities
509
-
510
- self.set_layer_obj(
511
- data,
512
- self.Entities,
513
- self._encode_entity_list(entities),
514
- )
515
-
516
- def _encode_block_entities(
517
- self, chunk: Chunk, data: ChunkDataType, floor_cy: int, height_cy: int
518
- ):
519
- self.set_layer_obj(
520
- data,
521
- self.BlockEntities,
522
- self._encode_block_entity_list(chunk.block_entities),
523
- )
524
-
525
- @staticmethod
526
- def _encode_ticks(ticks: Dict[BlockCoordinates, Tuple[str, int, int]]) -> ListTag:
527
- ticks_out = ListTag()
528
- if isinstance(ticks, dict):
529
- for k, v in ticks.items():
530
- try:
531
- (x, y, z), (i, t, p) = k, v
532
- ticks_out.append(
533
- CompoundTag(
534
- {
535
- "i": StringTag(i),
536
- "p": IntTag(p),
537
- "t": IntTag(t),
538
- "x": IntTag(x),
539
- "y": IntTag(y),
540
- "z": IntTag(x),
541
- }
542
- )
543
- )
544
- except Exception:
545
- log.error(f"Could not serialise tick data {k}: {v}")
546
- return ticks_out
547
-
548
- def _encode_block_ticks(
549
- self, chunk: Chunk, data: ChunkDataType, floor_cy: int, height_cy: int
550
- ):
551
- self.set_layer_obj(
552
- data, self.BlockTicks, self._encode_ticks(chunk.misc.get("block_ticks", {}))
553
- )
554
-
555
- def _pack_light(
556
- self,
557
- chunk: Chunk,
558
- data: ChunkDataType,
559
- floor_cy: int,
560
- height_cy: int,
561
- feature_key: str,
562
- section_key: str,
563
- ):
564
- light_container = chunk.misc.get(feature_key, {})
565
- if not isinstance(light_container, dict):
566
- light_container = {}
567
- for cy, section in self._get_encode_sections(data, floor_cy, height_cy).items():
568
- light = light_container.get(cy, None)
569
- if (
570
- isinstance(light, numpy.ndarray)
571
- and numpy.issubdtype(light.dtype, numpy.integer)
572
- and light.shape == (16, 16, 16)
573
- ):
574
- light = light.ravel() % 16
575
- section[section_key] = ByteArrayTag(light[::2] + (light[1::2] << 4))
576
- elif self._features["light_optional"] == "false":
577
- section[section_key] = ByteArrayTag(
578
- numpy.full(2048, 255, dtype=numpy.uint8)
579
- )
580
-
581
- def _encode_block_light(
582
- self, chunk: Chunk, data: ChunkDataType, floor_cy: int, height_cy: int
583
- ):
584
- self._pack_light(chunk, data, floor_cy, height_cy, "block_light", "BlockLight")
585
-
586
- def _encode_sky_light(
587
- self, chunk: Chunk, data: ChunkDataType, floor_cy: int, height_cy: int
588
- ):
589
- self._pack_light(chunk, data, floor_cy, height_cy, "sky_light", "SkyLight")
590
-
591
- def _post_encode_sections(
592
- self, chunk: Chunk, data: ChunkDataType, floor_cy: int, height_cy: int
593
- ):
594
- """Strip out all empty sections"""
595
- sections = self.get_layer_obj(data, self.Sections)
596
- if sections:
597
- for i in range(len(sections) - 1, -1, -1):
598
- section = sections[i]
599
- if "Blocks" not in section or "Data" not in section:
600
- # in 1.12 if a section exists, Blocks and Data must exist
601
- sections.pop(i)
602
- if not sections:
603
- # if no sections remain we can remove the sections data
604
- self.get_layer_obj(data, self.Sections, pop_last=True)
605
-
606
-
607
- export = AnvilNAInterface
1
+ from __future__ import annotations
2
+
3
+ import logging
4
+ from typing import Tuple, Dict, Iterator, TYPE_CHECKING
5
+
6
+ import numpy
7
+ from amulet_nbt import (
8
+ ByteTag,
9
+ IntTag,
10
+ LongTag,
11
+ StringTag,
12
+ ListTag,
13
+ CompoundTag,
14
+ ByteArrayTag,
15
+ IntArrayTag,
16
+ AbstractBaseArrayTag,
17
+ NamedTag,
18
+ )
19
+
20
+ import amulet
21
+ from amulet.api.data_types import SubChunkNDArray, AnyNDArray, BlockCoordinates
22
+ from amulet.utils import world_utils
23
+ from amulet.api.wrapper import EntityIDType, EntityCoordType
24
+ from amulet.api.chunk import Chunk, StatusFormats
25
+ from .base_anvil_interface import (
26
+ BaseAnvilInterface,
27
+ ChunkDataType,
28
+ ChunkPathType,
29
+ )
30
+
31
+ if TYPE_CHECKING:
32
+ from amulet.api.chunk import Chunk
33
+
34
+ log = logging.getLogger(__name__)
35
+
36
+
37
+ class AnvilNAInterface(BaseAnvilInterface):
38
+ Level: ChunkPathType = ("region", [("Level", CompoundTag)], CompoundTag)
39
+ Sections: ChunkPathType = (
40
+ "region",
41
+ [("Level", CompoundTag), ("Sections", ListTag)],
42
+ ListTag,
43
+ )
44
+
45
+ BlockEntities: ChunkPathType = (
46
+ "region",
47
+ [("Level", CompoundTag), ("TileEntities", ListTag)],
48
+ ListTag,
49
+ )
50
+ Entities: ChunkPathType = (
51
+ "region",
52
+ [("Level", CompoundTag), ("Entities", ListTag)],
53
+ ListTag,
54
+ )
55
+ InhabitedTime: ChunkPathType = (
56
+ "region",
57
+ [("Level", CompoundTag), ("InhabitedTime", LongTag)],
58
+ LongTag,
59
+ )
60
+ LastUpdate: ChunkPathType = (
61
+ "region",
62
+ [("Level", CompoundTag), ("LastUpdate", LongTag)],
63
+ LongTag,
64
+ )
65
+ HeightMap: ChunkPathType = (
66
+ "region",
67
+ [("Level", CompoundTag), ("HeightMap", IntArrayTag)],
68
+ IntArrayTag,
69
+ )
70
+ TerrainPopulated: ChunkPathType = (
71
+ "region",
72
+ [("Level", CompoundTag), ("TerrainPopulated", ByteTag)],
73
+ ByteTag,
74
+ )
75
+ LightPopulated: ChunkPathType = (
76
+ "region",
77
+ [("Level", CompoundTag), ("LightPopulated", ByteTag)],
78
+ ByteTag,
79
+ )
80
+ V: ChunkPathType = (
81
+ "region",
82
+ [("Level", CompoundTag), ("V", ByteTag)],
83
+ ByteTag(1),
84
+ )
85
+ BlockTicks: ChunkPathType = (
86
+ "region",
87
+ [("Level", CompoundTag), ("TileTicks", ListTag)],
88
+ ListTag,
89
+ )
90
+ Biomes: ChunkPathType = (
91
+ "region",
92
+ [("Level", CompoundTag), ("Biomes", ByteArrayTag)],
93
+ None,
94
+ )
95
+ xPos: ChunkPathType = (
96
+ "region",
97
+ [("Level", CompoundTag), ("xPos", IntTag)],
98
+ IntTag,
99
+ )
100
+ zPos: ChunkPathType = (
101
+ "region",
102
+ [("Level", CompoundTag), ("zPos", IntTag)],
103
+ IntTag,
104
+ )
105
+
106
+ def __init__(self):
107
+ super().__init__()
108
+ self._set_feature("height_map", "256IA")
109
+
110
+ self._set_feature("light_optional", "false")
111
+
112
+ self._set_feature("block_entity_format", EntityIDType.namespace_str_id)
113
+ self._set_feature("block_entity_coord_format", EntityCoordType.xyz_int)
114
+
115
+ self._set_feature("entity_format", EntityIDType.namespace_str_id)
116
+ self._set_feature("entity_coord_format", EntityCoordType.Pos_list_double)
117
+
118
+ self._register_decoder(self._decode_coords)
119
+ self._register_decoder(self._decode_last_update)
120
+ self._register_decoder(self._decode_status)
121
+ self._register_decoder(self._decode_v_tag)
122
+ self._register_decoder(self._decode_inhabited_time)
123
+ self._register_decoder(self._decode_biomes)
124
+ self._register_decoder(self._decode_height)
125
+ self._register_decoder(self._decode_entities)
126
+ self._register_decoder(self._decode_blocks)
127
+ self._register_decoder(self._decode_block_entities)
128
+ self._register_decoder(self._decode_block_ticks)
129
+ self._register_decoder(self._decode_block_light)
130
+ self._register_decoder(self._decode_sky_light)
131
+
132
+ self._register_encoder(self._encode_coords)
133
+ self._register_encoder(self._encode_last_update)
134
+ self._register_encoder(self._encode_status)
135
+ self._register_encoder(self._encode_v_tag)
136
+ self._register_encoder(self._encode_inhabited_time)
137
+ self._register_encoder(self._encode_biomes)
138
+ self._register_encoder(self._encode_height)
139
+ self._register_encoder(self._encode_entities)
140
+ self._register_encoder(self._encode_blocks)
141
+ self._register_encoder(self._encode_block_entities)
142
+ self._register_encoder(self._encode_block_ticks)
143
+ self._register_encoder(self._encode_block_light)
144
+ self._register_encoder(self._encode_sky_light)
145
+
146
+ self._register_post_encoder(self._post_encode_sections)
147
+
148
+ @staticmethod
149
+ def minor_is_valid(key: int):
150
+ return key == -1
151
+
152
+ def decode(
153
+ self, cx: int, cz: int, data: ChunkDataType, bounds: Tuple[int, int]
154
+ ) -> Tuple["Chunk", AnyNDArray]:
155
+ chunk = self._init_decode(cx, cz, data)
156
+ floor_cy = self._get_floor_cy(data)
157
+ height_cy = (bounds[1] - bounds[0]) >> 4
158
+ self._do_decode(chunk, data, floor_cy, height_cy)
159
+ block_palette = chunk.misc.pop("block_palette")
160
+ return chunk, block_palette
161
+
162
+ def _get_floor_cy(self, data: ChunkDataType):
163
+ return 0
164
+
165
+ @staticmethod
166
+ def _init_decode(cx: int, cz: int, data: ChunkDataType) -> Chunk:
167
+ """Get the decode started by creating a chunk object."""
168
+ chunk = Chunk(cx, cz)
169
+ chunk.misc = {
170
+ # store the chunk data so that any non-versioned data can get saved back
171
+ "_java_chunk_data_layers": data
172
+ }
173
+ return chunk
174
+
175
+ def _get_level(self, data: ChunkDataType) -> CompoundTag:
176
+ """
177
+ Get the level data container
178
+ For older levels this is region:Level but newer worlds it is in region root
179
+ :param data: The raw chunk data
180
+ :return: The level data compound
181
+ """
182
+ return self.get_layer_obj(data, self.Level)
183
+
184
+ def _decode_coords(
185
+ self, chunk: Chunk, data: ChunkDataType, floor_cy: int, height_cy: int
186
+ ):
187
+ self.get_layer_obj(data, self.xPos, pop_last=True)
188
+ self.get_layer_obj(data, self.zPos, pop_last=True)
189
+
190
+ def _decode_last_update(
191
+ self, chunk: Chunk, data: ChunkDataType, floor_cy: int, height_cy: int
192
+ ):
193
+ chunk.misc["last_update"] = self.get_layer_obj(
194
+ data, self.LastUpdate, pop_last=True
195
+ ).py_int
196
+
197
+ def _decode_status(
198
+ self, chunk: Chunk, data: ChunkDataType, floor_cy: int, height_cy: int
199
+ ):
200
+ status = "empty"
201
+ if self.get_layer_obj(data, self.TerrainPopulated, pop_last=True):
202
+ status = "decorated"
203
+ if self.get_layer_obj(data, self.LightPopulated, pop_last=True):
204
+ status = "postprocessed"
205
+ chunk.status = status
206
+
207
+ def _decode_v_tag(
208
+ self, chunk: Chunk, data: ChunkDataType, floor_cy: int, height_cy: int
209
+ ):
210
+ chunk.misc["V"] = self.get_layer_obj(data, self.V, pop_last=True).py_int
211
+
212
+ def _decode_inhabited_time(
213
+ self, chunk: Chunk, data: ChunkDataType, floor_cy: int, height_cy: int
214
+ ):
215
+ chunk.misc["inhabited_time"] = self.get_layer_obj(
216
+ data, self.InhabitedTime, pop_last=True
217
+ ).py_int
218
+
219
+ def _decode_biomes(
220
+ self, chunk: Chunk, data: ChunkDataType, floor_cy: int, height_cy: int
221
+ ):
222
+ biomes = self.get_layer_obj(data, self.Biomes, pop_last=True)
223
+ if isinstance(biomes, AbstractBaseArrayTag) and biomes.np_array.size == 256:
224
+ chunk.biomes = biomes.np_array.astype(numpy.uint32).reshape((16, 16))
225
+
226
+ def _decode_height(
227
+ self, chunk: Chunk, data: ChunkDataType, floor_cy: int, height_cy: int
228
+ ):
229
+ height = self.get_layer_obj(data, self.HeightMap, pop_last=True).np_array
230
+ if isinstance(height, numpy.ndarray) and height.size == 256:
231
+ chunk.misc["height_map256IA"] = height.reshape((16, 16))
232
+
233
+ def _iter_sections(self, data: ChunkDataType) -> Iterator[Tuple[int, CompoundTag]]:
234
+ sections: ListTag = self.get_layer_obj(data, self.Sections)
235
+ for section in sections:
236
+ yield section["Y"].py_int, section
237
+
238
+ def _decode_blocks(
239
+ self, chunk: Chunk, data: ChunkDataType, floor_cy: int, height_cy: int
240
+ ):
241
+ blocks: Dict[int, SubChunkNDArray] = {}
242
+ palette = []
243
+ palette_len = 0
244
+ for cy, section in self._iter_sections(data):
245
+ block_tag = section.pop("Blocks", None)
246
+ data_tag = section.pop("Data", None)
247
+ if not isinstance(block_tag, AbstractBaseArrayTag) or not isinstance(
248
+ data_tag, AbstractBaseArrayTag
249
+ ):
250
+ continue
251
+ section_blocks = numpy.asarray(block_tag, dtype=numpy.uint8)
252
+ section_data = numpy.asarray(data_tag, dtype=numpy.uint8)
253
+ section_blocks = section_blocks.reshape((16, 16, 16))
254
+ section_blocks = section_blocks.astype(numpy.uint16)
255
+
256
+ section_data = world_utils.from_nibble_array(section_data)
257
+ section_data = section_data.reshape((16, 16, 16))
258
+
259
+ add_tag = section.pop("Add", None)
260
+ if isinstance(add_tag, AbstractBaseArrayTag):
261
+ add_blocks = numpy.asarray(add_tag, dtype=numpy.uint8)
262
+ add_blocks = world_utils.from_nibble_array(add_blocks)
263
+ add_blocks = add_blocks.reshape((16, 16, 16))
264
+
265
+ section_blocks |= add_blocks.astype(numpy.uint16) << 8
266
+ # TODO: fix this
267
+
268
+ (section_palette, blocks[cy]) = world_utils.fast_unique(
269
+ numpy.transpose(
270
+ (section_blocks << 4) + section_data, (2, 0, 1)
271
+ ) # YZX -> XYZ
272
+ )
273
+ blocks[cy] += palette_len
274
+ palette_len += len(section_palette)
275
+ palette.append(section_palette)
276
+
277
+ if palette:
278
+ final_palette, lut = numpy.unique(
279
+ numpy.concatenate(palette), return_inverse=True
280
+ )
281
+ final_palette: numpy.ndarray = numpy.array(
282
+ [final_palette >> 4, final_palette & 15]
283
+ ).T
284
+ for cy in blocks:
285
+ blocks[cy] = lut[blocks[cy]]
286
+ else:
287
+ final_palette = numpy.array([], dtype=object)
288
+ chunk.blocks = blocks
289
+ chunk.misc["block_palette"] = final_palette
290
+
291
+ def _unpack_light(
292
+ self, data: ChunkDataType, section_key: str
293
+ ) -> Dict[int, numpy.ndarray]:
294
+ light_container = {}
295
+ for cy, section in self._iter_sections(data):
296
+ if self.check_type(section, section_key, ByteArrayTag):
297
+ light: numpy.ndarray = section.pop(section_key).np_array
298
+ if light.size == 2048:
299
+ # TODO: check if this needs transposing or if the values are the other way around
300
+ light_container[cy] = (
301
+ (
302
+ light.reshape(-1, 1)
303
+ & numpy.array([0xF, 0xF0], dtype=numpy.uint8)
304
+ )
305
+ >> numpy.array([0, 4], dtype=numpy.uint8)
306
+ ).reshape((16, 16, 16))
307
+ return light_container
308
+
309
+ def _decode_block_light(
310
+ self, chunk: Chunk, data: ChunkDataType, floor_cy: int, height_cy: int
311
+ ):
312
+ chunk.misc["block_light"] = self._unpack_light(data, "BlockLight")
313
+
314
+ def _decode_sky_light(
315
+ self, chunk: Chunk, data: ChunkDataType, floor_cy: int, height_cy: int
316
+ ):
317
+ chunk.misc["sky_light"] = self._unpack_light(data, "SkyLight")
318
+
319
+ def _decode_entities(
320
+ self, chunk: Chunk, data: ChunkDataType, floor_cy: int, height_cy: int
321
+ ):
322
+ ents = self._decode_entity_list(
323
+ self.get_layer_obj(data, self.Entities, pop_last=True)
324
+ )
325
+ if amulet.entity_support:
326
+ chunk.entities = ents
327
+ else:
328
+ chunk._native_entities.extend(ents)
329
+ chunk._native_version = ("java", -1)
330
+
331
+ def _decode_block_entities(
332
+ self, chunk: Chunk, data: ChunkDataType, floor_cy: int, height_cy: int
333
+ ):
334
+ chunk.block_entities = self._decode_block_entity_list(
335
+ self.get_layer_obj(data, self.BlockEntities, pop_last=True)
336
+ )
337
+
338
+ @staticmethod
339
+ def _decode_ticks(ticks: ListTag) -> Dict[BlockCoordinates, Tuple[str, int, int]]:
340
+ return {
341
+ (tick["x"].py_data, tick["y"].py_data, tick["z"].py_data): (
342
+ tick["i"].py_data,
343
+ tick["t"].py_data,
344
+ tick["p"].py_data,
345
+ )
346
+ for tick in ticks
347
+ if all(c in tick and isinstance(tick[c], IntTag) for c in "xyztp")
348
+ and "i" in tick
349
+ and isinstance(tick["i"], StringTag)
350
+ }
351
+
352
+ def _decode_block_ticks(
353
+ self, chunk: Chunk, data: ChunkDataType, floor_cy: int, height_cy: int
354
+ ):
355
+ chunk.misc.setdefault("block_ticks", {}).update(
356
+ self._decode_ticks(self.get_layer_obj(data, self.BlockTicks, pop_last=True))
357
+ )
358
+
359
+ def encode(
360
+ self,
361
+ chunk: "Chunk",
362
+ palette: AnyNDArray,
363
+ max_world_version: Tuple[str, int],
364
+ bounds: Tuple[int, int],
365
+ ) -> ChunkDataType:
366
+ floor_cy = bounds[0] >> 4
367
+ height_cy = (bounds[1] - bounds[0]) >> 4
368
+ data = self._init_encode(chunk, max_world_version, floor_cy, height_cy)
369
+ chunk.misc["block_palette"] = palette
370
+ self._do_encode(chunk, data, floor_cy, height_cy)
371
+ return data
372
+
373
+ def _init_encode(
374
+ self,
375
+ chunk: "Chunk",
376
+ max_world_version: Tuple[str, int],
377
+ floor_cy: int,
378
+ height_cy: int,
379
+ ) -> ChunkDataType:
380
+ """Get or create the root data."""
381
+ data = chunk.misc.get("_java_chunk_data_layers", None)
382
+ if not isinstance(data, dict):
383
+ data = {}
384
+ return {
385
+ key: value
386
+ for key, value in data.items()
387
+ if isinstance(key, str) and isinstance(value, NamedTag)
388
+ }
389
+
390
+ def _get_encode_sections(
391
+ self,
392
+ data: ChunkDataType,
393
+ floor_cy: int,
394
+ height_cy: int,
395
+ ) -> Dict[int, CompoundTag]:
396
+ """Get or create the section array populating all valid sections"""
397
+ sections: ListTag = self.set_layer_obj(data, self.Sections, setdefault=True)
398
+ section_map: Dict[int, CompoundTag] = {}
399
+ section: CompoundTag
400
+ for section_index in range(len(sections) - 1, -1, -1):
401
+ section = sections[section_index]
402
+ cy = section.get("Y", None)
403
+ if isinstance(cy, ByteTag):
404
+ section_map[cy.py_int] = section
405
+ else:
406
+ sections.pop(section_index)
407
+ for cy in range(floor_cy, floor_cy + height_cy):
408
+ if cy not in section_map:
409
+ section = section_map[cy] = CompoundTag({"Y": ByteTag(cy)})
410
+ sections.append(section)
411
+ return section_map
412
+
413
+ def _encode_block_section(
414
+ self,
415
+ chunk: Chunk,
416
+ sections: Dict[int, CompoundTag],
417
+ palette: AnyNDArray,
418
+ cy: int,
419
+ ):
420
+ block_sub_array = palette[
421
+ numpy.transpose(
422
+ chunk.blocks.get_sub_chunk(cy), (1, 2, 0)
423
+ ).ravel() # XYZ -> YZX
424
+ ]
425
+
426
+ data_sub_array = block_sub_array[:, 1]
427
+ block_sub_array = block_sub_array[:, 0]
428
+ # if not numpy.any(block_sub_array) and not numpy.any(data_sub_array):
429
+ # return False
430
+ sections[cy]["Blocks"] = ByteArrayTag(block_sub_array.astype("uint8"))
431
+ sections[cy]["Data"] = ByteArrayTag(world_utils.to_nibble_array(data_sub_array))
432
+
433
+ def _encode_blocks(
434
+ self, chunk: Chunk, data: ChunkDataType, floor_cy: int, height_cy: int
435
+ ):
436
+ sections = self._get_encode_sections(data, floor_cy, height_cy)
437
+ block_palette = chunk.misc.pop("block_palette")
438
+ ceil_cy = floor_cy + height_cy
439
+ for cy in chunk.blocks.sub_chunks:
440
+ if floor_cy <= cy < ceil_cy:
441
+ self._encode_block_section(chunk, sections, block_palette, cy)
442
+
443
+ def _encode_coords(
444
+ self, chunk: Chunk, data: ChunkDataType, floor_cy: int, height_cy: int
445
+ ):
446
+ self.set_layer_obj(data, self.xPos, IntTag(chunk.cx))
447
+ self.set_layer_obj(data, self.zPos, IntTag(chunk.cz))
448
+
449
+ def _encode_last_update(
450
+ self, chunk: Chunk, data: ChunkDataType, floor_cy: int, height_cy: int
451
+ ):
452
+ self.set_layer_obj(
453
+ data, self.LastUpdate, LongTag(chunk.misc.get("last_update", 0))
454
+ )
455
+
456
+ def _encode_status(
457
+ self, chunk: Chunk, data: ChunkDataType, floor_cy: int, height_cy: int
458
+ ):
459
+ status = chunk.status.as_type(StatusFormats.Raw)
460
+ self.set_layer_obj(data, self.TerrainPopulated, ByteTag(int(status > -0.3)))
461
+ self.set_layer_obj(data, self.LightPopulated, ByteTag(int(status > -0.2)))
462
+
463
+ def _encode_v_tag(
464
+ self, chunk: Chunk, data: ChunkDataType, floor_cy: int, height_cy: int
465
+ ):
466
+ self.set_layer_obj(data, self.V, ByteTag(chunk.misc.get("V", 1)))
467
+
468
+ def _encode_inhabited_time(
469
+ self, chunk: Chunk, data: ChunkDataType, floor_cy: int, height_cy: int
470
+ ):
471
+ self.set_layer_obj(
472
+ data, self.InhabitedTime, LongTag(chunk.misc.get("inhabited_time", 0))
473
+ )
474
+
475
+ def _encode_biomes(
476
+ self, chunk: Chunk, data: ChunkDataType, floor_cy: int, height_cy: int
477
+ ):
478
+ chunk.biomes.convert_to_2d()
479
+ self.set_layer_obj(
480
+ data,
481
+ self.Biomes,
482
+ ByteArrayTag(chunk.biomes.astype(dtype=numpy.uint8).ravel()),
483
+ )
484
+
485
+ def _encode_height(
486
+ self, chunk: Chunk, data: ChunkDataType, floor_cy: int, height_cy: int
487
+ ):
488
+ height = chunk.misc.get("height_map256IA", None)
489
+ if (
490
+ isinstance(height, numpy.ndarray)
491
+ and numpy.issubdtype(height.dtype, numpy.integer)
492
+ and height.shape == (16, 16)
493
+ ):
494
+ self.set_layer_obj(
495
+ data,
496
+ self.HeightMap,
497
+ IntArrayTag(numpy.zeros(256, dtype=numpy.uint32)),
498
+ )
499
+ elif self._features["height_map"] == "256IARequired":
500
+ self.set_layer_obj(data, self.HeightMap, IntArrayTag(height.ravel()))
501
+
502
+ def _encode_entities(
503
+ self, chunk: Chunk, data: ChunkDataType, floor_cy: int, height_cy: int
504
+ ):
505
+ if amulet.entity_support:
506
+ entities = chunk.entities
507
+ else:
508
+ entities = chunk._native_entities
509
+
510
+ self.set_layer_obj(
511
+ data,
512
+ self.Entities,
513
+ self._encode_entity_list(entities),
514
+ )
515
+
516
+ def _encode_block_entities(
517
+ self, chunk: Chunk, data: ChunkDataType, floor_cy: int, height_cy: int
518
+ ):
519
+ self.set_layer_obj(
520
+ data,
521
+ self.BlockEntities,
522
+ self._encode_block_entity_list(chunk.block_entities),
523
+ )
524
+
525
+ @staticmethod
526
+ def _encode_ticks(ticks: Dict[BlockCoordinates, Tuple[str, int, int]]) -> ListTag:
527
+ ticks_out = ListTag()
528
+ if isinstance(ticks, dict):
529
+ for k, v in ticks.items():
530
+ try:
531
+ (x, y, z), (i, t, p) = k, v
532
+ ticks_out.append(
533
+ CompoundTag(
534
+ {
535
+ "i": StringTag(i),
536
+ "p": IntTag(p),
537
+ "t": IntTag(t),
538
+ "x": IntTag(x),
539
+ "y": IntTag(y),
540
+ "z": IntTag(x),
541
+ }
542
+ )
543
+ )
544
+ except Exception:
545
+ log.error(f"Could not serialise tick data {k}: {v}")
546
+ return ticks_out
547
+
548
+ def _encode_block_ticks(
549
+ self, chunk: Chunk, data: ChunkDataType, floor_cy: int, height_cy: int
550
+ ):
551
+ self.set_layer_obj(
552
+ data, self.BlockTicks, self._encode_ticks(chunk.misc.get("block_ticks", {}))
553
+ )
554
+
555
+ def _pack_light(
556
+ self,
557
+ chunk: Chunk,
558
+ data: ChunkDataType,
559
+ floor_cy: int,
560
+ height_cy: int,
561
+ feature_key: str,
562
+ section_key: str,
563
+ ):
564
+ light_container = chunk.misc.get(feature_key, {})
565
+ if not isinstance(light_container, dict):
566
+ light_container = {}
567
+ for cy, section in self._get_encode_sections(data, floor_cy, height_cy).items():
568
+ light = light_container.get(cy, None)
569
+ if (
570
+ isinstance(light, numpy.ndarray)
571
+ and numpy.issubdtype(light.dtype, numpy.integer)
572
+ and light.shape == (16, 16, 16)
573
+ ):
574
+ light = light.ravel() % 16
575
+ section[section_key] = ByteArrayTag(light[::2] + (light[1::2] << 4))
576
+ elif self._features["light_optional"] == "false":
577
+ section[section_key] = ByteArrayTag(
578
+ numpy.full(2048, 255, dtype=numpy.uint8)
579
+ )
580
+
581
+ def _encode_block_light(
582
+ self, chunk: Chunk, data: ChunkDataType, floor_cy: int, height_cy: int
583
+ ):
584
+ self._pack_light(chunk, data, floor_cy, height_cy, "block_light", "BlockLight")
585
+
586
+ def _encode_sky_light(
587
+ self, chunk: Chunk, data: ChunkDataType, floor_cy: int, height_cy: int
588
+ ):
589
+ self._pack_light(chunk, data, floor_cy, height_cy, "sky_light", "SkyLight")
590
+
591
+ def _post_encode_sections(
592
+ self, chunk: Chunk, data: ChunkDataType, floor_cy: int, height_cy: int
593
+ ):
594
+ """Strip out all empty sections"""
595
+ sections = self.get_layer_obj(data, self.Sections)
596
+ if sections:
597
+ for i in range(len(sections) - 1, -1, -1):
598
+ section = sections[i]
599
+ if "Blocks" not in section or "Data" not in section:
600
+ # in 1.12 if a section exists, Blocks and Data must exist
601
+ sections.pop(i)
602
+ if not sections:
603
+ # if no sections remain we can remove the sections data
604
+ self.get_layer_obj(data, self.Sections, pop_last=True)
605
+
606
+
607
+ export = AnvilNAInterface