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: ('town_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 or self.name == c.CASTLE):
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.objects:
130 properties = object.__dict__
131 if properties['name'] == 'start point':
132 if last_state == properties['properties']['state']:
133 posx = properties['x'] * 2
134 posy = (properties['y'] * 2) - 32
135 player = person.Player(properties['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.objects:
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.objects:
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['properties']['dialogue length'])):
250 dialogue_list.append(property_dict['properties']['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 'slow transition out': self.slow_fade_out}
317
318 return state_dict
319
320 def make_level_portals(self):
321 """
322 Make the portals to switch state.
323 """
324 portal_group = pg.sprite.Group()
325
326 for object in self.renderer.tmx_data.objects:
327 properties = object.__dict__
328 if properties['name'] == 'portal':
329 posx = properties['x'] * 2
330 posy = (properties['y'] * 2) - 32
331 new_state = properties['type']
332 portal_group.add(portal.Portal(posx, posy, new_state))
333
334
335 return portal_group
336
337 def running_normally(self, surface, keys, current_time):
338 """
339 Update level normally.
340 """
341 self.check_for_dialogue()
342 self.player.update(keys, current_time)
343 self.sprites.update(current_time)
344 self.collision_handler.update(keys, current_time)
345 self.check_for_battle()
346 self.check_for_portals()
347 self.check_for_end_of_game()
348 self.dialogue_handler.update(keys, current_time)
349 self.check_for_menu(keys)
350 self.viewport_update()
351 self.draw_level(surface)
352
353 def check_for_portals(self):
354 """
355 Check if the player walks into a door, requiring a level change.
356 """
357 if self.use_portal and not self.done:
358 self.player.location = self.player.get_tile_location()
359 self.update_game_data()
360 self.next = self.portal
361 self.state = 'transition_out'
362
363 def check_for_battle(self):
364 """
365 Check if the flag has been made true, indicating
366 to switch state to a battle.
367 """
368 if self.switch_to_battle and self.allow_battles and not self.done:
369 self.player.location = self.player.get_tile_location()
370 self.update_game_data()
371 self.next = 'battle'
372 self.state = 'transition_out'
373
374 def check_for_menu(self, keys):
375 """
376 Check if player hits enter to go to menu.
377 """
378 if keys[pg.K_RETURN] and self.allow_input:
379 if self.player.state == 'resting':
380 self.state = 'menu'
381 self.allow_input = False
382
383 if not keys[pg.K_RETURN]:
384 self.allow_input = True
385
386
387 def update_game_data(self):
388 """
389 Update the persistant game data dictionary.
390 """
391 self.game_data['last location'] = self.player.location
392 self.game_data['last direction'] = self.player.direction
393 self.game_data['last state'] = self.name
394 self.set_new_start_pos()
395
396 def check_for_end_of_game(self):
397 """
398 Switch scene to credits if main quest is complete.
399 """
400 if self.game_data['delivered crown']:
401 self.next = c.CREDITS
402 self.state = 'slow transition out'
403
404 def set_new_start_pos(self):
405 """
406 Set new start position based on previous state.
407 """
408 location = copy.deepcopy(self.game_data['last location'])
409 direction = self.game_data['last direction']
410
411 if self.next == 'player menu':
412 pass
413 elif direction == 'up':
414 location[1] += 1
415 elif direction == 'down':
416 location[1] -= 1
417 elif direction == 'left':
418 location[0] += 1
419 elif direction == 'right':
420 location[0] -= 1
421
422 def handling_dialogue(self, surface, keys, current_time):
423 """
424 Update only dialogue boxes.
425 """
426 self.dialogue_handler.update(keys, current_time)
427 self.draw_level(surface)
428
429 def goto_menu(self, surface, keys, *args):
430 """
431 Go to menu screen.
432 """
433 self.menu_screen.update(surface, keys)
434 self.menu_screen.draw(surface)
435
436 def check_for_dialogue(self):
437 """
438 Check if the level needs to freeze.
439 """
440 if self.dialogue_handler.textbox:
441 self.state = 'dialogue'
442
443 def transition_out(self, surface, *args):
444 """
445 Transition level to new scene.
446 """
447 transition_image = pg.Surface(self.transition_rect.size)
448 transition_image.fill(c.TRANSITION_COLOR)
449 transition_image.set_alpha(self.transition_alpha)
450 self.draw_level(surface)
451 surface.blit(transition_image, self.transition_rect)
452 self.transition_alpha += c.TRANSITION_SPEED
453 if self.transition_alpha >= 255:
454 self.transition_alpha = 255
455 self.done = True
456
457 def slow_fade_out(self, surface, *args):
458 """
459 Transition level to new scene.
460 """
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 += 2
467 if self.transition_alpha >= 255:
468 self.transition_alpha = 255
469 self.done = True
470
471 def transition_in(self, surface, *args):
472 """
473 Transition into level.
474 """
475 self.viewport_update()
476 transition_image = pg.Surface(self.transition_rect.size)
477 transition_image.fill(c.TRANSITION_COLOR)
478 transition_image.set_alpha(self.transition_alpha)
479 self.draw_level(surface)
480 surface.blit(transition_image, self.transition_rect)
481 self.transition_alpha -= c.TRANSITION_SPEED
482 if self.transition_alpha <= 0:
483 self.state = 'normal'
484 self.transition_alpha = 0
485
486 def update(self, surface, keys, current_time):
487 """
488 Update state.
489 """
490 state_function = self.state_dict[self.state]
491 state_function(surface, keys, current_time)
492
493 def viewport_update(self):
494 """
495 Update viewport so it stays centered on character,
496 unless at edge of map.
497 """
498 self.viewport.center = self.player.rect.center
499 self.viewport.clamp_ip(self.level_rect)
500
501 def draw_level(self, surface):
502 """
503 Blit all images to screen.
504 """
505 self.level_surface.blit(self.map_image, self.viewport, self.viewport)
506 self.level_surface.blit(self.player.image, self.player.rect)
507 self.sprites.draw(self.level_surface)
508
509 surface.blit(self.level_surface, (0, 0), self.viewport)
510 self.dialogue_handler.draw(surface)
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526