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.
Files changed (153) hide show
  1. mima/__init__.py +4 -0
  2. mima/backend/__init__.py +1 -0
  3. mima/backend/pygame_assets.py +401 -0
  4. mima/backend/pygame_audio.py +78 -0
  5. mima/backend/pygame_backend.py +603 -0
  6. mima/backend/pygame_camera.py +63 -0
  7. mima/backend/pygame_events.py +695 -0
  8. mima/backend/touch_control_scheme_a.py +126 -0
  9. mima/backend/touch_control_scheme_b.py +132 -0
  10. mima/core/__init__.py +0 -0
  11. mima/core/collision.py +325 -0
  12. mima/core/database.py +58 -0
  13. mima/core/engine.py +367 -0
  14. mima/core/mode_engine.py +81 -0
  15. mima/core/scene_engine.py +81 -0
  16. mima/integrated/__init__.py +0 -0
  17. mima/integrated/entity.py +183 -0
  18. mima/integrated/layered_map.py +351 -0
  19. mima/integrated/sprite.py +156 -0
  20. mima/layered/__init__.py +0 -0
  21. mima/layered/assets.py +56 -0
  22. mima/layered/scene.py +415 -0
  23. mima/layered/shape.py +99 -0
  24. mima/layered/shaped_sprite.py +78 -0
  25. mima/layered/virtual_input.py +302 -0
  26. mima/maps/__init__.py +0 -0
  27. mima/maps/template.py +71 -0
  28. mima/maps/tile.py +20 -0
  29. mima/maps/tile_animation.py +7 -0
  30. mima/maps/tile_info.py +10 -0
  31. mima/maps/tile_layer.py +52 -0
  32. mima/maps/tiled/__init__.py +0 -0
  33. mima/maps/tiled/tiled_layer.py +48 -0
  34. mima/maps/tiled/tiled_map.py +95 -0
  35. mima/maps/tiled/tiled_object.py +79 -0
  36. mima/maps/tiled/tiled_objectgroup.py +25 -0
  37. mima/maps/tiled/tiled_template.py +49 -0
  38. mima/maps/tiled/tiled_tile.py +90 -0
  39. mima/maps/tiled/tiled_tileset.py +51 -0
  40. mima/maps/tilemap.py +216 -0
  41. mima/maps/tileset.py +39 -0
  42. mima/maps/tileset_info.py +9 -0
  43. mima/maps/transition_map.py +146 -0
  44. mima/objects/__init__.py +0 -0
  45. mima/objects/animated_sprite.py +217 -0
  46. mima/objects/attribute_effect.py +26 -0
  47. mima/objects/attributes.py +126 -0
  48. mima/objects/creature.py +384 -0
  49. mima/objects/dynamic.py +206 -0
  50. mima/objects/effects/__init__.py +0 -0
  51. mima/objects/effects/colorize_screen.py +60 -0
  52. mima/objects/effects/debug_box.py +133 -0
  53. mima/objects/effects/light.py +103 -0
  54. mima/objects/effects/show_sprite.py +50 -0
  55. mima/objects/effects/walking_on_grass.py +70 -0
  56. mima/objects/effects/walking_on_water.py +57 -0
  57. mima/objects/loader.py +111 -0
  58. mima/objects/projectile.py +111 -0
  59. mima/objects/sprite.py +116 -0
  60. mima/objects/world/__init__.py +0 -0
  61. mima/objects/world/color_gate.py +67 -0
  62. mima/objects/world/color_switch.py +101 -0
  63. mima/objects/world/container.py +175 -0
  64. mima/objects/world/floor_switch.py +109 -0
  65. mima/objects/world/gate.py +178 -0
  66. mima/objects/world/light_source.py +121 -0
  67. mima/objects/world/logic_gate.py +157 -0
  68. mima/objects/world/movable.py +399 -0
  69. mima/objects/world/oneway.py +195 -0
  70. mima/objects/world/pickup.py +157 -0
  71. mima/objects/world/switch.py +179 -0
  72. mima/objects/world/teleport.py +308 -0
  73. mima/py.typed +0 -0
  74. mima/scripts/__init__.py +2 -0
  75. mima/scripts/command.py +38 -0
  76. mima/scripts/commands/__init__.py +0 -0
  77. mima/scripts/commands/add_quest.py +19 -0
  78. mima/scripts/commands/change_map.py +34 -0
  79. mima/scripts/commands/close_dialog.py +9 -0
  80. mima/scripts/commands/equip_weapon.py +23 -0
  81. mima/scripts/commands/give_item.py +26 -0
  82. mima/scripts/commands/give_resource.py +51 -0
  83. mima/scripts/commands/move_map.py +152 -0
  84. mima/scripts/commands/move_to.py +49 -0
  85. mima/scripts/commands/oneway_move.py +58 -0
  86. mima/scripts/commands/parallel.py +66 -0
  87. mima/scripts/commands/play_sound.py +13 -0
  88. mima/scripts/commands/present_item.py +53 -0
  89. mima/scripts/commands/progress_quest.py +12 -0
  90. mima/scripts/commands/quit_game.py +8 -0
  91. mima/scripts/commands/save_game.py +14 -0
  92. mima/scripts/commands/screen_fade.py +83 -0
  93. mima/scripts/commands/serial.py +69 -0
  94. mima/scripts/commands/set_facing_direction.py +21 -0
  95. mima/scripts/commands/set_spawn_map.py +17 -0
  96. mima/scripts/commands/show_choices.py +52 -0
  97. mima/scripts/commands/show_dialog.py +118 -0
  98. mima/scripts/commands/take_coins.py +23 -0
  99. mima/scripts/script_processor.py +61 -0
  100. mima/standalone/__init__.py +0 -0
  101. mima/standalone/camera.py +153 -0
  102. mima/standalone/geometry.py +1318 -0
  103. mima/standalone/multicolumn_list.py +54 -0
  104. mima/standalone/pixel_font.py +84 -0
  105. mima/standalone/scripting.py +145 -0
  106. mima/standalone/spatial.py +186 -0
  107. mima/standalone/sprite.py +158 -0
  108. mima/standalone/tiled_map.py +1247 -0
  109. mima/standalone/transformed_view.py +433 -0
  110. mima/standalone/user_input.py +563 -0
  111. mima/states/__init__.py +0 -0
  112. mima/states/game_state.py +189 -0
  113. mima/states/memory.py +28 -0
  114. mima/states/quest.py +71 -0
  115. mima/types/__init__.py +0 -0
  116. mima/types/alignment.py +7 -0
  117. mima/types/blend.py +8 -0
  118. mima/types/damage.py +42 -0
  119. mima/types/direction.py +44 -0
  120. mima/types/gate_color.py +7 -0
  121. mima/types/graphic_state.py +23 -0
  122. mima/types/keys.py +64 -0
  123. mima/types/mode.py +9 -0
  124. mima/types/nature.py +12 -0
  125. mima/types/object.py +22 -0
  126. mima/types/player.py +9 -0
  127. mima/types/position.py +13 -0
  128. mima/types/start.py +7 -0
  129. mima/types/terrain.py +9 -0
  130. mima/types/tile_collision.py +11 -0
  131. mima/types/weapon_slot.py +6 -0
  132. mima/types/window.py +44 -0
  133. mima/usables/__init__.py +0 -0
  134. mima/usables/item.py +51 -0
  135. mima/usables/weapon.py +68 -0
  136. mima/util/__init__.py +1 -0
  137. mima/util/colors.py +50 -0
  138. mima/util/constants.py +55 -0
  139. mima/util/functions.py +38 -0
  140. mima/util/input_defaults.py +170 -0
  141. mima/util/logging.py +51 -0
  142. mima/util/property.py +8 -0
  143. mima/util/runtime_config.py +327 -0
  144. mima/util/trading_item.py +23 -0
  145. mima/view/__init__.py +0 -0
  146. mima/view/camera.py +192 -0
  147. mima/view/mima_mode.py +618 -0
  148. mima/view/mima_scene.py +231 -0
  149. mima/view/mima_view.py +12 -0
  150. mima/view/mima_window.py +244 -0
  151. mima_engine-0.4.0.dist-info/METADATA +47 -0
  152. mima_engine-0.4.0.dist-info/RECORD +153 -0
  153. 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
+ )
File without changes