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