all repos — Legends-RPG @ 7606c0c7c7335bda8b7801f1f9498f3f965723fd

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.game_data['delivered crown'] = True
296                self.reset_dialogue = (sprite, thank_you_dialogue)
297            elif self.game_data['delivered crown']:
298                sprite.dialogue = thank_you_dialogue
299
300
301    def check_for_opened_chest(self, sprite):
302        if sprite.name == 'treasurechest':
303            if not self.game_data['treasure{}'.format(sprite.id)]:
304                sprite.dialogue = ['Empty.']
305                sprite.item = None
306                sprite.index = 1
307
308    def make_state_dict(self):
309        """
310        Make a dictionary of states the level can be in.
311        """
312        state_dict = {'normal': self.running_normally,
313                      'dialogue': self.handling_dialogue,
314                      'menu': self.goto_menu,
315                      'transition_in': self.transition_in,
316                      'transition_out': self.transition_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.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
396    def set_new_start_pos(self):
397        """
398        Set new start position based on previous state.
399        """
400        location = copy.deepcopy(self.game_data['last location'])
401        direction = self.game_data['last direction']
402
403        if self.next == 'player menu':
404            pass
405        elif direction == 'up':
406            location[1] += 1
407        elif direction == 'down':
408            location[1] -= 1
409        elif direction == 'left':
410            location[0] += 1
411        elif direction == 'right':
412            location[0] -= 1
413
414    def handling_dialogue(self, surface, keys, current_time):
415        """
416        Update only dialogue boxes.
417        """
418        self.dialogue_handler.update(keys, current_time)
419        self.draw_level(surface)
420
421    def goto_menu(self, surface, keys, *args):
422        """
423        Go to menu screen.
424        """
425        self.menu_screen.update(surface, keys)
426        self.menu_screen.draw(surface)
427
428    def check_for_dialogue(self):
429        """
430        Check if the level needs to freeze.
431        """
432        if self.dialogue_handler.textbox:
433            self.state = 'dialogue'
434
435    def transition_out(self, surface, *args):
436        """
437        Transition level to new scene.
438        """
439        transition_image = pg.Surface(self.transition_rect.size)
440        transition_image.fill(c.TRANSITION_COLOR)
441        transition_image.set_alpha(self.transition_alpha)
442        self.draw_level(surface)
443        surface.blit(transition_image, self.transition_rect)
444        self.transition_alpha += c.TRANSITION_SPEED
445        if self.transition_alpha >= 255:
446            self.transition_alpha = 255
447            self.done = True
448
449    def transition_in(self, surface, *args):
450        """
451        Transition into level.
452        """
453        self.viewport_update()
454        transition_image = pg.Surface(self.transition_rect.size)
455        transition_image.fill(c.TRANSITION_COLOR)
456        transition_image.set_alpha(self.transition_alpha)
457        self.draw_level(surface)
458        surface.blit(transition_image, self.transition_rect)
459        self.transition_alpha -= c.TRANSITION_SPEED 
460        if self.transition_alpha <= 0:
461            self.state = 'normal'
462            self.transition_alpha = 0
463
464    def update(self, surface, keys, current_time):
465        """
466        Update state.
467        """
468        state_function = self.state_dict[self.state]
469        state_function(surface, keys, current_time)
470
471    def viewport_update(self):
472        """
473        Update viewport so it stays centered on character,
474        unless at edge of map.
475        """
476        self.viewport.center = self.player.rect.center
477        self.viewport.clamp_ip(self.level_rect)
478
479    def draw_level(self, surface):
480        """
481        Blit all images to screen.
482        """
483        self.level_surface.blit(self.map_image, self.viewport, self.viewport)
484        self.level_surface.blit(self.player.image, self.player.rect)
485        self.sprites.draw(self.level_surface)
486
487        surface.blit(self.level_surface, (0, 0), self.viewport)
488        self.dialogue_handler.draw(surface)
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504