all repos — Legends-RPG @ 771a9169fe092728fcd9519c5a9f76f2fd9cb035

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