'/' enhancement

Add new entries to the menu used for the '/' command:  describe nearby
monsters, describe all shown monsters, describe nearby objects, and
describe all shown objects.  It makes a text window listing monsters--
or objects--currently displayed on the map, showing lines of
X  r,c  monster-or-object description
where 'X' is symbol (monster or object class letter for tty), 'r,c' is
row and column separated by comma, and description is similar to what
using '/y' or ';' manually would provide (how-seen info is omitted
when listing monsters).

Originally intended for blind players using screen readers to describe
what is displayed, but will probably get used by other players too.

The map doesn't use a separate set of glyphs for objects which are the
tops of piles, so the information that there are additional objects
beneath the ones shown isn't available to '/' and ';'.  That feels like
a bug to me....
This commit is contained in:
PatR
2015-07-06 19:07:26 -07:00
parent fe09f7156d
commit 63736ba33b
+259 -109
View File
@@ -1,4 +1,4 @@
/* NetHack 3.6 pager.c $NHDT-Date: 1433572586 2015/06/06 06:36:26 $ $NHDT-Branch: master $:$NHDT-Revision: 1.77 $ */ /* NetHack 3.6 pager.c $NHDT-Date: 1436234837 2015/07/07 02:07:17 $ $NHDT-Branch: master $:$NHDT-Revision: 1.78 $ */
/* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */ /* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */
/* NetHack may be freely redistributed. See license for details. */ /* NetHack may be freely redistributed. See license for details. */
@@ -10,9 +10,13 @@
STATIC_DCL boolean FDECL(is_swallow_sym, (int)); STATIC_DCL boolean FDECL(is_swallow_sym, (int));
STATIC_DCL int FDECL(append_str, (char *, const char *)); STATIC_DCL int FDECL(append_str, (char *, const char *));
STATIC_DCL void FDECL(look_at_object, (char *, int, int, int));
STATIC_DCL void FDECL(look_at_monster, (char *, char *,
struct monst *, int, int));
STATIC_DCL struct permonst *FDECL(lookat, (int, int, char *, char *)); STATIC_DCL struct permonst *FDECL(lookat, (int, int, char *, char *));
STATIC_DCL void FDECL(checkfile, STATIC_DCL void FDECL(checkfile, (char *, struct permonst *,
(char *, struct permonst *, BOOLEAN_P, BOOLEAN_P)); BOOLEAN_P, BOOLEAN_P));
STATIC_DCL void FDECL(look_all, (BOOLEAN_P,BOOLEAN_P));
STATIC_DCL boolean FDECL(help_menu, (int *)); STATIC_DCL boolean FDECL(help_menu, (int *));
#ifdef PORT_HELP #ifdef PORT_HELP
extern void NDECL(port_help); extern void NDECL(port_help);
@@ -112,67 +116,43 @@ struct obj **obj_p;
return fakeobj; /* when True, caller needs to dealloc *obj_p */ return fakeobj; /* when True, caller needs to dealloc *obj_p */
} }
/* STATIC_OVL void
* Return the name of the glyph found at (x,y). look_at_object(buf, x, y, glyph)
* If not hallucinating and the glyph is a monster, also monster data. char *buf; /* output buffer */
*/ int x, y, glyph;
STATIC_OVL struct permonst *
lookat(x, y, buf, monbuf)
int x, y;
char *buf, *monbuf;
{ {
register struct monst *mtmp = (struct monst *) 0; struct obj *otmp = 0;
struct permonst *pm = (struct permonst *) 0; boolean fakeobj = object_from_map(glyph, x, y, &otmp);
int glyph;
buf[0] = monbuf[0] = 0; if (otmp) {
glyph = glyph_at(x, y); Strcpy(buf, (otmp->otyp != STRANGE_OBJECT)
if (u.ux == x && u.uy == y && canspotself()) { ? distant_name(otmp, xname)
/* fill in buf[] */ : obj_descr[STRANGE_OBJECT].oc_name);
(void) self_lookat(buf); if (fakeobj)
dealloc_obj(otmp), otmp = 0;
} else
Strcpy(buf, something); /* sanity precaution */
/* file lookup can't distinguish between "gnomish wizard" monster if (levl[x][y].typ == STONE || levl[x][y].typ == SCORR)
and correspondingly named player character, always picking the Strcat(buf, " embedded in stone");
former; force it to find the general "wizard" entry instead */ else if (IS_WALL(levl[x][y].typ) || levl[x][y].typ == SDOOR)
if (Role_if(PM_WIZARD) && Race_if(PM_GNOME) && !Upolyd) Strcat(buf, " embedded in a wall");
pm = &mons[PM_WIZARD]; else if (closed_door(x, y))
Strcat(buf, " embedded in a door");
/* When you see yourself normally, no explanation is appended else if (is_pool(x, y))
(even if you could also see yourself via other means). Strcat(buf, " in water");
Sensing self while blind or swallowed is treated as if it else if (is_lava(x, y))
were by normal vision (cf canseeself()). */ Strcat(buf, " in molten lava"); /* [can this ever happen?] */
if ((Invisible || u.uundetected) && !Blind && !u.uswallow) { return;
unsigned how = 0;
if (Infravision)
how |= 1;
if (Unblind_telepat)
how |= 2;
if (Detect_monsters)
how |= 4;
if (how)
Sprintf(
eos(buf), " [seen: %s%s%s%s%s]",
(how & 1) ? "infravision" : "",
/* add comma if telep and infrav */
((how & 3) > 2) ? ", " : "", (how & 2) ? "telepathy" : "",
/* add comma if detect and (infrav or telep or both) */
((how & 7) > 4) ? ", " : "",
(how & 4) ? "monster detection" : "");
} }
} else if (u.uswallow) {
/* all locations when swallowed other than the hero are the monster */ STATIC_OVL void
Sprintf(buf, "interior of %s", look_at_monster(buf, monbuf, mtmp, x, y)
Blind ? "a monster" : a_monnam(u.ustuck)); char *buf, *monbuf; /* buf: output, monbuf: optional output */
pm = u.ustuck->data; struct monst *mtmp;
} else if (glyph_is_monster(glyph)) { int x, y;
bhitpos.x = x; {
bhitpos.y = y;
mtmp = m_at(x, y);
if (mtmp) {
char *name, monnambuf[BUFSZ]; char *name, monnambuf[BUFSZ];
unsigned how_seen;
boolean accurate = !Hallucination; boolean accurate = !Hallucination;
if (mtmp->data == &mons[PM_COYOTE] && accurate) if (mtmp->data == &mons[PM_COYOTE] && accurate)
@@ -180,20 +160,19 @@ char *buf, *monbuf;
else else
name = distant_monnam(mtmp, ARTICLE_NONE, monnambuf); name = distant_monnam(mtmp, ARTICLE_NONE, monnambuf);
pm = mtmp->data; Sprintf(buf, "%s%s%s",
Sprintf(
buf, "%s%s%s",
(mtmp->mx != x || mtmp->my != y) (mtmp->mx != x || mtmp->my != y)
? ((mtmp->isshk && accurate) ? "tail of " : "tail of a ") ? ((mtmp->isshk && accurate) ? "tail of " : "tail of a ")
: "", : "",
(mtmp->mtame && accurate) (mtmp->mtame && accurate)
? "tame " ? "tame "
: (mtmp->mpeaceful && accurate) ? "peaceful " : "", : (mtmp->mpeaceful && accurate)
? "peaceful "
: "",
name); name);
if (u.ustuck == mtmp) if (u.ustuck == mtmp)
Strcat(buf, (Upolyd && sticks(youmonst.data)) Strcat(buf, (Upolyd && sticks(youmonst.data))
? ", being held" ? ", being held" : ", holding you");
: ", holding you");
if (mtmp->mleashed) if (mtmp->mleashed)
Strcat(buf, ", leashed to you"); Strcat(buf, ", leashed to you");
@@ -202,14 +181,16 @@ char *buf, *monbuf;
int tt = t ? t->ttyp : NO_TRAP; int tt = t ? t->ttyp : NO_TRAP;
/* newsym lets you know of the trap, so mention it here */ /* newsym lets you know of the trap, so mention it here */
if (tt == BEAR_TRAP || tt == PIT || tt == SPIKED_PIT if (tt == BEAR_TRAP || tt == PIT || tt == SPIKED_PIT || tt == WEB)
|| tt == WEB)
Sprintf(eos(buf), ", trapped in %s", Sprintf(eos(buf), ", trapped in %s",
an(defsyms[trap_to_defsym(tt)].explanation)); an(defsyms[trap_to_defsym(tt)].explanation));
} }
how_seen = howmonseen(mtmp); if (monbuf) {
if (how_seen && how_seen != MONSEEN_NORMAL) { unsigned how_seen = howmonseen(mtmp);
monbuf[0] = '\0';
if (how_seen != 0 && how_seen != MONSEEN_NORMAL) {
if (how_seen & MONSEEN_NORMAL) { if (how_seen & MONSEEN_NORMAL) {
Strcat(monbuf, "normal vision"); Strcat(monbuf, "normal vision");
how_seen &= ~MONSEEN_NORMAL; how_seen &= ~MONSEEN_NORMAL;
@@ -264,30 +245,72 @@ char *buf, *monbuf;
Sprintf(eos(monbuf), "(%u)", how_seen); Sprintf(eos(monbuf), "(%u)", how_seen);
} }
} /* seen by something other than normal vision */ } /* seen by something other than normal vision */
} /* mtmp */ } /* monbuf is non-null */
} else if (glyph_is_object(glyph)) {
struct obj *otmp = 0;
boolean fakeobj = object_from_map(glyph, x, y, &otmp);
if (otmp) {
Strcpy(buf, (otmp->otyp != STRANGE_OBJECT)
? distant_name(otmp, xname)
: obj_descr[STRANGE_OBJECT].oc_name);
if (fakeobj)
dealloc_obj(otmp), otmp = 0;
} }
if (levl[x][y].typ == STONE || levl[x][y].typ == SCORR) /*
Strcat(buf, " embedded in stone"); * Return the name of the glyph found at (x,y).
else if (IS_WALL(levl[x][y].typ) || levl[x][y].typ == SDOOR) * If not hallucinating and the glyph is a monster, also monster data.
Strcat(buf, " embedded in a wall"); */
else if (closed_door(x, y)) STATIC_OVL struct permonst *
Strcat(buf, " embedded in a door"); lookat(x, y, buf, monbuf)
else if (is_pool(x, y)) int x, y;
Strcat(buf, " in water"); char *buf, *monbuf;
else if (is_lava(x, y)) {
Strcat(buf, " in molten lava"); /* [can this ever happen?] */ register struct monst *mtmp = (struct monst *) 0;
struct permonst *pm = (struct permonst *) 0;
int glyph;
buf[0] = monbuf[0] = '\0';
glyph = glyph_at(x, y);
if (u.ux == x && u.uy == y && canspotself()) {
/* fill in buf[] */
(void) self_lookat(buf);
/* file lookup can't distinguish between "gnomish wizard" monster
and correspondingly named player character, always picking the
former; force it to find the general "wizard" entry instead */
if (Role_if(PM_WIZARD) && Race_if(PM_GNOME) && !Upolyd)
pm = &mons[PM_WIZARD];
/* When you see yourself normally, no explanation is appended
(even if you could also see yourself via other means).
Sensing self while blind or swallowed is treated as if it
were by normal vision (cf canseeself()). */
if ((Invisible || u.uundetected) && !Blind && !u.uswallow) {
unsigned how = 0;
if (Infravision)
how |= 1;
if (Unblind_telepat)
how |= 2;
if (Detect_monsters)
how |= 4;
if (how)
Sprintf(
eos(buf), " [seen: %s%s%s%s%s]",
(how & 1) ? "infravision" : "",
/* add comma if telep and infrav */
((how & 3) > 2) ? ", " : "", (how & 2) ? "telepathy" : "",
/* add comma if detect and (infrav or telep or both) */
((how & 7) > 4) ? ", " : "",
(how & 4) ? "monster detection" : "");
}
} else if (u.uswallow) {
/* all locations when swallowed other than the hero are the monster */
Sprintf(buf, "interior of %s",
Blind ? "a monster" : a_monnam(u.ustuck));
pm = u.ustuck->data;
} else if (glyph_is_monster(glyph)) {
bhitpos.x = x;
bhitpos.y = y;
if ((mtmp = m_at(x, y)) != 0) {
look_at_monster(buf, monbuf, mtmp, x, y);
pm = mtmp->data;
}
} else if (glyph_is_object(glyph)) {
look_at_object(buf, x, y, glyph); /* fill in buf[] */
} else if (glyph_is_trap(glyph)) { } else if (glyph_is_trap(glyph)) {
int tnum = what_trap(glyph_to_trap(glyph)); int tnum = what_trap(glyph_to_trap(glyph));
Strcpy(buf, defsyms[trap_to_defsym(tnum)].explanation); Strcpy(buf, defsyms[trap_to_defsym(tnum)].explanation);
@@ -758,8 +781,7 @@ do_look(mode, click_cc)
int mode; int mode;
coord *click_cc; coord *click_cc;
{ {
boolean quick = boolean quick = (mode == 1); /* use cursor; don't search for "more info" */
(mode == 1); /* use cursor && don't search for "more info" */
boolean clicklook = (mode == 2); /* right mouse-click method */ boolean clicklook = (mode == 2); /* right mouse-click method */
char out_str[BUFSZ]; char out_str[BUFSZ];
const char *firstmatch = 0; const char *firstmatch = 0;
@@ -781,21 +803,50 @@ coord *click_cc;
menu_item *pick_list = (menu_item *) 0; menu_item *pick_list = (menu_item *) 0;
winid win; winid win;
anything any; anything any;
any = zeroany;
win = create_nhwindow(NHW_MENU); win = create_nhwindow(NHW_MENU);
start_menu(win); start_menu(win);
any.a_void = 0; any.a_char = '/';
any.a_char = 'a'; /* 'y' and 'n' to keep backwards compatability with previous
/* 'y' and 'n' to keep backwards compat with previous versions */ versions: "Specify unknown object by cursor?" */
add_menu(win, NO_GLYPH, &any, 'a', 'y', ATR_NONE, add_menu(win, NO_GLYPH, &any,
flags.lootabc ? 0 : any.a_char, 'y', ATR_NONE,
"something on the map", MENU_UNSELECTED); "something on the map", MENU_UNSELECTED);
any.a_void = 0; any.a_char = 'i';
any.a_char = 'b'; add_menu(win, NO_GLYPH, &any,
add_menu(win, NO_GLYPH, &any, 'b', 0, ATR_NONE, flags.lootabc ? 0 : any.a_char, 0, ATR_NONE,
"something you're carrying", MENU_UNSELECTED); "something you're carrying", MENU_UNSELECTED);
any.a_void = 0; any.a_char = '?';
any.a_char = 'c'; add_menu(win, NO_GLYPH, &any,
add_menu(win, NO_GLYPH, &any, 'c', 'n', ATR_NONE, flags.lootabc ? 0 : any.a_char, 'n', ATR_NONE,
"something else", MENU_UNSELECTED); "something else (by symbol or name)", MENU_UNSELECTED);
if (!u.uswallow && !Hallucination) {
any = zeroany;
add_menu(win, NO_GLYPH, &any, 0, 0, ATR_NONE,
"", MENU_UNSELECTED);
/* these options work sensibly for the swallowed case,
but there's no reason for the player to use them then;
objects work fine when hallucinating, but screen
symbol/monster class letter doesn't match up with
bogus monster type, so suppress when hallucinating */
any.a_char = 'm';
add_menu(win, NO_GLYPH, &any,
flags.lootabc ? 0 : any.a_char, 0, ATR_NONE,
"nearby monsters", MENU_UNSELECTED);
any.a_char = 'M';
add_menu(win, NO_GLYPH, &any,
flags.lootabc ? 0 : any.a_char, 0, ATR_NONE,
"all monsters shown on map", MENU_UNSELECTED);
any.a_char = 'o';
add_menu(win, NO_GLYPH, &any,
flags.lootabc ? 0 : any.a_char, 0, ATR_NONE,
"nearby objects", MENU_UNSELECTED);
any.a_char = 'O';
add_menu(win, NO_GLYPH, &any,
flags.lootabc ? 0 : any.a_char, 0, ATR_NONE,
"all objects shown on map", MENU_UNSELECTED);
}
end_menu(win, "What do you want to look at:"); end_menu(win, "What do you want to look at:");
if (select_menu(win, PICK_ONE, &pick_list) > 0) { if (select_menu(win, PICK_ONE, &pick_list) > 0) {
i = pick_list->item.a_char; i = pick_list->item.a_char;
@@ -809,17 +860,18 @@ coord *click_cc;
case 'q': case 'q':
return 0; return 0;
case 'y': case 'y':
case 'a': case '/':
from_screen = TRUE; from_screen = TRUE;
sym = 0; sym = 0;
cc.x = u.ux; cc.x = u.ux;
cc.y = u.uy; cc.y = u.uy;
break; break;
case 'b': { case 'i':
{
char invlet; char invlet;
struct obj *invobj; struct obj *invobj;
invlet = display_inventory(NULL, TRUE); invlet = display_inventory((const char *) 0, TRUE);
if (!invlet || invlet == '\033') if (!invlet || invlet == '\033')
return 0; return 0;
*out_str = '\0'; *out_str = '\0';
@@ -831,8 +883,8 @@ coord *click_cc;
if (*out_str) if (*out_str)
checkfile(out_str, pm, TRUE, TRUE); checkfile(out_str, pm, TRUE, TRUE);
return 0; return 0;
} break; }
case 'c': case '?':
from_screen = FALSE; from_screen = FALSE;
getlin("Specify what? (type the word)", out_str); getlin("Specify what? (type the word)", out_str);
if (out_str[0] == '\0' || out_str[0] == '\033') if (out_str[0] == '\0' || out_str[0] == '\033')
@@ -844,6 +896,18 @@ coord *click_cc;
} }
sym = out_str[0]; sym = out_str[0];
break; break;
case 'm':
look_all(TRUE, TRUE); /* list nearby monsters */
return 0;
case 'M':
look_all(FALSE, TRUE); /* list all monsters */
return 0;
case 'o':
look_all(TRUE, FALSE); /* list nearby objects */
return 0;
case 'O':
look_all(FALSE, FALSE); /* list all objects */
return 0;
} }
} else { /* clicklook */ } else { /* clicklook */
cc.x = click_cc->x; cc.x = click_cc->x;
@@ -909,6 +973,92 @@ coord *click_cc;
return 0; return 0;
} }
STATIC_OVL void
look_all(nearby, do_mons)
boolean nearby; /* True => within BOLTLIM, False => entire map */
boolean do_mons; /* True => monsters, False => objects */
{
winid win;
int x, y, lo_x, lo_y, hi_x, hi_y, glyph, count = 0;
char buf[BUFSZ], outbuf[BUFSZ], coordbuf[12], fmt[12]; /* "%02d,%02d" */
/* row,column orientation rather than cartesian x,y */
Sprintf(fmt, "%%%sd,%%%sd",
(ROWNO <= 100) ? "02" : (ROWNO <= 1000) ? "03" : "",
(COLNO <= 100) ? "02" : (COLNO <= 1000) ? "03" : "");
win = create_nhwindow(NHW_TEXT);
lo_y = nearby ? max(u.uy - BOLT_LIM, 0) : 0;
lo_x = nearby ? max(u.ux - BOLT_LIM, 1) : 1;
hi_y = nearby ? min(u.uy + BOLT_LIM, ROWNO - 1) : ROWNO - 1;
hi_x = nearby ? min(u.ux + BOLT_LIM, COLNO - 1) : COLNO - 1;
for (y = lo_y; y <= hi_y; y++) {
for (x = lo_x; x <= hi_x; x++) {
buf[0] = '\0';
glyph = glyph_at(x, y);
if (do_mons) {
if (glyph_is_monster(glyph)) {
struct monst *mtmp;
bhitpos.x = x; /* [is this actually necessary?] */
bhitpos.y = y;
if (x == u.ux && y == u.uy && canspotself()) {
(void) self_lookat(buf);
++count;
} else if ((mtmp = m_at(x, y)) != 0) {
look_at_monster(buf, (char *) 0, mtmp, x, y);
++count;
}
} else if (glyph_is_invisible(glyph)) {
/* remembered, unseen, creature */
Strcpy(buf, invisexplain);
++count;
} else if (glyph_is_warning(glyph)) {
int warnindx = glyph_to_warning(glyph);
Strcpy(buf, def_warnsyms[warnindx].explanation);
++count;
}
} else { /* !do_mons */
if (glyph_is_object(glyph)) {
look_at_object(buf, x, y, glyph);
++count;
}
}
if (*buf) {
if (count == 1) {
char which[12];
Strcpy(which, do_mons ? "monsters" : "objects");
if (nearby) {
Sprintf(coordbuf, fmt, u.uy, u.ux);
Sprintf(outbuf, "%s currently shown near %s:",
upstart(which), coordbuf);
} else
Sprintf(outbuf, "All %s currently shown on the map:",
which);
putstr(win, 0, outbuf);
putstr(win, 0, "");
}
Sprintf(coordbuf, fmt, y, x);
/* prefix: "C row,column " */
Sprintf(outbuf, "%s %s ", encglyph(glyph), coordbuf);
/* guard against potential overflow */
buf[sizeof buf - 1 - strlen(outbuf)] = '\0';
Strcat(outbuf, buf);
putmixed(win, 0, outbuf);
}
}
}
if (count)
display_nhwindow(win, TRUE);
else
pline("No %s are currently shown %s.",
do_mons ? "monsters" : "objects",
nearby ? "nearby" : "on the map");
destroy_nhwindow(win);
}
/* the '/' command */ /* the '/' command */
int int
dowhatis() dowhatis()