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
11STATIC_DCL boolean FDECL(is_swallow_sym, (int));
12STATIC_DCL int FDECL(append_str, (char *, const char *));
13STATIC_DCL struct permonst * FDECL(lookat, (int, int, char *, char *));
14STATIC_DCL void FDECL(checkfile,
15		      (char *,struct permonst *,BOOLEAN_P,BOOLEAN_P));
16STATIC_DCL int FDECL(do_look, (BOOLEAN_P));
17STATIC_DCL boolean FDECL(help_menu, (int *));
18#ifdef PORT_HELP
19extern void NDECL(port_help);
20#endif
21
22/* Returns "true" for characters that could represent a monster's stomach. */
23STATIC_OVL boolean
24is_swallow_sym(c)
25int 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 */
38STATIC_OVL int
39append_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 */
57STATIC_OVL struct permonst *
58lookat(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 */
301STATIC_OVL void
302checkfile(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) {
419bad_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 */
455const char what_is_an_unknown_object[] = "an unknown object";
456
457STATIC_OVL int
458do_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
750int
751dowhatis()
752{
753	return do_look(FALSE);
754}
755
756int
757doquickwhatis()
758{
759	return do_look(TRUE);
760}
761
762int
763doidtrap()
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
794char *
795dowhatdoes_core(q, cbuf)
796char q;
797char *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
839int
840dowhatdoes()
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() */
861static 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
885STATIC_OVL boolean
886help_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
929int
930dohelp()
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
957int
958dohistory()
959{
960	display_file(HISTORY, TRUE);
961	return 0;
962}
963
964/*pager.c*/
965