koro 1.1.6__py3-none-any.whl → 2.0.0rc2__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/stage/part.py ADDED
@@ -0,0 +1,1704 @@
1
+ from __future__ import annotations
2
+
3
+ from abc import ABC, abstractmethod
4
+ from collections.abc import Iterable, MutableSequence
5
+ from enum import Enum, unique, Flag
6
+ from sys import maxsize
7
+ from typing import Any, Final, Iterator, Literal, Self, SupportsIndex, overload
8
+
9
+ from .model import DecorationModel, DeviceModel, PartModel
10
+
11
+
12
+ __all__ = [
13
+ "Ant",
14
+ "BasePart",
15
+ "BlinkingTile",
16
+ "Bumper",
17
+ "Cannon",
18
+ "ConveyorBelt",
19
+ "DashTunnel",
20
+ "Drawbridge",
21
+ "Fan",
22
+ "FixedSpeedDevice",
23
+ "Gear",
24
+ "Goal",
25
+ "GreenCrystal",
26
+ "KororinCapsule",
27
+ "Magnet",
28
+ "MagnetSegment",
29
+ "MagnifyingGlass",
30
+ "MelodyTile",
31
+ "MovingCurve",
32
+ "MovingTile",
33
+ "Part",
34
+ "Press",
35
+ "ProgressMarker",
36
+ "Punch",
37
+ "Scissors",
38
+ "SeesawBlock",
39
+ "SizeTunnel",
40
+ "SlidingTile",
41
+ "Spring",
42
+ "Start",
43
+ "Thorn",
44
+ "TimedDevice",
45
+ "ToyTrain",
46
+ "TrainTrack",
47
+ "Turntable",
48
+ "UpsideDownBall",
49
+ "UpsideDownStageDevice",
50
+ "Walls",
51
+ "Warp",
52
+ ]
53
+
54
+
55
+ class BasePart(ABC):
56
+ """Base class for all level elements"""
57
+
58
+ __match_args__ = ("x_pos", "y_pos", "z_pos", "x_rot", "y_rot", "z_rot")
59
+ __slots__ = ("_x_pos", "_x_rot", "_y_pos", "_y_rot", "_z_pos", "_z_rot")
60
+
61
+ _x_pos: float
62
+ _x_rot: float
63
+ _y_pos: float
64
+ _y_rot: float
65
+ _z_pos: float
66
+ _z_rot: float
67
+
68
+ def __init__(
69
+ self,
70
+ x_pos: float,
71
+ y_pos: float,
72
+ z_pos: float,
73
+ x_rot: float,
74
+ y_rot: float,
75
+ z_rot: float,
76
+ ) -> None:
77
+ self.x_pos = x_pos
78
+ self.x_rot = x_rot
79
+ self.y_pos = y_pos
80
+ self.y_rot = y_rot
81
+ self.z_pos = z_pos
82
+ self.z_rot = z_rot
83
+
84
+ @property
85
+ @abstractmethod
86
+ def cost(self) -> int:
87
+ """The number of kororin points that this part costs to place."""
88
+
89
+ def __repr__(self) -> str:
90
+ return f"{type(self).__name__}({self.x_pos!r}, {self.y_pos!r}, {self.z_pos!r}, {self.x_rot!r}, {self.y_rot!r}, {self.z_rot!r})"
91
+
92
+ @property
93
+ def x_pos(self) -> float:
94
+ """Positive is right, negative is left"""
95
+ return self._x_pos
96
+
97
+ @x_pos.setter
98
+ def x_pos(self, value: float, /) -> None:
99
+ self._x_pos = value
100
+
101
+ @property
102
+ def x_rot(self) -> float:
103
+ """Represented in degrees from 0 to 360
104
+ Positive turns top face front, negative turns top face back
105
+ """
106
+ return self._x_rot
107
+
108
+ @x_rot.setter
109
+ def x_rot(self, value: float, /) -> None:
110
+ self._x_rot = value % 360
111
+
112
+ @property
113
+ def y_pos(self) -> float:
114
+ """Positive is up, negative is down"""
115
+ return self._y_pos
116
+
117
+ @y_pos.setter
118
+ def y_pos(self, value: float, /) -> None:
119
+ self._y_pos = value
120
+
121
+ @property
122
+ def y_rot(self) -> float:
123
+ """Represented in degrees from 0 to 360
124
+ Positive turns front face right, negative turns front face left
125
+ """
126
+ return self._y_rot
127
+
128
+ @y_rot.setter
129
+ def y_rot(self, value: float, /) -> None:
130
+ self._y_rot = value % 360
131
+
132
+ @property
133
+ def z_pos(self) -> float:
134
+ """Positive is front, negative is back"""
135
+ return self._z_pos
136
+
137
+ @z_pos.setter
138
+ def z_pos(self, value: float, /) -> None:
139
+ self._z_pos = value
140
+
141
+ @property
142
+ def z_rot(self) -> float:
143
+ """Represented in degrees from 0 to 360
144
+ Positive turns top face left, negative turns top face right
145
+ """
146
+ return self._z_rot
147
+
148
+ @z_rot.setter
149
+ def z_rot(self, value: float, /) -> None:
150
+ self._z_rot = value % 360
151
+
152
+
153
+ class Part(BasePart):
154
+ """(Usually) static model that has no behavior other than being solid."""
155
+
156
+ __slots__ = ("_shape",)
157
+
158
+ _shape: PartModel | DecorationModel
159
+
160
+ def __init__(
161
+ self,
162
+ x_pos: float,
163
+ y_pos: float,
164
+ z_pos: float,
165
+ x_rot: float,
166
+ y_rot: float,
167
+ z_rot: float,
168
+ *,
169
+ shape: PartModel | DecorationModel,
170
+ ) -> None:
171
+ super().__init__(x_pos, y_pos, z_pos, x_rot, y_rot, z_rot)
172
+ self.shape = shape
173
+
174
+ @property
175
+ def cost(self) -> Literal[10, 15, 20]:
176
+ if isinstance(self.shape, DecorationModel):
177
+ return 20
178
+ elif self.shape in frozenset(
179
+ {
180
+ PartModel.MagmaTile,
181
+ PartModel.SlipperyTile,
182
+ PartModel.StickyTile,
183
+ PartModel.InvisibleTile,
184
+ }
185
+ ):
186
+ return 15
187
+ else:
188
+ return 10
189
+
190
+ @property
191
+ def shape(self) -> PartModel | DecorationModel:
192
+ return self._shape
193
+
194
+ @shape.setter
195
+ def shape(self, value: PartModel | DecorationModel, /) -> None:
196
+ self._shape = value
197
+
198
+ def __repr__(self) -> str:
199
+ return f"{type(self).__name__}({self.x_pos!r}, {self.y_pos!r}, {self.z_pos!r}, {self.x_rot!r}, {self.y_rot!r}, {self.z_rot!r}, shape={self.shape!r})"
200
+
201
+
202
+ class Start(BasePart):
203
+ __slots__ = ()
204
+
205
+ @property
206
+ def cost(self) -> Literal[0]:
207
+ return 0
208
+
209
+
210
+ class Goal(BasePart):
211
+ __slots__ = ()
212
+
213
+ @property
214
+ def cost(self) -> Literal[0]:
215
+ return 0
216
+
217
+
218
+ class ProgressMarker(BasePart):
219
+ """Either a crystal (when progress is odd) or a respawn (when progress is even)"""
220
+
221
+ __slots__ = ("_progress",)
222
+
223
+ _progress: Literal[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
224
+
225
+ def __init__(
226
+ self,
227
+ x_pos: float,
228
+ y_pos: float,
229
+ z_pos: float,
230
+ x_rot: float,
231
+ y_rot: float,
232
+ z_rot: float,
233
+ *,
234
+ progress: Literal[1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
235
+ ) -> None:
236
+ super().__init__(x_pos, y_pos, z_pos, x_rot, y_rot, z_rot)
237
+ self.progress = progress
238
+
239
+ @property
240
+ def cost(self) -> Literal[15, 0]:
241
+ return 15 if self.progress & 1 else 0
242
+
243
+ @property
244
+ def progress(self) -> Literal[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]:
245
+ """The number displayed above the object. Controls which crystals are required to enable a respawn."""
246
+ return self._progress
247
+
248
+ @progress.setter
249
+ def progress(self, value: Literal[1, 2, 3, 4, 5, 6, 7, 8, 9, 10], /):
250
+ self._progress = value
251
+
252
+ def __repr__(self) -> str:
253
+ return f"{type(self).__name__}({self.x_pos!r}, {self.y_pos!r}, {self.z_pos!r}, {self.x_rot!r}, {self.y_rot!r}, {self.z_rot!r}, progress={self.progress!r})"
254
+
255
+
256
+ @unique
257
+ class Speed(Enum):
258
+ SLOW = 55
259
+ NORMAL = 39
260
+ FAST = 23
261
+
262
+
263
+ @unique
264
+ class Walls(Flag):
265
+ BACK = 8
266
+ FRONT = 2
267
+ LEFT = 4
268
+ RIGHT = 1
269
+
270
+
271
+ class MovingTile(BasePart):
272
+ """A part that moves in a straight line."""
273
+
274
+ __slots__ = (
275
+ "_dest_x",
276
+ "_dest_y",
277
+ "_dest_z",
278
+ "_shape",
279
+ "_speed",
280
+ "_switch",
281
+ "_walls",
282
+ )
283
+
284
+ _dest_x: float
285
+ _dest_y: float
286
+ _dest_z: float
287
+ _shape: Literal[
288
+ PartModel.Tile10x10,
289
+ PartModel.Tile20x20,
290
+ PartModel.TileA30x30,
291
+ PartModel.TileA30x90,
292
+ PartModel.Tile90x90,
293
+ PartModel.HoleB90x90,
294
+ PartModel.FunnelPipe,
295
+ PartModel.StraightPipe,
296
+ ]
297
+ _speed: float
298
+ _switch: bool
299
+ _walls: Walls
300
+
301
+ def __init__(
302
+ self,
303
+ x_pos: float,
304
+ y_pos: float,
305
+ z_pos: float,
306
+ x_rot: float,
307
+ y_rot: float,
308
+ z_rot: float,
309
+ *,
310
+ dest_x: float,
311
+ dest_y: float,
312
+ dest_z: float,
313
+ shape: Literal[
314
+ PartModel.Tile10x10,
315
+ PartModel.Tile20x20,
316
+ PartModel.TileA30x30,
317
+ PartModel.TileA30x90,
318
+ PartModel.Tile90x90,
319
+ PartModel.HoleB90x90,
320
+ PartModel.FunnelPipe,
321
+ PartModel.StraightPipe,
322
+ ],
323
+ speed: Speed | float = Speed.NORMAL,
324
+ switch: bool = False,
325
+ walls: Walls = Walls(0),
326
+ ) -> None:
327
+ super().__init__(x_pos, y_pos, z_pos, x_rot, y_rot, z_rot)
328
+ self.dest_x = dest_x
329
+ self.dest_y = dest_y
330
+ self.dest_z = dest_z
331
+ self.shape = shape
332
+ self.speed = speed # type: ignore[assignment]
333
+ self.switch = switch
334
+ self.walls = walls
335
+
336
+ @property
337
+ def cost(self) -> Literal[20, 70, 35, 40, 50, 65]:
338
+ match self.shape:
339
+ case PartModel.Tile10x10:
340
+ return 20
341
+ case PartModel.Tile20x20 | PartModel.TileA30x30:
342
+ return 70
343
+ case PartModel.TileA30x90:
344
+ return 35
345
+ case PartModel.Tile90x90 | PartModel.HoleB90x90:
346
+ return 40
347
+ case PartModel.FunnelPipe:
348
+ return 50
349
+ case PartModel.StraightPipe:
350
+ return 65
351
+
352
+ @property
353
+ def dest_x(self) -> float:
354
+ """Positive is right, negative is left"""
355
+ return self._dest_x
356
+
357
+ @dest_x.setter
358
+ def dest_x(self, value: float, /) -> None:
359
+ self._dest_x = value
360
+
361
+ @property
362
+ def dest_y(self) -> float:
363
+ return self._dest_y
364
+
365
+ @dest_y.setter
366
+ def dest_y(self, value: float, /) -> None:
367
+ """Positive is up, negative is down"""
368
+ self._dest_y = value
369
+
370
+ @property
371
+ def dest_z(self) -> float:
372
+ """Positive is front, negative is back"""
373
+ return self._dest_z
374
+
375
+ @dest_z.setter
376
+ def dest_z(self, value: float, /) -> None:
377
+ self._dest_z = value
378
+
379
+ def __repr__(self) -> str:
380
+ output: Final[list[str]] = [
381
+ f"{type(self).__name__}({self.x_pos!r}, {self.y_pos!r}, {self.z_pos!r}, {self.x_rot!r}, {self.y_rot!r}, {self.z_rot!r}, dest_x={self.dest_x!r}, dest_y={self.dest_y!r}, dest_z={self.dest_z!r}, shape={self.shape!r}"
382
+ ]
383
+ if self.speed != 1.0:
384
+ output.append(f", speed={self.speed!r}")
385
+ if self.switch:
386
+ output.append(f", switch={self.switch!r}")
387
+ if self.walls:
388
+ output.append(f", walls={self.walls!r}")
389
+ output.append(")")
390
+ return "".join(output)
391
+
392
+ @property
393
+ def shape(
394
+ self,
395
+ ) -> Literal[
396
+ PartModel.Tile10x10,
397
+ PartModel.Tile20x20,
398
+ PartModel.TileA30x30,
399
+ PartModel.TileA30x90,
400
+ PartModel.Tile90x90,
401
+ PartModel.HoleB90x90,
402
+ PartModel.FunnelPipe,
403
+ PartModel.StraightPipe,
404
+ ]:
405
+ """The appearance of this moving tile."""
406
+ return self._shape
407
+
408
+ @shape.setter
409
+ def shape(
410
+ self,
411
+ value: Literal[
412
+ PartModel.Tile10x10,
413
+ PartModel.Tile20x20,
414
+ PartModel.TileA30x30,
415
+ PartModel.TileA30x90,
416
+ PartModel.Tile90x90,
417
+ PartModel.HoleB90x90,
418
+ PartModel.FunnelPipe,
419
+ PartModel.StraightPipe,
420
+ ],
421
+ /,
422
+ ) -> None:
423
+ if (
424
+ value in frozenset({PartModel.FunnelPipe, PartModel.StraightPipe})
425
+ and self.switch
426
+ ):
427
+ raise ValueError("Moving pipes cannot be switches")
428
+ elif (
429
+ value not in frozenset({PartModel.Tile20x20, PartModel.TileA30x30})
430
+ and self.walls
431
+ ):
432
+ raise ValueError("Invalid shape for wall attachment")
433
+ else:
434
+ self._shape = value
435
+
436
+ @property
437
+ def speed(self) -> float:
438
+ return self._speed
439
+
440
+ @speed.setter
441
+ def speed(self, value: Speed | float, /) -> None:
442
+ match value:
443
+ case Speed.SLOW:
444
+ self.speed = 0.5
445
+ case Speed.NORMAL:
446
+ self.speed = 1.0
447
+ case Speed.FAST:
448
+ self.speed = 1.5
449
+ case _:
450
+ self._speed = value
451
+
452
+ @property
453
+ def switch(self) -> bool:
454
+ """Whether this moving tile must be touched in order to move."""
455
+ return self._switch
456
+
457
+ @switch.setter
458
+ def switch(self, value: bool, /) -> None:
459
+ if self.shape in frozenset({PartModel.FunnelPipe, PartModel.StraightPipe}):
460
+ raise ValueError("Moving pipes cannot be switches")
461
+ else:
462
+ self._switch = value
463
+
464
+ @property
465
+ def walls(self) -> Walls:
466
+ return self._walls
467
+
468
+ @walls.setter
469
+ def walls(self, value: Walls, /) -> None:
470
+ if self.shape in frozenset({PartModel.Tile20x20, PartModel.TileA30x30}):
471
+ self._walls = value
472
+ else:
473
+ raise ValueError("Invalid shape for wall attachment")
474
+
475
+
476
+ class FixedSpeedDevice(BasePart, ABC):
477
+ """Device template for devices with speeds that can be set to Slow, Normal, or Fast"""
478
+
479
+ __slots__ = ("_speed",)
480
+
481
+ _speed: Speed
482
+
483
+ def __init__(
484
+ self,
485
+ x_pos: float,
486
+ y_pos: float,
487
+ z_pos: float,
488
+ x_rot: float,
489
+ y_rot: float,
490
+ z_rot: float,
491
+ *,
492
+ speed: Speed = Speed.NORMAL,
493
+ ) -> None:
494
+ super().__init__(x_pos, y_pos, z_pos, x_rot, y_rot, z_rot)
495
+ self.speed = speed
496
+
497
+ def __repr__(self) -> str:
498
+ return (
499
+ f"{type(self).__name__}({self.x_pos!r}, {self.y_pos!r}, {self.z_pos!r}, {self.x_rot!r}, {self.y_rot!r}, {self.z_rot!r}, speed={self.speed!r})"
500
+ if self.speed != Speed.NORMAL
501
+ else f"{type(self).__name__}({self.x_pos!r}, {self.y_pos!r}, {self.z_pos!r}, {self.x_rot!r}, {self.y_rot!r}, {self.z_rot!r})"
502
+ )
503
+
504
+ @property
505
+ def speed(self) -> Speed:
506
+ return self._speed
507
+
508
+ @speed.setter
509
+ def speed(self, value: Speed, /) -> None:
510
+ self._speed = value
511
+
512
+
513
+ class MovingCurve(FixedSpeedDevice):
514
+ __slots__ = ("_shape",)
515
+
516
+ _shape: Literal[PartModel.CurveS, PartModel.CurveM, PartModel.CurveL]
517
+
518
+ def __init__(
519
+ self,
520
+ x_pos: float,
521
+ y_pos: float,
522
+ z_pos: float,
523
+ x_rot: float,
524
+ y_rot: float,
525
+ z_rot: float,
526
+ *,
527
+ shape: Literal[PartModel.CurveS, PartModel.CurveM, PartModel.CurveL],
528
+ speed: Speed = Speed.NORMAL,
529
+ ) -> None:
530
+ super().__init__(x_pos, y_pos, z_pos, x_rot, y_rot, z_rot, speed=speed)
531
+ self.shape = shape
532
+
533
+ @property
534
+ def cost(self) -> Literal[25]:
535
+ return 25
536
+
537
+ def __repr__(self) -> str:
538
+ return (
539
+ f"{type(self).__name__}({self.x_pos!r}, {self.y_pos!r}, {self.z_pos!r}, {self.x_rot!r}, {self.y_rot!r}, {self.z_rot!r}, shape={self.shape!r}, speed={self.speed!r})"
540
+ if self.speed != Speed.NORMAL
541
+ else f"{type(self).__name__}({self.x_pos!r}, {self.y_pos!r}, {self.z_pos!r}, {self.x_rot!r}, {self.y_rot!r}, {self.z_rot!r}, shape={self.shape!r})"
542
+ )
543
+
544
+ @property
545
+ def shape(self) -> Literal[PartModel.CurveS, PartModel.CurveM, PartModel.CurveL]:
546
+ return self._shape
547
+
548
+ @shape.setter
549
+ def shape(
550
+ self, value: Literal[PartModel.CurveS, PartModel.CurveM, PartModel.CurveL], /
551
+ ) -> None:
552
+ self._shape = value
553
+
554
+
555
+ class SlidingTile(BasePart):
556
+ __slots__ = ()
557
+
558
+ @property
559
+ def cost(self) -> Literal[100]:
560
+ return 100
561
+
562
+
563
+ class ConveyorBelt(BasePart):
564
+ __slots__ = ("_reversing",)
565
+
566
+ _reversing: bool
567
+
568
+ def __init__(
569
+ self,
570
+ x_pos: float,
571
+ y_pos: float,
572
+ z_pos: float,
573
+ x_rot: float,
574
+ y_rot: float,
575
+ z_rot: float,
576
+ *,
577
+ reversing: bool = False,
578
+ ) -> None:
579
+ super().__init__(x_pos, y_pos, z_pos, x_rot, y_rot, z_rot)
580
+ self.reversing = reversing
581
+
582
+ @property
583
+ def cost(self) -> Literal[60]:
584
+ return 60
585
+
586
+ def __repr__(self) -> str:
587
+ return (
588
+ f"{type(self).__name__}({self.x_pos!r}, {self.y_pos!r}, {self.z_pos!r}, {self.x_rot!r}, {self.y_rot!r}, {self.z_rot!r}, reversing={self.reversing!r})"
589
+ if self.reversing
590
+ else super().__repr__()
591
+ )
592
+
593
+ @property
594
+ def reversing(self) -> bool:
595
+ return self._reversing
596
+
597
+ @reversing.setter
598
+ def reversing(self, value: bool, /) -> None:
599
+ self._reversing = value
600
+
601
+
602
+ class MagnetSegment(BasePart):
603
+ __slots__ = ("_shape",)
604
+
605
+ _shape: Literal[
606
+ DeviceModel.EndMagnet,
607
+ DeviceModel.StraightMagnet,
608
+ DeviceModel.CurveMagnetL,
609
+ DeviceModel.CurveMagnetS,
610
+ ]
611
+
612
+ def __init__(
613
+ self,
614
+ x_pos: float,
615
+ y_pos: float,
616
+ z_pos: float,
617
+ x_rot: float,
618
+ y_rot: float,
619
+ z_rot: float,
620
+ *,
621
+ shape: Literal[
622
+ DeviceModel.EndMagnet,
623
+ DeviceModel.StraightMagnet,
624
+ DeviceModel.CurveMagnetL,
625
+ DeviceModel.CurveMagnetS,
626
+ ],
627
+ ) -> None:
628
+ super().__init__(x_pos, y_pos, z_pos, x_rot, y_rot, z_rot)
629
+ self.shape = shape
630
+
631
+ @property
632
+ def cost(self) -> Literal[15]:
633
+ return 15
634
+
635
+ def __repr__(self) -> str:
636
+ return f"{type(self).__name__}({self.x_pos!r}, {self.y_pos!r}, {self.z_pos!r}, {self.x_rot!r}, {self.y_rot!r}, {self.z_rot!r}, shape={self.shape!r})"
637
+
638
+ @property
639
+ def shape(self) -> Literal[
640
+ DeviceModel.EndMagnet,
641
+ DeviceModel.StraightMagnet,
642
+ DeviceModel.CurveMagnetL,
643
+ DeviceModel.CurveMagnetS,
644
+ ]:
645
+ return self._shape
646
+
647
+ @shape.setter
648
+ def shape(
649
+ self,
650
+ value: Literal[
651
+ DeviceModel.EndMagnet,
652
+ DeviceModel.StraightMagnet,
653
+ DeviceModel.CurveMagnetL,
654
+ DeviceModel.CurveMagnetS,
655
+ ],
656
+ /,
657
+ ) -> None:
658
+ self._shape = value
659
+
660
+
661
+ class Magnet(BasePart, MutableSequence[MagnetSegment]):
662
+ __slots__ = ("_segments",)
663
+
664
+ _segments: list[MagnetSegment]
665
+
666
+ def __init__(self, iterable: Iterable[MagnetSegment] = (), /) -> None:
667
+ self._segments = list(iterable)
668
+
669
+ def append(self, value: MagnetSegment) -> None:
670
+ return self._segments.append(value)
671
+
672
+ def clear(self) -> None:
673
+ self._segments.clear()
674
+
675
+ def __contains__(self, value: Any, /) -> bool:
676
+ return value in self._segments
677
+
678
+ @property
679
+ def cost(self) -> int:
680
+ return sum(segment.cost for segment in self)
681
+
682
+ def count(self, value: Any) -> int:
683
+ return self._segments.count(value)
684
+
685
+ def __delitem__(self, index: SupportsIndex | slice, /) -> None:
686
+ del self._segments[index]
687
+
688
+ def extend(self, iterable: Iterable[MagnetSegment], /) -> None:
689
+ self._segments.extend(iterable)
690
+
691
+ @overload
692
+ def __getitem__(self, index: SupportsIndex, /) -> MagnetSegment:
693
+ pass
694
+
695
+ @overload
696
+ def __getitem__(self, index: slice, /) -> MutableSequence[MagnetSegment]:
697
+ pass
698
+
699
+ def __getitem__(
700
+ self, index: SupportsIndex | slice, /
701
+ ) -> MagnetSegment | MutableSequence[MagnetSegment]:
702
+ return self._segments[index]
703
+
704
+ def __iadd__(self, value: Iterable[MagnetSegment], /) -> Self:
705
+ self._segments += value
706
+ return self
707
+
708
+ def index(
709
+ self, value: Any, start: SupportsIndex = 0, stop: SupportsIndex = maxsize, /
710
+ ) -> int:
711
+ return self._segments.index(value, start, stop)
712
+
713
+ def insert(self, index: SupportsIndex, value: MagnetSegment, /) -> None:
714
+ return self._segments.insert(index, value)
715
+
716
+ def __iter__(self) -> Iterator[MagnetSegment]:
717
+ return iter(self._segments)
718
+
719
+ def __len__(self) -> int:
720
+ return len(self._segments)
721
+
722
+ def pop(self, index: SupportsIndex = -1, /) -> MagnetSegment:
723
+ return self._segments.pop(index)
724
+
725
+ def remove(self, value: MagnetSegment, /) -> None:
726
+ self._segments.remove(value)
727
+
728
+ def __repr__(self) -> str:
729
+ return f"{type(self).__name__}({self._segments!r})"
730
+
731
+ def reverse(self) -> None:
732
+ self._segments.reverse()
733
+
734
+ def __reversed__(self) -> Iterator[MagnetSegment]:
735
+ return reversed(self._segments)
736
+
737
+ @overload
738
+ def __setitem__(self, index: SupportsIndex, value: MagnetSegment, /) -> None:
739
+ pass
740
+
741
+ @overload
742
+ def __setitem__(self, slice: slice, value: Iterable[MagnetSegment], /) -> None:
743
+ pass
744
+
745
+ def __setitem__(
746
+ self,
747
+ index: SupportsIndex | slice,
748
+ value: MagnetSegment | Iterable[MagnetSegment],
749
+ /,
750
+ ) -> None:
751
+ self._segments[index] = value # type: ignore
752
+
753
+ @property
754
+ def x_pos(self) -> float:
755
+ """Positive is right, negative is left"""
756
+ return self[-1].x_pos
757
+
758
+ @x_pos.setter
759
+ def x_pos(self, value: float, /) -> None:
760
+ offset: float = value - self.x_pos
761
+ for segment in self:
762
+ segment.x_pos += offset
763
+
764
+ @property
765
+ def x_rot(self) -> float:
766
+ """Represented in degrees from 0 to 360
767
+ Positive turns top face front, negative turns top face back
768
+ """
769
+ return self[-1].x_rot
770
+
771
+ @x_rot.setter
772
+ def x_rot(self, value: float, /) -> None:
773
+ """Positions of segments are not updated."""
774
+ offset: float = value - self.x_rot
775
+ for segment in self:
776
+ segment.x_rot += offset
777
+
778
+ @property
779
+ def y_pos(self) -> float:
780
+ """Positive is up, negative is down"""
781
+ return self[-1].y_pos
782
+
783
+ @y_pos.setter
784
+ def y_pos(self, value: float, /) -> None:
785
+ offset: float = value - self.y_pos
786
+ for segment in self:
787
+ segment.y_pos += offset
788
+
789
+ @property
790
+ def y_rot(self) -> float:
791
+ """Represented in degrees from 0 to 360
792
+ Positive turns front face right, negative turns front face left
793
+ """
794
+ return self[-1].y_rot
795
+
796
+ @y_rot.setter
797
+ def y_rot(self, value: float, /) -> None:
798
+ """Positions of segments are not updated."""
799
+ offset: float = value - self.y_rot
800
+ for segment in self:
801
+ segment.y_rot += offset
802
+
803
+ @property
804
+ def z_pos(self) -> float:
805
+ """Positive is front, negative is back"""
806
+ return self[-1].z_pos
807
+
808
+ @z_pos.setter
809
+ def z_pos(self, value: float, /) -> None:
810
+ offset: float = value - self.z_pos
811
+ for segment in self:
812
+ segment.z_pos += offset
813
+
814
+ @property
815
+ def z_rot(self) -> float:
816
+ """Represented in degrees from 0 to 360
817
+ Positive turns top face left, negative turns top face right
818
+ """
819
+ return self[-1].z_rot
820
+
821
+ @z_rot.setter
822
+ def z_rot(self, value: float, /) -> None:
823
+ """Positions of segments are not updated."""
824
+ offset: float = value - self.z_rot
825
+ for segment in self:
826
+ segment.z_rot += offset
827
+
828
+
829
+ class DashTunnel(BasePart):
830
+ __slots__ = ("_shape",)
831
+
832
+ _shape: Literal[DeviceModel.DashTunnelA, DeviceModel.DashTunnelB]
833
+
834
+ def __init__(
835
+ self,
836
+ x_pos: float,
837
+ y_pos: float,
838
+ z_pos: float,
839
+ x_rot: float,
840
+ y_rot: float,
841
+ z_rot: float,
842
+ *,
843
+ shape: Literal[DeviceModel.DashTunnelA, DeviceModel.DashTunnelB],
844
+ ) -> None:
845
+ super().__init__(x_pos, y_pos, z_pos, x_rot, y_rot, z_rot)
846
+ self.shape = shape
847
+
848
+ @property
849
+ def cost(self) -> Literal[35, 100]:
850
+ match self.shape:
851
+ case DeviceModel.DashTunnelA:
852
+ return 35
853
+ case DeviceModel.DashTunnelB:
854
+ return 100
855
+
856
+ def __repr__(self) -> str:
857
+ return f"{type(self).__name__}({self.x_pos!r}, {self.y_pos!r}, {self.z_pos!r}, {self.x_rot!r}, {self.y_rot!r}, {self.z_rot!r}, shape={self.shape!r})"
858
+
859
+ @property
860
+ def shape(self) -> Literal[DeviceModel.DashTunnelA, DeviceModel.DashTunnelB]:
861
+ return self._shape
862
+
863
+ @shape.setter
864
+ def shape(
865
+ self, value: Literal[DeviceModel.DashTunnelA, DeviceModel.DashTunnelB], /
866
+ ) -> None:
867
+ self._shape = value
868
+
869
+
870
+ class SeesawBlock(BasePart):
871
+ __slots__ = ("_auto", "_shape")
872
+
873
+ _auto: bool
874
+ _shape: Literal[DeviceModel.SeesawLBlock, DeviceModel.SeesawIBlock]
875
+
876
+ def __init__(
877
+ self,
878
+ x_pos: float,
879
+ y_pos: float,
880
+ z_pos: float,
881
+ x_rot: float,
882
+ y_rot: float,
883
+ z_rot: float,
884
+ *,
885
+ auto: bool = False,
886
+ shape: Literal[DeviceModel.SeesawLBlock, DeviceModel.SeesawIBlock],
887
+ ) -> None:
888
+ super().__init__(x_pos, y_pos, z_pos, x_rot, y_rot, z_rot)
889
+ self.auto = auto
890
+ self.shape = shape
891
+
892
+ @property
893
+ def auto(self) -> bool:
894
+ return self._auto
895
+
896
+ @auto.setter
897
+ def auto(self, value: bool, /) -> None:
898
+ self._auto = value
899
+
900
+ @property
901
+ def cost(self) -> Literal[100]:
902
+ return 100
903
+
904
+ def __repr__(self) -> str:
905
+ return (
906
+ f"{type(self).__name__}({self.x_pos!r}, {self.y_pos!r}, {self.z_pos!r}, {self.x_rot!r}, {self.y_rot!r}, {self.z_rot!r}, auto={self.auto!r}, shape={self.shape!r})"
907
+ if self.auto
908
+ else f"{type(self).__name__}({self.x_pos!r}, {self.y_pos!r}, {self.z_pos!r}, {self.x_rot!r}, {self.y_rot!r}, {self.z_rot!r}, shape={self.shape!r})"
909
+ )
910
+
911
+ @property
912
+ def shape(self) -> Literal[DeviceModel.SeesawLBlock, DeviceModel.SeesawIBlock]:
913
+ return self._shape
914
+
915
+ @shape.setter
916
+ def shape(
917
+ self, value: Literal[DeviceModel.SeesawLBlock, DeviceModel.SeesawIBlock], /
918
+ ) -> None:
919
+ self._shape = value
920
+
921
+
922
+ class Cannon(BasePart):
923
+ __slots__ = ()
924
+
925
+ @property
926
+ def cost(self) -> Literal[30]:
927
+ return 30
928
+
929
+
930
+ class Drawbridge(BasePart):
931
+ __slots__ = ()
932
+
933
+ @property
934
+ def cost(self) -> Literal[50]:
935
+ return 50
936
+
937
+
938
+ class Turntable(FixedSpeedDevice):
939
+ __slots__ = ()
940
+
941
+ @property
942
+ def cost(self) -> Literal[50]:
943
+ return 50
944
+
945
+
946
+ class Bumper(BasePart):
947
+ __slots__ = ("_powerful",)
948
+
949
+ _powerful: bool
950
+
951
+ def __init__(
952
+ self,
953
+ x_pos: float,
954
+ y_pos: float,
955
+ z_pos: float,
956
+ x_rot: float,
957
+ y_rot: float,
958
+ z_rot: float,
959
+ *,
960
+ powerful: bool = False,
961
+ ) -> None:
962
+ super().__init__(x_pos, y_pos, z_pos, x_rot, y_rot, z_rot)
963
+ self.powerful = powerful
964
+
965
+ @property
966
+ def cost(self) -> Literal[20]:
967
+ return 20
968
+
969
+ @property
970
+ def powerful(self) -> bool:
971
+ return self._powerful
972
+
973
+ @powerful.setter
974
+ def powerful(self, value: bool, /) -> None:
975
+ self._powerful = value
976
+
977
+ def __repr__(self) -> str:
978
+ return (
979
+ f"{type(self).__name__}({self.x_pos!r}, {self.y_pos!r}, {self.z_pos!r}, {self.x_rot!r}, {self.y_rot!r}, {self.z_rot!r}, powerful={self.powerful!r})"
980
+ if self.powerful
981
+ else f"{type(self).__name__}({self.x_pos!r}, {self.y_pos!r}, {self.z_pos!r}, {self.x_rot!r}, {self.y_rot!r}, {self.z_rot!r})"
982
+ )
983
+
984
+
985
+ class Thorn(BasePart):
986
+ __slots__ = ()
987
+
988
+ @property
989
+ def cost(self) -> Literal[35]:
990
+ return 35
991
+
992
+
993
+ class Gear(FixedSpeedDevice):
994
+ __slots__ = ()
995
+
996
+ @property
997
+ def cost(self) -> Literal[50]:
998
+ return 50
999
+
1000
+
1001
+ class Fan(BasePart):
1002
+ __slots__ = ("_wind_pattern",)
1003
+
1004
+ _wind_pattern: Literal[
1005
+ DeviceModel.Fan, DeviceModel.PowerfulFan, DeviceModel.TimerFan
1006
+ ]
1007
+
1008
+ def __init__(
1009
+ self,
1010
+ x_pos: float,
1011
+ y_pos: float,
1012
+ z_pos: float,
1013
+ x_rot: float,
1014
+ y_rot: float,
1015
+ z_rot: float,
1016
+ *,
1017
+ wind_pattern: Literal[
1018
+ DeviceModel.Fan, DeviceModel.PowerfulFan, DeviceModel.TimerFan
1019
+ ] = DeviceModel.Fan,
1020
+ ) -> None:
1021
+ super().__init__(x_pos, y_pos, z_pos, x_rot, y_rot, z_rot)
1022
+ self.wind_pattern = wind_pattern
1023
+
1024
+ @property
1025
+ def cost(self) -> Literal[50]:
1026
+ return 50
1027
+
1028
+ def __repr__(self) -> str:
1029
+ return (
1030
+ f"{type(self).__name__}({self.x_pos!r}, {self.y_pos!r}, {self.z_pos!r}, {self.x_rot!r}, {self.y_rot!r}, {self.z_rot!r}, wind_pattern={self.wind_pattern!r})"
1031
+ if self.wind_pattern != DeviceModel.Fan
1032
+ else f"{type(self).__name__}({self.x_pos!r}, {self.y_pos!r}, {self.z_pos!r}, {self.x_rot!r}, {self.y_rot!r}, {self.z_rot!r})"
1033
+ )
1034
+
1035
+ @property
1036
+ def wind_pattern(
1037
+ self,
1038
+ ) -> Literal[DeviceModel.Fan, DeviceModel.PowerfulFan, DeviceModel.TimerFan]:
1039
+ return self._wind_pattern
1040
+
1041
+ @wind_pattern.setter
1042
+ def wind_pattern(
1043
+ self,
1044
+ value: Literal[DeviceModel.Fan, DeviceModel.PowerfulFan, DeviceModel.TimerFan],
1045
+ /,
1046
+ ) -> None:
1047
+ self._wind_pattern = value
1048
+
1049
+
1050
+ class Spring(BasePart):
1051
+ __slots__ = ()
1052
+
1053
+ @property
1054
+ def cost(self) -> Literal[25]:
1055
+ return 25
1056
+
1057
+
1058
+ @unique
1059
+ class MovementTiming(Enum):
1060
+ A = 23
1061
+ B = 39
1062
+ C = 55
1063
+
1064
+
1065
+ class TimedDevice(BasePart, ABC):
1066
+ """Device Template for devices which can have one of three timings for their movements"""
1067
+
1068
+ __slots__ = ("_timing",)
1069
+
1070
+ _timing: MovementTiming
1071
+
1072
+ def __init__(
1073
+ self,
1074
+ x_pos: float,
1075
+ y_pos: float,
1076
+ z_pos: float,
1077
+ x_rot: float,
1078
+ y_rot: float,
1079
+ z_rot: float,
1080
+ *,
1081
+ timing: MovementTiming,
1082
+ ) -> None:
1083
+ super().__init__(x_pos, y_pos, z_pos, x_rot, y_rot, z_rot)
1084
+ self.timing = timing
1085
+
1086
+ def __repr__(self) -> str:
1087
+ return f"{type(self).__name__}({self.x_pos!r}, {self.y_pos!r}, {self.z_pos!r}, {self.x_rot!r}, {self.y_rot!r}, {self.z_rot!r}, timing={self.timing!r})"
1088
+
1089
+ @property
1090
+ def timing(self) -> MovementTiming:
1091
+ return self._timing
1092
+
1093
+ @timing.setter
1094
+ def timing(self, value: MovementTiming, /) -> None:
1095
+ self._timing = value
1096
+
1097
+
1098
+ class Punch(TimedDevice):
1099
+ __slots__ = ()
1100
+
1101
+ @property
1102
+ def cost(self) -> Literal[30]:
1103
+ return 30
1104
+
1105
+
1106
+ class Press(TimedDevice):
1107
+ __slots__ = ()
1108
+
1109
+ @property
1110
+ def cost(self) -> Literal[50]:
1111
+ return 50
1112
+
1113
+
1114
+ class Scissors(TimedDevice):
1115
+ __slots__ = ()
1116
+
1117
+ @property
1118
+ def cost(self) -> Literal[70]:
1119
+ return 70
1120
+
1121
+
1122
+ class MagnifyingGlass(BasePart):
1123
+ __slots__ = ()
1124
+
1125
+ @property
1126
+ def cost(self) -> Literal[200]:
1127
+ return 200
1128
+
1129
+
1130
+ class UpsideDownStageDevice(BasePart):
1131
+ __slots__ = ()
1132
+
1133
+ @property
1134
+ def cost(self) -> Literal[50]:
1135
+ return 50
1136
+
1137
+
1138
+ class UpsideDownBall(BasePart):
1139
+ __slots__ = ()
1140
+
1141
+ @property
1142
+ def cost(self) -> Literal[25]:
1143
+ return 25
1144
+
1145
+
1146
+ class SizeTunnel(BasePart):
1147
+ __slots__ = ("_size",)
1148
+
1149
+ _size: Literal[DeviceModel.SmallTunnel, DeviceModel.BigTunnel]
1150
+
1151
+ def __init__(
1152
+ self,
1153
+ x_pos: float,
1154
+ y_pos: float,
1155
+ z_pos: float,
1156
+ x_rot: float,
1157
+ y_rot: float,
1158
+ z_rot: float,
1159
+ *,
1160
+ size: Literal[DeviceModel.SmallTunnel, DeviceModel.BigTunnel],
1161
+ ) -> None:
1162
+ super().__init__(x_pos, y_pos, z_pos, x_rot, y_rot, z_rot)
1163
+ self.size = size
1164
+
1165
+ @property
1166
+ def cost(self) -> Literal[50]:
1167
+ return 50
1168
+
1169
+ def __repr__(self) -> str:
1170
+ return f"{type(self).__name__}({self.x_pos!r}, {self.y_pos!r}, {self.z_pos!r}, {self.x_rot!r}, {self.y_rot!r}, {self.z_rot!r}, size={self.size!r})"
1171
+
1172
+ @property
1173
+ def size(self) -> Literal[DeviceModel.SmallTunnel, DeviceModel.BigTunnel]:
1174
+ return self._size
1175
+
1176
+ @size.setter
1177
+ def size(
1178
+ self, value: Literal[DeviceModel.SmallTunnel, DeviceModel.BigTunnel], /
1179
+ ) -> None:
1180
+ self._size = value
1181
+
1182
+
1183
+ class TrainTrack(BasePart):
1184
+ __slots__ = ("_shape",)
1185
+
1186
+ _shape: Literal[
1187
+ DeviceModel.EndTracks,
1188
+ DeviceModel.LeftTracks,
1189
+ DeviceModel.RightTracks,
1190
+ DeviceModel.StraightTracks,
1191
+ ]
1192
+
1193
+ def __init__(
1194
+ self,
1195
+ x_pos: float,
1196
+ y_pos: float,
1197
+ z_pos: float,
1198
+ x_rot: float,
1199
+ y_rot: float,
1200
+ z_rot: float,
1201
+ *,
1202
+ shape: Literal[
1203
+ DeviceModel.EndTracks,
1204
+ DeviceModel.LeftTracks,
1205
+ DeviceModel.RightTracks,
1206
+ DeviceModel.StraightTracks,
1207
+ ],
1208
+ ) -> None:
1209
+ super().__init__(x_pos, y_pos, z_pos, x_rot, y_rot, z_rot)
1210
+ self.shape = shape
1211
+
1212
+ @property
1213
+ def cost(self) -> Literal[0, 20]:
1214
+ return 0 if self.shape is DeviceModel.EndTracks else 20
1215
+
1216
+ def __repr__(self) -> str:
1217
+ return f"{type(self).__name__}({self.x_pos!r}, {self.y_pos!r}, {self.z_pos!r}, {self.x_rot!r}, {self.y_rot!r}, {self.z_rot!r}, shape={self.shape!r})"
1218
+
1219
+ @property
1220
+ def shape(
1221
+ self,
1222
+ ) -> Literal[
1223
+ DeviceModel.EndTracks,
1224
+ DeviceModel.LeftTracks,
1225
+ DeviceModel.RightTracks,
1226
+ DeviceModel.StraightTracks,
1227
+ ]:
1228
+ return self._shape
1229
+
1230
+ @shape.setter
1231
+ def shape(
1232
+ self,
1233
+ value: Literal[
1234
+ DeviceModel.EndTracks,
1235
+ DeviceModel.LeftTracks,
1236
+ DeviceModel.RightTracks,
1237
+ DeviceModel.StraightTracks,
1238
+ ],
1239
+ /,
1240
+ ) -> None:
1241
+ self._shape = value
1242
+
1243
+
1244
+ class ToyTrain(BasePart, MutableSequence[TrainTrack]):
1245
+ __slots__ = ("_tracks",)
1246
+
1247
+ _tracks: list[TrainTrack]
1248
+
1249
+ def __init__(
1250
+ self,
1251
+ x_pos: float,
1252
+ y_pos: float,
1253
+ z_pos: float,
1254
+ x_rot: float,
1255
+ y_rot: float,
1256
+ z_rot: float,
1257
+ *,
1258
+ tracks: Iterable[TrainTrack] = (),
1259
+ ) -> None:
1260
+ super().__init__(x_pos, y_pos, z_pos, x_rot, y_rot, z_rot)
1261
+ self._tracks = list(tracks)
1262
+
1263
+ def append(self, value: TrainTrack) -> None:
1264
+ return self._tracks.append(value)
1265
+
1266
+ def clear(self) -> None:
1267
+ self._tracks.clear()
1268
+
1269
+ def __contains__(self, value: Any, /) -> bool:
1270
+ return value in self._tracks
1271
+
1272
+ @property
1273
+ def cost(self) -> int:
1274
+ return 100 + sum(track.cost for track in self)
1275
+
1276
+ def count(self, value: Any) -> int:
1277
+ return self._tracks.count(value)
1278
+
1279
+ def __delitem__(self, index: SupportsIndex | slice, /) -> None:
1280
+ del self._tracks[index]
1281
+
1282
+ def extend(self, iterable: Iterable[TrainTrack], /) -> None:
1283
+ self._tracks.extend(iterable)
1284
+
1285
+ @overload
1286
+ def __getitem__(self, index: SupportsIndex, /) -> TrainTrack:
1287
+ pass
1288
+
1289
+ @overload
1290
+ def __getitem__(self, index: slice, /) -> MutableSequence[TrainTrack]:
1291
+ pass
1292
+
1293
+ def __getitem__(
1294
+ self, index: SupportsIndex | slice, /
1295
+ ) -> TrainTrack | MutableSequence[TrainTrack]:
1296
+ return self._tracks[index]
1297
+
1298
+ def __iadd__(self, value: Iterable[TrainTrack], /) -> Self:
1299
+ self._tracks += value
1300
+ return self
1301
+
1302
+ def index(
1303
+ self, value: Any, start: SupportsIndex = 0, stop: SupportsIndex = maxsize, /
1304
+ ) -> int:
1305
+ return self._tracks.index(value, start, stop)
1306
+
1307
+ def insert(self, index: SupportsIndex, value: TrainTrack, /) -> None:
1308
+ return self._tracks.insert(index, value)
1309
+
1310
+ def __iter__(self) -> Iterator[TrainTrack]:
1311
+ return iter(self._tracks)
1312
+
1313
+ def __len__(self) -> int:
1314
+ return len(self._tracks)
1315
+
1316
+ def pop(self, index: SupportsIndex = -1, /) -> TrainTrack:
1317
+ return self._tracks.pop(index)
1318
+
1319
+ def remove(self, value: TrainTrack, /) -> None:
1320
+ self._tracks.remove(value)
1321
+
1322
+ def __repr__(self) -> str:
1323
+ return f"{type(self).__name__}({self.x_pos!r}, {self.y_pos!r}, {self.z_pos!r}, {self.x_rot!r}, {self.y_rot!r}, {self.z_rot!r}, tracks={self._tracks!r})"
1324
+
1325
+ def reverse(self) -> None:
1326
+ self._tracks.reverse()
1327
+
1328
+ def __reversed__(self) -> Iterator[TrainTrack]:
1329
+ return reversed(self._tracks)
1330
+
1331
+ @overload
1332
+ def __setitem__(self, index: SupportsIndex, value: TrainTrack, /) -> None:
1333
+ pass
1334
+
1335
+ @overload
1336
+ def __setitem__(self, slice: slice, value: Iterable[TrainTrack], /) -> None:
1337
+ pass
1338
+
1339
+ def __setitem__(
1340
+ self, index: SupportsIndex | slice, value: TrainTrack | Iterable[TrainTrack], /
1341
+ ) -> None:
1342
+ self._tracks[index] = value # type: ignore
1343
+
1344
+
1345
+ class Warp(BasePart):
1346
+ __slots__ = (
1347
+ "_dest_x",
1348
+ "_dest_y",
1349
+ "_dest_z",
1350
+ "_return_dest_x",
1351
+ "_return_dest_y",
1352
+ "_return_dest_z",
1353
+ "_return_x_pos",
1354
+ "_return_x_rot",
1355
+ "_return_y_pos",
1356
+ "_return_y_rot",
1357
+ "_return_z_pos",
1358
+ "_return_z_rot",
1359
+ )
1360
+
1361
+ _dest_x: float
1362
+ _dest_y: float
1363
+ _dest_z: float
1364
+ _return_dest_x: float
1365
+ _return_dest_y: float
1366
+ _return_dest_z: float
1367
+ _return_x_pos: float
1368
+ _return_x_rot: float
1369
+ _return_y_pos: float
1370
+ _return_y_rot: float
1371
+ _return_z_pos: float
1372
+ _return_z_rot: float
1373
+
1374
+ def __init__(
1375
+ self,
1376
+ x_pos: float,
1377
+ y_pos: float,
1378
+ z_pos: float,
1379
+ x_rot: float,
1380
+ y_rot: float,
1381
+ z_rot: float,
1382
+ *,
1383
+ dest_x: float,
1384
+ dest_y: float,
1385
+ dest_z: float,
1386
+ return_x_pos: float,
1387
+ return_y_pos: float,
1388
+ return_z_pos: float,
1389
+ return_x_rot: float,
1390
+ return_y_rot: float,
1391
+ return_z_rot: float,
1392
+ return_dest_x: float,
1393
+ return_dest_y: float,
1394
+ return_dest_z: float,
1395
+ ) -> None:
1396
+ super().__init__(x_pos, y_pos, z_pos, x_rot, y_rot, z_rot)
1397
+ self.dest_x = dest_x
1398
+ self.dest_y = dest_y
1399
+ self.dest_z = dest_z
1400
+ self.return_dest_x = return_dest_x
1401
+ self.return_dest_y = return_dest_y
1402
+ self.return_dest_z = return_dest_z
1403
+ self.return_x_pos = return_x_pos
1404
+ self.return_x_rot = return_x_rot
1405
+ self.return_y_pos = return_y_pos
1406
+ self.return_y_rot = return_y_rot
1407
+ self.return_z_pos = return_z_pos
1408
+ self.return_z_rot = return_z_rot
1409
+
1410
+ @property
1411
+ def cost(self) -> Literal[25]:
1412
+ return 25
1413
+
1414
+ @property
1415
+ def dest_x(self) -> float:
1416
+ """Positive is right, negative is left"""
1417
+ return self._dest_x
1418
+
1419
+ @dest_x.setter
1420
+ def dest_x(self, value: float, /) -> None:
1421
+ self._dest_x = value
1422
+
1423
+ @property
1424
+ def dest_y(self) -> float:
1425
+ return self._dest_y
1426
+
1427
+ @dest_y.setter
1428
+ def dest_y(self, value: float, /) -> None:
1429
+ """Positive is up, negative is down"""
1430
+ self._dest_y = value
1431
+
1432
+ @property
1433
+ def dest_z(self) -> float:
1434
+ """Positive is front, negative is back"""
1435
+ return self._dest_z
1436
+
1437
+ @dest_z.setter
1438
+ def dest_z(self, value: float, /) -> None:
1439
+ self._dest_z = value
1440
+
1441
+ def __repr__(self) -> str:
1442
+ return f"{type(self).__name__}({self.x_pos!r}, {self.y_pos!r}, {self.z_pos!r}, {self.x_rot!r}, {self.y_rot!r}, {self.z_rot!r}, dest_x={self.dest_x!r}, dest_y={self.dest_y!r}, dest_z={self.dest_z!r}, return_x_pos={self.return_x_pos}, return_y_pos={self.return_y_pos}, return_z_pos={self.return_z_pos}, return_x_rot={self.return_x_rot}, return_y_rot={self.return_y_rot}, return_z_rot={self.return_z_rot}, return_dest_x={self.return_dest_x}, return_dest_y={self.return_dest_y}, return_dest_z={self.return_dest_z})"
1443
+
1444
+ @property
1445
+ def return_dest_x(self) -> float:
1446
+ """Positive is right, negative is left"""
1447
+ return self._return_dest_x
1448
+
1449
+ @return_dest_x.setter
1450
+ def return_dest_x(self, value: float, /) -> None:
1451
+ self._return_dest_x = value
1452
+
1453
+ @property
1454
+ def return_dest_y(self) -> float:
1455
+ return self._return_dest_y
1456
+
1457
+ @return_dest_y.setter
1458
+ def return_dest_y(self, value: float, /) -> None:
1459
+ """Positive is up, negative is down"""
1460
+ self._return_dest_y = value
1461
+
1462
+ @property
1463
+ def return_dest_z(self) -> float:
1464
+ """Positive is front, negative is back"""
1465
+ return self._return_dest_z
1466
+
1467
+ @return_dest_z.setter
1468
+ def return_dest_z(self, value: float, /) -> None:
1469
+ self._return_dest_z = value
1470
+
1471
+ @property
1472
+ def return_x_pos(self) -> float:
1473
+ """Positive is right, negative is left"""
1474
+ return self._return_x_pos
1475
+
1476
+ @return_x_pos.setter
1477
+ def return_x_pos(self, value: float, /) -> None:
1478
+ self._return_x_pos = value
1479
+
1480
+ @property
1481
+ def return_x_rot(self) -> float:
1482
+ """Represented in degrees from 0 to 360
1483
+ Positive turns top face front, negative turns top face back
1484
+ """
1485
+ return self._return_x_rot
1486
+
1487
+ @return_x_rot.setter
1488
+ def return_x_rot(self, value: float, /) -> None:
1489
+ self._return_x_rot = value % 360
1490
+
1491
+ @property
1492
+ def return_y_pos(self) -> float:
1493
+ """Positive is up, negative is down"""
1494
+ return self._return_y_pos
1495
+
1496
+ @return_y_pos.setter
1497
+ def return_y_pos(self, value: float, /) -> None:
1498
+ self._return_y_pos = value
1499
+
1500
+ @property
1501
+ def return_y_rot(self) -> float:
1502
+ """Represented in degrees from 0 to 360
1503
+ Positive turns front face right, negative turns front face left
1504
+ """
1505
+ return self._return_y_rot
1506
+
1507
+ @return_y_rot.setter
1508
+ def return_y_rot(self, value: float, /) -> None:
1509
+ self._return_y_rot = value % 360
1510
+
1511
+ @property
1512
+ def return_z_pos(self) -> float:
1513
+ """Positive is front, negative is back"""
1514
+ return self._return_z_pos
1515
+
1516
+ @return_z_pos.setter
1517
+ def return_z_pos(self, value: float, /) -> None:
1518
+ self._return_z_pos = value
1519
+
1520
+ @property
1521
+ def return_z_rot(self) -> float:
1522
+ """Represented in degrees from 0 to 360
1523
+ Positive turns top face left, negative turns top face right
1524
+ """
1525
+ return self._return_z_rot
1526
+
1527
+ @return_z_rot.setter
1528
+ def return_z_rot(self, value: float, /) -> None:
1529
+ self._return_z_rot = value % 360
1530
+
1531
+
1532
+ class BlinkingTile(TimedDevice):
1533
+ __slots__ = ()
1534
+
1535
+ @property
1536
+ def cost(self) -> Literal[20]:
1537
+ return 20
1538
+
1539
+
1540
+ class MelodyTile(BasePart):
1541
+ __slots__ = ("_note",)
1542
+
1543
+ _note: Literal[
1544
+ DeviceModel.MelodyTileLowG,
1545
+ DeviceModel.MelodyTileLowGSharp,
1546
+ DeviceModel.MelodyTileLowA,
1547
+ DeviceModel.MelodyTileLowASharp,
1548
+ DeviceModel.MelodyTileLowB,
1549
+ DeviceModel.MelodyTileC,
1550
+ DeviceModel.MelodyTileCSharp,
1551
+ DeviceModel.MelodyTileD,
1552
+ DeviceModel.MelodyTileDSharp,
1553
+ DeviceModel.MelodyTileE,
1554
+ DeviceModel.MelodyTileF,
1555
+ DeviceModel.MelodyTileFSharp,
1556
+ DeviceModel.MelodyTileG,
1557
+ DeviceModel.MelodyTileGSharp,
1558
+ DeviceModel.MelodyTileA,
1559
+ DeviceModel.MelodyTileASharp,
1560
+ DeviceModel.MelodyTileB,
1561
+ DeviceModel.MelodyTileHighC,
1562
+ DeviceModel.MelodyTileHighCSharp,
1563
+ DeviceModel.MelodyTileHighD,
1564
+ DeviceModel.MelodyTileHighDSharp,
1565
+ DeviceModel.MelodyTileHighE,
1566
+ DeviceModel.MelodyTileHighF,
1567
+ DeviceModel.MelodyTileHighFSharp,
1568
+ DeviceModel.MelodyTileHighG,
1569
+ ]
1570
+
1571
+ def __init__(
1572
+ self,
1573
+ x_pos: float,
1574
+ y_pos: float,
1575
+ z_pos: float,
1576
+ x_rot: float,
1577
+ y_rot: float,
1578
+ z_rot: float,
1579
+ *,
1580
+ note: Literal[
1581
+ DeviceModel.MelodyTileLowG,
1582
+ DeviceModel.MelodyTileLowGSharp,
1583
+ DeviceModel.MelodyTileLowA,
1584
+ DeviceModel.MelodyTileLowASharp,
1585
+ DeviceModel.MelodyTileLowB,
1586
+ DeviceModel.MelodyTileC,
1587
+ DeviceModel.MelodyTileCSharp,
1588
+ DeviceModel.MelodyTileD,
1589
+ DeviceModel.MelodyTileDSharp,
1590
+ DeviceModel.MelodyTileE,
1591
+ DeviceModel.MelodyTileF,
1592
+ DeviceModel.MelodyTileFSharp,
1593
+ DeviceModel.MelodyTileG,
1594
+ DeviceModel.MelodyTileGSharp,
1595
+ DeviceModel.MelodyTileA,
1596
+ DeviceModel.MelodyTileASharp,
1597
+ DeviceModel.MelodyTileB,
1598
+ DeviceModel.MelodyTileHighC,
1599
+ DeviceModel.MelodyTileHighCSharp,
1600
+ DeviceModel.MelodyTileHighD,
1601
+ DeviceModel.MelodyTileHighDSharp,
1602
+ DeviceModel.MelodyTileHighE,
1603
+ DeviceModel.MelodyTileHighF,
1604
+ DeviceModel.MelodyTileHighFSharp,
1605
+ DeviceModel.MelodyTileHighG,
1606
+ ],
1607
+ ) -> None:
1608
+ super().__init__(x_pos, y_pos, z_pos, x_rot, y_rot, z_rot)
1609
+ self.note = note
1610
+
1611
+ @property
1612
+ def cost(self) -> Literal[20]:
1613
+ return 20
1614
+
1615
+ @property
1616
+ def note(self) -> Literal[
1617
+ DeviceModel.MelodyTileLowG,
1618
+ DeviceModel.MelodyTileLowGSharp,
1619
+ DeviceModel.MelodyTileLowA,
1620
+ DeviceModel.MelodyTileLowASharp,
1621
+ DeviceModel.MelodyTileLowB,
1622
+ DeviceModel.MelodyTileC,
1623
+ DeviceModel.MelodyTileCSharp,
1624
+ DeviceModel.MelodyTileD,
1625
+ DeviceModel.MelodyTileDSharp,
1626
+ DeviceModel.MelodyTileE,
1627
+ DeviceModel.MelodyTileF,
1628
+ DeviceModel.MelodyTileFSharp,
1629
+ DeviceModel.MelodyTileG,
1630
+ DeviceModel.MelodyTileGSharp,
1631
+ DeviceModel.MelodyTileA,
1632
+ DeviceModel.MelodyTileASharp,
1633
+ DeviceModel.MelodyTileB,
1634
+ DeviceModel.MelodyTileHighC,
1635
+ DeviceModel.MelodyTileHighCSharp,
1636
+ DeviceModel.MelodyTileHighD,
1637
+ DeviceModel.MelodyTileHighDSharp,
1638
+ DeviceModel.MelodyTileHighE,
1639
+ DeviceModel.MelodyTileHighF,
1640
+ DeviceModel.MelodyTileHighFSharp,
1641
+ DeviceModel.MelodyTileHighG,
1642
+ ]:
1643
+ return self._note
1644
+
1645
+ @note.setter
1646
+ def note(
1647
+ self,
1648
+ value: Literal[
1649
+ DeviceModel.MelodyTileLowG,
1650
+ DeviceModel.MelodyTileLowGSharp,
1651
+ DeviceModel.MelodyTileLowA,
1652
+ DeviceModel.MelodyTileLowASharp,
1653
+ DeviceModel.MelodyTileLowB,
1654
+ DeviceModel.MelodyTileC,
1655
+ DeviceModel.MelodyTileCSharp,
1656
+ DeviceModel.MelodyTileD,
1657
+ DeviceModel.MelodyTileDSharp,
1658
+ DeviceModel.MelodyTileE,
1659
+ DeviceModel.MelodyTileF,
1660
+ DeviceModel.MelodyTileFSharp,
1661
+ DeviceModel.MelodyTileG,
1662
+ DeviceModel.MelodyTileGSharp,
1663
+ DeviceModel.MelodyTileA,
1664
+ DeviceModel.MelodyTileASharp,
1665
+ DeviceModel.MelodyTileB,
1666
+ DeviceModel.MelodyTileHighC,
1667
+ DeviceModel.MelodyTileHighCSharp,
1668
+ DeviceModel.MelodyTileHighD,
1669
+ DeviceModel.MelodyTileHighDSharp,
1670
+ DeviceModel.MelodyTileHighE,
1671
+ DeviceModel.MelodyTileHighF,
1672
+ DeviceModel.MelodyTileHighFSharp,
1673
+ DeviceModel.MelodyTileHighG,
1674
+ ],
1675
+ /,
1676
+ ) -> None:
1677
+ self._note = value
1678
+
1679
+ def __repr__(self) -> str:
1680
+ return f"{type(self).__name__}({self.x_pos!r}, {self.y_pos!r}, {self.z_pos!r}, {self.x_rot!r}, {self.y_rot!r}, {self.z_rot!r}, note={self.note!r})"
1681
+
1682
+
1683
+ class KororinCapsule(BasePart):
1684
+ __slots__ = ()
1685
+
1686
+ @property
1687
+ def cost(self) -> Literal[0]:
1688
+ return 0
1689
+
1690
+
1691
+ class GreenCrystal(BasePart):
1692
+ __slots__ = ()
1693
+
1694
+ @property
1695
+ def cost(self) -> Literal[0]:
1696
+ return 0
1697
+
1698
+
1699
+ class Ant(BasePart):
1700
+ __slots__ = ()
1701
+
1702
+ @property
1703
+ def cost(self) -> Literal[0]:
1704
+ return 0