From fa0f3751a3075587a714fcc1d096aae9f7c3ce83 Mon Sep 17 00:00:00 2001 From: PatR Date: Wed, 1 Nov 2023 01:54:32 -0700 Subject: [PATCH] fix #K4026 - "object lost" panic during theft "object lost" panic occurred when hero's worn amulet of magical breathing was stolen. This prevents drown() -> emergency_disrobe() from dropping an item while in the midst of it being stolen, avoiding the possibility of it no longer being in inventory when the theft completes. There may be variations other than drowning that lead to unwear -> drop-or-destroy that are still vulnerable, and this fix can potentially cause items to vanish from hangup save files. It also has a side-effect of not being able to drop levitation boots to lighten encumbrance enough to crawl out of water if the drowning occurs while they are being taken off, not just when being stolen, even though they should be easily droppable in such circumstance. The hero will just need to drop other things instead. --- doc/fixes3-7-0.txt | 3 +++ src/steal.c | 35 ++++++++++++++++++++++++++++++++++- src/trap.c | 6 ++++-- 3 files changed, 41 insertions(+), 3 deletions(-) diff --git a/doc/fixes3-7-0.txt b/doc/fixes3-7-0.txt index c29efb795..465608193 100644 --- a/doc/fixes3-7-0.txt +++ b/doc/fixes3-7-0.txt @@ -1266,6 +1266,9 @@ if a temple was entered while blind, #overview could show a line of just "." rolling boulders ignored walls and trees shopkeepers consider the monster type when charging for tins, eggs and corpses separate level flags premapped and sokoban +hero had worn amulet of magical breathing become unworn during theft by nymph, + dropped it to be able to crawl out of water, so it wasn't there when + game tried to transfer it to thief, triggering an "object lost" panic Fixes to 3.7.0-x General Problems Exposed Via git Repository diff --git a/src/steal.c b/src/steal.c index 2f4476566..f2fa36efb 100644 --- a/src/steal.c +++ b/src/steal.c @@ -220,6 +220,23 @@ remove_worn_item( if (!obj->owornmask) return; + /* + * Losing worn gear might drop hero into water or lava or onto a + * location-changing trap or take away the ability to breathe in water. + * Marking it 'in_use' prevents emergency_disrobe() from dropping it. + * in_lava() appears to be ok; other cases impacting object location + * (or destruction) might still have issues. + * + * Note: if a hangup save occurs when 'in_use' is set, the item will + * be destroyed via useup() during restore. Maybe remove_worn_item() + * and emergency_disrobe() should switch to using obj->bypass instead + * but that would need a lot more cooperation by callers. It's a + * tradeoff between protecting the player against unintentional hangup + * and defending the game against deliberate hangup when player sees a + * message about something undesireable followed by --More--. + */ + obj->in_use = 1; + if (obj->owornmask & W_ARMOR) { if (obj == uskin) { impossible("Removing embedded scales?"); @@ -264,6 +281,21 @@ remove_worn_item( /* catchall */ setnotworn(obj); } + + /* + * Fingers crossed; hope unwearing obj didn't destroy it. Loss of + * levitation, flight, water walking, magical breathing or perhaps + * some other property can subject hero to hardship. drown() won't + * drop an 'in_use' item during emergency_disrobe() to crawl out + * of water. Surviving in_lava() only burns up items which aren't + * able to confer such properties but dying to it will destroy all + * in-use items, keeping them out of subsequent bones. Triggering + * traps might pose a risk of item destruction (fire, explosion) + * but usually that will be like the surviving lava case--the items + * that are affected aren't ones that will be unworn and trigger + * the whole mess. + */ + obj->in_use = 0; } /* Returns 1 when something was stolen (or at least, when N should flee now), @@ -470,8 +502,9 @@ steal(struct monst* mtmp, char* objnambuf) impossible("Tried to steal a strange worn thing. [%d]", otmp->oclass); } - } else if (otmp->owornmask) /* weapon or ball&chain */ + } else if (otmp->owornmask) { /* weapon or ball&chain */ remove_worn_item(otmp, TRUE); + } /* do this before removing it from inventory */ if (objnambuf) diff --git a/src/trap.c b/src/trap.c index 6fd5d0028..a9d7ac0c8 100644 --- a/src/trap.c +++ b/src/trap.c @@ -4634,14 +4634,16 @@ emergency_disrobe(boolean *lostsome) * Undroppables are: body armor, boots, gloves, * amulets, and rings because of the time and effort * in removing them + loadstone and other cursed stuff - * for obvious reasons. + * for obvious reasons. Also, any item in the midst + * of being taken off or stolen. */ if (!((obj->otyp == LOADSTONE && obj->cursed) || obj == uamul || obj == uleft || obj == uright || obj == ublindf || obj == uarm || obj == uarmc || obj == uarmg || obj == uarmf || obj == uarmu || (obj->cursed && (obj == uarmh || obj == uarms)) - || welded(obj))) + || welded(obj) + || obj->o_id == gs.stealoid || obj->in_use)) otmp = obj; /* reached the mark and found some stuff to drop? */ if (--i < 0 && otmp)