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