1 /**
2  * @file
3  * @brief Let the player search for descriptions of monsters, items, etc.
4  **/
5 
6 #include "AppHdr.h"
7 
8 #include "lookup-help.h"
9 
10 #include <functional>
11 
12 #include "ability.h"
13 #include "branch.h"
14 #include "cio.h"
15 #include "colour.h"
16 #include "cloud.h"
17 #include "database.h"
18 #include "dbg-util.h"
19 #include "decks.h"
20 #include "describe.h"
21 #include "describe-god.h"
22 #include "directn.h"
23 #include "english.h"
24 #include "enum.h"
25 #include "env.h"
26 #include "god-menu.h"
27 #include "item-prop.h"
28 #include "item-name.h"
29 #include "items.h"
30 #include "libutil.h" // map_find
31 #include "macro.h"
32 #include "makeitem.h" // item_colour
33 #include "menu.h"
34 #include "message.h"
35 #include "mon-info.h"
36 #include "mon-tentacle.h"
37 #include "output.h"
38 #include "prompt.h"
39 #include "religion.h"
40 #include "rltiles/tiledef-main.h"
41 #include "skills.h"
42 #include "spl-book.h"
43 #include "spl-util.h"
44 #include "stringutil.h"
45 #include "tag-version.h"
46 #include "terrain.h"
47 #include "tile-flags.h"
48 #include "tilepick.h"
49 #include "tileview.h"
50 #include "ui.h"
51 #include "viewchar.h"
52 #include "view.h"
53 
54 
55 typedef vector<string> (*keys_by_glyph)(char32_t showchar);
56 typedef vector<string> (*simple_key_list)();
57 typedef void (*db_keys_recap)(vector<string>&);
58 typedef MenuEntry* (*menu_entry_generator)(char letter, const string &str,
59                                            string &key);
60 typedef function<int (const string &, const string &, string)> key_describer;
61 
62 /// A set of optional functionality for lookup types.
63 enum class lookup_type
64 {
65     none            = 0,
66     /// append the 'type' to the db lookup (e.g. "<input> spell")
67     db_suffix       = 1<<0,
68     /// whether the sorting functionality should be turned off
69     disable_sort    = 1<<1,
70     /// whether the display menu for this has toggleable sorting
71     toggleable_sort = 1<<2,
72 };
73 DEF_BITFIELD(lookup_type_flags, lookup_type);
74 
75 /// A description of a lookup that the player can do. (e.g. (M)onster data)
76 class LookupType
77 {
78 public:
LookupType(char _symbol,string _type,db_keys_recap _recap,db_find_filter _filter_forbid,keys_by_glyph _glyph_fetch,simple_key_list _simple_key_fetch,menu_entry_generator _menu_gen,key_describer _describer,lookup_type_flags _flags)79     LookupType(char _symbol, string _type, db_keys_recap _recap,
80                db_find_filter _filter_forbid, keys_by_glyph _glyph_fetch,
81                simple_key_list _simple_key_fetch,
82                menu_entry_generator _menu_gen, key_describer _describer,
83                lookup_type_flags _flags)
84     : symbol(_symbol), type(_type), filter_forbid(_filter_forbid),
85       flags(_flags),
86       simple_key_fetch(_simple_key_fetch), glyph_fetch(_glyph_fetch),
87       recap(_recap), menu_gen(_menu_gen), describer(_describer)
88     {
89         // XXX: will crash at startup; compile-time would be better
90         // also, ugh
91         ASSERT(menu_gen != nullptr || type == "monster");
92         ASSERT(describer != nullptr);
93     }
94 
95     string prompt_string() const;
96     string suffix() const;
97     vector<string> matching_keys(string regex) const;
98     void display_keys(vector<string> &key_list) const;
99 
100     /**
101      * Does this lookup type have special support for single-character input
102      * (looking up corresponding glyphs - e.g. 'o' for orc, '(' for ammo...)
103      */
supports_glyph_lookup() const104     bool supports_glyph_lookup() const { return glyph_fetch != nullptr; }
105 
106     /**
107      * Does this lookup type return a list of keys without taking a search
108      * request (e.g. branches or gods)?
109      */
no_search() const110     bool no_search() const { return simple_key_fetch != nullptr; }
111 
112     int describe(const string &key, bool exact_match = false) const;
113 
114 public:
115     /// The letter pressed to choose this (e.g. 'M'). case insensitive
116     char symbol;
117     /// A description of the lookup type (e.g. "monster"). case insensitive
118     string type;
119     /// a function returning 'true' if the search result corresponding to
120     /// the corresponding search should be filtered out of the results
121     db_find_filter filter_forbid;
122     /// A set of optional functionality; see lookup_type for details
123     lookup_type_flags flags;
124 private:
125     MenuEntry* make_menu_entry(char letter, string &key) const;
126     string key_to_menu_str(const string &key) const;
127 
128     /**
129      * Does this lookup type support toggling the sort order of results?
130      */
toggleable_sort() const131     bool toggleable_sort() const
132     {
133         return bool(flags & lookup_type::toggleable_sort);
134     }
135 
136 private:
137     /// Function that fetches a list of keys, without taking arguments.
138     simple_key_list simple_key_fetch;
139     /// a function taking a single character & returning a list of keys
140     /// corresponding to that glyph
141     keys_by_glyph glyph_fetch;
142     /// take the list of keys that were automatically found and fix them
143     /// up if necessary
144     db_keys_recap recap;
145     /// take a letter & a key, return a corresponding new menu entry
146     menu_entry_generator menu_gen;
147     /// A function to handle describing & interacting with a given key.
148     key_describer describer;
149 };
150 
151 
152 
153 
154 /**
155  * What monster enum corresponds to the given Serpent of Hell name?
156  *
157  * @param soh_name  The name of the monster; e.g. "the Serpent of Hell dis".
158  * @return          The corresponding enum; e.g. MONS_SERPENT_OF_HELL_DIS.
159  */
_soh_type(string & soh_name)160 static monster_type _soh_type(string &soh_name)
161 {
162     const string flavour = lowercase_string(soh_name.substr(soh_name.find_last_of(' ')+1));
163 
164     branch_type branch = NUM_BRANCHES;
165     for (int b = BRANCH_FIRST_HELL; b <= BRANCH_LAST_HELL; ++b)
166         if (ends_with(flavour, lowercase_string(branches[b].shortname)))
167             branch = (branch_type)b;
168 
169     switch (branch)
170     {
171         case BRANCH_COCYTUS:
172             return MONS_SERPENT_OF_HELL_COCYTUS;
173         case BRANCH_DIS:
174             return MONS_SERPENT_OF_HELL_DIS;
175         case BRANCH_TARTARUS:
176             return MONS_SERPENT_OF_HELL_TARTARUS;
177         case BRANCH_GEHENNA:
178             return MONS_SERPENT_OF_HELL;
179         default:
180             die("bad serpent of hell name");
181     }
182 }
183 
_is_soh(string name)184 static bool _is_soh(string name)
185 {
186     return starts_with(lowercase(name), "the serpent of hell");
187 }
188 
_soh_name(monster_type m_type)189 static string _soh_name(monster_type m_type)
190 {
191     branch_type b = serpent_of_hell_branch(m_type);
192     return string("The Serpent of Hell (") + branches[b].longname + ")";
193 }
194 
_mon_by_name(string name)195 static monster_type _mon_by_name(string name)
196 {
197     return _is_soh(name) ? _soh_type(name) : get_monster_by_name(name);
198 }
199 
_compare_mon_names(MenuEntry * entry_a,MenuEntry * entry_b)200 static bool _compare_mon_names(MenuEntry *entry_a, MenuEntry* entry_b)
201 {
202     monster_info* a = static_cast<monster_info* >(entry_a->data);
203     monster_info* b = static_cast<monster_info* >(entry_b->data);
204 
205     if (a->type == b->type)
206         return false;
207 
208     string a_name = mons_type_name(a->type, DESC_PLAIN);
209     string b_name = mons_type_name(b->type, DESC_PLAIN);
210     return lowercase(a_name) < lowercase(b_name);
211 }
212 
213 // Compare monsters by location-independent level, or by hitdice if
214 // levels are equal, or by name if both level and hitdice are equal.
_compare_mon_toughness(MenuEntry * entry_a,MenuEntry * entry_b)215 static bool _compare_mon_toughness(MenuEntry *entry_a, MenuEntry* entry_b)
216 {
217     monster_info* a = static_cast<monster_info* >(entry_a->data);
218     monster_info* b = static_cast<monster_info* >(entry_b->data);
219 
220     if (a->type == b->type)
221         return false;
222 
223     int a_toughness = mons_avg_hp(a->type);
224     int b_toughness = mons_avg_hp(b->type);
225 
226     if (a_toughness == b_toughness)
227     {
228         string a_name = mons_type_name(a->type, DESC_PLAIN);
229         string b_name = mons_type_name(b->type, DESC_PLAIN);
230         return lowercase(a_name) < lowercase(b_name);
231     }
232     return a_toughness > b_toughness;
233 }
234 
235 class DescMenu : public Menu
236 {
237 public:
DescMenu(int _flags,bool _toggleable_sort)238     DescMenu(int _flags, bool _toggleable_sort) : Menu(_flags, ""), sort_alpha(true),
239     toggleable_sort(_toggleable_sort)
240     {
241         set_highlighter(nullptr);
242 
243         if (_toggleable_sort)
244             toggle_sorting();
245 
246         set_prompt();
247     }
248 
249     bool sort_alpha;
250     bool toggleable_sort;
251 
set_prompt()252     void set_prompt()
253     {
254         string prompt = "Describe which? ";
255 
256         if (toggleable_sort)
257         {
258             if (sort_alpha)
259                 prompt += "(CTRL-S to sort by monster toughness)";
260             else
261                 prompt += "(CTRL-S to sort by name)";
262         }
263         set_title(new MenuEntry(prompt, MEL_TITLE));
264     }
265 
sort()266     void sort()
267     {
268         if (!toggleable_sort)
269             return;
270 
271         if (sort_alpha)
272             ::sort(items.begin(), items.end(), _compare_mon_names);
273         else
274             ::sort(items.begin(), items.end(), _compare_mon_toughness);
275 
276         for (unsigned int i = 0, size = items.size(); i < size; i++)
277         {
278             const char letter = index_to_letter(i % 52);
279 
280             items[i]->hotkeys.clear();
281             items[i]->add_hotkey(letter);
282         }
283     }
284 
toggle_sorting()285     void toggle_sorting()
286     {
287         if (!toggleable_sort)
288             return;
289 
290         sort_alpha = !sort_alpha;
291 
292         sort();
293         set_prompt();
294     }
295 };
296 
_get_desc_keys(string regex,db_find_filter filter)297 static vector<string> _get_desc_keys(string regex, db_find_filter filter)
298 {
299     vector<string> key_matches = getLongDescKeysByRegex(regex, filter);
300     vector<string> body_matches = getLongDescBodiesByRegex(regex, filter);
301 
302     // Merge key_matches and body_matches, discarding duplicates.
303     vector<string> tmp = key_matches;
304     tmp.insert(tmp.end(), body_matches.begin(), body_matches.end());
305     sort(tmp.begin(), tmp.end());
306     vector<string> all_matches;
307     for (unsigned int i = 0, size = tmp.size(); i < size; i++)
308         if (i == 0 || all_matches[all_matches.size() - 1] != tmp[i])
309             all_matches.push_back(tmp[i]);
310 
311     return all_matches;
312 }
313 
_get_monster_keys(char32_t showchar)314 static vector<string> _get_monster_keys(char32_t showchar)
315 {
316     vector<string> mon_keys;
317 
318     for (monster_type i = MONS_0; i < NUM_MONSTERS; ++i)
319     {
320         if (i == MONS_PROGRAM_BUG)
321             continue;
322 
323         const monsterentry *me = get_monster_data(i);
324 
325         if (me == nullptr || me->name == nullptr || me->name[0] == '\0')
326             continue;
327 
328         if (me->mc != i)
329             continue;
330 
331         if ((char32_t)me->basechar != showchar)
332             continue;
333 
334         if (mons_species(i) == MONS_SERPENT_OF_HELL)
335         {
336             mon_keys.push_back(string(me->name) + " "
337                                + serpent_of_hell_flavour(i));
338             continue;
339         }
340 
341         if (getLongDescription(me->name).empty())
342             continue;
343 
344         mon_keys.push_back(me->name);
345     }
346 
347     return mon_keys;
348 }
349 
350 
_get_god_keys()351 static vector<string> _get_god_keys()
352 {
353     vector<string> names;
354 
355     for (int i = GOD_NO_GOD + 1; i < NUM_GODS; i++)
356     {
357         god_type which_god = static_cast<god_type>(i);
358 #if TAG_MAJOR_VERSION == 34
359         // XXX: currently disabled.
360         if (which_god != GOD_PAKELLAS)
361 #endif
362         names.push_back(god_name(which_god));
363     }
364 
365     return names;
366 }
367 
_get_branch_keys()368 static vector<string> _get_branch_keys()
369 {
370     vector<string> names;
371 
372     for (branch_iterator it; it; ++it)
373     {
374         // Skip unimplemented branches
375         if (branch_is_unfinished(it->id))
376             continue;
377 
378         names.push_back(it->shortname);
379     }
380     return names;
381 }
382 
_get_cloud_keys()383 static vector<string> _get_cloud_keys()
384 {
385     vector<string> names;
386 
387     for (int i = CLOUD_NONE + 1; i < NUM_CLOUD_TYPES; i++)
388         names.push_back(cloud_type_name((cloud_type) i) + " cloud");
389 
390     return names;
391 }
392 
393 /**
394  * Return a list of all skill names.
395  */
_get_skill_keys()396 static vector<string> _get_skill_keys()
397 {
398     vector<string> names;
399     for (skill_type sk = SK_FIRST_SKILL; sk < NUM_SKILLS; ++sk)
400     {
401         const string name = lowercase_string(skill_name(sk));
402 #if TAG_MAJOR_VERSION == 34
403         if (getLongDescription(name).empty())
404             continue; // obsolete skills
405 #endif
406 
407         names.emplace_back(name);
408     }
409     return names;
410 }
411 
_monster_filter(string key,string)412 static bool _monster_filter(string key, string /*body*/)
413 {
414     const monster_type mon_num = _mon_by_name(key);
415     return mons_class_flag(mon_num, M_CANT_SPAWN)
416            || mons_is_tentacle_segment(mon_num);
417 }
418 
_spell_filter(string key,string)419 static bool _spell_filter(string key, string /*body*/)
420 {
421     if (!strip_suffix(key, "spell"))
422         return true;
423 
424     spell_type spell = spell_by_name(key);
425 
426     if (spell == SPELL_NO_SPELL)
427         return true;
428 
429     if (get_spell_flags(spell) & spflag::testing)
430         return !you.wizard;
431 
432     return false;
433 }
434 
_item_filter(string key,string)435 static bool _item_filter(string key, string /*body*/)
436 {
437     return item_kind_by_name(key).base_type == OBJ_UNASSIGNED;
438 }
439 
_feature_filter(string key,string)440 static bool _feature_filter(string key, string /*body*/)
441 {
442     return feat_by_desc(key) == DNGN_UNSEEN;
443 }
444 
_card_filter(string key,string)445 static bool _card_filter(string key, string /*body*/)
446 {
447     lowercase(key);
448 
449     // Every card description contains the keyword "card".
450     if (!strip_suffix(key, "card"))
451         return true;
452 
453     for (int i = 0; i < NUM_CARDS; ++i)
454     {
455         if (key == lowercase_string(card_name(static_cast<card_type>(i))))
456             return false;
457     }
458     return true;
459 }
460 
_ability_filter(string key,string)461 static bool _ability_filter(string key, string /*body*/)
462 {
463     lowercase(key);
464 
465     if (!strip_suffix(key, "ability"))
466         return true;
467 
468     return !string_matches_ability_name(key);
469 }
470 
_status_filter(string key,string)471 static bool _status_filter(string key, string /*body*/)
472 {
473     return !strip_suffix(lowercase(key), " status");
474 }
475 
476 
_recap_mon_keys(vector<string> & keys)477 static void _recap_mon_keys(vector<string> &keys)
478 {
479     for (unsigned int i = 0, size = keys.size(); i < size; i++)
480     {
481         if (!_is_soh(keys[i]))
482         {
483             monster_type type = get_monster_by_name(keys[i]);
484             keys[i] = mons_type_name(type, DESC_PLAIN);
485         }
486     }
487 }
488 
489 /**
490  * Fixup spell names. (Correcting capitalization, mainly.)
491  *
492  * @param[in,out] keys      A lowercased list of spell names.
493  */
_recap_spell_keys(vector<string> & keys)494 static void _recap_spell_keys(vector<string> &keys)
495 {
496     for (unsigned int i = 0, size = keys.size(); i < size; i++)
497     {
498         // first, strip " spell"
499         const string key_name = keys[i].substr(0, keys[i].length() - 6);
500         // then get the real name
501         keys[i] = make_stringf("%s spell",
502                                spell_title(spell_by_name(key_name)));
503     }
504 }
505 
506 /**
507  * Fixup ability names. (Correcting capitalization, mainly.)
508  *
509  * @param[in,out] keys      A lowercased list of ability names.
510  */
_recap_ability_keys(vector<string> & keys)511 static void _recap_ability_keys(vector<string> &keys)
512 {
513     for (auto &key : keys)
514     {
515         strip_suffix(key, "ability");
516         // get the real name
517         key = make_stringf("%s ability", ability_name(ability_by_name(key)));
518     }
519 }
520 
_recap_feat_keys(vector<string> & keys)521 static void _recap_feat_keys(vector<string> &keys)
522 {
523     for (unsigned int i = 0, size = keys.size(); i < size; i++)
524     {
525         dungeon_feature_type type = feat_by_desc(keys[i]);
526         if (type == DNGN_ENTER_SHOP)
527             keys[i] = "A shop";
528         else
529             keys[i] = feature_description(type, NUM_TRAPS, "", DESC_A);
530     }
531 }
532 
_recap_card_keys(vector<string> & keys)533 static void _recap_card_keys(vector<string> &keys)
534 {
535     for (unsigned int i = 0, size = keys.size(); i < size; i++)
536     {
537         lowercase(keys[i]);
538 
539         for (int j = 0; j < NUM_CARDS; ++j)
540         {
541             card_type card = static_cast<card_type>(j);
542             if (keys[i] == lowercase_string(card_name(card)) + " card")
543             {
544                 keys[i] = string(card_name(card)) + " card";
545                 break;
546             }
547         }
548     }
549 }
550 
551 /**
552  * Make a basic, no-frills ?/<foo> menu entry.
553  *
554  * @param letter    The letter for the entry. (E.g. 'e' for the fifth entry.)
555  * @param str       A processed string for the entry. (E.g. "Blade".)
556  * @param key       The raw database key for the entry. (E.g. "blade card".)
557  * @return          A new menu entry.
558  */
_simple_menu_gen(char letter,const string & str,string & key)559 static MenuEntry* _simple_menu_gen(char letter, const string &str, string &key)
560 {
561     MenuEntry* me = new MenuEntry(str, MEL_ITEM, 1, letter);
562     me->data = &key;
563     return me;
564 }
565 
566 /**
567  * Generate a ?/M entry.
568  *
569  * @param letter      The letter for the entry. (E.g. 'e' for the fifth entry.)
570  * @param str         A processed string for the entry. (E.g. "Blade".)
571  * @param mslot[out]  A space in memory to store a fake monster.
572  * @return            A new menu entry.
573  */
_monster_menu_gen(char letter,const string & str,monster_info & mslot)574 static MenuEntry* _monster_menu_gen(char letter, const string &str,
575                                     monster_info &mslot)
576 {
577     // Create and store fake monsters, so the menu code will
578     // have something valid to refer to.
579     monster_type m_type = _mon_by_name(str);
580     const string name = _is_soh(str) ? _soh_name(m_type) : str;
581 
582     monster_type base_type = MONS_NO_MONSTER;
583     // HACK: Set an arbitrary humanoid monster as base type.
584     if (mons_class_is_zombified(m_type))
585         base_type = MONS_GOBLIN;
586 
587     monster_info fake_mon(m_type, base_type);
588     fake_mon.props["fake"] = true;
589 
590     mslot = fake_mon;
591 
592 #ifndef USE_TILE_LOCAL
593     int colour = mons_class_colour(m_type);
594     if (colour == BLACK)
595         colour = LIGHTGREY;
596 
597     string prefix = "(<";
598     prefix += colour_to_str(colour);
599     prefix += ">";
600     prefix += stringize_glyph(mons_char(m_type));
601     prefix += "</";
602     prefix += colour_to_str(colour);
603     prefix += ">) ";
604 
605     const string title = prefix + name;
606 #else
607     const string &title = name;
608 #endif
609 
610     // NOTE: MonsterMenuEntry::get_tiles() takes care of setting
611     // up a fake weapon when displaying a fake dancing weapon's
612     // tile.
613     return new MonsterMenuEntry(title, &mslot, letter);
614 }
615 
616 /**
617  * Generate a ?/I menu entry. (ref. _simple_menu_gen()).
618  */
_item_menu_gen(char letter,const string & str,string & key)619 static MenuEntry* _item_menu_gen(char letter, const string &str, string &key)
620 {
621     MenuEntry* me = _simple_menu_gen(letter, str, key);
622     item_def item;
623     item_kind kind = item_kind_by_name(key);
624     get_item_by_name(&item, key.c_str(), kind.base_type);
625     item_colour(item);
626     tileidx_t idx = tileidx_item(get_item_known_info(item));
627     tileidx_t base_item = tileidx_known_base_item(idx);
628     if (base_item)
629         me->add_tile(tile_def(base_item));
630     me->add_tile(tile_def(idx));
631     return me;
632 }
633 
634 /**
635  * Generate a ?/F menu entry. (ref. _simple_menu_gen()).
636  */
_feature_menu_gen(char letter,const string & str,string & key)637 static MenuEntry* _feature_menu_gen(char letter, const string &str, string &key)
638 {
639     MenuEntry* me = new MenuEntry(str, MEL_ITEM, 1, letter);
640     me->data = &key;
641 
642     const dungeon_feature_type feat = feat_by_desc(str);
643     if (feat)
644     {
645         const tileidx_t idx = tileidx_feature_base(feat);
646         me->add_tile(tile_def(idx));
647     }
648 
649     return me;
650 }
651 
652 /**
653  * Generate a ?/G menu entry. (ref. _simple_menu_gen()).
654  */
_god_menu_gen(char,const string &,string & key)655 static MenuEntry* _god_menu_gen(char /*letter*/, const string &/*str*/, string &key)
656 {
657     return new GodMenuEntry(str_to_god(key));
658 }
659 
660 /**
661  * Generate a ?/A menu entry. (ref. _simple_menu_gen()).
662  */
_ability_menu_gen(char letter,const string & str,string & key)663 static MenuEntry* _ability_menu_gen(char letter, const string &str, string &key)
664 {
665     MenuEntry* me = _simple_menu_gen(letter, str, key);
666 
667     const ability_type ability = ability_by_name(str);
668     if (ability != ABIL_NON_ABILITY)
669         me->add_tile(tile_def(tileidx_ability(ability)));
670 
671     return me;
672 }
673 
674 /**
675  * Generate a ?/C menu entry. (ref. _simple_menu_gen()).
676  */
_card_menu_gen(char letter,const string & str,string & key)677 static MenuEntry* _card_menu_gen(char letter, const string &str, string &key)
678 {
679     MenuEntry* me = _simple_menu_gen(letter, str, key);
680     me->add_tile(tile_def(TILEG_NEMELEX_CARD));
681     return me;
682 }
683 
684 /**
685  * Generate a ?/S menu entry. (ref. _simple_menu_gen()).
686  */
_spell_menu_gen(char letter,const string & str,string & key)687 static MenuEntry* _spell_menu_gen(char letter, const string &str, string &key)
688 {
689     MenuEntry* me = _simple_menu_gen(letter, str, key);
690 
691     const spell_type spell = spell_by_name(str);
692     if (spell != SPELL_NO_SPELL)
693         me->add_tile(tile_def(tileidx_spell(spell)));
694     me->colour = is_player_spell(spell) ? WHITE
695                                         : DARKGREY; // monster-only
696 
697     return me;
698 }
699 
700 /**
701  * Generate a ?/K menu entry. (ref. _simple_menu_gen()).
702  */
_skill_menu_gen(char letter,const string & str,string & key)703 static MenuEntry* _skill_menu_gen(char letter, const string &str, string &key)
704 {
705     MenuEntry* me = _simple_menu_gen(letter, str, key);
706 
707     const skill_type skill = str_to_skill_safe(str);
708     me->add_tile(tile_def(tileidx_skill(skill, TRAINING_ENABLED)));
709 
710     return me;
711 }
712 
713 /**
714  * Generate a ?/B menu entry. (ref. _simple_menu_gen()).
715  */
_branch_menu_gen(char letter,const string & str,string & key)716 static MenuEntry* _branch_menu_gen(char letter, const string &str, string &key)
717 {
718     MenuEntry* me = _simple_menu_gen(letter, str, key);
719 
720     const branch_type branch = branch_by_shortname(str);
721     int hotkey = branches[branch].travel_shortcut;
722     me->hotkeys = {hotkey, tolower_safe(hotkey)};
723     me->add_tile(tile_def(tileidx_branch(branch)));
724 
725     return me;
726 }
727 
728 /**
729  * Generate a ?/L menu entry. (ref. _simple_menu_gen()).
730  */
_cloud_menu_gen(char letter,const string & str,string & key)731 static MenuEntry* _cloud_menu_gen(char letter, const string &str, string &key)
732 {
733     MenuEntry* me = _simple_menu_gen(letter, str, key);
734 
735     const string cloud_name = lowercase_string(str);
736     const cloud_type cloud = cloud_name_to_type(cloud_name);
737     ASSERT(cloud != NUM_CLOUD_TYPES);
738 
739     cloud_struct fake_cloud;
740     fake_cloud.type = cloud;
741     fake_cloud.decay = 1000;
742     me->colour = element_colour(get_cloud_colour(fake_cloud));
743 
744     cloud_info fake_cloud_info;
745     fake_cloud_info.type = cloud;
746     fake_cloud_info.colour = me->colour;
747     const tileidx_t idx = tileidx_cloud(fake_cloud_info);
748     me->add_tile(tile_def(idx));
749 
750     return me;
751 }
752 
753 
754 /**
755  * How should this type be expressed in the prompt string?
756  *
757  * @return The 'type', with the first instance of the 'symbol' found &
758  *          replaced with an uppercase version surrounded by parens
759  *          e.g. "monster", 'm' -> "(M)onster"
760  */
prompt_string() const761 string LookupType::prompt_string() const
762 {
763     string prompt_str = lowercase_string(type);
764     const size_t symbol_pos = prompt_str.find(tolower_safe(symbol));
765     ASSERT(symbol_pos != string::npos);
766 
767     prompt_str.replace(symbol_pos, 1, make_stringf("(%c)", toupper_safe(symbol)));
768     return prompt_str;
769 }
770 
771 /**
772  * A suffix to be appended to the provided search string when looking for
773  * db info.
774  *
775  * @return      An appropriate suffix for types that need them (e.g.
776  *              " cards"); otherwise "".
777  */
suffix() const778 string LookupType::suffix() const
779 {
780     if (flags & lookup_type::db_suffix)
781         return " " + type;
782     return "";
783 }
784 
785 /**
786  * Get a list of string corresponding to the given regex.
787  */
matching_keys(string regex) const788 vector<string> LookupType::matching_keys(string regex) const
789 {
790     vector<string> key_list;
791 
792     if (no_search())
793         key_list = simple_key_fetch();
794     else if (regex.size() == 1 && supports_glyph_lookup())
795         key_list = glyph_fetch(regex[0]);
796     else
797         key_list = _get_desc_keys(regex, filter_forbid);
798 
799     if (recap != nullptr)
800         (*recap)(key_list);
801 
802     return key_list;
803 }
804 
_mons_desc_key(monster_type type)805 static string _mons_desc_key(monster_type type)
806 {
807     const string name = mons_type_name(type, DESC_PLAIN);
808     if (mons_species(type) == MONS_SERPENT_OF_HELL)
809         return name + " " + serpent_of_hell_flavour(type);
810     return name;
811 }
812 
813 /**
814  * Build a menu listing the given keys, and allow the player to interact
815  * with them.
816  */
display_keys(vector<string> & key_list) const817 void LookupType::display_keys(vector<string> &key_list) const
818 {
819     DescMenu desc_menu(MF_SINGLESELECT | MF_ANYPRINTABLE | MF_ALLOW_FORMATTING
820             | MF_NO_SELECT_QTY | MF_USE_TWO_COLUMNS , toggleable_sort());
821     desc_menu.set_tag("description");
822 
823     // XXX: ugh
824     const bool doing_mons = type == "monster";
825     vector<monster_info> monster_list(key_list.size());
826     for (unsigned int i = 0, size = key_list.size(); i < size; i++)
827     {
828         const char letter = index_to_letter(i % 52);
829         string &key = key_list[i];
830         // XXX: double ugh
831         if (doing_mons)
832         {
833             desc_menu.add_entry(_monster_menu_gen(letter,
834                                                   key_to_menu_str(key),
835                                                   monster_list[i]));
836         }
837         else
838             desc_menu.add_entry(make_menu_entry(letter, key));
839     }
840 
841     desc_menu.sort();
842 
843     desc_menu.on_single_selection = [this, doing_mons](const MenuEntry& item)
844     {
845         ASSERT(item.hotkeys.size() >= 1);
846 
847         string key;
848 
849         if (doing_mons)
850         {
851             monster_info* mon = (monster_info*) item.data;
852             key = _mons_desc_key(mon->type);
853         }
854         else
855             key = *((string*) item.data);
856 
857         describe(key);
858         return true;
859     };
860 
861     while (true)
862     {
863         desc_menu.show();
864         if (toggleable_sort() && desc_menu.getkey() == CONTROL('S'))
865             desc_menu.toggle_sorting();
866         else
867             break;
868     }
869 }
870 
871 /**
872  * Generate a description menu entry for the given key.
873  *
874  * @param letter    The letter with which the entry should be labeled.
875  * @param key       The key for the entry.
876  * @return          A pointer to a new MenuEntry object.
877  */
make_menu_entry(char letter,string & key) const878 MenuEntry* LookupType::make_menu_entry(char letter, string &key) const
879 {
880     ASSERT(menu_gen);
881     return menu_gen(letter, key_to_menu_str(key), key);
882 }
883 
884 /**
885  * Turn a DB string into a nice menu title.
886  *
887  * @param key       The key in question. (E.g. "blade card").
888  * @return          A nicer string. (E.g. "Blade").
889  */
key_to_menu_str(const string & key) const890 string LookupType::key_to_menu_str(const string &key) const
891 {
892     string str = uppercase_first(key);
893     // perhaps we should assert this?
894     strip_suffix(str, suffix());
895     return str;
896 }
897 
898 /**
899  * Handle describing & interacting with a given key.
900  * @return the last key pressed.
901  */
describe(const string & key,bool exact_match) const902 int LookupType::describe(const string &key, bool exact_match) const
903 {
904     const string footer
905         = exact_match ? "This entry is an exact match for '" + key
906         + "'. To see non-exact matches, press space."
907         : "";
908     return describer(key, suffix(), footer);
909 }
910 
911 /**
912  * Describe the thing with the given name.
913  *
914  * @param key           The name of the thing in question.
915  * @param suffix        A suffix to trim from the key when making the title.
916  * @param footer        A footer to append to the end of descriptions.
917  * @param extra_info    Extra info to append to the database description.
918  * @return              The keypress the user made to exit.
919  */
_describe_key(const string & key,const string & suffix,string footer,const string & extra_info,const tile_def * tile=nullptr)920 static int _describe_key(const string &key, const string &suffix,
921                          string footer, const string &extra_info,
922                          const tile_def *tile = nullptr)
923 {
924     describe_info inf;
925     inf.quote = getQuoteString(key);
926 
927     const string desc = getLongDescription(key);
928 
929     inf.body << desc << extra_info;
930     inf.title = [&]() {
931         string title = key;
932         strip_suffix(title, suffix);
933         return uppercase_first(title);
934     }();
935     inf.footer = footer;
936 
937     return show_description(inf, tile);
938 }
939 
940 /**
941  * Describe the thing with the given name.
942  *
943  * @param key       The name of the thing in question.
944  * @param suffix    A suffix to trim from the key when making the title.
945  * @param footer    A footer to append to the end of descriptions.
946  * @return          The keypress the user made to exit.
947  */
_describe_generic(const string & key,const string & suffix,string footer)948 static int _describe_generic(const string &key, const string &suffix,
949                              string footer)
950 {
951     return _describe_key(key, suffix, footer, "");
952 }
953 
954 /**
955  * Describe & allow examination of the monster with the given name.
956  *
957  * @param key       The name of the monster in question.
958  * @param suffix    A suffix to trim from the key when making the title.
959  * @param footer    A footer to append to the end of descriptions.
960  * @return          The keypress the user made to exit.
961  */
_describe_monster(const string & key,const string & suffix,string footer)962 static int _describe_monster(const string &key, const string &suffix,
963                              string footer)
964 {
965     const monster_type mon_num = _mon_by_name(key);
966     ASSERT(mon_num != MONS_PROGRAM_BUG);
967     // Don't attempt to get more information on ghost demon
968     // monsters, as the ghost struct has not been initialised, which
969     // will cause a crash. Similarly for zombified monsters, since
970     // they require a base monster.
971     if (mons_is_ghost_demon(mon_num) || mons_class_is_zombified(mon_num))
972         return _describe_generic(key, suffix, footer);
973 
974     monster_type base_type = MONS_NO_MONSTER;
975     // Might be better to show all possible combinations rather than picking
976     // one at random as this does?
977     if (mons_is_draconian_job(mon_num))
978         base_type = random_draconian_monster_species();
979     else if (mons_is_demonspawn_job(mon_num))
980         base_type = random_demonspawn_monster_species();
981     monster_info mi(mon_num, base_type);
982     // Avoid slime creature being described as "buggy"
983     if (mi.type == MONS_SLIME_CREATURE)
984         mi.slime_size = 1;
985     return describe_monsters(mi, footer);
986 }
987 
988 
989 /**
990  * Describe the spell with the given name.
991  *
992  * @param key       The name of the spell in question.
993  * @param suffix    A suffix to trim from the key when making the title.
994  * @param footer    A footer to append to the end of descriptions.
995  * @return          The keypress the user made to exit.
996  */
_describe_spell(const string & key,const string & suffix,string)997 static int _describe_spell(const string &key, const string &suffix,
998                              string /*footer*/)
999 {
1000     const string spell_name = key.substr(0, key.size() - suffix.size());
1001     const spell_type spell = spell_by_name(spell_name, true);
1002     ASSERT(spell != SPELL_NO_SPELL);
1003     describe_spell(spell);
1004     return 0;
1005 }
1006 
_describe_skill(const string & key,const string & suffix,string)1007 static int _describe_skill(const string &key, const string &suffix,
1008                              string /*footer*/)
1009 {
1010     const string skill_name = key.substr(0, key.size() - suffix.size());
1011     const skill_type skill = skill_from_name(skill_name.c_str());
1012     describe_skill(skill);
1013     return 0;
1014 }
1015 
_describe_ability(const string & key,const string & suffix,string)1016 static int _describe_ability(const string &key, const string &suffix,
1017                              string /*footer*/)
1018 {
1019     const string abil_name = key.substr(0, key.size() - suffix.size());
1020     const ability_type abil = ability_by_name(abil_name.c_str());
1021     describe_ability(abil);
1022     return 0;
1023 }
1024 
1025 /**
1026  * Describe the card with the given name.
1027  *
1028  * @param key       The name of the card in question.
1029  * @param suffix    A suffix to trim from the key when making the title.
1030  * @param footer    A footer to append to the end of descriptions.
1031  * @return          The keypress the user made to exit.
1032  */
_describe_card(const string & key,const string & suffix,string footer)1033 static int _describe_card(const string &key, const string &suffix,
1034                            string footer)
1035 {
1036     const string card_name = key.substr(0, key.size() - suffix.size());
1037     const card_type card = name_to_card(card_name);
1038     ASSERT(card != NUM_CARDS);
1039 #ifdef USE_TILE
1040     tile_def tile = tile_def(TILEG_NEMELEX_CARD);
1041     return _describe_key(key, suffix, footer, which_decks(card) + "\n", &tile);
1042 #else
1043     return _describe_key(key, suffix, footer, which_decks(card) + "\n");
1044 #endif
1045 }
1046 
1047 /**
1048  * Describe the cloud with the given name.
1049  *
1050  * @param key       The name of the cloud in question.
1051  * @param suffix    A suffix to trim from the key when making the title.
1052  * @param footer    A footer to append to the end of descriptions.
1053  * @return          The keypress the user made to exit.
1054  */
_describe_cloud(const string & key,const string & suffix,string footer)1055 static int _describe_cloud(const string &key, const string &suffix,
1056                            string footer)
1057 {
1058     const string cloud_name = key.substr(0, key.size() - suffix.size());
1059     const cloud_type cloud = cloud_name_to_type(cloud_name);
1060     ASSERT(cloud != NUM_CLOUD_TYPES);
1061 #ifdef USE_TILE
1062     cloud_info fake_cloud_info;
1063     fake_cloud_info.type = cloud;
1064     const tileidx_t idx = tileidx_cloud(fake_cloud_info);
1065     tile_def tile = tile_def(idx);
1066     return _describe_key(key, suffix, footer, extra_cloud_info(cloud), &tile);
1067 #else
1068     return _describe_key(key, suffix, footer, extra_cloud_info(cloud));
1069 #endif
1070 }
1071 
1072 /**
1073  * Describe the item with the given name.
1074  *
1075  * @param key       The name of the item in question.
1076  * @param suffix    A suffix to trim from the key when making the title.
1077  * @param footer    A footer to append to the end of descriptions.
1078  * @return          The keypress the user made to exit.
1079  */
_describe_item(const string & key,const string & suffix,string)1080 static int _describe_item(const string &key, const string &suffix,
1081                            string /*footer*/)
1082 {
1083     const string item_name = key.substr(0, key.size() - suffix.size());
1084     item_def item;
1085     if (!get_item_by_exact_name(item, item_name.c_str()))
1086         die("Unable to get item %s by name", key.c_str());
1087     describe_item_popup(item);
1088     return 0;
1089 }
1090 
_describe_feature(const string & key,const string & suffix,string)1091 static int _describe_feature(const string &key, const string &suffix,
1092                              string /*footer*/)
1093 {
1094     const string feat_name = key.substr(0, key.size() - suffix.size());
1095     const dungeon_feature_type feat = feat_by_desc(feat_name);
1096     describe_feature_type(feat);
1097     return 0;
1098 }
1099 
1100 /**
1101  * Describe the god with the given name.
1102  *
1103  * @param key       The name of the god in question.
1104  * @return          0.
1105  */
_describe_god(const string & key,const string &,string)1106 static int _describe_god(const string &key, const string &/*suffix*/,
1107                           string /*footer*/)
1108 {
1109     const god_type which_god = str_to_god(key);
1110     ASSERT(which_god != GOD_NO_GOD);
1111     describe_god(which_god);
1112 
1113     return 0; // no exact matches for gods, so output doesn't matter
1114 }
1115 
_branch_entry_runes(branch_type br)1116 static string _branch_entry_runes(branch_type br)
1117 {
1118     string desc;
1119     const int num_runes = runes_for_branch(br);
1120 
1121     if (num_runes > 0)
1122     {
1123         desc = make_stringf("\n\nThis %s can only be entered while carrying "
1124                             "at least %d rune%s of Zot.",
1125                             br == BRANCH_ZIGGURAT ? "portal" : "branch",
1126                             num_runes, num_runes > 1 ? "s" : "");
1127     }
1128 
1129     return desc;
1130 }
1131 
_branch_depth(branch_type br)1132 static string _branch_depth(branch_type br)
1133 {
1134     string desc;
1135     const int depth = branches[br].numlevels;
1136 
1137     // Abyss depth is explained in the description.
1138     if (depth > 1 && br != BRANCH_ABYSS)
1139     {
1140         desc = make_stringf("\n\nThis %s is %d levels deep.",
1141                             br == BRANCH_ZIGGURAT ? "portal"
1142                                                   : "branch",
1143                             depth);
1144     }
1145 
1146     return desc;
1147 }
1148 
_branch_location(branch_type br)1149 static string _branch_location(branch_type br)
1150 {
1151     string desc;
1152     const branch_type parent = branches[br].parent_branch;
1153     const int min = branches[br].mindepth;
1154     const int max = branches[br].maxdepth;
1155 
1156     // Ziggurat locations are explained in the description.
1157     if (parent != NUM_BRANCHES && br != BRANCH_ZIGGURAT)
1158     {
1159         desc = "\n\nThe entrance to this branch can be found ";
1160         if (min == max)
1161         {
1162             if (branches[parent].numlevels == 1)
1163                 desc += "in ";
1164             else
1165                 desc += make_stringf("on level %d of ", min);
1166         }
1167         else
1168             desc += make_stringf("between levels %d and %d of ", min, max);
1169         desc += branches[parent].longname;
1170         desc += ".";
1171     }
1172 
1173     return desc;
1174 }
1175 
_branch_subbranches(branch_type br)1176 static string _branch_subbranches(branch_type br)
1177 {
1178     string desc;
1179     vector<string> subbranch_names;
1180 
1181     for (branch_iterator it; it; ++it)
1182         if (it->parent_branch == br && !branch_is_unfinished(it->id))
1183             subbranch_names.push_back(it->longname);
1184 
1185     // Lair's random branches are explained in the description.
1186     if (!subbranch_names.empty() && br != BRANCH_LAIR)
1187     {
1188         desc += make_stringf("\n\nThis branch contains the entrance%s to %s.",
1189                              subbranch_names.size() > 1 ? "s" : "",
1190                              comma_separated_line(begin(subbranch_names),
1191                                                   end(subbranch_names)).c_str());
1192     }
1193 
1194     return desc;
1195 }
1196 
1197 /**
1198  * Describe the branch with the given name.
1199  *
1200  * @param key       The name of the branch in question.
1201  * @param suffix    A suffix to trim from the key when making the title.
1202  * @param footer    A footer to append to the end of descriptions.
1203  * @return          The keypress the user made to exit.
1204  */
_describe_branch(const string & key,const string & suffix,string footer)1205 static int _describe_branch(const string &key, const string &suffix,
1206                             string footer)
1207 {
1208     const string branch_name = key.substr(0, key.size() - suffix.size());
1209     const branch_type branch = branch_by_shortname(branch_name);
1210     ASSERT(branch != NUM_BRANCHES);
1211 
1212     string info = "";
1213     const string noise_desc = branch_noise_desc(branch);
1214     if (!noise_desc.empty())
1215         info += "\n\n" + noise_desc;
1216 
1217     info += _branch_location(branch)
1218             + _branch_entry_runes(branch)
1219             + _branch_depth(branch)
1220             + _branch_subbranches(branch)
1221             + "\n\n"
1222             + branch_rune_desc(branch, false);
1223 
1224     tile_def tile = tile_def(tileidx_branch(branch));
1225     return _describe_key(key, suffix, footer, info, &tile);
1226 }
1227 
1228 /// All types of ?/ queries the player can enter.
1229 static const vector<LookupType> lookup_types = {
1230     LookupType('M', "monster", _recap_mon_keys, _monster_filter,
1231                _get_monster_keys, nullptr, nullptr,
1232                _describe_monster, lookup_type::toggleable_sort),
1233     LookupType('S', "spell", _recap_spell_keys, _spell_filter,
1234                nullptr, nullptr, _spell_menu_gen,
1235                _describe_spell, lookup_type::db_suffix),
1236     LookupType('K', "skill", nullptr, nullptr,
1237                nullptr, _get_skill_keys, _skill_menu_gen,
1238                _describe_skill, lookup_type::none),
1239     LookupType('A', "ability", _recap_ability_keys, _ability_filter,
1240                nullptr, nullptr, _ability_menu_gen,
1241                _describe_ability, lookup_type::db_suffix),
1242     LookupType('C', "card", _recap_card_keys, _card_filter,
1243                nullptr, nullptr, _card_menu_gen,
1244                _describe_card, lookup_type::db_suffix),
1245     LookupType('I', "item", nullptr, _item_filter,
1246                item_name_list_for_glyph, nullptr, _item_menu_gen,
1247                _describe_item, lookup_type::none),
1248     LookupType('F', "feature", _recap_feat_keys, _feature_filter,
1249                nullptr, nullptr, _feature_menu_gen,
1250                _describe_feature, lookup_type::none),
1251     LookupType('G', "god", nullptr, nullptr,
1252                nullptr, _get_god_keys, _god_menu_gen,
1253                _describe_god, lookup_type::none),
1254     LookupType('B', "branch", nullptr, nullptr,
1255                nullptr, _get_branch_keys, _branch_menu_gen,
1256                _describe_branch, lookup_type::disable_sort),
1257     LookupType('L', "cloud", nullptr, nullptr,
1258                nullptr, _get_cloud_keys, _cloud_menu_gen,
1259                _describe_cloud, lookup_type::db_suffix),
1260     LookupType('T', "status", nullptr, _status_filter,
1261                nullptr, nullptr, _simple_menu_gen,
1262                _describe_generic, lookup_type::db_suffix),
1263 };
1264 
1265 /**
1266  * Build a mapping from LookupTypes' symbols to the objects themselves.
1267  */
_build_lookup_type_map()1268 static map<char, const LookupType*> _build_lookup_type_map()
1269 {
1270     map<char, const LookupType*> lookup_map;
1271     for (const auto &lookup : lookup_types)
1272         lookup_map[lookup.symbol] = &lookup;
1273     return lookup_map;
1274 }
1275 static const map<char, const LookupType*> _lookup_types_by_symbol
1276     = _build_lookup_type_map();
1277 
1278 /**
1279  * Prompt the player for a search string for the given lookup type.
1280  *
1281  * @param lookup_type  The LookupType in question (e.g. monsters, items...)
1282  * @param err[out]     Will be set to a non-empty string if the user failed to
1283  *                     provide a string.
1284  * @return             A search string, if one was provided; else "".
1285  */
_prompt_for_regex(const LookupType & lookup_type,string & err)1286 static string _prompt_for_regex(const LookupType &lookup_type, string &err)
1287 {
1288     const string type = lowercase_string(lookup_type.type);
1289     const string extra = lookup_type.supports_glyph_lookup() ?
1290         make_stringf(" Enter a single letter to list %s displayed by that"
1291                      " symbol.", pluralise(type).c_str()) :
1292         "";
1293     const string prompt = make_stringf(
1294          "Describe a %s; partial names and regexps are fine.%s\n"
1295          "Describe what? ",
1296          type.c_str(), extra.c_str());
1297 
1298     char buf[80];
1299     if (msgwin_get_line(prompt, buf, sizeof(buf)) || buf[0] == '\0')
1300     {
1301         err = "Okay, then.";
1302         return "";
1303     }
1304 
1305     const string regex = strlen(buf) == 1 ? buf : trimmed_string(buf);
1306     return regex;
1307 }
1308 
_exact_lookup_match(const LookupType & lookup_type,const string & regex)1309 static bool _exact_lookup_match(const LookupType &lookup_type,
1310                                 const string &regex)
1311 {
1312     if (lookup_type.no_search())
1313         return false; // no search, no exact match
1314 
1315     if (lookup_type.supports_glyph_lookup() && regex.size() == 1)
1316         return false; // glyph search doesn't have the concept
1317 
1318     if (lookup_type.filter_forbid && (*lookup_type.filter_forbid)(regex, ""))
1319         return false; // match found, but incredibly illegal to display
1320 
1321     return !getLongDescription(regex + lookup_type.suffix()).empty();
1322 }
1323 
1324 /**
1325  * Check if the provided keylist is invalid; if so, return the reason why.
1326  *
1327  * @param key_list      The list of keys to be checked before display.
1328  * @param type          The singular name of the things being listed.
1329  * @param regex         The search term that was used to fetch this list.
1330  * @param by_symbol     Whether the search is by regex or by glyph.
1331  * @return              A reason why the list is invalid, if it is
1332  *                      (e.g. "No monsters with symbol '��'."),
1333  *                      or the empty string if the list is valid.
1334  */
_keylist_invalid_reason(const vector<string> & key_list,const string & type,const string & regex,bool by_symbol)1335 static string _keylist_invalid_reason(const vector<string> &key_list,
1336                                       const string &type,
1337                                       const string &regex,
1338                                       bool by_symbol)
1339 {
1340     const string plur_type = pluralise(type);
1341 
1342     if (key_list.empty())
1343     {
1344         if (by_symbol)
1345             return "No " + plur_type + " with symbol '" + regex + "'.";
1346         return "No matching " + plur_type + ".";
1347     }
1348 
1349     // we're good!
1350     return "";
1351 }
1352 
_lookup_prompt()1353 static int _lookup_prompt()
1354 {
1355     // TODO: show this + the regex prompt in the same menu?
1356 #ifdef TOUCH_UI
1357     bool use_popup = true;
1358 #else
1359     bool use_popup = !crawl_state.need_save || ui::has_layout();
1360 #endif
1361 
1362     int ch = -1;
1363     const string lookup_type_prompts =
1364         comma_separated_fn(lookup_types.begin(), lookup_types.end(),
1365                            mem_fn(&LookupType::prompt_string), " or ");
1366     if (use_popup)
1367     {
1368         string prompt = make_stringf("Describe a %s? ",
1369                                                 lookup_type_prompts.c_str());
1370         linebreak_string(prompt, 72);
1371 
1372 #ifdef USE_TILE_WEB
1373         tiles_crt_popup show_as_popup;
1374         tiles.set_ui_state(UI_CRT);
1375 #endif
1376         auto prompt_ui =
1377                 make_shared<ui::Text>(formatted_string::parse_string(prompt));
1378         auto popup = make_shared<ui::Popup>(prompt_ui);
1379         bool done = false;
1380 
1381         popup->on_keydown_event([&](const ui::KeyEvent& ev) {
1382             ch = ev.key();
1383             return done = true;
1384         });
1385 
1386         mouse_control mc(MOUSE_MODE_MORE);
1387         ui::run_layout(move(popup), done);
1388     }
1389     else
1390     {
1391         mprf(MSGCH_PROMPT, "Describe a %s? ", lookup_type_prompts.c_str());
1392 
1393         {
1394             cursor_control con(true);
1395             ch = getchm();
1396         }
1397     }
1398     return toupper_safe(ch);
1399 }
1400 
1401 /**
1402  * Run an iteration of ?/.
1403  *
1404  * @param response[out]   A response to input, to print before the next iter.
1405  * @return                true if the ?/ loop should continue
1406  *                        false if it should return control to the caller
1407  */
_find_description(string & response)1408 static bool _find_description(string &response)
1409 {
1410     int ch = _lookup_prompt();
1411     const LookupType * const *lookup_type_ptr
1412         = map_find(_lookup_types_by_symbol, ch);
1413     if (!lookup_type_ptr)
1414         return false;
1415 
1416     ASSERT(*lookup_type_ptr);
1417     const LookupType ltype = **lookup_type_ptr;
1418 
1419     const bool want_regex = !(ltype.no_search());
1420     const string regex = want_regex ?
1421                          _prompt_for_regex(ltype, response) :
1422                          "";
1423 
1424     if (!response.empty())
1425         return true;
1426 
1427     // not actually sure how to trigger this branch...
1428     if (want_regex && regex.empty())
1429     {
1430         response = "Description must contain at least one non-space.";
1431         return true;
1432     }
1433 
1434 
1435     // Try to get an exact match first.
1436     const bool exact_match = _exact_lookup_match(ltype, regex);
1437 
1438     vector<string> key_list = ltype.matching_keys(regex);
1439 
1440     const bool by_symbol = ltype.supports_glyph_lookup()
1441                            && regex.size() == 1;
1442     const string type = lowercase_string(ltype.type);
1443     response = _keylist_invalid_reason(key_list, type, regex, by_symbol);
1444     if (!response.empty())
1445         return true;
1446 
1447     if (key_list.size() == 1)
1448     {
1449         ltype.describe(key_list[0]);
1450         return true;
1451     }
1452 
1453     if (exact_match && ltype.describe(regex, true) != ' ')
1454         return true;
1455 
1456     if (!(ltype.flags & lookup_type::disable_sort))
1457         sort(key_list.begin(), key_list.end());
1458 
1459     ltype.display_keys(key_list);
1460     return true;
1461 }
1462 
1463 /**
1464  * Run the ?/ loop, repeatedly prompting the player to query for monsters,
1465  * etc, until they indicate they're done.
1466  */
keyhelp_query_descriptions()1467 void keyhelp_query_descriptions()
1468 {
1469     string response;
1470     while (true)
1471     {
1472         redraw_screen();
1473         update_screen();
1474 
1475         if (!response.empty())
1476             mprf(MSGCH_PROMPT, "%s", response.c_str());
1477         response = "";
1478 
1479         if (!_find_description(response))
1480             break;
1481 
1482         clear_messages();
1483     }
1484 
1485     viewwindow();
1486     update_screen();
1487     mpr("Okay, then.");
1488 }
1489