summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--crawl-ref/docs/crawl_manual.txt7
-rw-r--r--crawl-ref/source/abl-show.cc11
-rw-r--r--crawl-ref/source/acr.cc604
-rw-r--r--crawl-ref/source/cio.cc15
-rw-r--r--crawl-ref/source/command.cc2
-rw-r--r--crawl-ref/source/debug.cc15
-rw-r--r--crawl-ref/source/decks.cc8
-rw-r--r--crawl-ref/source/defines.h1
-rw-r--r--crawl-ref/source/delay.cc14
-rw-r--r--crawl-ref/source/direct.cc151
-rw-r--r--crawl-ref/source/enum.h15
-rw-r--r--crawl-ref/source/externs.h4
-rw-r--r--crawl-ref/source/files.cc6
-rw-r--r--crawl-ref/source/food.cc5
-rw-r--r--crawl-ref/source/invent.cc1
-rw-r--r--crawl-ref/source/it_use3.cc4
-rw-r--r--crawl-ref/source/item_use.cc5
-rw-r--r--crawl-ref/source/items.cc14
-rw-r--r--crawl-ref/source/macro.cc193
-rw-r--r--crawl-ref/source/macro.h36
-rw-r--r--crawl-ref/source/makefile.obj1
-rw-r--r--crawl-ref/source/message.cc8
-rw-r--r--crawl-ref/source/misc.cc12
-rw-r--r--crawl-ref/source/misc.h5
-rw-r--r--crawl-ref/source/monstuff.cc18
-rw-r--r--crawl-ref/source/player.cc8
-rw-r--r--crawl-ref/source/spells1.cc12
-rw-r--r--crawl-ref/source/spl-book.cc7
-rw-r--r--crawl-ref/source/spl-cast.cc18
-rw-r--r--crawl-ref/source/state.cc235
-rw-r--r--crawl-ref/source/state.h42
-rw-r--r--crawl-ref/source/stuff.cc14
-rw-r--r--crawl-ref/source/view.h1
33 files changed, 1454 insertions, 38 deletions
diff --git a/crawl-ref/docs/crawl_manual.txt b/crawl-ref/docs/crawl_manual.txt
index 3791b53304..fa34e0386e 100644
--- a/crawl-ref/docs/crawl_manual.txt
+++ b/crawl-ref/docs/crawl_manual.txt
@@ -2157,6 +2157,11 @@ Other game-playing commands:
Ctrl-A Toggle autopickup. Note that encounters with
invisible monsters always turns autopickup off.
You need to switch it on with Ctrl-A afterwards.
+ Ctrl-V Toggle autoprayer. Note that encounters with
+ invisible monsters always turns autopickup off.
+ You need to switch it on with Ctrl-V afterwards.
+ ` Re-do previous command
+ 0 Repeat next command a given number of times
Non-game playing commands:
? The help menu.
@@ -2168,7 +2173,7 @@ Non-game playing commands:
: Add note to dump file (see option take_notes).
?: Read the notes in-game.
~ Add or save macros and key mappings.
- = Reassign inventory/spell/abilities letters.
+ = Reassign inventory/spell/abilities letters.
Stashes:
Ctrl-F Find. This searches in stashes and shops, you
diff --git a/crawl-ref/source/abl-show.cc b/crawl-ref/source/abl-show.cc
index 3770c286a8..11f454d2db 100644
--- a/crawl-ref/source/abl-show.cc
+++ b/crawl-ref/source/abl-show.cc
@@ -63,6 +63,7 @@
#include "spells2.h"
#include "spells3.h"
#include "spells4.h"
+#include "state.h"
#include "stuff.h"
#include "transfor.h"
#include "tutorial.h"
@@ -811,6 +812,7 @@ bool activate_ability()
if ( talents.empty() )
{
mpr("Sorry, you're not good enough to have a special ability.");
+ crawl_state.zero_turns_taken();
return false;
}
if ( you.duration[DUR_CONF] )
@@ -819,6 +821,7 @@ bool activate_ability()
if ( talents.empty() )
{
mpr("You're too confused!");
+ crawl_state.zero_turns_taken();
return false;
}
}
@@ -857,6 +860,7 @@ bool activate_ability()
if ( selected < 0 )
{
mpr("You can't do that.");
+ crawl_state.zero_turns_taken();
return (false);
}
}
@@ -886,6 +890,7 @@ static bool activate_talent(const talent& tal)
if (hungerCheck && you.hunger_state <= HS_STARVING)
{
mpr("You're too hungry.");
+ crawl_state.zero_turns_taken();
return (false);
}
@@ -893,10 +898,16 @@ static bool activate_talent(const talent& tal)
// check that we can afford to pay the costs
if (!enough_mp( abil.mp_cost, false ))
+ {
+ crawl_state.zero_turns_taken();
return (false);
+ }
if (!enough_hp( abil.hp_cost, false ))
+ {
+ crawl_state.zero_turns_taken();
return (false);
+ }
// no turning back now... {dlb}
if (random2avg(100, 3) < tal.fail)
diff --git a/crawl-ref/source/acr.cc b/crawl-ref/source/acr.cc
index 7089da5f34..13e9a79ece 100644
--- a/crawl-ref/source/acr.cc
+++ b/crawl-ref/source/acr.cc
@@ -148,6 +148,8 @@ 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] =
{
@@ -170,6 +172,9 @@ 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();
#ifdef DGL_SIMPLE_MESSAGING
static void read_messages();
@@ -387,11 +392,48 @@ static void handle_wizard_command( void )
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 'o':
+ case 'z':
+ case 'Z':
+ break;
+
+ default:
+ crawl_state.cant_cmd_repeat("You cannot repeat that "
+ "wizard command.");
+ return;
+ }
+ }
+
switch (wiz_command)
{
case '?':
@@ -963,16 +1005,156 @@ static void recharge_rods()
}
}
+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_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_GET_VERSION:
+ 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_ADJUST_INVENTORY:
+ 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_TOGGLE_AUTOPRAYER:
+ case CMD_MAKE_NOTE:
+ 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?");
+
+ 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;
/*
- This function handles the player's input. It's called from main(), from
- inside an endless loop.
+ * This function handles the player's input. It's called from main(),
+ * from inside an endless loop.
*/
static void input()
{
+ 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();
+ getchm();
+ return;
+ }
+
you.turn_is_over = false;
prep_input();
@@ -1015,6 +1197,9 @@ static void input()
if ( you.duration[DUR_PARALYSIS] )
{
+ crawl_state.cancel_cmd_repeat("Paralyzed, cancelling command "
+ "repetition.");
+
world_reacts();
return;
}
@@ -1047,7 +1232,17 @@ static void input()
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;
+ if (!crawl_state.is_repeating_cmd()
+ && !crawl_state.doing_prev_cmd_again)
+ {
+ crawl_state.input_line_strs.clear();
+ }
+
{
// Enable the cursor to read input. The cursor stays on while
// the command is being processed, so subsidiary prompts
@@ -1061,6 +1256,12 @@ static void input()
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;
+ }
+
if (cmd != CMD_MOUSE_MOVE)
c_input_reset(false);
@@ -1070,8 +1271,17 @@ static void input()
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 (need_to_autoinscribe())
@@ -1087,6 +1297,8 @@ static void input()
else
viewwindow(true, false);
+ update_replay_state();
+
if (you.num_turns != -1)
{
PlaceInfo& curr_PlaceInfo = you.get_place_info();
@@ -1332,6 +1544,51 @@ void process_command( command_type cmd )
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;
@@ -1785,6 +2042,14 @@ void process_command( command_type cmd )
version();
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)
@@ -2637,6 +2902,7 @@ command_type keycode_to_command( keycode_type 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;
case 'b': return CMD_MOVE_DOWN_LEFT;
case 'h': return CMD_MOVE_LEFT;
case 'j': return CMD_MOVE_DOWN;
@@ -2717,8 +2983,9 @@ command_type keycode_to_command( keycode_type key )
case '(': return CMD_LIST_WEAPONS;
case '\\': return CMD_DISPLAY_KNOWN_OBJECTS;
case '\'': return CMD_WEAPON_SWAP;
+ case '`': return CMD_PREV_CMD_AGAIN;
- case '0': return CMD_NO_CMD;
+ case '0': return CMD_REPEAT_CMD;
case '5': return CMD_REST;
case CONTROL('B'): return CMD_OPEN_DOOR_DOWN_LEFT;
@@ -2978,7 +3245,6 @@ static void close_door(int door_x, int door_y)
}
} // end open_door()
-
// initialise whole lot of stuff...
// returns true if a new character
static bool initialise(void)
@@ -3115,6 +3381,8 @@ static bool initialise(void)
activate_notes(true);
+ add_key_recorder(&repeat_again_rec);
+
return (newc);
}
@@ -3205,6 +3473,8 @@ static void move_player(int move_x, int move_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]
@@ -3275,6 +3545,7 @@ static void move_player(int move_x, int move_y)
move_y = 0;
you.turn_is_over = 0;
+ crawl_state.cancel_cmd_repeat();
}
if (you.running == RMODE_START)
@@ -3314,3 +3585,326 @@ static void move_player(int move_x, int move_y)
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 rercording 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[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();
+}
diff --git a/crawl-ref/source/cio.cc b/crawl-ref/source/cio.cc
index 4296b65654..bc9909c29b 100644
--- a/crawl-ref/source/cio.cc
+++ b/crawl-ref/source/cio.cc
@@ -108,6 +108,19 @@ void get_input_line( char *const buff, int len )
{
buff[0] = 0; // just in case
+ if (crawl_state.is_replaying_keys())
+ {
+ ASSERT(crawl_state.input_line_curr <
+ crawl_state.input_line_strs.size());
+
+ unsigned int curr = crawl_state.input_line_curr++;
+ std::string &line = crawl_state.input_line_strs[curr];
+
+ strcpy(buff, line.c_str());
+
+ return;
+ }
+
#if defined(UNIX)
get_input_line_from_curses( buff, len ); // implemented in libunix.cc
#elif defined(WIN32CONSOLE)
@@ -137,6 +150,8 @@ void get_input_line( char *const buff, int len )
else
break;
}
+
+ crawl_state.input_line_strs.push_back(buff);
}
// Hacky wrapper around getch() that returns CK_ codes for keys
diff --git a/crawl-ref/source/command.cc b/crawl-ref/source/command.cc
index eb4c5fee51..e10ec51b62 100644
--- a/crawl-ref/source/command.cc
+++ b/crawl-ref/source/command.cc
@@ -1008,6 +1008,8 @@ void list_commands(bool wizard, int hotkey)
"<w>p</w> : Pray\n"
"<w>Z</w> : cast a spell\n"
"<w>!</w> : shout or command allies\n"
+ "<w>`</w> : re-do previous command\n"
+ "<w>0</w> : repeat next command # of times\n"
" \n",
true, true, cmdhelp_textfilter);
diff --git a/crawl-ref/source/debug.cc b/crawl-ref/source/debug.cc
index ff127f49be..bc2307c6c4 100644
--- a/crawl-ref/source/debug.cc
+++ b/crawl-ref/source/debug.cc
@@ -82,6 +82,7 @@
#include "skills2.h"
#include "spl-cast.h"
#include "spl-util.h"
+#include "state.h"
#include "stuff.h"
#include "terrain.h"
#include "traps.h"
@@ -328,7 +329,11 @@ void cast_spec_spell(void)
if (spell == -1)
canned_msg( MSG_OK );
else
- your_spells( static_cast<spell_type>(spell), 0, false );
+ if (your_spells( static_cast<spell_type>(spell), 0, false )
+ == SPRET_ABORT)
+ {
+ crawl_state.cancel_cmd_repeat();
+ }
}
#endif
@@ -355,7 +360,11 @@ void cast_spec_spell_name(void)
if (strstr( strlwr(spname), strlwr(specs) ) != NULL)
{
- your_spells(static_cast<spell_type>(i), 0, false);
+ if (your_spells(static_cast<spell_type>(i), 0, false)
+ == SPRET_ABORT)
+ {
+ crawl_state.cancel_cmd_repeat();
+ }
return;
}
}
@@ -1250,6 +1259,8 @@ static void dump_item( const char *name, int num, const item_def &item )
get_ident_type( item.base_type, item.sub_type ) );
mprf(" x: %d; y: %d; link: %d", item.x, item.y, item.link );
+
+ crawl_state.cancel_cmd_repeat();
}
//---------------------------------------------------------------
diff --git a/crawl-ref/source/decks.cc b/crawl-ref/source/decks.cc
index 5a34d2554d..38ec142228 100644
--- a/crawl-ref/source/decks.cc
+++ b/crawl-ref/source/decks.cc
@@ -38,6 +38,7 @@
#include "spells4.h"
#include "spl-cast.h"
#include "spl-util.h"
+#include "state.h"
#include "stuff.h"
#include "terrain.h"
#include "transfor.h"
@@ -248,6 +249,7 @@ bool choose_deck_and_draw()
if ( !is_deck(deck) )
{
mpr("That isn't a deck!");
+ crawl_state.zero_turns_taken();
return false;
}
evoke_deck(deck);
@@ -261,12 +263,14 @@ bool deck_peek()
if ( !wielding_deck() )
{
mpr("You aren't wielding a deck!");
+ crawl_state.zero_turns_taken();
return false;
}
item_def& item(you.inv[you.equip[EQ_WEAPON]]);
if ( item.plus2 != 0 )
{
mpr("You already know what the next card will be.");
+ crawl_state.zero_turns_taken();
return false;
}
@@ -297,12 +301,14 @@ bool deck_stack()
if ( !wielding_deck() )
{
mpr("You aren't wielding a deck!");
+ crawl_state.zero_turns_taken();
return false;
}
item_def& item(you.inv[you.equip[EQ_WEAPON]]);
if ( item.plus2 != 0 )
{
mpr("You can't stack a marked deck.");
+ crawl_state.zero_turns_taken();
return false;
}
const int num_to_stack = (item.plus < 5 ? item.plus : 5);
@@ -357,6 +363,7 @@ bool deck_triple_draw()
if ( !wielding_deck() )
{
mpr("You aren't wielding a deck!");
+ crawl_state.zero_turns_taken();
return false;
}
@@ -366,6 +373,7 @@ bool deck_triple_draw()
if ( item.plus2 != 0 )
{
mpr("You can't triple draw from a marked deck.");
+ crawl_state.zero_turns_taken();
return false;
}
diff --git a/crawl-ref/source/defines.h b/crawl-ref/source/defines.h
index 65d11999cf..ddda72ddab 100644
--- a/crawl-ref/source/defines.h
+++ b/crawl-ref/source/defines.h
@@ -298,5 +298,6 @@
#define KEY_MACRO_MORE_PROTECT -10
#define KEY_MACRO_DISABLE_MORE -1
#define KEY_MACRO_ENABLE_MORE -2
+#define KEY_REPEAT_KEYS -3
#endif
diff --git a/crawl-ref/source/delay.cc b/crawl-ref/source/delay.cc
index 9097e371c2..b2b443c0d1 100644
--- a/crawl-ref/source/delay.cc
+++ b/crawl-ref/source/delay.cc
@@ -37,6 +37,7 @@
#include "randart.h"
#include "religion.h"
#include "spl-util.h"
+#include "state.h"
#include "stuff.h"
#include "travel.h"
#include "tutorial.h"
@@ -102,6 +103,8 @@ static void clear_pending_delays()
void start_delay( delay_type type, int turns, int parm1, int parm2 )
/***********************************************************/
{
+ ASSERT(!crawl_state.is_repeating_cmd() || type == DELAY_MACRO);
+
delay_queue_item delay;
delay.type = type;
@@ -146,6 +149,8 @@ void stop_delay( void )
delay_queue_item delay = you.delay_queue.front();
+ ASSERT(!crawl_state.is_repeating_cmd() || delay.type == DELAY_MACRO);
+
const bool butcher_swap_warn =
delay.type == DELAY_BUTCHER
&& (you.delay_queue.size() >= 2
@@ -304,6 +309,8 @@ void handle_delay( void )
delay_queue_item &delay = you.delay_queue.front();
+ ASSERT(!crawl_state.is_repeating_cmd() || delay.type == DELAY_MACRO);
+
// Run delays and Lua delays don't have a specific end time.
if (is_run_delay(delay.type))
{
@@ -992,7 +999,7 @@ inline static void monster_warning(activity_interrupt_type ai,
if (!mon->visible())
return;
#ifndef DEBUG_DIAGNOSTICS
- if (at.context != "uncharm")
+ if (at.context == "newly seen")
{
// Only say "comes into view" if the monster wasn't in view
// during the previous turn.
@@ -1084,7 +1091,10 @@ bool interrupt_activity( activity_interrupt_type ai,
const activity_interrupt_data &at )
{
paranoid_option_disable(ai, at);
-
+
+ if (crawl_state.is_repeating_cmd())
+ return interrupt_cmd_repeat(ai, at);
+
const int delay = current_delay_action();
if (delay == DELAY_NOT_DELAYED)
diff --git a/crawl-ref/source/direct.cc b/crawl-ref/source/direct.cc
index 9f246d67a9..02ef032460 100644
--- a/crawl-ref/source/direct.cc
+++ b/crawl-ref/source/direct.cc
@@ -46,6 +46,7 @@
#include "mon-util.h"
#include "player.h"
#include "shopping.h"
+#include "state.h"
#include "stuff.h"
#include "spells4.h"
#include "stash.h"
@@ -234,6 +235,135 @@ static void draw_ray_glyph(const coord_def &pos, int colour,
putch(glych);
}
+// We handle targeting for repeating commands and re-doing the
+// previous command differently (i.e., not just letting the keys
+// stuffed into the macro buffer replay as-is) because if the player
+// targeted a monster using the movement keys and the monster then
+// moved between repititions, then simply replaying the keys in the
+// buffer will target an empty square.
+static void direction_again(dist& moves, targeting_type restricts,
+ targ_mode_type mode, bool just_looking,
+ const char *prompt, targeting_behaviour *beh)
+{
+ moves.isValid = false;
+ moves.isTarget = false;
+ moves.isMe = false;
+ moves.isCancel = false;
+ moves.isEndpoint = false;
+ moves.choseRay = false;
+
+ if (you.prev_targ == MHITNOT && you.prev_grd_targ == coord_def(0, 0))
+ {
+ moves.isCancel = true;
+ crawl_state.cancel_cmd_repeat();
+ return;
+ }
+
+ int targ_types = 0;
+ if (you.prev_targ != MHITNOT && you.prev_targ != MHITYOU)
+ targ_types++;
+ if (you.prev_targ == MHITYOU)
+ targ_types++;
+ if (you.prev_grd_targ != coord_def(0, 0))
+ targ_types++;
+ ASSERT(targ_types == 1);
+
+ // Discard keys until we get to a set-target command
+ command_type key_command;
+
+ while (crawl_state.is_replaying_keys())
+ {
+ key_command = beh->get_command();
+
+ if (key_command == CMD_TARGET_PREV_TARGET
+ || key_command == CMD_TARGET_SELECT_ENDPOINT
+ || key_command == CMD_TARGET_SELECT
+ || key_command == CMD_TARGET_MAYBE_PREV_TARGET)
+ {
+ break;
+ }
+ }
+
+ if (!crawl_state.is_replaying_keys())
+ {
+ moves.isCancel = true;
+
+ mpr("Ran out of keys.");
+
+ return;
+ }
+
+ if (key_command == CMD_TARGET_SELECT_ENDPOINT)
+ moves.isEndpoint = true;
+
+ if (you.prev_grd_targ != coord_def(0, 0))
+ {
+ if (!see_grid(you.prev_grd_targ))
+ {
+ moves.isCancel = true;
+
+ crawl_state.cancel_cmd_repeat("You can no longer see the dungeon "
+ "square you previously targeted.");
+ return;
+ }
+ else if (you.prev_grd_targ.x == you.x_pos
+ && you.prev_grd_targ.y == you.y_pos)
+ {
+ moves.isCancel = true;
+
+ crawl_state.cancel_cmd_repeat("You are now standing on your "
+ "previously targeted dungeon "
+ "square.");
+ return;
+ }
+
+ moves.tx = you.prev_grd_targ.x;
+ moves.ty = you.prev_grd_targ.y;
+
+ ray_def ray;
+ find_ray(you.x_pos, you.y_pos, moves.tx, moves.ty, true, ray,
+ 0, true);
+ moves.ray = ray;
+ }
+ else if (you.prev_targ == MHITYOU)
+ {
+ moves.isMe = true;
+ moves.tx = you.x_pos;
+ moves.ty = you.y_pos;
+
+ // Discard 'Y' player gave to yesno()
+ if (mode == TARG_ENEMY && Options.confirm_self_target)
+ getchm();
+ }
+ else
+ {
+ const monsters *montarget = &menv[you.prev_targ];
+
+ if (!mons_near(montarget) ||
+ !player_monster_visible( montarget ))
+ {
+ moves.isCancel = true;
+
+ crawl_state.cancel_cmd_repeat("Your target is gone.");
+
+ return;
+ }
+
+ moves.tx = montarget->x;
+ moves.ty = montarget->y;
+
+ ray_def ray;
+ find_ray(you.x_pos, you.y_pos, moves.tx, moves.ty, true, ray,
+ 0, true);
+ moves.ray = ray;
+ }
+
+ moves.isValid = true;
+ moves.isTarget = true;
+
+ return;
+}
+
//---------------------------------------------------------------
//
// direction
@@ -264,7 +394,14 @@ void direction(dist& moves, targeting_type restricts,
beh = &stock_behaviour;
beh->just_looking = just_looking;
-
+
+ if (crawl_state.is_replaying_keys() && restricts != DIR_DIR)
+ {
+ direction_again(moves, restricts, mode, just_looking,
+ prompt, beh);
+ return;
+ }
+
// NOTE: Even if just_looking is set, moves is still interesting,
// because we can travel there!
@@ -536,10 +673,18 @@ void direction(dist& moves, targeting_type restricts,
moves.isValid = true;
moves.isTarget = true;
loop_done = true;
+
+ you.prev_grd_targ = coord_def(0, 0);
+
// maybe we should except just_looking here?
mid = mgrd[moves.tx][moves.ty];
+
if ( mid != NON_MONSTER )
you.prev_targ = mid;
+ else if (moves.tx == you.x_pos && moves.ty == you.y_pos)
+ you.prev_targ = MHITYOU;
+ else
+ you.prev_grd_targ = coord_def(moves.tx, moves.ty);
break;
case CMD_TARGET_OBJ_CYCLE_BACK:
@@ -1922,7 +2067,9 @@ targeting_behaviour::~targeting_behaviour()
int targeting_behaviour::get_key()
{
- flush_input_buffer(FLUSH_BEFORE_COMMAND);
+ if (!crawl_state.is_replaying_keys())
+ flush_input_buffer(FLUSH_BEFORE_COMMAND);
+
return unmangle_direction_keys(
getchm(KC_TARGETING), KC_TARGETING, false, false);
}
diff --git a/crawl-ref/source/enum.h b/crawl-ref/source/enum.h
index d5b091f080..dbdef1f880 100644
--- a/crawl-ref/source/enum.h
+++ b/crawl-ref/source/enum.h
@@ -633,8 +633,17 @@ enum command_type
CMD_ENABLE_MORE,
// [ds] Silently ignored, requests another round of input.
- CMD_NEXT_CMD
+ CMD_NEXT_CMD,
+ // Repeat previous command
+ CMD_PREV_CMD_AGAIN,
+
+ // Repeat next command a given number of times
+ CMD_REPEAT_CMD,
+
+ // Stick the keyspresses of the command to be repeated into the
+ // input buffer.
+ CMD_REPEAT_KEYS
};
enum conduct_type
@@ -1114,6 +1123,10 @@ enum flush_reason_type
FLUSH_ON_PROMPT, // flush on MSGCH_PROMPT messages
FLUSH_ON_UNSAFE_YES_OR_NO_PROMPT, // flush when !safe set to yesno()
FLUSH_LUA, // flush when Lua wants to flush
+ FLUSH_KEY_REPLAY_CANCEL, // flush when key replay is cancelled
+ FLUSH_ABORT_MACRO, // something wrong with macro being
+ // processed, so stop it
+ FLUSH_REPLAY_SETUP_FAILURE, // setup for key replay failed
NUM_FLUSH_REASONS
};
diff --git a/crawl-ref/source/externs.h b/crawl-ref/source/externs.h
index 083c5c83ce..8db0139a43 100644
--- a/crawl-ref/source/externs.h
+++ b/crawl-ref/source/externs.h
@@ -23,6 +23,7 @@
#include <set>
#include <memory>
#include <cstdlib>
+#include <deque>
#include <time.h>
@@ -464,6 +465,8 @@ typedef std::vector<delay_queue_item> delay_queue_type;
class KillMaster;
+
+
class player : public actor
{
public:
@@ -476,6 +479,7 @@ public:
bool just_autoprayed; // autopray just kicked in
unsigned char prev_targ;
+ coord_def prev_grd_targ;
char your_name[kNameLen];
species_type species;
diff --git a/crawl-ref/source/files.cc b/crawl-ref/source/files.cc
index b4b57ec247..7e2cf37bed 100644
--- a/crawl-ref/source/files.cc
+++ b/crawl-ref/source/files.cc
@@ -841,7 +841,8 @@ bool load( dungeon_feature_type stair_taken, int load_mode,
}
}
- you.prev_targ = MHITNOT;
+ you.prev_targ = MHITNOT;
+ you.prev_grd_targ = coord_def(0, 0);
// We clear twice - on save and on load.
// Once would be enough...
@@ -1026,7 +1027,8 @@ void save_level(int level_saved, level_area_type old_ltype,
where_were_you, old_ltype,
false );
- you.prev_targ = MHITNOT;
+ you.prev_targ = MHITNOT;
+ you.prev_grd_targ = coord_def(0, 0);
FILE *saveFile = fopen(cha_fil.c_str(), "wb");
diff --git a/crawl-ref/source/food.cc b/crawl-ref/source/food.cc
index 260100732d..f2b8301a46 100644
--- a/crawl-ref/source/food.cc
+++ b/crawl-ref/source/food.cc
@@ -46,6 +46,7 @@
#include "religion.h"
#include "skills2.h"
#include "spells2.h"
+#include "state.h"
#include "stuff.h"
#include "transfor.h"
#include "tutorial.h"
@@ -490,19 +491,23 @@ bool eat_food(bool run_hook)
if (you.is_undead == US_UNDEAD)
{
mpr("You can't eat.");
+ crawl_state.zero_turns_taken();
return (false);
}
if (you.hunger >= 11000)
{
mpr("You're too full to eat anything.");
+ crawl_state.zero_turns_taken();
return (false);
}
// If user hook ran, we don't know whether something
// was eaten or not...
if (run_hook && userdef_eat_food())
+ {
return (false);
+ }
if (igrd[you.x_pos][you.y_pos] != NON_ITEM)
{
diff --git a/crawl-ref/source/invent.cc b/crawl-ref/source/invent.cc
index 23d47a1504..696dbf5bff 100644
--- a/crawl-ref/source/invent.cc
+++ b/crawl-ref/source/invent.cc
@@ -1165,6 +1165,7 @@ int prompt_invent_item( const char *prompt,
{
if (count)
*count = items[0].quantity;
+
redraw_screen();
mesclr( true );
}
diff --git a/crawl-ref/source/it_use3.cc b/crawl-ref/source/it_use3.cc
index 417fd7f2d7..ac3c5f10e5 100644
--- a/crawl-ref/source/it_use3.cc
+++ b/crawl-ref/source/it_use3.cc
@@ -49,6 +49,7 @@
#include "spl-book.h"
#include "spl-cast.h"
#include "spl-util.h"
+#include "state.h"
#include "stuff.h"
#include "view.h"
@@ -379,6 +380,7 @@ bool evoke_wielded( void )
else if (wield == -1)
{
mpr("You aren't wielding anything!");
+ crawl_state.zero_turns_taken();
return (false);
}
@@ -708,6 +710,8 @@ bool evoke_wielded( void )
if (!unevokable)
you.turn_is_over = true;
+ else
+ crawl_state.zero_turns_taken();
return (did_work);
} // end evoke_wielded()
diff --git a/crawl-ref/source/item_use.cc b/crawl-ref/source/item_use.cc
index 76a3aa313a..4970d4fc81 100644
--- a/crawl-ref/source/item_use.cc
+++ b/crawl-ref/source/item_use.cc
@@ -69,6 +69,7 @@
#include "spl-book.h"
#include "spl-cast.h"
#include "spl-util.h"
+#include "state.h"
#include "stuff.h"
#include "transfor.h"
#include "tutorial.h"
@@ -3178,6 +3179,8 @@ bool drink_fountain()
grd[you.x_pos][you.y_pos] = DNGN_DRY_FOUNTAIN_I;
else
grd[you.x_pos][you.y_pos] = DNGN_DRY_FOUNTAIN_II;
+
+ crawl_state.cancel_cmd_repeat();
}
you.turn_is_over = true;
@@ -3553,6 +3556,7 @@ void read_scroll(void)
if (scroll.base_type != OBJ_BOOKS && scroll.base_type != OBJ_SCROLLS)
{
mpr("You can't read that!");
+ crawl_state.zero_turns_taken();
return;
}
@@ -3566,6 +3570,7 @@ void read_scroll(void)
if (silenced(you.x_pos, you.y_pos))
{
mpr("Magic scrolls do not work when you're silenced!");
+ crawl_state.zero_turns_taken();
return;
}
diff --git a/crawl-ref/source/items.cc b/crawl-ref/source/items.cc
index bad4a28d7a..e26558acdc 100644
--- a/crawl-ref/source/items.cc
+++ b/crawl-ref/source/items.cc
@@ -68,6 +68,7 @@
#include "spl-util.h"
#include "stuff.h"
#include "stash.h"
+#include "state.h"
#include "terrain.h"
#include "transfor.h"
#include "tutorial.h"
@@ -156,6 +157,8 @@ static bool item_ok_to_clean(int item)
// unsuccessful cleanup (should be exceedingly rare!)
int cull_items(void)
{
+ crawl_state.cancel_cmd_repeat();
+
// XXX: Not the prettiest of messages, but the player
// deserves to know whenever this kicks in. -- bwr
mpr( "Too many items on level, removing some.", MSGCH_WARN );
@@ -261,6 +264,12 @@ bool dec_inv_item_quantity( int obj, int amount )
you.inv[obj].quantity = 0;
ret = true;
+
+ // If we're repeating a command, the repetitions used up the
+ // item stack being repeated on, so stop rather than move onto
+ // the next stack.
+ crawl_state.cancel_cmd_repeat();
+ crawl_state.cancel_cmd_again();
}
else
{
@@ -280,6 +289,11 @@ bool dec_mitm_item_quantity( int obj, int amount )
if (mitm[obj].quantity <= amount)
{
destroy_item( obj );
+ // If we're repeating a command, the repetitions used up the
+ // item stack being repeated on, so stop rather than move onto
+ // the next stack.
+ crawl_state.cancel_cmd_repeat();
+ crawl_state.cancel_cmd_again();
return (true);
}
diff --git a/crawl-ref/source/macro.cc b/crawl-ref/source/macro.cc
index 219a43943e..803252ad7e 100644
--- a/crawl-ref/source/macro.cc
+++ b/crawl-ref/source/macro.cc
@@ -45,14 +45,15 @@
#include <cstdlib>
#include "cio.h"
+#include "delay.h"
#include "externs.h"
#include "message.h"
+#include "state.h"
#include "stuff.h"
// for trim_string:
#include "initfile.h"
-typedef std::deque<int> keyseq;
typedef std::deque<int> keybuf;
typedef std::map<keyseq,keyseq> macromap;
@@ -73,6 +74,8 @@ static keybuf Buffer;
#define USERFUNCBASE -10000
static std::vector<std::string> userfunctions;
+static std::vector<key_recorder*> recorders;
+
inline int userfunc_index(int key)
{
int index = (key <= USERFUNCBASE? USERFUNCBASE - key : -1);
@@ -406,7 +409,7 @@ static void macro_del( macromap &mapref, keyseq key )
* Adds keypresses from a sequence into the internal keybuffer. Ignores
* macros.
*/
-static void macro_buf_add( const keyseq &actions, bool reverse = false )
+void macro_buf_add( const keyseq &actions, bool reverse)
{
keyseq act;
bool need_more_reset = false;
@@ -426,14 +429,33 @@ static void macro_buf_add( const keyseq &actions, bool reverse = false )
Buffer.insert( reverse? Buffer.begin() : Buffer.end(),
act.begin(), act.end() );
-}
+
+ if (reverse)
+ {
+ for (int i = 0, size_i = recorders.size(); i < size_i; i++)
+ for (int j = act.size() - 1 ; j >= 0; j--)
+ recorders[i]->add_key(act[j], reverse);
+ }
+ else
+ {
+ for (int i = 0, size_i = recorders.size(); i < size_i; i++)
+ for (int j = 0, size_j = act.size(); j < size_j ; j++)
+ recorders[i]->add_key(act[j]);
+ }
+}
/*
* Adds a single keypress into the internal keybuffer.
*/
-void macro_buf_add( int key )
+void macro_buf_add( int key, bool reverse )
{
- Buffer.push_back( key );
+ if (reverse)
+ Buffer.push_front( key );
+ else
+ Buffer.push_back( key );
+
+ for (int i = 0, size = recorders.size(); i < size; i++)
+ recorders[i]->add_key(key, reverse);
}
@@ -491,6 +513,13 @@ static void macro_buf_add_long( keyseq actions,
}
}
+static int macro_keys_left = -1;
+
+bool is_processing_macro()
+{
+ return (macro_keys_left >= 0);
+}
+
/*
* Command macros are only applied from the immediate front of the
* buffer, and only when the game is expecting a command.
@@ -506,11 +535,23 @@ static void macro_buf_apply_command_macro( void )
if (result.size() > 0)
{
+ for (int i = 0, size_i = recorders.size(); i < size_i; i++)
+ recorders[i]->remove_trigger_keys(tmp.size());
+
// Found macro, remove match from front:
for (unsigned int i = 0; i < tmp.size(); i++)
+ {
Buffer.pop_front();
+ if (macro_keys_left >= 0)
+ macro_keys_left--;
+ }
+
+ if (macro_keys_left == -1)
+ macro_keys_left = 0;
+ macro_keys_left += result.size();
macro_buf_add(result, true);
+
break;
}
@@ -519,17 +560,27 @@ static void macro_buf_apply_command_macro( void )
}
/*
- * Removes the earlies keypress from the keybuffer, and returns its
+ * Removes the earliest keypress from the keybuffer, and returns its
* value. If buffer was empty, returns -1;
*/
static int macro_buf_get( void )
{
if (Buffer.size() == 0)
+ {
+ // If we're trying to fetch a new keystroke, then the processing
+ // of the previous keystroke is complete.
+ if (macro_keys_left == 0)
+ macro_keys_left = -1;
+
return (-1);
+ }
int key = Buffer.front();
Buffer.pop_front();
-
+
+ if (macro_keys_left >= 0)
+ macro_keys_left--;
+
return (key);
}
@@ -577,8 +628,17 @@ void macro_save( void )
static keyseq getch_mul( int (*rgetch)() = NULL )
{
keyseq keys;
- int a;
+ int a;
+ // Something's gone wrong with replaying keys if crawl needs to
+ // get new keys from the user.
+ if (crawl_state.is_replaying_keys())
+ {
+ mpr("(Key replay ran out of keys)");
+ crawl_state.cancel_cmd_repeat();
+ crawl_state.cancel_cmd_again();
+ }
+
if (!rgetch)
rgetch = m_getch;
@@ -644,7 +704,26 @@ int getch_with_command_macros( void )
*/
void flush_input_buffer( int reason )
{
- if (Options.flush_input[ reason ])
+ ASSERT(reason != FLUSH_KEY_REPLAY_CANCEL ||
+ crawl_state.is_replaying_keys());
+
+ ASSERT(reason != FLUSH_ABORT_MACRO || is_processing_macro());
+
+ // Any attempt to flush means that the processing of the previously
+ // fetched keystroke is complete.
+ if (macro_keys_left == 0)
+ macro_keys_left = -1;
+
+ if (crawl_state.is_replaying_keys() && reason != FLUSH_ABORT_MACRO
+ && reason != FLUSH_KEY_REPLAY_CANCEL &&
+ reason != FLUSH_REPLAY_SETUP_FAILURE)
+ {
+ return;
+ }
+
+ if (Options.flush_input[ reason ] || reason == FLUSH_ABORT_MACRO
+ || reason == FLUSH_KEY_REPLAY_CANCEL
+ || reason == FLUSH_REPLAY_SETUP_FAILURE)
{
while (!Buffer.empty())
{
@@ -653,6 +732,7 @@ void flush_input_buffer( int reason )
if (key == KEY_MACRO_ENABLE_MORE)
Options.show_more_prompt = true;
}
+ macro_keys_left = -1;
}
}
@@ -822,8 +902,103 @@ bool is_synthetic_key(int key)
case KEY_MACRO_ENABLE_MORE:
case KEY_MACRO_DISABLE_MORE:
case KEY_MACRO_MORE_PROTECT:
+ case KEY_REPEAT_KEYS:
return (true);
default:
return (false);
}
}
+
+key_recorder::key_recorder(key_recorder_callback cb, void* cb_data)
+ : paused(false), call_back(cb), call_back_data(cb_data)
+{
+ keys.clear();
+ macro_trigger_keys.clear();
+}
+
+void key_recorder::add_key(int key, bool reverse)
+{
+ if (paused)
+ return;
+
+ if (call_back)
+ {
+ // Don't record key if true
+ if ((*call_back)(this, key, reverse))
+ return;
+ }
+
+ if (reverse)
+ keys.push_front(key);
+ else
+ keys.push_back(key);
+}
+
+void key_recorder::remove_trigger_keys(int num_keys)
+{
+ ASSERT(num_keys >= 1);
+
+ if (paused)
+ return;
+
+ for (int i = 0; i < num_keys; i++)
+ {
+ ASSERT(keys.size() >= 1);
+
+ int key = keys[keys.size() - 1];
+
+ if (call_back)
+ {
+ // Key wasn't recorded in the first place, so no need to remove
+ // it
+ if ((*call_back)(this, key, true))
+ continue;
+ }
+
+ macro_trigger_keys.push_front(key);
+ keys.pop_back();
+ }
+}
+
+void key_recorder::clear()
+{
+ keys.clear();
+ macro_trigger_keys.clear();
+}
+
+void add_key_recorder(key_recorder* recorder)
+{
+ for (int i = 0, size = recorders.size(); i < size; i++)
+ ASSERT(recorders[i] != recorder);
+
+ recorders.push_back(recorder);
+}
+
+void remove_key_recorder(key_recorder* recorder)
+{
+ std::vector<key_recorder*>::iterator i;
+
+ for(i = recorders.begin(); i != recorders.end(); i++)
+ if (*i == recorder)
+ {
+ recorders.erase(i);
+ return;
+ }
+
+ end(1, true, "remove_key_recorder(): recorder not found\n");
+}
+
+// Add macro trigger keys to beginning of the buffer, then expand
+// them.
+void insert_macro_into_buff(const keyseq& keys)
+{
+ for (int i = (int) keys.size() - 1; i >= 0; i--)
+ macro_buf_add(keys[i], true);
+
+ macro_buf_apply_command_macro();
+}
+
+int get_macro_buf_size()
+{
+ return (Buffer.size());
+}
diff --git a/crawl-ref/source/macro.h b/crawl-ref/source/macro.h
index ff1c01b076..90d0557cda 100644
--- a/crawl-ref/source/macro.h
+++ b/crawl-ref/source/macro.h
@@ -14,6 +14,8 @@
#ifndef MACRO_H
#define MACRO_H
+#include <deque>
+
#ifndef MACRO_CC
#undef getch
@@ -30,6 +32,28 @@ enum KeymapContext {
KC_CONTEXT_COUNT // Must always be the last
};
+class key_recorder;
+typedef bool (*key_recorder_callback)(key_recorder *recorder,
+ int &ch, bool reverse);
+typedef std::deque<int> keyseq;
+
+class key_recorder {
+public:
+ bool paused;
+ keyseq keys;
+ keyseq macro_trigger_keys;
+ key_recorder_callback call_back;
+ void* call_back_data;
+
+public:
+ key_recorder(key_recorder_callback cb = NULL,
+ void* cb_data = NULL);
+
+ void add_key(int key, bool reverse = false);
+ void remove_trigger_keys(int num_keys);
+ void clear();
+};
+
int getchm(int (*rgetch)() = NULL); // keymaps applied (ie for prompts)
int getchm(KeymapContext context, int (*rgetch)() = NULL);
@@ -43,11 +67,21 @@ void macro_save(void);
void macro_userfn(const char *keys, const char *registryname);
-void macro_buf_add(int key);
+void macro_buf_add(int key, bool reverse = false);
+void macro_buf_add(const keyseq &actions, bool reverse = false );
bool is_userfunction(int key);
bool is_synthetic_key(int key);
const char *get_userfunction(int key);
+void add_key_recorder(key_recorder* recorder);
+void remove_key_recorder(key_recorder* recorder);
+
+bool is_processing_macro();
+
+void insert_macro_into_buff(const keyseq& keys);
+
+int get_macro_buf_size();
+
#endif
diff --git a/crawl-ref/source/makefile.obj b/crawl-ref/source/makefile.obj
index cc3e36a36f..8819ab5b27 100644
--- a/crawl-ref/source/makefile.obj
+++ b/crawl-ref/source/makefile.obj
@@ -73,6 +73,7 @@ spl-book.o \
spl-cast.o \
spl-util.o \
sqldbm.o \
+state.o \
stash.o \
stuff.o \
tags.o \
diff --git a/crawl-ref/source/message.cc b/crawl-ref/source/message.cc
index c09c1056b1..bd3abfdc82 100644
--- a/crawl-ref/source/message.cc
+++ b/crawl-ref/source/message.cc
@@ -728,6 +728,14 @@ void more(void)
return;
}
#endif
+
+ if (crawl_state.is_repeating_cmd()
+ && !crawl_state.cmd_repeat_start)
+ {
+ mesclr();
+ return;
+ }
+
if (Options.show_more_prompt && !suppress_messages)
{
char keypress = 0;
diff --git a/crawl-ref/source/misc.cc b/crawl-ref/source/misc.cc
index dcf7df5bdd..9163f37b87 100644
--- a/crawl-ref/source/misc.cc
+++ b/crawl-ref/source/misc.cc
@@ -43,16 +43,19 @@
#include "clua.h"
#include "cloud.h"
#include "delay.h"
+#include "direct.h"
#include "dgnevent.h"
#include "direct.h"
#include "dungeon.h"
#include "files.h"
#include "food.h"
+#include "format.h"
#include "hiscores.h"
#include "it_use2.h"
#include "itemprop.h"
#include "items.h"
#include "lev-pand.h"
+#include "macro.h"
#include "message.h"
#include "mon-util.h"
#include "monstuff.h"
@@ -557,9 +560,11 @@ void up_stairs(dungeon_feature_type force_stair)
ouch(INSTANT_DEATH, 0, KILLED_BY_LEAVING);
}
- you.prev_targ = MHITNOT;
+ you.prev_targ = MHITNOT;
you.pet_target = MHITNOT;
+ you.prev_grd_targ = coord_def(0, 0);
+
if (player_in_branch( BRANCH_VESTIBULE_OF_HELL ))
{
mpr("Thank you for visiting Hell. Please come again soon.");
@@ -803,9 +808,11 @@ void down_stairs( int old_level, dungeon_feature_type force_stair )
you.level_type = LEVEL_DUNGEON;
}
- you.prev_targ = MHITNOT;
+ you.prev_targ = MHITNOT;
you.pet_target = MHITNOT;
+ you.prev_grd_targ = coord_def(0, 0);
+
if (stair_find == DNGN_ENTER_HELL)
{
you.where_are_you = BRANCH_VESTIBULE_OF_HELL;
@@ -1623,4 +1630,3 @@ int speed_to_duration(int speed)
return div_rand_round(100, speed);
}
-
diff --git a/crawl-ref/source/misc.h b/crawl-ref/source/misc.h
index 48145be182..7a192db181 100644
--- a/crawl-ref/source/misc.h
+++ b/crawl-ref/source/misc.h
@@ -19,6 +19,8 @@
struct bolt;
struct dist;
+struct activity_interrupt_data;
+
// last updated 08jan2001 {gdl}
/* ***********************************************************************
@@ -116,4 +118,7 @@ int speed_to_duration(int speed);
bool scramble(void);
+bool interrupt_cmd_repeat( activity_interrupt_type ai,
+ const activity_interrupt_data &at );
+
#endif
diff --git a/crawl-ref/source/monstuff.cc b/crawl-ref/source/monstuff.cc
index db65a5a960..47ecdc7e8d 100644
--- a/crawl-ref/source/monstuff.cc
+++ b/crawl-ref/source/monstuff.cc
@@ -58,6 +58,7 @@
#include "spl-util.h"
#include "spells2.h"
#include "spells4.h"
+#include "state.h"
#include "stuff.h"
#include "terrain.h"
#include "traps.h"
@@ -464,7 +465,13 @@ void monster_die(monsters *monster, killer_type killer, int i, bool silent)
}
if (you.prev_targ == monster_killed)
+ {
you.prev_targ = MHITNOT;
+ crawl_state.cancel_cmd_repeat();
+ }
+
+ if (killer == KILL_YOU)
+ crawl_state.cancel_cmd_repeat();
const bool pet_kill = is_pet_kill(killer, i);
@@ -5313,6 +5320,17 @@ bool monster_descriptor(int which_class, unsigned char which_descriptor)
bool message_current_target()
{
+
+ if (crawl_state.is_replaying_keys())
+ {
+ if (you.prev_targ == MHITNOT || you.prev_targ == MHITYOU)
+ return false;
+
+ const monsters *montarget = &menv[you.prev_targ];
+ return (you.prev_targ != MHITNOT && you.prev_targ != MHITYOU
+ && mons_near(montarget) && player_monster_visible(montarget));
+ }
+
if (you.prev_targ != MHITNOT && you.prev_targ != MHITYOU)
{
const monsters *montarget = &menv[you.prev_targ];
diff --git a/crawl-ref/source/player.cc b/crawl-ref/source/player.cc
index d432fa0e86..3d992a3eb1 100644
--- a/crawl-ref/source/player.cc
+++ b/crawl-ref/source/player.cc
@@ -60,6 +60,7 @@
#include "spells3.h"
#include "spl-util.h"
#include "spells4.h"
+#include "state.h"
#include "stuff.h"
#include "terrain.h"
#include "transfor.h"
@@ -4168,6 +4169,8 @@ bool enough_hp(int minimum, bool suppress_msg)
if (!suppress_msg)
mpr("You haven't enough vitality at the moment.");
+ crawl_state.cancel_cmd_again();
+ crawl_state.cancel_cmd_repeat();
return false;
}
@@ -4181,6 +4184,8 @@ bool enough_mp(int minimum, bool suppress_msg)
if (!suppress_msg)
mpr("You haven't enough magic at the moment.");
+ crawl_state.cancel_cmd_again();
+ crawl_state.cancel_cmd_repeat();
return false;
}
@@ -5022,6 +5027,8 @@ void player::init()
prev_targ = MHITNOT;
pet_target = MHITNOT;
+ prev_grd_targ = coord_def(0, 0);
+
x_pos = 0;
y_pos = 0;
@@ -6044,3 +6051,4 @@ std::vector<PlaceInfo> player::get_all_place_info(bool visited_only,
return list;
}
+
diff --git a/crawl-ref/source/spells1.cc b/crawl-ref/source/spells1.cc
index 7b71d46c16..bd82ad3684 100644
--- a/crawl-ref/source/spells1.cc
+++ b/crawl-ref/source/spells1.cc
@@ -45,6 +45,7 @@
#include "spells3.h"
#include "spells4.h"
#include "spl-util.h"
+#include "state.h"
#include "stuff.h"
#include "terrain.h"
#include "transfor.h"
@@ -65,6 +66,14 @@ int blink(int pow, bool high_level_controlled_blink)
{
dist beam;
+ if (crawl_state.is_repeating_cmd())
+ {
+ crawl_state.cant_cmd_repeat("You can't repeat controlled blinks.");
+ crawl_state.cancel_cmd_again();
+ crawl_state.cancel_cmd_repeat();
+ return(1);
+ }
+
// yes, there is a logic to this ordering {dlb}:
if (scan_randarts(RAP_PREVENT_TELEPORTATION))
mpr("You feel a weird sense of stasis.");
@@ -143,6 +152,9 @@ int blink(int pow, bool high_level_controlled_blink)
}
}
+ crawl_state.cancel_cmd_again();
+ crawl_state.cancel_cmd_repeat();
+
return (1);
} // end blink()
diff --git a/crawl-ref/source/spl-book.cc b/crawl-ref/source/spl-book.cc
index f4fe9263c6..fcb9f7c09e 100644
--- a/crawl-ref/source/spl-book.cc
+++ b/crawl-ref/source/spl-book.cc
@@ -40,6 +40,7 @@
#include "religion.h"
#include "spl-cast.h"
#include "spl-util.h"
+#include "state.h"
#include "stuff.h"
#define SPELLBOOK_SIZE 8
@@ -1477,12 +1478,14 @@ int staff_spell( int staff )
if (food && (you.hunger_state <= HS_STARVING || you.hunger <= food))
{
mpr("You don't have the energy to cast that spell.");
+ crawl_state.zero_turns_taken();
return (-1);
}
if (istaff.plus < mana)
{
mpr("The rod doesn't have enough magic points.");
+ crawl_state.zero_turns_taken();
// Don't lose a turn for trying to evoke without enough MP - that's
// needlessly cruel for an honest error.
return (-1);
@@ -1491,12 +1494,16 @@ int staff_spell( int staff )
if (you.experience_level < diff)
{
mprf("You need to be at least level %d to use that.", diff);
+ crawl_state.zero_turns_taken();
return (-1);
}
// All checks passed, we can cast the spell
if (your_spells(spell, powc, false) == SPRET_ABORT)
+ {
+ crawl_state.zero_turns_taken();
return (-1);
+ }
make_hungry( food, true );
diff --git a/crawl-ref/source/spl-cast.cc b/crawl-ref/source/spl-cast.cc
index 4057290db2..1fa829b87b 100644
--- a/crawl-ref/source/spl-cast.cc
+++ b/crawl-ref/source/spl-cast.cc
@@ -51,6 +51,7 @@
#include "spells4.h"
#include "spl-book.h"
#include "spl-util.h"
+#include "state.h"
#include "stuff.h"
#include "transfor.h"
#include "view.h"
@@ -587,6 +588,7 @@ bool cast_a_spell()
if (!you.spell_no)
{
mpr("You don't know any spells.");
+ crawl_state.zero_turns_taken();
return (false);
}
@@ -599,6 +601,7 @@ bool cast_a_spell()
if (silenced(you.x_pos, you.y_pos))
{
mpr("You cannot cast spells when silenced!");
+ crawl_state.zero_turns_taken();
more();
return (false);
}
@@ -629,11 +632,15 @@ bool cast_a_spell()
}
if (keyin == ESCAPE)
+ {
+ canned_msg( MSG_OK );
return (false);
+ }
if (!isalpha(keyin))
{
mpr("You don't know that spell.");
+ crawl_state.zero_turns_taken();
return (false);
}
@@ -642,6 +649,7 @@ bool cast_a_spell()
if (spell == SPELL_NO_SPELL)
{
mpr("You don't know that spell.");
+ crawl_state.zero_turns_taken();
return (false);
}
@@ -665,7 +673,10 @@ bool cast_a_spell()
{
const spret_type cast_result = your_spells( spell );
if (cast_result == SPRET_ABORT)
+ {
+ crawl_state.zero_turns_taken();
return (false);
+ }
exercise_spell( spell, true, cast_result == SPRET_SUCCESS );
did_god_conduct( DID_SPELL_CASTING, 1 + random2(5) );
@@ -1005,6 +1016,7 @@ spret_type your_spells( spell_type spell, int powc, bool allow_fail )
break;
case SPELL_DELAYED_FIREBALL:
+ crawl_state.cant_cmd_repeat("You can't repeat delayed fireball.");
// This spell has two main advantages over Fireball:
//
// (1) The release is instantaneous, so monsters will not
@@ -1177,6 +1189,8 @@ spret_type your_spells( spell_type spell, int powc, bool allow_fail )
break;
case SPELL_SELECTIVE_AMNESIA:
+ crawl_state.cant_cmd_repeat("You can't repeat selective amnesia.");
+
if (!cast_selective_amnesia(false))
return (SPRET_ABORT);
break; // Sif Muna power calls with true
@@ -1422,6 +1436,7 @@ spret_type your_spells( spell_type spell, int powc, bool allow_fail )
break;
case SPELL_TUKIMAS_DANCE:
+ crawl_state.cant_cmd_repeat("You can't repeat dancing weapon.");
dancing_weapon(powc, false);
break;
@@ -1561,6 +1576,7 @@ spret_type your_spells( spell_type spell, int powc, bool allow_fail )
break;
case SPELL_ALTER_SELF:
+ crawl_state.cant_cmd_repeat("You can't repeat alter self.");
if (!enough_hp( you.hp_max / 2, true ))
{
mpr( "Your body is in too poor a condition "
@@ -1586,6 +1602,7 @@ spret_type your_spells( spell_type spell, int powc, bool allow_fail )
break;
case SPELL_PORTAL:
+ crawl_state.cant_cmd_repeat("You can't repeat create portal.");
if (portal() == -1)
return (SPRET_ABORT);
break;
@@ -1835,6 +1852,7 @@ spret_type your_spells( spell_type spell, int powc, bool allow_fail )
break;
case SPELL_SWAP:
+ crawl_state.cant_cmd_repeat("You can't swap.");
cast_swap(powc);
break;
diff --git a/crawl-ref/source/state.cc b/crawl-ref/source/state.cc
new file mode 100644
index 0000000000..a664a3464b
--- /dev/null
+++ b/crawl-ref/source/state.cc
@@ -0,0 +1,235 @@
+/*
+ * File: state.cc
+ * Summary: Game state functions.
+ * Written by: Matthew Cline
+ *
+ * Modified for Crawl Reference by $Author$ on $Date$
+ *
+ * Change History (most recent first):
+ *
+ * <1> 09/18/07 MPC Created
+ */
+
+#include "AppHdr.h"
+#include "externs.h"
+
+#include "delay.h"
+#include "direct.h"
+#include "macro.h"
+#include "mon-util.h"
+#include "menu.h" // For print_formatted_paragraph()
+#include "player.h"
+#include "state.h"
+#include "tutorial.h"
+#include "view.h"
+
+game_state::game_state()
+ : 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), 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)
+{
+ reset_cmd_repeat();
+ reset_cmd_again();
+}
+
+///////////////////////////////////////////////////////////////////////////
+// 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 (is_replaying_keys())
+ flush_input_buffer(FLUSH_KEY_REPLAY_CANCEL);
+
+ if (is_processing_macro())
+ flush_input_buffer(FLUSH_ABORT_MACRO);
+
+ reset_cmd_repeat();
+
+ if (reason != "")
+ 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 != "")
+ mpr(reason.c_str());
+}
+
+void game_state::cant_cmd_repeat(std::string reason)
+{
+ if (reason == "")
+ reason = "Can't repeat that command.";
+
+ cancel_cmd_repeat(reason);
+}
+
+void game_state::cant_cmd_again(std::string reason)
+{
+ if (reason == "")
+ reason = "Can't redo that command.";
+
+ cancel_cmd_again(reason);
+}
+
+// The mehtod is called to prevent the "no repeating zero turns
+// commands" message that input() generates (in the abscence 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 command repitition is being used to immitate the rest command,
+ // then everything interrupts it.
+ if (crawl_state.repeat_cmd == CMD_MOVE_NOWHERE
+ || crawl_state.repeat_cmd == CMD_SEARCH)
+ {
+ return true;
+ }
+
+ 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<const monsters*>(at.data);
+ if (!mon->visible())
+ 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")
+ {
+ std::string text = get_monster_desc(mon, false);
+ text += " comes into view.";
+ print_formatted_paragraph(text, get_number_of_cols(), MSGCH_WARN);
+ }
+
+ if (Options.tutorial_left)
+ {
+ // enforce that this message comes first
+ tutorial_first_monster(*mon);
+ if (get_mons_colour(mon) != mon->colour)
+ learned_something_new(TUT_MONSTER_BRAND);
+ }
+#else
+ formatted_string fs( channel_to_colour(MSGCH_WARN) );
+ fs.cprintf("%s (", mon->name(DESC_PLAIN, true).c_str());
+ fs.add_glyph( mon );
+ fs.cprintf(") in view: (%d,%d), see_grid: %s",
+ mon->x, mon->y,
+ see_grid(mon->x, mon->y)? "yes" : "no");
+ formatted_mpr(fs, MSGCH_WARN);
+#endif
+
+ 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<const monsters*>(at.data);
+
+ if (mons_class_flag(mon->type, M_NO_EXP_GAIN)
+ && player_monster_visible(mon))
+ {
+ 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();
+}
diff --git a/crawl-ref/source/state.h b/crawl-ref/source/state.h
index 2be86b8a76..0f4da3bbbd 100644
--- a/crawl-ref/source/state.h
+++ b/crawl-ref/source/state.h
@@ -38,14 +38,40 @@ struct game_state
void (*terminal_resize_handler)();
void (*terminal_resize_check)();
- game_state() : 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), unicode_ok(false),
- glyph2strfn(NULL), multibyte_strlen(NULL),
- terminal_resize_handler(NULL), terminal_resize_check(NULL)
- {
- }
+ bool doing_prev_cmd_again;
+ command_type prev_cmd;
+ std::deque<int> prev_cmd_keys;
+
+ command_type repeat_cmd;
+ std::deque<int> repeat_cmd_keys;
+ bool cmd_repeat_start;
+ int cmd_repeat_count;
+ int cmd_repeat_goal;
+ int prev_cmd_repeat_goal;
+ int prev_repetition_turn;
+ bool cmd_repeat_started_unsafe;
+
+ std::vector<std::string> input_line_strs;
+ unsigned int input_line_curr;
+
+protected:
+ void reset_cmd_repeat();
+ void reset_cmd_again();
+
+public:
+ game_state();
+
+ bool is_replaying_keys() const;
+
+ bool is_repeating_cmd() const;
+
+ void cancel_cmd_repeat(std::string reason = "");
+ void cancel_cmd_again(std::string reason = "");
+
+ void cant_cmd_repeat(std::string reason = "");
+ void cant_cmd_again(std::string reason = "");
+
+ void zero_turns_taken();
void check_term_size() const
{
diff --git a/crawl-ref/source/stuff.cc b/crawl-ref/source/stuff.cc
index 36eb33d543..1b6afe54dd 100644
--- a/crawl-ref/source/stuff.cc
+++ b/crawl-ref/source/stuff.cc
@@ -621,27 +621,34 @@ void canned_msg(canned_message_type which_message)
break;
case MSG_TOO_BERSERK:
mpr("You are too berserk!");
+ crawl_state.cancel_cmd_repeat();
break;
case MSG_PRESENT_FORM:
mpr("You can't do that in your present form.");
+ crawl_state.cancel_cmd_repeat();
break;
case MSG_NOTHING_CARRIED:
mpr("You aren't carrying anything.");
+ crawl_state.cancel_cmd_repeat();
break;
case MSG_CANNOT_DO_YET:
mpr("You can't do that yet.");
+ crawl_state.cancel_cmd_repeat();
break;
case MSG_OK:
mpr("Okay, then.");
+ crawl_state.cancel_cmd_repeat();
break;
case MSG_UNTHINKING_ACT:
mpr("Why would you want to do that?");
+ crawl_state.cancel_cmd_repeat();
break;
case MSG_SPELL_FIZZLES:
mpr("The spell fizzles.");
break;
case MSG_HUH:
mpr("Huh?");
+ crawl_state.cancel_cmd_repeat();
break;
case MSG_EMPTY_HANDED:
mpr("You are now empty-handed.");
@@ -656,8 +663,9 @@ void canned_msg(canned_message_type which_message)
bool yesno( const char *str, bool safe, int safeanswer, bool clear_after,
bool interrupt_delays, bool noprompt )
{
- if (interrupt_delays)
+ if (interrupt_delays && !crawl_state.is_repeating_cmd())
interrupt_activity( AI_FORCE_INTERRUPT );
+
for (;;)
{
if ( !noprompt )
@@ -691,7 +699,9 @@ bool yesno( const char *str, bool safe, int safeanswer, bool clear_after,
// like yesno(), but returns 0 for no, 1 for yes, and -1 for quit
int yesnoquit( const char* str, bool safe, int safeanswer, bool clear_after )
{
- interrupt_activity( AI_FORCE_INTERRUPT );
+ if (!crawl_state.is_repeating_cmd())
+ interrupt_activity( AI_FORCE_INTERRUPT );
+
while (1)
{
mpr(str, MSGCH_PROMPT);
diff --git a/crawl-ref/source/view.h b/crawl-ref/source/view.h
index 6f9e9f70b5..3afd19bab6 100644
--- a/crawl-ref/source/view.h
+++ b/crawl-ref/source/view.h
@@ -62,6 +62,7 @@ enum element_type
void init_char_table(char_set_type set);
void init_feature_table();
+void init_monsters_seens();
// last updated 29may2000 {dlb}
/* ***********************************************************************