all repos — Legends-RPG @ 46298b7559a64ba4be11d3fb68bc23cae32b124e

A fantasy mini-RPG built with Python and Pygame.

data/menugui.py (view raw)

  1# -*- coding: utf-8 -*-
  2
  3"""
  4This class controls all the GUI for the player
  5menu screen.
  6"""
  7import sys
  8import pygame as pg
  9from . import setup, observer
 10from . import constants as c
 11from . import tools
 12
 13#Python 2/3 compatibility.
 14if sys.version_info[0] == 2:
 15    range = xrange
 16
 17class SmallArrow(pg.sprite.Sprite):
 18    """Small arrow for menu"""
 19    def __init__(self, info_box):
 20        super(SmallArrow, self).__init__()
 21        self.image = setup.GFX['smallarrow']
 22        self.rect = self.image.get_rect()
 23        self.state = 'selectmenu'
 24        self.state_dict = self.make_state_dict()
 25        self.slots = info_box.slots
 26        self.pos_list = []
 27
 28    def make_state_dict(self):
 29        """Make state dictionary"""
 30        state_dict = {'selectmenu': self.navigate_select_menu,
 31                      'itemsubmenu': self.navigate_item_submenu,
 32                      'magicsubmenu': self.navigate_magic_submenu}
 33
 34        return state_dict
 35
 36    def navigate_select_menu(self, pos_index):
 37        """Nav the select menu"""
 38        self.pos_list = self.make_select_menu_pos_list()
 39        self.rect.topleft = self.pos_list[pos_index]
 40
 41    def navigate_item_submenu(self, pos_index):
 42        """Nav the item submenu"""
 43        self.pos_list = self.make_item_menu_pos_list()
 44        self.rect.topleft = self.pos_list[pos_index]
 45
 46    def navigate_magic_submenu(self, pos_index):
 47        """Nav the magic submenu"""
 48        self.pos_list = self.make_magic_menu_pos_list()
 49        self.rect.topleft = self.pos_list[pos_index]
 50
 51    def make_magic_menu_pos_list(self):
 52        """
 53        Make the list of possible arrow positions for magic submenu.
 54        """
 55        pos_list = [(310, 119),
 56                    (310, 169)]
 57
 58        return pos_list
 59
 60    def make_select_menu_pos_list(self):
 61        """Make the list of possible arrow positions"""
 62        pos_list = []
 63
 64        for i in range(3):
 65            pos = (35, 443 + (i * 45))
 66            pos_list.append(pos)
 67
 68        return pos_list
 69
 70    def make_item_menu_pos_list(self):
 71        """Make the list of arrow positions in the item submenu"""
 72        pos_list = [(300, 173),
 73                    (300, 223),
 74                    (300, 323),
 75                    (300, 373),
 76                    (300, 478),
 77                    (300, 528),
 78                    (535, 478),
 79                    (535, 528)]
 80
 81        return pos_list
 82
 83    def update(self, pos_index):
 84        """Update arrow position"""
 85        state_function = self.state_dict[self.state]
 86        state_function(pos_index)
 87
 88    def draw(self, surface):
 89        """Draw to surface"""
 90        surface.blit(self.image, self.rect)
 91
 92
 93class QuickStats(pg.sprite.Sprite):
 94    def __init__(self, game_data):
 95        self.inventory = game_data['player inventory']
 96        self.game_data = game_data
 97        self.health = game_data['player stats']['health']
 98        self.stats = self.game_data['player stats']
 99        self.font = pg.font.Font(setup.FONTS[c.MAIN_FONT], 22)
100        self.small_font = pg.font.Font(setup.FONTS[c.MAIN_FONT], 18)
101        self.image, self.rect = self.make_image()
102
103    def make_image(self):
104        """
105        Make the surface for the gold box.
106        """
107        stat_list = ['GOLD', 'health', 'magic'] 
108        magic_health_list  = ['health', 'magic']
109        image = setup.GFX['goldbox']
110        rect = image.get_rect(left=10, top=244)
111
112        surface = pg.Surface(rect.size)
113        surface.set_colorkey(c.BLACK)
114        surface.blit(image, (0, 0))
115
116        for i, stat in enumerate(stat_list):
117            first_letter = stat[0].upper()
118            rest_of_letters = stat[1:]
119            if stat in magic_health_list:
120                current = self.stats[stat]['current']
121                max = self.stats[stat]['maximum']
122                text = "{}{}: {}/{}".format(first_letter, rest_of_letters, current, max)
123            elif stat == 'GOLD':
124                text = "Gold: {}".format(self.inventory[stat]['quantity'])
125            render = self.small_font.render(text, True, c.NEAR_BLACK)
126            x = 26
127            y = 45 + (i*30)
128            text_rect = render.get_rect(x=x,
129                                        centery=y)
130            surface.blit(render, text_rect)
131        
132        return surface, rect
133
134    def update(self):
135        """
136        Update gold.
137        """
138        self.image, self.rect = self.make_image()
139
140    def draw(self, surface):
141        """
142        Draw to surface.
143        """
144        surface.blit(self.image, self.rect)
145
146
147class InfoBox(pg.sprite.Sprite):
148    def __init__(self, inventory, player_stats):
149        super(InfoBox, self).__init__()
150        self.inventory = inventory
151        self.player_stats = player_stats
152        self.attack_power = self.get_attack_power()
153        self.defense_power = self.get_defense_power()
154        self.font = pg.font.Font(setup.FONTS[c.MAIN_FONT], 22)
155        self.big_font = pg.font.Font(setup.FONTS[c.MAIN_FONT], 24)
156        self.title_font = pg.font.Font(setup.FONTS[c.MAIN_FONT], 28)
157        self.title_font.set_underline(True)
158        self.get_tile = tools.get_tile
159        self.sword = self.get_tile(48, 0, setup.GFX['shopsigns'], 16, 16, 2)
160        self.shield = self.get_tile(32, 0, setup.GFX['shopsigns'], 16, 16, 2)
161        self.potion = self.get_tile(16, 0, setup.GFX['shopsigns'], 16, 16, 2)
162        self.possible_potions = ['Healing Potion', 'ELIXIR', 'Ether Potion']
163        self.possible_armor = ['Wooden Shield', 'Chain Mail']
164        self.possible_weapons = ['Long Sword', 'Rapier']
165        self.possible_magic = ['Fire Blast', 'Cure']
166        self.quantity_items = ['Healing Potion', 'ELIXIR', 'Ether Potion']
167        self.slots = {}
168        self.state = 'invisible'
169        self.state_dict = self.make_state_dict()
170        self.print_slots = True
171
172    def get_attack_power(self):
173        """
174        Calculate the current attack power based on equipped weapons.
175        """
176        weapon = self.inventory['equipped weapon']
177        weapon_power = self.inventory[weapon]['power']
178        return weapon_power + (self.player_stats['Level'] * 5)        
179
180    def get_defense_power(self):
181        """
182        Calculate the current defense power based on equipped weapons.
183        """
184        defense_power = 0
185        for armor in self.inventory['equipped armor']:
186            defense_power += self.inventory[armor]['power']
187
188        return defense_power
189
190    def make_state_dict(self):
191        """Make the dictionary of state methods"""
192        state_dict = {'stats': self.show_player_stats,
193                      'items': self.show_items,
194                      'magic': self.show_magic,
195                      'invisible': self.show_nothing}
196
197        return state_dict
198
199
200    def show_player_stats(self):
201        """Show the player's main stats"""
202        title = 'STATS'
203        stat_list = ['Level', 'experience to next level',
204                     'health', 'magic', 'Attack Power', 
205                     'Defense Power', 'gold']
206        attack_power = 5
207        surface, rect = self.make_blank_info_box(title)
208
209        for i, stat in enumerate(stat_list):
210            if stat == 'health' or stat == 'magic':
211                text = "{}{}: {} / {}".format(stat[0].upper(),
212                                              stat[1:],
213                                              str(self.player_stats[stat]['current']),
214                                              str(self.player_stats[stat]['maximum']))
215            elif stat == 'experience to next level':
216                text = "{}{}: {}".format(stat[0].upper(),
217                                         stat[1:],
218                                         self.player_stats[stat])
219            elif stat == 'Attack Power':
220				text = "{}: {}".format(stat, self.get_attack_power()) 
221            elif stat == 'Defense Power':
222                text = "{}: {}".format(stat, self.get_defense_power())
223            elif stat == 'gold':
224                text = "Gold: {}".format(self.inventory['GOLD']['quantity'])
225            else:
226                text = "{}: {}".format(stat, str(self.player_stats[stat]))
227            text_image = self.font.render(text, True, c.NEAR_BLACK)
228            text_rect = text_image.get_rect(x=50, y=80+(i*50))
229            surface.blit(text_image, text_rect)
230
231        self.image = surface
232        self.rect = rect
233
234
235    def show_items(self):
236        """Show list of items the player has"""
237        title = 'ITEMS'
238        potions = ['POTIONS']
239        weapons = ['WEAPONS']
240        armor = ['ARMOR']
241        for i, item in enumerate(self.inventory):
242            if item in self.possible_weapons:
243                if item == self.inventory['equipped weapon']:
244                    item += " (E)"
245                weapons.append(item)
246            elif item in self.possible_armor:
247                if item in self.inventory['equipped armor']:
248                    item += " (E)"
249                armor.append(item)
250            elif item in self.possible_potions:
251                potions.append(item)
252
253        self.slots = {}
254        self.assign_slots(weapons, 85)
255        self.assign_slots(armor, 235)
256        self.assign_slots(potions, 390)
257
258        surface, rect = self.make_blank_info_box(title)
259
260        self.blit_item_lists(surface)
261
262        self.sword['rect'].topleft = 40, 80
263        self.shield['rect'].topleft = 40, 230
264        self.potion['rect'].topleft = 40, 385
265        surface.blit(self.sword['surface'], self.sword['rect'])
266        surface.blit(self.shield['surface'], self.shield['rect'])
267        surface.blit(self.potion['surface'], self.potion['rect'])
268
269        self.image = surface
270        self.rect = rect
271
272
273    def assign_slots(self, item_list, starty, weapon_or_armor=False):
274        """Assign each item to a slot in the menu"""
275        if len(item_list) > 3:
276            for i, item in enumerate(item_list[:3]):
277                posx = 80
278                posy = starty + (i * 50)
279                self.slots[(posx, posy)] = item
280            for i, item in enumerate(item_list[3:]):
281                posx = 315
282                posy = (starty + 50) + (i * 5)
283                self.slots[(posx, posy)] = item
284        else:
285            for i, item in enumerate(item_list):
286                posx = 80
287                posy = starty + (i * 50)
288                self.slots[(posx, posy)] = item
289
290    def assign_magic_slots(self, magic_list, starty):
291        """
292        Assign each magic spell to a slot in the menu.
293        """
294        for i, spell in enumerate(magic_list):
295            posx = 120
296            posy = starty + (i * 50)
297            self.slots[(posx, posy)] = spell
298
299    def blit_item_lists(self, surface):
300        """Blit item list to info box surface"""
301        for coord in self.slots:
302            item = self.slots[coord]
303
304            if item in self.possible_potions:
305                text = "{}: {}".format(self.slots[coord],
306                                       self.inventory[item]['quantity'])
307            else:
308                text = "{}".format(self.slots[coord])
309            text_image = self.font.render(text, True, c.NEAR_BLACK)
310            text_rect = text_image.get_rect(topleft=coord)
311            surface.blit(text_image, text_rect)
312
313    def show_magic(self):
314        """Show list of magic spells the player knows"""
315        title = 'MAGIC'
316        item_list = []
317        for item in self.inventory:
318            if item in self.possible_magic:
319                item_list.append(item)
320                item_list = sorted(item_list)
321
322        self.slots = {}
323        self.assign_magic_slots(item_list, 80)
324
325        surface, rect = self.make_blank_info_box(title)
326
327        for i, item in enumerate(item_list):
328            text_image = self.font.render(item, True, c.NEAR_BLACK)
329            text_rect = text_image.get_rect(x=100, y=80+(i*50))
330            surface.blit(text_image, text_rect)
331
332        self.image = surface
333        self.rect = rect
334
335    def show_nothing(self):
336        """
337        Show nothing when the menu is opened from a level.
338        """
339        self.image = pg.Surface((1, 1))
340        self.rect = self.image.get_rect()
341        self.image.fill(c.BLACK_BLUE)
342
343    def make_blank_info_box(self, title):
344        """Make an info box with title, otherwise blank"""
345        image = setup.GFX['playerstatsbox']
346        rect = image.get_rect(left=285, top=35)
347        centerx = rect.width / 2
348
349        surface = pg.Surface(rect.size)
350        surface.set_colorkey(c.BLACK)
351        surface.blit(image, (0,0))
352
353        title_image = self.title_font.render(title, True, c.NEAR_BLACK)
354        title_rect = title_image.get_rect(centerx=centerx, y=30)
355        surface.blit(title_image, title_rect)
356
357        return surface, rect
358
359
360    def update(self):
361        state_function = self.state_dict[self.state]
362        state_function()
363
364
365    def draw(self, surface):
366        """Draw to surface"""
367        surface.blit(self.image, self.rect)
368
369
370class SelectionBox(pg.sprite.Sprite):
371    def __init__(self):
372        self.font = pg.font.Font(setup.FONTS[c.MAIN_FONT], 22)
373        self.image, self.rect = self.make_image()
374
375    def make_image(self):
376        choices = ['Items', 'Magic', 'Stats']
377        image = setup.GFX['goldbox']
378        rect = image.get_rect(left=10, top=425)
379
380        surface = pg.Surface(rect.size)
381        surface.set_colorkey(c.BLACK)
382        surface.blit(image, (0, 0))
383
384        for i, choice in enumerate(choices):
385            choice_image = self.font.render(choice, True, c.NEAR_BLACK)
386            choice_rect = choice_image.get_rect(x=100, y=(15 + (i * 45)))
387            surface.blit(choice_image, choice_rect)
388
389        return surface, rect
390
391    def draw(self, surface):
392        """Draw to surface"""
393        surface.blit(self.image, self.rect)
394
395
396class MenuGui(object):
397    def __init__(self, level, inventory, stats):
398        self.level = level
399        self.game_data = self.level.game_data
400        self.sfx_observer = observer.SoundEffects()
401        self.observers = [self.sfx_observer]
402        self.inventory = inventory
403        self.stats = stats
404        self.info_box = InfoBox(inventory, stats)
405        self.gold_box = QuickStats(self.game_data)
406        self.selection_box = SelectionBox()
407        self.arrow = SmallArrow(self.info_box)
408        self.arrow_index = 0
409        self.allow_input = False
410
411    def check_for_input(self, keys):
412        """Check for input"""
413        if self.allow_input:
414            if keys[pg.K_DOWN]:
415                if self.arrow_index < len(self.arrow.pos_list) - 1:
416                    self.notify(c.CLICK)
417                    self.arrow_index += 1
418                    self.allow_input = False
419            elif keys[pg.K_UP]:
420                if self.arrow_index > 0:
421                    self.notify(c.CLICK)
422                    self.arrow_index -= 1
423                    self.allow_input = False
424            elif keys[pg.K_RIGHT]:
425                if self.info_box.state == 'items':
426                    if not self.arrow.state == 'itemsubmenu':
427                        self.notify(c.CLICK)
428                        self.arrow_index = 0
429                    self.arrow.state = 'itemsubmenu'
430                elif self.info_box.state == 'magic':
431                    if not self.arrow.state == 'magicsubmenu':
432                        self.notify(c.CLICK)
433                        self.arrow_index = 0
434                    self.arrow.state = 'magicsubmenu'
435                self.allow_input = False
436
437            elif keys[pg.K_LEFT]:
438                self.notify(c.CLICK)
439                self.arrow.state = 'selectmenu'
440                self.arrow_index = 0
441                self.allow_input = False
442            elif keys[pg.K_SPACE]:
443                self.notify(c.CLICK2)
444                if self.arrow.state == 'selectmenu':
445                    if self.arrow_index == 0:
446                        self.info_box.state = 'items'
447                    elif self.arrow_index == 1:
448                        self.info_box.state = 'magic'
449                    elif self.arrow_index == 2:
450                        self.info_box.state = 'stats'
451                elif self.arrow.state == 'itemsubmenu':
452                    self.select_item()
453                elif self.arrow.state == 'magicsubmenu':
454                    self.select_magic()
455
456                self.allow_input = False
457            elif keys[pg.K_RETURN]:
458                self.level.state = 'normal'
459                self.info_box.state = 'invisible'
460                self.allow_input = False
461                self.arrow_index = 0
462                self.arrow.state = 'selectmenu'
463
464        if (not keys[pg.K_DOWN]
465                and not keys[pg.K_UP]
466                and not keys[pg.K_RETURN]
467                and not keys[pg.K_SPACE]
468                and not keys[pg.K_RIGHT]
469                and not keys[pg.K_LEFT]):
470            self.allow_input = True
471
472    def notify(self, event):
473        """
474        Notify all observers of event.
475        """
476        for observer in self.observers:
477            observer.on_notify(event)
478
479    def select_item(self):
480        """
481        Select item from item menu.
482        """
483        health = self.game_data['player stats']['health']
484        posx = self.arrow.rect.x - 220
485        posy = self.arrow.rect.y - 38
486
487        if (posx, posy) in self.info_box.slots:
488            if self.info_box.slots[(posx, posy)][:7] == 'Healing':
489                potion = 'Healing Potion'
490                value = 30
491                self.drink_potion(potion, health, value)
492            elif self.info_box.slots[(posx, posy)][:5] == 'Ether':
493                potion = 'Ether Potion'
494                stat = self.game_data['player stats']['magic']
495                value = 30
496                self.drink_potion(potion, stat, value)
497            elif self.info_box.slots[(posx, posy)][:10] == 'Long Sword':
498                self.inventory['equipped weapon'] = 'Long Sword'
499            elif self.info_box.slots[(posx, posy)][:6] == 'Rapier':
500                self.inventory['equipped weapon'] = 'Rapier'
501            elif self.info_box.slots[(posx, posy)][:13] == 'Wooden Shield':
502                if 'Wooden Shield' in self.inventory['equipped armor']:
503                    self.inventory['equipped armor'].remove('Wooden Shield')
504                else:
505                    self.inventory['equipped armor'].append('Wooden Shield')
506            elif self.info_box.slots[(posx, posy)][:10] == 'Chain Mail':
507                if 'Chain Mail' in self.inventory['equipped armor']:
508                    self.inventory['equipped armor'].remove('Chain Mail')
509                else:
510                    self.inventory['equipped armor'].append('Chain Mail')
511
512    def select_magic(self):
513        """
514        Select spell from magic menu.
515        """
516        health = self.game_data['player stats']['health']
517        magic = self.game_data['player stats']['magic']
518        posx = self.arrow.rect.x - 190
519        posy = self.arrow.rect.y - 39
520
521        if (posx, posy) in self.info_box.slots:
522            if self.info_box.slots[(posx, posy)][:4] == 'Cure':
523               self.use_cure_spell()
524
525    def use_cure_spell(self):
526        """
527        Use cure spell to heal player.
528        """
529        health = self.game_data['player stats']['health']
530        magic = self.game_data['player stats']['magic']
531        inventory = self.game_data['player inventory']
532
533        if magic['current'] >= inventory['Cure']['magic points']:
534            magic['current'] -= inventory['Cure']['magic points']
535            health['current'] += inventory['Cure']['power']
536            if health['current'] > health['maximum']:
537                health['current'] = health['maximum']
538
539    def drink_potion(self, potion, stat, value):
540        """
541        Drink potion and change player stats.
542        """
543        self.inventory[potion]['quantity'] -= 1
544        stat['current'] += value
545        if stat['current'] > stat['maximum']:
546            stat['current'] = stat['maximum']
547        if not self.inventory[potion]['quantity']:
548            del self.inventory[potion]
549
550    def update(self, keys):
551        self.info_box.update()
552        self.gold_box.update()
553        self.arrow.update(self.arrow_index)
554        self.check_for_input(keys)
555
556
557    def draw(self, surface):
558        self.gold_box.draw(surface)
559        self.info_box.draw(surface)
560        self.selection_box.draw(surface)
561        self.arrow.draw(surface)