koro 1.1.6__py3-none-any.whl → 2.0.0.post1__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.
- koro/__init__.py +8 -8
- koro/slot/__init__.py +21 -0
- koro/{file → slot}/bin.py +13 -34
- koro/slot/file.py +70 -0
- koro/slot/save.py +137 -0
- koro/slot/xml.py +845 -0
- koro/stage/__init__.py +99 -0
- koro/stage/model.py +288 -0
- koro/stage/part.py +1754 -0
- koro-2.0.0.post1.dist-info/METADATA +51 -0
- koro-2.0.0.post1.dist-info/RECORD +14 -0
- {koro-1.1.6.dist-info → koro-2.0.0.post1.dist-info}/WHEEL +1 -1
- koro/file/__init__.py +0 -40
- koro/file/dir.py +0 -212
- koro/file/lvl.py +0 -40
- koro/file/zip.py +0 -223
- koro/item/__init__.py +0 -0
- koro/item/group.py +0 -48
- koro/item/level.py +0 -108
- koro/item/save.py +0 -29
- koro-1.1.6.dist-info/METADATA +0 -16
- koro-1.1.6.dist-info/RECORD +0 -15
- {koro-1.1.6.dist-info → koro-2.0.0.post1.dist-info}/LICENSE +0 -0
- {koro-1.1.6.dist-info → koro-2.0.0.post1.dist-info}/top_level.txt +0 -0
koro/slot/xml.py
ADDED
@@ -0,0 +1,845 @@
|
|
1
|
+
from collections.abc import Iterator, Sequence
|
2
|
+
from io import StringIO
|
3
|
+
from itertools import chain
|
4
|
+
from os import SEEK_END
|
5
|
+
from typing import Final
|
6
|
+
from xml.etree.ElementTree import Element, fromstring
|
7
|
+
|
8
|
+
from ..stage import EditUser, Stage, Theme
|
9
|
+
from ..stage.model import DecorationModel, DeviceModel, PartModel
|
10
|
+
from ..stage.part import (
|
11
|
+
Ant,
|
12
|
+
BasePart,
|
13
|
+
BlinkingTile,
|
14
|
+
Bumper,
|
15
|
+
TextBox,
|
16
|
+
Cannon,
|
17
|
+
ConveyorBelt,
|
18
|
+
DashTunnel,
|
19
|
+
Drawbridge,
|
20
|
+
Fan,
|
21
|
+
FixedSpeedDevice,
|
22
|
+
Gear,
|
23
|
+
Goal,
|
24
|
+
GreenCrystal,
|
25
|
+
KororinCapsule,
|
26
|
+
Magnet,
|
27
|
+
MagnetSegment,
|
28
|
+
MagnifyingGlass,
|
29
|
+
MelodyTile,
|
30
|
+
MovementTiming,
|
31
|
+
MovingCurve,
|
32
|
+
MovingTile,
|
33
|
+
Part,
|
34
|
+
Press,
|
35
|
+
ProgressMarker,
|
36
|
+
Punch,
|
37
|
+
Scissors,
|
38
|
+
SeesawBlock,
|
39
|
+
SizeTunnel,
|
40
|
+
SlidingTile,
|
41
|
+
Speed,
|
42
|
+
Spring,
|
43
|
+
Start,
|
44
|
+
Thorn,
|
45
|
+
TimedDevice,
|
46
|
+
ToyTrain,
|
47
|
+
TrainTrack,
|
48
|
+
Turntable,
|
49
|
+
UpsideDownBall,
|
50
|
+
UpsideDownStageDevice,
|
51
|
+
Walls,
|
52
|
+
Warp,
|
53
|
+
)
|
54
|
+
from .file import FileSlot
|
55
|
+
|
56
|
+
__all__ = ["XmlSlot"]
|
57
|
+
|
58
|
+
|
59
|
+
class XmlSlot(FileSlot):
|
60
|
+
__slots__ = ()
|
61
|
+
|
62
|
+
@staticmethod
|
63
|
+
def deserialize(data: bytes) -> Stage:
|
64
|
+
"""Behavior is undefined when passed invalid stage data"""
|
65
|
+
|
66
|
+
def get_values(element: Element, /, tag: str) -> Sequence[str]:
|
67
|
+
return element.find(tag).text.strip().split() # type: ignore[union-attr]
|
68
|
+
|
69
|
+
def get_pos_rot(element: Element, /) -> Iterator[float]:
|
70
|
+
return chain(
|
71
|
+
map(float, get_values(element, "pos")),
|
72
|
+
map(float, get_values(element, "rot")),
|
73
|
+
)
|
74
|
+
|
75
|
+
root: Final[Element] = fromstring(
|
76
|
+
data.decode("shift_jis", "xmlcharrefreplace").replace(
|
77
|
+
'<?xml version="1.0" encoding="SHIFT_JIS"?>',
|
78
|
+
'<?xml version="1.0"?>\n<body>',
|
79
|
+
)
|
80
|
+
+ "</body>"
|
81
|
+
)
|
82
|
+
editinfo: Final[Element] = root.find("EDITINFO") # type: ignore[assignment]
|
83
|
+
output: Final[Stage] = Stage(
|
84
|
+
(),
|
85
|
+
edit_user=(
|
86
|
+
EditUser.PROTECTED
|
87
|
+
if editinfo.find("EDITUSER") is None
|
88
|
+
else EditUser(int(editinfo.find("EDITUSER").text.strip())) # type: ignore[union-attr]
|
89
|
+
),
|
90
|
+
theme=Theme(int(editinfo.find("THEME").text.strip())), # type: ignore[union-attr]
|
91
|
+
tilt_lock=False if editinfo.find("LOCK") is None else bool(int(editinfo.find("LOCK").text.strip())), # type: ignore[union-attr]
|
92
|
+
)
|
93
|
+
groups: Final[dict[int, dict[int, Element]]] = {}
|
94
|
+
for elem in root.find("STAGEDATA") or ():
|
95
|
+
match elem.tag:
|
96
|
+
case "EDIT_LIGHT" | "EDIT_BG_NORMAL":
|
97
|
+
continue
|
98
|
+
case "EDIT_MAP_NORMAL":
|
99
|
+
output.add(
|
100
|
+
Part(
|
101
|
+
*get_pos_rot(elem),
|
102
|
+
shape=PartModel(int(get_values(elem, "model")[1])),
|
103
|
+
)
|
104
|
+
)
|
105
|
+
case "EDIT_MAP_EXT":
|
106
|
+
output.add(
|
107
|
+
Part(
|
108
|
+
*get_pos_rot(elem),
|
109
|
+
shape=DecorationModel(int(get_values(elem, "model")[1])),
|
110
|
+
)
|
111
|
+
)
|
112
|
+
case "EDIT_GIM_START":
|
113
|
+
output.add(Start(*get_pos_rot(elem)))
|
114
|
+
case "EDIT_GIM_GOAL":
|
115
|
+
output.add(Goal(*get_pos_rot(elem)))
|
116
|
+
case "EDIT_GIM_NORMAL":
|
117
|
+
match DeviceModel(int(get_values(elem, "model")[1])):
|
118
|
+
case DeviceModel.Crystal:
|
119
|
+
output.add(
|
120
|
+
ProgressMarker(
|
121
|
+
*get_pos_rot(elem),
|
122
|
+
progress=int(get_values(elem, "hook")[0]) * 2 + 1, # type: ignore[arg-type]
|
123
|
+
)
|
124
|
+
)
|
125
|
+
case DeviceModel.Respawn:
|
126
|
+
output.add(
|
127
|
+
ProgressMarker(
|
128
|
+
*get_pos_rot(elem),
|
129
|
+
progress=int(get_values(elem, "hook")[0]) * 2 + 2, # type: ignore[arg-type]
|
130
|
+
)
|
131
|
+
)
|
132
|
+
case DeviceModel.MovingTile10x10:
|
133
|
+
output.add(
|
134
|
+
MovingTile(
|
135
|
+
*get_pos_rot(elem),
|
136
|
+
**dict(
|
137
|
+
zip(
|
138
|
+
("dest_x", "dest_y", "dest_z"),
|
139
|
+
map(float, get_values(elem, "anmmov1")),
|
140
|
+
)
|
141
|
+
), # type: ignore[arg-type]
|
142
|
+
shape=PartModel.Tile10x10,
|
143
|
+
speed=float(get_values(elem, "anmspd")[0]),
|
144
|
+
)
|
145
|
+
)
|
146
|
+
case DeviceModel.MovingTile20x20:
|
147
|
+
output.add(
|
148
|
+
MovingTile(
|
149
|
+
*get_pos_rot(elem),
|
150
|
+
**dict(
|
151
|
+
zip(
|
152
|
+
("dest_x", "dest_y", "dest_z"),
|
153
|
+
map(float, get_values(elem, "anmmov1")),
|
154
|
+
)
|
155
|
+
), # type: ignore[arg-type]
|
156
|
+
shape=PartModel.Tile20x20,
|
157
|
+
speed=float(get_values(elem, "anmspd")[0]),
|
158
|
+
walls=(
|
159
|
+
Walls(0)
|
160
|
+
if elem.find("hook") is None
|
161
|
+
else Walls(int(get_values(elem, "hook")[1]))
|
162
|
+
),
|
163
|
+
)
|
164
|
+
)
|
165
|
+
case DeviceModel.MovingTile30x30:
|
166
|
+
output.add(
|
167
|
+
MovingTile(
|
168
|
+
*get_pos_rot(elem),
|
169
|
+
**dict(
|
170
|
+
zip(
|
171
|
+
("dest_x", "dest_y", "dest_z"),
|
172
|
+
map(float, get_values(elem, "anmmov1")),
|
173
|
+
)
|
174
|
+
), # type: ignore[arg-type]
|
175
|
+
shape=PartModel.TileA30x30,
|
176
|
+
speed=float(get_values(elem, "anmspd")[0]),
|
177
|
+
walls=(
|
178
|
+
Walls(0)
|
179
|
+
if elem.find("hook") is None
|
180
|
+
else Walls(int(get_values(elem, "hook")[1]))
|
181
|
+
),
|
182
|
+
)
|
183
|
+
)
|
184
|
+
case DeviceModel.MovingTile30x90:
|
185
|
+
output.add(
|
186
|
+
MovingTile(
|
187
|
+
*get_pos_rot(elem),
|
188
|
+
**dict(
|
189
|
+
zip(
|
190
|
+
("dest_x", "dest_y", "dest_z"),
|
191
|
+
map(float, get_values(elem, "anmmov1")),
|
192
|
+
)
|
193
|
+
), # type: ignore[arg-type]
|
194
|
+
shape=PartModel.TileA30x90,
|
195
|
+
speed=float(get_values(elem, "anmspd")[0]),
|
196
|
+
)
|
197
|
+
)
|
198
|
+
case DeviceModel.MovingTile90x90A:
|
199
|
+
output.add(
|
200
|
+
MovingTile(
|
201
|
+
*get_pos_rot(elem),
|
202
|
+
**dict(
|
203
|
+
zip(
|
204
|
+
("dest_x", "dest_y", "dest_z"),
|
205
|
+
map(float, get_values(elem, "anmmov1")),
|
206
|
+
)
|
207
|
+
), # type: ignore[arg-type]
|
208
|
+
shape=PartModel.Tile90x90,
|
209
|
+
speed=float(get_values(elem, "anmspd")[0]),
|
210
|
+
)
|
211
|
+
)
|
212
|
+
case DeviceModel.MovingTile90x90B:
|
213
|
+
output.add(
|
214
|
+
MovingTile(
|
215
|
+
*get_pos_rot(elem),
|
216
|
+
**dict(
|
217
|
+
zip(
|
218
|
+
("dest_x", "dest_y", "dest_z"),
|
219
|
+
map(float, get_values(elem, "anmmov1")),
|
220
|
+
)
|
221
|
+
), # type: ignore[arg-type]
|
222
|
+
shape=PartModel.HoleB90x90,
|
223
|
+
speed=float(get_values(elem, "anmspd")[0]),
|
224
|
+
)
|
225
|
+
)
|
226
|
+
case DeviceModel.MovingTile10x10Switch:
|
227
|
+
output.add(
|
228
|
+
MovingTile(
|
229
|
+
*get_pos_rot(elem),
|
230
|
+
**dict(
|
231
|
+
zip(
|
232
|
+
("dest_x", "dest_y", "dest_z"),
|
233
|
+
map(float, get_values(elem, "anmmov1")),
|
234
|
+
)
|
235
|
+
), # type: ignore[arg-type]
|
236
|
+
shape=PartModel.Tile10x10,
|
237
|
+
speed=float(get_values(elem, "anmspd")[0]),
|
238
|
+
switch=True,
|
239
|
+
)
|
240
|
+
)
|
241
|
+
case DeviceModel.MovingTile20x20Switch:
|
242
|
+
output.add(
|
243
|
+
MovingTile(
|
244
|
+
*get_pos_rot(elem),
|
245
|
+
**dict(
|
246
|
+
zip(
|
247
|
+
("dest_x", "dest_y", "dest_z"),
|
248
|
+
map(float, get_values(elem, "anmmov1")),
|
249
|
+
)
|
250
|
+
), # type: ignore[arg-type]
|
251
|
+
shape=PartModel.Tile20x20,
|
252
|
+
speed=float(get_values(elem, "anmspd")[0]),
|
253
|
+
switch=True,
|
254
|
+
walls=(
|
255
|
+
Walls(0)
|
256
|
+
if elem.find("hook") is None
|
257
|
+
else Walls(int(get_values(elem, "hook")[1]))
|
258
|
+
),
|
259
|
+
)
|
260
|
+
)
|
261
|
+
case DeviceModel.MovingTile30x30Switch:
|
262
|
+
output.add(
|
263
|
+
MovingTile(
|
264
|
+
*get_pos_rot(elem),
|
265
|
+
**dict(
|
266
|
+
zip(
|
267
|
+
("dest_x", "dest_y", "dest_z"),
|
268
|
+
map(float, get_values(elem, "anmmov1")),
|
269
|
+
)
|
270
|
+
), # type: ignore[arg-type]
|
271
|
+
shape=PartModel.TileA30x30,
|
272
|
+
speed=float(get_values(elem, "anmspd")[0]),
|
273
|
+
switch=True,
|
274
|
+
walls=(
|
275
|
+
Walls(0)
|
276
|
+
if elem.find("hook") is None
|
277
|
+
else Walls(int(get_values(elem, "hook")[1]))
|
278
|
+
),
|
279
|
+
)
|
280
|
+
)
|
281
|
+
case DeviceModel.MovingTile30x90Switch:
|
282
|
+
output.add(
|
283
|
+
MovingTile(
|
284
|
+
*get_pos_rot(elem),
|
285
|
+
**dict(
|
286
|
+
zip(
|
287
|
+
("dest_x", "dest_y", "dest_z"),
|
288
|
+
map(float, get_values(elem, "anmmov1")),
|
289
|
+
)
|
290
|
+
), # type: ignore[arg-type]
|
291
|
+
shape=PartModel.TileA30x90,
|
292
|
+
speed=float(get_values(elem, "anmspd")[0]),
|
293
|
+
switch=True,
|
294
|
+
)
|
295
|
+
)
|
296
|
+
case DeviceModel.MovingTile90x90ASwitch:
|
297
|
+
output.add(
|
298
|
+
MovingTile(
|
299
|
+
*get_pos_rot(elem),
|
300
|
+
**dict(
|
301
|
+
zip(
|
302
|
+
("dest_x", "dest_y", "dest_z"),
|
303
|
+
map(float, get_values(elem, "anmmov1")),
|
304
|
+
)
|
305
|
+
), # type: ignore[arg-type]
|
306
|
+
shape=PartModel.Tile90x90,
|
307
|
+
speed=float(get_values(elem, "anmspd")[0]),
|
308
|
+
switch=True,
|
309
|
+
)
|
310
|
+
)
|
311
|
+
case DeviceModel.MovingTile90x90BSwitch:
|
312
|
+
output.add(
|
313
|
+
MovingTile(
|
314
|
+
*get_pos_rot(elem),
|
315
|
+
**dict(
|
316
|
+
zip(
|
317
|
+
("dest_x", "dest_y", "dest_z"),
|
318
|
+
map(float, get_values(elem, "anmmov1")),
|
319
|
+
)
|
320
|
+
), # type: ignore[arg-type]
|
321
|
+
shape=PartModel.HoleB90x90,
|
322
|
+
speed=float(get_values(elem, "anmspd")[0]),
|
323
|
+
switch=True,
|
324
|
+
)
|
325
|
+
)
|
326
|
+
case DeviceModel.MovingFunnelPipe:
|
327
|
+
output.add(
|
328
|
+
MovingTile(
|
329
|
+
*get_pos_rot(elem),
|
330
|
+
**dict(
|
331
|
+
zip(
|
332
|
+
("dest_x", "dest_y", "dest_z"),
|
333
|
+
map(float, get_values(elem, "anmmov1")),
|
334
|
+
)
|
335
|
+
), # type: ignore[arg-type]
|
336
|
+
shape=PartModel.FunnelPipe,
|
337
|
+
speed=float(get_values(elem, "anmspd")[0]),
|
338
|
+
)
|
339
|
+
)
|
340
|
+
case DeviceModel.MovingStraightPipe:
|
341
|
+
output.add(
|
342
|
+
MovingTile(
|
343
|
+
*get_pos_rot(elem),
|
344
|
+
**dict(
|
345
|
+
zip(
|
346
|
+
("dest_x", "dest_y", "dest_z"),
|
347
|
+
map(float, get_values(elem, "anmmov1")),
|
348
|
+
)
|
349
|
+
), # type: ignore[arg-type]
|
350
|
+
shape=PartModel.StraightPipe,
|
351
|
+
speed=float(get_values(elem, "anmspd")[0]),
|
352
|
+
)
|
353
|
+
)
|
354
|
+
case DeviceModel.MovingCurveS:
|
355
|
+
output.add(
|
356
|
+
MovingCurve(
|
357
|
+
*get_pos_rot(elem),
|
358
|
+
shape=PartModel.CurveS,
|
359
|
+
speed=Speed(int(get_values(elem, "sts")[0])),
|
360
|
+
)
|
361
|
+
)
|
362
|
+
case DeviceModel.MovingCurveM:
|
363
|
+
output.add(
|
364
|
+
MovingCurve(
|
365
|
+
*get_pos_rot(elem),
|
366
|
+
shape=PartModel.CurveM,
|
367
|
+
speed=Speed(int(get_values(elem, "sts")[0])),
|
368
|
+
)
|
369
|
+
)
|
370
|
+
case DeviceModel.MovingCurveL:
|
371
|
+
output.add(
|
372
|
+
MovingCurve(
|
373
|
+
*get_pos_rot(elem),
|
374
|
+
shape=PartModel.CurveL,
|
375
|
+
speed=Speed(int(get_values(elem, "sts")[0])),
|
376
|
+
)
|
377
|
+
)
|
378
|
+
case DeviceModel.SlidingTile:
|
379
|
+
output.add(SlidingTile(*get_pos_rot(elem)))
|
380
|
+
case DeviceModel.ConveyorBelt:
|
381
|
+
output.add(
|
382
|
+
ConveyorBelt(
|
383
|
+
*get_pos_rot(elem),
|
384
|
+
reversing=get_values(elem, "sts")[0] == "39",
|
385
|
+
)
|
386
|
+
)
|
387
|
+
case DeviceModel.DashTunnelA:
|
388
|
+
output.add(
|
389
|
+
DashTunnel(
|
390
|
+
*get_pos_rot(elem),
|
391
|
+
shape=DeviceModel.DashTunnelA,
|
392
|
+
)
|
393
|
+
)
|
394
|
+
case DeviceModel.DashTunnelB:
|
395
|
+
output.add(
|
396
|
+
DashTunnel(
|
397
|
+
*get_pos_rot(elem),
|
398
|
+
shape=DeviceModel.DashTunnelB,
|
399
|
+
)
|
400
|
+
)
|
401
|
+
case DeviceModel.SeesawLBlock:
|
402
|
+
output.add(
|
403
|
+
SeesawBlock(
|
404
|
+
*get_pos_rot(elem),
|
405
|
+
shape=DeviceModel.SeesawLBlock,
|
406
|
+
)
|
407
|
+
)
|
408
|
+
case DeviceModel.SeesawIBlock:
|
409
|
+
output.add(
|
410
|
+
SeesawBlock(
|
411
|
+
*get_pos_rot(elem),
|
412
|
+
shape=DeviceModel.SeesawIBlock,
|
413
|
+
)
|
414
|
+
)
|
415
|
+
case DeviceModel.AutoSeesawLBlock:
|
416
|
+
output.add(
|
417
|
+
SeesawBlock(
|
418
|
+
*get_pos_rot(elem),
|
419
|
+
auto=True,
|
420
|
+
shape=DeviceModel.SeesawLBlock,
|
421
|
+
)
|
422
|
+
)
|
423
|
+
case DeviceModel.AutoSeesawIBlock:
|
424
|
+
output.add(
|
425
|
+
SeesawBlock(
|
426
|
+
*get_pos_rot(elem),
|
427
|
+
auto=True,
|
428
|
+
shape=DeviceModel.SeesawIBlock,
|
429
|
+
)
|
430
|
+
)
|
431
|
+
case DeviceModel.Cannon:
|
432
|
+
output.add(Cannon(*get_pos_rot(elem)))
|
433
|
+
case DeviceModel.Drawbridge:
|
434
|
+
output.add(Drawbridge(*get_pos_rot(elem)))
|
435
|
+
case DeviceModel.Turntable:
|
436
|
+
output.add(
|
437
|
+
Turntable(
|
438
|
+
*get_pos_rot(elem),
|
439
|
+
speed=Speed(int(get_values(elem, "sts")[0])),
|
440
|
+
)
|
441
|
+
)
|
442
|
+
case DeviceModel.Bumper:
|
443
|
+
output.add(Bumper(*get_pos_rot(elem)))
|
444
|
+
case DeviceModel.PowerfulBumper:
|
445
|
+
output.add(Bumper(*get_pos_rot(elem), powerful=True))
|
446
|
+
case DeviceModel.Thorn:
|
447
|
+
output.add(Thorn(*get_pos_rot(elem)))
|
448
|
+
case DeviceModel.Gear:
|
449
|
+
output.add(
|
450
|
+
Gear(
|
451
|
+
*get_pos_rot(elem),
|
452
|
+
speed=Speed(int(get_values(elem, "sts")[0])),
|
453
|
+
)
|
454
|
+
)
|
455
|
+
case DeviceModel.Fan:
|
456
|
+
output.add(Fan(*get_pos_rot(elem)))
|
457
|
+
case DeviceModel.PowerfulFan:
|
458
|
+
output.add(
|
459
|
+
Fan(
|
460
|
+
*get_pos_rot(elem),
|
461
|
+
wind_pattern=DeviceModel.PowerfulFan,
|
462
|
+
)
|
463
|
+
)
|
464
|
+
case DeviceModel.TimerFan:
|
465
|
+
output.add(
|
466
|
+
Fan(
|
467
|
+
*get_pos_rot(elem),
|
468
|
+
wind_pattern=DeviceModel.TimerFan,
|
469
|
+
)
|
470
|
+
)
|
471
|
+
case DeviceModel.Spring:
|
472
|
+
output.add(Spring(*get_pos_rot(elem)))
|
473
|
+
case DeviceModel.Punch:
|
474
|
+
output.add(
|
475
|
+
Punch(
|
476
|
+
*get_pos_rot(elem),
|
477
|
+
timing=MovementTiming(
|
478
|
+
int(get_values(elem, "sts")[0])
|
479
|
+
),
|
480
|
+
)
|
481
|
+
)
|
482
|
+
case DeviceModel.Press:
|
483
|
+
output.add(
|
484
|
+
Press(
|
485
|
+
*get_pos_rot(elem),
|
486
|
+
timing=MovementTiming(
|
487
|
+
int(get_values(elem, "sts")[0])
|
488
|
+
),
|
489
|
+
)
|
490
|
+
)
|
491
|
+
case DeviceModel.Scissors:
|
492
|
+
output.add(
|
493
|
+
Scissors(
|
494
|
+
*get_pos_rot(elem),
|
495
|
+
timing=MovementTiming(
|
496
|
+
int(get_values(elem, "sts")[0])
|
497
|
+
),
|
498
|
+
)
|
499
|
+
)
|
500
|
+
case DeviceModel.MagnifyingGlass:
|
501
|
+
output.add(MagnifyingGlass(*get_pos_rot(elem)))
|
502
|
+
case DeviceModel.UpsideDownStageDevice:
|
503
|
+
output.add(UpsideDownStageDevice(*get_pos_rot(elem)))
|
504
|
+
case DeviceModel.UpsideDownBall:
|
505
|
+
output.add(UpsideDownBall(*get_pos_rot(elem)))
|
506
|
+
case DeviceModel.SmallTunnel:
|
507
|
+
output.add(
|
508
|
+
SizeTunnel(
|
509
|
+
*get_pos_rot(elem), size=DeviceModel.SmallTunnel
|
510
|
+
)
|
511
|
+
)
|
512
|
+
case DeviceModel.BigTunnel:
|
513
|
+
output.add(
|
514
|
+
SizeTunnel(
|
515
|
+
*get_pos_rot(elem), size=DeviceModel.BigTunnel
|
516
|
+
)
|
517
|
+
)
|
518
|
+
case DeviceModel.BlinkingTile:
|
519
|
+
output.add(
|
520
|
+
BlinkingTile(
|
521
|
+
*get_pos_rot(elem),
|
522
|
+
timing=MovementTiming(
|
523
|
+
int(get_values(elem, "sts")[0])
|
524
|
+
),
|
525
|
+
)
|
526
|
+
)
|
527
|
+
case DeviceModel.CubicTextBox:
|
528
|
+
output.add(
|
529
|
+
TextBox(
|
530
|
+
*get_pos_rot(elem),
|
531
|
+
shape=DeviceModel.CubicTextBox,
|
532
|
+
text_id=int(get_values(elem, "sts")[0]),
|
533
|
+
)
|
534
|
+
)
|
535
|
+
case DeviceModel.WallTextBox:
|
536
|
+
output.add(
|
537
|
+
TextBox(
|
538
|
+
*get_pos_rot(elem),
|
539
|
+
shape=DeviceModel.WallTextBox,
|
540
|
+
text_id=int(get_values(elem, "sts")[0]),
|
541
|
+
)
|
542
|
+
)
|
543
|
+
case DeviceModel.KororinCapsule:
|
544
|
+
output.add(KororinCapsule(*get_pos_rot(elem)))
|
545
|
+
case DeviceModel.GreenCrystal:
|
546
|
+
output.add(GreenCrystal(*get_pos_rot(elem)))
|
547
|
+
case DeviceModel.Ant:
|
548
|
+
output.add(Ant(*get_pos_rot(elem)))
|
549
|
+
case model if model.name.startswith("MelodyTile"):
|
550
|
+
output.add(MelodyTile(*get_pos_rot(elem), note=model)) # type: ignore[arg-type]
|
551
|
+
case _:
|
552
|
+
groups.setdefault(int(get_values(elem, "group")[0]), {})[
|
553
|
+
int(get_values(elem, "group")[1])
|
554
|
+
] = elem
|
555
|
+
for group in groups.values():
|
556
|
+
match DeviceModel(int(get_values(group[0], "model")[1])):
|
557
|
+
case DeviceModel.EndMagnet:
|
558
|
+
m: Magnet = Magnet()
|
559
|
+
for _, elem in sorted(group.items()):
|
560
|
+
m.append(
|
561
|
+
MagnetSegment(
|
562
|
+
*get_pos_rot(elem),
|
563
|
+
shape=DeviceModel(int(get_values(elem, "model")[1])), # type: ignore[arg-type]
|
564
|
+
)
|
565
|
+
)
|
566
|
+
output.add(m)
|
567
|
+
case DeviceModel.ToyTrain:
|
568
|
+
t: ToyTrain = ToyTrain(*get_pos_rot(group[0]))
|
569
|
+
for i, elem in sorted(group.items()):
|
570
|
+
if i:
|
571
|
+
t.append(
|
572
|
+
TrainTrack(
|
573
|
+
*get_pos_rot(elem),
|
574
|
+
shape=DeviceModel(int(get_values(elem, "model")[1])), # type: ignore[arg-type]
|
575
|
+
)
|
576
|
+
)
|
577
|
+
output.add(t)
|
578
|
+
case DeviceModel.Warp:
|
579
|
+
output.add(
|
580
|
+
Warp(
|
581
|
+
*get_pos_rot(group[0]),
|
582
|
+
**dict(
|
583
|
+
zip(
|
584
|
+
("dest_x", "dest_y", "dest_z"),
|
585
|
+
map(float, get_values(group[0], "anmmov0")),
|
586
|
+
)
|
587
|
+
),
|
588
|
+
**dict(
|
589
|
+
zip(
|
590
|
+
(
|
591
|
+
"return_x_pos",
|
592
|
+
"return_y_pos",
|
593
|
+
"return_z_pos",
|
594
|
+
"return_x_rot",
|
595
|
+
"return_y_rot",
|
596
|
+
"return_z_rot",
|
597
|
+
),
|
598
|
+
get_pos_rot(group[1]),
|
599
|
+
strict=True,
|
600
|
+
)
|
601
|
+
),
|
602
|
+
**dict(
|
603
|
+
zip(
|
604
|
+
("return_dest_x", "return_dest_y", "return_dest_z"),
|
605
|
+
map(float, get_values(group[1], "anmmov0")),
|
606
|
+
)
|
607
|
+
),
|
608
|
+
) # type: ignore[misc]
|
609
|
+
)
|
610
|
+
return output
|
611
|
+
|
612
|
+
@staticmethod
|
613
|
+
def serialize(stage: Stage, /) -> bytes:
|
614
|
+
def minify(value: float, /) -> str:
|
615
|
+
"""Removes the decimal point from floats representing integers."""
|
616
|
+
return str(int(value) if value.is_integer() else value)
|
617
|
+
|
618
|
+
def serialize_numbers(*values: float) -> str:
|
619
|
+
"""Does not include leading or trailing spaces."""
|
620
|
+
return " ".join(minify(value) for value in values)
|
621
|
+
|
622
|
+
def anmtype(device: BasePart, /) -> DeviceModel:
|
623
|
+
if isinstance(device, ProgressMarker):
|
624
|
+
return (
|
625
|
+
DeviceModel.Crystal if device.progress % 2 else DeviceModel.Respawn
|
626
|
+
)
|
627
|
+
elif isinstance(device, MovingTile):
|
628
|
+
match device.shape:
|
629
|
+
case PartModel.Tile10x10:
|
630
|
+
return (
|
631
|
+
DeviceModel.MovingTile10x10Switch
|
632
|
+
if device.switch
|
633
|
+
else DeviceModel.MovingTile10x10
|
634
|
+
)
|
635
|
+
case PartModel.Tile20x20:
|
636
|
+
return (
|
637
|
+
DeviceModel.MovingTile20x20Switch
|
638
|
+
if device.switch
|
639
|
+
else DeviceModel.MovingTile20x20
|
640
|
+
)
|
641
|
+
case PartModel.TileA30x30:
|
642
|
+
return (
|
643
|
+
DeviceModel.MovingTile30x30Switch
|
644
|
+
if device.switch
|
645
|
+
else DeviceModel.MovingTile30x30
|
646
|
+
)
|
647
|
+
case PartModel.TileA30x90:
|
648
|
+
return (
|
649
|
+
DeviceModel.MovingTile30x90Switch
|
650
|
+
if device.switch
|
651
|
+
else DeviceModel.MovingTile30x90
|
652
|
+
)
|
653
|
+
case PartModel.Tile90x90:
|
654
|
+
return (
|
655
|
+
DeviceModel.MovingTile90x90ASwitch
|
656
|
+
if device.switch
|
657
|
+
else DeviceModel.MovingTile90x90A
|
658
|
+
)
|
659
|
+
case PartModel.HoleB90x90:
|
660
|
+
return (
|
661
|
+
DeviceModel.MovingTile90x90BSwitch
|
662
|
+
if device.switch
|
663
|
+
else DeviceModel.MovingTile90x90B
|
664
|
+
)
|
665
|
+
case PartModel.FunnelPipe:
|
666
|
+
return DeviceModel.MovingFunnelPipe
|
667
|
+
case PartModel.StraightPipe:
|
668
|
+
return DeviceModel.MovingStraightPipe
|
669
|
+
elif isinstance(device, MovingCurve):
|
670
|
+
match device.shape:
|
671
|
+
case PartModel.CurveS:
|
672
|
+
return DeviceModel.MovingCurveS
|
673
|
+
case PartModel.CurveM:
|
674
|
+
return DeviceModel.MovingCurveM
|
675
|
+
case PartModel.CurveL:
|
676
|
+
return DeviceModel.MovingCurveL
|
677
|
+
elif isinstance(device, SlidingTile):
|
678
|
+
return DeviceModel.SlidingTile
|
679
|
+
elif isinstance(device, ConveyorBelt):
|
680
|
+
return DeviceModel.ConveyorBelt
|
681
|
+
elif isinstance(device, (DashTunnel, TextBox)):
|
682
|
+
return device.shape
|
683
|
+
elif isinstance(device, SeesawBlock):
|
684
|
+
if device.auto:
|
685
|
+
match device.shape:
|
686
|
+
case DeviceModel.SeesawLBlock:
|
687
|
+
return DeviceModel.AutoSeesawLBlock
|
688
|
+
case DeviceModel.SeesawIBlock:
|
689
|
+
return DeviceModel.AutoSeesawIBlock
|
690
|
+
else:
|
691
|
+
return device.shape
|
692
|
+
elif isinstance(device, Cannon):
|
693
|
+
return DeviceModel.Cannon
|
694
|
+
elif isinstance(device, Drawbridge):
|
695
|
+
return DeviceModel.Drawbridge
|
696
|
+
elif isinstance(device, Turntable):
|
697
|
+
return DeviceModel.Turntable
|
698
|
+
elif isinstance(device, Bumper):
|
699
|
+
return (
|
700
|
+
DeviceModel.PowerfulBumper
|
701
|
+
if device.powerful
|
702
|
+
else DeviceModel.Bumper
|
703
|
+
)
|
704
|
+
elif isinstance(device, Thorn):
|
705
|
+
return DeviceModel.Thorn
|
706
|
+
elif isinstance(device, Gear):
|
707
|
+
return DeviceModel.Gear
|
708
|
+
elif isinstance(device, Fan):
|
709
|
+
return device.wind_pattern
|
710
|
+
elif isinstance(device, Spring):
|
711
|
+
return DeviceModel.Spring
|
712
|
+
elif isinstance(device, Punch):
|
713
|
+
return DeviceModel.Punch
|
714
|
+
elif isinstance(device, Press):
|
715
|
+
return DeviceModel.Press
|
716
|
+
elif isinstance(device, Scissors):
|
717
|
+
return DeviceModel.Scissors
|
718
|
+
elif isinstance(device, MagnifyingGlass):
|
719
|
+
return DeviceModel.MagnifyingGlass
|
720
|
+
elif isinstance(device, UpsideDownStageDevice):
|
721
|
+
return DeviceModel.UpsideDownStageDevice
|
722
|
+
elif isinstance(device, UpsideDownBall):
|
723
|
+
return DeviceModel.UpsideDownBall
|
724
|
+
elif isinstance(device, SizeTunnel):
|
725
|
+
return device.size
|
726
|
+
elif isinstance(device, BlinkingTile):
|
727
|
+
return DeviceModel.BlinkingTile
|
728
|
+
elif isinstance(device, MelodyTile):
|
729
|
+
return device.note
|
730
|
+
elif isinstance(device, KororinCapsule):
|
731
|
+
return DeviceModel.KororinCapsule
|
732
|
+
elif isinstance(device, GreenCrystal):
|
733
|
+
return DeviceModel.GreenCrystal
|
734
|
+
elif isinstance(device, Ant):
|
735
|
+
return DeviceModel.Ant
|
736
|
+
else:
|
737
|
+
raise ValueError(f"part {device!r} does not have a known anmtype")
|
738
|
+
|
739
|
+
def device_data(device: BasePart, /) -> str:
|
740
|
+
if isinstance(device, ProgressMarker):
|
741
|
+
return f"<hook> {(device._progress - 1) // 2} 0 </hook>\n"
|
742
|
+
elif isinstance(device, MovingTile):
|
743
|
+
anmmov: Final[str] = (
|
744
|
+
f"<anmspd> {minify(device.speed)} 0 </anmspd>\n<anmmov0> {serialize_numbers(device.x_pos, device.y_pos, device.z_pos)} </anmmov0>\n<anmmov1> {serialize_numbers(device.dest_x, device.dest_y, device.dest_z)} </anmmov1>"
|
745
|
+
)
|
746
|
+
if device.walls:
|
747
|
+
match device.shape:
|
748
|
+
case PartModel.Tile20x20:
|
749
|
+
return f"<hook> {DeviceModel.MovingTile20x20Wall.value} {device.walls.value} </hook>\n{anmmov}\n"
|
750
|
+
case PartModel.TileA30x30:
|
751
|
+
return f"<hook> {DeviceModel.MovingTile30x30Wall.value} {device.walls.value} </hook>\n{anmmov}\n"
|
752
|
+
return anmmov
|
753
|
+
else:
|
754
|
+
return ""
|
755
|
+
|
756
|
+
def sts(part: BasePart, /) -> int:
|
757
|
+
if isinstance(part, FixedSpeedDevice):
|
758
|
+
return part.speed.value
|
759
|
+
elif isinstance(part, ConveyorBelt):
|
760
|
+
return 39 if part.reversing else 23
|
761
|
+
elif isinstance(part, TimedDevice):
|
762
|
+
return part.timing.value
|
763
|
+
else:
|
764
|
+
return 7
|
765
|
+
|
766
|
+
with StringIO(
|
767
|
+
f'<?xml version="1.0" encoding="SHIFT_JIS"?>\n<EDITINFO>\n<THEME> {stage.theme.value} </THEME>\n<LOCK> {int(stage.tilt_lock)} </LOCK>\n<EDITUSER> {stage.edit_user.value} </EDITUSER>\n</EDITINFO>\n<STAGEDATA>\n<EDIT_BG_NORMAL>\n<model> "EBB_{stage.theme.value:02}.bin 0 </model>\n</EDIT_BG_NORMAL>\n'
|
768
|
+
) as output:
|
769
|
+
output.seek(0, SEEK_END)
|
770
|
+
group: int = 1
|
771
|
+
for part in stage:
|
772
|
+
if isinstance(part, Magnet):
|
773
|
+
for i, segment in enumerate(part):
|
774
|
+
output.write(
|
775
|
+
f'<EDIT_GIM_NORMAL>\n<model> "EGB_{stage.theme.value:02}.bin" {segment.shape.value} </model>\n<pos> {serialize_numbers(segment.x_pos, segment.y_pos, segment.z_pos)} </pos>\n<rot> {serialize_numbers(segment.x_rot, segment.y_rot, segment.z_rot)} </rot>\n<sts> 7 </sts>\n<group> {group} {i} </group>\n</EDIT_GIM_NORMAL>\n'
|
776
|
+
)
|
777
|
+
group += 1
|
778
|
+
elif isinstance(part, ToyTrain):
|
779
|
+
output.write(
|
780
|
+
f'<EDIT_GIM_NORMAL>\n<model> "EGB_{stage.theme.value:02}.bin" {DeviceModel.ToyTrain.value} </model>\n<pos> {serialize_numbers(part.x_pos, part.y_pos, part.z_pos)} </pos>\n<rot> {serialize_numbers(part.x_rot, part.y_rot, part.z_rot)} </rot>\n<sts> 7 </sts>\n<group> {group} 0 </group>\n</EDIT_GIM_NORMAL>\n'
|
781
|
+
)
|
782
|
+
for i, track in enumerate(part, 1):
|
783
|
+
output.write(
|
784
|
+
f'<EDIT_GIM_NORMAL>\n<model> "EGB_{stage.theme.value:02}.bin" {track.shape.value} </model>\n<pos> {serialize_numbers(track.x_pos, track.y_pos, track.z_pos)} </pos>\n<rot> {serialize_numbers(track.x_rot, track.y_rot, track.z_rot)} </rot>\n<sts> 7 </sts>\n<group> {group} {i} </group>\n</EDIT_GIM_NORMAL>\n'
|
785
|
+
)
|
786
|
+
group += 1
|
787
|
+
elif isinstance(part, Warp):
|
788
|
+
output.write(
|
789
|
+
f'<EDIT_GIM_NORMAL>\n<model> "EGB_{stage.theme.value:02}.bin" {DeviceModel.Warp.value} </model>\n<pos> {serialize_numbers(part.x_pos, part.y_pos, part.z_pos)} </pos>\n<rot> {serialize_numbers(part.x_rot, part.y_rot, part.z_rot)} </rot>\n<sts> 7 </sts>\n<anmmov0> {serialize_numbers(part.dest_x, part.dest_y, part.dest_z)} </anmmov0>\n<group> {group} 0 </group>\n</EDIT_GIM_NORMAL>\n<EDIT_GIM_NORMAL>\n<model> "EGB_{stage.theme.value:02}.bin" {DeviceModel.Warp.value} </model>\n<pos> {serialize_numbers(part.return_x_pos, part.return_y_pos, part.return_z_pos)} </pos>\n<rot> {serialize_numbers(part.return_x_rot, part.return_y_rot, part.return_z_rot)} </rot>\n<sts> 7 </sts>\n<anmmov0> {serialize_numbers(part.return_dest_x, part.return_dest_y, part.return_dest_z)} </anmmov0>\n<group> {group} 1 </group>\n</EDIT_GIM_NORMAL>\n'
|
790
|
+
)
|
791
|
+
group += 1
|
792
|
+
else:
|
793
|
+
if isinstance(part, Start):
|
794
|
+
output.write("<EDIT_GIM_START>\n")
|
795
|
+
elif isinstance(part, Goal):
|
796
|
+
output.write("<EDIT_GIM_GOAL>\n")
|
797
|
+
elif isinstance(part, Part):
|
798
|
+
if isinstance(part.shape, DecorationModel):
|
799
|
+
output.write("<EDIT_MAP_EXT>\n")
|
800
|
+
else:
|
801
|
+
output.write("<EDIT_MAP_NORMAL>\n")
|
802
|
+
else:
|
803
|
+
output.write("<EDIT_GIM_NORMAL>\n")
|
804
|
+
if isinstance(part, Part):
|
805
|
+
if isinstance(part.shape, DecorationModel):
|
806
|
+
output.write(
|
807
|
+
f'<model> "EME_{stage.theme.value:02}.bin" {part.shape.value} </model>\n'
|
808
|
+
)
|
809
|
+
else:
|
810
|
+
output.write(
|
811
|
+
f'<model> "EMB_{stage.theme.value:02}.bin" {part.shape.value} </model>\n'
|
812
|
+
)
|
813
|
+
elif isinstance(part, Start):
|
814
|
+
output.write(
|
815
|
+
f'<model> "EGB_{stage.theme.value:02}.bin" 0 </model>\n'
|
816
|
+
)
|
817
|
+
elif isinstance(part, Goal):
|
818
|
+
output.write(
|
819
|
+
f'<model> "EGB_{stage.theme.value:02}.bin" 1 </model>\n'
|
820
|
+
)
|
821
|
+
else:
|
822
|
+
output.write(
|
823
|
+
f'<model> "EGB_{stage.theme.value:02}.bin" {anmtype(part).value} </model>\n'
|
824
|
+
)
|
825
|
+
output.write(
|
826
|
+
f"<pos> {serialize_numbers(part.x_pos, part.y_pos, part.z_pos)} </pos>\n<rot> {serialize_numbers(part.x_rot, part.y_rot, part.z_rot)} </rot>\n<sts> {sts(part)} </sts>\n"
|
827
|
+
)
|
828
|
+
try:
|
829
|
+
output.write(f"<anmtype> {anmtype(part).value} </anmtype>\n")
|
830
|
+
except ValueError:
|
831
|
+
pass
|
832
|
+
output.write(device_data(part))
|
833
|
+
if isinstance(part, Start):
|
834
|
+
output.write("</EDIT_GIM_START>\n")
|
835
|
+
elif isinstance(part, Goal):
|
836
|
+
output.write("</EDIT_GIM_GOAL>\n")
|
837
|
+
elif isinstance(part, Part):
|
838
|
+
if isinstance(part.shape, DecorationModel):
|
839
|
+
output.write("</EDIT_MAP_EXT>\n")
|
840
|
+
else:
|
841
|
+
output.write("</EDIT_MAP_NORMAL>\n")
|
842
|
+
else:
|
843
|
+
output.write("</EDIT_GIM_NORMAL>\n")
|
844
|
+
output.write("</STAGEDATA>")
|
845
|
+
return output.getvalue().encode("shift_jis", "xmlcharrefreplace")
|