amulet-core 1.9.19__py3-none-any.whl → 1.9.20__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of amulet-core might be problematic. Click here for more details.
- amulet/__init__.py +27 -27
- amulet/__pyinstaller/__init__.py +2 -2
- amulet/__pyinstaller/hook-amulet.py +4 -4
- amulet/_version.py +21 -21
- amulet/api/__init__.py +2 -2
- amulet/api/abstract_base_entity.py +128 -128
- amulet/api/block.py +630 -630
- amulet/api/block_entity.py +71 -71
- amulet/api/cache.py +107 -107
- amulet/api/chunk/__init__.py +6 -6
- amulet/api/chunk/biomes.py +207 -207
- amulet/api/chunk/block_entity_dict.py +175 -175
- amulet/api/chunk/blocks.py +46 -46
- amulet/api/chunk/chunk.py +389 -389
- amulet/api/chunk/entity_list.py +75 -75
- amulet/api/chunk/status.py +167 -167
- amulet/api/data_types/__init__.py +4 -4
- amulet/api/data_types/generic_types.py +4 -4
- amulet/api/data_types/operation_types.py +16 -16
- amulet/api/data_types/world_types.py +49 -49
- amulet/api/data_types/wrapper_types.py +71 -71
- amulet/api/entity.py +74 -74
- amulet/api/errors.py +119 -119
- amulet/api/history/__init__.py +36 -36
- amulet/api/history/base/__init__.py +3 -3
- amulet/api/history/base/base_history.py +26 -26
- amulet/api/history/base/history_manager.py +63 -63
- amulet/api/history/base/revision_manager.py +73 -73
- amulet/api/history/changeable.py +15 -15
- amulet/api/history/data_types.py +7 -7
- amulet/api/history/history_manager/__init__.py +3 -3
- amulet/api/history/history_manager/container.py +102 -102
- amulet/api/history/history_manager/database.py +279 -279
- amulet/api/history/history_manager/meta.py +93 -93
- amulet/api/history/history_manager/object.py +116 -116
- amulet/api/history/revision_manager/__init__.py +2 -2
- amulet/api/history/revision_manager/disk.py +33 -33
- amulet/api/history/revision_manager/ram.py +12 -12
- amulet/api/item.py +75 -75
- amulet/api/level/__init__.py +4 -4
- amulet/api/level/base_level/__init__.py +1 -1
- amulet/api/level/base_level/base_level.py +1035 -1026
- amulet/api/level/base_level/chunk_manager.py +227 -227
- amulet/api/level/base_level/clone.py +389 -389
- amulet/api/level/base_level/player_manager.py +101 -101
- amulet/api/level/immutable_structure/__init__.py +1 -1
- amulet/api/level/immutable_structure/immutable_structure.py +94 -94
- amulet/api/level/immutable_structure/void_format_wrapper.py +117 -117
- amulet/api/level/structure.py +22 -22
- amulet/api/level/world.py +19 -19
- amulet/api/partial_3d_array/__init__.py +2 -2
- amulet/api/partial_3d_array/base_partial_3d_array.py +263 -263
- amulet/api/partial_3d_array/bounded_partial_3d_array.py +528 -528
- amulet/api/partial_3d_array/data_types.py +15 -15
- amulet/api/partial_3d_array/unbounded_partial_3d_array.py +229 -229
- amulet/api/partial_3d_array/util.py +152 -152
- amulet/api/player.py +65 -65
- amulet/api/registry/__init__.py +2 -2
- amulet/api/registry/base_registry.py +34 -34
- amulet/api/registry/biome_manager.py +153 -153
- amulet/api/registry/block_manager.py +156 -156
- amulet/api/selection/__init__.py +2 -2
- amulet/api/selection/abstract_selection.py +315 -315
- amulet/api/selection/box.py +805 -805
- amulet/api/selection/group.py +488 -488
- amulet/api/structure.py +37 -37
- amulet/api/wrapper/__init__.py +8 -8
- amulet/api/wrapper/chunk/interface.py +441 -441
- amulet/api/wrapper/chunk/translator.py +567 -567
- amulet/api/wrapper/format_wrapper.py +772 -772
- amulet/api/wrapper/structure_format_wrapper.py +116 -116
- amulet/api/wrapper/world_format_wrapper.py +63 -63
- amulet/level/__init__.py +1 -1
- amulet/level/formats/anvil_forge_world.py +40 -40
- amulet/level/formats/anvil_world/__init__.py +3 -3
- amulet/level/formats/anvil_world/_sector_manager.py +291 -384
- amulet/level/formats/anvil_world/data_pack/__init__.py +2 -2
- amulet/level/formats/anvil_world/data_pack/data_pack.py +224 -224
- amulet/level/formats/anvil_world/data_pack/data_pack_manager.py +77 -77
- amulet/level/formats/anvil_world/dimension.py +177 -177
- amulet/level/formats/anvil_world/format.py +769 -769
- amulet/level/formats/anvil_world/region.py +384 -384
- amulet/level/formats/construction/__init__.py +3 -3
- amulet/level/formats/construction/format_wrapper.py +515 -515
- amulet/level/formats/construction/interface.py +134 -134
- amulet/level/formats/construction/section.py +60 -60
- amulet/level/formats/construction/util.py +165 -165
- amulet/level/formats/leveldb_world/__init__.py +3 -3
- amulet/level/formats/leveldb_world/chunk.py +33 -33
- amulet/level/formats/leveldb_world/dimension.py +385 -419
- amulet/level/formats/leveldb_world/format.py +659 -641
- amulet/level/formats/leveldb_world/interface/chunk/__init__.py +36 -36
- amulet/level/formats/leveldb_world/interface/chunk/base_leveldb_interface.py +836 -836
- amulet/level/formats/leveldb_world/interface/chunk/generate_interface.py +31 -31
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_0.py +30 -30
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_1.py +12 -12
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_10.py +12 -12
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_11.py +12 -12
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_12.py +12 -12
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_13.py +12 -12
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_14.py +12 -12
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_15.py +12 -12
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_16.py +12 -12
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_17.py +12 -12
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_18.py +12 -12
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_19.py +12 -12
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_2.py +12 -12
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_20.py +12 -12
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_21.py +12 -12
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_22.py +12 -12
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_23.py +10 -10
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_24.py +10 -10
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_25.py +24 -24
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_26.py +10 -10
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_27.py +10 -10
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_28.py +10 -10
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_29.py +33 -33
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_3.py +57 -57
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_30.py +10 -10
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_31.py +10 -10
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_32.py +10 -10
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_33.py +10 -10
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_34.py +10 -10
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_35.py +10 -10
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_36.py +10 -10
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_37.py +10 -10
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_38.py +10 -10
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_39.py +12 -12
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_4.py +12 -12
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_40.py +16 -16
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_5.py +12 -12
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_6.py +12 -12
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_7.py +12 -12
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_8.py +180 -180
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_9.py +18 -18
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_chunk_versions.py +79 -79
- amulet/level/formats/mcstructure/__init__.py +3 -3
- amulet/level/formats/mcstructure/chunk.py +50 -50
- amulet/level/formats/mcstructure/format_wrapper.py +408 -408
- amulet/level/formats/mcstructure/interface.py +175 -175
- amulet/level/formats/schematic/__init__.py +3 -3
- amulet/level/formats/schematic/chunk.py +55 -55
- amulet/level/formats/schematic/data_types.py +4 -4
- amulet/level/formats/schematic/format_wrapper.py +373 -373
- amulet/level/formats/schematic/interface.py +142 -142
- amulet/level/formats/sponge_schem/__init__.py +4 -4
- amulet/level/formats/sponge_schem/chunk.py +62 -62
- amulet/level/formats/sponge_schem/format_wrapper.py +463 -463
- amulet/level/formats/sponge_schem/interface.py +118 -118
- amulet/level/formats/sponge_schem/varint/__init__.py +1 -1
- amulet/level/formats/sponge_schem/varint/varint.py +87 -87
- amulet/level/interfaces/chunk/anvil/anvil_0.py +72 -72
- amulet/level/interfaces/chunk/anvil/anvil_1444.py +336 -336
- amulet/level/interfaces/chunk/anvil/anvil_1466.py +94 -94
- amulet/level/interfaces/chunk/anvil/anvil_1467.py +37 -37
- amulet/level/interfaces/chunk/anvil/anvil_1484.py +20 -20
- amulet/level/interfaces/chunk/anvil/anvil_1503.py +20 -20
- amulet/level/interfaces/chunk/anvil/anvil_1519.py +34 -34
- amulet/level/interfaces/chunk/anvil/anvil_1901.py +20 -20
- amulet/level/interfaces/chunk/anvil/anvil_1908.py +20 -20
- amulet/level/interfaces/chunk/anvil/anvil_1912.py +21 -21
- amulet/level/interfaces/chunk/anvil/anvil_1934.py +20 -20
- amulet/level/interfaces/chunk/anvil/anvil_2203.py +69 -69
- amulet/level/interfaces/chunk/anvil/anvil_2529.py +19 -19
- amulet/level/interfaces/chunk/anvil/anvil_2681.py +76 -76
- amulet/level/interfaces/chunk/anvil/anvil_2709.py +19 -19
- amulet/level/interfaces/chunk/anvil/anvil_2844.py +267 -267
- amulet/level/interfaces/chunk/anvil/anvil_3463.py +19 -19
- amulet/level/interfaces/chunk/anvil/anvil_na.py +607 -607
- amulet/level/interfaces/chunk/anvil/base_anvil_interface.py +326 -326
- amulet/level/load.py +59 -59
- amulet/level/loader.py +95 -95
- amulet/level/translators/chunk/bedrock/__init__.py +267 -267
- amulet/level/translators/chunk/bedrock/bedrock_nbt_blockstate_translator.py +46 -46
- amulet/level/translators/chunk/bedrock/bedrock_numerical_translator.py +39 -39
- amulet/level/translators/chunk/bedrock/bedrock_psudo_numerical_translator.py +37 -37
- amulet/level/translators/chunk/java/java_1_18_translator.py +40 -40
- amulet/level/translators/chunk/java/java_blockstate_translator.py +94 -94
- amulet/level/translators/chunk/java/java_numerical_translator.py +62 -62
- amulet/libs/leveldb/__init__.py +7 -7
- amulet/operations/__init__.py +5 -5
- amulet/operations/clone.py +18 -18
- amulet/operations/delete_chunk.py +32 -32
- amulet/operations/fill.py +30 -30
- amulet/operations/paste.py +65 -65
- amulet/operations/replace.py +58 -58
- amulet/utils/__init__.py +14 -14
- amulet/utils/format_utils.py +41 -41
- amulet/utils/generator.py +15 -15
- amulet/utils/matrix.py +243 -243
- amulet/utils/numpy_helpers.py +46 -46
- amulet/utils/world_utils.py +349 -349
- {amulet_core-1.9.19.dist-info → amulet_core-1.9.20.dist-info}/METADATA +97 -97
- amulet_core-1.9.20.dist-info/RECORD +208 -0
- amulet_core-1.9.19.dist-info/RECORD +0 -208
- {amulet_core-1.9.19.dist-info → amulet_core-1.9.20.dist-info}/WHEEL +0 -0
- {amulet_core-1.9.19.dist-info → amulet_core-1.9.20.dist-info}/entry_points.txt +0 -0
- {amulet_core-1.9.19.dist-info → amulet_core-1.9.20.dist-info}/top_level.txt +0 -0
|
@@ -1,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)
|