1 /* NetHack 3.7	cmd.c	$NHDT-Date: 1618175625 2021/04/11 21:13:45 $  $NHDT-Branch: NetHack-3.7 $:$NHDT-Revision: 1.463 $ */
2 /* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */
3 /*-Copyright (c) Robert Patrick Rankin, 2013. */
4 /* NetHack may be freely redistributed.  See license for details. */
5 
6 #include "hack.h"
7 #include "func_tab.h"
8 
9 #ifdef ALTMETA
10 static boolean alt_esc = FALSE;
11 #endif
12 
13 #ifdef UNIX
14 /*
15  * Some systems may have getchar() return EOF for various reasons, and
16  * we should not quit before seeing at least NR_OF_EOFS consecutive EOFs.
17  */
18 #if defined(SYSV) || defined(DGUX) || defined(HPUX)
19 #define NR_OF_EOFS 20
20 #endif
21 #endif
22 
23 #define CMD_TRAVEL (char) 0x90
24 #define CMD_CLICKLOOK (char) 0x8F
25 
26 #ifdef DUMB /* stuff commented out in extern.h, but needed here */
27 extern int doapply(void);            /**/
28 extern int dorub(void);              /**/
29 extern int dojump(void);             /**/
30 extern int doextlist(void);          /**/
31 extern int enter_explore_mode(void); /**/
32 extern int dodrop(void);             /**/
33 extern int doddrop(void);            /**/
34 extern int dodown(void);             /**/
35 extern int doup(void);               /**/
36 extern int donull(void);             /**/
37 extern int dowipe(void);             /**/
38 extern int docallcnd(void);          /**/
39 extern int dotakeoff(void);          /**/
40 extern int doremring(void);          /**/
41 extern int dowear(void);             /**/
42 extern int doputon(void);            /**/
43 extern int doddoremarm(void);        /**/
44 extern int dokick(void);             /**/
45 extern int dofire(void);             /**/
46 extern int dothrow(void);            /**/
47 extern int doeat(void);              /**/
48 extern int done2(void);              /**/
49 extern int vanquished(void);         /**/
50 extern int doengrave(void);          /**/
51 extern int dopickup(void);           /**/
52 extern int ddoinv(void);             /**/
53 extern int dotypeinv(void);          /**/
54 extern int dolook(void);             /**/
55 extern int doprgold(void);           /**/
56 extern int doprwep(void);            /**/
57 extern int doprarm(void);            /**/
58 extern int doprring(void);           /**/
59 extern int dopramulet(void);         /**/
60 extern int doprtool(void);           /**/
61 extern int dosuspend(void);          /**/
62 extern int doforce(void);            /**/
63 extern int doopen(void);             /**/
64 extern int doclose(void);            /**/
65 extern int dosh(void);               /**/
66 extern int dodiscovered(void);       /**/
67 extern int doclassdisco(void);       /**/
68 extern int doset(void);              /**/
69 extern int dotogglepickup(void);     /**/
70 extern int dowhatis(void);           /**/
71 extern int doquickwhatis(void);      /**/
72 extern int dowhatdoes(void);         /**/
73 extern int dohelp(void);             /**/
74 extern int dohistory(void);          /**/
75 extern int doloot(void);             /**/
76 extern int dodrink(void);            /**/
77 extern int dodip(void);              /**/
78 extern int dosacrifice(void);        /**/
79 extern int dopray(void);             /**/
80 extern int dotip(void);              /**/
81 extern int doturn(void);             /**/
82 extern int doredraw(void);           /**/
83 extern int doread(void);             /**/
84 extern int dosave(void);             /**/
85 extern int dosearch(void);           /**/
86 extern int doidtrap(void);           /**/
87 extern int dopay(void);              /**/
88 extern int dosit(void);              /**/
89 extern int dotalk(void);             /**/
90 extern int docast(void);             /**/
91 extern int dovspell(void);           /**/
92 extern int dotelecmd(void);          /**/
93 extern int dountrap(void);           /**/
94 extern int doversion(void);          /**/
95 extern int doextversion(void);       /**/
96 extern int doswapweapon(void);       /**/
97 extern int dowield(void);            /**/
98 extern int dowieldquiver(void);      /**/
99 extern int dozap(void);              /**/
100 extern int doorganize(void);         /**/
101 #endif /* DUMB */
102 
103 static int dosuspend_core(void);
104 static int dosh_core(void);
105 static int doherecmdmenu(void);
106 static int dotherecmdmenu(void);
107 static int doprev_message(void);
108 static int timed_occupation(void);
109 static int doextcmd(void);
110 static int dotravel(void);
111 static int doterrain(void);
112 static int wiz_wish(void);
113 static int wiz_identify(void);
114 static int wiz_map(void);
115 static int wiz_makemap(void);
116 static int wiz_genesis(void);
117 static int wiz_where(void);
118 static int wiz_detect(void);
119 static int wiz_panic(void);
120 static int wiz_polyself(void);
121 static int wiz_load_lua(void);
122 static int wiz_level_tele(void);
123 static int wiz_level_change(void);
124 static int wiz_flip_level(void);
125 static int wiz_show_seenv(void);
126 static int wiz_show_vision(void);
127 static int wiz_smell(void);
128 static int wiz_intrinsic(void);
129 static int wiz_show_wmodes(void);
130 static int wiz_show_stats(void);
131 static int wiz_rumor_check(void);
132 #ifdef DEBUG_MIGRATING_MONS
133 static int wiz_migrate_mons(void);
134 #endif
135 
136 static void wiz_map_levltyp(void);
137 static void wiz_levltyp_legend(void);
138 #if defined(__BORLANDC__) && !defined(_WIN32)
139 extern void show_borlandc_stats(winid);
140 #endif
141 static int size_monst(struct monst *, boolean);
142 static int size_obj(struct obj *);
143 static void count_obj(struct obj *, long *, long *, boolean, boolean);
144 static void obj_chain(winid, const char *, struct obj *, boolean, long *,
145                       long *);
146 static void mon_invent_chain(winid, const char *, struct monst *, long *,
147                              long *);
148 static void mon_chain(winid, const char *, struct monst *, boolean, long *,
149                       long *);
150 static void contained_stats(winid, const char *, long *, long *);
151 static void misc_stats(winid, long *, long *);
152 static boolean accept_menu_prefix(int (*)(void));
153 
154 static void add_herecmd_menuitem(winid, int (*)(void), const char *);
155 static char here_cmd_menu(boolean);
156 static char there_cmd_menu(boolean, int, int);
157 static char *parse(void);
158 static void show_direction_keys(winid, char, boolean);
159 static boolean help_dir(char, int, const char *);
160 
161 static void commands_init(void);
162 static boolean keylist_func_has_key(const struct ext_func_tab *, boolean *);
163 static int keylist_putcmds(winid, boolean, int, int, boolean *);
164 static int ch2spkeys(char, int, int);
165 static boolean prefix_cmd(char);
166 static const char *spkey_name(int);
167 
168 static int (*timed_occ_fn)(void);
169 static char *doc_extcmd_flagstr(winid, const struct ext_func_tab *);
170 
171 static const char *readchar_queue = "";
172 /* for rejecting attempts to use wizard mode commands */
173 static const char unavailcmd[] = "Unavailable command '%s'.";
174 /* for rejecting #if !SHELL, !SUSPEND */
175 static const char cmdnotavail[] = "'%s' command not available.";
176 
177 static int
doprev_message(void)178 doprev_message(void)
179 {
180     return nh_doprev_message();
181 }
182 
183 /* Count down by decrementing multi */
184 static int
timed_occupation(void)185 timed_occupation(void)
186 {
187     (*timed_occ_fn)();
188     if (g.multi > 0)
189         g.multi--;
190     return g.multi > 0;
191 }
192 
193 /* If you have moved since initially setting some occupations, they
194  * now shouldn't be able to restart.
195  *
196  * The basic rule is that if you are carrying it, you can continue
197  * since it is with you.  If you are acting on something at a distance,
198  * your orientation to it must have changed when you moved.
199  *
200  * The exception to this is taking off items, since they can be taken
201  * off in a number of ways in the intervening time, screwing up ordering.
202  *
203  *      Currently:      Take off all armor.
204  *                      Picking Locks / Forcing Chests.
205  *                      Setting traps.
206  */
207 void
reset_occupations(void)208 reset_occupations(void)
209 {
210     reset_remarm();
211     reset_pick();
212     reset_trapset();
213 }
214 
215 /* If a time is given, use it to timeout this function, otherwise the
216  * function times out by its own means.
217  */
218 void
set_occupation(int (* fn)(void),const char * txt,int xtime)219 set_occupation(int (*fn)(void), const char *txt, int xtime)
220 {
221     if (xtime) {
222         g.occupation = timed_occupation;
223         timed_occ_fn = fn;
224     } else
225         g.occupation = fn;
226     g.occtxt = txt;
227     g.occtime = 0;
228     return;
229 }
230 
231 static char popch(void);
232 
233 static char
popch(void)234 popch(void)
235 {
236     /* If occupied, return '\0', letting tgetch know a character should
237      * be read from the keyboard.  If the character read is not the
238      * ABORT character (as checked in pcmain.c), that character will be
239      * pushed back on the pushq.
240      */
241     if (g.occupation)
242         return '\0';
243     if (g.in_doagain)
244         return (char) ((g.shead != g.stail) ? g.saveq[g.stail++] : '\0');
245     else
246         return (char) ((g.phead != g.ptail) ? g.pushq[g.ptail++] : '\0');
247 }
248 
249 char
pgetchar(void)250 pgetchar(void) /* courtesy of aeb@cwi.nl */
251 {
252     register int ch;
253 
254     if (iflags.debug_fuzzer)
255         return randomkey();
256     if (!(ch = popch()))
257         ch = nhgetch();
258     return (char) ch;
259 }
260 
261 /* A ch == 0 resets the pushq */
262 void
pushch(char ch)263 pushch(char ch)
264 {
265     if (!ch)
266         g.phead = g.ptail = 0;
267     if (g.phead < BSIZE)
268         g.pushq[g.phead++] = ch;
269     return;
270 }
271 
272 /* A ch == 0 resets the saveq.  Only save keystrokes when not
273  * replaying a previous command.
274  */
275 void
savech(char ch)276 savech(char ch)
277 {
278     if (!g.in_doagain) {
279         if (!ch)
280             g.phead = g.ptail = g.shead = g.stail = 0;
281         else if (g.shead < BSIZE)
282             g.saveq[g.shead++] = ch;
283     }
284     return;
285 }
286 
287 /* here after # - now read a full-word command */
288 static int
doextcmd(void)289 doextcmd(void)
290 {
291     int idx, retval;
292     int (*func)(void);
293 
294     /* keep repeating until we don't run help or quit */
295     do {
296         idx = get_ext_cmd();
297         if (idx < 0)
298             return 0; /* quit */
299 
300         func = extcmdlist[idx].ef_funct;
301         if (!wizard && (extcmdlist[idx].flags & WIZMODECMD)) {
302             You("can't do that.");
303             return 0;
304         }
305         if (iflags.menu_requested && !accept_menu_prefix(func)) {
306             pline("'%s' prefix has no effect for the %s command.",
307                   visctrl(g.Cmd.spkeys[NHKF_REQMENU]),
308                   extcmdlist[idx].ef_txt);
309             iflags.menu_requested = FALSE;
310         }
311         retval = (*func)();
312     } while (func == doextlist);
313 
314     return retval;
315 }
316 
317 /* format extended command flags for display */
318 static char *
doc_extcmd_flagstr(winid menuwin,const struct ext_func_tab * efp)319 doc_extcmd_flagstr(winid menuwin,
320                    /* if Null, add a footnote to the menu */
321                    const struct ext_func_tab *efp)
322 {
323     static char Abuf[10]; /* 5 would suffice: {'[','m','A',']','\0'} */
324 
325     /* note: tag shown for menu prefix is 'm' even if m-prefix action
326        has been bound to some other key */
327     if (!efp) {
328         char qbuf[QBUFSZ];
329         anything any = cg.zeroany;
330 
331         add_menu(menuwin, &nul_glyphinfo, &any, 0, 0, ATR_NONE,
332                  "[A] Command autocompletes", MENU_ITEMFLAGS_NONE);
333         Sprintf(qbuf, "[m] Command accepts '%c' prefix",
334                 g.Cmd.spkeys[NHKF_REQMENU]);
335         add_menu(menuwin, &nul_glyphinfo, &any, 0, 0, ATR_NONE, qbuf,
336                  MENU_ITEMFLAGS_NONE);
337         return (char *) 0;
338     } else {
339         boolean mprefix = accept_menu_prefix(efp->ef_funct),
340                 autocomplete = (efp->flags & AUTOCOMPLETE) != 0;
341         char *p = Abuf;
342 
343         /* "" or "[m]" or "[A]" or "[mA]" */
344         if (mprefix || autocomplete) {
345             *p++ = '[';
346             if (mprefix)
347                 *p++ = 'm';
348             if (autocomplete)
349                 *p++ = 'A';
350             *p++ = ']';
351         }
352         *p = '\0';
353         return Abuf;
354     }
355 }
356 
357 /* here after #? - now list all full-word commands and provide
358    some navigation capability through the long list */
359 int
doextlist(void)360 doextlist(void)
361 {
362     register const struct ext_func_tab *efp = (struct ext_func_tab *) 0;
363     char buf[BUFSZ], searchbuf[BUFSZ], promptbuf[QBUFSZ];
364     winid menuwin;
365     anything any;
366     menu_item *selected;
367     int n, pass;
368     int menumode = 0, menushown[2], onelist = 0;
369     boolean redisplay = TRUE, search = FALSE;
370     static const char *headings[] = { "Extended commands",
371                                       "Debugging Extended Commands" };
372 
373     searchbuf[0] = '\0';
374     menuwin = create_nhwindow(NHW_MENU);
375 
376     while (redisplay) {
377         redisplay = FALSE;
378         any = cg.zeroany;
379         start_menu(menuwin, MENU_BEHAVE_STANDARD);
380         add_menu(menuwin, &nul_glyphinfo, &any, 0, 0, ATR_NONE,
381                  "Extended Commands List",
382                  MENU_ITEMFLAGS_NONE);
383         add_menu(menuwin, &nul_glyphinfo, &any, 0, 0, ATR_NONE,
384                  "", MENU_ITEMFLAGS_NONE);
385 
386         Sprintf(buf, "Switch to %s commands that don't autocomplete",
387                 menumode ? "including" : "excluding");
388         any.a_int = 1;
389         add_menu(menuwin, &nul_glyphinfo, &any, 'a', 0, ATR_NONE, buf,
390                  MENU_ITEMFLAGS_NONE);
391 
392         if (!*searchbuf) {
393             any.a_int = 2;
394             /* was 's', but then using ':' handling within the interface
395                would only examine the two or three meta entries, not the
396                actual list of extended commands shown via separator lines;
397                having ':' as an explicit selector overrides the default
398                menu behavior for it; we retain 's' as a group accelerator */
399             add_menu(menuwin, &nul_glyphinfo, &any, ':', 's', ATR_NONE,
400                      "Search extended commands",
401                      MENU_ITEMFLAGS_NONE);
402         } else {
403             Strcpy(buf, "Switch back from search");
404             if (strlen(buf) + strlen(searchbuf) + strlen(" (\"\")") < QBUFSZ)
405                 Sprintf(eos(buf), " (\"%s\")", searchbuf);
406             any.a_int = 3;
407             /* specifying ':' as a group accelerator here is mostly a
408                statement of intent (we'd like to accept it as a synonym but
409                also want to hide it from general menu use) because it won't
410                work for interfaces which support ':' to search; use as a
411                general menu command takes precedence over group accelerator */
412             add_menu(menuwin, &nul_glyphinfo, &any, 's', ':', ATR_NONE,
413                      buf, MENU_ITEMFLAGS_NONE);
414         }
415         if (wizard) {
416             any.a_int = 4;
417             add_menu(menuwin, &nul_glyphinfo, &any, 'z', 0, ATR_NONE,
418           onelist ? "Switch to showing debugging commands in separate section"
419        : "Switch to showing all alphabetically, including debugging commands",
420                      MENU_ITEMFLAGS_NONE);
421         }
422         any = cg.zeroany;
423         add_menu(menuwin, &nul_glyphinfo, &any, 0, 0, ATR_NONE,
424                  "", MENU_ITEMFLAGS_NONE);
425         menushown[0] = menushown[1] = 0;
426         n = 0;
427         for (pass = 0; pass <= 1; ++pass) {
428             /* skip second pass if not in wizard mode or wizard mode
429                commands are being integrated into a single list */
430             if (pass == 1 && (onelist || !wizard))
431                 break;
432             for (efp = extcmdlist; efp->ef_txt; efp++) {
433                 int wizc;
434 
435                 if ((efp->flags & CMD_NOT_AVAILABLE) != 0)
436                     continue;
437                 /* if hiding non-autocomplete commands, skip such */
438                 if (menumode == 1 && (efp->flags & AUTOCOMPLETE) == 0)
439                     continue;
440                 /* if searching, skip this command if it doesn't match */
441                 if (*searchbuf
442                     /* first try case-insensitive substring match */
443                     && !strstri(efp->ef_txt, searchbuf)
444                     && !strstri(efp->ef_desc, searchbuf)
445                     /* wildcard support; most interfaces use case-insensitve
446                        pmatch rather than regexp for menu searching */
447                     && !pmatchi(searchbuf, efp->ef_txt)
448                     && !pmatchi(searchbuf, efp->ef_desc))
449                     continue;
450                 /* skip wizard mode commands if not in wizard mode;
451                    when showing two sections, skip wizard mode commands
452                    in pass==0 and skip other commands in pass==1 */
453                 wizc = (efp->flags & WIZMODECMD) != 0;
454                 if (wizc && !wizard)
455                     continue;
456                 if (!onelist && pass != wizc)
457                     continue;
458 
459                 /* We're about to show an item, have we shown the menu yet?
460                    Doing menu in inner loop like this on demand avoids a
461                    heading with no subordinate entries on the search
462                    results menu. */
463                 if (!menushown[pass]) {
464                     Strcpy(buf, headings[pass]);
465                     add_menu(menuwin, &nul_glyphinfo, &any, 0, 0,
466                              iflags.menu_headings, buf,
467                              MENU_ITEMFLAGS_NONE);
468                     menushown[pass] = 1;
469                 }
470                 /* longest ef_txt at present is "wizrumorcheck" (13 chars);
471                    2nd field will be "    " or " [A]" or " [m]" or "[mA]" */
472                 Sprintf(buf, " %-14s %4s %s", efp->ef_txt,
473                         doc_extcmd_flagstr(menuwin, efp), efp->ef_desc);
474                 add_menu(menuwin, &nul_glyphinfo, &any, 0, 0, ATR_NONE,
475                          buf, MENU_ITEMFLAGS_NONE);
476                 ++n;
477             }
478             if (n)
479                 add_menu(menuwin, &nul_glyphinfo, &any, 0, 0, ATR_NONE,
480                          "", MENU_ITEMFLAGS_NONE);
481         }
482         if (*searchbuf && !n)
483             add_menu(menuwin, &nul_glyphinfo, &any, 0, 0, ATR_NONE,
484                      "no matches", MENU_ITEMFLAGS_NONE);
485         else
486             (void) doc_extcmd_flagstr(menuwin, (struct ext_func_tab *) 0);
487 
488         end_menu(menuwin, (char *) 0);
489         n = select_menu(menuwin, PICK_ONE, &selected);
490         if (n > 0) {
491             switch (selected[0].item.a_int) {
492             case 1: /* 'a': toggle show/hide non-autocomplete */
493                 menumode = 1 - menumode;  /* toggle 0 -> 1, 1 -> 0 */
494                 redisplay = TRUE;
495                 break;
496             case 2: /* ':' when not searching yet: enable search */
497                 search = TRUE;
498                 break;
499             case 3: /* 's' when already searching: disable search */
500                 search = FALSE;
501                 searchbuf[0] = '\0';
502                 redisplay = TRUE;
503                 break;
504             case 4: /* 'z': toggle showing wizard mode commands separately */
505                 search = FALSE;
506                 searchbuf[0] = '\0';
507                 onelist = 1 - onelist;  /* toggle 0 -> 1, 1 -> 0 */
508                 redisplay = TRUE;
509                 break;
510             }
511             free((genericptr_t) selected);
512         } else {
513             search = FALSE;
514             searchbuf[0] = '\0';
515         }
516         if (search) {
517             Strcpy(promptbuf, "Extended command list search phrase");
518             Strcat(promptbuf, "?");
519             getlin(promptbuf, searchbuf);
520             (void) mungspaces(searchbuf);
521             if (searchbuf[0] == '\033')
522                 searchbuf[0] = '\0';
523             if (*searchbuf)
524                 redisplay = TRUE;
525             search = FALSE;
526         }
527     }
528     destroy_nhwindow(menuwin);
529     return 0;
530 }
531 
532 #if defined(TTY_GRAPHICS) || defined(CURSES_GRAPHICS)
533 #define MAX_EXT_CMD 200 /* Change if we ever have more ext cmds */
534 
535 DISABLE_WARNING_FORMAT_NONLITERAL
536 
537 /*
538  * This is currently used only by the tty interface and is
539  * controlled via runtime option 'extmenu'.  (Most other interfaces
540  * already use a menu all the time for extended commands.)
541  *
542  * ``# ?'' is counted towards the limit of the number of commands,
543  * so we actually support MAX_EXT_CMD-1 "real" extended commands.
544  *
545  * Here after # - now show pick-list of possible commands.
546  */
547 int
extcmd_via_menu(void)548 extcmd_via_menu(void)
549 {
550     const struct ext_func_tab *efp;
551     menu_item *pick_list = (menu_item *) 0;
552     winid win;
553     anything any;
554     const struct ext_func_tab *choices[MAX_EXT_CMD + 1];
555     char buf[BUFSZ];
556     char cbuf[QBUFSZ], prompt[QBUFSZ], fmtstr[20];
557     int i, n, nchoices, acount;
558     int ret, len, biggest;
559     int accelerator, prevaccelerator;
560     int matchlevel = 0;
561     boolean wastoolong, one_per_line;
562 
563     ret = 0;
564     cbuf[0] = '\0';
565     biggest = 0;
566     while (!ret) {
567         i = n = 0;
568         any = cg.zeroany;
569         /* populate choices */
570         for (efp = extcmdlist; efp->ef_txt; efp++) {
571             if ((efp->flags & CMD_NOT_AVAILABLE)
572                 || !(efp->flags & AUTOCOMPLETE)
573                 || (!wizard && (efp->flags & WIZMODECMD)))
574                 continue;
575             if (!matchlevel || !strncmp(efp->ef_txt, cbuf, matchlevel)) {
576                 choices[i] = efp;
577                 if ((len = (int) strlen(efp->ef_desc)) > biggest)
578                     biggest = len;
579                 if (++i > MAX_EXT_CMD) {
580 #if (NH_DEVEL_STATUS != NH_STATUS_RELEASED)
581                     impossible(
582       "Exceeded %d extended commands in doextcmd() menu; 'extmenu' disabled.",
583                                MAX_EXT_CMD);
584 #endif /* NH_DEVEL_STATUS != NH_STATUS_RELEASED */
585                     iflags.extmenu = 0;
586                     return -1;
587                 }
588             }
589         }
590         choices[i] = (struct ext_func_tab *) 0;
591         nchoices = i;
592         /* if we're down to one, we have our selection so get out of here */
593         if (nchoices  <= 1) {
594             ret = (nchoices == 1) ? (int) (choices[0] - extcmdlist) : -1;
595             break;
596         }
597 
598         /* otherwise... */
599         win = create_nhwindow(NHW_MENU);
600         start_menu(win, MENU_BEHAVE_STANDARD);
601         Sprintf(fmtstr, "%%-%ds", biggest + 15);
602         prompt[0] = '\0';
603         wastoolong = FALSE; /* True => had to wrap due to line width
604                              * ('w' in wizard mode) */
605         /* -3: two line menu header, 1 line menu footer (for prompt) */
606         one_per_line = (nchoices < ROWNO - 3);
607         accelerator = prevaccelerator = 0;
608         acount = 0;
609         for (i = 0; choices[i]; ++i) {
610             accelerator = choices[i]->ef_txt[matchlevel];
611             if (accelerator != prevaccelerator || one_per_line)
612                 wastoolong = FALSE;
613             if (accelerator != prevaccelerator || one_per_line
614                 || (acount >= 2
615                     /* +4: + sizeof " or " - sizeof "" */
616                     && (strlen(prompt) + 4 + strlen(choices[i]->ef_txt)
617                         /* -6: enough room for 1 space left margin
618                          *   + "%c - " menu selector + 1 space right margin */
619                         >= min(sizeof prompt, COLNO - 6)))) {
620                 if (acount) {
621                     /* flush extended cmds for that letter already in buf */
622                     Sprintf(buf, fmtstr, prompt);
623                     any.a_char = prevaccelerator;
624                     add_menu(win, &nul_glyphinfo, &any, any.a_char,
625                              0, ATR_NONE, buf, MENU_ITEMFLAGS_NONE);
626                     acount = 0;
627                     if (!(accelerator != prevaccelerator || one_per_line))
628                         wastoolong = TRUE;
629                 }
630             }
631             prevaccelerator = accelerator;
632             if (!acount || one_per_line) {
633                 Sprintf(prompt, "%s%s [%s]", wastoolong ? "or " : "",
634                         choices[i]->ef_txt, choices[i]->ef_desc);
635             } else if (acount == 1) {
636                 Sprintf(prompt, "%s%s or %s", wastoolong ? "or " : "",
637                         choices[i - 1]->ef_txt, choices[i]->ef_txt);
638             } else {
639                 Strcat(prompt, " or ");
640                 Strcat(prompt, choices[i]->ef_txt);
641             }
642             ++acount;
643         }
644         if (acount) {
645             /* flush buf */
646             Sprintf(buf, fmtstr, prompt);
647             any.a_char = prevaccelerator;
648             add_menu(win, &nul_glyphinfo, &any, any.a_char, 0,
649                      ATR_NONE, buf, MENU_ITEMFLAGS_NONE);
650         }
651         Snprintf(prompt, sizeof(prompt), "Extended Command: %s", cbuf);
652         end_menu(win, prompt);
653         n = select_menu(win, PICK_ONE, &pick_list);
654         destroy_nhwindow(win);
655         if (n == 1) {
656             if (matchlevel > (QBUFSZ - 2)) {
657                 free((genericptr_t) pick_list);
658 #if (NH_DEVEL_STATUS != NH_STATUS_RELEASED)
659                 impossible("Too many chars (%d) entered in extcmd_via_menu()",
660                            matchlevel);
661 #endif
662                 ret = -1;
663             } else {
664                 cbuf[matchlevel++] = pick_list[0].item.a_char;
665                 cbuf[matchlevel] = '\0';
666                 free((genericptr_t) pick_list);
667             }
668         } else {
669             if (matchlevel) {
670                 ret = 0;
671                 matchlevel = 0;
672             } else
673                 ret = -1;
674         }
675     }
676     return ret;
677 }
678 
679 RESTORE_WARNING_FORMAT_NONLITERAL
680 
681 #endif /* TTY_GRAPHICS */
682 
683 /* #monster command - use special monster ability while polymorphed */
684 int
domonability(void)685 domonability(void)
686 {
687     if (can_breathe(g.youmonst.data))
688         return dobreathe();
689     else if (attacktype(g.youmonst.data, AT_SPIT))
690         return dospit();
691     else if (g.youmonst.data->mlet == S_NYMPH)
692         return doremove();
693     else if (attacktype(g.youmonst.data, AT_GAZE))
694         return dogaze();
695     else if (is_were(g.youmonst.data))
696         return dosummon();
697     else if (webmaker(g.youmonst.data))
698         return dospinweb();
699     else if (is_hider(g.youmonst.data))
700         return dohide();
701     else if (is_mind_flayer(g.youmonst.data))
702         return domindblast();
703     else if (u.umonnum == PM_GREMLIN) {
704         if (IS_FOUNTAIN(levl[u.ux][u.uy].typ)) {
705             if (split_mon(&g.youmonst, (struct monst *) 0))
706                 dryup(u.ux, u.uy, TRUE);
707         } else
708             There("is no fountain here.");
709     } else if (is_unicorn(g.youmonst.data)) {
710         use_unicorn_horn((struct obj **) 0, FALSE);
711         return 1;
712     } else if (g.youmonst.data->msound == MS_SHRIEK) {
713         You("shriek.");
714         if (u.uburied)
715             pline("Unfortunately sound does not carry well through rock.");
716         else
717             aggravate();
718     } else if (is_vampire(g.youmonst.data) || is_vampshifter(&g.youmonst)) {
719         return dopoly();
720     } else if (Upolyd) {
721         pline("Any special ability you may have is purely reflexive.");
722     } else {
723         You("don't have a special ability in your normal form!");
724     }
725     return 0;
726 }
727 
728 int
enter_explore_mode(void)729 enter_explore_mode(void)
730 {
731     if (discover) {
732         You("are already in explore mode.");
733     } else if (iflags.debug_fuzzer) {
734         ; /* do nothing; explore mode significantly limits the fuzzer */
735     } else {
736         const char *oldmode = !wizard ? "normal game" : "debug mode";
737 
738 #ifdef SYSCF
739 #if defined(UNIX)
740         if (!sysopt.explorers || !sysopt.explorers[0]
741             || !check_user_string(sysopt.explorers)) {
742             if (!wizard) {
743                 You("cannot access explore mode.");
744                 return 0;
745             } else {
746                 pline(
747                  "Note: normally you wouldn't be allowed into explore mode.");
748                 /* keep going */
749             }
750         }
751 #endif
752 #endif
753         pline("Beware!  From explore mode there will be no return to %s,",
754               oldmode);
755         if (paranoid_query(ParanoidQuit,
756                            "Do you want to enter explore mode?")) {
757             discover = TRUE;
758             wizard = FALSE;
759             clear_nhwindow(WIN_MESSAGE);
760             You("are now in non-scoring explore mode.");
761         } else {
762             clear_nhwindow(WIN_MESSAGE);
763             pline("Continuing with %s.", oldmode);
764         }
765     }
766     return 0;
767 }
768 
769 /* ^W command - wish for something */
770 static int
wiz_wish(void)771 wiz_wish(void) /* Unlimited wishes for debug mode by Paul Polderman */
772 {
773     if (wizard) {
774         boolean save_verbose = flags.verbose;
775 
776         flags.verbose = FALSE;
777         makewish();
778         flags.verbose = save_verbose;
779         (void) encumber_msg();
780     } else
781         pline(unavailcmd, visctrl((int) cmd_from_func(wiz_wish)));
782     return 0;
783 }
784 
785 /* ^I command - reveal and optionally identify hero's inventory */
786 static int
wiz_identify(void)787 wiz_identify(void)
788 {
789     if (wizard) {
790         iflags.override_ID = (int) cmd_from_func(wiz_identify);
791         /* command remapping might leave #wizidentify as the only way
792            to invoke us, in which case cmd_from_func() will yield NUL;
793            it won't matter to display_inventory()/display_pickinv()
794            if ^I invokes some other command--what matters is that
795            display_pickinv() and xname() see override_ID as nonzero */
796         if (!iflags.override_ID)
797             iflags.override_ID = C('I');
798         (void) display_inventory((char *) 0, FALSE);
799         iflags.override_ID = 0;
800     } else
801         pline(unavailcmd, visctrl((int) cmd_from_func(wiz_identify)));
802     return 0;
803 }
804 
805 void
makemap_prepost(boolean pre,boolean wiztower)806 makemap_prepost(boolean pre, boolean wiztower)
807 {
808     NHFILE tmpnhfp;
809     struct monst *mtmp;
810 
811     if (pre) {
812         /* keep steed and other adjacent pets after releasing them
813            from traps, stopping eating, &c as if hero were ascending */
814         /* (pets-only; normally we'd be using 'FALSE' here) */
815         keepdogs(TRUE, FALSE);
816 
817         rm_mapseen(ledger_no(&u.uz));
818         for (mtmp = fmon; mtmp; mtmp = mtmp->nmon) {
819             int ndx = monsndx(mtmp->data);
820             if (mtmp->isgd) { /* vault is going away; get rid of guard */
821                 mtmp->isgd = 0;
822                 mongone(mtmp);
823             }
824             if (mtmp->data->geno & G_UNIQ)
825                 g.mvitals[ndx].mvflags &= ~(G_EXTINCT);
826             if (g.mvitals[ndx].born)
827                 g.mvitals[ndx].born--;
828             if (DEADMONSTER(mtmp))
829                 continue;
830             if (mtmp->isshk)
831                 setpaid(mtmp);
832         }
833         {
834             static const char Unachieve[] = "%s achievement revoked.";
835 
836             /* achievement tracking; if replacing a level that has a
837                special prize, lose credit for previously finding it and
838                reset for the new instance of that prize */
839             if (Is_mineend_level(&u.uz)) {
840                 if (remove_achievement(ACH_MINE_PRIZE))
841                     pline(Unachieve, "Mine's-end");
842                 g.context.achieveo.mines_prize_oid = 0;
843             } else if (Is_sokoend_level(&u.uz)) {
844                 if (remove_achievement(ACH_SOKO_PRIZE))
845                     pline(Unachieve, "Soko-prize");
846                 g.context.achieveo.soko_prize_oid = 0;
847             }
848         }
849         if (Punished) {
850             ballrelease(FALSE);
851             unplacebc();
852         }
853         /* reset lock picking unless it's for a carried container */
854         maybe_reset_pick((struct obj *) 0);
855         /* reset interrupted digging if it was taking place on this level */
856         if (on_level(&g.context.digging.level, &u.uz))
857             (void) memset((genericptr_t) &g.context.digging, 0,
858                           sizeof (struct dig_info));
859         /* reset cached targets */
860         iflags.travelcc.x = iflags.travelcc.y = 0; /* travel destination */
861         g.context.polearm.hitmon = (struct monst *) 0; /* polearm target */
862         /* escape from trap */
863         reset_utrap(FALSE);
864         check_special_room(TRUE); /* room exit */
865         (void) memset((genericptr_t)&g.dndest, 0, sizeof (dest_area));
866         (void) memset((genericptr_t)&g.updest, 0, sizeof (dest_area));
867         u.ustuck = (struct monst *) 0;
868         u.uswallow = u.uswldtim = 0;
869         set_uinwater(0); /* u.uinwater = 0 */
870         u.uundetected = 0; /* not hidden, even if means are available */
871         dmonsfree(); /* purge dead monsters from 'fmon' */
872 
873         /* discard current level; "saving" is used to release dynamic data */
874         zero_nhfile(&tmpnhfp);  /* also sets fd to -1 as desired */
875         tmpnhfp.mode = FREEING;
876         savelev(&tmpnhfp, ledger_no(&u.uz));
877     } else {
878         vision_reset();
879         g.vision_full_recalc = 1;
880         cls();
881         /* was using safe_teleds() but that doesn't honor arrival region
882            on levels which have such; we don't force stairs, just area */
883         u_on_rndspot((u.uhave.amulet ? 1 : 0) /* 'going up' flag */
884                      | (wiztower ? 2 : 0));
885         losedogs();
886         kill_genocided_monsters();
887         /* u_on_rndspot() might pick a spot that has a monster, or losedogs()
888            might pick the hero's spot (only if there isn't already a monster
889            there), so we might have to move hero or the co-located monster */
890         if ((mtmp = m_at(u.ux, u.uy)) != 0)
891             u_collide_m(mtmp);
892         initrack();
893         if (Punished) {
894             unplacebc();
895             placebc();
896         }
897         docrt();
898         flush_screen(1);
899         deliver_splev_message(); /* level entry */
900         check_special_room(FALSE); /* room entry */
901 #ifdef INSURANCE
902         save_currentstate();
903 #endif
904     }
905 }
906 
907 /* #wizmakemap - discard current dungeon level and replace with a new one */
908 static int
wiz_makemap(void)909 wiz_makemap(void)
910 {
911     if (wizard) {
912         boolean was_in_W_tower = In_W_tower(u.ux, u.uy, &u.uz);
913 
914         makemap_prepost(TRUE, was_in_W_tower);
915         /* create a new level; various things like bestowing a guardian
916            angel on Astral or setting off alarm on Ft.Ludios are handled
917            by goto_level(do.c) so won't occur for replacement levels */
918         mklev();
919         makemap_prepost(FALSE, was_in_W_tower);
920     } else {
921         pline(unavailcmd, "#wizmakemap");
922     }
923     return 0;
924 }
925 
926 /* ^F command - reveal the level map and any traps on it */
927 static int
wiz_map(void)928 wiz_map(void)
929 {
930     if (wizard) {
931         struct trap *t;
932         long save_Hconf = HConfusion, save_Hhallu = HHallucination;
933 
934         HConfusion = HHallucination = 0L;
935         for (t = g.ftrap; t != 0; t = t->ntrap) {
936             t->tseen = 1;
937             map_trap(t, TRUE);
938         }
939         do_mapping();
940         HConfusion = save_Hconf;
941         HHallucination = save_Hhallu;
942     } else
943         pline(unavailcmd, visctrl((int) cmd_from_func(wiz_map)));
944     return 0;
945 }
946 
947 /* ^G command - generate monster(s); a count prefix will be honored */
948 static int
wiz_genesis(void)949 wiz_genesis(void)
950 {
951     if (wizard)
952         (void) create_particular();
953     else
954         pline(unavailcmd, visctrl((int) cmd_from_func(wiz_genesis)));
955     return 0;
956 }
957 
958 /* ^O command - display dungeon layout */
959 static int
wiz_where(void)960 wiz_where(void)
961 {
962     if (wizard)
963         (void) print_dungeon(FALSE, (schar *) 0, (xchar *) 0);
964     else
965         pline(unavailcmd, visctrl((int) cmd_from_func(wiz_where)));
966     return 0;
967 }
968 
969 /* ^E command - detect unseen (secret doors, traps, hidden monsters) */
970 static int
wiz_detect(void)971 wiz_detect(void)
972 {
973     if (wizard)
974         (void) findit();
975     else
976         pline(unavailcmd, visctrl((int) cmd_from_func(wiz_detect)));
977     return 0;
978 }
979 
980 static int
wiz_load_lua(void)981 wiz_load_lua(void)
982 {
983     if (wizard && !iflags.debug_fuzzer) {
984         char buf[BUFSZ];
985 
986         buf[0] = '\0';
987         getlin("Load which lua file?", buf);
988         if (buf[0] == '\033' || buf[0] == '\0')
989             return 0;
990         if (!strchr(buf, '.'))
991             strcat(buf, ".lua");
992         (void) load_lua(buf);
993     } else
994         pline("Unavailable command 'wiz_load_lua'.");
995     return 0;
996 }
997 
998 static int
wiz_load_splua(void)999 wiz_load_splua(void)
1000 {
1001     if (wizard && !iflags.debug_fuzzer) {
1002         boolean was_in_W_tower = In_W_tower(u.ux, u.uy, &u.uz);
1003         char buf[BUFSZ];
1004         int ridx;
1005 
1006         buf[0] = '\0';
1007         getlin("Load which des lua file?", buf);
1008         if (buf[0] == '\033' || buf[0] == '\0')
1009             return 0;
1010         if (!strchr(buf, '.'))
1011             strcat(buf, ".lua");
1012         makemap_prepost(TRUE, was_in_W_tower);
1013 
1014         /* TODO: need to split some of this out of mklev(), makelevel(),
1015            makemaz() */
1016         g.in_mklev = TRUE;
1017         oinit(); /* assign level dependent obj probabilities */
1018         clear_level_structures();
1019 
1020         (void) load_special(buf);
1021 
1022         bound_digging();
1023         mineralize(-1, -1, -1, -1, FALSE);
1024         g.in_mklev = FALSE;
1025         if (g.level.flags.has_morgue)
1026             g.level.flags.graveyard = 1;
1027         if (!g.level.flags.is_maze_lev) {
1028             struct mkroom *croom;
1029             for (croom = &g.rooms[0]; croom != &g.rooms[g.nroom]; croom++)
1030 #ifdef SPECIALIZATION
1031                 topologize(croom, FALSE);
1032 #else
1033             topologize(croom);
1034 #endif
1035         }
1036         set_wall_state();
1037         /* for many room types, rooms[].rtype is zeroed once the room has been
1038            entered; rooms[].orig_rtype always retains original rtype value */
1039         for (ridx = 0; ridx < SIZE(g.rooms); ridx++)
1040             g.rooms[ridx].orig_rtype = g.rooms[ridx].rtype;
1041 
1042         makemap_prepost(FALSE, was_in_W_tower);
1043     } else
1044         pline("Unavailable command 'wiz_load_splua'.");
1045     return 0;
1046 }
1047 
1048 /* ^V command - level teleport */
1049 static int
wiz_level_tele(void)1050 wiz_level_tele(void)
1051 {
1052     if (wizard)
1053         level_tele();
1054     else
1055         pline(unavailcmd, visctrl((int) cmd_from_func(wiz_level_tele)));
1056     return 0;
1057 }
1058 
1059 /* #wizfliplevel - transpose the current level */
1060 static int
wiz_flip_level(void)1061 wiz_flip_level(void)
1062 {
1063     static const char choices[] = "0123",
1064         prmpt[] = "Flip 0=randomly, 1=vertically, 2=horizonally, 3=both:";
1065 
1066     /*
1067      * Does not handle
1068      *   levregions,
1069      *   monster mtrack,
1070      *   migrating monsters aimed at returning to specific coordinates
1071      *     on this level
1072      * as flipping is normally done only during level creation.
1073      */
1074     if (wizard) {
1075         char c = yn_function(prmpt, choices, '\0');
1076 
1077         if (c && index(choices, c)) {
1078             c -= '0';
1079 
1080             if (!c)
1081                 flip_level_rnd(3, TRUE);
1082             else
1083                 flip_level((int) c, TRUE);
1084 
1085             docrt();
1086         } else {
1087             pline("%s", Never_mind);
1088         }
1089     }
1090     return 0;
1091 }
1092 
1093 /* #levelchange command - adjust hero's experience level */
1094 static int
wiz_level_change(void)1095 wiz_level_change(void)
1096 {
1097     char buf[BUFSZ] = DUMMY;
1098     int newlevel = 0;
1099     int ret;
1100 
1101     getlin("To what experience level do you want to be set?", buf);
1102     (void) mungspaces(buf);
1103     if (buf[0] == '\033' || buf[0] == '\0')
1104         ret = 0;
1105     else
1106         ret = sscanf(buf, "%d", &newlevel);
1107 
1108     if (ret != 1) {
1109         pline1(Never_mind);
1110         return 0;
1111     }
1112     if (newlevel == u.ulevel) {
1113         You("are already that experienced.");
1114     } else if (newlevel < u.ulevel) {
1115         if (u.ulevel == 1) {
1116             You("are already as inexperienced as you can get.");
1117             return 0;
1118         }
1119         if (newlevel < 1)
1120             newlevel = 1;
1121         while (u.ulevel > newlevel)
1122             losexp("#levelchange");
1123     } else {
1124         if (u.ulevel >= MAXULEV) {
1125             You("are already as experienced as you can get.");
1126             return 0;
1127         }
1128         if (newlevel > MAXULEV)
1129             newlevel = MAXULEV;
1130         while (u.ulevel < newlevel)
1131             pluslvl(FALSE);
1132     }
1133     u.ulevelmax = u.ulevel;
1134     return 0;
1135 }
1136 
1137 /* #panic command - test program's panic handling */
1138 static int
wiz_panic(void)1139 wiz_panic(void)
1140 {
1141     if (iflags.debug_fuzzer) {
1142         u.uhp = u.uhpmax = 1000;
1143         u.uen = u.uenmax = 1000;
1144         return 0;
1145     }
1146     if (paranoid_query(TRUE,
1147                        "Do you want to call panic() and end your game?"))
1148         panic("Crash test.");
1149     return 0;
1150 }
1151 
1152 /* #polyself command - change hero's form */
1153 static int
wiz_polyself(void)1154 wiz_polyself(void)
1155 {
1156     polyself(1);
1157     return 0;
1158 }
1159 
1160 /* #seenv command */
1161 static int
wiz_show_seenv(void)1162 wiz_show_seenv(void)
1163 {
1164     winid win;
1165     int x, y, v, startx, stopx, curx;
1166     char row[COLNO + 1];
1167 
1168     win = create_nhwindow(NHW_TEXT);
1169     /*
1170      * Each seenv description takes up 2 characters, so center
1171      * the seenv display around the hero.
1172      */
1173     startx = max(1, u.ux - (COLNO / 4));
1174     stopx = min(startx + (COLNO / 2), COLNO);
1175     /* can't have a line exactly 80 chars long */
1176     if (stopx - startx == COLNO / 2)
1177         startx++;
1178 
1179     for (y = 0; y < ROWNO; y++) {
1180         for (x = startx, curx = 0; x < stopx; x++, curx += 2) {
1181             if (x == u.ux && y == u.uy) {
1182                 row[curx] = row[curx + 1] = '@';
1183             } else {
1184                 v = levl[x][y].seenv & 0xff;
1185                 if (v == 0)
1186                     row[curx] = row[curx + 1] = ' ';
1187                 else
1188                     Sprintf(&row[curx], "%02x", v);
1189             }
1190         }
1191         /* remove trailing spaces */
1192         for (x = curx - 1; x >= 0; x--)
1193             if (row[x] != ' ')
1194                 break;
1195         row[x + 1] = '\0';
1196 
1197         putstr(win, 0, row);
1198     }
1199     display_nhwindow(win, TRUE);
1200     destroy_nhwindow(win);
1201     return 0;
1202 }
1203 
1204 /* #vision command */
1205 static int
wiz_show_vision(void)1206 wiz_show_vision(void)
1207 {
1208     winid win;
1209     int x, y, v;
1210     char row[COLNO + 1];
1211 
1212     win = create_nhwindow(NHW_TEXT);
1213     Sprintf(row, "Flags: 0x%x could see, 0x%x in sight, 0x%x temp lit",
1214             COULD_SEE, IN_SIGHT, TEMP_LIT);
1215     putstr(win, 0, row);
1216     putstr(win, 0, "");
1217     for (y = 0; y < ROWNO; y++) {
1218         for (x = 1; x < COLNO; x++) {
1219             if (x == u.ux && y == u.uy) {
1220                 row[x] = '@';
1221             } else {
1222                 v = g.viz_array[y][x]; /* data access should be hidden */
1223                 row[x] = (v == 0) ? ' ' : ('0' + v);
1224             }
1225         }
1226         /* remove trailing spaces */
1227         for (x = COLNO - 1; x >= 1; x--)
1228             if (row[x] != ' ')
1229                 break;
1230         row[x + 1] = '\0';
1231 
1232         putstr(win, 0, &row[1]);
1233     }
1234     display_nhwindow(win, TRUE);
1235     destroy_nhwindow(win);
1236     return 0;
1237 }
1238 
1239 /* #wmode command */
1240 static int
wiz_show_wmodes(void)1241 wiz_show_wmodes(void)
1242 {
1243     winid win;
1244     int x, y;
1245     char row[COLNO + 1];
1246     struct rm *lev;
1247     boolean istty = WINDOWPORT("tty");
1248 
1249     win = create_nhwindow(NHW_TEXT);
1250     if (istty)
1251         putstr(win, 0, ""); /* tty only: blank top line */
1252     for (y = 0; y < ROWNO; y++) {
1253         for (x = 0; x < COLNO; x++) {
1254             lev = &levl[x][y];
1255             if (x == u.ux && y == u.uy)
1256                 row[x] = '@';
1257             else if (IS_WALL(lev->typ) || lev->typ == SDOOR)
1258                 row[x] = '0' + (lev->wall_info & WM_MASK);
1259             else if (lev->typ == CORR)
1260                 row[x] = '#';
1261             else if (IS_ROOM(lev->typ) || IS_DOOR(lev->typ))
1262                 row[x] = '.';
1263             else
1264                 row[x] = 'x';
1265         }
1266         row[COLNO] = '\0';
1267         /* map column 0, levl[0][], is off the left edge of the screen */
1268         putstr(win, 0, &row[1]);
1269     }
1270     display_nhwindow(win, TRUE);
1271     destroy_nhwindow(win);
1272     return 0;
1273 }
1274 
1275 /* wizard mode variant of #terrain; internal levl[][].typ values in base-36 */
1276 static void
wiz_map_levltyp(void)1277 wiz_map_levltyp(void)
1278 {
1279     winid win;
1280     int x, y, terrain;
1281     char row[COLNO + 1];
1282     boolean istty = !strcmp(windowprocs.name, "tty");
1283 
1284     win = create_nhwindow(NHW_TEXT);
1285     /* map row 0, levl[][0], is drawn on the second line of tty screen */
1286     if (istty)
1287         putstr(win, 0, ""); /* tty only: blank top line */
1288     for (y = 0; y < ROWNO; y++) {
1289         /* map column 0, levl[0][], is off the left edge of the screen;
1290            it should always have terrain type "undiggable stone" */
1291         for (x = 1; x < COLNO; x++) {
1292             terrain = levl[x][y].typ;
1293             /* assumes there aren't more than 10+26+26 terrain types */
1294             row[x - 1] = (char) ((terrain == STONE && !may_dig(x, y))
1295                                     ? '*'
1296                                     : (terrain < 10)
1297                                        ? '0' + terrain
1298                                        : (terrain < 36)
1299                                           ? 'a' + terrain - 10
1300                                           : 'A' + terrain - 36);
1301         }
1302         x--;
1303         if (levl[0][y].typ != STONE || may_dig(0, y))
1304             row[x++] = '!';
1305         row[x] = '\0';
1306         putstr(win, 0, row);
1307     }
1308 
1309     {
1310         char dsc[COLBUFSZ];
1311         s_level *slev = Is_special(&u.uz);
1312 
1313         Sprintf(dsc, "D:%d,L:%d", u.uz.dnum, u.uz.dlevel);
1314         /* [dungeon branch features currently omitted] */
1315         /* special level features */
1316         if (slev) {
1317             Sprintf(eos(dsc), " \"%s\"", slev->proto);
1318             /* special level flags (note: dungeon.def doesn't set `maze'
1319                or `hell' for any specific levels so those never show up) */
1320             if (slev->flags.maze_like)
1321                 Strcat(dsc, " mazelike");
1322             if (slev->flags.hellish)
1323                 Strcat(dsc, " hellish");
1324             if (slev->flags.town)
1325                 Strcat(dsc, " town");
1326             /* alignment currently omitted to save space */
1327         }
1328         /* level features */
1329         if (g.level.flags.nfountains)
1330             Sprintf(eos(dsc), " %c:%d", defsyms[S_fountain].sym,
1331                     (int) g.level.flags.nfountains);
1332         if (g.level.flags.nsinks)
1333             Sprintf(eos(dsc), " %c:%d", defsyms[S_sink].sym,
1334                     (int) g.level.flags.nsinks);
1335         if (g.level.flags.has_vault)
1336             Strcat(dsc, " vault");
1337         if (g.level.flags.has_shop)
1338             Strcat(dsc, " shop");
1339         if (g.level.flags.has_temple)
1340             Strcat(dsc, " temple");
1341         if (g.level.flags.has_court)
1342             Strcat(dsc, " throne");
1343         if (g.level.flags.has_zoo)
1344             Strcat(dsc, " zoo");
1345         if (g.level.flags.has_morgue)
1346             Strcat(dsc, " morgue");
1347         if (g.level.flags.has_barracks)
1348             Strcat(dsc, " barracks");
1349         if (g.level.flags.has_beehive)
1350             Strcat(dsc, " hive");
1351         if (g.level.flags.has_swamp)
1352             Strcat(dsc, " swamp");
1353         /* level flags */
1354         if (g.level.flags.noteleport)
1355             Strcat(dsc, " noTport");
1356         if (g.level.flags.hardfloor)
1357             Strcat(dsc, " noDig");
1358         if (g.level.flags.nommap)
1359             Strcat(dsc, " noMMap");
1360         if (!g.level.flags.hero_memory)
1361             Strcat(dsc, " noMem");
1362         if (g.level.flags.shortsighted)
1363             Strcat(dsc, " shortsight");
1364         if (g.level.flags.graveyard)
1365             Strcat(dsc, " graveyard");
1366         if (g.level.flags.is_maze_lev)
1367             Strcat(dsc, " maze");
1368         if (g.level.flags.is_cavernous_lev)
1369             Strcat(dsc, " cave");
1370         if (g.level.flags.arboreal)
1371             Strcat(dsc, " tree");
1372         if (Sokoban)
1373             Strcat(dsc, " sokoban-rules");
1374         /* non-flag info; probably should include dungeon branching
1375            checks (extra stairs and magic portals) here */
1376         if (Invocation_lev(&u.uz))
1377             Strcat(dsc, " invoke");
1378         if (On_W_tower_level(&u.uz))
1379             Strcat(dsc, " tower");
1380         /* append a branch identifier for completeness' sake */
1381         if (u.uz.dnum == 0)
1382             Strcat(dsc, " dungeon");
1383         else if (u.uz.dnum == mines_dnum)
1384             Strcat(dsc, " mines");
1385         else if (In_sokoban(&u.uz))
1386             Strcat(dsc, " sokoban");
1387         else if (u.uz.dnum == quest_dnum)
1388             Strcat(dsc, " quest");
1389         else if (Is_knox(&u.uz))
1390             Strcat(dsc, " ludios");
1391         else if (u.uz.dnum == 1)
1392             Strcat(dsc, " gehennom");
1393         else if (u.uz.dnum == tower_dnum)
1394             Strcat(dsc, " vlad");
1395         else if (In_endgame(&u.uz))
1396             Strcat(dsc, " endgame");
1397         else {
1398             /* somebody's added a dungeon branch we're not expecting */
1399             const char *brname = g.dungeons[u.uz.dnum].dname;
1400 
1401             if (!brname || !*brname)
1402                 brname = "unknown";
1403             if (!strncmpi(brname, "the ", 4))
1404                 brname += 4;
1405             Sprintf(eos(dsc), " %s", brname);
1406         }
1407         /* limit the line length to map width */
1408         if (strlen(dsc) >= COLNO)
1409             dsc[COLNO - 1] = '\0'; /* truncate */
1410         putstr(win, 0, dsc);
1411     }
1412 
1413     display_nhwindow(win, TRUE);
1414     destroy_nhwindow(win);
1415     return;
1416 }
1417 
1418 /* temporary? hack, since level type codes aren't the same as screen
1419    symbols and only the latter have easily accessible descriptions */
1420 const char *levltyp[] = {
1421     "stone", "vertical wall", "horizontal wall", "top-left corner wall",
1422     "top-right corner wall", "bottom-left corner wall",
1423     "bottom-right corner wall", "cross wall", "tee-up wall", "tee-down wall",
1424     "tee-left wall", "tee-right wall", "drawbridge wall", "tree",
1425     "secret door", "secret corridor", "pool", "moat", "water",
1426     "drawbridge up", "lava pool", "iron bars", "door", "corridor", "room",
1427     "stairs", "ladder", "fountain", "throne", "sink", "grave", "altar", "ice",
1428     "drawbridge down", "air", "cloud",
1429     /* not a real terrain type, but used for undiggable stone
1430        by wiz_map_levltyp() */
1431     "unreachable/undiggable",
1432     /* padding in case the number of entries above is odd */
1433     ""
1434 };
1435 
1436 const char *
levltyp_to_name(int typ)1437 levltyp_to_name(int typ)
1438 {
1439     if (typ >= 0 && typ < MAX_TYPE)
1440         return levltyp[typ];
1441     return NULL;
1442 }
1443 
1444 DISABLE_WARNING_FORMAT_NONLITERAL
1445 
1446 /* explanation of base-36 output from wiz_map_levltyp() */
1447 static void
wiz_levltyp_legend(void)1448 wiz_levltyp_legend(void)
1449 {
1450     winid win;
1451     int i, j, last, c;
1452     const char *dsc, *fmt;
1453     char buf[BUFSZ];
1454 
1455     win = create_nhwindow(NHW_TEXT);
1456     putstr(win, 0, "#terrain encodings:");
1457     putstr(win, 0, "");
1458     fmt = " %c - %-28s"; /* TODO: include tab-separated variant for win32 */
1459     *buf = '\0';
1460     /* output in pairs, left hand column holds [0],[1],...,[N/2-1]
1461        and right hand column holds [N/2],[N/2+1],...,[N-1];
1462        N ('last') will always be even, and may or may not include
1463        the empty string entry to pad out the final pair, depending
1464        upon how many other entries are present in levltyp[] */
1465     last = SIZE(levltyp) & ~1;
1466     for (i = 0; i < last / 2; ++i)
1467         for (j = i; j < last; j += last / 2) {
1468             dsc = levltyp[j];
1469             c = !*dsc ? ' '
1470                    : !strncmp(dsc, "unreachable", 11) ? '*'
1471                       /* same int-to-char conversion as wiz_map_levltyp() */
1472                       : (j < 10) ? '0' + j
1473                          : (j < 36) ? 'a' + j - 10
1474                             : 'A' + j - 36;
1475             Sprintf(eos(buf), fmt, c, dsc);
1476             if (j > i) {
1477                 putstr(win, 0, buf);
1478                 *buf = '\0';
1479             }
1480         }
1481     display_nhwindow(win, TRUE);
1482     destroy_nhwindow(win);
1483     return;
1484 }
1485 
1486 RESTORE_WARNING_FORMAT_NONLITERAL
1487 
1488 DISABLE_WARNING_CONDEXPR_IS_CONSTANT
1489 
1490 /* #wizsmell command - test usmellmon(). */
1491 static int
wiz_smell(void)1492 wiz_smell(void)
1493 {
1494     int ans = 0;
1495     int mndx;  /* monster index */
1496     coord cc;  /* screen pos of unknown glyph */
1497     int glyph; /* glyph at selected position */
1498 
1499     cc.x = u.ux;
1500     cc.y = u.uy;
1501     mndx = 0; /* gcc -Wall lint */
1502     if (!olfaction(g.youmonst.data)) {
1503         You("are incapable of detecting odors in your present form.");
1504         return 0;
1505     }
1506 
1507     pline("You can move the cursor to a monster that you want to smell.");
1508     do {
1509         pline("Pick a monster to smell.");
1510         ans = getpos(&cc, TRUE, "a monster");
1511         if (ans < 0 || cc.x < 0) {
1512             return 0; /* done */
1513         }
1514         /* Convert the glyph at the selected position to a mndxbol. */
1515         glyph = glyph_at(cc.x, cc.y);
1516         if (glyph_is_monster(glyph))
1517             mndx = glyph_to_mon(glyph);
1518         else
1519             mndx = 0;
1520         /* Is it a monster? */
1521         if (mndx) {
1522             if (!usmellmon(&mons[mndx]))
1523                 pline("That monster seems to give off no smell.");
1524         } else
1525             pline("That is not a monster.");
1526     } while (TRUE);
1527     return 0;
1528 }
1529 
1530 RESTORE_WARNING_CONDEXPR_IS_CONSTANT
1531 
1532 DISABLE_WARNING_FORMAT_NONLITERAL
1533 
1534 #define DEFAULT_TIMEOUT_INCR 30
1535 
1536 /* #wizinstrinsic command to set some intrinsics for testing */
1537 static int
wiz_intrinsic(void)1538 wiz_intrinsic(void)
1539 {
1540     if (wizard) {
1541         extern const struct propname {
1542             int prop_num;
1543             const char *prop_name;
1544         } propertynames[]; /* timeout.c */
1545         static const char wizintrinsic[] = "#wizintrinsic";
1546         static const char fmt[] = "You are%s %s.";
1547         winid win;
1548         anything any;
1549         char buf[BUFSZ];
1550         int i, j, n, p, amt, typ;
1551         long oldtimeout, newtimeout;
1552         const char *propname;
1553         menu_item *pick_list = (menu_item *) 0;
1554 
1555         any = cg.zeroany;
1556         win = create_nhwindow(NHW_MENU);
1557         start_menu(win, MENU_BEHAVE_STANDARD);
1558         if (iflags.cmdassist) {
1559             /* start menu with a subtitle */
1560             Sprintf(buf,
1561         "[Precede any selection with a count to increment by other than %d.]",
1562                     DEFAULT_TIMEOUT_INCR);
1563             any.a_int = 0;
1564             add_menu(win, &nul_glyphinfo, &any, 0, 0, ATR_NONE, buf,
1565                      MENU_ITEMFLAGS_NONE);
1566         }
1567         for (i = 0; (propname = propertynames[i].prop_name) != 0; ++i) {
1568             p = propertynames[i].prop_num;
1569             if (p == HALLUC_RES) {
1570                 /* Grayswandir vs hallucination; ought to be redone to
1571                    use u.uprops[HALLUC].blocked instead of being treated
1572                    as a separate property; letting in be manually toggled
1573                    even only in wizard mode would be asking for trouble... */
1574                 continue;
1575             }
1576             if (p == FIRE_RES) {
1577                 /* FIRE_RES and properties beyond it (in the propertynames[]
1578                    ordering, not their numerical PROP values), can only be
1579                    set to timed values here so show a separator */
1580                 any.a_int = 0;
1581                 add_menu(win, &nul_glyphinfo, &any, 0, 0,
1582                          ATR_NONE, "--", MENU_ITEMFLAGS_NONE);
1583             }
1584             any.a_int = i + 1; /* +1: avoid 0 */
1585             oldtimeout = u.uprops[p].intrinsic & TIMEOUT;
1586             if (oldtimeout)
1587                 Sprintf(buf, "%-27s [%li]", propname, oldtimeout);
1588             else
1589                 Sprintf(buf, "%s", propname);
1590             add_menu(win, &nul_glyphinfo, &any, 0, 0, ATR_NONE, buf,
1591                      MENU_ITEMFLAGS_NONE);
1592         }
1593         end_menu(win, "Which intrinsics?");
1594         n = select_menu(win, PICK_ANY, &pick_list);
1595         destroy_nhwindow(win);
1596 
1597         for (j = 0; j < n; ++j) {
1598             i = pick_list[j].item.a_int - 1; /* -1: reverse +1 above */
1599             p = propertynames[i].prop_num;
1600             oldtimeout = u.uprops[p].intrinsic & TIMEOUT;
1601             amt = (pick_list[j].count == -1L) ? DEFAULT_TIMEOUT_INCR
1602                                               : (int) pick_list[j].count;
1603             if (amt <= 0) /* paranoia */
1604                 continue;
1605             newtimeout = oldtimeout + (long) amt;
1606 
1607             switch (p) {
1608             case SICK:
1609             case SLIMED:
1610             case STONED:
1611                 if (oldtimeout > 0L && newtimeout > oldtimeout)
1612                     newtimeout = oldtimeout;
1613                 break;
1614             }
1615 
1616             switch (p) {
1617             case BLINDED:
1618                 make_blinded(newtimeout, TRUE);
1619                 break;
1620 #if 0       /* make_confused() only gives feedback when confusion is
1621              * ending so use the 'default' case for it instead */
1622             case CONFUSION:
1623                 make_confused(newtimeout, TRUE);
1624                 break;
1625 #endif /*0*/
1626             case DEAF:
1627                 make_deaf(newtimeout, TRUE);
1628                 break;
1629             case HALLUC:
1630                 make_hallucinated(newtimeout, TRUE, 0L);
1631                 break;
1632             case SICK:
1633                 typ = !rn2(2) ? SICK_VOMITABLE : SICK_NONVOMITABLE;
1634                 make_sick(newtimeout, wizintrinsic, TRUE, typ);
1635                 break;
1636             case SLIMED:
1637                 Sprintf(buf, fmt,
1638                         !Slimed ? "" : " still", "turning into slime");
1639                 make_slimed(newtimeout, buf);
1640                 break;
1641             case STONED:
1642                 Sprintf(buf, fmt,
1643                         !Stoned ? "" : " still", "turning into stone");
1644                 make_stoned(newtimeout, buf, KILLED_BY, wizintrinsic);
1645                 break;
1646             case STUNNED:
1647                 make_stunned(newtimeout, TRUE);
1648                 break;
1649             case VOMITING:
1650                 Sprintf(buf, fmt, !Vomiting ? "" : " still", "vomiting");
1651                 make_vomiting(newtimeout, FALSE);
1652                 pline1(buf);
1653                 break;
1654             case WARN_OF_MON:
1655                 if (!Warn_of_mon) {
1656                     g.context.warntype.speciesidx = PM_GRID_BUG;
1657                     g.context.warntype.species
1658                                        = &mons[g.context.warntype.speciesidx];
1659                 }
1660                 goto def_feedback;
1661             case GLIB:
1662                 /* slippery fingers might need a persistent inventory update
1663                    so needs more than simple incr_itimeout() but we want
1664                    the pline() issued with that */
1665                 make_glib((int) newtimeout);
1666                 /*FALLTHRU*/
1667             default:
1668  def_feedback:
1669                 if (p != GLIB)
1670                     incr_itimeout(&u.uprops[p].intrinsic, amt);
1671                 g.context.botl = 1; /* have pline() do a status update */
1672                 pline("Timeout for %s %s %d.", propertynames[i].prop_name,
1673                       oldtimeout ? "increased by" : "set to", amt);
1674                 break;
1675             }
1676             /* this has to be after incr_timeout() */
1677             if (p == LEVITATION || p == FLYING)
1678                 float_vs_flight();
1679         }
1680         if (n >= 1)
1681             free((genericptr_t) pick_list);
1682         doredraw();
1683     } else
1684         pline(unavailcmd, visctrl((int) cmd_from_func(wiz_intrinsic)));
1685     return 0;
1686 }
1687 
1688 RESTORE_WARNING_FORMAT_NONLITERAL
1689 
1690 /* #wizrumorcheck command - verify each rumor access */
1691 static int
wiz_rumor_check(void)1692 wiz_rumor_check(void)
1693 {
1694     rumor_check();
1695     return 0;
1696 }
1697 
1698 /* #terrain command -- show known map, inspired by crawl's '|' command */
1699 static int
doterrain(void)1700 doterrain(void)
1701 {
1702     winid men;
1703     menu_item *sel;
1704     anything any;
1705     int n;
1706     int which;
1707 
1708     /*
1709      * normal play: choose between known map without mons, obj, and traps
1710      *  (to see underlying terrain only), or
1711      *  known map without mons and objs (to see traps under mons and objs), or
1712      *  known map without mons (to see objects under monsters);
1713      * explore mode: normal choices plus full map (w/o mons, objs, traps);
1714      * wizard mode: normal and explore choices plus
1715      *  a dump of the internal levl[][].typ codes w/ level flags, or
1716      *  a legend for the levl[][].typ codes dump
1717      */
1718     men = create_nhwindow(NHW_MENU);
1719     start_menu(men, MENU_BEHAVE_STANDARD);
1720     any = cg.zeroany;
1721     any.a_int = 1;
1722     add_menu(men, &nul_glyphinfo, &any, 0, 0, ATR_NONE,
1723              "known map without monsters, objects, and traps",
1724              MENU_ITEMFLAGS_SELECTED);
1725     any.a_int = 2;
1726     add_menu(men, &nul_glyphinfo, &any, 0, 0, ATR_NONE,
1727              "known map without monsters and objects",
1728              MENU_ITEMFLAGS_NONE);
1729     any.a_int = 3;
1730     add_menu(men, &nul_glyphinfo, &any, 0, 0, ATR_NONE,
1731              "known map without monsters",
1732              MENU_ITEMFLAGS_NONE);
1733     if (discover || wizard) {
1734         any.a_int = 4;
1735         add_menu(men, &nul_glyphinfo, &any, 0, 0, ATR_NONE,
1736                  "full map without monsters, objects, and traps",
1737                  MENU_ITEMFLAGS_NONE);
1738         if (wizard) {
1739             any.a_int = 5;
1740             add_menu(men, &nul_glyphinfo, &any, 0, 0, ATR_NONE,
1741                      "internal levl[][].typ codes in base-36",
1742                      MENU_ITEMFLAGS_NONE);
1743             any.a_int = 6;
1744             add_menu(men, &nul_glyphinfo, &any, 0, 0, ATR_NONE,
1745                      "legend of base-36 levl[][].typ codes",
1746                      MENU_ITEMFLAGS_NONE);
1747         }
1748     }
1749     end_menu(men, "View which?");
1750 
1751     n = select_menu(men, PICK_ONE, &sel);
1752     destroy_nhwindow(men);
1753     /*
1754      * n <  0: player used ESC to cancel;
1755      * n == 0: preselected entry was explicitly chosen and got toggled off;
1756      * n == 1: preselected entry was implicitly chosen via <space>|<enter>;
1757      * n == 2: another entry was explicitly chosen, so skip preselected one.
1758      */
1759     which = (n < 0) ? -1 : (n == 0) ? 1 : sel[0].item.a_int;
1760     if (n > 1 && which == 1)
1761         which = sel[1].item.a_int;
1762     if (n > 0)
1763         free((genericptr_t) sel);
1764 
1765     switch (which) {
1766     case 1: /* known map */
1767         reveal_terrain(0, TER_MAP);
1768         break;
1769     case 2: /* known map with known traps */
1770         reveal_terrain(0, TER_MAP | TER_TRP);
1771         break;
1772     case 3: /* known map with known traps and objects */
1773         reveal_terrain(0, TER_MAP | TER_TRP | TER_OBJ);
1774         break;
1775     case 4: /* full map */
1776         reveal_terrain(1, TER_MAP);
1777         break;
1778     case 5: /* map internals */
1779         wiz_map_levltyp();
1780         break;
1781     case 6: /* internal details */
1782         wiz_levltyp_legend();
1783         break;
1784     default:
1785         break;
1786     }
1787     return 0; /* no time elapses */
1788 }
1789 
1790 /* extcmdlist: full command list, ordered by command name;
1791    commands with no keystroke or with only a meta keystroke generally
1792    need to be flagged as autocomplete and ones with a regular keystroke
1793    or control keystroke generally should not be; there are a few exceptions
1794    such as ^O/#overview and C/N/#name */
1795 struct ext_func_tab extcmdlist[] = {
1796     { '#',    "#", "perform an extended command",
1797               doextcmd, IFBURIED | GENERALCMD, NULL },
1798     { M('?'), "?", "list all extended commands",
1799               doextlist, IFBURIED | AUTOCOMPLETE | GENERALCMD, NULL },
1800     { M('a'), "adjust", "adjust inventory letters",
1801               doorganize, IFBURIED | AUTOCOMPLETE, NULL },
1802     { M('A'), "annotate", "name current level",
1803               donamelevel, IFBURIED | AUTOCOMPLETE, NULL },
1804     { 'a',    "apply", "apply (use) a tool (pick-axe, key, lamp...)",
1805               doapply, 0, NULL },
1806     { C('x'), "attributes", "show your attributes",
1807               doattributes, IFBURIED, NULL },
1808     { '@',    "autopickup", "toggle the 'autopickup' option on/off",
1809               dotogglepickup, IFBURIED, NULL },
1810     { 'C',    "call", "name a monster, specific object, or type of object",
1811               docallcmd, IFBURIED, NULL },
1812     { 'Z',    "cast", "zap (cast) a spell",
1813               docast, IFBURIED, NULL },
1814     { M('c'), "chat", "talk to someone",
1815               dotalk, IFBURIED | AUTOCOMPLETE, NULL },
1816     { 'c',    "close", "close a door",
1817               doclose, 0, NULL },
1818     { M('C'), "conduct", "list voluntary challenges you have maintained",
1819               doconduct, IFBURIED | AUTOCOMPLETE, NULL },
1820     { M('d'), "dip", "dip an object into something",
1821               dodip, AUTOCOMPLETE, NULL },
1822     { '>',    "down", "go down a staircase",
1823               dodown, 0, NULL },
1824     { 'd',    "drop", "drop an item",
1825               dodrop, 0, NULL },
1826     { 'D',    "droptype", "drop specific item types",
1827               doddrop, 0, NULL },
1828     { 'e',    "eat", "eat something",
1829               doeat, 0, NULL },
1830     { 'E',    "engrave", "engrave writing on the floor",
1831               doengrave, 0, NULL },
1832     { M('e'), "enhance", "advance or check weapon and spell skills",
1833               enhance_weapon_skill, IFBURIED | AUTOCOMPLETE, NULL },
1834     /* #exploremode should be flagged AUTOCOMPETE but that would negatively
1835        impact frequently used #enhance by making #e become ambiguous */
1836     { M('X'), "exploremode", "enter explore (discovery) mode",
1837               enter_explore_mode, IFBURIED | GENERALCMD, NULL },
1838     { 'f',    "fire", "fire ammunition from quiver",
1839               dofire, 0, NULL },
1840     { M('f'), "force", "force a lock",
1841               doforce, AUTOCOMPLETE, NULL },
1842     { ';',    "glance", "show what type of thing a map symbol corresponds to",
1843               doquickwhatis, IFBURIED | GENERALCMD, NULL },
1844     { '?',    "help", "give a help message",
1845               dohelp, IFBURIED | GENERALCMD, NULL },
1846     { '\0',   "herecmdmenu", "show menu of commands you can do here",
1847               doherecmdmenu, IFBURIED | AUTOCOMPLETE | GENERALCMD, NULL },
1848     { 'V',    "history", "show long version and game history",
1849               dohistory, IFBURIED | GENERALCMD, NULL },
1850     { 'i',    "inventory", "show your inventory",
1851               ddoinv, IFBURIED, NULL },
1852     { 'I',    "inventtype", "show inventory of one specific item class",
1853               dotypeinv, IFBURIED, NULL },
1854     { M('i'), "invoke", "invoke an object's special powers",
1855               doinvoke, IFBURIED | AUTOCOMPLETE, NULL },
1856     { M('j'), "jump", "jump to another location",
1857               dojump, AUTOCOMPLETE, NULL },
1858     { C('d'), "kick", "kick something",
1859               dokick, 0, NULL },
1860     { '\\',   "known", "show what object types have been discovered",
1861               dodiscovered, IFBURIED | GENERALCMD, NULL },
1862     { '`',    "knownclass", "show discovered types for one class of objects",
1863               doclassdisco, IFBURIED | GENERALCMD, NULL },
1864     { '\0',   "levelchange", "change experience level",
1865               wiz_level_change, IFBURIED | AUTOCOMPLETE | WIZMODECMD, NULL },
1866     { '\0',   "lightsources", "show mobile light sources",
1867               wiz_light_sources, IFBURIED | AUTOCOMPLETE | WIZMODECMD, NULL },
1868     { ':',    "look", "look at what is here",
1869               dolook, IFBURIED, NULL },
1870     { M('l'), "loot", "loot a box on the floor",
1871               doloot, AUTOCOMPLETE, NULL },
1872 #ifdef DEBUG_MIGRATING_MONS
1873     { '\0',   "migratemons", "migrate N random monsters",
1874               wiz_migrate_mons, IFBURIED | AUTOCOMPLETE | WIZMODECMD, NULL },
1875 #endif
1876     { M('m'), "monster", "use monster's special ability",
1877               domonability, IFBURIED | AUTOCOMPLETE, NULL },
1878     { 'N',    "name", "same as call; name a monster or object or object type",
1879               docallcmd, IFBURIED | AUTOCOMPLETE, NULL },
1880     { M('o'), "offer", "offer a sacrifice to the gods",
1881               dosacrifice, AUTOCOMPLETE, NULL },
1882     { 'o',    "open", "open a door",
1883               doopen, 0, NULL },
1884     { 'O',    "options", "show option settings, possibly change them",
1885               doset, IFBURIED | GENERALCMD, NULL },
1886     /* #overview used to need autocomplete and has retained that even
1887        after being assigned to ^O [old wizard mode ^O is now #wizwhere] */
1888     { C('o'), "overview", "show a summary of the explored dungeon",
1889               dooverview, IFBURIED | AUTOCOMPLETE, NULL },
1890     /* [should #panic actually autocomplete?] */
1891     { '\0',   "panic", "test panic routine (fatal to game)",
1892               wiz_panic, IFBURIED | AUTOCOMPLETE | WIZMODECMD, NULL },
1893     { 'p',    "pay", "pay your shopping bill",
1894               dopay, 0, NULL },
1895     { '|',    "perminv", "scroll persistent inventory display",
1896               doperminv, IFBURIED | GENERALCMD, NULL },
1897     { ',',    "pickup", "pick up things at the current location",
1898               dopickup, 0, NULL },
1899     { '\0',   "polyself", "polymorph self",
1900               wiz_polyself, IFBURIED | AUTOCOMPLETE | WIZMODECMD, NULL },
1901     { M('p'), "pray", "pray to the gods for help",
1902               dopray, IFBURIED | AUTOCOMPLETE, NULL },
1903     { C('p'), "prevmsg", "view recent game messages",
1904               doprev_message, IFBURIED | GENERALCMD, NULL },
1905     { 'P',    "puton", "put on an accessory (ring, amulet, etc)",
1906               doputon, 0, NULL },
1907     { 'q',    "quaff", "quaff (drink) something",
1908               dodrink, 0, NULL },
1909     { M('q'), "quit", "exit without saving current game",
1910               done2, IFBURIED | AUTOCOMPLETE | GENERALCMD, NULL },
1911     { 'Q',    "quiver", "select ammunition for quiver",
1912               dowieldquiver, 0, NULL },
1913     { 'r',    "read", "read a scroll or spellbook",
1914               doread, 0, NULL },
1915     { C('r'), "redraw", "redraw screen",
1916               doredraw, IFBURIED | GENERALCMD, NULL },
1917     { 'R',    "remove", "remove an accessory (ring, amulet, etc)",
1918               doremring, 0, NULL },
1919     { M('R'), "ride", "mount or dismount a saddled steed",
1920               doride, AUTOCOMPLETE, NULL },
1921     { M('r'), "rub", "rub a lamp or a stone",
1922               dorub, AUTOCOMPLETE, NULL },
1923     { 'S',    "save", "save the game and exit",
1924               dosave, IFBURIED | GENERALCMD, NULL },
1925     { 's',    "search", "search for traps and secret doors",
1926               dosearch, IFBURIED, "searching" },
1927     { '*',    "seeall", "show all equipment in use",
1928               doprinuse, IFBURIED, NULL },
1929     { AMULET_SYM, "seeamulet", "show the amulet currently worn",
1930               dopramulet, IFBURIED, NULL },
1931     { ARMOR_SYM, "seearmor", "show the armor currently worn",
1932               doprarm, IFBURIED, NULL },
1933     { RING_SYM, "seerings", "show the ring(s) currently worn",
1934               doprring, IFBURIED, NULL },
1935     { TOOL_SYM, "seetools", "show the tools currently in use",
1936               doprtool, IFBURIED, NULL },
1937     { WEAPON_SYM, "seeweapon", "show the weapon currently wielded",
1938               doprwep, IFBURIED, NULL },
1939     { '!', "shell", "leave game to enter a sub-shell ('exit' to come back)",
1940               dosh_core, (IFBURIED | GENERALCMD
1941 #ifndef SHELL
1942                        | CMD_NOT_AVAILABLE
1943 #endif /* SHELL */
1944                         ), NULL },
1945     /* $ is like ),=,&c but is not included with *, so not called "seegold" */
1946     { GOLD_SYM, "showgold", "show gold, possibly shop credit or debt",
1947               doprgold, IFBURIED, NULL },
1948     { SPBOOK_SYM, "showspells", "list and reorder known spells",
1949               dovspell, IFBURIED, NULL },
1950     { '^',    "showtrap", "describe an adjacent, discovered trap",
1951               doidtrap, IFBURIED, NULL },
1952     { M('s'), "sit", "sit down",
1953               dosit, AUTOCOMPLETE, NULL },
1954     { '\0',   "stats", "show memory statistics",
1955               wiz_show_stats, IFBURIED | AUTOCOMPLETE | WIZMODECMD, NULL },
1956     { C('z'), "suspend", "push game to background ('fg' to come back)",
1957               dosuspend_core, (IFBURIED | GENERALCMD
1958 #ifndef SUSPEND
1959                             | CMD_NOT_AVAILABLE
1960 #endif /* SUSPEND */
1961                                ), NULL },
1962     { 'x',    "swap", "swap wielded and secondary weapons",
1963               doswapweapon, 0, NULL },
1964     { 'T',    "takeoff", "take off one piece of armor",
1965               dotakeoff, 0, NULL },
1966     { 'A',    "takeoffall", "remove all armor",
1967               doddoremarm, 0, NULL },
1968     { C('t'), "teleport", "teleport around the level",
1969               dotelecmd, IFBURIED, NULL },
1970     /* \177 == <del> aka <delete> aka <rubout>; some terminals have an
1971        option to swap it with <backspace> so if there's a key labeled
1972        <delete> it may or may not actually invoke the #terrain command */
1973     { '\177', "terrain",
1974               "view map without monsters or objects obstructing it",
1975               doterrain, IFBURIED | AUTOCOMPLETE, NULL },
1976     /* therecmdmenu does not work as intended, should probably be removed */
1977     { '\0',   "therecmdmenu",
1978               "menu of commands you can do from here to adjacent spot",
1979               dotherecmdmenu, AUTOCOMPLETE | GENERALCMD, NULL },
1980     { 't',    "throw", "throw something",
1981               dothrow, 0, NULL },
1982     { '\0',   "timeout", "look at timeout queue and hero's timed intrinsics",
1983               wiz_timeout_queue, IFBURIED | AUTOCOMPLETE | WIZMODECMD, NULL },
1984     { M('T'), "tip", "empty a container",
1985               dotip, AUTOCOMPLETE, NULL },
1986     { '_',    "travel", "travel to a specific location on the map",
1987               dotravel, 0, NULL },
1988     { M('t'), "turn", "turn undead away",
1989               doturn, IFBURIED | AUTOCOMPLETE, NULL },
1990     { 'X',    "twoweapon", "toggle two-weapon combat",
1991               dotwoweapon, 0, NULL },
1992     { M('u'), "untrap", "untrap something",
1993               dountrap, AUTOCOMPLETE, NULL },
1994     { '<',    "up", "go up a staircase",
1995               doup, 0, NULL },
1996     { '\0',   "vanquished", "list vanquished monsters",
1997               dovanquished, IFBURIED | AUTOCOMPLETE | WIZMODECMD, NULL },
1998     { M('v'), "version",
1999               "list compile time options for this version of NetHack",
2000               doextversion, IFBURIED | AUTOCOMPLETE | GENERALCMD, NULL },
2001     { 'v',    "versionshort", "show version and date+time program was built",
2002               doversion, IFBURIED | GENERALCMD, NULL },
2003     { '\0',   "vision", "show vision array",
2004               wiz_show_vision, IFBURIED | AUTOCOMPLETE | WIZMODECMD, NULL },
2005     { '.',    "wait", "rest one move while doing nothing",
2006               donull, IFBURIED, "waiting" },
2007     { 'W',    "wear", "wear a piece of armor",
2008               dowear, 0, NULL },
2009     { '&',    "whatdoes", "tell what a command does",
2010               dowhatdoes, IFBURIED, NULL },
2011     { '/',    "whatis", "show what type of thing a symbol corresponds to",
2012               dowhatis, IFBURIED | GENERALCMD, NULL },
2013     { 'w',    "wield", "wield (put in use) a weapon",
2014               dowield, 0, NULL },
2015     { M('w'), "wipe", "wipe off your face",
2016               dowipe, AUTOCOMPLETE, NULL },
2017     { '\0',   "wizborn", "show stats of monsters created",
2018               doborn, IFBURIED | WIZMODECMD, NULL },
2019 #ifdef DEBUG
2020     { '\0',   "wizbury", "bury objs under and around you",
2021               wiz_debug_cmd_bury, IFBURIED | AUTOCOMPLETE | WIZMODECMD, NULL },
2022 #endif
2023     { C('e'), "wizdetect", "reveal hidden things within a small radius",
2024               wiz_detect, IFBURIED | WIZMODECMD, NULL },
2025     { '\0',   "wizfliplevel", "flip the level",
2026               wiz_flip_level, IFBURIED | WIZMODECMD, NULL },
2027     { C('g'), "wizgenesis", "create a monster",
2028               wiz_genesis, IFBURIED | WIZMODECMD, NULL },
2029     { C('i'), "wizidentify", "identify all items in inventory",
2030               wiz_identify, IFBURIED | WIZMODECMD, NULL },
2031     { '\0',   "wizintrinsic", "set an intrinsic",
2032               wiz_intrinsic, IFBURIED | AUTOCOMPLETE | WIZMODECMD, NULL },
2033     { C('v'), "wizlevelport", "teleport to another level",
2034               wiz_level_tele, IFBURIED | WIZMODECMD, NULL },
2035     { '\0',   "wizloaddes", "load and execute a des-file lua script",
2036               wiz_load_splua, IFBURIED | WIZMODECMD, NULL },
2037     { '\0',   "wizloadlua", "load and execute a lua script",
2038               wiz_load_lua, IFBURIED | WIZMODECMD, NULL },
2039     { '\0',   "wizmakemap", "recreate the current level",
2040               wiz_makemap, IFBURIED | WIZMODECMD, NULL },
2041     { C('f'), "wizmap", "map the level",
2042               wiz_map, IFBURIED | WIZMODECMD, NULL },
2043     { '\0',   "wizrumorcheck", "verify rumor boundaries",
2044               wiz_rumor_check, IFBURIED | AUTOCOMPLETE | WIZMODECMD, NULL },
2045     { '\0',   "wizseenv", "show map locations' seen vectors",
2046               wiz_show_seenv, IFBURIED | AUTOCOMPLETE | WIZMODECMD, NULL },
2047     { '\0',   "wizsmell", "smell monster",
2048               wiz_smell, IFBURIED | AUTOCOMPLETE | WIZMODECMD, NULL },
2049     { '\0',   "wizwhere", "show locations of special levels",
2050               wiz_where, IFBURIED | AUTOCOMPLETE | WIZMODECMD, NULL },
2051     { C('w'), "wizwish", "wish for something",
2052               wiz_wish, IFBURIED | WIZMODECMD, NULL },
2053     { '\0',   "wmode", "show wall modes",
2054               wiz_show_wmodes, IFBURIED | AUTOCOMPLETE | WIZMODECMD, NULL },
2055     { 'z',    "zap", "zap a wand",
2056               dozap, 0, NULL },
2057     { '\0', (char *) 0, (char *) 0, donull, 0, (char *) 0 } /* sentinel */
2058 };
2059 
2060 /* used by dokeylist() and by key2extcmdesc() for dowhatdoes() */
2061 static const char
2062     run_desc[] = "Prefix: run until something very interesting is seen",
2063     rush_desc[] = "Prefix: rush until something interesting is seen",
2064     forcefight_desc[] = "Prefix: force fight even if you don't see a monster";
2065 
2066 static const struct {
2067     int nhkf;
2068     const char *desc;
2069     boolean numpad;
2070 } misc_keys[] = {
2071     { NHKF_ESC, "cancel current prompt or pending prefix", FALSE },
2072     { NHKF_RUSH, rush_desc, FALSE },
2073     { NHKF_RUSH2, rush_desc, TRUE },
2074     { NHKF_RUN, run_desc, FALSE },
2075     { NHKF_RUN2, run_desc, TRUE },
2076     { NHKF_FIGHT, forcefight_desc, FALSE },
2077     { NHKF_FIGHT2, forcefight_desc, TRUE } ,
2078     { NHKF_NOPICKUP,
2079       "Prefix: move without picking up objects or fighting", FALSE },
2080     { NHKF_RUN_NOPICKUP,
2081       "Prefix: run without picking up objects or fighting", FALSE },
2082     { NHKF_REQMENU,
2083       "Prefix: request a menu (for some non-movement commands)", FALSE },
2084     { NHKF_COUNT,
2085       "Prefix: for digits when preceding a command with a count", TRUE },
2086     { NHKF_DOINV, "numpad: view full inventory", TRUE },
2087     /* NHKF_DOINV2 for num_pad+pcHack_compat isn't implemented:
2088     { NHKF_DOINV2, "numpad: view inventory of one class of objects", TRUE },
2089     */
2090     { NHKF_DOAGAIN , "repeat: perform the previous command again", FALSE },
2091     { 0, (const char *) 0, FALSE }
2092 };
2093 
2094 /* for key2extcmddesc() to support dowhatdoes() */
2095 struct movcmd {
2096     uchar k1, k2, k3, k4; /* 'normal', 'qwertz', 'numpad', 'phone' */
2097     const char *txt, *alt; /* compass direction, screen direction */
2098 };
2099 static const struct movcmd movtab[] = {
2100     { 'h', 'h', '4', '4', "west",      "left" },
2101     { 'j', 'j', '2', '8', "south",     "down" },
2102     { 'k', 'k', '8', '2', "north",     "up" },
2103     { 'l', 'l', '6', '6', "east",      "right" },
2104     { 'b', 'b', '1', '7', "southwest", "lower left" },
2105     { 'n', 'n', '3', '9', "southeast", "lower right" },
2106     { 'u', 'u', '9', '3', "northeast", "upper right" },
2107     { 'y', 'z', '7', '1', "northwest", "upper left" },
2108     {   0,   0,   0,   0,  (char *) 0, (char *) 0 }
2109 };
2110 
2111 int extcmdlist_length = SIZE(extcmdlist) - 1;
2112 
2113 const char *
key2extcmddesc(uchar key)2114 key2extcmddesc(uchar key)
2115 {
2116     static char key2cmdbuf[QBUFSZ];
2117     const struct movcmd *mov;
2118     int k, c, i, j;
2119     uchar M_5 = (uchar) M('5'), M_0 = (uchar) M('0');
2120 
2121     /* need to check for movement commands before checking the extended
2122        commands table because it contains entries for number_pad commands
2123        that match !number_pad movement (like 'j' for "jump") */
2124     key2cmdbuf[0] = '\0';
2125     if (movecmd(k = key))
2126         Strcpy(key2cmdbuf, "move"); /* "move or attack"? */
2127     else if (movecmd(k = unctrl(key)))
2128         Strcpy(key2cmdbuf, "rush");
2129     else if (movecmd(k = (g.Cmd.num_pad ? unmeta(key) : lowc(key))))
2130         Strcpy(key2cmdbuf, "run");
2131     if (*key2cmdbuf) {
2132         for (mov = &movtab[0]; mov->k1; ++mov) {
2133             c = !g.Cmd.num_pad ? (!g.Cmd.swap_yz ? mov->k1 : mov->k2)
2134                              : (!g.Cmd.phone_layout ? mov->k3 : mov->k4);
2135             if (c == k) {
2136                 Sprintf(eos(key2cmdbuf), " %s (screen %s)",
2137                         mov->txt, mov->alt);
2138                 return key2cmdbuf;
2139             }
2140         }
2141     } else if (digit(key) || (g.Cmd.num_pad && digit(unmeta(key)))) {
2142         key2cmdbuf[0] = '\0';
2143         if (!g.Cmd.num_pad)
2144             Strcpy(key2cmdbuf, "start of, or continuation of, a count");
2145         else if (key == '5' || key == M_5)
2146             Sprintf(key2cmdbuf, "%s prefix",
2147                     (!!g.Cmd.pcHack_compat ^ (key == M_5)) ? "run" : "rush");
2148         else if (key == '0' || (g.Cmd.pcHack_compat && key == M_0))
2149             Strcpy(key2cmdbuf, "synonym for 'i'");
2150         if (*key2cmdbuf)
2151             return key2cmdbuf;
2152     }
2153     /* check prefixes before regular commands; includes ^A pseudo-command */
2154     for (i = 0; misc_keys[i].desc; ++i) {
2155         if (misc_keys[i].numpad && !iflags.num_pad)
2156             continue;
2157         j = misc_keys[i].nhkf;
2158         if (key == (uchar) g.Cmd.spkeys[j])
2159             return misc_keys[i].desc;
2160     }
2161     /* finally, check whether 'key' is a command */
2162     if (g.Cmd.commands[key]) {
2163         if (g.Cmd.commands[key]->ef_txt) {
2164             Sprintf(key2cmdbuf, "%s (#%s)",
2165                     g.Cmd.commands[key]->ef_desc,
2166                     g.Cmd.commands[key]->ef_txt);
2167             return key2cmdbuf;
2168         }
2169     }
2170     return (char *) 0;
2171 }
2172 
2173 boolean
bind_key(uchar key,const char * command)2174 bind_key(uchar key, const char *command)
2175 {
2176     struct ext_func_tab *extcmd;
2177 
2178     /* special case: "nothing" is reserved for unbinding */
2179     if (!strcmpi(command, "nothing")) {
2180         g.Cmd.commands[key] = (struct ext_func_tab *) 0;
2181         return TRUE;
2182     }
2183 
2184     for (extcmd = extcmdlist; extcmd->ef_txt; extcmd++) {
2185         if (strcmpi(command, extcmd->ef_txt))
2186             continue;
2187         g.Cmd.commands[key] = extcmd;
2188 #if 0 /* silently accept key binding for unavailable command (!SHELL,&c) */
2189         if ((extcmd->flags & CMD_NOT_AVAILABLE) != 0) {
2190             char buf[BUFSZ];
2191 
2192             Sprintf(buf, cmdnotavail, extcmd->ef_txt);
2193             config_error_add("%s", buf);
2194         }
2195 #endif
2196         return TRUE;
2197     }
2198 
2199     return FALSE;
2200 }
2201 
2202 /* initialize all keyboard commands */
2203 static void
commands_init(void)2204 commands_init(void)
2205 {
2206     struct ext_func_tab *extcmd;
2207 
2208     for (extcmd = extcmdlist; extcmd->ef_txt; extcmd++)
2209         if (extcmd->key)
2210             g.Cmd.commands[extcmd->key] = extcmd;
2211 
2212     (void) bind_key(C('l'), "redraw"); /* if number_pad is set */
2213     /*       'b', 'B' : go sw */
2214     /*       'F' : fight (one time) */
2215     /*       'g', 'G' : multiple go */
2216     /*       'h', 'H' : go west */
2217     (void) bind_key('h',    "help"); /* if number_pad is set */
2218     (void) bind_key('j',    "jump"); /* if number_pad is on */
2219     /*       'j', 'J', 'k', 'K', 'l', 'L', 'm', 'M', 'n', 'N' move commands */
2220     (void) bind_key('k',    "kick"); /* if number_pad is on */
2221     (void) bind_key('l',    "loot"); /* if number_pad is on */
2222     (void) bind_key(C('n'), "annotate"); /* if number_pad is on */
2223     (void) bind_key(M('n'), "name");
2224     (void) bind_key(M('N'), "name");
2225     (void) bind_key('u',    "untrap"); /* if number_pad is on */
2226 
2227     /* alt keys: */
2228     (void) bind_key(M('O'), "overview");
2229     (void) bind_key(M('2'), "twoweapon");
2230 
2231     /* wait_on_space */
2232     (void) bind_key(' ',    "wait");
2233 }
2234 
2235 static boolean
keylist_func_has_key(const struct ext_func_tab * extcmd,boolean * skip_keys_used)2236 keylist_func_has_key(const struct ext_func_tab *extcmd,
2237                      boolean *skip_keys_used) /* boolean keys_used[256] */
2238 {
2239     int i;
2240 
2241     for (i = 0; i < 256; ++i) {
2242         if (skip_keys_used[i])
2243             continue;
2244 
2245         if (g.Cmd.commands[i] == extcmd)
2246             return TRUE;
2247     }
2248     return FALSE;
2249 }
2250 
2251 static int
keylist_putcmds(winid datawin,boolean docount,int incl_flags,int excl_flags,boolean * keys_used)2252 keylist_putcmds(winid datawin, boolean docount,
2253                 int incl_flags, int excl_flags,
2254                 boolean *keys_used) /* boolean keys_used[256] */
2255 {
2256     const struct ext_func_tab *extcmd;
2257     int i;
2258     char buf[BUFSZ], buf2[QBUFSZ];
2259     boolean keys_already_used[256]; /* copy of keys_used[] before updates */
2260     int count = 0;
2261 
2262     for (i = 0; i < 256; i++) {
2263         uchar key = (uchar) i;
2264 
2265         keys_already_used[i] = keys_used[i];
2266         if (keys_used[i])
2267             continue;
2268         if (key == ' ' && !flags.rest_on_space)
2269             continue;
2270         if ((extcmd = g.Cmd.commands[i]) != (struct ext_func_tab *) 0) {
2271             if ((incl_flags && !(extcmd->flags & incl_flags))
2272                 || (excl_flags && (extcmd->flags & excl_flags)))
2273                 continue;
2274             if (docount) {
2275                 count++;
2276                 continue;
2277             }
2278             Sprintf(buf, "%-7s %-13s %s", key2txt(key, buf2),
2279                     extcmd->ef_txt, extcmd->ef_desc);
2280             putstr(datawin, 0, buf);
2281             keys_used[i] = TRUE;
2282         }
2283     }
2284     /* also list commands that lack key assignments; most are wizard mode */
2285     for (extcmd = extcmdlist; extcmd->ef_txt; ++extcmd) {
2286         if ((incl_flags && !(extcmd->flags & incl_flags))
2287             || (excl_flags && (extcmd->flags & excl_flags)))
2288             continue;
2289         /* can't just check for non-Null extcmd->key; it holds the
2290            default assignment and a user-specified binding might hijack
2291            this command's default key for some other command; or this
2292            command might have been assigned a key being used for
2293            movement or as a prefix, intercepting that keystroke */
2294         if (keylist_func_has_key(extcmd, keys_already_used))
2295             continue;
2296         /* found a command for current category without any key assignment */
2297         if (docount) {
2298             count++;
2299             continue;
2300         }
2301         /* '#'+20 for one column here == 7+' '+13 for two columns above */
2302         Sprintf(buf, "#%-20s %s", extcmd->ef_txt, extcmd->ef_desc);
2303         putstr(datawin, 0, buf);
2304     }
2305     return count;
2306 }
2307 
2308 /* list all keys and their bindings, like dat/hh but dynamic */
2309 void
dokeylist(void)2310 dokeylist(void)
2311 {
2312     const struct ext_func_tab *extcmd;
2313     winid datawin;
2314     char buf[BUFSZ], buf2[BUFSZ];
2315     uchar key;
2316     boolean spkey_gap, keys_used[256], mov_seen[256];
2317     int i, j, pfx_seen[256];
2318 
2319     (void) memset((genericptr_t) keys_used, 0, sizeof keys_used);
2320     (void) memset((genericptr_t) pfx_seen, 0, sizeof pfx_seen);
2321 
2322     keys_used[(uchar) g.Cmd.move_NW] = keys_used[(uchar) g.Cmd.move_N]
2323         = keys_used[(uchar) g.Cmd.move_NE] = keys_used[(uchar) g.Cmd.move_W]
2324         = keys_used[(uchar) g.Cmd.move_E] = keys_used[(uchar) g.Cmd.move_SW]
2325         = keys_used[(uchar) g.Cmd.move_S] = keys_used[(uchar) g.Cmd.move_SE]
2326         = TRUE;
2327     if (!iflags.num_pad) {
2328         keys_used[(uchar) highc(g.Cmd.move_NW)]
2329             = keys_used[(uchar) highc(g.Cmd.move_N)]
2330             = keys_used[(uchar) highc(g.Cmd.move_NE)]
2331             = keys_used[(uchar) highc(g.Cmd.move_W)]
2332             = keys_used[(uchar) highc(g.Cmd.move_E)]
2333             = keys_used[(uchar) highc(g.Cmd.move_SW)]
2334             = keys_used[(uchar) highc(g.Cmd.move_S)]
2335             = keys_used[(uchar) highc(g.Cmd.move_SE)] = TRUE;
2336         keys_used[(uchar) C(g.Cmd.move_NW)]
2337             = keys_used[(uchar) C(g.Cmd.move_N)]
2338             = keys_used[(uchar) C(g.Cmd.move_NE)]
2339             = keys_used[(uchar) C(g.Cmd.move_W)]
2340             = keys_used[(uchar) C(g.Cmd.move_E)]
2341             = keys_used[(uchar) C(g.Cmd.move_SW)]
2342             = keys_used[(uchar) C(g.Cmd.move_S)]
2343             = keys_used[(uchar) C(g.Cmd.move_SE)] = TRUE;
2344     } else {
2345         /* num_pad */
2346         keys_used[(uchar) M('1')] = keys_used[(uchar) M('2')]
2347             = keys_used[(uchar) M('3')] = keys_used[(uchar) M('4')]
2348             = keys_used[(uchar) M('6')] = keys_used[(uchar) M('7')]
2349             = keys_used[(uchar) M('8')] = keys_used[(uchar) M('9')] = TRUE;
2350     }
2351 #ifndef NO_SIGNAL
2352     /* this is actually ambiguous; tty raw mode will override SIGINT;
2353        when enabled, treat it like a movement command since assigning
2354        other commands to this keystroke would be unwise... */
2355     key = (uchar) C('c');
2356     keys_used[key] = TRUE;
2357 #endif
2358 
2359     /* movement keys have been flagged in keys_used[]; clone them */
2360     (void) memcpy((genericptr_t) mov_seen, (genericptr_t) keys_used,
2361                   sizeof mov_seen);
2362 
2363     spkey_gap = FALSE;
2364     for (i = 0; misc_keys[i].desc; ++i) {
2365         if (misc_keys[i].numpad && !iflags.num_pad)
2366             continue;
2367         j = misc_keys[i].nhkf;
2368         key = (uchar) g.Cmd.spkeys[j];
2369         if (key && !mov_seen[key] && (!pfx_seen[key] || j == NHKF_REQMENU)) {
2370             keys_used[key] = TRUE;
2371             if (j != NHKF_REQMENU)
2372                 pfx_seen[key] = j;
2373         } else
2374             spkey_gap = TRUE;
2375     }
2376 
2377     datawin = create_nhwindow(NHW_TEXT);
2378     putstr(datawin, 0, "");
2379     Sprintf(buf, "%7s %s", "", "    Full Current Key Bindings List");
2380     putstr(datawin, 0, buf);
2381     for (extcmd = extcmdlist; extcmd->ef_txt; ++extcmd)
2382         if (spkey_gap || !keylist_func_has_key(extcmd, keys_used)) {
2383             Sprintf(buf, "%7s %s", "",
2384                                "(also commands with no key assignment)");
2385             putstr(datawin, 0, buf);
2386             break;
2387         }
2388 
2389     /* directional keys */
2390     putstr(datawin, 0, "");
2391     putstr(datawin, 0, "Directional keys:");
2392     show_direction_keys(datawin, '.', FALSE); /* '.'==self in direct'n grid */
2393 
2394     if (!iflags.num_pad) {
2395         putstr(datawin, 0, "");
2396         putstr(datawin, 0,
2397      "Ctrl+<direction> will run in specified direction until something very");
2398         Sprintf(buf, "%7s %s", "", "interesting is seen.");
2399         putstr(datawin, 0, buf);
2400         Strcpy(buf, "Shift"); /* append the rest below */
2401     } else {
2402         /* num_pad */
2403         putstr(datawin, 0, "");
2404         Strcpy(buf, "Meta"); /* append the rest next */
2405     }
2406     Strcat(buf,
2407           "+<direction> will run in specified direction until you encounter");
2408     putstr(datawin, 0, buf);
2409     Sprintf(buf, "%7s %s", "", "an obstacle.");
2410     putstr(datawin, 0, buf);
2411 
2412     putstr(datawin, 0, "");
2413     putstr(datawin, 0, "Miscellaneous keys:");
2414     for (i = 0; misc_keys[i].desc; ++i) {
2415         if (misc_keys[i].numpad && !iflags.num_pad)
2416             continue;
2417         j = misc_keys[i].nhkf;
2418         key = (uchar) g.Cmd.spkeys[j];
2419         if (key && !mov_seen[key]
2420             && (pfx_seen[key] == j || j == NHKF_REQMENU)) {
2421             Sprintf(buf, "%-7s %s", key2txt(key, buf2), misc_keys[i].desc);
2422             putstr(datawin, 0, buf);
2423         }
2424     }
2425     /* (see above) */
2426     key = (uchar) C('c');
2427 #ifndef NO_SIGNAL
2428     /* last of the special keys */
2429     Sprintf(buf, "%-7s", key2txt(key, buf2));
2430 #else
2431     /* first of the keyless commands */
2432     Sprintf(buf2, "[%s]", key2txt(key, buf));
2433     Sprintf(buf, "%-21s", buf2);
2434 #endif
2435     Strcat(buf, " interrupt: break out of NetHack (SIGINT)");
2436     putstr(datawin, 0, buf);
2437     /* keyless special key commands, if any */
2438     if (spkey_gap) {
2439         for (i = 0; misc_keys[i].desc; ++i) {
2440             if (misc_keys[i].numpad && !iflags.num_pad)
2441                 continue;
2442             j = misc_keys[i].nhkf;
2443             key = (uchar) g.Cmd.spkeys[j];
2444             if (!key || (pfx_seen[key] != j && j != NHKF_REQMENU)) {
2445                 Sprintf(buf2, "[%s]", spkey_name(j));
2446                 /* lines up with the other unassigned commands which use
2447                    "#%-20s ", but not with the other special keys */
2448                 Snprintf(buf, sizeof(buf), "%-21s %s", buf2,
2449                          misc_keys[i].desc);
2450                 putstr(datawin, 0, buf);
2451             }
2452         }
2453     }
2454 
2455     putstr(datawin, 0, "");
2456     show_menu_controls(datawin, TRUE);
2457 
2458     if (keylist_putcmds(datawin, TRUE, GENERALCMD, WIZMODECMD, keys_used)) {
2459         putstr(datawin, 0, "");
2460         putstr(datawin, 0, "General commands:");
2461         (void) keylist_putcmds(datawin, FALSE, GENERALCMD, WIZMODECMD,
2462                                  keys_used);
2463     }
2464 
2465     if (keylist_putcmds(datawin, TRUE, 0,
2466                         GENERALCMD | WIZMODECMD, keys_used)) {
2467         putstr(datawin, 0, "");
2468         putstr(datawin, 0, "Game commands:");
2469         (void) keylist_putcmds(datawin, FALSE, 0,
2470                                GENERALCMD | WIZMODECMD, keys_used);
2471     }
2472 
2473     if (wizard && keylist_putcmds(datawin, TRUE, WIZMODECMD, 0, keys_used)) {
2474         putstr(datawin, 0, "");
2475         putstr(datawin, 0, "Debug mode commands:");
2476         (void) keylist_putcmds(datawin, FALSE, WIZMODECMD, 0, keys_used);
2477     }
2478 
2479     display_nhwindow(datawin, FALSE);
2480     destroy_nhwindow(datawin);
2481 }
2482 
2483 char
cmd_from_func(int (* fn)(void))2484 cmd_from_func(int (*fn)(void))
2485 {
2486     int i;
2487 
2488     /* skip NUL; allowing it would wreak havoc */
2489     for (i = 1; i < 256; ++i) {
2490         /* skip space; we'll use it below as last resort if no other
2491            keystroke invokes space's command */
2492         if (i == ' ')
2493             continue;
2494 
2495         if (g.Cmd.commands[i] && g.Cmd.commands[i]->ef_funct == fn)
2496             return (char) i;
2497     }
2498     if (g.Cmd.commands[' '] && g.Cmd.commands[' ']->ef_funct == fn)
2499         return ' ';
2500     return '\0';
2501 }
2502 
2503 /* return extended command name (without leading '#') for command (*fn)() */
2504 const char *
cmdname_from_func(int (* fn)(void),char outbuf[],boolean fullname)2505 cmdname_from_func(int (*fn)(void), char outbuf[],
2506                   boolean fullname) /* False: just enough to disambiguate */
2507 {
2508     const struct ext_func_tab *extcmd, *cmdptr = 0;
2509     const char *res = 0;
2510 
2511     for (extcmd = extcmdlist; extcmd->ef_txt; ++extcmd)
2512         if (extcmd->ef_funct == fn) {
2513             cmdptr = extcmd;
2514             res = cmdptr->ef_txt;
2515             break;
2516         }
2517 
2518     if (!res) {
2519         /* make sure output buffer doesn't contain junk or stale data;
2520            return Null below */
2521         outbuf[0] = '\0';
2522     } else if (fullname) {
2523         /* easy; the entire command name */
2524         res = strcpy(outbuf, res);
2525     } else {
2526         const struct ext_func_tab *matchcmd = extcmdlist;
2527         int len = 0;
2528 
2529         /* find the shortest leading substring which is unambiguous */
2530         do {
2531             if (++len >= (int) strlen(res))
2532                 break;
2533             for (extcmd = matchcmd; extcmd->ef_txt; ++extcmd) {
2534                 if (extcmd == cmdptr)
2535                     continue;
2536                 if ((extcmd->flags & CMD_NOT_AVAILABLE) != 0
2537                     || ((extcmd->flags & WIZMODECMD) != 0 && !wizard))
2538                     continue;
2539                 if (!strncmp(res, extcmd->ef_txt, len)) {
2540                     matchcmd = extcmd;
2541                     break;
2542                 }
2543             }
2544         } while (extcmd->ef_txt);
2545         copynchars(outbuf, res, len);
2546         debugpline2("shortened %s: \"%s\"", res, outbuf);
2547         res = outbuf;
2548     }
2549     return res;
2550 }
2551 
2552 /*
2553  * wizard mode sanity_check code
2554  */
2555 
2556 static const char template[] = "%-27s  %4ld  %6ld";
2557 static const char stats_hdr[] = "                             count  bytes";
2558 static const char stats_sep[] = "---------------------------  ----- -------";
2559 
2560 static int
size_obj(struct obj * otmp)2561 size_obj(struct obj *otmp)
2562 {
2563     int sz = (int) sizeof (struct obj);
2564 
2565     if (otmp->oextra) {
2566         sz += (int) sizeof (struct oextra);
2567         if (ONAME(otmp))
2568             sz += (int) strlen(ONAME(otmp)) + 1;
2569         if (OMONST(otmp))
2570             sz += size_monst(OMONST(otmp), FALSE);
2571         if (OMAILCMD(otmp))
2572             sz += (int) strlen(OMAILCMD(otmp)) + 1;
2573         /* sz += (int) sizeof (unsigned); -- now part of oextra itself */
2574     }
2575     return sz;
2576 }
2577 
2578 static void
count_obj(struct obj * chain,long * total_count,long * total_size,boolean top,boolean recurse)2579 count_obj(struct obj *chain, long *total_count, long *total_size,
2580           boolean top, boolean recurse)
2581 {
2582     long count, size;
2583     struct obj *obj;
2584 
2585     for (count = size = 0, obj = chain; obj; obj = obj->nobj) {
2586         if (top) {
2587             count++;
2588             size += size_obj(obj);
2589         }
2590         if (recurse && obj->cobj)
2591             count_obj(obj->cobj, total_count, total_size, TRUE, TRUE);
2592     }
2593     *total_count += count;
2594     *total_size += size;
2595 }
2596 
2597 DISABLE_WARNING_FORMAT_NONLITERAL  /* RESTORE_WARNING follows show_wiz_stats */
2598 
2599 static void
obj_chain(winid win,const char * src,struct obj * chain,boolean force,long * total_count,long * total_size)2600 obj_chain(winid win, const char *src, struct obj *chain, boolean force,
2601           long *total_count, long *total_size)
2602 {
2603     char buf[BUFSZ];
2604     long count = 0L, size = 0L;
2605 
2606     count_obj(chain, &count, &size, TRUE, FALSE);
2607 
2608     if (count || size || force) {
2609         *total_count += count;
2610         *total_size += size;
2611         Sprintf(buf, template, src, count, size);
2612         putstr(win, 0, buf);
2613     }
2614 }
2615 
2616 static void
mon_invent_chain(winid win,const char * src,struct monst * chain,long * total_count,long * total_size)2617 mon_invent_chain(winid win, const char *src, struct monst *chain,
2618                  long *total_count, long *total_size)
2619 {
2620     char buf[BUFSZ];
2621     long count = 0, size = 0;
2622     struct monst *mon;
2623 
2624     for (mon = chain; mon; mon = mon->nmon)
2625         count_obj(mon->minvent, &count, &size, TRUE, FALSE);
2626 
2627     if (count || size) {
2628         *total_count += count;
2629         *total_size += size;
2630         Sprintf(buf, template, src, count, size);
2631         putstr(win, 0, buf);
2632     }
2633 }
2634 
2635 static void
contained_stats(winid win,const char * src,long * total_count,long * total_size)2636 contained_stats(winid win, const char *src, long *total_count,
2637                 long *total_size)
2638 {
2639     char buf[BUFSZ];
2640     long count = 0, size = 0;
2641     struct monst *mon;
2642 
2643     count_obj(g.invent, &count, &size, FALSE, TRUE);
2644     count_obj(fobj, &count, &size, FALSE, TRUE);
2645     count_obj(g.level.buriedobjlist, &count, &size, FALSE, TRUE);
2646     count_obj(g.migrating_objs, &count, &size, FALSE, TRUE);
2647     /* DEADMONSTER check not required in this loop since they have no
2648      * inventory */
2649     for (mon = fmon; mon; mon = mon->nmon)
2650         count_obj(mon->minvent, &count, &size, FALSE, TRUE);
2651     for (mon = g.migrating_mons; mon; mon = mon->nmon)
2652         count_obj(mon->minvent, &count, &size, FALSE, TRUE);
2653 
2654     if (count || size) {
2655         *total_count += count;
2656         *total_size += size;
2657         Sprintf(buf, template, src, count, size);
2658         putstr(win, 0, buf);
2659     }
2660 }
2661 
2662 static int
size_monst(struct monst * mtmp,boolean incl_wsegs)2663 size_monst(struct monst *mtmp, boolean incl_wsegs)
2664 {
2665     int sz = (int) sizeof (struct monst);
2666 
2667     if (mtmp->wormno && incl_wsegs)
2668         sz += size_wseg(mtmp);
2669 
2670     if (mtmp->mextra) {
2671         sz += (int) sizeof (struct mextra);
2672         if (MGIVENNAME(mtmp))
2673             sz += (int) strlen(MGIVENNAME(mtmp)) + 1;
2674         if (EGD(mtmp))
2675             sz += (int) sizeof (struct egd);
2676         if (EPRI(mtmp))
2677             sz += (int) sizeof (struct epri);
2678         if (ESHK(mtmp))
2679             sz += (int) sizeof (struct eshk);
2680         if (EMIN(mtmp))
2681             sz += (int) sizeof (struct emin);
2682         if (EDOG(mtmp))
2683             sz += (int) sizeof (struct edog);
2684         if (EBONES(mtmp))
2685             sz += (int) sizeof (struct ebones);
2686         /* mextra->mcorpsenm doesn't point to more memory */
2687     }
2688     return sz;
2689 }
2690 
2691 static void
mon_chain(winid win,const char * src,struct monst * chain,boolean force,long * total_count,long * total_size)2692 mon_chain(winid win, const char *src, struct monst *chain,
2693           boolean force, long *total_count, long *total_size)
2694 {
2695     char buf[BUFSZ];
2696     long count, size;
2697     struct monst *mon;
2698     /* mon->wormno means something different for migrating_mons and mydogs */
2699     boolean incl_wsegs = !strcmpi(src, "fmon");
2700 
2701     count = size = 0L;
2702     for (mon = chain; mon; mon = mon->nmon) {
2703         count++;
2704         size += size_monst(mon, incl_wsegs);
2705     }
2706     if (count || size || force) {
2707         *total_count += count;
2708         *total_size += size;
2709         Sprintf(buf, template, src, count, size);
2710         putstr(win, 0, buf);
2711     }
2712 }
2713 
2714 static void
misc_stats(winid win,long * total_count,long * total_size)2715 misc_stats(winid win, long *total_count, long *total_size)
2716 {
2717     char buf[BUFSZ], hdrbuf[QBUFSZ];
2718     long count, size;
2719     int idx;
2720     struct trap *tt;
2721     struct damage *sd; /* shop damage */
2722     struct kinfo *k; /* delayed killer */
2723     struct cemetery *bi; /* bones info */
2724 
2725     /* traps and engravings are output unconditionally;
2726      * others only if nonzero
2727      */
2728     count = size = 0L;
2729     for (tt = g.ftrap; tt; tt = tt->ntrap) {
2730         ++count;
2731         size += (long) sizeof *tt;
2732     }
2733     *total_count += count;
2734     *total_size += size;
2735     Sprintf(hdrbuf, "traps, size %ld", (long) sizeof (struct trap));
2736     Sprintf(buf, template, hdrbuf, count, size);
2737     putstr(win, 0, buf);
2738 
2739     count = size = 0L;
2740     engr_stats("engravings, size %ld+text", hdrbuf, &count, &size);
2741     *total_count += count;
2742     *total_size += size;
2743     Sprintf(buf, template, hdrbuf, count, size);
2744     putstr(win, 0, buf);
2745 
2746     count = size = 0L;
2747     light_stats("light sources, size %ld", hdrbuf, &count, &size);
2748     if (count || size) {
2749         *total_count += count;
2750         *total_size += size;
2751         Sprintf(buf, template, hdrbuf, count, size);
2752         putstr(win, 0, buf);
2753     }
2754 
2755     count = size = 0L;
2756     timer_stats("timers, size %ld", hdrbuf, &count, &size);
2757     if (count || size) {
2758         *total_count += count;
2759         *total_size += size;
2760         Sprintf(buf, template, hdrbuf, count, size);
2761         putstr(win, 0, buf);
2762     }
2763 
2764     count = size = 0L;
2765     for (sd = g.level.damagelist; sd; sd = sd->next) {
2766         ++count;
2767         size += (long) sizeof *sd;
2768     }
2769     if (count || size) {
2770         *total_count += count;
2771         *total_size += size;
2772         Sprintf(hdrbuf, "shop damage, size %ld",
2773                 (long) sizeof (struct damage));
2774         Sprintf(buf, template, hdrbuf, count, size);
2775         putstr(win, 0, buf);
2776     }
2777 
2778     count = size = 0L;
2779     region_stats("regions, size %ld+%ld*rect+N", hdrbuf, &count, &size);
2780     if (count || size) {
2781         *total_count += count;
2782         *total_size += size;
2783         Sprintf(buf, template, hdrbuf, count, size);
2784         putstr(win, 0, buf);
2785     }
2786 
2787     count = size = 0L;
2788     for (k = g.killer.next; k; k = k->next) {
2789         ++count;
2790         size += (long) sizeof *k;
2791     }
2792     if (count || size) {
2793         *total_count += count;
2794         *total_size += size;
2795         Sprintf(hdrbuf, "delayed killer%s, size %ld",
2796                 plur(count), (long) sizeof (struct kinfo));
2797         Sprintf(buf, template, hdrbuf, count, size);
2798         putstr(win, 0, buf);
2799     }
2800 
2801     count = size = 0L;
2802     for (bi = g.level.bonesinfo; bi; bi = bi->next) {
2803         ++count;
2804         size += (long) sizeof *bi;
2805     }
2806     if (count || size) {
2807         *total_count += count;
2808         *total_size += size;
2809         Sprintf(hdrbuf, "bones history, size %ld",
2810                 (long) sizeof (struct cemetery));
2811         Sprintf(buf, template, hdrbuf, count, size);
2812         putstr(win, 0, buf);
2813     }
2814 
2815     count = size = 0L;
2816     for (idx = 0; idx < NUM_OBJECTS; ++idx)
2817         if (objects[idx].oc_uname) {
2818             ++count;
2819             size += (long) (strlen(objects[idx].oc_uname) + 1);
2820         }
2821     if (count || size) {
2822         *total_count += count;
2823         *total_size += size;
2824         Strcpy(hdrbuf, "object type names, text");
2825         Sprintf(buf, template, hdrbuf, count, size);
2826         putstr(win, 0, buf);
2827     }
2828 }
2829 
2830 /*
2831  * Display memory usage of all monsters and objects on the level.
2832  */
2833 static int
wiz_show_stats(void)2834 wiz_show_stats(void)
2835 {
2836     char buf[BUFSZ];
2837     winid win;
2838     long total_obj_size, total_obj_count,
2839          total_mon_size, total_mon_count,
2840          total_ovr_size, total_ovr_count,
2841          total_misc_size, total_misc_count;
2842 
2843     win = create_nhwindow(NHW_TEXT);
2844     putstr(win, 0, "Current memory statistics:");
2845 
2846     total_obj_count = total_obj_size = 0L;
2847     putstr(win, 0, stats_hdr);
2848     Sprintf(buf, "  Objects, base size %ld", (long) sizeof (struct obj));
2849     putstr(win, 0, buf);
2850     obj_chain(win, "invent", g.invent, TRUE,
2851               &total_obj_count, &total_obj_size);
2852     obj_chain(win, "fobj", fobj, TRUE, &total_obj_count, &total_obj_size);
2853     obj_chain(win, "buried", g.level.buriedobjlist, FALSE,
2854               &total_obj_count, &total_obj_size);
2855     obj_chain(win, "migrating obj", g.migrating_objs, FALSE,
2856               &total_obj_count, &total_obj_size);
2857     obj_chain(win, "billobjs", g.billobjs, FALSE,
2858               &total_obj_count, &total_obj_size);
2859     mon_invent_chain(win, "minvent", fmon, &total_obj_count, &total_obj_size);
2860     mon_invent_chain(win, "migrating minvent", g.migrating_mons,
2861                      &total_obj_count, &total_obj_size);
2862     contained_stats(win, "contained", &total_obj_count, &total_obj_size);
2863     putstr(win, 0, stats_sep);
2864     Sprintf(buf, template, "  Obj total", total_obj_count, total_obj_size);
2865     putstr(win, 0, buf);
2866 
2867     total_mon_count = total_mon_size = 0L;
2868     putstr(win, 0, "");
2869     Sprintf(buf, "  Monsters, base size %ld", (long) sizeof (struct monst));
2870     putstr(win, 0, buf);
2871     mon_chain(win, "fmon", fmon, TRUE, &total_mon_count, &total_mon_size);
2872     mon_chain(win, "migrating", g.migrating_mons, FALSE,
2873               &total_mon_count, &total_mon_size);
2874     /* 'g.mydogs' is only valid during level change or end of game disclosure,
2875        but conceivably we've been called from within debugger at such time */
2876     if (g.mydogs) /* monsters accompanying hero */
2877         mon_chain(win, "mydogs", g.mydogs, FALSE,
2878                   &total_mon_count, &total_mon_size);
2879     putstr(win, 0, stats_sep);
2880     Sprintf(buf, template, "  Mon total", total_mon_count, total_mon_size);
2881     putstr(win, 0, buf);
2882 
2883     total_ovr_count = total_ovr_size = 0L;
2884     putstr(win, 0, "");
2885     putstr(win, 0, "  Overview");
2886     overview_stats(win, template, &total_ovr_count, &total_ovr_size);
2887     putstr(win, 0, stats_sep);
2888     Sprintf(buf, template, "  Over total", total_ovr_count, total_ovr_size);
2889     putstr(win, 0, buf);
2890 
2891     total_misc_count = total_misc_size = 0L;
2892     putstr(win, 0, "");
2893     putstr(win, 0, "  Miscellaneous");
2894     misc_stats(win, &total_misc_count, &total_misc_size);
2895     putstr(win, 0, stats_sep);
2896     Sprintf(buf, template, "  Misc total", total_misc_count, total_misc_size);
2897     putstr(win, 0, buf);
2898 
2899     putstr(win, 0, "");
2900     putstr(win, 0, stats_sep);
2901     Sprintf(buf, template, "  Grand total",
2902             (total_obj_count + total_mon_count
2903              + total_ovr_count + total_misc_count),
2904             (total_obj_size + total_mon_size
2905              + total_ovr_size + total_misc_size));
2906     putstr(win, 0, buf);
2907 
2908 #if defined(__BORLANDC__) && !defined(_WIN32)
2909     show_borlandc_stats(win);
2910 #endif
2911 
2912     display_nhwindow(win, FALSE);
2913     destroy_nhwindow(win);
2914     return 0;
2915 }
2916 
2917 RESTORE_WARNING_FORMAT_NONLITERAL
2918 
2919 void
sanity_check(void)2920 sanity_check(void)
2921 {
2922     (void) check_invent_gold("invent");
2923     obj_sanity_check();
2924     timer_sanity_check();
2925     mon_sanity_check();
2926     light_sources_sanity_check();
2927     bc_sanity_check();
2928 }
2929 
2930 #ifdef DEBUG_MIGRATING_MONS
2931 static int
wiz_migrate_mons(void)2932 wiz_migrate_mons(void)
2933 {
2934     int mcount = 0;
2935     char inbuf[BUFSZ] = DUMMY;
2936     struct permonst *ptr;
2937     struct monst *mtmp;
2938     d_level tolevel;
2939 
2940     getlin("How many random monsters to migrate? [0]", inbuf);
2941     if (*inbuf == '\033')
2942         return 0;
2943     mcount = atoi(inbuf);
2944     if (mcount < 0 || mcount > (COLNO * ROWNO) || Is_botlevel(&u.uz))
2945         return 0;
2946     while (mcount > 0) {
2947         if (Is_stronghold(&u.uz))
2948             assign_level(&tolevel, &valley_level);
2949         else
2950             get_level(&tolevel, depth(&u.uz) + 1);
2951         ptr = rndmonst();
2952         mtmp = makemon(ptr, 0, 0, NO_MM_FLAGS);
2953         if (mtmp)
2954             migrate_to_level(mtmp, ledger_no(&tolevel), MIGR_RANDOM,
2955                              (coord *) 0);
2956         mcount--;
2957     }
2958     return 0;
2959 }
2960 #endif
2961 
2962 #ifndef DOAGAIN /* might have gotten undefined in config.h */
2963 #define DOAGAIN '\0' /* undefined => 0, '\0' => no key to activate redo */
2964 #endif
2965 
2966 static struct {
2967     int nhkf;
2968     uchar key;
2969     const char *name;
2970 } const spkeys_binds[] = {
2971     { NHKF_ESC,              '\033', (char *) 0 }, /* no binding */
2972     { NHKF_DOAGAIN,          DOAGAIN, "repeat" },
2973     { NHKF_REQMENU,          'm', "reqmenu" },
2974     { NHKF_RUN,              'G', "run" },
2975     { NHKF_RUN2,             '5', "run.numpad" },
2976     { NHKF_RUSH,             'g', "rush" },
2977     { NHKF_RUSH2,            M('5'), "rush.numpad" },
2978     { NHKF_FIGHT,            'F', "fight" },
2979     { NHKF_FIGHT2,           '-', "fight.numpad" },
2980     { NHKF_NOPICKUP,         'm', "nopickup" },
2981     { NHKF_RUN_NOPICKUP,     'M', "run.nopickup" },
2982     { NHKF_DOINV,            '0', "doinv" },
2983     { NHKF_TRAVEL,           CMD_TRAVEL, (char *) 0 }, /* no binding */
2984     { NHKF_CLICKLOOK,        CMD_CLICKLOOK, (char *) 0 }, /* no binding */
2985     { NHKF_REDRAW,           C('r'), "redraw" },
2986     { NHKF_REDRAW2,          C('l'), "redraw.numpad" },
2987     { NHKF_GETDIR_SELF,      '.', "getdir.self" },
2988     { NHKF_GETDIR_SELF2,     's', "getdir.self2" },
2989     { NHKF_GETDIR_HELP,      '?', "getdir.help" },
2990     { NHKF_COUNT,            'n', "count" },
2991     { NHKF_GETPOS_SELF,      '@', "getpos.self" },
2992     { NHKF_GETPOS_PICK,      '.', "getpos.pick" },
2993     { NHKF_GETPOS_PICK_Q,    ',', "getpos.pick.quick" },
2994     { NHKF_GETPOS_PICK_O,    ';', "getpos.pick.once" },
2995     { NHKF_GETPOS_PICK_V,    ':', "getpos.pick.verbose" },
2996     { NHKF_GETPOS_SHOWVALID, '$', "getpos.valid" },
2997     { NHKF_GETPOS_AUTODESC,  '#', "getpos.autodescribe" },
2998     { NHKF_GETPOS_MON_NEXT,  'm', "getpos.mon.next" },
2999     { NHKF_GETPOS_MON_PREV,  'M', "getpos.mon.prev" },
3000     { NHKF_GETPOS_OBJ_NEXT,  'o', "getpos.obj.next" },
3001     { NHKF_GETPOS_OBJ_PREV,  'O', "getpos.obj.prev" },
3002     { NHKF_GETPOS_DOOR_NEXT, 'd', "getpos.door.next" },
3003     { NHKF_GETPOS_DOOR_PREV, 'D', "getpos.door.prev" },
3004     { NHKF_GETPOS_UNEX_NEXT, 'x', "getpos.unexplored.next" },
3005     { NHKF_GETPOS_UNEX_PREV, 'X', "getpos.unexplored.prev" },
3006     { NHKF_GETPOS_VALID_NEXT, 'z', "getpos.valid.next" },
3007     { NHKF_GETPOS_VALID_PREV, 'Z', "getpos.valid.prev" },
3008     { NHKF_GETPOS_INTERESTING_NEXT, 'a', "getpos.all.next" },
3009     { NHKF_GETPOS_INTERESTING_PREV, 'A', "getpos.all.prev" },
3010     { NHKF_GETPOS_HELP,      '?', "getpos.help" },
3011     { NHKF_GETPOS_LIMITVIEW, '"', "getpos.filter" },
3012     { NHKF_GETPOS_MOVESKIP,  '*', "getpos.moveskip" },
3013     { NHKF_GETPOS_MENU,      '!', "getpos.menu" }
3014 };
3015 
3016 boolean
bind_specialkey(uchar key,const char * command)3017 bind_specialkey(uchar key, const char *command)
3018 {
3019     int i;
3020 
3021     for (i = 0; i < SIZE(spkeys_binds); i++) {
3022         if (!spkeys_binds[i].name || strcmp(command, spkeys_binds[i].name))
3023             continue;
3024         g.Cmd.spkeys[spkeys_binds[i].nhkf] = key;
3025         return TRUE;
3026     }
3027     return FALSE;
3028 }
3029 
3030 static const char *
spkey_name(int nhkf)3031 spkey_name(int nhkf)
3032 {
3033     const char *name = 0;
3034     int i;
3035 
3036     for (i = 0; i < SIZE(spkeys_binds); i++) {
3037         if (spkeys_binds[i].nhkf == nhkf) {
3038             name = (nhkf == NHKF_ESC) ? "escape" : spkeys_binds[i].name;
3039             break;
3040         }
3041     }
3042     return name;
3043 }
3044 
3045 /* returns the text for a one-byte encoding;
3046  * must be shorter than a tab for proper formatting */
3047 char *
key2txt(uchar c,char * txt)3048 key2txt(uchar c, char *txt) /* sufficiently long buffer */
3049 {
3050     /* should probably switch to "SPC", "ESC", "RET"
3051        since nethack's documentation uses ESC for <escape> */
3052     if (c == ' ')
3053         Sprintf(txt, "<space>");
3054     else if (c == '\033')
3055         Sprintf(txt, "<esc>"); /* "<escape>" won't fit */
3056     else if (c == '\n')
3057         Sprintf(txt, "<enter>"); /* "<return>" won't fit */
3058     else if (c == '\177')
3059         Sprintf(txt, "<del>"); /* "<delete>" won't fit */
3060     else
3061         Strcpy(txt, visctrl((char) c));
3062     return txt;
3063 }
3064 
3065 
3066 void
parseautocomplete(char * autocomplete,boolean condition)3067 parseautocomplete(char *autocomplete, boolean condition)
3068 {
3069     struct ext_func_tab *efp;
3070     register char *autoc;
3071 
3072     /* break off first autocomplete from the rest; parse the rest */
3073     if ((autoc = index(autocomplete, ',')) != 0
3074         || (autoc = index(autocomplete, ':')) != 0) {
3075         *autoc++ = '\0';
3076         parseautocomplete(autoc, condition);
3077     }
3078 
3079     /* strip leading and trailing white space */
3080     autocomplete = trimspaces(autocomplete);
3081 
3082     if (!*autocomplete)
3083         return;
3084 
3085     /* take off negation */
3086     if (*autocomplete == '!') {
3087         /* unlike most options, a leading "no" might actually be a part of
3088          * the extended command.  Thus you have to use ! */
3089         autocomplete++;
3090         autocomplete = trimspaces(autocomplete);
3091         condition = !condition;
3092     }
3093 
3094     /* find and modify the extended command */
3095     for (efp = extcmdlist; efp->ef_txt; efp++) {
3096         if (!strcmp(autocomplete, efp->ef_txt)) {
3097             if (condition)
3098                 efp->flags |= AUTOCOMPLETE;
3099             else
3100                 efp->flags &= ~AUTOCOMPLETE;
3101             return;
3102         }
3103     }
3104 
3105     /* not a real extended command */
3106     raw_printf("Bad autocomplete: invalid extended command '%s'.",
3107                autocomplete);
3108     wait_synch();
3109 }
3110 
3111 /* called at startup and after number_pad is twiddled */
3112 void
reset_commands(boolean initial)3113 reset_commands(boolean initial)
3114 {
3115     static const char sdir[] = "hykulnjb><",
3116                       sdir_swap_yz[] = "hzkulnjb><",
3117                       ndir[] = "47896321><",
3118                       ndir_phone_layout[] = "41236987><";
3119     static const int ylist[] = {
3120         'y', 'Y', C('y'), M('y'), M('Y'), M(C('y'))
3121     };
3122     static struct ext_func_tab *back_dir_cmd[8];
3123     static boolean backed_dir_cmd = FALSE;
3124     const struct ext_func_tab *cmdtmp;
3125     boolean flagtemp;
3126     int c, i, updated = 0;
3127 
3128     if (initial) {
3129         updated = 1;
3130         g.Cmd.num_pad = FALSE;
3131         g.Cmd.pcHack_compat = g.Cmd.phone_layout = g.Cmd.swap_yz = FALSE;
3132         for (i = 0; i < SIZE(spkeys_binds); i++)
3133             g.Cmd.spkeys[spkeys_binds[i].nhkf] = spkeys_binds[i].key;
3134         commands_init();
3135     } else {
3136         if (backed_dir_cmd) {
3137             for (i = 0; i < 8; i++) {
3138                 g.Cmd.commands[(uchar) g.Cmd.dirchars[i]] = back_dir_cmd[i];
3139             }
3140         }
3141 
3142         /* basic num_pad */
3143         flagtemp = iflags.num_pad;
3144         if (flagtemp != g.Cmd.num_pad) {
3145             g.Cmd.num_pad = flagtemp;
3146             ++updated;
3147         }
3148         /* swap_yz mode (only applicable for !num_pad); intended for
3149            QWERTZ keyboard used in Central Europe, particularly Germany */
3150         flagtemp = (iflags.num_pad_mode & 1) ? !g.Cmd.num_pad : FALSE;
3151         if (flagtemp != g.Cmd.swap_yz) {
3152             g.Cmd.swap_yz = flagtemp;
3153             ++updated;
3154             /* FIXME? should Cmd.spkeys[] be scanned for y and/or z to swap?
3155                Cmd.swap_yz has been toggled;
3156                perform the swap (or reverse previous one) */
3157             for (i = 0; i < SIZE(ylist); i++) {
3158                 c = ylist[i] & 0xff;
3159                 cmdtmp = g.Cmd.commands[c];                /* tmp = [y] */
3160                 g.Cmd.commands[c] = g.Cmd.commands[c + 1]; /* [y] = [z] */
3161                 g.Cmd.commands[c + 1] = cmdtmp;            /* [z] = tmp */
3162             }
3163         }
3164         /* MSDOS compatibility mode (only applicable for num_pad) */
3165         flagtemp = (iflags.num_pad_mode & 1) ? g.Cmd.num_pad : FALSE;
3166         if (flagtemp != g.Cmd.pcHack_compat) {
3167             g.Cmd.pcHack_compat = flagtemp;
3168             ++updated;
3169             /* pcHack_compat has been toggled */
3170 #if 0
3171             c = M('5') & 0xff;
3172             cmdtmp = g.Cmd.commands['5'];
3173             g.Cmd.commands['5'] = g.Cmd.commands[c];
3174             g.Cmd.commands[c] = cmdtmp;
3175 #else
3176             c = g.Cmd.spkeys[NHKF_RUN2];
3177             g.Cmd.spkeys[NHKF_RUN2] = g.Cmd.spkeys[NHKF_RUSH2];
3178             g.Cmd.spkeys[NHKF_RUSH2] = c;
3179 #endif
3180             /* FIXME: NHKF_DOINV2 ought to be implemented instead of this */
3181             c = M('0') & 0xff;
3182             g.Cmd.commands[c] = g.Cmd.pcHack_compat ? g.Cmd.commands['I'] : 0;
3183         }
3184         /* phone keypad layout (only applicable for num_pad) */
3185         flagtemp = (iflags.num_pad_mode & 2) ? g.Cmd.num_pad : FALSE;
3186         if (flagtemp != g.Cmd.phone_layout) {
3187             g.Cmd.phone_layout = flagtemp;
3188             ++updated;
3189             /* phone_layout has been toggled */
3190             for (i = 0; i < 3; i++) {
3191                 c = '1' + i;             /* 1,2,3 <-> 7,8,9 */
3192                 cmdtmp = g.Cmd.commands[c];              /* tmp = [1] */
3193                 g.Cmd.commands[c] = g.Cmd.commands[c + 6]; /* [1] = [7] */
3194                 g.Cmd.commands[c + 6] = cmdtmp;          /* [7] = tmp */
3195                 c = (M('1') & 0xff) + i; /* M-1,M-2,M-3 <-> M-7,M-8,M-9 */
3196                 cmdtmp = g.Cmd.commands[c];              /* tmp = [M-1] */
3197                 g.Cmd.commands[c] = g.Cmd.commands[c + 6]; /* [M-1] = [M-7] */
3198                 g.Cmd.commands[c + 6] = cmdtmp;          /* [M-7] = tmp */
3199             }
3200         }
3201     } /*?initial*/
3202 
3203     if (updated)
3204         g.Cmd.serialno++;
3205     g.Cmd.dirchars = !g.Cmd.num_pad
3206                        ? (!g.Cmd.swap_yz ? sdir : sdir_swap_yz)
3207                        : (!g.Cmd.phone_layout ? ndir : ndir_phone_layout);
3208     g.Cmd.alphadirchars = !g.Cmd.num_pad ? g.Cmd.dirchars : sdir;
3209 
3210     g.Cmd.move_W = g.Cmd.dirchars[0];
3211     g.Cmd.move_NW = g.Cmd.dirchars[1];
3212     g.Cmd.move_N = g.Cmd.dirchars[2];
3213     g.Cmd.move_NE = g.Cmd.dirchars[3];
3214     g.Cmd.move_E = g.Cmd.dirchars[4];
3215     g.Cmd.move_SE = g.Cmd.dirchars[5];
3216     g.Cmd.move_S = g.Cmd.dirchars[6];
3217     g.Cmd.move_SW = g.Cmd.dirchars[7];
3218 
3219     if (!initial) {
3220         for (i = 0; i < 8; i++) {
3221             uchar di = (uchar) g.Cmd.dirchars[i];
3222 
3223             back_dir_cmd[i] = (struct ext_func_tab *) g.Cmd.commands[di];
3224             g.Cmd.commands[di] = (struct ext_func_tab *) 0;
3225         }
3226         backed_dir_cmd = TRUE;
3227         for (i = 0; i < 8; i++)
3228             (void) bind_key(g.Cmd.dirchars[i], "nothing");
3229     }
3230 }
3231 
3232 /* non-movement commands which accept 'm' prefix to request menu operation */
3233 static boolean
accept_menu_prefix(int (* cmd_func)(void))3234 accept_menu_prefix(int (*cmd_func)(void))
3235 {
3236     if (cmd_func == dopickup || cmd_func == dotip
3237         /* eat, #offer, and apply tinning-kit all use floorfood() to pick
3238            an item on floor or in invent; 'm' skips picking from floor
3239            (ie, inventory only) rather than request use of menu operation */
3240         || cmd_func == doeat || cmd_func == dosacrifice || cmd_func == doapply
3241         /* 'm' for removing saddle from adjacent monster without checking
3242            for containers at <u.ux,u.uy> */
3243         || cmd_func == doloot
3244         /* offer menu to choose discoveries sort order */
3245         || cmd_func == dodiscovered || cmd_func == doclassdisco
3246         /* travel: pop up a menu of interesting targets in view */
3247         || cmd_func == dotravel
3248         /* wait and search: allow even if next to a hostile monster */
3249         || cmd_func == donull || cmd_func == dosearch
3250         /* wizard mode ^V and ^T */
3251         || cmd_func == wiz_level_tele || cmd_func == dotelecmd
3252         /* 'm' prefix allowed for some extended commands */
3253         || cmd_func == doextcmd || cmd_func == doextlist)
3254         return TRUE;
3255     return FALSE;
3256 }
3257 
3258 void
fuz_log(const char * msg)3259 fuz_log(const char *msg)
3260 {
3261     fuzzer_log_idx = (fuzzer_log_idx + 1) % FUZZER_LOG_SIZE;
3262     if (fuzzer_log[fuzzer_log_idx])
3263         free(fuzzer_log[fuzzer_log_idx]);
3264     fuzzer_log[fuzzer_log_idx] = dupstr(msg);
3265 }
3266 
3267 char
randomkey(void)3268 randomkey(void)
3269 {
3270     static unsigned i = 0;
3271     char c;
3272 
3273     switch (rn2(16)) {
3274     default:
3275         c = '\033';
3276         break;
3277     case 0:
3278         c = '\n';
3279         break;
3280     case 1:
3281     case 2:
3282     case 3:
3283     case 4:
3284         c = (char) rn1('~' - ' ' + 1, ' ');
3285         break;
3286     case 5:
3287         c = (char) (rn2(2) ? '\t' : ' ');
3288         break;
3289     case 6:
3290         c = (char) rn1('z' - 'a' + 1, 'a');
3291         break;
3292     case 7:
3293         c = (char) rn1('Z' - 'A' + 1, 'A');
3294         break;
3295     case 8:
3296         c = extcmdlist[i++ % SIZE(extcmdlist)].key;
3297         break;
3298     case 9:
3299         c = '#';
3300         break;
3301     case 10:
3302     case 11:
3303     case 12:
3304         c = g.Cmd.dirchars[rn2(8)];
3305         if (!rn2(7))
3306             c = !g.Cmd.num_pad ? (!rn2(3) ? C(c) : (c + 'A' - 'a')) : M(c);
3307         break;
3308     case 13:
3309         c = (char) rn1('9' - '0' + 1, '0');
3310         break;
3311     case 14:
3312         c = (char) rn2(iflags.wc_eight_bit_input ? 256 : 128);
3313         break;
3314     }
3315 
3316     return c;
3317 }
3318 
3319 void
random_response(char * buf,int sz)3320 random_response(char *buf, int sz)
3321 {
3322     char c;
3323     int count = 0;
3324 
3325     for (;;) {
3326         c = randomkey();
3327         if (c == '\n')
3328             break;
3329         if (c == '\033') {
3330             count = 0;
3331             break;
3332         }
3333         if (count < sz - 1)
3334             buf[count++] = c;
3335     }
3336     buf[count] = '\0';
3337 }
3338 
3339 int
rnd_extcmd_idx(void)3340 rnd_extcmd_idx(void)
3341 {
3342     return rn2(extcmdlist_length + 1) - 1;
3343 }
3344 
3345 static int
ch2spkeys(char c,int start,int end)3346 ch2spkeys(char c, int start, int end)
3347 {
3348     int i;
3349 
3350     for (i = start; i <= end; i++)
3351         if (g.Cmd.spkeys[i] == c)
3352             return i;
3353     return NHKF_ESC;
3354 }
3355 
3356 void
rhack(char * cmd)3357 rhack(char *cmd)
3358 {
3359     int spkey;
3360     boolean prefix_seen, bad_command,
3361         firsttime = (cmd == 0);
3362 
3363     iflags.menu_requested = FALSE;
3364 #ifdef SAFERHANGUP
3365     if (g.program_state.done_hup)
3366         end_of_input();
3367 #endif
3368     if (firsttime) {
3369         g.context.nopick = 0;
3370         cmd = parse();
3371         {
3372             char buf[BUFSZ];
3373             Sprintf(buf, "rhack() cmd: '%s'", visctrl(*cmd));
3374             FUZLOG(buf);
3375         }
3376     }
3377     if (*cmd == g.Cmd.spkeys[NHKF_ESC]) {
3378         /* a clever player might try to press Escape to escape from a
3379          * monster... */
3380         if (u.ustuck && !sticks(g.youmonst.data)) {
3381             pline("You cannot escape from %s!", mon_nam(u.ustuck));
3382         }
3383         g.context.move = FALSE;
3384         return;
3385     }
3386     /* DOAGAIN might be '\0'; if so, don't execute it even if *cmd is too */
3387     if ((*cmd && *cmd == g.Cmd.spkeys[NHKF_DOAGAIN])
3388         && !g.in_doagain && g.saveq[0]) {
3389         g.in_doagain = TRUE;
3390         g.stail = 0;
3391         rhack((char *) 0); /* read and execute command */
3392         g.in_doagain = FALSE;
3393         return;
3394     }
3395     /* Special case of *cmd == ' ' handled better below */
3396     if (!*cmd || *cmd == (char) 0377) {
3397         nhbell();
3398         g.context.move = FALSE;
3399         return; /* probably we just had an interrupt */
3400     }
3401 
3402     /* handle most movement commands */
3403     prefix_seen = FALSE;
3404     g.context.travel = g.context.travel1 = 0;
3405     spkey = ch2spkeys(*cmd, NHKF_RUN, NHKF_CLICKLOOK);
3406 
3407     switch (spkey) {
3408     case NHKF_RUSH2:
3409         if (!g.Cmd.num_pad)
3410             break;
3411         /*FALLTHRU*/
3412     case NHKF_RUSH:
3413         if (movecmd(cmd[1])) {
3414             g.context.run = 2;
3415             g.domove_attempting |= DOMOVE_RUSH;
3416         } else
3417             prefix_seen = TRUE;
3418         break;
3419     case NHKF_RUN2:
3420         if (!g.Cmd.num_pad)
3421             break;
3422         /*FALLTHRU*/
3423     case NHKF_RUN:
3424         if (movecmd(lowc(cmd[1]))) {
3425             g.context.run = 3;
3426             g.domove_attempting |= DOMOVE_RUSH;
3427         } else
3428             prefix_seen = TRUE;
3429         break;
3430     case NHKF_FIGHT2:
3431         if (!g.Cmd.num_pad)
3432             break;
3433         /*FALLTHRU*/
3434     /* Effects of movement commands and invisible monsters:
3435      * m: always move onto space (even if 'I' remembered)
3436      * F: always attack space (even if 'I' not remembered)
3437      * normal movement: attack if 'I', move otherwise.
3438      */
3439     case NHKF_FIGHT:
3440         if (movecmd(cmd[1])) {
3441             g.context.forcefight = 1;
3442             g.domove_attempting |= DOMOVE_WALK;
3443         } else
3444             prefix_seen = TRUE;
3445         break;
3446     case NHKF_NOPICKUP:
3447         if (movecmd(cmd[1]) || u.dz) {
3448             g.context.run = 0;
3449             g.context.nopick = 1;
3450             if (!u.dz)
3451                 g.domove_attempting |= DOMOVE_WALK;
3452             else
3453                 cmd[0] = cmd[1]; /* "m<" or "m>" */
3454         } else
3455             prefix_seen = TRUE;
3456         break;
3457     case NHKF_RUN_NOPICKUP:
3458         if (movecmd(lowc(cmd[1]))) {
3459             g.context.run = 1;
3460             g.context.nopick = 1;
3461             g.domove_attempting |= DOMOVE_RUSH;
3462         } else
3463             prefix_seen = TRUE;
3464         break;
3465     case NHKF_DOINV:
3466         if (!g.Cmd.num_pad)
3467             break;
3468         (void) ddoinv(); /* a convenience borrowed from the PC */
3469         g.context.move = FALSE;
3470         g.multi = 0;
3471         return;
3472     case NHKF_CLICKLOOK:
3473         if (iflags.clicklook) {
3474             g.context.move = FALSE;
3475             do_look(2, &g.clicklook_cc);
3476         }
3477         return;
3478     case NHKF_TRAVEL:
3479         g.context.travel = 1;
3480         g.context.travel1 = 1;
3481         g.context.run = 8;
3482         g.context.nopick = 1;
3483         g.domove_attempting |= DOMOVE_RUSH;
3484         break;
3485     default:
3486         if (movecmd(*cmd)) { /* ordinary movement */
3487             g.context.run = 0; /* only matters here if it was 8 */
3488             g.domove_attempting |= DOMOVE_WALK;
3489         } else if (movecmd(g.Cmd.num_pad ? unmeta(*cmd) : lowc(*cmd))) {
3490             g.context.run = 1;
3491             g.domove_attempting |= DOMOVE_RUSH;
3492         } else if (movecmd(unctrl(*cmd))) {
3493             g.context.run = 3;
3494             g.domove_attempting |= DOMOVE_RUSH;
3495         }
3496         break;
3497     }
3498 
3499     /* after movement--if reqmenu duplicates a prefix, movement takes
3500        precedence; "request a menu" (default 'm') */
3501     if (cmd[0] == g.Cmd.spkeys[NHKF_REQMENU]) {
3502         /* (for func_tab cast, see below) */
3503         const struct ext_func_tab *ft = g.Cmd.commands[cmd[1] & 0xff];
3504         int (*func)(void) = ft ? ((struct ext_func_tab *) ft)->ef_funct : 0;
3505 
3506         if (func && accept_menu_prefix(func)) {
3507             iflags.menu_requested = TRUE;
3508             ++cmd;
3509             prefix_seen = FALSE;
3510         } else {
3511             prefix_seen = TRUE;
3512         }
3513     }
3514 
3515     if (((g.domove_attempting & (DOMOVE_RUSH | DOMOVE_WALK)) != 0L)
3516                             && !g.context.travel && !dxdy_moveok()) {
3517         /* trying to move diagonally as a grid bug;
3518            this used to be treated by movecmd() as not being
3519            a movement attempt, but that didn't provide for any
3520            feedback and led to strangeness if the key pressed
3521            ('u' in particular) was overloaded for num_pad use */
3522         You_cant("get there from here...");
3523         g.context.run = 0;
3524         g.context.nopick = g.context.forcefight = FALSE;
3525         g.context.move = g.context.mv = FALSE;
3526         g.multi = 0;
3527         return;
3528     }
3529 
3530     if ((g.domove_attempting & DOMOVE_WALK) != 0L) {
3531         if (g.multi)
3532             g.context.mv = TRUE;
3533         domove();
3534         g.context.forcefight = 0;
3535         return;
3536     } else if ((g.domove_attempting & DOMOVE_RUSH) != 0L) {
3537         if (firsttime) {
3538             if (!g.multi)
3539                 g.multi = max(COLNO, ROWNO);
3540             u.last_str_turn = 0;
3541         }
3542         g.context.mv = TRUE;
3543         domove();
3544         return;
3545     } else if (prefix_seen) {
3546         if (cmd[1] == g.Cmd.spkeys[NHKF_ESC]) {
3547         /* <prefix><escape> */
3548         /* don't report "unknown command" for change of heart... */
3549         bad_command = FALSE;
3550         } else { /* prefix followed by non-movement command */
3551             bad_command = TRUE; /* skip cmdlist[] loop */
3552         }
3553     } else if (*cmd == ' ' && !flags.rest_on_space) {
3554         bad_command = TRUE; /* skip cmdlist[] loop */
3555 
3556     /* handle all other commands */
3557     } else {
3558         register const struct ext_func_tab *tlist;
3559         int res, (*func)(void);
3560 
3561         /* current - use *cmd to directly index cmdlist array */
3562         if ((tlist = g.Cmd.commands[*cmd & 0xff]) != 0) {
3563             if (!wizard && (tlist->flags & WIZMODECMD)) {
3564                 You_cant("do that!");
3565                 res = 0;
3566             } else if (u.uburied && !(tlist->flags & IFBURIED)) {
3567                 You_cant("do that while you are buried!");
3568                 res = 0;
3569             } else {
3570                 /* we discard 'const' because some compilers seem to have
3571                    trouble with the pointer passed to set_occupation() */
3572                 func = ((struct ext_func_tab *) tlist)->ef_funct;
3573                 if (tlist->f_text && !g.occupation && g.multi)
3574                     set_occupation(func, tlist->f_text, g.multi);
3575                 {
3576                     char buf[BUFSZ];
3577                     Sprintf(buf, "rhack() extcmd: '%s'", tlist->ef_txt);
3578                     FUZLOG(buf);
3579                 }
3580                 res = (*func)(); /* perform the command */
3581             }
3582             if (!res) {
3583                 g.context.move = FALSE;
3584                 g.multi = 0;
3585             }
3586             return;
3587         }
3588         /* if we reach here, cmd wasn't found in cmdlist[] */
3589         bad_command = TRUE;
3590     }
3591 
3592     if (bad_command) {
3593         char expcmd[20]; /* we expect 'cmd' to point to 1 or 2 chars */
3594         char c, c1 = cmd[1];
3595 
3596         expcmd[0] = '\0';
3597         while ((c = *cmd++) != '\0')
3598             Strcat(expcmd, visctrl(c)); /* add 1..4 chars plus terminator */
3599 
3600         if (!prefix_seen || !help_dir(c1, spkey, "Invalid direction key!"))
3601             Norep("Unknown command '%s'.", expcmd);
3602     }
3603     /* didn't move */
3604     g.context.move = FALSE;
3605     g.multi = 0;
3606     return;
3607 }
3608 
3609 /* convert an x,y pair into a direction code */
3610 int
xytod(schar x,schar y)3611 xytod(schar x, schar y)
3612 {
3613     register int dd;
3614 
3615     for (dd = 0; dd < 8; dd++)
3616         if (x == xdir[dd] && y == ydir[dd])
3617             return dd;
3618     return -1;
3619 }
3620 
3621 /* convert a direction code into an x,y pair */
3622 void
dtoxy(coord * cc,int dd)3623 dtoxy(coord *cc, int dd)
3624 {
3625     cc->x = xdir[dd];
3626     cc->y = ydir[dd];
3627     return;
3628 }
3629 
3630 /* also sets u.dz, but returns false for <> */
3631 int
movecmd(char sym)3632 movecmd(char sym)
3633 {
3634     register const char *dp = index(g.Cmd.dirchars, sym);
3635 
3636     u.dz = 0;
3637     if (!dp || !*dp)
3638         return 0;
3639     u.dx = xdir[dp - g.Cmd.dirchars];
3640     u.dy = ydir[dp - g.Cmd.dirchars];
3641     u.dz = zdir[dp - g.Cmd.dirchars];
3642 #if 0 /* now handled elsewhere */
3643     if (u.dx && u.dy && NODIAG(u.umonnum)) {
3644         u.dx = u.dy = 0;
3645         return 0;
3646     }
3647 #endif
3648     return !u.dz;
3649 }
3650 
3651 /* grid bug handling which used to be in movecmd() */
3652 int
dxdy_moveok(void)3653 dxdy_moveok(void)
3654 {
3655     if (u.dx && u.dy && NODIAG(u.umonnum))
3656         u.dx = u.dy = 0;
3657     return u.dx || u.dy;
3658 }
3659 
3660 /* decide whether character (user input keystroke) requests screen repaint */
3661 boolean
redraw_cmd(char c)3662 redraw_cmd(char c)
3663 {
3664     return (boolean) (c == g.Cmd.spkeys[NHKF_REDRAW]
3665                       || (g.Cmd.num_pad && c == g.Cmd.spkeys[NHKF_REDRAW2]));
3666 }
3667 
3668 static boolean
prefix_cmd(char c)3669 prefix_cmd(char c)
3670 {
3671     return (c == g.Cmd.spkeys[NHKF_REQMENU]
3672             || c == g.Cmd.spkeys[NHKF_RUSH]
3673             || c == g.Cmd.spkeys[NHKF_RUN]
3674             || c == g.Cmd.spkeys[NHKF_NOPICKUP]
3675             || c == g.Cmd.spkeys[NHKF_RUN_NOPICKUP]
3676             || c == g.Cmd.spkeys[NHKF_FIGHT]
3677             || (g.Cmd.num_pad && (c == g.Cmd.spkeys[NHKF_RUN2]
3678                                   || c == g.Cmd.spkeys[NHKF_RUSH2]
3679                                   || c == g.Cmd.spkeys[NHKF_FIGHT2])));
3680 }
3681 
3682 /*
3683  * uses getdir() but unlike getdir() it specifically
3684  * produces coordinates using the direction from getdir()
3685  * and verifies that those coordinates are ok.
3686  *
3687  * If the call to getdir() returns 0, Never_mind is displayed.
3688  * If the resulting coordinates are not okay, emsg is displayed.
3689  *
3690  * Returns non-zero if coordinates in cc are valid.
3691  */
3692 int
get_adjacent_loc(const char * prompt,const char * emsg,xchar x,xchar y,coord * cc)3693 get_adjacent_loc(const char *prompt, const char *emsg,
3694                  xchar x, xchar y, coord *cc)
3695 {
3696     xchar new_x, new_y;
3697     if (!getdir(prompt)) {
3698         pline1(Never_mind);
3699         return 0;
3700     }
3701     new_x = x + u.dx;
3702     new_y = y + u.dy;
3703     if (cc && isok(new_x, new_y)) {
3704         cc->x = new_x;
3705         cc->y = new_y;
3706     } else {
3707         if (emsg)
3708             pline1(emsg);
3709         return 0;
3710     }
3711     return 1;
3712 }
3713 
3714 int
getdir(const char * s)3715 getdir(const char *s)
3716 {
3717     char dirsym;
3718     int is_mov;
3719 
3720  retry:
3721     if (g.in_doagain || *readchar_queue)
3722         dirsym = readchar();
3723     else
3724         dirsym = yn_function((s && *s != '^') ? s : "In what direction?",
3725                              (char *) 0, '\0');
3726     {
3727         char buf[BUFSZ];
3728         Sprintf(buf, "getdir() dirsym: '%s'", visctrl(dirsym));
3729         FUZLOG(buf);
3730     }
3731     /* remove the prompt string so caller won't have to */
3732     clear_nhwindow(WIN_MESSAGE);
3733 
3734     if (redraw_cmd(dirsym)) { /* ^R */
3735         docrt();              /* redraw */
3736         goto retry;
3737     }
3738     savech(dirsym);
3739 
3740     if (dirsym == g.Cmd.spkeys[NHKF_GETDIR_SELF]
3741         || dirsym == g.Cmd.spkeys[NHKF_GETDIR_SELF2]) {
3742         u.dx = u.dy = u.dz = 0;
3743     } else if (!(is_mov = movecmd(dirsym)) && !u.dz) {
3744         boolean did_help = FALSE, help_requested;
3745 
3746         if (!index(quitchars, dirsym)) {
3747             help_requested = (dirsym == g.Cmd.spkeys[NHKF_GETDIR_HELP]);
3748             if (help_requested || iflags.cmdassist) {
3749                 did_help = help_dir((s && *s == '^') ? dirsym : '\0',
3750                                     NHKF_ESC,
3751                                     help_requested ? (const char *) 0
3752                                                   : "Invalid direction key!");
3753                 if (help_requested)
3754                     goto retry;
3755             }
3756             if (!did_help)
3757                 pline("What a strange direction!");
3758         }
3759         return 0;
3760     } else if (is_mov && !dxdy_moveok()) {
3761         You_cant("orient yourself that direction.");
3762         return 0;
3763     }
3764     if (!u.dz && (Stunned || (Confusion && !rn2(5))))
3765         confdir();
3766     return 1;
3767 }
3768 
3769 static void
show_direction_keys(winid win,char centerchar,boolean nodiag)3770 show_direction_keys(winid win, /* should specify a window which is
3771                                 * using a fixed-width font... */
3772                     char centerchar, /* '.' or '@' or ' ' */
3773                     boolean nodiag)
3774 {
3775     char buf[BUFSZ];
3776 
3777     if (!centerchar)
3778         centerchar = ' ';
3779 
3780     if (nodiag) {
3781         Sprintf(buf, "             %c   ", g.Cmd.move_N);
3782         putstr(win, 0, buf);
3783         putstr(win, 0, "             |   ");
3784         Sprintf(buf, "          %c- %c -%c",
3785                 g.Cmd.move_W, centerchar, g.Cmd.move_E);
3786         putstr(win, 0, buf);
3787         putstr(win, 0, "             |   ");
3788         Sprintf(buf, "             %c   ", g.Cmd.move_S);
3789         putstr(win, 0, buf);
3790     } else {
3791         Sprintf(buf, "          %c  %c  %c",
3792                 g.Cmd.move_NW, g.Cmd.move_N, g.Cmd.move_NE);
3793         putstr(win, 0, buf);
3794         putstr(win, 0, "           \\ | / ");
3795         Sprintf(buf, "          %c- %c -%c",
3796                 g.Cmd.move_W, centerchar, g.Cmd.move_E);
3797         putstr(win, 0, buf);
3798         putstr(win, 0, "           / | \\ ");
3799         Sprintf(buf, "          %c  %c  %c",
3800                 g.Cmd.move_SW, g.Cmd.move_S, g.Cmd.move_SE);
3801         putstr(win, 0, buf);
3802     };
3803 }
3804 
3805 /* explain choices if player has asked for getdir() help or has given
3806    an invalid direction after a prefix key ('F', 'g', 'm', &c), which
3807    might be bogus but could be up, down, or self when not applicable */
3808 static boolean
help_dir(char sym,int spkey,const char * msg)3809 help_dir(char sym,
3810          int spkey, /* NHKF_ code for prefix key, if used, or for ESC */
3811          const char *msg)
3812 {
3813     static const char wiz_only_list[] = "EFGIVW";
3814     char ctrl;
3815     winid win;
3816     char buf[BUFSZ], buf2[BUFSZ], *explain;
3817     const char *dothat, *how;
3818     boolean prefixhandling, viawindow;
3819 
3820     /* NHKF_ESC indicates that player asked for help at getdir prompt */
3821     viawindow = (spkey == NHKF_ESC || iflags.cmdassist);
3822     prefixhandling = (spkey != NHKF_ESC);
3823     /*
3824      * Handling for prefix keys that don't want special directions.
3825      * Delivered via pline if 'cmdassist' is off, or instead of the
3826      * general message if it's on.
3827      */
3828     dothat = "do that";
3829     how = " at"; /* for "<action> at yourself"; not used for up/down */
3830     switch (spkey) {
3831     case NHKF_NOPICKUP:
3832         dothat = "move";
3833         break;
3834     case NHKF_RUSH2:
3835         if (!g.Cmd.num_pad)
3836             break;
3837         /*FALLTHRU*/
3838     case NHKF_RUSH:
3839         dothat = "rush";
3840         break;
3841     case NHKF_RUN2:
3842         if (!g.Cmd.num_pad)
3843             break;
3844         /*FALLTHRU*/
3845     case NHKF_RUN:
3846     case NHKF_RUN_NOPICKUP:
3847         dothat = "run";
3848         break;
3849     case NHKF_FIGHT2:
3850         if (!g.Cmd.num_pad)
3851             break;
3852         /*FALLTHRU*/
3853     case NHKF_FIGHT:
3854         dothat = "fight";
3855         how = ""; /* avoid "fight at yourself" */
3856         break;
3857     default:
3858         prefixhandling = FALSE;
3859         break;
3860     }
3861 
3862     buf[0] = '\0';
3863     /* for movement prefix followed by '.' or (numpad && 's') to mean 'self';
3864        note: '-' for hands (inventory form of 'self') is not handled here */
3865     if (prefixhandling
3866         && (sym == g.Cmd.spkeys[NHKF_GETDIR_SELF]
3867             || (g.Cmd.num_pad && sym == g.Cmd.spkeys[NHKF_GETDIR_SELF2]))) {
3868         Sprintf(buf, "You can't %s%s yourself.", dothat, how);
3869     /* for movement prefix followed by up or down */
3870     } else if (prefixhandling && (sym == '<' || sym == '>')) {
3871         Sprintf(buf, "You can't %s %s.", dothat,
3872                 /* was "upwards" and "downwards", but they're considered
3873                    to be variants of canonical "upward" and "downward" */
3874                 (sym == '<') ? "upward" : "downward");
3875     }
3876 
3877     /* if '!cmdassist', display via pline() and we're done (note: asking
3878        for help at getdir() prompt forces cmdassist for this operation) */
3879     if (!viawindow) {
3880         if (prefixhandling) {
3881             if (!*buf)
3882                 Sprintf(buf, "Invalid direction for '%s' prefix.",
3883                         visctrl(g.Cmd.spkeys[spkey]));
3884             pline("%s", buf);
3885             return TRUE;
3886         }
3887         /* when 'cmdassist' is off and caller doesn't insist, do nothing */
3888         return FALSE;
3889     }
3890 
3891     win = create_nhwindow(NHW_TEXT);
3892     if (!win)
3893         return FALSE;
3894 
3895     if (*buf) {
3896         /* show bad-prefix message instead of general invalid-direction one */
3897         putstr(win, 0, buf);
3898         putstr(win, 0, "");
3899     } else if (msg) {
3900         Sprintf(buf, "cmdassist: %s", msg);
3901         putstr(win, 0, buf);
3902         putstr(win, 0, "");
3903     }
3904 
3905     if (!prefixhandling && (letter(sym) || sym == '[')) {
3906         /* '[': old 'cmdhelp' showed ESC as ^[ */
3907         sym = highc(sym); /* @A-Z[ (note: letter() accepts '@') */
3908         ctrl = (sym - 'A') + 1; /* 0-27 (note: 28-31 aren't applicable) */
3909         if ((explain = dowhatdoes_core(ctrl, buf2)) != 0
3910             && (!index(wiz_only_list, sym) || wizard)) {
3911             Sprintf(buf, "Are you trying to use ^%c%s?", sym,
3912                     index(wiz_only_list, sym) ? ""
3913                         : " as specified in the Guidebook");
3914             putstr(win, 0, buf);
3915             putstr(win, 0, "");
3916             putstr(win, 0, explain);
3917             putstr(win, 0, "");
3918             putstr(win, 0,
3919                   "To use that command, hold down the <Ctrl> key as a shift");
3920             Sprintf(buf, "and press the <%c> key.", sym);
3921             putstr(win, 0, buf);
3922             putstr(win, 0, "");
3923         }
3924     }
3925 
3926     Sprintf(buf, "Valid direction keys%s%s%s are:",
3927             prefixhandling ? " to " : "", prefixhandling ? dothat : "",
3928             NODIAG(u.umonnum) ? " in your current form" : "");
3929     putstr(win, 0, buf);
3930     show_direction_keys(win, !prefixhandling ? '.' : ' ', NODIAG(u.umonnum));
3931 
3932     if (!prefixhandling || spkey == NHKF_NOPICKUP) {
3933         /* NOPICKUP: unlike the other prefix keys, 'm' allows up/down for
3934            stair traversal; we won't get here when "m<" or "m>" has been
3935            given but we include up and down for 'm'+invalid_direction;
3936            self is excluded as a viable direction for every prefix */
3937         putstr(win, 0, "");
3938         putstr(win, 0, "          <  up");
3939         putstr(win, 0, "          >  down");
3940         if (!prefixhandling) {
3941             int selfi = g.Cmd.num_pad ? NHKF_GETDIR_SELF2 : NHKF_GETDIR_SELF;
3942 
3943             Sprintf(buf,   "       %4s  direct at yourself",
3944                     visctrl(g.Cmd.spkeys[selfi]));
3945             putstr(win, 0, buf);
3946         }
3947     }
3948 
3949     if (msg) {
3950         /* non-null msg means that this wasn't an explicit user request */
3951         putstr(win, 0, "");
3952         putstr(win, 0,
3953                "(Suppress this message with !cmdassist in config file.)");
3954     }
3955     display_nhwindow(win, FALSE);
3956     destroy_nhwindow(win);
3957     return TRUE;
3958 }
3959 
3960 void
confdir(void)3961 confdir(void)
3962 {
3963     register int x = NODIAG(u.umonnum) ? 2 * rn2(4) : rn2(8);
3964 
3965     u.dx = xdir[x];
3966     u.dy = ydir[x];
3967     return;
3968 }
3969 
3970 const char *
directionname(int dir)3971 directionname(int dir)
3972 {
3973     static NEARDATA const char *const dirnames[] = {
3974         "west",      "northwest", "north",     "northeast", "east",
3975         "southeast", "south",     "southwest", "down",      "up",
3976     };
3977 
3978     if (dir < 0 || dir >= SIZE(dirnames))
3979         return "invalid";
3980     return dirnames[dir];
3981 }
3982 
3983 int
isok(register int x,register int y)3984 isok(register int x, register int y)
3985 {
3986     /* x corresponds to curx, so x==1 is the first column. Ach. %% */
3987     return x >= 1 && x <= COLNO - 1 && y >= 0 && y <= ROWNO - 1;
3988 }
3989 
3990 /* #herecmdmenu command */
3991 static int
doherecmdmenu(void)3992 doherecmdmenu(void)
3993 {
3994     char ch = here_cmd_menu(TRUE);
3995 
3996     return ch ? 1 : 0;
3997 }
3998 
3999 /* #therecmdmenu command, a way to test there_cmd_menu without mouse */
4000 static int
dotherecmdmenu(void)4001 dotherecmdmenu(void)
4002 {
4003     char ch;
4004 
4005     if (!getdir((const char *) 0) || !isok(u.ux + u.dx, u.uy + u.dy))
4006         return 0;
4007 
4008     if (u.dx || u.dy)
4009         ch = there_cmd_menu(TRUE, u.ux + u.dx, u.uy + u.dy);
4010     else
4011         ch = here_cmd_menu(TRUE);
4012 
4013     return ch ? 1 : 0;
4014 }
4015 
4016 static void
add_herecmd_menuitem(winid win,int (* func)(void),const char * text)4017 add_herecmd_menuitem(winid win, int (*func)(void), const char *text)
4018 {
4019     char ch;
4020     anything any;
4021 
4022     if ((ch = cmd_from_func(func)) != '\0') {
4023         any = cg.zeroany;
4024         any.a_nfunc = func;
4025         add_menu(win, &nul_glyphinfo, &any, 0, 0, ATR_NONE, text,
4026                  MENU_ITEMFLAGS_NONE);
4027     }
4028 }
4029 
4030 /* offer choice of actions to perform at adjacent location <x,y>;
4031    does not work as intended because the actions that get invoked
4032    ask for a direction or target instead of using our <x,y> */
4033 static char
there_cmd_menu(boolean doit,int x,int y)4034 there_cmd_menu(boolean doit, int x, int y)
4035 {
4036     winid win;
4037     char ch;
4038     char buf[BUFSZ];
4039     schar typ = levl[x][y].typ;
4040     int npick, K = 0;
4041     menu_item *picks = (menu_item *) 0;
4042     struct trap *ttmp;
4043     struct monst *mtmp;
4044 
4045     win = create_nhwindow(NHW_MENU);
4046     start_menu(win, MENU_BEHAVE_STANDARD);
4047 
4048     if (IS_DOOR(typ)) {
4049         boolean key_or_pick, card;
4050 
4051         if (door_is_closed(&levl[x][y])) {
4052             add_herecmd_menuitem(win, doopen, "Open the door"), ++K;
4053             /* unfortunately there's no lknown flag for doors to
4054                remember the locked/unlocked state */
4055             key_or_pick = (carrying(SKELETON_KEY) || carrying(LOCK_PICK));
4056             card = (carrying(CREDIT_CARD) != 0);
4057             if (key_or_pick || card) {
4058                 Sprintf(buf, "%sunlock the door",
4059                         key_or_pick ? "lock or " : "");
4060                 add_herecmd_menuitem(win, doapply, upstart(buf)), ++K;
4061             }
4062             /* unfortunately there's no tknown flag for doors (or chests)
4063                to remember whether a trap had been found */
4064             add_herecmd_menuitem(win, dountrap,
4065                                  "Search the door for a trap"), ++K;
4066             /* [what about #force?] */
4067             add_herecmd_menuitem(win, dokick, "Kick the door"), ++K;
4068         } else if (doorstate(&levl[x][y]) == D_ISOPEN) {
4069             add_herecmd_menuitem(win, doclose, "Close the door"), ++K;
4070         }
4071     }
4072 
4073     if (typ <= SCORR)
4074         add_herecmd_menuitem(win, dosearch, "Search for secret doors"), ++K;
4075 
4076     if ((ttmp = t_at(x, y)) != 0 && ttmp->tseen) {
4077         add_herecmd_menuitem(win, doidtrap, "Examine trap"), ++K;
4078         if (ttmp->ttyp != VIBRATING_SQUARE)
4079             add_herecmd_menuitem(win, dountrap,
4080                                  "Attempt to disarm trap"), ++K;
4081     }
4082 
4083     mtmp = m_at(x, y);
4084     if (mtmp && !canspotmon(mtmp))
4085         mtmp = 0;
4086     if (mtmp && which_armor(mtmp, W_SADDLE)) {
4087         char *mnam = x_monnam(mtmp, ARTICLE_THE, (char *) 0,
4088                               SUPPRESS_SADDLE, FALSE);
4089 
4090         if (!u.usteed) {
4091             Sprintf(buf, "Ride %s", mnam);
4092             add_herecmd_menuitem(win, doride, buf), ++K;
4093         }
4094         Sprintf(buf, "Remove saddle from %s", mnam);
4095         add_herecmd_menuitem(win, doloot, buf), ++K;
4096     }
4097     if (mtmp && can_saddle(mtmp) && !which_armor(mtmp, W_SADDLE)
4098         && carrying(SADDLE)) {
4099         Sprintf(buf, "Put saddle on %s", mon_nam(mtmp)), ++K;
4100         add_herecmd_menuitem(win, doapply, buf);
4101     }
4102 #if 0
4103     if (mtmp) {
4104         Sprintf(buf, "%s %s", mon_nam(mtmp),
4105                 !has_mname(mtmp) ? "Name" : "Rename"), ++K;
4106         /* need a way to pass mtmp or <ux+dx,uy+dy>to an 'int f()' function
4107            as well as reorganizinging do_mname() to use that function */
4108         add_herecmd_menuitem(win, XXX(), buf);
4109     }
4110 #endif
4111 #if 0
4112     /* these are necessary if click_to_cmd() is deferring to us; however,
4113        moving/fighting aren't implmented as independent commands so don't
4114        fit our menu's use of command functions */
4115     if (mtmp || glyph_is_invisible(glyph_at(x, y))) {
4116         /* "Attack %s", mtmp ? mon_nam(mtmp) : "unseen creature" */
4117     } else {
4118         /* "Move %s", direction */
4119     }
4120 #endif
4121 
4122     if (K) {
4123         end_menu(win, "What do you want to do?");
4124         npick = select_menu(win, PICK_ONE, &picks);
4125     } else {
4126         pline("No applicable actions.");
4127         npick = 0;
4128     }
4129     destroy_nhwindow(win);
4130     ch = '\033';
4131     if (npick > 0) {
4132         int (*func)(void) = picks->item.a_nfunc;
4133         free((genericptr_t) picks);
4134 
4135         if (doit) {
4136             int ret = (*func)();
4137 
4138             ch = (char) ret;
4139         } else {
4140             ch = cmd_from_func(func);
4141         }
4142     }
4143     return ch;
4144 }
4145 
4146 static char
here_cmd_menu(boolean doit)4147 here_cmd_menu(boolean doit)
4148 {
4149     winid win;
4150     char ch;
4151     char buf[BUFSZ];
4152     schar typ = levl[u.ux][u.uy].typ;
4153     int npick;
4154     menu_item *picks = (menu_item *) 0;
4155     stairway *stway = stairway_at(u.ux, u.uy);
4156 
4157     win = create_nhwindow(NHW_MENU);
4158     start_menu(win, MENU_BEHAVE_STANDARD);
4159 
4160     if (IS_FOUNTAIN(typ) || IS_SINK(typ)) {
4161         Sprintf(buf, "Drink from the %s",
4162                 defsyms[IS_FOUNTAIN(typ) ? S_fountain : S_sink].explanation);
4163         add_herecmd_menuitem(win, dodrink, buf);
4164     }
4165     if (IS_FOUNTAIN(typ))
4166         add_herecmd_menuitem(win, dodip,
4167                              "Dip something into the fountain");
4168     if (IS_THRONE(typ))
4169         add_herecmd_menuitem(win, dosit,
4170                              "Sit on the throne");
4171 
4172     if (stway && stway->up) {
4173         Sprintf(buf, "Go up the %s",
4174                 stway->isladder ? "ladder" : "stairs");
4175         add_herecmd_menuitem(win, doup, buf);
4176     }
4177     if (stway && !stway->up) {
4178         Sprintf(buf, "Go down the %s",
4179                 stway->isladder ? "ladder" : "stairs");
4180         add_herecmd_menuitem(win, dodown, buf);
4181     }
4182     if (u.usteed) { /* another movement choice */
4183         Sprintf(buf, "Dismount %s",
4184                 x_monnam(u.usteed, ARTICLE_THE, (char *) 0,
4185                          SUPPRESS_SADDLE, FALSE));
4186         add_herecmd_menuitem(win, doride, buf);
4187     }
4188 
4189 #if 0
4190     if (Upolyd) { /* before objects */
4191         Sprintf(buf, "Use %s special ability",
4192                 s_suffix(pmname(&mons[u.umonnum], Ugender)));
4193         add_herecmd_menuitem(win, domonability, buf);
4194     }
4195 #endif
4196 
4197     if (OBJ_AT(u.ux, u.uy)) {
4198         struct obj *otmp = g.level.objects[u.ux][u.uy];
4199 
4200         Sprintf(buf, "Pick up %s", otmp->nexthere ? "items" : doname(otmp));
4201         add_herecmd_menuitem(win, dopickup, buf);
4202 
4203         if (Is_container(otmp)) {
4204             Sprintf(buf, "Loot %s", doname(otmp));
4205             add_herecmd_menuitem(win, doloot, buf);
4206         }
4207         if (otmp->oclass == FOOD_CLASS) {
4208             Sprintf(buf, "Eat %s", doname(otmp));
4209             add_herecmd_menuitem(win, doeat, buf);
4210         }
4211     }
4212 
4213     if (g.invent)
4214         add_herecmd_menuitem(win, dodrop, "Drop items");
4215 
4216     add_herecmd_menuitem(win, donull, "Rest one turn");
4217     add_herecmd_menuitem(win, dosearch, "Search around you");
4218     add_herecmd_menuitem(win, dolook, "Look at what is here");
4219 
4220     end_menu(win, "What do you want to do?");
4221     npick = select_menu(win, PICK_ONE, &picks);
4222     destroy_nhwindow(win);
4223     ch = '\033';
4224     if (npick > 0) {
4225         int (*func)(void) = picks->item.a_nfunc;
4226         free((genericptr_t) picks);
4227 
4228         if (doit) {
4229             int ret = (*func)();
4230 
4231             ch = (char) ret;
4232         } else {
4233             ch = cmd_from_func(func);
4234         }
4235     }
4236     return ch;
4237 }
4238 
4239 /*
4240  * convert a MAP window position into a movecmd
4241  */
4242 const char *
click_to_cmd(int x,int y,int mod)4243 click_to_cmd(int x, int y, int mod)
4244 {
4245     static char cmd[4];
4246     struct obj *o;
4247     int dir, udist;
4248 
4249     cmd[0] = cmd[1] = '\0';
4250     if (iflags.clicklook && mod == CLICK_2) {
4251         g.clicklook_cc.x = x;
4252         g.clicklook_cc.y = y;
4253         cmd[0] = g.Cmd.spkeys[NHKF_CLICKLOOK];
4254         return cmd;
4255     }
4256     /* this used to be inside the 'if (flags.travelcmd)' block, but
4257        handle click-on-self even when travel is disabled; unlike
4258        accidentally zooming across the level because of a stray click,
4259        clicking on self can easily be cancelled if it wasn't intended */
4260     if (iflags.herecmd_menu && isok(x, y)) {
4261         udist = distu(x, y);
4262         if (!udist) {
4263             cmd[0] = here_cmd_menu(FALSE);
4264             return cmd;
4265         } else if (udist <= 2) {
4266 #if 0       /* there_cmd_menu() is broken; the commands it invokes
4267              * tend to ask for a direction or target instead of using
4268              * the adjacent coordinates that are being passed to it */
4269             cmd[0] = there_cmd_menu(FALSE, x, y);
4270             return cmd;
4271 #endif
4272         }
4273     }
4274 
4275     x -= u.ux;
4276     y -= u.uy;
4277 
4278     if (flags.travelcmd) {
4279         if (abs(x) <= 1 && abs(y) <= 1) {
4280             x = sgn(x), y = sgn(y);
4281         } else {
4282             u.tx = u.ux + x;
4283             u.ty = u.uy + y;
4284             cmd[0] = g.Cmd.spkeys[NHKF_TRAVEL];
4285             return cmd;
4286         }
4287 
4288         if (x == 0 && y == 0) {
4289             /* here */
4290             if (IS_FOUNTAIN(levl[u.ux][u.uy].typ)
4291                 || IS_SINK(levl[u.ux][u.uy].typ)) {
4292                 cmd[0] = cmd_from_func(mod == CLICK_1 ? dodrink : dodip);
4293                 return cmd;
4294             } else if (IS_THRONE(levl[u.ux][u.uy].typ)) {
4295                 cmd[0] = cmd_from_func(dosit);
4296                 return cmd;
4297             } else if (On_stairs_up(u.ux, u.uy)) {
4298                 cmd[0] = cmd_from_func(doup);
4299                 return cmd;
4300             } else if (On_stairs_dn(u.ux, u.uy)) {
4301                 cmd[0] = cmd_from_func(dodown);
4302                 return cmd;
4303             } else if ((o = vobj_at(u.ux, u.uy)) != 0) {
4304                 cmd[0] = cmd_from_func(Is_container(o) ? doloot : dopickup);
4305                 return cmd;
4306             } else {
4307                 cmd[0] = cmd_from_func(donull); /* just rest */
4308                 return cmd;
4309             }
4310         }
4311 
4312         /* directional commands */
4313 
4314         dir = xytod(x, y);
4315         if (!m_at(u.ux + x, u.uy + y)
4316             && !test_move(u.ux, u.uy, x, y, TEST_MOVE)) {
4317             cmd[1] = g.Cmd.dirchars[dir];
4318             cmd[2] = '\0';
4319 
4320             if (IS_DOOR(levl[u.ux + x][u.uy + y].typ)) {
4321                 /* slight assistance to player: choose kick/open for them */
4322                 if (door_is_locked(&levl[u.ux + x][u.uy + y])) {
4323                     cmd[0] = cmd_from_func(dokick);
4324                     return cmd;
4325                 }
4326                 if (door_is_closed(&levl[u.ux + x][u.uy + y])) {
4327                     cmd[0] = cmd_from_func(doopen);
4328                     return cmd;
4329                 }
4330             }
4331             if (levl[u.ux + x][u.uy + y].typ <= SCORR) {
4332                 cmd[0] = cmd_from_func(dosearch);
4333                 cmd[1] = 0;
4334                 return cmd;
4335             }
4336         }
4337     } else {
4338         /* convert without using floating point, allowing sloppy clicking */
4339         if (x > 2 * abs(y))
4340             x = 1, y = 0;
4341         else if (y > 2 * abs(x))
4342             x = 0, y = 1;
4343         else if (x < -2 * abs(y))
4344             x = -1, y = 0;
4345         else if (y < -2 * abs(x))
4346             x = 0, y = -1;
4347         else
4348             x = sgn(x), y = sgn(y);
4349 
4350         if (x == 0 && y == 0) {
4351             /* map click on player to "rest" command */
4352             cmd[0] = cmd_from_func(donull);
4353             return cmd;
4354         }
4355         dir = xytod(x, y);
4356     }
4357 
4358     /* move, attack, etc. */
4359     cmd[1] = 0;
4360     if (mod == CLICK_1) {
4361         cmd[0] = g.Cmd.dirchars[dir];
4362     } else {
4363         cmd[0] = (g.Cmd.num_pad
4364                      ? M(g.Cmd.dirchars[dir])
4365                      : (g.Cmd.dirchars[dir] - 'a' + 'A')); /* run command */
4366     }
4367 
4368     return cmd;
4369 }
4370 
4371 char
get_count(char * allowchars,char inkey,long maxcount,long * count,boolean historicmsg)4372 get_count(char *allowchars, char inkey,
4373           long maxcount, long *count,
4374           boolean historicmsg) /* whether to include in message
4375                                 * history: True => yes */
4376 {
4377     char qbuf[QBUFSZ];
4378     int key;
4379     long cnt = 0L;
4380     boolean backspaced = FALSE;
4381     /* this should be done in port code so that we have erase_char
4382        and kill_char available; we can at least fake erase_char */
4383 #define STANDBY_erase_char '\177'
4384 
4385     for (;;) {
4386         if (inkey) {
4387             key = inkey;
4388             inkey = '\0';
4389         } else
4390             key = readchar();
4391 
4392         if (digit(key)) {
4393             cnt = 10L * cnt + (long) (key - '0');
4394             if (cnt < 0)
4395                 cnt = 0;
4396             else if (maxcount > 0 && cnt > maxcount)
4397                 cnt = maxcount;
4398         } else if (cnt && (key == '\b' || key == STANDBY_erase_char)) {
4399             cnt = cnt / 10;
4400             backspaced = TRUE;
4401         } else if (key == g.Cmd.spkeys[NHKF_ESC]) {
4402             break;
4403         } else if (!allowchars || index(allowchars, key)) {
4404             *count = cnt;
4405             break;
4406         }
4407 
4408         if (cnt > 9 || backspaced) {
4409             clear_nhwindow(WIN_MESSAGE);
4410             if (backspaced && !cnt) {
4411                 Sprintf(qbuf, "Count: ");
4412             } else {
4413                 Sprintf(qbuf, "Count: %ld", cnt);
4414                 backspaced = FALSE;
4415             }
4416             custompline(SUPPRESS_HISTORY, "%s", qbuf);
4417             mark_synch();
4418         }
4419     }
4420 
4421     if (historicmsg) {
4422         Sprintf(qbuf, "Count: %ld ", *count);
4423         (void) key2txt((uchar) key, eos(qbuf));
4424         putmsghistory(qbuf, FALSE);
4425     }
4426 
4427     return key;
4428 }
4429 
4430 
4431 static char *
parse(void)4432 parse(void)
4433 {
4434     register int foo;
4435 
4436     iflags.in_parse = TRUE;
4437     g.command_count = 0;
4438     g.context.move = 1;
4439     flush_screen(1); /* Flush screen buffer. Put the cursor on the hero. */
4440 
4441 #ifdef ALTMETA
4442     alt_esc = iflags.altmeta; /* readchar() hack */
4443 #endif
4444     if (!g.Cmd.num_pad || (foo = readchar()) == g.Cmd.spkeys[NHKF_COUNT]) {
4445         foo = get_count((char *) 0, '\0', LARGEST_INT, &g.command_count, FALSE);
4446         g.last_command_count = g.command_count;
4447     }
4448 #ifdef ALTMETA
4449     alt_esc = FALSE; /* readchar() reset */
4450 #endif
4451 
4452     if (iflags.debug_fuzzer /* if fuzzing, override '!' and ^Z */
4453         && (g.Cmd.commands[foo & 0x0ff]
4454             && (g.Cmd.commands[foo & 0x0ff]->ef_funct == dosuspend_core
4455                 || g.Cmd.commands[foo & 0x0ff]->ef_funct == dosh_core)))
4456         foo = g.Cmd.spkeys[NHKF_ESC];
4457 
4458     if (foo == g.Cmd.spkeys[NHKF_ESC]) { /* esc cancels count (TH) */
4459         clear_nhwindow(WIN_MESSAGE);
4460         g.command_count = 0;
4461         g.last_command_count = 0;
4462     } else if (g.in_doagain) {
4463         g.command_count = g.last_command_count;
4464     } else if (foo && foo == g.Cmd.spkeys[NHKF_DOAGAIN]) {
4465         // g.command_count will be set again when we
4466         // re-enter with g.in_doagain set true
4467         g.command_count = g.last_command_count;
4468     } else {
4469         g.last_command_count = g.command_count;
4470         savech(0); /* reset input queue */
4471         savech((char) foo);
4472     }
4473 
4474     g.multi = g.command_count;
4475 
4476     if (g.multi) {
4477         g.multi--;
4478         g.save_cm = g.command_line;
4479     } else {
4480         g.save_cm = (char *) 0;
4481     }
4482     /* in 3.4.3 this was in rhack(), where it was too late to handle M-5 */
4483     if (g.Cmd.pcHack_compat) {
4484         /* This handles very old inconsistent DOS/Windows behaviour
4485            in a different way: earlier, the keyboard handler mapped
4486            these, which caused counts to be strange when entered
4487            from the number pad. Now do not map them until here. */
4488         switch (foo) {
4489         case '5':
4490             foo = g.Cmd.spkeys[NHKF_RUSH];
4491             break;
4492         case M('5'):
4493             foo = g.Cmd.spkeys[NHKF_RUN];
4494             break;
4495         case M('0'):
4496             foo = g.Cmd.spkeys[NHKF_DOINV];
4497             break;
4498         default:
4499             break; /* as is */
4500         }
4501     }
4502 
4503     g.command_line[0] = foo;
4504     g.command_line[1] = '\0';
4505     if (prefix_cmd(foo)) {
4506         foo = readchar();
4507         savech((char) foo);
4508         g.command_line[1] = foo;
4509         g.command_line[2] = 0;
4510     }
4511     clear_nhwindow(WIN_MESSAGE);
4512 
4513     iflags.in_parse = FALSE;
4514     return g.command_line;
4515 }
4516 
4517 #ifdef HANGUPHANDLING
4518 /* some very old systems, or descendents of such systems, expect signal
4519    handlers to have return type `int', but they don't actually inspect
4520    the return value so we should be safe using `void' unconditionally */
4521 /*ARGUSED*/
4522 void
hangup(int sig_unused UNUSED)4523 hangup(int sig_unused UNUSED)   /* called as signal() handler, so sent
4524                                    at least one arg */
4525 {
4526     if (g.program_state.exiting)
4527         g.program_state.in_moveloop = 0;
4528     nhwindows_hangup();
4529 #ifdef SAFERHANGUP
4530     /* When using SAFERHANGUP, the done_hup flag it tested in rhack
4531        and a couple of other places; actual hangup handling occurs then.
4532        This is 'safer' because it disallows certain cheats and also
4533        protects against losing objects in the process of being thrown,
4534        but also potentially riskier because the disconnected program
4535        must continue running longer before attempting a hangup save. */
4536     g.program_state.done_hup++;
4537     /* defer hangup iff game appears to be in progress */
4538     if (g.program_state.in_moveloop && g.program_state.something_worth_saving)
4539         return;
4540 #endif /* SAFERHANGUP */
4541     end_of_input();
4542 }
4543 
4544 void
end_of_input(void)4545 end_of_input(void)
4546 {
4547 #ifdef NOSAVEONHANGUP
4548 #ifdef INSURANCE
4549     if (flags.ins_chkpt && g.program_state.something_worth_saving)
4550         program_state.preserve_locks = 1; /* keep files for recovery */
4551 #endif
4552     g.program_state.something_worth_saving = 0; /* don't save */
4553 #endif
4554 
4555 #ifndef SAFERHANGUP
4556     if (!g.program_state.done_hup++)
4557 #endif
4558         if (g.program_state.something_worth_saving)
4559             (void) dosave0();
4560     if (iflags.window_inited)
4561         exit_nhwindows((char *) 0);
4562     clearlocks();
4563     nh_terminate(EXIT_SUCCESS);
4564     /*NOTREACHED*/ /* not necessarily true for vms... */
4565     return;
4566 }
4567 #endif /* HANGUPHANDLING */
4568 
4569 char
readchar(void)4570 readchar(void)
4571 {
4572     register int sym;
4573     int x = u.ux, y = u.uy, mod = 0;
4574 
4575     if (iflags.debug_fuzzer)
4576         return randomkey();
4577     if (*readchar_queue)
4578         sym = *readchar_queue++;
4579     else
4580         sym = g.in_doagain ? pgetchar() : nh_poskey(&x, &y, &mod);
4581 
4582 #ifdef NR_OF_EOFS
4583     if (sym == EOF) {
4584         register int cnt = NR_OF_EOFS;
4585         /*
4586          * Some SYSV systems seem to return EOFs for various reasons
4587          * (?like when one hits break or for interrupted systemcalls?),
4588          * and we must see several before we quit.
4589          */
4590         do {
4591             clearerr(stdin); /* omit if clearerr is undefined */
4592             sym = pgetchar();
4593         } while (--cnt && sym == EOF);
4594     }
4595 #endif /* NR_OF_EOFS */
4596 
4597     if (sym == EOF) {
4598 #ifdef HANGUPHANDLING
4599         hangup(0); /* call end_of_input() or set program_state.done_hup */
4600 #endif
4601         sym = '\033';
4602 #ifdef ALTMETA
4603     } else if (sym == '\033' && alt_esc) {
4604         /* iflags.altmeta: treat two character ``ESC c'' as single `M-c' */
4605         sym = *readchar_queue ? *readchar_queue++ : pgetchar();
4606         if (sym == EOF || sym == 0)
4607             sym = '\033';
4608         else if (sym != '\033')
4609             sym |= 0200; /* force 8th bit on */
4610 #endif /*ALTMETA*/
4611     } else if (sym == 0) {
4612         /* click event */
4613         readchar_queue = click_to_cmd(x, y, mod);
4614         sym = *readchar_queue++;
4615     }
4616     return (char) sym;
4617 }
4618 
4619 /* '_' command, #travel, via keyboard rather than mouse click */
4620 static int
dotravel(void)4621 dotravel(void)
4622 {
4623     static char cmd[2];
4624     coord cc;
4625 
4626     /*
4627      * Travelling used to be a no-op if user toggled 'travel' option
4628      * Off.  However, travel was initially implemented as a mouse-only
4629      * command and the original purpose of the option was to be able
4630      * to prevent clicks on the map from initiating travel.
4631      *
4632      * Travel via '_' came later.  Since it requires a destination--
4633      * which offers the user a chance to cancel if it was accidental--
4634      * there's no reason for the option to disable travel-by-keys.
4635      */
4636 
4637     cmd[1] = 0;
4638     cc.x = iflags.travelcc.x;
4639     cc.y = iflags.travelcc.y;
4640     if (cc.x == 0 && cc.y == 0) {
4641         /* No cached destination, start attempt from current position */
4642         cc.x = u.ux;
4643         cc.y = u.uy;
4644     }
4645     iflags.getloc_travelmode = TRUE;
4646     if (iflags.menu_requested) {
4647         int gf = iflags.getloc_filter;
4648         iflags.getloc_filter = GFILTER_VIEW;
4649         if (!getpos_menu(&cc, GLOC_INTERESTING)) {
4650             iflags.getloc_filter = gf;
4651             iflags.getloc_travelmode = FALSE;
4652             return 0;
4653         }
4654         iflags.getloc_filter = gf;
4655     } else {
4656         pline("Where do you want to travel to?");
4657         if (getpos(&cc, TRUE, "the desired destination") < 0) {
4658             /* user pressed ESC */
4659             iflags.getloc_travelmode = FALSE;
4660             return 0;
4661         }
4662     }
4663     iflags.getloc_travelmode = FALSE;
4664     iflags.travelcc.x = u.tx = cc.x;
4665     iflags.travelcc.y = u.ty = cc.y;
4666     cmd[0] = g.Cmd.spkeys[NHKF_TRAVEL];
4667     readchar_queue = cmd;
4668     return 0;
4669 }
4670 
4671 /*
4672  *   Parameter validator for generic yes/no function to prevent
4673  *   the core from sending too long a prompt string to the
4674  *   window port causing a buffer overflow there.
4675  */
4676 char
yn_function(const char * query,const char * resp,char def)4677 yn_function(const char *query, const char *resp, char def)
4678 {
4679     char res, qbuf[QBUFSZ];
4680 #if defined(DUMPLOG) || defined(DUMPHTML)
4681     unsigned idx = g.saved_pline_index;
4682     /* buffer to hold query+space+formatted_single_char_response */
4683     char dumplog_buf[QBUFSZ + 1 + 15]; /* [QBUFSZ+1+7] should suffice */
4684 #endif
4685 
4686     iflags.last_msg = PLNMSG_UNKNOWN; /* most recent pline is clobbered */
4687 
4688     /* maximum acceptable length is QBUFSZ-1 */
4689     if (strlen(query) >= QBUFSZ) {
4690         /* caller shouldn't have passed anything this long */
4691         paniclog("Query truncated: ", query);
4692         (void) strncpy(qbuf, query, QBUFSZ - 1 - 3);
4693         Strcpy(&qbuf[QBUFSZ - 1 - 3], "...");
4694         query = qbuf;
4695     }
4696     res = (*windowprocs.win_yn_function)(query, resp, def);
4697 #if defined(DUMPLOG) || defined(DUMPHTML)
4698     if (idx == g.saved_pline_index) {
4699         /* when idx is still the same as saved_pline_index, the interface
4700            didn't put the prompt into saved_plines[]; we put a simplified
4701            version in there now (without response choices or default) */
4702         Sprintf(dumplog_buf, "%s ", query);
4703         (void) key2txt((uchar) res, eos(dumplog_buf));
4704         dumplogmsg(dumplog_buf);
4705     }
4706 #endif
4707     return res;
4708 }
4709 
4710 /* for paranoid_confirm:quit,die,attack prompting */
4711 boolean
paranoid_query(boolean be_paranoid,const char * prompt)4712 paranoid_query(boolean be_paranoid, const char *prompt)
4713 {
4714     boolean confirmed_ok;
4715 
4716     /* when paranoid, player must respond with "yes" rather than just 'y'
4717        to give the go-ahead for this query; default is "no" unless the
4718        ParanoidConfirm flag is set in which case there's no default */
4719     if (be_paranoid) {
4720         char pbuf[BUFSZ], qbuf[QBUFSZ], ans[BUFSZ];
4721         const char *promptprefix = "",
4722                 *responsetype = ParanoidConfirm ? "(yes|no)" : "(yes) [no]";
4723         int k, trylimit = 6; /* 1 normal, 5 more with "Yes or No:" prefix */
4724 
4725         copynchars(pbuf, prompt, BUFSZ - 1);
4726         /* in addition to being paranoid about this particular
4727            query, we might be even more paranoid about all paranoia
4728            responses (ie, ParanoidConfirm is set) in which case we
4729            require "no" to reject in addition to "yes" to confirm
4730            (except we won't loop if response is ESC; it means no) */
4731         do {
4732             /* make sure we won't overflow a QBUFSZ sized buffer */
4733             k = (int) (strlen(promptprefix) + 1 + strlen(responsetype));
4734             if ((int) strlen(pbuf) + k > QBUFSZ - 1) {
4735                 /* chop off some at the end */
4736                 Strcpy(pbuf + (QBUFSZ - 1) - k - 4, "...?"); /* -4: "...?" */
4737             }
4738 
4739             Snprintf(qbuf, sizeof(qbuf), "%s%s %s", promptprefix, pbuf,
4740                      responsetype);
4741             *ans = '\0';
4742             getlin(qbuf, ans);
4743             (void) mungspaces(ans);
4744             confirmed_ok = !strcmpi(ans, "yes");
4745             if (confirmed_ok || *ans == '\033')
4746                 break;
4747             promptprefix = "\"Yes\" or \"No\": ";
4748         } while (ParanoidConfirm && strcmpi(ans, "no") && --trylimit);
4749     } else
4750         confirmed_ok = (yn(prompt) == 'y');
4751 
4752     return confirmed_ok;
4753 }
4754 
4755 /* ^Z command, #suspend */
4756 static int
dosuspend_core(void)4757 dosuspend_core(void)
4758 {
4759 #ifdef SUSPEND
4760     /* Does current window system support suspend? */
4761     if ((*windowprocs.win_can_suspend)()) {
4762         /* NB: SYSCF SHELLERS handled in port code. */
4763         dosuspend();
4764     } else
4765 #endif
4766         Norep(cmdnotavail, "#suspend");
4767     return 0;
4768 }
4769 
4770 /* '!' command, #shell */
4771 static int
dosh_core(void)4772 dosh_core(void)
4773 {
4774 #ifdef SHELL
4775     /* access restrictions, if any, are handled in port code */
4776     dosh();
4777 #else
4778     Norep(cmdnotavail, "#shell");
4779 #endif
4780     return 0;
4781 }
4782 
4783 /*cmd.c*/
4784