all repos — Legends-RPG @ c666912709c1be04f7a58a227cd1f51be896ddb9

A fantasy mini-RPG built with Python and Pygame.

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