mima-engine 0.4.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- mima/__init__.py +4 -0
- mima/backend/__init__.py +1 -0
- mima/backend/pygame_assets.py +401 -0
- mima/backend/pygame_audio.py +78 -0
- mima/backend/pygame_backend.py +603 -0
- mima/backend/pygame_camera.py +63 -0
- mima/backend/pygame_events.py +695 -0
- mima/backend/touch_control_scheme_a.py +126 -0
- mima/backend/touch_control_scheme_b.py +132 -0
- mima/core/__init__.py +0 -0
- mima/core/collision.py +325 -0
- mima/core/database.py +58 -0
- mima/core/engine.py +367 -0
- mima/core/mode_engine.py +81 -0
- mima/core/scene_engine.py +81 -0
- mima/integrated/__init__.py +0 -0
- mima/integrated/entity.py +183 -0
- mima/integrated/layered_map.py +351 -0
- mima/integrated/sprite.py +156 -0
- mima/layered/__init__.py +0 -0
- mima/layered/assets.py +56 -0
- mima/layered/scene.py +415 -0
- mima/layered/shape.py +99 -0
- mima/layered/shaped_sprite.py +78 -0
- mima/layered/virtual_input.py +302 -0
- mima/maps/__init__.py +0 -0
- mima/maps/template.py +71 -0
- mima/maps/tile.py +20 -0
- mima/maps/tile_animation.py +7 -0
- mima/maps/tile_info.py +10 -0
- mima/maps/tile_layer.py +52 -0
- mima/maps/tiled/__init__.py +0 -0
- mima/maps/tiled/tiled_layer.py +48 -0
- mima/maps/tiled/tiled_map.py +95 -0
- mima/maps/tiled/tiled_object.py +79 -0
- mima/maps/tiled/tiled_objectgroup.py +25 -0
- mima/maps/tiled/tiled_template.py +49 -0
- mima/maps/tiled/tiled_tile.py +90 -0
- mima/maps/tiled/tiled_tileset.py +51 -0
- mima/maps/tilemap.py +216 -0
- mima/maps/tileset.py +39 -0
- mima/maps/tileset_info.py +9 -0
- mima/maps/transition_map.py +146 -0
- mima/objects/__init__.py +0 -0
- mima/objects/animated_sprite.py +217 -0
- mima/objects/attribute_effect.py +26 -0
- mima/objects/attributes.py +126 -0
- mima/objects/creature.py +384 -0
- mima/objects/dynamic.py +206 -0
- mima/objects/effects/__init__.py +0 -0
- mima/objects/effects/colorize_screen.py +60 -0
- mima/objects/effects/debug_box.py +133 -0
- mima/objects/effects/light.py +103 -0
- mima/objects/effects/show_sprite.py +50 -0
- mima/objects/effects/walking_on_grass.py +70 -0
- mima/objects/effects/walking_on_water.py +57 -0
- mima/objects/loader.py +111 -0
- mima/objects/projectile.py +111 -0
- mima/objects/sprite.py +116 -0
- mima/objects/world/__init__.py +0 -0
- mima/objects/world/color_gate.py +67 -0
- mima/objects/world/color_switch.py +101 -0
- mima/objects/world/container.py +175 -0
- mima/objects/world/floor_switch.py +109 -0
- mima/objects/world/gate.py +178 -0
- mima/objects/world/light_source.py +121 -0
- mima/objects/world/logic_gate.py +157 -0
- mima/objects/world/movable.py +399 -0
- mima/objects/world/oneway.py +195 -0
- mima/objects/world/pickup.py +157 -0
- mima/objects/world/switch.py +179 -0
- mima/objects/world/teleport.py +308 -0
- mima/py.typed +0 -0
- mima/scripts/__init__.py +2 -0
- mima/scripts/command.py +38 -0
- mima/scripts/commands/__init__.py +0 -0
- mima/scripts/commands/add_quest.py +19 -0
- mima/scripts/commands/change_map.py +34 -0
- mima/scripts/commands/close_dialog.py +9 -0
- mima/scripts/commands/equip_weapon.py +23 -0
- mima/scripts/commands/give_item.py +26 -0
- mima/scripts/commands/give_resource.py +51 -0
- mima/scripts/commands/move_map.py +152 -0
- mima/scripts/commands/move_to.py +49 -0
- mima/scripts/commands/oneway_move.py +58 -0
- mima/scripts/commands/parallel.py +66 -0
- mima/scripts/commands/play_sound.py +13 -0
- mima/scripts/commands/present_item.py +53 -0
- mima/scripts/commands/progress_quest.py +12 -0
- mima/scripts/commands/quit_game.py +8 -0
- mima/scripts/commands/save_game.py +14 -0
- mima/scripts/commands/screen_fade.py +83 -0
- mima/scripts/commands/serial.py +69 -0
- mima/scripts/commands/set_facing_direction.py +21 -0
- mima/scripts/commands/set_spawn_map.py +17 -0
- mima/scripts/commands/show_choices.py +52 -0
- mima/scripts/commands/show_dialog.py +118 -0
- mima/scripts/commands/take_coins.py +23 -0
- mima/scripts/script_processor.py +61 -0
- mima/standalone/__init__.py +0 -0
- mima/standalone/camera.py +153 -0
- mima/standalone/geometry.py +1318 -0
- mima/standalone/multicolumn_list.py +54 -0
- mima/standalone/pixel_font.py +84 -0
- mima/standalone/scripting.py +145 -0
- mima/standalone/spatial.py +186 -0
- mima/standalone/sprite.py +158 -0
- mima/standalone/tiled_map.py +1247 -0
- mima/standalone/transformed_view.py +433 -0
- mima/standalone/user_input.py +563 -0
- mima/states/__init__.py +0 -0
- mima/states/game_state.py +189 -0
- mima/states/memory.py +28 -0
- mima/states/quest.py +71 -0
- mima/types/__init__.py +0 -0
- mima/types/alignment.py +7 -0
- mima/types/blend.py +8 -0
- mima/types/damage.py +42 -0
- mima/types/direction.py +44 -0
- mima/types/gate_color.py +7 -0
- mima/types/graphic_state.py +23 -0
- mima/types/keys.py +64 -0
- mima/types/mode.py +9 -0
- mima/types/nature.py +12 -0
- mima/types/object.py +22 -0
- mima/types/player.py +9 -0
- mima/types/position.py +13 -0
- mima/types/start.py +7 -0
- mima/types/terrain.py +9 -0
- mima/types/tile_collision.py +11 -0
- mima/types/weapon_slot.py +6 -0
- mima/types/window.py +44 -0
- mima/usables/__init__.py +0 -0
- mima/usables/item.py +51 -0
- mima/usables/weapon.py +68 -0
- mima/util/__init__.py +1 -0
- mima/util/colors.py +50 -0
- mima/util/constants.py +55 -0
- mima/util/functions.py +38 -0
- mima/util/input_defaults.py +170 -0
- mima/util/logging.py +51 -0
- mima/util/property.py +8 -0
- mima/util/runtime_config.py +327 -0
- mima/util/trading_item.py +23 -0
- mima/view/__init__.py +0 -0
- mima/view/camera.py +192 -0
- mima/view/mima_mode.py +618 -0
- mima/view/mima_scene.py +231 -0
- mima/view/mima_view.py +12 -0
- mima/view/mima_window.py +244 -0
- mima_engine-0.4.0.dist-info/METADATA +47 -0
- mima_engine-0.4.0.dist-info/RECORD +153 -0
- mima_engine-0.4.0.dist-info/WHEEL +4 -0
|
@@ -0,0 +1,351 @@
|
|
|
1
|
+
import math
|
|
2
|
+
|
|
3
|
+
from pygame import Vector2
|
|
4
|
+
from typing_extensions import Generic, Protocol
|
|
5
|
+
|
|
6
|
+
from mima.integrated.entity import Entity, Nature, TEntity
|
|
7
|
+
from mima.layered.shape import ShapeCollection
|
|
8
|
+
from mima.standalone.geometry import Rect, shape_from_str
|
|
9
|
+
from mima.standalone.spatial import SpatialGrid
|
|
10
|
+
from mima.standalone.tiled_map import TiledObject, TiledTile, TiledTilemap, vfloor
|
|
11
|
+
from mima.standalone.transformed_view import TileTransformedView
|
|
12
|
+
|
|
13
|
+
VOFF = Vector2(0.1, 0.1)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class Drawable(Protocol):
|
|
17
|
+
def draw(self, ttv: TileTransformedView, cache: bool = False) -> None: ...
|
|
18
|
+
|
|
19
|
+
def get_pos(self) -> Vector2: ...
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class Renderable:
|
|
23
|
+
def __init__(self, drawable: Drawable, layer: int, elevation: int) -> None:
|
|
24
|
+
self.drawable = drawable
|
|
25
|
+
self.layer = layer
|
|
26
|
+
self.elevation = elevation
|
|
27
|
+
|
|
28
|
+
def draw(self, ttv: TileTransformedView, cache: bool = False) -> None:
|
|
29
|
+
self.drawable.draw(ttv, cache)
|
|
30
|
+
|
|
31
|
+
def get_bottom(self) -> float:
|
|
32
|
+
return self.drawable.get_pos().y
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class ObjectLoader(Generic[TEntity]):
|
|
36
|
+
def load_objects(self, obj: TiledObject) -> list[tuple[TEntity, int]]: ...
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class LayeredMap(Generic[TEntity]):
|
|
40
|
+
def __init__(
|
|
41
|
+
self, tilemap: TiledTilemap, loader: ObjectLoader[TEntity] | None = None
|
|
42
|
+
) -> None:
|
|
43
|
+
self._tilemap: TiledTilemap = tilemap
|
|
44
|
+
self._world_size: Vector2 = self._tilemap.world_size
|
|
45
|
+
self._cell_size: int = 8
|
|
46
|
+
self._map_objects: list[tuple[TEntity, int]] = []
|
|
47
|
+
|
|
48
|
+
self._background_layer: SpatialGrid[TEntity] = SpatialGrid[TEntity](
|
|
49
|
+
self._world_size, self._cell_size
|
|
50
|
+
)
|
|
51
|
+
self._collision_layer: SpatialGrid[TEntity] = SpatialGrid[TEntity](
|
|
52
|
+
self._world_size, self._cell_size
|
|
53
|
+
)
|
|
54
|
+
self._foreground_layer: SpatialGrid[TEntity] = SpatialGrid[TEntity](
|
|
55
|
+
self._world_size, self._cell_size
|
|
56
|
+
)
|
|
57
|
+
self._ui_layer: list[TEntity] = []
|
|
58
|
+
self._large_objects: list[TEntity] = []
|
|
59
|
+
|
|
60
|
+
self._loader: ObjectLoader | None = loader
|
|
61
|
+
|
|
62
|
+
self._visible_objects: set[TEntity] = set()
|
|
63
|
+
self._collisions: set[frozenset[TEntity]] = set()
|
|
64
|
+
self._prev_interactions: set[tuple[TEntity, TEntity]] = set()
|
|
65
|
+
self._curr_interactions: set[tuple[TEntity, TEntity]] = set()
|
|
66
|
+
|
|
67
|
+
self._tilemap.prerender_layers()
|
|
68
|
+
self.draw_grid: bool = False
|
|
69
|
+
|
|
70
|
+
def start_new_frame(self) -> None:
|
|
71
|
+
for grid in [
|
|
72
|
+
self._background_layer,
|
|
73
|
+
self._collision_layer,
|
|
74
|
+
self._foreground_layer,
|
|
75
|
+
]:
|
|
76
|
+
objects = grid.get_all_objects()
|
|
77
|
+
redundants = [obj for obj in objects if obj.is_redundant]
|
|
78
|
+
|
|
79
|
+
for obj in redundants:
|
|
80
|
+
# TODO: query quests
|
|
81
|
+
obj.on_death()
|
|
82
|
+
grid.remove(obj)
|
|
83
|
+
|
|
84
|
+
self._tilemap.start_new_frame()
|
|
85
|
+
|
|
86
|
+
def update_global(self, elapsed_time: float) -> bool:
|
|
87
|
+
self._tilemap.update(elapsed_time)
|
|
88
|
+
it = [
|
|
89
|
+
(self._background_layer, self._background_layer.get_all_objects()),
|
|
90
|
+
(self._collision_layer, self._collision_layer.get_all_objects()),
|
|
91
|
+
(self._foreground_layer, self._foreground_layer.get_all_objects()),
|
|
92
|
+
]
|
|
93
|
+
for grid, objects in it:
|
|
94
|
+
for obj in objects:
|
|
95
|
+
_update_object_global(elapsed_time, obj, grid)
|
|
96
|
+
|
|
97
|
+
for obj in self._ui_layer:
|
|
98
|
+
obj.update_global(elapsed_time)
|
|
99
|
+
return False
|
|
100
|
+
|
|
101
|
+
def update_visible(self, elapsed_time: float, ttv: TileTransformedView) -> bool:
|
|
102
|
+
self.view = Rect(
|
|
103
|
+
ttv.get_tl_tile() - Vector2(0.5, 0.5),
|
|
104
|
+
ttv.get_br_tile() - ttv.get_tl_tile() + Vector2(1.5, 1.5),
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
self._curr_interactions.clear()
|
|
108
|
+
self._collisions.clear()
|
|
109
|
+
|
|
110
|
+
visible_objects = self._collision_layer.get_objects_in_region(
|
|
111
|
+
self.view.pos, self.view.size
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
for obj in visible_objects:
|
|
115
|
+
obj.update_visible(elapsed_time, ttv)
|
|
116
|
+
|
|
117
|
+
if not obj.is_collider:
|
|
118
|
+
continue
|
|
119
|
+
|
|
120
|
+
elevation = math.floor(obj.elevation)
|
|
121
|
+
hitbox = obj.get_hitbox()
|
|
122
|
+
tl = vfloor(hitbox.get_tl_pos() - VOFF)
|
|
123
|
+
br = vfloor(hitbox.get_br_pos() + VOFF)
|
|
124
|
+
if obj.collides_with_map:
|
|
125
|
+
# Collision with map
|
|
126
|
+
for y in range(int(tl.y), int(br.y) + 1):
|
|
127
|
+
for x in range(int(tl.x), int(br.x) + 1):
|
|
128
|
+
tiles = self._tilemap.get_tiles(x, y, elevation)
|
|
129
|
+
new_pos = collide_with_tiles(hitbox, tiles, Vector2(x, y))
|
|
130
|
+
self.relocate_object(self._collision_layer, obj, new_pos)
|
|
131
|
+
|
|
132
|
+
tl = hitbox.get_tl_pos() - (VOFF * 2)
|
|
133
|
+
br = hitbox.get_br_pos() + (VOFF * 2)
|
|
134
|
+
obj_view = Rect(tl, br - tl)
|
|
135
|
+
close_objects = self._collision_layer.get_objects_in_region(
|
|
136
|
+
obj_view.pos, obj_view.size
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
# Collision with other objects
|
|
140
|
+
for other in close_objects:
|
|
141
|
+
if obj == other:
|
|
142
|
+
continue
|
|
143
|
+
# TODO: implement overlaps/resolve_collision for hitboxes
|
|
144
|
+
if obj.get_hitbox().overlaps(other.get_hitbox().bounding_box):
|
|
145
|
+
self._collisions.add(frozenset({obj, other}))
|
|
146
|
+
|
|
147
|
+
for obj1, obj2 in self._collisions:
|
|
148
|
+
collider = obj1
|
|
149
|
+
target = obj2
|
|
150
|
+
|
|
151
|
+
move_both = obj1.is_collider and obj2.is_collider
|
|
152
|
+
if not move_both and obj2.is_collider:
|
|
153
|
+
collider, target = obj2, obj1
|
|
154
|
+
elif move_both and id(obj1) > id(obj2):
|
|
155
|
+
collider, target = obj2, obj1
|
|
156
|
+
|
|
157
|
+
if obj1.collides_with_dyn and obj2.collides_with_dyn:
|
|
158
|
+
new_pos1, new_pos2 = collider.get_hitbox().resolve_collision(
|
|
159
|
+
target.get_hitbox().bounding_box, move_both
|
|
160
|
+
)
|
|
161
|
+
self.relocate_object(self._collision_layer, collider, new_pos1)
|
|
162
|
+
self.relocate_object(self._collision_layer, target, new_pos2)
|
|
163
|
+
|
|
164
|
+
self._curr_interactions.add((obj1, obj2))
|
|
165
|
+
self._curr_interactions.add((obj2, obj1))
|
|
166
|
+
|
|
167
|
+
entered = self._curr_interactions - self._prev_interactions
|
|
168
|
+
stayed = self._curr_interactions & self._prev_interactions
|
|
169
|
+
exited = self._prev_interactions - self._curr_interactions
|
|
170
|
+
for collider, target in entered:
|
|
171
|
+
target.on_interaction(collider, Nature.ENTER)
|
|
172
|
+
# elif obj.is_player: # implicitly: obj.collides_with_dyn==True
|
|
173
|
+
# # Check quests (somehow; they are not available here)
|
|
174
|
+
# # self.on_interaction(other, Nature.WALK)
|
|
175
|
+
for collider, target in stayed:
|
|
176
|
+
target.on_interaction(collider, Nature.WALK)
|
|
177
|
+
for collider, target in exited:
|
|
178
|
+
target.on_interaction(collider, Nature.EXIT)
|
|
179
|
+
self._prev_interactions = self._curr_interactions.copy()
|
|
180
|
+
return False
|
|
181
|
+
|
|
182
|
+
def draw(self, ttv: TileTransformedView) -> None:
|
|
183
|
+
renderables: list[Renderable] = []
|
|
184
|
+
|
|
185
|
+
for layer in self._tilemap.get_rendered_layers():
|
|
186
|
+
renderables.append(Renderable(layer, layer.layer, layer.elevation))
|
|
187
|
+
|
|
188
|
+
for layer, objects in enumerate(
|
|
189
|
+
[
|
|
190
|
+
self._background_layer.get_objects_in_region(
|
|
191
|
+
self.view.pos, self.view.size
|
|
192
|
+
),
|
|
193
|
+
self._collision_layer.get_objects_in_region(
|
|
194
|
+
self.view.pos, self.view.size
|
|
195
|
+
),
|
|
196
|
+
self._foreground_layer.get_objects_in_region(
|
|
197
|
+
self.view.pos, self.view.size
|
|
198
|
+
),
|
|
199
|
+
]
|
|
200
|
+
):
|
|
201
|
+
for obj in objects:
|
|
202
|
+
renderables.append(Renderable(obj, layer, math.floor(obj.elevation)))
|
|
203
|
+
|
|
204
|
+
draw_sorted(renderables, ttv)
|
|
205
|
+
|
|
206
|
+
for obj in self._ui_layer:
|
|
207
|
+
obj.draw(ttv)
|
|
208
|
+
|
|
209
|
+
if self.draw_grid:
|
|
210
|
+
# cells = self._collision_layer.get_cells_in_region(
|
|
211
|
+
# self.view.pos, self.view.size
|
|
212
|
+
# )
|
|
213
|
+
# visible_cells = len(cells)
|
|
214
|
+
world_size = self._collision_layer._world_size # type: ignore[reportPrivateUsage]
|
|
215
|
+
cell_size = self._collision_layer._cell_size # type: ignore[reportPrivateUsage]
|
|
216
|
+
for i in range(0, int(world_size.x), cell_size):
|
|
217
|
+
ttv.draw_line(Vector2(i, 0), Vector2(i, world_size.y), (0, 255, 255))
|
|
218
|
+
for i in range(0, int(world_size.y), cell_size):
|
|
219
|
+
ttv.draw_line(Vector2(0, i), Vector2(world_size.x, i))
|
|
220
|
+
|
|
221
|
+
return
|
|
222
|
+
|
|
223
|
+
def relocate_object(
|
|
224
|
+
self, grid: SpatialGrid[TEntity], obj: TEntity, new_pos: Vector2
|
|
225
|
+
) -> None:
|
|
226
|
+
obj.old_pos = obj.get_pos()
|
|
227
|
+
obj.set_pos(new_pos)
|
|
228
|
+
grid.relocate(obj, obj.old_pos, new_pos)
|
|
229
|
+
|
|
230
|
+
def add_to_layer(self, obj: TEntity, layer: int = 1) -> None:
|
|
231
|
+
if layer == 0:
|
|
232
|
+
self._background_layer.insert(
|
|
233
|
+
obj, obj.get_pos(), obj.get_hitbox().get_bounding_size()
|
|
234
|
+
)
|
|
235
|
+
elif layer == 2:
|
|
236
|
+
self._foreground_layer.insert(
|
|
237
|
+
obj, obj.get_pos(), obj.get_hitbox().get_bounding_size()
|
|
238
|
+
)
|
|
239
|
+
elif layer == 3:
|
|
240
|
+
self._ui_layer.append(obj)
|
|
241
|
+
else:
|
|
242
|
+
self._collision_layer.insert(
|
|
243
|
+
obj, obj.get_pos(), obj.get_hitbox().get_bounding_size()
|
|
244
|
+
)
|
|
245
|
+
|
|
246
|
+
def remove_from_layer(self, obj: TEntity, layer: int = 1) -> None:
|
|
247
|
+
if layer == 0:
|
|
248
|
+
self._background_layer.remove(obj)
|
|
249
|
+
elif layer == 2:
|
|
250
|
+
self._foreground_layer.remove(obj)
|
|
251
|
+
elif layer == 3 and obj in self._ui_layer:
|
|
252
|
+
self._ui_layer.remove(obj)
|
|
253
|
+
else:
|
|
254
|
+
self._collision_layer.remove(obj)
|
|
255
|
+
|
|
256
|
+
def populate_dynamics(self) -> None:
|
|
257
|
+
self._background_layer.clear()
|
|
258
|
+
self._collision_layer.clear()
|
|
259
|
+
self._foreground_layer.clear()
|
|
260
|
+
|
|
261
|
+
if self._map_objects:
|
|
262
|
+
for dyn, layer in self._map_objects:
|
|
263
|
+
dyn.on_interaction(dyn, Nature.RESUME)
|
|
264
|
+
self.add_to_layer(dyn, layer)
|
|
265
|
+
elif self._loader is not None:
|
|
266
|
+
for obj in self._tilemap.get_objects():
|
|
267
|
+
dyns = self._loader.load_objects(obj)
|
|
268
|
+
for dyn, layer in dyns:
|
|
269
|
+
self.add_to_layer(dyn, layer)
|
|
270
|
+
self._map_objects.append((dyn, layer))
|
|
271
|
+
|
|
272
|
+
def is_tile_free(
|
|
273
|
+
self, pos: Vector2, size: Vector2 | None = None, elevation: int = 0
|
|
274
|
+
) -> bool:
|
|
275
|
+
size = Vector2(1, 1) if size is None else size
|
|
276
|
+
|
|
277
|
+
tiles = self._tilemap.get_tiles_in_area(pos, size, elevation)
|
|
278
|
+
|
|
279
|
+
free = True
|
|
280
|
+
for tile in tiles:
|
|
281
|
+
if tile.get_collision_boxes():
|
|
282
|
+
return False
|
|
283
|
+
|
|
284
|
+
# TODO: Check existing objects
|
|
285
|
+
return free
|
|
286
|
+
|
|
287
|
+
@property
|
|
288
|
+
def name(self) -> str:
|
|
289
|
+
return self._tilemap.name
|
|
290
|
+
|
|
291
|
+
@property
|
|
292
|
+
def tl(self) -> Vector2:
|
|
293
|
+
return self._tilemap.tl
|
|
294
|
+
|
|
295
|
+
@property
|
|
296
|
+
def br(self) -> Vector2:
|
|
297
|
+
return self._tilemap.br
|
|
298
|
+
|
|
299
|
+
@property
|
|
300
|
+
def n_objects(self) -> int:
|
|
301
|
+
return (
|
|
302
|
+
self._background_layer.n_objects
|
|
303
|
+
+ self._collision_layer.n_objects
|
|
304
|
+
+ self._foreground_layer.n_objects
|
|
305
|
+
+ len(self._ui_layer)
|
|
306
|
+
)
|
|
307
|
+
|
|
308
|
+
def set_loader(self, loader: ObjectLoader[TEntity]) -> None:
|
|
309
|
+
self._loader = loader
|
|
310
|
+
|
|
311
|
+
|
|
312
|
+
def _update_object_global(
|
|
313
|
+
elapsed_time: float, obj: TEntity, grid: SpatialGrid[TEntity]
|
|
314
|
+
) -> None:
|
|
315
|
+
obj.update_global(elapsed_time)
|
|
316
|
+
pos = obj.get_pos()
|
|
317
|
+
obj.old_pos = pos
|
|
318
|
+
new_pos = pos + obj.vel * obj.speed * elapsed_time
|
|
319
|
+
obj.set_pos(new_pos)
|
|
320
|
+
grid.relocate(obj, pos, new_pos)
|
|
321
|
+
|
|
322
|
+
|
|
323
|
+
def collide_with_tiles(
|
|
324
|
+
obj: ShapeCollection, tiles: list[TiledTile], tile_offset: Vector2
|
|
325
|
+
) -> Vector2:
|
|
326
|
+
obj_origin = obj.pos
|
|
327
|
+
|
|
328
|
+
for tile in tiles:
|
|
329
|
+
hitboxes = tile.get_collision_boxes()
|
|
330
|
+
|
|
331
|
+
if not hitboxes:
|
|
332
|
+
continue
|
|
333
|
+
|
|
334
|
+
for hitbox in hitboxes:
|
|
335
|
+
shape = shape_from_str(
|
|
336
|
+
hitbox.shape, hitbox.pos + tile_offset, hitbox.size, hitbox.radius
|
|
337
|
+
)
|
|
338
|
+
|
|
339
|
+
if obj.overlaps(shape):
|
|
340
|
+
res = obj.resolve_collision(shape)
|
|
341
|
+
obj.pos = res[0]
|
|
342
|
+
new_pos = obj.pos
|
|
343
|
+
obj.pos = obj_origin
|
|
344
|
+
return new_pos
|
|
345
|
+
|
|
346
|
+
|
|
347
|
+
def draw_sorted(renderables: list[Renderable], ttv: TileTransformedView) -> None:
|
|
348
|
+
renderables.sort(key=lambda r: (r.layer, r.elevation, r.get_bottom()))
|
|
349
|
+
|
|
350
|
+
for r in renderables:
|
|
351
|
+
r.draw(ttv, cache=True)
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
from enum import Enum
|
|
2
|
+
|
|
3
|
+
from pygame import Vector2
|
|
4
|
+
from typing_extensions import Tuple
|
|
5
|
+
|
|
6
|
+
from mima.layered.shaped_sprite import SpriteWithShape
|
|
7
|
+
from mima.standalone.tiled_map import TiledTileset
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class GraphicState(Enum):
|
|
11
|
+
STANDING = 0
|
|
12
|
+
WALKING = 1
|
|
13
|
+
ATTACKING = 2
|
|
14
|
+
DAMAGED = 3
|
|
15
|
+
CELEBRATING = 4
|
|
16
|
+
DEAD = 5
|
|
17
|
+
DEFEATED = 6
|
|
18
|
+
PUSHING = 7
|
|
19
|
+
OPEN = 8
|
|
20
|
+
CLOSED = 9
|
|
21
|
+
LOCKED = 10
|
|
22
|
+
OFF = 11
|
|
23
|
+
ON = 12
|
|
24
|
+
ICON = 13
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class Direction4(Enum):
|
|
28
|
+
SOUTH = 0
|
|
29
|
+
WEST = 1
|
|
30
|
+
NORTH = 2
|
|
31
|
+
EAST = 3
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
Direction = Direction4
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class Direction8(Enum):
|
|
38
|
+
SOUTH = 0
|
|
39
|
+
SOUTHWEST = 1
|
|
40
|
+
WEST = 2
|
|
41
|
+
NORTH_WEST = 3
|
|
42
|
+
NORTH = 4
|
|
43
|
+
NORTH_EAST = 5
|
|
44
|
+
EAST = 6
|
|
45
|
+
SOUTH_EAST = 7
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def sprite_from_tileset(
|
|
49
|
+
tileset: TiledTileset, sprite_name: str
|
|
50
|
+
) -> SpriteWithShape[GraphicState, Direction]:
|
|
51
|
+
sprite = SpriteWithShape[GraphicState, Direction]()
|
|
52
|
+
|
|
53
|
+
for tile in tileset.tiles.values():
|
|
54
|
+
if tile.get("sprite_name", "") != sprite_name:
|
|
55
|
+
continue
|
|
56
|
+
|
|
57
|
+
gs = GraphicState[tile.get("graphic_state", "standing").upper()]
|
|
58
|
+
d = Direction[tile.get("facing_direction", "south").upper()]
|
|
59
|
+
if tile.animated:
|
|
60
|
+
for frame in tile.frames:
|
|
61
|
+
sprite.add_frame(gs, d, frame.as_dict())
|
|
62
|
+
else:
|
|
63
|
+
sprite.add_frame(gs, d, tile.get_frame().as_dict())
|
|
64
|
+
|
|
65
|
+
return sprite
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def sprite_from_tileset_gid(
|
|
69
|
+
tileset: TiledTileset, gid: int
|
|
70
|
+
) -> SpriteWithShape[GraphicState, Direction]:
|
|
71
|
+
sprite = SpriteWithShape[GraphicState, Direction]()
|
|
72
|
+
|
|
73
|
+
tile = tileset.tiles[gid]
|
|
74
|
+
gs = GraphicState[tile.get("graphic_state", "standing").upper()]
|
|
75
|
+
d = Direction[tile.get("facing_direction", "south").upper()]
|
|
76
|
+
|
|
77
|
+
if tile.animated:
|
|
78
|
+
for frame in tile.frames:
|
|
79
|
+
sprite.add_frame(gs, d, frame.as_dict())
|
|
80
|
+
else:
|
|
81
|
+
sprite.add_frame(gs, d, tile.get_frame().as_dict())
|
|
82
|
+
|
|
83
|
+
return sprite
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def vel_to_dir(v: Vector2):
|
|
87
|
+
direction = None
|
|
88
|
+
if abs(v.x) >= abs(v.y):
|
|
89
|
+
if v.x > 0:
|
|
90
|
+
direction = Direction.EAST
|
|
91
|
+
elif v.x < 0:
|
|
92
|
+
direction = Direction.WEST
|
|
93
|
+
else:
|
|
94
|
+
# TODO: Check for up and down
|
|
95
|
+
pass
|
|
96
|
+
elif abs(v.x) < abs(v.y):
|
|
97
|
+
if v.y > 0:
|
|
98
|
+
direction = Direction.SOUTH
|
|
99
|
+
elif v.y < 0:
|
|
100
|
+
direction = Direction.NORTH
|
|
101
|
+
else:
|
|
102
|
+
# TODO: Check for left and right
|
|
103
|
+
pass
|
|
104
|
+
return direction
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def dir_to_vel(direction: Direction) -> Tuple[int, int]:
|
|
108
|
+
vx = vy = 0
|
|
109
|
+
|
|
110
|
+
if direction == Direction.SOUTH:
|
|
111
|
+
vy = 1
|
|
112
|
+
elif direction == Direction.EAST:
|
|
113
|
+
vx = 1
|
|
114
|
+
elif direction == Direction.NORTH:
|
|
115
|
+
vy = -1
|
|
116
|
+
elif direction == Direction.WEST:
|
|
117
|
+
vx = -1
|
|
118
|
+
return vx, vy
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
class Anchor(Enum):
|
|
122
|
+
TOP_LEFT = 0
|
|
123
|
+
TOP_RIGHT = 1
|
|
124
|
+
BOTTOM_LEFT = 2
|
|
125
|
+
BOTTOM_RIGHT = 3
|
|
126
|
+
CENTER = 4
|
|
127
|
+
TOP_CENTER = 5
|
|
128
|
+
RIGHT_CENTER = 6
|
|
129
|
+
BOTTOM_CENTER = 7
|
|
130
|
+
LEFT_CENTER = 8
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def anchor_offset(anchor: Anchor) -> Vector2:
|
|
134
|
+
if anchor == Anchor.TOP_RIGHT:
|
|
135
|
+
return Vector2(-1, 0)
|
|
136
|
+
elif anchor == Anchor.BOTTOM_LEFT:
|
|
137
|
+
return Vector2(0, -1)
|
|
138
|
+
elif anchor == Anchor.BOTTOM_RIGHT:
|
|
139
|
+
return Vector2(-1, -1)
|
|
140
|
+
elif anchor == Anchor.CENTER:
|
|
141
|
+
return Vector2(-0.5, -0.5)
|
|
142
|
+
elif anchor == Anchor.TOP_CENTER:
|
|
143
|
+
return Vector2(-0.5, 0)
|
|
144
|
+
elif anchor == Anchor.RIGHT_CENTER:
|
|
145
|
+
return Vector2(-1, -0.5)
|
|
146
|
+
elif anchor == Anchor.BOTTOM_CENTER:
|
|
147
|
+
return Vector2(-0.5, -1.0)
|
|
148
|
+
elif anchor == Anchor.LEFT_CENTER:
|
|
149
|
+
return Vector2(0, -0.5)
|
|
150
|
+
else:
|
|
151
|
+
return Vector2()
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
class Until(Enum):
|
|
155
|
+
UNLOCK = 0
|
|
156
|
+
NEXT_UPDATE = 1
|
mima/layered/__init__.py
ADDED
|
File without changes
|
mima/layered/assets.py
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import csv
|
|
2
|
+
import logging
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
from typing_extensions import TypeAlias
|
|
6
|
+
|
|
7
|
+
from mima.standalone.tiled_map import MapManager
|
|
8
|
+
|
|
9
|
+
CSVContent: TypeAlias = list[dict[str, str]]
|
|
10
|
+
LOG = logging.getLogger(__name__)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class AssetManager(MapManager):
|
|
14
|
+
def __init__(self) -> None:
|
|
15
|
+
super().__init__()
|
|
16
|
+
|
|
17
|
+
self._csvs: dict[str, CSVContent] = {}
|
|
18
|
+
|
|
19
|
+
def _load_file(self, fp: Path, skip_invalid: bool = False) -> None:
|
|
20
|
+
fp = fp.resolve()
|
|
21
|
+
if str(fp) in self._loaded_paths:
|
|
22
|
+
return
|
|
23
|
+
|
|
24
|
+
_, suf = fp.parts[-1].rsplit(".")
|
|
25
|
+
|
|
26
|
+
if suf.lower() == "png":
|
|
27
|
+
self._load_image(fp)
|
|
28
|
+
elif suf.lower() == "csv":
|
|
29
|
+
self._load_csv(fp)
|
|
30
|
+
else:
|
|
31
|
+
super()._load_file(fp, skip_invalid)
|
|
32
|
+
|
|
33
|
+
def _load_csv(self, path: Path) -> str:
|
|
34
|
+
name = Path(path).parts[-1].rsplit(".", 1)[0]
|
|
35
|
+
if name in self._csvs:
|
|
36
|
+
return name
|
|
37
|
+
|
|
38
|
+
with path.open("r") as csv_file:
|
|
39
|
+
reader = csv.DictReader(csv_file, delimiter=",")
|
|
40
|
+
self._csvs[name] = [r for r in reader]
|
|
41
|
+
|
|
42
|
+
self._loaded_paths[str(path)] = name
|
|
43
|
+
|
|
44
|
+
return name
|
|
45
|
+
|
|
46
|
+
def get_csv(self, name: str | Path) -> CSVContent:
|
|
47
|
+
name = str(name)
|
|
48
|
+
if name in self._csvs:
|
|
49
|
+
return self._csvs[name]
|
|
50
|
+
if name in self._loaded_paths:
|
|
51
|
+
return self._csvs[self._loaded_paths[name]]
|
|
52
|
+
|
|
53
|
+
LOG.info("CSV '%s' not found. Attempting to read from disk ...", name)
|
|
54
|
+
self._load_csv(Path(name))
|
|
55
|
+
|
|
56
|
+
return self._csvs[self._loaded_paths[name]]
|