/*
* 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 <cstring>
#include <sstream>
#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(
"<white>You must be new here indeed!</white>"
EOL EOL
"<cyan>You can be:</cyan>"
EOL EOL).display();
textcolor( LIGHTGREY );
for (int i = 0; i < TUT_TYPES_NUM; i++)
print_tutorial_menu(i);
formatted_string::parse_string(
EOL
"<brown>SPACE - Back to job selection; "
"Bksp - Back to species selection; X - Quit"
EOL "* - Random tutorial"
"</brown>" 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 << "<white>Welcome to Dungeon Crawl!</white>" EOL EOL
<< "Your object is to lead a <w>"
<< species_name(you.species, 1) << " " << you.class_name
<<
"</w> 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
" <white>?\?</white> - shows the items and the commands" EOL
" <white>S</white> - saves the game, to be resumed later "
"(but note that death is permanent)" EOL
" <white>x</white> - 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
" <lightblue>quickstart.txt</lightblue> - "
"A very short guide to Crawl." EOL
" <lightblue>crawl_manual.txt</lightblue> - "
"This contains all details on species, magic, skills, etc." EOL
" <lightblue>options_guide.txt</lightblue> - "
"Crawl's interface is highly configurable. This document " EOL
" explains all the options." EOL
EOL
"Press <white>Space</white> to proceed to the basics "
"(the screen division and movement)." EOL
"Press <white>Esc</white> 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 += "<white>";
result += info;
result += "</white>" EOL EOL;
result += "<lightblue>";
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 += "</lightblue>" 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 <w>@</w> in the center. The parts "
"of the map you remember but cannot currently see will be greyed "
"out.";
result += "</";
result += colour_to_str(channel_to_colour(MSGCH_TUTORIAL));
result += ">" EOL;
result += "<lightgrey> --more-- "
"Press <white>Escape</white> to skip the basics.</lightgrey>";
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 "
"<w>Health: " << you.hp << "/" << you.hp_max << "</w> "
"and meaning current out of maximum health points. When Health "
"drops to zero, you die." EOL
"<w>Magic: " << you.magic_points << "/" << you.max_magic_points
<< "</w> represents your energy for casting spells, although other "
"actions often draw from Magic, too." EOL
"<w>Str</w>ength, <w>Int</w>elligence, <w>Dex</w>terity 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"
"<w>Health: " << you.hp << "/" << you.hp_max << "</w> "
"and meaning current \n"
"out of maximum health points. When \n"
"Health drops to zero, you die. \n"
"<w>Magic: " << you.magic_points << "/" << you.max_magic_points
<< "</w> represents your energy \n"
"for casting spells, although other \n"
"actions often draw from Magic, too. \n"
"<w>Str</w>ength, <w>Int</w>elligence, <w>Dex</w>terity \n"
"below provide an all-around account \n"
"of the character's attributes. \n"
"Don't worry about the rest for now. \n"
<< "</" << colour_to_str(channel_to_colour(MSGCH_TUTORIAL)) << ">"
" \n"
"<lightgrey> --more-- "
"Press <white>Escape</white> to skip the basics.</lightgrey>\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 "
"<w>Ctrl-P</w>"
#ifdef USE_TILE
" or by <w>clicking into the message area</w>"
#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 += "<lightgrey> --more-- "
"Press <white>Escape</white> to skip the basics.</lightgrey>";
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 <w>hjklyubn</w>. "
#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 <w>?\?</w>, 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<monsters*> 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 <w>s</w>, <w>.</w>, <w>delete</w> or "
"<w>keypad-5</w>. Pressing <w>5</w> or "
"<w>shift-and-keypad-5</w>"
#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 "
"(<w>?\?</w>) 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 (<w>o</w>). 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 <w>Ctrl-G</w> or <w>G</w> and enter "
"the desired destination. If your travel gets interrupted, "
"issuing <w>Ctrl-G Enter</w> or <w>G Enter</w> will continue "
"it.";
}
else if (Tutorial.tut_stashes)
{
text = "You can search among all items existing in the dungeon with "
"the <w>Ctrl-F</w> command. For example, "
"<w>Ctrl-F \"knife\"</w> will list all knives. You can then "
"travel to one of the spots. It is even possible to enter "
"words like <w>\"shop\"</w> or <w>\"altar\"</w>.";
}
else
{
int hint = random2(4);
switch (hint)
{
case 0:
text = "The game keeps an automated logbook for your characters. "
"Use <w>?:</w> to read it. You can enter notes manually "
"with the <w>:</w> command. Once your character perishes, "
"two morgue files are left in the <w>morgue/</w> "
"directory. The one ending in .txt contains a copy of "
"your logbook. During play, you can create a dump file "
"with <w>#</w>.";
break;
case 1:
text = "Crawl has a macro function built in: press <w>~m</w> "
"to define a macro by first specifying a trigger key "
"(say, <w>F1</w>) and a command sequence, for example "
"<w>za+.</w>. The latter will make the <w>F1</w> "
"key always zap the spell in slot a at the nearest "
"monster. For more information on macros, type <w>?~</w>.";
break;
case 2:
text = "The interface can be greatly customised. All options are "
"explained in the file <w>options_guide.txt</w> which "
"can be found in the <w>docs</w> directory. The options "
"themselves are set in <w>init.txt</w> or "
"<w>.crawlrc</w>. 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 <w>#crawl</w> IRC (Internet Relay Chat) "
"channel on freenode (<w>irc.freenode.net</w>).";
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 <w>p</w>rayer 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 "
"<w>left mouse button</w>";
#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 "
"<w>5</w> or <w>Shift-numpad 5</w>"
#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 "
"<w>a</w>.";
}
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 << "</" << colour_str << ">";
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 <w>right mouse button</w>."
#else
text += _colourize_glyph(get_mons_glyph(&mon));
text += " is a monster, usually depicted by a letter. Some typical "
"early monsters look like <brown>r</brown>, <green>l</green>, "
"<brown>K</brown> or <lightgrey>g</lightgrey>. ";
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 <w>x</w> and "
"moving the cursor on the monster, and read the monster "
"description by then pressing <w>v</w>. "
#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 "
"<w>i</w>nventory, 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 <w>w</w>ield it, then follow the instructions.";
#ifdef USE_TILE
text += EOL "As a short-cut you can also <w>right-click</w> on your "
"bow to read its description, and <w>left-click</w> to wield "
"it.";
#endif
}
#ifdef USE_TILE
else
{
text += "Clicking with your <w>right mouse button</w> 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 "
"<w>i</w>nventory, you'll find an explanation of how to do "
"this.";
#ifdef USE_TILE
text += EOL "As a short-cut you can also <w>right-click</w> 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 <w>g</w> or "
"<w>,</w> you will pick it up. "
#ifndef USE_TILE
"Generally, items are shown by non-letter symbols like "
"<w>%?!\"=()[</w>. "
#else
"You can also pick up an item by clicking on your <w>left mouse "
"button</w> 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 <w>left mouse click</w> while pressing the <w>Shift key</w>. "
"Whenever you <w>right-click</w> on an item"
#else
"<w>d</w>. Any time you look at an item in your <w>i</w>nventory"
#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 <w>" << new_god_name
<< "</w>. ";
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 <w>^</w>"
#ifdef USE_TILE
" (alternatively press <w>Shift</w> while "
"<w>right-clicking</w> 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 <w>still</w> 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 <w>></w>. To return find "
#ifdef USE_TILE
"a similar looking portal tile "
#else
"another <w>\\</w> (though NOT the ancient stone arch you'll start "
"out on) "
#endif
"and press <w><<</w>.";
#ifdef USE_TILE
text << "\nAlternatively, clicking on your <w>left mouse button</w> "
"while pressing the <w>Shift key</w> 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
" ('<w>!</w>'). Use "
#else
". Simply click on it with your <w>left mouse button</w>, or "
"press "
#endif
"<w>q</w> to quaff it.";
break;
case TUT_SEEN_SCROLL:
text << "You have picked up your first scroll"
#ifndef USE_TILE
" ('<w>?</w>'). Type "
#else
". Simply click on it with your <w>left mouse button</w>, or "
"type "
#endif
"<w>r</w> to read it.";
break;
case TUT_SEEN_WAND:
text << "You have picked up your first wand"
#ifndef USE_TILE
" ('<w>/</w>'). Type "
#else
". Simply click on it with your <w>left mouse button</w>, or "
"type "
#endif
"<w>V</w> to evoke it.";
break;
case TUT_SEEN_SPBOOK:
text << "You have picked up a book ";
#ifndef USE_TILE
text << "('<w>";
text << static_cast<char>(get_item_symbol(SHOW_ITEM_BOOK))
<< "'</w>) "
<< "that you can read by typing <w>r</w>. "
"If it's a spellbook you'll then be able to memorise spells "
"via <w>M</w> and cast a memorised spell with <w>z</w>.";
#else
text << ". You can read it doing a <w>right click</w> with your "
"mouse, and memorise spells with a <w>left click</w>. ";
#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 "
"<w>a</w>bility.";
}
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 <w>i</w>nventory at the item in "
"question.";
#else
"clicking on it with your <w>right mouse button</w>.";
#endif
break;
case TUT_SEEN_WEAPON:
text << "This is the first weapon "
#ifndef USE_TILE
"('<w>)</w>') "
#endif
"you've picked up. Use <w>w</w> "
#ifdef USE_TILE
"or click on it with your <w>left mouse button</w> "
#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 <w>i</w>nventory"
#ifdef USE_TILE
" or by <w>right-clicking</w> 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
"('<w>(</w>') "
#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
"<w>Right-clicking</w> on "
#else
"Selecting "
#endif
"the item in your <w>i</w>nventory 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
"('<w>[</w>') "
#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 <w>right mouse click</w> will "
"show you its properties.";
#else
"Use <w>W</w> to wear it and <w>T</w> to take it off again. "
"You can view its properties from your <w>i</w>nventory.";
#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
" ('<w>%</w>')"
#endif
". You can eat it by typing <w>e</w>"
#ifdef USE_TILE
" or by clicking on it with your <w>left mouse button</w>"
#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 <w>c</w>hop 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 <w>e</w>at 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 "
"<w>left mouse click</w> while pressing <w>Shift</w>, and "
"then eat the resulting chunks with <w>Shift + right mouse "
"click</w>.";
#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 "
"<w>i</w>nventory.";
break;
case TUT_SEEN_JEWELLERY:
text << "You have picked up a a piece of jewellery, either a ring"
#ifndef USE_TILE
<< " ('<w>=</w>')"
#endif
<< " or an amulet"
#ifndef USE_TILE
<< " ('<w>\"</w>')"
<< ". Type <w>P</w> to put it on and <w>R</w> to remove "
"it. You can view its properties from your <w>i</w>nventory"
#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 <w>right "
"mouse button</w> 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 e<w>V</w>oke "
#else
"e<w>V</w>oking "
#endif
"it. Some items need to be wielded first before you can "
"e<w>v</w>oke them. As usual, selecting it from your "
"<w>i</w>nventory 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 '<w>";
text << static_cast<char>(get_item_symbol(SHOW_ITEM_STAVE))
<< "</w>'"
#endif
". Both must be <w>w</w>ielded 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 "
"e<w>v</w>oking 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 <w>left mouse button</w>.";
#endif
text << "\nDuring the tutorial you can reread this information at "
"any time by selecting the item in question in your "
"<w>i</w>nventory.";
break;
case TUT_SEEN_GOLD:
text << "You have picked up your first pile of gold"
#ifndef USE_TILE
" ('<yellow>$</yellow>')"
#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 <w>$</w>. It will also be "
"listed on the <w>%</w> 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 (<w>></w>). To get back to "
"this level again, press <w><<</w> while standing on the "
"upstairs.";
#ifdef USE_TILE
text << "\nAlternatively, clicking on your <w>left mouse button</w> "
"while pressing the <w>Shift key</w> 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 <w><<</w> and <w>></w>, "
"respectively "
#ifdef USE_TILE
"(or by using your <w>left mouse button</w> in combination "
"with the <w>Shift key</w>)"
#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 <w><<</w> or "
"<w>></w> 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 <w>^</w> 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 "
"<w>p</w> 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 <w><<</w> "
#ifdef USE_TILE
", or by pressing <w>Shift</w> and clicking on it with your "
"<w>left mouse button</w> "
#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 <w>@</w> 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 "
"<w>C</w> while standing next to it. If there are several "
"doors, you will then be prompted for a direction. "
"Alternatively, you can also use <w>Ctrl-Direction</w>.";
#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 (<w>X</w>) with "
"<w>ee</w> 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 <w>Xe</w>.";
}
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 <w>s</w>, "
"<w>.</w>, <w>delete</w> or <w>keypad-5</w>. Pressing "
"<w>5</w> or <w>shift-and-keypad-5</w> "
#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 <w>Ma</w>, which can then be zapped "
"with <w>zb</w>.";
#ifdef USE_TILE
text << " Memorising is also possible by doing a <w>left "
"click</w> 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 <w>m</w>.";
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. <w>Strength</w> "
"influences the amount you can carry, and increases the damage "
"you deal in melee. <w>Dexterity</w> increases your evasion "
"and thus influences your chance of dodging attacks or traps. "
"<w>Intelligence</w> 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 <w>@</w> for more details. A list of all "
"possible enchantments is in the manual (<w>?5</w>).";
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 <w>5</w>"
#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 <w>5</w>"
#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 <w>maximum</w> "
"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 <w>c</w>hop 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 <w>c</w>hop 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 "
"<w>e</w>at 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 <w>,</w> or <w>g</w> "
#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 "
"<w>d</w>rop 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 <w>Ctrl-F</w> command, which will let you "
"seach for all known items in the dungeon. For example, "
"<w>Ctrl-F \"knife\"</w> 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 <w>d</w>rop them now. When selecting items from "
"a menu, there's a shortcut (<w>&</w>) 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 "
"<w>c</w>hop up";
if (_cant_butcher())
{
text << "(or which you <w>could</w> 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 <w>could</w> 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 "
"<w>e</w>at. 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 <w>right mouse button</w> onto the corpse "
"or chunk.";
#else
"<w>v</w>iewing a corpse or chunk on the floor or by having a "
"look at it in your <w>i</w>nventory.";
#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 <w>p</w>raying over it to offer it. ";
break;
case TUT_SHIFT_RUN:
text << "Walking around takes fewer keystrokes if you press "
"<w>Shift-direction</w> or <w>/ direction</w>. "
"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 <w>X</w> to bring up the level map. Typing <w>?</w> "
"shows the list of level map commands. "
"Most importantly, moving the cursor to a spot and pressing "
"<w>.</w> or <w>Enter</w> lets your character move there on "
"its own.";
#ifdef USE_TILE
text << "\nAlso, clicking on the right-side minimap with your "
"<w>right mouse button</w> will zoom into that dungeon area. "
"Clicking with the <w>left mouse button</w> 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 <w>o</w> 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 (<w>X</w>) "
"by pressing <w>></w>. The cursor will jump to the nearest "
"staircase, and by pressing <w>.</w> or <w>Enter</w> 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 <w>s</w>, <w>.</w> or for 100 turns with "
"<w>5</w> "
#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 <w>.</w> or <w>s</w>, or for 100 turns with "
"<w>5</w> or <w>Shift-numpad 5</w>"
#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 (<w>X</w>) and then toggling the "
"exclusion radius on the monster position with <w>e</w>. "
"To make this easier some immobile monsters listed in the "
"<w>auto_exclude</w> 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 <w>Xee</w> 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 <w>5</w> or "
"<w>shift-numpad-5</w>"
#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 <w>q</w>uaff 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 <w>a</w>bility. 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 <w>@</w> 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 (<w>a</w>) "
"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 e<w>V</w>oke 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 <w>A</w>.";
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 <w>a</w> 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 <w>^</w> "
#ifdef USE_TILE
"or press <w>Shift</w> and <w>right-click</w> 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 <w>a</w> 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 <w>a</w> 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 <w>Shift</w> and <w>right-click</w> on your avatar.";
#else
"type <w>^</w>.";
#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 <w>'</w>.";
}
else
{
text << "You can easily switch back to your weapon in slot a by "
"pressing <w>'</w>. To change the slot of an item, type "
"<w>=i</w> 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 <w>i</w>nventory, 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 <w>x</w> 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 <w>t</w> "
"to talk to them.";
if (!mons_att_wont_attack(m->attitude))
{
text << "\nHowever, it is only <w>temporarily</w> 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 <w>do</w> 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 <w>technically</w> 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 <w>\\</w>)"
#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 <w>z\?</w> screen with and "
"without the item being worn.\n\n";
}
text << "If the spellcasting success chance is high (which can be "
"checked by entering <w>z\?</w> or <w>I</w>) 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 <w>z\?!</w> or <w>II</w>. "
"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
"<w>right-click</w> on the player avatar.";
#else
"press <w>%</w>.";
#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 <w>f</w>ire 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 <w>i</w>nventory, but you should "
"also check ";
std::vector<std::string> listed;
if (Tutorial.tutorial_type == TUT_MAGIC_CHAR)
listed.push_back("your spells (<w>z?</w>)");
if (!your_talents(false).empty())
listed.push_back("your <w>a</w>bilities");
if (Tutorial.tutorial_type != TUT_MAGIC_CHAR || how_mutated())
listed.push_back("your set of mutations (<w>A</w>)");
if (you.religion != GOD_NO_GOD)
listed.push_back("your religious standing (<w>^</w>)");
listed.push_back("the message history (<w>Ctrl-P</w>)");
listed.push_back("the character overview screen (<w>%</w>)");
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 <w>#</w> command. "
"You can then find said file in the <w>morgue/</w> folder (<w>"
<< you.your_name << ".txt</w>) 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 '<w>!</w>' or '<w>?</w>' to "
"toggle between ability selection and description.";
linebreak_string2(broken, _get_tutorial_cols());
text << broken;
text << "</" << colour_to_str(channel_to_colour(MSGCH_TUTORIAL)) << ">";
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>cyan percent value</cyan> shows your progress "
"towards the next skill level. You can toggle which skills to train by "
"pressing their slot letters. A <darkgrey>greyish</darkgrey> skill "
"will increase at a decidedly slower rate and ease training of others. "
"Press <w>?</w> to read your skills' descriptions.";
linebreak_string2(broken, _get_tutorial_cols());
text << broken;
text << "</" << colour_to_str(channel_to_colour(MSGCH_TUTORIAL)) << ">";
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 <w>?</w> again to return to the skill "
"selection.";
linebreak_string2(broken, _get_tutorial_cols());
text << broken;
text << "</" << colour_to_str(channel_to_colour(MSGCH_TUTORIAL)) << ">";
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 <w>+</w> or <w>-</w>. "
"Once you're aiming at the correct monster, simply hit "
"<w>f</w>, <w>Enter</w> or <w>.</w> to shoot at it. "
"If you miss, <w>";
if (spells)
result += "zap";
else
result += "ff";
result += "</w> 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 <w>w</w>ield it"; break;
case OBJ_ARMOUR: str += "first <w>W</w>ear it"; break;
case OBJ_JEWELLERY: str += "first <w>P</w>ut it on"; break;
default:
str += "<r>(BUG! this item shouldn't give an ability)</r>";
break;
}
str += ", then ";
}
str += "enter the ability menu with <w>a</w>, 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 <w>f</w> to fire, then <w>";
result += item.slot;
result += "</w> 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 <w>%</w>"
#ifdef USE_TILE
" or click on your avatar with the <w>right mouse "
"button</w>"
#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>w</w>, or use "
"<w>'</w> to switch between the weapons in slot "
"a and b. (Use <w>=i</w> 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 <w>"
<< skill_name(best_wpskill)
<< "</w> for a while, so maybe you should "
"continue training that rather than <w>"
<< skill_name(curr_wpskill)
<< "</w>. (Type <w>m</w> 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 "
"<w>left mouse click</w> on the monster while "
"prssing the <w>Shift key</w>. Alternatively, "
"you can <w>left mouse click</w> on the tile for "
"the ammo you wish to fire, and then <w>left "
"mouse click </w> on the monster.\n\n";
ostr << "To launch ammunition using the keyboard, ";
#endif
ostr << "you only need to "
"<w>f</w>ire 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 "
"<w>f</w>ired. ";
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 <w>f</w>ired 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 << "<w>left mouse click</w> on the monster you want "
"to hit while pressing the <w>Shift key</w>. "
"Alternatively, you can <w>left mouse click</w> on "
"this tile of the ammo you want to fire, and then "
"<w>left mouse click</w> on the monster you want "
"to hit.\n\n"
"To launch this ammo using the keyboard, you can "
"simply ";
#endif
ostr << "<w>f</w>ire "
<< (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 <w>w</w>ield 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 <w>A</w> 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 <w>A</w> 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>W</w> and take "
"them off again with <w>T</w>"
#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 <w>%</w>"
#ifdef USE_TILE
" or click on your avatar with the <w>right mouse "
"button</w>"
#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 "
"(<w>V</w>) it.";
#ifdef USE_TILE
ostr << " Alternatively, you can 1) <w>left mouse click</w> on "
"the monster you wish to target (or your player character "
"to target yourself) while pressing the <w>Alt key</w> "
"and pick the wand from the menu, or 2) "
"<w>left mouse click</w> on the wand tile and then "
"<w>left mouse click</w> on your target.";
#endif
Tutorial.tutorial_events[TUT_SEEN_WAND] = false;
break;
case OBJ_FOOD:
ostr << "Food can simply be <w>e</w>aten"
#ifdef USE_TILE
", something you can also do by <w>left clicking</w> 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 <w>r</w> to read this scroll"
#ifdef USE_TILE
"or simply click on it with your <w>left mouse button</w>"
#endif
".";
Tutorial.tutorial_events[TUT_SEEN_SCROLL] = false;
break;
case OBJ_JEWELLERY:
{
ostr << "Jewellery can be <w>P</w>ut on or <w>R</w>emoved "
"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 <w>%</w>"
#ifdef USE_TILE
" or click on your avatar with the <w>right mouse "
"button</w>"
#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 <w>q</w> to quaff this potion"
#ifdef USE_TILE
"or simply click on it with your <w>left mouse button</w>"
#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, <w>r</w>ead 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 <w>r</w>ead it.";
else
{
ostr << "A spellbook! You could <w>M</w>emorise some "
"spells and then cast them with <w>z</w>.";
}
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 "
"<w>a</w>bility. "
"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 <w>r</w>ead 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 <w>M</w>emorise some "
"spells and then cast them with <w>z</w>. ";
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 <lightblue>highlighted "
"spell</lightblue> can be <w>M</w>emorised "
"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 <w>left mouse click</w> on the "
"monster you wish to target (or on your "
"player character to cast a spell on "
"yourself) while pressing the <w>Control "
"key</w>, and then select a spell from the "
"menu. "
"\n\nAlternatively, ";
#endif
ostr << "you can type <w>z</w> and choose a "
"spell, e.g. <w>a</w> (check with <w>?</w>). "
"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 <w>d&</w>.";
}
break;
}
ostr << "Corpses lying on the floor can be <w>c</w>hopped 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)
<< " <w>p</w>raying 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 << "<w>d</w>rop this. Use <w>d&</w> 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 <w>left mouse button</w> while "
"<w>Shift</w> 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 "
"<w>d</w>rop menu. On a related note, butchering "
"several corpses on a floor square is facilitated by "
"using the <w>c</w>hop prompt where <w>c</w> is a "
"valid synonym for <w>y</w>es or you can directly chop "
"<w>a</w>ll 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 <w>w</w>ield and e<w>v</w>oke 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 "
"<w>w</w>ielding and e<w>v</w>oking 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 <w>%</w>"
#ifdef USE_TILE
" or click on your avatar with the <w>right mouse "
"button</w>"
#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 "
"<w>d</w>rop 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 "
"<w>w</w>ielding and e<w>v</w>oking 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 <w>?/c</w>.";
}
else
{
ostr << "Miscellaneous items sometimes harbour magical powers "
"that can be harnessed by e<w>V</w>oking the item.";
}
Tutorial.tutorial_events[TUT_SEEN_MISC] = false;
break;
default:
return;
}
ostr << "</" << colour_to_str(channel_to_colour(MSGCH_TUTORIAL)) << ">";
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 <w>?6</w> for more information.)" EOL;
text << "</" << colour_to_str(channel_to_colour(MSGCH_TUTORIAL)) << ">";
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 <w>Ctrl</w> "
"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 (<w>></w>). To get back to this level again, "
"press <w><<</w> while standing on the upstairs.";
#ifdef USE_TILE
ostr << " In Tiles, you can achieve the same, in either direction, "
"by clicking the <w>left mouse button</w> while pressing "
"<w>Shift</w>. ";
#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 (<w><<</w>). 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 <w>></w> while standing on the "
"downstairs.";
#ifdef USE_TILE
ostr << " In Tiles, you can perform either action simply by "
"clicking the <w>left mouse button</w> while pressing "
"<w>Shift</w> 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 "
"<w><<</w> and <w>></w>, 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 "
"(<w>X</w>) with <w>ee</w> 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 <w>Xe</w>.";
}
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 <w>"
<< god_name(altar_god)
<< "</w> by pressing <w>p</w> 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 "
"<w>d</w>ropped on an altar during prayer, or "
"already lying on an altar when you start "
"<w>p</w>raying 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 <w>";
ostr << god_name(altar_god);
ostr << "</w>, press <w>p</w> 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 <w>^</w>"
#ifdef USE_TILE
", or press <w>Shift</w> while clicking with "
"your <w>right mouse button</w> 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 << "</" << colour_to_str(channel_to_colour(MSGCH_TUTORIAL)) << ">";
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 (<w>%</w>) 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 << "</" << colour_to_str(channel_to_colour(MSGCH_TUTORIAL)) << ">";
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 << "</" << colour_to_str(channel_to_colour(MSGCH_TUTORIAL)) << ">";
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 <w>t</w>alking "
"to them.";
if (!mons_att_wont_attack(mons->attitude))
{
ostr << "\n\nHowever, it is only <w>temporarily</w> 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 <w>X</w> and then press <w>e</w> 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 <w>a</w>bility";
}
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 << "</" << colour_to_str(channel_to_colour(MSGCH_TUTORIAL)) << ">";
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);
}
}