data/states/levels.py (view raw)
1"""
2This is the base class for all level states (i.e. states
3where the player can move around the screen). Levels are
4differentiated by self.name and self.tmx_map.
5This class inherits from the generic state class
6found in the tools.py module.
7"""
8import copy, sys
9import pygame as pg
10from .. import tools, collision
11from .. import constants as c
12from .. components import person, textbox, portal
13from . import player_menu
14from .. import tilerender
15from .. import setup
16
17
18#Python 2/3 compatibility.
19if sys.version_info[0] == 2:
20 range = xrange
21
22
23class LevelState(tools._State):
24 def __init__(self, name, battles=False):
25 super(LevelState, self).__init__()
26 self.name = name
27 self.tmx_map = setup.TMX[name]
28 self.allow_battles = battles
29 self.music_title = None
30 self.previous_music = None
31 self.music = None
32 self.volume = None
33 self.portal = None
34
35 def startup(self, current_time, game_data):
36 """
37 Call when the State object is flipped to.
38 """
39 self.game_data = game_data
40 self.music, self.volume = self.set_music()
41 self.current_time = current_time
42 self.state = 'transition_in'
43 self.reset_dialogue = ()
44 self.switch_to_battle = False
45 self.use_portal = False
46 self.allow_input = False
47 self.cut_off_bottom_map = ['castle', 'town', 'dungeon']
48 self.renderer = tilerender.Renderer(self.tmx_map)
49 self.map_image = self.renderer.make_2x_map()
50
51 self.viewport = self.make_viewport(self.map_image)
52 self.level_surface = self.make_level_surface(self.map_image)
53 self.level_rect = self.level_surface.get_rect()
54 self.portals = self.make_level_portals()
55 self.player = self.make_player()
56 self.blockers = self.make_blockers()
57 self.sprites = self.make_sprites()
58
59 self.collision_handler = collision.CollisionHandler(self.player,
60 self.blockers,
61 self.sprites,
62 self.portals,
63 self)
64 self.dialogue_handler = textbox.TextHandler(self)
65 self.state_dict = self.make_state_dict()
66 self.menu_screen = player_menu.Player_Menu(game_data, self)
67 self.transition_rect = setup.SCREEN.get_rect()
68 self.transition_alpha = 255
69
70 def set_music(self):
71 """
72 Set music based on name.
73 """
74 music_dict = {c.TOWN: ('town_theme', .4),
75 c.OVERWORLD: ('overworld', .4),
76 c.CASTLE: ('kings_theme', .4),
77 c.DUNGEON: ('dungeon_theme', .4),
78 c.DUNGEON2: ('dungeon_theme', .4),
79 c.DUNGEON3: ('dungeon_theme', .4),
80 c.DUNGEON4: ('dungeon_theme', .4),
81 c.DUNGEON5: ('dungeon_theme', .4),
82 c.HOUSE: ('pleasant_creek', .1),
83 c.BROTHER_HOUSE: ('pleasant_creek', .1)}
84
85 if self.game_data['crown quest'] and self.name == c.TOWN:
86 self.music_title = 'kings_theme'
87 return setup.MUSIC['kings_theme'], .4
88 elif self.name in music_dict:
89 music = music_dict[self.name][0]
90 volume = music_dict[self.name][1]
91 self.music_title = music
92 return setup.MUSIC[music], volume
93 else:
94 return None, None
95
96 def make_viewport(self, map_image):
97 """
98 Create the viewport to view the level through.
99 """
100 map_rect = map_image.get_rect()
101 return setup.SCREEN.get_rect(bottom=map_rect.bottom)
102
103 def make_level_surface(self, map_image):
104 """
105 Create the surface all images are blitted to.
106 """
107 map_rect = map_image.get_rect()
108 map_width = map_rect.width
109 if self.name in self.cut_off_bottom_map:
110 map_height = map_rect.height - 32
111 else:
112 map_height = map_rect.height
113 size = map_width, map_height
114
115 return pg.Surface(size).convert()
116
117 def make_player(self):
118 """
119 Make the player and sets location.
120 """
121 last_state = self.previous
122
123 if last_state == 'battle':
124 player = person.Player(self.game_data['last direction'], self.game_data)
125 player.rect.x = self.game_data['last location'][0] * 32
126 player.rect.y = self.game_data['last location'][1] * 32
127
128 else:
129 for object in self.renderer.tmx_data.getObjects():
130 properties = object.__dict__
131 if properties['name'] == 'start point':
132 if last_state == properties['state']:
133 posx = properties['x'] * 2
134 posy = (properties['y'] * 2) - 32
135 player = person.Player(properties['direction'],
136 self.game_data)
137 player.rect.x = posx
138 player.rect.y = posy
139
140 return player
141
142 def make_blockers(self):
143 """
144 Make the blockers for the level.
145 """
146 blockers = []
147
148 for object in self.renderer.tmx_data.getObjects():
149 properties = object.__dict__
150 if properties['name'] == 'blocker':
151 left = properties['x'] * 2
152 top = ((properties['y']) * 2) - 32
153 blocker = pg.Rect(left, top, 32, 32)
154 blockers.append(blocker)
155
156 return blockers
157
158 def make_sprites(self):
159 """
160 Make any sprites for the level as needed.
161 """
162 sprites = pg.sprite.Group()
163
164 for object in self.renderer.tmx_data.getObjects():
165 properties = object.__dict__
166 if properties['name'] == 'sprite':
167 if 'direction' in properties:
168 direction = properties['direction']
169 else:
170 direction = 'down'
171
172 if properties['type'] == 'soldier' and direction == 'left':
173 index = 1
174 else:
175 index = 0
176
177 if 'item' in properties:
178 item = properties['item']
179 else:
180 item = None
181
182 if 'id' in properties:
183 id = properties['id']
184 else:
185 id = None
186
187 if 'battle' in properties:
188 battle = properties['battle']
189 else:
190 battle = None
191
192 if 'state' in properties:
193 sprite_state = properties['state']
194 else:
195 sprite_state = None
196
197
198 x = properties['x'] * 2
199 y = ((properties['y']) * 2) - 32
200
201 sprite_dict = {'oldman': person.Person('oldman',
202 x, y, direction),
203 'bluedressgirl': person.Person('femalevillager',
204 x, y, direction,
205 'resting', 1),
206 'femalewarrior': person.Person('femvillager2',
207 x, y, direction,
208 'autoresting'),
209 'devil': person.Person('devil', x, y,
210 'down', 'autoresting'),
211 'oldmanbrother': person.Person('oldmanbrother',
212 x, y, direction),
213 'soldier': person.Person('soldier',
214 x, y, direction,
215 'resting', index),
216 'king': person.Person('king', x, y, direction),
217 'evilwizard': person.Person('evilwizard', x, y, direction),
218 'treasurechest': person.Chest(x, y, id)}
219
220 sprite = sprite_dict[properties['type']]
221 if sprite_state:
222 sprite.state = sprite_state
223
224 if sprite.name == 'oldman':
225 if self.game_data['old man gift'] and not self.game_data['elixir received']:
226 sprite.item = self.game_data['old man gift']
227 else:
228 sprite.item = item
229 elif sprite.name == 'king':
230 if not self.game_data['talked to king']:
231 sprite.item = self.game_data['king item']
232 else:
233 sprite.item = item
234 sprite.battle = battle
235 self.assign_dialogue(sprite, properties)
236 self.check_for_opened_chest(sprite)
237 if sprite.name == 'evilwizard' and self.game_data['crown quest']:
238 pass
239 else:
240 sprites.add(sprite)
241
242 return sprites
243
244 def assign_dialogue(self, sprite, property_dict):
245 """
246 Assign dialogue from object property dictionaries in tmx maps to sprites.
247 """
248 dialogue_list = []
249 for i in range(int(property_dict['dialogue length'])):
250 dialogue_list.append(property_dict['dialogue'+str(i)])
251 sprite.dialogue = dialogue_list
252
253 if sprite.name == 'oldman':
254 quest_in_process_dialogue = ['Hurry to the NorthEast Shores!',
255 'I do not have much time left.']
256
257 if self.game_data['has brother elixir']:
258 if self.game_data['elixir received']:
259 sprite.dialogue = ['My good health is thanks to you.',
260 'I will be forever in your debt.']
261 else:
262 sprite.dialogue = ['Thank you for reaching my brother.',
263 'This ELIXIR will cure my ailment.',
264 'As a reward, I will teach you a magic spell.',
265 'Use it wisely.',
266 'You learned FIRE BLAST.']
267
268 elif self.game_data['talked to sick brother']:
269 sprite.dialogue = quest_in_process_dialogue
270
271 elif not self.game_data['talked to sick brother']:
272 self.reset_dialogue = (sprite, quest_in_process_dialogue)
273 elif sprite.name == 'oldmanbrother':
274 if self.game_data['has brother elixir']:
275 if self.game_data['elixir received']:
276 sprite.dialogue = ['I am glad my brother is doing well.',
277 'You have a wise and generous spirit.']
278 else:
279 sprite.dialogue = ['Hurry! There is precious little time.']
280 elif self.game_data['talked to sick brother']:
281 sprite.dialogue = ['My brother is sick?!?',
282 'I have not seen him in years. I had no idea he was not well.',
283 'Quick, take this ELIXIR to him immediately.']
284 elif sprite.name == 'king':
285 retrieved_crown_dialogue = ['My crown! You recovered my stolen crown!!!',
286 'I can not believe what I see before my eyes.',
287 'You are truly a brave and noble warrior.',
288 'Henceforth, I name thee Grand Protector of this Town!',
289 'You are the greatest warrior this world has ever known.']
290 thank_you_dialogue = ['Thank you for retrieving my crown.',
291 'My kingdom is forever in your debt.']
292
293 if self.game_data['crown quest'] and not self.game_data['delivered crown']:
294 sprite.dialogue = retrieved_crown_dialogue
295 self.reset_dialogue = (sprite, thank_you_dialogue)
296 elif self.game_data['delivered crown']:
297 sprite.dialogue = thank_you_dialogue
298
299
300 def check_for_opened_chest(self, sprite):
301 if sprite.name == 'treasurechest':
302 if not self.game_data['treasure{}'.format(sprite.id)]:
303 sprite.dialogue = ['Empty.']
304 sprite.item = None
305 sprite.index = 1
306
307 def make_state_dict(self):
308 """
309 Make a dictionary of states the level can be in.
310 """
311 state_dict = {'normal': self.running_normally,
312 'dialogue': self.handling_dialogue,
313 'menu': self.goto_menu,
314 'transition_in': self.transition_in,
315 'transition_out': self.transition_out}
316
317 return state_dict
318
319 def make_level_portals(self):
320 """
321 Make the portals to switch state.
322 """
323 portal_group = pg.sprite.Group()
324
325 for object in self.renderer.tmx_data.getObjects():
326 properties = object.__dict__
327 if properties['name'] == 'portal':
328 posx = properties['x'] * 2
329 posy = (properties['y'] * 2) - 32
330 new_state = properties['type']
331 portal_group.add(portal.Portal(posx, posy, new_state))
332
333
334 return portal_group
335
336 def running_normally(self, surface, keys, current_time):
337 """
338 Update level normally.
339 """
340 self.check_for_dialogue()
341 self.player.update(keys, current_time)
342 self.sprites.update(current_time)
343 self.collision_handler.update(keys, current_time)
344 self.check_for_battle()
345 self.check_for_portals()
346 self.check_for_end_of_game()
347 self.dialogue_handler.update(keys, current_time)
348 self.check_for_menu(keys)
349 self.viewport_update()
350 self.draw_level(surface)
351
352 def check_for_portals(self):
353 """
354 Check if the player walks into a door, requiring a level change.
355 """
356 if self.use_portal and not self.done:
357 self.player.location = self.player.get_tile_location()
358 self.update_game_data()
359 self.next = self.portal
360 self.state = 'transition_out'
361
362 def check_for_battle(self):
363 """
364 Check if the flag has been made true, indicating
365 to switch state to a battle.
366 """
367 if self.switch_to_battle and self.allow_battles and not self.done:
368 self.player.location = self.player.get_tile_location()
369 self.update_game_data()
370 self.next = 'battle'
371 self.state = 'transition_out'
372
373 def check_for_menu(self, keys):
374 """
375 Check if player hits enter to go to menu.
376 """
377 if keys[pg.K_RETURN] and self.allow_input:
378 if self.player.state == 'resting':
379 self.state = 'menu'
380 self.allow_input = False
381
382 if not keys[pg.K_RETURN]:
383 self.allow_input = True
384
385
386 def update_game_data(self):
387 """
388 Update the persistant game data dictionary.
389 """
390 self.game_data['last location'] = self.player.location
391 self.game_data['last direction'] = self.player.direction
392 self.game_data['last state'] = self.name
393 self.set_new_start_pos()
394
395 def check_for_end_of_game(self):
396 """
397 Switch scene to credits if main quest is complete.
398 """
399 if self.game_data['delivered crown']:
400 self.next = c.CREDITS
401 self.state = 'transition_out'
402
403 def set_new_start_pos(self):
404 """
405 Set new start position based on previous state.
406 """
407 location = copy.deepcopy(self.game_data['last location'])
408 direction = self.game_data['last direction']
409
410 if self.next == 'player menu':
411 pass
412 elif direction == 'up':
413 location[1] += 1
414 elif direction == 'down':
415 location[1] -= 1
416 elif direction == 'left':
417 location[0] += 1
418 elif direction == 'right':
419 location[0] -= 1
420
421 def handling_dialogue(self, surface, keys, current_time):
422 """
423 Update only dialogue boxes.
424 """
425 self.dialogue_handler.update(keys, current_time)
426 self.draw_level(surface)
427
428 def goto_menu(self, surface, keys, *args):
429 """
430 Go to menu screen.
431 """
432 self.menu_screen.update(surface, keys)
433 self.menu_screen.draw(surface)
434
435 def check_for_dialogue(self):
436 """
437 Check if the level needs to freeze.
438 """
439 if self.dialogue_handler.textbox:
440 self.state = 'dialogue'
441
442 def transition_out(self, surface, *args):
443 """
444 Transition level to new scene.
445 """
446 transition_image = pg.Surface(self.transition_rect.size)
447 transition_image.fill(c.TRANSITION_COLOR)
448 transition_image.set_alpha(self.transition_alpha)
449 self.draw_level(surface)
450 surface.blit(transition_image, self.transition_rect)
451 self.transition_alpha += c.TRANSITION_SPEED
452 if self.transition_alpha >= 255:
453 self.transition_alpha = 255
454 self.done = True
455
456 def transition_in(self, surface, *args):
457 """
458 Transition into level.
459 """
460 self.viewport_update()
461 transition_image = pg.Surface(self.transition_rect.size)
462 transition_image.fill(c.TRANSITION_COLOR)
463 transition_image.set_alpha(self.transition_alpha)
464 self.draw_level(surface)
465 surface.blit(transition_image, self.transition_rect)
466 self.transition_alpha -= c.TRANSITION_SPEED
467 if self.transition_alpha <= 0:
468 self.state = 'normal'
469 self.transition_alpha = 0
470
471 def update(self, surface, keys, current_time):
472 """
473 Update state.
474 """
475 state_function = self.state_dict[self.state]
476 state_function(surface, keys, current_time)
477
478 def viewport_update(self):
479 """
480 Update viewport so it stays centered on character,
481 unless at edge of map.
482 """
483 self.viewport.center = self.player.rect.center
484 self.viewport.clamp_ip(self.level_rect)
485
486 def draw_level(self, surface):
487 """
488 Blit all images to screen.
489 """
490 self.level_surface.blit(self.map_image, self.viewport, self.viewport)
491 self.level_surface.blit(self.player.image, self.player.rect)
492 self.sprites.draw(self.level_surface)
493
494 surface.blit(self.level_surface, (0, 0), self.viewport)
495 self.dialogue_handler.draw(surface)
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511