1 /* NetHack 3.6 pager.c $NHDT-Date: 1455674291 2016/02/17 01:58:11 $ $NHDT-Branch: NetHack-3.6.0 $:$NHDT-Revision: 1.93 $ */
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 */
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 boolean
FDECL(help_menu
, (int *));
21 STATIC_DCL
void NDECL(docontact
);
23 extern void NDECL(port_help
);
26 /* Returns "true" for characters that could represent a monster's stomach. */
33 for (i
= S_sw_tl
; i
<= S_sw_br
; i
++)
34 if ((int) showsyms
[i
] == c
)
40 * Append new_str to the end of buf if new_str doesn't already exist as
41 * a substring of buf. Return 1 if the string was appended, 0 otherwise.
42 * It is expected that buf is of size BUFSZ.
45 append_str(buf
, new_str
)
49 int space_left
; /* space remaining in buf */
51 if (strstri(buf
, new_str
))
54 space_left
= BUFSZ
- strlen(buf
) - 1;
55 (void) strncat(buf
, " or ", space_left
);
56 (void) strncat(buf
, new_str
, space_left
- 4);
60 /* shared by monster probing (via query_objlist!) as well as lookat() */
67 /* include race with role unless polymorphed */
70 Sprintf(race
, "%s ", urace
.adj
);
71 Sprintf(outbuf
, "%s%s%s called %s",
72 /* being blinded may hide invisibility from self */
73 (Invis
&& (senseself() || !Blind
)) ? "invisible " : "", race
,
74 mons
[u
.umonnum
].mname
, plname
);
76 Sprintf(eos(outbuf
), ", mounted on %s", y_monnam(u
.usteed
));
80 /* describe a hidden monster; used for look_at during extended monster
81 detection and for probing */
83 mhidden_description(mon
, altmon
, outbuf
)
85 boolean altmon
; /* for probing: if mimicking a monster, say so */
90 int x
= mon
->mx
, y
= mon
->my
, glyph
= levl
[x
][y
].glyph
;
93 if (mon
->m_ap_type
== M_AP_FURNITURE
94 || mon
->m_ap_type
== M_AP_OBJECT
) {
95 Strcpy(outbuf
, ", mimicking ");
96 if (mon
->m_ap_type
== M_AP_FURNITURE
) {
97 Strcat(outbuf
, an(defsyms
[mon
->mappearance
].explanation
));
98 } else if (mon
->m_ap_type
== M_AP_OBJECT
99 /* remembered glyph, not glyph_at() which is 'mon' */
100 && glyph_is_object(glyph
)) {
102 otmp
= (struct obj
*) 0;
103 fakeobj
= object_from_map(glyph
, x
, y
, &otmp
);
104 Strcat(outbuf
, (otmp
&& otmp
->otyp
!= STRANGE_OBJECT
)
105 ? ansimpleoname(otmp
)
106 : an(obj_descr
[STRANGE_OBJECT
].oc_name
));
110 Strcat(outbuf
, something
);
112 } else if (mon
->m_ap_type
== M_AP_MONSTER
) {
114 Sprintf(outbuf
, ", masquerading as %s",
115 an(mons
[mon
->mappearance
].mname
));
116 } else if (mon
->mundetected
) {
117 Strcpy(outbuf
, ", hiding");
118 if (hides_under(mon
->data
)) {
119 Strcat(outbuf
, " under ");
120 /* remembered glyph, not glyph_at() which is 'mon' */
121 if (glyph_is_object(glyph
))
123 Strcat(outbuf
, something
);
124 } else if (is_hider(mon
->data
)) {
125 Sprintf(eos(outbuf
), " on the %s",
126 (is_flyer(mon
->data
) || mon
->data
->mlet
== S_PIERCER
)
128 : surface(x
, y
)); /* trapper */
130 if (mon
->data
->mlet
== S_EEL
&& is_pool(x
, y
))
131 Strcat(outbuf
, " in murky water");
136 /* extracted from lookat(); also used by namefloorobj() */
138 object_from_map(glyph
, x
, y
, obj_p
)
142 boolean fakeobj
= FALSE
;
144 struct obj
*otmp
= vobj_at(x
, y
);
145 int glyphotyp
= glyph_to_obj(glyph
);
147 *obj_p
= (struct obj
*) 0;
148 /* there might be a mimic here posing as an object */
150 if (mtmp
&& is_obj_mappear(mtmp
, (unsigned) glyphotyp
))
155 if (!otmp
|| otmp
->otyp
!= glyphotyp
) {
156 /* this used to exclude STRANGE_OBJECT; now caller deals with it */
157 otmp
= mksobj(glyphotyp
, FALSE
, FALSE
);
161 if (otmp
->oclass
== COIN_CLASS
)
162 otmp
->quan
= 2L; /* to force pluralization */
163 else if (otmp
->otyp
== SLIME_MOLD
)
164 otmp
->spe
= context
.current_fruit
; /* give it a type */
165 if (mtmp
&& has_mcorpsenm(mtmp
)) /* mimic as corpse/statue */
166 otmp
->corpsenm
= MCORPSENM(mtmp
);
168 /* if located at adjacent spot, mark it as having been seen up close */
169 if (otmp
&& distu(x
, y
) <= 2 && !Blind
&& !Hallucination
)
173 return fakeobj
; /* when True, caller needs to dealloc *obj_p */
177 look_at_object(buf
, x
, y
, glyph
)
178 char *buf
; /* output buffer */
181 struct obj
*otmp
= 0;
182 boolean fakeobj
= object_from_map(glyph
, x
, y
, &otmp
);
185 Strcpy(buf
, (otmp
->otyp
!= STRANGE_OBJECT
)
186 ? distant_name(otmp
, doname
)
187 : obj_descr
[STRANGE_OBJECT
].oc_name
);
189 dealloc_obj(otmp
), otmp
= 0;
191 Strcpy(buf
, something
); /* sanity precaution */
193 if (levl
[x
][y
].typ
== STONE
|| levl
[x
][y
].typ
== SCORR
)
194 Strcat(buf
, " embedded in stone");
195 else if (IS_WALL(levl
[x
][y
].typ
) || levl
[x
][y
].typ
== SDOOR
)
196 Strcat(buf
, " embedded in a wall");
197 else if (closed_door(x
, y
))
198 Strcat(buf
, " embedded in a door");
199 else if (is_pool(x
, y
))
200 Strcat(buf
, " in water");
201 else if (is_lava(x
, y
))
202 Strcat(buf
, " in molten lava"); /* [can this ever happen?] */
207 look_at_monster(buf
, monbuf
, mtmp
, x
, y
)
208 char *buf
, *monbuf
; /* buf: output, monbuf: optional output */
212 char *name
, monnambuf
[BUFSZ
];
213 boolean accurate
= !Hallucination
;
215 name
= (mtmp
->data
== &mons
[PM_COYOTE
] && accurate
)
216 ? coyotename(mtmp
, monnambuf
)
217 : distant_monnam(mtmp
, ARTICLE_NONE
, monnambuf
);
218 Sprintf(buf
, "%s%s%s",
219 (mtmp
->mx
!= x
|| mtmp
->my
!= y
)
220 ? ((mtmp
->isshk
&& accurate
) ? "tail of " : "tail of a ")
222 (mtmp
->mtame
&& accurate
)
224 : (mtmp
->mpeaceful
&& accurate
)
228 if (u
.ustuck
== mtmp
)
229 Strcat(buf
, (Upolyd
&& sticks(youmonst
.data
))
230 ? ", being held" : ", holding you");
232 Strcat(buf
, ", leashed to you");
234 if (mtmp
->mtrapped
&& cansee(mtmp
->mx
, mtmp
->my
)) {
235 struct trap
*t
= t_at(mtmp
->mx
, mtmp
->my
);
236 int tt
= t
? t
->ttyp
: NO_TRAP
;
238 /* newsym lets you know of the trap, so mention it here */
239 if (tt
== BEAR_TRAP
|| tt
== PIT
|| tt
== SPIKED_PIT
|| tt
== WEB
)
240 Sprintf(eos(buf
), ", trapped in %s",
241 an(defsyms
[trap_to_defsym(tt
)].explanation
));
244 /* we know the hero sees a monster at this location, but if it's shown
245 due to persistant monster detection he might remember something else */
246 if (mtmp
->mundetected
|| mtmp
->m_ap_type
)
247 mhidden_description(mtmp
, FALSE
, eos(buf
));
250 unsigned how_seen
= howmonseen(mtmp
);
253 if (how_seen
!= 0 && how_seen
!= MONSEEN_NORMAL
) {
254 if (how_seen
& MONSEEN_NORMAL
) {
255 Strcat(monbuf
, "normal vision");
256 how_seen
&= ~MONSEEN_NORMAL
;
257 /* how_seen can't be 0 yet... */
259 Strcat(monbuf
, ", ");
261 if (how_seen
& MONSEEN_SEEINVIS
) {
262 Strcat(monbuf
, "see invisible");
263 how_seen
&= ~MONSEEN_SEEINVIS
;
265 Strcat(monbuf
, ", ");
267 if (how_seen
& MONSEEN_INFRAVIS
) {
268 Strcat(monbuf
, "infravision");
269 how_seen
&= ~MONSEEN_INFRAVIS
;
271 Strcat(monbuf
, ", ");
273 if (how_seen
& MONSEEN_TELEPAT
) {
274 Strcat(monbuf
, "telepathy");
275 how_seen
&= ~MONSEEN_TELEPAT
;
277 Strcat(monbuf
, ", ");
279 if (how_seen
& MONSEEN_XRAYVIS
) {
280 /* Eyes of the Overworld */
281 Strcat(monbuf
, "astral vision");
282 how_seen
&= ~MONSEEN_XRAYVIS
;
284 Strcat(monbuf
, ", ");
286 if (how_seen
& MONSEEN_DETECT
) {
287 Strcat(monbuf
, "monster detection");
288 how_seen
&= ~MONSEEN_DETECT
;
290 Strcat(monbuf
, ", ");
292 if (how_seen
& MONSEEN_WARNMON
) {
294 Strcat(monbuf
, "paranoid delusion");
296 Sprintf(eos(monbuf
), "warned of %s",
297 makeplural(mtmp
->data
->mname
));
298 how_seen
&= ~MONSEEN_WARNMON
;
300 Strcat(monbuf
, ", ");
302 /* should have used up all the how_seen bits by now */
304 impossible("lookat: unknown method of seeing monster");
305 Sprintf(eos(monbuf
), "(%u)", how_seen
);
307 } /* seen by something other than normal vision */
308 } /* monbuf is non-null */
312 * Return the name of the glyph found at (x,y).
313 * If not hallucinating and the glyph is a monster, also monster data.
315 STATIC_OVL
struct permonst
*
316 lookat(x
, y
, buf
, monbuf
)
320 struct monst
*mtmp
= (struct monst
*) 0;
321 struct permonst
*pm
= (struct permonst
*) 0;
324 buf
[0] = monbuf
[0] = '\0';
325 glyph
= glyph_at(x
, y
);
326 if (u
.ux
== x
&& u
.uy
== y
&& canspotself()) {
328 (void) self_lookat(buf
);
330 /* file lookup can't distinguish between "gnomish wizard" monster
331 and correspondingly named player character, always picking the
332 former; force it to find the general "wizard" entry instead */
333 if (Role_if(PM_WIZARD
) && Race_if(PM_GNOME
) && !Upolyd
)
334 pm
= &mons
[PM_WIZARD
];
336 /* When you see yourself normally, no explanation is appended
337 (even if you could also see yourself via other means).
338 Sensing self while blind or swallowed is treated as if it
339 were by normal vision (cf canseeself()). */
340 if ((Invisible
|| u
.uundetected
) && !Blind
&& !u
.uswallow
) {
352 eos(buf
), " [seen: %s%s%s%s%s]",
353 (how
& 1) ? "infravision" : "",
354 /* add comma if telep and infrav */
355 ((how
& 3) > 2) ? ", " : "", (how
& 2) ? "telepathy" : "",
356 /* add comma if detect and (infrav or telep or both) */
357 ((how
& 7) > 4) ? ", " : "",
358 (how
& 4) ? "monster detection" : "");
360 } else if (u
.uswallow
) {
361 /* all locations when swallowed other than the hero are the monster */
362 Sprintf(buf
, "interior of %s",
363 Blind
? "a monster" : a_monnam(u
.ustuck
));
365 } else if (glyph_is_monster(glyph
)) {
368 if ((mtmp
= m_at(x
, y
)) != 0) {
369 look_at_monster(buf
, monbuf
, mtmp
, x
, y
);
372 } else if (glyph_is_object(glyph
)) {
373 look_at_object(buf
, x
, y
, glyph
); /* fill in buf[] */
374 } else if (glyph_is_trap(glyph
)) {
375 int tnum
= what_trap(glyph_to_trap(glyph
));
377 Strcpy(buf
, defsyms
[trap_to_defsym(tnum
)].explanation
);
378 } else if (glyph_is_warning(glyph
)) {
379 int warnindx
= glyph_to_warning(glyph
);
380 Strcpy(buf
, def_warnsyms
[warnindx
].explanation
);
381 } else if (!glyph_is_cmap(glyph
)) {
382 Strcpy(buf
, "unexplored area");
384 switch (glyph_to_cmap(glyph
)) {
386 Sprintf(buf
, "%s %saltar",
387 /* like endgame high priests, endgame high altars
388 are only recognizable when immediately adjacent */
389 (Is_astralevel(&u
.uz
) && distu(x
, y
) > 2)
392 Amask2align(levl
[x
][y
].altarmask
& ~AM_SHRINE
)),
393 ((levl
[x
][y
].altarmask
& AM_SHRINE
)
394 && (Is_astralevel(&u
.uz
) || Is_sanctum(&u
.uz
)))
399 if (is_drawbridge_wall(x
, y
) >= 0)
400 Strcpy(buf
, "open drawbridge portcullis");
401 else if ((levl
[x
][y
].doormask
& ~D_TRAPPED
) == D_BROKEN
)
402 Strcpy(buf
, "broken door");
404 Strcpy(buf
, "doorway");
408 Is_airlevel(&u
.uz
) ? "cloudy area" : "fog/vapor cloud");
411 if (!levl
[x
][y
].seenv
) {
412 Strcpy(buf
, "unexplored");
414 } else if (levl
[x
][y
].typ
== STONE
|| levl
[x
][y
].typ
== SCORR
) {
415 Strcpy(buf
, "stone");
420 Strcpy(buf
, defsyms
[glyph_to_cmap(glyph
)].explanation
);
424 return (pm
&& !Hallucination
) ? pm
: (struct permonst
*) 0;
428 * Look in the "data" file for more info. Called if the user typed in the
429 * whole name (user_typed_name == TRUE), or we've found a possible match
430 * with a character/glyph and flags.help is TRUE.
432 * NOTE: when (user_typed_name == FALSE), inp is considered read-only and
433 * must not be changed directly, e.g. via lcase(). We want to force
434 * lcase() for data.base lookup so that we can have a clean key.
435 * Therefore, we create a copy of inp _just_ for data.base lookup.
438 checkfile(inp
, pm
, user_typed_name
, without_asking
)
441 boolean user_typed_name
, without_asking
;
444 char buf
[BUFSZ
], newstr
[BUFSZ
], givenname
[BUFSZ
];
445 char *ep
, *dbase_str
;
446 unsigned long txt_offset
= 0L;
447 int chk_skip
, pass
= 1;
448 boolean found_in_file
= FALSE
, skipping_entry
= FALSE
, yes_to_moreinfo
;
449 winid datawin
= WIN_ERR
;
451 fp
= dlb_fopen(DATAFILE
, "r");
453 pline("Cannot open data file!");
458 * If someone passed us garbage, prevent fault.
460 if (!inp
|| (inp
&& strlen(inp
) > (BUFSZ
- 1))) {
461 pline("bad do_look buffer passed!");
465 /* To prevent the need for entries in data.base like *ngel to account
466 * for Angel and angel, make the lookup string the same for both
467 * user_typed_name and picked name.
469 if (pm
!= (struct permonst
*) 0 && !user_typed_name
)
470 dbase_str
= strcpy(newstr
, pm
->mname
);
472 dbase_str
= strcpy(newstr
, inp
);
473 (void) lcase(dbase_str
);
475 if (!strncmp(dbase_str
, "interior of ", 12))
477 if (!strncmp(dbase_str
, "a ", 2))
479 else if (!strncmp(dbase_str
, "an ", 3))
481 else if (!strncmp(dbase_str
, "the ", 4))
483 if (!strncmp(dbase_str
, "tame ", 5))
485 else if (!strncmp(dbase_str
, "peaceful ", 9))
487 if (!strncmp(dbase_str
, "invisible ", 10))
489 if (!strncmp(dbase_str
, "saddled ", 8))
491 if (!strncmp(dbase_str
, "statue of ", 10))
493 else if (!strncmp(dbase_str
, "figurine of ", 12))
496 /* Make sure the name is non-empty. */
498 /* adjust the input to remove "named " and convert to lower case */
499 char *alt
= 0; /* alternate description */
501 if ((ep
= strstri(dbase_str
, " named ")) != 0)
503 else if ((ep
= strstri(dbase_str
, " called ")) != 0) {
504 if (strlen(ep
+ 8) < BUFSZ
- 1) {
505 Strcpy(givenname
, ep
+ 8);
506 givenname
[BUFSZ
-1] = '\0';
511 ep
= strstri(dbase_str
, ", ");
512 if (ep
&& ep
> dbase_str
)
516 * If the object is named, then the name is the alternate description;
517 * otherwise, the result of makesingular() applied to the name is.
519 * isn't strictly optimal, but named objects of interest to the user
520 * will usually be found under their name, rather than under their
521 * object type, so looking for a singular form is pointless.
524 alt
= makesingular(dbase_str
);
526 if (!strcmp(alt
, dbase_str
))
529 for (; pass
>= 0; pass
--) {
531 if (dlb_fseek(fp
, txt_offset
, SEEK_SET
) < 0 ) {
532 impossible("can't get to start of 'data' file");
536 /* skip first record; read second */
537 if (!dlb_fgets(buf
, BUFSZ
, fp
) || !dlb_fgets(buf
, BUFSZ
, fp
)) {
538 impossible("can't read 'data' file");
539 (void) dlb_fclose(fp
);
541 } else if (sscanf(buf
, "%8lx\n", &txt_offset
) < 1
545 /* look for the appropriate entry */
546 while (dlb_fgets(buf
, BUFSZ
, fp
)) {
548 break; /* we passed last entry without success */
551 /* a number indicates the end of current entry */
552 skipping_entry
= FALSE
;
553 } else if (!skipping_entry
) {
554 if (!(ep
= index(buf
, '\n')))
556 (void) strip_newline((ep
> buf
) ? ep
- 1 : ep
);
557 /* if we match a key that begins with "~", skip this entry */
558 chk_skip
= (*buf
== '~') ? 1 : 0;
559 if ((pass
== 0 && pmatch(&buf
[chk_skip
], dbase_str
)) ||
560 (pass
== 1 && alt
&& pmatch(&buf
[chk_skip
], alt
))) {
562 skipping_entry
= TRUE
;
565 found_in_file
= TRUE
;
576 /* skip over other possible matches for the info */
578 if (!dlb_fgets(buf
, BUFSZ
, fp
))
580 } while (!digit(*buf
));
581 if (sscanf(buf
, "%ld,%d\n", &entry_offset
, &entry_count
) < 2) {
583 impossible("'data' file in wrong format or corrupted");
584 /* window will exist if we came here from below via 'goto' */
585 if (datawin
!= WIN_ERR
)
586 destroy_nhwindow(datawin
);
587 (void) dlb_fclose(fp
);
591 yes_to_moreinfo
= FALSE
;
592 if (!user_typed_name
&& !without_asking
) {
593 unsigned maxt
= strlen("More info about \"\"?");
594 char *entrytext
= pass
? alt
: dbase_str
;
595 char question
[BUFSZ
];
596 if (strlen(entrytext
) < BUFSZ
- maxt
) {
597 Strcpy(question
, "More info about \"");
598 Strcat(question
, entrytext
);
599 Strcat(question
, "\"?");
601 if (yn(question
) == 'y')
602 yes_to_moreinfo
= TRUE
;
605 if (user_typed_name
|| without_asking
|| yes_to_moreinfo
) {
607 (long) txt_offset
+ entry_offset
, SEEK_SET
) < 0) {
608 pline("? Seek error on 'data' file!");
609 (void) dlb_fclose(fp
);
612 datawin
= create_nhwindow(NHW_MENU
);
613 for (i
= 0; i
< entry_count
; i
++) {
614 if (!dlb_fgets(buf
, BUFSZ
, fp
))
616 (void) strip_newline(buf
);
617 if (index(buf
+ 1, '\t') != 0)
618 (void) tabexpand(buf
+ 1);
619 putstr(datawin
, 0, buf
+ 1);
621 display_nhwindow(datawin
, FALSE
);
622 destroy_nhwindow(datawin
);
624 } else if (user_typed_name
&& pass
== 0)
625 pline("I don't have any information on those things.");
628 (void) dlb_fclose(fp
);
632 do_screen_description(cc
, looked
, sym
, out_str
, firstmatch
)
637 const char **firstmatch
;
639 boolean need_to_look
= FALSE
;
640 int glyph
= NO_GLYPH
;
641 static char look_buf
[BUFSZ
];
643 int found
= 0; /* count of matching syms found */
645 int skipped_venom
= 0;
648 static const char *mon_interior
= "the interior of a monster";
654 glyph
= glyph_at(cc
.x
, cc
.y
);
655 /* Convert glyph at selected position to a symbol for use below. */
656 (void) mapglyph(glyph
, &sym
, &oc
, &os
, cc
.x
, cc
.y
);
658 Sprintf(prefix
, "%s ", encglyph(glyph
));
660 Sprintf(prefix
, "%c ", sym
);
663 * Check all the possibilities, saving all explanations in a buffer.
664 * When all have been checked then the string is printed.
668 * Special case: if identifying from the screen, and we're swallowed,
669 * and looking at something other than our own symbol, then just say
670 * "the interior of a monster".
672 if (u
.uswallow
&& looked
673 && (is_swallow_sym(sym
) || (int) showsyms
[S_stone
] == sym
)) {
675 Sprintf(out_str
, "%s%s", prefix
, mon_interior
);
676 *firstmatch
= mon_interior
;
678 found
+= append_str(out_str
, mon_interior
);
684 /* Check for monsters */
685 for (i
= 0; i
< MAXMCLASSES
; i
++) {
686 if (sym
== (looked
? showsyms
[i
+ SYM_OFF_M
] : def_monsyms
[i
].sym
)
687 && def_monsyms
[i
].explain
) {
690 Sprintf(out_str
, "%s%s", prefix
, an(def_monsyms
[i
].explain
));
691 *firstmatch
= def_monsyms
[i
].explain
;
694 found
+= append_str(out_str
, an(def_monsyms
[i
].explain
));
698 /* handle '@' as a special case if it refers to you and you're
699 playing a character which isn't normally displayed by that
700 symbol; firstmatch is assumed to already be set for '@' */
701 if ((looked
? (sym
== showsyms
[S_HUMAN
+ SYM_OFF_M
]
702 && cc
.x
== u
.ux
&& cc
.y
== u
.uy
)
703 : (sym
== def_monsyms
[S_HUMAN
].sym
&& !flags
.showrace
))
704 && !(Race_if(PM_HUMAN
) || Race_if(PM_ELF
)) && !Upolyd
)
705 found
+= append_str(out_str
, "you"); /* tack on "or you" */
707 /* Now check for objects */
708 for (i
= 1; i
< MAXOCLASSES
; i
++) {
709 if (sym
== (looked
? showsyms
[i
+ SYM_OFF_O
] : def_oc_syms
[i
].sym
)) {
711 if (looked
&& i
== VENOM_CLASS
) {
716 Sprintf(out_str
, "%s%s", prefix
, an(def_oc_syms
[i
].explain
));
717 *firstmatch
= def_oc_syms
[i
].explain
;
720 found
+= append_str(out_str
, an(def_oc_syms
[i
].explain
));
725 if (sym
== DEF_INVISIBLE
) {
727 Sprintf(out_str
, "%s%s", prefix
, an(invisexplain
));
728 *firstmatch
= invisexplain
;
731 found
+= append_str(out_str
, an(invisexplain
));
735 #define is_cmap_trap(i) ((i) >= S_arrow_trap && (i) <= S_polymorph_trap)
736 #define is_cmap_drawbridge(i) ((i) >= S_vodbridge && (i) <= S_hcdbridge)
738 /* Now check for graphics symbols */
739 alt_i
= (sym
== (looked
? showsyms
[0] : defsyms
[0].sym
)) ? 0 : (2 + 1);
740 for (hit_trap
= FALSE
, i
= 0; i
< MAXPCHARS
; i
++) {
741 /* when sym is the default background character, we process
742 i == 0 three times: unexplored, stone, dark part of a room */
744 x_str
= !alt_i
++ ? "unexplored" : "stone";
745 i
= 0; /* for second iteration, undo loop increment */
746 /* alt_i is now 1 or 2 */
749 i
= 0; /* undo loop increment */
750 x_str
= defsyms
[i
].explanation
;
751 /* alt_i is now 3 or more and no longer of interest */
753 if (sym
== (looked
? showsyms
[i
] : defsyms
[i
].sym
) && *x_str
) {
754 /* avoid "an unexplored", "an stone", "an air", "a water",
755 "a floor of a room", "a dark part of a room";
756 article==2 => "the", 1 => "an", 0 => (none) */
757 int article
= strstri(x_str
, " of a room") ? 2
759 || strcmp(x_str
, "air") == 0
760 || strcmp(x_str
, "water") == 0);
763 if (is_cmap_trap(i
)) {
764 Sprintf(out_str
, "%sa trap", prefix
);
767 Sprintf(out_str
, "%s%s", prefix
,
768 article
== 2 ? the(x_str
)
769 : article
== 1 ? an(x_str
) : x_str
);
773 } else if (!u
.uswallow
&& !(hit_trap
&& is_cmap_trap(i
))
774 && !(found
>= 3 && is_cmap_drawbridge(i
))
775 /* don't mention vibrating square outside of Gehennom
776 unless this happens to be one (hallucination?) */
777 && (i
!= S_vibrating_square
|| Inhell
778 || (looked
&& glyph_is_trap(glyph
)
779 && glyph_to_trap(glyph
) == VIBRATING_SQUARE
))) {
780 found
+= append_str(out_str
, (article
== 2) ? the(x_str
)
781 : (article
== 1) ? an(x_str
)
787 if (i
== S_altar
|| is_cmap_trap(i
))
792 /* Now check for warning symbols */
793 for (i
= 1; i
< WARNCOUNT
; i
++) {
794 x_str
= def_warnsyms
[i
].explanation
;
795 if (sym
== (looked
? warnsyms
[i
] : def_warnsyms
[i
].sym
)) {
797 Sprintf(out_str
, "%s%s", prefix
, def_warnsyms
[i
].explanation
);
798 *firstmatch
= def_warnsyms
[i
].explanation
;
801 found
+= append_str(out_str
, def_warnsyms
[i
].explanation
);
803 /* Kludge: warning trumps boulders on the display.
804 Reveal the boulder too or player can get confused */
805 if (looked
&& sobj_at(BOULDER
, cc
.x
, cc
.y
))
806 Strcat(out_str
, " co-located with a boulder");
807 break; /* out of for loop*/
811 /* if we ignored venom and list turned out to be short, put it back */
812 if (skipped_venom
&& found
< 2) {
813 x_str
= def_oc_syms
[VENOM_CLASS
].explain
;
815 Sprintf(out_str
, "%s%s", prefix
, an(x_str
));
819 found
+= append_str(out_str
, an(x_str
));
823 /* handle optional boulder symbol as a special case */
824 if (iflags
.bouldersym
&& sym
== iflags
.bouldersym
) {
826 *firstmatch
= "boulder";
827 Sprintf(out_str
, "%s%s", prefix
, an(*firstmatch
));
830 found
+= append_str(out_str
, "boulder");
835 * If we are looking at the screen, follow multiple possibilities or
836 * an ambiguous explanation by something more detailed.
840 if (found
> 1 || need_to_look
) {
842 char temp_buf
[BUFSZ
];
844 (void) lookat(cc
.x
, cc
.y
, look_buf
, monbuf
);
845 *firstmatch
= look_buf
;
846 if (*(*firstmatch
)) {
847 Sprintf(temp_buf
, " (%s)", *firstmatch
);
848 (void) strncat(out_str
, temp_buf
,
849 BUFSZ
- strlen(out_str
) - 1);
850 found
= 1; /* we have something to look up */
853 Sprintf(temp_buf
, " [seen: %s]", monbuf
);
854 (void) strncat(out_str
, temp_buf
,
855 BUFSZ
- strlen(out_str
) - 1);
863 /* getpos() return values */
864 #define LOOK_TRADITIONAL 0 /* '.' -- ask about "more info?" */
865 #define LOOK_QUICK 1 /* ',' -- skip "more info?" */
866 #define LOOK_ONCE 2 /* ';' -- skip and stop looping */
867 #define LOOK_VERBOSE 3 /* ':' -- show more info w/o asking */
869 /* also used by getpos hack in do_name.c */
870 const char what_is_an_unknown_object
[] = "an unknown object";
873 do_look(mode
, click_cc
)
877 boolean quick
= (mode
== 1); /* use cursor; don't search for "more info" */
878 boolean clicklook
= (mode
== 2); /* right mouse-click method */
880 const char *firstmatch
= 0;
881 struct permonst
*pm
= 0;
882 int i
= '\0', ans
= 0;
883 int sym
; /* typed symbol or converted glyph */
884 int found
; /* count of matching syms found */
885 coord cc
; /* screen pos of unknown glyph */
886 boolean save_verbose
; /* saved value of flags.verbose */
887 boolean from_screen
; /* question from the screen */
891 from_screen
= TRUE
; /* yes, we want to use the cursor */
896 menu_item
*pick_list
= (menu_item
*) 0;
901 win
= create_nhwindow(NHW_MENU
);
904 /* 'y' and 'n' to keep backwards compatibility with previous
905 versions: "Specify unknown object by cursor?" */
906 add_menu(win
, NO_GLYPH
, &any
,
907 flags
.lootabc
? 0 : any
.a_char
, 'y', ATR_NONE
,
908 "something on the map", MENU_UNSELECTED
);
910 add_menu(win
, NO_GLYPH
, &any
,
911 flags
.lootabc
? 0 : any
.a_char
, 0, ATR_NONE
,
912 "something you're carrying", MENU_UNSELECTED
);
914 add_menu(win
, NO_GLYPH
, &any
,
915 flags
.lootabc
? 0 : any
.a_char
, 'n', ATR_NONE
,
916 "something else (by symbol or name)", MENU_UNSELECTED
);
917 if (!u
.uswallow
&& !Hallucination
) {
919 add_menu(win
, NO_GLYPH
, &any
, 0, 0, ATR_NONE
,
920 "", MENU_UNSELECTED
);
921 /* these options work sensibly for the swallowed case,
922 but there's no reason for the player to use them then;
923 objects work fine when hallucinating, but screen
924 symbol/monster class letter doesn't match up with
925 bogus monster type, so suppress when hallucinating */
927 add_menu(win
, NO_GLYPH
, &any
,
928 flags
.lootabc
? 0 : any
.a_char
, 0, ATR_NONE
,
929 "nearby monsters", MENU_UNSELECTED
);
931 add_menu(win
, NO_GLYPH
, &any
,
932 flags
.lootabc
? 0 : any
.a_char
, 0, ATR_NONE
,
933 "all monsters shown on map", MENU_UNSELECTED
);
935 add_menu(win
, NO_GLYPH
, &any
,
936 flags
.lootabc
? 0 : any
.a_char
, 0, ATR_NONE
,
937 "nearby objects", MENU_UNSELECTED
);
939 add_menu(win
, NO_GLYPH
, &any
,
940 flags
.lootabc
? 0 : any
.a_char
, 0, ATR_NONE
,
941 "all objects shown on map", MENU_UNSELECTED
);
943 end_menu(win
, "What do you want to look at:");
944 if (select_menu(win
, PICK_ONE
, &pick_list
) > 0) {
945 i
= pick_list
->item
.a_char
;
946 free((genericptr_t
) pick_list
);
948 destroy_nhwindow(win
);
967 invlet
= display_inventory((const char *) 0, TRUE
);
968 if (!invlet
|| invlet
== '\033')
971 for (invobj
= invent
; invobj
; invobj
= invobj
->nobj
)
972 if (invobj
->invlet
== invlet
) {
973 strcpy(out_str
, singular(invobj
, xname
));
977 checkfile(out_str
, pm
, TRUE
, TRUE
);
982 getlin("Specify what? (type the word)", out_str
);
983 if (out_str
[0] == '\0' || out_str
[0] == '\033')
986 if (out_str
[1]) { /* user typed in a complete string */
987 checkfile(out_str
, pm
, TRUE
, TRUE
);
993 look_all(TRUE
, TRUE
); /* list nearby monsters */
996 look_all(FALSE
, TRUE
); /* list all monsters */
999 look_all(TRUE
, FALSE
); /* list nearby objects */
1002 look_all(FALSE
, FALSE
); /* list all objects */
1005 } else { /* clicklook */
1009 from_screen
= FALSE
;
1012 /* Save the verbose flag, we change it later. */
1013 save_verbose
= flags
.verbose
;
1014 flags
.verbose
= flags
.verbose
&& !quick
;
1016 * The user typed one letter, or we're identifying from the screen.
1019 /* Reset some variables. */
1020 pm
= (struct permonst
*) 0;
1024 if (from_screen
|| clicklook
) {
1027 pline("Please move the cursor to %s.",
1028 what_is_an_unknown_object
);
1030 pline("Pick an object.");
1032 ans
= getpos(&cc
, quick
, what_is_an_unknown_object
);
1033 if (ans
< 0 || cc
.x
< 0) {
1034 flags
.verbose
= save_verbose
;
1035 return 0; /* done */
1037 flags
.verbose
= FALSE
; /* only print long question once */
1041 found
= do_screen_description(cc
, (from_screen
|| clicklook
), sym
,
1042 out_str
, &firstmatch
);
1044 /* Finally, print out our explanation. */
1046 /* Used putmixed() because there may be an encoded glyph present
1048 putmixed(WIN_MESSAGE
, 0, out_str
);
1050 /* check the data file for information about this thing */
1051 if (found
== 1 && ans
!= LOOK_QUICK
&& ans
!= LOOK_ONCE
1052 && (ans
== LOOK_VERBOSE
|| (flags
.help
&& !quick
))
1054 char temp_buf
[BUFSZ
];
1056 Strcpy(temp_buf
, firstmatch
);
1057 checkfile(temp_buf
, pm
, FALSE
,
1058 (boolean
) (ans
== LOOK_VERBOSE
));
1061 pline("I've never heard of such things.");
1064 } while (from_screen
&& !quick
&& ans
!= LOOK_ONCE
&& !clicklook
);
1066 flags
.verbose
= save_verbose
;
1071 look_all(nearby
, do_mons
)
1072 boolean nearby
; /* True => within BOLTLIM, False => entire map */
1073 boolean do_mons
; /* True => monsters, False => objects */
1076 int x
, y
, lo_x
, lo_y
, hi_x
, hi_y
, glyph
, count
= 0;
1077 char lookbuf
[BUFSZ
], outbuf
[BUFSZ
];
1079 win
= create_nhwindow(NHW_TEXT
);
1080 lo_y
= nearby
? max(u
.uy
- BOLT_LIM
, 0) : 0;
1081 lo_x
= nearby
? max(u
.ux
- BOLT_LIM
, 1) : 1;
1082 hi_y
= nearby
? min(u
.uy
+ BOLT_LIM
, ROWNO
- 1) : ROWNO
- 1;
1083 hi_x
= nearby
? min(u
.ux
+ BOLT_LIM
, COLNO
- 1) : COLNO
- 1;
1084 for (y
= lo_y
; y
<= hi_y
; y
++) {
1085 for (x
= lo_x
; x
<= hi_x
; x
++) {
1087 glyph
= glyph_at(x
, y
);
1089 if (glyph_is_monster(glyph
)) {
1092 bhitpos
.x
= x
; /* [is this actually necessary?] */
1094 if (x
== u
.ux
&& y
== u
.uy
&& canspotself()) {
1095 (void) self_lookat(lookbuf
);
1097 } else if ((mtmp
= m_at(x
, y
)) != 0) {
1098 look_at_monster(lookbuf
, (char *) 0, mtmp
, x
, y
);
1101 } else if (glyph_is_invisible(glyph
)) {
1102 /* remembered, unseen, creature */
1103 Strcpy(lookbuf
, invisexplain
);
1105 } else if (glyph_is_warning(glyph
)) {
1106 int warnindx
= glyph_to_warning(glyph
);
1108 Strcpy(lookbuf
, def_warnsyms
[warnindx
].explanation
);
1111 } else { /* !do_mons */
1112 if (glyph_is_object(glyph
)) {
1113 look_at_object(lookbuf
, x
, y
, glyph
);
1118 char coordbuf
[20], which
[12], cmode
;
1120 cmode
= (iflags
.getpos_coords
!= GPCOORDS_NONE
)
1121 ? iflags
.getpos_coords
: GPCOORDS_MAP
;
1123 Strcpy(which
, do_mons
? "monsters" : "objects");
1125 Sprintf(outbuf
, "%s currently shown near %s:",
1127 (cmode
!= GPCOORDS_COMPASS
)
1128 ? coord_desc(u
.ux
, u
.uy
, coordbuf
, cmode
)
1129 : !canspotself() ? "your position" : "you");
1131 Sprintf(outbuf
, "All %s currently shown on the map:",
1133 putstr(win
, 0, outbuf
);
1136 /* prefix: "coords C " where 'C' is mon or obj symbol */
1137 Sprintf(outbuf
, (cmode
== GPCOORDS_SCREEN
) ? "%s "
1138 : (cmode
== GPCOORDS_MAP
) ? "%8s "
1140 coord_desc(x
, y
, coordbuf
, cmode
));
1141 Sprintf(eos(outbuf
), "%s ", encglyph(glyph
));
1142 /* guard against potential overflow */
1143 lookbuf
[sizeof lookbuf
- 1 - strlen(outbuf
)] = '\0';
1144 Strcat(outbuf
, lookbuf
);
1145 putmixed(win
, 0, outbuf
);
1150 display_nhwindow(win
, TRUE
);
1152 pline("No %s are currently shown %s.",
1153 do_mons
? "monsters" : "objects",
1154 nearby
? "nearby" : "on the map");
1155 destroy_nhwindow(win
);
1158 /* the '/' command */
1162 return do_look(0, (coord
*) 0);
1165 /* the ';' command */
1169 return do_look(1, (coord
*) 0);
1172 /* the '^' command */
1176 register struct trap
*trap
;
1183 for (trap
= ftrap
; trap
; trap
= trap
->ntrap
)
1184 if (trap
->tx
== x
&& trap
->ty
== y
) {
1189 if (u
.dz
< 0 ? (tt
== TRAPDOOR
|| tt
== HOLE
)
1194 pline("That is %s%s%s.",
1195 an(defsyms
[trap_to_defsym(tt
)].explanation
),
1200 /* trap doors & spiked pits can't be made by
1201 player, and should be considered at least
1202 as much "set" as "dug" anyway */
1203 : (tt
== HOLE
|| tt
== PIT
)
1206 !trap
->madeby_u
? "" : " by you");
1209 pline("I can't see a trap there.");
1214 dowhatdoes_core(q
, cbuf
)
1220 register char *buf
= &bufr
[6], ctrl
, meta
;
1222 fp
= dlb_fopen(CMDHELPFILE
, "r");
1224 pline("Cannot open data file!");
1228 ctrl
= ((q
<= '\033') ? (q
- 1 + 'A') : 0);
1229 meta
= ((0x80 & q
) ? (0x7f & q
) : 0);
1230 while (dlb_fgets(buf
, BUFSZ
- 6, fp
)) {
1231 if ((ctrl
&& *buf
== '^' && *(buf
+ 1) == ctrl
)
1232 || (meta
&& *buf
== 'M' && *(buf
+ 1) == '-'
1233 && *(buf
+ 2) == meta
) || *buf
== q
) {
1234 (void) strip_newline(buf
);
1235 if (ctrl
&& buf
[2] == '\t') {
1237 (void) strncpy(buf
, "^? ", 8);
1239 } else if (meta
&& buf
[3] == '\t') {
1241 (void) strncpy(buf
, "M-? ", 8);
1243 } else if (buf
[1] == '\t') {
1246 (void) strncpy(buf
+ 1, " ", 7);
1248 (void) dlb_fclose(fp
);
1253 (void) dlb_fclose(fp
);
1263 #if defined(UNIX) || defined(VMS)
1266 q
= yn_function("What command?", (char *) 0, '\0');
1267 #if defined(UNIX) || defined(VMS)
1270 reslt
= dowhatdoes_core(q
, bufr
);
1274 pline("I've never heard of such commands.");
1281 winid cwin
= create_nhwindow(NHW_TEXT
);
1284 if (sysopt
.support
) {
1285 /*XXX overflow possibilities*/
1286 Sprintf(buf
, "To contact local support, %s", sysopt
.support
);
1287 putstr(cwin
, 0, buf
);
1288 putstr(cwin
, 0, "");
1289 } else if (sysopt
.fmtd_wizard_list
) { /* formatted SYSCF WIZARDS */
1290 Sprintf(buf
, "To contact local support, contact %s.",
1291 sysopt
.fmtd_wizard_list
);
1292 putstr(cwin
, 0, buf
);
1293 putstr(cwin
, 0, "");
1295 putstr(cwin
, 0, "To contact the NetHack development team directly,");
1296 /*XXX overflow possibilities*/
1297 Sprintf(buf
, "see the 'Contact' form on our website or email <%s>.",
1299 putstr(cwin
, 0, buf
);
1300 putstr(cwin
, 0, "");
1301 putstr(cwin
, 0, "For more information on NetHack, or to report a bug,");
1302 Sprintf(buf
, "visit our website \"%s\".", DEVTEAM_URL
);
1303 putstr(cwin
, 0, buf
);
1304 display_nhwindow(cwin
, FALSE
);
1305 destroy_nhwindow(cwin
);
1308 /* data for help_menu() */
1309 static const char *help_menu_items
[] = {
1310 /* 0*/ "About NetHack (version information).",
1311 /* 1*/ "Long description of the game and commands.",
1312 /* 2*/ "List of game commands.",
1313 /* 3*/ "Concise history of NetHack.",
1314 /* 4*/ "Info on a character in the game display.",
1315 /* 5*/ "Info on what a given key does.",
1316 /* 6*/ "List of game options.",
1317 /* 7*/ "Longer explanation of game options.",
1318 /* 8*/ "List of extended commands.",
1319 /* 9*/ "The NetHack license.",
1320 /* 10*/ "Support information.",
1322 "%s-specific help and commands.",
1323 #define PORT_HELP_ID 100
1324 #define WIZHLP_SLOT 12
1326 #define WIZHLP_SLOT 11
1328 "List of wizard-mode commands.", "", (char *) 0
1335 winid tmpwin
= create_nhwindow(NHW_MENU
);
1337 char helpbuf
[QBUFSZ
];
1340 menu_item
*selected
;
1343 any
= zeroany
; /* zero all bits */
1346 help_menu_items
[WIZHLP_SLOT
] = "",
1347 help_menu_items
[WIZHLP_SLOT
+ 1] = (char *) 0;
1348 for (i
= 0; help_menu_items
[i
]; i
++)
1350 /* port-specific line has a %s in it for the PORT_ID */
1351 if (help_menu_items
[i
][0] == '%') {
1352 Sprintf(helpbuf
, help_menu_items
[i
], PORT_ID
);
1353 any
.a_int
= PORT_HELP_ID
+ 1;
1354 add_menu(tmpwin
, NO_GLYPH
, &any
, 0, 0, ATR_NONE
, helpbuf
,
1359 any
.a_int
= (*help_menu_items
[i
]) ? i
+ 1 : 0;
1360 add_menu(tmpwin
, NO_GLYPH
, &any
, 0, 0, ATR_NONE
,
1361 help_menu_items
[i
], MENU_UNSELECTED
);
1363 end_menu(tmpwin
, "Select one item:");
1364 n
= select_menu(tmpwin
, PICK_ONE
, &selected
);
1365 destroy_nhwindow(tmpwin
);
1367 *sel
= selected
[0].item
.a_int
- 1;
1368 free((genericptr_t
) selected
);
1374 /* the '?' command */
1380 if (help_menu(&sel
)) {
1383 (void) doextversion();
1386 display_file(HELP
, TRUE
);
1389 display_file(SHELP
, TRUE
);
1398 (void) dowhatdoes();
1404 display_file(OPTIONFILE
, TRUE
);
1410 display_file(LICENSE
, TRUE
);
1421 /* handle slot 11 or 12 */
1422 display_file(DEBUGHELP
, TRUE
);
1429 /* the 'V' command; also a choice for '?' */
1433 display_file(HISTORY
, TRUE
);