summaryrefslogtreecommitdiffstats
path: root/crawl-ref/source
diff options
context:
space:
mode:
authorzelgadis <zelgadis@c06c8d41-db1a-0410-9941-cceddc491573>2007-09-19 01:19:56 +0000
committerzelgadis <zelgadis@c06c8d41-db1a-0410-9941-cceddc491573>2007-09-19 01:19:56 +0000
commit725cb5b4d5a3ade5c5e7b04a6210cedc839c2bdd (patch)
treed2644ce5f13e7ab649b27b49f7bdcde18802a6b1 /crawl-ref/source
parent1a36197574afe860feff7208cd883770a5d28946 (diff)
downloadcrawl-ref-725cb5b4d5a3ade5c5e7b04a6210cedc839c2bdd.tar.gz
crawl-ref-725cb5b4d5a3ade5c5e7b04a6210cedc839c2bdd.zip
Added new commands "re-do previous command" (bound to `) and "repeat
next command" (bound to 0). Though this is just an interface change, it changes code in the core input processing function (input() in acr.cc), and also messes around with the input buffer, so it could probably do with more testing before merging it into the 0.3 branch. git-svn-id: https://crawl-ref.svn.sourceforge.net/svnroot/crawl-ref/trunk@2137 c06c8d41-db1a-0410-9941-cceddc491573
Diffstat (limited to 'crawl-ref/source')
-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
32 files changed, 1448 insertions, 37 deletions
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}
/* ***********************************************************************