1 /**
2  * @file
3  * @brief A hints mode as an introduction on how to play Dungeon Crawl.
4 **/
5 
6 #include "AppHdr.h"
7 
8 #include "hints.h"
9 
10 #include <cstring>
11 #include <sstream>
12 
13 #include "ability.h"
14 #include "artefact.h"
15 #include "cloud.h"
16 #include "colour.h"
17 #include "database.h"
18 #include "describe.h"
19 #include "end.h"
20 #include "english.h"
21 #include "env.h"
22 #include "fprop.h"
23 #include "invent.h"
24 #include "item-prop.h"
25 #include "item-status-flag-type.h"
26 #include "items.h"
27 #include "jobs.h"
28 #include "libutil.h"
29 #include "macro.h"
30 #include "message.h"
31 #include "mutation.h"
32 #include "outer-menu.h"
33 #include "nearby-danger.h"
34 #include "options.h"
35 #include "output.h"
36 #include "religion.h"
37 #include "shopping.h"
38 #include "showsymb.h"
39 #include "skills.h"
40 #include "spl-book.h"
41 #include "state.h"
42 #include "stringutil.h"
43 #include "tag-version.h"
44 #include "terrain.h"
45 #include "tilepick-p.h"
46 #include "travel.h"
47 #include "viewchar.h"
48 #include "viewgeom.h"
49 #include "viewmap.h"
50 #include "ui.h"
51 
52 using namespace ui;
53 
54 static species_type _get_hints_species(unsigned int type);
55 static job_type     _get_hints_job(unsigned int type);
56 static bool         _hints_feat_interesting(dungeon_feature_type feat);
57 static void         _hints_describe_cloud(int x, int y, ostringstream& ostr);
58 static void         _hints_describe_feature(int x, int y, ostringstream& ostr);
59 static void         _hints_healing_reminder();
60 
_get_hints_cols()61 static int _get_hints_cols()
62 {
63 #ifdef USE_TILE_LOCAL
64     return crawl_view.msgsz.x;
65 #else
66     int ncols = get_number_of_cols();
67     return ncols > 80 ? 80 : ncols;
68 #endif
69 }
70 
71 hints_state Hints;
72 
save_hints(writer & outf)73 void save_hints(writer& outf)
74 {
75     marshallInt(outf, HINT_EVENTS_NUM);
76     marshallShort(outf, Hints.hints_type);
77     for (int i = 0; i < HINT_EVENTS_NUM; ++i)
78         marshallBoolean(outf, Hints.hints_events[i]);
79 }
80 
load_hints(reader & inf)81 void load_hints(reader& inf)
82 {
83     int version = unmarshallInt(inf);
84     // Discard everything if the number doesn't match.
85     if (version != HINT_EVENTS_NUM)
86         return;
87 
88     Hints.hints_type = unmarshallShort(inf);
89     for (int i = 0; i < HINT_EVENTS_NUM; ++i)
90         Hints.hints_events[i] = unmarshallBoolean(inf);
91 }
92 
93 // Override init file definition for some options.
init_hints_options()94 void init_hints_options()
95 {
96     if (!crawl_state.game_is_hints())
97         return;
98 
99     // Clear possible debug messages before messing
100     // with messaging options.
101     clear_messages(true);
102 //     Options.clear_messages = true;
103     Options.show_more  = true;
104     Options.small_more = false;
105 
106 #ifdef USE_TILE
107     Options.tile_tag_pref = TAGPREF_TUTORIAL;
108 #endif
109 }
110 
init_hints()111 void init_hints()
112 {
113     // Activate all triggers.
114     // This is rather backwards: If (true) an event still needs to be
115     // triggered, if (false) the relevant message was already printed.
116     Hints.hints_events.init(true);
117 
118     // Used to compare which fighting means was used most often.
119     // XXX: This gets reset with every save, which seems odd.
120     //      On the other hand, it's precisely between saves that
121     //      players are most likely to forget these.
122     Hints.hints_spell_counter   = 0;
123     Hints.hints_throw_counter   = 0;
124     Hints.hints_melee_counter   = 0;
125     Hints.hints_berserk_counter = 0;
126 
127     // Store whether explore, stash search or travelling was used.
128     // XXX: Also not stored across save games.
129     Hints.hints_explored = true;
130     Hints.hints_stashes  = true;
131     Hints.hints_travel   = true;
132 
133     // For occasional healing reminders.
134     Hints.hints_last_healed = 0;
135 
136     // Did the player recently see a monster turn invisible?
137     Hints.hints_seen_invisible = 0;
138 }
139 
_print_hints_menu(hints_types type)140 static string _print_hints_menu(hints_types type)
141 {
142     char letter = 'a' + type;
143     char desc[100];
144 
145     switch (type)
146     {
147     case HINT_BERSERK_CHAR:
148         strcpy(desc, "(Melee oriented character with divine support)");
149         break;
150     case HINT_MAGIC_CHAR:
151         strcpy(desc, "(Magic oriented character)");
152         break;
153     case HINT_RANGER_CHAR:
154         strcpy(desc, "(Ranged fighter)");
155         break;
156     default: // no further choices
157         strcpy(desc, "(erroneous character)");
158         break;
159     }
160 
161     return make_stringf("%c - %s %s %s",
162             letter, species::name(_get_hints_species(type)).c_str(),
163                     get_job_name(_get_hints_job(type)), desc);
164 }
165 
_fill_newgame_choice_for_hints(newgame_def & choice,hints_types type)166 static void _fill_newgame_choice_for_hints(newgame_def& choice, hints_types type)
167 {
168     choice.species  = _get_hints_species(type);
169     choice.job = _get_hints_job(type);
170     // easiest choice for fighters
171     choice.weapon = choice.job == JOB_HUNTER ? WPN_SHORTBOW
172                                                 : WPN_HAND_AXE;
173 }
174 
175 // Hints mode selection screen and choice.
pick_hints(newgame_def & choice)176 void pick_hints(newgame_def& choice)
177 {
178     string prompt = "<white>You must be new here indeed!</white>"
179         "\n\n"
180         "<cyan>You can be:</cyan>";
181     auto prompt_ui = make_shared<Text>(formatted_string::parse_string(prompt));
182 
183     auto vbox = make_shared<Box>(Box::VERT);
184     vbox->set_cross_alignment(Widget::Align::STRETCH);
185     vbox->add_child(prompt_ui);
186 
187     auto main_items = make_shared<OuterMenu>(true, 1, 3);
188     main_items->set_margin_for_sdl(15, 0);
189     main_items->set_margin_for_crt(1, 0);
190     vbox->add_child(main_items);
191 
192     for (int i = 0; i < 3; i++)
193     {
194         auto label = make_shared<Text>();
195         label->set_text(_print_hints_menu(static_cast<hints_types>(i)));
196 
197 #ifdef USE_TILE_LOCAL
198         auto hbox = make_shared<Box>(Box::HORZ);
199         hbox->set_cross_alignment(Widget::Align::CENTER);
200         dolls_data doll;
201         newgame_def tng = choice;
202         _fill_newgame_choice_for_hints(tng, static_cast<hints_types>(i));
203         fill_doll_for_newgame(doll, tng);
204         auto tile = make_shared<ui::PlayerDoll>(doll);
205         tile->set_margin_for_sdl(0, 6, 0, 0);
206         hbox->add_child(move(tile));
207         hbox->add_child(label);
208 #endif
209 
210         auto btn = make_shared<MenuButton>();
211 #ifdef USE_TILE_LOCAL
212         hbox->set_margin_for_sdl(4,8);
213         btn->set_child(move(hbox));
214 #else
215         btn->set_child(move(label));
216 #endif
217         btn->id = i;
218         btn->hotkey = 'a' + i;
219 
220         if (i == 0)
221             main_items->set_initial_focus(btn.get());
222 
223         main_items->add_button(btn, 0, i);
224     }
225 
226     auto sub_items = make_shared<OuterMenu>(false, 1, 2);
227     vbox->add_child(sub_items);
228 
229     bool cancelled = false;
230     bool done = false;
231     vbox->on_activate_event([&](const ActivateEvent& event) {
232         const auto button = static_pointer_cast<MenuButton>(event.target());
233         int id = button->id;
234         if (id == CK_ESCAPE)
235             return done = cancelled = true;
236         else if (id == '*')
237             id = random2(HINT_TYPES_NUM);
238         Hints.hints_type = id;
239         _fill_newgame_choice_for_hints(choice, static_cast<hints_types>(id));
240         return done = true;
241     });
242 
243     main_items->linked_menus[2] = sub_items;
244     sub_items->linked_menus[0] = main_items;
245 
246     {
247         auto label = make_shared<Text>(formatted_string("Esc - Quit", BROWN));
248         auto btn = make_shared<MenuButton>();
249         btn->set_child(move(label));
250         btn->hotkey = CK_ESCAPE;
251         btn->id = CK_ESCAPE;
252         sub_items->add_button(btn, 0, 0);
253     }
254     {
255         auto label = make_shared<Text>(formatted_string("  * - Random hints mode character", BROWN));
256         auto btn = make_shared<MenuButton>();
257         btn->set_child(move(label));
258         btn->hotkey = '*';
259         btn->id = '*';
260         sub_items->add_button(btn, 0, 1);
261     }
262 
263     auto popup = make_shared<ui::Popup>(vbox);
264 
265     ui::run_layout(move(popup), done);
266 
267     if (cancelled)
268     {
269 #ifdef USE_TILE_WEB
270         tiles.send_exit_reason("cancel");
271 #endif
272         game_ended(game_exit::abort);
273     }
274 }
275 
hints_load_game()276 void hints_load_game()
277 {
278     if (!crawl_state.game_is_hints())
279         return;
280 
281     learned_something_new(HINT_LOAD_SAVED_GAME);
282 
283     // Reinitialise counters for explore, stash search and travelling.
284     Hints.hints_explored = Hints.hints_events[HINT_AUTO_EXPLORE];
285     Hints.hints_stashes  = true;
286     Hints.hints_travel   = true;
287 }
288 
_get_hints_species(unsigned int type)289 static species_type _get_hints_species(unsigned int type)
290 {
291     switch (type)
292     {
293     case HINT_BERSERK_CHAR:
294         return SP_HILL_ORC;
295     case HINT_MAGIC_CHAR:
296         return SP_DEEP_ELF;
297     case HINT_RANGER_CHAR:
298         return SP_MINOTAUR;
299     default:
300         // Use something fancy for debugging.
301         return SP_TENGU;
302     }
303 }
304 
_get_hints_job(unsigned int type)305 static job_type _get_hints_job(unsigned int type)
306 {
307     switch (type)
308     {
309     case HINT_BERSERK_CHAR:
310         return JOB_BERSERKER;
311     case HINT_MAGIC_CHAR:
312         return JOB_CONJURER;
313     case HINT_RANGER_CHAR:
314         return JOB_HUNTER;
315     default:
316         // Use something fancy for debugging.
317         return JOB_NECROMANCER;
318     }
319 }
320 
_replace_static_tags(string & text)321 static void _replace_static_tags(string &text)
322 {
323     size_t p;
324     while ((p = text.find("$cmd[")) != string::npos)
325     {
326         size_t q = text.find("]", p + 5);
327         if (q == string::npos)
328         {
329             text += "<lightred>ERROR: unterminated $cmd</lightred>";
330             break;
331         }
332 
333         string command = text.substr(p + 5, q - p - 5);
334         command_type cmd = name_to_command(command);
335 
336         command = command_to_string(cmd);
337         if (command == "<")
338             command += "<";
339 
340         text.replace(p, q - p + 1, command);
341     }
342 
343     while ((p = text.find("$item[")) != string::npos)
344     {
345         size_t q = text.find("]", p + 6);
346         if (q == string::npos)
347         {
348             text += "<lightred>ERROR: unterminated $item</lightred>";
349             break;
350         }
351 
352         string item = text.substr(p + 6, q - p - 6);
353         int type;
354         for (type = OBJ_WEAPONS; type < NUM_OBJECT_CLASSES; ++type)
355             if (item == item_class_name(type, true))
356                 break;
357 
358         item_def dummy;
359         dummy.base_type = static_cast<object_class_type>(type);
360         dummy.sub_type = 0;
361         if (item == "amulet") // yay shared item classes
362             dummy.base_type = OBJ_JEWELLERY, dummy.sub_type = AMU_FAITH;
363         item = stringize_glyph(get_item_symbol(show_type(dummy).item));
364 
365         if (item == "<")
366             item += "<";
367 
368         text.replace(p, q - p + 1, item);
369     }
370 
371     // Brand user-input -related (tutorial) items with <w>[(text here)]</w>.
372     while ((p = text.find("<input>")) != string::npos)
373     {
374         size_t q = text.find("</input>", p + 7);
375         if (q == string::npos)
376         {
377             text += "<lightred>ERROR: unterminated <input></lightred>";
378             break;
379         }
380 
381         string input = text.substr(p + 7, q - p - 7);
382         input = "<w>[" + input;
383         input += "]</w>";
384         text.replace(p, q - p + 8, input);
385     }
386 }
387 
388 // Prints the hints mode welcome screen.
hints_starting_screen()389 void hints_starting_screen()
390 {
391     string text = getHintString("welcome");
392     _replace_static_tags(text);
393     trim_string(text);
394 
395     auto prompt_ui = make_shared<Text>(formatted_string::parse_string(text));
396     prompt_ui->set_wrap_text(true);
397 #ifdef USE_TILE_LOCAL
398     prompt_ui->max_size().width = 800;
399 #else
400     prompt_ui->max_size().width = 80;
401 #endif
402 
403     bool done = false;
404     auto popup = make_shared<ui::Popup>(prompt_ui);
405     popup->on_keydown_event([&](const KeyEvent&) { return done = true; });
406 
407     mouse_control mc(MOUSE_MODE_MORE);
408     ui::run_layout(move(popup), done);
409 }
410 
411 // Called each turn from _input. Better name welcome.
hints_new_turn()412 void hints_new_turn()
413 {
414     if (!crawl_state.game_is_hints())
415         return;
416 
417     Hints.hints_just_triggered = false;
418 
419     if (you.attribute[ATTR_HELD])
420     {
421         learned_something_new(HINT_CAUGHT_IN_NET);
422         return;
423     }
424 
425     if (i_feel_safe() && !player_in_branch(BRANCH_ABYSS))
426     {
427         // We don't want those "Whew, it's safe to rest now" messages
428         // if you were just cast into the Abyss. Right?
429 
430         if (2 * you.hp < you.hp_max
431             || 2 * you.magic_points < you.max_magic_points)
432         {
433             _hints_healing_reminder();
434             return;
435         }
436 
437         if (you.running
438                  || you.hp != you.hp_max
439                  || you.magic_points != you.max_magic_points)
440         {
441             return;
442         }
443 
444         if (Hints.hints_events[HINT_SHIFT_RUN] && you.num_turns >= 200)
445             learned_something_new(HINT_SHIFT_RUN);
446         else if (Hints.hints_events[HINT_MAP_VIEW] && you.num_turns >= 500)
447             learned_something_new(HINT_MAP_VIEW);
448         else if (Hints.hints_events[HINT_AUTO_EXPLORE] && you.num_turns >= 700)
449             learned_something_new(HINT_AUTO_EXPLORE);
450 
451         return;
452     }
453 
454     if (poison_is_lethal())
455     {
456         if (Hints.hints_events[HINT_NEED_POISON_HEALING])
457             learned_something_new(HINT_NEED_POISON_HEALING);
458     }
459     else if (2*you.hp < you.hp_max)
460         learned_something_new(HINT_RUN_AWAY);
461 
462     if (Hints.hints_type == HINT_MAGIC_CHAR && you.magic_points < 1)
463         learned_something_new(HINT_RETREAT_CASTER);
464 }
465 
466 /**
467  * Look up and display a hint message from the database. Is usable from dlua,
468  * so wizard mode in-game Lua interpreter can be used to test the messages.
469  * @param arg1 A string that can be inserted into the hint message.
470  * @param arg2 Another string that can be inserted into the hint message.
471  */
print_hint(string key,const string & arg1,const string & arg2)472 void print_hint(string key, const string& arg1, const string& arg2)
473 {
474     string text = getHintString(key);
475     if (text.empty())
476         return mprf(MSGCH_ERROR, "Error, no hint for '%s'.", key.c_str());
477 
478     _replace_static_tags(text);
479     text = untag_tiles_console(text);
480     text = replace_all(text, "$1", arg1);
481     text = replace_all(text, "$2", arg2);
482 
483     // "\n" to preserve indented parts, the rest is unwrapped, or split into
484     // paragraphs by "\n\n", split_string() will ignore the empty line.
485     for (const string &chunk : split_string("\n", text))
486         mprf(MSGCH_TUTORIAL, "%s", chunk.c_str());
487 
488     stop_running();
489 }
490 
491 // Once a hints mode character dies, offer some last playing hints.
hints_death_screen()492 void hints_death_screen()
493 {
494     string text;
495 
496     print_hint("death");
497     more();
498 
499     if (Hints.hints_type == HINT_MAGIC_CHAR
500         && Hints.hints_spell_counter < Hints.hints_melee_counter)
501     {
502         print_hint("death conjurer melee");
503     }
504     else if (you_worship(GOD_TROG) && Hints.hints_berserk_counter <= 3
505              && !you.berserk() && !you.duration[DUR_BERSERK_COOLDOWN])
506     {
507         print_hint("death berserker unberserked");
508     }
509     else if (Hints.hints_type == HINT_RANGER_CHAR
510              && 2*Hints.hints_throw_counter < Hints.hints_melee_counter)
511     {
512         print_hint("death ranger melee");
513     }
514     else
515     {
516         int hint = random2(6);
517 
518         bool skip_first_hint = false;
519         // If a character has been unusually busy with projectiles and spells
520         // give some other hint rather than the first one.
521         if (hint == 0 && Hints.hints_throw_counter + Hints.hints_spell_counter
522                           >= Hints.hints_melee_counter)
523         {
524             hint = random2(5) + 1;
525             skip_first_hint = true;
526         }
527         // FIXME: The hints below could be somewhat less random, so that e.g.
528         // the message for fighting several monsters in a corridor only happens
529         // if there's more than one monster around and you're not in a corridor,
530         // or the one about using consumable objects only if you actually have
531         // any (useful or unidentified) scrolls/wands/potions.
532 
533         if (hint == 5)
534         {
535             vector<monster* > visible =
536                 get_nearby_monsters(false, true, true, false);
537 
538             if (visible.size() < 2)
539             {
540                 if (skip_first_hint)
541                     hint = random2(4) + 1;
542                 else
543                     hint = random2(5);
544             }
545         }
546 
547         print_hint(make_stringf("death random %d", hint));
548     }
549     mprf(MSGCH_TUTORIAL, "%s", untag_tiles_console(text).c_str());
550     more();
551 
552     mprf(MSGCH_TUTORIAL, "See you next game!");
553 
554     Hints.hints_events.init(false);
555 }
556 
557 // If a character survives until XL 7, the hints mode is declared finished
558 // and they get a more advanced playing hint, depending on what they might
559 // know by now.
hints_finished()560 void hints_finished()
561 {
562     crawl_state.type = GAME_TYPE_NORMAL;
563 
564     print_hint("finished");
565     more();
566 
567     if (Hints.hints_explored)
568         print_hint("finished explored");
569     else if (Hints.hints_travel)
570         print_hint("finished travel");
571     else if (Hints.hints_stashes)
572         print_hint("finished stashes");
573     else
574         print_hint(make_stringf("finished random %d", random2(4)));
575     more();
576 
577     Hints.hints_events.init(false);
578 
579     // Remove the hints mode file.
580     you.save->delete_chunk("tut");
581 }
582 
_advise_use_healing_potion()583 static bool _advise_use_healing_potion()
584 {
585     for (auto &obj : you.inv)
586     {
587         if (!obj.defined())
588             continue;
589 
590         if (obj.base_type != OBJ_POTIONS)
591             continue;
592 
593         if (!item_type_known(obj))
594             continue;
595 
596         if (obj.sub_type == POT_CURING
597             || obj.sub_type == POT_HEAL_WOUNDS)
598         {
599             return true;
600         }
601     }
602 
603     return false;
604 }
605 
hints_healing_check()606 void hints_healing_check()
607 {
608     if (2*you.hp <= you.hp_max
609         && _advise_use_healing_potion())
610     {
611         learned_something_new(HINT_HEALING_POTIONS);
612     }
613 }
614 
615 // Occasionally remind injured characters of resting.
_hints_healing_reminder()616 static void _hints_healing_reminder()
617 {
618     if (!crawl_state.game_is_hints())
619         return;
620 
621     if (Hints.hints_seen_invisible > 0
622         && you.num_turns - Hints.hints_seen_invisible <= 20)
623     {
624         // If we recently encountered an invisible monster, we need a
625         // special message.
626         learned_something_new(HINT_NEED_HEALING_INVIS);
627         // If that one was already displayed, don't print a reminder.
628     }
629     else
630     {
631         if (Hints.hints_events[HINT_NEED_HEALING])
632             learned_something_new(HINT_NEED_HEALING);
633         else if (you.num_turns - Hints.hints_last_healed >= 50
634                  && !you.duration[DUR_POISONING])
635         {
636             if (Hints.hints_just_triggered)
637                 return;
638 
639             Hints.hints_just_triggered = true;
640 
641             string text;
642             text =  "Remember to rest between fights and to enter unexplored "
643                     "terrain with full health and magic. Ideally you "
644                     "should retreat into areas you've already explored and "
645                     "cleared of monsters; resting on the edge of the explored "
646                     "terrain increases the chances of your rest being "
647                     "interrupted by wandering monsters. To rest, press "
648                     "<w>5</w> or <w>Shift-numpad 5</w>"
649                     "<tiles>, or <w>click the rest button</w></tiles>"
650                     ".";
651 
652             if (you.hp < you.hp_max && you_worship(GOD_TROG)
653                 && you.can_go_berserk())
654             {
655                 text += "\nAlso, berserking might help you not to lose so much "
656                         "health in the first place. To use your abilities "
657                         "press <w>a</w>.";
658             }
659             mprf(MSGCH_TUTORIAL, "%s", untag_tiles_console(text).c_str());
660 
661             if (is_resting())
662                 stop_running();
663         }
664         Hints.hints_last_healed = you.num_turns;
665     }
666 }
667 
668 // Give a message if you see, pick up or inspect an item type for the
669 // first time.
taken_new_item(object_class_type item_type)670 void taken_new_item(object_class_type item_type)
671 {
672     switch (item_type)
673     {
674     case OBJ_WANDS:
675         learned_something_new(HINT_SEEN_WAND);
676         break;
677     case OBJ_SCROLLS:
678         learned_something_new(HINT_SEEN_SCROLL);
679         break;
680     case OBJ_JEWELLERY:
681         learned_something_new(HINT_SEEN_JEWELLERY);
682         break;
683     case OBJ_POTIONS:
684         learned_something_new(HINT_SEEN_POTION);
685         break;
686     case OBJ_BOOKS:
687         learned_something_new(HINT_SEEN_SPBOOK);
688         break;
689     case OBJ_CORPSES:
690         learned_something_new(HINT_SEEN_CARRION);
691         break;
692     case OBJ_WEAPONS:
693         learned_something_new(HINT_SEEN_WEAPON);
694         break;
695     case OBJ_ARMOUR:
696         learned_something_new(HINT_SEEN_ARMOUR);
697         break;
698     case OBJ_MISSILES:
699         learned_something_new(HINT_SEEN_MISSILES);
700         break;
701     case OBJ_MISCELLANY:
702         learned_something_new(HINT_SEEN_MISC);
703         break;
704     case OBJ_STAVES:
705         learned_something_new(HINT_SEEN_STAFF);
706         break;
707     case OBJ_GOLD:
708         learned_something_new(HINT_SEEN_GOLD);
709         break;
710     default: // nothing to be done
711         return;
712     }
713 }
714 
715 // Give a special message if you gain a skill you didn't have before.
hints_gained_new_skill(skill_type skill)716 void hints_gained_new_skill(skill_type skill)
717 {
718     if (!crawl_state.game_is_hints())
719         return;
720 
721     learned_something_new(HINT_SKILL_RAISE);
722 
723     switch (skill)
724     {
725     // Special cases first.
726     case SK_FIGHTING:
727     case SK_ARMOUR:
728     case SK_STEALTH:
729     case SK_UNARMED_COMBAT:
730     case SK_INVOCATIONS:
731     case SK_EVOCATIONS:
732     case SK_DODGING:
733     case SK_SHIELDS:
734     case SK_THROWING:
735     case SK_SPELLCASTING:
736     {
737         mprf(MSGCH_TUTORIAL, "%s", get_skill_description(skill).c_str());
738         stop_running();
739         break;
740     }
741     // Only one message for all magic skills (except Spellcasting).
742     case SK_CONJURATIONS:
743     case SK_HEXES:
744     case SK_SUMMONINGS:
745     case SK_NECROMANCY:
746     case SK_TRANSLOCATIONS:
747     case SK_TRANSMUTATIONS:
748     case SK_FIRE_MAGIC:
749     case SK_ICE_MAGIC:
750     case SK_AIR_MAGIC:
751     case SK_EARTH_MAGIC:
752     case SK_POISON_MAGIC:
753         learned_something_new(HINT_GAINED_MAGICAL_SKILL);
754         break;
755 
756     // Melee skills.
757     case SK_SHORT_BLADES:
758     case SK_LONG_BLADES:
759     case SK_AXES:
760     case SK_MACES_FLAILS:
761     case SK_POLEARMS:
762     case SK_STAVES:
763         learned_something_new(HINT_GAINED_MELEE_SKILL);
764         break;
765 
766     // Ranged skills.
767     case SK_SLINGS:
768     case SK_BOWS:
769     case SK_CROSSBOWS:
770         learned_something_new(HINT_GAINED_RANGED_SKILL);
771         break;
772 
773     default:
774         break;
775     }
776 }
777 
778 #ifndef USE_TILE
779 // As safely as possible, colourize the passed glyph.
780 // Stringizes it and handles quoting "<".
_colourize_glyph(int col,unsigned ch)781 static string _colourize_glyph(int col, unsigned ch)
782 {
783     cglyph_t g;
784     g.col = col;
785     g.ch = ch;
786     return glyph_to_tagstr(g);
787 }
788 #endif
789 
_mons_is_highlighted(const monster * mons)790 static bool _mons_is_highlighted(const monster* mons)
791 {
792     return mons->friendly()
793                && Options.friend_brand != CHATTR_NORMAL
794            || mons_looks_stabbable(*mons)
795                && Options.stab_brand != CHATTR_NORMAL
796            || mons_looks_distracted(*mons)
797                && Options.may_stab_brand != CHATTR_NORMAL;
798 }
799 
_advise_use_wand()800 static bool _advise_use_wand()
801 {
802     for (auto &obj : you.inv)
803     {
804         if (!obj.defined())
805             continue;
806 
807         if (obj.base_type != OBJ_WANDS)
808             continue;
809 
810         // Wand type unknown, might be useful.
811         if (!item_type_known(obj))
812             return true;
813 
814         // Can it be used to fight?
815         switch (obj.sub_type)
816         {
817         case WAND_FLAME:
818         case WAND_PARALYSIS:
819         case WAND_ICEBLAST:
820         case WAND_CHARMING:
821         case WAND_ACID:
822         case WAND_MINDBURST:
823             return true;
824         }
825     }
826 
827     return false;
828 }
829 
hints_monster_seen(const monster & mon)830 void hints_monster_seen(const monster& mon)
831 {
832     if (mons_is_firewood(mon))
833     {
834         if (Hints.hints_events[HINT_SEEN_ZERO_EXP_MON])
835         {
836             if (Hints.hints_just_triggered)
837                 return;
838 
839             learned_something_new(HINT_SEEN_ZERO_EXP_MON, mon.pos());
840             return;
841         }
842 
843         // Don't do HINT_SEEN_MONSTER for zero exp monsters.
844         if (Hints.hints_events[HINT_SEEN_MONSTER])
845             return;
846     }
847 
848     if (!Hints.hints_events[HINT_SEEN_MONSTER])
849     {
850         if (Hints.hints_just_triggered)
851             return;
852 
853         if (_mons_is_highlighted(&mon))
854             learned_something_new(HINT_MONSTER_BRAND, mon.pos());
855         if (mon.friendly())
856             learned_something_new(HINT_MONSTER_FRIENDLY, mon.pos());
857 
858         if (you_worship(GOD_TROG) && you.can_go_berserk()
859             && one_chance_in(4))
860         {
861             learned_something_new(HINT_CAN_BERSERK);
862         }
863         return;
864     }
865 
866     stop_running();
867 
868     Hints.hints_events[HINT_SEEN_MONSTER] = false;
869     Hints.hints_just_triggered = true;
870 
871     monster_info mi(&mon);
872 #ifdef USE_TILE
873     // need to highlight monster
874     const coord_def gc = mon.pos();
875     tiles.place_cursor(CURSOR_TUTORIAL, gc);
876     tiles.add_text_tag(TAG_TUTORIAL, mi);
877 #endif
878 
879     string text = "That ";
880 
881     if (is_tiles())
882     {
883         text +=
884             string("monster is a ") +
885             mon.name(DESC_PLAIN).c_str() +
886             ". You can learn about any monster by hovering your mouse over it,"
887             " and read its description by <w>right-clicking</w> on it.";
888     }
889     else
890     {
891         text +=
892             glyph_to_tagstr(get_mons_glyph(mi)) +
893             " is a monster, usually depicted by a letter. Some typical "
894             "early monsters look like <brown>r</brown>, <green>l</green>, "
895             "<brown>K</brown> or <lightgrey>g</lightgrey>. ";
896         if (crawl_view.mlistsz.y > 0)
897         {
898             text += "Your console settings allowing, you'll always see a "
899                     "list of monsters somewhere on the screen.\n";
900         }
901         text += "You can gain information about it by pressing <w>x</w> and "
902                 "moving the cursor over the monster, and read the monster "
903                 "description by then pressing <w>v</w>. ";
904     }
905 
906     text += "\nTo attack this monster with your wielded weapon, just move "
907             "into it. ";
908     if (is_tiles())
909     {
910         text +=
911             "Note that as long as there's a non-friendly monster in view you "
912             "won't be able to automatically move to distant squares, to avoid "
913             "death by misclicking.";
914     }
915 
916     mprf(MSGCH_TUTORIAL, "%s", text.c_str());
917 
918     if (Hints.hints_type == HINT_RANGER_CHAR)
919     {
920         text =  "However, as a hunter you will want to deal with it using your "
921                 "bow. If you have a look at your shortbow from your "
922                 "<w>i</w>nventory, you'll find an explanation of how to do "
923                 "this. ";
924 
925         if (!you.weapon()
926             || you.weapon()->base_type != OBJ_WEAPONS
927             || you.weapon()->sub_type != WPN_SHORTBOW)
928         {
929             text += "First <w>w</w>ield it, then follow the instructions."
930                 "<tiles>\nAs a short-cut you can also <w>right-click</w> on your "
931                 "shortbow to read its description, and <w>left-click</w> to wield "
932                 "it.</tiles>";
933         }
934         else
935         {
936             text += "<tiles>Clicking with your <w>right mouse button</w> on your "
937                     "shortbow will also let you read its description.</tiles>";
938         }
939 
940         mprf(MSGCH_TUTORIAL, "%s", untag_tiles_console(text).c_str());
941 
942     }
943     else if (Hints.hints_type == HINT_MAGIC_CHAR)
944     {
945         text =  "However, as a conjurer you will want to deal with it using "
946                 "magic. If you look at the help entry for the "
947                 "<w>M</w>emorisation screen you'll find an explanation of how "
948                 "to do this.";
949         mprf(MSGCH_TUTORIAL, "%s", untag_tiles_console(text).c_str());
950 
951     }
952 }
953 
hints_first_item(const item_def & item)954 void hints_first_item(const item_def &item)
955 {
956     // Happens if monster is standing on dropped corpse or item.
957     if (monster_at(item.pos))
958         return;
959 
960     if (!Hints.hints_events[HINT_SEEN_FIRST_OBJECT]
961         || Hints.hints_just_triggered)
962     {
963         // NOTE: Since a new player might not think to pick up a
964         // corpse (and why should they?), HINT_SEEN_CARRION is done when a
965         // corpse is first seen.
966         if (!Hints.hints_just_triggered
967             && item.base_type == OBJ_CORPSES
968             && !monster_at(item.pos))
969         {
970             learned_something_new(HINT_SEEN_CARRION, item.pos);
971         }
972         return;
973     }
974 
975     stop_running();
976 
977     Hints.hints_events[HINT_SEEN_FIRST_OBJECT] = false;
978     Hints.hints_just_triggered = true;
979 
980 #ifdef USE_TILE
981     const coord_def gc = item.pos;
982     tiles.place_cursor(CURSOR_TUTORIAL, gc);
983     tiles.add_text_tag(TAG_TUTORIAL, item.name(DESC_A), gc);
984 #endif
985 
986     print_hint("HINT_SEEN_FIRST_OBJECT",
987                glyph_to_tagstr(get_item_glyph(item)));
988 }
989 
_describe_portal(const coord_def & gc)990 static string _describe_portal(const coord_def &gc)
991 {
992     const dungeon_feature_type feat = env.grid(gc);
993     string text;
994 
995     // For the sake of completeness, though it's very unlikely that a
996     // player will find a bazaar entrance before reaching XL 7.
997     if (feat == DNGN_ENTER_BAZAAR)
998     {
999         text =  "is a portal to an inter-dimensional bazaar filled with "
1000                 "shops. It will disappear if you don't enter it soon, "
1001                 "so hurry. ";
1002     }
1003     // Sewers can appear on D:3-6, ossuaries D:4-8.
1004     else
1005     {
1006         text =  "is a portal to an optional level, offering extra loot in "
1007                 "exchange for extra danger. There's no penalty for skipping "
1008                 "it, but the portal will disappear if you wait, so you have "
1009                 "to decide now if you want to risk it. ";
1010     }
1011 
1012     text += "You can enter a portal just like a set of stairs, by standing on "
1013             "it and <tiles><w>clicking</w></tiles>"
1014             "<console>pressing <w>></w></console>. "
1015             "To return, find "
1016             "<tiles>a similar looking portal.</tiles>"
1017             "<console>another <w>"
1018           + stringize_glyph(get_feat_symbol(DNGN_EXIT_SEWER))
1019           + "</w> - but NOT the ancient stone arch you'll start "
1020             "out on!</console>";
1021 
1022     return text;
1023 }
1024 
1025 #define DELAY_EVENT \
1026 { \
1027     Hints.hints_events[seen_what] = true; \
1028     return; \
1029 }
1030 
1031 // Really rare or important events should get a comment even if
1032 // learned_something_new() was already triggered this turn.
1033 // NOTE: If put off, the SEEN_<feature> variant will be triggered the
1034 //       next turn, so they may be rare but aren't urgent.
_rare_hints_event(hints_event_type event)1035 static bool _rare_hints_event(hints_event_type event)
1036 {
1037     switch (event)
1038     {
1039     case HINT_SEEN_RUNED_DOOR: // The runed door could be opened in one turn.
1040     case HINT_KILLED_MONSTER:
1041     case HINT_NEW_LEVEL:
1042     case HINT_YOU_ENCHANTED:
1043     case HINT_YOU_POISON:
1044     case HINT_GLOWING:
1045     case HINT_CAUGHT_IN_NET:
1046     case HINT_YOU_SILENCE:
1047     case HINT_NEED_POISON_HEALING:
1048     case HINT_INVISIBLE_DANGER:
1049     case HINT_NEED_HEALING_INVIS:
1050     case HINT_ABYSS:
1051     case HINT_RUN_AWAY:
1052     case HINT_RETREAT_CASTER:
1053     case HINT_YOU_MUTATED:
1054     case HINT_NEW_ABILITY_GOD:
1055     case HINT_NEW_ABILITY_ITEM:
1056     case HINT_CONVERT:
1057     case HINT_GOD_DISPLEASED:
1058     case HINT_EXCOMMUNICATE:
1059     case HINT_GAINED_MAGICAL_SKILL:
1060     case HINT_GAINED_MELEE_SKILL:
1061     case HINT_GAINED_RANGED_SKILL:
1062     case HINT_CHOOSE_STAT:
1063     case HINT_AUTO_EXCLUSION:
1064         return true;
1065     default:
1066         return false;
1067     }
1068 }
1069 
1070 // Allow for a few specific hint mode messages.
_tutorial_interesting(hints_event_type event)1071 static bool _tutorial_interesting(hints_event_type event)
1072 {
1073     switch (event)
1074     {
1075     case HINT_AUTOPICKUP_THROWN:
1076     case HINT_TARGET_NO_FOE:
1077     case HINT_YOU_POISON:
1078     case HINT_NEW_ABILITY_ITEM:
1079     case HINT_ITEM_RESISTANCES:
1080     case HINT_HEALING_POTIONS:
1081     case HINT_GAINED_SPELLCASTING:
1082     case HINT_FUMBLING_SHALLOW_WATER:
1083     case HINT_SPELL_MISCAST:
1084     case HINT_CLOUD_WARNING:
1085     case HINT_ANIMATE_CORPSE_SKELETON:
1086     case HINT_SKILL_RAISE:
1087         return true;
1088     default:
1089         return false;
1090     }
1091 }
1092 
1093 // A few special tutorial explanations require triggers.
1094 // Initialize the corresponding events, so they can get displayed.
tutorial_init_hints()1095 void tutorial_init_hints()
1096 {
1097     Hints.hints_events.init(false);
1098     for (int i = 0; i < HINT_EVENTS_NUM; ++i)
1099         if (_tutorial_interesting((hints_event_type) i))
1100             Hints.hints_events[i] = true;
1101 }
1102 
1103 // Here most of the hints mode messages for various triggers are handled.
learned_something_new(hints_event_type seen_what,coord_def gc)1104 void learned_something_new(hints_event_type seen_what, coord_def gc)
1105 {
1106     if (!crawl_state.game_is_hints_tutorial())
1107         return;
1108 
1109     // Already learned about that.
1110     if (!Hints.hints_events[seen_what])
1111         return;
1112 
1113     // Don't trigger twice in the same turn.
1114     // Not required in the tutorial.
1115     if (crawl_state.game_is_hints() && Hints.hints_just_triggered
1116         && !_rare_hints_event(seen_what))
1117     {
1118         return;
1119     }
1120 
1121     ostringstream text;
1122     vector<command_type> cmd;
1123 
1124     Hints.hints_just_triggered    = true;
1125     Hints.hints_events[seen_what] = false;
1126 
1127     switch (seen_what)
1128     {
1129     case HINT_SEEN_POTION:
1130         text << "You have picked up your first potion"
1131                 "<console> ('<w>"
1132              << stringize_glyph(get_item_symbol(SHOW_ITEM_POTION))
1133              << "</w>'). Use </console>"
1134                 "<tiles>. Simply <w>left-click</w> on it, or press </tiles>"
1135                 "<w>%</w> to quaff it.\n"
1136                 "Once you've identified a potion, either with a scroll of "
1137                 "identification or by drinking it, you'll automatically "
1138                 "recognize all other potions of the same type; this means "
1139                 "it's sometimes useful to drink potions just to identify them.";
1140         cmd.push_back(CMD_QUAFF);
1141         break;
1142 
1143     case HINT_SEEN_SCROLL:
1144         text << "You have picked up your first scroll"
1145                 "<console> ('<w>"
1146              << stringize_glyph(get_item_symbol(SHOW_ITEM_SCROLL))
1147              << "</w>'). Press </console>"
1148                 "<tiles>. Simply <w>left-click</w> on it, or press </tiles>"
1149                 "<w>%</w> to read it.\n"
1150                 "Once you've identified a scroll, either with a scroll of "
1151                 "identification or by reading it, you'll automatically "
1152                 "recognize all other scrolls of the same type; this means "
1153                 "it's sometimes useful to read scrolls just to identify them.";
1154         cmd.push_back(CMD_READ);
1155         break;
1156 
1157     case HINT_SEEN_WAND:
1158         text << "You have picked up your first wand"
1159                 "<console> ('<w>"
1160              << stringize_glyph(get_item_symbol(SHOW_ITEM_WAND))
1161              << "</w>'). Press </console>"
1162                 "<tiles>. Simply <w>left-click</w> on it, or press </tiles>"
1163                 "<w>%</w> to evoke it.\n"
1164                 "If you find more wands of the same type, they'll merge "
1165                 "into this wand and add charges to it.";
1166         cmd.push_back(CMD_EVOKE);
1167         break;
1168 
1169     case HINT_SEEN_SPBOOK:
1170         text << "You have picked up a book"
1171                 "<console> ('<w>"
1172              << stringize_glyph(get_item_symbol(SHOW_ITEM_BOOK))
1173              << "</w>')</console>"
1174                 ". On pickup, its spells are immediately added to your "
1175                 "library. You'll be able to memorise spells from it via "
1176                 "<w>%</w>, and cast them with <w>%</w>.";
1177         cmd.push_back(CMD_MEMORISE_SPELL);
1178         cmd.push_back(CMD_CAST_SPELL);
1179 
1180         if (you_worship(GOD_TROG))
1181         {
1182             text << " "
1183                  << god_name(GOD_TROG)
1184                  << " hates it when you study or use magic, though!";
1185             cmd.push_back(CMD_USE_ABILITY);
1186         }
1187         break;
1188 
1189     case HINT_SEEN_WEAPON:
1190         text << "This is the first weapon "
1191                 "<console>('<w>"
1192              << stringize_glyph(get_item_symbol(SHOW_ITEM_WEAPON))
1193              << "</w>') </console>"
1194                 "you've picked up. Use <w>%</w> "
1195                 "<tiles>or <w>left-click</w> on it </tiles>"
1196                 "to wield it, but be aware that this weapon "
1197                 "might train a different skill from your current one. You can "
1198                 "view the weapon's properties from your <w>%</w>nventory"
1199                 "<tiles> or by <w>right-clicking</w> on it</tiles>"
1200                 ".";
1201 
1202         cmd.push_back(CMD_WIELD_WEAPON);
1203         cmd.push_back(CMD_DISPLAY_INVENTORY);
1204 
1205         if (Hints.hints_type == HINT_BERSERK_CHAR)
1206         {
1207             text << "\nYou should probably stick with axes. Checking other "
1208                     "axes' enchantments can be worthwhile, though!";
1209         }
1210         break;
1211 
1212     case HINT_SEEN_MISSILES:
1213         text << "This is the first stack of missiles "
1214                 "<console>('<w>"
1215              << stringize_glyph(get_item_symbol(SHOW_ITEM_MISSILE))
1216              << "</w>') </console>"
1217                 "you've picked up. Missiles like boomerangs and throwing nets "
1218                 "can be thrown by hand, but other missiles like arrows and "
1219                 "bolts require a launcher and training in using it to be "
1220                 "really effective. "
1221 #ifdef USE_TILE_LOCAL
1222                 "<w>Right-clicking</w> on "
1223 #else
1224                 "Selecting "
1225 #endif
1226                 "the item in your <w>%</w>nventory will give more "
1227                 "information about both missiles and launcher.";
1228 
1229         cmd.push_back(CMD_DISPLAY_INVENTORY);
1230 
1231         if (Hints.hints_type == HINT_RANGER_CHAR)
1232         {
1233             text << "\nAs you're already trained in Bows, you only need to "
1234                     "bother collecting arrows.";
1235         }
1236         else if (Hints.hints_type == HINT_MAGIC_CHAR)
1237         {
1238             text << "\nHowever, as a spellslinger, you don't really need "
1239                     "another type of ranged attack.";
1240         }
1241         else
1242         {
1243             text << "\nFor now you might be best off with sticking to "
1244                     "stones for ranged attacks.";
1245         }
1246         break;
1247 
1248     case HINT_SEEN_ARMOUR:
1249         text << "This is the first piece of armour "
1250                 "<console>('<w>"
1251              << stringize_glyph(get_item_symbol(SHOW_ITEM_ARMOUR))
1252              << "</w>') </console>"
1253                 "you've picked up. "
1254                 "<tiles>You can click on it to wear it, and click again to "
1255                 "remove it. <w>Right-clicking</w> on it will give more "
1256                 "information.</tiles>"
1257                 "<console>Use <w>%</w> to wear it and <w>%</w> to take it off "
1258                 "again. You can view its properties from your "
1259                 "<w>%</w>nventory.</console>";
1260         cmd.push_back(CMD_WEAR_ARMOUR);
1261         cmd.push_back(CMD_REMOVE_ARMOUR);
1262         cmd.push_back(CMD_DISPLAY_INVENTORY);
1263 
1264         if (you.get_innate_mutation_level(MUT_HORNS) > 0)
1265         {
1266             text << "\nNote that because of your horns you will be unable"
1267                     " to wear helmets. "
1268                     "(Press <w>%</w> to see a list of your mutations and "
1269                     "innate abilities.)";
1270             cmd.push_back(CMD_DISPLAY_MUTATIONS);
1271         }
1272         break;
1273 
1274     case HINT_SEEN_RANDART:
1275         text << "Weapons and armour that have unusual descriptions like this "
1276                 "are much more likely to be of higher enchantment or have "
1277                 "special properties, good or bad.";
1278         break;
1279 
1280     case HINT_SEEN_CARRION:
1281         // TODO: Specialcase skeletons!
1282 
1283         if (gc.x <= 0 || gc.y <= 0) // XXX: only relevant for carrion shops?
1284             text << "Ah, a corpse!";
1285         else
1286         {
1287             int i = you.visible_igrd(gc);
1288             if (i == NON_ITEM)
1289                 text << "Ah, a corpse!";
1290             else
1291             {
1292                 text << "That <console>";
1293                 string glyph = glyph_to_tagstr(get_item_glyph(env.item[i]));
1294                 const string::size_type found = glyph.find("%");
1295                 if (found != string::npos)
1296                     glyph.replace(found, 1, "percent");
1297                 text << glyph << " ";
1298                 text << "</console>is a corpse.";
1299 #ifdef USE_TILE
1300                 tiles.place_cursor(CURSOR_TUTORIAL, gc);
1301                 tiles.add_text_tag(TAG_TUTORIAL, env.item[i].name(DESC_A), gc);
1302 #endif
1303             }
1304         }
1305         break;
1306 
1307     case HINT_SEEN_JEWELLERY:
1308         text << "You have picked up a piece of jewellery, either a ring"
1309              << "<console> ('<w>"
1310              << stringize_glyph(get_item_symbol(SHOW_ITEM_RING))
1311              << "</w>')</console>"
1312              << " or an amulet"
1313              << "<console> ('<w>"
1314              << stringize_glyph(get_item_symbol(SHOW_ITEM_AMULET))
1315              << "</w>')"
1316              << ". Press <w>%</w> to put it on and <w>%</w> to remove "
1317                 "it. You can view its properties from your <w>%</w>nventory</console>"
1318              << "<tiles>. You can click on it to put it on, and click again "
1319                 "to remove it. By <w>right-clicking</w> on it, you can view its "
1320                 "properties</tiles>.";
1321         cmd.push_back(CMD_WEAR_JEWELLERY);
1322         cmd.push_back(CMD_REMOVE_JEWELLERY);
1323         cmd.push_back(CMD_DISPLAY_INVENTORY);
1324         break;
1325 
1326     case HINT_SEEN_MISC:
1327         text << "This is a curious object indeed. You can play around with "
1328                 "it to find out what it does by "
1329                 "<tiles>clicking on it to e<w>%</w>oke </tiles>"
1330                 "<console>e<w>%</w>oking </console>"
1331                 "it. As usual, selecting it from your <w>%</w>nventory "
1332                 "might give you more information.";
1333         cmd.push_back(CMD_EVOKE);
1334         cmd.push_back(CMD_EVOKE);
1335         cmd.push_back(CMD_DISPLAY_INVENTORY);
1336         break;
1337 
1338     case HINT_SEEN_STAFF:
1339         text << "You have picked up a magic staff"
1340                 "<console> ('<w>";
1341 
1342         text << stringize_glyph(get_item_symbol(SHOW_ITEM_STAFF))
1343              << "</w>')</console>"
1344                 ". It must be <w>%</w>ielded to be of use. "
1345                 "Magicians use staves to increase their power in certain "
1346                 "spell schools. It can also be used as a weapon."
1347                 "<tiles> You can wield a staff by <w>left-clicking</w>.</tiles>";
1348         cmd.push_back(CMD_WIELD_WEAPON);
1349         break;
1350 
1351     case HINT_SEEN_GOLD:
1352         text << "You have picked up your first pile of gold"
1353                 "<console> ('<yellow>"
1354              << stringize_glyph(get_item_symbol(SHOW_ITEM_GOLD))
1355              << "</yellow>')</console>"
1356                 ". Unlike most other objects in Crawl it takes up no space in "
1357                 "your inventory and can't be dropped. Gold can be used to buy "
1358                 "items from shops, and is appreciated by certain gods.";
1359         break;
1360 
1361     case HINT_SEEN_STAIRS:
1362         // Don't give this information during the first turn, to give
1363         // the player time to have a look around.
1364         if (you.num_turns < 1)
1365             DELAY_EVENT;
1366 
1367         text << "These ";
1368 #ifndef USE_TILE
1369         // Is a monster blocking the view?
1370         if (monster_at(gc))
1371             DELAY_EVENT;
1372 
1373         text << glyph_to_tagstr(get_cell_glyph(gc)) << " ";
1374 #else
1375         tiles.place_cursor(CURSOR_TUTORIAL, gc);
1376         tiles.add_text_tag(TAG_TUTORIAL, "Stairs", gc);
1377 #endif
1378         text << "are downstairs. You can enter the next (deeper) "
1379                 "level by following them down (<w>%</w>). To return to "
1380                 "this level, press <w>%</w> while standing on the "
1381                 "upstairs.";
1382         cmd.push_back(CMD_GO_DOWNSTAIRS);
1383         cmd.push_back(CMD_GO_UPSTAIRS);
1384 
1385 #ifdef USE_TILE
1386         text << "\nAlternately, you can <w>left-click</w> on stairs you're "
1387                 "standing on to use them.";
1388 #endif
1389         break;
1390 
1391     case HINT_SEEN_ESCAPE_HATCH:
1392         if (you.num_turns < 1)
1393             DELAY_EVENT;
1394 
1395         // monsters standing on stairs
1396         if (monster_at(gc))
1397             DELAY_EVENT;
1398 
1399         text << "This ";
1400 #ifndef USE_TILE
1401         text << glyph_to_tagstr(get_cell_glyph(gc));
1402         text << " ";
1403 #else
1404         tiles.place_cursor(CURSOR_TUTORIAL, gc);
1405         tiles.add_text_tag(TAG_TUTORIAL, "Escape hatch", gc);
1406 #endif
1407         text << "is an escape hatch. You can use it to "
1408                 "leave a level with <w>%</w> and <w>%</w> respectively"
1409 #ifdef USE_TILE
1410                 " (or by <w>left-clicking</w>)"
1411 #endif
1412                 ", like stairs; unlike stairs, however, hatches are a one-way "
1413                 "trip, so be careful when descending!";
1414         cmd.push_back(CMD_GO_UPSTAIRS);
1415         cmd.push_back(CMD_GO_DOWNSTAIRS);
1416         break;
1417 
1418     case HINT_SEEN_BRANCH:
1419         text << "This ";
1420 #ifndef USE_TILE
1421         // Is a monster blocking the view?
1422         if (monster_at(gc))
1423             DELAY_EVENT;
1424 
1425         // FIXME: Branch entrance character is not being coloured yellow.
1426         text << glyph_to_tagstr(get_cell_glyph(gc)) << " ";
1427 #else
1428         tiles.place_cursor(CURSOR_TUTORIAL, gc);
1429         tiles.add_text_tag(TAG_TUTORIAL, "Branch stairs", gc);
1430 #endif
1431         text << "is the entrance to a different branch of the dungeon, "
1432                 "which might have different terrain, level layout and "
1433                 "monsters from the current main branch you're in. Some "
1434                 "branches contain only a single level, and others are many "
1435                 "levels deep. They can also contain entrances to other "
1436                 "branches."
1437 
1438                 "\n\nThe first three branches you'll encounter are the "
1439                 "Temple, the Lair and the Orcish Mines. While the Lair"
1440                 "and the Mines can be dangerous for the new adventurer, "
1441                 "the Temple is completely safe and contains a number of "
1442                 "altars at which you might convert to a new god.";
1443         break;
1444 
1445     case HINT_SEEN_PORTAL:
1446         // Delay in the unlikely event that a player still in hints mode
1447         // creates a portal with a Trowel card, since a portal vault
1448         // entry's description doesn't seem to get set properly until
1449         // after the vault is done being placed.
1450         if (you.pos() == gc)
1451             DELAY_EVENT;
1452 
1453         text << "This ";
1454 #ifndef USE_TILE
1455         // Is a monster blocking the view?
1456         if (monster_at(gc))
1457             DELAY_EVENT;
1458 
1459         text << glyph_to_tagstr(get_cell_glyph(gc)) << " ";
1460 #else
1461         tiles.place_cursor(CURSOR_TUTORIAL, gc);
1462         tiles.add_text_tag(TAG_TUTORIAL, "Portal", gc);
1463 #endif
1464         text << _describe_portal(gc);
1465         break;
1466 
1467     case HINT_STAIR_BRAND:
1468         // Monster or player standing on stairs.
1469         if (actor_at(gc))
1470             DELAY_EVENT;
1471 
1472 #ifdef USE_TILE
1473         text << "A small symbol on a stair tile signifies that there are "
1474                 "items in that position that you may want to check out.";
1475 #else
1476         text << "If any items are covering stairs or an escape hatch, then "
1477                 "that will be indicated by highlighting the <w><<</w> or "
1478                 "<w>></w> symbol, instead of hiding the stair symbol with "
1479                 "an item glyph.";
1480 #endif
1481         break;
1482 
1483     case HINT_HEAP_BRAND:
1484         // Monster or player standing on heap.
1485         if (actor_at(gc))
1486             DELAY_EVENT;
1487 
1488 #ifdef USE_TILE
1489         text << "A small symbol on an item tile signifies that there is at "
1490                 "least one other item in the same heap that you may want to "
1491                 "check out.";
1492         break;
1493 #else
1494         text << "If two or more items are on a single square, then the square "
1495                 "will be highlighted, and the symbol for the item on the top "
1496                 "of the heap will be shown.";
1497 #endif
1498         break;
1499 
1500     case HINT_TRAP_BRAND:
1501 #ifdef USE_TILE
1502         // Tiles show both the trap and the item heap.
1503         return;
1504 #else
1505         // Monster or player standing on trap.
1506         if (actor_at(gc))
1507             DELAY_EVENT;
1508 
1509         text << "If any items are covering a trap, then that will be "
1510                 "indicated by highlighting the <w>^</w> symbol, instead of "
1511                 "hiding the trap symbol with an item glyph.";
1512 #endif
1513         break;
1514 
1515     case HINT_SEEN_TRAP:
1516         if (you.pos() == gc)
1517             text << "Oops... you just triggered a trap. ";
1518         else
1519             text << "You just discovered a trap. ";
1520 
1521         text << "You'll occasionally stumble into these nasty constructions";
1522 #ifndef USE_TILE
1523         {
1524             cglyph_t g = get_cell_glyph(gc);
1525 
1526             if (g.ch == ' ' || g.col == BLACK)
1527                 g.col = LIGHTCYAN;
1528 
1529             text << ", depicted by " << _colourize_glyph(g.col, '^');
1530         }
1531 #endif
1532         text << ". Each type of trap has a different effect when stepped on, "
1533                 "like alerting enemies or causing random teleportation.";
1534         break;
1535 
1536     case HINT_SEEN_ALTAR:
1537         text << "That ";
1538 #ifndef USE_TILE
1539         // Is a monster blocking the view?
1540         if (monster_at(gc))
1541             DELAY_EVENT;
1542 
1543         text << glyph_to_tagstr(get_cell_glyph(gc)) << " ";
1544 #else
1545         {
1546             tiles.place_cursor(CURSOR_TUTORIAL, gc);
1547             string altar = "An altar to ";
1548             altar += god_name(feat_altar_god(env.grid(gc)));
1549             tiles.add_text_tag(TAG_TUTORIAL, altar, gc);
1550         }
1551 #endif
1552         text << "is an altar. "
1553 #ifdef USE_TILE
1554                 "By <w>right-clicking</w> on it with your mouse "
1555 #else
1556                 "If you target the altar with <w>x</w>, then press <w>v</w> "
1557 #endif
1558                 "you can get a short description.\n"
1559                 "Press <w>%</w> or <w>%</w> while standing on the square to join the faith "
1560                 "or read some information about the god in question. Before "
1561                 "taking up the corresponding faith you'll be asked for "
1562                 "confirmation.";
1563         cmd.push_back(CMD_GO_UPSTAIRS);
1564         cmd.push_back(CMD_GO_DOWNSTAIRS);
1565 
1566         if (you_worship(GOD_NO_GOD)
1567             && Hints.hints_type == HINT_MAGIC_CHAR)
1568         {
1569             text << "\n\nThe simplest god for an unexperienced conjurer is "
1570                     "probably Vehumet, though Sif Muna is a good second "
1571                     "choice.";
1572         }
1573         break;
1574 
1575     case HINT_SEEN_SHOP:
1576 #ifdef USE_TILE
1577         tiles.place_cursor(CURSOR_TUTORIAL, gc);
1578         tiles.add_text_tag(TAG_TUTORIAL, shop_name(*shop_at(gc)), gc);
1579 #else
1580         // Is a monster blocking the view?
1581         if (monster_at(gc))
1582             DELAY_EVENT;
1583 #endif
1584         text << "That "
1585 #ifndef USE_TILE
1586              << glyph_to_tagstr(get_cell_glyph(gc)) << " "
1587 #endif
1588                 "is a shop. You can enter it by typing <w>%</w> or <w>%</w>"
1589 #ifdef USE_TILE
1590                 ", or by <w>left-clicking</w> on it "
1591 #endif
1592                 "while standing on the square.";
1593         cmd.push_back(CMD_GO_UPSTAIRS);
1594         cmd.push_back(CMD_GO_DOWNSTAIRS);
1595 
1596         text << "\n\nIf there's anything you want which you can't afford yet "
1597                 "you can hold <w>Shift</w> and press the corresponding letter "
1598                 "to put it on your shopping list. The game will then remind "
1599                 "you when you gather enough gold to buy it.";
1600         break;
1601 
1602     case HINT_SEEN_DOOR:
1603         if (you.num_turns < 1)
1604             DELAY_EVENT;
1605 
1606 #ifdef USE_TILE
1607         tiles.place_cursor(CURSOR_TUTORIAL, gc);
1608         tiles.add_text_tag(TAG_TUTORIAL, "Closed door", gc);
1609 #endif
1610 
1611         text << "That "
1612 #ifndef USE_TILE
1613              << glyph_to_tagstr(get_cell_glyph(gc)) << " "
1614 #endif
1615                 "is a closed door. You can open it by walking into it. "
1616                 "Sometimes it is useful to close a door. Do so by <console>"
1617                 "pressing <w>%</w> while next to it. If there are several "
1618                 "doors, you will then be prompted for a direction.</console>"
1619                 "<tiles>clicking on the door while next to it.</tiles>";
1620         cmd.push_back(CMD_CLOSE_DOOR);
1621         break;
1622 
1623     case HINT_SEEN_RUNED_DOOR:
1624 #ifdef USE_TILE
1625         tiles.place_cursor(CURSOR_TUTORIAL, gc);
1626         tiles.add_text_tag(TAG_TUTORIAL, "Runed door", gc);
1627 #endif
1628         text << "That ";
1629 #ifndef USE_TILE
1630         text << glyph_to_tagstr(get_cell_glyph(gc)) << " ";
1631 #endif
1632         text << "is a runed door, which is probably in front of something "
1633                 "unusually dangerous. Monsters will never open it. If you do, "
1634                 "the runes will be broken, and it will become a normal door.";
1635         break;
1636 
1637     case HINT_KILLED_MONSTER:
1638         text << "Congratulations, your character just gained experience by "
1639                 "killing this monster! This will raise some of your skills, "
1640                 "making you more deadly.";
1641         // A more detailed description of skills is given when you go past an
1642         // integer point.
1643 
1644         if (you_worship(GOD_TROG))
1645         {
1646             text << " Also, most kills will grant you favour in the eyes of "
1647                     "Trog.";
1648         }
1649         break;
1650 
1651     case HINT_NEW_LEVEL:
1652         text << "Well done! Reaching a new experience level is always a "
1653                 "nice event: you get more health and magic points, and "
1654                 "occasionally increases to your attributes: strength, "
1655                 "intelligence, and dexterity.";
1656 
1657         if (Hints.hints_type == HINT_MAGIC_CHAR)
1658         {
1659             text << "\nGaining an experience level also lets you learn more "
1660                     "difficult spells. You can memorise a second spell "
1661                     "with <w>%</w>"
1662 #ifdef USE_TILE
1663                     ", or by <w>clicking</w> on it in the memorisation tab"
1664 #endif
1665                     ".";
1666             cmd.push_back(CMD_MEMORISE_SPELL);
1667         }
1668         break;
1669 
1670     case HINT_SKILL_RAISE:
1671 
1672         text << "One of your skills just reached a milestone. The skills you "
1673                 "use are automatically trained whenever you gain experience, "
1674                 "by killing monsters. By default, experience goes to skills "
1675                 "you actively use. To view or manage your skills, "
1676 #ifdef USE_TILE_WEB
1677                 "type <w>%</w>.";
1678 #else
1679                 "<console>type <w>%</w>.</console>"
1680                 "<tiles>click on the skill tab in the bottom-right.</tiles>";
1681 #endif
1682         cmd.push_back(CMD_DISPLAY_SKILLS);
1683         break;
1684 
1685     case HINT_GAINED_MAGICAL_SKILL:
1686         text << "Being skilled in a magical \"school\" makes it easier to "
1687                 "learn and cast spells of that school. Many spells belong to "
1688                 "a combination of several schools, in which case the average "
1689                 "skill in these schools will determine spellcasting success "
1690                 "and power.";
1691         break;
1692 
1693     case HINT_GAINED_MELEE_SKILL:
1694         text << "Being skilled with a particular type of weapon will make you "
1695                 "deal slightly more damage and attack significantly faster "
1696                 "with all weapons of that type. It's a good idea to focus on "
1697                 "just one weapon type, usually one you start with, though "
1698                 "finding a particularly good weapon may justify a switch.";
1699         break;
1700 
1701     case HINT_GAINED_RANGED_SKILL:
1702         text << "Being skilled in a particular type of ranged attack will let "
1703                 "you deal more damage when using the appropriate weapons. It "
1704                 "is usually best to concentrate on one type of ranged attack "
1705                 "(including spells), and to use a melee weapon as a backup "
1706                 "for fighting weak enemies.";
1707         break;
1708 
1709     case HINT_CHOOSE_STAT:
1710         text << "Every third level, you get to choose an attribute to raise: "
1711                 "strength, intelligence, or dexterity.\n"
1712                 "<w>Strength</w> makes heavy armour less cumbersome and "
1713                 "slightly increases weapon damage.\n"
1714                 "<w>Intelligence</w> makes your spells more reliable and "
1715                 "powerful.\n"
1716                 "<w>Dexterity</w> increases your evasion and stealth.";
1717         break;
1718 
1719     case HINT_YOU_ENCHANTED:
1720         text << "Enchantments of all types can befall you temporarily. "
1721                 "Brief descriptions of these appear at the lower end of the "
1722                 "stats area. Press <w>%</w> for more details. You can search "
1723                 "for full enchantment descriptions by pressing <w>%/T</w>.";
1724         cmd.push_back(CMD_DISPLAY_CHARACTER_STATUS);
1725         cmd.push_back(CMD_DISPLAY_COMMANDS);
1726         cmd.push_back(CMD_DISPLAY_COMMANDS); // yes, twice
1727         break;
1728 
1729     case HINT_YOU_POISON:
1730         if (crawl_state.game_is_hints())
1731         {
1732             // Hack: reset hints_just_triggered, to force recursive calling of
1733             // learned_something_new(). Don't do this for the tutorial!
1734             Hints.hints_just_triggered = false;
1735             learned_something_new(HINT_YOU_ENCHANTED);
1736             Hints.hints_just_triggered = true;
1737         }
1738         text << "Poison will rapidly reduce your health. You can wait it out "
1739                 "with <w>%</w>, but if you're in combat or lethally poisoned, "
1740                 "you'll probably want to quaff a potion of curing.";
1741         cmd.push_back(CMD_REST);
1742         break;
1743 
1744     case HINT_MULTI_PICKUP:
1745         text << "There are a lot of items here. You choose what to pick up "
1746                 "from a menu: press <w>%</w> "
1747 #ifdef USE_TILE
1748                 "or <w>click</w> on the player doll "
1749 #endif
1750                 "to enter the pickup menu. To leave the menu, confirm your "
1751                 "selection with <w>Enter</w>.";
1752         cmd.push_back(CMD_PICKUP);
1753         break;
1754 
1755     case HINT_FULL_INVENTORY:
1756         text << "Sadly, your inventory is limited to 52 items, and it "
1757             "appears your knapsack is full.";
1758         text << " However, this is easy enough to rectify: simply "
1759                 "<w>%</w>rop some of the stuff you don't need right now.";
1760         cmd.push_back(CMD_DROP);
1761 
1762 #ifdef USE_TILE
1763         text << " In the drop menu you can then comfortably select which "
1764                 "items to drop by pressing their inventory letter, or by "
1765                 "clicking on them.";
1766 #endif
1767         break;
1768 
1769     case HINT_SHIFT_RUN:
1770         text << "Walking around takes fewer keystrokes if you press "
1771                 "<w>Shift-direction</w> or <w>/ direction</w>. "
1772                 "That will let you run until a monster comes into sight or "
1773                 "your character sees something interesting.";
1774         break;
1775 
1776     case HINT_MAP_VIEW:
1777         text << "As you explore a level, orientation can become difficult. "
1778                 "Press <w>%</w> to bring up the level map. Typing <w>?</w> "
1779                 "shows the list of level map commands. "
1780                 "Most importantly, moving the cursor to a spot and pressing "
1781                 "<w>.</w> or <w>Enter</w> lets your character move there on "
1782                 "its own.";
1783         cmd.push_back(CMD_DISPLAY_MAP);
1784 
1785 #ifdef USE_TILE
1786         text << "\nAlso, clicking on the right-side minimap with your "
1787                 "<w>right mouse button</w> will zoom into that dungeon area. "
1788                 "Clicking with the <w>left mouse button</w> instead will let "
1789                 "you move there.";
1790 #endif
1791         break;
1792 
1793     case HINT_AUTO_EXPLORE:
1794         if (!Hints.hints_explored)
1795             return;
1796 
1797         text << "Fully exploring a level and picking up all the interesting "
1798                 "looking items can be tedious. To save on this tedium you "
1799                 "can press <w>%</w> to auto-explore, which will "
1800                 "automatically explore unmapped regions, automatically pick "
1801                 "up interesting items, and stop if a monster or interesting "
1802                 "dungeon feature (stairs, altar, etc.) is encountered.";
1803         cmd.push_back(CMD_EXPLORE);
1804         Hints.hints_explored = false;
1805         break;
1806 
1807     case HINT_DONE_EXPLORE:
1808         // XXX: You'll only get this message if you're using auto exploration.
1809         text << "Hey, you've finished exploring the dungeon on this level! "
1810                 "You can search for stairs from the level map (<w>%</w>) "
1811                 "by pressing <w>></w>. The cursor will jump to the nearest "
1812                 "staircase, and by pressing <w>.</w> or <w>Enter</w> your "
1813                 "character can move there, too. Each level of Crawl has three "
1814 #ifndef USE_TILE
1815                 "white "
1816 #endif
1817                 "up and three "
1818 #ifndef USE_TILE
1819                 "white "
1820 #endif
1821                 "down stairs. Unexplored parts can often be accessed via "
1822                 "another level.";
1823         cmd.push_back(CMD_DISPLAY_MAP);
1824         break;
1825 
1826     case HINT_AUTO_EXCLUSION:
1827         // In the highly unlikely case the player encounters a
1828         // hostile statue or oklob plant during the hints mode...
1829         if (Hints.hints_explored)
1830         {
1831             // Hack: Reset hints_just_triggered, to force recursive calling of
1832             //       learned_something_new().
1833             Hints.hints_just_triggered = false;
1834             learned_something_new(HINT_AUTO_EXPLORE);
1835             Hints.hints_just_triggered = true;
1836         }
1837         text << "\nTo prevent autotravel or autoexplore taking you into "
1838                 "dangerous territory, you can set travel exclusions by "
1839                 "entering the map view (<w>%</w>) and then toggling the "
1840                 "exclusion radius on the monster position with <w>e</w>. "
1841                 "To make this easier some immobile monsters listed in the "
1842                 "<w>auto_exclude</w> option (such as this one) are considered "
1843                 "dangerous enough to warrant an automatic setting of an "
1844                 "exclusion. It will be automatically cleared if you manage to "
1845                 "kill the monster. You could also manually remove the "
1846                 "exclusion with <w>%ee</w> but unless you remove this monster "
1847                 "from the auto_exclude list, the exclusion will be reset the "
1848                 "next turn.";
1849         cmd.push_back(CMD_DISPLAY_MAP);
1850         cmd.push_back(CMD_DISPLAY_MAP);
1851         break;
1852 
1853     case HINT_HEALING_POTIONS:
1854         text << "Your health is getting dangerously low. Retreating and/or "
1855                 "quaffing a potion of heal wounds or curing might be a good idea.";
1856         break;
1857 
1858     case HINT_NEED_HEALING:
1859         text << "If you're low on health or magic and there's no urgent "
1860                 "need to move, you can rest for a bit. Ideally, you should "
1861                 "retreat to an area you've already explored and cleared "
1862                 "of monsters before resting, since resting on the edge of "
1863                 "the explored terrain increases the risk of rest being "
1864                 "interrupted by a wandering monster. Press <w>%</w> or "
1865                 "<w>shift-numpad-5</w>"
1866 #ifdef USE_TILE
1867                 ", or click on the 'rest' button,"
1868 #endif
1869                 " to do so.";
1870         cmd.push_back(CMD_REST);
1871         break;
1872 
1873     case HINT_NEED_POISON_HEALING:
1874         text << "You are lethally poisoned, so now would be a good time to "
1875                 "<w>%</w>uaff a potion of heal wounds or, better yet, a "
1876                 "potion of curing. If you have seen neither of these so far, "
1877                 "try unknown potions in your inventory. Good luck!";
1878         cmd.push_back(CMD_QUAFF);
1879         break;
1880 
1881     case HINT_INVISIBLE_DANGER:
1882         text << "Fighting against a monster you cannot see is difficult. "
1883                 "If you don't have a source of see invisibility, you can find "
1884                 "invisible monsters by luring them into shallow water, opaque "
1885                 "clouds (from a scroll of fog), or a corridor, where their "
1886                 "movements will be more predictable. If things go south, try "
1887                 "teleporting out, or leaving the level entirely!";
1888 
1889         // To prevent this text being immediately followed by the next one...
1890         Hints.hints_last_healed = you.num_turns - 30;
1891         break;
1892 
1893             // XXX: replace this with an explanation of autopickup toggles?
1894     case HINT_NEED_HEALING_INVIS:
1895         text << "You recently noticed an invisible monster, so unless you "
1896                 "killed it or left the scene resting might not be safe. If you "
1897                 "still need to replenish your health or magic, you'll have "
1898                 "to quaff an appropriate potion. For normal resting you will "
1899                 "first have to get away from the danger.";
1900 
1901         Hints.hints_last_healed = you.num_turns;
1902         break;
1903 
1904     case HINT_CAN_BERSERK:
1905         // Don't print this information if the player already knows it.
1906         if (Hints.hints_berserk_counter)
1907             return;
1908 
1909         text << "Against particularly difficult foes, you should use your "
1910                 "Berserk <w>%</w>bility. Killing monsters while berserking "
1911                 "makes it last longer.";
1912         cmd.push_back(CMD_USE_ABILITY);
1913         break;
1914 
1915     case HINT_POSTBERSERK:
1916         text << "Berserking is extremely exhausting! Afterwards you are slowed "
1917                 "down, and you won't be able to berserk again until enough "
1918                 "time passes for your exhaustion to fade.";
1919         break;
1920 
1921     case HINT_RUN_AWAY:
1922         text << "Whenever your health is very low and you're in danger of "
1923                 "dying, check your options carefully. Often, retreat or use "
1924                 "of some item might be a viable alternative to fighting on.";
1925 
1926         if (you_worship(GOD_TROG) && you.can_go_berserk())
1927         {
1928             text << "With "
1929                 << apostrophise(god_name(you.religion))
1930                 << " support you can use your Berserk ability (<w>%</w>) to "
1931                     "temporarily gain more health and greater strength. Bear "
1932                     "in mind that berserking at the last minute is often "
1933                     "risky, and prevents you from using items to escape!";
1934             cmd.push_back(CMD_USE_ABILITY);
1935         }
1936 
1937         text << " If retreating to another level, keep in mind that monsters "
1938                 "may follow you if they're standing right next to you when "
1939                 "you start climbing or descending the stairs. And even if "
1940                 "you've managed to shake them off, they'll still be there when "
1941                 "you come back, so you might want to use a different set of "
1942                 "stairs when you return.";
1943 
1944         break;
1945 
1946     case HINT_RETREAT_CASTER:
1947         text << "Without magical power you're unable to cast spells. While "
1948                 "melee is a possibility, that's not where your strengths "
1949                 "lie, so retreat (if possible) might be the better option.";
1950 
1951         if (_advise_use_wand())
1952         {
1953             text << "\n\nOr you could e<w>%</w>oke a wand to deal damage.";
1954             cmd.push_back(CMD_EVOKE);
1955         }
1956         break;
1957 
1958     case HINT_YOU_MUTATED:
1959         text << "Mutations can be obtained from several sources, among them "
1960                 "potions, spell miscasts, and overuse of strong enchantments "
1961                 "like invisibility. The gods Zin and Jiyva will cure your "
1962                 "mutations. Check your mutations with <w>%</w>.";
1963         cmd.push_back(CMD_DISPLAY_MUTATIONS);
1964         break;
1965 
1966     case HINT_NEW_ABILITY_GOD:
1967         switch (you.religion)
1968         {
1969         // Gods where first granted ability is passive.
1970         case GOD_ASHENZARI:
1971         case GOD_BEOGH:
1972         case GOD_MAKHLEB:
1973         case GOD_VEHUMET:
1974         case GOD_XOM:
1975         case GOD_SHINING_ONE:
1976             // TODO: update me
1977             text << "You just gained a divine ability. Press <w>%</w> "
1978 #ifdef USE_TILE
1979                     "or press <w>Shift</w> and <w>right-click</w> on the "
1980                     "player tile "
1981 #endif
1982                     "to take a look at your abilities.";
1983             cmd.push_back(CMD_DISPLAY_RELIGION);
1984             break;
1985 
1986         // Gods where first granted ability is active.
1987         default:
1988             text << "You just gained a divine ability. Press <w>%</w> to "
1989                     "take a look at your abilities or to use one of them.";
1990             cmd.push_back(CMD_USE_ABILITY);
1991             break;
1992         }
1993         break;
1994 
1995     case HINT_NEW_ABILITY_ITEM:
1996         text << "That item you just equipped granted you a new ability. "
1997                 "Press <w>%</w> "
1998 #ifdef USE_TILE
1999                 "(or <w>click</w> in the <w>command panel</w>) "
2000 #endif
2001                 "to take a look at your abilities or to use one of them.";
2002         cmd.push_back(CMD_USE_ABILITY);
2003         break;
2004 
2005     case HINT_ITEM_RESISTANCES:
2006         // Specialcase flight because it's a guaranteed trigger in the
2007         // tutorial.
2008         if (you.equip_flight())
2009         {
2010             text << "Flight will allow you to cross deep water or lava. Items "
2011                     "that allow you to fly will activate automatically when "
2012                     "worn.";
2013         }
2014         else
2015         {
2016             text << "Equipping this item affects your resistances. Check the "
2017                     "overview screen (<w>%</w>"
2018 #ifdef USE_TILE_LOCAL
2019                     " or click on the <w>character overview button</w> in the "
2020                     "command panel"
2021 #endif
2022                     ") for details.";
2023             cmd.push_back(CMD_RESISTS_SCREEN);
2024         }
2025         break;
2026 
2027             // TODO: rethink this v
2028     case HINT_CONVERT:
2029         switch (you.religion)
2030         {
2031                 // gods without traditional piety
2032             case GOD_XOM:
2033                 return print_hint("HINT_CONVERT Xom");
2034             case GOD_GOZAG:
2035                 return print_hint("HINT_CONVERT Gozag");
2036             case GOD_RU:
2037                 return print_hint("HINT_CONVERT Ru");
2038             case GOD_USKAYAW:
2039                 return print_hint("HINT_CONVERT Uskayaw");
2040             default:
2041                 print_hint("HINT_CONVERT");
2042 
2043         }
2044 
2045         break;
2046 
2047     case HINT_GOD_DISPLEASED:
2048         text << "Uh-oh, " << god_name(you.religion) << " is growing "
2049                 "displeased because your piety is running low. Possibly this "
2050                 "is the case because you're committing heretical acts, "
2051                 "because " << god_name(you.religion) << " finds your "
2052                 "worship lacking, or a combination of the two. "
2053                 "If your piety goes to zero, then you'll be excommunicated. "
2054                 "Better get cracking on raising your piety, and/or stop "
2055                 "annoying your god. ";
2056 
2057         text << "In any case, you'd better reread the religious description. "
2058                 "To do so, press <w>%</w>"
2059 #ifdef USE_TILE
2060                 " or press <w>Shift</w> and <w>right-click</w> on your avatar"
2061 #endif
2062                 ".";
2063         cmd.push_back(CMD_DISPLAY_RELIGION);
2064         break;
2065 
2066     case HINT_EXCOMMUNICATE:
2067     {
2068         const god_type new_god   = (god_type) gc.x;
2069         const int      old_piety = gc.y;
2070 
2071         god_type old_god = GOD_NO_GOD;
2072         for (god_iterator it; it; ++it)
2073             if (you.worshipped[*it] > 0)
2074             {
2075                 old_god = *it;
2076                 break;
2077             }
2078 
2079         const string old_god_name  = god_name(old_god);
2080         const string new_god_name  = god_name(new_god);
2081 
2082         if (new_god == GOD_NO_GOD)
2083         {
2084             if (old_piety < 1)
2085             {
2086                 text << "Uh-oh, " << old_god_name << " just excommunicated you "
2087                         "for running out of piety (your divine favour went "
2088                         "to nothing). Maybe you repeatedly violated the "
2089                         "religious rules, or maybe you failed to please your "
2090                         "deity often enough, or some combination of the two. "
2091                         "If you can find an altar dedicated to "
2092                      << old_god_name;
2093             }
2094             else
2095             {
2096                 text << "Should you decide that abandoning " << old_god_name
2097                      << "wasn't such a smart move after all, and you'd like to "
2098                         "return to your old faith, you'll have to find an "
2099                         "altar dedicated to " << old_god_name << " where";
2100             }
2101             text << " you can re-convert, and all will be well. Otherwise "
2102                     "you'll have to weather this god's displeasure until all "
2103                     "divine wrath is spent.";
2104 
2105         }
2106         else
2107         {
2108             bool angry = false;
2109             if (is_good_god(old_god))
2110             {
2111                 if (is_good_god(new_god))
2112                 {
2113                     text << "Fortunately, it seems that " << old_god_name <<
2114                             " didn't mind your converting to " << new_god_name
2115                          << ". ";
2116 
2117                     if (old_piety > piety_breakpoint(0))
2118                         text << "You even kept some of your piety! ";
2119 
2120                     text << "Note that this kind of alliance only exists "
2121                             "between the three good gods, so don't expect this "
2122                             "to be the norm.";
2123                 }
2124                 else if (!god_hates_your_god(old_god))
2125                 {
2126                     text << "Fortunately, it seems that " << old_god_name <<
2127                             " didn't mind your converting to " << new_god_name
2128                          << ". That's because " << old_god_name << " is one of "
2129                             "the good gods who generally are rather forgiving "
2130                             "about change of faith - unless you switch over to "
2131                             "the path of evil, in which case their retribution "
2132                             "can be nasty indeed!";
2133                 }
2134                 else
2135                 {
2136                     text << "Looks like " << old_god_name << " didn't "
2137                             "appreciate your converting to " << new_god_name
2138                          << "! But really, changing from one of the good gods "
2139                             "to an evil one, what did you expect!? For any god "
2140                             "not on the opposing side of the faith, "
2141                          << old_god_name << " would have been much more "
2142                             "forgiving. ";
2143 
2144                     angry = true;
2145                 }
2146             }
2147             else if (god_hates_your_god(old_god))
2148             {
2149                 text << "Looks like " << old_god_name << " didn't appreciate "
2150                         "your converting to " << new_god_name << "! (Actually, "
2151                         "only the three good gods will usually be forgiving "
2152                         "about this kind of faithlessness.) ";
2153 
2154                 angry = true;
2155             }
2156 
2157             if (angry)
2158             {
2159                 text << "Unfortunately, while converting back would appease "
2160                      << old_god_name << ", it would annoy " << new_god_name
2161                      << ", so you're stuck with having to suffer the wrath of "
2162                         "one god or another.";
2163             }
2164         }
2165 
2166         break;
2167     }
2168 
2169     case HINT_WIELD_WEAPON:
2170     {
2171         int wpn = you.equip[EQ_WEAPON];
2172         if (Hints.hints_type == HINT_RANGER_CHAR && wpn != -1
2173             && you.inv[wpn].is_type(OBJ_WEAPONS, WPN_SHORTBOW))
2174         {
2175             text << "You can easily switch between weapons in slots a and "
2176                     "b by pressing <w>%</w>.";
2177             cmd.push_back(CMD_WEAPON_SWAP);
2178         }
2179         else
2180         {
2181             text << "You can easily switch back to your weapon in slot a by "
2182                     "pressing <w>%</w>. To change the slot of an item, press "
2183                     "<w>%i</w> and choose the appropriate slots.";
2184             cmd.push_back(CMD_WEAPON_SWAP);
2185             cmd.push_back(CMD_ADJUST_INVENTORY);
2186         }
2187         break;
2188     }
2189     case HINT_FLEEING_MONSTER:
2190         if (Hints.hints_type != HINT_BERSERK_CHAR)
2191             return;
2192 
2193         text << "Now that monster is scared of you! Note that you do not "
2194                 "absolutely have to follow it. Rather, you can let it run "
2195                 "away. Sometimes, though, it can be useful to attack a "
2196                 "fleeing creature by throwing something after it. If you "
2197                 "have any stones in your <w>%</w>nventory, you can look "
2198                 "at one of them to read an explanation of how to do this.";
2199         cmd.push_back(CMD_DISPLAY_INVENTORY);
2200         break;
2201 
2202     case HINT_MONSTER_BRAND:
2203 #ifdef USE_TILE
2204         tiles.place_cursor(CURSOR_TUTORIAL, gc);
2205         if (const monster* m = monster_at(gc))
2206             tiles.add_text_tag(TAG_TUTORIAL, m->name(DESC_A), gc);
2207 #endif
2208         text << "That monster looks a bit unusual. You might wish to examine "
2209                 "it a bit more closely by "
2210 #ifdef USE_TILE
2211                 "hovering your mouse over its tile.";
2212 #else
2213                 "pressing <w>%</w> and moving the cursor onto its square.";
2214         cmd.push_back(CMD_LOOK_AROUND);
2215 #endif
2216         break;
2217 
2218     case HINT_MONSTER_FRIENDLY:
2219     {
2220         const monster* m = monster_at(gc);
2221 
2222         if (!m)
2223             DELAY_EVENT;
2224 
2225 #ifdef USE_TILE
2226         tiles.place_cursor(CURSOR_TUTORIAL, gc);
2227         tiles.add_text_tag(TAG_TUTORIAL, m->name(DESC_A), gc);
2228 #endif
2229         text << "That monster is friendly to you and will attack your "
2230                 "enemies, though you'll get only part of the experience for "
2231                 "monsters damaged by allies, compared to what you'd get for "
2232                 "doing all the work yourself. You can command your allies by "
2233                 "pressing <w>%</w>.";
2234         cmd.push_back(CMD_SHOUT);
2235 
2236         if (!mons_att_wont_attack(m->attitude))
2237         {
2238             text << "\nHowever, it is only <w>temporarily</w> friendly, and "
2239                     "will become dangerous again when this friendliness "
2240                     "wears off.";
2241         }
2242         break;
2243     }
2244 
2245     case HINT_MONSTER_SHOUT:
2246     {
2247         const monster* m = monster_at(gc);
2248 
2249         if (!m)
2250             DELAY_EVENT;
2251 
2252         // "Shouts" from zero experience monsters are boring, ignore
2253         // them.
2254         if (!mons_is_threatening(*m))
2255         {
2256             Hints.hints_events[HINT_MONSTER_SHOUT] = true;
2257             return;
2258         }
2259 
2260         const bool vis = you.can_see(*m);
2261 
2262 #ifdef USE_TILE
2263         if (vis)
2264         {
2265             tiles.place_cursor(CURSOR_TUTORIAL, gc);
2266             tiles.add_text_tag(TAG_TUTORIAL, m->name(DESC_A), gc);
2267         }
2268 #endif
2269         if (!vis)
2270         {
2271             text << "Uh-oh, some monster noticed you, either one that's "
2272                     "around a corner or one that's invisible. Plus, the "
2273                     "noise it made will alert other monsters in the "
2274                     "vicinity, who will come to check out what the commotion "
2275                     "was about.";
2276         }
2277         else if (!mons_can_shout(m->type))
2278         {
2279             text << "Uh-oh, that monster noticed you! Fortunately, it "
2280                     "didn't make any noise, but many monsters do make "
2281                     "noise when they notice you. That will alert other "
2282                     "monsters in the area, who will come to check out what "
2283                     "the commotion was about.";
2284         }
2285         else
2286         {
2287             text << "Uh-oh, that monster noticed you! Plus, the "
2288                     "noise it made will alert other monsters in the "
2289                     "vicinity, who will come to check out what the commotion "
2290                     "was about.";
2291         }
2292         break;
2293     }
2294 
2295     case HINT_MONSTER_LEFT_LOS:
2296     {
2297         const monster* m = monster_at(gc);
2298 
2299         if (!m || !you.can_see(*m))
2300             DELAY_EVENT;
2301 
2302         text << m->name(DESC_THE, true) << " didn't vanish, but merely "
2303                 "moved onto a square which you can't currently see. It's still "
2304                 "nearby, unless something happens to it in the short amount of "
2305                 "time it's out of sight.";
2306         break;
2307     }
2308 
2309     case HINT_SEEN_MONSTER:
2310     case HINT_SEEN_FIRST_OBJECT:
2311         // Handled in special functions.
2312         break;
2313 
2314     case HINT_SEEN_ZERO_EXP_MON:
2315     {
2316         const monster_info* mi = env.map_knowledge(gc).monsterinfo();
2317 
2318         if (!mi)
2319             DELAY_EVENT;
2320 
2321         text << "That ";
2322 #ifdef USE_TILE
2323         // need to highlight monster
2324         tiles.place_cursor(CURSOR_TUTORIAL, gc);
2325         tiles.add_text_tag(TAG_TUTORIAL, *mi);
2326 
2327         text << "is a ";
2328 #else
2329         text << glyph_to_tagstr(get_mons_glyph(*mi)) << " is a ";
2330 #endif
2331         text << mi->proper_name(DESC_PLAIN).c_str() << ". ";
2332 
2333         text << "While <w>technically</w> a monster, it's more like "
2334                 "dungeon furniture, since it's harmless and doesn't move. "
2335                 "If it's in your way you can attack and kill it like other "
2336                 "monsters, but you won't get any experience for doing so. ";
2337         break;
2338     }
2339 
2340     case HINT_ABYSS:
2341         text << "Uh-oh, you've wound up in the Abyss! The Abyss is a special "
2342                 "place where you cannot remember or map where you've been; it "
2343                 "is filled with nasty monsters, and you're probably going to "
2344                 "die.\n";
2345         text << "To increase your chances of survival until you can find the "
2346                 "exit"
2347 #ifndef USE_TILE
2348                 " (a flickering <w>"
2349              << stringize_glyph(get_feat_symbol(DNGN_EXIT_ABYSS))
2350              << "</w>)"
2351 #endif
2352                 ", keep moving, don't fight any of the monsters, and don't "
2353                 "chase after items on the ground. If monsters are closing in, "
2354                 "try to use items of hasting to get away.";
2355         break;
2356 
2357     case HINT_SPELL_MISCAST:
2358     {
2359         // Don't give at the beginning of your spellcasting career.
2360         if (you.max_magic_points <= 2)
2361             DELAY_EVENT;
2362 
2363         if (!crawl_state.game_is_hints())
2364         {
2365             text << "Miscasting a spell can have nasty consequences, "
2366                     "particularly for the more difficult spells. Your chance "
2367                     "of successfully casting a spell increases with your magic "
2368                     "skills, and can also be improved with the help of some "
2369                     "items. Use the <w>%</w> command "
2370 #ifdef USE_TILE_LOCAL
2371                     "or mouse over the spell tiles "
2372 #endif
2373                     "to check your current failure rates.";
2374             cmd.push_back(CMD_DISPLAY_SPELLS);
2375             break;
2376         }
2377         text << "You just miscast a spell. ";
2378 
2379         const item_def *shield = you.slot_item(EQ_SHIELD, false);
2380         if (!player_effectively_in_light_armour() || shield)
2381         {
2382             text << "Wearing heavy body armour or using a shield, especially a "
2383                     "large one, can severely hamper your spellcasting "
2384                     "abilities. You can check the effect of this by comparing "
2385                     "the failure rates on the <w>%\?</w> screen with and "
2386                     "without the item being worn.\n\n";
2387             cmd.push_back(CMD_CAST_SPELL);
2388         }
2389 
2390         text << "If the spellcasting success chance is high (which can be "
2391                 "checked by entering <w>%\?</w> or <w>%</w>) then a miscast "
2392                 "merely means the spell is not working, along with a harmless "
2393                 "side effect. "
2394                 "However, for spells with a high failure rate, there's a "
2395                 "chance of contaminating yourself with magical energy, plus a "
2396                 "chance of an additional harmful side effect. Normally this "
2397                 "isn't a problem, since magical contamination bleeds off over "
2398                 "time, but if you're repeatedly contaminated in a short amount "
2399                 "of time you'll mutate or suffer from other ill side effects."
2400                 "\n\n";
2401         cmd.push_back(CMD_CAST_SPELL);
2402         cmd.push_back(CMD_DISPLAY_SPELLS);
2403 
2404         text << "Note that a miscast spell will still consume the full amount "
2405                 "of MP that a successfully cast spell would.";
2406         break;
2407     }
2408 
2409     case HINT_GLOWING:
2410         text << "You've accumulated so much magical contamination that you're "
2411                 "glowing! You usually acquire magical contamination from using "
2412                 "some powerful magics, like invisibility, or from "
2413                 "miscasting spells. ";
2414 
2415         if (!player_severe_contamination())
2416         {
2417             text << "As long as the status only shows in grey nothing will "
2418                     "actually happen as a result of it, but as you continue "
2419                     "suffusing yourself with magical contamination you'll "
2420                     "eventually start glowing for real, which ";
2421         }
2422         else
2423         {
2424             text << "This normally isn't a problem as contamination slowly "
2425                     "bleeds off on its own, but it seems that you've "
2426                     "accumulated so much contamination over a short amount of "
2427                     "time that it ";
2428         }
2429         text << "can have nasty effects, such as mutating you or dealing direct "
2430                 "damage. In addition, glowing is going to make you much more "
2431                 "noticeable.";
2432         break;
2433 
2434     case HINT_YOU_RESIST:
2435         text << "There are many dangers in Crawl. Luckily, there are ways to "
2436                 "(at least partially) resist some of them, if you are "
2437                 "fortunate enough to find them. There are two basic variants "
2438                 "of resistances: the innate willpower that depends on your "
2439                 "species, grows with experience level, and protects against"
2440                 "many magical effects; and the specific resistances against "
2441                 "certain other effects, e.g. fire or draining.\n"
2442                 "You can find items in the dungeon or gain mutations that will "
2443                 "increase (or lower) one or more of your resistances. To view "
2444                 "your current set of resistances, "
2445 #ifdef USE_TILE
2446                 "<w>right-click</w> on the player avatar.";
2447 #else
2448                 "press <w>%</w>.";
2449         cmd.push_back(CMD_RESISTS_SCREEN);
2450 #endif
2451         break;
2452 
2453     case HINT_CAUGHT_IN_NET:
2454         text << "While you are held in a net, you cannot move around or engage "
2455                 "monsters in combat. Instead, any movement you take is counted "
2456                 "as an attempt to struggle free from the net.";
2457         cmd.push_back(CMD_FIRE);
2458 
2459         if (Hints.hints_type == HINT_MAGIC_CHAR)
2460         {
2461             text << " Note that casting spells is still very much possible, "
2462                     "as is using wands, scrolls and potions.";
2463         }
2464         else
2465         {
2466             text << " Note that using wands, scrolls and potions is still "
2467                     "very much possible.";
2468         }
2469         break;
2470 
2471     case HINT_YOU_SILENCE:
2472         redraw_screen();
2473         update_screen();
2474         text << "While you are silenced, you cannot cast spells, read scrolls "
2475                 "or use divine invocations. The same is true for any monster "
2476                 "within the effect radius. The field of silence (recognizable "
2477                 "by "
2478 #ifdef USE_TILE
2479                 "the special-looking frame tiles"
2480 #else
2481                 "different-coloured floor squares"
2482 #endif
2483                 ") is always centered on you and will move along with you. "
2484                 "The radius will gradually shrink, eventually making you the "
2485                 "only one affected, before the effect fades entirely.";
2486         break;
2487 
2488     case HINT_LOAD_SAVED_GAME:
2489     {
2490         text << "Welcome back! If it's been a while, you may want to refresh "
2491                 "your memory.\nYour <w>%</w>nventory, ";
2492         cmd.push_back(CMD_DISPLAY_INVENTORY);
2493 
2494         vector<const char *> listed;
2495         if (you.spell_no > 0)
2496         {
2497             listed.push_back("your spells (<w>%?</w>)");
2498             cmd.push_back(CMD_CAST_SPELL);
2499         }
2500         if (!your_talents(false).empty())
2501         {
2502             listed.push_back("your <w>%</w>bilities");
2503             cmd.push_back(CMD_USE_ABILITY);
2504         }
2505         if (Hints.hints_type != HINT_MAGIC_CHAR || you.how_mutated())
2506         {
2507             listed.push_back("your set of mutations (<w>%</w>)");
2508             cmd.push_back(CMD_DISPLAY_MUTATIONS);
2509         }
2510         if (!you_worship(GOD_NO_GOD))
2511         {
2512             listed.push_back("your religious standing (<w>%</w>)");
2513             cmd.push_back(CMD_DISPLAY_RELIGION);
2514         }
2515 
2516         listed.push_back("the message history (<w>%</w>)");
2517         listed.push_back("the character overview screen (<w>%</w>)");
2518         listed.push_back("the dungeon overview screen (<w>%</w>)");
2519         text << comma_separated_line(listed.begin(), listed.end())
2520              << " are good things to check.";
2521         cmd.push_back(CMD_REPLAY_MESSAGES);
2522         cmd.push_back(CMD_RESISTS_SCREEN);
2523         cmd.push_back(CMD_DISPLAY_OVERMAP);
2524         break;
2525     }
2526     case HINT_AUTOPICKUP_THROWN:
2527         text << "When stepping on items you've thrown, they will be "
2528                 "picked up automatically.";
2529         break;
2530     case HINT_GAINED_SPELLCASTING:
2531         text << "As your Spellcasting skill increases, you will be able to "
2532              << "memorise more spells, and will suffer "
2533              << "somewhat fewer failures when you cast them.\n"
2534              << "Press <w>%</w> "
2535 #ifdef USE_TILE_LOCAL
2536              << "(or click on the <w>skill button</w> in the command panel) "
2537 #endif
2538              << "to have a look at your skills and manage their training.";
2539         cmd.push_back(CMD_DISPLAY_SKILLS);
2540         break;
2541     case HINT_FUMBLING_SHALLOW_WATER:
2542         text << "Fighting in shallow water will sometimes cause you to slip "
2543                 "and fumble your attack. If possible, try to fight on "
2544                 "firm ground.";
2545         break;
2546     case HINT_CLOUD_WARNING:
2547         text << "Rather than step into this cloud and hurt yourself, you "
2548                 "should either wait for a few turns to see if it "
2549                 "vanishes (with <w>%</w>), or just step around it.";
2550         cmd.push_back(CMD_WAIT);
2551         break;
2552     case HINT_ANIMATE_CORPSE_SKELETON:
2553         text << "Animate Skeleton works on the corpse of any monster that has "
2554                 "a skeleton inside.";
2555         break;
2556     default:
2557         text << "You've found something new (but I don't know what)!";
2558     }
2559 
2560     if (!text.str().empty())
2561     {
2562         string output = text.str();
2563         if (!cmd.empty())
2564             insert_commands(output, cmd);
2565         else
2566             output = untag_tiles_console(output); // also in insert_commands
2567         mprf(MSGCH_TUTORIAL, "%s", output.c_str());
2568 
2569         stop_running();
2570     }
2571 }
2572 
hints_abilities_info()2573 formatted_string hints_abilities_info()
2574 {
2575     ostringstream text;
2576     text << "<" << colour_to_str(channel_to_colour(MSGCH_TUTORIAL)) << ">";
2577     string broken = "This screen shows your character's set of talents. "
2578         "You can gain new abilities via certain items, through religion or by "
2579         "way of mutations. Activation of an ability usually comes at a cost, "
2580         "e.g. Magic power. Press '<w>!</w>' or '<w>?</w>' to "
2581         "toggle between ability selection and description.";
2582     linebreak_string(broken, _get_hints_cols());
2583     text << broken;
2584 
2585     text << "</" << colour_to_str(channel_to_colour(MSGCH_TUTORIAL)) << ">";
2586 
2587     return formatted_string::parse_string(text.str());
2588 }
2589 
2590 // Explains the basics of the skill screen. Don't bother the player with the
2591 // aptitude information.
hints_skills_info()2592 string hints_skills_info()
2593 {
2594     ostringstream text;
2595     text << "<" << colour_to_str(channel_to_colour(MSGCH_TUTORIAL)) << ">";
2596     string broken = "This screen shows the skill set of your character. "
2597         "The number next to the skill is your current level, the higher the "
2598         "better. <w>Training</w> displays training percentages. "
2599         "<w>Costs</w> displays relative training costs. "
2600         "<w>Targets</w> displays skill training targets. "
2601         "You can toggle which skills to train by "
2602         "pressing their slot letters. A <darkgrey>grey</darkgrey> skill "
2603         "will not be trained and ease the training of others.";
2604     text << broken;
2605     text << "</" << colour_to_str(channel_to_colour(MSGCH_TUTORIAL)) << ">";
2606 
2607     return text.str();
2608 }
2609 
hints_skill_training_info()2610 string hints_skill_training_info()
2611 {
2612     ostringstream text;
2613     text << "<" << colour_to_str(channel_to_colour(MSGCH_TUTORIAL)) << ">";
2614     string broken = "The training percentage (in <brown>brown</brown>) "
2615         "shows the relative amount of the experience gained which will be "
2616         "used to train each skill. It is automatically set depending on "
2617         "which skills you have used recently. Disabling a skill sets the "
2618         "training rate to 0.";
2619     text << broken;
2620     text << "</" << colour_to_str(channel_to_colour(MSGCH_TUTORIAL)) << ">";
2621 
2622     return text.str();
2623 }
2624 
hints_skill_costs_info()2625 string hints_skill_costs_info()
2626 {
2627     ostringstream text;
2628     text << "<" << colour_to_str(channel_to_colour(MSGCH_TUTORIAL)) << ">";
2629     string broken = "The training cost (in <cyan>cyan</cyan>) "
2630         "shows the experience cost to raise the given skill one level, "
2631         "relative to the cost of raising an aptitude zero skill from level "
2632         "zero to level one.";
2633     text << broken;
2634     text << "</" << colour_to_str(channel_to_colour(MSGCH_TUTORIAL)) << ">";
2635 
2636     return text.str();
2637 }
2638 
hints_skill_targets_info()2639 string hints_skill_targets_info()
2640 {
2641     ostringstream text;
2642     text << "<" << colour_to_str(channel_to_colour(MSGCH_TUTORIAL)) << ">";
2643     string broken = "Press the letter of a skill to set a training target. "
2644         "When the target is reached a message will appear and "
2645         "the training of the skill will be disabled.";
2646     text << broken;
2647     text << "</" << colour_to_str(channel_to_colour(MSGCH_TUTORIAL)) << ">";
2648 
2649     return text.str();
2650 }
2651 
hints_skills_description_info()2652 string hints_skills_description_info()
2653 {
2654     ostringstream text;
2655     text << "<" << colour_to_str(channel_to_colour(MSGCH_TUTORIAL)) << ">";
2656     string broken = "This screen shows the skill set of your character. "
2657                     "Press the letter of a skill to read its description, or "
2658                     "press <w>?</w> again to return to the skill selection.";
2659 
2660     linebreak_string(broken, _get_hints_cols());
2661     text << broken;
2662     text << "</" << colour_to_str(channel_to_colour(MSGCH_TUTORIAL)) << ">";
2663 
2664     return text.str();
2665 }
2666 
2667 // A short explanation of Crawl's target mode and its most important commands.
_hints_target_mode(bool spells=false)2668 static string _hints_target_mode(bool spells = false)
2669 {
2670     string result;
2671     result = "then be taken to target mode with the nearest monster or "
2672              "previous target already targeted. You can also cycle through "
2673              "all hostile monsters in sight with <w>+</w> or <w>-</w>. "
2674              "Once you're aiming at the correct monster, simply hit "
2675              "<w>f</w>, <w>Enter</w> or <w>.</w> to shoot at it. "
2676              "If you miss, <w>";
2677 
2678     command_type cmd;
2679     if (spells)
2680     {
2681         result += "%ap";
2682         cmd = CMD_CAST_SPELL;
2683     }
2684     else
2685     {
2686         result += "%f";
2687         cmd = CMD_FIRE;
2688     }
2689 
2690     result += "</w> fires at the same target again.";
2691     insert_commands(result, { cmd });
2692 
2693     return result;
2694 }
2695 
hints_memorise_info()2696 string hints_memorise_info()
2697 {
2698     // TODO: this should probably be in z or I, but adding it to the memorise
2699     // menu was easier for the moment.
2700     ostringstream text;
2701     vector<command_type> cmd;
2702     text << "<" << colour_to_str(channel_to_colour(MSGCH_TUTORIAL)) << ">";
2703     string m = "This screen shows the spells in your spell library. From here "
2704                "you can memorise spells by selecting them, as well as view "
2705                "spell descriptions, search for them, and organize them. As a "
2706                "conjurer, you start with five memorisable spells, and one "
2707                "already memorised: Magic Dart. (To view memorised spells, you "
2708                "can exit this menu and select <w>I</w>.)";
2709 
2710     if (player_has_available_spells())
2711     {
2712         m += "\n\nA spell that isn't <darkgray>grayed out</darkgray> or "
2713              "<lightred>forbidden</lightred> can be "
2714              "memorised right away by selecting it at this menu.";
2715     }
2716     else
2717     {
2718         m += "\n\nYou cannot memorise any ";
2719         m += (you.spell_no ? "more " : "");
2720         m += "spells right now. This will change as you grow in levels and "
2721              "Spellcasting proficiency. ";
2722     }
2723 
2724     if (you.spell_no)
2725     {
2726         m += "\n\nTo use magic, ";
2727 #ifdef USE_TILE
2728         m += "you can <w>left mouse click</w> on the monster you wish to "
2729              "target (or on your player character to cast a spell on "
2730              "yourself) while pressing the <w>Control key</w>, and then select "
2731              "a spell from the menu. Or you can switch to the spellcasting "
2732              "display by <w>clicking on the</w> corresponding <w>tab</w>."
2733              "\n\nAlternatively, ";
2734 #endif
2735         m += "you can press <w>%</w> and choose a spell, e.g. <w>a</w> (check "
2736              "with <w>?</w>). For attack spells you'll ";
2737         cmd.push_back(CMD_CAST_SPELL);
2738     }
2739 
2740     if (you.spell_no)
2741         m += _hints_target_mode(true);
2742     linebreak_string(m, _get_hints_cols());
2743     if (!cmd.empty())
2744         insert_commands(m, cmd);
2745     text << m;
2746 
2747     return text.str();
2748 }
2749 
_hints_abilities(const item_def & item)2750 static string _hints_abilities(const item_def& item)
2751 {
2752     string str = "To do this, ";
2753 
2754     vector<command_type> cmd;
2755     if (!item_is_equipped(item))
2756     {
2757         switch (item.base_type)
2758         {
2759         case OBJ_WEAPONS:
2760             str += "first <w>%</w>ield it";
2761             cmd.push_back(CMD_WIELD_WEAPON);
2762             break;
2763         case OBJ_ARMOUR:
2764             str += "first <w>%</w>ear it";
2765             cmd.push_back(CMD_WEAR_ARMOUR);
2766             break;
2767         case OBJ_JEWELLERY:
2768             str += "first <w>%</w>ut it on";
2769             cmd.push_back(CMD_WEAR_JEWELLERY);
2770             break;
2771         default:
2772             str += "<r>(BUG! this item shouldn't give an ability)</r>";
2773             break;
2774         }
2775         str += ", then ";
2776     }
2777     str += "enter the ability menu with <w>%</w>, and then "
2778            "choose the corresponding ability. Note that such an attempt of "
2779            "activation, especially by the untrained, is likely to fail.";
2780     cmd.push_back(CMD_USE_ABILITY);
2781 
2782     insert_commands(str, cmd);
2783     return str;
2784 }
2785 
_hints_throw_stuff(const item_def & item)2786 static string _hints_throw_stuff(const item_def &item)
2787 {
2788     string result;
2789 
2790     result  = "To do this, press <w>%</w> to fire, then ";
2791     if (item.slot)
2792     {
2793         result += "<w>";
2794         result += item.slot;
2795         result += "</w> for";
2796     }
2797     else
2798     {
2799         // you don't have this/these stuff(s) at present
2800         result += "select ";
2801     }
2802     result += (item.quantity > 1 ? "these" : "this");
2803     result += " ";
2804     result += item_base_name(item);
2805     result += (item.quantity > 1? "s" : "");
2806     result += ". You'll ";
2807     result += _hints_target_mode();
2808 
2809     insert_commands(result, { CMD_FIRE });
2810     return result;
2811 }
2812 
2813 // num_old_talents describes the number of activatable abilities you had
2814 // before putting on this item.
check_item_hint(const item_def & item,unsigned int num_old_talents)2815 void check_item_hint(const item_def &item, unsigned int num_old_talents)
2816 {
2817     if (Hints.hints_events[HINT_NEW_ABILITY_ITEM]
2818         && your_talents(false).size() > num_old_talents)
2819     {
2820         learned_something_new(HINT_NEW_ABILITY_ITEM);
2821     }
2822     else if (Hints.hints_events[HINT_ITEM_RESISTANCES]
2823              && gives_resistance(item))
2824     {
2825         learned_something_new(HINT_ITEM_RESISTANCES);
2826     }
2827 }
2828 
2829 // Explains the most important commands necessary to use an item, and mentions
2830 // special effects, etc.
hints_describe_item(const item_def & item)2831 string hints_describe_item(const item_def &item)
2832 {
2833     ostringstream ostr;
2834     ostr << "<" << colour_to_str(channel_to_colour(MSGCH_TUTORIAL)) << ">";
2835     vector<command_type> cmd;
2836 
2837     switch (item.base_type)
2838     {
2839         case OBJ_WEAPONS:
2840         {
2841             if (is_artefact(item) && item_type_known(item))
2842             {
2843                 if (gives_ability(item))
2844                 {
2845                     // You can activate it.
2846                     ostr << "When wielded, some weapons (such as this one) "
2847                             "offer certain abilities you can activate. ";
2848                     ostr << _hints_abilities(item);
2849                     break;
2850                 }
2851                 else if (gives_resistance(item))
2852                 {
2853                     // It grants a resistance.
2854                     ostr << "\nThis weapon offers its wearer protection from "
2855                             "certain sources. For an overview of your "
2856                             "resistances (among other things) press <w>%</w>"
2857 #ifdef USE_TILE
2858                             " or click on your avatar with the <w>right mouse "
2859                             "button</w>"
2860 #endif
2861                             ".";
2862                     cmd.push_back(CMD_RESISTS_SCREEN);
2863                     break;
2864                 }
2865                 else
2866                     return "";
2867             }
2868 
2869             item_def *weap = you.slot_item(EQ_WEAPON, false);
2870             bool wielded = (weap && weap->slot == item.slot);
2871 
2872             if (!wielded)
2873             {
2874                 ostr << "You can wield this weapon with <w>%</w>, or use "
2875                         "<w>%</w> to switch between the weapons in slot "
2876                         "a and b. (Use <w>%i</w> to adjust item slots.)";
2877                 cmd.push_back(CMD_WIELD_WEAPON);
2878                 cmd.push_back(CMD_WEAPON_SWAP);
2879                 cmd.push_back(CMD_ADJUST_INVENTORY);
2880 
2881                 // Weapon skill used by this weapon and the best weapon skill.
2882                 skill_type curr_wpskill = item_attack_skill(item);
2883                 skill_type best_wpskill;
2884 
2885                 // Maybe this is a launching weapon?
2886                 if (is_range_weapon(item))
2887                     best_wpskill = best_skill(SK_SLINGS, SK_THROWING);
2888                 else
2889                     best_wpskill = best_skill(SK_SHORT_BLADES, SK_STAVES);
2890 
2891                 // Maybe unarmed is better.
2892                 if (you.skills[SK_UNARMED_COMBAT] > you.skills[best_wpskill])
2893                     best_wpskill = SK_UNARMED_COMBAT;
2894 
2895                 if (you.skills[curr_wpskill] + 2 < you.skills[best_wpskill])
2896                 {
2897                     ostr << "\nHowever, you've been training in <w>"
2898                          << skill_name(best_wpskill)
2899                          << "</w> for a while, so maybe you should "
2900                             "continue training that rather than <w>"
2901                          << skill_name(curr_wpskill)
2902                          << "</w>. (Press <w>%</w> to see the skill "
2903                             "management screen for the actual numbers.)";
2904 
2905                     cmd.push_back(CMD_DISPLAY_SKILLS);
2906                 }
2907             }
2908             else // wielded weapon
2909             {
2910                 if (is_range_weapon(item))
2911                 {
2912                     ostr << "To attack a monster, ";
2913 #ifdef USE_TILE
2914                     ostr << "if you have appropriate ammo quivered you can "
2915                             "<w>left mouse click</w> on the monster while "
2916                             "prssing the <w>Shift key</w>. Alternatively, "
2917                             "you can <w>left mouse click</w> on the tile for "
2918                             "the ammo you wish to fire, and then <w>left "
2919                             "mouse click</w> on the monster.\n\n";
2920                     ostr << "To launch ammunition using the keyboard, ";
2921 #endif
2922                     ostr << "you only need to "
2923                             "<w>%</w>ire the appropriate type of ammunition. "
2924                             "You'll ";
2925                     ostr << _hints_target_mode();
2926                     cmd.push_back(CMD_FIRE);
2927                 }
2928                 else
2929                     ostr << "To attack a monster, you can simply walk into it.";
2930             }
2931 
2932             if (!item_type_known(item)
2933                 && (is_artefact(item)
2934                     || get_equip_desc(item) != ISFLAG_NO_DESC))
2935             {
2936                 ostr << "\n\nWeapons and armour that have unusual descriptions "
2937                      << "like this are much more likely to be of higher "
2938                      << "enchantment or have special properties, good or bad.";
2939 
2940                 Hints.hints_events[HINT_SEEN_RANDART] = false;
2941             }
2942             Hints.hints_events[HINT_SEEN_WEAPON] = false;
2943             break;
2944         }
2945         case OBJ_MISSILES:
2946             if (is_throwable(&you, item))
2947             {
2948                 ostr << item.name(DESC_YOUR)
2949                      << " can be <w>%</w>ired without the use of a launcher. ";
2950                 ostr << _hints_throw_stuff(item);
2951                 cmd.push_back(CMD_FIRE);
2952             }
2953             else if (is_launched(&you, you.weapon(), item) == launch_retval::LAUNCHED)
2954             {
2955                 ostr << "As you're already wielding the appropriate launcher, "
2956                         "you can simply ";
2957 #ifdef USE_TILE
2958                 ostr << "<w>left mouse click</w> on the monster you want "
2959                         "to hit while pressing the <w>Shift key</w>. "
2960                         "Alternatively, you can <w>left mouse click</w> on "
2961                         "this tile of the ammo you want to fire, and then "
2962                         "<w>left mouse click</w> on the monster you want "
2963                         "to hit.\n\n"
2964 
2965                         "To launch this ammo using the keyboard, you can "
2966                         "simply ";
2967 #endif
2968 
2969                 ostr << "<w>%</w>ire "
2970                      << (item.quantity > 1 ? "these" : "this")
2971                      << " " << item.name(DESC_BASENAME)
2972                      << (item.quantity > 1? "s" : "")
2973                      << ". You'll ";
2974                 ostr << _hints_target_mode();
2975                 cmd.push_back(CMD_FIRE);
2976             }
2977             else
2978             {
2979                 ostr << "To shoot "
2980                      << (item.quantity > 1 ? "these" : "this")
2981                      << " " << item.name(DESC_BASENAME)
2982                      << (item.quantity > 1? "s" : "")
2983                      << ", first you need to <w>%</w>ield an appropriate "
2984                         "launcher.";
2985                 cmd.push_back(CMD_WIELD_WEAPON);
2986             }
2987             Hints.hints_events[HINT_SEEN_MISSILES] = false;
2988             break;
2989 
2990         case OBJ_ARMOUR:
2991         {
2992             bool wearable = true;
2993             if (you.get_innate_mutation_level(MUT_HORNS) > 0
2994                 && is_hard_helmet(item))
2995             {
2996                 ostr << "Because of your horns you cannot wear helmets. "
2997                         "(Press <w>%</w> to see a list of your mutations and "
2998                         "innate abilities.)";
2999                 cmd.push_back(CMD_DISPLAY_MUTATIONS);
3000                 wearable = false;
3001             }
3002             else if (item.sub_type == ARM_BARDING)
3003             {
3004                 ostr << "Only nagas and palentongas can wear barding.";
3005                 wearable = false;
3006             }
3007             else
3008             {
3009                 ostr << "You can wear pieces of armour with <w>%</w> and take "
3010                         "them off again with <w>%</w>"
3011 #ifdef USE_TILE
3012                         ", or, alternatively, simply click on their tiles to "
3013                         "perform either action"
3014 #endif
3015                         ".";
3016                 cmd.push_back(CMD_WEAR_ARMOUR);
3017                 cmd.push_back(CMD_REMOVE_ARMOUR);
3018             }
3019 
3020             if (Hints.hints_type == HINT_MAGIC_CHAR
3021                 && get_armour_slot(item) == EQ_BODY_ARMOUR
3022                 && !is_effectively_light_armour(&item))
3023             {
3024                 ostr << "\nNote that body armour with a high encumbrance "
3025                         "rating may hinder your ability to cast spells. Light "
3026                         "armour such as robes and leather armour will be "
3027                         "generally safe for any aspiring spellcaster.";
3028             }
3029             else if (Hints.hints_type == HINT_MAGIC_CHAR
3030                      && is_shield(item))
3031             {
3032                 ostr << "\nNote that shields will hinder your ability to "
3033                         "cast spells, until you've gained enough Shields "
3034                         "skill to remove the penalty.";
3035             }
3036             else if (Hints.hints_type == HINT_RANGER_CHAR
3037                      && is_shield(item))
3038             {
3039                 ostr << "\nNote that many ranged weapons are two handed and so "
3040                         "cannot be used with a shield.";
3041             }
3042 
3043             if (!item_type_known(item)
3044                 && (is_artefact(item)
3045                     || get_equip_desc(item) != ISFLAG_NO_DESC))
3046             {
3047                 ostr << "\n\nWeapons and armour that have unusual descriptions "
3048                      << "like this are much more likely to be of higher "
3049                      << "enchantment or have special properties, good or bad.";
3050 
3051                 Hints.hints_events[HINT_SEEN_RANDART] = false;
3052             }
3053             if (wearable)
3054             {
3055                 if (gives_resistance(item))
3056                 {
3057                     ostr << "\n\nThis armour offers its wearer protection from "
3058                             "certain sources. For an overview of your"
3059                             " resistances (among other things) press <w>%</w>"
3060 #ifdef USE_TILE
3061                             " or click on your avatar with the <w>right mouse "
3062                             "button</w>"
3063 #endif
3064                             ".";
3065                     cmd.push_back(CMD_RESISTS_SCREEN);
3066                 }
3067                 if (gives_ability(item))
3068                 {
3069                     ostr << "\n\nWhen worn, some types of armour (such as "
3070                             "this one) offer certain <w>%</w>bilities you can "
3071                             "activate. ";
3072                     ostr << _hints_abilities(item);
3073                     cmd.push_back(CMD_USE_ABILITY);
3074                 }
3075             }
3076             Hints.hints_events[HINT_SEEN_ARMOUR] = false;
3077             break;
3078         }
3079         case OBJ_WANDS:
3080             ostr << "The magic within can be unleashed by evoking "
3081                     "(<w>%</w>) it.";
3082             cmd.push_back(CMD_EVOKE);
3083 #ifdef USE_TILE
3084             ostr << " Alternatively, you can 1) <w>left mouse click</w> on "
3085                     "the monster you wish to target (or your player character "
3086                     "to target yourself) while pressing the <w>";
3087 #ifdef USE_TILE_WEB
3088             ostr << "Ctrl + Shift keys";
3089 #else
3090 #if defined(UNIX) && defined(USE_TILE_LOCAL)
3091             if (!tiles.is_fullscreen())
3092               ostr << "Ctrl + Shift keys";
3093             else
3094 #endif
3095               ostr << "Alt key";
3096 #endif
3097             ostr << "</w> and pick the wand from the menu, or 2) "
3098                     "<w>left mouse click</w> on the wand tile and then "
3099                     "<w>left mouse click</w> on your target.";
3100 #endif
3101             Hints.hints_events[HINT_SEEN_WAND] = false;
3102             break;
3103 
3104         case OBJ_SCROLLS:
3105             ostr << "Press <w>%</w> to read this scroll"
3106 #ifdef USE_TILE
3107                     "or simply click on it with your <w>left mouse button</w>"
3108 #endif
3109                     ".";
3110             cmd.push_back(CMD_READ);
3111 
3112             Hints.hints_events[HINT_SEEN_SCROLL] = false;
3113             break;
3114 
3115         case OBJ_JEWELLERY:
3116         {
3117             ostr << "Jewellery can be <w>%</w>ut on or <w>%</w>emoved "
3118                     "again"
3119 #ifdef USE_TILE
3120                     ", though in Tiles, either can be done by clicking on the "
3121                     "item in your inventory"
3122 #endif
3123                     ".";
3124             cmd.push_back(CMD_WEAR_JEWELLERY);
3125             cmd.push_back(CMD_REMOVE_JEWELLERY);
3126 
3127             if (gives_resistance(item))
3128             {
3129                 ostr << "\n\nThis "
3130                      << (item.sub_type < NUM_RINGS ? "ring" : "amulet")
3131                      << " offers its wearer protection "
3132                         "from certain sources. For an overview of your "
3133                         "resistances (among other things) press <w>%</w>"
3134 #ifdef USE_TILE
3135                         " or click on your avatar with the <w>right mouse "
3136                         "button</w>"
3137 #endif
3138                         ".";
3139                 cmd.push_back(CMD_RESISTS_SCREEN);
3140             }
3141             if (gives_ability(item))
3142             {
3143                 ostr << "\n\nWhen worn, some types of jewellery (such as this "
3144                         "one) offer certain <w>%</w>bilities you can activate. ";
3145                 cmd.push_back(CMD_USE_ABILITY);
3146                 ostr << _hints_abilities(item);
3147             }
3148             Hints.hints_events[HINT_SEEN_JEWELLERY] = false;
3149             break;
3150         }
3151         case OBJ_POTIONS:
3152             ostr << "Press <w>%</w> to quaff this potion"
3153 #ifdef USE_TILE
3154                     "or simply click on it with your <w>left mouse button</w>"
3155 #endif
3156                     ".";
3157             cmd.push_back(CMD_QUAFF);
3158             Hints.hints_events[HINT_SEEN_POTION] = false;
3159             break;
3160 
3161         case OBJ_BOOKS:
3162             if (item.sub_type == BOOK_MANUAL)
3163             {
3164                 ostr << "A manual can greatly help you in training a skill. "
3165                         "After you pick one up, the skill in question will be "
3166                         "trained more efficiently and will level up faster "
3167                         "until you exhaust the manual's contents.";
3168                 cmd.push_back(CMD_READ);
3169             }
3170             else // It's a spellbook!
3171             {
3172                 ostr << "\nYou can pick up a spellbook to add its spells to "
3173                         "your spell library. (View your spell library with "
3174                         "<w>%</w>.)";
3175                 cmd.push_back(CMD_MEMORISE_SPELL);
3176             }
3177             ostr << "\n";
3178             Hints.hints_events[HINT_SEEN_SPBOOK] = false;
3179             break;
3180 
3181         case OBJ_CORPSES:
3182             Hints.hints_events[HINT_SEEN_CARRION] = false;
3183             ostr << "Skeletons and corpses can be used as components for "
3184                     "certain necromantic spells. Apart from that, they are "
3185                     "largely useless.";
3186             break;
3187 
3188        case OBJ_STAVES:
3189             ostr << "This staff can enhance your spellcasting, making spells "
3190                     "of its related spell school more powerful.";
3191 
3192             if (gives_resistance(item))
3193             {
3194                 ostr << "It also offers its wielder protection from "
3195                         "certain sources. For an overview of your "
3196                         "resistances (among other things) press <w>%</w>"
3197 #ifdef USE_TILE
3198                         " or click on your avatar with the <w>right mouse "
3199                         "button</w>"
3200 #endif
3201                         ".";
3202 
3203                 cmd.push_back(CMD_RESISTS_SCREEN);
3204             }
3205             else if (you_worship(GOD_TROG))
3206             {
3207                 ostr << "\n\nSeeing how "
3208                      << god_name(GOD_TROG, false)
3209                      << " frowns upon the use of magic, this staff will be "
3210                         "of little use to you and you might just as well "
3211                         "<w>%</w>rop it now.";
3212                 cmd.push_back(CMD_DROP);
3213             }
3214             Hints.hints_events[HINT_SEEN_STAFF] = false;
3215             break;
3216 
3217         case OBJ_MISCELLANY:
3218             ostr << "Miscellaneous items sometimes harbour magical powers "
3219                     "that can be harnessed by e<w>%</w>oking the item.";
3220             cmd.push_back(CMD_EVOKE);
3221 
3222             Hints.hints_events[HINT_SEEN_MISC] = false;
3223             break;
3224 
3225         default:
3226             return "";
3227     }
3228 
3229     ostr << "</" << colour_to_str(channel_to_colour(MSGCH_TUTORIAL)) << ">";
3230     string broken = ostr.str();
3231     if (!cmd.empty())
3232         insert_commands(broken, cmd);
3233     return broken;
3234 }
3235 
3236 // FIXME: With the new targeting system, the hints for interesting monsters
3237 //        and features ("right-click/press v for more information") are no
3238 //        longer getting displayed.
3239 //        Players might still end up e'x'aming and particularly clicking on
3240 //        but it's a lot more hit'n'miss now.
hints_pos_interesting(int x,int y)3241 bool hints_pos_interesting(int x, int y)
3242 {
3243     return cloud_at(coord_def(x, y))
3244            || _hints_feat_interesting(env.grid[x][y]);
3245 }
3246 
_hints_feat_interesting(dungeon_feature_type feat)3247 static bool _hints_feat_interesting(dungeon_feature_type feat)
3248 {
3249     // Altars and branch entrances are always interesting.
3250     return feat_is_altar(feat)
3251            || feat_is_branch_entrance(feat)
3252            || feat_is_stone_stair(feat)
3253            || feat_is_escape_hatch(feat)
3254            || feat_is_trap(feat)
3255            || feat_is_statuelike(feat);
3256 }
3257 
hints_describe_pos(int x,int y)3258 string hints_describe_pos(int x, int y)
3259 {
3260     ostringstream ostr;
3261     _hints_describe_cloud(x, y, ostr);
3262     _hints_describe_feature(x, y, ostr);
3263     if (ostr.str().empty())
3264         return "";
3265     return "\n<" + colour_to_str(channel_to_colour(MSGCH_TUTORIAL)) + ">"
3266             + ostr.str()
3267             + "</" + colour_to_str(channel_to_colour(MSGCH_TUTORIAL)) + ">";
3268 }
3269 
_hints_describe_feature(int x,int y,ostringstream & ostr)3270 static void _hints_describe_feature(int x, int y, ostringstream& ostr)
3271 {
3272     const dungeon_feature_type feat = env.grid[x][y];
3273     const coord_def            where(x, y);
3274 
3275     if (!ostr.str().empty())
3276         ostr << "\n\n";
3277 
3278     switch (feat)
3279     {
3280     case DNGN_ORCISH_IDOL:
3281     case DNGN_GRANITE_STATUE:
3282         ostr << "It's just a harmless statue - or is it?\nEven if not "
3283                 "a danger by themselves, statues often mark special "
3284                 "areas, dangerous ones or ones harbouring treasure.";
3285         break;
3286 
3287     case DNGN_TRAP_TELEPORT:
3288     case DNGN_TRAP_TELEPORT_PERMANENT:
3289     case DNGN_TRAP_ALARM:
3290     case DNGN_TRAP_ZOT:
3291 #if TAG_MAJOR_VERSION == 34
3292     case DNGN_TRAP_MECHANICAL:
3293 #endif
3294     case DNGN_TRAP_ARROW:
3295     case DNGN_TRAP_SPEAR:
3296     case DNGN_TRAP_BLADE:
3297     case DNGN_TRAP_DART:
3298     case DNGN_TRAP_BOLT:
3299     case DNGN_TRAP_NET:
3300     case DNGN_TRAP_PLATE:
3301         ostr << "These nasty constructions can cause a range of "
3302                 "unpleasant effects. You won't be able to avoid "
3303                 "tripping traps by flying over them; their magic "
3304                 "construction will cause them to be triggered anyway.";
3305         Hints.hints_events[HINT_SEEN_TRAP] = false;
3306         break;
3307 
3308     case DNGN_TRAP_SHAFT:
3309         ostr << "The dungeon contains a number of natural obstacles such "
3310                 "as shafts, which lead one to three levels down. Once you "
3311                 "know the shaft is there, you can safely step over it.\n"
3312                 "If you want to jump down there, use <w>></w> to do so. "
3313                 "Be warned that getting back here might be difficult.";
3314         Hints.hints_events[HINT_SEEN_TRAP] = false;
3315         break;
3316 
3317     case DNGN_TRAP_WEB:
3318         ostr << "Some areas of the dungeon, such as the Spider Nest, may "
3319                 "be strewn with giant webs that may ensnare you for a short "
3320                 "time. Players in Spider Form can safely navigate the webs (as "
3321                 "can incorporeal entities and various oozes).";
3322         Hints.hints_events[HINT_SEEN_WEB] = false;
3323         break;
3324 
3325     case DNGN_STONE_STAIRS_DOWN_I:
3326     case DNGN_STONE_STAIRS_DOWN_II:
3327     case DNGN_STONE_STAIRS_DOWN_III:
3328         ostr << "You can enter the next (deeper) level by following them "
3329                 "down (<w>></w>). To get back to this level again, "
3330                 "press <w><<</w> while standing on the upstairs.";
3331 #ifdef USE_TILE
3332         ostr << " In Tiles, you can achieve the same, in either direction, "
3333                 "by clicking the <w>left mouse button</w>.";
3334 #endif
3335 
3336         if (is_unknown_stair(where))
3337         {
3338             ostr << "\n\nYou have not yet passed through this particular "
3339                     "set of stairs. ";
3340         }
3341 
3342         Hints.hints_events[HINT_SEEN_STAIRS] = false;
3343         break;
3344 
3345     case DNGN_EXIT_DUNGEON:
3346         ostr << "These stairs lead out of the dungeon. Following them "
3347                 "will end the game. The only way to win is to "
3348                 "transport the fabled Orb of Zot outside.";
3349         break;
3350 
3351     case DNGN_STONE_STAIRS_UP_I:
3352     case DNGN_STONE_STAIRS_UP_II:
3353     case DNGN_STONE_STAIRS_UP_III:
3354         ostr << "You can enter the previous (shallower) level by "
3355                 "following these up (<w><<</w>). This is ideal for "
3356                 "retreating or finding a safe resting spot, since the "
3357                 "previous level will have less monsters, and monsters "
3358                 "on this level can't follow you up unless they're "
3359                 "standing right next to you. To get back to this "
3360                 "level again, press <w>></w> while standing on the "
3361                 "downstairs.";
3362 #ifdef USE_TILE
3363         ostr << " In Tiles, you can perform either action simply by "
3364                 "clicking the <w>left mouse button</w> instead.";
3365 #endif
3366         if (is_unknown_stair(where))
3367         {
3368             ostr << "\n\nYou have not yet passed through this "
3369                     "particular set of stairs. ";
3370         }
3371         Hints.hints_events[HINT_SEEN_STAIRS] = false;
3372         break;
3373 
3374     case DNGN_ESCAPE_HATCH_DOWN:
3375     case DNGN_ESCAPE_HATCH_UP:
3376         ostr << "Escape hatches can be used to quickly leave a level with "
3377                 "<w><<</w> and <w>></w>, respectively. Note that you will "
3378                 "usually be unable to return right away.";
3379 
3380         Hints.hints_events[HINT_SEEN_ESCAPE_HATCH] = false;
3381         break;
3382 
3383 #if TAG_MAJOR_VERSION == 34
3384     case DNGN_ENTER_PORTAL_VAULT:
3385         ostr << "This " << _describe_portal(where);
3386         Hints.hints_events[HINT_SEEN_PORTAL] = false;
3387         break;
3388 #endif
3389 
3390     case DNGN_CLOSED_DOOR:
3391     case DNGN_CLOSED_CLEAR_DOOR:
3392         if (!Hints.hints_explored)
3393         {
3394             ostr << "\nTo avoid accidentally opening a door you'd rather "
3395                     "remain closed during travel or autoexplore, you can "
3396                     "mark it with an exclusion from the map view "
3397                     "(<w>X</w>) with <w>ee</w> while your cursor is on the "
3398                     "grid in question. Such an exclusion will prevent "
3399                     "autotravel from ever entering that grid until you "
3400                     "remove the exclusion with another press of <w>Xe</w>.";
3401         }
3402         break;
3403 
3404     default:
3405         if (feat_is_altar(feat))
3406         {
3407             god_type altar_god = feat_altar_god(feat);
3408 
3409             // TODO: mention Gozag here?
3410             if (you_worship(GOD_NO_GOD))
3411             {
3412                 ostr << "This is your chance to join a religion! In "
3413                         "general, the gods will help their followers, "
3414                         "bestowing powers of all sorts upon them, but many "
3415                         "of them demand a life of dedication, constant "
3416                         "tributes or entertainment in return.\n";
3417                 if (altar_god == GOD_ECUMENICAL)
3418                 {
3419                     ostr << "This particular altar is so ancient that you "
3420                             "cannot make out which god it is dedicated to! "
3421                             "Converting here by pressing <w>></w> while "
3422                             "standing on the altar will enter you into service "
3423                             "of a random god, who will grant you some "
3424                             "additional piety in thanks.";
3425                 }
3426                 else
3427                 {
3428                     ostr << "You can get information about <w>"
3429                          << god_name(altar_god)
3430                          << "</w> by pressing <w>></w> while standing on the "
3431                             "altar. Before taking up the responding faith "
3432                             "you'll be asked for confirmation.";
3433                 }
3434             }
3435             else if (you_worship(altar_god))
3436             {
3437                 // If we don't have anything to say, return early.
3438                 return;
3439             }
3440             else if (altar_god == GOD_ECUMENICAL)
3441             {
3442                 ostr << "This particular altar is so ancient that you cannot "
3443                         "make out which god it is dedicated to, and "
3444                      << god_name(you.religion)
3445                      << " probably won't like it if you switch allegiance. If "
3446                         "you want to take the risk, you can convert here by "
3447                         "pressing <w>></w> while standing on the altar. Doing "
3448                         "so will enter you into service of a random god, who "
3449                         "will grant you some additional piety in thanks.";
3450             }
3451             else
3452             {
3453                 ostr << god_name(you.religion)
3454                      << " probably won't like it if you switch allegiance, "
3455                         "but having a look won't hurt: to get information "
3456                         "on <w>";
3457                 ostr << god_name(altar_god);
3458                 ostr << "</w>, press <w>></w> while standing on the "
3459                         "altar. Before taking up the responding faith (and "
3460                         "abandoning your current one!) you'll be asked for "
3461                         "confirmation."
3462                         "\nTo see your current standing with "
3463                      << god_name(you.religion)
3464                      << " press <w>^</w>"
3465 #ifdef USE_TILE
3466                         ", or click with your <w>right mouse button</w> "
3467                         "on your avatar while pressing <w>Shift</w>"
3468 #endif
3469                         ".";
3470             }
3471             Hints.hints_events[HINT_SEEN_ALTAR] = false;
3472             break;
3473         }
3474         else if (feat_is_branch_entrance(feat))
3475         {
3476             ostr << "An entryway into one of the many dungeon side branches in "
3477                     "Crawl. ";
3478             if (feat != DNGN_ENTER_TEMPLE)
3479                 ostr << "Beware, sometimes these can be deadly!";
3480             break;
3481         }
3482     }
3483 }
3484 
_hints_describe_cloud(int x,int y,ostringstream & ostr)3485 static void _hints_describe_cloud(int x, int y, ostringstream& ostr)
3486 {
3487     cloud_struct* cloud = cloud_at(coord_def(x, y));
3488     if (!cloud)
3489         return;
3490 
3491     const string cname = cloud->cloud_name(true);
3492     const cloud_type ctype = cloud->type;
3493 
3494     if (!ostr.str().empty())
3495         ostr << "\n\n";
3496 
3497     ostr << "The " << cname << " ";
3498 
3499     if (ends_with(cname, "s"))
3500         ostr << "are ";
3501     else
3502         ostr << "is ";
3503 
3504     bool need_cloud = false;
3505     if (is_harmless_cloud(ctype))
3506         ostr << "harmless. ";
3507     else if (is_damaging_cloud(ctype, true))
3508     {
3509         ostr << "probably dangerous, and you should stay out of it if you "
3510                 "can. ";
3511     }
3512     else
3513     {
3514         ostr << "currently harmless, but that could change at some point. "
3515                 "Check the overview screen (<w>%</w>) to view your "
3516                 "resistances.";
3517         need_cloud = true;
3518     }
3519 
3520     if (is_opaque_cloud(ctype))
3521     {
3522         ostr << (need_cloud? "\nThis cloud" : "It")
3523              << " is opaque. If two or more opaque clouds are between "
3524                 "you and a square, you won't be able to see anything in that "
3525                 "square.";
3526     }
3527 }
3528 
hints_monster_interesting(const monster * mons)3529 bool hints_monster_interesting(const monster* mons)
3530 {
3531     if (mons_is_unique(mons->type) || mons->type == MONS_PLAYER_GHOST)
3532         return true;
3533 
3534     // Highlighted in some way.
3535     if (_mons_is_highlighted(mons))
3536         return true;
3537 
3538     // Dangerous.
3539     return mons_threat_level(*mons) == MTHRT_NASTY;
3540 }
3541 
hints_describe_monster(const monster_info & mi,bool has_stat_desc)3542 string hints_describe_monster(const monster_info& mi, bool has_stat_desc)
3543 {
3544     ostringstream ostr;
3545     bool dangerous = false;
3546     if (mons_is_unique(mi.type))
3547     {
3548         ostr << "Did you think you were the only adventurer in the dungeon? "
3549                 "Well, you thought wrong! These unique adversaries often "
3550                 "possess skills that normal monsters wouldn't, so be "
3551                 "careful.\n\n";
3552         dangerous = true;
3553     }
3554     else if (mi.type == MONS_PLAYER_GHOST)
3555     {
3556         ostr << "The ghost of a deceased adventurer, it would like nothing "
3557                 "better than to send you the same way.\n\n";
3558         dangerous = true;
3559     }
3560     else
3561     {
3562         const int tier = mons_demon_tier(mi.type);
3563         if (tier > 0)
3564         {
3565             ostr << "This monster is a demon of the "
3566                  << (tier == 1 ? "highest" :
3567                      tier == 2 ? "second-highest" :
3568                      tier == 3 ? "middle" :
3569                      tier == 4 ? "second-lowest" :
3570                      tier == 5 ? "lowest"
3571                                : "buggy")
3572                  << " tier.\n\n";
3573         }
3574 
3575         // Don't call friendly monsters dangerous.
3576         if (!mons_att_wont_attack(mi.attitude))
3577         {
3578             if (mi.threat == MTHRT_NASTY)
3579             {
3580                 ostr << "This monster appears to be really dangerous!\n";
3581                 dangerous = true;
3582             }
3583             else if (mi.threat == MTHRT_TOUGH)
3584             {
3585                 ostr << "This monster appears to be quite dangerous.\n";
3586                 dangerous = true;
3587             }
3588         }
3589     }
3590 
3591     if (mi.is(MB_BERSERK))
3592     {
3593         ostr << "A berserking monster is bloodthirsty and fighting madly. "
3594                 "Such a blood rage makes it particularly dangerous!\n\n";
3595         dangerous = true;
3596     }
3597 
3598     // Monster is highlighted.
3599     if (mi.attitude == ATT_FRIENDLY)
3600     {
3601         ostr << "Friendly monsters will follow you around and attempt to aid "
3602                 "you in battle. You can order nearby allies by <w>t</w>alking "
3603                 "to them.";
3604 
3605         if (!mons_att_wont_attack(mi.attitude))
3606         {
3607             ostr << "\n\nHowever, it is only <w>temporarily</w> friendly, "
3608                     "and will become dangerous again when this friendliness "
3609                     "wears off.";
3610         }
3611     }
3612     else if (dangerous)
3613     {
3614         if (!Hints.hints_explored && (mi.is(MB_WANDERING) || mi.is(MB_UNAWARE)))
3615         {
3616             ostr << "You can easily mark its square as dangerous to avoid "
3617                     "accidentally entering into its field of view when using "
3618                     "auto-explore or auto-travel. To do so, enter targeting "
3619                     "mode with <w>x</w> and then press <w>e</w> when your "
3620                     "cursor is hovering over the monster's grid. Doing so will "
3621                     "mark this grid and all surrounding ones within a radius "
3622                     "of 8 as \"excluded\" ones that explore or travel modes "
3623                     "won't enter.";
3624         }
3625         else
3626         {
3627             ostr << "This might be a good time to run away";
3628 
3629             if (you_worship(GOD_TROG) && you.can_go_berserk())
3630                 ostr << " or apply your Berserk <w>a</w>bility";
3631             ostr << ".";
3632         }
3633     }
3634     else if (Options.stab_brand != CHATTR_NORMAL
3635              && mi.is(MB_STABBABLE))
3636     {
3637         ostr << "Apparently it has not noticed you - yet. Note that you do "
3638                 "not have to engage every monster you meet. Sometimes, "
3639                 "discretion is the better part of valour.";
3640     }
3641     else if (Options.may_stab_brand != CHATTR_NORMAL
3642              && mi.is(MB_DISTRACTED))
3643     {
3644         ostr << "Apparently it has been distracted by something. You could "
3645                 "use this opportunity to sneak up on this monster - or to "
3646                 "sneak away.";
3647     }
3648 
3649     if (!dangerous && !has_stat_desc)
3650     {
3651         ostr << "\nThis monster doesn't appear to have any resistances or "
3652                 "susceptibilities. It cannot fly and is of average speed. "
3653                 "Examining other, possibly more high-level monsters can give "
3654                 "important clues as to how to deal with them.";
3655     }
3656 
3657     if (ostr.str().empty())
3658         return "";
3659     return "\n<" + colour_to_str(channel_to_colour(MSGCH_TUTORIAL)) + ">"
3660             + ostr.str()
3661             + "</" + colour_to_str(channel_to_colour(MSGCH_TUTORIAL)) + ">";
3662 }
3663 
hints_observe_cell(const coord_def & gc)3664 void hints_observe_cell(const coord_def& gc)
3665 {
3666     if (feat_is_escape_hatch(env.grid(gc)))
3667         learned_something_new(HINT_SEEN_ESCAPE_HATCH, gc);
3668     else if (feat_is_branch_entrance(env.grid(gc)))
3669         learned_something_new(HINT_SEEN_BRANCH, gc);
3670     else if (is_feature('>', gc))
3671         learned_something_new(HINT_SEEN_STAIRS, gc);
3672     else if (is_feature('_', gc))
3673         learned_something_new(HINT_SEEN_ALTAR, gc);
3674     else if (is_feature('^', gc))
3675         learned_something_new(HINT_SEEN_TRAP, gc);
3676     else if (feat_is_runed(env.grid(gc)))
3677         learned_something_new(HINT_SEEN_RUNED_DOOR, gc);
3678     else if (feat_is_door(env.grid(gc)))
3679         learned_something_new(HINT_SEEN_DOOR, gc);
3680     else if (env.grid(gc) == DNGN_ENTER_SHOP)
3681         learned_something_new(HINT_SEEN_SHOP, gc);
3682     else if (feat_is_portal_entrance(env.grid(gc)))
3683         learned_something_new(HINT_SEEN_PORTAL, gc);
3684 
3685     const int it = you.visible_igrd(gc);
3686     if (it != NON_ITEM)
3687     {
3688         const item_def& item(env.item[it]);
3689 
3690         if (Options.feature_item_brand != CHATTR_NORMAL
3691             && (is_feature('>', gc) || is_feature('<', gc)))
3692         {
3693             learned_something_new(HINT_STAIR_BRAND, gc);
3694         }
3695         else if (Options.trap_item_brand != CHATTR_NORMAL
3696                  && is_feature('^', gc))
3697         {
3698             learned_something_new(HINT_TRAP_BRAND, gc);
3699         }
3700         else if (Options.heap_brand != CHATTR_NORMAL && item.link != NON_ITEM)
3701             learned_something_new(HINT_HEAP_BRAND, gc);
3702     }
3703 }
3704 
tutorial_msg(const char * key,bool end)3705 void tutorial_msg(const char *key, bool end)
3706 {
3707     string text = getHintString(key);
3708     if (text.empty())
3709         return mprf(MSGCH_ERROR, "Error, no message for '%s'.", key);
3710 
3711     _replace_static_tags(text);
3712     text = untag_tiles_console(text);
3713 
3714     if (end)
3715         screen_end_game(text);
3716 
3717     // "\n" to preserve indented parts, the rest is unwrapped, or split into
3718     // paragraphs by "\n\n", split_string() will ignore the empty line.
3719     for (const string &chunk : split_string("\n", text, false))
3720         mprf(MSGCH_TUTORIAL, "%s", chunk.c_str());
3721 
3722     // tutorial_msg can get called in an vault epilogue during --builddb,
3723     // which can lead to a crash on tiles builds in runrest::stop as
3724     // there is no `tiles`. This seemed like the best place to fix this.
3725     #ifdef USE_TILE_LOCAL
3726     if (!crawl_state.tiles_disabled)
3727     #endif
3728         stop_running();
3729 }
3730