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:
@@ -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;
|
||||
}
|
||||
|
||||
24
src/mhitm.c
24
src/mhitm.c
@@ -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:
|
||||
|
||||
@@ -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: {
|
||||
|
||||
13
src/mkobj.c
13
src/mkobj.c
@@ -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;
|
||||
}
|
||||
|
||||
45
src/mon.c
45
src/mon.c
@@ -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);
|
||||
|
||||
31
src/uhitm.c
31
src/uhitm.c
@@ -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:
|
||||
|
||||
21
src/zap.c
21
src/zap.c
@@ -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 */
|
||||
|
||||
Reference in New Issue
Block a user