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