all repos — Legends-RPG @ 8d1f715d2aebeefc83f203f833ab8b86d0acec1b

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