all repos — Legends-RPG @ 3f30da44bb69bd438432750872fdd354c4f07ee3

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