cmdhelp revamp

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