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 */
11 STATIC_DCL boolean
FDECL(is_swallow_sym
, (int));
12 STATIC_DCL
int FDECL(append_str
, (char *, const char *));
13 STATIC_DCL
void FDECL(look_at_object
, (char *, int, int, int));
14 STATIC_DCL
void FDECL(look_at_monster
, (char *, char *,
15 struct monst
*, int, int));
16 STATIC_DCL
struct permonst
*FDECL(lookat
, (int, int, char *, char *));
17 STATIC_DCL
void FDECL(checkfile
, (char *, struct permonst
*,
18 BOOLEAN_P
, BOOLEAN_P
));
19 STATIC_DCL
void FDECL(look_all
, (BOOLEAN_P
,BOOLEAN_P
));
20 STATIC_DCL
void NDECL(whatdoes_help
);
21 STATIC_DCL
void NDECL(docontact
);
22 STATIC_DCL
void NDECL(dispfile_help
);
23 STATIC_DCL
void NDECL(dispfile_shelp
);
24 STATIC_DCL
void NDECL(dispfile_optionfile
);
25 STATIC_DCL
void NDECL(dispfile_license
);
26 STATIC_DCL
void NDECL(dispfile_debughelp
);
27 STATIC_DCL
void NDECL(hmenu_doextversion
);
28 STATIC_DCL
void NDECL(hmenu_dohistory
);
29 STATIC_DCL
void NDECL(hmenu_dowhatis
);
30 STATIC_DCL
void NDECL(hmenu_dowhatdoes
);
31 STATIC_DCL
void NDECL(hmenu_doextlist
);
33 extern void NDECL(port_help
);
36 /* Returns "true" for characters that could represent a monster's stomach. */
43 for (i
= S_sw_tl
; i
<= S_sw_br
; i
++)
44 if ((int) showsyms
[i
] == c
)
50 * Append new_str to the end of buf if new_str doesn't already exist as
51 * a substring of buf. Return 1 if the string was appended, 0 otherwise.
52 * It is expected that buf is of size BUFSZ.
55 append_str(buf
, new_str
)
59 int space_left
; /* space remaining in buf */
61 if (strstri(buf
, new_str
))
64 space_left
= BUFSZ
- strlen(buf
) - 1;
67 (void) strncat(buf
, " or ", space_left
);
68 (void) strncat(buf
, new_str
, space_left
- 4);
72 /* shared by monster probing (via query_objlist!) as well as lookat() */
79 /* include race with role unless polymorphed */
82 Sprintf(race
, "%s ", urace
.adj
);
83 Sprintf(outbuf
, "%s%s%s called %s",
84 /* being blinded may hide invisibility from self */
85 (Invis
&& (senseself() || !Blind
)) ? "invisible " : "", race
,
86 mons
[u
.umonnum
].mname
, plname
);
88 Sprintf(eos(outbuf
), ", mounted on %s", y_monnam(u
.usteed
));
89 if (u
.uundetected
|| (Upolyd
&& youmonst
.m_ap_type
))
90 mhidden_description(&youmonst
, FALSE
, eos(outbuf
));
94 /* describe a hidden monster; used for look_at during extended monster
95 detection and for probing; also when looking at self */
97 mhidden_description(mon
, altmon
, outbuf
)
99 boolean altmon
; /* for probing: if mimicking a monster, say so */
103 boolean fakeobj
, isyou
= (mon
== &youmonst
);
104 int x
= isyou
? u
.ux
: mon
->mx
, y
= isyou
? u
.uy
: mon
->my
,
105 glyph
= (level
.flags
.hero_memory
&& !isyou
) ? levl
[x
][y
].glyph
109 if (mon
->m_ap_type
== M_AP_FURNITURE
110 || mon
->m_ap_type
== M_AP_OBJECT
) {
111 Strcpy(outbuf
, ", mimicking ");
112 if (mon
->m_ap_type
== M_AP_FURNITURE
) {
113 Strcat(outbuf
, an(defsyms
[mon
->mappearance
].explanation
));
114 } else if (mon
->m_ap_type
== M_AP_OBJECT
115 /* remembered glyph, not glyph_at() which is 'mon' */
116 && glyph_is_object(glyph
)) {
118 otmp
= (struct obj
*) 0;
119 fakeobj
= object_from_map(glyph
, x
, y
, &otmp
);
120 Strcat(outbuf
, (otmp
&& otmp
->otyp
!= STRANGE_OBJECT
)
121 ? ansimpleoname(otmp
)
122 : an(obj_descr
[STRANGE_OBJECT
].oc_name
));
126 Strcat(outbuf
, something
);
128 } else if (mon
->m_ap_type
== M_AP_MONSTER
) {
130 Sprintf(outbuf
, ", masquerading as %s",
131 an(mons
[mon
->mappearance
].mname
));
132 } else if (isyou
? u
.uundetected
: mon
->mundetected
) {
133 Strcpy(outbuf
, ", hiding");
134 if (hides_under(mon
->data
)) {
135 Strcat(outbuf
, " under ");
136 /* remembered glyph, not glyph_at() which is 'mon' */
137 if (glyph_is_object(glyph
))
139 Strcat(outbuf
, something
);
140 } else if (is_hider(mon
->data
)) {
141 Sprintf(eos(outbuf
), " on the %s",
142 (is_flyer(mon
->data
) || mon
->data
->mlet
== S_PIERCER
)
144 : surface(x
, y
)); /* trapper */
146 if (mon
->data
->mlet
== S_EEL
&& is_pool(x
, y
))
147 Strcat(outbuf
, " in murky water");
152 /* extracted from lookat(); also used by namefloorobj() */
154 object_from_map(glyph
, x
, y
, obj_p
)
158 boolean fakeobj
= FALSE
;
161 int glyphotyp
= glyph_to_obj(glyph
);
163 *obj_p
= (struct obj
*) 0;
164 /* TODO: check inside containers in case glyph came from detection */
165 if ((otmp
= sobj_at(glyphotyp
, x
, y
)) == 0)
166 for (otmp
= level
.buriedobjlist
; otmp
; otmp
= otmp
->nobj
)
167 if (otmp
->ox
== x
&& otmp
->oy
== y
&& otmp
->otyp
== glyphotyp
)
170 /* there might be a mimic here posing as an object */
172 if (mtmp
&& is_obj_mappear(mtmp
, (unsigned) glyphotyp
))
177 if (!otmp
|| otmp
->otyp
!= glyphotyp
) {
178 /* this used to exclude STRANGE_OBJECT; now caller deals with it */
179 otmp
= mksobj(glyphotyp
, FALSE
, FALSE
);
183 if (otmp
->oclass
== COIN_CLASS
)
184 otmp
->quan
= 2L; /* to force pluralization */
185 else if (otmp
->otyp
== SLIME_MOLD
)
186 otmp
->spe
= context
.current_fruit
; /* give it a type */
187 if (mtmp
&& has_mcorpsenm(mtmp
)) /* mimic as corpse/statue */
188 otmp
->corpsenm
= MCORPSENM(mtmp
);
190 /* if located at adjacent spot, mark it as having been seen up close
191 (corpse type will be known even if dknown is 0, so we don't need a
192 touch check for cockatrice corpse--we're looking without touching) */
193 if (otmp
&& distu(x
, y
) <= 2 && !Blind
&& !Hallucination
194 /* redundant: we only look for an object which matches current
195 glyph among floor and buried objects; when !Blind, any buried
196 object's glyph will have been replaced by whatever is present
197 on the surface as soon as we moved next to its spot */
198 && otmp
->where
== OBJ_FLOOR
/* not buried */
199 /* terrain mode views what's already known, doesn't learn new stuff */
200 && !iflags
.terrainmode
) /* so don't set dknown when in terrain mode */
201 otmp
->dknown
= 1; /* if a pile, clearly see the top item only */
204 return fakeobj
; /* when True, caller needs to dealloc *obj_p */
208 look_at_object(buf
, x
, y
, glyph
)
209 char *buf
; /* output buffer */
212 struct obj
*otmp
= 0;
213 boolean fakeobj
= object_from_map(glyph
, x
, y
, &otmp
);
216 Strcpy(buf
, (otmp
->otyp
!= STRANGE_OBJECT
)
217 ? distant_name(otmp
, doname_vague_quan
)
218 : obj_descr
[STRANGE_OBJECT
].oc_name
);
220 dealloc_obj(otmp
), otmp
= 0;
222 Strcpy(buf
, something
); /* sanity precaution */
224 if (otmp
&& otmp
->where
== OBJ_BURIED
)
225 Strcat(buf
, " (buried)");
226 else if (levl
[x
][y
].typ
== STONE
|| levl
[x
][y
].typ
== SCORR
)
227 Strcat(buf
, " embedded in stone");
228 else if (IS_WALL(levl
[x
][y
].typ
) || levl
[x
][y
].typ
== SDOOR
)
229 Strcat(buf
, " embedded in a wall");
230 else if (closed_door(x
, y
))
231 Strcat(buf
, " embedded in a door");
232 else if (is_pool(x
, y
))
233 Strcat(buf
, " in water");
234 else if (is_lava(x
, y
))
235 Strcat(buf
, " in molten lava"); /* [can this ever happen?] */
240 look_at_monster(buf
, monbuf
, mtmp
, x
, y
)
241 char *buf
, *monbuf
; /* buf: output, monbuf: optional output */
245 char *name
, monnambuf
[BUFSZ
];
246 boolean accurate
= !Hallucination
;
248 name
= (mtmp
->data
== &mons
[PM_COYOTE
] && accurate
)
249 ? coyotename(mtmp
, monnambuf
)
250 : distant_monnam(mtmp
, ARTICLE_NONE
, monnambuf
);
251 Sprintf(buf
, "%s%s%s",
252 (mtmp
->mx
!= x
|| mtmp
->my
!= y
)
253 ? ((mtmp
->isshk
&& accurate
) ? "tail of " : "tail of a ")
255 (mtmp
->mtame
&& accurate
)
257 : (mtmp
->mpeaceful
&& accurate
)
261 if (u
.ustuck
== mtmp
) {
262 if (u
.uswallow
|| iflags
.save_uswallow
) /* monster detection */
263 Strcat(buf
, is_animal(mtmp
->data
)
264 ? ", swallowing you" : ", engulfing you");
266 Strcat(buf
, (Upolyd
&& sticks(youmonst
.data
))
267 ? ", being held" : ", holding you");
270 Strcat(buf
, ", leashed to you");
272 if (mtmp
->mtrapped
&& cansee(mtmp
->mx
, mtmp
->my
)) {
273 struct trap
*t
= t_at(mtmp
->mx
, mtmp
->my
);
274 int tt
= t
? t
->ttyp
: NO_TRAP
;
276 /* newsym lets you know of the trap, so mention it here */
277 if (tt
== BEAR_TRAP
|| tt
== PIT
|| tt
== SPIKED_PIT
|| tt
== WEB
)
278 Sprintf(eos(buf
), ", trapped in %s",
279 an(defsyms
[trap_to_defsym(tt
)].explanation
));
282 /* we know the hero sees a monster at this location, but if it's shown
283 due to persistant monster detection he might remember something else */
284 if (mtmp
->mundetected
|| mtmp
->m_ap_type
)
285 mhidden_description(mtmp
, FALSE
, eos(buf
));
288 unsigned how_seen
= howmonseen(mtmp
);
291 if (how_seen
!= 0 && how_seen
!= MONSEEN_NORMAL
) {
292 if (how_seen
& MONSEEN_NORMAL
) {
293 Strcat(monbuf
, "normal vision");
294 how_seen
&= ~MONSEEN_NORMAL
;
295 /* how_seen can't be 0 yet... */
297 Strcat(monbuf
, ", ");
299 if (how_seen
& MONSEEN_SEEINVIS
) {
300 Strcat(monbuf
, "see invisible");
301 how_seen
&= ~MONSEEN_SEEINVIS
;
303 Strcat(monbuf
, ", ");
305 if (how_seen
& MONSEEN_INFRAVIS
) {
306 Strcat(monbuf
, "infravision");
307 how_seen
&= ~MONSEEN_INFRAVIS
;
309 Strcat(monbuf
, ", ");
311 if (how_seen
& MONSEEN_TELEPAT
) {
312 Strcat(monbuf
, "telepathy");
313 how_seen
&= ~MONSEEN_TELEPAT
;
315 Strcat(monbuf
, ", ");
317 if (how_seen
& MONSEEN_XRAYVIS
) {
318 /* Eyes of the Overworld */
319 Strcat(monbuf
, "astral vision");
320 how_seen
&= ~MONSEEN_XRAYVIS
;
322 Strcat(monbuf
, ", ");
324 if (how_seen
& MONSEEN_DETECT
) {
325 Strcat(monbuf
, "monster detection");
326 how_seen
&= ~MONSEEN_DETECT
;
328 Strcat(monbuf
, ", ");
330 if (how_seen
& MONSEEN_WARNMON
) {
332 Strcat(monbuf
, "paranoid delusion");
334 Sprintf(eos(monbuf
), "warned of %s",
335 makeplural(mtmp
->data
->mname
));
336 how_seen
&= ~MONSEEN_WARNMON
;
338 Strcat(monbuf
, ", ");
340 /* should have used up all the how_seen bits by now */
342 impossible("lookat: unknown method of seeing monster");
343 Sprintf(eos(monbuf
), "(%u)", how_seen
);
345 } /* seen by something other than normal vision */
346 } /* monbuf is non-null */
350 * Return the name of the glyph found at (x,y).
351 * If not hallucinating and the glyph is a monster, also monster data.
353 STATIC_OVL
struct permonst
*
354 lookat(x
, y
, buf
, monbuf
)
358 struct monst
*mtmp
= (struct monst
*) 0;
359 struct permonst
*pm
= (struct permonst
*) 0;
362 buf
[0] = monbuf
[0] = '\0';
363 glyph
= glyph_at(x
, y
);
364 if (u
.ux
== x
&& u
.uy
== y
&& canspotself()
365 && !(iflags
.save_uswallow
&& glyph
== mon_to_glyph(u
.ustuck
))
366 && (!iflags
.terrainmode
|| (iflags
.terrainmode
& TER_MON
) != 0)) {
368 (void) self_lookat(buf
);
370 /* file lookup can't distinguish between "gnomish wizard" monster
371 and correspondingly named player character, always picking the
372 former; force it to find the general "wizard" entry instead */
373 if (Role_if(PM_WIZARD
) && Race_if(PM_GNOME
) && !Upolyd
)
374 pm
= &mons
[PM_WIZARD
];
376 /* When you see yourself normally, no explanation is appended
377 (even if you could also see yourself via other means).
378 Sensing self while blind or swallowed is treated as if it
379 were by normal vision (cf canseeself()). */
380 if ((Invisible
|| u
.uundetected
) && !Blind
381 && !(u
.uswallow
|| iflags
.save_uswallow
)) {
392 Sprintf(eos(buf
), " [seen: %s%s%s%s%s]",
393 (how
& 1) ? "infravision" : "",
394 /* add comma if telep and infrav */
395 ((how
& 3) > 2) ? ", " : "",
396 (how
& 2) ? "telepathy" : "",
397 /* add comma if detect and (infrav or telep or both) */
398 ((how
& 7) > 4) ? ", " : "",
399 (how
& 4) ? "monster detection" : "");
401 } else if (u
.uswallow
) {
402 /* when swallowed, we're only called for spots adjacent to hero,
403 and blindness doesn't prevent hero from feeling what holds him */
404 Sprintf(buf
, "interior of %s", a_monnam(u
.ustuck
));
406 } else if (glyph_is_monster(glyph
)) {
409 if ((mtmp
= m_at(x
, y
)) != 0) {
410 look_at_monster(buf
, monbuf
, mtmp
, x
, y
);
413 } else if (glyph_is_object(glyph
)) {
414 look_at_object(buf
, x
, y
, glyph
); /* fill in buf[] */
415 } else if (glyph_is_trap(glyph
)) {
416 int tnum
= what_trap(glyph_to_trap(glyph
));
418 /* Trap detection displays a bear trap at locations having
419 * a trapped door or trapped container or both.
420 * TODO: we should create actual trap types for doors and
421 * chests so that they can have their own glyphs and tiles.
423 if (trapped_chest_at(tnum
, x
, y
))
424 Strcpy(buf
, "trapped chest"); /* might actually be a large box */
425 else if (trapped_door_at(tnum
, x
, y
))
426 Strcpy(buf
, "trapped door"); /* not "trap door"... */
428 Strcpy(buf
, defsyms
[trap_to_defsym(tnum
)].explanation
);
429 } else if (glyph_is_warning(glyph
)) {
430 int warnindx
= glyph_to_warning(glyph
);
432 Strcpy(buf
, def_warnsyms
[warnindx
].explanation
);
433 } else if (!glyph_is_cmap(glyph
)) {
434 Strcpy(buf
, "unexplored area");
436 switch (glyph_to_cmap(glyph
)) {
438 Sprintf(buf
, "%s %saltar",
439 /* like endgame high priests, endgame high altars
440 are only recognizable when immediately adjacent */
441 (Is_astralevel(&u
.uz
) && distu(x
, y
) > 2)
444 Amask2align(levl
[x
][y
].altarmask
& ~AM_SHRINE
)),
445 ((levl
[x
][y
].altarmask
& AM_SHRINE
)
446 && (Is_astralevel(&u
.uz
) || Is_sanctum(&u
.uz
)))
451 if (is_drawbridge_wall(x
, y
) >= 0)
452 Strcpy(buf
, "open drawbridge portcullis");
453 else if ((levl
[x
][y
].doormask
& ~D_TRAPPED
) == D_BROKEN
)
454 Strcpy(buf
, "broken door");
456 Strcpy(buf
, "doorway");
460 Is_airlevel(&u
.uz
) ? "cloudy area" : "fog/vapor cloud");
463 if (!levl
[x
][y
].seenv
) {
464 Strcpy(buf
, "unexplored");
466 } else if (Underwater
&& !Is_waterlevel(&u
.uz
)) {
467 /* "unknown" == previously mapped but not visible when
468 submerged; better terminology appreciated... */
469 Strcpy(buf
, (distu(x
, y
) <= 2) ? "land" : "unknown");
471 } else if (levl
[x
][y
].typ
== STONE
|| levl
[x
][y
].typ
== SCORR
) {
472 Strcpy(buf
, "stone");
477 Strcpy(buf
, defsyms
[glyph_to_cmap(glyph
)].explanation
);
481 return (pm
&& !Hallucination
) ? pm
: (struct permonst
*) 0;
485 * Look in the "data" file for more info. Called if the user typed in the
486 * whole name (user_typed_name == TRUE), or we've found a possible match
487 * with a character/glyph and flags.help is TRUE.
489 * NOTE: when (user_typed_name == FALSE), inp is considered read-only and
490 * must not be changed directly, e.g. via lcase(). We want to force
491 * lcase() for data.base lookup so that we can have a clean key.
492 * Therefore, we create a copy of inp _just_ for data.base lookup.
495 checkfile(inp
, pm
, user_typed_name
, without_asking
)
498 boolean user_typed_name
, without_asking
;
501 char buf
[BUFSZ
], newstr
[BUFSZ
], givenname
[BUFSZ
];
502 char *ep
, *dbase_str
;
503 unsigned long txt_offset
= 0L;
504 int chk_skip
, pass
= 1;
505 boolean found_in_file
= FALSE
, skipping_entry
= FALSE
, yes_to_moreinfo
;
506 winid datawin
= WIN_ERR
;
508 fp
= dlb_fopen(DATAFILE
, "r");
510 pline("Cannot open data file!");
515 * If someone passed us garbage, prevent fault.
517 if (!inp
|| (inp
&& strlen(inp
) > (BUFSZ
- 1))) {
518 pline("bad do_look buffer passed!");
522 /* To prevent the need for entries in data.base like *ngel to account
523 * for Angel and angel, make the lookup string the same for both
524 * user_typed_name and picked name.
526 if (pm
!= (struct permonst
*) 0 && !user_typed_name
)
527 dbase_str
= strcpy(newstr
, pm
->mname
);
529 dbase_str
= strcpy(newstr
, inp
);
530 (void) lcase(dbase_str
);
532 if (!strncmp(dbase_str
, "interior of ", 12))
534 if (!strncmp(dbase_str
, "a ", 2))
536 else if (!strncmp(dbase_str
, "an ", 3))
538 else if (!strncmp(dbase_str
, "the ", 4))
540 if (!strncmp(dbase_str
, "tame ", 5))
542 else if (!strncmp(dbase_str
, "peaceful ", 9))
544 if (!strncmp(dbase_str
, "invisible ", 10))
546 if (!strncmp(dbase_str
, "saddled ", 8))
548 if (!strncmp(dbase_str
, "statue of ", 10))
550 else if (!strncmp(dbase_str
, "figurine of ", 12))
553 /* Make sure the name is non-empty. */
555 /* adjust the input to remove "named " and convert to lower case */
556 char *alt
= 0; /* alternate description */
558 if ((ep
= strstri(dbase_str
, " named ")) != 0)
560 else if ((ep
= strstri(dbase_str
, " called ")) != 0) {
561 if (strlen(ep
+ 8) < BUFSZ
- 1) {
562 Strcpy(givenname
, ep
+ 8);
563 givenname
[BUFSZ
-1] = '\0';
568 ep
= strstri(dbase_str
, ", ");
569 if (ep
&& ep
> dbase_str
)
573 * If the object is named, then the name is the alternate description;
574 * otherwise, the result of makesingular() applied to the name is.
575 * This isn't strictly optimal, but named objects of interest to the
576 * user will usually be found under their name, rather than under
577 * their object type, so looking for a singular form is pointless.
580 alt
= makesingular(dbase_str
);
582 if (!strcmp(alt
, dbase_str
))
585 for (; pass
>= 0; pass
--) {
587 if (dlb_fseek(fp
, txt_offset
, SEEK_SET
) < 0 ) {
588 impossible("can't get to start of 'data' file");
592 /* skip first record; read second */
593 if (!dlb_fgets(buf
, BUFSZ
, fp
) || !dlb_fgets(buf
, BUFSZ
, fp
)) {
594 impossible("can't read 'data' file");
595 (void) dlb_fclose(fp
);
597 } else if (sscanf(buf
, "%8lx\n", &txt_offset
) < 1
601 /* look for the appropriate entry */
602 while (dlb_fgets(buf
, BUFSZ
, fp
)) {
604 break; /* we passed last entry without success */
607 /* a number indicates the end of current entry */
608 skipping_entry
= FALSE
;
609 } else if (!skipping_entry
) {
610 if (!(ep
= index(buf
, '\n')))
612 (void) strip_newline((ep
> buf
) ? ep
- 1 : ep
);
613 /* if we match a key that begins with "~", skip
615 chk_skip
= (*buf
== '~') ? 1 : 0;
616 if ((pass
== 0 && pmatch(&buf
[chk_skip
], dbase_str
))
617 || (pass
== 1 && alt
&& pmatch(&buf
[chk_skip
], alt
))) {
619 skipping_entry
= TRUE
;
622 found_in_file
= TRUE
;
633 /* skip over other possible matches for the info */
635 if (!dlb_fgets(buf
, BUFSZ
, fp
))
637 } while (!digit(*buf
));
638 if (sscanf(buf
, "%ld,%d\n", &entry_offset
, &entry_count
) < 2) {
640 impossible("'data' file in wrong format or corrupted");
641 /* window will exist if came here from below via 'goto' */
642 if (datawin
!= WIN_ERR
)
643 destroy_nhwindow(datawin
);
644 (void) dlb_fclose(fp
);
648 yes_to_moreinfo
= FALSE
;
649 if (!user_typed_name
&& !without_asking
) {
650 unsigned maxt
= strlen("More info about \"\"?");
651 char *entrytext
= pass
? alt
: dbase_str
;
652 char question
[BUFSZ
];
654 if (strlen(entrytext
) < BUFSZ
- maxt
) {
655 Strcpy(question
, "More info about \"");
656 Strcat(question
, entrytext
);
657 Strcat(question
, "\"?");
659 if (yn(question
) == 'y')
660 yes_to_moreinfo
= TRUE
;
663 if (user_typed_name
|| without_asking
|| yes_to_moreinfo
) {
664 if (dlb_fseek(fp
, (long) txt_offset
+ entry_offset
,
666 pline("? Seek error on 'data' file!");
667 (void) dlb_fclose(fp
);
670 datawin
= create_nhwindow(NHW_MENU
);
671 for (i
= 0; i
< entry_count
; i
++) {
672 if (!dlb_fgets(buf
, BUFSZ
, fp
))
674 (void) strip_newline(buf
);
675 if (index(buf
+ 1, '\t') != 0)
676 (void) tabexpand(buf
+ 1);
677 putstr(datawin
, 0, buf
+ 1);
679 display_nhwindow(datawin
, FALSE
);
680 destroy_nhwindow(datawin
);
682 } else if (user_typed_name
&& pass
== 0)
683 pline("I don't have any information on those things.");
686 (void) dlb_fclose(fp
);
690 do_screen_description(cc
, looked
, sym
, out_str
, firstmatch
)
695 const char **firstmatch
;
697 static const char mon_interior
[] = "the interior of a monster",
698 unreconnoitered
[] = "unreconnoitered";
699 static char look_buf
[BUFSZ
];
701 int i
, alt_i
, glyph
= NO_GLYPH
,
702 skipped_venom
= 0, found
= 0; /* count of matching syms found */
703 boolean hit_trap
, need_to_look
= FALSE
,
704 submerged
= (Underwater
&& !Is_waterlevel(&u
.uz
));
711 glyph
= glyph_at(cc
.x
, cc
.y
);
712 /* Convert glyph at selected position to a symbol for use below. */
713 (void) mapglyph(glyph
, &sym
, &oc
, &os
, cc
.x
, cc
.y
);
715 Sprintf(prefix
, "%s ", encglyph(glyph
));
717 Sprintf(prefix
, "%c ", sym
);
720 * Check all the possibilities, saving all explanations in a buffer.
721 * When all have been checked then the string is printed.
725 * Handle restricted vision range (limited to adjacent spots when
726 * swallowed or underwater) cases first.
728 * 3.6.0 listed anywhere on map, other than self, as "interior
729 * of a monster" when swallowed, and non-adjacent water or
730 * non-water anywhere as "dark part of a room" when underwater.
731 * "unreconnoitered" is an attempt to convey "even if you knew
732 * what was there earlier, you don't know what is there in the
733 * current circumstance".
735 * (Note: 'self' will always be visible when swallowed so we don't
736 * need special swallow handling for <ux,uy>.
737 * Another note: for '#terrain' without monsters, u.uswallow and
738 * submerged will always both be False and skip this code.)
742 ; /* skip special handling */
743 } else if (((u
.uswallow
|| submerged
) && distu(cc
.x
, cc
.y
) > 2)
744 /* detection showing some category, so mostly background */
745 || ((iflags
.terrainmode
& (TER_DETECT
| TER_MAP
)) == TER_DETECT
746 && glyph
== cmap_to_glyph(S_stone
))) {
747 x_str
= unreconnoitered
;
748 need_to_look
= FALSE
;
749 } else if (is_swallow_sym(sym
)) {
750 x_str
= mon_interior
;
751 need_to_look
= TRUE
; /* for specific monster type */
754 /* we know 'found' is zero here, but guard against some other
755 special case being inserted ahead of us someday */
757 Sprintf(out_str
, "%s%s", prefix
, x_str
);
761 found
+= append_str(out_str
, x_str
); /* not 'an(x_str)' */
763 /* for is_swallow_sym(), we want to list the current symbol's
764 other possibilities (wand for '/', throne for '\\', &c) so
765 don't jump to the end for the x_str==mon_interior case */
766 if (x_str
== unreconnoitered
)
770 /* Check for monsters */
771 if (!iflags
.terrainmode
|| (iflags
.terrainmode
& TER_MON
) != 0) {
772 for (i
= 0; i
< MAXMCLASSES
; i
++) {
773 if (sym
== (looked
? showsyms
[i
+ SYM_OFF_M
] : def_monsyms
[i
].sym
)
774 && def_monsyms
[i
].explain
) {
777 Sprintf(out_str
, "%s%s",
778 prefix
, an(def_monsyms
[i
].explain
));
779 *firstmatch
= def_monsyms
[i
].explain
;
782 found
+= append_str(out_str
, an(def_monsyms
[i
].explain
));
786 /* handle '@' as a special case if it refers to you and you're
787 playing a character which isn't normally displayed by that
788 symbol; firstmatch is assumed to already be set for '@' */
789 if ((looked
? (sym
== showsyms
[S_HUMAN
+ SYM_OFF_M
]
790 && cc
.x
== u
.ux
&& cc
.y
== u
.uy
)
791 : (sym
== def_monsyms
[S_HUMAN
].sym
&& !flags
.showrace
))
792 && !(Race_if(PM_HUMAN
) || Race_if(PM_ELF
)) && !Upolyd
)
793 found
+= append_str(out_str
, "you"); /* tack on "or you" */
796 /* Now check for objects */
797 if (!iflags
.terrainmode
|| (iflags
.terrainmode
& TER_OBJ
) != 0) {
798 for (i
= 1; i
< MAXOCLASSES
; i
++) {
799 if (sym
== (looked
? showsyms
[i
+ SYM_OFF_O
]
800 : def_oc_syms
[i
].sym
)
801 || (looked
&& i
== ROCK_CLASS
&& glyph_is_statue(glyph
))) {
803 if (looked
&& i
== VENOM_CLASS
) {
808 Sprintf(out_str
, "%s%s",
809 prefix
, an(def_oc_syms
[i
].explain
));
810 *firstmatch
= def_oc_syms
[i
].explain
;
813 found
+= append_str(out_str
, an(def_oc_syms
[i
].explain
));
819 if (sym
== DEF_INVISIBLE
) {
821 Sprintf(out_str
, "%s%s", prefix
, an(invisexplain
));
822 *firstmatch
= invisexplain
;
825 found
+= append_str(out_str
, an(invisexplain
));
829 /* Now check for graphics symbols */
830 alt_i
= (sym
== (looked
? showsyms
[0] : defsyms
[0].sym
)) ? 0 : (2 + 1);
831 for (hit_trap
= FALSE
, i
= 0; i
< MAXPCHARS
; i
++) {
832 /* when sym is the default background character, we process
833 i == 0 three times: unexplored, stone, dark part of a room */
835 x_str
= !alt_i
++ ? "unexplored" : submerged
? "unknown" : "stone";
836 i
= 0; /* for second iteration, undo loop increment */
837 /* alt_i is now 1 or 2 */
840 i
= 0; /* undo loop increment */
841 x_str
= defsyms
[i
].explanation
;
842 if (submerged
&& !strcmp(x_str
, defsyms
[0].explanation
))
843 x_str
= "land"; /* replace "dark part of a room" */
844 /* alt_i is now 3 or more and no longer of interest */
846 if (sym
== (looked
? showsyms
[i
] : defsyms
[i
].sym
) && *x_str
) {
847 /* avoid "an unexplored", "an stone", "an air", "a water",
848 "a floor of a room", "a dark part of a room";
849 article==2 => "the", 1 => "an", 0 => (none) */
850 int article
= strstri(x_str
, " of a room") ? 2
852 || strcmp(x_str
, "air") == 0
853 || strcmp(x_str
, "land") == 0
854 || strcmp(x_str
, "water") == 0);
857 if (is_cmap_trap(i
)) {
858 Sprintf(out_str
, "%sa trap", prefix
);
861 Sprintf(out_str
, "%s%s", prefix
,
862 article
== 2 ? the(x_str
)
863 : article
== 1 ? an(x_str
) : x_str
);
867 } else if (!(hit_trap
&& is_cmap_trap(i
))
868 && !(found
>= 3 && is_cmap_drawbridge(i
))
869 /* don't mention vibrating square outside of Gehennom
870 unless this happens to be one (hallucination?) */
871 && (i
!= S_vibrating_square
|| Inhell
872 || (looked
&& glyph_is_trap(glyph
)
873 && glyph_to_trap(glyph
) == VIBRATING_SQUARE
))) {
874 found
+= append_str(out_str
, (article
== 2) ? the(x_str
)
875 : (article
== 1) ? an(x_str
)
881 if (i
== S_altar
|| is_cmap_trap(i
))
886 /* Now check for warning symbols */
887 for (i
= 1; i
< WARNCOUNT
; i
++) {
888 x_str
= def_warnsyms
[i
].explanation
;
889 if (sym
== (looked
? warnsyms
[i
] : def_warnsyms
[i
].sym
)) {
891 Sprintf(out_str
, "%s%s", prefix
, def_warnsyms
[i
].explanation
);
892 *firstmatch
= def_warnsyms
[i
].explanation
;
895 found
+= append_str(out_str
, def_warnsyms
[i
].explanation
);
897 /* Kludge: warning trumps boulders on the display.
898 Reveal the boulder too or player can get confused */
899 if (looked
&& sobj_at(BOULDER
, cc
.x
, cc
.y
))
900 Strcat(out_str
, " co-located with a boulder");
901 break; /* out of for loop*/
905 /* if we ignored venom and list turned out to be short, put it back */
906 if (skipped_venom
&& found
< 2) {
907 x_str
= def_oc_syms
[VENOM_CLASS
].explain
;
909 Sprintf(out_str
, "%s%s", prefix
, an(x_str
));
913 found
+= append_str(out_str
, an(x_str
));
917 /* handle optional boulder symbol as a special case */
918 if (iflags
.bouldersym
&& sym
== iflags
.bouldersym
) {
920 *firstmatch
= "boulder";
921 Sprintf(out_str
, "%s%s", prefix
, an(*firstmatch
));
924 found
+= append_str(out_str
, "boulder");
929 * If we are looking at the screen, follow multiple possibilities or
930 * an ambiguous explanation by something more detailed.
934 Sprintf(out_str
, "%s", "That can be many things");
938 if (found
> 1 || need_to_look
) {
940 char temp_buf
[BUFSZ
];
942 (void) lookat(cc
.x
, cc
.y
, look_buf
, monbuf
);
943 *firstmatch
= look_buf
;
944 if (*(*firstmatch
)) {
945 Sprintf(temp_buf
, " (%s)", *firstmatch
);
946 (void) strncat(out_str
, temp_buf
,
947 BUFSZ
- strlen(out_str
) - 1);
948 found
= 1; /* we have something to look up */
951 Sprintf(temp_buf
, " [seen: %s]", monbuf
);
952 (void) strncat(out_str
, temp_buf
,
953 BUFSZ
- strlen(out_str
) - 1);
961 /* also used by getpos hack in do_name.c */
962 const char what_is_an_unknown_object
[] = "an unknown object";
965 do_look(mode
, click_cc
)
969 boolean quick
= (mode
== 1); /* use cursor; don't search for "more info" */
970 boolean clicklook
= (mode
== 2); /* right mouse-click method */
972 const char *firstmatch
= 0;
973 struct permonst
*pm
= 0;
974 int i
= '\0', ans
= 0;
975 int sym
; /* typed symbol or converted glyph */
976 int found
; /* count of matching syms found */
977 coord cc
; /* screen pos of unknown glyph */
978 boolean save_verbose
; /* saved value of flags.verbose */
979 boolean from_screen
; /* question from the screen */
983 from_screen
= TRUE
; /* yes, we want to use the cursor */
986 menu_item
*pick_list
= (menu_item
*) 0;
991 win
= create_nhwindow(NHW_MENU
);
994 /* 'y' and 'n' to keep backwards compatibility with previous
995 versions: "Specify unknown object by cursor?" */
996 add_menu(win
, NO_GLYPH
, &any
,
997 flags
.lootabc
? 0 : any
.a_char
, 'y', ATR_NONE
,
998 "something on the map", MENU_UNSELECTED
);
1000 add_menu(win
, NO_GLYPH
, &any
,
1001 flags
.lootabc
? 0 : any
.a_char
, 0, ATR_NONE
,
1002 "something you're carrying", MENU_UNSELECTED
);
1004 add_menu(win
, NO_GLYPH
, &any
,
1005 flags
.lootabc
? 0 : any
.a_char
, 'n', ATR_NONE
,
1006 "something else (by symbol or name)", MENU_UNSELECTED
);
1007 if (!u
.uswallow
&& !Hallucination
) {
1009 add_menu(win
, NO_GLYPH
, &any
, 0, 0, ATR_NONE
,
1010 "", MENU_UNSELECTED
);
1011 /* these options work sensibly for the swallowed case,
1012 but there's no reason for the player to use them then;
1013 objects work fine when hallucinating, but screen
1014 symbol/monster class letter doesn't match up with
1015 bogus monster type, so suppress when hallucinating */
1017 add_menu(win
, NO_GLYPH
, &any
,
1018 flags
.lootabc
? 0 : any
.a_char
, 0, ATR_NONE
,
1019 "nearby monsters", MENU_UNSELECTED
);
1021 add_menu(win
, NO_GLYPH
, &any
,
1022 flags
.lootabc
? 0 : any
.a_char
, 0, ATR_NONE
,
1023 "all monsters shown on map", MENU_UNSELECTED
);
1025 add_menu(win
, NO_GLYPH
, &any
,
1026 flags
.lootabc
? 0 : any
.a_char
, 0, ATR_NONE
,
1027 "nearby objects", MENU_UNSELECTED
);
1029 add_menu(win
, NO_GLYPH
, &any
,
1030 flags
.lootabc
? 0 : any
.a_char
, 0, ATR_NONE
,
1031 "all objects shown on map", MENU_UNSELECTED
);
1033 end_menu(win
, "What do you want to look at:");
1034 if (select_menu(win
, PICK_ONE
, &pick_list
) > 0) {
1035 i
= pick_list
->item
.a_char
;
1036 free((genericptr_t
) pick_list
);
1038 destroy_nhwindow(win
);
1057 invlet
= display_inventory((const char *) 0, TRUE
);
1058 if (!invlet
|| invlet
== '\033')
1061 for (invobj
= invent
; invobj
; invobj
= invobj
->nobj
)
1062 if (invobj
->invlet
== invlet
) {
1063 strcpy(out_str
, singular(invobj
, xname
));
1067 checkfile(out_str
, pm
, TRUE
, TRUE
);
1071 from_screen
= FALSE
;
1072 getlin("Specify what? (type the word)", out_str
);
1073 if (strcmp(out_str
, " ")) /* keep single space as-is */
1074 /* remove leading and trailing whitespace and
1075 condense consecutive internal whitespace */
1076 mungspaces(out_str
);
1077 if (out_str
[0] == '\0' || out_str
[0] == '\033')
1080 if (out_str
[1]) { /* user typed in a complete string */
1081 checkfile(out_str
, pm
, TRUE
, TRUE
);
1087 look_all(TRUE
, TRUE
); /* list nearby monsters */
1090 look_all(FALSE
, TRUE
); /* list all monsters */
1093 look_all(TRUE
, FALSE
); /* list nearby objects */
1096 look_all(FALSE
, FALSE
); /* list all objects */
1099 } else { /* clicklook */
1103 from_screen
= FALSE
;
1106 /* Save the verbose flag, we change it later. */
1107 save_verbose
= flags
.verbose
;
1108 flags
.verbose
= flags
.verbose
&& !quick
;
1110 * The user typed one letter, or we're identifying from the screen.
1113 /* Reset some variables. */
1114 pm
= (struct permonst
*) 0;
1118 if (from_screen
|| clicklook
) {
1121 pline("Please move the cursor to %s.",
1122 what_is_an_unknown_object
);
1124 pline("Pick an object.");
1126 ans
= getpos(&cc
, quick
, what_is_an_unknown_object
);
1127 if (ans
< 0 || cc
.x
< 0) {
1128 flags
.verbose
= save_verbose
;
1129 return 0; /* done */
1131 flags
.verbose
= FALSE
; /* only print long question once */
1135 found
= do_screen_description(cc
, (from_screen
|| clicklook
), sym
,
1136 out_str
, &firstmatch
);
1138 /* Finally, print out our explanation. */
1140 /* Used putmixed() because there may be an encoded glyph present
1142 putmixed(WIN_MESSAGE
, 0, out_str
);
1144 /* check the data file for information about this thing */
1145 if (found
== 1 && ans
!= LOOK_QUICK
&& ans
!= LOOK_ONCE
1146 && (ans
== LOOK_VERBOSE
|| (flags
.help
&& !quick
))
1148 char temp_buf
[BUFSZ
];
1150 Strcpy(temp_buf
, firstmatch
);
1151 checkfile(temp_buf
, pm
, FALSE
,
1152 (boolean
) (ans
== LOOK_VERBOSE
));
1155 pline("I've never heard of such things.");
1158 } while (from_screen
&& !quick
&& ans
!= LOOK_ONCE
&& !clicklook
);
1160 flags
.verbose
= save_verbose
;
1165 look_all(nearby
, do_mons
)
1166 boolean nearby
; /* True => within BOLTLIM, False => entire map */
1167 boolean do_mons
; /* True => monsters, False => objects */
1170 int x
, y
, lo_x
, lo_y
, hi_x
, hi_y
, glyph
, count
= 0;
1171 char lookbuf
[BUFSZ
], outbuf
[BUFSZ
];
1173 win
= create_nhwindow(NHW_TEXT
);
1174 lo_y
= nearby
? max(u
.uy
- BOLT_LIM
, 0) : 0;
1175 lo_x
= nearby
? max(u
.ux
- BOLT_LIM
, 1) : 1;
1176 hi_y
= nearby
? min(u
.uy
+ BOLT_LIM
, ROWNO
- 1) : ROWNO
- 1;
1177 hi_x
= nearby
? min(u
.ux
+ BOLT_LIM
, COLNO
- 1) : COLNO
- 1;
1178 for (y
= lo_y
; y
<= hi_y
; y
++) {
1179 for (x
= lo_x
; x
<= hi_x
; x
++) {
1181 glyph
= glyph_at(x
, y
);
1183 if (glyph_is_monster(glyph
)) {
1186 bhitpos
.x
= x
; /* [is this actually necessary?] */
1188 if (x
== u
.ux
&& y
== u
.uy
&& canspotself()) {
1189 (void) self_lookat(lookbuf
);
1191 } else if ((mtmp
= m_at(x
, y
)) != 0) {
1192 look_at_monster(lookbuf
, (char *) 0, mtmp
, x
, y
);
1195 } else if (glyph_is_invisible(glyph
)) {
1196 /* remembered, unseen, creature */
1197 Strcpy(lookbuf
, invisexplain
);
1199 } else if (glyph_is_warning(glyph
)) {
1200 int warnindx
= glyph_to_warning(glyph
);
1202 Strcpy(lookbuf
, def_warnsyms
[warnindx
].explanation
);
1205 } else { /* !do_mons */
1206 if (glyph_is_object(glyph
)) {
1207 look_at_object(lookbuf
, x
, y
, glyph
);
1212 char coordbuf
[20], which
[12], cmode
;
1214 cmode
= (iflags
.getpos_coords
!= GPCOORDS_NONE
)
1215 ? iflags
.getpos_coords
: GPCOORDS_MAP
;
1217 Strcpy(which
, do_mons
? "monsters" : "objects");
1219 Sprintf(outbuf
, "%s currently shown near %s:",
1221 (cmode
!= GPCOORDS_COMPASS
)
1222 ? coord_desc(u
.ux
, u
.uy
, coordbuf
, cmode
)
1223 : !canspotself() ? "your position" : "you");
1225 Sprintf(outbuf
, "All %s currently shown on the map:",
1227 putstr(win
, 0, outbuf
);
1230 /* prefix: "coords C " where 'C' is mon or obj symbol */
1231 Sprintf(outbuf
, (cmode
== GPCOORDS_SCREEN
) ? "%s "
1232 : (cmode
== GPCOORDS_MAP
) ? "%8s "
1234 coord_desc(x
, y
, coordbuf
, cmode
));
1235 Sprintf(eos(outbuf
), "%s ", encglyph(glyph
));
1236 /* guard against potential overflow */
1237 lookbuf
[sizeof lookbuf
- 1 - strlen(outbuf
)] = '\0';
1238 Strcat(outbuf
, lookbuf
);
1239 putmixed(win
, 0, outbuf
);
1244 display_nhwindow(win
, TRUE
);
1246 pline("No %s are currently shown %s.",
1247 do_mons
? "monsters" : "objects",
1248 nearby
? "nearby" : "on the map");
1249 destroy_nhwindow(win
);
1252 /* the '/' command */
1256 return do_look(0, (coord
*) 0);
1259 /* the ';' command */
1263 return do_look(1, (coord
*) 0);
1266 /* the '^' command */
1270 register struct trap
*trap
;
1271 int x
, y
, tt
, glyph
;
1278 /* check fake bear trap from confused gold detection */
1279 glyph
= glyph_at(x
, y
);
1280 if (glyph_is_trap(glyph
) && (tt
= glyph_to_trap(glyph
)) == BEAR_TRAP
) {
1281 boolean chesttrap
= trapped_chest_at(tt
, x
, y
);
1283 if (chesttrap
|| trapped_door_at(tt
, x
, y
)) {
1284 pline("That is a trapped %s.", chesttrap
? "chest" : "door");
1285 return 0; /* trap ID'd, but no time elapses */
1289 for (trap
= ftrap
; trap
; trap
= trap
->ntrap
)
1290 if (trap
->tx
== x
&& trap
->ty
== y
) {
1295 if (u
.dz
< 0 ? (tt
== TRAPDOOR
|| tt
== HOLE
)
1300 pline("That is %s%s%s.",
1301 an(defsyms
[trap_to_defsym(tt
)].explanation
),
1306 /* trap doors & spiked pits can't be made by
1307 player, and should be considered at least
1308 as much "set" as "dug" anyway */
1309 : (tt
== HOLE
|| tt
== PIT
)
1312 !trap
->madeby_u
? "" : " by you");
1315 pline("I can't see a trap there.");
1320 Implements a rudimentary if/elif/else/endif interpretor and use
1321 conditionals in dat/cmdhelp to describe what command each keystroke
1322 currently invokes, so that there isn't a lot of "(debug mode only)"
1323 and "(if number_pad is off)" cluttering the feedback that the user
1324 sees. (The conditionals add quite a bit of clutter to the raw data
1325 but users don't see that. number_pad produces a lot of conditional
1326 commands: basic letters vs digits, 'g' vs 'G' for '5', phone
1327 keypad vs normal layout of digits, and QWERTZ keyboard swap between
1328 y/Y/^Y/M-y/M-Y/M-^Y and z/Z/^Z/M-z/M-Z/M-^Z.)
1330 The interpretor understands
1332 '&? option' for 'if' (also '&? !option'
1333 or '&? option=value[,value2,...]'
1334 or '&? !option=value[,value2,...]'),
1335 '&: option' for 'elif' (with argument variations same as 'if';
1336 any number of instances for each 'if'),
1337 '&:' for 'else' (also '&: #comment';
1338 0 or 1 instance for a given 'if'), and
1339 '&.' for 'endif' (also '&. #comment'; required for each 'if').
1341 The option handling is a bit of a mess, with no generality for
1342 which options to deal with and only a comma separated list of
1343 integer values for the '=value' part. number_pad is the only
1344 supported option that has a value; the few others (wizard/debug,
1345 rest_on_space, #if SHELL, #if SUSPEND) are booleans.
1352 char *p
, buf
[BUFSZ
];
1353 winid tmpwin
= create_nhwindow(NHW_TEXT
);
1355 fp
= dlb_fopen(KEYHELP
, "r");
1357 pline("Cannot open \"%s\" data file!", KEYHELP
);
1358 display_nhwindow(WIN_MESSAGE
, TRUE
);
1361 while (dlb_fgets(buf
, (int) sizeof buf
, fp
)) {
1364 for (p
= buf
; *p
; p
++)
1365 if (*p
!= ' ' && *p
!= '\t')
1367 putstr(tmpwin
, 0, p
);
1369 (void) dlb_fclose(fp
);
1370 display_nhwindow(tmpwin
, TRUE
);
1371 destroy_nhwindow(tmpwin
);
1374 #define WD_STACKLIMIT 5
1375 struct wd_stack_frame
{
1376 Bitfield(active
, 1);
1377 Bitfield(been_true
, 1);
1378 Bitfield(else_seen
, 1);
1381 STATIC_DCL boolean
FDECL(whatdoes_cond
, (char *, struct wd_stack_frame
*,
1385 whatdoes_cond(buf
, stack
, depth
, lnum
)
1387 struct wd_stack_frame
*stack
;
1390 const char badstackfmt
[] = "cmdhlp: too many &%c directives at line %d.";
1391 boolean newcond
, neg
, gotopt
;
1392 char *p
, *q
, act
= buf
[1];
1395 newcond
= (act
== '?' || !stack
[*depth
].been_true
);
1398 if (act
== '#' || *buf
== '#' || !*buf
|| !newcond
) {
1399 gotopt
= (*buf
&& *buf
!= '#');
1401 neg
= FALSE
; /* lint suppression */
1405 if ((neg
= (*buf
== '!')) != 0)
1408 p
= index(buf
, '='), q
= index(buf
, ':');
1409 if (!p
|| (q
&& q
< p
))
1411 if (p
) { /* we have a value specified */
1412 /* handle a space before or after (or both) '=' (or ':') */
1413 if (p
> buf
&& p
[-1] == ' ')
1414 p
[-1] = '\0'; /* end of keyword in buf[] */
1415 *p
++ = '\0'; /* terminate keyword, advance to start of value */
1420 if (*buf
&& (act
== '?' || act
== ':')) {
1421 if (!strcmpi(buf
, "number_pad")) {
1423 newcond
= iflags
.num_pad
;
1425 /* convert internal encoding (separate yes/no and 0..3)
1426 back to user-visible one (-1..4) */
1427 np
= iflags
.num_pad
? (1 + iflags
.num_pad_mode
) /* 1..4 */
1428 : (-1 * iflags
.num_pad_mode
); /* -1..0 */
1434 if (atoi(p
) == np
) {
1440 } else if (!strcmpi(buf
, "rest_on_space")) {
1441 newcond
= flags
.rest_on_space
;
1442 } else if (!strcmpi(buf
, "debug") || !strcmpi(buf
, "wizard")) {
1443 newcond
= flags
.debug
; /* == wizard */
1444 } else if (!strcmpi(buf
, "shell")) {
1446 /* should we also check sysopt.shellers? */
1451 } else if (!strcmpi(buf
, "suspend")) {
1453 /* sysopt.shellers is also used for dosuspend()... */
1460 "cmdhelp: unrecognized &%c conditional at line %d: \"%.20s\"",
1464 /* this works for number_pad too: &? !number_pad:-1,0
1465 would be true for 1..4 after negation */
1471 case '#': /* comment */
1473 case '.': /* endif */
1475 impossible(badstackfmt
, '.', lnum
);
1479 case ':': /* else or elif */
1480 if (*depth
== 0 || stack
[*depth
].else_seen
) {
1481 impossible(badstackfmt
, ':', lnum
);
1482 *depth
= 1; /* so that stack[*depth - 1] is a valid access */
1484 if (stack
[*depth
].active
|| stack
[*depth
].been_true
1485 || !stack
[*depth
- 1].active
)
1486 stack
[*depth
].active
= 0;
1488 stack
[*depth
].active
= stack
[*depth
].been_true
= 1;
1490 stack
[*depth
].else_seen
= 1;
1493 if (++*depth
>= WD_STACKLIMIT
) {
1494 impossible(badstackfmt
, '?', lnum
);
1495 *depth
= WD_STACKLIMIT
- 1;
1497 stack
[*depth
].active
= (newcond
&& stack
[*depth
- 1].active
) ? 1 : 0;
1498 stack
[*depth
].been_true
= stack
[*depth
].active
;
1499 stack
[*depth
].else_seen
= 0;
1502 return stack
[*depth
].active
? TRUE
: FALSE
;
1506 dowhatdoes_core(q
, cbuf
)
1512 struct wd_stack_frame stack
[WD_STACKLIMIT
];
1514 int ctrl
, meta
, depth
= 0, lnum
= 0;
1516 fp
= dlb_fopen(CMDHELPFILE
, "r");
1518 pline("Cannot open \"%s\" data file!", CMDHELPFILE
);
1522 meta
= (0x80 & (uchar
) q
) != 0;
1525 ctrl
= (0x1f & (uchar
) q
) == (uchar
) q
;
1527 q
|= 0x40; /* NUL -> '@', ^A -> 'A', ... ^Z -> 'Z', ^[ -> '[', ... */
1531 (void) memset((genericptr_t
) stack
, 0, sizeof stack
);
1532 cond
= stack
[0].active
= 1;
1533 while (dlb_fgets(buf
, sizeof buf
, fp
)) {
1535 if (buf
[0] == '&' && buf
[1] && index("?:.#", buf
[1])) {
1536 cond
= whatdoes_cond(buf
, stack
, &depth
, lnum
);
1541 if (meta
? (buf
[0] == 'M' && buf
[1] == '-'
1542 && (ctrl
? buf
[2] == '^' && highc(buf
[3]) == q
1544 : (ctrl
? buf
[0] == '^' && highc(buf
[1]) == q
1546 (void) strip_newline(buf
);
1547 if (index(buf
, '\t'))
1548 (void) tabexpand(buf
);
1549 if (meta
&& ctrl
&& buf
[4] == ' ') {
1550 (void) strncpy(buf
, "M-^? ", 8);
1552 } else if (meta
&& buf
[3] == ' ') {
1553 (void) strncpy(buf
, "M-? ", 8);
1555 } else if (ctrl
&& buf
[2] == ' ') {
1556 (void) strncpy(buf
, "^? ", 8);
1558 } else if (buf
[1] == ' ') {
1559 (void) strncpy(buf
, "? ", 8);
1562 (void) dlb_fclose(fp
);
1567 (void) dlb_fclose(fp
);
1569 impossible("cmdhelp: mismatched &? &: &. conditionals.");
1576 static boolean once
= FALSE
;
1581 pline("Ask about '&' or '?' to get more info.%s",
1583 iflags
.altmeta
? " (For ESC, type it twice.)" :
1588 #if defined(UNIX) || defined(VMS)
1589 introff(); /* disables ^C but not ^\ */
1591 q
= yn_function("What command?", (char *) 0, '\0');
1593 if (q
== '\033' && iflags
.altmeta
) {
1594 /* in an ideal world, we would know whether another keystroke
1595 was already pending, but this is not an ideal world...
1596 if user typed ESC, we'll essentially hang until another
1597 character is typed */
1598 q
= yn_function("]", (char *) 0, '\0');
1600 q
= (char) ((uchar
) q
| 0200);
1603 #if defined(UNIX) || defined(VMS)
1604 intron(); /* reenables ^C */
1606 reslt
= dowhatdoes_core(q
, bufr
);
1608 if (q
== '&' || q
== '?')
1612 pline("No such command '%s', char code %d (0%03o or 0x%02x).",
1613 visctrl(q
), (uchar
) q
, (uchar
) q
, (uchar
) q
);
1621 winid cwin
= create_nhwindow(NHW_TEXT
);
1624 if (sysopt
.support
) {
1625 /*XXX overflow possibilities*/
1626 Sprintf(buf
, "To contact local support, %s", sysopt
.support
);
1627 putstr(cwin
, 0, buf
);
1628 putstr(cwin
, 0, "");
1629 } else if (sysopt
.fmtd_wizard_list
) { /* formatted SYSCF WIZARDS */
1630 Sprintf(buf
, "To contact local support, contact %s.",
1631 sysopt
.fmtd_wizard_list
);
1632 putstr(cwin
, 0, buf
);
1633 putstr(cwin
, 0, "");
1635 putstr(cwin
, 0, "To contact the NetHack development team directly,");
1636 /*XXX overflow possibilities*/
1637 Sprintf(buf
, "see the 'Contact' form on our website or email <%s>.",
1639 putstr(cwin
, 0, buf
);
1640 putstr(cwin
, 0, "");
1641 putstr(cwin
, 0, "For more information on NetHack, or to report a bug,");
1642 Sprintf(buf
, "visit our website \"%s\".", DEVTEAM_URL
);
1643 putstr(cwin
, 0, buf
);
1644 display_nhwindow(cwin
, FALSE
);
1645 destroy_nhwindow(cwin
);
1651 display_file(HELP
, TRUE
);
1657 display_file(SHELP
, TRUE
);
1661 dispfile_optionfile()
1663 display_file(OPTIONFILE
, TRUE
);
1669 display_file(LICENSE
, TRUE
);
1673 dispfile_debughelp()
1675 display_file(DEBUGHELP
, TRUE
);
1679 hmenu_doextversion()
1681 (void) doextversion();
1699 (void) dowhatdoes();
1708 /* data for dohelp() */
1712 } help_menu_items
[] = {
1713 { hmenu_doextversion
, "About NetHack (version information)." },
1714 { dispfile_help
, "Long description of the game and commands." },
1715 { dispfile_shelp
, "List of game commands." },
1716 { hmenu_dohistory
, "Concise history of NetHack." },
1717 { hmenu_dowhatis
, "Info on a character in the game display." },
1718 { hmenu_dowhatdoes
, "Info on what a given key does." },
1719 { option_help
, "List of game options." },
1720 { dispfile_optionfile
, "Longer explanation of game options." },
1721 { hmenu_doextlist
, "List of extended commands." },
1722 { dispfile_license
, "The NetHack license." },
1723 { docontact
, "Support information." },
1725 { port_help
, "%s-specific help and commands." },
1727 { dispfile_debughelp
, "List of wizard-mode commands." },
1728 { NULL
, (char *) 0 }
1731 /* the '?' command */
1735 winid tmpwin
= create_nhwindow(NHW_MENU
);
1736 char helpbuf
[QBUFSZ
];
1738 menu_item
*selected
;
1743 any
= zeroany
; /* zero all bits */
1746 for (i
= 0; help_menu_items
[i
].text
; i
++) {
1747 if (!wizard
&& help_menu_items
[i
].f
== dispfile_debughelp
)
1749 if (help_menu_items
[i
].text
[0] == '%') {
1750 Sprintf(helpbuf
, help_menu_items
[i
].text
, PORT_ID
);
1753 bufptr
= (char *)help_menu_items
[i
].text
;
1756 add_menu(tmpwin
, NO_GLYPH
, &any
, 0, 0, ATR_NONE
,
1757 bufptr
, MENU_UNSELECTED
);
1759 end_menu(tmpwin
, "Select one item:");
1760 n
= select_menu(tmpwin
, PICK_ONE
, &selected
);
1761 destroy_nhwindow(tmpwin
);
1763 sel
= selected
[0].item
.a_int
- 1;
1764 free((genericptr_t
) selected
);
1765 (void)(*help_menu_items
[sel
].f
)();
1770 /* the 'V' command; also a choice for '?' */
1774 display_file(HISTORY
, TRUE
);