sonolus.py 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of sonolus.py might be problematic. Click here for more details.
- sonolus/__init__.py +0 -0
- sonolus/backend/__init__.py +0 -0
- sonolus/backend/allocate.py +51 -0
- sonolus/backend/blocks.py +756 -0
- sonolus/backend/excepthook.py +37 -0
- sonolus/backend/finalize.py +69 -0
- sonolus/backend/flow.py +92 -0
- sonolus/backend/interpret.py +333 -0
- sonolus/backend/ir.py +89 -0
- sonolus/backend/mode.py +24 -0
- sonolus/backend/node.py +40 -0
- sonolus/backend/ops.py +197 -0
- sonolus/backend/optimize.py +9 -0
- sonolus/backend/passes.py +6 -0
- sonolus/backend/place.py +90 -0
- sonolus/backend/simplify.py +30 -0
- sonolus/backend/utils.py +48 -0
- sonolus/backend/visitor.py +880 -0
- sonolus/build/__init__.py +0 -0
- sonolus/build/cli.py +170 -0
- sonolus/build/collection.py +293 -0
- sonolus/build/compile.py +90 -0
- sonolus/build/defaults.py +32 -0
- sonolus/build/engine.py +149 -0
- sonolus/build/level.py +23 -0
- sonolus/build/node.py +43 -0
- sonolus/build/project.py +94 -0
- sonolus/py.typed +0 -0
- sonolus/script/__init__.py +0 -0
- sonolus/script/archetype.py +651 -0
- sonolus/script/array.py +241 -0
- sonolus/script/bucket.py +192 -0
- sonolus/script/callbacks.py +105 -0
- sonolus/script/comptime.py +146 -0
- sonolus/script/containers.py +247 -0
- sonolus/script/debug.py +70 -0
- sonolus/script/effect.py +132 -0
- sonolus/script/engine.py +101 -0
- sonolus/script/globals.py +234 -0
- sonolus/script/graphics.py +141 -0
- sonolus/script/icon.py +73 -0
- sonolus/script/internal/__init__.py +5 -0
- sonolus/script/internal/builtin_impls.py +144 -0
- sonolus/script/internal/context.py +365 -0
- sonolus/script/internal/descriptor.py +17 -0
- sonolus/script/internal/error.py +15 -0
- sonolus/script/internal/generic.py +197 -0
- sonolus/script/internal/impl.py +69 -0
- sonolus/script/internal/introspection.py +14 -0
- sonolus/script/internal/native.py +38 -0
- sonolus/script/internal/value.py +144 -0
- sonolus/script/interval.py +98 -0
- sonolus/script/iterator.py +211 -0
- sonolus/script/level.py +52 -0
- sonolus/script/math.py +92 -0
- sonolus/script/num.py +382 -0
- sonolus/script/options.py +194 -0
- sonolus/script/particle.py +158 -0
- sonolus/script/pointer.py +30 -0
- sonolus/script/project.py +17 -0
- sonolus/script/range.py +58 -0
- sonolus/script/record.py +293 -0
- sonolus/script/runtime.py +526 -0
- sonolus/script/sprite.py +332 -0
- sonolus/script/text.py +404 -0
- sonolus/script/timing.py +42 -0
- sonolus/script/transform.py +118 -0
- sonolus/script/ui.py +160 -0
- sonolus/script/values.py +43 -0
- sonolus/script/vec.py +48 -0
- sonolus_py-0.1.0.dist-info/METADATA +10 -0
- sonolus_py-0.1.0.dist-info/RECORD +75 -0
- sonolus_py-0.1.0.dist-info/WHEEL +4 -0
- sonolus_py-0.1.0.dist-info/entry_points.txt +2 -0
- sonolus_py-0.1.0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from sonolus.script.array import Array
|
|
4
|
+
from sonolus.script.debug import error
|
|
5
|
+
from sonolus.script.iterator import ArrayLike, SonolusIterator
|
|
6
|
+
from sonolus.script.range import Range
|
|
7
|
+
from sonolus.script.record import Record
|
|
8
|
+
from sonolus.script.values import alloc, copy
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class Pair[T, U](Record):
|
|
12
|
+
first: T
|
|
13
|
+
second: U
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class VarArray[T, Capacity](Record, ArrayLike[T]):
|
|
17
|
+
_size: int
|
|
18
|
+
_array: Array[T, Capacity]
|
|
19
|
+
|
|
20
|
+
@classmethod
|
|
21
|
+
def new(cls):
|
|
22
|
+
element_type = cls._get_type_arg_(T)
|
|
23
|
+
capacity = cls._get_type_arg_(Capacity)
|
|
24
|
+
return cls(0, alloc(Array[element_type, capacity]))
|
|
25
|
+
|
|
26
|
+
def size(self) -> int:
|
|
27
|
+
return self._size
|
|
28
|
+
|
|
29
|
+
@classmethod
|
|
30
|
+
def capacity(cls) -> int:
|
|
31
|
+
return cls._get_type_arg_(Capacity)
|
|
32
|
+
|
|
33
|
+
def is_full(self) -> bool:
|
|
34
|
+
return self._size == self.capacity()
|
|
35
|
+
|
|
36
|
+
def __getitem__(self, item) -> T:
|
|
37
|
+
return self._array[item]
|
|
38
|
+
|
|
39
|
+
def __setitem__(self, key: int, value: T):
|
|
40
|
+
self._array[key] = value
|
|
41
|
+
|
|
42
|
+
def append(self, value: T):
|
|
43
|
+
"""Appends a copy of the given value to the end of the array."""
|
|
44
|
+
assert self._size < self._array.size()
|
|
45
|
+
self._array[self._size] = value
|
|
46
|
+
self._size += 1
|
|
47
|
+
|
|
48
|
+
def extend(self, values: ArrayLike[T]):
|
|
49
|
+
"""Appends copies of the values in the given array to the end of the array."""
|
|
50
|
+
for value in values:
|
|
51
|
+
self.append(value)
|
|
52
|
+
|
|
53
|
+
def pop(self, index: int | None = None) -> T:
|
|
54
|
+
"""Removes and returns a copy of the value at the given index.
|
|
55
|
+
|
|
56
|
+
Preserves the relative order of the elements.
|
|
57
|
+
"""
|
|
58
|
+
if index is None:
|
|
59
|
+
index = self._size - 1
|
|
60
|
+
assert 0 <= index < self._size
|
|
61
|
+
value = copy(self._array[index])
|
|
62
|
+
self._size -= 1
|
|
63
|
+
if index < self._size:
|
|
64
|
+
for i in Range(index, self._size):
|
|
65
|
+
self._array[i] = self._array[i + 1]
|
|
66
|
+
return value
|
|
67
|
+
|
|
68
|
+
def insert(self, index: int, value: T):
|
|
69
|
+
"""Inserts a copy of the given value at the given index.
|
|
70
|
+
|
|
71
|
+
Preserves the relative order of the elements.
|
|
72
|
+
"""
|
|
73
|
+
assert 0 <= index <= self._size
|
|
74
|
+
assert self._size < self._array.size()
|
|
75
|
+
self._size += 1
|
|
76
|
+
for i in Range(self._size - 1, index, -1):
|
|
77
|
+
self._array[i] = self._array[i - 1]
|
|
78
|
+
self._array[index] = value
|
|
79
|
+
|
|
80
|
+
def remove(self, value: T) -> bool:
|
|
81
|
+
"""Removes the first occurrence of the given value, returning whether the value was removed.
|
|
82
|
+
|
|
83
|
+
Preserves the relative order of the elements.
|
|
84
|
+
"""
|
|
85
|
+
index = self.index_of(value)
|
|
86
|
+
if index < 0:
|
|
87
|
+
return False
|
|
88
|
+
self.pop(index)
|
|
89
|
+
return True
|
|
90
|
+
|
|
91
|
+
def clear(self):
|
|
92
|
+
"""Sets size to zero."""
|
|
93
|
+
self._size = 0
|
|
94
|
+
|
|
95
|
+
def set_add(self, value: T) -> bool:
|
|
96
|
+
"""Adds a copy of the given value if it is not already present, returning whether the value was added.
|
|
97
|
+
|
|
98
|
+
If the value is already present, the array is not modified.
|
|
99
|
+
If the array is full, the value is not added.
|
|
100
|
+
"""
|
|
101
|
+
if self._size >= self._array.size():
|
|
102
|
+
return False
|
|
103
|
+
if value in self:
|
|
104
|
+
return False
|
|
105
|
+
self.append(value)
|
|
106
|
+
return True
|
|
107
|
+
|
|
108
|
+
def set_remove(self, value: T) -> bool:
|
|
109
|
+
"""Removes the first occurrence of the given value, returning whether the value was removed.
|
|
110
|
+
|
|
111
|
+
Does not preserve the relative order of the elements.
|
|
112
|
+
"""
|
|
113
|
+
index = self.index_of(value)
|
|
114
|
+
if index < 0:
|
|
115
|
+
return False
|
|
116
|
+
if index < self._size - 1:
|
|
117
|
+
self._array[index] = self._array[self._size - 1]
|
|
118
|
+
self._size -= 1
|
|
119
|
+
return True
|
|
120
|
+
|
|
121
|
+
def __eq__(self, other):
|
|
122
|
+
if self.size() != other.size():
|
|
123
|
+
return False
|
|
124
|
+
i = 0
|
|
125
|
+
while i < self.size():
|
|
126
|
+
if self[i] != other[i]:
|
|
127
|
+
return False
|
|
128
|
+
i += 1
|
|
129
|
+
return True
|
|
130
|
+
|
|
131
|
+
def __ne__(self, other):
|
|
132
|
+
return not self == other
|
|
133
|
+
|
|
134
|
+
def __hash__(self):
|
|
135
|
+
raise TypeError("unhashable type: 'VarArray'")
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
class ArrayMapEntry[K, V](Record):
|
|
139
|
+
key: K
|
|
140
|
+
value: V
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
class ArrayMap[K, V, Capacity](Record):
|
|
144
|
+
_size: int
|
|
145
|
+
_array: Array[ArrayMapEntry[K, V], Capacity]
|
|
146
|
+
|
|
147
|
+
@classmethod
|
|
148
|
+
def new(cls):
|
|
149
|
+
key_type = cls._get_type_arg_(K)
|
|
150
|
+
value_type = cls._get_type_arg_(V)
|
|
151
|
+
capacity = cls._get_type_arg_(Capacity)
|
|
152
|
+
return cls(0, alloc(Array[ArrayMapEntry[key_type, value_type], capacity]))
|
|
153
|
+
|
|
154
|
+
def size(self) -> int:
|
|
155
|
+
return self._size
|
|
156
|
+
|
|
157
|
+
@classmethod
|
|
158
|
+
def capacity(cls) -> int:
|
|
159
|
+
return cls._get_type_arg_(Capacity)
|
|
160
|
+
|
|
161
|
+
def is_full(self) -> bool:
|
|
162
|
+
return self._size == self.capacity()
|
|
163
|
+
|
|
164
|
+
def keys(self) -> SonolusIterator[K]:
|
|
165
|
+
return _ArrayMapKeyIterator(self, 0)
|
|
166
|
+
|
|
167
|
+
def values(self) -> SonolusIterator[V]:
|
|
168
|
+
return ArrayMapValueIterator(self, 0)
|
|
169
|
+
|
|
170
|
+
def items(self) -> SonolusIterator[tuple[K, V]]:
|
|
171
|
+
return ArrayMapEntryIterator(self, 0)
|
|
172
|
+
|
|
173
|
+
def __getitem__(self, key: K) -> V:
|
|
174
|
+
for i in Range(self._size):
|
|
175
|
+
entry = self._array[i]
|
|
176
|
+
if entry.key == key:
|
|
177
|
+
return entry.value
|
|
178
|
+
error()
|
|
179
|
+
|
|
180
|
+
def __setitem__(self, key: K, value: V):
|
|
181
|
+
for i in Range(self._size):
|
|
182
|
+
entry = self._array[i]
|
|
183
|
+
if entry.key == key:
|
|
184
|
+
entry.value = value
|
|
185
|
+
return
|
|
186
|
+
# assert self._size < self.capacity()
|
|
187
|
+
self._array[self._size] = ArrayMapEntry(key, value)
|
|
188
|
+
self._size += 1
|
|
189
|
+
|
|
190
|
+
def __contains__(self, key: K) -> bool:
|
|
191
|
+
for i in Range(self._size): # noqa: SIM110
|
|
192
|
+
if self._array[i].key == key:
|
|
193
|
+
return True
|
|
194
|
+
return False
|
|
195
|
+
|
|
196
|
+
def pop(self, key: K) -> V:
|
|
197
|
+
for i in Range(self._size):
|
|
198
|
+
entry = self._array[i]
|
|
199
|
+
if entry.key == key:
|
|
200
|
+
value = copy(entry.value)
|
|
201
|
+
self._size -= 1
|
|
202
|
+
if i < self._size:
|
|
203
|
+
self._array[i] = self._array[self._size]
|
|
204
|
+
return value
|
|
205
|
+
error()
|
|
206
|
+
|
|
207
|
+
def clear(self):
|
|
208
|
+
self._size = 0
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
class _ArrayMapKeyIterator[K, V, Capacity](Record, SonolusIterator):
|
|
212
|
+
_map: ArrayMap[K, V, Capacity]
|
|
213
|
+
_index: int
|
|
214
|
+
|
|
215
|
+
def has_next(self) -> bool:
|
|
216
|
+
return self._index < self._map.size()
|
|
217
|
+
|
|
218
|
+
def next(self) -> K:
|
|
219
|
+
key = self._map._array[self._index].key
|
|
220
|
+
self._index += 1
|
|
221
|
+
return key
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
class ArrayMapValueIterator[K, V, Capacity](Record, SonolusIterator):
|
|
225
|
+
_map: ArrayMap[K, V, Capacity]
|
|
226
|
+
_index: int
|
|
227
|
+
|
|
228
|
+
def has_next(self) -> bool:
|
|
229
|
+
return self._index < self._map.size()
|
|
230
|
+
|
|
231
|
+
def next(self) -> V:
|
|
232
|
+
value = self._map._array[self._index].value
|
|
233
|
+
self._index += 1
|
|
234
|
+
return value
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
class ArrayMapEntryIterator[K, V, Capacity](Record, SonolusIterator):
|
|
238
|
+
_map: ArrayMap[K, V, Capacity]
|
|
239
|
+
_index: int
|
|
240
|
+
|
|
241
|
+
def has_next(self) -> bool:
|
|
242
|
+
return self._index < self._map.size()
|
|
243
|
+
|
|
244
|
+
def next(self) -> tuple[K, V]:
|
|
245
|
+
entry = self._map._array[self._index]
|
|
246
|
+
self._index += 1
|
|
247
|
+
return entry.key, entry.value
|
sonolus/script/debug.py
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
from collections.abc import Callable
|
|
2
|
+
from typing import Any, Never
|
|
3
|
+
|
|
4
|
+
from sonolus.backend.flow import cfg_to_mermaid
|
|
5
|
+
from sonolus.backend.mode import Mode
|
|
6
|
+
from sonolus.backend.ops import Op
|
|
7
|
+
from sonolus.backend.simplify import CoalesceFlow
|
|
8
|
+
from sonolus.script.comptime import Comptime
|
|
9
|
+
from sonolus.script.internal.context import GlobalContextState, ctx, set_ctx
|
|
10
|
+
from sonolus.script.internal.impl import meta_fn, validate_value
|
|
11
|
+
from sonolus.script.internal.native import native_function
|
|
12
|
+
from sonolus.script.num import Num
|
|
13
|
+
from sonolus.script.values import with_default
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@meta_fn
|
|
17
|
+
def error(message: str | None = None) -> None:
|
|
18
|
+
message = Comptime._accept_(message)._as_py_() if message is not None else "Error"
|
|
19
|
+
if not isinstance(message, str):
|
|
20
|
+
raise ValueError("Expected a string")
|
|
21
|
+
if ctx():
|
|
22
|
+
debug_log(ctx().map_constant(message))
|
|
23
|
+
debug_pause()
|
|
24
|
+
terminate()
|
|
25
|
+
else:
|
|
26
|
+
raise RuntimeError(message)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@native_function(Op.DebugLog)
|
|
30
|
+
def debug_log(value: Num):
|
|
31
|
+
print(f"[DEBUG] {value}")
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
@native_function(Op.DebugPause)
|
|
35
|
+
def debug_pause():
|
|
36
|
+
input("[DEBUG] Paused")
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def assert_true(value: Num, message: str | None = None):
|
|
40
|
+
message = with_default(message, "Assertion failed")
|
|
41
|
+
if not value:
|
|
42
|
+
error(message)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def assert_false(value: Num, message: str | None = None):
|
|
46
|
+
message = with_default(message, "Assertion failed")
|
|
47
|
+
if value:
|
|
48
|
+
error(message)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
@meta_fn
|
|
52
|
+
def assert_unreachable(message: str | None = None) -> Never:
|
|
53
|
+
message = validate_value(message)._as_py_() or "Unreachable code reached"
|
|
54
|
+
raise RuntimeError(message)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
@meta_fn
|
|
58
|
+
def terminate():
|
|
59
|
+
if ctx():
|
|
60
|
+
set_ctx(ctx().into_dead())
|
|
61
|
+
else:
|
|
62
|
+
raise RuntimeError("Terminated")
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def visualize_cfg(fn: Callable[[], Any]) -> str:
|
|
66
|
+
from sonolus.build.compile import callback_to_cfg
|
|
67
|
+
|
|
68
|
+
cfg = callback_to_cfg(GlobalContextState(Mode.Play), fn, "")
|
|
69
|
+
cfg = CoalesceFlow().run(cfg)
|
|
70
|
+
return cfg_to_mermaid(cfg)
|
sonolus/script/effect.py
ADDED
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from typing import Annotated, Any, Protocol, dataclass_transform, get_origin
|
|
5
|
+
|
|
6
|
+
from sonolus.backend.ops import Op
|
|
7
|
+
from sonolus.script.internal.introspection import get_field_specifiers
|
|
8
|
+
from sonolus.script.internal.native import native_function
|
|
9
|
+
from sonolus.script.record import Record
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class Effect(Record):
|
|
13
|
+
id: int
|
|
14
|
+
|
|
15
|
+
def is_available(self) -> bool:
|
|
16
|
+
return _has_effect_clip(self.id)
|
|
17
|
+
|
|
18
|
+
def play(self, distance: float) -> None:
|
|
19
|
+
_play(self.id, distance)
|
|
20
|
+
|
|
21
|
+
def schedule(self, time: float, distance: float) -> None:
|
|
22
|
+
_play_scheduled(self.id, time, distance)
|
|
23
|
+
|
|
24
|
+
def loop(self) -> LoopedEffectHandle:
|
|
25
|
+
return LoopedEffectHandle(_play_looped(self.id))
|
|
26
|
+
|
|
27
|
+
def schedule_loop(self, start_time: float) -> ScheduledLoopedEffectHandle:
|
|
28
|
+
return ScheduledLoopedEffectHandle(_play_looped_scheduled(self.id, start_time))
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class LoopedEffectHandle(Record):
|
|
32
|
+
id: int
|
|
33
|
+
|
|
34
|
+
def stop(self) -> None:
|
|
35
|
+
_stop_looped(self.id)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class ScheduledLoopedEffectHandle(Record):
|
|
39
|
+
id: int
|
|
40
|
+
|
|
41
|
+
def stop(self, end_time: float) -> None:
|
|
42
|
+
_stop_looped_scheduled(self.id, end_time)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
@native_function(Op.HasEffectClip)
|
|
46
|
+
def _has_effect_clip(effect_id: int) -> bool:
|
|
47
|
+
raise NotImplementedError
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
@native_function(Op.Play)
|
|
51
|
+
def _play(effect_id: int, distance: float) -> None:
|
|
52
|
+
raise NotImplementedError
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
@native_function(Op.PlayLooped)
|
|
56
|
+
def _play_looped(effect_id: int) -> int:
|
|
57
|
+
raise NotImplementedError
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
@native_function(Op.PlayLoopedScheduled)
|
|
61
|
+
def _play_looped_scheduled(effect_id: int, start_time: float) -> int:
|
|
62
|
+
raise NotImplementedError
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
@native_function(Op.PlayScheduled)
|
|
66
|
+
def _play_scheduled(effect_id: int, time: float, distance: float) -> None:
|
|
67
|
+
raise NotImplementedError
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
@native_function(Op.StopLooped)
|
|
71
|
+
def _stop_looped(handle: int) -> None:
|
|
72
|
+
raise NotImplementedError
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
@native_function(Op.StopLoopedScheduled)
|
|
76
|
+
def _stop_looped_scheduled(handle: int, end_time: float) -> None:
|
|
77
|
+
raise NotImplementedError
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
@dataclass
|
|
81
|
+
class EffectInfo:
|
|
82
|
+
name: str
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def effect(name: str) -> Any:
|
|
86
|
+
return EffectInfo(name)
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
class Effects(Protocol):
|
|
90
|
+
_effects_: list[str]
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
@dataclass_transform()
|
|
94
|
+
def effects[T](cls: type[T]) -> T | Effects:
|
|
95
|
+
if len(cls.__bases__) != 1:
|
|
96
|
+
raise ValueError("Effects class must not inherit from any class (except object)")
|
|
97
|
+
instance = cls()
|
|
98
|
+
names = []
|
|
99
|
+
for i, (name, annotation) in enumerate(get_field_specifiers(cls).items()):
|
|
100
|
+
if get_origin(annotation) is not Annotated:
|
|
101
|
+
raise TypeError(f"Invalid annotation for effects: {annotation}")
|
|
102
|
+
annotation_type = annotation.__args__[0]
|
|
103
|
+
annotation_values = annotation.__metadata__
|
|
104
|
+
if annotation_type is not Effect:
|
|
105
|
+
raise TypeError(f"Invalid annotation for effects: {annotation}, expected annotation of type Effect")
|
|
106
|
+
if len(annotation_values) != 1 or not isinstance(annotation_values[0], EffectInfo):
|
|
107
|
+
raise TypeError(f"Invalid annotation for effects: {annotation}, expected a single string annotation value")
|
|
108
|
+
effect_name = annotation_values[0].name
|
|
109
|
+
names.append(effect_name)
|
|
110
|
+
setattr(instance, name, Effect(i))
|
|
111
|
+
instance._effects_ = names
|
|
112
|
+
instance._is_comptime_value_ = True
|
|
113
|
+
return instance
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
class StandardEffect:
|
|
117
|
+
Miss = Annotated[Effect, effect("#MISS")]
|
|
118
|
+
Perfect = Annotated[Effect, effect("#PERFECT")]
|
|
119
|
+
Great = Annotated[Effect, effect("#GREAT")]
|
|
120
|
+
Good = Annotated[Effect, effect("#GOOD")]
|
|
121
|
+
Hold = Annotated[Effect, effect("#HOLD")]
|
|
122
|
+
MissAlternative = Annotated[Effect, effect("#MISS_ALTERNATIVE")]
|
|
123
|
+
PerfectAlternative = Annotated[Effect, effect("#PERFECT_ALTERNATIVE")]
|
|
124
|
+
GreatAlternative = Annotated[Effect, effect("#GREAT_ALTERNATIVE")]
|
|
125
|
+
GoodAlternative = Annotated[Effect, effect("#GOOD_ALTERNATIVE")]
|
|
126
|
+
HoldAlternative = Annotated[Effect, effect("#HOLD_ALTERNATIVE")]
|
|
127
|
+
Stage = Annotated[Effect, effect("#STAGE")]
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
@effects
|
|
131
|
+
class EmptyEffects:
|
|
132
|
+
pass
|
sonolus/script/engine.py
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from collections.abc import Callable
|
|
4
|
+
|
|
5
|
+
from sonolus.build.collection import Asset
|
|
6
|
+
from sonolus.script.archetype import BaseArchetype
|
|
7
|
+
from sonolus.script.bucket import Buckets, EmptyBuckets
|
|
8
|
+
from sonolus.script.effect import Effects, EmptyEffects
|
|
9
|
+
from sonolus.script.options import EmptyOptions, Options
|
|
10
|
+
from sonolus.script.particle import EmptyParticles, Particles
|
|
11
|
+
from sonolus.script.sprite import EmptySkin, Skin
|
|
12
|
+
from sonolus.script.ui import UiConfig
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class Engine:
|
|
16
|
+
version = 12
|
|
17
|
+
|
|
18
|
+
def __init__(
|
|
19
|
+
self,
|
|
20
|
+
*,
|
|
21
|
+
name: str,
|
|
22
|
+
title: str | None = None,
|
|
23
|
+
subtitle: str = "Sonolus.py Engine",
|
|
24
|
+
author: str = "Unknown",
|
|
25
|
+
skin: str | None = None,
|
|
26
|
+
background: str | None = None,
|
|
27
|
+
effect: str | None = None,
|
|
28
|
+
particle: str | None = None,
|
|
29
|
+
thumbnail: Asset | None = None,
|
|
30
|
+
data: EngineData,
|
|
31
|
+
) -> None:
|
|
32
|
+
self.name = name
|
|
33
|
+
self.title = title or name
|
|
34
|
+
self.subtitle = subtitle
|
|
35
|
+
self.author = author
|
|
36
|
+
self.skin = skin
|
|
37
|
+
self.background = background
|
|
38
|
+
self.effect = effect
|
|
39
|
+
self.particle = particle
|
|
40
|
+
self.thumbnail = thumbnail
|
|
41
|
+
self.data = data
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def default_callback() -> float:
|
|
45
|
+
return 0.0
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class PlayMode:
|
|
49
|
+
def __init__(
|
|
50
|
+
self,
|
|
51
|
+
*,
|
|
52
|
+
archetypes: list[type[BaseArchetype]] | None = None,
|
|
53
|
+
skin: Skin = EmptySkin,
|
|
54
|
+
effects: Effects = EmptyEffects,
|
|
55
|
+
particles: Particles = EmptyParticles,
|
|
56
|
+
buckets: Buckets = EmptyBuckets,
|
|
57
|
+
) -> None:
|
|
58
|
+
self.archetypes = archetypes or []
|
|
59
|
+
self.skin = skin
|
|
60
|
+
self.effects = effects
|
|
61
|
+
self.particles = particles
|
|
62
|
+
self.buckets = buckets
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
class WatchMode:
|
|
66
|
+
def __init__(
|
|
67
|
+
self,
|
|
68
|
+
*,
|
|
69
|
+
archetypes: list[type[BaseArchetype]] | None = None,
|
|
70
|
+
skin: Skin = EmptySkin,
|
|
71
|
+
effects: Effects = EmptyEffects,
|
|
72
|
+
particles: Particles = EmptyParticles,
|
|
73
|
+
buckets: Buckets = EmptyBuckets,
|
|
74
|
+
update_spawn: Callable[[], float],
|
|
75
|
+
) -> None:
|
|
76
|
+
self.archetypes = archetypes or []
|
|
77
|
+
self.skin = skin
|
|
78
|
+
self.effects = effects
|
|
79
|
+
self.particles = particles
|
|
80
|
+
self.buckets = buckets
|
|
81
|
+
self.update_spawn = update_spawn
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
class EngineData:
|
|
85
|
+
ui: UiConfig
|
|
86
|
+
options: Options
|
|
87
|
+
play: PlayMode
|
|
88
|
+
watch: WatchMode
|
|
89
|
+
|
|
90
|
+
def __init__(
|
|
91
|
+
self,
|
|
92
|
+
*,
|
|
93
|
+
ui: UiConfig | None = None,
|
|
94
|
+
options: Options = EmptyOptions,
|
|
95
|
+
play: PlayMode | None = None,
|
|
96
|
+
watch: WatchMode | None = None,
|
|
97
|
+
) -> None:
|
|
98
|
+
self.ui = ui or UiConfig()
|
|
99
|
+
self.options = options
|
|
100
|
+
self.play = play or PlayMode()
|
|
101
|
+
self.watch = watch or WatchMode(update_spawn=default_callback)
|