mini-arcade-core 0.10.0__py3-none-any.whl → 1.0.1__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.
- mini_arcade_core/__init__.py +51 -63
- mini_arcade_core/backend/__init__.py +2 -6
- mini_arcade_core/backend/backend.py +148 -8
- mini_arcade_core/backend/events.py +1 -1
- mini_arcade_core/{keymaps/sdl.py → backend/sdl_map.py} +1 -1
- mini_arcade_core/engine/__init__.py +0 -0
- mini_arcade_core/engine/commands.py +169 -0
- mini_arcade_core/engine/game.py +369 -0
- mini_arcade_core/engine/render/__init__.py +0 -0
- mini_arcade_core/engine/render/packet.py +56 -0
- mini_arcade_core/engine/render/pipeline.py +63 -0
- mini_arcade_core/engine/render/viewport.py +203 -0
- mini_arcade_core/managers/__init__.py +0 -22
- mini_arcade_core/managers/cheats.py +71 -240
- mini_arcade_core/managers/inputs.py +5 -3
- mini_arcade_core/runtime/__init__.py +0 -0
- mini_arcade_core/runtime/audio/__init__.py +0 -0
- mini_arcade_core/runtime/audio/audio_adapter.py +20 -0
- mini_arcade_core/runtime/audio/audio_port.py +36 -0
- mini_arcade_core/runtime/capture/__init__.py +0 -0
- mini_arcade_core/runtime/capture/capture_adapter.py +143 -0
- mini_arcade_core/runtime/capture/capture_port.py +51 -0
- mini_arcade_core/runtime/context.py +53 -0
- mini_arcade_core/runtime/file/__init__.py +0 -0
- mini_arcade_core/runtime/file/file_adapter.py +20 -0
- mini_arcade_core/runtime/file/file_port.py +31 -0
- mini_arcade_core/runtime/input/__init__.py +0 -0
- mini_arcade_core/runtime/input/input_adapter.py +49 -0
- mini_arcade_core/runtime/input/input_port.py +31 -0
- mini_arcade_core/runtime/input_frame.py +71 -0
- mini_arcade_core/runtime/scene/__init__.py +0 -0
- mini_arcade_core/runtime/scene/scene_adapter.py +97 -0
- mini_arcade_core/runtime/scene/scene_port.py +149 -0
- mini_arcade_core/runtime/services.py +35 -0
- mini_arcade_core/runtime/window/__init__.py +0 -0
- mini_arcade_core/runtime/window/window_adapter.py +90 -0
- mini_arcade_core/runtime/window/window_port.py +109 -0
- mini_arcade_core/scenes/__init__.py +0 -22
- mini_arcade_core/scenes/autoreg.py +1 -1
- mini_arcade_core/scenes/registry.py +21 -19
- mini_arcade_core/scenes/sim_scene.py +41 -0
- mini_arcade_core/scenes/systems/__init__.py +0 -0
- mini_arcade_core/scenes/systems/base_system.py +40 -0
- mini_arcade_core/scenes/systems/system_pipeline.py +57 -0
- mini_arcade_core/sim/__init__.py +0 -0
- mini_arcade_core/sim/protocols.py +41 -0
- mini_arcade_core/sim/runner.py +222 -0
- mini_arcade_core/spaces/__init__.py +0 -12
- mini_arcade_core/spaces/d2/__init__.py +0 -30
- mini_arcade_core/spaces/d2/boundaries2d.py +10 -1
- mini_arcade_core/spaces/d2/collision2d.py +25 -28
- mini_arcade_core/spaces/d2/geometry2d.py +18 -0
- mini_arcade_core/spaces/d2/kinematics2d.py +2 -8
- mini_arcade_core/spaces/d2/physics2d.py +9 -0
- mini_arcade_core/ui/__init__.py +0 -26
- mini_arcade_core/ui/menu.py +271 -85
- mini_arcade_core/utils/__init__.py +10 -0
- mini_arcade_core/utils/deprecated_decorator.py +45 -0
- mini_arcade_core/utils/logging.py +168 -0
- {mini_arcade_core-0.10.0.dist-info → mini_arcade_core-1.0.1.dist-info}/METADATA +1 -1
- mini_arcade_core-1.0.1.dist-info/RECORD +66 -0
- {mini_arcade_core-0.10.0.dist-info → mini_arcade_core-1.0.1.dist-info}/WHEEL +1 -1
- mini_arcade_core/commands.py +0 -84
- mini_arcade_core/entity.py +0 -72
- mini_arcade_core/game.py +0 -287
- mini_arcade_core/keymaps/__init__.py +0 -15
- mini_arcade_core/managers/base.py +0 -132
- mini_arcade_core/managers/entities.py +0 -38
- mini_arcade_core/managers/overlays.py +0 -53
- mini_arcade_core/managers/system.py +0 -26
- mini_arcade_core/scenes/model.py +0 -34
- mini_arcade_core/scenes/runtime.py +0 -29
- mini_arcade_core/scenes/scene.py +0 -109
- mini_arcade_core/scenes/system.py +0 -69
- mini_arcade_core/ui/overlays.py +0 -41
- mini_arcade_core-0.10.0.dist-info/RECORD +0 -40
- /mini_arcade_core/{keymaps → backend}/keys.py +0 -0
- {mini_arcade_core-0.10.0.dist-info → mini_arcade_core-1.0.1.dist-info}/licenses/LICENSE +0 -0
mini_arcade_core/__init__.py
CHANGED
|
@@ -5,31 +5,21 @@ Provides access to core classes and a convenience function to run a game.
|
|
|
5
5
|
|
|
6
6
|
from __future__ import annotations
|
|
7
7
|
|
|
8
|
-
import
|
|
8
|
+
import traceback
|
|
9
9
|
from importlib.metadata import PackageNotFoundError, version
|
|
10
10
|
from typing import Callable, Type, Union
|
|
11
11
|
|
|
12
|
-
from mini_arcade_core import
|
|
13
|
-
from mini_arcade_core import
|
|
14
|
-
from mini_arcade_core import
|
|
15
|
-
from mini_arcade_core import
|
|
16
|
-
from mini_arcade_core import ui # noqa: F401
|
|
17
|
-
from mini_arcade_core.bus import event_bus
|
|
18
|
-
from mini_arcade_core.commands import (
|
|
19
|
-
BaseCommand,
|
|
20
|
-
BaseGameCommand,
|
|
21
|
-
BaseSceneCommand,
|
|
22
|
-
QuitGameCommand,
|
|
23
|
-
)
|
|
24
|
-
from mini_arcade_core.entity import Entity, KinematicEntity, SpriteEntity
|
|
25
|
-
from mini_arcade_core.game import Game, GameConfig
|
|
26
|
-
from mini_arcade_core.scenes import Scene, SceneRegistry
|
|
12
|
+
from mini_arcade_core.engine.game import Game, GameConfig, WindowConfig
|
|
13
|
+
from mini_arcade_core.scenes.registry import SceneRegistry
|
|
14
|
+
from mini_arcade_core.scenes.sim_scene import SimScene
|
|
15
|
+
from mini_arcade_core.utils import logger
|
|
27
16
|
|
|
28
|
-
SceneFactoryLike = Union[Type[
|
|
29
|
-
|
|
30
|
-
logger = logging.getLogger(__name__)
|
|
17
|
+
SceneFactoryLike = Union[Type[SimScene], Callable[[Game], SimScene]]
|
|
31
18
|
|
|
32
19
|
|
|
20
|
+
# TODO: Improve exception handling and logging in run_game
|
|
21
|
+
# TODO: Consider reducing parameters by using a single config object
|
|
22
|
+
# TODO: Delegate responsibilities to Game class where appropriate
|
|
33
23
|
def run_game(
|
|
34
24
|
scene: SceneFactoryLike | None = None,
|
|
35
25
|
config: GameConfig | None = None,
|
|
@@ -40,11 +30,14 @@ def run_game(
|
|
|
40
30
|
Convenience helper to bootstrap and run a game with a single scene.
|
|
41
31
|
|
|
42
32
|
Supports both:
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
33
|
+
- run_game(SceneClass, cfg) # legacy
|
|
34
|
+
- run_game(config=cfg, initial_scene="main", registry=...) # registry-based
|
|
35
|
+
- run_game(cfg) # config-only
|
|
36
|
+
|
|
37
|
+
:param scene: Optional SimScene factory/class to register
|
|
38
|
+
:type scene: SceneFactoryLike | None
|
|
46
39
|
|
|
47
|
-
:param initial_scene: The
|
|
40
|
+
:param initial_scene: The SimScene ID to start the game with.
|
|
48
41
|
:type initial_scene: str
|
|
49
42
|
|
|
50
43
|
:param config: Optional GameConfig to customize game settings.
|
|
@@ -55,47 +48,35 @@ def run_game(
|
|
|
55
48
|
|
|
56
49
|
:raises ValueError: If the provided config does not have a valid Backend.
|
|
57
50
|
"""
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
config
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
if
|
|
72
|
-
registry
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
51
|
+
try:
|
|
52
|
+
# Handle run_game(cfg) where the first arg is actually a GameConfig
|
|
53
|
+
if isinstance(scene, GameConfig) and config is None:
|
|
54
|
+
config = scene
|
|
55
|
+
scene = None
|
|
56
|
+
|
|
57
|
+
cfg = config or GameConfig()
|
|
58
|
+
if cfg.backend is None:
|
|
59
|
+
raise ValueError(
|
|
60
|
+
"GameConfig.backend must be set to a Backend instance"
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
# If user provided a SimScene factory/class, ensure it's registered
|
|
64
|
+
if scene is not None:
|
|
65
|
+
if registry is None:
|
|
66
|
+
registry = SceneRegistry(_factories={})
|
|
67
|
+
registry.register(
|
|
68
|
+
initial_scene, scene
|
|
69
|
+
) # SimScene class is callable(game) -> SimScene
|
|
70
|
+
|
|
71
|
+
game = Game(cfg, registry=registry)
|
|
72
|
+
game.run(initial_scene)
|
|
73
|
+
# Justification: We need to catch all exceptions while we improve error handling.
|
|
74
|
+
# pylint: disable=broad-exception-caught
|
|
75
|
+
except Exception as e:
|
|
76
|
+
logger.exception(f"Unhandled exception in game loop: {e}")
|
|
77
|
+
logger.debug(traceback.format_exc())
|
|
78
|
+
# pylint: enable=broad-exception-caught
|
|
80
79
|
|
|
81
|
-
__all__ = [
|
|
82
|
-
"Game",
|
|
83
|
-
"GameConfig",
|
|
84
|
-
"Entity",
|
|
85
|
-
"SpriteEntity",
|
|
86
|
-
"KinematicEntity",
|
|
87
|
-
"BaseCommand",
|
|
88
|
-
"BaseGameCommand",
|
|
89
|
-
"BaseSceneCommand",
|
|
90
|
-
"QuitGameCommand",
|
|
91
|
-
"event_bus",
|
|
92
|
-
"run_game",
|
|
93
|
-
"backend",
|
|
94
|
-
"keymaps",
|
|
95
|
-
"managers",
|
|
96
|
-
"spaces",
|
|
97
|
-
"ui",
|
|
98
|
-
]
|
|
99
80
|
|
|
100
81
|
PACKAGE_NAME = "mini-arcade-core" # or whatever is in your pyproject.toml
|
|
101
82
|
|
|
@@ -123,4 +104,11 @@ def get_version() -> str:
|
|
|
123
104
|
return "0.0.0"
|
|
124
105
|
|
|
125
106
|
|
|
107
|
+
__all__ = [
|
|
108
|
+
"Game",
|
|
109
|
+
"GameConfig",
|
|
110
|
+
"WindowConfig",
|
|
111
|
+
"run_game",
|
|
112
|
+
]
|
|
113
|
+
|
|
126
114
|
__version__ = get_version()
|
|
@@ -6,13 +6,9 @@ This is the only part of the code that talks to SDL/pygame directly.
|
|
|
6
6
|
|
|
7
7
|
from __future__ import annotations
|
|
8
8
|
|
|
9
|
-
from .backend import Backend
|
|
10
|
-
from .events import Event, EventType
|
|
11
|
-
from .types import Color
|
|
9
|
+
from .backend import Backend, WindowSettings
|
|
12
10
|
|
|
13
11
|
__all__ = [
|
|
14
12
|
"Backend",
|
|
15
|
-
"
|
|
16
|
-
"EventType",
|
|
17
|
-
"Color",
|
|
13
|
+
"WindowSettings",
|
|
18
14
|
]
|
|
@@ -5,12 +5,29 @@ This is the only part of the code that talks to SDL/pygame directly.
|
|
|
5
5
|
|
|
6
6
|
from __future__ import annotations
|
|
7
7
|
|
|
8
|
+
from dataclasses import dataclass
|
|
8
9
|
from typing import Iterable, Protocol
|
|
9
10
|
|
|
10
11
|
from .events import Event
|
|
11
12
|
from .types import Color
|
|
12
13
|
|
|
13
14
|
|
|
15
|
+
@dataclass
|
|
16
|
+
class WindowSettings:
|
|
17
|
+
"""
|
|
18
|
+
Settings for the backend window.
|
|
19
|
+
|
|
20
|
+
:ivar width (int): Width of the window in pixels.
|
|
21
|
+
:ivar height (int): Height of the window in pixels.
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
width: int
|
|
25
|
+
height: int
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
# TODO: Refactor backend interface into smaller protocols?
|
|
29
|
+
# Justification: Many public methods needed for backend interface
|
|
30
|
+
# pylint: disable=too-many-public-methods
|
|
14
31
|
class Backend(Protocol):
|
|
15
32
|
"""
|
|
16
33
|
Interface that any rendering/input backend must implement.
|
|
@@ -18,20 +35,23 @@ class Backend(Protocol):
|
|
|
18
35
|
mini-arcade-core only talks to this protocol, never to SDL/pygame directly.
|
|
19
36
|
"""
|
|
20
37
|
|
|
21
|
-
def init(self,
|
|
38
|
+
def init(self, window_settings: WindowSettings):
|
|
22
39
|
"""
|
|
23
40
|
Initialize the backend and open a window.
|
|
24
41
|
Should be called once before the main loop.
|
|
25
42
|
|
|
26
|
-
:param
|
|
27
|
-
:type
|
|
43
|
+
:param window_settings: Settings for the backend window.
|
|
44
|
+
:type window_settings: WindowSettings
|
|
45
|
+
"""
|
|
28
46
|
|
|
29
|
-
|
|
30
|
-
|
|
47
|
+
def set_window_title(self, title: str):
|
|
48
|
+
"""
|
|
49
|
+
Set the window title.
|
|
31
50
|
|
|
32
|
-
:param title:
|
|
51
|
+
:param title: The new title for the window.
|
|
33
52
|
:type title: str
|
|
34
53
|
"""
|
|
54
|
+
raise NotImplementedError
|
|
35
55
|
|
|
36
56
|
def poll_events(self) -> Iterable[Event]:
|
|
37
57
|
"""
|
|
@@ -96,14 +116,13 @@ class Backend(Protocol):
|
|
|
96
116
|
:type color: Color
|
|
97
117
|
"""
|
|
98
118
|
|
|
99
|
-
# pylint: enable=too-many-arguments,too-many-positional-arguments
|
|
100
|
-
|
|
101
119
|
def draw_text(
|
|
102
120
|
self,
|
|
103
121
|
x: int,
|
|
104
122
|
y: int,
|
|
105
123
|
text: str,
|
|
106
124
|
color: Color = (255, 255, 255),
|
|
125
|
+
font_size: int | None = None,
|
|
107
126
|
):
|
|
108
127
|
"""
|
|
109
128
|
Draw text at the given position in a default font and color.
|
|
@@ -124,6 +143,8 @@ class Backend(Protocol):
|
|
|
124
143
|
:type color: Color
|
|
125
144
|
"""
|
|
126
145
|
|
|
146
|
+
# pylint: enable=too-many-arguments,too-many-positional-arguments
|
|
147
|
+
|
|
127
148
|
def measure_text(self, text: str) -> tuple[int, int]:
|
|
128
149
|
"""
|
|
129
150
|
Measure the width and height of the given text string in pixels.
|
|
@@ -149,3 +170,122 @@ class Backend(Protocol):
|
|
|
149
170
|
:rtype: bytes | None
|
|
150
171
|
"""
|
|
151
172
|
raise NotImplementedError
|
|
173
|
+
|
|
174
|
+
def init_audio(
|
|
175
|
+
self, frequency: int = 44100, channels: int = 2, chunk_size: int = 2048
|
|
176
|
+
):
|
|
177
|
+
"""
|
|
178
|
+
Initialize SDL_mixer audio.
|
|
179
|
+
|
|
180
|
+
:param frequency: Audio frequency in Hz.
|
|
181
|
+
:type frequency: int
|
|
182
|
+
|
|
183
|
+
:param channels: Number of audio channels (1=mono, 2=stereo).
|
|
184
|
+
:type channels: int
|
|
185
|
+
|
|
186
|
+
:param chunk_size: Size of audio chunks.
|
|
187
|
+
:type chunk_size: int
|
|
188
|
+
"""
|
|
189
|
+
|
|
190
|
+
def shutdown_audio(self):
|
|
191
|
+
"""Shutdown SDL_mixer audio and free loaded sounds."""
|
|
192
|
+
|
|
193
|
+
def load_sound(self, sound_id: str, path: str):
|
|
194
|
+
"""
|
|
195
|
+
Load a WAV sound and store it by ID.
|
|
196
|
+
Example: backend.load_sound("hit", "assets/sfx/hit.wav")
|
|
197
|
+
|
|
198
|
+
:param sound_id: Unique identifier for the sound.
|
|
199
|
+
:type sound_id: str
|
|
200
|
+
|
|
201
|
+
:param path: File path to the WAV sound.
|
|
202
|
+
:type path: str
|
|
203
|
+
"""
|
|
204
|
+
|
|
205
|
+
def play_sound(self, sound_id: str, loops: int = 0):
|
|
206
|
+
"""
|
|
207
|
+
Play a loaded sound.
|
|
208
|
+
loops=0 => play once
|
|
209
|
+
loops=-1 => infinite loop
|
|
210
|
+
loops=1 => play twice (SDL convention)
|
|
211
|
+
|
|
212
|
+
:param sound_id: Unique identifier for the sound.
|
|
213
|
+
:type sound_id: str
|
|
214
|
+
|
|
215
|
+
:param loops: Number of times to loop the sound.
|
|
216
|
+
:type loops: int
|
|
217
|
+
"""
|
|
218
|
+
|
|
219
|
+
def set_master_volume(self, volume: int):
|
|
220
|
+
"""
|
|
221
|
+
Master volume: 0..128
|
|
222
|
+
"""
|
|
223
|
+
|
|
224
|
+
def set_sound_volume(self, sound_id: str, volume: int):
|
|
225
|
+
"""
|
|
226
|
+
Per-sound volume: 0..128
|
|
227
|
+
|
|
228
|
+
:param sound_id: Unique identifier for the sound.
|
|
229
|
+
:type sound_id: str
|
|
230
|
+
|
|
231
|
+
:param volume: Volume level (0-128).
|
|
232
|
+
:type volume: int
|
|
233
|
+
"""
|
|
234
|
+
|
|
235
|
+
def stop_all_sounds(self):
|
|
236
|
+
"""Stop all channels."""
|
|
237
|
+
|
|
238
|
+
def set_viewport_transform(
|
|
239
|
+
self, offset_x: int, offset_y: int, scale: float
|
|
240
|
+
):
|
|
241
|
+
"""
|
|
242
|
+
Apply a transform so draw_* receives VIRTUAL coords and backend maps to screen.
|
|
243
|
+
|
|
244
|
+
:param offset_x: X offset in pixels.
|
|
245
|
+
:type offset_x: int
|
|
246
|
+
|
|
247
|
+
:param offset_y: Y offset in pixels.
|
|
248
|
+
:type offset_y: int
|
|
249
|
+
|
|
250
|
+
:param scale: Scale factor.
|
|
251
|
+
:type scale: float
|
|
252
|
+
"""
|
|
253
|
+
raise NotImplementedError
|
|
254
|
+
|
|
255
|
+
def clear_viewport_transform(self):
|
|
256
|
+
"""Reset any viewport transform back to identity."""
|
|
257
|
+
raise NotImplementedError
|
|
258
|
+
|
|
259
|
+
def resize_window(self, width: int, height: int):
|
|
260
|
+
"""
|
|
261
|
+
Resize the actual OS window (SDL_SetWindowSize in native backend).
|
|
262
|
+
|
|
263
|
+
:param width: New width in pixels.
|
|
264
|
+
:type width: int
|
|
265
|
+
|
|
266
|
+
:param height: New height in pixels.
|
|
267
|
+
:type height: int
|
|
268
|
+
"""
|
|
269
|
+
raise NotImplementedError
|
|
270
|
+
|
|
271
|
+
def set_clip_rect(self, x: int, y: int, w: int, h: int):
|
|
272
|
+
"""
|
|
273
|
+
Set a clipping rectangle for rendering.
|
|
274
|
+
|
|
275
|
+
:param x: X position of the rectangle's top-left corner.
|
|
276
|
+
:type x: int
|
|
277
|
+
|
|
278
|
+
:param y: Y position of the rectangle's top-left corner.
|
|
279
|
+
:type y: int
|
|
280
|
+
|
|
281
|
+
:param w: Width of the rectangle.
|
|
282
|
+
:type w: int
|
|
283
|
+
|
|
284
|
+
:param h: Height of the rectangle.
|
|
285
|
+
:type h: int
|
|
286
|
+
"""
|
|
287
|
+
raise NotImplementedError
|
|
288
|
+
|
|
289
|
+
def clear_clip_rect(self):
|
|
290
|
+
"""Clear any clipping rectangle."""
|
|
291
|
+
raise NotImplementedError
|
|
File without changes
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Command protocol for executing commands with a given context.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
from dataclasses import dataclass, field
|
|
8
|
+
from typing import TYPE_CHECKING, List, Optional, Protocol, TypeVar
|
|
9
|
+
|
|
10
|
+
if TYPE_CHECKING:
|
|
11
|
+
from mini_arcade_core.runtime.services import RuntimeServices
|
|
12
|
+
|
|
13
|
+
# Justification: Generic type for context
|
|
14
|
+
# pylint: disable=invalid-name
|
|
15
|
+
TContext = TypeVar("TContext")
|
|
16
|
+
# pylint: enable=invalid-name
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@dataclass
|
|
20
|
+
class CommandContext:
|
|
21
|
+
"""
|
|
22
|
+
Context for command execution.
|
|
23
|
+
|
|
24
|
+
:ivar services (RuntimeServices): The runtime services.
|
|
25
|
+
:ivar commands (CommandQueue | None): Optional command queue.
|
|
26
|
+
:ivar settings (object | None): Optional settings object.
|
|
27
|
+
:ivar world (object | None): The world object (can be any type).
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
services: RuntimeServices
|
|
31
|
+
commands: Optional["CommandQueue"] = None
|
|
32
|
+
settings: Optional[object] = None
|
|
33
|
+
world: Optional[object] = None
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class Command(Protocol):
|
|
37
|
+
"""
|
|
38
|
+
A command is the only allowed "write path" from input/systems into:
|
|
39
|
+
- scene operations (push/pop/change/quit)
|
|
40
|
+
- capture
|
|
41
|
+
- global game lifecycle
|
|
42
|
+
- later: world mutations (if you pass a world reference)
|
|
43
|
+
|
|
44
|
+
For now we keep it simple: commands only need RuntimeServices.
|
|
45
|
+
"""
|
|
46
|
+
|
|
47
|
+
def execute(
|
|
48
|
+
self,
|
|
49
|
+
context: CommandContext,
|
|
50
|
+
):
|
|
51
|
+
"""
|
|
52
|
+
Execute the command with the given world and runtime services.
|
|
53
|
+
|
|
54
|
+
:param services: Runtime services for command execution.
|
|
55
|
+
:type services: RuntimeServices
|
|
56
|
+
|
|
57
|
+
:param commands: Optional command queue for command execution.
|
|
58
|
+
:type commands: object | None
|
|
59
|
+
|
|
60
|
+
:param settings: Optional settings object for command execution.
|
|
61
|
+
:type settings: object | None
|
|
62
|
+
|
|
63
|
+
:param world: The world object (can be any type).
|
|
64
|
+
:type world: object | None
|
|
65
|
+
"""
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
@dataclass
|
|
69
|
+
class CommandQueue:
|
|
70
|
+
"""
|
|
71
|
+
Queue for storing and executing commands.
|
|
72
|
+
"""
|
|
73
|
+
|
|
74
|
+
_items: List[Command] = field(default_factory=list)
|
|
75
|
+
|
|
76
|
+
def push(self, cmd: Command):
|
|
77
|
+
"""
|
|
78
|
+
Push a command onto the queue.
|
|
79
|
+
|
|
80
|
+
:param cmd: Command to be added to the queue.
|
|
81
|
+
:type cmd: Command
|
|
82
|
+
"""
|
|
83
|
+
self._items.append(cmd)
|
|
84
|
+
|
|
85
|
+
def drain(self) -> List[Command]:
|
|
86
|
+
"""
|
|
87
|
+
Drain and return all commands from the queue.
|
|
88
|
+
|
|
89
|
+
:return: List of commands that were in the queue.
|
|
90
|
+
:rtype: list[Command]
|
|
91
|
+
"""
|
|
92
|
+
items = self._items
|
|
93
|
+
self._items = []
|
|
94
|
+
return items
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
@dataclass(frozen=True)
|
|
98
|
+
class QuitCommand(Command):
|
|
99
|
+
"""Quit the game."""
|
|
100
|
+
|
|
101
|
+
def execute(
|
|
102
|
+
self,
|
|
103
|
+
context: CommandContext,
|
|
104
|
+
):
|
|
105
|
+
context.services.scenes.quit()
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
@dataclass(frozen=True)
|
|
109
|
+
class ScreenshotCommand(Command):
|
|
110
|
+
"""
|
|
111
|
+
Take a screenshot.
|
|
112
|
+
|
|
113
|
+
:ivar label (str | None): Optional label for the screenshot file.
|
|
114
|
+
"""
|
|
115
|
+
|
|
116
|
+
label: str | None = None
|
|
117
|
+
|
|
118
|
+
def execute(
|
|
119
|
+
self,
|
|
120
|
+
context: CommandContext,
|
|
121
|
+
):
|
|
122
|
+
context.services.capture.screenshot(label=self.label, mode="manual")
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
@dataclass(frozen=True)
|
|
126
|
+
class PushSceneCommand(Command):
|
|
127
|
+
"""
|
|
128
|
+
Push a new scene onto the scene stack.
|
|
129
|
+
|
|
130
|
+
:ivar scene_id (str): Identifier of the scene to push.
|
|
131
|
+
:ivar as_overlay (bool): Whether to push the scene as an overlay.
|
|
132
|
+
"""
|
|
133
|
+
|
|
134
|
+
scene_id: str
|
|
135
|
+
as_overlay: bool = False
|
|
136
|
+
|
|
137
|
+
def execute(
|
|
138
|
+
self,
|
|
139
|
+
context: CommandContext,
|
|
140
|
+
):
|
|
141
|
+
context.services.scenes.push(self.scene_id, as_overlay=self.as_overlay)
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
@dataclass(frozen=True)
|
|
145
|
+
class PopSceneCommand(Command):
|
|
146
|
+
"""Pop the current scene from the scene stack."""
|
|
147
|
+
|
|
148
|
+
def execute(
|
|
149
|
+
self,
|
|
150
|
+
context: CommandContext,
|
|
151
|
+
):
|
|
152
|
+
context.services.scenes.pop()
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
@dataclass(frozen=True)
|
|
156
|
+
class ChangeSceneCommand(Command):
|
|
157
|
+
"""
|
|
158
|
+
Change the current scene to the specified scene.
|
|
159
|
+
|
|
160
|
+
:ivar scene_id (str): Identifier of the scene to switch to.
|
|
161
|
+
"""
|
|
162
|
+
|
|
163
|
+
scene_id: str
|
|
164
|
+
|
|
165
|
+
def execute(
|
|
166
|
+
self,
|
|
167
|
+
context: CommandContext,
|
|
168
|
+
):
|
|
169
|
+
context.services.scenes.change(self.scene_id)
|