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
mima/view/mima_mode.py ADDED
@@ -0,0 +1,618 @@
1
+ from __future__ import annotations
2
+
3
+ import logging
4
+ import math
5
+ from typing import TYPE_CHECKING, Dict, List, Optional, Tuple
6
+
7
+ from ..core.collision import (
8
+ _chunk_index,
9
+ add_to_collision_chunk,
10
+ check_object_to_map_collision,
11
+ check_object_to_object_collision,
12
+ )
13
+ from ..objects.effects.debug_box import DynamicDebugBox, StaticDebugBox
14
+ from ..objects.loader import ObjectLoader
15
+ from ..types.nature import Nature
16
+ from ..types.player import Player
17
+ from ..types.position import Position
18
+ from ..types.tile_collision import TileCollision
19
+ from ..util.colors import (
20
+ TRANS_LIGHT_CYAN,
21
+ TRANS_LIGHT_GREEN,
22
+ TRANS_LIGHT_PURPLE,
23
+ TRANS_LIGHT_YELLOW,
24
+ )
25
+ from .mima_scene import MimaScene
26
+ from .mima_view import MimaView
27
+ from .mima_window import MimaWindow
28
+
29
+ if TYPE_CHECKING:
30
+ from ..maps.tilemap import Tilemap
31
+ from ..objects.dynamic import Dynamic
32
+ from ..objects.projectile import Projectile
33
+ from ..types.window import Window
34
+
35
+ LOG = logging.getLogger(__name__)
36
+
37
+
38
+ class MimaMode(MimaView):
39
+ """Base class for game modes"""
40
+
41
+ def __init__(self) -> None:
42
+ self.scenes: Dict[Player, MimaScene] = {}
43
+ self.dynamics: Dict[str, List[Dynamic]] = {}
44
+ self.projectiles: Dict[str, List[Projectile]] = {}
45
+ self.effects: Dict[str, List[Projectile]] = {}
46
+ self.maps: Dict[Player, Tilemap] = {}
47
+ self.players_on_map: Dict[str, List[Player]] = {}
48
+ self.dialog_to_show: Dict[Player, List[str]] = {}
49
+ self.collision_targets: Dict[str, Dict[int, List[Dynamic]]] = {}
50
+ self.colliders: Dict[str, Dict[int, List[Dynamic]]] = {}
51
+ self.chunk_size = 4
52
+
53
+ self._clear_color = self.engine.rtc.color_black
54
+ self._loader = ObjectLoader({}, {})
55
+ self.tile_collision = TileCollision.TOP
56
+
57
+ def load(self) -> bool:
58
+ """Load the scenes for this mode.
59
+
60
+ Overwrite this if needed!
61
+ """
62
+ self.scenes[Player.P1] = MimaScene(Player.P1, Position.CENTER)
63
+ self.populate_scenes(MimaWindow)
64
+
65
+ return True
66
+
67
+ def unload(self) -> None:
68
+ pass
69
+
70
+ def update(self, elapsed_time: float) -> bool:
71
+ self.housekeeping(elapsed_time)
72
+ self.handle_user_input()
73
+
74
+ self.update_maps(elapsed_time)
75
+ self.update_objects(elapsed_time)
76
+ self.update_scenes(elapsed_time)
77
+
78
+ self.engine.cameras = [s.camera.name for s in self.scenes.values()]
79
+
80
+ self.draw_map_and_objects()
81
+ if not self.engine.disable_filter:
82
+ self.engine.backend.apply_filter("all")
83
+
84
+ self.draw_scenes()
85
+
86
+ self.display_dialog()
87
+
88
+ return True
89
+
90
+ def get_camera_name(self, player: Player = Player.P1):
91
+ return self.scenes[player].camera.name
92
+
93
+ def unload_map(
94
+ self, player: Player = Player.P1, player_only: bool = False
95
+ ) -> None:
96
+ p_obj = self.engine.memory.player[player]
97
+ map_name = p_obj.tilemap.name
98
+
99
+ if player in self.players_on_map[map_name]:
100
+ self.players_on_map[map_name].remove(player)
101
+
102
+ if p_obj in self.dynamics[map_name]:
103
+ # Is not the case on Game Over
104
+ self.dynamics[map_name].remove(p_obj)
105
+ self.maps[player] = None
106
+
107
+ if not player_only and not self.players_on_map[map_name]:
108
+ # No more players on the map
109
+ for obj in self.dynamics[map_name]:
110
+ obj.kill()
111
+ for obj in self.projectiles[map_name] + self.effects[map_name]:
112
+ obj.cancel()
113
+
114
+ del self.players_on_map[map_name]
115
+ del self.dynamics[map_name]
116
+ del self.projectiles[map_name]
117
+ del self.effects[map_name]
118
+ self._unload_collision_chunks(map_name)
119
+ else:
120
+ for chid in p_obj.chunks:
121
+ if p_obj in self.collision_targets[map_name][chid]:
122
+ self.collision_targets[map_name][chid].remove(p_obj)
123
+
124
+ def prepare_object_lists(
125
+ self,
126
+ map_name: str,
127
+ player: Player = Player.P1,
128
+ p_obj: Optional[Dynamic] = None,
129
+ ):
130
+ self.maps[player] = self.engine.assets.get_map(map_name)
131
+ self.players_on_map[map_name] = [player]
132
+ self.dynamics[map_name] = []
133
+ self.projectiles[map_name] = []
134
+ self.effects[map_name] = []
135
+
136
+ if p_obj is not None:
137
+ self.dynamics[map_name].append(p_obj)
138
+
139
+ def load_map(
140
+ self, map_name: str, px: float, py: float, player: Player = Player.P1
141
+ ) -> None:
142
+ p_obj = self.engine.get_player(player)
143
+ p_obj.stop_shining()
144
+ p_obj.on_exit_map()
145
+
146
+ if map_name not in self.players_on_map:
147
+ # Map not loaded
148
+ self.prepare_object_lists(map_name, player, p_obj)
149
+
150
+ self._loader.populate_dynamics(
151
+ self.maps[player], self.dynamics[map_name]
152
+ )
153
+
154
+ for quest in self.engine.memory.quests:
155
+ quest.populate_dynamics(self.dynamics[map_name], map_name)
156
+ self._load_collision_targets(map_name)
157
+ else:
158
+ # Map already loaded
159
+ self.maps[player] = self.engine.assets.get_map(map_name)
160
+ if player not in self.players_on_map[map_name]:
161
+ self.players_on_map[map_name].append(player)
162
+ if p_obj not in self.dynamics[map_name]:
163
+ idx = len(self.players_on_map[map_name]) - 1
164
+ self.dynamics[map_name].insert(idx, p_obj)
165
+
166
+ p_obj.tilemap = self.maps[player]
167
+ p_obj.px = px
168
+ p_obj.py = py
169
+ p_obj.on_enter_map()
170
+ self.engine.memory.last_spawn_px[player] = px
171
+ self.engine.memory.last_spawn_py[player] = py
172
+
173
+ def _unload_collision_chunks(self, map_name):
174
+ del self.collision_targets[map_name]
175
+ del self.colliders[map_name]
176
+
177
+ def _load_collision_targets(self, map_name):
178
+ self.collision_targets[map_name] = {}
179
+ tilemap = self.engine.get_map(map_name)
180
+ chunks_per_row = math.ceil(tilemap.width / self.chunk_size) + 1
181
+
182
+ for obj in self.dynamics[map_name]:
183
+ if obj.moves_on_collision:
184
+ continue
185
+ else:
186
+ # self.collision_targets[map_name]
187
+ obj.chunks = add_to_collision_chunk(
188
+ self.collision_targets[map_name],
189
+ obj,
190
+ self.chunk_size,
191
+ chunks_per_row,
192
+ )
193
+ if self.engine.draw_chunk_info:
194
+ self.add_effect(
195
+ DynamicDebugBox(obj, n_frames=-1), map_name
196
+ )
197
+
198
+ def handle_user_input(self):
199
+ for player, scene in self.scenes.items():
200
+ scene.handle_user_input()
201
+
202
+ def housekeeping(self, elapsed_time: float) -> None:
203
+ self.engine.script.process_command(elapsed_time)
204
+ self.delete_redundant_objects()
205
+ self.save_to_game_state()
206
+
207
+ self.engine.backend.clear()
208
+
209
+ def save_to_game_state(self):
210
+ for quest in self.engine.memory.quests:
211
+ for map_name in self.players_on_map:
212
+ for obj in self.dynamics[map_name]:
213
+ try:
214
+ quest.on_interaction(obj, Nature.SAVE, obj.player)
215
+ except Exception:
216
+ if quest is not None and hasattr(quest, "name"):
217
+ msg = (
218
+ f"Error for quest {quest.name} "
219
+ f"({type(quest)}) while interacting with "
220
+ )
221
+ if obj is None:
222
+ msg += "None-object."
223
+ else:
224
+ msg += f"object {obj.name} ({obj.dyn_id})"
225
+ else:
226
+ msg = (
227
+ "Trying to interact with a quest that is None"
228
+ )
229
+
230
+ LOG.exception(msg)
231
+ raise
232
+
233
+ quest.save_state()
234
+
235
+ def delete_redundant_objects(self):
236
+ # TODO Handle player game over
237
+ # Find and erase redundant dynamics
238
+ for map_name in self.players_on_map:
239
+ d2rm = [d for d in self.dynamics[map_name] if d.redundant]
240
+ for dyn in d2rm:
241
+ for quest in self.engine.quests:
242
+ quest.on_interaction(dyn, Nature.KILLED, Player.P0)
243
+ dyn.on_death()
244
+ for chid in dyn.chunks:
245
+ self.collision_targets[map_name][chid].remove(dyn)
246
+ self.dynamics[map_name].remove(dyn)
247
+
248
+ # Find and erase redundant projectiles
249
+ p2rm = [p for p in self.projectiles[map_name] if p.redundant]
250
+ for pro in p2rm:
251
+ pro.on_death()
252
+ for chid in pro.chunks:
253
+ self.collision_targets[map_name][chid].remove(pro)
254
+ self.projectiles[map_name].remove(pro)
255
+
256
+ # Find and erase redundant effects
257
+ e2rm = [e for e in self.effects[map_name] if e.redundant]
258
+ for eff in e2rm:
259
+ eff.on_death()
260
+ self.effects[map_name].remove(eff)
261
+
262
+ # Find and erase completed quests
263
+ q2rm = [q for q in self.engine.quests if q.completed]
264
+ for quest in q2rm:
265
+ self.engine.quests.remove(quest)
266
+
267
+ def update_maps(self, elapsed_time: float) -> None:
268
+ for tmap in list(set(self.maps.values())):
269
+ if tmap is not None:
270
+ tmap.trigger_new_frame()
271
+ for tmap in list(set(self.maps.values())):
272
+ if tmap is not None:
273
+ tmap.update(elapsed_time)
274
+
275
+ def update_objects(self, elapsed_time: float) -> None:
276
+ self.update_dynamics(elapsed_time)
277
+ # print(collision_lists)
278
+ self.handle_collisions(elapsed_time)
279
+ self.update_effects(elapsed_time)
280
+
281
+ def update_dynamics(self, elapsed_time):
282
+ colors = [
283
+ [TRANS_LIGHT_YELLOW, TRANS_LIGHT_GREEN],
284
+ [TRANS_LIGHT_CYAN, TRANS_LIGHT_PURPLE],
285
+ ]
286
+ for map_name, players in self.players_on_map.items():
287
+ # if not players:
288
+ # continue
289
+ # self.colliders[map_name] = {}
290
+ tilemap = self.engine.get_map(map_name)
291
+ chunks_per_row = math.ceil(tilemap.width / self.chunk_size) + 1
292
+ if self.engine.draw_chunks:
293
+ for py in range(
294
+ -self.chunk_size // 2,
295
+ tilemap.height + self.chunk_size // 2,
296
+ self.chunk_size,
297
+ ):
298
+ for px in range(
299
+ -self.chunk_size // 2,
300
+ tilemap.width + self.chunk_size // 2,
301
+ self.chunk_size,
302
+ ):
303
+ chidx = _chunk_index(
304
+ px, py, self.chunk_size, chunks_per_row
305
+ )
306
+ # collision_lists[map_name][chidx] = []
307
+ self.add_effect(
308
+ StaticDebugBox(
309
+ px,
310
+ py,
311
+ self.chunk_size,
312
+ self.chunk_size,
313
+ colors[(py // self.chunk_size) % 2][
314
+ (px // self.chunk_size) % 2
315
+ ],
316
+ ids=[chidx],
317
+ ),
318
+ map_name,
319
+ )
320
+ # print((py // chunk_size) % 2, (px // chunk_size) % 2)
321
+ self.colliders[map_name] = []
322
+ for obj in self.dynamics[map_name] + self.projectiles[map_name]:
323
+ if obj.occupied:
324
+ continue
325
+ target = self._determine_target(obj, players)
326
+ # if obj.update_skippable and dist_to_target > max_dist:
327
+ # continue
328
+
329
+ self._update_velocity_z(obj, elapsed_time)
330
+ obj.update(elapsed_time, target)
331
+ self._update_position_z(obj, elapsed_time)
332
+
333
+ if obj.moves_on_collision: # or dist_to_target < max_dist:
334
+ obj.chunks = add_to_collision_chunk(
335
+ self.collision_targets[map_name],
336
+ obj,
337
+ self.chunk_size,
338
+ chunks_per_row,
339
+ )
340
+ self.colliders[map_name].append(obj)
341
+
342
+ if self.engine.draw_chunk_info:
343
+ self.add_effect(DynamicDebugBox(obj), map_name)
344
+
345
+ # return collision_lists
346
+
347
+ def _determine_target(self, obj, players) -> Dynamic:
348
+ players = [
349
+ p for p in players if not self.engine.get_player(p).occupied
350
+ ]
351
+ if not players:
352
+ return None
353
+ dists = [
354
+ [
355
+ p,
356
+ abs(obj.px - self.engine.memory.player[p].px)
357
+ + abs(obj.py - self.engine.memory.player[p].py),
358
+ ]
359
+ for p in players
360
+ ]
361
+ dists.sort(key=lambda x: x[1])
362
+
363
+ if len(players) > 1:
364
+ target = self.engine.memory.player[dists[0][0]]
365
+ else:
366
+ target = self.engine.memory.player[players[0]]
367
+
368
+ return target # , skip
369
+
370
+ def _update_velocity_z(self, obj: Dynamic, elapsed_time: float) -> None:
371
+ if obj.pz > 0.0:
372
+ obj.vz -= obj.attributes.gravity_vz * elapsed_time
373
+ # else:
374
+ # obj.vz = 0.0
375
+
376
+ def _update_position_z(self, obj: Dynamic, elapsed_time: float) -> None:
377
+ if obj.gravity:
378
+ obj.pz = obj.pz + obj.vz * elapsed_time
379
+ if obj.pz <= 0.0:
380
+ obj.pz = 0.0
381
+ obj.vz = 0.0
382
+
383
+ def handle_collisions(self, elapsed_time: float) -> None:
384
+ screen_width = (
385
+ self.engine.backend.render_width // self.engine.rtc.tile_width
386
+ )
387
+ screen_height = (
388
+ self.engine.backend.render_height // self.engine.rtc.tile_height
389
+ )
390
+ max_dist = screen_width + screen_height
391
+
392
+ for map_name, players in self.players_on_map.items():
393
+ # print(f"Collisions for {map_name}: {self.colliders[map_name]}")
394
+ for obj in self.colliders[map_name]:
395
+ if obj.occupied:
396
+ continue
397
+ objects = []
398
+ for chid in obj.chunks:
399
+ objects.extend(self.collision_targets[map_name][chid])
400
+ objects = list(set(objects))
401
+ objects.remove(obj)
402
+ objects.insert(0, obj)
403
+
404
+ if self._check_collision_skippable(obj, players, max_dist):
405
+ continue
406
+
407
+ new_px, new_py = self.update_position(obj, elapsed_time)
408
+ new_px, new_py = check_object_to_map_collision(
409
+ elapsed_time,
410
+ obj,
411
+ self.maps[self.players_on_map[map_name][0]],
412
+ new_px,
413
+ new_py,
414
+ collision=self.tile_collision,
415
+ )
416
+ if self.check_tile_properties(obj, map_name, new_px, new_py):
417
+ # If true, something happened to the object
418
+ continue
419
+ if len(objects) > 1: # and obj.moves_on_collision:
420
+ for other in objects:
421
+ if other == obj:
422
+ continue
423
+ args = [
424
+ obj,
425
+ new_px,
426
+ new_py,
427
+ other,
428
+ self.deal_damage,
429
+ self.engine.memory.quests,
430
+ ]
431
+ if self._probe_p2p_collision(obj, other):
432
+ new2_px, new2_py = (
433
+ check_object_to_object_collision(*args)
434
+ )
435
+
436
+ if new2_px == new_px and new2_py == new_py:
437
+ # No change = no collision
438
+ self.engine.trigger_player_collision(
439
+ True, obj.player
440
+ )
441
+ else:
442
+ new_px, new_py = check_object_to_object_collision(
443
+ *args
444
+ )
445
+ obj.px = new_px
446
+ obj.py = new_py
447
+
448
+ def _probe_p2p_collision(self, obj, other):
449
+ return (
450
+ obj.player.value > 0
451
+ and other.player.value > 0
452
+ and (
453
+ not self.engine.is_player_collision_active(obj.player)
454
+ or not self.engine.is_player_collision_active(other.player)
455
+ )
456
+ )
457
+
458
+ def update_position(self, obj: Dynamic, elapsed_time: float):
459
+ vx, vy = min(1, max(-1, obj.vx)), min(1, max(-1, obj.vy))
460
+
461
+ # Diagonal movement
462
+ if obj.vx != 0 and obj.vy != 0:
463
+ vx, vy = vx * 0.707, vy * 0.707
464
+
465
+ obj.real_vx = (
466
+ vx
467
+ if vx == obj.real_vx
468
+ else calculate(obj, vx, obj.real_vx, elapsed_time)
469
+ )
470
+ obj.real_vy = (
471
+ vy
472
+ if vy == obj.real_vy
473
+ else calculate(obj, vy, obj.real_vy, elapsed_time)
474
+ )
475
+
476
+ new_px = (
477
+ obj.px
478
+ + obj.real_vx * obj.speed * obj.attributes.speed_mod * elapsed_time
479
+ )
480
+ new_py = (
481
+ obj.py
482
+ + obj.real_vy * obj.speed * obj.attributes.speed_mod * elapsed_time
483
+ )
484
+
485
+ return new_px, new_py
486
+
487
+ def _check_collision_skippable(self, obj, players, max_dist) -> bool:
488
+ # if not obj.moves_on_collision:
489
+ # return True
490
+ # dists = [
491
+ # [
492
+ # p,
493
+ # abs(obj.px - self.engine.memory.player[p].px)
494
+ # + abs(obj.py - self.engine.memory.player[p].py),
495
+ # ]
496
+ # for p in players
497
+ # ]
498
+ # dists.sort(key=lambda x: x[1])
499
+
500
+ # if obj.offscreen_collision_skippable and dists[0][1] > max_dist:
501
+ # return True
502
+
503
+ return False
504
+
505
+ def check_tile_properties(self, obj, map_name, new_px, new_py):
506
+ return False
507
+
508
+ def update_effects(self, elapsed_time: float) -> None:
509
+ for map_name in self.players_on_map:
510
+ for effect in self.effects[map_name]:
511
+ effect.update(elapsed_time)
512
+ effect.px, effect.py = self.update_position(
513
+ effect, elapsed_time
514
+ )
515
+
516
+ def update_scenes(self, elapsed_time: float) -> None:
517
+ for player, scene in self.scenes.items():
518
+ if self.maps[player] is None:
519
+ map_width = map_height = 0
520
+ else:
521
+ map_width = self.maps[player].width
522
+ map_height = self.maps[player].height
523
+
524
+ scene.update(
525
+ elapsed_time,
526
+ target=self.engine.get_player(player),
527
+ map_width=map_width,
528
+ map_height=map_height,
529
+ )
530
+
531
+ def add_dynamic(self, dynamic: Dynamic, map_name: str):
532
+ self.dynamics[map_name].append(dynamic)
533
+ if (
534
+ map_name in self.collision_targets
535
+ and not dynamic.moves_on_collision
536
+ ):
537
+ dynamic.chunks = add_to_collision_chunk(
538
+ self.collision_targets[map_name],
539
+ dynamic,
540
+ self.chunk_size,
541
+ _chunks_per_row(
542
+ self.chunk_size, self.engine.get_map(map_name).width
543
+ ),
544
+ )
545
+
546
+ def add_projectile(self, projectile: Projectile, map_name: str):
547
+ self.projectiles[map_name].append(projectile)
548
+
549
+ def add_effect(self, projectile: Projectile, map_name: str):
550
+ self.effects[map_name].append(projectile)
551
+
552
+ def draw_map_and_objects(self):
553
+ # print(self.dynamics)
554
+ for player, scene in self.scenes.items():
555
+ tmap = self.maps[player]
556
+ if tmap is None:
557
+ continue
558
+
559
+ scene.draw_map_and_objects(
560
+ player,
561
+ scene.camera,
562
+ tmap,
563
+ self.dynamics[tmap.name],
564
+ self.projectiles[tmap.name],
565
+ self.effects[tmap.name],
566
+ )
567
+
568
+ def draw_scenes(self):
569
+ for scene in self.scenes.values():
570
+ scene.draw_ui()
571
+ scene.draw_camera_border()
572
+
573
+ def deal_damage(self, aggressor: Projectile, victim: Dynamic) -> None:
574
+ pass
575
+
576
+ def show_dialog(self, lines: List[str], player: Player):
577
+ if player == Player.P0:
578
+ for p in self.scenes:
579
+ self.dialog_to_show[p] = lines
580
+ self.engine.trigger_dialog(True, p)
581
+ else:
582
+ self.dialog_to_show[player] = lines
583
+ self.engine.trigger_dialog(True, player)
584
+
585
+ def display_dialog(self):
586
+ for player, scene in self.scenes.items():
587
+ if self.engine.is_dialog_active(player):
588
+ scene.display_dialog(self.dialog_to_show[player])
589
+
590
+ def add_window(self, window: Window, player: Player, additional_data=None):
591
+ self.scenes[player].window_stack.append(window)
592
+ self.scenes[player].windows[window].additional_data = additional_data
593
+
594
+ def populate_scenes(self, *windows: List[MimaWindow]):
595
+ for scene in self.scenes.values():
596
+ for idx, window_class in enumerate(windows):
597
+ window = window_class(self, scene)
598
+ scene.windows[window.wtype] = window
599
+ if idx == 0:
600
+ scene.window_stack.append(window.wtype)
601
+
602
+
603
+ def calculate(obj, v, real_v, elapsed_time):
604
+ if v == 0 and obj.use_friction:
605
+ mod = obj.attributes.friction
606
+ elif v != 0 and obj.use_acceleration:
607
+ mod = obj.attributes.acceleration
608
+ else:
609
+ return v
610
+
611
+ dif = v - real_v
612
+ if abs(dif) < 0.01:
613
+ return v
614
+ return real_v + (v - real_v) * mod
615
+
616
+
617
+ def _chunks_per_row(chunk_size, map_width):
618
+ return math.ceil(map_width / chunk_size) + 1