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)