summaryrefslogtreecommitdiffstats
path: root/crawl-ref/source/main.cc
diff options
context:
space:
mode:
Diffstat (limited to 'crawl-ref/source/main.cc')
-rw-r--r--crawl-ref/source/main.cc4454
1 files changed, 4454 insertions, 0 deletions
diff --git a/crawl-ref/source/main.cc b/crawl-ref/source/main.cc
new file mode 100644
index 0000000000..2b32824db3
--- /dev/null
+++ b/crawl-ref/source/main.cc
@@ -0,0 +1,4454 @@
+/*
+ * File: main.cc
+ * Summary: Main entry point, event loop, and some initialization functions
+ * Written by: Linley Henzell
+ */
+
+#include "AppHdr.h"
+
+#include <string>
+#include <algorithm>
+
+// I don't seem to need values.h for VACPP..
+#if !defined(__IBMCPP__)
+#include <limits.h>
+#endif
+
+#ifdef DEBUG
+ // this contains the DBL_MAX constant
+ #include <float.h>
+#endif
+
+#include <errno.h>
+#include <time.h>
+#include <stdlib.h>
+#include <string.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <sstream>
+#include <iostream>
+
+#ifdef USE_UNIX_SIGNALS
+#include <signal.h>
+#endif
+
+#include "externs.h"
+#include "options.h"
+#include "species.h"
+
+#include "abl-show.h"
+#include "abyss.h"
+#include "artefact.h"
+#include "arena.h"
+#include "branch.h"
+#include "chardump.h"
+#include "cio.h"
+#include "cloud.h"
+#include "clua.h"
+#include "command.h"
+#include "ctest.h"
+#include "crash.h"
+#include "database.h"
+#include "dbg-maps.h"
+#include "dbg-scan.h"
+#include "debug.h"
+#include "delay.h"
+#include "describe.h"
+#include "dlua.h"
+#include "directn.h"
+#include "dungeon.h"
+#include "effects.h"
+#include "map_knowledge.h"
+#include "fprop.h"
+#include "fight.h"
+#include "files.h"
+#include "food.h"
+#include "godabil.h"
+#include "hiscores.h"
+#include "initfile.h"
+#include "invent.h"
+#include "item_use.h"
+#include "it_use3.h"
+#include "itemname.h"
+#include "itemprop.h"
+#include "items.h"
+#include "lev-pand.h"
+#include "los.h"
+#include "luaterp.h"
+#include "macro.h"
+#include "makeitem.h"
+#include "mapmark.h"
+#include "maps.h"
+#include "message.h"
+#include "misc.h"
+#include "mon-act.h"
+#include "mon-cast.h"
+#include "mon-iter.h"
+#include "mon-place.h"
+#include "mon-stuff.h"
+#include "mon-util.h"
+#include "mutation.h"
+#include "newgame.h"
+#include "ng-init.h"
+#include "notes.h"
+#include "ouch.h"
+#include "output.h"
+#include "overmap.h"
+#include "player.h"
+#include "quiver.h"
+#include "random.h"
+#include "religion.h"
+#include "shopping.h"
+#include "skills.h"
+#include "skills2.h"
+#include "spells1.h"
+#include "spells2.h"
+#include "spells3.h"
+#include "spells4.h"
+#include "spl-book.h"
+#include "spl-cast.h"
+#include "spl-util.h"
+#include "stash.h"
+#include "state.h"
+#include "stuff.h"
+#include "tags.h"
+#include "terrain.h"
+#include "transfor.h"
+#include "traps.h"
+#include "travel.h"
+#include "tutorial.h"
+#include "view.h"
+#include "shout.h"
+#include "viewchar.h"
+#include "viewgeom.h"
+#include "stash.h"
+#include "wiz-dgn.h"
+#include "wiz-fsim.h"
+#include "wiz-item.h"
+#include "wiz-mon.h"
+#include "wiz-you.h"
+#include "xom.h"
+
+#ifdef USE_TILE
+#include "tiles.h"
+#include "tiledef-dngn.h"
+#endif
+
+// ----------------------------------------------------------------------
+// Globals whose construction/destruction order needs to be managed
+// ----------------------------------------------------------------------
+
+CLua clua(true);
+CLua dlua(false); // Lua interpreter for the dungeon builder.
+crawl_environment env; // Requires dlua.
+player you;
+system_environment SysEnv;
+game_state crawl_state;
+
+
+
+std::string init_file_error; // externed in newgame.cc
+
+char info[ INFO_SIZE ]; // messaging queue extern'd everywhere {dlb}
+
+int stealth; // externed in view.cc
+
+void world_reacts();
+
+static key_recorder repeat_again_rec;
+
+// Clockwise, around the compass from north (same order as enum RUN_DIR)
+const struct coord_def Compass[8] =
+{
+ coord_def(0, -1), coord_def(1, -1), coord_def(1, 0), coord_def(1, 1),
+ coord_def(0, 1), coord_def(-1, 1), coord_def(-1, 0), coord_def(-1, -1),
+};
+
+// Functions in main module
+static void _do_berserk_no_combat_penalty(void);
+static bool _initialise(void);
+static void _input(void);
+static void _move_player(int move_x, int move_y);
+static void _move_player(coord_def move);
+static int _check_adjacent(dungeon_feature_type feat, coord_def& delta);
+static void _open_door(coord_def move, bool check_confused = true);
+static void _open_door(int x, int y, bool check_confused = true)
+{
+ _open_door(coord_def(x, y), check_confused);
+}
+static void _close_door(coord_def move);
+static void _start_running( int dir, int mode );
+
+static void _prep_input();
+static command_type _get_next_cmd();
+static keycode_type _get_next_keycode();
+static command_type _keycode_to_command( keycode_type key );
+static void _setup_cmd_repeat();
+static void _do_prev_cmd_again();
+static void _update_replay_state();
+
+static void _show_commandline_options_help();
+static void _wanderer_startup_message();
+static void _god_greeting_message( bool game_start );
+static void _take_starting_note();
+static void _startup_tutorial();
+
+#ifdef DGL_SIMPLE_MESSAGING
+static void _read_messages();
+#endif
+
+static void _compile_time_asserts();
+
+//
+// It all starts here. Some initialisations are run first, then straight
+// to new_game and then input.
+//
+
+#ifdef USE_TILE
+#include <SDL_main.h>
+#endif
+
+int main( int argc, char *argv[] )
+{
+ _compile_time_asserts(); // Just to quiet "unused static function" warning.
+
+ init_crash_handler();
+
+ // Hardcoded initial keybindings.
+ init_keybindings();
+
+ // Load in the system environment variables
+ get_system_environment();
+
+ // Parse command line args -- look only for initfile & crawl_dir entries.
+ if (!parse_args(argc, argv, true))
+ {
+ _show_commandline_options_help();
+ return 1;
+ }
+
+ // Init monsters up front - needed to handle the mon_glyph option right.
+ init_monsters();
+
+ // Init name cache so that we can parse stash_filter by item name.
+ init_properties();
+ init_item_name_cache();
+
+ // Read the init file.
+ init_file_error = read_init_file();
+
+ // Now parse the args again, looking for everything else.
+ parse_args( argc, argv, false );
+
+ if (Options.sc_entries != 0 || !SysEnv.scorefile.empty())
+ {
+ hiscores_print_all( Options.sc_entries, Options.sc_format );
+ return 0;
+ }
+ else
+ {
+ // Don't allow scorefile override for actual gameplay, only for
+ // score listings.
+ SysEnv.scorefile.clear();
+ }
+
+#ifdef USE_TILE
+ if (!tiles.initialise())
+ return -1;
+#endif
+
+ const bool game_start = _initialise();
+
+ // Override some options for tutorial.
+ init_tutorial_options();
+
+ msg::stream << "Welcome" << (game_start? "" : " back") << ", "
+ << you.your_name << " the "
+ << species_name( you.species,you.experience_level )
+ << " " << you.class_name << "."
+ << std::endl;
+
+ // Activate markers only after the welcome message, so the
+ // player can see any resulting messages.
+ env.markers.activate_all();
+
+ if (game_start && you.char_class == JOB_WANDERER)
+ _wanderer_startup_message();
+
+ _god_greeting_message( game_start );
+
+ // Warn player about their weapon, if unsuitable.
+ wield_warning(false);
+
+ _prep_input();
+
+ if (game_start)
+ {
+ if (Options.tutorial_left)
+ _startup_tutorial();
+ _take_starting_note();
+ }
+ else
+ tutorial_load_game();
+
+ // Catch up on any experience levels we did not assign last time. This
+ // can happen if Crawl sees SIGHUP while it is waiting for the player
+ // to dismiss a level-up prompt.
+ level_change();
+
+ while (true)
+ _input();
+
+ clear_globals_on_exit();
+
+ return 0;
+}
+
+static void _show_commandline_options_help()
+{
+ puts("Command line options:");
+ puts(" -help prints this list of options");
+ puts(" -name <string> character name");
+ puts(" -species <arg> preselect race (by letter, abbreviation, or name)");
+ puts(" -job <arg> preselect class (by letter, abbreviation, or name)");
+ puts(" -plain don't use IBM extended characters");
+ puts(" -dir <path> crawl directory");
+ puts(" -rc <file> init file name");
+ puts(" -rcdir <dir> directory that contains (included) rc files");
+ puts(" -morgue <dir> directory to save character dumps");
+ puts(" -macro <dir> directory to save/find macro.txt");
+ puts(" -version Crawl version (and compilation info)");
+ puts("");
+ puts("Command line options override init file options, which override");
+ puts("environment options (CRAWL_NAME, CRAWL_DIR, CRAWL_RC).");
+ puts("");
+
+ puts(" -extra-opt-first optname=optval");
+ puts(" -extra-opt-last optname=optval");
+ puts("");
+ puts("Acts as if 'optname=optval' was at the top or bottom of the init");
+ puts("file. Can be used multiple times.");
+ puts("");
+
+ puts("Highscore list options: (Can be redirected to more, etc.)");
+ puts(" -scores [N] highscore list");
+ puts(" -tscores [N] terse highscore list");
+ puts(" -vscores [N] verbose highscore list");
+ puts(" -scorefile <filename> scorefile to report on");
+ puts("");
+ puts("Arena options: (Stage a tournament between various monsters.)");
+ puts(" -arena \"<monster list> v <monster list> arena:<arena map>\"");
+#if DEBUG_DIAGNOSTICS
+ puts("");
+ puts(" -test run test cases in ./test");
+#endif
+}
+
+static void _wanderer_startup_message()
+{
+ int skill_levels = 0;
+ for (int i = 0; i < NUM_SKILLS; ++i)
+ skill_levels += you.skills[ i ];
+
+ if (skill_levels <= 2)
+ {
+ // Some wanderers stand to not be able to see any of their
+ // skills at the start of the game (one or two skills should be
+ // easily guessed from starting equipment). Anyway, we'll give
+ // the player a message to warn them (and a reason why). - bwr
+ mpr("You wake up in a daze, and can't recall much.");
+ }
+}
+
+static void _god_greeting_message(bool game_start)
+{
+ switch (you.religion)
+ {
+ case GOD_ZIN:
+ simple_god_message(" says: Spread the light, my child.");
+ break;
+ case GOD_SHINING_ONE:
+ simple_god_message(" says: Lead the forces of light to victory!");
+ break;
+ case GOD_KIKUBAAQUDGHA:
+ simple_god_message(" says: Welcome...");
+ break;
+ case GOD_YREDELEMNUL:
+ simple_god_message(" says: Carry the black torch! Rouse the idle dead!");
+ break;
+ case GOD_NEMELEX_XOBEH:
+ simple_god_message(" says: It's all in the cards!");
+ break;
+ case GOD_XOM:
+ if (game_start)
+ simple_god_message(" says: A new plaything!");
+ break;
+ case GOD_VEHUMET:
+ simple_god_message(" says: Let it end in hellfire!");
+ break;
+ case GOD_OKAWARU:
+ simple_god_message(" says: Welcome, disciple.");
+ break;
+ case GOD_MAKHLEB:
+ god_speaks(you.religion, "Blood and souls for Makhleb!");
+ break;
+ case GOD_SIF_MUNA:
+ simple_god_message(" whispers: I know many secrets...");
+ break;
+ case GOD_TROG:
+ simple_god_message(" says: Kill them all!");
+ break;
+ case GOD_ELYVILON:
+ simple_god_message(" says: Go forth and aid the weak!");
+ break;
+ case GOD_LUGONU:
+ simple_god_message(" says: Spread carnage and corruption!");
+ break;
+ case GOD_BEOGH:
+ simple_god_message(" says: Drown the unbelievers in a sea of blood!");
+ break;
+ case GOD_JIYVA:
+ god_speaks(you.religion, "Slime for the Slime God!");
+ break;
+ case GOD_FEDHAS:
+ simple_god_message(" says: Spread life and death.");
+ break;
+ case GOD_CHEIBRIADOS:
+ simple_god_message(" says: Take it easy.");
+ break;
+
+ case GOD_NO_GOD:
+ case NUM_GODS:
+ case GOD_RANDOM:
+ case GOD_NAMELESS:
+ break;
+ }
+}
+
+static void _take_starting_note()
+{
+ std::ostringstream notestr;
+ notestr << you.your_name << ", the "
+ << species_name(you.species,you.experience_level) << " "
+ << you.class_name
+ << ", began the quest for the Orb.";
+ take_note(Note(NOTE_MESSAGE, 0, 0, notestr.str().c_str()));
+
+ notestr.str("");
+ notestr.clear();
+
+#ifdef WIZARD
+ if (you.wizard)
+ {
+ notestr << "You started the game in wizard mode.";
+ take_note(Note(NOTE_MESSAGE, 0, 0, notestr.str().c_str()));
+
+ notestr.str("");
+ notestr.clear();
+ }
+#endif
+
+ notestr << "HP: " << you.hp << "/" << you.hp_max
+ << " MP: " << you.magic_points << "/" << you.max_magic_points;
+
+ take_note(Note(NOTE_XP_LEVEL_CHANGE, you.experience_level, 0,
+ notestr.str().c_str()));
+}
+
+static void _startup_tutorial()
+{
+ // Don't allow triggering at game start.
+ Options.tut_just_triggered = true;
+
+ msg::streams(MSGCH_TUTORIAL)
+ << "Press any key to start the tutorial intro, or Escape to skip it."
+ << std::endl;
+
+ flush_prev_message();
+ const int ch = c_getch();
+ if (ch != ESCAPE)
+ tut_starting_screen();
+}
+
+#ifdef WIZARD
+static void _do_wizard_command(int wiz_command, bool silent_fail)
+{
+ ASSERT(you.wizard);
+
+ switch (wiz_command)
+ {
+ case '?':
+ {
+ const int key = list_wizard_commands(true);
+ _do_wizard_command(key, true);
+ return;
+ }
+
+ case CONTROL('D'): wizard_edit_durations(); break;
+ case CONTROL('F'): debug_fight_statistics(false, true); break;
+ case CONTROL('G'): save_ghost(true); break;
+ case CONTROL('H'): wizard_set_hunger_state(); break;
+ case CONTROL('I'): debug_item_statistics(); break;
+ case CONTROL('L'): wizard_set_xl(); break;
+ case CONTROL('T'): debug_terp_dlua(); break;
+
+ case 'O': debug_test_explore(); break;
+ case 'S': wizard_set_skill_level(); break;
+ case 'A': wizard_set_all_skills(); break;
+ case 'a': acquirement(OBJ_RANDOM, AQ_WIZMODE); break;
+ case 'v': wizard_value_artefact(); break;
+ case '+': wizard_make_object_randart(); break;
+ case '|': wizard_create_all_artefacts(); break;
+ case 'C': wizard_uncurse_item(); break;
+ case 'g': wizard_exercise_skill(); break;
+ case 'G': wizard_dismiss_all_monsters(); break;
+ case 'c': wizard_draw_card(); break;
+ case 'H': wizard_heal(true); break;
+ case 'h': wizard_heal(false); break;
+ case 'b': blink(1000, true, true); break;
+ case '~': wizard_interlevel_travel(); break;
+ case '"': debug_list_monsters(); break;
+ case 't': wizard_tweak_object(); break;
+ case 'T': debug_make_trap(); break;
+ case '\\': debug_make_shop(); break;
+ case 'f': debug_fight_statistics(false); break;
+ case 'F': debug_fight_statistics(true); break;
+ case 'm': wizard_create_spec_monster(); break;
+ case 'M': wizard_create_spec_monster_name(); break;
+ case 'R': wizard_spawn_control(); break;
+ case 'r': wizard_change_species(); break;
+ case '>': wizard_place_stairs(true); break;
+ case '<': wizard_place_stairs(false); break;
+ case 'P': wizard_create_portal(); break;
+ case 'L': debug_place_map(); break;
+ case 'i': wizard_identify_pack(); break;
+ case 'I': wizard_unidentify_pack(); break;
+ case 'z': wizard_cast_spec_spell(); break;
+ case 'Z': wizard_cast_spec_spell_name(); break;
+ case '(': wizard_create_feature_number(); break;
+ case ')': wizard_create_feature_name(); break;
+ case ':': wizard_list_branches(); break;
+ case '{': wizard_map_level(); break;
+ case '@': wizard_set_stats(); break;
+ case '^': wizard_gain_piety(); break;
+ case '_': wizard_get_religion(); break;
+ case '\'': wizard_list_items(); break;
+ case 'd': wizard_level_travel(true); break;
+ case 'D': wizard_detect_creatures(); break;
+ case 'u': case 'U': wizard_level_travel(false); break;
+ case '%': case 'o': wizard_create_spec_object(); break;
+
+ case 'x':
+ you.experience = 1 + exp_needed( 2 + you.experience_level );
+ level_change();
+ break;
+
+ case 's':
+ you.exp_available = 20000;
+ you.redraw_experience = true;
+ break;
+
+ case '$':
+ you.add_gold(1000);
+ if (!Options.show_gold_turns)
+ {
+ mprf("You now have %d gold piece%s.",
+ you.gold, you.gold != 1 ? "s" : "");
+ }
+ break;
+
+ case 'B':
+ if (you.level_type != LEVEL_ABYSS)
+ banished(DNGN_ENTER_ABYSS, "wizard command");
+ else
+ down_stairs(you.your_level, DNGN_EXIT_ABYSS);
+ break;
+
+ case CONTROL('A'):
+ if (you.level_type == LEVEL_ABYSS)
+ abyss_teleport(true);
+ else
+ mpr("You can only abyss_teleport() inside the Abyss.");
+ break;
+
+ case ']':
+ if (!wizard_add_mutation())
+ mpr( "Failure to give mutation." );
+ break;
+
+ case '=':
+ mprf( "Cost level: %d Skill points: %d Next cost level: %d",
+ you.skill_cost_level, you.total_skill_points,
+ skill_cost_needed( you.skill_cost_level + 1 ) );
+ break;
+
+ case 'X':
+ if (you.religion == GOD_XOM)
+ xom_acts(abs(you.piety - HALF_MAX_PIETY));
+ else
+ xom_acts(coinflip(), random_range(0, HALF_MAX_PIETY));
+ break;
+
+ case 'p':
+ dungeon_terrain_changed(you.pos(), DNGN_ENTER_PANDEMONIUM, false);
+ break;
+
+ case 'l':
+ dungeon_terrain_changed(you.pos(), DNGN_ENTER_LABYRINTH, false);
+ break;
+
+ case 'k':
+ if (you.level_type == LEVEL_LABYRINTH)
+ change_labyrinth(true);
+ else
+ mpr("This only makes sense in a labyrinth!");
+ break;
+
+
+ default:
+ if (!silent_fail)
+ {
+ formatted_mpr(formatted_string::parse_string(
+ "Not a <magenta>Wizard</magenta> Command."));
+ }
+ break;
+ }
+ // Force the placement of any delayed monster gifts.
+ you.turn_is_over = true;
+ religion_turn_end();
+
+ you.turn_is_over = false;
+}
+
+static void _handle_wizard_command( void )
+{
+ int wiz_command;
+
+ // WIZ_NEVER gives protection for those who have wiz compiles,
+ // and don't want to risk their characters.
+ if (Options.wiz_mode == WIZ_NEVER)
+ return;
+
+ if (!you.wizard)
+ {
+ mpr("WARNING: ABOUT TO ENTER WIZARD MODE!", MSGCH_WARN);
+
+#ifndef SCORE_WIZARD_MODE
+ mpr("If you continue, your game will not be scored!", MSGCH_WARN);
+#endif
+
+ if (!yesno( "Do you really want to enter wizard mode?", false, 'n' ))
+ return;
+
+ take_note(Note(NOTE_MESSAGE, 0, 0, "Entered wizard mode."));
+
+ you.wizard = true;
+ redraw_screen();
+
+ if (crawl_state.cmd_repeat_start)
+ {
+ crawl_state.cancel_cmd_repeat("Can't repeat entering wizard "
+ "mode.");
+ return;
+ }
+ }
+
+ mpr("Enter Wizard Command (? - help): ", MSGCH_PROMPT);
+ wiz_command = getch();
+
+ if (crawl_state.cmd_repeat_start)
+ {
+ // Easiest to list which wizard commands *can* be repeated.
+ switch (wiz_command)
+ {
+ case 'x':
+ case '$':
+ case 'a':
+ case 'c':
+ case 'h':
+ case 'H':
+ case 'm':
+ case 'M':
+ case 'X':
+ case '!':
+ case '[':
+ case ']':
+ case '^':
+ case '%':
+ case 'o':
+ case 'z':
+ case 'Z':
+ break;
+
+ default:
+ crawl_state.cant_cmd_repeat("You cannot repeat that "
+ "wizard command.");
+ return;
+ }
+ }
+
+ _do_wizard_command(wiz_command, false);
+}
+#endif
+
+// 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] = false;
+
+ if (i_feel_safe(true))
+ you.running.initialise(dir, mode);
+}
+
+static bool _recharge_rod( item_def &rod, bool wielded )
+{
+ if (!item_is_rod(rod) || rod.plus >= rod.plus2 || !enough_mp(1, true))
+ return (false);
+
+ const int charge = rod.plus / ROD_CHARGE_MULT;
+
+ int rate = ((charge + 1) * ROD_CHARGE_MULT) / 10;
+
+ rate *= (10 + skill_bump( SK_EVOCATIONS ));
+ rate = div_rand_round( rate, 100 );
+
+ if (rate < 5)
+ rate = 5;
+ else if (rate > ROD_CHARGE_MULT / 2)
+ rate = ROD_CHARGE_MULT / 2;
+
+ // If not wielded, the rod charges far more slowly.
+ if (!wielded)
+ rate /= 5;
+ // Shields hamper recharging for wielded rods.
+ else if (you.shield())
+ rate /= 2;
+
+ if (rod.plus / ROD_CHARGE_MULT != (rod.plus + rate) / ROD_CHARGE_MULT)
+ {
+ dec_mp(1);
+ if (wielded)
+ you.wield_change = true;
+ }
+
+ rod.plus += rate;
+ if (rod.plus > rod.plus2)
+ rod.plus = rod.plus2;
+
+ if (wielded && rod.plus == rod.plus2)
+ {
+ mpr("Your rod has recharged.");
+ if (is_resting())
+ stop_running();
+ }
+
+ return (true);
+}
+
+static void _recharge_rods()
+{
+ const int wielded = you.equip[EQ_WEAPON];
+ if (wielded != -1 && _recharge_rod( you.inv[wielded], true ))
+ return;
+
+ for (int i = 0; i < ENDOFPACK; ++i)
+ {
+ if (i != wielded && you.inv[i].is_valid()
+ && one_chance_in(3)
+ && _recharge_rod( you.inv[i], false ))
+ {
+ return;
+ }
+ }
+}
+
+static bool _cmd_is_repeatable(command_type cmd, bool is_again = false)
+{
+ switch (cmd)
+ {
+ // Informational commands
+ case CMD_LOOK_AROUND:
+ case CMD_INSPECT_FLOOR:
+ case CMD_EXAMINE_OBJECT:
+ case CMD_LIST_WEAPONS:
+ case CMD_LIST_ARMOUR:
+ case CMD_LIST_JEWELLERY:
+ case CMD_LIST_EQUIPMENT:
+ case CMD_LIST_GOLD:
+ case CMD_CHARACTER_DUMP:
+ case CMD_DISPLAY_COMMANDS:
+ case CMD_DISPLAY_INVENTORY:
+ case CMD_DISPLAY_KNOWN_OBJECTS:
+ case CMD_DISPLAY_MUTATIONS:
+ case CMD_DISPLAY_SKILLS:
+ case CMD_DISPLAY_OVERMAP:
+ case CMD_DISPLAY_RELIGION:
+ case CMD_DISPLAY_CHARACTER_STATUS:
+ case CMD_DISPLAY_SPELLS:
+ case CMD_EXPERIENCE_CHECK:
+ case CMD_RESISTS_SCREEN:
+ case CMD_READ_MESSAGES:
+ case CMD_SEARCH_STASHES:
+ mpr("You can't repeat informational commands.");
+ return (false);
+
+ // Multi-turn commands
+ case CMD_PICKUP:
+ case CMD_DROP:
+ case CMD_BUTCHER:
+ case CMD_GO_UPSTAIRS:
+ case CMD_GO_DOWNSTAIRS:
+ case CMD_WIELD_WEAPON:
+ case CMD_WEAPON_SWAP:
+ case CMD_WEAR_JEWELLERY:
+ case CMD_REMOVE_JEWELLERY:
+ case CMD_MEMORISE_SPELL:
+ case CMD_EXPLORE:
+ case CMD_INTERLEVEL_TRAVEL:
+ mpr("You can't repeat multi-turn commands.");
+ return (false);
+
+ // Miscellaneous non-repeatable commands.
+ case CMD_TOGGLE_AUTOPICKUP:
+ case CMD_TOGGLE_FRIENDLY_PICKUP:
+ case CMD_ADJUST_INVENTORY:
+ case CMD_QUIVER_ITEM:
+ case CMD_REPLAY_MESSAGES:
+ case CMD_REDRAW_SCREEN:
+ case CMD_MACRO_ADD:
+ case CMD_SAVE_GAME:
+ case CMD_SAVE_GAME_NOW:
+ case CMD_SUSPEND_GAME:
+ case CMD_QUIT:
+ case CMD_DESTROY_ITEM:
+ case CMD_FORGET_STASH:
+ case CMD_FIX_WAYPOINT:
+ case CMD_CLEAR_MAP:
+ case CMD_INSCRIBE_ITEM:
+ case CMD_MAKE_NOTE:
+ case CMD_CYCLE_QUIVER_FORWARD:
+#ifdef USE_TILE
+ case CMD_EDIT_PLAYER_TILE:
+#endif
+ mpr("You can't repeat that command.");
+ return (false);
+
+ case CMD_DISPLAY_MAP:
+ mpr("You can't repeat map commands.");
+ return (false);
+
+ case CMD_MOUSE_MOVE:
+ case CMD_MOUSE_CLICK:
+ mpr("You can't repeat mouse clicks or movements.");
+ return (false);
+
+ case CMD_REPEAT_CMD:
+ mpr("You can't repeat the repeat command!");
+ return (false);
+
+ case CMD_RUN_LEFT:
+ case CMD_RUN_DOWN:
+ case CMD_RUN_UP:
+ case CMD_RUN_RIGHT:
+ case CMD_RUN_UP_LEFT:
+ case CMD_RUN_DOWN_LEFT:
+ case CMD_RUN_UP_RIGHT:
+ case CMD_RUN_DOWN_RIGHT:
+ mpr("Why would you want to repeat a run command?");
+ return (false);
+
+ case CMD_PREV_CMD_AGAIN:
+ ASSERT(!is_again);
+ if (crawl_state.prev_cmd == CMD_NO_CMD)
+ {
+ mpr("No previous command to repeat.");
+ return (false);
+ }
+
+ return _cmd_is_repeatable(crawl_state.prev_cmd, true);
+
+ case CMD_MOVE_NOWHERE:
+ case CMD_REST:
+ case CMD_SEARCH:
+ return (i_feel_safe(true));
+
+ case CMD_MOVE_LEFT:
+ case CMD_MOVE_DOWN:
+ case CMD_MOVE_UP:
+ case CMD_MOVE_RIGHT:
+ case CMD_MOVE_UP_LEFT:
+ case CMD_MOVE_DOWN_LEFT:
+ case CMD_MOVE_UP_RIGHT:
+ case CMD_MOVE_DOWN_RIGHT:
+ if (!i_feel_safe())
+ {
+ return yesno("Really repeat movement command while monsters "
+ "are nearby?", false, 'n');
+ }
+
+ return (true);
+
+ case CMD_NO_CMD:
+ mpr("Unknown command, not repeating.");
+ return (false);
+
+ default:
+ return (true);
+ }
+
+ return (false);
+}
+
+// Used to determine whether to apply the berserk penalty at end of round.
+bool apply_berserk_penalty = false;
+
+static void _center_cursor()
+{
+#ifndef USE_TILE
+ const coord_def cwhere = grid2view(you.pos());
+ cgotoxy(cwhere.x, cwhere.y);
+#endif
+}
+
+//
+// This function handles the player's input. It's called from main(),
+// from inside an endless loop.
+//
+static void _input()
+{
+#if defined(USE_UNIX_SIGNALS) && defined(SIGHUP_SAVE) && defined(USE_CURSES)
+ if (crawl_state.seen_hups)
+ sighup_save_and_exit();
+#endif
+
+ crawl_state.clear_mon_acting();
+
+ religion_turn_start();
+ you.update_beholders();
+
+ // Currently only set if Xom accidentally kills the player.
+ you.reset_escaped_death();
+ flush_prev_message();
+
+ if (crawl_state.is_replaying_keys() && crawl_state.is_repeating_cmd()
+ && kbhit())
+ {
+ // User pressed a key, so stop repeating commands and discard
+ // the keypress.
+ crawl_state.cancel_cmd_repeat("Key pressed, interrupting command "
+ "repetition.");
+ crawl_state.prev_cmd = CMD_NO_CMD;
+ getchm();
+ return;
+ }
+
+ update_monsters_in_view();
+
+ you.turn_is_over = false;
+ _prep_input();
+
+ const bool player_feels_safe = i_feel_safe();
+
+ if (Options.tutorial_left)
+ {
+ Options.tut_just_triggered = false;
+
+ if (you.attribute[ATTR_HELD])
+ learned_something_new(TUT_CAUGHT_IN_NET);
+ else if (player_feels_safe && you.level_type != LEVEL_ABYSS)
+ {
+ // We don't want those "Whew, it's safe to rest now" messages
+ // if you were just cast into the Abyss. Right?
+
+ if (2 * you.hp < you.hp_max
+ || 2 * you.magic_points < you.max_magic_points)
+ {
+ tutorial_healing_reminder();
+ }
+ else if (!you.running
+ && Options.tutorial_events[TUT_SHIFT_RUN]
+ && you.num_turns >= 200
+ && you.hp == you.hp_max
+ && you.magic_points == you.max_magic_points)
+ {
+ learned_something_new(TUT_SHIFT_RUN);
+ }
+ else if (!you.running
+ && Options.tutorial_events[TUT_MAP_VIEW]
+ && you.num_turns >= 500
+ && you.hp == you.hp_max
+ && you.magic_points == you.max_magic_points)
+ {
+ learned_something_new(TUT_MAP_VIEW);
+ }
+ else if (!you.running
+ && Options.tutorial_events[TUT_AUTO_EXPLORE]
+ && you.num_turns >= 700
+ && you.hp == you.hp_max
+ && you.magic_points == you.max_magic_points)
+ {
+ learned_something_new(TUT_AUTO_EXPLORE);
+ }
+ }
+ else
+ {
+ if (2*you.hp < you.hp_max)
+ learned_something_new(TUT_RUN_AWAY);
+
+ if (Options.tutorial_type == TUT_MAGIC_CHAR && you.magic_points < 1)
+ learned_something_new(TUT_RETREAT_CASTER);
+ }
+ }
+
+ if (you.cannot_act())
+ {
+ if (crawl_state.repeat_cmd != CMD_WIZARD)
+ {
+ crawl_state.cancel_cmd_repeat("Cannot move, cancelling command "
+ "repetition.");
+ }
+ world_reacts();
+ return;
+ }
+
+ // Stop autoclearing more now that we have control back.
+ if (!you_are_delayed())
+ reset_more_autoclear();
+
+ if (need_to_autopickup())
+ autopickup();
+
+ if (need_to_autoinscribe())
+ autoinscribe();
+
+ handle_delay();
+
+ if (you_are_delayed() && current_delay_action() != DELAY_MACRO_PROCESS_KEY)
+ {
+ if (you.time_taken)
+ world_reacts();
+ return;
+ }
+
+ if (you.turn_is_over)
+ {
+ world_reacts();
+ return;
+ }
+
+ crawl_state.check_term_size();
+ if (crawl_state.terminal_resized)
+ handle_terminal_resize();
+
+ repeat_again_rec.paused = (crawl_state.is_replaying_keys()
+ && !crawl_state.cmd_repeat_start);
+
+ crawl_state.input_line_curr = 0;
+
+ {
+ flush_prev_message();
+
+ clear_macro_process_key_delay();
+
+ crawl_state.waiting_for_command = true;
+ c_input_reset(true);
+
+ _center_cursor();
+
+#ifdef USE_TILE
+ cursor_control con(false);
+#else
+ // Enable the cursor to read input. The cursor stays on while
+ // the command is being processed, so subsidiary prompts
+ // shouldn't need to turn it on explicitly.
+ cursor_control con(true);
+#endif
+ const command_type cmd = _get_next_cmd();
+
+#if defined(USE_UNIX_SIGNALS) && defined(SIGHUP_SAVE) && defined(USE_CURSES)
+ if (crawl_state.seen_hups)
+ sighup_save_and_exit();
+#endif
+
+ crawl_state.waiting_for_command = false;
+
+ if (cmd != CMD_PREV_CMD_AGAIN && cmd != CMD_REPEAT_CMD
+ && cmd != CMD_NO_CMD
+ && !crawl_state.is_replaying_keys())
+ {
+ crawl_state.prev_cmd = cmd;
+ crawl_state.input_line_strs.clear();
+ }
+
+ if (cmd != CMD_MOUSE_MOVE)
+ c_input_reset(false);
+
+ // [dshaligram] If get_next_cmd encountered a Lua macro
+ // binding, your turn may be ended by the first invoke of the
+ // macro.
+ if (!you.turn_is_over && cmd != CMD_NEXT_CMD)
+ process_command( cmd );
+
+ repeat_again_rec.paused = true;
+
+ if (cmd != CMD_MOUSE_MOVE)
+ c_input_reset(false, true);
+
+ // If the command was CMD_REPEAT_CMD, then the key for the
+ // command to repeat has been placed into the macro buffer,
+ // so return now to let input() be called again while
+ // the keys to repeat are recorded.
+ if (cmd == CMD_REPEAT_CMD)
+ return;
+
+ // If the command was CMD_PREV_CMD_AGAIN then _input() has been
+ // recursively called by _do_prev_cmd_again() via process_command()
+ // to re-do the command, so there's nothing more to do.
+ if (cmd == CMD_PREV_CMD_AGAIN)
+ return;
+ }
+
+ if (need_to_autoinscribe())
+ autoinscribe();
+
+ if (you.turn_is_over)
+ {
+ if (apply_berserk_penalty)
+ _do_berserk_no_combat_penalty();
+
+ world_reacts();
+ }
+ else
+ viewwindow(false);
+
+ _update_replay_state();
+
+ if (you.num_turns != -1)
+ {
+ PlaceInfo& curr_PlaceInfo = you.get_place_info();
+ PlaceInfo delta;
+
+ delta.turns_total++;
+ delta.elapsed_total += you.time_taken;
+
+ switch (you.running)
+ {
+ case RMODE_INTERLEVEL:
+ delta.turns_interlevel++;
+ delta.elapsed_interlevel += you.time_taken;
+ break;
+
+ case RMODE_EXPLORE_GREEDY:
+ case RMODE_EXPLORE:
+ delta.turns_explore++;
+ delta.elapsed_explore += you.time_taken;
+ break;
+
+ case RMODE_TRAVEL:
+ delta.turns_travel++;
+ delta.elapsed_travel += you.time_taken;
+ break;
+
+ default:
+ // prev_was_rest is needed so that the turn in which
+ // a player is interrupted from resting is counted
+ // as a resting turn, rather than "other".
+ static bool prev_was_rest = false;
+
+ if (!you.delay_queue.empty()
+ && you.delay_queue.front().type == DELAY_REST)
+ {
+ prev_was_rest = true;
+ }
+
+ if (prev_was_rest)
+ {
+ delta.turns_resting++;
+ delta.elapsed_resting += you.time_taken;
+ }
+ else
+ {
+ delta.turns_other++;
+ delta.elapsed_other += you.time_taken;
+ }
+
+ if (you.delay_queue.empty()
+ || you.delay_queue.front().type != DELAY_REST)
+ {
+ prev_was_rest = false;
+ }
+ break;
+ }
+
+ you.global_info += delta;
+ you.global_info.assert_validity();
+
+ curr_PlaceInfo += delta;
+ curr_PlaceInfo.assert_validity();
+ }
+
+ crawl_state.clear_god_acting();
+}
+
+static bool _stairs_check_mesmerised()
+{
+ if (you.beheld() && !you.confused())
+ {
+ const monsters* beholder = you.get_any_beholder();
+ mprf("You cannot move away from %s!",
+ beholder->name(DESC_NOCAP_THE, true).c_str());
+ return (true);
+ }
+
+ return (false);
+}
+
+static bool _marker_vetoes_stair()
+{
+ return marker_vetoes_operation("veto_stair");
+}
+
+// Maybe prompt to enter a portal, return true if we should enter the
+// portal, false if the user said no at the prompt.
+static bool _prompt_dangerous_portal(dungeon_feature_type ftype)
+{
+ switch(ftype)
+ {
+ case DNGN_ENTER_PANDEMONIUM:
+ case DNGN_ENTER_ABYSS:
+ return yesno("If you enter this portal you will not be able to return "
+ "immediately. Continue?", false, 'n');
+
+ default:
+ return (true);
+ }
+}
+
+static void _go_downstairs();
+static void _go_upstairs()
+{
+ ASSERT(!crawl_state.arena && !crawl_state.arena_suspended);
+
+ const dungeon_feature_type ygrd = grd(you.pos());
+
+ if (_stairs_check_mesmerised())
+ return;
+
+ if (you.attribute[ATTR_HELD])
+ {
+ mpr("You're held in a net!");
+ return;
+ }
+
+ if (ygrd == DNGN_ENTER_SHOP)
+ {
+ if (you.berserk())
+ canned_msg(MSG_TOO_BERSERK);
+ else
+ shop();
+ return;
+ }
+ else if (ygrd == DNGN_ENTER_HELL && you.level_type != LEVEL_DUNGEON)
+ {
+ mpr("You can't enter Hell from outside the dungeon!",
+ MSGCH_ERROR);
+ return;
+ }
+ // Up and down both work for portals.
+ else if (get_feature_dchar(ygrd) == DCHAR_ARCH
+ && feat_stair_direction(ygrd) != CMD_NO_CMD
+ && ygrd != DNGN_ENTER_ZOT)
+ {
+ ;
+ }
+ else if (feat_stair_direction(ygrd) != CMD_GO_UPSTAIRS)
+ {
+ if (ygrd == DNGN_STONE_ARCH)
+ mpr("There is nothing on the other side of the stone arch.");
+ else if (ygrd == DNGN_ABANDONED_SHOP)
+ mpr("This shop appears to be closed.");
+ else
+ mpr("You can't go up here!");
+ return;
+ }
+
+ if (!_prompt_dangerous_portal(ygrd))
+ return;
+
+ // Does the next level have a warning annotation?
+ if (!check_annotation_exclusion_warning())
+ return;
+
+ if (_marker_vetoes_stair())
+ return;
+
+ tag_followers(); // Only those beside us right now can follow.
+ start_delay(DELAY_ASCENDING_STAIRS,
+ 1 + (you.burden_state > BS_UNENCUMBERED));
+}
+
+static void _go_downstairs()
+{
+ ASSERT(!crawl_state.arena && !crawl_state.arena_suspended);
+
+ const dungeon_feature_type ygrd = grd(you.pos());
+
+ const bool shaft = (get_trap_type(you.pos()) == TRAP_SHAFT
+ && ygrd != DNGN_UNDISCOVERED_TRAP);
+
+ if (_stairs_check_mesmerised())
+ return;
+
+ if (shaft && you.flight_mode() == FL_LEVITATE)
+ {
+ mpr("You can't fall through a shaft while levitating.");
+ return;
+ }
+
+ // Up and down both work for shops.
+ if (ygrd == DNGN_ENTER_SHOP)
+ {
+ if (you.berserk())
+ canned_msg(MSG_TOO_BERSERK);
+ else
+ shop();
+ return;
+ }
+ else if (ygrd == DNGN_ENTER_HELL && you.level_type != LEVEL_DUNGEON)
+ {
+ mpr("You can't enter Hell from outside the dungeon!",
+ MSGCH_ERROR);
+ return;
+ }
+ // Up and down both work for portals.
+ else if (get_feature_dchar(ygrd) == DCHAR_ARCH
+ && feat_stair_direction(ygrd) != CMD_NO_CMD
+ && ygrd != DNGN_ENTER_ZOT)
+ {
+ ;
+ }
+ else if (feat_stair_direction(ygrd) != CMD_GO_DOWNSTAIRS
+ && !shaft)
+ {
+ if (ygrd == DNGN_STONE_ARCH)
+ mpr("There is nothing on the other side of the stone arch.");
+ else if (ygrd == DNGN_ABANDONED_SHOP)
+ mpr("This shop appears to be closed.");
+ else
+ mpr( "You can't go down here!" );
+ return;
+ }
+
+ if (you.attribute[ATTR_HELD])
+ {
+ mpr("You're held in a net!");
+ return;
+ }
+
+ if (!_prompt_dangerous_portal(ygrd))
+ return;
+
+ // Does the next level have a warning annotation?
+ // Also checks for entering a labyrinth with teleportitis.
+ if (!check_annotation_exclusion_warning())
+ return;
+
+ if (shaft)
+ {
+ start_delay( DELAY_DESCENDING_STAIRS, 0, you.your_level );
+ }
+ else
+ {
+ if (_marker_vetoes_stair())
+ return;
+
+ tag_followers(); // Only those beside us right now can follow.
+ start_delay(DELAY_DESCENDING_STAIRS,
+ 1 + (you.burden_state > BS_UNENCUMBERED),
+ you.your_level);
+ }
+}
+
+static void _experience_check()
+{
+ mprf("You are a level %d %s %s.",
+ you.experience_level,
+ species_name(you.species,you.experience_level).c_str(),
+ you.class_name);
+
+ if (you.experience_level < 27)
+ {
+ int xp_needed = (exp_needed(you.experience_level+2)-you.experience)+1;
+ mprf( "Level %d requires %ld experience (%d point%s to go!)",
+ you.experience_level + 1,
+ exp_needed(you.experience_level + 2) + 1,
+ xp_needed,
+ (xp_needed > 1) ? "s" : "");
+ }
+ 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)
+ {
+ const time_t curr = you.real_time + (time(NULL) - you.start_time);
+ msg::stream << "Play time: " << make_time_string(curr)
+ << " (" << you.num_turns << " turns)"
+ << std::endl;
+ }
+#ifdef DEBUG_DIAGNOSTICS
+ if (wearing_amulet(AMU_THE_GOURMAND))
+ mprf(MSGCH_DIAGNOSTICS, "Gourmand charge: %d",
+ you.duration[DUR_GOURMAND]);
+
+ mprf(MSGCH_DIAGNOSTICS, "Turns spent on this level: %d",
+ env.turns_on_level);
+#endif
+}
+
+static void _print_friendly_pickup_setting(bool was_changed)
+{
+ std::string now = (was_changed? "now " : "");
+
+ if (you.friendly_pickup == FRIENDLY_PICKUP_NONE)
+ {
+ mprf("Your intelligent allies are %sforbidden to pick up anything at all.",
+ now.c_str());
+ }
+ else if (you.friendly_pickup == FRIENDLY_PICKUP_FRIEND)
+ {
+ mprf("Your intelligent allies may %sonly pick up items dropped by allies.",
+ now.c_str());
+ }
+ else if (you.friendly_pickup == FRIENDLY_PICKUP_PLAYER)
+ {
+ mprf("Your intelligent allies may %sonly pick up items dropped by you "
+ "and your allies.", now.c_str());
+ }
+ else if (you.friendly_pickup == FRIENDLY_PICKUP_ALL)
+ {
+ mprf("Your intelligent allies may %spick up anything they need.",
+ now.c_str());
+ }
+ else
+ mprf(MSGCH_ERROR, "Your allies%s are collecting bugs!", now.c_str());
+}
+
+// Note that in some actions, you don't want to clear afterwards.
+// e.g. list_jewellery, etc.
+void process_command( command_type cmd )
+{
+ apply_berserk_penalty = true;
+ switch (cmd)
+ {
+#ifdef USE_TILE
+ case CMD_EDIT_PLAYER_TILE:
+ tiles.draw_doll_edit();
+ break;
+
+ case CMD_TOGGLE_SPELL_DISPLAY:
+ if (Options.tile_display == TDSP_SPELLS)
+ Options.tile_display = TDSP_INVENT;
+ else
+ Options.tile_display = TDSP_SPELLS;
+
+ tiles.update_inventory();
+ break;
+#endif
+
+ 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;
+ case CMD_OPEN_DOOR_RIGHT: _open_door( 1, 0); break;
+ case CMD_OPEN_DOOR_DOWN_RIGHT: _open_door( 1, 1); break;
+ case CMD_OPEN_DOOR_DOWN: _open_door( 0, 1); break;
+ case CMD_OPEN_DOOR_DOWN_LEFT: _open_door(-1, 1); break;
+ case CMD_OPEN_DOOR_LEFT: _open_door(-1, 0); break;
+
+ case CMD_MOVE_DOWN_LEFT: _move_player(-1, 1); break;
+ case CMD_MOVE_DOWN: _move_player( 0, 1); break;
+ case CMD_MOVE_UP_RIGHT: _move_player( 1, -1); break;
+ case CMD_MOVE_UP: _move_player( 0, -1); break;
+ case CMD_MOVE_UP_LEFT: _move_player(-1, -1); break;
+ case CMD_MOVE_LEFT: _move_player(-1, 0); break;
+ case CMD_MOVE_DOWN_RIGHT: _move_player( 1, 1); break;
+ case CMD_MOVE_RIGHT: _move_player( 1, 0); break;
+
+ case CMD_REST:
+ if (you.hunger_state == HS_STARVING && !you_min_hunger())
+ {
+ mpr("You are too hungry to rest.");
+ break;
+ }
+ if (i_feel_safe())
+ {
+ if ((you.hp == you.hp_max || you.species == SP_VAMPIRE
+ && you.hunger_state == HS_STARVING)
+ && you.magic_points == you.max_magic_points )
+ {
+ mpr("You start searching.");
+ }
+ else
+ mpr("You start resting.");
+ }
+ _start_running( RDIR_REST, RMODE_REST_DURATION );
+ break;
+
+ case CMD_RUN_DOWN_LEFT:
+ _start_running( RDIR_DOWN_LEFT, RMODE_START );
+ break;
+ case CMD_RUN_DOWN:
+ _start_running( RDIR_DOWN, RMODE_START );
+ break;
+ case CMD_RUN_UP_RIGHT:
+ _start_running( RDIR_UP_RIGHT, RMODE_START );
+ break;
+ case CMD_RUN_UP:
+ _start_running( RDIR_UP, RMODE_START );
+ break;
+ case CMD_RUN_UP_LEFT:
+ _start_running( RDIR_UP_LEFT, RMODE_START );
+ break;
+ case CMD_RUN_LEFT:
+ _start_running( RDIR_LEFT, RMODE_START );
+ break;
+ case CMD_RUN_DOWN_RIGHT:
+ _start_running( RDIR_DOWN_RIGHT, RMODE_START );
+ break;
+ case CMD_RUN_RIGHT:
+ _start_running( RDIR_RIGHT, RMODE_START );
+ break;
+
+ case CMD_DISABLE_MORE:
+ Options.show_more_prompt = false;
+ break;
+
+ case CMD_ENABLE_MORE:
+ Options.show_more_prompt = true;
+ break;
+
+ case CMD_REPEAT_KEYS:
+ ASSERT(crawl_state.is_repeating_cmd());
+
+ if (crawl_state.prev_repetition_turn == you.num_turns
+ && crawl_state.repeat_cmd != CMD_WIZARD
+ && (crawl_state.repeat_cmd != CMD_PREV_CMD_AGAIN
+ || crawl_state.prev_cmd != CMD_WIZARD))
+ {
+ // This is a catch-all that shouldn't really happen.
+ // If the command always takes zero turns, then it
+ // should be prevented in cmd_is_repeatable(). If
+ // a command sometimes takes zero turns (because it
+ // can't be done, for instance), then
+ // crawl_state.zero_turns_taken() should be called when
+ // it does take zero turns, to cancel command repetition
+ // before we reach here.
+#ifdef WIZARD
+ crawl_state.cant_cmd_repeat("Can't repeat a command which "
+ "takes no turns (unless it's a "
+ "wizard command), cancelling ");
+#else
+ crawl_state.cant_cmd_repeat("Can't repeat a command which "
+ "takes no turns, cancelling "
+ "repetitions.");
+#endif
+ crawl_state.cancel_cmd_repeat();
+ return;
+ }
+
+ crawl_state.cmd_repeat_count++;
+ if (crawl_state.cmd_repeat_count >= crawl_state.cmd_repeat_goal)
+ {
+ crawl_state.cancel_cmd_repeat();
+ return;
+ }
+
+ ASSERT(crawl_state.repeat_cmd_keys.size() > 0);
+ repeat_again_rec.paused = true;
+ macro_buf_add(crawl_state.repeat_cmd_keys);
+ macro_buf_add(KEY_REPEAT_KEYS);
+
+ crawl_state.prev_repetition_turn = you.num_turns;
+
+ break;
+
+ case CMD_TOGGLE_AUTOPICKUP:
+ if (Options.autopickup_on < 1)
+ Options.autopickup_on = 1;
+ else
+ Options.autopickup_on = 0;
+ mprf("Autopickup is now %s.", Options.autopickup_on > 0 ? "on" : "off");
+ break;
+
+ case CMD_TOGGLE_FRIENDLY_PICKUP:
+ {
+ // Toggle pickup mode for friendlies.
+ _print_friendly_pickup_setting(false);
+
+ mpr("Change to (d)efault, (n)othing, (f)riend-dropped, (p)layer, "
+ "or (a)ll? ", MSGCH_PROMPT);
+
+ char type = (char) getchm(KMC_DEFAULT);
+ type = tolower(type);
+
+ if (type == 'd')
+ you.friendly_pickup = Options.default_friendly_pickup;
+ else if (type == 'n')
+ you.friendly_pickup = FRIENDLY_PICKUP_NONE;
+ else if (type == 'f')
+ you.friendly_pickup = FRIENDLY_PICKUP_FRIEND;
+ else if (type == 'p')
+ you.friendly_pickup = FRIENDLY_PICKUP_PLAYER;
+ else if (type == 'a')
+ you.friendly_pickup = FRIENDLY_PICKUP_ALL;
+ else
+ {
+ canned_msg( MSG_OK );
+ break;
+ }
+ _print_friendly_pickup_setting(true);
+ break;
+ }
+ case CMD_MAKE_NOTE:
+ make_user_note();
+ break;
+
+ case CMD_READ_MESSAGES:
+#ifdef DGL_SIMPLE_MESSAGING
+ if (SysEnv.have_messages)
+ _read_messages();
+#endif
+ break;
+
+ case CMD_CLEAR_MAP:
+ if (player_in_mappable_area())
+ {
+ mpr("Clearing level map.");
+ clear_map();
+ crawl_view.set_player_at(you.pos());
+ }
+ break;
+
+ case CMD_DISPLAY_OVERMAP: display_overmap(); break;
+ case CMD_GO_UPSTAIRS: _go_upstairs(); break;
+ case CMD_GO_DOWNSTAIRS: _go_downstairs(); break;
+ case CMD_OPEN_DOOR: _open_door(0, 0); break;
+ case CMD_CLOSE_DOOR: _close_door(coord_def(0, 0)); break;
+
+ case CMD_DROP:
+ drop();
+ if (Options.stash_tracking >= STM_DROPPED)
+ StashTrack.add_stash();
+ break;
+
+ case CMD_SEARCH_STASHES:
+ if (Options.tut_stashes)
+ Options.tut_stashes = 0;
+ StashTrack.search_stashes();
+ break;
+
+ case CMD_FORGET_STASH:
+ if (Options.stash_tracking >= STM_EXPLICIT)
+ StashTrack.no_stash();
+ break;
+
+ case CMD_BUTCHER:
+ butchery();
+ break;
+
+ case CMD_DISPLAY_INVENTORY:
+ get_invent(OSEL_ANY);
+ break;
+
+ case CMD_EVOKE:
+ if (!evoke_item())
+ flush_input_buffer( FLUSH_ON_FAILURE );
+ break;
+
+ case CMD_EVOKE_WIELDED:
+ if (!evoke_item(you.equip[EQ_WEAPON]))
+ flush_input_buffer( FLUSH_ON_FAILURE );
+ break;
+
+ case CMD_PICKUP:
+ pickup();
+ break;
+
+ case CMD_INSPECT_FLOOR:
+ request_autopickup();
+ break;
+
+ case CMD_FULL_VIEW:
+ full_describe_view();
+ break;
+
+ case CMD_WIELD_WEAPON:
+ wield_weapon(false);
+ break;
+
+ case CMD_FIRE:
+ fire_thing();
+ break;
+
+ case CMD_QUIVER_ITEM:
+ choose_item_for_quiver();
+ break;
+
+ case CMD_THROW_ITEM_NO_QUIVER:
+ throw_item_no_quiver();
+ break;
+
+ case CMD_WEAR_ARMOUR:
+ wear_armour();
+ break;
+
+ case CMD_REMOVE_ARMOUR:
+ {
+ if (player_in_bat_form())
+ {
+ mpr("You can't wear or remove anything in your present form.");
+ break;
+ }
+ int index = 0;
+
+ if (armour_prompt("Take off which item?", &index, OPER_TAKEOFF))
+ takeoff_armour(index);
+ }
+ break;
+
+ case CMD_REMOVE_JEWELLERY:
+ remove_ring();
+ break;
+
+ case CMD_WEAR_JEWELLERY:
+ puton_ring(-1);
+ break;
+
+ case CMD_ADJUST_INVENTORY:
+ adjust();
+ break;
+
+ case CMD_MEMORISE_SPELL:
+ if (!learn_spell())
+ flush_input_buffer( FLUSH_ON_FAILURE );
+ break;
+
+ case CMD_ZAP_WAND:
+ zap_wand();
+ break;
+
+ case CMD_EAT:
+ eat_food();
+ break;
+
+ case CMD_USE_ABILITY:
+ if (!activate_ability())
+ flush_input_buffer( FLUSH_ON_FAILURE );
+ break;
+
+ case CMD_DISPLAY_MUTATIONS:
+ display_mutations();
+ redraw_screen();
+ break;
+
+ case CMD_EXAMINE_OBJECT:
+ examine_object();
+ break;
+
+ case CMD_PRAY:
+ pray();
+ break;
+
+ case CMD_DISPLAY_RELIGION:
+ describe_god( you.religion, true );
+ redraw_screen();
+ break;
+
+ case CMD_MOVE_NOWHERE:
+ case CMD_SEARCH:
+ search_around();
+ you.turn_is_over = true;
+ break;
+
+ case CMD_QUAFF:
+ drink();
+ break;
+
+ case CMD_READ:
+ read_scroll();
+ break;
+
+ case CMD_LOOK_AROUND:
+ {
+ mpr("Move the cursor around to observe a square "
+ "(v - describe square, ? - help)", MSGCH_PROMPT);
+
+ struct dist lmove; // Will be initialised by direction().
+ direction(lmove, DIR_TARGET, TARG_ANY, -1, true);
+ if (lmove.isValid && lmove.isTarget && !lmove.isCancel
+ && !crawl_state.arena_suspended)
+ {
+ start_travel( lmove.target );
+ }
+ break;
+ }
+
+ case CMD_CAST_SPELL:
+ case CMD_FORCE_CAST_SPELL:
+ if (player_in_bat_form()
+ || you.attribute[ATTR_TRANSFORMATION] == TRAN_PIG)
+ {
+ canned_msg(MSG_PRESENT_FORM);
+ break;
+ }
+
+ // Randart weapons.
+ if (scan_artefacts(ARTP_PREVENT_SPELLCASTING))
+ {
+ mpr("Something interferes with your magic!");
+ flush_input_buffer( FLUSH_ON_FAILURE );
+ break;
+ }
+
+ if (Options.tutorial_left)
+ Options.tut_spell_counter++;
+ if (!cast_a_spell(cmd == CMD_CAST_SPELL))
+ flush_input_buffer( FLUSH_ON_FAILURE );
+ break;
+
+ case CMD_DISPLAY_SPELLS:
+ inspect_spells();
+ break;
+
+ case CMD_WEAPON_SWAP:
+ wield_weapon(true);
+ break;
+
+ // [ds] Waypoints can be added from the level-map, and we need
+ // Ctrl+F for nobler things. Who uses waypoints, anyway?
+ // Update: Appears people do use waypoints. Reinstating, on
+ // CONTROL('W'). This means Ctrl+W is no longer a wizmode
+ // trigger, but there's always '&'. :-)
+ case CMD_FIX_WAYPOINT:
+ travel_cache.add_waypoint();
+ break;
+
+ case CMD_INTERLEVEL_TRAVEL:
+ if (Options.tut_travel)
+ Options.tut_travel = 0;
+
+ if (!can_travel_interlevel())
+ {
+ if (you.running.pos == you.pos())
+ {
+ mpr("You're already here.");
+ break;
+ }
+ else if (!you.running.pos.x || !you.running.pos.y)
+ {
+ mpr("Sorry, you can't auto-travel out of here.");
+ break;
+ }
+
+ // Don't ask for a destination if you can only travel
+ // within level anyway.
+ start_travel(you.running.pos);
+ }
+ else
+ start_translevel_travel();
+
+ if (you.running)
+ mesclr();
+ break;
+
+ case CMD_ANNOTATE_LEVEL:
+ annotate_level();
+ break;
+
+ case CMD_EXPLORE:
+ if (you.hunger_state == HS_STARVING && !you_min_hunger())
+ {
+ mpr("You need to eat something NOW!");
+ break;
+ }
+ // Start exploring
+ start_explore(Options.explore_greedy);
+ break;
+
+ case CMD_DISPLAY_MAP:
+ if (Options.tutorial_events[TUT_MAP_VIEW])
+ Options.tutorial_events[TUT_MAP_VIEW] = false;
+
+#if (!DEBUG_DIAGNOSTICS)
+ if (!player_in_mappable_area())
+ {
+ mpr("It would help if you knew where you were, first.");
+ break;
+ }
+#endif
+ {
+ level_pos pos;
+#ifdef USE_TILE
+ // Since there's no actual overview map, but the functionality
+ // exists, give a message to explain what's going on.
+ std::string str = "Move the cursor to view the level map, or "
+ "type <w>?</w> for a list of commands.";
+ print_formatted_paragraph(str);
+#endif
+
+ show_map(pos, true);
+ redraw_screen();
+
+#ifdef USE_TILE
+ mpr("Returning to the game...");
+#endif
+ if (pos.pos.x > 0)
+ start_translevel_travel(pos);
+ }
+ break;
+
+ case CMD_DISPLAY_KNOWN_OBJECTS:
+ check_item_knowledge();
+ break;
+
+ case CMD_REPLAY_MESSAGES:
+ replay_messages();
+ redraw_screen();
+ break;
+
+ case CMD_REDRAW_SCREEN:
+ redraw_screen();
+ break;
+
+ case CMD_SAVE_GAME_NOW:
+ mpr("Saving game... please wait.");
+ save_game(true);
+ break;
+
+#ifdef USE_UNIX_SIGNALS
+ case CMD_SUSPEND_GAME:
+ // CTRL-Z suspend behaviour is implemented here,
+ // because we want to have CTRL-Y available...
+ // and unfortunately they tend to be stuck together.
+ clrscr();
+#ifndef USE_TILE
+ unixcurses_shutdown();
+ kill(0, SIGTSTP);
+ unixcurses_startup();
+#endif
+ redraw_screen();
+ break;
+#endif
+
+ case CMD_DISPLAY_COMMANDS:
+ list_commands(0, true);
+ break;
+
+ case CMD_EXPERIENCE_CHECK:
+ _experience_check();
+ break;
+
+ case CMD_SHOUT:
+ yell();
+ break;
+
+ case CMD_MOUSE_MOVE:
+ {
+ const coord_def dest = view2grid(crawl_view.mousep);
+ if (in_bounds(dest))
+ terse_describe_square(dest);
+ break;
+ }
+
+ case CMD_MOUSE_CLICK:
+ {
+ // XXX: We should probably use specific commands such as
+ // CMD_MOUSE_TRAVEL and get rid of CMD_MOUSE_CLICK and
+ // CMD_MOUSE_MOVE.
+ c_mouse_event cme = get_mouse_event();
+ if (cme && crawl_view.in_view_viewport(cme.pos))
+ {
+ const coord_def dest = view2grid(cme.pos);
+ if (cme.left_clicked())
+ {
+ if (in_bounds(dest))
+ start_travel(dest);
+ }
+ else if (cme.right_clicked())
+ {
+ if (you.see_cell(dest))
+ full_describe_square(dest);
+ else
+ mpr("You can't see that place.");
+ }
+ }
+ break;
+ }
+
+ case CMD_DISPLAY_CHARACTER_STATUS:
+ display_char_status();
+ break;
+
+ case CMD_RESISTS_SCREEN:
+ print_overview_screen();
+ break;
+
+ case CMD_DISPLAY_SKILLS:
+ show_skills();
+ redraw_screen();
+ break;
+
+ case CMD_CHARACTER_DUMP:
+ if (dump_char(you.your_name, false))
+ mpr("Char dumped successfully.");
+ else
+ mpr("Char dump unsuccessful! Sorry about that.");
+ break;
+
+ case CMD_MACRO_ADD:
+ macro_add_query();
+ break;
+
+ case CMD_CYCLE_QUIVER_FORWARD:
+ case CMD_CYCLE_QUIVER_BACKWARD:
+ {
+ int dir = (cmd == CMD_CYCLE_QUIVER_FORWARD ? +1 : -1);
+ int cur = you.m_quiver->get_fire_item();
+ const int next = get_next_fire_item(cur, dir);
+#ifdef DEBUG_QUIVER
+ mprf(MSGCH_DIAGNOSTICS, "next slot: %d, item: %s", next,
+ next == -1 ? "none" : you.inv[next].name(DESC_PLAIN).c_str());
+#endif
+ if (next != -1)
+ {
+ // Kind of a hacky way to get quiver to change.
+ you.m_quiver->on_item_fired(you.inv[next], true);
+
+ if (next == cur)
+ mpr("No other missiles available. Use F to throw any item.");
+ }
+ else if (cur == -1)
+ mpr("No missiles available. Use F to throw any item.");
+ break;
+ }
+
+ case CMD_LIST_WEAPONS:
+ list_weapons();
+ break;
+
+ case CMD_LIST_ARMOUR:
+ list_armour();
+ break;
+
+ case CMD_LIST_JEWELLERY:
+ list_jewellery();
+ break;
+
+ case CMD_LIST_EQUIPMENT:
+ get_invent(OSEL_EQUIP);
+ break;
+
+ case CMD_LIST_GOLD:
+ if (shopping_list.size() == 0)
+ mprf("You have %d gold piece%s.",
+ you.gold, you.gold != 1 ? "s" : "");
+ else
+ shopping_list.display();
+
+ break;
+
+ case CMD_INSCRIBE_ITEM:
+ prompt_inscribe_item();
+ break;
+
+#ifdef WIZARD
+ case CMD_WIZARD:
+ _handle_wizard_command();
+ break;
+#endif
+
+ case CMD_SAVE_GAME:
+ if (yesno("Save game and exit?", true, 'n'))
+ save_game(true);
+ break;
+
+ case CMD_QUIT:
+ if (yes_or_no("Are you sure you want to quit"))
+ ouch(INSTANT_DEATH, NON_MONSTER, KILLED_BY_QUITTING);
+ else
+ canned_msg(MSG_OK);
+ break;
+
+ case CMD_REPEAT_CMD:
+ _setup_cmd_repeat();
+ break;
+
+ case CMD_PREV_CMD_AGAIN:
+ _do_prev_cmd_again();
+ break;
+
+ case CMD_NO_CMD:
+ default:
+ if (Options.tutorial_left)
+ {
+ std::string msg = "Unknown command. (For a list of commands type "
+ "<w>?\?<lightgrey>.)";
+ print_formatted_paragraph(msg);
+ }
+ else // well, not examine, but...
+ mpr("Unknown command.", MSGCH_EXAMINE_FILTER);
+ break;
+ }
+
+ flush_prev_message();
+}
+
+static void _prep_input()
+{
+ you.time_taken = player_speed();
+ you.shield_blocks = 0; // no blocks this round
+
+ textcolor(LIGHTGREY);
+
+ set_redraw_status( REDRAW_LINE_2_MASK | REDRAW_LINE_3_MASK );
+ print_stats();
+}
+
+// Decrement a single duration. Print the message if the duration runs out.
+// Returns true if the duration ended.
+// At midpoint (defined by get_expiration_threshold() in player.cc)
+// print midmsg and decrease duration by midloss (a randomised amount so as
+// to make it impossible to know the exact remaining duration for sure).
+// NOTE: The maximum possible midloss should be smaller than midpoint,
+// otherwise the duration may end in the same turn the warning
+// message is printed which would be a bit late.
+static bool _decrement_a_duration(duration_type dur, const char* endmsg = NULL,
+ int midloss = 0, const char* midmsg = NULL,
+ msg_channel_type chan = MSGCH_DURATION)
+{
+ if (you.duration[dur] < 1)
+ return (false);
+
+ const int midpoint = get_expiration_threshold(dur);
+
+ you.duration[dur]--;
+ if (you.duration[dur] == midpoint)
+ {
+ if (midmsg)
+ mpr(midmsg, chan);
+ you.duration[dur] -= midloss;
+ }
+
+ // allow fall-through in case midloss ended the duration (it shouldn't)
+ if (you.duration[dur] == 0)
+ {
+ if (endmsg)
+ mpr(endmsg, chan);
+ return true;
+ }
+
+ return false;
+}
+
+// Perhaps we should write functions like: update_liquid_flames(), etc.
+// Even better, we could have a vector of callback functions (or
+// objects) which get installed at some point.
+static void _decrement_durations()
+{
+ if (wearing_amulet(AMU_THE_GOURMAND))
+ {
+ if (you.duration[DUR_GOURMAND] < GOURMAND_MAX && coinflip())
+ you.duration[DUR_GOURMAND]++;
+ }
+ else
+ you.duration[DUR_GOURMAND] = 0;
+
+ if (you.duration[DUR_ICEMAIL_DEPLETED] > 0)
+ {
+ --you.duration[DUR_ICEMAIL_DEPLETED];
+
+ if (!you.duration[DUR_ICEMAIL_DEPLETED])
+ mpr("Your icy envelope is fully restored.", MSGCH_DURATION);
+
+ you.redraw_armour_class = true;
+ }
+
+ // Must come before might/haste/berserk.
+ if (_decrement_a_duration(DUR_BUILDING_RAGE))
+ go_berserk(false);
+
+ if (_decrement_a_duration(DUR_SLEEP))
+ you.awake();
+
+ // Sticky flame paradox: It both lasts longer and does more damage
+ // overall if you're moving more slowly.
+ //
+ // Rationalisation: I guess it gets rubbed off/falls off/etc. if you
+ // move around more.
+ dec_napalm_player();
+
+ if (_decrement_a_duration(DUR_ICY_ARMOUR,
+ "Your icy armour evaporates.", coinflip(),
+ "Your icy armour starts to melt."))
+ {
+ you.redraw_armour_class = true;
+ }
+
+ if (_decrement_a_duration(DUR_SILENCE, "Your hearing returns."))
+ you.attribute[ATTR_WAS_SILENCED] = 0;
+
+ _decrement_a_duration(DUR_REPEL_MISSILES,
+ "You feel less protected from missiles.",
+ coinflip(),
+ "Your repel missiles spell is about to expire...");
+
+ _decrement_a_duration(DUR_DEFLECT_MISSILES,
+ "You feel less protected from missiles.",
+ coinflip(),
+ "Your deflect missiles spell is about to expire...");
+
+ if (_decrement_a_duration(DUR_REGENERATION,
+ NULL, coinflip(),
+ "Your skin is crawling a little less now."))
+ {
+ remove_regen(you.attribute[ATTR_DIVINE_REGENERATION]);
+ }
+
+ if (you.duration[DUR_PRAYER] > 1)
+ you.duration[DUR_PRAYER]--;
+ else if (you.duration[DUR_PRAYER] == 1)
+ end_prayer();
+
+ if (you.duration[DUR_DIVINE_SHIELD] > 0)
+ {
+ if (you.duration[DUR_DIVINE_SHIELD] > 1)
+ {
+ if (--you.duration[DUR_DIVINE_SHIELD] == 1)
+ mpr("Your divine shield starts to fade.", MSGCH_DURATION);
+ }
+
+ if (you.duration[DUR_DIVINE_SHIELD] == 1 && !one_chance_in(3))
+ {
+ you.redraw_armour_class = true;
+ if (--you.attribute[ATTR_DIVINE_SHIELD] == 0)
+ {
+ you.duration[DUR_DIVINE_SHIELD] = 0;
+ mpr("Your divine shield fades away.", MSGCH_DURATION);
+ }
+ }
+ }
+
+ //jmf: More flexible weapon branding code.
+ if (you.duration[DUR_WEAPON_BRAND] > 1)
+ you.duration[DUR_WEAPON_BRAND]--;
+ else if (you.duration[DUR_WEAPON_BRAND] == 1)
+ {
+ item_def& weapon = *you.weapon();
+ const int temp_effect = get_weapon_brand(weapon);
+
+ you.duration[DUR_WEAPON_BRAND] = 0;
+ set_item_ego_type(weapon, OBJ_WEAPONS, SPWPN_NORMAL);
+ std::string msg = weapon.name(DESC_CAP_YOUR);
+
+ switch (temp_effect)
+ {
+ case SPWPN_VORPAL:
+ if (get_vorpal_type(weapon) == DVORP_SLICING)
+ msg += " seems blunter.";
+ else
+ msg += " feels lighter.";
+ break;
+ case SPWPN_FLAMING:
+ msg += " goes out.";
+ break;
+ case SPWPN_FREEZING:
+ msg += " stops glowing.";
+ break;
+ case SPWPN_VENOM:
+ msg += " stops dripping with poison.";
+ break;
+ case SPWPN_DRAINING:
+ msg += " stops crackling.";
+ break;
+ case SPWPN_DISTORTION:
+ msg += " seems straighter.";
+ break;
+ case SPWPN_PAIN:
+ msg += " seems less painful.";
+ break;
+ default:
+ msg += " seems inexplicably less special.";
+ break;
+ }
+
+ mpr(msg.c_str(), MSGCH_DURATION);
+ you.wield_change = true;
+ }
+
+ // Vampire bat transformations are permanent (until ended).
+ if (you.species != SP_VAMPIRE || !player_in_bat_form()
+ || you.duration[DUR_TRANSFORMATION] <= 5)
+ {
+ if (_decrement_a_duration(DUR_TRANSFORMATION, NULL, random2(3),
+ "Your transformation is almost over."))
+ {
+ untransform();
+ you.duration[DUR_BREATH_WEAPON] = 0;
+ }
+ }
+
+ // Must come after transformation duration.
+ _decrement_a_duration(DUR_BREATH_WEAPON, "You have got your breath back.",
+ 0, NULL, MSGCH_RECOVERY);
+
+ _decrement_a_duration(DUR_SWIFTNESS,
+ "You feel sluggish.", coinflip(),
+ "You start to feel a little slower.");
+ _decrement_a_duration(DUR_INSULATION,
+ "You feel conductive.", coinflip(),
+ "You start to feel a little less insulated.");
+
+ if (_decrement_a_duration(DUR_STONEMAIL,
+ "Your scaly stone armour disappears.",
+ coinflip(),
+ "Your scaly stone armour is starting "
+ "to flake away."))
+ {
+ you.redraw_armour_class = true;
+ burden_change();
+ }
+
+ if (_decrement_a_duration(DUR_PHASE_SHIFT,
+ "You are firmly grounded in the material plane once more.",
+ coinflip(),
+ "You feel closer to the material plane."))
+ {
+ you.redraw_evasion = true;
+ }
+
+ if (_decrement_a_duration(DUR_SEE_INVISIBLE) && !you.can_see_invisible())
+ mpr("Your eyesight blurs momentarily.", MSGCH_DURATION);
+
+ _decrement_a_duration(DUR_TELEPATHY, "You feel less empathic.");
+
+ if (_decrement_a_duration(DUR_CONDENSATION_SHIELD))
+ remove_condensation_shield();
+
+ if (you.duration[DUR_CONDENSATION_SHIELD] && player_res_cold() < 0)
+ {
+ mpr("You feel very cold.");
+ ouch(2 + random2avg(13, 2), NON_MONSTER, KILLED_BY_FREEZING);
+ }
+
+ if (_decrement_a_duration(DUR_MAGIC_SHIELD,
+ "Your magical shield disappears."))
+ {
+ you.redraw_armour_class = true;
+ }
+
+ if (_decrement_a_duration(DUR_STONESKIN, "Your skin feels tender."))
+ you.redraw_armour_class = true;
+
+ if (_decrement_a_duration(DUR_TELEPORT))
+ {
+ // Only to a new area of the abyss sometimes (for abyss teleports).
+ you_teleport_now(true, one_chance_in(5));
+ untag_followers();
+ }
+
+ _decrement_a_duration(DUR_CONTROL_TELEPORT,
+ "You feel uncertain.", coinflip(),
+ "You start to feel a little uncertain.");
+
+ if (_decrement_a_duration(DUR_DEATH_CHANNEL,
+ "Your unholy channel expires.", coinflip(),
+ "Your unholy channel is weakening."))
+ {
+ you.attribute[ATTR_DIVINE_DEATH_CHANNEL] = 0;
+ }
+
+ _decrement_a_duration(DUR_SAGE, "You feel less studious.");
+ _decrement_a_duration(DUR_STEALTH, "You feel less stealthy.");
+ _decrement_a_duration(DUR_RESIST_FIRE, "Your fire resistance expires.");
+ _decrement_a_duration(DUR_RESIST_COLD, "Your cold resistance expires.");
+ _decrement_a_duration(DUR_RESIST_POISON, "Your poison resistance expires.");
+ _decrement_a_duration(DUR_SLAYING, "You feel less lethal.");
+
+ _decrement_a_duration(DUR_INVIS, "You flicker back into view.",
+ coinflip(), "You flicker for a moment.");
+
+ _decrement_a_duration(DUR_BARGAIN, "You feel less charismatic.");
+ _decrement_a_duration(DUR_CONF, "You feel less confused.");
+ _decrement_a_duration(DUR_LOWERED_MR, "You feel more resistant to magic.");
+ _decrement_a_duration(DUR_SLIMIFY, "You feel less slimy.",
+ coinflip(), "Your slime is starting to congeal.");
+
+ if (you.duration[DUR_PARALYSIS] || you.petrified())
+ {
+ _decrement_a_duration(DUR_PARALYSIS);
+ _decrement_a_duration(DUR_PETRIFIED);
+
+ if (!you.duration[DUR_PARALYSIS] && !you.petrified())
+ {
+ mpr("You can move again.", MSGCH_DURATION);
+ you.redraw_evasion = true;
+ }
+ }
+
+ _decrement_a_duration(DUR_EXHAUSTED, "You feel less fatigued.");
+
+ _decrement_a_duration(DUR_CONFUSING_TOUCH,
+ ((std::string("Your ") + your_hand(true)) +
+ " stop glowing.").c_str());
+
+ _decrement_a_duration(DUR_SURE_BLADE,
+ "The bond with your blade fades away.");
+
+ if (_decrement_a_duration(DUR_MESMERISED, "You break out of your daze.",
+ 0, NULL, MSGCH_RECOVERY))
+ {
+ you.clear_beholders();
+ }
+
+ dec_slow_player();
+ dec_haste_player();
+
+ if (_decrement_a_duration(DUR_MIGHT, "You feel a little less mighty now."))
+ modify_stat(STAT_STRENGTH, -5, true, "might running out");
+
+ if (_decrement_a_duration(DUR_AGILITY, "You feel a little less agile now."))
+ modify_stat(STAT_DEXTERITY, -5, true, "agility running out");
+
+ if (_decrement_a_duration(DUR_BRILLIANCE, "You feel a little less clever now."))
+ modify_stat(STAT_INTELLIGENCE, -5, true, "brilliance running out");
+
+ if (_decrement_a_duration(DUR_BERSERKER, "You are no longer berserk."))
+ {
+ //jmf: Guilty for berserking /after/ berserk.
+ did_god_conduct(DID_STIMULANTS, 6 + random2(6));
+
+ // Sometimes berserk leaves us physically drained.
+ //
+ // Chance of passing out:
+ // - mutation gives a large plus in order to try and
+ // avoid the mutation being a "death sentence" to
+ // certain characters.
+ // - knowing the spell gives an advantage just
+ // so that people who have invested 3 spell levels
+ // are better off than the casual potion drinker...
+ // this should make it a bit more interesting for
+ // Crusaders again.
+ // - similarly for the amulet
+
+ if (you.berserk_penalty != NO_BERSERK_PENALTY)
+ {
+ const int chance =
+ 10 + player_mutation_level(MUT_BERSERK) * 25
+ + (wearing_amulet(AMU_RAGE) ? 10 : 0)
+ + (you.has_spell(SPELL_BERSERKER_RAGE) ? 5 : 0);
+
+ // Note the beauty of Trog! They get an extra save that's at
+ // the very least 20% and goes up to 100%.
+ if (you.religion == GOD_TROG && x_chance_in_y(you.piety, 150)
+ && !player_under_penance())
+ {
+ mpr("Trog's vigour flows through your veins.");
+ }
+ else if (one_chance_in(chance))
+ {
+ mpr("You pass out from exhaustion.", MSGCH_WARN);
+ you.duration[DUR_PARALYSIS] += roll_dice(1, 4);
+ }
+ }
+
+ if (!you.duration[DUR_PARALYSIS] && !you.petrified())
+ mpr("You are exhausted.", MSGCH_WARN);
+
+ // This resets from an actual penalty or from NO_BERSERK_PENALTY.
+ you.berserk_penalty = 0;
+
+ int dur = 12 + roll_dice(2, 12);
+ you.duration[DUR_EXHAUSTED] += dur;
+
+ // Don't trigger too many tutorial messages.
+ const bool tut_slow = Options.tutorial_events[TUT_YOU_ENCHANTED];
+ Options.tutorial_events[TUT_YOU_ENCHANTED] = false;
+
+ {
+ // Don't give duplicate 'You feel yourself slow down' messages.
+ no_messages nm;
+
+ // While the amulet of resist slowing does prevent the post-berserk
+ // slowing, exhaustion still ends haste.
+ if (you.duration[DUR_HASTE] > 0)
+ {
+ if (wearing_amulet(AMU_RESIST_SLOW))
+ {
+ if (you.duration[DUR_HASTE > 6])
+ {
+ you.duration[DUR_HASTE] = 2 + coinflip();
+ mpr("Your extra speed is starting to run out.",
+ MSGCH_DURATION);
+ }
+ else
+ {
+ mpr("You feel yourself slow down.", MSGCH_DURATION);
+ you.duration[DUR_HASTE] = 0;
+ }
+ did_god_conduct(DID_HASTY, 3, true);
+ }
+ else
+ {
+ // Silently cancel haste, then slow player.
+ you.duration[DUR_HASTE] = 0;
+ }
+ }
+ slow_player(dur);
+ }
+
+ make_hungry(700, true);
+ you.hunger = std::max(50, you.hunger);
+
+ // 1KB: No berserk healing.
+ you.hp = (you.hp + 1) / 2;
+ calc_hp();
+
+ learned_something_new(TUT_POSTBERSERK);
+ Options.tutorial_events[TUT_YOU_ENCHANTED] = tut_slow;
+ }
+
+ if (you.duration[DUR_CORONA] && !--you.duration[DUR_CORONA]
+ && !you.backlit())
+ {
+ mpr("You are no longer glowing.", MSGCH_DURATION);
+ }
+
+ // Leak piety from the piety pool into actual piety.
+ // Note that changes of religious status without corresponding actions
+ // (killing monsters, offering items, ...) might be confusing for characters
+ // of other religions.
+ // For now, though, keep information about what happened hidden.
+ if (you.piety < MAX_PIETY && you.duration[DUR_PIETY_POOL] > 0
+ && one_chance_in(5))
+ {
+ you.duration[DUR_PIETY_POOL]--;
+ gain_piety(1);
+
+#if DEBUG_DIAGNOSTICS || DEBUG_SACRIFICE || DEBUG_PIETY
+ mpr("Piety increases by 1 due to piety pool.", MSGCH_DIAGNOSTICS);
+
+ if (you.duration[DUR_PIETY_POOL] == 0)
+ mpr("Piety pool is now empty.", MSGCH_DIAGNOSTICS);
+#endif
+ }
+
+ if (!you.permanent_levitation() && !you.permanent_flight())
+ {
+ if (_decrement_a_duration(DUR_LEVITATION,
+ "You float gracefully downwards.",
+ random2(6),
+ "You are starting to lose your buoyancy!"))
+ {
+ burden_change();
+ // Landing kills controlled flight.
+ you.duration[DUR_CONTROLLED_FLIGHT] = 0;
+ // Re-enter the terrain.
+ move_player_to_grid(you.pos(), false, true, true);
+ }
+ }
+
+ if (!you.permanent_flight())
+ {
+ if (_decrement_a_duration(DUR_CONTROLLED_FLIGHT) && you.airborne())
+ mpr("You lose control over your flight.", MSGCH_DURATION);
+ }
+
+ if (you.rotting > 0)
+ {
+ // XXX: Mummies have an ability (albeit an expensive one) that
+ // can fix rotted HPs now... it's probably impossible for them
+ // to even start rotting right now, but that could be changed. -- bwr
+ // It's not normal biology, so Cheibriados won't help.
+ if (you.species == SP_MUMMY)
+ you.rotting = 0;
+ else if (x_chance_in_y(you.rotting, 20))
+ {
+ mpr("You feel your flesh rotting away.", MSGCH_WARN);
+ ouch(1, NON_MONSTER, KILLED_BY_ROTTING);
+ rot_hp(1);
+ you.rotting--;
+ }
+ }
+
+ // ghoul rotting is special, but will deduct from you.rotting
+ // if it happens to be positive - because this is placed after
+ // the "normal" rotting check, rotting attacks can be somewhat
+ // more painful on ghouls - reversing order would make rotting
+ // attacks somewhat less painful, but that seems wrong-headed {dlb}:
+ if (you.species == SP_GHOUL)
+ {
+ if (one_chance_in((you.religion == GOD_CHEIBRIADOS && you.piety >
+ piety_breakpoint(0)) ? 600 : 400))
+ {
+ mpr("You feel your flesh rotting away.", MSGCH_WARN);
+ ouch(1, NON_MONSTER, KILLED_BY_ROTTING);
+ rot_hp(1);
+
+ if (you.rotting > 0)
+ you.rotting--;
+ }
+ }
+
+ dec_disease_player();
+
+ dec_poison_player();
+
+ if (you.duration[DUR_DEATHS_DOOR])
+ {
+ if (you.hp > allowed_deaths_door_hp())
+ {
+ mpr("Your life is in your own hands once again.", MSGCH_DURATION);
+ you.duration[DUR_PARALYSIS] += 5 + random2(5);
+ confuse_player(10 + random2(10));
+ you.hp_max--;
+ deflate_hp(you.hp_max, false);
+ you.duration[DUR_DEATHS_DOOR] = 0;
+ }
+ else
+ {
+ _decrement_a_duration(DUR_DEATHS_DOOR,
+ "Your life is in your own hands again!",
+ random2(6),
+ "Your time is quickly running out!");
+ }
+ }
+
+ if (_decrement_a_duration(DUR_DIVINE_VIGOUR))
+ remove_divine_vigour();
+
+ if (_decrement_a_duration(DUR_DIVINE_STAMINA))
+ remove_divine_stamina();
+
+ _decrement_a_duration(DUR_REPEL_STAIRS_MOVE);
+ _decrement_a_duration(DUR_REPEL_STAIRS_CLIMB);
+}
+
+static void _check_banished()
+{
+ if (you.banished)
+ {
+ you.banished = false;
+ if (you.level_type != LEVEL_ABYSS)
+ {
+ mpr("You are cast into the Abyss!");
+ more();
+ banished(DNGN_ENTER_ABYSS, you.banished_by);
+ }
+ you.banished_by.clear();
+ }
+}
+
+static void _check_shafts()
+{
+ for (int i = 0; i < MAX_TRAPS; ++i)
+ {
+ trap_def &trap = env.trap[i];
+
+ if (trap.type != TRAP_SHAFT)
+ continue;
+
+ ASSERT(in_bounds(trap.pos));
+
+ handle_items_on_shaft(trap.pos, true);
+ }
+}
+
+static void _check_sanctuary()
+{
+ if (env.sanctuary_time <= 0)
+ return;
+
+ decrease_sanctuary_radius();
+}
+
+static void _regenerate_hp_and_mp()
+{
+ // XXX: using an int tmp to fix the fact that hit_points_regeneration
+ // is only an unsigned char and is thus likely to overflow. -- bwr
+ int tmp = you.hit_points_regeneration;
+
+ if (you.hp < you.hp_max && !you.disease && !you.duration[DUR_DEATHS_DOOR])
+ tmp += player_regen();
+
+ while (tmp >= 100)
+ {
+ inc_hp(1, false);
+ tmp -= 100;
+ }
+
+ ASSERT( tmp >= 0 && tmp < 100 );
+ you.hit_points_regeneration = static_cast<unsigned char>(tmp);
+
+ // XXX: Doing the same as the above, although overflow isn't an
+ // issue with magic point regeneration, yet. -- bwr
+ tmp = you.magic_points_regeneration;
+
+ if (you.magic_points < you.max_magic_points)
+ tmp += 7 + you.max_magic_points / 2;
+
+ while (tmp >= 100)
+ {
+ inc_mp(1, false);
+ tmp -= 100;
+ }
+
+ ASSERT( tmp >= 0 && tmp < 100 );
+ you.magic_points_regeneration = static_cast<unsigned char>(tmp);
+}
+
+void world_reacts()
+{
+ crawl_state.clear_mon_acting();
+
+ if (!crawl_state.arena)
+ {
+ you.turn_is_over = true;
+ religion_turn_end();
+ crawl_state.clear_god_acting();
+ }
+
+#ifdef USE_TILE
+ if (Options.tutorial_left)
+ {
+ tiles.clear_text_tags(TAG_TUTORIAL);
+ tiles.place_cursor(CURSOR_TUTORIAL, Region::NO_CURSOR);
+ }
+#endif
+
+ if (you.num_turns != -1)
+ {
+ if (you.num_turns < LONG_MAX)
+ you.num_turns++;
+ if (env.turns_on_level < INT_MAX)
+ env.turns_on_level++;
+ update_turn_count();
+ }
+
+ _check_banished();
+ _check_shafts();
+ _check_sanctuary();
+
+ run_environment_effects();
+
+ if (!you.cannot_act() && !player_mutation_level(MUT_BLURRY_VISION)
+ && x_chance_in_y(you.skills[SK_TRAPS_DOORS], 50))
+ {
+ search_around(false); // Check nonadjacent squares too.
+ }
+
+ if (!crawl_state.arena)
+ stealth = check_stealth();
+
+#ifdef DEBUG_STEALTH
+ // Too annoying for regular diagnostics.
+ mprf(MSGCH_DIAGNOSTICS, "stealth: %d", stealth );
+#endif
+
+ if (you.attribute[ATTR_NOISES])
+ noisy_equipment();
+
+ if (you.attribute[ATTR_SHADOWS])
+ shadow_lantern_effect();
+
+ if (you.unrand_reacts != 0)
+ unrand_reacts();
+
+ if (!crawl_state.arena && one_chance_in(10))
+ {
+ // this is instantaneous
+ if (player_teleport() > 0 && one_chance_in(100 / player_teleport()))
+ you_teleport_now( true );
+ else if (you.level_type == LEVEL_ABYSS && one_chance_in(30))
+ you_teleport_now( false, true ); // to new area of the Abyss
+ }
+
+ if (!crawl_state.arena && env.cgrid(you.pos()) != EMPTY_CLOUD)
+ in_a_cloud();
+
+ if (you.level_type == LEVEL_DUNGEON && you.duration[DUR_TELEPATHY])
+ detect_creatures( 1 + you.duration[DUR_TELEPATHY] / 2, true );
+
+ _decrement_durations();
+
+ const int food_use = player_hunger_rate();
+ if (food_use > 0 && you.hunger >= 40)
+ make_hungry(food_use, true);
+
+ _regenerate_hp_and_mp();
+
+ // If you're wielding a rod, it'll gradually recharge.
+ _recharge_rods();
+
+ viewwindow(true);
+
+ maybe_update_stashes();
+ handle_monsters();
+
+ _check_banished();
+
+ ASSERT(you.time_taken >= 0);
+ // Make sure we don't overflow.
+ ASSERT(DBL_MAX - you.elapsed_time > you.time_taken);
+
+ you.elapsed_time += you.time_taken;
+
+ if (you.synch_time <= you.time_taken)
+ {
+ handle_time(200 + (you.time_taken - you.synch_time));
+ you.synch_time = 200;
+ _check_banished();
+ }
+ else
+ {
+ const long old_synch_time = you.synch_time;
+ you.synch_time -= you.time_taken;
+
+ // Call spawn_random_monsters() more often than the rest of
+ // handle_time() so the spawning rates work out correctly.
+ if (old_synch_time >= 150 && you.synch_time < 150
+ || old_synch_time >= 100 && you.synch_time < 100
+ || old_synch_time >= 50 && you.synch_time < 50)
+ {
+ spawn_random_monsters();
+ }
+ }
+
+ manage_clouds();
+
+ if (you.duration[DUR_FIRE_SHIELD] > 0)
+ manage_fire_shield();
+
+ // Food death check.
+ if (you.is_undead != US_UNDEAD && you.hunger <= 500)
+ {
+ if (!you.cannot_act() && one_chance_in(40))
+ {
+ mpr("You lose consciousness!", MSGCH_FOOD);
+ stop_running();
+
+ you.duration[DUR_PARALYSIS] += 5 + random2(8);
+
+ if (you.duration[DUR_PARALYSIS] > 13)
+ you.duration[DUR_PARALYSIS] = 13;
+ }
+
+ if (you.hunger <= 100)
+ {
+ mpr("You have starved to death.", MSGCH_FOOD);
+ ouch(INSTANT_DEATH, NON_MONSTER, KILLED_BY_STARVATION);
+ }
+ }
+
+ viewwindow(false);
+
+ if (you.cannot_act() && any_messages()
+ && crawl_state.repeat_cmd != CMD_WIZARD)
+ {
+ more();
+ }
+
+#if defined(DEBUG_TENSION) || defined(DEBUG_RELIGION)
+ if (you.religion != GOD_NO_GOD)
+ mprf(MSGCH_DIAGNOSTICS, "TENSION = %d", get_tension());
+#endif
+}
+
+#ifdef DGL_SIMPLE_MESSAGING
+
+static struct stat mfilestat;
+
+static void _show_message_line(std::string line)
+{
+ const std::string::size_type sender_pos = line.find(":");
+ if (sender_pos == std::string::npos)
+ mpr(line.c_str());
+ else
+ {
+ std::string sender = line.substr(0, sender_pos);
+ line = line.substr(sender_pos + 1);
+ trim_string(line);
+ formatted_string fs;
+ fs.textcolor(WHITE);
+ fs.cprintf("%s: ", sender.c_str());
+ fs.textcolor(LIGHTGREY);
+ fs.cprintf("%s", line.c_str());
+ formatted_mpr(fs, MSGCH_PLAIN, 0);
+ take_note(Note( NOTE_MESSAGE, MSGCH_PLAIN, 0,
+ (sender + ": " + line).c_str() ));
+ }
+}
+
+static void _kill_messaging(FILE *mf)
+{
+ if (mf)
+ fclose(mf);
+ SysEnv.have_messages = false;
+ Options.messaging = false;
+}
+
+static void _read_each_message()
+{
+ bool say_got_msg = true;
+ FILE *mf = fopen(SysEnv.messagefile.c_str(), "r+");
+ if (!mf)
+ {
+ mprf(MSGCH_ERROR, "Couldn't read %s: %s", SysEnv.messagefile.c_str(),
+ strerror(errno));
+ _kill_messaging(mf);
+ return;
+ }
+
+ // Read messages, code borrowed from the SIMPLEMAIL patch.
+ char line[120];
+
+ if (!lock_file_handle(mf, F_RDLCK))
+ {
+ mprf(MSGCH_ERROR, "Failed to lock %s: %s", SysEnv.messagefile.c_str(),
+ strerror(errno));
+ _kill_messaging(mf);
+ return;
+ }
+
+ while (fgets(line, sizeof line, mf))
+ {
+ unlock_file_handle(mf);
+
+ const int len = strlen(line);
+ if (len)
+ {
+ if (line[len - 1] == '\n')
+ line[len - 1] = 0;
+
+ if (say_got_msg)
+ {
+ mprf(MSGCH_PROMPT, "Your messages:");
+ say_got_msg = false;
+ }
+
+ _show_message_line(line);
+ }
+
+ if (!lock_file_handle(mf, F_RDLCK))
+ {
+ mprf(MSGCH_ERROR, "Failed to lock %s: %s",
+ SysEnv.messagefile.c_str(),
+ strerror(errno));
+ _kill_messaging(mf);
+ return;
+ }
+ }
+ if (!lock_file_handle(mf, F_WRLCK))
+ {
+ mprf(MSGCH_ERROR, "Unable to write lock %s: %s",
+ SysEnv.messagefile.c_str(),
+ strerror(errno));
+ }
+ if (!ftruncate(fileno(mf), 0))
+ mfilestat.st_mtime = 0;
+ unlock_file_handle(mf);
+ fclose(mf);
+
+ SysEnv.have_messages = false;
+}
+
+static void _read_messages()
+{
+ _read_each_message();
+ update_message_status();
+}
+
+static void _announce_messages()
+{
+ // XXX: We could do a NetHack-like mail daemon here at some point.
+ mprf("Beep! Your pager goes off! Use _ to check your messages.");
+}
+
+static void _check_messages()
+{
+ if (!Options.messaging
+ || SysEnv.have_messages
+ || SysEnv.messagefile.empty()
+ || kbhit()
+ || (SysEnv.message_check_tick++ % DGL_MESSAGE_CHECK_INTERVAL))
+ {
+ return;
+ }
+
+ const bool had_messages = SysEnv.have_messages;
+ struct stat st;
+ if (stat(SysEnv.messagefile.c_str(), &st))
+ {
+ mfilestat.st_mtime = 0;
+ return;
+ }
+
+ if (st.st_mtime > mfilestat.st_mtime)
+ {
+ if (st.st_size)
+ SysEnv.have_messages = true;
+ mfilestat.st_mtime = st.st_mtime;
+ }
+
+ if (SysEnv.have_messages && !had_messages)
+ {
+ _announce_messages();
+ update_message_status();
+ // Recenter the cursor on the player.
+ _center_cursor();
+ }
+}
+#endif
+
+static command_type _get_next_cmd()
+{
+#ifdef DGL_SIMPLE_MESSAGING
+ _check_messages();
+#endif
+
+#if DEBUG_DIAGNOSTICS
+ // Save hunger at start of round for use with hunger "delta-meter"
+ // in output.cc.
+ you.old_hunger = you.hunger;
+#endif
+
+#if DEBUG_ITEM_SCAN
+ debug_item_scan();
+#endif
+#if DEBUG_MONS_SCAN
+ debug_mons_scan();
+#endif
+
+ const time_t before = time(NULL);
+ keycode_type keyin = _get_next_keycode();
+
+ const time_t after = time(NULL);
+
+ // Clamp idle time so that play time is more meaningful.
+ if (after - before > IDLE_TIME_CLAMP)
+ {
+ you.real_time += int(before - you.start_time) + IDLE_TIME_CLAMP;
+ you.start_time = after;
+ }
+
+ if (is_userfunction(keyin))
+ {
+ run_macro(get_userfunction(keyin).c_str());
+ return (CMD_NEXT_CMD);
+ }
+
+ return _keycode_to_command(keyin);
+}
+
+// We handle the synthetic keys, key_to_command() handles the
+// real ones.
+static command_type _keycode_to_command( keycode_type key )
+{
+ switch ( key )
+ {
+#ifdef USE_TILE
+ case CK_MOUSE_CMD: return CMD_NEXT_CMD;
+#endif
+
+ case KEY_MACRO_DISABLE_MORE: return CMD_DISABLE_MORE;
+ case KEY_MACRO_ENABLE_MORE: return CMD_ENABLE_MORE;
+ case KEY_REPEAT_KEYS: return CMD_REPEAT_KEYS;
+
+ default:
+ return key_to_command(key, KMC_DEFAULT);
+ }
+}
+
+static keycode_type _get_next_keycode()
+{
+ keycode_type keyin;
+
+ flush_input_buffer( FLUSH_BEFORE_COMMAND );
+
+ mouse_control mc(MOUSE_MODE_COMMAND);
+ keyin = unmangle_direction_keys(getch_with_command_macros());
+
+ if (!is_synthetic_key(keyin))
+ mesclr();
+
+ return (keyin);
+}
+
+// Check squares adjacent to player for given feature and return how
+// many there are. If there's only one, return the dx and dy.
+static int _check_adjacent(dungeon_feature_type feat, coord_def& delta)
+{
+ int num = 0;
+
+ for (adjacent_iterator ai(you.pos(), false); ai; ++ai)
+ {
+ if (grd(*ai) == feat)
+ {
+ num++;
+ delta = *ai - you.pos();
+ }
+ }
+
+ return num;
+}
+
+// Handles some aspects of untrapping. Returns false if the target is a
+// closed door that will need to be opened.
+static bool _untrap_target(const coord_def move, bool check_confused)
+{
+ const coord_def target = you.pos() + move;
+ monsters* mon = monster_at(target);
+ if (mon && player_can_hit_monster(mon))
+ {
+ if (mon->caught() && mon->friendly()
+ && player_can_open_doors() && !you.confused())
+ {
+ const std::string prompt =
+ make_stringf("Do you want to try to take the net off %s?",
+ mon->name(DESC_NOCAP_THE).c_str());
+
+ if (yesno(prompt.c_str(), true, 'n'))
+ {
+ remove_net_from(mon);
+ return (true);
+ }
+ }
+
+ you.turn_is_over = true;
+ you_attack(mon->mindex(), true);
+
+ if (you.berserk_penalty != NO_BERSERK_PENALTY)
+ you.berserk_penalty = 0;
+
+ return (true);
+ }
+
+ if (find_trap(target) && grd(target) != DNGN_UNDISCOVERED_TRAP)
+ {
+ if (!you.confused())
+ {
+ if (!player_can_open_doors())
+ {
+ mpr("You can't disarm traps in your present form.");
+ return (true);
+ }
+ if (env.cgrid(target) != EMPTY_CLOUD)
+ {
+ mpr("You can't get to that trap right now.");
+ return (true);
+ }
+ }
+
+ // If you're confused, you may attempt it and stumble into the trap.
+ disarm_trap(target);
+ return (true);
+ }
+
+ const dungeon_feature_type feat = grd(target);
+ if (!feat_is_closed_door(feat) || you.confused())
+ {
+ switch (feat)
+ {
+ case DNGN_OPEN_DOOR:
+ _close_door(move); // for convenience
+ return (true);
+ default:
+ {
+ bool do_msg = true;
+
+ // Press trigger/switch/button in wall.
+ if (feat_is_solid(feat))
+ {
+ dgn_event event(DET_WALL_HIT, target);
+ event.arg1 = NON_MONSTER;
+
+ // Listener can veto the event to prevent the "You swing at
+ // nothing" message.
+ do_msg =
+ dungeon_events.fire_vetoable_position_event(event,
+ target);
+ }
+ if (do_msg)
+ mpr("You swing at nothing.");
+ make_hungry(3, true);
+ you.turn_is_over = true;
+ return (true);
+ }
+ }
+ }
+
+ // Else it's a closed door and needs further handling.
+ return (false);
+}
+
+// Opens doors and may also handle untrapping/attacking, etc.
+// If either move_x or move_y are non-zero, the pair carries a specific
+// direction for the door to be opened (eg if you type ctrl + dir).
+static void _open_door(coord_def move, bool check_confused)
+{
+ ASSERT(!crawl_state.arena && !crawl_state.arena_suspended);
+
+ if (you.attribute[ATTR_HELD])
+ {
+ free_self_from_net();
+ you.turn_is_over = true;
+ return;
+ }
+
+ // The player used Ctrl + dir or a variant thereof.
+ if (!move.origin())
+ {
+ if (check_confused && you.confused() && !one_chance_in(3))
+ {
+ do
+ move = coord_def(random2(3) - 1, random2(3) - 1);
+ while (move.origin());
+ }
+ if (_untrap_target(move, check_confused))
+ return;
+ }
+
+ // If we get here, the player either hasn't picked a direction yet,
+ // or the chosen direction actually contains a closed door.
+ if (!player_can_open_doors())
+ {
+ mpr("You can't open doors in your present form.");
+ return;
+ }
+
+ dist door_move;
+
+ // The player hasn't picked a direction yet.
+ if (move.origin())
+ {
+ const int num = _check_adjacent(DNGN_CLOSED_DOOR, move)
+ + _check_adjacent(DNGN_DETECTED_SECRET_DOOR, move);
+
+ if (num == 0)
+ {
+ mpr("There's nothing to open nearby.");
+ return;
+ }
+
+ // If there's only one door to open, don't ask.
+ if (num == 1)
+ door_move.delta = move;
+ else
+ {
+ mpr("Which direction? ", MSGCH_PROMPT);
+ direction(door_move, DIR_DIR);
+
+ if (!door_move.isValid)
+ return;
+ }
+ }
+ else
+ door_move.delta = move;
+
+ if (check_confused && you.confused() && !one_chance_in(3))
+ {
+ do
+ door_move.delta = coord_def(random2(3) - 1, random2(3) - 1);
+ while (door_move.delta.origin());
+ }
+
+ // We got a valid direction.
+ const coord_def doorpos = you.pos() + door_move.delta;
+ const dungeon_feature_type feat = (in_bounds(doorpos) ? grd(doorpos)
+ : DNGN_UNSEEN);
+
+ if (!feat_is_closed_door(feat))
+ {
+ if (you.confused())
+ {
+ mpr("You swing at nothing.");
+ make_hungry(3, true);
+ you.turn_is_over = true;
+ return;
+ }
+ switch (feat)
+ {
+ case DNGN_OPEN_DOOR:
+ mpr("It's already open!");
+ break;
+ default:
+ mpr("There isn't anything that you can open there!");
+ break;
+ }
+ // Don't lose a turn.
+ return;
+ }
+
+ // Finally, open the closed door!
+ std::set<coord_def> all_door;
+ find_connected_range(doorpos, DNGN_CLOSED_DOOR, DNGN_SECRET_DOOR, all_door);
+ const char *adj, *noun;
+ get_door_description(all_door.size(), &adj, &noun);
+
+ if (!(check_confused && you.confused()))
+ {
+ std::string door_open_prompt =
+ env.markers.property_at(doorpos, MAT_ANY, "door_open_prompt");
+
+ bool ignore_exclude = false;
+
+ if (!door_open_prompt.empty())
+ {
+ door_open_prompt += " (y/N)";
+ if (!yesno(door_open_prompt.c_str(), true, 'n', true, false))
+ {
+ if (is_exclude_root(doorpos))
+ canned_msg(MSG_OK);
+ else
+ {
+ if (yesno("Put travel exclusion on door? (Y/n)",
+ true, 'y'))
+ {
+ // Zero radius exclusion right on top of door.
+ set_exclude(doorpos, 0);
+ }
+ }
+ interrupt_activity(AI_FORCE_INTERRUPT);
+ return;
+ }
+ ignore_exclude = true;
+ }
+
+ if (!ignore_exclude && is_exclude_root(doorpos))
+ {
+ std::string prompt =
+ make_stringf("This %s%s is marked as excluded! Open it "
+ "anyway?", adj, noun);
+
+ if (!yesno(prompt.c_str(), true, 'n', true, false))
+ {
+ canned_msg(MSG_OK);
+ interrupt_activity(AI_FORCE_INTERRUPT);
+ return;
+ }
+ }
+ }
+
+ int skill = you.dex
+ + (you.skills[SK_TRAPS_DOORS] + you.skills[SK_STEALTH]) / 2;
+
+ if (you.berserk())
+ {
+ // XXX: Better flavour for larger doors?
+ if (silenced(you.pos()))
+ mprf("The %s%s flies open!", adj, noun);
+ else
+ {
+ mprf(MSGCH_SOUND, "The %s%s flies open with a bang!", adj, noun);
+ noisy(15, you.pos());
+ }
+ }
+ else if (one_chance_in(skill) && !silenced(you.pos()))
+ {
+ mprf(MSGCH_SOUND, "As you open the %s%s, it creaks loudly!",
+ adj, noun);
+ noisy(10, you.pos());
+ }
+ else
+ {
+ const char* verb = (you.airborne() ? "reach down and open"
+ : "open");
+ mprf("You %s the %s%s.", verb, adj, noun);
+ }
+
+ bool seen_secret = false;
+ std::vector<coord_def> excludes;
+ for (std::set<coord_def>::iterator i = all_door.begin();
+ i != all_door.end(); ++i)
+ {
+ const coord_def& dc = *i;
+ // Even if some of the door is out of LOS, we want the entire
+ // door to be updated. Hitting this case requires a really big
+ // door!
+ if (is_terrain_seen(dc))
+ {
+ set_map_knowledge_obj(dc, DNGN_OPEN_DOOR);
+#ifdef USE_TILE
+ env.tile_bk_bg(dc) = TILE_DNGN_OPEN_DOOR;
+#endif
+ if (!seen_secret && grd(dc) == DNGN_SECRET_DOOR)
+ {
+ seen_secret = true;
+ dungeon_feature_type secret
+ = grid_secret_door_appearance(dc);
+ mprf("That %s was a secret door!",
+ feature_description(secret, NUM_TRAPS, false,
+ DESC_PLAIN, false).c_str());
+ }
+ }
+ grd(dc) = DNGN_OPEN_DOOR;
+ if (is_excluded(dc))
+ excludes.push_back(dc);
+ }
+
+ update_exclusion_los(excludes);
+
+ you.turn_is_over = true;
+}
+
+static void _close_door(coord_def move)
+{
+ if (!player_can_open_doors())
+ {
+ mpr("You can't close doors in your present form.");
+ return;
+ }
+
+ if (you.attribute[ATTR_HELD])
+ {
+ mpr("You can't close doors while held in a net.");
+ return;
+ }
+
+ dist door_move;
+
+ // The player hasn't yet told us a direction.
+ if (move.origin())
+ {
+ // If there's only one door to close, don't ask.
+ int num = _check_adjacent(DNGN_OPEN_DOOR, move);
+ if (num == 0)
+ {
+ mpr("There's nothing to close nearby.");
+ return;
+ }
+ else if (num == 1)
+ door_move.delta = move;
+ else
+ {
+ mpr("Which direction? ", MSGCH_PROMPT);
+ direction(door_move, DIR_DIR);
+
+ if (!door_move.isValid)
+ return;
+ }
+ }
+ else
+ door_move.delta = move;
+
+ if (you.confused() && !one_chance_in(3))
+ {
+ do
+ door_move.delta = coord_def(random2(3) - 1, random2(3) - 1);
+ while (door_move.delta.origin());
+ }
+
+ if (door_move.delta.origin())
+ {
+ mpr("You can't close doors on yourself!");
+ return;
+ }
+
+ const coord_def doorpos = you.pos() + door_move.delta;
+ const dungeon_feature_type feat = (in_bounds(doorpos) ? grd(doorpos)
+ : DNGN_UNSEEN);
+
+ if (feat == DNGN_OPEN_DOOR)
+ {
+ std::set<coord_def> all_door;
+ find_connected_identical(doorpos, grd(doorpos), all_door);
+ const char *adj, *noun;
+ get_door_description(all_door.size(), &adj, &noun);
+
+ for (std::set<coord_def>::const_iterator i = all_door.begin();
+ i != all_door.end(); ++i)
+ {
+ const coord_def& dc = *i;
+ if (monsters* mon = monster_at(dc))
+ {
+ // Need to make sure that turn_is_over is set if
+ // creature is invisible.
+ if (!you.can_see(mon))
+ {
+ mprf("Something is blocking the %sway!", noun);
+ you.turn_is_over = true;
+ }
+ else
+ mprf("There's a creature in the %sway!", noun);
+ return;
+ }
+
+ if (igrd(dc) != NON_ITEM)
+ {
+ mprf("There's something blocking the %sway.", noun);
+ return;
+ }
+
+ if (you.pos() == dc)
+ {
+ mprf("There's a thick-headed creature in the %sway!", noun);
+ return;
+ }
+ }
+
+ int skill = you.dex
+ + (you.skills[SK_TRAPS_DOORS] + you.skills[SK_STEALTH]) / 2;
+
+ if (you.berserk())
+ {
+ if (silenced(you.pos()))
+ mprf("You slam the %s%s shut!", adj, noun);
+ else
+ {
+ mprf(MSGCH_SOUND, "You slam the %s%s shut with a bang!",
+ adj, noun);
+ noisy(25, you.pos());
+ }
+ }
+ else if (one_chance_in(skill) && !silenced(you.pos()))
+ {
+ mprf(MSGCH_SOUND, "As you close the %s%s, it creaks loudly!",
+ adj, noun);
+ noisy(10, you.pos());
+ }
+ else
+ {
+ const char* verb = you.airborne() ? "reach down and close"
+ : "close";
+
+ mprf("You %s the %s%s.", verb, adj, noun);
+ }
+
+ std::vector<coord_def> excludes;
+ for (std::set<coord_def>::const_iterator i = all_door.begin();
+ i != all_door.end(); ++i)
+ {
+ const coord_def& dc = *i;
+ // Once opened, formerly secret doors become normal doors.
+ grd(dc) = DNGN_CLOSED_DOOR;
+
+ // Even if some of the door is out of LOS once it's closed
+ // (or even if some of it is out of LOS when it's open), we
+ // want the entire door to be updated.
+ if (is_terrain_seen(dc))
+ {
+ set_map_knowledge_obj(dc, DNGN_CLOSED_DOOR);
+#ifdef USE_TILE
+ env.tile_bk_bg(dc) = TILE_DNGN_CLOSED_DOOR;
+#endif
+ }
+ if (is_excluded(dc))
+ excludes.push_back(dc);
+ }
+
+ update_exclusion_los(excludes);
+
+ you.turn_is_over = true;
+ }
+ else if (you.confused())
+ _open_door(door_move.delta);
+ else
+ {
+ switch (feat)
+ {
+ case DNGN_CLOSED_DOOR:
+ case DNGN_DETECTED_SECRET_DOOR:
+ mpr("It's already closed!");
+ break;
+ default:
+ mpr("There isn't anything that you can close there!");
+ break;
+ }
+ }
+}
+
+// Initialise a whole lot of stuff...
+// Returns true if a new character.
+static bool _initialise(void)
+{
+ Options.fixup_options();
+
+ // Read the options the player used last time they created a new
+ // character.
+ read_startup_prefs();
+
+ you.symbol = '@';
+ you.colour = LIGHTGREY;
+
+ seed_rng();
+ get_typeid_array().init(ID_UNKNOWN_TYPE);
+ init_char_table(Options.char_set);
+ init_show_table();
+ init_monster_symbols();
+ init_spell_descs(); // This needs to be way up top. {dlb}
+ init_mon_name_cache();
+ init_mons_spells();
+
+ // init_item_name_cache() needs to be redone after init_char_table()
+ // and init_show_table() have been called, so that the glyphs will
+ // be set to use with item_names_by_glyph_cache.
+ init_item_name_cache();
+
+ msg::initialise_mpr_streams();
+
+ // Init item array.
+ for (int i = 0; i < MAX_ITEMS; ++i)
+ init_item(i);
+
+ // Empty messaging string.
+ info[0] = 0;
+
+ for (int i = 0; i < MAX_MONSTERS; ++i)
+ menv[i].reset();
+
+ igrd.init(NON_ITEM);
+ mgrd.init(NON_MONSTER);
+ env.map_knowledge.init(map_cell());
+ env.pgrid.init(0);
+
+ you.unique_creatures.init(false);
+ you.unique_items.init(UNIQ_NOT_EXISTS);
+
+ // Set up the Lua interpreter for the dungeon builder.
+ init_dungeon_lua();
+
+ // Initialise internal databases.
+ databaseSystemInit();
+
+ init_feat_desc_cache();
+ init_spell_name_cache();
+ init_spell_rarities();
+
+ // Read special levels and vaults.
+ read_maps();
+
+ cio_init();
+
+ // System initialisation stuff.
+ textbackground(0);
+
+ clrscr();
+
+#ifdef DEBUG_DIAGNOSTICS
+ if (crawl_state.map_stat_gen)
+ {
+ generate_map_stats();
+ end(0, false);
+ }
+#endif
+
+ if (crawl_state.test)
+ {
+#if DEBUG_TESTS && !DEBUG
+#error "DEBUG must be defined if DEBUG_TESTS is defined"
+#endif
+
+#if DEBUG_DIAGNOSTICS || DEBUG_TESTS
+#ifdef USE_TILE
+ init_player_doll();
+ tiles.initialise_items();
+#endif
+ Options.show_more_prompt = false;
+ makeitem_tests();
+ crawl_tests::run_tests(true);
+ // Superfluous, just to make it clear that this is the end of
+ // the line.
+ end(0, false);
+#else
+ end(1, false, "Non-debug Crawl cannot run tests. "
+ "Please use a debug build (defined FULLDEBUG, DEBUG_DIAGNOSTIC "
+ "or DEBUG_TESTS)");
+#endif
+ }
+
+
+ if (crawl_state.arena)
+ {
+ run_map_preludes();
+ initialise_item_descriptions();
+#ifdef USE_TILE
+ tiles.initialise_items();
+#endif
+
+ run_arena();
+ end(0, false);
+ }
+
+ // Sets up a new game.
+ const bool newc = new_game();
+ if (!newc)
+ restore_game();
+
+ // Fix the mutation definitions for the species we're playing.
+ fixup_mutations();
+
+ // Load macros
+ macro_init();
+
+ crawl_state.need_save = true;
+
+ calc_hp();
+ calc_mp();
+
+ run_map_preludes();
+
+ if (newc && you.char_direction == GDT_GAME_START)
+ {
+ // Chaos Knights of Lugonu start out in the Abyss.
+ you.level_type = LEVEL_ABYSS;
+ you.entry_cause = EC_UNKNOWN;
+ }
+
+ load(you.entering_level ? you.transit_stair : DNGN_STONE_STAIRS_DOWN_I,
+ you.entering_level ? LOAD_ENTER_LEVEL :
+ newc ? LOAD_START_GAME : LOAD_RESTART_GAME,
+ NUM_LEVEL_AREA_TYPES, -1, you.where_are_you);
+
+ if (newc && you.char_direction == GDT_GAME_START)
+ {
+ // Randomise colours properly for the Abyss.
+ init_pandemonium();
+ }
+
+#if DEBUG_DIAGNOSTICS
+ // Debug compiles display a lot of "hidden" information, so we auto-wiz.
+ you.wizard = true;
+#endif
+
+ init_properties();
+ burden_change();
+ make_hungry(0, true);
+
+ you.redraw_strength = true;
+ you.redraw_intelligence = true;
+ you.redraw_dexterity = true;
+ you.redraw_armour_class = true;
+ you.redraw_evasion = true;
+ you.redraw_experience = true;
+ you.redraw_quiver = true;
+ you.wield_change = true;
+
+ // Start timer on session.
+ you.start_time = time( NULL );
+
+#ifdef CLUA_BINDINGS
+ clua.runhook("chk_startgame", "b", newc);
+ std::string yname = you.your_name; // XXX: what's this for?
+ read_init_file(true);
+ Options.fixup_options();
+ you.your_name = yname;
+
+ // In case Lua changed the character set.
+ init_char_table(Options.char_set);
+ init_show_table();
+ init_monster_symbols();
+#endif
+
+#ifdef USE_TILE
+ // Override inventory weights options for tiled menus.
+ if (Options.tile_menu_icons && Options.show_inventory_weights)
+ Options.show_inventory_weights = false;
+
+ init_player_doll();
+
+ tiles.resize();
+#endif
+
+ draw_border();
+ new_level();
+ update_turn_count();
+
+ trackers_init_new_level(false);
+
+ // Reset lava/water nearness check to unknown, so it'll be
+ // recalculated for the next monster that tries to reach us.
+ you.lava_in_sight = you.water_in_sight = -1;
+
+ // Set vision radius to player's current vision.
+ set_los_radius(you.current_vision);
+ init_exclusion_los();
+
+ if (newc) // start a new game
+ {
+ you.friendly_pickup = Options.default_friendly_pickup;
+
+ // Mark items in inventory as of unknown origin.
+ origin_set_inventory(origin_set_unknown);
+
+ // For a new game, wipe out monsters in LOS, and
+ // for new tutorial games also the items.
+ zap_los_monsters(Options.tutorial_events[TUT_SEEN_FIRST_OBJECT]);
+
+ // For a newly started tutorial, turn secret doors into normal ones.
+ if (Options.tutorial_left)
+ tutorial_zap_secret_doors();
+ }
+
+#ifdef USE_TILE
+ tiles.initialise_items();
+ // Must re-run as the feature table wasn't initialised yet.
+ TileNewLevel(newc);
+#endif
+
+ set_cursor_enabled(false);
+ maybe_update_stashes();
+
+ // This just puts the view up for the first turn.
+ viewwindow(false);
+
+ activate_notes(true);
+
+ add_key_recorder(&repeat_again_rec);
+
+ return (newc);
+}
+
+// An attempt to tone down berserk a little bit. -- bwross
+//
+// This function does the accounting for not attacking while berserk
+// This gives a triangular number function for the additional penalty
+// Turn: 1 2 3 4 5 6 7 8
+// Penalty: 1 3 6 10 15 21 28 36
+//
+// Total penalty (including the standard one during upkeep is:
+// 2 5 9 14 20 27 35 44
+//
+static void _do_berserk_no_combat_penalty(void)
+{
+ // Butchering/eating a corpse will maintain a blood rage.
+ const int delay = current_delay_action();
+ if (delay == DELAY_BUTCHER || delay == DELAY_EAT)
+ return;
+
+ if (you.berserk_penalty == NO_BERSERK_PENALTY)
+ return;
+
+ if (you.berserk())
+ {
+ you.berserk_penalty++;
+
+ switch (you.berserk_penalty)
+ {
+ case 2:
+ mpr("You feel a strong urge to attack something.", MSGCH_DURATION);
+ break;
+ case 4:
+ mpr("You feel your anger subside.", MSGCH_DURATION);
+ break;
+ case 6:
+ mpr("Your blood rage is quickly leaving you.", MSGCH_DURATION);
+ break;
+ }
+
+ // I do these three separately, because the might and
+ // haste counters can be different.
+ you.duration[DUR_BERSERKER] -= you.berserk_penalty;
+ if (you.duration[DUR_BERSERKER] < 1)
+ you.duration[DUR_BERSERKER] = 1;
+
+ you.duration[DUR_MIGHT] -= you.berserk_penalty;
+ if (you.duration[DUR_MIGHT] < 1)
+ you.duration[DUR_MIGHT] = 1;
+
+ you.duration[DUR_HASTE] -= you.berserk_penalty;
+ if (you.duration[DUR_HASTE] < 1)
+ you.duration[DUR_HASTE] = 1;
+ }
+ return;
+} // end do_berserk_no_combat_penalty()
+
+
+// Called when the player moves by walking/running. Also calls attack
+// function etc when necessary.
+static void _move_player(int move_x, int move_y)
+{
+ _move_player( coord_def(move_x, move_y) );
+}
+
+static void _move_player(coord_def move)
+{
+ ASSERT(!crawl_state.arena && !crawl_state.arena_suspended);
+
+ bool attacking = false;
+ bool moving = true; // used to prevent eventual movement (swap)
+ bool swap = false;
+
+ if (you.attribute[ATTR_HELD])
+ {
+ free_self_from_net();
+ you.turn_is_over = true;
+ return;
+ }
+
+ // When confused, sometimes make a random move
+ if (you.confused())
+ {
+ dungeon_feature_type dangerous = DNGN_FLOOR;
+ for (adjacent_iterator ai(you.pos(), false); ai; ++ai)
+ {
+ if (is_feat_dangerous(grd(*ai))
+ && (dangerous == DNGN_FLOOR || grd(*ai) == DNGN_LAVA))
+ {
+ dangerous = grd(*ai);
+ }
+ }
+ if (dangerous != DNGN_FLOOR)
+ {
+ std::string prompt = "Are you sure you want to move while confused "
+ "and next to ";
+ prompt += (dangerous == DNGN_LAVA ? "lava"
+ : "deep water");
+ prompt += "? ";
+
+ if (!yesno(prompt.c_str(), false, 'n'))
+ {
+ canned_msg(MSG_OK);
+ return;
+ }
+ }
+
+ if (!one_chance_in(3))
+ {
+ move.x = random2(3) - 1;
+ move.y = random2(3) - 1;
+ you.reset_prev_move();
+ }
+
+ const coord_def& new_targ = you.pos() + move;
+ if (!in_bounds(new_targ) || !you.can_pass_through(new_targ))
+ {
+ you.turn_is_over = true;
+ mpr("Ouch!");
+ apply_berserk_penalty = true;
+ crawl_state.cancel_cmd_repeat();
+
+ return;
+ }
+ }
+
+ const coord_def& targ = you.pos() + move;
+
+ // You can't walk out of bounds!
+ if (!in_bounds(targ))
+ return;
+
+ const dungeon_feature_type targ_grid = grd(targ);
+
+ monsters* targ_monst = monster_at(targ);
+ if (fedhas_passthrough(targ_monst))
+ {
+ // Moving on a plant takes 1.5 x normal move delay. We
+ // will print a message about it but only when moving
+ // from open space->plant (hopefully this will cut down
+ // on the message spam).
+ you.time_taken = div_rand_round(you.time_taken * 3, 2);
+
+ monsters * current = monster_at(you.pos());
+ if(!current || !fedhas_passthrough(current))
+ {
+ // Probably need better messages. -cao
+ if(mons_genus(targ_monst->type) == MONS_FUNGUS)
+ {
+ mprf("You walk carefully through the fungus.");
+ }
+ else
+ mprf("You walk carefully through the plants.");
+ }
+ targ_monst = NULL;
+ }
+
+ const bool targ_pass = you.can_pass_through(targ);
+
+ // You can swap places with a friendly or good neutral monster if
+ // you're not confused, or if both of you are inside a sanctuary.
+ const bool can_swap_places = targ_monst
+ && !mons_is_stationary(targ_monst)
+ && (targ_monst->wont_attack()
+ && !you.confused()
+ || is_sanctuary(you.pos())
+ && is_sanctuary(targ));
+
+ // You cannot move away from a mermaid but you CAN fight monsters on
+ // neighbouring squares.
+ monsters *beholder = NULL;
+ if (!you.confused())
+ beholder = you.get_beholder(targ);
+
+ if (you.running.check_stop_running())
+ {
+ // [ds] Do we need this? Shouldn't it be false to start with?
+ you.turn_is_over = false;
+ return;
+ }
+
+ coord_def mon_swap_dest;
+
+ if (targ_monst && !targ_monst->submerged())
+ {
+ if (can_swap_places && !beholder)
+ {
+ if (swap_check(targ_monst, mon_swap_dest))
+ swap = true;
+ else
+ moving = false;
+ }
+ else if (!can_swap_places) // attack!
+ {
+ // XXX: Moving into a normal wall does nothing and uses no
+ // turns or energy, but moving into a wall which contains
+ // an invisible monster attacks the monster, thus allowing
+ // the player to figure out which adjacent wall an invis
+ // monster is in "for free".
+ you.turn_is_over = true;
+ you_attack(targ_monst->mindex(), true);
+
+ // We don't want to create a penalty if there isn't
+ // supposed to be one.
+ if (you.berserk_penalty != NO_BERSERK_PENALTY)
+ you.berserk_penalty = 0;
+
+ attacking = true;
+ }
+ }
+
+ if (!attacking && targ_pass && moving && !beholder)
+ {
+ you.time_taken *= player_movement_speed();
+ you.time_taken /= 10;
+ if (!move_player_to_grid(targ, true, false, false, swap))
+ return;
+
+ if (swap)
+ swap_places(targ_monst, mon_swap_dest);
+
+ you.prev_move = move;
+ move.reset();
+ you.turn_is_over = true;
+ request_autopickup();
+ }
+
+ // BCR - Easy doors single move
+ if (Options.easy_open && !attacking && feat_is_closed_door(targ_grid))
+ {
+ _open_door(move.x, move.y, false);
+ you.prev_move = move;
+ }
+ else if (!targ_pass && !attacking)
+ {
+ if (grd(targ) == DNGN_OPEN_SEA)
+ mpr("You can't go out to sea!");
+
+ stop_running();
+ move.reset();
+ you.turn_is_over = false;
+ crawl_state.cancel_cmd_repeat();
+ return;
+ }
+ else if (beholder && !attacking)
+ {
+ mprf("You cannot move away from %s!",
+ beholder->name(DESC_NOCAP_THE, true).c_str());
+ return;
+ }
+
+ if (you.running == RMODE_START)
+ you.running = RMODE_CONTINUE;
+
+ if (you.level_type == LEVEL_ABYSS
+ && (you.pos().x <= 15 || you.pos().x >= (GXM - 16)
+ || you.pos().y <= 15 || you.pos().y >= (GYM - 16)))
+ {
+ area_shift();
+ if (you.pet_target != MHITYOU)
+ you.pet_target = MHITNOT;
+
+#if DEBUG_DIAGNOSTICS
+ mpr("Shifting.", MSGCH_DIAGNOSTICS);
+ int j = 0;
+ for (int i = 0; i < MAX_ITEMS; ++i)
+ if (mitm[i].is_valid())
+ ++j;
+
+ mprf(MSGCH_DIAGNOSTICS, "Number of items present: %d", j);
+
+ j = 0;
+ for (monster_iterator mi; mi; ++mi)
+ ++j;
+
+ mprf(MSGCH_DIAGNOSTICS, "Number of monsters present: %d", j);
+ mprf(MSGCH_DIAGNOSTICS, "Number of clouds present: %d", env.cloud_no);
+#endif
+ }
+
+ apply_berserk_penalty = !attacking;
+
+ if (!attacking && you.religion == GOD_CHEIBRIADOS && one_chance_in(10)
+ && player_equip_ego_type(EQ_BOOTS, SPARM_RUNNING))
+ {
+ did_god_conduct(DID_HASTY, 1, true);
+ }
+}
+
+
+static int _get_num_and_char_keyfun(int &ch)
+{
+ if (ch == CK_BKSP || isdigit(ch) || ch >= 128)
+ return 1;
+
+ return -1;
+}
+
+static int _get_num_and_char(const char* prompt, char* buf, int buf_len)
+{
+ if (prompt != NULL)
+ mpr(prompt, MSGCH_PROMPT);
+
+ line_reader reader(buf, buf_len);
+
+ reader.set_keyproc(_get_num_and_char_keyfun);
+
+ return reader.read_line(true);
+}
+
+static void _setup_cmd_repeat()
+{
+ if (is_processing_macro())
+ {
+ flush_input_buffer(FLUSH_ABORT_MACRO);
+ crawl_state.cancel_cmd_again();
+ crawl_state.cancel_cmd_repeat();
+ return;
+ }
+
+ ASSERT(!crawl_state.is_repeating_cmd());
+
+ char buf[80];
+
+ // Function ensures that the buffer contains only digits.
+ int ch = _get_num_and_char("Number of times to repeat, then command key: ",
+ buf, 80);
+
+ if (ch == ESCAPE)
+ {
+ // This *might* be part of the trigger for a macro.
+ keyseq trigger;
+ trigger.push_back(ch);
+
+ if (get_macro_buf_size() == 0)
+ {
+ // Was just a single ESCAPE key, so not a macro trigger.
+ canned_msg( MSG_OK );
+ crawl_state.cancel_cmd_again();
+ crawl_state.cancel_cmd_repeat();
+ flush_input_buffer(FLUSH_REPLAY_SETUP_FAILURE);
+ return;
+ }
+ ch = getchm();
+ trigger.push_back(ch);
+
+ // Now that we have the entirety of the (possible) macro trigger,
+ // clear out the keypress recorder so that we won't have recorded
+ // the trigger twice.
+ repeat_again_rec.clear();
+
+ insert_macro_into_buff(trigger);
+
+ ch = getchm();
+ if (ch == ESCAPE)
+ {
+ if (get_macro_buf_size() > 0)
+ // User pressed an Alt key which isn't bound to a macro.
+ mpr("That key isn't bound to a macro.");
+ else
+ // Wasn't a macro trigger, just an ordinary escape.
+ canned_msg( MSG_OK );
+
+ crawl_state.cancel_cmd_again();
+ crawl_state.cancel_cmd_repeat();
+ flush_input_buffer(FLUSH_REPLAY_SETUP_FAILURE);
+ return;
+ }
+ // *WAS* a macro trigger, keep going.
+ }
+
+ if (strlen(buf) == 0)
+ {
+ mpr("You must enter the number of times for the command to repeat.");
+
+ crawl_state.cancel_cmd_again();
+ crawl_state.cancel_cmd_repeat();
+ flush_input_buffer(FLUSH_REPLAY_SETUP_FAILURE);
+
+ return;
+ }
+
+ int count = atoi(buf);
+
+ if (crawl_state.doing_prev_cmd_again)
+ count = crawl_state.prev_cmd_repeat_goal;
+
+ if (count <= 0)
+ {
+ canned_msg( MSG_OK );
+ crawl_state.cancel_cmd_again();
+ crawl_state.cancel_cmd_repeat();
+ flush_input_buffer(FLUSH_REPLAY_SETUP_FAILURE);
+ return;
+ }
+
+ if (crawl_state.doing_prev_cmd_again)
+ {
+ // If a "do previous command again" caused a command
+ // repetition to be redone, the keys to be repeated are
+ // already in the key recording buffer, so we just need to
+ // discard all the keys saying how many times the command
+ // should be repeated.
+ do
+ {
+ repeat_again_rec.keys.pop_front();
+ }
+ while (repeat_again_rec.keys.size() > 0
+ && repeat_again_rec.keys[0] != ch);
+
+ repeat_again_rec.keys.pop_front();
+ }
+
+ // User can type space or enter and then the command key, in case
+ // they want to repeat a command bound to a number key.
+ c_input_reset(true);
+ if (ch == ' ' || ch == CK_ENTER)
+ {
+ if (!crawl_state.doing_prev_cmd_again)
+ repeat_again_rec.keys.pop_back();
+
+ mpr("Enter command to be repeated: ");
+ // Enable the cursor to read input. The cursor stays on while
+ // the command is being processed, so subsidiary prompts
+ // shouldn't need to turn it on explicitly.
+ cursor_control con(true);
+
+ crawl_state.waiting_for_command = true;
+
+ ch = _get_next_keycode();
+
+ crawl_state.waiting_for_command = false;
+ }
+
+ command_type cmd = _keycode_to_command( (keycode_type) ch);
+
+ if (cmd != CMD_MOUSE_MOVE)
+ c_input_reset(false);
+
+ if (!is_processing_macro() && !_cmd_is_repeatable(cmd))
+ {
+ crawl_state.cancel_cmd_again();
+ crawl_state.cancel_cmd_repeat();
+ flush_input_buffer(FLUSH_REPLAY_SETUP_FAILURE);
+ return;
+ }
+
+ if (!crawl_state.doing_prev_cmd_again && cmd != CMD_PREV_CMD_AGAIN)
+ crawl_state.prev_cmd_keys = repeat_again_rec.keys;
+
+ if (is_processing_macro())
+ {
+ // Put back in first key of the expanded macro, which
+ // get_next_keycode() fetched
+ repeat_again_rec.paused = true;
+ macro_buf_add(ch, true);
+
+ // If we're repeating a macro, get rid the keys saying how
+ // many times to repeat, because the way that macros are
+ // repeated means that the number keys will be repeated if
+ // they aren't discarded.
+ keyseq &keys = repeat_again_rec.keys;
+ ch = keys[keys.size() - 1];
+ while (isdigit(ch) || ch == ' ' || ch == CK_ENTER)
+ {
+ keys.pop_back();
+ ASSERT(keys.size() > 0);
+ ch = keys[keys.size() - 1];
+ }
+ }
+
+ repeat_again_rec.paused = false;
+ // Discard the setup for the command repetition, since what's
+ // going to be repeated is yet to be typed, except for the fist
+ // key typed which has to be put back in (unless we're repeating a
+ // macro, in which case everything to be repeated is already in
+ // the macro buffer).
+ if (!is_processing_macro())
+ {
+ repeat_again_rec.clear();
+ macro_buf_add(ch, crawl_state.doing_prev_cmd_again);
+ }
+
+ crawl_state.cmd_repeat_start = true;
+ crawl_state.cmd_repeat_count = 0;
+ crawl_state.repeat_cmd = cmd;
+ crawl_state.cmd_repeat_goal = count;
+ crawl_state.prev_cmd_repeat_goal = count;
+ crawl_state.prev_repetition_turn = you.num_turns;
+
+ crawl_state.cmd_repeat_started_unsafe = !i_feel_safe();
+
+ crawl_state.input_line_strs.clear();
+}
+
+static void _do_prev_cmd_again()
+{
+ if (is_processing_macro())
+ {
+ mpr("Can't re-do previous command from within a macro.");
+ flush_input_buffer(FLUSH_ABORT_MACRO);
+ crawl_state.cancel_cmd_again();
+ crawl_state.cancel_cmd_repeat();
+ return;
+ }
+
+ if (crawl_state.prev_cmd == CMD_NO_CMD)
+ {
+ mpr("No previous command to re-do.");
+ crawl_state.cancel_cmd_again();
+ crawl_state.cancel_cmd_repeat();
+ repeat_again_rec.clear();
+ return;
+ }
+
+ ASSERT(!crawl_state.doing_prev_cmd_again
+ || (crawl_state.is_repeating_cmd()
+ && crawl_state.repeat_cmd == CMD_PREV_CMD_AGAIN));
+
+ crawl_state.doing_prev_cmd_again = true;
+ repeat_again_rec.paused = false;
+
+ if (crawl_state.prev_cmd == CMD_REPEAT_CMD)
+ {
+ crawl_state.cmd_repeat_start = true;
+ crawl_state.cmd_repeat_count = 0;
+ crawl_state.cmd_repeat_goal = crawl_state.prev_cmd_repeat_goal;
+ crawl_state.prev_repetition_turn = you.num_turns;
+ }
+
+ const keyseq &keys = crawl_state.prev_cmd_keys;
+ ASSERT(keys.size() > 0);
+
+ // Insert keys at front of input buffer, rather than at the end,
+ // since if the player holds down the "`" key, then the buffer
+ // might get two "`" in a row, and if the keys to be replayed go after
+ // the second "`" then we get an assertion.
+ macro_buf_add(keys, true);
+
+ bool was_doing_repeats = crawl_state.is_repeating_cmd();
+
+ _input();
+
+ // crawl_state.doing_prev_cmd_again can be set to false
+ // while input() does its stuff if something causes
+ // crawl_state.cancel_cmd_again() to be called.
+ while (!was_doing_repeats && crawl_state.is_repeating_cmd()
+ && crawl_state.doing_prev_cmd_again)
+ {
+ _input();
+ }
+
+ if (!was_doing_repeats && crawl_state.is_repeating_cmd()
+ && !crawl_state.doing_prev_cmd_again)
+ {
+ crawl_state.cancel_cmd_repeat();
+ }
+
+ crawl_state.doing_prev_cmd_again = false;
+}
+
+static void _update_replay_state()
+{
+ if (crawl_state.is_repeating_cmd())
+ {
+ // First repeat is to copy down the keys the user enters,
+ // grab them so we can go on autopilot for the remaining
+ // iterations.
+ if (crawl_state.cmd_repeat_start)
+ {
+ ASSERT(repeat_again_rec.keys.size() > 0);
+
+ crawl_state.cmd_repeat_start = false;
+ crawl_state.repeat_cmd_keys = repeat_again_rec.keys;
+
+ // Setting up the "previous command key sequence"
+ // for a repeated command is different from normal,
+ // since in addition to all of the keystrokes for
+ // the command, it needs the repeat command plus the
+ // number of repeats at the very beginning of the
+ // sequence.
+ if (!crawl_state.doing_prev_cmd_again)
+ {
+ keyseq &prev = crawl_state.prev_cmd_keys;
+ keyseq &curr = repeat_again_rec.keys;
+
+ if (is_processing_macro())
+ prev = curr;
+ else
+ {
+ // Skip first key, because that's command key that's
+ // being repeated, which crawl_state.prev_cmd_keys
+ // aleardy contains.
+ keyseq::iterator begin = curr.begin();
+ begin++;
+
+ prev.insert(prev.end(), begin, curr.end());
+ }
+ }
+
+ repeat_again_rec.paused = true;
+ macro_buf_add(KEY_REPEAT_KEYS);
+ }
+ }
+
+ if (!crawl_state.is_replaying_keys() && !crawl_state.cmd_repeat_start
+ && crawl_state.prev_cmd != CMD_NO_CMD)
+ {
+ if (repeat_again_rec.keys.size() > 0)
+ crawl_state.prev_cmd_keys = repeat_again_rec.keys;
+ }
+
+ if (!is_processing_macro())
+ repeat_again_rec.clear();
+}
+
+
+static void _compile_time_asserts()
+{
+ // Check that the numbering comments in enum.h haven't been
+ // disturbed accidentally.
+ COMPILE_CHECK(SK_UNARMED_COMBAT == 18 , c1);
+ COMPILE_CHECK(SK_EVOCATIONS == 38 , c2);
+ COMPILE_CHECK(SP_VAMPIRE == 30 , c3);
+ COMPILE_CHECK(SPELL_DEBUGGING_RAY == 103 , c4);
+ COMPILE_CHECK(SPELL_RETURNING_AMMUNITION == 162 , c5);
+ COMPILE_CHECK(NUM_SPELLS == 211 , c6);
+
+ //jmf: NEW ASSERTS: we ought to do a *lot* of these
+ COMPILE_CHECK(NUM_SPECIES < SP_UNKNOWN , c7);
+ COMPILE_CHECK(NUM_JOBS < JOB_UNKNOWN , c8);
+
+ // Make sure there's enough room in you.unique_items to hold all
+ // the unrandarts.
+ COMPILE_CHECK(NO_UNRANDARTS < MAX_UNRANDARTS, c9);
+
+ // Non-artefact brands and unrandart indexes both go into
+ // item.special, so make sure they don't overlap.
+ COMPILE_CHECK((int) NUM_SPECIAL_WEAPONS < (int) UNRAND_START, c10);
+
+ // Also some runtime stuff; I don't know if the order of branches[]
+ // needs to match the enum, but it currently does.
+ for (int i = 0; i < NUM_BRANCHES; ++i)
+ ASSERT(branches[i].id == i);
+}