From: Pasi Kallinen Date: Thu, 4 Aug 2022 11:00:47 +0000 (+0300) Subject: Rudimentary key rebinding in game options X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=4ff9537b0dd164e60cb0b9b99727e278a9578fab;p=nethack Rudimentary key rebinding in game options Currently shown only in the full options list, as it's not quite complete. (For example, it doesn't handle movement commands, or the getpos keys) --- diff --git a/doc/fixes3-7-0.txt b/doc/fixes3-7-0.txt index 01ddfb0df..f4654e37f 100644 --- a/doc/fixes3-7-0.txt +++ b/doc/fixes3-7-0.txt @@ -1764,6 +1764,7 @@ some quest nemeses release a cloud of poisonous gas when they die taming magic acting on an already tame creature might make it become tamer eliminate scimitar skill and have scimitars use saber skill simplified configuration options menu +rudimentary key rebinding in game options Platform- and/or Interface-Specific New Features diff --git a/include/extern.h b/include/extern.h index 40817593d..c9508c9ae 100644 --- a/include/extern.h +++ b/include/extern.h @@ -264,6 +264,8 @@ extern void savech_extcmd(const char *, boolean); extern char extcmd_initiator(void); extern int doextcmd(void); extern struct ext_func_tab *extcmds_getentry(int); +extern int count_bind_keys(void); +extern void handler_rebind_keys(void); extern int extcmds_match(const char *, int, int **); extern const char *key2extcmddesc(uchar); extern boolean bind_specialkey(uchar, const char *); diff --git a/include/optlist.h b/include/optlist.h index 18d59f645..fdbbc12e7 100644 --- a/include/optlist.h +++ b/include/optlist.h @@ -132,6 +132,8 @@ opt_##a, + sizeof "kick" + sizeof "force" + 20), opt_out, set_in_game, Yes, Yes, No, Yes, NoAlias, "action to take when encountering locked door or chest") + NHOPTO("bind keys", Advanced, o_bind_keys, BUFSZ, opt_in, + set_in_game, No, Yes, No, NoAlias, "edit key binds") #if defined(MICRO) && !defined(AMIGA) NHOPTB(BIOS, Advanced, 0, opt_in, set_in_config, Off, Yes, No, No, NoAlias, &iflags.BIOS) diff --git a/src/cmd.c b/src/cmd.c index 7f3efa760..ac742d95e 100644 --- a/src/cmd.c +++ b/src/cmd.c @@ -161,6 +161,7 @@ static char *parse(void); static void show_direction_keys(winid, char, boolean); static boolean help_dir(char, uchar, const char *); +static void handler_rebind_keys_add(boolean); static boolean bind_key_fn(uchar, int (*)(void)); static void commands_init(void); static boolean keylist_func_has_key(const struct ext_func_tab *, boolean *); @@ -2826,6 +2827,187 @@ extcmds_getentry(int i) return &extcmdlist[i]; } +/* return number of extended commands bound to a non-default key */ +int +count_bind_keys(void) +{ + int nbinds = 0; + int i; + + for (i = 0; i < extcmdlist_length; i++) + if (extcmdlist[i].key && g.Cmd.commands[extcmdlist[i].key] != &extcmdlist[i]) + nbinds++; + return nbinds; +} + +static void +display_changed_key_binds(void) +{ + winid win; + int i; + char buf[BUFSZ]; + char buf2[QBUFSZ]; + + win = create_nhwindow(NHW_TEXT); + for (i = 0; i < extcmdlist_length; i++) { + struct ext_func_tab *ec = &extcmdlist[i]; + + if (ec->key && g.Cmd.commands[ec->key] + && g.Cmd.commands[ec->key] != ec) { + Sprintf(buf, "BIND=%s:%s", key2txt(ec->key, buf2), + g.Cmd.commands[ec->key]->ef_txt); + putstr(win, 0, buf); + } + } + display_nhwindow(win, TRUE); + destroy_nhwindow(win); +} + +/* interactive key binding */ +static void +handler_rebind_keys_add(boolean keyfirst) +{ + struct ext_func_tab *ec; + winid win; + anything any; + int i, npick; + menu_item *picks = (menu_item *) 0; + char buf[BUFSZ]; + char buf2[QBUFSZ]; + uchar key = '\0'; + + if (keyfirst) { + pline("Bind which key? "); + key = pgetchar(); + + if (!key || key == '\027') + return; + } + + win = create_nhwindow(NHW_MENU); + start_menu(win, MENU_BEHAVE_STANDARD); + any = cg.zeroany; + + if (key) { + if (g.Cmd.commands[key]) { + Sprintf(buf, "Key '%s' is currently bound to \"%s\".", + key2txt(key, buf2), g.Cmd.commands[key]->ef_txt); + } else { + Sprintf(buf, "Key '%s' is not bound to anything.", + key2txt(key, buf2)); + } + add_menu(win, &nul_glyphinfo, &any, '\0', 0, ATR_NONE, 0, buf, + MENU_ITEMFLAGS_NONE); + add_menu(win, &nul_glyphinfo, &any, '\0', 0, ATR_NONE, 0, "", + MENU_ITEMFLAGS_NONE); + } + + any.a_int = -1; + add_menu(win, &nul_glyphinfo, &any, '\0', 0, ATR_NONE, 0, "nothing: unbind the key", + MENU_ITEMFLAGS_NONE); + + any.a_int = 0; + add_menu(win, &nul_glyphinfo, &any, '\0', 0, ATR_NONE, 0, "", + MENU_ITEMFLAGS_NONE); + + for (i = 0; i < extcmdlist_length; i++) { + ec = &extcmdlist[i]; + + if ((ec->flags & (MOVEMENTCMD|INTERNALCMD|CMD_NOT_AVAILABLE)) != 0) + continue; + + any.a_int = (i + 1); + Sprintf(buf, "%s: %s", ec->ef_txt, ec->ef_desc); + add_menu(win, &nul_glyphinfo, &any, '\0', 0, ATR_NONE, 0, buf, + MENU_ITEMFLAGS_NONE); + } + if (key) + Sprintf(buf, "Bind '%s' to what command?", key2txt(key, buf2)); + else + Sprintf(buf, "Bind what command?"); + end_menu(win, buf); + npick = select_menu(win, PICK_ONE, &picks); + destroy_nhwindow(win); + if (npick > 0) { + const struct ext_func_tab *prevec; + const char *cmdstr; + + i = picks->item.a_int; + free((genericptr_t) picks); + + if (i == -1) { + ec = NULL; + cmdstr = "nothing"; + goto bindit; + } else { + ec = &extcmdlist[i-1]; + cmdstr = ec->ef_txt; + } +bindit: + if (!key) { + pline("Bind which key? "); + key = pgetchar(); + + if (!key || key == '\027') + return; + } + + prevec = g.Cmd.commands[key]; + + if (bind_key(key, cmdstr)) { + if (prevec && prevec != ec) { + pline("Changed key '%s' from \"%s\" to \"%s\".", + key2txt(key, buf2), prevec->ef_txt, cmdstr); + } else if (!prevec) { + pline("Bound key '%s' to \"%s\".", key2txt(key, buf2), cmdstr); + } + } else { + pline("Key binding failed?!"); + } + } +} + +void +handler_rebind_keys(void) +{ + winid win; + anything any; + int i, npick; + menu_item *picks = (menu_item *) 0; + +redo_rebind: + + win = create_nhwindow(NHW_MENU); + start_menu(win, MENU_BEHAVE_STANDARD); + any = cg.zeroany; + + any.a_int = 1; + add_menu(win, &nul_glyphinfo, &any, '\0', 0, ATR_NONE, 0, "bind key to a command", + MENU_ITEMFLAGS_NONE); + any.a_int = 2; + add_menu(win, &nul_glyphinfo, &any, '\0', 0, ATR_NONE, 0, "bind command to a key", + MENU_ITEMFLAGS_NONE); + if (count_bind_keys()) { + any.a_int = 3; + add_menu(win, &nul_glyphinfo, &any, '\0', 0, ATR_NONE, 0, "view changed key binds", + MENU_ITEMFLAGS_NONE); + } + end_menu(win, "Do what?"); + npick = select_menu(win, PICK_ONE, &picks); + destroy_nhwindow(win); + if (npick > 0) { + i = picks->item.a_int; + free((genericptr_t) picks); + + if (i == 1 || i == 2) { + handler_rebind_keys_add((i == 1)); + } else if (i == 3) { + display_changed_key_binds(); + } + goto redo_rebind; + } +} + /* find extended command entries matching findstr. if findstr is NULL, returns all available entries. returns: number of matching extended commands, diff --git a/src/options.c b/src/options.c index af149a928..0d2ad23a8 100644 --- a/src/options.c +++ b/src/options.c @@ -7567,6 +7567,28 @@ optfn_o_autopickup_exceptions( return optn_ok; } +static int +optfn_o_bind_keys( + int optidx UNUSED, int req, boolean negated UNUSED, + char *opts, char *op UNUSED) +{ + if (req == do_init) { + return optn_ok; + } + if (req == do_set) { + } + if (req == get_val) { + if (!opts) + return optn_err; + Sprintf(opts, n_currently_set, count_bind_keys()); + return optn_ok; + } + if (req == do_handler) { + handler_rebind_keys(); + } + return optn_ok; +} + static int optfn_o_menu_colors(int optidx UNUSED, int req, boolean negated UNUSED, char *opts, char *op UNUSED)