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