From 1fd943e8609f5932f34ef35de3d099e47c2c9854 Mon Sep 17 00:00:00 2001 From: PatR Date: Thu, 29 Aug 2024 14:08:23 -0700 Subject: [PATCH] trap/secret door detection enhancement The old secret door detection just redisplayed locations with discoveries (secret doors and traps, mostly). Somewhere along the line it was augmented to find hidden monsters and to deliver one or two messages reporting how many things had been discovered. Now it has been augmented again, to find trapped doors and chests, and to supply a message when the detection attempt fails to find anything. More substantially, it highlights the relevant locations as they're found, before the feedback message(s). Initially I was using tmp_at() to mark all significant locations, but that required --More-- and for player to acknowledge it when detection was done. That would probably be ok for wand of secret door detection and spell of detect unseen, but it would be a hassle for ^E. It's been revised to use flash_glyph_at() [previously only used when ^G creates unseen monsters, I think]. The new behavior seems to be working reasonably well. For curses, the 'timed_delay' option must be set. flash_glyph_at() calls flush_screen() between its output and nap in each cycle of multiple flashes, but that evidently isn't sufficient for curses. Maybe curses init should just force on 'timed_delay'. I've left the tmp_at() stuff in. We might want to modify things to use it instead of flash_glyph_at() when the accessibility flag is set. Its current compile-time selection won't be adequate though. --- doc/fixes3-7-0.txt | 2 + src/detect.c | 205 ++++++++++++++++++++++++++++++++++----------- 2 files changed, 158 insertions(+), 49 deletions(-) diff --git a/doc/fixes3-7-0.txt b/doc/fixes3-7-0.txt index a29a52c5d..9d9552b29 100644 --- a/doc/fixes3-7-0.txt +++ b/doc/fixes3-7-0.txt @@ -2664,6 +2664,8 @@ if hero is punished or tethered to a buried iron ball and has no inventory (or during streaming video 'query_menu' option to use a menu when asked certain yes/no questions pyrolisk eggs explode when broken +wand of secret door detection, spell of detect unseen, and wizard mode ^E now + flash the cursor at each location where detection finds something Platform- and/or Interface-Specific New Features diff --git a/src/detect.c b/src/detect.c index 347829a34..1cadedf69 100644 --- a/src/detect.c +++ b/src/detect.c @@ -11,6 +11,16 @@ #include "hack.h" #include "artifact.h" +#ifndef FOUND_FLASH_COUNT +/* for screen alert shown to player when secret door detection or ^E + finds stuff; to use tmp_at() instead of flash_glyph_at(), define as 0; + extra code for tmp_at() will be included and the flash_glyph_at() + calls will execute but won't do anything */ +#define FOUND_FLASH_COUNT 6 +#endif + +struct found_things; + staticfn boolean unconstrain_map(void); staticfn void reconstrain_map(void); staticfn void map_redisplay(void); @@ -20,13 +30,16 @@ staticfn void do_dknown_of(struct obj *); staticfn boolean check_map_spot(coordxy, coordxy, char, unsigned); staticfn boolean clear_stale_map(char, unsigned); staticfn void sense_trap(struct trap *, coordxy, coordxy, int); -staticfn int detect_obj_traps(struct obj *, boolean, int); +staticfn int detect_obj_traps(struct obj *, boolean, int, + struct found_things *) NO_NNARGS; staticfn void display_trap_map(int); staticfn int furniture_detect(void); +staticfn void foundone(coordxy, coordxy, int); staticfn void findone(coordxy, coordxy, genericptr_t); staticfn void openone(coordxy, coordxy, genericptr_t); staticfn int mfind0(struct monst *, boolean); -staticfn int reveal_terrain_getglyph(coordxy, coordxy, unsigned, int, unsigned); +staticfn int reveal_terrain_getglyph(coordxy, coordxy, unsigned, int, + unsigned); /* dummytrap: used when detecting traps finds a door or chest trap; the couple of fields that matter are always re-initialized during use so @@ -36,6 +49,7 @@ static struct trap dummytrap; /* data for enhanced feedback from findone() */ struct found_things { + coord ft_cc; /* for passing extra info to detect_obj_traps() */ uchar num_sdoors; uchar num_scorrs; uchar num_traps; @@ -892,11 +906,12 @@ staticfn int detect_obj_traps( struct obj *objlist, boolean show_them, - int how) /* 1 for misleading map feedback */ + int how, /* 1 for misleading map feedback */ + struct found_things *ft) /* being called by findone() when non-Null */ { struct obj *otmp; coordxy x, y; - int result = OTRAP_NONE; + int trapglyph, result = OTRAP_NONE; /* * TODO? Display locations of unarmed land mine and beartrap objects. @@ -904,17 +919,32 @@ detect_obj_traps( */ dummytrap.ttyp = TRAPPED_CHEST; + trapglyph = ft ? trap_to_glyph(&dummytrap) : GLYPH_NOTHING; for (otmp = objlist; otmp; otmp = otmp->nobj) { - if (Is_box(otmp) && otmp->otrapped - && get_obj_location(otmp, &x, &y, BURIED_TOO | CONTAINED_TOO)) { + x = y = 0; /* lint suppression */ + if ((Is_box(otmp) && otmp->otrapped) || Has_contents(otmp)) { + /* !get_obj_location and !isok should both be impossible here */ + if (!get_obj_location(otmp, &x, &y, BURIED_TOO | CONTAINED_TOO) + || !isok(x, y) + || (ft && (x != ft->ft_cc.x || y != ft->ft_cc.y))) + continue; + } + if (Is_box(otmp) && otmp->otrapped) { result |= u_at(x, y) ? OTRAP_HERE : OTRAP_THERE; + if (ft) { + flash_glyph_at(x, y, trapglyph, FOUND_FLASH_COUNT); + } if (show_them) { dummytrap.tx = x, dummytrap.ty = y; sense_trap(&dummytrap, x, y, how); } + if (ft) { + foundone(x, y, trapglyph); + ft->num_traps++; + } } if (Has_contents(otmp)) - result |= detect_obj_traps(otmp->cobj, show_them, how); + result |= detect_obj_traps(otmp->cobj, show_them, how, ft); } return result; } @@ -924,7 +954,7 @@ display_trap_map(int cursed_src) { struct monst *mon; struct trap *ttmp; - int door, glyph, ter_typ = TER_DETECT | ( cursed_src ? TER_OBJ : TER_TRP ); + int door, glyph, ter_typ = TER_DETECT | (cursed_src ? TER_OBJ : TER_TRP); coord cc; cls(); @@ -933,14 +963,14 @@ display_trap_map(int cursed_src) /* show chest traps first, first buried chests then floor chests, so that subsequent floor trap display will override if both types are present at the same location */ - (void) detect_obj_traps(svl.level.buriedobjlist, TRUE, cursed_src); - (void) detect_obj_traps(fobj, TRUE, cursed_src); + (void) detect_obj_traps(svl.level.buriedobjlist, TRUE, cursed_src, NULL); + (void) detect_obj_traps(fobj, TRUE, cursed_src, NULL); for (mon = fmon; mon; mon = mon->nmon) { if (DEADMONSTER(mon) || (mon->isgd && !mon->mx)) continue; - (void) detect_obj_traps(mon->minvent, TRUE, cursed_src); + (void) detect_obj_traps(mon->minvent, TRUE, cursed_src, NULL); } - (void) detect_obj_traps(gi.invent, TRUE, cursed_src); + (void) detect_obj_traps(gi.invent, TRUE, cursed_src, NULL); for (ttmp = gf.ftrap; ttmp; ttmp = ttmp->ntrap) sense_trap(ttmp, 0, 0, cursed_src); @@ -948,7 +978,7 @@ display_trap_map(int cursed_src) dummytrap.ttyp = TRAPPED_DOOR; for (door = 0; door < gd.doorindex; door++) { cc = svd.doors[door]; - if (levl[cc.x][cc.y].typ == SDOOR) /* see above */ + if (levl[cc.x][cc.y].typ == SDOOR) /* can't be trapped; see above */ continue; if (levl[cc.x][cc.y].doormask & D_TRAPPED) { dummytrap.tx = cc.x, dummytrap.ty = cc.y; @@ -997,14 +1027,14 @@ trap_detect( found = TRUE; } /* chest traps (might be buried or carried) */ - if ((tr = detect_obj_traps(fobj, FALSE, 0)) != OTRAP_NONE) { + if ((tr = detect_obj_traps(fobj, FALSE, 0, NULL)) != OTRAP_NONE) { if (tr & OTRAP_THERE) { display_trap_map(cursed_src); return 0; } found = TRUE; } - if ((tr = detect_obj_traps(svl.level.buriedobjlist, FALSE, 0)) + if ((tr = detect_obj_traps(svl.level.buriedobjlist, FALSE, 0, NULL)) != OTRAP_NONE) { if (tr & OTRAP_THERE) { display_trap_map(cursed_src); @@ -1015,7 +1045,8 @@ trap_detect( for (mon = fmon; mon; mon = mon->nmon) { if (DEADMONSTER(mon) || (mon->isgd && !mon->mx)) continue; - if ((tr = detect_obj_traps(mon->minvent, FALSE, 0)) != OTRAP_NONE) { + if ((tr = detect_obj_traps(mon->minvent, FALSE, 0, NULL)) + != OTRAP_NONE) { if (tr & OTRAP_THERE) { display_trap_map(cursed_src); return 0; @@ -1023,7 +1054,7 @@ trap_detect( found = TRUE; } } - if (detect_obj_traps(gi.invent, FALSE, 0) != OTRAP_NONE) + if (detect_obj_traps(gi.invent, FALSE, 0, NULL) != OTRAP_NONE) found = TRUE; /* door traps */ for (door = 0; door < gd.doorindex; door++) { @@ -1568,58 +1599,113 @@ cvt_sdoor_to_door(struct rm *lev) lev->doormask = newmask; } -/* find something at one location; it should find all somethings there +/* update the map for something which has just been found by wand of secret + door detection or wizard mode ^E; will be called multiple times during a + single operation if multiple things of interest are discovered */ +staticfn void +foundone(coordxy zx, coordxy zy, int glyph) +{ + if (glyph_is_cmap(glyph) || glyph_is_unexplored(glyph)) + levl[zx][zy].seenv = SVALL; + + if (!Blind) { + seenV save_viz = gv.viz_array[zy][zx]; + + gv.viz_array[zy][zx] = COULD_SEE | IN_SIGHT; + newsym(zx, zy); + gv.viz_array[zy][zx] = save_viz; + } + +#if FOUND_FLASH_COUNT == 0 + /* + * This works [for non-monsters at present] but flash_glyph_at() + * seems preferrable because the tmp_at() variation requires that + * the player respond to --More-- at the end, the flash_glyph + * variation doesn't. + */ + tmp_at(DISP_CHANGE, glyph); + tmp_at(zx, zy); +#endif +} + +/* find something at one location; this should find all somethings there since it is used for magical detection rather than physical searching */ staticfn void findone(coordxy zx, coordxy zy, genericptr_t whatfound) { - struct trap *ttmp; - struct monst *mtmp; + struct rm *lev = &levl[zx][zy]; + struct trap *ttmp = t_at(zx, zy); + struct monst *mtmp = m_at(zx, zy); struct found_things *found_p = (struct found_things *) whatfound; - /* - * This used to use if/else-if/else-if/else/end-if but that only - * found the first hidden thing at the location. Two hidden things - * at the same spot is uncommon, but it's possible for an undetected - * monster to be hiding at the location of an unseen trap. - */ + if (mtmp && (DEADMONSTER(mtmp) || (mtmp->isgd && !mtmp->mx))) + mtmp = (struct monst *) NULL; + found_p->ft_cc.x = zx; /* needed by detect_obj_traps() */ + found_p->ft_cc.y = zy; - if (levl[zx][zy].typ == SDOOR) { - cvt_sdoor_to_door(&levl[zx][zy]); /* .typ = DOOR */ + if (lev->typ == SDOOR) { + nhsym sym = lev->horizontal ? S_hcdoor : S_vcdoor; + + flash_glyph_at(zx, zy, cmap_to_glyph(sym), FOUND_FLASH_COUNT); + cvt_sdoor_to_door(lev); /* set lev->typ = DOOR */ magic_map_background(zx, zy, 0); - newsym(zx, zy); + foundone(zx, zy, back_to_glyph(zx, zy)); found_p->num_sdoors++; - } else if (levl[zx][zy].typ == SCORR) { - levl[zx][zy].typ = CORR; + } else if (lev->typ == SCORR) { + flash_glyph_at(zx, zy, cmap_to_glyph(S_corr), FOUND_FLASH_COUNT); + lev->typ = CORR; unblock_point(zx, zy); magic_map_background(zx, zy, 0); - newsym(zx, zy); + foundone(zx, zy, cmap_to_glyph(S_corr)); found_p->num_scorrs++; } - if ((ttmp = t_at(zx, zy)) != 0 && !ttmp->tseen + if (ttmp && !ttmp->tseen /* [shouldn't successful 'find' reveal and activate statue traps?] */ && ttmp->ttyp != STATUE_TRAP) { + flash_glyph_at(zx, zy, trap_to_glyph(ttmp), FOUND_FLASH_COUNT); ttmp->tseen = 1; - newsym(zx, zy); + foundone(zx, zy, trap_to_glyph(ttmp)); found_p->num_traps++; } + if (closed_door(zx, zy) && (lev->doormask & D_TRAPPED) != 0) { + dummytrap.ttyp = TRAPPED_DOOR; + dummytrap.tx = zx, dummytrap.ty = zy; + flash_glyph_at(zx, zy, trap_to_glyph(&dummytrap), FOUND_FLASH_COUNT); + dummytrap.tseen = 1; + map_trap(&dummytrap, 1); + sense_trap(&dummytrap, zx, zy, 0); /* handles Hallucination */ + foundone(zx, zy, trap_to_glyph(&dummytrap)); + found_p->num_traps++; + } + /* trapped chests */ + (void) detect_obj_traps(svl.level.buriedobjlist, TRUE, 0, found_p); + (void) detect_obj_traps(fobj, TRUE, 0, found_p); + if (mtmp) + (void) detect_obj_traps(mtmp->minvent, TRUE, 0, found_p); + if (u_at(zx, zy)) + (void) detect_obj_traps(gi.invent, TRUE, 0, found_p); - if ((mtmp = m_at(zx, zy)) != 0 - /* brings hidden monster out of hiding even if already sensed */ - && (!canspotmon(mtmp) || mtmp->mundetected || M_AP_TYPE(mtmp))) { + if (mtmp && (!canspotmon(mtmp) || mtmp->mundetected || M_AP_TYPE(mtmp))) { if (M_AP_TYPE(mtmp)) { + flash_glyph_at(zx, zy, mon_to_glyph(mtmp, rn2_on_display_rng), + FOUND_FLASH_COUNT); seemimic(mtmp); + /*foundone(zx, zy, mon_to_glyph(mtmp, rn2_on_display_rng);*/ found_p->num_mons++; } else if (mtmp->mundetected && (is_hider(mtmp->data) || hides_under(mtmp->data) || mtmp->data->mlet == S_EEL)) { + flash_glyph_at(zx, zy, mon_to_glyph(mtmp, rn2_on_display_rng), + FOUND_FLASH_COUNT); mtmp->mundetected = 0; + /*foundone(zx, zy, mon_to_glyph(mtmp, rn2_on_display_rng);*/ newsym(zx, zy); found_p->num_mons++; } - if (!glyph_is_invisible(levl[zx][zy].glyph)) { + if (!glyph_is_invisible(lev->glyph)) { if (!canspotmon(mtmp)) { + flash_glyph_at(zx, zy, GLYPH_INVISIBLE, FOUND_FLASH_COUNT); map_invisible(zx, zy); found_p->num_invis++; } @@ -1627,6 +1713,8 @@ findone(coordxy zx, coordxy zy, genericptr_t whatfound) found_p->num_kept_invis++; } } else if (unmap_invisible(zx, zy)) { + /* flash the invisible monster glyph because it is already gone */ + flash_glyph_at(zx, zy, GLYPH_INVISIBLE, FOUND_FLASH_COUNT); found_p->num_cleared_invis++; } } @@ -1702,20 +1790,21 @@ findit(void) struct found_things found; /* - * FIXME: - * When things are found, this should show the updated map and allow - * browsing. - * - * Currently, "You reveal a trap!" will map the trap but not reveal it - * if that trap is covered by something, which is fairly common for - * early levels where corpses of fake heroes usually hide the traps - * that killed them. That's most likely to occur for wizard mode ^E - * but can happen in normal play by using wand of secret door detection. + * findit() -> do_clear_area(findone) -> findone() -> foundone() + * is used to notify player where various things have been found. + * Changing FOUND_FLASH_COUNT to 0 will switch to tmp_at() to + * highlight all discoveries for the current operation, but requires + * player to respond to --More-- when done. Neither allows browsing + * the map via getpos() autodescribe (until after it has reverted to + * normal display, where found traps might be covered by objects). */ if (u.uswallow) return 0; +#if FOUND_FLASH_COUNT == 0 /* _COUNT > 0 doesn't need to init tmp_at() */ + tmp_at(DISP_ALL, GLYPH_NOTHING); +#endif (void) memset((genericptr_t) &found, 0, sizeof found); do_clear_area(u.ux, u.uy, BOLT_LIM, findone, (genericptr_t) &found); /* count that controls "reveal" punctuation; 0..4 */ @@ -1751,6 +1840,12 @@ findit(void) Strcat(buf, "a trap"); num += found.num_traps; } + +#if FOUND_FLASH_COUNT == 0 + int tmp_num; + tmp_num = num; /* sdoors, scorrs, and traps call tmp_at() */ +#endif + if (found.num_mons) { if (*buf) Strcat(buf, (k > 2) ? ", and " : " and "); @@ -1765,10 +1860,10 @@ findit(void) if (found.num_invis) { if (found.num_invis > 1) - Sprintf(buf, "%d%s invisible monsters", found.num_invis, + Sprintf(buf, "%d%s unseen monsters", found.num_invis, found.num_kept_invis ? " other" : ""); else - Sprintf(buf, "%s invisible monster", + Sprintf(buf, "%s unseen monster", found.num_kept_invis ? "another" : "an"); You("detect %s!", buf); num += found.num_invis; @@ -1783,6 +1878,16 @@ findit(void) } /* note: num_kept_invis is not included in the final result */ + if (!num) + You("don't find anything."); +#if FOUND_FLASH_COUNT == 0 + else if (tmp_num) { + flush_screen(1); + display_nhwindow(WIN_MAP, TRUE); + } + tmp_at(DISP_END, GLYPH_NOTHING); /* note: outside of 'if (tmp_num) { }' */ +#endif + return num; } @@ -2283,4 +2388,6 @@ reveal_terrain( return; } +#undef FOUND_FLASH_COUNT + /*detect.c*/