From: PatR Date: Fri, 25 Feb 2022 02:10:52 +0000 (-0800) Subject: lost objects thrown by monsters X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=20eccf8ead926603f12f3a200255841872d3234c;p=nethack 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. --- diff --git a/doc/fixes3-7-0.txt b/doc/fixes3-7-0.txt index df96a2524..efb0f3bb4 100644 --- a/doc/fixes3-7-0.txt +++ b/doc/fixes3-7-0.txt @@ -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 diff --git a/src/explode.c b/src/explode.c index 406867267..661eb1c16 100644 --- a/src/explode.c +++ b/src/explode.c @@ -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; } } } diff --git a/src/mthrowu.c b/src/mthrowu.c index a0cb795ff..2fca18fb6 100644 --- a/src/mthrowu.c +++ b/src/mthrowu.c @@ -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