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
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
from .attributes import Attributes
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class Effect(Attributes):
|
|
5
|
+
def __init__(self):
|
|
6
|
+
super().__init__()
|
|
7
|
+
|
|
8
|
+
self.effect_id: str = ""
|
|
9
|
+
self.duration: float = 0.0
|
|
10
|
+
self.redundant: bool = False
|
|
11
|
+
self.health_cost: float = 0.0
|
|
12
|
+
self.magic_cost: float = 0.0
|
|
13
|
+
self.stamina_cost: float = 0.0
|
|
14
|
+
|
|
15
|
+
def update(self, elapsed_time: float):
|
|
16
|
+
self.duration -= self.elapsed_time
|
|
17
|
+
if self.duration < 0.0:
|
|
18
|
+
self.redundant = True
|
|
19
|
+
|
|
20
|
+
@staticmethod
|
|
21
|
+
def from_dict(data):
|
|
22
|
+
attr = Effect()
|
|
23
|
+
for key, val in data.items():
|
|
24
|
+
setattr(attr, key, val)
|
|
25
|
+
|
|
26
|
+
return attr
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
from typing import Dict
|
|
2
|
+
|
|
3
|
+
from ..types.damage import Damage
|
|
4
|
+
from ..util.constants import ATTRIBUTE_TIMER
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class Attributes:
|
|
8
|
+
"""A class holding all the attributes of an object.
|
|
9
|
+
|
|
10
|
+
Attributes
|
|
11
|
+
----------
|
|
12
|
+
|
|
13
|
+
health: float
|
|
14
|
+
The current health of the object.
|
|
15
|
+
health_max: float
|
|
16
|
+
The maximum health of the object.
|
|
17
|
+
speed: float
|
|
18
|
+
The current speed value of the object. If different run speeds
|
|
19
|
+
are not relevant for the object, this is the only value to
|
|
20
|
+
consider. Otherwise, this value might be changed by objects
|
|
21
|
+
itself to reflect different states.
|
|
22
|
+
speed_mod: float
|
|
23
|
+
A modification of the speed used by the game engine, e.g., when
|
|
24
|
+
walking in shallow water
|
|
25
|
+
walk_speed: float
|
|
26
|
+
The speed value when the objects moves slow, e.g., when it is
|
|
27
|
+
walking.
|
|
28
|
+
run_speed: float
|
|
29
|
+
The speed value when the object moves fast, e.g., when it is
|
|
30
|
+
running.
|
|
31
|
+
acceleration: float
|
|
32
|
+
The acceleration of the object when it starts to move. Ranges
|
|
33
|
+
between 0 and 1
|
|
34
|
+
friction: float
|
|
35
|
+
The friction of the object when it stops moving. Ranges between
|
|
36
|
+
0 and 1.
|
|
37
|
+
|
|
38
|
+
"""
|
|
39
|
+
|
|
40
|
+
def __init__(self):
|
|
41
|
+
self.health: float = 10.0
|
|
42
|
+
self.health_max: float = 10.0
|
|
43
|
+
self.health_per_second: float = 0.0
|
|
44
|
+
self.magic: float = 10.0
|
|
45
|
+
self.magic_max: float = 10.0
|
|
46
|
+
self.magic_per_second: float = 0.1
|
|
47
|
+
self.stamina: float = 10.0
|
|
48
|
+
self.stamina_max: float = 10.0
|
|
49
|
+
self.stamina_per_second: float = 2.0
|
|
50
|
+
self.speed: float = 1.0
|
|
51
|
+
self.speed_mod: float = 1.0
|
|
52
|
+
self.walk_speed: float = 1.0
|
|
53
|
+
self.run_speed: float = 1.0
|
|
54
|
+
# self.current_speed: float = 0.0 # Acceleration and Friction
|
|
55
|
+
self.knock_speed: float = 5.0
|
|
56
|
+
self.acceleration: float = 15.0
|
|
57
|
+
self.friction: float = 15.0
|
|
58
|
+
|
|
59
|
+
self.timer: float = 0.25
|
|
60
|
+
self.gravity_vz: float = 40.0
|
|
61
|
+
self.light_radius: float = 32
|
|
62
|
+
|
|
63
|
+
self.coins: int = 0
|
|
64
|
+
self.coins_max: int = 100_000
|
|
65
|
+
self.keys: int = 0
|
|
66
|
+
self.keys_max: int = 100_000
|
|
67
|
+
self.bombs: int = 0
|
|
68
|
+
self.bombs_max: int = 5
|
|
69
|
+
self.arrows: int = 0
|
|
70
|
+
self.arrows_max: int = 15
|
|
71
|
+
|
|
72
|
+
self.body_damage: int = 0
|
|
73
|
+
|
|
74
|
+
self.strength: int = 0
|
|
75
|
+
self.dexterity: int = 0
|
|
76
|
+
self.intelligence: int = 0
|
|
77
|
+
self.wisdom: int = 0
|
|
78
|
+
self.initiative: int = 0
|
|
79
|
+
|
|
80
|
+
self.experience: int = 0
|
|
81
|
+
self.defense: Dict[Damage, int] = {dt: 0 for dt in Damage}
|
|
82
|
+
|
|
83
|
+
def update(self, elapsed_time: float):
|
|
84
|
+
self.timer -= elapsed_time
|
|
85
|
+
if self.timer <= 0.0:
|
|
86
|
+
self.timer += ATTRIBUTE_TIMER
|
|
87
|
+
|
|
88
|
+
self.health = min(
|
|
89
|
+
self.health + self.health_per_second * ATTRIBUTE_TIMER,
|
|
90
|
+
self.health_max,
|
|
91
|
+
)
|
|
92
|
+
self.magic = min(
|
|
93
|
+
self.magic + self.magic_per_second * ATTRIBUTE_TIMER,
|
|
94
|
+
self.magic_max,
|
|
95
|
+
)
|
|
96
|
+
self.stamina = min(
|
|
97
|
+
self.stamina + self.stamina_per_second * ATTRIBUTE_TIMER,
|
|
98
|
+
self.stamina_max,
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
@staticmethod
|
|
102
|
+
def from_dict(data):
|
|
103
|
+
attr = Attributes()
|
|
104
|
+
|
|
105
|
+
for key, val in data.items():
|
|
106
|
+
if "defense" in key:
|
|
107
|
+
def_key = key.split("_", 1)[1]
|
|
108
|
+
attr.defense[def_key] = val
|
|
109
|
+
setattr(attr, key, val)
|
|
110
|
+
|
|
111
|
+
return attr
|
|
112
|
+
|
|
113
|
+
@property
|
|
114
|
+
def health_percent(self):
|
|
115
|
+
return self.health / self.health_max
|
|
116
|
+
|
|
117
|
+
@property
|
|
118
|
+
def magic_percent(self):
|
|
119
|
+
return self.magic / self.magic_max
|
|
120
|
+
|
|
121
|
+
@property
|
|
122
|
+
def stamina_percent(self):
|
|
123
|
+
return self.stamina / self.stamina_max
|
mima/objects/creature.py
ADDED
|
@@ -0,0 +1,332 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import TYPE_CHECKING, 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.terrain import Terrain
|
|
10
|
+
from ..types.weapon_slot import WeaponSlot
|
|
11
|
+
|
|
12
|
+
# from ..types.weapon_slot import WeaponSlot
|
|
13
|
+
from ..util.colors import BLACK
|
|
14
|
+
from ..util.constants import DEFAULT_KNOCK_SPEED
|
|
15
|
+
from .animated_sprite import AnimatedSprite
|
|
16
|
+
from .dynamic import Dynamic
|
|
17
|
+
from .effects.walking_on_grass import WalkingOnGrass
|
|
18
|
+
from .effects.walking_on_water import WalkingOnWater
|
|
19
|
+
from .projectile import Projectile
|
|
20
|
+
|
|
21
|
+
# if TYPE_CHECKING:
|
|
22
|
+
# from ..usables.weapon import Weapon
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class Creature(Dynamic):
|
|
26
|
+
def __init__(
|
|
27
|
+
self,
|
|
28
|
+
name: str,
|
|
29
|
+
tileset_name: str,
|
|
30
|
+
image_name: str,
|
|
31
|
+
sprite_name: str,
|
|
32
|
+
px: float,
|
|
33
|
+
py: float,
|
|
34
|
+
dyn_id: int = -1,
|
|
35
|
+
):
|
|
36
|
+
super().__init__(name, px, py, dyn_id)
|
|
37
|
+
|
|
38
|
+
self.sprite = AnimatedSprite(tileset_name, image_name, sprite_name)
|
|
39
|
+
self.type = ObjectType.CREATURE
|
|
40
|
+
self.knock_speed: float = DEFAULT_KNOCK_SPEED
|
|
41
|
+
|
|
42
|
+
# self.sprite.name = sprite_name
|
|
43
|
+
# self.sprite.num_frames = 2
|
|
44
|
+
self.attackable = True
|
|
45
|
+
|
|
46
|
+
self._knock_vx: float = 0.0
|
|
47
|
+
self._knock_vy: float = 0.0
|
|
48
|
+
self.real_vx: float = 0.0
|
|
49
|
+
self.real_vy: float = 0.0
|
|
50
|
+
self.last_vx: float = 0.0
|
|
51
|
+
self.last_vy: float = 0.0
|
|
52
|
+
self._attack_timer: float = 0.0
|
|
53
|
+
self._invincible_timer: float = 0.0
|
|
54
|
+
self._knock_timer: float = 0.0
|
|
55
|
+
self._state_timer: float = 0.0
|
|
56
|
+
|
|
57
|
+
self.invincible: bool = False
|
|
58
|
+
# self.use_acceleration: bool = True
|
|
59
|
+
# self.use_friction: bool = True
|
|
60
|
+
|
|
61
|
+
self.projectiles: List[Projectile] = []
|
|
62
|
+
self.weapons: Dict[WeaponSlot, Optional[Weapon]] = {
|
|
63
|
+
WeaponSlot.FIRST_HAND: None,
|
|
64
|
+
WeaponSlot.SECOND_HAND: None,
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
def update(self, elapsed_time: float, target: Optional[Dynamic] = None):
|
|
68
|
+
|
|
69
|
+
if not self.visible:
|
|
70
|
+
self.vx = self.vy = 0.0
|
|
71
|
+
return
|
|
72
|
+
|
|
73
|
+
if self._knock_timer > 0.0: # Handle knocking
|
|
74
|
+
self._knock_timer -= elapsed_time
|
|
75
|
+
self._invincible_timer -= elapsed_time
|
|
76
|
+
self.vx, self.vy = self._knock_vx, self._knock_vy
|
|
77
|
+
self.speed = self.knock_speed
|
|
78
|
+
self.change_graphic_state(GraphicState.DAMAGED)
|
|
79
|
+
|
|
80
|
+
if self._knock_timer <= 0.0: # Knocking is over
|
|
81
|
+
self._state_timer = 0.0
|
|
82
|
+
self.controllable = True
|
|
83
|
+
self.solid_vs_dyn = True
|
|
84
|
+
self.sprite.reset()
|
|
85
|
+
|
|
86
|
+
else: # Usual update
|
|
87
|
+
if self._invincible_timer > 0.0:
|
|
88
|
+
self._invincible_timer -= elapsed_time
|
|
89
|
+
if self._invincible_timer <= 0.0:
|
|
90
|
+
self.invincible = False
|
|
91
|
+
self._invincible_timer = 0.0
|
|
92
|
+
|
|
93
|
+
if self._attack_timer > 0.0:
|
|
94
|
+
self._attack_timer -= elapsed_time
|
|
95
|
+
self.change_graphic_state(GraphicState.ATTACKING)
|
|
96
|
+
|
|
97
|
+
if self._attack_timer <= 0:
|
|
98
|
+
self._attack_timer = 0.0
|
|
99
|
+
if abs(self.vx) > 0 or abs(self.vy) > 0:
|
|
100
|
+
self.change_graphic_state(GraphicState.WALKING)
|
|
101
|
+
else:
|
|
102
|
+
self.change_graphic_state(GraphicState.STANDING)
|
|
103
|
+
|
|
104
|
+
if self.attributes.health <= 0:
|
|
105
|
+
self.change_graphic_state(GraphicState.DEAD)
|
|
106
|
+
|
|
107
|
+
if self.vx < -0.01:
|
|
108
|
+
self.facing_direction = Direction.WEST
|
|
109
|
+
if self.vx > 0.01:
|
|
110
|
+
self.facing_direction = Direction.EAST
|
|
111
|
+
if self.vy < -0.01:
|
|
112
|
+
self.facing_direction = Direction.NORTH
|
|
113
|
+
if self.vy > 0.01:
|
|
114
|
+
self.facing_direction = Direction.SOUTH
|
|
115
|
+
|
|
116
|
+
self.speed = self.attributes.speed
|
|
117
|
+
|
|
118
|
+
if self.can_act():
|
|
119
|
+
self.behavior(elapsed_time, target)
|
|
120
|
+
|
|
121
|
+
self.sprite.update(
|
|
122
|
+
elapsed_time, self.facing_direction, self.graphic_state
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
self._handle_terrain(elapsed_time)
|
|
126
|
+
|
|
127
|
+
if self.graphic_state == GraphicState.DEAD:
|
|
128
|
+
self.attackable = False
|
|
129
|
+
self.vx = self.vy = 0
|
|
130
|
+
if self.despawn_timer is None:
|
|
131
|
+
self.despawn_timer = self.despawn_duration
|
|
132
|
+
self.engine.play_sound("enemy_killed")
|
|
133
|
+
else:
|
|
134
|
+
self.despawn_timer -= elapsed_time
|
|
135
|
+
|
|
136
|
+
if self.despawn_timer <= 0.0:
|
|
137
|
+
self.kill()
|
|
138
|
+
else:
|
|
139
|
+
eff2r = []
|
|
140
|
+
for eff in self.attribute_effects:
|
|
141
|
+
if (
|
|
142
|
+
self.attributes.health < eff.health_cost
|
|
143
|
+
or self.attributes.magic < eff.magic_cost
|
|
144
|
+
or self.attributes.stamina < eff.stamina_cost
|
|
145
|
+
# and self.attributes.arrows >= weapon.arrow_cost
|
|
146
|
+
# and self.attributes.bombs >= weapon.bomb_cost
|
|
147
|
+
):
|
|
148
|
+
eff.redundant = True
|
|
149
|
+
self.attributes.health_per_second += eff.health_cost
|
|
150
|
+
self.attributes.magic_per_second += eff.magic_cost
|
|
151
|
+
self.attributes.stamina_per_second += eff.stamina_cost
|
|
152
|
+
eff2r.append(eff)
|
|
153
|
+
for e in eff2r:
|
|
154
|
+
self.attribute_effects.remove(e)
|
|
155
|
+
|
|
156
|
+
self.attributes.update(elapsed_time)
|
|
157
|
+
|
|
158
|
+
if self._gs_lock_condition == Until.NEXT_UPDATE:
|
|
159
|
+
self.graphic_state_locked = False
|
|
160
|
+
|
|
161
|
+
def draw_self(self, ox: float, oy: float):
|
|
162
|
+
if (
|
|
163
|
+
self.sprite.name is None
|
|
164
|
+
or self.sprite.name == ""
|
|
165
|
+
or not self.visible
|
|
166
|
+
):
|
|
167
|
+
return
|
|
168
|
+
|
|
169
|
+
px = self.px - ox + self.extra_ox
|
|
170
|
+
py = self.py - oy + self.extra_oy
|
|
171
|
+
|
|
172
|
+
if self.pz != 0:
|
|
173
|
+
self.engine.backend.fill_circle(
|
|
174
|
+
px + 0.5 * self.sprite.width,
|
|
175
|
+
py + 0.7 * self.sprite.height,
|
|
176
|
+
0.3125 * self.sprite.width,
|
|
177
|
+
BLACK,
|
|
178
|
+
)
|
|
179
|
+
|
|
180
|
+
self.sprite.draw_self(px, py - self.pz)
|
|
181
|
+
# for effect in self.effects:
|
|
182
|
+
# effect.draw_self(px, py - self.pz)
|
|
183
|
+
|
|
184
|
+
def behavior(self, elapsed_time: float, target: Optional[Dynamic] = None):
|
|
185
|
+
# No default behavior
|
|
186
|
+
pass
|
|
187
|
+
|
|
188
|
+
def on_interaction(self, target=None, nature=Nature.WALK):
|
|
189
|
+
if nature == Nature.SIGNAL:
|
|
190
|
+
self.solid_vs_dyn = False
|
|
191
|
+
self.visible = False
|
|
192
|
+
return True
|
|
193
|
+
|
|
194
|
+
elif nature == Nature.NO_SIGNAL:
|
|
195
|
+
self.visible = True
|
|
196
|
+
self.solid_vs_dyn = True
|
|
197
|
+
return True
|
|
198
|
+
return False
|
|
199
|
+
|
|
200
|
+
def knock_back(self, vx: float, vy: float, dist: float):
|
|
201
|
+
self._knock_vx = vx
|
|
202
|
+
self._knock_vy = vy
|
|
203
|
+
self._knock_timer = dist
|
|
204
|
+
self.invincible_timer = dist + 0.2
|
|
205
|
+
# self.solid_vs_dyn = False
|
|
206
|
+
self.controllable = False
|
|
207
|
+
self.invincible = True
|
|
208
|
+
self.sprite.reset()
|
|
209
|
+
self.cancel_attack()
|
|
210
|
+
|
|
211
|
+
def can_act(self):
|
|
212
|
+
actable_states = [
|
|
213
|
+
GraphicState.STANDING,
|
|
214
|
+
GraphicState.WALKING,
|
|
215
|
+
GraphicState.CELEBRATING,
|
|
216
|
+
# GraphicState.DAMAGED,
|
|
217
|
+
GraphicState.PUSHING,
|
|
218
|
+
]
|
|
219
|
+
if self.graphic_state in actable_states:
|
|
220
|
+
return True
|
|
221
|
+
|
|
222
|
+
return False
|
|
223
|
+
|
|
224
|
+
def perform_attack(self, slot: WeaponSlot) -> bool:
|
|
225
|
+
weapon = self.weapons.get(slot, None)
|
|
226
|
+
|
|
227
|
+
if weapon is None or not self.can_attack:
|
|
228
|
+
# print(f"Cannot attack. Weapon={weapon}")
|
|
229
|
+
return False
|
|
230
|
+
|
|
231
|
+
# Clean up all previous projectiles
|
|
232
|
+
self.cancel_attack()
|
|
233
|
+
|
|
234
|
+
if (
|
|
235
|
+
self.attributes.health >= weapon.health_cost
|
|
236
|
+
and self.attributes.magic >= weapon.magic_cost
|
|
237
|
+
and self.attributes.stamina >= weapon.stamina_cost
|
|
238
|
+
and self.attributes.arrows >= weapon.arrow_cost
|
|
239
|
+
and self.attributes.bombs >= weapon.bomb_cost
|
|
240
|
+
):
|
|
241
|
+
self.attributes.health -= weapon.health_cost
|
|
242
|
+
self.attributes.magic -= weapon.magic_cost
|
|
243
|
+
self.attributes.stamina -= weapon.stamina_cost
|
|
244
|
+
self.attributes.arrows -= weapon.arrow_cost
|
|
245
|
+
self.attributes.bombs -= weapon.bomb_cost
|
|
246
|
+
else:
|
|
247
|
+
return False
|
|
248
|
+
|
|
249
|
+
if weapon.on_use(self):
|
|
250
|
+
self.vx = self.vy = 0.0
|
|
251
|
+
self._attack_timer = weapon.swing_timer
|
|
252
|
+
return True
|
|
253
|
+
|
|
254
|
+
return False
|
|
255
|
+
|
|
256
|
+
def cancel_attack(self):
|
|
257
|
+
# if self.projectiles:
|
|
258
|
+
for projectile in self.projectiles:
|
|
259
|
+
if projectile is not None:
|
|
260
|
+
projectile.spawn_on_death = []
|
|
261
|
+
projectile.kill()
|
|
262
|
+
|
|
263
|
+
self.projectiles = []
|
|
264
|
+
|
|
265
|
+
def on_death(self):
|
|
266
|
+
if self.spawn_on_death:
|
|
267
|
+
for do in self.spawn_on_death:
|
|
268
|
+
if do.redundant:
|
|
269
|
+
continue
|
|
270
|
+
if do.type == ObjectType.PROJECTILE:
|
|
271
|
+
if do.inherit_pos:
|
|
272
|
+
do.px = self.px
|
|
273
|
+
do.py = self.py
|
|
274
|
+
self.engine.scene.add_projectile(do)
|
|
275
|
+
else:
|
|
276
|
+
self.engine.scene.add_dynamic(do)
|
|
277
|
+
|
|
278
|
+
self.spawn_on_death = []
|
|
279
|
+
|
|
280
|
+
def _handle_terrain(self, elapsed_time: float):
|
|
281
|
+
e2rm = []
|
|
282
|
+
for effect in self.effects:
|
|
283
|
+
if isinstance(effect, WalkingOnGrass):
|
|
284
|
+
if self.walking_on == Terrain.DEFAULT:
|
|
285
|
+
e2rm.append(effect)
|
|
286
|
+
|
|
287
|
+
for effect in e2rm:
|
|
288
|
+
self.effects.remove(effect)
|
|
289
|
+
|
|
290
|
+
if self.walking_on in [Terrain.GRASS, Terrain.SHALLOW_WATER]:
|
|
291
|
+
self.attributes.speed_mod = 0.7
|
|
292
|
+
effect_active = False
|
|
293
|
+
for effect in self.effects:
|
|
294
|
+
if isinstance(effect, (WalkingOnGrass, WalkingOnWater)):
|
|
295
|
+
effect_active = True
|
|
296
|
+
effect.renew = True
|
|
297
|
+
break
|
|
298
|
+
|
|
299
|
+
if not effect_active:
|
|
300
|
+
if self.walking_on == Terrain.GRASS:
|
|
301
|
+
eff = WalkingOnGrass(self)
|
|
302
|
+
else:
|
|
303
|
+
eff = WalkingOnWater(self)
|
|
304
|
+
self.effects.append(eff)
|
|
305
|
+
self.engine.scene.add_effect(eff)
|
|
306
|
+
else:
|
|
307
|
+
self.attributes.speed_mod = 1.0
|
|
308
|
+
|
|
309
|
+
def equip_weapon(self, slot, weapon):
|
|
310
|
+
if self.weapons[slot] == weapon:
|
|
311
|
+
# Weapon already equipped; unequip
|
|
312
|
+
self.weapons[slot] = None
|
|
313
|
+
weapon.on_unequip(self)
|
|
314
|
+
return
|
|
315
|
+
|
|
316
|
+
for s, w in self.weapons.items():
|
|
317
|
+
if slot != s and weapon == w:
|
|
318
|
+
# Weapon equipped in a different slot; change
|
|
319
|
+
self.weapons[slot] = weapon
|
|
320
|
+
self.weapons[s] = None
|
|
321
|
+
return
|
|
322
|
+
|
|
323
|
+
if self.weapons[slot] is not None:
|
|
324
|
+
# Other weapon equipped; unequip that
|
|
325
|
+
self.weapons[slot].on_unequip(self)
|
|
326
|
+
|
|
327
|
+
self.weapons[slot] = weapon
|
|
328
|
+
weapon.on_equip(self)
|
|
329
|
+
|
|
330
|
+
def unequip_weapon(self, slot):
|
|
331
|
+
if self.weapons[slot] is not None:
|
|
332
|
+
self.weapons[slot].on_unequip(self)
|
mima/objects/dynamic.py
ADDED
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import TYPE_CHECKING, List, Optional
|
|
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.terrain import Terrain
|
|
12
|
+
from .attributes import Attributes
|
|
13
|
+
from .attribute_effect import Effect
|
|
14
|
+
from .sprite import Sprite
|
|
15
|
+
|
|
16
|
+
if TYPE_CHECKING:
|
|
17
|
+
from ..engine import MimaEngine
|
|
18
|
+
from .projectile import Projectile
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class Dynamic:
|
|
22
|
+
engine: MimaEngine
|
|
23
|
+
|
|
24
|
+
def __init__(
|
|
25
|
+
self,
|
|
26
|
+
name: str = "Unnamed Dynamic",
|
|
27
|
+
px: float = 0.0,
|
|
28
|
+
py: float = 0.0,
|
|
29
|
+
dyn_id=-1,
|
|
30
|
+
):
|
|
31
|
+
self.name: str = name
|
|
32
|
+
self.dyn_id: int = dyn_id # ID given by Tiled
|
|
33
|
+
self.layer: int = 1
|
|
34
|
+
|
|
35
|
+
self.px: float = px
|
|
36
|
+
self.py: float = py
|
|
37
|
+
self.pz: float = 0.0
|
|
38
|
+
self.vx: float = 0.0
|
|
39
|
+
self.vy: float = 0.0
|
|
40
|
+
self.vz: float = 0.0
|
|
41
|
+
self.real_vx: float = 0.0
|
|
42
|
+
self.real_vy: float = 0.0
|
|
43
|
+
self.hitbox_px: float = 0.1
|
|
44
|
+
self.hitbox_py: float = 0.1
|
|
45
|
+
self.hitbox_width: float = 0.8
|
|
46
|
+
self.hitbox_height: float = 0.8
|
|
47
|
+
self.attribute_timer: float = 0.25
|
|
48
|
+
self.despawn_timer: float = 0.5
|
|
49
|
+
self.despawn_duration: float = 0.2
|
|
50
|
+
self.timer: float = 0.0
|
|
51
|
+
self.speed: float = 1.0
|
|
52
|
+
|
|
53
|
+
self.solid_vs_map: bool = True
|
|
54
|
+
self.solid_vs_dyn: bool = True
|
|
55
|
+
# self.is_player: bool = False
|
|
56
|
+
# self.is_projectile: bool = False
|
|
57
|
+
self.redundant: bool = False
|
|
58
|
+
self.persistent: bool = False
|
|
59
|
+
self.attackable: bool = False
|
|
60
|
+
self.controllable: bool = True
|
|
61
|
+
self.state_changed: bool = False
|
|
62
|
+
self.visible: bool = True
|
|
63
|
+
self.gravity: bool = True
|
|
64
|
+
self.graphic_state_locked: bool = False
|
|
65
|
+
self.use_acceleration: bool = False
|
|
66
|
+
self.use_friction: bool = False
|
|
67
|
+
|
|
68
|
+
self.alignment: Alignment = Alignment.GOOD
|
|
69
|
+
self.walking_on: Terrain = Terrain.DEFAULT
|
|
70
|
+
self.attributes: Attributes = Attributes()
|
|
71
|
+
self.facing_direction: Direction = Direction.SOUTH
|
|
72
|
+
self.graphic_state: GraphicState = GraphicState.STANDING
|
|
73
|
+
self._gs_lock_condition: Until = Until.UNLOCK
|
|
74
|
+
self.type: ObjectType = ObjectType.UNDEFINED
|
|
75
|
+
self.spawn_on_death: List[Dynamic] = []
|
|
76
|
+
self.childs: List[Dynamic] = []
|
|
77
|
+
self.effects: List[Projectile] = []
|
|
78
|
+
self.attribute_effects: List[Effect] = []
|
|
79
|
+
|
|
80
|
+
self.extra_ox: float = 0.0
|
|
81
|
+
self.extra_oy: float = 0.0
|
|
82
|
+
|
|
83
|
+
self.can_attack: bool = True
|
|
84
|
+
self.can_lift: bool = False
|
|
85
|
+
self.can_push: bool = False
|
|
86
|
+
|
|
87
|
+
self.sprite: Sprite = Sprite()
|
|
88
|
+
|
|
89
|
+
# Performance flags
|
|
90
|
+
self.update_skippable: bool = False
|
|
91
|
+
"""Update may be skipped if object is offscreen."""
|
|
92
|
+
self.offscreen_collision_skippable: bool = True
|
|
93
|
+
"""Collision checks may be skipped if object is offscreen."""
|
|
94
|
+
self.onscreen_collision_skippable: bool = False
|
|
95
|
+
"""Collision checks may be skipped if object is onscreen."""
|
|
96
|
+
self.draw_skippable: bool = True
|
|
97
|
+
"""Drawing the unit may be skipped if object is offscreen."""
|
|
98
|
+
|
|
99
|
+
def update(self, elapsed_time: float, target: Optional[Dynamic]):
|
|
100
|
+
"""Update this dynamic."""
|
|
101
|
+
pass
|
|
102
|
+
|
|
103
|
+
def draw_self(self, ox: float, oy: float):
|
|
104
|
+
"""Draw self to screen"""
|
|
105
|
+
pass
|
|
106
|
+
|
|
107
|
+
def on_interaction(
|
|
108
|
+
self, target: Dynamic = None, nature: Nature = Nature.WALK
|
|
109
|
+
) -> bool:
|
|
110
|
+
"""Handle interaction with other dynamic objects."""
|
|
111
|
+
return False
|
|
112
|
+
|
|
113
|
+
def on_death(self) -> bool:
|
|
114
|
+
return False
|
|
115
|
+
|
|
116
|
+
def kill(self):
|
|
117
|
+
self.redundant = True
|
|
118
|
+
|
|
119
|
+
def change_graphic_state(self, new_state: GraphicState) -> bool:
|
|
120
|
+
if self.graphic_state_locked or new_state == self.graphic_state:
|
|
121
|
+
return False
|
|
122
|
+
|
|
123
|
+
self.graphic_state = new_state
|
|
124
|
+
self.sprite.update(0.0, self.facing_direction, self.graphic_state)
|
|
125
|
+
|
|
126
|
+
return True
|
|
127
|
+
|
|
128
|
+
def lock_graphic_state(
|
|
129
|
+
self,
|
|
130
|
+
new_state: GraphicState = GraphicState.STANDING,
|
|
131
|
+
until: Until = Until.UNLOCK,
|
|
132
|
+
):
|
|
133
|
+
self.change_graphic_state(new_state)
|
|
134
|
+
self.graphic_state_locked = True
|
|
135
|
+
self._gs_lock_condition = until
|
|
136
|
+
|
|
137
|
+
def unlock_graphic_state(self):
|
|
138
|
+
self.graphic_state_locked = False
|
|
139
|
+
self.change_graphic_state(GraphicState.STANDING)
|
|
140
|
+
|
|
141
|
+
def get_defense_value(self, dtype: Damage) -> int:
|
|
142
|
+
return self.attributes.defense[dtype]
|
|
143
|
+
|
|
144
|
+
def is_py_lower(self, other: Dynamic) -> bool:
|
|
145
|
+
return self.py < other.py
|
|
146
|
+
|
|
147
|
+
def get_alignment(self) -> Alignment:
|
|
148
|
+
return self.alignment
|
|
149
|
+
|
|
150
|
+
def add_effect(self, eff: Effect):
|
|
151
|
+
self.attribute_effects.append(eff)
|
|
152
|
+
|
|
153
|
+
def remove_effect(self, eff: Union[str, Effect]):
|
|
154
|
+
if isinstance(eff, str):
|
|
155
|
+
obj = [e for e in self.attribute_effects if e.effect_id == eff]
|
|
156
|
+
if not obj:
|
|
157
|
+
print("Effect not active")
|
|
158
|
+
return
|
|
159
|
+
else:
|
|
160
|
+
eff = obj[0]
|
|
161
|
+
|
|
162
|
+
self.attribute_effects.remove(eff)
|
|
163
|
+
|
|
164
|
+
def has_effect(self, eff: Union[str, Effect]):
|
|
165
|
+
if isinstance(eff, str):
|
|
166
|
+
for e in self.attribute_effects:
|
|
167
|
+
if e.effect_id == eff:
|
|
168
|
+
return True
|
|
169
|
+
return False
|
|
170
|
+
else:
|
|
171
|
+
if eff in self.attribute_effects:
|
|
172
|
+
return True
|
|
173
|
+
else:
|
|
174
|
+
return False
|
|
175
|
+
|
|
176
|
+
def light_radius(self):
|
|
177
|
+
rad = self.attributes.light_radius
|
|
178
|
+
|
|
179
|
+
for eff in self.attribute_effects:
|
|
180
|
+
rad += eff.light_radius
|
|
181
|
+
|
|
182
|
+
return rad
|
|
File without changes
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
from ...types.alignment import Alignment
|
|
2
|
+
from ...util.colors import Color, BLACK
|
|
3
|
+
from ...util.constants import HEIGHT, WIDTH
|
|
4
|
+
from ..dynamic import Dynamic
|
|
5
|
+
from ..projectile import Projectile
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class ColorizeScreen(Projectile):
|
|
9
|
+
def __init__(
|
|
10
|
+
self,
|
|
11
|
+
color: Color = BLACK,
|
|
12
|
+
alpha=BLACK.alpha,
|
|
13
|
+
duration: float = -1.0,
|
|
14
|
+
to_filter: bool = False,
|
|
15
|
+
):
|
|
16
|
+
super().__init__(0, 0, 0, 0, duration, Alignment.GOOD)
|
|
17
|
+
self.layer = 1
|
|
18
|
+
self.solid_vs_map = False
|
|
19
|
+
self.color = color
|
|
20
|
+
self.alpha = alpha
|
|
21
|
+
self.vanishs_after_time: bool = duration > 0
|
|
22
|
+
self.to_filter = to_filter
|
|
23
|
+
|
|
24
|
+
def update(self, elapsed_time: float, target: Dynamic = None):
|
|
25
|
+
if self.vanishs_after_time:
|
|
26
|
+
self.duration -= elapsed_time
|
|
27
|
+
if self.duration <= 0.0:
|
|
28
|
+
self.kill()
|
|
29
|
+
|
|
30
|
+
# self.sprite.update(elapsed_time, self.facing_direction, self.graphic_state)
|
|
31
|
+
|
|
32
|
+
def draw_self(self, ox: float, oy: float):
|
|
33
|
+
color = Color(self.color.red, self.color.green, self.color.blue, self.alpha)
|
|
34
|
+
self.engine.backend.fill_rect(
|
|
35
|
+
self.px, self.py, self.engine.backend.render_width, self.engine.backend.render_height, color, draw_to_filter=self.to_filter
|
|
36
|
+
)
|