/* * File: acr.cc * Summary: Main entry point, event loop, and some initialization functions * Written by: Linley Henzell * * Modified for Crawl Reference by $Author$ on $Date$ * * Change History (most recent first): * * <18> 7/29/00 JDJ values.h isn't included on Macs * <17> 19jun2000 GDL added Windows console support * <16> 06mar2000 bwr changes to berserk * <15> 09jan2000 BCR new Wiz command: blink * <14> 10/13/99 BCR Added auto door opening, * move "you have no * religion" to describe.cc * <13> 10/11/99 BCR Added Daniel's wizard patch * <12> 10/9/99 BCR swapped 'v' and 'V' commands, * added wizard help command * <11> 10/7/99 BCR haste and slow now take amulet of * resist slow into account * <10> 9/25/99 CDL Changes to Linux input * switch on command enums * <9> 6/12/99 BWR New init code, restructured * wiz commands, added equipment * listing commands * <8> 6/7/99 DML Autopickup * <7> 5/30/99 JDJ Added game_has_started. * <6> 5/25/99 BWR Changed move() to move_player() * <5> 5/20/99 BWR New berserk code, checking * scan_randarts for NO_TELEPORT * and NO_SPELLCASTING. * <4> 5/12/99 BWR Solaris support. * <3> 5/09/99 JDJ look_around no longer prints a prompt. * <2> 5/08/99 JDJ you and env are no longer arrays. * <1> -/--/-- LRH Created */ #include "AppHdr.h" #include #include // I don't seem to need values.h for VACPP.. #if !defined(__IBMCPP__) #include #endif #if DEBUG // this contains the DBL_MAX constant #include #endif #include #include #include #include #include #include #include #ifdef DOS #include #include #include #endif #ifdef USE_UNIX_SIGNALS #include #endif #include "externs.h" #include "abl-show.h" #include "abyss.h" #include "branch.h" #include "chardump.h" #include "cio.h" #include "cloud.h" #include "clua.h" #include "command.h" #include "database.h" #include "debug.h" #include "delay.h" #include "describe.h" #include "directn.h" #include "dungeon.h" #include "effects.h" #include "fight.h" #include "files.h" #include "food.h" #include "hiscores.h" #include "initfile.h" #include "invent.h" #include "item_use.h" #include "it_use2.h" #include "it_use3.h" #include "itemname.h" #include "itemprop.h" #include "items.h" #include "lev-pand.h" #include "luadgn.h" #include "macro.h" #include "makeitem.h" #include "mapmark.h" #include "maps.h" #include "message.h" #include "misc.h" #include "monplace.h" #include "monstuff.h" #include "mon-util.h" #include "mutation.h" #include "newgame.h" #include "notes.h" #include "ouch.h" #include "output.h" #include "overmap.h" #include "player.h" #include "quiver.h" #include "randart.h" #include "religion.h" #include "shopping.h" #include "skills.h" #include "skills2.h" #include "spells1.h" #include "spells2.h" #include "spells3.h" #include "spl-book.h" #include "spl-cast.h" #include "spl-util.h" #include "state.h" #include "stuff.h" #include "tags.h" #include "terrain.h" #include "transfor.h" #include "traps.h" #include "travel.h" #include "tutorial.h" #include "view.h" #include "stash.h" #include "xom.h" #include "tiles.h" // ---------------------------------------------------------------------- // 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 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 int _check_adjacent(dungeon_feature_type feat, int &dx, int &dy); static void _open_door(int move_x, int move_y, bool check_confused = true); static void _close_door(int move_x, int move_y); static void _start_running( int dir, int mode ); static void _prep_input(); static void _world_reacts(); 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 WIN32TILES int old_main( int argc, char *argv[] ) #else int main( int argc, char *argv[] ) #endif { // Hardcoded initial keybindings. init_keybindings(); _compile_time_asserts(); // Just to quiet "unused static function" warning. // 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(mcolour); // 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(); } const bool game_start = _initialise(); // Override some options for tutorial. init_tutorial_options(); msg::stream << "Welcome, " << you.your_name << " the " << species_name( you.species,you.experience_level ) << " " << you.class_name << "." << std::endl; // Activate markers only after the welcome message, so the // player can see any resulting messages. env.markers.activate_all(); if (game_start && you.char_class == JOB_WANDERER) _wanderer_startup_message(); _god_greeting_message( game_start ); // Warn player about their weapon, if unsuitable. wield_warning(false); if (game_start) { if (Options.tutorial_left) _startup_tutorial(); _take_starting_note(); } else learned_something_new(TUT_LOAD_SAVED_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. _prep_input(); level_change(); while (true) _input(); // Should never reach this stage, right? #if defined(USE_TILE) libgui_shutdown(); #elif defined(UNIX) unixcurses_shutdown(); #endif return 0; } // end main() static void _show_commandline_options_help() { puts("Command line options:"); puts(" -name character name"); puts(" -race preselect race (by letter, abbreviation, or name)"); puts(" -class preselect class (by letter, abbreviation, or name)"); puts(" -pizza crawl pizza"); 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(""); puts("Command line options override init file options, which override"); puts("environment options (CRAWL_NAME, CRAWL_PIZZA, CRAWL_DIR, CRAWL_RC)."); puts(""); puts("Highscore list options: (Can now 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"); } 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) { // Demigods and Demonspawn 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)... Anyways, we'll give the // player a message to warn them (and give 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: case GOD_YREDELEMNUL: simple_god_message(" says: Welcome..."); 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_NO_GOD: case NUM_GODS: case GOD_RANDOM: break; } } static void _take_starting_note() { std::ostringstream notestr; notestr << you.your_name << ", the " << species_name(you.species,you.experience_level) << " " << you.class_name << ", began the quest for the Orb."; take_note(Note(NOTE_MESSAGE, 0, 0, notestr.str().c_str())); notestr.str(""); notestr.clear(); #ifdef WIZARD if (you.wizard) { notestr << "You started the game in wizard mode."; take_note(Note(NOTE_MESSAGE, 0, 0, notestr.str().c_str())); notestr.str(""); notestr.clear(); } #endif notestr << "HP: " << you.hp << "/" << you.hp_max << " MP: " << you.magic_points << "/" << you.max_magic_points; take_note(Note(NOTE_XP_LEVEL_CHANGE, you.experience_level, 0, notestr.str().c_str())); } static void _startup_tutorial() { // Don't allow triggering at game start. Options.tut_just_triggered = true; // Print stats and everything. _prep_input(); #ifdef USE_TILE tile_draw_inv(REGION_INV1); #endif msg::streams(MSGCH_TUTORIAL) << "Press any key to start the tutorial intro, or Escape to skip it." << std::endl; const int ch = c_getch(); if (ch != ESCAPE) tut_starting_screen(); } #ifdef WIZARD // Returns whether an item of this type can be an artefact, or cursed. static bool _item_type_can_be_artefact( int type) { return (type == OBJ_WEAPONS || type == OBJ_ARMOUR || type == OBJ_JEWELLERY); } static void _handle_wizard_command( void ) { int wiz_command, tmp; char specs[256]; // WIZ_NEVER gives protection for those who have wiz compiles, // and don't want to risk their characters. if (Options.wiz_mode == WIZ_NEVER) return; if (!you.wizard) { mpr( "WARNING: ABOUT TO ENTER WIZARD MODE!", MSGCH_WARN ); #ifndef SCORE_WIZARD_MODE mpr( "If you continue, your game will not be scored!", MSGCH_WARN ); #endif if (!yesno( "Do you really want to enter wizard mode?", false, 'n' )) return; take_note(Note(NOTE_MESSAGE, 0, 0, "Entered wizard mode.")); you.wizard = true; redraw_screen(); if (crawl_state.cmd_repeat_start) { crawl_state.cancel_cmd_repeat("Can't repeat entering wizard " "mode."); return; } } mpr( "Enter Wizard Command (? - help): ", MSGCH_PROMPT ); wiz_command = getch(); if (crawl_state.cmd_repeat_start) { // Easiest to list which wizard commands *can* be repeated. switch (wiz_command) { case 'x': case '$': case 'a': case 'c': case 'h': case 'H': case 'm': case 'M': case 'X': case '!': case '[': case ']': case '^': case '%': case 'o': case 'z': case 'Z': break; default: crawl_state.cant_cmd_repeat("You cannot repeat that " "wizard command."); return; } } switch (wiz_command) { case '?': list_commands(true, 0, true); // tell it to list wizard commands break; case CONTROL('G'): save_ghost(true); break; case 'x': you.experience = 1 + exp_needed( 2 + you.experience_level ); level_change(); break; case CONTROL('X'): wizard_set_xl(); break; case 'O': debug_test_explore(); break; case 's': you.exp_available = 20000; you.redraw_experience = true; break; case 'S': wizard_set_skill_level(); break; case 'A': wizard_set_all_skills(); break; case '$': you.gold += 1000; you.redraw_gold = true; if (!Options.show_gold_turns) mprf("You now have %d gold.", you.gold); break; case 'a': acquirement( OBJ_RANDOM, AQ_WIZMODE ); break; case 'v': { // This command isn't very exciting... feel free to replace. int i = prompt_invent_item( "Value of which item?", MT_INVLIST, -1 ); if (prompt_failed(i)) break; else if (!is_random_artefact( you.inv[i] )) mpr("That item is not an artefact!"); else mprf("randart val: %d", randart_value(you.inv[i])); break; } case '+': { int i = prompt_invent_item( "Make an artefact out of which item?", MT_INVLIST, -1 ); if (prompt_failed(i)) break; if (!_item_type_can_be_artefact(you.inv[i].base_type)) { mpr("That item cannot be turned into an artefact."); break; } int j; // Set j == equipment slot of chosen item, remove old randart benefits. for (j = 0; j < NUM_EQUIP; j++) { if (you.equip[j] == i) { if (j == EQ_WEAPON) you.wield_change = true; if (is_random_artefact( you.inv[i] )) unuse_randart( i ); break; } } make_item_randart( you.inv[i] ); // if equipped, apply new randart benefits if (j != NUM_EQUIP) use_randart( i ); mpr( you.inv[i].name(DESC_INVENTORY_EQUIP).c_str() ); break; } case '|': // Create all unrandarts. for (tmp = 1; tmp < NO_UNRANDARTS; tmp++) { int islot = get_item_slot(); if (islot == NON_ITEM) break; make_item_unrandart( mitm[islot], tmp ); mitm[ islot ].quantity = 1; set_ident_flags( mitm[ islot ], ISFLAG_IDENT_MASK ); msg::streams(MSGCH_DIAGNOSTICS) << "Made " << mitm[islot].name(DESC_NOCAP_A) << std::endl; move_item_to_grid( &islot, you.x_pos, you.y_pos ); } // Create all fixed artefacts. for (tmp = SPWPN_START_FIXEDARTS; tmp < SPWPN_START_NOGEN_FIXEDARTS; tmp++) { int islot = get_item_slot(); if (islot == NON_ITEM) break; if (make_item_fixed_artefact( mitm[ islot ], false, tmp )) { mitm[ islot ].quantity = 1; item_colour( mitm[ islot ] ); set_ident_flags( mitm[ islot ], ISFLAG_IDENT_MASK ); move_item_to_grid( &islot, you.x_pos, you.y_pos ); msg::streams(MSGCH_DIAGNOSTICS) << "Made " << mitm[islot].name(DESC_NOCAP_A) << std::endl; } } // Create Horn of Geryon { int islot = get_item_slot(); if (islot == NON_ITEM) break; mitm[islot].base_type = OBJ_MISCELLANY; mitm[islot].sub_type = MISC_HORN_OF_GERYON; mitm[islot].plus = 0; mitm[islot].plus2 = 0; mitm[islot].special = 0; mitm[islot].flags = 0; mitm[islot].quantity = 1; set_ident_flags( mitm[ islot ], ISFLAG_IDENT_MASK ); move_item_to_grid( &islot, you.x_pos, you.y_pos ); msg::streams(MSGCH_DIAGNOSTICS) << "Made " << mitm[islot].name(DESC_NOCAP_A) << std::endl; } break; case 'C': { // This command isn't very exciting but it's useful for debugging. int i = prompt_invent_item( "(Un)curse which item?", MT_INVLIST, -1 ); if (prompt_failed(i)) break; item_def& item(you.inv[i]); if (item_cursed(item)) do_uncurse_item(item); else if (_item_type_can_be_artefact(item.base_type)) do_curse_item(item); else mpr("That item cannot be cursed."); 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 'g': wizard_exercise_skill(); break; case 'G': wizard_dismiss_all_monsters(); break; case 'c': wizard_draw_card(); break; case 'H': // super-heal you.magic_contamination = 0; you.duration[DUR_LIQUID_FLAMES] = 0; if (you.duration[DUR_BEHELD]) { you.duration[DUR_BEHELD] = 0; you.beheld_by.clear(); } inc_hp( 10, true ); // intentional fall-through case 'h': you.rotting = 0; you.disease = 0; you.duration[DUR_CONF] = 0; you.duration[DUR_POISONING] = 0; set_hp( abs(you.hp_max), false ); set_hunger( 10999, true ); you.redraw_hit_points = true; break; case 'b': // wizards can always blink, with no restrictions or // magical contamination. blink(1000, true, true); break; case '~': wizard_interlevel_travel(); break; case '"': debug_list_monsters(); break; case 'd': case 'D': wizard_level_travel(true); break; case 'u': case 'U': wizard_level_travel(false); break; case '%': case 'o': wizard_create_spec_object(); break; case 't': wizard_tweak_object(); break; case 'T': debug_make_trap(); break; case '\\': debug_make_shop(); break; case CONTROL('F'): debug_fight_statistics(false, true); 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_change_species(); break; case '>': wizard_place_stairs(true); break; case '<': wizard_place_stairs(false); break; case 'p': grd[you.x_pos][you.y_pos] = DNGN_ENTER_PANDEMONIUM; break; case 'P': { mpr( "Destination for portal (defaults to 'bazaar')? ", MSGCH_PROMPT ); if (cancelable_get_line( specs, sizeof( specs ) )) { canned_msg( MSG_OK ); break; } std::string dst = specs; dst = trim_string(dst); dst = replace_all(dst, " ", "_"); if (dst.empty()) dst = "bazaar"; if (find_map_by_name(dst) == -1 && random_map_for_tag(dst, false) == -1) { mprf("No map named '%s' or tagged '%s'.", dst.c_str(), dst.c_str()); break; } grd[you.x_pos][you.y_pos] = DNGN_ENTER_PORTAL_VAULT; map_wiz_props_marker *marker = new map_wiz_props_marker(you.pos()); marker->set_property("dst", dst); marker->set_property("desc", "wizard portal, dest = " + dst); env.markers.add(marker); break; } case 'l': grd[you.x_pos][you.y_pos] = DNGN_ENTER_LABYRINTH; break; case 'L': debug_place_map(); break; case 'i': mpr( "You feel a rush of knowledge." ); for (int i = 0; i < ENDOFPACK; i++) { if (is_valid_item( you.inv[i] )) { set_ident_type( you.inv[i], ID_KNOWN_TYPE ); set_ident_flags( you.inv[i], ISFLAG_IDENT_MASK ); } } you.wield_change = true; you.redraw_quiver = true; break; case 'I': mpr( "You feel a rush of antiknowledge." ); for (int i = 0; i < ENDOFPACK; i++) { if (is_valid_item( you.inv[i] )) { set_ident_type( you.inv[i], ID_UNKNOWN_TYPE ); unset_ident_flags( you.inv[i], ISFLAG_IDENT_MASK ); } } you.wield_change = true; you.redraw_quiver = true; // Forget things that nearby monsters are carrying, as well. // (For use with the "give monster an item" wizard targetting // command.) for (int i = 0; i < MAX_MONSTERS; i++) { monsters* mon = &menv[i]; if (!invalid_monster(mon) && mons_near(mon)) { for (int j = 0; j < NUM_MONSTER_SLOTS; j++) { if (mon->inv[j] == NON_ITEM) continue; item_def &item = mitm[mon->inv[j]]; if (!is_valid_item(item)) continue; set_ident_type( item, ID_UNKNOWN_TYPE ); unset_ident_flags( item, ISFLAG_IDENT_MASK ); } } } break; case CONTROL('I'): debug_item_statistics(); break; case 'X': xom_acts(abs(you.piety - 100)); break; case 'z': wizard_cast_spec_spell(); // Cast spell by number. break; case 'Z': wizard_cast_spec_spell_name(); // Cast spell by name. break; case '(': mpr( "Create which feature (by number)? ", MSGCH_PROMPT ); get_input_line( specs, sizeof( specs ) ); if (specs[0] != '\0') { if (const int feat = atoi(specs)) grd[you.x_pos][you.y_pos] = static_cast( feat ); } break; case ')': mpr("Create which feature (by name)? ", MSGCH_PROMPT); get_input_line(specs, sizeof specs); if (*specs) { // Accept both "shallow_water" and "Shallow water" std::string name = lowercase_string(specs); name = replace_all(name, " ", "_"); dungeon_feature_type feat = dungeon_feature_by_name(name); if (feat == DNGN_UNSEEN) { std::vector matches = dungeon_feature_matches(name); if (matches.empty()) { mprf(MSGCH_DIAGNOSTICS, "No features matching '%s'", name.c_str()); return; } // Only one possible match, use that. if (matches.size() == 1) { name = matches[0]; feat = dungeon_feature_by_name(name); } // Multiple matches, list them to wizard else { std::string prefix = "No exact match for feature '" + name + "', possible matches are: "; // Use mpr_comma_separated_list() because the list // might be *LONG*. mpr_comma_separated_list(prefix, matches, " and ", ", ", MSGCH_DIAGNOSTICS); return; } } mprf(MSGCH_DIAGNOSTICS, "Setting (%d,%d) to %s (%d)", you.x_pos, you.y_pos, name.c_str(), feat); grd(you.pos()) = feat; } break; case ']': if (!wizard_add_mutation()) mpr( "Failure to give mutation." ); break; case '[': demonspawn(); break; case ':': for (int i = 0; i < NUM_BRANCHES; i++) { if (branches[i].startdepth != - 1) { mprf(MSGCH_DIAGNOSTICS, "Branch %d (%s) is on level %d of %s", i, branches[i].longname, branches[i].startdepth, branches[branches[i].parent_branch].abbrevname); } else if (i == BRANCH_SWAMP || i == BRANCH_SHOALS) { mprf(MSGCH_DIAGNOSTICS, "Branch %d (%s) was not generated " "this game", i, branches[i].longname); } } break; case '{': if (testbits(env.level_flags, LFLAG_NOT_MAPPABLE) || testbits(get_branch_flags(), BFLAG_NOT_MAPPABLE)) { if (!yesno("Force level to be mappable?", true, 'n')) { canned_msg( MSG_OK ); return; } unset_level_flags(LFLAG_NOT_MAPPABLE | LFLAG_NO_MAGIC_MAP); unset_branch_flags(BFLAG_NOT_MAPPABLE | BFLAG_NO_MAGIC_MAP); } magic_mapping(1000, 100, true, true); break; case '@': wizard_set_stats(); break; case '^': { if (you.religion == GOD_NO_GOD) { mpr("You are not religious!"); break; } else if (you.religion == GOD_XOM) // increase amusement instead { xom_is_stimulated(50, XM_NORMAL, true); break; } const int old_piety = you.piety; const int old_penance = you.penance[you.religion]; if (old_piety >= MAX_PIETY && !old_penance) { mprf("Your piety (%d) is already %s maximum.", old_piety, old_piety == MAX_PIETY ? "at" : "above"); } // Even at maximum, you can still gain gifts. // Try at least once for maximum, or repeat until something // happens. Rarely, this might result in several gifts during the // same round! do { gain_piety(50); } while (old_piety < MAX_PIETY && old_piety == you.piety && old_penance == you.penance[you.religion]); if (old_penance) { mprf("Congratulations, your penance was decreased from %d to %d!", old_penance, you.penance[you.religion]); } else if (you.piety > old_piety) { mprf("Congratulations, your piety went from %d to %d!", old_piety, you.piety); } } 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 '_': wizard_get_religion(); break; case '\'': { bool has_shops = false; for (int i = 0; i < MAX_SHOPS; i++) if (env.shop[i].type != SHOP_UNASSIGNED) { has_shops = true; break; } if (has_shops) { mpr("Shop items:"); for (int i = 0; i < MAX_SHOPS; i++) if (env.shop[i].type != SHOP_UNASSIGNED) { int objl = igrd[0][i + 5]; while (objl != NON_ITEM) { item_def &item(mitm[objl]); std::string name = item.name(DESC_PLAIN, false, false, false); mpr(name.c_str()); objl = item.link; } } mpr(EOL); } mpr("Item stacks (by location and top item):"); for (int i = 0; i < MAX_ITEMS; i++) { item_def &item(mitm[i]); if (!is_valid_item(item) || item.x == 0 || item.y == 0) continue; if (item.link != NON_ITEM) mprf("(%2d,%2d): %s", item.x, item.y, item.name(DESC_PLAIN, false, false, false).c_str() ); } mpr(EOL); mpr("Floor items (stacks only show top item):"); for (int i = 1; i < GXM; i++) for (int j = 1; j < GYM; j++) { int item = igrd[i][j]; if (item != NON_ITEM) { mprf("%3d at (%2d,%2d): %s", item, i, j, mitm[item].name(DESC_PLAIN, false, false, false).c_str() ); } } break; } default: formatted_mpr(formatted_string::parse_string("Not a Wizard Command.")); break; } you.turn_is_over = false; } #endif // Set up the running variables for the current run. static void _start_running( int dir, int mode ) { if (Options.tutorial_events[TUT_SHIFT_RUN] && mode == RMODE_START) Options.tutorial_events[TUT_SHIFT_RUN] = false; if (i_feel_safe(true)) you.running.initialise(dir, mode); } static bool _recharge_rod( item_def &rod, bool wielded ) { if (!item_is_rod(rod) || rod.plus >= rod.plus2 || !enough_mp(1, true)) return (false); const int charge = rod.plus / ROD_CHARGE_MULT; int rate = ((charge + 1) * ROD_CHARGE_MULT) / 10; rate *= (10 + skill_bump( SK_EVOCATIONS )); rate = div_rand_round( rate, 100 ); if (rate < 5) rate = 5; else if (rate > ROD_CHARGE_MULT / 2) rate = ROD_CHARGE_MULT / 2; // If not wielded, the rod charges far more slowly. if (!wielded) rate /= 5; // Shields hamper recharging for wielded rods. else if (player_shield()) rate /= 2; if (rod.plus / ROD_CHARGE_MULT != (rod.plus + rate) / ROD_CHARGE_MULT) { dec_mp(1); if (wielded) you.wield_change = true; } rod.plus += rate; if (rod.plus > rod.plus2) rod.plus = rod.plus2; if (wielded && rod.plus == rod.plus2) { mpr("Your rod has recharged."); if (is_resting()) stop_running(); } return (true); } static void _recharge_rods() { const int wielded = you.equip[EQ_WEAPON]; if (wielded != -1 && _recharge_rod( you.inv[wielded], true )) return; for (int i = 0; i < ENDOFPACK; ++i) { if (i != wielded && is_valid_item(you.inv[i]) && one_chance_in(3) && _recharge_rod( you.inv[i], false )) { return; } } } static bool _cmd_is_repeatable(command_type cmd, bool is_again = false) { switch(cmd) { // Informational commands case CMD_LOOK_AROUND: case CMD_INSPECT_FLOOR: case CMD_EXAMINE_OBJECT: case CMD_LIST_WEAPONS: case CMD_LIST_ARMOUR: case CMD_LIST_JEWELLERY: case CMD_LIST_EQUIPMENT: case CMD_LIST_GOLD: case CMD_CHARACTER_DUMP: case CMD_DISPLAY_COMMANDS: case CMD_DISPLAY_INVENTORY: case CMD_DISPLAY_KNOWN_OBJECTS: case CMD_DISPLAY_MUTATIONS: case CMD_DISPLAY_SKILLS: case CMD_DISPLAY_OVERMAP: case CMD_DISPLAY_RELIGION: case CMD_DISPLAY_CHARACTER_STATUS: case CMD_DISPLAY_SPELLS: case CMD_EXPERIENCE_CHECK: case CMD_RESISTS_SCREEN: case CMD_READ_MESSAGES: case CMD_SEARCH_STASHES: mpr("You can't repeat informational commands."); return (false); // Multi-turn commands case CMD_PICKUP: case CMD_DROP: case CMD_BUTCHER: case CMD_GO_UPSTAIRS: case CMD_GO_DOWNSTAIRS: case CMD_WIELD_WEAPON: case CMD_WEAPON_SWAP: case CMD_WEAR_JEWELLERY: case CMD_REMOVE_JEWELLERY: case CMD_MEMORISE_SPELL: case CMD_EXPLORE: case CMD_INTERLEVEL_TRAVEL: mpr("You can't repeat multi-turn commands."); return (false); // Miscellaneous non-repeatable commands. case CMD_TOGGLE_AUTOPICKUP: case CMD_TOGGLE_FRIENDLY_PICKUP: case CMD_ADJUST_INVENTORY: case CMD_QUIVER_ITEM: case CMD_REPLAY_MESSAGES: case CMD_REDRAW_SCREEN: case CMD_MACRO_ADD: case CMD_SAVE_GAME: case CMD_SAVE_GAME_NOW: case CMD_SUSPEND_GAME: case CMD_QUIT: case CMD_DESTROY_ITEM: case CMD_MARK_STASH: 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: mpr("You can't repeat that command."); return (false); case CMD_DISPLAY_MAP: mpr("You can't repeat map commands."); return (false); case CMD_MOUSE_MOVE: case CMD_MOUSE_CLICK: mpr("You can't repeat mouse clicks or movements."); return (false); case CMD_REPEAT_CMD: mpr("You can't repeat the repeat command!"); return (false); case CMD_RUN_LEFT: case CMD_RUN_DOWN: case CMD_RUN_UP: case CMD_RUN_RIGHT: case CMD_RUN_UP_LEFT: case CMD_RUN_DOWN_LEFT: case CMD_RUN_UP_RIGHT: case CMD_RUN_DOWN_RIGHT: mpr("Why would you want to repeat a run command?"); return (false); case CMD_PREV_CMD_AGAIN: ASSERT(!is_again); if (crawl_state.prev_cmd == CMD_NO_CMD) { mpr("No previous command to repeat."); return (false); } return _cmd_is_repeatable(crawl_state.prev_cmd, true); case CMD_MOVE_NOWHERE: case CMD_REST: case CMD_SEARCH: return (i_feel_safe(true)); case CMD_MOVE_LEFT: case CMD_MOVE_DOWN: case CMD_MOVE_UP: case CMD_MOVE_RIGHT: case CMD_MOVE_UP_LEFT: case CMD_MOVE_DOWN_LEFT: case CMD_MOVE_UP_RIGHT: case CMD_MOVE_DOWN_RIGHT: if (!i_feel_safe()) { return yesno("Really repeat movement command while monsters " "are nearby?", false, 'n'); } return (true); case CMD_NO_CMD: mpr("Unknown command, not repeating."); return (false); default: return (true); } return (false); } // Used to determine whether to apply the berserk penalty at end of round. bool apply_berserk_penalty = false; static void _center_cursor() { const coord_def cwhere = grid2view(you.pos()); cgotoxy(cwhere.x, cwhere.y); } // // This function handles the player's input. It's called from main(), // from inside an endless loop. // static void _input() { religion_turn_start(); check_beholders(); 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; } you.turn_is_over = false; _prep_input(); fire_monster_alerts(); const bool player_feels_safe = i_feel_safe(); if (Options.tutorial_left) { Options.tut_just_triggered = false; if (you.attribute[ATTR_HELD]) learned_something_new(TUT_CAUGHT_IN_NET); else if (player_feels_safe && you.level_type != LEVEL_ABYSS) { // We don't want those "Whew, it's safe to rest now" messages // when you were just cast into the Abyss. Right? if (2 * you.hp < you.hp_max || 2 * you.magic_points < you.max_magic_points) { tutorial_healing_reminder(); } else if (!you.running && Options.tutorial_events[TUT_SHIFT_RUN] && you.num_turns >= 200 && you.hp == you.hp_max && you.magic_points == you.max_magic_points) { learned_something_new(TUT_SHIFT_RUN); } else if (!you.running && Options.tutorial_events[TUT_MAP_VIEW] && you.num_turns >= 500 && you.hp == you.hp_max && you.magic_points == you.max_magic_points) { learned_something_new(TUT_MAP_VIEW); } } else { if (2*you.hp < you.hp_max) learned_something_new(TUT_RUN_AWAY); if (Options.tutorial_type == TUT_MAGIC_CHAR && you.magic_points < 1) learned_something_new(TUT_RETREAT_CASTER); } } if (you.cannot_act()) { crawl_state.cancel_cmd_repeat("Cannot move, cancelling command " "repetition."); _world_reacts(); return; } if (need_to_autopickup()) autopickup(); if (need_to_autoinscribe()) autoinscribe(); handle_delay(); _center_cursor(); if (you_are_delayed()) { 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; { // Enable the cursor to read input. The cursor stays on while // the command is being processed, so subsidiary prompts // shouldn't need to turn it on explicitly. #ifdef USE_TILE cursor_control con(false); #else cursor_control con(true); #endif crawl_state.waiting_for_command = true; c_input_reset(true); const command_type cmd = _get_next_cmd(); crawl_state.waiting_for_command = false; if (cmd != CMD_PREV_CMD_AGAIN && cmd != CMD_REPEAT_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(true, 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 _toggle_flag( bool* flag, const char* flagname ) { *flag = !(*flag); mprf( "%s is now %s.", flagname, (*flag) ? "on" : "off" ); return *flag; } static bool _stairs_check_beheld() { if (you.duration[DUR_BEHELD] && !you.duration[DUR_CONF]) { mprf("You cannot move away from %s!", menv[you.beheld_by[0]].name(DESC_NOCAP_THE, true).c_str()); return (true); } return (false); } static void _go_downstairs(); static void _go_upstairs() { const dungeon_feature_type ygrd = grd(you.pos()); if (_stairs_check_beheld()) return; if (you.attribute[ATTR_HELD]) { mpr("You're held in a net!"); return; } if (ygrd == DNGN_ENTER_SHOP) { if (you.duration[DUR_BERSERKER]) canned_msg(MSG_TOO_BERSERK); else shop(); return; } else if (grid_stair_direction(ygrd) != CMD_GO_UPSTAIRS) { if (ygrd == DNGN_STONE_ARCH) mpr("There is nothing on the other side of the stone arch."); else mpr("You can't go up here!"); return; } // Does the next level have a warning annotation? if (!check_annotation_exclusion_warning()) return; tag_followers(); // Only those beside us right now can follow. start_delay( DELAY_ASCENDING_STAIRS, 1 + (you.burden_state > BS_UNENCUMBERED) ); } static void _go_downstairs() { bool shaft = (trap_type_at_xy(you.x_pos, you.y_pos) == TRAP_SHAFT && grd[you.x_pos][you.y_pos] != DNGN_UNDISCOVERED_TRAP); if (_stairs_check_beheld()) return; if (shaft && you.flight_mode() == FL_LEVITATE) { mpr("You can't fall through a shaft while levitating."); return; } if (grid_stair_direction(grd(you.pos())) != CMD_GO_DOWNSTAIRS && !shaft) { if (grd(you.pos()) == DNGN_STONE_ARCH) mpr("There is nothing on the other side of the stone arch."); else mpr( "You can't go down here!" ); return; } if (you.attribute[ATTR_HELD]) { mpr("You're held in a net!"); return; } // Does the next level have a warning annotation? if (!check_annotation_exclusion_warning()) return; if (shaft) { start_delay( DELAY_DESCENDING_STAIRS, 0, you.your_level ); } else { 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_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_PREFS: edit_prefs(); break; case CMD_USE_ITEM: { int idx; InvAction act; gui_get_mouse_inv(idx, act); tile_use_item(idx, act); } break; case CMD_VIEW_ITEM: { int idx; InvAction act; gui_get_mouse_inv(idx, act); if (idx < 0) // item on floor describe_item(mitm[-idx]); else // item in inventory describe_item(you.inv[idx], true); redraw_screen(); } break; case CMD_EDIT_PLAYER_TILE: TilePlayerEdit(); break; #endif // USE_TILE 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 (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: _toggle_flag( &Options.autopickup_on, "Autopickup"); break; case CMD_TOGGLE_FRIENDLY_PICKUP: { // Toggle pickup mode for friendlies. _print_friendly_pickup_setting(false); if (Options.tutorial_left && !god_gives_permanent_followers(you.religion)) { mpr("Only intelligent allies may equip themselves, which excludes " "all types of zombies as well as hostile monsters you " "enslaved.", MSGCH_TUTORIAL); } mpr("Change to (d)efault, (n)othing, (f)riend-dropped, or (a)ll? ", MSGCH_PROMPT); char type = (char) getchm(KC_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 == '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(0, 0); break; case CMD_DROP: drop(); if (Options.stash_tracking >= STM_DROPPED) StashTrack.add_stash(); break; case CMD_SEARCH_STASHES: if (Options.tut_stashes) Options.tut_stashes = 0; StashTrack.search_stashes(); break; case CMD_MARK_STASH: if (Options.stash_tracking >= STM_EXPLICIT) StashTrack.add_stash(-1, -1, true); 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_wielded()) flush_input_buffer( FLUSH_ON_FAILURE ); break; case CMD_PICKUP: pickup(); break; case CMD_INSPECT_FLOOR: request_autopickup(); break; case CMD_FULL_VIEW: mpr(mpr_monster_list().c_str()); 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 (you.attribute[ATTR_TRANSFORMATION] == TRAN_BAT) { 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, false); break; case CMD_ADJUST_INVENTORY: adjust(); break; case CMD_MEMORISE_SPELL: if (you.attribute[ATTR_TRANSFORMATION] == TRAN_BAT) { canned_msg(MSG_PRESENT_FORM); break; } if (!learn_spell()) flush_input_buffer( FLUSH_ON_FAILURE ); break; case CMD_ZAP_WAND: if (you.attribute[ATTR_TRANSFORMATION] == TRAN_BAT) { canned_msg(MSG_PRESENT_FORM); break; } 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: if (you.attribute[ATTR_TRANSFORMATION] == TRAN_BAT) { canned_msg(MSG_PRESENT_FORM); break; } 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 initialized by direction(). direction(lmove, DIR_TARGET, TARG_ANY, -1, true); if (lmove.isValid && lmove.isTarget && !lmove.isCancel) start_travel( lmove.tx, lmove.ty ); break; } case CMD_CAST_SPELL: if (you.attribute[ATTR_TRANSFORMATION] == TRAN_BAT) { canned_msg(MSG_PRESENT_FORM); break; } // Randart weapons. if (scan_randarts(RAP_PREVENT_SPELLCASTING)) { mpr("Something interferes with your magic!"); flush_input_buffer( FLUSH_ON_FAILURE ); break; } if (Options.tutorial_left) Options.tut_spell_counter++; if (!cast_a_spell()) flush_input_buffer( FLUSH_ON_FAILURE ); break; case CMD_DISPLAY_SPELLS: inspect_spells(); break; case CMD_WEAPON_SWAP: wield_weapon(true); break; // [ds] Waypoints can be added from the level-map, and we need // Ctrl+F for nobler things. Who uses waypoints, anyway? // Update: Appears people do use waypoints. Reinstating, on // CONTROL('W'). This means Ctrl+W is no longer a wizmode // trigger, but there's always '&'. :-) case CMD_FIX_WAYPOINT: travel_cache.add_waypoint(); break; case CMD_INTERLEVEL_TRAVEL: if (Options.tut_travel) Options.tut_travel = 0; if (!can_travel_interlevel()) { if (you.running.x == you.x_pos && you.running.y == you.running.y) { mpr("You're already here."); break; } else if (!you.running.x || !you.running.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.x, you.running.y); } else start_translevel_travel(); if (you.running) mesclr(); break; case CMD_ANNOTATE_LEVEL: annotate_level(); break; case CMD_EXPLORE: // Start exploring start_explore(Options.explore_greedy); break; case CMD_DISPLAY_MAP: if (Options.tutorial_events[TUT_MAP_VIEW]) Options.tutorial_events[TUT_MAP_VIEW] = false; #if (!DEBUG_DIAGNOSTICS) if (!player_in_mappable_area()) { mpr("It would help if you knew where you were, first."); break; } #endif { coord_def 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, get_number_of_cols()); #endif show_map(pos, true); redraw_screen(); #ifdef USE_TILE mpr("Returning to the game..."); #endif if (pos.x > 0) start_travel(pos.x, pos.y); } 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(false, 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.x, dest.y); } else if (cme.right_clicked()) { if (see_grid(dest.x, dest.y)) 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: char name_your[kNameLen+1]; strncpy(name_your, you.your_name, kNameLen); name_your[kNameLen] = 0; if (dump_char( name_your, 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: { int cur; you.m_quiver->get_desired_item(NULL, &cur); const int next = get_next_fire_item(cur, +1); #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); } 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: mprf("You have %d gold piece%s.", you.gold, (you.gold > 1) ? "s" : ""); 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, 0, KILLED_BY_QUITTING); else canned_msg(MSG_OK); break; case CMD_REPEAT_CMD: _setup_cmd_repeat(); break; case CMD_PREV_CMD_AGAIN: _do_prev_cmd_again(); break; case CMD_NO_CMD: default: if (Options.tutorial_left) { std::string msg = "Unknown command. (For a list of commands type " "?\?.)"; print_formatted_paragraph(msg, get_number_of_cols()); } else // well, not examine, but... mpr("Unknown command.", MSGCH_EXAMINE_FILTER); break; } } 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(); update_screen(); } // Decrement a single duration. Print the message if the duration runs out. static bool _decrement_a_duration(duration_type dur, const char* endmsg = NULL, int midpoint = -1, int midloss = 0, const char* midmsg = NULL, msg_channel_type chan = MSGCH_DURATION ) { bool rc = false; if (you.duration[dur] > 1) { you.duration[dur]--; if (you.duration[dur] == midpoint) { if (midmsg) mpr(midmsg, chan); you.duration[dur] -= midloss; } } else if (you.duration[dur] == 1) { if (endmsg) mpr(endmsg, chan); rc = true; you.duration[dur] = 0; } return rc; } // Perhaps we should write functions like: update_repel_undead(), // update_liquid_flames(), and so on. Even better, we could have a // vector of callback functions (or objects) which get installed // at some point. static void _decrement_durations() { if (wearing_amulet(AMU_THE_GOURMAND)) { if (you.duration[DUR_GOURMAND] < GOURMAND_MAX && coinflip()) you.duration[DUR_GOURMAND]++; } else you.duration[DUR_GOURMAND] = 0; // Must come before might/haste/berserk. if (_decrement_a_duration(DUR_BUILDING_RAGE)) go_berserk(false); if (_decrement_a_duration(DUR_SLEEP)) you.awake(); // Paradox: It both lasts longer & does more damage overall if you're // moving slower. // Rationalisation: I guess it gets rubbed off/falls off/etc if you // move around more. if (you.duration[DUR_LIQUID_FLAMES] > 0) you.duration[DUR_LIQUID_FLAMES]--; if (you.duration[DUR_LIQUID_FLAMES] != 0) { const int res_fire = player_res_fire(); mpr( "You are covered in liquid flames!", MSGCH_WARN ); expose_player_to_element(BEAM_NAPALM, 12); if (res_fire > 0) { ouch( (((random2avg(9, 2) + 1) * you.time_taken) / (1 + (res_fire * res_fire))) / 10, 0, KILLED_BY_BURNING ); } if (res_fire <= 0) { ouch(((random2avg(9, 2) + 1) * you.time_taken) / 10, 0, KILLED_BY_BURNING); } if (res_fire < 0) { ouch(((random2avg(9, 2) + 1) * you.time_taken) / 10, 0, KILLED_BY_BURNING); } if (you.duration[DUR_CONDENSATION_SHIELD] > 0) { mpr("Your icy shield dissipates!", MSGCH_DURATION); you.duration[DUR_CONDENSATION_SHIELD] = 0; you.redraw_armour_class = true; } } if (_decrement_a_duration(DUR_ICY_ARMOUR, "Your icy armour evaporates.", 6, coinflip(), "Your icy armour starts to melt.")) { you.redraw_armour_class = true; } if (_decrement_a_duration(DUR_SILENCE, "Your hearing returns.")) you.attribute[ATTR_WAS_SILENCED] = 0; _decrement_a_duration(DUR_REPEL_MISSILES, "You feel less protected from missiles.", 6, coinflip(), "Your repel missiles spell is about to expire..."); _decrement_a_duration(DUR_DEFLECT_MISSILES, "You feel less protected from missiles.", 6, coinflip(), "Your deflect missiles spell is about to expire..."); _decrement_a_duration(DUR_REGENERATION, "Your skin stops crawling.", 6, coinflip(), "Your skin is crawling a little less now."); if (you.duration[DUR_PRAYER] > 1) you.duration[DUR_PRAYER]--; else if (you.duration[DUR_PRAYER] == 1) { mpr( "Your prayer is over.", MSGCH_PRAY, you.religion ); you.duration[DUR_PRAYER] = 0; } if (you.duration[DUR_DIVINE_SHIELD]) { if (you.duration[DUR_DIVINE_SHIELD] > 1) { if (--you.duration[DUR_DIVINE_SHIELD] == 1) mpr("Your divine shield starts to fade.", MSGCH_DURATION); } if (you.duration[DUR_DIVINE_SHIELD] == 1 && !one_chance_in(3)) { you.redraw_armour_class = true; if (--you.attribute[ATTR_DIVINE_SHIELD] == 0) { you.duration[DUR_DIVINE_SHIELD] = 0; mpr("Your divine shield fades completely.", MSGCH_DURATION); } } } //jmf: More flexible weapon branding code. if (you.duration[DUR_WEAPON_BRAND] > 1) you.duration[DUR_WEAPON_BRAND]--; else if (you.duration[DUR_WEAPON_BRAND] == 1) { const int wpn = you.equip[EQ_WEAPON]; const int temp_effect = get_weapon_brand( you.inv[wpn] ); you.duration[DUR_WEAPON_BRAND] = 0; set_item_ego_type( you.inv[wpn], OBJ_WEAPONS, SPWPN_NORMAL ); std::string msg = you.inv[wpn].name(DESC_CAP_YOUR); switch (temp_effect) { case SPWPN_VORPAL: if (get_vorpal_type(you.inv[you.equip[EQ_WEAPON]]) == DVORP_SLICING) msg += " seems blunter."; else msg += " feels lighter."; break; case SPWPN_FLAMING: msg += " goes out."; break; case SPWPN_FREEZING: msg += " stops glowing."; break; case SPWPN_VENOM: msg += " stops dripping with poison."; break; case SPWPN_DRAINING: msg += " stops crackling."; break; case SPWPN_DISTORTION: msg += " seems straighter."; break; case SPWPN_PAIN: msg += " seems less painful."; break; default: msg += " seems inexplicably less special."; break; } mpr(msg.c_str(), MSGCH_DURATION); you.wield_change = true; } // Vampire bat transformations are permanent (until ended.) if (you.species != SP_VAMPIRE || you.attribute[ATTR_TRANSFORMATION] != TRAN_BAT || you.duration[DUR_TRANSFORMATION] <= 5) { if ( _decrement_a_duration(DUR_TRANSFORMATION, NULL, 10, random2(3), "Your transformation is almost over.") ) { untransform(); you.duration[DUR_BREATH_WEAPON] = 0; } } // Must come after transformation duration. _decrement_a_duration(DUR_BREATH_WEAPON, "You have got your breath back.", -1, 0, NULL, MSGCH_RECOVERY); _decrement_a_duration(DUR_REPEL_UNDEAD, "Your holy aura fades away.", 4, random2(3), "Your holy aura is starting to fade."); _decrement_a_duration(DUR_SWIFTNESS, "You feel sluggish.", 6, coinflip(), "You start to feel a little slower."); _decrement_a_duration(DUR_INSULATION, "You feel conductive.", 6, coinflip(), "You start to feel a little less insulated."); if ( _decrement_a_duration(DUR_STONEMAIL, "Your scaly stone armour disappears.", 6, coinflip(), "Your scaly stone armour is starting " "to flake away.") ) { you.redraw_armour_class = true; burden_change(); } if ( _decrement_a_duration(DUR_FORESCRY, "You feel firmly rooted in the present.", 6, coinflip(), "Your vision of the future begins to falter.")) { you.redraw_evasion = true; } if ( _decrement_a_duration(DUR_SEE_INVISIBLE) && !player_see_invis() ) mpr("Your eyesight blurs momentarily.", MSGCH_DURATION); _decrement_a_duration(DUR_SEE_INVISIBLE); // jmf: cute message // handled elsewhere _decrement_a_duration(DUR_TELEPATHY, "You feel less empathic."); if ( _decrement_a_duration(DUR_CONDENSATION_SHIELD, "Your icy shield evaporates.") ) you.redraw_armour_class = true; if (you.duration[DUR_CONDENSATION_SHIELD] > 0 && player_res_cold() < 0) { mpr( "You feel very cold." ); ouch( 2 + random2avg(13, 2), 0, KILLED_BY_FREEZING ); } if ( _decrement_a_duration(DUR_MAGIC_SHIELD, "Your magical shield disappears.") ) you.redraw_armour_class = true; if ( _decrement_a_duration(DUR_STONESKIN, "Your skin feels tender.") ) you.redraw_armour_class = true; if ( _decrement_a_duration(DUR_TELEPORT) ) { // Only to a new area of the abyss sometimes (for abyss teleports). you_teleport_now( true, one_chance_in(5) ); untag_followers(); } _decrement_a_duration(DUR_CONTROL_TELEPORT, "You feel uncertain.", 6, coinflip(), "You start to feel a little uncertain."); if (_decrement_a_duration(DUR_DEATH_CHANNEL, "Your unholy channel expires.", 6, coinflip(), "Your unholy channel is weakening.")) { you.attribute[ATTR_DIVINE_DEATH_CHANNEL] = 0; } _decrement_a_duration(DUR_SAGE, "You feel less studious."); _decrement_a_duration(DUR_STEALTH, "You feel less stealthy."); _decrement_a_duration(DUR_RESIST_FIRE, "Your fire resistance expires."); _decrement_a_duration(DUR_RESIST_COLD, "Your cold resistance expires."); _decrement_a_duration(DUR_RESIST_POISON, "Your poison resistance expires."); _decrement_a_duration(DUR_SLAYING, "You feel less lethal."); _decrement_a_duration(DUR_INVIS, "You flicker back into view.", 6, coinflip(), "You flicker for a moment."); _decrement_a_duration(DUR_BARGAIN, "You feel less charismatic."); _decrement_a_duration(DUR_CONF, "You feel less confused."); if (you.duration[DUR_PARALYSIS] || you.duration[DUR_PETRIFIED]) { _decrement_a_duration(DUR_PARALYSIS); _decrement_a_duration(DUR_PETRIFIED); if (!you.duration[DUR_PARALYSIS] && !you.duration[DUR_PETRIFIED]) { mpr("You can move again.", MSGCH_DURATION); you.redraw_evasion = true; } } _decrement_a_duration(DUR_EXHAUSTED, "You feel less fatigued."); _decrement_a_duration( DUR_CONFUSING_TOUCH, ((std::string("Your ") + your_hand(true)) + " stop glowing.").c_str() ); _decrement_a_duration( DUR_SURE_BLADE, "The bond with your blade fades away." ); if ( _decrement_a_duration( DUR_BEHELD, "You break out of your daze.", -1, 0, NULL, MSGCH_RECOVERY )) { you.beheld_by.clear(); } dec_slow_player(); dec_haste_player(); if (_decrement_a_duration(DUR_MIGHT, "You feel a little less mighty now.")) modify_stat(STAT_STRENGTH, -5, true, "might running out"); if (_decrement_a_duration(DUR_BERSERKER, "You are no longer berserk.")) { //jmf: Guilty for berserking /after/ berserk. did_god_conduct( DID_STIMULANTS, 6 + random2(6) ); // Sometimes berserk leaves us physically drained. // // Chance of passing out: // - mutation gives a large plus in order to try and // avoid the mutation being a "death sentence" to // certain characters. // - knowing the spell gives an advantage just // so that people who have invested 3 spell levels // are better off than the casual potion drinker... // this should make it a bit more interesting for // Crusaders again. // - similarly for the amulet if (you.berserk_penalty != NO_BERSERK_PENALTY) { const int chance = 10 + player_mutation_level(MUT_BERSERK) * 25 + (wearing_amulet( AMU_RAGE ) ? 10 : 0) + (player_has_spell( SPELL_BERSERKER_RAGE ) ? 5 : 0); // Note the beauty of Trog! They get an extra save that's at // the very least 20% and goes up to 100%. if (you.religion == GOD_TROG && x_chance_in_y(you.piety, 150) && !player_under_penance()) { mpr("Trog's vigour flows through your veins."); } else if (one_chance_in(chance)) { mpr("You pass out from exhaustion.", MSGCH_WARN); you.duration[DUR_PARALYSIS] += roll_dice( 1, 4 ); } } if (you.duration[DUR_PARALYSIS] == 0 && you.duration[DUR_PETRIFIED] == 0) { mpr("You are exhausted.", MSGCH_WARN); } // This resets from an actual penalty or from NO_BERSERK_PENALTY. you.berserk_penalty = 0; int dur = 12 + roll_dice( 2, 12 ); you.duration[DUR_EXHAUSTED] += dur; // Don't trigger too many tutorial messages. const bool tut_slow = Options.tutorial_events[TUT_YOU_ENCHANTED]; Options.tutorial_events[TUT_YOU_ENCHANTED] = false; { // Don't give duplicate 'You feel yourself slow down' messages. no_messages nm; slow_player( dur ); } make_hungry(700, true); if (you.hunger < 50) you.hunger = 50; calc_hp(); learned_something_new(TUT_POSTBERSERK); Options.tutorial_events[TUT_YOU_ENCHANTED] = tut_slow; } if (you.duration[DUR_BACKLIGHT] > 0 && !--you.duration[DUR_BACKLIGHT] && !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.duration[DUR_PIETY_POOL] && one_chance_in(5)) { you.duration[DUR_PIETY_POOL]--; // Decrease even if piety at maximum. 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]) mpr("Piety pool is now empty.", MSGCH_DIAGNOSTICS); #endif } if (!you.permanent_levitation() && !you.permanent_flight()) { if ( _decrement_a_duration(DUR_LEVITATION, "You float gracefully downwards.", 10, 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.x_pos, you.y_pos, false, true, true ); } } if (!you.permanent_flight()) { if ( _decrement_a_duration(DUR_CONTROLLED_FLIGHT) && you.airborne() ) mpr("You lose control over your flight.", MSGCH_DURATION); } if (you.rotting > 0) { // XXX: Mummies have an ability (albeit an expensive one) that // can fix rotted HPs now... it's probably impossible for them // to even start rotting right now, but that could be changed. -- bwr 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, 0, 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(400)) { mpr("You feel your flesh rotting away.", MSGCH_WARN); ouch(1, 0, KILLED_BY_ROTTING); rot_hp(1); if (you.rotting > 0) you.rotting--; } } dec_disease_player(); if (you.duration[DUR_POISONING] > 0) { if (x_chance_in_y(you.duration[DUR_POISONING], 5)) { if (you.duration[DUR_POISONING] > 10 && random2(you.duration[DUR_POISONING]) >= 8) { ouch(random2(10) + 5, 0, KILLED_BY_POISON); mpr("You feel extremely sick.", MSGCH_DANGER); } else if (you.duration[DUR_POISONING] > 5 && coinflip()) { ouch((coinflip()? 3 : 2), 0, KILLED_BY_POISON); mpr("You feel very sick.", MSGCH_WARN); } else { // the poison running through your veins."); ouch(1, 0, KILLED_BY_POISON); mpr("You feel sick."); } if ((you.hp == 1 && one_chance_in(3)) || one_chance_in(8)) reduce_poison_player(1); } } if (you.duration[DUR_DEATHS_DOOR]) { if (you.hp > allowed_deaths_door_hp()) { mpr("Your life is in your own hands once again.", MSGCH_DURATION); you.duration[DUR_PARALYSIS] += 5 + random2(5); confuse_player( 10 + random2(10) ); you.hp_max--; deflate_hp(you.hp_max, false); you.duration[DUR_DEATHS_DOOR] = 0; } else you.duration[DUR_DEATHS_DOOR]--; if (you.duration[DUR_DEATHS_DOOR] == 10) { mpr("Your time is quickly running out!", MSGCH_DURATION); you.duration[DUR_DEATHS_DOOR] -= random2(6); } if (you.duration[DUR_DEATHS_DOOR] == 1) { mpr("Your life is in your own hands again!", MSGCH_DURATION); more(); } } reduce_vitalisation_chain(1); if (_decrement_a_duration(DUR_DIVINE_ROBUSTNESS)) remove_divine_robustness(); if (_decrement_a_duration(DUR_DIVINE_STAMINA)) remove_divine_stamina(); } static void _check_banished() { if (you.banished) { you.banished = false; if (you.level_type != LEVEL_ABYSS) { mpr("You are cast into the Abyss!"); banished(DNGN_ENTER_ABYSS, you.banished_by); } you.banished_by.clear(); } } static void _check_shafts() { for (int i = 0; i < MAX_TRAPS; i++) { trap_struct &trap = env.trap[i]; if (trap.type != TRAP_SHAFT) continue; ASSERT(in_bounds(trap.x, trap.y)); handle_items_on_shaft(trap.x, trap.y, true); } } static void _check_sanctuary() { if (env.sanctuary_time <= 0) return; decrease_sanctuary_radius(); } static void _world_reacts() { crawl_state.clear_god_acting(); 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. } stealth = check_stealth(); #ifdef DEBUG_STEALTH // Too annoying for regular diagnostics. mprf(MSGCH_DIAGNOSTICS, "stealth: %d", stealth ); #endif if (you.special_wield != SPWLD_NONE) special_wielded(); if (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 (env.cgrid[you.x_pos][you.y_pos] != EMPTY_CLOUD) in_a_cloud(); if (you.level_type == LEVEL_DUNGEON && you.duration[DUR_TELEPATHY]) detect_creatures( 1 + you.duration[DUR_TELEPATHY] / 2, true ); _decrement_durations(); const int food_use = player_hunger_rate(); if (food_use > 0 && you.hunger >= 40) make_hungry( food_use, true ); // 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 = static_cast< int >( you.hit_points_regeneration ); if (you.hp < you.hp_max && !you.disease && !you.duration[DUR_DEATHS_DOOR]) tmp += player_regen(); while (tmp >= 100) { inc_hp(1, false); tmp -= 100; } ASSERT( tmp >= 0 && tmp < 100 ); you.hit_points_regeneration = static_cast< unsigned char >( tmp ); // XXX: Doing the same as the above, although overflow isn't an // issue with magic point regeneration, yet. -- bwr tmp = static_cast< int >( you.magic_points_regeneration ); if (you.magic_points < you.max_magic_points) tmp += 7 + you.max_magic_points / 2; while (tmp >= 100) { inc_mp(1, false); tmp -= 100; } ASSERT( tmp >= 0 && tmp < 100 ); you.magic_points_regeneration = static_cast< unsigned char >( tmp ); // If you're wielding a rod, it'll gradually recharge. _recharge_rods(); viewwindow(true, true); if (Options.stash_tracking) { StashTrack.update_visible_stashes( Options.stash_tracking == STM_ALL ? StashTracker::ST_AGGRESSIVE : StashTracker::ST_PASSIVE); } handle_monsters(); _check_banished(); ASSERT(you.time_taken >= 0); // Make sure we don't overflow. ASSERT(DBL_MAX - you.elapsed_time > you.time_taken); you.elapsed_time += you.time_taken; if (you.synch_time <= you.time_taken) { handle_time(200 + (you.time_taken - you.synch_time)); you.synch_time = 200; _check_banished(); } else { const long old_synch_time = you.synch_time; you.synch_time -= you.time_taken; // Call spawn_random_monsters() more often than the rest of // handle_time() so the spawning rates work out correctly. if (old_synch_time >= 150 && you.synch_time < 150 || old_synch_time >= 100 && you.synch_time < 100 || old_synch_time >= 50 && you.synch_time < 50) { spawn_random_monsters(); } } manage_clouds(); if (you.duration[DUR_FIRE_SHIELD] > 0) manage_fire_shield(); // Food death check. if (you.is_undead != US_UNDEAD && you.hunger <= 500) { if (!you.cannot_act() && one_chance_in(40)) { mpr("You lose consciousness!", MSGCH_FOOD); stop_running(); you.duration[DUR_PARALYSIS] += 5 + random2(8); if (you.duration[DUR_PARALYSIS] > 13) you.duration[DUR_PARALYSIS] = 13; } if (you.hunger <= 100) { mpr( "You have starved to death.", MSGCH_FOOD ); ouch( INSTANT_DEATH, 0, KILLED_BY_STARVATION ); } } viewwindow(true, false); if (you.cannot_act() && any_messages()) more(); } #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 _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)); goto kill_messaging; } // 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)); goto kill_messaging; } 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)); goto kill_messaging; } } 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; return; kill_messaging: if (mf) fclose(mf); SysEnv.have_messages = false; Options.messaging = 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 ) { 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, KC_DEFAULT); } } static keycode_type _get_next_keycode() { keycode_type keyin; flush_input_buffer( FLUSH_BEFORE_COMMAND ); #ifdef USE_TILE tile_draw_inv(REGION_INV1); #ifdef USE_X11 update_screen(); #endif mouse_set_mode(MOUSE_MODE_COMMAND); keyin = unmangle_direction_keys(getch_with_command_macros()); mouse_set_mode(MOUSE_MODE_NORMAL); #else // !USE_TILE keyin = unmangle_direction_keys(getch_with_command_macros()); #endif 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, int &dx, int &dy) { int num = 0; int _dx = 0, _dy = 0; for (int x = -1; x <= 1; x++) for (int y = -1; y <= 1; y++) if (grd[you.x_pos + x][you.y_pos + y] == feat) { num++; _dx = x; _dy = y; } if (num == 1) { dx = _dx; dy = _dy; } return num; } // Opens doors and handles some aspects of untrapping. 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(int move_x, int move_y, bool check_confused) { if (you.attribute[ATTR_TRANSFORMATION] == TRAN_BAT) { mpr("You can't open doors in your present form."); return; } struct dist door_move; int dx, dy; // door x, door y if (you.attribute[ATTR_HELD]) { free_self_from_net(); you.turn_is_over = true; return; } // If there's only one door to open, don't ask. if ((!check_confused || !you.duration[DUR_CONF]) && !(move_x || move_y)) { if (_check_adjacent(DNGN_CLOSED_DOOR, move_x, move_y) == 0) { mpr("There's nothing to open."); return; } } if (check_confused && you.duration[DUR_CONF] && !one_chance_in(3)) { move_x = random2(3) - 1; move_y = random2(3) - 1; } door_move.dx = move_x; door_move.dy = move_y; if (move_x || move_y) { // convenience dx = you.x_pos + move_x; dy = you.y_pos + move_y; const int mon = mgrd[dx][dy]; if (mon != NON_MONSTER && player_can_hit_monster(&menv[mon])) { if (mons_is_caught(&menv[mon])) { std::string prompt = "Do you want to try to take the net off "; prompt += (&menv[mon])->name(DESC_NOCAP_THE); prompt += '?'; if (yesno(prompt.c_str(), true, 'n')) { remove_net_from(&menv[mon]); return; } } you.turn_is_over = true; you_attack(mgrd[dx][dy], true); if (you.berserk_penalty != NO_BERSERK_PENALTY) you.berserk_penalty = 0; return; } if (grd[dx][dy] >= DNGN_TRAP_MECHANICAL && grd[dx][dy] <= DNGN_TRAP_NATURAL) { if (env.cgrid[dx][dy] != EMPTY_CLOUD) { mpr("You can't get to that trap right now."); return; } disarm_trap(door_move); return; } } else { mpr("Which direction?", MSGCH_PROMPT); direction( door_move, DIR_DIR ); if (!door_move.isValid) return; // convenience dx = you.x_pos + door_move.dx; dy = you.y_pos + door_move.dy; const dungeon_feature_type feat = in_bounds(dx, dy) ? grd[dx][dy] : DNGN_UNSEEN; if (feat != DNGN_CLOSED_DOOR) { switch (feat) { case DNGN_OPEN_DOOR: mpr("It's already open!"); break; default: mpr("There isn't anything that you can open there!"); break; } // Don't lose a turn. return; } } if (grd[dx][dy] == DNGN_CLOSED_DOOR) { std::set all_door; find_connected_range(coord_def(dx,dy), DNGN_CLOSED_DOOR, DNGN_SECRET_DOOR, all_door); const char *adj, *noun; get_door_description(all_door.size(), &adj, &noun); int skill = you.dex + (you.skills[SK_TRAPS_DOORS] + you.skills[SK_STEALTH]) / 2; if (you.duration[DUR_BERSERKER]) { // XXX: Better flavour for larger doors? if (silenced(you.x_pos, you.y_pos)) mprf("The %s%s flies open!", adj, noun); else { mprf(MSGCH_SOUND, "The %s%s flies open with a bang!", adj, noun); noisy(15, you.x_pos, you.y_pos); } } else if (one_chance_in(skill) && !silenced(you.x_pos, you.y_pos)) { mprf(MSGCH_SOUND, "As you open the %s%s, it creaks loudly!", adj, noun); noisy(10, you.x_pos, you.y_pos); } else { const char* verb = player_is_airborne() ? "reach down and open" : "open"; mprf( "You %s the %s%s.", verb, adj, noun ); } bool seen_secret = false; 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_envmap_obj(dc.x, dc.y, DNGN_OPEN_DOOR); #ifdef USE_TILE tile_place_tile_bk(dc.x, dc.y, TILE_DNGN_OPEN_DOOR); #endif if (!seen_secret && grd[dc.x][dc.y] == DNGN_SECRET_DOOR) { seen_secret = true; dungeon_feature_type secret = grid_secret_door_appearance(dc.x, dc.y); mprf("That %s was a secret door!", feature_description(secret, NUM_TRAPS, false, DESC_PLAIN, false).c_str()); } } grd[dc.x][dc.y] = DNGN_OPEN_DOOR; } you.turn_is_over = true; } else if (grd[dx][dy] == DNGN_OPEN_DOOR) _close_door(move_x, move_y); // for convenience else { mpr("You swing at nothing."); make_hungry(3, true); you.turn_is_over = true; } } static void _close_door(int door_x, int door_y) { if (you.attribute[ATTR_TRANSFORMATION] == TRAN_BAT) { 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; } struct dist door_move; int dx, dy; // door x, door y // If there's only one door to close, don't ask. if (!you.duration[DUR_CONF] && !(door_x || door_y)) { int num = _check_adjacent(DNGN_OPEN_DOOR, door_x, door_y); if (num == 0) { mpr("There's nothing to close."); return; } else if (num == 1 && !(door_x || door_y)) { mpr("You can't close doors on yourself!"); return; } } if (you.duration[DUR_CONF] && !one_chance_in(3)) { door_x = random2(3) - 1; door_y = random2(3) - 1; } door_move.dx = door_x; door_move.dy = door_y; if (!door_x && !door_y) { mpr("Which direction?", MSGCH_PROMPT); direction( door_move, DIR_DIR ); if (!door_move.isValid) return; } if (door_move.dx == 0 && door_move.dy == 0) { mpr("You can't close doors on yourself!"); return; } // convenience dx = you.x_pos + door_move.dx; dy = you.y_pos + door_move.dy; const dungeon_feature_type feat = in_bounds(dx, dy) ? grd[dx][dy] : DNGN_UNSEEN; if (feat == DNGN_OPEN_DOOR) { std::set all_door; find_connected_identical(coord_def(dx,dy), grd[dx][dy], all_door); const char *adj, *noun; get_door_description(all_door.size(), &adj, &noun); const coord_def you_coord(you.x_pos, you.y_pos); for (std::set::iterator i = all_door.begin(); i != all_door.end(); ++i) { const coord_def& dc = *i; if (mgrd[dc.x][dc.y] != NON_MONSTER) { // Need to make sure that turn_is_over is set if creature is // invisible. if (!player_monster_visible(&menv[mgrd[dc.x][dc.y]])) { mprf("Something is blocking the %sway!", noun); you.turn_is_over = true; } else mprf("There's a creature in the %sway!", noun); door_move.dx = 0; door_move.dy = 0; return; } if (igrd[dc.x][dc.y] != NON_ITEM) { mprf("There's something blocking the %sway.", noun); door_move.dx = 0; door_move.dy = 0; return; } if (you_coord == dc) { mprf("There's a thickheaded creature in the %sway!", noun); return; } } int skill = you.dex + (you.skills[SK_TRAPS_DOORS] + you.skills[SK_STEALTH]) / 2; if (you.duration[DUR_BERSERKER]) { if (silenced(you.x_pos, you.y_pos)) { mprf("You slam the %s%s shut!", adj, noun); } else { mprf(MSGCH_SOUND, "You slam the %s%s shut with an echoing bang!", adj, noun); noisy(25, you.x_pos, you.y_pos); } } else if (one_chance_in(skill) && !silenced(you.x_pos, you.y_pos)) { mprf(MSGCH_SOUND, "As you close the %s%s, it creaks loudly!", adj, noun); noisy(10, you.x_pos, you.y_pos); } else { const char* verb = player_is_airborne() ? "reach down and close" : "close"; mprf( "You %s the %s%s.", verb, adj, noun ); } for (std::set::iterator i = all_door.begin(); i != all_door.end(); ++i) { const coord_def& dc = *i; 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_envmap_obj(dc.x, dc.y, DNGN_CLOSED_DOOR); #ifdef USE_TILE tile_place_tile_bk(dc.x, dc.y, TILE_DNGN_CLOSED_DOOR); #endif } } you.turn_is_over = true; } else { switch (feat) { case DNGN_CLOSED_DOOR: mpr("It's already closed!"); break; default: mpr("There isn't anything that you can close there!"); break; } } } // Initialise 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_feature_table(); init_monster_symbols(); init_properties(); init_spell_descs(); // This needs to be way up top. {dlb} init_mon_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.init(map_cell()); you.unique_creatures.init(false); you.unique_items.init(UNIQ_NOT_EXISTS); // Set up the Lua interpreter for the dungeon builder. init_dungeon_lua(); // Read special levels and vaults. read_maps(); // Initialise internal databases. databaseSystemInit(); init_feat_desc_cache(); init_spell_name_cache(); init_item_name_cache(); cio_init(); // System initialisation stuff. textbackground(0); clrscr(); #ifdef DEBUG_DIAGNOSTICS sanity_check_mutation_defs(); if (crawl_state.map_stat_gen) { generate_map_stats(); end(0, false); } #endif // Sets up a new game. const bool newc = new_game(); if (!newc) restore_game(); // Fix the mutation definitions for the species we're playing. fixup_mutations(); // Load macros macro_init(); crawl_state.need_save = true; calc_hp(); calc_mp(); run_map_preludes(); if (newc && you.char_direction == GDT_GAME_START) { // Chaos Knights of Lugonu start out in the Abyss. you.level_type = LEVEL_ABYSS; you.entry_cause = EC_UNKNOWN; } load(you.entering_level ? you.transit_stair : DNGN_STONE_STAIRS_DOWN_I, you.entering_level ? LOAD_ENTER_LEVEL : newc ? LOAD_START_GAME : LOAD_RESTART_GAME, NUM_LEVEL_AREA_TYPES, -1, you.where_are_you); if (newc && you.char_direction == GDT_GAME_START) { // Randomise colours properly for the Abyss. init_pandemonium(); } #if DEBUG_DIAGNOSTICS // Debug compiles display a lot of "hidden" information, so we auto-wiz. you.wizard = true; #endif init_properties(); burden_change(); make_hungry(0, true); you.redraw_strength = true; you.redraw_intelligence = true; you.redraw_dexterity = true; you.redraw_armour_class = true; you.redraw_evasion = true; you.redraw_experience = true; you.redraw_gold = 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; read_init_file(true); Options.fixup_options(); strncpy(you.your_name, yname.c_str(), kNameLen); you.your_name[kNameLen - 1] = 0; // In case Lua changed the character set. init_char_table(Options.char_set); init_feature_table(); init_monster_symbols(); #endif draw_border(); new_level(); update_turn_count(); trackers_init_new_level(false); // Reset lava/water nearness check to unknown, so it'll be // recalculated for the next monster that tries to reach us. you.lava_in_sight = you.water_in_sight = -1; // Set vision radius to player's current vision. setLOSRadius(you.current_vision); 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. zap_los_monsters(); // For a newly started tutorial, turn secret doors into normal ones. if (Options.tutorial_left) tutorial_zap_secret_doors(); } #ifdef USE_TILE TilePlayerInit(); TileInitItems(); TileNewLevel(newc); #endif set_cursor_enabled(false); // This just puts the view up for the first turn. viewwindow(true, 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.duration[DUR_BERSERKER]) { you.berserk_penalty++; switch (you.berserk_penalty) { case 2: mpr("You feel a strong urge to attack something.", MSGCH_DURATION); break; case 4: mpr("You feel your anger subside.", MSGCH_DURATION); break; case 6: mpr("Your blood rage is quickly leaving you.", MSGCH_DURATION); break; } // I do these three separately, because the might and // haste counters can be different. you.duration[DUR_BERSERKER] -= you.berserk_penalty; if (you.duration[DUR_BERSERKER] < 1) you.duration[DUR_BERSERKER] = 1; you.duration[DUR_MIGHT] -= you.berserk_penalty; if (you.duration[DUR_MIGHT] < 1) you.duration[DUR_MIGHT] = 1; you.duration[DUR_HASTE] -= you.berserk_penalty; if (you.duration[DUR_HASTE] < 1) you.duration[DUR_HASTE] = 1; } return; } // end do_berserk_no_combat_penalty() // Called when the player moves by walking/running. Also calls attack // function etc when necessary. static void _move_player(int move_x, int move_y) { bool attacking = false; bool moving = true; // used to prevent eventual movement (swap) bool swap = false; monsters *beholder = NULL; // beholding monster preventing movement if (you.attribute[ATTR_HELD]) { free_self_from_net(); you.turn_is_over = true; return; } if (you.duration[DUR_CONF]) { if (!one_chance_in(3)) { move_x = random2(3) - 1; move_y = random2(3) - 1; you.reset_prev_move(); } const int new_targ_x = you.x_pos + move_x; const int new_targ_y = you.y_pos + move_y; if (!in_bounds(new_targ_x, new_targ_y) || !you.can_pass_through(new_targ_x, new_targ_y)) { you.turn_is_over = true; mpr("Ouch!"); apply_berserk_penalty = true; crawl_state.cancel_cmd_repeat(); return; } } // end of if you.duration[DUR_CONF] const int targ_x = you.x_pos + move_x; const int targ_y = you.y_pos + move_y; const dungeon_feature_type targ_grid = grd[ targ_x ][ targ_y ]; const unsigned short targ_monst = mgrd[ targ_x ][ targ_y ]; const bool targ_pass = you.can_pass_through(targ_x, targ_y); // 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 != NON_MONSTER && !mons_is_stationary(&menv[targ_monst]) && (mons_wont_attack(&menv[targ_monst]) && !you.duration[DUR_CONF] || is_sanctuary(you.x_pos, you.y_pos) && is_sanctuary(targ_x, targ_y)); // You cannot move away from a mermaid but you CAN fight monsters on // neighbouring squares. if (you.duration[DUR_BEHELD] && !you.duration[DUR_CONF]) { for (unsigned int i = 0; i < you.beheld_by.size(); i++) { monsters* mon = &menv[you.beheld_by[i]]; coord_def pos = mon->pos(); int olddist = grid_distance(you.x_pos, you.y_pos, pos.x, pos.y); int newdist = grid_distance(you.x_pos + move_x, you.y_pos + move_y, pos.x, pos.y); if (olddist < newdist) { beholder = mon; break; } } } // end of beholding check if (you.running.check_stop_running()) { move_x = 0; move_y = 0; // [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 != NON_MONSTER && !mons_is_submerged(&menv[targ_monst])) { monsters *mon = &menv[targ_monst]; if (can_swap_places && !beholder) { if (swap_check(mon, 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, 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_x, targ_y, true, false, swap, swap)) return; if (swap) swap_places(&menv[targ_monst], mon_swap_dest); you.prev_move_x = move_x; you.prev_move_y = move_y; move_x = 0; move_y = 0; you.turn_is_over = true; // item_check( false ); request_autopickup(); } // BCR - Easy doors single move if (targ_grid == DNGN_CLOSED_DOOR && Options.easy_open && !attacking) { _open_door(move_x, move_y, false); you.prev_move_x = move_x; you.prev_move_y = move_y; } else if (!targ_pass && !attacking) { stop_running(); move_x = 0; move_y = 0; you.turn_is_over = 0; crawl_state.cancel_cmd_repeat(); } else if (beholder && !attacking) { mprf("You cannot move away from %s!", beholder->name(DESC_NOCAP_THE, true).c_str()); move_x = 0; move_y = 0; return; } if (you.running == RMODE_START) you.running = RMODE_CONTINUE; if (you.level_type == LEVEL_ABYSS && (you.x_pos <= 15 || you.x_pos >= (GXM - 16) || you.y_pos <= 15 || you.y_pos >= (GYM - 16))) { area_shift(); if (you.pet_target != MHITYOU) you.pet_target = MHITNOT; #if DEBUG_DIAGNOSTICS mpr( "Shifting.", MSGCH_DIAGNOSTICS ); int igly = 0; int ig2 = 0; for (igly = 0; igly < MAX_ITEMS; igly++) if (is_valid_item( mitm[igly] )) ig2++; mprf( MSGCH_DIAGNOSTICS, "Number of items present: %d", ig2 ); ig2 = 0; for (igly = 0; igly < MAX_MONSTERS; igly++) if (menv[igly].type != -1) ig2++; mprf( MSGCH_DIAGNOSTICS, "Number of monsters present: %d", ig2); mprf( MSGCH_DIAGNOSTICS, "Number of clouds present: %d", env.cloud_no); #endif } apply_berserk_penalty = !attacking; } // end move_player() 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); line_reader reader(buf, buf_len); reader.set_keyproc(_get_num_and_char_keyfun); return reader.read_line(true); } static void _setup_cmd_repeat() { if (is_processing_macro()) { flush_input_buffer(FLUSH_ABORT_MACRO); crawl_state.cancel_cmd_again(); crawl_state.cancel_cmd_repeat(); return; } ASSERT(!crawl_state.is_repeating_cmd()); char buf[80]; // Function ensures that the buffer contains only digits. int ch = _get_num_and_char("Number of times to repeat, then command key: ", buf, 80); if (ch == ESCAPE) { // This *might* be part of the trigger for a macro. keyseq trigger; trigger.push_back(ch); if (get_macro_buf_size() == 0) { // Was just a single ESCAPE key, so not a macro trigger. canned_msg( MSG_OK ); crawl_state.cancel_cmd_again(); crawl_state.cancel_cmd_repeat(); flush_input_buffer(FLUSH_REPLAY_SETUP_FAILURE); return; } ch = getchm(); trigger.push_back(ch); // Now that we have the entirety of the (possible) macro trigger, // clear out the keypress recorder so that we won't have recorded // the trigger twice. repeat_again_rec.clear(); insert_macro_into_buff(trigger); ch = getchm(); if (ch == ESCAPE) { if (get_macro_buf_size() > 0) // User pressed an Alt key which isn't bound to a macro. mpr("That key isn't bound to a macro."); else // Wasn't a macro trigger, just an ordinary escape. canned_msg( MSG_OK ); crawl_state.cancel_cmd_again(); crawl_state.cancel_cmd_repeat(); flush_input_buffer(FLUSH_REPLAY_SETUP_FAILURE); return; } // *WAS* a macro trigger, keep going. } if (strlen(buf) == 0) { mpr("You must enter the number of times for the command to repeat."); crawl_state.cancel_cmd_again(); crawl_state.cancel_cmd_repeat(); flush_input_buffer(FLUSH_REPLAY_SETUP_FAILURE); return; } int count = atoi(buf); if (crawl_state.doing_prev_cmd_again) count = crawl_state.prev_cmd_repeat_goal; if (count <= 0) { canned_msg( MSG_OK ); crawl_state.cancel_cmd_again(); crawl_state.cancel_cmd_repeat(); flush_input_buffer(FLUSH_REPLAY_SETUP_FAILURE); return; } if (crawl_state.doing_prev_cmd_again) { // If a "do previous command again" caused a command // repetition to be redone, the keys to be repeated are // already in the key recording buffer, so we just need to // discard all the keys saying how many times the command // should be repeated. do { repeat_again_rec.keys.pop_front(); } while (repeat_again_rec.keys.size() > 0 && repeat_again_rec.keys[0] != ch); repeat_again_rec.keys.pop_front(); } // User can type space or enter and then the command key, in case // they want to repeat a command bound to a number key. c_input_reset(true); if (ch == ' ' || ch == CK_ENTER) { if (!crawl_state.doing_prev_cmd_again) repeat_again_rec.keys.pop_back(); mpr("Enter command to be repeated: "); // Enable the cursor to read input. The cursor stays on while // the command is being processed, so subsidiary prompts // shouldn't need to turn it on explicitly. cursor_control con(true); crawl_state.waiting_for_command = true; ch = _get_next_keycode(); crawl_state.waiting_for_command = false; } command_type cmd = _keycode_to_command( (keycode_type) ch); if (cmd != CMD_MOUSE_MOVE) c_input_reset(false); if (!is_processing_macro() && !_cmd_is_repeatable(cmd)) { crawl_state.cancel_cmd_again(); crawl_state.cancel_cmd_repeat(); flush_input_buffer(FLUSH_REPLAY_SETUP_FAILURE); return; } if (!crawl_state.doing_prev_cmd_again && cmd != CMD_PREV_CMD_AGAIN) crawl_state.prev_cmd_keys = repeat_again_rec.keys; if (is_processing_macro()) { // Put back in first key of the expanded macro, which // get_next_keycode() fetched repeat_again_rec.paused = true; macro_buf_add(ch, true); // If we're repeating a macro, get rid the keys saying how // many times to repeat, because the way that macros are // repeated means that the number keys will be repeated if // they aren't discarded. keyseq &keys = repeat_again_rec.keys; ch = keys[keys.size() - 1]; while (isdigit(ch) || ch == ' ' || ch == CK_ENTER) { keys.pop_back(); ASSERT(keys.size() > 0); ch = keys[keys.size() - 1]; } } repeat_again_rec.paused = false; // Discard the setup for the command repetition, since what's // going to be repeated is yet to be typed, except for the fist // key typed which has to be put back in (unless we're repeating a // macro, in which case everything to be repeated is already in // the macro buffer). if (!is_processing_macro()) { repeat_again_rec.clear(); macro_buf_add(ch, crawl_state.doing_prev_cmd_again); } crawl_state.cmd_repeat_start = true; crawl_state.cmd_repeat_count = 0; crawl_state.repeat_cmd = cmd; crawl_state.cmd_repeat_goal = count; crawl_state.prev_cmd_repeat_goal = count; crawl_state.prev_repetition_turn = you.num_turns; crawl_state.cmd_repeat_started_unsafe = !i_feel_safe(); crawl_state.input_line_strs.clear(); } static void _do_prev_cmd_again() { if (is_processing_macro()) { mpr("Can't re-do previous command from within a macro."); flush_input_buffer(FLUSH_ABORT_MACRO); crawl_state.cancel_cmd_again(); crawl_state.cancel_cmd_repeat(); return; } if (crawl_state.prev_cmd == CMD_NO_CMD) { mpr("No previous command to re-do."); crawl_state.cancel_cmd_again(); crawl_state.cancel_cmd_repeat(); 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 than normal, // since in addition to all of the keystrokes for // the command, it needs the repeat command plus the // number of repeats at the very beginning of the // sequence. if (!crawl_state.doing_prev_cmd_again) { keyseq &prev = crawl_state.prev_cmd_keys; keyseq &curr = repeat_again_rec.keys; if (is_processing_macro()) prev = curr; else { // Skip first key, because that's command key that's // being repeated, which crawl_state.prev_cmd_keys // aleardy contains. keyseq::iterator begin = curr.begin(); begin++; prev.insert(prev.end(), begin, curr.end()); } } repeat_again_rec.paused = true; macro_buf_add(KEY_REPEAT_KEYS); } } if (!crawl_state.is_replaying_keys() && !crawl_state.cmd_repeat_start && crawl_state.prev_cmd != CMD_NO_CMD) { if (repeat_again_rec.keys.size() > 0) crawl_state.prev_cmd_keys = repeat_again_rec.keys; } if (!is_processing_macro()) repeat_again_rec.clear(); } static void _compile_time_asserts() { // Check that the numbering comments in enum.h haven't been // disturbed accidentally. COMPILE_CHECK(SK_UNARMED_COMBAT == 19 , c1); COMPILE_CHECK(SK_EVOCATIONS == 39 , c2); COMPILE_CHECK(SP_VAMPIRE == 34 , c3); COMPILE_CHECK(SPELL_BOLT_OF_MAGMA == 18 , c4); COMPILE_CHECK(SPELL_POISON_ARROW == 94 , c5); COMPILE_CHECK(SPELL_SUMMON_MUSHROOMS == 223 , c6); //jmf: NEW ASSERTS: we ought to do a *lot* of these COMPILE_CHECK(NUM_SPELLS < SPELL_NO_SPELL , c7); COMPILE_CHECK(NUM_JOBS < JOB_UNKNOWN , c8); // 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); }