all repos — Legends-RPG @ 68e9a52fe83686831824476d53ff7135dbcec2cf

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                    dialogue = ['My good health is thanks to you.',
242                                'I will be forever in your debt.']
243                    self.reset_dialogue = sprite, dialogue
244
245            elif self.game_data['talked to sick brother']:
246                sprite.dialogue = quest_in_process_dialogue
247
248            elif not self.game_data['talked to sick brother']:
249                self.game_data['talked to sick brother'] = True
250                self.reset_dialogue = (sprite, quest_in_process_dialogue)
251        elif sprite.name == 'oldmanbrother':
252            if self.game_data['has brother elixir']:
253                if self.game_data['elixir received']:
254                    sprite.dialogue = ['I am glad my brother is doing well.',
255                                       'You have a wise and generous spirit.']
256                else:
257                    sprite.dialogue = ['Hurry! There is precious little time.']
258            elif self.game_data['talked to sick brother']:
259                sprite.dialogue = ['My brother is sick?!?',
260                                   'I have not seen him in years.  I had no idea he was not well.',
261                                   'Quick, take this ELIXIR to him immediately.']
262        elif sprite.name == 'king':
263            retrieved_crown_dialogue = ['My crown! You recovered my stolen crown!!!',
264                                        'I can not believe what I see before my eyes.',
265                                        'You are truly a brave and noble warrior.',
266                                        'Henceforth, I name thee Grand Protector of this Town!',
267                                        'Go forth and be recognized.',
268                                        'You are the greatest warrior this world has ever known.']
269            thank_you_dialogue = ['Thank you for retrieving my crown.',
270                                  'My kingdom is forever in your debt.']
271
272            if self.game_data['crown quest'] and not self.game_data['delivered crown']:
273                sprite.dialogue = retrieved_crown_dialogue
274                self.game_data['delivered crown'] = True
275                self.reset_dialogue = (sprite, thank_you_dialogue)
276            elif self.game_data['delivered crown']:
277                sprite.dialogue = thank_you_dialogue
278
279
280    def check_for_opened_chest(self, sprite):
281        if sprite.name == 'treasurechest':
282            if not self.game_data['treasure{}'.format(sprite.id)]:
283                sprite.dialogue = ['Empty.']
284                sprite.item = None
285                sprite.index = 1
286
287    def make_state_dict(self):
288        """
289        Make a dictionary of states the level can be in.
290        """
291        state_dict = {'normal': self.running_normally,
292                      'dialogue': self.handling_dialogue,
293                      'menu': self.goto_menu,
294                      'transition_in': self.transition_in,
295                      'transition_out': self.transition_out}
296
297        return state_dict
298
299    def make_level_portals(self):
300        """
301        Make the portals to switch state.
302        """
303        portal_group = pg.sprite.Group()
304
305        for object in self.renderer.tmx_data.getObjects():
306            properties = object.__dict__
307            if properties['name'] == 'portal':
308                posx = properties['x'] * 2
309                posy = (properties['y'] * 2) - 32
310                new_state = properties['type']
311                portal_group.add(portal.Portal(posx, posy, new_state))
312
313
314        return portal_group
315
316    def running_normally(self, surface, keys, current_time):
317        """
318        Update level normally.
319        """
320        self.check_for_dialogue()
321        self.player.update(keys, current_time)
322        self.sprites.update(current_time)
323        self.collision_handler.update(keys, current_time)
324        self.check_for_portals()
325        self.check_for_battle()
326        self.dialogue_handler.update(keys, current_time)
327        self.check_for_menu(keys)
328        self.viewport_update()
329        self.draw_level(surface)
330
331    def check_for_portals(self):
332        """
333        Check if the player walks into a door, requiring a level change.
334        """
335        portal = pg.sprite.spritecollideany(self.player, self.portals)
336
337        if portal and self.player.state == 'resting':
338            self.player.location = self.player.get_tile_location()
339            self.next = portal.name
340            self.update_game_data()
341            self.state = 'transition_out'
342
343    def check_for_battle(self):
344        """
345        Check if the flag has been made true, indicating
346        to switch state to a battle.
347        """
348        if self.switch_to_battle and self.allow_battles and not self.done:
349            self.player.location = self.player.get_tile_location()
350            self.update_game_data()
351            self.next = 'battle'
352            self.state = 'transition_out'
353
354    def check_for_menu(self, keys):
355        """
356        Check if player hits enter to go to menu.
357        """
358        if keys[pg.K_RETURN] and self.allow_input:
359            if self.player.state == 'resting':
360                self.state = 'menu'
361                self.allow_input = False
362
363        if not keys[pg.K_RETURN]:
364            self.allow_input = True
365
366
367    def update_game_data(self):
368        """
369        Update the persistant game data dictionary.
370        """
371        self.game_data['last location'] = self.player.location
372        self.game_data['last direction'] = self.player.direction
373        self.game_data['last state'] = self.name
374        self.set_new_start_pos()
375
376
377    def set_new_start_pos(self):
378        """
379        Set new start position based on previous state.
380        """
381        location = copy.deepcopy(self.game_data['last location'])
382        direction = self.game_data['last direction']
383
384        if self.next == 'player menu':
385            pass
386        elif direction == 'up':
387            location[1] += 1
388        elif direction == 'down':
389            location[1] -= 1
390        elif direction == 'left':
391            location[0] += 1
392        elif direction == 'right':
393            location[0] -= 1
394
395    def handling_dialogue(self, surface, keys, current_time):
396        """
397        Update only dialogue boxes.
398        """
399        self.dialogue_handler.update(keys, current_time)
400        self.draw_level(surface)
401
402    def goto_menu(self, surface, keys, *args):
403        """
404        Go to menu screen.
405        """
406        self.menu_screen.update(surface, keys)
407        self.menu_screen.draw(surface)
408
409    def check_for_dialogue(self):
410        """
411        Check if the level needs to freeze.
412        """
413        if self.dialogue_handler.textbox:
414            self.state = 'dialogue'
415
416    def transition_out(self, surface, *args):
417        """
418        Transition level to new scene.
419        """
420        transition_image = pg.Surface(self.transition_rect.size)
421        transition_image.fill(c.TRANSITION_COLOR)
422        transition_image.set_alpha(self.transition_alpha)
423        self.draw_level(surface)
424        surface.blit(transition_image, self.transition_rect)
425        self.transition_alpha += c.TRANSITION_SPEED
426        if self.transition_alpha >= 255:
427            self.transition_alpha = 255
428            self.done = True
429
430    def transition_in(self, surface, *args):
431        """
432        Transition into level.
433        """
434        self.viewport_update()
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 <= 0:
442            self.state = 'normal'
443            self.transition_alpha = 0
444
445    def update(self, surface, keys, current_time):
446        """
447        Update state.
448        """
449        state_function = self.state_dict[self.state]
450        state_function(surface, keys, current_time)
451
452    def viewport_update(self):
453        """
454        Update viewport so it stays centered on character,
455        unless at edge of map.
456        """
457        self.viewport.center = self.player.rect.center
458        self.viewport.clamp_ip(self.level_rect)
459
460    def draw_level(self, surface):
461        """
462        Blit all images to screen.
463        """
464        self.level_surface.blit(self.map_image, self.viewport, self.viewport)
465        self.level_surface.blit(self.player.image, self.player.rect)
466        self.sprites.draw(self.level_surface)
467
468        surface.blit(self.level_surface, (0, 0), self.viewport)
469        self.dialogue_handler.draw(surface)
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485