From 9b4a8dfb05a3a14d4e700930f998d51d484307cc Mon Sep 17 00:00:00 2001 From: nhmall Date: Sun, 23 Aug 2026 18:51:13 -0400 Subject: [PATCH] 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. --- doc/fixes5-0-1.txt | 2 ++ include/decl.h | 3 +++ include/extern.h | 3 +++ include/hack.h | 8 ++++++ include/monst.h | 28 +++++++++++++-------- src/allmain.c | 5 ++++ src/decl.c | 2 ++ src/do.c | 1 + src/dog.c | 2 ++ src/mon.c | 63 +++++++++++++++++++++++++++++++++++++++++++++- src/restore.c | 9 ++++--- src/vault.c | 1 + src/wizcmds.c | 3 ++- 13 files changed, 114 insertions(+), 16 deletions(-) diff --git a/doc/fixes5-0-1.txt b/doc/fixes5-0-1.txt index c6cbcfb88..6e1c22cc1 100644 --- a/doc/fixes5-0-1.txt +++ b/doc/fixes5-0-1.txt @@ -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 diff --git a/include/decl.h b/include/decl.h index 86ec5b977..49f9e17c8 100644 --- a/include/decl.h +++ b/include/decl.h @@ -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; diff --git a/include/extern.h b/include/extern.h index ade96f43c..3b859bed1 100644 --- a/include/extern.h +++ b/include/extern.h @@ -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 ### */ diff --git a/include/hack.h b/include/hack.h index 0d140e8b1..a7c28315d 100644 --- a/include/hack.h +++ b/include/hack.h @@ -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, diff --git a/include/monst.h b/include/monst.h index 060995f86..5cd6aa64d 100644 --- a/include/monst.h +++ b/include/monst.h @@ -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 diff --git a/src/allmain.c b/src/allmain.c index 4177cd0cd..0ca4d41d3 100644 --- a/src/allmain.c +++ b/src/allmain.c @@ -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) diff --git a/src/decl.c b/src/decl.c index 6263eb708..64c829ce2 100644 --- a/src/decl.c +++ b/src/decl.c @@ -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 */ diff --git a/src/do.c b/src/do.c index bd1e18286..ab179765f 100644 --- a/src/do.c +++ b/src/do.c @@ -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 */ diff --git a/src/dog.c b/src/dog.c index 1bbee167e..63783be37 100644 --- a/src/dog.c +++ b/src/dog.c @@ -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; diff --git a/src/mon.c b/src/mon.c index 6ad8a0fb5..4bd6b1b07 100644 --- a/src/mon.c +++ b/src/mon.c @@ -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)) diff --git a/src/restore.c b/src/restore.c index 48e851dc6..cd6e86d0c 100644 --- a/src/restore.c +++ b/src/restore.c @@ -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 */ diff --git a/src/vault.c b/src/vault.c index 6de691f73..1f2d97a53 100644 --- a/src/vault.c +++ b/src/vault.c @@ -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); } diff --git a/src/wizcmds.c b/src/wizcmds.c index 2d255c3d3..fd0568120 100644 --- a/src/wizcmds.c +++ b/src/wizcmds.c @@ -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;