]> granicus.if.org Git - nethack/commitdiff
autounlock:untrap
authorPatR <rankin@nethack.org>
Fri, 6 May 2022 21:44:57 +0000 (14:44 -0700)
committerPatR <rankin@nethack.org>
Fri, 6 May 2022 21:44:57 +0000 (14:44 -0700)
Implement 'untrap' as an 'autounlock' action.  Quite a bit more work
than anticipated.  The new documentation is rather clumsy; too many
if-this and if-not-that clauses have intruded.

I'll be astonished if all the return values are correct....

[A couple of places were checking for (rx != 0 && ry != 0) to decide
whether they were performating an autounlock action at <rx,ry> but
that erroneously excludes the top line of the map if the current
level extends that far.  Just check rx for zero/non-zero.]

dat/opthelp
doc/Guidebook.mn
doc/Guidebook.tex
doc/fixes3-7-0.txt
include/extern.h
src/artifact.c
src/lock.c
src/options.c
src/pickup.c
src/trap.c

index a9b604d2fe092665126c38c92b56059e4c581867..6fc5cc94196f047472b39f0d926deda770be58e7 100644 (file)
@@ -148,12 +148,18 @@ Compound options are written as option_name:option_value.
 
 Compound options which can be set during the game are:
 
-autounlock    when attempting to open a door or loot a          [Apply-Key]
-              container that is locked, specifies an action to take:
-              can be None, or one or more of Apply-Key + Kick + Force;
+autounlock    when attempting to open a locked door or loot     [Apply-Key]
+              a locked container, specifies an action to take: can be
+              None, or one or more of Untrap + Apply-Key + Kick + Force;
+              if Untrap is included, it will be handled first; the
+              others apply if you answer "no" to "check for traps?";
+              for "yes", it won't necessary find anything even when a
+              trap happens to be present and will use up the rest of
+              the current move regardless of whether it finds anything;
               Kick is only useful for doors and Force is only useful for
-              containers; either will only be attempted if Apply-Key is
-              omitted or you aren't carrying any unlocking tool
+              containers; either will only be attempted if Untrap is
+              omitted or skipped and Apply-Key is omitted or you aren't
+              carrying an unlocking tool or you decline to use one
 boulder       override the default boulder symbol                       [`]
 disclose      the types of information you want         [ni na nv ng nc no]
               offered at the end of the game
index 338f350a59aa136278995a5bd43a32b47ec9ec0b..be7f9542c90fa8d5156421855d936556c8c12fbf 100644 (file)
@@ -3662,15 +3662,26 @@ Persistent.
 Controls what action to take when attempting to walk into a locked door
 or to loot a locked container.
 Takes a plus-sign separated list of values:
-\fIapply-key\fP which will attempt to use a key or other unlocking tool
-if you have one;
-\fIkick\fP which will kick the door (if you lack a key or omit apply-key;
+.PS Apply-Key
+.PL Untrap
+prompt about whether to attempt to find a trap;
+it might fail to find one even when present; if it does find one, it
+will ask whether you want to try to disarm the trap; if you decline,
+your character will forget that the door or box is trapped;
+.PL Apply-Key
+if carrying a key or other unlocking tool, prompt about using it;
+.PL Kick
+kick the door (if you omit untrap or decline to attempt untrap and
+you omit apply-key or you lack a key or you decline to use the key;
 has no effect on containers);
-\fIforce\fP which will try to force a container's lid with your currently
-wielded weapon (if you lack a key or omit apply-key; has no effect on
-doors); or
-\fInone\fP which can't be combined with the other choices.
-.lp ""
+.PL Force
+try to force a container's lid with your currently
+wielded weapon (if you omit untrap or decline to attempt untrap and
+you omit apply-key or you lack a key or you decline to use the key;
+has no effect on doors);
+.PL None
+none of the above; can't be combined with the other choices.
+.PE
 Omitting the value is treated as if \f(CRautounlock:apply-key\fP.
 Preceding \f(CRautounlock\fP with \(oq!\(cq or \(lqno\(rq is treated as
 \f(CRautounlock:none\fP.
@@ -3680,7 +3691,7 @@ Successfully kicking a door will break it and wake up nearby monsters.
 Successfully forcing a container open will break its lock and might also
 destroy some of its contents or damage your weapon or both.
 .lp ""
-The default is \fIapply-key\fP.
+The default is Apply-Key.
 Persistent.
 .lp blind
 Start the character permanently blind (default false).
index fc35a5a0454a13610265b4d3cf925c01e12947e0..7e2084d8ce8119785e7ddacc6d4ab32253ae454e 100644 (file)
@@ -3970,19 +3970,42 @@ If no weapon is found or the option is
 false, the `t' (throw) command is executed instead.  Persistent.
 %.lp
 \item[\ib{autounlock}]
+%\hyphenation{apply\-key}%this needs to be tested...
 Controls what action to take when attempting to walk into a locked door
 or to loot a locked container.
 Takes a plus-sign separated list of values:
-{\it apply-key\/} which will attempt to use a key or other unlocking tool
-if you have one;
-{\it kick\/} which will kick the door (if you lack a key or omit apply-key;
+% paranoid
+% au => autounlock
+\newlength{\auwidth}
+%.PS Apply-Key
+\settowidth{\auwidth}{\tt Apply-Key}
+\addtolength{\auwidth}{\labelsep}
+\blist{\leftmargin \auwidth \topsep 1mm \itemsep 0mm}
+%.PL Untrap
+\item[{\tt Untrap}]
+prompt about whether to attempt to find a trap;
+it might fail to find one even when present; if it does find one, it
+will ask whether you want to try to disarm the trap; if you decline,
+your character will forget that the door or box is trapped;
+%.PL Apply-Key
+\item[{\tt Apply-Key}]
+if carrying a key or other unlocking tool, prompt about using it;
+%.PL Kick
+\item[{\tt Kick}]
+kick the door (if you omit untrap or decline to attempt untrap and
+you omit apply-key or you lack a key or you decline to use the key;
 has no effect on containers);
-{\it force\/} which will try to force a container's lid with your currently
-wielded weapon (if you lack a key or omit apply-key; has no effect on
-doors); or
-{\it none\/} which can't be combined with the other choices.
-\\
-%.lp ""
+%.PL Force
+\item[{\tt Force}]
+try to force a container's lid with your currently
+wielded weapon (if you omit untrap or decline to attempt untrap and
+you omit apply-key or you lack a key or you decline to use the key;
+has no effect on doors);
+%.PL None
+\item[{\tt None}]
+none of the above; can't be combined with the other choices.
+%.PE
+\elist
 Omitting the value is treated as if {\tt autounlock:apply-key}.
 Preceding {\tt autounlock} with `{\tt !}' or ``{\tt no}'' is treated as
 {\tt autounlock:none}.
@@ -3994,7 +4017,7 @@ Successfully forcing a container open will break its lock and might also
 destroy some of its contents or damage your weapon or both.
 \\
 %.lp ""
-The default is {\it apply-key\/}.
+The default is Apply-Key.
 Persistent.
 %.lp
 \item[\ib{blind}]
index 1bc975ee4e69c67e564cfafdbd6e4ff0ee564746..386cac3230a7a691c84c1dcdb09a7526193cb6ed 100644 (file)
@@ -1197,6 +1197,7 @@ using #wizmakemap on Plane of Water added a new set of air bubbles each time
        it was run and eventually replaced just about all the water; likewise
        with clouds on Plane of Air
 avoid new "where are we?" panic if player quits during character selection
+add Untrap as a potential 'autounlock' action
 
 curses: 'msg_window' option wasn't functional for curses unless the binary
        also included tty support
index 92e99f964f4dfe269451d1bac586c2c261927b5b..bdd94e6412fcc25064cf6b760886f02a50edef99 100644 (file)
@@ -2750,8 +2750,9 @@ extern void water_damage_chain(struct obj *, boolean);
 extern boolean drown(void);
 extern void drain_en(int);
 extern int dountrap(void);
+extern int could_untrap(boolean, boolean);
 extern void cnv_trap_obj(int, int, struct trap *, boolean);
-extern int untrap(boolean);
+extern int untrap(boolean, int, int, struct obj *);
 extern boolean openholdingtrap(struct monst *, boolean *);
 extern boolean closeholdingtrap(struct monst *, boolean *);
 extern boolean openfallingtrap(struct monst *, boolean, boolean *);
index 7d251ce33c578c841dcfe02c7997f61d5298a0b9..2ecbb2bc86b2457fbfc83e101d27ebea3e31dc6f 100644 (file)
@@ -1720,7 +1720,7 @@ arti_invoke(struct obj *obj)
             break;
         }
         case UNTRAP: {
-            if (!untrap(TRUE)) {
+            if (!untrap(TRUE, 0, 0, (struct obj *) 0)) {
                 obj->age = 0; /* don't charge for changing their mind */
                 return ECMD_OK;
             }
index 80fdbedbc4aac7c37f8c5d1a433fb552c8e13565..d2d934cb2cad52b05ebae93027f367d6cda407fb 100644 (file)
@@ -356,14 +356,19 @@ pick_lock(
                          * does not prompt for direction if these are set */
     struct obj *container) /* container, for autounlock */
 {
+    struct obj dummypick;
     int picktyp, c, ch;
     coord cc;
     struct rm *door;
     struct obj *otmp;
     char qbuf[QBUFSZ];
-    boolean autounlock = (((rx != 0 && ry != 0) || container != NULL)
-                          && (flags.autounlock & AUTOUNLOCK_APPLY_KEY) != 0);
+    boolean autounlock = (rx != 0 || container != NULL);
 
+    /* 'pick' might be Null [called by do_loot_cont() for AUTOUNLOCK_UNTRAP] */
+    if (!pick) {
+        dummypick = cg.zeroobj;
+        pick = &dummypick; /* pick->otyp will be STRANGE_OBJECT */
+    }
     picktyp = pick->otyp;
 
     /* check whether we're resuming an interrupted previous attempt */
@@ -401,15 +406,14 @@ pick_lock(
         return PICKLOCK_DID_NOTHING;
     }
 
-    if (picktyp != LOCK_PICK
-        && picktyp != CREDIT_CARD
-        && picktyp != SKELETON_KEY) {
+    if (pick != &dummypick && picktyp != SKELETON_KEY
+        && picktyp != LOCK_PICK && picktyp != CREDIT_CARD) {
         impossible("picking lock with object %d?", picktyp);
         return PICKLOCK_DID_NOTHING;
     }
     ch = 0; /* lint suppression */
 
-    if (rx != 0 && ry != 0) { /* autounlock; caller has provided coordinates */
+    if (rx != 0) { /* autounlock; caller has provided coordinates */
         cc.x = rx;
         cc.y = ry;
     } else if (!get_adjacent_loc((char *) 0, "Invalid location!",
@@ -458,11 +462,25 @@ pick_lock(
                 else
                     verb = "pick";
 
-                if (autounlock) {
-                    Sprintf(qbuf, "Unlock it with %s?", yname(pick));
-                    c = yn(qbuf);
-                    if (c == 'n')
-                        return 0;
+                if (autounlock && (flags.autounlock & AUTOUNLOCK_UNTRAP) != 0
+                    && could_untrap(FALSE, TRUE)
+                    && (c = ynq(safe_qbuf(qbuf, "Check ", " for a trap?",
+                                          otmp, yname, ysimple_name, "this")))
+                       != 'n') {
+                    if (c == 'q')
+                        return PICKLOCK_DID_NOTHING; /* c == 'q' */
+                    /* c == 'y' */
+                    untrap(FALSE, 0, 0, otmp);
+                    return PICKLOCK_DID_SOMETHING; /* even if no trap found */
+                } else if (autounlock
+                          && (flags.autounlock & AUTOUNLOCK_APPLY_KEY) != 0) {
+                    c = 'q';
+                    if (pick != &dummypick) {
+                        Sprintf(qbuf, "Unlock it with %s?", yname(pick));
+                        c = ynq(qbuf);
+                    }
+                    if (c != 'y')
+                        return PICKLOCK_DID_NOTHING;
                 } else {
                     /* "There is <a box> here; <verb> <it|its lock>?" */
                     Sprintf(qsfx, " here; %s %s?",
@@ -473,9 +491,9 @@ pick_lock(
 
                     c = ynq(qbuf);
                     if (c == 'q')
-                        return 0;
+                        return PICKLOCK_DID_NOTHING;
                     if (c == 'n')
-                        continue;
+                        continue; /* try next box */
                 }
 
                 if (otmp->obroken) {
@@ -561,6 +579,15 @@ pick_lock(
             pline("This door is broken.");
             return PICKLOCK_LEARNED_SOMETHING;
         default:
+            if ((flags.autounlock & AUTOUNLOCK_UNTRAP) != 0
+                && could_untrap(FALSE, FALSE)
+                && (c = ynq("Check this door for a trap?")) != 'n') {
+                if (c == 'q')
+                    return PICKLOCK_DID_NOTHING;
+                /* c == 'y' */
+                untrap(FALSE, cc.x, cc.y, (struct obj *) 0);
+                return PICKLOCK_DID_SOMETHING; /* even if no trap found */
+            }
             /* credit cards are only good for unlocking */
             if (picktyp == CREDIT_CARD && !(door->doormask & D_LOCKED)) {
                 You_cant("lock a door with a credit card.");
@@ -571,9 +598,8 @@ pick_lock(
                     (door->doormask & D_LOCKED) ? "Unlock" : "Lock",
                     autounlock ? " with " : "",
                     autounlock ? yname(pick) : "");
-
-            c = yn(qbuf);
-            if (c == 'n')
+            c = ynq(qbuf);
+            if (c != 'y')
                 return 0;
 
             /* note: for !autounlock, 'apply' already did touch check */
index 6a29cd5620b8e721f089a86cc4cc612f44998c54..796935a7214a87181f9f0bc0823c0da1cf87f7d3 100644 (file)
@@ -4534,8 +4534,6 @@ handler_autounlock(int optidx)
     start_menu(tmpwin, MENU_BEHAVE_STANDARD);
     any = cg.zeroany;
     for (i = 0; i < SIZE(unlocktypes); ++i) {
-        if (i == 1)             /*** suppress 'untrap' from the menu... ***/
-            continue;           /*** until it actually gets implemented ***/
         Sprintf(buf, "%-10.10s%c%.40s",
                 unlocktypes[i][0], sep, unlocktypes[i][1]);
         presel = !i ? !flags.autounlock : (flags.autounlock & (1 << (i - 1)));
index b358040e8f15cd0ae0294b0c6706314ab36f8ebc..79f74ebc103e0e3406f3d015bb2f17c1ce4a1f71 100644 (file)
@@ -1863,43 +1863,64 @@ mon_beside(int x, int y)
 }
 
 static int
-do_loot_cont(struct obj **cobjp,
-             int cindex, /* index of this container (1..N)... */
-             int ccount) /* ...number of them (N) */
+do_loot_cont(
+    struct obj **cobjp,
+    int cindex, /* index of this container (1..N)... */
+    int ccount) /* ...number of them (N) */
 {
     struct obj *cobj = *cobjp;
 
     if (!cobj)
         return ECMD_OK;
     if (cobj->olocked) {
+        int res = ECMD_OK;
+
+#if 0
         if (ccount < 2 && (g.level.objects[cobj->ox][cobj->oy] == cobj))
             pline("%s locked.",
                   cobj->lknown ? "It is" : "Hmmm, it turns out to be");
-        else if (cobj->lknown)
+        else
+#endif
+        if (cobj->lknown)
             pline("%s is locked.", The(xname(cobj)));
         else
             pline("Hmmm, %s turns out to be locked.", the(xname(cobj)));
         cobj->lknown = 1;
 
         if (flags.autounlock) {
-            struct obj *unlocktool;
+            struct obj *otmp, *unlocktool = 0;
+            xchar ox = cobj->ox, oy = cobj->oy;
 
-            /* TODO: handle AUTOUNLOCK_UNTRAP and maybe add kicking at
-               self when chest present to handle AUTOUNLOCK_KICK */
             u.dz = 0; /* might be non-zero from previous command since
                        * #loot isn't a move command; pick_lock() cares */
-            if ((flags.autounlock & AUTOUNLOCK_APPLY_KEY) != 0
-                && (unlocktool = autokey(TRUE)) != 0) {
+            /* if both the untrap and apply_key bits are set, untrap
+               attempt will be performed first but we need to set up
+               unlocktool in case "check for trap?" is declined */
+            if (((flags.autounlock & AUTOUNLOCK_APPLY_KEY) != 0
+                 && (unlocktool = autokey(TRUE)) != 0)
+                || (flags.autounlock & AUTOUNLOCK_UNTRAP) != 0) {
                 /* pass ox and oy to avoid direction prompt */
-                return (pick_lock(unlocktool, cobj->ox, cobj->oy, cobj) != 0);
-            } else if ((flags.autounlock & AUTOUNLOCK_FORCE) != 0
-                       && ccount == 1 && u_have_forceable_weapon()) {
+                if (pick_lock(unlocktool, ox, oy, cobj))
+                    res = ECMD_TIME;
+                /* attempting to untrap or unlock might trigger a trap
+                   which destroys 'cobj'; inform caller if that happens */
+                for (otmp = g.level.objects[ox][oy]; otmp;
+                     otmp = otmp->nexthere)
+                    if (otmp == cobj)
+                        break;
+                if (!otmp)
+                    *cobjp = (struct obj *) 0;
+                return res;
+            }
+            if ((flags.autounlock & AUTOUNLOCK_FORCE) != 0
+                && res != ECMD_TIME
+                && ccount == 1 && u_have_forceable_weapon()) {
                 /* single container, and we could #force it open... */
                 cmdq_add_ec(doforce); /* doforce asks for confirmation */
                 g.abort_looting = TRUE;
             }
         }
-        return ECMD_OK;
+        return res;
     }
     cobj->lknown = 1; /* floor container, so no need for update_inventory() */
 
index e66de0966e4b407dbebcaaabe1f8864a8fa2adf8..1996e9cc47f861adb37c3482e0ee5fae91977461 100644 (file)
@@ -61,6 +61,7 @@ static void clear_conjoined_pits(struct trap *);
 static boolean adj_nonconjoined_pit(struct trap *);
 static int try_lift(struct monst *, struct trap *, int, boolean);
 static int help_monster_out(struct monst *, struct trap *);
+static void untrap_box(struct obj *, boolean, boolean);
 #if 0
 static void join_adjacent_pits(struct trap *);
 #endif
@@ -4495,23 +4496,40 @@ drain_en(int n)
 int
 dountrap(void)
 {
-    if (near_capacity() >= HVY_ENCUMBER) {
-        pline("You're too strained to do that.");
-        return ECMD_OK;
-    }
-    if ((nohands(g.youmonst.data) && !webmaker(g.youmonst.data))
-        || !g.youmonst.data->mmove) {
-        pline("And just how do you expect to do that?");
+    if (!could_untrap(TRUE, FALSE))
         return ECMD_OK;
+
+    return untrap(FALSE, 0, 0, (struct obj *) 0) ? ECMD_TIME : ECMD_OK;
+}
+
+/* the #untrap command - disarm a trap */
+int
+could_untrap(boolean verbosely, boolean check_floor)
+{
+    char buf[BUFSZ];
+
+    buf[0] = '\0';
+    if (near_capacity() >= HVY_ENCUMBER) {
+        Strcpy(buf, "You're too strained to do that.");
+    } else if ((nohands(g.youmonst.data) && !webmaker(g.youmonst.data))
+               || !g.youmonst.data->mmove) {
+        Strcpy(buf, "And just how do you expect to do that?");
     } else if (u.ustuck && sticks(g.youmonst.data)) {
-        pline("You'll have to let go of %s first.", mon_nam(u.ustuck));
-        return ECMD_OK;
-    }
-    if (u.ustuck || (welded(uwep) && bimanual(uwep))) {
-        Your("%s seem to be too busy for that.", makeplural(body_part(HAND)));
-        return ECMD_OK;
+        Sprintf(buf, "You'll have to let go of %s first.", mon_nam(u.ustuck));
+    } else if (u.ustuck || (welded(uwep) && bimanual(uwep))) {
+        Sprintf(buf, "Your %s seem to be too busy for that.",
+                makeplural(body_part(HAND)));
+    } else if (check_floor && !can_reach_floor(FALSE)) {
+        /* only checked here for autounlock of chest/box and that will
+           be !verbosely so precise details of the message don't matter */
+        Sprintf(buf, "You can't reach the %s.", surface(u.ux, u.uy));
+    }
+    if (buf[0]) {
+        if (verbosely)
+            pline("%s", buf);
+        return 0;
     }
-    return untrap(FALSE) ? ECMD_TIME : ECMD_OK;
+    return 1;
 }
 
 /* Probability of disabling a trap.  Helge Hafting */
@@ -4964,8 +4982,50 @@ help_monster_out(
     return 1;
 }
 
+/* check a particular container for a trap and optionally disarm it */
+static void
+untrap_box(
+    struct obj *box,
+    boolean force,
+    boolean confused)
+{
+    if ((box->otrapped
+         && (force || (!confused && rn2(MAXULEV + 1 - u.ulevel) < 10)))
+        || (!force && confused && !rn2(3))) {
+        You("find a trap on %s!", the(xname(box)));
+        if (!confused)
+            exercise(A_WIS, TRUE);
+
+        if (ynq("Disarm it?") == 'y') {
+            if (box->otrapped) {
+                int ch = ACURR(A_DEX) + u.ulevel;
+
+                if (Role_if(PM_ROGUE))
+                    ch *= 2;
+                if (!force && (confused || Fumbling
+                               || rnd(75 + level_difficulty() / 2) > ch)) {
+                    (void) chest_trap(box, FINGER, TRUE);
+                    /* 'box' might be gone now */
+                } else {
+                    You("disarm it!");
+                    box->otrapped = 0;
+                }
+                exercise(A_DEX, TRUE);
+            } else {
+                pline("That %s was not trapped.", xname(box));
+            }
+        }
+    } else {
+        You("find no traps on %s.", the(xname(box)));
+    }
+}
+
+/* hero is able to attempt untrap, so do so */
 int
-untrap(boolean force)
+untrap(
+    boolean force,
+    int rx, int ry,
+    struct obj *container)
 {
     register struct obj *otmp;
     register int x, y;
@@ -4975,22 +5035,35 @@ untrap(boolean force)
     const char *trapdescr;
     boolean here, useplural, deal_with_floor_trap,
             confused = (Confusion || Hallucination),
-            trap_skipped = FALSE;
+            trap_skipped = FALSE, autounlock_door = FALSE;
     int boxcnt = 0;
     char the_trap[BUFSZ], qbuf[QBUFSZ];
 
-    if (!getdir((char *) 0))
-        return 0;
-    x = u.ux + u.dx;
-    y = u.uy + u.dy;
+    /* 'force' is true for #invoke; if carrying MKoT, make it be true
+       for #untrap or autounlock */
+    if (!force && has_magic_key(&g.youmonst))
+        force = TRUE;
+
+    if (!rx && !container) {
+        /* usual case */
+        if (!getdir((char *) 0))
+            return 0;
+        x = u.ux + u.dx;
+        y = u.uy + u.dy;
+    } else {
+        /* autounlock's untrap; skip most prompting */
+        if (container) {
+            untrap_box(container, force, confused);
+            return 1;
+        }
+        /* levl[rx][ry] is a locked or trapped door */
+        x = rx, y = ry;
+        autounlock_door = TRUE;
+    }
     if (!isok(x, y)) {
         pline_The("perils lurking there are beyond your grasp.");
         return 0;
     }
-    /* 'force' is true for #invoke; make it be true for #untrap if
-       carrying MKoT */
-    if (!force && has_magic_key(&g.youmonst))
-        force = TRUE;
 
     ttmp = t_at(x, y);
     if (ttmp && !ttmp->tseen)
@@ -5006,7 +5079,9 @@ untrap(boolean force)
             }
 
     deal_with_floor_trap = can_reach_floor(FALSE);
-    if (!deal_with_floor_trap) {
+    if (autounlock_door) {
+        ; /* skip a bunch */
+    } else if (!deal_with_floor_trap) {
         *the_trap = '\0';
         if (ttmp)
             Strcat(the_trap, an(trapdescr));
@@ -5095,6 +5170,11 @@ untrap(boolean force)
         } /* end if */
 
         if (boxcnt) {
+            /* 3.7: this used to allow searching for traps on multiple
+               containers on the same move and needed to keep track of
+               whether any had been found but not attempted to untrap;
+               now at most one per move may be checked and we only
+               continue on to door handling if they are all declined */
             for (otmp = g.level.objects[x][y]; otmp; otmp = otmp->nexthere)
                 if (Is_box(otmp)) {
                     (void) safe_qbuf(qbuf, "There is ",
@@ -5103,59 +5183,34 @@ untrap(boolean force)
                     switch (ynq(qbuf)) {
                     case 'q':
                         return 0;
-                    case 'n':
-                        continue;
-                    }
-
-                    if ((otmp->otrapped
-                         && (force || (!confused
-                                       && rn2(MAXULEV + 1 - u.ulevel) < 10)))
-                        || (!force && confused && !rn2(3))) {
-                        You("find a trap on %s!", the(xname(otmp)));
-                        if (!confused)
-                            exercise(A_WIS, TRUE);
-
-                        switch (ynq("Disarm it?")) {
-                        case 'q':
-                            return 1;
-                        case 'n':
-                            trap_skipped = TRUE;
-                            continue;
-                        }
-
-                        if (otmp->otrapped) {
-                            exercise(A_DEX, TRUE);
-                            ch = ACURR(A_DEX) + u.ulevel;
-                            if (Role_if(PM_ROGUE))
-                                ch *= 2;
-                            if (!force && (confused || Fumbling
-                                           || rnd(75 + level_difficulty() / 2)
-                                                  > ch)) {
-                                (void) chest_trap(otmp, FINGER, TRUE);
-                            } else {
-                                You("disarm it!");
-                                otmp->otrapped = 0;
-                            }
-                        } else
-                            pline("That %s was not trapped.", xname(otmp));
-                        return 1;
-                    } else {
-                        You("find no traps on %s.", the(xname(otmp)));
-                        return 1;
+                    case 'y':
+                        untrap_box(otmp, force, confused);
+                        return 1; /* even for 'no' at "Disarm it?" prompt */
                     }
+                    /* 'n' => continue to next box */
                 }
-
-            You(trap_skipped ? "find no other traps here."
-                             : "know of no traps here.");
-            return 0;
+            There("are no other chests or boxes here.");
         }
 
         if (stumble_on_door_mimic(x, y))
             return 1;
-
     } /* deal_with_floor_trap */
-    /* doors can be manipulated even while levitating/unskilled riding */
 
+    /*
+     * Doors can be manipulated even while levitating/unskilled riding.
+     *
+     * Ordinarily there won't be a closed or locked door at the same
+     * location as a floor trap or a container.  However, there could
+     * be a container at a closed/locked door spot if it was dropped
+     * there by a monster or poly'd hero with Passes_walls capability,
+     * and poly'd hero could move onto that spot and attempt #untrap
+     * in direction '.' or '>'.  We'll get here for that situation if
+     * player declines to check all containers for traps.
+     *
+     * The usual situation is #untrap toward an adjacent closed door.
+     * No floor trap would be present and any containers would be
+     * ignored because they're only checked when direction is '.'/'>'.
+     */
     if (!IS_DOOR(levl[x][y].typ)) {
         if (!trap_skipped)
             You("know of no traps there.");