all repos — Legends-RPG @ 3b7c7c72ba6ca35b3f03d001e978c5539f8a0ebe

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