all repos — Legends-RPG @ 7ffb96f9e29029896fd0d90de035beb42211b4ce

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"""
  8
  9import copy
 10import pygame as pg
 11from .. import tools, collision
 12from .. import constants as c
 13from .. components import person, textbox, portal
 14from . import player_menu
 15from .. import tilerender
 16from .. import setup
 17
 18
 19
 20class LevelState(tools._State):
 21    def __init__(self, name, battles=False):
 22        super(LevelState, self).__init__()
 23        self.name = name
 24        self.tmx_map = setup.TMX[name]
 25        self.allow_battles = battles
 26        self.music = self.set_music()
 27
 28    def set_music(self):
 29        """
 30        Set music based on name.
 31        """
 32        music_dict = {c.TOWN: 'town_theme',
 33                      c.OVERWORLD: 'overworld',
 34                      c.CASTLE: 'kings_theme',
 35                      c.DUNGEON:'dungeon_theme',
 36                      c.DUNGEON2: 'dungeon_theme',
 37                      c.DUNGEON3: 'dungeon_theme',
 38                      c.DUNGEON4: 'dungeon_theme',
 39                      c.DUNGEON5: 'dungeon_theme'}
 40
 41        if self.name in music_dict:
 42            music = music_dict[self.name]
 43            return setup.MUSIC[music]
 44        else:
 45            return None
 46
 47    def startup(self, current_time, game_data):
 48        """
 49        Call when the State object is flipped to.
 50        """
 51        self.game_data = game_data
 52        self.current_time = current_time
 53        self.state = 'normal'
 54        self.reset_dialogue = ()
 55        self.switch_to_battle = False
 56        self.allow_input = False
 57        self.cut_off_bottom_map = ['castle', 'town', 'dungeon']
 58        self.renderer = tilerender.Renderer(self.tmx_map)
 59        self.map_image = self.renderer.make_2x_map()
 60
 61        self.viewport = self.make_viewport(self.map_image)
 62        self.level_surface = self.make_level_surface(self.map_image)
 63        self.level_rect = self.level_surface.get_rect()
 64        self.portals = None
 65        self.player = self.make_player()
 66        self.blockers = self.make_blockers()
 67        self.sprites = self.make_sprites()
 68
 69        self.collision_handler = collision.CollisionHandler(self.player,
 70                                                            self.blockers,
 71                                                            self.sprites,
 72                                                            self)
 73        self.dialogue_handler = textbox.TextHandler(self)
 74        self.state_dict = self.make_state_dict()
 75        self.portals = self.make_level_portals()
 76        self.menu_screen = player_menu.Player_Menu(game_data, self)
 77
 78    def make_viewport(self, map_image):
 79        """
 80        Create the viewport to view the level through.
 81        """
 82        map_rect = map_image.get_rect()
 83        return setup.SCREEN.get_rect(bottom=map_rect.bottom)
 84
 85    def make_level_surface(self, map_image):
 86        """
 87        Create the surface all images are blitted to.
 88        """
 89        map_rect = map_image.get_rect()
 90        map_width = map_rect.width
 91        if self.name in self.cut_off_bottom_map:
 92            map_height = map_rect.height - 32
 93        else:
 94            map_height = map_rect.height
 95        size = map_width, map_height
 96
 97        return pg.Surface(size).convert()
 98
 99    def make_player(self):
100        """
101        Make the player and sets location.
102        """
103        last_state = self.game_data['last state']
104
105
106        if last_state == 'battle':
107            player = person.Player(self.game_data['last direction'], self.game_data)
108            player.rect.x = self.game_data['last location'][0] * 32
109            player.rect.y = self.game_data['last location'][1] * 32
110
111        else:
112            for object in self.renderer.tmx_data.getObjects():
113                properties = object.__dict__
114                if properties['name'] == 'start point':
115                    if last_state == properties['state']:
116                        posx = properties['x'] * 2
117                        posy = (properties['y'] * 2) - 32
118                        player = person.Player(properties['direction'],
119                                               self.game_data)
120                        player.rect.x = posx
121                        player.rect.y = posy
122
123        return player
124
125    def make_blockers(self):
126        """
127        Make the blockers for the level.
128        """
129        blockers = []
130
131        for object in self.renderer.tmx_data.getObjects():
132            properties = object.__dict__
133            if properties['name'] == 'blocker':
134                left = properties['x'] * 2
135                top = ((properties['y']) * 2) - 32
136                blocker = pg.Rect(left, top, 32, 32)
137                blockers.append(blocker)
138
139        return blockers
140
141    def make_sprites(self):
142        """
143        Make any sprites for the level as needed.
144        """
145        sprites = pg.sprite.Group()
146
147        for object in self.renderer.tmx_data.getObjects():
148            properties = object.__dict__
149            if properties['name'] == 'sprite':
150                if 'direction' in properties:
151                    direction = properties['direction']
152                else:
153                    direction = 'down'
154
155                if properties['type'] == 'soldier' and direction == 'left':
156                    index = 1
157                else:
158                    index = 0
159
160                if 'item' in properties:
161                    item = properties['item']
162                else:
163                    item = None
164
165                if 'id' in properties:
166                    id = properties['id']
167                else:
168                    id = None
169
170                if 'battle' in properties:
171                    battle = properties['battle']
172                else:
173                    battle = None
174
175
176                x = properties['x'] * 2
177                y = ((properties['y']) * 2) - 32
178
179                sprite_dict = {'oldman': person.Person('oldman',
180                                                       x, y, direction),
181                               'bluedressgirl': person.Person('femalevillager',
182                                                              x, y, direction,
183                                                              'resting', 1),
184                               'femalewarrior': person.Person('femvillager2',
185                                                              x, y, direction,
186                                                              'autoresting'),
187                               'devil': person.Person('devil', x, y,
188                                                      'down', 'autoresting'),
189                               'oldmanbrother': person.Person('oldmanbrother',
190                                                              x, y, direction),
191                               'soldier': person.Person('soldier',
192                                                        x, y, direction,
193                                                        'resting', index),
194                               'king': person.Person('king', x, y, direction),
195                               'evilwizard': person.Person('evilwizard', x, y, direction),
196                               'treasurechest': person.Chest(x, y, id)}
197
198                sprite = sprite_dict[properties['type']]
199                if sprite.name == 'oldman':
200                    if self.game_data['old man gift']:
201                        sprite.item = self.game_data['old man gift']
202                        self.game_data['old man gift'] = {}
203                    else:
204                        sprite.item = item
205                else:
206                    sprite.item = item
207                sprite.battle = battle
208                self.assign_dialogue(sprite, properties)
209                self.check_for_opened_chest(sprite)
210                if sprite.name == 'evilwizard' and self.game_data['crown quest']:
211                    pass
212                else:
213                    sprites.add(sprite)
214
215        return sprites
216
217    def assign_dialogue(self, sprite, property_dict):
218        """
219        Assign dialogue from object property dictionaries in tmx maps to sprites.
220        """
221        dialogue_list = []
222        for i in range(int(property_dict['dialogue length'])):
223            dialogue_list.append(property_dict['dialogue'+str(i)])
224            sprite.dialogue = dialogue_list
225
226        if sprite.name == 'oldman':
227            quest_in_process_dialogue = ['Hurry to the NorthEast Shores!',
228                                         'I do not have much time left.']
229
230            if self.game_data['has brother elixir']:
231                if self.game_data['elixir received']:
232                    sprite.dialogue = ['My good health is thanks to you.',
233                                       'I will be forever in your debt.']
234                else:
235                    sprite.dialogue = ['Thank you for reaching my brother.',
236                                       'This ELIXIR will cure my ailment.',
237                                       'As a reward, I will teach you a magic spell.',
238                                       'Use it wisely.',
239                                       'You learned FIRE BLAST.']
240                    del self.game_data['player inventory']['ELIXIR']
241                    self.game_data['elixir received'] = True
242                    dialogue = ['My good health is thanks to you.',
243                                'I will be forever in your debt.']
244                    self.reset_dialogue = sprite, dialogue
245
246            elif self.game_data['talked to sick brother']:
247                sprite.dialogue = quest_in_process_dialogue
248
249            elif not self.game_data['talked to sick brother']:
250                self.game_data['talked to sick brother'] = True
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
296        return state_dict
297
298    def make_level_portals(self):
299        """
300        Make the portals to switch state.
301        """
302        portal_group = pg.sprite.Group()
303
304        for object in self.renderer.tmx_data.getObjects():
305            properties = object.__dict__
306            if properties['name'] == 'portal':
307                posx = properties['x'] * 2
308                posy = (properties['y'] * 2) - 32
309                new_state = properties['type']
310                portal_group.add(portal.Portal(posx, posy, new_state))
311
312
313        return portal_group
314
315    def running_normally(self, surface, keys, current_time):
316        """
317        Update level normally.
318        """
319        self.check_for_dialogue()
320        self.player.update(keys, current_time)
321        self.sprites.update(current_time)
322        self.collision_handler.update(keys, current_time)
323        self.check_for_portals()
324        self.check_for_battle()
325        self.dialogue_handler.update(keys, current_time)
326        self.check_for_menu(keys)
327        self.viewport_update()
328        self.draw_level(surface)
329
330    def check_for_portals(self):
331        """
332        Check if the player walks into a door, requiring a level change.
333        """
334        portal = pg.sprite.spritecollideany(self.player, self.portals)
335
336        if portal and self.player.state == 'resting':
337            self.player.location = self.player.get_tile_location()
338            self.next = portal.name
339            self.update_game_data()
340            self.done = True
341
342    def check_for_battle(self):
343        """
344        Check if the flag has been made true, indicating
345        to switch state to a battle.
346        """
347        if self.switch_to_battle and self.allow_battles and not self.done:
348            self.player.location = self.player.get_tile_location()
349            self.update_game_data()
350            self.next = 'battle'
351            self.done = True
352
353    def check_for_menu(self, keys):
354        """
355        Check if player hits enter to go to menu.
356        """
357        if keys[pg.K_RETURN] and self.allow_input:
358            if self.player.state == 'resting':
359                self.state = 'menu'
360                self.allow_input = False
361
362        if not keys[pg.K_RETURN]:
363            self.allow_input = True
364
365
366    def update_game_data(self):
367        """
368        Update the persistant game data dictionary.
369        """
370        self.game_data['last location'] = self.player.location
371        self.game_data['last direction'] = self.player.direction
372        self.game_data['last state'] = self.name
373        self.set_new_start_pos()
374
375
376    def set_new_start_pos(self):
377        """
378        Set new start position based on previous state.
379        """
380        location = copy.deepcopy(self.game_data['last location'])
381        direction = self.game_data['last direction']
382
383        if self.next == 'player menu':
384            pass
385        elif direction == 'up':
386            location[1] += 1
387        elif direction == 'down':
388            location[1] -= 1
389        elif direction == 'left':
390            location[0] += 1
391        elif direction == 'right':
392            location[0] -= 1
393
394
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
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
412    def check_for_dialogue(self):
413        """
414        Check if the level needs to freeze.
415        """
416        if self.dialogue_handler.textbox:
417            self.state = 'dialogue'
418
419    def update(self, surface, keys, current_time):
420        """
421        Update state.
422        """
423        state_function = self.state_dict[self.state]
424        state_function(surface, keys, current_time)
425
426    def viewport_update(self):
427        """
428        Update viewport so it stays centered on character,
429        unless at edge of map.
430        """
431        self.viewport.center = self.player.rect.center
432        self.viewport.clamp_ip(self.level_rect)
433
434    def draw_level(self, surface):
435        """
436        Blit all images to screen.
437        """
438        self.level_surface.blit(self.map_image, self.viewport, self.viewport)
439        self.level_surface.blit(self.player.image, self.player.rect)
440        self.sprites.draw(self.level_surface)
441
442        surface.blit(self.level_surface, (0, 0), self.viewport)
443        self.dialogue_handler.draw(surface)
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459