source: nagslang/protagonist.py@ 374:150332d6c1fb

Last change on this file since 374:150332d6c1fb was 374:150332d6c1fb, checked in by Stefano Rivera <stefano@…>, 9 years ago

Move the inventory to world, to slightly reduce overall hackyness

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