From 24be3228d463d6b2501cbca9368e14e91ab42182 Mon Sep 17 00:00:00 2001 From: dshaligram Date: Tue, 6 Feb 2007 16:03:52 +0000 Subject: Tutorial (JPEG) and some formatting cleanup. git-svn-id: https://crawl-ref.svn.sourceforge.net/svnroot/crawl-ref/trunk@924 c06c8d41-db1a-0410-9941-cceddc491573 --- crawl-ref/source/FixVec.h | 14 +- crawl-ref/source/Kills.cc | 9 +- crawl-ref/source/acr.cc | 131 +++- crawl-ref/source/clua.cc | 27 +- crawl-ref/source/command.cc | 65 ++ crawl-ref/source/command.h | 1 + crawl-ref/source/delay.cc | 5 +- crawl-ref/source/dungeon.cc | 12 +- crawl-ref/source/enum.h | 1 + crawl-ref/source/externs.h | 16 + crawl-ref/source/fight.cc | 70 ++- crawl-ref/source/files.cc | 20 + crawl-ref/source/food.cc | 3 + crawl-ref/source/initfile.h | 21 +- crawl-ref/source/invent.h | 3 +- crawl-ref/source/item_use.cc | 67 +- crawl-ref/source/items.cc | 32 +- crawl-ref/source/libutil.cc | 12 +- crawl-ref/source/libw32c.cc | 30 +- crawl-ref/source/makefile.obj | 1 + crawl-ref/source/message.cc | 40 +- crawl-ref/source/misc.cc | 8 +- crawl-ref/source/monstuff.cc | 10 + crawl-ref/source/newgame.cc | 151 ++--- crawl-ref/source/newgame.h | 2 + crawl-ref/source/notes.cc | 70 ++- crawl-ref/source/notes.h | 6 +- crawl-ref/source/ouch.cc | 6 +- crawl-ref/source/player.cc | 15 +- crawl-ref/source/religion.cc | 2 + crawl-ref/source/skills.cc | 2 + crawl-ref/source/skills2.cc | 9 +- crawl-ref/source/stash.cc | 7 + crawl-ref/source/stuff.cc | 23 +- crawl-ref/source/travel.cc | 9 +- crawl-ref/source/tutorial.cc | 1351 +++++++++++++++++++++++++++++++++++++++++ crawl-ref/source/tutorial.h | 100 +++ crawl-ref/source/view.cc | 160 +++-- crawl-ref/source/view.h | 1 + 39 files changed, 2210 insertions(+), 302 deletions(-) create mode 100644 crawl-ref/source/tutorial.cc create mode 100644 crawl-ref/source/tutorial.h (limited to 'crawl-ref') diff --git a/crawl-ref/source/FixVec.h b/crawl-ref/source/FixVec.h index 176ced5cf4..0086750ae3 100644 --- a/crawl-ref/source/FixVec.h +++ b/crawl-ref/source/FixVec.h @@ -22,7 +22,8 @@ // class FixedVector // ========================================================================== -template class FixedVector { +template class FixedVector +{ //----------------------------------- // Types @@ -48,7 +49,8 @@ public: FixedVector() {} - FixedVector(TYPE def) : mData() { + FixedVector(TYPE def) : mData() + { init(def); } @@ -112,10 +114,10 @@ FixedVector::FixedVector(TYPE value0, TYPE value1, ...) va_list ap; va_start(ap, value1); // second argument is last fixed parameter - for (int index = 2; index < SIZE; index++) { - TYPE value = va_arg(ap, TYPE); - - mData[index] = value; + for (int index = 2; index < SIZE; index++) + { + TYPE value = va_arg(ap, TYPE); + mData[index] = value; } va_end(ap); diff --git a/crawl-ref/source/Kills.cc b/crawl-ref/source/Kills.cc index bd1936db50..06b8dc8cd1 100644 --- a/crawl-ref/source/Kills.cc +++ b/crawl-ref/source/Kills.cc @@ -694,14 +694,17 @@ void kill_monster_desc::load(FILE *file) // #define KILLEXP_ACCESS(name, type, field) \ - static int kill_lualc_##name(lua_State *ls) { \ - if (!lua_islightuserdata(ls, 1)) { \ + static int kill_lualc_##name(lua_State *ls) \ + { \ + if (!lua_islightuserdata(ls, 1)) \ + { \ luaL_argerror(ls, 1, "Unexpected argument type"); \ return 0; \ } \ \ kill_exp *ke = static_cast( lua_touserdata(ls, 1) ); \ - if (ke) { \ + if (ke) \ + { \ lua_push##type(ls, ke->field); \ return 1; \ } \ diff --git a/crawl-ref/source/acr.cc b/crawl-ref/source/acr.cc index 80019c23b0..ea6c8daeb1 100644 --- a/crawl-ref/source/acr.cc +++ b/crawl-ref/source/acr.cc @@ -122,6 +122,7 @@ #include "tags.h" #include "transfor.h" #include "travel.h" +#include "tutorial.h" #include "view.h" #include "stash.h" @@ -223,12 +224,15 @@ int main( int argc, char *argv[] ) bool game_start = initialise(); + // override some options for tutorial + init_tutorial_options(); + if (game_start || Options.always_greet) { - snprintf( info, INFO_SIZE, "Welcome, %s the %s %s.", - you.your_name, species_name( you.species,you.experience_level ), you.class_name ); - - mpr( info ); + mprf( "Welcome, %s the %s %s.", + you.your_name, + species_name( you.species,you.experience_level ), + you.class_name ); // Starting messages can go here as this should only happen // at the start of a new game -- bwr @@ -295,6 +299,19 @@ int main( int argc, char *argv[] ) wield_warning(false); } + if (Options.tutorial_left) + { + // print stats and everything + prep_input(); + char ch = 'x'; + mpr("Press any key to start the tutorial intro, or Escape to skip it.", + MSGCH_TUTORIAL); + ch = c_getch(); + + if (ch != ESCAPE) + tut_starting_screen(); + } + if ( game_start ) { snprintf(info, INFO_SIZE, @@ -311,7 +328,6 @@ int main( int argc, char *argv[] ) while (true) { input(); - // cprintf("x"); } // Should never reach this stage, right? @@ -738,6 +754,8 @@ static void handle_wizard_command( void ) // Set up the running variables for the current run. static void start_running( int dir, int mode ) { + if (Options.tutorial_events[TUT_SHIFT_RUN] && mode == RMODE_START) + Options.tutorial_events[TUT_SHIFT_RUN] = 0; if (i_feel_safe(true)) you.running.initialise(dir, mode); } @@ -816,6 +834,30 @@ static void input() fire_monster_alerts(); + + if (Options.tut_just_triggered) + Options.tut_just_triggered = false; + + bool help; + if (Options.tutorial_events[TUT_SEEN_MONSTER]) + help = i_feel_safe(); + + if (Options.tutorial_events[TUT_RUN_AWAY] + && 2*you.hp < you.hp_max && !i_feel_safe()) + { + learned_something_new(TUT_RUN_AWAY); + } + + if (Options.tutorial_left && i_feel_safe()) + { + if ( 2*you.hp < you.hp_max || 2*you.magic_points < you.max_magic_points ) + tutorial_healing_reminder(); + else if (Options.tutorial_events[TUT_SHIFT_RUN] && you.num_turns >= 200) + learned_something_new(TUT_SHIFT_RUN); + else if (Options.tutorial_events[TUT_MAP_VIEW] && you.num_turns >= 500) + learned_something_new(TUT_MAP_VIEW); + } + if ( you.paralysis ) { world_reacts(); @@ -868,7 +910,8 @@ static void input() viewwindow(true, false); } -static int toggle_flag( bool* flag, const char* flagname ) { +static int toggle_flag( bool* flag, const char* flagname ) +{ char buf[INFO_SIZE]; *flag = !(*flag); sprintf( buf, "%s is now %s.", flagname, @@ -877,7 +920,8 @@ static int toggle_flag( bool* flag, const char* flagname ) { return *flag; } -static void go_upstairs() { +static void go_upstairs() +{ if (grd[you.x_pos][you.y_pos] == DNGN_ENTER_SHOP) { if ( you.berserker ) @@ -889,7 +933,8 @@ static void go_upstairs() { else if ((grd[you.x_pos][you.y_pos] < DNGN_STONE_STAIRS_UP_I || grd[you.x_pos][you.y_pos] > DNGN_ROCK_STAIRS_UP) && (grd[you.x_pos][you.y_pos] < DNGN_RETURN_FROM_ORCISH_MINES - || grd[you.x_pos][you.y_pos] >= 150)) { + || grd[you.x_pos][you.y_pos] >= 150)) + { mpr( "You can't go up here!" ); return; } @@ -899,8 +944,8 @@ static void go_upstairs() { 1 + (you.burden_state > BS_UNENCUMBERED) ); } -static void go_downstairs() { - +static void go_downstairs() +{ if ((grd[you.x_pos][you.y_pos] < DNGN_ENTER_LABYRINTH || grd[you.x_pos][you.y_pos] > DNGN_ROCK_STAIRS_DOWN) && grd[you.x_pos][you.y_pos] != DNGN_ENTER_HELL @@ -908,7 +953,8 @@ static void go_downstairs() { || grd[you.x_pos][you.y_pos] > DNGN_TRANSIT_PANDEMONIUM) && grd[you.x_pos][you.y_pos] != DNGN_STONE_ARCH) && !(grd[you.x_pos][you.y_pos] >= DNGN_ENTER_ORCISH_MINES - && grd[you.x_pos][you.y_pos] < DNGN_RETURN_FROM_ORCISH_MINES)) { + && grd[you.x_pos][you.y_pos] < DNGN_RETURN_FROM_ORCISH_MINES)) + { mpr( "You can't go down here!" ); return; } @@ -919,14 +965,16 @@ static void go_downstairs() { you.your_level ); } -static void experience_check() { +static void experience_check() +{ snprintf( info, INFO_SIZE, "You are a level %d %s %s.", you.experience_level, species_name(you.species,you.experience_level), you.class_name); mpr(info); - if (you.experience_level < 27) { + if (you.experience_level < 27) + { int xp_needed = (exp_needed(you.experience_level+2)-you.experience)+1; snprintf( info, INFO_SIZE, "Level %d requires %ld experience (%d point%s to go!)", @@ -936,12 +984,14 @@ static void experience_check() { (xp_needed > 1) ? "s" : ""); mpr(info); } - else { + else + { mpr( "I'm sorry, level 27 is as high as you can go." ); mpr( "With the way you've been playing, I'm surprised you got this far." ); } - if (you.real_time != -1) { + if (you.real_time != -1) + { const time_t curr = you.real_time + (time(NULL) - you.start_time); char buff[200]; @@ -962,13 +1012,14 @@ static void experience_check() { /* note that in some actions, you don't want to clear afterwards. e.g. list_jewellery, etc. */ -void process_command( command_type cmd ) { +void process_command( command_type cmd ) +{ FixedVector < int, 2 > plox; apply_berserk_penalty = true; - switch ( cmd ) { - + switch (cmd) + { case CMD_OPEN_DOOR_UP_RIGHT: open_door(-1, -1); break; case CMD_OPEN_DOOR_UP: open_door( 0, -1); break; case CMD_OPEN_DOOR_UP_LEFT: open_door( 1, -1); break; @@ -1033,12 +1084,14 @@ void process_command( command_type cmd ) { break; case CMD_MAKE_NOTE: +// Options.tut_made_note = 0; make_user_note(); break; case CMD_CLEAR_MAP: if (you.level_type != LEVEL_LABYRINTH && - you.level_type != LEVEL_ABYSS) { + you.level_type != LEVEL_ABYSS) + { mpr("Clearing level map."); clear_map(); } @@ -1057,6 +1110,8 @@ void process_command( command_type cmd ) { break; case CMD_SEARCH_STASHES: + if (Options.tut_stashes) + Options.tut_stashes = 0; stashes.search_stashes(); break; @@ -1096,10 +1151,14 @@ void process_command( command_type cmd ) { break; case CMD_THROW: + if (Options.tutorial_left) + Options.tut_throw_counter++; throw_anything(); break; case CMD_FIRE: + if (Options.tutorial_left) + Options.tut_throw_counter++; shoot_thing(); break; @@ -1198,6 +1257,8 @@ void process_command( command_type cmd ) { break; } + if (Options.tutorial_left) + Options.tut_spell_counter++; if (!cast_a_spell()) flush_input_buffer( FLUSH_ON_FAILURE ); break; @@ -1216,6 +1277,8 @@ void process_command( command_type cmd ) { break; case CMD_INTERLEVEL_TRAVEL: + if (Options.tut_travel) + Options.tut_travel = 0; if (!can_travel_interlevel()) { mpr("Sorry, you can't auto-travel out of here."); @@ -1227,6 +1290,8 @@ void process_command( command_type cmd ) { break; case CMD_EXPLORE: + if (Options.tut_explored) + Options.tut_explored = 0; if (you.level_type == LEVEL_LABYRINTH || you.level_type == LEVEL_ABYSS) { mpr("It would help if you knew where you were, first."); @@ -1237,6 +1302,8 @@ void process_command( command_type cmd ) { break; case CMD_DISPLAY_MAP: + if (Options.tutorial_events[TUT_MAP_VIEW]) + Options.tutorial_events[TUT_MAP_VIEW] = 0; #if (!DEBUG_DIAGNOSTICS) if (you.level_type == LEVEL_LABYRINTH || you.level_type == LEVEL_ABYSS) { @@ -1289,7 +1356,10 @@ void process_command( command_type cmd ) { #endif case CMD_DISPLAY_COMMANDS: - list_commands(false); + if (Options.tutorial_left) + list_tutorial_help(); + else + list_commands(false); redraw_screen(); break; @@ -1372,7 +1442,8 @@ void process_command( command_type cmd ) { } } -static void prep_input() { +static void prep_input() +{ you.time_taken = player_speed(); you.shield_blocks = 0; // no blocks this round #ifdef UNIX @@ -1847,7 +1918,8 @@ static void decrement_durations() chances[1] = you.mutation[MUT_BERSERK] * 25; chances[2] = (wearing_amulet( AMU_RAGE ) ? 10 : 0); chances[3] = (player_has_spell( SPELL_BERSERKER_RAGE ) ? 5 : 0); - const char* reasons[4] = { + const char* reasons[4] = + { "You struggle, and manage to stay standing.", "Your mutated body refuses to collapse.", "You feel your neck pulse as blood rushes through your body.", @@ -1897,6 +1969,9 @@ static void decrement_durations() int dur = 12 + roll_dice( 2, 12 ); you.exhausted += dur; + // for tutorial + unsigned tut_slow = Options.tutorial_events[TUT_YOU_ENCHANTED]; + Options.tutorial_events[TUT_YOU_ENCHANTED] = 0; slow_player( dur ); make_hungry(700, true); @@ -1905,6 +1980,9 @@ static void decrement_durations() you.hunger = 50; calc_hp(); + + learned_something_new(TUT_POSTBERSERK); + Options.tutorial_events[TUT_YOU_ENCHANTED] = tut_slow; } if (you.confusing_touch > 1) @@ -2297,8 +2375,10 @@ static command_type get_next_cmd() } /* for now, this is an extremely yucky hack */ -command_type keycode_to_command( keycode_type key ) { - switch ( key ) { +command_type keycode_to_command( keycode_type key ) +{ + switch ( key ) + { case 'b': return CMD_MOVE_DOWN_LEFT; case 'h': return CMD_MOVE_LEFT; case 'j': return CMD_MOVE_DOWN; @@ -2423,7 +2503,8 @@ keycode_type get_next_keycode() return (keyin); } -static void middle_input() { +static void middle_input() +{ if (Options.stash_tracking) stashes.update_visible_stashes( Options.stash_tracking == STM_ALL? diff --git a/crawl-ref/source/clua.cc b/crawl-ref/source/clua.cc index 0a4d1f3d17..ce8dcd358a 100644 --- a/crawl-ref/source/clua.cc +++ b/crawl-ref/source/clua.cc @@ -43,8 +43,10 @@ #include #define CL_RESETSTACK_RETURN(ls, oldtop, retval) \ - if (true) {\ - if (oldtop != lua_gettop(ls)) { \ + if (true) \ + {\ + if (oldtop != lua_gettop(ls)) \ + { \ lua_settop(ls, oldtop); \ } \ return (retval); \ @@ -318,13 +320,15 @@ int CLua::push_args(lua_State *ls, const char *format, va_list args, format = cs + 1; int argc = 0; - for (const char *run = format; *run; run++) { + for (const char *run = format; *run; run++) + { if (*run == '>') break; char argtype = *run; ++argc; - switch (argtype) { + switch (argtype) + { case 'u': // Light userdata lua_pushlightuserdata(ls, va_arg(args, void*)); break; @@ -1065,7 +1069,8 @@ static int l_item_class(lua_State *ls) } // FIXME: Fold this back into itemname.cc. -static const char *ring_types[] = { +static const char *ring_types[] = +{ "regeneration", "protection", "protection from fire", @@ -1092,7 +1097,8 @@ static const char *ring_types[] = { "teleport control", }; -static const char *amulet_types[] = { +static const char *amulet_types[] = +{ "rage", "resist slowing", "clarity", "warding", "resist corrosion", "gourmand", "conservation", "controlled flight", "inaccuracy", "resist mutation" @@ -2013,7 +2019,8 @@ static int l_mons_y(lua_State *ls, monsters *mons, const char *attr) return (1); } -struct MonsAccessor { +struct MonsAccessor +{ const char *attribute; int (*accessor)(lua_State *ls, monsters *mons, const char *attr); }; @@ -2159,7 +2166,8 @@ void lua_open_globals(lua_State *ls) // We could simplify this a great deal by just using lex and yacc, but I // don't know if we want to introduce them. -struct lua_pat_op { +struct lua_pat_op +{ const char *token; const char *luatok; @@ -2167,7 +2175,8 @@ struct lua_pat_op { bool posttext; // Is this followed by a pattern? }; -static lua_pat_op pat_ops[] = { +static lua_pat_op pat_ops[] = +{ { "<<", " ( ", false, true }, { ">>", " ) ", true, false }, { "!!", " not ", false, true }, diff --git a/crawl-ref/source/command.cc b/crawl-ref/source/command.cc index 814c49e68e..f9eafe4a7f 100644 --- a/crawl-ref/source/command.cc +++ b/crawl-ref/source/command.cc @@ -993,6 +993,71 @@ void list_commands(bool wizard) show_keyhelp_menu(cols.formatted_lines(), true); } +void list_tutorial_help() +{ + // 2 columns, split at column 40. + column_composer cols(2, 41); + // Page size is number of lines - one line for --more-- prompt. + cols.set_pagesize(get_number_of_lines()); + + cols.add_formatted( + 0, + "Item types (and common commands)\n" + ") : hand weapons (wield)\n" + "( : missiles (throw or fire)\n" + "[ : armour (Wear and Take off)\n" + "% : food and corpses (eat and Dissect)\n" + "? : scrolls (read)\n" + "! : potions (quaff)\n" + "= : rings (Put on and Remove)\n" + "\" : amulets (Put on and Remove)\n" + "/ : wands (zap)\n" + // is it possible to replace that with e.g. Options.char_table[DNGN_ITEM_BOOK] + "+, : : books (read, Memorise and Zap)\n" + "\\, | : staves, rods (wield and Evoke)\n" + "\n" + "Movement and attacking\n" + "Use the numpad for movement (try both\n" + "Numlock on and off). You can also use\n" + " hjkl : left, down, up, right and\n" + " yubn : diagonal movement.\n" + "Walking into a monster will attack it\n" + "with the wielded weapon or barehanded.\n" + "For ranged attacks use either\n" + "f to launch missiles (like arrows)\n" + "t to throw items by hand (like darts)\n" + "Z to cast spells (Z? lists spells).\n", + true, true, cmdhelp_textfilter); + + cols.add_formatted( + 1, + "Additional important commands\n" + "S : Save the game and exit\n" + "s : search for one turn (also . and Del)\n" + "5 : rest full/search longer (Shift-Num 5)\n" + "x : examine surroundings\n" + "v : examine object in inventory\n" + "i : list inventory\n" + "g : pick up item from ground (also ,)\n" + "d : drop item\n" + "X : show map of the whole level\n" + "<< or > : ascend/descend the stairs\n" + "Ctrl-P : show previous messages\n" + "\n" + "Targeting (for spells and missiles)\n" + "Use + (or =) and - to cycle between\n" + "hostile monsters. Enter or . or Del\n" + "all fire at the selected target.\n" + "If the previous target is still alive\n" + "and in sight, one of f, p, t fires\n" + "at it again (without selecting anything).\n" + "Any movement key fires straight away in\n" + "the chosen direction.\n", + true, true, cmdhelp_textfilter,40); + + show_keyhelp_menu(cols.formatted_lines(), false); +} + static void list_wizard_commands() { const char *line; diff --git a/crawl-ref/source/command.h b/crawl-ref/source/command.h index f33e79954a..37ed7353c3 100644 --- a/crawl-ref/source/command.h +++ b/crawl-ref/source/command.h @@ -62,6 +62,7 @@ void swap_inv_slots(int slot1, int slot2, bool verbose); void show_levelmap_help(); void show_targeting_help(); void list_commands(bool wizard); +void list_tutorial_help(void); // Actually defined in acr.cc; we may want to move this to command.cc void process_command(command_type cmd); diff --git a/crawl-ref/source/delay.cc b/crawl-ref/source/delay.cc index 2279e4fb68..fdc04f3360 100644 --- a/crawl-ref/source/delay.cc +++ b/crawl-ref/source/delay.cc @@ -38,6 +38,7 @@ #include "spl-util.h" #include "stuff.h" #include "travel.h" +#include "tutorial.h" extern std::vector items_for_multidrop; @@ -751,8 +752,10 @@ static void armour_wear_effects(const int item_slot) if (is_random_artefact( arm )) use_randart( item_slot ); - if (item_cursed( arm )) + if (item_cursed( arm )) { mpr( "Oops, that feels deathly cold." ); + learned_something_new(TUT_YOU_CURSED); + } if (eq_slot == EQ_SHIELD) warn_shield_penalties(); diff --git a/crawl-ref/source/dungeon.cc b/crawl-ref/source/dungeon.cc index 7aac2b04b5..ba8baac296 100644 --- a/crawl-ref/source/dungeon.cc +++ b/crawl-ref/source/dungeon.cc @@ -56,12 +56,14 @@ #define MAX_PIT_MONSTERS 10 -struct pit_mons_def { +struct pit_mons_def +{ int type; int rare; }; -struct spec_t { +struct spec_t +{ bool created; bool hooked_up; int x1; @@ -3838,7 +3840,8 @@ static int builder_by_type(int level_number, char level_type) if (which_demon >= 0) { - const char *pandemon_level_names[] = { + const char *pandemon_level_names[] = + { "mnoleg", "lom_lobon", "cerebov", "gloorx_vloq" }; you.unique_creatures[40 + which_demon] = 1; @@ -3896,6 +3899,9 @@ static int random_map_for_dlevel(int level_number, bool wantmini = false) vault = random_map_for_tag("entry", wantmini); } + if (Options.tutorial_left) + vault = -1; + return (vault); } diff --git a/crawl-ref/source/enum.h b/crawl-ref/source/enum.h index 15fb845e5c..4819753756 100644 --- a/crawl-ref/source/enum.h +++ b/crawl-ref/source/enum.h @@ -1739,6 +1739,7 @@ enum msg_channel_type MSGCH_FLOOR_ITEMS, // like equipment, but lists of floor items MSGCH_MULTITURN_ACTION, // delayed action messages MSGCH_DIAGNOSTICS, // various diagnostic messages + MSGCH_TUTORIAL, // messages for tutorial NUM_MESSAGE_CHANNELS // always last }; diff --git a/crawl-ref/source/externs.h b/crawl-ref/source/externs.h index 3d90aeb275..8db5af8ee5 100644 --- a/crawl-ref/source/externs.h +++ b/crawl-ref/source/externs.h @@ -996,6 +996,22 @@ public: int prev_book; bool prev_randpick; + /////////////////////////////////////////////////////////////////////// + // tutorial + FixedVector tutorial_events; +// bool tut_made_note; + bool tut_explored; + bool tut_stashes; + bool tut_travel; + unsigned int tut_spell_counter; + unsigned int tut_throw_counter; + unsigned int tut_berserk_counter; + unsigned tut_last_healed; + + bool tut_just_triggered; + unsigned int tutorial_type; + unsigned int tutorial_left; + public: // Convenience accessors for the second-class options in named_options. int o_int(const char *name, int def = 0) const; diff --git a/crawl-ref/source/fight.cc b/crawl-ref/source/fight.cc index 6f2f3ad4e7..0c41990f30 100644 --- a/crawl-ref/source/fight.cc +++ b/crawl-ref/source/fight.cc @@ -69,6 +69,7 @@ #include "spells4.h" #include "spl-cast.h" #include "stuff.h" +#include "tutorial.h" #include "view.h" #define HIT_WEAK 7 @@ -130,7 +131,8 @@ int effective_stat_bonus( int wepType ) // returns random2(x) is random_factor is true, otherwise // the mean. -static int maybe_random2( int x, bool random_factor ) { +static int maybe_random2( int x, bool random_factor ) +{ if ( random_factor ) return random2(x); else @@ -139,8 +141,8 @@ static int maybe_random2( int x, bool random_factor ) { // Returns the to-hit for your extra unarmed.attacks. // DOES NOT do the final roll (i.e., random2(your_to_hit)). -static int calc_your_to_hit_unarmed() { - +static int calc_your_to_hit_unarmed() +{ int your_to_hit; your_to_hit = 13 + you.dex / 2 + you.skills[SK_UNARMED_COMBAT] / 2 @@ -162,14 +164,16 @@ static int calc_your_to_hit_unarmed() { int calc_your_to_hit( int heavy_armour, bool hand_and_a_half_bonus, bool water_attack, - bool random_factor ) { + bool random_factor ) +{ const int weapon = you.equip[EQ_WEAPON]; const bool ur_armed = (weapon != -1); // compacts code a bit {dlb} int wpn_skill = SK_UNARMED_COMBAT; - if (weapon != -1) { + if (weapon != -1) + { wpn_skill = weapon_skill( you.inv[weapon].base_type, you.inv[weapon].sub_type ); } @@ -193,13 +197,16 @@ int calc_your_to_hit( int heavy_armour, your_to_hit += maybe_random2(1 + you.skills[SK_FIGHTING], random_factor); // weapon skill contribution - if (ur_armed) { - if (wpn_skill != SK_FIGHTING) { + if (ur_armed) + { + if (wpn_skill != SK_FIGHTING) + { your_to_hit += maybe_random2(you.skills[wpn_skill] + 1, random_factor); } } - else { // ...you must be unarmed + else + { // ...you must be unarmed your_to_hit += (you.species == SP_TROLL || you.species == SP_GHOUL) ? 4 : 2; @@ -261,12 +268,14 @@ int calc_your_to_hit( int heavy_armour, you.sure_blade / 2); // other stuff - if (!ur_armed) { + if (!ur_armed) + { if ( you.confusing_touch ) // just trying to touch is easier that trying to damage your_to_hit += maybe_random2(you.dex, random_factor); - switch ( you.attribute[ATTR_TRANSFORMATION] ) { + switch ( you.attribute[ATTR_TRANSFORMATION] ) + { case TRAN_NONE: break; case TRAN_SPIDER: @@ -301,8 +310,8 @@ int calc_your_to_hit( int heavy_armour, // Calculates your heavy armour penalty. If random_factor is true, // be stochastic; if false, deterministic (e.g. for chardumps.) -int calc_heavy_armour_penalty( bool random_factor ) { - +int calc_heavy_armour_penalty( bool random_factor ) +{ const bool ur_armed = (you.equip[EQ_WEAPON] != -1); int heavy_armour = 0; @@ -350,12 +359,15 @@ int calc_heavy_armour_penalty( bool random_factor ) { // ??? what is the reasoning behind this ??? {dlb} // My guess is that its supposed to encourage monk-style play -- bwr - if (!ur_armed) { - if ( random_factor ) { + if (!ur_armed) + { + if ( random_factor ) + { heavy_armour *= (coinflip() ? 3 : 2); } // (2+3)/2 - else { + else + { heavy_armour *= 5; heavy_armour /= 2; } @@ -401,6 +413,10 @@ bool you_attack(int monster_attacked, bool unarmed_attacks) // We're trying to hit a monster, break out of travel/explore now. interrupt_activity(AI_HIT_MONSTER, defender); + if (ur_armed && (you.inv[weapon].base_type != OBJ_WEAPONS + || you.inv[weapon].sub_type == WPN_BOW || you.inv[weapon].sub_type == WPN_SLING)) + learned_something_new(TUT_WIELD_WEAPON); + if (ur_armed && you.inv[weapon].base_type == OBJ_WEAPONS && is_random_artefact( you.inv[weapon] )) { @@ -2567,9 +2583,10 @@ void monster_attack(int monster_attacking) if (you.paralysis > 0) mpr("You still can't move!", MSGCH_WARN); else - mpr("You suddenly lose the ability to move!", MSGCH_WARN); - if ( you.paralysis == 0 || mclas == MONS_RED_WASP ) - you.paralysis += 1 + random2(3); + mpr("You suddenly lose the ability to move!", + MSGCH_WARN); + if ( you.paralysis == 0 || mclas == MONS_RED_WASP ) + you.paralysis += 1 + random2(3); } break; @@ -2743,15 +2760,16 @@ void monster_attack(int monster_attacking) } break; - case MONS_MOTH_OF_WRATH: - if (one_chance_in(3)) { - simple_monster_message(attacker, " infuriates you!"); - go_berserk(false); - } - break; + case MONS_MOTH_OF_WRATH: + if (one_chance_in(3)) + { + simple_monster_message(attacker, " infuriates you!"); + go_berserk(false); + } + break; - default: - break; + default: + break; } // end of switch for special attacks. /* use break for level drain, maybe with beam variables, because so many creatures use it. */ diff --git a/crawl-ref/source/files.cc b/crawl-ref/source/files.cc index b8b5aa518d..7ea4991c0d 100644 --- a/crawl-ref/source/files.cc +++ b/crawl-ref/source/files.cc @@ -76,6 +76,7 @@ #include "stuff.h" #include "tags.h" #include "travel.h" +#include "tutorial.h" #ifdef SHARED_FILES_CHMOD_PRIVATE #define DO_CHMOD_PRIVATE(x) chmod( (x), SHARED_FILES_CHMOD_PRIVATE ) @@ -1202,6 +1203,16 @@ void save_game(bool leave_game) DO_CHMOD_PRIVATE(notesFile.c_str()); } + /* tutorial */ + + std::string tutorFile = get_savedir_filename(you.your_name, "", "tut"); + FILE *tutorf = fopen(tutorFile.c_str(), "wb"); + if (tutorf) { + save_tutorial(tutorf); + fclose(tutorf); + DO_CHMOD_PRIVATE(tutorFile.c_str()); + } + std::string charFile = get_savedir_filename(you.your_name, "", "sav"); FILE *charf = fopen(charFile.c_str(), "wb"); if (!charf) @@ -1399,6 +1410,15 @@ void restore_game(void) load_notes(notesf); fclose(notesf); } + + /* tutorial */ + + std::string tutorFile = get_savedir_filename(you.your_name, "", "tut"); + FILE *tutorf = fopen(tutorFile.c_str(), "rb"); + if (tutorf) { + load_tutorial(tutorf); + fclose(tutorf); + } } static bool determine_version( FILE *restoreFile, diff --git a/crawl-ref/source/food.cc b/crawl-ref/source/food.cc index e042123eb0..0ac4a3801c 100644 --- a/crawl-ref/source/food.cc +++ b/crawl-ref/source/food.cc @@ -44,6 +44,7 @@ #include "skills2.h" #include "spells2.h" #include "stuff.h" +#include "tutorial.h" static int determine_chunk_effect(int which_chunk_type, bool rotten_chunk); static void eat_chunk( int chunk_effect ); @@ -506,9 +507,11 @@ static bool food_change(bool suppress_message) { case HS_STARVING: mpr("You are starving!", MSGCH_FOOD); + learned_something_new(TUT_YOU_STARVING); break; case HS_HUNGRY: mpr("You are feeling hungry.", MSGCH_FOOD); + learned_something_new(TUT_YOU_HUNGRY); break; default: break; diff --git a/crawl-ref/source/initfile.h b/crawl-ref/source/initfile.h index f5c05972e0..ae76fda4bc 100644 --- a/crawl-ref/source/initfile.h +++ b/crawl-ref/source/initfile.h @@ -57,22 +57,26 @@ std::string channel_to_str(int ch); int str_to_channel(const std::string &); -class InitLineInput { +class InitLineInput +{ public: virtual ~InitLineInput() { } virtual bool eof() = 0; virtual std::string getline() = 0; }; -class FileLineInput : public InitLineInput { +class FileLineInput : public InitLineInput +{ public: FileLineInput(FILE *f) : file(f) { } - bool eof() { + bool eof() + { return !file || feof(file); } - std::string getline() { + std::string getline() + { char s[256] = ""; if (!eof()) fgets(s, sizeof s, file); @@ -82,15 +86,18 @@ private: FILE *file; }; -class StringLineInput : public InitLineInput { +class StringLineInput : public InitLineInput +{ public: StringLineInput(const std::string &s) : str(s), pos(0) { } - bool eof() { + bool eof() + { return pos >= str.length(); } - std::string getline() { + std::string getline() + { if (eof()) return ""; std::string::size_type newl = str.find("\n", pos); diff --git a/crawl-ref/source/invent.h b/crawl-ref/source/invent.h index 9ce3fbc5ac..5696a2ea8a 100644 --- a/crawl-ref/source/invent.h +++ b/crawl-ref/source/invent.h @@ -69,7 +69,8 @@ private: void add_class_hotkeys(const item_def &i); }; -class InvShowPrices { +class InvShowPrices +{ public: InvShowPrices(bool doshow = true); ~InvShowPrices(); diff --git a/crawl-ref/source/item_use.cc b/crawl-ref/source/item_use.cc index f6cb476c34..1f65e6233d 100644 --- a/crawl-ref/source/item_use.cc +++ b/crawl-ref/source/item_use.cc @@ -66,6 +66,7 @@ #include "spl-cast.h" #include "stuff.h" #include "transfor.h" +#include "tutorial.h" #include "view.h" bool drink_fountain(void); @@ -599,8 +600,8 @@ bool armour_prompt( const std::string & mesg, int *index, operation_types oper) else { slot = prompt_invent_item( mesg.c_str(), MT_INVSELECT, OBJ_ARMOUR, - true, true, true, 0, NULL, - oper ); + true, true, true, 0, NULL, + oper ); if (slot != PROMPT_ABORT) { @@ -1020,7 +1021,7 @@ void throw_anything(void) throw_slot = prompt_invent_item( "Throw which item? (* to show all)", MT_INVSELECT, OBJ_MISSILES, true, true, true, 0, NULL, - OPER_THROW ); + OPER_THROW ); if (throw_slot == PROMPT_ABORT) { canned_msg( MSG_OK ); @@ -2054,8 +2055,11 @@ void jewellery_wear_effects(item_def &item) set_ident_flags( item, ISFLAG_EQ_JEWELLERY_MASK ); if (item_cursed( item )) + { mprf("Oops, that %s feels deathly cold.", jewellery_is_amulet(item)? "amulet" : "ring"); + learned_something_new(TUT_YOU_CURSED); + } // cursed or not, we know that since we've put the ring on set_ident_flags( item, ISFLAG_KNOW_CURSE ); @@ -2373,8 +2377,8 @@ bool remove_ring(int slot, bool announce) int equipn = slot == -1? prompt_invent_item( "Remove which piece of jewellery?", MT_INVSELECT, - OBJ_JEWELLERY, true, true, true, - 0, NULL, OPER_REMOVE) + OBJ_JEWELLERY, true, true, true, + 0, NULL, OPER_REMOVE) : slot; if (equipn == PROMPT_ABORT) @@ -2469,8 +2473,8 @@ void zap_wand(void) item_slot = prompt_invent_item( "Zap which item?", MT_INVSELECT, OBJ_WANDS, - true, true, true, 0, NULL, - OPER_ZAP ); + true, true, true, 0, NULL, + OPER_ZAP ); if (item_slot == PROMPT_ABORT) { canned_msg( MSG_OK ); @@ -2488,22 +2492,22 @@ void zap_wand(void) if (you.equip[EQ_WEAPON] == item_slot) you.wield_change = true; - if ( you.inv[item_slot].plus < 1 ) { - // it's an empty wand, inscribe it that way + if ( you.inv[item_slot].plus < 1 ) + { + // it's an empty wand, inscribe it that way canned_msg(MSG_NOTHING_HAPPENS); you.turn_is_over = true; - if ( !item_ident(you.inv[item_slot], ISFLAG_KNOW_PLUSES) ) { - - if ( you.inv[item_slot].inscription.find("empty") == - std::string::npos ) { - - if ( !you.inv[item_slot].inscription.empty() ) - you.inv[item_slot].inscription += ' '; - you.inv[item_slot].inscription += "empty"; - - } - } - return; + if ( !item_ident(you.inv[item_slot], ISFLAG_KNOW_PLUSES) ) + { + if ( you.inv[item_slot].inscription.find("empty") == + std::string::npos ) + { + if ( !you.inv[item_slot].inscription.empty() ) + you.inv[item_slot].inscription += ' '; + you.inv[item_slot].inscription += "empty"; + } + } + return; } if (item_type_known( you.inv[item_slot] )) @@ -2620,8 +2624,8 @@ void inscribe_item() char buf[79]; if (inv_count() < 1) { - mpr("You don't have anything to inscribe."); - return; + mpr("You don't have anything to inscribe."); + return; } item_slot = prompt_invent_item( "Inscribe which item? ", @@ -2669,8 +2673,8 @@ void drink(void) item_slot = prompt_invent_item( "Drink which item?", MT_INVSELECT, OBJ_POTIONS, - true, true, true, 0, NULL, - OPER_QUAFF ); + true, true, true, 0, NULL, + OPER_QUAFF ); if (item_slot == PROMPT_ABORT) { canned_msg( MSG_OK ); @@ -3243,8 +3247,8 @@ void read_scroll(void) case SCR_PAPER: // remember paper scrolls handled as special case above, too: mpr("This scroll appears to be blank."); - if (you.mutation[MUT_BLURRY_VISION] == 3) - id_the_scroll = false; + if (you.mutation[MUT_BLURRY_VISION] == 3) + id_the_scroll = false; break; case SCR_RANDOM_USELESSNESS: @@ -3324,7 +3328,7 @@ void read_scroll(void) case SCR_IMMOLATION: mpr("The scroll explodes in your hands!"); - // we do this here to prevent it from blowing itself up + // we do this here to prevent it from blowing itself up dec_inv_item_quantity( item_slot, 1 ); beam.type = SYM_BURST; @@ -3345,7 +3349,8 @@ void read_scroll(void) break; case SCR_IDENTIFY: - if ( get_ident_type( OBJ_SCROLLS, scroll_type ) != ID_KNOWN_TYPE ) { + if ( get_ident_type( OBJ_SCROLLS, scroll_type ) != ID_KNOWN_TYPE ) + { mpr("This is a scroll of identify!"); more(); } @@ -3519,8 +3524,8 @@ void examine_object(void) { int item_slot = prompt_invent_item( "Examine which item?", MT_INVSELECT, -1, - true, true, true, 0, NULL, - OPER_EXAMINE ); + true, true, true, 0, NULL, + OPER_EXAMINE ); if (item_slot == PROMPT_ABORT) { canned_msg( MSG_OK ); diff --git a/crawl-ref/source/items.cc b/crawl-ref/source/items.cc index a9093da5fa..22bde71fe0 100644 --- a/crawl-ref/source/items.cc +++ b/crawl-ref/source/items.cc @@ -59,6 +59,7 @@ #include "spl-cast.h" #include "stuff.h" #include "stash.h" +#include "tutorial.h" static bool invisible_to_player( const item_def& item ); static void item_list_on_square( std::vector& items, @@ -826,8 +827,10 @@ void item_check(char keyin) } } - if (counter_max > 5 && keyin != ';') + if (counter_max > 5 && keyin != ';') { mpr("There are several objects here."); + learned_something_new(TUT_MULTI_PICKUP); + } } void show_items() @@ -873,11 +876,13 @@ void pickup_menu(int item_link) if (result == 0) { mpr("You can't carry that much weight."); + learned_something_new(TUT_HEAVY_LOAD); return; } else if (result == -1) { mpr("You can't carry that many items."); + learned_something_new(TUT_HEAVY_LOAD); return; } break; @@ -1142,11 +1147,13 @@ bool pickup_single_item(int link, int qty) if (num == -1) { mpr("You can't carry that many items."); + learned_something_new(TUT_HEAVY_LOAD); return (false); } else if (num == 0) { mpr("You can't carry that much weight."); + learned_something_new(TUT_HEAVY_LOAD); return (false); } @@ -1179,7 +1186,8 @@ void pickup() { if (inv_count() >= ENDOFPACK) { - mpr("There is a portable altar here, but you can't carry anything else."); + mpr("There is a portable altar here, " + "but you can't carry anything else."); return; } @@ -1225,8 +1233,8 @@ void pickup() } else if (mitm[o].link == NON_ITEM) // just one item? { - // deliberately allowing the player to pick up - // a killed item here + // deliberately allowing the player to pick up + // a killed item here pickup_single_item(o, mitm[o].quantity); } else if (Options.pickup_mode != -1 && @@ -1243,9 +1251,10 @@ void pickup() // must save this because pickup can destroy the item next = mitm[o].link; - if ( num_nonsquelched && invisible_to_player(mitm[o]) ) { + if ( num_nonsquelched && invisible_to_player(mitm[o]) ) + { o = next; - continue; + continue; } if (keyin != 'a') @@ -1542,7 +1551,6 @@ int move_item_to_player( int obj, int quant_got, bool quiet ) item.link = freeslot; autoinscribe_item( item ); - origin_freeze(item, you.x_pos, you.y_pos); @@ -1555,7 +1563,14 @@ int move_item_to_player( int obj, int quant_got, bool quiet ) in_name( freeslot, DESC_INVENTORY, info ); mpr(info); } - + + if (Options.tutorial_left) + { + taken_new_item(item.base_type); + if (is_random_artefact(you.inv[freeslot])) + learned_something_new(TUT_SEEN_RANDART); + } + if (item.base_type == OBJ_ORBS && you.char_direction == DIR_DESCENDING) { @@ -2856,6 +2871,7 @@ void handle_time( long time_delta ) MSGCH_ROTTEN_MEAT ); break; } + learned_something_new(TUT_ROTTEN_FOOD); } // exercise armour *xor* stealth skill: {dlb} diff --git a/crawl-ref/source/libutil.cc b/crawl-ref/source/libutil.cc index 0a7caaaaa4..d380b92419 100644 --- a/crawl-ref/source/libutil.cc +++ b/crawl-ref/source/libutil.cc @@ -88,16 +88,20 @@ int unmangle_direction_keys(int keyin, int km) /* FIXME haranp - hackiness */ const char DOSidiocy[10] = { "OPQKSMGHI" }; const char DOSunidiocy[10] = { "bjnh.lyku" }; - const int DOScontrolidiocy[9] = { + const int DOScontrolidiocy[9] = + { 117, 145, 118, 115, 76, 116, 119, 141, 132 }; keyin = getchm(keymap); - for (int j = 0; j < 9; ++j ) { - if (keyin == DOSidiocy[j]) { + for (int j = 0; j < 9; ++j ) + { + if (keyin == DOSidiocy[j]) + { keyin = DOSunidiocy[j]; break; } - if (keyin == DOScontrolidiocy[j]) { + if (keyin == DOScontrolidiocy[j]) + { keyin = CONTROL(toupper(DOSunidiocy[j])); break; } diff --git a/crawl-ref/source/libw32c.cc b/crawl-ref/source/libw32c.cc index dea7c01fb9..f07f2ba00c 100644 --- a/crawl-ref/source/libw32c.cc +++ b/crawl-ref/source/libw32c.cc @@ -162,7 +162,8 @@ static void addcall(int i, LARGE_INTEGER &tm1, LARGE_INTEGER &tm2) #define CLOCKOUT(x) {QueryPerformanceCounter(&t2); \ addcall((x), t1, t2);} -static char *descrip[] = { +static char *descrip[] = +{ "bflush:WriteConsoleOutput", "_setCursorType:SetConsoleCursorInfo", "gotoxy:SetConsoleCursorPosition", @@ -314,12 +315,14 @@ void setStringInput(bool value) outmodes = 0; } - if ( SetConsoleMode( inbuf, inmodes ) == 0) { + if ( SetConsoleMode( inbuf, inmodes ) == 0) + { fputs("Error initialising console input mode.", stderr); exit(0); } - if ( SetConsoleMode( outbuf, outmodes ) == 0) { + if ( SetConsoleMode( outbuf, outmodes ) == 0) + { fputs("Error initialising console output mode.", stderr); exit(0); } @@ -354,7 +357,8 @@ void init_libw32c(void) inbuf = GetStdHandle( STD_INPUT_HANDLE ); outbuf = GetStdHandle( STD_OUTPUT_HANDLE ); - if (inbuf == INVALID_HANDLE_VALUE || outbuf == INVALID_HANDLE_VALUE) { + if (inbuf == INVALID_HANDLE_VALUE || outbuf == INVALID_HANDLE_VALUE) + { fputs("Could not initialise libw32c console support.", stderr); exit(0); } @@ -370,7 +374,8 @@ void init_libw32c(void) // by default, set string input to false: use char-input only setStringInput( false ); - if (SetConsoleMode( outbuf, 0 ) == 0) { + if (SetConsoleMode( outbuf, 0 ) == 0) + { fputs("Error initialising console output mode.", stderr); exit(0); } @@ -715,7 +720,8 @@ static int vk_tr[4][VKEY_MAPPINGS] = // virtual key, unmodified, shifted, contro { CK_CTRL_END, CK_CTRL_DOWN, CK_CTRL_PGDN, CK_CTRL_LEFT, CK_CTRL_CLEAR, CK_CTRL_RIGHT, CK_CTRL_HOME, CK_CTRL_UP, CK_CTRL_PGUP, CK_CTRL_INSERT }, }; -static int ck_tr[] = { +static int ck_tr[] = +{ 'k', 'j', 'h', 'l', '0', 'y', 'b', '.', 'u', 'n', // 'b', 'j', 'n', 'h', '.', 'l', 'y', 'k', 'u' , '8', '2', '4', '6', '0', '7', '1', '5', '9', '3', @@ -724,7 +730,8 @@ static int ck_tr[] = { // 2, 10, 14, 8, 0, 12, 25, 11, 21 , }; -int key_to_command( int keyin ) { +int key_to_command( int keyin ) +{ if (keyin >= CK_UP && keyin <= CK_CTRL_PGDN) return ck_tr[ keyin - CK_UP ]; @@ -772,7 +779,8 @@ int vk_translate( WORD VirtCode, CHAR c, DWORD cKeys) if (VirtCode == vk_tr[0][mkey]) break; // step 4 - just return the damn key. - if (mkey == VKEY_MAPPINGS) { + if (mkey == VKEY_MAPPINGS) + { if (c) return c; @@ -884,9 +892,11 @@ int kbhit() INPUT_RECORD ir[10]; DWORD read_count = 0; PeekConsoleInput(inbuf, ir, sizeof ir / sizeof(ir[0]), &read_count); - if (read_count > 0) { + if (read_count > 0) + { for (unsigned i = 0; i < read_count; ++i) - if (ir[i].EventType == KEY_EVENT) { + if (ir[i].EventType == KEY_EVENT) + { KEY_EVENT_RECORD *kr; kr = &(ir[i].Event.KeyEvent); diff --git a/crawl-ref/source/makefile.obj b/crawl-ref/source/makefile.obj index da61c5d0d2..89afdcb15b 100644 --- a/crawl-ref/source/makefile.obj +++ b/crawl-ref/source/makefile.obj @@ -67,6 +67,7 @@ stuff.o \ tags.o \ transfor.o \ travel.o \ +tutorial.o \ view.o \ Kills.o \ mt19937ar.o \ diff --git a/crawl-ref/source/message.cc b/crawl-ref/source/message.cc index 9040a2d2ea..5e3d72c639 100644 --- a/crawl-ref/source/message.cc +++ b/crawl-ref/source/message.cc @@ -36,6 +36,7 @@ #include "notes.h" #include "stash.h" #include "religion.h" +#include "tutorial.h" // circular buffer for keeping past messages message_item Store_Message[ NUM_STORED_MESSAGES ]; // buffer of old messages @@ -136,7 +137,7 @@ int channel_to_colour( int channel, int param ) switch (channel) { case MSGCH_GOD: - case MSGCH_PRAY: + case MSGCH_PRAY: ret = (Options.channels[ channel ] == MSGCOL_DEFAULT) ? god_colour( param ) : god_message_altar_colour( param ); @@ -170,6 +171,10 @@ int channel_to_colour( int channel, int param ) ret = WHITE; break; + case MSGCH_TUTORIAL: + ret = MAGENTA; + break; + case MSGCH_MONSTER_SPELL: case MSGCH_MONSTER_ENCHANT: ret = LIGHTMAGENTA; @@ -316,8 +321,10 @@ static void base_mpr(const char *inf, int channel, int param) std::string imsg = inf; - for (unsigned i = 0; i < Options.note_messages.size(); ++i) { - if (Options.note_messages[i].matches(imsg)) { + for (unsigned i = 0; i < Options.note_messages.size(); ++i) + { + if (Options.note_messages[i].matches(imsg)) + { take_note(Note(NOTE_MESSAGE, channel, param, inf)); break; } @@ -414,8 +421,10 @@ void formatted_mpr(const formatted_string& fs, int channel, int param) const std::string imsg = fs.tostring(); - for (unsigned i = 0; i < Options.note_messages.size(); ++i) { - if (Options.note_messages[i].matches(imsg)) { + for (unsigned i = 0; i < Options.note_messages.size(); ++i) + { + if (Options.note_messages[i].matches(imsg)) + { take_note(Note(NOTE_MESSAGE, channel, param, imsg.c_str())); break; } @@ -544,13 +553,18 @@ void more(void) { char keypress = 0; - message_out(get_message_window_height() - 1, - LIGHTGREY, "--more--", 2, false); + if (Options.tutorial_left) + message_out(get_message_window_height() - 1, + LIGHTGREY, + "--more-- " + "Press Ctrl-P to reread old messages", + 2, false); + else + message_out(get_message_window_height() - 1, + LIGHTGREY, "--more--", 2, false); do - { keypress = getch(); - } while (keypress != ' ' && keypress != '\r' && keypress != '\n'); mesclr( !Options.delay_message_clear && @@ -650,6 +664,14 @@ void replay_messages(void) #if DEBUG_DIAGNOSTICS cprintf( "%d: %s", line, Store_Message[ line ].text.c_str() ); #else + /* TODO: allow colour changes in previous messages, as well + if (Store_Message[ line ].channel == MSGCH_TUTORIAL) + { + formatted_string help = formatted_string::parse_string(Store_Message[ line ].text); + help.display(); + } + else + */ cprintf( "%s", Store_Message[ line ].text.c_str() ); #endif diff --git a/crawl-ref/source/misc.cc b/crawl-ref/source/misc.cc index 3c9f0755fa..b3dd260423 100644 --- a/crawl-ref/source/misc.cc +++ b/crawl-ref/source/misc.cc @@ -61,6 +61,7 @@ #include "stuff.h" #include "transfor.h" #include "travel.h" +#include "tutorial.h" #include "view.h" extern FixedVector Visible_Statue; // defined in acr.cc @@ -1370,6 +1371,7 @@ void handle_traps(char trt, int i, bool trap_known) 75 + random2(100), 3, "a Zot trap" ); break; } + learned_something_new(TUT_SEEN_TRAPS); } // end handle_traps() void disarm_trap( struct dist &disa ) @@ -1687,6 +1689,8 @@ bool go_berserk(bool intentional) return false; } + if (Options.tutorial_left) + Options.tut_berserk_counter++; mpr("A red film seems to cover your vision as you go berserk!"); mpr("You feel yourself moving faster!"); mpr("You feel mighty!"); @@ -1918,13 +1922,15 @@ bool i_feel_safe(bool announce) { if (announce) mons.push_back(mon); - else + else { + tutorial_first_monster(*mon); return false; } } } } } + } if (announce) { diff --git a/crawl-ref/source/monstuff.cc b/crawl-ref/source/monstuff.cc index e6f9f17a24..dc56a2b137 100644 --- a/crawl-ref/source/monstuff.cc +++ b/crawl-ref/source/monstuff.cc @@ -53,6 +53,7 @@ #include "spells2.h" #include "spells4.h" #include "stuff.h" +#include "tutorial.h" #include "view.h" #include "stash.h" @@ -323,6 +324,8 @@ static void place_monster_corpse(const monsters *monster) // Don't care if 'o' is changed, and it shouldn't be (corpses don't stack) move_item_to_grid( &o, monster->x, monster->y ); + if (you.hunger_state < HS_SATIATED) + learned_something_new(TUT_MAKE_CHUNKS); } // end place_monster_corpse() void monster_die(struct monsters *monster, char killer, int i) @@ -446,6 +449,13 @@ void monster_die(struct monsters *monster, char killer, int i) mpr("That felt strangely unrewarding."); } + // killing triggers tutorial lesson + if (Options.tutorial_events[TUT_KILLED_MONSTER]) + learned_something_new(TUT_KILLED_MONSTER); + else if (Options.tutorial_left && (you.religion == GOD_TROG || you.religion == GOD_OKAWARU || you.religion == GOD_MAKHLEB) + && !you.duration[DUR_PRAYER]) + tutorial_prayer_reminder(); + // Xom doesn't care who you killed: if (you.religion == GOD_XOM && random2(70) <= 10 + monster->hit_dice) diff --git a/crawl-ref/source/newgame.cc b/crawl-ref/source/newgame.cc index 9b207ca2c6..a43f7ec5f9 100644 --- a/crawl-ref/source/newgame.cc +++ b/crawl-ref/source/newgame.cc @@ -87,6 +87,7 @@ #include "skills2.h" #include "spl-util.h" #include "stuff.h" +#include "tutorial.h" #include "version.h" #include "view.h" @@ -117,8 +118,6 @@ static void give_random_secondary_armour( int slot ); static bool give_wanderer_weapon( int slot, int wpn_skill ); static void create_wanderer(void); static void give_items_skills(void); -static bool choose_race(void); -static bool choose_class(void); static char letter_to_species(int keyn); static char letter_to_class(int keyn); @@ -326,7 +325,8 @@ static unsigned char random_potion_description() { int desc, nature, colour; - do { + do + { desc = random2( PDQ_NQUALS * PDC_NCOLOURS ); if (coinflip()) @@ -1423,68 +1423,69 @@ static void choose_book( item_def& book, int firstbook, int numbooks ) // fire books, CONJ_II and MINOR_MAGIC_II are both ice books if ( Options.book && Options.book <= numbooks ) { - book.sub_type = firstbook + Options.book - 1; - ng_book = Options.book; - return; + book.sub_type = firstbook + Options.book - 1; + ng_book = Options.book; + return; } if ( Options.prev_book > numbooks && Options.prev_book != SBT_RANDOM ) - Options.prev_book = SBT_NO_SELECTION; + Options.prev_book = SBT_NO_SELECTION; if ( !Options.random_pick ) { - textcolor( CYAN ); - cprintf(EOL " You have a choice of books:" EOL); - textcolor( LIGHTGREY ); - - for (int i=0; i < numbooks; ++i) - { - char buf[ITEMNAME_SIZE]; - book.sub_type = firstbook + i; - item_name( book, DESC_PLAIN, buf ); - cprintf("%c - %s" EOL, 'a' + i, buf); - } - - textcolor(BROWN); - cprintf(EOL "? - Random" ); - if ( Options.prev_book != SBT_NO_SELECTION ) { + textcolor( CYAN ); + cprintf(EOL " You have a choice of books:" EOL); + textcolor( LIGHTGREY ); + + for (int i=0; i < numbooks; ++i) + { + char buf[ITEMNAME_SIZE]; + book.sub_type = firstbook + i; + item_name( book, DESC_PLAIN, buf ); + cprintf("%c - %s" EOL, 'a' + i, buf); + } + + textcolor(BROWN); + cprintf(EOL "? - Random" ); + if ( Options.prev_book != SBT_NO_SELECTION ) + { cprintf("; Enter - %s", - Options.prev_book == SBT_FIRE ? "Fire" : - Options.prev_book == SBT_COLD ? "Cold" : - Options.prev_book == SBT_SUMM ? "Summoning" : + Options.prev_book == SBT_FIRE ? "Fire" : + Options.prev_book == SBT_COLD ? "Cold" : + Options.prev_book == SBT_SUMM ? "Summoning" : Options.prev_book == SBT_RANDOM ? "Random" : - "Buggy Book"); - } - cprintf(EOL); + "Buggy Book"); + } + cprintf(EOL); - do - { - textcolor( CYAN ); - cprintf(EOL "Which book? "); - textcolor( LIGHTGREY ); - - keyin = get_ch(); - } while (keyin != '?' && - ((keyin != '\r' && keyin != '\n') || - Options.prev_book == SBT_NO_SELECTION ) && - (keyin < 'a' || keyin > ('a' + numbooks))); - - if ( keyin == '\r' || keyin == '\n' ) - { - if ( Options.prev_book == SBT_RANDOM ) - keyin = '?'; - else - keyin = ('a' + Options.prev_book - 1); - } + do + { + textcolor( CYAN ); + cprintf(EOL "Which book? "); + textcolor( LIGHTGREY ); + + keyin = get_ch(); + } while (keyin != '?' && + ((keyin != '\r' && keyin != '\n') || + Options.prev_book == SBT_NO_SELECTION ) && + (keyin < 'a' || keyin > ('a' + numbooks))); + + if ( keyin == '\r' || keyin == '\n' ) + { + if ( Options.prev_book == SBT_RANDOM ) + keyin = '?'; + else + keyin = ('a' + Options.prev_book - 1); + } } if (Options.random_pick || Options.book == SBT_RANDOM || keyin == '?') - ng_book = SBT_RANDOM; + ng_book = SBT_RANDOM; else - ng_book = keyin - 'a' + 1; + ng_book = keyin - 'a' + 1; if ( Options.random_pick || keyin == '?' ) - keyin = random2(numbooks) + 'a'; + keyin = random2(numbooks) + 'a'; book.sub_type = firstbook + keyin - 'a'; } @@ -2595,12 +2596,12 @@ static void create_wanderer( void ) // Could only have learned spells in common schools... const int school_list[5] = { SK_CONJURATIONS, - SK_ENCHANTMENTS, SK_ENCHANTMENTS, - SK_TRANSLOCATIONS, SK_NECROMANCY }; + SK_ENCHANTMENTS, SK_ENCHANTMENTS, + SK_TRANSLOCATIONS, SK_NECROMANCY }; - //jmf: Two of those spells are gone due to their munchkinicity. - // crush() and arc() are like having good melee capability. - // Therefore giving them to "harder" class makes less-than- + //jmf: Two of those spells are gone due to their munchkinicity. + // crush() and arc() are like having good melee capability. + // Therefore giving them to "harder" class makes less-than- // zero sense, and they're now gone. const int spell_list[5] = { SPELL_MAGIC_DART, @@ -2910,9 +2911,11 @@ spec_query: textcolor( WHITE ); cprintf("You must be new here!"); } + cprintf(" (Press T to enter a tutorial.)"); cprintf(EOL EOL); textcolor( CYAN ); cprintf("You can be:"); + cprintf(" (Press ? for more information)"); cprintf(EOL EOL); textcolor( LIGHTGREY ); @@ -2955,12 +2958,11 @@ spec_query: "SPACE - Choose class first; * - Random Species; " "! - Random Character" EOL - "? - Help; X - Quit" + "X - Quit" EOL); else cprintf(EOL - "? - Help * - Random; " - "Bksp - Back to class selection; X - Quit" + "* - Random; Bksp - Back to class selection; X - Quit" EOL); if (Options.prev_race) @@ -3037,6 +3039,10 @@ spec_query: ng_random = true; return false; } + else if (keyn == 'T') + { + return !pick_tutorial(); + } if (!(you.species = letter_to_species(keyn))) { @@ -3119,10 +3125,12 @@ job_query: textcolor( WHITE ); cprintf("You must be new here!"); } + cprintf(" (Press T to enter a tutorial.)"); cprintf(EOL EOL); textcolor( CYAN ); cprintf("You can be:"); + cprintf(" (Press ? for more information)"); cprintf(EOL EOL); textcolor( LIGHTGREY ); @@ -3160,12 +3168,11 @@ job_query: "SPACE - Choose species first; * - Random Class; " "! - Random Character" EOL - "? - Help; X - Quit" + "X - Quit" EOL); else cprintf(EOL - "? - Help; * - Random; " - "Bksp - Back to species selection; X - Quit" + "* - Random; Bksp - Back to species selection; X - Quit" EOL); if (Options.prev_cls) @@ -3255,6 +3262,10 @@ job_query: ng_random = true; return true; } + else if (keyn == 'T') + { + return pick_tutorial(); + } else if ((keyn == ' ' && !you.species) || keyn == 'x' || keyn == ESCAPE || keyn == CK_BKSP) { @@ -3483,7 +3494,7 @@ void give_items_skills() you.equip[EQ_BODY_ARMOUR] = 1; // extra items being tested: - choose_book( you.inv[2], BOOK_MINOR_MAGIC_I, 3 ); + choose_book( you.inv[2], BOOK_MINOR_MAGIC_I, 3 ); you.skills[SK_DODGING] = 1; you.skills[SK_STEALTH] = 1; @@ -4072,14 +4083,14 @@ void give_items_skills() you.equip[EQ_WEAPON] = 0; you.equip[EQ_BODY_ARMOUR] = 1; - if ( you.char_class == JOB_CONJURER ) - choose_book( you.inv[2], BOOK_CONJURATIONS_I, 2 ); - else - { - you.inv[2].base_type = OBJ_BOOKS; - // subtype will always be overridden - you.inv[2].plus = 0; - } + if ( you.char_class == JOB_CONJURER ) + choose_book( you.inv[2], BOOK_CONJURATIONS_I, 2 ); + else + { + you.inv[2].base_type = OBJ_BOOKS; + // subtype will always be overridden + you.inv[2].plus = 0; + } switch (you.char_class) { diff --git a/crawl-ref/source/newgame.h b/crawl-ref/source/newgame.h index 6b71e483ba..066bab2418 100644 --- a/crawl-ref/source/newgame.h +++ b/crawl-ref/source/newgame.h @@ -20,5 +20,7 @@ bool new_game(); int give_first_conjuration_book(); +bool choose_race(void); +bool choose_class(void); #endif diff --git a/crawl-ref/source/notes.cc b/crawl-ref/source/notes.cc index 94af4546e7..496b83f336 100644 --- a/crawl-ref/source/notes.cc +++ b/crawl-ref/source/notes.cc @@ -35,7 +35,8 @@ static int real_god_power( int religion, int idx ) return count; } -static bool is_noteworthy_skill_level( int level ) { +static bool is_noteworthy_skill_level( int level ) +{ unsigned i; for ( i = 0; i < Options.note_skill_levels.size(); ++i ) if ( level == Options.note_skill_levels[i] ) @@ -43,8 +44,10 @@ static bool is_noteworthy_skill_level( int level ) { return false; } -static bool is_highest_skill( int skill ) { - for ( int i = 0; i < NUM_SKILLS; ++i ) { +static bool is_highest_skill( int skill ) +{ + for ( int i = 0; i < NUM_SKILLS; ++i ) + { if ( i == skill ) continue; if ( you.skills[i] >= you.skills[skill] ) @@ -53,18 +56,21 @@ static bool is_highest_skill( int skill ) { return true; } -static bool is_noteworthy_hp( int hp, int maxhp ) { +static bool is_noteworthy_hp( int hp, int maxhp ) +{ return (hp > 0 && Options.note_hp_percent && hp <= (maxhp * Options.note_hp_percent) / 100); } -static int dungeon_branch_depth( unsigned char branch ) { +static int dungeon_branch_depth( unsigned char branch ) +{ if ( branch >= NUM_BRANCHES ) return -1; return branches[branch].depth; } -static bool is_noteworthy_dlevel( unsigned short place ) { +static bool is_noteworthy_dlevel( unsigned short place ) +{ unsigned const char branch = (unsigned char) ((place >> 8) & 0xFF); const int lev = (place & 0xFF); @@ -84,8 +90,8 @@ static bool is_noteworthy_dlevel( unsigned short place ) { This function assumes that game state has not changed since the note was taken, e.g. you.* is valid. */ -static bool is_noteworthy( const Note& note ) { - +static bool is_noteworthy( const Note& note ) +{ /* always noteworthy */ if ( note.type == NOTE_XP_LEVEL_CHANGE || note.type == NOTE_GET_GOD || @@ -143,11 +149,13 @@ static bool is_noteworthy( const Note& note ) { if ( note.type == NOTE_LEARN_SPELL && Options.note_all_spells ) return true; - for ( unsigned i = 0; i < note_list.size(); ++i ) { + for ( unsigned i = 0; i < note_list.size(); ++i ) + { if ( note_list[i].type != note.type ) continue; const Note& rnote( note_list[i] ); - switch ( note.type ) { + switch ( note.type ) + { case NOTE_DUNGEON_LEVEL_CHANGE: if ( rnote.packed_place == note.packed_place ) return false; @@ -183,7 +191,8 @@ static bool is_noteworthy( const Note& note ) { return true; } -const char* number_to_ordinal( int number ) { +const char* number_to_ordinal( int number ) +{ const char* ordinals[5] = { "first", "second", "third", "fourth", "fifth" }; if ( number < 1) @@ -193,7 +202,8 @@ const char* number_to_ordinal( int number ) { return ordinals[number-1]; } -std::string Note::describe( bool when, bool where, bool what ) const { +std::string Note::describe( bool when, bool where, bool what ) const +{ std::string result; @@ -215,7 +225,8 @@ std::string Note::describe( bool when, bool where, bool what ) const { if ( what ) { char buf[200]; - switch ( type ) { + switch ( type ) + { case NOTE_HP_CHANGE: // [ds] Shortened HP change note from "Had X hitpoints" to // accommodate the cause for the loss of hitpoints. @@ -311,13 +322,15 @@ std::string Note::describe( bool when, bool where, bool what ) const { return result; } -Note::Note() { +Note::Note() +{ turn = you.num_turns; packed_place = get_packed_place(); } Note::Note( NOTE_TYPES t, int f, int s, const char* n, const char* d ) : - type(t), first(f), second(s) { + type(t), first(f), second(s) +{ if (n) name = std::string(n); if (d) @@ -326,7 +339,8 @@ Note::Note( NOTE_TYPES t, int f, int s, const char* n, const char* d ) : packed_place = get_packed_place(); } -void Note::save( FILE* fp ) const { +void Note::save( FILE* fp ) const +{ writeLong( fp, type ); writeLong( fp, turn ); writeShort( fp, packed_place ); @@ -336,7 +350,8 @@ void Note::save( FILE* fp ) const { writeString( fp, desc ); } -void Note::load( FILE* fp ) { +void Note::load( FILE* fp ) +{ type = (NOTE_TYPES)(readLong( fp )); turn = readLong( fp ); packed_place = readShort( fp ); @@ -348,39 +363,46 @@ void Note::load( FILE* fp ) { bool notes_active = false; -bool notes_are_active() { +bool notes_are_active() +{ return notes_active; } -void take_note( const Note& note, bool force ) { +void take_note( const Note& note, bool force ) +{ if ( notes_active && (force || is_noteworthy(note)) ) note_list.push_back( note ); } -void activate_notes( bool active ) { +void activate_notes( bool active ) +{ notes_active = active; } -void save_notes( FILE* fp ) { +void save_notes( FILE* fp ) +{ writeLong( fp, NOTES_VERSION_NUMBER ); writeLong( fp, note_list.size() ); for ( unsigned i = 0; i < note_list.size(); ++i ) note_list[i].save(fp); } -void load_notes( FILE* fp ) { +void load_notes( FILE* fp ) +{ if ( readLong(fp) != NOTES_VERSION_NUMBER ) return; const long num_notes = readLong(fp); - for ( long i = 0; i < num_notes; ++i ) { + for ( long i = 0; i < num_notes; ++i ) + { Note new_note; new_note.load(fp); note_list.push_back(new_note); } } -void make_user_note() { +void make_user_note() +{ mpr("Enter note: ", MSGCH_PROMPT); char buf[400]; bool validline = !cancelable_get_line(buf, sizeof(buf)); diff --git a/crawl-ref/source/notes.h b/crawl-ref/source/notes.h index 6dfa327992..0948334b06 100644 --- a/crawl-ref/source/notes.h +++ b/crawl-ref/source/notes.h @@ -15,7 +15,8 @@ #include #include -enum NOTE_TYPES { +enum NOTE_TYPES +{ NOTE_HP_CHANGE = 0, /* needs: new hp, max hp */ NOTE_MAXHP_CHANGE, /* needs: new maxhp */ NOTE_MP_CHANGE, /* needs: new mp, max mp */ @@ -41,7 +42,8 @@ enum NOTE_TYPES { NOTE_NUM_TYPES }; -struct Note { +struct Note +{ Note(); Note( NOTE_TYPES t, int f = 0, int s = 0, const char* n = 0, const char* d = 0); diff --git a/crawl-ref/source/ouch.cc b/crawl-ref/source/ouch.cc index 85cfe0ad83..e022bb8d44 100644 --- a/crawl-ref/source/ouch.cc +++ b/crawl-ref/source/ouch.cc @@ -70,6 +70,7 @@ #include "skills2.h" #include "spells4.h" #include "stuff.h" +#include "tutorial.h" #include "view.h" @@ -871,7 +872,7 @@ void end_game( struct scorefile_entry &se ) #ifdef PACKAGE_SUFFIX PACKAGE_SUFFIX , #endif - ".st", ".kil", ".tc", ".nts", ".sav" + ".st", ".kil", ".tc", ".nts", ".tut", ".sav" }; const int num_suffixes = sizeof(suffixes) / sizeof(const char*); @@ -888,6 +889,9 @@ void end_game( struct scorefile_entry &se ) viewwindow(1, false); // don't do this for leaving/winning characters } + if (Options.tutorial_left) + tutorial_death_screen(); + if (!crawl_state.seen_hups) more(); diff --git a/crawl-ref/source/player.cc b/crawl-ref/source/player.cc index 962334975b..4cd501d463 100644 --- a/crawl-ref/source/player.cc +++ b/crawl-ref/source/player.cc @@ -54,6 +54,7 @@ #include "stuff.h" #include "transfor.h" #include "travel.h" +#include "tutorial.h" #include "view.h" /* @@ -2069,8 +2070,10 @@ int burden_change(void) { you.burden_state = BS_ENCUMBERED; - if (old_burdenstate != you.burden_state) + if (old_burdenstate != you.burden_state) { mpr("You are being weighed down by all of your possessions."); + learned_something_new(TUT_HEAVY_LOAD); + } } else { @@ -2155,6 +2158,8 @@ void gain_exp( unsigned int exp_gained ) you.exp_available += exp_gained; level_change(); + if (Options.tutorial_left && you.experience_level == 5) + tutorial_finished(); } // end gain_exp() void level_change(void) @@ -2732,9 +2737,13 @@ void level_change(void) if (you.religion == GOD_XOM) Xom_acts(true, you.experience_level, true); + + learned_something_new(TUT_NEW_LEVEL); + } redraw_skill( you.your_name, player_title() ); + } // end level_change() // here's a question for you: does the ordering of mods make a difference? @@ -4174,6 +4183,7 @@ void poison_player( int amount, bool force ) // XXX: which message channel for this message? mpr( info ); + learned_something_new(TUT_YOU_POISON); } } @@ -4219,6 +4229,7 @@ void confuse_player( int amount, bool resistable ) // XXX: which message channel for this message? mpr( info ); + learned_something_new(TUT_YOU_ENCHANTED); } } @@ -4256,6 +4267,7 @@ void slow_player( int amount ) if (you.slow > 100) you.slow = 100; + learned_something_new(TUT_YOU_ENCHANTED); } } @@ -4339,6 +4351,7 @@ void disease_player( int amount ) const int tmp = you.disease + amount; you.disease = (tmp > 210) ? 210 : tmp; + learned_something_new(TUT_YOU_SICK); } void dec_disease_player( void ) diff --git a/crawl-ref/source/religion.cc b/crawl-ref/source/religion.cc index 46574758b4..cf0a5b775a 100644 --- a/crawl-ref/source/religion.cc +++ b/crawl-ref/source/religion.cc @@ -61,6 +61,7 @@ #include "spells3.h" #include "spl-cast.h" #include "stuff.h" +#include "tutorial.h" #include "view.h" const char *sacrifice[] = { @@ -1605,6 +1606,7 @@ void gain_piety(char pgn) god_speaks(you.religion, info); } } + learned_something_new(TUT_NEW_ABILITY); } } diff --git a/crawl-ref/source/skills.cc b/crawl-ref/source/skills.cc index 3bb6a54119..24387490ad 100644 --- a/crawl-ref/source/skills.cc +++ b/crawl-ref/source/skills.cc @@ -27,6 +27,7 @@ #include "player.h" #include "skills2.h" #include "stuff.h" +#include "tutorial.h" // MAX_COST_LIMIT is the maximum XP amount it will cost to raise a skill @@ -416,6 +417,7 @@ static int exercise2( int exsk ) } mpr( info, MSGCH_INTRINSIC_GAIN ); + learned_something_new(TUT_SKILL_RAISE); // Recalculate this skill's order for tie breaking skills // at its new level. See skills2.cc::init_skill_order() diff --git a/crawl-ref/source/skills2.cc b/crawl-ref/source/skills2.cc index 810f8e6df7..0bbc35dace 100644 --- a/crawl-ref/source/skills2.cc +++ b/crawl-ref/source/skills2.cc @@ -48,7 +48,8 @@ // the character's race will be listed on the next line. Its only really // intended for cases where things might be really awkward without it. -- bwr -const char *skills[50][6] = { +const char *skills[50][6] = +{ {"Fighting", "Skirmisher", "Grunt", "Veteran", "Warrior", "Slayer"}, // 0 {"Short Blades", "Stabber", "Cutter", "Knifefighter", "Eviscerator", "Blademaster"}, {"Long Blades", "Slasher", "Slicer", "Fencer", "Swordfighter", "Swordmaster"}, @@ -123,7 +124,8 @@ const char *martial_arts_titles[6] = 3.10: but it never is, and CLASSES is probably broken now. Anyway, the Spellcasting skill (25) is actually about 130% of what is shown here. */ -const int spec_skills[ NUM_SPECIES ][40] = { +const int spec_skills[ NUM_SPECIES ][40] = +{ { // SP_HUMAN (1) 100, // SK_FIGHTING 100, // SK_SHORT_BLADES @@ -1788,7 +1790,8 @@ JOB_PALADIN: ************************************************************* */ -static const int skill_display_order[] = { +static const int skill_display_order[] = +{ SK_FIGHTING, SK_SHORT_BLADES, SK_LONG_SWORDS, SK_AXES, SK_MACES_FLAILS, SK_POLEARMS, SK_STAVES, SK_UNARMED_COMBAT, diff --git a/crawl-ref/source/stash.cc b/crawl-ref/source/stash.cc index 890d6a141c..eca4bf4da6 100644 --- a/crawl-ref/source/stash.cc +++ b/crawl-ref/source/stash.cc @@ -26,6 +26,7 @@ #include "stuff.h" #include "tags.h" #include "travel.h" +#include "tutorial.h" #include #include @@ -283,6 +284,12 @@ void Stash::update() // There's something on this square. Take a squint at it. const item_def &item = mitm[objl]; + // note item when first entering field of vision + // (only works with stashes enabled) +// learned_something_new(TUT_SEEN_FIRST_OBJECT, x, y); +// learned_something_new(TUT_SEEN_FIRST_OBJECT, item); + tutorial_first_item(item); + if (item.link == NON_ITEM) items.clear(); diff --git a/crawl-ref/source/stuff.cc b/crawl-ref/source/stuff.cc index 0c492f03ee..4aa1315c1a 100644 --- a/crawl-ref/source/stuff.cc +++ b/crawl-ref/source/stuff.cc @@ -50,7 +50,7 @@ #include "delay.h" #include "externs.h" - +#include "items.h" #include "macro.h" #include "misc.h" #include "monstuff.h" @@ -60,6 +60,7 @@ #include "output.h" #include "player.h" #include "skills2.h" +#include "tutorial.h" #include "view.h" // Crude, but functional. @@ -643,9 +644,9 @@ int yesnoquit( const char* str, bool safe, int safeanswer, bool clear_after ) tmp = (unsigned char) getch(); - if ( tmp == 27 || tmp == 'q' || tmp == 'Q' ) - return -1; - + if ( tmp == 27 || tmp == 'q' || tmp == 'Q' ) + return -1; + if ((tmp == ' ' || tmp == '\r' || tmp == '\n') && safeanswer) tmp = safeanswer; @@ -769,9 +770,9 @@ unsigned char random_colour(void) unsigned char random_uncommon_colour() { unsigned char result; - do { + do result = random_colour(); - } while ( result == LIGHTCYAN || result == CYAN || result == BROWN ); + while ( result == LIGHTCYAN || result == CYAN || result == BROWN ); return result; } @@ -1051,6 +1052,16 @@ void zap_los_monsters() continue; int imon = mgrd[gx][gy]; + + // at tutorial beginning disallow items in line of sight + if (Options.tutorial_events[TUT_SEEN_FIRST_OBJECT]) + { + int item = igrd[gx][gy]; + + if (item != NON_ITEM && is_valid_item(mitm[item]) ) + destroy_item(item); + } + if (imon == NON_MONSTER || imon == MHITYOU) continue; diff --git a/crawl-ref/source/travel.cc b/crawl-ref/source/travel.cc index a3983a4145..4fa02e4764 100644 --- a/crawl-ref/source/travel.cc +++ b/crawl-ref/source/travel.cc @@ -27,6 +27,7 @@ #include "stash.h" #include "stuff.h" #include "travel.h" +#include "tutorial.h" #include "view.h" #include @@ -832,8 +833,10 @@ static void explore_find_target_square() // No place to go? Report to the player. const int estatus = find_explore_status(tp); - if (!estatus) + if (!estatus) { mpr("Done exploring."); + learned_something_new(TUT_DONE_EXPLORE); + } else { std::vector inacc; @@ -3462,6 +3465,10 @@ void explore_discoveries::add_item(const item_def &i) } items.push_back( named_thing(item_name(i, DESC_NOCAP_A), i) ); + + // first item of this type? + // only works when travelling + tutorial_first_item(i); } void explore_discoveries::found_item(const coord_def &pos, const item_def &i) diff --git a/crawl-ref/source/tutorial.cc b/crawl-ref/source/tutorial.cc new file mode 100644 index 0000000000..c2557ef8d9 --- /dev/null +++ b/crawl-ref/source/tutorial.cc @@ -0,0 +1,1351 @@ +/* + * Created for Crawl Reference by JPEG on $Date: 2007-01-11$ + */ + +#include "AppHdr.h" +#include "tutorial.h" +#include + +#include "command.h" +#include "files.h" +#include "itemprop.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" + EOL "* - Random tutorial; X - Quit" + 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_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 += "Welcome to Dungeon Crawl!" 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 += " ? - shows the items and the commands" EOL; + result += " S - saves the game, to be resumed later (but note that death is permanent)" EOL; + result += " x - 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 += " readme.txt - A very short guide to Crawl." EOL; + result += " manual.txt - This contains all details on races, magic, skills, etc." EOL; + result += " crawl_options.txt - 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 Space to proceed to the basics (the screen division and movement)." EOL; + result += "Press Esc 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 += ""; + result += info; + result += "" EOL EOL; + + result += ""; + 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 += "" EOL 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 += ""; + result += "What you see here is the typical Crawl screen. The upper left map shows your " EOL; + result += "hero as the @ in the center. The parts of the map you remember but cannot " EOL; + result += "currently see, will be greyed out." EOL; + result += ""; + result += "[--more-- Press Escape to skip the basics]"; + + return formatted_string::parse_string(result); +} + +static formatted_string tutorial_stats_intro() +{ + std::string result; + result += ""; + + 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, "HP: %d/%d. ", 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, "Magic: %d/%d", 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, Strength, Dexterity and " EOL; + result += "Intelligence are shown and provide an " EOL; + result += "all-around account of the character's " EOL; + result += "attributes. " EOL; + + result += ""; + result += " " EOL; + result += "[--more-- Press Escape 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 += ""; + result += "This lower part of the screen is reserved for messages. Everything related to " EOL; + result += "the tutorial is shown in this colour. If you missed something, previous messages " EOL; + result += "can be shown again with Ctrl-P. " EOL; + result += ""; + result += "[--more-- Press Escape to skip the basics]"; + + return formatted_string::parse_string(result); +} + +static void tutorial_movement_info() +{ + snprintf(info, INFO_SIZE, "To move your character, use the numpad; try Numlock both on and off. If your "); + formatted_mpr(formatted_string::parse_string(info), MSGCH_TUTORIAL); + snprintf(info, INFO_SIZE, "system has no number pad, or if you are familiar with the vi keys, movement is "); + formatted_mpr(formatted_string::parse_string(info), MSGCH_TUTORIAL); + snprintf(info, INFO_SIZE, "also possible with hjklyubn. A basic command list can be found under ?, and the "); + formatted_mpr(formatted_string::parse_string(info), MSGCH_TUTORIAL); + snprintf(info, INFO_SIZE, "most important commands will be explained to you as it becomes necessary. "); + formatted_mpr(formatted_string::parse_string(info), MSGCH_TUTORIAL); +} + +// copied from display_mutations and adapted +void tut_starting_screen() +{ +#ifdef DOS_TERM + char buffer[4800]; +#endif + 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 = 19; // message window + } + else if (i==2) + { + x2 = 40; // map window + y2 = 18; + } + +#ifdef DOS_TERM + window(x1, y1, x2, y2); + gettext(x1, y1, x2, y2, buffer); +#endif + 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(); + +#ifdef DOS_TERM + puttext(x1, y1, x2, y2, buffer); +#endif + redraw_screen(); + if (ch == ESCAPE) + break; + + } + // too important to break off early + +} + +void tutorial_death_screen() +{ + Options.tutorial_left = 0; + + mpr( "Welcome to Crawl! Stuff like this happens all the time.", MSGCH_TUTORIAL, 0); + mpr( "Okay, that didn't go too well. Maybe you need to change your tactics. Here's one out of several hints:", MSGCH_TUTORIAL); + more(); + + int hint = random2(6); + switch(hint) + { + case 0: + snprintf(info2, INFO2_SIZE, "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."); + mpr(info2, MSGCH_TUTORIAL); + break; + case 1: + snprintf(info2, INFO2_SIZE, "Learn when to run away from things you can't handle - this is important! " + "It is often wise to skip a dangerous level. But don't overdo this as monsters will only get harder " + "the deeper you get."); + mpr(info2, MSGCH_TUTORIAL); + break; + case 2: +// 1 5 10 5 20 5 30 5 40 5 50 5 60 5 70 5 80 + snprintf(info, INFO_SIZE, "Rest between encounters. In Crawl, searching and resting are one and the same."); + formatted_mpr(formatted_string::parse_string(info), MSGCH_TUTORIAL); + snprintf(info, INFO_SIZE, "To search for one turn, press s, ., delete or keypad-5. Pressing 5 or"); + formatted_mpr(formatted_string::parse_string(info), MSGCH_TUTORIAL); + snprintf(info, INFO_SIZE, "shift-and-keypad-5 will let you rest for a longer time (you will stop resting"); + formatted_mpr(formatted_string::parse_string(info), MSGCH_TUTORIAL); + snprintf(info, INFO_SIZE, "when fully healed)."); + formatted_mpr(formatted_string::parse_string(info), MSGCH_TUTORIAL); + break; + case 3: + snprintf(info2, INFO2_SIZE, "Always consider using projectiles before engaging monsters in close combat."); + mpr(info2, MSGCH_TUTORIAL); + break; // check Options.tut_throw_counter + case 4: + snprintf(info2, INFO2_SIZE, "If you are badly wounded, you can run away from monsters to buy some time. " + "Try losing them in corridors or find a place where you can run around in circles to heal while the " + "monster chases you."); + mpr(info2, MSGCH_TUTORIAL); + break; + case 5: + snprintf(info2, INFO2_SIZE, "Never fight more than one monster if you can help it. Always back into a " + "corridor so that they must fight you one on one."); + mpr(info2, MSGCH_TUTORIAL); + break; + default: + snprintf(info2, INFO2_SIZE, "Sorry, no hint this time, although there should have been one."); + mpr(info2, MSGCH_TUTORIAL); + } + more(); + if (Options.tutorial_type == TUT_MAGIC_CHAR && Options.tut_spell_counter < 10 ) + mpr( "As a Conjurer your main weapon should be offensive magic. Cast spells more often!", MSGCH_TUTORIAL); + else if (Options.tutorial_type == TUT_BERSERK_CHAR && Options.tut_berserk_counter < 1 ) + mpr( "Also remember to use Berserk when fighting stronger foes.", MSGCH_TUTORIAL); + else if (Options.tutorial_type == TUT_RANGER_CHAR && Options.tut_throw_counter < 10 ) + mpr( "Also remember to use your bow and arrows against distant monsters.", MSGCH_TUTORIAL); + + mpr( "See you next game!", MSGCH_TUTORIAL); + + for ( long i = 0; i < TUT_EVENTS_NUM; ++i ) + Options.tutorial_events[i] = 0; +} + +void tutorial_finished() +{ + Options.tutorial_left = 0; + snprintf(info, INFO_SIZE, "That's it for now: For the time being you've learned enough, so we'll let you "); + mpr(info, MSGCH_TUTORIAL); + snprintf(info, INFO_SIZE, "find out the rest for yourself. If you haven't already done so you might try a "); + mpr(info, MSGCH_TUTORIAL); + snprintf(info, INFO_SIZE, "different tutorial sometime to gain an insight into other aspects of Crawl. "); + mpr(info, MSGCH_TUTORIAL); + snprintf(info, INFO_SIZE, "Note that from now on the help screen (?) will look a bit different. Let us say"); + formatted_mpr(formatted_string::parse_string(info), MSGCH_TUTORIAL); + snprintf(info, INFO_SIZE, "goodbye with a playing hint:"); + mpr(info, MSGCH_TUTORIAL); + more(); + + if (Options.tut_explored) + { + snprintf(info, INFO_SIZE, "Walking around and exploring levels gets easier by using auto-explore (Ctrl-O)."); + formatted_mpr(formatted_string::parse_string(info), MSGCH_TUTORIAL); + } + else if (Options.tut_travel) + { +// 1 5 10 5 20 5 30 5 40 5 50 5 60 5 70 5 80 + snprintf(info, INFO_SIZE, "There is a convenient way for travelling between far away dungeon levels: press"); + mpr(info, MSGCH_TUTORIAL); + snprintf(info, INFO_SIZE, "Ctrl-G and enter the desired destination. If your travel gets interrupted,"); + formatted_mpr(formatted_string::parse_string(info), MSGCH_TUTORIAL); + snprintf(info, INFO_SIZE, "issueing Ctrl-G Enter will continue it."); + formatted_mpr(formatted_string::parse_string(info), MSGCH_TUTORIAL); + } + else if (Options.tut_stashes) + { + snprintf(info, INFO_SIZE, "To keep an account on the great many items scattered throughout the dungeon,"); + mpr(info, MSGCH_TUTORIAL); + snprintf(info, INFO_SIZE, "use the Ctrl-F command. This lets you find anything that matches the word you"); + formatted_mpr(formatted_string::parse_string(info), MSGCH_TUTORIAL); + snprintf(info, INFO_SIZE, "entered. For example, Ctrl-F \"knife\" will list all places where knives have"); + formatted_mpr(formatted_string::parse_string(info), MSGCH_TUTORIAL); + snprintf(info, INFO_SIZE, "been seen. Also, by pressing the letter of one the spots, you will be able to"); + mpr(info, MSGCH_TUTORIAL); + snprintf(info, INFO_SIZE, "travel there automatically. It is even possible to search for dungeon features"); + mpr(info, MSGCH_TUTORIAL); + snprintf(info, INFO_SIZE, "like \"shop\", \"altar\", or for more general terms like \"blade\". The latter"); + formatted_mpr(formatted_string::parse_string(info), MSGCH_TUTORIAL); + snprintf(info, INFO_SIZE, "will search for all weapons falling into either the Short Blades or the Long"); + mpr(info, MSGCH_TUTORIAL); + snprintf(info, INFO_SIZE, "Blades classes."); + mpr(info, MSGCH_TUTORIAL); + snprintf(info, INFO_SIZE, "For those familiar with regular expressions: these can be used here as well."); + mpr(info, MSGCH_TUTORIAL); + } + else + { + switch (random2(2)+3) + { + case 3: + snprintf(info, INFO_SIZE, "The game will keep an automated logbook for your characters. Use ?: to read it."); + formatted_mpr(formatted_string::parse_string(info), MSGCH_TUTORIAL); + snprintf(info, INFO_SIZE, "You can also enter notes manually with the : command. Once your character"); + formatted_mpr(formatted_string::parse_string(info), MSGCH_TUTORIAL); + snprintf(info, INFO_SIZE, "perishes, two morgue files will be left (look in the /morgue directory). The"); + formatted_mpr(formatted_string::parse_string(info), MSGCH_TUTORIAL); + snprintf(info, INFO_SIZE, "one ending in txt contains a copy of your logbook."); + mpr(info, MSGCH_TUTORIAL); + break; + case 4: + snprintf(info, INFO_SIZE, "Crawl has a macro function built in: press ~m to define a macro by first"); + formatted_mpr(formatted_string::parse_string(info), MSGCH_TUTORIAL); + snprintf(info, INFO_SIZE, "specifying a trigger key (say, F) and a command sequence. So, for example"); + formatted_mpr(formatted_string::parse_string(info), MSGCH_TUTORIAL); + snprintf(info, INFO_SIZE, "~mFZa+. will make the F key always zap the spell in slot a on the nearest"); + formatted_mpr(formatted_string::parse_string(info), MSGCH_TUTORIAL); + snprintf(info, INFO_SIZE, "monster. Do not overwrite crucial commands! The functions keys F1-F12 are"); + mpr(info, MSGCH_TUTORIAL); + snprintf(info, INFO_SIZE, "generally safe. The ~ also lets you define keybindings. These are useful when"); + formatted_mpr(formatted_string::parse_string(info), MSGCH_TUTORIAL); + snprintf(info, INFO_SIZE, "some keys do not work as expected on your system. In addition, you can define"); + mpr(info, MSGCH_TUTORIAL); + snprintf(info, INFO_SIZE, "macro-type sequences for the level map or targeting modes only."); + mpr(info, MSGCH_TUTORIAL); + break; + /* + case 5: + snprintf(info2, INFO2_SIZE, "You can use the { command to inscribe some words on one of your items. " + "Besides general item-related notes, there are some further functions for this: inscribing @w1 on a weapon " + "lets you wield it with w1, regardless of the inventory letter (1 can be replaced by any digit and w " + "could be any action key or *, meaning all actions). Inscribing !d will ask you before dropping the item."); + break; + case 6: + snprintf(info2, INFO2_SIZE, "The interface can be greatly customised. All options are explained in the file " + "crawl_options.txt which should be in the /docs directory. The options are set in init.txt or white .crawlrc. " + "Crawl will complain when it can't find either.\n" + "Options of the yes/no (or true/false) type appear the opposite of their default, e.g. \"#show_turns = false\". " + "Just removing the comment sign will change the behaviour of such options. Other options require numbers/words as " + "arguments - read the doc file for these."); + break; + */ + default: + snprintf(info2, INFO2_SIZE, "Oops... No hint for now. Better luck next time!"); + } + } + mpr("Have a crunchy Crawling!", MSGCH_TUTORIAL, 0); + more(); + + Options.tutorial_left = 0; + for ( long i = 0; i < TUT_EVENTS_NUM; ++i ) + Options.tutorial_events[i] = 0; +} + +void tutorial_prayer_reminder() +{ + if (coinflip()) + {// always would be too annoying + snprintf(info, INFO_SIZE, "Remember to pray before battle, so as to dedicate your kills to %s. ", god_name(you.religion)); + formatted_mpr(formatted_string::parse_string(info), MSGCH_TUTORIAL); + snprintf(info, INFO_SIZE, "Should the monster leave a corpse, consider Dissecting it as a sacrifice to"); + formatted_mpr(formatted_string::parse_string(info), MSGCH_TUTORIAL); + snprintf(info, INFO_SIZE, "%s, as well.", god_name(you.religion)); + mpr(info, MSGCH_TUTORIAL); + } +} + +void tutorial_healing_reminder() +{ + if (you.poison) + { + if (Options.tutorial_events[TUT_NEED_POISON_HEALING]) + return; + 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.poison) + { + snprintf(info, INFO_SIZE, "Remember to rest between fights and to enter unexplored terrain with full"); + mpr(info, MSGCH_TUTORIAL); + snprintf(info, INFO_SIZE, "hitpoints and/or magic. To do this, press 5."); + formatted_mpr(formatted_string::parse_string(info), MSGCH_TUTORIAL); + } + Options.tut_last_healed = you.num_turns; + } +} + +void taken_new_item(unsigned char item_type) +{ +/* + if (Options.tutorial_events[TUT_SEEN_FIRST_OBJECT]) + learned_something_new(TUT_SEEN_FIRST_OBJECT); +*/ + 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 help = "That "; + formatted_string st = formatted_string::parse_string(help); + st.formatted_string::textcolor(channel_to_colour(MSGCH_TUTORIAL)); + st.formatted_string::add_glyph(&mon); + help = " is a monster, usually depicted by letters, e.g. typical early monsters "; + st += formatted_string::parse_string(help); + formatted_mpr(st, MSGCH_TUTORIAL); + + learned_something_new(TUT_SEEN_MONSTER); +} + +void tutorial_first_item(item_def item) +{ + if (!Options.tutorial_events[TUT_SEEN_FIRST_OBJECT] || Options.tut_just_triggered) + return; + + std::string help = "That "; + formatted_string st = formatted_string::parse_string(help); + st.formatted_string::textcolor(channel_to_colour(MSGCH_TUTORIAL)); + st.formatted_string::add_glyph(&item); + help = " is an item. If you move to this spot and press g or , you can pick it up."; + st += formatted_string::parse_string(help); + formatted_mpr(st, MSGCH_TUTORIAL); + + learned_something_new(TUT_SEEN_FIRST_OBJECT); +} + +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 && seen_what != TUT_SEEN_MONSTER) + return; + + // If seeing something right at game start wait for input +/* if (!you.num_turns && !you.turn_is_over && getch() == 0) + getch(); +*/ + switch(seen_what) + { + case TUT_SEEN_FIRST_OBJECT: +// 1 5 10 5 20 5 30 5 40 5 50 5 60 5 70 5 80 + snprintf(info, INFO_SIZE, "Generally, items are shown by non-letter symbols like %%?!\"=()[. Once picked up,"); + formatted_mpr(formatted_string::parse_string(info), MSGCH_TUTORIAL); + snprintf(info, INFO_SIZE, "you can drop it again with d. Depending on the current options, several types of"); + formatted_mpr(formatted_string::parse_string(info), MSGCH_TUTORIAL); + snprintf(info, INFO_SIZE, "objects will be picked up automatically."); + formatted_mpr(formatted_string::parse_string(info), MSGCH_TUTORIAL); + break; + case TUT_SEEN_POTION: + snprintf(info, INFO_SIZE, "You have picked up your first potion ('!'). Use q to quaff (drink) it."); + formatted_mpr(formatted_string::parse_string(info), MSGCH_TUTORIAL); + break; + case TUT_SEEN_SCROLL: + snprintf(info, INFO_SIZE, "You have picked up your first scroll ('?'). Type r to read it."); + formatted_mpr(formatted_string::parse_string(info), MSGCH_TUTORIAL); + break; + case TUT_SEEN_WAND: + snprintf(info, INFO_SIZE, "You have picked up your first wand ('/'). Type z to zap it."); + formatted_mpr(formatted_string::parse_string(info), MSGCH_TUTORIAL); + break; + case TUT_SEEN_SPBOOK: + snprintf(info, INFO_SIZE, "You have picked up a spellbook ('+' or ':'). You can read it by typing r,"); + formatted_mpr(formatted_string::parse_string(info), MSGCH_TUTORIAL); + snprintf(info, INFO_SIZE, "memorise spells via M and cast a memorised spell with Z."); + formatted_mpr(formatted_string::parse_string(info), MSGCH_TUTORIAL); + if (you.skills[SK_SPELLCASTING]) + { + snprintf(info, INFO_SIZE, "However, for now you will be unable to do so. First your mind has to become"); + mpr(info, MSGCH_TUTORIAL); + snprintf(info, INFO_SIZE, "accustomed to using magic by reading lots of scrolls."); + mpr(info, MSGCH_TUTORIAL); + } + break; + case TUT_SEEN_WEAPON: + snprintf(info, INFO_SIZE, "This is the first weapon ('(') you've picked up. Use w to wield it, but be"); + formatted_mpr(formatted_string::parse_string(info), MSGCH_TUTORIAL); + snprintf(info, INFO_SIZE, "aware that this weapon might use a different skill from the one you've been"); + formatted_mpr(formatted_string::parse_string(info), MSGCH_TUTORIAL); + snprintf(info, INFO_SIZE, "training with your current one. You can use v (followed by the weapon's letter)"); + formatted_mpr(formatted_string::parse_string(info), MSGCH_TUTORIAL); + snprintf(info, INFO_SIZE, "to find out about that and other extras the weapon might have. To take a look at"); + formatted_mpr(formatted_string::parse_string(info), MSGCH_TUTORIAL); + snprintf(info, INFO_SIZE, "your skills, press m."); + formatted_mpr(formatted_string::parse_string(info), MSGCH_TUTORIAL); + break; + case TUT_SEEN_MISSILES: + snprintf(info, INFO_SIZE, "This is the first stack of missiles (')') you've picked up. Darts can be thrown"); + formatted_mpr(formatted_string::parse_string(info), MSGCH_TUTORIAL); + snprintf(info, INFO_SIZE, "by hand, but other missile types like arrows and needles require a launcher (and"); + formatted_mpr(formatted_string::parse_string(info), MSGCH_TUTORIAL); + snprintf(info, INFO_SIZE, "training in using it) to be really effective. v gives more information about"); + formatted_mpr(formatted_string::parse_string(info), MSGCH_TUTORIAL); + snprintf(info, INFO_SIZE, "both missiles and launcher."); + formatted_mpr(formatted_string::parse_string(info), MSGCH_TUTORIAL); + if (Options.tutorial_type == TUT_RANGER_CHAR) + { + snprintf(info, INFO_SIZE, "As 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) + { + snprintf(info2, INFO2_SIZE, "However, as a spellslinger you don't really need another type of ranged attack, unless there's another effect in addition to damage."); + } + else + { + snprintf(info2, INFO2_SIZE, "For now you might be best off with sticking to darts or stones for ranged attacks."); + } + mpr(info2, MSGCH_TUTORIAL, 1); + break; + case TUT_SEEN_ARMOUR: + snprintf(info, INFO_SIZE, "This is the first piece of armour ('[') you've picked up. Use W to wear it and"); + formatted_mpr(formatted_string::parse_string(info), MSGCH_TUTORIAL); + snprintf(info, INFO_SIZE, "T to take it off again. You can also use v (followed by the armour's letter)"); + formatted_mpr(formatted_string::parse_string(info), MSGCH_TUTORIAL); + snprintf(info, INFO_SIZE, "to get some information about it."); + formatted_mpr(formatted_string::parse_string(info), MSGCH_TUTORIAL); + if (you.species == SP_CENTAUR) + { + snprintf( info, INFO_SIZE, "Note that as a %s, you will be unable to wear boots.", species_name(you.species, 1) ); + mpr(info, MSGCH_TUTORIAL); + } + else if (you.species == SP_MINOTAUR) + { + snprintf( info, INFO_SIZE, "Note that as a %s, you will be unable to wear helmets.", species_name(you.species, 1) ); + mpr(info, MSGCH_TUTORIAL); + } + break; + case TUT_SEEN_RANDART: + snprintf(info, INFO_SIZE, "Weapons and armour that have descriptions like this are more likely to be of higher" + "enchantment or have special properties, good or bad."); + mpr(info, MSGCH_TUTORIAL); + break; + case TUT_SEEN_FOOD: + snprintf(info, INFO_SIZE, "You have picked up some food ('%%'). You can eat it by typing e."); + formatted_mpr(formatted_string::parse_string(info), MSGCH_TUTORIAL); + break; + case TUT_SEEN_CARRION: + snprintf(info, INFO_SIZE, "You have picked up a corpse ('%%'). You can dissect it with D - it needs to be"); + formatted_mpr(formatted_string::parse_string(info), MSGCH_TUTORIAL); + snprintf(info, INFO_SIZE, "on the ground for that to work, though - and eat the resulting chunks (though"); + formatted_mpr(formatted_string::parse_string(info), MSGCH_TUTORIAL); + snprintf(info, INFO_SIZE, "they may not be healthy). During prayer you can offer corpses to your god by dissecting them, as well."); + mpr(info, MSGCH_TUTORIAL); + break; + case TUT_SEEN_JEWELLERY: + snprintf(info, INFO_SIZE, "You have picked up a a piece of jewellery, either a ring ('=') or an amulet"); + formatted_mpr(formatted_string::parse_string(info), MSGCH_TUTORIAL); + snprintf(info, INFO_SIZE, "an amulet ('\"'). Type P to put it on and R to remove it. You can also use v"); + formatted_mpr(formatted_string::parse_string(info), MSGCH_TUTORIAL); + snprintf(info, INFO_SIZE, "(followed by the appropriate letter) to get some information about it."); + formatted_mpr(formatted_string::parse_string(info), MSGCH_TUTORIAL); + break; + case TUT_SEEN_MISC: + snprintf(info, INFO_SIZE, "This is a strange object. You can play around with it to find out what it does"); + formatted_mpr(formatted_string::parse_string(info), MSGCH_TUTORIAL); + snprintf(info, INFO_SIZE, "by wielding and Evoking it."); + formatted_mpr(formatted_string::parse_string(info), MSGCH_TUTORIAL); + break; + case TUT_SEEN_MONSTER: +// snprintf(info, INFO_SIZE, "The %s is a monster, usually depicted by letters, e.g. typical early monsters are", st.c_str()); + snprintf(info, INFO_SIZE, "are r, g, b or K. You can gain some information about it by pressing x, moving"); + formatted_mpr(formatted_string::parse_string(info), MSGCH_TUTORIAL); + snprintf(info, INFO_SIZE, "the cursor on the monster and then pressing v. To attack it with your current"); + formatted_mpr(formatted_string::parse_string(info), MSGCH_TUTORIAL); + snprintf(info, INFO_SIZE, "weapon, simply move into it."); + mpr(info, MSGCH_TUTORIAL); + + if (Options.tutorial_type == TUT_RANGER_CHAR) + { + snprintf(info, INFO_SIZE, "However, as a hunter you might want to deal with it using your bow. Do this"); + mpr(info, MSGCH_TUTORIAL); + snprintf(info, INFO_SIZE, "with the following keypresses: wbf+. Here, wb wields the bow, f fires"); + formatted_mpr(formatted_string::parse_string(info), MSGCH_TUTORIAL); + snprintf(info, INFO_SIZE, "appropriate ammunition (your arrows) and enters targeting mode. A very simple"); + mpr(info, MSGCH_TUTORIAL); + snprintf(info, INFO_SIZE, "way to aim is by pressing + and - until you target the proper monster. Finally,"); + formatted_mpr(formatted_string::parse_string(info), MSGCH_TUTORIAL); + snprintf(info, INFO_SIZE, ". or Enter will fire. If you miss, ff will fire at the previous target."); + formatted_mpr(formatted_string::parse_string(info), MSGCH_TUTORIAL); + } + else if (Options.tutorial_type == TUT_MAGIC_CHAR) + { + snprintf(info, INFO_SIZE, "However, as a conjurer you might want to deal with it using magic. Do this"); + mpr(info, MSGCH_TUTORIAL); + snprintf(info, INFO_SIZE, "with the following key presses: Za+. Here, Za zaps the first spell you know,"); + formatted_mpr(formatted_string::parse_string(info), MSGCH_TUTORIAL); + snprintf(info, INFO_SIZE, "%s, and enters targeting mode. A very simple way to aim is by ", spell_title(get_spell_by_letter('a'))); + formatted_mpr(formatted_string::parse_string(info), MSGCH_TUTORIAL); + snprintf(info, INFO_SIZE, "pressing + and - until you target the proper monster. Finally, fire by pressing"); + formatted_mpr(formatted_string::parse_string(info), MSGCH_TUTORIAL); + snprintf(info, INFO_SIZE, ". or Enter. If you miss, Zap will fire at the previous target."); + formatted_mpr(formatted_string::parse_string(info), MSGCH_TUTORIAL); + } + break; + case TUT_SEEN_STAIRS: + snprintf(info, INFO_SIZE, "These are some downstairs. You can enter the next (deeper) level by following"); + mpr(info, MSGCH_TUTORIAL); + snprintf(info, INFO_SIZE, "them down (>). To get back to this level again, press << while standing on the"); + formatted_mpr(formatted_string::parse_string(info), MSGCH_TUTORIAL); + snprintf(info, INFO_SIZE, "upstairs."); + mpr(info, MSGCH_TUTORIAL); + break; + case TUT_SEEN_TRAPS: + snprintf(info, INFO_SIZE, "Oops... you just entered a trap. An unwary adventurer will occasionally stumble"); + mpr(info, MSGCH_TUTORIAL); + snprintf(info, INFO_SIZE, "into one of these nasty constructions depicted by ^. They can do physical"); + formatted_mpr(formatted_string::parse_string(info), MSGCH_TUTORIAL); + snprintf(info, INFO_SIZE, "damage (with darts or needles, for example) or have other, more magical " + "effects, e.g. teleportation."); + mpr(info, MSGCH_TUTORIAL); + break; + case TUT_SEEN_ALTAR: + snprintf(info, INFO_SIZE, "The _ is an altar. You can get information about it or join this god by pressing"); + formatted_mpr(formatted_string::parse_string(info), MSGCH_TUTORIAL); + snprintf(info, INFO_SIZE, "p while standing on the square. Don't worry: You'll be asked for confirmation!"); + formatted_mpr(formatted_string::parse_string(info), MSGCH_TUTORIAL); + break; + case TUT_SEEN_SHOP: + snprintf(info, INFO_SIZE, "The %c is a shop. You can enter it same as you would a new level, by typing <<.", get_screen_glyph(x,y)); + formatted_mpr(formatted_string::parse_string(info), MSGCH_TUTORIAL); + break; + case TUT_SEEN_DOOR: + snprintf(info, INFO_SIZE, "The %c is a closed door. You can open it by walking into it.", get_screen_glyph(x,y)); + formatted_mpr(formatted_string::parse_string(info), MSGCH_TUTORIAL); + snprintf(info, INFO_SIZE, "Sometimes it is useful to close a door. Do so by pressing c, followed by the direction."); + formatted_mpr(formatted_string::parse_string(info), MSGCH_TUTORIAL); + break; + case TUT_KILLED_MONSTER: + snprintf(info, INFO_SIZE, "Congratulations, your character just gained some experience by killing this " + "monster! By taking actions, certain skills will be trained and become better."); + mpr(info, MSGCH_TUTORIAL); + if (/*Options.tutorial_type == TUT_MELEE_CHAR ||*/ Options.tutorial_type == TUT_BERSERK_CHAR) + snprintf(info, INFO_SIZE, "For example, killing monsters in melee battle will raise your Axes and Fighting skills."); + else if (Options.tutorial_type == TUT_RANGER_CHAR) + snprintf(info, INFO_SIZE, "For example, killing monsters using bow and arrows will raise your Bows and Ranged Combat skills."); + else //if (Options.tutorial_type == TUT_MAGIC_CHAR) + snprintf(info, INFO_SIZE, "For example, killing monsters with offensive magic will raise your Conjurations" "and Spellcasting skills."); + mpr(info, MSGCH_TUTORIAL); + + if (you.religion != GOD_NO_GOD) + { + snprintf(info, INFO_SIZE, "To dedicate your kills to %s, pray (p) before battle. ", god_name(you.religion)); + formatted_mpr(formatted_string::parse_string(info), MSGCH_TUTORIAL); + snprintf(info, INFO_SIZE, "Not all gods will be equally pleased at doing this, nor will they accept every kill."); + mpr(info, MSGCH_TUTORIAL); + } + break; + case TUT_NEW_LEVEL: + snprintf(info2, INFO2_SIZE, "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)."); + mpr(info2, MSGCH_TUTORIAL); + if (Options.tutorial_type == TUT_MAGIC_CHAR) + { + snprintf(info, INFO_SIZE, "New experience levels will let you learn more spells (the Spellcasting skill"); + mpr(info, MSGCH_TUTORIAL); + snprintf(info, INFO_SIZE, "also does this). For now, you should try to memorise the second spell of your"); + mpr(info, MSGCH_TUTORIAL); + snprintf(info, INFO_SIZE, "starting book with M, the letter of the book, and b. Once learnt, you can zap"); + formatted_mpr(formatted_string::parse_string(info), MSGCH_TUTORIAL); + snprintf(info, INFO_SIZE, "it with Zb."); + formatted_mpr(formatted_string::parse_string(info), MSGCH_TUTORIAL); + } + break; + case TUT_SKILL_RAISE: + snprintf(info, INFO_SIZE, "One of your skills just got raised. Type m to take a look at your skills screen."); + formatted_mpr(formatted_string::parse_string(info), MSGCH_TUTORIAL); + break; + case TUT_YOU_ENCHANTED: + snprintf(info, INFO_SIZE, "Enchantments of all types can befall you temporarily. Abbreviated signalisation"); + mpr(info, MSGCH_TUTORIAL); + snprintf(info, INFO_SIZE, "appears at the lower end of the stats area. Press @ for more details. A list of"); + formatted_mpr(formatted_string::parse_string(info), MSGCH_TUTORIAL); + snprintf(info, INFO_SIZE, "all possible enchantments is given in the manual."); + mpr(info, MSGCH_TUTORIAL); + break; + case TUT_YOU_SICK: + learned_something_new(TUT_YOU_ENCHANTED); + snprintf(info, INFO_SIZE, "Sometimes corpses become spoiled or inedible, resulting in sickness. Also, some"); + mpr(info, MSGCH_TUTORIAL); + snprintf(info, INFO_SIZE, "monsters' flesh is less palatable than others'. While sick, your hitpoints will"); + mpr(info, MSGCH_TUTORIAL); + snprintf(info, INFO_SIZE, "not regenerate, and sickness often takes a toll on your body. It wears off with"); + mpr(info, MSGCH_TUTORIAL); + snprintf(info, INFO_SIZE, "time (wait with 5) or, if you've got the means, you could heal yourself."); + formatted_mpr(formatted_string::parse_string(info), MSGCH_TUTORIAL); + break; + case TUT_YOU_POISON: + learned_something_new(TUT_YOU_ENCHANTED); + snprintf(info, INFO_SIZE, "Poison will slowly reduce your hp. It wears off with time (wait with 5) or, if"); + formatted_mpr(formatted_string::parse_string(info), MSGCH_TUTORIAL); + snprintf(info, INFO_SIZE, "you've got the means, you could heal yourself."); + mpr(info, MSGCH_TUTORIAL); + break; + case TUT_YOU_CURSED: + snprintf(info2, INFO2_SIZE, "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 to remove the curse."); + mpr(info2, MSGCH_TUTORIAL); + break; + case TUT_YOU_HUNGRY: + snprintf(info, INFO_SIZE, "There are two ways to overcome hunger: food rations you started with or found,"); + mpr(info, MSGCH_TUTORIAL); + snprintf(info, INFO_SIZE, "and selfmade food from corpses. To get the latter, all you need to do is chop"); + mpr(info, MSGCH_TUTORIAL); + snprintf(info, INFO_SIZE, "up a corpse, that is, Dissect it while standing on the corpse in question. You"); + formatted_mpr(formatted_string::parse_string(info), MSGCH_TUTORIAL); + snprintf(info, INFO_SIZE, "need to wield a sharp weapon to do that. Your starting weapon will do nicely."); + mpr(info, MSGCH_TUTORIAL); + break; + case TUT_YOU_STARVING: + snprintf(info, INFO_SIZE, "You are now suffering from terrible hunger. You'll need to eat something"); + formatted_mpr(formatted_string::parse_string(info), MSGCH_TUTORIAL); + snprintf(info, INFO_SIZE, "quickly, or you'll starve. The safest way to deal with this is to simply eat something from"); + mpr(info, MSGCH_TUTORIAL); + snprintf(info, INFO_SIZE, "your inventory rather than wait for a monster to leave a corpse."); + mpr(info, MSGCH_TUTORIAL); + break; + case TUT_MULTI_PICKUP: + snprintf(info, INFO_SIZE, "There's a more comfortable way to pick up several items at the same time. If"); + mpr(info, MSGCH_TUTORIAL); + snprintf(info, INFO_SIZE, "you press , or g twice you can choose items from a menu. This takes"); + formatted_mpr(formatted_string::parse_string(info), MSGCH_TUTORIAL); + snprintf(info, INFO_SIZE, "less keystrokes but has no influence on the number of turns needed. Multi-pickup will be"); + mpr(info, MSGCH_TUTORIAL); + snprintf(info, INFO_SIZE, "interrupted by a monster attacking you or similar potentially dangerous events."); + mpr(info, MSGCH_TUTORIAL); + break; + case TUT_HEAVY_LOAD: + if (you.burden_state != BS_UNENCUMBERED) + snprintf(info, INFO_SIZE, "It is not usually a good idea to run around encumbered; it slows you down and increases your hunger."); + else + snprintf(info, INFO_SIZE, "Sadly, your inventory is limited to 52 items, and it appears your knapsack is full."); + mpr(info, MSGCH_TUTORIAL); + + snprintf(info, INFO_SIZE, "However, this is easy enough to rectify: simply drop some of the stuff you"); + formatted_mpr(formatted_string::parse_string(info), MSGCH_TUTORIAL); + snprintf(info, INFO_SIZE, "don't need or that's too heavy to lug around permanently."); + mpr(info, MSGCH_TUTORIAL); + break; + case TUT_ROTTEN_FOOD: + snprintf(info, INFO_SIZE, "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 drop them now."); + mpr(info, MSGCH_TUTORIAL); + if (you.religion == GOD_TROG || you.religion == GOD_MAKHLEB || you.religion == GOD_OKAWARU) + { + snprintf(info, INFO_SIZE, "Also, if it is a rotting corpse you carry now might be a good time to drop"); + formatted_mpr(formatted_string::parse_string(info), MSGCH_TUTORIAL); + snprintf(info, INFO_SIZE, "and Dissect it during prayer (p) as an offer to %s.", god_name(you.religion)); + formatted_mpr(formatted_string::parse_string(info), MSGCH_TUTORIAL); + } + break; + case TUT_MAKE_CHUNKS: + snprintf(info, INFO_SIZE, "How lucky! You just killed a monster which left a corpse. Standing over the"); + mpr(info, MSGCH_TUTORIAL); + snprintf(info, INFO_SIZE, "corpse, you can press D to dissect it. One or more chunks will appear, which"); + formatted_mpr(formatted_string::parse_string(info), MSGCH_TUTORIAL); + snprintf(info, INFO_SIZE, "can be eaten with e."); + formatted_mpr(formatted_string::parse_string(info), MSGCH_TUTORIAL); + snprintf(info, INFO_SIZE, "Be warned that not all chunks are edible, some never, others only sometimes. Only experience will " + "help you, here. But remember that you are what you eat."); + mpr(info, MSGCH_TUTORIAL); + + if (you.duration[DUR_PRAYER] && + (you.religion == GOD_OKAWARU || you.religion == GOD_MAKHLEB || you.religion == GOD_TROG || you.religion == GOD_ELYVILON)) + { + snprintf(info, INFO_SIZE, "Note that doing this while praying will instead sacrifice it to %s, ", god_name(you.religion)); + mpr(info, MSGCH_TUTORIAL); + snprintf(info, INFO_SIZE, "which a god may or may not like. (Type ^ to get a hint about that.)"); + formatted_mpr(formatted_string::parse_string(info), MSGCH_TUTORIAL); + } + break; + case TUT_SHIFT_RUN: + snprintf(info, INFO_SIZE, "Walking around takes less keystrokes if you press Shift while moving. That will"); + formatted_mpr(formatted_string::parse_string(info), MSGCH_TUTORIAL); + snprintf(info, INFO_SIZE, "let you run into the specified direction until a monster comes into sight or your character " + "sees something interesting. Shift-running does not reduce the number of turns needed to move."); + mpr(info, MSGCH_TUTORIAL); + break; + case TUT_MAP_VIEW: + snprintf(info, INFO_SIZE, "As you explore more and more of a level, orientation becomes rather difficult."); + mpr(info, MSGCH_TUTORIAL); + snprintf(info, INFO_SIZE, "Here the map overview comes in handy: Press X to see a much larger portion of"); + formatted_mpr(formatted_string::parse_string(info), MSGCH_TUTORIAL); + snprintf(info, INFO_SIZE, "the map. You can easily return to the main screen with a number of keys. If you"); + mpr(info, MSGCH_TUTORIAL); + snprintf(info, INFO_SIZE, "move your cursor while on the level map and then press . your character will"); + formatted_mpr(formatted_string::parse_string(info), MSGCH_TUTORIAL); + snprintf(info, INFO_SIZE, "attempt to move there on his own. Travel will be interrupted by monsters"); + mpr(info, MSGCH_TUTORIAL); + snprintf(info, INFO_SIZE, "stepping into sight, but can be resumed by pressing X. once more. Also, << and >"); + formatted_mpr(formatted_string::parse_string(info), MSGCH_TUTORIAL); + snprintf(info, INFO_SIZE, "can be used as shortcuts for cycling through the known staircases."); + mpr(info, MSGCH_TUTORIAL); + break; +/* + case TUT_AUTOEXPLORE: + snprintf(info2, INFO2_SIZE, "Exploration of a level gets even easier by another shortcut command: Ctrl-O will automatically move towards the " + "nearest unexplored part of the map, but stop whenever you find something interesting or a monster moves into sight."); + break; +*/ + case TUT_DONE_EXPLORE: + snprintf(info, INFO_SIZE, "You have explored the dungeon on this level. Search for some downstairs, which"); + mpr(info, MSGCH_TUTORIAL); + snprintf(info, INFO_SIZE, "look like this: '>'. In order to descend, press > when standing on such a"); + formatted_mpr(formatted_string::parse_string(info), MSGCH_TUTORIAL); + snprintf(info, INFO_SIZE, "staircase."); + mpr(info, MSGCH_TUTORIAL); + if (Options.tutorial_events[TUT_SEEN_STAIRS]) + { + snprintf(info, INFO_SIZE, "In rare cases, you may have found no downstairs at all. Try searching for"); + mpr(info, MSGCH_TUTORIAL); + snprintf(info, INFO_SIZE, "secret doors in suspicious looking spots; use s, . or 5 to do so."); + formatted_mpr(formatted_string::parse_string(info), MSGCH_TUTORIAL); + } + else + { + snprintf(info, INFO_SIZE, "Each level of Crawl has at least three up and three down stairs. If you've"); + mpr(info, MSGCH_TUTORIAL); + snprintf(info, INFO_SIZE, "encountered less than that, you've explored only part of this level. The rest"); + mpr(info, MSGCH_TUTORIAL); + snprintf(info, INFO_SIZE, "may be accessible via another level or through secret doors. To find the"); + mpr(info, MSGCH_TUTORIAL); + snprintf(info, INFO_SIZE, "latter, try searching the walls with s, . (search for one turn) or 5 (search"); + formatted_mpr(formatted_string::parse_string(info), MSGCH_TUTORIAL); + snprintf(info, INFO_SIZE, "longer)."); + mpr(info, MSGCH_TUTORIAL); + } + break; + case TUT_NEED_HEALING: + snprintf(info, INFO_SIZE, "If you're low on hitpoints or magic and there's no urgent need to move, you can"); + mpr(info, MSGCH_TUTORIAL); + snprintf(info, INFO_SIZE, "rest for a bit. Press 5 or shift-numpad-5 to do so."); + formatted_mpr(formatted_string::parse_string(info), MSGCH_TUTORIAL); + break; + case TUT_NEED_POISON_HEALING: +// 1 5 10 5 20 5 30 5 40 5 50 5 60 5 70 5 80 + snprintf(info, INFO_SIZE, "Your poisoning could easily kill you, so now would be a good time to quaff a"); + formatted_mpr(formatted_string::parse_string(info), MSGCH_TUTORIAL); + snprintf(info, INFO_SIZE, "potion of heal wounds, or better yet, a potion of healing. If you haven't seen"); + mpr(info, MSGCH_TUTORIAL); + snprintf(info, INFO_SIZE, "one of these so far, try unknown ones in your inventory. Good luck!"); + mpr(info, MSGCH_TUTORIAL); + break; + case TUT_POSTBERSERK: + snprintf(info, INFO_SIZE, "Berserking is extremely exhausting! It burns a lot of nutrition, and afterwards"); + mpr(info, MSGCH_TUTORIAL); + snprintf(info, INFO_SIZE, "you are slowed down and occasionally even pass out."); + mpr(info, MSGCH_TUTORIAL); + break; + case TUT_RUN_AWAY: + snprintf(info2, INFO2_SIZE, "Sometimes, when you've got only few hitpoints left and you're in danger of dying, retreat might be " + "the best option. %sTry to shake off any monster following you by leading them through corridors, or once there's some space between you " + "and your pursuer, change levels.", (you.species == SP_CENTAUR ? "As a four-legged centaur you are particularly well equipped " + "for doing so. " : "") ); + mpr(info2, MSGCH_TUTORIAL); + if (Options.tutorial_type == TUT_BERSERK_CHAR && !you.berserker) + { + snprintf(info, INFO_SIZE, "Also, with %s's support you can use your Berserk ability (a) to ", god_name(you.religion)); + formatted_mpr(formatted_string::parse_string(info), MSGCH_TUTORIAL); + snprintf(info, INFO_SIZE, "temporarily gain more hitpoints and greater strength. Be warned that going into"); + mpr(info, MSGCH_TUTORIAL); + snprintf(info, INFO_SIZE, "a frenzy like that also has its drawbacks."); + mpr(info, MSGCH_TUTORIAL); + } + break; + case TUT_YOU_MUTATED: + snprintf(info2, INFO_SIZE, "Mutations can be obtained from several sources, among them potions of " + "mutations, spell miscasts, and overuse of strong enchantments like Haste or " + "Invisibility. The only reliable way to get rid of mutations is with potions of " + "cure mutation. Almost all mutations occur in three degrees. There are about as " + "many harmful as beneficial mutations."); + mpr(info2, MSGCH_TUTORIAL); + snprintf(info, INFO_SIZE, "To have a look at your mutations press A."); + formatted_mpr(formatted_string::parse_string(info), MSGCH_TUTORIAL); + break; + case TUT_NEW_ABILITY: + snprintf(info, INFO_SIZE, "You just gained a new ability. Press a to take a look at your abilities or use"); + formatted_mpr(formatted_string::parse_string(info), MSGCH_TUTORIAL); + snprintf(info, INFO_SIZE, "one of them."); + mpr(info, MSGCH_TUTORIAL); + break; + case TUT_WIELD_WEAPON: + snprintf(info, INFO_SIZE, "You might want to wield a more suitable implement when attacking monsters."); + formatted_mpr(formatted_string::parse_string(info), MSGCH_TUTORIAL); + if (item_cursed( you.inv[ you.equip[EQ_WEAPON] ] )) + { + snprintf(info, INFO_SIZE, "To get rid of the curse you need to read a scroll of remove curse or enchant weapon."); + mpr(info, MSGCH_TUTORIAL); + } + else if (Options.tutorial_type == TUT_RANGER_CHAR && you.inv[ you.equip[EQ_WEAPON] ].sub_type == WPN_BOW) + { +// 1 5 10 5 20 5 30 5 40 5 50 5 60 5 70 5 80 + snprintf(info, INFO_SIZE, "You can easily switch between your primary (a) and secondary (b) weapon by "); + mpr(info, MSGCH_TUTORIAL); + snprintf(info, INFO_SIZE, "pressing '."); + formatted_mpr(formatted_string::parse_string(info), MSGCH_TUTORIAL); + } + break; + default: +// 1 5 10 5 20 5 30 5 40 5 50 5 60 5 70 5 80 + snprintf(info, INFO_SIZE, "You've found something new (but I don't know what)!"); + mpr(info, MSGCH_TUTORIAL); + } + more(); + + Options.tut_just_triggered = true; + + Options.tutorial_events[seen_what] = 0; + Options.tutorial_left--; + + // there are so many triggers out there, chances that all will be called are small +/* if (Options.tutorial_left < 10) + tutorial_finished(); */ +} diff --git a/crawl-ref/source/tutorial.h b/crawl-ref/source/tutorial.h new file mode 100644 index 0000000000..7c2029f07c --- /dev/null +++ b/crawl-ref/source/tutorial.h @@ -0,0 +1,100 @@ +/* + * File: tutorial.h + * Summary: Stuff needed for tutorial + * Written by: JPEG + * + * Created on 2007-01-11. + */ + +#ifndef TUTORIAL_H +#define TUTORIAL_H + +// for formatted_string +#include "menu.h" + +#include +#include +#include +#include + +void save_tutorial( FILE* fp ); +void load_tutorial( FILE* fp ); +void init_tutorial_options(void); +bool pick_tutorial(void); +void print_tutorial_menu(unsigned int type); +unsigned int get_tutorial_species(unsigned int type); +unsigned int get_tutorial_job(unsigned int type); +//formatted_string tut_starting_info(unsigned int width); +formatted_string tut_starting_info2(); +void tut_starting_screen(); +void tutorial_death_screen(void); +void tutorial_finished(void); +void tutorial_prayer_reminder(void); +void tutorial_healing_reminder(void); +void taken_new_item(unsigned char item_type); +void tutorial_first_monster(const monsters mon); +void tutorial_first_item(const item_def item); +void learned_something_new(unsigned int seen_what, int x=0, int y=0); +//void learned_something_new(unsigned int seen_what/*, formatted_string st = formatted_string::parse_string("") */); +//void tutorial_output_commands(const std::string &str, unsigned int colour); + +enum tutorial_event +{ + TUT_SEEN_FIRST_OBJECT, // 0 + /* seen certain items */ + TUT_SEEN_POTION, + TUT_SEEN_SCROLL, + TUT_SEEN_WAND, + TUT_SEEN_SPBOOK, + TUT_SEEN_JEWELLERY, // 5 + TUT_SEEN_MISC, + TUT_SEEN_WEAPON, + TUT_SEEN_MISSILES, + TUT_SEEN_ARMOUR, + TUT_SEEN_RANDART, // 10 + TUT_SEEN_FOOD, + TUT_SEEN_CARRION, + TUT_SEEN_STAIRS, + TUT_SEEN_TRAPS, + TUT_SEEN_ALTAR, // 15 + TUT_SEEN_SHOP, // not tested so far + TUT_SEEN_DOOR, + /* other 'first events */ + TUT_SEEN_MONSTER, + TUT_KILLED_MONSTER, + TUT_NEW_LEVEL, // 20 + TUT_SKILL_RAISE, + TUT_YOU_ENCHANTED, + TUT_YOU_SICK, + TUT_YOU_POISON, + TUT_YOU_CURSED, // 25 + TUT_YOU_HUNGRY, + TUT_YOU_STARVING, + TUT_MULTI_PICKUP, + TUT_HEAVY_LOAD, + TUT_ROTTEN_FOOD, // 30 + TUT_NEED_HEALING, + TUT_NEED_POISON_HEALING, + TUT_RUN_AWAY, + TUT_MAKE_CHUNKS, + TUT_POSTBERSERK, // 35 + TUT_SHIFT_RUN, + TUT_MAP_VIEW, +// TUT_AUTOEXPLORE, + TUT_DONE_EXPLORE, + TUT_YOU_MUTATED, // 39 + TUT_NEW_ABILITY, + TUT_WIELD_WEAPON, + TUT_EVENTS_NUM // 42 +}; // for numbers higher than 45 change size of tutorial_events in externs.h + +enum tutorial_types +{ +// TUT_MELEE_CHAR, // 0 + TUT_BERSERK_CHAR, + TUT_MAGIC_CHAR, + TUT_RANGER_CHAR, + TUT_TYPES_NUM // 4 +}; + +#endif diff --git a/crawl-ref/source/view.cc b/crawl-ref/source/view.cc index afc0c56bb1..fef73784fc 100644 --- a/crawl-ref/source/view.cc +++ b/crawl-ref/source/view.cc @@ -53,6 +53,7 @@ #include "spells4.h" #include "stash.h" #include "travel.h" +#include "tutorial.h" // These are hidden from the rest of the world... use the functions // below to get information about the map grid. @@ -1016,7 +1017,8 @@ bool get_bit_in_long_array( const unsigned long* data, int where ) return ((data[wordloc] & (1UL << bitloc)) != 0); } -static void set_bit_in_long_array( unsigned long* data, int where ) { +static void set_bit_in_long_array( unsigned long* data, int where ) +{ int wordloc = where / LONGSIZE; int bitloc = where % LONGSIZE; data[wordloc] |= (1UL << bitloc); @@ -1053,13 +1055,16 @@ static int find_next_intercept(double* accx, double* accy, const double slope) const double distdiff = (xdistance * slope - ydistance); // exact corner - if ( double_is_zero( distdiff ) ) { + if ( double_is_zero( distdiff ) ) + { // move somewhat away from the corner - if ( slope > 1.0 ) { + if ( slope > 1.0 ) + { *accx = xtarget + EPSILON_VALUE * 2; *accy = ytarget + EPSILON_VALUE * 2 * slope; } - else { + else + { *accx = xtarget + EPSILON_VALUE * 2 / slope; *accy = ytarget + EPSILON_VALUE * 2; } @@ -1068,11 +1073,13 @@ static int find_next_intercept(double* accx, double* accy, const double slope) double traveldist; int rc = -1; - if ( distdiff > 0.0 ) { + if ( distdiff > 0.0 ) + { traveldist = ydistance / slope; rc = 1; } - else { + else + { traveldist = xdistance; rc = 0; } @@ -1087,7 +1094,8 @@ static int find_next_intercept(double* accx, double* accy, const double slope) void ray_def::advance_and_bounce() { // 0 = down-right, 1 = down-left, 2 = up-left, 3 = up-right - int bouncequad[4][3] = { + int bouncequad[4][3] = + { { 1, 3, 2 }, { 0, 2, 3 }, { 3, 1, 0 }, { 2, 0, 1 } }; int oldx = x(), oldy = y(); @@ -1425,15 +1433,17 @@ void raycast() // of x/y: in that case, every step on the X axis means an increase // of 1 in the Y axis at the intercept point. We can assume gcd(x,y)=1, // so we look at steps of 1/y. - for ( xangle = 1; xangle <= LOS_MAX_RANGE; ++xangle ) { - for ( yangle = 1; yangle <= LOS_MAX_RANGE; ++yangle ) { - + for ( xangle = 1; xangle <= LOS_MAX_RANGE; ++xangle ) + { + for ( yangle = 1; yangle <= LOS_MAX_RANGE; ++yangle ) + { if ( gcd(xangle, yangle) != 1 ) continue; const double slope = ((double)(yangle)) / xangle; const double rslope = ((double)(xangle)) / yangle; - for ( int intercept = 0; intercept <= yangle; ++intercept ) { + for ( int intercept = 0; intercept <= yangle; ++intercept ) + { double xstart = ((double)(intercept)) / yangle; if ( intercept == 0 ) xstart += EPSILON_VALUE / 10.0; @@ -1544,12 +1554,14 @@ bool find_ray( int sourcex, int sourcey, int targetx, int targety, } } } - if ( allow_fallback ) { + if ( allow_fallback ) + { ray.accx = sourcex + 0.5; ray.accy = sourcey + 0.5; if ( targetx == sourcex ) ray.slope = 10000.0; - else { + else + { ray.slope = targety - sourcey; ray.slope /= targetx - sourcex; if ( ray.slope < 0 ) @@ -1612,7 +1624,8 @@ void losight(FixedArray < unsigned int, 19, 19 > &sh, const unsigned int num_cellrays = compressed_ray_x.size(); const unsigned int num_words = (num_cellrays + LONGSIZE - 1) / LONGSIZE; - for ( int quadrant = 0; quadrant < 4; ++quadrant ) { + for ( int quadrant = 0; quadrant < 4; ++quadrant ) + { const int xmult = quadrant_x[quadrant]; const int ymult = quadrant_y[quadrant]; @@ -1621,9 +1634,11 @@ void losight(FixedArray < unsigned int, 19, 19 > &sh, // kill all blocked rays const unsigned long* inptr = los_blockrays; - for ( int xdiff = 0; xdiff <= LOS_MAX_RANGE_X; ++xdiff ) { + for ( int xdiff = 0; xdiff <= LOS_MAX_RANGE_X; ++xdiff ) + { for (int ydiff = 0; ydiff <= LOS_MAX_RANGE_Y; - ++ydiff, inptr += num_words ) { + ++ydiff, inptr += num_words ) + { const int realx = x_p + xdiff * xmult; const int realy = y_p + ydiff * ymult; @@ -1632,7 +1647,8 @@ void losight(FixedArray < unsigned int, 19, 19 > &sh, continue; // if this cell is opaque... - if ( grid_is_opaque(gr[realx][realy])) { + if ( grid_is_opaque(gr[realx][realy])) + { // then block the appropriate rays for ( unsigned int i = 0; i < num_words; ++i ) dead_rays[i] |= inptr[i]; @@ -1643,12 +1659,15 @@ void losight(FixedArray < unsigned int, 19, 19 > &sh, // ray calculation done, now work out which cells in this // quadrant are visible unsigned int rayidx = 0; - for ( unsigned int wordloc = 0; wordloc < num_words; ++wordloc ) { + for ( unsigned int wordloc = 0; wordloc < num_words; ++wordloc ) + { const unsigned long curword = dead_rays[wordloc]; // Note: the last word may be incomplete - for ( unsigned int bitloc = 0; bitloc < LONGSIZE; ++bitloc) { + for ( unsigned int bitloc = 0; bitloc < LONGSIZE; ++bitloc) + { // make the cells seen by this ray at this point visible - if ( ((curword >> bitloc) & 1UL) == 0 ) { + if ( ((curword >> bitloc) & 1UL) == 0 ) + { // this ray is alive! const int realx = xmult * compressed_ray_x[rayidx]; const int realy = ymult * compressed_ray_y[rayidx]; @@ -1705,7 +1724,8 @@ void draw_border(void) // 3. '^' for traps // 4. '_' for altars // 5. Anything else will look for the exact same character in the level map. -bool is_feature(int feature, int x, int y) { +bool is_feature(int feature, int x, int y) +{ unsigned char envfeat = (unsigned char) env.map[x - 1][y - 1]; if (!envfeat) return false; @@ -1714,7 +1734,8 @@ bool is_feature(int feature, int x, int y) { // warnings about out-of-range case values. short grid = grd[x][y]; - switch (feature) { + switch (feature) + { case 'X': return (travel_point_distance[x][y] == PD_EXCLUDED); case 'F': @@ -1723,7 +1744,8 @@ bool is_feature(int feature, int x, int y) { case 'I': return is_stash(x, y); case '_': - switch (grid) { + switch (grid) + { case DNGN_ALTAR_ZIN: case DNGN_ALTAR_SHINING_ONE: case DNGN_ALTAR_KIKUBAAQUDGHA: @@ -1743,7 +1765,8 @@ bool is_feature(int feature, int x, int y) { } case '\t': case '\\': - switch (grid) { + switch (grid) + { case DNGN_ENTER_HELL: case DNGN_ENTER_LABYRINTH: case DNGN_ENTER_SHOP: @@ -1764,7 +1787,8 @@ bool is_feature(int feature, int x, int y) { return false; } case '<': - switch (grid) { + switch (grid) + { case DNGN_ROCK_STAIRS_UP: case DNGN_STONE_STAIRS_UP_I: case DNGN_STONE_STAIRS_UP_II: @@ -1786,7 +1810,8 @@ bool is_feature(int feature, int x, int y) { return false; } case '>': - switch (grid) { + switch (grid) + { case DNGN_ROCK_STAIRS_DOWN: case DNGN_STONE_STAIRS_DOWN_I: case DNGN_STONE_STAIRS_DOWN_II: @@ -1808,7 +1833,8 @@ bool is_feature(int feature, int x, int y) { return false; } case '^': - switch (grid) { + switch (grid) + { case DNGN_TRAP_MECHANICAL: case DNGN_TRAP_MAGICAL: case DNGN_TRAP_III: @@ -1823,7 +1849,8 @@ bool is_feature(int feature, int x, int y) { static int find_feature(unsigned char feature, int curs_x, int curs_y, int start_x, int start_y, int anchor_x, int anchor_y, - int ignore_count, char *move_x, char *move_y) { + int ignore_count, char *move_x, char *move_y) +{ int cx = anchor_x, cy = anchor_y; @@ -1832,30 +1859,38 @@ static int find_feature(unsigned char feature, int curs_x, int curs_y, // Find the first occurrence of feature 'feature', spiralling around (x,y) int maxradius = GXM > GYM? GXM : GYM; - for (int radius = 1; radius < maxradius; ++radius) { - for (int axis = -2; axis < 2; ++axis) { + for (int radius = 1; radius < maxradius; ++radius) + { + for (int axis = -2; axis < 2; ++axis) + { int rad = radius - (axis < 0); - for (int var = -rad; var <= rad; ++var) { + for (int var = -rad; var <= rad; ++var) + { int dx = radius, dy = var; if (axis % 2) dx = -dx; - if (axis < 0) { + if (axis < 0) + { int temp = dx; dx = dy; dy = temp; } int x = cx + dx, y = cy + dy; - if (x < 0 || y < 0 || x >= GXM || y >= GYM) continue; - if (is_feature(feature, x + 1, y + 1)) { + if (x < 0 || y < 0 || x >= GXM || y >= GYM) + continue; + if (is_feature(feature, x + 1, y + 1)) + { ++matchcount; - if (!ignore_count--) { + if (!ignore_count--) + { // We want to cursor to (x,y) *move_x = x - (start_x + curs_x - 1); *move_y = y - (start_y + curs_y - 1); return matchcount; } - else if (firstx == -1) { + else if (firstx == -1) + { firstx = x; firsty = y; } @@ -1865,7 +1900,8 @@ static int find_feature(unsigned char feature, int curs_x, int curs_y, } // We found something, but ignored it because of an ignorecount - if (firstx != -1) { + if (firstx != -1) + { *move_x = firstx - (start_x + curs_x - 1); *move_y = firsty - (start_y + curs_y - 1); return 1; @@ -1874,8 +1910,10 @@ static int find_feature(unsigned char feature, int curs_x, int curs_y, } void find_features(const std::vector& features, - unsigned char feature, std::vector *found) { - for (unsigned feat = 0; feat < features.size(); ++feat) { + unsigned char feature, std::vector *found) +{ + for (unsigned feat = 0; feat < features.size(); ++feat) + { const coord_def& coord = features[feat]; if (is_feature(feature, coord.x, coord.y)) found->push_back(coord); @@ -1887,22 +1925,27 @@ static int find_feature( const std::vector& features, int start_x, int start_y, int ignore_count, char *move_x, char *move_y, - bool forward) { + bool forward) +{ int firstx = -1, firsty = -1, firstmatch = -1; int matchcount = 0; - for (unsigned feat = 0; feat < features.size(); ++feat) { + for (unsigned feat = 0; feat < features.size(); ++feat) + { const coord_def& coord = features[feat]; - if (is_feature(feature, coord.x, coord.y)) { + if (is_feature(feature, coord.x, coord.y)) + { ++matchcount; - if (forward? !ignore_count-- : --ignore_count == 1) { + if (forward? !ignore_count-- : --ignore_count == 1) + { // We want to cursor to (x,y) *move_x = coord.x - (start_x + curs_x); *move_y = coord.y - (start_y + curs_y); return matchcount; } - else if (!forward || firstx == -1) { + else if (!forward || firstx == -1) + { firstx = coord.x; firsty = coord.y; firstmatch = matchcount; @@ -1911,7 +1954,8 @@ static int find_feature( const std::vector& features, } // We found something, but ignored it because of an ignorecount - if (firstx != -1) { + if (firstx != -1) + { *move_x = firstx - (start_x + curs_x); *move_y = firsty - (start_y + curs_y); return firstmatch; @@ -2295,11 +2339,13 @@ void show_map( FixedVector &spec_place, bool travel_mode ) move_x = 0; move_y = 0; - if (anchor_x == -1) { + if (anchor_x == -1) + { anchor_x = start_x + curs_x - 1; anchor_y = start_y + curs_y - 1; } - if (search_feat != getty) { + if (search_feat != getty) + { search_feat = getty; search_found = 0; } @@ -2325,13 +2371,15 @@ void show_map( FixedVector &spec_place, bool travel_mode ) int x = start_x + curs_x, y = start_y + curs_y; if (travel_mode && x == you.x_pos && y == you.y_pos) { - if (you.travel_x > 0 && you.travel_y > 0) { + if (you.travel_x > 0 && you.travel_y > 0) + { move_x = you.travel_x - x; move_y = you.travel_y - y; } break; } - else { + else + { spec_place[0] = x; spec_place[1] = y; map_alive = false; @@ -3168,7 +3216,7 @@ void init_feature_table( void ) } } -static int get_screen_glyph( int x, int y ) +int get_screen_glyph( int x, int y ) { const int ex = x - you.x_pos + 9; const int ey = y - you.y_pos + 9; @@ -3357,6 +3405,18 @@ void viewwindow(bool draw_it, bool do_updates) const int gx = count_x + you.x_pos - 16; const int gy = count_y + you.y_pos - 8; + if (Options.tutorial_left && in_bounds(gx, gy)) + { + if (is_feature('>',gx,gy)) + learned_something_new(TUT_SEEN_STAIRS); + else if (is_feature('_',gx,gy)) + learned_something_new(TUT_SEEN_ALTAR); + else if (grd[gx][gy] == DNGN_CLOSED_DOOR) + learned_something_new(TUT_SEEN_DOOR,gx,gy); + else if (grd[gx][gy] == DNGN_ENTER_SHOP) + learned_something_new(TUT_SEEN_SHOP,gx,gy); + } + // order is important here if (!map_bounds( gx, gy )) { diff --git a/crawl-ref/source/view.h b/crawl-ref/source/view.h index bd7e63ecf0..9b9f0f34c3 100644 --- a/crawl-ref/source/view.h +++ b/crawl-ref/source/view.h @@ -116,6 +116,7 @@ void get_item_glyph(const item_def *item, unsigned short *glych, unsigned short *glycol); void get_mons_glyph(const monsters *mons, unsigned short *glych, unsigned short *glycol); +int get_screen_glyph( int x, int y ); void set_envmap_char( int x, int y, unsigned char chr ); unsigned get_envmap_char(int x, int y); -- cgit v1.2.3-54-g00ecf