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/backend/mode.py
CHANGED
|
@@ -7,10 +7,10 @@ from sonolus.backend.blocks import Block, PlayBlock, PreviewBlock, TutorialBlock
|
|
|
7
7
|
class Mode(Enum):
|
|
8
8
|
blocks: type[Block]
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
10
|
+
PLAY = (PlayBlock,)
|
|
11
|
+
WATCH = (WatchBlock,)
|
|
12
|
+
PREVIEW = (PreviewBlock,)
|
|
13
|
+
TUTORIAL = (TutorialBlock,)
|
|
14
14
|
|
|
15
15
|
def __init__(self, blocks: type[Block]):
|
|
16
16
|
self.blocks = blocks
|
sonolus/backend/visitor.py
CHANGED
|
@@ -17,7 +17,7 @@ from sonolus.script.internal.error import CompilationError
|
|
|
17
17
|
from sonolus.script.internal.impl import try_validate_value, validate_value
|
|
18
18
|
from sonolus.script.internal.value import Value
|
|
19
19
|
from sonolus.script.iterator import SonolusIterator
|
|
20
|
-
from sonolus.script.num import Num
|
|
20
|
+
from sonolus.script.num import Num, is_num
|
|
21
21
|
|
|
22
22
|
_compiler_internal_ = True
|
|
23
23
|
|
|
@@ -622,6 +622,8 @@ class Visitor(ast.NodeVisitor):
|
|
|
622
622
|
|
|
623
623
|
def visit_Call(self, node):
|
|
624
624
|
fn = self.visit(node.func)
|
|
625
|
+
if fn is Num:
|
|
626
|
+
raise ValueError("Calling int/bool/float is not supported")
|
|
625
627
|
args = []
|
|
626
628
|
kwargs = {}
|
|
627
629
|
for arg in node.args:
|
|
@@ -833,7 +835,7 @@ class Visitor(ast.NodeVisitor):
|
|
|
833
835
|
|
|
834
836
|
def ensure_boolean_num(self, value) -> Num:
|
|
835
837
|
# This just checks the type for now, although we could support custom __bool__ implementations in the future
|
|
836
|
-
if not
|
|
838
|
+
if not is_num(value):
|
|
837
839
|
raise TypeError(f"Invalid type where a bool (Num) was expected: {type(value).__name__}")
|
|
838
840
|
return value
|
|
839
841
|
|
sonolus/build/compile.py
CHANGED
|
@@ -19,7 +19,7 @@ from sonolus.script.internal.context import (
|
|
|
19
19
|
ctx,
|
|
20
20
|
using_ctx,
|
|
21
21
|
)
|
|
22
|
-
from sonolus.script.num import
|
|
22
|
+
from sonolus.script.num import is_num
|
|
23
23
|
|
|
24
24
|
|
|
25
25
|
def compile_mode(
|
|
@@ -43,7 +43,7 @@ def compile_mode(
|
|
|
43
43
|
archetype_data["imports"] = [
|
|
44
44
|
{"name": name, "index": index} for name, index in archetype._imported_keys_.items()
|
|
45
45
|
]
|
|
46
|
-
if mode == Mode.
|
|
46
|
+
if mode == Mode.PLAY:
|
|
47
47
|
archetype_data["exports"] = [
|
|
48
48
|
{"name": name, "index": index} for name, index in archetype._exported_keys_.items()
|
|
49
49
|
]
|
|
@@ -85,6 +85,6 @@ def callback_to_cfg(
|
|
|
85
85
|
result = compile_and_call(callback, archetype._for_compilation())
|
|
86
86
|
else:
|
|
87
87
|
result = compile_and_call(callback)
|
|
88
|
-
if
|
|
88
|
+
if is_num(result):
|
|
89
89
|
ctx().add_statements(IRInstr(Op.Break, [IRConst(1), result.ir()]))
|
|
90
90
|
return context_to_cfg(context)
|
sonolus/build/engine.py
CHANGED
|
@@ -7,12 +7,15 @@ from pathlib import Path
|
|
|
7
7
|
|
|
8
8
|
from sonolus.backend.mode import Mode
|
|
9
9
|
from sonolus.build.compile import compile_mode
|
|
10
|
-
from sonolus.build.defaults import EMPTY_ENGINE_PREVIEW_DATA, EMPTY_ENGINE_TUTORIAL_DATA
|
|
11
10
|
from sonolus.script.archetype import BaseArchetype
|
|
12
11
|
from sonolus.script.bucket import Buckets
|
|
13
|
-
from sonolus.script.callbacks import update_spawn_callback
|
|
12
|
+
from sonolus.script.callbacks import navigate_callback, preprocess_callback, update_callback, update_spawn_callback
|
|
14
13
|
from sonolus.script.effect import Effects
|
|
15
14
|
from sonolus.script.engine import EngineData
|
|
15
|
+
from sonolus.script.instruction import (
|
|
16
|
+
TutorialInstructionIcons,
|
|
17
|
+
TutorialInstructions,
|
|
18
|
+
)
|
|
16
19
|
from sonolus.script.internal.context import ReadOnlyMemory
|
|
17
20
|
from sonolus.script.options import Options
|
|
18
21
|
from sonolus.script.particle import Particles
|
|
@@ -61,12 +64,28 @@ def package_engine(engine: EngineData):
|
|
|
61
64
|
rom=rom,
|
|
62
65
|
update_spawn=engine.watch.update_spawn,
|
|
63
66
|
)
|
|
67
|
+
preview_data = build_preview_mode(
|
|
68
|
+
archetypes=engine.preview.archetypes,
|
|
69
|
+
skin=engine.preview.skin,
|
|
70
|
+
rom=rom,
|
|
71
|
+
)
|
|
72
|
+
tutorial_data = build_tutorial_mode(
|
|
73
|
+
skin=engine.tutorial.skin,
|
|
74
|
+
effects=engine.tutorial.effects,
|
|
75
|
+
particles=engine.tutorial.particles,
|
|
76
|
+
instructions=engine.tutorial.instructions,
|
|
77
|
+
instruction_icons=engine.tutorial.instruction_icons,
|
|
78
|
+
preprocess=engine.tutorial.preprocess,
|
|
79
|
+
navigate=engine.tutorial.navigate,
|
|
80
|
+
update=engine.tutorial.update,
|
|
81
|
+
rom=rom,
|
|
82
|
+
)
|
|
64
83
|
return PackagedEngine(
|
|
65
84
|
configuration=package_output(configuration),
|
|
66
85
|
play_data=package_output(play_data),
|
|
67
86
|
watch_data=package_output(watch_data),
|
|
68
|
-
preview_data=package_output(
|
|
69
|
-
tutorial_data=package_output(
|
|
87
|
+
preview_data=package_output(preview_data),
|
|
88
|
+
tutorial_data=package_output(tutorial_data),
|
|
70
89
|
rom=package_rom(rom),
|
|
71
90
|
)
|
|
72
91
|
|
|
@@ -90,7 +109,7 @@ def build_play_mode(
|
|
|
90
109
|
rom: ReadOnlyMemory,
|
|
91
110
|
):
|
|
92
111
|
return {
|
|
93
|
-
**compile_mode(mode=Mode.
|
|
112
|
+
**compile_mode(mode=Mode.PLAY, rom=rom, archetypes=archetypes, global_callbacks=None),
|
|
94
113
|
"skin": build_skin(skin),
|
|
95
114
|
"effect": build_effects(effects),
|
|
96
115
|
"particle": build_particles(particles),
|
|
@@ -109,7 +128,7 @@ def build_watch_mode(
|
|
|
109
128
|
):
|
|
110
129
|
return {
|
|
111
130
|
**compile_mode(
|
|
112
|
-
mode=Mode.
|
|
131
|
+
mode=Mode.WATCH, rom=rom, archetypes=archetypes, global_callbacks=[(update_spawn_callback, update_spawn)]
|
|
113
132
|
),
|
|
114
133
|
"skin": build_skin(skin),
|
|
115
134
|
"effect": build_effects(effects),
|
|
@@ -118,6 +137,46 @@ def build_watch_mode(
|
|
|
118
137
|
}
|
|
119
138
|
|
|
120
139
|
|
|
140
|
+
def build_preview_mode(
|
|
141
|
+
archetypes: list[type[BaseArchetype]],
|
|
142
|
+
skin: Skin,
|
|
143
|
+
rom: ReadOnlyMemory,
|
|
144
|
+
):
|
|
145
|
+
return {
|
|
146
|
+
**compile_mode(mode=Mode.PREVIEW, rom=rom, archetypes=archetypes, global_callbacks=None),
|
|
147
|
+
"skin": build_skin(skin),
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def build_tutorial_mode(
|
|
152
|
+
skin: Skin,
|
|
153
|
+
effects: Effects,
|
|
154
|
+
particles: Particles,
|
|
155
|
+
instructions: TutorialInstructions,
|
|
156
|
+
instruction_icons: TutorialInstructionIcons,
|
|
157
|
+
preprocess: Callable[[], None],
|
|
158
|
+
navigate: Callable[[int], None],
|
|
159
|
+
update: Callable[[], None],
|
|
160
|
+
rom: ReadOnlyMemory,
|
|
161
|
+
):
|
|
162
|
+
return {
|
|
163
|
+
**compile_mode(
|
|
164
|
+
mode=Mode.TUTORIAL,
|
|
165
|
+
rom=rom,
|
|
166
|
+
archetypes=[],
|
|
167
|
+
global_callbacks=[
|
|
168
|
+
(preprocess_callback, preprocess),
|
|
169
|
+
(navigate_callback, navigate),
|
|
170
|
+
(update_callback, update),
|
|
171
|
+
],
|
|
172
|
+
),
|
|
173
|
+
"skin": build_skin(skin),
|
|
174
|
+
"effect": build_effects(effects),
|
|
175
|
+
"particle": build_particles(particles),
|
|
176
|
+
"instruction": build_instructions(instructions, instruction_icons),
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
|
|
121
180
|
def build_skin(skin: Skin) -> JsonValue:
|
|
122
181
|
return {"sprites": [{"name": name, "id": i} for i, name in enumerate(skin._sprites_)]}
|
|
123
182
|
|
|
@@ -134,6 +193,13 @@ def build_buckets(buckets: Buckets) -> JsonValue:
|
|
|
134
193
|
return [bucket.to_dict() for bucket in buckets._buckets_]
|
|
135
194
|
|
|
136
195
|
|
|
196
|
+
def build_instructions(instructions: TutorialInstructions, instruction_icons: TutorialInstructionIcons) -> JsonValue:
|
|
197
|
+
return {
|
|
198
|
+
"texts": [{"name": name, "id": i} for i, name in enumerate(instructions._instructions_)],
|
|
199
|
+
"icons": [{"name": name, "id": i} for i, name in enumerate(instruction_icons._instruction_icons_)],
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
|
|
137
203
|
def package_rom(rom: ReadOnlyMemory) -> bytes:
|
|
138
204
|
values = rom.values or [0]
|
|
139
205
|
output = bytearray()
|
sonolus/script/archetype.py
CHANGED
|
@@ -3,7 +3,7 @@ from __future__ import annotations
|
|
|
3
3
|
import inspect
|
|
4
4
|
from collections.abc import Callable
|
|
5
5
|
from dataclasses import dataclass
|
|
6
|
-
from enum import Enum
|
|
6
|
+
from enum import Enum, StrEnum
|
|
7
7
|
from types import FunctionType
|
|
8
8
|
from typing import Annotated, Any, ClassVar, Self, get_origin
|
|
9
9
|
|
|
@@ -11,7 +11,7 @@ from sonolus.backend.ir import IRConst, IRInstr
|
|
|
11
11
|
from sonolus.backend.mode import Mode
|
|
12
12
|
from sonolus.backend.ops import Op
|
|
13
13
|
from sonolus.script.bucket import Bucket, Judgment
|
|
14
|
-
from sonolus.script.callbacks import PLAY_CALLBACKS, WATCH_ARCHETYPE_CALLBACKS, CallbackInfo
|
|
14
|
+
from sonolus.script.callbacks import PLAY_CALLBACKS, PREVIEW_CALLBACKS, WATCH_ARCHETYPE_CALLBACKS, CallbackInfo
|
|
15
15
|
from sonolus.script.internal.context import ctx
|
|
16
16
|
from sonolus.script.internal.descriptor import SonolusDescriptor
|
|
17
17
|
from sonolus.script.internal.generic import validate_concrete_type
|
|
@@ -174,11 +174,11 @@ _annotation_defaults: dict[Callable, ArchetypeFieldInfo] = {
|
|
|
174
174
|
|
|
175
175
|
|
|
176
176
|
class StandardImport:
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
177
|
+
BEAT = Annotated[float, imported(name="#BEAT")]
|
|
178
|
+
BPM = Annotated[float, imported(name="#BPM")]
|
|
179
|
+
TIMESCALE = Annotated[float, imported(name="#TIMESCALE")]
|
|
180
|
+
JUDGMENT = Annotated[int, imported(name="#JUDGMENT")]
|
|
181
|
+
ACCURACY = Annotated[float, imported(name="#ACCURACY")]
|
|
182
182
|
|
|
183
183
|
|
|
184
184
|
def callback[T: Callable](order: int) -> Callable[[T], T]:
|
|
@@ -559,16 +559,42 @@ class WatchArchetype(BaseArchetype):
|
|
|
559
559
|
self.result.target_time = value
|
|
560
560
|
|
|
561
561
|
|
|
562
|
+
class PreviewArchetype(BaseArchetype):
|
|
563
|
+
_supported_callbacks_ = PREVIEW_CALLBACKS
|
|
564
|
+
|
|
565
|
+
def preprocess(self):
|
|
566
|
+
pass
|
|
567
|
+
|
|
568
|
+
def render(self):
|
|
569
|
+
pass
|
|
570
|
+
|
|
571
|
+
@property
|
|
572
|
+
def _info(self) -> PreviewEntityInfo:
|
|
573
|
+
if not ctx():
|
|
574
|
+
raise RuntimeError("Calling info is only allowed within a callback")
|
|
575
|
+
match self._data_:
|
|
576
|
+
case ArchetypeSelfData():
|
|
577
|
+
return deref(ctx().blocks.EntityInfo, 0, PreviewEntityInfo)
|
|
578
|
+
case ArchetypeReferenceData(index=index):
|
|
579
|
+
return deref(ctx().blocks.EntityInfoArray, index * PreviewEntityInfo._size_(), PreviewEntityInfo)
|
|
580
|
+
case _:
|
|
581
|
+
raise RuntimeError("Info is only accessible from the entity itself")
|
|
582
|
+
|
|
583
|
+
@property
|
|
584
|
+
def index(self) -> int:
|
|
585
|
+
return self._info.index
|
|
586
|
+
|
|
587
|
+
|
|
562
588
|
@meta_fn
|
|
563
589
|
def entity_info_at(index: Num) -> PlayEntityInfo | WatchEntityInfo | PreviewEntityInfo:
|
|
564
590
|
if not ctx():
|
|
565
591
|
raise RuntimeError("Calling entity_info_at is only allowed within a callback")
|
|
566
592
|
match ctx().global_state.mode:
|
|
567
|
-
case Mode.
|
|
593
|
+
case Mode.PLAY:
|
|
568
594
|
return deref(ctx().blocks.EntityInfoArray, index * PlayEntityInfo._size_(), PlayEntityInfo)
|
|
569
|
-
case Mode.
|
|
595
|
+
case Mode.WATCH:
|
|
570
596
|
return deref(ctx().blocks.EntityInfoArray, index * WatchEntityInfo._size_(), WatchEntityInfo)
|
|
571
|
-
case Mode.
|
|
597
|
+
case Mode.PREVIEW:
|
|
572
598
|
return deref(ctx().blocks.EntityInfoArray, index * PreviewEntityInfo._size_(), PreviewEntityInfo)
|
|
573
599
|
case _:
|
|
574
600
|
raise RuntimeError(f"Entity info is not available in mode '{ctx().global_state.mode}'")
|
|
@@ -581,7 +607,7 @@ def archetype_life_of(archetype: type[BaseArchetype] | BaseArchetype) -> Archety
|
|
|
581
607
|
if not ctx():
|
|
582
608
|
raise RuntimeError("Calling archetype_life_of is only allowed within a callback")
|
|
583
609
|
match ctx().global_state.mode:
|
|
584
|
-
case Mode.
|
|
610
|
+
case Mode.PLAY | Mode.WATCH:
|
|
585
611
|
return deref(ctx().blocks.ArchetypeLife, archetype.id() * ArchetypeLife._size_(), ArchetypeLife)
|
|
586
612
|
case _:
|
|
587
613
|
raise RuntimeError(f"Archetype life is not available in mode '{ctx().global_state.mode}'")
|
|
@@ -649,3 +675,8 @@ class EntityRef[A: BaseArchetype](Record):
|
|
|
649
675
|
|
|
650
676
|
def get(self) -> A:
|
|
651
677
|
return self.archetype().at(Num(self.index))
|
|
678
|
+
|
|
679
|
+
|
|
680
|
+
class StandardArchetypeName(StrEnum):
|
|
681
|
+
BPM_CHANGE = "#BPM_CHANGE"
|
|
682
|
+
TIMESCALE_CHANGE = "#TIMESCALE_CHANGE"
|
sonolus/script/bucket.py
CHANGED
|
@@ -88,9 +88,9 @@ class Bucket(Record):
|
|
|
88
88
|
if not ctx():
|
|
89
89
|
raise RuntimeError("Bucket window access outside of compilation")
|
|
90
90
|
match ctx().global_state.mode:
|
|
91
|
-
case Mode.
|
|
91
|
+
case Mode.PLAY:
|
|
92
92
|
return deref(ctx().blocks.LevelBucket, self.id * JudgmentWindow._size_(), JudgmentWindow)
|
|
93
|
-
case Mode.
|
|
93
|
+
case Mode.WATCH:
|
|
94
94
|
return deref(ctx().blocks.LevelBucket, self.id * JudgmentWindow._size_(), JudgmentWindow)
|
|
95
95
|
case _:
|
|
96
96
|
raise RuntimeError("Invalid mode for bucket window access")
|
sonolus/script/callbacks.py
CHANGED
|
@@ -75,6 +75,24 @@ update_spawn_callback = CallbackInfo(
|
|
|
75
75
|
supports_order=False,
|
|
76
76
|
returns_value=True,
|
|
77
77
|
)
|
|
78
|
+
render_callback = CallbackInfo(
|
|
79
|
+
name="render",
|
|
80
|
+
py_name="render",
|
|
81
|
+
supports_order=False,
|
|
82
|
+
returns_value=False,
|
|
83
|
+
)
|
|
84
|
+
navigate_callback = CallbackInfo(
|
|
85
|
+
name="navigate",
|
|
86
|
+
py_name="navigate",
|
|
87
|
+
supports_order=False,
|
|
88
|
+
returns_value=False,
|
|
89
|
+
)
|
|
90
|
+
update_callback = CallbackInfo(
|
|
91
|
+
name="update",
|
|
92
|
+
py_name="update",
|
|
93
|
+
supports_order=False,
|
|
94
|
+
returns_value=False,
|
|
95
|
+
)
|
|
78
96
|
|
|
79
97
|
|
|
80
98
|
def _by_name(*callbacks: CallbackInfo) -> dict[str, CallbackInfo]:
|
|
@@ -103,3 +121,7 @@ WATCH_ARCHETYPE_CALLBACKS = _by_name(
|
|
|
103
121
|
WATCH_GLOBAL_CALLBACKS = _by_name(
|
|
104
122
|
update_spawn_callback,
|
|
105
123
|
)
|
|
124
|
+
PREVIEW_CALLBACKS = _by_name(
|
|
125
|
+
preprocess_callback,
|
|
126
|
+
render_callback,
|
|
127
|
+
)
|
sonolus/script/comptime.py
CHANGED
|
@@ -3,6 +3,7 @@ from typing import TYPE_CHECKING, Any, Self, TypeVar, final
|
|
|
3
3
|
|
|
4
4
|
from sonolus.backend.place import BlockPlace
|
|
5
5
|
from sonolus.script.internal.generic import GenericValue
|
|
6
|
+
from sonolus.script.internal.impl import meta_fn, validate_value
|
|
6
7
|
|
|
7
8
|
|
|
8
9
|
@final
|
|
@@ -92,6 +93,19 @@ class _Comptime[T, V](GenericValue):
|
|
|
92
93
|
def _copy_(self) -> Self:
|
|
93
94
|
return self
|
|
94
95
|
|
|
96
|
+
@meta_fn
|
|
97
|
+
def __eq__(self, other):
|
|
98
|
+
other = validate_value(other)
|
|
99
|
+
match self.value():
|
|
100
|
+
case str():
|
|
101
|
+
return other._is_py_() and other._as_py_() == self.value()
|
|
102
|
+
case _:
|
|
103
|
+
raise TypeError("Unsupported comparison with comptime value")
|
|
104
|
+
|
|
105
|
+
@meta_fn
|
|
106
|
+
def __hash__(self):
|
|
107
|
+
return hash(self.value())
|
|
108
|
+
|
|
95
109
|
@classmethod
|
|
96
110
|
def _alloc_(cls) -> Self:
|
|
97
111
|
return cls._instance
|
sonolus/script/debug.py
CHANGED
|
@@ -65,6 +65,6 @@ def terminate():
|
|
|
65
65
|
def visualize_cfg(fn: Callable[[], Any]) -> str:
|
|
66
66
|
from sonolus.build.compile import callback_to_cfg
|
|
67
67
|
|
|
68
|
-
cfg = callback_to_cfg(GlobalContextState(Mode.
|
|
68
|
+
cfg = callback_to_cfg(GlobalContextState(Mode.PLAY), fn, "")
|
|
69
69
|
cfg = CoalesceFlow().run(cfg)
|
|
70
70
|
return cfg_to_mermaid(cfg)
|
sonolus/script/effect.py
CHANGED
|
@@ -113,17 +113,17 @@ def effects[T](cls: type[T]) -> T | Effects:
|
|
|
113
113
|
|
|
114
114
|
|
|
115
115
|
class StandardEffect:
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
116
|
+
MISS = Annotated[Effect, effect("#MISS")]
|
|
117
|
+
PERFECT = Annotated[Effect, effect("#PERFECT")]
|
|
118
|
+
GREAT = Annotated[Effect, effect("#GREAT")]
|
|
119
|
+
GOOD = Annotated[Effect, effect("#GOOD")]
|
|
120
|
+
HOLD = Annotated[Effect, effect("#HOLD")]
|
|
121
|
+
MISS_ALTERNATIVE = Annotated[Effect, effect("#MISS_ALTERNATIVE")]
|
|
122
|
+
PERFECT_ALTERNATIVE = Annotated[Effect, effect("#PERFECT_ALTERNATIVE")]
|
|
123
|
+
GREAT_ALTERNATIVE = Annotated[Effect, effect("#GREAT_ALTERNATIVE")]
|
|
124
|
+
GOOD_ALTERNATIVE = Annotated[Effect, effect("#GOOD_ALTERNATIVE")]
|
|
125
|
+
HOLD_ALTERNATIVE = Annotated[Effect, effect("#HOLD_ALTERNATIVE")]
|
|
126
|
+
STAGE = Annotated[Effect, effect("#STAGE")]
|
|
127
127
|
|
|
128
128
|
|
|
129
129
|
@effects
|
sonolus/script/engine.py
CHANGED
|
@@ -1,11 +1,18 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
from collections.abc import Callable
|
|
4
|
+
from typing import Any
|
|
4
5
|
|
|
5
6
|
from sonolus.build.collection import Asset
|
|
6
|
-
from sonolus.script.archetype import BaseArchetype
|
|
7
|
+
from sonolus.script.archetype import BaseArchetype, PlayArchetype, PreviewArchetype, WatchArchetype
|
|
7
8
|
from sonolus.script.bucket import Buckets, EmptyBuckets
|
|
8
9
|
from sonolus.script.effect import Effects, EmptyEffects
|
|
10
|
+
from sonolus.script.instruction import (
|
|
11
|
+
EmptyInstructionIcons,
|
|
12
|
+
EmptyInstructions,
|
|
13
|
+
TutorialInstructionIcons,
|
|
14
|
+
TutorialInstructions,
|
|
15
|
+
)
|
|
9
16
|
from sonolus.script.options import EmptyOptions, Options
|
|
10
17
|
from sonolus.script.particle import EmptyParticles, Particles
|
|
11
18
|
from sonolus.script.sprite import EmptySkin, Skin
|
|
@@ -41,7 +48,7 @@ class Engine:
|
|
|
41
48
|
self.data = data
|
|
42
49
|
|
|
43
50
|
|
|
44
|
-
def default_callback() ->
|
|
51
|
+
def default_callback() -> Any:
|
|
45
52
|
return 0.0
|
|
46
53
|
|
|
47
54
|
|
|
@@ -61,6 +68,10 @@ class PlayMode:
|
|
|
61
68
|
self.particles = particles
|
|
62
69
|
self.buckets = buckets
|
|
63
70
|
|
|
71
|
+
for archetype in self.archetypes:
|
|
72
|
+
if not issubclass(archetype, PlayArchetype):
|
|
73
|
+
raise ValueError(f"archetype {archetype} is not a PlayArchetype")
|
|
74
|
+
|
|
64
75
|
|
|
65
76
|
class WatchMode:
|
|
66
77
|
def __init__(
|
|
@@ -80,13 +91,50 @@ class WatchMode:
|
|
|
80
91
|
self.buckets = buckets
|
|
81
92
|
self.update_spawn = update_spawn
|
|
82
93
|
|
|
94
|
+
for archetype in self.archetypes:
|
|
95
|
+
if not issubclass(archetype, WatchArchetype):
|
|
96
|
+
raise ValueError(f"archetype {archetype} is not a PlayArchetype")
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
class PreviewMode:
|
|
100
|
+
def __init__(
|
|
101
|
+
self,
|
|
102
|
+
*,
|
|
103
|
+
archetypes: list[type[BaseArchetype]] | None = None,
|
|
104
|
+
skin: Skin = EmptySkin,
|
|
105
|
+
) -> None:
|
|
106
|
+
self.archetypes = archetypes or []
|
|
107
|
+
self.skin = skin
|
|
108
|
+
|
|
109
|
+
for archetype in self.archetypes:
|
|
110
|
+
if not issubclass(archetype, PreviewArchetype):
|
|
111
|
+
raise ValueError(f"archetype {archetype} is not a BaseArchetype")
|
|
83
112
|
|
|
84
|
-
class EngineData:
|
|
85
|
-
ui: UiConfig
|
|
86
|
-
options: Options
|
|
87
|
-
play: PlayMode
|
|
88
|
-
watch: WatchMode
|
|
89
113
|
|
|
114
|
+
class TutorialMode:
|
|
115
|
+
def __init__(
|
|
116
|
+
self,
|
|
117
|
+
*,
|
|
118
|
+
skin: Skin = EmptySkin,
|
|
119
|
+
effects: Effects = EmptyEffects,
|
|
120
|
+
particles: Particles = EmptyParticles,
|
|
121
|
+
instructions: TutorialInstructions = EmptyInstructions,
|
|
122
|
+
instruction_icons: TutorialInstructionIcons = EmptyInstructionIcons,
|
|
123
|
+
preprocess: Callable[[], None],
|
|
124
|
+
navigate: Callable[[], None],
|
|
125
|
+
update: Callable[[], None],
|
|
126
|
+
) -> None:
|
|
127
|
+
self.skin = skin
|
|
128
|
+
self.effects = effects
|
|
129
|
+
self.particles = particles
|
|
130
|
+
self.instructions = instructions
|
|
131
|
+
self.instruction_icons = instruction_icons
|
|
132
|
+
self.preprocess = preprocess
|
|
133
|
+
self.navigate = navigate
|
|
134
|
+
self.update = update
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
class EngineData:
|
|
90
138
|
def __init__(
|
|
91
139
|
self,
|
|
92
140
|
*,
|
|
@@ -94,8 +142,16 @@ class EngineData:
|
|
|
94
142
|
options: Options = EmptyOptions,
|
|
95
143
|
play: PlayMode | None = None,
|
|
96
144
|
watch: WatchMode | None = None,
|
|
145
|
+
preview: PreviewMode | None = None,
|
|
146
|
+
tutorial: TutorialMode | None = None,
|
|
97
147
|
) -> None:
|
|
98
148
|
self.ui = ui or UiConfig()
|
|
99
149
|
self.options = options
|
|
100
150
|
self.play = play or PlayMode()
|
|
101
151
|
self.watch = watch or WatchMode(update_spawn=default_callback)
|
|
152
|
+
self.preview = preview or PreviewMode()
|
|
153
|
+
self.tutorial = tutorial or TutorialMode(
|
|
154
|
+
preprocess=default_callback,
|
|
155
|
+
navigate=default_callback,
|
|
156
|
+
update=default_callback,
|
|
157
|
+
)
|