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