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)