1 /* SCCS Id: @(#)pager.c 3.4 2003/08/13 */
2 /* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */
3 /* NetHack may be freely redistributed. See license for details. */
5 /* This file contains the command routines dowhatis() and dohelp() and */
6 /* a few other help related facilities */
11 STATIC_DCL boolean FDECL(is_swallow_sym, (int));
12 STATIC_DCL int FDECL(append_str, (char *, const char *));
13 STATIC_DCL struct permonst * FDECL(lookat, (int, int, char *, char *));
14 STATIC_DCL void FDECL(checkfile,
15 (char *,struct permonst *,BOOLEAN_P,BOOLEAN_P));
16 STATIC_DCL int FDECL(do_look, (BOOLEAN_P));
17 STATIC_DCL boolean FDECL(help_menu, (int *));
19 extern void NDECL(port_help);
22 /* Returns "true" for characters that could represent a monster's stomach. */
28 for (i = S_sw_tl; i <= S_sw_br; i++)
29 if ((int)showsyms[i] == c) return TRUE;
34 * Append new_str to the end of buf if new_str doesn't already exist as
35 * a substring of buf. Return 1 if the string was appended, 0 otherwise.
36 * It is expected that buf is of size BUFSZ.
39 append_str(buf, new_str)
43 int space_left; /* space remaining in buf */
45 if (strstri(buf, new_str)) return 0;
47 space_left = BUFSZ - strlen(buf) - 1;
48 (void) strncat(buf, " or ", space_left);
49 (void) strncat(buf, new_str, space_left - 4);
54 * Return the name of the glyph found at (x,y).
55 * If not hallucinating and the glyph is a monster, also monster data.
57 STATIC_OVL struct permonst *
58 lookat(x, y, buf, monbuf)
62 register struct monst *mtmp = (struct monst *) 0;
63 struct permonst *pm = (struct permonst *) 0;
66 buf[0] = monbuf[0] = 0;
67 glyph = glyph_at(x,y);
68 if (u.ux == x && u.uy == y && senseself()) {
71 /* if not polymorphed, show both the role and the race */
74 Sprintf(race, "%s ", urace.adj);
77 Sprintf(buf, "%s%s%s called %s",
78 Invis ? "invisible " : "",
80 mons[u.umonnum].mname,
82 /* file lookup can't distinguish between "gnomish wizard" monster
83 and correspondingly named player character, always picking the
84 former; force it to find the general "wizard" entry instead */
85 if (Role_if(PM_WIZARD) && Race_if(PM_GNOME) && !Upolyd)
86 pm = &mons[PM_WIZARD];
92 Sprintf(steedbuf, ", mounted on %s", y_monnam(u.usteed));
93 /* assert((sizeof buf >= strlen(buf)+strlen(steedbuf)+1); */
94 Strcat(buf, steedbuf);
97 /* When you see yourself normally, no explanation is appended
98 (even if you could also see yourself via other means).
99 Sensing self while blind or swallowed is treated as if it
100 were by normal vision (cf canseeself()). */
101 if ((Invisible || u.uundetected) && !Blind && !u.uswallow) {
104 if (Infravision) how |= 1;
105 if (Unblind_telepat) how |= 2;
106 if (Detect_monsters) how |= 4;
109 Sprintf(eos(buf), " [seen: %s%s%s%s%s]",
110 (how & 1) ? "infravision" : "",
111 /* add comma if telep and infrav */
112 ((how & 3) > 2) ? ", " : "",
113 (how & 2) ? "telepathy" : "",
114 /* add comma if detect and (infrav or telep or both) */
115 ((how & 7) > 4) ? ", " : "",
116 (how & 4) ? "monster detection" : "");
118 } else if (u.uswallow) {
119 /* all locations when swallowed other than the hero are the monster */
120 Sprintf(buf, "interior of %s",
121 Blind ? "a monster" : a_monnam(u.ustuck));
123 } else if (glyph_is_monster(glyph)) {
127 if (mtmp != (struct monst *) 0) {
128 char *name, monnambuf[BUFSZ];
129 boolean accurate = !Hallucination;
131 if (mtmp->data == &mons[PM_COYOTE] && accurate)
132 name = coyotename(mtmp, monnambuf);
134 name = distant_monnam(mtmp, ARTICLE_NONE, monnambuf);
137 Sprintf(buf, "%s%s%s",
138 (mtmp->mx != x || mtmp->my != y) ?
139 ((mtmp->isshk && accurate)
140 ? "tail of " : "tail of a ") : "",
141 (mtmp->mtame && accurate) ? "tame " :
142 (mtmp->mpeaceful && accurate) ? "peaceful " : "",
144 if (u.ustuck == mtmp)
145 Strcat(buf, (Upolyd && sticks(youmonst.data)) ?
146 ", being held" : ", holding you");
148 Strcat(buf, ", leashed to you");
150 if (mtmp->mtrapped && cansee(mtmp->mx, mtmp->my)) {
151 struct trap *t = t_at(mtmp->mx, mtmp->my);
152 int tt = t ? t->ttyp : NO_TRAP;
154 /* newsym lets you know of the trap, so mention it here */
155 if (tt == BEAR_TRAP || tt == PIT ||
156 tt == SPIKED_PIT || tt == WEB)
157 Sprintf(eos(buf), ", trapped in %s",
158 an(defsyms[trap_to_defsym(tt)].explanation));
162 int ways_seen = 0, normal = 0, xraydist;
163 boolean useemon = (boolean) canseemon(mtmp);
165 xraydist = (u.xray_range<0) ? -1 : u.xray_range * u.xray_range;
167 if ((mtmp->wormno ? worm_known(mtmp) : cansee(mtmp->mx, mtmp->my)) &&
168 mon_visible(mtmp) && !mtmp->minvis) {
173 if (useemon && mtmp->minvis)
176 if ((!mtmp->minvis || See_invisible) && see_with_infrared(mtmp))
179 if (tp_sensemon(mtmp))
182 if (useemon && xraydist > 0 &&
183 distu(mtmp->mx, mtmp->my) <= xraydist)
187 if (MATCH_WARN_OF_MON(mtmp))
190 if (ways_seen > 1 || !normal) {
192 Strcat(monbuf, "normal vision");
193 /* can't actually be 1 yet here */
194 if (ways_seen-- > 1) Strcat(monbuf, ", ");
196 if (useemon && mtmp->minvis) {
197 Strcat(monbuf, "see invisible");
198 if (ways_seen-- > 1) Strcat(monbuf, ", ");
200 if ((!mtmp->minvis || See_invisible) &&
201 see_with_infrared(mtmp)) {
202 Strcat(monbuf, "infravision");
203 if (ways_seen-- > 1) Strcat(monbuf, ", ");
205 if (tp_sensemon(mtmp)) {
206 Strcat(monbuf, "telepathy");
207 if (ways_seen-- > 1) Strcat(monbuf, ", ");
209 if (useemon && xraydist > 0 &&
210 distu(mtmp->mx, mtmp->my) <= xraydist) {
211 /* Eyes of the Overworld */
212 Strcat(monbuf, "astral vision");
213 if (ways_seen-- > 1) Strcat(monbuf, ", ");
215 if (Detect_monsters) {
216 Strcat(monbuf, "monster detection");
217 if (ways_seen-- > 1) Strcat(monbuf, ", ");
219 if (MATCH_WARN_OF_MON(mtmp)) {
222 Strcat(monbuf, "paranoid delusion");
224 Sprintf(wbuf, "warned of %s",
225 makeplural(mtmp->data->mname));
226 Strcat(monbuf, wbuf);
228 if (ways_seen-- > 1) Strcat(monbuf, ", ");
234 else if (glyph_is_object(glyph)) {
235 struct obj *otmp = vobj_at(x,y);
237 if (!otmp || otmp->otyp != glyph_to_obj(glyph)) {
238 if (glyph_to_obj(glyph) != STRANGE_OBJECT) {
239 otmp = mksobj(glyph_to_obj(glyph), FALSE, FALSE);
240 if (otmp->oclass == COIN_CLASS)
241 otmp->quan = 2L; /* to force pluralization */
242 else if (otmp->otyp == SLIME_MOLD)
243 otmp->spe = current_fruit; /* give the fruit a type */
244 Strcpy(buf, distant_name(otmp, xname));
248 Strcpy(buf, distant_name(otmp, xname));
250 if (levl[x][y].typ == STONE || levl[x][y].typ == SCORR)
251 Strcat(buf, " embedded in stone");
252 else if (IS_WALL(levl[x][y].typ) || levl[x][y].typ == SDOOR)
253 Strcat(buf, " embedded in a wall");
254 else if (closed_door(x,y))
255 Strcat(buf, " embedded in a door");
256 else if (is_pool(x,y))
257 Strcat(buf, " in water");
258 else if (is_lava(x,y))
259 Strcat(buf, " in molten lava"); /* [can this ever happen?] */
260 } else if (glyph_is_trap(glyph)) {
261 int tnum = what_trap(glyph_to_trap(glyph));
262 Strcpy(buf, defsyms[trap_to_defsym(tnum)].explanation);
263 } else if(!glyph_is_cmap(glyph)) {
264 Strcpy(buf,"dark part of a room");
265 } else switch(glyph_to_cmap(glyph)) {
267 if(!In_endgame(&u.uz))
268 Sprintf(buf, "%s altar",
269 align_str(Amask2align(levl[x][y].altarmask & ~AM_SHRINE)));
270 else Sprintf(buf, "aligned altar");
273 if (is_drawbridge_wall(x, y) >= 0)
274 Strcpy(buf,"open drawbridge portcullis");
275 else if ((levl[x][y].doormask & ~D_TRAPPED) == D_BROKEN)
276 Strcpy(buf,"broken door");
278 Strcpy(buf,"doorway");
281 Strcpy(buf, Is_airlevel(&u.uz) ? "cloudy area" : "fog/vapor cloud");
284 Strcpy(buf,defsyms[glyph_to_cmap(glyph)].explanation);
288 return ((pm && !Hallucination) ? pm : (struct permonst *) 0);
292 * Look in the "data" file for more info. Called if the user typed in the
293 * whole name (user_typed_name == TRUE), or we've found a possible match
294 * with a character/glyph and flags.help is TRUE.
296 * NOTE: when (user_typed_name == FALSE), inp is considered read-only and
297 * must not be changed directly, e.g. via lcase(). We want to force
298 * lcase() for data.base lookup so that we can have a clean key.
299 * Therefore, we create a copy of inp _just_ for data.base lookup.
302 checkfile(inp, pm, user_typed_name, without_asking)
305 boolean user_typed_name, without_asking;
308 char buf[BUFSZ], newstr[BUFSZ];
309 char *ep, *dbase_str;
312 boolean found_in_file = FALSE, skipping_entry = FALSE;
314 fp = dlb_fopen(DATAFILE, "r");
316 pline("Cannot open data file!");
320 /* To prevent the need for entries in data.base like *ngel to account
321 * for Angel and angel, make the lookup string the same for both
322 * user_typed_name and picked name.
324 if (pm != (struct permonst *) 0 && !user_typed_name)
325 dbase_str = strcpy(newstr, pm->mname);
326 else dbase_str = strcpy(newstr, inp);
327 (void) lcase(dbase_str);
329 if (!strncmp(dbase_str, "interior of ", 12))
331 if (!strncmp(dbase_str, "a ", 2))
333 else if (!strncmp(dbase_str, "an ", 3))
335 else if (!strncmp(dbase_str, "the ", 4))
337 if (!strncmp(dbase_str, "tame ", 5))
339 else if (!strncmp(dbase_str, "peaceful ", 9))
341 if (!strncmp(dbase_str, "invisible ", 10))
343 if (!strncmp(dbase_str, "statue of ", 10))
345 else if (!strncmp(dbase_str, "figurine of ", 12))
348 /* Make sure the name is non-empty. */
350 /* adjust the input to remove "named " and convert to lower case */
351 char *alt = 0; /* alternate description */
353 if ((ep = strstri(dbase_str, " named ")) != 0)
356 ep = strstri(dbase_str, " called ");
357 if (!ep) ep = strstri(dbase_str, ", ");
358 if (ep && ep > dbase_str) *ep = '\0';
361 * If the object is named, then the name is the alternate description;
362 * otherwise, the result of makesingular() applied to the name is. This
363 * isn't strictly optimal, but named objects of interest to the user
364 * will usually be found under their name, rather than under their
365 * object type, so looking for a singular form is pointless.
369 alt = makesingular(dbase_str);
374 /* skip first record; read second */
376 if (!dlb_fgets(buf, BUFSZ, fp) || !dlb_fgets(buf, BUFSZ, fp)) {
377 impossible("can't read 'data' file");
378 (void) dlb_fclose(fp);
380 } else if (sscanf(buf, "%8lx\n", &txt_offset) < 1 || txt_offset <= 0)
383 /* look for the appropriate entry */
384 while (dlb_fgets(buf,BUFSZ,fp)) {
385 if (*buf == '.') break; /* we passed last entry without success */
388 /* a number indicates the end of current entry */
389 skipping_entry = FALSE;
390 } else if (!skipping_entry) {
391 if (!(ep = index(buf, '\n'))) goto bad_data_file;
393 /* if we match a key that begins with "~", skip this entry */
394 chk_skip = (*buf == '~') ? 1 : 0;
395 if (pmatch(&buf[chk_skip], dbase_str) ||
396 (alt && pmatch(&buf[chk_skip], alt))) {
398 skipping_entry = TRUE;
401 found_in_file = TRUE;
414 /* skip over other possible matches for the info */
416 if (!dlb_fgets(buf, BUFSZ, fp)) goto bad_data_file;
417 } while (!digit(*buf));
418 if (sscanf(buf, "%ld,%d\n", &entry_offset, &entry_count) < 2) {
419 bad_data_file: impossible("'data' file in wrong format");
420 (void) dlb_fclose(fp);
424 if (user_typed_name || without_asking || yn("More info?") == 'y') {
427 if (dlb_fseek(fp, txt_offset + entry_offset, SEEK_SET) < 0) {
428 pline("? Seek error on 'data' file!");
429 (void) dlb_fclose(fp);
432 datawin = create_nhwindow(NHW_MENU);
433 for (i = 0; i < entry_count; i++) {
434 if (!dlb_fgets(buf, BUFSZ, fp)) goto bad_data_file;
435 if ((ep = index(buf, '\n')) != 0) *ep = 0;
436 if (index(buf+1, '\t') != 0) (void) tabexpand(buf+1);
437 putstr(datawin, 0, buf+1);
439 display_nhwindow(datawin, FALSE);
440 destroy_nhwindow(datawin);
442 } else if (user_typed_name)
443 pline("I don't have any information on those things.");
445 (void) dlb_fclose(fp);
448 /* getpos() return values */
449 #define LOOK_TRADITIONAL 0 /* '.' -- ask about "more info?" */
450 #define LOOK_QUICK 1 /* ',' -- skip "more info?" */
451 #define LOOK_ONCE 2 /* ';' -- skip and stop looping */
452 #define LOOK_VERBOSE 3 /* ':' -- show more info w/o asking */
454 /* also used by getpos hack in do_name.c */
455 const char what_is_an_unknown_object[] = "an unknown object";
459 boolean quick; /* use cursor && don't search for "more info" */
461 char out_str[BUFSZ], look_buf[BUFSZ];
462 const char *x_str, *firstmatch = 0;
463 struct permonst *pm = 0;
465 int sym; /* typed symbol or converted glyph */
466 int found; /* count of matching syms found */
467 coord cc; /* screen pos of unknown glyph */
468 boolean save_verbose; /* saved value of flags.verbose */
469 boolean from_screen; /* question from the screen */
470 boolean need_to_look; /* need to get explan. from glyph */
471 boolean hit_trap; /* true if found trap explanation */
472 int skipped_venom; /* non-zero if we ignored "splash of venom" */
473 static const char *mon_interior = "the interior of a monster";
476 from_screen = TRUE; /* yes, we want to use the cursor */
478 i = ynq("Specify unknown object by cursor?");
479 if (i == 'q') return 0;
480 from_screen = (i == 'y');
486 sym = 0; /* gcc -Wall lint */
488 getlin("Specify what? (type the word)", out_str);
489 if (out_str[0] == '\0' || out_str[0] == '\033')
492 if (out_str[1]) { /* user typed in a complete string */
493 checkfile(out_str, pm, TRUE, TRUE);
499 /* Save the verbose flag, we change it later. */
500 save_verbose = flags.verbose;
501 flags.verbose = flags.verbose && !quick;
503 * The user typed one letter, or we're identifying from the screen.
506 /* Reset some variables. */
507 need_to_look = FALSE;
508 pm = (struct permonst *)0;
514 int glyph; /* glyph at selected position */
517 pline("Please move the cursor to %s.",
518 what_is_an_unknown_object);
520 pline("Pick an object.");
522 ans = getpos(&cc, quick, what_is_an_unknown_object);
523 if (ans < 0 || cc.x < 0) {
524 flags.verbose = save_verbose;
527 flags.verbose = FALSE; /* only print long question once */
529 /* Convert the glyph at the selected position to a symbol. */
530 glyph = glyph_at(cc.x,cc.y);
531 if (glyph_is_cmap(glyph)) {
532 sym = showsyms[glyph_to_cmap(glyph)];
533 } else if (glyph_is_trap(glyph)) {
534 sym = showsyms[trap_to_defsym(glyph_to_trap(glyph))];
535 } else if (glyph_is_object(glyph)) {
536 sym = oc_syms[(int)objects[glyph_to_obj(glyph)].oc_class];
537 if (sym == '`' && iflags.bouldersym && (int)glyph_to_obj(glyph) == BOULDER)
538 sym = iflags.bouldersym;
539 } else if (glyph_is_monster(glyph)) {
540 /* takes care of pets, detected, ridden, and regular mons */
541 sym = monsyms[(int)mons[glyph_to_mon(glyph)].mlet];
542 } else if (glyph_is_swallow(glyph)) {
543 sym = showsyms[glyph_to_swallow(glyph)+S_sw_tl];
544 } else if (glyph_is_invisible(glyph)) {
546 } else if (glyph_is_warning(glyph)) {
547 sym = glyph_to_warning(glyph);
550 impossible("do_look: bad glyph %d at (%d,%d)",
551 glyph, (int)cc.x, (int)cc.y);
557 * Check all the possibilities, saving all explanations in a buffer.
558 * When all have been checked then the string is printed.
561 /* Check for monsters */
562 for (i = 0; i < MAXMCLASSES; i++) {
563 if (sym == (from_screen ? monsyms[i] : def_monsyms[i]) &&
567 Sprintf(out_str, "%c %s", sym, an(monexplain[i]));
568 firstmatch = monexplain[i];
571 found += append_str(out_str, an(monexplain[i]));
575 /* handle '@' as a special case if it refers to you and you're
576 playing a character which isn't normally displayed by that
577 symbol; firstmatch is assumed to already be set for '@' */
579 (sym == monsyms[S_HUMAN] && cc.x == u.ux && cc.y == u.uy) :
580 (sym == def_monsyms[S_HUMAN] && !iflags.showrace)) &&
581 !(Race_if(PM_HUMAN) || Race_if(PM_ELF)) && !Upolyd)
582 found += append_str(out_str, "you"); /* tack on "or you" */
585 * Special case: if identifying from the screen, and we're swallowed,
586 * and looking at something other than our own symbol, then just say
587 * "the interior of a monster".
589 if (u.uswallow && from_screen && is_swallow_sym(sym)) {
591 Sprintf(out_str, "%c %s", sym, mon_interior);
592 firstmatch = mon_interior;
594 found += append_str(out_str, mon_interior);
599 /* Now check for objects */
600 for (i = 1; i < MAXOCLASSES; i++) {
601 if (sym == (from_screen ? oc_syms[i] : def_oc_syms[i])) {
603 if (from_screen && i == VENOM_CLASS) {
608 Sprintf(out_str, "%c %s", sym, an(objexplain[i]));
609 firstmatch = objexplain[i];
612 found += append_str(out_str, an(objexplain[i]));
617 if (sym == DEF_INVISIBLE) {
619 Sprintf(out_str, "%c %s", sym, an(invisexplain));
620 firstmatch = invisexplain;
623 found += append_str(out_str, an(invisexplain));
627 #define is_cmap_trap(i) ((i) >= S_arrow_trap && (i) <= S_polymorph_trap)
628 #define is_cmap_drawbridge(i) ((i) >= S_vodbridge && (i) <= S_hcdbridge)
630 /* Now check for graphics symbols */
631 for (hit_trap = FALSE, i = 0; i < MAXPCHARS; i++) {
632 x_str = defsyms[i].explanation;
633 if (sym == (from_screen ? showsyms[i] : defsyms[i].sym) && *x_str) {
634 /* avoid "an air", "a water", or "a floor of a room" */
635 int article = (i == S_room) ? 2 : /* 2=>"the" */
636 !(strcmp(x_str, "air") == 0 || /* 1=>"an" */
637 strcmp(x_str, "water") == 0); /* 0=>(none)*/
640 if (is_cmap_trap(i)) {
641 Sprintf(out_str, "%c a trap", sym);
644 Sprintf(out_str, "%c %s", sym,
645 article == 2 ? the(x_str) :
646 article == 1 ? an(x_str) : x_str);
650 } else if (!u.uswallow && !(hit_trap && is_cmap_trap(i)) &&
651 !(found >= 3 && is_cmap_drawbridge(i))) {
652 found += append_str(out_str,
653 article == 2 ? the(x_str) :
654 article == 1 ? an(x_str) : x_str);
655 if (is_cmap_trap(i)) hit_trap = TRUE;
658 if (i == S_altar || is_cmap_trap(i))
663 /* Now check for warning symbols */
664 for (i = 1; i < WARNCOUNT; i++) {
665 x_str = def_warnsyms[i].explanation;
666 if (sym == (from_screen ? warnsyms[i] : def_warnsyms[i].sym)) {
668 Sprintf(out_str, "%c %s",
669 sym, def_warnsyms[i].explanation);
670 firstmatch = def_warnsyms[i].explanation;
673 found += append_str(out_str, def_warnsyms[i].explanation);
675 /* Kludge: warning trumps boulders on the display.
676 Reveal the boulder too or player can get confused */
677 if (from_screen && sobj_at(BOULDER, cc.x, cc.y))
678 Strcat(out_str, " co-located with a boulder");
679 break; /* out of for loop*/
683 /* if we ignored venom and list turned out to be short, put it back */
684 if (skipped_venom && found < 2) {
685 x_str = objexplain[VENOM_CLASS];
687 Sprintf(out_str, "%c %s", sym, an(x_str));
691 found += append_str(out_str, an(x_str));
695 /* handle optional boulder symbol as a special case */
696 if (iflags.bouldersym && sym == iflags.bouldersym) {
698 firstmatch = "boulder";
699 Sprintf(out_str, "%c %s", sym, an(firstmatch));
702 found += append_str(out_str, "boulder");
707 * If we are looking at the screen, follow multiple possibilities or
708 * an ambiguous explanation by something more detailed.
711 if (found > 1 || need_to_look) {
713 char temp_buf[BUFSZ];
715 pm = lookat(cc.x, cc.y, look_buf, monbuf);
716 firstmatch = look_buf;
718 Sprintf(temp_buf, " (%s)", firstmatch);
719 (void)strncat(out_str, temp_buf, BUFSZ-strlen(out_str)-1);
720 found = 1; /* we have something to look up */
723 Sprintf(temp_buf, " [seen: %s]", monbuf);
724 (void)strncat(out_str, temp_buf, BUFSZ-strlen(out_str)-1);
729 /* Finally, print out our explanation. */
731 pline("%s", out_str);
732 /* check the data file for information about this thing */
733 if (found == 1 && ans != LOOK_QUICK && ans != LOOK_ONCE &&
734 (ans == LOOK_VERBOSE || (flags.help && !quick))) {
735 char temp_buf[BUFSZ];
736 Strcpy(temp_buf, firstmatch);
737 checkfile(temp_buf, pm, FALSE, (boolean)(ans == LOOK_VERBOSE));
740 pline("I've never heard of such things.");
743 } while (from_screen && !quick && ans != LOOK_ONCE);
745 flags.verbose = save_verbose;
753 return do_look(FALSE);
759 return do_look(TRUE);
765 register struct trap *trap;
768 if (!getdir("^")) return 0;
771 for (trap = ftrap; trap; trap = trap->ntrap)
772 if (trap->tx == x && trap->ty == y) {
773 if (!trap->tseen) break;
776 if (u.dz < 0 ? (tt == TRAPDOOR || tt == HOLE) :
777 tt == ROCKTRAP) break;
780 pline("That is %s%s%s.",
781 an(defsyms[trap_to_defsym(tt)].explanation),
782 !trap->madeby_u ? "" : (tt == WEB) ? " woven" :
783 /* trap doors & spiked pits can't be made by
784 player, and should be considered at least
785 as much "set" as "dug" anyway */
786 (tt == HOLE || tt == PIT) ? " dug" : " set",
787 !trap->madeby_u ? "" : " by you");
790 pline("I can't see a trap there.");
795 dowhatdoes_core(q, cbuf)
801 register char *buf = &bufr[6], *ep, ctrl, meta;
803 fp = dlb_fopen(CMDHELPFILE, "r");
805 pline("Cannot open data file!");
809 ctrl = ((q <= '\033') ? (q - 1 + 'A') : 0);
810 meta = ((0x80 & q) ? (0x7f & q) : 0);
811 while(dlb_fgets(buf,BUFSZ-6,fp)) {
812 if ((ctrl && *buf=='^' && *(buf+1)==ctrl) ||
813 (meta && *buf=='M' && *(buf+1)=='-' && *(buf+2)==meta) ||
815 ep = index(buf, '\n');
817 if (ctrl && buf[2] == '\t'){
819 (void) strncpy(buf, "^? ", 8);
821 } else if (meta && buf[3] == '\t'){
823 (void) strncpy(buf, "M-? ", 8);
825 } else if(buf[1] == '\t'){
828 (void) strncpy(buf+1, " ", 7);
830 (void) dlb_fclose(fp);
835 (void) dlb_fclose(fp);
845 #if defined(UNIX) || defined(VMS)
848 q = yn_function("What command?", (char *)0, '\0');
849 #if defined(UNIX) || defined(VMS)
852 reslt = dowhatdoes_core(q, bufr);
856 pline("I've never heard of such commands.");
860 /* data for help_menu() */
861 static const char *help_menu_items[] = {
862 /* 0*/ "Long description of the game and commands.",
863 /* 1*/ "List of game commands.",
864 /* 2*/ "Concise history of NetHack.",
865 /* 3*/ "Info on a character in the game display.",
866 /* 4*/ "Info on what a given key does.",
867 /* 5*/ "List of game options.",
868 /* 6*/ "Longer explanation of game options.",
869 /* 7*/ "List of extended commands.",
870 /* 8*/ "The NetHack license.",
872 "%s-specific help and commands.",
873 #define PORT_HELP_ID 100
874 #define WIZHLP_SLOT 10
876 #define WIZHLP_SLOT 9
879 "List of wizard-mode commands.",
889 winid tmpwin = create_nhwindow(NHW_MENU);
891 char helpbuf[QBUFSZ];
897 any.a_void = 0; /* zero all bits */
900 if (!wizard) help_menu_items[WIZHLP_SLOT] = "",
901 help_menu_items[WIZHLP_SLOT+1] = (char *)0;
903 for (i = 0; help_menu_items[i]; i++)
905 /* port-specific line has a %s in it for the PORT_ID */
906 if (help_menu_items[i][0] == '%') {
907 Sprintf(helpbuf, help_menu_items[i], PORT_ID);
908 any.a_int = PORT_HELP_ID + 1;
909 add_menu(tmpwin, NO_GLYPH, &any, 0, 0, ATR_NONE,
910 helpbuf, MENU_UNSELECTED);
914 any.a_int = (*help_menu_items[i]) ? i+1 : 0;
915 add_menu(tmpwin, NO_GLYPH, &any, 0, 0,
916 ATR_NONE, help_menu_items[i], MENU_UNSELECTED);
918 end_menu(tmpwin, "Select one item:");
919 n = select_menu(tmpwin, PICK_ONE, &selected);
920 destroy_nhwindow(tmpwin);
922 *sel = selected[0].item.a_int - 1;
923 free((genericptr_t)selected);
934 if (help_menu(&sel)) {
936 case 0: display_file(HELP, TRUE); break;
937 case 1: display_file(SHELP, TRUE); break;
938 case 2: (void) dohistory(); break;
939 case 3: (void) dowhatis(); break;
940 case 4: (void) dowhatdoes(); break;
941 case 5: option_help(); break;
942 case 6: display_file(OPTIONFILE, TRUE); break;
943 case 7: (void) doextlist(); break;
944 case 8: display_file(LICENSE, TRUE); break;
946 /* handle slot 9 or 10 */
947 default: display_file(DEBUGHELP, TRUE); break;
950 case PORT_HELP_ID: port_help(); break;
960 display_file(HISTORY, TRUE);