/* * File: tutorial.cc * Summary: A tutorial mode as an introduction on how to play Dungeon Crawl. * Written by: j-p-e-g * * Created on 2007-01-11. */ #include "AppHdr.h" #include "cio.h" #include #include #include "tutorial.h" #include "abl-show.h" #include "artefact.h" #include "cloud.h" #include "colour.h" #include "coordit.h" #include "command.h" #include "decks.h" #include "describe.h" #include "food.h" #include "format.h" #include "fprop.h" #include "invent.h" #include "itemname.h" #include "itemprop.h" #include "items.h" #include "kills.h" #include "menu.h" #include "message.h" #include "misc.h" #include "mon-behv.h" #include "mon-pick.h" #include "mon-util.h" #include "mutation.h" #include "newgame.h" #include "options.h" #include "jobs.h" #include "player.h" #include "random.h" #include "religion.h" #include "shopping.h" #include "showsymb.h" #include "skills2.h" #include "species.h" #include "spl-book.h" #include "stuff.h" #include "env.h" #include "tags.h" #include "terrain.h" #ifdef USE_TILE #include "tiles.h" #endif #include "travel.h" #include "view.h" #include "viewchar.h" #include "viewgeom.h" static species_type _get_tutorial_species(unsigned int type); static job_type _get_tutorial_job(unsigned int type); static bool _tutorial_feat_interesting(dungeon_feature_type feat); static void _tutorial_describe_disturbance(int x, int y); static void _tutorial_describe_cloud(int x, int y); static void _tutorial_describe_feature(int x, int y); static bool _water_is_disturbed(int x, int y); //#define TUTORIAL_DEBUG #define TUTORIAL_VERSION 10 static int _get_tutorial_cols() { #ifdef USE_TILE return crawl_view.msgsz.x; #else int ncols = get_number_of_cols(); return (ncols > 80? 80 : ncols); #endif } tutorial_state Tutorial; void save_tutorial(writer& outf) { marshallLong( outf, TUTORIAL_VERSION); marshallShort( outf, Tutorial.tutorial_type); for (long i = 0; i < TUT_EVENTS_NUM; ++i) marshallBoolean( outf, Tutorial.tutorial_events[i] ); } void load_tutorial(reader& inf) { Tutorial.tutorial_left = 0; int version = unmarshallLong(inf); if (version != TUTORIAL_VERSION) return; Tutorial.tutorial_type = unmarshallShort(inf); for (long i = 0; i < TUT_EVENTS_NUM; ++i) { Tutorial.tutorial_events[i] = unmarshallBoolean(inf); Tutorial.tutorial_left += Tutorial.tutorial_events[i]; } } // Override init file definition for some options. void init_tutorial_options() { if (!Tutorial.tutorial_left) return; Options.delay_message_clear = false; Options.auto_list = true; #ifdef USE_TILE Options.tile_tag_pref = TAGPREF_TUTORIAL; #endif } // Tutorial selection screen and choice. bool pick_tutorial() { clrscr(); cgotoxy(1,1); formatted_string::parse_string( "You must be new here indeed!" EOL EOL "You can be:" EOL EOL).display(); textcolor( LIGHTGREY ); for (int i = 0; i < TUT_TYPES_NUM; i++) print_tutorial_menu(i); formatted_string::parse_string( EOL "SPACE - Back to job selection; " "Bksp - Back to species selection; X - Quit" EOL "* - Random tutorial" "" EOL).display(); while (true) { char keyn = c_getch(); // Random choice. if (keyn == '*' || keyn == '+' || keyn == '!' || keyn == '#') keyn = 'a' + random2(TUT_TYPES_NUM); // Choose character for tutorial game and set starting values. if (keyn >= 'a' && keyn <= 'a' + TUT_TYPES_NUM - 1) { Tutorial.tutorial_type = keyn - 'a'; you.species = _get_tutorial_species(Tutorial.tutorial_type); you.char_class = _get_tutorial_job(Tutorial.tutorial_type); // Activate all triggers. // This is rather backwards: If (true) an event still needs to be // triggered, if (false) the relevant message was already printed. Tutorial.tutorial_events.init(true); Tutorial.tutorial_left = TUT_EVENTS_NUM; // Used to compare which fighting means was used most often. // XXX: This gets reset with every save, which seems odd. // On the other hand, it's precisely between saves that // players are most likely to forget these. Tutorial.tut_spell_counter = 0; Tutorial.tut_throw_counter = 0; Tutorial.tut_melee_counter = 0; Tutorial.tut_berserk_counter = 0; // Store whether explore, stash search or travelling was used. // XXX: Also not stored across save games. Tutorial.tut_explored = true; Tutorial.tut_stashes = true; Tutorial.tut_travel = true; // For occasional healing reminders. Tutorial.tut_last_healed = 0; // Did the player recently see a monster turn invisible? Tutorial.tut_seen_invisible = 0; Options.random_pick = false; if (!Options.book || Options.book == SBT_SUMM) Options.book = SBT_RANDOM; Options.weapon = WPN_HAND_AXE; // easiest choice for fighters return (true); } if (keyn == CK_BKSP || keyn == ' ' || keyn == ESCAPE) { // In this case, undo previous choices. you.species = SP_UNKNOWN; you.char_class = JOB_UNKNOWN; Options.race = 0; Options.cls = 0; } switch (keyn) { case CK_BKSP: case ESCAPE: return (false); case ' ': return (false); case 'X': cprintf(EOL "Goodbye!"); end(0); return (false); } } return (false); } void tutorial_load_game() { if (!Tutorial.tutorial_left) return; learned_something_new(TUT_LOAD_SAVED_GAME); // Reinitialise counters for explore, stash search and travelling. Tutorial.tut_explored = Tutorial.tutorial_events[TUT_AUTO_EXPLORE]; Tutorial.tut_stashes = true; Tutorial.tut_travel = true; } void print_tutorial_menu(unsigned int type) { char letter = 'a' + type; char desc[100]; switch (type) { case TUT_BERSERK_CHAR: strcpy(desc, "(Melee oriented character with divine support)"); break; case TUT_MAGIC_CHAR: strcpy(desc, "(Magic oriented character)"); break; case TUT_RANGER_CHAR: strcpy(desc, "(Ranged fighter)"); break; default: // no further choices strcpy(desc, "(erroneous character)"); break; } cprintf("%c - %s %s %s" EOL, letter, species_name(_get_tutorial_species(type), 1).c_str(), get_class_name(_get_tutorial_job(type)), desc); } static species_type _get_tutorial_species(unsigned int type) { switch (type) { case TUT_BERSERK_CHAR: return SP_MINOTAUR; case TUT_MAGIC_CHAR: return SP_DEEP_ELF; case TUT_RANGER_CHAR: return SP_CENTAUR; default: // Use something fancy for debugging. return SP_KENKU; } } static job_type _get_tutorial_job(unsigned int type) { switch (type) { case TUT_BERSERK_CHAR: return JOB_BERSERKER; case TUT_MAGIC_CHAR: return JOB_CONJURER; case TUT_RANGER_CHAR: return JOB_HUNTER; default: // Use something fancy for debugging. return JOB_NECROMANCER; } } // Converts all secret doors in a fixed radius around the player's starting // position into normal closed doors. void tutorial_zap_secret_doors() { for (radius_iterator ri(you.pos(), 10, true, false); ri; ++ri) if (grd(*ri) == DNGN_SECRET_DOOR) grd(*ri) = DNGN_CLOSED_DOOR; } // Prints the tutorial welcome screen. static formatted_string _tut_starting_info(unsigned int width) { std::ostringstream istr; istr << "Welcome to Dungeon Crawl!" EOL EOL << "Your object is to lead a " << species_name(you.species, 1) << " " << you.class_name << " safely through the depths of the dungeon, retrieving the " "fabled Orb of Zot and returning it to the surface. " "In the beginning, however, let discovery be your " "main goal. Try to delve as deeply as possible but beware; " "death lurks around every corner." EOL EOL "For the moment, just remember the following keys " "and their functions:" EOL " ?\? - shows the items and the commands" EOL " S - saves the game, to be resumed later " "(but note that death is permanent)" EOL " x - examines something in your vicinity" EOL EOL "This tutorial will help you play Crawl without reading any " "documentation. If you feel intrigued, there is more information " "available in the following files from the docs/ folder (all of " "which can also be read in-game):" EOL " quickstart.txt - " "A very short guide to Crawl." EOL " crawl_manual.txt - " "This contains all details on species, magic, skills, etc." EOL " options_guide.txt - " "Crawl's interface is highly configurable. This document " EOL " explains all the options." EOL EOL "Press Space to proceed to the basics " "(the screen division and movement)." EOL "Press Esc to fast forward to the game start."; std::string broken = istr.str(); linebreak_string2(broken, width); return formatted_string::parse_block(broken); } #ifdef TUTORIAL_DEBUG static std::string _tut_debug_list(int event) { switch (event) { case TUT_SEEN_FIRST_OBJECT: return "seen first object"; case TUT_SEEN_POTION: return "seen first potion"; case TUT_SEEN_SCROLL: return "seen first scroll"; case TUT_SEEN_WAND: return "seen first wand"; case TUT_SEEN_SPBOOK: return "seen first spellbook"; case TUT_SEEN_WEAPON: return "seen first weapon"; case TUT_SEEN_MISSILES: return "seen first missiles"; case TUT_SEEN_ARMOUR: return "seen first armour"; case TUT_SEEN_RANDART: return "seen first random artefact"; case TUT_SEEN_FOOD: return "seen first food"; case TUT_SEEN_CARRION: return "seen first corpse"; case TUT_SEEN_GOLD: return "seen first pile of gold"; case TUT_SEEN_JEWELLERY: return "seen first jewellery"; case TUT_SEEN_MISC: return "seen first misc. item"; case TUT_SEEN_MONSTER: return "seen first monster"; case TUT_SEEN_ZERO_EXP_MON: return "seen first zero experience monster"; case TUT_SEEN_TOADSTOOL: return "seen first toadstool"; case TUT_SEEN_STAIRS: return "seen first stairs"; case TUT_SEEN_ESCAPE_HATCH: return "seen first escape hatch"; case TUT_SEEN_BRANCH: return "seen first branch entrance"; case TUT_SEEN_PORTAL: return "seen first portal vault entrance"; case TUT_SEEN_TRAP: return "encountered a trap"; case TUT_SEEN_ALTAR: return "seen an altar"; case TUT_SEEN_SHOP: return "seen a shop"; case TUT_SEEN_DOOR: return "seen a closed door"; case TUT_SEEN_SECRET_DOOR: return "found a secret door"; case TUT_KILLED_MONSTER: return "killed first monster"; case TUT_NEW_LEVEL: return "gained a new level"; case TUT_SKILL_RAISE: return "raised a skill"; case TUT_YOU_ENCHANTED: return "caught an enchantment"; case TUT_YOU_SICK: return "became sick"; case TUT_YOU_POISON: return "were poisoned"; case TUT_YOU_ROTTING: return "were rotting"; case TUT_YOU_CURSED: return "had something cursed"; case TUT_YOU_HUNGRY: return "felt hungry"; case TUT_YOU_STARVING: return "were starving"; case TUT_MAKE_CHUNKS: return "learned about chunks"; case TUT_OFFER_CORPSE: return "learned about sacrifice"; case TUT_MULTI_PICKUP: return "read about pickup menu"; case TUT_HEAVY_LOAD: return "were encumbered"; case TUT_ROTTEN_FOOD: return "carried rotten food"; case TUT_NEED_HEALING: return "needed healing"; case TUT_NEED_POISON_HEALING: return "needed healing for poison"; case TUT_INVISIBLE_DANGER: return "encountered an invisible foe"; case TUT_NEED_HEALING_INVIS: return "had to heal near an unseen monster"; case TUT_ABYSS: return "was cast into the Abyss"; case TUT_POSTBERSERK: return "learned about Berserk after-effects"; case TUT_RUN_AWAY: return "were told to run away"; case TUT_RETREAT_CASTER: return "were told to retreat as a caster"; case TUT_SHIFT_RUN: return "learned about shift-run"; case TUT_MAP_VIEW: return "learned about the level map"; case TUT_AUTO_EXPLORE: return "learned about auto-explore"; case TUT_DONE_EXPLORE: return "explored a level"; case TUT_AUTO_EXCLUSION: return "learned about exclusions"; case TUT_YOU_MUTATED: return "caught a mutation"; case TUT_NEW_ABILITY_GOD: return "gained a divine ability"; case TUT_NEW_ABILITY_MUT: return "gained a mutation-granted ability"; case TUT_NEW_ABILITY_ITEM: return "gained an item-granted ability"; case TUT_WIELD_WEAPON: return "wielded an unsuitable weapon"; case TUT_FLEEING_MONSTER: return "made a monster flee"; case TUT_MONSTER_BRAND: return "learned about colour brandings"; case TUT_MONSTER_FRIENDLY: return "seen first friendly monster"; case TUT_MONSTER_SHOUT: return "experienced first shouting monster"; case TUT_CONVERT: return "converted to a god"; case TUT_GOD_DISPLEASED: return "piety ran low"; case TUT_EXCOMMUNICATE: return "excommunicated by a god"; case TUT_SPELL_MISCAST: return "spell miscast"; case TUT_SPELL_HUNGER: return "spell casting caused hunger"; case TUT_GLOWING: return "player glowing from contamination"; case TUT_STAIR_BRAND: return "saw stairs with objects on it"; case TUT_HEAP_BRAND: return "saw heap of objects"; case TUT_TRAP_BRAND: return "saw trap with objects on it"; case TUT_YOU_RESIST: return "resisted some magic"; case TUT_CAUGHT_IN_NET: return "were caught in a net"; case TUT_LOAD_SAVED_GAME: return "restored a saved game"; case TUT_GAINED_MAGICAL_SKILL: return "gained a new magical skill"; case TUT_CHOOSE_STAT: return "could choose a stat"; case TUT_CAN_BERSERK: return "were told to Berserk"; default: return "faced a bug"; } } // Lists all triggerable events and whether they actually were triggered // at some point, at game start or reload. static formatted_string _tutorial_debug() { std::string result; bool lbreak = false; snprintf(info, INFO_SIZE, "Tutorial Debug Screen"); int i = _get_tutorial_cols()/2-1 - strlen(info) / 2; result += std::string(i, ' '); result += ""; result += info; result += "" EOL EOL; result += ""; for (i = 0; i < TUT_EVENTS_NUM; i++) { snprintf(info, INFO_SIZE, "%d: %s (%s)", i, _tut_debug_list(i).c_str(), Tutorial.tutorial_events[i] ? "true" : "false"); result += info; // Break text into 2 columns where possible. if (strlen(info) >= _get_tutorial_cols()/2 || lbreak) { result += EOL; lbreak = false; } else { result += std::string(_get_tutorial_cols()/2-1 - strlen(info), ' '); lbreak = true; } } result += "" EOL EOL; snprintf(info, INFO_SIZE, "tutorial_left: %d\n", Tutorial.tutorial_left); result += info; result += EOL; snprintf(info, INFO_SIZE, "You are a %s %s, and the tutorial will reflect " "that.", species_name(you.species, 1), you.class_name); result += info; return formatted_string::parse_string(result); } #endif // debug #ifndef USE_TILE static formatted_string _tutorial_map_intro() { std::string result; result = "<"; result += colour_to_str(channel_to_colour(MSGCH_TUTORIAL)); result += ">"; result += "What you see here is the typical Crawl screen. The upper left " "map shows your hero as the @ in the center. The parts " "of the map you remember but cannot currently see will be greyed " "out."; result += "" EOL; result += " --more-- " "Press Escape to skip the basics."; linebreak_string2(result, _get_tutorial_cols()); return formatted_string::parse_block(result, false); } #endif static void _tutorial_stats_intro() { std::ostringstream istr; #ifdef USE_TILE istr << "To the upper right, important properties of the character are " "displayed. The most basic one is Health, shown as " "Health: " << you.hp << "/" << you.hp_max << " " "and meaning current out of maximum health points. When Health " "drops to zero, you die." EOL "Magic: " << you.magic_points << "/" << you.max_magic_points << " represents your energy for casting spells, although other " "actions often draw from Magic, too." EOL "Strength, Intelligence, Dexterity below " "below provide an all-around account of the character's " "attributes. Don't worry about the rest for now."; formatted_message_history(istr.str(), MSGCH_TUTORIAL, 0, _get_tutorial_cols()); #else // Note: must fill up everything to override the map istr << "<" << colour_to_str(channel_to_colour(MSGCH_TUTORIAL)) << ">" << "To the right, important properties \n" "of the character are displayed. The \n" "most basic one is Health, shown as \n" "Health: " << you.hp << "/" << you.hp_max << " " "and meaning current \n" "out of maximum health points. When \n" "Health drops to zero, you die. \n" "Magic: " << you.magic_points << "/" << you.max_magic_points << " represents your energy \n" "for casting spells, although other \n" "actions often draw from Magic, too. \n" "Strength, Intelligence, Dexterity \n" "below provide an all-around account \n" "of the character's attributes. \n" "Don't worry about the rest for now. \n" << "" " \n" " --more-- " "Press Escape to skip the basics.\n" " \n" " \n"; formatted_string::parse_block(istr.str(), false).display(); #endif } static void _tutorial_message_intro() { std::string result; result = "This lower " #ifdef USE_TILE "left " #endif "part of the screen is reserved for messages. " "Everything related to the tutorial is shown in this colour. If " "you missed something, previous messages can be read again with " "Ctrl-P" #ifdef USE_TILE " or by clicking into the message area" #endif "." EOL; #ifdef USE_TILE result += EOL; result += "To the bottom right of the screen is a set of items. " "The top four lines are the items in your inventory, and " "the bottom two are items underneath you on the floor." EOL; #endif result += " --more-- " "Press Escape to skip the basics."; mesclr(); formatted_message_history(result, MSGCH_TUTORIAL, 0, _get_tutorial_cols()); } static void _tutorial_movement_info() { std::string text = "To move your character, use the numpad; try Numlock both on and off. " "If your system has no number pad, or if you are familiar with the vi " "keys, movement is also possible with hjklyubn. " #ifdef USE_TILE "You can also move by clicking somewhere on the map. If this is " "considered safe, i.e. there are no monsters around, you'll move " "towards the chosen square." #endif EOL "A basic command list can be found under ?\?, and the most " "important commands will be explained to you as it becomes necessary."; mesclr(); formatted_message_history(text, MSGCH_TUTORIAL, 0, _get_tutorial_cols()); } // copied from display_mutations and adapted void tut_starting_screen() { #ifndef USE_TILE int y_pos; #endif int MAX_INFO = 4; #ifdef TUTORIAL_DEBUG MAX_INFO = 5; // add tutorial_debug #endif char ch; int i; for (i = 0; i <= MAX_INFO; i++) { #ifndef USE_TILE // Map window (starts at 1) or message window (starts at 18). // FIXME: This should be done more cleanly using the // crawl_view settings! y_pos = (i == 1 || i == 3) ? 18 : 1; cgotoxy(1, y_pos); #endif if (i == 0) clrscr(); int width = _get_tutorial_cols(); #ifdef USE_TILE // use a more sensible screen width if (width < 80 && width < crawl_view.msgsz.x + crawl_view.hudsz.x) width = crawl_view.msgsz.x + crawl_view.hudsz.x; if (width > 80) width = 80; #endif if (i == 0) _tut_starting_info(width).display(); else if (i == 1) #ifdef USE_TILE // Skip map explanation for Tiles. continue; #else _tutorial_map_intro().display(); #endif else if (i == 2) _tutorial_stats_intro(); else if (i == 3) _tutorial_message_intro(); else if (i == 4) _tutorial_movement_info(); else { #ifdef TUTORIAL_DEBUG clrscr(); #ifndef USE_TILE cgotoxy(1,y_pos); #endif _tutorial_debug().display(); #else continue; #endif } if (i < MAX_INFO) { #ifndef USE_TILE ch = c_getch(); #else mouse_control mc(MOUSE_MODE_MORE); ch = getch(); #endif redraw_screen(); if (ch == ESCAPE) break; } } if (i >= MAX_INFO) more(); mesclr(); } // Once a tutorial character dies, offer some playing hints. void tutorial_death_screen() { Tutorial.tutorial_left = 0; std::string text; mpr( "Condolences! Your character's premature death is a sad, but " "common occurrence in Crawl. Rest assured that with diligence and " "playing experience your characters will last longer.", MSGCH_TUTORIAL); mpr( "Perhaps the following advice can improve your playing style:", MSGCH_TUTORIAL); more(); if (Tutorial.tutorial_type == TUT_MAGIC_CHAR && Tutorial.tut_spell_counter < Tutorial.tut_melee_counter ) { text = "As a Conjurer your main weapon should be offensive magic. Cast " "spells more often! Remember to rest when your Magic is low."; } else if (you.religion == GOD_TROG && Tutorial.tut_berserk_counter <= 3 && !you.duration[DUR_EXHAUSTED]) { text = "Don't forget to go berserk when fighting particularly " "difficult foes. It is risky, but makes you faster and beefier."; if (you.hunger_state <= HS_HUNGRY) { text += " Berserking is impossible while hungry or worse, so make " "sure to keep some food with you that you can eat when you " "need to go berserk."; } } else if (Tutorial.tutorial_type == TUT_RANGER_CHAR && 2*Tutorial.tut_throw_counter < Tutorial.tut_melee_counter ) { text = "Your bow and arrows are extremely powerful against distant " "monsters. Be sure to collect all arrows lying around in the " "dungeon."; } else { int hint = random2(6); bool skip_first_hint = false; // If a character has been unusually busy with projectiles and spells // give some other hint rather than the first one. if (hint == 0 && Tutorial.tut_throw_counter + Tutorial.tut_spell_counter >= Tutorial.tut_melee_counter) { hint = random2(5) + 1; skip_first_hint = true; } // FIXME: The hints below could be somewhat less random, so that e.g. // the message for fighting several monsters in a corridor only happens // if there's more than one monster around and you're not in a corridor, // or the one about using consumable objects only if you actually have // any (useful or unidentified) scrolls/wands/potions. if (hint == 5) { std::vector visible = get_nearby_monsters(false, true, true, false); if (visible.size() < 2) { if (skip_first_hint) hint = random2(4) + 1; else hint = random2(5); } } switch (hint) { case 0: text = "Always consider using projectiles, wands or spells before " "engaging monsters in close combat."; break; case 1: text = "Learn when to run away from things you can't handle - this " "is important! It is often wise to skip a particularly " "dangerous level. But don't overdo this as monsters will " "only get harder the deeper you delve."; break; case 2: text = "Rest between encounters, if possible in an area already " "explored and cleared of monsters. In Crawl, searching and " "resting are one and the same. To search for one turn, " "press s, ., delete or " "keypad-5. Pressing 5 or " "shift-and-keypad-5" #ifdef USE_TILE ", or clicking into the stat area" #endif " will let you rest for a longer time (you will stop " "resting after 100 turns, or when fully healed)."; break; case 3: text = "Remember to use those scrolls, potions or wands you've " "found. Very often, you cannot expect to identify " "everything with the scroll only. Learn to improvise: " "identify through usage."; break; case 4: text = "If a particular encounter feels overwhelming don't " "forget to use emergency items early on. A scroll of " "teleportation or a potion of speed can really save your " "bacon."; break; case 5: text = "Never fight more than one monster, if you can help it. " "Always back into a corridor so that they are forced to " "fight you one on one."; break; default: text = "Sorry, no hint this time, though there should have been " "one."; } } formatted_message_history(text, MSGCH_TUTORIAL, 0, _get_tutorial_cols()); more(); mpr( "See you next game!", MSGCH_TUTORIAL); Tutorial.tutorial_events.init(false); } // If a character survives until Xp 7, the tutorial is declared finished // and they get a more advanced playing hint, depending on what they might // know by now. void tutorial_finished() { std::string text; Tutorial.tutorial_left = 0; text = "Congrats! You survived until the end of this tutorial - be sure " "to try the other ones as well. Note that the command help screen " "(?\?) will look very different from now on. Here's a last " "playing hint:"; formatted_message_history(text, MSGCH_TUTORIAL, 0, _get_tutorial_cols()); more(); if (Tutorial.tut_explored) { text = "Walking around and exploring levels gets easier by using " "auto-explore (o). Crawl will let you automatically " "move to and pick up interesting items."; } else if (Tutorial.tut_travel) { text = "There is a convenient way for travelling between far away " "dungeon levels: press Ctrl-G or G and enter " "the desired destination. If your travel gets interrupted, " "issuing Ctrl-G Enter or G Enter will continue " "it."; } else if (Tutorial.tut_stashes) { text = "You can search among all items existing in the dungeon with " "the Ctrl-F command. For example, " "Ctrl-F \"knife\" will list all knives. You can then " "travel to one of the spots. It is even possible to enter " "words like \"shop\" or \"altar\"."; } else { int hint = random2(4); switch (hint) { case 0: text = "The game keeps an automated logbook for your characters. " "Use ?: to read it. You can enter notes manually " "with the : command. Once your character perishes, " "two morgue files are left in the morgue/ " "directory. The one ending in .txt contains a copy of " "your logbook. During play, you can create a dump file " "with #."; break; case 1: text = "Crawl has a macro function built in: press ~m " "to define a macro by first specifying a trigger key " "(say, F1) and a command sequence, for example " "za+.. The latter will make the F1 " "key always zap the spell in slot a at the nearest " "monster. For more information on macros, type ?~."; break; case 2: text = "The interface can be greatly customised. All options are " "explained in the file options_guide.txt which " "can be found in the docs directory. The options " "themselves are set in init.txt or " ".crawlrc. Crawl will complain if it can't find " "either file."; break; case 3: text = "You can ask other Crawl players for advice and help " "on the #crawl IRC (Internet Relay Chat) " "channel on freenode (irc.freenode.net)."; break; default: text = "Oops... No hint for now. Better luck next time!"; } } formatted_message_history(text, MSGCH_TUTORIAL, 0, _get_tutorial_cols()); more(); Tutorial.tutorial_events.init(false); } // Occasionally remind religious characters of sacrifices. void tutorial_dissection_reminder(bool healthy) { if (Tutorial.tut_just_triggered || !Tutorial.tutorial_left) return; // When hungry, give appropriate message or at least don't suggest // sacrifice. if (you.hunger_state < HS_SATIATED && healthy) { learned_something_new(TUT_MAKE_CHUNKS); return; } if (!god_likes_fresh_corpses(you.religion)) return; if (Tutorial.tutorial_events[TUT_OFFER_CORPSE]) learned_something_new(TUT_OFFER_CORPSE); else if (one_chance_in(8)) { Tutorial.tut_just_triggered = true; std::string text; text += "If you don't want to eat it, consider offering this " "corpse up under prayer as a sacrifice to "; text += god_name(you.religion); #ifdef USE_TILE text += ". You can also chop up any corpse that shows in the floor " "part of your inventory tiles by clicking on it with your " "left mouse button"; #endif text += ". Whenever you view a corpse while in tutorial mode you can " "reread this information."; formatted_message_history(text, MSGCH_TUTORIAL, 0, _get_tutorial_cols()); if (is_resting()) stop_running(); } } // Occasionally remind injured characters of resting. void tutorial_healing_reminder() { if (!Tutorial.tutorial_left) return; if (you.duration[DUR_POISONING] && 2*you.hp < you.hp_max) { if (Tutorial.tutorial_events[TUT_NEED_POISON_HEALING]) learned_something_new(TUT_NEED_POISON_HEALING); } else if (Tutorial.tut_seen_invisible > 0 && you.num_turns - Tutorial.tut_seen_invisible <= 20) { // If we recently encountered an invisible monster, we need a // special message. learned_something_new(TUT_NEED_HEALING_INVIS); // If that one was already displayed, don't print a reminder. } else { if (Tutorial.tutorial_events[TUT_NEED_HEALING]) learned_something_new(TUT_NEED_HEALING); else if (you.num_turns - Tutorial.tut_last_healed >= 50 && !you.duration[DUR_POISONING]) { if (Tutorial.tut_just_triggered) return; Tutorial.tut_just_triggered = 1; std::string text; text = "Remember to rest between fights and to enter unexplored " "terrain with full hitpoints and magic. Ideally you " "should retreat into areas you've already explored and " "cleared of monsters; resting on the edge of the explored " "terrain increases the chances of your rest being " "interrupted by wandering monsters. For resting, press " "5 or Shift-numpad 5" #ifdef USE_TILE ", or click on the stat area with your mouse" #endif "."; if (you.religion == GOD_TROG && !you.berserk() && !you.duration[DUR_EXHAUSTED] && you.hunger_state >= HS_SATIATED) { text += "\nAlso, berserking might help you not to lose so many " "hitpoints in the first place. To use your abilities type " "a."; } formatted_message_history(text, MSGCH_TUTORIAL, 0, _get_tutorial_cols()); if (is_resting()) stop_running(); } Tutorial.tut_last_healed = you.num_turns; } } // Give a message if you see, pick up or inspect an item type for the // first time. void taken_new_item(unsigned char item_type) { switch (item_type) { case OBJ_WANDS: learned_something_new(TUT_SEEN_WAND); break; case OBJ_SCROLLS: learned_something_new(TUT_SEEN_SCROLL); break; case OBJ_JEWELLERY: learned_something_new(TUT_SEEN_JEWELLERY); break; case OBJ_POTIONS: learned_something_new(TUT_SEEN_POTION); break; case OBJ_BOOKS: learned_something_new(TUT_SEEN_SPBOOK); break; case OBJ_FOOD: learned_something_new(TUT_SEEN_FOOD); break; case OBJ_CORPSES: learned_something_new(TUT_SEEN_CARRION); break; case OBJ_WEAPONS: learned_something_new(TUT_SEEN_WEAPON); break; case OBJ_ARMOUR: learned_something_new(TUT_SEEN_ARMOUR); break; case OBJ_MISSILES: learned_something_new(TUT_SEEN_MISSILES); break; case OBJ_MISCELLANY: learned_something_new(TUT_SEEN_MISC); break; case OBJ_STAVES: learned_something_new(TUT_SEEN_STAFF); break; case OBJ_GOLD: learned_something_new(TUT_SEEN_GOLD); break; default: // nothing to be done return; } } // Give a special message if you gain a skill you didn't have before. void tut_gained_new_skill(int skill) { if (!Tutorial.tutorial_left) return; learned_something_new(TUT_SKILL_RAISE); switch (skill) { // Special cases first. case SK_FIGHTING: case SK_ARMOUR: case SK_STEALTH: case SK_STABBING: case SK_TRAPS_DOORS: case SK_UNARMED_COMBAT: case SK_INVOCATIONS: case SK_EVOCATIONS: case SK_DODGING: case SK_SHIELDS: case SK_THROWING: case SK_SPELLCASTING: { formatted_message_history(get_skill_description(skill), MSGCH_TUTORIAL, 0, _get_tutorial_cols()); stop_running(); break; } // Only one message for all magic skills (except Spellcasting). case SK_CONJURATIONS: case SK_ENCHANTMENTS: case SK_SUMMONINGS: case SK_NECROMANCY: case SK_TRANSLOCATIONS: case SK_TRANSMUTATIONS: case SK_FIRE_MAGIC: case SK_ICE_MAGIC: case SK_AIR_MAGIC: case SK_EARTH_MAGIC: case SK_POISON_MAGIC: learned_something_new(TUT_GAINED_MAGICAL_SKILL); break; // Melee skills. case SK_SHORT_BLADES: case SK_LONG_BLADES: case SK_AXES: case SK_MACES_FLAILS: case SK_POLEARMS: case SK_STAVES: learned_something_new(TUT_GAINED_MELEE_SKILL); break; // Ranged skills. case SK_SLINGS: case SK_BOWS: case SK_CROSSBOWS: learned_something_new(TUT_GAINED_RANGED_SKILL); break; default: break; } } #ifndef USE_TILE // As safely as possible, colourize the passed glyph. // Handles quoting "<", MBCS-ing unicode, and // making DEC characters safe if not properly printable. static std::string _colourize_glyph(int col, unsigned glyph) { std::string colour_str = colour_to_str(col); std::ostringstream text; text << "<" << colour_str << ">"; text << stringize_glyph(glyph); if (glyph == '<') text << '<'; text << ""; return text.str(); } static std::string _colourize_glyph(glyph g) { return (_colourize_glyph(g.col, g.ch)); } #endif static bool _mons_is_highlighted(const monsters *mons) { return (mons->friendly() && Options.friend_brand != CHATTR_NORMAL || mons_looks_stabbable(mons) && Options.stab_brand != CHATTR_NORMAL || mons_looks_distracted(mons) && Options.may_stab_brand != CHATTR_NORMAL); } static bool _advise_use_wand() { for (int i = 0; i < ENDOFPACK; i++) { item_def &obj(you.inv[i]); if (!obj.is_valid()) continue; if (obj.base_type != OBJ_WANDS) continue; // Wand type unknown, might be useful. if (!item_type_known(obj)) return (true); // Empty wands are no good. if (obj.plus2 == ZAPCOUNT_EMPTY || item_ident(obj, ISFLAG_KNOW_PLUSES) && obj.plus <= 0) { continue; } // Can it be used to fight? switch (obj.sub_type) { case WAND_FLAME: case WAND_FROST: case WAND_SLOWING: case WAND_MAGIC_DARTS: case WAND_PARALYSIS: case WAND_FIRE: case WAND_COLD: case WAND_CONFUSION: case WAND_FIREBALL: case WAND_TELEPORTATION: case WAND_LIGHTNING: case WAND_ENSLAVEMENT: case WAND_DRAINING: case WAND_RANDOM_EFFECTS: case WAND_DISINTEGRATION: return (true); } } return (false); } void tutorial_monster_seen(const monsters &mon) { if (mons_class_flag(mon.type, M_NO_EXP_GAIN)) { tutorial_event_type et = mon.type == MONS_TOADSTOOL ? TUT_SEEN_TOADSTOOL : TUT_SEEN_ZERO_EXP_MON; if (Tutorial.tutorial_events[et]) { if (Tutorial.tut_just_triggered) return; learned_something_new(et, mon.pos()); return; } // Don't do TUT_SEEN_MONSTER for zero exp monsters. if (Tutorial.tutorial_events[TUT_SEEN_MONSTER]) return; } if (!Tutorial.tutorial_events[TUT_SEEN_MONSTER]) { if (Tutorial.tut_just_triggered) return; if (_mons_is_highlighted(&mon)) learned_something_new(TUT_MONSTER_BRAND, mon.pos()); if (mon.friendly()) learned_something_new(TUT_MONSTER_FRIENDLY, mon.pos()); if (you.religion == GOD_TROG && !you.berserk() && !you.duration[DUR_EXHAUSTED] && you.hunger_state >= HS_SATIATED && one_chance_in(4)) { learned_something_new(TUT_CAN_BERSERK); } return; } stop_running(); Tutorial.tutorial_events[TUT_SEEN_MONSTER] = false; Tutorial.tutorial_left--; Tutorial.tut_just_triggered = true; std::string text = "That "; #ifdef USE_TILE // need to highlight monster const coord_def gc = mon.pos(); tiles.place_cursor(CURSOR_TUTORIAL, gc); tiles.add_text_tag(TAG_TUTORIAL, &mon); text += "monster is a "; text += mon.name(DESC_PLAIN).c_str(); text += ". Examples for typical early monsters are rats, giant newts, " "kobolds, or goblins. You can gain information about any monster " "by hovering your mouse over its tile, and read the monster " "description by clicking on it with your right mouse button." #else text += _colourize_glyph(get_mons_glyph(&mon)); text += " is a monster, usually depicted by a letter. Some typical " "early monsters look like r, l, " "K or g. "; if (crawl_view.mlistsz.y > 0) { text += "Your console settings allowing, you'll always see a " "list of monsters somewhere on the screen." EOL; } text += "You can gain information about it by pressing x and " "moving the cursor on the monster, and read the monster " "description by then pressing v. " #endif "\nTo attack this monster with your wielded weapon, just move " "into it. "; #ifdef USE_TILE text += "Note that as long as there's a non-friendly monster in view you " "won't be able to automatically move to distant squares, to avoid " "death by misclicking."; #endif formatted_message_history(text, MSGCH_TUTORIAL, 0, _get_tutorial_cols()); if (Tutorial.tutorial_type == TUT_RANGER_CHAR) { text = "However, as a hunter you will want to deal with it using your " "bow. If you have a look at your bow from your " "inventory, you'll find an explanation of how to do " "this. "; if (!you.weapon() || you.weapon()->base_type != OBJ_WEAPONS || you.weapon()->sub_type != WPN_BOW) { text += "First wield it, then follow the instructions."; #ifdef USE_TILE text += EOL "As a short-cut you can also right-click on your " "bow to read its description, and left-click to wield " "it."; #endif } #ifdef USE_TILE else { text += "Clicking with your right mouse button on your bow " "will also let you read its description."; } #endif formatted_message_history(text, MSGCH_TUTORIAL, 0, _get_tutorial_cols()); } else if (Tutorial.tutorial_type == TUT_MAGIC_CHAR) { text = "However, as a conjurer you will want to deal with it using " "magic. If you have a look at your spellbook from your " "inventory, you'll find an explanation of how to do " "this."; #ifdef USE_TILE text += EOL "As a short-cut you can also right-click on your " "book in your inventory to read its description."; #endif formatted_message_history(text, MSGCH_TUTORIAL, 0, _get_tutorial_cols()); } } void tutorial_first_item(const item_def &item) { // Happens if monster is standing on dropped corpse or item. if (monster_at(item.pos)) return; if (!Tutorial.tutorial_events[TUT_SEEN_FIRST_OBJECT] || Tutorial.tut_just_triggered) { // NOTE: Since a new player might not think to pick up a // corpse (and why should they?), TUT_SEEN_CARRION is done when a // corpse is first seen. if (!Tutorial.tut_just_triggered && item.base_type == OBJ_CORPSES && !monster_at(item.pos)) { learned_something_new(TUT_SEEN_CARRION, item.pos); } return; } stop_running(); Tutorial.tutorial_events[TUT_SEEN_FIRST_OBJECT] = false; Tutorial.tutorial_left--; Tutorial.tut_just_triggered = true; std::string text = "That "; #ifndef USE_TILE text += _colourize_glyph(get_item_glyph(&item)); text += " "; #else const coord_def gc = item.pos; tiles.place_cursor(CURSOR_TUTORIAL, gc); tiles.add_text_tag(TAG_TUTORIAL, item.name(DESC_CAP_A), gc); #endif text += "is an item. If you move there and press g or " ", you will pick it up. " #ifndef USE_TILE "Generally, items are shown by non-letter symbols like " "%?!\"=()[. " #else "You can also pick up an item by clicking on your left mouse " "button while standing on its square. " #endif "Also, several types of objects will usually be picked up " "automatically. " #ifdef USE_TILE "(In Tiles, these will be marked with a green frame around them.)" #endif EOL "Once it is in your inventory, you can drop it again with " #ifdef USE_TILE "a left mouse click while pressing the Shift key. " "Whenever you right-click on an item" #else "d. Any time you look at an item in your inventory" #endif ", you can read about its properties and its description."; formatted_message_history(text, MSGCH_TUTORIAL, 0, _get_tutorial_cols()); } static void _new_god_conduct() { std::ostringstream text; const std::string new_god_name = god_name(you.religion); text << "You've just converted to worshipping " << new_god_name << ". "; if (you.religion == GOD_XOM) { // Xom is a special case. text << "You can keep Xom happy by keeping him amused; you do " "absolutely not want this god to grow bored with you!\n" "If you keep Xom amused he'll treat you like a plaything, " "randomly helping and harming you for his own amusement; " "otherwise he'll treat you like a disfavoured plaything."; formatted_message_history(text.str(), MSGCH_TUTORIAL, 0, _get_tutorial_cols()); return; } if (is_good_god(you.religion)) { // For the good gods, piety grows over time. text << "From now on, " << new_god_name << " will watch over you and " "judge your behaviour. Thus, your actions will greatly " "influence your piety (divine favour). If your piety runs out "; } else { text << "Your piety (divine favour) will gradually decrease over time, " "and if it runs out "; } text << new_god_name << " will excommunicate you and punish you. " "You can prevent this, however, and even gain enough piety to get " "powers and divine gifts, by doing things to please " << new_god_name << ". But don't panic: you start out with a decent " "amount of piety, so any danger of excommunication is far off.\n"; formatted_message_history(text.str(), MSGCH_TUTORIAL, 0, _get_tutorial_cols()); text.str(""); text << "\nYou can check your god's likes and dislikes, as well as your " "current standing and divine abilities, by typing ^" #ifdef USE_TILE " (alternatively press Shift while " "right-clicking on your avatar)" #endif "."; formatted_message_history(text.str(), MSGCH_TUTORIAL, 0, _get_tutorial_cols()); } // If the player is wielding a cursed non-slicing weapon then butchery // isn't currently possible. static bool _cant_butcher() { const item_def *wpn = you.weapon(); if (!wpn || wpn->base_type != OBJ_WEAPONS) return false; return (wpn->cursed() && !can_cut_meat(*wpn)); } static int _num_butchery_tools() { int num = 0; for (int i = 0; i < ENDOFPACK; ++i) { const item_def& tool(you.inv[i]); if (tool.is_valid() && tool.base_type == OBJ_WEAPONS && can_cut_meat( tool )) { num++; } } return (num); } static std::string _describe_portal(const coord_def &gc) { const std::string desc = feature_description(gc); std::ostringstream text; // Ziggurat entrances can rarely appear as early as DL 3. if (desc.find("zig") != std::string::npos) { text << "is a portal to a set of special levels filled with very " "tough monsters; you probably shouldn't even think of going " "in here. Additionally, entering a ziggurat takes a lot of " "gold, a lot more than you'd have right now; don't bother " "saving gold up for it, since at this point your gold is " "better spent at shops buying items which can help you " "survive." "\n\nIf you still want to enter (and somehow have " "gathered enough gold to do so) "; } // For the sake of completeness, though it's very unlikely that a // player will find a bazaar entrance before reahing XL 7. else if (desc.find("bazaar") != std::string::npos) { text << "is a portal to an inter-dimensional bazaar filled with " "shops. It will disappear if you don't enter it soon, " "so hurry. To enter "; } // The sewers can appear from DL 3 to DL 6. else { text << "is a portal to a special level where you'll have to fight " "your way back to the exit through some tougher than average " "monsters (the monsters around the portal should give a " "good indication as to how tough), but with the reward of " "some good loot. There's no penalty for skipping it, but if " "you do skip it the portal will disappear, so you have to " "decide now if you want to risk it. To enter "; } text << "stand over the portal and press >. To return find " #ifdef USE_TILE "a similar looking portal tile " #else "another \\ (though NOT the ancient stone arch you'll start " "out on) " #endif "and press <<."; #ifdef USE_TILE text << "\nAlternatively, clicking on your left mouse button " "while pressing the Shift key will let you enter any " "portal you're standing on."; #endif return (text.str()); } #define DELAY_EVENT \ { \ Tutorial.tutorial_events[seen_what] = true; \ Tutorial.tutorial_left++; \ return; \ } // Really rare or important events should get a comment even if // learned_something_new() was already triggered this turn. static bool _rare_tutorial_event(tutorial_event_type event) { switch (event) { case TUT_SEEN_SECRET_DOOR: case TUT_KILLED_MONSTER: case TUT_NEW_LEVEL: case TUT_YOU_ENCHANTED: case TUT_YOU_SICK: case TUT_YOU_POISON: case TUT_YOU_ROTTING: case TUT_YOU_CURSED: case TUT_YOU_HUNGRY: case TUT_YOU_STARVING: case TUT_NEED_POISON_HEALING: case TUT_INVISIBLE_DANGER: case TUT_NEED_HEALING_INVIS: case TUT_ABYSS: case TUT_RUN_AWAY: case TUT_RETREAT_CASTER: case TUT_YOU_MUTATED: case TUT_NEW_ABILITY_GOD: case TUT_NEW_ABILITY_MUT: case TUT_NEW_ABILITY_ITEM: case TUT_CONVERT: case TUT_GOD_DISPLEASED: case TUT_EXCOMMUNICATE: case TUT_GLOWING: case TUT_CAUGHT_IN_NET: case TUT_GAINED_MAGICAL_SKILL: case TUT_CHOOSE_STAT: return (true); default: return (false); } } // Here most of the tutorial messages for various triggers are handled. void learned_something_new(tutorial_event_type seen_what, coord_def gc) { // Already learned about that. if (!Tutorial.tutorial_events[seen_what]) return; // Don't trigger twice in the same turn. if (Tutorial.tut_just_triggered && !_rare_tutorial_event(seen_what)) return; std::ostringstream text; #ifndef USE_TILE const coord_def e = grid2show(gc); #endif Tutorial.tut_just_triggered = true; Tutorial.tutorial_events[seen_what] = false; Tutorial.tutorial_left--; switch (seen_what) { case TUT_SEEN_POTION: text << "You have picked up your first potion" #ifndef USE_TILE " ('!'). Use " #else ". Simply click on it with your left mouse button, or " "press " #endif "q to quaff it."; break; case TUT_SEEN_SCROLL: text << "You have picked up your first scroll" #ifndef USE_TILE " ('?'). Type " #else ". Simply click on it with your left mouse button, or " "type " #endif "r to read it."; break; case TUT_SEEN_WAND: text << "You have picked up your first wand" #ifndef USE_TILE " ('/'). Type " #else ". Simply click on it with your left mouse button, or " "type " #endif "V to evoke it."; break; case TUT_SEEN_SPBOOK: text << "You have picked up a book "; #ifndef USE_TILE text << "('"; text << static_cast(get_item_symbol(SHOW_ITEM_BOOK)) << "') " << "that you can read by typing r. " "If it's a spellbook you'll then be able to memorise spells " "via M and cast a memorised spell with z."; #else text << ". You can read it doing a right click with your " "mouse, and memorise spells with a left click. "; #endif if (you.religion == GOD_TROG) { text << "\nAs a worshipper of " << god_name(GOD_TROG) << ", though, you might instead wish to burn those tomes " "of hated magic by using the corresponding " "ability."; } else if (!you.skills[SK_SPELLCASTING]) { text << "\nHowever, first you will have to get accustomed to " "spellcasting by reading lots of scrolls."; } text << "\nDuring the tutorial you can reread this information at " "any time by " #ifndef USE_TILE "having a look in your inventory at the item in " "question."; #else "clicking on it with your right mouse button."; #endif break; case TUT_SEEN_WEAPON: text << "This is the first weapon " #ifndef USE_TILE "(')') " #endif "you've picked up. Use w " #ifdef USE_TILE "or click on it with your left mouse button " #endif "to wield it, but be aware that this weapon " "might train a different skill from your current one. You can " "view the weapon's properties from your inventory" #ifdef USE_TILE " or by right-clicking on it" #endif "."; if (Tutorial.tutorial_type == TUT_BERSERK_CHAR) { text << "\nAs you're already trained in Axes you should stick " "with these. Checking other axes can be worthwhile."; } else if (Tutorial.tutorial_type == TUT_MAGIC_CHAR) { text << "\nAs a spellslinger you don't need a weapon to fight. " "However, you should still carry at least one knife, " "dagger, sword or axe so that you can chop up corpses."; } break; case TUT_SEEN_MISSILES: text << "This is the first stack of missiles " #ifndef USE_TILE "('(') " #endif "you've picked up. Missiles like darts and throwing nets " "can be thrown by hand, but other missiles like arrows and " "needles require a launcher and training in using it to be " "really effective. " #ifdef USE_TILE "Right-clicking on " #else "Selecting " #endif "the item in your inventory will give more " "information about both missiles and launcher."; if (Tutorial.tutorial_type == TUT_RANGER_CHAR) { text << "\nAs you're already trained in Bows you should stick " "with arrows and collect more of them in the dungeon."; } else if (Tutorial.tutorial_type == TUT_MAGIC_CHAR) { text << "\nHowever, as a spellslinger, you don't really need " "another type of ranged attack, unless there's another " "effect in addition to damage."; } else { text << "\nFor now you might be best off with sticking to darts " "or stones for ranged attacks."; } break; case TUT_SEEN_ARMOUR: text << "This is the first piece of armour " #ifndef USE_TILE "('[') " #endif "you've picked up. " #ifdef USE_TILE "You can click on it to wear it, and click a second time to " "take it off again. Doing a right mouse click will " "show you its properties."; #else "Use W to wear it and T to take it off again. " "You can view its properties from your inventory."; #endif if (you.species == SP_CENTAUR || you.species == SP_MINOTAUR) { text << "\nNote that as a " << species_name(you.species, 1) << " you will be unable to wear " << (you.species == SP_CENTAUR ? "boots" : "helmets") << "."; } break; case TUT_SEEN_RANDART: text << "Weapons and armour that have unusual descriptions like this " "are much more likely to be of higher enchantment or have " "special properties, good or bad. The rarer the description, " "the greater the potential value of an item."; break; case TUT_SEEN_FOOD: text << "You have picked up some food" #ifndef USE_TILE " ('%')" #endif ". You can eat it by typing e" #ifdef USE_TILE " or by clicking on it with your left mouse button" #endif ". However, it is usually best to conserve rations and fruit " "until you are hungry or even starving."; break; case TUT_SEEN_CARRION: // NOTE: This is called when a corpse is first seen as well as when // first picked up, since a new player might not think to pick // up a corpse. if (gc.x <= 0 || gc.y <= 0) text << "Ah, a corpse!"; else { int i = you.visible_igrd(gc); while (i != NON_ITEM) { if (mitm[i].base_type == OBJ_CORPSES) break; i = mitm[i].link; } if (i == NON_ITEM) text << "Ah, a corpse!"; else { text << "That "; #ifndef USE_TILE text << _colourize_glyph(get_item_glyph(&mitm[i])); text << " "; #else tiles.place_cursor(CURSOR_TUTORIAL, gc); tiles.add_text_tag(TAG_TUTORIAL, mitm[i].name(DESC_CAP_A), gc); #endif text << "is a corpse."; } } text << " When a corpse is lying on the ground, you " "can chop it up with a sharp implement"; if (_cant_butcher()) { text << " (though unfortunately you can't do that right now, " "since the cursed weapon you're wielding can't slice up " "meat, and you can't let go of it to wield one that " "can)"; } else if (_num_butchery_tools() == 0) { text << " (but you currently possess nothing which can do this, " "so you should pick up the first knife, dagger, sword " "or axe you find)"; } text << ". Once hungry you can then eat the resulting chunks " "(though they may not be healthful)."; #ifdef USE_TILE text << " With tiles, you can also chop up any corpse that shows up in " "the floor part of your inventory region, simply by doing a " "left mouse click while pressing Shift, and " "then eat the resulting chunks with Shift + right mouse " "click."; #endif if (god_likes_fresh_corpses(you.religion)) { text << " You can offer corpses to " << god_name(you.religion) << " by praying over them to offer them. Note that the gods will " << "not accept rotting flesh."; } text << "\nDuring the tutorial you can reread this information at " "any time by selecting the item in question in your " "inventory."; break; case TUT_SEEN_JEWELLERY: text << "You have picked up a a piece of jewellery, either a ring" #ifndef USE_TILE << " ('=')" #endif << " or an amulet" #ifndef USE_TILE << " ('\"')" << ". Type P to put it on and R to remove " "it. You can view its properties from your inventory" #else << ". You can click on it to put it on, and click a second time " "remove it off again. By clicking on it with your right " "mouse button you can view its properties" #endif << ", though often magic is necessary to reveal its true " "nature."; break; case TUT_SEEN_MISC: text << "This is a curious object indeed. You can play around with " "it to find out what it does by " #ifdef USE_TILE "clicking on it to eVoke " #else "eVoking " #endif "it. Some items need to be wielded first before you can " "evoke them. As usual, selecting it from your " "inventory might give you more information."; break; case TUT_SEEN_STAFF: text << "You have picked up a magic staff or a rod" #ifndef USE_TILE ", both of which are represented by '"; text << static_cast(get_item_symbol(SHOW_ITEM_STAVE)) << "'" #endif ". Both must be wielded to be of use. " "Magicians use staves to increase their power in certain " "spell schools. By contrast, a rod allows the casting of " "certain spells even without magic knowledge simply by " "evoking it. For the latter the power depends on " "your Evocations skill."; #ifdef USE_TILE text << " Both wielding and evoking a wielded item can be achieved " "by clicking on it with your left mouse button."; #endif text << "\nDuring the tutorial you can reread this information at " "any time by selecting the item in question in your " "inventory."; break; case TUT_SEEN_GOLD: text << "You have picked up your first pile of gold" #ifndef USE_TILE " ('$')" #endif ". Unlike all other objects in Crawl it doesn't show up in " "your inventory, takes up no space in your inventory, weighs " "nothing and can't be dropped. Gold can be used to buy " "items from shops, and can also be sacrificed to some gods. "; if (!Options.show_gold_turns) { text << "Whenever you pick up some gold, your current amount will " "be mentioned. If you'd like to check your wealth at other " "times, you can press $. It will also be " "listed on the % screen."; } break; case TUT_SEEN_STAIRS: // Don't give this information during the first turn, to give // the player time to have a look around. if (you.num_turns < 1) DELAY_EVENT; text << "These "; #ifndef USE_TILE // Is a monster blocking the view? if (monster_at(gc)) DELAY_EVENT; text << _colourize_glyph(get_show_glyph(env.show(e))) << " "; #else tiles.place_cursor(CURSOR_TUTORIAL, gc); tiles.add_text_tag(TAG_TUTORIAL, "Stairs", gc); #endif text << "are some downstairs. You can enter the next (deeper) " "level by following them down (>). To get back to " "this level again, press << while standing on the " "upstairs."; #ifdef USE_TILE text << "\nAlternatively, clicking on your left mouse button " "while pressing the Shift key will let you follow any " "stairs you're standing on."; #endif break; case TUT_SEEN_ESCAPE_HATCH: if (you.num_turns < 1) DELAY_EVENT; // monsters standing on stairs if (monster_at(gc)) DELAY_EVENT; text << "These "; #ifndef USE_TILE text << _colourize_glyph(get_show_glyph(env.show(e))); text << " "; #else tiles.place_cursor(CURSOR_TUTORIAL, gc); tiles.add_text_tag(TAG_TUTORIAL, "Escape hatch", gc); #endif text << "are some kind of escape hatch. You can use them to " "quickly leave a level with << and >, " "respectively " #ifdef USE_TILE "(or by using your left mouse button in combination " "with the Shift key)" #endif ", but will usually be unable to return right away."; break; case TUT_SEEN_BRANCH: text << "This "; #ifndef USE_TILE // Is a monster blocking the view? if (monster_at(gc)) DELAY_EVENT; // FIXME: Branch entrance character is not being colored yellow. text << _colourize_glyph(get_show_glyph(env.show(e))) << " "; #else tiles.place_cursor(CURSOR_TUTORIAL, gc); tiles.add_text_tag(TAG_TUTORIAL, "Branch stairs", gc); #endif text << "is the entrance to a different branch of the dungeon, " "which might have different terrain, level layout and " "monsters from the current main branch you're in. Branches " "can range from being up to ten levels deep to having only " "a single level. They can also contain entrances to other " "branches." "\n\nThe first three branches you'll encounter are the " "Temple, the Orcish Mines and the Lair. While the Mines " "and the Lair can be dangerous for the new adventurer, " "the Temple is completely safe and contains a number of " "altars at which you might convert to a new god."; break; case TUT_SEEN_PORTAL: // Delay in the unlikely event that a player still in tutorial mode // creates a portal with a Trowel card, since a portal vault // entry's description doesn't seem to get set properly until // after the vault is done being placed. if (you.pos() == gc) DELAY_EVENT; text << "This "; #ifndef USE_TILE // Is a monster blocking the view? if (monster_at(gc)) DELAY_EVENT; text << _colourize_glyph(get_show_glyph(env.show(e))) << " "; #else tiles.place_cursor(CURSOR_TUTORIAL, gc); tiles.add_text_tag(TAG_TUTORIAL, "Portal", gc); #endif text << _describe_portal(gc); break; case TUT_STAIR_BRAND: // Monster or player standing on stairs. if (actor_at(gc)) DELAY_EVENT; #ifdef USE_TILE text << "A small question mark on a stair tile signifies that there " "are items in that position that you may want to check out."; #else text << "If any items are covering stairs or an escape hatch, then " "that will be indicated by highlighting the << or " "> symbol, instead of hiding the stair symbol with " "an item glyph."; #endif break; case TUT_HEAP_BRAND: // Monster or player standing on heap. if (actor_at(gc)) DELAY_EVENT; #ifdef USE_TILE text << "A small question mark on an item tile signifies that there " "is at least one other item in the same heap that you may want " "to check out."; break; #else text << "If two or more items are on a single square, then the square " "will be highlighted, and the symbol for the item on the top " "of the heap will be shown."; #endif break; case TUT_TRAP_BRAND: #ifdef USE_TILE // Tiles show both the trap and the item heap. return; #else // Monster or player standing on trap. if (actor_at(gc)) DELAY_EVENT; text << "If any items are covering a trap, then that will be " "indicated by highlighting the ^ symbol, instead of " "hiding the trap symbol with an item glyph."; #endif break; case TUT_SEEN_TRAP: if (you.pos() == gc) text << "Oops... you just triggered a trap. "; else text << "You just discovered a trap. "; text << "An unwary adventurer will occasionally stumble into one " "of these nasty constructions"; #ifndef USE_TILE { glyph g = get_show_glyph(env.show(e)); if (g.ch == ' ' || g.col == BLACK) g.col = LIGHTCYAN; text << " depicted by " << _colourize_glyph(g.col, '^'); } #endif text << ". They can do physical damage (with darts or needles, for " "example) or have other, more magical effects, like " "teleportation."; break; case TUT_SEEN_ALTAR: text << "That "; #ifndef USE_TILE text << _colourize_glyph(get_show_glyph(env.show(e))) << " "; #else { tiles.place_cursor(CURSOR_TUTORIAL, gc); std::string altar = "An altar to "; altar += god_name(feat_altar_god(grd(gc))); tiles.add_text_tag(TAG_TUTORIAL, altar, gc); } #endif text << "is an altar. You can get information about it by pressing " "p while standing on the square. Before taking up " "the corresponding faith you'll be asked for confirmation."; if (you.religion == GOD_NO_GOD && Tutorial.tutorial_type == TUT_MAGIC_CHAR) { text << "\n\nThe best god for an unexperienced conjurer is " "probably Vehumet, though Sif Muna is a good second " "choice."; } break; case TUT_SEEN_SHOP: #ifdef USE_TILE tiles.place_cursor(CURSOR_TUTORIAL, gc); tiles.add_text_tag(TAG_TUTORIAL, shop_name(gc), gc); #endif text << "That " #ifndef USE_TILE << _colourize_glyph(YELLOW, get_screen_glyph(gc)) << " " #endif "is a shop. You can enter it by typing << " #ifdef USE_TILE ", or by pressing Shift and clicking on it with your " "left mouse button " #endif "while standing on the square."; text << "\n\nIf there's anything you want which you can't afford yet " "you can select those items and press @ to put them " "on your shopping list. The game will then remind you when " "you gather enough gold to buy the items on your list."; break; case TUT_SEEN_DOOR: if (you.num_turns < 1) DELAY_EVENT; #ifdef USE_TILE tiles.place_cursor(CURSOR_TUTORIAL, gc); tiles.add_text_tag(TAG_TUTORIAL, "Closed door", gc); #endif text << "That " #ifndef USE_TILE << _colourize_glyph(WHITE, get_screen_glyph(gc)) << " " #endif "is a closed door. You can open it by walking into it. " "Sometimes it is useful to close a door. Do so by pressing " "C while standing next to it. If there are several " "doors, you will then be prompted for a direction. " "Alternatively, you can also use Ctrl-Direction."; #ifdef USE_TILE text << "\nIn Tiles, the same can be achieved by clicking on an " "adjacent door square."; #endif if (!Tutorial.tut_explored) { text << "\nTo avoid accidentally opening a door you'd rather " "remain closed during travel or autoexplore, you can mark " "it with an exclusion from the map view (X) with " "ee while your cursor is on the grid in question. " "Such an exclusion will prevent autotravel from ever " "entering that grid until you remove the exclusion with " "another press of Xe."; } break; case TUT_SEEN_SECRET_DOOR: #ifdef USE_TILE tiles.place_cursor(CURSOR_TUTORIAL, gc); tiles.add_text_tag(TAG_TUTORIAL, "Secret door", gc); #endif text << "That " #ifndef USE_TILE << _colourize_glyph(WHITE, get_screen_glyph(gc)) << " " #endif "was a secret door. You can actively try to find secret " "doors by searching. To search for one turn, press s, " "., delete or keypad-5. Pressing " "5 or shift-and-keypad-5 " #ifdef USE_TILE ", or clicking into the stat area " #endif "will search 100 times, stopping early if you find any " "secret doors or traps, or when your HP or MP fully " "recovers.\n\n" "If you can't find all three (or any) of the down stairs " "on a level, you should try searching for secret doors, since " "the missing stairs might be in sections of the level blocked " "off by them. If you really can't find any secret doors, then " "the missing stairs are probably in sections of the level " "totally disconnected from the section you're searching."; break; case TUT_KILLED_MONSTER: text << "Congratulations, your character just gained some experience " "by killing this monster! Every action will use up some of " "it to train certain skills. For example, fighting monsters "; if (Tutorial.tutorial_type == TUT_BERSERK_CHAR) { text << "in melee battle will raise your Axes and Fighting " "skills."; } else if (Tutorial.tutorial_type == TUT_RANGER_CHAR) { text << "using bow and arrows will raise your Bows skill."; } else // if (Tutorial.tutorial_type == TUT_MAGIC_CHAR) { text << "with offensive magic will raise your Conjurations and " "Spellcasting skills."; } if (you.religion == GOD_TROG) { text << " Also, kills of living creatures are automatically " "dedicated to " << god_name(you.religion) << "."; } break; case TUT_NEW_LEVEL: text << "Well done! Reaching a new experience level is always a nice " "event: you get more health and magic points, and " "occasionally increases to your attributes (strength, " "dexterity, intelligence)."; if (Tutorial.tutorial_type == TUT_MAGIC_CHAR) { text << "\nAlso, new experience levels let you learn more spells " "(the Spellcasting skill also does this). For now, you " "should try to memorise the second spell of your " "starting book with Ma, which can then be zapped " "with zb."; #ifdef USE_TILE text << " Memorising is also possible by doing a left " "click on the book in your inventory."; #endif } break; case TUT_SKILL_RAISE: text << "One of your skills just got raised. You can train your skills " "or pick up new ones by performing the corresponding actions. " "To view or manage your skill set, type m."; break; case TUT_GAINED_MAGICAL_SKILL: text << "Being skilled in a magical \"school\" makes it easier to " "learn and cast spells of this school. Many spells belong to " "a combination of several schools, in which case the average " "skill in these schools will decide on spellcasting success " "and power."; break; case TUT_GAINED_MELEE_SKILL: text << "Being skilled with a particular type of weapon will make it " "easier to fight with all weapons of this type, and make you " "deal more damage with them. It is generally recommended to " "concentrate your efforts on one or two weapon types to become " "more powerful in them. Some weapons are closely related, and " "being trained in one will ease training the other. This is " "true for the following pairs: Short Blades/Long Blades, " "Axes/Polearms, Polearms/Staves, Axes/Maces, and (though not " "strictly a weapon skill) Slings/Throwing."; break; case TUT_GAINED_RANGED_SKILL: text << "Being skilled in a particular type of ranged attack will let " "you deal more damage when using the appropriate weapons. It " "is usually best to concentrate on one type of ranged attack " "(including spells), and to add another one as back-up."; break; case TUT_CHOOSE_STAT: text << "Every third level you may choose what stat to invest in, " "Strength, Dexterity, or Intelligence. Strength " "influences the amount you can carry, and increases the damage " "you deal in melee. Dexterity increases your evasion " "and thus influences your chance of dodging attacks or traps. " "Intelligence increases your success in casting spells " "and decreases the amount by which you hunger when you do so.\n" "Note that it is generally recommended to raise all your " "stats to a minimum of 8, so as to prevent death by stat loss."; break; case TUT_YOU_ENCHANTED: text << "Enchantments of all types can befall you temporarily. " "Brief descriptions of these appear at the lower end of the " "stats area. Press @ for more details. A list of all " "possible enchantments is in the manual (?5)."; break; case TUT_YOU_SICK: // Hack: reset tut_just_triggered, to force recursive calling of // learned_something_new(). Tutorial.tut_just_triggered = false; learned_something_new(TUT_YOU_ENCHANTED); Tutorial.tut_just_triggered = true; text << "Corpses can be spoiled or inedible, making you sick. " "Also, some monsters' flesh is less palatable than others'. " "While sick, your hitpoints won't regenerate and sometimes " "an attribute may decrease. It wears off with time ("; if (!i_feel_safe()) text << "find a quiet corner and "; text << "wait with 5" #ifdef USE_TILE " or by clicking into the stats area" #endif "), or you could quaff a potion of healing."; break; case TUT_YOU_POISON: // Hack: reset tut_just_triggered, to force recursive calling of // learned_something_new(). Tutorial.tut_just_triggered = false; learned_something_new(TUT_YOU_ENCHANTED); Tutorial.tut_just_triggered = true; text << "Poison will slowly reduce your HP. It wears off with time ("; if (!i_feel_safe()) text << "find a quiet corner and "; text << "wait with 5" #ifdef USE_TILE "or by clicking onto the stats area" #endif "), or you could quaff a potion of healing. "; break; case TUT_YOU_ROTTING: // Hack: Reset tut_just_triggered, to force recursive calling of // learned_something_new(). Tutorial.tut_just_triggered = false; learned_something_new(TUT_YOU_ENCHANTED); Tutorial.tut_just_triggered = true; text << "Ugh, your flesh is rotting! Not only does this slowly " "reduce your HP, it also slowly reduces your maximum " "HP (your usual maximum HP will be indicated by a number in " "parentheses).\n" "While you can wait it out, you'll probably want to stop " "rotting as soon as possible by drinking a potion of healing, " "since the longer you wait the more your maximum HP will be " "reduced. Once you've stopped rotting you can restore your " "maximum HP to normal by drinking potions of healing and heal " "wounds while fully healed."; break; case TUT_YOU_CURSED: text << "Curses are comparatively harmless, but they do mean that " "you cannot remove cursed equipment and will have to suffer " "the (possibly) bad effects until you find and read a scroll " "of remove curse (though if you're wielding a cursed " "non-slicing weapon, you'll be unable to chop up " "corpses into chunks). Weapons and armour can also be " "uncursed using the appropriate enchantment scrolls."; break; case TUT_YOU_HUNGRY: text << "There are two ways to overcome hunger: food you started " "with or found, and self-made chunks from corpses. To get the " "latter, all you need to do is chop up a corpse " "with a sharp implement. "; if (_cant_butcher()) { text << "Unfortunately you can't butcher corpses right now, " "since the cursed weapon you're wielding can't slice up " "meat, and you can't let go of it to wield one that " "can."; } else { const int num = _num_butchery_tools(); if (num == 0) { text << "However, you currently possess no sharp implements, " "so you should pick up the first knife, dagger, sword " "or axe you find. "; } else if (Tutorial.tutorial_type != TUT_MAGIC_CHAR) text << "Your starting weapon will do nicely. "; else if (num == 1) text << "The slicing weapon you picked up will do nicely. "; else { text << "One of the slicing weapons you picked up will do " "nicely. "; } } text << "Try to dine on chunks in order to save permanent food."; if (Tutorial.tutorial_type == TUT_BERSERK_CHAR) text << "\nNote that you cannot Berserk while hungry or worse."; break; case TUT_YOU_STARVING: text << "You are now suffering from terrible hunger. You'll need to " "eat something quickly, or you'll die. The safest " "way to deal with this is to simply eat something from your " "inventory, rather than wait for a monster to leave a corpse. " "In a pinch, potions and fountains also can provide some " "nutrition, though not as much as food."; if (Tutorial.tutorial_type == TUT_MAGIC_CHAR) text << "\nNote that you cannot cast spells while starving."; break; case TUT_MULTI_PICKUP: text << "There's a more comfortable way to pick up several items at " "the same time. If you press , or g " #ifdef USE_TILE ", or click your left mouse button " #endif "twice you can choose items from a menu" #ifdef USE_TILE ", either by pressing their letter, or by clicking the " "corresponding lines in the menu" #endif ".\nThis takes fewer keystrokes but has no influence on the " "number of turns needed. Multi-pickup will be interrupted by " "monsters or other dangerous events."; break; case TUT_HEAVY_LOAD: if (you.burden_state != BS_UNENCUMBERED) { text << "It is not usually a good idea to run around encumbered; " "it slows you down and increases your hunger."; } else { text << "Sadly, your inventory is limited to 52 items, and it " "appears your knapsack is full."; } text << " However, this is easy enough to rectify: simply " "drop some of the stuff you don't need or that's too " "heavy to lug around permanently."; #ifdef USE_TILE text << " In the drop menu you can then comfortably select which " "items to drop by pressing their inventory letter, or by " "clicking on them."; #endif if (Tutorial.tut_stashes) { text << "\n\nYou can easily find items you've left on the floor " "with the Ctrl-F command, which will let you " "seach for all known items in the dungeon. For example, " "Ctrl-F \"knife\" will list all knives. You can " "can then travel to one of the spots."; Tutorial.tut_stashes = false; } text << "\n\nBe warned that items that you leave on the floor can " "be picked up and used by monsters."; break; case TUT_ROTTEN_FOOD: text << "One or more of the chunks or corpses you carry has started " "to rot. Few species can digest these safely, so you might " "just as well drop them now. When selecting items from " "a menu, there's a shortcut (&) to select all skeletons " "and rotten chunks or corpses in the stash at once."; break; case TUT_MAKE_CHUNKS: text << "How lucky! That monster left a corpse which you can now " "chop up"; if (_cant_butcher()) { text << "(or which you could chop up if it weren't for " "the fact that you can't let go of your cursed " "non-chopping weapon)"; } else if (_num_butchery_tools() == 0) { text << "(or which you could chop up if you had a " "chopping weapon; you should pick up the first knife, " "dagger, sword or axe you find if you want to be able " "to chop up corpses)"; } text << ". One or more chunks will appear that you can then " "eat. Beware that some chunks may be, sometimes or " "always, hazardous. You can find out whether that might be the " "case by " #ifdef USE_TILE "clicking with your right mouse button onto the corpse " "or chunk."; #else "viewing a corpse or chunk on the floor or by having a " "look at it in your inventory."; #endif break; case TUT_OFFER_CORPSE: if (!god_likes_fresh_corpses(you.religion) || you.hunger_state < HS_SATIATED) { return; } text << "Hey, that monster left a corpse! If you don't need it for " "food or other purposes, you can sacrifice it to " << god_name(you.religion) << " by praying over it to offer it. "; break; case TUT_SHIFT_RUN: text << "Walking around takes fewer keystrokes if you press " "Shift-direction or / direction. " "That will let you run until a monster comes into sight or " "your character sees something interesting."; break; case TUT_MAP_VIEW: text << "As you explore a level, orientation can become difficult. " "Press X to bring up the level map. Typing ? " "shows the list of level map commands. " "Most importantly, moving the cursor to a spot and pressing " ". or Enter lets your character move there on " "its own."; #ifdef USE_TILE text << "\nAlso, clicking on the right-side minimap with your " "right mouse button will zoom into that dungeon area. " "Clicking with the left mouse button instead will let " "you move there."; #endif break; case TUT_AUTO_EXPLORE: if (!Tutorial.tut_explored) return; text << "Fully exploring a level and picking up all the interesting " "looking items can be tedious. To save on this tedium you " "can press o to auto-explore, which will " "automatically explore unmapped regions, automatically pick " "up interesting items, and stop if a monster or interesting " "dungeon feature (stairs, altar, etc.) is encountered."; Tutorial.tut_explored = false; break; case TUT_DONE_EXPLORE: // XXX: You'll only get this message if you're using auto exploration. text << "Hey, you've finished exploring the dungeon on this level! " "You can search for stairs from the level map (X) " "by pressing >. The cursor will jump to the nearest " "staircase, and by pressing . or Enter your " "character can move there, too. "; if (Tutorial.tutorial_events[TUT_SEEN_STAIRS]) { text << "In rare cases, you may have found no downstairs at all. " "Try searching for secret doors in suspicious looking " "spots; use s, . or for 100 turns with " "5 " #ifdef USE_TILE "(or alternatively click into the stat area) " #endif "to do so."; } else { text << "Each level of Crawl has three " #ifndef USE_TILE "white " #endif "up and three " #ifndef USE_TILE "white " #endif "down stairs. Unexplored parts can often be accessed via " "another level or through secret doors. To find the " "latter, search the adjacent squares of walls for one " "turn with . or s, or for 100 turns with " "5 or Shift-numpad 5" #ifdef USE_TILE ", or by clicking on the stat area" #endif ".\n\n"; } break; case TUT_AUTO_EXCLUSION: // In the highly unlikely case the player encounters a // hostile statue or oklob plant during the tutorial... if (Tutorial.tut_explored) { // Hack: Reset tut_just_triggered, to force recursive calling of // learned_something_new(). Tutorial.tut_just_triggered = false; learned_something_new(TUT_AUTO_EXPLORE); Tutorial.tut_just_triggered = true; } text << "\nTo prevent autotravel or autoexplore taking you into " "dangerous territory, you can set travel exclusions by " "entering the map view (X) and then toggling the " "exclusion radius on the monster position with e. " "To make this easier some immobile monsters listed in the " "auto_exclude option (such as this one) are considered " "dangerous enough to warrant an automatic setting of an " "exclusion. It will be automatically cleared if you manage to " "kill the monster. You could also manually remove the " "exclusion with Xee but unless you remove this monster " "from the auto_exclude list, the exclusion will be reset the " "next turn."; break; case TUT_NEED_HEALING: text << "If you're low on hitpoints or magic and there's no urgent " "need to move, you can rest for a bit. Ideally, you should " "retreat to an area you've already explored and cleared " "of monsters before resting, since resting on the edge of " "the explored terrain increases the risk of rest being " "interrupted by a wandering monster. Press 5 or " "shift-numpad-5" #ifdef USE_TILE ", or click into the stat area" #endif " to do so."; break; case TUT_NEED_POISON_HEALING: text << "Your poisoning could easily kill you, so now would be a " "good time to quaff a potion of heal wounds or, " "better yet, a potion of healing. If you have seen neither " "of these so far, try unknown ones in your inventory. Good " "luck!"; break; case TUT_INVISIBLE_DANGER: text << "Fighting against a monster you cannot see is difficult. " "Your best bet is probably a strategic retreat, be it via " "teleportation or by getting off the level. " "Or else, luring the monster into a corridor should at least " "make it easier for you to hit it."; // To prevent this text being immediately followed by the next one... Tutorial.tut_last_healed = you.num_turns - 30; break; case TUT_NEED_HEALING_INVIS: text << "You recently noticed an invisible monster, so unless you " "killed it or left the scene resting might not be safe. If you " "still need to replenish your hitpoints or magic, you'll have " "to quaff an appropriate potion. For normal resting you will " "first have to get away from the danger."; Tutorial.tut_last_healed = you.num_turns; break; case TUT_CAN_BERSERK: // Don't print this information if the player already knows it. if (Tutorial.tut_berserk_counter) return; text << "Against particularly difficult foes, you should use your " "Berserk ability. Berserk will last longer if you " "kill a lot of monsters."; break; case TUT_POSTBERSERK: text << "Berserking is extremely exhausting! It burns a lot of " "nutrition, and afterwards you are slowed down and " "occasionally even pass out. Press @ to see your " "current exhaustion status."; break; case TUT_RUN_AWAY: text << "Whenever you've got only a few hitpoints left and you're in " "danger of dying, check your options carefully. Often, " "retreat or use of some item might be a viable alternative " "to fighting on."; if (you.species == SP_CENTAUR) { text << " As a four-legged centaur you are particularly quick - " "running is an option!"; } text << " If retreating to another level, keep in mind that monsters " "may follow you if they're standing right next to you when " "you start climbing or descending the stairs. And even if " "you've managed to shake them off, they'll still be there when " "you come back, so you might want to use a different set of " "stairs when you return."; if (you.religion == GOD_TROG && !you.berserk() && !you.duration[DUR_EXHAUSTED] && you.hunger_state >= HS_SATIATED) { text << "\nAlso, with " << apostrophise(god_name(you.religion)) << " support you can use your Berserk ability (a) " "to temporarily gain more hitpoints and greater " "strength. "; } break; case TUT_RETREAT_CASTER: text << "Without magical power you're unable to cast spells. While " "melee is a possibility, that's not where your strengths " "lie, so retreat (if possible) might be the better option."; if (_advise_use_wand()) text << "\n\nOr you could eVoke a wand to deal damage."; break; case TUT_YOU_MUTATED: text << "Mutations can be obtained from several sources, among them " "potions, spell miscasts, and overuse of strong enchantments " "like Invisibility. The only reliable way to get rid of " "mutations is with potions of cure mutation. There are about " "as many harmful as beneficial mutations, and most of them " "have three levels. Check your mutations with A."; break; case TUT_NEW_ABILITY_GOD: switch (you.religion) { // Gods where first granted ability is active. case GOD_KIKUBAAQUDGHA: case GOD_YREDELEMNUL: case GOD_NEMELEX_XOBEH: case GOD_ZIN: case GOD_OKAWARU: case GOD_SIF_MUNA: case GOD_TROG: case GOD_ELYVILON: case GOD_LUGONU: text << "You just gained a divine ability. Press a to " "take a look at your abilities or to use one of them."; break; // Gods where first granted ability is passive. default: text << "You just gained a divine ability. Press ^ " #ifdef USE_TILE "or press Shift and right-click on the " "player tile " #endif "to take a look at your abilities."; break; } break; case TUT_NEW_ABILITY_MUT: text << "That mutation granted you a new ability. Press a to " "take a look at your abilities or to use one of them."; break; break; case TUT_NEW_ABILITY_ITEM: text << "That item you just equipped granted you a new ability " "(un-equipping the item will remove the ability). " "Press a to take a look at your abilities or to " "use one of them."; break; break; case TUT_CONVERT: _new_god_conduct(); break; case TUT_GOD_DISPLEASED: text << "Uh-oh, " << god_name(you.religion) << " is growing " "displeased because your piety is running low. Possibly this " "is the case because you're committing heretic acts"; if (!is_good_god(you.religion)) { // Piety decreases over time for non-good gods. text << ", because " << god_name(you.religion) << " finds your " "worship lacking, or a combination of the two"; } text << ". If your piety goes to zero, then you'll be excommunicated. " "Better get cracking on raising your piety, and/or stop " "annoying your god. "; text << "In any case, you'd better reread the religious description. " "To do so, " #ifdef USE_TILE "press Shift and right-click on your avatar."; #else "type ^."; #endif break; case TUT_EXCOMMUNICATE: { const god_type new_god = (god_type) gc.x; const int old_piety = gc.y; god_type old_god = GOD_NO_GOD; for (int i = 0; i < MAX_NUM_GODS; i++) if (you.worshipped[i] > 0) { old_god = (god_type) i; break; } const std::string old_god_name = god_name(old_god); const std::string new_god_name = god_name(new_god); if (new_god == GOD_NO_GOD) { if (old_piety < 1) { text << "Uh-oh, " << old_god_name << " just excommunicated you " "for running out of piety (your divine favour went " "to nothing). Maybe you repeatedly violated the " "religious rules, or maybe you failed to please your " "deity often enough, or some combination of the two. " "If you can find an altar dedicated to " << old_god_name; } else { text << "Should you decide that abandoning " << old_god_name << "wasn't such a smart move after all, and you'd like to " "return to your old faith, you'll have to find an " "altar dedicated to " << old_god_name << " where"; } text << " you can re-convert, and all will be well. Otherwise " "you'll have to weather this god's displeasure until all " "divine wrath is spent."; } else { bool angry = false; if (is_good_god(old_god)) { if (is_good_god(new_god)) { text << "Fortunately, it seems that " << old_god_name << " didn't mind your converting to " << new_god_name << ". "; if (old_piety > 30) text << "You even kept some of your piety! "; text << "Note that this kind of alliance only exists " "between the three good gods, so don't expect this " "to be the norm."; } else if (!god_hates_your_god(old_god)) { text << "Fortunately, it seems that " << old_god_name << " didn't mind your converting to " << new_god_name << ". That's because " << old_god_name << " is one of " "the good gods who generally are rather forgiving " "about change of faith - unless you switch over to " "the path of evil, in which case their retribution " "can be nasty indeed!"; } else { text << "Looks like " << old_god_name << " didn't " "appreciate your converting to " << new_god_name << "! But really, changing from one of the good gods " "to an evil one, what did you expect!? For any god " "not on the opposing side of the faith, " << old_god_name << " would have been much more " "forgiving. "; angry = true; } } else { text << "Looks like " << old_god_name << " didn't appreciate " "your converting to " << new_god_name << "! (Actually, " "only the three good gods will sometimes be forgiving " "about this kind of faithlessness.) "; angry = true; } if (angry) { text << "Unfortunately, while converting back would appease " << old_god_name << ", it would annoy " << new_god_name << ", so you're stuck with having to suffer the wrath of " "one god or another."; } } break; } case TUT_WIELD_WEAPON: { int wpn = you.equip[EQ_WEAPON]; if (wpn != -1 && you.inv[wpn].base_type == OBJ_WEAPONS && you.inv[wpn].cursed()) { // Don't trigger if the wielded weapon is cursed. Tutorial.tutorial_events[seen_what] = true; Tutorial.tutorial_left++; return; } if (Tutorial.tutorial_type == TUT_RANGER_CHAR && wpn != -1 && you.inv[wpn].base_type == OBJ_WEAPONS && you.inv[wpn].sub_type == WPN_BOW) { text << "You can easily switch between weapons in slots a and " "b by pressing '."; } else { text << "You can easily switch back to your weapon in slot a by " "pressing '. To change the slot of an item, type " "=i and choose the appropriate slots."; } break; } case TUT_FLEEING_MONSTER: if (Tutorial.tutorial_type != TUT_BERSERK_CHAR) return; text << "Now that monster is scared of you! Note that you do not " "absolutely have to follow it. Rather, you can let it run " "away. Sometimes, though, it can be useful to attack a " "fleeing creature by throwing something after it. If you " "have any daggers or hand axes in your inventory, you " "can look at one of them to read an explanation of how to do " "this."; break; case TUT_MONSTER_BRAND: #ifdef USE_TILE tiles.place_cursor(CURSOR_TUTORIAL, gc); if (const monsters *m = monster_at(gc)) tiles.add_text_tag(TAG_TUTORIAL, m->name(DESC_CAP_A), gc); #endif text << "That monster looks a bit unusual. You might wish to examine " "it a bit more closely by " #ifdef USE_TILE "hovering your mouse over its tile"; #else "pressing x and moving the cursor onto its square."; #endif break; case TUT_MONSTER_FRIENDLY: { const monsters *m = monster_at(gc); if (!m) DELAY_EVENT; #ifdef USE_TILE tiles.place_cursor(CURSOR_TUTORIAL, gc); tiles.add_text_tag(TAG_TUTORIAL, m->name(DESC_CAP_A), gc); #endif text << "That monster is friendly to you and will attack your " "enemies, though you'll get only half the experience for " "monsters killed by allies of what you'd get for killing them " "yourself. You can command your allies by pressing t " "to talk to them."; if (!mons_att_wont_attack(m->attitude)) { text << "\nHowever, it is only temporarily friendly, and " "will become dangerous again when this friendliness " "wears off."; } break; } case TUT_MONSTER_SHOUT: { const monsters* m = monster_at(gc); if (!m) DELAY_EVENT; // "Shouts" from zero experience monsters are boring, ignore // them. if (mons_class_flag(m->type, M_NO_EXP_GAIN)) { Tutorial.tutorial_events[TUT_MONSTER_SHOUT] = true; return; } const bool vis = you.can_see(m); #ifdef USE_TILE if (vis) { tiles.place_cursor(CURSOR_TUTORIAL, gc); tiles.add_text_tag(TAG_TUTORIAL, m->name(DESC_CAP_A), gc); } #endif if (!vis) { text << "Uh-oh, some monster noticed you, either one that's " "around a corner or one that's invisible. Plus, the " "noise it made will alert other monsters in the " "vicinity, who will come to check out what the commotion " "was about."; } else if (mons_shouts(m->type, false) == S_SILENT) { text << "Uh-oh, that monster noticed you! Fortunately, it " "didn't make any noise, but many monsters do make " "noise when they notice you, which alerts other monsters " "in the area, who will come to check out what the " "commotion was about."; } else { text << "Uh-oh, that monster noticed you! Plus, the " "noise it made will alert other monsters in the " "vicinity, who will come to check out what the commotion " "was about."; } break; } case TUT_MONSTER_LEFT_LOS: { const monsters* m = monster_at(gc); if (!m || !you.can_see(m)) DELAY_EVENT; text << m->name(DESC_CAP_THE, true) << " didn't vanish, but merely " "moved onto a square which you can't currently see. It's still " "nearby, unless something happens to it in the short amount of " "time it's out of sight."; break; } case TUT_SEEN_MONSTER: case TUT_SEEN_FIRST_OBJECT: // Handled in special functions. break; case TUT_SEEN_TOADSTOOL: { const monsters* m = monster_at(gc); if (!m || !you.can_see(m)) DELAY_EVENT; text << "Sometimes toadstools will grow on decaying corpses, and " "will wither away soon after appearing. Worshipers of " "Fedhas Madash, the plant god, can make use of them, " "but to everyone else they're just ugly dungeon decoration."; break; } case TUT_SEEN_ZERO_EXP_MON: { const monsters* m = monster_at(gc); if (!m || !you.can_see(m)) DELAY_EVENT; text << "That "; #ifdef USE_TILE // need to highlight monster tiles.place_cursor(CURSOR_TUTORIAL, gc); tiles.add_text_tag(TAG_TUTORIAL, m); text << "is a "; #else text << _colourize_glyph(get_mons_glyph(m)) << " is a "; #endif text << m->name(DESC_PLAIN).c_str() << ". "; text << "While technically a monster, it's more like " "dungeon furniture, since it's harmless and doesn't move. " "If it's in your way you can attack and kill it like other " "monsters, but you won't get any expereince for doing so. "; break; } case TUT_ABYSS: text << "Uh-oh, you've wound up in the Abyss! The Abyss is a special " "place where you cannot remember or map where you've been; it " "is filled with nasty monsters, and you're probably going to " "die.\n"; text << "To increase your chances of survival until you can find the " "exit" #ifndef USE_TILE " (a flickering \\)" #endif ", keep moving, don't fight any of the monsters, and don't " "bother picking up any items on the ground. If you're " "encumbered or overburdened, then lighten up your load, and if " "the monsters are closing in, try to use items of speed to get " "away. Also, wherever possible, move in a direction slightly " "off from a compass direction (for example, north-by-northwest " "instead of north or northwest), as you're more likely to miss " "the exit if you keep heading solely in a compass direction."; break; case TUT_SPELL_MISCAST: { text << "You just miscast a spell. "; item_def *armour = you.slot_item(EQ_BODY_ARMOUR); item_def *shield = you.slot_item(EQ_SHIELD); if (armour && !is_light_armour(*armour) || shield) { text << "Wearing heavy body armour or using a shield, especially a " "large one, can severely hamper your spellcasting " "abilities. You can check the effect of this by comparing " "the success rates on the z\? screen with and " "without the item being worn.\n\n"; } text << "If the spellcasting success chance is high (which can be " "checked by entering z\? or I) then a miscast " "merely means the spell is not working, along with a harmless " "side effect. " "However, for spells with a low success rate, there's a chance " "of contaminating yourself with magical energy, plus a chance " "of an additional harmful side effect. Normally this isn't a " "problem, since magical contamination bleeds off over time, " "but if you're repeatedly contaminated in a short amount of " "time you'll mutate or suffer from other ill side effects.\n\n"; text << "Note that a miscast spell will still consume the full amount " "of MP and nutrition that a successfully cast spell would."; break; } case TUT_SPELL_HUNGER: text << "The spell you just cast made you hungrier; you can see how " "hungry spells make you by entering z\?! or II. " "The amount of nutrition consumed increases with the level of " "the spell and decreases depending on your intelligence stat " "and your Spellcasting skill. If both of these are high " "enough a spell might even not cost you any nutrition at all."; break; case TUT_GLOWING: text << "You've accumulated so much magical contamination that you're " "glowing! You usually acquire magical contamination from using " "some powerful magics, like invisibility, haste or potions of " "resistance. "; if (you.magic_contamination < 5) { text << "As long as the status only shows in grey nothing will " "actually happen as a result of it, but as you continue " "suffusing yourself with magical contamination you'll " "eventually start glowing for real, which "; } else { text << "This normally isn't a problem as contamination slowly " "bleeds off on its own, but it seems that you've " "accumulated so much contamination over a short amount of " "time that it "; } text << "can have nasty effects, such as mutate you or deal direct " "damage. In addition, glowing is going to make you much more " "noticeable."; break; case TUT_YOU_RESIST: text << "There are many dangers in Crawl. Luckily, there are ways to " "(at least partially) resist some of them, if you are " "fortunate enough to find them. There are two basic variants " "of resistances: the innate magic resistance that depends on " "your species, grows with the experience level, and protects " "against hostile enchantments; and the specific resistances " "against certain types of magic and also other effects, e.g. " "fire or draining.\n" "You can find items in the dungeon or gain mutations that will " "increase (or lower) one or more of your resistances. To view " "your current set of resistances, " #ifdef USE_TILE "right-click on the player avatar."; #else "press %."; #endif break; case TUT_CAUGHT_IN_NET: text << "While you are held in a net, you cannot move around or engage " "monsters in combat. Instead, any movement you take is counted " "as an attempt to struggle free from the net. With a wielded " "bladed weapon you will be able to cut the net faster"; if (Tutorial.tutorial_type == TUT_BERSERK_CHAR) text << ", especially if you're berserking while doing so"; text << ". Small species may also wriggle out of a net, only damaging " "it a bit, so as to then fire it at a monster."; if (Tutorial.tutorial_type == TUT_MAGIC_CHAR) { text << " Note that casting spells is still very much possible, " "as is using wands, scrolls and potions."; } else { text << " Note that using wands, scrolls and potions is still " "very much possible."; } break; case TUT_LOAD_SAVED_GAME: { text << "Welcome back! If it's been a while since you last played this " "character, you should take some time to refresh your memory " "of your character's progress. It is recommended to at least " "have a look through your inventory, but you should " "also check "; std::vector listed; if (Tutorial.tutorial_type == TUT_MAGIC_CHAR) listed.push_back("your spells (z?)"); if (!your_talents(false).empty()) listed.push_back("your abilities"); if (Tutorial.tutorial_type != TUT_MAGIC_CHAR || how_mutated()) listed.push_back("your set of mutations (A)"); if (you.religion != GOD_NO_GOD) listed.push_back("your religious standing (^)"); listed.push_back("the message history (Ctrl-P)"); listed.push_back("the character overview screen (%)"); text << comma_separated_line(listed.begin(), listed.end()) << "."; text << "\nAlternatively, you can dump all information pertaining to " "your character into a text file with the # command. " "You can then find said file in the morgue/ folder (" << you.your_name << ".txt) and read it at your leisure. Also, " "such a file will automatically be created upon death (the " "filename will then also contain the date) but that won't be " "of much use to you now."; break; } default: text << "You've found something new (but I don't know what)!"; } if (!text.str().empty()) { formatted_message_history(text.str(), MSGCH_TUTORIAL, 0, _get_tutorial_cols()); stop_running(); } } formatted_string tut_abilities_info() { std::ostringstream text; text << "<" << colour_to_str(channel_to_colour(MSGCH_TUTORIAL)) << ">"; std::string broken = "This screen shows your character's set of talents. " "You can gain new abilities via certain items, through religion or by " "way of mutations. Activation of an ability usually comes at a cost, " "e.g. nutrition or Magic power. Press '!' or '?' to " "toggle between ability selection and description."; linebreak_string2(broken, _get_tutorial_cols()); text << broken; text << ""; return formatted_string::parse_string(text.str(), false); } // Explains the basics of the skill screen. Don't bother the player with the // aptitude information. (Toggling is still possible, of course.) void print_tut_skills_info() { textcolor(channel_to_colour(MSGCH_TUTORIAL)); std::ostringstream text; text << "<" << colour_to_str(channel_to_colour(MSGCH_TUTORIAL)) << ">"; std::string broken = "This screen shows the skill set of your character. " "The number next to the skill is your current level, the higher the " "better. The cyan percent value shows your progress " "towards the next skill level. You can toggle which skills to train by " "pressing their slot letters. A greyish skill " "will increase at a decidedly slower rate and ease training of others. " "Press ? to read your skills' descriptions."; linebreak_string2(broken, _get_tutorial_cols()); text << broken; text << ""; formatted_string::parse_string(text.str(), false).display(); } void print_tut_skills_description_info() { textcolor(channel_to_colour(MSGCH_TUTORIAL)); std::ostringstream text; text << "<" << colour_to_str(channel_to_colour(MSGCH_TUTORIAL)) << ">"; std::string broken = "This screen shows the skill set of your character. " "Press the letter of a skill to read its description, " "or press ? again to return to the skill " "selection."; linebreak_string2(broken, _get_tutorial_cols()); text << broken; text << ""; formatted_string::parse_string(text.str(), false).display(); } // A short explanation of Crawl's target mode and its most important commands. static std::string _tut_target_mode(bool spells = false) { std::string result; result = "then be taken to target mode with the nearest monster or previous " "target already targeted. You can also cycle through all hostile " "monsters in sight with + or -. " "Once you're aiming at the correct monster, simply hit " "f, Enter or . to shoot at it. " "If you miss, "; if (spells) result += "zap"; else result += "ff"; result += " fires at the same target again."; return (result); } static std::string _tut_abilities(const item_def& item) { std::string str = "To do this, "; if (!item_is_equipped(item)) { switch(item.base_type) { case OBJ_WEAPONS: str += "first wield it"; break; case OBJ_ARMOUR: str += "first Wear it"; break; case OBJ_JEWELLERY: str += "first Put it on"; break; default: str += "(BUG! this item shouldn't give an ability)"; break; } str += ", then "; } str += "enter the ability menu with a, and then " "choose the corresponding ability. Note that such an attempt of " "activation, especially by the untrained, is likely to fail."; return (str); } static std::string _tut_throw_stuff(const item_def &item) { std::string result; result = "To do this, type f to fire, then "; result += item.slot; result += " for "; result += (item.quantity > 1 ? "these" : "this"); result += " "; result += item_base_name(item); result += (item.quantity > 1? "s" : ""); result += ". You'll "; result += _tut_target_mode(); return (result); } // Explains the most important commands necessary to use an item, and mentions // special effects, etc. // NOTE: For identified artefacts don't give all this information! // (The screen is likely to overflow.) Artefacts need special information // if they are evokable or grant resistances. // In any case, check whether we still have enough space for the // inscription prompt and answer. void tutorial_describe_item(const item_def &item) { std::ostringstream ostr; ostr << "<" << colour_to_str(channel_to_colour(MSGCH_TUTORIAL)) << ">"; switch (item.base_type) { case OBJ_WEAPONS: { if (is_artefact(item) && item_type_known(item)) { if (gives_ability(item) && wherey() <= get_number_of_lines() - 5) { // You can activate it. ostr << "When wielded, some weapons (such as this one) " "offer certain abilities you can activate. "; ostr << _tut_abilities(item); break; } else if (gives_resistance(item) && wherey() <= get_number_of_lines() - 3) { // It grants a resistance. ostr << "\nThis weapon offers its wearer protection from " "certain sources. For an overview of your " "resistances (among other things) type %" #ifdef USE_TILE " or click on your avatar with the right mouse " "button" #endif "."; break; } return; } item_def *weap = you.slot_item(EQ_WEAPON); bool wielded = (weap && (*weap).slot == item.slot); bool long_text = false; if (!wielded) { ostr << "You can wield this weapon with w, or use " "' to switch between the weapons in slot " "a and b. (Use =i to adjust item slots.)"; // Weapon skill used by this weapon and the best weapon skill. int curr_wpskill, best_wpskill; // Maybe this is a launching weapon? if (is_range_weapon(item)) { // Then only compare with other launcher skills. curr_wpskill = range_skill(item); best_wpskill = best_skill(SK_SLINGS, SK_THROWING, 99); } else { // Compare with other melee weapons. curr_wpskill = weapon_skill(item); best_wpskill = best_skill(SK_SHORT_BLADES, SK_STAVES, 99); // Maybe unarmed is better. if (you.skills[SK_UNARMED_COMBAT] > you.skills[best_wpskill]) best_wpskill = SK_UNARMED_COMBAT; } if (you.skills[curr_wpskill] + 2 < you.skills[best_wpskill]) { ostr << "\nOn second look, you've been training in " << skill_name(best_wpskill) << " for a while, so maybe you should " "continue training that rather than " << skill_name(curr_wpskill) << ". (Type m to see the skill " "management screen for the actual numbers.)"; long_text = true; } } else // wielded weapon { if (is_range_weapon(item)) { ostr << "To attack a monster, "; #ifdef USE_TILE ostr << "if you have appropriate ammo quivered you can " "left mouse click on the monster while " "prssing the Shift key. Alternatively, " "you can left mouse click on the tile for " "the ammo you wish to fire, and then left " "mouse click on the monster.\n\n"; ostr << "To launch ammunition using the keyboard, "; #endif ostr << "you only need to " "fire the appropriate type of ammunition. " "You'll "; ostr << _tut_target_mode(); } else { ostr << "To attack a monster, you can simply walk into it."; } } if (is_throwable(&you, item) && !long_text) { ostr << "\n\nSome weapons (including this one), can also be " "fired. "; ostr << _tut_throw_stuff(item); long_text = true; } if (!item_type_known(item) && (is_artefact(item) || get_equip_desc(item) != ISFLAG_NO_DESC)) { ostr << "\n\nWeapons and armour that have unusual descriptions " << "like this are much more likely to be of higher " << "enchantment or have special properties, good or bad. " << "The rarer the description, the greater the potential " << "value of an item."; Tutorial.tutorial_events[TUT_SEEN_RANDART] = false; } if (item_known_cursed( item ) && !long_text) { ostr << "\n\nOnce wielded, a cursed weapon won't leave your " "hands again until the curse has been lifted by " "reading a scroll of remove curse or one of the " "enchantment scrolls."; if (!wielded && is_throwable(&you, item)) ostr << " (Throwing it is safe, though.)"; Tutorial.tutorial_events[TUT_YOU_CURSED] = false; } Tutorial.tutorial_events[TUT_SEEN_WEAPON] = false; break; } case OBJ_MISSILES: if (is_throwable(&you, item)) { ostr << item.name(DESC_CAP_YOUR) << " can be fired without the use of a launcher. "; ostr << _tut_throw_stuff(item); } else if (is_launched(&you, you.weapon(), item)) { ostr << "As you're already wielding the appropriate launcher, " "you can simply "; #ifdef USE_TILE ostr << "left mouse click on the monster you want " "to hit while pressing the Shift key. " "Alternatively, you can left mouse click on " "this tile of the ammo you want to fire, and then " "left mouse click on the monster you want " "to hit.\n\n" "To launch this ammo using the keyboard, you can " "simply "; #endif ostr << "fire " << (item.quantity > 1 ? "these" : "this") << " " << item.name(DESC_BASENAME) << (item.quantity > 1? "s" : "") << ". You'll "; ostr << _tut_target_mode(); } else { ostr << "To shoot " << (item.quantity > 1 ? "these" : "this") << " " << item.name(DESC_BASENAME) << (item.quantity > 1? "s" : "") << ", first you need to wield an appropriate " "launcher."; } Tutorial.tutorial_events[TUT_SEEN_MISSILES] = false; break; case OBJ_ARMOUR: { bool wearable = true; if (you.species == SP_CENTAUR && item.sub_type == ARM_BOOTS) { ostr << "As a Centaur you cannot wear boots. " "(Type A to see a list of your mutations and " "innate abilities.)"; wearable = false; } else if (you.species == SP_MINOTAUR && is_hard_helmet(item)) { ostr << "As a Minotaur you cannot wear helmets. " "(Type A to see a list of your mutations and " "innate abilities.)"; wearable = false; } else if (item.sub_type == ARM_CENTAUR_BARDING) { ostr << "Only centaurs can wear centaur barding."; wearable = false; } else if (item.sub_type == ARM_NAGA_BARDING) { ostr << "Only nagas can wear naga barding."; wearable = false; } else { ostr << "You can wear pieces of armour with W and take " "them off again with T" #ifdef USE_TILE ", or, alternatively, simply click on their tiles to " "perform either action." #endif "."; } if (Tutorial.tutorial_type == TUT_MAGIC_CHAR && !is_light_armour(item) && get_armour_slot(item) == EQ_BODY_ARMOUR) { ostr << "\nNote that body armour with high evasion penalties " "may hinder your ability to learn and cast spells. " "Light armour such as robes, leather armour or any " "elven armour will be generally safe for any aspiring " "spellcaster."; } else if (Tutorial.tutorial_type == TUT_MAGIC_CHAR && is_shield(item)) { ostr << "\nNote that shields will hinder you ability to " "cast spells; the larger the shield, the bigger " "the penalty."; } else if (Tutorial.tutorial_type == TUT_RANGER_CHAR && is_shield(item)) { ostr << "\nNote that wearing a shield will greatly decrease " "the speed at which you can shoot arrows."; } if (!item_type_known(item) && (is_artefact(item) || get_equip_desc( item ) != ISFLAG_NO_DESC)) { ostr << "\n\nWeapons and armour that have unusual descriptions " << "like this are much more likely to be of higher " << "enchantment or have special properties, good or bad. " << "The rarer the description, the greater the potential " << "value of an item."; Tutorial.tutorial_events[TUT_SEEN_RANDART] = false; } if (wearable) { if (item_known_cursed( item )) { ostr << "\nA cursed piece of armour, once worn, cannot be " "removed again until the curse has been lifted by " "reading a scroll of remove curse or enchant " "armour."; } if (gives_resistance(item)) { ostr << "\n\nThis armour offers its wearer protection from " "certain sources. For an overview of your" " resistances (among other things) type %" #ifdef USE_TILE " or click on your avatar with the right mouse " "button" #endif "."; } if (gives_ability(item)) { ostr << "\n\nWhen worn, some types of armour (such as " "this one) offer certain abilities you can " "activate. "; ostr << _tut_abilities(item); } } Tutorial.tutorial_events[TUT_SEEN_ARMOUR] = false; break; } case OBJ_WANDS: ostr << "The magic within can be unleashed by evoking " "(V) it."; #ifdef USE_TILE ostr << " Alternatively, you can 1) left mouse click on " "the monster you wish to target (or your player character " "to target yourself) while pressing the Alt key " "and pick the wand from the menu, or 2) " "left mouse click on the wand tile and then " "left mouse click on your target."; #endif Tutorial.tutorial_events[TUT_SEEN_WAND] = false; break; case OBJ_FOOD: ostr << "Food can simply be eaten" #ifdef USE_TILE ", something you can also do by left clicking on it" #endif ". "; if (item.sub_type == FOOD_CHUNK) { ostr << "Note that most species refuse to eat raw meat unless " "really hungry. "; if (food_is_rotten(item)) { ostr << "Even fewer can safely digest rotten meat, and " "you're probably not part of that group."; } } Tutorial.tutorial_events[TUT_SEEN_FOOD] = false; break; case OBJ_SCROLLS: ostr << "Type r to read this scroll" #ifdef USE_TILE "or simply click on it with your left mouse button" #endif "."; Tutorial.tutorial_events[TUT_SEEN_SCROLL] = false; break; case OBJ_JEWELLERY: { ostr << "Jewellery can be Put on or Removed " "again" #ifdef USE_TILE ", though in Tiles, either can be done by clicking on the " "item in your inventory" #endif "."; if (item_known_cursed( item )) { ostr << "\nA cursed piece of jewellery will cling to its " "unfortunate wearer's neck or fingers until the curse " "is finally lifted when he or she reads a scroll of " "remove curse."; } if (gives_resistance(item)) { ostr << "\n\nThis " << (item.sub_type < NUM_RINGS ? "ring" : "amulet") << " offers its wearer protection " "from certain sources. For an overview of your " "resistances (among other things) type %" #ifdef USE_TILE " or click on your avatar with the right mouse " "button" #endif "."; } if (gives_ability(item)) { ostr << "\n\nWhen worn, some types of jewellery (such as this " "one) offer certain abilities you can activate. "; ostr << _tut_abilities(item); } Tutorial.tutorial_events[TUT_SEEN_JEWELLERY] = false; break; } case OBJ_POTIONS: ostr << "Type q to quaff this potion" #ifdef USE_TILE "or simply click on it with your left mouse button" #endif "."; Tutorial.tutorial_events[TUT_SEEN_POTION] = false; break; case OBJ_BOOKS: if (item.sub_type == BOOK_MANUAL) { ostr << "A manual can greatly help you in training a skill. " "To use it, read it while your experience " "pool (the number in brackets) is large. Note that " "this will drain said pool, so only use this manual " "if you think you need the skill in question."; } else // It's a spellbook! { if (you.religion == GOD_TROG && (item.sub_type != BOOK_DESTRUCTION || !item_ident(item, ISFLAG_KNOW_TYPE))) { if (!item_ident(item, ISFLAG_KNOW_TYPE)) ostr << "It's a book, you can read it."; else { ostr << "A spellbook! You could Memorise some " "spells and then cast them with z."; } ostr << "\nAs a worshipper of " << god_name(GOD_TROG) << ", though, you might instead wish to burn this " "tome of hated magic by using the corresponding " "ability. " "Note that this only works on books that are lying " "on the floor and not on your current square. "; } else if (!item_ident(item, ISFLAG_KNOW_TYPE)) { ostr << "It's a book, you can read it" #ifdef USE_TILE ", something that can also be achieved by clicking " "on its tile in your inventory." #endif "."; } else if (item.sub_type == BOOK_DESTRUCTION) { ostr << "This magical item can cause great destruction " "- to you, or your surroundings. Use with care!"; } else if (!you.skills[SK_SPELLCASTING]) { ostr << "A spellbook! You could Memorise some " "spells and then cast them with z. "; ostr << "\nFor now, however, that will have to wait until " "you've learned the basics of Spellcasting by " "reading lots of scrolls."; } else // You actually can cast spells. { if (player_can_memorise(item)) { ostr << "Such a highlighted " "spell can be Memorised " "right away. "; } else { ostr << "You cannot memorise any " << (you.spell_no ? "more " : "") << "spells right now. This will change as you " "grow in levels and Spellcasting proficiency. "; } if (you.spell_no) { ostr << "\n\nTo do magic, "; #ifdef USE_TILE ostr << "you can left mouse click on the " "monster you wish to target (or on your " "player character to cast a spell on " "yourself) while pressing the Control " "key, and then select a spell from the " "menu. " "\n\nAlternatively, "; #endif ostr << "you can type z and choose a " "spell, e.g. a (check with ?). " "For attack spells you'll "; ostr << _tut_target_mode(true); } } } ostr << "\n"; Tutorial.tutorial_events[TUT_SEEN_SPBOOK] = false; break; case OBJ_CORPSES: Tutorial.tutorial_events[TUT_SEEN_CARRION] = false; if (item.sub_type == CORPSE_SKELETON) { ostr << "Skeletons can be used as components for certain " "necromantic spells. Apart from that, they are " "largely useless."; if (in_inventory(item)) { ostr << " In the drop menu you can select all skeletons " "and rotten chunks or corpses in your inventory " "at once with d&."; } break; } ostr << "Corpses lying on the floor can be chopped up with " "a sharp implement to produce chunks for food (though they " "may not be healthy)"; if (!food_is_rotten(item) && god_likes_fresh_corpses(you.religion)) { ostr << ", or offered as a sacrifice by " << god_name(you.religion) << " praying over them."; } ostr << ". "; if (food_is_rotten(item)) { ostr << "Rotten corpses won't be of any use to you, though, so " "you might just as well "; if (!in_inventory(item)) ostr << "ignore them. "; else { ostr << "drop this. Use d& to select all " "skeletons and rotten chunks or corpses in your " "inventory. "; } ostr << "No god will accept such rotten sacrifice, either."; } else { #ifdef USE_TILE ostr << " For an individual corpse in your inventory, the most " "practical way to chop it up is to drop it by clicking " "on it with your left mouse button while " "Shift is pressed, and then repeat that command " "for the corpse tile now lying on the floor."; #endif } if (!in_inventory(item)) break; ostr << "\n\nIf there are several items in your inventory you'd " "like to drop, the most convenient way is to use the " "drop menu. On a related note, butchering " "several corpses on a floor square is facilitated by " "using the chop prompt where c is a " "valid synonym for yes or you can directly chop " "all corpses."; break; case OBJ_STAVES: if (item_is_rod( item )) { if (!item_ident(item, ISFLAG_KNOW_TYPE)) { ostr << "\n\nTo find out what this rod might do, you have " "to wield and evoke it to see if you " "can use the spells hidden within" #ifdef USE_TILE ", both of which can be done by clicking on it" #endif "."; } else { ostr << "\n\nYou can use this rod's magic by " "wielding and evoking it" #ifdef USE_TILE ", both of which can be achieved by clicking on it" #endif "."; } } else { ostr << "This staff can enhance your spellcasting of specific " "spell schools. "; bool gives_resist = false; if (you.spell_no && !item_ident(item, ISFLAG_KNOW_TYPE)) { ostr << "You can find out which one by casting spells " "while wielding this staff. Eventually, the staff " "might react to a spell of its school and identify " "itself."; } else if (gives_resistance(item)) { ostr << "It also offers its wielder protection from " "certain sources. For an overview of your " "resistances (among other things) type %" #ifdef USE_TILE " or click on your avatar with the right mouse " "button" #endif "."; gives_resist = true; } if (!gives_resist && you.religion == GOD_TROG) { ostr << "\n\nSeeing how " << god_name(GOD_TROG, false) << " frowns upon the use of magic, this staff will be " "of little use to you and you might just as well " "drop it now."; } } Tutorial.tutorial_events[TUT_SEEN_STAFF] = false; break; case OBJ_MISCELLANY: if (is_deck(item)) { ostr << "Decks of cards are powerful magical items. Try " "wielding and evoking it" #ifdef USE_TILE ", either of which can be done by clicking on it" #endif ". You can read about the effect of a card by " "searching the game's database with ?/c."; } else { ostr << "Miscellaneous items sometimes harbour magical powers " "that can be harnessed by eVoking the item."; } Tutorial.tutorial_events[TUT_SEEN_MISC] = false; break; default: return; } ostr << ""; std::string broken = ostr.str(); linebreak_string2(broken, _get_tutorial_cols()); cgotoxy(1, wherey() + 2); formatted_string::parse_block(broken, false).display(); } // tutorial_describe_item() void tutorial_inscription_info(bool autoinscribe, std::string prompt) { // Don't print anything if there's not enough space. if (wherey() >= get_number_of_lines() - 1) return; std::ostringstream text; text << "<" << colour_to_str(channel_to_colour(MSGCH_TUTORIAL)) << ">"; bool longtext = false; if (wherey() <= get_number_of_lines() - (autoinscribe ? 10 : 8)) { text << EOL "Inscriptions are a powerful concept of Dungeon Crawl." EOL "You can inscribe items to differentiate them, or to comment on them, " EOL "but also to set rules for item interaction. If you are new to Crawl, " EOL "you can safely ignore this feature, though."; longtext = true; } if (autoinscribe && wherey() <= get_number_of_lines() - 6) { text << EOL "Artefacts can be autoinscribed to give a brief overview of their " EOL "known properties."; longtext = true; } text << EOL "(In the main screen, press ?6 for more information.)" EOL; text << ""; formatted_string::parse_string(text.str()).display(); // Ask a second time, if it's been a longish interruption. if (longtext && !prompt.empty() && wherey() <= get_number_of_lines() - 2) formatted_string::parse_string(prompt).display(); } bool tutorial_pos_interesting(int x, int y) { return (cloud_type_at(coord_def(x, y)) != CLOUD_NONE || _water_is_disturbed(x, y) || _tutorial_feat_interesting(grd[x][y])); } static bool _tutorial_feat_interesting(dungeon_feature_type feat) { // Altars and branch entrances are always interesting. if (feat >= DNGN_ALTAR_FIRST_GOD && feat <= DNGN_ALTAR_LAST_GOD) return (true); if (feat >= DNGN_ENTER_FIRST_BRANCH && feat <= DNGN_ENTER_LAST_BRANCH) return (true); switch (feat) { // So are statues, traps, and stairs. case DNGN_ORCISH_IDOL: case DNGN_GRANITE_STATUE: case DNGN_TRAP_MAGICAL: case DNGN_TRAP_MECHANICAL: case DNGN_TRAP_NATURAL: case DNGN_STONE_STAIRS_DOWN_I: case DNGN_STONE_STAIRS_DOWN_II: case DNGN_STONE_STAIRS_DOWN_III: case DNGN_STONE_STAIRS_UP_I: case DNGN_STONE_STAIRS_UP_II: case DNGN_STONE_STAIRS_UP_III: case DNGN_ESCAPE_HATCH_DOWN: case DNGN_ESCAPE_HATCH_UP: case DNGN_ENTER_PORTAL_VAULT: return (true); default: return (false); } } void tutorial_describe_pos(int x, int y) { _tutorial_describe_disturbance(x, y); _tutorial_describe_cloud(x, y); _tutorial_describe_feature(x, y); } static void _tutorial_describe_feature(int x, int y) { const dungeon_feature_type feat = grd[x][y]; const coord_def where(x, y); std::ostringstream ostr; ostr << "\n\n<" << colour_to_str(channel_to_colour(MSGCH_TUTORIAL)) << ">"; bool boring = false; switch (feat) { case DNGN_ORCISH_IDOL: case DNGN_GRANITE_STATUE: ostr << "It's just a harmless statue - or is it?\nEven if not " "a danger by themselves, statues often mark special " "areas, dangerous ones or ones harbouring treasure."; break; case DNGN_TRAP_MAGICAL: case DNGN_TRAP_MECHANICAL: ostr << "These nasty constructions can do physical damage (with " "darts or needles, for example) or have other, more " "magical effects. "; if (feat == DNGN_TRAP_MECHANICAL) { ostr << "You can attempt to deactivate the mechanical type by " "standing next to it and then pressing Ctrl " "and the direction of the trap. Note that this usually " "causes the trap to go off, so it can be quite a " "dangerous task.\n\n" "You can safely pass over a mechanical trap if " "you're flying or levitating."; } else { ostr << "Magical traps can't be disarmed, and unlike " "mechanical traps you can't avoid tripping them " "by levitating or flying over them."; } Tutorial.tutorial_events[TUT_SEEN_TRAP] = false; break; case DNGN_TRAP_NATURAL: // only shafts for now ostr << "The dungeon contains a number of natural obstacles such " "as shafts, which lead one to three levels down. They " "can't be disarmed, but you can safely pass over them " "if you're levitating or flying."; Tutorial.tutorial_events[TUT_SEEN_TRAP] = false; break; case DNGN_STONE_STAIRS_DOWN_I: case DNGN_STONE_STAIRS_DOWN_II: case DNGN_STONE_STAIRS_DOWN_III: ostr << "You can enter the next (deeper) level by following them " "down (>). To get back to this level again, " "press << while standing on the upstairs."; #ifdef USE_TILE ostr << " In Tiles, you can achieve the same, in either direction, " "by clicking the left mouse button while pressing " "Shift. "; #endif if (is_unknown_stair(where)) { ostr << "\n\nYou have not yet passed through this particular " "set of stairs. "; } Tutorial.tutorial_events[TUT_SEEN_STAIRS] = false; break; case DNGN_STONE_STAIRS_UP_I: case DNGN_STONE_STAIRS_UP_II: case DNGN_STONE_STAIRS_UP_III: if (you.your_level < 1) { ostr << "These stairs lead out of the dungeon. Following them " "will end the game. The only way to win is to " "transport the fabled Orb of Zot outside."; } else { ostr << "You can enter the previous (shallower) level by " "following these up (<<). This is ideal for " "retreating or finding a safe resting spot, since the " "previous level will have less monsters and monsters " "on this level can't follow you up unless they're " "standing right next to you. To get back to this " "level again, press > while standing on the " "downstairs."; #ifdef USE_TILE ostr << " In Tiles, you can perform either action simply by " "clicking the left mouse button while pressing " "Shift instead. "; #endif if (is_unknown_stair(where)) { ostr << "\n\nYou have not yet passed through this " "particular set of stairs. "; } } Tutorial.tutorial_events[TUT_SEEN_STAIRS] = false; break; case DNGN_ESCAPE_HATCH_DOWN: case DNGN_ESCAPE_HATCH_UP: ostr << "Escape hatches can be used to quickly leave a level with " "<< and >, respectively. Note that you will " "usually be unable to return right away."; Tutorial.tutorial_events[TUT_SEEN_ESCAPE_HATCH] = false; break; case DNGN_ENTER_PORTAL_VAULT: ostr << "This " << _describe_portal(where); Tutorial.tutorial_events[TUT_SEEN_PORTAL] = false; break; case DNGN_CLOSED_DOOR: case DNGN_DETECTED_SECRET_DOOR: if (!Tutorial.tut_explored) { ostr << "\nTo avoid accidentally opening a door you'd rather " "remain closed during travel or autoexplore, you can " "mark it with an exclusion from the map view " "(X) with ee while your cursor is on the " "grid in question. Such an exclusion will prevent " "autotravel from ever entering that grid until you " "remove the exclusion with another press of Xe."; } break; default: if (feat >= DNGN_ALTAR_FIRST_GOD && feat <= DNGN_ALTAR_LAST_GOD) { god_type altar_god = feat_altar_god(feat); if (you.religion == GOD_NO_GOD) { ostr << "This is your chance to join a religion! In general, " "the gods will help their followers, bestowing powers " "of all sorts upon them, but many of them demand a " "life of dedication, constant tributes or " "entertainment in return. \nYou can get information " "about " << god_name(altar_god) << " by pressing p while standing on the " "altar. Before taking up the responding faith you'll " "be asked for confirmation."; } else if (you.religion == altar_god) { if (god_likes_items(you.religion)) { ostr << "If " << god_name(you.religion) << " likes to have certain items or corpses " "sacrificed on altars, any appropriate item " "dropped on an altar during prayer, or " "already lying on an altar when you start " "praying will be automatically " "sacrificed to " << god_name(you.religion) << "."; } } else { ostr << god_name(you.religion) << " probably won't like it if you switch allegiance, " "but having a look won't hurt: to get information " "on "; ostr << god_name(altar_god); ostr << ", press p while standing on the " "altar. Before taking up the responding faith (and " "abandoning your current one!) you'll be asked for " "confirmation." "\nTo see your current standing with " << god_name(you.religion) << " press ^" #ifdef USE_TILE ", or press Shift while clicking with " "your right mouse button on your avatar" #endif "."; } Tutorial.tutorial_events[TUT_SEEN_ALTAR] = false; break; } else if (feat >= DNGN_ENTER_FIRST_BRANCH && feat <= DNGN_ENTER_LAST_BRANCH) { ostr << "An entryway into one of the many dungeon branches in " "Crawl. "; if (feat != DNGN_ENTER_TEMPLE) ostr << "Beware, sometimes these can be deadly!"; break; } else { // Describe blood-stains even for boring features. if (!is_bloodcovered(where)) return; boring = true; } } if (is_bloodcovered(where)) { if (!boring) ostr << "\n\n"; ostr << "Many forms of combat and some forms of magical attack " "will splatter the surrounings with blood (if the victim has " "any blood, that is). Some monsters can smell blood from " "a distance and will come looking for whatever the blood " "was spilled from."; } ostr << ""; std::string broken = ostr.str(); linebreak_string2(broken, _get_tutorial_cols()); formatted_string::parse_block(broken, false).display(); } static void _tutorial_describe_cloud(int x, int y) { cloud_type ctype = cloud_type_at(coord_def(x, y)); if (ctype == CLOUD_NONE) return; std::string cname = cloud_name(env.cgrid(coord_def(x, y))); std::ostringstream ostr; ostr << "\n\n<" << colour_to_str(channel_to_colour(MSGCH_TUTORIAL)) << ">"; ostr << "The " << cname << " "; if (ends_with(cname, "s")) ostr << "are "; else ostr << "is "; bool need_cloud = false; switch (ctype) { case CLOUD_BLACK_SMOKE: case CLOUD_GREY_SMOKE: case CLOUD_BLUE_SMOKE: case CLOUD_TLOC_ENERGY: case CLOUD_PURPLE_SMOKE: case CLOUD_MIST: case CLOUD_MAGIC_TRAIL: ostr << "harmless. "; break; default: if (!is_damaging_cloud(ctype, true)) { ostr << "currently harmless, but that could change at some point. " "Check the overview screen (%) to view your " "resistances."; need_cloud = true; } else { ostr << "probably dangerous, and you should stay out of it if you " "can. "; } } if (is_opaque_cloud(env.cgrid[x][y])) { ostr << (need_cloud? "\nThis cloud" : "It") << " is opaque. If two or more opaque clouds are between " "you and a square you won't be able to see anything in that " "square."; } ostr << ""; std::string broken = ostr.str(); linebreak_string2(broken, _get_tutorial_cols()); formatted_string::parse_block(broken, false).display(); } static void _tutorial_describe_disturbance(int x, int y) { if (!_water_is_disturbed(x, y)) return; std::ostringstream ostr; ostr << "\n\n<" << colour_to_str(channel_to_colour(MSGCH_TUTORIAL)) << ">"; ostr << "The strange disturbance means that there's a monster hiding " "under the surface of the shallow water. Other than non-submerged " "monsters, a submerged monster will not be autotargeted when doing " "a ranged attack while there are other, visible targets in sight. " "Of course you can still target it manually if you wish to."; ostr << ""; std::string broken = ostr.str(); linebreak_string2(broken, _get_tutorial_cols()); formatted_string::parse_block(broken, false).display(); } static bool _water_is_disturbed(int x, int y) { const coord_def c(x,y); const monsters *mon = monster_at(c); if (!mon || grd(c) != DNGN_SHALLOW_WATER || !you.see_cell(c)) return (false); return (!mon->visible_to(&you) && !mons_flies(mon)); } bool tutorial_monster_interesting(const monsters *mons) { if (mons_is_unique(mons->type) || mons->type == MONS_PLAYER_GHOST) return (true); // Highlighted in some way. if (_mons_is_highlighted(mons)) return (true); // The monster is (seriously) out of depth. if (you.level_type == LEVEL_DUNGEON && mons_level(mons->type) >= you.your_level + 8) { return (true); } return (false); } void tutorial_describe_monster(const monsters *mons) { std::ostringstream ostr; ostr << "\n\n<" << colour_to_str(channel_to_colour(MSGCH_TUTORIAL)) << ">"; bool dangerous = false; if (mons_is_unique(mons->type)) { ostr << "Did you think you were the only adventurer in the dungeon? " "Well, you thought wrong! These unique adversaries often " "possess skills that normal monster wouldn't, so be " "careful.\n\n"; dangerous = true; } else if (mons->type == MONS_PLAYER_GHOST) { ostr << "The ghost of a deceased adventurer, it would like nothing " "better than to send you the same way.\n\n"; dangerous = true; } // Don't call friendly horrible things dangerous. else if (!mons_att_wont_attack(mons->attitude)) { // 8 is the default value for the note-taking of OOD monsters. // Since I'm too lazy to come up with any measurement of my own // I'll simply reuse that one. int level_diff = mons_level(mons->type) - (you.your_level + 8); if (you.level_type == LEVEL_DUNGEON && level_diff >= 0) { ostr << "This kind of monster is usually only encountered " << (level_diff > 5 ? "much " : "") << "deeper in the dungeon, so it's probably " << (level_diff > 5 ? "extremely" : "very") << " dangerous!\n\n"; dangerous = true; } } if (mons->berserk()) { ostr << "A berserking monster is bloodthirsty and fighting madly. " "Such a blood rage makes it particularly dangerous!\n\n"; dangerous = true; } // Monster is highlighted. if (mons->friendly()) { ostr << "Friendly monsters will follow you around and attempt to aid " "you in battle. You can order your allies by talking " "to them."; if (!mons_att_wont_attack(mons->attitude)) { ostr << "\n\nHowever, it is only temporarily friendly, " "and will become dangerous again when this friendliness " "wears off."; } } else if (dangerous) { if (!Tutorial.tut_explored && mons->foe != MHITYOU) { ostr << "You can easily mark its square as dangerous to avoid " "accidentally entering into its field of view when using " "auto-explore or auto-travel. To do so, enter the level " "map with X and then press e when your " "cursor is hovering over the monster's grid. Doing so will " "mark this grid and all surrounding ones within a radius " "of 8 as \"excluded\" ones that explore or travel modus " "won't enter.\n"; #ifdef USE_TILE ostr << "Upon returning to the main map, you'll even find all " "surrounding grids visibly highlighted."; #endif } else { ostr << "This might be a good time to run away"; if (you.religion == GOD_TROG && !you.berserk() && !you.duration[DUR_EXHAUSTED] && you.hunger_state >= HS_SATIATED) { ostr << " or apply your Berserk ability"; } ostr << "."; } } else if (Options.stab_brand != CHATTR_NORMAL && mons_looks_stabbable(mons)) { ostr << "Apparently " << mons_pronoun((monster_type) mons->type, PRONOUN_NOCAP) << " has not noticed you - yet. Note that you do not have to " "engage every monster you meet. Sometimes, discretion is the " "better part of valour."; } else if (Options.may_stab_brand != CHATTR_NORMAL && mons_looks_distracted(mons)) { ostr << "Apparently " << mons_pronoun((monster_type) mons->type, PRONOUN_NOCAP) << " has been distracted by something. You could use this " "opportunity to sneak up on this monster - or to sneak away."; } ostr << ""; std::string broken = ostr.str(); linebreak_string2(broken, _get_tutorial_cols()); formatted_string::parse_block(broken, false).display(); } void tutorial_observe_cell(const coord_def& gc) { if (feat_is_escape_hatch(grd(gc))) learned_something_new(TUT_SEEN_ESCAPE_HATCH, gc); else if (feat_is_branch_stairs(grd(gc))) learned_something_new(TUT_SEEN_BRANCH, gc); else if (is_feature('>', gc)) learned_something_new(TUT_SEEN_STAIRS, gc); else if (is_feature('_', gc)) learned_something_new(TUT_SEEN_ALTAR, gc); else if (is_feature('^', gc)) learned_something_new(TUT_SEEN_TRAP, gc); else if (feat_is_closed_door(grd(gc))) learned_something_new(TUT_SEEN_DOOR, gc); else if (grd(gc) == DNGN_ENTER_SHOP) learned_something_new(TUT_SEEN_SHOP, gc); else if (grd(gc) == DNGN_ENTER_PORTAL_VAULT) learned_something_new(TUT_SEEN_PORTAL, gc); const int it = you.visible_igrd(gc); if (it != NON_ITEM) { const item_def& item(mitm[it]); if (Options.feature_item_brand != CHATTR_NORMAL && (is_feature('>', gc) || is_feature('<', gc))) { learned_something_new(TUT_STAIR_BRAND, gc); } else if (Options.trap_item_brand != CHATTR_NORMAL && is_feature('^', gc)) { learned_something_new(TUT_TRAP_BRAND, gc); } else if (Options.heap_brand != CHATTR_NORMAL && item.link != NON_ITEM) learned_something_new(TUT_HEAP_BRAND, gc); } }