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'])
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 player.rect.x = posx
99 player.rect.y = posy
100
101 return player
102
103 def make_blockers(self):
104 """
105 Make the blockers for the level.
106 """
107 blockers = []
108
109 for object in self.renderer.tmx_data.getObjects():
110 properties = object.__dict__
111 if properties['name'] == 'blocker':
112 left = properties['x'] * 2
113 top = ((properties['y']) * 2) - 32
114 blocker = pg.Rect(left, top, 32, 32)
115 blockers.append(blocker)
116
117 return blockers
118
119 def make_sprites(self):
120 """
121 Make any sprites for the level as needed.
122 """
123 sprites = pg.sprite.Group()
124
125 for object in self.renderer.tmx_data.getObjects():
126 properties = object.__dict__
127 if properties['name'] == 'sprite':
128 if 'direction' in properties:
129 direction = properties['direction']
130 else:
131 direction = 'down'
132
133 if properties['type'] == 'soldier' and direction == 'left':
134 index = 1
135 else:
136 index = 0
137
138 if 'item' in properties:
139 item = properties['item']
140 else:
141 item = None
142
143 if 'id' in properties:
144 id = properties['id']
145 else:
146 id = None
147
148 if 'battle' in properties:
149 battle = properties['battle']
150 else:
151 battle = None
152
153
154 x = properties['x'] * 2
155 y = ((properties['y']) * 2) - 32
156
157 sprite_dict = {'oldman': person.Person('oldman',
158 x, y, direction),
159 'bluedressgirl': person.Person('femalevillager',
160 x, y, direction,
161 'resting', 1),
162 'femalewarrior': person.Person('femvillager2',
163 x, y, direction,
164 'autoresting'),
165 'devil': person.Person('devil', x, y,
166 'down', 'autoresting'),
167 'oldmanbrother': person.Person('oldmanbrother',
168 x, y, direction),
169 'soldier': person.Person('soldier',
170 x, y, direction,
171 'resting', index),
172 'king': person.Person('king', x, y, direction),
173 'evilwizard': person.Person('evilwizard', x, y, direction),
174 'treasurechest': person.Chest(x, y, id)}
175
176 sprite = sprite_dict[properties['type']]
177 if sprite.name == 'oldman':
178 if self.game_data['old man gift']:
179 sprite.item = self.game_data['old man gift']
180 self.game_data['old man gift'] = {}
181 else:
182 sprite.item = item
183 else:
184 sprite.item = item
185 sprite.battle = battle
186 self.assign_dialogue(sprite, properties)
187 self.check_for_opened_chest(sprite)
188 if sprite.name == 'evilwizard' and self.game_data['crown quest']:
189 pass
190 else:
191 sprites.add(sprite)
192
193 return sprites
194
195 def assign_dialogue(self, sprite, property_dict):
196 """
197 Assign dialogue from object property dictionaries in tmx maps to sprites.
198 """
199 dialogue_list = []
200 for i in range(int(property_dict['dialogue length'])):
201 dialogue_list.append(property_dict['dialogue'+str(i)])
202 sprite.dialogue = dialogue_list
203
204 if sprite.name == 'oldman':
205 if self.game_data['has brother elixir']:
206 if self.game_data['elixir received']:
207 sprite.dialogue = ['My good health is thanks to you.',
208 'I will be forever in your debt.']
209 else:
210 sprite.dialogue = ['Thank you for reaching my brother.',
211 'This ELIXIR will cure my ailment.',
212 'As a reward, I will teach you a magic spell.',
213 'Use it wisely.',
214 'You learned FIRE BLAST.']
215 del self.game_data['player inventory']['ELIXIR']
216 self.game_data['elixir received'] = True
217 dialogue = ['My good health is thanks to you.',
218 'I will be forever in your debt.']
219 self.reset_dialogue = sprite, dialogue
220 elif sprite.name == 'oldmanbrother':
221 if self.game_data['has brother elixir']:
222 if self.game_data['elixir received']:
223 sprite.dialogue = ['I am glad my brother is doing well.',
224 'You have wise and generous spirit.']
225 else:
226 sprite.dialogue = ['Hurry! There is precious little time.']
227
228
229 def check_for_opened_chest(self, sprite):
230 if sprite.name == 'treasurechest':
231 if not self.game_data['treasure{}'.format(sprite.id)]:
232 sprite.dialogue = ['Empty.']
233 sprite.item = None
234 sprite.index = 1
235
236 def make_state_dict(self):
237 """
238 Make a dictionary of states the level can be in.
239 """
240 state_dict = {'normal': self.running_normally,
241 'dialogue': self.handling_dialogue,
242 'menu': self.goto_menu}
243
244 return state_dict
245
246 def make_level_portals(self):
247 """
248 Make the portals to switch state.
249 """
250 portal_group = pg.sprite.Group()
251
252 for object in self.renderer.tmx_data.getObjects():
253 properties = object.__dict__
254 if properties['name'] == 'portal':
255 posx = properties['x'] * 2
256 posy = (properties['y'] * 2) - 32
257 new_state = properties['type']
258 portal_group.add(portal.Portal(posx, posy, new_state))
259
260
261 return portal_group
262
263 def running_normally(self, surface, keys, current_time):
264 """
265 Update level normally.
266 """
267 self.check_for_dialogue()
268 self.player.update(keys, current_time)
269 self.sprites.update(current_time)
270 self.collision_handler.update(keys, current_time)
271 self.check_for_portals()
272 self.check_for_battle()
273 self.dialogue_handler.update(keys, current_time)
274 self.check_for_menu(keys)
275 self.viewport_update()
276 self.draw_level(surface)
277
278 def check_for_portals(self):
279 """
280 Check if the player walks into a door, requiring a level change.
281 """
282 portal = pg.sprite.spritecollideany(self.player, self.portals)
283
284 if portal and self.player.state == 'resting':
285 self.player.location = self.player.get_tile_location()
286 self.next = portal.name
287 self.update_game_data()
288 self.done = True
289
290 def check_for_battle(self):
291 """
292 Check if the flag has been made true, indicating
293 to switch state to a battle.
294 """
295 if self.switch_to_battle and self.allow_battles and not self.done:
296 self.player.location = self.player.get_tile_location()
297 self.update_game_data()
298 self.next = 'battle'
299 self.done = True
300
301 def check_for_menu(self, keys):
302 """
303 Check if player hits enter to go to menu.
304 """
305 if keys[pg.K_RETURN] and self.allow_input:
306 if self.player.state == 'resting':
307 self.state = 'menu'
308 self.allow_input = False
309
310 if not keys[pg.K_RETURN]:
311 self.allow_input = True
312
313
314 def update_game_data(self):
315 """
316 Update the persistant game data dictionary.
317 """
318 self.game_data['last location'] = self.player.location
319 self.game_data['last direction'] = self.player.direction
320 self.game_data['last state'] = self.name
321 self.set_new_start_pos()
322
323
324 def set_new_start_pos(self):
325 """
326 Set new start position based on previous state.
327 """
328 location = copy.deepcopy(self.game_data['last location'])
329 direction = self.game_data['last direction']
330
331 if self.next == 'player menu':
332 pass
333 elif direction == 'up':
334 location[1] += 1
335 elif direction == 'down':
336 location[1] -= 1
337 elif direction == 'left':
338 location[0] += 1
339 elif direction == 'right':
340 location[0] -= 1
341
342
343
344 def handling_dialogue(self, surface, keys, current_time):
345 """
346 Update only dialogue boxes.
347 """
348 self.dialogue_handler.update(keys, current_time)
349 self.draw_level(surface)
350
351
352 def goto_menu(self, surface, keys, *args):
353 """
354 Go to menu screen.
355 """
356 self.menu_screen.update(surface, keys)
357 self.menu_screen.draw(surface)
358
359
360 def check_for_dialogue(self):
361 """
362 Check if the level needs to freeze.
363 """
364 if self.dialogue_handler.textbox:
365 self.state = 'dialogue'
366
367 def update(self, surface, keys, current_time):
368 """
369 Update state.
370 """
371 state_function = self.state_dict[self.state]
372 state_function(surface, keys, current_time)
373
374 def viewport_update(self):
375 """
376 Update viewport so it stays centered on character,
377 unless at edge of map.
378 """
379 self.viewport.center = self.player.rect.center
380 self.viewport.clamp_ip(self.level_rect)
381
382 def draw_level(self, surface):
383 """
384 Blit all images to screen.
385 """
386 self.level_surface.blit(self.map_image, self.viewport, self.viewport)
387 self.level_surface.blit(self.player.image, self.player.rect)
388 self.sprites.draw(self.level_surface)
389
390 surface.blit(self.level_surface, (0, 0), self.viewport)
391 self.dialogue_handler.draw(surface)
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407