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))