koro 1.1.6__py3-none-any.whl → 2.0.0rc2__py3-none-any.whl

Sign up to get free protection for your applications and to get access to all the features.
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