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 = 'normal'
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
78 def make_viewport(self, map_image):
79 """
80 Create the viewport to view the level through.
81 """
82 map_rect = map_image.get_rect()
83 return setup.SCREEN.get_rect(bottom=map_rect.bottom)
84
85 def make_level_surface(self, map_image):
86 """
87 Create the surface all images are blitted to.
88 """
89 map_rect = map_image.get_rect()
90 map_width = map_rect.width
91 if self.name in self.cut_off_bottom_map:
92 map_height = map_rect.height - 32
93 else:
94 map_height = map_rect.height
95 size = map_width, map_height
96
97 return pg.Surface(size).convert()
98
99 def make_player(self):
100 """
101 Make the player and sets location.
102 """
103 last_state = self.game_data['last state']
104
105
106 if last_state == 'battle':
107 player = person.Player(self.game_data['last direction'], self.game_data)
108 player.rect.x = self.game_data['last location'][0] * 32
109 player.rect.y = self.game_data['last location'][1] * 32
110
111 else:
112 for object in self.renderer.tmx_data.getObjects():
113 properties = object.__dict__
114 if properties['name'] == 'start point':
115 if last_state == properties['state']:
116 posx = properties['x'] * 2
117 posy = (properties['y'] * 2) - 32
118 player = person.Player(properties['direction'],
119 self.game_data)
120 player.rect.x = posx
121 player.rect.y = posy
122
123 return player
124
125 def make_blockers(self):
126 """
127 Make the blockers for the level.
128 """
129 blockers = []
130
131 for object in self.renderer.tmx_data.getObjects():
132 properties = object.__dict__
133 if properties['name'] == 'blocker':
134 left = properties['x'] * 2
135 top = ((properties['y']) * 2) - 32
136 blocker = pg.Rect(left, top, 32, 32)
137 blockers.append(blocker)
138
139 return blockers
140
141 def make_sprites(self):
142 """
143 Make any sprites for the level as needed.
144 """
145 sprites = pg.sprite.Group()
146
147 for object in self.renderer.tmx_data.getObjects():
148 properties = object.__dict__
149 if properties['name'] == 'sprite':
150 if 'direction' in properties:
151 direction = properties['direction']
152 else:
153 direction = 'down'
154
155 if properties['type'] == 'soldier' and direction == 'left':
156 index = 1
157 else:
158 index = 0
159
160 if 'item' in properties:
161 item = properties['item']
162 else:
163 item = None
164
165 if 'id' in properties:
166 id = properties['id']
167 else:
168 id = None
169
170 if 'battle' in properties:
171 battle = properties['battle']
172 else:
173 battle = None
174
175
176 x = properties['x'] * 2
177 y = ((properties['y']) * 2) - 32
178
179 sprite_dict = {'oldman': person.Person('oldman',
180 x, y, direction),
181 'bluedressgirl': person.Person('femalevillager',
182 x, y, direction,
183 'resting', 1),
184 'femalewarrior': person.Person('femvillager2',
185 x, y, direction,
186 'autoresting'),
187 'devil': person.Person('devil', x, y,
188 'down', 'autoresting'),
189 'oldmanbrother': person.Person('oldmanbrother',
190 x, y, direction),
191 'soldier': person.Person('soldier',
192 x, y, direction,
193 'resting', index),
194 'king': person.Person('king', x, y, direction),
195 'evilwizard': person.Person('evilwizard', x, y, direction),
196 'treasurechest': person.Chest(x, y, id)}
197
198 sprite = sprite_dict[properties['type']]
199 if sprite.name == 'oldman':
200 if self.game_data['old man gift']:
201 sprite.item = self.game_data['old man gift']
202 self.game_data['old man gift'] = {}
203 else:
204 sprite.item = item
205 else:
206 sprite.item = item
207 sprite.battle = battle
208 self.assign_dialogue(sprite, properties)
209 self.check_for_opened_chest(sprite)
210 if sprite.name == 'evilwizard' and self.game_data['crown quest']:
211 pass
212 else:
213 sprites.add(sprite)
214
215 return sprites
216
217 def assign_dialogue(self, sprite, property_dict):
218 """
219 Assign dialogue from object property dictionaries in tmx maps to sprites.
220 """
221 dialogue_list = []
222 for i in range(int(property_dict['dialogue length'])):
223 dialogue_list.append(property_dict['dialogue'+str(i)])
224 sprite.dialogue = dialogue_list
225
226 if sprite.name == 'oldman':
227 quest_in_process_dialogue = ['Hurry to the NorthEast Shores!',
228 'I do not have much time left.']
229
230 if self.game_data['has brother elixir']:
231 if self.game_data['elixir received']:
232 sprite.dialogue = ['My good health is thanks to you.',
233 'I will be forever in your debt.']
234 else:
235 sprite.dialogue = ['Thank you for reaching my brother.',
236 'This ELIXIR will cure my ailment.',
237 'As a reward, I will teach you a magic spell.',
238 'Use it wisely.',
239 'You learned FIRE BLAST.']
240 del self.game_data['player inventory']['ELIXIR']
241 self.game_data['elixir received'] = True
242 dialogue = ['My good health is thanks to you.',
243 'I will be forever in your debt.']
244 self.reset_dialogue = sprite, dialogue
245
246 elif self.game_data['talked to sick brother']:
247 sprite.dialogue = quest_in_process_dialogue
248
249 elif not self.game_data['talked to sick brother']:
250 self.game_data['talked to sick brother'] = True
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
296 return state_dict
297
298 def make_level_portals(self):
299 """
300 Make the portals to switch state.
301 """
302 portal_group = pg.sprite.Group()
303
304 for object in self.renderer.tmx_data.getObjects():
305 properties = object.__dict__
306 if properties['name'] == 'portal':
307 posx = properties['x'] * 2
308 posy = (properties['y'] * 2) - 32
309 new_state = properties['type']
310 portal_group.add(portal.Portal(posx, posy, new_state))
311
312
313 return portal_group
314
315 def running_normally(self, surface, keys, current_time):
316 """
317 Update level normally.
318 """
319 self.check_for_dialogue()
320 self.player.update(keys, current_time)
321 self.sprites.update(current_time)
322 self.collision_handler.update(keys, current_time)
323 self.check_for_portals()
324 self.check_for_battle()
325 self.dialogue_handler.update(keys, current_time)
326 self.check_for_menu(keys)
327 self.viewport_update()
328 self.draw_level(surface)
329
330 def check_for_portals(self):
331 """
332 Check if the player walks into a door, requiring a level change.
333 """
334 portal = pg.sprite.spritecollideany(self.player, self.portals)
335
336 if portal and self.player.state == 'resting':
337 self.player.location = self.player.get_tile_location()
338 self.next = portal.name
339 self.update_game_data()
340 self.done = True
341
342 def check_for_battle(self):
343 """
344 Check if the flag has been made true, indicating
345 to switch state to a battle.
346 """
347 if self.switch_to_battle and self.allow_battles and not self.done:
348 self.player.location = self.player.get_tile_location()
349 self.update_game_data()
350 self.next = 'battle'
351 self.done = True
352
353 def check_for_menu(self, keys):
354 """
355 Check if player hits enter to go to menu.
356 """
357 if keys[pg.K_RETURN] and self.allow_input:
358 if self.player.state == 'resting':
359 self.state = 'menu'
360 self.allow_input = False
361
362 if not keys[pg.K_RETURN]:
363 self.allow_input = True
364
365
366 def update_game_data(self):
367 """
368 Update the persistant game data dictionary.
369 """
370 self.game_data['last location'] = self.player.location
371 self.game_data['last direction'] = self.player.direction
372 self.game_data['last state'] = self.name
373 self.set_new_start_pos()
374
375
376 def set_new_start_pos(self):
377 """
378 Set new start position based on previous state.
379 """
380 location = copy.deepcopy(self.game_data['last location'])
381 direction = self.game_data['last direction']
382
383 if self.next == 'player menu':
384 pass
385 elif direction == 'up':
386 location[1] += 1
387 elif direction == 'down':
388 location[1] -= 1
389 elif direction == 'left':
390 location[0] += 1
391 elif direction == 'right':
392 location[0] -= 1
393
394
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
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
412 def check_for_dialogue(self):
413 """
414 Check if the level needs to freeze.
415 """
416 if self.dialogue_handler.textbox:
417 self.state = 'dialogue'
418
419 def update(self, surface, keys, current_time):
420 """
421 Update state.
422 """
423 state_function = self.state_dict[self.state]
424 state_function(surface, keys, current_time)
425
426 def viewport_update(self):
427 """
428 Update viewport so it stays centered on character,
429 unless at edge of map.
430 """
431 self.viewport.center = self.player.rect.center
432 self.viewport.clamp_ip(self.level_rect)
433
434 def draw_level(self, surface):
435 """
436 Blit all images to screen.
437 """
438 self.level_surface.blit(self.map_image, self.viewport, self.viewport)
439 self.level_surface.blit(self.player.image, self.player.rect)
440 self.sprites.draw(self.level_surface)
441
442 surface.blit(self.level_surface, (0, 0), self.viewport)
443 self.dialogue_handler.draw(surface)
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459