mima-engine 0.1.5__py3-none-any.whl → 0.2.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of mima-engine might be problematic. Click here for more details.

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