From dad1c3f8b7fa7483be3daf1d3e530616314b39c6 Mon Sep 17 00:00:00 2001 From: copperwater Date: Sat, 15 Apr 2023 20:25:26 -0400 Subject: [PATCH] Fix: breathless monsters always generate in water in special levels ... unless explicitly specified to generate at a specific point or within a specific area. But if they are permitted to generate anywhere on the level, and it contains water, they always end up in the water. I noticed this when trying to explicitly specify ghouls to generate anywhere on a level with a minimal amount of water. This was due to the definition of "amphibious" being conflated with "breathless", such that all breathless monsters counted as amphibious. There are plenty of breathless monsters in the game that decidedly don't normally inhabit water, such as undead, but they would pass the amphibious() check in pm_to_humidity and thus the game decides that they must generate in wet terrain if there is any available. This fix takes the approach of changing amphibious() so that it no longer checks the M1_BREATHLESS flag and only considers M1_AMPHIBIOUS, then updating the places where amphibious() and Amphibious are used accordingly. I also added a new macro cant_drown() which wraps up swimming, amphibiousness, and breathlessness because these three things are frequently checked together in the context of whether something should drown. Places where amphibious() or Amphibious did NOT have an extra breathless() or Breathless check added on, and thus where behavior has been changed: - The pm_to_humidity function (to fix the bug). - Player vs water in goodpos; it didn't seem like being polymorphed into a breathless non-amphibious monster should make it fair game to randomly teleport into water even though it's technically safe. - Awarding extra experience when killing an eel. (So the hero will get the extra experience if they are polymorphed into a breathless non-amphibious monster and don't have magical breathing. Very much an edge case.) --- include/mondata.h | 4 ++-- src/dbridge.c | 4 ++-- src/do_wear.c | 3 +-- src/hack.c | 3 ++- src/mhitu.c | 3 +-- src/mon.c | 3 +-- src/steed.c | 2 +- src/trap.c | 9 +++++---- src/uhitm.c | 10 +++++----- src/zap.c | 3 +-- 10 files changed, 21 insertions(+), 23 deletions(-) diff --git a/include/mondata.h b/include/mondata.h index c3941ec65..8db4b4249 100644 --- a/include/mondata.h +++ b/include/mondata.h @@ -42,8 +42,8 @@ && (!is_clinger(ptr) || !has_ceiling(&u.uz))) #define is_swimmer(ptr) (((ptr)->mflags1 & M1_SWIM) != 0L) #define breathless(ptr) (((ptr)->mflags1 & M1_BREATHLESS) != 0L) -#define amphibious(ptr) \ - (((ptr)->mflags1 & (M1_AMPHIBIOUS | M1_BREATHLESS)) != 0L) +#define amphibious(ptr) (((ptr)->mflags1 & M1_AMPHIBIOUS) != 0L) +#define cant_drown(ptr) (is_swimmer(ptr) || amphibious(ptr) || breathless(ptr)) #define passes_walls(ptr) (((ptr)->mflags1 & M1_WALLWALK) != 0L) #define amorphous(ptr) (((ptr)->mflags1 & M1_AMORPHOUS) != 0L) #define noncorporeal(ptr) ((ptr)->mlet == S_GHOST) diff --git a/src/dbridge.c b/src/dbridge.c index 91817a6bb..9855ca021 100644 --- a/src/dbridge.c +++ b/src/dbridge.c @@ -378,8 +378,8 @@ e_survives_at(struct entity *etmp, coordxy x, coordxy y) if (noncorporeal(etmp->edata)) return TRUE; if (is_pool(x, y)) - return (boolean) ((is_u(etmp) && (Wwalking || Amphibious || Swimming - || Flying || Levitation)) + return (boolean) ((is_u(etmp) && (Wwalking || Amphibious || Breathless + || Swimming || Flying || Levitation)) || is_swimmer(etmp->edata) || is_flyer(etmp->edata) || is_floater(etmp->edata)); diff --git a/src/do_wear.c b/src/do_wear.c index 767d80988..e22d5c8f7 100644 --- a/src/do_wear.c +++ b/src/do_wear.c @@ -1002,8 +1002,7 @@ Amulet_off(void) /* HMagical_breathing must be set off before calling drown() */ setworn((struct obj *) 0, W_AMUL); - if (!breathless(gy.youmonst.data) && !amphibious(gy.youmonst.data) - && !Swimming) { + if (!cant_drown(gy.youmonst.data) && !Swimming) { You("suddenly inhale an unhealthy amount of %s!", hliquid("water")); (void) drown(); diff --git a/src/hack.c b/src/hack.c index 7c4f290ff..b777fbd3a 100644 --- a/src/hack.c +++ b/src/hack.c @@ -2825,7 +2825,8 @@ pooleffects( if (lava_effects()) return TRUE; } else if ((!Wwalking || is_waterwall(u.ux,u.uy)) - && (newspot || !u.uinwater || !(Swimming || Amphibious))) { + && (newspot || !u.uinwater + || !(Swimming || Amphibious || Breathless))) { if (drown()) return TRUE; } diff --git a/src/mhitu.c b/src/mhitu.c index 65f947c48..94371dbfd 100644 --- a/src/mhitu.c +++ b/src/mhitu.c @@ -1328,8 +1328,7 @@ gulpmu(struct monst *mtmp, struct attack *mattk) : amphibious(gy.youmonst.data) ? "feel comforted." : "can barely breathe!"); - /* NB: Amphibious includes Breathless */ - if (Amphibious && !flaming(gy.youmonst.data)) + if ((Amphibious || Breathless) && !flaming(gy.youmonst.data)) tmp = 0; } else { You("are %s!", enfolds(mtmp->data) ? "being squashed" diff --git a/src/mon.c b/src/mon.c index dccda6267..232abc976 100644 --- a/src/mon.c +++ b/src/mon.c @@ -827,8 +827,7 @@ minliquid_core(struct monst* mtmp) * water damage to dead monsters' inventory, but survivors need to * be handled here. Swimmers are able to protect their stuff... */ - if ((waterwall || !is_clinger(mtmp->data)) - && !is_swimmer(mtmp->data) && !amphibious(mtmp->data)) { + if ((waterwall || !is_clinger(mtmp->data)) && !cant_drown(mtmp->data)) { /* like hero with teleport intrinsic or spell, teleport away if possible */ if (can_teleport(mtmp->data) && !tele_restrict(mtmp)) { diff --git a/src/steed.c b/src/steed.c index 5f6c66bf0..4235a5e8e 100644 --- a/src/steed.c +++ b/src/steed.c @@ -689,7 +689,7 @@ dismount_steed( if (!Underwater) pline("%s falls into the %s!", Monnam(mtmp), surface(u.ux, u.uy)); - if (!is_swimmer(mdat) && !amphibious(mdat)) { + if (!cant_drown(mdat)) { killed(mtmp); adjalign(-1); } diff --git a/src/trap.c b/src/trap.c index b4dbdb076..5aa53738a 100644 --- a/src/trap.c +++ b/src/trap.c @@ -4485,7 +4485,7 @@ drown(void) feel_newsym(u.ux, u.uy); /* in case Blind, map the water here */ /* happily wading in the same contiguous pool */ if (u.uinwater && is_pool(u.ux - u.dx, u.uy - u.dy) - && (Swimming || Amphibious)) { + && (Swimming || Amphibious || Breathless)) { /* water effects on objects every now and then */ if (!rn2(5)) inpool_ok = TRUE; @@ -4495,7 +4495,8 @@ drown(void) if (!u.uinwater) { You("%s into the %s%c", is_solid ? "plunge" : "fall", - waterbody_name(u.ux, u.uy), (Amphibious || Swimming) ? '.' : '!'); + waterbody_name(u.ux, u.uy), + (Amphibious || Swimming || Breathless) ? '.' : '!'); if (!Swimming && !is_solid) You("sink like %s.", Hallucination ? "the Titanic" : "a rock"); } @@ -4520,8 +4521,8 @@ drown(void) unleash_all(); } - if (Amphibious || Swimming) { - if (Amphibious) { + if (Amphibious || Breathless || Swimming) { + if (Amphibious || Breathless) { if (Verbose(3, drown)) pline("But you aren't drowning."); if (!Is_waterlevel(&u.uz)) { diff --git a/src/uhitm.c b/src/uhitm.c index b22c9acd2..e5b8c49e5 100644 --- a/src/uhitm.c +++ b/src/uhitm.c @@ -3094,8 +3094,7 @@ mhitm_ad_wrap( } } else if (u.ustuck == mdef) { /* Monsters don't wear amulets of magical breathing */ - if (is_pool(u.ux, u.uy) && !is_swimmer(pd) - && !amphibious(pd)) { + if (is_pool(u.ux, u.uy) && !cant_drown(pd)) { You("drown %s...", mon_nam(mdef)); mhm->damage = mdef->mhp; } else if (mattk->aatyp == AT_HUGS) @@ -3124,7 +3123,8 @@ mhitm_ad_wrap( Some_Monnam(magr), coil ? "coils" : "swings"); } } else if (u.ustuck == magr) { - if (is_pool(magr->mx, magr->my) && !Swimming && !Amphibious) { + if (is_pool(magr->mx, magr->my) && !Swimming && !Amphibious + && !Breathless) { boolean moat = (levl[magr->mx][magr->my].typ != POOL) && !is_waterwall(magr->mx, magr->my) && !Is_medusa_level(&u.uz) @@ -3311,7 +3311,7 @@ mhitm_ad_slim( if (gv.vis && canseemon(mdef)) ncflags |= NC_SHOW_MSG; - if (newcham(mdef, &mons[PM_GREEN_SLIME], ncflags)) + if (newcham(mdef, &mons[PM_GREEN_SLIME], ncflags)) pd = mdef->data; mdef->mstrategy &= ~STRAT_WAITFORU; mhm->hitflags = M_ATTK_HIT; @@ -4769,7 +4769,7 @@ gulpum(struct monst *mdef, struct attack *mattk) case AD_PHYS: if (gy.youmonst.data == &mons[PM_FOG_CLOUD]) { pline("%s is laden with your moisture.", Monnam(mdef)); - if (amphibious(pd) && !flaming(pd)) { + if ((breathless(pd) || amphibious(pd)) && !flaming(pd)) { dam = 0; pline("%s seems unharmed.", Monnam(mdef)); } diff --git a/src/zap.c b/src/zap.c index 16b6b4bf6..1c6b9abfd 100644 --- a/src/zap.c +++ b/src/zap.c @@ -50,8 +50,7 @@ static void wishcmdassist(int); #define is_hero_spell(type) ((type) >= 10 && (type) < 20) -#define M_IN_WATER(ptr) \ - ((ptr)->mlet == S_EEL || amphibious(ptr) || is_swimmer(ptr)) +#define M_IN_WATER(ptr) ((ptr)->mlet == S_EEL || cant_drown(ptr)) static const char are_blinded_by_the_flash[] = "are blinded by the flash!";