mima-engine 0.1.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.

Potentially problematic release.


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

Files changed (114) hide show
  1. mima/__init__.py +1 -0
  2. mima/backend/__init__.py +1 -0
  3. mima/backend/pygame_assets.py +345 -0
  4. mima/backend/pygame_audio.py +75 -0
  5. mima/backend/pygame_backend.py +399 -0
  6. mima/backend/pygame_events.py +430 -0
  7. mima/collision.py +237 -0
  8. mima/engine.py +197 -0
  9. mima/maps/__init__.py +0 -0
  10. mima/maps/template.py +41 -0
  11. mima/maps/tile.py +20 -0
  12. mima/maps/tile_animation.py +7 -0
  13. mima/maps/tile_info.py +10 -0
  14. mima/maps/tile_layer.py +52 -0
  15. mima/maps/tiled/__init__.py +0 -0
  16. mima/maps/tiled/tiled_layer.py +48 -0
  17. mima/maps/tiled/tiled_map.py +95 -0
  18. mima/maps/tiled/tiled_object.py +79 -0
  19. mima/maps/tiled/tiled_objectgroup.py +25 -0
  20. mima/maps/tiled/tiled_template.py +49 -0
  21. mima/maps/tiled/tiled_tile.py +90 -0
  22. mima/maps/tiled/tiled_tileset.py +45 -0
  23. mima/maps/tilemap.py +159 -0
  24. mima/maps/tileset.py +32 -0
  25. mima/maps/tileset_info.py +9 -0
  26. mima/maps/transition_map.py +148 -0
  27. mima/objects/__init__.py +0 -0
  28. mima/objects/animated_sprite.py +198 -0
  29. mima/objects/attribute_effect.py +26 -0
  30. mima/objects/attributes.py +123 -0
  31. mima/objects/creature.py +332 -0
  32. mima/objects/dynamic.py +182 -0
  33. mima/objects/effects/__init__.py +0 -0
  34. mima/objects/effects/colorize_screen.py +36 -0
  35. mima/objects/effects/light.py +107 -0
  36. mima/objects/effects/walking_on_grass.py +38 -0
  37. mima/objects/effects/walking_on_water.py +41 -0
  38. mima/objects/loader.py +103 -0
  39. mima/objects/projectile.py +86 -0
  40. mima/objects/sprite.py +110 -0
  41. mima/objects/world/__init__.py +0 -0
  42. mima/objects/world/color_gate.py +68 -0
  43. mima/objects/world/color_switch.py +105 -0
  44. mima/objects/world/container.py +171 -0
  45. mima/objects/world/floor_switch.py +111 -0
  46. mima/objects/world/gate.py +174 -0
  47. mima/objects/world/light_source.py +124 -0
  48. mima/objects/world/logic_gate.py +163 -0
  49. mima/objects/world/movable.py +338 -0
  50. mima/objects/world/oneway.py +168 -0
  51. mima/objects/world/pickup.py +88 -0
  52. mima/objects/world/switch.py +165 -0
  53. mima/objects/world/teleport.py +288 -0
  54. mima/scene_engine.py +79 -0
  55. mima/scripts/__init__.py +2 -0
  56. mima/scripts/command.py +24 -0
  57. mima/scripts/commands/__init__.py +0 -0
  58. mima/scripts/commands/add_quest.py +19 -0
  59. mima/scripts/commands/change_map.py +15 -0
  60. mima/scripts/commands/close_dialog.py +8 -0
  61. mima/scripts/commands/give_item.py +24 -0
  62. mima/scripts/commands/give_resource.py +51 -0
  63. mima/scripts/commands/move_map.py +152 -0
  64. mima/scripts/commands/move_to.py +49 -0
  65. mima/scripts/commands/oneway_move.py +57 -0
  66. mima/scripts/commands/parallel.py +53 -0
  67. mima/scripts/commands/play_sound.py +13 -0
  68. mima/scripts/commands/present_item.py +51 -0
  69. mima/scripts/commands/progress_quest.py +12 -0
  70. mima/scripts/commands/quit_game.py +8 -0
  71. mima/scripts/commands/save_game.py +13 -0
  72. mima/scripts/commands/screen_fade.py +65 -0
  73. mima/scripts/commands/serial.py +46 -0
  74. mima/scripts/commands/set_facing_direction.py +21 -0
  75. mima/scripts/commands/set_spawn_map.py +14 -0
  76. mima/scripts/commands/show_choices.py +43 -0
  77. mima/scripts/commands/show_dialog.py +11 -0
  78. mima/scripts/commands/take_coins.py +23 -0
  79. mima/scripts/script_processor.py +40 -0
  80. mima/states/__init__.py +0 -0
  81. mima/states/game_state.py +162 -0
  82. mima/states/quest.py +72 -0
  83. mima/types/__init__.py +0 -0
  84. mima/types/alignment.py +7 -0
  85. mima/types/blend.py +8 -0
  86. mima/types/damage.py +42 -0
  87. mima/types/direction.py +44 -0
  88. mima/types/gate_color.py +7 -0
  89. mima/types/graphic_state.py +22 -0
  90. mima/types/keys.py +16 -0
  91. mima/types/mode.py +15 -0
  92. mima/types/nature.py +12 -0
  93. mima/types/object.py +22 -0
  94. mima/types/start.py +7 -0
  95. mima/types/terrain.py +9 -0
  96. mima/types/weapon_slot.py +6 -0
  97. mima/usables/__init__.py +0 -0
  98. mima/usables/item.py +31 -0
  99. mima/usables/weapon.py +48 -0
  100. mima/util/__init__.py +1 -0
  101. mima/util/colors.py +45 -0
  102. mima/util/constants.py +47 -0
  103. mima/util/functions.py +13 -0
  104. mima/util/input_defaults.py +49 -0
  105. mima/util/logging.py +51 -0
  106. mima/util/property.py +8 -0
  107. mima/util/runtime_config.py +133 -0
  108. mima/view/__init__.py +0 -0
  109. mima/view/camera.py +51 -0
  110. mima/view/scene.py +350 -0
  111. mima_engine-0.1.0.dist-info/METADATA +14 -0
  112. mima_engine-0.1.0.dist-info/RECORD +114 -0
  113. mima_engine-0.1.0.dist-info/WHEEL +5 -0
  114. mima_engine-0.1.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,90 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import TYPE_CHECKING, List
4
+
5
+ from ...types.direction import Direction
6
+ from ...types.graphic_state import GraphicState
7
+ from ...types.terrain import Terrain
8
+ from ...util.functions import strtobool
9
+ from ..tile import Tile
10
+ from ..tile_animation import TileAnimation
11
+
12
+ if TYPE_CHECKING:
13
+ from xml.etree.ElementTree import Element
14
+
15
+
16
+ class TiledTile(Tile):
17
+ def __init__(self, t_xtree: Element):
18
+ super().__init__()
19
+
20
+ self.tile_type: str = t_xtree.attrib.get(
21
+ "class", t_xtree.attrib.get("type", "tile")
22
+ )
23
+
24
+ self.basic_tile_id: int = int(t_xtree.attrib["id"])
25
+ self.tile_id: int = self.basic_tile_id
26
+
27
+ self._frames: List[TileAnimation] = []
28
+ self._frame: int = 0
29
+ self._num_frames: int = 0
30
+ self._frame_timer: float = 0.0
31
+
32
+ animation = t_xtree.findall("animation")
33
+
34
+ if animation:
35
+ frames = animation[0].findall("frame")
36
+ for frame in frames:
37
+ self._frames.append(
38
+ TileAnimation(
39
+ frame_id=int(frame.attrib["tileid"]),
40
+ duration=int(frame.attrib["duration"]) / 1000.0,
41
+ )
42
+ )
43
+ self.animated = True
44
+ else:
45
+ self._frames.append(
46
+ TileAnimation(frame_id=self.basic_tile_id, duration=0.0)
47
+ )
48
+
49
+ self._num_frames = len(self._frames)
50
+
51
+ self.tile_id = self._frames[0].frame_id
52
+ self._frame_timer = self._frames[0].duration
53
+
54
+ properties = t_xtree.findall("properties")
55
+ if properties:
56
+ properties = properties[0].findall("property")
57
+ for prop in properties:
58
+ if prop.attrib["name"] == "solid":
59
+ self.solid = strtobool(prop.attrib["value"])
60
+ if prop.attrib["name"] == "ground_type":
61
+ try:
62
+ self.terrain = Terrain[prop.attrib["value"].upper()]
63
+ except:
64
+ self.terrain = Terrain.DEFAULT
65
+ if prop.attrib["name"] == "z_height":
66
+ self.z_height = float(prop.attrib["value"])
67
+ if prop.attrib["name"] == "facing_direction":
68
+ self.facing_direction = Direction[
69
+ prop.attrib.get("value", "south").upper()
70
+ ]
71
+ if prop.attrib["name"] == "graphic_state":
72
+ self.graphic_state = GraphicState[
73
+ prop.attrib.get("value", "standing").upper()
74
+ ]
75
+ if prop.attrib["name"] == "sprite_name":
76
+ self.sprite_name = prop.attrib.get("value", "")
77
+
78
+ def update(self, elapsed_time: float) -> bool:
79
+ if self._num_frames <= 1:
80
+ return False
81
+
82
+ self._frame_timer -= elapsed_time
83
+ if self._frame_timer <= 0:
84
+ self._frame = (self._frame + 1) % self._num_frames
85
+ self.tile_id = self._frames[self._frame].frame_id
86
+ self._frame_timer += self._frames[self._frame].duration
87
+
88
+ return True
89
+
90
+ return False
@@ -0,0 +1,45 @@
1
+ from __future__ import annotations
2
+
3
+ import logging
4
+ import os
5
+ from xml.etree import ElementTree
6
+
7
+ from ..tileset import Tileset
8
+ from .tiled_tile import TiledTile
9
+
10
+ LOG = logging.getLogger(__name__)
11
+
12
+
13
+ class TiledTileset(Tileset):
14
+ def __init__(self, name: str, filename: str):
15
+ super().__init__()
16
+
17
+ self.name = name
18
+
19
+ LOG.info(
20
+ "Loading tileset %s from TSX file at '%s' ...",
21
+ name,
22
+ filename,
23
+ )
24
+ tree = ElementTree.parse(filename)
25
+ LOG.debug("Loaded file %s successfully.", filename)
26
+
27
+ root = tree.getroot()
28
+ LOG.debug("Loading tileset properties ...")
29
+ self.tile_width: int = int(root.attrib["tilewidth"])
30
+ self.tile_height: int = int(root.attrib["tileheight"])
31
+ self.tile_count: int = int(root.attrib["tilecount"])
32
+ self.columns: int = int(root.attrib["columns"])
33
+
34
+ LOG.debug("Loading image properties ...")
35
+ image = root.findall("image")[0]
36
+ self.sprite_name: str = os.path.split(image.attrib["source"])[-1][:-4]
37
+ self.sprite_width: int = int(image.attrib["width"])
38
+ self.sprite_height: int = int(image.attrib["height"])
39
+
40
+ LOG.debug("Loading tiles ...")
41
+ tiles = root.findall("tile")
42
+ for tile in tiles:
43
+ self.tiles.append(TiledTile(tile))
44
+ if self.tiles[-1].animated:
45
+ self.animated_tiles.append(self.tiles[-1])
mima/maps/tilemap.py ADDED
@@ -0,0 +1,159 @@
1
+ from __future__ import annotations
2
+
3
+ import logging
4
+ import math
5
+ from typing import TYPE_CHECKING, Dict, List, Optional
6
+
7
+ from ..util.constants import TILE_HEIGHT, TILE_WIDTH
8
+ from .tile_info import TileInfo
9
+ from .tile_layer import TileLayer
10
+ from .tileset_info import TilesetInfo
11
+ from .template import Template
12
+
13
+ if TYPE_CHECKING:
14
+ from ..objects.dynamic import Dynamic
15
+ from ..types.nature import Nature
16
+ from .tile import Tile
17
+
18
+
19
+ LOG = logging.getLogger(__name__)
20
+
21
+
22
+ class Tilemap(Template):
23
+
24
+ def __init__(self, name: str):
25
+ super().__init__(name)
26
+ self.width: int = 0
27
+ self.height: int = 0
28
+ self.tile_width = TILE_WIDTH
29
+ self.tile_height = TILE_HEIGHT
30
+
31
+ self._layers: List[TileLayer] = []
32
+ self._tilesets: List[TilesetInfo] = []
33
+ self._cache: Dict[int, TileInfo] = {}
34
+
35
+ def populate_dynamics(self, dynamics: List[Dynamic]) -> bool:
36
+ """Load all map-related objects into the game."""
37
+ return False
38
+
39
+ def update(self, elapsed_time: float) -> bool:
40
+ for info in self._tilesets:
41
+ info.tileset.update(elapsed_time)
42
+
43
+ for layer in self._layers:
44
+ layer.update(elapsed_time)
45
+
46
+ return True
47
+
48
+ def draw_self(
49
+ self,
50
+ ox: float,
51
+ oy: float,
52
+ visible_tiles_sx: int,
53
+ visible_tiles_sy: int,
54
+ visible_tiles_ex: int,
55
+ visible_tiles_ey: int,
56
+ layer_pos: int = 0,
57
+ ):
58
+ # Get offsets for smooth movement
59
+ tile_ox = (ox - math.floor(ox)) * self.tile_width
60
+ tile_oy = (oy - math.floor(oy)) * self.tile_height
61
+
62
+ for layer in self._layers:
63
+ if layer.layer_pos != layer_pos:
64
+ continue
65
+
66
+ layer_ox = (
67
+ layer.layer_ox - math.floor(layer.layer_ox)
68
+ ) * self.tile_width
69
+ layer_oy = (
70
+ layer.layer_oy - math.floor(layer.layer_oy)
71
+ ) * self.tile_height
72
+
73
+ layer_visible_tiles_sx = int(visible_tiles_sx)
74
+ layer_visible_tiles_sy = int(visible_tiles_sy)
75
+
76
+ if layer.speed_x != 0.0:
77
+ layer_visible_tiles_sx -= 1
78
+ if layer.speed_y != 0.0:
79
+ layer_visible_tiles_sy -= 1
80
+
81
+ # Draw visible tiles of the map
82
+ for x in range(layer_visible_tiles_sx, int(visible_tiles_ex) + 1):
83
+ for y in range(
84
+ layer_visible_tiles_sy, int(visible_tiles_ey) + 1
85
+ ):
86
+ tile_index = layer.get_index(
87
+ int(x + math.floor(ox)),
88
+ int(y + math.floor(oy)),
89
+ )
90
+ if tile_index <= 0:
91
+ # Zero means the tile was not set in Tiled
92
+ continue
93
+
94
+ if tile_index not in self._cache:
95
+ if not self._load_to_cache(tile_index):
96
+ continue
97
+
98
+ info = self._cache[tile_index]
99
+
100
+ sx = info.tile.tile_id % info.tileset.columns
101
+ sy = info.tile.tile_id // info.tileset.columns
102
+
103
+ self.engine.backend.draw_partial_sprite(
104
+ math.floor(x * self.tile_width - tile_ox + layer_ox),
105
+ math.floor(y * self.tile_height - tile_oy + layer_oy),
106
+ info.tileset.sprite_name,
107
+ sx * self.tile_width,
108
+ sy * self.tile_height,
109
+ self.tile_width,
110
+ self.tile_height,
111
+ )
112
+ return True
113
+
114
+ def on_interaction(self, target: Dynamic, nature: Nature) -> bool:
115
+ return False
116
+
117
+ def is_solid(
118
+ self, px: int, py: int, layer_pos: Optional[int] = None
119
+ ) -> bool:
120
+ tile = self.get_tile(px, py, layer_pos)
121
+ if tile is not None and tile.solid:
122
+ return True
123
+ return False
124
+
125
+ def get_tile(
126
+ self, px: int, py: int, layer_pos: Optional[int] = None
127
+ ) -> Optional[Tile]:
128
+ for layer in self._layers[::-1]:
129
+ if layer_pos is not None and layer_pos != layer.layer_pos:
130
+ continue
131
+ tile_index = layer.get_index(math.floor(px), math.floor(py))
132
+ if tile_index not in self._cache:
133
+ if not self._load_to_cache(tile_index):
134
+ continue
135
+
136
+ info = self._cache[tile_index]
137
+ if info.tile is not None:
138
+ return info.tile
139
+
140
+ return None
141
+
142
+ def _load_to_cache(self, tile_index: int) -> bool:
143
+ tileset = None
144
+ firstgid = 0
145
+ for tsinfo in self._tilesets:
146
+ if tile_index < tsinfo.first_gid:
147
+ break
148
+
149
+ firstgid = tsinfo.first_gid
150
+ tileset = tsinfo.tileset
151
+
152
+ if tileset is None:
153
+ return False
154
+
155
+ tidx = tile_index - firstgid
156
+ tile = tileset.get_tile(tidx)
157
+
158
+ self._cache[tile_index] = TileInfo(tileset=tileset, tile=tile)
159
+ return True
mima/maps/tileset.py ADDED
@@ -0,0 +1,32 @@
1
+ from typing import List
2
+
3
+ from .tile import Tile
4
+
5
+
6
+ class Tileset:
7
+ def __init__(self):
8
+ self.name: str = "Unnamed Tileset"
9
+ self.sprite_name: str = ""
10
+ # self.filename: str = ""
11
+ self.sprite_width: int = 0
12
+ self.sprite_height: int = 0
13
+ self.tile_width: int = 0
14
+ self.tile_height: int = 0
15
+ self.tile_count: int = 0
16
+ self.columns: int = 0
17
+
18
+ self.tiles: List[Tile] = []
19
+ self.animated_tiles: List[Tile] = []
20
+
21
+ def update(self, elapsed_time: float) -> bool:
22
+ for tile in self.animated_tiles:
23
+ tile.update(elapsed_time)
24
+
25
+ return True
26
+
27
+ def get_tile(self, tile_id: int) -> Tile:
28
+ for tile in self.tiles:
29
+ if tile.basic_tile_id == tile_id:
30
+ return tile
31
+
32
+ return self.tiles[0]
@@ -0,0 +1,9 @@
1
+ from dataclasses import dataclass
2
+
3
+ from .tileset import Tileset
4
+
5
+
6
+ @dataclass
7
+ class TilesetInfo:
8
+ tileset: Tileset
9
+ first_gid: int
@@ -0,0 +1,148 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import TYPE_CHECKING, Optional
4
+
5
+ from ..util.constants import (
6
+ MAP_TRANSITION_DURATION_FACTOR,
7
+ MOVE_MAP_DURATION,
8
+ TILE_HEIGHT,
9
+ TILE_WIDTH,
10
+ UI_HIGHT,
11
+ )
12
+ from .tilemap import Tilemap
13
+
14
+ if TYPE_CHECKING:
15
+ from .tile import Tile
16
+
17
+
18
+ class TransitionMap(Tilemap):
19
+ def __init__(
20
+ self, src_map: Tilemap, dst_map: Tilemap, dst_vx: int, dst_vy: int
21
+ ):
22
+ super().__init__(src_map.name)
23
+
24
+ self.src_map: Tilemap = src_map
25
+ self.dst_map: Tilemap = dst_map
26
+ self.vx: int = int(dst_vx)
27
+ self.vy: int = int(dst_vy)
28
+ self.progress: float = 0.0
29
+ self.duration: float = (
30
+ MOVE_MAP_DURATION * MAP_TRANSITION_DURATION_FACTOR
31
+ )
32
+ self.time_so_far: float = 0.0
33
+ self.step_size: float = 1.0
34
+ self._extra_oy = UI_HIGHT
35
+
36
+ self.width: int = self.src_map.width
37
+ self.height: int = self.src_map.height
38
+ self.tiles_nx = self.engine.backend.render_width / TILE_WIDTH
39
+ self.tiles_ny = self.engine.backend.render_height / TILE_HEIGHT
40
+
41
+ if self.vx != 0:
42
+ self.step_size = self.duration / (self.tiles_nx * 0.75)
43
+ elif self.vy != 0:
44
+ self.step_size = self.duration / ((self.tiles_ny - 1) * 0.75)
45
+
46
+ # print(
47
+ # f"Step Size={self.step_size}, Duration={self.duration}, Size={WIDTH, HEIGHT}, TileSize={TILE_WIDTH, TILE_HEIGHT} "
48
+ # )
49
+
50
+ def update(self, elapsed_time: float) -> bool:
51
+ self.progress = self.time_so_far / self.step_size
52
+ self.time_so_far += elapsed_time
53
+
54
+ return self.src_map.update(elapsed_time)
55
+
56
+ def draw_self(
57
+ self,
58
+ ox: float,
59
+ oy: float,
60
+ visible_tiles_sx: int,
61
+ visible_tiles_sy: int,
62
+ visible_tiles_ex: int,
63
+ visible_tiles_ey: int,
64
+ layer_pos: int = 0,
65
+ ) -> bool:
66
+ src_tiles_sx = dst_tiles_sx = int(visible_tiles_sx)
67
+ src_tiles_sy = dst_tiles_sy = int(visible_tiles_sy)
68
+ src_tiles_ex = dst_tiles_ex = int(visible_tiles_ex)
69
+ src_tiles_ey = dst_tiles_ey = int(visible_tiles_ey)
70
+ src_ox = dst_ox = ox
71
+ src_oy = dst_oy = oy
72
+
73
+ if self.vx < 0: # Transition west
74
+ src_tiles_sx += int(self.progress)
75
+ src_tiles_ex += int(self.progress)
76
+ dst_tiles_ex = src_tiles_sx + 1
77
+ src_ox = max(-self.tiles_nx, -self.progress)
78
+ dst_ox = (
79
+ self.dst_map.width - self.tiles_nx + visible_tiles_ex + src_ox
80
+ )
81
+
82
+ elif self.vx > 0: # Transition east
83
+ src_tiles_sx -= int(self.progress)
84
+ src_tiles_ex -= int(self.progress)
85
+ dst_tiles_sx = src_tiles_ex
86
+ src_ox = self.src_map.width + min(0, self.progress - self.tiles_nx)
87
+ dst_ox = src_ox - self.src_map.width
88
+
89
+ elif self.vy < 0: # Transition north
90
+ src_tiles_sy += int(self.progress)
91
+ src_tiles_ey += int(self.progress)
92
+ dst_tiles_ey = src_tiles_sy + 1
93
+ src_oy = max(-self.tiles_ny, -self.progress - self._extra_oy)
94
+ dst_oy = (
95
+ self.dst_map.height - self.tiles_ny + visible_tiles_ey + src_oy
96
+ )
97
+
98
+ elif self.vy > 0: # Transition south
99
+ src_tiles_sy -= int(self.progress)
100
+ src_tiles_ey -= int(self.progress)
101
+ dst_tiles_sy = src_tiles_ey
102
+ src_oy = self.src_map.height + min(
103
+ 0, self.progress - self.tiles_ny
104
+ )
105
+ dst_oy = -visible_tiles_ey + self.progress
106
+
107
+ # if layer_pos == 0:
108
+ # print(
109
+ # f"o({ox:.2f}),t({self.tiles_nx}),"
110
+ # f"ss({self.src_map.width}),so({src_ox:.2f}),"
111
+ # f"ts({src_tiles_sx}),te({src_tiles_ex}) -> "
112
+ # f"ds({self.dst_map.width}),do({dst_ox:.2f}),"
113
+ # f"ts({dst_tiles_sx}),te({dst_tiles_ex})"
114
+ # )
115
+ # print(
116
+ # f"o({ox:.2f},{oy:.2f}),"
117
+ # f"ss({self.src_map.width},{self.src_map.height}),so({src_ox:.2f},{src_oy:.2f}),"
118
+ # f"ts({src_tiles_sx},{src_tiles_sy}),te{src_tiles_ex,src_tiles_ey} -> "
119
+ # f"ds({self.dst_map.width},{self.dst_map.height}),do({dst_ox:.2f},{dst_oy:.2f}),"
120
+ # f"ts({dst_tiles_sx},{dst_tiles_sy}),te({dst_tiles_ex},{dst_tiles_ey})"
121
+ # )
122
+ # print(src_oy, dst_oy, self.map_screen_oy)
123
+ self.src_map.draw_self(
124
+ src_ox,
125
+ src_oy,
126
+ src_tiles_sx,
127
+ src_tiles_sy,
128
+ src_tiles_ex,
129
+ src_tiles_ey,
130
+ layer_pos,
131
+ )
132
+ self.dst_map.draw_self(
133
+ dst_ox,
134
+ dst_oy,
135
+ dst_tiles_sx,
136
+ dst_tiles_sy,
137
+ dst_tiles_ex,
138
+ dst_tiles_ey,
139
+ layer_pos,
140
+ )
141
+
142
+ return True
143
+
144
+ def is_solid(self, px: int, py: int) -> bool:
145
+ return False
146
+
147
+ def get_tile(self, px: int, py: int) -> Optional[Tile]:
148
+ return None
File without changes
@@ -0,0 +1,198 @@
1
+ import logging
2
+ import math
3
+ from typing import Any, Dict, Optional
4
+
5
+ from ..types.direction import Direction
6
+ from ..types.graphic_state import GraphicState
7
+ from ..util.constants import TILE_HEIGHT, TILE_WIDTH
8
+
9
+ LOG = logging.getLogger(__name__)
10
+
11
+
12
+ class AnimatedSprite:
13
+ engine = None
14
+ sprite_sets = {}
15
+
16
+ def __init__(
17
+ self,
18
+ tileset_name: str,
19
+ image_name: str,
20
+ sprite_name: Optional[str] = None,
21
+ graphic_state: GraphicState = GraphicState.STANDING,
22
+ facing_direction: Direction = Direction.SOUTH,
23
+ ):
24
+ self._sprite_name = (
25
+ tileset_name if sprite_name is None else sprite_name
26
+ )
27
+
28
+ self._tileset_name = tileset_name
29
+ self.name = image_name
30
+ # TODO: Handle ""
31
+ if tileset_name and image_name and self._sprite_name:
32
+ LOG.info(
33
+ {
34
+ "operation": "load sprite",
35
+ "tileset": tileset_name,
36
+ "image": image_name,
37
+ "sprite": self._sprite_name,
38
+ },
39
+ )
40
+ tileset = self.engine.assets.get_tileset(tileset_name)
41
+
42
+ self.width = tileset.tile_width
43
+ self.height = tileset.tile_height
44
+
45
+ self._sprites: Dict[
46
+ GraphicState, Dict[Direction, Dict[str, Any]]
47
+ ] = self._load_sprites_from_tileset(tileset, self._sprite_name)
48
+
49
+ self._last_direction: Direction = facing_direction
50
+ self._last_graphic_state: GraphicState = graphic_state
51
+ data = self._get_data(
52
+ self._last_graphic_state, self._last_direction
53
+ )
54
+
55
+ self._frame_index: int = 0
56
+ self._timer: float = data["duration"][0]
57
+ else:
58
+ LOG.debug(
59
+ f"Sprite information uncomplete. Tileset={tileset_name}, Image"
60
+ f"={image_name}, Sprite={self._sprite_name}. Will continue without "
61
+ "sprite."
62
+ )
63
+ self.name = self._tileset_name = self._sprite_name = ""
64
+
65
+ def update(
66
+ self,
67
+ elapsed_time: float,
68
+ direction: Direction = Direction.SOUTH,
69
+ graphic_state: GraphicState = GraphicState.STANDING,
70
+ ):
71
+ if not self.name:
72
+ return
73
+
74
+ data = self._get_data(graphic_state, direction)
75
+ # try:
76
+ # self._timer_reset = data["duration"][self._frame_index]
77
+ # except KeyError as err:
78
+ # LOG.exception(
79
+ # f"Data of {self._tileset_name, self.name} is "
80
+ # f"malformed: {data}"
81
+ # )
82
+
83
+ if (
84
+ direction == self._last_direction
85
+ and graphic_state == self._last_graphic_state
86
+ ):
87
+ # No changes, normal case
88
+ self._timer -= elapsed_time
89
+ if self._timer <= 0.0:
90
+ self._frame_index = (self._frame_index + 1) % len(
91
+ data["duration"]
92
+ )
93
+ self._timer += data["duration"][self._frame_index]
94
+
95
+ else:
96
+ self._frame_index = 0
97
+ # Something changed
98
+ # if graphic_state != self._last_graphic_state:
99
+ # State changed
100
+
101
+ self._timer = data["duration"][0]
102
+
103
+ self._last_direction = direction
104
+ self._last_graphic_state = graphic_state
105
+
106
+ def draw_self(self, px: float, py: float, absolute_position: bool = False):
107
+ if self.name == "":
108
+ return
109
+
110
+ data = self._get_data(self._last_graphic_state, self._last_direction)
111
+ if not absolute_position:
112
+ px *= TILE_WIDTH
113
+ py *= TILE_HEIGHT
114
+ px, py = math.floor(px), math.floor(py)
115
+ try:
116
+ self.engine.backend.draw_partial_sprite(
117
+ px,
118
+ py,
119
+ self.name,
120
+ data["ox"][self._frame_index] * self.width,
121
+ data["oy"][self._frame_index] * self.height,
122
+ self.width,
123
+ self.height,
124
+ )
125
+ except KeyError as err:
126
+ LOG.exception(
127
+ f"Data of {self._tileset_name, self.name} is malformed: {data}"
128
+ )
129
+
130
+ def _load_sprites_from_tileset(self, tileset, sprite_name):
131
+ if sprite_name in AnimatedSprite.sprite_sets:
132
+ # Caching
133
+ return AnimatedSprite.sprite_sets[sprite_name]
134
+
135
+ sprites = {}
136
+
137
+ for tile in tileset.tiles:
138
+ if tile.sprite_name != sprite_name:
139
+ continue
140
+
141
+ if tile.animated:
142
+ data = {"duration": [], "ox": [], "oy": []}
143
+ for frame in tile._frames:
144
+ data["duration"].append(frame.duration)
145
+ data["ox"].append(frame.frame_id % tileset.columns)
146
+ data["oy"].append(frame.frame_id // tileset.columns)
147
+ else:
148
+ data = {
149
+ "duration": [1000],
150
+ "ox": [tile.tile_id % tileset.columns],
151
+ "oy": [tile.tile_id // tileset.columns],
152
+ }
153
+
154
+ sprites.setdefault(tile.graphic_state, {})
155
+ sprites[tile.graphic_state][tile.facing_direction] = data
156
+ LOG.debug(
157
+ {
158
+ "operation": "add frames",
159
+ "image": self.name,
160
+ "sprite": sprite_name,
161
+ "graphic_state": tile.graphic_state.name,
162
+ "direction": tile.facing_direction.name,
163
+ "frame_data": data,
164
+ }
165
+ )
166
+
167
+ AnimatedSprite.sprite_sets[sprite_name] = sprites
168
+ return sprites
169
+ # for tile in tileset.tiles
170
+ # Check non-animated tiles if necessary
171
+
172
+ def reset(self):
173
+ self._frame_index = 0
174
+ self._timer = 0.0
175
+
176
+ def _get_data(self, graphic_state, direction):
177
+ if graphic_state == GraphicState.DEFEATED:
178
+ graphic_state = graphic_state.DEAD
179
+
180
+ data = self._sprites.get(
181
+ graphic_state, self._sprites.get(GraphicState.STANDING, {})
182
+ )
183
+ data = data.get(
184
+ direction,
185
+ data.get(Direction.SOUTH, {}),
186
+ )
187
+ if not data:
188
+ try:
189
+ LOG.debug(
190
+ f"Animation of sprite {self._tileset_name,self._sprite_name}"
191
+ f" is empty for {graphic_state.name, direction.name} "
192
+ )
193
+ except Exception:
194
+ # print(graphic_state, direction)
195
+ LOG.exception(graphic_state, direction)
196
+ raise
197
+ data = {"ox": [0], "oy": [0], "duration": [1.0]}
198
+ return data