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