From 061c2ab1b1a89a22ff35095a1981f08bcb9a74e8 Mon Sep 17 00:00:00 2001 From: PatR Date: Sun, 4 Feb 2024 15:40:00 -0800 Subject: [PATCH] fix #K4098 - lava burns up item being stolen Three months ago to prevent an "object lost" panic situation when stealing an item that let hero survive water (several candidates) would result in drowning, remove_worn_item() was changed to flag the item being removed as in_use and emergency_disrobe() was changed to avoid dropping in_use items while drowning. That seemed to work ok. But for lava instead of water, in_use is a flag to destroy the item (set in advance, before issuing messages that can give the player a chance to trigger a hangup save). So instead of keeping the item around for theft to finish, it was deallocating it. steal() would format the freed object and then access some of its fields, leading to havoc. This adds a hack to allow one item already flagged as in_use to be treated differently by lava_effects() from the ones it flags for destruction. This also seems to work ok, but we may need to start putting freed items on a deferred deallocation list similar to how dead monsters are kept around for the rest of the current move. The fix/hack has revealed two more bugs that this doesn't address. An item being stolen is removed without any message, then if that removal doesn't kill the hero a theft message is given. The message sequencing is wrong. Flying hero who loses amulet of flying just gets affected by lava; player is only told why after life saving. The other issue is that life-saving from lava can teleport the hero to where the thief can no longer be seen, yielding "It steals " even though "It" was visible when the theft started. --- doc/fixes3-7-0.txt | 4 ++++ src/trap.c | 31 +++++++++++++++++++++++++++---- 2 files changed, 31 insertions(+), 4 deletions(-) diff --git a/doc/fixes3-7-0.txt b/doc/fixes3-7-0.txt index c648fd898..a4bdf6e2d 100644 --- a/doc/fixes3-7-0.txt +++ b/doc/fixes3-7-0.txt @@ -1854,6 +1854,10 @@ debug fuzzer was triggering out of bounds array access in loseexp() if levels and restored those all the way to level 30; introducing an assert(u.ulevel < MAXULEV) changed bounds issue to assertion failure strength less than 25 was unintentionally being capped at 18/07 +if an amulet of flying gets stolen while hero is over lava without other + protection against lava, it will be destroyed even though it isn't + flammable, then attempting to format it for messages will trigger an + impossible warning about "glorkum", then a crash occurs Fixes to 3.7.0-x Platform and/or Interface Problems Exposed Via git Repository diff --git a/src/trap.c b/src/trap.c index 5e46a03b6..321a38798 100644 --- a/src/trap.c +++ b/src/trap.c @@ -6516,6 +6516,7 @@ lava_effects(void) { register struct obj *obj, *obj2; boolean usurvive, boil_away; + unsigned protect_oid = 0; int burncount = 0, burnmesgcount = 0; int dmg = d(6, 6); /* only applicable for water walking */ @@ -6539,14 +6540,34 @@ lava_effects(void) * that player causing hangup at --More-- won't get an * emergency save file created before item destruction. */ - if (!usurvive) - for (obj = gi.invent; obj; obj = obj->nobj) + if (!usurvive) { + for (obj = gi.invent; obj; obj = obj->nobj) { + if (obj->in_use) { /* remove_worn_item() sets in_use */ + /* one item can be protected from burning up [accommodates + steal(AMULET_OF_FLYING) -> remove_worn_item() -> drop + into lava (which happens before item is transferred + from invent to thief->minvent)]; item will still be in + inventory when we return to caller or save bones (or + perform hangup save if that occurs) */ + if (!protect_oid) { + protect_oid = obj->o_id; + obj->in_use = 0; + } else { + impossible( + "lava_effects: '%s' (#%u) is already in use; so is #%u.", + simpleonames(obj), obj->o_id, protect_oid); + } + continue; + } + /* set obj->in_use for items which will be destroyed below */ if ((is_organic(obj) || obj->oclass == POTION_CLASS) && !obj->oerodeproof && objects[obj->otyp].oc_oprop != FIRE_RES && obj->otyp != SCR_FIRE && obj->otyp != SPE_FIREBALL && !obj_resists(obj, 0, 0)) /* for invocation items */ obj->in_use = 1; + } + } /* Check whether we should burn away boots *first* so we know whether to * make the player sink into the lava. Assumption: water walking only @@ -6554,7 +6575,7 @@ lava_effects(void) * (3.7: that assumption is no longer true, but having boots be the first * thing to come into contact with lava makes sense.) */ - if (uarmf && is_organic(uarmf) && !uarmf->oerodeproof) { + if (uarmf && uarmf->in_use) { obj = uarmf; pline("%s into flame!", Yobjnam2(obj, "burst")); ++burnmesgcount; @@ -6589,7 +6610,9 @@ lava_effects(void) for (obj = gi.invent; obj; obj = obj2) { obj2 = obj->nobj; /* above, we set in_use for objects which are to be destroyed */ - if (obj->otyp == SPE_BOOK_OF_THE_DEAD && !Blind) { + if (obj->o_id == protect_oid) { + ; /* skip protected item; caller expects to retain access */ + } else if (obj->otyp == SPE_BOOK_OF_THE_DEAD && !Blind) { if (usurvive) pline("%s glows a strange %s, but remains intact.", The(xname(obj)), hcolor("dark red"));