Give feedback just before timed levitation runs out
[aNetHack.git] / src / pager.c
blob184027ba053fe6332da4b2c392186e42c933eba1
1 /* NetHack 3.6 pager.c $NHDT-Date: 1471112245 2016/08/13 18:17:25 $ $NHDT-Branch: NetHack-3.6.0 $:$NHDT-Revision: 1.112 $ */
2 /* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */
3 /* NetHack may be freely redistributed. See license for details. */
5 /* This file contains the command routines dowhatis() and dohelp() and */
6 /* a few other help related facilities */
8 #include "hack.h"
9 #include "dlb.h"
11 STATIC_DCL boolean FDECL(is_swallow_sym, (int));
12 STATIC_DCL int FDECL(append_str, (char *, const char *));
13 STATIC_DCL void FDECL(look_at_object, (char *, int, int, int));
14 STATIC_DCL void FDECL(look_at_monster, (char *, char *,
15 struct monst *, int, int));
16 STATIC_DCL struct permonst *FDECL(lookat, (int, int, char *, char *));
17 STATIC_DCL void FDECL(checkfile, (char *, struct permonst *,
18 BOOLEAN_P, BOOLEAN_P));
19 STATIC_DCL void FDECL(look_all, (BOOLEAN_P,BOOLEAN_P));
20 STATIC_DCL void NDECL(whatdoes_help);
21 STATIC_DCL void NDECL(docontact);
22 STATIC_DCL void NDECL(dispfile_help);
23 STATIC_DCL void NDECL(dispfile_shelp);
24 STATIC_DCL void NDECL(dispfile_optionfile);
25 STATIC_DCL void NDECL(dispfile_license);
26 STATIC_DCL void NDECL(dispfile_debughelp);
27 STATIC_DCL void NDECL(hmenu_doextversion);
28 STATIC_DCL void NDECL(hmenu_dohistory);
29 STATIC_DCL void NDECL(hmenu_dowhatis);
30 STATIC_DCL void NDECL(hmenu_dowhatdoes);
31 STATIC_DCL void NDECL(hmenu_doextlist);
32 #ifdef PORT_HELP
33 extern void NDECL(port_help);
34 #endif
36 /* Returns "true" for characters that could represent a monster's stomach. */
37 STATIC_OVL boolean
38 is_swallow_sym(c)
39 int c;
41 int i;
43 for (i = S_sw_tl; i <= S_sw_br; i++)
44 if ((int) showsyms[i] == c)
45 return TRUE;
46 return FALSE;
50 * Append new_str to the end of buf if new_str doesn't already exist as
51 * a substring of buf. Return 1 if the string was appended, 0 otherwise.
52 * It is expected that buf is of size BUFSZ.
54 STATIC_OVL int
55 append_str(buf, new_str)
56 char *buf;
57 const char *new_str;
59 int space_left; /* space remaining in buf */
61 if (strstri(buf, new_str))
62 return 0;
64 space_left = BUFSZ - strlen(buf) - 1;
65 if (space_left < 1)
66 return 0;
67 (void) strncat(buf, " or ", space_left);
68 (void) strncat(buf, new_str, space_left - 4);
69 return 1;
72 /* shared by monster probing (via query_objlist!) as well as lookat() */
73 char *
74 self_lookat(outbuf)
75 char *outbuf;
77 char race[QBUFSZ];
79 /* include race with role unless polymorphed */
80 race[0] = '\0';
81 if (!Upolyd)
82 Sprintf(race, "%s ", urace.adj);
83 Sprintf(outbuf, "%s%s%s called %s",
84 /* being blinded may hide invisibility from self */
85 (Invis && (senseself() || !Blind)) ? "invisible " : "", race,
86 mons[u.umonnum].mname, plname);
87 if (u.usteed)
88 Sprintf(eos(outbuf), ", mounted on %s", y_monnam(u.usteed));
89 if (u.uundetected || (Upolyd && youmonst.m_ap_type))
90 mhidden_description(&youmonst, FALSE, eos(outbuf));
91 return outbuf;
94 /* describe a hidden monster; used for look_at during extended monster
95 detection and for probing; also when looking at self */
96 void
97 mhidden_description(mon, altmon, outbuf)
98 struct monst *mon;
99 boolean altmon; /* for probing: if mimicking a monster, say so */
100 char *outbuf;
102 struct obj *otmp;
103 boolean fakeobj, isyou = (mon == &youmonst);
104 int x = isyou ? u.ux : mon->mx, y = isyou ? u.uy : mon->my,
105 glyph = (level.flags.hero_memory && !isyou) ? levl[x][y].glyph
106 : glyph_at(x, y);
108 *outbuf = '\0';
109 if (mon->m_ap_type == M_AP_FURNITURE
110 || mon->m_ap_type == M_AP_OBJECT) {
111 Strcpy(outbuf, ", mimicking ");
112 if (mon->m_ap_type == M_AP_FURNITURE) {
113 Strcat(outbuf, an(defsyms[mon->mappearance].explanation));
114 } else if (mon->m_ap_type == M_AP_OBJECT
115 /* remembered glyph, not glyph_at() which is 'mon' */
116 && glyph_is_object(glyph)) {
117 objfrommap:
118 otmp = (struct obj *) 0;
119 fakeobj = object_from_map(glyph, x, y, &otmp);
120 Strcat(outbuf, (otmp && otmp->otyp != STRANGE_OBJECT)
121 ? ansimpleoname(otmp)
122 : an(obj_descr[STRANGE_OBJECT].oc_name));
123 if (fakeobj)
124 dealloc_obj(otmp);
125 } else {
126 Strcat(outbuf, something);
128 } else if (mon->m_ap_type == M_AP_MONSTER) {
129 if (altmon)
130 Sprintf(outbuf, ", masquerading as %s",
131 an(mons[mon->mappearance].mname));
132 } else if (isyou ? u.uundetected : mon->mundetected) {
133 Strcpy(outbuf, ", hiding");
134 if (hides_under(mon->data)) {
135 Strcat(outbuf, " under ");
136 /* remembered glyph, not glyph_at() which is 'mon' */
137 if (glyph_is_object(glyph))
138 goto objfrommap;
139 Strcat(outbuf, something);
140 } else if (is_hider(mon->data)) {
141 Sprintf(eos(outbuf), " on the %s",
142 (is_flyer(mon->data) || mon->data->mlet == S_PIERCER)
143 ? "ceiling"
144 : surface(x, y)); /* trapper */
145 } else {
146 if (mon->data->mlet == S_EEL && is_pool(x, y))
147 Strcat(outbuf, " in murky water");
152 /* extracted from lookat(); also used by namefloorobj() */
153 boolean
154 object_from_map(glyph, x, y, obj_p)
155 int glyph, x, y;
156 struct obj **obj_p;
158 boolean fakeobj = FALSE;
159 struct monst *mtmp;
160 struct obj *otmp;
161 int glyphotyp = glyph_to_obj(glyph);
163 *obj_p = (struct obj *) 0;
164 /* TODO: check inside containers in case glyph came from detection */
165 if ((otmp = sobj_at(glyphotyp, x, y)) == 0)
166 for (otmp = level.buriedobjlist; otmp; otmp = otmp->nobj)
167 if (otmp->ox == x && otmp->oy == y && otmp->otyp == glyphotyp)
168 break;
170 /* there might be a mimic here posing as an object */
171 mtmp = m_at(x, y);
172 if (mtmp && is_obj_mappear(mtmp, (unsigned) glyphotyp))
173 otmp = 0;
174 else
175 mtmp = 0;
177 if (!otmp || otmp->otyp != glyphotyp) {
178 /* this used to exclude STRANGE_OBJECT; now caller deals with it */
179 otmp = mksobj(glyphotyp, FALSE, FALSE);
180 if (!otmp)
181 return FALSE;
182 fakeobj = TRUE;
183 if (otmp->oclass == COIN_CLASS)
184 otmp->quan = 2L; /* to force pluralization */
185 else if (otmp->otyp == SLIME_MOLD)
186 otmp->spe = context.current_fruit; /* give it a type */
187 if (mtmp && has_mcorpsenm(mtmp)) /* mimic as corpse/statue */
188 otmp->corpsenm = MCORPSENM(mtmp);
190 /* if located at adjacent spot, mark it as having been seen up close
191 (corpse type will be known even if dknown is 0, so we don't need a
192 touch check for cockatrice corpse--we're looking without touching) */
193 if (otmp && distu(x, y) <= 2 && !Blind && !Hallucination
194 /* redundant: we only look for an object which matches current
195 glyph among floor and buried objects; when !Blind, any buried
196 object's glyph will have been replaced by whatever is present
197 on the surface as soon as we moved next to its spot */
198 && otmp->where == OBJ_FLOOR /* not buried */
199 /* terrain mode views what's already known, doesn't learn new stuff */
200 && !iflags.terrainmode) /* so don't set dknown when in terrain mode */
201 otmp->dknown = 1; /* if a pile, clearly see the top item only */
203 *obj_p = otmp;
204 return fakeobj; /* when True, caller needs to dealloc *obj_p */
207 STATIC_OVL void
208 look_at_object(buf, x, y, glyph)
209 char *buf; /* output buffer */
210 int x, y, glyph;
212 struct obj *otmp = 0;
213 boolean fakeobj = object_from_map(glyph, x, y, &otmp);
215 if (otmp) {
216 Strcpy(buf, (otmp->otyp != STRANGE_OBJECT)
217 ? distant_name(otmp, doname_vague_quan)
218 : obj_descr[STRANGE_OBJECT].oc_name);
219 if (fakeobj)
220 dealloc_obj(otmp), otmp = 0;
221 } else
222 Strcpy(buf, something); /* sanity precaution */
224 if (otmp && otmp->where == OBJ_BURIED)
225 Strcat(buf, " (buried)");
226 else if (levl[x][y].typ == STONE || levl[x][y].typ == SCORR)
227 Strcat(buf, " embedded in stone");
228 else if (IS_WALL(levl[x][y].typ) || levl[x][y].typ == SDOOR)
229 Strcat(buf, " embedded in a wall");
230 else if (closed_door(x, y))
231 Strcat(buf, " embedded in a door");
232 else if (is_pool(x, y))
233 Strcat(buf, " in water");
234 else if (is_lava(x, y))
235 Strcat(buf, " in molten lava"); /* [can this ever happen?] */
236 return;
239 STATIC_OVL void
240 look_at_monster(buf, monbuf, mtmp, x, y)
241 char *buf, *monbuf; /* buf: output, monbuf: optional output */
242 struct monst *mtmp;
243 int x, y;
245 char *name, monnambuf[BUFSZ];
246 boolean accurate = !Hallucination;
248 name = (mtmp->data == &mons[PM_COYOTE] && accurate)
249 ? coyotename(mtmp, monnambuf)
250 : distant_monnam(mtmp, ARTICLE_NONE, monnambuf);
251 Sprintf(buf, "%s%s%s",
252 (mtmp->mx != x || mtmp->my != y)
253 ? ((mtmp->isshk && accurate) ? "tail of " : "tail of a ")
254 : "",
255 (mtmp->mtame && accurate)
256 ? "tame "
257 : (mtmp->mpeaceful && accurate)
258 ? "peaceful "
259 : "",
260 name);
261 if (u.ustuck == mtmp) {
262 if (u.uswallow || iflags.save_uswallow) /* monster detection */
263 Strcat(buf, is_animal(mtmp->data)
264 ? ", swallowing you" : ", engulfing you");
265 else
266 Strcat(buf, (Upolyd && sticks(youmonst.data))
267 ? ", being held" : ", holding you");
269 if (mtmp->mleashed)
270 Strcat(buf, ", leashed to you");
272 if (mtmp->mtrapped && cansee(mtmp->mx, mtmp->my)) {
273 struct trap *t = t_at(mtmp->mx, mtmp->my);
274 int tt = t ? t->ttyp : NO_TRAP;
276 /* newsym lets you know of the trap, so mention it here */
277 if (tt == BEAR_TRAP || tt == PIT || tt == SPIKED_PIT || tt == WEB)
278 Sprintf(eos(buf), ", trapped in %s",
279 an(defsyms[trap_to_defsym(tt)].explanation));
282 /* we know the hero sees a monster at this location, but if it's shown
283 due to persistant monster detection he might remember something else */
284 if (mtmp->mundetected || mtmp->m_ap_type)
285 mhidden_description(mtmp, FALSE, eos(buf));
287 if (monbuf) {
288 unsigned how_seen = howmonseen(mtmp);
290 monbuf[0] = '\0';
291 if (how_seen != 0 && how_seen != MONSEEN_NORMAL) {
292 if (how_seen & MONSEEN_NORMAL) {
293 Strcat(monbuf, "normal vision");
294 how_seen &= ~MONSEEN_NORMAL;
295 /* how_seen can't be 0 yet... */
296 if (how_seen)
297 Strcat(monbuf, ", ");
299 if (how_seen & MONSEEN_SEEINVIS) {
300 Strcat(monbuf, "see invisible");
301 how_seen &= ~MONSEEN_SEEINVIS;
302 if (how_seen)
303 Strcat(monbuf, ", ");
305 if (how_seen & MONSEEN_INFRAVIS) {
306 Strcat(monbuf, "infravision");
307 how_seen &= ~MONSEEN_INFRAVIS;
308 if (how_seen)
309 Strcat(monbuf, ", ");
311 if (how_seen & MONSEEN_TELEPAT) {
312 Strcat(monbuf, "telepathy");
313 how_seen &= ~MONSEEN_TELEPAT;
314 if (how_seen)
315 Strcat(monbuf, ", ");
317 if (how_seen & MONSEEN_XRAYVIS) {
318 /* Eyes of the Overworld */
319 Strcat(monbuf, "astral vision");
320 how_seen &= ~MONSEEN_XRAYVIS;
321 if (how_seen)
322 Strcat(monbuf, ", ");
324 if (how_seen & MONSEEN_DETECT) {
325 Strcat(monbuf, "monster detection");
326 how_seen &= ~MONSEEN_DETECT;
327 if (how_seen)
328 Strcat(monbuf, ", ");
330 if (how_seen & MONSEEN_WARNMON) {
331 if (Hallucination)
332 Strcat(monbuf, "paranoid delusion");
333 else
334 Sprintf(eos(monbuf), "warned of %s",
335 makeplural(mtmp->data->mname));
336 how_seen &= ~MONSEEN_WARNMON;
337 if (how_seen)
338 Strcat(monbuf, ", ");
340 /* should have used up all the how_seen bits by now */
341 if (how_seen) {
342 impossible("lookat: unknown method of seeing monster");
343 Sprintf(eos(monbuf), "(%u)", how_seen);
345 } /* seen by something other than normal vision */
346 } /* monbuf is non-null */
350 * Return the name of the glyph found at (x,y).
351 * If not hallucinating and the glyph is a monster, also monster data.
353 STATIC_OVL struct permonst *
354 lookat(x, y, buf, monbuf)
355 int x, y;
356 char *buf, *monbuf;
358 struct monst *mtmp = (struct monst *) 0;
359 struct permonst *pm = (struct permonst *) 0;
360 int glyph;
362 buf[0] = monbuf[0] = '\0';
363 glyph = glyph_at(x, y);
364 if (u.ux == x && u.uy == y && canspotself()
365 && !(iflags.save_uswallow && glyph == mon_to_glyph(u.ustuck))
366 && (!iflags.terrainmode || (iflags.terrainmode & TER_MON) != 0)) {
367 /* fill in buf[] */
368 (void) self_lookat(buf);
370 /* file lookup can't distinguish between "gnomish wizard" monster
371 and correspondingly named player character, always picking the
372 former; force it to find the general "wizard" entry instead */
373 if (Role_if(PM_WIZARD) && Race_if(PM_GNOME) && !Upolyd)
374 pm = &mons[PM_WIZARD];
376 /* When you see yourself normally, no explanation is appended
377 (even if you could also see yourself via other means).
378 Sensing self while blind or swallowed is treated as if it
379 were by normal vision (cf canseeself()). */
380 if ((Invisible || u.uundetected) && !Blind
381 && !(u.uswallow || iflags.save_uswallow)) {
382 unsigned how = 0;
384 if (Infravision)
385 how |= 1;
386 if (Unblind_telepat)
387 how |= 2;
388 if (Detect_monsters)
389 how |= 4;
391 if (how)
392 Sprintf(eos(buf), " [seen: %s%s%s%s%s]",
393 (how & 1) ? "infravision" : "",
394 /* add comma if telep and infrav */
395 ((how & 3) > 2) ? ", " : "",
396 (how & 2) ? "telepathy" : "",
397 /* add comma if detect and (infrav or telep or both) */
398 ((how & 7) > 4) ? ", " : "",
399 (how & 4) ? "monster detection" : "");
401 } else if (u.uswallow) {
402 /* when swallowed, we're only called for spots adjacent to hero,
403 and blindness doesn't prevent hero from feeling what holds him */
404 Sprintf(buf, "interior of %s", a_monnam(u.ustuck));
405 pm = u.ustuck->data;
406 } else if (glyph_is_monster(glyph)) {
407 bhitpos.x = x;
408 bhitpos.y = y;
409 if ((mtmp = m_at(x, y)) != 0) {
410 look_at_monster(buf, monbuf, mtmp, x, y);
411 pm = mtmp->data;
413 } else if (glyph_is_object(glyph)) {
414 look_at_object(buf, x, y, glyph); /* fill in buf[] */
415 } else if (glyph_is_trap(glyph)) {
416 int tnum = what_trap(glyph_to_trap(glyph));
418 /* Trap detection displays a bear trap at locations having
419 * a trapped door or trapped container or both.
420 * TODO: we should create actual trap types for doors and
421 * chests so that they can have their own glyphs and tiles.
423 if (trapped_chest_at(tnum, x, y))
424 Strcpy(buf, "trapped chest"); /* might actually be a large box */
425 else if (trapped_door_at(tnum, x, y))
426 Strcpy(buf, "trapped door"); /* not "trap door"... */
427 else
428 Strcpy(buf, defsyms[trap_to_defsym(tnum)].explanation);
429 } else if (glyph_is_warning(glyph)) {
430 int warnindx = glyph_to_warning(glyph);
432 Strcpy(buf, def_warnsyms[warnindx].explanation);
433 } else if (!glyph_is_cmap(glyph)) {
434 Strcpy(buf, "unexplored area");
435 } else
436 switch (glyph_to_cmap(glyph)) {
437 case S_altar:
438 Sprintf(buf, "%s %saltar",
439 /* like endgame high priests, endgame high altars
440 are only recognizable when immediately adjacent */
441 (Is_astralevel(&u.uz) && distu(x, y) > 2)
442 ? "aligned"
443 : align_str(
444 Amask2align(levl[x][y].altarmask & ~AM_SHRINE)),
445 ((levl[x][y].altarmask & AM_SHRINE)
446 && (Is_astralevel(&u.uz) || Is_sanctum(&u.uz)))
447 ? "high "
448 : "");
449 break;
450 case S_ndoor:
451 if (is_drawbridge_wall(x, y) >= 0)
452 Strcpy(buf, "open drawbridge portcullis");
453 else if ((levl[x][y].doormask & ~D_TRAPPED) == D_BROKEN)
454 Strcpy(buf, "broken door");
455 else
456 Strcpy(buf, "doorway");
457 break;
458 case S_cloud:
459 Strcpy(buf,
460 Is_airlevel(&u.uz) ? "cloudy area" : "fog/vapor cloud");
461 break;
462 case S_stone:
463 if (!levl[x][y].seenv) {
464 Strcpy(buf, "unexplored");
465 break;
466 } else if (Underwater && !Is_waterlevel(&u.uz)) {
467 /* "unknown" == previously mapped but not visible when
468 submerged; better terminology appreciated... */
469 Strcpy(buf, (distu(x, y) <= 2) ? "land" : "unknown");
470 break;
471 } else if (levl[x][y].typ == STONE || levl[x][y].typ == SCORR) {
472 Strcpy(buf, "stone");
473 break;
475 /*else FALLTHRU*/
476 default:
477 Strcpy(buf, defsyms[glyph_to_cmap(glyph)].explanation);
478 break;
481 return (pm && !Hallucination) ? pm : (struct permonst *) 0;
485 * Look in the "data" file for more info. Called if the user typed in the
486 * whole name (user_typed_name == TRUE), or we've found a possible match
487 * with a character/glyph and flags.help is TRUE.
489 * NOTE: when (user_typed_name == FALSE), inp is considered read-only and
490 * must not be changed directly, e.g. via lcase(). We want to force
491 * lcase() for data.base lookup so that we can have a clean key.
492 * Therefore, we create a copy of inp _just_ for data.base lookup.
494 STATIC_OVL void
495 checkfile(inp, pm, user_typed_name, without_asking)
496 char *inp;
497 struct permonst *pm;
498 boolean user_typed_name, without_asking;
500 dlb *fp;
501 char buf[BUFSZ], newstr[BUFSZ], givenname[BUFSZ];
502 char *ep, *dbase_str;
503 unsigned long txt_offset = 0L;
504 int chk_skip, pass = 1;
505 boolean found_in_file = FALSE, skipping_entry = FALSE, yes_to_moreinfo;
506 winid datawin = WIN_ERR;
508 fp = dlb_fopen(DATAFILE, "r");
509 if (!fp) {
510 pline("Cannot open data file!");
511 return;
515 * If someone passed us garbage, prevent fault.
517 if (!inp || (inp && strlen(inp) > (BUFSZ - 1))) {
518 pline("bad do_look buffer passed!");
519 return;
522 /* To prevent the need for entries in data.base like *ngel to account
523 * for Angel and angel, make the lookup string the same for both
524 * user_typed_name and picked name.
526 if (pm != (struct permonst *) 0 && !user_typed_name)
527 dbase_str = strcpy(newstr, pm->mname);
528 else
529 dbase_str = strcpy(newstr, inp);
530 (void) lcase(dbase_str);
532 if (!strncmp(dbase_str, "interior of ", 12))
533 dbase_str += 12;
534 if (!strncmp(dbase_str, "a ", 2))
535 dbase_str += 2;
536 else if (!strncmp(dbase_str, "an ", 3))
537 dbase_str += 3;
538 else if (!strncmp(dbase_str, "the ", 4))
539 dbase_str += 4;
540 if (!strncmp(dbase_str, "tame ", 5))
541 dbase_str += 5;
542 else if (!strncmp(dbase_str, "peaceful ", 9))
543 dbase_str += 9;
544 if (!strncmp(dbase_str, "invisible ", 10))
545 dbase_str += 10;
546 if (!strncmp(dbase_str, "saddled ", 8))
547 dbase_str += 8;
548 if (!strncmp(dbase_str, "statue of ", 10))
549 dbase_str[6] = '\0';
550 else if (!strncmp(dbase_str, "figurine of ", 12))
551 dbase_str[8] = '\0';
553 /* Make sure the name is non-empty. */
554 if (*dbase_str) {
555 /* adjust the input to remove "named " and convert to lower case */
556 char *alt = 0; /* alternate description */
558 if ((ep = strstri(dbase_str, " named ")) != 0)
559 alt = ep + 7;
560 else if ((ep = strstri(dbase_str, " called ")) != 0) {
561 if (strlen(ep + 8) < BUFSZ - 1) {
562 Strcpy(givenname, ep + 8);
563 givenname[BUFSZ-1] = '\0';
564 alt = &givenname[0];
567 if (!ep)
568 ep = strstri(dbase_str, ", ");
569 if (ep && ep > dbase_str)
570 *ep = '\0';
573 * If the object is named, then the name is the alternate description;
574 * otherwise, the result of makesingular() applied to the name is.
575 * This isn't strictly optimal, but named objects of interest to the
576 * user will usually be found under their name, rather than under
577 * their object type, so looking for a singular form is pointless.
579 if (!alt)
580 alt = makesingular(dbase_str);
582 if (!strcmp(alt, dbase_str))
583 pass = 0;
585 for (; pass >= 0; pass--) {
586 txt_offset = 0L;
587 if (dlb_fseek(fp, txt_offset, SEEK_SET) < 0 ) {
588 impossible("can't get to start of 'data' file");
589 dlb_fclose(fp);
590 return;
592 /* skip first record; read second */
593 if (!dlb_fgets(buf, BUFSZ, fp) || !dlb_fgets(buf, BUFSZ, fp)) {
594 impossible("can't read 'data' file");
595 (void) dlb_fclose(fp);
596 return;
597 } else if (sscanf(buf, "%8lx\n", &txt_offset) < 1
598 || txt_offset == 0L)
599 goto bad_data_file;
601 /* look for the appropriate entry */
602 while (dlb_fgets(buf, BUFSZ, fp)) {
603 if (*buf == '.')
604 break; /* we passed last entry without success */
606 if (digit(*buf)) {
607 /* a number indicates the end of current entry */
608 skipping_entry = FALSE;
609 } else if (!skipping_entry) {
610 if (!(ep = index(buf, '\n')))
611 goto bad_data_file;
612 (void) strip_newline((ep > buf) ? ep - 1 : ep);
613 /* if we match a key that begins with "~", skip
614 this entry */
615 chk_skip = (*buf == '~') ? 1 : 0;
616 if ((pass == 0 && pmatch(&buf[chk_skip], dbase_str))
617 || (pass == 1 && alt && pmatch(&buf[chk_skip], alt))) {
618 if (chk_skip) {
619 skipping_entry = TRUE;
620 continue;
621 } else {
622 found_in_file = TRUE;
623 break;
628 if (found_in_file) {
629 long entry_offset;
630 int entry_count;
631 int i;
633 /* skip over other possible matches for the info */
634 do {
635 if (!dlb_fgets(buf, BUFSZ, fp))
636 goto bad_data_file;
637 } while (!digit(*buf));
638 if (sscanf(buf, "%ld,%d\n", &entry_offset, &entry_count) < 2) {
639 bad_data_file:
640 impossible("'data' file in wrong format or corrupted");
641 /* window will exist if came here from below via 'goto' */
642 if (datawin != WIN_ERR)
643 destroy_nhwindow(datawin);
644 (void) dlb_fclose(fp);
645 return;
648 yes_to_moreinfo = FALSE;
649 if (!user_typed_name && !without_asking) {
650 unsigned maxt = strlen("More info about \"\"?");
651 char *entrytext = pass ? alt : dbase_str;
652 char question[BUFSZ];
654 if (strlen(entrytext) < BUFSZ - maxt) {
655 Strcpy(question, "More info about \"");
656 Strcat(question, entrytext);
657 Strcat(question, "\"?");
659 if (yn(question) == 'y')
660 yes_to_moreinfo = TRUE;
663 if (user_typed_name || without_asking || yes_to_moreinfo) {
664 if (dlb_fseek(fp, (long) txt_offset + entry_offset,
665 SEEK_SET) < 0) {
666 pline("? Seek error on 'data' file!");
667 (void) dlb_fclose(fp);
668 return;
670 datawin = create_nhwindow(NHW_MENU);
671 for (i = 0; i < entry_count; i++) {
672 if (!dlb_fgets(buf, BUFSZ, fp))
673 goto bad_data_file;
674 (void) strip_newline(buf);
675 if (index(buf + 1, '\t') != 0)
676 (void) tabexpand(buf + 1);
677 putstr(datawin, 0, buf + 1);
679 display_nhwindow(datawin, FALSE);
680 destroy_nhwindow(datawin);
682 } else if (user_typed_name && pass == 0)
683 pline("I don't have any information on those things.");
686 (void) dlb_fclose(fp);
690 do_screen_description(cc, looked, sym, out_str, firstmatch)
691 coord cc;
692 boolean looked;
693 int sym;
694 char *out_str;
695 const char **firstmatch;
697 static const char mon_interior[] = "the interior of a monster",
698 unreconnoitered[] = "unreconnoitered";
699 static char look_buf[BUFSZ];
700 char prefix[BUFSZ];
701 int i, alt_i, glyph = NO_GLYPH,
702 skipped_venom = 0, found = 0; /* count of matching syms found */
703 boolean hit_trap, need_to_look = FALSE,
704 submerged = (Underwater && !Is_waterlevel(&u.uz));
705 const char *x_str;
707 if (looked) {
708 int oc;
709 unsigned os;
711 glyph = glyph_at(cc.x, cc.y);
712 /* Convert glyph at selected position to a symbol for use below. */
713 (void) mapglyph(glyph, &sym, &oc, &os, cc.x, cc.y);
715 Sprintf(prefix, "%s ", encglyph(glyph));
716 } else
717 Sprintf(prefix, "%c ", sym);
720 * Check all the possibilities, saving all explanations in a buffer.
721 * When all have been checked then the string is printed.
725 * Handle restricted vision range (limited to adjacent spots when
726 * swallowed or underwater) cases first.
728 * 3.6.0 listed anywhere on map, other than self, as "interior
729 * of a monster" when swallowed, and non-adjacent water or
730 * non-water anywhere as "dark part of a room" when underwater.
731 * "unreconnoitered" is an attempt to convey "even if you knew
732 * what was there earlier, you don't know what is there in the
733 * current circumstance".
735 * (Note: 'self' will always be visible when swallowed so we don't
736 * need special swallow handling for <ux,uy>.
737 * Another note: for '#terrain' without monsters, u.uswallow and
738 * submerged will always both be False and skip this code.)
740 x_str = 0;
741 if (!looked) {
742 ; /* skip special handling */
743 } else if (((u.uswallow || submerged) && distu(cc.x, cc.y) > 2)
744 /* detection showing some category, so mostly background */
745 || ((iflags.terrainmode & (TER_DETECT | TER_MAP)) == TER_DETECT
746 && glyph == cmap_to_glyph(S_stone))) {
747 x_str = unreconnoitered;
748 need_to_look = FALSE;
749 } else if (is_swallow_sym(sym)) {
750 x_str = mon_interior;
751 need_to_look = TRUE; /* for specific monster type */
753 if (x_str) {
754 /* we know 'found' is zero here, but guard against some other
755 special case being inserted ahead of us someday */
756 if (!found) {
757 Sprintf(out_str, "%s%s", prefix, x_str);
758 *firstmatch = x_str;
759 found++;
760 } else {
761 found += append_str(out_str, x_str); /* not 'an(x_str)' */
763 /* for is_swallow_sym(), we want to list the current symbol's
764 other possibilities (wand for '/', throne for '\\', &c) so
765 don't jump to the end for the x_str==mon_interior case */
766 if (x_str == unreconnoitered)
767 goto didlook;
770 /* Check for monsters */
771 if (!iflags.terrainmode || (iflags.terrainmode & TER_MON) != 0) {
772 for (i = 0; i < MAXMCLASSES; i++) {
773 if (sym == (looked ? showsyms[i + SYM_OFF_M] : def_monsyms[i].sym)
774 && def_monsyms[i].explain) {
775 need_to_look = TRUE;
776 if (!found) {
777 Sprintf(out_str, "%s%s",
778 prefix, an(def_monsyms[i].explain));
779 *firstmatch = def_monsyms[i].explain;
780 found++;
781 } else {
782 found += append_str(out_str, an(def_monsyms[i].explain));
786 /* handle '@' as a special case if it refers to you and you're
787 playing a character which isn't normally displayed by that
788 symbol; firstmatch is assumed to already be set for '@' */
789 if ((looked ? (sym == showsyms[S_HUMAN + SYM_OFF_M]
790 && cc.x == u.ux && cc.y == u.uy)
791 : (sym == def_monsyms[S_HUMAN].sym && !flags.showrace))
792 && !(Race_if(PM_HUMAN) || Race_if(PM_ELF)) && !Upolyd)
793 found += append_str(out_str, "you"); /* tack on "or you" */
796 /* Now check for objects */
797 if (!iflags.terrainmode || (iflags.terrainmode & TER_OBJ) != 0) {
798 for (i = 1; i < MAXOCLASSES; i++) {
799 if (sym == (looked ? showsyms[i + SYM_OFF_O]
800 : def_oc_syms[i].sym)
801 || (looked && i == ROCK_CLASS && glyph_is_statue(glyph))) {
802 need_to_look = TRUE;
803 if (looked && i == VENOM_CLASS) {
804 skipped_venom++;
805 continue;
807 if (!found) {
808 Sprintf(out_str, "%s%s",
809 prefix, an(def_oc_syms[i].explain));
810 *firstmatch = def_oc_syms[i].explain;
811 found++;
812 } else {
813 found += append_str(out_str, an(def_oc_syms[i].explain));
819 if (sym == DEF_INVISIBLE) {
820 if (!found) {
821 Sprintf(out_str, "%s%s", prefix, an(invisexplain));
822 *firstmatch = invisexplain;
823 found++;
824 } else {
825 found += append_str(out_str, an(invisexplain));
829 /* Now check for graphics symbols */
830 alt_i = (sym == (looked ? showsyms[0] : defsyms[0].sym)) ? 0 : (2 + 1);
831 for (hit_trap = FALSE, i = 0; i < MAXPCHARS; i++) {
832 /* when sym is the default background character, we process
833 i == 0 three times: unexplored, stone, dark part of a room */
834 if (alt_i < 2) {
835 x_str = !alt_i++ ? "unexplored" : submerged ? "unknown" : "stone";
836 i = 0; /* for second iteration, undo loop increment */
837 /* alt_i is now 1 or 2 */
838 } else {
839 if (alt_i++ == 2)
840 i = 0; /* undo loop increment */
841 x_str = defsyms[i].explanation;
842 if (submerged && !strcmp(x_str, defsyms[0].explanation))
843 x_str = "land"; /* replace "dark part of a room" */
844 /* alt_i is now 3 or more and no longer of interest */
846 if (sym == (looked ? showsyms[i] : defsyms[i].sym) && *x_str) {
847 /* avoid "an unexplored", "an stone", "an air", "a water",
848 "a floor of a room", "a dark part of a room";
849 article==2 => "the", 1 => "an", 0 => (none) */
850 int article = strstri(x_str, " of a room") ? 2
851 : !(alt_i <= 2
852 || strcmp(x_str, "air") == 0
853 || strcmp(x_str, "land") == 0
854 || strcmp(x_str, "water") == 0);
856 if (!found) {
857 if (is_cmap_trap(i)) {
858 Sprintf(out_str, "%sa trap", prefix);
859 hit_trap = TRUE;
860 } else {
861 Sprintf(out_str, "%s%s", prefix,
862 article == 2 ? the(x_str)
863 : article == 1 ? an(x_str) : x_str);
865 *firstmatch = x_str;
866 found++;
867 } else if (!(hit_trap && is_cmap_trap(i))
868 && !(found >= 3 && is_cmap_drawbridge(i))
869 /* don't mention vibrating square outside of Gehennom
870 unless this happens to be one (hallucination?) */
871 && (i != S_vibrating_square || Inhell
872 || (looked && glyph_is_trap(glyph)
873 && glyph_to_trap(glyph) == VIBRATING_SQUARE))) {
874 found += append_str(out_str, (article == 2) ? the(x_str)
875 : (article == 1) ? an(x_str)
876 : x_str);
877 if (is_cmap_trap(i))
878 hit_trap = TRUE;
881 if (i == S_altar || is_cmap_trap(i))
882 need_to_look = TRUE;
886 /* Now check for warning symbols */
887 for (i = 1; i < WARNCOUNT; i++) {
888 x_str = def_warnsyms[i].explanation;
889 if (sym == (looked ? warnsyms[i] : def_warnsyms[i].sym)) {
890 if (!found) {
891 Sprintf(out_str, "%s%s", prefix, def_warnsyms[i].explanation);
892 *firstmatch = def_warnsyms[i].explanation;
893 found++;
894 } else {
895 found += append_str(out_str, def_warnsyms[i].explanation);
897 /* Kludge: warning trumps boulders on the display.
898 Reveal the boulder too or player can get confused */
899 if (looked && sobj_at(BOULDER, cc.x, cc.y))
900 Strcat(out_str, " co-located with a boulder");
901 break; /* out of for loop*/
905 /* if we ignored venom and list turned out to be short, put it back */
906 if (skipped_venom && found < 2) {
907 x_str = def_oc_syms[VENOM_CLASS].explain;
908 if (!found) {
909 Sprintf(out_str, "%s%s", prefix, an(x_str));
910 *firstmatch = x_str;
911 found++;
912 } else {
913 found += append_str(out_str, an(x_str));
917 /* handle optional boulder symbol as a special case */
918 if (iflags.bouldersym && sym == iflags.bouldersym) {
919 if (!found) {
920 *firstmatch = "boulder";
921 Sprintf(out_str, "%s%s", prefix, an(*firstmatch));
922 found++;
923 } else {
924 found += append_str(out_str, "boulder");
929 * If we are looking at the screen, follow multiple possibilities or
930 * an ambiguous explanation by something more detailed.
933 if (found > 4)
934 Sprintf(out_str, "%s", "That can be many things");
936 didlook:
937 if (looked) {
938 if (found > 1 || need_to_look) {
939 char monbuf[BUFSZ];
940 char temp_buf[BUFSZ];
942 (void) lookat(cc.x, cc.y, look_buf, monbuf);
943 *firstmatch = look_buf;
944 if (*(*firstmatch)) {
945 Sprintf(temp_buf, " (%s)", *firstmatch);
946 (void) strncat(out_str, temp_buf,
947 BUFSZ - strlen(out_str) - 1);
948 found = 1; /* we have something to look up */
950 if (monbuf[0]) {
951 Sprintf(temp_buf, " [seen: %s]", monbuf);
952 (void) strncat(out_str, temp_buf,
953 BUFSZ - strlen(out_str) - 1);
958 return found;
961 /* also used by getpos hack in do_name.c */
962 const char what_is_an_unknown_object[] = "an unknown object";
965 do_look(mode, click_cc)
966 int mode;
967 coord *click_cc;
969 boolean quick = (mode == 1); /* use cursor; don't search for "more info" */
970 boolean clicklook = (mode == 2); /* right mouse-click method */
971 char out_str[BUFSZ];
972 const char *firstmatch = 0;
973 struct permonst *pm = 0;
974 int i = '\0', ans = 0;
975 int sym; /* typed symbol or converted glyph */
976 int found; /* count of matching syms found */
977 coord cc; /* screen pos of unknown glyph */
978 boolean save_verbose; /* saved value of flags.verbose */
979 boolean from_screen; /* question from the screen */
981 if (!clicklook) {
982 if (quick) {
983 from_screen = TRUE; /* yes, we want to use the cursor */
984 i = 'y';
985 } else {
986 menu_item *pick_list = (menu_item *) 0;
987 winid win;
988 anything any;
990 any = zeroany;
991 win = create_nhwindow(NHW_MENU);
992 start_menu(win);
993 any.a_char = '/';
994 /* 'y' and 'n' to keep backwards compatibility with previous
995 versions: "Specify unknown object by cursor?" */
996 add_menu(win, NO_GLYPH, &any,
997 flags.lootabc ? 0 : any.a_char, 'y', ATR_NONE,
998 "something on the map", MENU_UNSELECTED);
999 any.a_char = 'i';
1000 add_menu(win, NO_GLYPH, &any,
1001 flags.lootabc ? 0 : any.a_char, 0, ATR_NONE,
1002 "something you're carrying", MENU_UNSELECTED);
1003 any.a_char = '?';
1004 add_menu(win, NO_GLYPH, &any,
1005 flags.lootabc ? 0 : any.a_char, 'n', ATR_NONE,
1006 "something else (by symbol or name)", MENU_UNSELECTED);
1007 if (!u.uswallow && !Hallucination) {
1008 any = zeroany;
1009 add_menu(win, NO_GLYPH, &any, 0, 0, ATR_NONE,
1010 "", MENU_UNSELECTED);
1011 /* these options work sensibly for the swallowed case,
1012 but there's no reason for the player to use them then;
1013 objects work fine when hallucinating, but screen
1014 symbol/monster class letter doesn't match up with
1015 bogus monster type, so suppress when hallucinating */
1016 any.a_char = 'm';
1017 add_menu(win, NO_GLYPH, &any,
1018 flags.lootabc ? 0 : any.a_char, 0, ATR_NONE,
1019 "nearby monsters", MENU_UNSELECTED);
1020 any.a_char = 'M';
1021 add_menu(win, NO_GLYPH, &any,
1022 flags.lootabc ? 0 : any.a_char, 0, ATR_NONE,
1023 "all monsters shown on map", MENU_UNSELECTED);
1024 any.a_char = 'o';
1025 add_menu(win, NO_GLYPH, &any,
1026 flags.lootabc ? 0 : any.a_char, 0, ATR_NONE,
1027 "nearby objects", MENU_UNSELECTED);
1028 any.a_char = 'O';
1029 add_menu(win, NO_GLYPH, &any,
1030 flags.lootabc ? 0 : any.a_char, 0, ATR_NONE,
1031 "all objects shown on map", MENU_UNSELECTED);
1033 end_menu(win, "What do you want to look at:");
1034 if (select_menu(win, PICK_ONE, &pick_list) > 0) {
1035 i = pick_list->item.a_char;
1036 free((genericptr_t) pick_list);
1038 destroy_nhwindow(win);
1041 switch (i) {
1042 default:
1043 case 'q':
1044 return 0;
1045 case 'y':
1046 case '/':
1047 from_screen = TRUE;
1048 sym = 0;
1049 cc.x = u.ux;
1050 cc.y = u.uy;
1051 break;
1052 case 'i':
1054 char invlet;
1055 struct obj *invobj;
1057 invlet = display_inventory((const char *) 0, TRUE);
1058 if (!invlet || invlet == '\033')
1059 return 0;
1060 *out_str = '\0';
1061 for (invobj = invent; invobj; invobj = invobj->nobj)
1062 if (invobj->invlet == invlet) {
1063 strcpy(out_str, singular(invobj, xname));
1064 break;
1066 if (*out_str)
1067 checkfile(out_str, pm, TRUE, TRUE);
1068 return 0;
1070 case '?':
1071 from_screen = FALSE;
1072 getlin("Specify what? (type the word)", out_str);
1073 if (strcmp(out_str, " ")) /* keep single space as-is */
1074 /* remove leading and trailing whitespace and
1075 condense consecutive internal whitespace */
1076 mungspaces(out_str);
1077 if (out_str[0] == '\0' || out_str[0] == '\033')
1078 return 0;
1080 if (out_str[1]) { /* user typed in a complete string */
1081 checkfile(out_str, pm, TRUE, TRUE);
1082 return 0;
1084 sym = out_str[0];
1085 break;
1086 case 'm':
1087 look_all(TRUE, TRUE); /* list nearby monsters */
1088 return 0;
1089 case 'M':
1090 look_all(FALSE, TRUE); /* list all monsters */
1091 return 0;
1092 case 'o':
1093 look_all(TRUE, FALSE); /* list nearby objects */
1094 return 0;
1095 case 'O':
1096 look_all(FALSE, FALSE); /* list all objects */
1097 return 0;
1099 } else { /* clicklook */
1100 cc.x = click_cc->x;
1101 cc.y = click_cc->y;
1102 sym = 0;
1103 from_screen = FALSE;
1106 /* Save the verbose flag, we change it later. */
1107 save_verbose = flags.verbose;
1108 flags.verbose = flags.verbose && !quick;
1110 * The user typed one letter, or we're identifying from the screen.
1112 do {
1113 /* Reset some variables. */
1114 pm = (struct permonst *) 0;
1115 found = 0;
1116 out_str[0] = '\0';
1118 if (from_screen || clicklook) {
1119 if (from_screen) {
1120 if (flags.verbose)
1121 pline("Please move the cursor to %s.",
1122 what_is_an_unknown_object);
1123 else
1124 pline("Pick an object.");
1126 ans = getpos(&cc, quick, what_is_an_unknown_object);
1127 if (ans < 0 || cc.x < 0) {
1128 flags.verbose = save_verbose;
1129 return 0; /* done */
1131 flags.verbose = FALSE; /* only print long question once */
1135 found = do_screen_description(cc, (from_screen || clicklook), sym,
1136 out_str, &firstmatch);
1138 /* Finally, print out our explanation. */
1139 if (found) {
1140 /* Used putmixed() because there may be an encoded glyph present
1142 putmixed(WIN_MESSAGE, 0, out_str);
1144 /* check the data file for information about this thing */
1145 if (found == 1 && ans != LOOK_QUICK && ans != LOOK_ONCE
1146 && (ans == LOOK_VERBOSE || (flags.help && !quick))
1147 && !clicklook) {
1148 char temp_buf[BUFSZ];
1150 Strcpy(temp_buf, firstmatch);
1151 checkfile(temp_buf, pm, FALSE,
1152 (boolean) (ans == LOOK_VERBOSE));
1154 } else {
1155 pline("I've never heard of such things.");
1158 } while (from_screen && !quick && ans != LOOK_ONCE && !clicklook);
1160 flags.verbose = save_verbose;
1161 return 0;
1164 STATIC_OVL void
1165 look_all(nearby, do_mons)
1166 boolean nearby; /* True => within BOLTLIM, False => entire map */
1167 boolean do_mons; /* True => monsters, False => objects */
1169 winid win;
1170 int x, y, lo_x, lo_y, hi_x, hi_y, glyph, count = 0;
1171 char lookbuf[BUFSZ], outbuf[BUFSZ];
1173 win = create_nhwindow(NHW_TEXT);
1174 lo_y = nearby ? max(u.uy - BOLT_LIM, 0) : 0;
1175 lo_x = nearby ? max(u.ux - BOLT_LIM, 1) : 1;
1176 hi_y = nearby ? min(u.uy + BOLT_LIM, ROWNO - 1) : ROWNO - 1;
1177 hi_x = nearby ? min(u.ux + BOLT_LIM, COLNO - 1) : COLNO - 1;
1178 for (y = lo_y; y <= hi_y; y++) {
1179 for (x = lo_x; x <= hi_x; x++) {
1180 lookbuf[0] = '\0';
1181 glyph = glyph_at(x, y);
1182 if (do_mons) {
1183 if (glyph_is_monster(glyph)) {
1184 struct monst *mtmp;
1186 bhitpos.x = x; /* [is this actually necessary?] */
1187 bhitpos.y = y;
1188 if (x == u.ux && y == u.uy && canspotself()) {
1189 (void) self_lookat(lookbuf);
1190 ++count;
1191 } else if ((mtmp = m_at(x, y)) != 0) {
1192 look_at_monster(lookbuf, (char *) 0, mtmp, x, y);
1193 ++count;
1195 } else if (glyph_is_invisible(glyph)) {
1196 /* remembered, unseen, creature */
1197 Strcpy(lookbuf, invisexplain);
1198 ++count;
1199 } else if (glyph_is_warning(glyph)) {
1200 int warnindx = glyph_to_warning(glyph);
1202 Strcpy(lookbuf, def_warnsyms[warnindx].explanation);
1203 ++count;
1205 } else { /* !do_mons */
1206 if (glyph_is_object(glyph)) {
1207 look_at_object(lookbuf, x, y, glyph);
1208 ++count;
1211 if (*lookbuf) {
1212 char coordbuf[20], which[12], cmode;
1214 cmode = (iflags.getpos_coords != GPCOORDS_NONE)
1215 ? iflags.getpos_coords : GPCOORDS_MAP;
1216 if (count == 1) {
1217 Strcpy(which, do_mons ? "monsters" : "objects");
1218 if (nearby)
1219 Sprintf(outbuf, "%s currently shown near %s:",
1220 upstart(which),
1221 (cmode != GPCOORDS_COMPASS)
1222 ? coord_desc(u.ux, u.uy, coordbuf, cmode)
1223 : !canspotself() ? "your position" : "you");
1224 else
1225 Sprintf(outbuf, "All %s currently shown on the map:",
1226 which);
1227 putstr(win, 0, outbuf);
1228 putstr(win, 0, "");
1230 /* prefix: "coords C " where 'C' is mon or obj symbol */
1231 Sprintf(outbuf, (cmode == GPCOORDS_SCREEN) ? "%s "
1232 : (cmode == GPCOORDS_MAP) ? "%8s "
1233 : "%12s ",
1234 coord_desc(x, y, coordbuf, cmode));
1235 Sprintf(eos(outbuf), "%s ", encglyph(glyph));
1236 /* guard against potential overflow */
1237 lookbuf[sizeof lookbuf - 1 - strlen(outbuf)] = '\0';
1238 Strcat(outbuf, lookbuf);
1239 putmixed(win, 0, outbuf);
1243 if (count)
1244 display_nhwindow(win, TRUE);
1245 else
1246 pline("No %s are currently shown %s.",
1247 do_mons ? "monsters" : "objects",
1248 nearby ? "nearby" : "on the map");
1249 destroy_nhwindow(win);
1252 /* the '/' command */
1254 dowhatis()
1256 return do_look(0, (coord *) 0);
1259 /* the ';' command */
1261 doquickwhatis()
1263 return do_look(1, (coord *) 0);
1266 /* the '^' command */
1268 doidtrap()
1270 register struct trap *trap;
1271 int x, y, tt, glyph;
1273 if (!getdir("^"))
1274 return 0;
1275 x = u.ux + u.dx;
1276 y = u.uy + u.dy;
1278 /* check fake bear trap from confused gold detection */
1279 glyph = glyph_at(x, y);
1280 if (glyph_is_trap(glyph) && (tt = glyph_to_trap(glyph)) == BEAR_TRAP) {
1281 boolean chesttrap = trapped_chest_at(tt, x, y);
1283 if (chesttrap || trapped_door_at(tt, x, y)) {
1284 pline("That is a trapped %s.", chesttrap ? "chest" : "door");
1285 return 0; /* trap ID'd, but no time elapses */
1289 for (trap = ftrap; trap; trap = trap->ntrap)
1290 if (trap->tx == x && trap->ty == y) {
1291 if (!trap->tseen)
1292 break;
1293 tt = trap->ttyp;
1294 if (u.dz) {
1295 if (u.dz < 0 ? (tt == TRAPDOOR || tt == HOLE)
1296 : tt == ROCKTRAP)
1297 break;
1299 tt = what_trap(tt);
1300 pline("That is %s%s%s.",
1301 an(defsyms[trap_to_defsym(tt)].explanation),
1302 !trap->madeby_u
1303 ? ""
1304 : (tt == WEB)
1305 ? " woven"
1306 /* trap doors & spiked pits can't be made by
1307 player, and should be considered at least
1308 as much "set" as "dug" anyway */
1309 : (tt == HOLE || tt == PIT)
1310 ? " dug"
1311 : " set",
1312 !trap->madeby_u ? "" : " by you");
1313 return 0;
1315 pline("I can't see a trap there.");
1316 return 0;
1320 Implements a rudimentary if/elif/else/endif interpretor and use
1321 conditionals in dat/cmdhelp to describe what command each keystroke
1322 currently invokes, so that there isn't a lot of "(debug mode only)"
1323 and "(if number_pad is off)" cluttering the feedback that the user
1324 sees. (The conditionals add quite a bit of clutter to the raw data
1325 but users don't see that. number_pad produces a lot of conditional
1326 commands: basic letters vs digits, 'g' vs 'G' for '5', phone
1327 keypad vs normal layout of digits, and QWERTZ keyboard swap between
1328 y/Y/^Y/M-y/M-Y/M-^Y and z/Z/^Z/M-z/M-Z/M-^Z.)
1330 The interpretor understands
1331 '&#' for comment,
1332 '&? option' for 'if' (also '&? !option'
1333 or '&? option=value[,value2,...]'
1334 or '&? !option=value[,value2,...]'),
1335 '&: option' for 'elif' (with argument variations same as 'if';
1336 any number of instances for each 'if'),
1337 '&:' for 'else' (also '&: #comment';
1338 0 or 1 instance for a given 'if'), and
1339 '&.' for 'endif' (also '&. #comment'; required for each 'if').
1341 The option handling is a bit of a mess, with no generality for
1342 which options to deal with and only a comma separated list of
1343 integer values for the '=value' part. number_pad is the only
1344 supported option that has a value; the few others (wizard/debug,
1345 rest_on_space, #if SHELL, #if SUSPEND) are booleans.
1348 STATIC_DCL void
1349 whatdoes_help()
1351 dlb *fp;
1352 char *p, buf[BUFSZ];
1353 winid tmpwin = create_nhwindow(NHW_TEXT);
1355 fp = dlb_fopen(KEYHELP, "r");
1356 if (!fp) {
1357 pline("Cannot open \"%s\" data file!", KEYHELP);
1358 display_nhwindow(WIN_MESSAGE, TRUE);
1359 return;
1361 while (dlb_fgets(buf, (int) sizeof buf, fp)) {
1362 if (*buf == '#')
1363 continue;
1364 for (p = buf; *p; p++)
1365 if (*p != ' ' && *p != '\t')
1366 break;
1367 putstr(tmpwin, 0, p);
1369 (void) dlb_fclose(fp);
1370 display_nhwindow(tmpwin, TRUE);
1371 destroy_nhwindow(tmpwin);
1374 #define WD_STACKLIMIT 5
1375 struct wd_stack_frame {
1376 Bitfield(active, 1);
1377 Bitfield(been_true, 1);
1378 Bitfield(else_seen, 1);
1381 STATIC_DCL boolean FDECL(whatdoes_cond, (char *, struct wd_stack_frame *,
1382 int *, int));
1384 STATIC_OVL boolean
1385 whatdoes_cond(buf, stack, depth, lnum)
1386 char *buf;
1387 struct wd_stack_frame *stack;
1388 int *depth, lnum;
1390 const char badstackfmt[] = "cmdhlp: too many &%c directives at line %d.";
1391 boolean newcond, neg, gotopt;
1392 char *p, *q, act = buf[1];
1393 int np = 0;
1395 newcond = (act == '?' || !stack[*depth].been_true);
1396 buf += 2;
1397 mungspaces(buf);
1398 if (act == '#' || *buf == '#' || !*buf || !newcond) {
1399 gotopt = (*buf && *buf != '#');
1400 *buf = '\0';
1401 neg = FALSE; /* lint suppression */
1402 p = q = (char *) 0;
1403 } else {
1404 gotopt = TRUE;
1405 if ((neg = (*buf == '!')) != 0)
1406 if (*++buf == ' ')
1407 ++buf;
1408 p = index(buf, '='), q = index(buf, ':');
1409 if (!p || (q && q < p))
1410 p = q;
1411 if (p) { /* we have a value specified */
1412 /* handle a space before or after (or both) '=' (or ':') */
1413 if (p > buf && p[-1] == ' ')
1414 p[-1] = '\0'; /* end of keyword in buf[] */
1415 *p++ = '\0'; /* terminate keyword, advance to start of value */
1416 if (*p == ' ')
1417 p++;
1420 if (*buf && (act == '?' || act == ':')) {
1421 if (!strcmpi(buf, "number_pad")) {
1422 if (!p) {
1423 newcond = iflags.num_pad;
1424 } else {
1425 /* convert internal encoding (separate yes/no and 0..3)
1426 back to user-visible one (-1..4) */
1427 np = iflags.num_pad ? (1 + iflags.num_pad_mode) /* 1..4 */
1428 : (-1 * iflags.num_pad_mode); /* -1..0 */
1429 newcond = FALSE;
1430 for (; p; p = q) {
1431 q = index(p, ',');
1432 if (q)
1433 *q++ = '\0';
1434 if (atoi(p) == np) {
1435 newcond = TRUE;
1436 break;
1440 } else if (!strcmpi(buf, "rest_on_space")) {
1441 newcond = flags.rest_on_space;
1442 } else if (!strcmpi(buf, "debug") || !strcmpi(buf, "wizard")) {
1443 newcond = flags.debug; /* == wizard */
1444 } else if (!strcmpi(buf, "shell")) {
1445 #ifdef SHELL
1446 /* should we also check sysopt.shellers? */
1447 newcond = TRUE;
1448 #else
1449 newcond = FALSE;
1450 #endif
1451 } else if (!strcmpi(buf, "suspend")) {
1452 #ifdef SUSPEND
1453 /* sysopt.shellers is also used for dosuspend()... */
1454 newcond = TRUE;
1455 #else
1456 newcond = FALSE;
1457 #endif
1458 } else {
1459 impossible(
1460 "cmdhelp: unrecognized &%c conditional at line %d: \"%.20s\"",
1461 act, lnum, buf);
1462 neg = FALSE;
1464 /* this works for number_pad too: &? !number_pad:-1,0
1465 would be true for 1..4 after negation */
1466 if (neg)
1467 newcond = !newcond;
1469 switch (act) {
1470 default:
1471 case '#': /* comment */
1472 break;
1473 case '.': /* endif */
1474 if (--*depth < 0) {
1475 impossible(badstackfmt, '.', lnum);
1476 *depth = 0;
1478 break;
1479 case ':': /* else or elif */
1480 if (*depth == 0 || stack[*depth].else_seen) {
1481 impossible(badstackfmt, ':', lnum);
1482 *depth = 1; /* so that stack[*depth - 1] is a valid access */
1484 if (stack[*depth].active || stack[*depth].been_true
1485 || !stack[*depth - 1].active)
1486 stack[*depth].active = 0;
1487 else if (newcond)
1488 stack[*depth].active = stack[*depth].been_true = 1;
1489 if (!gotopt)
1490 stack[*depth].else_seen = 1;
1491 break;
1492 case '?': /* if */
1493 if (++*depth >= WD_STACKLIMIT) {
1494 impossible(badstackfmt, '?', lnum);
1495 *depth = WD_STACKLIMIT - 1;
1497 stack[*depth].active = (newcond && stack[*depth - 1].active) ? 1 : 0;
1498 stack[*depth].been_true = stack[*depth].active;
1499 stack[*depth].else_seen = 0;
1500 break;
1502 return stack[*depth].active ? TRUE : FALSE;
1505 char *
1506 dowhatdoes_core(q, cbuf)
1507 char q;
1508 char *cbuf;
1510 dlb *fp;
1511 char buf[BUFSZ];
1512 struct wd_stack_frame stack[WD_STACKLIMIT];
1513 boolean cond;
1514 int ctrl, meta, depth = 0, lnum = 0;
1516 fp = dlb_fopen(CMDHELPFILE, "r");
1517 if (!fp) {
1518 pline("Cannot open \"%s\" data file!", CMDHELPFILE);
1519 return 0;
1522 meta = (0x80 & (uchar) q) != 0;
1523 if (meta)
1524 q &= 0x7f;
1525 ctrl = (0x1f & (uchar) q) == (uchar) q;
1526 if (ctrl)
1527 q |= 0x40; /* NUL -> '@', ^A -> 'A', ... ^Z -> 'Z', ^[ -> '[', ... */
1528 else if (q == 0x7f)
1529 ctrl = 1, q = '?';
1531 (void) memset((genericptr_t) stack, 0, sizeof stack);
1532 cond = stack[0].active = 1;
1533 while (dlb_fgets(buf, sizeof buf, fp)) {
1534 ++lnum;
1535 if (buf[0] == '&' && buf[1] && index("?:.#", buf[1])) {
1536 cond = whatdoes_cond(buf, stack, &depth, lnum);
1537 continue;
1539 if (!cond)
1540 continue;
1541 if (meta ? (buf[0] == 'M' && buf[1] == '-'
1542 && (ctrl ? buf[2] == '^' && highc(buf[3]) == q
1543 : buf[2] == q))
1544 : (ctrl ? buf[0] == '^' && highc(buf[1]) == q
1545 : buf[0] == q)) {
1546 (void) strip_newline(buf);
1547 if (index(buf, '\t'))
1548 (void) tabexpand(buf);
1549 if (meta && ctrl && buf[4] == ' ') {
1550 (void) strncpy(buf, "M-^? ", 8);
1551 buf[3] = q;
1552 } else if (meta && buf[3] == ' ') {
1553 (void) strncpy(buf, "M-? ", 8);
1554 buf[2] = q;
1555 } else if (ctrl && buf[2] == ' ') {
1556 (void) strncpy(buf, "^? ", 8);
1557 buf[1] = q;
1558 } else if (buf[1] == ' ') {
1559 (void) strncpy(buf, "? ", 8);
1560 buf[0] = q;
1562 (void) dlb_fclose(fp);
1563 Strcpy(cbuf, buf);
1564 return cbuf;
1567 (void) dlb_fclose(fp);
1568 if (depth != 0)
1569 impossible("cmdhelp: mismatched &? &: &. conditionals.");
1570 return (char *) 0;
1574 dowhatdoes()
1576 static boolean once = FALSE;
1577 char bufr[BUFSZ];
1578 char q, *reslt;
1580 if (!once) {
1581 pline("Ask about '&' or '?' to get more info.%s",
1582 #ifdef ALTMETA
1583 iflags.altmeta ? " (For ESC, type it twice.)" :
1584 #endif
1585 "");
1586 once = TRUE;
1588 #if defined(UNIX) || defined(VMS)
1589 introff(); /* disables ^C but not ^\ */
1590 #endif
1591 q = yn_function("What command?", (char *) 0, '\0');
1592 #ifdef ALTMETA
1593 if (q == '\033' && iflags.altmeta) {
1594 /* in an ideal world, we would know whether another keystroke
1595 was already pending, but this is not an ideal world...
1596 if user typed ESC, we'll essentially hang until another
1597 character is typed */
1598 q = yn_function("]", (char *) 0, '\0');
1599 if (q != '\033')
1600 q = (char) ((uchar) q | 0200);
1602 #endif /*ALTMETA*/
1603 #if defined(UNIX) || defined(VMS)
1604 intron(); /* reenables ^C */
1605 #endif
1606 reslt = dowhatdoes_core(q, bufr);
1607 if (reslt) {
1608 if (q == '&' || q == '?')
1609 whatdoes_help();
1610 pline("%s", reslt);
1611 } else {
1612 pline("No such command '%s', char code %d (0%03o or 0x%02x).",
1613 visctrl(q), (uchar) q, (uchar) q, (uchar) q);
1615 return 0;
1618 STATIC_OVL void
1619 docontact()
1621 winid cwin = create_nhwindow(NHW_TEXT);
1622 char buf[BUFSZ];
1624 if (sysopt.support) {
1625 /*XXX overflow possibilities*/
1626 Sprintf(buf, "To contact local support, %s", sysopt.support);
1627 putstr(cwin, 0, buf);
1628 putstr(cwin, 0, "");
1629 } else if (sysopt.fmtd_wizard_list) { /* formatted SYSCF WIZARDS */
1630 Sprintf(buf, "To contact local support, contact %s.",
1631 sysopt.fmtd_wizard_list);
1632 putstr(cwin, 0, buf);
1633 putstr(cwin, 0, "");
1635 putstr(cwin, 0, "To contact the NetHack development team directly,");
1636 /*XXX overflow possibilities*/
1637 Sprintf(buf, "see the 'Contact' form on our website or email <%s>.",
1638 DEVTEAM_EMAIL);
1639 putstr(cwin, 0, buf);
1640 putstr(cwin, 0, "");
1641 putstr(cwin, 0, "For more information on NetHack, or to report a bug,");
1642 Sprintf(buf, "visit our website \"%s\".", DEVTEAM_URL);
1643 putstr(cwin, 0, buf);
1644 display_nhwindow(cwin, FALSE);
1645 destroy_nhwindow(cwin);
1648 void
1649 dispfile_help()
1651 display_file(HELP, TRUE);
1654 void
1655 dispfile_shelp()
1657 display_file(SHELP, TRUE);
1660 void
1661 dispfile_optionfile()
1663 display_file(OPTIONFILE, TRUE);
1666 void
1667 dispfile_license()
1669 display_file(LICENSE, TRUE);
1672 void
1673 dispfile_debughelp()
1675 display_file(DEBUGHELP, TRUE);
1678 void
1679 hmenu_doextversion()
1681 (void) doextversion();
1684 void
1685 hmenu_dohistory()
1687 (void) dohistory();
1690 void
1691 hmenu_dowhatis()
1693 (void) dowhatis();
1696 void
1697 hmenu_dowhatdoes()
1699 (void) dowhatdoes();
1702 void
1703 hmenu_doextlist()
1705 (void) doextlist();
1708 /* data for dohelp() */
1709 static struct {
1710 void (*f)();
1711 const char *text;
1712 } help_menu_items[] = {
1713 { hmenu_doextversion, "About NetHack (version information)." },
1714 { dispfile_help, "Long description of the game and commands." },
1715 { dispfile_shelp, "List of game commands." },
1716 { hmenu_dohistory, "Concise history of NetHack." },
1717 { hmenu_dowhatis, "Info on a character in the game display." },
1718 { hmenu_dowhatdoes, "Info on what a given key does." },
1719 { option_help, "List of game options." },
1720 { dispfile_optionfile, "Longer explanation of game options." },
1721 { hmenu_doextlist, "List of extended commands." },
1722 { dispfile_license, "The NetHack license." },
1723 { docontact, "Support information." },
1724 #ifdef PORT_HELP
1725 { port_help, "%s-specific help and commands." },
1726 #endif
1727 { dispfile_debughelp, "List of wizard-mode commands." },
1728 { NULL, (char *) 0 }
1731 /* the '?' command */
1733 dohelp()
1735 winid tmpwin = create_nhwindow(NHW_MENU);
1736 char helpbuf[QBUFSZ];
1737 int i, n;
1738 menu_item *selected;
1739 anything any;
1740 int sel;
1741 char *bufptr;
1743 any = zeroany; /* zero all bits */
1744 start_menu(tmpwin);
1746 for (i = 0; help_menu_items[i].text; i++) {
1747 if (!wizard && help_menu_items[i].f == dispfile_debughelp)
1748 continue;
1749 if (help_menu_items[i].text[0] == '%') {
1750 Sprintf(helpbuf, help_menu_items[i].text, PORT_ID);
1751 bufptr = helpbuf;
1752 } else {
1753 bufptr = (char *)help_menu_items[i].text;
1755 any.a_int = i + 1;
1756 add_menu(tmpwin, NO_GLYPH, &any, 0, 0, ATR_NONE,
1757 bufptr, MENU_UNSELECTED);
1759 end_menu(tmpwin, "Select one item:");
1760 n = select_menu(tmpwin, PICK_ONE, &selected);
1761 destroy_nhwindow(tmpwin);
1762 if (n > 0) {
1763 sel = selected[0].item.a_int - 1;
1764 free((genericptr_t) selected);
1765 (void)(*help_menu_items[sel].f)();
1767 return 0;
1770 /* the 'V' command; also a choice for '?' */
1772 dohistory()
1774 display_file(HISTORY, TRUE);
1775 return 0;
1778 /*pager.c*/