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,419 +1,385 @@
1
- from __future__ import annotations
2
-
3
- import struct
4
- from typing import (
5
- Dict,
6
- Set,
7
- Optional,
8
- List,
9
- TYPE_CHECKING,
10
- Tuple,
11
- Union,
12
- )
13
- from threading import RLock
14
- import logging
15
- from contextlib import suppress
16
-
17
- from amulet_nbt import (
18
- NamedTag,
19
- LongTag,
20
- StringTag,
21
- CompoundTag,
22
- NBTLoadError,
23
- load as load_nbt,
24
- utf8_escape_decoder,
25
- utf8_escape_encoder,
26
- )
27
-
28
- from amulet.api.errors import ChunkDoesNotExist, DimensionDoesNotExist
29
- from amulet.api.data_types import ChunkCoordinates
30
- from leveldb import LevelDB
31
- from .chunk import ChunkData
32
-
33
- if TYPE_CHECKING:
34
- from amulet.api.data_types import Dimension
35
- from .format import LevelDBFormat
36
-
37
- log = logging.getLogger(__name__)
38
-
39
- InternalDimension = Optional[int]
40
- OVERWORLD = "minecraft:overworld"
41
- THE_NETHER = "minecraft:the_nether"
42
- THE_END = "minecraft:the_end"
43
-
44
-
45
- class ActorCounter:
46
- _lock: RLock
47
- _session: int
48
- _count: int
49
-
50
- def __init__(self):
51
- self._lock = RLock()
52
- self._session = -1
53
- self._count = 0
54
-
55
- @classmethod
56
- def from_level(cls, level: LevelDBFormat):
57
- session = level.root_tag.compound.get_long(
58
- "worldStartCount", LongTag(0xFFFFFFFF)
59
- ).py_int
60
- # for some reason this is a signed int stored in a signed long. Manually apply the sign correctly
61
- session -= (session & 0x80000000) << 1
62
-
63
- # create the counter object and set the session
64
- counter = ActorCounter()
65
- counter._session = session
66
-
67
- # increment and write back so there are no conflicts
68
- session -= 1
69
- if session < 0:
70
- session += 0x100000000
71
- level.root_tag.compound["worldStartCount"] = LongTag(session)
72
- level.root_tag.save()
73
-
74
- return counter
75
-
76
- def next(self) -> Tuple[int, int]:
77
- """
78
- Get the next unique session id and actor counter.
79
- Session id is usually negative
80
-
81
- :return: Tuple[session id, actor id]
82
- """
83
- with self._lock:
84
- count = self._count
85
- self._count += 1
86
- return self._session, count
87
-
88
-
89
- class LevelDBDimensionManager:
90
- # tag_ids = {45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 118}
91
-
92
- # A borrowed reference to the leveldb
93
- _db: LevelDB
94
- # A class to keep track of unique actor ids
95
- _actor_counter: Optional[ActorCounter]
96
-
97
- def __init__(self, level: LevelDBFormat):
98
- """
99
- :param level: The leveldb format to read data from
100
- """
101
- self._db = level.level_db
102
- self._actor_counter = ActorCounter.from_level(level)
103
- # self._levels format Dict[level, Dict[Tuple[cx, cz], List[Tuple[full_key, key_extension]]]]
104
- self._levels: Dict[InternalDimension, Set[ChunkCoordinates]] = {}
105
- self._dimension_name_map: Dict["Dimension", InternalDimension] = {}
106
- self._lock = RLock()
107
-
108
- self.register_dimension(None, OVERWORLD)
109
- self.register_dimension(1, THE_NETHER)
110
- self.register_dimension(2, THE_END)
111
-
112
- for key in self._db.keys():
113
- if 9 <= len(key) <= 10 and key[8] in [44, 118]: # "," "v"
114
- self._add_chunk(key)
115
-
116
- elif 13 <= len(key) <= 14 and key[12] in [44, 118]: # "," "v"
117
- self._add_chunk(key, has_level=True)
118
-
119
- @property
120
- def dimensions(self) -> List["Dimension"]:
121
- """A list of all the levels contained in the world"""
122
- return list(self._dimension_name_map.keys())
123
-
124
- def register_dimension(
125
- self,
126
- dimension_internal: InternalDimension,
127
- dimension_name: Optional["Dimension"] = None,
128
- ):
129
- """
130
- Register a new dimension.
131
-
132
- :param dimension_internal: The internal representation of the dimension
133
- :param dimension_name: The name of the dimension shown to the user
134
- :return:
135
- """
136
- if dimension_name is None:
137
- dimension_name: "Dimension" = f"DIM{dimension_internal}"
138
-
139
- with self._lock:
140
- if (
141
- dimension_internal not in self._levels
142
- and dimension_name not in self._dimension_name_map
143
- ):
144
- self._levels[dimension_internal] = set()
145
- self._dimension_name_map[dimension_name] = dimension_internal
146
-
147
- def _get_internal_dimension(self, dimension: "Dimension") -> InternalDimension:
148
- if dimension in self._dimension_name_map:
149
- return self._dimension_name_map[dimension]
150
- else:
151
- raise DimensionDoesNotExist(dimension)
152
-
153
- def all_chunk_coords(self, dimension: "Dimension") -> Set[ChunkCoordinates]:
154
- internal_dimension = self._get_internal_dimension(dimension)
155
- if internal_dimension in self._levels:
156
- return self._levels[internal_dimension]
157
- else:
158
- return set()
159
-
160
- @staticmethod
161
- def _get_key(cx: int, cz: int, internal_dimension: InternalDimension) -> bytes:
162
- if internal_dimension is None:
163
- return struct.pack("<ii", cx, cz)
164
- else:
165
- return struct.pack("<iii", cx, cz, internal_dimension)
166
-
167
- def _has_chunk(
168
- self, cx: int, cz: int, internal_dimension: InternalDimension
169
- ) -> bool:
170
- return (
171
- internal_dimension in self._levels
172
- and (cx, cz) in self._levels[internal_dimension]
173
- )
174
-
175
- def has_chunk(self, cx: int, cz: int, dimension: Dimension) -> bool:
176
- return self._has_chunk(cx, cz, self._get_internal_dimension(dimension))
177
-
178
- def _add_chunk(self, key_: bytes, has_level: bool = False):
179
- if has_level:
180
- cx, cz, level = struct.unpack("<iii", key_[:12])
181
- else:
182
- cx, cz = struct.unpack("<ii", key_[:8])
183
- level = None
184
- if level not in self._levels:
185
- self.register_dimension(level)
186
- self._levels[level].add((cx, cz))
187
-
188
- def get_chunk_data(self, cx: int, cz: int, dimension: "Dimension") -> ChunkData:
189
- """Get a dictionary of chunk key extension in bytes to the raw data in the key.
190
- chunk key extension are the character(s) after <cx><cz>[level] in the key
191
- Will raise ChunkDoesNotExist if the chunk does not exist
192
- """
193
- internal_dimension = self._get_internal_dimension(dimension)
194
- if self._has_chunk(cx, cz, internal_dimension):
195
- prefix = self._get_key(cx, cz, internal_dimension)
196
- prefix_len = len(prefix)
197
- iter_end = prefix + b"\xff\xff\xff\xff"
198
-
199
- chunk_data = ChunkData()
200
- for key, val in self._db.iterate(prefix, iter_end):
201
- if key[:prefix_len] == prefix and len(key) <= prefix_len + 2:
202
- chunk_data[key[prefix_len:]] = val
203
-
204
- with suppress(KeyError):
205
- digp_key = b"digp" + prefix
206
- digp = self._db.get(digp_key)
207
- chunk_data[
208
- b"digp"
209
- ] = b"" # The presence of this key signals to the put method that this should be created and written
210
- for i in range(0, (len(digp) // 8) * 8, 8):
211
- actor_key = b"actorprefix" + digp[i : i + 8]
212
- try:
213
- actor_bytes = self._db.get(actor_key)
214
- actor = load_nbt(
215
- actor_bytes,
216
- little_endian=True,
217
- string_decoder=utf8_escape_decoder,
218
- )
219
- actor_tag = actor.compound
220
- except KeyError:
221
- log.error(f"Could not find actor {actor_key}. Skipping.")
222
- except NBTLoadError:
223
- log.error(f"Failed to parse actor {actor_key}. Skipping.")
224
- else:
225
- actor_tag.pop("UniqueID", None)
226
- internal_components = actor_tag.setdefault(
227
- "internalComponents",
228
- CompoundTag(
229
- EntityStorageKeyComponent=CompoundTag(
230
- StorageKey=StringTag()
231
- )
232
- ),
233
- ) # 717
234
- if (
235
- isinstance(internal_components, CompoundTag)
236
- and internal_components
237
- ):
238
- if "EntityStorageKeyComponent" in internal_components:
239
- # it is an entity
240
- entity_component = internal_components[
241
- "EntityStorageKeyComponent"
242
- ]
243
- if isinstance(entity_component, CompoundTag):
244
- # delete the storage key component
245
- if isinstance(
246
- entity_component.get("StorageKey"), StringTag
247
- ):
248
- del entity_component["StorageKey"]
249
- # if there is no other data then delete internalComponents
250
- if (
251
- len(entity_component) == 0
252
- and len(internal_components) == 1
253
- ):
254
- del actor_tag["internalComponents"]
255
- else:
256
- log.warning(
257
- f"Extra components found {repr(entity_component)}"
258
- )
259
- else:
260
- log.warning(
261
- f"Unrecognised EntityStorageKeyComponent type {repr(entity_component)}"
262
- )
263
-
264
- chunk_data.entity_actor.append(actor)
265
- else:
266
- # it is an unknown actor
267
- log.warning(
268
- f"Actor {actor_key} has an unknown format. Please report this to a developer {repr(internal_components)}"
269
- )
270
- for k, v in internal_components.items():
271
- if isinstance(v, CompoundTag) and isinstance(
272
- v.get("StorageKey"), StringTag
273
- ):
274
- v["StorageKey"] = StringTag()
275
- chunk_data.unknown_actor.append(actor)
276
- else:
277
- log.error(
278
- f"internalComponents was not valid for actor {actor_key}. Skipping."
279
- )
280
- continue
281
-
282
- return chunk_data
283
- else:
284
- raise ChunkDoesNotExist
285
-
286
- def put_chunk_data(
287
- self,
288
- cx: int,
289
- cz: int,
290
- chunk_data: ChunkData,
291
- dimension: "Dimension",
292
- ):
293
- """pass data to the region file class"""
294
- # get the region key
295
- internal_dimension = self._get_internal_dimension(dimension)
296
- self._levels[internal_dimension].add((cx, cz))
297
- key_prefix = self._get_key(cx, cz, internal_dimension)
298
-
299
- batch = {}
300
-
301
- if b"digp" in chunk_data:
302
- # if writing the digp key we need to delete all actors pointed to by the old digp key otherwise there will be memory leaks
303
- digp_key = b"digp" + key_prefix
304
- try:
305
- old_digp = self._db.get(digp_key)
306
- except KeyError:
307
- pass
308
- else:
309
- for i in range(0, len(old_digp) // 8 * 8, 8):
310
- actor_key = b"actorprefix" + old_digp[i : i + 8]
311
- self._db.delete(actor_key)
312
-
313
- digp = []
314
-
315
- def add_actor(actor: NamedTag, is_entity: bool):
316
- if not (
317
- isinstance(actor, NamedTag) and isinstance(actor.tag, CompoundTag)
318
- ):
319
- log.error(f"Actor must be a NamedTag[Compound]")
320
- return
321
- actor_tag = actor.compound
322
- internal_components = actor_tag.setdefault(
323
- "internalComponents", CompoundTag()
324
- )
325
- if not isinstance(internal_components, CompoundTag):
326
- log.error(
327
- f"Invalid internalComponents value. Must be Compound. Skipping. {repr(internal_components)}"
328
- )
329
- return
330
-
331
- if is_entity:
332
- entity_storage = internal_components.setdefault(
333
- "EntityStorageKeyComponent", CompoundTag()
334
- )
335
- if not isinstance(entity_storage, CompoundTag):
336
- log.error(
337
- f"Invalid EntityStorageKeyComponent value. Must be Compound. Skipping. {repr(entity_storage)}"
338
- )
339
- return
340
- storages = [entity_storage]
341
- else:
342
- storages = []
343
- for storage in internal_components.values():
344
- if (
345
- isinstance(storage, CompoundTag)
346
- and storage.get("StorageKey") == StringTag()
347
- ):
348
- storages.append(storage)
349
- if not storages:
350
- log.error(
351
- f"No valid StorageKeyComponent to write in for unknown actor {internal_components}"
352
- )
353
- return
354
-
355
- session, uid = self._actor_counter.next()
356
- # session is already negative
357
- key = struct.pack(">ii", -session, uid)
358
- # b'\x00\x00\x00\x01\x00\x00\x00\x0c' 1, 12
359
- for storage in storages:
360
- storage["StorageKey"] = StringTag(utf8_escape_decoder(key))
361
- # -4294967284 ">q" b'\xff\xff\xff\xff\x00\x00\x00\x0c' ">ii" -1, 12
362
- actor_tag["UniqueID"] = LongTag(
363
- struct.unpack(">q", struct.pack(">ii", session, uid))[0]
364
- )
365
-
366
- batch[b"actorprefix" + key] = actor.save_to(
367
- little_endian=True,
368
- compressed=False,
369
- string_encoder=utf8_escape_encoder,
370
- )
371
- digp.append(key)
372
-
373
- for actor_ in chunk_data.entity_actor:
374
- add_actor(actor_, True)
375
-
376
- for actor_ in chunk_data.unknown_actor:
377
- add_actor(actor_, False)
378
-
379
- del chunk_data[b"digp"]
380
- batch[digp_key] = b"".join(digp)
381
-
382
- for key, val in chunk_data.items():
383
- key = key_prefix + key
384
- if val is None:
385
- self._db.delete(key)
386
- else:
387
- batch[key] = val
388
- if batch:
389
- self._db.putBatch(batch)
390
-
391
- def delete_chunk(self, cx: int, cz: int, dimension: "Dimension"):
392
- if dimension not in self._dimension_name_map:
393
- return # dimension does not exists so chunk cannot
394
-
395
- internal_dimension = self._dimension_name_map[dimension]
396
- if not self._has_chunk(cx, cz, internal_dimension):
397
- return # chunk does not exists
398
-
399
- prefix = self._get_key(cx, cz, internal_dimension)
400
- prefix_len = len(prefix)
401
- iter_end = prefix + b"\xff\xff\xff\xff"
402
- keys = []
403
- for key, _ in self._db.iterate(prefix, iter_end):
404
- if key[:prefix_len] == prefix and len(key) <= prefix_len + 2:
405
- keys.append(key)
406
-
407
- try:
408
- digp = self._db.get(b"digp" + prefix)
409
- except KeyError:
410
- pass
411
- else:
412
- self._db.delete(b"digp" + prefix)
413
- for i in range(0, len(digp) // 8 * 8, 8):
414
- actor_key = b"actorprefix" + digp[i : i + 8]
415
- self._db.delete(actor_key)
416
-
417
- self._levels[internal_dimension].remove((cx, cz))
418
- for key in keys:
419
- self._db.delete(key)
1
+ from __future__ import annotations
2
+
3
+ import struct
4
+ from typing import (
5
+ Dict,
6
+ Set,
7
+ Optional,
8
+ List,
9
+ TYPE_CHECKING,
10
+ Tuple,
11
+ )
12
+ from threading import RLock
13
+ import logging
14
+ from contextlib import suppress
15
+
16
+ from amulet_nbt import (
17
+ NamedTag,
18
+ LongTag,
19
+ StringTag,
20
+ CompoundTag,
21
+ NBTLoadError,
22
+ load as load_nbt,
23
+ utf8_escape_decoder,
24
+ utf8_escape_encoder,
25
+ )
26
+
27
+ from amulet.api.errors import ChunkDoesNotExist
28
+ from amulet.api.data_types import ChunkCoordinates
29
+ from leveldb import LevelDB
30
+ from .chunk import ChunkData
31
+
32
+ if TYPE_CHECKING:
33
+ from .format import LevelDBFormat
34
+
35
+ log = logging.getLogger(__name__)
36
+
37
+ InternalDimension = Optional[int]
38
+
39
+
40
+ class ActorCounter:
41
+ _lock: RLock
42
+ _session: int
43
+ _count: int
44
+
45
+ def __init__(self):
46
+ self._lock = RLock()
47
+ self._session = -1
48
+ self._count = 0
49
+
50
+ @classmethod
51
+ def from_level(cls, level: LevelDBFormat):
52
+ session = level.root_tag.compound.get_long(
53
+ "worldStartCount", LongTag(0xFFFFFFFF)
54
+ ).py_int
55
+ # for some reason this is a signed int stored in a signed long. Manually apply the sign correctly
56
+ session -= (session & 0x80000000) << 1
57
+
58
+ # create the counter object and set the session
59
+ counter = ActorCounter()
60
+ counter._session = session
61
+
62
+ # increment and write back so there are no conflicts
63
+ session -= 1
64
+ if session < 0:
65
+ session += 0x100000000
66
+ level.root_tag.compound["worldStartCount"] = LongTag(session)
67
+ level.root_tag.save()
68
+
69
+ return counter
70
+
71
+ def next(self) -> Tuple[int, int]:
72
+ """
73
+ Get the next unique session id and actor counter.
74
+ Session id is usually negative
75
+
76
+ :return: Tuple[session id, actor id]
77
+ """
78
+ with self._lock:
79
+ count = self._count
80
+ self._count += 1
81
+ return self._session, count
82
+
83
+
84
+ class LevelDBDimensionManager:
85
+ # tag_ids = {45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 118}
86
+
87
+ # A borrowed reference to the leveldb
88
+ _db: LevelDB
89
+ # A class to keep track of unique actor ids
90
+ _actor_counter: Optional[ActorCounter]
91
+
92
+ def __init__(self, level: LevelDBFormat):
93
+ """
94
+ :param level: The leveldb format to read data from
95
+ """
96
+ self._db = level.level_db
97
+ self._actor_counter = ActorCounter.from_level(level)
98
+ # self._levels format Dict[level, Dict[Tuple[cx, cz], List[Tuple[full_key, key_extension]]]]
99
+ self._levels: Dict[InternalDimension, Set[ChunkCoordinates]] = {}
100
+ self._lock = RLock()
101
+
102
+ self.register_dimension(None) # overworld
103
+ self.register_dimension(1) # the nether
104
+ self.register_dimension(2) # the end
105
+
106
+ for key in self._db.keys():
107
+ if 9 <= len(key) <= 10 and key[8] in [44, 118]: # "," "v"
108
+ self._add_chunk(key)
109
+
110
+ elif 13 <= len(key) <= 14 and key[12] in [44, 118]: # "," "v"
111
+ self._add_chunk(key, has_level=True)
112
+
113
+ @property
114
+ def dimensions(self) -> List[InternalDimension]:
115
+ """A list of all the levels contained in the world"""
116
+ return list(self._levels)
117
+
118
+ def register_dimension(self, dimension: InternalDimension):
119
+ """
120
+ Register a new dimension.
121
+
122
+ :param dimension: The internal representation of the dimension
123
+ :return:
124
+ """
125
+ with self._lock:
126
+ if dimension not in self._levels:
127
+ self._levels[dimension] = set()
128
+
129
+ def all_chunk_coords(self, dimension: InternalDimension) -> Set[ChunkCoordinates]:
130
+ if dimension in self._levels:
131
+ return self._levels[dimension]
132
+ else:
133
+ return set()
134
+
135
+ @staticmethod
136
+ def _get_key(cx: int, cz: int, dimension: InternalDimension) -> bytes:
137
+ if dimension is None:
138
+ return struct.pack("<ii", cx, cz)
139
+ else:
140
+ return struct.pack("<iii", cx, cz, dimension)
141
+
142
+ def has_chunk(self, cx: int, cz: int, dimension: InternalDimension) -> bool:
143
+ return dimension in self._levels and (cx, cz) in self._levels[dimension]
144
+
145
+ def _add_chunk(self, key_: bytes, has_level: bool = False):
146
+ if has_level:
147
+ cx, cz, level = struct.unpack("<iii", key_[:12])
148
+ else:
149
+ cx, cz = struct.unpack("<ii", key_[:8])
150
+ level = None
151
+ if level not in self._levels:
152
+ self.register_dimension(level)
153
+ self._levels[level].add((cx, cz))
154
+
155
+ def get_chunk_data(
156
+ self, cx: int, cz: int, dimension: InternalDimension
157
+ ) -> ChunkData:
158
+ """Get a dictionary of chunk key extension in bytes to the raw data in the key.
159
+ chunk key extension are the character(s) after <cx><cz>[level] in the key
160
+ Will raise ChunkDoesNotExist if the chunk does not exist
161
+ """
162
+ if self.has_chunk(cx, cz, dimension):
163
+ prefix = self._get_key(cx, cz, dimension)
164
+ prefix_len = len(prefix)
165
+ iter_end = prefix + b"\xff\xff\xff\xff"
166
+
167
+ chunk_data = ChunkData()
168
+ for key, val in self._db.iterate(prefix, iter_end):
169
+ if key[:prefix_len] == prefix and len(key) <= prefix_len + 2:
170
+ chunk_data[key[prefix_len:]] = val
171
+
172
+ with suppress(KeyError):
173
+ digp_key = b"digp" + prefix
174
+ digp = self._db.get(digp_key)
175
+ chunk_data[
176
+ b"digp"
177
+ ] = b"" # The presence of this key signals to the put method that this should be created and written
178
+ for i in range(0, (len(digp) // 8) * 8, 8):
179
+ actor_key = b"actorprefix" + digp[i : i + 8]
180
+ try:
181
+ actor_bytes = self._db.get(actor_key)
182
+ actor = load_nbt(
183
+ actor_bytes,
184
+ little_endian=True,
185
+ string_decoder=utf8_escape_decoder,
186
+ )
187
+ actor_tag = actor.compound
188
+ except KeyError:
189
+ log.error(f"Could not find actor {actor_key}. Skipping.")
190
+ except NBTLoadError:
191
+ log.error(f"Failed to parse actor {actor_key}. Skipping.")
192
+ else:
193
+ actor_tag.pop("UniqueID", None)
194
+ internal_components = actor_tag.setdefault(
195
+ "internalComponents",
196
+ CompoundTag(
197
+ EntityStorageKeyComponent=CompoundTag(
198
+ StorageKey=StringTag()
199
+ )
200
+ ),
201
+ ) # 717
202
+ if (
203
+ isinstance(internal_components, CompoundTag)
204
+ and internal_components
205
+ ):
206
+ if "EntityStorageKeyComponent" in internal_components:
207
+ # it is an entity
208
+ entity_component = internal_components[
209
+ "EntityStorageKeyComponent"
210
+ ]
211
+ if isinstance(entity_component, CompoundTag):
212
+ # delete the storage key component
213
+ if isinstance(
214
+ entity_component.get("StorageKey"), StringTag
215
+ ):
216
+ del entity_component["StorageKey"]
217
+ # if there is no other data then delete internalComponents
218
+ if (
219
+ len(entity_component) == 0
220
+ and len(internal_components) == 1
221
+ ):
222
+ del actor_tag["internalComponents"]
223
+ else:
224
+ log.warning(
225
+ f"Extra components found {repr(entity_component)}"
226
+ )
227
+ else:
228
+ log.warning(
229
+ f"Unrecognised EntityStorageKeyComponent type {repr(entity_component)}"
230
+ )
231
+
232
+ chunk_data.entity_actor.append(actor)
233
+ else:
234
+ # it is an unknown actor
235
+ log.warning(
236
+ f"Actor {actor_key} has an unknown format. Please report this to a developer {repr(internal_components)}"
237
+ )
238
+ for k, v in internal_components.items():
239
+ if isinstance(v, CompoundTag) and isinstance(
240
+ v.get("StorageKey"), StringTag
241
+ ):
242
+ v["StorageKey"] = StringTag()
243
+ chunk_data.unknown_actor.append(actor)
244
+ else:
245
+ log.error(
246
+ f"internalComponents was not valid for actor {actor_key}. Skipping."
247
+ )
248
+ continue
249
+
250
+ return chunk_data
251
+ else:
252
+ raise ChunkDoesNotExist
253
+
254
+ def put_chunk_data(
255
+ self,
256
+ cx: int,
257
+ cz: int,
258
+ chunk_data: ChunkData,
259
+ dimension: InternalDimension,
260
+ ):
261
+ """pass data to the region file class"""
262
+ # get the region key
263
+ self._levels[dimension].add((cx, cz))
264
+ key_prefix = self._get_key(cx, cz, dimension)
265
+
266
+ batch = {}
267
+
268
+ if b"digp" in chunk_data:
269
+ # if writing the digp key we need to delete all actors pointed to by the old digp key otherwise there will be memory leaks
270
+ digp_key = b"digp" + key_prefix
271
+ try:
272
+ old_digp = self._db.get(digp_key)
273
+ except KeyError:
274
+ pass
275
+ else:
276
+ for i in range(0, len(old_digp) // 8 * 8, 8):
277
+ actor_key = b"actorprefix" + old_digp[i : i + 8]
278
+ self._db.delete(actor_key)
279
+
280
+ digp = []
281
+
282
+ def add_actor(actor: NamedTag, is_entity: bool):
283
+ if not (
284
+ isinstance(actor, NamedTag) and isinstance(actor.tag, CompoundTag)
285
+ ):
286
+ log.error(f"Actor must be a NamedTag[Compound]")
287
+ return
288
+ actor_tag = actor.compound
289
+ internal_components = actor_tag.setdefault(
290
+ "internalComponents", CompoundTag()
291
+ )
292
+ if not isinstance(internal_components, CompoundTag):
293
+ log.error(
294
+ f"Invalid internalComponents value. Must be Compound. Skipping. {repr(internal_components)}"
295
+ )
296
+ return
297
+
298
+ if is_entity:
299
+ entity_storage = internal_components.setdefault(
300
+ "EntityStorageKeyComponent", CompoundTag()
301
+ )
302
+ if not isinstance(entity_storage, CompoundTag):
303
+ log.error(
304
+ f"Invalid EntityStorageKeyComponent value. Must be Compound. Skipping. {repr(entity_storage)}"
305
+ )
306
+ return
307
+ storages = [entity_storage]
308
+ else:
309
+ storages = []
310
+ for storage in internal_components.values():
311
+ if (
312
+ isinstance(storage, CompoundTag)
313
+ and storage.get("StorageKey") == StringTag()
314
+ ):
315
+ storages.append(storage)
316
+ if not storages:
317
+ log.error(
318
+ f"No valid StorageKeyComponent to write in for unknown actor {internal_components}"
319
+ )
320
+ return
321
+
322
+ session, uid = self._actor_counter.next()
323
+ # session is already negative
324
+ key = struct.pack(">ii", -session, uid)
325
+ # b'\x00\x00\x00\x01\x00\x00\x00\x0c' 1, 12
326
+ for storage in storages:
327
+ storage["StorageKey"] = StringTag(utf8_escape_decoder(key))
328
+ # -4294967284 ">q" b'\xff\xff\xff\xff\x00\x00\x00\x0c' ">ii" -1, 12
329
+ actor_tag["UniqueID"] = LongTag(
330
+ struct.unpack(">q", struct.pack(">ii", session, uid))[0]
331
+ )
332
+
333
+ batch[b"actorprefix" + key] = actor.save_to(
334
+ little_endian=True,
335
+ compressed=False,
336
+ string_encoder=utf8_escape_encoder,
337
+ )
338
+ digp.append(key)
339
+
340
+ for actor_ in chunk_data.entity_actor:
341
+ add_actor(actor_, True)
342
+
343
+ for actor_ in chunk_data.unknown_actor:
344
+ add_actor(actor_, False)
345
+
346
+ del chunk_data[b"digp"]
347
+ batch[digp_key] = b"".join(digp)
348
+
349
+ for key, val in chunk_data.items():
350
+ key = key_prefix + key
351
+ if val is None:
352
+ self._db.delete(key)
353
+ else:
354
+ batch[key] = val
355
+ if batch:
356
+ self._db.putBatch(batch)
357
+
358
+ def delete_chunk(self, cx: int, cz: int, dimension: InternalDimension):
359
+ if dimension not in self._levels:
360
+ return # dimension does not exists so chunk cannot
361
+
362
+ if not self.has_chunk(cx, cz, dimension):
363
+ return # chunk does not exists
364
+
365
+ prefix = self._get_key(cx, cz, dimension)
366
+ prefix_len = len(prefix)
367
+ iter_end = prefix + b"\xff\xff\xff\xff"
368
+ keys = []
369
+ for key, _ in self._db.iterate(prefix, iter_end):
370
+ if key[:prefix_len] == prefix and len(key) <= prefix_len + 2:
371
+ keys.append(key)
372
+
373
+ try:
374
+ digp = self._db.get(b"digp" + prefix)
375
+ except KeyError:
376
+ pass
377
+ else:
378
+ self._db.delete(b"digp" + prefix)
379
+ for i in range(0, len(digp) // 8 * 8, 8):
380
+ actor_key = b"actorprefix" + digp[i : i + 8]
381
+ self._db.delete(actor_key)
382
+
383
+ self._levels[dimension].remove((cx, cz))
384
+ for key in keys:
385
+ self._db.delete(key)