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
JM7UAK777RAVDAVLQLEOBRTGNW2B47S5G55XITJXO243IUNZHVYQC
R5DXVK5AC7Z3IEBBNJGDR5IOSDFJ5MNWMUXSAD2JWTLFVFYN3KCQC
GOX2FK35IXLRJJJYT6QHL2GTFUWN6IJ6P7GIWKSP2LDOGTWA7XEQC
E6JXIMRH2TX5WHZ6BT2QZ3EANM3PWCHCVYC4XWRJGRBDSP42X2RAC
DH3D44HGNRXTSZLL5HTHKOPKCO4VZQZVBEMFOAPRJGQMLGN2BFQAC
Z7SW3IKYNXMOHHD77NGXN6RGL32PZBO6AIHLJY74IRO23AIRKWMQC
PFQAR3EWCVJXCXU7VHWZOXA2FSYLFJ6EE3WG5FVOVYTXASCAXWEAC
FXLBM7PB5FCPMKZNI3NSVDAEM26MAQU5RG55BEBL3LRN7QMPLWTAC
DOZORMA366M4HB5JKSS27BMCR6ET7QNZNND2B7KV3NVEEPR5H7EAC
OVM7ZULJJ6Q23AQ747AWBGISGDQYCGF2NT5YTXKW633I56WIYZPQC
GCIZIUXO5TYROKDUYB3HAY7H7MRDTJNM7HR7DGSH7KXDIZC2LCDAC
K2CS6TCX2NDVL2ASEHGP4J4K4IJ6FP3ANNKTSIWVG43HPYSBX6ZQC
2VOD7XONHR3G2JGZGXOPHNR2AN7WUQZFR5KJH5ZY4P4G67H3RCSQC
547JREUJXTZNYVGHNNAET5F5O5JYYGNTDQB6ABZNT7YX5EY64OHAC
RPOZZWKG5GLPHVZZ7ZKMKS64ZMV2LDCQSARBJFJ6FZOTOKCQO7FAC
SVY2PTCLXR3KNPQAWXVXTTGCC5DR334HOAKHYO3VDDRWM2BWMALAC
UL7XFKMUX3WIU4O2LZANK4ECJ654UZPDBFGNXUEYZYOLKBYBCG6AC
NQMXQ6OQVUSC7Y7F7IL252QW4A5JED224EECNHWAM4ZZYVNY745AC
C5VA63WAQRWPENIMXRPUPZLZJMC77PL2B3A77HYFWDCZU5QDG7VQC
GQL5SIGBHLU3FMCE54XVGLRY5AZHRM6DUEB722REA2DPLGJSN6EQC
GBUB77EAYHOFY6GQ5IY3ZSBC7FSQFZZKYNBD5QMCQFIKFLYLWHOQC
35KOQQV4ZBNNIAXGKPOYHOWTN7RBQY7HRZTVMR3WAS4GOYFBM6PQC
L4RYVF46EQKMVOEADGRG4WMPVTQ6NNFGYMU4SHAH6XJIKWVHT77QC
FMBJCM5LJKCG326YRJGOOJU6QNWONXAHK2AB4CP4SAOHKJ5CORXQC
2D77G7XIIDVS2RUE33YH2NAPSXNLXKXXN3RP2TKPLWRXTEAVSZ3QC
SDLKLUNFGVKDS55DDJZCBAVIB7NL3RRYPTACAY65SCUQKV6APFSAC
MQ62OAMLGJVRW2QIL4PAZRAU6PC52ZVGY2FCOBIY6IWGQIHMU5CAC
IIN7AVA6JYRBXH6ZYRR7BY7TV6PW7ANAQ2A3PD55FKBKKQFEEF2AC
JYCMD6WMNHXA53K4LLKVTNX6PLRLU25F6J2TYMPQXM2ENAE66NIAC
R5JKQLY5QE6UBG3RH3Y5ZRSX6H35CHYI2HYNDZF6ZHVRULUORXBQC
FEGNPOJI2SALUA2PVIXIQ2CIXFLSXD7UB7CNUSAAKV4L3POXCRFQC
HSRRNAU5UAYC6B6IQWGJPFROMZBTJICPCH6DJVZDHDTAGOQ6IOYAC
YAAJ6PTN6QUSWE52URI5AENOGD366FIHOIFUOXFUJLVZYE4OG6HQC
TJRYL3NXPW5IUGEV3YOC7JYWEXCZDBFPLT4AUG4P227WVKVB72ZAC
YHSVOROKPYS33Y4RYZRVZTE3G5LXOFX52HEDNLV6HIXOJYNOKH3QC
5R4WV4H5SNIM5WU2X33JJ63HIEGKCXN2HELZ6FRRKKANPLMRLF3QC
ODNAIEJW732NG7USKQKCIP4R4DAEYXXJQX6LY7TIN32NKE75454QC
ITQ2EXDFLN3EHCURKYHZYVWFACD4KW7NA6CMHNKGWTDKHCHQAE6AC
QYQKV4R47PTERXVFQNNWWQVICGSOMBHW6WM5TAZAKLIYOLLPUAJAC
5HPIIGNWB3UXJ5APQLAGFOV2CA7J2GB7AWAVVZDBB2YZS4TNCMAQC
3KAINFIXO7WNWGUGZB43EUNFRS2ZPBLQZDTY456QACMRHYIJ7WDAC
2IJDLTWK74ULETLFTIPUIY2MSG6RNONVAFNFE7MADPT6UNYSSBDAC
2ESKXYN266CEMLSL6DNCKG4REDO34FXL4ODVGMWDJTNJRKMXWCRQC
RR2J4VLJCZSAKY3HNS334KI4YUAPOMSETO2HGCGEEIUCUJAYAGSQC
DDU4A3JGN5IUIPP5IASOODKPR2WBHSDSV4FITZ6HNXNSXXQACWAQC
YCL3W2PFE6ILTGBFODCSXNPDIA46KVSZP2TI7HDMYAOEJT65RIEAC
TV3ZC6WOZKSQQJQN26JIVKCHK6UK7WMDBYZDUYRWEAZ4JB4YVNAAC
5ASC3STDYCNLZFEBN6UTMUCGDETHBR2OCBZCF5VIAZ5RRWLOTDYQC
ANOEQTM6IGCBTESKKQ5PCBSDTZ7VGRCMDIOAFEH4R7DJHKWKDFAAC
43ZTEB57FU7KE5EVMYWZONNVJBZCGF3JEAJZIY25LC4LGE65PG5QC
W6IY6LF3MREPXC23AAKA2BJNUCJYCSOWY55DIWJWFLUEE2Y3LGNQC
TOKBONNNPTP2CIEHMMR4QAJZTXYETS55OGGDA6FY6NIMNDYMWJDAC
UDYVF65OZSNPANLHDI3ODBEGUAKAVZ4KH4OZFAKR2CQJPO4AXU6QC
YRY2TC3VHOYE47M23UJGUWDGF7H7WGU7WLWI4SUNM4EDNTGUPHGAC
Y2VKZYSQXLYYQNB6OSQP44IYLT2M56SE2ZW2MHOAZUODKCVDHEAQC
KW43PGXTTM57DXUGGBQXJ5G5OYYIY3WB76TXIKL2ZCIJGH7GH4LAC
WXSNNK2RXP3DQFAEQGQUZJHFWXJC7ZKG2WURZGL566UDM4YXFSWQC
RVST2QHYJ757ZHK4AUJ5NGPDZ44AD6RVFVXYPKQIBJXZBDNUCHXQC
WQU4ORZR2C52JNRTH5DN4XKNLEC4Q4SUZ7MLO6MBU6SSL2YXLSOAC
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)
{
}
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();
/*
* 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();
}
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));
}
};
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();
}
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]);
}
}
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)
}
}
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());
// 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;
}
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;
}
}
}
}
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;
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;
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();
}