1    | /*	SCCS Id: @(#)pager.c	3.3	1999/10/10	*/
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 && canseeself()) {
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   | 
83   | #ifdef STEED
84   | 	if (u.usteed) {
85   | 	    char steedbuf[BUFSZ];
86   | 
87   | 	    Sprintf(steedbuf, ", mounted on %s", y_monnam(u.usteed));
88   | 	    /* assert((sizeof buf >= strlen(buf)+strlen(steedbuf)+1); */
89   | 	    Strcat(buf, steedbuf);
90   | 	}
91   | #endif
92   |     } else if (u.uswallow) {
93   | 	/* all locations when swallowed other than the hero are the monster */
94   | 	Sprintf(buf, "interior of %s",
95   | 				    Blind ? "a monster" : a_monnam(u.ustuck));
96   | 	pm = u.ustuck->data;
97   |     } else if (glyph_is_monster(glyph)) {
98   | 	bhitpos.x = x;
99   | 	bhitpos.y = y;
100  | 	mtmp = m_at(x,y);
101  | 	if(mtmp != (struct monst *) 0) {
102  | 	    register boolean hp = (mtmp->data == &mons[PM_HIGH_PRIEST]);
103  | 
104  | 	    pm = mtmp->data;
105  | 	    Sprintf(buf, "%s%s%s",
106  | 		    (mtmp->mx != x || mtmp->my != y) ?
107  | 			((mtmp->isshk && !Hallucination)
108  | 				? "tail of " : "tail of a ") : "",
109  | 		    (!hp && mtmp->mtame && !Hallucination) ? "tame " :
110  | 		    (!hp && mtmp->mpeaceful && !Hallucination) ?
111  | 		                                          "peaceful " : "",
112  | 		    (hp ? "high priest" : x_monnam(mtmp, ARTICLE_NONE, (char *)0, 0, TRUE)));
113  | 	    if (u.ustuck == mtmp)
114  | 		Strcat(buf, (Upolyd && sticks(youmonst.data)) ?
115  | 			", being held" : ", holding you");
116  | 	    if (mtmp->mleashed)
117  | 		Strcat(buf, ", leashed to you");
118  | 
119  | 	    {
120  | 		int ways_seen = 0, normal = 0, xraydist;
121  | 		boolean useemon = (boolean) canseemon(mtmp);
122  | 
123  | 		xraydist = (u.xray_range<0) ? -1 : u.xray_range * u.xray_range;
124  | 		/* normal vision */
125  | 		if ((mtmp->wormno ? worm_known(mtmp) : cansee(mtmp->mx, mtmp->my)) &&
126  | 			mon_visible(mtmp) && !mtmp->minvis) {
127  | 		    ways_seen++;
128  | 		    normal++;
129  | 		}
130  | 		/* see invisible */
131  | 		if (useemon && mtmp->minvis)
132  | 		    ways_seen++;
133  | 		/* infravision */
134  | 		if ((!mtmp->minvis || See_invisible) && see_with_infrared(mtmp))
135  | 		    ways_seen++;
136  | 		/* telepathy */
137  | 		if (tp_sensemon(mtmp))
138  | 		    ways_seen++;
139  | 		/* xray */
140  | 		if (useemon && xraydist > 0 &&
141  | 			distu(mtmp->mx, mtmp->my) <= xraydist)
142  | 		    ways_seen++;
143  | 		if (Detect_monsters)
144  | 		    ways_seen++;
145  | 		if (MATCH_WARN_OF_MON(mtmp))
146  | 		    ways_seen++;
147  | 
148  | 		if (ways_seen > 1 || !normal) {
149  | 		    if (normal) {
150  | 			Strcat(monbuf, "normal vision");
151  | 			/* can't actually be 1 yet here */
152  | 			if (ways_seen-- > 1) Strcat(monbuf, ", ");
153  | 		    }
154  | 		    if (useemon && mtmp->minvis) {
155  | 			Strcat(monbuf, "see invisible");
156  | 			if (ways_seen-- > 1) Strcat(monbuf, ", ");
157  | 		    }
158  | 		    if ((!mtmp->minvis || See_invisible) &&
159  | 			    see_with_infrared(mtmp)) {
160  | 			Strcat(monbuf, "infravision");
161  | 			if (ways_seen-- > 1) Strcat(monbuf, ", ");
162  | 		    }
163  | 		    if (tp_sensemon(mtmp)) {
164  | 			Strcat(monbuf, "telepathy");
165  | 			if (ways_seen-- > 1) Strcat(monbuf, ", ");
166  | 		    }
167  | 		    if (useemon && xraydist > 0 &&
168  | 			    distu(mtmp->mx, mtmp->my) <= xraydist) {
169  | 			/* Eyes of the Overworld */
170  | 			Strcat(monbuf, "astral vision");
171  | 			if (ways_seen-- > 1) Strcat(monbuf, ", ");
172  | 		    }
173  | 		    if (Detect_monsters) {
174  | 			Strcat(monbuf, "monster detection");
175  | 			if (ways_seen-- > 1) Strcat(monbuf, ", ");
176  | 		    }
177  | 		    if (MATCH_WARN_OF_MON(mtmp)) {
178  | 		    	char wbuf[BUFSZ];
179  | 		    	Sprintf(wbuf, "warned of %s", makeplural(mtmp->data->mname));
180  | 		    	Strcat(monbuf, wbuf);
181  | 		    	if (ways_seen-- > 1) Strcat(monbuf, ", ");
182  | 		    }
183  | 		}
184  | 	    }
185  | 	}
186  |     }
187  |     else if (glyph_is_object(glyph)) {
188  | 	struct obj *otmp = vobj_at(x,y);
189  | 
190  | 	if (!otmp || otmp->otyp != glyph_to_obj(glyph)) {
191  | 	    if (glyph_to_obj(glyph) != STRANGE_OBJECT) {
192  | 		otmp = mksobj(glyph_to_obj(glyph), FALSE, FALSE);
193  | 		if (otmp->oclass == GOLD_CLASS)
194  | 		    otmp->quan = 2L; /* to force pluralization */
195  | 		else if (otmp->otyp == SLIME_MOLD)
196  | 		    otmp->spe = current_fruit;	/* give the fruit a type */
197  | 		Strcpy(buf, distant_name(otmp, xname));
198  | 		dealloc_obj(otmp);
199  | 	    }
200  | 	} else
201  | 	    Strcpy(buf, distant_name(otmp, xname));
202  | 
203  | 	if (levl[x][y].typ == STONE || levl[x][y].typ == SCORR)
204  | 	    Strcat(buf, " embedded in stone");
205  | 	else if (IS_WALL(levl[x][y].typ) || levl[x][y].typ == SDOOR)
206  | 	    Strcat(buf, " embedded in a wall");
207  | 	else if (closed_door(x,y))
208  | 	    Strcat(buf, " embedded in a door");
209  | 	else if (is_pool(x,y))
210  | 	    Strcat(buf, " in water");
211  | 	else if (is_lava(x,y))
212  | 	    Strcat(buf, " in molten lava");	/* [can this ever happen?] */
213  |     } else if (glyph_is_trap(glyph)) {
214  | 	int tnum = what_trap(glyph_to_trap(glyph));
215  | 	Strcpy(buf, defsyms[trap_to_defsym(tnum)].explanation);
216  |     } else if(!glyph_is_cmap(glyph)) {
217  | 	Strcpy(buf,"dark part of a room");
218  |     } else switch(glyph_to_cmap(glyph)) {
219  |     case S_altar:
220  | 	if(!In_endgame(&u.uz))
221  | 	    Sprintf(buf, "%s altar",
222  | 		align_str(Amask2align(levl[x][y].altarmask & ~AM_SHRINE)));
223  | 	else Sprintf(buf, "aligned altar");
224  | 	break;
225  |     case S_ndoor:
226  | 	if (is_drawbridge_wall(x, y) >= 0)
227  | 	    Strcpy(buf,"open drawbridge portcullis");
228  | 	else if ((levl[x][y].doormask & ~D_TRAPPED) == D_BROKEN)
229  | 	    Strcpy(buf,"broken door");
230  | 	else
231  | 	    Strcpy(buf,"doorway");
232  | 	break;
233  |     case S_cloud:
234  | 	Strcpy(buf, Is_airlevel(&u.uz) ? "cloudy area" : "fog/vapor cloud");
235  | 	break;
236  |     default:
237  | 	Strcpy(buf,defsyms[glyph_to_cmap(glyph)].explanation);
238  | 	break;
239  |     }
240  | 
241  |     return ((pm && !Hallucination) ? pm : (struct permonst *) 0);
242  | }
243  | 
244  | /*
245  |  * Look in the "data" file for more info.  Called if the user typed in the
246  |  * whole name (user_typed_name == TRUE), or we've found a possible match
247  |  * with a character/glyph and flags.help is TRUE.
248  |  *
249  |  * NOTE: when (user_typed_name == FALSE), inp is considered read-only and
250  |  *	 must not be changed directly, e.g. via lcase(). We want to force
251  |  *	 lcase() for data.base lookup so that we can have a clean key.
252  |  *	 Therefore, we create a copy of inp _just_ for data.base lookup.
253  |  */
254  | STATIC_OVL void
255  | checkfile(inp, pm, user_typed_name, without_asking)
256  |     char *inp;
257  |     struct permonst *pm;
258  |     boolean user_typed_name, without_asking;
259  | {
260  |     dlb *fp;
261  |     char buf[BUFSZ], newstr[BUFSZ];
262  |     char *ep, *dbase_str;
263  |     long txt_offset;
264  |     int chk_skip;
265  |     boolean found_in_file = FALSE, skipping_entry = FALSE;
266  | 
267  |     fp = dlb_fopen(DATAFILE, "r");
268  |     if (!fp) {
269  | 	pline("Cannot open data file!");
270  | 	return;
271  |     }
272  | 
273  |     /* To prevent the need for entries in data.base like *ngel to account
274  |      * for Angel and angel, make the lookup string the same for both
275  |      * user_typed_name and picked name.
276  |      */
277  |     if (pm != (struct permonst *) 0 && !user_typed_name)
278  | 	dbase_str = strcpy(newstr, pm->mname);
279  |     else dbase_str = strcpy(newstr, inp);
280  |     (void) lcase(dbase_str);
281  | 
282  |     if (!strncmp(dbase_str, "interior of ", 12))
283  | 	dbase_str += 12;
284  |     if (!strncmp(dbase_str, "a ", 2))
285  | 	dbase_str += 2;
286  |     else if (!strncmp(dbase_str, "an ", 3))
287  | 	dbase_str += 3;
288  |     else if (!strncmp(dbase_str, "the ", 4))
289  | 	dbase_str += 4;
290  |     if (!strncmp(dbase_str, "tame ", 5))
291  | 	dbase_str += 5;
292  |     else if (!strncmp(dbase_str, "peaceful ", 9))
293  | 	dbase_str += 9;
294  |     if (!strncmp(dbase_str, "invisible ", 10))
295  | 	dbase_str += 10;
296  |     if (!strncmp(dbase_str, "statue of ", 10))
297  | 	dbase_str[6] = '\0';
298  |     else if (!strncmp(dbase_str, "figurine of ", 12))
299  | 	dbase_str[8] = '\0';
300  | 
301  |     /* Make sure the name is non-empty. */
302  |     if (*dbase_str) {
303  | 	/* adjust the input to remove "named " and convert to lower case */
304  | 	char *alt = 0;	/* alternate description */
305  | 	if ((ep = strstri(dbase_str, " named ")) != 0)
306  | 	    alt = ep + 7;
307  | 	else
308  | 	    ep = strstri(dbase_str, " called ");
309  | 	if (ep) *ep = '\0';
310  | 	else if ((ep = strstri(dbase_str, ", ")) != 0) *ep = '\0';
311  | 
312  | 	/*
313  | 	 * If the object is named, then the name is the alternate description;
314  | 	 * otherwise, the result of makesingular() applied to the name is. This
315  | 	 * isn't strictly optimal, but named objects of interest to the user
316  | 	 * will usually be found under their name, rather than under their
317  | 	 * object type, so looking for a singular form is pointless.
318  | 	 */
319  | 
320  | 	if (!alt)
321  | 	    alt = makesingular(dbase_str);
322  | 	else
323  | 	    if (user_typed_name)
324  | 		(void) lcase(alt);
325  | 
326  | 	/* skip first record; read second */
327  | 	txt_offset = 0L;
328  | 	if (!dlb_fgets(buf, BUFSZ, fp) || !dlb_fgets(buf, BUFSZ, fp)) {
329  | 	    impossible("can't read 'data' file");
330  | 	    (void) dlb_fclose(fp);
331  | 	    return;
332  | 	} else if (sscanf(buf, "%8lx\n", &txt_offset) < 1 || txt_offset <= 0)
333  | 	    goto bad_data_file;
334  | 
335  | 	/* look for the appropriate entry */
336  | 	while (dlb_fgets(buf,BUFSZ,fp)) {
337  | 	    if (*buf == '.') break;  /* we passed last entry without success */
338  | 
339  | 	    if (digit(*buf)) {
340  | 		/* a number indicates the end of current entry */
341  | 		skipping_entry = FALSE;
342  | 	    } else if (!skipping_entry) {
343  | 		if (!(ep = index(buf, '\n'))) goto bad_data_file;
344  | 		*ep = 0;
345  | 		/* if we match a key that begins with "~", skip this entry */
346  | 		chk_skip = (*buf == '~') ? 1 : 0;
347  | 		if (pmatch(&buf[chk_skip], dbase_str) ||
348  | 			(alt && pmatch(&buf[chk_skip], alt))) {
349  | 		    if (chk_skip) {
350  | 			skipping_entry = TRUE;
351  | 			continue;
352  | 		    } else {
353  | 			found_in_file = TRUE;
354  | 			break;
355  | 		    }
356  | 		}
357  | 	    }
358  | 	}
359  |     }
360  | 
361  |     if(found_in_file) {
362  | 	long entry_offset;
363  | 	int  entry_count;
364  | 	int  i;
365  | 
366  | 	/* skip over other possible matches for the info */
367  | 	do {
368  | 	    if (!dlb_fgets(buf, BUFSZ, fp)) goto bad_data_file;
369  | 	} while (!digit(*buf));
370  | 	if (sscanf(buf, "%ld,%d\n", &entry_offset, &entry_count) < 2) {
371  | bad_data_file:	impossible("'data' file in wrong format");
372  | 		(void) dlb_fclose(fp);
373  | 		return;
374  | 	}
375  | 
376  | 	if (user_typed_name || without_asking || yn("More info?") == 'y') {
377  | 	    winid datawin;
378  | 
379  | 	    if (dlb_fseek(fp, txt_offset + entry_offset, SEEK_SET) < 0) {
380  | 		pline("? Seek error on 'data' file!");
381  | 		(void) dlb_fclose(fp);
382  | 		return;
383  | 	    }
384  | 	    datawin = create_nhwindow(NHW_MENU);
385  | 	    for (i = 0; i < entry_count; i++) {
386  | 		if (!dlb_fgets(buf, BUFSZ, fp)) goto bad_data_file;
387  | 		if ((ep = index(buf, '\n')) != 0) *ep = 0;
388  | 		if (index(buf+1, '\t') != 0) (void) tabexpand(buf+1);
389  | 		putstr(datawin, 0, buf+1);
390  | 	    }
391  | 	    display_nhwindow(datawin, FALSE);
392  | 	    destroy_nhwindow(datawin);
393  | 	}
394  |     } else if (user_typed_name)
395  | 	pline("I don't have any information on those things.");
396  | 
397  |     (void) dlb_fclose(fp);
398  | }
399  | 
400  | /* getpos() return values */
401  | #define LOOK_TRADITIONAL	0	/* '.' -- ask about "more info?" */
402  | #define LOOK_QUICK		1	/* ',' -- skip "more info?" */
403  | #define LOOK_ONCE		2	/* ';' -- skip and stop looping */
404  | #define LOOK_VERBOSE		3	/* ':' -- show more info w/o asking */
405  | 
406  | /* also used by getpos hack in do_name.c */
407  | const char what_is_an_unknown_object[] = "an unknown object";
408  | 
409  | STATIC_OVL int
410  | do_look(quick)
411  |     boolean quick;	/* use cursor && don't search for "more info" */
412  | {
413  |     char    out_str[BUFSZ], look_buf[BUFSZ];
414  |     const char *x_str, *firstmatch = 0;
415  |     struct permonst *pm = 0;
416  |     int     i, ans = 0;
417  |     int     sym;		/* typed symbol or converted glyph */
418  |     int	    found;		/* count of matching syms found */
419  |     coord   cc;			/* screen pos of unknown glyph */
420  |     boolean save_verbose;	/* saved value of flags.verbose */
421  |     boolean from_screen;	/* question from the screen */
422  |     boolean need_to_look;	/* need to get explan. from glyph */
423  |     boolean hit_trap;		/* true if found trap explanation */
424  |     int skipped_venom = 0;	/* non-zero if we ignored "splash of venom" */
425  |     static const char *mon_interior = "the interior of a monster";
426  | 
427  |     if (quick) {
428  | 	from_screen = TRUE;	/* yes, we want to use the cursor */
429  |     } else {
430  | 	i = ynq("Specify unknown object by cursor?");
431  | 	if (i == 'q') return 0;
432  | 	from_screen = (i == 'y');
433  |     }
434  | 
435  |     if (from_screen) {
436  | 	cc.x = u.ux;
437  | 	cc.y = u.uy;
438  | 	sym = 0;		/* gcc -Wall lint */
439  |     } else {
440  | 	getlin("Specify what? (type the word)", out_str);
441  | 	if (out_str[0] == '\0' || out_str[0] == '\033')
442  | 	    return 0;
443  | 
444  | 	if (out_str[1]) {	/* user typed in a complete string */
445  | 	    checkfile(out_str, pm, TRUE, TRUE);
446  | 	    return 0;
447  | 	}
448  | 	sym = out_str[0];
449  |     }
450  | 
451  |     /* Save the verbose flag, we change it later. */
452  |     save_verbose = flags.verbose;
453  |     flags.verbose = flags.verbose && !quick;
454  |     /*
455  |      * The user typed one letter, or we're identifying from the screen.
456  |      */
457  |     do {
458  | 	/* Reset some variables. */
459  | 	need_to_look = FALSE;
460  | 	pm = (struct permonst *)0;
461  | 	found = 0;
462  | 	out_str[0] = '\0';
463  | 
464  | 	if (from_screen) {
465  | 	    int glyph;	/* glyph at selected position */
466  | 
467  | 	    if (flags.verbose)
468  | 		pline("Please move the cursor to %s.",
469  | 		       what_is_an_unknown_object);
470  | 	    else
471  | 		pline("Pick an object.");
472  | 
473  | 	    ans = getpos(&cc, quick, what_is_an_unknown_object);
474  | 	    if (ans < 0 || cc.x < 0) {
475  | 		flags.verbose = save_verbose;
476  | 		return 0;	/* done */
477  | 	    }
478  | 	    flags.verbose = FALSE;	/* only print long question once */
479  | 
480  | 	    /* Convert the glyph at the selected position to a symbol. */
481  | 	    glyph = glyph_at(cc.x,cc.y);
482  | 	    if (glyph_is_cmap(glyph)) {
483  | 		sym = showsyms[glyph_to_cmap(glyph)];
484  | 	    } else if (glyph_is_trap(glyph)) {
485  | 		sym = showsyms[trap_to_defsym(glyph_to_trap(glyph))];
486  | 	    } else if (glyph_is_object(glyph)) {
487  | 		sym = oc_syms[(int)objects[glyph_to_obj(glyph)].oc_class];
488  | 	    } else if (glyph_is_monster(glyph)) {
489  | 		/* takes care of pets, detected, ridden, and regular mons */
490  | 		sym = monsyms[(int)mons[glyph_to_mon(glyph)].mlet];
491  | 	    } else if (glyph_is_swallow(glyph)) {
492  | 		sym = showsyms[glyph_to_swallow(glyph)+S_sw_tl];
493  | 	    } else if (glyph_is_invisible(glyph)) {
494  | 		sym = DEF_INVISIBLE;
495  | 	    } else if (glyph_is_warning(glyph)) {
496  | 		sym = glyph_to_warning(glyph);
497  | 	    	sym = warnsyms[sym];
498  | 	    } else {
499  | 		impossible("do_look:  bad glyph %d at (%d,%d)",
500  | 						glyph, (int)cc.x, (int)cc.y);
501  | 		sym = ' ';
502  | 	    }
503  | 	}
504  | 
505  | 	/*
506  | 	 * Check all the possibilities, saving all explanations in a buffer.
507  | 	 * When all have been checked then the string is printed.
508  | 	 */
509  | 
510  | 	/* Check for monsters */
511  | 	for (i = 0; i < MAXMCLASSES; i++) {
512  | 	    if (sym == (from_screen ? monsyms[i] : def_monsyms[i])) {
513  | 		need_to_look = TRUE;
514  | 		if (!found) {
515  | 		    Sprintf(out_str, "%c       %s", sym, an(monexplain[i]));
516  | 		    firstmatch = monexplain[i];
517  | 		    found++;
518  | 		} else {
519  | 		    found += append_str(out_str, an(monexplain[i]));
520  | 		}
521  | 	    }
522  | 	}
523  | 	/* handle '@' as a special case; firstmatch is guaranteed
524  | 	   to already be set in that case */
525  | 	if (!from_screen ? (sym == def_monsyms[S_HUMAN]) :
526  | 		(cc.x == u.ux && cc.y == u.uy && sym == monsyms[S_HUMAN]))
527  | 	     found += append_str(out_str, "you");	/* tack on "or you" */
528  | 
529  | 	/*
530  | 	 * Special case: if identifying from the screen, and we're swallowed,
531  | 	 * and looking at something other than our own symbol, then just say
532  | 	 * "the interior of a monster".
533  | 	 */
534  | 	if (u.uswallow && from_screen && is_swallow_sym(sym)) {
535  | 	    if (!found) {
536  | 		Sprintf(out_str, "%c       %s", sym, mon_interior);
537  | 		firstmatch = mon_interior;
538  | 	    } else {
539  | 		found += append_str(out_str, mon_interior);
540  | 	    }
541  | 	    need_to_look = TRUE;
542  | 	}
543  | 
544  | 	/* Now check for objects */
545  | 	for (i = 1; i < MAXOCLASSES; i++) {
546  | 	    if (sym == (from_screen ? oc_syms[i] : def_oc_syms[i])) {
547  | 		need_to_look = TRUE;
548  | 		if (from_screen && i == VENOM_CLASS) {
549  | 		    skipped_venom++;
550  | 		    continue;
551  | 		}
552  | 		if (!found) {
553  | 		    Sprintf(out_str, "%c       %s", sym, an(objexplain[i]));
554  | 		    firstmatch = objexplain[i];
555  | 		    found++;
556  | 		} else {
557  | 		    found += append_str(out_str, an(objexplain[i]));
558  | 		}
559  | 	    }
560  | 	}
561  | 
562  | 	if (sym == DEF_INVISIBLE) {
563  | 	    if (!found) {
564  | 		Sprintf(out_str, "%c       %s", sym, an(invisexplain));
565  | 		firstmatch = invisexplain;
566  | 		found++;
567  | 	    } else {
568  | 		found += append_str(out_str, an(invisexplain));
569  | 	    }
570  | 	}
571  | 
572  | #define is_cmap_trap(i) ((i) >= S_arrow_trap && (i) <= S_polymorph_trap)
573  | #define is_cmap_drawbridge(i) ((i) >= S_vodbridge && (i) <= S_hcdbridge)
574  | 
575  | 	/* Now check for graphics symbols */
576  | 	for (hit_trap = FALSE, i = 0; i < MAXPCHARS; i++) {
577  | 	    x_str = defsyms[i].explanation;
578  | 	    if (sym == (from_screen ? showsyms[i] : defsyms[i].sym) && *x_str) {
579  | 		/* avoid "an air", "a water", or "a floor of a room" */
580  | 		int article = (i == S_room) ? 2 :		/* 2=>"the" */
581  | 			      !(strcmp(x_str, "air") == 0 ||	/* 1=>"an"  */
582  | 				strcmp(x_str, "water") == 0);	/* 0=>(none)*/
583  | 
584  | 		if (!found) {
585  | 		    if (is_cmap_trap(i)) {
586  | 			Sprintf(out_str, "%c       a trap", sym);
587  | 			hit_trap = TRUE;
588  | 		    } else {
589  | 			Sprintf(out_str, "%c       %s", sym,
590  | 				article == 2 ? the(x_str) :
591  | 				article == 1 ? an(x_str) : x_str);
592  | 		    }
593  | 		    firstmatch = x_str;
594  | 		    found++;
595  | 		} else if (!u.uswallow && !(hit_trap && is_cmap_trap(i)) &&
596  | 			   !(found >= 3 && is_cmap_drawbridge(i))) {
597  | 		    found += append_str(out_str,
598  | 					article == 2 ? the(x_str) :
599  | 					article == 1 ? an(x_str) : x_str);
600  | 		    if (is_cmap_trap(i)) hit_trap = TRUE;
601  | 		}
602  | 
603  | 		if (i == S_altar || is_cmap_trap(i))
604  | 		    need_to_look = TRUE;
605  | 	    }
606  | 	}
607  | 
608  | 	/* Now check for warning symbols */
609  | 	for (i = 0; i < WARNCOUNT; i++) {
610  | 	    x_str = def_warnsyms[i].explanation;
611  | 	    if (sym == (from_screen ? warnsyms[i] : def_warnsyms[i].sym)) {
612  | 		if (!found) {
613  | 			Sprintf(out_str, "%c       %s",
614  | 				sym, def_warnsyms[i].explanation);
615  | 			firstmatch = def_warnsyms[i].explanation;
616  | 			found++;
617  | 		} else {
618  | 			found += append_str(out_str, def_warnsyms[i].explanation);
619  | 		}
620  | 		break;	/* out of for loop*/
621  | 	    }
622  | 	}
623  |     
624  | 	/* if we ignored venom and list turned out to be short, put it back */
625  | 	if (skipped_venom && found < 2) {
626  | 	    x_str = objexplain[VENOM_CLASS];
627  | 	    if (!found) {
628  | 		Sprintf(out_str, "%c       %s", sym, an(x_str));
629  | 		firstmatch = x_str;
630  | 		found++;
631  | 	    } else {
632  | 		found += append_str(out_str, an(x_str));
633  | 	    }
634  | 	}
635  | 
636  | 	/*
637  | 	 * If we are looking at the screen, follow multiple possibilities or
638  | 	 * an ambiguous explanation by something more detailed.
639  | 	 */
640  | 	if (from_screen) {
641  | 	    if (found > 1 || need_to_look) {
642  | 		char monbuf[BUFSZ];
643  | 		char temp_buf[BUFSZ], coybuf[QBUFSZ];
644  | 
645  | 		pm = lookat(cc.x, cc.y, look_buf, monbuf);
646  | 		firstmatch = look_buf;
647  | 		if (*firstmatch) {
648  | 		    Sprintf(temp_buf, " (%s)",
649  | 				(pm == &mons[PM_COYOTE]) ?
650  | 				coyotename(coybuf) : firstmatch);
651  | 		    (void)strncat(out_str, temp_buf, BUFSZ-strlen(out_str)-1);
652  | 		    found = 1;	/* we have something to look up */
653  | 		}
654  | 		if (monbuf[0]) {
655  | 		    Sprintf(temp_buf, " [seen: %s]", monbuf);
656  | 		    (void)strncat(out_str, temp_buf, BUFSZ-strlen(out_str)-1);
657  | 		}
658  | 	    }
659  | 	}
660  | 
661  | 	/* Finally, print out our explanation. */
662  | 	if (found) {
663  | 	    pline("%s", out_str);
664  | 	    /* check the data file for information about this thing */
665  | 	    if (found == 1 && ans != LOOK_QUICK && ans != LOOK_ONCE &&
666  | 			(ans == LOOK_VERBOSE || (flags.help && !quick))) {
667  | 		char temp_buf[BUFSZ];
668  | 		Strcpy(temp_buf, firstmatch);
669  | 		checkfile(temp_buf, pm, FALSE, (boolean)(ans == LOOK_VERBOSE));
670  | 	    }
671  | 	} else {
672  | 	    pline("I've never heard of such things.");
673  | 	}
674  | 
675  |     } while (from_screen && !quick && ans != LOOK_ONCE);
676  | 
677  |     flags.verbose = save_verbose;
678  |     return 0;
679  | }
680  | 
681  | 
682  | int
683  | dowhatis()
684  | {
685  | 	return do_look(FALSE);
686  | }
687  | 
688  | int
689  | doquickwhatis()
690  | {
691  | 	return do_look(TRUE);
692  | }
693  | 
694  | int
695  | doidtrap()
696  | {
697  | 	register struct trap *trap;
698  | 	int x, y, tt;
699  | 
700  | 	if (!getdir((char *)0)) return 0;
701  | 	x = u.ux + u.dx;
702  | 	y = u.uy + u.dy;
703  | 	for (trap = ftrap; trap; trap = trap->ntrap)
704  | 	    if (trap->tx == x && trap->ty == y) {
705  | 		if (!trap->tseen) break;
706  | 		tt = trap->ttyp;
707  | 		if (u.dz) {
708  | 		    if (u.dz < 0 ? (tt == TRAPDOOR || tt == HOLE) :
709  | 			    tt == ROCKTRAP) break;
710  | 		}
711  | 		tt = what_trap(tt);
712  | 		pline("That is %s%s%s.",
713  | 		      an(defsyms[trap_to_defsym(tt)].explanation),
714  | 		      !trap->madeby_u ? "" : (tt == WEB) ? " woven" :
715  | 			  /* trap doors & spiked pits can't be made by
716  | 			     player, and should be considered at least
717  | 			     as much "set" as "dug" anyway */
718  | 			  (tt == HOLE || tt == PIT) ? " dug" : " set",
719  | 		      !trap->madeby_u ? "" : " by you");
720  | 		return 0;
721  | 	    }
722  | 	pline("I can't see a trap there.");
723  | 	return 0;
724  | }
725  | 
726  | int
727  | dowhatdoes()
728  | {
729  | 	dlb *fp;
730  | 	char bufr[BUFSZ+6];
731  | 	register char *buf = &bufr[6], *ep, q, ctrl, meta;
732  | 
733  | 	fp = dlb_fopen(CMDHELPFILE, "r");
734  | 	if (!fp) {
735  | 		pline("Cannot open data file!");
736  | 		return 0;
737  | 	}
738  | 
739  | #if defined(UNIX) || defined(VMS)
740  | 	introff();
741  | #endif
742  | 	q = yn_function("What command?", (char *)0, '\0');
743  | #if defined(UNIX) || defined(VMS)
744  | 	intron();
745  | #endif
746  | 	ctrl = ((q <= '\033') ? (q - 1 + 'A') : 0);
747  | 	meta = ((0x80 & q) ? (0x7f & q) : 0);
748  | 	while(dlb_fgets(buf,BUFSZ,fp))
749  | 	    if ((ctrl && *buf=='^' && *(buf+1)==ctrl) ||
750  | 		(meta && *buf=='M' && *(buf+1)=='-' && *(buf+2)==meta) ||
751  | 		*buf==q) {
752  | 		ep = index(buf, '\n');
753  | 		if(ep) *ep = 0;
754  | 		if (ctrl && buf[2] == '\t'){
755  | 			buf = bufr + 1;
756  | 			(void) strncpy(buf, "^?      ", 8);
757  | 			buf[1] = ctrl;
758  | 		} else if (meta && buf[3] == '\t'){
759  | 			buf = bufr + 2;
760  | 			(void) strncpy(buf, "M-?     ", 8);
761  | 			buf[2] = meta;
762  | 		} else if(buf[1] == '\t'){
763  | 			buf = bufr;
764  | 			buf[0] = q;
765  | 			(void) strncpy(buf+1, "       ", 7);
766  | 		}
767  | 		pline("%s", buf);
768  | 		(void) dlb_fclose(fp);
769  | 		return 0;
770  | 	    }
771  | 	pline("I've never heard of such commands.");
772  | 	(void) dlb_fclose(fp);
773  | 	return 0;
774  | }
775  | 
776  | /* data for help_menu() */
777  | static const char *help_menu_items[] = {
778  | /* 0*/	"Long description of the game and commands.",
779  | /* 1*/	"List of game commands.",
780  | /* 2*/	"Concise history of NetHack.",
781  | /* 3*/	"Info on a character in the game display.",
782  | /* 4*/	"Info on what a given key does.",
783  | /* 5*/	"List of game options.",
784  | /* 6*/	"Longer explanation of game options.",
785  | /* 7*/	"List of extended commands.",
786  | /* 8*/	"The NetHack license.",
787  | #ifdef PORT_HELP
788  | 	"%s-specific help and commands.",
789  | #define PORT_HELP_ID 100
790  | #define WIZHLP_SLOT 10
791  | #else
792  | #define WIZHLP_SLOT 9
793  | #endif
794  | #ifdef WIZARD
795  | 	"List of wizard-mode commands.",
796  | #endif
797  | 	"",
798  | 	(char *)0
799  | };
800  | 
801  | STATIC_OVL boolean
802  | help_menu(sel)
803  | 	int *sel;
804  | {
805  | 	winid tmpwin = create_nhwindow(NHW_MENU);
806  | #ifdef PORT_HELP
807  | 	char helpbuf[QBUFSZ];
808  | #endif
809  | 	int i, n;
810  | 	menu_item *selected;
811  | 	anything any;
812  | 
813  | 	any.a_void = 0;		/* zero all bits */
814  | 	start_menu(tmpwin);
815  | #ifdef WIZARD
816  | 	if (!wizard) help_menu_items[WIZHLP_SLOT] = "",
817  | 		     help_menu_items[WIZHLP_SLOT+1] = (char *)0;
818  | #endif
819  | 	for (i = 0; help_menu_items[i]; i++)
820  | #ifdef PORT_HELP
821  | 	    /* port-specific line has a %s in it for the PORT_ID */
822  | 	    if (help_menu_items[i][0] == '%') {
823  | 		Sprintf(helpbuf, help_menu_items[i], PORT_ID);
824  | 		any.a_int = PORT_HELP_ID + 1;
825  | 		add_menu(tmpwin, NO_GLYPH, &any, 0, 0, ATR_NONE,
826  | 			 helpbuf, MENU_UNSELECTED);
827  | 	    } else
828  | #endif
829  | 	    {
830  | 		any.a_int = (*help_menu_items[i]) ? i+1 : 0;
831  | 		add_menu(tmpwin, NO_GLYPH, &any, 0, 0,
832  | 			ATR_NONE, help_menu_items[i], MENU_UNSELECTED);
833  | 	    }
834  | 	end_menu(tmpwin, "Select one item:");
835  | 	n = select_menu(tmpwin, PICK_ONE, &selected);
836  | 	destroy_nhwindow(tmpwin);
837  | 	if (n > 0) {
838  | 	    *sel = selected[0].item.a_int - 1;
839  | 	    free((genericptr_t)selected);
840  | 	    return TRUE;
841  | 	}
842  | 	return FALSE;
843  | }
844  | 
845  | int
846  | dohelp()
847  | {
848  | 	int sel = 0;
849  | 
850  | 	if (help_menu(&sel)) {
851  | 		switch (sel) {
852  | 			case  0:  display_file(HELP, TRUE);  break;
853  | 			case  1:  display_file(SHELP, TRUE);  break;
854  | 			case  2:  (void) dohistory();  break;
855  | 			case  3:  (void) dowhatis();  break;
856  | 			case  4:  (void) dowhatdoes();  break;
857  | 			case  5:  option_help();  break;
858  | 			case  6:  display_file(OPTIONFILE, TRUE);  break;
859  | 			case  7:  (void) doextlist();  break;
860  | 			case  8:  display_file(LICENSE, TRUE);  break;
861  | #ifdef WIZARD
862  | 			/* handle slot 9 or 10 */
863  | 			default: display_file(DEBUGHELP, TRUE);  break;
864  | #endif
865  | #ifdef PORT_HELP
866  | 			case PORT_HELP_ID:  port_help();  break;
867  | #endif
868  | 		}
869  | 	}
870  | 	return 0;
871  | }
872  | 
873  | int
874  | dohistory()
875  | {
876  | 	display_file(HISTORY, TRUE);
877  | 	return 0;
878  | }
879  | 
880  | /*pager.c*/