From bbba8b82d2f3435fe6eba546773fe213299c5308 Mon Sep 17 00:00:00 2001 From: PatR Date: Fri, 16 Jun 2023 21:19:43 -0700 Subject: [PATCH] fix issue #1062 - monster hiding messages Reported by Umbire: if a statue of a hider-under was activated by a statue trap, it would hide underneath its own statue. Also, the hero saw a snake hide under unseen submerged kelp. Both of those things were exposed by new "you see hide" message rather than caused by it. It also led to the [re-]discovery that an existing monster hiding under a statue that was a not-yet- triggered trap prevented the trap from producing a monster. This redoes yesterday's can't-hide-under-statue change: hiders can hide under statues again, but they can't hide under anything at trap locations. [Pits containing one or more objects are an exception, although it seems silly that a hero is prevented from falling into one by the presence of a tiny creepy-crawly hiding under a ring or dart in there.] So, hider-underers won't be able to interfere with statue traps by being present at the trap location. [Trappers and lurkers-above probably need a similar restriction; I didn't look. They avoid trap spots rather than get lured to such by objects.] It also prevents newly created hider-underers from becoming hidden as part of the their creation (except when that creation is part of level creation) whether their creation uses up an object (statue activation, egg hatching) or there are simply other items present. That will prevent statue of a hider producing a monster that hides under the activated statue (which was happening due to the sequence create monster, transfer any statue contents to monster inventory, destroy statue). The can't-hide-under-statues code has been repurposed to prevent hiding under gold pieces unless there are at least 10 (arbitrary threshold) of those or they're in a pile with some other object(s). Sea monsters hide in water regardless of the presence of objects. Prevent other swimmers from hiding under objects at water locations. Such creatures don't have gills and shouldn't be able to stay submerged in hiding for an arbitrary length of time. [No exception is made for non-breathers. The overlap between swimmers and hider- underers is limited to small snakes, even though it is feasible for a creature wearing an amulet of magical breathing to polymorph into one. Heros don't spend enough time underwater to worry about snakes hiding under kelp or thrown junk.] Lastly, alter the "suddenly, you notice a " message if monster-vs-monster activity causes one you've just seen going into hiding comes back out again without any intervening messages. [I'm not sure whether something similar is needed for the "Wait. There's something there" message in the you-vs-monster case.] Fixes #1062 --- include/decl.h | 3 +++ include/flag.h | 3 ++- src/decl.c | 2 ++ src/makemon.c | 9 +++++--- src/mhitm.c | 13 ++++++++--- src/mon.c | 58 ++++++++++++++++++++++++++++++++++---------------- src/monmove.c | 47 ++++++++++++++++++++++++++++++++++------ src/polyself.c | 14 +++++++----- 8 files changed, 113 insertions(+), 36 deletions(-) diff --git a/include/decl.h b/include/decl.h index 075e8a996..5c61aa19d 100644 --- a/include/decl.h +++ b/include/decl.h @@ -515,6 +515,9 @@ struct instance_globals_l { /* mklev.c */ genericptr_t luathemes[MAXDUNGEON]; + /* mon.c */ + unsigned last_hider; /* m_id of hides-under mon seen going into hiding */ + /* nhlan.c */ #ifdef MAX_LAN_USERNAME char lusername[MAX_LAN_USERNAME]; diff --git a/include/flag.h b/include/flag.h index a17e67439..87202603d 100644 --- a/include/flag.h +++ b/include/flag.h @@ -442,7 +442,8 @@ enum plnmsg_types { PLNMSG_OK_DONT_DIE, /* overriding death in explore/wizard mode */ PLNMSG_BACK_ON_GROUND, /* leaving water */ PLNMSG_GROWL, /* growl() gave some message */ - PLNMSG_enum /* allows inserting new entries with unconditional trailing comma */ + PLNMSG_HIDE_UNDER, /* hero saw a monster hide under something */ + PLNMSG_enum /* 'none of the above' */ }; /* runmode options */ diff --git a/src/decl.c b/src/decl.c index 07ff63806..df1a36f18 100644 --- a/src/decl.c +++ b/src/decl.c @@ -477,6 +477,8 @@ const struct instance_globals_l g_init_l = { UNDEFINED_PTR, /* light_base */ /* mklev.c */ { UNDEFINED_PTR }, /* luathemes[] */ + /* mon.c */ + 0U, /* last_hider */ /* nhlan.c */ #ifdef MAX_LAN_USERNAME UNDEFINED_VALUES, /* lusername */ diff --git a/src/makemon.c b/src/makemon.c index 59eac9516..c6ec1a687 100644 --- a/src/makemon.c +++ b/src/makemon.c @@ -1277,10 +1277,11 @@ makemon( break; case S_SPIDER: case S_SNAKE: - if (gi.in_mklev) + if (gi.in_mklev) { if (x && y) (void) mkobj_at(RANDOM_CLASS, x, y, TRUE); - (void) hideunder(mtmp); + (void) hideunder(mtmp); + } break; case S_LIGHT: case S_ELEMENTAL: @@ -1290,7 +1291,9 @@ makemon( } break; case S_EEL: - (void) hideunder(mtmp); + if (gi.in_mklev) { + (void) hideunder(mtmp); + } break; case S_LEPRECHAUN: mtmp->msleeping = 1; diff --git a/src/mhitm.c b/src/mhitm.c index 5bd612c1c..cf25fed41 100644 --- a/src/mhitm.c +++ b/src/mhitm.c @@ -322,7 +322,7 @@ mattackm( mdef->msleeping = 0; } - /* undetect monsters become un-hidden if they are attacked */ + /* mundetected monsters become un-hidden if they are attacked */ if (mdef->mundetected) { mdef->mundetected = 0; newsym(mdef->mx, mdef->my); @@ -336,8 +336,15 @@ mattackm( if (!justone) montype = makeplural(montype); You("dream of %s.", montype); - } else - pline("Suddenly, you notice %s.", a_monnam(mdef)); + } else { + if (iflags.last_msg == PLNMSG_HIDE_UNDER + && mdef->m_id == gl.last_hider) + pline("%s emerges from hiding.", Monnam(mdef)); + else if (mdef->m_id == gl.last_hider) + You("notice %s.", mon_nam(mdef)); + else + pline("Suddenly, you notice %s.", a_monnam(mdef)); + } } } diff --git a/src/mon.c b/src/mon.c index 9cc190b62..777041459 100644 --- a/src/mon.c +++ b/src/mon.c @@ -4203,52 +4203,74 @@ maybe_unhide_at(coordxy x, coordxy y) (void) hideunder(mtmp); } -/* monster/hero tries to hide under something at the current location */ +/* monster/hero tries to hide under something at the current location; + if used by monster creation, should only happen during during level + creation, otherwise there will be message sequencing issues */ boolean hideunder(struct monst *mtmp) { struct trap *t; + struct obj *otmp; const char *seenmon = (char *) 0, *seenobj = (char *) 0; - int seeit = canseemon(mtmp); + int seeit = gi.in_mklev ? 0 : canseemon(mtmp); boolean oldundetctd, undetected = FALSE, is_u = (mtmp == &gy.youmonst); coordxy x = is_u ? u.ux : mtmp->mx, y = is_u ? u.uy : mtmp->my; - struct obj *otmp; if (mtmp == u.ustuck) { ; /* undetected==FALSE; can't hide if holding you or held by you */ - } else if (is_u ? (u.utrap && u.utraptype != TT_PIT) - : (mtmp->mtrapped - && (t = t_at(x, y)) != 0 && !is_pit(t->ttyp))) { - ; /* undetected==FALSE; can't hide while stuck in a non-pit trap */ + } else if ((is_u ? u.utrap : mtmp->mtrapped) + || ((t = t_at(x, y)) != 0 && !is_pit(t->ttyp))) { + ; /* undetected==FALSE; can't hide while trapped or on/in/under + any non-pit trap when not trapped */ } else if (mtmp->data->mlet == S_EEL) { - undetected = (is_pool(x, y) && !Is_waterlevel(&u.uz)); + /* aquatic creatures only hide under water, not under objects; + they don't do so on the Plane of Water or when hero is also + under water unless some obstacle blocks line-of-sight */ + undetected = (is_pool(x, y) && !Is_waterlevel(&u.uz) + && (!Underwater || !couldsee(x, y))); if (seeit) seenobj = "the water"; - } else if (hides_under(mtmp->data) && OBJ_AT(x, y) - && (otmp = gl.level.objects[x][y]) != 0 && can_hide_under_obj(otmp)) { - if (seeit) + } else if (hides_under(mtmp->data) + /* hider-underers only hide under objects */ + && (otmp = gl.level.objects[x][y]) != 0 + /* most things can be hidden under, but not all */ + && can_hide_under_obj(otmp) + /* aquatic creatures don't reach here; other swimmers + shouldn't hide beneath underwater objects */ + && !is_pool_or_lava(x, y)) { + if (seeit) /*&& (!is_pool(x, y) || (Underwater && distu(x, y) <= 2))*/ seenobj = ansimpleoname(otmp); - /* most monsters won't hide under cockatrice corpse but they + /* most monsters won't hide under a cockatrice corpse but they can hide under a pile containing more than just such corpses */ - while (otmp && otmp->otyp == CORPSE - && touch_petrifies(&mons[otmp->corpsenm])) - otmp = otmp->nexthere; - if (otmp != 0 || ((mtmp == &gy.youmonst) ? Stone_resistance - : resists_ston(mtmp))) + if (is_u ? !Stone_resistance : !resists_ston(mtmp)) + while (otmp && otmp->otyp == CORPSE + && touch_petrifies(&mons[otmp->corpsenm])) + otmp = otmp->nexthere; + if (otmp) undetected = TRUE; } if (is_u) { oldundetctd = u.uundetected != 0; u.uundetected = undetected ? 1 : 0; +#if 0 /* feedback handled via #monster */ + if (undetected && !oldundeteced && seenobj) + You("hide under %s.", seenobj); +#endif } else { if (seeit) seenmon = y_monnam(mtmp); oldundetctd = mtmp->mundetected != 0; mtmp->mundetected = undetected ? 1 : 0; - if (undetected && seenmon && seenobj) + /* the "you see" message won't be shown for monster hiding during + level creation because 'seeit' will be 0 so 'seenmon' and 'seenobj' + will be Null */ + if (undetected && seenmon && seenobj) { You_see("%s %s under %s.", seenmon, locomotion(mtmp->data, "hide"), seenobj); + iflags.last_msg = PLNMSG_HIDE_UNDER; + gl.last_hider = mtmp->m_id; + } } if (undetected != oldundetctd) newsym(x, y); diff --git a/src/monmove.c b/src/monmove.c index 369feb279..14e4d40c4 100644 --- a/src/monmove.c +++ b/src/monmove.c @@ -1898,15 +1898,50 @@ m_move_aggress(struct monst *mtmp, coordxy x, coordxy y) boolean can_hide_under_obj(struct obj *obj) { +/* uncomment '#define NO_HIDING_UNDER_STATUES' to prevent hiding under + * statues; that was introduced to avoid nullifying statue traps but + * isn't needed now that hiding at any non-pit trap site is disallowed */ +/* #define NO_HIDING_UNDER_STATUES */ struct trap *t; - if (!obj || (obj->otyp == STATUE - /* uncomment this next line for just statue traps */ - /* && (t = t_at(obj->ox, obj->oy)) != 0 && t->ttyp == STATUE_TRAP */ - )) + if (!obj || obj->where != OBJ_FLOOR) return FALSE; - return TRUE; - nhUse(t); + /* can't hide in/on/under traps (except pits) even when there is an + object here; since obj is on floor, its are up to date */ + if ((t = t_at(obj->ox, obj->oy)) != 0 && !is_pit(t->ttyp)) + return FALSE; + /* can't hide under small amount of coins unless non-coins are also + present; we expect coins to be a single stack but don't assume that */ + if (obj->oclass == COIN_CLASS) { + long coinquan = 0L; + + do { + /* 10 coins is arbitrary amount considered enough to hide under */ + if ((coinquan += obj->quan) >= 10L) + break; /* fall through to other checks */ + obj = obj->nexthere; + if (!obj) + return FALSE; /* whole pile was less than 10 coins */ + } while (obj->oclass == COIN_CLASS); + } +#ifdef NO_HIDING_UNDER_STATUES + /* + * 'obj' might have been changed, but only if we've skipped coins that + * are on the top of a pile. However, the statue loop will clobber it. + */ + /* can't hide under statues regardless of pile stacking order */ + while (obj) { + if (obj->otyp == STATUE) + return FALSE; + obj = obj->nexthere; + } + /* + * If we reach here, 'obj' is now Null but wasn't earlier so the original + * 'obj' can be hidden beneath. + */ +#undef NO_HIDING_UNDER_STATUES +#endif + return TRUE; /* can hide under the object */ } void diff --git a/src/polyself.c b/src/polyself.c index f15215cb5..0042dc488 100644 --- a/src/polyself.c +++ b/src/polyself.c @@ -682,7 +682,8 @@ polymon(int mntmp) { char buf[BUFSZ], ustuckNam[BUFSZ]; boolean sticking = sticks(gy.youmonst.data) && u.ustuck && !u.uswallow, - was_blind = !!Blind, dochange = FALSE, was_expelled = FALSE; + was_blind = !!Blind, dochange = FALSE, was_expelled = FALSE, + was_hiding_under = u.uundetected && hides_under(gy.youmonst.data); int mlvl, newMaxStr; if (gm.mvitals[mntmp].mvflags & G_GENOD) { /* allow G_EXTINCT */ @@ -833,7 +834,10 @@ polymon(int mntmp) break_armor(); drop_weapon(1); find_ac(); /* (repeated below) */ - (void) hideunder(&gy.youmonst); + /* if hiding under something and can't hide anymore, unhide now; + but don't auto-hide when not already hiding-under */ + if (was_hiding_under) + (void) hideunder(&gy.youmonst); if (u.utrap && u.utraptype == TT_PIT) { set_utrap(rn1(6, 2), TT_PIT); /* time to escape resets */ @@ -1702,6 +1706,7 @@ dogaze(void) return ECMD_TIME; } +/* called by domonability() for #monster */ int dohide(void) { @@ -1718,15 +1723,14 @@ dohide(void) : !sticks(gy.youmonst.data) ? "being held" : (humanoid(u.ustuck->data) ? "holding someone" : "holding that creature")); - if (u.uundetected - || (ismimic && U_AP_TYPE != M_AP_NOTHING)) { + if (u.uundetected || (ismimic && U_AP_TYPE != M_AP_NOTHING)) { u.uundetected = 0; gy.youmonst.m_ap_type = M_AP_NOTHING; newsym(u.ux, u.uy); } return ECMD_OK; } - /* note: the eel and hides_under cases are hypothetical; + /* note: hero-as-eel handling is incomplete but unnecessary; such critters aren't offered the option of hiding via #monster */ if (gy.youmonst.data->mlet == S_EEL && !is_pool(u.ux, u.uy)) { if (IS_FOUNTAIN(levl[u.ux][u.uy].typ))