all repos — Legends-RPG @ c0a1a80585351c8001637d8bf163dd13b033aaa7

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: ('kings_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:
 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
317        return state_dict
318
319    def make_level_portals(self):
320        """
321        Make the portals to switch state.
322        """
323        portal_group = pg.sprite.Group()
324
325        for object in self.renderer.tmx_data.getObjects():
326            properties = object.__dict__
327            if properties['name'] == 'portal':
328                posx = properties['x'] * 2
329                posy = (properties['y'] * 2) - 32
330                new_state = properties['type']
331                portal_group.add(portal.Portal(posx, posy, new_state))
332
333
334        return portal_group
335
336    def running_normally(self, surface, keys, current_time):
337        """
338        Update level normally.
339        """
340        self.check_for_dialogue()
341        self.player.update(keys, current_time)
342        self.sprites.update(current_time)
343        self.collision_handler.update(keys, current_time)
344        self.check_for_battle()
345        self.check_for_portals()
346        self.check_for_end_of_game()
347        self.dialogue_handler.update(keys, current_time)
348        self.check_for_menu(keys)
349        self.viewport_update()
350        self.draw_level(surface)
351
352    def check_for_portals(self):
353        """
354        Check if the player walks into a door, requiring a level change.
355        """
356        if self.use_portal and not self.done:
357            self.player.location = self.player.get_tile_location()
358            self.update_game_data()
359            self.next = self.portal
360            self.state = 'transition_out'
361
362    def check_for_battle(self):
363        """
364        Check if the flag has been made true, indicating
365        to switch state to a battle.
366        """
367        if self.switch_to_battle and self.allow_battles and not self.done:
368            self.player.location = self.player.get_tile_location()
369            self.update_game_data()
370            self.next = 'battle'
371            self.state = 'transition_out'
372
373    def check_for_menu(self, keys):
374        """
375        Check if player hits enter to go to menu.
376        """
377        if keys[pg.K_RETURN] and self.allow_input:
378            if self.player.state == 'resting':
379                self.state = 'menu'
380                self.allow_input = False
381
382        if not keys[pg.K_RETURN]:
383            self.allow_input = True
384
385
386    def update_game_data(self):
387        """
388        Update the persistant game data dictionary.
389        """
390        self.game_data['last location'] = self.player.location
391        self.game_data['last direction'] = self.player.direction
392        self.game_data['last state'] = self.name
393        self.set_new_start_pos()
394
395    def check_for_end_of_game(self):
396        """
397        Switch scene to credits if main quest is complete.
398        """
399        if self.game_data['delivered crown']:
400            self.next = c.CREDITS
401            self.state = 'transition_out'
402
403    def set_new_start_pos(self):
404        """
405        Set new start position based on previous state.
406        """
407        location = copy.deepcopy(self.game_data['last location'])
408        direction = self.game_data['last direction']
409
410        if self.next == 'player menu':
411            pass
412        elif direction == 'up':
413            location[1] += 1
414        elif direction == 'down':
415            location[1] -= 1
416        elif direction == 'left':
417            location[0] += 1
418        elif direction == 'right':
419            location[0] -= 1
420
421    def handling_dialogue(self, surface, keys, current_time):
422        """
423        Update only dialogue boxes.
424        """
425        self.dialogue_handler.update(keys, current_time)
426        self.draw_level(surface)
427
428    def goto_menu(self, surface, keys, *args):
429        """
430        Go to menu screen.
431        """
432        self.menu_screen.update(surface, keys)
433        self.menu_screen.draw(surface)
434
435    def check_for_dialogue(self):
436        """
437        Check if the level needs to freeze.
438        """
439        if self.dialogue_handler.textbox:
440            self.state = 'dialogue'
441
442    def transition_out(self, surface, *args):
443        """
444        Transition level to new scene.
445        """
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 >= 255:
453            self.transition_alpha = 255
454            self.done = True
455
456    def transition_in(self, surface, *args):
457        """
458        Transition into level.
459        """
460        self.viewport_update()
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 -= c.TRANSITION_SPEED 
467        if self.transition_alpha <= 0:
468            self.state = 'normal'
469            self.transition_alpha = 0
470
471    def update(self, surface, keys, current_time):
472        """
473        Update state.
474        """
475        state_function = self.state_dict[self.state]
476        state_function(surface, keys, current_time)
477
478    def viewport_update(self):
479        """
480        Update viewport so it stays centered on character,
481        unless at edge of map.
482        """
483        self.viewport.center = self.player.rect.center
484        self.viewport.clamp_ip(self.level_rect)
485
486    def draw_level(self, surface):
487        """
488        Blit all images to screen.
489        """
490        self.level_surface.blit(self.map_image, self.viewport, self.viewport)
491        self.level_surface.blit(self.player.image, self.player.rect)
492        self.sprites.draw(self.level_surface)
493
494        surface.blit(self.level_surface, (0, 0), self.viewport)
495        self.dialogue_handler.draw(surface)
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511