mini-arcade-core 0.9.8__py3-none-any.whl → 0.10.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.
- mini_arcade_core/__init__.py +24 -37
- mini_arcade_core/bus.py +57 -0
- mini_arcade_core/commands.py +84 -0
- mini_arcade_core/entity.py +3 -2
- mini_arcade_core/game.py +1 -1
- mini_arcade_core/managers/__init__.py +22 -0
- mini_arcade_core/managers/base.py +132 -0
- mini_arcade_core/{cheats.py → managers/cheats.py} +131 -11
- mini_arcade_core/managers/entities.py +38 -0
- mini_arcade_core/managers/inputs.py +282 -0
- mini_arcade_core/managers/overlays.py +53 -0
- mini_arcade_core/managers/system.py +26 -0
- mini_arcade_core/scenes/__init__.py +11 -1
- mini_arcade_core/scenes/model.py +34 -0
- mini_arcade_core/scenes/runtime.py +29 -0
- mini_arcade_core/scenes/scene.py +42 -82
- mini_arcade_core/scenes/system.py +69 -0
- mini_arcade_core/spaces/__init__.py +12 -0
- mini_arcade_core/{two_d → spaces/d2}/kinematics2d.py +3 -0
- mini_arcade_core/ui/__init__.py +13 -1
- mini_arcade_core/ui/menu.py +232 -54
- mini_arcade_core/ui/overlays.py +41 -0
- {mini_arcade_core-0.9.8.dist-info → mini_arcade_core-0.10.0.dist-info}/METADATA +1 -1
- mini_arcade_core-0.10.0.dist-info/RECORD +40 -0
- mini_arcade_core-0.9.8.dist-info/RECORD +0 -27
- /mini_arcade_core/{two_d → spaces/d2}/__init__.py +0 -0
- /mini_arcade_core/{two_d → spaces/d2}/boundaries2d.py +0 -0
- /mini_arcade_core/{two_d → spaces/d2}/collision2d.py +0 -0
- /mini_arcade_core/{two_d → spaces/d2}/geometry2d.py +0 -0
- /mini_arcade_core/{two_d → spaces/d2}/physics2d.py +0 -0
- {mini_arcade_core-0.9.8.dist-info → mini_arcade_core-0.10.0.dist-info}/WHEEL +0 -0
- {mini_arcade_core-0.9.8.dist-info → mini_arcade_core-0.10.0.dist-info}/licenses/LICENSE +0 -0
mini_arcade_core/__init__.py
CHANGED
|
@@ -9,24 +9,21 @@ import logging
|
|
|
9
9
|
from importlib.metadata import PackageNotFoundError, version
|
|
10
10
|
from typing import Callable, Type, Union
|
|
11
11
|
|
|
12
|
-
from mini_arcade_core
|
|
13
|
-
from mini_arcade_core
|
|
12
|
+
from mini_arcade_core import backend # noqa: F401
|
|
13
|
+
from mini_arcade_core import keymaps # noqa: F401
|
|
14
|
+
from mini_arcade_core import managers # noqa: F401
|
|
15
|
+
from mini_arcade_core import spaces # noqa: F401
|
|
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
|
+
)
|
|
14
24
|
from mini_arcade_core.entity import Entity, KinematicEntity, SpriteEntity
|
|
15
25
|
from mini_arcade_core.game import Game, GameConfig
|
|
16
|
-
from mini_arcade_core.
|
|
17
|
-
from mini_arcade_core.scenes import Scene, SceneRegistry, register_scene
|
|
18
|
-
from mini_arcade_core.two_d import (
|
|
19
|
-
Bounds2D,
|
|
20
|
-
KinematicData,
|
|
21
|
-
Position2D,
|
|
22
|
-
RectCollider,
|
|
23
|
-
RectKinematic,
|
|
24
|
-
RectSprite,
|
|
25
|
-
Size2D,
|
|
26
|
-
Velocity2D,
|
|
27
|
-
VerticalBounce,
|
|
28
|
-
VerticalWrap,
|
|
29
|
-
)
|
|
26
|
+
from mini_arcade_core.scenes import Scene, SceneRegistry
|
|
30
27
|
|
|
31
28
|
SceneFactoryLike = Union[Type[Scene], Callable[[Game], Scene]]
|
|
32
29
|
|
|
@@ -84,30 +81,20 @@ def run_game(
|
|
|
84
81
|
__all__ = [
|
|
85
82
|
"Game",
|
|
86
83
|
"GameConfig",
|
|
87
|
-
"Scene",
|
|
88
84
|
"Entity",
|
|
89
85
|
"SpriteEntity",
|
|
90
|
-
"run_game",
|
|
91
|
-
"Backend",
|
|
92
|
-
"Event",
|
|
93
|
-
"EventType",
|
|
94
|
-
"Velocity2D",
|
|
95
|
-
"Position2D",
|
|
96
|
-
"Size2D",
|
|
97
86
|
"KinematicEntity",
|
|
98
|
-
"
|
|
99
|
-
"
|
|
100
|
-
"
|
|
101
|
-
"
|
|
102
|
-
"
|
|
103
|
-
"
|
|
104
|
-
"
|
|
105
|
-
"
|
|
106
|
-
"
|
|
107
|
-
"
|
|
108
|
-
"
|
|
109
|
-
"CheatManager",
|
|
110
|
-
"CheatCode",
|
|
87
|
+
"BaseCommand",
|
|
88
|
+
"BaseGameCommand",
|
|
89
|
+
"BaseSceneCommand",
|
|
90
|
+
"QuitGameCommand",
|
|
91
|
+
"event_bus",
|
|
92
|
+
"run_game",
|
|
93
|
+
"backend",
|
|
94
|
+
"keymaps",
|
|
95
|
+
"managers",
|
|
96
|
+
"spaces",
|
|
97
|
+
"ui",
|
|
111
98
|
]
|
|
112
99
|
|
|
113
100
|
PACKAGE_NAME = "mini-arcade-core" # or whatever is in your pyproject.toml
|
mini_arcade_core/bus.py
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Event bus module.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
from typing import Callable, ClassVar, Dict, List, Optional
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class EventBus:
|
|
11
|
+
"""
|
|
12
|
+
Simple event bus for managing event subscriptions and emissions.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
_instance: ClassVar[Optional["EventBus"]] = None
|
|
16
|
+
_subscribers: Dict[str, List[Callable]]
|
|
17
|
+
|
|
18
|
+
def __new__(cls):
|
|
19
|
+
if cls._instance is None:
|
|
20
|
+
inst = super().__new__(cls)
|
|
21
|
+
inst._subscribers = {}
|
|
22
|
+
cls._instance = inst
|
|
23
|
+
return cls._instance
|
|
24
|
+
|
|
25
|
+
def on(self, event_type: str, handler: Callable):
|
|
26
|
+
"""
|
|
27
|
+
Subscribe a handler to an event type.
|
|
28
|
+
|
|
29
|
+
:param event_type: The type of event to subscribe to.
|
|
30
|
+
:type event_type: str
|
|
31
|
+
|
|
32
|
+
:param handler: The handler function to call when the event is emitted.
|
|
33
|
+
:type handler: Callable
|
|
34
|
+
"""
|
|
35
|
+
if event_type not in self._subscribers:
|
|
36
|
+
self._subscribers[event_type] = []
|
|
37
|
+
self._subscribers[event_type].append(handler)
|
|
38
|
+
|
|
39
|
+
def emit(self, event_type: str, **kwargs):
|
|
40
|
+
"""
|
|
41
|
+
Emit an event of a given type, calling all subscribed handlers.
|
|
42
|
+
|
|
43
|
+
:param event_type: The type of event to emit.
|
|
44
|
+
:type event_type: str
|
|
45
|
+
|
|
46
|
+
:param kwargs: Additional keyword arguments to pass to handlers.
|
|
47
|
+
"""
|
|
48
|
+
handlers = self._subscribers.get(event_type, [])
|
|
49
|
+
for handler in handlers:
|
|
50
|
+
handler(**kwargs)
|
|
51
|
+
|
|
52
|
+
def clear(self):
|
|
53
|
+
"""Clear all subscribers from the event bus."""
|
|
54
|
+
self._subscribers.clear()
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
event_bus = EventBus()
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Command protocol for executing commands with a given context.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
from typing import TYPE_CHECKING, Generic, Protocol, TypeVar
|
|
8
|
+
|
|
9
|
+
from mini_arcade_core.game import Game
|
|
10
|
+
from mini_arcade_core.scenes.model import SceneModel
|
|
11
|
+
|
|
12
|
+
if TYPE_CHECKING:
|
|
13
|
+
from mini_arcade_core.scenes.scene import Scene
|
|
14
|
+
|
|
15
|
+
# Justification: Generic type for context
|
|
16
|
+
# pylint: disable=invalid-name
|
|
17
|
+
TContext = TypeVar("TContext")
|
|
18
|
+
# pylint: enable=invalid-name
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class BaseCommand(Protocol, Generic[TContext]):
|
|
22
|
+
"""
|
|
23
|
+
Protocol for a command that can be executed with a given context.
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
def __call__(self, context: TContext) -> None:
|
|
27
|
+
"""
|
|
28
|
+
Execute the cheat code with the given context.
|
|
29
|
+
|
|
30
|
+
:param context: Context object for cheat execution.
|
|
31
|
+
:type context: TContext
|
|
32
|
+
"""
|
|
33
|
+
self.execute(context)
|
|
34
|
+
|
|
35
|
+
def execute(self, context: TContext):
|
|
36
|
+
"""
|
|
37
|
+
Execute the command with the given context.
|
|
38
|
+
|
|
39
|
+
:param context: Context object for command execution.
|
|
40
|
+
:type context: TContext
|
|
41
|
+
"""
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class BaseGameCommand(BaseCommand[Game]):
|
|
45
|
+
"""
|
|
46
|
+
Base class for commands that operate on the Game context.
|
|
47
|
+
"""
|
|
48
|
+
|
|
49
|
+
def execute(self, context: Game) -> None:
|
|
50
|
+
"""
|
|
51
|
+
Execute the command with the given Game context.
|
|
52
|
+
|
|
53
|
+
:param context: Game context for command execution.
|
|
54
|
+
:type context: Game
|
|
55
|
+
"""
|
|
56
|
+
raise NotImplementedError(
|
|
57
|
+
"Execute method must be implemented by subclasses."
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
class BaseSceneCommand(BaseCommand[SceneModel]):
|
|
62
|
+
"""
|
|
63
|
+
Base class for commands that operate on the Scene SceneModel context within a scene.
|
|
64
|
+
"""
|
|
65
|
+
|
|
66
|
+
def execute(self, context: SceneModel) -> None:
|
|
67
|
+
"""
|
|
68
|
+
Execute the command with the given Scene Model context.
|
|
69
|
+
|
|
70
|
+
:param context: Scene Model context for command execution.
|
|
71
|
+
:type context: SceneModel
|
|
72
|
+
"""
|
|
73
|
+
raise NotImplementedError(
|
|
74
|
+
"Execute method must be implemented by subclasses."
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
class QuitGameCommand(BaseGameCommand):
|
|
79
|
+
"""
|
|
80
|
+
Command to quit the game.
|
|
81
|
+
"""
|
|
82
|
+
|
|
83
|
+
def execute(self, context: Game) -> None:
|
|
84
|
+
context.quit()
|
mini_arcade_core/entity.py
CHANGED
|
@@ -6,7 +6,7 @@ from __future__ import annotations
|
|
|
6
6
|
|
|
7
7
|
from typing import Any
|
|
8
8
|
|
|
9
|
-
from mini_arcade_core.
|
|
9
|
+
from mini_arcade_core.spaces.d2 import (
|
|
10
10
|
KinematicData,
|
|
11
11
|
Position2D,
|
|
12
12
|
RectCollider,
|
|
@@ -63,9 +63,10 @@ class KinematicEntity(SpriteEntity):
|
|
|
63
63
|
size=kinematic_data.size,
|
|
64
64
|
)
|
|
65
65
|
|
|
66
|
+
self.time_scale = kinematic_data.time_scale
|
|
66
67
|
self.velocity = kinematic_data.velocity
|
|
67
68
|
|
|
68
69
|
def update(self, dt: float):
|
|
69
70
|
self.position.x, self.position.y = self.velocity.advance(
|
|
70
|
-
self.position.x, self.position.y, dt
|
|
71
|
+
self.position.x, self.position.y, dt * self.time_scale
|
|
71
72
|
)
|
mini_arcade_core/game.py
CHANGED
|
@@ -14,7 +14,7 @@ from typing import TYPE_CHECKING, Literal, Union
|
|
|
14
14
|
from PIL import Image # type: ignore[import]
|
|
15
15
|
|
|
16
16
|
from mini_arcade_core.backend import Backend
|
|
17
|
-
from mini_arcade_core.scenes import SceneRegistry
|
|
17
|
+
from mini_arcade_core.scenes.registry import SceneRegistry
|
|
18
18
|
|
|
19
19
|
if TYPE_CHECKING: # avoid runtime circular import
|
|
20
20
|
from mini_arcade_core.scenes import Scene
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Managers module for Mini Arcade Core.
|
|
3
|
+
Provides various manager classes for handling game entities and resources.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
from .cheats import BaseCheatCommand, CheatCode, CheatManager
|
|
9
|
+
from .entities import EntityManager
|
|
10
|
+
from .inputs import InputManager
|
|
11
|
+
from .overlays import OverlayManager
|
|
12
|
+
from .system import SystemManager
|
|
13
|
+
|
|
14
|
+
__all__ = [
|
|
15
|
+
"EntityManager",
|
|
16
|
+
"OverlayManager",
|
|
17
|
+
"CheatCode",
|
|
18
|
+
"CheatManager",
|
|
19
|
+
"BaseCheatCommand",
|
|
20
|
+
"InputManager",
|
|
21
|
+
"SystemManager",
|
|
22
|
+
]
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Base manager classes for handling collections of items.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
from dataclasses import dataclass, field
|
|
8
|
+
from typing import Callable, Generic, Iterable, List, Protocol, TypeVar
|
|
9
|
+
|
|
10
|
+
from mini_arcade_core.backend.backend import Backend
|
|
11
|
+
|
|
12
|
+
# ---- shared types ----
|
|
13
|
+
T = TypeVar("T")
|
|
14
|
+
OverlayFunc = Callable[[Backend], None]
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class Overlay(Protocol):
|
|
18
|
+
"""
|
|
19
|
+
Defines an overlay function.
|
|
20
|
+
|
|
21
|
+
:ivar enabled (bool): Whether the overlay is enabled.
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
enabled: bool
|
|
25
|
+
|
|
26
|
+
def update(self, dt: float):
|
|
27
|
+
"""
|
|
28
|
+
Update the overlay state.
|
|
29
|
+
|
|
30
|
+
:param dt: Time delta in seconds.
|
|
31
|
+
:type dt: float
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
def draw(self, surface: "Backend"):
|
|
35
|
+
"""
|
|
36
|
+
Draw the overlay on the given surface.
|
|
37
|
+
|
|
38
|
+
:param surface: The backend surface to draw on.
|
|
39
|
+
:type surface: Backend
|
|
40
|
+
"""
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class Drawable(Protocol):
|
|
44
|
+
"""Defines a drawable entity."""
|
|
45
|
+
|
|
46
|
+
def draw(self, surface: Backend):
|
|
47
|
+
"""
|
|
48
|
+
Draw the entity on the given surface.
|
|
49
|
+
|
|
50
|
+
:param surface: The backend surface to draw on.
|
|
51
|
+
:type surface: Backend
|
|
52
|
+
"""
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
class Updatable(Protocol):
|
|
56
|
+
"""Defines an updatable entity."""
|
|
57
|
+
|
|
58
|
+
def update(self, dt: float):
|
|
59
|
+
"""
|
|
60
|
+
Update the entity state.
|
|
61
|
+
|
|
62
|
+
:param dt: Time delta in seconds.
|
|
63
|
+
:type dt: float
|
|
64
|
+
"""
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
class EntityLike(Drawable, Updatable, Protocol):
|
|
68
|
+
"""Defines a game entity."""
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
@dataclass
|
|
72
|
+
class ListManager(Generic[T]):
|
|
73
|
+
"""
|
|
74
|
+
Generic manager for a list of items.
|
|
75
|
+
|
|
76
|
+
:ivar items (List[T]): List of managed items.
|
|
77
|
+
"""
|
|
78
|
+
|
|
79
|
+
items: List[T] = field(default_factory=list)
|
|
80
|
+
|
|
81
|
+
def add(self, *items: T):
|
|
82
|
+
"""
|
|
83
|
+
Add one or more items to the manager.
|
|
84
|
+
|
|
85
|
+
:param items: One or more items to add.
|
|
86
|
+
:type items: T
|
|
87
|
+
"""
|
|
88
|
+
self.items.extend(items)
|
|
89
|
+
|
|
90
|
+
def add_many(self, items: Iterable[T]):
|
|
91
|
+
"""
|
|
92
|
+
Add multiple items to the manager.
|
|
93
|
+
|
|
94
|
+
:param items: An iterable of items to add.
|
|
95
|
+
:type items: Iterable[T]
|
|
96
|
+
"""
|
|
97
|
+
self.items.extend(items)
|
|
98
|
+
|
|
99
|
+
def remove(self, item: T):
|
|
100
|
+
"""
|
|
101
|
+
Remove a single item from the manager, if present.
|
|
102
|
+
|
|
103
|
+
:param item: The item to remove.
|
|
104
|
+
:type item: T
|
|
105
|
+
"""
|
|
106
|
+
if item in self.items:
|
|
107
|
+
self.items.remove(item)
|
|
108
|
+
|
|
109
|
+
def get(self, item: T) -> T | None:
|
|
110
|
+
"""
|
|
111
|
+
Get an item from the manager.
|
|
112
|
+
|
|
113
|
+
:param item: The item to get.
|
|
114
|
+
:type item: T
|
|
115
|
+
|
|
116
|
+
:return: The item if found, else None.
|
|
117
|
+
:rtype: T | None
|
|
118
|
+
"""
|
|
119
|
+
for it in self.items:
|
|
120
|
+
if it == item:
|
|
121
|
+
return it
|
|
122
|
+
return None
|
|
123
|
+
|
|
124
|
+
def clear(self):
|
|
125
|
+
"""Clear all items from the manager."""
|
|
126
|
+
self.items.clear()
|
|
127
|
+
|
|
128
|
+
def __iter__(self):
|
|
129
|
+
return iter(self.items)
|
|
130
|
+
|
|
131
|
+
def __len__(self) -> int:
|
|
132
|
+
return len(self.items)
|
|
@@ -5,17 +5,78 @@ Provides cheat codes and related functionality.
|
|
|
5
5
|
|
|
6
6
|
from __future__ import annotations
|
|
7
7
|
|
|
8
|
+
from abc import ABC, abstractmethod
|
|
8
9
|
from collections import deque
|
|
9
10
|
from dataclasses import dataclass
|
|
10
11
|
from enum import Enum
|
|
11
|
-
from typing import
|
|
12
|
+
from typing import (
|
|
13
|
+
TYPE_CHECKING,
|
|
14
|
+
Callable,
|
|
15
|
+
Deque,
|
|
16
|
+
Dict,
|
|
17
|
+
Generic,
|
|
18
|
+
Optional,
|
|
19
|
+
Protocol,
|
|
20
|
+
Sequence,
|
|
21
|
+
TypeVar,
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
from mini_arcade_core.backend import Event, EventType
|
|
25
|
+
from mini_arcade_core.scenes.system import BaseSceneSystem
|
|
26
|
+
|
|
27
|
+
if TYPE_CHECKING:
|
|
28
|
+
from mini_arcade_core.scenes import Scene
|
|
12
29
|
|
|
13
30
|
# Justification: We want to keep the type variable name simple here.
|
|
14
31
|
# pylint: disable=invalid-name
|
|
15
32
|
TContext = TypeVar("TContext")
|
|
33
|
+
TScene = TypeVar("TScene", bound="Scene")
|
|
16
34
|
# pylint: enable=invalid-name
|
|
17
35
|
|
|
18
|
-
|
|
36
|
+
|
|
37
|
+
@dataclass
|
|
38
|
+
class BaseCheatCommand(ABC, Generic[TContext]):
|
|
39
|
+
"""
|
|
40
|
+
Base class for cheat codes.
|
|
41
|
+
|
|
42
|
+
:ivar enabled (bool): Whether the cheat code is enabled.
|
|
43
|
+
"""
|
|
44
|
+
|
|
45
|
+
enabled: bool = True
|
|
46
|
+
|
|
47
|
+
def __call__(self, context: TContext) -> None:
|
|
48
|
+
"""
|
|
49
|
+
Execute the cheat code with the given context.
|
|
50
|
+
|
|
51
|
+
:param context: Context object for cheat execution.
|
|
52
|
+
:type context: TContext
|
|
53
|
+
"""
|
|
54
|
+
if not self.enabled:
|
|
55
|
+
return False
|
|
56
|
+
self.execute(context)
|
|
57
|
+
return True
|
|
58
|
+
|
|
59
|
+
@abstractmethod
|
|
60
|
+
def execute(self, context: TContext):
|
|
61
|
+
"""
|
|
62
|
+
Execute the cheat code with the given context.
|
|
63
|
+
|
|
64
|
+
:param context: Context object for cheat execution.
|
|
65
|
+
:type context: TContext
|
|
66
|
+
"""
|
|
67
|
+
raise NotImplementedError("CheatCommand.execute must be overridden.")
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
class CheatAction(Protocol[TContext]):
|
|
71
|
+
"""
|
|
72
|
+
Protocol for cheat code actions.
|
|
73
|
+
|
|
74
|
+
:ivar enabled: Whether the cheat action is enabled.
|
|
75
|
+
"""
|
|
76
|
+
|
|
77
|
+
enabled: bool
|
|
78
|
+
|
|
79
|
+
def __call__(self, ctx: TContext) -> bool: ...
|
|
19
80
|
|
|
20
81
|
|
|
21
82
|
@dataclass(frozen=True)
|
|
@@ -25,14 +86,14 @@ class CheatCode:
|
|
|
25
86
|
|
|
26
87
|
:ivar name (str): Unique name of the cheat code.
|
|
27
88
|
:ivar sequence (tuple[str, ...]): Sequence of key strings that trigger the cheat.
|
|
28
|
-
:ivar
|
|
89
|
+
:ivar action (CheatAction): BaseCheatCommand to call when the cheat is activated.
|
|
29
90
|
:ivar clear_buffer_on_match (bool): Whether to clear the input buffer after a match.
|
|
30
91
|
:ivar enabled (bool): Whether the cheat code is enabled.
|
|
31
92
|
"""
|
|
32
93
|
|
|
33
94
|
name: str
|
|
34
95
|
sequence: tuple[str, ...]
|
|
35
|
-
|
|
96
|
+
action: CheatAction[TContext]
|
|
36
97
|
clear_buffer_on_match: bool = False
|
|
37
98
|
enabled: bool = True
|
|
38
99
|
|
|
@@ -74,17 +135,17 @@ class CheatManager:
|
|
|
74
135
|
|
|
75
136
|
# Justification: We want to keep the number of arguments manageable here.
|
|
76
137
|
# pylint: disable=too-many-arguments
|
|
77
|
-
def
|
|
138
|
+
def register_command(
|
|
78
139
|
self,
|
|
79
140
|
name: str,
|
|
80
141
|
sequence: Sequence[str],
|
|
81
|
-
|
|
142
|
+
command: BaseCheatCommand[TContext],
|
|
82
143
|
*,
|
|
83
144
|
clear_buffer_on_match: bool = False,
|
|
84
145
|
enabled: bool = True,
|
|
85
146
|
):
|
|
86
147
|
"""
|
|
87
|
-
Register a new cheat code.
|
|
148
|
+
Register a new cheat code that triggers a BaseCheatCommand.
|
|
88
149
|
|
|
89
150
|
:param name: Unique name for the cheat code.
|
|
90
151
|
:type name: str
|
|
@@ -92,8 +153,8 @@ class CheatManager:
|
|
|
92
153
|
:param sequence: Sequence of key strings that trigger the cheat.
|
|
93
154
|
:type sequence: Sequence[str]
|
|
94
155
|
|
|
95
|
-
:param
|
|
96
|
-
:type
|
|
156
|
+
:param command: BaseCheatCommand to execute when the cheat is activated.
|
|
157
|
+
:type command: BaseCheatCommand[TContext]
|
|
97
158
|
|
|
98
159
|
:param clear_buffer_on_match: Whether to clear the input buffer after a match.
|
|
99
160
|
:type clear_buffer_on_match: bool
|
|
@@ -112,7 +173,7 @@ class CheatManager:
|
|
|
112
173
|
self._codes[name] = CheatCode(
|
|
113
174
|
name=name,
|
|
114
175
|
sequence=norm_seq,
|
|
115
|
-
|
|
176
|
+
action=command,
|
|
116
177
|
clear_buffer_on_match=clear_buffer_on_match,
|
|
117
178
|
enabled=enabled,
|
|
118
179
|
)
|
|
@@ -182,7 +243,7 @@ class CheatManager:
|
|
|
182
243
|
if len(seq) > len(buf):
|
|
183
244
|
continue
|
|
184
245
|
if buf[-len(seq) :] == seq:
|
|
185
|
-
code.
|
|
246
|
+
code.action(context)
|
|
186
247
|
matched.append(code.name)
|
|
187
248
|
if code.clear_buffer_on_match:
|
|
188
249
|
self.clear_buffer()
|
|
@@ -233,3 +294,62 @@ class CheatManager:
|
|
|
233
294
|
return str(v)
|
|
234
295
|
|
|
235
296
|
return None
|
|
297
|
+
|
|
298
|
+
|
|
299
|
+
class CheatSystem(BaseSceneSystem, Generic[TScene]):
|
|
300
|
+
"""
|
|
301
|
+
Scene system for handling cheat codes.
|
|
302
|
+
|
|
303
|
+
:ivar priority (int): Priority of the system (lower runs first).
|
|
304
|
+
:ivar enabled (bool): Whether the system is enabled.
|
|
305
|
+
"""
|
|
306
|
+
|
|
307
|
+
priority = 10
|
|
308
|
+
|
|
309
|
+
def __init__(self, scene: TScene, buffer_size=16):
|
|
310
|
+
"""
|
|
311
|
+
:param buffer_size: Size of the cheat input buffer.
|
|
312
|
+
:type buffer_size: int
|
|
313
|
+
"""
|
|
314
|
+
super().__init__(scene)
|
|
315
|
+
self.cheats = CheatManager(buffer_size=buffer_size)
|
|
316
|
+
|
|
317
|
+
def register(self, name: str, seq: list[str], cmd: Callable, **kwargs):
|
|
318
|
+
"""
|
|
319
|
+
Helper to register a cheat command.
|
|
320
|
+
|
|
321
|
+
:param name: Unique name for the cheat code.
|
|
322
|
+
:type name: str
|
|
323
|
+
|
|
324
|
+
:param seq: Sequence of key strings that trigger the cheat.
|
|
325
|
+
:type seq: list[str]
|
|
326
|
+
|
|
327
|
+
:param cmd: Callable to execute when the cheat is activated.
|
|
328
|
+
:type cmd: Callable
|
|
329
|
+
|
|
330
|
+
:param kwargs: Additional keyword arguments for cheat registration.
|
|
331
|
+
"""
|
|
332
|
+
self.cheats.register_command(name, seq, cmd, **kwargs)
|
|
333
|
+
|
|
334
|
+
def on_enter(self):
|
|
335
|
+
"""
|
|
336
|
+
Called when the scene is entered.
|
|
337
|
+
"""
|
|
338
|
+
# register codes here (or via a builder)
|
|
339
|
+
|
|
340
|
+
def handle_event(self, event: Event) -> bool:
|
|
341
|
+
"""
|
|
342
|
+
Handle an event.
|
|
343
|
+
|
|
344
|
+
:param event: The event to handle.
|
|
345
|
+
:type event: Event
|
|
346
|
+
|
|
347
|
+
:param scene: The scene receiving the event.
|
|
348
|
+
:type scene: TScene
|
|
349
|
+
|
|
350
|
+
:return: True if the event was handled, False otherwise.
|
|
351
|
+
:rtype: bool
|
|
352
|
+
"""
|
|
353
|
+
if event.type == EventType.KEYDOWN:
|
|
354
|
+
self.cheats.process_event(event, self.scene)
|
|
355
|
+
return False # usually don't consume
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Entity manager for handling a collection of entities.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
from dataclasses import dataclass
|
|
8
|
+
|
|
9
|
+
from mini_arcade_core.backend import Backend
|
|
10
|
+
|
|
11
|
+
from .base import EntityLike, ListManager
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@dataclass
|
|
15
|
+
class EntityManager(ListManager[EntityLike]):
|
|
16
|
+
"""
|
|
17
|
+
Manages a collection of entities within a scene.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
def update(self, dt: float):
|
|
21
|
+
"""
|
|
22
|
+
Update all managed entities.
|
|
23
|
+
|
|
24
|
+
:param dt: Time delta in seconds.
|
|
25
|
+
:type dt: float
|
|
26
|
+
"""
|
|
27
|
+
for ent in list(self.items):
|
|
28
|
+
ent.update(dt)
|
|
29
|
+
|
|
30
|
+
def draw(self, surface: "Backend"):
|
|
31
|
+
"""
|
|
32
|
+
Draw all managed entities.
|
|
33
|
+
|
|
34
|
+
:param surface: The backend surface to draw on.
|
|
35
|
+
:type surface: Backend
|
|
36
|
+
"""
|
|
37
|
+
for ent in list(self.items):
|
|
38
|
+
ent.draw(surface)
|