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.
- mima/__init__.py +1 -1
- mima/backend/pygame_assets.py +14 -8
- mima/backend/pygame_audio.py +5 -2
- mima/backend/pygame_backend.py +255 -57
- mima/backend/pygame_camera.py +63 -0
- mima/backend/pygame_events.py +369 -120
- mima/collision.py +182 -111
- mima/engine.py +155 -15
- mima/maps/tiled/tiled_map.py +3 -3
- mima/maps/tiled/tiled_tileset.py +1 -0
- mima/maps/tilemap.py +78 -15
- mima/maps/tileset.py +8 -2
- mima/maps/transition_map.py +6 -8
- mima/mode_engine.py +80 -0
- mima/objects/animated_sprite.py +23 -15
- mima/objects/attributes.py +3 -0
- mima/objects/creature.py +54 -17
- mima/objects/dynamic.py +30 -8
- mima/objects/effects/colorize_screen.py +22 -6
- mima/objects/effects/debug_box.py +124 -0
- mima/objects/effects/light.py +21 -30
- mima/objects/effects/show_sprite.py +39 -0
- mima/objects/effects/walking_on_grass.py +25 -7
- mima/objects/effects/walking_on_water.py +17 -6
- mima/objects/loader.py +24 -13
- mima/objects/projectile.py +21 -6
- mima/objects/sprite.py +7 -8
- mima/objects/world/color_gate.py +5 -2
- mima/objects/world/color_switch.py +12 -6
- mima/objects/world/container.py +17 -8
- mima/objects/world/floor_switch.py +8 -4
- mima/objects/world/gate.py +8 -5
- mima/objects/world/light_source.py +11 -9
- mima/objects/world/logic_gate.py +8 -7
- mima/objects/world/movable.py +72 -28
- mima/objects/world/oneway.py +14 -9
- mima/objects/world/pickup.py +10 -5
- mima/objects/world/switch.py +28 -25
- mima/objects/world/teleport.py +76 -55
- mima/scene_engine.py +19 -20
- mima/scripts/command.py +16 -2
- mima/scripts/commands/change_map.py +23 -4
- mima/scripts/commands/equip_weapon.py +23 -0
- mima/scripts/commands/give_item.py +5 -3
- mima/scripts/commands/move_map.py +9 -9
- mima/scripts/commands/parallel.py +16 -3
- mima/scripts/commands/present_item.py +7 -5
- mima/scripts/commands/screen_fade.py +30 -12
- mima/scripts/commands/serial.py +30 -7
- mima/scripts/commands/set_spawn_map.py +6 -3
- mima/scripts/commands/show_choices.py +16 -7
- mima/scripts/commands/show_dialog.py +110 -3
- mima/scripts/script_processor.py +41 -20
- mima/states/game_state.py +2 -0
- mima/states/memory.py +28 -0
- mima/states/quest.py +2 -3
- mima/types/keys.py +48 -0
- mima/types/mode.py +4 -10
- mima/types/player.py +9 -0
- mima/types/position.py +13 -0
- mima/types/tile_collision.py +11 -0
- mima/types/window.py +44 -0
- mima/usables/item.py +1 -0
- mima/util/colors.py +5 -0
- mima/util/constants.py +6 -0
- mima/util/functions.py +27 -0
- mima/util/input_defaults.py +109 -0
- mima/util/runtime_config.py +234 -30
- mima/util/trading_item.py +20 -0
- mima/view/camera.py +160 -19
- mima/view/mima_mode.py +612 -0
- mima/view/mima_scene.py +225 -0
- mima/view/mima_view.py +12 -0
- mima/view/mima_window.py +153 -0
- {mima_engine-0.1.5.dist-info → mima_engine-0.2.1.dist-info}/METADATA +4 -2
- mima_engine-0.2.1.dist-info/RECORD +128 -0
- {mima_engine-0.1.5.dist-info → mima_engine-0.2.1.dist-info}/WHEEL +1 -1
- mima/view/scene.py +0 -322
- mima_engine-0.1.5.dist-info/RECORD +0 -114
- {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
|