data/components/person.py (view raw)
1import math, random
2import pygame as pg
3from .. import setup
4from .. import constants as c
5
6
7class Person(pg.sprite.Sprite):
8 """Base class for all world characters
9 controlled by the computer"""
10
11 def __init__(self, sheet_key, x, y, direction='down', state='resting', index=0):
12 super(Person, self).__init__()
13 self.name = sheet_key
14 self.get_image = setup.tools.get_image
15 self.spritesheet_dict = self.create_spritesheet_dict(sheet_key)
16 self.animation_dict = self.create_animation_dict()
17 self.index = index
18 self.direction = direction
19 self.image_list = self.animation_dict[self.direction]
20 self.image = self.image_list[self.index]
21 self.rect = self.image.get_rect(left=x, top=y)
22 self.origin_pos = self.rect.topleft
23 self.state_dict = self.create_state_dict()
24 self.vector_dict = self.create_vector_dict()
25 self.x_vel = 0
26 self.y_vel = 0
27 self.timer = 0.0
28 self.move_timer = 0.0
29 self.current_time = 0.0
30 self.state = state
31 self.blockers = self.set_blockers()
32 self.location = self.get_tile_location()
33 self.dialogue = ['Location: ' + str(self.location)]
34 self.default_direction = direction
35 self.item = None
36 self.wander_box = self.make_wander_box()
37 self.observers = []
38
39 def create_spritesheet_dict(self, sheet_key):
40 """Implemented by inheriting classes"""
41 image_list = []
42 image_dict = {}
43 sheet = setup.GFX[sheet_key]
44
45 image_keys = ['facing up 1', 'facing up 2',
46 'facing down 1', 'facing down 2',
47 'facing left 1', 'facing left 2',
48 'facing right 1', 'facing right 2']
49
50 for row in range(2):
51 for column in range(4):
52 image_list.append(
53 self.get_image(column*32, row*32, 32, 32, sheet))
54
55 for key, image in zip(image_keys, image_list):
56 image_dict[key] = image
57
58 return image_dict
59
60 def create_animation_dict(self):
61 """Return a dictionary of image lists for animation"""
62 image_dict = self.spritesheet_dict
63
64 left_list = [image_dict['facing left 1'], image_dict['facing left 2']]
65 right_list = [image_dict['facing right 1'], image_dict['facing right 2']]
66 up_list = [image_dict['facing up 1'], image_dict['facing up 2']]
67 down_list = [image_dict['facing down 1'], image_dict['facing down 2']]
68
69 direction_dict = {'left': left_list,
70 'right': right_list,
71 'up': up_list,
72 'down': down_list}
73
74 return direction_dict
75
76 def create_state_dict(self):
77 """Return a dictionary of all state methods"""
78 state_dict = {'resting': self.resting,
79 'moving': self.moving,
80 'animated resting': self.animated_resting,
81 'autoresting': self.auto_resting,
82 'automoving': self.auto_moving,
83 'battle resting': self.battle_resting,
84 'attack': self.attack,
85 'enemy attack': self.enemy_attack}
86
87 return state_dict
88
89 def create_vector_dict(self):
90 """Return a dictionary of x and y velocities set to
91 direction keys."""
92 vector_dict = {'up': (0, -1),
93 'down': (0, 1),
94 'left': (-1, 0),
95 'right': (1, 0)}
96
97 return vector_dict
98
99 def update(self, current_time, *args):
100 """Implemented by inheriting classes"""
101 self.blockers = self.set_blockers()
102 self.current_time = current_time
103 self.image_list = self.animation_dict[self.direction]
104 state_function = self.state_dict[self.state]
105 state_function()
106 self.location = self.get_tile_location()
107
108
109
110 def set_blockers(self):
111 """Sets blockers to prevent collision with other sprites"""
112 blockers = []
113
114 if self.state == 'resting' or self.state == 'autoresting':
115 blockers.append(pg.Rect(self.rect.x, self.rect.y, 32, 32))
116
117 elif self.state == 'moving' or self.state == 'automoving':
118 if self.rect.x % 32 == 0:
119 tile_float = self.rect.y / float(32)
120 tile1 = (self.rect.x, math.ceil(tile_float)*32)
121 tile2 = (self.rect.x, math.floor(tile_float)*32)
122 tile_rect1 = pg.Rect(tile1[0], tile1[1], 32, 32)
123 tile_rect2 = pg.Rect(tile2[0], tile2[1], 32, 32)
124 blockers.extend([tile_rect1, tile_rect2])
125
126 elif self.rect.y % 32 == 0:
127 tile_float = self.rect.x / float(32)
128 tile1 = (math.ceil(tile_float)*32, self.rect.y)
129 tile2 = (math.floor(tile_float)*32, self.rect.y)
130 tile_rect1 = pg.Rect(tile1[0], tile1[1], 32, 32)
131 tile_rect2 = pg.Rect(tile2[0], tile2[1], 32, 32)
132 blockers.extend([tile_rect1, tile_rect2])
133
134 return blockers
135
136 def get_tile_location(self):
137 """
138 Convert pygame coordinates into tile coordinates.
139 """
140 if self.rect.x == 0:
141 tile_x = 0
142 elif self.rect.x % 32 == 0:
143 tile_x = (self.rect.x / 32)
144 else:
145 tile_x = 0
146
147 if self.rect.y == 0:
148 tile_y = 0
149 elif self.rect.y % 32 == 0:
150 tile_y = (self.rect.y / 32)
151 else:
152 tile_y = 0
153
154 return [tile_x, tile_y]
155
156
157 def make_wander_box(self):
158 """
159 Make a list of rects that surround the initial location
160 of a sprite to limit his/her wandering.
161 """
162 x = int(self.location[0])
163 y = int(self.location[1])
164 box_list = []
165 box_rects = []
166
167 for i in range(x-3, x+4):
168 box_list.append([i, y-3])
169 box_list.append([i, y+3])
170
171 for i in range(y-2, y+3):
172 box_list.append([x-3, i])
173 box_list.append([x+3, i])
174
175 for box in box_list:
176 left = box[0]*32
177 top = box[1]*32
178 box_rects.append(pg.Rect(left, top, 32, 32))
179
180 return box_rects
181
182
183 def resting(self):
184 """
185 When the Person is not moving between tiles.
186 Checks if the player is centered on a tile.
187 """
188 self.image = self.image_list[self.index]
189
190 assert(self.rect.y % 32 == 0), ('Player not centered on tile: '
191 + str(self.rect.y) + " : " + str(self.name))
192 assert(self.rect.x % 32 == 0), ('Player not centered on tile'
193 + str(self.rect.x))
194
195 def moving(self):
196 """
197 Increment index and set self.image for animation.
198 """
199 self.animation()
200 assert(self.rect.x % 32 == 0 or self.rect.y % 32 == 0), \
201 'Not centered on tile'
202
203 def animated_resting(self):
204 self.animation(500)
205
206 def animation(self, freq=100):
207 """
208 Adjust sprite image frame based on timer.
209 """
210 if (self.current_time - self.timer) > freq:
211 if self.index < (len(self.image_list) - 1):
212 self.index += 1
213 else:
214 self.index = 0
215 self.timer = self.current_time
216
217 self.image = self.image_list[self.index]
218
219 def begin_moving(self, direction):
220 """
221 Transition the player into the 'moving' state.
222 """
223 self.direction = direction
224 self.image_list = self.animation_dict[direction]
225 self.timer = self.current_time
226 self.move_timer = self.current_time
227 self.state = 'moving'
228
229 if self.rect.x % 32 == 0:
230 self.y_vel = self.vector_dict[self.direction][1]
231 if self.rect.y % 32 == 0:
232 self.x_vel = self.vector_dict[self.direction][0]
233
234
235 def begin_resting(self):
236 """
237 Transition the player into the 'resting' state.
238 """
239 self.state = 'resting'
240 self.index = 1
241 self.x_vel = self.y_vel = 0
242
243 def begin_auto_moving(self, direction):
244 """
245 Transition sprite to a automatic moving state.
246 """
247 self.direction = direction
248 self.image_list = self.animation_dict[direction]
249 self.state = 'automoving'
250 self.x_vel = self.vector_dict[direction][0]
251 self.y_vel = self.vector_dict[direction][1]
252 self.move_timer = self.current_time
253
254 def begin_auto_resting(self):
255 """
256 Transition sprite to an automatic resting state.
257 """
258 self.state = 'autoresting'
259 self.index = 1
260 self.x_vel = self.y_vel = 0
261 self.move_timer = self.current_time
262
263
264 def auto_resting(self):
265 """
266 Determine when to move a sprite from resting to moving in a random
267 direction.
268 """
269 #self.image = self.image_list[self.index]
270 self.image_list = self.animation_dict[self.direction]
271 self.image = self.image_list[self.index]
272
273 assert(self.rect.y % 32 == 0), ('Player not centered on tile: '
274 + str(self.rect.y))
275 assert(self.rect.x % 32 == 0), ('Player not centered on tile'
276 + str(self.rect.x))
277
278 if (self.current_time - self.move_timer) > 2000:
279 direction_list = ['up', 'down', 'left', 'right']
280 random.shuffle(direction_list)
281 direction = direction_list[0]
282 self.begin_auto_moving(direction)
283 self.move_timer = self.current_time
284
285 def battle_resting(self):
286 """
287 Player stays still during battle state unless he attacks.
288 """
289 pass
290
291 def enter_attack_state(self, enemy):
292 """
293 Set values for attack state.
294 """
295 self.attacked_enemy = enemy
296 self.x_vel = -5
297 self.state = 'attack'
298
299 def attack(self):
300 """
301 Player does an attack animation.
302 """
303 SLOW_BACK = 1
304 FAST_FORWARD = -5
305 FAST_BACK = 5
306
307 self.rect.x += self.x_vel
308
309 if self.x_vel == SLOW_BACK:
310 if self.rect.x >= self.origin_pos[0] + 20:
311 self.x_vel = FAST_FORWARD
312 elif self.x_vel == FAST_FORWARD:
313 if self.rect.topleft >= self.origin_pos:
314 self.image = self.spritesheet_dict['facing left 1']
315 self.image = pg.transform.scale2x(self.image)
316 elif self.rect.x <= self.origin_pos[0] - 110:
317 self.x_vel = FAST_BACK
318 self.notify('attack animation')
319 else:
320 if self.rect.x >= self.origin_pos[0]:
321 self.rect.x = self.origin_pos[0]
322 self.x_vel = 0
323 self.state = 'battle resting'
324 self.image = self.spritesheet_dict['facing left 2']
325 self.image = pg.transform.scale2x(self.image)
326 self.notify(c.PLAYER_FINISHED_ATTACK)
327
328 def enter_enemy_attack_state(self):
329 """
330 Set values for enemy attack state.
331 """
332 self.x_vel = -5
333 self.state = 'enemy attack'
334 self.origin_pos = self.rect.topleft
335 self.move_counter = 0
336
337 def enemy_attack(self):
338 """
339 Enemy does an attack animation.
340 """
341 FAST_LEFT = -5
342 FAST_RIGHT = 5
343 STARTX = self.origin_pos[0]
344
345 self.rect.x += self.x_vel
346
347 if self.move_counter == 3:
348 self.x_vel = 0
349 self.state = 'battle resting'
350 self.rect.x = STARTX
351 self.notify(c.SWITCH_ENEMY)
352
353 elif self.x_vel == FAST_LEFT:
354 if self.rect.x <= (STARTX - 15):
355 self.x_vel = FAST_RIGHT
356 elif self.x_vel == FAST_RIGHT:
357 if self.rect.x >= (STARTX + 15):
358 self.move_counter += 1
359 self.x_vel = FAST_LEFT
360
361 def auto_moving(self):
362 """
363 Animate sprite and check to stop.
364 """
365 self.animation()
366
367 assert(self.rect.x % 32 == 0 or self.rect.y % 32 == 0), \
368 'Not centered on tile'
369
370 def notify(self, event):
371 """
372 Notify all observers of events.
373 """
374 for observer in self.observers:
375 observer.on_notify(event)
376
377
378class Player(Person):
379 """
380 User controlled character.
381 """
382
383 def __init__(self, direction, x=0, y=0, state='resting', index=0):
384 super(Player, self).__init__('player', x, y, direction, state, index)
385
386 def create_vector_dict(self):
387 """Return a dictionary of x and y velocities set to
388 direction keys."""
389 vector_dict = {'up': (0, -2),
390 'down': (0, 2),
391 'left': (-2, 0),
392 'right': (2, 0)}
393
394 return vector_dict
395
396 def update(self, keys, current_time):
397 """Updates player behavior"""
398 self.blockers = self.set_blockers()
399 self.keys = keys
400 self.current_time = current_time
401 self.check_for_input()
402 state_function = self.state_dict[self.state]
403 state_function()
404 self.location = self.get_tile_location()
405
406 def check_for_input(self):
407 """Checks for player input"""
408 if self.state == 'resting':
409 if self.keys[pg.K_UP]:
410 self.begin_moving('up')
411 elif self.keys[pg.K_DOWN]:
412 self.begin_moving('down')
413 elif self.keys[pg.K_LEFT]:
414 self.begin_moving('left')
415 elif self.keys[pg.K_RIGHT]:
416 self.begin_moving('right')
417
418
419
420
421class Well(pg.sprite.Sprite):
422 """Talking well"""
423 def __init__(self, x, y):
424 super(Well, self).__init__()
425 self.image = pg.Surface((32, 32))
426 self.image.set_colorkey((0,0,0))
427 self.rect = self.image.get_rect(left=x, top=y)
428 self.location = self.get_location()
429 self.dialogue = ["I'm a well!"]
430 self.blockers = [self.rect]
431 self.x_vel = self.y_vel = 0
432 self.state = 'resting'
433 self.direction = 'down'
434 self.default_direction = self.direction
435 self.item = None
436 self.wander_box = []
437
438 def get_location(self):
439 """Get tile location"""
440 x = self.rect.x / 32
441 y = self.rect.y / 32
442
443 return [x, y]
444
445 def begin_auto_resting(self):
446 """Placeholder"""
447 pass
448
449