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
mima/objects/creature.py
ADDED
|
@@ -0,0 +1,384 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import TYPE_CHECKING, Dict, List, Optional
|
|
4
|
+
|
|
5
|
+
from ..types.direction import Direction
|
|
6
|
+
from ..types.graphic_state import GraphicState, Until
|
|
7
|
+
from ..types.nature import Nature
|
|
8
|
+
from ..types.object import ObjectType
|
|
9
|
+
from ..types.player import Player
|
|
10
|
+
from ..types.terrain import Terrain
|
|
11
|
+
from ..types.weapon_slot import WeaponSlot
|
|
12
|
+
|
|
13
|
+
# from ..types.weapon_slot import WeaponSlot
|
|
14
|
+
from ..util.colors import BLACK
|
|
15
|
+
from ..util.constants import DEFAULT_KNOCK_SPEED
|
|
16
|
+
from .animated_sprite import AnimatedSprite
|
|
17
|
+
from .dynamic import Dynamic
|
|
18
|
+
from .effects.light import Light
|
|
19
|
+
from .effects.walking_on_grass import WalkingOnGrass
|
|
20
|
+
from .effects.walking_on_water import WalkingOnWater
|
|
21
|
+
from .projectile import Projectile
|
|
22
|
+
|
|
23
|
+
if TYPE_CHECKING:
|
|
24
|
+
from ..maps.tilemap import Tilemap
|
|
25
|
+
from ..usables.weapon import Weapon
|
|
26
|
+
from ..util.trading_item import TradingItem
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class Creature(Dynamic):
|
|
30
|
+
def __init__(
|
|
31
|
+
self,
|
|
32
|
+
px: float = 0,
|
|
33
|
+
py: float = 0,
|
|
34
|
+
name: str = "Unnamed Creature",
|
|
35
|
+
*,
|
|
36
|
+
sprite_name: str = "",
|
|
37
|
+
tilemap: Tilemap = None,
|
|
38
|
+
dyn_id: int = -1,
|
|
39
|
+
player: Player = Player.P0,
|
|
40
|
+
):
|
|
41
|
+
super().__init__(
|
|
42
|
+
px,
|
|
43
|
+
py,
|
|
44
|
+
name,
|
|
45
|
+
sprite_name=sprite_name,
|
|
46
|
+
tilemap=tilemap,
|
|
47
|
+
dyn_id=dyn_id,
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
# self.sprite = self.engine.db.get_sprite(
|
|
51
|
+
# sprite_name
|
|
52
|
+
# ) # AnimatedSprite(tileset_name, image_name, sprite_name)
|
|
53
|
+
self.player = player
|
|
54
|
+
self.type = ObjectType.CREATURE
|
|
55
|
+
self.knock_speed: float = DEFAULT_KNOCK_SPEED
|
|
56
|
+
|
|
57
|
+
# self.sprite.name = sprite_name
|
|
58
|
+
# self.sprite.num_frames = 2
|
|
59
|
+
self.attackable = True
|
|
60
|
+
self.knockable = True
|
|
61
|
+
self.moves_on_collision = True
|
|
62
|
+
|
|
63
|
+
self._knock_vx: float = 0.0
|
|
64
|
+
self._knock_vy: float = 0.0
|
|
65
|
+
self.real_vx: float = 0.0
|
|
66
|
+
self.real_vy: float = 0.0
|
|
67
|
+
self.last_vx: float = 0.0
|
|
68
|
+
self.last_vy: float = 0.0
|
|
69
|
+
self._attack_timer: float = 0.0
|
|
70
|
+
self._invincible_timer: float = 0.0
|
|
71
|
+
self._knock_timer: float = 0.0
|
|
72
|
+
self._state_timer: float = 0.0
|
|
73
|
+
|
|
74
|
+
self.invincible: bool = False
|
|
75
|
+
# self.use_acceleration: bool = True
|
|
76
|
+
# self.use_friction: bool = True
|
|
77
|
+
|
|
78
|
+
self.projectiles: List[Projectile] = []
|
|
79
|
+
self.weapons: Dict[WeaponSlot, Optional[Weapon]] = {
|
|
80
|
+
WeaponSlot.FIRST_HAND: None,
|
|
81
|
+
WeaponSlot.SECOND_HAND: None,
|
|
82
|
+
}
|
|
83
|
+
self._light = None
|
|
84
|
+
|
|
85
|
+
# self.attributes.speed = 1.0
|
|
86
|
+
# self.use_acceleration = True
|
|
87
|
+
# self.use_friction = True
|
|
88
|
+
# self.attributes.friction = 0.1
|
|
89
|
+
|
|
90
|
+
def start_shining(self):
|
|
91
|
+
self._light = Light(self, fixed_size=True, update_from_target=True)
|
|
92
|
+
self.engine.get_view().add_effect(self._light, self.tilemap.name)
|
|
93
|
+
|
|
94
|
+
def stop_shining(self):
|
|
95
|
+
if self._light is not None:
|
|
96
|
+
self._light.kill()
|
|
97
|
+
self._light = None
|
|
98
|
+
|
|
99
|
+
def update(self, elapsed_time: float, target: Optional[Dynamic] = None):
|
|
100
|
+
if not self.visible:
|
|
101
|
+
self.vx = self.vy = 0.0
|
|
102
|
+
return
|
|
103
|
+
|
|
104
|
+
if self._knock_timer > 0.0: # Handle knocking
|
|
105
|
+
self._knock_timer -= elapsed_time
|
|
106
|
+
self._invincible_timer -= elapsed_time
|
|
107
|
+
self.vx, self.vy = self._knock_vx, self._knock_vy
|
|
108
|
+
self.speed = self.knock_speed
|
|
109
|
+
self.change_graphic_state(GraphicState.DAMAGED)
|
|
110
|
+
|
|
111
|
+
if self._knock_timer <= 0.0: # Knocking is over
|
|
112
|
+
self._state_timer = 0.0
|
|
113
|
+
self.controllable = True
|
|
114
|
+
self.solid_vs_dyn = True
|
|
115
|
+
self.sprite.reset()
|
|
116
|
+
|
|
117
|
+
else: # Usual update
|
|
118
|
+
if self._invincible_timer > 0.0:
|
|
119
|
+
self._invincible_timer -= elapsed_time
|
|
120
|
+
if self._invincible_timer <= 0.0:
|
|
121
|
+
self.invincible = False
|
|
122
|
+
self.knockable = True
|
|
123
|
+
self._invincible_timer = 0.0
|
|
124
|
+
|
|
125
|
+
if self._attack_timer > 0.0:
|
|
126
|
+
self._attack_timer -= elapsed_time
|
|
127
|
+
self.change_graphic_state(GraphicState.ATTACKING)
|
|
128
|
+
|
|
129
|
+
if self._attack_timer <= 0:
|
|
130
|
+
self._attack_timer = 0.0
|
|
131
|
+
if abs(self.vx) > 0 or abs(self.vy) > 0:
|
|
132
|
+
self.change_graphic_state(GraphicState.WALKING)
|
|
133
|
+
else:
|
|
134
|
+
self.change_graphic_state(GraphicState.STANDING)
|
|
135
|
+
|
|
136
|
+
if self.attributes.health <= 0:
|
|
137
|
+
self.change_graphic_state(GraphicState.DEAD)
|
|
138
|
+
|
|
139
|
+
if self.can_act():
|
|
140
|
+
self.behavior(elapsed_time, target)
|
|
141
|
+
|
|
142
|
+
if self.vx < -0.01:
|
|
143
|
+
self.facing_direction = Direction.WEST
|
|
144
|
+
if self.vx > 0.01:
|
|
145
|
+
self.facing_direction = Direction.EAST
|
|
146
|
+
if self.vy < -0.01:
|
|
147
|
+
self.facing_direction = Direction.NORTH
|
|
148
|
+
if self.vy > 0.01:
|
|
149
|
+
self.facing_direction = Direction.SOUTH
|
|
150
|
+
|
|
151
|
+
self.speed = self.attributes.speed
|
|
152
|
+
|
|
153
|
+
self.sprite.update(
|
|
154
|
+
elapsed_time, self.facing_direction, self.graphic_state
|
|
155
|
+
)
|
|
156
|
+
|
|
157
|
+
self._handle_terrain(elapsed_time)
|
|
158
|
+
|
|
159
|
+
if self.graphic_state == GraphicState.DEAD:
|
|
160
|
+
self.attackable = False
|
|
161
|
+
self.vx = self.vy = 0
|
|
162
|
+
if self.despawn_timer is None:
|
|
163
|
+
self.despawn_timer = self.despawn_duration
|
|
164
|
+
self.engine.play_sound("enemy_killed")
|
|
165
|
+
else:
|
|
166
|
+
self.despawn_timer -= elapsed_time
|
|
167
|
+
|
|
168
|
+
if self.despawn_timer <= 0.0:
|
|
169
|
+
self.kill()
|
|
170
|
+
else:
|
|
171
|
+
eff2r = []
|
|
172
|
+
for eff in self.attribute_effects:
|
|
173
|
+
if (
|
|
174
|
+
self.attributes.health < eff.health_cost
|
|
175
|
+
or self.attributes.magic < eff.magic_cost
|
|
176
|
+
or self.attributes.stamina < eff.stamina_cost
|
|
177
|
+
# and self.attributes.arrows >= weapon.arrow_cost
|
|
178
|
+
# and self.attributes.bombs >= weapon.bomb_cost
|
|
179
|
+
):
|
|
180
|
+
eff.redundant = True
|
|
181
|
+
self.attributes.health_per_second += eff.health_cost
|
|
182
|
+
self.attributes.magic_per_second += eff.magic_cost
|
|
183
|
+
self.attributes.stamina_per_second += eff.stamina_cost
|
|
184
|
+
eff2r.append(eff)
|
|
185
|
+
for e in eff2r:
|
|
186
|
+
self.attribute_effects.remove(e)
|
|
187
|
+
|
|
188
|
+
self.attributes.update(elapsed_time)
|
|
189
|
+
|
|
190
|
+
if self._gs_lock_condition == Until.NEXT_UPDATE:
|
|
191
|
+
self.graphic_state_locked = False
|
|
192
|
+
|
|
193
|
+
def draw_self(self, ox: float, oy: float, camera_name: str = "display"):
|
|
194
|
+
if (
|
|
195
|
+
self.sprite.name is None
|
|
196
|
+
or self.sprite.name == ""
|
|
197
|
+
or not self.visible
|
|
198
|
+
):
|
|
199
|
+
return
|
|
200
|
+
|
|
201
|
+
px = self.px - ox + self.extra_ox
|
|
202
|
+
py = self.py - oy + self.extra_oy
|
|
203
|
+
|
|
204
|
+
if self.pz != 0:
|
|
205
|
+
self.engine.backend.fill_circle(
|
|
206
|
+
(px + 0.5) * self.sprite.width,
|
|
207
|
+
(py + 0.7) * self.sprite.height,
|
|
208
|
+
0.3125 * self.sprite.width,
|
|
209
|
+
BLACK,
|
|
210
|
+
camera_name,
|
|
211
|
+
)
|
|
212
|
+
|
|
213
|
+
self.sprite.draw_self(px, py - self.pz, camera_name)
|
|
214
|
+
# for effect in self.effects:
|
|
215
|
+
# effect.draw_self(px, py - self.pz)
|
|
216
|
+
|
|
217
|
+
def behavior(self, elapsed_time: float, target: Optional[Dynamic] = None):
|
|
218
|
+
# No default behavior
|
|
219
|
+
pass
|
|
220
|
+
|
|
221
|
+
def on_interaction(self, target=None, nature=Nature.WALK):
|
|
222
|
+
if nature == Nature.SIGNAL:
|
|
223
|
+
self.solid_vs_dyn = False
|
|
224
|
+
self.visible = False
|
|
225
|
+
return True
|
|
226
|
+
|
|
227
|
+
elif nature == Nature.NO_SIGNAL:
|
|
228
|
+
self.visible = True
|
|
229
|
+
self.solid_vs_dyn = True
|
|
230
|
+
return True
|
|
231
|
+
return False
|
|
232
|
+
|
|
233
|
+
def knock_back(self, vx: float, vy: float, dist: float):
|
|
234
|
+
if self.knockable:
|
|
235
|
+
self._knock_vx = vx
|
|
236
|
+
self._knock_vy = vy
|
|
237
|
+
self._knock_timer = dist
|
|
238
|
+
|
|
239
|
+
self.knockable = False
|
|
240
|
+
self._invincible_timer = dist + 0.5
|
|
241
|
+
# self.solid_vs_dyn = False
|
|
242
|
+
self.controllable = False
|
|
243
|
+
self.invincible = True
|
|
244
|
+
self.sprite.reset()
|
|
245
|
+
self.cancel_attack()
|
|
246
|
+
|
|
247
|
+
def can_act(self):
|
|
248
|
+
actable_states = [
|
|
249
|
+
GraphicState.STANDING,
|
|
250
|
+
GraphicState.WALKING,
|
|
251
|
+
GraphicState.CELEBRATING,
|
|
252
|
+
# GraphicState.DAMAGED,
|
|
253
|
+
GraphicState.PUSHING,
|
|
254
|
+
]
|
|
255
|
+
if self.graphic_state in actable_states:
|
|
256
|
+
return True
|
|
257
|
+
|
|
258
|
+
return False
|
|
259
|
+
|
|
260
|
+
def perform_attack(self, slot: WeaponSlot) -> bool:
|
|
261
|
+
weapon = self.weapons.get(slot, None)
|
|
262
|
+
|
|
263
|
+
if weapon is None or not self.can_attack:
|
|
264
|
+
# print(f"Cannot attack. Weapon={weapon}")
|
|
265
|
+
return False
|
|
266
|
+
|
|
267
|
+
# Clean up all previous projectiles
|
|
268
|
+
self.cancel_attack()
|
|
269
|
+
|
|
270
|
+
if (
|
|
271
|
+
self.attributes.health >= weapon.health_cost
|
|
272
|
+
and self.attributes.magic >= weapon.magic_cost
|
|
273
|
+
and self.attributes.stamina >= weapon.stamina_cost
|
|
274
|
+
and self.attributes.arrows >= weapon.arrow_cost
|
|
275
|
+
and self.attributes.bombs >= weapon.bomb_cost
|
|
276
|
+
):
|
|
277
|
+
self.attributes.health -= weapon.health_cost
|
|
278
|
+
self.attributes.magic -= weapon.magic_cost
|
|
279
|
+
self.attributes.stamina -= weapon.stamina_cost
|
|
280
|
+
self.attributes.arrows -= weapon.arrow_cost
|
|
281
|
+
self.attributes.bombs -= weapon.bomb_cost
|
|
282
|
+
else:
|
|
283
|
+
return False
|
|
284
|
+
|
|
285
|
+
if weapon.on_use(self):
|
|
286
|
+
self.vx = self.vy = 0.0
|
|
287
|
+
self._attack_timer = weapon.swing_timer
|
|
288
|
+
return True
|
|
289
|
+
|
|
290
|
+
return False
|
|
291
|
+
|
|
292
|
+
def cancel_attack(self):
|
|
293
|
+
# if self.projectiles:
|
|
294
|
+
for projectile in self.projectiles:
|
|
295
|
+
if projectile is not None:
|
|
296
|
+
projectile.spawn_on_death = []
|
|
297
|
+
projectile.kill()
|
|
298
|
+
|
|
299
|
+
self.projectiles = []
|
|
300
|
+
|
|
301
|
+
def on_enter_map(self):
|
|
302
|
+
pass
|
|
303
|
+
|
|
304
|
+
def on_exit_map(self):
|
|
305
|
+
pass
|
|
306
|
+
|
|
307
|
+
def on_death(self):
|
|
308
|
+
if self.spawn_on_death:
|
|
309
|
+
for do in self.spawn_on_death:
|
|
310
|
+
if do.redundant:
|
|
311
|
+
continue
|
|
312
|
+
if do.type == ObjectType.PROJECTILE:
|
|
313
|
+
if do.inherit_pos:
|
|
314
|
+
do.px = self.px
|
|
315
|
+
do.py = self.py
|
|
316
|
+
self.engine.get_view().add_projectile(
|
|
317
|
+
do, self.tilemap.name
|
|
318
|
+
)
|
|
319
|
+
else:
|
|
320
|
+
self.engine.get_view().add_dynamic(do, self.tilemap.name)
|
|
321
|
+
|
|
322
|
+
self.spawn_on_death = []
|
|
323
|
+
|
|
324
|
+
def _handle_terrain(self, elapsed_time: float):
|
|
325
|
+
e2rm = []
|
|
326
|
+
for effect in self.effects:
|
|
327
|
+
if isinstance(effect, WalkingOnGrass):
|
|
328
|
+
if self.walking_on == Terrain.DEFAULT:
|
|
329
|
+
e2rm.append(effect)
|
|
330
|
+
|
|
331
|
+
for effect in e2rm:
|
|
332
|
+
self.effects.remove(effect)
|
|
333
|
+
|
|
334
|
+
if self.walking_on in [Terrain.GRASS, Terrain.SHALLOW_WATER]:
|
|
335
|
+
self.attributes.speed_mod = 0.7
|
|
336
|
+
effect_active = False
|
|
337
|
+
for effect in self.effects:
|
|
338
|
+
if isinstance(effect, (WalkingOnGrass, WalkingOnWater)):
|
|
339
|
+
effect_active = True
|
|
340
|
+
effect.renew = True
|
|
341
|
+
break
|
|
342
|
+
|
|
343
|
+
if not effect_active:
|
|
344
|
+
if self.walking_on == Terrain.GRASS:
|
|
345
|
+
eff = WalkingOnGrass(self)
|
|
346
|
+
else:
|
|
347
|
+
eff = WalkingOnWater(self)
|
|
348
|
+
self.effects.append(eff)
|
|
349
|
+
self.engine.get_view().add_effect(eff, self.tilemap.name)
|
|
350
|
+
else:
|
|
351
|
+
self.attributes.speed_mod = 1.0
|
|
352
|
+
|
|
353
|
+
def equip_weapon(self, slot, weapon):
|
|
354
|
+
if self.weapons[slot] == weapon:
|
|
355
|
+
# Weapon already equipped; unequip
|
|
356
|
+
self.weapons[slot] = None
|
|
357
|
+
weapon.on_unequip(self)
|
|
358
|
+
return
|
|
359
|
+
|
|
360
|
+
for s, w in self.weapons.items():
|
|
361
|
+
if slot != s and weapon == w:
|
|
362
|
+
# Weapon equipped in a different slot; change
|
|
363
|
+
if self.weapons[slot] is not None:
|
|
364
|
+
self.weapons[slot].on_unequip(self)
|
|
365
|
+
self.weapons[slot] = weapon
|
|
366
|
+
self.weapons[s] = None
|
|
367
|
+
return
|
|
368
|
+
|
|
369
|
+
if self.weapons[slot] is not None:
|
|
370
|
+
# Other weapon equipped; unequip that
|
|
371
|
+
self.weapons[slot].on_unequip(self)
|
|
372
|
+
|
|
373
|
+
self.weapons[slot] = weapon
|
|
374
|
+
weapon.on_equip(self)
|
|
375
|
+
|
|
376
|
+
def unequip_weapon(self, slot):
|
|
377
|
+
if self.weapons[slot] is not None:
|
|
378
|
+
self.weapons[slot].on_unequip(self)
|
|
379
|
+
|
|
380
|
+
def get_trading_items(self) -> List[TradingItem]:
|
|
381
|
+
return []
|
|
382
|
+
|
|
383
|
+
def __str__(self):
|
|
384
|
+
return f"C({self.name}, {self.dyn_id})"
|
mima/objects/dynamic.py
ADDED
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import TYPE_CHECKING, List, Optional, Union
|
|
4
|
+
|
|
5
|
+
from ..types.alignment import Alignment
|
|
6
|
+
from ..types.damage import Damage
|
|
7
|
+
from ..types.direction import Direction
|
|
8
|
+
from ..types.graphic_state import GraphicState, Until
|
|
9
|
+
from ..types.nature import Nature
|
|
10
|
+
from ..types.object import ObjectType
|
|
11
|
+
from ..types.player import Player
|
|
12
|
+
from ..types.terrain import Terrain
|
|
13
|
+
from .attribute_effect import Effect
|
|
14
|
+
from .attributes import Attributes
|
|
15
|
+
from .sprite import Sprite
|
|
16
|
+
|
|
17
|
+
if TYPE_CHECKING:
|
|
18
|
+
from ..engine import MimaEngine
|
|
19
|
+
from ..maps.tilemap import Tilemap
|
|
20
|
+
from .projectile import Projectile
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class Dynamic:
|
|
24
|
+
engine: MimaEngine
|
|
25
|
+
|
|
26
|
+
def __init__(
|
|
27
|
+
self,
|
|
28
|
+
px: float = 0.0,
|
|
29
|
+
py: float = 0.0,
|
|
30
|
+
name: str = "Unnamed Dynamic",
|
|
31
|
+
*,
|
|
32
|
+
sprite_name: str = "",
|
|
33
|
+
tilemap: Optional[Tilemap] = None,
|
|
34
|
+
dyn_id=-1,
|
|
35
|
+
):
|
|
36
|
+
self.name: str = name
|
|
37
|
+
self.dyn_id: int = dyn_id # ID given by Tiled
|
|
38
|
+
self.layer: int = 1
|
|
39
|
+
self.tilemap = tilemap # The object's current map
|
|
40
|
+
self.player = Player.P0
|
|
41
|
+
|
|
42
|
+
self.px: float = px if px is not None else 0.0
|
|
43
|
+
self.py: float = py if py is not None else 0.0
|
|
44
|
+
self.pz: float = 0.0
|
|
45
|
+
self.vx: float = 0.0
|
|
46
|
+
self.vy: float = 0.0
|
|
47
|
+
self.vz: float = 0.0
|
|
48
|
+
self.real_vx: float = 0.0
|
|
49
|
+
self.real_vy: float = 0.0
|
|
50
|
+
self.hitbox_px: float = 0.1
|
|
51
|
+
self.hitbox_py: float = 0.1
|
|
52
|
+
self.hitbox_width: float = 0.8
|
|
53
|
+
self.hitbox_height: float = 0.8
|
|
54
|
+
self.attribute_timer: float = 0.25
|
|
55
|
+
self.despawn_timer: float = 0.5
|
|
56
|
+
self.despawn_duration: float = 0.2
|
|
57
|
+
self.timer: float = 0.0
|
|
58
|
+
self.speed: float = 1.0
|
|
59
|
+
|
|
60
|
+
self.solid_vs_map: bool = True
|
|
61
|
+
self.solid_vs_dyn: bool = True
|
|
62
|
+
self.solid_vs_player: bool = True
|
|
63
|
+
# self.is_player: bool = False
|
|
64
|
+
# self.is_projectile: bool = False
|
|
65
|
+
self.redundant: bool = False
|
|
66
|
+
self.persistent: bool = False
|
|
67
|
+
self.attackable: bool = False
|
|
68
|
+
self.controllable: bool = True
|
|
69
|
+
self.state_changed: bool = False
|
|
70
|
+
self.visible: bool = True
|
|
71
|
+
self.gravity: bool = True
|
|
72
|
+
self.graphic_state_locked: bool = False
|
|
73
|
+
self.use_acceleration: bool = False
|
|
74
|
+
self.use_friction: bool = False
|
|
75
|
+
self.occupied: bool = False
|
|
76
|
+
|
|
77
|
+
self.alignment: Alignment = Alignment.GOOD
|
|
78
|
+
self.walking_on: Terrain = Terrain.DEFAULT
|
|
79
|
+
self.attributes: Attributes = Attributes()
|
|
80
|
+
self.facing_direction: Direction = Direction.SOUTH
|
|
81
|
+
self.graphic_state: GraphicState = GraphicState.STANDING
|
|
82
|
+
self._gs_lock_condition: Until = Until.UNLOCK
|
|
83
|
+
self.type: ObjectType = ObjectType.UNDEFINED
|
|
84
|
+
self.spawn_on_death: List[Dynamic] = []
|
|
85
|
+
self.childs: List[Dynamic] = []
|
|
86
|
+
self.effects: List[Projectile] = []
|
|
87
|
+
self.attribute_effects: List[Effect] = []
|
|
88
|
+
|
|
89
|
+
self.extra_ox: float = 0.0
|
|
90
|
+
self.extra_oy: float = 0.0
|
|
91
|
+
|
|
92
|
+
self.can_attack: bool = True
|
|
93
|
+
self.can_lift: bool = False
|
|
94
|
+
self.can_push: bool = False
|
|
95
|
+
|
|
96
|
+
self.sprite: Sprite = self.engine.db.get_sprite(sprite_name)
|
|
97
|
+
|
|
98
|
+
# Performance
|
|
99
|
+
self.chunks: List[int] = []
|
|
100
|
+
self.moves_on_collision: bool = False
|
|
101
|
+
|
|
102
|
+
self.update_skippable: bool = False
|
|
103
|
+
"""Update may be skipped if object is offscreen."""
|
|
104
|
+
self.offscreen_collision_skippable: bool = True
|
|
105
|
+
"""Collision checks may be skipped if object is offscreen."""
|
|
106
|
+
self.onscreen_collision_skippable: bool = False
|
|
107
|
+
"""Collision checks may be skipped if object is onscreen."""
|
|
108
|
+
self.draw_skippable: bool = True
|
|
109
|
+
"""Drawing the unit may be skipped if object is offscreen."""
|
|
110
|
+
|
|
111
|
+
def update(self, elapsed_time: float, target: Optional[Dynamic]):
|
|
112
|
+
"""Update this dynamic."""
|
|
113
|
+
pass
|
|
114
|
+
|
|
115
|
+
def draw_self(self, ox: float, oy: float, camera_name: str = "display"):
|
|
116
|
+
"""Draw self to screen"""
|
|
117
|
+
pass
|
|
118
|
+
|
|
119
|
+
def on_interaction(
|
|
120
|
+
self, target: Dynamic = None, nature: Nature = Nature.WALK
|
|
121
|
+
) -> bool:
|
|
122
|
+
"""Handle interaction with other dynamic objects."""
|
|
123
|
+
return False
|
|
124
|
+
|
|
125
|
+
def on_death(self) -> bool:
|
|
126
|
+
return False
|
|
127
|
+
|
|
128
|
+
def kill(self):
|
|
129
|
+
self.redundant = True
|
|
130
|
+
|
|
131
|
+
def change_graphic_state(self, new_state: GraphicState) -> bool:
|
|
132
|
+
if self.graphic_state_locked or new_state == self.graphic_state:
|
|
133
|
+
return False
|
|
134
|
+
|
|
135
|
+
self.graphic_state = new_state
|
|
136
|
+
self.sprite.update(0.0, self.facing_direction, self.graphic_state)
|
|
137
|
+
|
|
138
|
+
return True
|
|
139
|
+
|
|
140
|
+
def lock_graphic_state(
|
|
141
|
+
self,
|
|
142
|
+
new_state: GraphicState = GraphicState.STANDING,
|
|
143
|
+
until: Until = Until.UNLOCK,
|
|
144
|
+
):
|
|
145
|
+
self.change_graphic_state(new_state)
|
|
146
|
+
self.graphic_state_locked = True
|
|
147
|
+
self._gs_lock_condition = until
|
|
148
|
+
|
|
149
|
+
def unlock_graphic_state(self):
|
|
150
|
+
self.graphic_state_locked = False
|
|
151
|
+
self.change_graphic_state(GraphicState.STANDING)
|
|
152
|
+
|
|
153
|
+
def get_defense_value(self, dtype: Damage) -> int:
|
|
154
|
+
return self.attributes.defense[dtype]
|
|
155
|
+
|
|
156
|
+
def is_py_lower(self, other: Dynamic) -> bool:
|
|
157
|
+
return self.py < other.py
|
|
158
|
+
|
|
159
|
+
def get_alignment(self) -> Alignment:
|
|
160
|
+
return self.alignment
|
|
161
|
+
|
|
162
|
+
def add_effect(self, eff: Effect):
|
|
163
|
+
self.attribute_effects.append(eff)
|
|
164
|
+
|
|
165
|
+
def remove_effect(self, eff: Union[str, Effect]):
|
|
166
|
+
if isinstance(eff, str):
|
|
167
|
+
obj = [e for e in self.attribute_effects if e.effect_id == eff]
|
|
168
|
+
if not obj:
|
|
169
|
+
return
|
|
170
|
+
else:
|
|
171
|
+
eff = obj[0]
|
|
172
|
+
|
|
173
|
+
self.attribute_effects.remove(eff)
|
|
174
|
+
|
|
175
|
+
def has_effect(self, eff: Union[str, Effect]):
|
|
176
|
+
if isinstance(eff, str):
|
|
177
|
+
for e in self.attribute_effects:
|
|
178
|
+
if e.effect_id == eff:
|
|
179
|
+
return True
|
|
180
|
+
return False
|
|
181
|
+
else:
|
|
182
|
+
if eff in self.attribute_effects:
|
|
183
|
+
return True
|
|
184
|
+
else:
|
|
185
|
+
return False
|
|
186
|
+
|
|
187
|
+
def light_radius(self):
|
|
188
|
+
rad = self.attributes.light_radius
|
|
189
|
+
|
|
190
|
+
for eff in self.attribute_effects:
|
|
191
|
+
rad += eff.light_radius
|
|
192
|
+
|
|
193
|
+
return rad
|
|
194
|
+
|
|
195
|
+
def get_player(self):
|
|
196
|
+
return self.player
|
|
197
|
+
|
|
198
|
+
def halt(self):
|
|
199
|
+
self.vx = 0
|
|
200
|
+
self.vy = 0
|
|
201
|
+
|
|
202
|
+
def __str__(self):
|
|
203
|
+
return f"D({self.name}, {self.dyn_id})"
|
|
204
|
+
|
|
205
|
+
def __repr__(self):
|
|
206
|
+
return str(self)
|
|
File without changes
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
from typing import List, Optional
|
|
2
|
+
|
|
3
|
+
from ...types.alignment import Alignment
|
|
4
|
+
from ...types.player import Player
|
|
5
|
+
from ...util.colors import BLACK, Color
|
|
6
|
+
from ...util.constants import HEIGHT, WIDTH
|
|
7
|
+
from ..dynamic import Dynamic
|
|
8
|
+
from ..projectile import Projectile
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class ColorizeScreen(Projectile):
|
|
12
|
+
def __init__(
|
|
13
|
+
self,
|
|
14
|
+
color: Color = BLACK,
|
|
15
|
+
alpha=BLACK.alpha,
|
|
16
|
+
duration: float = -1.0,
|
|
17
|
+
to_filter: bool = False,
|
|
18
|
+
cameras: Optional[List[str]] = None,
|
|
19
|
+
):
|
|
20
|
+
super().__init__(
|
|
21
|
+
0,
|
|
22
|
+
0,
|
|
23
|
+
"ColorizeScreen",
|
|
24
|
+
vx=0,
|
|
25
|
+
vy=0,
|
|
26
|
+
duration=duration,
|
|
27
|
+
alignment=Alignment.GOOD,
|
|
28
|
+
)
|
|
29
|
+
self.layer = 1
|
|
30
|
+
self.solid_vs_map = False
|
|
31
|
+
self.color = color
|
|
32
|
+
self.alpha = alpha
|
|
33
|
+
self.vanishs_after_time: bool = duration > 0
|
|
34
|
+
self.to_filter = to_filter
|
|
35
|
+
self._cameras = (
|
|
36
|
+
cameras if (cameras is not None and cameras) else ["C_DISPLAY"]
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
def update(self, elapsed_time: float, target: Dynamic = None):
|
|
40
|
+
if self.vanishs_after_time:
|
|
41
|
+
self.duration -= elapsed_time
|
|
42
|
+
if self.duration <= 0.0:
|
|
43
|
+
self.kill()
|
|
44
|
+
|
|
45
|
+
# self.sprite.update(elapsed_time, self.facing_direction, self.graphic_state)
|
|
46
|
+
|
|
47
|
+
def draw_self(self, ox: float, oy: float, camera_name=None):
|
|
48
|
+
color = Color(
|
|
49
|
+
self.color.red, self.color.green, self.color.blue, self.alpha
|
|
50
|
+
)
|
|
51
|
+
if camera_name in self._cameras:
|
|
52
|
+
self.engine.backend.fill_rect(
|
|
53
|
+
self.px,
|
|
54
|
+
self.py,
|
|
55
|
+
self.engine.backend.render_width,
|
|
56
|
+
self.engine.backend.render_height,
|
|
57
|
+
color,
|
|
58
|
+
camera_name,
|
|
59
|
+
draw_to_filter=self.to_filter,
|
|
60
|
+
)
|