all repos — Legends-RPG @ 3f30da44bb69bd438432750872fdd354c4f07ee3

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