1 | import math |
---|
2 | |
---|
3 | import pymunk |
---|
4 | import pymunk.pygame_util |
---|
5 | |
---|
6 | from nagslang import render |
---|
7 | from nagslang.constants import COLLISION_TYPE_ENEMY, ZORDER_MID |
---|
8 | from nagslang.game_object import GameObject, SingleShapePhysicser, make_body |
---|
9 | from nagslang.mutators import FLIP_H |
---|
10 | from nagslang.resources import resources |
---|
11 | |
---|
12 | |
---|
13 | def get_editable_enemies(): |
---|
14 | classes = [] |
---|
15 | for cls_name, cls in globals().iteritems(): |
---|
16 | if isinstance(cls, type) and issubclass(cls, Enemy): |
---|
17 | if hasattr(cls, 'requires'): |
---|
18 | classes.append((cls_name, cls)) |
---|
19 | return classes |
---|
20 | |
---|
21 | |
---|
22 | class Enemy(GameObject): |
---|
23 | """A base class for mobile enemies""" |
---|
24 | |
---|
25 | def __init__(self, space, world, position): |
---|
26 | self._setup_physics(space, position) |
---|
27 | self._setup_renderer() |
---|
28 | |
---|
29 | super(Enemy, self).__init__( |
---|
30 | self._physicser, self.renderer) |
---|
31 | self.zorder = ZORDER_MID |
---|
32 | self.world = world |
---|
33 | |
---|
34 | def _get_image(self, name, *transforms): |
---|
35 | return resources.get_image('creatures', name, transforms=transforms) |
---|
36 | |
---|
37 | def _setup_physics(self, space, position): |
---|
38 | raise NotImplementedError |
---|
39 | |
---|
40 | def _setup_renderer(self): |
---|
41 | raise NotImplementedError |
---|
42 | |
---|
43 | def attack(self): |
---|
44 | raise NotImplementedError |
---|
45 | |
---|
46 | @classmethod |
---|
47 | def requires(cls): |
---|
48 | return [("name", "string"), ("position", "coordinates")] |
---|
49 | |
---|
50 | |
---|
51 | class PatrollingAlien(Enemy): |
---|
52 | is_moving = True # Always walking. |
---|
53 | |
---|
54 | def __init__(self, space, world, position, end_position): |
---|
55 | # An enemy that patrols between the two points |
---|
56 | super(PatrollingAlien, self).__init__(space, world, position) |
---|
57 | self._start_pos = position |
---|
58 | self._end_pos = end_position |
---|
59 | self._direction = 'away' |
---|
60 | |
---|
61 | def _setup_physics(self, space, position): |
---|
62 | self._body = make_body(10, pymunk.inf, position, 0.8) |
---|
63 | |
---|
64 | self._shape = pymunk.Circle(self._body, 30) |
---|
65 | |
---|
66 | self._shape.elasticity = 1.0 |
---|
67 | self._shape.friction = 0.05 |
---|
68 | self._shape.collision_type = COLLISION_TYPE_ENEMY |
---|
69 | self._physicser = SingleShapePhysicser(space, self._shape) |
---|
70 | self.impulse_factor = 50 |
---|
71 | self.angle = 0 |
---|
72 | |
---|
73 | def _setup_renderer(self): |
---|
74 | self.renderer = render.FacingSelectionRenderer({ |
---|
75 | 'left': render.TimedAnimatedRenderer( |
---|
76 | [self._get_image('alien_A_1.png'), |
---|
77 | self._get_image('alien_A_2.png')], 3), |
---|
78 | 'right': render.TimedAnimatedRenderer( |
---|
79 | [self._get_image('alien_A_1.png', FLIP_H), |
---|
80 | self._get_image('alien_A_2.png', FLIP_H)], 3), |
---|
81 | }) |
---|
82 | |
---|
83 | def get_render_angle(self): |
---|
84 | # No image rotation when rendering, please. |
---|
85 | return 0 |
---|
86 | |
---|
87 | def get_facing_direction(self): |
---|
88 | # Enemies can face left or right. |
---|
89 | if - math.pi / 2 < self.angle <= math.pi / 2: |
---|
90 | return 'right' |
---|
91 | else: |
---|
92 | return 'left' |
---|
93 | |
---|
94 | def _switch_direction(self): |
---|
95 | if self._direction == 'away': |
---|
96 | self._direction = 'towards' |
---|
97 | else: |
---|
98 | self._direction = 'away' |
---|
99 | |
---|
100 | def set_direction(self, dx, dy): |
---|
101 | self.angle = pymunk.Vec2d((dx, dy)).angle |
---|
102 | self._body.apply_impulse( |
---|
103 | (dx * self.impulse_factor, dy * self.impulse_factor)) |
---|
104 | |
---|
105 | def animate(self): |
---|
106 | # Calculate the step every frame |
---|
107 | if self._direction == 'away': |
---|
108 | target = self._end_pos |
---|
109 | else: |
---|
110 | target = self._start_pos |
---|
111 | x_step = 0 |
---|
112 | y_step = 0 |
---|
113 | if (target[0] < self._body.position[0]): |
---|
114 | x_step = max(-1, target[0] - self._body.position[0]) |
---|
115 | elif (target[0] > self._body.position[0]): |
---|
116 | x_step = min(1, target[0] - self._body.position[0]) |
---|
117 | if abs(x_step) < 0.5: |
---|
118 | x_step = 0 |
---|
119 | if (target[1] < self._body.position[1]): |
---|
120 | y_step = max(-1, target[1] - self._body.position[1]) |
---|
121 | elif (target[1] > self._body.position[1]): |
---|
122 | y_step = min(1, target[1] - self._body.position[1]) |
---|
123 | if abs(y_step) < 0.5: |
---|
124 | y_step = 0 |
---|
125 | if abs(x_step) < 1 and abs(y_step) < 1: |
---|
126 | self._switch_direction() |
---|
127 | self.set_direction(x_step, y_step) |
---|
128 | super(PatrollingAlien, self).animate() |
---|
129 | |
---|
130 | def collide_with_protagonist(self, protagonist): |
---|
131 | protagonist.lose_health(15) |
---|
132 | |
---|
133 | @classmethod |
---|
134 | def requires(cls): |
---|
135 | return [("name", "string"), ("position", "coordinates"), |
---|
136 | ("end_position", "coordinates")] |
---|