all repos — Legends-RPG @ c00a96d3b62fa0d580bba601b8f00a621b26947a

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