source: nagslang/protagonist.py@ 357:d2c7e17299a7

Last change on this file since 357:d2c7e17299a7 was 357:d2c7e17299a7, checked in by Jeremy Thurgood <firxen@…>, 9 years ago

Moonlight tiles force wolf form.

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