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