data/shopgui.py (view raw)
1"""
2This class controls the textbox GUI for any shop state.
3A Gui object is created and updated by the shop state.
4"""
5
6import pygame as pg
7from . import setup, observer
8from . components import textbox
9from . import constants as c
10
11
12class Gui(object):
13 """Class that controls the GUI of the shop state"""
14 def __init__(self, level):
15 self.level = level
16 self.game_data = self.level.game_data
17 self.level.game_data['last direction'] = 'down'
18 self.SFX_observer = observer.SoundEffects()
19 self.observers = [self.SFX_observer]
20 self.sellable_items = level.sell_items
21 self.player_inventory = level.game_data['player inventory']
22 self.name = level.name
23 self.state = 'dialogue'
24 self.no_selling = ['Inn', 'Magic Shop']
25 self.weapon_list = ['Long Sword', 'Rapier']
26 self.armor_list = ['Chain Mail', 'Wooden Shield']
27 self.font = pg.font.Font(setup.FONTS[c.MAIN_FONT], 22)
28 self.index = 0
29 self.timer = 0.0
30 self.allow_input = False
31 self.items = level.items
32 self.item_to_be_sold = None
33 self.item_to_be_purchased = None
34 self.dialogue = level.dialogue
35 self.accept_dialogue = level.accept_dialogue
36 self.accept_sale_dialogue = level.accept_sale_dialogue
37 self.arrow = textbox.NextArrow()
38 self.selection_arrow = textbox.NextArrow()
39 self.arrow_pos1 = (50, 475)
40 self.arrow_pos2 = (50, 515)
41 self.arrow_pos3 = (50, 555)
42 self.arrow_pos4 = (50, 495)
43 self.arrow_pos5 = (50, 535)
44 self.arrow_pos_list = [self.arrow_pos1, self.arrow_pos2, self.arrow_pos3]
45 self.two_arrow_pos_list = [self.arrow_pos4, self.arrow_pos5]
46 self.arrow_index = 0
47 self.selection_arrow.rect.topleft = self.arrow_pos1
48 self.dialogue_box = self.make_dialogue_box(self.dialogue, self.index)
49 self.gold_box = self.make_gold_box()
50 if self.name in self.no_selling:
51 choices = self.items[0]['dialogue']
52 else:
53 choices = ['Buy', 'Sell', 'Leave']
54 self.selection_box = self.make_selection_box(choices)
55 self.state_dict = self.make_state_dict()
56
57 def notify(self, event):
58 """
59 Notify all observers of event.
60 """
61 for observer in self.observers:
62 observer.on_notify(event)
63
64 def make_dialogue_box(self, dialogue_list, index):
65 """
66 Make the sprite that controls the dialogue.
67 """
68 image = setup.GFX['dialoguebox']
69 rect = image.get_rect()
70 surface = pg.Surface(rect.size)
71 surface.set_colorkey(c.BLACK)
72 surface.blit(image, rect)
73 dialogue = self.font.render(dialogue_list[index],
74 True,
75 c.NEAR_BLACK)
76 dialogue_rect = dialogue.get_rect(left=50, top=50)
77 surface.blit(dialogue, dialogue_rect)
78 sprite = pg.sprite.Sprite()
79 sprite.image = surface
80 sprite.rect = rect
81 self.check_to_draw_arrow(sprite)
82
83 return sprite
84
85 def check_to_draw_arrow(self, sprite):
86 """
87 Blink arrow if more text needs to be read.
88 """
89 if self.index < len(self.dialogue) - 1:
90 sprite.image.blit(self.arrow.image, self.arrow.rect)
91
92 def make_gold_box(self):
93 """Make the box to display total gold"""
94 image = setup.GFX['goldbox']
95 rect = image.get_rect(bottom=608, right=800)
96
97 surface = pg.Surface(rect.size)
98 surface.set_colorkey(c.BLACK)
99 surface.blit(image, (0, 0))
100 gold = self.player_inventory['GOLD']['quantity']
101 text = 'Gold: ' + str(gold)
102 text_render = self.font.render(text, True, c.NEAR_BLACK)
103 text_rect = text_render.get_rect(x=80, y=60)
104
105 surface.blit(text_render, text_rect)
106
107 sprite = pg.sprite.Sprite()
108 sprite.image = surface
109 sprite.rect = rect
110
111 return sprite
112
113 def make_selection_box(self, choices):
114 """Make the box for the player to select options"""
115 image = setup.GFX['shopbox']
116 rect = image.get_rect(bottom=608)
117
118 surface = pg.Surface(rect.size)
119 surface.set_colorkey(c.BLACK)
120 surface.blit(image, (0, 0))
121
122 if len(choices) == 2:
123 choice1 = self.font.render(choices[0], True, c.NEAR_BLACK)
124 choice1_rect = choice1.get_rect(x=200, y=35)
125 choice2 = self.font.render(choices[1], True, c.NEAR_BLACK)
126 choice2_rect = choice2.get_rect(x=200, y=75)
127
128 surface.blit(choice1, choice1_rect)
129 surface.blit(choice2, choice2_rect)
130
131 elif len(choices) == 3:
132 choice1 = self.font.render(choices[0], True, c.NEAR_BLACK)
133 choice1_rect = choice1.get_rect(x=200, y=15)
134 choice2 = self.font.render(choices[1], True, c.NEAR_BLACK)
135 choice2_rect = choice2.get_rect(x=200, y=55)
136 choice3 = self.font.render(choices[2], True, c.NEAR_BLACK)
137 choice3_rect = choice3.get_rect(x=200, y=95)
138
139 surface.blit(choice1, choice1_rect)
140 surface.blit(choice2, choice2_rect)
141 surface.blit(choice3, choice3_rect)
142
143 sprite = pg.sprite.Sprite()
144 sprite.image = surface
145 sprite.rect = rect
146
147 return sprite
148
149
150 def make_state_dict(self):
151 """Make the state dictionary for the GUI behavior"""
152 state_dict = {'dialogue': self.control_dialogue,
153 'select': self.make_selection,
154 'confirmpurchase': self.confirm_purchase,
155 'confirmsell': self.confirm_sell,
156 'reject': self.reject_insufficient_gold,
157 'accept': self.accept_purchase,
158 'acceptsell': self.accept_sale,
159 'hasitem': self.has_item,
160 'buysell': self.buy_sell,
161 'sell': self.sell_items,
162 'cantsell': self.cant_sell,
163 'cantsellequippedweapon': self.cant_sell_equipped_weapon,
164 'cantsellequippedarmor': self.cant_sell_equipped_armor}
165
166 return state_dict
167
168 def control_dialogue(self, keys, current_time):
169 """Control the dialogue boxes"""
170 self.dialogue_box = self.make_dialogue_box(self.dialogue, self.index)
171
172 if self.index < (len(self.dialogue) - 1) and self.allow_input:
173 if keys[pg.K_SPACE]:
174 self.index += 1
175 self.allow_input = False
176
177 if self.index == (len(self.dialogue) - 1):
178 self.state = self.begin_new_transaction()
179
180 if not keys[pg.K_SPACE]:
181 self.allow_input = True
182
183 def begin_new_transaction(self):
184 """Set state to buysell or select, depending if the shop
185 is a Inn/Magic shop or not"""
186 if self.level.name in self.no_selling:
187 state = 'select'
188 else:
189 state = 'buysell'
190
191 return state
192
193
194 def make_selection(self, keys, current_time):
195 """Control the selection"""
196 choices = []
197 for item in self.items:
198 choices.append(item['dialogue'])
199 if self.name in self.no_selling:
200 choices.append('Leave')
201 else:
202 choices.append('Cancel')
203 self.dialogue_box = self.make_dialogue_box(self.dialogue, self.index)
204 self.selection_box = self.make_selection_box(choices)
205 self.gold_box = self.make_gold_box()
206
207 if len(choices) == 2:
208 arrow_list = self.two_arrow_pos_list
209 elif len(choices) == 3:
210 arrow_list = self.arrow_pos_list
211 else:
212 arrow_list = None
213 AssertionError('Only two items supported')
214
215 self.selection_arrow.rect.topleft = arrow_list[self.arrow_index]
216
217
218 if keys[pg.K_DOWN] and self.allow_input:
219 if self.arrow_index < (len(choices) - 1):
220 self.arrow_index += 1
221 self.allow_input = False
222 self.notify(c.CLICK)
223 elif keys[pg.K_UP] and self.allow_input:
224 if self.arrow_index > 0:
225 self.arrow_index -= 1
226 self.allow_input = False
227 self.notify(c.CLICK)
228 elif keys[pg.K_SPACE] and self.allow_input:
229 if self.arrow_index == 0:
230 self.state = 'confirmpurchase'
231 self.item_to_be_purchased = self.items[0]
232
233 elif self.arrow_index == 1 and len(choices) == 3:
234 self.state = 'confirmpurchase'
235 self.item_to_be_purchased = self.items[1]
236
237 else:
238 if self.level.name in self.no_selling:
239 self.level.state = 'transition out'
240 self.game_data['last state'] = self.level.name
241 else:
242 self.state = 'buysell'
243
244 self.notify(c.CLICK2)
245 self.arrow_index = 0
246 self.allow_input = False
247
248 if not keys[pg.K_SPACE] and not keys[pg.K_UP] and not keys[pg.K_DOWN]:
249 self.allow_input = True
250
251
252
253 def confirm_purchase(self, keys, current_time):
254 """Confirm selection state for GUI"""
255 dialogue = ['Are you sure?']
256 choices = ['Yes', 'No']
257 self.selection_box = self.make_selection_box(choices)
258 self.gold_box = self.make_gold_box()
259 self.dialogue_box = self.make_dialogue_box(dialogue, 0)
260 self.selection_arrow.rect.topleft = self.two_arrow_pos_list[self.arrow_index]
261
262 if keys[pg.K_DOWN] and self.allow_input:
263 if self.arrow_index < (len(choices) - 1):
264 self.arrow_index += 1
265 self.allow_input = False
266 self.notify(c.CLICK)
267 elif keys[pg.K_UP] and self.allow_input:
268 if self.arrow_index > 0:
269 self.arrow_index -= 1
270 self.allow_input = False
271 self.notify(c.CLICK)
272 elif keys[pg.K_SPACE] and self.allow_input:
273 if self.arrow_index == 0:
274 self.buy_item()
275 elif self.arrow_index == 1:
276 self.state = self.begin_new_transaction()
277 self.notify(c.CLICK2)
278 self.arrow_index = 0
279 self.allow_input = False
280
281 if not keys[pg.K_SPACE] and not keys[pg.K_DOWN] and not keys[pg.K_UP]:
282 self.allow_input = True
283
284
285 def buy_item(self):
286 """Attempt to allow player to purchase item"""
287 item = self.item_to_be_purchased
288
289 self.player_inventory['GOLD']['quantity'] -= item['price']
290
291 if self.player_inventory['GOLD']['quantity'] < 0:
292 self.player_inventory['GOLD']['quantity'] += item['price']
293 self.state = 'reject'
294 else:
295 if (item['type'] in self.player_inventory and
296 not self.name == c.POTION_SHOP):
297 self.state = 'hasitem'
298 self.player_inventory['GOLD']['quantity'] += item['price']
299 else:
300 self.notify(c.CLOTH_BELT)
301 self.state = 'accept'
302 self.add_player_item(item)
303
304
305 def add_player_item(self, item):
306 """
307 Add item to player's inventory.
308 """
309 item_type = item['type']
310 quantity = item['quantity']
311 value = item['price']
312 power = item['power']
313 magic_list = ['Cure', 'Fire Blast']
314 player_armor = ['Chain Mail', 'Wooden Shield']
315 player_weapons = ['Rapier', 'Long Sword']
316 player_items = self.level.game_data['player inventory']
317 player_health = self.level.game_data['player stats']['health']
318 player_magic = self.level.game_data['player stats']['magic']
319 equipped_armor = self.level.game_data['player inventory']['equipped armor']
320
321 item_to_add = {'quantity': quantity,
322 'value': value,
323 'power': power}
324
325 if item_type in magic_list:
326 item_to_add = {'magic points': item['magic points'],
327 'power': item['power']}
328 player_items[item_type] = item_to_add
329 if item_type in player_armor:
330 equipped_armor.append(item_type)
331 if item_type in player_weapons:
332 player_items['equipped weapon'] = item_type
333 if item_type in player_items:
334 player_items[item_type]['quantity'] += quantity
335 elif quantity > 0:
336 player_items[item_type] = item_to_add
337 elif item_type == 'room':
338 player_health['current'] = player_health['maximum']
339 player_magic['current'] = player_magic['maximum']
340
341 def confirm_sell(self, keys, current_time):
342 """
343 Confirm player wants to sell item.
344 """
345 dialogue = ['Are you sure?']
346 choices = ['Yes', 'No']
347 self.dialogue_box = self.make_dialogue_box(dialogue, 0)
348 self.selection_box = self.make_selection_box(choices)
349 self.selection_arrow.rect.topleft = self.two_arrow_pos_list[self.arrow_index]
350
351 if keys[pg.K_DOWN] and self.allow_input:
352 if self.arrow_index < (len(choices) - 1):
353 self.arrow_index += 1
354 self.allow_input = False
355 self.notify(c.CLICK)
356 elif keys[pg.K_UP] and self.allow_input:
357 if self.arrow_index > 0:
358 self.arrow_index -= 1
359 self.allow_input = False
360 self.notify(c.CLICK)
361 elif keys[pg.K_SPACE] and self.allow_input:
362 if self.arrow_index == 0:
363 self.sell_item_from_inventory()
364 elif self.arrow_index == 1:
365 self.state = self.begin_new_transaction()
366 self.notify(c.CLICK2)
367 self.allow_input = False
368 self.arrow_index = 0
369
370 if not keys[pg.K_SPACE] and not keys[pg.K_UP] and not keys[pg.K_DOWN]:
371 self.allow_input = True
372
373
374 def sell_item_from_inventory(self):
375 """
376 Allow player to sell item to shop.
377 """
378 item_price = self.item_to_be_sold['price']
379 item_name = self.item_to_be_sold['type']
380
381 if item_name in self.weapon_list:
382 if item_name == self.game_data['player inventory']['equipped weapon']:
383 self.state = 'cantsellequippedweapon'
384 else:
385 self.notify(c.CLOTH_BELT)
386 self.sell_inventory_data_adjust(item_price, item_name)
387
388 elif item_name in self.armor_list:
389 if item_name in self.game_data['player inventory']['equipped armor']:
390 self.state = 'cantsellequippedarmor'
391 else:
392 self.notify(c.CLOTH_BELT)
393 self.sell_inventory_data_adjust(item_price, item_name)
394 else:
395 self.notify(c.CLOTH_BELT)
396 self.sell_inventory_data_adjust(item_price, item_name)
397
398 def sell_inventory_data_adjust(self, item_price, item_name):
399 """
400 Add gold and subtract item during sale.
401 """
402 self.player_inventory['GOLD']['quantity'] += (item_price / 2)
403 self.state = 'acceptsell'
404 if self.player_inventory[item_name]['quantity'] > 1:
405 self.player_inventory[item_name]['quantity'] -= 1
406 else:
407 del self.player_inventory[self.item_to_be_sold['type']]
408
409 def reject_insufficient_gold(self, keys, current_time):
410 """Reject player selection if they do not have enough gold"""
411 dialogue = ["You don't have enough gold!"]
412 self.dialogue_box = self.make_dialogue_box(dialogue, 0)
413
414 if keys[pg.K_SPACE] and self.allow_input:
415 self.notify(c.CLICK2)
416 self.state = self.begin_new_transaction()
417 self.selection_arrow.rect.topleft = self.arrow_pos1
418 self.allow_input = False
419
420 if not keys[pg.K_SPACE]:
421 self.allow_input = True
422
423 def accept_purchase(self, keys, current_time):
424 """Accept purchase and confirm with message"""
425 self.dialogue_box = self.make_dialogue_box(self.accept_dialogue, 0)
426 self.gold_box = self.make_gold_box()
427
428 if keys[pg.K_SPACE] and self.allow_input:
429 self.notify(c.CLICK2)
430 self.state = self.begin_new_transaction()
431 self.selection_arrow.rect.topleft = self.arrow_pos1
432 self.allow_input = False
433
434 if not keys[pg.K_SPACE]:
435 self.allow_input = True
436
437 def accept_sale(self, keys, current_time):
438 """Confirm to player that item was sold"""
439 self.dialogue_box = self.make_dialogue_box(self.accept_sale_dialogue, 0)
440 self.gold_box = self.make_gold_box()
441
442 if keys[pg.K_SPACE] and self.allow_input:
443 self.notify(c.CLICK2)
444 self.state = self.begin_new_transaction()
445 self.selection_arrow.rect.topleft = self.arrow_pos1
446 self.allow_input = False
447
448 if not keys[pg.K_SPACE]:
449 self.allow_input = True
450
451
452 def has_item(self, keys, current_time):
453 """Tell player he has item already"""
454 dialogue = ["You have that item already."]
455 self.dialogue_box = self.make_dialogue_box(dialogue, 0)
456
457 if keys[pg.K_SPACE] and self.allow_input:
458 self.state = self.begin_new_transaction()
459 self.selection_arrow.rect.topleft = self.arrow_pos1
460 self.allow_input = False
461 self.notify(c.CLICK2)
462
463 if not keys[pg.K_SPACE]:
464 self.allow_input = True
465
466
467 def buy_sell(self, keys, current_time):
468 """Ask player if they want to buy or sell something"""
469 dialogue = ["Would you like to buy or sell an item?"]
470 choices = ['Buy', 'Sell', 'Leave']
471 self.dialogue_box = self.make_dialogue_box(dialogue, 0)
472 self.selection_box = self.make_selection_box(choices)
473 self.selection_arrow.rect.topleft = self.arrow_pos_list[self.arrow_index]
474
475 if keys[pg.K_DOWN] and self.allow_input:
476 if self.arrow_index < (len(self.arrow_pos_list) - 1):
477 self.arrow_index += 1
478 self.allow_input = False
479 self.notify(c.CLICK)
480
481 elif keys[pg.K_UP] and self.allow_input:
482 if self.arrow_index > 0:
483 self.arrow_index -= 1
484 self.allow_input = False
485 self.notify(c.CLICK)
486 elif keys[pg.K_SPACE] and self.allow_input:
487 if self.arrow_index == 0:
488 self.state = 'select'
489 self.allow_input = False
490 self.arrow_index = 0
491 elif self.arrow_index == 1:
492 if self.check_for_sellable_items():
493 self.state = 'sell'
494 self.allow_input = False
495 self.arrow_index = 0
496 else:
497 self.state = 'cantsell'
498 self.allow_input = False
499 self.arrow_index = 0
500 else:
501 self.level.state = 'transition out'
502 self.game_data['last state'] = self.level.name
503
504 self.arrow_index = 0
505 self.notify(c.CLICK2)
506
507 if not keys[pg.K_SPACE] and not keys[pg.K_DOWN] and not keys[pg.K_UP]:
508 self.allow_input = True
509
510 def check_for_sellable_items(self):
511 """Check for sellable items"""
512 for item in self.player_inventory:
513 if item in self.sellable_items:
514 return True
515 else:
516 return False
517
518 def sell_items(self, keys, current_time):
519 """Have player select items to sell"""
520 dialogue = ["What would you like to sell?"]
521 choices = []
522 item_list = []
523 for item in self.items:
524 if item['type'] in self.player_inventory:
525 name = item['type']
526 price = " (" + str(item['price'] / 2) + " gold)"
527 choices.append(name + price)
528 item_list.append(name)
529 choices.append('Cancel')
530 self.dialogue_box = self.make_dialogue_box(dialogue, 0)
531 self.selection_box = self.make_selection_box(choices)
532
533 if len(choices) == 2:
534 self.selection_arrow.rect.topleft = self.two_arrow_pos_list[self.arrow_index]
535 elif len(choices) == 3:
536 self.selection_arrow.rect.topleft = self.arrow_pos_list[self.arrow_index]
537
538 if keys[pg.K_DOWN] and self.allow_input:
539 if self.arrow_index < (len(self.arrow_pos_list) - 1):
540 self.arrow_index += 1
541 self.allow_input = False
542 self.notify(c.CLICK)
543 elif keys[pg.K_UP] and self.allow_input:
544 if self.arrow_index > 0:
545 self.arrow_index -= 1
546 self.allow_input = False
547 self.notify(c.CLICK)
548 elif keys[pg.K_SPACE] and self.allow_input:
549 if self.arrow_index == 0:
550 self.state = 'confirmsell'
551 self.allow_input = False
552 for item in self.items:
553 if item['type'] == item_list[0]:
554 self.item_to_be_sold = item
555
556 elif self.arrow_index == 1 and len(choices) == 3:
557 self.state = 'confirmsell'
558 self.allow_input = False
559 for item in self.items:
560 if item['type'] == item_list[1]:
561 self.item_to_be_sold = item
562 else:
563 self.state = 'buysell'
564 self.allow_input = False
565 self.arrow_index = 0
566 self.notify(c.CLICK2)
567
568 if not keys[pg.K_SPACE] and not keys[pg.K_DOWN] and not keys[pg.K_UP]:
569 self.allow_input = True
570
571
572 def cant_sell(self, keys, current_time):
573 """Do not allow player to sell anything"""
574 dialogue = ["You don't have anything to sell!"]
575 self.dialogue_box = self.make_dialogue_box(dialogue, 0)
576
577 if keys[pg.K_SPACE] and self.allow_input:
578 self.state = 'buysell'
579 self.allow_input = False
580 self.notify(c.CLICK2)
581
582
583 if not keys[pg.K_SPACE]:
584 self.allow_input = True
585
586 def cant_sell_equipped_weapon(self, keys, *args):
587 """
588 Do not sell weapon the player has equipped.
589 """
590 dialogue = ["You can't sell an equipped weapon."]
591 self.dialogue_box = self.make_dialogue_box(dialogue, 0)
592
593 if keys[pg.K_SPACE] and self.allow_input:
594 self.state = 'buysell'
595 self.allow_input = False
596 self.notify(c.CLICK2)
597
598 if not keys[pg.K_SPACE]:
599 self.allow_input = True
600
601 def cant_sell_equipped_armor(self, keys, *args):
602 """
603 Do not sell armor the player has equipped.
604 """
605 dialogue = ["You can't sell equipped armor."]
606 self.dialogue_box = self.make_dialogue_box(dialogue, 0)
607
608 if keys[pg.K_SPACE] and self.allow_input:
609 self.state = 'buysell'
610 self.allow_input = False
611
612 if not keys[pg.K_SPACE]:
613 self.allow_input = True
614
615
616
617 def update(self, keys, current_time):
618 """Updates the shop GUI"""
619 state_function = self.state_dict[self.state]
620 state_function(keys, current_time)
621
622
623 def draw(self, surface):
624 """Draw GUI to level surface"""
625 state_list1 = ['dialogue', 'reject', 'accept', 'hasitem']
626 state_list2 = ['select', 'confirmpurchase', 'buysell', 'sell', 'confirmsell']
627
628 surface.blit(self.dialogue_box.image, self.dialogue_box.rect)
629 surface.blit(self.gold_box.image, self.gold_box.rect)
630 if self.state in state_list2:
631 surface.blit(self.selection_box.image, self.selection_box.rect)
632 surface.blit(self.selection_arrow.image, self.selection_arrow.rect)
633