all repos — Legends-RPG @ 9e643337e6380fdb1ab32813bf8848ad7e7219c8

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