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