mhpmax of life-drained monsters

The report about problems after stone-to-flesh on a petrified
long worm included stethoscope feedback of 0(-1) hit points, after
life-draining.  I was unable to reproduce a maximum hp of -1 and hope
that it was a side-effect of the [already fixed] stale mon->wormno
value used when resurrecting the long worm.  Anyway, this changes
life-draining to never take mon->hpmax below mon->m_lev + 1 (the +1
is needed to cope with m_lev==0 monsters).  The same limit is also
applied to monster life-saving but more to avoid replicating the
arbitrary minimum of 10 (four instances) then because it might be
less than m_lev+1 somehow.

Sanity checking now tests whether a monster's max HP is less than
its level + 1 so if there are ways other than life-drain attacks for
it to drop that low, the fuzzer will choke.  The new check also tests
whether a monster's current HP is greater than max HP.

Polymophred hero killing a golem or vortex by vampire bite reported
"<Mon> dies."  Give an alternate message since those aren't alive.
This commit is contained in:
PatR
2020-06-27 18:15:19 -07:00
parent 30b19a3891
commit 7d7b98f0ae
7 changed files with 134 additions and 48 deletions

View File

@@ -1,4 +1,4 @@
/* NetHack 3.6 artifact.c $NHDT-Date: 1581886858 2020/02/16 21:00:58 $ $NHDT-Branch: NetHack-3.7 $:$NHDT-Revision: 1.153 $ */
/* NetHack 3.6 artifact.c $NHDT-Date: 1593306896 2020/06/28 01:14:56 $ $NHDT-Branch: NetHack-3.7 $:$NHDT-Revision: 1.157 $ */
/* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */
/*-Copyright (c) Robert Patrick Rankin, 2013. */
/* NetHack may be freely redistributed. See license for details. */
@@ -1337,11 +1337,21 @@ int dieroll; /* needed for Magicbane and vorpal blades */
}
}
if (spec_ability(otmp, SPFX_DRLI)) {
/* some non-living creatures (golems, vortices) are
vulnerable to life drain effects */
/* some non-living creatures (golems, vortices) are vulnerable to
life drain effects so can get "<Arti> draws the <life>" feedback */
const char *life = nonliving(mdef->data) ? "animating force" : "life";
if (!youdefend) {
int m_lev = (int) mdef->m_lev, /* will be 0 for 1d4 mon */
mhpmax = mdef->mhpmax,
drain = monhp_per_lvl(mdef); /* usually 1d8 */
/* note: DRLI attack uses 2d6, attacker doesn't get healed */
/* stop draining HP if it drops too low (still drains level;
also caller still inflicts regular weapon damage) */
if (mhpmax - drain <= m_lev)
drain = (mhpmax > m_lev) ? (mhpmax - (m_lev + 1)) : 0;
if (vis) {
if (otmp->oartifact == ART_STORMBRINGER)
pline_The("%s blade draws the %s from %s!",
@@ -1355,21 +1365,20 @@ int dieroll; /* needed for Magicbane and vorpal blades */
/* losing a level when at 0 is fatal */
*dmgptr = 2 * mdef->mhp + FATAL_DAMAGE_MODIFIER;
} else {
int drain = monhp_per_lvl(mdef);
*dmgptr += drain;
mdef->mhpmax -= drain;
mdef->m_lev--;
drain /= 2;
if (drain) {
/* attacker heals in proportion to amount drained */
if (youattack) {
healup(drain, 0, FALSE, FALSE);
} else {
magr->mhp += drain;
if (magr->mhp > magr->mhpmax)
magr->mhp = magr->mhpmax;
}
}
if (drain > 0) {
/* drain: was target's damage, now heal attacker by half */
drain = (drain + 1) / 2; /* drain/2 rounded up */
if (youattack) {
healup(drain, 0, FALSE, FALSE);
} else {
magr->mhp += drain;
if (magr->mhp > magr->mhpmax)
magr->mhp = magr->mhpmax;
}
}
return vis;
@@ -1389,7 +1398,7 @@ int dieroll; /* needed for Magicbane and vorpal blades */
life);
losexp("life drainage");
if (magr && magr->mhp < magr->mhpmax) {
magr->mhp += (oldhpmax - u.uhpmax) / 2;
magr->mhp += (oldhpmax - u.uhpmax + 1) / 2;
if (magr->mhp > magr->mhpmax)
magr->mhp = magr->mhpmax;
}

View File

@@ -1,4 +1,4 @@
/* NetHack 3.6 mhitm.c $NHDT-Date: 1583608838 2020/03/07 19:20:38 $ $NHDT-Branch: NetHack-3.7 $:$NHDT-Revision: 1.132 $ */
/* NetHack 3.6 mhitm.c $NHDT-Date: 1593306906 2020/06/28 01:15:06 $ $NHDT-Branch: NetHack-3.7 $:$NHDT-Revision: 1.137 $ */
/* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */
/*-Copyright (c) Robert Patrick Rankin, 2011. */
/* NetHack may be freely redistributed. See license for details. */
@@ -1286,22 +1286,32 @@ int dieroll;
}
if (!tele_restrict(magr)) {
boolean couldspot = canspotmon(magr);
(void) rloc(magr, TRUE);
if (g.vis && couldspot && !canspotmon(magr))
pline("%s suddenly disappears!", buf);
}
break;
case AD_DRLI:
case AD_DRLI: /* drain life */
if (!cancelled && !rn2(3) && !resists_drli(mdef)) {
tmp = d(2, 6);
tmp = d(2, 6); /* Stormbringer uses monhp_per_lvl(usually 1d8) */
if (g.vis && canspotmon(mdef))
pline("%s suddenly seems weaker!", Monnam(mdef));
mdef->mhpmax -= tmp;
if (mdef->m_lev == 0)
pline("%s becomes weaker!", Monnam(mdef));
if (mdef->mhpmax - tmp > (int) mdef->m_lev) {
mdef->mhpmax -= tmp;
} else {
/* limit floor of mhpmax reduction to current m_lev + 1;
avoid increasing it if somehow already less than that */
if (mdef->mhpmax > (int) mdef->m_lev)
mdef->mhpmax = (int) mdef->m_lev + 1;
}
if (mdef->m_lev == 0) /* automatic kill if drained past level 0 */
tmp = mdef->mhp;
else
mdef->m_lev--;
/* Automatic kill if drained past level 0 */
/* unlike hitting with Stormbringer, wounded attacker doesn't
heal any from the drained life */
}
break;
case AD_SSEX:

View File

@@ -1,4 +1,4 @@
/* NetHack 3.6 mhitu.c $NHDT-Date: 1586913203 2020/04/15 01:13:23 $ $NHDT-Branch: NetHack-3.7 $:$NHDT-Revision: 1.187 $ */
/* NetHack 3.6 mhitu.c $NHDT-Date: 1593306907 2020/06/28 01:15:07 $ $NHDT-Branch: NetHack-3.7 $:$NHDT-Revision: 1.192 $ */
/* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */
/*-Copyright (c) Robert Patrick Rankin, 2012. */
/* NetHack may be freely redistributed. See license for details. */
@@ -1232,10 +1232,13 @@ register struct attack *mattk;
}
}
break;
case AD_DRLI:
case AD_DRLI: /* drain life */
hitmsg(mtmp, mattk);
if (uncancelled && !rn2(3) && !Drain_resistance) {
losexp("life drainage");
/* unlike hitting with Stormbringer, wounded attacker doesn't
heal any from the drained life */
}
break;
case AD_LEGS: {

View File

@@ -1,4 +1,4 @@
/* NetHack 3.6 mkobj.c $NHDT-Date: 1591178399 2020/06/03 09:59:59 $ $NHDT-Branch: NetHack-3.7 $:$NHDT-Revision: 1.180 $ */
/* NetHack 3.6 mkobj.c $NHDT-Date: 1593306908 2020/06/28 01:15:08 $ $NHDT-Branch: NetHack-3.7 $:$NHDT-Revision: 1.181 $ */
/* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */
/*-Copyright (c) Derek S. Ray, 2015. */
/* NetHack may be freely redistributed. See license for details. */
@@ -1583,6 +1583,7 @@ struct monst *mtmp;
if (!has_omonst(obj))
newomonst(obj);
if (has_omonst(obj)) {
int baselevel = mtmp->data->mlevel;
struct monst *mtmp2 = OMONST(obj);
*mtmp2 = *mtmp;
@@ -1600,6 +1601,16 @@ struct monst *mtmp;
/* if mtmp is a long worm with segments, its saved traits will
be one without any segments */
mtmp2->wormno = 0;
/* mtmp might have been killed by repeated life draining; make sure
mtmp2 can survive if revived ('baselevel' will be 0 for 1d4 mon) */
if (mtmp2->mhpmax <= baselevel)
mtmp2->mhpmax = baselevel + 1;
/* mtmp is assumed to be dead but we don't kill it or its saved
traits, just force those to have a sane value for current HP */
if (mtmp2->mhp > mtmp2->mhpmax)
mtmp2->mhp = mtmp2->mhpmax;
if (mtmp2->mhp < 1)
mtmp2->mhp = 0;
}
return obj;
}

View File

@@ -1,4 +1,4 @@
/* NetHack 3.6 mon.c $NHDT-Date: 1591017419 2020/06/01 13:16:59 $ $NHDT-Branch: NetHack-3.7 $:$NHDT-Revision: 1.337 $ */
/* NetHack 3.6 mon.c $NHDT-Date: 1593306909 2020/06/28 01:15:09 $ $NHDT-Branch: NetHack-3.7 $:$NHDT-Revision: 1.338 $ */
/* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */
/*-Copyright (c) Derek S. Ray, 2015. */
/* NetHack may be freely redistributed. See license for details. */
@@ -21,6 +21,7 @@ static struct permonst *FDECL(accept_newcham_form, (struct monst *, int));
static struct obj *FDECL(make_corpse, (struct monst *, unsigned));
static int FDECL(minliquid_core, (struct monst *));
static void FDECL(m_detach, (struct monst *, struct permonst *));
static void FDECL(set_mon_min_mhpmax, (struct monst *, int));
static void FDECL(lifesaved_monster, (struct monst *));
static void FDECL(migrate_mon, (struct monst *, XCHAR_P, XCHAR_P));
static boolean FDECL(ok_to_obliterate, (struct monst *));
@@ -55,6 +56,16 @@ const char *msg;
mtmp->mnum, mndx, msg);
mtmp->mnum = mndx;
}
/* check before DEADMONSTER() because dead monsters should still
have sane mhpmax */
if (mtmp->mhpmax < 1
|| mtmp->mhpmax < (int) mtmp->m_lev + 1
|| mtmp->mhp > mtmp->mhpmax)
impossible(
"%s: level %d monster #%u [%s] has %d cur HP, %d max HP",
msg, (int) mtmp->m_lev,
mtmp->m_id, fmt_ptr((genericptr_t) mtmp),
mtmp->mhp, mtmp->mhpmax);
if (DEADMONSTER(mtmp)) {
#if 0
/* bad if not fmons list or if not vault guard */
@@ -2007,6 +2018,26 @@ struct permonst *mptr; /* reflects mtmp->data _prior_ to mtmp's death */
iflags.purge_monsters++;
}
/* give a life-saved monster a reasonable mhpmax value in case it has
been the victim of excessive life draining */
static void
set_mon_min_mhpmax(mon, minimum_mhpmax)
struct monst *mon;
int minimum_mhpmax; /* monster life-saving has traditionally used 10 */
{
/* can't be less than m_lev+1 (if we just used m_lev itself, level 0
monsters would end up allowing a minimum of 0); since life draining
reduces m_lev, this usually won't give the monster much of a boost */
if (mon->mhpmax < (int) mon->m_lev + 1)
mon->mhpmax = (int) mon->m_lev + 1;
/* caller can specify an alternate minimum; we'll honor it iff it is
greater than m_lev+1; the traditional arbitrary value of 10 always
gives level 0 and level 1 monsters a boost and has a moderate
chance of doing so for level 2, a tiny chance for levels 3..9 */
if (mon->mhpmax < minimum_mhpmax)
mon->mhpmax = minimum_mhpmax;
}
/* find the worn amulet of life saving which will save a monster */
struct obj *
mlifesaver(mon)
@@ -2057,8 +2088,7 @@ struct monst *mtmp;
if (mtmp->mtame && !mtmp->isminion) {
wary_dog(mtmp, !surviver);
}
if (mtmp->mhpmax <= 0)
mtmp->mhpmax = 10;
set_mon_min_mhpmax(mtmp, 10); /* mtmp->mhpmax=max(mtmp->m_lev+1,10) */
mtmp->mhp = mtmp->mhpmax;
if (!surviver) {
@@ -2115,8 +2145,7 @@ register struct monst *mtmp;
spec_death ? "reconstitutes" : "transforms");
mtmp->mcanmove = 1;
mtmp->mfrozen = 0;
if (mtmp->mhpmax <= 0)
mtmp->mhpmax = 10;
set_mon_min_mhpmax(mtmp, 10); /* mtmp->mhpmax=max(m_lev+1,10) */
mtmp->mhp = mtmp->mhpmax;
/* mtmp==u.ustuck can happen if previously a fog cloud
or poly'd hero is hugging a vampire bat */
@@ -2731,8 +2760,7 @@ struct monst *mtmp;
surface(x,y));
mtmp->mcanmove = 1;
mtmp->mfrozen = 0;
if (mtmp->mhpmax <= 0)
mtmp->mhpmax = 10;
set_mon_min_mhpmax(mtmp, 10); /* mtmp->mhpmax=max(m_lev+1,10) */
mtmp->mhp = mtmp->mhpmax;
/* this can happen if previously a fog cloud */
if (u.uswallow && (mtmp == u.ustuck))
@@ -2766,8 +2794,7 @@ struct monst *mtmp;
they revert to innate shape rather than become a statue */
mtmp->mcanmove = 1;
mtmp->mfrozen = 0;
if (mtmp->mhpmax <= 0)
mtmp->mhpmax = 10;
set_mon_min_mhpmax(mtmp, 10); /* mtmp->mhpmax=max(mtmp->m_lev+1,10) */
mtmp->mhp = mtmp->mhpmax;
(void) newcham(mtmp, &mons[mtmp->cham], FALSE, TRUE);
newsym(mtmp->mx, mtmp->my);

View File

@@ -1,4 +1,4 @@
/* NetHack 3.6 uhitm.c $NHDT-Date: 1591017421 2020/06/01 13:17:01 $ $NHDT-Branch: NetHack-3.7 $:$NHDT-Revision: 1.236 $ */
/* NetHack 3.6 uhitm.c $NHDT-Date: 1593306911 2020/06/28 01:15:11 $ $NHDT-Branch: NetHack-3.7 $:$NHDT-Revision: 1.237 $ */
/* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */
/*-Copyright (c) Robert Patrick Rankin, 2012. */
/* NetHack may be freely redistributed. See license for details. */
@@ -1845,21 +1845,32 @@ int specialdmg; /* blessed and/or silver bonus against various things */
}
tmp = 0;
break;
case AD_DRLI:
case AD_DRLI: /* drain life */
if (!negated && !rn2(3) && !resists_drli(mdef)) {
int xtmp = d(2, 6);
pline("%s suddenly seems weaker!", Monnam(mdef));
mdef->mhpmax -= xtmp;
mdef->mhp -= xtmp;
tmp = d(2, 6); /* Stormbringer uses monhp_per_lvl(usually 1d8) */
pline("%s becomes weaker!", Monnam(mdef));
if (mdef->mhpmax - tmp > (int) mdef->m_lev) {
mdef->mhpmax -= tmp;
} else {
/* limit floor of mhpmax reduction to current m_lev + 1;
avoid increasing it if somehow already less than that */
if (mdef->mhpmax > (int) mdef->m_lev)
mdef->mhpmax = (int) mdef->m_lev + 1;
}
mdef->mhp -= tmp;
/* !m_lev: level 0 monster is killed regardless of hit points
rather than drop to level -1 */
rather than drop to level -1; note: some non-living creatures
(golems, vortices) are subject to life-drain */
if (DEADMONSTER(mdef) || !mdef->m_lev) {
pline("%s dies!", Monnam(mdef));
pline("%s %s!", Monnam(mdef),
nonliving(mdef->data) ? "expires" : "dies");
xkilled(mdef, XKILL_NOMSG);
} else
mdef->m_lev--;
tmp = 0;
tmp = 0; /* damage has already been inflicted */
/* unlike hitting with Stormbringer, wounded hero doesn't
heal any from the drained life */
}
break;
case AD_RUST:

View File

@@ -1,4 +1,4 @@
/* NetHack 3.6 zap.c $NHDT-Date: 1591219976 2020/06/03 21:32:56 $ $NHDT-Branch: NetHack-3.7 $:$NHDT-Revision: 1.342 $ */
/* NetHack 3.6 zap.c $NHDT-Date: 1593306912 2020/06/28 01:15:12 $ $NHDT-Branch: NetHack-3.7 $:$NHDT-Revision: 1.343 $ */
/* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */
/*-Copyright (c) Robert Patrick Rankin, 2013. */
/* NetHack may be freely redistributed. See license for details. */
@@ -600,8 +600,23 @@ boolean adjacentok; /* False: at obj's spot only, True: nearby is allowed */
return (struct monst *) 0;
}
/* heal the monster */
if (mtmp->mhpmax > mtmp2->mhpmax && is_rider(mtmp2->data))
/* heal the monster; lower than normal level might come from
adj_lev() but we assume it has come from 'mtmp' being level
drained before finally killed; give a chance to restore
some levels so that trolls and Riders can't be drained to
level 0 and then trivially killed repeatedly */
if ((int) mtmp->m_lev < mtmp->data->mlevel) {
int ltmp = rnd(mtmp->data->mlevel + 1);
if (ltmp > (int) mtmp->m_lev) {
while ((int) mtmp->m_lev < ltmp) {
mtmp->m_lev++;
mtmp->mhpmax += monhp_per_lvl(mtmp);
}
mtmp2->m_lev = mtmp->m_lev;
}
}
if (mtmp->mhpmax > mtmp2->mhpmax) /* &&is_rider(mtmp2->data)*/
mtmp2->mhpmax = mtmp->mhpmax;
mtmp2->mhp = mtmp2->mhpmax;
/* Get these ones from mtmp */