lost objects thrown by monsters

Reported by entrez:  if a monster or explosion kills the hero with an
object that has timers or is a light source, it could trigger a panic
when end of game cleanup can't find it because it has been removed
from the map or monster's inventory and not placed back on the map
yet.  This isn't much different from something thrown by hero which
had a similar situation dealt with a long time ago.  Fix by setting
'thrownobj' for monster-launched and explosion-launched missiles.
That way done_object_cleanup() called from really_done() will place the
missile on the map where saving bones or general cleanup can find it.

It doesn't bother dealing with exploding a lit potion of oil that
kills the hero by missile damage before the potion explodes.  If that
ends up in bones, it should still be lit and might blow up before the
new character reaches it.  (Not verified.)

The code for a hero polymorphed into a unicorn and catching a thrown
gem has been moved into its own routine.  No change in behavior, just
less clutter in the thrown-object-hits-hero section of the monster
throwing routine.
This commit is contained in:
PatR
2022-02-24 18:10:52 -08:00
parent ab2bcf4dac
commit 20eccf8ead
3 changed files with 49 additions and 23 deletions

View File

@@ -819,6 +819,10 @@ discovering an object on first turn with persistent inventory enabled might
or spellbook and read it as first action; it becomes discovered but
will still be shown as if undiscovered until next inventory update)
most traps now require touching the floor to trigger
if a lit potion of oil on the floor was launched by an explosion and it hit
and killed the hero via missile damage rather than its own explosion,
it could trigger an "obj_is_local" panic when end of game cleanup
tried to extinguish it as a light source
Fixes to 3.7.0-x Problems that Were Exposed Via git Repository

View File

@@ -746,6 +746,7 @@ scatter(int sx, int sy, /* location of objects to scatter */
while (farthest-- > 0) {
for (stmp = schain; stmp; stmp = stmp->next) {
if ((stmp->range-- > 0) && (!stmp->stopped)) {
g.thrownobj = stmp->obj; /* mainly in case it kills hero */
g.bhitpos.x = stmp->ox + stmp->dx;
g.bhitpos.y = stmp->oy + stmp->dy;
typ = levl[g.bhitpos.x][g.bhitpos.y].typ;
@@ -794,6 +795,7 @@ scatter(int sx, int sy, /* location of objects to scatter */
stmp->oy = g.bhitpos.y;
if (IS_SINK(levl[stmp->ox][stmp->oy].typ))
stmp->stopped = TRUE;
g.thrownobj = (struct obj *) 0;
}
}
}

View File

@@ -7,6 +7,7 @@
static int monmulti(struct monst *, struct obj *, struct obj *);
static void monshoot(struct monst *, struct obj *, struct obj *);
static boolean ucatchgem(struct obj *, struct monst *);
static const char* breathwep_name(int);
static int drop_throw(struct obj *, boolean, int, int);
static int m_lined_up(struct monst *, struct monst *);
@@ -189,8 +190,10 @@ drop_throw(
retvalu = 0;
}
}
} else
} else {
delobj(obj);
}
g.thrownobj = 0;
return retvalu;
}
@@ -491,6 +494,34 @@ ohitmon(
return 0;
}
/* hero catches gem thrown by mon iff poly'd into unicorn; might drop it */
static boolean
ucatchgem(
struct obj *gem, /* caller has verified gem->oclass */
struct monst *mon)
{
/* won't catch rock or gray stone; catch (then drop) worthless glass */
if (gem->otyp <= LAST_GEM + NUM_GLASS_GEMS
&& is_unicorn(g.youmonst.data)) {
char *gem_xname = xname(gem),
*mon_s_name = s_suffix(mon_nam(mon));
if (gem->otyp > LAST_GEM) {
You("catch the %s.", gem_xname);
You("are not interested in %s junk.", mon_s_name);
makeknown(gem->otyp);
dropy(gem);
} else {
You("accept %s gift in the spirit in which it was intended.",
mon_s_name);
(void) hold_another_object(gem, "You catch, but drop, %s.",
gem_xname, "You catch:");
}
return TRUE;
}
return FALSE;
}
#define MT_FLIGHTCHECK(pre,forcehit) \
(/* missile hits edge of screen */ \
!isok(g.bhitpos.x + dx, g.bhitpos.y + dy) \
@@ -550,6 +581,8 @@ m_throw(
singleobj = splitobj(obj, 1L);
obj_extract_self(singleobj);
}
/* global pointer for missile object in OBJ_FREE state */
g.thrownobj = singleobj;
singleobj->owornmask = 0; /* threw one of multiple weapons in hand? */
if (!canseemon(mon))
@@ -585,8 +618,8 @@ m_throw(
if (sym)
tmp_at(DISP_FLASH, obj_to_glyph(singleobj, rn2_on_display_rng));
while (range-- > 0) { /* Actually the loop is always exited by break */
g.bhitpos.x += dx;
g.bhitpos.y += dy;
singleobj->ox = g.bhitpos.x += dx;
singleobj->oy = g.bhitpos.y += dy;
if (cansee(g.bhitpos.x, g.bhitpos.y))
singleobj->dknown = 1;
@@ -602,29 +635,13 @@ m_throw(
if (g.multi)
nomul(0);
if (singleobj->oclass == GEM_CLASS
&& singleobj->otyp <= LAST_GEM + NUM_GLASS_GEMS
&& is_unicorn(g.youmonst.data)) {
if (singleobj->otyp > LAST_GEM) {
You("catch the %s.", xname(singleobj));
You("are not interested in %s junk.",
s_suffix(mon_nam(mon)));
makeknown(singleobj->otyp);
dropy(singleobj);
} else {
You(
"accept %s gift in the spirit in which it was intended.",
s_suffix(mon_nam(mon)));
(void) hold_another_object(singleobj,
"You catch, but drop, %s.",
xname(singleobj),
"You catch:");
}
break;
}
if (singleobj->oclass == POTION_CLASS) {
potionhit(&g.youmonst, singleobj, POTHIT_MONST_THROW);
break;
} else if (singleobj->oclass == GEM_CLASS) {
/* hero might be poly'd into a unicorn */
if (ucatchgem(singleobj, mon))
break;
}
oldumort = u.umortality;
@@ -748,6 +765,9 @@ m_throw(
if (!Blind)
Your1(vision_clears);
}
/* note: all early returns follow drop_throw() which clears thrownobj */
g.thrownobj = 0;
return;
}
#undef MT_FLIGHTCHECK