mima-engine 0.4.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.
- mima/__init__.py +4 -0
- mima/backend/__init__.py +1 -0
- mima/backend/pygame_assets.py +401 -0
- mima/backend/pygame_audio.py +78 -0
- mima/backend/pygame_backend.py +603 -0
- mima/backend/pygame_camera.py +63 -0
- mima/backend/pygame_events.py +695 -0
- mima/backend/touch_control_scheme_a.py +126 -0
- mima/backend/touch_control_scheme_b.py +132 -0
- mima/core/__init__.py +0 -0
- mima/core/collision.py +325 -0
- mima/core/database.py +58 -0
- mima/core/engine.py +367 -0
- mima/core/mode_engine.py +81 -0
- mima/core/scene_engine.py +81 -0
- mima/integrated/__init__.py +0 -0
- mima/integrated/entity.py +183 -0
- mima/integrated/layered_map.py +351 -0
- mima/integrated/sprite.py +156 -0
- mima/layered/__init__.py +0 -0
- mima/layered/assets.py +56 -0
- mima/layered/scene.py +415 -0
- mima/layered/shape.py +99 -0
- mima/layered/shaped_sprite.py +78 -0
- mima/layered/virtual_input.py +302 -0
- mima/maps/__init__.py +0 -0
- mima/maps/template.py +71 -0
- mima/maps/tile.py +20 -0
- mima/maps/tile_animation.py +7 -0
- mima/maps/tile_info.py +10 -0
- mima/maps/tile_layer.py +52 -0
- mima/maps/tiled/__init__.py +0 -0
- mima/maps/tiled/tiled_layer.py +48 -0
- mima/maps/tiled/tiled_map.py +95 -0
- mima/maps/tiled/tiled_object.py +79 -0
- mima/maps/tiled/tiled_objectgroup.py +25 -0
- mima/maps/tiled/tiled_template.py +49 -0
- mima/maps/tiled/tiled_tile.py +90 -0
- mima/maps/tiled/tiled_tileset.py +51 -0
- mima/maps/tilemap.py +216 -0
- mima/maps/tileset.py +39 -0
- mima/maps/tileset_info.py +9 -0
- mima/maps/transition_map.py +146 -0
- mima/objects/__init__.py +0 -0
- mima/objects/animated_sprite.py +217 -0
- mima/objects/attribute_effect.py +26 -0
- mima/objects/attributes.py +126 -0
- mima/objects/creature.py +384 -0
- mima/objects/dynamic.py +206 -0
- mima/objects/effects/__init__.py +0 -0
- mima/objects/effects/colorize_screen.py +60 -0
- mima/objects/effects/debug_box.py +133 -0
- mima/objects/effects/light.py +103 -0
- mima/objects/effects/show_sprite.py +50 -0
- mima/objects/effects/walking_on_grass.py +70 -0
- mima/objects/effects/walking_on_water.py +57 -0
- mima/objects/loader.py +111 -0
- mima/objects/projectile.py +111 -0
- mima/objects/sprite.py +116 -0
- mima/objects/world/__init__.py +0 -0
- mima/objects/world/color_gate.py +67 -0
- mima/objects/world/color_switch.py +101 -0
- mima/objects/world/container.py +175 -0
- mima/objects/world/floor_switch.py +109 -0
- mima/objects/world/gate.py +178 -0
- mima/objects/world/light_source.py +121 -0
- mima/objects/world/logic_gate.py +157 -0
- mima/objects/world/movable.py +399 -0
- mima/objects/world/oneway.py +195 -0
- mima/objects/world/pickup.py +157 -0
- mima/objects/world/switch.py +179 -0
- mima/objects/world/teleport.py +308 -0
- mima/py.typed +0 -0
- mima/scripts/__init__.py +2 -0
- mima/scripts/command.py +38 -0
- mima/scripts/commands/__init__.py +0 -0
- mima/scripts/commands/add_quest.py +19 -0
- mima/scripts/commands/change_map.py +34 -0
- mima/scripts/commands/close_dialog.py +9 -0
- mima/scripts/commands/equip_weapon.py +23 -0
- mima/scripts/commands/give_item.py +26 -0
- mima/scripts/commands/give_resource.py +51 -0
- mima/scripts/commands/move_map.py +152 -0
- mima/scripts/commands/move_to.py +49 -0
- mima/scripts/commands/oneway_move.py +58 -0
- mima/scripts/commands/parallel.py +66 -0
- mima/scripts/commands/play_sound.py +13 -0
- mima/scripts/commands/present_item.py +53 -0
- mima/scripts/commands/progress_quest.py +12 -0
- mima/scripts/commands/quit_game.py +8 -0
- mima/scripts/commands/save_game.py +14 -0
- mima/scripts/commands/screen_fade.py +83 -0
- mima/scripts/commands/serial.py +69 -0
- mima/scripts/commands/set_facing_direction.py +21 -0
- mima/scripts/commands/set_spawn_map.py +17 -0
- mima/scripts/commands/show_choices.py +52 -0
- mima/scripts/commands/show_dialog.py +118 -0
- mima/scripts/commands/take_coins.py +23 -0
- mima/scripts/script_processor.py +61 -0
- mima/standalone/__init__.py +0 -0
- mima/standalone/camera.py +153 -0
- mima/standalone/geometry.py +1318 -0
- mima/standalone/multicolumn_list.py +54 -0
- mima/standalone/pixel_font.py +84 -0
- mima/standalone/scripting.py +145 -0
- mima/standalone/spatial.py +186 -0
- mima/standalone/sprite.py +158 -0
- mima/standalone/tiled_map.py +1247 -0
- mima/standalone/transformed_view.py +433 -0
- mima/standalone/user_input.py +563 -0
- mima/states/__init__.py +0 -0
- mima/states/game_state.py +189 -0
- mima/states/memory.py +28 -0
- mima/states/quest.py +71 -0
- mima/types/__init__.py +0 -0
- mima/types/alignment.py +7 -0
- mima/types/blend.py +8 -0
- mima/types/damage.py +42 -0
- mima/types/direction.py +44 -0
- mima/types/gate_color.py +7 -0
- mima/types/graphic_state.py +23 -0
- mima/types/keys.py +64 -0
- mima/types/mode.py +9 -0
- mima/types/nature.py +12 -0
- mima/types/object.py +22 -0
- mima/types/player.py +9 -0
- mima/types/position.py +13 -0
- mima/types/start.py +7 -0
- mima/types/terrain.py +9 -0
- mima/types/tile_collision.py +11 -0
- mima/types/weapon_slot.py +6 -0
- mima/types/window.py +44 -0
- mima/usables/__init__.py +0 -0
- mima/usables/item.py +51 -0
- mima/usables/weapon.py +68 -0
- mima/util/__init__.py +1 -0
- mima/util/colors.py +50 -0
- mima/util/constants.py +55 -0
- mima/util/functions.py +38 -0
- mima/util/input_defaults.py +170 -0
- mima/util/logging.py +51 -0
- mima/util/property.py +8 -0
- mima/util/runtime_config.py +327 -0
- mima/util/trading_item.py +23 -0
- mima/view/__init__.py +0 -0
- mima/view/camera.py +192 -0
- mima/view/mima_mode.py +618 -0
- mima/view/mima_scene.py +231 -0
- mima/view/mima_view.py +12 -0
- mima/view/mima_window.py +244 -0
- mima_engine-0.4.0.dist-info/METADATA +47 -0
- mima_engine-0.4.0.dist-info/RECORD +153 -0
- mima_engine-0.4.0.dist-info/WHEEL +4 -0
|
@@ -0,0 +1,563 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
from dataclasses import dataclass
|
|
5
|
+
from enum import Enum
|
|
6
|
+
|
|
7
|
+
import pygame
|
|
8
|
+
from typing_extensions import Callable, Iterable, Protocol, TypeAlias, TypeVar
|
|
9
|
+
|
|
10
|
+
LOG = logging.getLogger(__name__)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class Input(Enum):
|
|
14
|
+
"""Logical input identifiers independent of physical devices."""
|
|
15
|
+
|
|
16
|
+
UP = 0
|
|
17
|
+
DOWN = 1
|
|
18
|
+
LEFT = 2
|
|
19
|
+
RIGHT = 3
|
|
20
|
+
A = 4
|
|
21
|
+
B = 5
|
|
22
|
+
X = 6
|
|
23
|
+
Y = 7
|
|
24
|
+
START = 8
|
|
25
|
+
SELECT = 9
|
|
26
|
+
L1 = 10
|
|
27
|
+
R1 = 11
|
|
28
|
+
L2 = 12
|
|
29
|
+
R2 = 13
|
|
30
|
+
L3 = 14
|
|
31
|
+
R3 = 15
|
|
32
|
+
DPAD_UP = 16
|
|
33
|
+
DPAD_DOWN = 17
|
|
34
|
+
DPAD_LEFT = 18
|
|
35
|
+
DPAD_RIGHT = 19
|
|
36
|
+
LEFT_STICK_X = 20 # left x-axis
|
|
37
|
+
LEFT_STICK_Y = 21 # left y-axis
|
|
38
|
+
RIGHT_STICK_X = 22 # left x-axis
|
|
39
|
+
RIGHT_STICK_Y = 23 # left y-axis
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class Player(Enum):
|
|
43
|
+
NP = 0 # No player
|
|
44
|
+
P1 = 1 # Player 1
|
|
45
|
+
P2 = 2 # Player 2
|
|
46
|
+
P3 = 3 # Player 3
|
|
47
|
+
P4 = 4 # Player 4
|
|
48
|
+
RP = 5 # Remote player
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
LOCAL_PLAYERS = [Player.P1, Player.P2, Player.P3, Player.P4]
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
@dataclass(frozen=True)
|
|
55
|
+
class InputEvent:
|
|
56
|
+
button: Input
|
|
57
|
+
player: Player
|
|
58
|
+
value: float # 1 set, 0 unset, -1 and everything between for axes
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
class PlayerAssignment:
|
|
62
|
+
"""Manages controller to player assignment.
|
|
63
|
+
|
|
64
|
+
Policy:
|
|
65
|
+
- First connect controller gets P1
|
|
66
|
+
- Next gets p2, ect.
|
|
67
|
+
- Released on disconnect
|
|
68
|
+
"""
|
|
69
|
+
|
|
70
|
+
def __init__(self) -> None:
|
|
71
|
+
self._joy_to_player: dict[int, Player] = {}
|
|
72
|
+
self._player_to_joy: dict[Player, int] = {}
|
|
73
|
+
|
|
74
|
+
def assign(self, instance_id: int) -> Player | None:
|
|
75
|
+
if instance_id in self._joy_to_player:
|
|
76
|
+
return self._joy_to_player[instance_id]
|
|
77
|
+
|
|
78
|
+
for player in LOCAL_PLAYERS:
|
|
79
|
+
if player not in self._player_to_joy:
|
|
80
|
+
self._joy_to_player[instance_id] = player
|
|
81
|
+
self._player_to_joy[player] = instance_id
|
|
82
|
+
return player
|
|
83
|
+
|
|
84
|
+
return None
|
|
85
|
+
|
|
86
|
+
def release(self, instance_id: int) -> Player | None:
|
|
87
|
+
player = self._joy_to_player.pop(instance_id, None)
|
|
88
|
+
if player is not None:
|
|
89
|
+
self._player_to_joy.pop(player, None)
|
|
90
|
+
return player
|
|
91
|
+
|
|
92
|
+
def get_player(self, instance_id: int) -> Player | None:
|
|
93
|
+
return self._joy_to_player.get(instance_id)
|
|
94
|
+
|
|
95
|
+
def force_assign(self, instance_id: int, player: Player) -> None:
|
|
96
|
+
self._joy_to_player[instance_id] = player
|
|
97
|
+
|
|
98
|
+
def mapping(self) -> dict[int, Player]:
|
|
99
|
+
"""Return read-only copy."""
|
|
100
|
+
return dict(self._joy_to_player)
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
class InputScheme(Protocol):
|
|
104
|
+
"""Translate pygame events into logical input events."""
|
|
105
|
+
|
|
106
|
+
def handle(self, event: pygame.event.Event) -> list[InputEvent]: ...
|
|
107
|
+
|
|
108
|
+
def poll(self) -> list[InputEvent]: ...
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
Proto = TypeVar("Proto", bound=InputScheme)
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
class InputManager:
|
|
115
|
+
"""A class that manages keyboards, gamepad, and touch events."""
|
|
116
|
+
|
|
117
|
+
def __init__(self) -> None:
|
|
118
|
+
pygame.joystick.init()
|
|
119
|
+
|
|
120
|
+
self._players = PlayerAssignment()
|
|
121
|
+
|
|
122
|
+
self._previous: dict[Player, dict[Input, float]] = {
|
|
123
|
+
p: {i: 0.0 for i in Input} for p in Player
|
|
124
|
+
}
|
|
125
|
+
self._current: dict[Player, dict[Input, float]] = {
|
|
126
|
+
p: {i: 0.0 for i in Input} for p in Player
|
|
127
|
+
}
|
|
128
|
+
self._schemes: list[InputScheme] = []
|
|
129
|
+
self._joysticks: dict[int, pygame.joystick.JoystickType] = {}
|
|
130
|
+
|
|
131
|
+
self._discover_joysticks()
|
|
132
|
+
|
|
133
|
+
def add_input_scheme(self, scheme: InputScheme) -> None:
|
|
134
|
+
"""Add an input scheme to this manager."""
|
|
135
|
+
scheme.im = self # type: ignore[reportAttributeAccessIssue]
|
|
136
|
+
self._schemes.append(scheme)
|
|
137
|
+
|
|
138
|
+
def process_events(
|
|
139
|
+
self, events: list[pygame.event.Event] | None = None
|
|
140
|
+
) -> list[pygame.event.Event]:
|
|
141
|
+
"""Query and process all pygame events that are relevant for input.
|
|
142
|
+
|
|
143
|
+
Events may be provided or queried directly from pygame. Processing of
|
|
144
|
+
events is delegated to the input schemes.
|
|
145
|
+
|
|
146
|
+
Args:
|
|
147
|
+
events: List of pygame events or None. When None is passed, the
|
|
148
|
+
inputs will be queried with ``pygame.event.get()``.
|
|
149
|
+
|
|
150
|
+
Returns:
|
|
151
|
+
A list with all pygame events.
|
|
152
|
+
"""
|
|
153
|
+
if events is None or not events:
|
|
154
|
+
events = list(pygame.event.get())
|
|
155
|
+
|
|
156
|
+
# Snapshot previous state
|
|
157
|
+
self._previous = {p: state.copy() for p, state in self._current.items()}
|
|
158
|
+
|
|
159
|
+
logical_events: list[InputEvent] = []
|
|
160
|
+
|
|
161
|
+
# Event-driven input
|
|
162
|
+
for event in events:
|
|
163
|
+
self._handle_device_events(event)
|
|
164
|
+
|
|
165
|
+
for scheme in self._schemes:
|
|
166
|
+
logical_events.extend(scheme.handle(event))
|
|
167
|
+
|
|
168
|
+
# Polled input (axes, continuous values)
|
|
169
|
+
for scheme in self._schemes:
|
|
170
|
+
logical_events.extend(scheme.poll())
|
|
171
|
+
|
|
172
|
+
# Apply results
|
|
173
|
+
self._apply_input_events(logical_events)
|
|
174
|
+
|
|
175
|
+
return events
|
|
176
|
+
|
|
177
|
+
def pressed(self, button: Input, player: Player = Player.P1) -> bool:
|
|
178
|
+
"""Return True when the requested input has been newly pressed."""
|
|
179
|
+
return (
|
|
180
|
+
self._current[player][button] != 0.0
|
|
181
|
+
and self._previous[player][button] == 0.0
|
|
182
|
+
)
|
|
183
|
+
|
|
184
|
+
def held(self, button: Input, player: Player = Player.P1) -> bool:
|
|
185
|
+
"""Return True when the requested input has been held."""
|
|
186
|
+
return self._current[player][button] != 0.0
|
|
187
|
+
|
|
188
|
+
def released(self, button: Input, player: Player = Player.P1) -> bool:
|
|
189
|
+
"""Return True when the requested input is no longer held."""
|
|
190
|
+
return (
|
|
191
|
+
self._previous[player][button] != 0.0
|
|
192
|
+
and self._current[player][button] == 0.0
|
|
193
|
+
)
|
|
194
|
+
|
|
195
|
+
def value(self, button: Input, player: Player = Player.P1) -> float:
|
|
196
|
+
"""Return the value of the requested input."""
|
|
197
|
+
return self._current[player][button]
|
|
198
|
+
|
|
199
|
+
def get_joysticks(self):
|
|
200
|
+
return dict(self._joysticks)
|
|
201
|
+
|
|
202
|
+
def get_player_assignments(self) -> dict[int, Player]:
|
|
203
|
+
return self._players.mapping()
|
|
204
|
+
|
|
205
|
+
def get_player_for_joystick(self, instance_id: int) -> Player | None:
|
|
206
|
+
return self._players.mapping().get(instance_id)
|
|
207
|
+
|
|
208
|
+
def force_assign_controller(self, instance_id: int, player: Player) -> None:
|
|
209
|
+
self._players.force_assign(instance_id, player)
|
|
210
|
+
|
|
211
|
+
def set_value(
|
|
212
|
+
self, value: float, button: Input, player: Player = Player.P1
|
|
213
|
+
) -> None:
|
|
214
|
+
"""Set the value for the requested input."""
|
|
215
|
+
self._current[player][button] = value
|
|
216
|
+
|
|
217
|
+
def _apply_input_events(self, events: Iterable[InputEvent]) -> None:
|
|
218
|
+
for event in events:
|
|
219
|
+
self._current[event.player][event.button] = event.value
|
|
220
|
+
|
|
221
|
+
def _discover_joysticks(self) -> None:
|
|
222
|
+
for index in range(pygame.joystick.get_count()):
|
|
223
|
+
js = pygame.joystick.Joystick(index)
|
|
224
|
+
jid = js.get_instance_id()
|
|
225
|
+
|
|
226
|
+
self._joysticks[jid] = js
|
|
227
|
+
self._players.assign(jid)
|
|
228
|
+
|
|
229
|
+
LOG.info("Detected gamepad #%d: %s", jid, js.get_name())
|
|
230
|
+
|
|
231
|
+
def _handle_device_events(self, event: pygame.event.Event) -> None:
|
|
232
|
+
if event.type == pygame.JOYDEVICEADDED:
|
|
233
|
+
js = pygame.joystick.Joystick(event.device_index)
|
|
234
|
+
jid = js.get_instance_id()
|
|
235
|
+
|
|
236
|
+
self._joysticks[jid] = js
|
|
237
|
+
self._players.assign(jid)
|
|
238
|
+
LOG.info("New device detected: #%d %s", jid, js.get_name())
|
|
239
|
+
|
|
240
|
+
elif event.type == pygame.JOYDEVICEREMOVED:
|
|
241
|
+
self._joysticks.pop(event.instance_id, None)
|
|
242
|
+
self._players.release(event.instance_id)
|
|
243
|
+
LOG.info("Device removed: #%d", event.instance_id)
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
class KeyboardMapping:
|
|
247
|
+
def __init__(self, mapping: dict[Player, dict[Input, list[str]]]) -> None:
|
|
248
|
+
self.im: InputManager | None = None
|
|
249
|
+
self._mapping: dict[Player, dict[Input, list[str]]] = {}
|
|
250
|
+
self._reverse_mapping: dict[int, tuple[Input, Player]] = {}
|
|
251
|
+
for p, m in mapping.items():
|
|
252
|
+
self._mapping.setdefault(p, {})
|
|
253
|
+
for i, k in m.items():
|
|
254
|
+
self._mapping[p][i] = []
|
|
255
|
+
for val in k:
|
|
256
|
+
if len(val) > 1:
|
|
257
|
+
# text is uppercase, letters are lowercase
|
|
258
|
+
val = val.upper()
|
|
259
|
+
elif len(val) == 0:
|
|
260
|
+
val = val.lower()
|
|
261
|
+
key = getattr(pygame, f"K_{val}")
|
|
262
|
+
self._mapping[p][i].append(key)
|
|
263
|
+
self._reverse_mapping[key] = (i, p)
|
|
264
|
+
|
|
265
|
+
def handle(self, event: pygame.event.Event) -> list[InputEvent]:
|
|
266
|
+
actions = []
|
|
267
|
+
|
|
268
|
+
if event.type == pygame.KEYDOWN:
|
|
269
|
+
ip = self._reverse_mapping.get(event.key, ())
|
|
270
|
+
if ip and len(ip) == 2:
|
|
271
|
+
actions.append(InputEvent(button=ip[0], player=ip[1], value=1.0))
|
|
272
|
+
if event.type == pygame.KEYUP:
|
|
273
|
+
ip = self._reverse_mapping.get(event.key, ())
|
|
274
|
+
if ip and len(ip) == 2:
|
|
275
|
+
actions.append(InputEvent(button=ip[0], player=ip[1], value=0.0))
|
|
276
|
+
return actions
|
|
277
|
+
|
|
278
|
+
def poll(self) -> list[InputEvent]:
|
|
279
|
+
return []
|
|
280
|
+
|
|
281
|
+
|
|
282
|
+
@dataclass(frozen=True)
|
|
283
|
+
class JoyButton:
|
|
284
|
+
index: int
|
|
285
|
+
|
|
286
|
+
|
|
287
|
+
@dataclass(frozen=True)
|
|
288
|
+
class JoyAxis:
|
|
289
|
+
index: int
|
|
290
|
+
deadzone: float = 0.25
|
|
291
|
+
invert: bool = False
|
|
292
|
+
|
|
293
|
+
|
|
294
|
+
@dataclass(frozen=True)
|
|
295
|
+
class JoyHat:
|
|
296
|
+
hat: int # usually 0
|
|
297
|
+
axis: int # 0 = x, 1 = y
|
|
298
|
+
direction: int # -1 or +1
|
|
299
|
+
|
|
300
|
+
|
|
301
|
+
PhysicalInput: TypeAlias = JoyButton | JoyAxis | JoyHat
|
|
302
|
+
ControllerMapping: TypeAlias = dict[Input, list[PhysicalInput]]
|
|
303
|
+
|
|
304
|
+
|
|
305
|
+
@dataclass(frozen=True)
|
|
306
|
+
class ControllerProfile:
|
|
307
|
+
name: str
|
|
308
|
+
check: Callable[[pygame.joystick.JoystickType], bool]
|
|
309
|
+
mapping: ControllerMapping
|
|
310
|
+
|
|
311
|
+
|
|
312
|
+
class _ResolveMapping:
|
|
313
|
+
def __init__(self, mapping: ControllerMapping) -> None:
|
|
314
|
+
self._button_map: dict[int, list[Input]] = {}
|
|
315
|
+
self._hat_map: list[tuple[JoyHat, Input]] = []
|
|
316
|
+
self._axis_map: list[tuple[JoyAxis, Input]] = []
|
|
317
|
+
|
|
318
|
+
for logical, inputs in mapping.items():
|
|
319
|
+
for phys in inputs:
|
|
320
|
+
if isinstance(phys, JoyButton):
|
|
321
|
+
self._button_map.setdefault(phys.index, []).append(logical)
|
|
322
|
+
elif isinstance(phys, JoyHat):
|
|
323
|
+
self._hat_map.append((phys, logical))
|
|
324
|
+
elif isinstance(phys, JoyAxis):
|
|
325
|
+
self._axis_map.append((phys, logical))
|
|
326
|
+
|
|
327
|
+
|
|
328
|
+
class GamepadMapping:
|
|
329
|
+
"""Translatest joystick input into logical InputEvents."""
|
|
330
|
+
|
|
331
|
+
def __init__(self, profiles: list[ControllerProfile] | None = None) -> None:
|
|
332
|
+
self.im: InputManager | None = None
|
|
333
|
+
|
|
334
|
+
profiles = profiles or [
|
|
335
|
+
RETROID5_PROFILE,
|
|
336
|
+
XBOX_PROFILE,
|
|
337
|
+
PLAYSTATION_PROFILE,
|
|
338
|
+
GENERIC_PROFILE,
|
|
339
|
+
]
|
|
340
|
+
|
|
341
|
+
self._profiles: list[ControllerProfile] = profiles
|
|
342
|
+
|
|
343
|
+
# Per-joystick resolved mapping
|
|
344
|
+
self._resolved: dict[int, _ResolveMapping] = {}
|
|
345
|
+
|
|
346
|
+
def handle(self, event: pygame.event.Event) -> list[InputEvent]:
|
|
347
|
+
actions: list[InputEvent] = []
|
|
348
|
+
|
|
349
|
+
if not hasattr(event, "instance_id") or self.im is None:
|
|
350
|
+
return actions
|
|
351
|
+
|
|
352
|
+
jid = event.instance_id
|
|
353
|
+
player = self.im.get_player_for_joystick(jid)
|
|
354
|
+
if player is None:
|
|
355
|
+
return actions
|
|
356
|
+
|
|
357
|
+
mapping = self._get_mapping(jid)
|
|
358
|
+
if not mapping:
|
|
359
|
+
return actions
|
|
360
|
+
|
|
361
|
+
if event.type == pygame.JOYBUTTONDOWN:
|
|
362
|
+
for logical in mapping._button_map.get(event.button, []):
|
|
363
|
+
actions.append(InputEvent(logical, player, 1.0))
|
|
364
|
+
LOG.debug(
|
|
365
|
+
"Joy #%d button=%d, logical=%s player=%s",
|
|
366
|
+
event.instance_id,
|
|
367
|
+
event.button,
|
|
368
|
+
logical,
|
|
369
|
+
player,
|
|
370
|
+
)
|
|
371
|
+
|
|
372
|
+
if event.type == pygame.JOYBUTTONUP:
|
|
373
|
+
for logical in mapping._button_map.get(event.button, []):
|
|
374
|
+
actions.append(InputEvent(logical, player, 0.0))
|
|
375
|
+
|
|
376
|
+
if event.type == pygame.JOYHATMOTION:
|
|
377
|
+
for phys, logical in mapping._hat_map:
|
|
378
|
+
x, y = event.value
|
|
379
|
+
val = x if phys.axis == 0 else y
|
|
380
|
+
actions.append(
|
|
381
|
+
InputEvent(logical, player, 1.0 if val == phys.direction else 0.0)
|
|
382
|
+
)
|
|
383
|
+
LOG.debug(
|
|
384
|
+
"Joy #%d hat=%d, axis=%d, val=%d, logical=%s player=%s",
|
|
385
|
+
event.instance_id,
|
|
386
|
+
event.hat,
|
|
387
|
+
phys.axis,
|
|
388
|
+
val,
|
|
389
|
+
logical,
|
|
390
|
+
player,
|
|
391
|
+
)
|
|
392
|
+
|
|
393
|
+
return actions
|
|
394
|
+
|
|
395
|
+
def poll(self) -> list[InputEvent]:
|
|
396
|
+
actions: list[InputEvent] = []
|
|
397
|
+
if self.im is None:
|
|
398
|
+
return actions
|
|
399
|
+
|
|
400
|
+
for jid, js in self.im.get_joysticks().items():
|
|
401
|
+
player = self.im.get_player_for_joystick(jid)
|
|
402
|
+
if player is None:
|
|
403
|
+
continue
|
|
404
|
+
|
|
405
|
+
mapping = self._get_mapping(jid)
|
|
406
|
+
if not mapping:
|
|
407
|
+
continue
|
|
408
|
+
|
|
409
|
+
for phys, logical in mapping._axis_map:
|
|
410
|
+
try:
|
|
411
|
+
raw = js.get_axis(phys.index)
|
|
412
|
+
except Exception:
|
|
413
|
+
LOG.exception(
|
|
414
|
+
"Error querying axis %d for joysting #%d %s",
|
|
415
|
+
phys.index,
|
|
416
|
+
js.get_instance_id(),
|
|
417
|
+
js.get_name(),
|
|
418
|
+
)
|
|
419
|
+
continue
|
|
420
|
+
|
|
421
|
+
value = normalize_axis(raw, phys.deadzone, phys.invert)
|
|
422
|
+
actions.append(InputEvent(logical, player, value))
|
|
423
|
+
return actions
|
|
424
|
+
|
|
425
|
+
def _get_mapping(self, jid: int) -> _ResolveMapping | None:
|
|
426
|
+
if jid in self._resolved:
|
|
427
|
+
return self._resolved[jid]
|
|
428
|
+
|
|
429
|
+
if self.im is None:
|
|
430
|
+
return None
|
|
431
|
+
js = self.im.get_joysticks().get(jid)
|
|
432
|
+
if js is None:
|
|
433
|
+
return None
|
|
434
|
+
|
|
435
|
+
for profile in self._profiles:
|
|
436
|
+
if profile.check(js):
|
|
437
|
+
resolved = _ResolveMapping(profile.mapping)
|
|
438
|
+
self._resolved[jid] = resolved
|
|
439
|
+
return resolved
|
|
440
|
+
return None
|
|
441
|
+
|
|
442
|
+
|
|
443
|
+
def normalize_axis(value: float, deadzone: float, invert: bool) -> float:
|
|
444
|
+
if abs(value) < deadzone:
|
|
445
|
+
return 0.0
|
|
446
|
+
return -value if invert else value
|
|
447
|
+
|
|
448
|
+
|
|
449
|
+
class TouchMapping:
|
|
450
|
+
def handle(self, event: pygame.event.Event) -> list[InputEvent]:
|
|
451
|
+
return []
|
|
452
|
+
|
|
453
|
+
|
|
454
|
+
GENERIC_PROFILE = ControllerProfile(
|
|
455
|
+
name="Generic Gamepad",
|
|
456
|
+
check=lambda js: True, # Fallback
|
|
457
|
+
mapping={
|
|
458
|
+
Input.A: [JoyButton(0)],
|
|
459
|
+
Input.B: [JoyButton(1)],
|
|
460
|
+
Input.X: [JoyButton(2)],
|
|
461
|
+
Input.Y: [JoyButton(3)],
|
|
462
|
+
Input.L1: [JoyButton(4)],
|
|
463
|
+
Input.R1: [JoyButton(5)],
|
|
464
|
+
Input.SELECT: [JoyButton(6)],
|
|
465
|
+
Input.START: [JoyButton(7)],
|
|
466
|
+
Input.L3: [JoyButton(8)],
|
|
467
|
+
Input.R3: [JoyButton(9)],
|
|
468
|
+
Input.LEFT_STICK_X: [JoyAxis(0)],
|
|
469
|
+
Input.LEFT_STICK_Y: [JoyAxis(1)],
|
|
470
|
+
Input.RIGHT_STICK_X: [JoyAxis(2)],
|
|
471
|
+
Input.RIGHT_STICK_Y: [JoyAxis(3)],
|
|
472
|
+
Input.L2: [JoyAxis(4)],
|
|
473
|
+
Input.R2: [JoyAxis(5)],
|
|
474
|
+
Input.DPAD_LEFT: [JoyHat(0, 0, -1)],
|
|
475
|
+
Input.DPAD_RIGHT: [JoyHat(0, 0, 1)],
|
|
476
|
+
Input.DPAD_UP: [JoyHat(0, 1, 1)],
|
|
477
|
+
Input.DPAD_DOWN: [JoyHat(0, 1, -1)],
|
|
478
|
+
},
|
|
479
|
+
)
|
|
480
|
+
|
|
481
|
+
XBOX_PROFILE = ControllerProfile(
|
|
482
|
+
name="Xbox Controller",
|
|
483
|
+
check=lambda js: "xbox" in js.get_name().lower(),
|
|
484
|
+
mapping={
|
|
485
|
+
Input.A: [JoyButton(0)],
|
|
486
|
+
Input.B: [JoyButton(1)],
|
|
487
|
+
Input.X: [JoyButton(2)],
|
|
488
|
+
Input.Y: [JoyButton(3)],
|
|
489
|
+
Input.L1: [JoyButton(4)],
|
|
490
|
+
Input.R1: [JoyButton(5)],
|
|
491
|
+
Input.SELECT: [JoyButton(6)],
|
|
492
|
+
Input.START: [JoyButton(7)],
|
|
493
|
+
Input.L3: [JoyButton(8)],
|
|
494
|
+
Input.R3: [JoyButton(9)],
|
|
495
|
+
Input.LEFT_STICK_X: [JoyAxis(0)],
|
|
496
|
+
Input.LEFT_STICK_Y: [JoyAxis(1)],
|
|
497
|
+
Input.RIGHT_STICK_X: [JoyAxis(4)],
|
|
498
|
+
Input.RIGHT_STICK_Y: [JoyAxis(3)],
|
|
499
|
+
Input.L2: [JoyAxis(2)],
|
|
500
|
+
Input.R2: [JoyAxis(5)],
|
|
501
|
+
Input.DPAD_LEFT: [JoyHat(0, 0, -1)],
|
|
502
|
+
Input.DPAD_RIGHT: [JoyHat(0, 0, 1)],
|
|
503
|
+
Input.DPAD_UP: [JoyHat(0, 1, 1)],
|
|
504
|
+
Input.DPAD_DOWN: [JoyHat(0, 1, -1)],
|
|
505
|
+
},
|
|
506
|
+
)
|
|
507
|
+
|
|
508
|
+
PLAYSTATION_PROFILE = ControllerProfile(
|
|
509
|
+
name="PlayStation Controller",
|
|
510
|
+
check=lambda js: any(
|
|
511
|
+
k in js.get_name().lower()
|
|
512
|
+
for k in ("playstation", "dualshock", "dualsense", "ps4", "ps5")
|
|
513
|
+
),
|
|
514
|
+
mapping={
|
|
515
|
+
Input.A: [JoyButton(1)], # Cross
|
|
516
|
+
Input.B: [JoyButton(2)], # Circle
|
|
517
|
+
Input.X: [JoyButton(0)], # Square
|
|
518
|
+
Input.Y: [JoyButton(3)], # Triangle
|
|
519
|
+
Input.L1: [JoyButton(4)],
|
|
520
|
+
Input.R1: [JoyButton(5)],
|
|
521
|
+
Input.SELECT: [JoyButton(8)],
|
|
522
|
+
Input.START: [JoyButton(9)],
|
|
523
|
+
Input.L3: [JoyButton(10)],
|
|
524
|
+
Input.R3: [JoyButton(11)],
|
|
525
|
+
Input.LEFT_STICK_X: [JoyAxis(0)],
|
|
526
|
+
Input.LEFT_STICK_Y: [JoyAxis(1)],
|
|
527
|
+
Input.RIGHT_STICK_X: [JoyAxis(2)],
|
|
528
|
+
Input.RIGHT_STICK_Y: [JoyAxis(5)],
|
|
529
|
+
Input.L2: [JoyAxis(3)],
|
|
530
|
+
Input.R2: [JoyAxis(4)],
|
|
531
|
+
Input.DPAD_LEFT: [JoyHat(0, 0, -1)],
|
|
532
|
+
Input.DPAD_RIGHT: [JoyHat(0, 0, 1)],
|
|
533
|
+
Input.DPAD_UP: [JoyHat(0, 1, 1)],
|
|
534
|
+
Input.DPAD_DOWN: [JoyHat(0, 1, -1)],
|
|
535
|
+
},
|
|
536
|
+
)
|
|
537
|
+
|
|
538
|
+
RETROID5_PROFILE = ControllerProfile(
|
|
539
|
+
name="Retroid Pocket Controller",
|
|
540
|
+
check=lambda js: "retroid" in js.get_name().lower(),
|
|
541
|
+
mapping={
|
|
542
|
+
Input.A: [JoyButton(0)],
|
|
543
|
+
Input.B: [JoyButton(1)],
|
|
544
|
+
Input.X: [JoyButton(2)],
|
|
545
|
+
Input.Y: [JoyButton(3)],
|
|
546
|
+
Input.L1: [JoyButton(9)],
|
|
547
|
+
Input.R1: [JoyButton(10)],
|
|
548
|
+
Input.SELECT: [JoyButton(4)],
|
|
549
|
+
Input.START: [JoyButton(6)],
|
|
550
|
+
Input.L3: [JoyButton(7)],
|
|
551
|
+
Input.R3: [JoyButton(8)],
|
|
552
|
+
Input.LEFT_STICK_X: [JoyAxis(0)],
|
|
553
|
+
Input.LEFT_STICK_Y: [JoyAxis(1)],
|
|
554
|
+
Input.RIGHT_STICK_X: [JoyAxis(4)],
|
|
555
|
+
Input.RIGHT_STICK_Y: [JoyAxis(3)],
|
|
556
|
+
Input.L2: [JoyButton(15)],
|
|
557
|
+
Input.R2: [JoyButton(16)],
|
|
558
|
+
Input.DPAD_LEFT: [JoyButton(13)],
|
|
559
|
+
Input.DPAD_RIGHT: [JoyButton(14)],
|
|
560
|
+
Input.DPAD_UP: [JoyButton(11)],
|
|
561
|
+
Input.DPAD_DOWN: [JoyButton(12)],
|
|
562
|
+
},
|
|
563
|
+
)
|
mima/states/__init__.py
ADDED
|
File without changes
|