1 /* NetHack 3.6	cmd.c	$NHDT-Date: 1575245052 2019/12/02 00:04:12 $  $NHDT-Branch: NetHack-3.6 $:$NHDT-Revision: 1.350 $ */
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 "lev.h"
8 #include "func_tab.h"
9 
10 /* Macros for meta and ctrl modifiers:
11  *   M and C return the meta/ctrl code for the given character;
12  *     e.g., (C('c') is ctrl-c
13  */
14 #ifndef M
15 #ifndef NHSTDC
16 #define M(c) (0x80 | (c))
17 #else
18 #define M(c) ((c) - 128)
19 #endif /* NHSTDC */
20 #endif
21 
22 #ifndef C
23 #define C(c) (0x1f & (c))
24 #endif
25 
26 #define unctrl(c) ((c) <= C('z') ? (0x60 | (c)) : (c))
27 #define unmeta(c) (0x7f & (c))
28 
29 #ifdef ALTMETA
30 STATIC_VAR boolean alt_esc = FALSE;
31 #endif
32 
33 struct cmd Cmd = { 0 }; /* flag.h */
34 
35 extern const char *hu_stat[];  /* hunger status from eat.c */
36 extern const char *enc_stat[]; /* encumbrance status from botl.c */
37 
38 #ifdef UNIX
39 /*
40  * Some systems may have getchar() return EOF for various reasons, and
41  * we should not quit before seeing at least NR_OF_EOFS consecutive EOFs.
42  */
43 #if defined(SYSV) || defined(DGUX) || defined(HPUX)
44 #define NR_OF_EOFS 20
45 #endif
46 #endif
47 
48 #define CMD_TRAVEL (char) 0x90
49 #define CMD_CLICKLOOK (char) 0x8F
50 
51 #ifdef DEBUG
52 extern int NDECL(wiz_debug_cmd_bury);
53 #endif
54 
55 #ifdef DUMB /* stuff commented out in extern.h, but needed here */
56 extern int NDECL(doapply);            /**/
57 extern int NDECL(dorub);              /**/
58 extern int NDECL(dojump);             /**/
59 extern int NDECL(doextlist);          /**/
60 extern int NDECL(enter_explore_mode); /**/
61 extern int NDECL(dodrop);             /**/
62 extern int NDECL(doddrop);            /**/
63 extern int NDECL(dodown);             /**/
64 extern int NDECL(doup);               /**/
65 extern int NDECL(donull);             /**/
66 extern int NDECL(dowipe);             /**/
67 extern int NDECL(docallcnd);          /**/
68 extern int NDECL(dotakeoff);          /**/
69 extern int NDECL(doremring);          /**/
70 extern int NDECL(dowear);             /**/
71 extern int NDECL(doputon);            /**/
72 extern int NDECL(doddoremarm);        /**/
73 extern int NDECL(dokick);             /**/
74 extern int NDECL(dofire);             /**/
75 extern int NDECL(dothrow);            /**/
76 extern int NDECL(doeat);              /**/
77 extern int NDECL(done2);              /**/
78 extern int NDECL(vanquished);         /**/
79 extern int NDECL(doengrave);          /**/
80 extern int NDECL(dopickup);           /**/
81 extern int NDECL(ddoinv);             /**/
82 extern int NDECL(dotypeinv);          /**/
83 extern int NDECL(dolook);             /**/
84 extern int NDECL(doprgold);           /**/
85 extern int NDECL(doprwep);            /**/
86 extern int NDECL(doprarm);            /**/
87 extern int NDECL(doprring);           /**/
88 extern int NDECL(dopramulet);         /**/
89 extern int NDECL(doprtool);           /**/
90 extern int NDECL(dosuspend);          /**/
91 extern int NDECL(doforce);            /**/
92 extern int NDECL(doopen);             /**/
93 extern int NDECL(doclose);            /**/
94 extern int NDECL(dosh);               /**/
95 extern int NDECL(dodiscovered);       /**/
96 extern int NDECL(doclassdisco);       /**/
97 extern int NDECL(doset);              /**/
98 extern int NDECL(dotogglepickup);     /**/
99 extern int NDECL(dowhatis);           /**/
100 extern int NDECL(doquickwhatis);      /**/
101 extern int NDECL(dowhatdoes);         /**/
102 extern int NDECL(dohelp);             /**/
103 extern int NDECL(dohistory);          /**/
104 extern int NDECL(doloot);             /**/
105 extern int NDECL(dodrink);            /**/
106 extern int NDECL(dodip);              /**/
107 extern int NDECL(dosacrifice);        /**/
108 extern int NDECL(dopray);             /**/
109 extern int NDECL(dotip);              /**/
110 extern int NDECL(doturn);             /**/
111 extern int NDECL(doredraw);           /**/
112 extern int NDECL(doread);             /**/
113 extern int NDECL(dosave);             /**/
114 extern int NDECL(dosearch);           /**/
115 extern int NDECL(doidtrap);           /**/
116 extern int NDECL(dopay);              /**/
117 extern int NDECL(dosit);              /**/
118 extern int NDECL(dotalk);             /**/
119 extern int NDECL(docast);             /**/
120 extern int NDECL(dovspell);           /**/
121 extern int NDECL(dotelecmd);          /**/
122 extern int NDECL(dountrap);           /**/
123 extern int NDECL(doversion);          /**/
124 extern int NDECL(doextversion);       /**/
125 extern int NDECL(doswapweapon);       /**/
126 extern int NDECL(dowield);            /**/
127 extern int NDECL(dowieldquiver);      /**/
128 extern int NDECL(dozap);              /**/
129 extern int NDECL(doorganize);         /**/
130 #endif /* DUMB */
131 
132 static int NDECL((*timed_occ_fn));
133 
134 STATIC_PTR int NDECL(dosuspend_core);
135 STATIC_PTR int NDECL(dosh_core);
136 STATIC_PTR int NDECL(doherecmdmenu);
137 STATIC_PTR int NDECL(dotherecmdmenu);
138 STATIC_PTR int NDECL(doprev_message);
139 STATIC_PTR int NDECL(timed_occupation);
140 STATIC_PTR int NDECL(doextcmd);
141 STATIC_PTR int NDECL(dotravel);
142 STATIC_PTR int NDECL(doterrain);
143 STATIC_PTR int NDECL(wiz_wish);
144 STATIC_PTR int NDECL(wiz_identify);
145 STATIC_PTR int NDECL(wiz_intrinsic);
146 STATIC_PTR int NDECL(wiz_map);
147 STATIC_PTR int NDECL(wiz_makemap);
148 STATIC_PTR int NDECL(wiz_genesis);
149 STATIC_PTR int NDECL(wiz_where);
150 STATIC_PTR int NDECL(wiz_detect);
151 STATIC_PTR int NDECL(wiz_panic);
152 STATIC_PTR int NDECL(wiz_polyself);
153 STATIC_PTR int NDECL(wiz_level_tele);
154 STATIC_PTR int NDECL(wiz_level_change);
155 STATIC_PTR int NDECL(wiz_show_seenv);
156 STATIC_PTR int NDECL(wiz_show_vision);
157 STATIC_PTR int NDECL(wiz_smell);
158 STATIC_PTR int NDECL(wiz_show_wmodes);
159 STATIC_DCL void NDECL(wiz_map_levltyp);
160 STATIC_DCL void NDECL(wiz_levltyp_legend);
161 #if defined(__BORLANDC__) && !defined(_WIN32)
162 extern void FDECL(show_borlandc_stats, (winid));
163 #endif
164 #ifdef DEBUG_MIGRATING_MONS
165 STATIC_PTR int NDECL(wiz_migrate_mons);
166 #endif
167 STATIC_DCL int FDECL(size_monst, (struct monst *, BOOLEAN_P));
168 STATIC_DCL int FDECL(size_obj, (struct obj *));
169 STATIC_DCL void FDECL(count_obj, (struct obj *, long *, long *,
170                                   BOOLEAN_P, BOOLEAN_P));
171 STATIC_DCL void FDECL(obj_chain, (winid, const char *, struct obj *,
172                                   BOOLEAN_P, long *, long *));
173 STATIC_DCL void FDECL(mon_invent_chain, (winid, const char *, struct monst *,
174                                          long *, long *));
175 STATIC_DCL void FDECL(mon_chain, (winid, const char *, struct monst *,
176                                   BOOLEAN_P, long *, long *));
177 STATIC_DCL void FDECL(contained_stats, (winid, const char *, long *, long *));
178 STATIC_DCL void FDECL(misc_stats, (winid, long *, long *));
179 STATIC_PTR int NDECL(wiz_show_stats);
180 STATIC_DCL boolean FDECL(accept_menu_prefix, (int NDECL((*))));
181 STATIC_PTR int NDECL(wiz_rumor_check);
182 STATIC_PTR int NDECL(doattributes);
183 
184 STATIC_DCL void FDECL(enlght_out, (const char *));
185 STATIC_DCL void FDECL(enlght_line, (const char *, const char *, const char *,
186                                     const char *));
187 STATIC_DCL char *FDECL(enlght_combatinc, (const char *, int, int, char *));
188 STATIC_DCL void FDECL(enlght_halfdmg, (int, int));
189 STATIC_DCL boolean NDECL(walking_on_water);
190 STATIC_DCL boolean FDECL(cause_known, (int));
191 STATIC_DCL char *FDECL(attrval, (int, int, char *));
192 STATIC_DCL void FDECL(background_enlightenment, (int, int));
193 STATIC_DCL void FDECL(basics_enlightenment, (int, int));
194 STATIC_DCL void FDECL(characteristics_enlightenment, (int, int));
195 STATIC_DCL void FDECL(one_characteristic, (int, int, int));
196 STATIC_DCL void FDECL(status_enlightenment, (int, int));
197 STATIC_DCL void FDECL(attributes_enlightenment, (int, int));
198 
199 STATIC_DCL void FDECL(add_herecmd_menuitem, (winid, int NDECL((*)),
200                                              const char *));
201 STATIC_DCL char FDECL(here_cmd_menu, (BOOLEAN_P));
202 STATIC_DCL char FDECL(there_cmd_menu, (BOOLEAN_P, int, int));
203 STATIC_DCL char *NDECL(parse);
204 STATIC_DCL void FDECL(show_direction_keys, (winid, CHAR_P, BOOLEAN_P));
205 STATIC_DCL boolean FDECL(help_dir, (CHAR_P, int, const char *));
206 
207 static const char *readchar_queue = "";
208 static coord clicklook_cc;
209 /* for rejecting attempts to use wizard mode commands */
210 static const char unavailcmd[] = "Unavailable command '%s'.";
211 /* for rejecting #if !SHELL, !SUSPEND */
212 static const char cmdnotavail[] = "'%s' command not available.";
213 
214 STATIC_PTR int
doprev_message(VOID_ARGS)215 doprev_message(VOID_ARGS)
216 {
217     return nh_doprev_message();
218 }
219 
220 /* Count down by decrementing multi */
221 STATIC_PTR int
timed_occupation(VOID_ARGS)222 timed_occupation(VOID_ARGS)
223 {
224     (*timed_occ_fn)();
225     if (multi > 0)
226         multi--;
227     return multi > 0;
228 }
229 
230 /* If you have moved since initially setting some occupations, they
231  * now shouldn't be able to restart.
232  *
233  * The basic rule is that if you are carrying it, you can continue
234  * since it is with you.  If you are acting on something at a distance,
235  * your orientation to it must have changed when you moved.
236  *
237  * The exception to this is taking off items, since they can be taken
238  * off in a number of ways in the intervening time, screwing up ordering.
239  *
240  *      Currently:      Take off all armor.
241  *                      Picking Locks / Forcing Chests.
242  *                      Setting traps.
243  */
244 void
reset_occupations()245 reset_occupations()
246 {
247     reset_remarm();
248     reset_pick();
249     reset_trapset();
250 }
251 
252 /* If a time is given, use it to timeout this function, otherwise the
253  * function times out by its own means.
254  */
255 void
256 set_occupation(fn, txt, xtime)
257 int NDECL((*fn));
258 const char *txt;
259 int xtime;
260 {
261     if (xtime) {
262         occupation = timed_occupation;
263         timed_occ_fn = fn;
264     } else
265         occupation = fn;
266     occtxt = txt;
267     occtime = 0;
268     return;
269 }
270 
271 STATIC_DCL char NDECL(popch);
272 
273 /* Provide a means to redo the last command.  The flag `in_doagain' is set
274  * to true while redoing the command.  This flag is tested in commands that
275  * require additional input (like `throw' which requires a thing and a
276  * direction), and the input prompt is not shown.  Also, while in_doagain is
277  * TRUE, no keystrokes can be saved into the saveq.
278  */
279 #define BSIZE 20
280 static char pushq[BSIZE], saveq[BSIZE];
281 static NEARDATA int phead, ptail, shead, stail;
282 
283 STATIC_OVL char
popch()284 popch()
285 {
286     /* If occupied, return '\0', letting tgetch know a character should
287      * be read from the keyboard.  If the character read is not the
288      * ABORT character (as checked in pcmain.c), that character will be
289      * pushed back on the pushq.
290      */
291     if (occupation)
292         return '\0';
293     if (in_doagain)
294         return (char) ((shead != stail) ? saveq[stail++] : '\0');
295     else
296         return (char) ((phead != ptail) ? pushq[ptail++] : '\0');
297 }
298 
299 char
pgetchar()300 pgetchar() /* courtesy of aeb@cwi.nl */
301 {
302     register int ch;
303 
304     if (iflags.debug_fuzzer)
305         return randomkey();
306     if (!(ch = popch()))
307         ch = nhgetch();
308     return (char) ch;
309 }
310 
311 /* A ch == 0 resets the pushq */
312 void
pushch(ch)313 pushch(ch)
314 char ch;
315 {
316     if (!ch)
317         phead = ptail = 0;
318     if (phead < BSIZE)
319         pushq[phead++] = ch;
320     return;
321 }
322 
323 /* A ch == 0 resets the saveq.  Only save keystrokes when not
324  * replaying a previous command.
325  */
326 void
savech(ch)327 savech(ch)
328 char ch;
329 {
330     if (!in_doagain) {
331         if (!ch)
332             phead = ptail = shead = stail = 0;
333         else if (shead < BSIZE)
334             saveq[shead++] = ch;
335     }
336     return;
337 }
338 
339 /* here after # - now read a full-word command */
340 STATIC_PTR int
doextcmd(VOID_ARGS)341 doextcmd(VOID_ARGS)
342 {
343     int idx, retval;
344     int NDECL((*func));
345 
346     /* keep repeating until we don't run help or quit */
347     do {
348         idx = get_ext_cmd();
349         if (idx < 0)
350             return 0; /* quit */
351 
352         func = extcmdlist[idx].ef_funct;
353         if (!wizard && (extcmdlist[idx].flags & WIZMODECMD)) {
354             You("can't do that.");
355             return 0;
356         }
357         if (iflags.menu_requested && !accept_menu_prefix(func)) {
358             pline("'%s' prefix has no effect for the %s command.",
359                   visctrl(Cmd.spkeys[NHKF_REQMENU]),
360                   extcmdlist[idx].ef_txt);
361             iflags.menu_requested = FALSE;
362         }
363         retval = (*func)();
364     } while (func == doextlist);
365 
366     return retval;
367 }
368 
369 /* here after #? - now list all full-word commands and provid
370    some navigation capability through the long list */
371 int
doextlist(VOID_ARGS)372 doextlist(VOID_ARGS)
373 {
374     register const struct ext_func_tab *efp;
375     char buf[BUFSZ], searchbuf[BUFSZ], promptbuf[QBUFSZ];
376     winid menuwin;
377     anything any;
378     menu_item *selected;
379     int n, pass;
380     int menumode = 0, menushown[2], onelist = 0;
381     boolean redisplay = TRUE, search = FALSE;
382     static const char *headings[] = { "Extended commands",
383                                       "Debugging Extended Commands" };
384 
385     searchbuf[0] = '\0';
386     menuwin = create_nhwindow(NHW_MENU);
387 
388     while (redisplay) {
389         redisplay = FALSE;
390         any = zeroany;
391         start_menu(menuwin);
392         add_menu(menuwin, NO_GLYPH, &any, 0, 0, ATR_NONE,
393                  "Extended Commands List", MENU_UNSELECTED);
394         add_menu(menuwin, NO_GLYPH, &any, 0, 0, ATR_NONE,
395                  "", MENU_UNSELECTED);
396 
397         Strcpy(buf, menumode ? "Show" : "Hide");
398         Strcat(buf, " commands that don't autocomplete");
399         if (!menumode)
400             Strcat(buf, " (those not marked with [A])");
401         any.a_int = 1;
402         add_menu(menuwin, NO_GLYPH, &any, 'a', 0, ATR_NONE, buf,
403                  MENU_UNSELECTED);
404 
405         if (!*searchbuf) {
406             any.a_int = 2;
407             /* was 's', but then using ':' handling within the interface
408                would only examine the two or three meta entries, not the
409                actual list of extended commands shown via separator lines;
410                having ':' as an explicit selector overrides the default
411                menu behavior for it; we retain 's' as a group accelerator */
412             add_menu(menuwin, NO_GLYPH, &any, ':', 's', ATR_NONE,
413                      "Search extended commands", MENU_UNSELECTED);
414         } else {
415             Strcpy(buf, "Show all, clear search");
416             if (strlen(buf) + strlen(searchbuf) + strlen(" (\"\")") < QBUFSZ)
417                 Sprintf(eos(buf), " (\"%s\")", searchbuf);
418             any.a_int = 3;
419             /* specifying ':' as a group accelerator here is mostly a
420                statement of intent (we'd like to accept it as a synonym but
421                also want to hide it from general menu use) because it won't
422                work for interfaces which support ':' to search; use as a
423                general menu command takes precedence over group accelerator */
424             add_menu(menuwin, NO_GLYPH, &any, 's', ':', ATR_NONE,
425                      buf, MENU_UNSELECTED);
426         }
427         if (wizard) {
428             any.a_int = 4;
429             add_menu(menuwin, NO_GLYPH, &any, 'z', 0, ATR_NONE,
430                      onelist ? "Show debugging commands in separate section"
431                      : "Show all alphabetically, including debugging commands",
432                      MENU_UNSELECTED);
433         }
434         any = zeroany;
435         add_menu(menuwin, NO_GLYPH, &any, 0, 0, ATR_NONE,
436                  "", MENU_UNSELECTED);
437         menushown[0] = menushown[1] = 0;
438         n = 0;
439         for (pass = 0; pass <= 1; ++pass) {
440             /* skip second pass if not in wizard mode or wizard mode
441                commands are being integrated into a single list */
442             if (pass == 1 && (onelist || !wizard))
443                 break;
444             for (efp = extcmdlist; efp->ef_txt; efp++) {
445                 int wizc;
446 
447                 if ((efp->flags & CMD_NOT_AVAILABLE) != 0)
448                     continue;
449                 /* if hiding non-autocomplete commands, skip such */
450                 if (menumode == 1 && (efp->flags & AUTOCOMPLETE) == 0)
451                     continue;
452                 /* if searching, skip this command if it doesn't match */
453                 if (*searchbuf
454                     /* first try case-insensitive substring match */
455                     && !strstri(efp->ef_txt, searchbuf)
456                     && !strstri(efp->ef_desc, searchbuf)
457                     /* wildcard support; most interfaces use case-insensitve
458                        pmatch rather than regexp for menu searching */
459                     && !pmatchi(searchbuf, efp->ef_txt)
460                     && !pmatchi(searchbuf, efp->ef_desc))
461                     continue;
462                 /* skip wizard mode commands if not in wizard mode;
463                    when showing two sections, skip wizard mode commands
464                    in pass==0 and skip other commands in pass==1 */
465                 wizc = (efp->flags & WIZMODECMD) != 0;
466                 if (wizc && !wizard)
467                     continue;
468                 if (!onelist && pass != wizc)
469                     continue;
470 
471                 /* We're about to show an item, have we shown the menu yet?
472                    Doing menu in inner loop like this on demand avoids a
473                    heading with no subordinate entries on the search
474                    results menu. */
475                 if (!menushown[pass]) {
476                     Strcpy(buf, headings[pass]);
477                     add_menu(menuwin, NO_GLYPH, &any, 0, 0,
478                              iflags.menu_headings, buf, MENU_UNSELECTED);
479                     menushown[pass] = 1;
480                 }
481                 Sprintf(buf, " %-14s %-3s %s",
482                         efp->ef_txt,
483                         (efp->flags & AUTOCOMPLETE) ? "[A]" : " ",
484                         efp->ef_desc);
485                 add_menu(menuwin, NO_GLYPH, &any, 0, 0, ATR_NONE,
486                          buf, MENU_UNSELECTED);
487                 ++n;
488             }
489             if (n)
490                 add_menu(menuwin, NO_GLYPH, &any, 0, 0, ATR_NONE,
491                          "", MENU_UNSELECTED);
492         }
493         if (*searchbuf && !n)
494             add_menu(menuwin, NO_GLYPH, &any, 0, 0, ATR_NONE,
495                      "no matches", MENU_UNSELECTED);
496 
497         end_menu(menuwin, (char *) 0);
498         n = select_menu(menuwin, PICK_ONE, &selected);
499         if (n > 0) {
500             switch (selected[0].item.a_int) {
501             case 1: /* 'a': toggle show/hide non-autocomplete */
502                 menumode = 1 - menumode;  /* toggle 0 -> 1, 1 -> 0 */
503                 redisplay = TRUE;
504                 break;
505             case 2: /* ':' when not searching yet: enable search */
506                 search = TRUE;
507                 break;
508             case 3: /* 's' when already searching: disable search */
509                 search = FALSE;
510                 searchbuf[0] = '\0';
511                 redisplay = TRUE;
512                 break;
513             case 4: /* 'z': toggle showing wizard mode commands separately */
514                 search = FALSE;
515                 searchbuf[0] = '\0';
516                 onelist = 1 - onelist;  /* toggle 0 -> 1, 1 -> 0 */
517                 redisplay = TRUE;
518                 break;
519             }
520             free((genericptr_t) selected);
521         } else {
522             search = FALSE;
523             searchbuf[0] = '\0';
524         }
525         if (search) {
526             Strcpy(promptbuf, "Extended command list search phrase");
527             Strcat(promptbuf, "?");
528             getlin(promptbuf, searchbuf);
529             (void) mungspaces(searchbuf);
530             if (searchbuf[0] == '\033')
531                 searchbuf[0] = '\0';
532             if (*searchbuf)
533                 redisplay = TRUE;
534             search = FALSE;
535         }
536     }
537     destroy_nhwindow(menuwin);
538     return 0;
539 }
540 
541 #if defined(TTY_GRAPHICS) || defined(CURSES_GRAPHICS)
542 #define MAX_EXT_CMD 200 /* Change if we ever have more ext cmds */
543 
544 /*
545  * This is currently used only by the tty interface and is
546  * controlled via runtime option 'extmenu'.  (Most other interfaces
547  * already use a menu all the time for extended commands.)
548  *
549  * ``# ?'' is counted towards the limit of the number of commands,
550  * so we actually support MAX_EXT_CMD-1 "real" extended commands.
551  *
552  * Here after # - now show pick-list of possible commands.
553  */
554 int
extcmd_via_menu()555 extcmd_via_menu()
556 {
557     const struct ext_func_tab *efp;
558     menu_item *pick_list = (menu_item *) 0;
559     winid win;
560     anything any;
561     const struct ext_func_tab *choices[MAX_EXT_CMD + 1];
562     char buf[BUFSZ];
563     char cbuf[QBUFSZ], prompt[QBUFSZ], fmtstr[20];
564     int i, n, nchoices, acount;
565     int ret, len, biggest;
566     int accelerator, prevaccelerator;
567     int matchlevel = 0;
568     boolean wastoolong, one_per_line;
569 
570     ret = 0;
571     cbuf[0] = '\0';
572     biggest = 0;
573     while (!ret) {
574         i = n = 0;
575         any = zeroany;
576         /* populate choices */
577         for (efp = extcmdlist; efp->ef_txt; efp++) {
578             if ((efp->flags & CMD_NOT_AVAILABLE)
579                 || !(efp->flags & AUTOCOMPLETE)
580                 || (!wizard && (efp->flags & WIZMODECMD)))
581                 continue;
582             if (!matchlevel || !strncmp(efp->ef_txt, cbuf, matchlevel)) {
583                 choices[i] = efp;
584                 if ((len = (int) strlen(efp->ef_desc)) > biggest)
585                     biggest = len;
586                 if (++i > MAX_EXT_CMD) {
587 #if (NH_DEVEL_STATUS != NH_STATUS_RELEASED)
588                     impossible(
589       "Exceeded %d extended commands in doextcmd() menu; 'extmenu' disabled.",
590                                MAX_EXT_CMD);
591 #endif /* NH_DEVEL_STATUS != NH_STATUS_RELEASED */
592                     iflags.extmenu = 0;
593                     return -1;
594                 }
595             }
596         }
597         choices[i] = (struct ext_func_tab *) 0;
598         nchoices = i;
599         /* if we're down to one, we have our selection so get out of here */
600         if (nchoices  <= 1) {
601             ret = (nchoices == 1) ? (int) (choices[0] - extcmdlist) : -1;
602             break;
603         }
604 
605         /* otherwise... */
606         win = create_nhwindow(NHW_MENU);
607         start_menu(win);
608         Sprintf(fmtstr, "%%-%ds", biggest + 15);
609         prompt[0] = '\0';
610         wastoolong = FALSE; /* True => had to wrap due to line width
611                              * ('w' in wizard mode) */
612         /* -3: two line menu header, 1 line menu footer (for prompt) */
613         one_per_line = (nchoices < ROWNO - 3);
614         accelerator = prevaccelerator = 0;
615         acount = 0;
616         for (i = 0; choices[i]; ++i) {
617             accelerator = choices[i]->ef_txt[matchlevel];
618             if (accelerator != prevaccelerator || one_per_line)
619                 wastoolong = FALSE;
620             if (accelerator != prevaccelerator || one_per_line
621                 || (acount >= 2
622                     /* +4: + sizeof " or " - sizeof "" */
623                     && (strlen(prompt) + 4 + strlen(choices[i]->ef_txt)
624                         /* -6: enough room for 1 space left margin
625                          *   + "%c - " menu selector + 1 space right margin */
626                         >= min(sizeof prompt, COLNO - 6)))) {
627                 if (acount) {
628                     /* flush extended cmds for that letter already in buf */
629                     Sprintf(buf, fmtstr, prompt);
630                     any.a_char = prevaccelerator;
631                     add_menu(win, NO_GLYPH, &any, any.a_char, 0, ATR_NONE,
632                              buf, FALSE);
633                     acount = 0;
634                     if (!(accelerator != prevaccelerator || one_per_line))
635                         wastoolong = TRUE;
636                 }
637             }
638             prevaccelerator = accelerator;
639             if (!acount || one_per_line) {
640                 Sprintf(prompt, "%s%s [%s]", wastoolong ? "or " : "",
641                         choices[i]->ef_txt, choices[i]->ef_desc);
642             } else if (acount == 1) {
643                 Sprintf(prompt, "%s%s or %s", wastoolong ? "or " : "",
644                         choices[i - 1]->ef_txt, choices[i]->ef_txt);
645             } else {
646                 Strcat(prompt, " or ");
647                 Strcat(prompt, choices[i]->ef_txt);
648             }
649             ++acount;
650         }
651         if (acount) {
652             /* flush buf */
653             Sprintf(buf, fmtstr, prompt);
654             any.a_char = prevaccelerator;
655             add_menu(win, NO_GLYPH, &any, any.a_char, 0, ATR_NONE, buf,
656                      FALSE);
657         }
658         Sprintf(prompt, "Extended Command: %s", cbuf);
659         end_menu(win, prompt);
660         n = select_menu(win, PICK_ONE, &pick_list);
661         destroy_nhwindow(win);
662         if (n == 1) {
663             if (matchlevel > (QBUFSZ - 2)) {
664                 free((genericptr_t) pick_list);
665 #if (NH_DEVEL_STATUS != NH_STATUS_RELEASED)
666                 impossible("Too many chars (%d) entered in extcmd_via_menu()",
667                            matchlevel);
668 #endif
669                 ret = -1;
670             } else {
671                 cbuf[matchlevel++] = pick_list[0].item.a_char;
672                 cbuf[matchlevel] = '\0';
673                 free((genericptr_t) pick_list);
674             }
675         } else {
676             if (matchlevel) {
677                 ret = 0;
678                 matchlevel = 0;
679             } else
680                 ret = -1;
681         }
682     }
683     return ret;
684 }
685 #endif /* TTY_GRAPHICS */
686 
687 /* #monster command - use special monster ability while polymorphed */
688 int
domonability(VOID_ARGS)689 domonability(VOID_ARGS)
690 {
691     if (can_breathe(youmonst.data))
692         return dobreathe();
693     else if (attacktype(youmonst.data, AT_SPIT))
694         return dospit();
695     else if (youmonst.data->mlet == S_NYMPH)
696         return doremove();
697     else if (attacktype(youmonst.data, AT_GAZE))
698         return dogaze();
699     else if (is_were(youmonst.data))
700         return dosummon();
701     else if (webmaker(youmonst.data))
702         return dospinweb();
703     else if (is_hider(youmonst.data))
704         return dohide();
705     else if (is_mind_flayer(youmonst.data))
706         return domindblast();
707     else if (u.umonnum == PM_GREMLIN) {
708         if (IS_FOUNTAIN(levl[u.ux][u.uy].typ)) {
709             if (split_mon(&youmonst, (struct monst *) 0))
710                 dryup(u.ux, u.uy, TRUE);
711         } else
712             There("is no fountain here.");
713     } else if (is_unicorn(youmonst.data)) {
714         use_unicorn_horn((struct obj *) 0);
715         return 1;
716     } else if (youmonst.data->msound == MS_SHRIEK) {
717         You("shriek.");
718         if (u.uburied)
719             pline("Unfortunately sound does not carry well through rock.");
720         else
721             aggravate();
722     } else if (youmonst.data->mlet == S_VAMPIRE)
723         return dopoly();
724     else if (Upolyd)
725         pline("Any special ability you may have is purely reflexive.");
726     else
727         You("don't have a special ability in your normal form!");
728     return 0;
729 }
730 
731 int
enter_explore_mode(VOID_ARGS)732 enter_explore_mode(VOID_ARGS)
733 {
734     if (wizard) {
735         You("are in debug mode.");
736     } else if (discover) {
737         You("are already in explore mode.");
738     } else {
739 #ifdef SYSCF
740 #if defined(UNIX)
741         if (!sysopt.explorers || !sysopt.explorers[0]
742             || !check_user_string(sysopt.explorers)) {
743             You("cannot access explore mode.");
744             return 0;
745         }
746 #endif
747 #endif
748         pline(
749         "Beware!  From explore mode there will be no return to normal game.");
750         if (paranoid_query(ParanoidQuit,
751                            "Do you want to enter explore mode?")) {
752             clear_nhwindow(WIN_MESSAGE);
753             You("are now in non-scoring explore mode.");
754             discover = TRUE;
755         } else {
756             clear_nhwindow(WIN_MESSAGE);
757             pline("Resuming normal game.");
758         }
759     }
760     return 0;
761 }
762 
763 /* ^W command - wish for something */
764 STATIC_PTR int
wiz_wish(VOID_ARGS)765 wiz_wish(VOID_ARGS) /* Unlimited wishes for debug mode by Paul Polderman */
766 {
767     if (wizard) {
768         boolean save_verbose = flags.verbose;
769 
770         flags.verbose = FALSE;
771         makewish();
772         flags.verbose = save_verbose;
773         (void) encumber_msg();
774     } else
775         pline(unavailcmd, visctrl((int) cmd_from_func(wiz_wish)));
776     return 0;
777 }
778 
779 /* ^I command - reveal and optionally identify hero's inventory */
780 STATIC_PTR int
wiz_identify(VOID_ARGS)781 wiz_identify(VOID_ARGS)
782 {
783     if (wizard) {
784         iflags.override_ID = (int) cmd_from_func(wiz_identify);
785         /* command remapping might leave #wizidentify as the only way
786            to invoke us, in which case cmd_from_func() will yield NUL;
787            it won't matter to display_inventory()/display_pickinv()
788            if ^I invokes some other command--what matters is that
789            display_pickinv() and xname() see override_ID as nonzero */
790         if (!iflags.override_ID)
791             iflags.override_ID = C('I');
792         (void) display_inventory((char *) 0, FALSE);
793         iflags.override_ID = 0;
794     } else
795         pline(unavailcmd, visctrl((int) cmd_from_func(wiz_identify)));
796     return 0;
797 }
798 
799 /* #wizmakemap - discard current dungeon level and replace with a new one */
800 STATIC_PTR int
wiz_makemap(VOID_ARGS)801 wiz_makemap(VOID_ARGS)
802 {
803     if (wizard) {
804         struct monst *mtmp;
805         boolean was_in_W_tower = In_W_tower(u.ux, u.uy, &u.uz);
806 
807         rm_mapseen(ledger_no(&u.uz));
808         for (mtmp = fmon; mtmp; mtmp = mtmp->nmon) {
809             if (mtmp->isgd) { /* vault is going away; get rid of guard */
810                 mtmp->isgd = 0;
811                 mongone(mtmp);
812             }
813             if (DEADMONSTER(mtmp))
814                 continue;
815             if (mtmp->isshk)
816                 setpaid(mtmp);
817             /* TODO?
818              *  Reduce 'born' tally for each monster about to be discarded
819              *  by savelev(), otherwise replacing heavily populated levels
820              *  tends to make their inhabitants become extinct.
821              */
822         }
823         if (Punished) {
824             ballrelease(FALSE);
825             unplacebc();
826         }
827         /* reset lock picking unless it's for a carried container */
828         maybe_reset_pick((struct obj *) 0);
829         /* reset interrupted digging if it was taking place on this level */
830         if (on_level(&context.digging.level, &u.uz))
831             (void) memset((genericptr_t) &context.digging, 0,
832                           sizeof (struct dig_info));
833         /* reset cached targets */
834         iflags.travelcc.x = iflags.travelcc.y = 0; /* travel destination */
835         context.polearm.hitmon = (struct monst *) 0; /* polearm target */
836         /* escape from trap */
837         reset_utrap(FALSE);
838         check_special_room(TRUE); /* room exit */
839         u.ustuck = (struct monst *) 0;
840         u.uswallow = 0;
841         u.uinwater = 0;
842         u.uundetected = 0; /* not hidden, even if means are available */
843         dmonsfree(); /* purge dead monsters from 'fmon' */
844         /* keep steed and other adjacent pets after releasing them
845            from traps, stopping eating, &c as if hero were ascending */
846         keepdogs(TRUE); /* (pets-only; normally we'd be using 'FALSE' here) */
847 
848         /* discard current level; "saving" is used to release dynamic data */
849         savelev(-1, ledger_no(&u.uz), FREE_SAVE);
850         /* create a new level; various things like bestowing a guardian
851            angel on Astral or setting off alarm on Ft.Ludios are handled
852            by goto_level(do.c) so won't occur for replacement levels */
853         mklev();
854 
855         vision_reset();
856         vision_full_recalc = 1;
857         cls();
858         /* was using safe_teleds() but that doesn't honor arrival region
859            on levels which have such; we don't force stairs, just area */
860         u_on_rndspot((u.uhave.amulet ? 1 : 0) /* 'going up' flag */
861                      | (was_in_W_tower ? 2 : 0));
862         losedogs();
863         kill_genocided_monsters();
864         /* u_on_rndspot() might pick a spot that has a monster, or losedogs()
865            might pick the hero's spot (only if there isn't already a monster
866            there), so we might have to move hero or the co-located monster */
867         if ((mtmp = m_at(u.ux, u.uy)) != 0)
868             u_collide_m(mtmp);
869         initrack();
870         if (Punished) {
871             unplacebc();
872             placebc();
873         }
874         docrt();
875         flush_screen(1);
876         deliver_splev_message(); /* level entry */
877         check_special_room(FALSE); /* room entry */
878 #ifdef INSURANCE
879         save_currentstate();
880 #endif
881     } else {
882         pline(unavailcmd, "#wizmakemap");
883     }
884     return 0;
885 }
886 
887 /* ^F command - reveal the level map and any traps on it */
888 STATIC_PTR int
wiz_map(VOID_ARGS)889 wiz_map(VOID_ARGS)
890 {
891     if (wizard) {
892         struct trap *t;
893         long save_Hconf = HConfusion, save_Hhallu = HHallucination;
894 
895         HConfusion = HHallucination = 0L;
896         for (t = ftrap; t != 0; t = t->ntrap) {
897             t->tseen = 1;
898             map_trap(t, TRUE);
899         }
900         do_mapping();
901         HConfusion = save_Hconf;
902         HHallucination = save_Hhallu;
903     } else
904         pline(unavailcmd, visctrl((int) cmd_from_func(wiz_map)));
905     return 0;
906 }
907 
908 /* ^G command - generate monster(s); a count prefix will be honored */
909 STATIC_PTR int
wiz_genesis(VOID_ARGS)910 wiz_genesis(VOID_ARGS)
911 {
912     if (wizard)
913         (void) create_particular();
914     else
915         pline(unavailcmd, visctrl((int) cmd_from_func(wiz_genesis)));
916     return 0;
917 }
918 
919 /* ^O command - display dungeon layout */
920 STATIC_PTR int
wiz_where(VOID_ARGS)921 wiz_where(VOID_ARGS)
922 {
923     if (wizard)
924         (void) print_dungeon(FALSE, (schar *) 0, (xchar *) 0);
925     else
926         pline(unavailcmd, visctrl((int) cmd_from_func(wiz_where)));
927     return 0;
928 }
929 
930 /* ^E command - detect unseen (secret doors, traps, hidden monsters) */
931 STATIC_PTR int
wiz_detect(VOID_ARGS)932 wiz_detect(VOID_ARGS)
933 {
934     if (wizard)
935         (void) findit();
936     else
937         pline(unavailcmd, visctrl((int) cmd_from_func(wiz_detect)));
938     return 0;
939 }
940 
941 /* ^V command - level teleport */
942 STATIC_PTR int
wiz_level_tele(VOID_ARGS)943 wiz_level_tele(VOID_ARGS)
944 {
945     if (wizard)
946         level_tele();
947     else
948         pline(unavailcmd, visctrl((int) cmd_from_func(wiz_level_tele)));
949     return 0;
950 }
951 
952 /* #levelchange command - adjust hero's experience level */
953 STATIC_PTR int
wiz_level_change(VOID_ARGS)954 wiz_level_change(VOID_ARGS)
955 {
956     char buf[BUFSZ] = DUMMY;
957     int newlevel = 0;
958     int ret;
959 
960     getlin("To what experience level do you want to be set?", buf);
961     (void) mungspaces(buf);
962     if (buf[0] == '\033' || buf[0] == '\0')
963         ret = 0;
964     else
965         ret = sscanf(buf, "%d", &newlevel);
966 
967     if (ret != 1) {
968         pline1(Never_mind);
969         return 0;
970     }
971     if (newlevel == u.ulevel) {
972         You("are already that experienced.");
973     } else if (newlevel < u.ulevel) {
974         if (u.ulevel == 1) {
975             You("are already as inexperienced as you can get.");
976             return 0;
977         }
978         if (newlevel < 1)
979             newlevel = 1;
980         while (u.ulevel > newlevel)
981             losexp("#levelchange");
982     } else {
983         if (u.ulevel >= MAXULEV) {
984             You("are already as experienced as you can get.");
985             return 0;
986         }
987         if (newlevel > MAXULEV)
988             newlevel = MAXULEV;
989         while (u.ulevel < newlevel)
990             pluslvl(FALSE);
991     }
992     u.ulevelmax = u.ulevel;
993     return 0;
994 }
995 
996 /* #panic command - test program's panic handling */
997 STATIC_PTR int
wiz_panic(VOID_ARGS)998 wiz_panic(VOID_ARGS)
999 {
1000     if (iflags.debug_fuzzer) {
1001         u.uhp = u.uhpmax = 1000;
1002         u.uen = u.uenmax = 1000;
1003         return 0;
1004     }
1005     if (paranoid_query(ParanoidQuit,
1006                        "Do you want to call panic() and end your game?"))
1007         panic("Crash test.");
1008     return 0;
1009 }
1010 
1011 /* #polyself command - change hero's form */
1012 STATIC_PTR int
wiz_polyself(VOID_ARGS)1013 wiz_polyself(VOID_ARGS)
1014 {
1015     polyself(1);
1016     return 0;
1017 }
1018 
1019 /* #seenv command */
1020 STATIC_PTR int
wiz_show_seenv(VOID_ARGS)1021 wiz_show_seenv(VOID_ARGS)
1022 {
1023     winid win;
1024     int x, y, v, startx, stopx, curx;
1025     char row[COLNO + 1];
1026 
1027     win = create_nhwindow(NHW_TEXT);
1028     /*
1029      * Each seenv description takes up 2 characters, so center
1030      * the seenv display around the hero.
1031      */
1032     startx = max(1, u.ux - (COLNO / 4));
1033     stopx = min(startx + (COLNO / 2), COLNO);
1034     /* can't have a line exactly 80 chars long */
1035     if (stopx - startx == COLNO / 2)
1036         startx++;
1037 
1038     for (y = 0; y < ROWNO; y++) {
1039         for (x = startx, curx = 0; x < stopx; x++, curx += 2) {
1040             if (x == u.ux && y == u.uy) {
1041                 row[curx] = row[curx + 1] = '@';
1042             } else {
1043                 v = levl[x][y].seenv & 0xff;
1044                 if (v == 0)
1045                     row[curx] = row[curx + 1] = ' ';
1046                 else
1047                     Sprintf(&row[curx], "%02x", v);
1048             }
1049         }
1050         /* remove trailing spaces */
1051         for (x = curx - 1; x >= 0; x--)
1052             if (row[x] != ' ')
1053                 break;
1054         row[x + 1] = '\0';
1055 
1056         putstr(win, 0, row);
1057     }
1058     display_nhwindow(win, TRUE);
1059     destroy_nhwindow(win);
1060     return 0;
1061 }
1062 
1063 /* #vision command */
1064 STATIC_PTR int
wiz_show_vision(VOID_ARGS)1065 wiz_show_vision(VOID_ARGS)
1066 {
1067     winid win;
1068     int x, y, v;
1069     char row[COLNO + 1];
1070 
1071     win = create_nhwindow(NHW_TEXT);
1072     Sprintf(row, "Flags: 0x%x could see, 0x%x in sight, 0x%x temp lit",
1073             COULD_SEE, IN_SIGHT, TEMP_LIT);
1074     putstr(win, 0, row);
1075     putstr(win, 0, "");
1076     for (y = 0; y < ROWNO; y++) {
1077         for (x = 1; x < COLNO; x++) {
1078             if (x == u.ux && y == u.uy)
1079                 row[x] = '@';
1080             else {
1081                 v = viz_array[y][x]; /* data access should be hidden */
1082                 if (v == 0)
1083                     row[x] = ' ';
1084                 else
1085                     row[x] = '0' + viz_array[y][x];
1086             }
1087         }
1088         /* remove trailing spaces */
1089         for (x = COLNO - 1; x >= 1; x--)
1090             if (row[x] != ' ')
1091                 break;
1092         row[x + 1] = '\0';
1093 
1094         putstr(win, 0, &row[1]);
1095     }
1096     display_nhwindow(win, TRUE);
1097     destroy_nhwindow(win);
1098     return 0;
1099 }
1100 
1101 /* #wmode command */
1102 STATIC_PTR int
wiz_show_wmodes(VOID_ARGS)1103 wiz_show_wmodes(VOID_ARGS)
1104 {
1105     winid win;
1106     int x, y;
1107     char row[COLNO + 1];
1108     struct rm *lev;
1109     boolean istty = WINDOWPORT("tty");
1110 
1111     win = create_nhwindow(NHW_TEXT);
1112     if (istty)
1113         putstr(win, 0, ""); /* tty only: blank top line */
1114     for (y = 0; y < ROWNO; y++) {
1115         for (x = 0; x < COLNO; x++) {
1116             lev = &levl[x][y];
1117             if (x == u.ux && y == u.uy)
1118                 row[x] = '@';
1119             else if (IS_WALL(lev->typ) || lev->typ == SDOOR)
1120                 row[x] = '0' + (lev->wall_info & WM_MASK);
1121             else if (lev->typ == CORR)
1122                 row[x] = '#';
1123             else if (IS_ROOM(lev->typ) || IS_DOOR(lev->typ))
1124                 row[x] = '.';
1125             else
1126                 row[x] = 'x';
1127         }
1128         row[COLNO] = '\0';
1129         /* map column 0, levl[0][], is off the left edge of the screen */
1130         putstr(win, 0, &row[1]);
1131     }
1132     display_nhwindow(win, TRUE);
1133     destroy_nhwindow(win);
1134     return 0;
1135 }
1136 
1137 /* wizard mode variant of #terrain; internal levl[][].typ values in base-36 */
1138 STATIC_OVL void
wiz_map_levltyp(VOID_ARGS)1139 wiz_map_levltyp(VOID_ARGS)
1140 {
1141     winid win;
1142     int x, y, terrain;
1143     char row[COLNO + 1];
1144     boolean istty = !strcmp(windowprocs.name, "tty");
1145 
1146     win = create_nhwindow(NHW_TEXT);
1147     /* map row 0, levl[][0], is drawn on the second line of tty screen */
1148     if (istty)
1149         putstr(win, 0, ""); /* tty only: blank top line */
1150     for (y = 0; y < ROWNO; y++) {
1151         /* map column 0, levl[0][], is off the left edge of the screen;
1152            it should always have terrain type "undiggable stone" */
1153         for (x = 1; x < COLNO; x++) {
1154             terrain = levl[x][y].typ;
1155             /* assumes there aren't more than 10+26+26 terrain types */
1156             row[x - 1] = (char) ((terrain == STONE && !may_dig(x, y))
1157                                     ? '*'
1158                                     : (terrain < 10)
1159                                        ? '0' + terrain
1160                                        : (terrain < 36)
1161                                           ? 'a' + terrain - 10
1162                                           : 'A' + terrain - 36);
1163         }
1164         x--;
1165         if (levl[0][y].typ != STONE || may_dig(0, y))
1166             row[x++] = '!';
1167         row[x] = '\0';
1168         putstr(win, 0, row);
1169     }
1170 
1171     {
1172         char dsc[BUFSZ];
1173         s_level *slev = Is_special(&u.uz);
1174 
1175         Sprintf(dsc, "D:%d,L:%d", u.uz.dnum, u.uz.dlevel);
1176         /* [dungeon branch features currently omitted] */
1177         /* special level features */
1178         if (slev) {
1179             Sprintf(eos(dsc), " \"%s\"", slev->proto);
1180             /* special level flags (note: dungeon.def doesn't set `maze'
1181                or `hell' for any specific levels so those never show up) */
1182             if (slev->flags.maze_like)
1183                 Strcat(dsc, " mazelike");
1184             if (slev->flags.hellish)
1185                 Strcat(dsc, " hellish");
1186             if (slev->flags.town)
1187                 Strcat(dsc, " town");
1188             if (slev->flags.rogue_like)
1189                 Strcat(dsc, " roguelike");
1190             /* alignment currently omitted to save space */
1191         }
1192         /* level features */
1193         if (level.flags.nfountains)
1194             Sprintf(eos(dsc), " %c:%d", defsyms[S_fountain].sym,
1195                     (int) level.flags.nfountains);
1196         if (level.flags.nsinks)
1197             Sprintf(eos(dsc), " %c:%d", defsyms[S_sink].sym,
1198                     (int) level.flags.nsinks);
1199         if (level.flags.has_vault)
1200             Strcat(dsc, " vault");
1201         if (level.flags.has_shop)
1202             Strcat(dsc, " shop");
1203         if (level.flags.has_temple)
1204             Strcat(dsc, " temple");
1205         if (level.flags.has_court)
1206             Strcat(dsc, " throne");
1207         if (level.flags.has_zoo)
1208             Strcat(dsc, " zoo");
1209         if (level.flags.has_morgue)
1210             Strcat(dsc, " morgue");
1211         if (level.flags.has_barracks)
1212             Strcat(dsc, " barracks");
1213         if (level.flags.has_beehive)
1214             Strcat(dsc, " hive");
1215         if (level.flags.has_swamp)
1216             Strcat(dsc, " swamp");
1217         /* level flags */
1218         if (level.flags.noteleport)
1219             Strcat(dsc, " noTport");
1220         if (level.flags.hardfloor)
1221             Strcat(dsc, " noDig");
1222         if (level.flags.nommap)
1223             Strcat(dsc, " noMMap");
1224         if (!level.flags.hero_memory)
1225             Strcat(dsc, " noMem");
1226         if (level.flags.shortsighted)
1227             Strcat(dsc, " shortsight");
1228         if (level.flags.graveyard)
1229             Strcat(dsc, " graveyard");
1230         if (level.flags.is_maze_lev)
1231             Strcat(dsc, " maze");
1232         if (level.flags.is_cavernous_lev)
1233             Strcat(dsc, " cave");
1234         if (level.flags.arboreal)
1235             Strcat(dsc, " tree");
1236         if (Sokoban)
1237             Strcat(dsc, " sokoban-rules");
1238         /* non-flag info; probably should include dungeon branching
1239            checks (extra stairs and magic portals) here */
1240         if (Invocation_lev(&u.uz))
1241             Strcat(dsc, " invoke");
1242         if (On_W_tower_level(&u.uz))
1243             Strcat(dsc, " tower");
1244         /* append a branch identifier for completeness' sake */
1245         if (u.uz.dnum == 0)
1246             Strcat(dsc, " dungeon");
1247         else if (u.uz.dnum == mines_dnum)
1248             Strcat(dsc, " mines");
1249         else if (In_sokoban(&u.uz))
1250             Strcat(dsc, " sokoban");
1251         else if (u.uz.dnum == quest_dnum)
1252             Strcat(dsc, " quest");
1253         else if (Is_knox(&u.uz))
1254             Strcat(dsc, " ludios");
1255         else if (u.uz.dnum == 1)
1256             Strcat(dsc, " gehennom");
1257         else if (u.uz.dnum == tower_dnum)
1258             Strcat(dsc, " vlad");
1259         else if (In_endgame(&u.uz))
1260             Strcat(dsc, " endgame");
1261         else {
1262             /* somebody's added a dungeon branch we're not expecting */
1263             const char *brname = dungeons[u.uz.dnum].dname;
1264 
1265             if (!brname || !*brname)
1266                 brname = "unknown";
1267             if (!strncmpi(brname, "the ", 4))
1268                 brname += 4;
1269             Sprintf(eos(dsc), " %s", brname);
1270         }
1271         /* limit the line length to map width */
1272         if (strlen(dsc) >= COLNO)
1273             dsc[COLNO - 1] = '\0'; /* truncate */
1274         putstr(win, 0, dsc);
1275     }
1276 
1277     display_nhwindow(win, TRUE);
1278     destroy_nhwindow(win);
1279     return;
1280 }
1281 
1282 /* temporary? hack, since level type codes aren't the same as screen
1283    symbols and only the latter have easily accessible descriptions */
1284 static const char *levltyp[] = {
1285     "stone", "vertical wall", "horizontal wall", "top-left corner wall",
1286     "top-right corner wall", "bottom-left corner wall",
1287     "bottom-right corner wall", "cross wall", "tee-up wall", "tee-down wall",
1288     "tee-left wall", "tee-right wall", "drawbridge wall", "tree",
1289     "secret door", "secret corridor", "pool", "moat", "water",
1290     "drawbridge up", "lava pool", "iron bars", "door", "corridor", "room",
1291     "stairs", "ladder", "fountain", "throne", "sink", "grave", "altar", "ice",
1292     "drawbridge down", "air", "cloud",
1293     /* not a real terrain type, but used for undiggable stone
1294        by wiz_map_levltyp() */
1295     "unreachable/undiggable",
1296     /* padding in case the number of entries above is odd */
1297     ""
1298 };
1299 
1300 /* explanation of base-36 output from wiz_map_levltyp() */
1301 STATIC_OVL void
wiz_levltyp_legend(VOID_ARGS)1302 wiz_levltyp_legend(VOID_ARGS)
1303 {
1304     winid win;
1305     int i, j, last, c;
1306     const char *dsc, *fmt;
1307     char buf[BUFSZ];
1308 
1309     win = create_nhwindow(NHW_TEXT);
1310     putstr(win, 0, "#terrain encodings:");
1311     putstr(win, 0, "");
1312     fmt = " %c - %-28s"; /* TODO: include tab-separated variant for win32 */
1313     *buf = '\0';
1314     /* output in pairs, left hand column holds [0],[1],...,[N/2-1]
1315        and right hand column holds [N/2],[N/2+1],...,[N-1];
1316        N ('last') will always be even, and may or may not include
1317        the empty string entry to pad out the final pair, depending
1318        upon how many other entries are present in levltyp[] */
1319     last = SIZE(levltyp) & ~1;
1320     for (i = 0; i < last / 2; ++i)
1321         for (j = i; j < last; j += last / 2) {
1322             dsc = levltyp[j];
1323             c = !*dsc ? ' '
1324                    : !strncmp(dsc, "unreachable", 11) ? '*'
1325                       /* same int-to-char conversion as wiz_map_levltyp() */
1326                       : (j < 10) ? '0' + j
1327                          : (j < 36) ? 'a' + j - 10
1328                             : 'A' + j - 36;
1329             Sprintf(eos(buf), fmt, c, dsc);
1330             if (j > i) {
1331                 putstr(win, 0, buf);
1332                 *buf = '\0';
1333             }
1334         }
1335     display_nhwindow(win, TRUE);
1336     destroy_nhwindow(win);
1337     return;
1338 }
1339 
1340 /* #wizsmell command - test usmellmon(). */
1341 STATIC_PTR int
wiz_smell(VOID_ARGS)1342 wiz_smell(VOID_ARGS)
1343 {
1344     int ans = 0;
1345     int mndx;  /* monster index */
1346     coord cc;  /* screen pos of unknown glyph */
1347     int glyph; /* glyph at selected position */
1348 
1349     cc.x = u.ux;
1350     cc.y = u.uy;
1351     mndx = 0; /* gcc -Wall lint */
1352     if (!olfaction(youmonst.data)) {
1353         You("are incapable of detecting odors in your present form.");
1354         return 0;
1355     }
1356 
1357     pline("You can move the cursor to a monster that you want to smell.");
1358     do {
1359         pline("Pick a monster to smell.");
1360         ans = getpos(&cc, TRUE, "a monster");
1361         if (ans < 0 || cc.x < 0) {
1362             return 0; /* done */
1363         }
1364         /* Convert the glyph at the selected position to a mndxbol. */
1365         glyph = glyph_at(cc.x, cc.y);
1366         if (glyph_is_monster(glyph))
1367             mndx = glyph_to_mon(glyph);
1368         else
1369             mndx = 0;
1370         /* Is it a monster? */
1371         if (mndx) {
1372             if (!usmellmon(&mons[mndx]))
1373                 pline("That monster seems to give off no smell.");
1374         } else
1375             pline("That is not a monster.");
1376     } while (TRUE);
1377     return 0;
1378 }
1379 
1380 /* #wizinstrinsic command to set some intrinsics for testing */
1381 STATIC_PTR int
wiz_intrinsic(VOID_ARGS)1382 wiz_intrinsic(VOID_ARGS)
1383 {
1384     if (wizard) {
1385         extern const struct propname {
1386             int prop_num;
1387             const char *prop_name;
1388         } propertynames[]; /* timeout.c */
1389         static const char wizintrinsic[] = "#wizintrinsic";
1390         static const char fmt[] = "You are%s %s.";
1391         winid win;
1392         anything any;
1393         char buf[BUFSZ];
1394         int i, j, n, p, amt, typ;
1395         long oldtimeout, newtimeout;
1396         const char *propname;
1397         menu_item *pick_list = (menu_item *) 0;
1398 
1399         any = zeroany;
1400         win = create_nhwindow(NHW_MENU);
1401         start_menu(win);
1402         for (i = 0; (propname = propertynames[i].prop_name) != 0; ++i) {
1403             p = propertynames[i].prop_num;
1404             if (p == HALLUC_RES) {
1405                 /* Grayswandir vs hallucination; ought to be redone to
1406                    use u.uprops[HALLUC].blocked instead of being treated
1407                    as a separate property; letting in be manually toggled
1408                    even only in wizard mode would be asking for trouble... */
1409                 continue;
1410             }
1411             if (p == FIRE_RES) {
1412                 any.a_int = 0;
1413                 add_menu(win, NO_GLYPH, &any, 0, 0, ATR_NONE, "--", FALSE);
1414             }
1415             any.a_int = i + 1; /* +1: avoid 0 */
1416             oldtimeout = u.uprops[p].intrinsic & TIMEOUT;
1417             if (oldtimeout)
1418                 Sprintf(buf, "%-27s [%li]", propname, oldtimeout);
1419             else
1420                 Sprintf(buf, "%s", propname);
1421             add_menu(win, NO_GLYPH, &any, 0, 0, ATR_NONE, buf, FALSE);
1422         }
1423         end_menu(win, "Which intrinsics?");
1424         n = select_menu(win, PICK_ANY, &pick_list);
1425         destroy_nhwindow(win);
1426 
1427         amt = 30; /* TODO: prompt for duration */
1428         for (j = 0; j < n; ++j) {
1429             i = pick_list[j].item.a_int - 1; /* -1: reverse +1 above */
1430             p = propertynames[i].prop_num;
1431             oldtimeout = u.uprops[p].intrinsic & TIMEOUT;
1432             newtimeout = oldtimeout + (long) amt;
1433             switch (p) {
1434             case SICK:
1435             case SLIMED:
1436             case STONED:
1437                 if (oldtimeout > 0L && newtimeout > oldtimeout)
1438                     newtimeout = oldtimeout;
1439                 break;
1440             }
1441 
1442             switch (p) {
1443             case BLINDED:
1444                 make_blinded(newtimeout, TRUE);
1445                 break;
1446 #if 0       /* make_confused() only gives feedback when confusion is
1447              * ending so use the 'default' case for it instead */
1448             case CONFUSION:
1449                 make_confused(newtimeout, TRUE);
1450                 break;
1451 #endif /*0*/
1452             case DEAF:
1453                 make_deaf(newtimeout, TRUE);
1454                 break;
1455             case HALLUC:
1456                 make_hallucinated(newtimeout, TRUE, 0L);
1457                 break;
1458             case SICK:
1459                 typ = !rn2(2) ? SICK_VOMITABLE : SICK_NONVOMITABLE;
1460                 make_sick(newtimeout, wizintrinsic, TRUE, typ);
1461                 break;
1462             case SLIMED:
1463                 Sprintf(buf, fmt,
1464                         !Slimed ? "" : " still", "turning into slime");
1465                 make_slimed(newtimeout, buf);
1466                 break;
1467             case STONED:
1468                 Sprintf(buf, fmt,
1469                         !Stoned ? "" : " still", "turning into stone");
1470                 make_stoned(newtimeout, buf, KILLED_BY, wizintrinsic);
1471                 break;
1472             case STUNNED:
1473                 make_stunned(newtimeout, TRUE);
1474                 break;
1475             case VOMITING:
1476                 Sprintf(buf, fmt, !Vomiting ? "" : " still", "vomiting");
1477                 make_vomiting(newtimeout, FALSE);
1478                 pline1(buf);
1479                 break;
1480             case WARN_OF_MON:
1481                 if (!Warn_of_mon) {
1482                     context.warntype.speciesidx = PM_GRID_BUG;
1483                     context.warntype.species
1484                                          = &mons[context.warntype.speciesidx];
1485                 }
1486                 goto def_feedback;
1487             case GLIB:
1488                 /* slippery fingers applies to gloves if worn at the time
1489                    so persistent inventory might need updating */
1490                 make_glib((int) newtimeout);
1491                 goto def_feedback;
1492             case LEVITATION:
1493             case FLYING:
1494                 float_vs_flight();
1495                 /*FALLTHRU*/
1496             default:
1497  def_feedback:
1498                 pline("Timeout for %s %s %d.", propertynames[i].prop_name,
1499                       oldtimeout ? "increased by" : "set to", amt);
1500                 if (p != GLIB)
1501                     incr_itimeout(&u.uprops[p].intrinsic, amt);
1502                 break;
1503             }
1504             context.botl = 1; /* probably not necessary... */
1505         }
1506         if (n >= 1)
1507             free((genericptr_t) pick_list);
1508         doredraw();
1509     } else
1510         pline(unavailcmd, visctrl((int) cmd_from_func(wiz_intrinsic)));
1511     return 0;
1512 }
1513 
1514 /* #wizrumorcheck command - verify each rumor access */
1515 STATIC_PTR int
wiz_rumor_check(VOID_ARGS)1516 wiz_rumor_check(VOID_ARGS)
1517 {
1518     rumor_check();
1519     return 0;
1520 }
1521 
1522 /* #terrain command -- show known map, inspired by crawl's '|' command */
1523 STATIC_PTR int
doterrain(VOID_ARGS)1524 doterrain(VOID_ARGS)
1525 {
1526     winid men;
1527     menu_item *sel;
1528     anything any;
1529     int n;
1530     int which;
1531 
1532     /*
1533      * normal play: choose between known map without mons, obj, and traps
1534      *  (to see underlying terrain only), or
1535      *  known map without mons and objs (to see traps under mons and objs), or
1536      *  known map without mons (to see objects under monsters);
1537      * explore mode: normal choices plus full map (w/o mons, objs, traps);
1538      * wizard mode: normal and explore choices plus
1539      *  a dump of the internal levl[][].typ codes w/ level flags, or
1540      *  a legend for the levl[][].typ codes dump
1541      */
1542     men = create_nhwindow(NHW_MENU);
1543     start_menu(men);
1544     any = zeroany;
1545     any.a_int = 1;
1546     add_menu(men, NO_GLYPH, &any, 0, 0, ATR_NONE,
1547              "known map without monsters, objects, and traps",
1548              MENU_SELECTED);
1549     any.a_int = 2;
1550     add_menu(men, NO_GLYPH, &any, 0, 0, ATR_NONE,
1551              "known map without monsters and objects",
1552              MENU_UNSELECTED);
1553     any.a_int = 3;
1554     add_menu(men, NO_GLYPH, &any, 0, 0, ATR_NONE,
1555              "known map without monsters",
1556              MENU_UNSELECTED);
1557     if (discover || wizard) {
1558         any.a_int = 4;
1559         add_menu(men, NO_GLYPH, &any, 0, 0, ATR_NONE,
1560                  "full map without monsters, objects, and traps",
1561                  MENU_UNSELECTED);
1562         if (wizard) {
1563             any.a_int = 5;
1564             add_menu(men, NO_GLYPH, &any, 0, 0, ATR_NONE,
1565                      "internal levl[][].typ codes in base-36",
1566                      MENU_UNSELECTED);
1567             any.a_int = 6;
1568             add_menu(men, NO_GLYPH, &any, 0, 0, ATR_NONE,
1569                      "legend of base-36 levl[][].typ codes",
1570                      MENU_UNSELECTED);
1571         }
1572     }
1573     end_menu(men, "View which?");
1574 
1575     n = select_menu(men, PICK_ONE, &sel);
1576     destroy_nhwindow(men);
1577     /*
1578      * n <  0: player used ESC to cancel;
1579      * n == 0: preselected entry was explicitly chosen and got toggled off;
1580      * n == 1: preselected entry was implicitly chosen via <space>|<enter>;
1581      * n == 2: another entry was explicitly chosen, so skip preselected one.
1582      */
1583     which = (n < 0) ? -1 : (n == 0) ? 1 : sel[0].item.a_int;
1584     if (n > 1 && which == 1)
1585         which = sel[1].item.a_int;
1586     if (n > 0)
1587         free((genericptr_t) sel);
1588 
1589     switch (which) {
1590     case 1: /* known map */
1591         reveal_terrain(0, TER_MAP);
1592         break;
1593     case 2: /* known map with known traps */
1594         reveal_terrain(0, TER_MAP | TER_TRP);
1595         break;
1596     case 3: /* known map with known traps and objects */
1597         reveal_terrain(0, TER_MAP | TER_TRP | TER_OBJ);
1598         break;
1599     case 4: /* full map */
1600         reveal_terrain(1, TER_MAP);
1601         break;
1602     case 5: /* map internals */
1603         wiz_map_levltyp();
1604         break;
1605     case 6: /* internal details */
1606         wiz_levltyp_legend();
1607         break;
1608     default:
1609         break;
1610     }
1611     return 0; /* no time elapses */
1612 }
1613 
1614 /* -enlightenment and conduct- */
1615 static winid en_win = WIN_ERR;
1616 static boolean en_via_menu = FALSE;
1617 static const char You_[] = "You ", are[] = "are ", were[] = "were ",
1618                   have[] = "have ", had[] = "had ", can[] = "can ",
1619                   could[] = "could ";
1620 static const char have_been[] = "have been ", have_never[] = "have never ",
1621                   never[] = "never ";
1622 
1623 #define enl_msg(prefix, present, past, suffix, ps) \
1624     enlght_line(prefix, final ? past : present, suffix, ps)
1625 #define you_are(attr, ps) enl_msg(You_, are, were, attr, ps)
1626 #define you_have(attr, ps) enl_msg(You_, have, had, attr, ps)
1627 #define you_can(attr, ps) enl_msg(You_, can, could, attr, ps)
1628 #define you_have_been(goodthing) enl_msg(You_, have_been, were, goodthing, "")
1629 #define you_have_never(badthing) \
1630     enl_msg(You_, have_never, never, badthing, "")
1631 #define you_have_X(something) \
1632     enl_msg(You_, have, (const char *) "", something, "")
1633 
1634 static void
enlght_out(buf)1635 enlght_out(buf)
1636 const char *buf;
1637 {
1638     if (en_via_menu) {
1639         anything any;
1640 
1641         any = zeroany;
1642         add_menu(en_win, NO_GLYPH, &any, 0, 0, ATR_NONE, buf, FALSE);
1643     } else
1644         putstr(en_win, 0, buf);
1645 }
1646 
1647 static void
enlght_line(start,middle,end,ps)1648 enlght_line(start, middle, end, ps)
1649 const char *start, *middle, *end, *ps;
1650 {
1651     char buf[BUFSZ];
1652 
1653     Sprintf(buf, " %s%s%s%s.", start, middle, end, ps);
1654     enlght_out(buf);
1655 }
1656 
1657 /* format increased chance to hit or damage or defense (Protection) */
1658 static char *
enlght_combatinc(inctyp,incamt,final,outbuf)1659 enlght_combatinc(inctyp, incamt, final, outbuf)
1660 const char *inctyp;
1661 int incamt, final;
1662 char *outbuf;
1663 {
1664     const char *modif, *bonus;
1665     boolean invrt;
1666     int absamt;
1667 
1668     absamt = abs(incamt);
1669     /* Protection amount is typically larger than damage or to-hit;
1670        reduce magnitude by a third in order to stretch modifier ranges
1671        (small:1..5, moderate:6..10, large:11..19, huge:20+) */
1672     if (!strcmp(inctyp, "defense"))
1673         absamt = (absamt * 2) / 3;
1674 
1675     if (absamt <= 3)
1676         modif = "small";
1677     else if (absamt <= 6)
1678         modif = "moderate";
1679     else if (absamt <= 12)
1680         modif = "large";
1681     else
1682         modif = "huge";
1683 
1684     modif = !incamt ? "no" : an(modif); /* ("no" case shouldn't happen) */
1685     bonus = (incamt >= 0) ? "bonus" : "penalty";
1686     /* "bonus <foo>" (to hit) vs "<bar> bonus" (damage, defense) */
1687     invrt = strcmp(inctyp, "to hit") ? TRUE : FALSE;
1688 
1689     Sprintf(outbuf, "%s %s %s", modif, invrt ? inctyp : bonus,
1690             invrt ? bonus : inctyp);
1691     if (final || wizard)
1692         Sprintf(eos(outbuf), " (%s%d)", (incamt > 0) ? "+" : "", incamt);
1693 
1694     return outbuf;
1695 }
1696 
1697 /* report half physical or half spell damage */
1698 STATIC_OVL void
enlght_halfdmg(category,final)1699 enlght_halfdmg(category, final)
1700 int category;
1701 int final;
1702 {
1703     const char *category_name;
1704     char buf[BUFSZ];
1705 
1706     switch (category) {
1707     case HALF_PHDAM:
1708         category_name = "physical";
1709         break;
1710     case HALF_SPDAM:
1711         category_name = "spell";
1712         break;
1713     default:
1714         category_name = "unknown";
1715         break;
1716     }
1717     Sprintf(buf, " %s %s damage", (final || wizard) ? "half" : "reduced",
1718             category_name);
1719     enl_msg(You_, "take", "took", buf, from_what(category));
1720 }
1721 
1722 /* is hero actively using water walking capability on water (or lava)? */
1723 STATIC_OVL boolean
walking_on_water()1724 walking_on_water()
1725 {
1726     if (u.uinwater || Levitation || Flying)
1727         return FALSE;
1728     return (boolean) (Wwalking
1729                       && (is_pool(u.ux, u.uy) || is_lava(u.ux, u.uy)));
1730 }
1731 
1732 /* check whether hero is wearing something that player definitely knows
1733    confers the target property; item must have been seen and its type
1734    discovered but it doesn't necessarily have to be fully identified */
1735 STATIC_OVL boolean
cause_known(propindx)1736 cause_known(propindx)
1737 int propindx; /* index of a property which can be conveyed by worn item */
1738 {
1739     register struct obj *o;
1740     long mask = W_ARMOR | W_AMUL | W_RING | W_TOOL;
1741 
1742     /* simpler than from_what()/what_gives(); we don't attempt to
1743        handle artifacts and we deliberately ignore wielded items */
1744     for (o = invent; o; o = o->nobj) {
1745         if (!(o->owornmask & mask))
1746             continue;
1747         if ((int) objects[o->otyp].oc_oprop == propindx
1748             && objects[o->otyp].oc_name_known && o->dknown)
1749             return TRUE;
1750     }
1751     return FALSE;
1752 }
1753 
1754 /* format a characteristic value, accommodating Strength's strangeness */
1755 STATIC_OVL char *
attrval(attrindx,attrvalue,resultbuf)1756 attrval(attrindx, attrvalue, resultbuf)
1757 int attrindx, attrvalue;
1758 char resultbuf[]; /* should be at least [7] to hold "18/100\0" */
1759 {
1760     if (attrindx != A_STR || attrvalue <= 18)
1761         Sprintf(resultbuf, "%d", attrvalue);
1762     else if (attrvalue > STR18(100)) /* 19 to 25 */
1763         Sprintf(resultbuf, "%d", attrvalue - 100);
1764     else /* simplify "18/ **" to be "18/100" */
1765         Sprintf(resultbuf, "18/%02d", attrvalue - 18);
1766     return resultbuf;
1767 }
1768 
1769 void
enlightenment(mode,final)1770 enlightenment(mode, final)
1771 int mode;  /* BASICENLIGHTENMENT | MAGICENLIGHTENMENT (| both) */
1772 int final; /* ENL_GAMEINPROGRESS:0, ENL_GAMEOVERALIVE, ENL_GAMEOVERDEAD */
1773 {
1774     char buf[BUFSZ], tmpbuf[BUFSZ];
1775 
1776     en_win = create_nhwindow(NHW_MENU);
1777     en_via_menu = !final;
1778     if (en_via_menu)
1779         start_menu(en_win);
1780 
1781     Strcpy(tmpbuf, plname);
1782     *tmpbuf = highc(*tmpbuf); /* same adjustment as bottom line */
1783     /* as in background_enlightenment, when poly'd we need to use the saved
1784        gender in u.mfemale rather than the current you-as-monster gender */
1785     Sprintf(buf, "%s the %s's attributes:", tmpbuf,
1786             ((Upolyd ? u.mfemale : flags.female) && urole.name.f)
1787                 ? urole.name.f
1788                 : urole.name.m);
1789 
1790     /* title */
1791     enlght_out(buf); /* "Conan the Archeologist's attributes:" */
1792     /* background and characteristics; ^X or end-of-game disclosure */
1793     if (mode & BASICENLIGHTENMENT) {
1794         /* role, race, alignment, deities, dungeon level, time, experience */
1795         background_enlightenment(mode, final);
1796         /* hit points, energy points, armor class, gold */
1797         basics_enlightenment(mode, final);
1798         /* strength, dexterity, &c */
1799         characteristics_enlightenment(mode, final);
1800     }
1801     /* expanded status line information, including things which aren't
1802        included there due to space considerations--such as obvious
1803        alternative movement indicators (riding, levitation, &c), and
1804        various troubles (turning to stone, trapped, confusion, &c);
1805        shown for both basic and magic enlightenment */
1806     status_enlightenment(mode, final);
1807     /* remaining attributes; shown for potion,&c or wizard mode and
1808        explore mode ^X or end of game disclosure */
1809     if (mode & MAGICENLIGHTENMENT) {
1810         /* intrinsics and other traditional enlightenment feedback */
1811         attributes_enlightenment(mode, final);
1812     }
1813 
1814     if (!en_via_menu) {
1815         display_nhwindow(en_win, TRUE);
1816     } else {
1817         menu_item *selected = 0;
1818 
1819         end_menu(en_win, (char *) 0);
1820         if (select_menu(en_win, PICK_NONE, &selected) > 0)
1821             free((genericptr_t) selected);
1822         en_via_menu = FALSE;
1823     }
1824     destroy_nhwindow(en_win);
1825     en_win = WIN_ERR;
1826 }
1827 
1828 /*ARGSUSED*/
1829 /* display role, race, alignment and such to en_win */
1830 STATIC_OVL void
background_enlightenment(unused_mode,final)1831 background_enlightenment(unused_mode, final)
1832 int unused_mode UNUSED;
1833 int final;
1834 {
1835     const char *role_titl, *rank_titl;
1836     int innategend, difgend, difalgn;
1837     char buf[BUFSZ], tmpbuf[BUFSZ];
1838 
1839     /* note that if poly'd, we need to use u.mfemale instead of flags.female
1840        to access hero's saved gender-as-human/elf/&c rather than current one */
1841     innategend = (Upolyd ? u.mfemale : flags.female) ? 1 : 0;
1842     role_titl = (innategend && urole.name.f) ? urole.name.f : urole.name.m;
1843     rank_titl = rank_of(u.ulevel, Role_switch, innategend);
1844 
1845     enlght_out(""); /* separator after title */
1846     enlght_out("Background:");
1847 
1848     /* if polymorphed, report current shape before underlying role;
1849        will be repeated as first status: "you are transformed" and also
1850        among various attributes: "you are in beast form" (after being
1851        told about lycanthropy) or "you are polymorphed into <a foo>"
1852        (with countdown timer appended for wizard mode); we really want
1853        the player to know he's not a samurai at the moment... */
1854     if (Upolyd) {
1855         struct permonst *uasmon = youmonst.data;
1856 
1857         tmpbuf[0] = '\0';
1858         /* here we always use current gender, not saved role gender */
1859         if (!is_male(uasmon) && !is_female(uasmon) && !is_neuter(uasmon))
1860             Sprintf(tmpbuf, "%s ", genders[flags.female ? 1 : 0].adj);
1861         Sprintf(buf, "%sin %s%s form", !final ? "currently " : "", tmpbuf,
1862                 uasmon->mname);
1863         you_are(buf, "");
1864     }
1865 
1866     /* report role; omit gender if it's redundant (eg, "female priestess") */
1867     tmpbuf[0] = '\0';
1868     if (!urole.name.f
1869         && ((urole.allow & ROLE_GENDMASK) == (ROLE_MALE | ROLE_FEMALE)
1870             || innategend != flags.initgend))
1871         Sprintf(tmpbuf, "%s ", genders[innategend].adj);
1872     buf[0] = '\0';
1873     if (Upolyd)
1874         Strcpy(buf, "actually "); /* "You are actually a ..." */
1875     if (!strcmpi(rank_titl, role_titl)) {
1876         /* omit role when rank title matches it */
1877         Sprintf(eos(buf), "%s, level %d %s%s", an(rank_titl), u.ulevel,
1878                 tmpbuf, urace.noun);
1879     } else {
1880         Sprintf(eos(buf), "%s, a level %d %s%s %s", an(rank_titl), u.ulevel,
1881                 tmpbuf, urace.adj, role_titl);
1882     }
1883     you_are(buf, "");
1884 
1885     /* report alignment (bypass you_are() in order to omit ending period);
1886        adverb is used to distinguish between temporary change (helm of opp.
1887        alignment), permanent change (one-time conversion), and original */
1888     Sprintf(buf, " %s%s%s, %son a mission for %s",
1889             You_, !final ? are : were,
1890             align_str(u.ualign.type),
1891             /* helm of opposite alignment (might hide conversion) */
1892             (u.ualign.type != u.ualignbase[A_CURRENT])
1893                /* what's the past tense of "currently"? if we used "formerly"
1894                   it would sound like a reference to the original alignment */
1895                ? (!final ? "currently " : "temporarily ")
1896                /* permanent conversion */
1897                : (u.ualign.type != u.ualignbase[A_ORIGINAL])
1898                   /* and what's the past tense of "now"? certainly not "then"
1899                      in a context like this...; "belatedly" == weren't that
1900                      way sooner (in other words, didn't start that way) */
1901                   ? (!final ? "now " : "belatedly ")
1902                   /* atheist (ignored in very early game) */
1903                   : (!u.uconduct.gnostic && moves > 1000L)
1904                      ? "nominally "
1905                      /* lastly, normal case */
1906                      : "",
1907             u_gname());
1908     enlght_out(buf);
1909     /* show the rest of this game's pantheon (finishes previous sentence)
1910        [appending "also Moloch" at the end would allow for straightforward
1911        trailing "and" on all three aligned entries but looks too verbose] */
1912     Sprintf(buf, " who %s opposed by", !final ? "is" : "was");
1913     if (u.ualign.type != A_LAWFUL)
1914         Sprintf(eos(buf), " %s (%s) and", align_gname(A_LAWFUL),
1915                 align_str(A_LAWFUL));
1916     if (u.ualign.type != A_NEUTRAL)
1917         Sprintf(eos(buf), " %s (%s)%s", align_gname(A_NEUTRAL),
1918                 align_str(A_NEUTRAL),
1919                 (u.ualign.type != A_CHAOTIC) ? " and" : "");
1920     if (u.ualign.type != A_CHAOTIC)
1921         Sprintf(eos(buf), " %s (%s)", align_gname(A_CHAOTIC),
1922                 align_str(A_CHAOTIC));
1923     Strcat(buf, "."); /* terminate sentence */
1924     enlght_out(buf);
1925 
1926     /* show original alignment,gender,race,role if any have been changed;
1927        giving separate message for temporary alignment change bypasses need
1928        for tricky phrasing otherwise necessitated by possibility of having
1929        helm of opposite alignment mask a permanent alignment conversion */
1930     difgend = (innategend != flags.initgend);
1931     difalgn = (((u.ualign.type != u.ualignbase[A_CURRENT]) ? 1 : 0)
1932                + ((u.ualignbase[A_CURRENT] != u.ualignbase[A_ORIGINAL])
1933                   ? 2 : 0));
1934     if (difalgn & 1) { /* have temporary alignment so report permanent one */
1935         Sprintf(buf, "actually %s", align_str(u.ualignbase[A_CURRENT]));
1936         you_are(buf, "");
1937         difalgn &= ~1; /* suppress helm from "started out <foo>" message */
1938     }
1939     if (difgend || difalgn) { /* sex change or perm align change or both */
1940         Sprintf(buf, " You started out %s%s%s.",
1941                 difgend ? genders[flags.initgend].adj : "",
1942                 (difgend && difalgn) ? " and " : "",
1943                 difalgn ? align_str(u.ualignbase[A_ORIGINAL]) : "");
1944         enlght_out(buf);
1945     }
1946 
1947     /* As of 3.6.2: dungeon level, so that ^X really has all status info as
1948        claimed by the comment below; this reveals more information than
1949        the basic status display, but that's one of the purposes of ^X;
1950        similar information is revealed by #overview; the "You died in
1951        <location>" given by really_done() is more rudimentary than this */
1952     *buf = *tmpbuf = '\0';
1953     if (In_endgame(&u.uz)) {
1954         int egdepth = observable_depth(&u.uz);
1955 
1956         (void) endgamelevelname(tmpbuf, egdepth);
1957         Sprintf(buf, "in the endgame, on the %s%s",
1958                 !strncmp(tmpbuf, "Plane", 5) ? "Elemental " : "", tmpbuf);
1959     } else if (Is_knox(&u.uz)) {
1960         /* this gives away the fact that the knox branch is only 1 level */
1961         Sprintf(buf, "on the %s level", dungeons[u.uz.dnum].dname);
1962         /* TODO? maybe phrase it differently when actually inside the fort,
1963            if we're able to determine that (not trivial) */
1964     } else {
1965         char dgnbuf[QBUFSZ];
1966 
1967         Strcpy(dgnbuf, dungeons[u.uz.dnum].dname);
1968         if (!strncmpi(dgnbuf, "The ", 4))
1969             *dgnbuf = lowc(*dgnbuf);
1970         Sprintf(tmpbuf, "level %d",
1971                 In_quest(&u.uz) ? dunlev(&u.uz) : depth(&u.uz));
1972         /* TODO? maybe extend this bit to include various other automatic
1973            annotations from the dungeon overview code */
1974         if (Is_rogue_level(&u.uz))
1975             Strcat(tmpbuf, ", a primitive area");
1976         else if (Is_bigroom(&u.uz) && !Blind)
1977             Strcat(tmpbuf, ", a very big room");
1978         Sprintf(buf, "in %s, on %s", dgnbuf, tmpbuf);
1979     }
1980     you_are(buf, "");
1981 
1982     /* this is shown even if the 'time' option is off */
1983     if (moves == 1L) {
1984         you_have("just started your adventure", "");
1985     } else {
1986         /* 'turns' grates on the nerves in this context... */
1987         Sprintf(buf, "the dungeon %ld turn%s ago", moves, plur(moves));
1988         /* same phrasing for current and final: "entered" is unconditional */
1989         enlght_line(You_, "entered ", buf, "");
1990     }
1991 
1992     /* for gameover, these have been obtained in really_done() so that they
1993        won't vary if user leaves a disclosure prompt or --More-- unanswered
1994        long enough for the dynamic value to change between then and now */
1995     if (final ? iflags.at_midnight : midnight()) {
1996         enl_msg("It ", "is ", "was ", "the midnight hour", "");
1997     } else if (final ? iflags.at_night : night()) {
1998         enl_msg("It ", "is ", "was ", "nighttime", "");
1999     }
2000     /* other environmental factors */
2001     if (flags.moonphase == FULL_MOON || flags.moonphase == NEW_MOON) {
2002         /* [This had "tonight" but has been changed to "in effect".
2003            There is a similar issue to Friday the 13th--it's the value
2004            at the start of the current session but that session might
2005            have dragged on for an arbitrary amount of time.  We want to
2006            report the values that currently affect play--or affected
2007            play when game ended--rather than actual outside situation.] */
2008         Sprintf(buf, "a %s moon in effect%s",
2009                 (flags.moonphase == FULL_MOON) ? "full"
2010                 : (flags.moonphase == NEW_MOON) ? "new"
2011                   /* showing these would probably just lead to confusion
2012                      since they have no effect on game play... */
2013                   : (flags.moonphase < FULL_MOON) ? "first quarter"
2014                     : "last quarter",
2015                 /* we don't have access to 'how' here--aside from survived
2016                    vs died--so settle for general platitude */
2017                 final ? " when your adventure ended" : "");
2018         enl_msg("There ", "is ", "was ", buf, "");
2019     }
2020     if (flags.friday13) {
2021         /* let player know that friday13 penalty is/was in effect;
2022            we don't say "it is/was Friday the 13th" because that was at
2023            the start of the session and it might be past midnight (or
2024            days later if the game has been paused without save/restore),
2025            so phrase this similar to the start up message */
2026         Sprintf(buf, " Bad things %s on Friday the 13th.",
2027                 !final ? "can happen"
2028                 : (final == ENL_GAMEOVERALIVE) ? "could have happened"
2029                   /* there's no may to tell whether -1 Luck made a
2030                      difference but hero has died... */
2031                   : "happened");
2032         enlght_out(buf);
2033     }
2034 
2035     if (!Upolyd) {
2036         int ulvl = (int) u.ulevel;
2037         /* [flags.showexp currently does not matter; should it?] */
2038 
2039         /* experience level is already shown above */
2040         Sprintf(buf, "%-1ld experience point%s", u.uexp, plur(u.uexp));
2041         /* TODO?
2042          *  Remove wizard-mode restriction since patient players can
2043          *  determine the numbers needed without resorting to spoilers
2044          *  (even before this started being disclosed for 'final';
2045          *  just enable 'showexp' and look at normal status lines
2046          *  after drinking gain level potions or eating wraith corpses
2047          *  or being level-drained by vampires).
2048          */
2049         if (ulvl < 30 && (final || wizard)) {
2050             long nxtlvl = newuexp(ulvl), delta = nxtlvl - u.uexp;
2051 
2052             Sprintf(eos(buf), ", %ld %s%sneeded %s level %d",
2053                     delta, (u.uexp > 0) ? "more " : "",
2054                     /* present tense=="needed", past tense=="were needed" */
2055                     !final ? "" : (delta == 1L) ? "was " : "were ",
2056                     /* "for": grammatically iffy but less likely to wrap */
2057                     (ulvl < 18) ? "to attain" : "for", (ulvl + 1));
2058         }
2059         you_have(buf, "");
2060     }
2061 #ifdef SCORE_ON_BOTL
2062     if (flags.showscore) {
2063         /* describes what's shown on status line, which is an approximation;
2064            only show it here if player has the 'showscore' option enabled */
2065         Sprintf(buf, "%ld%s", botl_score(),
2066                 !final ? "" : " before end-of-game adjustments");
2067         enl_msg("Your score ", "is ", "was ", buf, "");
2068     }
2069 #endif
2070 }
2071 
2072 /* hit points, energy points, armor class -- essential information which
2073    doesn't fit very well in other categories */
2074 /*ARGSUSED*/
2075 STATIC_OVL void
basics_enlightenment(mode,final)2076 basics_enlightenment(mode, final)
2077 int mode UNUSED;
2078 int final;
2079 {
2080     static char Power[] = "energy points (spell power)";
2081     char buf[BUFSZ];
2082     int pw = u.uen, hp = (Upolyd ? u.mh : u.uhp),
2083         pwmax = u.uenmax, hpmax = (Upolyd ? u.mhmax : u.uhpmax);
2084 
2085     enlght_out(""); /* separator after background */
2086     enlght_out("Basics:");
2087 
2088     if (hp < 0)
2089         hp = 0;
2090     /* "1 out of 1" rather than "all" if max is only 1; should never happen */
2091     if (hp == hpmax && hpmax > 1)
2092         Sprintf(buf, "all %d hit points", hpmax);
2093     else
2094         Sprintf(buf, "%d out of %d hit point%s", hp, hpmax, plur(hpmax));
2095     you_have(buf, "");
2096 
2097     /* low max energy is feasible, so handle couple of extra special cases */
2098     if (pwmax == 0 || (pw == pwmax && pwmax == 2)) /* both: "all 2" is silly */
2099         Sprintf(buf, "%s %s", !pwmax ? "no" : "both", Power);
2100     else if (pw == pwmax && pwmax > 2)
2101         Sprintf(buf, "all %d %s", pwmax, Power);
2102     else
2103         Sprintf(buf, "%d out of %d %s", pw, pwmax, Power);
2104     you_have(buf, "");
2105 
2106     if (Upolyd) {
2107         switch (mons[u.umonnum].mlevel) {
2108         case 0:
2109             /* status line currently being explained shows "HD:0" */
2110             Strcpy(buf, "0 hit dice (actually 1/2)");
2111             break;
2112         case 1:
2113             Strcpy(buf, "1 hit die");
2114             break;
2115         default:
2116             Sprintf(buf, "%d hit dice", mons[u.umonnum].mlevel);
2117             break;
2118         }
2119         you_have(buf, "");
2120     }
2121 
2122     Sprintf(buf, "%d", u.uac);
2123     enl_msg("Your armor class ", "is ", "was ", buf, "");
2124 
2125     /* gold; similar to doprgold(#seegold) but without shop billing info;
2126        same amount as shown on status line which ignores container contents */
2127     {
2128         static const char Your_wallet[] = "Your wallet ";
2129         long umoney = money_cnt(invent);
2130 
2131         if (!umoney) {
2132             enl_msg(Your_wallet, "is ", "was ", "empty", "");
2133         } else {
2134             Sprintf(buf, "%ld %s", umoney, currency(umoney));
2135             enl_msg(Your_wallet, "contains ", "contained ", buf, "");
2136         }
2137     }
2138 
2139     if (flags.pickup) {
2140         char ocl[MAXOCLASSES + 1];
2141 
2142         Strcpy(buf, "on");
2143         oc_to_str(flags.pickup_types, ocl);
2144         Sprintf(eos(buf), " for %s%s%s",
2145                 *ocl ? "'" : "", *ocl ? ocl : "all types", *ocl ? "'" : "");
2146         if (flags.pickup_thrown && *ocl) /* *ocl: don't show if 'all types' */
2147             Strcat(buf, " plus thrown");
2148         if (apelist)
2149             Strcat(buf, ", with exceptions");
2150     } else
2151         Strcpy(buf, "off");
2152     enl_msg("Autopickup ", "is ", "was ", buf, "");
2153 }
2154 
2155 /* characteristics: expanded version of bottom line strength, dexterity, &c */
2156 STATIC_OVL void
characteristics_enlightenment(mode,final)2157 characteristics_enlightenment(mode, final)
2158 int mode;
2159 int final;
2160 {
2161     char buf[BUFSZ];
2162 
2163     enlght_out("");
2164     Sprintf(buf, "%s Characteristics:", !final ? "Current" : "Final");
2165     enlght_out(buf);
2166 
2167     /* bottom line order */
2168     one_characteristic(mode, final, A_STR); /* strength */
2169     one_characteristic(mode, final, A_DEX); /* dexterity */
2170     one_characteristic(mode, final, A_CON); /* constitution */
2171     one_characteristic(mode, final, A_INT); /* intelligence */
2172     one_characteristic(mode, final, A_WIS); /* wisdom */
2173     one_characteristic(mode, final, A_CHA); /* charisma */
2174 }
2175 
2176 /* display one attribute value for characteristics_enlightenment() */
2177 STATIC_OVL void
one_characteristic(mode,final,attrindx)2178 one_characteristic(mode, final, attrindx)
2179 int mode, final, attrindx;
2180 {
2181     extern const char *const attrname[]; /* attrib.c */
2182     boolean hide_innate_value = FALSE, interesting_alimit;
2183     int acurrent, abase, apeak, alimit;
2184     const char *paren_pfx;
2185     char subjbuf[BUFSZ], valubuf[BUFSZ], valstring[32];
2186 
2187     /* being polymorphed or wearing certain cursed items prevents
2188        hero from reliably tracking changes to characteristics so
2189        we don't show base & peak values then; when the items aren't
2190        cursed, hero could take them off to check underlying values
2191        and we show those in such case so that player doesn't need
2192        to actually resort to doing that */
2193     if (Upolyd) {
2194         hide_innate_value = TRUE;
2195     } else if (Fixed_abil) {
2196         if (stuck_ring(uleft, RIN_SUSTAIN_ABILITY)
2197             || stuck_ring(uright, RIN_SUSTAIN_ABILITY))
2198             hide_innate_value = TRUE;
2199     }
2200     switch (attrindx) {
2201     case A_STR:
2202         if (uarmg && uarmg->otyp == GAUNTLETS_OF_POWER && uarmg->cursed)
2203             hide_innate_value = TRUE;
2204         break;
2205     case A_DEX:
2206         break;
2207     case A_CON:
2208         if (uwep && uwep->oartifact == ART_OGRESMASHER && uwep->cursed)
2209             hide_innate_value = TRUE;
2210         break;
2211     case A_INT:
2212         if (uarmh && uarmh->otyp == DUNCE_CAP && uarmh->cursed)
2213             hide_innate_value = TRUE;
2214         break;
2215     case A_WIS:
2216         if (uarmh && uarmh->otyp == DUNCE_CAP && uarmh->cursed)
2217             hide_innate_value = TRUE;
2218         break;
2219     case A_CHA:
2220         break;
2221     default:
2222         return; /* impossible */
2223     };
2224     /* note: final disclosure includes MAGICENLIGHTENTMENT */
2225     if ((mode & MAGICENLIGHTENMENT) && !Upolyd)
2226         hide_innate_value = FALSE;
2227 
2228     acurrent = ACURR(attrindx);
2229     (void) attrval(attrindx, acurrent, valubuf); /* Sprintf(valubuf,"%d",) */
2230     Sprintf(subjbuf, "Your %s ", attrname[attrindx]);
2231 
2232     if (!hide_innate_value) {
2233         /* show abase, amax, and/or attrmax if acurr doesn't match abase
2234            (a magic bonus or penalty is in effect) or abase doesn't match
2235            amax (some points have been lost to poison or exercise abuse
2236            and are restorable) or attrmax is different from normal human
2237            (while game is in progress; trying to reduce dependency on
2238            spoilers to keep track of such stuff) or attrmax was different
2239            from abase (at end of game; this attribute wasn't maxed out) */
2240         abase = ABASE(attrindx);
2241         apeak = AMAX(attrindx);
2242         alimit = ATTRMAX(attrindx);
2243         /* criterium for whether the limit is interesting varies */
2244         interesting_alimit =
2245             final ? TRUE /* was originally `(abase != alimit)' */
2246                   : (alimit != (attrindx != A_STR ? 18 : STR18(100)));
2247         paren_pfx = final ? " (" : " (current; ";
2248         if (acurrent != abase) {
2249             Sprintf(eos(valubuf), "%sbase:%s", paren_pfx,
2250                     attrval(attrindx, abase, valstring));
2251             paren_pfx = ", ";
2252         }
2253         if (abase != apeak) {
2254             Sprintf(eos(valubuf), "%speak:%s", paren_pfx,
2255                     attrval(attrindx, apeak, valstring));
2256             paren_pfx = ", ";
2257         }
2258         if (interesting_alimit) {
2259             Sprintf(eos(valubuf), "%s%slimit:%s", paren_pfx,
2260                     /* more verbose if exceeding 'limit' due to magic bonus */
2261                     (acurrent > alimit) ? "innate " : "",
2262                     attrval(attrindx, alimit, valstring));
2263             /* paren_pfx = ", "; */
2264         }
2265         if (acurrent != abase || abase != apeak || interesting_alimit)
2266             Strcat(valubuf, ")");
2267     }
2268     enl_msg(subjbuf, "is ", "was ", valubuf, "");
2269 }
2270 
2271 /* status: selected obvious capabilities, assorted troubles */
2272 STATIC_OVL void
status_enlightenment(mode,final)2273 status_enlightenment(mode, final)
2274 int mode;
2275 int final;
2276 {
2277     boolean magic = (mode & MAGICENLIGHTENMENT) ? TRUE : FALSE;
2278     int cap, wtype;
2279     char buf[BUFSZ], youtoo[BUFSZ];
2280     boolean Riding = (u.usteed
2281                       /* if hero dies while dismounting, u.usteed will still
2282                          be set; we want to ignore steed in that situation */
2283                       && !(final == ENL_GAMEOVERDEAD
2284                            && !strcmp(killer.name, "riding accident")));
2285     const char *steedname = (!Riding ? (char *) 0
2286                       : x_monnam(u.usteed,
2287                                  u.usteed->mtame ? ARTICLE_YOUR : ARTICLE_THE,
2288                                  (char *) 0,
2289                                  (SUPPRESS_SADDLE | SUPPRESS_HALLUCINATION),
2290                                  FALSE));
2291 
2292     /*\
2293      * Status (many are abbreviated on bottom line; others are or
2294      *     should be discernible to the hero hence to the player)
2295     \*/
2296     enlght_out(""); /* separator after title or characteristics */
2297     enlght_out(final ? "Final Status:" : "Current Status:");
2298 
2299     Strcpy(youtoo, You_);
2300     /* not a traditional status but inherently obvious to player; more
2301        detail given below (attributes section) for magic enlightenment */
2302     if (Upolyd) {
2303         Strcpy(buf, "transformed");
2304         if (ugenocided())
2305             Sprintf(eos(buf), " and %s %s inside",
2306                     final ? "felt" : "feel", udeadinside());
2307         you_are(buf, "");
2308     }
2309     /* not a trouble, but we want to display riding status before maybe
2310        reporting steed as trapped or hero stuck to cursed saddle */
2311     if (Riding) {
2312         Sprintf(buf, "riding %s", steedname);
2313         you_are(buf, "");
2314         Sprintf(eos(youtoo), "and %s ", steedname);
2315     }
2316     /* other movement situations that hero should always know */
2317     if (Levitation) {
2318         if (Lev_at_will && magic)
2319             you_are("levitating, at will", "");
2320         else
2321             enl_msg(youtoo, are, were, "levitating", from_what(LEVITATION));
2322     } else if (Flying) { /* can only fly when not levitating */
2323         enl_msg(youtoo, are, were, "flying", from_what(FLYING));
2324     }
2325     if (Underwater) {
2326         you_are("underwater", "");
2327     } else if (u.uinwater) {
2328         you_are(Swimming ? "swimming" : "in water", from_what(SWIMMING));
2329     } else if (walking_on_water()) {
2330         /* show active Wwalking here, potential Wwalking elsewhere */
2331         Sprintf(buf, "walking on %s",
2332                 is_pool(u.ux, u.uy) ? "water"
2333                 : is_lava(u.ux, u.uy) ? "lava"
2334                   : surface(u.ux, u.uy)); /* catchall; shouldn't happen */
2335         you_are(buf, from_what(WWALKING));
2336     }
2337     if (Upolyd && (u.uundetected || U_AP_TYPE != M_AP_NOTHING))
2338         youhiding(TRUE, final);
2339 
2340     /* internal troubles, mostly in the order that prayer ranks them */
2341     if (Stoned) {
2342         if (final && (Stoned & I_SPECIAL))
2343             enlght_out(" You turned into stone.");
2344         else
2345             you_are("turning to stone", "");
2346     }
2347     if (Slimed) {
2348         if (final && (Slimed & I_SPECIAL))
2349             enlght_out(" You turned into slime.");
2350         else
2351             you_are("turning into slime", "");
2352     }
2353     if (Strangled) {
2354         if (u.uburied) {
2355             you_are("buried", "");
2356         } else {
2357             if (final && (Strangled & I_SPECIAL)) {
2358                 enlght_out(" You died from strangulation.");
2359             } else {
2360                 Strcpy(buf, "being strangled");
2361                 if (wizard)
2362                     Sprintf(eos(buf), " (%ld)", (Strangled & TIMEOUT));
2363                 you_are(buf, from_what(STRANGLED));
2364             }
2365         }
2366     }
2367     if (Sick) {
2368         /* the two types of sickness are lumped together; hero can be
2369            afflicted by both but there is only one timeout; botl status
2370            puts TermIll before FoodPois and death due to timeout reports
2371            terminal illness if both are in effect, so do the same here */
2372         if (final && (Sick & I_SPECIAL)) {
2373             Sprintf(buf, " %sdied from %s.", You_, /* has trailing space */
2374                     (u.usick_type & SICK_NONVOMITABLE)
2375                     ? "terminal illness" : "food poisoning");
2376             enlght_out(buf);
2377         } else {
2378             /* unlike death due to sickness, report the two cases separately
2379                because it is possible to cure one without curing the other */
2380             if (u.usick_type & SICK_NONVOMITABLE)
2381                 you_are("terminally sick from illness", "");
2382             if (u.usick_type & SICK_VOMITABLE)
2383                 you_are("terminally sick from food poisoning", "");
2384         }
2385     }
2386     if (Vomiting)
2387         you_are("nauseated", "");
2388     if (Stunned)
2389         you_are("stunned", "");
2390     if (Confusion)
2391         you_are("confused", "");
2392     if (Hallucination)
2393         you_are("hallucinating", "");
2394     if (Blind) {
2395         /* from_what() (currently wizard-mode only) checks !haseyes()
2396            before u.uroleplay.blind, so we should too */
2397         Sprintf(buf, "%s blind",
2398                 !haseyes(youmonst.data) ? "innately"
2399                 : u.uroleplay.blind ? "permanently"
2400                   /* better phrasing desperately wanted... */
2401                   : Blindfolded_only ? "deliberately"
2402                     : "temporarily");
2403         if (wizard && (Blinded & TIMEOUT) != 0L
2404             && !u.uroleplay.blind && haseyes(youmonst.data))
2405             Sprintf(eos(buf), " (%ld)", (Blinded & TIMEOUT));
2406         /* !haseyes: avoid "you are innately blind innately" */
2407         you_are(buf, !haseyes(youmonst.data) ? "" : from_what(BLINDED));
2408     }
2409     if (Deaf)
2410         you_are("deaf", from_what(DEAF));
2411 
2412     /* external troubles, more or less */
2413     if (Punished) {
2414         if (uball) {
2415             Sprintf(buf, "chained to %s", ansimpleoname(uball));
2416         } else {
2417             impossible("Punished without uball?");
2418             Strcpy(buf, "punished");
2419         }
2420         you_are(buf, "");
2421     }
2422     if (u.utrap) {
2423         char predicament[BUFSZ];
2424         struct trap *t;
2425         boolean anchored = (u.utraptype == TT_BURIEDBALL);
2426 
2427         if (anchored) {
2428             Strcpy(predicament, "tethered to something buried");
2429         } else if (u.utraptype == TT_INFLOOR || u.utraptype == TT_LAVA) {
2430             Sprintf(predicament, "stuck in %s", the(surface(u.ux, u.uy)));
2431         } else {
2432             Strcpy(predicament, "trapped");
2433             if ((t = t_at(u.ux, u.uy)) != 0)
2434                 Sprintf(eos(predicament), " in %s",
2435                         an(defsyms[trap_to_defsym(t->ttyp)].explanation));
2436         }
2437         if (u.usteed) { /* not `Riding' here */
2438             Sprintf(buf, "%s%s ", anchored ? "you and " : "", steedname);
2439             *buf = highc(*buf);
2440             enl_msg(buf, (anchored ? "are " : "is "),
2441                     (anchored ? "were " : "was "), predicament, "");
2442         } else
2443             you_are(predicament, "");
2444     } /* (u.utrap) */
2445     if (u.uswallow) {
2446         Sprintf(buf, "swallowed by %s", a_monnam(u.ustuck));
2447         if (wizard)
2448             Sprintf(eos(buf), " (%u)", u.uswldtim);
2449         you_are(buf, "");
2450     } else if (u.ustuck) {
2451         Sprintf(buf, "%s %s",
2452                 (Upolyd && sticks(youmonst.data)) ? "holding" : "held by",
2453                 a_monnam(u.ustuck));
2454         you_are(buf, "");
2455     }
2456     if (Riding) {
2457         struct obj *saddle = which_armor(u.usteed, W_SADDLE);
2458 
2459         if (saddle && saddle->cursed) {
2460             Sprintf(buf, "stuck to %s %s", s_suffix(steedname),
2461                     simpleonames(saddle));
2462             you_are(buf, "");
2463         }
2464     }
2465     if (Wounded_legs) {
2466         /* when mounted, Wounded_legs applies to steed rather than to
2467            hero; we only report steed's wounded legs in wizard mode */
2468         if (u.usteed) { /* not `Riding' here */
2469             if (wizard && steedname) {
2470                 Strcpy(buf, steedname);
2471                 *buf = highc(*buf);
2472                 enl_msg(buf, " has", " had", " wounded legs", "");
2473             }
2474         } else {
2475             Sprintf(buf, "wounded %s", makeplural(body_part(LEG)));
2476             you_have(buf, "");
2477         }
2478     }
2479     if (Glib) {
2480         Sprintf(buf, "slippery %s", fingers_or_gloves(TRUE));
2481         if (wizard)
2482             Sprintf(eos(buf), " (%ld)", (Glib & TIMEOUT));
2483         you_have(buf, "");
2484     }
2485     if (Fumbling) {
2486         if (magic || cause_known(FUMBLING))
2487             enl_msg(You_, "fumble", "fumbled", "", from_what(FUMBLING));
2488     }
2489     if (Sleepy) {
2490         if (magic || cause_known(SLEEPY)) {
2491             Strcpy(buf, from_what(SLEEPY));
2492             if (wizard)
2493                 Sprintf(eos(buf), " (%ld)", (HSleepy & TIMEOUT));
2494             enl_msg("You ", "fall", "fell", " asleep uncontrollably", buf);
2495         }
2496     }
2497     /* hunger/nutrition */
2498     if (Hunger) {
2499         if (magic || cause_known(HUNGER))
2500             enl_msg(You_, "hunger", "hungered", " rapidly",
2501                     from_what(HUNGER));
2502     }
2503     Strcpy(buf, hu_stat[u.uhs]); /* hunger status; omitted if "normal" */
2504     mungspaces(buf);             /* strip trailing spaces */
2505     if (*buf) {
2506         *buf = lowc(*buf); /* override capitalization */
2507         if (!strcmp(buf, "weak"))
2508             Strcat(buf, " from severe hunger");
2509         else if (!strncmp(buf, "faint", 5)) /* fainting, fainted */
2510             Strcat(buf, " due to starvation");
2511         you_are(buf, "");
2512     }
2513     /* encumbrance */
2514     if ((cap = near_capacity()) > UNENCUMBERED) {
2515         const char *adj = "?_?"; /* (should always get overridden) */
2516 
2517         Strcpy(buf, enc_stat[cap]);
2518         *buf = lowc(*buf);
2519         switch (cap) {
2520         case SLT_ENCUMBER:
2521             adj = "slightly";
2522             break; /* burdened */
2523         case MOD_ENCUMBER:
2524             adj = "moderately";
2525             break; /* stressed */
2526         case HVY_ENCUMBER:
2527             adj = "very";
2528             break; /* strained */
2529         case EXT_ENCUMBER:
2530             adj = "extremely";
2531             break; /* overtaxed */
2532         case OVERLOADED:
2533             adj = "not possible";
2534             break;
2535         }
2536         Sprintf(eos(buf), "; movement %s %s%s", !final ? "is" : "was", adj,
2537                 (cap < OVERLOADED) ? " slowed" : "");
2538         you_are(buf, "");
2539     } else {
2540         /* last resort entry, guarantees Status section is non-empty
2541            (no longer needed for that purpose since weapon status added;
2542            still useful though) */
2543         you_are("unencumbered", "");
2544     }
2545 
2546     /* report being weaponless; distinguish whether gloves are worn */
2547     if (!uwep) {
2548         you_are(uarmg ? "empty handed" /* gloves imply hands */
2549                       : humanoid(youmonst.data)
2550                          /* hands but no weapon and no gloves */
2551                          ? "bare handed"
2552                          /* alternate phrasing for paws or lack of hands */
2553                          : "not wielding anything",
2554                 "");
2555     /* two-weaponing implies hands (can't be polymorphed) and
2556        a weapon or wep-tool (not other odd stuff) in each hand */
2557     } else if (u.twoweap) {
2558         you_are("wielding two weapons at once", "");
2559     /* report most weapons by their skill class (so a katana will be
2560        described as a long sword, for instance; mattock and hook are
2561        exceptions), or wielded non-weapon item by its object class */
2562     } else {
2563         const char *what = weapon_descr(uwep);
2564 
2565         if (!strcmpi(what, "armor") || !strcmpi(what, "food")
2566             || !strcmpi(what, "venom"))
2567             Sprintf(buf, "wielding some %s", what);
2568         else
2569             Sprintf(buf, "wielding %s",
2570                     (uwep->quan == 1L) ? an(what) : makeplural(what));
2571         you_are(buf, "");
2572     }
2573     /*
2574      * Skill with current weapon.  Might help players who've never
2575      * noticed #enhance or decided that it was pointless.
2576      *
2577      * TODO?  Maybe merge wielding line and skill line into one sentence.
2578      */
2579     if ((wtype = uwep_skill_type()) != P_NONE) {
2580         char sklvlbuf[20];
2581         int sklvl = P_SKILL(wtype);
2582         boolean hav = (sklvl != P_UNSKILLED && sklvl != P_SKILLED);
2583 
2584         if (sklvl == P_ISRESTRICTED)
2585             Strcpy(sklvlbuf, "no");
2586         else
2587             (void) lcase(skill_level_name(wtype, sklvlbuf));
2588         /* "you have no/basic/expert/master/grand-master skill with <skill>"
2589            or "you are unskilled/skilled in <skill>" */
2590         Sprintf(buf, "%s %s %s", sklvlbuf,
2591                 hav ? "skill with" : "in", skill_name(wtype));
2592         if (can_advance(wtype, FALSE))
2593             Sprintf(eos(buf), " and %s that",
2594                     !final ? "can enhance" : "could have enhanced");
2595         if (hav)
2596             you_have(buf, "");
2597         else
2598             you_are(buf, "");
2599     }
2600     /* report 'nudity' */
2601     if (!uarm && !uarmu && !uarmc && !uarms && !uarmg && !uarmf && !uarmh) {
2602         if (u.uroleplay.nudist)
2603             enl_msg(You_, "do", "did", " not wear any armor", "");
2604         else
2605             you_are("not wearing any armor", "");
2606     }
2607 }
2608 
2609 /* attributes: intrinsics and the like, other non-obvious capabilities */
2610 STATIC_OVL void
attributes_enlightenment(unused_mode,final)2611 attributes_enlightenment(unused_mode, final)
2612 int unused_mode UNUSED;
2613 int final;
2614 {
2615     static NEARDATA const char if_surroundings_permitted[] =
2616         " if surroundings permitted";
2617     int ltmp, armpro;
2618     char buf[BUFSZ];
2619 
2620     /*\
2621      *  Attributes
2622     \*/
2623     enlght_out("");
2624     enlght_out(final ? "Final Attributes:" : "Current Attributes:");
2625 
2626     if (u.uevent.uhand_of_elbereth) {
2627         static const char *const hofe_titles[3] = { "the Hand of Elbereth",
2628                                                     "the Envoy of Balance",
2629                                                     "the Glory of Arioch" };
2630         you_are(hofe_titles[u.uevent.uhand_of_elbereth - 1], "");
2631     }
2632 
2633     Sprintf(buf, "%s", piousness(TRUE, "aligned"));
2634     if (u.ualign.record >= 0)
2635         you_are(buf, "");
2636     else
2637         you_have(buf, "");
2638 
2639     if (wizard) {
2640         Sprintf(buf, " %d", u.ualign.record);
2641         enl_msg("Your alignment ", "is", "was", buf, "");
2642     }
2643 
2644     /*** Resistances to troubles ***/
2645     if (Invulnerable)
2646         you_are("invulnerable", from_what(INVULNERABLE));
2647     if (Antimagic)
2648         you_are("magic-protected", from_what(ANTIMAGIC));
2649     if (Fire_resistance)
2650         you_are("fire resistant", from_what(FIRE_RES));
2651     if (Cold_resistance)
2652         you_are("cold resistant", from_what(COLD_RES));
2653     if (Sleep_resistance)
2654         you_are("sleep resistant", from_what(SLEEP_RES));
2655     if (Disint_resistance)
2656         you_are("disintegration-resistant", from_what(DISINT_RES));
2657     if (Shock_resistance)
2658         you_are("shock resistant", from_what(SHOCK_RES));
2659     if (Poison_resistance)
2660         you_are("poison resistant", from_what(POISON_RES));
2661     if (Acid_resistance)
2662         you_are("acid resistant", from_what(ACID_RES));
2663     if (Drain_resistance)
2664         you_are("level-drain resistant", from_what(DRAIN_RES));
2665     if (Sick_resistance)
2666         you_are("immune to sickness", from_what(SICK_RES));
2667     if (Stone_resistance)
2668         you_are("petrification resistant", from_what(STONE_RES));
2669     if (Halluc_resistance)
2670         enl_msg(You_, "resist", "resisted", " hallucinations",
2671                 from_what(HALLUC_RES));
2672     if (u.uedibility)
2673         you_can("recognize detrimental food", "");
2674 
2675     /*** Vision and senses ***/
2676     if (!Blind && (Blinded || !haseyes(youmonst.data)))
2677         you_can("see", from_what(-BLINDED)); /* Eyes of the Overworld */
2678     if (See_invisible) {
2679         if (!Blind)
2680             enl_msg(You_, "see", "saw", " invisible", from_what(SEE_INVIS));
2681         else
2682             enl_msg(You_, "will see", "would have seen",
2683                     " invisible when not blind", from_what(SEE_INVIS));
2684     }
2685     if (Blind_telepat)
2686         you_are("telepathic", from_what(TELEPAT));
2687     if (Warning)
2688         you_are("warned", from_what(WARNING));
2689     if (Warn_of_mon && context.warntype.obj) {
2690         Sprintf(buf, "aware of the presence of %s",
2691                 (context.warntype.obj & M2_ORC) ? "orcs"
2692                 : (context.warntype.obj & M2_ELF) ? "elves"
2693                 : (context.warntype.obj & M2_DEMON) ? "demons" : something);
2694         you_are(buf, from_what(WARN_OF_MON));
2695     }
2696     if (Warn_of_mon && context.warntype.polyd) {
2697         Sprintf(buf, "aware of the presence of %s",
2698                 ((context.warntype.polyd & (M2_HUMAN | M2_ELF))
2699                  == (M2_HUMAN | M2_ELF))
2700                     ? "humans and elves"
2701                     : (context.warntype.polyd & M2_HUMAN)
2702                           ? "humans"
2703                           : (context.warntype.polyd & M2_ELF)
2704                                 ? "elves"
2705                                 : (context.warntype.polyd & M2_ORC)
2706                                       ? "orcs"
2707                                       : (context.warntype.polyd & M2_DEMON)
2708                                             ? "demons"
2709                                             : "certain monsters");
2710         you_are(buf, "");
2711     }
2712     if (Warn_of_mon && context.warntype.speciesidx >= LOW_PM) {
2713         Sprintf(buf, "aware of the presence of %s",
2714                 makeplural(mons[context.warntype.speciesidx].mname));
2715         you_are(buf, from_what(WARN_OF_MON));
2716     }
2717     if (Undead_warning)
2718         you_are("warned of undead", from_what(WARN_UNDEAD));
2719     if (Searching)
2720         you_have("automatic searching", from_what(SEARCHING));
2721     if (Clairvoyant)
2722         you_are("clairvoyant", from_what(CLAIRVOYANT));
2723     else if ((HClairvoyant || EClairvoyant) && BClairvoyant) {
2724         Strcpy(buf, from_what(-CLAIRVOYANT));
2725         if (!strncmp(buf, " because of ", 12))
2726             /* overwrite substring; strncpy doesn't add terminator */
2727             (void) strncpy(buf, " if not for ", 12);
2728         enl_msg(You_, "could be", "could have been", " clairvoyant", buf);
2729     }
2730     if (Infravision)
2731         you_have("infravision", from_what(INFRAVISION));
2732     if (Detect_monsters)
2733         you_are("sensing the presence of monsters", "");
2734     if (u.umconf)
2735         you_are("going to confuse monsters", "");
2736 
2737     /*** Appearance and behavior ***/
2738     if (Adornment) {
2739         int adorn = 0;
2740 
2741         if (uleft && uleft->otyp == RIN_ADORNMENT)
2742             adorn += uleft->spe;
2743         if (uright && uright->otyp == RIN_ADORNMENT)
2744             adorn += uright->spe;
2745         /* the sum might be 0 (+0 ring or two which negate each other);
2746            that yields "you are charismatic" (which isn't pointless
2747            because it potentially impacts seduction attacks) */
2748         Sprintf(buf, "%scharismatic",
2749                 (adorn > 0) ? "more " : (adorn < 0) ? "less " : "");
2750         you_are(buf, from_what(ADORNED));
2751     }
2752     if (Invisible)
2753         you_are("invisible", from_what(INVIS));
2754     else if (Invis)
2755         you_are("invisible to others", from_what(INVIS));
2756     /* ordinarily "visible" is redundant; this is a special case for
2757        the situation when invisibility would be an expected attribute */
2758     else if ((HInvis || EInvis) && BInvis)
2759         you_are("visible", from_what(-INVIS));
2760     if (Displaced)
2761         you_are("displaced", from_what(DISPLACED));
2762     if (Stealth)
2763         you_are("stealthy", from_what(STEALTH));
2764     if (Aggravate_monster)
2765         enl_msg("You aggravate", "", "d", " monsters",
2766                 from_what(AGGRAVATE_MONSTER));
2767     if (Conflict)
2768         enl_msg("You cause", "", "d", " conflict", from_what(CONFLICT));
2769 
2770     /*** Transportation ***/
2771     if (Jumping)
2772         you_can("jump", from_what(JUMPING));
2773     if (Teleportation)
2774         you_can("teleport", from_what(TELEPORT));
2775     if (Teleport_control)
2776         you_have("teleport control", from_what(TELEPORT_CONTROL));
2777     /* actively levitating handled earlier as a status condition */
2778     if (BLevitation) { /* levitation is blocked */
2779         long save_BLev = BLevitation;
2780 
2781         BLevitation = 0L;
2782         if (Levitation) {
2783             /* either trapped in the floor or inside solid rock
2784                (or both if chained to buried iron ball and have
2785                moved one step into solid rock somehow) */
2786             boolean trapped = (save_BLev & I_SPECIAL) != 0L,
2787                     terrain = (save_BLev & FROMOUTSIDE) != 0L;
2788 
2789             Sprintf(buf, "%s%s%s",
2790                     trapped ? " if not trapped" : "",
2791                     (trapped && terrain) ? " and" : "",
2792                     terrain ? if_surroundings_permitted : "");
2793             enl_msg(You_, "would levitate", "would have levitated", buf, "");
2794         }
2795         BLevitation = save_BLev;
2796     }
2797     /* actively flying handled earlier as a status condition */
2798     if (BFlying) { /* flight is blocked */
2799         long save_BFly = BFlying;
2800 
2801         BFlying = 0L;
2802         if (Flying) {
2803             enl_msg(You_, "would fly", "would have flown",
2804                     /* wording quibble: for past tense, "hadn't been"
2805                        would sound better than "weren't" (and
2806                        "had permitted" better than "permitted"), but
2807                        "weren't" and "permitted" are adequate so the
2808                        extra complexity to handle that isn't worth it */
2809                     Levitation
2810                        ? " if you weren't levitating"
2811                        : (save_BFly == I_SPECIAL)
2812                           /* this is an oversimpliction; being trapped
2813                              might also be blocking levitation so flight
2814                              would still be blocked after escaping trap */
2815                           ? " if you weren't trapped"
2816                           : (save_BFly == FROMOUTSIDE)
2817                              ? if_surroundings_permitted
2818                              /* two or more of levitation, surroundings,
2819                                 and being trapped in the floor */
2820                              : " if circumstances permitted",
2821                     "");
2822         }
2823         BFlying = save_BFly;
2824     }
2825     /* actively walking on water handled earlier as a status condition */
2826     if (Wwalking && !walking_on_water())
2827         you_can("walk on water", from_what(WWALKING));
2828     /* actively swimming (in water but not under it) handled earlier */
2829     if (Swimming && (Underwater || !u.uinwater))
2830         you_can("swim", from_what(SWIMMING));
2831     if (Breathless)
2832         you_can("survive without air", from_what(MAGICAL_BREATHING));
2833     else if (Amphibious)
2834         you_can("breathe water", from_what(MAGICAL_BREATHING));
2835     if (Passes_walls)
2836         you_can("walk through walls", from_what(PASSES_WALLS));
2837 
2838     /*** Physical attributes ***/
2839     if (Regeneration)
2840         enl_msg("You regenerate", "", "d", "", from_what(REGENERATION));
2841     if (Slow_digestion)
2842         you_have("slower digestion", from_what(SLOW_DIGESTION));
2843     if (u.uhitinc)
2844         you_have(enlght_combatinc("to hit", u.uhitinc, final, buf), "");
2845     if (u.udaminc)
2846         you_have(enlght_combatinc("damage", u.udaminc, final, buf), "");
2847     if (u.uspellprot || Protection) {
2848         int prot = 0;
2849 
2850         if (uleft && uleft->otyp == RIN_PROTECTION)
2851             prot += uleft->spe;
2852         if (uright && uright->otyp == RIN_PROTECTION)
2853             prot += uright->spe;
2854         if (HProtection & INTRINSIC)
2855             prot += u.ublessed;
2856         prot += u.uspellprot;
2857         if (prot)
2858             you_have(enlght_combatinc("defense", prot, final, buf), "");
2859     }
2860     if ((armpro = magic_negation(&youmonst)) > 0) {
2861         /* magic cancellation factor, conferred by worn armor */
2862         static const char *const mc_types[] = {
2863             "" /*ordinary*/, "warded", "guarded", "protected",
2864         };
2865         /* sanity check */
2866         if (armpro >= SIZE(mc_types))
2867             armpro = SIZE(mc_types) - 1;
2868         you_are(mc_types[armpro], "");
2869     }
2870     if (Half_physical_damage)
2871         enlght_halfdmg(HALF_PHDAM, final);
2872     if (Half_spell_damage)
2873         enlght_halfdmg(HALF_SPDAM, final);
2874     /* polymorph and other shape change */
2875     if (Protection_from_shape_changers)
2876         you_are("protected from shape changers",
2877                 from_what(PROT_FROM_SHAPE_CHANGERS));
2878     if (Unchanging) {
2879         const char *what = 0;
2880 
2881         if (!Upolyd) /* Upolyd handled below after current form */
2882             you_can("not change from your current form",
2883                     from_what(UNCHANGING));
2884         /* blocked shape changes */
2885         if (Polymorph)
2886             what = !final ? "polymorph" : "have polymorphed";
2887         else if (u.ulycn >= LOW_PM)
2888             what = !final ? "change shape" : "have changed shape";
2889         if (what) {
2890             Sprintf(buf, "would %s periodically", what);
2891             /* omit from_what(UNCHANGING); too verbose */
2892             enl_msg(You_, buf, buf, " if not locked into your current form",
2893                     "");
2894         }
2895     } else if (Polymorph) {
2896         you_are("polymorphing periodically", from_what(POLYMORPH));
2897     }
2898     if (Polymorph_control)
2899         you_have("polymorph control", from_what(POLYMORPH_CONTROL));
2900     if (Upolyd && u.umonnum != u.ulycn
2901         /* if we've died from turning into slime, we're polymorphed
2902            right now but don't want to list it as a temporary attribute
2903            [we need a more reliable way to detect this situation] */
2904         && !(final == ENL_GAMEOVERDEAD
2905              && u.umonnum == PM_GREEN_SLIME && !Unchanging)) {
2906         /* foreign shape (except were-form which is handled below) */
2907         Sprintf(buf, "polymorphed into %s", an(youmonst.data->mname));
2908         if (wizard)
2909             Sprintf(eos(buf), " (%d)", u.mtimedone);
2910         you_are(buf, "");
2911     }
2912     if (lays_eggs(youmonst.data) && flags.female) /* Upolyd */
2913         you_can("lay eggs", "");
2914     if (u.ulycn >= LOW_PM) {
2915         /* "you are a werecreature [in beast form]" */
2916         Strcpy(buf, an(mons[u.ulycn].mname));
2917         if (u.umonnum == u.ulycn) {
2918             Strcat(buf, " in beast form");
2919             if (wizard)
2920                 Sprintf(eos(buf), " (%d)", u.mtimedone);
2921         }
2922         you_are(buf, "");
2923     }
2924     if (Unchanging && Upolyd) /* !Upolyd handled above */
2925         you_can("not change from your current form", from_what(UNCHANGING));
2926     if (Hate_silver)
2927         you_are("harmed by silver", "");
2928     /* movement and non-armor-based protection */
2929     if (Fast)
2930         you_are(Very_fast ? "very fast" : "fast", from_what(FAST));
2931     if (Reflecting)
2932         you_have("reflection", from_what(REFLECTING));
2933     if (Free_action)
2934         you_have("free action", from_what(FREE_ACTION));
2935     if (Fixed_abil)
2936         you_have("fixed abilities", from_what(FIXED_ABIL));
2937     if (Lifesaved)
2938         enl_msg("Your life ", "will be", "would have been", " saved", "");
2939 
2940     /*** Miscellany ***/
2941     if (Luck) {
2942         ltmp = abs((int) Luck);
2943         Sprintf(buf, "%s%slucky",
2944                 ltmp >= 10 ? "extremely " : ltmp >= 5 ? "very " : "",
2945                 Luck < 0 ? "un" : "");
2946         if (wizard)
2947             Sprintf(eos(buf), " (%d)", Luck);
2948         you_are(buf, "");
2949     } else if (wizard)
2950         enl_msg("Your luck ", "is", "was", " zero", "");
2951     if (u.moreluck > 0)
2952         you_have("extra luck", "");
2953     else if (u.moreluck < 0)
2954         you_have("reduced luck", "");
2955     if (carrying(LUCKSTONE) || stone_luck(TRUE)) {
2956         ltmp = stone_luck(FALSE);
2957         if (ltmp <= 0)
2958             enl_msg("Bad luck ", "does", "did", " not time out for you", "");
2959         if (ltmp >= 0)
2960             enl_msg("Good luck ", "does", "did", " not time out for you", "");
2961     }
2962 
2963     if (u.ugangr) {
2964         Sprintf(buf, " %sangry with you",
2965                 u.ugangr > 6 ? "extremely " : u.ugangr > 3 ? "very " : "");
2966         if (wizard)
2967             Sprintf(eos(buf), " (%d)", u.ugangr);
2968         enl_msg(u_gname(), " is", " was", buf, "");
2969     } else {
2970         /*
2971          * We need to suppress this when the game is over, because death
2972          * can change the value calculated by can_pray(), potentially
2973          * resulting in a false claim that you could have prayed safely.
2974          */
2975         if (!final) {
2976 #if 0
2977             /* "can [not] safely pray" vs "could [not] have safely prayed" */
2978             Sprintf(buf, "%s%ssafely pray%s", can_pray(FALSE) ? "" : "not ",
2979                     final ? "have " : "", final ? "ed" : "");
2980 #else
2981             Sprintf(buf, "%ssafely pray", can_pray(FALSE) ? "" : "not ");
2982 #endif
2983             if (wizard)
2984                 Sprintf(eos(buf), " (%d)", u.ublesscnt);
2985             you_can(buf, "");
2986         }
2987     }
2988 
2989 #ifdef DEBUG
2990     /* named fruit debugging (doesn't really belong here...); to enable,
2991        include 'fruit' in DEBUGFILES list (even though it isn't a file...) */
2992     if (wizard && explicitdebug("fruit")) {
2993         struct fruit *f;
2994 
2995         reorder_fruit(TRUE); /* sort by fruit index, from low to high;
2996                               * this modifies the ffruit chain, so could
2997                               * possibly mask or even introduce a problem,
2998                               * but it does useful sanity checking */
2999         for (f = ffruit; f; f = f->nextf) {
3000             Sprintf(buf, "Fruit #%d ", f->fid);
3001             enl_msg(buf, "is ", "was ", f->fname, "");
3002         }
3003         enl_msg("The current fruit ", "is ", "was ", pl_fruit, "");
3004         Sprintf(buf, "%d", flags.made_fruit);
3005         enl_msg("The made fruit flag ", "is ", "was ", buf, "");
3006     }
3007 #endif
3008 
3009     {
3010         const char *p;
3011 
3012         buf[0] = '\0';
3013         if (final < 2) { /* still in progress, or quit/escaped/ascended */
3014             p = "survived after being killed ";
3015             switch (u.umortality) {
3016             case 0:
3017                 p = !final ? (char *) 0 : "survived";
3018                 break;
3019             case 1:
3020                 Strcpy(buf, "once");
3021                 break;
3022             case 2:
3023                 Strcpy(buf, "twice");
3024                 break;
3025             case 3:
3026                 Strcpy(buf, "thrice");
3027                 break;
3028             default:
3029                 Sprintf(buf, "%d times", u.umortality);
3030                 break;
3031             }
3032         } else { /* game ended in character's death */
3033             p = "are dead";
3034             switch (u.umortality) {
3035             case 0:
3036                 impossible("dead without dying?");
3037             case 1:
3038                 break; /* just "are dead" */
3039             default:
3040                 Sprintf(buf, " (%d%s time!)", u.umortality,
3041                         ordin(u.umortality));
3042                 break;
3043             }
3044         }
3045         if (p)
3046             enl_msg(You_, "have been killed ", p, buf, "");
3047     }
3048 }
3049 
3050 #if 0  /* no longer used */
3051 STATIC_DCL boolean NDECL(minimal_enlightenment);
3052 
3053 /*
3054  * Courtesy function for non-debug, non-explorer mode players
3055  * to help refresh them about who/what they are.
3056  * Returns FALSE if menu cancelled (dismissed with ESC), TRUE otherwise.
3057  */
3058 STATIC_OVL boolean
3059 minimal_enlightenment()
3060 {
3061     winid tmpwin;
3062     menu_item *selected;
3063     anything any;
3064     int genidx, n;
3065     char buf[BUFSZ], buf2[BUFSZ];
3066     static const char untabbed_fmtstr[] = "%-15s: %-12s";
3067     static const char untabbed_deity_fmtstr[] = "%-17s%s";
3068     static const char tabbed_fmtstr[] = "%s:\t%-12s";
3069     static const char tabbed_deity_fmtstr[] = "%s\t%s";
3070     static const char *fmtstr;
3071     static const char *deity_fmtstr;
3072 
3073     fmtstr = iflags.menu_tab_sep ? tabbed_fmtstr : untabbed_fmtstr;
3074     deity_fmtstr = iflags.menu_tab_sep ? tabbed_deity_fmtstr
3075                                        : untabbed_deity_fmtstr;
3076     any = zeroany;
3077     buf[0] = buf2[0] = '\0';
3078     tmpwin = create_nhwindow(NHW_MENU);
3079     start_menu(tmpwin);
3080     add_menu(tmpwin, NO_GLYPH, &any, 0, 0, iflags.menu_headings,
3081              "Starting", FALSE);
3082 
3083     /* Starting name, race, role, gender */
3084     Sprintf(buf, fmtstr, "name", plname);
3085     add_menu(tmpwin, NO_GLYPH, &any, 0, 0, ATR_NONE, buf, FALSE);
3086     Sprintf(buf, fmtstr, "race", urace.noun);
3087     add_menu(tmpwin, NO_GLYPH, &any, 0, 0, ATR_NONE, buf, FALSE);
3088     Sprintf(buf, fmtstr, "role",
3089             (flags.initgend && urole.name.f) ? urole.name.f : urole.name.m);
3090     add_menu(tmpwin, NO_GLYPH, &any, 0, 0, ATR_NONE, buf, FALSE);
3091     Sprintf(buf, fmtstr, "gender", genders[flags.initgend].adj);
3092     add_menu(tmpwin, NO_GLYPH, &any, 0, 0, ATR_NONE, buf, FALSE);
3093 
3094     /* Starting alignment */
3095     Sprintf(buf, fmtstr, "alignment", align_str(u.ualignbase[A_ORIGINAL]));
3096     add_menu(tmpwin, NO_GLYPH, &any, 0, 0, ATR_NONE, buf, FALSE);
3097 
3098     /* Current name, race, role, gender */
3099     add_menu(tmpwin, NO_GLYPH, &any, 0, 0, ATR_NONE, "", FALSE);
3100     add_menu(tmpwin, NO_GLYPH, &any, 0, 0, iflags.menu_headings,
3101              "Current", FALSE);
3102     Sprintf(buf, fmtstr, "race", Upolyd ? youmonst.data->mname : urace.noun);
3103     add_menu(tmpwin, NO_GLYPH, &any, 0, 0, ATR_NONE, buf, FALSE);
3104     if (Upolyd) {
3105         Sprintf(buf, fmtstr, "role (base)",
3106                 (u.mfemale && urole.name.f) ? urole.name.f
3107                                             : urole.name.m);
3108         add_menu(tmpwin, NO_GLYPH, &any, 0, 0, ATR_NONE, buf, FALSE);
3109     } else {
3110         Sprintf(buf, fmtstr, "role",
3111                 (flags.female && urole.name.f) ? urole.name.f
3112                                                : urole.name.m);
3113         add_menu(tmpwin, NO_GLYPH, &any, 0, 0, ATR_NONE, buf, FALSE);
3114     }
3115     /* don't want poly_gender() here; it forces `2' for non-humanoids */
3116     genidx = is_neuter(youmonst.data) ? 2 : flags.female;
3117     Sprintf(buf, fmtstr, "gender", genders[genidx].adj);
3118     add_menu(tmpwin, NO_GLYPH, &any, 0, 0, ATR_NONE, buf, FALSE);
3119     if (Upolyd && (int) u.mfemale != genidx) {
3120         Sprintf(buf, fmtstr, "gender (base)", genders[u.mfemale].adj);
3121         add_menu(tmpwin, NO_GLYPH, &any, 0, 0, ATR_NONE, buf, FALSE);
3122     }
3123 
3124     /* Current alignment */
3125     Sprintf(buf, fmtstr, "alignment", align_str(u.ualign.type));
3126     add_menu(tmpwin, NO_GLYPH, &any, 0, 0, ATR_NONE, buf, FALSE);
3127 
3128     /* Deity list */
3129     add_menu(tmpwin, NO_GLYPH, &any, 0, 0, ATR_NONE, "", FALSE);
3130     add_menu(tmpwin, NO_GLYPH, &any, 0, 0, iflags.menu_headings,
3131              "Deities", FALSE);
3132     Sprintf(buf2, deity_fmtstr, align_gname(A_CHAOTIC),
3133             (u.ualignbase[A_ORIGINAL] == u.ualign.type
3134              && u.ualign.type == A_CHAOTIC)               ? " (s,c)"
3135                 : (u.ualignbase[A_ORIGINAL] == A_CHAOTIC) ? " (s)"
3136                 : (u.ualign.type   == A_CHAOTIC)          ? " (c)" : "");
3137     Sprintf(buf, fmtstr, "Chaotic", buf2);
3138     add_menu(tmpwin, NO_GLYPH, &any, 0, 0, ATR_NONE, buf, FALSE);
3139 
3140     Sprintf(buf2, deity_fmtstr, align_gname(A_NEUTRAL),
3141             (u.ualignbase[A_ORIGINAL] == u.ualign.type
3142              && u.ualign.type == A_NEUTRAL)               ? " (s,c)"
3143                 : (u.ualignbase[A_ORIGINAL] == A_NEUTRAL) ? " (s)"
3144                 : (u.ualign.type   == A_NEUTRAL)          ? " (c)" : "");
3145     Sprintf(buf, fmtstr, "Neutral", buf2);
3146     add_menu(tmpwin, NO_GLYPH, &any, 0, 0, ATR_NONE, buf, FALSE);
3147 
3148     Sprintf(buf2, deity_fmtstr, align_gname(A_LAWFUL),
3149             (u.ualignbase[A_ORIGINAL] == u.ualign.type
3150              && u.ualign.type == A_LAWFUL)                ? " (s,c)"
3151                 : (u.ualignbase[A_ORIGINAL] == A_LAWFUL)  ? " (s)"
3152                 : (u.ualign.type   == A_LAWFUL)           ? " (c)" : "");
3153     Sprintf(buf, fmtstr, "Lawful", buf2);
3154     add_menu(tmpwin, NO_GLYPH, &any, 0, 0, ATR_NONE, buf, FALSE);
3155 
3156     end_menu(tmpwin, "Base Attributes");
3157     n = select_menu(tmpwin, PICK_NONE, &selected);
3158     destroy_nhwindow(tmpwin);
3159     return (boolean) (n != -1);
3160 }
3161 #endif /*0*/
3162 
3163 /* ^X command */
3164 STATIC_PTR int
doattributes(VOID_ARGS)3165 doattributes(VOID_ARGS)
3166 {
3167     int mode = BASICENLIGHTENMENT;
3168 
3169     /* show more--as if final disclosure--for wizard and explore modes */
3170     if (wizard || discover)
3171         mode |= MAGICENLIGHTENMENT;
3172 
3173     enlightenment(mode, ENL_GAMEINPROGRESS);
3174     return 0;
3175 }
3176 
3177 void
youhiding(via_enlghtmt,msgflag)3178 youhiding(via_enlghtmt, msgflag)
3179 boolean via_enlghtmt; /* englightment line vs topl message */
3180 int msgflag;          /* for variant message phrasing */
3181 {
3182     char *bp, buf[BUFSZ];
3183 
3184     Strcpy(buf, "hiding");
3185     if (U_AP_TYPE != M_AP_NOTHING) {
3186         /* mimic; hero is only able to mimic a strange object or gold
3187            or hallucinatory alternative to gold, so we skip the details
3188            for the hypothetical furniture and monster cases */
3189         bp = eos(strcpy(buf, "mimicking"));
3190         if (U_AP_TYPE == M_AP_OBJECT) {
3191             Sprintf(bp, " %s", an(simple_typename(youmonst.mappearance)));
3192         } else if (U_AP_TYPE == M_AP_FURNITURE) {
3193             Strcpy(bp, " something");
3194         } else if (U_AP_TYPE == M_AP_MONSTER) {
3195             Strcpy(bp, " someone");
3196         } else {
3197             ; /* something unexpected; leave 'buf' as-is */
3198         }
3199     } else if (u.uundetected) {
3200         bp = eos(buf); /* points past "hiding" */
3201         if (youmonst.data->mlet == S_EEL) {
3202             if (is_pool(u.ux, u.uy))
3203                 Sprintf(bp, " in the %s", waterbody_name(u.ux, u.uy));
3204         } else if (hides_under(youmonst.data)) {
3205             struct obj *o = level.objects[u.ux][u.uy];
3206 
3207             if (o)
3208                 Sprintf(bp, " underneath %s", ansimpleoname(o));
3209         } else if (is_clinger(youmonst.data) || Flying) {
3210             /* Flying: 'lurker above' hides on ceiling but doesn't cling */
3211             Sprintf(bp, " on the %s", ceiling(u.ux, u.uy));
3212         } else {
3213             /* on floor; is_hider() but otherwise not special: 'trapper' */
3214             if (u.utrap && u.utraptype == TT_PIT) {
3215                 struct trap *t = t_at(u.ux, u.uy);
3216 
3217                 Sprintf(bp, " in a %spit",
3218                         (t && t->ttyp == SPIKED_PIT) ? "spiked " : "");
3219             } else
3220                 Sprintf(bp, " on the %s", surface(u.ux, u.uy));
3221         }
3222     } else {
3223         ; /* shouldn't happen; will result in generic "you are hiding" */
3224     }
3225 
3226     if (via_enlghtmt) {
3227         int final = msgflag; /* 'final' is used by you_are() macro */
3228 
3229         you_are(buf, "");
3230     } else {
3231         /* for dohide(), when player uses '#monster' command */
3232         You("are %s %s.", msgflag ? "already" : "now", buf);
3233     }
3234 }
3235 
3236 /* KMH, #conduct
3237  * (shares enlightenment's tense handling)
3238  */
3239 int
doconduct(VOID_ARGS)3240 doconduct(VOID_ARGS)
3241 {
3242     show_conduct(0);
3243     return 0;
3244 }
3245 
3246 void
show_conduct(final)3247 show_conduct(final)
3248 int final;
3249 {
3250     char buf[BUFSZ];
3251     int ngenocided;
3252 
3253     /* Create the conduct window */
3254     en_win = create_nhwindow(NHW_MENU);
3255     putstr(en_win, 0, "Voluntary challenges:");
3256 
3257     if (u.uroleplay.blind)
3258         you_have_been("blind from birth");
3259     if (u.uroleplay.nudist)
3260         you_have_been("faithfully nudist");
3261 
3262     if (!u.uconduct.food)
3263         enl_msg(You_, "have gone", "went", " without food", "");
3264         /* but beverages are okay */
3265     else if (!u.uconduct.unvegan)
3266         you_have_X("followed a strict vegan diet");
3267     else if (!u.uconduct.unvegetarian)
3268         you_have_been("vegetarian");
3269 
3270     if (!u.uconduct.gnostic)
3271         you_have_been("an atheist");
3272 
3273     if (!u.uconduct.weaphit) {
3274         you_have_never("hit with a wielded weapon");
3275     } else if (wizard) {
3276         Sprintf(buf, "used a wielded weapon %ld time%s", u.uconduct.weaphit,
3277                 plur(u.uconduct.weaphit));
3278         you_have_X(buf);
3279     }
3280     if (!u.uconduct.killer)
3281         you_have_been("a pacifist");
3282 
3283     if (!u.uconduct.literate) {
3284         you_have_been("illiterate");
3285     } else if (wizard) {
3286         Sprintf(buf, "read items or engraved %ld time%s", u.uconduct.literate,
3287                 plur(u.uconduct.literate));
3288         you_have_X(buf);
3289     }
3290 
3291     ngenocided = num_genocides();
3292     if (ngenocided == 0) {
3293         you_have_never("genocided any monsters");
3294     } else {
3295         Sprintf(buf, "genocided %d type%s of monster%s", ngenocided,
3296                 plur(ngenocided), plur(ngenocided));
3297         you_have_X(buf);
3298     }
3299 
3300     if (!u.uconduct.polypiles) {
3301         you_have_never("polymorphed an object");
3302     } else if (wizard) {
3303         Sprintf(buf, "polymorphed %ld item%s", u.uconduct.polypiles,
3304                 plur(u.uconduct.polypiles));
3305         you_have_X(buf);
3306     }
3307 
3308     if (!u.uconduct.polyselfs) {
3309         you_have_never("changed form");
3310     } else if (wizard) {
3311         Sprintf(buf, "changed form %ld time%s", u.uconduct.polyselfs,
3312                 plur(u.uconduct.polyselfs));
3313         you_have_X(buf);
3314     }
3315 
3316     if (!u.uconduct.wishes) {
3317         you_have_X("used no wishes");
3318     } else {
3319         Sprintf(buf, "used %ld wish%s", u.uconduct.wishes,
3320                 (u.uconduct.wishes > 1L) ? "es" : "");
3321         if (u.uconduct.wisharti) {
3322             /* if wisharti == wishes
3323              *  1 wish (for an artifact)
3324              *  2 wishes (both for artifacts)
3325              *  N wishes (all for artifacts)
3326              * else (N is at least 2 in order to get here; M < N)
3327              *  N wishes (1 for an artifact)
3328              *  N wishes (M for artifacts)
3329              */
3330             if (u.uconduct.wisharti == u.uconduct.wishes)
3331                 Sprintf(eos(buf), " (%s",
3332                         (u.uconduct.wisharti > 2L) ? "all "
3333                           : (u.uconduct.wisharti == 2L) ? "both " : "");
3334             else
3335                 Sprintf(eos(buf), " (%ld ", u.uconduct.wisharti);
3336 
3337             Sprintf(eos(buf), "for %s)",
3338                     (u.uconduct.wisharti == 1L) ? "an artifact"
3339                                                 : "artifacts");
3340         }
3341         you_have_X(buf);
3342 
3343         if (!u.uconduct.wisharti)
3344             enl_msg(You_, "have not wished", "did not wish",
3345                     " for any artifacts", "");
3346     }
3347 
3348     /* Pop up the window and wait for a key */
3349     display_nhwindow(en_win, TRUE);
3350     destroy_nhwindow(en_win);
3351     en_win = WIN_ERR;
3352 }
3353 
3354 /* ordered by command name */
3355 struct ext_func_tab extcmdlist[] = {
3356     { '#', "#", "perform an extended command",
3357             doextcmd, IFBURIED | GENERALCMD },
3358     { M('?'), "?", "list all extended commands",
3359             doextlist, IFBURIED | AUTOCOMPLETE | GENERALCMD },
3360     { M('a'), "adjust", "adjust inventory letters",
3361             doorganize, IFBURIED | AUTOCOMPLETE },
3362     { M('A'), "annotate", "name current level",
3363             donamelevel, IFBURIED | AUTOCOMPLETE },
3364     { 'a', "apply", "apply (use) a tool (pick-axe, key, lamp...)",
3365             doapply },
3366     { C('x'), "attributes", "show your attributes",
3367             doattributes, IFBURIED },
3368     { '@', "autopickup", "toggle the pickup option on/off",
3369             dotogglepickup, IFBURIED },
3370     { 'C', "call", "call (name) something", docallcmd, IFBURIED },
3371     { 'Z', "cast", "zap (cast) a spell", docast, IFBURIED },
3372     { M('c'), "chat", "talk to someone", dotalk, IFBURIED | AUTOCOMPLETE },
3373     { 'c', "close", "close a door", doclose },
3374     { M('C'), "conduct", "list voluntary challenges you have maintained",
3375             doconduct, IFBURIED | AUTOCOMPLETE },
3376     { M('d'), "dip", "dip an object into something", dodip, AUTOCOMPLETE },
3377     { '>', "down", "go down a staircase", dodown },
3378     { 'd', "drop", "drop an item", dodrop },
3379     { 'D', "droptype", "drop specific item types", doddrop },
3380     { 'e', "eat", "eat something", doeat },
3381     { 'E', "engrave", "engrave writing on the floor", doengrave },
3382     { M('e'), "enhance", "advance or check weapon and spell skills",
3383             enhance_weapon_skill, IFBURIED | AUTOCOMPLETE },
3384     { '\0', "exploremode", "enter explore (discovery) mode",
3385             enter_explore_mode, IFBURIED },
3386     { 'f', "fire", "fire ammunition from quiver", dofire },
3387     { M('f'), "force", "force a lock", doforce, AUTOCOMPLETE },
3388     { ';', "glance", "show what type of thing a map symbol corresponds to",
3389             doquickwhatis, IFBURIED | GENERALCMD },
3390     { '?', "help", "give a help message", dohelp, IFBURIED | GENERALCMD },
3391     { '\0', "herecmdmenu", "show menu of commands you can do here",
3392             doherecmdmenu, IFBURIED },
3393     { 'V', "history", "show long version and game history",
3394             dohistory, IFBURIED | GENERALCMD },
3395     { 'i', "inventory", "show your inventory", ddoinv, IFBURIED },
3396     { 'I', "inventtype", "inventory specific item types",
3397             dotypeinv, IFBURIED },
3398     { M('i'), "invoke", "invoke an object's special powers",
3399             doinvoke, IFBURIED | AUTOCOMPLETE },
3400     { M('j'), "jump", "jump to another location", dojump, AUTOCOMPLETE },
3401     { C('d'), "kick", "kick something", dokick },
3402     { '\\', "known", "show what object types have been discovered",
3403             dodiscovered, IFBURIED | GENERALCMD },
3404     { '`', "knownclass", "show discovered types for one class of objects",
3405             doclassdisco, IFBURIED | GENERALCMD },
3406     { '\0', "levelchange", "change experience level",
3407             wiz_level_change, IFBURIED | AUTOCOMPLETE | WIZMODECMD },
3408     { '\0', "lightsources", "show mobile light sources",
3409             wiz_light_sources, IFBURIED | AUTOCOMPLETE | WIZMODECMD },
3410     { ':', "look", "look at what is here", dolook, IFBURIED },
3411     { M('l'), "loot", "loot a box on the floor", doloot, AUTOCOMPLETE },
3412 #ifdef DEBUG_MIGRATING_MONS
3413     { '\0', "migratemons", "migrate N random monsters",
3414             wiz_migrate_mons, IFBURIED | AUTOCOMPLETE | WIZMODECMD },
3415 #endif
3416     { M('m'), "monster", "use monster's special ability",
3417             domonability, IFBURIED | AUTOCOMPLETE },
3418     { 'N', "name", "name a monster or an object",
3419             docallcmd, IFBURIED | AUTOCOMPLETE },
3420     { M('o'), "offer", "offer a sacrifice to the gods",
3421             dosacrifice, AUTOCOMPLETE },
3422     { 'o', "open", "open a door", doopen },
3423     { 'O', "options", "show option settings, possibly change them",
3424             doset, IFBURIED | GENERALCMD },
3425     { C('o'), "overview", "show a summary of the explored dungeon",
3426             dooverview, IFBURIED | AUTOCOMPLETE },
3427     { '\0', "panic", "test panic routine (fatal to game)",
3428             wiz_panic, IFBURIED | AUTOCOMPLETE | WIZMODECMD },
3429     { 'p', "pay", "pay your shopping bill", dopay },
3430     { ',', "pickup", "pick up things at the current location", dopickup },
3431     { '\0', "polyself", "polymorph self",
3432             wiz_polyself, IFBURIED | AUTOCOMPLETE | WIZMODECMD },
3433     { M('p'), "pray", "pray to the gods for help",
3434             dopray, IFBURIED | AUTOCOMPLETE },
3435     { C('p'), "prevmsg", "view recent game messages",
3436             doprev_message, IFBURIED | GENERALCMD },
3437     { 'P', "puton", "put on an accessory (ring, amulet, etc)", doputon },
3438     { 'q', "quaff", "quaff (drink) something", dodrink },
3439     { M('q'), "quit", "exit without saving current game",
3440             done2, IFBURIED | AUTOCOMPLETE | GENERALCMD },
3441     { 'Q', "quiver", "select ammunition for quiver", dowieldquiver },
3442     { 'r', "read", "read a scroll or spellbook", doread },
3443     { C('r'), "redraw", "redraw screen", doredraw, IFBURIED | GENERALCMD },
3444     { 'R', "remove", "remove an accessory (ring, amulet, etc)", doremring },
3445     { M('R'), "ride", "mount or dismount a saddled steed",
3446             doride, AUTOCOMPLETE },
3447     { M('r'), "rub", "rub a lamp or a stone", dorub, AUTOCOMPLETE },
3448     { 'S', "save", "save the game and exit", dosave, IFBURIED | GENERALCMD },
3449     { 's', "search", "search for traps and secret doors",
3450             dosearch, IFBURIED, "searching" },
3451     { '*', "seeall", "show all equipment in use", doprinuse, IFBURIED },
3452     { AMULET_SYM, "seeamulet", "show the amulet currently worn",
3453             dopramulet, IFBURIED },
3454     { ARMOR_SYM, "seearmor", "show the armor currently worn",
3455             doprarm, IFBURIED },
3456     { GOLD_SYM, "seegold", "count your gold", doprgold, IFBURIED },
3457     { '\0', "seenv", "show seen vectors",
3458             wiz_show_seenv, IFBURIED | AUTOCOMPLETE | WIZMODECMD },
3459     { RING_SYM, "seerings", "show the ring(s) currently worn",
3460             doprring, IFBURIED },
3461     { SPBOOK_SYM, "seespells", "list and reorder known spells",
3462             dovspell, IFBURIED },
3463     { TOOL_SYM, "seetools", "show the tools currently in use",
3464             doprtool, IFBURIED },
3465     { '^', "seetrap", "show the type of adjacent trap", doidtrap, IFBURIED },
3466     { WEAPON_SYM, "seeweapon", "show the weapon currently wielded",
3467             doprwep, IFBURIED },
3468     { '!', "shell", "do a shell escape",
3469             dosh_core, IFBURIED | GENERALCMD
3470 #ifndef SHELL
3471                        | CMD_NOT_AVAILABLE
3472 #endif /* SHELL */
3473     },
3474     { M('s'), "sit", "sit down", dosit, AUTOCOMPLETE },
3475     { '\0', "stats", "show memory statistics",
3476             wiz_show_stats, IFBURIED | AUTOCOMPLETE | WIZMODECMD },
3477     { C('z'), "suspend", "suspend the game",
3478             dosuspend_core, IFBURIED | GENERALCMD
3479 #ifndef SUSPEND
3480                             | CMD_NOT_AVAILABLE
3481 #endif /* SUSPEND */
3482     },
3483     { 'x', "swap", "swap wielded and secondary weapons", doswapweapon },
3484     { 'T', "takeoff", "take off one piece of armor", dotakeoff },
3485     { 'A', "takeoffall", "remove all armor", doddoremarm },
3486     { C('t'), "teleport", "teleport around the level", dotelecmd, IFBURIED },
3487     { '\0', "terrain", "show map without obstructions",
3488             doterrain, IFBURIED | AUTOCOMPLETE },
3489     { '\0', "therecmdmenu",
3490             "menu of commands you can do from here to adjacent spot",
3491             dotherecmdmenu },
3492     { 't', "throw", "throw something", dothrow },
3493     { '\0', "timeout", "look at timeout queue and hero's timed intrinsics",
3494             wiz_timeout_queue, IFBURIED | AUTOCOMPLETE | WIZMODECMD },
3495     { M('T'), "tip", "empty a container", dotip, AUTOCOMPLETE },
3496     { '_', "travel", "travel to a specific location on the map", dotravel },
3497     { M('t'), "turn", "turn undead away", doturn, IFBURIED | AUTOCOMPLETE },
3498     { 'X', "twoweapon", "toggle two-weapon combat",
3499             dotwoweapon, AUTOCOMPLETE },
3500     { M('u'), "untrap", "untrap something", dountrap, AUTOCOMPLETE },
3501     { '<', "up", "go up a staircase", doup },
3502     { '\0', "vanquished", "list vanquished monsters",
3503             dovanquished, IFBURIED | AUTOCOMPLETE | WIZMODECMD },
3504     { M('v'), "version",
3505             "list compile time options for this version of NetHack",
3506             doextversion, IFBURIED | AUTOCOMPLETE | GENERALCMD },
3507     { 'v', "versionshort", "show version", doversion, IFBURIED | GENERALCMD },
3508     { '\0', "vision", "show vision array",
3509             wiz_show_vision, IFBURIED | AUTOCOMPLETE | WIZMODECMD },
3510     { '.', "wait", "rest one move while doing nothing",
3511             donull, IFBURIED, "waiting" },
3512     { 'W', "wear", "wear a piece of armor", dowear },
3513     { '&', "whatdoes", "tell what a command does", dowhatdoes, IFBURIED },
3514     { '/', "whatis", "show what type of thing a symbol corresponds to",
3515             dowhatis, IFBURIED | GENERALCMD },
3516     { 'w', "wield", "wield (put in use) a weapon", dowield },
3517     { M('w'), "wipe", "wipe off your face", dowipe, AUTOCOMPLETE },
3518 #ifdef DEBUG
3519     { '\0', "wizbury", "bury objs under and around you",
3520             wiz_debug_cmd_bury, IFBURIED | AUTOCOMPLETE | WIZMODECMD },
3521 #endif
3522     { C('e'), "wizdetect", "reveal hidden things within a small radius",
3523             wiz_detect, IFBURIED | AUTOCOMPLETE | WIZMODECMD },
3524     { C('g'), "wizgenesis", "create a monster",
3525             wiz_genesis, IFBURIED | AUTOCOMPLETE | WIZMODECMD },
3526     { C('i'), "wizidentify", "identify all items in inventory",
3527             wiz_identify, IFBURIED | AUTOCOMPLETE | WIZMODECMD },
3528     { '\0', "wizintrinsic", "set an intrinsic",
3529             wiz_intrinsic, IFBURIED | AUTOCOMPLETE | WIZMODECMD },
3530     { C('v'), "wizlevelport", "teleport to another level",
3531             wiz_level_tele, IFBURIED | AUTOCOMPLETE | WIZMODECMD },
3532     { '\0', "wizmakemap", "recreate the current level",
3533             wiz_makemap, IFBURIED | WIZMODECMD },
3534     { C('f'), "wizmap", "map the level",
3535             wiz_map, IFBURIED | AUTOCOMPLETE | WIZMODECMD },
3536     { '\0', "wizrumorcheck", "verify rumor boundaries",
3537             wiz_rumor_check, IFBURIED | AUTOCOMPLETE | WIZMODECMD },
3538     { '\0', "wizsmell", "smell monster",
3539             wiz_smell, IFBURIED | AUTOCOMPLETE | WIZMODECMD },
3540     { '\0', "wizwhere", "show locations of special levels",
3541             wiz_where, IFBURIED | AUTOCOMPLETE | WIZMODECMD },
3542     { C('w'), "wizwish", "wish for something",
3543             wiz_wish, IFBURIED | AUTOCOMPLETE | WIZMODECMD },
3544     { '\0', "wmode", "show wall modes",
3545             wiz_show_wmodes, IFBURIED | AUTOCOMPLETE | WIZMODECMD },
3546     { 'z', "zap", "zap a wand", dozap },
3547     { '\0', (char *) 0, (char *) 0, donull, 0, (char *) 0 } /* sentinel */
3548 };
3549 
3550 /* for key2extcmddesc() to support dowhatdoes() */
3551 struct movcmd {
3552     uchar k1, k2, k3, k4; /* 'normal', 'qwertz', 'numpad', 'phone' */
3553     const char *txt, *alt; /* compass direction, screen direction */
3554 };
3555 static const struct movcmd movtab[] = {
3556     { 'h', 'h', '4', '4', "west",      "left" },
3557     { 'j', 'j', '2', '8', "south",     "down" },
3558     { 'k', 'k', '8', '2', "north",     "up" },
3559     { 'l', 'l', '6', '6', "east",      "right" },
3560     { 'b', 'b', '1', '7', "southwest", "lower left" },
3561     { 'n', 'n', '3', '9', "southeast", "lower right" },
3562     { 'u', 'u', '9', '3', "northeast", "upper right" },
3563     { 'y', 'z', '7', '1', "northwest", "upper left" },
3564     {   0,   0,   0,   0,  (char *) 0, (char *) 0 }
3565 };
3566 
3567 int extcmdlist_length = SIZE(extcmdlist) - 1;
3568 
3569 const char *
key2extcmddesc(key)3570 key2extcmddesc(key)
3571 uchar key;
3572 {
3573     static char key2cmdbuf[48];
3574     const struct movcmd *mov;
3575     int k, c;
3576     uchar M_5 = (uchar) M('5'), M_0 = (uchar) M('0');
3577 
3578     /* need to check for movement commands before checking the extended
3579        commands table because it contains entries for number_pad commands
3580        that match !number_pad movement (like 'j' for "jump") */
3581     key2cmdbuf[0] = '\0';
3582     if (movecmd(k = key))
3583         Strcpy(key2cmdbuf, "move"); /* "move or attack"? */
3584     else if (movecmd(k = unctrl(key)))
3585         Strcpy(key2cmdbuf, "rush");
3586     else if (movecmd(k = (Cmd.num_pad ? unmeta(key) : lowc(key))))
3587         Strcpy(key2cmdbuf, "run");
3588     if (*key2cmdbuf) {
3589         for (mov = &movtab[0]; mov->k1; ++mov) {
3590             c = !Cmd.num_pad ? (!Cmd.swap_yz ? mov->k1 : mov->k2)
3591                              : (!Cmd.phone_layout ? mov->k3 : mov->k4);
3592             if (c == k) {
3593                 Sprintf(eos(key2cmdbuf), " %s (screen %s)",
3594                         mov->txt, mov->alt);
3595                 return key2cmdbuf;
3596             }
3597         }
3598     } else if (digit(key) || (Cmd.num_pad && digit(unmeta(key)))) {
3599         key2cmdbuf[0] = '\0';
3600         if (!Cmd.num_pad)
3601             Strcpy(key2cmdbuf, "start of, or continuation of, a count");
3602         else if (key == '5' || key == M_5)
3603             Sprintf(key2cmdbuf, "%s prefix",
3604                     (!!Cmd.pcHack_compat ^ (key == M_5)) ? "run" : "rush");
3605         else if (key == '0' || (Cmd.pcHack_compat && key == M_0))
3606             Strcpy(key2cmdbuf, "synonym for 'i'");
3607         if (*key2cmdbuf)
3608             return key2cmdbuf;
3609     }
3610     if (Cmd.commands[key]) {
3611         if (Cmd.commands[key]->ef_txt)
3612             return Cmd.commands[key]->ef_desc;
3613 
3614     }
3615     return (char *) 0;
3616 }
3617 
3618 boolean
bind_key(key,command)3619 bind_key(key, command)
3620 uchar key;
3621 const char *command;
3622 {
3623     struct ext_func_tab *extcmd;
3624 
3625     /* special case: "nothing" is reserved for unbinding */
3626     if (!strcmp(command, "nothing")) {
3627         Cmd.commands[key] = (struct ext_func_tab *) 0;
3628         return TRUE;
3629     }
3630 
3631     for (extcmd = extcmdlist; extcmd->ef_txt; extcmd++) {
3632         if (strcmp(command, extcmd->ef_txt))
3633             continue;
3634         Cmd.commands[key] = extcmd;
3635 #if 0 /* silently accept key binding for unavailable command (!SHELL,&c) */
3636         if ((extcmd->flags & CMD_NOT_AVAILABLE) != 0) {
3637             char buf[BUFSZ];
3638 
3639             Sprintf(buf, cmdnotavail, extcmd->ef_txt);
3640             config_error_add("%s", buf);
3641         }
3642 #endif
3643         return TRUE;
3644     }
3645 
3646     return FALSE;
3647 }
3648 
3649 /* initialize all keyboard commands */
3650 void
commands_init()3651 commands_init()
3652 {
3653     struct ext_func_tab *extcmd;
3654 
3655     for (extcmd = extcmdlist; extcmd->ef_txt; extcmd++)
3656         if (extcmd->key)
3657             Cmd.commands[extcmd->key] = extcmd;
3658 
3659     (void) bind_key(C('l'), "redraw"); /* if number_pad is set */
3660     /*       'b', 'B' : go sw */
3661     /*       'F' : fight (one time) */
3662     /*       'g', 'G' : multiple go */
3663     /*       'h', 'H' : go west */
3664     (void) bind_key('h',    "help"); /* if number_pad is set */
3665     (void) bind_key('j',    "jump"); /* if number_pad is on */
3666     /*       'j', 'J', 'k', 'K', 'l', 'L', 'm', 'M', 'n', 'N' move commands */
3667     (void) bind_key('k',    "kick"); /* if number_pad is on */
3668     (void) bind_key('l',    "loot"); /* if number_pad is on */
3669     (void) bind_key(C('n'), "annotate"); /* if number_pad is on */
3670     (void) bind_key(M('n'), "name");
3671     (void) bind_key(M('N'), "name");
3672     (void) bind_key('u',    "untrap"); /* if number_pad is on */
3673 
3674     /* alt keys: */
3675     (void) bind_key(M('O'), "overview");
3676     (void) bind_key(M('2'), "twoweapon");
3677 
3678     /* wait_on_space */
3679     (void) bind_key(' ',    "wait");
3680 }
3681 
3682 int
dokeylist_putcmds(datawin,docount,cmdflags,exflags,keys_used)3683 dokeylist_putcmds(datawin, docount, cmdflags, exflags, keys_used)
3684 winid datawin;
3685 boolean docount;
3686 int cmdflags, exflags;
3687 boolean *keys_used; /* boolean keys_used[256] */
3688 {
3689     int i;
3690     char buf[BUFSZ];
3691     char buf2[QBUFSZ];
3692     int count = 0;
3693 
3694     for (i = 0; i < 256; i++) {
3695         const struct ext_func_tab *extcmd;
3696         uchar key = (uchar) i;
3697 
3698         if (keys_used[i])
3699             continue;
3700         if (key == ' ' && !flags.rest_on_space)
3701             continue;
3702         if ((extcmd = Cmd.commands[i]) != (struct ext_func_tab *) 0) {
3703             if ((cmdflags && !(extcmd->flags & cmdflags))
3704                 || (exflags && (extcmd->flags & exflags)))
3705                 continue;
3706             if (docount) {
3707                 count++;
3708                 continue;
3709             }
3710             Sprintf(buf, "%-8s %-12s %s", key2txt(key, buf2),
3711                     extcmd->ef_txt,
3712                     extcmd->ef_desc);
3713             putstr(datawin, 0, buf);
3714             keys_used[i] = TRUE;
3715         }
3716     }
3717     return count;
3718 }
3719 
3720 /* list all keys and their bindings, like dat/hh but dynamic */
3721 void
dokeylist(VOID_ARGS)3722 dokeylist(VOID_ARGS)
3723 {
3724     char buf[BUFSZ], buf2[BUFSZ];
3725     uchar key;
3726     boolean keys_used[256] = {0};
3727     winid datawin;
3728     int i;
3729     static const char
3730         run_desc[] = "Prefix: run until something very interesting is seen",
3731         forcefight_desc[] =
3732                      "Prefix: force fight even if you don't see a monster";
3733     static const struct {
3734         int nhkf;
3735         const char *desc;
3736         boolean numpad;
3737     } misc_keys[] = {
3738         { NHKF_ESC, "escape from the current query/action", FALSE },
3739         { NHKF_RUSH,
3740           "Prefix: rush until something interesting is seen", FALSE },
3741         { NHKF_RUN, run_desc, FALSE },
3742         { NHKF_RUN2, run_desc, TRUE },
3743         { NHKF_FIGHT, forcefight_desc, FALSE },
3744         { NHKF_FIGHT2, forcefight_desc, TRUE } ,
3745         { NHKF_NOPICKUP,
3746           "Prefix: move without picking up objects/fighting", FALSE },
3747         { NHKF_RUN_NOPICKUP,
3748           "Prefix: run without picking up objects/fighting", FALSE },
3749         { NHKF_DOINV, "view inventory", TRUE },
3750         { NHKF_REQMENU, "Prefix: request a menu", FALSE },
3751 #ifdef REDO
3752         { NHKF_DOAGAIN , "re-do: perform the previous command again", FALSE },
3753 #endif
3754         { 0, (const char *) 0, FALSE }
3755     };
3756 
3757     datawin = create_nhwindow(NHW_TEXT);
3758     putstr(datawin, 0, "");
3759     putstr(datawin, 0, "            Full Current Key Bindings List");
3760 
3761     /* directional keys */
3762     putstr(datawin, 0, "");
3763     putstr(datawin, 0, "Directional keys:");
3764     show_direction_keys(datawin, '.', FALSE); /* '.'==self in direction grid */
3765 
3766     keys_used[(uchar) Cmd.move_NW] = keys_used[(uchar) Cmd.move_N]
3767         = keys_used[(uchar) Cmd.move_NE] = keys_used[(uchar) Cmd.move_W]
3768         = keys_used[(uchar) Cmd.move_E] = keys_used[(uchar) Cmd.move_SW]
3769         = keys_used[(uchar) Cmd.move_S] = keys_used[(uchar) Cmd.move_SE]
3770         = TRUE;
3771 
3772     if (!iflags.num_pad) {
3773         keys_used[(uchar) highc(Cmd.move_NW)]
3774             = keys_used[(uchar) highc(Cmd.move_N)]
3775             = keys_used[(uchar) highc(Cmd.move_NE)]
3776             = keys_used[(uchar) highc(Cmd.move_W)]
3777             = keys_used[(uchar) highc(Cmd.move_E)]
3778             = keys_used[(uchar) highc(Cmd.move_SW)]
3779             = keys_used[(uchar) highc(Cmd.move_S)]
3780             = keys_used[(uchar) highc(Cmd.move_SE)] = TRUE;
3781         keys_used[(uchar) C(Cmd.move_NW)]
3782             = keys_used[(uchar) C(Cmd.move_N)]
3783             = keys_used[(uchar) C(Cmd.move_NE)]
3784             = keys_used[(uchar) C(Cmd.move_W)]
3785             = keys_used[(uchar) C(Cmd.move_E)]
3786             = keys_used[(uchar) C(Cmd.move_SW)]
3787             = keys_used[(uchar) C(Cmd.move_S)]
3788             = keys_used[(uchar) C(Cmd.move_SE)] = TRUE;
3789         putstr(datawin, 0, "");
3790         putstr(datawin, 0,
3791           "Shift-<direction> will move in specified direction until you hit");
3792         putstr(datawin, 0, "        a wall or run into something.");
3793         putstr(datawin, 0,
3794           "Ctrl-<direction> will run in specified direction until something");
3795         putstr(datawin, 0, "        very interesting is seen.");
3796     }
3797 
3798     putstr(datawin, 0, "");
3799     putstr(datawin, 0, "Miscellaneous keys:");
3800     for (i = 0; misc_keys[i].desc; i++) {
3801         key = Cmd.spkeys[misc_keys[i].nhkf];
3802         if (key && ((misc_keys[i].numpad && iflags.num_pad)
3803                     || !misc_keys[i].numpad)) {
3804             keys_used[(uchar) key] = TRUE;
3805             Sprintf(buf, "%-8s %s", key2txt(key, buf2), misc_keys[i].desc);
3806             putstr(datawin, 0, buf);
3807         }
3808     }
3809 #ifndef NO_SIGNAL
3810     putstr(datawin, 0, "^c       break out of NetHack (SIGINT)");
3811     keys_used[(uchar) C('c')] = TRUE;
3812 #endif
3813 
3814     putstr(datawin, 0, "");
3815     show_menu_controls(datawin, TRUE);
3816 
3817     if (dokeylist_putcmds(datawin, TRUE, GENERALCMD, WIZMODECMD, keys_used)) {
3818         putstr(datawin, 0, "");
3819         putstr(datawin, 0, "General commands:");
3820         (void) dokeylist_putcmds(datawin, FALSE, GENERALCMD, WIZMODECMD,
3821                                  keys_used);
3822     }
3823 
3824     if (dokeylist_putcmds(datawin, TRUE, 0, WIZMODECMD, keys_used)) {
3825         putstr(datawin, 0, "");
3826         putstr(datawin, 0, "Game commands:");
3827         (void) dokeylist_putcmds(datawin, FALSE, 0, WIZMODECMD, keys_used);
3828     }
3829 
3830     if (wizard
3831         && dokeylist_putcmds(datawin, TRUE, WIZMODECMD, 0, keys_used)) {
3832         putstr(datawin, 0, "");
3833         putstr(datawin, 0, "Wizard-mode commands:");
3834         (void) dokeylist_putcmds(datawin, FALSE, WIZMODECMD, 0, keys_used);
3835     }
3836 
3837     display_nhwindow(datawin, FALSE);
3838     destroy_nhwindow(datawin);
3839 }
3840 
3841 char
3842 cmd_from_func(fn)
3843 int NDECL((*fn));
3844 {
3845     int i;
3846 
3847     for (i = 0; i < 256; ++i)
3848         if (Cmd.commands[i] && Cmd.commands[i]->ef_funct == fn)
3849             return (char) i;
3850     return '\0';
3851 }
3852 
3853 /*
3854  * wizard mode sanity_check code
3855  */
3856 
3857 static const char template[] = "%-27s  %4ld  %6ld";
3858 static const char stats_hdr[] = "                             count  bytes";
3859 static const char stats_sep[] = "---------------------------  ----- -------";
3860 
3861 STATIC_OVL int
size_obj(otmp)3862 size_obj(otmp)
3863 struct obj *otmp;
3864 {
3865     int sz = (int) sizeof (struct obj);
3866 
3867     if (otmp->oextra) {
3868         sz += (int) sizeof (struct oextra);
3869         if (ONAME(otmp))
3870             sz += (int) strlen(ONAME(otmp)) + 1;
3871         if (OMONST(otmp))
3872             sz += size_monst(OMONST(otmp), FALSE);
3873         if (OMID(otmp))
3874             sz += (int) sizeof (unsigned);
3875         if (OLONG(otmp))
3876             sz += (int) sizeof (long);
3877         if (OMAILCMD(otmp))
3878             sz += (int) strlen(OMAILCMD(otmp)) + 1;
3879     }
3880     return sz;
3881 }
3882 
3883 STATIC_OVL void
count_obj(chain,total_count,total_size,top,recurse)3884 count_obj(chain, total_count, total_size, top, recurse)
3885 struct obj *chain;
3886 long *total_count;
3887 long *total_size;
3888 boolean top;
3889 boolean recurse;
3890 {
3891     long count, size;
3892     struct obj *obj;
3893 
3894     for (count = size = 0, obj = chain; obj; obj = obj->nobj) {
3895         if (top) {
3896             count++;
3897             size += size_obj(obj);
3898         }
3899         if (recurse && obj->cobj)
3900             count_obj(obj->cobj, total_count, total_size, TRUE, TRUE);
3901     }
3902     *total_count += count;
3903     *total_size += size;
3904 }
3905 
3906 STATIC_OVL void
obj_chain(win,src,chain,force,total_count,total_size)3907 obj_chain(win, src, chain, force, total_count, total_size)
3908 winid win;
3909 const char *src;
3910 struct obj *chain;
3911 boolean force;
3912 long *total_count;
3913 long *total_size;
3914 {
3915     char buf[BUFSZ];
3916     long count = 0L, size = 0L;
3917 
3918     count_obj(chain, &count, &size, TRUE, FALSE);
3919 
3920     if (count || size || force) {
3921         *total_count += count;
3922         *total_size += size;
3923         Sprintf(buf, template, src, count, size);
3924         putstr(win, 0, buf);
3925     }
3926 }
3927 
3928 STATIC_OVL void
mon_invent_chain(win,src,chain,total_count,total_size)3929 mon_invent_chain(win, src, chain, total_count, total_size)
3930 winid win;
3931 const char *src;
3932 struct monst *chain;
3933 long *total_count;
3934 long *total_size;
3935 {
3936     char buf[BUFSZ];
3937     long count = 0, size = 0;
3938     struct monst *mon;
3939 
3940     for (mon = chain; mon; mon = mon->nmon)
3941         count_obj(mon->minvent, &count, &size, TRUE, FALSE);
3942 
3943     if (count || size) {
3944         *total_count += count;
3945         *total_size += size;
3946         Sprintf(buf, template, src, count, size);
3947         putstr(win, 0, buf);
3948     }
3949 }
3950 
3951 STATIC_OVL void
contained_stats(win,src,total_count,total_size)3952 contained_stats(win, src, total_count, total_size)
3953 winid win;
3954 const char *src;
3955 long *total_count;
3956 long *total_size;
3957 {
3958     char buf[BUFSZ];
3959     long count = 0, size = 0;
3960     struct monst *mon;
3961 
3962     count_obj(invent, &count, &size, FALSE, TRUE);
3963     count_obj(fobj, &count, &size, FALSE, TRUE);
3964     count_obj(level.buriedobjlist, &count, &size, FALSE, TRUE);
3965     count_obj(migrating_objs, &count, &size, FALSE, TRUE);
3966     /* DEADMONSTER check not required in this loop since they have no
3967      * inventory */
3968     for (mon = fmon; mon; mon = mon->nmon)
3969         count_obj(mon->minvent, &count, &size, FALSE, TRUE);
3970     for (mon = migrating_mons; mon; mon = mon->nmon)
3971         count_obj(mon->minvent, &count, &size, FALSE, TRUE);
3972 
3973     if (count || size) {
3974         *total_count += count;
3975         *total_size += size;
3976         Sprintf(buf, template, src, count, size);
3977         putstr(win, 0, buf);
3978     }
3979 }
3980 
3981 STATIC_OVL int
size_monst(mtmp,incl_wsegs)3982 size_monst(mtmp, incl_wsegs)
3983 struct monst *mtmp;
3984 boolean incl_wsegs;
3985 {
3986     int sz = (int) sizeof (struct monst);
3987 
3988     if (mtmp->wormno && incl_wsegs)
3989         sz += size_wseg(mtmp);
3990 
3991     if (mtmp->mextra) {
3992         sz += (int) sizeof (struct mextra);
3993         if (MNAME(mtmp))
3994             sz += (int) strlen(MNAME(mtmp)) + 1;
3995         if (EGD(mtmp))
3996             sz += (int) sizeof (struct egd);
3997         if (EPRI(mtmp))
3998             sz += (int) sizeof (struct epri);
3999         if (ESHK(mtmp))
4000             sz += (int) sizeof (struct eshk);
4001         if (EMIN(mtmp))
4002             sz += (int) sizeof (struct emin);
4003         if (EDOG(mtmp))
4004             sz += (int) sizeof (struct edog);
4005         /* mextra->mcorpsenm doesn't point to more memory */
4006     }
4007     return sz;
4008 }
4009 
4010 STATIC_OVL void
mon_chain(win,src,chain,force,total_count,total_size)4011 mon_chain(win, src, chain, force, total_count, total_size)
4012 winid win;
4013 const char *src;
4014 struct monst *chain;
4015 boolean force;
4016 long *total_count;
4017 long *total_size;
4018 {
4019     char buf[BUFSZ];
4020     long count, size;
4021     struct monst *mon;
4022     /* mon->wormno means something different for migrating_mons and mydogs */
4023     boolean incl_wsegs = !strcmpi(src, "fmon");
4024 
4025     count = size = 0L;
4026     for (mon = chain; mon; mon = mon->nmon) {
4027         count++;
4028         size += size_monst(mon, incl_wsegs);
4029     }
4030     if (count || size || force) {
4031         *total_count += count;
4032         *total_size += size;
4033         Sprintf(buf, template, src, count, size);
4034         putstr(win, 0, buf);
4035     }
4036 }
4037 
4038 STATIC_OVL void
misc_stats(win,total_count,total_size)4039 misc_stats(win, total_count, total_size)
4040 winid win;
4041 long *total_count;
4042 long *total_size;
4043 {
4044     char buf[BUFSZ], hdrbuf[QBUFSZ];
4045     long count, size;
4046     int idx;
4047     struct trap *tt;
4048     struct damage *sd; /* shop damage */
4049     struct kinfo *k; /* delayed killer */
4050     struct cemetery *bi; /* bones info */
4051 
4052     /* traps and engravings are output unconditionally;
4053      * others only if nonzero
4054      */
4055     count = size = 0L;
4056     for (tt = ftrap; tt; tt = tt->ntrap) {
4057         ++count;
4058         size += (long) sizeof *tt;
4059     }
4060     *total_count += count;
4061     *total_size += size;
4062     Sprintf(hdrbuf, "traps, size %ld", (long) sizeof (struct trap));
4063     Sprintf(buf, template, hdrbuf, count, size);
4064     putstr(win, 0, buf);
4065 
4066     count = size = 0L;
4067     engr_stats("engravings, size %ld+text", hdrbuf, &count, &size);
4068     *total_count += count;
4069     *total_size += size;
4070     Sprintf(buf, template, hdrbuf, count, size);
4071     putstr(win, 0, buf);
4072 
4073     count = size = 0L;
4074     light_stats("light sources, size %ld", hdrbuf, &count, &size);
4075     if (count || size) {
4076         *total_count += count;
4077         *total_size += size;
4078         Sprintf(buf, template, hdrbuf, count, size);
4079         putstr(win, 0, buf);
4080     }
4081 
4082     count = size = 0L;
4083     timer_stats("timers, size %ld", hdrbuf, &count, &size);
4084     if (count || size) {
4085         *total_count += count;
4086         *total_size += size;
4087         Sprintf(buf, template, hdrbuf, count, size);
4088         putstr(win, 0, buf);
4089     }
4090 
4091     count = size = 0L;
4092     for (sd = level.damagelist; sd; sd = sd->next) {
4093         ++count;
4094         size += (long) sizeof *sd;
4095     }
4096     if (count || size) {
4097         *total_count += count;
4098         *total_size += size;
4099         Sprintf(hdrbuf, "shop damage, size %ld",
4100                 (long) sizeof (struct damage));
4101         Sprintf(buf, template, hdrbuf, count, size);
4102         putstr(win, 0, buf);
4103     }
4104 
4105     count = size = 0L;
4106     region_stats("regions, size %ld+%ld*rect+N", hdrbuf, &count, &size);
4107     if (count || size) {
4108         *total_count += count;
4109         *total_size += size;
4110         Sprintf(buf, template, hdrbuf, count, size);
4111         putstr(win, 0, buf);
4112     }
4113 
4114     count = size = 0L;
4115     for (k = killer.next; k; k = k->next) {
4116         ++count;
4117         size += (long) sizeof *k;
4118     }
4119     if (count || size) {
4120         *total_count += count;
4121         *total_size += size;
4122         Sprintf(hdrbuf, "delayed killer%s, size %ld",
4123                 plur(count), (long) sizeof (struct kinfo));
4124         Sprintf(buf, template, hdrbuf, count, size);
4125         putstr(win, 0, buf);
4126     }
4127 
4128     count = size = 0L;
4129     for (bi = level.bonesinfo; bi; bi = bi->next) {
4130         ++count;
4131         size += (long) sizeof *bi;
4132     }
4133     if (count || size) {
4134         *total_count += count;
4135         *total_size += size;
4136         Sprintf(hdrbuf, "bones history, size %ld",
4137                 (long) sizeof (struct cemetery));
4138         Sprintf(buf, template, hdrbuf, count, size);
4139         putstr(win, 0, buf);
4140     }
4141 
4142     count = size = 0L;
4143     for (idx = 0; idx < NUM_OBJECTS; ++idx)
4144         if (objects[idx].oc_uname) {
4145             ++count;
4146             size += (long) (strlen(objects[idx].oc_uname) + 1);
4147         }
4148     if (count || size) {
4149         *total_count += count;
4150         *total_size += size;
4151         Strcpy(hdrbuf, "object type names, text");
4152         Sprintf(buf, template, hdrbuf, count, size);
4153         putstr(win, 0, buf);
4154     }
4155 }
4156 
4157 /*
4158  * Display memory usage of all monsters and objects on the level.
4159  */
4160 static int
wiz_show_stats()4161 wiz_show_stats()
4162 {
4163     char buf[BUFSZ];
4164     winid win;
4165     long total_obj_size, total_obj_count,
4166          total_mon_size, total_mon_count,
4167          total_ovr_size, total_ovr_count,
4168          total_misc_size, total_misc_count;
4169 
4170     win = create_nhwindow(NHW_TEXT);
4171     putstr(win, 0, "Current memory statistics:");
4172 
4173     total_obj_count = total_obj_size = 0L;
4174     putstr(win, 0, stats_hdr);
4175     Sprintf(buf, "  Objects, base size %ld", (long) sizeof (struct obj));
4176     putstr(win, 0, buf);
4177     obj_chain(win, "invent", invent, TRUE, &total_obj_count, &total_obj_size);
4178     obj_chain(win, "fobj", fobj, TRUE, &total_obj_count, &total_obj_size);
4179     obj_chain(win, "buried", level.buriedobjlist, FALSE,
4180               &total_obj_count, &total_obj_size);
4181     obj_chain(win, "migrating obj", migrating_objs, FALSE,
4182               &total_obj_count, &total_obj_size);
4183     obj_chain(win, "billobjs", billobjs, FALSE,
4184               &total_obj_count, &total_obj_size);
4185     mon_invent_chain(win, "minvent", fmon, &total_obj_count, &total_obj_size);
4186     mon_invent_chain(win, "migrating minvent", migrating_mons,
4187                      &total_obj_count, &total_obj_size);
4188     contained_stats(win, "contained", &total_obj_count, &total_obj_size);
4189     putstr(win, 0, stats_sep);
4190     Sprintf(buf, template, "  Obj total", total_obj_count, total_obj_size);
4191     putstr(win, 0, buf);
4192 
4193     total_mon_count = total_mon_size = 0L;
4194     putstr(win, 0, "");
4195     Sprintf(buf, "  Monsters, base size %ld", (long) sizeof (struct monst));
4196     putstr(win, 0, buf);
4197     mon_chain(win, "fmon", fmon, TRUE, &total_mon_count, &total_mon_size);
4198     mon_chain(win, "migrating", migrating_mons, FALSE,
4199               &total_mon_count, &total_mon_size);
4200     /* 'mydogs' is only valid during level change or end of game disclosure,
4201        but conceivably we've been called from within debugger at such time */
4202     if (mydogs) /* monsters accompanying hero */
4203         mon_chain(win, "mydogs", mydogs, FALSE,
4204                   &total_mon_count, &total_mon_size);
4205     putstr(win, 0, stats_sep);
4206     Sprintf(buf, template, "  Mon total", total_mon_count, total_mon_size);
4207     putstr(win, 0, buf);
4208 
4209     total_ovr_count = total_ovr_size = 0L;
4210     putstr(win, 0, "");
4211     putstr(win, 0, "  Overview");
4212     overview_stats(win, template, &total_ovr_count, &total_ovr_size);
4213     putstr(win, 0, stats_sep);
4214     Sprintf(buf, template, "  Over total", total_ovr_count, total_ovr_size);
4215     putstr(win, 0, buf);
4216 
4217     total_misc_count = total_misc_size = 0L;
4218     putstr(win, 0, "");
4219     putstr(win, 0, "  Miscellaneous");
4220     misc_stats(win, &total_misc_count, &total_misc_size);
4221     putstr(win, 0, stats_sep);
4222     Sprintf(buf, template, "  Misc total", total_misc_count, total_misc_size);
4223     putstr(win, 0, buf);
4224 
4225     putstr(win, 0, "");
4226     putstr(win, 0, stats_sep);
4227     Sprintf(buf, template, "  Grand total",
4228             (total_obj_count + total_mon_count
4229              + total_ovr_count + total_misc_count),
4230             (total_obj_size + total_mon_size
4231              + total_ovr_size + total_misc_size));
4232     putstr(win, 0, buf);
4233 
4234 #if defined(__BORLANDC__) && !defined(_WIN32)
4235     show_borlandc_stats(win);
4236 #endif
4237 
4238     display_nhwindow(win, FALSE);
4239     destroy_nhwindow(win);
4240     return 0;
4241 }
4242 
4243 void
sanity_check()4244 sanity_check()
4245 {
4246     obj_sanity_check();
4247     timer_sanity_check();
4248     mon_sanity_check();
4249     light_sources_sanity_check();
4250     bc_sanity_check();
4251 }
4252 
4253 #ifdef DEBUG_MIGRATING_MONS
4254 static int
wiz_migrate_mons()4255 wiz_migrate_mons()
4256 {
4257     int mcount = 0;
4258     char inbuf[BUFSZ] = DUMMY;
4259     struct permonst *ptr;
4260     struct monst *mtmp;
4261     d_level tolevel;
4262 
4263     getlin("How many random monsters to migrate? [0]", inbuf);
4264     if (*inbuf == '\033')
4265         return 0;
4266     mcount = atoi(inbuf);
4267     if (mcount < 0 || mcount > (COLNO * ROWNO) || Is_botlevel(&u.uz))
4268         return 0;
4269     while (mcount > 0) {
4270         if (Is_stronghold(&u.uz))
4271             assign_level(&tolevel, &valley_level);
4272         else
4273             get_level(&tolevel, depth(&u.uz) + 1);
4274         ptr = rndmonst();
4275         mtmp = makemon(ptr, 0, 0, NO_MM_FLAGS);
4276         if (mtmp)
4277             migrate_to_level(mtmp, ledger_no(&tolevel), MIGR_RANDOM,
4278                              (coord *) 0);
4279         mcount--;
4280     }
4281     return 0;
4282 }
4283 #endif
4284 
4285 struct {
4286     int nhkf;
4287     char key;
4288     const char *name;
4289 } const spkeys_binds[] = {
4290     { NHKF_ESC,              '\033', (char *) 0 }, /* no binding */
4291     { NHKF_DOAGAIN,          DOAGAIN, "repeat" },
4292     { NHKF_REQMENU,          'm', "reqmenu" },
4293     { NHKF_RUN,              'G', "run" },
4294     { NHKF_RUN2,             '5', "run.numpad" },
4295     { NHKF_RUSH,             'g', "rush" },
4296     { NHKF_FIGHT,            'F', "fight" },
4297     { NHKF_FIGHT2,           '-', "fight.numpad" },
4298     { NHKF_NOPICKUP,         'm', "nopickup" },
4299     { NHKF_RUN_NOPICKUP,     'M', "run.nopickup" },
4300     { NHKF_DOINV,            '0', "doinv" },
4301     { NHKF_TRAVEL,           CMD_TRAVEL, (char *) 0 }, /* no binding */
4302     { NHKF_CLICKLOOK,        CMD_CLICKLOOK, (char *) 0 }, /* no binding */
4303     { NHKF_REDRAW,           C('r'), "redraw" },
4304     { NHKF_REDRAW2,          C('l'), "redraw.numpad" },
4305     { NHKF_GETDIR_SELF,      '.', "getdir.self" },
4306     { NHKF_GETDIR_SELF2,     's', "getdir.self2" },
4307     { NHKF_GETDIR_HELP,      '?', "getdir.help" },
4308     { NHKF_COUNT,            'n', "count" },
4309     { NHKF_GETPOS_SELF,      '@', "getpos.self" },
4310     { NHKF_GETPOS_PICK,      '.', "getpos.pick" },
4311     { NHKF_GETPOS_PICK_Q,    ',', "getpos.pick.quick" },
4312     { NHKF_GETPOS_PICK_O,    ';', "getpos.pick.once" },
4313     { NHKF_GETPOS_PICK_V,    ':', "getpos.pick.verbose" },
4314     { NHKF_GETPOS_SHOWVALID, '$', "getpos.valid" },
4315     { NHKF_GETPOS_AUTODESC,  '#', "getpos.autodescribe" },
4316     { NHKF_GETPOS_MON_NEXT,  'm', "getpos.mon.next" },
4317     { NHKF_GETPOS_MON_PREV,  'M', "getpos.mon.prev" },
4318     { NHKF_GETPOS_OBJ_NEXT,  'o', "getpos.obj.next" },
4319     { NHKF_GETPOS_OBJ_PREV,  'O', "getpos.obj.prev" },
4320     { NHKF_GETPOS_DOOR_NEXT, 'd', "getpos.door.next" },
4321     { NHKF_GETPOS_DOOR_PREV, 'D', "getpos.door.prev" },
4322     { NHKF_GETPOS_UNEX_NEXT, 'x', "getpos.unexplored.next" },
4323     { NHKF_GETPOS_UNEX_PREV, 'X', "getpos.unexplored.prev" },
4324     { NHKF_GETPOS_VALID_NEXT, 'z', "getpos.valid.next" },
4325     { NHKF_GETPOS_VALID_PREV, 'Z', "getpos.valid.prev" },
4326     { NHKF_GETPOS_INTERESTING_NEXT, 'a', "getpos.all.next" },
4327     { NHKF_GETPOS_INTERESTING_PREV, 'A', "getpos.all.prev" },
4328     { NHKF_GETPOS_HELP,      '?', "getpos.help" },
4329     { NHKF_GETPOS_LIMITVIEW, '"', "getpos.filter" },
4330     { NHKF_GETPOS_MOVESKIP,  '*', "getpos.moveskip" },
4331     { NHKF_GETPOS_MENU,      '!', "getpos.menu" }
4332 };
4333 
4334 boolean
bind_specialkey(key,command)4335 bind_specialkey(key, command)
4336 uchar key;
4337 const char *command;
4338 {
4339     int i;
4340     for (i = 0; i < SIZE(spkeys_binds); i++) {
4341         if (!spkeys_binds[i].name || strcmp(command, spkeys_binds[i].name))
4342             continue;
4343         Cmd.spkeys[spkeys_binds[i].nhkf] = key;
4344         return TRUE;
4345     }
4346     return FALSE;
4347 }
4348 
4349 /* returns a one-byte character from the text (it may massacre the txt
4350  * buffer) */
4351 char
txt2key(txt)4352 txt2key(txt)
4353 char *txt;
4354 {
4355     txt = trimspaces(txt);
4356     if (!*txt)
4357         return '\0';
4358 
4359     /* simple character */
4360     if (!txt[1])
4361         return txt[0];
4362 
4363     /* a few special entries */
4364     if (!strcmp(txt, "<enter>"))
4365         return '\n';
4366     if (!strcmp(txt, "<space>"))
4367         return ' ';
4368     if (!strcmp(txt, "<esc>"))
4369         return '\033';
4370 
4371     /* control and meta keys */
4372     switch (*txt) {
4373     case 'm': /* can be mx, Mx, m-x, M-x */
4374     case 'M':
4375         txt++;
4376         if (*txt == '-' && txt[1])
4377             txt++;
4378         if (txt[1])
4379             return '\0';
4380         return M(*txt);
4381     case 'c': /* can be cx, Cx, ^x, c-x, C-x, ^-x */
4382     case 'C':
4383     case '^':
4384         txt++;
4385         if (*txt == '-' && txt[1])
4386             txt++;
4387         if (txt[1])
4388             return '\0';
4389         return C(*txt);
4390     }
4391 
4392     /* ascii codes: must be three-digit decimal */
4393     if (*txt >= '0' && *txt <= '9') {
4394         uchar key = 0;
4395         int i;
4396 
4397         for (i = 0; i < 3; i++) {
4398             if (txt[i] < '0' || txt[i] > '9')
4399                 return '\0';
4400             key = 10 * key + txt[i] - '0';
4401         }
4402         return key;
4403     }
4404 
4405     return '\0';
4406 }
4407 
4408 /* returns the text for a one-byte encoding;
4409  * must be shorter than a tab for proper formatting */
4410 char *
key2txt(c,txt)4411 key2txt(c, txt)
4412 uchar c;
4413 char *txt; /* sufficiently long buffer */
4414 {
4415     /* should probably switch to "SPC", "ESC", "RET"
4416        since nethack's documentation uses ESC for <escape> */
4417     if (c == ' ')
4418         Sprintf(txt, "<space>");
4419     else if (c == '\033')
4420         Sprintf(txt, "<esc>");
4421     else if (c == '\n')
4422         Sprintf(txt, "<enter>");
4423     else if (c == '\177')
4424         Sprintf(txt, "<del>"); /* "<delete>" won't fit */
4425     else
4426         Strcpy(txt, visctrl((char) c));
4427     return txt;
4428 }
4429 
4430 
4431 void
parseautocomplete(autocomplete,condition)4432 parseautocomplete(autocomplete, condition)
4433 char *autocomplete;
4434 boolean condition;
4435 {
4436     struct ext_func_tab *efp;
4437     register char *autoc;
4438 
4439     /* break off first autocomplete from the rest; parse the rest */
4440     if ((autoc = index(autocomplete, ',')) != 0
4441         || (autoc = index(autocomplete, ':')) != 0) {
4442         *autoc++ = '\0';
4443         parseautocomplete(autoc, condition);
4444     }
4445 
4446     /* strip leading and trailing white space */
4447     autocomplete = trimspaces(autocomplete);
4448 
4449     if (!*autocomplete)
4450         return;
4451 
4452     /* take off negation */
4453     if (*autocomplete == '!') {
4454         /* unlike most options, a leading "no" might actually be a part of
4455          * the extended command.  Thus you have to use ! */
4456         autocomplete++;
4457         autocomplete = trimspaces(autocomplete);
4458         condition = !condition;
4459     }
4460 
4461     /* find and modify the extended command */
4462     for (efp = extcmdlist; efp->ef_txt; efp++) {
4463         if (!strcmp(autocomplete, efp->ef_txt)) {
4464             if (condition)
4465                 efp->flags |= AUTOCOMPLETE;
4466             else
4467                 efp->flags &= ~AUTOCOMPLETE;
4468             return;
4469         }
4470     }
4471 
4472     /* not a real extended command */
4473     raw_printf("Bad autocomplete: invalid extended command '%s'.",
4474                autocomplete);
4475     wait_synch();
4476 }
4477 
4478 /* called at startup and after number_pad is twiddled */
4479 void
reset_commands(initial)4480 reset_commands(initial)
4481 boolean initial;
4482 {
4483     static const char sdir[] = "hykulnjb><",
4484                       sdir_swap_yz[] = "hzkulnjb><",
4485                       ndir[] = "47896321><",
4486                       ndir_phone_layout[] = "41236987><";
4487     static const int ylist[] = {
4488         'y', 'Y', C('y'), M('y'), M('Y'), M(C('y'))
4489     };
4490     static struct ext_func_tab *back_dir_cmd[8];
4491     const struct ext_func_tab *cmdtmp;
4492     boolean flagtemp;
4493     int c, i, updated = 0;
4494     static boolean backed_dir_cmd = FALSE;
4495 
4496     if (initial) {
4497         updated = 1;
4498         Cmd.num_pad = FALSE;
4499         Cmd.pcHack_compat = Cmd.phone_layout = Cmd.swap_yz = FALSE;
4500         for (i = 0; i < SIZE(spkeys_binds); i++)
4501             Cmd.spkeys[spkeys_binds[i].nhkf] = spkeys_binds[i].key;
4502         commands_init();
4503     } else {
4504 
4505         if (backed_dir_cmd) {
4506             for (i = 0; i < 8; i++) {
4507                 Cmd.commands[(uchar) Cmd.dirchars[i]] = back_dir_cmd[i];
4508             }
4509         }
4510 
4511         /* basic num_pad */
4512         flagtemp = iflags.num_pad;
4513         if (flagtemp != Cmd.num_pad) {
4514             Cmd.num_pad = flagtemp;
4515             ++updated;
4516         }
4517         /* swap_yz mode (only applicable for !num_pad); intended for
4518            QWERTZ keyboard used in Central Europe, particularly Germany */
4519         flagtemp = (iflags.num_pad_mode & 1) ? !Cmd.num_pad : FALSE;
4520         if (flagtemp != Cmd.swap_yz) {
4521             Cmd.swap_yz = flagtemp;
4522             ++updated;
4523             /* Cmd.swap_yz has been toggled;
4524                perform the swap (or reverse previous one) */
4525             for (i = 0; i < SIZE(ylist); i++) {
4526                 c = ylist[i] & 0xff;
4527                 cmdtmp = Cmd.commands[c];              /* tmp = [y] */
4528                 Cmd.commands[c] = Cmd.commands[c + 1]; /* [y] = [z] */
4529                 Cmd.commands[c + 1] = cmdtmp;          /* [z] = tmp */
4530             }
4531         }
4532         /* MSDOS compatibility mode (only applicable for num_pad) */
4533         flagtemp = (iflags.num_pad_mode & 1) ? Cmd.num_pad : FALSE;
4534         if (flagtemp != Cmd.pcHack_compat) {
4535             Cmd.pcHack_compat = flagtemp;
4536             ++updated;
4537             /* pcHack_compat has been toggled */
4538             c = M('5') & 0xff;
4539             cmdtmp = Cmd.commands['5'];
4540             Cmd.commands['5'] = Cmd.commands[c];
4541             Cmd.commands[c] = cmdtmp;
4542             c = M('0') & 0xff;
4543             Cmd.commands[c] = Cmd.pcHack_compat ? Cmd.commands['I'] : 0;
4544         }
4545         /* phone keypad layout (only applicable for num_pad) */
4546         flagtemp = (iflags.num_pad_mode & 2) ? Cmd.num_pad : FALSE;
4547         if (flagtemp != Cmd.phone_layout) {
4548             Cmd.phone_layout = flagtemp;
4549             ++updated;
4550             /* phone_layout has been toggled */
4551             for (i = 0; i < 3; i++) {
4552                 c = '1' + i;             /* 1,2,3 <-> 7,8,9 */
4553                 cmdtmp = Cmd.commands[c];              /* tmp = [1] */
4554                 Cmd.commands[c] = Cmd.commands[c + 6]; /* [1] = [7] */
4555                 Cmd.commands[c + 6] = cmdtmp;          /* [7] = tmp */
4556                 c = (M('1') & 0xff) + i; /* M-1,M-2,M-3 <-> M-7,M-8,M-9 */
4557                 cmdtmp = Cmd.commands[c];              /* tmp = [M-1] */
4558                 Cmd.commands[c] = Cmd.commands[c + 6]; /* [M-1] = [M-7] */
4559                 Cmd.commands[c + 6] = cmdtmp;          /* [M-7] = tmp */
4560             }
4561         }
4562     } /*?initial*/
4563 
4564     if (updated)
4565         Cmd.serialno++;
4566     Cmd.dirchars = !Cmd.num_pad
4567                        ? (!Cmd.swap_yz ? sdir : sdir_swap_yz)
4568                        : (!Cmd.phone_layout ? ndir : ndir_phone_layout);
4569     Cmd.alphadirchars = !Cmd.num_pad ? Cmd.dirchars : sdir;
4570 
4571     Cmd.move_W = Cmd.dirchars[0];
4572     Cmd.move_NW = Cmd.dirchars[1];
4573     Cmd.move_N = Cmd.dirchars[2];
4574     Cmd.move_NE = Cmd.dirchars[3];
4575     Cmd.move_E = Cmd.dirchars[4];
4576     Cmd.move_SE = Cmd.dirchars[5];
4577     Cmd.move_S = Cmd.dirchars[6];
4578     Cmd.move_SW = Cmd.dirchars[7];
4579 
4580     if (!initial) {
4581         for (i = 0; i < 8; i++) {
4582             back_dir_cmd[i] =
4583                 (struct ext_func_tab *) Cmd.commands[(uchar) Cmd.dirchars[i]];
4584             Cmd.commands[(uchar) Cmd.dirchars[i]] = (struct ext_func_tab *) 0;
4585         }
4586         backed_dir_cmd = TRUE;
4587         for (i = 0; i < 8; i++)
4588             (void) bind_key(Cmd.dirchars[i], "nothing");
4589     }
4590 }
4591 
4592 /* non-movement commands which accept 'm' prefix to request menu operation */
4593 STATIC_OVL boolean
4594 accept_menu_prefix(cmd_func)
4595 int NDECL((*cmd_func));
4596 {
4597     if (cmd_func == dopickup || cmd_func == dotip
4598         /* eat, #offer, and apply tinning-kit all use floorfood() to pick
4599            an item on floor or in invent; 'm' skips picking from floor
4600            (ie, inventory only) rather than request use of menu operation */
4601         || cmd_func == doeat || cmd_func == dosacrifice || cmd_func == doapply
4602         /* 'm' for removing saddle from adjacent monster without checking
4603            for containers at <u.ux,u.uy> */
4604         || cmd_func == doloot
4605         /* travel: pop up a menu of interesting targets in view */
4606         || cmd_func == dotravel
4607         /* wizard mode ^V and ^T */
4608         || cmd_func == wiz_level_tele || cmd_func == dotelecmd
4609         /* 'm' prefix allowed for some extended commands */
4610         || cmd_func == doextcmd || cmd_func == doextlist)
4611         return TRUE;
4612     return FALSE;
4613 }
4614 
4615 char
randomkey()4616 randomkey()
4617 {
4618     static unsigned i = 0;
4619     char c;
4620 
4621     switch (rn2(16)) {
4622     default:
4623         c = '\033';
4624         break;
4625     case 0:
4626         c = '\n';
4627         break;
4628     case 1:
4629     case 2:
4630     case 3:
4631     case 4:
4632         c = (char) rn1('~' - ' ' + 1, ' ');
4633         break;
4634     case 5:
4635         c = (char) (rn2(2) ? '\t' : ' ');
4636         break;
4637     case 6:
4638         c = (char) rn1('z' - 'a' + 1, 'a');
4639         break;
4640     case 7:
4641         c = (char) rn1('Z' - 'A' + 1, 'A');
4642         break;
4643     case 8:
4644         c = extcmdlist[i++ % SIZE(extcmdlist)].key;
4645         break;
4646     case 9:
4647         c = '#';
4648         break;
4649     case 10:
4650     case 11:
4651     case 12:
4652         c = Cmd.dirchars[rn2(8)];
4653         if (!rn2(7))
4654             c = !Cmd.num_pad ? (!rn2(3) ? C(c) : (c + 'A' - 'a')) : M(c);
4655         break;
4656     case 13:
4657         c = (char) rn1('9' - '0' + 1, '0');
4658         break;
4659     case 14:
4660         /* any char, but avoid '\0' because it's used for mouse click */
4661         c = (char) rnd(iflags.wc_eight_bit_input ? 255 : 127);
4662         break;
4663     }
4664 
4665     return c;
4666 }
4667 
4668 void
random_response(buf,sz)4669 random_response(buf, sz)
4670 char *buf;
4671 int sz;
4672 {
4673     char c;
4674     int count = 0;
4675 
4676     for (;;) {
4677         c = randomkey();
4678         if (c == '\n')
4679             break;
4680         if (c == '\033') {
4681             count = 0;
4682             break;
4683         }
4684         if (count < sz - 1)
4685             buf[count++] = c;
4686     }
4687     buf[count] = '\0';
4688 }
4689 
4690 int
rnd_extcmd_idx(VOID_ARGS)4691 rnd_extcmd_idx(VOID_ARGS)
4692 {
4693     return rn2(extcmdlist_length + 1) - 1;
4694 }
4695 
4696 int
ch2spkeys(c,start,end)4697 ch2spkeys(c, start, end)
4698 char c;
4699 int start,end;
4700 {
4701     int i;
4702 
4703     for (i = start; i <= end; i++)
4704         if (Cmd.spkeys[i] == c)
4705             return i;
4706     return NHKF_ESC;
4707 }
4708 
4709 void
rhack(cmd)4710 rhack(cmd)
4711 register char *cmd;
4712 {
4713     int spkey;
4714     boolean prefix_seen, bad_command,
4715         firsttime = (cmd == 0);
4716 
4717     iflags.menu_requested = FALSE;
4718 #ifdef SAFERHANGUP
4719     if (program_state.done_hup)
4720         end_of_input();
4721 #endif
4722     if (firsttime) {
4723         context.nopick = 0;
4724         cmd = parse();
4725     }
4726     if (*cmd == Cmd.spkeys[NHKF_ESC]) {
4727         context.move = FALSE;
4728         return;
4729     }
4730     if (*cmd == DOAGAIN && !in_doagain && saveq[0]) {
4731         in_doagain = TRUE;
4732         stail = 0;
4733         rhack((char *) 0); /* read and execute command */
4734         in_doagain = FALSE;
4735         return;
4736     }
4737     /* Special case of *cmd == ' ' handled better below */
4738     if (!*cmd || *cmd == (char) 0377) {
4739         nhbell();
4740         context.move = FALSE;
4741         return; /* probably we just had an interrupt */
4742     }
4743 
4744     /* handle most movement commands */
4745     prefix_seen = FALSE;
4746     context.travel = context.travel1 = 0;
4747     spkey = ch2spkeys(*cmd, NHKF_RUN, NHKF_CLICKLOOK);
4748 
4749     switch (spkey) {
4750     case NHKF_RUSH:
4751         if (movecmd(cmd[1])) {
4752             context.run = 2;
4753             domove_attempting |= DOMOVE_RUSH;
4754         } else
4755             prefix_seen = TRUE;
4756         break;
4757     case NHKF_RUN2:
4758         if (!Cmd.num_pad)
4759             break;
4760         /*FALLTHRU*/
4761     case NHKF_RUN:
4762         if (movecmd(lowc(cmd[1]))) {
4763             context.run = 3;
4764             domove_attempting |= DOMOVE_RUSH;
4765         } else
4766             prefix_seen = TRUE;
4767         break;
4768     case NHKF_FIGHT2:
4769         if (!Cmd.num_pad)
4770             break;
4771         /*FALLTHRU*/
4772     /* Effects of movement commands and invisible monsters:
4773      * m: always move onto space (even if 'I' remembered)
4774      * F: always attack space (even if 'I' not remembered)
4775      * normal movement: attack if 'I', move otherwise.
4776      */
4777     case NHKF_FIGHT:
4778         if (movecmd(cmd[1])) {
4779             context.forcefight = 1;
4780             domove_attempting |= DOMOVE_WALK;
4781         } else
4782             prefix_seen = TRUE;
4783         break;
4784     case NHKF_NOPICKUP:
4785         if (movecmd(cmd[1]) || u.dz) {
4786             context.run = 0;
4787             context.nopick = 1;
4788             if (!u.dz)
4789                 domove_attempting |= DOMOVE_WALK;
4790             else
4791                 cmd[0] = cmd[1]; /* "m<" or "m>" */
4792         } else
4793             prefix_seen = TRUE;
4794         break;
4795     case NHKF_RUN_NOPICKUP:
4796         if (movecmd(lowc(cmd[1]))) {
4797             context.run = 1;
4798             context.nopick = 1;
4799             domove_attempting |= DOMOVE_RUSH;
4800         } else
4801             prefix_seen = TRUE;
4802         break;
4803     case NHKF_DOINV:
4804         if (!Cmd.num_pad)
4805             break;
4806         (void) ddoinv(); /* a convenience borrowed from the PC */
4807         context.move = FALSE;
4808         multi = 0;
4809         return;
4810     case NHKF_CLICKLOOK:
4811         if (iflags.clicklook) {
4812             context.move = FALSE;
4813             do_look(2, &clicklook_cc);
4814         }
4815         return;
4816     case NHKF_TRAVEL:
4817         if (flags.travelcmd) {
4818             context.travel = 1;
4819             context.travel1 = 1;
4820             context.run = 8;
4821             context.nopick = 1;
4822             domove_attempting |= DOMOVE_RUSH;
4823             break;
4824         }
4825         /*FALLTHRU*/
4826     default:
4827         if (movecmd(*cmd)) { /* ordinary movement */
4828             context.run = 0; /* only matters here if it was 8 */
4829             domove_attempting |= DOMOVE_WALK;
4830         } else if (movecmd(Cmd.num_pad ? unmeta(*cmd) : lowc(*cmd))) {
4831             context.run = 1;
4832             domove_attempting |= DOMOVE_RUSH;
4833         } else if (movecmd(unctrl(*cmd))) {
4834             context.run = 3;
4835             domove_attempting |= DOMOVE_RUSH;
4836         }
4837         break;
4838     }
4839 
4840     /* some special prefix handling */
4841     /* overload 'm' prefix to mean "request a menu" */
4842     if (prefix_seen && cmd[0] == Cmd.spkeys[NHKF_REQMENU]) {
4843         /* (for func_tab cast, see below) */
4844         const struct ext_func_tab *ft = Cmd.commands[cmd[1] & 0xff];
4845         int NDECL((*func)) = ft ? ((struct ext_func_tab *) ft)->ef_funct : 0;
4846 
4847         if (func && accept_menu_prefix(func)) {
4848             iflags.menu_requested = TRUE;
4849             ++cmd;
4850         }
4851     }
4852 
4853     if (((domove_attempting & (DOMOVE_RUSH | DOMOVE_WALK)) != 0L)
4854                             && !context.travel && !dxdy_moveok()) {
4855         /* trying to move diagonally as a grid bug;
4856            this used to be treated by movecmd() as not being
4857            a movement attempt, but that didn't provide for any
4858            feedback and led to strangeness if the key pressed
4859            ('u' in particular) was overloaded for num_pad use */
4860         You_cant("get there from here...");
4861         context.run = 0;
4862         context.nopick = context.forcefight = FALSE;
4863         context.move = context.mv = FALSE;
4864         multi = 0;
4865         return;
4866     }
4867 
4868     if ((domove_attempting & DOMOVE_WALK) != 0L) {
4869         if (multi)
4870             context.mv = TRUE;
4871         domove();
4872         context.forcefight = 0;
4873         return;
4874     } else if ((domove_attempting & DOMOVE_RUSH) != 0L) {
4875         if (firsttime) {
4876             if (!multi)
4877                 multi = max(COLNO, ROWNO);
4878             u.last_str_turn = 0;
4879         }
4880         context.mv = TRUE;
4881         domove();
4882         return;
4883     } else if (prefix_seen && cmd[1] == Cmd.spkeys[NHKF_ESC]) {
4884         /* <prefix><escape> */
4885         /* don't report "unknown command" for change of heart... */
4886         bad_command = FALSE;
4887     } else if (*cmd == ' ' && !flags.rest_on_space) {
4888         bad_command = TRUE; /* skip cmdlist[] loop */
4889 
4890     /* handle all other commands */
4891     } else {
4892         register const struct ext_func_tab *tlist;
4893         int res, NDECL((*func));
4894 
4895         /* current - use *cmd to directly index cmdlist array */
4896         if ((tlist = Cmd.commands[*cmd & 0xff]) != 0) {
4897             if (!wizard && (tlist->flags & WIZMODECMD)) {
4898                 You_cant("do that!");
4899                 res = 0;
4900             } else if (u.uburied && !(tlist->flags & IFBURIED)) {
4901                 You_cant("do that while you are buried!");
4902                 res = 0;
4903             } else {
4904                 /* we discard 'const' because some compilers seem to have
4905                    trouble with the pointer passed to set_occupation() */
4906                 func = ((struct ext_func_tab *) tlist)->ef_funct;
4907                 if (tlist->f_text && !occupation && multi)
4908                     set_occupation(func, tlist->f_text, multi);
4909                 res = (*func)(); /* perform the command */
4910             }
4911             if (!res) {
4912                 context.move = FALSE;
4913                 multi = 0;
4914             }
4915             return;
4916         }
4917         /* if we reach here, cmd wasn't found in cmdlist[] */
4918         bad_command = TRUE;
4919     }
4920 
4921     if (bad_command) {
4922         char expcmd[20]; /* we expect 'cmd' to point to 1 or 2 chars */
4923         char c, c1 = cmd[1];
4924 
4925         expcmd[0] = '\0';
4926         while ((c = *cmd++) != '\0')
4927             Strcat(expcmd, visctrl(c)); /* add 1..4 chars plus terminator */
4928 
4929         if (!prefix_seen || !help_dir(c1, spkey, "Invalid direction key!"))
4930             Norep("Unknown command '%s'.", expcmd);
4931     }
4932     /* didn't move */
4933     context.move = FALSE;
4934     multi = 0;
4935     return;
4936 }
4937 
4938 /* convert an x,y pair into a direction code */
4939 int
xytod(x,y)4940 xytod(x, y)
4941 schar x, y;
4942 {
4943     register int dd;
4944 
4945     for (dd = 0; dd < 8; dd++)
4946         if (x == xdir[dd] && y == ydir[dd])
4947             return dd;
4948     return -1;
4949 }
4950 
4951 /* convert a direction code into an x,y pair */
4952 void
dtoxy(cc,dd)4953 dtoxy(cc, dd)
4954 coord *cc;
4955 register int dd;
4956 {
4957     cc->x = xdir[dd];
4958     cc->y = ydir[dd];
4959     return;
4960 }
4961 
4962 /* also sets u.dz, but returns false for <> */
4963 int
movecmd(sym)4964 movecmd(sym)
4965 char sym;
4966 {
4967     register const char *dp = index(Cmd.dirchars, sym);
4968 
4969     u.dz = 0;
4970     if (!dp || !*dp)
4971         return 0;
4972     u.dx = xdir[dp - Cmd.dirchars];
4973     u.dy = ydir[dp - Cmd.dirchars];
4974     u.dz = zdir[dp - Cmd.dirchars];
4975 #if 0 /* now handled elsewhere */
4976     if (u.dx && u.dy && NODIAG(u.umonnum)) {
4977         u.dx = u.dy = 0;
4978         return 0;
4979     }
4980 #endif
4981     return !u.dz;
4982 }
4983 
4984 /* grid bug handling which used to be in movecmd() */
4985 int
dxdy_moveok()4986 dxdy_moveok()
4987 {
4988     if (u.dx && u.dy && NODIAG(u.umonnum))
4989         u.dx = u.dy = 0;
4990     return u.dx || u.dy;
4991 }
4992 
4993 /* decide whether a character (user input keystroke) requests screen repaint */
4994 boolean
redraw_cmd(c)4995 redraw_cmd(c)
4996 char c;
4997 {
4998     return (boolean) (c == Cmd.spkeys[NHKF_REDRAW]
4999                       || (Cmd.num_pad && c == Cmd.spkeys[NHKF_REDRAW2]));
5000 }
5001 
5002 boolean
prefix_cmd(c)5003 prefix_cmd(c)
5004 char c;
5005 {
5006     return (c == Cmd.spkeys[NHKF_RUSH]
5007             || c == Cmd.spkeys[NHKF_RUN]
5008             || c == Cmd.spkeys[NHKF_NOPICKUP]
5009             || c == Cmd.spkeys[NHKF_RUN_NOPICKUP]
5010             || c == Cmd.spkeys[NHKF_FIGHT]
5011             || (Cmd.num_pad && (c == Cmd.spkeys[NHKF_RUN2]
5012                                 || c == Cmd.spkeys[NHKF_FIGHT2])));
5013 }
5014 
5015 /*
5016  * uses getdir() but unlike getdir() it specifically
5017  * produces coordinates using the direction from getdir()
5018  * and verifies that those coordinates are ok.
5019  *
5020  * If the call to getdir() returns 0, Never_mind is displayed.
5021  * If the resulting coordinates are not okay, emsg is displayed.
5022  *
5023  * Returns non-zero if coordinates in cc are valid.
5024  */
5025 int
get_adjacent_loc(prompt,emsg,x,y,cc)5026 get_adjacent_loc(prompt, emsg, x, y, cc)
5027 const char *prompt, *emsg;
5028 xchar x, y;
5029 coord *cc;
5030 {
5031     xchar new_x, new_y;
5032     if (!getdir(prompt)) {
5033         pline1(Never_mind);
5034         return 0;
5035     }
5036     new_x = x + u.dx;
5037     new_y = y + u.dy;
5038     if (cc && isok(new_x, new_y)) {
5039         cc->x = new_x;
5040         cc->y = new_y;
5041     } else {
5042         if (emsg)
5043             pline1(emsg);
5044         return 0;
5045     }
5046     return 1;
5047 }
5048 
5049 int
getdir(s)5050 getdir(s)
5051 const char *s;
5052 {
5053     char dirsym;
5054     int is_mov;
5055 
5056  retry:
5057     if (in_doagain || *readchar_queue)
5058         dirsym = readchar();
5059     else
5060         dirsym = yn_function((s && *s != '^') ? s : "In what direction?",
5061                              (char *) 0, '\0');
5062     /* remove the prompt string so caller won't have to */
5063     clear_nhwindow(WIN_MESSAGE);
5064 
5065     if (redraw_cmd(dirsym)) { /* ^R */
5066         docrt();              /* redraw */
5067         goto retry;
5068     }
5069     savech(dirsym);
5070 
5071     if (dirsym == Cmd.spkeys[NHKF_GETDIR_SELF]
5072         || dirsym == Cmd.spkeys[NHKF_GETDIR_SELF2]) {
5073         u.dx = u.dy = u.dz = 0;
5074     } else if (!(is_mov = movecmd(dirsym)) && !u.dz) {
5075         boolean did_help = FALSE, help_requested;
5076 
5077         if (!index(quitchars, dirsym)) {
5078             help_requested = (dirsym == Cmd.spkeys[NHKF_GETDIR_HELP]);
5079             if (help_requested || iflags.cmdassist) {
5080                 did_help = help_dir((s && *s == '^') ? dirsym : '\0',
5081                                     NHKF_ESC,
5082                                     help_requested ? (const char *) 0
5083                                                   : "Invalid direction key!");
5084                 if (help_requested)
5085                     goto retry;
5086             }
5087             if (!did_help)
5088                 pline("What a strange direction!");
5089         }
5090         return 0;
5091     } else if (is_mov && !dxdy_moveok()) {
5092         You_cant("orient yourself that direction.");
5093         return 0;
5094     }
5095     if (!u.dz && (Stunned || (Confusion && !rn2(5))))
5096         confdir();
5097     return 1;
5098 }
5099 
5100 STATIC_OVL void
show_direction_keys(win,centerchar,nodiag)5101 show_direction_keys(win, centerchar, nodiag)
5102 winid win; /* should specify a window which is using a fixed-width font... */
5103 char centerchar; /* '.' or '@' or ' ' */
5104 boolean nodiag;
5105 {
5106     char buf[BUFSZ];
5107 
5108     if (!centerchar)
5109         centerchar = ' ';
5110 
5111     if (nodiag) {
5112         Sprintf(buf, "             %c   ", Cmd.move_N);
5113         putstr(win, 0, buf);
5114         putstr(win, 0, "             |   ");
5115         Sprintf(buf, "          %c- %c -%c",
5116                 Cmd.move_W, centerchar, Cmd.move_E);
5117         putstr(win, 0, buf);
5118         putstr(win, 0, "             |   ");
5119         Sprintf(buf, "             %c   ", Cmd.move_S);
5120         putstr(win, 0, buf);
5121     } else {
5122         Sprintf(buf, "          %c  %c  %c",
5123                 Cmd.move_NW, Cmd.move_N, Cmd.move_NE);
5124         putstr(win, 0, buf);
5125         putstr(win, 0, "           \\ | / ");
5126         Sprintf(buf, "          %c- %c -%c",
5127                 Cmd.move_W, centerchar, Cmd.move_E);
5128         putstr(win, 0, buf);
5129         putstr(win, 0, "           / | \\ ");
5130         Sprintf(buf, "          %c  %c  %c",
5131                 Cmd.move_SW, Cmd.move_S, Cmd.move_SE);
5132         putstr(win, 0, buf);
5133     };
5134 }
5135 
5136 /* explain choices if player has asked for getdir() help or has given
5137    an invalid direction after a prefix key ('F', 'g', 'm', &c), which
5138    might be bogus but could be up, down, or self when not applicable */
5139 STATIC_OVL boolean
help_dir(sym,spkey,msg)5140 help_dir(sym, spkey, msg)
5141 char sym;
5142 int spkey; /* NHKF_ code for prefix key, if one was used, or for ESC */
5143 const char *msg;
5144 {
5145     static const char wiz_only_list[] = "EFGIVW";
5146     char ctrl;
5147     winid win;
5148     char buf[BUFSZ], buf2[BUFSZ], *explain;
5149     const char *dothat, *how;
5150     boolean prefixhandling, viawindow;
5151 
5152     /* NHKF_ESC indicates that player asked for help at getdir prompt */
5153     viawindow = (spkey == NHKF_ESC || iflags.cmdassist);
5154     prefixhandling = (spkey != NHKF_ESC);
5155     /*
5156      * Handling for prefix keys that don't want special directions.
5157      * Delivered via pline if 'cmdassist' is off, or instead of the
5158      * general message if it's on.
5159      */
5160     dothat = "do that";
5161     how = " at"; /* for "<action> at yourself"; not used for up/down */
5162     switch (spkey) {
5163     case NHKF_NOPICKUP:
5164         dothat = "move";
5165         break;
5166     case NHKF_RUSH:
5167         dothat = "rush";
5168         break;
5169     case NHKF_RUN2:
5170         if (!Cmd.num_pad)
5171             break;
5172         /*FALLTHRU*/
5173     case NHKF_RUN:
5174     case NHKF_RUN_NOPICKUP:
5175         dothat = "run";
5176         break;
5177     case NHKF_FIGHT2:
5178         if (!Cmd.num_pad)
5179             break;
5180         /*FALLTHRU*/
5181     case NHKF_FIGHT:
5182         dothat = "fight";
5183         how = ""; /* avoid "fight at yourself" */
5184         break;
5185     default:
5186         prefixhandling = FALSE;
5187         break;
5188     }
5189 
5190     buf[0] = '\0';
5191     /* for movement prefix followed by '.' or (numpad && 's') to mean 'self';
5192        note: '-' for hands (inventory form of 'self') is not handled here */
5193     if (prefixhandling
5194         && (sym == Cmd.spkeys[NHKF_GETDIR_SELF]
5195             || (Cmd.num_pad && sym == Cmd.spkeys[NHKF_GETDIR_SELF2]))) {
5196         Sprintf(buf, "You can't %s%s yourself.", dothat, how);
5197     /* for movement prefix followed by up or down */
5198     } else if (prefixhandling && (sym == '<' || sym == '>')) {
5199         Sprintf(buf, "You can't %s %s.", dothat,
5200                 /* was "upwards" and "downwards", but they're considered
5201                    to be variants of canonical "upward" and "downward" */
5202                 (sym == '<') ? "upward" : "downward");
5203     }
5204 
5205     /* if '!cmdassist', display via pline() and we're done (note: asking
5206        for help at getdir() prompt forces cmdassist for this operation) */
5207     if (!viawindow) {
5208         if (prefixhandling) {
5209             if (!*buf)
5210                 Sprintf(buf, "Invalid direction for '%s' prefix.",
5211                         visctrl(Cmd.spkeys[spkey]));
5212             pline("%s", buf);
5213             return TRUE;
5214         }
5215         /* when 'cmdassist' is off and caller doesn't insist, do nothing */
5216         return FALSE;
5217     }
5218 
5219     win = create_nhwindow(NHW_TEXT);
5220     if (!win)
5221         return FALSE;
5222 
5223     if (*buf) {
5224         /* show bad-prefix message instead of general invalid-direction one */
5225         putstr(win, 0, buf);
5226         putstr(win, 0, "");
5227     } else if (msg) {
5228         Sprintf(buf, "cmdassist: %s", msg);
5229         putstr(win, 0, buf);
5230         putstr(win, 0, "");
5231     }
5232 
5233     if (!prefixhandling && (letter(sym) || sym == '[')) {
5234         /* '[': old 'cmdhelp' showed ESC as ^[ */
5235         sym = highc(sym); /* @A-Z[ (note: letter() accepts '@') */
5236         ctrl = (sym - 'A') + 1; /* 0-27 (note: 28-31 aren't applicable) */
5237         if ((explain = dowhatdoes_core(ctrl, buf2)) != 0
5238             && (!index(wiz_only_list, sym) || wizard)) {
5239             Sprintf(buf, "Are you trying to use ^%c%s?", sym,
5240                     index(wiz_only_list, sym) ? ""
5241                         : " as specified in the Guidebook");
5242             putstr(win, 0, buf);
5243             putstr(win, 0, "");
5244             putstr(win, 0, explain);
5245             putstr(win, 0, "");
5246             putstr(win, 0,
5247                   "To use that command, hold down the <Ctrl> key as a shift");
5248             Sprintf(buf, "and press the <%c> key.", sym);
5249             putstr(win, 0, buf);
5250             putstr(win, 0, "");
5251         }
5252     }
5253 
5254     Sprintf(buf, "Valid direction keys%s%s%s are:",
5255             prefixhandling ? " to " : "", prefixhandling ? dothat : "",
5256             NODIAG(u.umonnum) ? " in your current form" : "");
5257     putstr(win, 0, buf);
5258     show_direction_keys(win, !prefixhandling ? '.' : ' ', NODIAG(u.umonnum));
5259 
5260     if (!prefixhandling || spkey == NHKF_NOPICKUP) {
5261         /* NOPICKUP: unlike the other prefix keys, 'm' allows up/down for
5262            stair traversal; we won't get here when "m<" or "m>" has been
5263            given but we include up and down for 'm'+invalid_direction;
5264            self is excluded as a viable direction for every prefix */
5265         putstr(win, 0, "");
5266         putstr(win, 0, "          <  up");
5267         putstr(win, 0, "          >  down");
5268         if (!prefixhandling) {
5269             int selfi = Cmd.num_pad ? NHKF_GETDIR_SELF2 : NHKF_GETDIR_SELF;
5270 
5271             Sprintf(buf,   "       %4s  direct at yourself",
5272                     visctrl(Cmd.spkeys[selfi]));
5273             putstr(win, 0, buf);
5274         }
5275     }
5276 
5277     if (msg) {
5278         /* non-null msg means that this wasn't an explicit user request */
5279         putstr(win, 0, "");
5280         putstr(win, 0,
5281                "(Suppress this message with !cmdassist in config file.)");
5282     }
5283     display_nhwindow(win, FALSE);
5284     destroy_nhwindow(win);
5285     return TRUE;
5286 }
5287 
5288 void
confdir()5289 confdir()
5290 {
5291     register int x = NODIAG(u.umonnum) ? 2 * rn2(4) : rn2(8);
5292 
5293     u.dx = xdir[x];
5294     u.dy = ydir[x];
5295     return;
5296 }
5297 
5298 const char *
directionname(dir)5299 directionname(dir)
5300 int dir;
5301 {
5302     static NEARDATA const char *const dirnames[] = {
5303         "west",      "northwest", "north",     "northeast", "east",
5304         "southeast", "south",     "southwest", "down",      "up",
5305     };
5306 
5307     if (dir < 0 || dir >= SIZE(dirnames))
5308         return "invalid";
5309     return dirnames[dir];
5310 }
5311 
5312 int
isok(x,y)5313 isok(x, y)
5314 register int x, y;
5315 {
5316     /* x corresponds to curx, so x==1 is the first column. Ach. %% */
5317     return x >= 1 && x <= COLNO - 1 && y >= 0 && y <= ROWNO - 1;
5318 }
5319 
5320 /* #herecmdmenu command */
5321 STATIC_PTR int
doherecmdmenu(VOID_ARGS)5322 doherecmdmenu(VOID_ARGS)
5323 {
5324     char ch = here_cmd_menu(TRUE);
5325 
5326     return ch ? 1 : 0;
5327 }
5328 
5329 /* #therecmdmenu command, a way to test there_cmd_menu without mouse */
5330 STATIC_PTR int
dotherecmdmenu(VOID_ARGS)5331 dotherecmdmenu(VOID_ARGS)
5332 {
5333     char ch;
5334 
5335     if (!getdir((const char *) 0) || !isok(u.ux + u.dx, u.uy + u.dy))
5336         return 0;
5337 
5338     if (u.dx || u.dy)
5339         ch = there_cmd_menu(TRUE, u.ux + u.dx, u.uy + u.dy);
5340     else
5341         ch = here_cmd_menu(TRUE);
5342 
5343     return ch ? 1 : 0;
5344 }
5345 
5346 STATIC_OVL void
add_herecmd_menuitem(win,func,text)5347 add_herecmd_menuitem(win, func, text)
5348 winid win;
5349 int NDECL((*func));
5350 const char *text;
5351 {
5352     char ch;
5353     anything any;
5354 
5355     if ((ch = cmd_from_func(func)) != '\0') {
5356         any = zeroany;
5357         any.a_nfunc = func;
5358         add_menu(win, NO_GLYPH, &any, 0, 0, ATR_NONE, text, MENU_UNSELECTED);
5359     }
5360 }
5361 
5362 STATIC_OVL char
there_cmd_menu(doit,x,y)5363 there_cmd_menu(doit, x, y)
5364 boolean doit;
5365 int x, y;
5366 {
5367     winid win;
5368     char ch;
5369     char buf[BUFSZ];
5370     schar typ = levl[x][y].typ;
5371     int npick, K = 0;
5372     menu_item *picks = (menu_item *) 0;
5373     struct trap *ttmp;
5374     struct monst *mtmp;
5375 
5376     win = create_nhwindow(NHW_MENU);
5377     start_menu(win);
5378 
5379     if (IS_DOOR(typ)) {
5380         boolean key_or_pick, card;
5381         int dm = levl[x][y].doormask;
5382 
5383         if ((dm & (D_CLOSED | D_LOCKED))) {
5384             add_herecmd_menuitem(win, doopen, "Open the door"), ++K;
5385             /* unfortunately there's no lknown flag for doors to
5386                remember the locked/unlocked state */
5387             key_or_pick = (carrying(SKELETON_KEY) || carrying(LOCK_PICK));
5388             card = (carrying(CREDIT_CARD) != 0);
5389             if (key_or_pick || card) {
5390                 Sprintf(buf, "%sunlock the door",
5391                         key_or_pick ? "lock or " : "");
5392                 add_herecmd_menuitem(win, doapply, upstart(buf)), ++K;
5393             }
5394             /* unfortunately there's no tknown flag for doors (or chests)
5395                to remember whether a trap had been found */
5396             add_herecmd_menuitem(win, dountrap,
5397                                  "Search the door for a trap"), ++K;
5398             /* [what about #force?] */
5399             add_herecmd_menuitem(win, dokick, "Kick the door"), ++K;
5400         } else if ((dm & D_ISOPEN)) {
5401             add_herecmd_menuitem(win, doclose, "Close the door"), ++K;
5402         }
5403     }
5404 
5405     if (typ <= SCORR)
5406         add_herecmd_menuitem(win, dosearch, "Search for secret doors"), ++K;
5407 
5408     if ((ttmp = t_at(x, y)) != 0 && ttmp->tseen) {
5409         add_herecmd_menuitem(win, doidtrap, "Examine trap"), ++K;
5410         if (ttmp->ttyp != VIBRATING_SQUARE)
5411             add_herecmd_menuitem(win, dountrap, "Attempt to disarm trap"), ++K;
5412     }
5413 
5414     mtmp = m_at(x, y);
5415     if (mtmp && !canspotmon(mtmp))
5416         mtmp = 0;
5417     if (mtmp && which_armor(mtmp, W_SADDLE)) {
5418         char *mnam = x_monnam(mtmp, ARTICLE_THE, (char *) 0,
5419                               SUPPRESS_SADDLE, FALSE);
5420 
5421         if (!u.usteed) {
5422             Sprintf(buf, "Ride %s", mnam);
5423             add_herecmd_menuitem(win, doride, buf), ++K;
5424         }
5425         Sprintf(buf, "Remove saddle from %s", mnam);
5426         add_herecmd_menuitem(win, doloot, buf), ++K;
5427     }
5428     if (mtmp && can_saddle(mtmp) && !which_armor(mtmp, W_SADDLE)
5429         && carrying(SADDLE)) {
5430         Sprintf(buf, "Put saddle on %s", mon_nam(mtmp)), ++K;
5431         add_herecmd_menuitem(win, doapply, buf);
5432     }
5433 #if 0
5434     if (mtmp || glyph_is_invisible(glyph_at(x, y))) {
5435         /* "Attack %s", mtmp ? mon_nam(mtmp) : "unseen creature" */
5436     } else {
5437         /* "Move %s", direction */
5438     }
5439 #endif
5440 
5441     if (K) {
5442         end_menu(win, "What do you want to do?");
5443         npick = select_menu(win, PICK_ONE, &picks);
5444     } else {
5445         pline("No applicable actions.");
5446         npick = 0;
5447     }
5448     destroy_nhwindow(win);
5449     ch = '\0';
5450     if (npick > 0) {
5451         int NDECL((*func)) = picks->item.a_nfunc;
5452         free((genericptr_t) picks);
5453 
5454         if (doit) {
5455             int ret = (*func)();
5456 
5457             ch = (char) ret;
5458         } else {
5459             ch = cmd_from_func(func);
5460         }
5461     }
5462     return ch;
5463 }
5464 
5465 STATIC_OVL char
here_cmd_menu(doit)5466 here_cmd_menu(doit)
5467 boolean doit;
5468 {
5469     winid win;
5470     char ch;
5471     char buf[BUFSZ];
5472     schar typ = levl[u.ux][u.uy].typ;
5473     int npick;
5474     menu_item *picks = (menu_item *) 0;
5475 
5476     win = create_nhwindow(NHW_MENU);
5477     start_menu(win);
5478 
5479     if (IS_FOUNTAIN(typ) || IS_SINK(typ)) {
5480         Sprintf(buf, "Drink from the %s",
5481                 defsyms[IS_FOUNTAIN(typ) ? S_fountain : S_sink].explanation);
5482         add_herecmd_menuitem(win, dodrink, buf);
5483     }
5484     if (IS_FOUNTAIN(typ))
5485         add_herecmd_menuitem(win, dodip,
5486                              "Dip something into the fountain");
5487     if (IS_THRONE(typ))
5488         add_herecmd_menuitem(win, dosit,
5489                              "Sit on the throne");
5490 
5491     if ((u.ux == xupstair && u.uy == yupstair)
5492         || (u.ux == sstairs.sx && u.uy == sstairs.sy && sstairs.up)
5493         || (u.ux == xupladder && u.uy == yupladder)) {
5494         Sprintf(buf, "Go up the %s",
5495                 (u.ux == xupladder && u.uy == yupladder)
5496                 ? "ladder" : "stairs");
5497         add_herecmd_menuitem(win, doup, buf);
5498     }
5499     if ((u.ux == xdnstair && u.uy == ydnstair)
5500         || (u.ux == sstairs.sx && u.uy == sstairs.sy && !sstairs.up)
5501         || (u.ux == xdnladder && u.uy == ydnladder)) {
5502         Sprintf(buf, "Go down the %s",
5503                 (u.ux == xupladder && u.uy == yupladder)
5504                 ? "ladder" : "stairs");
5505         add_herecmd_menuitem(win, dodown, buf);
5506     }
5507     if (u.usteed) { /* another movement choice */
5508         Sprintf(buf, "Dismount %s",
5509                 x_monnam(u.usteed, ARTICLE_THE, (char *) 0,
5510                          SUPPRESS_SADDLE, FALSE));
5511         add_herecmd_menuitem(win, doride, buf);
5512     }
5513 
5514 #if 0
5515     if (Upolyd) { /* before objects */
5516         Sprintf(buf, "Use %s special ability",
5517                 s_suffix(mons[u.umonnum].mname));
5518         add_herecmd_menuitem(win, domonability, buf);
5519     }
5520 #endif
5521 
5522     if (OBJ_AT(u.ux, u.uy)) {
5523         struct obj *otmp = level.objects[u.ux][u.uy];
5524 
5525         Sprintf(buf, "Pick up %s", otmp->nexthere ? "items" : doname(otmp));
5526         add_herecmd_menuitem(win, dopickup, buf);
5527 
5528         if (Is_container(otmp)) {
5529             Sprintf(buf, "Loot %s", doname(otmp));
5530             add_herecmd_menuitem(win, doloot, buf);
5531         }
5532         if (otmp->oclass == FOOD_CLASS) {
5533             Sprintf(buf, "Eat %s", doname(otmp));
5534             add_herecmd_menuitem(win, doeat, buf);
5535         }
5536     }
5537 
5538     if (invent)
5539         add_herecmd_menuitem(win, dodrop, "Drop items");
5540 
5541     add_herecmd_menuitem(win, donull, "Rest one turn");
5542     add_herecmd_menuitem(win, dosearch, "Search around you");
5543     add_herecmd_menuitem(win, dolook, "Look at what is here");
5544 
5545     end_menu(win, "What do you want to do?");
5546     npick = select_menu(win, PICK_ONE, &picks);
5547     destroy_nhwindow(win);
5548     ch = '\0';
5549     if (npick > 0) {
5550         int NDECL((*func)) = picks->item.a_nfunc;
5551         free((genericptr_t) picks);
5552 
5553         if (doit) {
5554             int ret = (*func)();
5555 
5556             ch = (char) ret;
5557         } else {
5558             ch = cmd_from_func(func);
5559         }
5560     }
5561     return ch;
5562 }
5563 
5564 
5565 static NEARDATA int last_multi;
5566 
5567 /*
5568  * convert a MAP window position into a movecmd
5569  */
5570 const char *
click_to_cmd(x,y,mod)5571 click_to_cmd(x, y, mod)
5572 int x, y, mod;
5573 {
5574     int dir;
5575     static char cmd[4];
5576     cmd[1] = 0;
5577 
5578     if (iflags.clicklook && mod == CLICK_2) {
5579         clicklook_cc.x = x;
5580         clicklook_cc.y = y;
5581         cmd[0] = Cmd.spkeys[NHKF_CLICKLOOK];
5582         return cmd;
5583     }
5584 
5585     x -= u.ux;
5586     y -= u.uy;
5587 
5588     if (flags.travelcmd) {
5589         if (abs(x) <= 1 && abs(y) <= 1) {
5590             x = sgn(x), y = sgn(y);
5591         } else {
5592             u.tx = u.ux + x;
5593             u.ty = u.uy + y;
5594             cmd[0] = Cmd.spkeys[NHKF_TRAVEL];
5595             return cmd;
5596         }
5597 
5598         if (x == 0 && y == 0) {
5599             if (iflags.herecmd_menu) {
5600                 cmd[0] = here_cmd_menu(FALSE);
5601                 return cmd;
5602             }
5603 
5604             /* here */
5605             if (IS_FOUNTAIN(levl[u.ux][u.uy].typ)
5606                 || IS_SINK(levl[u.ux][u.uy].typ)) {
5607                 cmd[0] = cmd_from_func(mod == CLICK_1 ? dodrink : dodip);
5608                 return cmd;
5609             } else if (IS_THRONE(levl[u.ux][u.uy].typ)) {
5610                 cmd[0] = cmd_from_func(dosit);
5611                 return cmd;
5612             } else if ((u.ux == xupstair && u.uy == yupstair)
5613                        || (u.ux == sstairs.sx && u.uy == sstairs.sy
5614                            && sstairs.up)
5615                        || (u.ux == xupladder && u.uy == yupladder)) {
5616                 cmd[0] = cmd_from_func(doup);
5617                 return cmd;
5618             } else if ((u.ux == xdnstair && u.uy == ydnstair)
5619                        || (u.ux == sstairs.sx && u.uy == sstairs.sy
5620                            && !sstairs.up)
5621                        || (u.ux == xdnladder && u.uy == ydnladder)) {
5622                 cmd[0] = cmd_from_func(dodown);
5623                 return cmd;
5624             } else if (OBJ_AT(u.ux, u.uy)) {
5625                 cmd[0] = cmd_from_func(Is_container(level.objects[u.ux][u.uy])
5626                                        ? doloot : dopickup);
5627                 return cmd;
5628             } else {
5629                 cmd[0] = cmd_from_func(donull); /* just rest */
5630                 return cmd;
5631             }
5632         }
5633 
5634         /* directional commands */
5635 
5636         dir = xytod(x, y);
5637 
5638         if (!m_at(u.ux + x, u.uy + y)
5639             && !test_move(u.ux, u.uy, x, y, TEST_MOVE)) {
5640             cmd[1] = Cmd.dirchars[dir];
5641             cmd[2] = '\0';
5642             if (iflags.herecmd_menu) {
5643                 cmd[0] = there_cmd_menu(FALSE, u.ux + x, u.uy + y);
5644                 if (cmd[0] == '\0')
5645                     cmd[1] = '\0';
5646                 return cmd;
5647             }
5648 
5649             if (IS_DOOR(levl[u.ux + x][u.uy + y].typ)) {
5650                 /* slight assistance to the player: choose kick/open for them
5651                  */
5652                 if (levl[u.ux + x][u.uy + y].doormask & D_LOCKED) {
5653                     cmd[0] = cmd_from_func(dokick);
5654                     return cmd;
5655                 }
5656                 if (levl[u.ux + x][u.uy + y].doormask & D_CLOSED) {
5657                     cmd[0] = cmd_from_func(doopen);
5658                     return cmd;
5659                 }
5660             }
5661             if (levl[u.ux + x][u.uy + y].typ <= SCORR) {
5662                 cmd[0] = cmd_from_func(dosearch);
5663                 cmd[1] = 0;
5664                 return cmd;
5665             }
5666         }
5667     } else {
5668         /* convert without using floating point, allowing sloppy clicking */
5669         if (x > 2 * abs(y))
5670             x = 1, y = 0;
5671         else if (y > 2 * abs(x))
5672             x = 0, y = 1;
5673         else if (x < -2 * abs(y))
5674             x = -1, y = 0;
5675         else if (y < -2 * abs(x))
5676             x = 0, y = -1;
5677         else
5678             x = sgn(x), y = sgn(y);
5679 
5680         if (x == 0 && y == 0) {
5681             /* map click on player to "rest" command */
5682             cmd[0] = cmd_from_func(donull);
5683             return cmd;
5684         }
5685         dir = xytod(x, y);
5686     }
5687 
5688     /* move, attack, etc. */
5689     cmd[1] = 0;
5690     if (mod == CLICK_1) {
5691         cmd[0] = Cmd.dirchars[dir];
5692     } else {
5693         cmd[0] = (Cmd.num_pad
5694                      ? M(Cmd.dirchars[dir])
5695                      : (Cmd.dirchars[dir] - 'a' + 'A')); /* run command */
5696     }
5697 
5698     return cmd;
5699 }
5700 
5701 char
get_count(allowchars,inkey,maxcount,count,historical)5702 get_count(allowchars, inkey, maxcount, count, historical)
5703 char *allowchars;
5704 char inkey;
5705 long maxcount;
5706 long *count;
5707 boolean historical; /* whether to include in message history: True => yes */
5708 {
5709     char qbuf[QBUFSZ];
5710     int key;
5711     long cnt = 0L;
5712     boolean backspaced = FALSE;
5713     /* this should be done in port code so that we have erase_char
5714        and kill_char available; we can at least fake erase_char */
5715 #define STANDBY_erase_char '\177'
5716 
5717     for (;;) {
5718         if (inkey) {
5719             key = inkey;
5720             inkey = '\0';
5721         } else
5722             key = readchar();
5723 
5724         if (digit(key)) {
5725             cnt = 10L * cnt + (long) (key - '0');
5726             if (cnt < 0)
5727                 cnt = 0;
5728             else if (maxcount > 0 && cnt > maxcount)
5729                 cnt = maxcount;
5730         } else if (cnt && (key == '\b' || key == STANDBY_erase_char)) {
5731             cnt = cnt / 10;
5732             backspaced = TRUE;
5733         } else if (key == Cmd.spkeys[NHKF_ESC]) {
5734             break;
5735         } else if (!allowchars || index(allowchars, key)) {
5736             *count = cnt;
5737             break;
5738         }
5739 
5740         if (cnt > 9 || backspaced) {
5741             clear_nhwindow(WIN_MESSAGE);
5742             if (backspaced && !cnt) {
5743                 Sprintf(qbuf, "Count: ");
5744             } else {
5745                 Sprintf(qbuf, "Count: %ld", cnt);
5746                 backspaced = FALSE;
5747             }
5748             custompline(SUPPRESS_HISTORY, "%s", qbuf);
5749             mark_synch();
5750         }
5751     }
5752 
5753     if (historical) {
5754         Sprintf(qbuf, "Count: %ld ", *count);
5755         (void) key2txt((uchar) key, eos(qbuf));
5756         putmsghistory(qbuf, FALSE);
5757     }
5758 
5759     return key;
5760 }
5761 
5762 
5763 STATIC_OVL char *
parse()5764 parse()
5765 {
5766 #ifdef LINT /* static char in_line[COLNO]; */
5767     char in_line[COLNO];
5768 #else
5769     static char in_line[COLNO];
5770 #endif
5771     register int foo;
5772 
5773     iflags.in_parse = TRUE;
5774     multi = 0;
5775     context.move = 1;
5776     flush_screen(1); /* Flush screen buffer. Put the cursor on the hero. */
5777 
5778 #ifdef ALTMETA
5779     alt_esc = iflags.altmeta; /* readchar() hack */
5780 #endif
5781     if (!Cmd.num_pad || (foo = readchar()) == Cmd.spkeys[NHKF_COUNT]) {
5782         long tmpmulti = multi;
5783 
5784         foo = get_count((char *) 0, '\0', LARGEST_INT, &tmpmulti, FALSE);
5785         last_multi = multi = tmpmulti;
5786     }
5787 #ifdef ALTMETA
5788     alt_esc = FALSE; /* readchar() reset */
5789 #endif
5790 
5791     if (iflags.debug_fuzzer /* if fuzzing, override '!' and ^Z */
5792         && (Cmd.commands[foo & 0x0ff]
5793             && (Cmd.commands[foo & 0x0ff]->ef_funct == dosuspend_core
5794                 || Cmd.commands[foo & 0x0ff]->ef_funct == dosh_core)))
5795         foo = Cmd.spkeys[NHKF_ESC];
5796 
5797     if (foo == Cmd.spkeys[NHKF_ESC]) { /* esc cancels count (TH) */
5798         clear_nhwindow(WIN_MESSAGE);
5799         multi = last_multi = 0;
5800     } else if (foo == Cmd.spkeys[NHKF_DOAGAIN] || in_doagain) {
5801         multi = last_multi;
5802     } else {
5803         last_multi = multi;
5804         savech(0); /* reset input queue */
5805         savech((char) foo);
5806     }
5807 
5808     if (multi) {
5809         multi--;
5810         save_cm = in_line;
5811     } else {
5812         save_cm = (char *) 0;
5813     }
5814     /* in 3.4.3 this was in rhack(), where it was too late to handle M-5 */
5815     if (Cmd.pcHack_compat) {
5816         /* This handles very old inconsistent DOS/Windows behaviour
5817            in a different way: earlier, the keyboard handler mapped
5818            these, which caused counts to be strange when entered
5819            from the number pad. Now do not map them until here. */
5820         switch (foo) {
5821         case '5':
5822             foo = Cmd.spkeys[NHKF_RUSH];
5823             break;
5824         case M('5'):
5825             foo = Cmd.spkeys[NHKF_RUN];
5826             break;
5827         case M('0'):
5828             foo = Cmd.spkeys[NHKF_DOINV];
5829             break;
5830         default:
5831             break; /* as is */
5832         }
5833     }
5834 
5835     in_line[0] = foo;
5836     in_line[1] = '\0';
5837     if (prefix_cmd(foo)) {
5838         foo = readchar();
5839         savech((char) foo);
5840         in_line[1] = foo;
5841         in_line[2] = 0;
5842     }
5843     clear_nhwindow(WIN_MESSAGE);
5844 
5845     iflags.in_parse = FALSE;
5846     return in_line;
5847 }
5848 
5849 #ifdef HANGUPHANDLING
5850 /* some very old systems, or descendents of such systems, expect signal
5851    handlers to have return type `int', but they don't actually inspect
5852    the return value so we should be safe using `void' unconditionally */
5853 /*ARGUSED*/
5854 void
hangup(sig_unused)5855 hangup(sig_unused) /* called as signal() handler, so sent at least one arg */
5856 int sig_unused UNUSED;
5857 {
5858     if (program_state.exiting)
5859         program_state.in_moveloop = 0;
5860     nhwindows_hangup();
5861 #ifdef SAFERHANGUP
5862     /* When using SAFERHANGUP, the done_hup flag it tested in rhack
5863        and a couple of other places; actual hangup handling occurs then.
5864        This is 'safer' because it disallows certain cheats and also
5865        protects against losing objects in the process of being thrown,
5866        but also potentially riskier because the disconnected program
5867        must continue running longer before attempting a hangup save. */
5868     program_state.done_hup++;
5869     /* defer hangup iff game appears to be in progress */
5870     if (program_state.in_moveloop && program_state.something_worth_saving)
5871         return;
5872 #endif /* SAFERHANGUP */
5873     end_of_input();
5874 }
5875 
5876 void
end_of_input()5877 end_of_input()
5878 {
5879 #ifdef NOSAVEONHANGUP
5880 #ifdef INSURANCE
5881     if (flags.ins_chkpt && program_state.something_worth_saving)
5882         program_state.preserve_locks = 1; /* keep files for recovery */
5883 #endif
5884     program_state.something_worth_saving = 0; /* don't save */
5885 #endif
5886 
5887 #ifndef SAFERHANGUP
5888     if (!program_state.done_hup++)
5889 #endif
5890         if (program_state.something_worth_saving)
5891             (void) dosave0();
5892     if (iflags.window_inited)
5893         exit_nhwindows((char *) 0);
5894     clearlocks();
5895     nh_terminate(EXIT_SUCCESS);
5896     /*NOTREACHED*/ /* not necessarily true for vms... */
5897     return;
5898 }
5899 #endif /* HANGUPHANDLING */
5900 
5901 char
readchar()5902 readchar()
5903 {
5904     register int sym;
5905     int x = u.ux, y = u.uy, mod = 0;
5906 
5907     if (iflags.debug_fuzzer)
5908         return randomkey();
5909     if (*readchar_queue)
5910         sym = *readchar_queue++;
5911     else
5912         sym = in_doagain ? pgetchar() : nh_poskey(&x, &y, &mod);
5913 
5914 #ifdef NR_OF_EOFS
5915     if (sym == EOF) {
5916         register int cnt = NR_OF_EOFS;
5917         /*
5918          * Some SYSV systems seem to return EOFs for various reasons
5919          * (?like when one hits break or for interrupted systemcalls?),
5920          * and we must see several before we quit.
5921          */
5922         do {
5923             clearerr(stdin); /* omit if clearerr is undefined */
5924             sym = pgetchar();
5925         } while (--cnt && sym == EOF);
5926     }
5927 #endif /* NR_OF_EOFS */
5928 
5929     if (sym == EOF) {
5930 #ifdef HANGUPHANDLING
5931         hangup(0); /* call end_of_input() or set program_state.done_hup */
5932 #endif
5933         sym = '\033';
5934 #ifdef ALTMETA
5935     } else if (sym == '\033' && alt_esc) {
5936         /* iflags.altmeta: treat two character ``ESC c'' as single `M-c' */
5937         sym = *readchar_queue ? *readchar_queue++ : pgetchar();
5938         if (sym == EOF || sym == 0)
5939             sym = '\033';
5940         else if (sym != '\033')
5941             sym |= 0200; /* force 8th bit on */
5942 #endif /*ALTMETA*/
5943     } else if (sym == 0) {
5944         /* click event */
5945         readchar_queue = click_to_cmd(x, y, mod);
5946         sym = *readchar_queue++;
5947     }
5948     return (char) sym;
5949 }
5950 
5951 /* '_' command, #travel, via keyboard rather than mouse click */
5952 STATIC_PTR int
dotravel(VOID_ARGS)5953 dotravel(VOID_ARGS)
5954 {
5955     static char cmd[2];
5956     coord cc;
5957 
5958     /* [FIXME?  Supporting the ability to disable traveling via mouse
5959        click makes some sense, depending upon overall mouse usage.
5960        Disabling '_' on a user by user basis makes no sense at all since
5961        even if it is typed by accident, aborting when picking a target
5962        destination is trivial.  Travel via mouse predates travel via '_',
5963        and this use of OPTION=!travel is probably just a mistake....] */
5964     if (!flags.travelcmd)
5965         return 0;
5966 
5967     cmd[1] = 0;
5968     cc.x = iflags.travelcc.x;
5969     cc.y = iflags.travelcc.y;
5970     if (cc.x == 0 && cc.y == 0) {
5971         /* No cached destination, start attempt from current position */
5972         cc.x = u.ux;
5973         cc.y = u.uy;
5974     }
5975     iflags.getloc_travelmode = TRUE;
5976     if (iflags.menu_requested) {
5977         int gf = iflags.getloc_filter;
5978         iflags.getloc_filter = GFILTER_VIEW;
5979         if (!getpos_menu(&cc, GLOC_INTERESTING)) {
5980             iflags.getloc_filter = gf;
5981             iflags.getloc_travelmode = FALSE;
5982             return 0;
5983         }
5984         iflags.getloc_filter = gf;
5985     } else {
5986         pline("Where do you want to travel to?");
5987         if (getpos(&cc, TRUE, "the desired destination") < 0) {
5988             /* user pressed ESC */
5989             iflags.getloc_travelmode = FALSE;
5990             return 0;
5991         }
5992     }
5993     iflags.getloc_travelmode = FALSE;
5994     iflags.travelcc.x = u.tx = cc.x;
5995     iflags.travelcc.y = u.ty = cc.y;
5996     cmd[0] = Cmd.spkeys[NHKF_TRAVEL];
5997     readchar_queue = cmd;
5998     return 0;
5999 }
6000 
6001 /*
6002  *   Parameter validator for generic yes/no function to prevent
6003  *   the core from sending too long a prompt string to the
6004  *   window port causing a buffer overflow there.
6005  */
6006 char
yn_function(query,resp,def)6007 yn_function(query, resp, def)
6008 const char *query, *resp;
6009 char def;
6010 {
6011     char res, qbuf[QBUFSZ];
6012 #ifdef DUMPLOG
6013     extern unsigned saved_pline_index; /* pline.c */
6014     unsigned idx = saved_pline_index;
6015     /* buffer to hold query+space+formatted_single_char_response */
6016     char dumplog_buf[QBUFSZ + 1 + 15]; /* [QBUFSZ+1+7] should suffice */
6017 #endif
6018 
6019     iflags.last_msg = PLNMSG_UNKNOWN; /* most recent pline is clobbered */
6020 
6021     /* maximum acceptable length is QBUFSZ-1 */
6022     if (strlen(query) >= QBUFSZ) {
6023         /* caller shouldn't have passed anything this long */
6024         paniclog("Query truncated: ", query);
6025         (void) strncpy(qbuf, query, QBUFSZ - 1 - 3);
6026         Strcpy(&qbuf[QBUFSZ - 1 - 3], "...");
6027         query = qbuf;
6028     }
6029     res = (*windowprocs.win_yn_function)(query, resp, def);
6030 #ifdef DUMPLOG
6031     if (idx == saved_pline_index) {
6032         /* when idx is still the same as saved_pline_index, the interface
6033            didn't put the prompt into saved_plines[]; we put a simplified
6034            version in there now (without response choices or default) */
6035         Sprintf(dumplog_buf, "%s ", query);
6036         (void) key2txt((uchar) res, eos(dumplog_buf));
6037         dumplogmsg(dumplog_buf);
6038     }
6039 #endif
6040     return res;
6041 }
6042 
6043 /* for paranoid_confirm:quit,die,attack prompting */
6044 boolean
paranoid_query(be_paranoid,prompt)6045 paranoid_query(be_paranoid, prompt)
6046 boolean be_paranoid;
6047 const char *prompt;
6048 {
6049     boolean confirmed_ok;
6050 
6051     /* when paranoid, player must respond with "yes" rather than just 'y'
6052        to give the go-ahead for this query; default is "no" unless the
6053        ParanoidConfirm flag is set in which case there's no default */
6054     if (be_paranoid) {
6055         char pbuf[BUFSZ], qbuf[QBUFSZ], ans[BUFSZ];
6056         const char *promptprefix = "",
6057                 *responsetype = ParanoidConfirm ? "(yes|no)" : "(yes) [no]";
6058         int k, trylimit = 6; /* 1 normal, 5 more with "Yes or No:" prefix */
6059 
6060         copynchars(pbuf, prompt, BUFSZ - 1);
6061         /* in addition to being paranoid about this particular
6062            query, we might be even more paranoid about all paranoia
6063            responses (ie, ParanoidConfirm is set) in which case we
6064            require "no" to reject in addition to "yes" to confirm
6065            (except we won't loop if response is ESC; it means no) */
6066         do {
6067             /* make sure we won't overflow a QBUFSZ sized buffer */
6068             k = (int) (strlen(promptprefix) + 1 + strlen(responsetype));
6069             if ((int) strlen(pbuf) + k > QBUFSZ - 1) {
6070                 /* chop off some at the end */
6071                 Strcpy(pbuf + (QBUFSZ - 1) - k - 4, "...?"); /* -4: "...?" */
6072             }
6073 
6074             Sprintf(qbuf, "%s%s %s", promptprefix, pbuf, responsetype);
6075             *ans = '\0';
6076             getlin(qbuf, ans);
6077             (void) mungspaces(ans);
6078             confirmed_ok = !strcmpi(ans, "yes");
6079             if (confirmed_ok || *ans == '\033')
6080                 break;
6081             promptprefix = "\"Yes\" or \"No\": ";
6082         } while (ParanoidConfirm && strcmpi(ans, "no") && --trylimit);
6083     } else
6084         confirmed_ok = (yn(prompt) == 'y');
6085 
6086     return confirmed_ok;
6087 }
6088 
6089 /* ^Z command, #suspend */
6090 STATIC_PTR int
dosuspend_core(VOID_ARGS)6091 dosuspend_core(VOID_ARGS)
6092 {
6093 #ifdef SUSPEND
6094     /* Does current window system support suspend? */
6095     if ((*windowprocs.win_can_suspend)()) {
6096         /* NB: SYSCF SHELLERS handled in port code. */
6097         dosuspend();
6098     } else
6099 #endif
6100         Norep(cmdnotavail, "#suspend");
6101     return 0;
6102 }
6103 
6104 /* '!' command, #shell */
6105 STATIC_PTR int
dosh_core(VOID_ARGS)6106 dosh_core(VOID_ARGS)
6107 {
6108 #ifdef SHELL
6109     /* access restrictions, if any, are handled in port code */
6110     dosh();
6111 #else
6112     Norep(cmdnotavail, "#shell");
6113 #endif
6114     return 0;
6115 }
6116 
6117 /*cmd.c*/
6118