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 ®ex)
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 ®ex,
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