Files
nethack/src/getpos.c
PatR 93f8e5b3b3 fix #S14702 - travel to covered vibrating square
Targeting '~' when vibrating square has been discovered would report
"Can't find dungeon feature '~'" if it was covered by an object or a
monster.

That's normal behavior for a trap but the vibrating square is only
one of those for display purposes.
2025-11-20 15:02:54 -08:00

1170 lines
42 KiB
C

/* NetHack 3.7 getpos.c $NHDT-Date: 1763708572 2025/11/20 23:02:52 $ $NHDT-Branch: NetHack-3.7 $:$NHDT-Revision: 1.6 $ */
/*-Copyright (c) Pasi Kallinen, 2023. */
/* NetHack may be freely redistributed. See license for details. */
#include "hack.h"
extern const char what_is_a_location[]; /* from pager.c */
staticfn void getpos_toggle_hilite_state(void);
staticfn void getpos_getvalids_selection(struct selectionvar *,
boolean (*)(coordxy, coordxy));
staticfn void getpos_help_keyxhelp(winid, const char *, const char *, int);
staticfn void getpos_help(boolean, const char *);
staticfn int QSORTCALLBACK cmp_coord_distu(const void *, const void *);
staticfn int gloc_filter_classify_glyph(int);
staticfn int gloc_filter_floodfill_matcharea(coordxy, coordxy);
staticfn void gloc_filter_floodfill(coordxy, coordxy);
staticfn void gloc_filter_init(void);
staticfn void gloc_filter_done(void);
staticfn void gather_locs(coord **, int *, int);
staticfn boolean known_vibrating_square_at(coordxy, coordxy);
staticfn void truncate_to_map(coordxy *, coordxy *, schar, schar);
staticfn void getpos_refresh(void);
/* Callback function for getpos() to highlight desired map locations.
* Parameter TRUE: initialize and highlight, FALSE: done (remove highlights).
*/
staticfn void (*getpos_hilitefunc)(boolean) = (void (*)(boolean)) 0;
staticfn boolean (*getpos_getvalid)(coordxy, coordxy)
= (boolean (*)(coordxy, coordxy)) 0;
enum getposHiliteState {
HiliteNormalMap = 0,
HiliteGoodposSymbol = 1,
HiliteBackground = 2,
};
static enum getposHiliteState
getpos_hilite_state = HiliteNormalMap,
defaultHiliteState = HiliteNormalMap;
void
getpos_sethilite(
void (*gp_hilitef)(boolean),
boolean (*gp_getvalidf)(coordxy, coordxy))
{
boolean (*old_getvalid)(coordxy, coordxy) = getpos_getvalid;
uint32 old_map_frame_color = gw.wsettings.map_frame_color;
struct selectionvar *sel = selection_new();
defaultHiliteState = iflags.bgcolors ? HiliteBackground : HiliteNormalMap;
if (gp_getvalidf != old_getvalid)
getpos_hilite_state = defaultHiliteState;
getpos_getvalids_selection(sel, getpos_getvalid);
getpos_hilitefunc = gp_hilitef;
getpos_getvalid = gp_getvalidf;
getpos_getvalids_selection(sel, getpos_getvalid);
gw.wsettings.map_frame_color = (getpos_hilite_state == HiliteBackground)
? HI_ZAP : NO_COLOR;
if (getpos_getvalid != old_getvalid
|| gw.wsettings.map_frame_color != old_map_frame_color)
selection_force_newsyms(sel);
selection_free(sel, TRUE);
}
/* cycle 'getpos_hilite_state' to its next state;
when 'bgcolors' is Off, it will alternate between not showing valid
positions and showing them via temporary S_goodpos symbol;
when 'bgcolors' is On, there are three states and showing them via
setting background color becomes the default */
staticfn void
getpos_toggle_hilite_state(void)
{
/* getpos_hilitefunc isn't Null */
if (getpos_hilite_state == HiliteGoodposSymbol) {
/* currently on, finish */
(*getpos_hilitefunc)(FALSE); /* tmp_at(DISP_END) */
}
getpos_hilite_state = (getpos_hilite_state + 1)
% (iflags.bgcolors ? 3 : 2);
/* resetting the callback functions to their current values will draw
valid-spots with background color if that is the new state and turn
off that color if it was the previous state */
getpos_sethilite(getpos_hilitefunc, getpos_getvalid);
if (getpos_hilite_state == HiliteGoodposSymbol) {
/* now on, begin */
(*getpos_hilitefunc)(TRUE);
}
}
boolean
mapxy_valid(coordxy x, coordxy y)
{
if (getpos_getvalid)
return (*getpos_getvalid)(x, y);
return FALSE;
}
staticfn void
getpos_getvalids_selection(
struct selectionvar *sel,
boolean (*validf)(coordxy, coordxy))
{
coordxy x, y;
if (!sel || !validf)
return;
for (x = 1; x < sel->wid; x++)
for (y = 0; y < sel->hei; y++)
if ((*validf)(x, y))
selection_setpoint(x, y, sel, 1);
}
static const char *const gloc_descr[NUM_GLOCS][4] = {
{ "any monsters", "monster", "next/previous monster", "monsters" },
{ "any items", "item", "next/previous object", "objects" },
{ "any doors", "door", "next/previous door or doorway",
"doors or doorways" },
{ "any unexplored areas", "unexplored area", "unexplored location",
"locations next to unexplored locations" },
{ "anything interesting", "interesting thing", "anything interesting",
"anything interesting" },
{ "any valid locations", "valid location", "valid location",
"valid locations" }
};
static const char *const gloc_filtertxt[NUM_GFILTER] = {
"",
" in view",
" in this area"
};
staticfn void
getpos_help_keyxhelp(
winid tmpwin,
const char *k1, const char *k2,
int gloc)
{
char sbuf[BUFSZ], fbuf[QBUFSZ];
const char *move_cursor_to = "move the cursor to ",
*filtertxt = gloc_filtertxt[iflags.getloc_filter];
if (gloc == GLOC_EXPLORE) {
/* default of "move to unexplored location" is inaccurate
because the position will be one spot short of that */
move_cursor_to = "move the cursor next to an ";
if (iflags.getloc_usemenu)
/* default is too wide for basic 80-column tty so shorten it
to avoid wrapping */
filtertxt = strsubst(strcpy(fbuf, filtertxt),
"this area", "area");
}
Sprintf(sbuf, "Use '%s'/'%s' to %s%s%s.",
k1, k2,
iflags.getloc_usemenu ? "get a menu of " : move_cursor_to,
gloc_descr[gloc][2 + iflags.getloc_usemenu], filtertxt);
putstr(tmpwin, 0, sbuf);
}
DISABLE_WARNING_FORMAT_NONLITERAL
/* the response for '?' help request in getpos() */
staticfn void
getpos_help(boolean force, const char *goal)
{
static const char *const fastmovemode[2] = { "8 units at a time",
"skipping same glyphs" };
char sbuf[BUFSZ];
boolean doing_what_is;
winid tmpwin = create_nhwindow(NHW_MENU);
Sprintf(sbuf,
"Use '%s', '%s', '%s', '%s' to move the cursor to %s.", /* hjkl */
visctrl(cmd_from_func(do_move_west)),
visctrl(cmd_from_func(do_move_south)),
visctrl(cmd_from_func(do_move_north)),
visctrl(cmd_from_func(do_move_east)), goal);
putstr(tmpwin, 0, sbuf);
Sprintf(sbuf,
"Use '%s', '%s', '%s', '%s' to fast-move the cursor, %s.",
visctrl(cmd_from_func(do_run_west)),
visctrl(cmd_from_func(do_run_south)),
visctrl(cmd_from_func(do_run_north)),
visctrl(cmd_from_func(do_run_east)),
fastmovemode[iflags.getloc_moveskip]);
putstr(tmpwin, 0, sbuf);
Sprintf(sbuf, "(or prefix normal move with '%s' or '%s' to fast-move)",
visctrl(cmd_from_func(do_run)),
visctrl(cmd_from_func(do_rush)));
putstr(tmpwin, 0, sbuf);
putstr(tmpwin, 0, "Or enter a background symbol (ex. '<').");
Sprintf(sbuf, "Use '%s' to move the cursor on yourself.",
visctrl(gc.Cmd.spkeys[NHKF_GETPOS_SELF]));
putstr(tmpwin, 0, sbuf);
if (!iflags.terrainmode || (iflags.terrainmode & TER_MON) != 0) {
getpos_help_keyxhelp(tmpwin,
visctrl(gc.Cmd.spkeys[NHKF_GETPOS_MON_NEXT]),
visctrl(gc.Cmd.spkeys[NHKF_GETPOS_MON_PREV]),
GLOC_MONS);
}
if (goal && !strcmp(goal, "a monster"))
goto skip_non_mons;
if (!iflags.terrainmode || (iflags.terrainmode & TER_OBJ) != 0) {
getpos_help_keyxhelp(tmpwin,
visctrl(gc.Cmd.spkeys[NHKF_GETPOS_OBJ_NEXT]),
visctrl(gc.Cmd.spkeys[NHKF_GETPOS_OBJ_PREV]),
GLOC_OBJS);
}
if (!iflags.terrainmode || (iflags.terrainmode & TER_MAP) != 0) {
/* these are primarily useful when choosing a travel
destination for the '_' command */
getpos_help_keyxhelp(tmpwin,
visctrl(gc.Cmd.spkeys[NHKF_GETPOS_DOOR_NEXT]),
visctrl(gc.Cmd.spkeys[NHKF_GETPOS_DOOR_PREV]),
GLOC_DOOR);
getpos_help_keyxhelp(tmpwin,
visctrl(gc.Cmd.spkeys[NHKF_GETPOS_UNEX_NEXT]),
visctrl(gc.Cmd.spkeys[NHKF_GETPOS_UNEX_PREV]),
GLOC_EXPLORE);
getpos_help_keyxhelp(tmpwin,
visctrl(gc.Cmd.spkeys[NHKF_GETPOS_INTERESTING_NEXT]),
visctrl(gc.Cmd.spkeys[NHKF_GETPOS_INTERESTING_PREV]),
GLOC_INTERESTING);
}
Sprintf(sbuf, "Use '%s' to change fast-move mode to %s.",
visctrl(gc.Cmd.spkeys[NHKF_GETPOS_MOVESKIP]),
fastmovemode[!iflags.getloc_moveskip]);
putstr(tmpwin, 0, sbuf);
if (!iflags.terrainmode || (iflags.terrainmode & TER_DETECT) == 0) {
Sprintf(sbuf, "Use '%s' to toggle menu listing for possible targets.",
visctrl(gc.Cmd.spkeys[NHKF_GETPOS_MENU]));
putstr(tmpwin, 0, sbuf);
Sprintf(sbuf,
"Use '%s' to change the mode of limiting possible targets.",
visctrl(gc.Cmd.spkeys[NHKF_GETPOS_LIMITVIEW]));
putstr(tmpwin, 0, sbuf);
}
if (!iflags.terrainmode) {
char kbuf[BUFSZ];
if (getpos_getvalid) {
Sprintf(sbuf, "Use '%s' or '%s' to move to valid locations.",
visctrl(gc.Cmd.spkeys[NHKF_GETPOS_VALID_NEXT]),
visctrl(gc.Cmd.spkeys[NHKF_GETPOS_VALID_PREV]));
putstr(tmpwin, 0, sbuf);
}
if (getpos_hilitefunc) {
Sprintf(sbuf, "Use '%s' to toggle marking of valid locations.",
visctrl(gc.Cmd.spkeys[NHKF_GETPOS_SHOWVALID]));
putstr(tmpwin, 0, sbuf);
}
Sprintf(sbuf, "Use '%s' to toggle automatic description.",
visctrl(gc.Cmd.spkeys[NHKF_GETPOS_AUTODESC]));
putstr(tmpwin, 0, sbuf);
if (iflags.cmdassist) { /* assisting the '/' command, I suppose... */
Sprintf(sbuf,
(iflags.getpos_coords == GPCOORDS_NONE)
? "(Set 'whatis_coord' option to include coordinates with '%s' text.)"
: "(Reset 'whatis_coord' option to omit coordinates from '%s' text.)",
visctrl(gc.Cmd.spkeys[NHKF_GETPOS_AUTODESC]));
}
skip_non_mons:
/* disgusting hack; the alternate selection characters work for any
getpos call, but only matter for dowhatis (and doquickwhatis,
also for dotherecmdmenu's simulated mouse) */
doing_what_is = (goal == what_is_a_location);
if (doing_what_is) {
Sprintf(kbuf, "'%s' or '%s' or '%s' or '%s'",
visctrl(gc.Cmd.spkeys[NHKF_GETPOS_PICK]),
visctrl(gc.Cmd.spkeys[NHKF_GETPOS_PICK_Q]),
visctrl(gc.Cmd.spkeys[NHKF_GETPOS_PICK_O]),
visctrl(gc.Cmd.spkeys[NHKF_GETPOS_PICK_V]));
} else {
Sprintf(kbuf, "'%s'", visctrl(gc.Cmd.spkeys[NHKF_GETPOS_PICK]));
}
Snprintf(sbuf, sizeof(sbuf),
"Type a %s when you are at the right place.", kbuf);
putstr(tmpwin, 0, sbuf);
if (doing_what_is) {
Sprintf(sbuf,
" '%s' describe current spot, show 'more info', move to another spot.",
visctrl(gc.Cmd.spkeys[NHKF_GETPOS_PICK_V]));
putstr(tmpwin, 0, sbuf);
Sprintf(sbuf,
" '%s' describe current spot,%s move to another spot;",
visctrl(gc.Cmd.spkeys[NHKF_GETPOS_PICK]),
flags.help && !force ? " prompt if 'more info'," : "");
putstr(tmpwin, 0, sbuf);
Sprintf(sbuf,
" '%s' describe current spot, move to another spot;",
visctrl(gc.Cmd.spkeys[NHKF_GETPOS_PICK_Q]));
putstr(tmpwin, 0, sbuf);
Sprintf(sbuf,
" '%s' describe current spot, stop looking at things;",
visctrl(gc.Cmd.spkeys[NHKF_GETPOS_PICK_O]));
putstr(tmpwin, 0, sbuf);
}
}
if (!force)
putstr(tmpwin, 0, "Type Space or Escape when you're done.");
putstr(tmpwin, 0, "");
display_nhwindow(tmpwin, TRUE);
destroy_nhwindow(tmpwin);
}
RESTORE_WARNING_FORMAT_NONLITERAL
staticfn int QSORTCALLBACK
cmp_coord_distu(const void *a, const void *b)
{
const coord *c1 = a;
const coord *c2 = b;
int dx, dy, dist_1, dist_2;
dx = u.ux - c1->x;
dy = u.uy - c1->y;
dist_1 = max(abs(dx), abs(dy));
dx = u.ux - c2->x;
dy = u.uy - c2->y;
dist_2 = max(abs(dx), abs(dy));
if (dist_1 == dist_2)
return (c1->y != c2->y) ? (c1->y - c2->y) : (c1->x - c2->x);
return dist_1 - dist_2;
}
#define IS_UNEXPLORED_LOC(x,y) \
(isok((x), (y)) \
&& glyph_is_unexplored(levl[(x)][(y)].glyph) \
&& !levl[(x)][(y)].seenv)
#define GLOC_SAME_AREA(x,y) \
(isok((x), (y)) \
&& (selection_getpoint((x),(y), gg.gloc_filter_map)))
staticfn int
gloc_filter_classify_glyph(int glyph)
{
int c;
if (!glyph_is_cmap(glyph))
return 0;
c = glyph_to_cmap(glyph);
if (is_cmap_room(c) || is_cmap_furniture(c))
return 1;
else if (is_cmap_wall(c) || c == S_tree)
return 2;
else if (is_cmap_corr(c))
return 3;
else if (is_cmap_water(c))
return 4;
else if (is_cmap_lava(c))
return 5;
return 0;
}
staticfn int
gloc_filter_floodfill_matcharea(coordxy x, coordxy y)
{
int glyph = back_to_glyph(x, y);
if (!levl[x][y].seenv)
return FALSE;
if (glyph == gg.gloc_filter_floodfill_match_glyph)
return TRUE;
if (gloc_filter_classify_glyph(glyph)
== gloc_filter_classify_glyph(gg.gloc_filter_floodfill_match_glyph))
return TRUE;
return FALSE;
}
staticfn void
gloc_filter_floodfill(coordxy x, coordxy y)
{
gg.gloc_filter_floodfill_match_glyph = back_to_glyph(x, y);
set_selection_floodfillchk(gloc_filter_floodfill_matcharea);
selection_floodfill(gg.gloc_filter_map, x, y, FALSE);
}
staticfn void
gloc_filter_init(void)
{
if (iflags.getloc_filter == GFILTER_AREA) {
if (!gg.gloc_filter_map) {
gg.gloc_filter_map = selection_new();
}
/* special case: if we're in a doorway, try to figure out which
direction we're moving, and use that side of the doorway */
if (IS_DOOR(levl[u.ux][u.uy].typ)) {
if ((u.dx || u.dy) && isok(u.ux + u.dx, u.uy + u.dy)) {
gloc_filter_floodfill(u.ux + u.dx, u.uy + u.dy);
} else {
/* TODO: maybe add both sides of the doorway? */
}
} else {
gloc_filter_floodfill(u.ux, u.uy);
}
}
}
staticfn void
gloc_filter_done(void)
{
if (gg.gloc_filter_map) {
selection_free(gg.gloc_filter_map, TRUE);
gg.gloc_filter_map = (struct selectionvar *) 0;
}
}
staticfn boolean
known_vibrating_square_at(coordxy x, coordxy y)
{
/* note: this only acknowledges the genuine vibrating square, not
fake ones produced by wizard mode wishing for traps which could
possibly be transfered to normal play via bones file */
if (invocation_pos(x, y)) {
struct trap *ttmp = t_at(x, y);
return ttmp && ttmp->ttyp == VIBRATING_SQUARE && ttmp->tseen;
}
return FALSE;
}
DISABLE_WARNING_UNREACHABLE_CODE
boolean
gather_locs_interesting(
coordxy x, coordxy y,
int gloc)
{
int glyph, sym;
if (iflags.getloc_filter == GFILTER_VIEW && !cansee(x, y))
return FALSE;
if (iflags.getloc_filter == GFILTER_AREA && !GLOC_SAME_AREA(x, y)
&& !GLOC_SAME_AREA(x - 1, y) && !GLOC_SAME_AREA(x, y - 1)
&& !GLOC_SAME_AREA(x + 1, y) && !GLOC_SAME_AREA(x, y + 1))
return FALSE;
glyph = glyph_at(x, y);
sym = glyph_is_cmap(glyph) ? glyph_to_cmap(glyph) : -1;
switch (gloc) {
default:
case GLOC_MONS:
/* unlike '/M', this skips monsters revealed by
warning glyphs and remembered unseen ones */
return (glyph_is_monster(glyph)
&& glyph != monnum_to_glyph(PM_LONG_WORM_TAIL, MALE)
&& glyph != monnum_to_glyph(PM_LONG_WORM_TAIL, FEMALE));
case GLOC_OBJS:
return (glyph_is_object(glyph)
&& glyph != objnum_to_glyph(BOULDER)
&& glyph != objnum_to_glyph(ROCK));
case GLOC_DOOR:
return (glyph_is_cmap(glyph)
&& (is_cmap_door(sym)
|| is_cmap_drawbridge(sym)
|| sym == S_ndoor));
case GLOC_EXPLORE:
return (glyph_is_cmap(glyph)
&& !glyph_is_nothing(glyph_to_cmap(glyph))
&& (is_cmap_door(sym)
|| is_cmap_drawbridge(sym)
|| sym == S_ndoor
|| is_cmap_room(sym)
|| is_cmap_corr(sym))
&& (IS_UNEXPLORED_LOC(x + 1, y)
|| IS_UNEXPLORED_LOC(x - 1, y)
|| IS_UNEXPLORED_LOC(x, y + 1)
|| IS_UNEXPLORED_LOC(x, y - 1)));
case GLOC_VALID:
if (getpos_getvalid)
return (*getpos_getvalid)(x, y);
FALLTHROUGH;
/*FALLTHRU*/
case GLOC_INTERESTING:
return (gather_locs_interesting(x, y, GLOC_DOOR)
|| !((glyph_is_cmap(glyph)
&& (is_cmap_wall(sym)
|| sym == S_tree
|| sym == S_bars
|| sym == S_ice
|| sym == S_air
|| sym == S_cloud
|| is_cmap_lava(sym)
|| is_cmap_water(sym)
|| sym == S_ndoor
|| is_cmap_room(sym)
|| is_cmap_corr(sym)))
|| glyph_is_nothing(glyph)
|| glyph_is_unexplored(glyph))
|| known_vibrating_square_at(x, y));
}
/*NOTREACHED*/
return FALSE;
}
RESTORE_WARNINGS
/* gather locations for monsters or objects shown on the map */
staticfn void
gather_locs(coord **arr_p, int *cnt_p, int gloc)
{
int pass, idx;
coordxy x, y;
/*
* We always include the hero's location even if there is no monster
* (invisible hero without see invisible) or object (usual case)
* displayed there. That way, the count will always be at least 1,
* and player has a visual indicator (cursor returns to hero's spot)
* highlighting when successive 'm's or 'o's have cycled all the way
* through all monsters or objects.
*
* Hero's spot will always sort to array[0] because it will always
* be the shortest distance (namely, 0 units) away from <u.ux,u.uy>.
*/
gloc_filter_init();
*cnt_p = idx = 0;
for (pass = 0; pass < 2; pass++) {
for (x = 1; x < COLNO; x++)
for (y = 0; y < ROWNO; y++) {
if (u_at(x, y) || gather_locs_interesting(x, y, gloc)) {
if (!pass) {
++*cnt_p;
} else {
(*arr_p)[idx].x = x;
(*arr_p)[idx].y = y;
++idx;
}
}
}
if (!pass) /* end of first pass */
*arr_p = (coord *) alloc(*cnt_p * sizeof (coord));
else /* end of second pass */
qsort(*arr_p, *cnt_p, sizeof (coord), cmp_coord_distu);
} /* pass */
gloc_filter_done();
}
char *
dxdy_to_dist_descr(coordxy dx, coordxy dy, boolean fulldir)
{
static char buf[30];
int dst;
if (!dx && !dy) {
Sprintf(buf, "here");
} else if ((dst = xytod(dx, dy)) != -1) {
/* explicit direction; 'one step' is implicit */
Sprintf(buf, "%s", directionname(dst));
} else {
static const char *const dirnames[4][2] = {
{ "n", "north" },
{ "s", "south" },
{ "w", "west" },
{ "e", "east" } };
buf[0] = '\0';
/* 9999: protect buf[] against overflow caused by invalid values */
if (dy) {
if (abs(dy) > 9999)
dy = sgn(dy) * 9999;
Sprintf(eos(buf), "%d%s%s", abs(dy), dirnames[(dy > 0)][fulldir],
dx ? "," : "");
}
if (dx) {
if (abs(dx) > 9999)
dx = sgn(dx) * 9999;
Sprintf(eos(buf), "%d%s", abs(dx),
dirnames[2 + (dx > 0)][fulldir]);
}
}
return buf;
}
DISABLE_WARNING_FORMAT_NONLITERAL
/* coordinate formatting for 'whatis_coord' option */
char *
coord_desc(coordxy x, coordxy y, char *outbuf, char cmode)
{
static char screen_fmt[16]; /* [12] suffices: "[%02d,%02d]" */
int dx, dy;
outbuf[0] = '\0';
switch (cmode) {
default:
break;
case GPCOORDS_COMFULL:
case GPCOORDS_COMPASS:
/* "east", "3s", "2n,4w" */
dx = x - u.ux;
dy = y - u.uy;
Sprintf(outbuf, "(%s)",
dxdy_to_dist_descr(dx, dy, cmode == GPCOORDS_COMFULL));
break;
case GPCOORDS_MAP: /* x,y */
/* upper left corner of map is <1,0>;
with default COLNO,ROWNO lower right corner is <79,20> */
Sprintf(outbuf, "<%d,%d>", x, y);
break;
case GPCOORDS_SCREEN: /* y+2,x */
/* for normal map sizes, force a fixed-width formatting so that
/m, /M, /o, and /O output lines up cleanly; map sizes bigger
than Nx999 or 999xM will still work, but not line up like normal
when displayed in a column setting.
The (100) is placed in brackets below to mark the [: "03"] as
explicit compile-time dead code for clang */
if (!*screen_fmt)
Sprintf(screen_fmt, "[%%%sd,%%%sd]",
(ROWNO - 1 + 2 < (100)) ? "02" : "03",
(COLNO - 1 < (100)) ? "02" : "03");
/* map line 0 is screen row 2;
map column 0 isn't used, map column 1 is screen column 1 */
Sprintf(outbuf, screen_fmt, y + 2, x);
break;
}
return outbuf;
}
RESTORE_WARNING_FORMAT_NONLITERAL
void
auto_describe(coordxy cx, coordxy cy)
{
coord cc;
int sym = 0;
char tmpbuf[BUFSZ];
const char *firstmatch = "unknown";
cc.x = cx;
cc.y = cy;
if (do_screen_description(cc, TRUE, sym, tmpbuf, &firstmatch,
(struct permonst **) 0)) {
(void) coord_desc(cx, cy, tmpbuf, iflags.getpos_coords);
custompline((SUPPRESS_HISTORY | OVERRIDE_MSGTYPE | NO_CURS_ON_U),
"%s%s%s%s%s", firstmatch, *tmpbuf ? " " : "", tmpbuf,
(iflags.autodescribe
&& getpos_getvalid && !(*getpos_getvalid)(cx, cy))
? " (invalid target)" : "",
(iflags.getloc_travelmode && !is_valid_travelpt(cx, cy))
? " (no travel path)" : "");
curs(WIN_MAP, cx, cy);
flush_screen(0);
}
}
boolean
getpos_menu(coord *ccp, int gloc)
{
coord *garr = DUMMY;
int gcount = 0;
winid tmpwin;
anything any;
int i, pick_cnt;
menu_item *picks = (menu_item *) 0;
char tmpbuf[BUFSZ];
int clr = NO_COLOR;
gather_locs(&garr, &gcount, gloc);
if (gcount < 2) { /* gcount always includes the hero */
free((genericptr_t) garr);
You("cannot %s %s.",
(iflags.getloc_filter == GFILTER_VIEW) ? "see" : "detect",
gloc_descr[gloc][0]);
return FALSE;
}
tmpwin = create_nhwindow(NHW_MENU);
start_menu(tmpwin, MENU_BEHAVE_STANDARD);
any = cg.zeroany;
/* gather_locs returns array[0] == you. skip it. */
for (i = 1; i < gcount; i++) {
char fullbuf[BUFSZ];
coord tmpcc;
const char *firstmatch = "unknown";
int sym = 0;
any.a_int = i + 1;
tmpcc.x = garr[i].x;
tmpcc.y = garr[i].y;
if (do_screen_description(tmpcc, TRUE, sym, tmpbuf,
&firstmatch, (struct permonst **)0)) {
(void) coord_desc(garr[i].x, garr[i].y, tmpbuf,
iflags.getpos_coords);
Snprintf(fullbuf, sizeof fullbuf, "%s%s%s", firstmatch,
(*tmpbuf ? " " : ""), tmpbuf);
add_menu(tmpwin, &nul_glyphinfo, &any, 0, 0,
ATR_NONE, clr, fullbuf, MENU_ITEMFLAGS_NONE);
}
}
Sprintf(tmpbuf, "Pick %s%s%s",
an(gloc_descr[gloc][1]),
gloc_filtertxt[iflags.getloc_filter],
iflags.getloc_travelmode ? " for travel destination" : "");
end_menu(tmpwin, tmpbuf);
pick_cnt = select_menu(tmpwin, PICK_ONE, &picks);
destroy_nhwindow(tmpwin);
if (pick_cnt > 0) {
ccp->x = garr[picks->item.a_int - 1].x;
ccp->y = garr[picks->item.a_int - 1].y;
free((genericptr_t) picks);
}
free((genericptr_t) garr);
return (pick_cnt > 0);
}
/* add dx,dy to cx,cy, truncating at map edges */
staticfn void
truncate_to_map(coordxy *cx, coordxy *cy, schar dx, schar dy)
{
/* diagonal moves complicate this... */
if (*cx + dx < 1) {
dy -= sgn(dy) * (1 - (*cx + dx));
dx = 1 - *cx; /* so that (cx+dx == 1) */
} else if (*cx + dx > COLNO - 1) {
dy += sgn(dy) * ((COLNO - 1) - (*cx + dx));
dx = (COLNO - 1) - *cx;
}
if (*cy + dy < 0) {
dx -= sgn(dx) * (0 - (*cy + dy));
dy = 0 - *cy; /* so that (cy+dy == 0) */
} else if (*cy + dy > ROWNO - 1) {
dx += sgn(dx) * ((ROWNO - 1) - (*cy + dy));
dy = (ROWNO - 1) - *cy;
}
*cx += dx;
*cy += dy;
}
/* called when ^R typed; if '$' is being shown for valid spots, remove that;
if alternate background color is being shown for that, redraw it */
staticfn void
getpos_refresh(void)
{
if (getpos_hilitefunc && getpos_hilite_state == HiliteGoodposSymbol) {
(*getpos_hilitefunc)(FALSE); /* tmp_at(DISP_END) */
getpos_hilite_state = defaultHiliteState;
}
docrt_flags(docrtRefresh);
if (getpos_hilitefunc && getpos_hilite_state == HiliteBackground) {
/* resetting to current values will draw valid-spots highlighting */
getpos_sethilite(getpos_hilitefunc, getpos_getvalid);
}
}
/* have the player use movement keystrokes to position the cursor at a
particular map location, then use one of [.,:;] to pick the spot */
int
getpos(coord *ccp, boolean force, const char *goal)
{
static struct {
int nhkf, ret;
} const pick_chars_def[] = {
{ NHKF_GETPOS_PICK, LOOK_TRADITIONAL },
{ NHKF_GETPOS_PICK_Q, LOOK_QUICK },
{ NHKF_GETPOS_PICK_O, LOOK_ONCE },
{ NHKF_GETPOS_PICK_V, LOOK_VERBOSE }
};
static const int mMoOdDxX_def[] = {
NHKF_GETPOS_MON_NEXT,
NHKF_GETPOS_MON_PREV,
NHKF_GETPOS_OBJ_NEXT,
NHKF_GETPOS_OBJ_PREV,
NHKF_GETPOS_DOOR_NEXT,
NHKF_GETPOS_DOOR_PREV,
NHKF_GETPOS_UNEX_NEXT,
NHKF_GETPOS_UNEX_PREV,
NHKF_GETPOS_INTERESTING_NEXT,
NHKF_GETPOS_INTERESTING_PREV,
NHKF_GETPOS_VALID_NEXT,
NHKF_GETPOS_VALID_PREV
};
struct _cmd_queue cq, *cmdq;
const char *cp;
char pick_chars[6];
char mMoOdDxX[13];
int result = 0;
int i, c;
int sidx;
coordxy cx, cy;
coordxy tx = u.ux, ty = u.uy;
boolean msg_given = TRUE; /* clear message window by default */
boolean show_goal_msg = FALSE;
coord *garr[NUM_GLOCS] = DUMMY;
int gcount[NUM_GLOCS] = DUMMY;
int gidx[NUM_GLOCS] = DUMMY;
schar udx = u.dx, udy = u.dy, udz = u.dz;
int dx, dy;
boolean rushrun = FALSE;
/* temporary? if we have a queued direction, return the adjacent spot
in that direction */
if (!gi.in_doagain) {
if ((cmdq = cmdq_pop()) != 0) {
cq = *cmdq;
free((genericptr_t) cmdq);
if (cq.typ == CMDQ_DIR && !cq.dirz) {
ccp->x = u.ux + cq.dirx;
ccp->y = u.uy + cq.diry;
} else {
cmdq_clear(CQ_CANNED);
result = -1;
}
return result;
}
}
for (i = 0; i < SIZE(pick_chars_def); i++)
pick_chars[i] = gc.Cmd.spkeys[pick_chars_def[i].nhkf];
pick_chars[SIZE(pick_chars_def)] = '\0';
for (i = 0; i < SIZE(mMoOdDxX_def); i++)
mMoOdDxX[i] = gc.Cmd.spkeys[mMoOdDxX_def[i]];
mMoOdDxX[SIZE(mMoOdDxX_def)] = '\0';
if (handle_tip(TIP_GETPOS))
show_goal_msg = TRUE; /* tip has overwritten prompt in mesg window */
if (!goal)
goal = "desired location";
if (flags.verbose) {
pline("(For instructions type a '%s')",
visctrl(gc.Cmd.spkeys[NHKF_GETPOS_HELP]));
msg_given = TRUE;
}
cx = gg.getposx = ccp->x;
cy = gg.getposy = ccp->y;
#ifdef CLIPPING
cliparound(cx, cy);
#endif
curs(WIN_MAP, cx, cy);
flush_screen(0);
#ifdef MAC
lock_mouse_cursor(TRUE);
#endif
lock_mouse_buttons(TRUE);
for (;;) {
if (show_goal_msg) {
pline("Move cursor to %s:", goal);
curs(WIN_MAP, cx, cy);
flush_screen(0);
show_goal_msg = FALSE;
} else if (iflags.autodescribe && !msg_given) {
auto_describe(cx, cy);
}
rushrun = FALSE;
if ((cmdq = cmdq_pop()) != 0) {
if (cmdq->typ == CMDQ_KEY) {
c = cmdq->key;
} else {
cmdq_clear(CQ_CANNED);
result = -1;
goto exitgetpos;
}
free(cmdq);
} else {
c = readchar_poskey(&tx, &ty, &sidx);
/* remember_getpos is normally False because reusing the
cursor positioning during ^A is almost never the right
thing to do, but caller could set it if that was needed */
if (iflags.remember_getpos && !gi.in_doagain)
cmdq_add_key(CQ_REPEAT, c);
}
if (iflags.autodescribe)
msg_given = FALSE;
if (c == gc.Cmd.spkeys[NHKF_ESC]) {
cx = cy = -10;
msg_given = TRUE; /* force clear */
result = -1;
break;
}
if (c == cmd_from_func(do_run) || c == cmd_from_func(do_rush)) {
c = readchar_poskey(&tx, &ty, &sidx);
rushrun = TRUE;
}
if (c == 0) {
if (!isok(tx, ty))
continue;
/* a mouse click event, just assign and return */
cx = tx;
cy = ty;
break;
}
if ((cp = strchr(pick_chars, c)) != 0) {
/* '.' => 0, ',' => 1, ';' => 2, ':' => 3 */
result = pick_chars_def[(int) (cp - pick_chars)].ret;
break;
} else if (movecmd(c, MV_WALK)) {
if (rushrun)
goto do_rushrun;
dx = u.dx;
dy = u.dy;
truncate_to_map(&cx, &cy, dx, dy);
goto nxtc;
} else if (movecmd(c, MV_RUSH) || movecmd(c, MV_RUN)) {
do_rushrun:
if (iflags.getloc_moveskip) {
/* skip same glyphs */
int glyph = glyph_at(cx, cy);
dx = u.dx;
dy = u.dy;
while (isok(cx + dx, cy + dy)
&& glyph == glyph_at(cx + dx, cy + dy)
&& isok(cx + dx + u.dx, cy + dy + u.dy)
&& glyph == glyph_at(cx + dx + u.dx, cy + dy + u.dy)) {
dx += u.dx;
dy += u.dy;
}
} else {
dx = 8 * u.dx;
dy = 8 * u.dy;
}
truncate_to_map(&cx, &cy, dx, dy);
goto nxtc;
}
if (c == gc.Cmd.spkeys[NHKF_GETPOS_HELP] || redraw_cmd(c)) {
/* '?' will redraw twice, first when removing popup text window
after showing the help text, then to reset highlighting */
if (c == gc.Cmd.spkeys[NHKF_GETPOS_HELP])
getpos_help(force, goal);
/* ^R: docrt(), hilite_state = default */
getpos_refresh();
curs(WIN_MAP, cx, cy);
/* update message window to reflect that we're still targeting */
show_goal_msg = TRUE;
} else if (c == gc.Cmd.spkeys[NHKF_GETPOS_SHOWVALID]) {
if (getpos_hilitefunc) {
getpos_toggle_hilite_state();
curs(WIN_MAP, cx, cy);
}
show_goal_msg = TRUE; /* we're still targeting */
goto nxtc;
} else if (c == gc.Cmd.spkeys[NHKF_GETPOS_AUTODESC]) {
iflags.autodescribe = !iflags.autodescribe;
pline("Automatic description %sis %s.",
flags.verbose ? "of features under cursor " : "",
iflags.autodescribe ? "on" : "off");
if (!iflags.autodescribe)
show_goal_msg = TRUE;
msg_given = TRUE;
goto nxtc;
} else if (c == gc.Cmd.spkeys[NHKF_GETPOS_LIMITVIEW]) {
static const char *const view_filters[NUM_GFILTER] = {
"Not limiting targets",
"Limiting targets to those in sight",
"Limiting targets to those in same area"
};
iflags.getloc_filter = (iflags.getloc_filter + 1) % NUM_GFILTER;
for (i = 0; i < NUM_GLOCS; i++) {
if (garr[i]) {
free((genericptr_t) garr[i]);
garr[i] = NULL;
}
gidx[i] = gcount[i] = 0;
}
pline("%s.", view_filters[iflags.getloc_filter]);
msg_given = TRUE;
goto nxtc;
} else if (c == gc.Cmd.spkeys[NHKF_GETPOS_MENU]) {
iflags.getloc_usemenu = !iflags.getloc_usemenu;
pline("%s a menu to show possible targets%s.",
iflags.getloc_usemenu ? "Using" : "Not using",
iflags.getloc_usemenu
? " for 'm|M', 'o|O', 'd|D', and 'x|X'" : "");
msg_given = TRUE;
goto nxtc;
} else if (c == gc.Cmd.spkeys[NHKF_GETPOS_SELF]) {
/* reset 'm&M', 'o&O', &c; otherwise, there's no way for player
to achieve that except by manually cycling through all spots */
for (i = 0; i < NUM_GLOCS; i++)
gidx[i] = 0;
cx = u.ux;
cy = u.uy;
goto nxtc;
} else if (c == gc.Cmd.spkeys[NHKF_GETPOS_MOVESKIP]) {
iflags.getloc_moveskip = !iflags.getloc_moveskip;
pline("%skipping over similar terrain when fastmoving the cursor.",
iflags.getloc_moveskip ? "S" : "Not s");
msg_given = TRUE;
goto nxtc;
} else if ((cp = strchr(mMoOdDxX, c)) != 0) { /* 'm|M', 'o|O', &c */
/* nearest or farthest monster or object or door or unexplored */
int gtmp = (int) (cp - mMoOdDxX), /* 0..7 */
gloc = gtmp >> 1; /* 0..3 */
if (iflags.getloc_usemenu) {
coord tmpcrd;
if (getpos_menu(&tmpcrd, gloc)) {
cx = tmpcrd.x;
cy = tmpcrd.y;
}
goto nxtc;
}
if (!garr[gloc]) {
gather_locs(&garr[gloc], &gcount[gloc], gloc);
gidx[gloc] = 0; /* garr[][0] is hero's spot */
}
if (!(gtmp & 1)) { /* c=='m' || c=='o' || c=='d' || c=='x') */
gidx[gloc] = (gidx[gloc] + 1) % gcount[gloc];
} else { /* c=='M' || c=='O' || c=='D' || c=='X') */
if (--gidx[gloc] < 0)
gidx[gloc] = gcount[gloc] - 1;
}
cx = garr[gloc][gidx[gloc]].x;
cy = garr[gloc][gidx[gloc]].y;
goto nxtc;
} else {
if (!strchr(quitchars, c)) {
char matching[MAXPCHARS];
int pass, k = 0;
coordxy lo_x, lo_y, hi_x, hi_y;
(void) memset((genericptr_t) matching, 0, sizeof matching);
for (sidx = 0; sidx < MAXPCHARS; sidx++) {
/* don't even try to match some terrain: walls, room... */
if (is_cmap_wall(sidx) || is_cmap_room(sidx)
|| is_cmap_corr(sidx) || is_cmap_door(sidx)
|| sidx == S_ndoor)
continue;
if (c == defsyms[sidx].sym
|| c == (int) gs.showsyms[sidx]
/* have '^' match webs and vibrating square or any
other trap that uses something other than '^' */
|| (c == '^' && is_cmap_trap(sidx))
/* have room engraving character (default '`')
match corridor engravings (default '#') too */
|| (c == gs.showsyms[S_engroom]
&& is_cmap_engraving(sidx)))
matching[sidx] = (char) ++k;
}
if (k) {
for (pass = 0; pass <= 1; pass++) {
/* pass 0: just past current pos to lower right;
pass 1: upper left corner to current pos */
lo_y = (pass == 0) ? cy : 0;
hi_y = (pass == 0) ? ROWNO - 1 : cy;
for (ty = lo_y; ty <= hi_y; ty++) {
lo_x = (pass == 0 && ty == lo_y) ? cx + 1 : 1;
hi_x = (pass == 1 && ty == hi_y) ? cx : COLNO - 1;
for (tx = lo_x; tx <= hi_x; tx++) {
/* first, look at what is currently visible
(might be monster) */
k = glyph_at(tx, ty);
if (glyph_is_cmap(k)
&& matching[glyph_to_cmap(k)])
goto foundc;
/* next, try glyph that's remembered here
(might be trap or object) */
if (svl.level.flags.hero_memory
/* !terrainmode: don't move to remembered
trap or object if not currently shown */
&& !iflags.terrainmode) {
k = levl[tx][ty].glyph;
if (glyph_is_cmap(k)
&& matching[glyph_to_cmap(k)])
goto foundc;
}
/* FIXME: check player-specified vib.sq trap
symbol rather than or in addition to '~' */
if (c == '~'
&& known_vibrating_square_at(tx, ty))
goto foundc;
/* last, try actual terrain here (shouldn't
we be using svl.lastseentyp[][] instead?) */
if (levl[tx][ty].seenv) {
k = back_to_glyph(tx, ty);
if (glyph_is_cmap(k)
&& matching[glyph_to_cmap(k)])
goto foundc;
}
continue;
foundc:
cx = tx, cy = ty;
if (msg_given) {
clear_nhwindow(WIN_MESSAGE);
msg_given = FALSE;
}
goto nxtc;
} /* column */
} /* row */
} /* pass */
pline("Can't find dungeon feature '%c'.", c);
msg_given = TRUE;
goto nxtc;
} else {
char note[QBUFSZ];
if (!force)
Strcpy(note, "aborted");
else /* hjkl */
Sprintf(note, "use '%s', '%s', '%s', '%s' or '%s'",
visctrl(cmd_from_func(do_move_west)),
visctrl(cmd_from_func(do_move_south)),
visctrl(cmd_from_func(do_move_north)),
visctrl(cmd_from_func(do_move_east)),
visctrl(gc.Cmd.spkeys[NHKF_GETPOS_PICK]));
pline("Unknown direction: '%s' (%s).", visctrl((char) c),
note);
msg_given = TRUE;
} /* k => matching */
} /* !quitchars */
if (force)
goto nxtc;
pline("Done.");
msg_given = FALSE; /* suppress clear */
cx = -1;
cy = 0;
result = 0; /* not -1 */
break;
}
nxtc:
gg.getposx = cx, gg.getposy = cy;
#ifdef CLIPPING
cliparound(cx, cy);
#endif
curs(WIN_MAP, cx, cy);
flush_screen(0);
}
exitgetpos:
#ifdef MAC
lock_mouse_cursor(FALSE);
#endif
lock_mouse_buttons(FALSE);
if (msg_given)
clear_nhwindow(WIN_MESSAGE);
ccp->x = cx;
ccp->y = cy;
gg.getposx = gg.getposy = 0;
for (i = 0; i < NUM_GLOCS; i++)
if (garr[i])
free((genericptr_t) garr[i]);
getpos_sethilite(NULL, NULL);
u.dx = udx, u.dy = udy, u.dz = udz;
return result;
}
/*getpos.c*/