1 /**
2 * @file
3 * @brief Shop keeper functions.
4 **/
5
6 #include "AppHdr.h"
7
8 #include "shopping.h"
9
10 #include <cstdio>
11 #include <cstdlib>
12 #include <cstring>
13
14 #include "artefact.h"
15 #include "branch.h"
16 #include "cio.h"
17 #include "colour.h"
18 #include "describe.h"
19 #include "dgn-overview.h"
20 #include "english.h"
21 #include "env.h"
22 #include "files.h"
23 #include "invent.h"
24 #include "item-name.h"
25 #include "item-prop.h"
26 #include "item-status-flag-type.h"
27 #include "items.h"
28 #include "libutil.h"
29 #include "menu.h"
30 #include "message.h"
31 #include "notes.h"
32 #include "options.h"
33 #include "output.h"
34 #include "player.h"
35 #include "prompt.h"
36 #include "spl-book.h"
37 #include "stash.h"
38 #include "state.h"
39 #include "stepdown.h"
40 #include "stringutil.h"
41 #include "tag-version.h"
42 #ifdef USE_TILE_LOCAL
43 #include "tilereg-crt.h"
44 #endif
45 #include "travel.h"
46 #include "unicode.h"
47 #include "unwind.h"
48
49 ShoppingList shopping_list;
50
_shop_get_item_value(const item_def & item,int greed,bool id)51 static int _shop_get_item_value(const item_def& item, int greed, bool id)
52 {
53 int result = (greed * item_value(item, id) / 10);
54
55 return max(result, 1);
56 }
57
item_price(const item_def & item,const shop_struct & shop)58 int item_price(const item_def& item, const shop_struct& shop)
59 {
60 return _shop_get_item_value(item, shop.greed, shoptype_identifies_stock(shop.type));
61 }
62
63 // This probably still needs some work. Rings used to be the only
64 // artefacts which had a change in price, and that value corresponds
65 // to returning 50 from this function. Good artefacts will probably
66 // be returning just over 30 right now. Note that this isn't used
67 // as a multiple, its used in the old ring way: 7 * ret is added to
68 // the price of the artefact. -- bwr
artefact_value(const item_def & item)69 int artefact_value(const item_def &item)
70 {
71 ASSERT(is_artefact(item));
72
73 int ret = 10;
74 artefact_properties_t prop;
75 artefact_properties(item, prop);
76
77 // Brands are already accounted for via existing ego checks
78
79 // This should probably be more complex... but this isn't so bad:
80 ret += 6 * prop[ARTP_AC]
81 + 6 * prop[ARTP_EVASION]
82 + 4 * prop[ARTP_SHIELDING]
83 + 6 * prop[ARTP_SLAYING]
84 + 3 * prop[ARTP_STRENGTH]
85 + 3 * prop[ARTP_INTELLIGENCE]
86 + 3 * prop[ARTP_DEXTERITY]
87 + 4 * prop[ARTP_HP]
88 + 3 * prop[ARTP_MAGICAL_POWER];
89
90 // These resistances have meaningful levels
91 if (prop[ARTP_FIRE] > 0)
92 ret += 5 + 5 * (prop[ARTP_FIRE] * prop[ARTP_FIRE]);
93 else if (prop[ARTP_FIRE] < 0)
94 ret -= 10;
95
96 if (prop[ARTP_COLD] > 0)
97 ret += 5 + 5 * (prop[ARTP_COLD] * prop[ARTP_COLD]);
98 else if (prop[ARTP_COLD] < 0)
99 ret -= 10;
100
101 if (prop[ARTP_WILLPOWER] > 0)
102 ret += 4 + 4 * prop[ARTP_WILLPOWER];
103 else if (prop[ARTP_WILLPOWER] < 0)
104 ret -= 6;
105
106 if (prop[ARTP_NEGATIVE_ENERGY] > 0)
107 ret += 3 + 3 * (prop[ARTP_NEGATIVE_ENERGY] * prop[ARTP_NEGATIVE_ENERGY]);
108
109 // Discount Stlth-, charge for Stlth+
110 ret += 2 * prop[ARTP_STEALTH];
111 // Stlth+ costs more than Stlth- cheapens
112 if (prop[ARTP_STEALTH] > 0)
113 ret += 2 * prop[ARTP_STEALTH];
114
115 // only one meaningful level:
116 if (prop[ARTP_POISON])
117 ret += 6;
118
119 // only one meaningful level (hard to get):
120 if (prop[ARTP_ELECTRICITY])
121 ret += 10;
122
123 // only one meaningful level (hard to get):
124 if (prop[ARTP_RCORR])
125 ret += 8;
126
127 // only one meaningful level (hard to get):
128 if (prop[ARTP_RMUT])
129 ret += 8;
130
131 if (prop[ARTP_SEE_INVISIBLE])
132 ret += 6;
133
134 // abilities:
135 if (prop[ARTP_FLY])
136 ret += 3;
137
138 if (prop[ARTP_BLINK])
139 ret += 10;
140
141 if (prop[ARTP_BERSERK])
142 ret += 5;
143
144 if (prop[ARTP_INVISIBLE])
145 ret += 10;
146
147 if (prop[ARTP_ANGRY])
148 ret -= 3;
149
150 if (prop[ARTP_CAUSE_TELEPORTATION])
151 ret -= 3;
152
153 if (prop[ARTP_NOISE])
154 ret -= 5;
155
156 if (prop[ARTP_PREVENT_TELEPORTATION])
157 ret -= 8;
158
159 if (prop[ARTP_PREVENT_SPELLCASTING])
160 ret -= 10;
161
162 if (prop[ARTP_CONTAM])
163 ret -= 8;
164
165 if (prop[ARTP_CORRODE])
166 ret -= 8;
167
168 if (prop[ARTP_DRAIN])
169 ret -= 8;
170
171 if (prop[ARTP_SLOW])
172 ret -= 8;
173
174 if (prop[ARTP_FRAGILE])
175 ret -= 8;
176
177 if (prop[ARTP_RMSL])
178 ret += 20;
179
180 if (prop[ARTP_CLARITY])
181 ret += 20;
182
183 if (prop[ARTP_ARCHMAGI])
184 ret += 20;
185
186 return (ret > 0) ? ret : 0;
187 }
188
item_value(item_def item,bool ident)189 unsigned int item_value(item_def item, bool ident)
190 {
191 // Note that we pass item in by value, since we want a local
192 // copy to mangle as necessary.
193 item.flags = (ident) ? (item.flags | ISFLAG_IDENT_MASK) : (item.flags);
194
195 if (is_unrandom_artefact(item)
196 && item_ident(item, ISFLAG_KNOW_PROPERTIES))
197 {
198 const unrandart_entry *entry = get_unrand_entry(item.unrand_idx);
199 if (entry->value != 0)
200 return entry->value;
201 }
202
203 int valued = 0;
204
205 switch (item.base_type)
206 {
207 case OBJ_WEAPONS:
208 valued += weapon_base_price((weapon_type)item.sub_type);
209
210 if (item_type_known(item))
211 {
212 switch (get_weapon_brand(item))
213 {
214 case SPWPN_NORMAL:
215 default: // randart
216 valued *= 10;
217 break;
218
219 case SPWPN_SPEED:
220 case SPWPN_VAMPIRISM:
221 case SPWPN_ANTIMAGIC:
222 valued *= 30;
223 break;
224
225 case SPWPN_DISTORTION:
226 case SPWPN_ELECTROCUTION:
227 case SPWPN_PAIN:
228 case SPWPN_ACID: // Unrand-only.
229 case SPWPN_PENETRATION: // Unrand-only.
230 case SPWPN_SPECTRAL:
231 valued *= 25;
232 break;
233
234 case SPWPN_CHAOS:
235 case SPWPN_DRAINING:
236 case SPWPN_FLAMING:
237 case SPWPN_FREEZING:
238 case SPWPN_HOLY_WRATH:
239 valued *= 18;
240 break;
241
242 case SPWPN_VORPAL:
243 valued *= 15;
244 break;
245
246 case SPWPN_PROTECTION:
247 case SPWPN_VENOM:
248 valued *= 12;
249 break;
250 }
251
252 valued /= 10;
253 }
254
255 if (item_ident(item, ISFLAG_KNOW_PLUSES))
256 valued += 50 * item.plus;
257
258 if (is_artefact(item))
259 {
260 if (item_type_known(item))
261 valued += (7 * artefact_value(item));
262 else
263 valued += 50;
264 }
265 else if (item_type_known(item)
266 && get_equip_desc(item) != 0) // ???
267 {
268 valued += 20;
269 }
270 else if (!(item.flags & ISFLAG_IDENT_MASK)
271 && (get_equip_desc(item) != 0))
272 {
273 valued += 30; // un-id'd "glowing" - arbitrary added cost
274 }
275
276 break;
277
278 case OBJ_MISSILES: // ammunition
279 valued += missile_base_price((missile_type)item.sub_type);
280
281 if (item_type_known(item))
282 {
283 switch (get_ammo_brand(item))
284 {
285 case SPMSL_NORMAL:
286 default:
287 valued *= 10;
288 break;
289
290 case SPMSL_CHAOS:
291 valued *= 40;
292 break;
293
294 case SPMSL_CURARE:
295 case SPMSL_BLINDING:
296 case SPMSL_SILVER:
297 #if TAG_MAJOR_VERSION == 34
298 case SPMSL_PARALYSIS:
299 case SPMSL_PENETRATION:
300 case SPMSL_STEEL:
301 #endif
302 case SPMSL_DISPERSAL:
303 valued *= 30;
304 break;
305
306 #if TAG_MAJOR_VERSION == 34
307 case SPMSL_FLAME:
308 case SPMSL_FROST:
309 case SPMSL_SLEEP:
310 case SPMSL_CONFUSION:
311 valued *= 25;
312 break;
313 #endif
314
315 case SPMSL_POISONED:
316 #if TAG_MAJOR_VERSION == 34
317 case SPMSL_RETURNING:
318 case SPMSL_EXPLODING:
319 case SPMSL_SLOW:
320 case SPMSL_SICKNESS:
321 #endif
322 case SPMSL_FRENZY:
323 valued *= 20;
324 break;
325 }
326
327 valued /= 10;
328 }
329 break;
330
331 case OBJ_ARMOUR:
332 valued += armour_base_price((armour_type)item.sub_type);
333
334 if (item_type_known(item))
335 {
336 const int sparm = get_armour_ego_type(item);
337 switch (sparm)
338 {
339 case SPARM_ARCHMAGI:
340 case SPARM_RESISTANCE:
341 valued += 250;
342 break;
343
344 case SPARM_COLD_RESISTANCE:
345 case SPARM_DEXTERITY:
346 case SPARM_FIRE_RESISTANCE:
347 case SPARM_SEE_INVISIBLE:
348 case SPARM_INTELLIGENCE:
349 case SPARM_FLYING:
350 case SPARM_STEALTH:
351 case SPARM_STRENGTH:
352 case SPARM_INVISIBILITY:
353 case SPARM_WILLPOWER:
354 case SPARM_PROTECTION:
355 case SPARM_ARCHERY:
356 case SPARM_REPULSION:
357 case SPARM_PRESERVATION:
358 case SPARM_SHADOWS:
359 case SPARM_RAMPAGING:
360 valued += 50;
361 break;
362
363 case SPARM_POSITIVE_ENERGY:
364 case SPARM_POISON_RESISTANCE:
365 case SPARM_REFLECTION:
366 case SPARM_SPIRIT_SHIELD:
367 case SPARM_HARM:
368 valued += 20;
369 break;
370
371 case SPARM_PONDEROUSNESS:
372 valued -= 250;
373 break;
374 }
375 }
376
377 if (item_ident(item, ISFLAG_KNOW_PLUSES))
378 valued += 50 * item.plus;
379
380 if (is_artefact(item))
381 {
382 if (item_type_known(item))
383 valued += (7 * artefact_value(item));
384 else
385 valued += 50;
386 }
387 else if (item_type_known(item) && get_equip_desc(item) != 0)
388 valued += 20; // ???
389 else if (!(item.flags & ISFLAG_IDENT_MASK)
390 && (get_equip_desc(item) != 0))
391 {
392 valued += 30; // un-id'd "glowing" - arbitrary added cost
393 }
394
395 break;
396
397 case OBJ_WANDS:
398 if (!item_type_known(item))
399 valued += 40;
400 else
401 {
402 // true if the wand is of a good type, a type with significant
403 // inherent value even when empty. Good wands are less expensive
404 // per charge.
405 bool good = false;
406 switch (item.sub_type)
407 {
408 case WAND_ACID:
409 case WAND_DIGGING:
410 valued += 80;
411 good = true;
412 break;
413
414 case WAND_ICEBLAST:
415 case WAND_MINDBURST:
416 valued += 40;
417 good = true;
418 break;
419
420 case WAND_CHARMING:
421 case WAND_POLYMORPH:
422 case WAND_PARALYSIS:
423 valued += 20;
424 break;
425
426 case WAND_FLAME:
427 valued += 10;
428 break;
429
430 default:
431 valued += 6;
432 break;
433 }
434
435 if (item_ident(item, ISFLAG_KNOW_PLUSES))
436 {
437 if (good) valued += (valued * item.plus) / 4;
438 else valued += (valued * item.plus) / 2;
439 }
440 }
441 break;
442
443 case OBJ_POTIONS:
444 if (!item_type_known(item))
445 valued += 9;
446 else
447 {
448 switch (item.sub_type)
449 {
450 case POT_EXPERIENCE:
451 valued += 500;
452 break;
453
454 case POT_RESISTANCE:
455 case POT_HASTE:
456 valued += 100;
457 break;
458
459 case POT_MAGIC:
460 case POT_INVISIBILITY:
461 case POT_CANCELLATION:
462 case POT_AMBROSIA:
463 case POT_MUTATION:
464 valued += 80;
465 break;
466
467 case POT_BERSERK_RAGE:
468 case POT_HEAL_WOUNDS:
469 valued += 50;
470 break;
471
472 case POT_MIGHT:
473 case POT_BRILLIANCE:
474 valued += 40;
475 break;
476
477 case POT_CURING:
478 case POT_LIGNIFY:
479 case POT_ATTRACTION:
480 case POT_FLIGHT:
481 valued += 30;
482 break;
483
484 case POT_DEGENERATION:
485 valued += 10;
486 break;
487
488 CASE_REMOVED_POTIONS(item.sub_type)
489 }
490 }
491 break;
492
493 case OBJ_SCROLLS:
494 if (!item_type_known(item))
495 valued += 10;
496 else
497 {
498 switch (item.sub_type)
499 {
500 case SCR_ACQUIREMENT:
501 valued += 520;
502 break;
503
504 case SCR_BRAND_WEAPON:
505 valued += 200;
506 break;
507
508 case SCR_SUMMONING:
509 valued += 95;
510 break;
511
512 case SCR_BLINKING:
513 case SCR_ENCHANT_ARMOUR:
514 case SCR_ENCHANT_WEAPON:
515 case SCR_TORMENT:
516 case SCR_HOLY_WORD:
517 case SCR_SILENCE:
518 case SCR_VULNERABILITY:
519 valued += 75;
520 break;
521
522 case SCR_AMNESIA:
523 case SCR_FEAR:
524 case SCR_IMMOLATION:
525 case SCR_MAGIC_MAPPING:
526 valued += 35;
527 break;
528
529 case SCR_TELEPORTATION:
530 valued += 30;
531 break;
532
533 case SCR_FOG:
534 case SCR_IDENTIFY:
535 #if TAG_MAJOR_VERSION == 34
536 case SCR_CURSE_ARMOUR:
537 case SCR_CURSE_WEAPON:
538 case SCR_CURSE_JEWELLERY:
539 #endif
540 valued += 20;
541 break;
542
543 case SCR_NOISE:
544 valued += 10;
545 break;
546 }
547 }
548 break;
549
550 case OBJ_JEWELLERY:
551 if (!item_type_known(item))
552 valued += 50;
553 else
554 {
555 // Variable-strength rings.
556 if (jewellery_type_has_plusses(item.sub_type))
557 {
558 // Formula: price = kn(n+1) / 2, where k depends on the subtype,
559 // n is the power. (The base variable is equal to 2n.)
560 int base = 0;
561 int coefficient = 0;
562 if (item.sub_type == RING_SLAYING)
563 base = 3 * item.plus;
564 else
565 base = 2 * item.plus;
566
567 switch (item.sub_type)
568 {
569 case RING_SLAYING:
570 case RING_PROTECTION:
571 case RING_EVASION:
572 coefficient = 40;
573 break;
574 case RING_STRENGTH:
575 case RING_DEXTERITY:
576 case RING_INTELLIGENCE:
577 coefficient = 30;
578 break;
579 default:
580 break;
581 }
582
583 if (base <= 0)
584 valued += 25 * base;
585 else
586 valued += (coefficient * base * (base + 1)) / 8;
587 }
588 else
589 {
590 switch (item.sub_type)
591 {
592 case AMU_FAITH:
593 valued += 400;
594 break;
595
596 case RING_WIZARDRY:
597 case AMU_REGENERATION:
598 case AMU_GUARDIAN_SPIRIT:
599 case AMU_MANA_REGENERATION:
600 case AMU_ACROBAT:
601 case AMU_REFLECTION:
602 valued += 300;
603 break;
604
605 case RING_FIRE:
606 case RING_ICE:
607 case RING_PROTECTION_FROM_COLD:
608 case RING_PROTECTION_FROM_FIRE:
609 case RING_WILLPOWER:
610 valued += 250;
611 break;
612
613 case RING_MAGICAL_POWER:
614 case RING_LIFE_PROTECTION:
615 case RING_POISON_RESISTANCE:
616 case RING_RESIST_CORROSION:
617 valued += 200;
618 break;
619
620 case RING_STEALTH:
621 case RING_FLIGHT:
622 valued += 175;
623 break;
624
625 case RING_SEE_INVISIBLE:
626 valued += 150;
627 break;
628
629 case AMU_NOTHING:
630 valued += 75;
631 break;
632 }
633 }
634
635 if (is_artefact(item))
636 {
637 // in this branch we're guaranteed to know
638 // the item type!
639 if (valued < 0)
640 valued = (artefact_value(item) - 5) * 7;
641 else
642 valued += artefact_value(item) * 7;
643 }
644
645 // Hard minimum, as it's worth 20 to ID a ring.
646 valued = max(20, valued);
647 }
648 break;
649
650 case OBJ_MISCELLANY:
651 switch (item.sub_type)
652 {
653 case MISC_HORN_OF_GERYON:
654 case MISC_ZIGGURAT:
655 valued += 5000;
656 break;
657
658 case MISC_PHIAL_OF_FLOODS:
659 case MISC_TIN_OF_TREMORSTONES:
660 case MISC_BOX_OF_BEASTS:
661 case MISC_CONDENSER_VANE:
662 case MISC_PHANTOM_MIRROR:
663 valued += 400;
664 break;
665
666 case MISC_LIGHTNING_ROD:
667 valued += 300;
668 break;
669
670 case MISC_XOMS_CHESSBOARD:
671 default:
672 valued += 200;
673 }
674 break;
675
676 case OBJ_BOOKS:
677 {
678 valued = 0;
679 const book_type book = static_cast<book_type>(item.sub_type);
680 if (book == BOOK_MANUAL)
681 return 800;
682 #if TAG_MAJOR_VERSION == 34
683 if (book == BOOK_BUGGY_DESTRUCTION)
684 break;
685 #endif
686 int levels = 0;
687 const vector<spell_type> spells = spells_in_book(item);
688 for (spell_type spell : spells)
689 levels += spell_difficulty(spell);
690 // Level 9 spells are worth 4x level 1 spells.
691 valued += levels * 20 + spells.size() * 20;
692 break;
693 }
694
695 case OBJ_STAVES:
696 valued = item_type_known(item) ? 250 : 120;
697 break;
698
699 case OBJ_ORBS:
700 valued = 250000;
701 break;
702
703 case OBJ_RUNES:
704 valued = 10000;
705 break;
706
707 default:
708 break;
709 } // end switch
710
711 if (valued < 1)
712 valued = 1;
713
714 valued = stepdown_value(valued, 1000, 1000, 10000, 10000);
715
716 return item.quantity * valued;
717 }
718
is_worthless_consumable(const item_def & item)719 bool is_worthless_consumable(const item_def &item)
720 {
721 switch (item.base_type)
722 {
723 case OBJ_POTIONS:
724 switch (item.sub_type)
725 {
726 // Blood potions are worthless because they are easy to make.
727 case POT_DEGENERATION:
728 return true;
729 default:
730 return false;
731 CASE_REMOVED_POTIONS(item.sub_type)
732 }
733 case OBJ_SCROLLS:
734 switch (item.sub_type)
735 {
736 #if TAG_MAJOR_VERSION == 34
737 case SCR_CURSE_ARMOUR:
738 case SCR_CURSE_WEAPON:
739 case SCR_CURSE_JEWELLERY:
740 #endif
741 case SCR_NOISE:
742 return true;
743 default:
744 return false;
745 }
746
747 // Only consumables are worthless.
748 default:
749 return false;
750 }
751 }
752
_count_identical(const vector<item_def> & stock,const item_def & item)753 static int _count_identical(const vector<item_def>& stock, const item_def& item)
754 {
755 int count = 0;
756 for (const item_def& other : stock)
757 if (ShoppingList::items_are_same(item, other))
758 count++;
759
760 return count;
761 }
762
763 /** Buy an item from a shop!
764 *
765 * @param shop the shop to purchase from.
766 * @param pos where the shop is located
767 * @param index the index of the item to buy in shop.stock
768 * @returns true if it went in your inventory, false otherwise.
769 */
_purchase(shop_struct & shop,const level_pos & pos,int index)770 static bool _purchase(shop_struct& shop, const level_pos& pos, int index)
771 {
772 item_def item = shop.stock[index]; // intentional copy
773 const int cost = item_price(item, shop);
774 shop.stock.erase(shop.stock.begin() + index);
775
776 // Remove from shopping list if it's unique
777 // (i.e., if the shop has multiple scrolls of
778 // identify, don't remove the other scrolls
779 // from the shopping list if there's any
780 // left).
781 if (shopping_list.is_on_list(item, &pos)
782 && _count_identical(shop.stock, item) == 0)
783 {
784 shopping_list.del_thing(item, &pos);
785 }
786
787 // Take a note of the purchase.
788 take_note(Note(NOTE_BUY_ITEM, cost, 0,
789 item.name(DESC_A).c_str()));
790
791 // But take no further similar notes.
792 item.flags |= ISFLAG_NOTED_GET;
793
794 if (fully_identified(item))
795 item.flags |= ISFLAG_NOTED_ID;
796
797 you.del_gold(cost);
798
799 you.attribute[ATTR_PURCHASES] += cost;
800
801 origin_purchased(item);
802
803 if (shoptype_identifies_stock(shop.type)
804 || item_type_is_equipment(item.base_type))
805 {
806 // Identify the item and its type.
807 // This also takes the ID note if necessary.
808 identify_item(item);
809 }
810
811 // Shopkeepers will place goods you can't carry outside the shop.
812 if (item_is_stationary(item)
813 || !move_item_to_inv(item))
814 {
815 copy_item_to_grid(item, shop.pos);
816 return false;
817 }
818 return true;
819 }
820
_hyphenated_letters(int how_many,char first)821 static string _hyphenated_letters(int how_many, char first)
822 {
823 string s = "<w>";
824 s += first;
825 s += "</w>";
826 if (how_many > 1)
827 {
828 s += "-<w>";
829 s += first + how_many - 1;
830 s += "</w>";
831 }
832 return s;
833 }
834
835 enum shopping_order
836 {
837 ORDER_TYPE,
838 ORDER_DEFAULT = ORDER_TYPE,
839 ORDER_PRICE,
840 ORDER_ALPHABETICAL,
841 NUM_ORDERS
842 };
843
844 static const char * const shopping_order_names[NUM_ORDERS] =
845 {
846 "type", "price", "name"
847 };
848
operator ++(shopping_order & x)849 static shopping_order operator++(shopping_order &x)
850 {
851 x = static_cast<shopping_order>(x + 1);
852 if (x == NUM_ORDERS)
853 x = ORDER_DEFAULT;
854 return x;
855 }
856
857 class ShopMenu : public InvMenu
858 {
859 friend class ShopEntry;
860
861 shop_struct& shop;
862 shopping_order order = ORDER_DEFAULT;
863 level_pos pos;
864 bool can_purchase;
865
866 int selected_cost() const;
867
868 void init_entries();
869 void update_help();
870 void resort();
871 void purchase_selected();
872
873 virtual bool process_key(int keyin) override;
874
875 public:
876 bool bought_something = false;
877
878 ShopMenu(shop_struct& _shop, const level_pos& _pos, bool _can_purchase);
879 };
880
881 class ShopEntry : public InvEntry
882 {
883 ShopMenu& menu;
884
get_text(bool need_cursor=false) const885 string get_text(bool need_cursor = false) const override
886 {
887 need_cursor = need_cursor && show_cursor;
888 const int cost = item_price(*item, menu.shop);
889 const int total_cost = menu.selected_cost();
890 const bool on_list = shopping_list.is_on_list(*item, &menu.pos);
891 // Colour stock as follows:
892 // * lightcyan, if on the shopping list and not selected.
893 // * lightred, if you can't buy all you selected.
894 // * lightgreen, if this item is purchasable along with your selections
895 // * red, if this item is not purchasable even by itself.
896 // * yellow, if this item would be purchasable if you deselected
897 // something else.
898
899 // Is this too complicated? (jpeg)
900 const colour_t keycol =
901 !selected() && on_list ? LIGHTCYAN :
902 selected() && total_cost > you.gold ? LIGHTRED :
903 cost <= you.gold - total_cost ? LIGHTGREEN :
904 cost > you.gold ? RED :
905 YELLOW;
906 const string keystr = colour_to_str(keycol);
907 const string itemstr =
908 colour_to_str(menu_colour(text, item_prefix(*item), tag));
909 return make_stringf(" <%s>%c%c%c%c</%s><%s>%4d gold %s%s</%s>",
910 keystr.c_str(),
911 hotkeys[0],
912 need_cursor ? '[' : ' ',
913 selected() ? '+' : on_list ? '$' : '-',
914 need_cursor ? ']' : ' ',
915 keystr.c_str(),
916 itemstr.c_str(),
917 cost,
918 text.c_str(),
919 shop_item_unknown(*item) ? " (unknown)" : "",
920 itemstr.c_str());
921 }
922
select(int qty=-1)923 virtual void select(int qty = -1) override
924 {
925 if (shopping_list.is_on_list(*item, &menu.pos) && qty != 0)
926 shopping_list.del_thing(*item, &menu.pos);
927
928 InvEntry::select(qty);
929 }
930 public:
ShopEntry(const item_def & i,ShopMenu & m)931 ShopEntry(const item_def& i, ShopMenu& m)
932 : InvEntry(i),
933 menu(m)
934 {
935 show_background = false;
936 }
937 };
938
ShopMenu(shop_struct & _shop,const level_pos & _pos,bool _can_purchase)939 ShopMenu::ShopMenu(shop_struct& _shop, const level_pos& _pos, bool _can_purchase)
940 : InvMenu(MF_MULTISELECT | MF_NO_SELECT_QTY | MF_QUIET_SELECT
941 | MF_ALWAYS_SHOW_MORE | MF_ALLOW_FORMATTING),
942 shop(_shop),
943 pos(_pos),
944 can_purchase(_can_purchase)
945 {
946 menu_action = can_purchase ? ACT_EXECUTE : ACT_EXAMINE;
947 set_flags(get_flags() & ~MF_USE_TWO_COLUMNS);
948
949 set_tag("shop");
950
951 init_entries();
952 resort();
953
954 update_help();
955
956 set_title("Welcome to " + shop_name(shop) + "! What would you "
957 "like to do?");
958 }
959
init_entries()960 void ShopMenu::init_entries()
961 {
962 menu_letter ckey = 'a';
963 for (item_def& item : shop.stock)
964 {
965 auto newentry = make_unique<ShopEntry>(item, *this);
966 newentry->hotkeys.clear();
967 newentry->add_hotkey(ckey++);
968 add_entry(move(newentry));
969 }
970 }
971
selected_cost() const972 int ShopMenu::selected_cost() const
973 {
974 int cost = 0;
975 for (auto item : selected_entries())
976 cost += item_price(*dynamic_cast<ShopEntry*>(item)->item, shop);
977 return cost;
978 }
979
update_help()980 void ShopMenu::update_help()
981 {
982 string top_line = make_stringf("<yellow>You have %d gold piece%s.",
983 you.gold,
984 you.gold != 1 ? "s" : "");
985 const int total_cost = selected_cost();
986 if (total_cost > you.gold)
987 {
988 top_line += "<lightred>";
989 top_line +=
990 make_stringf(" You are short %d gold piece%s for the purchase.",
991 total_cost - you.gold,
992 (total_cost - you.gold != 1) ? "s" : "");
993 top_line += "</lightred>";
994 }
995 else if (total_cost)
996 {
997 top_line +=
998 make_stringf(" After the purchase, you will have %d gold piece%s.",
999 you.gold - total_cost,
1000 (you.gold - total_cost != 1) ? "s" : "");
1001 }
1002 top_line += "</yellow>";
1003
1004 // Ensure length >= 80ch, which prevents the local tiles menu from resizing
1005 // as the player selects/deselects entries. Blegh..
1006 int top_line_width = strwidth(formatted_string::parse_string(top_line).tostring());
1007 top_line += string(max(0, 80 - top_line_width), ' ') + '\n';
1008
1009 set_more(formatted_string::parse_string(top_line + make_stringf(
1010 //You have 0 gold pieces.
1011 //[Esc/R-Click] exit [!] buy|examine items [a-i] select item for purchase
1012 //[/] sort (default) [Enter] make purchase [A-I] put item on shopping list
1013 #if defined(USE_TILE) && !defined(TOUCH_UI)
1014 "[<w>Esc</w>/<w>R-Click</w>] exit "
1015 #else
1016 // "/R-Click"
1017 "[<w>Esc</w>] exit "
1018 #endif
1019 "%s [%s] %s\n"
1020 "[<w>/</w>] sort (%s)%s %s [%s] put item on shopping list",
1021 !can_purchase ? " " " " " " " " " " :
1022 menu_action == ACT_EXECUTE ? "[<w>!</w>] <w>buy</w>|examine items" :
1023 "[<w>!</w>] buy|<w>examine</w> items",
1024 _hyphenated_letters(item_count(), 'a').c_str(),
1025 menu_action == ACT_EXECUTE ? "select item for purchase" : "examine item",
1026 shopping_order_names[order],
1027 // strwidth("default")
1028 string(7 - strwidth(shopping_order_names[order]), ' ').c_str(),
1029 !can_purchase ? " " " " " " :
1030 "[<w>Enter</w>] make purchase",
1031 _hyphenated_letters(item_count(), 'A').c_str())));
1032 }
1033
purchase_selected()1034 void ShopMenu::purchase_selected()
1035 {
1036 bool buying_from_list = false;
1037 vector<MenuEntry*> selected = selected_entries();
1038 int cost = selected_cost();
1039 if (selected.empty())
1040 {
1041 ASSERT(cost == 0);
1042 buying_from_list = true;
1043 for (auto item : items)
1044 {
1045 const item_def& it = *dynamic_cast<ShopEntry*>(item)->item;
1046 if (shopping_list.is_on_list(it, &pos))
1047 {
1048 selected.push_back(item);
1049 cost += item_price(it, shop);
1050 }
1051 }
1052 }
1053 if (selected.empty())
1054 return;
1055 const string col = colour_to_str(channel_to_colour(MSGCH_PROMPT));
1056 update_help();
1057 const formatted_string old_more = more;
1058 if (cost > you.gold)
1059 {
1060 more = formatted_string::parse_string(make_stringf(
1061 "<%s>You don't have enough money.</%s>\n",
1062 col.c_str(),
1063 col.c_str()));
1064 more += old_more;
1065 update_more();
1066 return;
1067 }
1068 more = formatted_string::parse_string(make_stringf(
1069 "<%s>Purchase items%s for %d gold? (%s/N)</%s>\n",
1070 col.c_str(),
1071 buying_from_list ? " in shopping list" : "",
1072 cost,
1073 Options.easy_confirm == easy_confirm_type::none ? "Y" : "y",
1074 col.c_str()));
1075 more += old_more;
1076 update_more();
1077 if (!yesno(nullptr, true, 'n', false, false, true))
1078 {
1079 more = old_more;
1080 update_more();
1081 return;
1082 }
1083 sort(begin(selected), end(selected),
1084 [](MenuEntry* a, MenuEntry* b)
1085 {
1086 return a->data > b->data;
1087 });
1088 vector<int> bought_indices;
1089 int outside_items = 0;
1090
1091 // Store last_pickup in case we need to restore it.
1092 // Then clear it to fill with items purchased.
1093 map<int,int> tmp_l_p = you.last_pickup;
1094 you.last_pickup.clear();
1095
1096 // Will iterate backwards through the shop (because of the earlier sort).
1097 // This means we can erase() from shop.stock (since it only invalidates
1098 // pointers to later elements), but nothing else.
1099 for (auto entry : selected)
1100 {
1101 const int i = static_cast<item_def*>(entry->data) - shop.stock.data();
1102 item_def& item(shop.stock[i]);
1103 // Can happen if the price changes due to id status
1104 if (item_price(item, shop) > you.gold)
1105 continue;
1106 const int quant = item.quantity;
1107
1108 if (!_purchase(shop, pos, i))
1109 {
1110 // The purchased item didn't fit into your
1111 // knapsack.
1112 outside_items += quant;
1113 }
1114
1115 bought_indices.push_back(i);
1116 bought_something = true;
1117 }
1118
1119 if (you.last_pickup.empty())
1120 you.last_pickup = tmp_l_p;
1121
1122 // Since the old ShopEntrys may now point to past the end of shop.stock (or
1123 // just the wrong place in general) nuke the whole thing and start over.
1124 deleteAll(items);
1125 init_entries();
1126 resort();
1127
1128 if (outside_items)
1129 {
1130 update_help();
1131 const formatted_string next_more = more;
1132 more = formatted_string::parse_string(make_stringf(
1133 "<%s>I'll put %s outside for you.</%s>\n",
1134 col.c_str(),
1135 bought_indices.size() == 1 ? "it" :
1136 (int) bought_indices.size() == outside_items ? "them"
1137 : "some of them",
1138 col.c_str()));
1139 more += next_more;
1140 update_more();
1141 }
1142 else
1143 update_help();
1144
1145 update_menu(true);
1146 }
1147
1148 // Doesn't handle redrawing itself.
resort()1149 void ShopMenu::resort()
1150 {
1151 switch (order)
1152 {
1153 case ORDER_TYPE:
1154 {
1155 const bool id = shoptype_identifies_stock(shop.type);
1156 // Using a map to sort reduces the number of item->name() calls.
1157 multimap<const string, MenuEntry *> list;
1158 for (const auto entry : items)
1159 {
1160 const auto item = dynamic_cast<ShopEntry*>(entry)->item;
1161 if (is_known_artefact(*item))
1162 list.insert({item->name(DESC_QUALNAME, false, id), entry});
1163 else
1164 {
1165 list.insert({item->name(DESC_DBNAME, false, id) + " "
1166 + item->name(DESC_PLAIN, false, id), entry});
1167 }
1168 }
1169 items.clear();
1170 for (auto &entry : list)
1171 items.push_back(entry.second);
1172 break;
1173 }
1174 case ORDER_PRICE:
1175 sort(begin(items), end(items),
1176 [this](MenuEntry* a, MenuEntry* b)
1177 {
1178 return item_price(*dynamic_cast<ShopEntry*>(a)->item, shop)
1179 < item_price(*dynamic_cast<ShopEntry*>(b)->item, shop);
1180 });
1181 break;
1182 case ORDER_ALPHABETICAL:
1183 sort(begin(items), end(items),
1184 [this](MenuEntry* a, MenuEntry* b) -> bool
1185 {
1186 const bool id = shoptype_identifies_stock(shop.type);
1187 return dynamic_cast<ShopEntry*>(a)->item->name(DESC_PLAIN, false, id)
1188 < dynamic_cast<ShopEntry*>(b)->item->name(DESC_PLAIN, false, id);
1189 });
1190 break;
1191 case NUM_ORDERS:
1192 die("invalid ordering");
1193 }
1194 for (size_t i = 0; i < items.size(); ++i)
1195 items[i]->hotkeys[0] = index_to_letter(i);
1196 }
1197
process_key(int keyin)1198 bool ShopMenu::process_key(int keyin)
1199 {
1200 switch (keyin)
1201 {
1202 case '!':
1203 case '?':
1204 if (can_purchase)
1205 {
1206 if (menu_action == ACT_EXECUTE)
1207 menu_action = ACT_EXAMINE;
1208 else
1209 menu_action = ACT_EXECUTE;
1210 update_help();
1211 update_more();
1212 }
1213 return true;
1214 case ' ':
1215 case CK_MOUSE_CLICK:
1216 case CK_ENTER:
1217 if (can_purchase)
1218 purchase_selected();
1219 return true;
1220 case '$':
1221 {
1222 const vector<MenuEntry*> selected = selected_entries();
1223 if (!selected.empty())
1224 {
1225 // Move selected to shopping list.
1226 for (auto entry : selected)
1227 {
1228 const item_def& item = *dynamic_cast<ShopEntry*>(entry)->item;
1229 entry->selected_qty = 0;
1230 if (!shopping_list.is_on_list(item, &pos))
1231 shopping_list.add_thing(item, item_price(item, shop), &pos);
1232 }
1233 }
1234 else
1235 // Move shoplist to selection.
1236 for (auto entry : items)
1237 if (shopping_list.is_on_list(*dynamic_cast<ShopEntry*>(entry)->item, &pos))
1238 entry->select(-2);
1239 // Move shoplist to selection.
1240 update_menu(true);
1241 return true;
1242 }
1243 case '/':
1244 ++order;
1245 resort();
1246 update_help();
1247 update_menu(true);
1248 return true;
1249 default:
1250 break;
1251 }
1252
1253 if (keyin - 'a' >= 0 && keyin - 'a' < (int)items.size()
1254 && menu_action == ACT_EXAMINE)
1255 {
1256 // A hack to make the description more useful.
1257 // The default copy constructor is non-const for item_def,
1258 // so we need this violation of const hygene to tweak the flags
1259 // to make the description more useful. The flags are copied by
1260 // value by the default copy constructor so this is safe.
1261 item_def& item(*const_cast<item_def*>(dynamic_cast<ShopEntry*>(
1262 items[letter_to_index(keyin)])->item));
1263 if (shoptype_identifies_stock(shop.type))
1264 {
1265 item.flags |= (ISFLAG_IDENT_MASK | ISFLAG_NOTED_ID
1266 | ISFLAG_NOTED_GET);
1267 }
1268 describe_item_popup(item);
1269
1270 return true;
1271 }
1272 else if (keyin - 'A' >= 0 && keyin - 'A' < (int)items.size())
1273 {
1274 const auto index = letter_to_index(keyin) % 26;
1275 auto entry = dynamic_cast<ShopEntry*>(items[index]);
1276 entry->selected_qty = 0;
1277 const item_def& item(*entry->item);
1278 if (shopping_list.is_on_list(item, &pos))
1279 shopping_list.del_thing(item, &pos);
1280 else
1281 shopping_list.add_thing(item, item_price(item, shop), &pos);
1282 update_menu(true);
1283 return true;
1284 }
1285
1286 auto old_selected = selected_entries();
1287 bool ret = InvMenu::process_key(keyin);
1288 if (old_selected != selected_entries())
1289 {
1290 // Update the footer to display the new $$$ info.
1291 update_help();
1292 update_menu(true);
1293 }
1294 return ret;
1295 }
1296
shop()1297 void shop()
1298 {
1299 if (!shop_at(you.pos()))
1300 {
1301 mprf(MSGCH_ERROR, "Help! Non-existent shop.");
1302 return;
1303 }
1304
1305 shop_struct& shop = *shop_at(you.pos());
1306 const string shopname = shop_name(shop);
1307
1308 // Quick out, if no inventory
1309 if (shop.stock.empty())
1310 {
1311 mprf("%s appears to be closed.", shopname.c_str());
1312 destroy_shop_at(you.pos());
1313 return;
1314 }
1315
1316 bool culled = false;
1317 for (const auto& item : shop.stock)
1318 {
1319 const int cost = item_price(item, shop);
1320 culled |= shopping_list.cull_identical_items(item, cost);
1321 }
1322 if (culled)
1323 more(); // make sure all messages appear before menu
1324
1325 ShopMenu menu(shop, level_pos::current(), true);
1326 menu.show();
1327
1328 StashTrack.get_shop(shop.pos) = ShopInfo(shop);
1329 bool any_on_list = any_of(begin(shop.stock), end(shop.stock),
1330 [](const item_def& item)
1331 {
1332 return shopping_list.is_on_list(item);
1333 });
1334
1335 // If the shop is now empty, erase it from the overview.
1336 if (shop.stock.empty())
1337 destroy_shop_at(you.pos());
1338 redraw_screen();
1339 update_screen();
1340 if (menu.bought_something)
1341 mprf("Thank you for shopping at %s!", shopname.c_str());
1342 if (any_on_list)
1343 mpr("You can access your shopping list by pressing '$'.");
1344 }
1345
shop(shop_struct & shop,const level_pos & pos)1346 void shop(shop_struct& shop, const level_pos& pos)
1347 {
1348 ASSERT(shop.pos == pos.pos);
1349 ShopMenu(shop, pos, false).show();
1350 }
1351
destroy_shop_at(coord_def p)1352 void destroy_shop_at(coord_def p)
1353 {
1354 if (shop_at(p))
1355 {
1356 env.shop.erase(p);
1357 env.grid(p) = DNGN_ABANDONED_SHOP;
1358 unnotice_feature(level_pos(level_id::current(), p));
1359 }
1360 }
1361
shop_at(const coord_def & where)1362 shop_struct *shop_at(const coord_def& where)
1363 {
1364 if (env.grid(where) != DNGN_ENTER_SHOP)
1365 return nullptr;
1366
1367 auto it = env.shop.find(where);
1368 ASSERT(it != env.shop.end());
1369 ASSERT(it->second.pos == where);
1370 ASSERT(it->second.type != SHOP_UNASSIGNED);
1371
1372 return &it->second;
1373 }
1374
shop_type_name(shop_type type)1375 string shop_type_name(shop_type type)
1376 {
1377 switch (type)
1378 {
1379 case SHOP_WEAPON_ANTIQUE:
1380 return "Antique Weapon";
1381 case SHOP_ARMOUR_ANTIQUE:
1382 return "Antique Armour";
1383 case SHOP_WEAPON:
1384 return "Weapon";
1385 case SHOP_ARMOUR:
1386 return "Armour";
1387 case SHOP_JEWELLERY:
1388 return "Jewellery";
1389 case SHOP_BOOK:
1390 return "Book";
1391 #if TAG_MAJOR_VERSION == 34
1392 case SHOP_EVOKABLES:
1393 return "Gadget";
1394 case SHOP_FOOD:
1395 return "Removed Food";
1396 #endif
1397 case SHOP_SCROLL:
1398 return "Magic Scroll";
1399 case SHOP_GENERAL_ANTIQUE:
1400 return "Assorted Antiques";
1401 case SHOP_DISTILLERY:
1402 return "Distillery";
1403 case SHOP_GENERAL:
1404 return "General Store";
1405 default:
1406 return "Bug";
1407 }
1408 }
1409
_shop_type_suffix(shop_type type,const coord_def & where)1410 static const char *_shop_type_suffix(shop_type type, const coord_def &where)
1411 {
1412 if (type == SHOP_GENERAL
1413 || type == SHOP_GENERAL_ANTIQUE
1414 || type == SHOP_DISTILLERY)
1415 {
1416 return "";
1417 }
1418
1419 static const char * const suffixnames[] =
1420 {
1421 "Shoppe", "Boutique", "Emporium", "Shop"
1422 };
1423 return suffixnames[(where.x + where.y) % ARRAYSZ(suffixnames)];
1424 }
1425
shop_name(const shop_struct & shop)1426 string shop_name(const shop_struct& shop)
1427 {
1428 const shop_type type = shop.type;
1429
1430 string sh_name = "";
1431
1432 #if TAG_MAJOR_VERSION == 34
1433 // xref ShopInfo::load
1434 if (shop.shop_name == " ")
1435 return shop.shop_type_name;
1436 #endif
1437 if (!shop.shop_name.empty())
1438 sh_name += apostrophise(shop.shop_name) + " ";
1439 else
1440 {
1441 uint32_t seed = static_cast<uint32_t>(shop.keeper_name[0])
1442 | (static_cast<uint32_t>(shop.keeper_name[1]) << 8)
1443 | (static_cast<uint32_t>(shop.keeper_name[1]) << 16);
1444
1445 sh_name += apostrophise(make_name(seed)) + " ";
1446 }
1447
1448 if (!shop.shop_type_name.empty())
1449 sh_name += shop.shop_type_name;
1450 else
1451 sh_name += shop_type_name(type);
1452
1453 if (!shop.shop_suffix_name.empty())
1454 sh_name += " " + shop.shop_suffix_name;
1455 else
1456 {
1457 string sh_suffix = _shop_type_suffix(type, shop.pos);
1458 if (!sh_suffix.empty())
1459 sh_name += " " + sh_suffix;
1460 }
1461
1462 return sh_name;
1463 }
1464
is_shop_item(const item_def & item)1465 bool is_shop_item(const item_def &item)
1466 {
1467 return item.link == ITEM_IN_SHOP;
1468 }
1469
shoptype_identifies_stock(shop_type type)1470 bool shoptype_identifies_stock(shop_type type)
1471 {
1472 return type != SHOP_WEAPON_ANTIQUE
1473 && type != SHOP_ARMOUR_ANTIQUE
1474 && type != SHOP_GENERAL_ANTIQUE;
1475 }
1476
shop_item_unknown(const item_def & item)1477 bool shop_item_unknown(const item_def &item)
1478 {
1479 return item_type_has_ids(item.base_type)
1480 && item_type_known(item)
1481 && !get_ident_type(item)
1482 && !is_artefact(item);
1483 }
1484
1485 static const char *shop_types[] =
1486 {
1487 "weapon",
1488 "armour",
1489 "antique weapon",
1490 "antique armour",
1491 "antiques",
1492 "jewellery",
1493 #if TAG_MAJOR_VERSION == 34
1494 "removed gadget",
1495 #endif
1496 "book",
1497 #if TAG_MAJOR_VERSION == 34
1498 "removed food",
1499 #endif
1500 "distillery",
1501 "scroll",
1502 "general",
1503 };
1504
1505 /** What shop type is this?
1506 *
1507 * @param s the shop type, in a string.
1508 * @returns the corresponding enum, or SHOP_UNASSIGNED if none.
1509 */
str_to_shoptype(const string & s)1510 shop_type str_to_shoptype(const string &s)
1511 {
1512 #if TAG_MAJOR_VERSION == 34
1513 if (s == "removed gadget" || s == "removed food")
1514 return SHOP_UNASSIGNED;
1515 #endif
1516 if (s == "random" || s == "any")
1517 return SHOP_RANDOM;
1518
1519 for (size_t i = 0; i < ARRAYSZ(shop_types); ++i)
1520 if (s == shop_types[i])
1521 return static_cast<shop_type>(i);
1522
1523 return SHOP_UNASSIGNED;
1524 }
1525
shoptype_to_str(shop_type type)1526 const char *shoptype_to_str(shop_type type)
1527 {
1528 return shop_types[type];
1529 }
1530
list_shop_types()1531 void list_shop_types()
1532 {
1533 mpr_nojoin(MSGCH_PLAIN, "Available shop types: ");
1534 for (const char *type : shop_types)
1535 mprf_nocap("%s", type);
1536 }
1537
1538 ////////////////////////////////////////////////////////////////////////
1539
1540 // TODO:
1541 // * Warn if buying something not on the shopping list would put
1542 // something on shopping list out of your reach.
1543
1544 #define SHOPPING_LIST_KEY "shopping_list_key"
1545 #define SHOPPING_LIST_COST_KEY "shopping_list_cost_key"
1546 #define SHOPPING_THING_COST_KEY "cost_key"
1547 #define SHOPPING_THING_ITEM_KEY "item_key"
1548 #define SHOPPING_THING_DESC_KEY "desc_key"
1549 #define SHOPPING_THING_VERB_KEY "verb_key"
1550 #define SHOPPING_THING_POS_KEY "pos_key"
1551
ShoppingList()1552 ShoppingList::ShoppingList()
1553 : list(nullptr), min_unbuyable_cost(0), min_unbuyable_idx(0),
1554 max_buyable_cost(0), max_buyable_idx(0)
1555 {
1556 }
1557
1558 #define SETUP_POS() \
1559 ASSERT(list); \
1560 level_pos pos; \
1561 if (_pos != nullptr) \
1562 pos = *_pos; \
1563 else \
1564 pos = level_pos::current(); \
1565 ASSERT(pos.is_valid());
1566
add_thing(const item_def & item,int cost,const level_pos * _pos)1567 bool ShoppingList::add_thing(const item_def &item, int cost,
1568 const level_pos* _pos)
1569 {
1570 ASSERT(item.defined());
1571 ASSERT(cost > 0);
1572
1573 SETUP_POS();
1574
1575 if (!find_thing(item, pos).empty()) // TODO: this check isn't working?
1576 {
1577 mprf(MSGCH_ERROR, "%s is already on the shopping list.",
1578 item.name(DESC_THE).c_str());
1579 return false;
1580 }
1581
1582 CrawlHashTable *thing = new CrawlHashTable();
1583 (*thing)[SHOPPING_THING_COST_KEY] = cost;
1584 (*thing)[SHOPPING_THING_POS_KEY] = pos;
1585 (*thing)[SHOPPING_THING_ITEM_KEY] = item;
1586 list->push_back(*thing);
1587 refresh();
1588
1589 return true;
1590 }
1591
is_on_list(const item_def & item,const level_pos * _pos) const1592 bool ShoppingList::is_on_list(const item_def &item, const level_pos* _pos) const
1593 {
1594 SETUP_POS();
1595
1596 return !find_thing(item, pos).empty();
1597 }
1598
is_on_list(string desc,const level_pos * _pos) const1599 bool ShoppingList::is_on_list(string desc, const level_pos* _pos) const
1600 {
1601 SETUP_POS();
1602
1603 return !find_thing(desc, pos).empty();
1604 }
1605
del_thing_at_index(int idx)1606 void ShoppingList::del_thing_at_index(int idx)
1607 {
1608 ASSERT_RANGE(idx, 0, list->size());
1609 list->erase(idx);
1610 refresh();
1611 }
1612
1613 template <typename C>
del_thing_at_indices(C const & idxs)1614 void ShoppingList::del_thing_at_indices(C const &idxs)
1615 {
1616 set<int,greater<int>> indices(idxs.begin(), idxs.end());
1617
1618 for (auto idx : indices)
1619 {
1620 ASSERT_RANGE(idx, 0, list->size());
1621 list->erase(idx);
1622 }
1623 refresh();
1624 }
1625
del_things_from(const level_id & lid)1626 void ShoppingList::del_things_from(const level_id &lid)
1627 {
1628 for (unsigned int i = 0; i < list->size(); i++)
1629 {
1630 const CrawlHashTable &thing = (*list)[i];
1631
1632 if (thing_pos(thing).is_on(lid))
1633 list->erase(i--);
1634 }
1635 refresh();
1636 }
1637
del_thing(const item_def & item,const level_pos * _pos)1638 bool ShoppingList::del_thing(const item_def &item,
1639 const level_pos* _pos)
1640 {
1641 SETUP_POS();
1642
1643 auto indices = find_thing(item, pos);
1644
1645 if (indices.empty())
1646 {
1647 mprf(MSGCH_ERROR, "%s isn't on shopping list, can't delete it.",
1648 item.name(DESC_THE).c_str());
1649 return false;
1650 }
1651
1652 del_thing_at_indices(indices);
1653 return true;
1654 }
1655
del_thing(string desc,const level_pos * _pos)1656 bool ShoppingList::del_thing(string desc, const level_pos* _pos)
1657 {
1658 SETUP_POS();
1659
1660 auto indices = find_thing(desc, pos);
1661
1662 if (indices.empty())
1663 {
1664 mprf(MSGCH_ERROR, "%s isn't on shopping list, can't delete it.",
1665 desc.c_str());
1666 return false;
1667 }
1668
1669 del_thing_at_indices(indices);
1670 return true;
1671 }
1672
1673 #undef SETUP_POS
1674
1675 #define REMOVE_PROMPTED_KEY "remove_prompted_key"
1676 #define REPLACE_PROMPTED_KEY "replace_prompted_key"
1677
1678 // TODO:
1679 //
1680 // * If you get a randart which lets you turn invisible, then remove
1681 // any ordinary rings of invisibility from the shopping list.
1682 //
1683 // * If you collected enough spellbooks that all the spells in a
1684 // shopping list book are covered, then auto-remove it.
cull_identical_items(const item_def & item,int cost)1685 bool ShoppingList::cull_identical_items(const item_def& item, int cost)
1686 {
1687 // Dead men can't update their shopping lists.
1688 if (!crawl_state.need_save)
1689 return 0;
1690
1691 // Can't put items in Bazaar shops in the shopping list, so
1692 // don't bother transferring shopping list items to Bazaar shops.
1693 // TODO: temporary shoplists
1694 if (cost != -1 && !is_connected_branch(you.where_are_you))
1695 return 0;
1696
1697 switch (item.base_type)
1698 {
1699 case OBJ_JEWELLERY:
1700 case OBJ_BOOKS:
1701 case OBJ_STAVES:
1702 // Only these are really interchangeable.
1703 break;
1704 case OBJ_MISCELLANY:
1705 // ... and a few of these.
1706 if (!is_xp_evoker(item))
1707 return 0;
1708 break;
1709 default:
1710 return 0;
1711 }
1712
1713 if (!item_type_known(item) || is_artefact(item))
1714 return 0;
1715
1716 // Ignore stat-modification rings which reduce a stat, since they're
1717 // worthless.
1718 if (item.base_type == OBJ_JEWELLERY && item.plus < 0)
1719 return 0;
1720
1721 // Manuals are consumable, and interesting enough to keep on list.
1722 if (item.is_type(OBJ_BOOKS, BOOK_MANUAL))
1723 return 0;
1724
1725 // Item is already on shopping-list.
1726 const bool on_list = !find_thing(item, level_pos::current()).empty();
1727
1728 const bool do_prompt = item.base_type == OBJ_JEWELLERY
1729 && !jewellery_is_amulet(item)
1730 && ring_has_stackable_effect(item);
1731
1732 bool add_item = false;
1733
1734 typedef pair<item_def, level_pos> list_pair;
1735 vector<list_pair> to_del;
1736
1737 // NOTE: Don't modify the shopping list while iterating over it.
1738 for (CrawlHashTable &thing : *list)
1739 {
1740 if (!thing_is_item(thing))
1741 continue;
1742
1743 const item_def& list_item = get_thing_item(thing);
1744
1745 if (list_item.base_type != item.base_type
1746 || list_item.sub_type != item.sub_type)
1747 {
1748 continue;
1749 }
1750
1751 if (!item_type_known(list_item) || is_artefact(list_item))
1752 continue;
1753
1754 // Don't prompt to remove rings with strictly better pluses
1755 // than the new one. Also, don't prompt to remove rings with
1756 // known pluses when the new ring's pluses are unknown.
1757 if (item.base_type == OBJ_JEWELLERY)
1758 {
1759 const bool has_plus = jewellery_has_pluses(item);
1760 const int delta_p = item.plus - list_item.plus;
1761 if (has_plus
1762 && item_ident(list_item, ISFLAG_KNOW_PLUSES)
1763 && (!item_ident(item, ISFLAG_KNOW_PLUSES)
1764 || delta_p < 0))
1765 {
1766 continue;
1767 }
1768 }
1769
1770 // Don't prompt to remove known manuals when the new one is unknown
1771 // or for a different skill.
1772 if (item.is_type(OBJ_BOOKS, BOOK_MANUAL)
1773 && item_type_known(list_item)
1774 && (!item_type_known(item) || item.plus != list_item.plus))
1775 {
1776 continue;
1777 }
1778
1779 list_pair listed(list_item, thing_pos(thing));
1780
1781 // cost = -1, we just found a shop item which is cheaper than
1782 // one on the shopping list.
1783 if (cost != -1)
1784 {
1785 int list_cost = thing_cost(thing);
1786
1787 if (cost >= list_cost)
1788 continue;
1789
1790 // Only prompt once.
1791 if (thing.exists(REPLACE_PROMPTED_KEY))
1792 continue;
1793 thing[REPLACE_PROMPTED_KEY] = (bool) true;
1794
1795 string prompt =
1796 make_stringf("Shopping list: replace %dgp %s with cheaper "
1797 "one? (Y/n)", list_cost,
1798 describe_thing(thing).c_str());
1799
1800 if (yesno(prompt.c_str(), true, 'y', false))
1801 {
1802 add_item = true;
1803 to_del.push_back(listed);
1804 }
1805 continue;
1806 }
1807
1808 // cost == -1, we just got an item which is on the shopping list.
1809 if (do_prompt)
1810 {
1811 // Only prompt once.
1812 if (thing.exists(REMOVE_PROMPTED_KEY))
1813 continue;
1814 thing[REMOVE_PROMPTED_KEY] = (bool) true;
1815
1816 string prompt = make_stringf("Shopping list: remove %s? (Y/n)",
1817 describe_thing(thing, DESC_A).c_str());
1818
1819 if (yesno(prompt.c_str(), true, 'y', false))
1820 {
1821 to_del.push_back(listed);
1822 mprf("Shopping list: removing %s",
1823 describe_thing(thing, DESC_A).c_str());
1824 }
1825 else
1826 canned_msg(MSG_OK);
1827 }
1828 else
1829 {
1830 mprf("Shopping list: removing %s",
1831 describe_thing(thing, DESC_A).c_str());
1832 to_del.push_back(listed);
1833 }
1834 }
1835
1836 for (list_pair &entry : to_del)
1837 del_thing(entry.first, &entry.second);
1838
1839 if (add_item && !on_list)
1840 add_thing(item, cost);
1841
1842 return to_del.size();
1843 }
1844
item_type_identified(object_class_type base_type,int sub_type)1845 void ShoppingList::item_type_identified(object_class_type base_type,
1846 int sub_type)
1847 {
1848 // Dead men can't update their shopping lists.
1849 if (!crawl_state.need_save)
1850 return;
1851
1852 // Only restore the excursion at the very end.
1853 level_excursion le;
1854
1855 #if TAG_MAJOR_VERSION == 34
1856 // Handle removed Gozag shops from old saves. Only do this once:
1857 // future Gozag abandonment will call remove_dead_shops itself.
1858 if (!you.props.exists(REMOVED_DEAD_SHOPS_KEY))
1859 {
1860 remove_dead_shops();
1861 you.props[REMOVED_DEAD_SHOPS_KEY] = true;
1862 }
1863 #endif
1864
1865 for (CrawlHashTable &thing : *list)
1866 {
1867 if (!thing_is_item(thing))
1868 continue;
1869
1870 const item_def& item = get_thing_item(thing);
1871
1872 if (item.base_type != base_type || item.sub_type != sub_type)
1873 continue;
1874
1875 const level_pos place = thing_pos(thing);
1876
1877 le.go_to(place.id);
1878 const shop_struct *shop = shop_at(place.pos);
1879 ASSERT(shop);
1880 if (shoptype_identifies_stock(shop->type))
1881 continue;
1882
1883 thing[SHOPPING_THING_COST_KEY] =
1884 _shop_get_item_value(item, shop->greed, false);
1885 }
1886
1887 // Prices could have changed.
1888 refresh();
1889 }
1890
spells_added_to_library(const vector<spell_type> & spells,bool quiet)1891 void ShoppingList::spells_added_to_library(const vector<spell_type>& spells, bool quiet)
1892 {
1893 if (!list) /* let's not make book starts crash instantly... */
1894 return;
1895
1896 unordered_set<int> indices_to_del;
1897 for (CrawlHashTable &thing : *list)
1898 {
1899 if (!thing_is_item(thing)) // ???
1900 continue;
1901 const item_def& book = get_thing_item(thing);
1902 if (book.base_type != OBJ_BOOKS || book.sub_type == BOOK_MANUAL)
1903 continue;
1904
1905 const auto item_spells = spells_in_book(book);
1906 if (any_of(item_spells.begin(), item_spells.end(), [&spells](const spell_type st) {
1907 return find(spells.begin(), spells.end(), st) != spells.end();
1908 }) && is_useless_item(book, false))
1909 {
1910 level_pos pos = thing_pos(thing); // TODO: unreliable?
1911 auto thing_indices = find_thing(get_thing_item(thing), pos);
1912 indices_to_del.insert(thing_indices.begin(), thing_indices.end());
1913 }
1914 }
1915 if (!quiet)
1916 {
1917 for (auto idx : indices_to_del)
1918 {
1919 ASSERT_RANGE(idx, 0, list->size());
1920 mprf("Shopping list: removing %s",
1921 describe_thing((*list)[idx], DESC_A).c_str());
1922 }
1923 }
1924 del_thing_at_indices(indices_to_del);
1925 }
1926
remove_dead_shops()1927 void ShoppingList::remove_dead_shops()
1928 {
1929 // Only restore the excursion at the very end.
1930 level_excursion le;
1931
1932 set<level_pos> shops_to_remove;
1933
1934 for (CrawlHashTable &thing : *list)
1935 {
1936 const level_pos place = thing_pos(thing);
1937 le.go_to(place.id); // thereby running DACT_REMOVE_GOZAG_SHOPS
1938 const shop_struct *shop = shop_at(place.pos);
1939
1940 if (!shop)
1941 shops_to_remove.insert(place);
1942 }
1943
1944 for (auto pos : shops_to_remove)
1945 forget_pos(pos);
1946
1947 // Prices could have changed.
1948 refresh();
1949 }
1950
entries()1951 vector<shoplist_entry> ShoppingList::entries()
1952 {
1953 ASSERT(list);
1954
1955 vector<shoplist_entry> list_entries;
1956 for (const CrawlHashTable &entry : *list)
1957 {
1958 list_entries.push_back(
1959 make_pair(name_thing(entry), thing_cost(entry))
1960 );
1961 }
1962
1963 return list_entries;
1964 }
1965
size() const1966 int ShoppingList::size() const
1967 {
1968 ASSERT(list);
1969
1970 return list->size();
1971 }
1972
items_are_same(const item_def & item_a,const item_def & item_b)1973 bool ShoppingList::items_are_same(const item_def& item_a,
1974 const item_def& item_b)
1975 {
1976 return item_name_simple(item_a) == item_name_simple(item_b);
1977 }
1978
move_things(const coord_def & _src,const coord_def & _dst)1979 void ShoppingList::move_things(const coord_def &_src, const coord_def &_dst)
1980 {
1981 if (crawl_state.map_stat_gen
1982 || crawl_state.obj_stat_gen
1983 || crawl_state.test)
1984 {
1985 return; // Shopping list is unitialized and uneeded.
1986 }
1987
1988 const level_pos src(level_id::current(), _src);
1989 const level_pos dst(level_id::current(), _dst);
1990
1991 for (CrawlHashTable &thing : *list)
1992 if (thing_pos(thing) == src)
1993 thing[SHOPPING_THING_POS_KEY] = dst;
1994 }
1995
forget_pos(const level_pos & pos)1996 void ShoppingList::forget_pos(const level_pos &pos)
1997 {
1998 if (!crawl_state.need_save)
1999 return; // Shopping list is uninitialized and unneeded.
2000
2001 for (unsigned int i = 0; i < list->size(); i++)
2002 {
2003 const CrawlHashTable &thing = (*list)[i];
2004
2005 if (thing_pos(thing) == pos)
2006 {
2007 list->erase(i);
2008 i--;
2009 }
2010 }
2011
2012 // Maybe we just forgot about a shop.
2013 refresh();
2014 }
2015
gold_changed(int old_amount,int new_amount)2016 void ShoppingList::gold_changed(int old_amount, int new_amount)
2017 {
2018 ASSERT(list);
2019
2020 if (new_amount > old_amount && new_amount >= min_unbuyable_cost)
2021 {
2022 ASSERT(min_unbuyable_idx < list->size());
2023
2024 vector<string> descs;
2025 for (unsigned int i = min_unbuyable_idx; i < list->size(); i++)
2026 {
2027 const CrawlHashTable &thing = (*list)[i];
2028 const int cost = thing_cost(thing);
2029
2030 if (cost > new_amount)
2031 {
2032 ASSERT(i > (unsigned int) min_unbuyable_idx);
2033 break;
2034 }
2035
2036 string desc;
2037
2038 if (thing.exists(SHOPPING_THING_VERB_KEY))
2039 desc += thing[SHOPPING_THING_VERB_KEY].get_string();
2040 else
2041 desc = "buy";
2042 desc += " ";
2043
2044 desc += describe_thing(thing, DESC_A);
2045
2046 descs.push_back(desc);
2047 }
2048 ASSERT(!descs.empty());
2049
2050 mpr_comma_separated_list("You now have enough gold to ", descs,
2051 ", or ");
2052 mpr("You can access your shopping list by pressing '$'.");
2053
2054 // Our gold has changed, maybe we can buy different things now.
2055 refresh();
2056 }
2057 else if (new_amount < old_amount && new_amount < max_buyable_cost)
2058 {
2059 // As above.
2060 refresh();
2061 }
2062 }
2063
2064 class ShoppingListMenu : public Menu
2065 {
2066 public:
ShoppingListMenu()2067 ShoppingListMenu() : Menu(MF_MULTISELECT | MF_ALLOW_FORMATTING) {}
2068 bool view_only {false};
2069
2070 protected:
2071 virtual formatted_string calc_title() override;
2072 };
2073
calc_title()2074 formatted_string ShoppingListMenu::calc_title()
2075 {
2076 formatted_string fs;
2077 const int total_cost = you.props[SHOPPING_LIST_COST_KEY];
2078
2079 fs.textcolour(title->colour);
2080 fs.cprintf("%d %s%s, total cost %d gp",
2081 title->quantity, title->text.c_str(),
2082 title->quantity > 1 ? "s" : "",
2083 total_cost);
2084
2085 string s = "<lightgrey> [<w>a-z</w>] ";
2086
2087 if (view_only)
2088 {
2089 fs += formatted_string::parse_string(s + "<w>examine</w>");
2090 return fs;
2091 }
2092
2093 switch (menu_action)
2094 {
2095 case ACT_EXECUTE:
2096 s += "<w>travel</w>|examine|delete";
2097 break;
2098 case ACT_EXAMINE:
2099 s += "travel|<w>examine</w>|delete";
2100 break;
2101 default:
2102 s += "travel|examine|<w>delete</w>";
2103 break;
2104 }
2105
2106 s += " [<w>?</w>/<w>!</w>] change action</lightgrey>";
2107 fs += formatted_string::parse_string(s);
2108 return fs;
2109 }
2110
2111 /**
2112 * Describe the location of a given shopping list entry.
2113 *
2114 * @param thing A shopping list entry.
2115 * @return Something like [Orc:4], probably.
2116 */
describe_thing_pos(const CrawlHashTable & thing)2117 string ShoppingList::describe_thing_pos(const CrawlHashTable &thing)
2118 {
2119 return make_stringf("[%s]", thing_pos(thing).id.describe().c_str());
2120 }
2121
fill_out_menu(Menu & shopmenu)2122 void ShoppingList::fill_out_menu(Menu& shopmenu)
2123 {
2124 menu_letter hotkey;
2125 int longest = 0;
2126 // How much space does the longest entry need for proper alignment?
2127 for (const CrawlHashTable &thing : *list)
2128 longest = max(longest, strwidth(describe_thing_pos(thing)));
2129
2130 for (CrawlHashTable &thing : *list)
2131 {
2132 const int cost = thing_cost(thing);
2133 const bool unknown = thing_is_item(thing)
2134 && shop_item_unknown(get_thing_item(thing));
2135
2136 const string etitle =
2137 make_stringf(
2138 "%*s%5d gold %s%s",
2139 longest,
2140 describe_thing_pos(thing).c_str(),
2141 cost,
2142 name_thing(thing, DESC_A).c_str(),
2143 unknown ? " (unknown)" : "");
2144
2145 MenuEntry *me = new MenuEntry(etitle, MEL_ITEM, 1, hotkey);
2146 me->data = &thing;
2147
2148 if (thing_is_item(thing))
2149 {
2150 // Colour shopping list item according to menu colours.
2151 const item_def &item = get_thing_item(thing);
2152
2153 const string colprf = item_prefix(item);
2154 const int col = menu_colour(item.name(DESC_A),
2155 colprf, "shop");
2156
2157 vector<tile_def> item_tiles;
2158 get_tiles_for_item(item, item_tiles, true);
2159 for (const auto &tile : item_tiles)
2160 me->add_tile(tile);
2161
2162 if (col != -1)
2163 me->colour = col;
2164 }
2165 if (cost > you.gold)
2166 me->colour = DARKGREY;
2167
2168 shopmenu.add_entry(me);
2169 ++hotkey;
2170 }
2171 }
2172
display(bool view_only)2173 void ShoppingList::display(bool view_only)
2174 {
2175 if (list->empty())
2176 return;
2177
2178 ShoppingListMenu shopmenu;
2179 shopmenu.view_only = view_only;
2180 shopmenu.set_tag("shop");
2181 shopmenu.menu_action = view_only ? Menu::ACT_EXAMINE : Menu::ACT_EXECUTE;
2182 shopmenu.action_cycle = view_only ? Menu::CYCLE_NONE : Menu::CYCLE_CYCLE;
2183 string title = "item";
2184
2185 MenuEntry *mtitle = new MenuEntry(title, MEL_TITLE);
2186 mtitle->quantity = list->size();
2187 shopmenu.set_title(mtitle);
2188
2189 string more_str = make_stringf("<yellow>You have %d gp</yellow>", you.gold);
2190 shopmenu.set_more(formatted_string::parse_string(more_str));
2191
2192 shopmenu.set_flags(MF_SINGLESELECT | MF_ALWAYS_SHOW_MORE
2193 | MF_ALLOW_FORMATTING);
2194
2195 fill_out_menu(shopmenu);
2196
2197 shopmenu.on_single_selection =
2198 [this, &shopmenu, &mtitle](const MenuEntry& sel)
2199 {
2200 const CrawlHashTable* thing =
2201 static_cast<const CrawlHashTable *>(sel.data);
2202
2203 const bool is_item = thing_is_item(*thing);
2204
2205 if (shopmenu.menu_action == Menu::ACT_EXECUTE)
2206 {
2207 int cost = thing_cost(*thing);
2208
2209 if (cost > you.gold)
2210 {
2211 string prompt =
2212 make_stringf("You cannot afford %s; travel there "
2213 "anyway? (y/N)",
2214 describe_thing(*thing, DESC_A).c_str());
2215 clrscr();
2216 if (!yesno(prompt.c_str(), true, 'n'))
2217 return true;
2218 }
2219
2220 const level_pos lp(thing_pos(*thing));
2221 start_translevel_travel(lp);
2222 return false;
2223 }
2224 else if (shopmenu.menu_action == Menu::ACT_EXAMINE)
2225 {
2226 if (is_item)
2227 {
2228 const item_def &item = get_thing_item(*thing);
2229 describe_item_popup(item);
2230 }
2231 else // not an item, so we only stored a description.
2232 {
2233 // HACK: Assume it's some kind of portal vault.
2234 const string info = make_stringf(
2235 "%s with an entry fee of %d gold pieces.",
2236 describe_thing(*thing, DESC_A).c_str(),
2237 (int) thing_cost(*thing));
2238 show_description(info.c_str());
2239 }
2240 }
2241 else if (shopmenu.menu_action == Menu::ACT_MISC)
2242 {
2243 const int index = shopmenu.get_entry_index(&sel);
2244 if (index == -1)
2245 {
2246 mprf(MSGCH_ERROR, "ERROR: Unable to delete thing from shopping list!");
2247 more();
2248 return true;
2249 }
2250
2251 del_thing_at_index(index);
2252 mtitle->quantity = this->list->size();
2253 shopmenu.set_title(mtitle);
2254
2255 if (this->list->empty())
2256 {
2257 mpr("Your shopping list is now empty.");
2258 return false;
2259 }
2260
2261 shopmenu.clear();
2262 fill_out_menu(shopmenu);
2263 shopmenu.update_menu(true);
2264 }
2265 else
2266 die("Invalid menu action type");
2267 return true;
2268 };
2269
2270 shopmenu.show();
2271 redraw_screen();
2272 update_screen();
2273 }
2274
_compare_shopping_things(const CrawlStoreValue & a,const CrawlStoreValue & b)2275 static bool _compare_shopping_things(const CrawlStoreValue& a,
2276 const CrawlStoreValue& b)
2277 {
2278 const CrawlHashTable& hash_a = a.get_table();
2279 const CrawlHashTable& hash_b = b.get_table();
2280
2281 const int a_cost = hash_a[SHOPPING_THING_COST_KEY];
2282 const int b_cost = hash_b[SHOPPING_THING_COST_KEY];
2283
2284 const level_id id_a = hash_a[SHOPPING_THING_POS_KEY].get_level_pos().id;
2285 const level_id id_b = hash_b[SHOPPING_THING_POS_KEY].get_level_pos().id;
2286
2287 // Put Bazaar items at the top of the shopping list.
2288 if (!player_in_branch(BRANCH_BAZAAR) || id_a.branch == id_b.branch)
2289 return a_cost < b_cost;
2290 else
2291 return id_a.branch == BRANCH_BAZAAR;
2292 }
2293
2294 // Reset max_buyable and min_unbuyable info. Call this anytime any of the
2295 // player's gold, the shopping list, and the prices of the items on it
2296 // change.
refresh()2297 void ShoppingList::refresh()
2298 {
2299 if (!you.props.exists(SHOPPING_LIST_KEY))
2300 you.props[SHOPPING_LIST_KEY].new_vector(SV_HASH, SFLAG_CONST_TYPE);
2301 list = &you.props[SHOPPING_LIST_KEY].get_vector();
2302
2303 stable_sort(list->begin(), list->end(), _compare_shopping_things);
2304
2305 min_unbuyable_cost = INT_MAX;
2306 min_unbuyable_idx = -1;
2307 max_buyable_cost = -1;
2308 max_buyable_idx = -1;
2309
2310 int total_cost = 0;
2311
2312 for (unsigned int i = 0; i < list->size(); i++)
2313 {
2314 const CrawlHashTable &thing = (*list)[i];
2315
2316 const int cost = thing_cost(thing);
2317
2318 if (cost <= you.gold)
2319 {
2320 max_buyable_cost = cost;
2321 max_buyable_idx = i;
2322 }
2323 else if (min_unbuyable_idx == -1)
2324 {
2325 min_unbuyable_cost = cost;
2326 min_unbuyable_idx = i;
2327 }
2328 total_cost += cost;
2329 }
2330 you.props[SHOPPING_LIST_COST_KEY].get_int() = total_cost;
2331 }
2332
find_thing(const item_def & item,const level_pos & pos) const2333 unordered_set<int> ShoppingList::find_thing(const item_def &item,
2334 const level_pos &pos) const
2335 {
2336 unordered_set<int> result;
2337 for (unsigned int i = 0; i < list->size(); i++)
2338 {
2339 const CrawlHashTable &thing = (*list)[i];
2340 const level_pos _pos = thing_pos(thing);
2341
2342 if (pos != _pos) // TODO: using thing_pos above seems to make this unreliable?
2343 continue;
2344
2345 if (!thing_is_item(thing))
2346 continue;
2347
2348 const item_def &_item = get_thing_item(thing);
2349
2350 if (item_name_simple(item) == item_name_simple(_item))
2351 result.insert(i);
2352 }
2353
2354 return result;
2355 }
2356
find_thing(const string & desc,const level_pos & pos) const2357 unordered_set<int> ShoppingList::find_thing(const string &desc, const level_pos &pos) const
2358 {
2359 unordered_set<int> result;
2360 for (unsigned int i = 0; i < list->size(); i++)
2361 {
2362 const CrawlHashTable &thing = (*list)[i];
2363 const level_pos _pos = thing_pos(thing);
2364
2365 if (pos != _pos)
2366 continue;
2367
2368 if (thing_is_item(thing))
2369 continue;
2370
2371 if (desc == name_thing(thing))
2372 result.insert(i);
2373 }
2374
2375 return result;
2376 }
2377
thing_is_item(const CrawlHashTable & thing)2378 bool ShoppingList::thing_is_item(const CrawlHashTable& thing)
2379 {
2380 return thing.exists(SHOPPING_THING_ITEM_KEY);
2381 }
2382
get_thing_item(const CrawlHashTable & thing)2383 const item_def& ShoppingList::get_thing_item(const CrawlHashTable& thing)
2384 {
2385 ASSERT(thing.exists(SHOPPING_THING_ITEM_KEY));
2386
2387 const item_def &item = thing[SHOPPING_THING_ITEM_KEY].get_item();
2388 ASSERT(item.defined());
2389
2390 return item;
2391 }
2392
get_thing_desc(const CrawlHashTable & thing)2393 string ShoppingList::get_thing_desc(const CrawlHashTable& thing)
2394 {
2395 ASSERT(thing.exists(SHOPPING_THING_DESC_KEY));
2396
2397 string desc = thing[SHOPPING_THING_DESC_KEY].get_string();
2398 return desc;
2399 }
2400
thing_cost(const CrawlHashTable & thing)2401 int ShoppingList::thing_cost(const CrawlHashTable& thing)
2402 {
2403 ASSERT(thing.exists(SHOPPING_THING_COST_KEY));
2404 return thing[SHOPPING_THING_COST_KEY].get_int();
2405 }
2406
thing_pos(const CrawlHashTable & thing)2407 level_pos ShoppingList::thing_pos(const CrawlHashTable& thing)
2408 {
2409 ASSERT(thing.exists(SHOPPING_THING_POS_KEY));
2410 return thing[SHOPPING_THING_POS_KEY].get_level_pos();
2411 }
2412
name_thing(const CrawlHashTable & thing,description_level_type descrip)2413 string ShoppingList::name_thing(const CrawlHashTable& thing,
2414 description_level_type descrip)
2415 {
2416 if (thing_is_item(thing))
2417 {
2418 const item_def &item = get_thing_item(thing);
2419 return item.name(descrip);
2420 }
2421 else
2422 {
2423 string desc = get_thing_desc(thing);
2424 return apply_description(descrip, desc);
2425 }
2426 }
2427
describe_thing(const CrawlHashTable & thing,description_level_type descrip)2428 string ShoppingList::describe_thing(const CrawlHashTable& thing,
2429 description_level_type descrip)
2430 {
2431 string desc = name_thing(thing, descrip) + " on ";
2432
2433 const level_pos pos = thing_pos(thing);
2434 if (pos.id == level_id::current())
2435 desc += "this level";
2436 else
2437 desc += pos.id.describe();
2438
2439 return desc;
2440 }
2441
2442 // Item name without inscription.
item_name_simple(const item_def & item,bool ident)2443 string ShoppingList::item_name_simple(const item_def& item, bool ident)
2444 {
2445 return item.name(DESC_PLAIN, false, ident, false, false);
2446 }
2447