From 9a7f8418ee3e8ad9af85cf9ee36bef9dd57a07cf Mon Sep 17 00:00:00 2001 From: PatR Date: Fri, 18 Nov 2022 16:07:15 -0800 Subject: [PATCH] 'nethack --usage' and '?' menu Write up a description of how the command line works on UNIX and put that in new file dat/usagehlp. Add support for |nethack --usage | --help | -? | ? to display it and exit. Also add a menu entry for nethack's help command to show it during play. That can be suppressed by uncommenting new '#define HIDE_USAGE' in config.h since it won't be useful on servers that don't give players access to command lines. New genl_display_file() just writes to stdout. opt_usage(), which calls it, might need some suid/sgid handling to make sure the output is done as the player rather than as nethack. doc/nethack.6 is already out of date again. --- dat/usagehlp | 132 ++++++++++++++++++++++++++++++++++++++++++ include/config.h | 9 +++ include/extern.h | 1 + include/global.h | 1 + src/pager.c | 15 +++++ src/windows.c | 106 ++++++++++++++++++++++----------- sys/unix/Makefile.src | 2 +- sys/unix/Makefile.top | 2 +- sys/unix/unixmain.c | 31 ++++++++++ 9 files changed, 262 insertions(+), 37 deletions(-) create mode 100644 dat/usagehlp diff --git a/dat/usagehlp b/dat/usagehlp new file mode 100644 index 000000000..a6a4b6b77 --- /dev/null +++ b/dat/usagehlp @@ -0,0 +1,132 @@ +This is a terse description of nethack's command line arguments. +It is oriented toward UNIX (including descendants such as linux and +macOS) and might not be accurate for other platforms. + +This text is available in the menu for the game's '?' command or can be +viewed via 'nethack --usage | more' at the shell prompt. + +In all cases, if there is a save file for the chosen character name then +it will be restored, otherwise a new game using that name will start. + +nethack + with no arguments; uses character name from run-time configuration file's + OPTIONS=name:character-name entry, or player's username if none. + +nethack -u character-name [-X or -D] + '-u character-name' specifies the name to use for this game's character; + -u must be lowercase; the space between it and character-name may + be omitted; + '-X' play in non-scoring explore mode also known as discovery mode; + -X must be uppercase; character starts with a wand of wishing and + player may opt to be life-saved and keep going if character dies; + '-D' run in debug mode also known as wizard mode; -D must be uppercase; + if player is not allowed then nethack will switch to -X. + + A character name may have a suffix specifying any or all of role, race, + gender, and alignment such as -u Conan-Bar-Hum-Mal-Neu or -u Tim-Wiz. + The components present must be at least three letters long but can be + longer; their case doesn't matter. See also -p and -r, next, + +nethack -p Ppp -r Rrr [-@] + '-p Ppp' specify role; p for "profession" is used because -r is in use; + 'Ppp' is three or more letters of the role name such as Val for + Valkyrie; unlike -p itself, upper/lower case of Ppp doesn't matter + '-r Rrr' specify race or species: Hum[an], Elf, Orc, Dwa[rf], Gno[me]; + '-@' force non-interactive start; any of role, race, gender, and + alignment that is not specified on the command line or in the + run-time configuration file gets chosen randomly without prompting; + the at-sign might need to be quoted by preceding it with backslash. + + The old form for role is also still accepted: -A or -Arc[heologist], + -B or -Bar[barian], -C or -Cav[eman] or -Cavew[oman], -H or -Hea[ler], + -K or -Kni[ght], -M or -Mon[k], -P or -Pri[est] or -Prieste[ss], + -Ran[ger], -R or -Rog[ue], -S or -Sam[urai], -T or -Tou[rist], + -V or -Val[kyrie], -W or -Wiz[ard]. The single-letter form must be in + uppercase, the three or more letter form can be in any case. There is + no single-letter option for the Ranger role. + +nethack -DEC[graphics] +nethack -IBM[graphics] + selects the DEC or IBM symbol set to use line-drawing characters on a + text map; might be ignored depending on interface, or ineffective or + even scrambled depending on display capability; -DECgraphics and + -IBMgraphics are mutually exclusive; they can be any case but must use + at least three letters. + +nethack -wIii +nethack --windowtype:Iii + where 'Iii' represents an interface designation: tty, curses, X11, or + Qt; only useful if the program was built to support more than one + interface (the game's '#version' command will disclose that); overrides + OPTIONS=windowtype:Iii in run-time configuration file and build-time + default; '-w' or '--windowtype' must be lowercase, the interface + designation itself may be any case; variations '--windowtype Iii' and + '-w Iii' work too. + + On Windows, nethack.exe might support both tty and curses; nethackW.exe + supports mswin only. MS-DOS is tty only. + +nethack -n + don't show the 'news' file is one is present in nethack's directory. + +nethack --nethackrc:File + use File instead of the default run-time configuration file (which is + usually '~/.nethackrc'); the file name should include full path unless + located in nethack's directory; +nethack --no-nethackrc + don't use any run-time configuration file; equivalent to + --nethackrc:/dev/null which behaves as if an empty file. + +nethack -dDir +nethack --directory:Dir + could be used to override the build-time value of NETHACKDIR with + location Dir; if used, it should precede other command line arguments. + +The assorted options above can be combined on a single command line; +they're listed separately for readability. + +******* + +Other options which perform some action and then exit rather than play +the game: + +nethack --usage +nethack --help + show this text; 'nethack ?' and 'nethack -?' might also work but the + question mark will need to be quoted to prevent the shell from attempting + filename matching. + +nethack -s +nethack --scores + show scores for the default character; optional additional arguments: +nethack -s character-name [character-name2 [character-name3 [...]]] + show scores for one or more specific character names (might not be + effective if PERS_IS_UID=1 is specified in nethack's sysconf file); + character names may be preceded by '-u' but that isn't required; + special character-name "all" is used to display all scores that pass + other criteria; +nethack -s -p Ppp -r Rrr + show scores for specific roles or races; multiple instances can be used; + if both '-p' and '-r' are used, scores that match either will be shown + rather than scores that match both; +nethack -s -v + show scores only for the current version if the high scores file (record) + also contains entries for any older versions; when -v is used, it should + immediately follow -s or --scores, preceding any name(s) or -p or -r; +nethack -dDir -s +nethack --directory:Dir -s + as above; alternative directory, if specified, should come first. + +nethack --version +nethack --version:paste + '--version' display the program's version number and exit; + '--version:paste' display version number and also copy it into system + pasteboard (should work on macOS and Windows; might not work on other + systems) so that it could be copied from there into a subsequent email + or web contact form, then exit. + +nethack --showpaths + list expected locations for various files and directorys, then exit; + includes the name and location for the run-time configuration file which + can vary from platform to platform. + diff --git a/include/config.h b/include/config.h index 07e350a72..f518340cc 100644 --- a/include/config.h +++ b/include/config.h @@ -421,6 +421,15 @@ */ #endif /* CHDIR */ +/* + * Define HIDE_USAGE to keep the "description of command line" out of + * the help menu if players have no access to a command line or if that + * is radically different from the description for UNIX in dat/usagehlp + * (a better solution for that would be a separate file and different + * value for USAGEHELP in global.h). + */ +/* #define HIDE_USAGE */ /* */ + /* * Section 3: Definitions that may vary with system type. * For example, both schar and uchar should be short ints on diff --git a/include/extern.h b/include/extern.h index 979331619..b2d15cb0d 100644 --- a/include/extern.h +++ b/include/extern.h @@ -3303,6 +3303,7 @@ extern char *encglyph(int); extern int decode_glyph(const char *str, int *glyph_ptr); extern char *decode_mixed(char *, const char *); extern void genl_putmixed(winid, int, const char *); +extern void genl_display_file(const char *, boolean); extern boolean menuitem_invert_test(int, unsigned, boolean); /* ### windows.c ### */ diff --git a/include/global.h b/include/global.h index b04668ea2..6b53a4b95 100644 --- a/include/global.h +++ b/include/global.h @@ -26,6 +26,7 @@ #define LICENSE "license" /* file with license information */ #define OPTIONFILE "opthelp" /* file explaining runtime options */ #define OPTMENUHELP "optmenu" /* file explaining #options command */ +#define USAGEHELP "usagehlp" /* file explaining command line use */ #define OPTIONS_USED "options" /* compile-time options, for #version */ #define SYMBOLS "symbols" /* replacement symbol sets */ #define EPITAPHFILE "epitaph" /* random epitaphs on graves */ diff --git a/src/pager.c b/src/pager.c index 6d72ffb46..dc323cd80 100644 --- a/src/pager.c +++ b/src/pager.c @@ -32,6 +32,9 @@ static void dispfile_optionfile(void); static void dispfile_optmenu(void); static void dispfile_license(void); static void dispfile_debughelp(void); +#ifndef HIDE_USAGE +static void dispfile_usagehelp(void); +#endif static void hmenu_doextversion(void); static void hmenu_dohistory(void); static void hmenu_dowhatis(void); @@ -2355,6 +2358,14 @@ dispfile_debughelp(void) display_file(DEBUGHELP, TRUE); } +#ifndef HIDE_USAGE +static void +dispfile_usagehelp(void) +{ + display_file(USAGEHELP, TRUE); +} +#endif + static void hmenu_doextversion(void) { @@ -2389,6 +2400,7 @@ static void domenucontrols(void) { winid cwin = create_nhwindow(NHW_TEXT); + show_menu_controls(cwin, FALSE); display_nhwindow(cwin, FALSE); destroy_nhwindow(cwin); @@ -2411,6 +2423,9 @@ static const struct { { dokeylist, "Full list of keyboard commands." }, { hmenu_doextlist, "List of extended commands." }, { domenucontrols, "List menu control keys." }, +#ifndef HIDE_USAGE + { dispfile_usagehelp, "Description of NetHack's command line." }, +#endif { dispfile_license, "The NetHack license." }, { docontact, "Support information." }, #ifdef PORT_HELP diff --git a/src/windows.c b/src/windows.c index f7981e28e..f1440efdf 100644 --- a/src/windows.c +++ b/src/windows.c @@ -3,6 +3,7 @@ /* NetHack may be freely redistributed. See license for details. */ #include "hack.h" +#include "dlb.h" #ifdef TTY_GRAPHICS #include "wintty.h" #endif @@ -533,7 +534,8 @@ static win_request_info *hup_ctrl_nhwindow(winid, int, win_request_info *); static struct window_procs hup_procs = { WPID(hup), 0L, 0L, - {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, + { FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, + FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE }, /* colors */ hup_init_nhwindows, hup_void_ndecl, /* player_selection */ hup_void_ndecl, /* askname */ @@ -577,7 +579,7 @@ static struct window_procs hup_procs = { hup_void_ndecl, /* status_finish */ genl_status_enablefield, hup_status_update, genl_can_suspend_no, - hup_void_fdecl_int, /* update_inventory */ + hup_void_fdecl_int, /* update_inventory */ hup_ctrl_nhwindow, }; @@ -634,9 +636,10 @@ hup_nhgetch(void) /*ARGSUSED*/ static char -hup_yn_function(const char *prompt UNUSED, - const char *resp UNUSED, - char deflt) +hup_yn_function( + const char *prompt UNUSED, + const char *resp UNUSED, + char deflt) { if (!deflt) deflt = '\033'; @@ -673,23 +676,26 @@ hup_create_nhwindow(int type UNUSED) /*ARGSUSED*/ static int -hup_select_menu(winid window UNUSED, int how UNUSED, - struct mi **menu_list UNUSED) +hup_select_menu( + winid window UNUSED, + int how UNUSED, + struct mi **menu_list UNUSED) { return -1; } /*ARGSUSED*/ static void -hup_add_menu(winid window UNUSED, - const glyph_info *glyphinfo UNUSED, - const anything *identifier UNUSED, - char sel UNUSED, - char grpsel UNUSED, - int attr UNUSED, - int clr UNUSED, - const char *txt UNUSED, - unsigned int itemflags UNUSED) +hup_add_menu( + winid window UNUSED, + const glyph_info *glyphinfo UNUSED, + const anything *identifier UNUSED, + char sel UNUSED, + char grpsel UNUSED, + int attr UNUSED, + int clr UNUSED, + const char *txt UNUSED, + unsigned int itemflags UNUSED) { return; } @@ -710,10 +716,11 @@ hup_putstr(winid window UNUSED, int attr UNUSED, const char *text UNUSED) /*ARGSUSED*/ static void -hup_print_glyph(winid window UNUSED, - coordxy x UNUSED, coordxy y UNUSED, - const glyph_info *glyphinfo UNUSED, - const glyph_info *bkglyphinfo UNUSED) +hup_print_glyph( + winid window UNUSED, + coordxy x UNUSED, coordxy y UNUSED, + const glyph_info *glyphinfo UNUSED, + const glyph_info *bkglyphinfo UNUSED) { return; } @@ -781,9 +788,10 @@ hup_get_color_string(void) /*ARGSUSED*/ static void -hup_status_update(int idx UNUSED, genericptr_t ptr UNUSED, int chg UNUSED, - int pc UNUSED, int color UNUSED, - unsigned long *colormasks UNUSED) +hup_status_update( + int idx UNUSED, genericptr_t ptr UNUSED, + int chg UNUSED, int pc UNUSED, + int color UNUSED, unsigned long *colormasks UNUSED) { return; } @@ -820,8 +828,9 @@ hup_void_fdecl_winid(winid window UNUSED) /*ARGUSED*/ static void -hup_void_fdecl_winid_ulong(winid window UNUSED, - unsigned long mbehavior UNUSED) +hup_void_fdecl_winid_ulong( + winid window UNUSED, + unsigned long mbehavior UNUSED) { return; } @@ -885,8 +894,11 @@ genl_status_finish(void) } void -genl_status_enablefield(int fieldidx, const char *nm, const char *fmt, - boolean enable) +genl_status_enablefield( + int fieldidx, + const char *nm, + const char *fmt, + boolean enable) { status_fieldfmt[fieldidx] = fmt; status_fieldnm[fieldidx] = nm; @@ -897,9 +909,11 @@ DISABLE_WARNING_FORMAT_NONLITERAL /* call once for each field, then call with BL_FLUSH to output the result */ void -genl_status_update(int idx, genericptr_t ptr, int chg UNUSED, - int percent UNUSED, int color UNUSED, - unsigned long *colormasks UNUSED) +genl_status_update( + int idx, + genericptr_t ptr, + int chg UNUSED, int percent UNUSED, + int color UNUSED, unsigned long *colormasks UNUSED) { char newbot1[MAXCO], newbot2[MAXCO]; long cond, *condptr = (long *) ptr; @@ -1084,11 +1098,12 @@ static FILE *dumplog_file; static time_t dumplog_now; char * -dump_fmtstr(const char *fmt, char *buf, - boolean fullsubs) /* True -> full substitution for file name, - False -> partial substitution for - '--showpaths' feedback where there's - no game in progress when executed */ +dump_fmtstr( + const char *fmt, + char *buf, + boolean fullsubs) /* True -> full substitution for file name, + * False -> partial substitution for '--showpaths' + * feedback where there's no game in progress */ { const char *fp = fmt; char *bp = buf; @@ -1493,6 +1508,27 @@ genl_putmixed(winid window, int attr, const char *str) putstr(window, attr, decode_mixed(buf, str)); } +/* possibly called to show usage info during command line processing when + an interface hasn't yet been chosen and set up */ +void +genl_display_file(const char *fname, boolean complain) +{ + char buf[BUFSZ]; + dlb *f = dlb_fopen(fname, "r"); + + if (!f) { + if (complain) /* send complaint to stdout rather than to stderr */ + fprintf(stdout, "\nCannot open \"%s\".\n", fname); + } else { + /* straight copy to stdout, no pagination or other interaction */ + while (dlb_fgets(buf, BUFSZ, f)) { + if (fputs(buf, stdout) < 0) + break; + } + (void) dlb_fclose(f); + } +} + /* * Window port helper function for menu invert routines to move the decision * logic into one place instead of 7 different window-port routines. diff --git a/sys/unix/Makefile.src b/sys/unix/Makefile.src index 423465bf2..903feb3a0 100644 --- a/sys/unix/Makefile.src +++ b/sys/unix/Makefile.src @@ -1191,7 +1191,7 @@ $(TARGETPFX)vision.o: vision.c $(HACK_H) $(TARGETPFX)weapon.o: weapon.c $(HACK_H) $(TARGETPFX)were.o: were.c $(HACK_H) $(TARGETPFX)wield.o: wield.c $(HACK_H) -$(TARGETPFX)windows.o: windows.c $(HACK_H) ../include/wintty.h +$(TARGETPFX)windows.o: windows.c $(HACK_H) ../include/dlb.h ../include/wintty.h $(TARGETPFX)wizard.o: wizard.c $(HACK_H) $(TARGETPFX)worm.o: worm.c $(HACK_H) $(TARGETPFX)worn.o: worn.c $(HACK_H) diff --git a/sys/unix/Makefile.top b/sys/unix/Makefile.top index 802e9b7e3..f3cc25fe1 100644 --- a/sys/unix/Makefile.top +++ b/sys/unix/Makefile.top @@ -91,7 +91,7 @@ LUA_VERSION = 5.4.4 # end of configuration # -DATHELP = help hh cmdhelp keyhelp history opthelp optmenu wizhelp +DATHELP = help hh cmdhelp keyhelp history opthelp optmenu usagehlp wizhelp SPEC_LEVS = asmodeus.lua baalz.lua bigrm-*.lua castle.lua fakewiz?.lua \ juiblex.lua knox.lua medusa-?.lua minend-?.lua minefill.lua \ diff --git a/sys/unix/unixmain.c b/sys/unix/unixmain.c index 6cdf30c94..b21220883 100644 --- a/sys/unix/unixmain.c +++ b/sys/unix/unixmain.c @@ -36,6 +36,7 @@ static void consume_arg(int, int *, char ***); static void consume_two_args(int, int *, char ***); static void early_options(int *, char ***, char **); static void opt_terminate(void) NORETURN; +static void opt_usage(const char *) NORETURN; static void opt_showpaths(const char *); static void scores_only(int, char **, const char *) NORETURN; @@ -571,6 +572,12 @@ early_options(int *argc_p, char ***argv_p, char **hackdir_p) config_error_init(FALSE, "command line", FALSE); + /* treat "nethack ?" as a request for usage info; due to shell + processing, player likely has to use "nethack \?" or "nethack '?'" + [won't work if used as "nethack -dpath ?" or "nethack -d path ?"] */ + if (*argc_p > 1 && !strcmp((*argv_p)[1], "?")) + opt_usage(*hackdir_p); /* doesn't return */ + /* * Both *argc_p and *argv_p account for the program name as (*argv_p)[0]; * local argc and argv impicitly discard that (by starting 'ndx' at 1). @@ -617,6 +624,12 @@ early_options(int *argc_p, char ***argv_p, char **hackdir_p) #endif /* CHDIR */ } break; + case 'h': + case '?': + if (lopt(arg, ArgValDisallowed, "-help", origarg, &argc, &argv) + || lopt(arg, ArgValDisallowed, "-?", origarg, &argc, &argv)) + opt_usage(*hackdir_p); /* doesn't return */ + break; case 'n': oldargc = argc; if (!strcmp(arg, "-no-nethackrc")) /* no abbreviation allowed */ @@ -653,6 +666,10 @@ early_options(int *argc_p, char ***argv_p, char **hackdir_p) /*NOTREACHED*/ } break; + case 'u': + if (lopt(arg, ArgValDisallowed, "-usage", origarg, &argc, &argv)) + opt_usage(*hackdir_p); + break; case 'v': if (argcheck(argc, argv, ARG_VERSION) == 2) { opt_terminate(); @@ -689,6 +706,20 @@ opt_terminate(void) /*NOTREACHED*/ } +static void +opt_usage(const char *hackdir) +{ +#ifdef CHDIR + chdirx(hackdir, TRUE); +#else + nhUse(hackdir); +#endif + dlb_init(); + + genl_display_file(USAGEHELP, TRUE); + opt_terminate(); +} + /* show the sysconf file name, playground directory, run-time configuration file name, dumplog file name if applicable, and some other things */ static void -- 2.49.0