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.
- mima/__init__.py +1 -0
- mima/backend/__init__.py +1 -0
- mima/backend/pygame_assets.py +345 -0
- mima/backend/pygame_audio.py +75 -0
- mima/backend/pygame_backend.py +399 -0
- mima/backend/pygame_events.py +430 -0
- mima/collision.py +237 -0
- mima/engine.py +197 -0
- mima/maps/__init__.py +0 -0
- mima/maps/template.py +41 -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 +45 -0
- mima/maps/tilemap.py +159 -0
- mima/maps/tileset.py +32 -0
- mima/maps/tileset_info.py +9 -0
- mima/maps/transition_map.py +148 -0
- mima/objects/__init__.py +0 -0
- mima/objects/animated_sprite.py +198 -0
- mima/objects/attribute_effect.py +26 -0
- mima/objects/attributes.py +123 -0
- mima/objects/creature.py +332 -0
- mima/objects/dynamic.py +182 -0
- mima/objects/effects/__init__.py +0 -0
- mima/objects/effects/colorize_screen.py +36 -0
- mima/objects/effects/light.py +107 -0
- mima/objects/effects/walking_on_grass.py +38 -0
- mima/objects/effects/walking_on_water.py +41 -0
- mima/objects/loader.py +103 -0
- mima/objects/projectile.py +86 -0
- mima/objects/sprite.py +110 -0
- mima/objects/world/__init__.py +0 -0
- mima/objects/world/color_gate.py +68 -0
- mima/objects/world/color_switch.py +105 -0
- mima/objects/world/container.py +171 -0
- mima/objects/world/floor_switch.py +111 -0
- mima/objects/world/gate.py +174 -0
- mima/objects/world/light_source.py +124 -0
- mima/objects/world/logic_gate.py +163 -0
- mima/objects/world/movable.py +338 -0
- mima/objects/world/oneway.py +168 -0
- mima/objects/world/pickup.py +88 -0
- mima/objects/world/switch.py +165 -0
- mima/objects/world/teleport.py +288 -0
- mima/scene_engine.py +79 -0
- mima/scripts/__init__.py +2 -0
- mima/scripts/command.py +24 -0
- mima/scripts/commands/__init__.py +0 -0
- mima/scripts/commands/add_quest.py +19 -0
- mima/scripts/commands/change_map.py +15 -0
- mima/scripts/commands/close_dialog.py +8 -0
- mima/scripts/commands/give_item.py +24 -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 +57 -0
- mima/scripts/commands/parallel.py +53 -0
- mima/scripts/commands/play_sound.py +13 -0
- mima/scripts/commands/present_item.py +51 -0
- mima/scripts/commands/progress_quest.py +12 -0
- mima/scripts/commands/quit_game.py +8 -0
- mima/scripts/commands/save_game.py +13 -0
- mima/scripts/commands/screen_fade.py +65 -0
- mima/scripts/commands/serial.py +46 -0
- mima/scripts/commands/set_facing_direction.py +21 -0
- mima/scripts/commands/set_spawn_map.py +14 -0
- mima/scripts/commands/show_choices.py +43 -0
- mima/scripts/commands/show_dialog.py +11 -0
- mima/scripts/commands/take_coins.py +23 -0
- mima/scripts/script_processor.py +40 -0
- mima/states/__init__.py +0 -0
- mima/states/game_state.py +162 -0
- mima/states/quest.py +72 -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 +22 -0
- mima/types/keys.py +16 -0
- mima/types/mode.py +15 -0
- mima/types/nature.py +12 -0
- mima/types/object.py +22 -0
- mima/types/start.py +7 -0
- mima/types/terrain.py +9 -0
- mima/types/weapon_slot.py +6 -0
- mima/usables/__init__.py +0 -0
- mima/usables/item.py +31 -0
- mima/usables/weapon.py +48 -0
- mima/util/__init__.py +1 -0
- mima/util/colors.py +45 -0
- mima/util/constants.py +47 -0
- mima/util/functions.py +13 -0
- mima/util/input_defaults.py +49 -0
- mima/util/logging.py +51 -0
- mima/util/property.py +8 -0
- mima/util/runtime_config.py +133 -0
- mima/view/__init__.py +0 -0
- mima/view/camera.py +51 -0
- mima/view/scene.py +350 -0
- mima_engine-0.1.0.dist-info/METADATA +14 -0
- mima_engine-0.1.0.dist-info/RECORD +114 -0
- mima_engine-0.1.0.dist-info/WHEEL +5 -0
- mima_engine-0.1.0.dist-info/top_level.txt +1 -0
mima/engine.py
ADDED
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
from abc import ABC, abstractmethod
|
|
5
|
+
from typing import Dict
|
|
6
|
+
|
|
7
|
+
from .backend.pygame_assets import PygameAssets
|
|
8
|
+
from .backend.pygame_audio import PygameAudio
|
|
9
|
+
from .backend.pygame_backend import PygameBackend
|
|
10
|
+
from .backend.pygame_events import PygameUserInput
|
|
11
|
+
from .maps.template import Template
|
|
12
|
+
from .maps.tilemap import Tilemap
|
|
13
|
+
from .objects.animated_sprite import AnimatedSprite
|
|
14
|
+
from .objects.creature import Creature
|
|
15
|
+
from .objects.dynamic import Dynamic
|
|
16
|
+
from .objects.sprite import Sprite
|
|
17
|
+
from .scripts import Command, ScriptProcessor
|
|
18
|
+
from .states.quest import Quest
|
|
19
|
+
from .types.mode import Mode
|
|
20
|
+
from .types.gate_color import GateColor
|
|
21
|
+
from .usables.item import Item
|
|
22
|
+
from .util import RuntimeConfig
|
|
23
|
+
from .util.logging import install_trace_logger
|
|
24
|
+
from .view.camera import Camera
|
|
25
|
+
from .view.scene import Scene
|
|
26
|
+
|
|
27
|
+
LOG = logging.getLogger(__name__)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class MimaEngine(ABC):
|
|
31
|
+
def __init__(
|
|
32
|
+
self, init_file: str, platform: str = "PC", caption: str = "MimaEngine"
|
|
33
|
+
):
|
|
34
|
+
self.rtc = RuntimeConfig()
|
|
35
|
+
install_trace_logger()
|
|
36
|
+
|
|
37
|
+
self.backend: PygameBackend = PygameBackend(
|
|
38
|
+
self.rtc, init_file, platform
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
self._caption: str = caption
|
|
42
|
+
self.seconds_total: float = 0.0
|
|
43
|
+
self.app_fps: float = 0.0
|
|
44
|
+
self.game_fps: float = 0.0
|
|
45
|
+
self.elapsed_time: float = 0.00022
|
|
46
|
+
self._app_time: float = 0.0
|
|
47
|
+
|
|
48
|
+
self.mode: Mode = Mode.LOADING
|
|
49
|
+
self.gate_color: GateColor = GateColor.RED
|
|
50
|
+
self.n_gate_colors = 2
|
|
51
|
+
self.script: ScriptProcessor = None
|
|
52
|
+
self.player: Creature
|
|
53
|
+
self.quests: List[Quest] = []
|
|
54
|
+
self._items: Dict[str, Item] = {}
|
|
55
|
+
|
|
56
|
+
def construct(
|
|
57
|
+
self,
|
|
58
|
+
width: int,
|
|
59
|
+
height: int,
|
|
60
|
+
pixel_size: int,
|
|
61
|
+
fullscreen: bool = False,
|
|
62
|
+
target_fps: int = 60,
|
|
63
|
+
resizable: bool = False,
|
|
64
|
+
):
|
|
65
|
+
"""Initialize backend and create a window."""
|
|
66
|
+
AnimatedSprite.engine = self
|
|
67
|
+
Camera.engine = self
|
|
68
|
+
Command.engine = self
|
|
69
|
+
Dynamic.engine = self
|
|
70
|
+
PygameBackend.engine = self
|
|
71
|
+
Quest.engine = self
|
|
72
|
+
Scene.engine = self
|
|
73
|
+
ScriptProcessor.engine = self
|
|
74
|
+
Sprite.engine = self
|
|
75
|
+
Template.engine = self
|
|
76
|
+
Tilemap.engine = self
|
|
77
|
+
Item.engine = self
|
|
78
|
+
|
|
79
|
+
self.script = ScriptProcessor()
|
|
80
|
+
self.backend.init()
|
|
81
|
+
self.backend.construct(
|
|
82
|
+
width, height, pixel_size, fullscreen, target_fps, resizable
|
|
83
|
+
)
|
|
84
|
+
return True
|
|
85
|
+
|
|
86
|
+
def start(self):
|
|
87
|
+
"""Start the main loop"""
|
|
88
|
+
app_frames = 0
|
|
89
|
+
game_frames = 0
|
|
90
|
+
app_seconds = 0.0
|
|
91
|
+
game_seconds = 0.0
|
|
92
|
+
app_frames_total = 0
|
|
93
|
+
game_frames_total = 0
|
|
94
|
+
self.seconds_total = 0.0
|
|
95
|
+
|
|
96
|
+
if self.on_user_create():
|
|
97
|
+
while self.backend.keep_running():
|
|
98
|
+
self.backend.set_caption(
|
|
99
|
+
f"{self._caption} ({self.game_fps:.2f}/{self.app_fps:.2f} fps)"
|
|
100
|
+
)
|
|
101
|
+
self.backend.process_events()
|
|
102
|
+
|
|
103
|
+
if not self.backend.keep_running():
|
|
104
|
+
break
|
|
105
|
+
|
|
106
|
+
if not self.on_user_update(self.elapsed_time):
|
|
107
|
+
print("Error in on_user_update")
|
|
108
|
+
break
|
|
109
|
+
|
|
110
|
+
self.backend.update_display()
|
|
111
|
+
|
|
112
|
+
self._app_time = self.backend.tick()
|
|
113
|
+
self.elapsed_time = min(self._app_time, 1.0 / 30.0)
|
|
114
|
+
|
|
115
|
+
app_seconds += self._app_time
|
|
116
|
+
game_seconds += self.elapsed_time
|
|
117
|
+
app_frames += 1
|
|
118
|
+
game_frames += 1
|
|
119
|
+
|
|
120
|
+
if game_seconds >= 1.0:
|
|
121
|
+
game_frames_total += game_frames
|
|
122
|
+
self.game_fps = game_frames
|
|
123
|
+
game_frames = 0
|
|
124
|
+
game_seconds -= 1.0
|
|
125
|
+
if app_seconds >= 1.0:
|
|
126
|
+
app_frames_total += app_frames
|
|
127
|
+
self.seconds_total += app_seconds
|
|
128
|
+
self.app_fps = app_frames
|
|
129
|
+
app_frames = 0
|
|
130
|
+
app_seconds -= 1.0
|
|
131
|
+
|
|
132
|
+
print(
|
|
133
|
+
f"App/Game Frames total: {app_frames_total}/{game_frames_total}"
|
|
134
|
+
)
|
|
135
|
+
print(f"Seconds total: {self.seconds_total:.3f}")
|
|
136
|
+
print(
|
|
137
|
+
f"Average App/Game FPS: {app_frames_total/self.seconds_total:.3f}/"
|
|
138
|
+
f"{game_frames_total/self.seconds_total:.3f}"
|
|
139
|
+
)
|
|
140
|
+
|
|
141
|
+
self.backend.shutdown()
|
|
142
|
+
|
|
143
|
+
@abstractmethod
|
|
144
|
+
def on_user_update(self, elapsed_time: float) -> bool:
|
|
145
|
+
"""Update."""
|
|
146
|
+
raise NotImplementedError()
|
|
147
|
+
|
|
148
|
+
@abstractmethod
|
|
149
|
+
def on_user_create(self) -> bool:
|
|
150
|
+
raise NotImplementedError()
|
|
151
|
+
|
|
152
|
+
def on_user_terminate(self) -> bool:
|
|
153
|
+
self.backend.terminate = True
|
|
154
|
+
return True
|
|
155
|
+
|
|
156
|
+
@property
|
|
157
|
+
def assets(self) -> PygameAssets:
|
|
158
|
+
return self.backend.assets
|
|
159
|
+
|
|
160
|
+
@property
|
|
161
|
+
def audio(self) -> PygameAudio:
|
|
162
|
+
return self.backend.audio
|
|
163
|
+
|
|
164
|
+
@property
|
|
165
|
+
def keys(self) -> PygameUserInput:
|
|
166
|
+
return self.backend.user_input
|
|
167
|
+
|
|
168
|
+
def get_map(self, map_name: str):
|
|
169
|
+
return self.backend.assets.get_map(map_name)
|
|
170
|
+
|
|
171
|
+
def load_item(self, item: Item):
|
|
172
|
+
LOG.debug(f"Loading item {item.name}.")
|
|
173
|
+
self._items[item.name] = item
|
|
174
|
+
|
|
175
|
+
def get_item(self, item_id: str):
|
|
176
|
+
try:
|
|
177
|
+
return self._items[item_id]
|
|
178
|
+
except KeyError:
|
|
179
|
+
LOG.error(f"Item '{item_id}' is not defined!")
|
|
180
|
+
raise
|
|
181
|
+
|
|
182
|
+
def progress_quest(self, quest_name: str, new_state: int):
|
|
183
|
+
for quest in self.quests:
|
|
184
|
+
if quest.name == quest_name:
|
|
185
|
+
quest.state = new_state
|
|
186
|
+
|
|
187
|
+
def on_enter_background(self):
|
|
188
|
+
LOG.debug("About to enter background")
|
|
189
|
+
|
|
190
|
+
def on_entered_background(self):
|
|
191
|
+
LOG.debug("Entered background")
|
|
192
|
+
|
|
193
|
+
def on_enter_foreground(self):
|
|
194
|
+
LOG.debug("About to enter foreground")
|
|
195
|
+
|
|
196
|
+
def on_entered_foreground(self):
|
|
197
|
+
LOG.debug("Entered foreground")
|
mima/maps/__init__.py
ADDED
|
File without changes
|
mima/maps/template.py
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import TYPE_CHECKING, Dict, Optional
|
|
4
|
+
|
|
5
|
+
from ..util.functions import strtobool
|
|
6
|
+
from ..util.property import Property
|
|
7
|
+
|
|
8
|
+
if TYPE_CHECKING:
|
|
9
|
+
from ..engine import MimaEngine
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class Template:
|
|
13
|
+
engine: MimaEngine
|
|
14
|
+
|
|
15
|
+
def __init__(self, name: str):
|
|
16
|
+
self.name: str = name
|
|
17
|
+
self.properties: Dict[str, Property] = {}
|
|
18
|
+
|
|
19
|
+
def get_string(self, key: str, default_val: str = "") -> str:
|
|
20
|
+
if key in self.properties:
|
|
21
|
+
return self.properties[key].value
|
|
22
|
+
else:
|
|
23
|
+
return default_val
|
|
24
|
+
|
|
25
|
+
def get_int(self, key: str, default_val: int = 0) -> int:
|
|
26
|
+
if key in self.properties:
|
|
27
|
+
return int(self.properties[key].value)
|
|
28
|
+
else:
|
|
29
|
+
return default_val
|
|
30
|
+
|
|
31
|
+
def get_float(self, key: str, default_val: float = 0.0) -> float:
|
|
32
|
+
if key in self.properties:
|
|
33
|
+
return float(self.properties[key].value)
|
|
34
|
+
else:
|
|
35
|
+
return default_val
|
|
36
|
+
|
|
37
|
+
def get_bool(self, key: str, default_val: bool = False) -> bool:
|
|
38
|
+
if key in self.properties:
|
|
39
|
+
return bool(strtobool(self.properties[key].value))
|
|
40
|
+
else:
|
|
41
|
+
return default_val
|
mima/maps/tile.py
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
from ..types.direction import Direction
|
|
2
|
+
from ..types.graphic_state import GraphicState
|
|
3
|
+
from ..types.terrain import Terrain
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class Tile:
|
|
7
|
+
def __init__(self):
|
|
8
|
+
self.basic_tile_id: int = 0
|
|
9
|
+
self.tile_id: int = 0
|
|
10
|
+
self.solid: bool = False
|
|
11
|
+
self.animated: bool = False
|
|
12
|
+
self.pz: float = 0.0
|
|
13
|
+
self.z_height: float = 0.0
|
|
14
|
+
self.terrain: Terrain = Terrain.DEFAULT
|
|
15
|
+
self.facing_direction: Direction = Direction.SOUTH
|
|
16
|
+
self.graphic_state: GraphicState = GraphicState.STANDING
|
|
17
|
+
self.sprite_name: str = ""
|
|
18
|
+
|
|
19
|
+
def update(self, elapsed_time: float) -> bool:
|
|
20
|
+
return True
|
mima/maps/tile_info.py
ADDED
mima/maps/tile_layer.py
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import math
|
|
2
|
+
from typing import List
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class TileLayer:
|
|
6
|
+
def __init__(self):
|
|
7
|
+
self.name: str = "Unnamed Layer"
|
|
8
|
+
self.layer_id: int = 0
|
|
9
|
+
self.width: int = 0
|
|
10
|
+
self.height: int = 0
|
|
11
|
+
self.layer_pos: int = 0
|
|
12
|
+
self.speed_x: float = 0.0
|
|
13
|
+
self.speed_y: float = 0.0
|
|
14
|
+
self.layer_ox: float = 0.0
|
|
15
|
+
self.layer_oy: float = 0.0
|
|
16
|
+
self.parallax_x: float = 0.0 # Not used yet
|
|
17
|
+
self.parallax_y: float = 0.0 # Not used yet
|
|
18
|
+
|
|
19
|
+
self.indices: List[int] = []
|
|
20
|
+
|
|
21
|
+
def update(self, elapsed_time: float):
|
|
22
|
+
self.layer_ox += self.speed_x * elapsed_time
|
|
23
|
+
self.layer_oy += self.speed_y * elapsed_time
|
|
24
|
+
|
|
25
|
+
if self.layer_ox > self.width:
|
|
26
|
+
self.layer_ox -= self.width
|
|
27
|
+
if self.layer_ox < 0:
|
|
28
|
+
self.layer_ox += self.width
|
|
29
|
+
if self.layer_oy > self.height:
|
|
30
|
+
self.layer_oy -= self.height
|
|
31
|
+
if self.layer_oy < 0:
|
|
32
|
+
self.layer_oy += self.height
|
|
33
|
+
|
|
34
|
+
def get_index(self, px: float, py: float) -> int:
|
|
35
|
+
if self.layer_ox != 0.0:
|
|
36
|
+
px = math.floor(px - self.layer_ox)
|
|
37
|
+
if px > self.width:
|
|
38
|
+
px -= self.width
|
|
39
|
+
while px < 0:
|
|
40
|
+
px += self.width
|
|
41
|
+
|
|
42
|
+
if self.layer_oy != 0.0:
|
|
43
|
+
py = math.floor(py - self.layer_oy)
|
|
44
|
+
if py > self.height:
|
|
45
|
+
py -= self.height
|
|
46
|
+
while py < 0:
|
|
47
|
+
py += self.height
|
|
48
|
+
|
|
49
|
+
if 0 <= px < self.width and 0 <= py < self.height:
|
|
50
|
+
return self.indices[py * self.width + px]
|
|
51
|
+
else:
|
|
52
|
+
return 0
|
|
File without changes
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import csv
|
|
4
|
+
from io import StringIO
|
|
5
|
+
from typing import TYPE_CHECKING, List
|
|
6
|
+
|
|
7
|
+
from ..tile_layer import TileLayer
|
|
8
|
+
|
|
9
|
+
if TYPE_CHECKING:
|
|
10
|
+
from xml.etree.ElementTree import Element
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class TiledLayer(TileLayer):
|
|
14
|
+
def __init__(self, l_xtree: Element):
|
|
15
|
+
super().__init__()
|
|
16
|
+
|
|
17
|
+
self.name: str = l_xtree.attrib["name"]
|
|
18
|
+
self.layer_id: int = int(l_xtree.attrib["id"])
|
|
19
|
+
self.type: str = l_xtree.attrib.get("class", "NormalLayer")
|
|
20
|
+
if "Foreground" in self.type:
|
|
21
|
+
# FIXME: Hack, propertytypes.json should be read and applied
|
|
22
|
+
self.layer_pos = 1
|
|
23
|
+
self.width: int = int(l_xtree.attrib["width"])
|
|
24
|
+
self.height: int = int(l_xtree.attrib["height"])
|
|
25
|
+
self.indices: List[int] = []
|
|
26
|
+
|
|
27
|
+
layer_data = l_xtree.findall("data")[0]
|
|
28
|
+
reader = csv.reader(StringIO(layer_data.text), delimiter=",")
|
|
29
|
+
|
|
30
|
+
for row in reader:
|
|
31
|
+
if len(row) <= 0:
|
|
32
|
+
continue
|
|
33
|
+
|
|
34
|
+
for entry in row:
|
|
35
|
+
try:
|
|
36
|
+
self.indices.append(int(entry))
|
|
37
|
+
except ValueError:
|
|
38
|
+
pass # Empty string
|
|
39
|
+
|
|
40
|
+
property_data = l_xtree.findall("properties")
|
|
41
|
+
for pdata in property_data:
|
|
42
|
+
properties = pdata.findall("property")
|
|
43
|
+
|
|
44
|
+
for prop in properties:
|
|
45
|
+
if prop.attrib["name"] == "layer":
|
|
46
|
+
self.layer_pos = int(prop.attrib["value"])
|
|
47
|
+
if prop.attrib["name"] == "speed":
|
|
48
|
+
self.speed_x = float(prop.attrib.get("value", 0.0))
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
import os
|
|
5
|
+
from typing import List
|
|
6
|
+
from xml.etree import ElementTree
|
|
7
|
+
|
|
8
|
+
from ..tilemap import Tilemap
|
|
9
|
+
from ..tileset_info import TilesetInfo
|
|
10
|
+
from .tiled_layer import TiledLayer
|
|
11
|
+
from .tiled_objectgroup import TiledObjectgroup
|
|
12
|
+
from ...util.property import Property
|
|
13
|
+
|
|
14
|
+
# if TYPE_CHECKING:
|
|
15
|
+
# from ..engine import Avare
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
LOG = logging.getLogger(__name__)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class TiledMap(Tilemap):
|
|
22
|
+
def __init__(self, name: str, filename: str = ""):
|
|
23
|
+
super().__init__(name)
|
|
24
|
+
|
|
25
|
+
if filename == "":
|
|
26
|
+
filename = f"{name}.tmx"
|
|
27
|
+
|
|
28
|
+
if not os.path.isfile(filename):
|
|
29
|
+
filename = os.path.join(
|
|
30
|
+
self.engine.backend.data_path,
|
|
31
|
+
"maps",
|
|
32
|
+
os.path.split(filename)[-1],
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
self._objects: List[TiledObjectgroup] = []
|
|
36
|
+
|
|
37
|
+
if not os.path.isfile(filename):
|
|
38
|
+
filename = os.path.join(
|
|
39
|
+
self.engine.backend.data_path, "maps", filename
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
LOG.info("Loading map %s from TMX file '%s' ...", name, filename)
|
|
43
|
+
tree = ElementTree.parse(filename)
|
|
44
|
+
LOG.debug("Loaded file %s successfully.", filename)
|
|
45
|
+
root = tree.getroot()
|
|
46
|
+
|
|
47
|
+
LOG.debug("Loading map properties ...")
|
|
48
|
+
self.width = int(root.attrib["width"])
|
|
49
|
+
self.height = int(root.attrib["height"])
|
|
50
|
+
self.tile_width = int(root.attrib["tilewidth"])
|
|
51
|
+
self.tile_height = int(root.attrib["tileheight"])
|
|
52
|
+
|
|
53
|
+
LOG.debug("Loading properties ...")
|
|
54
|
+
properties = root.findall("properties")
|
|
55
|
+
if properties:
|
|
56
|
+
properties = properties[0].findall("property")
|
|
57
|
+
for p in properties:
|
|
58
|
+
pname = p.attrib["name"]
|
|
59
|
+
self.properties[pname] = Property(
|
|
60
|
+
name=pname,
|
|
61
|
+
dtype=p.attrib.get("type", "str"),
|
|
62
|
+
value=p.attrib["value"]
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
LOG.debug("Loading tilesets ...")
|
|
66
|
+
tilesets = root.findall("tileset") # Only one tileset
|
|
67
|
+
for tileset in tilesets:
|
|
68
|
+
tname = os.path.split(tileset.attrib["source"])[-1][:-4]
|
|
69
|
+
first_gid = int(tileset.attrib["firstgid"])
|
|
70
|
+
self._tilesets.append(
|
|
71
|
+
TilesetInfo(
|
|
72
|
+
tileset=self.engine.assets.get_tileset(tname),
|
|
73
|
+
first_gid=first_gid,
|
|
74
|
+
)
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
LOG.debug("Loading layers ...")
|
|
78
|
+
layers = root.findall("layer")
|
|
79
|
+
for layer in layers:
|
|
80
|
+
self._layers.append(TiledLayer(layer))
|
|
81
|
+
|
|
82
|
+
LOG.debug("Loading objects ...")
|
|
83
|
+
objectgroups = root.findall("objectgroup")
|
|
84
|
+
for objectgroup in objectgroups:
|
|
85
|
+
self._objects.append(TiledObjectgroup(objectgroup))
|
|
86
|
+
|
|
87
|
+
LOG.info("Map %s successfully loaded.", self.name)
|
|
88
|
+
|
|
89
|
+
@property
|
|
90
|
+
def objects(self):
|
|
91
|
+
all_objects = []
|
|
92
|
+
for group in self._objects:
|
|
93
|
+
all_objects += group.objects
|
|
94
|
+
|
|
95
|
+
return all_objects
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
from typing import TYPE_CHECKING
|
|
5
|
+
|
|
6
|
+
from ...util.property import Property
|
|
7
|
+
from ..template import Template
|
|
8
|
+
|
|
9
|
+
if TYPE_CHECKING:
|
|
10
|
+
from xml.etree.ElementTree import Element
|
|
11
|
+
|
|
12
|
+
from .tiled_template import TiledTemplate
|
|
13
|
+
from .tiled_tileset import TiledTileset
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class TiledObject(Template):
|
|
17
|
+
def __init__(self, o_xtree: Element):
|
|
18
|
+
super().__init__("TiledObject")
|
|
19
|
+
self.object_id: int = int(o_xtree.attrib["id"])
|
|
20
|
+
|
|
21
|
+
self.name: str = o_xtree.attrib.get("name", "Unnamed")
|
|
22
|
+
self.type: str = o_xtree.attrib.get(
|
|
23
|
+
"type", o_xtree.attrib.get("class", "Untyped")
|
|
24
|
+
)
|
|
25
|
+
self.px: float = float(o_xtree.attrib["x"])
|
|
26
|
+
self.py: float = float(o_xtree.attrib["y"])
|
|
27
|
+
|
|
28
|
+
tsource = o_xtree.attrib.get("template", None)
|
|
29
|
+
if tsource is not None:
|
|
30
|
+
tname = os.path.split(tsource)[-1].split(".")[0]
|
|
31
|
+
tpl: TiledTemplate = self.engine.assets.get_template(tname)
|
|
32
|
+
|
|
33
|
+
self.name = tpl.oname
|
|
34
|
+
self.type = tpl.otype
|
|
35
|
+
self.width = tpl.width
|
|
36
|
+
self.height = tpl.height
|
|
37
|
+
# Templates' y positions are bottom instead of top
|
|
38
|
+
self.py -= self.height
|
|
39
|
+
|
|
40
|
+
for key, prop in tpl.properties.items():
|
|
41
|
+
self.properties[key] = Property(
|
|
42
|
+
name=prop.name, dtype=prop.dtype, value=prop.value
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
ts: TiledTileset = self.engine.assets.get_tileset(tpl.tileset_name)
|
|
46
|
+
self.properties["tileset_name"] = Property(
|
|
47
|
+
name="tileset_name", dtype="str", value=tpl.tileset_name
|
|
48
|
+
)
|
|
49
|
+
self.properties["image_name"] = Property(
|
|
50
|
+
name="image_name", dtype="str", value=ts.sprite_name
|
|
51
|
+
)
|
|
52
|
+
self.properties["sprite_offset_x"] = Property(
|
|
53
|
+
name="sprite_offset_x",
|
|
54
|
+
dtype="int",
|
|
55
|
+
value=f"{(tpl.gid - tpl.first_gid) % ts.columns}",
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
self.properties["sprite_offset_y"] = Property(
|
|
59
|
+
name="sprite_offset_y",
|
|
60
|
+
dtype="int",
|
|
61
|
+
value=f"{(tpl.gid - tpl.first_gid) // ts.columns}",
|
|
62
|
+
)
|
|
63
|
+
else:
|
|
64
|
+
self.width = float(o_xtree.attrib["width"])
|
|
65
|
+
self.height = float(o_xtree.attrib["height"])
|
|
66
|
+
|
|
67
|
+
if self.type == "container":
|
|
68
|
+
self.engine.total_chests += 1
|
|
69
|
+
props = o_xtree.findall("properties")
|
|
70
|
+
if props:
|
|
71
|
+
props = props[0].findall("property")
|
|
72
|
+
|
|
73
|
+
for p in props:
|
|
74
|
+
pname = p.attrib["name"]
|
|
75
|
+
self.properties[pname] = Property(
|
|
76
|
+
name=pname,
|
|
77
|
+
dtype=p.attrib.get("type", "str"),
|
|
78
|
+
value=p.attrib["value"],
|
|
79
|
+
)
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import TYPE_CHECKING, List
|
|
4
|
+
|
|
5
|
+
from .tiled_object import TiledObject
|
|
6
|
+
|
|
7
|
+
if TYPE_CHECKING:
|
|
8
|
+
from xml.etree.ElementTree import Element
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class TiledObjectgroup:
|
|
12
|
+
def __init__(self, o_xtree: Element):
|
|
13
|
+
self.name: str = o_xtree.attrib["name"]
|
|
14
|
+
self.layer_id: int = int(o_xtree.attrib["id"])
|
|
15
|
+
|
|
16
|
+
self._objects: List[TiledObject] = []
|
|
17
|
+
|
|
18
|
+
objects = o_xtree.findall("object")
|
|
19
|
+
|
|
20
|
+
for obj in objects:
|
|
21
|
+
self._objects.append(TiledObject(obj))
|
|
22
|
+
|
|
23
|
+
@property
|
|
24
|
+
def objects(self):
|
|
25
|
+
return self._objects
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import os
|
|
3
|
+
from xml.etree import ElementTree
|
|
4
|
+
|
|
5
|
+
from ...util.property import Property
|
|
6
|
+
from ..template import Template
|
|
7
|
+
|
|
8
|
+
LOG = logging.getLogger(__name__)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class TiledTemplate(Template):
|
|
12
|
+
def __init__(self, name: str, filename: str):
|
|
13
|
+
super().__init__(name)
|
|
14
|
+
|
|
15
|
+
LOG.info(
|
|
16
|
+
"Loading template %s from TX file at '%s' ...",
|
|
17
|
+
name,
|
|
18
|
+
filename,
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
tree = ElementTree.parse(filename)
|
|
22
|
+
LOG.debug("Loaded file %s successfully.", filename)
|
|
23
|
+
|
|
24
|
+
root = tree.getroot()
|
|
25
|
+
tileset = root.findall("tileset")[0]
|
|
26
|
+
LOG.debug("Loading tileset properties ...")
|
|
27
|
+
self.first_gid = int(tileset.attrib["firstgid"])
|
|
28
|
+
self.tileset_name = os.path.split(tileset.attrib["source"])[-1].split(
|
|
29
|
+
"."
|
|
30
|
+
)[0]
|
|
31
|
+
|
|
32
|
+
obj = root.findall("object")[0]
|
|
33
|
+
self.oname = obj.attrib.get("name", "Unnamed")
|
|
34
|
+
self.otype = obj.attrib.get("type", obj.attrib.get("class", "Untyped"))
|
|
35
|
+
self.gid = int(obj.attrib["gid"])
|
|
36
|
+
self.width = int(obj.attrib["width"])
|
|
37
|
+
self.height = int(obj.attrib["height"])
|
|
38
|
+
|
|
39
|
+
props = obj.findall("properties")
|
|
40
|
+
if props:
|
|
41
|
+
props = props[0].findall("property")
|
|
42
|
+
|
|
43
|
+
for p in props:
|
|
44
|
+
pname = p.attrib["name"]
|
|
45
|
+
self.properties[pname] = Property(
|
|
46
|
+
name=pname,
|
|
47
|
+
dtype=p.attrib.get("type", "str"),
|
|
48
|
+
value=p.attrib["value"],
|
|
49
|
+
)
|