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