all repos — Legends-RPG @ 2ded5c74f1c07582e76cc562f5d3027c49637745

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
184                x = properties['x'] * 2
185                y = ((properties['y']) * 2) - 32
186
187                sprite_dict = {'oldman': person.Person('oldman',
188                                                       x, y, direction),
189                               'bluedressgirl': person.Person('femalevillager',
190                                                              x, y, direction,
191                                                              'resting', 1),
192                               'femalewarrior': person.Person('femvillager2',
193                                                              x, y, direction,
194                                                              'autoresting'),
195                               'devil': person.Person('devil', x, y,
196                                                      'down', 'autoresting'),
197                               'oldmanbrother': person.Person('oldmanbrother',
198                                                              x, y, direction),
199                               'soldier': person.Person('soldier',
200                                                        x, y, direction,
201                                                        'resting', index),
202                               'king': person.Person('king', x, y, direction),
203                               'evilwizard': person.Person('evilwizard', x, y, direction),
204                               'treasurechest': person.Chest(x, y, id)}
205
206                sprite = sprite_dict[properties['type']]
207                if sprite.name == 'oldman':
208                    if self.game_data['old man gift'] and not self.game_data['elixir received']:
209                        sprite.item = self.game_data['old man gift']
210                    else:
211                        sprite.item = item
212                else:
213                    sprite.item = item
214                sprite.battle = battle
215                self.assign_dialogue(sprite, properties)
216                self.check_for_opened_chest(sprite)
217                if sprite.name == 'evilwizard' and self.game_data['crown quest']:
218                    pass
219                else:
220                    sprites.add(sprite)
221
222        return sprites
223
224    def assign_dialogue(self, sprite, property_dict):
225        """
226        Assign dialogue from object property dictionaries in tmx maps to sprites.
227        """
228        dialogue_list = []
229        for i in range(int(property_dict['dialogue length'])):
230            dialogue_list.append(property_dict['dialogue'+str(i)])
231            sprite.dialogue = dialogue_list
232
233        if sprite.name == 'oldman':
234            quest_in_process_dialogue = ['Hurry to the NorthEast Shores!',
235                                         'I do not have much time left.']
236
237            if self.game_data['has brother elixir']:
238                if self.game_data['elixir received']:
239                    sprite.dialogue = ['My good health is thanks to you.',
240                                       'I will be forever in your debt.']
241                else:
242                    sprite.dialogue = ['Thank you for reaching my brother.',
243                                       'This ELIXIR will cure my ailment.',
244                                       'As a reward, I will teach you a magic spell.',
245                                       'Use it wisely.',
246                                       'You learned FIRE BLAST.']
247
248            elif self.game_data['talked to sick brother']:
249                sprite.dialogue = quest_in_process_dialogue
250
251            elif not self.game_data['talked to sick brother']:
252                self.reset_dialogue = (sprite, quest_in_process_dialogue)
253        elif sprite.name == 'oldmanbrother':
254            if self.game_data['has brother elixir']:
255                if self.game_data['elixir received']:
256                    sprite.dialogue = ['I am glad my brother is doing well.',
257                                       'You have a wise and generous spirit.']
258                else:
259                    sprite.dialogue = ['Hurry! There is precious little time.']
260            elif self.game_data['talked to sick brother']:
261                sprite.dialogue = ['My brother is sick?!?',
262                                   'I have not seen him in years.  I had no idea he was not well.',
263                                   'Quick, take this ELIXIR to him immediately.']
264        elif sprite.name == 'king':
265            retrieved_crown_dialogue = ['My crown! You recovered my stolen crown!!!',
266                                        'I can not believe what I see before my eyes.',
267                                        'You are truly a brave and noble warrior.',
268                                        'Henceforth, I name thee Grand Protector of this Town!',
269                                        'Go forth and be recognized.',
270                                        'You are the greatest warrior this world has ever known.']
271            thank_you_dialogue = ['Thank you for retrieving my crown.',
272                                  'My kingdom is forever in your debt.']
273
274            if self.game_data['crown quest'] and not self.game_data['delivered crown']:
275                sprite.dialogue = retrieved_crown_dialogue
276                self.game_data['delivered crown'] = True
277                self.reset_dialogue = (sprite, thank_you_dialogue)
278            elif self.game_data['delivered crown']:
279                sprite.dialogue = thank_you_dialogue
280
281
282    def check_for_opened_chest(self, sprite):
283        if sprite.name == 'treasurechest':
284            if not self.game_data['treasure{}'.format(sprite.id)]:
285                sprite.dialogue = ['Empty.']
286                sprite.item = None
287                sprite.index = 1
288
289    def make_state_dict(self):
290        """
291        Make a dictionary of states the level can be in.
292        """
293        state_dict = {'normal': self.running_normally,
294                      'dialogue': self.handling_dialogue,
295                      'menu': self.goto_menu,
296                      'transition_in': self.transition_in,
297                      'transition_out': self.transition_out}
298
299        return state_dict
300
301    def make_level_portals(self):
302        """
303        Make the portals to switch state.
304        """
305        portal_group = pg.sprite.Group()
306
307        for object in self.renderer.tmx_data.getObjects():
308            properties = object.__dict__
309            if properties['name'] == 'portal':
310                posx = properties['x'] * 2
311                posy = (properties['y'] * 2) - 32
312                new_state = properties['type']
313                portal_group.add(portal.Portal(posx, posy, new_state))
314
315
316        return portal_group
317
318    def running_normally(self, surface, keys, current_time):
319        """
320        Update level normally.
321        """
322        self.check_for_dialogue()
323        self.player.update(keys, current_time)
324        self.sprites.update(current_time)
325        self.collision_handler.update(keys, current_time)
326        self.check_for_portals()
327        self.check_for_battle()
328        self.dialogue_handler.update(keys, current_time)
329        self.check_for_menu(keys)
330        self.viewport_update()
331        self.draw_level(surface)
332
333    def check_for_portals(self):
334        """
335        Check if the player walks into a door, requiring a level change.
336        """
337        portal = pg.sprite.spritecollideany(self.player, self.portals)
338
339        if portal and self.player.state == 'resting':
340            self.player.location = self.player.get_tile_location()
341            self.next = portal.name
342            self.update_game_data()
343            self.state = 'transition_out'
344
345    def check_for_battle(self):
346        """
347        Check if the flag has been made true, indicating
348        to switch state to a battle.
349        """
350        if self.switch_to_battle and self.allow_battles and not self.done:
351            self.player.location = self.player.get_tile_location()
352            self.update_game_data()
353            self.next = 'battle'
354            self.state = 'transition_out'
355
356    def check_for_menu(self, keys):
357        """
358        Check if player hits enter to go to menu.
359        """
360        if keys[pg.K_RETURN] and self.allow_input:
361            if self.player.state == 'resting':
362                self.state = 'menu'
363                self.allow_input = False
364
365        if not keys[pg.K_RETURN]:
366            self.allow_input = True
367
368
369    def update_game_data(self):
370        """
371        Update the persistant game data dictionary.
372        """
373        self.game_data['last location'] = self.player.location
374        self.game_data['last direction'] = self.player.direction
375        self.game_data['last state'] = self.name
376        self.set_new_start_pos()
377
378
379    def set_new_start_pos(self):
380        """
381        Set new start position based on previous state.
382        """
383        location = copy.deepcopy(self.game_data['last location'])
384        direction = self.game_data['last direction']
385
386        if self.next == 'player menu':
387            pass
388        elif direction == 'up':
389            location[1] += 1
390        elif direction == 'down':
391            location[1] -= 1
392        elif direction == 'left':
393            location[0] += 1
394        elif direction == 'right':
395            location[0] -= 1
396
397    def handling_dialogue(self, surface, keys, current_time):
398        """
399        Update only dialogue boxes.
400        """
401        self.dialogue_handler.update(keys, current_time)
402        self.draw_level(surface)
403
404    def goto_menu(self, surface, keys, *args):
405        """
406        Go to menu screen.
407        """
408        self.menu_screen.update(surface, keys)
409        self.menu_screen.draw(surface)
410
411    def check_for_dialogue(self):
412        """
413        Check if the level needs to freeze.
414        """
415        if self.dialogue_handler.textbox:
416            self.state = 'dialogue'
417
418    def transition_out(self, surface, *args):
419        """
420        Transition level to new scene.
421        """
422        transition_image = pg.Surface(self.transition_rect.size)
423        transition_image.fill(c.TRANSITION_COLOR)
424        transition_image.set_alpha(self.transition_alpha)
425        self.draw_level(surface)
426        surface.blit(transition_image, self.transition_rect)
427        self.transition_alpha += c.TRANSITION_SPEED
428        if self.transition_alpha >= 255:
429            self.transition_alpha = 255
430            self.done = True
431
432    def transition_in(self, surface, *args):
433        """
434        Transition into level.
435        """
436        self.viewport_update()
437        transition_image = pg.Surface(self.transition_rect.size)
438        transition_image.fill(c.TRANSITION_COLOR)
439        transition_image.set_alpha(self.transition_alpha)
440        self.draw_level(surface)
441        surface.blit(transition_image, self.transition_rect)
442        self.transition_alpha -= c.TRANSITION_SPEED 
443        if self.transition_alpha <= 0:
444            self.state = 'normal'
445            self.transition_alpha = 0
446
447    def update(self, surface, keys, current_time):
448        """
449        Update state.
450        """
451        state_function = self.state_dict[self.state]
452        state_function(surface, keys, current_time)
453
454    def viewport_update(self):
455        """
456        Update viewport so it stays centered on character,
457        unless at edge of map.
458        """
459        self.viewport.center = self.player.rect.center
460        self.viewport.clamp_ip(self.level_rect)
461
462    def draw_level(self, surface):
463        """
464        Blit all images to screen.
465        """
466        self.level_surface.blit(self.map_image, self.viewport, self.viewport)
467        self.level_surface.blit(self.player.image, self.player.rect)
468        self.sprites.draw(self.level_surface)
469
470        surface.blit(self.level_surface, (0, 0), self.viewport)
471        self.dialogue_handler.draw(surface)
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487