]> granicus.if.org Git - nethack/commitdiff
cmdhelp revamp
authorPatR <rankin@nethack.org>
Wed, 8 Jun 2016 21:58:35 +0000 (14:58 -0700)
committerPatR <rankin@nethack.org>
Wed, 8 Jun 2016 21:58:35 +0000 (14:58 -0700)
Implement a rudimentary if/elif/else/endif interpretor and use
conditionals in dat/cmdhelp to describe what command each keystroke
currently invokes, so that there isn't a lot of "(debug mode only)"
and "(if number_pad is off)" cluttering the feedback that the user
sees.  (The conditionals add quite a bit of clutter to the raw data
but users don't see that.  number_pad produces a lot of conditional
commands:  basic letters vs digits, 'g' vs 'G' for '5', phone
keypad vs normal layout of digits, and QWERTZ keyboard swap between
y/Y/^Y/M-y/M-Y/M-^Y and z/Z/^Z/M-z/M-Z/M-^Z.)

The interpretor understands
 '&#' for comment,
 '&? option' for 'if' (also '&? !option'
                       or '&? option=value[,value2,...]'
                       or '&? !option=value[,value2,...]'),
 '&: option' for 'elif' (with argument variations same as 'if';
                         any number of instances for each 'if'),
 '&:' for 'else' (also '&: #comment';
                  0 or 1 instance for a given 'if'), and
 '&.' for 'endif' (also '&. #comment'; required for each 'if').

The option handling is a bit of a mess, with no generality for
which options to deal with and only a comma separated list of
integer values for the '=value' part.  number_pad is the only
supported option that has a value; the few others (wizard/debug,
rest_on_space, #if SHELL, #if SUSPEND) are booleans.

dat/cmdhelp
src/pager.c

index ed3f76107002bad8baf1a8674c961c94b4fb5131..f275dca8cdce7f2dafa635d5a75dbc4b84ae8882 100644 (file)
-^       Show the type of a trap
-^[      Cancel command (same as ESCape key)
-^A      Redo the previous command
-^C      Quit the game
-^D      Kick something (usually a door, chest, or box)
-^E      Search a room (available in debug mode only)
-^F      Map the level (available in debug mode only)
-^G      Create a monster (available in debug mode only)
-^I      Identify all items (available in debug mode only)
-^O      Show dungeon overview (normal play) or special levels (debug mode)
-^P      Toggle through previously displayed game messages
-^R      Redraw screen
-^T      Teleport around level
-^V      Teleport between levels (available in debug mode only)
-^W      Wish (available in debug mode only)
-^X      Show your attributes (shows more in debug or explore mode)
-^Z      Suspend game (only if defined)
-a       Apply (use) a tool
-A       Remove all armor
-b       Go southwest 1 space
-B       Go southwest until you are on top of something
-^B      Go southwest until you are near something
-c       Close a door
-C       Call (name) a monster, an individual object, or a type of object
-d       Drop an item
-D       Drop specific item types
-e       Eat something
-E       Engrave writing on the floor
-f       Fire ammunition from quiver
-F       Followed by direction, fight a monster (even if you don't sense it)
-g       Followed by direction, move until you are near something
-G       Followed by direction, same as control-direction
-h       Go west 1 space (if number_pad is on, display help message)
-H       Go west until you are on top of something
-^H      Go west until you are near something
-i       Show your inventory
-I       Inventory specific item types
-j       Go south 1 space (or if number_pad is on, jump to another location)
-J       Go south until you are on top of something
-^J      Go south until you are near something
-k       Go north 1 space (or if number_pad is on, kick something)
-K       Go north until you are on top of something
-^K      Go north until you are near something
-l       Go east 1 space (or if number_pad is on, loot a box on the floor)
-L       Go east until you are on top of something
-^L      Go east until you are near something
-m       Followed by direction, move without picking anything up or fighting
-M       Followed by direction, move a distance without picking anything up
-n       Go southeast 1 space
-N       Go southeast until you are on something (if number_pad is on, name)
-^N      Go southeast until you are near something
-o       Open a door
-O       Show option settings, possibly change them
-p       Pay your shopping bill
-P       Put on an accessory (ring, amulet, etc)
-q       Quaff (drink) something (potion, water, etc)
-Q       Select ammunition for quiver
-r       Read a scroll or spellbook
-R       Remove an accessory (ring, amulet, etc)
-s       Search for traps and secret doors
-S       Save the game
-t       Throw something
-T       Take off one piece of armor
-u       Go northeast 1 space (or if number_pad is on, untrap something)
-U       Go northeast until you are on top of something
-^U      Go northeast until you are near something
-v       Show version
-V       Show long version and game history
-w       Wield (put in use) a weapon
-W       Wear a piece of armor
-x       Swap wielded and secondary weapons
-X       Toggle two-weapon combat
-y       Go northwest 1 space
-Y       Go northwest until you are on top of something
-^Y      Go northwest until you are near something
-z       Zap a wand
-Z       Zap (cast) a spell
-<       Go up a staircase
->       Go down a staircase
-/       Show what type of thing a symbol corresponds to
-?       Give a help message
-&       Tell what a command does
-!       Do a shell escape (only if defined)
-\       Show what object types have been discovered
-`       Show discovered types for one class of objects
-_       Travel via a shortest-path algorithm to a point on the map 
-.       Rest one move while doing nothing
-        Rest one move while doing nothing (if rest_on_space option is on)
-:       Look at what is on the floor
-;       Show what type of thing a map symbol on the level corresponds to
-,       Pick up things at the current location
-@       Toggle the pickup option on/off
-)       Show the weapon currently wielded
-[       Show the armor currently worn
-=       Show the ring(s) currently worn
-"       Show the amulet currently worn
-(       Show the tools currently in use
-*       Show all equipment in use (combination of the ),[,=,",( commands)
-$       Count your gold
-+       List known spells
-#       Perform an extended command
-M-?     Display extended command help (if the platform allows this)
-M-2     Toggle two-weapon combat (unless number_pad is enabled)
-M-a     Adjust inventory letters
-M-A     Annotate: supply a name for the current dungeon level
-M-c     Talk to someone
-M-C     Conduct: list voluntary challenges you have maintained
-M-d     Dip an object into something
-M-e     Advance or check weapons skills
-M-f     Force a lock
-M-i     Invoke an object's special powers
-M-j     Jump to another location
-M-l     Loot a box on the floor
-M-m     Use a monster's special ability
-M-n     Name a monster, an individual object, or a type of object
-M-o     Offer a sacrifice to the gods
-M-O     Overview: show a summary of the explored dungeon
-M-p     Pray to the gods for help
-M-q     Quit
-M-r     Rub a lamp
-M-R     Ride: mount or dismount a saddled steed
-M-s     Sit down
-M-t     Turn undead
-M-T     Tip: empty a container
-M-u     Untrap something (trap, door, or chest)
-M-v     Print compile time options for this version of NetHack
-M-w     Wipe off your face
+&# cmdhelp
+&      Tell what command a keystroke invokes
+^      Show the type of an adjacent trap
+^[     Cancel command (same as ESCape key)
+&? debug
+^E     Search for nearby traps, secret doors, and unseen monsters
+^F     Map level; reveals traps and secret corridors but not secret doors
+^G     Create a monster by name or class
+^I     View inventory with all items identified
+^O     List special level locations
+^V     Teleport between levels
+^W     Wish for something
+&: #!debug
+^E     unavailable debugging command
+^F     unavailable debugging command
+^G     unavailable debugging command
+^I     unavailable debugging command
+^O     Shortcut for '#overview': list interesting levels you have visited
+^V     unavailable debugging command
+^W     unavailable debugging command
+&. #?debug
+&? number_pad=0,-1
+b      Go southwest 1 space
+B      Go southwest until you are on top of something
+h      Go west 1 space
+H      Go west until you are on top of something
+j      Go south 1 space
+J      Go south until you are on top of something
+k      Go north 1 space
+K      Go north until you are on top of something
+l      Go east 1 space
+L      Go east until you are on top of something
+n      Go southeast 1 space
+N      Go southeast until you are on something
+u      Go northeast 1 space
+U      Go northeast until you are on top of something
+&# y,Y handled below
+&: #number_pad=1,2,3,4
+h      Help: synonym for '?'
+j      Jump: shortcut for '#jump'
+k      Kick: synonym for '^D'
+l      Loot: shortcut for '#loot'
+n      Start a count; continue with digit(s)
+N      Name: shortcut for '#name'
+u      Untrap: shortcut for '#untrap'
+&. #0,-1 vs 1,2,3,4
+a      Apply (use) a tool or break a wand
+A      Remove all armor and/or all accessories and/or unwield weapons
+^A     Redo the previous command
+^B     Go southwest until you are near something
+c      Close a door
+C      Call (name) a monster, an individual object, or a type of object
+^C     Interrupt: quit the game
+d      Drop an item
+D      Drop specific item types
+^D     Kick something (usually a door, chest, or box)
+e      Eat something
+E      Engrave writing on the floor
+f      Fire ammunition from quiver
+F      Followed by direction, fight a monster (even if you don't sense it)
+g      Followed by direction, move until you are near something
+G      Followed by direction, same as control-direction
+^H     Go west until you are near something
+i      Show your inventory
+I      Inventory specific item types
+^J     Go south until you are near something
+^K     Go north until you are near something
+^L     Go east until you are near something
+m      Followed by direction, move without picking anything up or fighting
+M      Followed by direction, move a distance without picking anything up
+^N     Go southeast until you are near something
+o      Open a door
+O      Show option settings, possibly change them
+p      Pay your shopping bill
+P      Put on an accessory (ring, amulet, etc; will work for armor too)
+^P     Toggle through previously displayed game messages
+q      Quaff (drink) something (potion, water, etc)
+Q      Select ammunition for quiver (use '#quit' to quit)
+r      Read a scroll or spellbook
+R      Remove an accessory (ring, amulet, etc; will work for armor too)
+^R     Redraw screen
+s      Search all immediately adjacent locations for traps and secret doors
+S      Save the game (and exit; there is no "save and keep going")
+t      Throw something (choose an item, then a direction--not a target)
+T      Take off one piece of armor (will work for accessories too)
+^T     Teleport around level
+^U     Go northeast until you are near something
+v      Show version ('#version' shows more information)
+V      Show history of game's development
+w      Wield a weapon (for dual weapons: 'w' secondary, 'x', 'w' primary, 'X')
+W      Wear a piece of armor (will work for accessories too)
+x      Swap wielded and secondary weapons
+X      Toggle two-weapon combat
+^X     Show your attributes (shows more in debug or explore mode)
+&? number_pad=0,1,2,3,4
+&? number_pad=0
+y      Go northwest 1 space
+Y      Go northwest until you are on top of something
+&.
+^Y     Go northwest until you are near something
+z      Zap a wand
+Z      Zap (cast) a spell
+&? suspend
+^Z     Suspend game; 'fg' (foreground) to resume
+&:
+^Z     unavailable command: suspend
+&.
+&: number_pad=-1
+y      Zap a wand
+Y      Zap (cast) a spell
+&? suspend
+^Y     Suspend game; 'fg' (foreground) to resume
+&:
+^Y     unavailable command: suspend
+&.
+z      Go northwest 1 space
+Z      Go northwest until you are on top of something
+^Z     Go northwest until you are near something
+&. #0,1..4 vs -1
+<      Go up a staircase
+>      Go down a staircase
+/      Show what type of thing a symbol corresponds to
+?      Give a help message
+&? shell
+!      Do a shell escape; 'exit' shell to come back
+&:
+!      unavailable command: shell
+&.
+\      Show what object types have been discovered
+`      Show discovered types for one class of objects
+_      Travel via a shortest-path algorithm to a point on the map
+.      Rest one move while doing nothing
+&? rest_on_space
+       Rest one move while doing nothing
+&.
+:      Look at what is on the floor
+;      Show what type of thing a map symbol on the level corresponds to
+,      Pick up things at the current location
+@      Toggle the pickup option on/off
+)      Show the weapon(s) currently wielded or readied
+[      Show the armor currently worn
+=      Show the ring(s) currently worn
+"      Show the amulet currently worn
+(      Show the tools currently in use
+*      Show all equipment in use (combination of the ),[,=,",( commands)
+$      Count your gold
++      List known spells
+#      Perform an extended command (use '#?' to list choices)
+&# number_pad:
+&#  -1 = numpad off, swap y with z (including Y with Z, ^Y with ^Z, M-y &c)
+&#   0 = numpad off (default)
+&#   1 = numpad on, normal keypad layout, '5'->'g'
+&#   2 = numpad on, normal keypad layout, '5'->'G'
+&#   3 = numpad on, phone keypad layout, '5'->'g'
+&#   4 = numpad on, phone keypad layout, '5'->'G'
+&? number_pad = 1,2,3,4
+0      Show inventory
+4      Move west
+6      Move east
+&: #-1,0
+0      Continue a count
+4      Start or continue a count
+6      Start or continue a count
+&. #1,2,3,4 vs -1,0
+&? number_pad=1,2
+7      Move northwest
+8      Move north
+9      Move northeast
+1      Move southwest
+2      Move south
+3      Move southeast
+&: number_pad=3,4
+1      Move northwest
+2      Move north
+3      Move northeast
+7      Move southwest
+8      Move south
+9      Move southeast
+&: #-1,0
+1      Start or continue a count
+2      Start or continue a count
+3      Start or continue a count
+7      Start or continue a count
+8      Start or continue a count
+9      Start or continue a count
+&. #1,2 vs 3,4 vs -1,0
+&? number_pad=1,3
+5      'g' movement prefix
+&: number_pad=2,4
+5      'G' movement prefix
+&: #-1,0
+5      Start or continue a count
+M-2    Toggle two-weapon combat
+&. #1,3 vs 2,4 vs -1,0
+M-?    Display extended command help (if the platform allows this)
+M-a    Adjust inventory letters
+M-A    Annotate: supply a name for the current dungeon level
+M-c    Chat: talk to an adjacent creature
+M-C    Conduct: list voluntary challenges you have maintained
+M-d    Dip an object into something
+M-e    Enhance: check weapons skills, advance them if eligible
+M-f    Force a lock
+M-i    Invoke an object's special powers
+M-j    Jump to a nearby location
+M-l    Loot a box on the floor
+M-m    When polymorphed, use a monster's special ability
+M-n    Name a monster, an individual object, or a type of object
+M-N    Name a monster, an individual object, or a type of object
+M-o    Offer a sacrifice to the gods
+M-O    Overview: show a summary of the explored dungeon
+M-p    Pray to the gods for help
+M-q    Quit (exit without saving)
+M-r    Rub a lamp or a touchstone
+M-R    Ride: mount or dismount a saddled steed
+M-s    Sit down
+M-t    Turn undead
+M-T    Tip: empty a container
+M-u    Untrap something (trap, door, or chest)
+M-v    Print compile time options for this version of NetHack
+M-w    Wipe off your face
index b35601f9e143e099ebd89cf97de3fefcf4efb428..19b1509920b0b610c851664852f9aeb30f669006 100644 (file)
@@ -17,6 +17,8 @@ STATIC_DCL struct permonst *FDECL(lookat, (int, int, char *, char *));
 STATIC_DCL void FDECL(checkfile, (char *, struct permonst *,
                                   BOOLEAN_P, BOOLEAN_P));
 STATIC_DCL void FDECL(look_all, (BOOLEAN_P,BOOLEAN_P));
+STATIC_DCL void NDECL(whatdoes_help);
+STATIC_DCL boolean FDECL(whatdoes_cond, (char *, boolean *, int *, int));
 STATIC_DCL boolean FDECL(help_menu, (int *));
 STATIC_DCL void NDECL(docontact);
 #ifdef PORT_HELP
@@ -1307,7 +1309,7 @@ doidtrap()
 }
 
 STATIC_DCL void
-dowhatdoes_help()
+whatdoes_help()
 {
     dlb *fp;
     char *p, buf[BUFSZ];
@@ -1332,14 +1334,131 @@ dowhatdoes_help()
     destroy_nhwindow(tmpwin);
 }
 
+#define WD_STACKLIMIT 5
+
+STATIC_OVL boolean
+whatdoes_cond(buf, stack, depth, lnum)
+char *buf;
+boolean *stack;
+int *depth, lnum;
+{
+    const char badstackfmt[] = "cmdhlp: too many &%c directives at line %d.";
+    boolean newcond, neg;
+    char *p, *q, act = buf[1];
+    int np = 0;
+
+    buf += 2;
+    mungspaces(buf);
+    if (act == '#' || *buf == '#') {
+        *buf = '\0';
+        neg = FALSE; /* lint suppression */
+        p = q = (char *) 0;
+    } else {
+        if ((neg = (*buf == '!')) != 0)
+            if (*++buf == ' ')
+                ++buf;
+        p = index(buf, '='), q = index(buf, ':');
+        if (!p || (q && q < p))
+            p = q;
+        if (p) { /* we have a value specified */
+            /* handle a space before or after (or both) '=' (or ':') */
+            if (p > buf && p[-1] == ' ')
+                p[-1] = '\0'; /* end of keyword in buf[] */
+            *p++ = '\0'; /* terminate keyword, advance to start of value */
+            if (*p == ' ')
+                p++;
+        }
+    }
+    newcond = TRUE;
+    if (*buf && (act == '?' || act == ':')) {
+        if (!strcmpi(buf, "number_pad")) {
+            if (!p) {
+                newcond = iflags.num_pad;
+            } else {
+                /* convert internal encoding (separate yes/no and 0..3)
+                   back to user-visible one (-1..4) */
+                np = iflags.num_pad ? (1 + iflags.num_pad_mode) /* 1..4 */
+                                    : (-1 * iflags.num_pad_mode); /* -1..0 */
+                newcond = FALSE;
+                for (; p; p = q) {
+                    q = index(p, ',');
+                    if (q)
+                        *q++ = '\0';
+                    if (atoi(p) == np) {
+                        newcond = TRUE;
+                        break;
+                    }
+                }
+            }
+        } else if (!strcmpi(buf, "rest_on_space")) {
+            newcond = flags.rest_on_space;
+        } else if (!strcmpi(buf, "debug") || !strcmpi(buf, "wizard")) {
+            newcond = flags.debug; /* == wizard */
+        } else if (!strcmpi(buf, "shell")) {
+#ifdef SHELL
+            /* should we also check sysopt.shellers? */
+            newcond = TRUE;
+#else
+            newcond = FALSE;
+#endif
+        } else if (!strcmpi(buf, "suspend")) {
+#ifdef SUSPEND
+            /* sysopt.shellers is also used for dosuspend()... */
+            newcond = TRUE;
+#else
+            newcond = FALSE;
+#endif
+        } else {
+            impossible(
+                "cmdhelp: unrecognized &%c conditional at line %d: \"%.20s\"",
+                       act, lnum, buf);
+            neg = FALSE;
+        }
+        /* this works for number_pad too: &? !number_pad:-1,0
+           would be true for 1..4 after negation */
+        if (neg)
+            newcond = !newcond;
+    }
+    switch (act) {
+    default:
+    case '#': /* comment */
+        break;
+    case '.': /* endif */
+        if (--*depth < 0) {
+            impossible(badstackfmt, '.', lnum);
+            *depth = 0;
+        }
+        break;
+    case ':': /* else or elif */
+        if (*depth == 0) {
+            impossible(badstackfmt, ':', lnum);
+            *depth = 1; /* so that stack[*depth - 1] is a valid access */
+        }
+        if (stack[*depth] || !stack[*depth - 1])
+            stack[*depth] = FALSE;
+        else if (newcond)
+            stack[*depth] = TRUE;
+        break;
+    case '?': /* if */
+        if (++*depth >= WD_STACKLIMIT) {
+            impossible(badstackfmt, '?', lnum);
+            *depth = WD_STACKLIMIT - 1;
+        }
+        stack[*depth] = newcond && stack[*depth - 1];
+        break;
+    }
+    return stack[*depth];
+}
+
 char *
 dowhatdoes_core(q, cbuf)
 char q;
 char *cbuf;
 {
     dlb *fp;
-    char bufr[BUFSZ];
-    register char *buf = &bufr[6], ctrl, meta;
+    char buf[BUFSZ];
+    boolean cond, stack[WD_STACKLIMIT];
+    int ctrl, meta, depth = 0, lnum = 0;
 
     fp = dlb_fopen(CMDHELPFILE, "r");
     if (!fp) {
@@ -1347,25 +1466,44 @@ char *cbuf;
         return 0;
     }
 
-    ctrl = ((q <= '\033') ? (q - 1 + 'A') : 0);
-    meta = ((0x80 & q) ? (0x7f & q) : 0);
-    while (dlb_fgets(buf, BUFSZ - 6, fp)) {
-        if ((ctrl && *buf == '^' && *(buf + 1) == ctrl)
-            || (meta && *buf == 'M' && *(buf + 1) == '-'
-                && *(buf + 2) == meta) || *buf == q) {
+    meta = (0x80 & (uchar) q) != 0;
+    if (meta)
+        q &= 0x7f;
+    ctrl = (0x1f & (uchar) q) == (uchar) q;
+    if (ctrl)
+        q |= 0x40; /* NUL -> '@', ^A -> 'A', ... ^Z -> 'Z', ^[ -> '[', ... */
+    else if (q == 0x7f)
+        ctrl = 1, q = '?';
+
+    cond = stack[0] = TRUE, stack[1] = FALSE;
+    while (dlb_fgets(buf, sizeof buf, fp)) {
+        ++lnum;
+        if (buf[0] == '&' && buf[1] && index("?:.#", buf[1])) {
+            cond = whatdoes_cond(buf, stack, &depth, lnum);
+            continue;
+        }
+        if (!cond)
+            continue;
+        if (meta ? (buf[0] == 'M' && buf[1] == '-'
+                    && (ctrl ? buf[2] == '^' && highc(buf[3]) == q
+                             : buf[2] == q))
+                 : (ctrl ? buf[0] == '^' && highc(buf[1]) == q
+                         : buf[0] == q)) {
             (void) strip_newline(buf);
-            if (ctrl && buf[2] == '\t') {
-                buf = bufr + 1;
-                (void) strncpy(buf, "^?      ", 8);
-                buf[1] = ctrl;
-            } else if (meta && buf[3] == '\t') {
-                buf = bufr + 2;
+            if (index(buf, '\t'))
+                (void) tabexpand(buf);
+            if (meta && ctrl && buf[4] == ' ') {
+                (void) strncpy(buf, "M-^?    ", 8);
+                buf[3] = q;
+            } else if (meta && buf[3] == ' ') {
                 (void) strncpy(buf, "M-?     ", 8);
-                buf[2] = meta;
-            } else if (buf[1] == '\t') {
-                buf = bufr;
+                buf[2] = q;
+            } else if (ctrl && buf[2] == ' ') {
+                (void) strncpy(buf, "^?      ", 8);
+                buf[1] = q;
+            } else if (buf[1] == ' ') {
+                (void) strncpy(buf, "?       ", 8);
                 buf[0] = q;
-                (void) strncpy(buf + 1, "       ", 7);
             }
             (void) dlb_fclose(fp);
             Strcpy(cbuf, buf);
@@ -1373,6 +1511,8 @@ char *cbuf;
         }
     }
     (void) dlb_fclose(fp);
+    if (depth != 0)
+        impossible("cmdhelp: mismatched &? &: &. conditionals.");
     return (char *) 0;
 }
 
@@ -1395,9 +1535,9 @@ dowhatdoes()
     if (q == '\033' && iflags.altmeta) {
         /* in an ideal world, we would know whether another keystroke
            was already pending, but this is not an ideal world...
-           if user types ESC, we'll essentially hang until another
+           if user typed ESC, we'll essentially hang until another
            character is typed */
-        q = yn_function("", (char *) 0, '\0');
+        q = yn_function("]", (char *) 0, '\0');
         if (q != '\033')
             q = (char) ((uchar) q | 0200);
     }
@@ -1407,7 +1547,7 @@ dowhatdoes()
     reslt = dowhatdoes_core(q, bufr);
     if (reslt) {
         if (q == '&' || q == '?')
-            dowhatdoes_help();
+            whatdoes_help();
         pline("%s", reslt);
     } else {
         pline("No such command '%s', char code %d (0%03o or 0x%02x).",
@@ -1416,7 +1556,7 @@ dowhatdoes()
     return 0;
 }
 
-void
+STATIC_OVL void
 docontact()
 {
     winid cwin = create_nhwindow(NHW_TEXT);