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