OSDN Git Service

Initial Import
[nethackexpress/trunk.git] / src / pager.c
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. */
4
5 /* This file contains the command routines dowhatis() and dohelp() and */
6 /* a few other help related facilities */
7
8 #include "hack.h"
9 #include "dlb.h"
10
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 *));
18 #ifdef PORT_HELP
19 extern void NDECL(port_help);
20 #endif
21
22 /* Returns "true" for characters that could represent a monster's stomach. */
23 STATIC_OVL boolean
24 is_swallow_sym(c)
25 int c;
26 {
27     int i;
28     for (i = S_sw_tl; i <= S_sw_br; i++)
29         if ((int)showsyms[i] == c) return TRUE;
30     return FALSE;
31 }
32
33 /*
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.
37  */
38 STATIC_OVL int
39 append_str(buf, new_str)
40     char *buf;
41     const char *new_str;
42 {
43     int space_left;     /* space remaining in buf */
44
45     if (strstri(buf, new_str)) return 0;
46
47     space_left = BUFSZ - strlen(buf) - 1;
48     (void) strncat(buf, " or ", space_left);
49     (void) strncat(buf, new_str, space_left - 4);
50     return 1;
51 }
52
53 /*
54  * Return the name of the glyph found at (x,y).
55  * If not hallucinating and the glyph is a monster, also monster data.
56  */
57 STATIC_OVL struct permonst *
58 lookat(x, y, buf, monbuf)
59     int x, y;
60     char *buf, *monbuf;
61 {
62     register struct monst *mtmp = (struct monst *) 0;
63     struct permonst *pm = (struct permonst *) 0;
64     int glyph;
65
66     buf[0] = monbuf[0] = 0;
67     glyph = glyph_at(x,y);
68     if (u.ux == x && u.uy == y && senseself()) {
69         char race[QBUFSZ];
70
71         /* if not polymorphed, show both the role and the race */
72         race[0] = 0;
73         if (!Upolyd) {
74             Sprintf(race, "%s ", urace.adj);
75         }
76
77         Sprintf(buf, "%s%s%s called %s",
78                 Invis ? "invisible " : "",
79                 race,
80                 mons[u.umonnum].mname,
81                 plname);
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];
87
88 #ifdef STEED
89         if (u.usteed) {
90             char steedbuf[BUFSZ];
91
92             Sprintf(steedbuf, ", mounted on %s", y_monnam(u.usteed));
93             /* assert((sizeof buf >= strlen(buf)+strlen(steedbuf)+1); */
94             Strcat(buf, steedbuf);
95         }
96 #endif
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) {
102             unsigned how = 0;
103
104             if (Infravision)     how |= 1;
105             if (Unblind_telepat) how |= 2;
106             if (Detect_monsters) how |= 4;
107
108             if (how)
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" : "");
117         }
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));
122         pm = u.ustuck->data;
123     } else if (glyph_is_monster(glyph)) {
124         bhitpos.x = x;
125         bhitpos.y = y;
126         mtmp = m_at(x,y);
127         if (mtmp != (struct monst *) 0) {
128             char *name, monnambuf[BUFSZ];
129             boolean accurate = !Hallucination;
130
131             if (mtmp->data == &mons[PM_COYOTE] && accurate)
132                 name = coyotename(mtmp, monnambuf);
133             else
134                 name = distant_monnam(mtmp, ARTICLE_NONE, monnambuf);
135
136             pm = mtmp->data;
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 " : "",
143                     name);
144             if (u.ustuck == mtmp)
145                 Strcat(buf, (Upolyd && sticks(youmonst.data)) ?
146                         ", being held" : ", holding you");
147             if (mtmp->mleashed)
148                 Strcat(buf, ", leashed to you");
149
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;
153
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));
159             }
160
161             {
162                 int ways_seen = 0, normal = 0, xraydist;
163                 boolean useemon = (boolean) canseemon(mtmp);
164
165                 xraydist = (u.xray_range<0) ? -1 : u.xray_range * u.xray_range;
166                 /* normal vision */
167                 if ((mtmp->wormno ? worm_known(mtmp) : cansee(mtmp->mx, mtmp->my)) &&
168                         mon_visible(mtmp) && !mtmp->minvis) {
169                     ways_seen++;
170                     normal++;
171                 }
172                 /* see invisible */
173                 if (useemon && mtmp->minvis)
174                     ways_seen++;
175                 /* infravision */
176                 if ((!mtmp->minvis || See_invisible) && see_with_infrared(mtmp))
177                     ways_seen++;
178                 /* telepathy */
179                 if (tp_sensemon(mtmp))
180                     ways_seen++;
181                 /* xray */
182                 if (useemon && xraydist > 0 &&
183                         distu(mtmp->mx, mtmp->my) <= xraydist)
184                     ways_seen++;
185                 if (Detect_monsters)
186                     ways_seen++;
187                 if (MATCH_WARN_OF_MON(mtmp))
188                     ways_seen++;
189
190                 if (ways_seen > 1 || !normal) {
191                     if (normal) {
192                         Strcat(monbuf, "normal vision");
193                         /* can't actually be 1 yet here */
194                         if (ways_seen-- > 1) Strcat(monbuf, ", ");
195                     }
196                     if (useemon && mtmp->minvis) {
197                         Strcat(monbuf, "see invisible");
198                         if (ways_seen-- > 1) Strcat(monbuf, ", ");
199                     }
200                     if ((!mtmp->minvis || See_invisible) &&
201                             see_with_infrared(mtmp)) {
202                         Strcat(monbuf, "infravision");
203                         if (ways_seen-- > 1) Strcat(monbuf, ", ");
204                     }
205                     if (tp_sensemon(mtmp)) {
206                         Strcat(monbuf, "telepathy");
207                         if (ways_seen-- > 1) Strcat(monbuf, ", ");
208                     }
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, ", ");
214                     }
215                     if (Detect_monsters) {
216                         Strcat(monbuf, "monster detection");
217                         if (ways_seen-- > 1) Strcat(monbuf, ", ");
218                     }
219                     if (MATCH_WARN_OF_MON(mtmp)) {
220                         char wbuf[BUFSZ];
221                         if (Hallucination)
222                                 Strcat(monbuf, "paranoid delusion");
223                         else {
224                                 Sprintf(wbuf, "warned of %s",
225                                         makeplural(mtmp->data->mname));
226                                 Strcat(monbuf, wbuf);
227                         }
228                         if (ways_seen-- > 1) Strcat(monbuf, ", ");
229                     }
230                 }
231             }
232         }
233     }
234     else if (glyph_is_object(glyph)) {
235         struct obj *otmp = vobj_at(x,y);
236
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));
245                 dealloc_obj(otmp);
246             }
247         } else
248             Strcpy(buf, distant_name(otmp, xname));
249
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)) {
266     case S_altar:
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");
271         break;
272     case S_ndoor:
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");
277         else
278             Strcpy(buf,"doorway");
279         break;
280     case S_cloud:
281         Strcpy(buf, Is_airlevel(&u.uz) ? "cloudy area" : "fog/vapor cloud");
282         break;
283     default:
284         Strcpy(buf,defsyms[glyph_to_cmap(glyph)].explanation);
285         break;
286     }
287
288     return ((pm && !Hallucination) ? pm : (struct permonst *) 0);
289 }
290
291 /*
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.
295  *
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.
300  */
301 STATIC_OVL void
302 checkfile(inp, pm, user_typed_name, without_asking)
303     char *inp;
304     struct permonst *pm;
305     boolean user_typed_name, without_asking;
306 {
307     dlb *fp;
308     char buf[BUFSZ], newstr[BUFSZ];
309     char *ep, *dbase_str;
310     long txt_offset;
311     int chk_skip;
312     boolean found_in_file = FALSE, skipping_entry = FALSE;
313
314     fp = dlb_fopen(DATAFILE, "r");
315     if (!fp) {
316         pline("Cannot open data file!");
317         return;
318     }
319
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.
323      */
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);
328
329     if (!strncmp(dbase_str, "interior of ", 12))
330         dbase_str += 12;
331     if (!strncmp(dbase_str, "a ", 2))
332         dbase_str += 2;
333     else if (!strncmp(dbase_str, "an ", 3))
334         dbase_str += 3;
335     else if (!strncmp(dbase_str, "the ", 4))
336         dbase_str += 4;
337     if (!strncmp(dbase_str, "tame ", 5))
338         dbase_str += 5;
339     else if (!strncmp(dbase_str, "peaceful ", 9))
340         dbase_str += 9;
341     if (!strncmp(dbase_str, "invisible ", 10))
342         dbase_str += 10;
343     if (!strncmp(dbase_str, "statue of ", 10))
344         dbase_str[6] = '\0';
345     else if (!strncmp(dbase_str, "figurine of ", 12))
346         dbase_str[8] = '\0';
347
348     /* Make sure the name is non-empty. */
349     if (*dbase_str) {
350         /* adjust the input to remove "named " and convert to lower case */
351         char *alt = 0;  /* alternate description */
352
353         if ((ep = strstri(dbase_str, " named ")) != 0)
354             alt = ep + 7;
355         else
356             ep = strstri(dbase_str, " called ");
357         if (!ep) ep = strstri(dbase_str, ", ");
358         if (ep && ep > dbase_str) *ep = '\0';
359
360         /*
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.
366          */
367
368         if (!alt)
369             alt = makesingular(dbase_str);
370         else
371             if (user_typed_name)
372                 (void) lcase(alt);
373
374         /* skip first record; read second */
375         txt_offset = 0L;
376         if (!dlb_fgets(buf, BUFSZ, fp) || !dlb_fgets(buf, BUFSZ, fp)) {
377             impossible("can't read 'data' file");
378             (void) dlb_fclose(fp);
379             return;
380         } else if (sscanf(buf, "%8lx\n", &txt_offset) < 1 || txt_offset <= 0)
381             goto bad_data_file;
382
383         /* look for the appropriate entry */
384         while (dlb_fgets(buf,BUFSZ,fp)) {
385             if (*buf == '.') break;  /* we passed last entry without success */
386
387             if (digit(*buf)) {
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;
392                 *ep = 0;
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))) {
397                     if (chk_skip) {
398                         skipping_entry = TRUE;
399                         continue;
400                     } else {
401                         found_in_file = TRUE;
402                         break;
403                     }
404                 }
405             }
406         }
407     }
408
409     if(found_in_file) {
410         long entry_offset;
411         int  entry_count;
412         int  i;
413
414         /* skip over other possible matches for the info */
415         do {
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);
421                 return;
422         }
423
424         if (user_typed_name || without_asking || yn("More info?") == 'y') {
425             winid datawin;
426
427             if (dlb_fseek(fp, txt_offset + entry_offset, SEEK_SET) < 0) {
428                 pline("? Seek error on 'data' file!");
429                 (void) dlb_fclose(fp);
430                 return;
431             }
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);
438             }
439             display_nhwindow(datawin, FALSE);
440             destroy_nhwindow(datawin);
441         }
442     } else if (user_typed_name)
443         pline("I don't have any information on those things.");
444
445     (void) dlb_fclose(fp);
446 }
447
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 */
453
454 /* also used by getpos hack in do_name.c */
455 const char what_is_an_unknown_object[] = "an unknown object";
456
457 STATIC_OVL int
458 do_look(quick)
459     boolean quick;      /* use cursor && don't search for "more info" */
460 {
461     char    out_str[BUFSZ], look_buf[BUFSZ];
462     const char *x_str, *firstmatch = 0;
463     struct permonst *pm = 0;
464     int     i, ans = 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";
474
475     if (quick) {
476         from_screen = TRUE;     /* yes, we want to use the cursor */
477     } else {
478         i = ynq("Specify unknown object by cursor?");
479         if (i == 'q') return 0;
480         from_screen = (i == 'y');
481     }
482
483     if (from_screen) {
484         cc.x = u.ux;
485         cc.y = u.uy;
486         sym = 0;                /* gcc -Wall lint */
487     } else {
488         getlin("Specify what? (type the word)", out_str);
489         if (out_str[0] == '\0' || out_str[0] == '\033')
490             return 0;
491
492         if (out_str[1]) {       /* user typed in a complete string */
493             checkfile(out_str, pm, TRUE, TRUE);
494             return 0;
495         }
496         sym = out_str[0];
497     }
498
499     /* Save the verbose flag, we change it later. */
500     save_verbose = flags.verbose;
501     flags.verbose = flags.verbose && !quick;
502     /*
503      * The user typed one letter, or we're identifying from the screen.
504      */
505     do {
506         /* Reset some variables. */
507         need_to_look = FALSE;
508         pm = (struct permonst *)0;
509         skipped_venom = 0;
510         found = 0;
511         out_str[0] = '\0';
512
513         if (from_screen) {
514             int glyph;  /* glyph at selected position */
515
516             if (flags.verbose)
517                 pline("Please move the cursor to %s.",
518                        what_is_an_unknown_object);
519             else
520                 pline("Pick an object.");
521
522             ans = getpos(&cc, quick, what_is_an_unknown_object);
523             if (ans < 0 || cc.x < 0) {
524                 flags.verbose = save_verbose;
525                 return 0;       /* done */
526             }
527             flags.verbose = FALSE;      /* only print long question once */
528
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)) {
545                 sym = DEF_INVISIBLE;
546             } else if (glyph_is_warning(glyph)) {
547                 sym = glyph_to_warning(glyph);
548                 sym = warnsyms[sym];
549             } else {
550                 impossible("do_look:  bad glyph %d at (%d,%d)",
551                                                 glyph, (int)cc.x, (int)cc.y);
552                 sym = ' ';
553             }
554         }
555
556         /*
557          * Check all the possibilities, saving all explanations in a buffer.
558          * When all have been checked then the string is printed.
559          */
560
561         /* Check for monsters */
562         for (i = 0; i < MAXMCLASSES; i++) {
563             if (sym == (from_screen ? monsyms[i] : def_monsyms[i]) &&
564                 monexplain[i]) {
565                 need_to_look = TRUE;
566                 if (!found) {
567                     Sprintf(out_str, "%c       %s", sym, an(monexplain[i]));
568                     firstmatch = monexplain[i];
569                     found++;
570                 } else {
571                     found += append_str(out_str, an(monexplain[i]));
572                 }
573             }
574         }
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 '@' */
578         if ((from_screen ?
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" */
583
584         /*
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".
588          */
589         if (u.uswallow && from_screen && is_swallow_sym(sym)) {
590             if (!found) {
591                 Sprintf(out_str, "%c       %s", sym, mon_interior);
592                 firstmatch = mon_interior;
593             } else {
594                 found += append_str(out_str, mon_interior);
595             }
596             need_to_look = TRUE;
597         }
598
599         /* Now check for objects */
600         for (i = 1; i < MAXOCLASSES; i++) {
601             if (sym == (from_screen ? oc_syms[i] : def_oc_syms[i])) {
602                 need_to_look = TRUE;
603                 if (from_screen && i == VENOM_CLASS) {
604                     skipped_venom++;
605                     continue;
606                 }
607                 if (!found) {
608                     Sprintf(out_str, "%c       %s", sym, an(objexplain[i]));
609                     firstmatch = objexplain[i];
610                     found++;
611                 } else {
612                     found += append_str(out_str, an(objexplain[i]));
613                 }
614             }
615         }
616
617         if (sym == DEF_INVISIBLE) {
618             if (!found) {
619                 Sprintf(out_str, "%c       %s", sym, an(invisexplain));
620                 firstmatch = invisexplain;
621                 found++;
622             } else {
623                 found += append_str(out_str, an(invisexplain));
624             }
625         }
626
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)
629
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)*/
638
639                 if (!found) {
640                     if (is_cmap_trap(i)) {
641                         Sprintf(out_str, "%c       a trap", sym);
642                         hit_trap = TRUE;
643                     } else {
644                         Sprintf(out_str, "%c       %s", sym,
645                                 article == 2 ? the(x_str) :
646                                 article == 1 ? an(x_str) : x_str);
647                     }
648                     firstmatch = x_str;
649                     found++;
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;
656                 }
657
658                 if (i == S_altar || is_cmap_trap(i))
659                     need_to_look = TRUE;
660             }
661         }
662
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)) {
667                 if (!found) {
668                         Sprintf(out_str, "%c       %s",
669                                 sym, def_warnsyms[i].explanation);
670                         firstmatch = def_warnsyms[i].explanation;
671                         found++;
672                 } else {
673                         found += append_str(out_str, def_warnsyms[i].explanation);
674                 }
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*/
680             }
681         }
682     
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];
686             if (!found) {
687                 Sprintf(out_str, "%c       %s", sym, an(x_str));
688                 firstmatch = x_str;
689                 found++;
690             } else {
691                 found += append_str(out_str, an(x_str));
692             }
693         }
694
695         /* handle optional boulder symbol as a special case */ 
696         if (iflags.bouldersym && sym == iflags.bouldersym) {
697             if (!found) {
698                 firstmatch = "boulder";
699                 Sprintf(out_str, "%c       %s", sym, an(firstmatch));
700                 found++;
701             } else {
702                 found += append_str(out_str, "boulder");
703             }
704         }
705         
706         /*
707          * If we are looking at the screen, follow multiple possibilities or
708          * an ambiguous explanation by something more detailed.
709          */
710         if (from_screen) {
711             if (found > 1 || need_to_look) {
712                 char monbuf[BUFSZ];
713                 char temp_buf[BUFSZ];
714
715                 pm = lookat(cc.x, cc.y, look_buf, monbuf);
716                 firstmatch = look_buf;
717                 if (*firstmatch) {
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 */
721                 }
722                 if (monbuf[0]) {
723                     Sprintf(temp_buf, " [seen: %s]", monbuf);
724                     (void)strncat(out_str, temp_buf, BUFSZ-strlen(out_str)-1);
725                 }
726             }
727         }
728
729         /* Finally, print out our explanation. */
730         if (found) {
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));
738             }
739         } else {
740             pline("I've never heard of such things.");
741         }
742
743     } while (from_screen && !quick && ans != LOOK_ONCE);
744
745     flags.verbose = save_verbose;
746     return 0;
747 }
748
749
750 int
751 dowhatis()
752 {
753         return do_look(FALSE);
754 }
755
756 int
757 doquickwhatis()
758 {
759         return do_look(TRUE);
760 }
761
762 int
763 doidtrap()
764 {
765         register struct trap *trap;
766         int x, y, tt;
767
768         if (!getdir("^")) return 0;
769         x = u.ux + u.dx;
770         y = u.uy + u.dy;
771         for (trap = ftrap; trap; trap = trap->ntrap)
772             if (trap->tx == x && trap->ty == y) {
773                 if (!trap->tseen) break;
774                 tt = trap->ttyp;
775                 if (u.dz) {
776                     if (u.dz < 0 ? (tt == TRAPDOOR || tt == HOLE) :
777                             tt == ROCKTRAP) break;
778                 }
779                 tt = what_trap(tt);
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");
788                 return 0;
789             }
790         pline("I can't see a trap there.");
791         return 0;
792 }
793
794 char *
795 dowhatdoes_core(q, cbuf)
796 char q;
797 char *cbuf;
798 {
799         dlb *fp;
800         char bufr[BUFSZ];
801         register char *buf = &bufr[6], *ep, ctrl, meta;
802
803         fp = dlb_fopen(CMDHELPFILE, "r");
804         if (!fp) {
805                 pline("Cannot open data file!");
806                 return 0;
807         }
808
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) ||
814                 *buf==q) {
815                 ep = index(buf, '\n');
816                 if(ep) *ep = 0;
817                 if (ctrl && buf[2] == '\t'){
818                         buf = bufr + 1;
819                         (void) strncpy(buf, "^?      ", 8);
820                         buf[1] = ctrl;
821                 } else if (meta && buf[3] == '\t'){
822                         buf = bufr + 2;
823                         (void) strncpy(buf, "M-?     ", 8);
824                         buf[2] = meta;
825                 } else if(buf[1] == '\t'){
826                         buf = bufr;
827                         buf[0] = q;
828                         (void) strncpy(buf+1, "       ", 7);
829                 }
830                 (void) dlb_fclose(fp);
831                 Strcpy(cbuf, buf);
832                 return cbuf;
833             }
834         }
835         (void) dlb_fclose(fp);
836         return (char *)0;
837 }
838
839 int
840 dowhatdoes()
841 {
842         char bufr[BUFSZ];
843         char q, *reslt;
844
845 #if defined(UNIX) || defined(VMS)
846         introff();
847 #endif
848         q = yn_function("What command?", (char *)0, '\0');
849 #if defined(UNIX) || defined(VMS)
850         intron();
851 #endif
852         reslt = dowhatdoes_core(q, bufr);
853         if (reslt)
854                 pline("%s", reslt);
855         else
856                 pline("I've never heard of such commands.");
857         return 0;
858 }
859
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.",
871 #ifdef PORT_HELP
872         "%s-specific help and commands.",
873 #define PORT_HELP_ID 100
874 #define WIZHLP_SLOT 10
875 #else
876 #define WIZHLP_SLOT 9
877 #endif
878 #ifdef WIZARD
879         "List of wizard-mode commands.",
880 #endif
881         "",
882         (char *)0
883 };
884
885 STATIC_OVL boolean
886 help_menu(sel)
887         int *sel;
888 {
889         winid tmpwin = create_nhwindow(NHW_MENU);
890 #ifdef PORT_HELP
891         char helpbuf[QBUFSZ];
892 #endif
893         int i, n;
894         menu_item *selected;
895         anything any;
896
897         any.a_void = 0;         /* zero all bits */
898         start_menu(tmpwin);
899 #ifdef WIZARD
900         if (!wizard) help_menu_items[WIZHLP_SLOT] = "",
901                      help_menu_items[WIZHLP_SLOT+1] = (char *)0;
902 #endif
903         for (i = 0; help_menu_items[i]; i++)
904 #ifdef PORT_HELP
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);
911             } else
912 #endif
913             {
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);
917             }
918         end_menu(tmpwin, "Select one item:");
919         n = select_menu(tmpwin, PICK_ONE, &selected);
920         destroy_nhwindow(tmpwin);
921         if (n > 0) {
922             *sel = selected[0].item.a_int - 1;
923             free((genericptr_t)selected);
924             return TRUE;
925         }
926         return FALSE;
927 }
928
929 int
930 dohelp()
931 {
932         int sel = 0;
933
934         if (help_menu(&sel)) {
935                 switch (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;
945 #ifdef WIZARD
946                         /* handle slot 9 or 10 */
947                         default: display_file(DEBUGHELP, TRUE);  break;
948 #endif
949 #ifdef PORT_HELP
950                         case PORT_HELP_ID:  port_help();  break;
951 #endif
952                 }
953         }
954         return 0;
955 }
956
957 int
958 dohistory()
959 {
960         display_file(HISTORY, TRUE);
961         return 0;
962 }
963
964 /*pager.c*/