sonolus.py 0.1.1__py3-none-any.whl → 0.1.3__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of sonolus.py might be problematic. Click here for more details.
- sonolus/backend/mode.py +4 -4
- sonolus/backend/visitor.py +4 -2
- sonolus/build/compile.py +3 -3
- sonolus/build/engine.py +72 -6
- sonolus/script/archetype.py +42 -11
- sonolus/script/bucket.py +2 -2
- sonolus/script/callbacks.py +22 -0
- sonolus/script/comptime.py +14 -0
- sonolus/script/debug.py +1 -1
- sonolus/script/effect.py +11 -11
- sonolus/script/engine.py +63 -7
- sonolus/script/globals.py +79 -44
- sonolus/script/graphics.py +37 -28
- sonolus/script/instruction.py +151 -0
- sonolus/script/internal/builtin_impls.py +3 -3
- sonolus/script/internal/native.py +2 -2
- sonolus/script/interval.py +14 -0
- sonolus/script/iterator.py +3 -0
- sonolus/script/level.py +7 -7
- sonolus/script/num.py +30 -4
- sonolus/script/options.py +4 -4
- sonolus/script/particle.py +48 -48
- sonolus/script/pointer.py +3 -3
- sonolus/script/print.py +81 -0
- sonolus/script/runtime.py +150 -35
- sonolus/script/sprite.py +106 -104
- sonolus/script/text.py +407 -404
- sonolus/script/transform.py +13 -17
- sonolus/script/vec.py +31 -1
- {sonolus_py-0.1.1.dist-info → sonolus_py-0.1.3.dist-info}/METADATA +1 -2
- {sonolus_py-0.1.1.dist-info → sonolus_py-0.1.3.dist-info}/RECORD +34 -34
- {sonolus_py-0.1.1.dist-info → sonolus_py-0.1.3.dist-info}/WHEEL +1 -1
- sonolus/build/defaults.py +0 -32
- sonolus/script/icon.py +0 -73
- {sonolus_py-0.1.1.dist-info → sonolus_py-0.1.3.dist-info}/entry_points.txt +0 -0
- {sonolus_py-0.1.1.dist-info → sonolus_py-0.1.3.dist-info}/licenses/LICENSE +0 -0
sonolus/script/globals.py
CHANGED
|
@@ -86,42 +86,47 @@ def create_global(cls: type, blocks: dict[Mode, Block], offset: int | None):
|
|
|
86
86
|
|
|
87
87
|
@dataclass_transform()
|
|
88
88
|
def _play_runtime_environment[T](cls: type[T]) -> T:
|
|
89
|
-
return create_global(cls, {Mode.
|
|
89
|
+
return create_global(cls, {Mode.PLAY: PlayBlock.RuntimeEnvironment}, 0)
|
|
90
90
|
|
|
91
91
|
|
|
92
92
|
@dataclass_transform()
|
|
93
93
|
def _watch_runtime_environment[T](cls: type[T]) -> T:
|
|
94
|
-
return create_global(cls, {Mode.
|
|
94
|
+
return create_global(cls, {Mode.WATCH: WatchBlock.RuntimeEnvironment}, 0)
|
|
95
95
|
|
|
96
96
|
|
|
97
97
|
@dataclass_transform()
|
|
98
98
|
def _tutorial_runtime_environment[T](cls: type[T]) -> T:
|
|
99
|
-
return create_global(cls, {Mode.
|
|
99
|
+
return create_global(cls, {Mode.TUTORIAL: TutorialBlock.RuntimeEnvironment}, 0)
|
|
100
100
|
|
|
101
101
|
|
|
102
102
|
@dataclass_transform()
|
|
103
103
|
def _preview_runtime_environment[T](cls: type[T]) -> T:
|
|
104
|
-
return create_global(cls, {Mode.
|
|
104
|
+
return create_global(cls, {Mode.PREVIEW: PreviewBlock.RuntimeEnvironment}, 0)
|
|
105
105
|
|
|
106
106
|
|
|
107
107
|
@dataclass_transform()
|
|
108
108
|
def _play_runtime_update[T](cls: type[T]) -> T:
|
|
109
|
-
return create_global(cls, {Mode.
|
|
109
|
+
return create_global(cls, {Mode.PLAY: PlayBlock.RuntimeUpdate}, 0)
|
|
110
110
|
|
|
111
111
|
|
|
112
112
|
@dataclass_transform()
|
|
113
113
|
def _watch_runtime_update[T](cls: type[T]) -> T:
|
|
114
|
-
return create_global(cls, {Mode.
|
|
114
|
+
return create_global(cls, {Mode.WATCH: WatchBlock.RuntimeUpdate}, 0)
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
@dataclass_transform()
|
|
118
|
+
def _preview_runtime_canvas[T](cls: type[T]) -> T:
|
|
119
|
+
return create_global(cls, {Mode.PREVIEW: PreviewBlock.RuntimeCanvas}, 0)
|
|
115
120
|
|
|
116
121
|
|
|
117
122
|
@dataclass_transform()
|
|
118
123
|
def _tutorial_runtime_update[T](cls: type[T]) -> T:
|
|
119
|
-
return create_global(cls, {Mode.
|
|
124
|
+
return create_global(cls, {Mode.TUTORIAL: TutorialBlock.RuntimeUpdate}, 0)
|
|
120
125
|
|
|
121
126
|
|
|
122
127
|
@dataclass_transform()
|
|
123
128
|
def _runtime_touch_array[T](cls: type[T]) -> T:
|
|
124
|
-
return create_global(cls, {Mode.
|
|
129
|
+
return create_global(cls, {Mode.PLAY: PlayBlock.RuntimeTouchArray}, 0)
|
|
125
130
|
|
|
126
131
|
|
|
127
132
|
@dataclass_transform()
|
|
@@ -129,9 +134,10 @@ def _runtime_skin_transform[T](cls: type[T]) -> T:
|
|
|
129
134
|
return create_global(
|
|
130
135
|
cls,
|
|
131
136
|
{
|
|
132
|
-
Mode.
|
|
133
|
-
Mode.
|
|
134
|
-
Mode.
|
|
137
|
+
Mode.PLAY: PlayBlock.RuntimeSkinTransform,
|
|
138
|
+
Mode.WATCH: WatchBlock.RuntimeSkinTransform,
|
|
139
|
+
Mode.PREVIEW: PreviewBlock.RuntimeSkinTransform,
|
|
140
|
+
Mode.TUTORIAL: TutorialBlock.RuntimeSkinTransform,
|
|
135
141
|
},
|
|
136
142
|
0,
|
|
137
143
|
)
|
|
@@ -142,9 +148,9 @@ def _runtime_particle_transform[T](cls: type[T]) -> T:
|
|
|
142
148
|
return create_global(
|
|
143
149
|
cls,
|
|
144
150
|
{
|
|
145
|
-
Mode.
|
|
146
|
-
Mode.
|
|
147
|
-
Mode.
|
|
151
|
+
Mode.PLAY: PlayBlock.RuntimeParticleTransform,
|
|
152
|
+
Mode.WATCH: WatchBlock.RuntimeParticleTransform,
|
|
153
|
+
Mode.TUTORIAL: TutorialBlock.RuntimeParticleTransform,
|
|
148
154
|
},
|
|
149
155
|
0,
|
|
150
156
|
)
|
|
@@ -155,55 +161,84 @@ def _runtime_background[T](cls: type[T]) -> T:
|
|
|
155
161
|
return create_global(
|
|
156
162
|
cls,
|
|
157
163
|
{
|
|
158
|
-
Mode.
|
|
159
|
-
Mode.
|
|
160
|
-
Mode.
|
|
164
|
+
Mode.PLAY: PlayBlock.RuntimeBackground,
|
|
165
|
+
Mode.WATCH: WatchBlock.RuntimeBackground,
|
|
166
|
+
Mode.TUTORIAL: TutorialBlock.RuntimeBackground,
|
|
161
167
|
},
|
|
162
168
|
0,
|
|
163
169
|
)
|
|
164
170
|
|
|
165
171
|
|
|
166
172
|
@dataclass_transform()
|
|
167
|
-
def
|
|
168
|
-
return create_global(
|
|
169
|
-
cls,
|
|
170
|
-
{
|
|
171
|
-
Mode.Play: PlayBlock.RuntimeUI,
|
|
172
|
-
Mode.Watch: WatchBlock.RuntimeUI,
|
|
173
|
-
Mode.Tutorial: TutorialBlock.RuntimeUI,
|
|
174
|
-
Mode.Preview: PreviewBlock.RuntimeUI,
|
|
175
|
-
},
|
|
176
|
-
0,
|
|
177
|
-
)
|
|
173
|
+
def _play_runtime_ui[T](cls: type[T]) -> T:
|
|
174
|
+
return create_global(cls, {Mode.PLAY: PlayBlock.RuntimeUI}, 0)
|
|
178
175
|
|
|
179
176
|
|
|
180
177
|
@dataclass_transform()
|
|
181
|
-
def
|
|
182
|
-
return create_global(
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
178
|
+
def _watch_runtime_ui[T](cls: type[T]) -> T:
|
|
179
|
+
return create_global(cls, {Mode.WATCH: WatchBlock.RuntimeUI}, 0)
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
@dataclass_transform()
|
|
183
|
+
def _tutorial_runtime_ui[T](cls: type[T]) -> T:
|
|
184
|
+
return create_global(cls, {Mode.TUTORIAL: TutorialBlock.RuntimeUI}, 0)
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
@dataclass_transform()
|
|
188
|
+
def _preview_runtime_ui[T](cls: type[T]) -> T:
|
|
189
|
+
return create_global(cls, {Mode.PREVIEW: PreviewBlock.RuntimeUI}, 0)
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
@dataclass_transform()
|
|
193
|
+
def _play_runtime_ui_configuration[T](cls: type[T]) -> T:
|
|
194
|
+
return create_global(cls, {Mode.PLAY: PlayBlock.RuntimeUIConfiguration}, 0)
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
@dataclass_transform()
|
|
198
|
+
def _watch_runtime_ui_configuration[T](cls: type[T]) -> T:
|
|
199
|
+
return create_global(cls, {Mode.WATCH: WatchBlock.RuntimeUIConfiguration}, 0)
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
@dataclass_transform()
|
|
203
|
+
def _tutorial_runtime_ui_configuration[T](cls: type[T]) -> T:
|
|
204
|
+
return create_global(cls, {Mode.TUTORIAL: TutorialBlock.RuntimeUIConfiguration}, 0)
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
@dataclass_transform()
|
|
208
|
+
def _preview_runtime_ui_configuration[T](cls: type[T]) -> T:
|
|
209
|
+
return create_global(cls, {Mode.PREVIEW: PreviewBlock.RuntimeUIConfiguration}, 0)
|
|
192
210
|
|
|
193
211
|
|
|
194
212
|
@dataclass_transform()
|
|
195
213
|
def _tutorial_instruction[T](cls: type[T]) -> T:
|
|
196
|
-
return create_global(cls, {Mode.
|
|
214
|
+
return create_global(cls, {Mode.TUTORIAL: TutorialBlock.TutorialInstruction}, 0)
|
|
197
215
|
|
|
198
216
|
|
|
199
217
|
@dataclass_transform()
|
|
200
218
|
def level_memory[T](cls: type[T]) -> T:
|
|
201
|
-
return create_global(
|
|
219
|
+
return create_global(
|
|
220
|
+
cls,
|
|
221
|
+
{
|
|
222
|
+
Mode.PLAY: PlayBlock.LevelMemory,
|
|
223
|
+
Mode.WATCH: WatchBlock.LevelMemory,
|
|
224
|
+
Mode.TUTORIAL: TutorialBlock.TutorialMemory,
|
|
225
|
+
},
|
|
226
|
+
None,
|
|
227
|
+
)
|
|
202
228
|
|
|
203
229
|
|
|
204
230
|
@dataclass_transform()
|
|
205
231
|
def level_data[T](cls: type[T]) -> T:
|
|
206
|
-
return create_global(
|
|
232
|
+
return create_global(
|
|
233
|
+
cls,
|
|
234
|
+
{
|
|
235
|
+
Mode.PLAY: PlayBlock.LevelData,
|
|
236
|
+
Mode.WATCH: WatchBlock.LevelData,
|
|
237
|
+
Mode.PREVIEW: PreviewBlock.PreviewData,
|
|
238
|
+
Mode.TUTORIAL: TutorialBlock.TutorialData,
|
|
239
|
+
},
|
|
240
|
+
None,
|
|
241
|
+
)
|
|
207
242
|
|
|
208
243
|
|
|
209
244
|
# level_option is handled by the options decorator
|
|
@@ -212,12 +247,12 @@ def level_data[T](cls: type[T]) -> T:
|
|
|
212
247
|
|
|
213
248
|
@dataclass_transform()
|
|
214
249
|
def _level_score[T](cls: type[T]) -> T:
|
|
215
|
-
return create_global(cls, {Mode.
|
|
250
|
+
return create_global(cls, {Mode.PLAY: PlayBlock.LevelScore, Mode.WATCH: WatchBlock.LevelScore}, 0)
|
|
216
251
|
|
|
217
252
|
|
|
218
253
|
@dataclass_transform()
|
|
219
254
|
def _level_life[T](cls: type[T]) -> T:
|
|
220
|
-
return create_global(cls, {Mode.
|
|
255
|
+
return create_global(cls, {Mode.PLAY: PlayBlock.LevelLife, Mode.WATCH: WatchBlock.LevelLife}, 0)
|
|
221
256
|
|
|
222
257
|
|
|
223
258
|
# engine_rom is handled by the compiler
|
sonolus/script/graphics.py
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
from typing import Protocol
|
|
3
|
+
from typing import Protocol, Self
|
|
4
4
|
|
|
5
5
|
from sonolus.script.record import Record
|
|
6
6
|
from sonolus.script.vec import Vec2
|
|
@@ -20,21 +20,12 @@ class Rect(Record):
|
|
|
20
20
|
l: float # noqa: E741
|
|
21
21
|
|
|
22
22
|
@classmethod
|
|
23
|
-
def
|
|
23
|
+
def from_center(cls, center: Vec2, dimensions: Vec2) -> Rect:
|
|
24
24
|
return cls(
|
|
25
|
-
t=y +
|
|
26
|
-
r=x +
|
|
27
|
-
b=y,
|
|
28
|
-
l=x,
|
|
29
|
-
)
|
|
30
|
-
|
|
31
|
-
@classmethod
|
|
32
|
-
def from_center(cls, x: float, y: float, w: float, h: float) -> Rect:
|
|
33
|
-
return cls(
|
|
34
|
-
t=y + h / 2,
|
|
35
|
-
r=x + w / 2,
|
|
36
|
-
b=y - h / 2,
|
|
37
|
-
l=x - w / 2,
|
|
25
|
+
t=center.y + dimensions.y / 2,
|
|
26
|
+
r=center.x + dimensions.x / 2,
|
|
27
|
+
b=center.y - dimensions.y / 2,
|
|
28
|
+
l=center.x - dimensions.x / 2,
|
|
38
29
|
)
|
|
39
30
|
|
|
40
31
|
@property
|
|
@@ -99,19 +90,37 @@ class Rect(Record):
|
|
|
99
90
|
br=self.br,
|
|
100
91
|
)
|
|
101
92
|
|
|
102
|
-
def scale(self,
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
def
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
93
|
+
def scale(self, factor: Vec2, /) -> Self:
|
|
94
|
+
return Rect(
|
|
95
|
+
t=self.t * factor.y,
|
|
96
|
+
r=self.r * factor.x,
|
|
97
|
+
b=self.b * factor.y,
|
|
98
|
+
l=self.l * factor.x,
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
def expand(self, expansion: Vec2, /) -> Self:
|
|
102
|
+
return Rect(
|
|
103
|
+
t=self.t + expansion.y,
|
|
104
|
+
r=self.r + expansion.x,
|
|
105
|
+
b=self.b - expansion.y,
|
|
106
|
+
l=self.l - expansion.x,
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
def shrink(self, shrinkage: Vec2, /) -> Self:
|
|
110
|
+
return Rect(
|
|
111
|
+
t=self.t - shrinkage.y,
|
|
112
|
+
r=self.r - shrinkage.x,
|
|
113
|
+
b=self.b + shrinkage.y,
|
|
114
|
+
l=self.l + shrinkage.x,
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
def translate(self, translation: Vec2, /) -> Self:
|
|
118
|
+
return Rect(
|
|
119
|
+
t=self.t + translation.y,
|
|
120
|
+
r=self.r + translation.x,
|
|
121
|
+
b=self.b + translation.y,
|
|
122
|
+
l=self.l + translation.x,
|
|
123
|
+
)
|
|
115
124
|
|
|
116
125
|
|
|
117
126
|
class QuadLike(Protocol):
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
from typing import Annotated, Any, NewType, dataclass_transform, get_origin
|
|
3
|
+
|
|
4
|
+
from sonolus.backend.ops import Op
|
|
5
|
+
from sonolus.script.internal.introspection import get_field_specifiers
|
|
6
|
+
from sonolus.script.internal.native import native_function
|
|
7
|
+
from sonolus.script.record import Record
|
|
8
|
+
from sonolus.script.runtime import _TutorialInstruction
|
|
9
|
+
from sonolus.script.text import StandardText
|
|
10
|
+
from sonolus.script.vec import Vec2
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class InstructionText(Record):
|
|
14
|
+
id: int
|
|
15
|
+
|
|
16
|
+
def show(self):
|
|
17
|
+
show_instruction(self)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class InstructionIcon(Record):
|
|
21
|
+
id: int
|
|
22
|
+
|
|
23
|
+
def paint(self, position: Vec2, size: float, rotation: float, z: float, a: float):
|
|
24
|
+
_paint(self.id, position.x, position.y, size, rotation, z, a)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@dataclass
|
|
28
|
+
class InstructionTextInfo:
|
|
29
|
+
name: str
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@dataclass
|
|
33
|
+
class InstructionIconInfo:
|
|
34
|
+
name: str
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def instruction(name: str) -> Any:
|
|
38
|
+
return InstructionTextInfo(name=name)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def instruction_icon(name: str) -> Any:
|
|
42
|
+
return InstructionIconInfo(name=name)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
type TutorialInstructions = NewType("TutorialInstructions", Any)
|
|
46
|
+
type TutorialInstructionIcons = NewType("TutorialInstructionIcons", Any)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
@dataclass_transform()
|
|
50
|
+
def instructions[T](cls: type[T]) -> T | TutorialInstructions:
|
|
51
|
+
if len(cls.__bases__) != 1:
|
|
52
|
+
raise ValueError("Instructions class must not inherit from any class (except object)")
|
|
53
|
+
instance = cls()
|
|
54
|
+
names = []
|
|
55
|
+
for i, (name, annotation) in enumerate(get_field_specifiers(cls).items()):
|
|
56
|
+
if get_origin(annotation) is not Annotated:
|
|
57
|
+
raise TypeError(f"Invalid annotation for instruction: {annotation}")
|
|
58
|
+
annotation_type = annotation.__args__[0]
|
|
59
|
+
annotation_values = annotation.__metadata__
|
|
60
|
+
if annotation_type is not InstructionText:
|
|
61
|
+
raise TypeError(
|
|
62
|
+
f"Invalid annotation for instruction: {annotation}, expected annotation of type InstructionText"
|
|
63
|
+
)
|
|
64
|
+
if len(annotation_values) != 1 or not isinstance(annotation_values[0], InstructionTextInfo):
|
|
65
|
+
raise TypeError(f"Invalid annotation for instruction: {annotation}, expected a single annotation value")
|
|
66
|
+
instruction_name = annotation_values[0].name
|
|
67
|
+
names.append(instruction_name)
|
|
68
|
+
setattr(instance, name, InstructionText(i))
|
|
69
|
+
instance._instructions_ = names
|
|
70
|
+
instance._is_comptime_value_ = True
|
|
71
|
+
return instance
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
@dataclass_transform()
|
|
75
|
+
def instruction_icons[T](cls: type[T]) -> T | TutorialInstructionIcons:
|
|
76
|
+
if len(cls.__bases__) != 1:
|
|
77
|
+
raise ValueError("Instruction icons class must not inherit from any class (except object)")
|
|
78
|
+
instance = cls()
|
|
79
|
+
names = []
|
|
80
|
+
for i, (name, annotation) in enumerate(get_field_specifiers(cls).items()):
|
|
81
|
+
if get_origin(annotation) is not Annotated:
|
|
82
|
+
raise TypeError(f"Invalid annotation for instruction icon: {annotation}")
|
|
83
|
+
annotation_type = annotation.__args__[0]
|
|
84
|
+
annotation_values = annotation.__metadata__
|
|
85
|
+
if annotation_type is not InstructionIcon:
|
|
86
|
+
raise TypeError(
|
|
87
|
+
f"Invalid annotation for instruction icon: {annotation}, expected annotation of type InstructionIcon"
|
|
88
|
+
)
|
|
89
|
+
if len(annotation_values) != 1 or not isinstance(annotation_values[0], InstructionIconInfo):
|
|
90
|
+
raise TypeError(
|
|
91
|
+
f"Invalid annotation for instruction icon: {annotation}, expected a single annotation value"
|
|
92
|
+
)
|
|
93
|
+
icon_name = annotation_values[0].name
|
|
94
|
+
names.append(icon_name)
|
|
95
|
+
setattr(instance, name, InstructionIcon(i))
|
|
96
|
+
instance._instruction_icons_ = names
|
|
97
|
+
instance._is_comptime_value_ = True
|
|
98
|
+
return instance
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
class StandardInstruction:
|
|
102
|
+
TAP = Annotated[InstructionText, instruction(StandardText.TAP)]
|
|
103
|
+
TAP_HOLD = Annotated[InstructionText, instruction(StandardText.TAP_HOLD)]
|
|
104
|
+
TAP_RELEASE = Annotated[InstructionText, instruction(StandardText.TAP_RELEASE)]
|
|
105
|
+
TAP_FLICK = Annotated[InstructionText, instruction(StandardText.TAP_FLICK)]
|
|
106
|
+
TAP_SLIDE = Annotated[InstructionText, instruction(StandardText.TAP_SLIDE)]
|
|
107
|
+
HOLD = Annotated[InstructionText, instruction(StandardText.HOLD)]
|
|
108
|
+
HOLD_SLIDE = Annotated[InstructionText, instruction(StandardText.HOLD_SLIDE)]
|
|
109
|
+
HOLD_FOLLOW = Annotated[InstructionText, instruction(StandardText.HOLD_FOLLOW)]
|
|
110
|
+
RELEASE = Annotated[InstructionText, instruction(StandardText.RELEASE)]
|
|
111
|
+
FLICK = Annotated[InstructionText, instruction(StandardText.FLICK)]
|
|
112
|
+
SLIDE = Annotated[InstructionText, instruction(StandardText.SLIDE)]
|
|
113
|
+
SLIDE_FLICK = Annotated[InstructionText, instruction(StandardText.SLIDE_FLICK)]
|
|
114
|
+
AVOID = Annotated[InstructionText, instruction(StandardText.AVOID)]
|
|
115
|
+
JIGGLE = Annotated[InstructionText, instruction(StandardText.JIGGLE)]
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
class StandardInstructionIcon:
|
|
119
|
+
HAND = Annotated[InstructionIcon, instruction_icon("#HAND")]
|
|
120
|
+
ARROW = Annotated[InstructionIcon, instruction_icon("#ARROW")]
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
@instructions
|
|
124
|
+
class EmptyInstructions:
|
|
125
|
+
pass
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
@instruction_icons
|
|
129
|
+
class EmptyInstructionIcons:
|
|
130
|
+
pass
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
@native_function(Op.Paint)
|
|
134
|
+
def _paint(
|
|
135
|
+
icon_id: int,
|
|
136
|
+
x: float,
|
|
137
|
+
y: float,
|
|
138
|
+
size: float,
|
|
139
|
+
rotation: float,
|
|
140
|
+
z: float,
|
|
141
|
+
a: float,
|
|
142
|
+
):
|
|
143
|
+
raise NotImplementedError()
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def show_instruction(inst: InstructionText, /):
|
|
147
|
+
_TutorialInstruction.text_id = inst.id
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
def clear_instruction():
|
|
151
|
+
_TutorialInstruction.text_id = -1
|
|
@@ -5,7 +5,7 @@ from sonolus.script.internal.context import ctx
|
|
|
5
5
|
from sonolus.script.internal.impl import meta_fn, validate_value
|
|
6
6
|
from sonolus.script.iterator import ArrayLike, Enumerator, SonolusIterator
|
|
7
7
|
from sonolus.script.math import MATH_BUILTIN_IMPLS
|
|
8
|
-
from sonolus.script.num import
|
|
8
|
+
from sonolus.script.num import is_num
|
|
9
9
|
from sonolus.script.range import Range
|
|
10
10
|
|
|
11
11
|
|
|
@@ -74,7 +74,7 @@ def _max(*args):
|
|
|
74
74
|
else:
|
|
75
75
|
raise TypeError(f"Unsupported type: {type(iterable)} for max")
|
|
76
76
|
else:
|
|
77
|
-
if not all(
|
|
77
|
+
if not all(is_num(arg) for arg in args):
|
|
78
78
|
raise TypeError("Arguments to max must be numbers")
|
|
79
79
|
if ctx():
|
|
80
80
|
result = compile_and_call(_max2, args[0], args[1])
|
|
@@ -114,7 +114,7 @@ def _min(*args):
|
|
|
114
114
|
else:
|
|
115
115
|
raise TypeError(f"Unsupported type: {type(iterable)} for min")
|
|
116
116
|
else:
|
|
117
|
-
if not all(
|
|
117
|
+
if not all(is_num(arg) for arg in args):
|
|
118
118
|
raise TypeError("Arguments to min must be numbers")
|
|
119
119
|
if ctx():
|
|
120
120
|
result = compile_and_call(_min2, args[0], args[1])
|
|
@@ -6,14 +6,14 @@ from sonolus.backend.ir import IRInstr, IRPureInstr, IRSet
|
|
|
6
6
|
from sonolus.backend.ops import Op
|
|
7
7
|
from sonolus.script.internal.context import ctx
|
|
8
8
|
from sonolus.script.internal.impl import meta_fn, validate_value
|
|
9
|
-
from sonolus.script.num import Num
|
|
9
|
+
from sonolus.script.num import Num, is_num
|
|
10
10
|
|
|
11
11
|
|
|
12
12
|
def native_call(op: Op, *args: Num) -> Num:
|
|
13
13
|
if not ctx():
|
|
14
14
|
raise RuntimeError("Unexpected native call")
|
|
15
15
|
args = tuple(validate_value(arg) for arg in args)
|
|
16
|
-
if not all(
|
|
16
|
+
if not all(is_num(arg) for arg in args):
|
|
17
17
|
raise RuntimeError("All arguments must be of type Num")
|
|
18
18
|
result = ctx().alloc(size=1)
|
|
19
19
|
ctx().add_statements(IRSet(result, (IRPureInstr if op.pure else IRInstr)(op, [arg.ir() for arg in args])))
|
sonolus/script/interval.py
CHANGED
|
@@ -55,6 +55,12 @@ class Interval(Record):
|
|
|
55
55
|
def __and__(self, other: Self) -> Self:
|
|
56
56
|
return Interval(max(self.start, other.start), min(self.end, other.end))
|
|
57
57
|
|
|
58
|
+
def shrink(self, value: float | int) -> Self:
|
|
59
|
+
return Interval(self.start + value, self.end - value)
|
|
60
|
+
|
|
61
|
+
def expand(self, value: float | int) -> Self:
|
|
62
|
+
return Interval(self.start - value, self.end + value)
|
|
63
|
+
|
|
58
64
|
def lerp(self, x: float, /) -> float:
|
|
59
65
|
return lerp(self.start, self.end, x)
|
|
60
66
|
|
|
@@ -67,6 +73,9 @@ class Interval(Record):
|
|
|
67
73
|
def unlerp_clamped(self, x: float, /) -> float:
|
|
68
74
|
return unlerp_clamped(self.start, self.end, x)
|
|
69
75
|
|
|
76
|
+
def clamp(self, x: float, /) -> float:
|
|
77
|
+
return clamp(x, self.start, self.end)
|
|
78
|
+
|
|
70
79
|
|
|
71
80
|
@native_function(Op.Lerp)
|
|
72
81
|
def lerp(a, b, x, /):
|
|
@@ -96,3 +105,8 @@ def remap(a, b, c, d, x, /):
|
|
|
96
105
|
@native_function(Op.RemapClamped)
|
|
97
106
|
def remap_clamped(a, b, c, d, x, /):
|
|
98
107
|
return c + (d - c) * max(0, min(1, (x - a) / (b - a)))
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
@native_function(Op.Clamp)
|
|
111
|
+
def clamp(x, a, b, /):
|
|
112
|
+
return max(a, min(b, x))
|
sonolus/script/iterator.py
CHANGED
|
@@ -180,6 +180,9 @@ class ArrayReverser[V: ArrayLike](Record, ArrayLike):
|
|
|
180
180
|
def __setitem__(self, index: Num, value: V):
|
|
181
181
|
self.array[self.size() - 1 - index] = value
|
|
182
182
|
|
|
183
|
+
def reversed(self) -> ArrayLike[V]:
|
|
184
|
+
return self.array
|
|
185
|
+
|
|
183
186
|
|
|
184
187
|
class Enumerator[V: SonolusIterator](Record, SonolusIterator):
|
|
185
188
|
i: int
|
sonolus/script/level.py
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
from sonolus.build.collection import Asset
|
|
4
|
-
from sonolus.script.archetype import PlayArchetype, StandardImport
|
|
4
|
+
from sonolus.script.archetype import PlayArchetype, StandardArchetypeName, StandardImport
|
|
5
5
|
|
|
6
6
|
|
|
7
7
|
class Level:
|
|
@@ -39,14 +39,14 @@ class LevelData:
|
|
|
39
39
|
|
|
40
40
|
|
|
41
41
|
class BpmChange(PlayArchetype):
|
|
42
|
-
name =
|
|
42
|
+
name = StandardArchetypeName.BPM_CHANGE
|
|
43
43
|
|
|
44
|
-
beat: StandardImport.
|
|
45
|
-
bpm: StandardImport.
|
|
44
|
+
beat: StandardImport.BEAT
|
|
45
|
+
bpm: StandardImport.BPM
|
|
46
46
|
|
|
47
47
|
|
|
48
48
|
class TimescaleChange(PlayArchetype):
|
|
49
|
-
name =
|
|
49
|
+
name = StandardArchetypeName.TIMESCALE_CHANGE
|
|
50
50
|
|
|
51
|
-
beat: StandardImport.
|
|
52
|
-
timescale: StandardImport.
|
|
51
|
+
beat: StandardImport.BEAT
|
|
52
|
+
timescale: StandardImport.TIMESCALE
|
sonolus/script/num.py
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
# ruff: noqa: N801
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
2
4
|
import operator
|
|
3
5
|
from collections.abc import Callable, Iterable
|
|
4
|
-
from typing import TYPE_CHECKING, Any, Self, final
|
|
6
|
+
from typing import TYPE_CHECKING, Any, Self, TypeIs, final
|
|
5
7
|
|
|
6
8
|
from sonolus.backend.ir import IRConst, IRGet, IRPureInstr, IRSet
|
|
7
9
|
from sonolus.backend.ops import Op
|
|
@@ -12,14 +14,30 @@ from sonolus.script.internal.impl import meta_fn
|
|
|
12
14
|
from sonolus.script.internal.value import Value
|
|
13
15
|
|
|
14
16
|
|
|
17
|
+
class NumMeta(type):
|
|
18
|
+
def __instancecheck__(cls, instance):
|
|
19
|
+
return isinstance(instance, float | int | bool) or is_num(instance)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def is_num(value: Any) -> TypeIs[Num]:
|
|
23
|
+
"""Check if a value is a precisely Num instance."""
|
|
24
|
+
return type.__instancecheck__(Num, value) # type: ignore # noqa: PLC2801
|
|
25
|
+
|
|
26
|
+
|
|
15
27
|
@final
|
|
16
|
-
class _Num(Value):
|
|
28
|
+
class _Num(Value, metaclass=NumMeta):
|
|
29
|
+
# This works for ints, floats, and bools
|
|
30
|
+
# Since we don't support complex numbers, real is equal to the original number
|
|
31
|
+
__match_args__ = ("real",)
|
|
32
|
+
|
|
17
33
|
data: BlockPlace | float | int | bool
|
|
18
34
|
|
|
19
35
|
def __init__(self, data: Place | float | int | bool):
|
|
36
|
+
if isinstance(data, complex):
|
|
37
|
+
raise TypeError("Cannot create a Num from a complex number")
|
|
20
38
|
if isinstance(data, int):
|
|
21
39
|
data = float(data)
|
|
22
|
-
if
|
|
40
|
+
if is_num(data):
|
|
23
41
|
raise InternalError("Cannot create a Num from a Num")
|
|
24
42
|
self.data = data
|
|
25
43
|
|
|
@@ -55,7 +73,7 @@ class _Num(Value):
|
|
|
55
73
|
def _accept_(cls, value: Any) -> Self:
|
|
56
74
|
if not cls._accepts_(value):
|
|
57
75
|
raise TypeError(f"Cannot accept {value}")
|
|
58
|
-
if
|
|
76
|
+
if is_num(value):
|
|
59
77
|
return value
|
|
60
78
|
return cls(value)
|
|
61
79
|
|
|
@@ -368,6 +386,14 @@ class _Num(Value):
|
|
|
368
386
|
def not_(self) -> Self:
|
|
369
387
|
return self._unary_op(operator.not_, Op.Not)
|
|
370
388
|
|
|
389
|
+
@property
|
|
390
|
+
def real(self) -> Self:
|
|
391
|
+
return self
|
|
392
|
+
|
|
393
|
+
@property
|
|
394
|
+
def imag(self) -> Self:
|
|
395
|
+
return 0
|
|
396
|
+
|
|
371
397
|
|
|
372
398
|
if TYPE_CHECKING:
|
|
373
399
|
|
sonolus/script/options.py
CHANGED
|
@@ -139,13 +139,13 @@ class OptionField(SonolusDescriptor):
|
|
|
139
139
|
def __get__(self, instance, owner):
|
|
140
140
|
if ctx():
|
|
141
141
|
match ctx().global_state.mode:
|
|
142
|
-
case Mode.
|
|
142
|
+
case Mode.PLAY:
|
|
143
143
|
block = ctx().blocks.LevelOption
|
|
144
|
-
case Mode.
|
|
144
|
+
case Mode.WATCH:
|
|
145
145
|
block = ctx().blocks.LevelOption
|
|
146
|
-
case Mode.
|
|
146
|
+
case Mode.PREVIEW:
|
|
147
147
|
block = ctx().blocks.PreviewOption
|
|
148
|
-
case Mode.
|
|
148
|
+
case Mode.TUTORIAL:
|
|
149
149
|
block = None
|
|
150
150
|
case _:
|
|
151
151
|
assert_unreachable()
|