Also, some more clean-up of tutorial.cc. As this involved a lot of spacing changes it was easier to just replace the file as a whole. Sorry about that. There are still some differences between the Linux and Windows versions that I don't really understand but using get_number_of_cols helps a lot.
git-svn-id: https://crawl-ref.svn.sourceforge.net/svnroot/crawl-ref/trunk@1055 c06c8d41-db1a-0410-9941-cceddc491573
7NDXS36TE7QVXTXJWMYSVG5UHCCLPIO4VL6NXFGTDK3ZNKE3A2IAC
4XGOVPFCU6KZIYHKWCHUTZY6G5S326DKBG3UREPR34Q4TSDD3TAAC
XPCGZBHHSL6MB3ORMUJI64BAERU6AZTIY6RK56BBW7SNB3IK24IAC
NQMXQ6OQVUSC7Y7F7IL252QW4A5JED224EECNHWAM4ZZYVNY745AC
77H4BWWPPGLM3PLZH4QTAJRXIZTSDVNCOKZE223I437FN2UJ34RQC
X3RDT655FEYO6XEVPIUAPEPJZAFE55KZBH2AZOLK3NGHINMVIGFQC
K2CS6TCX2NDVL2ASEHGP4J4K4IJ6FP3ANNKTSIWVG43HPYSBX6ZQC
RPOZZWKG5GLPHVZZ7ZKMKS64ZMV2LDCQSARBJFJ6FZOTOKCQO7FAC
Q5SFQO7ANODRI6OXKHPFQ4QWKGQ367S64DPURQW2TWK7ANTYO4NQC
74LQ7JXVLAFSHLI7LCBKFX47CNTYSKGUQSXNX5FCIUIGCC2JTR3QC
JR2RAQ523LOWNDYJNK6AZVKI6WVMI622PIV72XWOVZYPXPUKSQWAC
LIBWXPN6HLJAIGEFJYLOL4HLIUD236U3WM5QPHIZ3DSB4CCUJERAC
SDLKLUNFGVKDS55DDJZCBAVIB7NL3RRYPTACAY65SCUQKV6APFSAC
/*
* Created for Crawl Reference by JPEG on $Date: 2007-01-11$
*/
#include "AppHdr.h"
#include "tutorial.h"
#include <cstring>
#include "command.h"
#include "files.h"
#include "itemprop.h"
#include "menu.h"
#include "message.h"
#include "misc.h"
#include "newgame.h"
#include "output.h"
#include "player.h"
#include "religion.h"
#include "spl-util.h"
#include "stuff.h"
#include "view.h"
//#define TUTORIAL_DEBUG
#define TUTORIAL_VERSION 101
int INFO2_SIZE = 500;
char info2[500];
static std::string tut_debug_list(int event);
void save_tutorial( FILE* fp )
{
writeLong( fp, TUTORIAL_VERSION);
writeShort( fp, Options.tutorial_type);
for ( unsigned i = 0; i < TUT_EVENTS_NUM; ++i )
writeShort( fp, Options.tutorial_events[i] );
}
void load_tutorial( FILE* fp )
{
Options.tutorial_left = 0;
int version = readLong(fp);
Options.tutorial_type = readShort(fp);
if (version != TUTORIAL_VERSION)
return;
for ( long i = 0; i < TUT_EVENTS_NUM; ++i )
{
Options.tutorial_events[i] = readShort(fp);
Options.tutorial_left += Options.tutorial_events[i];
}
}
// override init file definition for some options
void init_tutorial_options()
{
if (!Options.tutorial_left) {
return;
}
Options.delay_message_clear = false;
Options.auto_list = true;
}
// tutorial selection screen and choice
bool pick_tutorial()
{
char keyn;
bool printed = false;
tut_query:
if (!printed)
{
clrscr();
textcolor( WHITE );
cprintf("You must be new here indeed!");
cprintf(EOL EOL);
textcolor( CYAN );
cprintf("You can be:");
cprintf(EOL EOL);
textcolor( LIGHTGREY );
for (int i = 0; i < TUT_TYPES_NUM; i++)
print_tutorial_menu(i);
textcolor( BROWN );
cprintf(EOL "SPACE - Back to class selection; Bksp - Back to race selection; X - Quit"
EOL "* - Random tutorial"
EOL);
printed = true;
}
keyn = c_getch();
if (keyn == '*')
keyn = 'a' + random2(TUT_TYPES_NUM);
// choose character for tutorial game
if (keyn >= 'a' && keyn <= 'a' + TUT_TYPES_NUM - 1)
{
Options.tutorial_type = keyn - 'a';
for (int i = 0; i < TUT_EVENTS_NUM; i++)
Options.tutorial_events[i] = 1;
Options.tut_explored = 1;
Options.tut_stashes = 1;
Options.tut_travel = 1;
// possibly combine to specialty_counter or something
Options.tut_spell_counter = 0;
Options.tut_throw_counter = 0;
Options.tut_berserk_counter = 0;
Options.tut_melee_counter = 0;
Options.tut_last_healed = 0;
Options.tutorial_left = TUT_EVENTS_NUM;
you.species = get_tutorial_species(Options.tutorial_type);
you.char_class = get_tutorial_job(Options.tutorial_type);
Options.random_pick = true; // random choice of starting spellbook
Options.weapon = WPN_HAND_AXE; // easiest choice for dwarves
return true;
}
if (keyn == CK_BKSP || keyn == ' ')
{
// in this case, undo previous choices
// set_startup_options();
you.species = 0; you.char_class = JOB_UNKNOWN;
Options.race = 0; Options.cls = 0;
}
switch (keyn)
{
case CK_BKSP:
choose_race();
break;
case ' ':
choose_class();
break;
case 'X':
cprintf(EOL "Goodbye!");
end(0);
break;
default:
printed = false;
goto tut_query;
}
return false;
}
void print_tutorial_menu(unsigned int type)
{
char letter = 'a' + type;
char desc[100];
switch(type)
{
/*
case TUT_MELEE_CHAR:
strcpy(desc, "(Standard melee oriented character)");
break;
*/
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
snprintf(info, INFO_SIZE, "Error in print_tutorial_menu: type = %d", type);
mpr( info );
break;
}
cprintf("%c - %s %s %s" EOL, letter, species_name(get_tutorial_species(type), 1),
get_class_name(get_tutorial_job(type)), desc);
}
unsigned int get_tutorial_species(unsigned int type)
{
switch(type)
{
/*
case TUT_MELEE_CHAR:
return SP_MOUNTAIN_DWARF;
*/
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;
}
}
// TO DO: check whether job and species are compatible...
unsigned int get_tutorial_job(unsigned int type)
{
switch(type)
{
/*
case TUT_MELEE_CHAR:
return JOB_FIGHTER;
*/
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;
}
}
static formatted_string formatted_paragraph(std::string text, unsigned int width)
{
std::string result;
size_t start = 0, pos = 0, end = 0;
for (; end < strlen(text.c_str()); pos = end+1)
{
// get next "word"
end = text.find(' ', pos);
if ( end >= strlen(text.c_str()) )
pos = end;
if (end - start >= width || end >= strlen(text.c_str()) )
{
end = pos;
result += text.substr(start, end-start-1);
result += EOL;
start = pos;
}
}
result += EOL;
return formatted_string::parse_string(result);
}
static formatted_string tut_starting_info(unsigned int width)
{
std::string result; // the entire page
std::string text; // one paragraph
result += "<white>Welcome to Dungeon Crawl!</white>" EOL EOL;
text += "Your object is to lead a ";
snprintf(info, INFO_SIZE, "%s %s ", species_name(get_tutorial_species(Options.tutorial_type), 1),
get_class_name(get_tutorial_job(Options.tutorial_type)));
text += info2;
text += "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 deep as possible but beware, as death lurks around "
"every corner here.";
result += formatted_paragraph(text, width);
result += "For the moment, just remember the following keys and their functions:" EOL;
result += " <white>?</white> - shows the items and the commands" EOL;
result += " <white>S</white> - saves the game, to be resumed later (but note that death is permanent)" EOL;
result += " <white>x</white> - examine something in your vicinity" EOL EOL;
text = "This tutorial will help you playing Crawl without reading any documentation. "
"If you feel intrigued, there is more information available in these files "
"(all of which can also be read in-game):";
result += formatted_paragraph(text,width);
result += " <darkgray>readme.txt</darkgray> - A very short guide to Crawl." EOL;
result += " <darkgray>manual.txt</darkgray> - This contains all details on races, magic, skills, etc." EOL;
result += " <darkgray>crawl_options.txt</darkgray> - Crawl's interface is highly configurable. This document " EOL;
result += " explains all the options (which themselves are set in " EOL;
result += " the init.txt or .crawlrc files)." EOL;
result += EOL;
result += "Press <white>Space</white> to proceed to the basics (the screen division and movement)." EOL;
result += "Press <white>Esc</white> to fast forward to the game start." EOL EOL;
return formatted_string::parse_string(result);
}
formatted_string tutorial_debug()
{
std::string result;
bool lbreak = false;
snprintf(info, INFO2_SIZE, "Tutorial Debug Screen");
int i = 39 - strlen(info) / 2;
result += std::string(i, ' ');
result += "<white>";
result += info;
result += "</white>" EOL EOL;
result += "<darkgray>";
for (i=0; i < TUT_EVENTS_NUM; i++)
{
snprintf(info, INFO_SIZE, "%d: %d (%s)",
i, Options.tutorial_events[i], tut_debug_list(i).c_str());
result += info;
// break text into 2 columns where possible
if (strlen(info) > 39 || lbreak)
{
result += EOL;
lbreak = false;
}
else
{
result += std::string(39 - strlen(info), ' ');
lbreak = true;
}
}
result += "</darkgray>" EOL EOL;
snprintf(info, INFO_SIZE, "tutorial_left: %d\n", Options.tutorial_left);
result += info;
result += EOL;
snprintf(info, INFO_SIZE, "You are a %s %s, and the tutorial will reflect that.",
species_name(get_tutorial_species(Options.tutorial_type), 1),
get_class_name(get_tutorial_job(Options.tutorial_type)));
result += info;
return formatted_string::parse_string(result);
}
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: // doesn't work for now
return "seen first random artefact";
case TUT_SEEN_FOOD:
return "seen first food";
case TUT_SEEN_CARRION:
return "seen first corpse";
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_STAIRS:
return "seen first stairs";
case TUT_SEEN_TRAPS:
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_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_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_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_POSTBERSERK:
return "learned about Berserk aftereffects";
case TUT_RUN_AWAY:
return "were told to run away";
case TUT_SHIFT_RUN:
return "learned about shift-run";
case TUT_MAP_VIEW:
return "learned about the level map";
/*
case TUT_AUTOEXPLORE:
return "learned about autoexplore";
*/
case TUT_DONE_EXPLORE:
return "explored a level";
case TUT_YOU_MUTATED:
return "caught a mutation";
case TUT_NEW_ABILITY:
return "gained a divine ability";
case TUT_WIELD_WEAPON:
return "wielded an unsuitable weapon";
default:
return "faced a bug";
}
}
static formatted_string tutorial_map_intro()
{
std::string result;
result = "<magenta>"
"What you see here is the typical Crawl screen. The upper left map "
"shows your hero as the <white>@<magenta> in the center. The parts "
"of the map you remember but cannot currently see, will be greyed "
"out."
"</magenta>" EOL;
result += "[--more-- Press <white>Escape</white> to skip the basics]";
linebreak_string2(result,80);
return formatted_string::parse_string(result);
}
static formatted_string tutorial_stats_intro()
{
std::string result;
result += "<magenta>";
result += "To the right, important properties of " EOL;
result += "the character are displayed. The most " EOL;
result += "basic one is your health, measured as " EOL;
snprintf(info, INFO_SIZE, "<white>HP: %d/%d<magenta>. ", you.hp, you.hp_max);
result += info;
if (Options.tutorial_type==TUT_MAGIC_CHAR) result += " ";
result += "These are your current out " EOL;
result += "of maximum health points. When health " EOL;
result += "drops to zero, you die. " EOL;
snprintf(info, INFO_SIZE, "<white>Magic: %d/%d<magenta>", you.magic_points, you.max_magic_points);
result += info;
result += " is your energy for casting " EOL;
result += "spells, although more mundane actions " EOL;
result += "often draw from Magic, too. " EOL;
result += "Further down, <white>Str<magenta>ength, <white>Dex<magenta>terity and " EOL;
result += "<white>Int<magenta>elligence are shown and provide an " EOL;
result += "all-around account of the character's " EOL;
result += "attributes. " EOL;
result += "</magenta>";
result += " " EOL;
result += "[--more-- Press <white>Escape</white> to skip the basics]" EOL;
result += " " EOL;
result += " " EOL;
return formatted_string::parse_string(result);
}
static formatted_string tutorial_message_intro()
{
std::string result;
result = "<magenta>"
"This lower 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 "
"<white>Ctrl-P<magenta>."
"</magenta>" EOL;
result += "[--more-- Press <white>Escape</white> to skip the basics]";
linebreak_string2(result,80);
return formatted_string::parse_string(result);
}
static void tutorial_movement_info()
{
std::string text;
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<magenta>. A basic "
"command list can be found under <w>?<magenta>, and the most important "
"commands will be explained to you as it becomes necessary. ";
print_formatted_paragraph(text, 80, MSGCH_TUTORIAL);
}
// copied from display_mutations and adapted
void tut_starting_screen()
{
int x1, x2, y1, y2;
int MAX_INFO = 4;
#ifdef TUTORIAL_DEBUG
MAX_INFO++;
#endif
char ch;
for (int i=0; i<=MAX_INFO; i++)
{
x1 = 1; y1 = 1;
x2 = 80; y2 = 25;
if (i==1 || i==3)
{
y1 = 18; // message window
}
else if (i==2)
{
x2 = 40; // map window
y2 = 18;
}
if (i==0)
clrscr();
gotoxy(x1,y1);
if (i==0)
tut_starting_info(x2).display();
else if (i==1)
tutorial_map_intro().display();
else if (i==2)
tutorial_stats_intro().display();
else if (i==3)
tutorial_message_intro().display();
else if (i==4)
tutorial_movement_info();
else
{
#ifdef TUTORIAL_DEBUG
clrscr();
gotoxy(x1,y1);
tutorial_debug().display();
#else
continue;
#endif
}
ch = c_getch();
redraw_screen();
if (ch == ESCAPE)
break;
}
}
void tutorial_death_screen()
{
Options.tutorial_left = 0;
std::string text;
mpr( "Condolences! Your character's premature perishing is a sad, but "
"common occurence in Crawl. Rest assured that with diligence and "
"playing experience your characters will last longer.\n", MSGCH_TUTORIAL, 0);
mpr( "Perhaps the following advice can improve your playing style:", MSGCH_TUTORIAL);
more();
if (Options.tutorial_type == TUT_MAGIC_CHAR
&& Options.tut_spell_counter < Options.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 (Options.tutorial_type == TUT_BERSERK_CHAR
&& Options.tut_berserk_counter < 1 )
{
text = "Don't forget to go berserk when fighting particularly "
"difficult foes. It is risky, but makes you faster "
"and beefier. Also try to pray prior to battles so that ";
text += god_name(you.religion);
text += " will soon provide more abilities.";
}
else if (Options.tutorial_type == TUT_RANGER_CHAR
&& 2*Options.tut_throw_counter < Options.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);
// If a character has been unusually busy with projectiles and spells
// give some other hint rather than the first one.
if (Options.tut_throw_counter + Options.tut_spell_counter >= Options.tut_melee_counter
&& hint == 0)
hint = random2(5)+1;
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. In Crawl, searching and resting are "
"one and the same. To search for one turn, press <w>s<magenta>, "
"<w>.<magenta>, <w>delete<magenta> or <w>keypad-5<magenta>. "
"Pressing <w>5<magenta> or <w>shift-and-keypad-5<magenta> will "
"let you rest for a longer time (you will stop resting 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.";
}
}
print_formatted_paragraph(text, 80, MSGCH_TUTORIAL);
more();
mpr( "See you next game!", MSGCH_TUTORIAL);
for ( long i = 0; i < TUT_EVENTS_NUM; ++i )
Options.tutorial_events[i] = 0;
}
void tutorial_finished()
{
std::string text;
Options.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 help screen (<w>?<magenta>) "
"will look different from now on. Here's a last playing hint:";
print_formatted_paragraph(text, 80, MSGCH_TUTORIAL);
more();
if (Options.tut_explored)
{
text = "Walking around and exploring levels gets easier by using "
"auto-explore (<w>Ctrl-O<magenta>). You can even make Crawl "
"automatically pick up interesting items by setting the "
"option <w>explore_greedy=true<magenta> in the init file.";
}
else if (Options.tut_travel)
{
text = "There is a convenient way for travelling between far away "
"dungeon levels: press <w>Ctrl-G<magenta> and enter the desired "
"destination. If your travel gets interrupted, issueing "
"<w>Ctrl-G Enter<magenta> will continue it.";
}
else if (Options.tut_stashes)
{
text = "You can search among all items existing in the dungeon with "
"the <w>Ctrl-F<magenta> command. For example, "
"<w>Ctrl-F \"knife\"<magenta> will list all knives. You can then "
"travel to one of the spots. It is even possible to enter words "
"like <w>\"shop\"<magenta> or <w>\"altar\"<magenta>.";
}
else
{
int hint = random2(3);
switch (hint)
{
case 0:
text = "The game keeps an automated logbook for your characters. Use "
"<w>?:<magenta> to read it. You can enter notes manually with "
"the <w>:<magenta> command. Once your character perishes, two "
"morgue files are left in the <w>/morgue<magenta> directory. "
"The one ending in .txt contains a copy of your logbook. "
"During play, you can create a dump file with <w>#<magenta>.";
break;
case 1:
text = "Crawl has a macro function built in: press <w>~m<magenta> "
"to define a macro by first specifying a trigger key (say, "
"<w>F1<magenta>) and a command sequence, for example "
"<w>Za+.<magenta>. The latter will make the <w>F1<magenta> "
"key always zap the spell in slot a at the nearest monster. "
"For more information on macros, type <w>?~<magenta>.";
break;
case 2:
text = "The interface can be greatly customised. All options are "
"explained in the file <w>crawl_options.txt<magenta> which "
"can be found in the <w>/docs<magenta> directory. The "
"options themselves are set in <w>init.txt<magenta> or "
"<w>.crawlrc<magenta>. Crawl will complain when it can't "
"find either file.\n";
break;
default:
text = "Oops... No hint for now. Better luck next time!";
}
}
print_formatted_paragraph(text, 80, MSGCH_TUTORIAL);
more();
for ( long i = 0; i < TUT_EVENTS_NUM; ++i )
Options.tutorial_events[i] = 0;
}
void tutorial_prayer_reminder()
{
if (Options.tut_just_triggered)
return;
if (coinflip()) // always would be too annoying
{
std::string text;
text = "Remember to <w>p<magenta>ray before battle, so as to dedicate "
"your kills to ";
text += god_name(you.religion);
text += ". Should the monster leave a corpse, consider "
"<w>D<magenta>issecting it as a sacrifice to ";
text += god_name(you.religion);
text += ", as well.";
print_formatted_paragraph(text, 80, MSGCH_TUTORIAL);
}
}
void tutorial_healing_reminder()
{
if (you.poisoning)
{
if (Options.tutorial_events[TUT_NEED_POISON_HEALING])
learned_something_new(TUT_NEED_POISON_HEALING);
}
else
{
if (Options.tutorial_events[TUT_NEED_HEALING])
learned_something_new(TUT_NEED_HEALING);
else if (you.num_turns - Options.tut_last_healed >= 50 && !you.poisoning)
{
if (Options.tut_just_triggered)
return;
std::string text;
text = "Remember to rest between fights and to enter unexplored "
"terrain with full hitpoints and/or magic. For resting, "
"press <w>5<magenta> or <w>Shift-numpad 5<magenta>.";
print_formatted_paragraph(text, 80, MSGCH_TUTORIAL);
}
Options.tut_last_healed = you.num_turns;
}
}
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_STAVES:
case OBJ_MISCELLANY:
learned_something_new(TUT_SEEN_MISC);
break;
default: /* nothing to be done */
return;
}
}
void tutorial_first_monster(monsters mon)
{
if (!Options.tutorial_events[TUT_SEEN_MONSTER])
return;
std::string text = "<magenta>That ";
formatted_string st = formatted_string::parse_string(text);
st.formatted_string::textcolor(channel_to_colour(MSGCH_TUTORIAL));
st.formatted_string::add_glyph(&mon);
text = " is a monster, usually depicted by a letter. Some typical early monsters ";
st += formatted_string::parse_string(text);
formatted_mpr(st, MSGCH_TUTORIAL);
text = "look like <brown>r<magenta>, <w>g<magenta>, <lightgray>b<magenta> or "
"<brown>K<magenta>. You can gain information about it by pressing "
"<w>x<magenta>, moving the cursor on the monster and then pressing "
"<w>v<magenta>. To attack it with your wielded weapon, just move into it.";
print_formatted_paragraph(text, 80, MSGCH_TUTORIAL);
more();
if (Options.tutorial_type == TUT_RANGER_CHAR)
{
text = "However, as a hunter you will want to deal with it using your "
"bow. Do this as follows: <w>wbf+.<magenta> where <w>wb<magenta> "
"wields the bow, <w>f<magenta> fires appropriate ammunition "
"(your arrows) and enters targeting mode. <w>+<magenta> and "
"<w>-<magenta> allow you to select the proper monster. Finally, "
"<w>Enter<magenta> or <w>.<magenta> fire. If you miss, "
"<w>ff<magenta> fires at the previous target again.";
print_formatted_paragraph(text, 80, MSGCH_TUTORIAL);
}
else if (Options.tutorial_type == TUT_MAGIC_CHAR)
{
text = "However, as a conjurer you will want to deal with it using magic."
"Do this as follows: <w>Za+.<magenta> where <w>Za<magenta> zaps "
"the first spell you know, ";
text += spell_title(get_spell_by_letter('a'));
text += ", and enters targeting mode. <w>+<magenta> and <w>-<magenta> "
"allow you to select the proper target. Finally, <w>Enter<magenta> "
"or <w>.<magenta> fire. If you miss, <w>Zap<magenta> will "
"fire at the previous target again.";
print_formatted_paragraph(text, 80, MSGCH_TUTORIAL);
}
Options.tutorial_events[TUT_SEEN_MONSTER] = 0;
Options.tutorial_left--;
Options.tut_just_triggered = 1;
}
void tutorial_first_item(item_def item)
{
if (!Options.tutorial_events[TUT_SEEN_FIRST_OBJECT] || Options.tut_just_triggered)
return;
std::string text = "<magenta>That ";
formatted_string st = formatted_string::parse_string(text);
st.formatted_string::textcolor(channel_to_colour(MSGCH_TUTORIAL));
st.formatted_string::add_glyph(&item);
text = " is an item. If you move there and press <w>g<magenta> or "
"<w>,<magenta> you will pick it up.";
st += formatted_string::parse_string(text);
formatted_mpr(st, MSGCH_TUTORIAL);
text = "Generally, items are shown by non-letter symbols like "
"<w>%%?!\"=()[<magenta>. Once it is in your inventory, you can drop "
"it again with <w>d<magenta>. Several types of objects will usually "
"be picked up automatically.";
print_formatted_paragraph(text, 80, MSGCH_TUTORIAL);
Options.tutorial_events[TUT_SEEN_FIRST_OBJECT] = 0;
Options.tutorial_left--;
Options.tut_just_triggered = 1;
}
#if 0
static void tutorial_first_stairs(int x, int y) {
std::string text = "<magenta>The ";
formatted_string st = formatted_string::parse_string(text);
st.formatted_string::textcolor(env.show_col[x-you.x_pos + 9][y-you.y_pos + 9]);
text = get_screen_glyph(x,y);
text += "<magenta> are some downstairs. You can enter the next (deeper) "
"level by following ";
st += formatted_string::parse_string(text);
formatted_mpr(st);
text = "them down (<w>><magenta>). To get back to "
"this level again, press <w><<<magenta> while standing on the "
"upstairs.";
print_formatted_paragraph(text, 80, MSGCH_TUTORIAL);
}
#endif
void learned_something_new(unsigned int seen_what, int x, int y)
{
// already learned about that
if (!Options.tutorial_events[seen_what])
return;
// don't trigger twice in the same turn
if (Options.tut_just_triggered)
return;
std::string text;
switch(seen_what)
{
case TUT_SEEN_POTION:
text = "You have picked up your first potion ('<w>!<magenta>'). Use "
"<w>q<magenta> to drink (quaff) it.";
break;
case TUT_SEEN_SCROLL:
text = "You have picked up your first scroll ('<w>?<magenta>'). Type "
"<w>r<magenta> to read it.";
break;
case TUT_SEEN_WAND:
text = "You have picked up your first wand ('<w>/<magenta>'). Type "
"<w>z<magenta> to zap it.";
break;
case TUT_SEEN_SPBOOK:
text = "You have picked up a spellbook ('<w>+<magenta>' or "
"'<w>:<magenta>'). You can read it by typing <w>r<magenta>, "
"memorise spells via <w>M<magenta> and cast a memorised spell "
"with <w>Z<magenta>.";
if (!you.skills[SK_SPELLCASTING])
{
text += "\nHowever, first you will have to get accustomed to "
"spellcasting by reading lots of scrolls.";
}
break;
case TUT_SEEN_WEAPON:
text = "This is the first weapon ('<w>(<magenta>') you've picked up. "
"Use <w>w<magenta> 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 with <w>v<magenta>.";
if (Options.tutorial_type == TUT_BERSERK_CHAR)
{
text += "\nAs you're already trained in Axes you should stick with "
"these. Checking other axes can be worthwhile.";
}
break;
case TUT_SEEN_MISSILES:
text = "This is the first stack of missiles ('<w>)<magenta>') you've "
"picked up. Darts can be thrown by hand, but other missile types "
"like arrows and needles require a launcher and training in "
"using it to be really effective. <w>v<magenta> gives more "
"information about both missiles and launcher.";
if (Options.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 (Options.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 ('<w>[<magenta>') you've "
"picked up. Use <w>W<magenta> to wear it and <w>T<magenta> to "
"take it off again. You can view its properties with "
"<w>v<magenta>.";
if (you.species == SP_CENTAUR || you.species == SP_MINOTAUR)
{
snprintf( info, INFO_SIZE, "\nNote that as a %s, you will be unable to wear %s.",
species_name(you.species, 1), you.species == SP_CENTAUR ? "boots" : "helmets");
text += info;
}
break;
case TUT_SEEN_RANDART:
text = "Weapons and armour that have descriptions like this are more "
"likely to be of higher enchantment or have special properties, "
"good or bad.";
break;
case TUT_SEEN_FOOD:
text = "You have picked up some food ('<w>%%<magenta>'). You can eat it "
"by typing <w>e<magenta>.";
break;
case TUT_SEEN_CARRION:
text = "You have picked up a corpse ('<w>%%<magenta>'). When a corpse "
"is lying on the ground, you can <w>D<magenta>issect with a "
"sharp implement. Once hungry you can <w>e<magenta>at the "
"resulting chunks (though they may not be healthy).";
if (Options.tutorial_type == TUT_BERSERK_CHAR)
{
text += " During prayer you can offer corpses to ";
text += god_name(you.religion);
text += "by dissecting them, as well.";
}
break;
case TUT_SEEN_JEWELLERY:
text = "You have picked up a a piece of jewellery, either a ring "
"('<w>=<magenta>') or an amulet ('<w>\"<magenta>'). Type "
"<w>P<magenta> to put it on and <w>R<magenta> to remove it. You "
"can view it with <w>v<magenta> although 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 <w>w<magenta>ielding and "
"<w>E<magenta>voking it.";
break;
case TUT_SEEN_STAIRS:
if (you.num_turns < 1)
return;
// tutorial_first_stairs(x,y);
text = "The <w>";
text += get_screen_glyph(x,y);
text += "<magenta> are some downstairs. You can enter the next (deeper) "
"level by following them down (<w>><magenta>). To get back to "
"this level again, press <w><<<magenta> while standing on the "
"upstairs.";
break;
case TUT_SEEN_TRAPS:
text = "Oops... you just entered a trap. An unwary adventurer will "
"occasionally stumble into one of these nasty constructions "
"depicted by <w>^<magenta>. 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 = "The <blue>";
text += get_screen_glyph(x,y);
text += "<magenta> is an altar. You can get information about it by pressing "
"<w>p<magenta> while standing on the square. Before taking up "
"the responding faith you'll be asked for confirmation.";
break;
case TUT_SEEN_SHOP:
text = "The <yellow>";
text += get_screen_glyph(x,y);
text += "<magenta> is a shop. You can enter it by typing <w><<<magenta>.";
break;
case TUT_SEEN_DOOR:
if (you.num_turns < 1)
return;
text = "The <w>";
text += get_screen_glyph(x,y);
text += "<magenta> 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<magenta>, followed by the direction.";
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 (Options.tutorial_type == TUT_BERSERK_CHAR)
text += "in melee battle will raise your Axes and Fighting skills.";
else if (Options.tutorial_type == TUT_RANGER_CHAR)
text += "using bow and arrows will raise your Bows and Ranged Combat "
"skills.";
else // if (Options.tutorial_type == TUT_MAGIC_CHAR)
text += "with offensive magic will raise your Conjurations and "
"Spellcasting skills.";
if (you.religion != GOD_NO_GOD)
{
text += "\nTo dedicate your kills to ";
text += god_name(you.religion);
text += " <w>p<magenta>ray before battle. Not all gods will be "
"pleased by doing this.";
}
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 (Options.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>Mcb<magenta>, which can then be "
"zapped with <w>Zb<magenta>.";
}
break;
case TUT_SKILL_RAISE:
text = "One of your skills just got raised. Type <w>m<magenta> to take "
"a look at your skills screen.";
break;
case TUT_YOU_ENCHANTED:
text = "Enchantments of all types can befall you temporarily. "
"Abbreviated signalisation appears at the lower end of the stats "
"area. Press <w>@<magenta> for more details. A list of all "
"possible enchantments is given in the manual.";
break;
case TUT_YOU_SICK:
learned_something_new(TUT_YOU_ENCHANTED);
text = "Corpses can be spoiled or inedible, resulting in sickness. "
"Also, some monsters' flesh is less palatable than others'. "
"During sickness your hitpoints won't regenerate and sometimes "
"an attribute may decrease. It wears off with time (wait with "
"<w>5<magenta>) or you could quaff a potion of healing.";
break;
case TUT_YOU_POISON:
learned_something_new(TUT_YOU_ENCHANTED);
text = "Poison will slowly reduce your hp. It wears off with time "
"(wait with <w>5<magenta>) or you could quaff a potion of "
"healing.";
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. Weapons can also be uncursed using enchanting "
"scrolls.";
break;
case TUT_YOU_HUNGRY:
text = "There are two ways to overcome hunger: food you started "
"with or found, and selfmade chunks from corpses. To get the "
"latter, all you need to do is <w>D<magenta> a corpse with a "
"sharp implement. Your starting weapon will do nicely."
"Try to dine on chunks in order to save permanent food.";
break;
case TUT_YOU_STARVING:
text = "You are now suffering from terrible hunger. You'll need to "
"<w>e<magenta>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.";
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>,<magenta> or <w>g<magenta> twice "
"you can choose items from a menu. This takes less 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<magenta>rop "
"some of the stuff you don't need or that's too heavy to lug "
"around permanently.";
break;
case TUT_ROTTEN_FOOD:
text = "One or more of the chunks or corpses you carry has started to "
"rot. Few races can digest these safely, so you might just as "
"well <w>d<magenta>rop them now.";
if (you.religion == GOD_TROG || you.religion == GOD_MAKHLEB ||
you.religion == GOD_OKAWARU)
{
text += "\nIf it is a rotting corpse you carry now might be a good "
"time to <w>d<magenta>rop and <w>D<magenta>issect it during "
"prayer (<w>p<magenta>) as an offer to ";
text += god_name(you.religion);
text += ".";
}
break;
case TUT_MAKE_CHUNKS:
text = "How lucky! That monster left a corpse which you can now "
"<w>D<magenta>issect. One or more chunks will appear, to be "
"<w>e<magenta>aten later on. Beware that some chunks may be, "
"sometimes or always, hazardous. Only experience can help "
"you here.";
if (you.duration[DUR_PRAYER] &&
(you.religion == GOD_OKAWARU || you.religion == GOD_MAKHLEB ||
you.religion == GOD_TROG || you.religion == GOD_ELYVILON))
{
text += "\nNote that dissection under prayer offers the corpse to ";
text += god_name(you.religion);
text += " - check your god's attitude about this with <w>^<magenta>.";
}
break;
case TUT_SHIFT_RUN:
text = "Walking around takes less keystrokes if you press "
"<w>Shift-direction<magenta> or <w>/ direction<magenta>. "
"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<magenta> to bring up the level map. Typing "
"<w>?<magenta> shows the list of level map commands. "
"Most importantly, moving the cursor to a spot and pressing "
"<w>.<magenta> or <w>Enter<magenta> lets your character move "
"there on its own.";
break;
case TUT_DONE_EXPLORE:
text = "You have explored the dungeon on this level. The downstairs look "
"like '<w>><magenta>'. Proceed there and press <w>><magenta> to "
"go down.";
if (Options.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<magenta>, <w>.<magenta> or <w>5<magenta> to do so.";
}
else
{
text += "Each level of Crawl has three white up and three white 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>s<magenta> "
"or <w>.<magenta>, or for 100 turns with <w>5<magenta> or "
"<w>Shift-numpad 5.";
}
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. Press <w>5<magenta> or "
"<w>shift-numpad-5<magenta> 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<magenta>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_POSTBERSERK:
text = "Berserking is extremely exhausting! It burns a lot of nutrition, "
"and afterwards you are slowed down and occasionally even pass "
"out.";
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! ";
if (Options.tutorial_type == TUT_BERSERK_CHAR && !you.berserker)
{
text += "\nAlso, with ";
text += god_name(you.religion);
text += "'s support you can use your Berserk ability (<w>a<magenta>) "
"to temporarily gain more hitpoints and greater strength. ";
}
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<magenta>.";
break;
case TUT_NEW_ABILITY:
text = "You just gained a new ability. Press <w>a<magenta> to take a "
"look at your abilities or to use one of them.";
break;
case TUT_WIELD_WEAPON:
text = "You might want to <w>w<magenta>ield a more suitable implement "
"when attacking monsters.";
if (Options.tutorial_type == TUT_RANGER_CHAR
&& you.inv[ you.equip[EQ_WEAPON] ].sub_type == WPN_BOW)
{
text += "You can easily switch between weapons in slots a and "
"b by pressing <w>'<magenta>.";
}
break;
case TUT_SEEN_MONSTER:
case TUT_SEEN_FIRST_OBJECT:
break;
default:
text += "You've found something new (but I don't know what)!";
}
if (seen_what != TUT_SEEN_MONSTER && seen_what != TUT_SEEN_FIRST_OBJECT)
print_formatted_paragraph(text, 80, MSGCH_TUTORIAL);
more();
Options.tut_just_triggered = true;
Options.tutorial_events[seen_what] = 0;
Options.tutorial_left--;
}
/*
* Created for Crawl Reference by JPEG on $Date: 2007-01-11$
*/
#include "tutorial.h"
#include <cstring>
#include "command.h"
#include "files.h"
#include "itemprop.h"
#include "menu.h"
#include "message.h"
#include "misc.h"
#include "newgame.h"
#include "output.h"
#include "player.h"
#include "religion.h"
#include "spl-util.h"
#include "stuff.h"
#include "view.h"
//#define TUTORIAL_DEBUG
#define TUTORIAL_VERSION 110
int COLS = get_number_of_cols();
void save_tutorial( FILE* fp )
{
writeLong( fp, TUTORIAL_VERSION);
writeShort( fp, Options.tutorial_type);
for ( unsigned i = 0; i < TUT_EVENTS_NUM; ++i )
writeShort( fp, Options.tutorial_events[i] );
}
void load_tutorial( FILE* fp )
{
Options.tutorial_left = 0;
int version = readLong(fp);
Options.tutorial_type = readShort(fp);
if (version != TUTORIAL_VERSION)
return;
for ( long i = 0; i < TUT_EVENTS_NUM; ++i )
{
Options.tutorial_events[i] = readShort(fp);
Options.tutorial_left += Options.tutorial_events[i];
}
}
// override init file definition for some options
void init_tutorial_options()
{
if (!Options.tutorial_left)
return;
Options.delay_message_clear = false;
Options.auto_list = true;
}
// tutorial selection screen and choice
bool pick_tutorial()
{
char keyn;
bool printed = false;
tut_query:
if (!printed)
{
clrscr();
textcolor( WHITE );
cprintf("You must be new here indeed!");
cprintf(EOL EOL);
textcolor( CYAN );
cprintf("You can be:");
cprintf(EOL EOL);
textcolor( LIGHTGREY );
for (int i = 0; i < TUT_TYPES_NUM; i++)
print_tutorial_menu(i);
textcolor( BROWN );
cprintf(EOL "SPACE - Back to class selection; Bksp - Back to race selection; X - Quit"
EOL "* - Random tutorial"
EOL);
printed = true;
}
keyn = c_getch();
if (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)
{
Options.tutorial_type = keyn - 'a';
you.species = get_tutorial_species(Options.tutorial_type);
you.char_class = get_tutorial_job(Options.tutorial_type);
// activate all triggers
for (int i = 0; i < TUT_EVENTS_NUM; i++)
Options.tutorial_events[i] = 1;
Options.tutorial_left = TUT_EVENTS_NUM;
// store whether explore, stash search or travelling was used
Options.tut_explored = 1;
Options.tut_stashes = 1;
Options.tut_travel = 1;
// used to compare which fighting means was used most often
Options.tut_spell_counter = 0;
Options.tut_throw_counter = 0;
Options.tut_berserk_counter = 0;
Options.tut_melee_counter = 0;
// for occasional healing reminders
Options.tut_last_healed = 0;
Options.random_pick = true; // random choice of starting spellbook
Options.weapon = WPN_HAND_AXE; // easiest choice for fighters
return true;
}
if (keyn == CK_BKSP || keyn == ' ')
{
// in this case, undo previous choices
// set_startup_options();
you.species = 0; you.char_class = JOB_UNKNOWN;
Options.race = 0; Options.cls = 0;
}
switch (keyn)
{
case CK_BKSP:
choose_race();
break;
case ' ':
choose_class();
break;
case 'X':
cprintf(EOL "Goodbye!");
end(0);
break;
default:
printed = false;
goto tut_query;
}
return false;
}
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),
get_class_name(get_tutorial_job(type)), desc);
}
unsigned int 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;
}
}
// TO DO: check whether job and species are compatible...
unsigned int 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;
}
}
// the tutorial welcome screen
static formatted_string tut_starting_info(unsigned int width)
{
std::string result; // the entire page
std::string text; // one paragraph
result += "<white>Welcome to Dungeon Crawl!</white>" EOL EOL;
text += "Your object is to lead a ";
snprintf(info, INFO_SIZE, "%s %s ", species_name(get_tutorial_species(Options.tutorial_type), 1),
get_class_name(get_tutorial_job(Options.tutorial_type)));
text += info;
text += "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 as death lurks around "
"every corner here." EOL EOL;
linebreak_string2(text, width);
result += formatted_string::parse_string(text);
result += "For the moment, just remember the following two keys and their functions:" EOL;
result += " <white>?</white> - shows the items and the commands" EOL;
result += " <white>S</white> - saves the game, to be resumed later (but note that death is permanent)" EOL;
result += " <white>x</white> - examine something in your vicinity" EOL EOL;
text = "This tutorial will help you playing Crawl without reading any documentation. "
"If you feel intrigued, there is more information available in these files "
"(all of which can also be read in-game):" EOL;
linebreak_string2(text, width);
result += formatted_string::parse_string(text);
result += " <darkgray>readme.txt</darkgray> - A very short guide to Crawl." EOL;
result += " <darkgray>manual.txt</darkgray> - This contains all details on races, magic, skills, etc." EOL;
result += " <darkgray>crawl_options.txt</darkgray> - Crawl's interface is highly configurable. This document " EOL;
result += " explains all the options." EOL;
result += EOL;
result += "Press <white>Space</white> to proceed to the basics (the screen division and movement)." EOL;
result += "Press <white>Esc</white> to fast forward to the game start.";
return formatted_string::parse_string(result);
}
#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_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_STAIRS:
return "seen first stairs";
case TUT_SEEN_TRAPS:
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_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_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_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_POSTBERSERK:
return "learned about Berserk aftereffects";
case TUT_RUN_AWAY:
return "were told to run away";
case TUT_SHIFT_RUN:
return "learned about shift-run";
case TUT_MAP_VIEW:
return "learned about the level map";
case TUT_DONE_EXPLORE:
return "explored a level";
case TUT_YOU_MUTATED:
return "caught a mutation";
case TUT_NEW_ABILITY:
return "gained a divine ability";
case TUT_WIELD_WEAPON:
return "wielded an unsuitable weapon";
default:
return "faced a bug";
}
}
static formatted_string tutorial_debug()
{
std::string result;
bool lbreak = false;
snprintf(info, INFO_SIZE, "Tutorial Debug Screen");
int i = COLS/2-1 - strlen(info) / 2;
result += std::string(i, ' ');
result += "<white>";
result += info;
result += "</white>" EOL EOL;
result += "<darkgray>";
for (i=0; i < TUT_EVENTS_NUM; i++)
{
snprintf(info, INFO_SIZE, "%d: %d (%s)",
i, Options.tutorial_events[i], tut_debug_list(i).c_str());
result += info;
// break text into 2 columns where possible
if (strlen(info) >= COLS/2 || lbreak)
{
result += EOL;
lbreak = false;
}
else
{
result += std::string(COLS/2-1 - strlen(info), ' ');
lbreak = true;
}
}
result += "</darkgray>" EOL EOL;
snprintf(info, INFO_SIZE, "tutorial_left: %d\n", Options.tutorial_left);
result += info;
result += EOL;
snprintf(info, INFO_SIZE, "You are a %s %s, and the tutorial will reflect that.",
species_name(get_tutorial_species(Options.tutorial_type), 1),
get_class_name(get_tutorial_job(Options.tutorial_type)));
result += info;
return formatted_string::parse_string(result);
}
#endif // debug
static formatted_string tutorial_map_intro()
{
std::string result;
result = "<magenta>"
"What you see here is the typical Crawl screen. The upper left map "
"shows your hero as the <white>@<magenta> in the center. The parts "
"of the map you remember but cannot currently see, will be greyed "
"out."
"</magenta>" EOL;
result += " --more-- Press <white>Escape</white> to skip the basics";
linebreak_string2(result,COLS);
return formatted_string::parse_string(result);
}
static formatted_string tutorial_stats_intro()
{
std::string result;
result += "<magenta>";
result += "To the right, important properties of " EOL;
result += "the character are displayed. The most " EOL;
result += "basic one is your health, measured as " EOL;
snprintf(info, INFO_SIZE, "<white>HP: %d/%d<magenta>. ", you.hp, you.hp_max);
result += info;
if (Options.tutorial_type==TUT_MAGIC_CHAR)
result += " ";
result += "These are your current out " EOL;
result += "of maximum health points. When health " EOL;
result += "drops to zero, you die. " EOL;
snprintf(info, INFO_SIZE, "<white>Magic: %d/%d<magenta>", you.magic_points, you.max_magic_points);
result += info;
result += " is your energy for casting " EOL;
result += "spells, although more mundane actions " EOL;
result += "often draw from Magic, too. " EOL;
result += "Further down, <white>Str<magenta>ength, <white>Dex<magenta>terity and " EOL;
result += "<white>Int<magenta>elligence are shown and provide an " EOL;
result += "all-around account of the character's " EOL;
result += "attributes. " EOL;
result += "</magenta>";
result += " " EOL;
result += " --more-- Press <white>Escape</white> to skip the basics" EOL;
result += " " EOL;
result += " " EOL;
return formatted_string::parse_string(result);
}
static formatted_string tutorial_message_intro()
{
std::string result;
result = "<magenta>"
"This lower 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 "
"<white>Ctrl-P<magenta>."
"</magenta>" EOL;
result += " --more-- Press <white>Escape</white> to skip the basics";
linebreak_string2(result,COLS);
return formatted_string::parse_string(result);
}
static void tutorial_movement_info()
{
std::string text;
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<magenta>. A basic "
"command list can be found under <w>?<magenta>, and the most important "
"commands will be explained to you as it becomes necessary. ";
print_formatted_paragraph(text, COLS, MSGCH_TUTORIAL);
}
// copied from display_mutations and adapted
void tut_starting_screen()
{
int x1, x2, y1, y2;
int MAX_INFO = 4;
#ifdef TUTORIAL_DEBUG
MAX_INFO++;
#endif
char ch;
for (int i=0; i<=MAX_INFO; i++)
{
x1 = 1; y1 = 1;
x2 = COLS; y2 = get_number_of_lines();
if (i==1 || i==3)
{
y1 = 18; // message window
}
else if (i==2)
{
x2 = 40; // map window
y2 = 18;
}
if (i==0)
clrscr();
gotoxy(x1,y1);
if (i==0)
tut_starting_info(x2).display();
else if (i==1)
tutorial_map_intro().display();
else if (i==2)
tutorial_stats_intro().display();
else if (i==3)
tutorial_message_intro().display();
else if (i==4)
tutorial_movement_info();
else
{
#ifdef TUTORIAL_DEBUG
clrscr();
gotoxy(x1,y1);
tutorial_debug().display();
#else
continue;
#endif
}
ch = c_getch();
redraw_screen();
if (ch == ESCAPE)
break;
}
}
// once a tutorial character dies, offer some playing hints
void tutorial_death_screen()
{
Options.tutorial_left = 0;
std::string text;
mpr( "Condolences! Your character's premature perishing is a sad, but "
"common occurence in Crawl. Rest assured that with diligence and "
"playing experience your characters will last longer.\n", MSGCH_TUTORIAL);
mpr( "Perhaps the following advice can improve your playing style:", MSGCH_TUTORIAL);
more();
if (Options.tutorial_type == TUT_MAGIC_CHAR
&& Options.tut_spell_counter < Options.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 (Options.tutorial_type == TUT_BERSERK_CHAR
&& Options.tut_berserk_counter < 1 )
{
text = "Don't forget to go berserk when fighting particularly "
"difficult foes. It is risky, but makes you faster "
"and beefier. Also try to pray prior to battles so that ";
text += god_name(you.religion);
text += " will soon feel like providing more abilities.";
}
else if (Options.tutorial_type == TUT_RANGER_CHAR
&& 2*Options.tut_throw_counter < Options.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);
// If a character has been unusually busy with projectiles and spells
// give some other hint rather than the first one.
if (Options.tut_throw_counter + Options.tut_spell_counter >= Options.tut_melee_counter
&& hint == 0)
hint = random2(5)+1;
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. In Crawl, searching and resting are "
"one and the same. To search for one turn, press <w>s<magenta>, "
"<w>.<magenta>, <w>delete<magenta> or <w>keypad-5<magenta>. "
"Pressing <w>5<magenta> or <w>shift-and-keypad-5<magenta> will "
"let you rest for a longer time (you will stop resting 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.";
}
}
print_formatted_paragraph(text, COLS, MSGCH_TUTORIAL);
more();
mpr( "See you next game!", MSGCH_TUTORIAL);
for ( long i = 0; i < TUT_EVENTS_NUM; ++i )
Options.tutorial_events[i] = 0;
}
// if a character survives until Xp 5, 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;
Options.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 help screen (<w>?<magenta>) "
"will look different from now on. Here's a last playing hint:";
print_formatted_paragraph(text, COLS, MSGCH_TUTORIAL);
more();
if (Options.tut_explored)
{
text = "Walking around and exploring levels gets easier by using "
"auto-explore (<w>Ctrl-O<magenta>). You can even make Crawl "
"automatically pick up interesting items by setting the "
"option <w>explore_greedy=true<magenta> in the init file.";
}
else if (Options.tut_travel)
{
text = "There is a convenient way for travelling between far away "
"dungeon levels: press <w>Ctrl-G<magenta> and enter the desired "
"destination. If your travel gets interrupted, issueing "
"<w>Ctrl-G Enter<magenta> will continue it.";
}
else if (Options.tut_stashes)
{
text = "You can search among all items existing in the dungeon with "
"the <w>Ctrl-F<magenta> command. For example, "
"<w>Ctrl-F \"knife\"<magenta> will list all knives. You can then "
"travel to one of the spots. It is even possible to enter words "
"like <w>\"shop\"<magenta> or <w>\"altar\"<magenta>.";
}
else
{
int hint = random2(3);
switch (hint)
{
case 0:
text = "The game keeps an automated logbook for your characters. Use "
"<w>?:<magenta> to read it. You can enter notes manually with "
"the <w>:<magenta> command. Once your character perishes, two "
"morgue files are left in the <w>/morgue<magenta> directory. "
"The one ending in .txt contains a copy of your logbook. "
"During play, you can create a dump file with <w>#<magenta>.";
break;
case 1:
text = "Crawl has a macro function built in: press <w>~m<magenta> "
"to define a macro by first specifying a trigger key (say, "
"<w>F1<magenta>) and a command sequence, for example "
"<w>Za+.<magenta>. The latter will make the <w>F1<magenta> "
"key always zap the spell in slot a at the nearest monster. "
"For more information on macros, type <w>?~<magenta>.";
break;
case 2:
text = "The interface can be greatly customised. All options are "
"explained in the file <w>crawl_options.txt<magenta> which "
"can be found in the <w>/docs<magenta> directory. The "
"options themselves are set in <w>init.txt<magenta> or "
"<w>.crawlrc<magenta>. Crawl will complain when it can't "
"find either file.\n";
break;
default:
text = "Oops... No hint for now. Better luck next time!";
}
}
print_formatted_paragraph(text, COLS, MSGCH_TUTORIAL);
more();
for ( long i = 0; i < TUT_EVENTS_NUM; ++i )
Options.tutorial_events[i] = 0;
}
// occasionally remind religious characters of praying
void tutorial_prayer_reminder()
{
if (Options.tut_just_triggered)
return;
if (coinflip()) // always would be too annoying
{
std::string text;
text = "Remember to <w>p<magenta>ray before battle, so as to dedicate "
"your kills to ";
text += god_name(you.religion);
text += ". Should the monster leave a corpse, consider "
"<w>D<magenta>issecting it as a sacrifice to ";
text += god_name(you.religion);
text += ", as well.";
print_formatted_paragraph(text, COLS, MSGCH_TUTORIAL);
}
}
// occasionally remind injured characters of resting
void tutorial_healing_reminder()
{
if (you.poisoning && 2*you.hp < you.hp_max)
{
if (Options.tutorial_events[TUT_NEED_POISON_HEALING])
learned_something_new(TUT_NEED_POISON_HEALING);
}
else
{
if (Options.tutorial_events[TUT_NEED_HEALING])
learned_something_new(TUT_NEED_HEALING);
else if (you.num_turns - Options.tut_last_healed >= 50 && !you.poisoning)
{
if (Options.tut_just_triggered)
return;
std::string text;
text = "Remember to rest between fights and to enter unexplored "
"terrain with full hitpoints and/or magic. For resting, "
"press <w>5<magenta> or <w>Shift-numpad 5<magenta>.";
print_formatted_paragraph(text, COLS, MSGCH_TUTORIAL);
}
Options.tut_last_healed = you.num_turns;
}
}
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;
default: /* nothing to be done */
return;
}
}
void tutorial_first_monster(monsters mon)
{
if (!Options.tutorial_events[TUT_SEEN_MONSTER])
return;
std::string text = "<magenta>That ";
formatted_string st = formatted_string::parse_string(text);
st.formatted_string::textcolor(channel_to_colour(MSGCH_TUTORIAL));
st.formatted_string::add_glyph(&mon);
text = " is a monster, usually depicted by a letter. Some typical early monsters ";
st += formatted_string::parse_string(text);
formatted_mpr(st, MSGCH_TUTORIAL);
text = "look like <brown>r<magenta>, <w>g<magenta>, <lightgray>b<magenta> or "
"<brown>K<magenta>. You can gain information about it by pressing "
"<w>x<magenta>, moving the cursor on the monster and then pressing "
"<w>v<magenta>. To attack it with your wielded weapon, just move into it.";
print_formatted_paragraph(text, COLS, MSGCH_TUTORIAL);
more();
if (Options.tutorial_type == TUT_RANGER_CHAR)
{
text = "However, as a hunter you will want to deal with it using your "
"bow. Do this as follows: <w>wbf+.<magenta> where <w>wb<magenta> "
"wields the bow, <w>f<magenta> fires appropriate ammunition "
"(your arrows) and enters targeting mode. <w>+<magenta> and "
"<w>-<magenta> allow you to select the proper monster. Finally, "
"<w>Enter<magenta> or <w>.<magenta> fire. If you miss, "
"<w>ff<magenta> fires at the previous target again.";
print_formatted_paragraph(text, COLS, MSGCH_TUTORIAL);
}
else if (Options.tutorial_type == TUT_MAGIC_CHAR)
{
text = "However, as a conjurer you will want to deal with it using magic."
"Do this as follows: <w>Za+.<magenta> where <w>Za<magenta> zaps "
"the first spell you know, ";
text += spell_title(get_spell_by_letter('a'));
text += ", and enters targeting mode. <w>+<magenta> and <w>-<magenta> "
"allow you to select the proper target. Finally, <w>Enter<magenta> "
"or <w>.<magenta> fire. If you miss, <w>Zap<magenta> will "
"fire at the previous target again.";
print_formatted_paragraph(text, COLS, MSGCH_TUTORIAL);
}
Options.tutorial_events[TUT_SEEN_MONSTER] = 0;
Options.tutorial_left--;
Options.tut_just_triggered = 1;
}
void tutorial_first_item(item_def item)
{
if (!Options.tutorial_events[TUT_SEEN_FIRST_OBJECT] || Options.tut_just_triggered)
return;
std::string text = "<magenta>That ";
formatted_string st = formatted_string::parse_string(text);
st.formatted_string::textcolor(channel_to_colour(MSGCH_TUTORIAL));
st.formatted_string::add_glyph(&item);
text = " is an item. If you move there and press <w>g<magenta> or "
"<w>,<magenta> you will pick it up.";
st += formatted_string::parse_string(text);
formatted_mpr(st, MSGCH_TUTORIAL);
text = "Generally, items are shown by non-letter symbols like "
"<w>%%?!\"=()[<magenta>. Once it is in your inventory, you can drop "
"it again with <w>d<magenta>. Several types of objects will usually "
"be picked up automatically.";
print_formatted_paragraph(text, COLS, MSGCH_TUTORIAL);
Options.tutorial_events[TUT_SEEN_FIRST_OBJECT] = 0;
Options.tutorial_left--;
Options.tut_just_triggered = 1;
}
// Here most of the tutorial messages for various triggers are handled.
void learned_something_new(unsigned int seen_what, int x, int y)
{
// already learned about that
if (!Options.tutorial_events[seen_what])
return;
// don't trigger twice in the same turn
if (Options.tut_just_triggered)
return;
std::string text;
unsigned short ch, colour;
switch(seen_what)
{
case TUT_SEEN_POTION:
text = "You have picked up your first potion ('<w>!<magenta>'). Use "
"<w>q<magenta> to drink (quaff) it.";
break;
case TUT_SEEN_SCROLL:
text = "You have picked up your first scroll ('<w>?<magenta>'). Type "
"<w>r<magenta> to read it.";
break;
case TUT_SEEN_WAND:
text = "You have picked up your first wand ('<w>/<magenta>'). Type "
"<w>z<magenta> to zap it.";
break;
case TUT_SEEN_SPBOOK:
get_item_symbol(DNGN_ITEM_BOOK, &ch, &colour);
snprintf(info, INFO_SIZE, "%c", ch);
text = "You have picked up a spellbook ('<w>";
text += info;
text += "'<magenta>). You can read it by typing <w>r<magenta>, "
"memorise spells via <w>M<magenta> and cast a memorised spell "
"with <w>Z<magenta>.";
if (!you.skills[SK_SPELLCASTING])
{
text += "\nHowever, first you will have to get accustomed to "
"spellcasting by reading lots of scrolls.";
}
break;
case TUT_SEEN_WEAPON:
text = "This is the first weapon ('<w>(<magenta>') you've picked up. "
"Use <w>w<magenta> 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 with <w>v<magenta>.";
if (Options.tutorial_type == TUT_BERSERK_CHAR)
{
text += "\nAs you're already trained in Axes you should stick with "
"these. Checking other axes can be worthwhile.";
}
break;
case TUT_SEEN_MISSILES:
text = "This is the first stack of missiles ('<w>)<magenta>') you've "
"picked up. Darts can be thrown by hand, but other missile types "
"like arrows and needles require a launcher and training in "
"using it to be really effective. <w>v<magenta> gives more "
"information about both missiles and launcher.";
if (Options.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 (Options.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 ('<w>[<magenta>') you've "
"picked up. Use <w>W<magenta> to wear it and <w>T<magenta> to "
"take it off again. You can view its properties with "
"<w>v<magenta>.";
if (you.species == SP_CENTAUR || you.species == SP_MINOTAUR)
{
snprintf( info, INFO_SIZE, "\nNote that as a %s, you will be unable to wear %s.",
species_name(you.species, 1), you.species == SP_CENTAUR ? "boots" : "helmets");
text += info;
}
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 ('<w>%%<magenta>'). You can eat it "
"by typing <w>e<magenta>.";
break;
case TUT_SEEN_CARRION:
text = "You have picked up a corpse ('<w>%%<magenta>'). When a corpse "
"is lying on the ground, you can <w>D<magenta>issect with a "
"sharp implement. Once hungry you can <w>e<magenta>at the "
"resulting chunks (though they may not be healthy).";
if (Options.tutorial_type == TUT_BERSERK_CHAR)
{
text += " During prayer you can offer corpses to ";
text += god_name(you.religion);
text += " by dissecting them, as well.";
}
break;
case TUT_SEEN_JEWELLERY:
text = "You have picked up a a piece of jewellery, either a ring "
"('<w>=<magenta>') or an amulet ('<w>\"<magenta>'). Type "
"<w>P<magenta> to put it on and <w>R<magenta> to remove it. You "
"can view it with <w>v<magenta> although 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 <w>w<magenta>ielding and "
"<w>E<magenta>voking it.";
break;
case TUT_SEEN_STAFF:
get_item_symbol(DNGN_ITEM_STAVE, &ch, &colour);
snprintf(info, INFO_SIZE, "%c", ch);
text = "You have picked up a magic staff or a rod, both of which are "
"represented by '<w>";
text += info;
text += "<magenta>'. Both must be <w>i<magenta>elded 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 "
"<w>E<magenta>voking it. For the latter the power depends on "
"your Evocations skill.";
break;
case TUT_SEEN_STAIRS:
if (you.num_turns < 1)
return;
text = "The <w>";
text += get_screen_glyph(x,y);
text += "<magenta> are some downstairs. You can enter the next (deeper) "
"level by following them down (<w>><magenta>). To get back to "
"this level again, press <w><<<magenta> while standing on the "
"upstairs.";
break;
case TUT_SEEN_TRAPS:
text = "Oops... you just entered a trap. An unwary adventurer will "
"occasionally stumble into one of these nasty constructions "
"depicted by <w>^<magenta>. 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 = "The <blue>";
text += get_screen_glyph(x,y);
text += "<magenta> is an altar. You can get information about it by pressing "
"<w>p<magenta> while standing on the square. Before taking up "
"the responding faith you'll be asked for confirmation.";
break;
case TUT_SEEN_SHOP:
text = "The <yellow>";
text += get_screen_glyph(x,y);
text += "<magenta> is a shop. You can enter it by typing <w><<<magenta>.";
break;
case TUT_SEEN_DOOR:
if (you.num_turns < 1)
return;
text = "The <w>";
text += get_screen_glyph(x,y);
text += "<magenta> 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<magenta>, followed by the direction.";
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 (Options.tutorial_type == TUT_BERSERK_CHAR)
text += "in melee battle will raise your Axes and Fighting skills.";
else if (Options.tutorial_type == TUT_RANGER_CHAR)
text += "using bow and arrows will raise your Bows and Ranged Combat "
"skills.";
else // if (Options.tutorial_type == TUT_MAGIC_CHAR)
text += "with offensive magic will raise your Conjurations and "
"Spellcasting skills.";
if (you.religion != GOD_NO_GOD)
{
text += "\nTo dedicate your kills to ";
text += god_name(you.religion);
text += " <w>p<magenta>ray before battle. Not all gods will be "
"pleased by doing this.";
}
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 (Options.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>Mcb<magenta>, which can then be "
"zapped with <w>Zb<magenta>.";
}
break;
case TUT_SKILL_RAISE:
text = "One of your skills just got raised. Type <w>m<magenta> to take "
"a look at your skills screen.";
break;
case TUT_YOU_ENCHANTED:
text = "Enchantments of all types can befall you temporarily. "
"Abbreviated signalisation appears at the lower end of the stats "
"area. Press <w>@<magenta> for more details. A list of all "
"possible enchantments is given in the manual.";
break;
case TUT_YOU_SICK:
learned_something_new(TUT_YOU_ENCHANTED);
text = "Corpses can be spoiled or inedible, resulting in sickness. "
"Also, some monsters' flesh is less palatable than others'. "
"During sickness your hitpoints won't regenerate and sometimes "
"an attribute may decrease. It wears off with time (wait with "
"<w>5<magenta>) or you could quaff a potion of healing.";
break;
case TUT_YOU_POISON:
learned_something_new(TUT_YOU_ENCHANTED);
text = "Poison will slowly reduce your hp. It wears off with time "
"(wait with <w>5<magenta>) or you could quaff a potion of "
"healing.";
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. Weapons can also be uncursed using enchanting "
"scrolls.";
break;
case TUT_YOU_HUNGRY:
text = "There are two ways to overcome hunger: food you started "
"with or found, and selfmade chunks from corpses. To get the "
"latter, all you need to do is <w>D<magenta> a corpse with a "
"sharp implement. Your starting weapon will do nicely."
"Try to dine on chunks in order to save permanent food.";
break;
case TUT_YOU_STARVING:
text = "You are now suffering from terrible hunger. You'll need to "
"<w>e<magenta>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.";
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>,<magenta> or <w>g<magenta> twice "
"you can choose items from a menu. This takes less 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<magenta>rop "
"some of the stuff you don't need or that's too heavy to lug "
"around permanently.";
break;
case TUT_ROTTEN_FOOD:
text = "One or more of the chunks or corpses you carry has started to "
"rot. Few races can digest these safely, so you might just as "
"well <w>d<magenta>rop them now.";
if (you.religion == GOD_TROG || you.religion == GOD_MAKHLEB ||
you.religion == GOD_OKAWARU)
{
text += "\nIf it is a rotting corpse you carry now might be a good "
"time to <w>d<magenta>rop and <w>D<magenta>issect it during "
"prayer (<w>p<magenta>) as an offer to ";
text += god_name(you.religion);
text += ".";
}
break;
case TUT_MAKE_CHUNKS:
text = "How lucky! That monster left a corpse which you can now "
"<w>D<magenta>issect. One or more chunks will appear that you can "
"then <w>e<magenta>at. Beware that some chunks may be, "
"sometimes or always, hazardous. Only experience can help "
"you here.";
if (you.duration[DUR_PRAYER] &&
(you.religion == GOD_OKAWARU || you.religion == GOD_MAKHLEB ||
you.religion == GOD_TROG || you.religion == GOD_ELYVILON))
{
text += "\nNote that dissection under prayer offers the corpse to ";
text += god_name(you.religion);
text += " - check your god's attitude about this with <w>^<magenta>.";
}
break;
case TUT_SHIFT_RUN:
text = "Walking around takes less keystrokes if you press "
"<w>Shift-direction<magenta> or <w>/ direction<magenta>. "
"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<magenta> to bring up the level map. Typing "
"<w>?<magenta> shows the list of level map commands. "
"Most importantly, moving the cursor to a spot and pressing "
"<w>.<magenta> or <w>Enter<magenta> lets your character move "
"there on its own.";
break;
case TUT_DONE_EXPLORE:
text = "You have explored the dungeon on this level. The downstairs look "
"like '<w>><magenta>'. Proceed there and press <w>><magenta> to "
"go down. ";
if (Options.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<magenta>, <w>.<magenta> or <w>5<magenta> to do so.";
}
else
{
text += "Each level of Crawl has three white up and three white 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>s<magenta> "
"or <w>.<magenta>, or for 100 turns with <w>5<magenta> or "
"<w>Shift-numpad 5.";
}
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. Press <w>5<magenta> or "
"<w>shift-numpad-5<magenta> 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<magenta>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_POSTBERSERK:
text = "Berserking is extremely exhausting! It burns a lot of nutrition, "
"and afterwards you are slowed down and occasionally even pass "
"out.";
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! ";
if (Options.tutorial_type == TUT_BERSERK_CHAR && !you.berserker)
{
text += "\nAlso, with ";
text += god_name(you.religion);
text += "'s support you can use your Berserk ability (<w>a<magenta>) "
"to temporarily gain more hitpoints and greater strength. ";
}
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<magenta>.";
break;
case TUT_NEW_ABILITY:
text = "You just gained a new ability. Press <w>a<magenta> to take a "
"look at your abilities or to use one of them.";
break;
case TUT_WIELD_WEAPON:
text = "You might want to <w>w<magenta>ield a more suitable implement "
"when attacking monsters.";
if (Options.tutorial_type == TUT_RANGER_CHAR
&& you.inv[ you.equip[EQ_WEAPON] ].sub_type == WPN_BOW)
{
text += "You can easily switch between weapons in slots a and "
"b by pressing <w>'<magenta>.";
}
break;
case TUT_SEEN_MONSTER:
case TUT_SEEN_FIRST_OBJECT:
break;
default:
text += "You've found something new (but I don't know what)!";
}
if (seen_what != TUT_SEEN_MONSTER && seen_what != TUT_SEEN_FIRST_OBJECT)
print_formatted_paragraph(text, COLS, MSGCH_TUTORIAL);
// more();
Options.tut_just_triggered = true;
Options.tutorial_events[seen_what] = 0;
Options.tutorial_left--;
}
clrscr();
bool calc_unid = false;
formatted_scroller cmd_help;
// Set flags, and don't use easy exit.
cmd_help.set_flags(MF_NOSELECT | MF_NOWRAP, false);
cmd_help.set_more( formatted_string::parse_string(
"<cyan>[ + : Page down. - : Page up. Esc exits.]"));
formatted_string fs = get_full_detail2(false);
gotoxy(1, 1);
fs.display();
getch();
redraw_screen();
}
formatted_string get_full_detail2(bool calc_unid)
{
case 0:
snprintf(race_class, sizeof race_class,
"(%s%s)",
get_species_abbrev(you.species),
get_class_abbrev(you.char_class) );
break;
case 1:
strcpy(title, "");
break;
default:
break;
case 0:
snprintf(race_class, sizeof race_class,
"(%s%s)",
get_species_abbrev(you.species),
get_class_abbrev(you.char_class) );
break;
case 1:
strcpy(title, "");
break;
default:
break;
output.textcolor(YELLOW);
output.cprintf("%s%s%s", you.your_name, title, race_class);
output.gotoxy(get_number_of_cols() - strlen(time_turns), -1);
output.cprintf("%s", time_turns);
output.textcolor(LIGHTGREY);
output.cprintf(EOL);
text = "<yellow>";
snprintf(info, INFO_SIZE, "%s%s%s", you.your_name, title, race_class);
text += info;
int k = get_number_of_cols() - linelength -1;
text += std::string(k, ' ');
snprintf(info, INFO_SIZE, "%s", time_turns);
text += info;
text += "</yellow>\n\n";
cmd_help.add_text(text);
char buf[1000];
// 3 columns, splits at columns 32, 52
column_composer cols1(4, 16, 27, 38);
if (!player_rotted())
snprintf(buf, sizeof buf, "HP %3d/%d",you.hp,you.hp_max);
else
snprintf(buf, sizeof buf, "HP %3d/%d (%d)",
you.hp, you.hp_max, you.hp_max + player_rotted() );
std::vector<formatted_string> vfs = get_stat_info();
for (unsigned int i = 0; i < vfs.size(); i++)
cols1.add_formatted(0, buf, false);
snprintf(buf, sizeof buf, "MP %3d/%d",
you.magic_points, you.max_magic_points);
cols1.add_formatted(0, buf, false);
if (you.strength == you.max_strength)
snprintf(buf, sizeof buf, "Str %3d", you.strength);
else
snprintf(buf, sizeof buf, "Str <yellow>%3d</yellow> (%d)",
you.strength, you.max_strength);
cols1.add_formatted(1, buf, false);
if (you.intel == you.max_intel)
snprintf(buf, sizeof buf, "Int %3d", you.intel);
else
snprintf(buf, sizeof buf, "Int <yellow>%3d</yellow> (%d)",
you.intel, you.max_intel);
cols1.add_formatted(1, buf, false);
if (you.dex == you.max_dex)
snprintf(buf, sizeof buf, "Dex %3d", you.dex);
else
snprintf(buf, sizeof buf, "Dex <yellow>%3d</yellow> (%d)",
you.dex, you.max_dex);
cols1.add_formatted(1, buf, false);
snprintf(buf, sizeof buf,
"AC %3d\n"
"EV %3d\n"
"Sh %3d\n",
player_AC(),
player_evasion(),
player_shield_class());
cols1.add_formatted(2, buf, false);
char god_colour_tag[20];
god_colour_tag[0] = 0;
std::string godpowers(god_name(you.religion));
if ( you.religion != GOD_NO_GOD )
output += vfs[i];
output += formatted_string::parse_string("\n");
if ( player_under_penance() )
strcpy(god_colour_tag, "<red>*");
else
{
snprintf(god_colour_tag, sizeof god_colour_tag, "<%s>",
colour_to_str(god_colour(you.religion)));
// piety rankings
int prank = piety_rank() - 1;
if ( prank < 0 )
prank = 0;
// Careful about overflow. We erase some of the god's name
// if necessary.
godpowers = godpowers.substr(0, 17 - prank) +
std::string(prank, '*');
}
output += formatted_string::parse_string("\n");
snprintf(buf, sizeof buf,
"Experience: %d/%lu (%d)\n"
"Spells: %2d memorised, %2d level%s left\n"
"God: %s%s<lightgrey> Gold: %d\n",
you.experience_level, you.experience, you.exp_available,
you.spell_no, player_spell_levels(), (player_spell_levels() == 1) ? "" : "s",
god_colour_tag, godpowers.c_str(), you.gold);
cols1.add_formatted(3, buf, false);
std::vector<formatted_string> blines = cols1.formatted_lines();
unsigned i;
for (i = 0; i < blines.size(); ++i )
cmd_help.add_item_formatted_string(blines[i]);
cmd_help.add_text("\n");
vfs = get_res_info(calc_unid);
for (unsigned int i = 0; i < vfs.size(); i++)
// 3 columns, splits at columns 21, 38
column_composer cols(3, 21, 38);
const int rfire = player_res_fire(calc_unid);
const int rcold = player_res_cold(calc_unid);
const int rlife = player_prot_life(calc_unid);
const int rpois = player_res_poison(calc_unid);
const int relec = player_res_electricity(calc_unid);
const int rsust = player_sust_abil(calc_unid);
const int rmuta = wearing_amulet(AMU_RESIST_MUTATION, calc_unid);
const int rslow = wearing_amulet(AMU_RESIST_SLOW, calc_unid);
snprintf(buf, sizeof buf,
"%sRes.Fire : %s\n"
"%sRes.Cold : %s\n"
"%sLife Prot.: %s\n"
"%sRes.Poison: %s\n"
"%sRes.Elec. : %s\n"
"\n"
"%sSust.Abil.: %s\n"
"%sRes.Mut. : %s\n"
"%sRes.Slow : %s\n",
determine_color_string(rfire), itosym3(rfire),
determine_color_string(rcold), itosym3(rcold),
determine_color_string(rlife), itosym3(rlife),
determine_color_string(rpois), itosym1(rpois),
determine_color_string(relec), itosym1(relec),
determine_color_string(rsust), itosym1(rsust),
determine_color_string(rmuta), itosym1(rmuta),
determine_color_string(rslow), itosym1(rslow));
cols.add_formatted(0, buf, false);
int saplevel = 0;
switch (you.species)
output += vfs[i];
output += formatted_string::parse_string("\n");
case SP_GHOUL:
saplevel = 3;
snprintf(buf, sizeof buf, "%sSaprovore : %s",
determine_color_string(3), itosym3(3) );
break;
case SP_KOBOLD:
case SP_TROLL:
saplevel = 2;
snprintf(buf, sizeof buf, "%sSaprovore : %s",
determine_color_string(2), itosym3(2) );
break;
case SP_HILL_ORC:
case SP_OGRE:
saplevel = 1;
break;
default:
saplevel = 0;
break;
}
const char* pregourmand;
const char* postgourmand;
if ( wearing_amulet(AMU_THE_GOURMAND, calc_unid) )
{
pregourmand = "Gourmand : ";
postgourmand = itosym1(1);
saplevel = 1;
}
else
{
pregourmand = "Saprovore : ";
postgourmand = itosym3(saplevel);
}
snprintf(buf, sizeof buf, "%s%s%s",
determine_color_string(saplevel), pregourmand, postgourmand);
cols.add_formatted(0, buf, false);
const int rinvi = player_see_invis(calc_unid);
const int rward = wearing_amulet(AMU_WARDING, calc_unid) ||
(you.religion == GOD_VEHUMET && you.duration[DUR_PRAYER] &&
!player_under_penance() && you.piety >= piety_breakpoint(2));
const int rcons = wearing_amulet(AMU_CONSERVATION, calc_unid);
const int rcorr = wearing_amulet(AMU_RESIST_CORROSION, calc_unid);
const int rclar = wearing_amulet(AMU_CLARITY, calc_unid);
snprintf(buf, sizeof buf,
"%sSee Invis. : %s\n"
"%sWarding : %s\n"
"%sConserve : %s\n"
"%sRes.Corr. : %s\n"
"%sClarity : %s\n"
"\n",
determine_color_string(rinvi), itosym1(rinvi),
determine_color_string(rward), itosym1(rward),
determine_color_string(rcons), itosym1(rcons),
determine_color_string(rcorr), itosym1(rcorr),
determine_color_string(rclar), itosym1(rclar));
cols.add_formatted(1, buf, false);
if ( scan_randarts(RAP_PREVENT_TELEPORTATION, calc_unid) )
snprintf(buf, sizeof buf, "\n%sPrev.Telep.: %s",
determine_color_string(-1), itosym1(1));
else
{
const int rrtel = player_teleport(calc_unid);
snprintf(buf, sizeof buf, "\n%sRnd.Telep. : %s",
determine_color_string(rrtel), itosym1(rrtel));
}
cols.add_formatted(1, buf, false);
const int rctel = player_control_teleport(calc_unid);
const int rlevi = player_is_levitating();
const int rcfli = wearing_amulet(AMU_CONTROLLED_FLIGHT, calc_unid);
snprintf(buf, sizeof buf,
"%sCtrl.Telep.: %s\n"
"%sLevitation : %s\n"
"%sCtrl.Flight: %s\n",
determine_color_string(rctel), itosym1(rctel),
determine_color_string(rlevi), itosym1(rlevi),
determine_color_string(rcfli), itosym1(rcfli));
cols.add_formatted(1, buf, false);
{
char str_pass[ITEMNAME_SIZE];
const int e_order[] =
{
EQ_WEAPON, EQ_BODY_ARMOUR, EQ_SHIELD, EQ_HELMET, EQ_CLOAK,
EQ_GLOVES, EQ_BOOTS, EQ_AMULET, EQ_RIGHT_RING, EQ_LEFT_RING
};
for(i = 0; i < NUM_EQUIP; i++)
{
int eqslot = e_order[i];
const char *slot = equip_slot_to_name( eqslot );
if (eqslot == EQ_LEFT_RING || eqslot == EQ_RIGHT_RING)
slot = "Ring";
else if (eqslot == EQ_BOOTS &&
(you.species == SP_CENTAUR || you.species == SP_NAGA))
slot = "Barding";
if ( you.equip[ e_order[i] ] != -1)
{
const int inum = you.equip[e_order[i]];
in_name( inum, DESC_PLAIN, str_pass, true );
str_pass[38] = 0; // truncate
const char* colname = colour_to_str(you.inv[inum].colour);
snprintf(buf, sizeof buf, "%-7s: <%s>%s</%s>",
slot, colname, str_pass, colname);
}
else
{
if (e_order[i] == EQ_WEAPON)
{
if (you.attribute[ATTR_TRANSFORMATION] == TRAN_BLADE_HANDS)
snprintf(buf, sizeof buf, "%-7s: Blade Hands", slot);
else if (you.skills[SK_UNARMED_COMBAT])
snprintf(buf, sizeof buf, "%-7s: Unarmed", slot);
else
snprintf(buf, sizeof buf, "%-7s:", slot);
}
else
{
snprintf(buf, sizeof buf, "%-7s:", slot);
}
}
cols.add_formatted(2, buf, false);
}
// get next "word"
pos = text.find(' ', oldpos);
if (pos - start >= 80 && pos < strlen(text.c_str())) {
output += formatted_string::parse_string(
text.substr(start, oldpos-start));
output += formatted_string::parse_string(EOL);
start = oldpos;
}
oldpos = pos;
}
output += formatted_string::parse_string(text.substr(start, pos-start));
text = "";
case TRAN_SPIDER:
text += "\nYou are in spider-form.";
break;
case TRAN_BLADE_HANDS:
text += "\nYou have blades for hands.";
break;
case TRAN_STATUE:
text += "\nYou are a statue.";
break;
case TRAN_ICE_BEAST:
text += "\nYou are an ice creature.";
break;
case TRAN_DRAGON:
text += "\nYou are in dragon-form.";
break;
case TRAN_LICH:
text += "\nYou are in lich-form.";
break;
case TRAN_SERPENT_OF_HELL:
text += "\nYou are a huge demonic serpent.";
break;
case TRAN_AIR:
text += "\nYou are a cloud of diffuse gas.";
break;
case TRAN_SPIDER:
text += "\nYou are in spider-form.";
break;
case TRAN_BLADE_HANDS:
text += "\nYou have blades for hands.";
break;
case TRAN_STATUE:
text += "\nYou are a statue.";
break;
case TRAN_ICE_BEAST:
text += "\nYou are an ice creature.";
break;
case TRAN_DRAGON:
text += "\nYou are in dragon-form.";
break;
case TRAN_LICH:
text += "\nYou are in lich-form.";
break;
case TRAN_SERPENT_OF_HELL:
text += "\nYou are a huge demonic serpent.";
break;
case TRAN_AIR:
text += "\nYou are a cloud of diffuse gas.";
break;
case SP_MERFOLK:
text += "change form in water";
have_any = true;
break;
case SP_NAGA:
// breathe poison replaces spit poison:
if (!you.mutation[MUT_BREATHE_POISON])
text += "spit poison" EOL;
else
text += "breathe poison";
case SP_MERFOLK:
text += "change form in water";
have_any = true;
break;
case SP_KENKU:
text += "cannot wear helmets";
if (you.experience_level > 4)
{
text += ", able to fly";
if (you.experience_level > 14)
text += " continuously";
have_any = true;
}
break;
case SP_HIGH_ELF:
if (you.experience_level > 14)
{
text += "charming";
have_any = true;
}
break;
case SP_GOLDEN_DRACONIAN:
if (you.experience_level > 6)
{
text += "spit acid";
text += ", acid resistance";
have_any = true;
}
break;
case SP_GOLDEN_DRACONIAN:
if (you.experience_level > 6)
{
text += "spit acid";
text += ", acid resistance";
have_any = true;
}
break;
start = 0;
oldpos = 0;
for (pos = 0; pos < strlen(text.c_str()); oldpos++)
{
// get next "word"
pos = text.find(' ', oldpos);
if (pos - start >= 80 && pos < strlen(text.c_str()))
{
output += formatted_string::parse_string(text.substr(start, oldpos-start));
output += formatted_string::parse_string(EOL);
start = oldpos;
}
oldpos = pos;
}
output += formatted_string::parse_string(text.substr(start, pos-start));
/*
text = "\n<w>a:</w> ";
have_any = false;
for (unsigned int loopy = 0; loopy < 52; loopy++)
{
if (Curr_abil[loopy].which != ABIL_NON_ABILITY)
{
have_any = true;
const struct ability_def abil = get_ability_def( Curr_abil[loopy].which );
if (have_any)
text += ", ";
snprintf(info, INFO_SIZE, "%s", abil.name );
text += info;
}
}
if (!have_any)
text += "no special abilities";
start = 0;
oldpos = 0;
for (pos = 0; pos < strlen(text.c_str()); oldpos++)
{
// get next "word"
pos = text.find(' ', oldpos);
if (pos - start >= 80 && pos < strlen(text.c_str()))
{
output += formatted_string::parse_string(text.substr(start, oldpos-start));
output += formatted_string::parse_string(EOL);
start = oldpos;
}
oldpos = pos;
}
output += formatted_string::parse_string(text.substr(start, pos-start));
*/
return output;
}
std::vector<formatted_string> get_stat_info()
{
char buf[1000];
// 3 columns, splits at columns 32, 52
column_composer cols(4, 16, 27, 38);
if (!player_rotted())
snprintf(buf, sizeof buf, "HP %3d/%d",you.hp,you.hp_max);
else
snprintf(buf, sizeof buf, "HP %3d/%d (%d)",
you.hp, you.hp_max, you.hp_max + player_rotted() );
cols.add_formatted(0, buf, false);
snprintf(buf, sizeof buf, "MP %3d/%d",
you.magic_points, you.max_magic_points);
cols.add_formatted(0, buf, false);
if (you.strength == you.max_strength)
snprintf(buf, sizeof buf, "Str %3d", you.strength);
else
snprintf(buf, sizeof buf, "Str <yellow>%3d</yellow> (%d)",
you.strength, you.max_strength);
cols.add_formatted(1, buf, false);
if (you.intel == you.max_intel)
snprintf(buf, sizeof buf, "Int %3d", you.intel);
else
snprintf(buf, sizeof buf, "Int <yellow>%3d</yellow> (%d)",
you.intel, you.max_intel);
cols.add_formatted(1, buf, false);
if (you.dex == you.max_dex)
snprintf(buf, sizeof buf, "Dex %3d", you.dex);
else
snprintf(buf, sizeof buf, "Dex <yellow>%3d</yellow> (%d)",
you.dex, you.max_dex);
cols.add_formatted(1, buf, false);
snprintf(buf, sizeof buf,
"AC %3d\n"
"EV %3d\n"
"Sh %3d\n",
player_AC(),
player_evasion(),
player_shield_class());
cols.add_formatted(2, buf, false);
char god_colour_tag[20];
god_colour_tag[0] = 0;
std::string godpowers(god_name(you.religion));
if ( you.religion != GOD_NO_GOD )
{
if ( player_under_penance() )
strcpy(god_colour_tag, "<red>*");
else
{
snprintf(god_colour_tag, sizeof god_colour_tag, "<%s>",
colour_to_str(god_colour(you.religion)));
// piety rankings
int prank = piety_rank() - 1;
if ( prank < 0 )
prank = 0;
// Careful about overflow. We erase some of the god's name
// if necessary.
godpowers = godpowers.substr(0, 17 - prank) +
std::string(prank, '*');
}
}
snprintf(buf, sizeof buf,
"Experience: %d/%lu (%d)\n"
"Spells: %2d memorised, %2d level%s left\n"
"God: %s%s<lightgrey> Gold: %d\n",
you.experience_level, you.experience, you.exp_available,
you.spell_no, player_spell_levels(), (player_spell_levels() == 1) ? "" : "s",
god_colour_tag, godpowers.c_str(), you.gold);
cols.add_formatted(3, buf, false);
text += print_abilities();
linebreak_string2(text, get_number_of_cols());
std::vector<formatted_string> get_res_info(bool calc_unid)
{
char buf[1000];
// 3 columns, splits at columns 21, 38
column_composer cols(3, 21, 38);
const int rfire = player_res_fire(calc_unid);
const int rcold = player_res_cold(calc_unid);
const int rlife = player_prot_life(calc_unid);
const int rpois = player_res_poison(calc_unid);
const int relec = player_res_electricity(calc_unid);
const int rsust = player_sust_abil(calc_unid);
const int rmuta = wearing_amulet(AMU_RESIST_MUTATION, calc_unid);
const int rslow = wearing_amulet(AMU_RESIST_SLOW, calc_unid);
snprintf(buf, sizeof buf,
"%sRes.Fire : %s\n"
"%sRes.Cold : %s\n"
"%sLife Prot.: %s\n"
"%sRes.Poison: %s\n"
"%sRes.Elec. : %s\n"
"\n"
"%sSust.Abil.: %s\n"
"%sRes.Mut. : %s\n"
"%sRes.Slow : %s\n",
determine_color_string(rfire), itosym3(rfire),
determine_color_string(rcold), itosym3(rcold),
determine_color_string(rlife), itosym3(rlife),
determine_color_string(rpois), itosym1(rpois),
determine_color_string(relec), itosym1(relec),
determine_color_string(rsust), itosym1(rsust),
determine_color_string(rmuta), itosym1(rmuta),
determine_color_string(rslow), itosym1(rslow));
cols.add_formatted(0, buf, false);
int saplevel = 0;
switch (you.species)
{
case SP_GHOUL:
saplevel = 3;
snprintf(buf, sizeof buf, "%sSaprovore : %s",
determine_color_string(3), itosym3(3) );
break;
case SP_KOBOLD:
case SP_TROLL:
saplevel = 2;
snprintf(buf, sizeof buf, "%sSaprovore : %s",
determine_color_string(2), itosym3(2) );
break;
case SP_HILL_ORC:
case SP_OGRE:
saplevel = 1;
break;
default:
saplevel = 0;
break;
}
const char* pregourmand;
const char* postgourmand;
if ( wearing_amulet(AMU_THE_GOURMAND, calc_unid) )
{
pregourmand = "Gourmand : ";
postgourmand = itosym1(1);
saplevel = 1;
}
else
{
pregourmand = "Saprovore : ";
postgourmand = itosym3(saplevel);
}
snprintf(buf, sizeof buf, "%s%s%s",
determine_color_string(saplevel), pregourmand, postgourmand);
cols.add_formatted(0, buf, false);
const int rinvi = player_see_invis(calc_unid);
const int rward = wearing_amulet(AMU_WARDING, calc_unid) ||
(you.religion == GOD_VEHUMET && you.duration[DUR_PRAYER] &&
!player_under_penance() && you.piety >= piety_breakpoint(2));
const int rcons = wearing_amulet(AMU_CONSERVATION, calc_unid);
const int rcorr = wearing_amulet(AMU_RESIST_CORROSION, calc_unid);
const int rclar = wearing_amulet(AMU_CLARITY, calc_unid);
snprintf(buf, sizeof buf,
"%sSee Invis. : %s\n"
"%sWarding : %s\n"
"%sConserve : %s\n"
"%sRes.Corr. : %s\n"
"%sClarity : %s\n"
"\n",
determine_color_string(rinvi), itosym1(rinvi),
determine_color_string(rward), itosym1(rward),
determine_color_string(rcons), itosym1(rcons),
determine_color_string(rcorr), itosym1(rcorr),
determine_color_string(rclar), itosym1(rclar));
cols.add_formatted(1, buf, false);
if ( scan_randarts(RAP_PREVENT_TELEPORTATION, calc_unid) )
snprintf(buf, sizeof buf, "\n%sPrev.Telep.: %s",
determine_color_string(-1), itosym1(1));
else
{
const int rrtel = player_teleport(calc_unid);
snprintf(buf, sizeof buf, "\n%sRnd.Telep. : %s",
determine_color_string(rrtel), itosym1(rrtel));
}
cols.add_formatted(1, buf, false);
const int rctel = player_control_teleport(calc_unid);
const int rlevi = player_is_levitating();
const int rcfli = wearing_amulet(AMU_CONTROLLED_FLIGHT, calc_unid);
snprintf(buf, sizeof buf,
"%sCtrl.Telep.: %s\n"
"%sLevitation : %s\n"
"%sCtrl.Flight: %s\n",
determine_color_string(rctel), itosym1(rctel),
determine_color_string(rlevi), itosym1(rlevi),
determine_color_string(rcfli), itosym1(rcfli));
cols.add_formatted(1, buf, false);
{
char str_pass[ITEMNAME_SIZE];
const int e_order[] =
{
EQ_WEAPON, EQ_BODY_ARMOUR, EQ_SHIELD, EQ_HELMET, EQ_CLOAK,
EQ_GLOVES, EQ_BOOTS, EQ_AMULET, EQ_RIGHT_RING, EQ_LEFT_RING
};
for(int i = 0; i < NUM_EQUIP; i++)
{
int eqslot = e_order[i];
const char *slot = equip_slot_to_name( eqslot );
if (eqslot == EQ_LEFT_RING || eqslot == EQ_RIGHT_RING)
slot = "Ring";
else if (eqslot == EQ_BOOTS &&
(you.species == SP_CENTAUR || you.species == SP_NAGA))
slot = "Barding";
if ( you.equip[ e_order[i] ] != -1)
{
const int inum = you.equip[e_order[i]];
in_name( inum, DESC_PLAIN, str_pass, true );
str_pass[38] = 0; // truncate
const char* colname = colour_to_str(you.inv[inum].colour);
snprintf(buf, sizeof buf, "%-7s: <%s>%s</%s>",
slot, colname, str_pass, colname);
}
else
{
if (e_order[i] == EQ_WEAPON)
{
if (you.attribute[ATTR_TRANSFORMATION] == TRAN_BLADE_HANDS)
snprintf(buf, sizeof buf, "%-7s: Blade Hands", slot);
else if (you.skills[SK_UNARMED_COMBAT])
snprintf(buf, sizeof buf, "%-7s: Unarmed", slot);
else
snprintf(buf, sizeof buf, "%-7s:", slot);
}
else
{
snprintf(buf, sizeof buf, "%-7s:", slot);
}
}
cols.add_formatted(2, buf, false);
}
}
return cols.formatted_lines();
}
"<darkgrey>/</darkgrey> : wands (<w>z</w>ap)\n"
// is it possible to replace that with e.g. Options.char_table[DNGN_ITEM_BOOK]
"<lightcyan>+</lightcyan>, <lightcyan>:</lightcyan> : books (<w>r</w>ead, <w>M</w>emorise and <w>Z</w>ap)\n"
"<brown>\\</brown>, <brown>|</brown> : staves, rods (<w>w</w>ield and <w>E</w>voke)\n"
"<darkgrey>/</darkgrey> : wands (<w>z</w>ap)\n"
"<lightcyan>";
get_item_symbol(DNGN_ITEM_BOOK, &ch, &colour);
snprintf(info, INFO_SIZE, "%c", ch);
text += info;
text += "</lightcyan> : books (<w>r</w>ead, <w>M</w>emorise and <w>Z</w>ap)\n"
"<brown>";
get_item_symbol(DNGN_ITEM_STAVE, &ch, &colour);
snprintf(info, INFO_SIZE, "%c", ch);
text += info;
text += "</brown> : staves, rods (<w>w</w>ield and <w>E</w>voke)\n"