mon interaction with terrain was postponed

Fix #576

Reported initially by @copperwater for polymorphed monsters:
"observe how the [polymorphed-monster] hovers placidly above the water for
several turns like Wile E. Coyote before it gets a move, realizes it's
above water, and drowns. Ditto for lava."

A comment in the GitHub issue thread by @Tomsod pointed out that a
revived corpse could do the same.

This should set things up for other terrain fallout if discovered or
implemented in the future.
   - consume an additional bit in enum mon_terrain_effects (hack.h)
   - include the additional bit in TERRAIN_FALLOUT_MASK (monst.h)
   - add detection to maybe_set_terrain_effects (mon.c)
   - add action to terrain_effects (mon.c)

Implemented by stealing some upper unused mstate bits to avoid
invalidating existing save and bones.
This commit is contained in:
nhmall
2026-08-23 18:51:13 -04:00
parent f913cb3704
commit 9b4a8dfb05
13 changed files with 114 additions and 16 deletions
+2
View File
@@ -85,6 +85,8 @@ don't refer to pet as "Your pet" until after the taming process has finished
checkpoint fixes (pr #1660 by k21971)
upon return to a level, catch up on remaining nutrition value by the same
percentage as the catch up for the weight
after polymorph or revival from corpse, monsters were not interacting with
the terrain until that monster's next move
Platform- and/or Interface-Specific Fixes
+3
View File
@@ -786,6 +786,9 @@ struct instance_globals_p {
unsigned pline_flags;
char prevmsg[BUFSZ];
/* mon.c */
enum mon_terrain_effects pending_terrain_effects;
/* potion.c */
int potion_nothing;
int potion_unkn;
+3
View File
@@ -1869,6 +1869,9 @@ extern void see_monster_closeup(struct monst *, boolean) NONNULLARG1;
extern void see_nearby_monsters(void);
extern void shieldeff_mon(struct monst *) NONNULLARG1;
extern void flash_mon(struct monst *) NONNULLARG1;
extern boolean maybe_set_terrain_effects(struct monst *,
struct permonst *) NONNULLARG1;
extern void terrain_effects(void);
/* ### mondata.c ### */
+8
View File
@@ -725,6 +725,14 @@ struct plinemsg_type {
/* bitmask for callers of hide_unhide_msgtypes() */
#define MSGTYP_MASK_REP_SHOW ((1 << MSGTYP_NOREP) | (1 << MSGTYP_NOSHOW))
enum mon_terrain_effects {
no_terrain_effects = 0x00000000,
/* these bit values must be beyond the MON_ ranges in monst.h
since they get set on mon->mstate */
nonflyer_vs_liquid = 0x10000000,
candrown_vs_liquid = 0x20000000,
};
/* polyself flags */
enum polyself_flags {
POLY_NOFLAGS = 0x00,
+17 -11
View File
@@ -55,17 +55,23 @@ enum m_ap_types {
M_AP_MONSTER = 3 /* a monster; mostly used for cloned Wizard */
};
#define MON_FLOOR 0x0000
#define MON_OFFMAP 0x0001
#define MON_DETACH 0x0002
#define MON_MIGRATING 0x0004
#define MON_LIMBO 0x0008
#define MON_BUBBLEMOVE 0x0010
#define MON_ENDGAME_FREE 0x0020
#define MON_ENDGAME_MIGR 0x0040
#define MON_OBLITERATE 0x0080
#define MON_STILL_ARRIVING 0x0100
#define MON_PARKED 0x0200
#define MON_FLOOR 0x00000000
#define MON_OFFMAP 0x00000001
#define MON_DETACH 0x00000002
#define MON_MIGRATING 0x00000004
#define MON_LIMBO 0x00000008
#define MON_BUBBLEMOVE 0x00000010
#define MON_ENDGAME_FREE 0x00000020
#define MON_ENDGAME_MIGR 0x00000040
#define MON_OBLITERATE 0x00000080
#define MON_STILL_ARRIVING 0x00000100
#define MON_PARKED 0x00000200
/* 0x10000000 through 0x40000000 match enum pending_terrain_effects */
/* 0x10000000 */ /* nonflyer_vs_liquid */
/* 0x20000000 */ /* candrown_vs_liquid */
/* 0x40000000 */
#define TERRAIN_FALLOUT_MASK 0x70000000
#define M_AP_TYPMASK 0x7
#define M_AP_F_DKNOWN 0x8
+5
View File
@@ -204,6 +204,11 @@ moveloop_core(void)
encumber_msg();
svc.context.mon_moving = TRUE;
/* call terrain_effects during mon_moving phase */
if (gp.pending_terrain_effects) {
terrain_effects();
gp.pending_terrain_effects = no_terrain_effects;
}
do {
monscanmove = movemon();
if (u.umovement >= NORMAL_SPEED)
+2
View File
@@ -637,6 +637,8 @@ static const struct instance_globals_p g_init_p = {
/* pline.c */
0U, /* pline_flags */
UNDEFINED_VALUES, /* prevmsg */
/* mon.c */
no_terrain_effects, /* pending_terrain_effects */
/* potion.c */
UNDEFINED_VALUE, /* potion_nothing */
UNDEFINED_VALUE, /* potion_unkn */
+1
View File
@@ -2185,6 +2185,7 @@ revive_corpse(struct obj *corpse)
pline("%s disappears%s!", The(cname), effect);
}
}
(void) maybe_set_terrain_effects(mtmp, 0);
break;
case OBJ_MINVENT: /* probably a nymph's */
+2
View File
@@ -427,6 +427,7 @@ mon_arrive(struct monst *mtmp, int when)
stairway *stway;
d_level fromdlev;
mtmp->mstate &= ~TERRAIN_FALLOUT_MASK; /* shouldn't be set anyway */
mtmp->mstate |= MON_STILL_ARRIVING;
mtmp->nmon = fmon;
fmon = mtmp;
@@ -905,6 +906,7 @@ migrate_to_level(
/* prepare to take mtmp off the map */
num_segs = mon_leave(mtmp);
/* take off map and move mtmp from fmon list to migrating_mons */
mtmp->mstate &= ~TERRAIN_FALLOUT_MASK;
relmon(mtmp, &gm.migrating_mons); /* mtmp->mx,my retain their value */
mtmp->mstate |= MON_MIGRATING;
+62 -1
View File
@@ -2790,9 +2790,9 @@ m_detach(
shkgone(mtmp);
if (mtmp->wormno)
wormgone(mtmp);
mtmp->mstate &= ~TERRAIN_FALLOUT_MASK;
if (In_endgame(&u.uz))
mtmp->mstate |= MON_ENDGAME_FREE;
if ((mtmp->mstate & MON_DETACH) != 0) {
impossible("m_detach: %s is already detached?",
minimal_monnam(mtmp, FALSE));
@@ -3936,6 +3936,7 @@ elemental_clog(struct monst *mon)
if (mtmp) {
int mx = mtmp->mx, my = mtmp->my;
mtmp->mstate &= ~TERRAIN_FALLOUT_MASK;
mtmp->mstate |= MON_OBLITERATE;
mongone(mtmp);
/* places in the code might still reference mtmp->mx, mtmp->my */
@@ -4055,6 +4056,7 @@ mnearto(
but for the moment it is leaving */
mon_leaving_level(othermon);
othermon->mx = othermon->my = 0; /* 'othermon' is not on the map */
othermon->mstate &= ~TERRAIN_FALLOUT_MASK;
othermon->mstate |= MON_OFFMAP;
}
@@ -5493,6 +5495,7 @@ newcham(
if (!(mtmp->misc_worn_check & W_ARMG))
mselftouch(mtmp, "No longer petrify-resistant, ",
!svc.context.mon_moving);
(void) maybe_set_terrain_effects(mtmp, olddata);
check_gear_next_turn(mtmp);
/* This ought to re-test can_carry() on each item in the inventory
@@ -5541,6 +5544,64 @@ newcham(
return 1;
}
boolean
maybe_set_terrain_effects(struct monst *mtmp, struct permonst *oldmdat)
{
struct permonst *mdat = mtmp->data;
boolean changed = FALSE;
/* mtmp is in liquid */
if (is_pool(mtmp->mx, mtmp->my) || Is_waterlevel(&u.uz)
|| is_lava(mtmp->mx, mtmp->my)
|| IS_FOUNTAIN(levl[mtmp->mx][mtmp->my].typ)) {
/* mtmp is a non-flyer/floater/levitator */
if (!is_flyer(mdat)) {
changed = (!oldmdat ||
(((is_flyer(oldmdat) != is_flyer(mdat))
|| (is_floater(oldmdat) != is_floater(mdat)))
&& !(mtmp == u.usteed && (Flying || Levitation))));
gp.pending_terrain_effects |= nonflyer_vs_liquid;
mtmp->mstate |= (long) nonflyer_vs_liquid;
}
/* swimmer/amphibious/breathless to something that is not */
if (!cant_drown(mdat)) {
changed = (!oldmdat || ((cant_drown(oldmdat) != cant_drown(mdat))));
gp.pending_terrain_effects |= candrown_vs_liquid;
mtmp->mstate |= (long) candrown_vs_liquid;
}
}
/* TODO: handle other terrains that could be harmful to a
revived mon or polymorphed mon */
return changed;
}
/*
* The terrain is what it is, but the monster changed
* in some way (polymorphed or newly revived), and has
* been flagged as problematic with the terrain.
*/
void
terrain_effects(void)
{
struct monst *mtmp, *mtmp2;
for (mtmp = fmon; mtmp; mtmp = mtmp2) {
mtmp2 = mtmp->nmon;
if ((mtmp->mstate & TERRAIN_FALLOUT_MASK) != 0) {
/* nonflyer_vs_liquid */
if (!DEADMONSTER(mtmp)
&& ((mtmp->mstate & (long) (nonflyer_vs_liquid | candrown_vs_liquid)) != 0))
(void) minliquid(mtmp);
/* always clear these bits, even if DEADMONSTER */
mtmp->mstate &= ~TERRAIN_FALLOUT_MASK;
}
}
}
/* sometimes an egg will be special */
#define BREEDER_EGG (!rn2(77))
+6 -3
View File
@@ -450,10 +450,11 @@ restmonchn(NHFILE *nhfp)
restpriest(mtmp, ghostly);
if (mtmp->isgd) {
/* fixup for new bit MON_PARKED added post 5.0.0 */
if (!mtmp->mx
&& (mtmp->mstate & MON_PARKED) == 0L
&& (mtmp->mstate & MON_MIGRATING) == 0L)
if (!mtmp->mx && (mtmp->mstate & MON_PARKED) == 0L
&& (mtmp->mstate & MON_MIGRATING) == 0L) {
mtmp->mstate &= ~TERRAIN_FALLOUT_MASK;
mtmp->mstate |= MON_PARKED;
}
}
if (!ghostly) {
@@ -1210,6 +1211,8 @@ getlev(NHFILE *nhfp, int pid, xint8 lev)
for (y = 0; y < ROWNO; y++)
svl.level.monsters[x][y] = (struct monst *) 0;
for (mtmp = fmon; mtmp; mtmp = mtmp->nmon) {
if ((mtmp->mstate & TERRAIN_FALLOUT_MASK) != 0)
gp.pending_terrain_effects |= (mtmp->mstate & TERRAIN_FALLOUT_MASK);
if (mtmp->isshk)
set_residency(mtmp, FALSE);
/* set some monst fields to sane values when coming from a bones file */
+1
View File
@@ -164,6 +164,7 @@ parkguard(struct monst *grd)
newsym(grd->mx, grd->my);
}
if (m_at(0, 0) != grd) {
grd->mstate &= ~TERRAIN_FALLOUT_MASK;
grd->mstate |= MON_PARKED;
place_monster(grd, 0, 0);
}
+2 -1
View File
@@ -97,7 +97,8 @@ makemap_unmakemon(struct monst *mtmp, boolean migratory)
monsters won't get out of sync; it is not on the map but
mongone() -> m_detach() -> mon_leaving_level() copes with that */
mtmp->mstate |= MON_OFFMAP;
mtmp->mstate &= ~(MON_MIGRATING | MON_LIMBO | MON_ENDGAME_MIGR);
mtmp->mstate &= ~(MON_MIGRATING | MON_LIMBO | MON_ENDGAME_MIGR
| TERRAIN_FALLOUT_MASK);
/* FIXME: will post-5.0.0 MON_PARKED need to be dealt with here? */
mtmp->nmon = fmon;
fmon = mtmp;