source: nagslang/enemies.py @ 358:911547a1c378

Last change on this file since 358:911547a1c378 was 358:911547a1c378, checked in by Stefano Rivera <stefano@…>, 7 years ago

Kill dead speed limits

File size: 7.2 KB
Line 
1import math
2
3import pymunk
4import pymunk.pygame_util
5
6from nagslang import render
7from nagslang.constants import (COLLISION_TYPE_ENEMY, COLLISION_TYPE_FURNITURE,
8                                ZORDER_MID)
9from nagslang.events import EnemyDeathEvent
10from nagslang.game_object import GameObject, SingleShapePhysicser, make_body
11from nagslang.mutators import FLIP_H
12from nagslang.resources import resources
13from nagslang.utils import vec_with_length
14
15
16def get_editable_enemies():
17    classes = []
18    for cls_name, cls in globals().iteritems():
19        if isinstance(cls, type) and issubclass(cls, Enemy):
20            if hasattr(cls, 'requires'):
21                classes.append((cls_name, cls))
22    return classes
23
24
25def get_alien_image(enemy_type, suffix, *transforms):
26    image_name = 'alien_%s_%s.png' % (enemy_type, suffix)
27    return resources.get_image('creatures', image_name, transforms=transforms)
28
29
30class Enemy(GameObject):
31    """A base class for mobile enemies"""
32    zorder = ZORDER_MID
33    enemy_type = None  # Which set of images to use
34    enemy_damage = None
35    health = None
36    impulse_factor = None
37
38    def __init__(self, space, world, position):
39        super(Enemy, self).__init__(
40            self.make_physics(space, position), self.make_renderer())
41        self.world = world
42        self.angle = 0
43
44    def make_physics(self, space, position):
45        raise NotImplementedError
46
47    def make_renderer(self):
48        return render.FacingSelectionRenderer({
49            'left': render.TimedAnimatedRenderer(
50                [get_alien_image(self.enemy_type, '1'),
51                 get_alien_image(self.enemy_type, '2')], 3),
52            'right': render.TimedAnimatedRenderer(
53                [get_alien_image(self.enemy_type, '1', FLIP_H),
54                 get_alien_image(self.enemy_type, '2', FLIP_H)], 3),
55        })
56
57    def attack(self):
58        raise NotImplementedError
59
60    @classmethod
61    def requires(cls):
62        return [("name", "string"), ("position", "coordinates")]
63
64    def hit(self, weapon):
65        self.lose_health(weapon.damage)
66
67    def lose_health(self, amount):
68        self.health -= amount
69        if self.health < 0:
70            self.physicser.remove_from_space()
71            self.remove = True
72            EnemyDeathEvent.post(self.physicser.position, self.enemy_type)
73
74    def set_direction(self, dx, dy):
75        vec = vec_with_length((dx, dy), self.impulse_factor)
76        self.angle = vec.angle
77        self.physicser.apply_impulse(vec)
78
79    def collide_with_protagonist(self, protagonist):
80        if self.enemy_damage is not None:
81            protagonist.lose_health(self.enemy_damage)
82
83    def collide_with_claw_attack(self, claw_attack):
84        self.lose_health(claw_attack.damage)
85
86
87class DeadEnemy(GameObject):
88    def __init__(self, space, world, position, enemy_type='A'):
89        body = make_body(10, 10000, position, damping=0.5)
90        self.shape = pymunk.Poly(
91            body, [(-20, -20), (20, -20), (20, 20), (-20, 20)])
92        self.shape.friction = 0.5
93        self.shape.collision_type = COLLISION_TYPE_FURNITURE
94        super(DeadEnemy, self).__init__(
95            SingleShapePhysicser(space, self.shape),
96            render.ImageRenderer(resources.get_image(
97                'creatures', 'alien_%s_dead.png' % enemy_type)),
98        )
99
100    @classmethod
101    def requires(cls):
102        return [("name", "string"), ("position", "coordinates")]
103
104
105class PatrollingAlien(Enemy):
106    is_moving = True  # Always walking.
107    enemy_type = 'A'
108    health = 42
109    enemy_damage = 15
110    impulse_factor = 50
111
112    def __init__(self, space, world, position, end_position):
113        # An enemy that patrols between the two points
114        super(PatrollingAlien, self).__init__(space, world, position)
115        self._start_pos = position
116        self._end_pos = end_position
117        self._direction = 'away'
118
119    def make_physics(self, space, position):
120        body = make_body(10, pymunk.inf, position, 0.8)
121        shape = pymunk.Circle(body, 30)
122        shape.elasticity = 1.0
123        shape.friction = 0.05
124        shape.collision_type = COLLISION_TYPE_ENEMY
125        return SingleShapePhysicser(space, shape)
126
127    def get_render_angle(self):
128        # No image rotation when rendering, please.
129        return 0
130
131    def get_facing_direction(self):
132        # Enemies can face left or right.
133        if - math.pi / 2 < self.angle <= math.pi / 2:
134            return 'right'
135        else:
136            return 'left'
137
138    def _switch_direction(self):
139        if self._direction == 'away':
140            self._direction = 'towards'
141        else:
142            self._direction = 'away'
143
144    def update(self, dt):
145        # Calculate the step every frame
146        if self._direction == 'away':
147            target = self._end_pos
148        else:
149            target = self._start_pos
150        x_step = 0
151        y_step = 0
152        if (target[0] < self.physicser.position[0]):
153            x_step = max(-1, target[0] - self.physicser.position[0])
154        elif (target[0] > self.physicser.position[0]):
155            x_step = min(1, target[0] - self.physicser.position[0])
156        if abs(x_step) < 0.5:
157            x_step = 0
158        if (target[1] < self.physicser.position[1]):
159            y_step = max(-1, target[1] - self.physicser.position[1])
160        elif (target[1] > self.physicser.position[1]):
161            y_step = min(1, target[1] - self.physicser.position[1])
162        if abs(y_step) < 0.5:
163            y_step = 0
164        if abs(x_step) < 1 and abs(y_step) < 1:
165            self._switch_direction()
166        self.set_direction(x_step, y_step)
167        super(PatrollingAlien, self).update(dt)
168
169    @classmethod
170    def requires(cls):
171        return [("name", "string"), ("position", "coordinates"),
172                ("end_position", "coordinates")]
173
174
175class ChargingAlien(Enemy):
176    # Simplistic charging of the protagonist
177    is_moving = False
178    enemy_type = 'B'
179    health = 42
180    enemy_damage = 20
181    impulse_factor = 300
182
183    def __init__(self, space, world, position, attack_range=100):
184        super(ChargingAlien, self).__init__(space, world, position)
185        self._range = attack_range
186
187    def make_physics(self, space, position):
188        body = make_body(100, pymunk.inf, position, 0.8)
189        shape = pymunk.Circle(body, 30)
190        shape.elasticity = 1.0
191        shape.friction = 0.05
192        shape.collision_type = COLLISION_TYPE_ENEMY
193        return SingleShapePhysicser(space, shape)
194
195    def get_render_angle(self):
196        # No image rotation when rendering, please.
197        return 0
198
199    def get_facing_direction(self):
200        # Enemies can face left or right.
201        if - math.pi / 2 < self.angle <= math.pi / 2:
202            return 'right'
203        else:
204            return 'left'
205
206    def update(self, dt):
207        # Calculate the step every frame
208        # Distance to the protagonist
209        pos = self.physicser.position
210        target = self.world.protagonist.get_shape().body.position
211        if pos.get_distance(target) > self._range:
212            # stop
213            self.is_moving = False
214            return
215        self.is_moving = True
216        self.set_direction(target.x - pos.x, target.y - pos.y)
217        super(ChargingAlien, self).update(dt)
218
219    @classmethod
220    def requires(cls):
221        return [("name", "string"), ("position", "coordinates"),
222                ("attack_range", "distance")]
Note: See TracBrowser for help on using the repository browser.