@@ -47,13 +47,19 @@ class SpongeSchemWriteError(ObjectWriteError):
4747
4848sponge_schem_interface = SpongeSchemInterface ()
4949
50- max_schem_version = 2
50+ max_schem_version = 3
5151
5252
5353def _is_sponge (path : str ):
5454 """Check if a file is actually a sponge schematic file."""
5555 try :
56- return "BlockData" in load_nbt (path ).compound
56+ tag = load_nbt (path ).compound
57+ schematic = tag .get ("Schematic" )
58+ if isinstance (schematic , CompoundTag ):
59+ root = schematic
60+ else :
61+ root = tag
62+ return "Version" in root
5763 except :
5864 return False
5965
@@ -84,6 +90,7 @@ def _create(
8490 bounds : Union [
8591 SelectionGroup , Dict [Dimension , Optional [SelectionGroup ]], None
8692 ] = None ,
93+ schematic_version : int = max_schem_version ,
8794 ** kwargs ,
8895 ):
8996 if not overwrite and os .path .isfile (self .path ):
@@ -95,18 +102,24 @@ def _create(
95102 self ._set_selection (bounds )
96103 self ._is_open = True
97104 self ._has_lock = True
105+ self ._schem_version = schematic_version
98106
99107 def open_from (self , f : BinaryIO ):
100- sponge_schem = load_nbt (f ).compound
108+ root_tag = load_nbt (f ).compound
109+ schematic_tag = root_tag .get ("Schematic" )
110+ if isinstance (schematic_tag , CompoundTag ):
111+ sponge_schem = schematic_tag
112+ else :
113+ sponge_schem = root_tag
101114 version_tag = sponge_schem .get ("Version" )
102115 if not isinstance (version_tag , IntTag ):
103116 raise SpongeSchemReadError ("Version key must exist and be an integer." )
104- version = version_tag .py_int
117+ self . _schem_version = version = version_tag .py_int
105118 if version == 1 :
106119 raise SpongeSchemReadError (
107120 "Sponge Schematic Version 1 is not supported currently."
108121 )
109- elif version == 2 :
122+ elif 2 <= version <= 3 :
110123 offset = sponge_schem .get ("Offset" )
111124 if isinstance (offset , IntArrayTag ) and len (offset ) == 3 :
112125 min_point = numpy .array (offset )
@@ -138,12 +151,25 @@ def open_from(self, f: BinaryIO):
138151 self ._platform = translator_version .platform
139152 self ._version = translator_version .data_version
140153
141- packed_block_data = sponge_schem .get ("BlockData" )
142- if not isinstance (packed_block_data , ByteArrayTag ):
154+ if version == 2 :
155+ block_palette_tag = sponge_schem .get ("Palette" )
156+ block_index_tag = sponge_schem .get ("BlockData" )
157+ block_entities_tag = sponge_schem .get ("BlockEntities" )
158+ elif version == 3 :
159+ blocks_tag = sponge_schem .get ("Blocks" )
160+ if not isinstance (blocks_tag , CompoundTag ):
161+ raise SpongeSchemReadError ("Blocks must be a CompoundTag" )
162+ block_palette_tag = blocks_tag .get ("Palette" )
163+ block_index_tag = blocks_tag .get ("Data" )
164+ block_entities_tag = blocks_tag .get ("BlockEntities" )
165+ else :
166+ raise RuntimeError
167+
168+ if not isinstance (block_index_tag , ByteArrayTag ):
143169 raise SpongeSchemReadError ("BlockData must be a ByteArrayTag" )
144170
145171 unpacked_block_data = decode_byte_array (
146- numpy .array (packed_block_data , dtype = numpy .uint8 )
172+ numpy .array (block_index_tag , dtype = numpy .uint8 )
147173 )
148174 if len (unpacked_block_data ) != numpy .prod (size ):
149175 raise SpongeSchemReadError (
@@ -158,17 +184,11 @@ def open_from(self, f: BinaryIO):
158184 (2 , 0 , 1 ), # YZX => XYZ
159185 )
160186
161- if "Palette" not in sponge_schem :
162- raise SpongeSchemReadError (
163- "Amulet is not able to read Sponge Schem files with no block palette."
164- )
165-
166- palette_data = sponge_schem .get ("Palette" )
167- if not isinstance (palette_data , CompoundTag ):
187+ if not isinstance (block_palette_tag , CompoundTag ):
168188 raise SpongeSchemReadError ("Palette must be a CompoundTag." )
169189
170190 block_palette : Dict [int , Block ] = {}
171- for blockstate , index_tag in palette_data .items ():
191+ for blockstate , index_tag in block_palette_tag .items ():
172192 index = index_tag .py_int
173193 if index in block_palette :
174194 raise SpongeSchemReadError (
@@ -207,29 +227,26 @@ def open_from(self, f: BinaryIO):
207227 [],
208228 )
209229
210- if "BlockEntities" in sponge_schem :
211- block_entities = sponge_schem ["BlockEntities" ]
212- if not (
213- isinstance (block_entities , ListTag )
214- and (
215- len (block_entities ) == 0 or block_entities .list_data_type == 10
216- ) # CompoundTag.tag_id
217- ):
218- raise SpongeSchemReadError (
219- "BlockEntities must be a ListTag of compound tags."
220- )
221-
222- for block_entity in block_entities :
223- if "Pos" not in block_entity :
224- continue
225-
226- pos_tag = block_entity ["Pos" ]
230+ if (
231+ isinstance (block_entities_tag , ListTag )
232+ and block_entities_tag .list_data_type == CompoundTag .tag_id
233+ ):
234+ for block_entity in block_entities_tag :
235+ pos_tag = block_entity .get ("Pos" )
227236 if not (isinstance (pos_tag , IntArrayTag ) and len (pos_tag ) == 3 ):
228237 continue
229238
230239 pos = pos_tag .np_array + min_point
231240 x , y , z = pos
232241 block_entity ["Pos" ] = IntArrayTag (pos )
242+ if version == 2 :
243+ extra = block_entity .pop ("Extra" , None )
244+ elif version == 3 :
245+ extra = block_entity .pop ("Data" , None )
246+ else :
247+ raise RuntimeError
248+ if isinstance (extra , CompoundTag ):
249+ block_entity .update (extra )
233250 cx , cz = x >> 4 , z >> 4
234251 if (cx , cz ) in self ._chunks and (x , y , z ) in self ._chunks [
235252 (cx , cz )
@@ -249,10 +266,7 @@ def open_from(self, f: BinaryIO):
249266 )
250267
251268 for entity in entities :
252- if "Pos" not in entity :
253- continue
254-
255- pos = entity ["Pos" ]
269+ pos = entity .get ("Pos" )
256270 if not (
257271 isinstance (pos , ListTag )
258272 and len (pos ) == 3
@@ -272,6 +286,14 @@ def open_from(self, f: BinaryIO):
272286 IntTag (z ),
273287 ]
274288 )
289+ if version == 2 :
290+ extra = entity .pop ("Extra" , None )
291+ elif version == 3 :
292+ extra = entity .pop ("Data" , None )
293+ else :
294+ raise RuntimeError
295+ if isinstance (extra , CompoundTag ):
296+ entity .update (extra )
275297 cx , cz = numpy .floor ([x , z ]).astype (int ) >> 4
276298 if (cx , cz ) in self ._chunks and (x , y , z ) in self ._chunks [
277299 (cx , cz )
@@ -316,16 +338,16 @@ def save_to(self, f: BinaryIO):
316338 raise SpongeSchemReadError (
317339 "Sponge Schematic Version 1 is not supported currently."
318340 )
319- elif self ._schem_version == 2 :
341+ elif 2 <= self ._schem_version <= 3 :
320342 selection = self ._bounds [self .dimensions [0 ]].selection_boxes [0 ]
321343 if any (s > 2 ** 16 - 1 for s in selection .shape ):
322344 raise SpongeSchemWriteError (
323345 "The structure is too large to be exported to a Sponge Schematic file. It must be 2^16 - 1 at most in each dimension."
324346 )
325347 overflowed_shape = [s if s < 2 ** 15 else s - 2 ** 16 for s in selection .shape ]
326- tag = CompoundTag (
348+ schematic_tag = CompoundTag (
327349 {
328- "Version" : IntTag (2 ),
350+ "Version" : IntTag (self . _schem_version ),
329351 "DataVersion" : IntTag (self ._version ),
330352 "Width" : ShortTag (overflowed_shape [0 ]),
331353 "Height" : ShortTag (overflowed_shape [1 ]),
@@ -355,22 +377,42 @@ def save_to(self, f: BinaryIO):
355377 blocks [box .slice ] = chunk .blocks + palette_len
356378 palette .append (chunk .palette )
357379 palette_len += len (chunk .palette )
358- for be in chunk .block_entities :
359- be = copy .deepcopy (be )
360- be ["Pos" ] = IntArrayTag (be ["Pos" ].np_array - selection .min )
361- block_entities .append (be )
362-
363- for e in chunk .entities :
364- e = copy .deepcopy (e )
365- x , y , z = e ["Pos" ]
366- e ["Pos" ] = ListTag (
380+ for block_entity in chunk .block_entities :
381+ block_entity = copy .deepcopy (block_entity )
382+ pos = IntArrayTag (
383+ block_entity .pop ("Pos" ).np_array - selection .min
384+ )
385+ if self ._schem_version == 2 :
386+ block_entity ["Pos" ] = pos
387+ elif self ._schem_version == 3 :
388+ id_ = block_entity .pop ("Id" )
389+ block_entity = CompoundTag (
390+ {"Pos" : pos , "Id" : id_ , "Data" : block_entity }
391+ )
392+ else :
393+ raise RuntimeError
394+ block_entities .append (block_entity )
395+
396+ for entity in chunk .entities :
397+ entity = copy .deepcopy (entity )
398+ x , y , z = entity ["Pos" ]
399+ pos = ListTag (
367400 [
368401 IntTag (x - selection .min_x ),
369402 IntTag (y - selection .min_y ),
370403 IntTag (z - selection .min_z ),
371404 ]
372405 )
373- entities .append (e )
406+ if self ._schem_version == 2 :
407+ entity ["Pos" ] = pos
408+ elif self ._schem_version == 3 :
409+ id_ = entity .pop ("Id" )
410+ entity = CompoundTag (
411+ {"Pos" : pos , "Id" : id_ , "Data" : entity }
412+ )
413+ else :
414+ raise RuntimeError
415+ entities .append (entity )
374416
375417 compact_palette , lut = brute_sort_objects_no_hash (
376418 numpy .concatenate (palette )
@@ -381,20 +423,43 @@ def save_to(self, f: BinaryIO):
381423 block : Block
382424 block_palette .append (block .blockstate )
383425
384- tag ["PaletteMax" ] = IntTag (len (compact_palette ))
385- tag ["Palette" ] = CompoundTag (
426+ block_palette_tag = CompoundTag (
386427 {
387428 blockstate : IntTag (index )
388429 for index , blockstate in enumerate (block_palette )
389430 }
390431 )
391- tag ["BlockData" ] = ByteArrayTag (list (encode_array (blocks )))
392- if block_entities :
393- tag ["BlockEntities" ] = ListTag (block_entities )
432+ block_index_tag = ByteArrayTag (list (encode_array (blocks )))
433+
434+ if self ._schem_version == 2 :
435+ schematic_tag ["PaletteMax" ] = IntTag (len (compact_palette ))
436+ schematic_tag ["Palette" ] = block_palette_tag
437+ schematic_tag ["BlockData" ] = block_index_tag
438+ if block_entities :
439+ schematic_tag ["BlockEntities" ] = ListTag (block_entities )
440+ elif self ._schem_version == 3 :
441+ blocks_tag = CompoundTag (
442+ {
443+ "Palette" : block_palette_tag ,
444+ "Data" : block_index_tag ,
445+ }
446+ )
447+ if block_entities :
448+ blocks_tag ["BlockEntities" ] = ListTag (block_entities )
449+ schematic_tag ["Blocks" ] = blocks_tag
450+ else :
451+ raise RuntimeError
452+
394453 if entities :
395- tag ["Entities" ] = ListTag (entities )
454+ schematic_tag ["Entities" ] = ListTag (entities )
396455
397- NamedTag (tag , "Schematic" ).save_to (f )
456+ if self ._schem_version == 2 :
457+ named_tag = NamedTag (schematic_tag , "Schematic" )
458+ elif self ._schem_version == 3 :
459+ named_tag = NamedTag (CompoundTag ({"Schematic" : schematic_tag }), "" )
460+ else :
461+ raise RuntimeError
462+ named_tag .save_to (f )
398463 else :
399464 raise SpongeSchemReadError (
400465 f"Sponge Schematic Version { self ._schem_version } is not supported currently."
0 commit comments