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,227 +1,227 @@
1
- from __future__ import annotations
2
- from typing import Optional, Tuple, Generator, Set, Iterable, Dict
3
- import weakref
4
-
5
- from amulet.api.data_types import DimensionCoordinates, Dimension
6
- from amulet.api.chunk import Chunk
7
- from amulet.api.history.data_types import EntryType, EntryKeyType
8
- from amulet.api.history.base import RevisionManager
9
- from amulet.api.history.revision_manager import DBRevisionManager
10
- from amulet.api.errors import ChunkDoesNotExist, ChunkLoadError
11
- from amulet.api.history.history_manager import DatabaseHistoryManager
12
- from amulet.api import level as api_level
13
- from leveldb import LevelDB
14
-
15
-
16
- class ChunkDBEntry(DBRevisionManager):
17
- __slots__ = ("_world", "_history_db")
18
-
19
- def __init__(
20
- self,
21
- world: api_level.BaseLevel,
22
- history_db: LevelDB,
23
- prefix: str,
24
- initial_state: EntryType,
25
- ):
26
- self._world = weakref.ref(world)
27
- self._history_db = weakref.ref(history_db)
28
- super().__init__(prefix, initial_state)
29
-
30
- @property
31
- def world(self) -> api_level.BaseLevel:
32
- return self._world()
33
-
34
- def _serialise(self, path: str, entry: Optional[Chunk]) -> Optional[str]:
35
- if entry is None:
36
- return None
37
- else:
38
- pickled_bytes = entry.pickle()
39
- self._history_db().put(path.encode("utf-8"), pickled_bytes)
40
- return path
41
-
42
- def _deserialise(self, path: Optional[str]) -> Optional[Chunk]:
43
- if path is None:
44
- return None
45
- else:
46
- pickled_bytes = self._history_db().get(path.encode("utf-8"))
47
- return Chunk.unpickle(
48
- pickled_bytes, self.world.block_palette, self.world.biome_palette
49
- )
50
-
51
-
52
- class ChunkManager(DatabaseHistoryManager):
53
- """
54
- The ChunkManager class is a class that handles chunks within a world.
55
-
56
- It handles the temporary database of chunks in RAM that can be directly modified.
57
-
58
- It handles a serialised version of the chunks on disk to reduce RAM usage.
59
-
60
- It also contains a history manager to allow undoing and redoing of changes.
61
- """
62
-
63
- _temporary_database: Dict[DimensionCoordinates, EntryType]
64
- _history_database: Dict[DimensionCoordinates, RevisionManager]
65
-
66
- DoesNotExistError = ChunkDoesNotExist
67
- LoadError = ChunkLoadError
68
-
69
- def __init__(self, level: api_level.BaseLevel, history_db: LevelDB):
70
- """
71
- Construct a :class:`ChunkManager` instance.
72
-
73
- Should not be directly used by third party code.
74
-
75
- :param level: The world that this chunk manager is associated with
76
- """
77
- super().__init__()
78
- self._prefix: str = f"chunks" # the location to serialise Chunks to
79
- self._level = weakref.ref(level)
80
- self._history_db = weakref.ref(history_db)
81
-
82
- @property
83
- def level(self) -> api_level.BaseLevel:
84
- """The level that this chunk manager is associated with."""
85
- return self._level()
86
-
87
- def changed_chunks(self) -> Generator[DimensionCoordinates, None, None]:
88
- """The location of every chunk that has been changed since the last save."""
89
- yield from self.changed_entries()
90
-
91
- def unload(self, safe_area: Optional[Tuple[Dimension, int, int, int, int]] = None):
92
- """
93
- Unload all chunks from the temporary database that are not in the safe area.
94
-
95
- :param safe_area: The area that should not be unloaded [dimension, min_chunk_x, min_chunk_z, max_chunk_x, max_chunk_z]. If None will unload all chunk data.
96
- """
97
- with self._lock:
98
- if safe_area is None:
99
- self._temporary_database.clear()
100
- else:
101
- unload_chunks = []
102
- dimension, minx, minz, maxx, maxz = safe_area
103
- for (cd, cx, cz), chunk in self._temporary_database.items():
104
- if not (
105
- cd == dimension and minx <= cx <= maxx and minz <= cz <= maxz
106
- ):
107
- unload_chunks.append((cd, cx, cz))
108
- for chunk_key in unload_chunks:
109
- del self._temporary_database[chunk_key]
110
-
111
- def __contains__(self, item: DimensionCoordinates) -> bool:
112
- """
113
- Is the chunk specified present in the level.
114
-
115
- >>> ("minecraft:overworld", 0, 0) in level.chunks
116
- True
117
-
118
- :param item: A tuple of dimension, chunk x coordinate and chunk z coordinate.
119
- :return: True if the chunk is present, False otherwise
120
- """
121
- return self._has_entry(item)
122
-
123
- def has_chunk(self, dimension: Dimension, cx: int, cz: int) -> bool:
124
- """
125
- Is the chunk specified present in the level.
126
-
127
- :param dimension: The dimension of the chunk to check.
128
- :param cx: The chunk x coordinate.
129
- :param cz: The chunk z coordinate.
130
- :return: True if the chunk is present, False otherwise
131
- """
132
- return self._has_entry((dimension, cx, cz))
133
-
134
- def _raw_has_entry(self, key: DimensionCoordinates):
135
- dimension, cx, cz = key
136
- return self.level.level_wrapper.has_chunk(cx, cz, dimension)
137
-
138
- def all_chunk_coords(self, dimension: Dimension) -> Set[Tuple[int, int]]:
139
- """
140
- The coordinates of every chunk in this world.
141
-
142
- This is the combination of chunks saved to the world and chunks yet to be saved.
143
- """
144
- return self._all_entries(dimension)
145
-
146
- def _all_entries(self, dimension: Dimension) -> Set[Tuple[int, int]]:
147
- # custom behaviour to handle the dimension option.
148
- with self._lock:
149
- coords = set()
150
- deleted_chunks = set()
151
- for key in self._temporary_database.keys():
152
- dim, cx, cz = key
153
- if dim == dimension:
154
- if self._temporary_database[key] is None:
155
- deleted_chunks.add((cx, cz))
156
- else:
157
- coords.add((cx, cz))
158
-
159
- for key in self._history_database.keys():
160
- dim, cx, cz = key
161
- if dim == dimension and key not in self._temporary_database:
162
- if self._history_database[key].is_deleted:
163
- deleted_chunks.add((cx, cz))
164
- else:
165
- coords.add((cx, cz))
166
-
167
- for key in self._raw_all_entries(dimension):
168
- if key not in coords and key not in deleted_chunks:
169
- coords.add(key)
170
-
171
- return coords
172
-
173
- def _raw_all_entries(self, dimension: Dimension) -> Iterable[Tuple[int, int]]:
174
- return self.level.level_wrapper.all_chunk_coords(dimension)
175
-
176
- def get_chunk(self, dimension: Dimension, cx: int, cz: int) -> Chunk:
177
- """
178
- Gets the :class:`Chunk` object at the specified chunk coordinates.
179
-
180
- This may be a :class:`Chunk` instance if the chunk exists, None if it is known to not exist
181
- or :class:`~amulet.api.errors.ChunkDoesNotExist` will be raised if there is no record so it is unknown if it exists or not.
182
-
183
- Use has_chunk to check if there is a record of the chunk.
184
-
185
- :param dimension: The dimension to get the chunk from
186
- :param cx: The X coordinate of the desired chunk
187
- :param cz: The Z coordinate of the desired chunk
188
- :return: A Chunk instance or None
189
- :raises:
190
- :class:`~amulet.api.errors.ChunkDoesNotExist`: If the chunk does not exist (was deleted or never created)
191
- """
192
- return self._get_entry((dimension, cx, cz))
193
-
194
- def _raw_get_entry(self, key: EntryKeyType) -> EntryType:
195
- dimension, cx, cz = key
196
- chunk = self.level.level_wrapper.load_chunk(cx, cz, dimension)
197
- chunk.block_palette = self.level.block_palette
198
- chunk.biome_palette = self.level.biome_palette
199
- return chunk
200
-
201
- def put_chunk(self, chunk: Chunk, dimension: Dimension):
202
- """
203
- Add a given chunk to the chunk manager.
204
-
205
- :param chunk: The :class:`Chunk` to add to the chunk manager. It will be added at the location stored in :attr:`Chunk.coordinates`
206
- :param dimension: The dimension to add the chunk to.
207
- """
208
- chunk.block_palette = self.level.block_palette
209
- chunk.biome_palette = self.level.biome_palette
210
- self._put_entry((dimension, chunk.cx, chunk.cz), chunk)
211
-
212
- def delete_chunk(self, dimension: Dimension, cx: int, cz: int):
213
- """
214
- Delete a chunk from the chunk manager.
215
-
216
- :param cx: The X coordinate of the chunk
217
- :param cz: The Z coordinate of the chunk
218
- :param dimension: The dimension to delete the chunk from.
219
- """
220
- self._delete_entry((dimension, cx, cz))
221
-
222
- def _create_new_revision_manager(
223
- self, key: EntryKeyType, original_entry: EntryType
224
- ) -> RevisionManager:
225
- dimension, cx, cz = key
226
- prefix = f"{self._prefix}/{dimension}/{cx}.{cz}"
227
- return ChunkDBEntry(self.level, self._history_db(), prefix, original_entry)
1
+ from __future__ import annotations
2
+ from typing import Optional, Tuple, Generator, Set, Iterable, Dict
3
+ import weakref
4
+
5
+ from amulet.api.data_types import DimensionCoordinates, Dimension
6
+ from amulet.api.chunk import Chunk
7
+ from amulet.api.history.data_types import EntryType, EntryKeyType
8
+ from amulet.api.history.base import RevisionManager
9
+ from amulet.api.history.revision_manager import DBRevisionManager
10
+ from amulet.api.errors import ChunkDoesNotExist, ChunkLoadError
11
+ from amulet.api.history.history_manager import DatabaseHistoryManager
12
+ from amulet.api import level as api_level
13
+ from leveldb import LevelDB
14
+
15
+
16
+ class ChunkDBEntry(DBRevisionManager):
17
+ __slots__ = ("_world", "_history_db")
18
+
19
+ def __init__(
20
+ self,
21
+ world: api_level.BaseLevel,
22
+ history_db: LevelDB,
23
+ prefix: str,
24
+ initial_state: EntryType,
25
+ ):
26
+ self._world = weakref.ref(world)
27
+ self._history_db = weakref.ref(history_db)
28
+ super().__init__(prefix, initial_state)
29
+
30
+ @property
31
+ def world(self) -> api_level.BaseLevel:
32
+ return self._world()
33
+
34
+ def _serialise(self, path: str, entry: Optional[Chunk]) -> Optional[str]:
35
+ if entry is None:
36
+ return None
37
+ else:
38
+ pickled_bytes = entry.pickle()
39
+ self._history_db().put(path.encode("utf-8"), pickled_bytes)
40
+ return path
41
+
42
+ def _deserialise(self, path: Optional[str]) -> Optional[Chunk]:
43
+ if path is None:
44
+ return None
45
+ else:
46
+ pickled_bytes = self._history_db().get(path.encode("utf-8"))
47
+ return Chunk.unpickle(
48
+ pickled_bytes, self.world.block_palette, self.world.biome_palette
49
+ )
50
+
51
+
52
+ class ChunkManager(DatabaseHistoryManager):
53
+ """
54
+ The ChunkManager class is a class that handles chunks within a world.
55
+
56
+ It handles the temporary database of chunks in RAM that can be directly modified.
57
+
58
+ It handles a serialised version of the chunks on disk to reduce RAM usage.
59
+
60
+ It also contains a history manager to allow undoing and redoing of changes.
61
+ """
62
+
63
+ _temporary_database: Dict[DimensionCoordinates, EntryType]
64
+ _history_database: Dict[DimensionCoordinates, RevisionManager]
65
+
66
+ DoesNotExistError = ChunkDoesNotExist
67
+ LoadError = ChunkLoadError
68
+
69
+ def __init__(self, level: api_level.BaseLevel, history_db: LevelDB):
70
+ """
71
+ Construct a :class:`ChunkManager` instance.
72
+
73
+ Should not be directly used by third party code.
74
+
75
+ :param level: The world that this chunk manager is associated with
76
+ """
77
+ super().__init__()
78
+ self._prefix: str = f"chunks" # the location to serialise Chunks to
79
+ self._level = weakref.ref(level)
80
+ self._history_db = weakref.ref(history_db)
81
+
82
+ @property
83
+ def level(self) -> api_level.BaseLevel:
84
+ """The level that this chunk manager is associated with."""
85
+ return self._level()
86
+
87
+ def changed_chunks(self) -> Generator[DimensionCoordinates, None, None]:
88
+ """The location of every chunk that has been changed since the last save."""
89
+ yield from self.changed_entries()
90
+
91
+ def unload(self, safe_area: Optional[Tuple[Dimension, int, int, int, int]] = None):
92
+ """
93
+ Unload all chunks from the temporary database that are not in the safe area.
94
+
95
+ :param safe_area: The area that should not be unloaded [dimension, min_chunk_x, min_chunk_z, max_chunk_x, max_chunk_z]. If None will unload all chunk data.
96
+ """
97
+ with self._lock:
98
+ if safe_area is None:
99
+ self._temporary_database.clear()
100
+ else:
101
+ unload_chunks = []
102
+ dimension, minx, minz, maxx, maxz = safe_area
103
+ for (cd, cx, cz), chunk in self._temporary_database.items():
104
+ if not (
105
+ cd == dimension and minx <= cx <= maxx and minz <= cz <= maxz
106
+ ):
107
+ unload_chunks.append((cd, cx, cz))
108
+ for chunk_key in unload_chunks:
109
+ del self._temporary_database[chunk_key]
110
+
111
+ def __contains__(self, item: DimensionCoordinates) -> bool:
112
+ """
113
+ Is the chunk specified present in the level.
114
+
115
+ >>> ("minecraft:overworld", 0, 0) in level.chunks
116
+ True
117
+
118
+ :param item: A tuple of dimension, chunk x coordinate and chunk z coordinate.
119
+ :return: True if the chunk is present, False otherwise
120
+ """
121
+ return self._has_entry(item)
122
+
123
+ def has_chunk(self, dimension: Dimension, cx: int, cz: int) -> bool:
124
+ """
125
+ Is the chunk specified present in the level.
126
+
127
+ :param dimension: The dimension of the chunk to check.
128
+ :param cx: The chunk x coordinate.
129
+ :param cz: The chunk z coordinate.
130
+ :return: True if the chunk is present, False otherwise
131
+ """
132
+ return self._has_entry((dimension, cx, cz))
133
+
134
+ def _raw_has_entry(self, key: DimensionCoordinates):
135
+ dimension, cx, cz = key
136
+ return self.level.level_wrapper.has_chunk(cx, cz, dimension)
137
+
138
+ def all_chunk_coords(self, dimension: Dimension) -> Set[Tuple[int, int]]:
139
+ """
140
+ The coordinates of every chunk in this world.
141
+
142
+ This is the combination of chunks saved to the world and chunks yet to be saved.
143
+ """
144
+ return self._all_entries(dimension)
145
+
146
+ def _all_entries(self, dimension: Dimension) -> Set[Tuple[int, int]]:
147
+ # custom behaviour to handle the dimension option.
148
+ with self._lock:
149
+ coords = set()
150
+ deleted_chunks = set()
151
+ for key in self._temporary_database.keys():
152
+ dim, cx, cz = key
153
+ if dim == dimension:
154
+ if self._temporary_database[key] is None:
155
+ deleted_chunks.add((cx, cz))
156
+ else:
157
+ coords.add((cx, cz))
158
+
159
+ for key in self._history_database.keys():
160
+ dim, cx, cz = key
161
+ if dim == dimension and key not in self._temporary_database:
162
+ if self._history_database[key].is_deleted:
163
+ deleted_chunks.add((cx, cz))
164
+ else:
165
+ coords.add((cx, cz))
166
+
167
+ for key in self._raw_all_entries(dimension):
168
+ if key not in coords and key not in deleted_chunks:
169
+ coords.add(key)
170
+
171
+ return coords
172
+
173
+ def _raw_all_entries(self, dimension: Dimension) -> Iterable[Tuple[int, int]]:
174
+ return self.level.level_wrapper.all_chunk_coords(dimension)
175
+
176
+ def get_chunk(self, dimension: Dimension, cx: int, cz: int) -> Chunk:
177
+ """
178
+ Gets the :class:`Chunk` object at the specified chunk coordinates.
179
+
180
+ This may be a :class:`Chunk` instance if the chunk exists, None if it is known to not exist
181
+ or :class:`~amulet.api.errors.ChunkDoesNotExist` will be raised if there is no record so it is unknown if it exists or not.
182
+
183
+ Use has_chunk to check if there is a record of the chunk.
184
+
185
+ :param dimension: The dimension to get the chunk from
186
+ :param cx: The X coordinate of the desired chunk
187
+ :param cz: The Z coordinate of the desired chunk
188
+ :return: A Chunk instance or None
189
+ :raises:
190
+ :class:`~amulet.api.errors.ChunkDoesNotExist`: If the chunk does not exist (was deleted or never created)
191
+ """
192
+ return self._get_entry((dimension, cx, cz))
193
+
194
+ def _raw_get_entry(self, key: EntryKeyType) -> EntryType:
195
+ dimension, cx, cz = key
196
+ chunk = self.level.level_wrapper.load_chunk(cx, cz, dimension)
197
+ chunk.block_palette = self.level.block_palette
198
+ chunk.biome_palette = self.level.biome_palette
199
+ return chunk
200
+
201
+ def put_chunk(self, chunk: Chunk, dimension: Dimension):
202
+ """
203
+ Add a given chunk to the chunk manager.
204
+
205
+ :param chunk: The :class:`Chunk` to add to the chunk manager. It will be added at the location stored in :attr:`Chunk.coordinates`
206
+ :param dimension: The dimension to add the chunk to.
207
+ """
208
+ chunk.block_palette = self.level.block_palette
209
+ chunk.biome_palette = self.level.biome_palette
210
+ self._put_entry((dimension, chunk.cx, chunk.cz), chunk)
211
+
212
+ def delete_chunk(self, dimension: Dimension, cx: int, cz: int):
213
+ """
214
+ Delete a chunk from the chunk manager.
215
+
216
+ :param cx: The X coordinate of the chunk
217
+ :param cz: The Z coordinate of the chunk
218
+ :param dimension: The dimension to delete the chunk from.
219
+ """
220
+ self._delete_entry((dimension, cx, cz))
221
+
222
+ def _create_new_revision_manager(
223
+ self, key: EntryKeyType, original_entry: EntryType
224
+ ) -> RevisionManager:
225
+ dimension, cx, cz = key
226
+ prefix = f"{self._prefix}/{dimension}/{cx}.{cz}"
227
+ return ChunkDBEntry(self.level, self._history_db(), prefix, original_entry)