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