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.game_data['delivered crown'] = True
296 self.reset_dialogue = (sprite, thank_you_dialogue)
297 elif self.game_data['delivered crown']:
298 sprite.dialogue = thank_you_dialogue
299
300
301 def check_for_opened_chest(self, sprite):
302 if sprite.name == 'treasurechest':
303 if not self.game_data['treasure{}'.format(sprite.id)]:
304 sprite.dialogue = ['Empty.']
305 sprite.item = None
306 sprite.index = 1
307
308 def make_state_dict(self):
309 """
310 Make a dictionary of states the level can be in.
311 """
312 state_dict = {'normal': self.running_normally,
313 'dialogue': self.handling_dialogue,
314 'menu': self.goto_menu,
315 'transition_in': self.transition_in,
316 'transition_out': self.transition_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.getObjects():
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.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
396 def set_new_start_pos(self):
397 """
398 Set new start position based on previous state.
399 """
400 location = copy.deepcopy(self.game_data['last location'])
401 direction = self.game_data['last direction']
402
403 if self.next == 'player menu':
404 pass
405 elif direction == 'up':
406 location[1] += 1
407 elif direction == 'down':
408 location[1] -= 1
409 elif direction == 'left':
410 location[0] += 1
411 elif direction == 'right':
412 location[0] -= 1
413
414 def handling_dialogue(self, surface, keys, current_time):
415 """
416 Update only dialogue boxes.
417 """
418 self.dialogue_handler.update(keys, current_time)
419 self.draw_level(surface)
420
421 def goto_menu(self, surface, keys, *args):
422 """
423 Go to menu screen.
424 """
425 self.menu_screen.update(surface, keys)
426 self.menu_screen.draw(surface)
427
428 def check_for_dialogue(self):
429 """
430 Check if the level needs to freeze.
431 """
432 if self.dialogue_handler.textbox:
433 self.state = 'dialogue'
434
435 def transition_out(self, surface, *args):
436 """
437 Transition level to new scene.
438 """
439 transition_image = pg.Surface(self.transition_rect.size)
440 transition_image.fill(c.TRANSITION_COLOR)
441 transition_image.set_alpha(self.transition_alpha)
442 self.draw_level(surface)
443 surface.blit(transition_image, self.transition_rect)
444 self.transition_alpha += c.TRANSITION_SPEED
445 if self.transition_alpha >= 255:
446 self.transition_alpha = 255
447 self.done = True
448
449 def transition_in(self, surface, *args):
450 """
451 Transition into level.
452 """
453 self.viewport_update()
454 transition_image = pg.Surface(self.transition_rect.size)
455 transition_image.fill(c.TRANSITION_COLOR)
456 transition_image.set_alpha(self.transition_alpha)
457 self.draw_level(surface)
458 surface.blit(transition_image, self.transition_rect)
459 self.transition_alpha -= c.TRANSITION_SPEED
460 if self.transition_alpha <= 0:
461 self.state = 'normal'
462 self.transition_alpha = 0
463
464 def update(self, surface, keys, current_time):
465 """
466 Update state.
467 """
468 state_function = self.state_dict[self.state]
469 state_function(surface, keys, current_time)
470
471 def viewport_update(self):
472 """
473 Update viewport so it stays centered on character,
474 unless at edge of map.
475 """
476 self.viewport.center = self.player.rect.center
477 self.viewport.clamp_ip(self.level_rect)
478
479 def draw_level(self, surface):
480 """
481 Blit all images to screen.
482 """
483 self.level_surface.blit(self.map_image, self.viewport, self.viewport)
484 self.level_surface.blit(self.player.image, self.player.rect)
485 self.sprites.draw(self.level_surface)
486
487 surface.blit(self.level_surface, (0, 0), self.viewport)
488 self.dialogue_handler.draw(surface)
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504