koro 2.0.0rc3__py3-none-any.whl → 2.0.1__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 CHANGED
@@ -1,1754 +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 level 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
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