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