all repos — Legends-RPG @ 0c36050b5841a8b230a30b7dcda0ff5d32b310ed

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