/* * File: state.cc * Summary: Game state functions. * Written by: Matthew Cline */ #include "AppHdr.h" #include "externs.h" #include "dbg-util.h" #include "delay.h" #include "directn.h" #include "exclude.h" #include "macro.h" #include "misc.h" #include "menu.h" // For print_formatted_paragraph() #include "message.h" #include "mon-util.h" #include "player.h" #include "religion.h" #include "showsymb.h" #include "state.h" #include "tutorial.h" game_state::game_state() : game_crashed(false), mouse_enabled(false), waiting_for_command(false), terminal_resized(false), io_inited(false), need_save(false), saving_game(false), updating_scores(false), seen_hups(0), map_stat_gen(false), arena(false), arena_suspended(false), build_db(false), unicode_ok(false), glyph2strfn(NULL), multibyte_strlen(NULL), terminal_resize_handler(NULL), terminal_resize_check(NULL), doing_prev_cmd_again(false), prev_cmd(CMD_NO_CMD), repeat_cmd(CMD_NO_CMD), cmd_repeat_count(0), cmd_repeat_goal(0), prev_repetition_turn(0), cmd_repeat_started_unsafe(false), input_line_curr(0), level_annotation_shown(false), #ifndef USE_TILE mlist_targetting(false), #endif darken_range(-1) { reset_cmd_repeat(); reset_cmd_again(); } void game_state::add_startup_error(const std::string &err) { startup_errors.push_back(err); } void game_state::show_startup_errors() { formatted_scroller error_menu; error_menu.set_flags(MF_NOSELECT | MF_ALWAYS_SHOW_MORE | MF_NOWRAP | MF_EASY_EXIT); error_menu.set_more( formatted_string::parse_string( "[ + : Page down. - : Page up." " Esc or Enter to continue.]")); error_menu.set_title( new MenuEntry("Warning: Crawl encountered errors during startup:", MEL_TITLE)); for (int i = 0, size = startup_errors.size(); i < size; ++i) error_menu.add_entry(new MenuEntry(startup_errors[i])); error_menu.show(); } /////////////////////////////////////////////////////////////////////////// // Repeating commands and doing the previous command over again. bool game_state::is_replaying_keys() const { return (crawl_state.doing_prev_cmd_again || (crawl_state.is_repeating_cmd() && !crawl_state.cmd_repeat_start)); } bool game_state::is_repeating_cmd() const { ASSERT((cmd_repeat_goal == 0 && cmd_repeat_count == 0 && repeat_cmd == CMD_NO_CMD && !cmd_repeat_start) || (cmd_repeat_goal > 0 && cmd_repeat_count <= cmd_repeat_goal && repeat_cmd != CMD_NO_CMD)); return (repeat_cmd != CMD_NO_CMD); } void game_state::cancel_cmd_repeat(std::string reason) { if (!is_repeating_cmd()) return; if (repeat_cmd == CMD_WIZARD) { // Don't interrupt wizard testing of religion. if (is_god_acting()) return; // Don't interrupt wizard testing just because we can't // move. if (you.cannot_act()) return; // We've probably just recovered from being unable to act; // again, don't interrupt. if (you.turn_is_over) return; } if (is_replaying_keys() || cmd_repeat_start) flush_input_buffer(FLUSH_KEY_REPLAY_CANCEL); if (is_processing_macro()) flush_input_buffer(FLUSH_ABORT_MACRO); reset_cmd_repeat(); if (!reason.empty()) mpr(reason.c_str()); } void game_state::cancel_cmd_again(std::string reason) { if (!doing_prev_cmd_again) return; flush_input_buffer(FLUSH_KEY_REPLAY_CANCEL); if (is_processing_macro()) flush_input_buffer(FLUSH_ABORT_MACRO); reset_cmd_again(); if (!reason.empty()) mpr(reason.c_str()); } void game_state::cancel_cmd_all(std::string reason) { cancel_cmd_repeat(reason); cancel_cmd_again(reason); } void game_state::cant_cmd_repeat(std::string reason) { if (reason.empty()) reason = "Can't repeat that command."; cancel_cmd_repeat(reason); } void game_state::cant_cmd_again(std::string reason) { if (reason.empty()) reason = "Can't redo that command."; cancel_cmd_again(reason); } void game_state::cant_cmd_any(std::string reason) { cant_cmd_repeat(reason); cant_cmd_again(reason); } // The method is called to prevent the "no repeating zero turns // commands" message that input() generates (in the absence of // cancelling the repeition) for a repeated command that took no // turns. A wrapper around cancel_cmd_repeat(), its only purpose it // to make it clear why cancel_cmd_repeat() is being called. void game_state::zero_turns_taken() { ASSERT(!you.turn_is_over); cancel_cmd_repeat(); } bool interrupt_cmd_repeat( activity_interrupt_type ai, const activity_interrupt_data &at ) { if (crawl_state.cmd_repeat_start) return (false); if (crawl_state.repeat_cmd == CMD_WIZARD) return (false); switch (ai) { case AI_STATUE: case AI_HUNGRY: case AI_TELEPORT: case AI_FORCE_INTERRUPT: case AI_HP_LOSS: case AI_MONSTER_ATTACKS: crawl_state.cancel_cmd_repeat("Command repetition interrupted."); return (true); default: break; } if (ai == AI_SEE_MONSTER) { const monsters* mon = static_cast(at.data); if (!you.can_see(mon)) return (false); if (crawl_state.cmd_repeat_started_unsafe && at.context != "newly seen") { return (false); } crawl_state.cancel_cmd_repeat(); #ifndef DEBUG_DIAGNOSTICS if (at.context == "newly seen") { set_auto_exclude(mon); std::string text = get_monster_equipment_desc(mon, false); text += " comes into view."; print_formatted_paragraph(text, MSGCH_WARN); } if (Tutorial.tutorial_left) tutorial_monster_seen(*mon); #else formatted_string fs( channel_to_colour(MSGCH_WARN) ); fs.cprintf("%s (", mon->name(DESC_PLAIN, true).c_str()); fs.add_glyph(get_mons_glyph(mon)); fs.cprintf(") in view: (%d,%d), see_cell: %s", mon->pos().x, mon->pos().y, you.see_cell(mon->pos())? "yes" : "no"); formatted_mpr(fs, MSGCH_WARN); #endif return (true); } // If command repetition is being used to imitate the rest command, // then everything interrupts it. if (crawl_state.repeat_cmd == CMD_MOVE_NOWHERE || crawl_state.repeat_cmd == CMD_SEARCH) { if (ai == AI_FULL_MP) crawl_state.cancel_cmd_repeat("Magic restored."); else if (ai == AI_FULL_HP) crawl_state.cancel_cmd_repeat("HP restored."); else crawl_state.cancel_cmd_repeat("Command repetition interrupted."); return (true); } if (crawl_state.cmd_repeat_started_unsafe) return (false); if (ai == AI_HIT_MONSTER) { // This check is for when command repetition is used to // whack away at a 0xp monster, since the player feels safe // when the only monsters around are 0xp. const monsters* mon = static_cast(at.data); if (mons_class_flag(mon->type, M_NO_EXP_GAIN) && mon->visible_to(&you)) { return (false); } crawl_state.cancel_cmd_repeat("Command repetition interrupted."); return (true); } return (false); } void game_state::reset_cmd_repeat() { repeat_cmd = CMD_NO_CMD; cmd_repeat_count = 0; cmd_repeat_goal = 0; cmd_repeat_start = false; prev_repetition_turn = 0; repeat_cmd_keys.clear(); } void game_state::reset_cmd_again() { doing_prev_cmd_again = false; prev_cmd = CMD_NO_CMD; prev_cmd_keys.clear(); } /////////////////////////////////////////////////////////// // Keeping track of which god is currently doing something /////////////////////////////////////////////////////////// god_act_state::god_act_state() { reset(); } void god_act_state::reset() { which_god = GOD_NO_GOD; retribution = false; depth = 0; } bool game_state::is_god_acting() const { ASSERT(god_act.depth >= 0); ASSERT(!(god_act.depth > 0 && god_act.which_god == GOD_NO_GOD)); ASSERT(!(god_act.depth == 0 && god_act.which_god != GOD_NO_GOD)); ASSERT(!(god_act.depth == 0 && god_act_stack.size() > 0)); return (god_act.depth > 0); } bool game_state::is_god_retribution() const { ASSERT(is_god_acting()); return (god_act.retribution); } god_type game_state::which_god_acting() const { return god_act.which_god; } void game_state::inc_god_acting(bool is_retribution) { inc_god_acting(you.religion, is_retribution); } void game_state::inc_god_acting(god_type which_god, bool is_retribution) { ASSERT(which_god != GOD_NO_GOD); if (god_act.which_god != GOD_NO_GOD && god_act.which_god != which_god) { ASSERT(god_act.depth >= 1); god_act_stack.push_back(god_act); god_act.reset(); } god_act.which_god = which_god; god_act.retribution = is_retribution || god_act.retribution; god_act.depth++; } void game_state::dec_god_acting() { dec_god_acting(you.religion); } void game_state::dec_god_acting(god_type which_god) { ASSERT(which_god != GOD_NO_GOD); ASSERT(god_act.depth > 0); ASSERT(god_act.which_god == which_god); god_act.depth--; if (god_act.depth == 0) { god_act.reset(); if (god_act_stack.size() > 0) { god_act = god_act_stack[god_act_stack.size() - 1]; god_act_stack.pop_back(); ASSERT(god_act.depth >= 1); ASSERT(god_act.which_god != GOD_NO_GOD); ASSERT(god_act.which_god != which_god); } } } void game_state::clear_god_acting() { ASSERT(!is_god_acting()); ASSERT(god_act_stack.size() == 0); god_act.reset(); } std::vector game_state::other_gods_acting() const { ASSERT(is_god_acting()); return god_act_stack; } bool game_state::is_mon_acting() const { return (mon_act != NULL); } monsters* game_state::which_mon_acting() const { return (mon_act); } void game_state::inc_mon_acting(monsters* mon) { ASSERT(!invalid_monster(mon)); if (mon_act != NULL) mon_act_stack.push_back(mon_act); mon_act = mon; } void game_state::dec_mon_acting(monsters* mon) { ASSERT(mon_act == mon); mon_act = NULL; const unsigned int size = mon_act_stack.size(); if (size > 0) { mon_act = mon_act_stack[size - 1]; ASSERT(!invalid_monster(mon_act)); mon_act_stack.pop_back(); } } void game_state::clear_mon_acting() { mon_act = NULL; mon_act_stack.clear(); } void game_state::mon_gone(monsters* mon) { for (unsigned int i = 0, size = mon_act_stack.size(); i < size; i++) { if (mon_act_stack[i] == mon) { mon_act_stack.erase(mon_act_stack.begin() + i); i--; size--; } } if (mon_act == mon) dec_mon_acting(mon); } void game_state::dump() { fprintf(stderr, EOL "Game state:" EOL EOL); fprintf(stderr, "mouse_enabled: %d, waiting_for_command: %d, " "terminal_resized: %d" EOL, mouse_enabled, waiting_for_command, terminal_resized); fprintf(stderr, "io_inited: %d, need_save: %d, saving_game: %d, " "updating_scores: %d:" EOL, io_inited, need_save, saving_game, updating_scores); fprintf(stderr, "seen_hups: %d, map_stat_gen: %d, arena: %d, " "arena_suspended: %d, unicode_ok: %d" EOL, seen_hups, map_stat_gen, arena, arena_suspended, unicode_ok); fprintf(stderr, EOL); // Arena mode can change behavior of the rest of the code and/or lead // to asserts. unwind_bool _arena(arena, false); unwind_bool _arena_suspended(arena_suspended, false); if (!startup_errors.empty()) { fprintf(stderr, "Startup errors:" EOL); for (unsigned int i = 0; i < startup_errors.size(); i++) fprintf(stderr, "%s" EOL, startup_errors[i].c_str()); fprintf(stderr, EOL); } fprintf(stderr, "prev_cmd = %s" EOL, command_to_name(prev_cmd).c_str()); if (doing_prev_cmd_again) { fprintf(stderr, "Doing prev_cmd again with keys: "); for (unsigned int i = 0; i < prev_cmd_keys.size(); i++) fprintf(stderr, "%d, ", prev_cmd_keys[i]); fprintf(stderr, EOL); fprintf(stderr, "As ASCII keys: "); for (unsigned int i = 0; i < prev_cmd_keys.size(); i++) fprintf(stderr, "%c", (char) prev_cmd_keys[i]); fprintf(stderr, EOL EOL); } fprintf(stderr, "repeat_cmd = %s" EOL, command_to_name(repeat_cmd).c_str()); if (cmd_repeat_count > 0 || cmd_repeat_goal > 0) { fprintf(stderr, "Doing command repetition:" EOL); fprintf(stderr, "cmd_repeat_start:%d, cmd_repeat_count: %d, " "cmd_repeat_goal:%d" EOL "prev_cmd_repeat_goal: %d" EOL, cmd_repeat_start, cmd_repeat_count, cmd_repeat_goal, prev_cmd_repeat_goal); fprintf(stderr, "Keys being repeated: "); for (unsigned int i = 0; i < repeat_cmd_keys.size(); i++) fprintf(stderr, "%d, ", repeat_cmd_keys[i]); fprintf(stderr, EOL); fprintf(stderr, "As ASCII keys: "); for (unsigned int i = 0; i < repeat_cmd_keys.size(); i++) fprintf(stderr, "%c", (char) repeat_cmd_keys[i]); fprintf(stderr, EOL); } fprintf(stderr, EOL); if (god_act.which_god != GOD_NO_GOD || god_act.depth != 0) { fprintf(stderr, "God %s currently acting with depth %d" EOL EOL, god_name(god_act.which_god).c_str(), god_act.depth); } if (god_act_stack.size() != 0) { fprintf(stderr, "Other gods acting:" EOL); for (unsigned int i = 0; i < god_act_stack.size(); i++) fprintf(stderr, "God %s with depth %d" EOL, god_name(god_act_stack[i].which_god).c_str(), god_act_stack[i].depth); fprintf(stderr, EOL EOL); } if (mon_act != NULL) { fprintf(stderr, "%s currently acting:" EOL EOL, debug_mon_str(mon_act).c_str()); debug_dump_mon(mon_act, true); } if (mon_act_stack.size() != 0) { fprintf(stderr, "Others monsters acting:" EOL); for (unsigned int i = 0; i < mon_act_stack.size(); i++) fprintf(stderr, " %s" EOL, debug_mon_str(mon_act_stack[i]).c_str()); } }