/* * File: main.cc * Summary: Main entry point, event loop, and some initialization functions * Written by: Linley Henzell */ #include "AppHdr.h" #include #include // I don't seem to need values.h for VACPP.. #if !defined(__IBMCPP__) #include #endif #ifdef DEBUG // this contains the DBL_MAX constant #include #endif #include #include #include #include #include #include #include #include #ifdef USE_UNIX_SIGNALS #include #endif #include "externs.h" #include "abl-show.h" #include "abyss.h" #include "areas.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 "coord.h" #include "coordit.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 "env.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-stuff.h" #include "mon-util.h" #include "mutation.h" #include "newgame.h" #include "ng-init.h" #include "notes.h" #include "options.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 "species.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 "transform.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 #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(); // Call again to make Ctrl-X work correctly for items // in los on turn 0. maybe_update_stashes(); #ifdef USE_TILE viewwindow(false); #endif 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 (Tutorial.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(); cursor_control ccon(!Options.use_fake_player_cursor); 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 character name"); puts(" -species preselect race (by letter, abbreviation, or name)"); puts(" -job preselect class (by letter, abbreviation, or name)"); puts(" -plain don't use IBM extended characters"); puts(" -dir crawl directory"); puts(" -rc init file name"); puts(" -rcdir directory that contains (included) rc files"); puts(" -morgue directory to save character dumps"); puts(" -macro directory to save/find macro.txt"); puts(" -version Crawl version (and compilation info)"); puts(" -save-version Save file version for the given player"); 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 scorefile to report on"); puts(""); puts("Arena options: (Stage a tournament between various monsters.)"); puts(" -arena \" v arena:\""); #if DEBUG_DIAGNOSTICS puts(""); puts(" -test run test cases in ./test"); puts(" -script run script matching in ./scripts"); #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. Tutorial.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('B'): you.teleport(true, false, true); break; case CONTROL('D'): wizard_edit_durations(); break; case CONTROL('F'): debug_fight_statistics(false, true); break; case CONTROL('G'): debug_ghosts(); 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 CONTROL('X'): debug_xom_effects(); 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': case 'z': wizard_cast_spec_spell(); break; case '(': case ')': wizard_create_feature(); break; case ':': wizard_list_branches(); break; case '{': wizard_map_level(); break; case '}': wizard_reveal_traps(); break; case '@': wizard_set_stats(); break; case '^': wizard_gain_piety(); break; case '_': wizard_get_religion(); break; case '-': wizard_get_god_gift(); 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 = FULL_EXP_POOL; 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': { int result = 0; do { if (you.religion == GOD_XOM) result = xom_acts(abs(you.piety - HALF_MAX_PIETY)); else result = xom_acts(coinflip(), random_range(0, HALF_MAX_PIETY)); } while (result == 0); 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 Wizard 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); cursor_control con(true); 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 (Tutorial.tutorial_events[TUT_SHIFT_RUN] && mode == RMODE_START) Tutorial.tutorial_events[TUT_SHIFT_RUN] = false; if (i_feel_safe(true)) you.running.initialise(dir, mode); } 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: case CMD_NO_CMD_DEFAULT: 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 (Tutorial.tutorial_left) { Tutorial.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 && Tutorial.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 && Tutorial.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 && Tutorial.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 (Tutorial.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()) set_more_autoclear(false); 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); #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; if (you.duration[DUR_MISLED]) { mpr("Away from their source, illusions no longer mislead you.", MSGCH_DURATION); you.duration[DUR_MISLED] = 0; } 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 (you.duration[DUR_MISLED]) { mpr("Away from their source, illusions no longer mislead you.", MSGCH_DURATION); you.duration[DUR_MISLED] = 0; } 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; { cursor_control con(true); 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 (Tutorial.tut_stashes) Tutorial.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, false); 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 (Tutorial.tutorial_left) Tutorial.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 (Tutorial.tut_travel) Tutorial.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_prompt(); 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; } else if (you.level_type == LEVEL_LABYRINTH) { mpr("No exploration algorithm can help you here."); break; } // Start exploring start_explore(Options.explore_greedy); break; case CMD_DISPLAY_MAP: if (Tutorial.tutorial_events[TUT_MAP_VIEW]) Tutorial.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 ? 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 (Tutorial.tutorial_left) { std::string msg = "Unknown command. (For a list of commands type " "?\?.)"; 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, int delay, 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); int old_dur = you.duration[dur]; you.duration[dur] -= delay; if (you.duration[dur] < 0) you.duration[dur] = 0; // Did we cross the mid point? (No longer likely to hit it exactly) -cao if (you.duration[dur] <= midpoint && old_dur > midpoint) { if (midmsg) mpr(midmsg, chan); you.duration[dur] -= midloss * BASELINE_DELAY; } // 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() { int delay = you.time_taken; if (wearing_amulet(AMU_THE_GOURMAND)) { if (you.duration[DUR_GOURMAND] < GOURMAND_MAX && coinflip()) you.duration[DUR_GOURMAND] += delay; } else you.duration[DUR_GOURMAND] = 0; if (you.duration[DUR_ICEMAIL_DEPLETED] > 0) { if(delay > you.duration[DUR_ICEMAIL_DEPLETED]) you.duration[DUR_ICEMAIL_DEPLETED] = 0; else you.duration[DUR_ICEMAIL_DEPLETED] -= delay; 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, delay)) go_berserk(false); if (_decrement_a_duration(DUR_SLEEP, delay)) you.awake(); dec_napalm_player(delay); if (_decrement_a_duration(DUR_ICY_ARMOUR, delay, "Your icy armour evaporates.", coinflip(), "Your icy armour starts to melt.")) { you.redraw_armour_class = true; } if (_decrement_a_duration(DUR_SILENCE, delay, "Your hearing returns.")) you.attribute[ATTR_WAS_SILENCED] = 0; _decrement_a_duration(DUR_REPEL_MISSILES, delay, "You feel less protected from missiles.", coinflip(), "Your repel missiles spell is about to expire..."); _decrement_a_duration(DUR_DEFLECT_MISSILES, delay, "You feel less protected from missiles.", coinflip(), "Your deflect missiles spell is about to expire..."); if (_decrement_a_duration(DUR_REGENERATION, delay, 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) { you.duration[DUR_DIVINE_SHIELD] -= delay; if(you.duration[DUR_DIVINE_SHIELD] <= 1) { 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. int last_value = you.duration[DUR_WEAPON_BRAND]; if (last_value > 0) { you.duration[DUR_WEAPON_BRAND] -= delay; if (you.duration[DUR_WEAPON_BRAND] <= 0) { you.duration[DUR_WEAPON_BRAND] = 0; item_def& weapon = *you.weapon(); const int temp_effect = get_weapon_brand(weapon); 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_FLAME: case SPWPN_FLAMING: msg += " goes out."; break; case SPWPN_FREEZING: msg += " stops glowing."; break; case SPWPN_FROST: msg += "'s frost melts away."; 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 pained."; break; default: msg += " seems inexplicably less special."; break; } mpr(msg.c_str(), MSGCH_DURATION); you.wield_change = true; } } // FIXME: [ds] Remove this once we've ensured durations can never go < 0? if (you.duration[DUR_TRANSFORMATION] <= 0 && you.attribute[ATTR_TRANSFORMATION] != TRAN_NONE) { you.duration[DUR_TRANSFORMATION] = 1; } // Vampire bat transformations are permanent (until ended). if (you.species != SP_VAMPIRE || !player_in_bat_form() || you.duration[DUR_TRANSFORMATION] <= 5 * BASELINE_DELAY) { if (_decrement_a_duration(DUR_TRANSFORMATION, delay, 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, delay, "You have got your breath back.", 0, NULL, MSGCH_RECOVERY); _decrement_a_duration(DUR_SWIFTNESS, delay, "You feel sluggish.", coinflip(), "You start to feel a little slower."); _decrement_a_duration(DUR_INSULATION, delay, "You feel conductive.", coinflip(), "You start to feel a little less insulated."); if (_decrement_a_duration(DUR_STONEMAIL, delay, "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, delay, "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, delay) && !you.can_see_invisible()) { mpr("Your eyesight blurs momentarily.", MSGCH_DURATION); } _decrement_a_duration(DUR_TELEPATHY, delay, "You feel less empathic."); if (_decrement_a_duration(DUR_CONDENSATION_SHIELD, delay)) 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, delay, "Your magical shield disappears.")) { you.redraw_armour_class = true; } if (_decrement_a_duration(DUR_STONESKIN, delay, "Your skin feels tender.")) you.redraw_armour_class = true; if (_decrement_a_duration(DUR_TELEPORT, delay)) { // 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, delay, "You feel uncertain.", coinflip(), "You start to feel a little uncertain."); if (_decrement_a_duration(DUR_DEATH_CHANNEL, delay, "Your unholy channel expires.", coinflip(), "Your unholy channel is weakening.")) { you.attribute[ATTR_DIVINE_DEATH_CHANNEL] = 0; } _decrement_a_duration(DUR_SAGE, delay, "You feel less studious."); _decrement_a_duration(DUR_STEALTH, delay, "You feel less stealthy."); _decrement_a_duration(DUR_RESIST_FIRE, delay, "Your fire resistance expires."); _decrement_a_duration(DUR_RESIST_COLD, delay, "Your cold resistance expires."); _decrement_a_duration(DUR_RESIST_POISON, delay, "Your poison resistance expires."); _decrement_a_duration(DUR_SLAYING, delay, "You feel less lethal."); _decrement_a_duration(DUR_INVIS, delay, "You flicker back into view.", coinflip(), "You flicker for a moment."); _decrement_a_duration(DUR_BARGAIN, delay, "You feel less charismatic."); _decrement_a_duration(DUR_CONF, delay, "You feel less confused."); _decrement_a_duration(DUR_LOWERED_MR, delay, "You feel more resistant to magic."); _decrement_a_duration(DUR_SLIMIFY, delay, "You feel less slimy.", coinflip(), "Your slime is starting to congeal."); _decrement_a_duration(DUR_MISLED, delay, "Your thoughts are your own once more."); if (you.duration[DUR_PARALYSIS] || you.petrified()) { _decrement_a_duration(DUR_PARALYSIS, delay); _decrement_a_duration(DUR_PETRIFIED, delay); if (!you.duration[DUR_PARALYSIS] && !you.petrified()) { mpr("You can move again.", MSGCH_DURATION); you.redraw_evasion = true; } } _decrement_a_duration(DUR_EXHAUSTED, delay, "You feel less fatigued."); _decrement_a_duration(DUR_CONFUSING_TOUCH, delay, ((std::string("Your ") + your_hand(true)) + " stop glowing.").c_str()); _decrement_a_duration(DUR_SURE_BLADE, delay, "The bond with your blade fades away."); if (_decrement_a_duration(DUR_MESMERISED, delay, "You break out of your daze.", 0, NULL, MSGCH_RECOVERY)) { you.clear_beholders(); } dec_slow_player(delay); dec_haste_player(delay); if (_decrement_a_duration(DUR_MIGHT, delay, "You feel a little less mighty now.")) { modify_stat(STAT_STRENGTH, -5, true, "might running out"); } if (_decrement_a_duration(DUR_AGILITY, delay, "You feel a little less agile now.")) { modify_stat(STAT_DEXTERITY, -5, true, "agility running out"); } if (_decrement_a_duration(DUR_BRILLIANCE, delay, "You feel a little less clever now.")) { modify_stat(STAT_INTELLIGENCE, -5, true, "brilliance running out"); } if (_decrement_a_duration(DUR_BERSERKER, delay, "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.increase_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); // For consistency with slow give exhaustion 2 times the nominal // duration. you.increase_duration(DUR_EXHAUSTED, dur * 2); // Don't trigger too many tutorial messages. const bool tut_slow = Tutorial.tutorial_events[TUT_YOU_ENCHANTED]; Tutorial.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) { // 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 / 3; calc_hp(); learned_something_new(TUT_POSTBERSERK); Tutorial.tutorial_events[TUT_YOU_ENCHANTED] = tut_slow; } if (you.duration[DUR_CORONA]) { you.duration[DUR_CORONA] -= delay; if (you.duration[DUR_CORONA] < 1) you.duration[DUR_CORONA] = 0; if (!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, delay, "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() && _decrement_a_duration(DUR_CONTROLLED_FLIGHT, delay) && 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(delay); 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.increase_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, delay, "Your life is in your own hands again!", random2(6), "Your time is quickly running out!"); } } if (_decrement_a_duration(DUR_DIVINE_VIGOUR, delay)) remove_divine_vigour(); if (_decrement_a_duration(DUR_DIVINE_STAMINA, delay)) remove_divine_stamina(); _decrement_a_duration(DUR_REPEL_STAIRS_MOVE, 1); _decrement_a_duration(DUR_REPEL_STAIRS_CLIMB, 1); } static void _check_banished() { if (you.banished) { you.banished = false; if (you.level_type != LEVEL_ABYSS) { mpr("You are cast into the Abyss!", MSGCH_BANISHMENT); 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(int delay) { // 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]) { int base_val = player_regen(); tmp += div_rand_round(base_val * delay, BASELINE_DELAY); } while (tmp >= 100) { inc_hp(1, false); tmp -= 100; } // XXX: Don't let DD use guardian spirit for free HP. (due, dpeg) if (player_spirit_shield() && you.species == SP_DEEP_DWARF) return; ASSERT( tmp >= 0 && tmp < 100 ); you.hit_points_regeneration = static_cast(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) { int base_val = 7 + you.max_magic_points / 2; tmp += div_rand_round(base_val * delay, BASELINE_DELAY); } while (tmp >= 100) { inc_mp(1, false); tmp -= 100; } ASSERT( tmp >= 0 && tmp < 100 ); you.magic_points_regeneration = static_cast(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 (Tutorial.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 * BASELINE_DELAY), true ); _decrement_durations(); int food_use = player_hunger_rate(); food_use = div_rand_round(food_use * you.time_taken, BASELINE_DELAY); if (food_use > 0 && you.hunger >= 40) make_hungry(food_use, true); _regenerate_hp_and_mp(you.time_taken); // If you're wielding a rod, it'll gradually recharge. recharge_rods(you.time_taken, false); 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; handle_time(); manage_clouds(); if (you.duration[DUR_FIRE_SHIELD] > 0) manage_fire_shield(you.time_taken); // 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.increase_duration(DUR_PARALYSIS, 5 + random2(8), 13); if (you.religion == GOD_XOM) xom_is_stimulated(get_tension() > 0 ? 255 : 128); } 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); } const int cloud = env.cgrid(target); if (cloud != EMPTY_CLOUD && is_damaging_cloud(env.cloud[ cloud ].type, true)) { 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); std::string door_already_open = ""; if (in_bounds(doorpos)) door_already_open = env.markers.property_at(doorpos, MAT_ANY, "door_verb_already_open"); 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) { // This doesn't ever sem to be triggered. case DNGN_OPEN_DOOR: if (!door_already_open.empty()) mpr(door_already_open.c_str()); else 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 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); const std::string door_desc_adj = env.markers.property_at(doorpos, MAT_ANY, "door_description_adjective"); const std::string door_desc_noun = env.markers.property_at(doorpos, MAT_ANY, "door_description_noun"); if (!door_desc_adj.empty()) adj = door_desc_adj.c_str(); if (!door_desc_noun.empty()) noun = door_desc_noun.c_str(); 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; std::string berserk_open = env.markers.property_at(doorpos, MAT_ANY, "door_berserk_verb_open"); std::string berserk_adjective = env.markers.property_at(doorpos, MAT_ANY, "door_berserk_adjective"); std::string door_open_creak = env.markers.property_at(doorpos, MAT_ANY, "door_noisy_verb_open"); std::string door_airborne = env.markers.property_at(doorpos, MAT_ANY, "door_airborne_verb_open"); std::string door_open_verb = env.markers.property_at(doorpos, MAT_ANY, "door_verb_open"); if (you.berserk()) { // XXX: Better flavour for larger doors? if (silenced(you.pos())) { if (!berserk_open.empty()) { berserk_open += "."; mprf(berserk_open.c_str(), adj, noun); } else mprf("The %s%s flies open!", adj, noun); } else { if (!berserk_open.empty()) { if (!berserk_adjective.empty()) berserk_open += " " + berserk_adjective; else berserk_open += "."; mprf(MSGCH_SOUND, berserk_open.c_str(), 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())) { if (!door_open_creak.empty()) mprf(MSGCH_SOUND, door_open_creak.c_str(), adj, noun); else mprf(MSGCH_SOUND, "As you open the %s%s, it creaks loudly!", adj, noun); noisy(10, you.pos()); } else { const char* verb; if (you.airborne()) { if (!door_airborne.empty()) verb = door_airborne.c_str(); else verb = "You reach down and open the %s%s."; } else { if (!door_open_verb.empty()) verb = door_open_verb.c_str(); else verb = "You open the %s%s."; } mprf(verb, adj, noun); } bool seen_secret = false; std::vector excludes; for (std::set::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); std::string berserk_close = env.markers.property_at(doorpos, MAT_ANY, "door_berserk_verb_close"); std::string berserk_adjective = env.markers.property_at(doorpos, MAT_ANY, "door_berserk_adjective"); std::string door_close_creak = env.markers.property_at(doorpos, MAT_ANY, "door_noisy_verb_close"); std::string door_airborne = env.markers.property_at(doorpos, MAT_ANY, "door_airborne_verb_close"); std::string door_close_verb = env.markers.property_at(doorpos, MAT_ANY, "door_verb_close"); if (feat == DNGN_OPEN_DOOR) { std::set all_door; find_connected_identical(doorpos, grd(doorpos), all_door); const char *adj, *noun; get_door_description(all_door.size(), &adj, &noun); const std::string waynoun_str = make_stringf("%sway", noun); const char *waynoun = waynoun_str.c_str(); const std::string door_desc_adj = env.markers.property_at(doorpos, MAT_ANY, "door_description_adjective"); const std::string door_desc_noun = env.markers.property_at(doorpos, MAT_ANY, "door_description_noun"); if (!door_desc_adj.empty()) adj = door_desc_adj.c_str(); if (!door_desc_noun.empty()) { noun = door_desc_noun.c_str(); waynoun = noun; } for (std::set::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 %s!", waynoun); you.turn_is_over = true; } else mprf("There's a creature in the %s!", waynoun); return; } if (igrd(dc) != NON_ITEM) { mprf("There's something blocking the %s.", waynoun); return; } if (you.pos() == dc) { mprf("There's a thick-headed creature in the %s!", waynoun); return; } } int skill = you.dex + (you.skills[SK_TRAPS_DOORS] + you.skills[SK_STEALTH]) / 2; if (you.berserk()) { if (silenced(you.pos())) { if (!berserk_close.empty()) { berserk_close += "."; mprf(berserk_close.c_str(), adj, noun); } else mprf("You slam the %s%s shut!", adj, noun); } else { if (!berserk_close.empty()) { if (!berserk_adjective.empty()) berserk_close += " " + berserk_adjective; else berserk_close += "."; mprf(MSGCH_SOUND, berserk_close.c_str(), adj, noun); } else mprf(MSGCH_SOUND, "You slam the %s%s shut with a bang!", adj, noun); noisy(15, you.pos()); } } else if (one_chance_in(skill) && !silenced(you.pos())) { if (!door_close_creak.empty()) mprf(MSGCH_SOUND, door_close_creak.c_str(), adj, noun); else mprf(MSGCH_SOUND, "As you close the %s%s, it creaks loudly!", adj, noun); noisy(10, you.pos()); } else { const char* verb; if (you.airborne()) { if (!door_airborne.empty()) verb = door_airborne.c_str(); else verb = "You reach down and close the %s%s."; } else { if (!door_close_verb.empty()) verb = door_close_verb.c_str(); else verb = "You close the %s%s."; } mprf(verb, adj, noun); } std::vector excludes; for (std::set::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(); #ifdef USE_TILE // Draw the splash screen before the database gets initialised as that // may take awhile and it's better if the player can look at a pretty // screen while this happens. if (!crawl_state.map_stat_gen && !crawl_state.test && Options.tile_title_screen) { tiles.draw_title(); } #endif // Initialise internal databases. databaseSystemInit(); init_feat_desc_cache(); init_spell_name_cache(); init_spell_rarities(); // Read special levels and vaults. read_maps(); if (crawl_state.build_db) end(0); 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; 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); // Make sure monsters have a valid LOS before the first turn starts. // This prevents mesmerization from being broken when a game is // restored. for (monster_iterator mi; mi; ++mi) mi->update_los(); 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(); // 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(); trackers_init_new_level(false); 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(Tutorial.tutorial_events[TUT_SEEN_FIRST_OBJECT]); // For a newly started tutorial, turn secret doors into normal ones. if (Tutorial.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 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. int berserk_delay_penalty = you.berserk_penalty * BASELINE_DELAY; you.duration[DUR_BERSERKER] -= berserk_delay_penalty; if (you.duration[DUR_BERSERKER] < 1) you.duration[DUR_BERSERKER] = 1; you.duration[DUR_MIGHT] -= berserk_delay_penalty; if (you.duration[DUR_MIGHT] < 1) you.duration[DUR_MIGHT] = 1; you.duration[DUR_HASTE] -= berserk_delay_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 _cancel_cmd_repeat() { crawl_state.cancel_cmd_again(); crawl_state.cancel_cmd_repeat(); flush_input_buffer(FLUSH_REPLAY_SETUP_FAILURE); } 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 ); _cancel_cmd_repeat(); 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 ); _cancel_cmd_repeat(); return; } // *WAS* a macro trigger, keep going. } if (strlen(buf) == 0) { mpr("You must enter the number of times for the command to repeat."); _cancel_cmd_repeat(); 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 ); _cancel_cmd_repeat(); 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: ", MSGCH_PROMPT); // Enable the cursor to read input. 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)) { _cancel_cmd_repeat(); 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(); // Handle the case where the user has macroed enter to itself. if (keys.empty()) { _cancel_cmd_repeat(); return; } 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 && crawl_state.prev_cmd != CMD_NEXT_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 == 17 , c1); COMPILE_CHECK(SK_EVOCATIONS == 38 , c2); COMPILE_CHECK(SP_VAMPIRE == 30 , c3); COMPILE_CHECK(SPELL_DEBUGGING_RAY == 102 , c4); COMPILE_CHECK(SPELL_PETRIFY == 154 , c5); COMPILE_CHECK(NUM_SPELLS == 198 , 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); // We have space for 32 brands in the bitfield. COMPILE_CHECK((int) SP_UNKNOWN_BRAND < 8*sizeof(you.seen_weapon[0]), c11); COMPILE_CHECK((int) SP_UNKNOWN_BRAND < 8*sizeof(you.seen_armour[0]), c12); COMPILE_CHECK(NUM_SPECIAL_WEAPONS <= SP_UNKNOWN_BRAND, c13); COMPILE_CHECK(NUM_SPECIAL_ARMOURS <= SP_UNKNOWN_BRAND, c14); // 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); }