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