source: nagslang/enemies.py@ 333:3dd32686dbc3

Last change on this file since 333:3dd32686dbc3 was 333:3dd32686dbc3, checked in by Jeremy Thurgood <firxen@…>, 8 years ago

Better wolf claw attack.

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