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