source: nagslang/enemies.py@ 361:534eac55a178

Last change on this file since 361:534eac55a178 was 361:534eac55a178, checked in by Stefano Rivera <stefano@…>, 9 years ago

ChargingEnemy spits acid

File size: 7.6 KB
RevLine 
[229]1import math
2
[168]3import pymunk
4import pymunk.pygame_util
5
[207]6from nagslang import render
[318]7from nagslang.constants import (COLLISION_TYPE_ENEMY, COLLISION_TYPE_FURNITURE,
[361]8 ACID_SPEED, ACID_DAMAGE, ZORDER_MID)
9from nagslang.events import EnemyDeathEvent, FireEvent
[207]10from nagslang.game_object import GameObject, SingleShapePhysicser, make_body
[168]11from nagslang.mutators import FLIP_H
12from nagslang.resources import resources
[334]13from nagslang.utils import vec_with_length
[168]14
15
[235]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
[334]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
[168]30class Enemy(GameObject):
31 """A base class for mobile enemies"""
[334]32 zorder = ZORDER_MID
33 enemy_type = None # Which set of images to use
[333]34 enemy_damage = None
[334]35 health = None
36 impulse_factor = None
[168]37
[277]38 def __init__(self, space, world, position):
[334]39 super(Enemy, self).__init__(
40 self.make_physics(space, position), self.make_renderer())
41 self.world = world
42 self.angle = 0
[168]43
[334]44 def make_physics(self, space, position):
[168]45 raise NotImplementedError
46
[334]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 })
[168]56
57 def attack(self):
58 raise NotImplementedError
59
[235]60 @classmethod
61 def requires(cls):
62 return [("name", "string"), ("position", "coordinates")]
63
[310]64 def hit(self, weapon):
65 self.lose_health(weapon.damage)
66
[305]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
[310]72 EnemyDeathEvent.post(self.physicser.position, self.enemy_type)
[308]73
[334]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
[333]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
[308]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
[318]93 self.shape.collision_type = COLLISION_TYPE_FURNITURE
[308]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")]
[305]103
[168]104
105class PatrollingAlien(Enemy):
[218]106 is_moving = True # Always walking.
[308]107 enemy_type = 'A'
[334]108 health = 42
[341]109 enemy_damage = 15
[334]110 impulse_factor = 50
[168]111
[277]112 def __init__(self, space, world, position, end_position):
[168]113 # An enemy that patrols between the two points
[277]114 super(PatrollingAlien, self).__init__(space, world, position)
[168]115 self._start_pos = position
116 self._end_pos = end_position
117 self._direction = 'away'
118
[334]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)
[168]126
127 def get_render_angle(self):
[229]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'
[168]137
138 def _switch_direction(self):
139 if self._direction == 'away':
140 self._direction = 'towards'
141 else:
142 self._direction = 'away'
143
[333]144 def update(self, dt):
[168]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
[334]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])
[168]156 if abs(x_step) < 0.5:
157 x_step = 0
[334]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])
[168]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)
[333]167 super(PatrollingAlien, self).update(dt)
[258]168
[235]169 @classmethod
170 def requires(cls):
171 return [("name", "string"), ("position", "coordinates"),
172 ("end_position", "coordinates")]
[278]173
174
175class ChargingAlien(Enemy):
176 # Simplistic charging of the protagonist
177 is_moving = False
[308]178 enemy_type = 'B'
[334]179 health = 42
[341]180 enemy_damage = 20
[334]181 impulse_factor = 300
[361]182 reload_time = 0.2
[278]183
184 def __init__(self, space, world, position, attack_range=100):
185 super(ChargingAlien, self).__init__(space, world, position)
186 self._range = attack_range
[361]187 self._last_fired = 0
[278]188
[334]189 def make_physics(self, space, position):
190 body = make_body(100, pymunk.inf, position, 0.8)
191 shape = pymunk.Circle(body, 30)
192 shape.elasticity = 1.0
193 shape.friction = 0.05
194 shape.collision_type = COLLISION_TYPE_ENEMY
195 return SingleShapePhysicser(space, shape)
[278]196
197 def get_render_angle(self):
198 # No image rotation when rendering, please.
199 return 0
200
201 def get_facing_direction(self):
202 # Enemies can face left or right.
203 if - math.pi / 2 < self.angle <= math.pi / 2:
204 return 'right'
205 else:
206 return 'left'
207
[333]208 def update(self, dt):
[278]209 # Calculate the step every frame
210 # Distance to the protagonist
[334]211 pos = self.physicser.position
[278]212 target = self.world.protagonist.get_shape().body.position
213 if pos.get_distance(target) > self._range:
214 # stop
215 self.is_moving = False
216 return
217 self.is_moving = True
[361]218 dx = target.x - pos.x
219 dy = target.y - pos.y
220 self.set_direction(dx, dy)
221 if self.lifetime - self._last_fired >= self.reload_time:
222 FireEvent.post(pos, vec_with_length((dx, dy), ACID_SPEED),
223 ACID_DAMAGE, COLLISION_TYPE_ENEMY)
224 self._last_fired = self.lifetime
[333]225 super(ChargingAlien, self).update(dt)
[278]226
227 @classmethod
228 def requires(cls):
229 return [("name", "string"), ("position", "coordinates"),
230 ("attack_range", "distance")]
Note: See TracBrowser for help on using the repository browser.