1 /**
2 * @file
3 * @brief Misc commands.
4 **/
5
6 #include "AppHdr.h"
7
8 #include "command.h"
9
10 #include <cctype>
11 #include <cinttypes>
12 #include <cstdio>
13 #include <cstring>
14 #include <sstream>
15
16 #include "chardump.h"
17 #include "database.h"
18 #include "describe.h"
19 #include "env.h"
20 #include "files.h"
21 #include "hints.h"
22 #include "invent.h"
23 #include "item-prop.h"
24 #include "items.h"
25 #include "libutil.h"
26 #include "lookup-help.h"
27 #include "macro.h"
28 #include "message.h"
29 #include "prompt.h"
30 #include "scroller.h"
31 #include "showsymb.h"
32 #include "state.h"
33 #include "stringutil.h"
34 #include "syscalls.h"
35 #include "unicode.h"
36 #include "version.h"
37 #include "viewchar.h"
38
39 using namespace ui;
40
41 #ifdef USE_TILE
42 #include "rltiles/tiledef-gui.h"
43 #endif
44
45 static const char *features[] =
46 {
47 #ifdef CLUA_BINDINGS
48 "Lua user scripts",
49 #endif
50
51 #ifdef USE_TILE_LOCAL
52 "Tile support",
53 #endif
54
55 #ifdef USE_TILE_WEB
56 "Web Tile support",
57 #endif
58
59 #ifdef WIZARD
60 "Wizard mode",
61 #endif
62
63 #ifdef DEBUG
64 "Debug mode",
65 #endif
66
67 #if defined(REGEX_POSIX)
68 "POSIX regexps",
69 #elif defined(REGEX_PCRE)
70 "PCRE regexps",
71 #else
72 "Glob patterns",
73 #endif
74
75 #if defined(USE_SOUND) && defined(SOUND_BACKEND)
76 SOUND_BACKEND,
77 #endif
78
79 #ifdef DGL_MILESTONES
80 "Milestones",
81 #endif
82 };
83
_get_version_information()84 static string _get_version_information()
85 {
86 string result = string("This is <w>" CRAWL " ") + Version::Long + "</w>";
87 return result;
88 }
89
_get_version_features()90 static string _get_version_features()
91 {
92 string result;
93 if (crawl_state.need_save
94 #ifdef DGAMELAUNCH
95 && (you.wizard || crawl_state.type == GAME_TYPE_CUSTOM_SEED)
96 #endif
97 )
98 {
99 if (you.fully_seeded)
100 {
101 result += seed_description();
102 if (Version::history_size() > 1)
103 result += " (seed may be affected by game upgrades)";
104 }
105 else
106 result += "Game is not seeded.";
107 result += "\n\n";
108 }
109 if (Version::history_size() > 1)
110 {
111 result += "Version history for your current game:\n";
112 result += Version::history();
113 result += "\n\n";
114 }
115
116 result += "<w>Features</w>\n"
117 "--------\n";
118
119 for (const char *feature : features)
120 {
121 result += " * ";
122 result += feature;
123 result += "\n";
124 }
125
126 return result;
127 }
128
_get_version_changes()129 static string _get_version_changes()
130 {
131 // Attempts to print "Highlights" of the latest version.
132 FILE* fp = fopen_u(datafile_path("changelog.txt", false).c_str(), "r");
133 if (!fp)
134 return "";
135
136 string result = "";
137 string help;
138 char buf[200];
139 bool start = false;
140 while (fgets(buf, sizeof buf, fp))
141 {
142 // Remove trailing spaces.
143 for (int i = strlen(buf) - 1; i >= 0; i++)
144 {
145 if (isspace(buf[i]))
146 buf[i] = 0;
147 else
148 break;
149 }
150 help = buf;
151
152 // Look for version headings
153 if (starts_with(help, "Stone Soup "))
154 {
155 // Stop if this is for an older major version; otherwise, highlight
156 if (help.find(string("Stone Soup ")+Version::Major) == string::npos)
157 break;
158 else
159 goto highlight;
160 }
161
162 if (help.find("Highlights") != string::npos)
163 {
164 highlight:
165 // Highlight the Highlights, so to speak.
166 result += "<w>" + help + "</w>\n";
167 // And start printing from now on.
168 start = true;
169 }
170 else if (!start)
171 continue;
172 else
173 {
174 result += buf;
175 result += "\n";
176 }
177 }
178 fclose(fp);
179
180 // Did we ever get to print the Highlights?
181 if (start)
182 {
183 result.erase(1+result.find_last_not_of('\n'));
184 result += "\n\n";
185 result += "For earlier changes, see changelog.txt "
186 "in the docs/ directory.";
187 }
188 else
189 {
190 result += "For a list of changes, see changelog.txt in the docs/ "
191 "directory.";
192 }
193
194 return result;
195 }
196
197 //#define DEBUG_FILES
_print_version()198 static void _print_version()
199 {
200 const string info = _get_version_information(),
201 feats = _get_version_features(),
202 changes = _get_version_changes();
203
204 auto vbox = make_shared<Box>(Widget::VERT);
205
206 #ifdef USE_TILE_LOCAL
207 vbox->max_size().width = tiles.get_crt_font()->char_width()*80;
208 #endif
209
210 auto title_hbox = make_shared<Box>(Widget::HORZ);
211 #ifdef USE_TILE
212 auto icon = make_shared<Image>();
213 icon->set_tile(tile_def(TILEG_STARTUP_STONESOUP));
214 title_hbox->add_child(move(icon));
215 #endif
216
217 auto title = make_shared<Text>(formatted_string::parse_string(info));
218 title->set_margin_for_sdl(0, 0, 0, 10);
219 title_hbox->add_child(move(title));
220
221 title_hbox->set_cross_alignment(Widget::CENTER);
222 title_hbox->set_margin_for_crt(0, 0, 1, 0);
223 title_hbox->set_margin_for_sdl(0, 0, 20, 0);
224 vbox->add_child(move(title_hbox));
225
226 auto scroller = make_shared<Scroller>();
227 auto content = formatted_string::parse_string(feats + "\n\n" + changes);
228 auto text = make_shared<Text>(move(content));
229 text->set_wrap_text(true);
230 scroller->set_child(move(text));
231 vbox->add_child(scroller);
232
233 auto popup = make_shared<ui::Popup>(vbox);
234
235 bool done = false;
236 popup->on_keydown_event([&](const KeyEvent& ev) {
237 done = !scroller->on_event(ev);
238 return true;
239 });
240
241 #ifdef USE_TILE_WEB
242 tiles.json_open_object();
243 tiles.json_write_string("information", info);
244 tiles.json_write_string("features", feats);
245 tiles.json_write_string("changes", changes);
246 tiles.push_ui_layout("version", 0);
247 popup->on_layout_pop([](){ tiles.pop_ui_layout(); });
248 #endif
249
250 ui::run_layout(move(popup), done);
251 }
252
list_armour()253 void list_armour()
254 {
255 ostringstream estr;
256 for (int j = EQ_MIN_ARMOUR; j <= EQ_MAX_ARMOUR; j++)
257 {
258 const equipment_type i = static_cast<equipment_type>(j);
259 const int armour_id = you.equip[i];
260 int colour = MSGCOL_BLACK;
261
262 estr.str("");
263 estr.clear();
264
265 estr << ((i == EQ_CLOAK) ? "Cloak " :
266 (i == EQ_HELMET) ? "Helmet " :
267 (i == EQ_GLOVES) ? "Gloves " :
268 (i == EQ_SHIELD) ? "Shield " :
269 (i == EQ_BODY_ARMOUR) ? "Armour " :
270 (i == EQ_BOOTS) ?
271 (you.wear_barding() ? "Barding"
272 : "Boots ")
273 : "unknown")
274 << " : ";
275
276 if (you_can_wear(i) == MB_FALSE)
277 estr << " (unavailable)";
278 else if (you_can_wear(i, true) == MB_FALSE)
279 estr << " (currently unavailable)";
280 else if (armour_id != -1)
281 {
282 estr << you.inv[armour_id].name(DESC_INVENTORY);
283 colour = menu_colour(estr.str(), item_prefix(you.inv[armour_id]),
284 "equip");
285 }
286 else if (you_can_wear(i) == MB_MAYBE)
287 estr << " (restricted)";
288 else
289 estr << " none";
290
291 if (colour == MSGCOL_BLACK)
292 colour = menu_colour(estr.str(), "", "equip");
293
294 mprf(MSGCH_EQUIPMENT, colour, "%s", estr.str().c_str());
295 }
296 }
297
list_jewellery()298 void list_jewellery()
299 {
300 string jstr;
301 int cols = get_number_of_cols() - 1;
302 bool split = species::arm_count(you.species) > 2 && cols > 84;
303
304 for (int j = EQ_LEFT_RING; j < NUM_EQUIP; j++)
305 {
306 const equipment_type i = static_cast<equipment_type>(j);
307 if (!you_can_wear(i))
308 continue;
309
310 const int jewellery_id = you.equip[i];
311 int colour = MSGCOL_BLACK;
312
313 const char *slot =
314 (i == EQ_LEFT_RING) ? "Left ring" :
315 (i == EQ_RIGHT_RING) ? "Right ring" :
316 (i == EQ_AMULET) ? "Amulet" :
317 (i == EQ_RING_ONE) ? "1st ring" :
318 (i == EQ_RING_TWO) ? "2nd ring" :
319 (i == EQ_RING_THREE) ? "3rd ring" :
320 (i == EQ_RING_FOUR) ? "4th ring" :
321 (i == EQ_RING_FIVE) ? "5th ring" :
322 (i == EQ_RING_SIX) ? "6th ring" :
323 (i == EQ_RING_SEVEN) ? "7th ring" :
324 (i == EQ_RING_EIGHT) ? "8th ring" :
325 (i == EQ_RING_AMULET) ? "Amulet ring"
326 : "unknown";
327
328 string item;
329 if (you_can_wear(i, true) == MB_FALSE)
330 item = " (currently unavailable)";
331 else if (jewellery_id != -1)
332 {
333 item = you.inv[jewellery_id].name(DESC_INVENTORY);
334 string prefix = item_prefix(you.inv[jewellery_id]);
335 colour = menu_colour(item, prefix, "equip");
336 }
337 else
338 item = " none";
339
340 if (colour == MSGCOL_BLACK)
341 colour = menu_colour(item, "", "equip");
342
343 item = chop_string(make_stringf("%-*s: %s",
344 split ? cols > 96 ? 9 : 8 : 11,
345 slot, item.c_str()),
346 split && i > EQ_AMULET ? (cols - 1) / 2 : cols);
347 item = colour_string(item, colour);
348
349 // doesn't handle arbitrary arm counts
350 if (i == EQ_RING_SEVEN && you.arm_count() == 7)
351 mprf(MSGCH_EQUIPMENT, "%s", item.c_str());
352 else if (split && i > EQ_AMULET && (i - EQ_AMULET) % 2)
353 jstr = item + " ";
354 else
355 mprf(MSGCH_EQUIPMENT, "%s%s", jstr.c_str(), item.c_str());
356 }
357 }
358
359 static const char *targeting_help_1 =
360 "<h>Examine surroundings ('<w>x</w><h>' in main):\n"
361 "<w>Esc</w> : cancel (also <w>Space</w>, <w>x</w>)\n"
362 "<w>Ctrl-X</w> : list all things in view\n"
363 "<w>Dir.</w>: move cursor in that direction\n"
364 "<w>.</w> : move to cursor (also <w>Enter</w>, <w>Del</w>)\n"
365 "<w>g</w> : pick up item at cursor\n"
366 "<w>v</w> : describe monster under cursor\n"
367 "<w>+</w> : cycle monsters forward (also <w>=</w>)\n"
368 "<w>-</w> : cycle monsters backward\n"
369 "<w>*</w> : cycle objects forward (also <w>'</w>)\n"
370 "<w>/</w> : cycle objects backward (also <w>;</w>)\n"
371 "<w>^</w> : cycle through traps\n"
372 "<w>_</w> : cycle through altars\n"
373 "<w><<</w>/<w>></w> : cycle through up/down stairs\n"
374 "<w>Tab</w> : cycle through shops and portals\n"
375 "<w>r</w> : move cursor to you\n"
376 "<w>e</w> : create/remove travel exclusion\n"
377 "<w>Ctrl-P</w> : repeat prompt\n"
378 ;
379 #ifdef WIZARD
380 static const char *targeting_help_wiz =
381 "<h>Wizard targeting commands:</h>\n"
382 "<w>Ctrl-C</w> : cycle through beam paths\n"
383 "<w>D</w>: get debugging information about the monster\n"
384 "<w>o</w>: give item to monster\n"
385 "<w>F</w>: cycle monster friendly/good neutral/neutral/hostile\n"
386 "<w>G</w>: make monster gain experience\n"
387 "<w>Ctrl-H</w>: heal the monster fully\n"
388 "<w>P</w>: apply divine blessing to monster\n"
389 "<w>m</w>: move monster or player\n"
390 "<w>M</w>: cause spell miscast for monster or player\n"
391 "<w>s</w>: force monster to shout or speak\n"
392 "<w>S</w>: make monster a summoned monster\n"
393 "<w>w</w>: calculate shortest path to any point on the map\n"
394 "<w>\"</w>: get debugging information about a portal\n"
395 "<w>~</w>: polymorph monster to specific type\n"
396 "<w>,</w>: bring down the monster to 1 hp\n"
397 "<w>Ctrl-(</w>: place a mimic\n"
398 "<w>Ctrl-B</w>: banish monster\n"
399 "<w>Ctrl-K</w>: kill monster\n"
400 ;
401 #endif
402
403 static const char *targeting_help_2 =
404 "<h>Targeting (zap wands, cast spells, etc.):\n"
405 "Most keys from examine surroundings work.\n"
406 "Some keys fire at the target. <w>Ctrl-X</w> only\n"
407 "lists eligible targets. By default,\n"
408 "range is respected and beams don't stop.\n"
409 "<w>Enter</w> : fire (<w>Space</w>, <w>Del</w>)\n"
410 "<w>.</w> : fire, stop at target\n"
411 "<w>@</w> : fire, stop at target, ignore range\n"
412 "<w>!</w> : fire, don't stop, ignore range\n"
413 "<w>p</w> : fire at Previous target (also <w>f</w>)\n"
414 "<w>:</w> : show/hide beam path\n"
415 "<w>Shift-Dir.</w> : fire straight-line beam\n"
416 "\n"
417 "<h>Firing mode ('<w>f</w><h>' in main):\n"
418 "<w>Q</w> : choose fire action.\n"
419 "<w>(</w> : cycle to previous suitable action\n"
420 "<w>)</w> : cycle to next suitable action.\n"
421 ;
422
423 struct help_file
424 {
425 const char* name;
426 int hotkey;
427 bool auto_hotkey;
428 };
429
430 static help_file help_files[] =
431 {
432 { "crawl_manual.txt", '*', true },
433 { "aptitudes.txt", '%', false },
434 { "quickstart.txt", '^', false },
435 { "macros_guide.txt", '~', false },
436 { "options_guide.txt", '&', false },
437 #ifdef USE_TILE_LOCAL
438 { "tiles_help.txt", 't', false },
439 #endif
440 { nullptr, 0, false }
441 };
442
443 // Reads all questions from database/FAQ.txt, outputs them in the form of
444 // a selectable menu and prints the corresponding answer for a chosen question.
_handle_FAQ()445 static void _handle_FAQ()
446 {
447 vector<string> question_keys = getAllFAQKeys();
448 if (question_keys.empty())
449 {
450 mpr("No questions found in FAQ! Please submit a bug report!");
451 return;
452 }
453 Menu FAQmenu(MF_SINGLESELECT | MF_ANYPRINTABLE | MF_ALLOW_FORMATTING);
454 MenuEntry *title = new MenuEntry("Frequently Asked Questions");
455 title->colour = YELLOW;
456 FAQmenu.set_title(title);
457
458 for (unsigned int i = 0, size = question_keys.size(); i < size; i++)
459 {
460 const char letter = index_to_letter(i);
461 string question = getFAQ_Question(question_keys[i]);
462 trim_string_right(question);
463 MenuEntry *me = new MenuEntry(question, MEL_ITEM, 1, letter);
464 me->data = &question_keys[i];
465 FAQmenu.add_entry(me);
466 }
467
468 while (true)
469 {
470 vector<MenuEntry*> sel = FAQmenu.show();
471 if (sel.empty())
472 return;
473 else
474 {
475 ASSERT(sel.size() == 1);
476 ASSERT(sel[0]->hotkeys.size() == 1);
477
478 string key = *((string*) sel[0]->data);
479 string answer = getFAQ_Answer(key);
480 if (answer.empty())
481 {
482 answer = "No answer found in the FAQ! Please submit a "
483 "bug report!";
484 }
485 answer = "Q: " + getFAQ_Question(key) + "\n" + answer;
486 show_description(answer);
487 }
488 }
489
490 return;
491 }
492
493 ////////////////////////////////////////////////////////////////////////////
494
show_keyhelp_menu(const vector<formatted_string> & lines)495 int show_keyhelp_menu(const vector<formatted_string> &lines)
496 {
497 int flags = FS_PREWRAPPED_TEXT | FS_EASY_EXIT;
498 formatted_scroller cmd_help(flags);
499 cmd_help.set_tag("help");
500 cmd_help.set_more();
501
502 for (unsigned i = 0; i < lines.size(); ++i)
503 cmd_help.add_formatted_string(lines[i], i < lines.size()-1);
504
505 cmd_help.show();
506
507 return cmd_help.get_lastch();
508 }
509
show_specific_help(const string & key)510 void show_specific_help(const string &key)
511 {
512 string help = getHelpString(key);
513 trim_string_right(help);
514 vector<formatted_string> formatted_lines;
515 for (const string &line : split_string("\n", help, false, true))
516 formatted_lines.push_back(formatted_string::parse_string(line));
517 show_keyhelp_menu(formatted_lines);
518 }
519
show_levelmap_help()520 void show_levelmap_help()
521 {
522 show_specific_help("level-map");
523 }
524
show_targeting_help()525 void show_targeting_help()
526 {
527 column_composer cols(2, 40);
528 cols.add_formatted(0, targeting_help_1, true);
529 #ifdef WIZARD
530 if (you.wizard)
531 cols.add_formatted(0, targeting_help_wiz, true);
532 #endif
533 cols.add_formatted(1, targeting_help_2, true);
534 show_keyhelp_menu(cols.formatted_lines());
535 }
show_interlevel_travel_branch_help()536 void show_interlevel_travel_branch_help()
537 {
538 show_specific_help("interlevel-travel.branch.prompt");
539 }
540
show_interlevel_travel_depth_help()541 void show_interlevel_travel_depth_help()
542 {
543 show_specific_help("interlevel-travel.depth.prompt");
544 }
545
show_interlevel_travel_altar_help()546 void show_interlevel_travel_altar_help()
547 {
548 show_specific_help("interlevel-travel.altar.prompt");
549 }
550
show_annotate_help()551 void show_annotate_help()
552 {
553 show_specific_help("annotate.prompt");
554 }
555
show_stash_search_help()556 void show_stash_search_help()
557 {
558 show_specific_help("stash-search.prompt");
559 }
560
show_skill_menu_help()561 void show_skill_menu_help()
562 {
563 show_specific_help("skill-menu");
564 }
565
show_spell_library_help()566 void show_spell_library_help()
567 {
568 if (crawl_state.game_is_hints_tutorial())
569 {
570 const string help1 = hints_memorise_info() + "\n\n";
571 vector<formatted_string> formatted_lines;
572 for (const string &line : split_string("\n", help1, false, true))
573 {
574 formatted_lines.push_back(formatted_string::parse_string(line,
575 channel_to_colour(MSGCH_TUTORIAL)));
576 }
577 const string help2 = getHelpString("spell-library");
578 for (const string &line : split_string("\n", help2, false, true))
579 formatted_lines.push_back(formatted_string::parse_string(line));
580 show_keyhelp_menu(formatted_lines);
581 }
582 else
583 show_specific_help("spell-library");
584 }
585
_add_command(column_composer & cols,const int column,const command_type cmd,const string & desc,const unsigned int space_to_colon=7)586 static void _add_command(column_composer &cols, const int column,
587 const command_type cmd,
588 const string &desc,
589 const unsigned int space_to_colon = 7)
590 {
591 string command_name = command_to_string(cmd);
592 if (strcmp(command_name.c_str(), "<") == 0)
593 command_name += "<";
594
595 const int cmd_len = strwidth(command_name);
596 string line = "<w>" + command_name + "</w>";
597 for (unsigned int i = cmd_len; i < space_to_colon; ++i)
598 line += " ";
599 line += ": " + untag_tiles_console(desc) + "\n";
600
601 cols.add_formatted(
602 column,
603 line.c_str(),
604 false);
605 }
606
_add_insert_commands(column_composer & cols,const int column,const unsigned int space_to_colon,command_type lead_cmd,string desc,const vector<command_type> & cmd_vector)607 static void _add_insert_commands(column_composer &cols, const int column,
608 const unsigned int space_to_colon,
609 command_type lead_cmd, string desc,
610 const vector<command_type> &cmd_vector)
611 {
612 insert_commands(desc, cmd_vector);
613 desc += "\n";
614 _add_command(cols, column, lead_cmd, desc, space_to_colon);
615 }
616
_add_insert_commands(column_composer & cols,const int column,string desc,const vector<command_type> & cmd_vector)617 static void _add_insert_commands(column_composer &cols, const int column,
618 string desc,
619 const vector<command_type> &cmd_vector)
620 {
621 insert_commands(desc, cmd_vector);
622 desc += "\n";
623 cols.add_formatted(column, desc.c_str(), false);
624 }
625
_add_formatted_help_menu(column_composer & cols)626 static void _add_formatted_help_menu(column_composer &cols)
627 {
628 cols.add_formatted(
629 0,
630 "<h>Dungeon Crawl Help\n"
631 "\n"
632 "Press one of the following keys to\n"
633 "obtain more information on a certain\n"
634 "aspect of Dungeon Crawl.\n"
635
636 "<w>?</w>: List of commands\n"
637 "<w>^</w>: Quickstart Guide\n"
638 "<w>:</w>: Browse character notes\n"
639 "<w>#</w>: Browse character dump\n"
640 "<w>~</w>: Macros help\n"
641 "<w>&</w>: Options help\n"
642 "<w>%</w>: Table of aptitudes\n"
643 "<w>/</w>: Lookup description\n"
644 "<w>Q</w>: FAQ\n"
645 #ifdef USE_TILE_LOCAL
646 "<w>T</w>: Tiles key help\n"
647 #endif
648 "<w>V</w>: Version information\n"
649 "<w>Home</w>: This screen\n");
650
651 // TODO: generate this from the manual somehow
652 cols.add_formatted(
653 1,
654 "<h>Manual Contents\n\n"
655 "<w>*</w> Table of contents\n"
656 "<w>A</w>. Overview\n"
657 "<w>B</w>. Starting Screen\n"
658 "<w>C</w>. Attributes and Stats\n"
659 "<w>D</w>. Exploring the Dungeon\n"
660 "<w>E</w>. Experience and Skills\n"
661 "<w>F</w>. Monsters\n"
662 "<w>G</w>. Items\n"
663 "<w>H</w>. Spellcasting\n"
664 "<w>I</w>. Targeting\n"
665 "<w>J</w>. Religion\n"
666 "<w>K</w>. Mutations\n"
667 "<w>L</w>. Licence, Contact, History\n"
668 "<w>M</w>. Macros, Options, Performance\n"
669 "<w>N</w>. Philosophy\n"
670 "<w>1</w>. List of Character Species\n"
671 "<w>2</w>. List of Character Backgrounds\n"
672 "<w>3</w>. List of Skills\n"
673 "<w>4</w>. List of Keys and Commands\n"
674 "<w>5</w>. Inscriptions\n"
675 "<w>6</w>. Dungeon sprint modes\n");
676 }
677
_add_formatted_keyhelp(column_composer & cols)678 static void _add_formatted_keyhelp(column_composer &cols)
679 {
680 cols.add_formatted(
681 0,
682 "<h>Movement:\n"
683 "To move in a direction or to attack, \n"
684 "use the numpad (try Numlock off and \n"
685 "on) or vi keys:\n");
686
687 _add_insert_commands(cols, 0, " <w>7 8 9 % % %",
688 { CMD_MOVE_UP_LEFT, CMD_MOVE_UP, CMD_MOVE_UP_RIGHT });
689 _add_insert_commands(cols, 0, " \\|/ \\|/", {});
690 _add_insert_commands(cols, 0, " <w>4</w>-<w>5</w>-<w>6</w>"
691 " <w>%</w>-<w>%</w>-<w>%</w>",
692 { CMD_MOVE_LEFT, CMD_WAIT, CMD_MOVE_RIGHT });
693 _add_insert_commands(cols, 0, " /|\\ /|\\", {});
694 _add_insert_commands(cols, 0, " <w>1 2 3 % % %",
695 { CMD_MOVE_DOWN_LEFT, CMD_MOVE_DOWN,
696 CMD_MOVE_DOWN_RIGHT });
697
698 cols.add_formatted(
699 0,
700 "<h>Rest:\n");
701
702 _add_command(cols, 0, CMD_WAIT, "wait a turn (also <w>s</w>, <w>Del</w>)", 2);
703 _add_command(cols, 0, CMD_REST, "rest and long wait; stops when", 2);
704 cols.add_formatted(
705 0,
706 " Health or Magic become full or\n"
707 " something is detected. If Health\n"
708 " and Magic are already full, stops\n"
709 " when 100 turns over (<w>numpad-5</w>)\n",
710 false);
711
712 cols.add_formatted(
713 0,
714 "<h>Extended Movement:\n");
715
716 _add_command(cols, 0, CMD_EXPLORE, "auto-explore");
717 _add_command(cols, 0, CMD_INTERLEVEL_TRAVEL, "interlevel travel");
718 _add_command(cols, 0, CMD_SEARCH_STASHES, "Find items");
719 _add_command(cols, 0, CMD_FIX_WAYPOINT, "set Waypoint");
720
721 cols.add_formatted(
722 0,
723 "<w>/ Dir.</w>, <w>Shift-Dir.</w>: long walk\n"
724 "<w>* Dir.</w>, <w>Ctrl-Dir.</w> : attack without move \n",
725 false);
726
727 cols.add_formatted(
728 0,
729 "<h>Autofight:\n"
730 "<w>Tab</w> : attack nearest monster,\n"
731 " moving if necessary\n"
732 "<w>Shift-Tab</w>, <w>p</w> : trigger quivered action;\n"
733 " if targeted, aims at\n"
734 " nearest monster\n");
735
736 cols.add_formatted(
737 0,
738 "<h>Item types (and common commands)\n");
739
740 _add_insert_commands(cols, 0, "<cyan>)</cyan> : hand weapons (<w>%</w>ield)",
741 { CMD_WIELD_WEAPON });
742 _add_insert_commands(cols, 0, "<brown>(</brown> : missiles (<w>%</w>uiver, "
743 "<w>%</w>ire, <w>%</w>/<w>%</w> cycle)",
744 { CMD_QUIVER_ITEM, CMD_FIRE, CMD_CYCLE_QUIVER_FORWARD,
745 CMD_CYCLE_QUIVER_BACKWARD });
746 _add_insert_commands(cols, 0, "<cyan>[</cyan> : armour (<w>%</w>ear and <w>%</w>ake off)",
747 { CMD_WEAR_ARMOUR, CMD_REMOVE_ARMOUR });
748 _add_insert_commands(cols, 0, "<w>?</w> : scrolls (<w>%</w>ead)",
749 { CMD_READ });
750 _add_insert_commands(cols, 0, "<magenta>!</magenta> : potions (<w>%</w>uaff)",
751 { CMD_QUAFF });
752 _add_insert_commands(cols, 0, "<blue>=</blue> : rings (<w>%</w>ut on and <w>%</w>emove)",
753 { CMD_WEAR_JEWELLERY, CMD_REMOVE_JEWELLERY });
754 _add_insert_commands(cols, 0, "<red>\"</red> : amulets (<w>%</w>ut on and <w>%</w>emove)",
755 { CMD_WEAR_JEWELLERY, CMD_REMOVE_JEWELLERY });
756 _add_insert_commands(cols, 0, "<lightgrey>/</lightgrey> : wands (e<w>%</w>oke)",
757 { CMD_EVOKE });
758
759 string item_types = "<lightcyan>";
760 item_types += stringize_glyph(get_item_symbol(SHOW_ITEM_BOOK));
761 item_types +=
762 "</lightcyan> : books (<w>%</w>emorise, <w>%</w>ap, <w>%</w>ap,\n"
763 " pick up to add to library)";
764 _add_insert_commands(cols, 0, item_types,
765 { CMD_MEMORISE_SPELL, CMD_CAST_SPELL,
766 CMD_FORCE_CAST_SPELL });
767 _add_insert_commands(cols, 0, "<brown>|</brown> : staves (<w>%</w>ield)",
768 { CMD_WIELD_WEAPON});
769 _add_insert_commands(cols, 0, "<lightgreen>}</lightgreen> : miscellaneous items (e<w>%</w>oke)",
770 { CMD_EVOKE });
771 _add_insert_commands(cols, 0, "<yellow>$</yellow> : gold (<w>%</w> counts gold)",
772 { CMD_LIST_GOLD });
773
774 cols.add_formatted(
775 0,
776 "<lightmagenta>0</lightmagenta> : the Orb of Zot\n"
777 " Carry it to the surface and win!\n",
778 false);
779
780 cols.add_formatted(
781 0,
782 "<h>Other Gameplay Actions:\n");
783
784 _add_insert_commands(cols, 0, 2, CMD_USE_ABILITY,
785 "use special Ability (<w>%!</w> for help)",
786 { CMD_USE_ABILITY });
787 _add_command(cols, 0, CMD_CAST_SPELL, "cast spell, abort without targets", 2);
788 _add_command(cols, 0, CMD_FORCE_CAST_SPELL, "cast spell, no matter what", 2);
789 _add_command(cols, 0, CMD_DISPLAY_SPELLS, "list all memorised spells", 2);
790 _add_command(cols, 0, CMD_MEMORISE_SPELL, "Memorise a spell from your library", 2);
791
792 _add_insert_commands(cols, 0, 2, CMD_SHOUT,
793 "tell allies (<w>%t</w> to shout)",
794 { CMD_SHOUT });
795 _add_command(cols, 0, CMD_PREV_CMD_AGAIN, "re-do previous command", 2);
796 _add_command(cols, 0, CMD_REPEAT_CMD, "repeat next command # of times", 2);
797
798 cols.add_formatted(
799 0,
800 "<h>Non-Gameplay Commands / Info\n");
801
802 _add_command(cols, 0, CMD_GAME_MENU, "game menu", 2);
803 _add_command(cols, 0, CMD_REPLAY_MESSAGES, "show Previous messages");
804 _add_command(cols, 0, CMD_REDRAW_SCREEN, "Redraw screen");
805 _add_command(cols, 0, CMD_CLEAR_MAP, "Clear main and level maps");
806 _add_command(cols, 0, CMD_MACRO_ADD, "quick add macro");
807 _add_command(cols, 0, CMD_MACRO_MENU, "edit macros");
808 _add_command(cols, 0, CMD_ANNOTATE_LEVEL, "annotate the dungeon level", 2);
809 _add_command(cols, 0, CMD_CHARACTER_DUMP, "dump character to file", 2);
810 _add_insert_commands(cols, 0, 2, CMD_MAKE_NOTE,
811 "add note (use <w>%:</w> to read notes)",
812 { CMD_DISPLAY_COMMANDS });
813 _add_command(cols, 0, CMD_ADJUST_INVENTORY, "reassign inventory/spell letters", 2);
814 #ifdef USE_TILE_LOCAL
815 _add_command(cols, 0, CMD_EDIT_PLAYER_TILE, "edit player doll", 2);
816 #else
817 #ifdef USE_TILE_WEB
818 if (tiles.is_controlled_from_web())
819 {
820 cols.add_formatted(0, "<w>F12</w> : read messages (online play only)",
821 false);
822 }
823 else
824 #endif
825 _add_command(cols, 0, CMD_READ_MESSAGES, "read messages (online play only)", 2);
826 #endif
827
828 cols.add_formatted(
829 1,
830 "<h>Game Saving and Quitting:\n");
831
832 _add_command(cols, 1, CMD_SAVE_GAME, "Save game and exit");
833 _add_command(cols, 1, CMD_SAVE_GAME_NOW, "Save and exit without query");
834 _add_command(cols, 1, CMD_QUIT, "Abandon the current character");
835 cols.add_formatted(1, " and quit the game\n",
836 false);
837
838 cols.add_formatted(
839 1,
840 "<h>Player Character Information:\n");
841
842 _add_command(cols, 1, CMD_DISPLAY_CHARACTER_STATUS, "display character status", 2);
843 _add_command(cols, 1, CMD_DISPLAY_SKILLS, "show skill screen", 2);
844 _add_command(cols, 1, CMD_RESISTS_SCREEN, "character overview", 2);
845 _add_command(cols, 1, CMD_DISPLAY_RELIGION, "show religion screen", 2);
846 _add_command(cols, 1, CMD_DISPLAY_MUTATIONS, "show Abilities/mutations", 2);
847 _add_command(cols, 1, CMD_DISPLAY_KNOWN_OBJECTS, "show item knowledge", 2);
848 _add_command(cols, 1, CMD_MEMORISE_SPELL, "show your spell library", 2);
849 _add_command(cols, 1, CMD_DISPLAY_RUNES, "show runes collected", 2);
850 _add_command(cols, 1, CMD_LIST_ARMOUR, "display worn armour", 2);
851 _add_command(cols, 1, CMD_LIST_JEWELLERY, "display worn jewellery", 2);
852 _add_command(cols, 1, CMD_LIST_GOLD, "display gold in possession", 2);
853 _add_command(cols, 1, CMD_EXPERIENCE_CHECK, "display experience info", 2);
854
855 cols.add_formatted(
856 1,
857 "<h>Dungeon Interaction and Information:\n");
858
859 _add_insert_commands(cols, 1, "<w>%</w>/<w>%</w> : Open/Close door",
860 { CMD_OPEN_DOOR, CMD_CLOSE_DOOR });
861 _add_insert_commands(cols, 1, "<w>%</w>/<w>%</w> : use staircase",
862 { CMD_GO_UPSTAIRS, CMD_GO_DOWNSTAIRS });
863
864 _add_command(cols, 1, CMD_INSPECT_FLOOR, "examine occupied tile and");
865 cols.add_formatted(1, " pickup part of a single stack\n",
866 false);
867
868 _add_command(cols, 1, CMD_LOOK_AROUND, "eXamine surroundings/targets");
869 _add_insert_commands(cols, 1, 7, CMD_DISPLAY_MAP,
870 "eXamine level map (<w>%?</w> for help)",
871 { CMD_DISPLAY_MAP });
872 _add_command(cols, 1, CMD_FULL_VIEW, "list monsters, items, features");
873 cols.add_formatted(1, " in view\n",
874 false);
875 _add_command(cols, 1, CMD_SHOW_TERRAIN, "toggle view layers");
876 _add_command(cols, 1, CMD_DISPLAY_OVERMAP, "show dungeon Overview");
877 _add_command(cols, 1, CMD_TOGGLE_AUTOPICKUP, "toggle auto-pickup");
878 #ifdef USE_SOUND
879 _add_command(cols, 1, CMD_TOGGLE_SOUND, "mute/unmute sound effects");
880 #endif
881 _add_command(cols, 1, CMD_TOGGLE_TRAVEL_SPEED, "set your travel speed to your");
882 cols.add_formatted(1, " slowest ally\n",
883 false);
884 #ifdef USE_TILE_LOCAL
885 _add_insert_commands(cols, 1, "<w>%</w>/<w>%</w> : zoom out/in",
886 { CMD_ZOOM_OUT, CMD_ZOOM_IN });
887 #endif
888
889 cols.add_formatted(
890 1,
891 "<h>Inventory management:\n");
892
893 _add_command(cols, 1, CMD_DISPLAY_INVENTORY, "show Inventory list", 2);
894 _add_command(cols, 1, CMD_PICKUP, "pick up items (also <w>g</w>)", 2);
895 cols.add_formatted(
896 1,
897 " (press twice for pick up menu)\n",
898 false);
899
900 _add_command(cols, 1, CMD_DROP, "Drop an item", 2);
901 _add_insert_commands(cols, 1, "<w>%#</w>: Drop exact number of items",
902 { CMD_DROP });
903 _add_command(cols, 1, CMD_DROP_LAST, "Drop the last item(s) you picked up", 2);
904
905 cols.add_formatted(
906 1,
907 "<h>Item Interaction:\n");
908
909 _add_command(cols, 1, CMD_INSCRIBE_ITEM, "inscribe item", 2);
910 _add_command(cols, 1, CMD_FIRE, "Fire the currently quivered action", 2);
911 _add_command(cols, 1, CMD_THROW_ITEM_NO_QUIVER, "select an item and Fire it", 2);
912 _add_command(cols, 1, CMD_QUIVER_ITEM, "select action to be Quivered", 2);
913 _add_command(cols, 1, CMD_SWAP_QUIVER_RECENT, "swap between most recent quiver actions", 2);
914 _add_command(cols, 1, CMD_QUAFF, "Quaff a potion", 2);
915 _add_command(cols, 1, CMD_READ, "Read a scroll", 2);
916 _add_command(cols, 1, CMD_WIELD_WEAPON, "Wield an item (<w>-</w> for none)", 2);
917 _add_command(cols, 1, CMD_WEAPON_SWAP, "wield item a, or switch to b", 2);
918
919 _add_insert_commands(cols, 1, " (use <w>%</w> to assign slots)",
920 { CMD_ADJUST_INVENTORY });
921
922 _add_command(cols, 1, CMD_PRIMARY_ATTACK, "attack with wielded item", 2);
923 _add_command(cols, 1, CMD_EVOKE, "eVoke wand and miscellaneous item", 2);
924
925 _add_insert_commands(cols, 1, "<w>%</w>/<w>%</w> : Wear or Take off armour",
926 { CMD_WEAR_ARMOUR, CMD_REMOVE_ARMOUR });
927 _add_insert_commands(cols, 1, "<w>%</w>/<w>%</w> : Put on or Remove jewellery",
928 { CMD_WEAR_JEWELLERY, CMD_REMOVE_JEWELLERY });
929
930 cols.add_formatted(
931 1,
932 "<h>Additional help:\n");
933
934 string text =
935 "Many commands have context sensitive "
936 "help, among them <w>%</w>, <w>%</w>, <w>%</w> (or any "
937 "form of targeting), <w>%</w>, and <w>%</w>.\n"
938 "You can read descriptions of your "
939 "current spells (<w>%</w>), skills (<w>%?</w>) and "
940 "abilities (<w>%!</w>).";
941 insert_commands(text, { CMD_DISPLAY_MAP, CMD_LOOK_AROUND, CMD_FIRE,
942 CMD_SEARCH_STASHES, CMD_INTERLEVEL_TRAVEL,
943 CMD_DISPLAY_SPELLS, CMD_DISPLAY_SKILLS,
944 CMD_USE_ABILITY
945 });
946 linebreak_string(text, 40);
947
948 cols.add_formatted(
949 1, text,
950 false);
951 }
952
_add_formatted_hints_help(column_composer & cols)953 static void _add_formatted_hints_help(column_composer &cols)
954 {
955 // First column.
956 cols.add_formatted(
957 0,
958 "<h>Movement:\n"
959 "To move in a direction or to attack, \n"
960 "use the numpad (try Numlock off and \n"
961 "on) or vi keys:\n",
962 false);
963
964 _add_insert_commands(cols, 0, " <w>7 8 9 % % %",
965 { CMD_MOVE_UP_LEFT, CMD_MOVE_UP, CMD_MOVE_UP_RIGHT });
966 _add_insert_commands(cols, 0, " \\|/ \\|/", {});
967 _add_insert_commands(cols, 0, " <w>4</w>-<w>5</w>-<w>6</w>"
968 " <w>%</w>-<w>%</w>-<w>%</w>",
969 { CMD_MOVE_LEFT, CMD_WAIT, CMD_MOVE_RIGHT });
970 _add_insert_commands(cols, 0, " /|\\ /|\\", {});
971 _add_insert_commands(cols, 0, " <w>1 2 3 % % %",
972 { CMD_MOVE_DOWN_LEFT, CMD_MOVE_DOWN,
973 CMD_MOVE_DOWN_RIGHT });
974
975 cols.add_formatted(0, " ", false);
976 cols.add_formatted(0, "<w>Shift-Dir.</w> runs into one direction",
977 false);
978 _add_insert_commands(cols, 0, "<w>%</w> or <w>%</w> : ascend/descend the stairs",
979 { CMD_GO_UPSTAIRS, CMD_GO_DOWNSTAIRS });
980 _add_command(cols, 0, CMD_EXPLORE, "autoexplore", 2);
981
982 cols.add_formatted(
983 0,
984 "<h>Rest:\n");
985
986 _add_command(cols, 0, CMD_WAIT, "wait a turn (also <w>s</w>, <w>Del</w>)", 2);
987 _add_command(cols, 0, CMD_REST, "rest and long wait; stops when", 2);
988 cols.add_formatted(
989 0,
990 " Health or Magic become full or\n"
991 " something is detected. If Health\n"
992 " and Magic are already full, stops\n"
993 " when 100 turns over (<w>numpad-5</w>)\n",
994 false);
995
996 cols.add_formatted(
997 0,
998 "\n<h>Attacking monsters\n"
999 "Walking into a monster will attack it\n"
1000 "with the wielded weapon or barehanded.",
1001 false);
1002
1003 cols.add_formatted(
1004 0,
1005 "\n<h>Ranged combat and magic\n",
1006 false);
1007
1008 _add_insert_commands(cols, 0, "<w>%</w> to throw/fire missiles",
1009 { CMD_FIRE });
1010 _add_insert_commands(cols, 0, "<w>%</w>/<w>%</w> to cast spells "
1011 "(<w>%?/%</w> lists spells)",
1012 { CMD_CAST_SPELL, CMD_FORCE_CAST_SPELL, CMD_CAST_SPELL,
1013 CMD_DISPLAY_SPELLS });
1014 _add_command(cols, 0, CMD_MEMORISE_SPELL, "Memorise spells and view spell\n"
1015 " library (get books to add to it)", 2);
1016
1017 // Second column.
1018 cols.add_formatted(
1019 1, "<h>Item types and inventory management\n",
1020 false);
1021
1022 _add_insert_commands(cols, 1,
1023 "<console><cyan>)</cyan> : </console>"
1024 "hand weapons (<w>%</w>ield)",
1025 { CMD_WIELD_WEAPON });
1026 _add_insert_commands(cols, 1,
1027 "<console><brown>(</brown> : </console>"
1028 "missiles (<w>%</w>uiver, <w>%</w>ire, <w>%</w>/<w>%</w> cycle)",
1029 { CMD_QUIVER_ITEM, CMD_FIRE, CMD_CYCLE_QUIVER_FORWARD,
1030 CMD_CYCLE_QUIVER_BACKWARD });
1031 _add_insert_commands(cols, 1,
1032 "<console><cyan>[</cyan> : </console>"
1033 "armour (<w>%</w>ear and <w>%</w>ake off)",
1034 { CMD_WEAR_ARMOUR, CMD_REMOVE_ARMOUR });
1035 _add_insert_commands(cols, 1,
1036 "<console><w>?</w> : </console>"
1037 "scrolls (<w>%</w>ead)",
1038 { CMD_READ });
1039 _add_insert_commands(cols, 1,
1040 "<console><magenta>!</magenta> : </console>"
1041 "potions (<w>%</w>uaff)",
1042 { CMD_QUAFF });
1043 _add_insert_commands(cols, 1,
1044 "<console><blue>=</blue> : </console>"
1045 "rings (<w>%</w>ut on and <w>%</w>emove)",
1046 { CMD_WEAR_JEWELLERY, CMD_REMOVE_JEWELLERY });
1047 _add_insert_commands(cols, 1,
1048 "<console><red>\"</red> : </console>"
1049 "amulets (<w>%</w>ut on and <w>%</w>emove)",
1050 { CMD_WEAR_JEWELLERY, CMD_REMOVE_JEWELLERY });
1051 _add_insert_commands(cols, 1,
1052 "<console><lightgrey>/</lightgrey> : </console>"
1053 "wands (e<w>%</w>oke)",
1054 { CMD_EVOKE });
1055
1056 string item_types =
1057 "<console><lightcyan>";
1058 item_types += stringize_glyph(get_item_symbol(SHOW_ITEM_BOOK));
1059 item_types +=
1060 "</lightcyan> : </console>"
1061 "books (<w>%</w>emorise, <w>%</w>ap, <w>%</w>ap,\n"
1062 " pick up to add to spell library)";
1063 _add_insert_commands(cols, 1, item_types,
1064 { CMD_MEMORISE_SPELL, CMD_CAST_SPELL,
1065 CMD_FORCE_CAST_SPELL });
1066
1067 item_types =
1068 "<console><brown>";
1069 item_types += stringize_glyph(get_item_symbol(SHOW_ITEM_STAFF));
1070 item_types +=
1071 "</brown> : </console>"
1072 "staves (<w>%</w>ield)";
1073 _add_insert_commands(cols, 1, item_types,
1074 { CMD_WIELD_WEAPON });
1075
1076 cols.add_formatted(1, " ", false);
1077 _add_command(cols, 1, CMD_DISPLAY_INVENTORY, "inventory (select item to view)", 2);
1078 _add_command(cols, 1, CMD_PICKUP, "pick up item from ground (also <w>g</w>)", 2);
1079 _add_command(cols, 1, CMD_DROP, "drop item", 2);
1080 _add_command(cols, 1, CMD_DROP_LAST, "drop the last item(s) you picked up", 2);
1081
1082 cols.add_formatted(
1083 1,
1084 "<h>Additional important commands\n");
1085
1086 _add_command(cols, 1, CMD_SAVE_GAME_NOW, "Save the game and exit", 2);
1087 _add_command(cols, 1, CMD_REPLAY_MESSAGES, "show previous messages", 2);
1088 _add_command(cols, 1, CMD_USE_ABILITY, "use an ability", 2);
1089 _add_command(cols, 1, CMD_RESISTS_SCREEN, "show character overview", 2);
1090 _add_command(cols, 1, CMD_DISPLAY_RELIGION, "show religion overview", 2);
1091 _add_command(cols, 1, CMD_DISPLAY_MAP, "show map of the whole level", 2);
1092 _add_command(cols, 1, CMD_DISPLAY_OVERMAP, "show dungeon overview", 2);
1093
1094 cols.add_formatted(
1095 1,
1096 "\n<h>Targeting\n"
1097 "<w>Enter</w> or <w>.</w> or <w>Del</w> : confirm target\n"
1098 "<w>+</w> and <w>-</w> : cycle between targets\n"
1099 "<w>f</w> or <w>p</w> : shoot at previous target\n"
1100 " if still alive and in sight\n",
1101 false);
1102 }
1103
_col_conv(void (* func)(column_composer &))1104 static formatted_string _col_conv(void (*func)(column_composer &))
1105 {
1106 column_composer cols(2, 42);
1107 func(cols);
1108 formatted_string contents;
1109 for (const auto& line : cols.formatted_lines())
1110 {
1111 contents += line;
1112 contents += "\n";
1113 }
1114 contents.ops.pop_back();
1115 return contents;
1116 }
1117
_get_help_section(int section,formatted_string & header_out,formatted_string & text_out,int & scroll_out)1118 static int _get_help_section(int section, formatted_string &header_out, formatted_string &text_out, int &scroll_out)
1119 {
1120 static map<int, int> hotkeys;
1121 static map<int, formatted_string> page_text;
1122 static map<int, string> headers = {
1123 {'*', "Manual"}, {'%', "Aptitudes"}, {'^', "Quickstart"},
1124 {'~', "Macros"}, {'&', "Options"}, {'t', "Tiles"},
1125 {'?', "Key help"}
1126 };
1127
1128 if (!page_text.size())
1129 {
1130 for (int i = 0; help_files[i].name != nullptr; ++i)
1131 {
1132 formatted_string text;
1133 bool next_is_hotkey = false;
1134 char buf[200];
1135 string fname = canonicalise_file_separator(help_files[i].name);
1136 FILE* fp = fopen_u(datafile_path(fname, false).c_str(), "r");
1137 ASSERTM(fp, "Failed to open '%s'!", fname.c_str());
1138 while (fgets(buf, sizeof buf, fp))
1139 {
1140 text += string(buf);
1141 if (next_is_hotkey && (isaupper(buf[0]) || isadigit(buf[0])))
1142 {
1143 int hotkey = tolower_safe(buf[0]);
1144 hotkeys[hotkey] = count_occurrences(text.tostring(), "\n");
1145 }
1146
1147 next_is_hotkey =
1148 strstr(buf, "------------------------------------------"
1149 "------------------------------") == buf;
1150 }
1151 trim_string_right(text.ops.back().text);
1152 page_text[help_files[i].hotkey] = text;
1153 }
1154 }
1155
1156 // All hotkeys are currently on *-page
1157 const int page = hotkeys.count(section) ? '*' : section;
1158
1159 string header = headers.count(page) ? ": "+headers[page] : "";
1160 header_out = formatted_string::parse_string(
1161 "<yellow>Dungeon Crawl Help"+header+"</yellow>");
1162 scroll_out = 0;
1163 switch (section)
1164 {
1165 case '?':
1166 if (crawl_state.game_is_hints_tutorial())
1167 text_out = _col_conv(_add_formatted_hints_help);
1168 else
1169 text_out = _col_conv(_add_formatted_keyhelp);
1170 return page;
1171 case CK_HOME:
1172 text_out = _col_conv(_add_formatted_help_menu);
1173 return page;
1174 default:
1175 if (hotkeys.count(section))
1176 scroll_out = hotkeys[section];
1177 if (page_text.count(page))
1178 {
1179 text_out = page_text[page];
1180 return page;
1181 }
1182 break;
1183 }
1184 return 0;
1185 }
1186
1187 class help_popup : public formatted_scroller
1188 {
1189 public:
help_popup(int key)1190 help_popup(int key) : formatted_scroller(FS_PREWRAPPED_TEXT) {
1191 set_tag("help");
1192 process_key(key);
1193 };
1194 private:
process_key(int ch)1195 bool process_key(int ch) override
1196 {
1197 int key = toalower(ch);
1198
1199 #ifdef USE_TILE_LOCAL
1200 const int line_height = tiles.get_crt_font()->char_height();
1201 #else
1202 const int line_height = 1;
1203 #endif
1204
1205 int scroll, page;
1206 formatted_string header_text, help_text;
1207 switch (key)
1208 {
1209 case CK_ESCAPE: case ':': case '#': case '/': case 'q': case 'v':
1210 return false;
1211 default:
1212 if (!(page = _get_help_section(key, header_text, help_text, scroll)))
1213 break;
1214 if (page != prev_page)
1215 {
1216 contents = help_text;
1217 m_contents_dirty = true;
1218 prev_page = page;
1219 }
1220 scroll = scroll ? (scroll-2)*line_height : 0;
1221 set_scroll(scroll);
1222 return true;
1223 }
1224
1225 return formatted_scroller::process_key(ch);
1226 };
1227 int prev_page{0};
1228 };
1229
_show_help_special(int key)1230 static bool _show_help_special(int key)
1231 {
1232 switch (key)
1233 {
1234 case ':':
1235 // If the game has begun, show notes.
1236 if (crawl_state.need_save)
1237 display_notes();
1238 return true;
1239 case '#':
1240 // If the game has begun, show dump.
1241 if (crawl_state.need_save)
1242 display_char_dump();
1243 return true;
1244 case '/':
1245 keyhelp_query_descriptions();
1246 return true;
1247 case 'q':
1248 _handle_FAQ();
1249 return true;
1250 case 'v':
1251 _print_version();
1252 return true;
1253 default:
1254 return false;
1255 }
1256 }
1257
show_help(int section,string highlight_string)1258 void show_help(int section, string highlight_string)
1259 {
1260 // if `section` is a special case, don't instantiate a help popup at all.
1261 if (_show_help_special(toalower(section)))
1262 return;
1263 help_popup help(section);
1264 help.highlight = highlight_string;
1265 int key = toalower(help.show());
1266 // handle the case where one of the special case help sections is triggered
1267 // from the help main menu.
1268 _show_help_special(key);
1269 }
1270