source: nagslang/protagonist.py@ 280:7bb6296024c4

Last change on this file since 280:7bb6296024c4 was 277:56e42c00da25, checked in by Neil Muller <drnlmuller@…>, 8 years ago

Protagonist and enemies should see the world

File size: 9.5 KB
Line 
1import pymunk
2import pymunk.pygame_util
3from pymunk.vec2d import Vec2d
4
5from nagslang import render
6from nagslang.constants import COLLISION_TYPE_PLAYER, ZORDER_MID, \
7 WEREWOLF_SOAK_FACTOR, PROTAGONIST_HEALTH_MIN_LEVEL, \
8 PROTAGONIST_HEALTH_MAX_LEVEL
9from nagslang.events import FireEvent
10from nagslang.game_object import GameObject, Physicser, make_body
11from nagslang.mutators import FLIP_H
12from nagslang.resources import resources
13
14
15class ProtagonistPhysicser(Physicser):
16 def __init__(self, space, form_shapes):
17 self._space = space
18 self._form_shapes = form_shapes
19
20 def switch_form(self, old_form, new_form):
21 self._space.remove(self._form_shapes[old_form])
22 shape = self._form_shapes[new_form]
23 self._space.add(shape)
24 for attr, value in shape.protagonist_body_props.iteritems():
25 setattr(shape.body, attr, value)
26
27 def get_shape(self):
28 return self._form_shapes[self.game_object.form]
29
30
31class ProtagonistFormSelectionRenderer(render.RendererSelectionRenderer):
32 def select_renderer(self):
33 return self.game_object.form
34
35
36class Protagonist(GameObject):
37 """Representation of our fearless protagonist.
38
39 TODO: Factor out a bunch of this stuff when we need it for other objects.
40 """
41
42 HUMAN_FORM = 'human'
43 WOLF_FORM = 'wolf'
44
45 def __init__(self, space, world, position):
46 physicser = self._make_physics(space, position)
47 renderer = self._make_renderer()
48 self.inventory = {}
49 self.form = self.HUMAN_FORM
50 self.angle = 0
51 self.is_moving = False
52 self.world = world
53
54 super(Protagonist, self).__init__(physicser, renderer)
55 self.zorder = ZORDER_MID
56
57 self.health_level = 100
58
59 self.go_human()
60
61 def _make_physics(self, space, position):
62 body = make_body(10, pymunk.inf, position, 0.8)
63 body.velocity_limit = 1000
64
65 human = pymunk.Poly(body, [(-15, -30), (15, -30), (15, 30), (-15, 30)])
66 human.elasticity = 1.0
67 human.collision_type = COLLISION_TYPE_PLAYER
68 human.protagonist_body_props = {
69 'mass': 10,
70 'damping': 0.8,
71 }
72
73 wolf = pymunk.Circle(body, 30)
74 wolf.elasticity = 1.0
75 wolf.collision_type = COLLISION_TYPE_PLAYER
76 wolf.protagonist_body_props = {
77 'mass': 100,
78 'damping': 0.9,
79 }
80
81 return ProtagonistPhysicser(space, {
82 self.HUMAN_FORM: human,
83 self.WOLF_FORM: wolf,
84 })
85
86 def _get_image(self, name, *transforms):
87 return resources.get_image('creatures', name, transforms=transforms)
88
89 def change_space(self, new_space):
90 self.physicser.remove_from_space()
91 self.physicser.set_space(new_space)
92 self.physicser.add_to_space()
93
94 def _make_renderer(self):
95 return ProtagonistFormSelectionRenderer({
96 self.HUMAN_FORM: render.FacingSelectionRenderer(
97 {
98 'N': render.MovementAnimatedRenderer(
99 [self._get_image('human_N_1.png'),
100 self._get_image('human_N_2.png')], 3),
101 'S': render.MovementAnimatedRenderer(
102 [self._get_image('human_S_1.png'),
103 self._get_image('human_S_2.png')], 3),
104 'W': render.MovementAnimatedRenderer(
105 [self._get_image('human_W_1.png'),
106 self._get_image('human_W_2.png')], 3),
107 'E': render.MovementAnimatedRenderer(
108 [self._get_image('human_W_1.png', FLIP_H),
109 self._get_image('human_W_2.png', FLIP_H)], 3),
110 'NW': render.MovementAnimatedRenderer(
111 [self._get_image('human_NW_1.png'),
112 self._get_image('human_NW_2.png')], 3),
113 'NE': render.MovementAnimatedRenderer(
114 [self._get_image('human_NW_1.png', FLIP_H),
115 self._get_image('human_NW_2.png', FLIP_H)], 3),
116 'SW': render.MovementAnimatedRenderer(
117 [self._get_image('human_SW_1.png'),
118 self._get_image('human_SW_2.png')], 3),
119 'SE': render.MovementAnimatedRenderer(
120 [self._get_image('human_SW_1.png', FLIP_H),
121 self._get_image('human_SW_2.png', FLIP_H)], 3),
122 }),
123 self.WOLF_FORM: render.FacingSelectionRenderer(
124 {
125 'N': render.MovementAnimatedRenderer(
126 [self._get_image('werewolf_N_1.png'),
127 self._get_image('werewolf_N_2.png')], 3),
128 'S': render.MovementAnimatedRenderer(
129 [self._get_image('werewolf_S_1.png'),
130 self._get_image('werewolf_S_2.png')], 3),
131 'W': render.MovementAnimatedRenderer(
132 [self._get_image('werewolf_W_1.png'),
133 self._get_image('werewolf_W_2.png')], 3),
134 'E': render.MovementAnimatedRenderer(
135 [self._get_image('werewolf_W_1.png', FLIP_H),
136 self._get_image('werewolf_W_2.png', FLIP_H)], 3),
137 'NW': render.MovementAnimatedRenderer(
138 [self._get_image('werewolf_NW_1.png'),
139 self._get_image('werewolf_NW_2.png')], 3),
140 'NE': render.MovementAnimatedRenderer(
141 [self._get_image('werewolf_NW_1.png', FLIP_H),
142 self._get_image('werewolf_NW_2.png', FLIP_H)], 3),
143 'SW': render.MovementAnimatedRenderer(
144 [self._get_image('werewolf_SW_1.png'),
145 self._get_image('werewolf_SW_2.png')], 3),
146 'SE': render.MovementAnimatedRenderer(
147 [self._get_image('werewolf_SW_1.png', FLIP_H),
148 self._get_image('werewolf_SW_2.png', FLIP_H)], 3),
149 }),
150 })
151
152 @classmethod
153 def from_saved_state(cls, saved_state):
154 """Create an instance from the provided serialised state.
155 """
156 obj = cls()
157 # TODO: Update from saved state.
158 return obj
159
160 def get_render_angle(self):
161 # No image rotation when rendering, please.
162 return 0
163
164 def get_facing_direction(self):
165 # It's easier to work with a vector than an angle here.
166 vec = Vec2d.unit()
167 vec.angle = self.angle
168 # We probably don't have exactly -1, 0, or 1 here.
169 x = int(round(vec.x))
170 y = int(round(vec.y))
171
172 return {
173 (0, 1): 'N',
174 (0, -1): 'S',
175 (-1, 0): 'W',
176 (1, 0): 'E',
177 (1, 1): 'NE',
178 (1, -1): 'SE',
179 (-1, 1): 'NW',
180 (-1, -1): 'SW',
181 }[(x, y)]
182
183 def go_werewolf(self):
184 self.physicser.switch_form(self.form, self.WOLF_FORM)
185 self.form = self.WOLF_FORM
186 self.impulse_factor = 4000
187
188 def go_human(self):
189 self.physicser.switch_form(self.form, self.HUMAN_FORM)
190 self.form = self.HUMAN_FORM
191 self.impulse_factor = 500
192
193 def set_direction(self, dx, dy):
194 if (dx, dy) == (0, 0):
195 self.is_moving = False
196 return
197 self.is_moving = True
198 self.angle = pymunk.Vec2d((dx, dy)).angle
199 self.physicser.apply_impulse(
200 (dx * self.impulse_factor, dy * self.impulse_factor))
201
202 def set_position(self, position):
203 self.physicser.position = position
204
205 def copy_state(self, old_protagonist):
206 self.physicser.position = old_protagonist.physicser.position
207 self.physicser.switch_form(self.form, old_protagonist.form)
208 self.impulse_factor = old_protagonist.impulse_factor
209 self.form = old_protagonist.form
210 self.angle = old_protagonist.angle
211 self.inventory = old_protagonist.inventory
212
213 def toggle_form(self):
214 if self.form == self.WOLF_FORM:
215 self.go_human()
216 else:
217 self.go_werewolf()
218
219 def act_on(self, target):
220 """Perform an action on the target.
221 """
222 # TODO: Decide how best to do this.
223 pass
224
225 def attack(self):
226 """Attempt to hurt something.
227 """
228 if self.in_wolf_form():
229 pass
230 else:
231 vec = Vec2d.unit()
232 vec.angle = self.angle
233 vec.length = 100
234 FireEvent.post(self.physicser.position, vec)
235 print "Biff", self.physicser.position, vec
236
237 def in_wolf_form(self):
238 return self.form == self.WOLF_FORM
239
240 def in_human_form(self):
241 return self.form == self.HUMAN_FORM
242
243 def has_item(self, item):
244 return item in self.inventory
245
246 def environmental_movement(self, dx, dy):
247 if (dx, dy) == (0, 0):
248 return
249 self.physicser.apply_impulse((dx, dy))
250
251 def get_health_level(self):
252 """Return current health level
253 """
254 return self.health_level
255
256 def lose_health(self, amount):
257 if self.in_human_form():
258 self.health_level -= amount
259 else:
260 self.health_level -= amount / WEREWOLF_SOAK_FACTOR
261 if self.health_level < PROTAGONIST_HEALTH_MIN_LEVEL:
262 self.health_level = PROTAGONIST_HEALTH_MIN_LEVEL
263
264 def gain_health(self, amount):
265 self.health_level += amount
266 if self.health_level > PROTAGONIST_HEALTH_MAX_LEVEL:
267 self.health_level = PROTAGONIST_HEALTH_MAX_LEVEL
Note: See TracBrowser for help on using the repository browser.