source: nagslang/protagonist.py@ 342:11febdb72296

Last change on this file since 342:11febdb72296 was 341:63d0c70a4e15, checked in by Jeremy Thurgood <firxen@…>, 8 years ago

Enemies can hurt things again. (Oops.)

File size: 12.2 KB
Line 
1import pymunk
2import pymunk.pygame_util
3
4from nagslang import render
5from nagslang.constants import (
6 COLLISION_TYPE_PLAYER, ZORDER_MID, WEREWOLF_SOAK_FACTOR,
7 PROTAGONIST_HEALTH_MIN_LEVEL, PROTAGONIST_HEALTH_MAX_LEVEL,
8 NON_GAME_OBJECT_COLLIDERS, BULLET_DAMAGE, CLAW_DAMAGE,
9 CMD_TOGGLE_FORM, CMD_ATTACK, CMD_ACTION)
10from nagslang.events import FireEvent, ClawEvent
11from nagslang.game_object import GameObject, Physicser, make_body
12from nagslang.mutators import FLIP_H
13from nagslang.resources import resources
14from nagslang.events import ScreenChange
15from nagslang.utils import vec_from_angle, vec_with_length
16
17
18class ProtagonistPhysicser(Physicser):
19 def __init__(self, space, form_shapes):
20 self._space = space
21 self._form_shapes = form_shapes
22
23 def switch_form(self, old_form, new_form):
24 self._space.remove(self._form_shapes[old_form])
25 shape = self._form_shapes[new_form]
26 self._space.add(shape)
27 for attr, value in shape.protagonist_body_props.iteritems():
28 setattr(shape.body, attr, value)
29
30 def get_shape(self):
31 return self._form_shapes[self.game_object.form]
32
33
34class ProtagonistFormSelectionRenderer(render.RendererSelectionRenderer):
35 def select_renderer(self):
36 return self.game_object.form
37
38
39def _make_change_sequence(old, new):
40 return (
41 new, new, old, old, old, old,
42 new, new, old, old, old,
43 new, new, old, old,
44 new, new, new, old, old,
45 new, new, new, new, old, old,
46 new)
47
48
49class Protagonist(GameObject):
50 """Representation of our fearless protagonist.
51
52 TODO: Factor out a bunch of this stuff when we need it for other objects.
53 """
54
55 HUMAN_FORM = 'human'
56 WOLF_FORM = 'wolf'
57
58 CHANGING_SEQUENCE = {
59 # The key is the form we're changing *from*.
60 HUMAN_FORM: _make_change_sequence(HUMAN_FORM, WOLF_FORM),
61 WOLF_FORM: _make_change_sequence(WOLF_FORM, HUMAN_FORM),
62 }
63
64 zorder = ZORDER_MID
65
66 def __init__(self, space, world, position):
67 self.form = self.HUMAN_FORM
68 super(Protagonist, self).__init__(
69 self._make_physics(space, position), self._make_renderer())
70 self.world = world
71 self.inventory = {}
72 self.health_level = PROTAGONIST_HEALTH_MAX_LEVEL
73
74 self.angle = 0
75 self.is_moving = False
76 self.changing_sequence = []
77
78 self.go_human()
79
80 def _make_physics(self, space, position):
81 body = make_body(10, pymunk.inf, position, 0.8)
82 body.velocity_limit = 1000
83
84 human = pymunk.Poly(body, [(-15, -30), (15, -30), (15, 30), (-15, 30)])
85 human.elasticity = 1.0
86 human.collision_type = COLLISION_TYPE_PLAYER
87 human.protagonist_body_props = {
88 'mass': 10,
89 'damping': 0.8,
90 }
91
92 wolf = pymunk.Circle(body, 30)
93 wolf.elasticity = 1.0
94 wolf.collision_type = COLLISION_TYPE_PLAYER
95 wolf.protagonist_body_props = {
96 'mass': 100,
97 'damping': 0.9,
98 }
99
100 return ProtagonistPhysicser(space, {
101 self.HUMAN_FORM: human,
102 self.WOLF_FORM: wolf,
103 })
104
105 def _get_image(self, name, *transforms):
106 return resources.get_image('creatures', name, transforms=transforms)
107
108 def change_space(self, new_space):
109 self.physicser.remove_from_space()
110 self.physicser.set_space(new_space)
111 self.physicser.add_to_space()
112
113 def reset(self):
114 self.health_level = PROTAGONIST_HEALTH_MAX_LEVEL
115 self.is_moving = False
116
117 self.go_human()
118
119 def _make_renderer(self):
120 return ProtagonistFormSelectionRenderer({
121 self.HUMAN_FORM: render.FacingSelectionRenderer(
122 {
123 'N': render.MovementAnimatedRenderer(
124 [self._get_image('human_N_1.png'),
125 self._get_image('human_N_2.png')], 3),
126 'S': render.MovementAnimatedRenderer(
127 [self._get_image('human_S_1.png'),
128 self._get_image('human_S_2.png')], 3),
129 'W': render.MovementAnimatedRenderer(
130 [self._get_image('human_W_1.png'),
131 self._get_image('human_W_2.png')], 3),
132 'E': render.MovementAnimatedRenderer(
133 [self._get_image('human_W_1.png', FLIP_H),
134 self._get_image('human_W_2.png', FLIP_H)], 3),
135 'NW': render.MovementAnimatedRenderer(
136 [self._get_image('human_NW_1.png'),
137 self._get_image('human_NW_2.png')], 3),
138 'NE': render.MovementAnimatedRenderer(
139 [self._get_image('human_NW_1.png', FLIP_H),
140 self._get_image('human_NW_2.png', FLIP_H)], 3),
141 'SW': render.MovementAnimatedRenderer(
142 [self._get_image('human_SW_1.png'),
143 self._get_image('human_SW_2.png')], 3),
144 'SE': render.MovementAnimatedRenderer(
145 [self._get_image('human_SW_1.png', FLIP_H),
146 self._get_image('human_SW_2.png', FLIP_H)], 3),
147 }),
148 self.WOLF_FORM: render.FacingSelectionRenderer(
149 {
150 'N': render.MovementAnimatedRenderer(
151 [self._get_image('werewolf_N_1.png'),
152 self._get_image('werewolf_N_2.png')], 3),
153 'S': render.MovementAnimatedRenderer(
154 [self._get_image('werewolf_S_1.png'),
155 self._get_image('werewolf_S_2.png')], 3),
156 'W': render.MovementAnimatedRenderer(
157 [self._get_image('werewolf_W_1.png'),
158 self._get_image('werewolf_W_2.png')], 3),
159 'E': render.MovementAnimatedRenderer(
160 [self._get_image('werewolf_W_1.png', FLIP_H),
161 self._get_image('werewolf_W_2.png', FLIP_H)], 3),
162 'NW': render.MovementAnimatedRenderer(
163 [self._get_image('werewolf_NW_1.png'),
164 self._get_image('werewolf_NW_2.png')], 3),
165 'NE': render.MovementAnimatedRenderer(
166 [self._get_image('werewolf_NW_1.png', FLIP_H),
167 self._get_image('werewolf_NW_2.png', FLIP_H)], 3),
168 'SW': render.MovementAnimatedRenderer(
169 [self._get_image('werewolf_SW_1.png'),
170 self._get_image('werewolf_SW_2.png')], 3),
171 'SE': render.MovementAnimatedRenderer(
172 [self._get_image('werewolf_SW_1.png', FLIP_H),
173 self._get_image('werewolf_SW_2.png', FLIP_H)], 3),
174 }),
175 })
176
177 @classmethod
178 def from_saved_state(cls, saved_state):
179 """Create an instance from the provided serialised state.
180 """
181 obj = cls()
182 # TODO: Update from saved state.
183 return obj
184
185 def handle_keypress(self, key_command):
186 if self.changing_sequence:
187 print "Changing, can't act."
188 return
189 if key_command == CMD_TOGGLE_FORM:
190 self.world.transformations += 1
191 self.toggle_form()
192 if key_command == CMD_ATTACK:
193 self.world.attacks += 1
194 self.attack()
195 if key_command == CMD_ACTION:
196 self.perform_action()
197
198 def get_render_angle(self):
199 # No image rotation when rendering, please.
200 return 0
201
202 def get_facing_direction(self):
203 # Our angle is quantised to 45 degree intervals, so possible values for
204 # x and y in a unit vector are +/-(0, sqrt(2)/2, 1) with some floating
205 # point imprecision. Rounding will normalise these to (-1.0, 0.0, 1.0)
206 # which we can safely turn into integers and use as dict keys.
207 vec = vec_from_angle(self.angle)
208 x = int(round(vec.x))
209 y = int(round(vec.y))
210
211 return {
212 (0, 1): 'N',
213 (0, -1): 'S',
214 (-1, 0): 'W',
215 (1, 0): 'E',
216 (1, 1): 'NE',
217 (1, -1): 'SE',
218 (-1, 1): 'NW',
219 (-1, -1): 'SW',
220 }[(x, y)]
221
222 def go_werewolf(self):
223 self.physicser.switch_form(self.form, self.WOLF_FORM)
224 self.form = self.WOLF_FORM
225 self.impulse_factor = 4000
226
227 def go_human(self):
228 self.physicser.switch_form(self.form, self.HUMAN_FORM)
229 self.form = self.HUMAN_FORM
230 self.impulse_factor = 500
231
232 def set_direction(self, dx, dy):
233 if (dx, dy) == (0, 0) or self.changing_sequence:
234 self.is_moving = False
235 return
236 self.is_moving = True
237 vec = vec_with_length((dx, dy), self.impulse_factor)
238 self.angle = vec.angle
239 self.physicser.apply_impulse(vec)
240
241 def set_position(self, position):
242 self.physicser.position = position
243
244 def copy_state(self, old_protagonist):
245 self.physicser.position = old_protagonist.physicser.position
246 self.physicser.switch_form(self.form, old_protagonist.form)
247 self.impulse_factor = old_protagonist.impulse_factor
248 self.form = old_protagonist.form
249 self.angle = old_protagonist.angle
250 self.inventory = old_protagonist.inventory
251
252 def toggle_form(self):
253 self.changing_sequence.extend(self.CHANGING_SEQUENCE[self.form])
254
255 def _go_to_next_form(self):
256 if self.changing_sequence.pop(0) == self.WOLF_FORM:
257 self.go_werewolf()
258 else:
259 self.go_human()
260
261 def get_current_interactible(self):
262 for shape in self.get_space().shape_query(self.get_shape()):
263 if shape.collision_type in NON_GAME_OBJECT_COLLIDERS:
264 # No game object here.
265 continue
266 interactible = shape.physicser.game_object.interactible
267 if interactible is not None:
268 return interactible
269 return None
270
271 def perform_action(self):
272 """Perform an action on the target.
273 """
274 interactible = self.get_current_interactible()
275 if interactible is None:
276 # Nothing to interact with.
277 return
278 action = interactible.select_action(self)
279 if action is None:
280 # Nothing to do with it.
281 return
282 return action.perform(self)
283
284 def attack(self):
285 """Attempt to hurt something.
286 """
287 if self.in_wolf_form():
288 self.claw()
289 else:
290 self.shoot()
291
292 def shoot(self):
293 if not self.has_item('gun'):
294 return
295 vec = vec_from_angle(self.angle, 1000)
296 FireEvent.post(
297 self.physicser.position, vec, BULLET_DAMAGE, COLLISION_TYPE_PLAYER)
298
299 def claw(self):
300 vec = vec_from_angle(self.angle, 300)
301 ClawEvent.post(self.physicser.position, vec, CLAW_DAMAGE)
302
303 def in_wolf_form(self):
304 return self.form == self.WOLF_FORM
305
306 def in_human_form(self):
307 return self.form == self.HUMAN_FORM
308
309 def has_item(self, item):
310 return item in self.inventory
311
312 def environmental_movement(self, dx, dy):
313 if (dx, dy) == (0, 0):
314 return
315 self.physicser.apply_impulse((dx, dy))
316
317 def get_health_level(self):
318 """Return current health level
319 """
320 return self.health_level
321
322 def die(self):
323 # Handle player death - may be called due to other reasons
324 # than zero health
325 ScreenChange.post('dead')
326
327 def lose_health(self, amount):
328 if self.in_human_form():
329 self.health_level -= amount
330 else:
331 self.health_level -= amount / WEREWOLF_SOAK_FACTOR
332 if self.health_level <= PROTAGONIST_HEALTH_MIN_LEVEL:
333 self.die()
334
335 def gain_health(self, amount):
336 self.health_level += amount
337 if self.health_level > PROTAGONIST_HEALTH_MAX_LEVEL:
338 self.health_level = PROTAGONIST_HEALTH_MAX_LEVEL
339
340 def update(self, dt):
341 if self.changing_sequence:
342 self._go_to_next_form()
343 if int(self.lifetime + dt) > int(self.lifetime):
344 if self.in_wolf_form():
345 self.gain_health(1)
346 super(Protagonist, self).update(dt)
Note: See TracBrowser for help on using the repository browser.