all repos — Legends-RPG @ f0f531c2662a5962b0ee222c6b6e5373ac3ed6aa

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