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,163 @@
1
+ from enum import Enum
2
+ from typing import List, Optional, Union
3
+
4
+ from ...types.alignment import Alignment
5
+ from ...types.direction import Direction
6
+ from ...types.graphic_state import GraphicState
7
+ from ...types.nature import Nature
8
+ from ...types.object import ObjectType
9
+ from ...util.constants import TILE_HEIGHT, TILE_WIDTH
10
+ from ..animated_sprite import AnimatedSprite
11
+ from ..dynamic import Dynamic
12
+
13
+
14
+ class LogicFunction(Enum):
15
+ LOGIC_PASS = 0
16
+ LOGIC_NOT = 1
17
+ LOGIC_AND = 2
18
+ LOGIC_OR = 3
19
+ LOGIC_NAND = 4
20
+ LOGIC_NOR = 5
21
+ LOGIC_XOR = 6
22
+ LOGIC_IMPL = 7
23
+ LOGIC_EQUI = 8
24
+
25
+
26
+ class LogicGate(Dynamic):
27
+ def __init__(
28
+ self,
29
+ px: float,
30
+ py: float,
31
+ tileset_name: str,
32
+ image_name: str,
33
+ sprite_name: str,
34
+ graphic_state: GraphicState,
35
+ facing_direction: Direction,
36
+ mode=LogicFunction.LOGIC_PASS,
37
+ initial_signal=False,
38
+ visible=False,
39
+ dyn_id=-1,
40
+ name="Logic Gate",
41
+ ):
42
+ super().__init__(name, px, py, dyn_id)
43
+
44
+ self.sprite = AnimatedSprite(
45
+ tileset_name,
46
+ image_name,
47
+ sprite_name,
48
+ graphic_state,
49
+ facing_direction,
50
+ )
51
+ self.type = ObjectType.LOGIC_GATE
52
+ self.alignment = Alignment.NEUTRAL
53
+ self.graphic_state = graphic_state
54
+ self.facing_direction = facing_direction
55
+ self.visible = visible
56
+
57
+ self.input_id1: int = -1
58
+ self.input_id2: int = -1
59
+
60
+ self.mode = mode
61
+ self.signal: bool = False
62
+ self.last_signal: bool = False
63
+ self.input1: bool = False
64
+ self.input2: bool = False
65
+
66
+ self.listener_ids: List[int] = []
67
+ self.listeners: List[Dynamic] = []
68
+ self.send_initial_signal: bool = initial_signal
69
+ self.state_changed: bool = False
70
+
71
+ def update(self, elapsed_time: float, target: Optional[Dynamic] = None):
72
+ if self.mode == LogicFunction.LOGIC_NOT:
73
+ self.signal = not self.input1
74
+ elif self.mode == LogicFunction.LOGIC_AND:
75
+ self.signal = self.input1 and self.input2
76
+ elif self.mode == LogicFunction.LOGIC_OR:
77
+ self.signal = self.input1 or self.input2
78
+ else:
79
+ self.signal = self.input1
80
+
81
+ if self.last_signal != self.signal:
82
+ self.state_changed = True
83
+ else:
84
+ self.state_changed = False
85
+
86
+ if self.send_initial_signal or self.state_changed:
87
+ self.send_signal(self.signal)
88
+ self.send_initial_signal = False
89
+
90
+ self.last_signal = self.signal
91
+
92
+ self.graphic_state = (
93
+ GraphicState.ON if self.signal else GraphicState.OFF
94
+ )
95
+ self.sprite.update(
96
+ elapsed_time, self.facing_direction, self.graphic_state
97
+ )
98
+
99
+ def on_interaction(self, target: Dynamic, nature: Nature):
100
+ if nature == Nature.SIGNAL:
101
+ if target.dyn_id == self.input_id1:
102
+ self.input1 = True
103
+ elif target.dyn_id == self.input_id2:
104
+ self.input2 = True
105
+ else:
106
+ return False
107
+ return True
108
+ elif nature == Nature.NO_SIGNAL:
109
+ if target.dyn_id == self.input_id1:
110
+ self.input1 = False
111
+ elif target.dyn_id == self.input_id2:
112
+ self.input2 = False
113
+ else:
114
+ return False
115
+ return True
116
+
117
+ return False
118
+
119
+ def draw_self(self, ox: float, oy: float):
120
+ if self.visible:
121
+ self.sprite.draw_self(self.px - ox, self.py - oy)
122
+
123
+ def send_signal(self, nature: Union[Nature, bool]):
124
+ if isinstance(nature, bool):
125
+ nature = Nature.SIGNAL if nature else Nature.NO_SIGNAL
126
+
127
+ for listener in self.listeners:
128
+ listener.on_interaction(self, nature)
129
+
130
+ @staticmethod
131
+ def load_from_tiled_object(obj, px, py, width, height):
132
+ logic = LogicGate(
133
+ px=px,
134
+ py=py,
135
+ tileset_name=obj.get_string("tileset_name"),
136
+ image_name=obj.get_string("tileset_name"),
137
+ sprite_name=obj.get_string("sprite_name"),
138
+ graphic_state=GraphicState[
139
+ obj.get_string("graphic_state", "closed").upper()
140
+ ],
141
+ facing_direction=Direction[
142
+ obj.get_string("facing_direction", "south").upper()
143
+ ],
144
+ mode=LogicFunction[obj.get_string("mode", "logic_pass").upper()],
145
+ initial_signal=obj.get_bool("initial_signal", False),
146
+ visible=obj.get_bool("visible", True),
147
+ dyn_id=obj.object_id,
148
+ name=obj.name,
149
+ )
150
+
151
+ # logic.send_initial_signal =
152
+ logic.sprite.width = int(width * TILE_WIDTH)
153
+ logic.sprite.height = int(height * TILE_HEIGHT)
154
+
155
+ ctr = 1
156
+ while True:
157
+ listener_id = obj.get_int(f"output{ctr}", -1)
158
+ if listener_id < 0:
159
+ break
160
+ logic.listener_ids.append(listener_id)
161
+ ctr += 1
162
+
163
+ return [logic]
@@ -0,0 +1,338 @@
1
+ import math
2
+ from typing import List, Optional
3
+
4
+ from ...types.alignment import Alignment
5
+ from ...types.damage import Damage
6
+ from ...types.direction import Direction
7
+ from ...types.graphic_state import GraphicState
8
+ from ...types.keys import Key as K
9
+ from ...types.nature import Nature
10
+ from ...types.object import ObjectType
11
+ from ...util.colors import BLACK
12
+ from ...util.constants import TILE_HEIGHT, TILE_WIDTH
13
+ from ..animated_sprite import AnimatedSprite
14
+ from ..dynamic import Dynamic
15
+ from ..projectile import Projectile
16
+
17
+
18
+ class Movable(Dynamic):
19
+ def __init__(
20
+ self,
21
+ px: float,
22
+ py: float,
23
+ tileset_name: str,
24
+ image_name: str,
25
+ sprite_name: str,
26
+ facing_direction: Direction,
27
+ graphic_state: GraphicState,
28
+ mrange: float,
29
+ liftable: bool,
30
+ destroyable: bool,
31
+ movable: bool,
32
+ intangible: bool,
33
+ force_collision_check: bool,
34
+ dyn_id=-1,
35
+ name="Movable",
36
+ ):
37
+ super().__init__(name, px, py, dyn_id)
38
+
39
+ self.sprite = AnimatedSprite(
40
+ tileset_name,
41
+ image_name,
42
+ sprite_name,
43
+ graphic_state,
44
+ facing_direction,
45
+ )
46
+ self.type = ObjectType.MOVABLE
47
+ self.alignment = Alignment.NEUTRAL
48
+ self.facing_direction = facing_direction
49
+ self.graphic_state = graphic_state
50
+ self.solid_vs_map = True
51
+
52
+ self.range = mrange
53
+ self.total_range = 0
54
+ self.spawn_px = px
55
+ self.spawn_py = py
56
+
57
+ self.liftable = liftable
58
+ self.destroyable = destroyable
59
+ self.movable = movable
60
+ self.intangible = intangible
61
+ self.moving = False
62
+ self.lift_started = False
63
+ self.lifted = False
64
+ self.thrown = False
65
+ self.visible = True
66
+ self.visible_pz = 0.0
67
+ self.actor: Optional[Dynamic] = None
68
+ self.vx_mask = 0
69
+ self.vy_mask = 0
70
+ self.move_direction: str = ""
71
+ self.onscreen_collision_skippable = (
72
+ not self.movable and not force_collision_check
73
+ )
74
+
75
+ def update(self, elapsed_time: float, target: Optional[Dynamic] = None):
76
+ if self.intangible:
77
+ self.solid_vs_dyn = False
78
+ else:
79
+ self.solid_vs_dyn = (
80
+ self.visible and not self.lifted and not self.thrown
81
+ )
82
+ if self.pz > 1.0:
83
+ self.solid_vs_map = False
84
+ else:
85
+ self.solid_vs_map = True
86
+
87
+ self.sprite.update(
88
+ elapsed_time, self.facing_direction, self.graphic_state
89
+ )
90
+
91
+ if self.thrown:
92
+ return self._throw()
93
+
94
+ self.vx = self.vy = 0.0
95
+
96
+ if self.moving:
97
+ return self._move()
98
+
99
+ if self.lift_started or self.lifted:
100
+ return self._lift()
101
+
102
+ def on_interaction(self, target: Dynamic, nature: Nature):
103
+ if self.moving:
104
+ return False
105
+ if self.lifted:
106
+ return False
107
+
108
+ if target.type == ObjectType.PLAYER:
109
+ if nature == Nature.TALK and self.liftable and target.can_lift:
110
+ self.lift_started = True
111
+ self.actor = target
112
+ self.solid_vs_dyn = False
113
+ target.can_attack = False
114
+ return True
115
+
116
+ if (
117
+ self.movable
118
+ and self.visible
119
+ and self.total_range < self.range
120
+ and target.graphic_state
121
+ in [
122
+ GraphicState.WALKING,
123
+ GraphicState.PUSHING,
124
+ ]
125
+ ):
126
+ if (
127
+ target.facing_direction == Direction.WEST
128
+ and self.engine.keys.key_held(K.LEFT)
129
+ and target.vy == 0
130
+ ):
131
+ self.move_direction = K.LEFT
132
+ self.vx_mask = -1
133
+ elif (
134
+ target.facing_direction == Direction.EAST
135
+ and self.engine.keys.key_held(K.RIGHT)
136
+ and target.vy == 0
137
+ ):
138
+ self.move_direction = K.RIGHT
139
+ self.vx_mask = 1
140
+ elif (
141
+ target.facing_direction == Direction.SOUTH
142
+ and self.engine.keys.key_held(K.DOWN)
143
+ and target.vx == 0
144
+ ):
145
+ self.move_direction = K.DOWN
146
+ self.vy_mask = 1
147
+ elif (
148
+ target.facing_direction == Direction.NORTH
149
+ and self.engine.keys.key_held(K.UP)
150
+ and target.vx == 0
151
+ ):
152
+ self.move_direction = K.UP
153
+ self.vy_mask = -1
154
+ else:
155
+ return False
156
+
157
+ self.actor = target
158
+ self.moving = True
159
+ self.actor.lock_graphic_state(GraphicState.PUSHING)
160
+ return True
161
+
162
+ elif target.type == ObjectType.PROJECTILE:
163
+ if self.destroyable:
164
+ damage = target.damage - self.attributes.defense[target.dtype]
165
+ if damage > 0:
166
+ self.kill()
167
+ if target.one_hit:
168
+ target.kill()
169
+ return True
170
+
171
+ elif nature == Nature.SIGNAL:
172
+ self.visible = False
173
+ return True
174
+
175
+ elif nature == Nature.NO_SIGNAL:
176
+ self.visible = True
177
+ return True
178
+
179
+ return False
180
+
181
+ def draw_self(self, ox: float, oy: float):
182
+ if not self.visible:
183
+ return
184
+
185
+ py = self.py - oy - (self.pz + self.visible_pz)
186
+
187
+ if self.pz != 0:
188
+ self.engine.backend.fill_circle(
189
+ (self.px - ox + 0.5) * self.sprite.width,
190
+ (self.py - oy + 0.7) * self.sprite.height,
191
+ 0.3125 * self.sprite.width,
192
+ BLACK,
193
+ )
194
+ self.sprite.draw_self(self.px - ox, py)
195
+
196
+ def _throw(self):
197
+ if self.pz < 0.5:
198
+ self.solid_vs_dyn = True
199
+ if self.pz > 0:
200
+ return
201
+
202
+ self._create_impact()
203
+
204
+ # self.solid_vs_dyn = True
205
+ self.thrown = False
206
+ self.vx = self.vy = 0.0
207
+ if self.destroyable:
208
+ self.kill()
209
+ return
210
+
211
+ def _move(self):
212
+ if self.actor.graphic_state == GraphicState.PUSHING:
213
+ stop_moving = False
214
+ for button in [K.DOWN, K.LEFT, K.UP, K.RIGHT]:
215
+ if button == self.move_direction:
216
+ if self.engine.keys.key_held(button):
217
+ self.vx = self.vx_mask
218
+ self.vy = self.vy_mask
219
+ else:
220
+ if self.engine.keys.key_held(button):
221
+ stop_moving = True
222
+ self.vx = 0
223
+ self.vy = 0
224
+ break
225
+ if (
226
+ abs(self.actor.px - self.px) > 1.1
227
+ or abs(self.actor.py - self.py) > 1.1
228
+ ):
229
+ stop_moving = True
230
+
231
+ if not stop_moving and abs(self.vx) > abs(self.vy):
232
+ self.vy = 0
233
+ elif not stop_moving and abs(self.vy) > abs(self.vx):
234
+ self.vx = 0
235
+ else:
236
+ self.vx = self.vy = 0.0
237
+
238
+ dx = self.px - self.spawn_px
239
+ dy = self.py - self.spawn_py
240
+ self.total_range = math.sqrt(dx * dx + dy * dy)
241
+
242
+ if self.total_range >= self.range:
243
+ self.vx = self.vy = 0.0
244
+
245
+ if self.vx == 0.0 and self.vy == 0.0:
246
+ self.moving = False
247
+ self.vx_mask = self.vy_mask = 0
248
+ self.actor.unlock_graphic_state()
249
+ self.engine.audio.stop_sound("move_block")
250
+ else:
251
+ self.engine.audio.play_sound("move_block")
252
+ return
253
+
254
+ def _lift(self):
255
+ if self.lifted and self.engine.keys.new_key_press(K.A):
256
+ # Throw away
257
+ self.vx = self.vy = 0
258
+ if self.actor.facing_direction == Direction.SOUTH:
259
+ self.vy = 4
260
+ if self.actor.facing_direction == Direction.WEST:
261
+ self.vx = -4
262
+ if self.actor.facing_direction == Direction.NORTH:
263
+ self.vy = -4
264
+ if self.actor.facing_direction == Direction.EAST:
265
+ self.vx = 4
266
+
267
+ self.vz = 6.0
268
+ self.pz = self.actor.pz + 0.9
269
+ self.visible_pz = 0
270
+ self.actor.can_attack = True
271
+ self.lifted = False
272
+ self.actor = None
273
+ self.thrown = True
274
+
275
+ elif self.lift_started and self.engine.keys.new_key_release(K.A):
276
+ self.lift_started = False
277
+ self.lifted = True
278
+ self.solid_vs_dyn = False
279
+ else:
280
+ self.solid_vs_dyn = False
281
+ self.px = self.actor.px
282
+ self.py = self.actor.py
283
+ self.visible_pz = self.actor.pz + 0.9
284
+ self.vx = self.vy = 0.0
285
+
286
+ def _create_impact(self):
287
+ impact: List[Projectile] = []
288
+ impact.append(
289
+ Projectile(self.px + 0.5, self.py + 0.5, 0, 0, 0.2, self.alignment)
290
+ )
291
+ impact.append(
292
+ Projectile(self.px - 0.5, self.py + 0.5, 0, 0, 0.2, self.alignment)
293
+ )
294
+ impact.append(
295
+ Projectile(self.px - 0.5, self.py - 0.5, 0, 0, 0.2, self.alignment)
296
+ )
297
+ impact.append(
298
+ Projectile(self.px + 0.5, self.py - 0.5, 0, 0, 0.2, self.alignment)
299
+ )
300
+
301
+ for pro in impact:
302
+ pro.sprite.name = "explosion"
303
+ pro.solid_vs_dyn = False
304
+ pro.solid_vs_map = False
305
+ pro.damage = 5
306
+ self.engine.scene.add_projectile(pro)
307
+
308
+ @staticmethod
309
+ def load_from_tiled_object(obj, px, py, width, height):
310
+ movable = Movable(
311
+ px=px,
312
+ py=py,
313
+ tileset_name=obj.get_string("tileset_name"),
314
+ image_name=obj.get_string("tileset_name"),
315
+ sprite_name=obj.get_string("sprite_name"),
316
+ graphic_state=GraphicState[
317
+ obj.get_string("graphic_state", "standing").upper()
318
+ ],
319
+ facing_direction=Direction[
320
+ obj.get_string("facing_direction", "south").upper()
321
+ ],
322
+ mrange=obj.get_float("range"),
323
+ liftable=obj.get_bool("liftable"),
324
+ destroyable=obj.get_bool("destroyable"),
325
+ movable=obj.get_bool("movable"),
326
+ intangible=obj.get_bool("intangible"),
327
+ force_collision_check=obj.get_bool("force_collision_check"),
328
+ dyn_id=obj.object_id,
329
+ name=obj.name,
330
+ )
331
+ movable.sprite.width = int(width * TILE_WIDTH)
332
+ movable.sprite.height = int(height * TILE_HEIGHT)
333
+ for dt in Damage:
334
+ movable.attributes.defense[dt] = obj.get_int(
335
+ f"defense_{dt.name.lower()}"
336
+ )
337
+
338
+ return [movable]
@@ -0,0 +1,168 @@
1
+ from ...scripts.commands.oneway_move import CommandOnewayMove
2
+ from ...types.direction import Direction
3
+ from ...types.graphic_state import GraphicState
4
+ from ...types.nature import Nature
5
+ from ...types.object import ObjectType
6
+ from ...util.constants import ONEWAY_ACTIVATION_DELAY, TILE_HEIGHT, TILE_WIDTH
7
+ from ..animated_sprite import AnimatedSprite
8
+ from ..dynamic import Dynamic
9
+
10
+
11
+ class Oneway(Dynamic):
12
+ def __init__(
13
+ self,
14
+ px: float,
15
+ py: float,
16
+ tileset_name: str,
17
+ image_name: str,
18
+ sprite_name: str,
19
+ facing_direction: Direction,
20
+ graphic_state: GraphicState,
21
+ jump_vx: float,
22
+ jump_vy: float,
23
+ width: float,
24
+ height: float,
25
+ dyn_id=-1,
26
+ name="Oneway",
27
+ ):
28
+ super().__init__(name, px, py, dyn_id)
29
+ self.sprite = AnimatedSprite(
30
+ tileset_name,
31
+ image_name,
32
+ sprite_name,
33
+ graphic_state,
34
+ facing_direction,
35
+ )
36
+ self.type = ObjectType.ONEWAY
37
+ self.graphic_state = graphic_state
38
+ self.facing_direction = facing_direction
39
+ self.sprite.width = int(width * TILE_WIDTH)
40
+ self.sprite.height = int(height * TILE_HEIGHT)
41
+
42
+ self.hitbox_px, self.hitbox_py = 0.0, 0.0
43
+ self.hitbox_width, self.hitbox_height = 1.0, 1.0
44
+ self.solid_vs_map = False
45
+
46
+ self.width: float = width
47
+ self.height: float = height
48
+ self.jump_vx: float = 0.0
49
+ self.jump_vy: float = 0.0
50
+ self.activation_delay: float = ONEWAY_ACTIVATION_DELAY
51
+ self.triggered: bool = False
52
+ self.cooldown: float = 0.0
53
+
54
+ if jump_vx < 0:
55
+ self.jump_vx = jump_vx - 1
56
+ self.hitbox_px += 0.1
57
+ elif jump_vx > 0:
58
+ self.jump_vx = jump_vx + 1
59
+ self.hitbox_px -= 0.1
60
+
61
+ if jump_vy < 0:
62
+ self.jump_vy = jump_vy - 1
63
+ self.hitbox_py += 0.1
64
+ elif jump_vy > 0:
65
+ self.jump_vy = jump_vy + 1
66
+ self.hitbox_py -= 0.1
67
+
68
+ def update(self, elapsed_time, target=None):
69
+ self.sprite.update(
70
+ elapsed_time, self.facing_direction, self.graphic_state
71
+ )
72
+
73
+ # Can only be triggered again after a certain time has passed.
74
+ if self.cooldown >= 0.0:
75
+ self.cooldown -= elapsed_time
76
+ return
77
+ else:
78
+ self.cooldown = 0.0
79
+
80
+ # If no interaction happened in a frame, the activation timer
81
+ # gets resetted.
82
+ if not self.triggered:
83
+ self.timer = 0.0
84
+ return
85
+
86
+ # Activation countdown
87
+ if self.timer > 0.0:
88
+ self.timer -= elapsed_time
89
+
90
+ # Activation countdown reached 0 and the jump is initiated.
91
+ if self.timer <= 0.0:
92
+ self.engine.script.add_command(
93
+ CommandOnewayMove(target, self.jump_vx, self.jump_vy)
94
+ )
95
+ self.cooldown = 2.0
96
+
97
+ # Reset the triggered flag so it has to be activated again
98
+ # by interaction
99
+ self.triggered = False
100
+
101
+ def on_interaction(self, target, nature=Nature.WALK):
102
+ if target.type == ObjectType.PLAYER and nature == Nature.WALK:
103
+ # No interaction when target is higher than the oneway
104
+ if target.pz > 0:
105
+ return False
106
+
107
+ # We have to check that target is not placed "more" in the
108
+ # target direction than the oneway
109
+ if (
110
+ self.jump_vx < 0
111
+ and target.px < self.px + self.width - target.hitbox_px
112
+ ):
113
+ return False
114
+ if self.jump_vx > 0 and target.px >= self.px:
115
+ return False
116
+ if (
117
+ self.jump_vy < 0
118
+ and target.py <= self.py + self.height - target.hitbox_py
119
+ ):
120
+ return False
121
+ if self.jump_vy > 0 and target.py >= self.py:
122
+ return False
123
+
124
+ if self.jump_vx == 0:
125
+ if target.px >= self.px + self.width:
126
+ return False
127
+ if target.px + 1.0 <= self.px:
128
+ return False
129
+
130
+ if self.jump_vy == 0:
131
+ if target.py >= self.py + self.height:
132
+ return False
133
+ if target.py + 1.0 <= self.py:
134
+ return False
135
+
136
+ self.triggered = True
137
+ if self.timer <= 0.0:
138
+ self.timer = self.activation_delay
139
+
140
+ return True
141
+
142
+ return False
143
+
144
+ def draw_self(self, ox: float, oy: float):
145
+ self.sprite.draw_self(self.px - ox, self.py - oy)
146
+
147
+ @staticmethod
148
+ def load_from_tiled_object(obj, px, py, width, height):
149
+ oneway = Oneway(
150
+ px=px,
151
+ py=py,
152
+ tileset_name=obj.get_string("tileset_name"),
153
+ image_name=obj.get_string("tileset_name"),
154
+ sprite_name=obj.get_string("sprite_name"),
155
+ graphic_state=GraphicState[
156
+ obj.get_string("graphic_state", "standing").upper()
157
+ ],
158
+ facing_direction=Direction[
159
+ obj.get_string("facing_direction", "south").upper()
160
+ ],
161
+ jump_vx=obj.get_float("jump_vx"),
162
+ jump_vy=obj.get_float("jump_vy"),
163
+ width=width,
164
+ height=height,
165
+ dyn_id=obj.object_id,
166
+ name=obj.name,
167
+ )
168
+ return [oneway]