1 /**
2  * @file
3  * @brief Functions for inventory related commands.
4 **/
5 
6 #include "AppHdr.h"
7 
8 #include "invent.h"
9 
10 #include <algorithm> // any_of
11 #include <cctype>
12 #include <cstdlib>
13 #include <cstring>
14 #include <iomanip>
15 #include <sstream>
16 
17 #include "artefact.h"
18 #include "colour.h"
19 #include "command.h"
20 #include "describe.h"
21 #include "env.h"
22 #include "evoke.h"
23 #include "god-item.h"
24 #include "god-passive.h"
25 #include "initfile.h"
26 #include "item-prop.h"
27 #include "items.h"
28 #include "item-use.h"
29 #include "item-prop.h"
30 #include "item-status-flag-type.h"
31 #include "known-items.h"
32 #include "libutil.h"
33 #include "macro.h"
34 #include "message.h"
35 #include "options.h"
36 #include "output.h"
37 #include "prompt.h"
38 #include "religion.h"
39 #include "rltiles/tiledef-dngn.h"
40 #include "rltiles/tiledef-icons.h"
41 #include "rltiles/tiledef-main.h"
42 #include "showsymb.h"
43 #include "state.h"
44 #include "stringutil.h"
45 #include "tag-version.h"
46 #include "terrain.h"
47 #include "throw.h"
48 #include "tilepick.h"
49 #include "tile-env.h"
50 
51 ///////////////////////////////////////////////////////////////////////////////
52 // Inventory menu shenanigans
53 
54 static void _get_inv_items_to_show(vector<const item_def*> &v,
55                                    int selector, int excluded_slot = -1);
56 
InvTitle(Menu * mn,const string & title,invtitle_annotator tfn)57 InvTitle::InvTitle(Menu *mn, const string &title, invtitle_annotator tfn)
58     : MenuEntry(title, MEL_TITLE)
59 {
60     m       = mn;
61     titlefn = tfn;
62 }
63 
get_text(const bool) const64 string InvTitle::get_text(const bool) const
65 {
66     return titlefn ? titlefn(m, MenuEntry::get_text())
67                    : MenuEntry::get_text();
68 }
69 
InvEntry(const item_def & i)70 InvEntry::InvEntry(const item_def &i)
71     : MenuEntry("", MEL_ITEM), item(&i), _has_star(false)
72 {
73     // Data is an inherited void *. When using InvEntry in menus
74     // use the const item in this class whenever possible
75     data = const_cast<item_def *>(item);
76 
77     if (in_inventory(i) && i.base_type != OBJ_GOLD)
78     {
79         // We need to do this in order to get the 'wielded' annotation.
80         // We then toss out the first four characters, which look
81         // like this: "a - ". Ow. FIXME.
82         text = i.name(DESC_INVENTORY_EQUIP, false).substr(4);
83     }
84     else
85         text = i.name(DESC_A, false);
86 
87     if (item_is_stationary_net(i))
88     {
89         actor *trapped = actor_at(i.pos);
90         text += make_stringf(" (holding %s)",
91                             trapped ? trapped->name(DESC_A).c_str()
92                                     : "nobody"); // buggy net, but don't crash
93     }
94 
95     if (i.base_type != OBJ_GOLD && in_inventory(i))
96         add_hotkey(index_to_letter(i.link));
97     else
98         add_hotkey(' ');        // dummy hotkey
99 
100     add_class_hotkeys(i);
101 
102     quantity = i.quantity;
103 }
104 
get_basename() const105 const string &InvEntry::get_basename() const
106 {
107     if (basename.empty())
108         basename = item->name(DESC_BASENAME);
109     return basename;
110 }
111 
get_qualname() const112 const string &InvEntry::get_qualname() const
113 {
114     if (qualname.empty())
115         qualname = item->name(DESC_QUALNAME);
116     return qualname;
117 }
118 
get_fullname() const119 const string &InvEntry::get_fullname() const
120 {
121     return text;
122 }
123 
get_dbname() const124 const string &InvEntry::get_dbname() const
125 {
126     if (dbname.empty())
127         dbname = item->name(DESC_DBNAME);
128     return dbname;
129 }
130 
is_cursed() const131 bool InvEntry::is_cursed() const
132 {
133     return item->cursed();
134 }
135 
is_glowing() const136 bool InvEntry::is_glowing() const
137 {
138     return !item_ident(*item, ISFLAG_KNOW_TYPE)
139            && (get_equip_desc(*item)
140                || (is_artefact(*item)
141                    && (item->base_type == OBJ_WEAPONS
142                        || item->base_type == OBJ_ARMOUR
143                        || item->base_type == OBJ_BOOKS)));
144 }
145 
is_ego() const146 bool InvEntry::is_ego() const
147 {
148     return item_ident(*item, ISFLAG_KNOW_TYPE) && !is_artefact(*item)
149            && item->brand != 0
150            && (item->base_type == OBJ_WEAPONS
151                || item->base_type == OBJ_MISSILES
152                || item->base_type == OBJ_ARMOUR);
153 }
154 
is_art() const155 bool InvEntry::is_art() const
156 {
157     return item_ident(*item, ISFLAG_KNOW_TYPE) && is_artefact(*item);
158 }
159 
is_equipped() const160 bool InvEntry::is_equipped() const
161 {
162     if (item->link == -1 || item->pos != ITEM_IN_INVENTORY)
163         return false;
164 
165     for (int i = EQ_FIRST_EQUIP; i < NUM_EQUIP; i++)
166         if (item->link == you.equip[i])
167             return true;
168 
169     return false;
170 }
171 
select(int qty)172 void InvEntry::select(int qty)
173 {
174     if (item && item->quantity < qty)
175         qty = item->quantity;
176 
177     MenuEntry::select(qty);
178 }
179 
has_star() const180 bool InvEntry::has_star() const
181 {
182     return _has_star;
183 }
184 
get_filter_text() const185 string InvEntry::get_filter_text() const
186 {
187     return item_prefix(*item, false) + " " + get_text();
188 }
189 
get_text(bool need_cursor) const190 string InvEntry::get_text(bool need_cursor) const
191 {
192     need_cursor = need_cursor && show_cursor;
193 
194     ostringstream tstr;
195 
196     const bool nosel = hotkeys.empty();
197     const char key = nosel ? ' ' : static_cast<char>(hotkeys[0]);
198 
199     tstr << ' ';
200 
201     if (!nosel || tag == "pickup")
202     {
203         tstr << key;
204 
205         if (need_cursor)
206             tstr << '[';
207         else
208             tstr << ' ';
209 
210         if (nosel)
211             tstr << ' ';
212         else if (!selected_qty)
213             tstr << '-';
214         else if (selected_qty < quantity)
215             tstr << '#';
216         else if (_has_star)
217             tstr << '*';
218         else
219             tstr << '+';
220 
221         if (need_cursor)
222             tstr << ']';
223         else
224             tstr << ' ';
225     }
226     if (InvEntry::show_glyph)
227         tstr << "(" << glyph_to_tagstr(get_item_glyph(*item)) << ")" << " ";
228 
229     if (InvEntry::show_coordinates && in_bounds(item->pos))
230     {
231         const coord_def relpos = item->pos - you.pos();
232         tstr << "(" << relpos.x << ", " << -relpos.y << ")" << " ";
233     }
234 
235     tstr << text;
236     return tstr.str();
237 }
238 
get_class_hotkeys(const int type,vector<char> & glyphs)239 void get_class_hotkeys(const int type, vector<char> &glyphs)
240 {
241     switch (type)
242     {
243     case OBJ_GOLD:
244         glyphs.push_back('$');
245         break;
246     case OBJ_MISSILES:
247         glyphs.push_back('(');
248         break;
249     case OBJ_WEAPONS:
250         glyphs.push_back(')');
251         break;
252     case OBJ_ARMOUR:
253         glyphs.push_back('[');
254         break;
255     case OBJ_WANDS:
256         glyphs.push_back('/');
257         break;
258     case OBJ_BOOKS:
259         glyphs.push_back(':');
260         break;
261     case OBJ_SCROLLS:
262         glyphs.push_back('?');
263         break;
264     case OBJ_JEWELLERY:
265         glyphs.push_back('"');
266         glyphs.push_back('=');
267         break;
268     case OBJ_POTIONS:
269         glyphs.push_back('!');
270         break;
271     case OBJ_STAVES:
272         glyphs.push_back('|');
273         break;
274 #if TAG_MAJOR_VERSION == 34
275     case OBJ_RODS:
276         glyphs.push_back('\\');
277         break;
278 #endif
279     case OBJ_MISCELLANY:
280         glyphs.push_back('}');
281         break;
282     default:
283         break;
284     }
285 }
286 
add_class_hotkeys(const item_def & i)287 void InvEntry::add_class_hotkeys(const item_def &i)
288 {
289     const int type = i.base_type;
290     if (type == OBJ_JEWELLERY)
291     {
292         add_hotkey(i.sub_type >= AMU_FIRST_AMULET ? '"' : '=');
293         return;
294     }
295 
296     vector<char> glyphs;
297     get_class_hotkeys(type, glyphs);
298     for (char gly : glyphs)
299         add_hotkey(gly);
300 }
301 
302 bool InvEntry::show_cursor = false;
set_show_cursor(bool doshow)303 void InvEntry::set_show_cursor(bool doshow)
304 {
305     show_cursor = doshow;
306 }
307 
308 bool InvEntry::show_glyph = false;
set_show_glyph(bool doshow)309 void InvEntry::set_show_glyph(bool doshow)
310 {
311     show_glyph = doshow;
312 }
313 
314 bool InvEntry::show_coordinates = false;
set_show_coordinates(bool doshow)315 void InvEntry::set_show_coordinates(bool doshow)
316 {
317     show_coordinates = doshow;
318 }
319 
InvMenu(int mflags)320 InvMenu::InvMenu(int mflags)
321     : Menu(mflags, "inventory"), type(menu_type::invlist), pre_select(nullptr),
322       title_annotate(nullptr), _mode_special_drop(false)
323 {
324 #ifdef USE_TILE_LOCAL
325     if (Options.tile_menu_icons)
326         set_flags(mflags | MF_USE_TWO_COLUMNS);
327 #endif
328 
329     InvEntry::set_show_cursor(false);
330 }
331 
mode_special_drop() const332 bool InvMenu::mode_special_drop() const
333 {
334     return _mode_special_drop;
335 }
336 
set_type(menu_type t)337 void InvMenu::set_type(menu_type t)
338 {
339     type = t;
340 }
341 
set_title_annotator(invtitle_annotator afn)342 void InvMenu::set_title_annotator(invtitle_annotator afn)
343 {
344     title_annotate = afn;
345 }
346 
set_title(MenuEntry * t,bool first)347 void InvMenu::set_title(MenuEntry *t, bool first)
348 {
349     Menu::set_title(t, first);
350 }
351 
set_preselect(const vector<SelItem> * pre)352 void InvMenu::set_preselect(const vector<SelItem> *pre)
353 {
354     pre_select = pre;
355 }
356 
slot_description()357 string slot_description()
358 {
359     return make_stringf("%d/%d slots", inv_count(), ENDOFPACK);
360 }
361 
set_title(const string & s)362 void InvMenu::set_title(const string &s)
363 {
364     set_title(new InvTitle(this, s.empty() ? "Inventory: " + slot_description()
365                                            : s,
366                            title_annotate));
367 }
368 
pre_process(int key)369 int InvMenu::pre_process(int key)
370 {
371     if (type == menu_type::drop && key == '\\')
372     {
373         _mode_special_drop = !_mode_special_drop;
374         key = CK_NO_KEY;
375     }
376     else if (key == ';'
377              && you.last_unequip != -1
378              && (type == menu_type::drop || type == menu_type::invlist))
379     {
380         key = index_to_letter(you.last_unequip);
381     }
382     else if (key == '-')
383         _mode_special_drop = false;
384     return key;
385 }
386 
_item_is_permadrop_candidate(const item_def & item)387 static bool _item_is_permadrop_candidate(const item_def &item)
388 {
389     // Known, non-artefact items of the types you see on the '\' menu proper.
390     // (No disabling autopickup for "green fizzy potion", "+3 whip", etc.)
391     if (item_type_unknown(item))
392         return false;
393     return item.base_type == OBJ_MISCELLANY
394         || is_stackable_item(item)
395         || item_type_has_ids(item.base_type);
396 }
397 
select_item_index(int idx,int qty,bool draw_cursor)398 void InvMenu::select_item_index(int idx, int qty, bool draw_cursor)
399 {
400     if (type != menu_type::drop)
401         return Menu::select_item_index(idx, qty, draw_cursor);
402 
403     InvEntry *ie = static_cast<InvEntry*>(items[idx]);
404 
405     bool should_toggle_star = _item_is_permadrop_candidate(ie->item[0])
406         && (ie->has_star() || _mode_special_drop);
407 
408     if (should_toggle_star)
409     {
410         // Toggle starred items back to selected-but-not-starred in this mode
411         // instead of turning them all the way off.
412         qty = _mode_special_drop ? -2 : 0;
413         ie->set_star(!ie->has_star());
414     }
415     Menu::select_item_index(idx, qty, draw_cursor);
416 }
417 
set_star(bool val)418 void InvEntry::set_star(bool val)
419 {
420     _has_star = val;
421 }
422 
_has_melded_armour()423 static bool _has_melded_armour()
424 {
425     for (int e = EQ_CLOAK; e <= EQ_BODY_ARMOUR; e++)
426         if (you.melded[e])
427             return true;
428     return false;
429 }
430 
_has_temp_unwearable_armour()431 static bool _has_temp_unwearable_armour()
432 {
433     for (const auto &item : you.inv)
434     {
435         if (item.defined() && item.base_type == OBJ_ARMOUR
436             && can_wear_armour(item, false, true)
437             && !can_wear_armour(item, false, false))
438         {
439             return true;
440         }
441     }
442     return false;
443 }
444 
_has_hand_evokable()445 static bool _has_hand_evokable()
446 {
447     for (const auto &item : you.inv)
448     {
449         if (item.defined()
450             && item_is_evokable(item, true, false, false)
451             && !item_is_evokable(item, true, false, true))
452         {
453             return true;
454         }
455     }
456     return false;
457 }
458 
459 /**
460  * What message should the player be given when they look for items matching
461  * the given selector and don't find any?
462  *
463  * @param selector      The given type of object_selector.
464  * @return              A message such as "You aren't carrying any weapons."
465  *                      "Your armour is currently melded into you.", etc.
466  */
no_selectables_message(int item_selector)467 string no_selectables_message(int item_selector)
468 {
469     switch (item_selector)
470     {
471     case OSEL_ANY:
472         return "You aren't carrying anything.";
473     case OSEL_WIELD:
474     case OBJ_WEAPONS:
475         return "You aren't carrying any weapons.";
476     case OSEL_BLESSABLE_WEAPON:
477         return "You aren't carrying any weapons that can be blessed.";
478     case OBJ_ARMOUR:
479     {
480         if (_has_melded_armour())
481             return "Your armour is currently melded into you.";
482         else if (_has_temp_unwearable_armour())
483             return "You aren't carrying any currently wearable armour.";
484         else
485             return "You aren't carrying any wearable armour.";
486     }
487     case OSEL_UNIDENT:
488         return "You don't have any unidentified items.";
489     case OSEL_ENCHANTABLE_ARMOUR:
490         return "You aren't carrying any armour which can be enchanted further.";
491     case OBJ_CORPSES:
492         return "You don't have any corpses.";
493     case OBJ_POTIONS:
494         return "You aren't carrying any potions.";
495     case OBJ_SCROLLS:
496         return "You aren't carrying any scrolls.";
497     case OBJ_BOOKS:
498         return "You don't have any books.";
499     case OBJ_WANDS:
500         return "You aren't carrying any wands.";
501     case OBJ_JEWELLERY:
502         return "You aren't carrying any pieces of jewellery.";
503     case OSEL_THROWABLE:
504         return "You aren't carrying any items that might be thrown or fired.";
505     case OSEL_EVOKABLE:
506         if (_has_hand_evokable())
507             return "You aren't carrying any items that you can evoke without wielding.";
508         else
509             return "You aren't carrying any items that you can evoke.";
510     case OSEL_CURSED_WORN:
511         return "None of your equipped items are cursed.";
512 #if TAG_MAJOR_VERSION == 34
513     case OSEL_UNCURSED_WORN_ARMOUR:
514         return "You aren't wearing any piece of uncursed armour.";
515     case OSEL_UNCURSED_WORN_JEWELLERY:
516         return "You aren't wearing any piece of uncursed jewellery.";
517 #endif
518     case OSEL_BRANDABLE_WEAPON:
519         return "You aren't carrying any weapons that can be branded.";
520     case OSEL_ENCHANTABLE_WEAPON:
521         return "You aren't carrying any weapons that can be enchanted.";
522     case OSEL_BEOGH_GIFT:
523         return "You aren't carrying anything you can give to a follower.";
524     case OSEL_CURSABLE:
525         return "You aren't wearing any cursable items.";
526     case OSEL_UNCURSED_WORN_RINGS:
527         return "You aren't wearing any uncursed rings.";
528     case OSEL_QUIVER_ACTION:
529     case OSEL_QUIVER_ACTION_FORCE:
530         return "You don't have any quiverable items.";
531     }
532 
533     return "You aren't carrying any such object.";
534 }
535 
load_inv_items(int item_selector,int excluded_slot,function<MenuEntry * (MenuEntry *)> procfn)536 void InvMenu::load_inv_items(int item_selector, int excluded_slot,
537                              function<MenuEntry* (MenuEntry*)> procfn)
538 {
539     vector<const item_def *> tobeshown;
540     _get_inv_items_to_show(tobeshown, item_selector, excluded_slot);
541 
542     load_items(tobeshown, procfn);
543 
544     if (!item_count())
545         set_title(no_selectables_message(item_selector));
546     else
547         set_title("");
548 }
549 
get_tiles_for_item(const item_def & item,vector<tile_def> & tileset,bool show_background)550 bool get_tiles_for_item(const item_def &item, vector<tile_def>& tileset, bool show_background)
551 {
552     tileidx_t idx = tileidx_item(get_item_known_info(item));
553     if (!idx)
554         return false;
555 
556     if (in_inventory(item))
557     {
558         const equipment_type eq = item_equip_slot(item);
559         if (eq != EQ_NONE)
560         {
561             if (item.cursed())
562                 tileset.emplace_back(TILE_ITEM_SLOT_EQUIP_CURSED);
563             else
564                 tileset.emplace_back(TILE_ITEM_SLOT_EQUIP);
565         }
566         else if (item.cursed())
567             tileset.emplace_back(TILE_ITEM_SLOT_CURSED);
568 
569         tileidx_t base_item = tileidx_known_base_item(idx);
570         if (base_item)
571             tileset.emplace_back(base_item);
572         tileset.emplace_back(idx);
573 
574         if (eq != EQ_NONE && you.melded[eq])
575             tileset.emplace_back(TILEI_MESH);
576     }
577     else
578     {
579         // Do we want to display the floor type or is that too distracting?
580         const coord_def c = item.held_by_monster()
581             ? item.holding_monster()->pos()
582             : item.pos;
583         tileidx_t ch = 0;
584         if (c != coord_def() && show_background && item.link != ITEM_IN_SHOP)
585         {
586             ch = tileidx_feature(c);
587             if (ch == TILE_FLOOR_NORMAL)
588                 ch = tile_env.flv(c).floor;
589             else if (ch == TILE_WALL_NORMAL)
590                 ch = tile_env.flv(c).wall;
591 
592             tileset.emplace_back(ch);
593         }
594         tileidx_t base_item = tileidx_known_base_item(idx);
595         if (base_item)
596             tileset.emplace_back(base_item);
597 
598         tileset.emplace_back(idx);
599 
600         if (ch != 0)
601         {
602             // Needs to be displayed so as to not give away mimics in shallow water.
603             if (ch == TILE_DNGN_SHALLOW_WATER)
604                 tileset.emplace_back(TILEI_MASK_SHALLOW_WATER);
605             else if (ch == TILE_DNGN_SHALLOW_WATER_MURKY)
606                 tileset.emplace_back(TILEI_MASK_SHALLOW_WATER_MURKY);
607         }
608     }
609     if (item.base_type == OBJ_WEAPONS || item.base_type == OBJ_MISSILES
610         || item.base_type == OBJ_ARMOUR
611 #if TAG_MAJOR_VERSION == 34
612         || item.base_type == OBJ_RODS
613 #endif
614        )
615     {
616         tileidx_t brand = tileidx_known_brand(item);
617         if (brand)
618             tileset.emplace_back(brand);
619     }
620     else if (item.base_type == OBJ_CORPSES)
621     {
622         tileidx_t brand = tileidx_corpse_brand(item);
623         if (brand)
624             tileset.emplace_back(brand);
625     }
626 
627     return true;
628 }
629 
630 #ifdef USE_TILE
get_tiles(vector<tile_def> & tileset) const631 bool InvEntry::get_tiles(vector<tile_def>& tileset) const
632 {
633     if (!Options.tile_menu_icons)
634         return false;
635 
636     // Runes + orb of zot have a special uncollected tile
637     if (quantity <= 0 && (item->base_type != OBJ_RUNES && item->base_type != OBJ_ORBS))
638         return false;
639 
640     return get_tiles_for_item(*item, tileset, show_background);
641 }
642 #else
get_tiles(vector<tile_def> &) const643 bool InvEntry::get_tiles(vector<tile_def>& /*tileset*/) const { return false; }
644 #endif
645 
is_selectable(int index) const646 bool InvMenu::is_selectable(int index) const
647 {
648     if (type == menu_type::drop)
649     {
650         InvEntry *item = dynamic_cast<InvEntry*>(items[index]);
651         if (item->is_cursed() && item->is_equipped())
652             return false;
653 
654         string text = item->get_text();
655 
656         if (text.find("!*") != string::npos || text.find("!d") != string::npos)
657             return false;
658     }
659 
660     return Menu::is_selectable(index);
661 }
662 
663 template <const string &(InvEntry::*method)() const>
compare_item_str(const InvEntry * a,const InvEntry * b)664 static int compare_item_str(const InvEntry *a, const InvEntry *b)
665 {
666     return (a->*method)().compare((b->*method)());
667 }
668 
669 // Would call this just compare_item, but MSVC mistakenly thinks the next
670 // one is a specialization rather than an overload.
671 template <typename T, T (*proc)(const InvEntry *a)>
compare_item_fn(const InvEntry * a,const InvEntry * b)672 static int compare_item_fn(const InvEntry *a, const InvEntry *b)
673 {
674     return int(proc(a)) - int(proc(b));
675 }
676 
677 template <typename T, T (InvEntry::*method)() const>
compare_item(const InvEntry * a,const InvEntry * b)678 static int compare_item(const InvEntry *a, const InvEntry *b)
679 {
680     return int((a->*method)()) - int((b->*method)());
681 }
682 
683 template <typename T, T (InvEntry::*method)() const>
compare_item_rev(const InvEntry * a,const InvEntry * b)684 static int compare_item_rev(const InvEntry *a, const InvEntry *b)
685 {
686     return int((b->*method)()) - int((a->*method)());
687 }
688 
689 template <item_sort_fn cmp>
compare_reverse(const InvEntry * a,const InvEntry * b)690 static int compare_reverse(const InvEntry *a, const InvEntry *b)
691 {
692     return -cmp(a, b);
693 }
694 
695 // We need C++11 already!
696 // Some prototypes to prevent warnings; we can't make these static because
697 // they're used as template parameters.
698 int sort_item_qty(const InvEntry *a);
699 int sort_item_slot(const InvEntry *a);
700 bool sort_item_identified(const InvEntry *a);
701 bool sort_item_charged(const InvEntry *a);
702 
sort_item_qty(const InvEntry * a)703 int sort_item_qty(const InvEntry *a)
704 {
705     return a->quantity;
706 }
sort_item_slot(const InvEntry * a)707 int sort_item_slot(const InvEntry *a)
708 {
709     return a->item->link;
710 }
711 
sort_item_identified(const InvEntry * a)712 bool sort_item_identified(const InvEntry *a)
713 {
714     return !item_type_known(*(a->item));
715 }
716 
sort_item_charged(const InvEntry * a)717 bool sort_item_charged(const InvEntry *a)
718 {
719     return a->item->base_type != OBJ_WANDS
720            || !item_is_evokable(*(a->item), false);
721 }
722 
_compare_invmenu_items(const InvEntry * a,const InvEntry * b,const item_sort_comparators * cmps)723 static bool _compare_invmenu_items(const InvEntry *a, const InvEntry *b,
724                                    const item_sort_comparators *cmps)
725 {
726     for (const auto &comparator : *cmps)
727     {
728         const int cmp = comparator.compare(a, b);
729         if (cmp)
730             return cmp < 0;
731     }
732     return a->item->link < b->item->link;
733 }
734 
735 struct menu_entry_comparator
736 {
737     const menu_sort_condition *cond;
738 
menu_entry_comparatormenu_entry_comparator739     menu_entry_comparator(const menu_sort_condition *c)
740         : cond(c)
741     {
742     }
743 
operator ()menu_entry_comparator744     bool operator () (const MenuEntry* a, const MenuEntry* b) const
745     {
746         const InvEntry *ia = dynamic_cast<const InvEntry *>(a);
747         const InvEntry *ib = dynamic_cast<const InvEntry *>(b);
748         return _compare_invmenu_items(ia, ib, &cond->cmp);
749     }
750 };
751 
init_item_sort_comparators(item_sort_comparators & list,const string & set)752 void init_item_sort_comparators(item_sort_comparators &list, const string &set)
753 {
754     static struct
755     {
756         const string cname;
757         item_sort_fn cmp;
758     } cmp_map[]  =
759       {
760           { "basename",  compare_item_str<&InvEntry::get_basename> },
761           { "qualname",  compare_item_str<&InvEntry::get_qualname> },
762           { "fullname",  compare_item_str<&InvEntry::get_fullname> },
763           { "dbname",    compare_item_str<&InvEntry::get_dbname> },
764           { "curse",     compare_item<bool, &InvEntry::is_cursed> },
765           { "glowing",   compare_item_rev<bool, &InvEntry::is_glowing> },
766           { "ego",       compare_item_rev<bool, &InvEntry::is_ego> },
767           { "art",       compare_item_rev<bool, &InvEntry::is_art> },
768           { "equipped",  compare_item_rev<bool, &InvEntry::is_equipped> },
769           { "identified",compare_item_fn<bool, sort_item_identified> },
770           { "charged",   compare_item_fn<bool, sort_item_charged>},
771           { "qty",       compare_item_fn<int, sort_item_qty> },
772           { "slot",      compare_item_fn<int, sort_item_slot> },
773       };
774 
775     list.clear();
776     for (string s : split_string(",", set))
777     {
778         if (s.empty())
779             continue;
780 
781         const bool negated = s[0] == '>';
782         if (s[0] == '<' || s[0] == '>')
783             s = s.substr(1);
784 
785         for (const auto &ci : cmp_map)
786             if (ci.cname == s)
787             {
788                 list.emplace_back(ci.cmp, negated);
789                 break;
790             }
791     }
792 
793     if (list.empty())
794         list.emplace_back(compare_item_str<&InvEntry::get_fullname>);
795 }
796 
find_menu_sort_condition() const797 const menu_sort_condition *InvMenu::find_menu_sort_condition() const
798 {
799     for (int i = Options.sort_menus.size() - 1; i >= 0; --i)
800         if (Options.sort_menus[i].matches(type))
801             return &Options.sort_menus[i];
802 
803     return nullptr;
804 }
805 
sort_menu(vector<InvEntry * > & invitems,const menu_sort_condition * cond)806 void InvMenu::sort_menu(vector<InvEntry*> &invitems,
807                         const menu_sort_condition *cond)
808 {
809     if (!cond || cond->sort == -1 || (int) invitems.size() < cond->sort)
810         return;
811 
812     sort(invitems.begin(), invitems.end(), menu_entry_comparator(cond));
813 }
814 
815 FixedVector<int, NUM_OBJECT_CLASSES> inv_order(
816     OBJ_WEAPONS,
817     OBJ_MISSILES,
818     OBJ_ARMOUR,
819     OBJ_STAVES,
820 #if TAG_MAJOR_VERSION == 34
821     OBJ_RODS,
822 #endif
823     OBJ_JEWELLERY,
824     OBJ_WANDS,
825     OBJ_SCROLLS,
826     OBJ_POTIONS,
827     OBJ_BOOKS,
828     OBJ_MISCELLANY,
829 #if TAG_MAJOR_VERSION == 34
830     OBJ_FOOD,
831 #endif
832     // These four can't actually be in your inventory.
833     OBJ_CORPSES,
834     OBJ_RUNES,
835     OBJ_ORBS,
836     OBJ_GOLD);
837 
load_items(const vector<item_def> & mitems,function<MenuEntry * (MenuEntry *)> procfn,menu_letter ckey,bool sort)838 menu_letter InvMenu::load_items(const vector<item_def>& mitems,
839                                 function<MenuEntry* (MenuEntry*)> procfn,
840                                 menu_letter ckey, bool sort)
841 {
842     vector<const item_def*> xlatitems;
843     for (const item_def &item : mitems)
844         xlatitems.push_back(&item);
845     return load_items(xlatitems, procfn, ckey, sort);
846 }
847 
load_items(const vector<const item_def * > & mitems,function<MenuEntry * (MenuEntry *)> procfn,menu_letter ckey,bool sort)848 menu_letter InvMenu::load_items(const vector<const item_def*> &mitems,
849                                 function<MenuEntry* (MenuEntry*)> procfn,
850                                 menu_letter ckey, bool sort)
851 {
852     FixedVector< int, NUM_OBJECT_CLASSES > inv_class(0);
853     for (const item_def * const mitem : mitems)
854         inv_class[mitem->base_type]++;
855 
856     vector<InvEntry*> items_in_class;
857     const menu_sort_condition *cond = nullptr;
858     if (sort) cond = find_menu_sort_condition();
859 
860     for (int obj = 0; obj < NUM_OBJECT_CLASSES; ++obj)
861     {
862         int i = inv_order[obj];
863 
864         if (!inv_class[i])
865             continue;
866 
867         string subtitle = item_class_name(i);
868 
869         // Mention the class selection shortcuts.
870         if (is_set(MF_MULTISELECT))
871         {
872             vector<char> glyphs;
873             get_class_hotkeys(i, glyphs);
874             if (!glyphs.empty())
875             {
876                 // longest string
877                 const string str = "Magical Staves ";
878                 subtitle += string(strwidth(str) - strwidth(subtitle),
879                                    ' ');
880                 subtitle += "(select all with <w>";
881                 for (char gly : glyphs)
882                     subtitle += gly;
883                 subtitle += "</w><blue>)";
884             }
885         }
886         add_entry(new MenuEntry(subtitle, MEL_SUBTITLE));
887 
888         items_in_class.clear();
889 
890         InvEntry *forced_first = nullptr;
891         for (const item_def * const mitem : mitems)
892         {
893             if (mitem->base_type != i)
894                 continue;
895 
896             InvEntry * const ie = new InvEntry(*mitem);
897             if (mitem->sub_type == get_max_subtype(mitem->base_type))
898                 forced_first = ie;
899             else
900                 items_in_class.push_back(ie);
901         }
902 
903         sort_menu(items_in_class, cond);
904         if (forced_first)
905             items_in_class.insert(items_in_class.begin(),forced_first);
906 
907         for (InvEntry *ie : items_in_class)
908         {
909             if (tag == "pickup")
910             {
911                 if (ie->item && item_is_stationary(*ie->item))
912                     ie->tag = "nopickup";
913                 else
914                     ie->tag = "pickup";
915             }
916             if (get_flags() & MF_NOSELECT)
917                 ie->hotkeys.clear();
918             // If there's no hotkey, provide one.
919             else if (ie->hotkeys[0] == ' ')
920             {
921                 if (ie->tag == "nopickup")
922                     ie->hotkeys.clear();
923                 else
924                     ie->hotkeys[0] = ckey++;
925             }
926             do_preselect(ie);
927 
928             add_entry(procfn ? procfn(ie) : ie);
929         }
930     }
931 
932     return ckey;
933 }
934 
do_preselect(InvEntry * ie)935 void InvMenu::do_preselect(InvEntry *ie)
936 {
937     if (!pre_select || pre_select->empty())
938         return;
939 
940     for (const SelItem &presel : *pre_select)
941         if (ie->item && ie->item == presel.item)
942         {
943             ie->selected_qty = presel.quantity;
944             break;
945         }
946 }
947 
get_selitems() const948 vector<SelItem> InvMenu::get_selitems() const
949 {
950     vector<SelItem> selected_items;
951     for (MenuEntry *me : sel)
952     {
953         InvEntry *inv = dynamic_cast<InvEntry*>(me);
954         selected_items.emplace_back(inv->hotkeys[0], inv->selected_qty,
955                                     inv->item, inv->has_star());
956     }
957     return selected_items;
958 }
959 
help_key() const960 string InvMenu::help_key() const
961 {
962     return type == menu_type::drop || type == menu_type::pickup ? "pick-up"
963                                                                 : "";
964 }
965 
getkey() const966 int InvMenu::getkey() const
967 {
968     auto mkey = lastch;
969     if (type == menu_type::know && (mkey == 0 || mkey == CK_ENTER))
970         return mkey;
971 
972     if (!isaalnum(mkey) && mkey != '$' && mkey != '-' && mkey != '?'
973         && mkey != '*' && !key_is_escape(mkey) && mkey != '\\'
974         && mkey != ',')
975     {
976         mkey = ' ';
977     }
978     return mkey;
979 }
980 
981 //////////////////////////////////////////////////////////////////////////////
982 
in_inventory(const item_def & i)983 bool in_inventory(const item_def &i)
984 {
985     return i.pos == ITEM_IN_INVENTORY;
986 }
987 
item_class_name(int type,bool terse)988 const char *item_class_name(int type, bool terse)
989 {
990     if (terse)
991     {
992         switch (type)
993         {
994         case OBJ_STAVES:     return "magical staff";
995         case OBJ_MISCELLANY: return "misc";
996         default:             return base_type_string((object_class_type) type);
997         }
998     }
999     else
1000     {
1001         switch (type)
1002         {
1003         case OBJ_GOLD:       return "Gold";
1004         case OBJ_WEAPONS:    return "Hand Weapons";
1005         case OBJ_MISSILES:   return "Missiles";
1006         case OBJ_ARMOUR:     return "Armour";
1007         case OBJ_WANDS:      return "Wands";
1008 #if TAG_MAJOR_VERSION == 34
1009         case OBJ_FOOD:       return "Comestibles";
1010 #endif
1011         case OBJ_SCROLLS:    return "Scrolls";
1012         case OBJ_JEWELLERY:  return "Jewellery";
1013         case OBJ_POTIONS:    return "Potions";
1014         case OBJ_BOOKS:      return "Books";
1015         case OBJ_STAVES:     return "Magical Staves";
1016 #if TAG_MAJOR_VERSION == 34
1017         case OBJ_RODS:       return "Rods";
1018 #endif
1019         case OBJ_ORBS:       return "Orbs of Power";
1020         case OBJ_MISCELLANY: return "Miscellaneous";
1021         case OBJ_CORPSES:    return "Carrion";
1022         case OBJ_RUNES:      return "Runes of Zot";
1023         }
1024     }
1025     return "";
1026 }
1027 
item_slot_name(equipment_type type)1028 const char* item_slot_name(equipment_type type)
1029 {
1030     switch (type)
1031     {
1032     case EQ_CLOAK:       return "cloak";
1033     case EQ_HELMET:      return "helmet";
1034     case EQ_GLOVES:      return "gloves";
1035     case EQ_BOOTS:       return "boots";
1036     case EQ_SHIELD:      return "shield";
1037     case EQ_BODY_ARMOUR: return "body";
1038     default:             return "";
1039     }
1040 }
1041 
select_items(const vector<const item_def * > & items,const char * title,bool noselect,menu_type mtype)1042 vector<SelItem> select_items(const vector<const item_def*> &items,
1043                              const char *title, bool noselect,
1044                              menu_type mtype)
1045 {
1046     vector<SelItem> selected;
1047     if (!items.empty())
1048     {
1049         InvMenu menu;
1050         menu.set_type(mtype);
1051         menu.set_title(title);
1052         if (mtype == menu_type::pickup)
1053             menu.set_tag("pickup");
1054 
1055         menu.load_items(items);
1056         int new_flags = noselect ? MF_NOSELECT
1057                                  : MF_MULTISELECT | MF_ALLOW_FILTER;
1058 
1059         if (mtype == menu_type::sel_one)
1060         {
1061             new_flags |= MF_SINGLESELECT;
1062             new_flags &= ~MF_MULTISELECT;
1063         }
1064 
1065         new_flags |= MF_ALLOW_FORMATTING;
1066         new_flags |= menu.get_flags() & MF_USE_TWO_COLUMNS;
1067         menu.set_flags(new_flags);
1068         menu.show();
1069         selected = menu.get_selitems();
1070     }
1071     return selected;
1072 }
1073 
item_is_selected(const item_def & i,int selector)1074 bool item_is_selected(const item_def &i, int selector)
1075 {
1076     const object_class_type itype = i.base_type;
1077     if (selector == OSEL_ANY || selector == itype
1078                                 && itype != OBJ_ARMOUR)
1079     {
1080         return true;
1081     }
1082 
1083     switch (selector)
1084     {
1085     case OBJ_ARMOUR:
1086         return itype == OBJ_ARMOUR && can_wear_armour(i, false, false);
1087 
1088     case OSEL_WORN_ARMOUR:
1089         return itype == OBJ_ARMOUR && item_is_equipped(i);
1090 
1091     case OSEL_UNIDENT:
1092         return !fully_identified(i) && itype != OBJ_BOOKS;
1093 
1094     case OBJ_MISSILES:
1095         return itype == OBJ_MISSILES || itype == OBJ_WEAPONS;
1096 
1097     case OSEL_THROWABLE:
1098     {
1099         if (itype != OBJ_WEAPONS && itype != OBJ_MISSILES)
1100             return false;
1101 
1102         const launch_retval projected = is_launched(&you, you.weapon(), i);
1103 
1104         if (projected == launch_retval::FUMBLED)
1105             return false;
1106 
1107         return true;
1108     }
1109     case OBJ_WEAPONS:
1110     case OSEL_WIELD:
1111         return item_is_wieldable(i);
1112 
1113     case OSEL_EVOKABLE:
1114         // assumes valid link...would break with evoking from floor?
1115         return item_is_evokable(i, true) && item_is_evokable(i, true, false);//evoke_check(i.link, true);
1116 
1117     case OSEL_ENCHANTABLE_ARMOUR:
1118         return is_enchantable_armour(i, true);
1119 
1120     case OSEL_CURSED_WORN:
1121         return i.cursed() && item_is_equipped(i);
1122 
1123 #if TAG_MAJOR_VERSION == 34
1124     case OSEL_UNCURSED_WORN_ARMOUR:
1125         return !i.cursed() && item_is_equipped(i) && itype == OBJ_ARMOUR;
1126 
1127     case OSEL_UNCURSED_WORN_JEWELLERY:
1128         return !i.cursed() && item_is_equipped(i) && itype == OBJ_JEWELLERY;
1129 #endif
1130 
1131     case OSEL_BRANDABLE_WEAPON:
1132         return is_brandable_weapon(i, true);
1133 
1134     case OSEL_ENCHANTABLE_WEAPON:
1135         return itype == OBJ_WEAPONS
1136                && !is_artefact(i)
1137                && (!item_ident(i, ISFLAG_KNOW_PLUSES)
1138                    || i.plus < MAX_WPN_ENCHANT);
1139 
1140     case OSEL_BLESSABLE_WEAPON:
1141         return is_brandable_weapon(i, you_worship(GOD_SHINING_ONE), true);
1142 
1143     case OSEL_BEOGH_GIFT:
1144         return (itype == OBJ_WEAPONS
1145                 || is_shield(i)
1146                 || itype == OBJ_ARMOUR
1147                    && get_armour_slot(i) == EQ_BODY_ARMOUR)
1148                 && !item_is_equipped(i);
1149 
1150     case OSEL_CURSABLE:
1151         return item_is_equipped(i) && item_is_cursable(i);
1152 
1153     case OSEL_UNCURSED_WORN_RINGS:
1154         return !i.cursed() && item_is_equipped(i) && itype == OBJ_JEWELLERY
1155             && !jewellery_is_amulet(i);
1156 
1157     case OSEL_QUIVER_ACTION:
1158         return in_inventory(i) && quiver::slot_to_action(i.link)->is_valid();
1159     case OSEL_QUIVER_ACTION_FORCE:
1160         return in_inventory(i) && quiver::slot_to_action(i.link, true)->is_valid();
1161 
1162     default:
1163         return false;
1164     }
1165 }
1166 
_get_inv_items_to_show(vector<const item_def * > & v,int selector,int excluded_slot)1167 static void _get_inv_items_to_show(vector<const item_def*> &v,
1168                                    int selector, int excluded_slot)
1169 {
1170     for (const auto &item : you.inv)
1171     {
1172         if (item.defined()
1173             && item.link != excluded_slot
1174             && item_is_selected(item, selector))
1175         {
1176             v.push_back(&item);
1177         }
1178     }
1179 }
1180 
1181 
1182 /**
1183  * Does the player have any items of the given type?
1184  *
1185  * @param selector          A object_selector.
1186  * @param excluded_slot     An item slot to ignore.
1187  * @param inspect_floor     If true, also check the floor where the player is
1188  *                          standing.
1189  *
1190  * @return                  Whether there are any items matching the given
1191  *                          selector in the player's inventory.
1192  */
any_items_of_type(int selector,int excluded_slot,bool inspect_floor)1193 bool any_items_of_type(int selector, int excluded_slot, bool inspect_floor)
1194 {
1195     bool ret = any_of(begin(you.inv), end(you.inv),
1196                   [=] (const item_def &item) -> bool
1197                   {
1198                       return item.defined() && item.link != excluded_slot
1199                           && item_is_selected(item, selector);
1200                   });
1201     if (!ret && inspect_floor)
1202     {
1203         auto item_floor = item_list_on_square(you.visible_igrd(you.pos()));
1204         ret = any_of(begin(item_floor), end(item_floor),
1205                       [=] (const item_def* item) -> bool
1206                       {
1207                           return item->defined()
1208                                     && item_is_selected(*item, selector);
1209                       });
1210     }
1211     return ret;
1212 }
1213 
1214 // Use title = nullptr for stock Inventory title
1215 // type = menu_type::drop allows the multidrop toggle
_invent_select(const char * title=nullptr,menu_type type=menu_type::invlist,int item_selector=OSEL_ANY,int excluded_slot=-1,int flags=MF_NOSELECT,invtitle_annotator titlefn=nullptr,vector<SelItem> * items=nullptr,vector<text_pattern> * filter=nullptr,Menu::selitem_tfn selitemfn=nullptr,const vector<SelItem> * pre_select=nullptr)1216 static unsigned char _invent_select(const char *title = nullptr,
1217                                     menu_type type = menu_type::invlist,
1218                                     int item_selector = OSEL_ANY,
1219                                     int excluded_slot = -1,
1220                                     int flags = MF_NOSELECT,
1221                                     invtitle_annotator titlefn = nullptr,
1222                                     vector<SelItem> *items = nullptr,
1223                                     vector<text_pattern> *filter = nullptr,
1224                                     Menu::selitem_tfn selitemfn = nullptr,
1225                                     const vector<SelItem> *pre_select = nullptr)
1226 {
1227     InvMenu menu(flags | MF_ALLOW_FORMATTING);
1228 
1229     menu.set_preselect(pre_select);
1230     menu.set_title_annotator(titlefn);
1231     menu.f_selitem = selitemfn;
1232     if (filter)
1233         menu.set_select_filter(*filter);
1234     menu.load_inv_items(item_selector, excluded_slot);
1235     menu.set_type(type);
1236 
1237     // Don't override title if there are no items.
1238     if (title && menu.item_count())
1239         menu.set_title(title);
1240 
1241     menu.show(true);
1242 
1243     if (items)
1244         *items = menu.get_selitems();
1245 
1246     return menu.getkey();
1247 }
1248 
display_inventory()1249 void display_inventory()
1250 {
1251     InvMenu menu(MF_SINGLESELECT | MF_ALLOW_FORMATTING);
1252     menu.load_inv_items(OSEL_ANY, -1);
1253     menu.set_type(menu_type::invlist);
1254 
1255     menu.on_single_selection = [](const MenuEntry& item)
1256     {
1257         unsigned char select = item.hotkeys[0];
1258         const int invidx = letter_to_index(select);
1259         ASSERT(you.inv[invidx].defined());
1260         return describe_item(you.inv[invidx]);
1261     };
1262 
1263     menu.show(true);
1264     if (!crawl_state.doing_prev_cmd_again)
1265     {
1266         redraw_screen();
1267         update_screen();
1268     }
1269 }
1270 
1271 // Reads in digits for a count and apprends then to val, the
1272 // return value is the character that stopped the reading.
_get_invent_quant(unsigned char keyin,int & quant)1273 static unsigned char _get_invent_quant(unsigned char keyin, int &quant)
1274 {
1275     quant = keyin - '0';
1276 
1277     while (true)
1278     {
1279         keyin = get_ch();
1280 
1281         if (!isadigit(keyin))
1282             break;
1283 
1284         quant *= 10;
1285         quant += (keyin - '0');
1286 
1287         if (quant > 9999999)
1288         {
1289             quant = 9999999;
1290             keyin = '\0';
1291             break;
1292         }
1293     }
1294 
1295     return keyin;
1296 }
1297 
_drop_selitem_text(const vector<MenuEntry * > * s)1298 static string _drop_selitem_text(const vector<MenuEntry*> *s)
1299 {
1300     bool extraturns = false;
1301 
1302     if (s->empty())
1303         return "";
1304 
1305     for (MenuEntry *entry : *s)
1306     {
1307         const item_def *item = static_cast<item_def *>(entry->data);
1308         const int eq = get_equip_slot(item);
1309         if (eq > EQ_WEAPON && eq < NUM_EQUIP)
1310         {
1311             extraturns = true;
1312             break;
1313         }
1314     }
1315 
1316     return make_stringf(" (%u%s turn%s)",
1317                         (unsigned int)s->size(),
1318                         extraturns? "+" : "",
1319                         s->size() > 1? "s" : "");
1320 }
1321 
_drop_prompt(bool as_menu_title,bool menu_autopickup_mode)1322 static string _drop_prompt(bool as_menu_title, bool menu_autopickup_mode)
1323 {
1324     string prompt_base;
1325 
1326     if (as_menu_title && menu_autopickup_mode)
1327         prompt_base = "Drop (and turn off autopickup for) what? ";
1328     else if (as_menu_title)
1329         prompt_base = "Drop what?                               ";
1330     else
1331         prompt_base = "Drop what? ";
1332     return prompt_base + slot_description()
1333 #ifdef TOUCH_UI
1334                           + " (<Enter> or tap header to drop)";
1335 #else
1336                           + " (_ for help)";
1337 #endif
1338 }
1339 
_drop_menu_titlefn(const Menu * m,const string &)1340 static string _drop_menu_titlefn(const Menu *m, const string &)
1341 {
1342     const InvMenu *invmenu = static_cast<const InvMenu *>(m);
1343     return _drop_prompt(true, invmenu->mode_special_drop());
1344 }
1345 
1346 /**
1347  * Prompt the player to select zero or more items to drop.
1348  * TODO: deduplicate/merge with prompt_invent_item().
1349  *
1350  * @param   Items already selected to drop.
1351  * @return  The total set of items the player's chosen to drop.
1352  */
prompt_drop_items(const vector<SelItem> & preselected_items)1353 vector<SelItem> prompt_drop_items(const vector<SelItem> &preselected_items)
1354 {
1355     unsigned char  keyin = '?';
1356     int            ret = PROMPT_ABORT;
1357 
1358     bool           need_redraw = false;
1359     bool           need_prompt = true;
1360     bool           need_getch  = false;
1361 
1362     vector<SelItem> items;
1363     int count = -1;
1364     while (true)
1365     {
1366         if (need_redraw && !crawl_state.doing_prev_cmd_again)
1367         {
1368             redraw_screen();
1369             update_screen();
1370             clear_messages();
1371         }
1372 
1373         if (need_prompt)
1374         {
1375             const string prompt = _drop_prompt(false, false);
1376             mprf(MSGCH_PROMPT, "%s (<w>?</w> for menu, <w>Esc</w> to quit)",
1377                  prompt.c_str());
1378         }
1379 
1380         if (need_getch)
1381             keyin = get_ch();
1382 
1383         need_redraw = false;
1384         need_prompt = true;
1385         need_getch  = true;
1386 
1387         if (keyin == '_')
1388             show_specific_help("pick-up");
1389         else if (keyin == '?' || keyin == '*' || keyin == ',')
1390         {
1391             // The "view inventory listing" mode.
1392             const int ch = _invent_select("",
1393                                           menu_type::drop,
1394                                           OSEL_ANY,
1395                                           -1,
1396                                           MF_MULTISELECT | MF_ALLOW_FILTER,
1397                                           _drop_menu_titlefn,
1398                                           &items,
1399                                           &Options.drop_filter,
1400                                           _drop_selitem_text,
1401                                           &preselected_items);
1402 
1403             if (key_is_escape(ch))
1404             {
1405                 keyin       = ch;
1406                 need_prompt = false;
1407                 need_getch  = false;
1408             }
1409             else
1410             {
1411                 keyin       = 0;
1412                 need_prompt = true;
1413                 need_getch  = true;
1414             }
1415 
1416             if (!items.empty())
1417             {
1418                 if (!crawl_state.doing_prev_cmd_again)
1419                 {
1420                     redraw_screen();
1421                     update_screen();
1422                     clear_messages();
1423                 }
1424 
1425                 for (SelItem &sel : items)
1426                     sel.slot = letter_to_index(sel.slot);
1427                 return items;
1428             }
1429 
1430             need_redraw = !(keyin == '?' || keyin == '*'
1431                             || keyin == ',' || keyin == '+');
1432         }
1433         else if (isadigit(keyin))
1434         {
1435             // The "read in quantity" mode
1436             keyin = _get_invent_quant(keyin, count);
1437 
1438             need_prompt = false;
1439             need_getch  = false;
1440         }
1441         else if (key_is_escape(keyin)
1442                 || (Options.easy_quit_item_prompts && keyin == ' '))
1443         {
1444             ret = PROMPT_ABORT;
1445             break;
1446         }
1447         else if (isaalpha(keyin))
1448         {
1449             ret = letter_to_index(keyin);
1450 
1451             if (!you.inv[ret].defined())
1452                 mpr("You don't have any such object.");
1453             else
1454                 break;
1455         }
1456         else if (keyin == ';')
1457         {
1458             ret = you.last_unequip;
1459             break;
1460         }
1461         else if (!isspace(keyin))
1462         {
1463             // We've got a character we don't understand...
1464             canned_msg(MSG_HUH);
1465         }
1466         else
1467         {
1468             // We're going to loop back up, so don't draw another prompt.
1469             need_prompt = false;
1470         }
1471     }
1472 
1473     if (ret != PROMPT_ABORT)
1474         items.emplace_back(ret, count, &you.inv[ret]);
1475     return items;
1476 }
1477 
item_matches_digit_inscription(item_def & item,char digit,operation_types oper)1478 static bool item_matches_digit_inscription(item_def &item, char digit, operation_types oper)
1479 {
1480     const string& r(item.inscription);
1481     const char iletter = static_cast<char>(oper);
1482     for (unsigned int j = 0; j + 2 < r.size(); ++j)
1483         if (r[j] == '@' && (r[j+1] == iletter || r[j+1] == '*') && r[j+2] == digit)
1484             return true;
1485     return false;
1486 }
1487 
digit_inscription_to_item(char digit,operation_types oper)1488 item_def *digit_inscription_to_item(char digit, operation_types oper)
1489 {
1490     for (int i = 0; i < ENDOFPACK; ++i)
1491         if (you.inv[i].defined()
1492                 && item_matches_digit_inscription(you.inv[i], digit, oper))
1493         {
1494             return &you.inv[i];
1495         }
1496 
1497     for (stack_iterator si(you.pos(), true); si; ++si)
1498         if (item_matches_digit_inscription(*si, digit, oper))
1499             return &*si;
1500 
1501     return nullptr;
1502 }
1503 
_has_warning_inscription(const item_def & item,operation_types oper)1504 static bool _has_warning_inscription(const item_def& item,
1505                              operation_types oper)
1506 {
1507     const char iletter = static_cast<char>(oper);
1508 
1509     const string& r(item.inscription);
1510     for (unsigned int i = 0; i + 1 < r.size(); ++i)
1511     {
1512         if (r[i] == '!')
1513         {
1514             if (r[i+1] == iletter || r[i+1] == '*')
1515                 return true;
1516             else if (oper == OPER_ZAP && r[i+1] == 'z') // for the 0.3.4. keys
1517                 return true;
1518             else if (oper == OPER_EVOKE
1519                      && (r[i+1] == 'V' || toalower(r[i+1]) == 'z'))
1520             {
1521                 return true;
1522             }
1523         }
1524     }
1525 
1526     return false;
1527 }
1528 
1529 // In order to equip this item, we may need to remove an old item in the
1530 // corresponding slot which has a warning inscription. If this is the case,
1531 // prompt the user for confirmation.
check_old_item_warning(const item_def & item,operation_types oper,bool check_melded)1532 bool check_old_item_warning(const item_def& item,
1533                             operation_types oper,
1534                             bool check_melded)
1535 {
1536     item_def old_item;
1537     string prompt;
1538     bool penance = false;
1539     if (oper == OPER_WIELD) // can we safely unwield old item?
1540     {
1541         if (!you.slot_item(EQ_WEAPON, check_melded))
1542             return true;
1543 
1544         int equip = you.equip[EQ_WEAPON];
1545         if (equip == -1 || item.link == equip)
1546             return true;
1547 
1548         old_item = *you.slot_item(EQ_WEAPON, check_melded);
1549         if (!needs_handle_warning(old_item, OPER_WIELD, penance))
1550             return true;
1551 
1552         prompt += "Really unwield ";
1553     }
1554     else if (oper == OPER_WEAR) // can we safely take off old item?
1555     {
1556         if (item.base_type != OBJ_ARMOUR)
1557             return true;
1558 
1559         equipment_type eq_slot = get_armour_slot(item);
1560         int equip = you.equip[eq_slot];
1561         if (equip == -1 || item.link == equip)
1562             return true;
1563 
1564         old_item = you.inv[you.equip[eq_slot]];
1565 
1566         if (!needs_handle_warning(old_item, OPER_TAKEOFF, penance))
1567             return true;
1568 
1569         prompt += "Really take off ";
1570     }
1571     else if (oper == OPER_PUTON) // can we safely remove old item?
1572     {
1573         if (item.base_type != OBJ_JEWELLERY)
1574             return true;
1575 
1576         if (jewellery_is_amulet(item))
1577         {
1578             int equip = you.equip[EQ_AMULET];
1579             if (equip == -1 || item.link == equip)
1580                 return true;
1581 
1582             old_item = you.inv[equip];
1583             if (!needs_handle_warning(old_item, OPER_TAKEOFF, penance))
1584                 return true;
1585 
1586             prompt += "Really remove ";
1587         }
1588         else // rings handled in prompt_ring_to_remove
1589             return true;
1590     }
1591     else // anything else doesn't have a counterpart
1592         return true;
1593 
1594     // now ask
1595     if (old_item.cursed())
1596         prompt += "and destroy ";
1597     prompt += old_item.name(DESC_INVENTORY);
1598     prompt += "?";
1599     if (penance)
1600         prompt += " This could place you under penance!";
1601     return yesno(prompt.c_str(), false, 'n');
1602 }
1603 
_operation_verb(operation_types oper)1604 static string _operation_verb(operation_types oper)
1605 {
1606     switch (oper)
1607     {
1608     case OPER_WIELD:          return "wield";
1609     case OPER_QUAFF:          return "quaff";
1610     case OPER_DROP:           return "drop";
1611     case OPER_TAKEOFF:        return "take off";
1612     case OPER_WEAR:           return "wear";
1613     case OPER_PUTON:          return "put on";
1614     case OPER_REMOVE:         return "remove";
1615     case OPER_READ:           return "read";
1616     case OPER_MEMORISE:       return "memorise from";
1617     case OPER_ZAP:            return "zap";
1618     case OPER_FIRE:           return "fire";
1619     case OPER_EVOKE:          return "evoke";
1620     case OPER_DESTROY:        return "destroy";
1621     case OPER_QUIVER:         return "quiver";
1622     case OPER_ANY:
1623     default:
1624         return "choose";
1625     }
1626 }
1627 
_is_wielded(const item_def & item)1628 static bool _is_wielded(const item_def &item)
1629 {
1630     int equip = you.equip[EQ_WEAPON];
1631     return equip != -1 && item.link == equip;
1632 }
1633 
_is_known_no_tele_item(const item_def & item)1634 static bool _is_known_no_tele_item(const item_def &item)
1635 {
1636     if (!is_artefact(item))
1637         return false;
1638 
1639     return artefact_known_property(item, ARTP_PREVENT_TELEPORTATION);
1640 }
1641 
needs_notele_warning(const item_def & item,operation_types oper)1642 bool needs_notele_warning(const item_def &item, operation_types oper)
1643 {
1644     return (oper == OPER_PUTON || oper == OPER_WEAR
1645                 || oper == OPER_WIELD && !_is_wielded(item))
1646                 && (_is_known_no_tele_item(item) && you.duration[DUR_TELEPORT]);
1647 }
1648 
needs_handle_warning(const item_def & item,operation_types oper,bool & penance)1649 bool needs_handle_warning(const item_def &item, operation_types oper,
1650                           bool &penance)
1651 {
1652     if (_has_warning_inscription(item, oper))
1653         return true;
1654 
1655     // Curses first. Warn if something would take off (i.e. destroy) the cursed item.
1656     if (item.cursed()
1657         && (oper == OPER_WIELD && is_weapon(item)
1658             || oper == OPER_PUTON || oper == OPER_WEAR
1659             || oper == OPER_TAKEOFF || oper == OPER_REMOVE))
1660     {
1661         return true;
1662     }
1663 
1664     // The consequences of evokables are generally known.
1665     if (item.base_type == OBJ_MISCELLANY
1666         && oper == OPER_EVOKE && god_hates_item(item))
1667     {
1668         penance = true;
1669         return true;
1670     }
1671 
1672     // Everything else depends on knowing the item subtype/brand.
1673     if (!item_type_known(item))
1674         return false;
1675 
1676     if (oper == OPER_REMOVE
1677         && item.is_type(OBJ_JEWELLERY, AMU_FAITH)
1678         && !(you_worship(GOD_RU) && you.piety >= piety_breakpoint(5))
1679         && !you_worship(GOD_GOZAG)
1680         && !you_worship(GOD_NO_GOD)
1681         && !you_worship(GOD_XOM)
1682         && !you_worship(GOD_ASHENZARI))
1683     {
1684         return true;
1685     }
1686 
1687     if (needs_notele_warning(item, oper))
1688         return true;
1689 
1690     if (oper == OPER_ATTACK && god_hates_item(item)
1691 #if TAG_MAJOR_VERSION == 34
1692         && !you_worship(GOD_PAKELLAS)
1693 #endif
1694        )
1695     {
1696         penance = true;
1697         return true;
1698     }
1699 
1700     if (oper == OPER_WIELD // unwielding uses OPER_WIELD too
1701         && is_weapon(item))
1702     {
1703         if (get_weapon_brand(item) == SPWPN_DISTORTION
1704             && !have_passive(passive_t::safe_distortion))
1705         {
1706             return true;
1707         }
1708 
1709         if (is_artefact(item) && artefact_property(item, ARTP_CONTAM))
1710         {
1711             if (_is_wielded(item) && you_worship(GOD_ZIN))
1712                 penance = true;
1713             return true;
1714         }
1715 
1716         if (is_artefact(item) && (artefact_property(item, ARTP_DRAIN)
1717                                   || artefact_property(item, ARTP_FRAGILE)))
1718         {
1719             return true;
1720         }
1721     }
1722 
1723     if (oper == OPER_PUTON || oper == OPER_WEAR || oper == OPER_TAKEOFF
1724         || oper == OPER_REMOVE)
1725     {
1726         if (is_artefact(item) && artefact_property(item, ARTP_CONTAM))
1727         {
1728             if ((oper == OPER_TAKEOFF || oper == OPER_REMOVE)
1729                  && you_worship(GOD_ZIN))
1730             {
1731                 penance = true;
1732             }
1733             return true;
1734         }
1735 
1736         if (is_artefact(item) && (artefact_property(item, ARTP_DRAIN)
1737                                   || artefact_property(item, ARTP_FRAGILE)))
1738         {
1739             return true;
1740         }
1741     }
1742 
1743     if (oper == OPER_EVOKE && god_hates_item(item))
1744     {
1745         penance = true;
1746         return true;
1747     }
1748 
1749     return false;
1750 }
1751 
1752 // If there are warning inscriptions associated with performing this operation
1753 // on this item, prompt the user for confirmation. Return true if all prompts
1754 // are OK'd.
check_warning_inscriptions(const item_def & item,operation_types oper)1755 bool check_warning_inscriptions(const item_def& item,
1756                                  operation_types oper)
1757 {
1758     bool penance = false;
1759     if (item.defined()
1760         && needs_handle_warning(item, oper, penance))
1761     {
1762         // When it's about destroying an item, don't even ask.
1763         // If the player really wants to do that, they'll have
1764         // to remove the inscription.
1765         if (oper == OPER_DESTROY)
1766             return false;
1767 
1768         // Common pattern for wield/wear/put:
1769         // - if the player isn't capable of equipping it, return true
1770         //   immediately. No point warning, since the op is impossible.
1771         // - if the item is already worn, treat this as the corresponding
1772         //   unequip operation
1773         if (oper == OPER_WIELD)
1774         {
1775             // Can't use can_wield in item-use.cc because it wants
1776             // a non-const item_def.
1777             if (!you.can_wield(item))
1778                 return true;
1779 
1780             int equip = you.equip[EQ_WEAPON];
1781             if (equip != -1 && item.link == equip)
1782                 return check_old_item_warning(item, oper);
1783         }
1784         else if (oper == OPER_WEAR)
1785         {
1786             if (!can_wear_armour(item, false, false))
1787                 return true;
1788 
1789             int equip = you.equip[get_armour_slot(item)];
1790             if (equip != -1 && item.link == equip)
1791                 return check_old_item_warning(item, oper);
1792         }
1793         else if (oper == OPER_PUTON)
1794         {
1795             if (item.base_type != OBJ_JEWELLERY)
1796                 return true;
1797 
1798             if (jewellery_is_amulet(item))
1799             {
1800                 int equip = you.equip[EQ_AMULET];
1801                 if (equip != -1 && item.link == equip)
1802                     return check_old_item_warning(item, oper);
1803             }
1804             else
1805             {
1806                 for (int slots = EQ_FIRST_JEWELLERY; slots <= EQ_LAST_JEWELLERY; ++slots)
1807                 {
1808                     if (slots == EQ_AMULET)
1809                         continue;
1810 
1811                     int equip = you.equip[slots];
1812                     if (equip != -1 && item.link == equip)
1813                         return check_old_item_warning(item, oper);
1814                 }
1815             }
1816         }
1817         else if (oper == OPER_REMOVE || oper == OPER_TAKEOFF)
1818         {
1819             // Don't ask if it will fail anyway.
1820             if (item.cursed())
1821                 return true;
1822         }
1823 
1824         // XXX: duplicates a check in delay.cc:_finish_delay()
1825         string prompt = "Really " + _operation_verb(oper) + " ";
1826         prompt += (in_inventory(item) ? item.name(DESC_INVENTORY)
1827                                       : item.name(DESC_A));
1828         if (needs_notele_warning(item, oper)
1829             && item_ident(item, ISFLAG_KNOW_TYPE))
1830         {
1831             prompt += " while about to teleport";
1832         }
1833         prompt += "?";
1834         if (penance)
1835             prompt += " This could place you under penance!";
1836         return yesno(prompt.c_str(), false, 'n')
1837                && check_old_item_warning(item, oper);
1838     }
1839     else
1840         return check_old_item_warning(item, oper);
1841 }
1842 
1843 /**
1844  * Prompts the user for an item.
1845  *
1846  * Prompts the user for an item, handling the '?' and '*' listings,
1847  * and returns the inventory slot to the caller (which if
1848  * must_exist is true (the default) will be an assigned item), with
1849  * a positive quantity.
1850  *
1851  * Note: Does not check if the item is appropriate.
1852  *
1853  * @param prompt           The question to ask the user.
1854  * @param mtype            The menu type.
1855  * @param type_expect      The object_class_type or object_selector for
1856  *                         items to be listed.
1857  * @param oper             The operation_type that will be used on the result.
1858  *                         Modifies some checks, including applicability of
1859  *                         warning inscriptions.
1860  * @param flags            See comments on invent_prompt_flags.
1861  * @param other_valid_char A character that, if not '\0', will cause
1862  *                         PROMPT_GOT_SPECIAL to be returned when pressed.
1863  *
1864  * @return  the inventory slot of an item or one of the following special values
1865  *          - PROMPT_ABORT:       if the player hits escape.
1866  *          - PROMPT_GOT_SPECIAL: if the player hits the "other_valid_char".
1867  *          - PROMPT_NOTHING:     if there are no matching items.
1868  */
prompt_invent_item(const char * prompt,menu_type mtype,int type_expect,operation_types oper,invent_prompt_flags flags,const char other_valid_char)1869 int prompt_invent_item(const char *prompt,
1870                        menu_type mtype, int type_expect,
1871                        operation_types oper,
1872                        invent_prompt_flags flags,
1873                        const char other_valid_char)
1874 {
1875     const bool do_warning = !(flags & invprompt_flag::no_warning);
1876     const bool allow_list_known = !(flags & invprompt_flag::hide_known);
1877     const bool must_exist = !(flags & invprompt_flag::unthings_ok);
1878     const bool auto_list = !(flags & invprompt_flag::manual_list);
1879     const bool allow_easy_quit = !(flags & invprompt_flag::escape_only);
1880 
1881     if (!any_items_of_type(type_expect) && type_expect != OSEL_WIELD
1882         && type_expect != OSEL_QUIVER_ACTION)
1883     {
1884         mprf(MSGCH_PROMPT, "%s",
1885              no_selectables_message(type_expect).c_str());
1886         return PROMPT_NOTHING;
1887     }
1888 
1889     unsigned char  keyin = 0;
1890     int            ret = PROMPT_ABORT;
1891 
1892     int current_type_expected = type_expect;
1893     bool           need_redraw = false;
1894     bool           need_prompt = true;
1895     bool           need_getch  = true;
1896 
1897     if (auto_list)
1898     {
1899         need_prompt = false;
1900         need_getch = false;
1901 
1902         if (any_items_of_type(type_expect))
1903             keyin = '?';
1904         else
1905             keyin = '*';
1906     }
1907 
1908     while (true)
1909     {
1910         if (need_redraw && !crawl_state.doing_prev_cmd_again)
1911         {
1912             redraw_screen();
1913             clear_messages();
1914         }
1915 
1916         if (need_prompt)
1917         {
1918             mprf(MSGCH_PROMPT, "%s (<w>?</w> for menu, <w>Esc</w> to quit)",
1919                  prompt);
1920         }
1921         else
1922             flush_prev_message();
1923 
1924         if (need_getch)
1925             keyin = get_ch();
1926 
1927         need_redraw = false;
1928         need_prompt = true;
1929         need_getch  = true;
1930 
1931         // Note:  We handle any "special" character first, so that
1932         //        it can be used to override the others.
1933         if (other_valid_char != 0 && keyin == other_valid_char)
1934         {
1935             ret = PROMPT_GOT_SPECIAL;
1936             break;
1937         }
1938 
1939         // TODO: it seems like for some uses of this function, `*` shouldn't
1940         // be allowed at all, e.g. evoke.
1941         if (keyin == '?' || keyin == '*')
1942         {
1943             // The "view inventory listing" mode.
1944             vector< SelItem > items;
1945             const auto last_keyin = keyin;
1946             current_type_expected = keyin == '*' ? OSEL_ANY : type_expect;
1947             int mflags = MF_SINGLESELECT | MF_ANYPRINTABLE | MF_NO_SELECT_QTY;
1948             if (other_valid_char == '-')
1949                 mflags |= MF_SPECIAL_MINUS;
1950 
1951             while (true)
1952             {
1953                 keyin = _invent_select(prompt, mtype, current_type_expected, -1,
1954                                        mflags, nullptr, &items);
1955 
1956                 if (allow_list_known && keyin == '\\')
1957                 {
1958                     check_item_knowledge();
1959                     continue;
1960                 }
1961                 break;
1962             }
1963 
1964             // a continue at this point has need_prompt = need_getch = true
1965 
1966             // let `?` toggle the menu altogether. If this is a non-autolist,
1967             // return to the prompt, otherwise we will pass escape to the
1968             // abort handling below. (TODO: is this the right behavior?)
1969             // TODO: some versions of _invent_select, such as wield, never
1970             // return '?'. Is this a problem?
1971             if (keyin == '?' || key_is_escape(keyin) && !auto_list)
1972                 continue;
1973 
1974             if (keyin == '*')
1975             {
1976                 // let `*` act as a toggle. This is a slightly wacky
1977                 // implementation in that '?' as a toggle does something
1978                 // entirely different...
1979                 need_prompt = need_getch = false;
1980                 if (last_keyin == '*')
1981                     keyin = '?';
1982                 else
1983                     keyin = '*';
1984                 continue;
1985             }
1986 
1987             if (other_valid_char != 0 && keyin == other_valid_char)
1988             {
1989                 // need to handle overrides...ugly code duplication
1990                 ret = PROMPT_GOT_SPECIAL;
1991                 break;
1992             }
1993         }
1994 
1995         if (isadigit(keyin))
1996         {
1997             // scan for our item
1998             item_def *item = digit_inscription_to_item(keyin, oper);
1999             if (item && in_inventory(*item))
2000             {
2001                 ret = item->link;
2002                 if (!do_warning || check_warning_inscriptions(*item, oper))
2003                     break;
2004             }
2005         }
2006         else if (key_is_escape(keyin)
2007                  || (Options.easy_quit_item_prompts
2008                      && allow_easy_quit && keyin == ' '))
2009         {
2010             ret = PROMPT_ABORT;
2011             break;
2012         }
2013         else if (allow_list_known && keyin == '\\')
2014         {
2015             check_item_knowledge();
2016             keyin = '?';
2017             need_getch = false;
2018         }
2019         else if (isaalpha(keyin))
2020         {
2021             ret = letter_to_index(keyin);
2022 
2023             if (must_exist && !you.inv[ret].defined())
2024                 mpr("You don't have any such object.");
2025             else if (must_exist && !item_is_selected(you.inv[ret],
2026                                                      current_type_expected))
2027             {
2028                 mpr("That's the wrong kind of item! (Use * to select it.)");
2029             }
2030             else if (!do_warning || check_warning_inscriptions(you.inv[ret], oper))
2031                 break;
2032         }
2033         else if (keyin == ';')
2034         {
2035             ret = you.last_unequip;
2036             break;
2037         }
2038         else if (!isspace(keyin))
2039         {
2040             // We've got a character we don't understand...
2041             canned_msg(MSG_HUH);
2042         }
2043         else
2044         {
2045             // We're going to loop back up, so don't draw another prompt.
2046             need_prompt = false;
2047         }
2048     }
2049 
2050     return ret;
2051 }
2052 
prompt_failed(int retval)2053 bool prompt_failed(int retval)
2054 {
2055     if (retval != PROMPT_ABORT && retval != PROMPT_NOTHING)
2056         return false;
2057 
2058     if (retval == PROMPT_ABORT)
2059         canned_msg(MSG_OK);
2060 
2061     crawl_state.cancel_cmd_repeat();
2062 
2063     return true;
2064 }
2065 
2066 // Most items are wieldable, but this function check for items that needs to be
2067 // wielded to be used normally.
item_is_wieldable(const item_def & item)2068 bool item_is_wieldable(const item_def &item)
2069 {
2070     return is_weapon(item) && !you.has_mutation(MUT_NO_GRASPING);
2071 }
2072 
2073 /// Does the item only serve to produce summons or allies?
_item_ally_only(const item_def & item)2074 static bool _item_ally_only(const item_def &item)
2075 {
2076     if (item.base_type == OBJ_WANDS)
2077         return item.sub_type == WAND_CHARMING;
2078     else if (item.base_type == OBJ_MISCELLANY)
2079     {
2080         switch (item.sub_type)
2081         {
2082         case MISC_PHANTOM_MIRROR:
2083         case MISC_HORN_OF_GERYON:
2084         case MISC_BOX_OF_BEASTS:
2085             return true;
2086         default:
2087             return false;
2088         }
2089     }
2090     return false;
2091 }
2092 
2093 /**
2094  * Return whether an item can be evoked.
2095  *
2096  * @param item      The item to check
2097  * @param unskilled Do items that don't use Evocations skill (weapons of
2098  *                  reaching and tremorstones) count?
2099  * @param msg       Whether we need to print a message.
2100  * @param equip     When false, ignore wield and meld requirements.
2101  */
item_is_evokable(const item_def & item,bool unskilled,bool msg,bool equip)2102 bool item_is_evokable(const item_def &item, bool unskilled,
2103                       bool msg, bool equip)
2104 {
2105     const string error = item_is_melded(item)
2106             ? "Your " + item.name(DESC_QUALNAME) + " is melded into your body."
2107             : "That item can only be evoked when wielded.";
2108 
2109     const bool no_evocables = you.get_mutation_level(MUT_NO_ARTIFICE);
2110     const char* const no_evocable_error = "You cannot evoke magical items.";
2111 
2112     if (is_unrandom_artefact(item))
2113     {
2114         const unrandart_entry* entry = get_unrand_entry(item.unrand_idx);
2115 
2116         if ((entry->evoke_func || entry->targeted_evoke_func)
2117             && item_type_known(item))
2118         {
2119             if (no_evocables)
2120             {
2121                 if (msg)
2122                     mpr(no_evocable_error);
2123                 return false;
2124             }
2125 
2126             if (item_is_equipped(item) && !item_is_melded(item) || !equip)
2127                 return true;
2128 
2129             if (msg)
2130                 mpr(error);
2131 
2132             return false;
2133         }
2134         // Unrandart might still be evokable (e.g., reaching)
2135     }
2136 
2137     if (no_evocables
2138         && item.base_type != OBJ_WEAPONS // reaching is ok.
2139         && !(item.base_type == OBJ_MISCELLANY
2140              && item.sub_type == MISC_ZIGGURAT)) // zigfigs are OK.
2141     {
2142         // the rest are forbidden under sac evocables.
2143         if (msg)
2144             mpr(no_evocable_error);
2145         return false;
2146     }
2147 
2148     // TODO: check other summoning constraints here?
2149     if (_item_ally_only(item) && you.has_mutation(MUT_NO_LOVE))
2150     {
2151         if (msg)
2152             mpr("That item cannot be used by those hated by all!");
2153         return false;
2154     }
2155 
2156     const bool wielded = !equip || you.equip[EQ_WEAPON] == item.link
2157                                    && !item_is_melded(item);
2158 
2159     switch (item.base_type)
2160     {
2161     case OBJ_WANDS:
2162         return true;
2163 
2164     // TODO: move these out of evoke
2165     case OBJ_WEAPONS:
2166         if ((!wielded || !unskilled) && !msg)
2167             return false;
2168 
2169         // XX code duplication with evoke_check
2170         if (unskilled
2171             && (weapon_reach(item) > REACH_NONE && item_type_known(item)
2172                 || you.weapon() && is_range_weapon(*you.weapon())))
2173         {
2174             if (!wielded)
2175             {
2176                 if (msg)
2177                     mpr(error);
2178                 return false;
2179             }
2180             return true;
2181         }
2182 
2183         if (msg)
2184             mpr("That item cannot be evoked!");
2185         return false;
2186 
2187 #if TAG_MAJOR_VERSION == 34
2188     case OBJ_MISCELLANY:
2189         if (item.sub_type != MISC_BUGGY_LANTERN_OF_SHADOWS
2190             && item.sub_type != MISC_BUGGY_EBONY_CASKET)
2191         {
2192             return true;
2193         }
2194         // removed items fallthrough to failure
2195 #endif
2196 
2197     default:
2198         if (msg)
2199             mpr("That item cannot be evoked!");
2200         return false;
2201     }
2202 }
2203 
2204 /**
2205  * What xp-charged evocable items is the player currently devoting XP to, if
2206  * any?
2207  *
2208  * @param[out] evokers  A vector, to be filled with a list of the elemental
2209  *                      evokers that the player is currently charging.
2210  *                      (Only one of a type is charged at a time.)
2211  */
list_charging_evokers(FixedVector<item_def *,NUM_MISCELLANY> & evokers)2212 void list_charging_evokers(FixedVector<item_def*, NUM_MISCELLANY> &evokers)
2213 {
2214     for (auto &item : you.inv)
2215     {
2216         // can't charge non-evokers, or evokers that are full
2217         if (!is_xp_evoker(item) || evoker_debt(item.sub_type) == 0)
2218             continue;
2219 
2220         evokers[item.sub_type] = &item;
2221     }
2222 }
2223 
identify_inventory()2224 void identify_inventory()
2225 {
2226     for (auto &item : you.inv)
2227     {
2228         if (item.defined())
2229         {
2230             set_ident_type(item, true);
2231             set_ident_flags(item, ISFLAG_IDENT_MASK);
2232         }
2233     }
2234 }
2235