OSDN Git Service

update year to 2020
[jnethack/source.git] / src / zap.c
index 514f14d..ed2b0b0 100644 (file)
--- a/src/zap.c
+++ b/src/zap.c
@@ -1,10 +1,11 @@
-/* NetHack 3.6 zap.c   $NHDT-Date: 1447987787 2015/11/20 02:49:47 $  $NHDT-Branch: master $:$NHDT-Revision: 1.236 $ */
+/* NetHack 3.6 zap.c   $NHDT-Date: 1573688696 2019/11/13 23:44:56 $  $NHDT-Branch: NetHack-3.6 $:$NHDT-Revision: 1.316 $ */
 /* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */
+/*-Copyright (c) Robert Patrick Rankin, 2013. */
 /* NetHack may be freely redistributed.  See license for details. */
 
 /* JNetHack Copyright */
 /* (c) Issei Numata, Naoki Hamada, Shigehiro Miyashita, 1994-2000  */
-/* For 3.4-, Copyright (c) SHIRAKATA Kentaro, 2002-2016            */
+/* For 3.4-, Copyright (c) SHIRAKATA Kentaro, 2002-2020            */
 /* JNetHack may be freely redistributed.  See license for details. */
 
 #include "hack.h"
@@ -32,11 +33,12 @@ STATIC_DCL void FDECL(zhitu, (int, int, const char *, XCHAR_P, XCHAR_P));
 STATIC_DCL void FDECL(revive_egg, (struct obj *));
 STATIC_DCL boolean FDECL(zap_steed, (struct obj *));
 STATIC_DCL void FDECL(skiprange, (int, int *, int *));
-
 STATIC_DCL int FDECL(zap_hit, (int, int));
 STATIC_OVL void FDECL(disintegrate_mon, (struct monst *, int, const char *));
 STATIC_DCL void FDECL(backfire, (struct obj *));
 STATIC_DCL int FDECL(spell_hit_bonus, (int));
+STATIC_DCL void FDECL(destroy_one_item, (struct obj *, int, int));
+STATIC_DCL void FDECL(wishcmdassist, (int));
 
 #define ZT_MAGIC_MISSILE (AD_MAGM - 1)
 #define ZT_FIRE (AD_FIRE - 1)
@@ -63,12 +65,12 @@ STATIC_VAR const char are_blinded_by_the_flash[] =
 */
     "\82Ü\82Î\82ä\82¢\8cõ\82Å\96Ú\82ª\8c©\82¦\82È\82­\82È\82Á\82½\81I";
 
-const char *const flash_types[] =
-    {                  /* also used in buzzmu(mcastu.c) */
-#if 0 /*JP*/
-      "magic missile", /* Wands must be 0-9 */
-      "bolt of fire", "bolt of cold", "sleep ray", "death ray",
-      "bolt of lightning", "", "", "", "",
+const char *const flash_types[] =       /* also used in buzzmu(mcastu.c) */
+    {
+#if 0 /*JP:T*/
+        "magic missile", /* Wands must be 0-9 */
+        "bolt of fire", "bolt of cold", "sleep ray", "death ray",
+        "bolt of lightning", "", "", "", "",
 #else
         "\96\82\96@\82Ì\96î", /* Wands must be 0-9 */
         "\89Î\82Ì\91M\8cõ",
@@ -82,11 +84,11 @@ const char *const flash_types[] =
         "",
 #endif
 
-#if 0 /*JP*/
-      "magic missile", /* Spell equivalents must be 10-19 */
-      "fireball", "cone of cold", "sleep ray", "finger of death",
-      "bolt of lightning", /* There is no spell, used for retribution */
-      "", "", "", "",
+#if 0 /*JP:T*/
+        "magic missile", /* Spell equivalents must be 10-19 */
+        "fireball", "cone of cold", "sleep ray", "finger of death",
+        "bolt of lightning", /* there is no spell, used for retribution */
+        "", "", "", "",
 #else
         "\96\82\96@\82Ì\96î", /* Spell equivalents must be 10-19 */
         "\89Î\82Ì\8bÊ",
@@ -100,11 +102,11 @@ const char *const flash_types[] =
         "",
 #endif
 
-#if 0 /*JP*/
-      "blast of missiles", /* Dragon breath equivalents 20-29*/
-      "blast of fire", "blast of frost", "blast of sleep gas",
-      "blast of disintegration", "blast of lightning", "blast of poison gas",
-      "blast of acid", "", ""
+#if 0 /*JP:T*/
+        "blast of missiles", /* Dragon breath equivalents 20-29*/
+        "blast of fire", "blast of frost", "blast of sleep gas",
+        "blast of disintegration", "blast of lightning",
+        "blast of poison gas", "blast of acid", "", ""
 #else
         "\96\82\96@\82Ì\96î\82Ì\91§", /* Dragon breath equivalents 20-29*/
         "\89Î\82Ì\91§",
@@ -171,6 +173,7 @@ struct obj *obj;
             if (obj->dknown)
                 makeknown(obj->otyp);
         }
+        update_inventory();
     }
 }
 
@@ -184,27 +187,33 @@ struct obj *otmp;
     boolean wake = TRUE; /* Most 'zaps' should wake monster */
     boolean reveal_invis = FALSE, learn_it = FALSE;
     boolean dbldam = Role_if(PM_KNIGHT) && u.uhave.questart;
+    boolean skilled_spell, helpful_gesture = FALSE;
     int dmg, otyp = otmp->otyp;
 /*JP
     const char *zap_type_text = "spell";
 */
     const char *zap_type_text = "\96\82\96@";
     struct obj *obj;
-    boolean disguised_mimic =
-        (mtmp->data->mlet == S_MIMIC && mtmp->m_ap_type != M_AP_NOTHING);
+    boolean disguised_mimic = (mtmp->data->mlet == S_MIMIC
+                               && M_AP_TYPE(mtmp) != M_AP_NOTHING);
 
     if (u.uswallow && mtmp == u.ustuck)
         reveal_invis = FALSE;
 
+    notonhead = (mtmp->mx != bhitpos.x || mtmp->my != bhitpos.y);
+    skilled_spell = (otmp && otmp->oclass == SPBOOK_CLASS && otmp->blessed);
+
     switch (otyp) {
     case WAN_STRIKING:
 /*JP
         zap_type_text = "wand";
 */
         zap_type_text = "\8fñ";
-    /* fall through */
+    /*FALLTHRU*/
     case SPE_FORCE_BOLT:
         reveal_invis = TRUE;
+        if (disguised_mimic)
+            seemimic(mtmp);
         if (resists_magm(mtmp)) { /* match effect on player */
             shieldeff(mtmp->mx, mtmp->my);
 /*JP
@@ -227,6 +236,8 @@ struct obj *otmp;
     case WAN_SLOW_MONSTER:
     case SPE_SLOW_MONSTER:
         if (!resist(mtmp, otmp->oclass, 0, NOTELL)) {
+            if (disguised_mimic)
+                seemimic(mtmp);
             mon_adjust_speed(mtmp, -1, otmp);
             m_dowear(mtmp, FALSE); /* might want speed boots */
             if (u.uswallow && (mtmp == u.ustuck) && is_whirly(mtmp->data)) {
@@ -244,9 +255,13 @@ struct obj *otmp;
         break;
     case WAN_SPEED_MONSTER:
         if (!resist(mtmp, otmp->oclass, 0, NOTELL)) {
+            if (disguised_mimic)
+                seemimic(mtmp);
             mon_adjust_speed(mtmp, 1, otmp);
             m_dowear(mtmp, FALSE); /* might want speed boots */
         }
+        if (mtmp->mtame)
+            helpful_gesture = TRUE;
         break;
     case WAN_UNDEAD_TURNING:
     case SPE_TURN_UNDEAD:
@@ -263,7 +278,7 @@ struct obj *otmp;
                 dmg = spell_damage_bonus(dmg);
             context.bypasses = TRUE; /* for make_corpse() */
             if (!resist(mtmp, otmp->oclass, dmg, NOTELL)) {
-                if (mtmp->mhp > 0)
+                if (!DEADMONSTER(mtmp))
                     monflee(mtmp, 0, FALSE, TRUE);
             }
         }
@@ -271,16 +286,27 @@ struct obj *otmp;
     case WAN_POLYMORPH:
     case SPE_POLYMORPH:
     case POT_POLYMORPH:
-        if (resists_magm(mtmp)) {
+        if (mtmp->data == &mons[PM_LONG_WORM] && has_mcorpsenm(mtmp)) {
+            /* if a long worm has mcorpsenm set, it was polymophed by
+               the current zap and shouldn't be affected if hit again */
+            ;
+        } else if (resists_magm(mtmp)) {
             /* magic resistance protects from polymorph traps, so make
                it guard against involuntary polymorph attacks too... */
             shieldeff(mtmp->mx, mtmp->my);
         } else if (!resist(mtmp, otmp->oclass, 0, NOTELL)) {
+            boolean polyspot = (otyp != POT_POLYMORPH),
+                    give_msg = (!Hallucination
+                                && (canseemon(mtmp)
+                                    || (u.uswallow && mtmp == u.ustuck)));
+
             /* dropped inventory (due to death by system shock,
                or loss of wielded weapon and/or worn armor due to
                limitations of new shape) won't be hit by this zap */
-            for (obj = mtmp->minvent; obj; obj = obj->nobj)
-                bypass_obj(obj);
+            if (polyspot)
+                for (obj = mtmp->minvent; obj; obj = obj->nobj)
+                    bypass_obj(obj);
+
             /* natural shapechangers aren't affected by system shock
                (unless protection from shapechangers is interfering
                with their metabolism...) */
@@ -294,26 +320,55 @@ struct obj *otmp;
                 }
                 /* context.bypasses = TRUE; ## for make_corpse() */
                 /* no corpse after system shock */
-                xkilled(mtmp, 3);
+                xkilled(mtmp, XKILL_GIVEMSG | XKILL_NOCORPSE);
             } else if (newcham(mtmp, (struct permonst *) 0,
-                               (otyp != POT_POLYMORPH), FALSE)) {
-                if (!Hallucination && canspotmon(mtmp))
+                               polyspot, give_msg) != 0
+                       /* if shapechange failed because there aren't
+                          enough eligible candidates (most likely for
+                          vampshifter), try reverting to original form */
+                       || (mtmp->cham >= LOW_PM
+                           && newcham(mtmp, &mons[mtmp->cham],
+                                      polyspot, give_msg) != 0)) {
+                if (give_msg && (canspotmon(mtmp)
+                                 || (u.uswallow && mtmp == u.ustuck)))
                     learn_it = TRUE;
             }
+
+            /* do this even if polymorphed failed (otherwise using
+               flags.mon_polycontrol prompting to force mtmp to remain
+               'long worm' would prompt again if zap hit another segment) */
+            if (!DEADMONSTER(mtmp) && mtmp->data == &mons[PM_LONG_WORM]) {
+                if (!has_mcorpsenm(mtmp))
+                    newmcorpsenm(mtmp);
+                /* flag to indicate that mtmp became a long worm
+                   on current zap, so further hits (on mtmp's new
+                   tail) don't do further transforms */
+                MCORPSENM(mtmp) = PM_LONG_WORM;
+                /* flag to indicate that cleanup is needed; object
+                   bypass cleanup also clears mon->mextra->mcorpsenm
+                   for all long worms on the level */
+                context.bypasses = TRUE;
+            }
         }
         break;
     case WAN_CANCELLATION:
     case SPE_CANCELLATION:
+        if (disguised_mimic)
+            seemimic(mtmp);
         (void) cancel_monst(mtmp, otmp, TRUE, TRUE, FALSE);
         break;
     case WAN_TELEPORTATION:
     case SPE_TELEPORT_AWAY:
+        if (disguised_mimic)
+            seemimic(mtmp);
         reveal_invis = !u_teleport_mon(mtmp, TRUE);
         break;
     case WAN_MAKE_INVISIBLE: {
         int oldinvis = mtmp->minvis;
         char nambuf[BUFSZ];
 
+        if (disguised_mimic)
+            seemimic(mtmp);
         /* format monster's name before altering its visibility */
         Strcpy(nambuf, Monnam(mtmp));
         mon_set_minvis(mtmp);
@@ -322,6 +377,7 @@ struct obj *otmp;
             pline("%s turns transparent!", nambuf);
 */
             pline("%s\82Í\93§\96¾\82É\82È\82Á\82½\81I", nambuf);
+            reveal_invis = TRUE;
             learn_it = TRUE;
         }
         break;
@@ -364,17 +420,17 @@ struct obj *otmp;
         } else if ((obj = which_armor(mtmp, W_SADDLE)) != 0) {
             char buf[BUFSZ];
 
-#if 0 /*JP*/
+#if 0 /*JP:T*/
             Sprintf(buf, "%s %s", s_suffix(Monnam(mtmp)),
                     distant_name(obj, xname));
 #else
-            Sprintf(buf, "%s\82Ì%s", s_suffix(Monnam(mtmp)),
+            Sprintf(buf, "%s\82Ì%s", Monnam(mtmp),
                     distant_name(obj, xname));
 #endif
             if (cansee(mtmp->mx, mtmp->my)) {
                 if (!canspotmon(mtmp))
                     Strcpy(buf, An(distant_name(obj, xname)));
-#if 0 /*JP*/
+#if 0 /*JP:T*/
                 pline("%s falls to the %s.", buf,
                       surface(mtmp->mx, mtmp->my));
 #else
@@ -399,10 +455,12 @@ struct obj *otmp;
             mtmp->mhp += d(6, otyp == SPE_EXTRA_HEALING ? 8 : 4);
             if (mtmp->mhp > mtmp->mhpmax)
                 mtmp->mhp = mtmp->mhpmax;
-            if (mtmp->mblinded) {
-                mtmp->mblinded = 0;
-                mtmp->mcansee = 1;
-            }
+            /* plain healing must be blessed to cure blindness; extra
+               healing only needs to not be cursed, so spell always cures
+               [potions quaffed by monsters behave slightly differently;
+               we use the rules for the hero here...] */
+            if (skilled_spell || otyp == SPE_EXTRA_HEALING)
+                mcureblindness(mtmp, canseemon(mtmp));
             if (canseemon(mtmp)) {
                 if (disguised_mimic) {
                     if (is_obj_mappear(mtmp,STRANGE_OBJECT)) {
@@ -412,7 +470,7 @@ struct obj *otmp;
                     } else
                         mimic_hit_msg(mtmp, otyp);
                 } else
-#if 0 /*JP*/
+#if 0 /*JP:T*/
                     pline("%s looks%s better.", Monnam(mtmp),
                           otyp == SPE_EXTRA_HEALING ? " much" : "");
 #else
@@ -466,19 +524,23 @@ struct obj *otmp;
             wake = FALSE;
         break;
     case SPE_DRAIN_LIFE:
+        if (disguised_mimic)
+            seemimic(mtmp);
         dmg = monhp_per_lvl(mtmp);
         if (dbldam)
             dmg *= 2;
         if (otyp == SPE_DRAIN_LIFE)
             dmg = spell_damage_bonus(dmg);
-        if (resists_drli(mtmp))
+        if (resists_drli(mtmp)) {
             shieldeff(mtmp->mx, mtmp->my);
-        else if (!resist(mtmp, otmp->oclass, dmg, NOTELL) && mtmp->mhp > 0) {
+        } else if (!resist(mtmp, otmp->oclass, dmg, NOTELL)
+                   && !DEADMONSTER(mtmp)) {
             mtmp->mhp -= dmg;
             mtmp->mhpmax -= dmg;
-            if (mtmp->mhp <= 0 || mtmp->mhpmax <= 0 || mtmp->m_lev < 1)
-                xkilled(mtmp, 1);
-            else {
+            /* die if already level 0, regardless of hit points */
+            if (DEADMONSTER(mtmp) || mtmp->mhpmax <= 0 || mtmp->m_lev < 1) {
+                killed(mtmp);
+            } else {
                 mtmp->m_lev--;
                 if (canseemon(mtmp))
 /*JP
@@ -496,12 +558,12 @@ struct obj *otmp;
         break;
     }
     if (wake) {
-        if (mtmp->mhp > 0) {
-            wakeup(mtmp);
+        if (!DEADMONSTER(mtmp)) {
+            wakeup(mtmp, helpful_gesture ? FALSE : TRUE);
             m_respond(mtmp);
             if (mtmp->isshk && !*u.ushops)
                 hot_pursuit(mtmp);
-        } else if (mtmp->m_ap_type)
+        } else if (M_AP_TYPE(mtmp))
             seemimic(mtmp); /* might unblock if mimicing a boulder/door */
     }
     /* note: bhitpos won't be set if swallowed, but that's okay since
@@ -509,7 +571,7 @@ struct obj *otmp;
      * might be an invisible worm hit on the tail.
      */
     if (reveal_invis) {
-        if (mtmp->mhp > 0 && cansee(bhitpos.x, bhitpos.y)
+        if (!DEADMONSTER(mtmp) && cansee(bhitpos.x, bhitpos.y)
             && !canspotmon(mtmp))
             map_invisible(bhitpos.x, bhitpos.y);
     }
@@ -539,9 +601,10 @@ struct monst *mtmp;
                     otmp->cknown = 1;
             }
         }
-        (void) display_minventory(mtmp, MINV_ALL | MINV_NOLET, (char *) 0);
+        (void) display_minventory(mtmp, MINV_ALL | MINV_NOLET | PICK_NONE,
+                                  (char *) 0);
     } else {
-#if 0 /*JP*/
+#if 0 /*JP:T*/
         pline("%s is not carrying anything%s.", noit_Monnam(mtmp),
               (u.uswallow && mtmp == u.ustuck) ? " besides you" : "");
 #else
@@ -622,24 +685,27 @@ int locflags; /* non-zero means get location even if monster is buried */
 
 /* used by revive() and animate_statue() */
 struct monst *
-montraits(obj, cc)
+montraits(obj, cc, adjacentok)
 struct obj *obj;
 coord *cc;
+boolean adjacentok; /* False: at obj's spot only, True: nearby is allowed */
 {
-    struct monst *mtmp = (struct monst *) 0;
-    struct monst *mtmp2 = (struct monst *) 0;
+    struct monst *mtmp, *mtmp2 = has_omonst(obj) ? get_mtraits(obj, TRUE) : 0;
 
-    if (has_omonst(obj))
-        mtmp2 = get_mtraits(obj, TRUE);
     if (mtmp2) {
         /* save_mtraits() validated mtmp2->mnum */
         mtmp2->data = &mons[mtmp2->mnum];
         if (mtmp2->mhpmax <= 0 && !is_rider(mtmp2->data))
             return (struct monst *) 0;
         mtmp = makemon(mtmp2->data, cc->x, cc->y,
-                       NO_MINVENT | MM_NOWAIT | MM_NOCOUNTBIRTH);
-        if (!mtmp)
-            return mtmp;
+                       (NO_MINVENT | MM_NOWAIT | MM_NOCOUNTBIRTH
+                        | (adjacentok ? MM_ADJACENTOK : 0)));
+        if (!mtmp) {
+            /* mtmp2 is a copy of obj's object->oextra->omonst extension
+               and is not on the map or on any monst lists */
+            dealloc_monst(mtmp2);
+            return (struct monst *) 0;
+        }
 
         /* heal the monster */
         if (mtmp->mhpmax > mtmp2->mhpmax && is_rider(mtmp2->data))
@@ -653,9 +719,9 @@ coord *cc;
         if (mtmp->m_id) {
             mtmp2->m_id = mtmp->m_id;
             /* might be bringing quest leader back to life */
-            if (quest_status.leader_is_dead &&
+            if (quest_status.leader_is_dead
                 /* leader_is_dead implies leader_m_id is valid */
-                mtmp2->m_id == quest_status.leader_m_id)
+                && mtmp2->m_id == quest_status.leader_m_id)
                 quest_status.leader_is_dead = FALSE;
         }
         mtmp2->mx = mtmp->mx;
@@ -691,6 +757,16 @@ coord *cc;
         mtmp2->mblinded = 0;
         mtmp2->mstun = 0;
         mtmp2->mconf = 0;
+        /* when traits are for a shopeekper, dummy monster 'mtmp' won't
+           have necessary eshk data for replmon() -> replshk() */
+        if (mtmp2->isshk) {
+            neweshk(mtmp);
+            *ESHK(mtmp) = *ESHK(mtmp2);
+            if (ESHK(mtmp2)->bill_p != 0
+                && ESHK(mtmp2)->bill_p != (struct bill_x *) -1000)
+                ESHK(mtmp)->bill_p = &(ESHK(mtmp)->bill[0]);
+            mtmp->isshk = 1;
+        }
         replmon(mtmp, mtmp2);
         newsym(mtmp2->mx, mtmp2->my); /* Might now be invisible */
 
@@ -739,6 +815,8 @@ int *container_nesting;
 /*
  * Attempt to revive the given corpse, return the revived monster if
  * successful.  Note: this does NOT use up the corpse if it fails.
+ * If corpse->quan is more than 1, only one corpse will be affected
+ * and only one monster will be resurrected.
  */
 struct monst *
 revive(corpse, by_hero)
@@ -750,6 +828,7 @@ boolean by_hero;
     struct obj *container;
     coord xy;
     xchar x, y;
+    boolean one_of;
     int montype, container_nesting = 0;
 
     if (corpse->otyp != CORPSE) {
@@ -784,16 +863,16 @@ boolean by_hero;
             break; /* x,y are 0 */
         }
     }
-    if (!x || !y ||
+    if (!x || !y
         /* Rules for revival from containers:
-           - the container cannot be locked
-           - the container cannot be heavily nested (>2 is arbitrary)
-           - the container cannot be a statue or bag of holding
-           (except in very rare cases for the latter)
-        */
-        (container && (container->olocked || container_nesting > 2
-                       || container->otyp == STATUE
-                       || (container->otyp == BAG_OF_HOLDING && rn2(40)))))
+         *  - the container cannot be locked
+         *  - the container cannot be heavily nested (>2 is arbitrary)
+         *  - the container cannot be a statue or bag of holding
+         *    (except in very rare cases for the latter)
+         */
+        || (container && (container->olocked || container_nesting > 2
+                          || container->otyp == STATUE
+                          || (container->otyp == BAG_OF_HOLDING && rn2(40)))))
         return (struct monst *) 0;
 
     /* record the object's location now that we're sure where it is */
@@ -810,8 +889,10 @@ boolean by_hero;
             x = xy.x, y = xy.y;
     }
 
-    if (mons[montype].mlet == S_EEL && !IS_POOL(levl[x][y].typ)) {
-        if (by_hero && cansee(x,y))
+    if ((mons[montype].mlet == S_EEL && !IS_POOL(levl[x][y].typ))
+        || (mons[montype].mlet == S_TROLL
+            && uwep && uwep->oartifact == ART_TROLLSBANE)) {
+        if (by_hero && cansee(x, y))
 /*JP
             pline("%s twitches feebly.",
 */
@@ -841,7 +922,7 @@ boolean by_hero;
     } else if (has_omonst(corpse)) {
         /* use saved traits */
         xy.x = x, xy.y = y;
-        mtmp = montraits(corpse, &xy);
+        mtmp = montraits(corpse, &xy, FALSE);
         if (mtmp && mtmp->mtame && !mtmp->isminion)
             wary_dog(mtmp, TRUE);
     } else {
@@ -856,35 +937,61 @@ boolean by_hero;
         mtmp->mundetected = 0;
         newsym(mtmp->mx, mtmp->my);
     }
-    if (mtmp->m_ap_type)
+    if (M_AP_TYPE(mtmp))
         seemimic(mtmp);
 
+    one_of = (corpse->quan > 1L);
+    if (one_of)
+        corpse = splitobj(corpse, 1L);
+
     /* if this is caused by the hero there might be a shop charge */
     if (by_hero) {
         struct monst *shkp = 0;
 
         x = corpse->ox, y = corpse->oy;
-        if (costly_spot(x, y))
+        if (costly_spot(x, y)
+            && (carried(corpse) ? corpse->unpaid : !corpse->no_charge))
             shkp = shop_keeper(*in_rooms(x, y, SHOPBASE));
 
-        if (cansee(x, y))
+        if (cansee(x, y)) {
+            char buf[BUFSZ];
+
+#if 1 /*JP*//* \8e\80\91Ì\96¼\82Í\90æ\82É; Strcpy\82É\82È\82é */
+            Strcpy(buf, corpse_xname(corpse, (const char *) 0, CXN_NO_PFX));
+#endif
 #if 0 /*JP*/
-            pline(
-                "%s glows iridescently.",
-                upstart(corpse_xname(corpse, (const char *) 0, CXN_PFX_THE)));
-#else
-            pline(
-                "%s\82Í\93ø\90F\82É\8bP\82¢\82½\81D",
-                upstart(corpse_xname(corpse, (const char *) 0, CXN_PFX_THE)));
+            Strcpy(buf, one_of ? "one of " : "");
+#else /* \8cê\8f\87\82Í\95Ï\82í\82Á\82Ä\82¢\82é */
+            if (one_of) {
+                Strcat(buf, "\82Ì\88ê\82Â");
+            }
+#endif
+#if 0 /*JP*//* \82±\82±\82Å\81u\82 \82È\82½\82Ì\81v\82Í\95s\8e©\91R */
+            /* shk_your: "the " or "your " or "<mon>'s " or "<Shk>'s ".
+               If the result is "Shk's " then it will be ambiguous:
+               is Shk the mon carrying it, or does Shk's shop own it?
+               Let's not worry about that... */
+            (void) shk_your(eos(buf), corpse);
 #endif
-        else if (shkp)
+            if (one_of)
+                corpse->quan++; /* force plural */
+#if 0 /*JP*//* \8aù\82É\90Ý\92è\8dÏ\82Ý */
+            Strcat(buf, corpse_xname(corpse, (const char *) 0, CXN_NO_PFX));
+#endif
+            if (one_of) /* could be simplified to ''corpse->quan = 1L;'' */
+                corpse->quan--;
+/*JP
+            pline("%s glows iridescently.", upstart(buf));
+*/
+            pline("%s\82Í\93ø\90F\82É\8bP\82¢\82½\81D", upstart(buf));
+        } else if (shkp) {
             /* need some prior description of the corpse since
                stolen_value() will refer to the object as "it" */
 /*JP
             pline("A corpse is resuscitated.");
 */
             pline("\8e\80\91Ì\82ª\91h\90\82µ\82½\81D");
-
+        }
         /* don't charge for shopkeeper's own corpse if we just revived him */
         if (shkp && mtmp != shkp)
             (void) stolen_value(corpse, x, y, (boolean) shkp->mpeaceful,
@@ -905,7 +1012,7 @@ boolean by_hero;
         ghost = find_mid(m_id, FM_FMON);
         if (ghost && ghost->data == &mons[PM_GHOST]) {
             if (canseemon(ghost))
-#if 0 /*JP*/
+#if 0 /*JP:T*/
                 pline("%s is suddenly drawn into its former body!",
 #else
                 pline("%s\82Í\93Ë\91R\82à\82Æ\82Ì\91Ì\82É\88ø\82«\8d\9e\82Ü\82ê\82½\81I",
@@ -932,7 +1039,7 @@ boolean by_hero;
     }
 
     /* monster retains its name */
-    if (has_oname(corpse))
+    if (has_oname(corpse) && !unique_corpstat(mtmp->data))
         mtmp = christen_monst(mtmp, ONAME(corpse));
     /* partially eaten corpse yields wounded monster */
     if (corpse->oeaten)
@@ -990,10 +1097,11 @@ struct monst *mon;
     struct monst *mtmp2;
     char owner[BUFSZ], corpse[BUFSZ];
     boolean youseeit;
-    int once = 0, res = 0;
+    int res = 0;
 
     youseeit = (mon == &youmonst) ? TRUE : canseemon(mon);
     otmp2 = (mon == &youmonst) ? invent : mon->minvent;
+    owner[0] = corpse[0] = '\0'; /* lint suppression */
 
     while ((otmp = otmp2) != 0) {
         otmp2 = otmp->nobj;
@@ -1002,25 +1110,21 @@ struct monst *mon;
         if (otmp->otyp != CORPSE)
             continue;
         /* save the name; the object is liable to go away */
-        if (youseeit)
+        if (youseeit) {
             Strcpy(corpse,
                    corpse_xname(otmp, (const char *) 0, CXN_SINGULAR));
+            Shk_Your(owner, otmp); /* includes a trailing space */
+        }
 
-        /* for a merged group, only one is revived; should this be fixed? */
+        /* for a stack, only one is revived */
         if ((mtmp2 = revive(otmp, !context.mon_moving)) != 0) {
             ++res;
-            if (youseeit) {
-                if (!once++)
-/*JP
-                    Strcpy(owner, (mon == &youmonst) ? "Your"
-*/
-                    Strcpy(owner, (mon == &youmonst) ? "\82 \82È\82½"
-                                                     : s_suffix(Monnam(mon)));
+            if (youseeit)
 /*JP
-                pline("%s %s suddenly comes alive!", owner, corpse);
+                pline("%s%s suddenly comes alive!", owner, corpse);
 */
-                pline("%s\82Ì%s\82Í\93Ë\91R\90\96½\82ð\91Ñ\82Ñ\82½\81I", owner, corpse);
-            else if (canseemon(mtmp2))
+                pline("%s%s\82Í\93Ë\91R\90\96½\82ð\91Ñ\82Ñ\82½\81I", owner, corpse);
+            else if (canseemon(mtmp2))
 /*JP
                 pline("%s suddenly appears!", Amonnam(mtmp2));
 */
@@ -1133,8 +1237,9 @@ register struct obj *obj;
  * possibly carried by you or a monster
  */
 boolean
-drain_item(obj)
-register struct obj *obj;
+drain_item(obj, by_you)
+struct obj *obj;
+boolean by_you;
 {
     boolean u_ring;
 
@@ -1149,7 +1254,8 @@ register struct obj *obj;
         return FALSE;
 
     /* Charge for the cost of the object */
-    costly_alteration(obj, COST_DRAIN);
+    if (by_you)
+        costly_alteration(obj, COST_DRAIN);
 
     /* Drain the object and any implied effects */
     obj->spe--;
@@ -1181,6 +1287,10 @@ register struct obj *obj;
         if ((obj->owornmask & W_RING) && u_ring)
             u.udaminc--;
         break;
+    case RIN_PROTECTION:
+        if (u_ring)
+            context.botl = 1; /* bot() will recalc u.uac */
+        break;
     case HELM_OF_BRILLIANCE:
         if ((obj->owornmask & W_ARMH) && (obj == uarmh)) {
             ABON(A_INT)--;
@@ -1194,10 +1304,11 @@ register struct obj *obj;
             context.botl = 1;
         }
         break;
-    case RIN_PROTECTION:
-        context.botl = 1;
+    default:
         break;
     }
+    if (context.botl)
+        bot();
     if (carried(obj))
         update_inventory();
     return TRUE;
@@ -1400,7 +1511,7 @@ int okind;
     default:
         /* if all else fails... */
         pm_index = PM_STRAW_GOLEM;
-#if 0 /*JP*/
+#if 0 /*JP:T*/
         material = "";
 #else
         material = "\95¨\91Ì";
@@ -1415,7 +1526,7 @@ int okind;
     polyuse(obj, okind, (int) mons[pm_index].cwt);
 
     if (mtmp && cansee(mtmp->mx, mtmp->my)) {
-#if 0 /*JP*/
+#if 0 /*JP:T*/
         pline("Some %sobjects meld, and %s arises from the pile!", material,
               a_monnam(mtmp));
 #else
@@ -1489,7 +1600,8 @@ struct obj *obj;
 int id;
 {
     struct obj *otmp;
-    xchar ox, oy;
+    xchar ox = 0, oy = 0;
+    long old_wornmask, new_wornmask = 0L;
     boolean can_merge = (id == STRANGE_OBJECT);
     int obj_location = obj->where;
 
@@ -1555,7 +1667,7 @@ int id;
 
         /* now change it into something laid by the hero */
         while (tryct--) {
-            mnum = can_be_hatched(random_monster());
+            mnum = can_be_hatched(random_monster(rn2));
             if (mnum != NON_PM && !dead_species(mnum, TRUE)) {
                 otmp->spe = 1;            /* laid by hero */
                 set_corpsenm(otmp, mnum); /* also sets hatch timer */
@@ -1571,14 +1683,15 @@ int id;
 
     otmp->cursed = obj->cursed;
     otmp->blessed = obj->blessed;
-    otmp->oeroded = obj->oeroded;
-    otmp->oeroded2 = obj->oeroded2;
-    if (!is_flammable(otmp) && !is_rustprone(otmp))
-        otmp->oeroded = 0;
-    if (!is_corrodeable(otmp) && !is_rottable(otmp))
-        otmp->oeroded2 = 0;
-    if (is_damageable(otmp))
-        otmp->oerodeproof = obj->oerodeproof;
+
+    if (erosion_matters(otmp)) {
+        if (is_flammable(otmp) || is_rustprone(otmp))
+            otmp->oeroded = obj->oeroded;
+        if (is_corrodeable(otmp) || is_rottable(otmp))
+            otmp->oeroded2 = obj->oeroded2;
+        if (is_damageable(otmp))
+            otmp->oerodeproof = obj->oerodeproof;
+    }
 
     /* Keep chest/box traps and poisoned ammo if we may */
     if (obj->otrapped && Is_box(otmp))
@@ -1663,41 +1776,12 @@ int id;
     /* update the weight */
     otmp->owt = weight(otmp);
 
-    /* handle polymorph of worn item: stone-to-flesh cast on self can
-       affect multiple objects at once, but their new forms won't
-       produce any side-effects; a single worn item dipped into potion
-       of polymorph can produce side-effects but those won't yield out
-       of sequence messages because current polymorph is finished */
-    if (obj_location == OBJ_INVENT && obj->owornmask) {
-        long old_wornmask = obj->owornmask & ~(W_ART | W_ARTI),
-             new_wornmask = wearslot(otmp);
-        boolean was_twohanded = bimanual(obj), was_twoweap = u.twoweap;
-
-        remove_worn_item(obj, TRUE);
-        /* if the new form can be worn in the same slot, make it so
-           [possible extension:  if it could be worn in some other
-           slot which is currently unfilled, wear it there instead] */
-        if ((old_wornmask & W_QUIVER) != 0L) {
-            setuqwep(otmp);
-        } else if ((old_wornmask & W_SWAPWEP) != 0L) {
-            if (was_twohanded || !bimanual(otmp))
-                setuswapwep(otmp);
-            if (was_twoweap && uswapwep)
-                u.twoweap = TRUE;
-        } else if ((old_wornmask & W_WEP) != 0L) {
-            if (was_twohanded || !bimanual(otmp) || !uarms)
-                setuwep(otmp);
-            if (was_twoweap && uwep && !bimanual(uwep))
-                u.twoweap = TRUE;
-        } else if ((old_wornmask & new_wornmask) != 0L) {
-            new_wornmask &= old_wornmask;
-            setworn(otmp, new_wornmask);
-            set_wear(otmp); /* Armor_on() for side-effects */
-        }
-    }
-
-    /* ** we are now done adjusting the object ** */
+    /*
+     * ** we are now done adjusting the object (except possibly wearing it) **
+     */
 
+    (void) get_obj_location(obj, &ox, &oy, BURIED_TOO | CONTAINED_TOO);
+    old_wornmask = obj->owornmask & ~(W_ART | W_ARTI);
     /* swap otmp for obj */
     replace_object(obj, otmp);
     if (obj_location == OBJ_INVENT) {
@@ -1710,8 +1794,42 @@ int id;
         freeinv_core(obj);
         addinv_core1(otmp);
         addinv_core2(otmp);
+        /*
+         * Handle polymorph of worn item.  Stone-to-flesh cast on self can
+         * affect multiple objects at once, but their new forms won't
+         * produce any side-effects.  A single worn item dipped into potion
+         * of polymorph can produce side-effects but those won't yield out
+         * of sequence messages because current polymorph is finished.
+         */
+        if (old_wornmask) {
+            boolean was_twohanded = bimanual(obj), was_twoweap = u.twoweap;
+
+            /* wearslot() returns a mask which might have multiple bits set;
+               narrow that down to the bit(s) currently in use */
+            new_wornmask = wearslot(otmp) & old_wornmask;
+            remove_worn_item(obj, TRUE);
+            /* if the new form can be worn in the same slot, make it so */
+            if ((new_wornmask & W_WEP) != 0L) {
+                if (was_twohanded || !bimanual(otmp) || !uarms)
+                    setuwep(otmp);
+                if (was_twoweap && uwep && !bimanual(uwep))
+                    u.twoweap = TRUE;
+            } else if ((new_wornmask & W_SWAPWEP) != 0L) {
+                if (was_twohanded || !bimanual(otmp))
+                    setuswapwep(otmp);
+                if (was_twoweap && uswapwep)
+                    u.twoweap = TRUE;
+            } else if ((new_wornmask & W_QUIVER) != 0L) {
+                setuqwep(otmp);
+            } else if (new_wornmask) {
+                setworn(otmp, new_wornmask);
+                /* set_wear() might result in otmp being destroyed if
+                   worn amulet has been turned into an amulet of change */
+                set_wear(otmp);
+                otmp = wearmask_to_obj(new_wornmask); /* might be Null */
+            }
+        } /* old_wornmask */
     } else if (obj_location == OBJ_FLOOR) {
-        ox = otmp->ox, oy = otmp->oy; /* set by replace_object() */
         if (obj->otyp == BOULDER && otmp->otyp != BOULDER
             && !does_block(ox, oy, &levl[ox][oy]))
             unblock_point(ox, oy);
@@ -1720,11 +1838,9 @@ int id;
             block_point(ox, oy);
     }
 
-    if ((!carried(otmp) || obj->unpaid)
-        && get_obj_location(otmp, &ox, &oy, BURIED_TOO | CONTAINED_TOO)
-        && costly_spot(ox, oy)) {
-        register struct monst *shkp =
-            shop_keeper(*in_rooms(ox, oy, SHOPBASE));
+    /* note: if otmp is gone, billing for it was handled by useup() */
+    if (((otmp && !carried(otmp)) || obj->unpaid) && costly_spot(ox, oy)) {
+        struct monst *shkp = shop_keeper(*in_rooms(ox, oy, SHOPBASE));
 
         if ((!obj->no_charge
              || (Has_contents(obj)
@@ -1732,11 +1848,11 @@ int id;
             && inhishop(shkp)) {
             if (shkp->mpeaceful) {
                 if (*u.ushops
-                    && *in_rooms(u.ux, u.uy, 0)
-                           == *in_rooms(shkp->mx, shkp->my, 0)
-                    && !costly_spot(u.ux, u.uy))
+                    && (*in_rooms(u.ux, u.uy, 0)
+                        == *in_rooms(shkp->mx, shkp->my, 0))
+                    && !costly_spot(u.ux, u.uy)) {
                     make_angry_shk(shkp, ox, oy);
-                else {
+                else {
 /*JP
                     pline("%s gets angry!", Monnam(shkp));
 */
@@ -1761,7 +1877,7 @@ struct obj *obj;
 {
     int res = 1; /* affected object by default */
     struct permonst *ptr;
-    struct monst *mon;
+    struct monst *mon, *shkp;
     struct obj *item;
     xchar oox, ooy;
     boolean smell = FALSE, golem_xform = FALSE;
@@ -1792,19 +1908,18 @@ struct obj *obj;
                 break;
             }
             if (obj->otyp == STATUE) {
-                /* animate_statue() forces all golems to become flesh golems
-                 */
+                /* animate_statue() forces all golems to become flesh golems */
                 mon = animate_statue(obj, oox, ooy, ANIMATE_SPELL, (int *) 0);
             } else { /* (obj->otyp == FIGURINE) */
                 if (golem_xform)
                     ptr = &mons[PM_FLESH_GOLEM];
                 mon = makemon(ptr, oox, ooy, NO_MINVENT);
                 if (mon) {
-                    if (costly_spot(oox, ooy) && !obj->no_charge) {
-                        if (costly_spot(u.ux, u.uy))
-                            addtobill(obj, carried(obj), FALSE, FALSE);
-                        else
-                            stolen_value(obj, oox, ooy, TRUE, FALSE);
+                    if (costly_spot(oox, ooy)
+                        && (carried(obj) ? obj->unpaid : !obj->no_charge)) {
+                        shkp = shop_keeper(*in_rooms(oox, ooy, SHOPBASE));
+                        stolen_value(obj, oox, ooy,
+                                     (shkp && shkp->mpeaceful), FALSE);
                     }
                     if (obj->timed)
                         obj_stop_timers(obj);
@@ -1813,7 +1928,7 @@ struct obj *obj;
                     else
                         delobj(obj);
                     if (cansee(mon->mx, mon->my))
-#if 0 /*JP*/
+#if 0 /*JP:T*/
                         pline_The("figurine %sanimates!",
                                   golem_xform ? "turns to flesh and " : "");
 #else
@@ -1914,10 +2029,9 @@ struct obj *obj, *otmp;
          *             from its inventory as a result of the change.
          *             If the items fall to the floor, they are not
          *             subject to direct subsequent polymorphing
-         *             themselves on that same zap. This makes it
-         *             consistent with items that remain in the
-         *             monster's inventory. They are not polymorphed
-         *             either.
+         *             themselves on that same zap.  This makes it
+         *             consistent with items that remain in the monster's
+         *             inventory.  They are not polymorphed either.
          * UNDEAD_TURNING - When an undead creature gets killed via
          *             undead turning, prevent its corpse from being
          *             immediately revived by the same effect.
@@ -1929,6 +2043,10 @@ struct obj *obj, *otmp;
          * menu_drop(), askchain() - inventory traversal where multiple
          *             Drop can alter the invent chain while traversal
          *             is in progress (bhito isn't involved).
+         * destroy_item(), destroy_mitem() - inventory traversal where
+         *             item destruction can trigger drop or destruction of
+         *             other item(s) and alter the invent or mon->minvent
+         *             chain, possibly recursively.
          *
          * The bypass bit on all objects is reset each turn, whenever
          * context.bypasses is set.
@@ -1977,9 +2095,9 @@ struct obj *obj, *otmp;
                 (void) boxlock(obj, otmp);
 
             if (obj_shudders(obj)) {
-                boolean cover =
-                    ((obj == level.objects[u.ux][u.uy]) && u.uundetected
-                     && hides_under(youmonst.data));
+                boolean cover = ((obj == level.objects[u.ux][u.uy])
+                                 && u.uundetected
+                                 && hides_under(youmonst.data));
 
                 if (cansee(obj->ox, obj->oy))
                     learn_it = TRUE;
@@ -1999,19 +2117,31 @@ struct obj *obj, *otmp;
             if (Is_container(obj) || obj->otyp == STATUE) {
                 obj->cknown = obj->lknown = 1;
                 if (!obj->cobj) {
-                    boolean catbox = SchroedingersBox(obj);
-
+/*JP
+                    pline("%s empty.", Tobjnam(obj, "are"));
+*/
+                    pline("%s\82Í\8bó\82Á\82Û\82¾\81D", xname(obj));
+                } else if (SchroedingersBox(obj)) {
                     /* we don't want to force alive vs dead
                        determination for Schroedinger's Cat here,
                        so just make probing be inconclusive for it */
-                    if (catbox)
-                        obj->cknown = 0;
-/*JP
-                    pline("%s empty.", Tobjnam(obj, catbox ? "seem" : "are"));
-*/
-                    pline("%s\82Í\8bó\82Á\82Û%s\82¾\81D", xname(obj), catbox ? "\82Ì\82æ\82¤" : "");
+#if 0 /*JP:T*/
+                    You("aren't sure whether %s has %s or its corpse inside.",
+                        the(xname(obj)),
+                        /* unfortunately, we can't tell whether rndmonnam()
+                           picks a form which can't leave a corpse */
+                        an(Hallucination ? rndmonnam((char *) 0) : "cat"));
+#else
+                    pline("%s\82É%s\82ª\93ü\82Á\82Ä\82¢\82é\82Ì\82©\82»\82Ì\8e\80\91Ì\82ª\93ü\82Á\82Ä\82¢\82é\82Ì\82©\82í\82©\82ç\82È\82¢\81D",
+                        xname(obj),
+                        /* unfortunately, we can't tell whether rndmonnam()
+                           picks a form which can't leave a corpse */
+                        Hallucination ? rndmonnam((char *) 0) : "\94L");
+#endif
+                    obj->cknown = 0;
                 } else {
                     struct obj *o;
+
                     /* view contents (not recursively) */
                     for (o = obj->cobj; o; o = o->nobj)
                         o->dknown = 1; /* "seen", even if blind */
@@ -2059,10 +2189,14 @@ struct obj *obj, *otmp;
                         You_hear("\89½\82©\82ª\8dÓ\82¯\82é\89¹\82ð\95·\82¢\82½\81D");
                 }
             } else {
+                int oox = obj->ox;
+                int ooy = obj->oy;
                 if (context.mon_moving
                         ? !breaks(obj, obj->ox, obj->oy)
                         : !hero_breaks(obj, obj->ox, obj->oy, FALSE))
                     maybelearnit = FALSE; /* nothing broke */
+                else
+                    newsym_force(oox,ooy);
                 res = 0;
             }
             if (maybelearnit)
@@ -2076,7 +2210,7 @@ struct obj *obj, *otmp;
 #endif
             break;
         case SPE_DRAIN_LIFE:
-            (void) drain_item(obj);
+            (void) drain_item(obj, TRUE);
             break;
         case WAN_TELEPORTATION:
         case SPE_TELEPORT_AWAY:
@@ -2089,29 +2223,66 @@ struct obj *obj, *otmp;
             if (obj->otyp == EGG) {
                 revive_egg(obj);
             } else if (obj->otyp == CORPSE) {
+                struct monst *mtmp;
+                xchar ox, oy;
                 int corpsenm = corpse_revive_type(obj);
+                char *corpsname = cxname_singular(obj);
 
-                res = !!revive(obj, TRUE);
-                if (res && Role_if(PM_HEALER)) {
-                    if (Hallucination && !Deaf) {
-/*JP
-                        You_hear("the sound of a defibrillator.");
-*/
-                        You_hear("\8f\9c\8d×\93®\8aí\82Ì\89¹\82ð\95·\82¢\82½\81D");
-                        learn_it = TRUE;
-                    } else if (!Blind) {
-#if 0 /*JP*/
-                        You("observe %s %s change dramatically.",
-                            s_suffix(an(mons[corpsenm].mname)),
-                            nonliving(&mons[corpsenm]) ? "motility"
-                                                       : "health");
+                /* get corpse's location before revive() uses it up */
+                if (!get_obj_location(obj, &ox, &oy, 0))
+                    ox = obj->ox, oy = obj->oy; /* won't happen */
+
+                mtmp = revive(obj, TRUE);
+                if (!mtmp) {
+                    res = 0; /* no monster implies corpse was left intact */
+                } else {
+                    if (cansee(ox, oy)) {
+                        if (canspotmon(mtmp)) {
+#if 0 /*JP:T*/
+                            pline("%s is resurrected!",
+                                  upstart(noname_monnam(mtmp, ARTICLE_THE)));
 #else
-                        You("%s\82Ì%s\82ª\8c\80\93I\82É\95Ï\89»\82·\82é\82Ì\82ð\8c©\82½\81D",
-                            s_suffix(an(mons[corpsenm].mname)),
-                            nonliving(&mons[corpsenm]) ? "\89^\93®\90«"
-                                                       : "\8c\92\8dN");
+                            pline("%s\82Í\90\82«\95Ô\82Á\82½\81I",
+                                  upstart(noname_monnam(mtmp, ARTICLE_THE)));
 #endif
-                        learn_it = TRUE;
+                            learn_it = TRUE;
+                        } else {
+                            /* saw corpse but don't see monster: maybe
+                               mtmp is invisible, or has been placed at
+                               a different spot than <ox,oy> */
+                            if (!type_is_pname(&mons[corpsenm]))
+                                corpsname = The(corpsname);
+/*JP
+                            pline("%s disappears.", corpsname);
+*/
+                            pline("%s\82Í\8fÁ\82¦\82½\81D", corpsname);
+                        }
+                    } else {
+                        /* couldn't see corpse's location */
+                        if (Role_if(PM_HEALER) && !Deaf
+                            && !nonliving(&mons[corpsenm])) {
+                            if (!type_is_pname(&mons[corpsenm]))
+                                corpsname = an(corpsname);
+                            if (!Hallucination)
+/*JP
+                                You_hear("%s reviving.", corpsname);
+*/
+                                You_hear("%s\82ª\90\82«\95Ô\82é\89¹\82ð\95·\82¢\82½\81D", corpsname);
+                            else
+/*JP
+                                You_hear("a defibrillator.");
+*/
+                                You_hear("\8f\9c\8d×\93®\8aí\82Ì\89¹\82ð\95·\82¢\82½\81D");
+                            learn_it = TRUE;
+                        }
+                        if (canspotmon(mtmp))
+                            /* didn't see corpse but do see monster: it
+                               has been placed somewhere other than <ox,oy>
+                               or blind hero spots it with ESP */
+/*JP
+                            pline("%s appears.", Monnam(mtmp));
+*/
+                            pline("%s\82ª\8c»\82ê\82½\81D", Monnam(mtmp));
                     }
                     if (learn_it)
                         exercise(A_WIS, TRUE);
@@ -2281,6 +2452,7 @@ backfire(otmp)
 struct obj *otmp;
 {
     int dmg;
+
     otmp->in_use = TRUE; /* in case losehp() is fatal */
 /*JP
     pline("%s suddenly explodes!", The(xname(otmp)));
@@ -2330,10 +2502,12 @@ dozap()
         if ((damage = zapyourself(obj, TRUE)) != 0) {
 #if 0 /*JP*/
             char buf[BUFSZ];
+
             Sprintf(buf, "zapped %sself with a wand", uhim());
             losehp(Maybe_Half_Phys(damage), buf, NO_KILLER_PREFIX);
 #else
-            losehp(Maybe_Half_Phys(damage), "\8e©\95ª\8e©\90g\82Ì\8fñ\82Ì\97Í\82ð\97\81\82Ñ\82Ä", KILLED_BY);
+            losehp(Maybe_Half_Phys(damage),
+                "\8e©\95ª\8e©\90g\82Ì\8fñ\82Ì\97Í\82ð\97\81\82Ñ\82Ä", KILLED_BY);
 #endif
         }
     } else {
@@ -2493,7 +2667,7 @@ boolean ordinary;
 
     case WAN_CANCELLATION:
     case SPE_CANCELLATION:
-        (void) cancel_monst(&youmonst, obj, TRUE, FALSE, TRUE);
+        (void) cancel_monst(&youmonst, obj, TRUE, TRUE, TRUE);
         break;
 
     case SPE_DRAIN_LIFE:
@@ -2625,7 +2799,7 @@ boolean ordinary;
         learn_it = TRUE;
         (void) unturn_dead(&youmonst);
         if (is_undead(youmonst.data)) {
-#if 0 /*JP*/
+#if 0 /*JP:T*/
             You_feel("frightened and %sstunned.",
                      Stunned ? "even more " : "");
 #else
@@ -2643,7 +2817,7 @@ boolean ordinary;
     case SPE_EXTRA_HEALING:
         learn_it = TRUE; /* (no effect for spells...) */
         healup(d(6, obj->otyp == SPE_EXTRA_HEALING ? 8 : 4), 0, FALSE,
-               (obj->otyp == SPE_EXTRA_HEALING));
+               (obj->blessed || obj->otyp == SPE_EXTRA_HEALING));
 /*JP
         You_feel("%sbetter.", obj->otyp == SPE_EXTRA_HEALING ? "much " : "");
 */
@@ -2652,6 +2826,7 @@ boolean ordinary;
     case WAN_LIGHT: /* (broken wand) */
         /* assert( !ordinary ); */
         damage = d(obj->spe, 25);
+        /*FALLTHRU*/
     case EXPENSIVE_CAMERA:
         if (!damage)
             damage = 5;
@@ -2667,16 +2842,39 @@ boolean ordinary;
             learn_it = TRUE;
             unpunish();
         }
-        if (u.utrap) { /* escape web or bear trap */
-            (void) openholdingtrap(&youmonst, &learn_it);
-        } else { /* trigger previously escaped trapdoor */
+        /* invent is hit iff hero doesn't escape from a trap */
+        if (!u.utrap || !openholdingtrap(&youmonst, &learn_it)) {
+            struct obj *otmp;
+            boolean boxing = FALSE;
+
+            /* unlock carried boxes */
+            for (otmp = invent; otmp; otmp = otmp->nobj)
+                if (Is_box(otmp)) {
+                    (void) boxlock(otmp, obj);
+                    boxing = TRUE;
+                }
+            if (boxing)
+                update_inventory(); /* in case any box->lknown has changed */
+
+            /* trigger previously escaped trapdoor */
             (void) openfallingtrap(&youmonst, TRUE, &learn_it);
         }
         break;
     case WAN_LOCKING:
     case SPE_WIZARD_LOCK:
-        if (!u.utrap) {
-            (void) closeholdingtrap(&youmonst, &learn_it);
+        /* similar logic to opening; invent is hit iff no trap triggered */
+        if (u.utrap || !closeholdingtrap(&youmonst, &learn_it)) {
+            struct obj *otmp;
+            boolean boxing = FALSE;
+
+            /* lock carried boxes */
+            for (otmp = invent; otmp; otmp = otmp->nobj)
+                if (Is_box(otmp)) {
+                    (void) boxlock(otmp, obj);
+                    boxing = TRUE;
+                }
+            if (boxing)
+                update_inventory(); /* in case any box->lknown has changed */
         }
         break;
     case WAN_DIGGING:
@@ -2695,6 +2893,7 @@ boolean ordinary;
                     otmp->cknown = 1;
             }
         }
+        update_inventory();
         learn_it = TRUE;
         ustatusline();
         break;
@@ -2779,8 +2978,10 @@ int amt;          /* pseudo-damage used to determine blindness duration */
         pline("\82¨\82í\81C\8cõ\82ª\92É\82¢%s", (dmg > 2 || u.mh <= 5) ? "\81I" : "\81D");
         /* [composing killer/reason is superfluous here; if fatal, cause
            of death will always be "killed while stuck in creature form"] */
+#if 0 /*JP*/
         if (obj->oclass == SCROLL_CLASS || obj->oclass == SPBOOK_CLASS)
             ordinary = FALSE; /* say blasted rather than zapped */
+#endif
         how = (obj->oclass != SPBOOK_CLASS)
                   ? (const char *) ansimpleoname(obj)
 /*JP
@@ -2828,6 +3029,8 @@ struct obj *obj; /* wand or spell */
 {
     int steedhit = FALSE;
 
+    bhitpos.x = u.usteed->mx, bhitpos.y = u.usteed->my;
+    notonhead = FALSE;
     switch (obj->otyp) {
     /*
      * Wands that are allowed to hit the steed
@@ -2920,11 +3123,24 @@ boolean youattack, allow_cancel_kill, self_cancel;
 
     /* now handle special cases */
     if (youdefend) {
-        if (Upolyd) {
-            if ((u.umonnum == PM_CLAY_GOLEM) && !Blind)
-                pline(writing_vanishes, your);
-
-            if (Unchanging)
+        if (Upolyd) { /* includes lycanthrope in creature form */
+            /*
+             * Return to normal form unless Unchanging.
+             * Hero in clay golem form dies if Unchanging.
+             * Does not cure lycanthropy or stop timed random polymorph.
+             */
+            if (u.umonnum == PM_CLAY_GOLEM) {
+                if (!Blind)
+                    pline(writing_vanishes, your);
+                else /* note: "dark" rather than "heavy" is intentional... */
+#if 0 /*JP*/
+                    You_feel("%s headed.", Hallucination ? "dark" : "light");
+#else /*\82¢\82¢\96ó\8cê\82ð\8ev\82¢\82Â\82©\82È\82¢\82Ì\82Å\8c\8ao\82Å\83\81\83b\83Z\81[\83W\82ð\95Ï\82¦\82È\82¢ */
+                    You_feel("\8cy\82Í\82¸\82Ý\82¾\82Á\82½\8bC\82ª\82µ\82½\81D");
+#endif
+                u.mh = 0; /* fatal; death handled by rehumanize() */
+            }
+            if (Unchanging && u.mh > 0)
 /*JP
                 Your("amulet grows hot for a moment, then cools.");
 */
@@ -2933,15 +3149,30 @@ boolean youattack, allow_cancel_kill, self_cancel;
                 rehumanize();
         }
     } else {
-        mdef->mcan = TRUE;
-
-        if (is_were(mdef->data) && mdef->data->mlet != S_HUMAN)
+        mdef->mcan = 1;
+        /* force shapeshifter into its base form */
+        if (M_AP_TYPE(mdef) != M_AP_NOTHING)
+            seemimic(mdef);
+        /* [not 'else if'; chameleon might have been hiding as a mimic] */
+        if (mdef->cham >= LOW_PM) {
+            /* note: newcham() uncancels shapechangers (resets m->mcan
+               to 0), but only for shapechangers whose m->cham is already
+               NON_PM and we just verified that it's LOW_PM or higher */
+            newcham(mdef, &mons[mdef->cham], FALSE, FALSE);
+            mdef->cham = NON_PM; /* cancelled shapeshifter can't shift */
+        }
+        if (is_were(mdef->data) && !is_human(mdef->data))
             were_change(mdef);
 
         if (mdef->data == &mons[PM_CLAY_GOLEM]) {
             if (canseemon(mdef))
+#if 0 /*JP*/
                 pline(writing_vanishes, s_suffix(mon_nam(mdef)));
-
+#else
+                pline(writing_vanishes, mon_nam(mdef));
+#endif
+            /* !allow_cancel_kill is for Magicbane, where clay golem
+               will be killed somewhere back up the call/return chain... */
             if (allow_cancel_kill) {
                 if (youattack)
                     killed(mdef);
@@ -2999,10 +3230,10 @@ struct obj *obj; /* wand or spell */
         if (is_db_wall(x, y) && find_drawbridge(&xx, &yy)) {
             open_drawbridge(xx, yy);
             disclose = TRUE;
-        } else if (u.dz > 0 && (x == xdnstair && y == ydnstair) &&
+        } else if (u.dz > 0 && (x == xdnstair && y == ydnstair)
                    /* can't use the stairs down to quest level 2 until
                       leader "unlocks" them; give feedback if you try */
-                   on_level(&u.uz, &qstart_level) && !ok_to_quest()) {
+                   && on_level(&u.uz, &qstart_level) && !ok_to_quest()) {
 /*JP
             pline_The("stairs seem to ripple momentarily.");
 */
@@ -3038,7 +3269,7 @@ struct obj *obj; /* wand or spell */
                    && !Is_qstart(&u.uz)) {
             int dmg;
             /* similar to zap_dig() */
-#if 0 /*JP*/
+#if 0 /*JP:T*/
             pline("A rock is dislodged from the %s and falls on your %s.",
                   ceiling(x, y), body_part(HEAD));
 #else
@@ -3086,7 +3317,7 @@ struct obj *obj; /* wand or spell */
                 /* locking transforms hole into trapdoor */
                 ttmp->ttyp = TRAPDOOR;
                 if (Blind || !ttmp->tseen) {
-#if 0 /*JP*/
+#if 0 /*JP:T*/
                     pline("Some %s swirls beneath you.",
                           is_ice(x, y) ? "frost" : "dust");
 #else
@@ -3168,7 +3399,7 @@ struct obj *obj; /* wand or spell */
             case SPE_STONE_TO_FLESH:
                 if (e->engr_type == ENGRAVE) {
                     /* only affects things in stone */
-#if 0 /*JP*/
+#if 0 /*JP:T*/
                     pline_The(Hallucination
                                   ? "floor runs like butter!"
                                   : "edges on the floor get smoother.");
@@ -3375,7 +3606,7 @@ const char *force; /* usually either "." or "!" */
 */
         pline("%s\82Í\89½\82©\82É\96½\92\86\82µ\82½\81D", str);
     else
-#if 0 /*JP*/
+#if 0 /*JP:T*/
         pline("%s %s %s%s", The(str), vtense(str, "hit"),
               mon_nam(mtmp), force);
 #else
@@ -3389,7 +3620,7 @@ miss(str, mtmp)
 register const char *str;
 register struct monst *mtmp;
 {
-#if 0 /*JP*/
+#if 0 /*JP:T*/
     pline(
         "%s %s %s.", The(str), vtense(str, "miss"),
         ((cansee(bhitpos.x, bhitpos.y) || canspotmon(mtmp)) && flags.verbose)
@@ -3410,6 +3641,7 @@ int range, *skipstart, *skipend;
 {
     int tr = (range / 4);
     int tmp = range - ((tr > 0) ? rnd(tr) : 0);
+
     *skipstart = tmp;
     *skipend = tmp - ((tmp / 4) * rnd(3));
     if (*skipend >= tmp)
@@ -3440,17 +3672,18 @@ int range, *skipstart, *skipend;
 struct monst *
 bhit(ddx, ddy, range, weapon, fhitm, fhito, pobj)
 register int ddx, ddy, range;          /* direction and range */
-int weapon;                            /* see values in hack.h */
+enum bhit_call_types weapon;           /* defined in hack.h */
 int FDECL((*fhitm), (MONST_P, OBJ_P)), /* fns called when mon/obj hit */
     FDECL((*fhito), (OBJ_P, OBJ_P));
 struct obj **pobj; /* object tossed/used, set to NULL
                     * if object is destroyed */
 {
-    struct monst *mtmp;
+    struct monst *mtmp, *result = (struct monst *) 0;
     struct obj *obj = *pobj;
     uchar typ;
     boolean shopdoor = FALSE, point_blank = TRUE;
     boolean in_skip = FALSE, allow_skip = FALSE;
+    boolean tethered_weapon = FALSE;
     int skiprange_start = 0, skiprange_end = 0, skipcount = 0;
 
     if (weapon == KICKED_WEAPON) {
@@ -3470,8 +3703,12 @@ struct obj **pobj; /* object tossed/used, set to NULL
 
     if (weapon == FLASHED_LIGHT) {
         tmp_at(DISP_BEAM, cmap_to_glyph(S_flashbeam));
+    } else if (weapon == THROWN_TETHERED_WEAPON && obj) {
+        tethered_weapon = TRUE;
+        weapon = THROWN_WEAPON; /* simplify 'if's that follow below */
+        tmp_at(DISP_TETHER, obj_to_glyph(obj, rn2_on_display_rng));
     } else if (weapon != ZAPPED_WAND && weapon != INVIS_BEAM)
-        tmp_at(DISP_FLASH, obj_to_glyph(obj));
+        tmp_at(DISP_FLASH, obj_to_glyph(obj, rn2_on_display_rng));
 
     while (range-- > 0) {
         int x, y;
@@ -3488,22 +3725,27 @@ struct obj **pobj; /* object tossed/used, set to NULL
         }
 
         if (is_pick(obj) && inside_shop(x, y)
-            && (mtmp = shkcatch(obj, x, y))) {
+            && (mtmp = shkcatch(obj, x, y)) != 0) {
             tmp_at(DISP_END, 0);
-            return mtmp;
+            result = mtmp;
+            goto bhit_done;
         }
 
         typ = levl[bhitpos.x][bhitpos.y].typ;
 
-        /* iron bars will block anything big enough */
-        if ((weapon == THROWN_WEAPON || weapon == KICKED_WEAPON)
-            && typ == IRONBARS && hits_bars(pobj, x - ddx, y - ddy,
-                                            point_blank ? 0 : !rn2(5), 1)) {
-            /* caveat: obj might now be null... */
-            obj = *pobj;
-            bhitpos.x -= ddx;
-            bhitpos.y -= ddy;
-            break;
+        /* iron bars will block anything big enough and break some things */
+        if (weapon == THROWN_WEAPON || weapon == KICKED_WEAPON) {
+            if (typ == IRONBARS
+                && hits_bars(pobj, x - ddx, y - ddy, bhitpos.x, bhitpos.y,
+                             point_blank ? 0 : !rn2(5), 1)) {
+                /* caveat: obj might now be null... */
+                obj = *pobj;
+                bhitpos.x -= ddx;
+                bhitpos.y -= ddy;
+                break;
+            } else if (obj->lamplit && !Blind) {
+                show_transient_light(obj, bhitpos.x, bhitpos.y);
+            }
         }
 
         if (weapon == ZAPPED_WAND && find_drawbridge(&x, &y)) {
@@ -3547,7 +3789,7 @@ struct obj **pobj; /* object tossed/used, set to NULL
             if (is_pool(bhitpos.x, bhitpos.y) && !mtmp) {
                 in_skip = TRUE;
                 if (!Blind)
-#if 0 /*JP*/
+#if 0 /*JP:T*/
                     pline("%s %s%s.", Yname2(obj), otense(obj, "skip"),
                           skipcount ? " again" : "");
 #else
@@ -3570,18 +3812,25 @@ struct obj **pobj; /* object tossed/used, set to NULL
                 if (range > 3) /* another bounce? */
                     skiprange(range, &skiprange_start, &skiprange_end);
             } else if (mtmp && M_IN_WATER(mtmp->data)) {
-                if ((!Blind && canseemon(mtmp)) || sensemon(mtmp))
-#if 0 /*JP*/
+                if (!Blind && canspotmon(mtmp))
+#if 0 /*JP:T*/
                     pline("%s %s over %s.", Yname2(obj), otense(obj, "pass"),
                           mon_nam(mtmp));
 #else
                     pline("%s\82Í%s\82ð\94ò\82Ñ\89z\82¦\82½\81D", Yname2(obj),
                           mon_nam(mtmp));
 #endif
+                mtmp = (struct monst *) 0;
             }
         }
 
-        if (mtmp && !(in_skip && M_IN_WATER(mtmp->data))) {
+        /* if mtmp is a shade and missile passes harmlessly through it,
+           give message and skip it in order to keep going */
+        if (mtmp && (weapon == THROWN_WEAPON || weapon == KICKED_WEAPON)
+            && shade_miss(&youmonst, mtmp, obj, TRUE, TRUE))
+            mtmp = (struct monst *) 0;
+
+        if (mtmp) {
             notonhead = (bhitpos.x != mtmp->mx || bhitpos.y != mtmp->my);
             if (weapon == FLASHED_LIGHT) {
                 /* FLASHED_LIGHT hitting invisible monster should
@@ -3596,7 +3845,8 @@ struct obj **pobj; /* object tossed/used, set to NULL
                     (void) flash_hits_mon(mtmp, obj);
                 } else {
                     tmp_at(DISP_END, 0);
-                    return mtmp; /* caller will call flash_hits_mon */
+                    result = mtmp; /* caller will call flash_hits_mon */
+                    goto bhit_done;
                 }
             } else if (weapon == INVIS_BEAM) {
                 /* Like FLASHED_LIGHT, INVIS_BEAM should continue
@@ -3604,14 +3854,20 @@ struct obj **pobj; /* object tossed/used, set to NULL
                    prepared for multiple hits so just get first one
                    that's either visible or could see its invisible
                    self.  [No tmp_at() cleanup is needed here.] */
-                if (!mtmp->minvis || perceives(mtmp->data))
-                    return mtmp;
+                if (!mtmp->minvis || perceives(mtmp->data)) {
+                    result = mtmp;
+                    goto bhit_done;
+                }
             } else if (weapon != ZAPPED_WAND) {
+
                 /* THROWN_WEAPON, KICKED_WEAPON */
-                tmp_at(DISP_END, 0);
+                if (!tethered_weapon)
+                    tmp_at(DISP_END, 0);
+
                 if (cansee(bhitpos.x, bhitpos.y) && !canspotmon(mtmp))
                     map_invisible(bhitpos.x, bhitpos.y);
-                return mtmp;
+                result = mtmp;
+                goto bhit_done;
             } else {
                 /* ZAPPED_WAND */
                 (*fhitm)(mtmp, obj);
@@ -3634,7 +3890,7 @@ struct obj **pobj; /* object tossed/used, set to NULL
                     || ship_object(obj, bhitpos.x, bhitpos.y,
                                    costly_spot(bhitpos.x, bhitpos.y)))) {
                 tmp_at(DISP_END, 0);
-                return (struct monst *) 0;
+                goto bhit_done; /* result == (struct monst *) 0 */
             }
         }
         if (weapon == ZAPPED_WAND && (IS_DOOR(typ) || typ == SDOOR)) {
@@ -3652,7 +3908,7 @@ struct obj **pobj; /* object tossed/used, set to NULL
                     if (levl[bhitpos.x][bhitpos.y].doormask == D_BROKEN
                         && *in_rooms(bhitpos.x, bhitpos.y, SHOPBASE)) {
                         shopdoor = TRUE;
-                        add_damage(bhitpos.x, bhitpos.y, 400L);
+                        add_damage(bhitpos.x, bhitpos.y, SHOP_DOOR_COST);
                     }
                 }
                 break;
@@ -3686,9 +3942,10 @@ struct obj **pobj; /* object tossed/used, set to NULL
             && obj->otyp == HEAVY_IRON_BALL) {
             struct obj *bobj;
             struct trap *t;
+
             if ((bobj = sobj_at(BOULDER, x, y)) != 0) {
                 if (cansee(x, y))
-#if 0 /*JP*/
+#if 0 /*JP:T*/
                     pline("%s hits %s.", The(distant_name(obj, xname)),
                           an(xname(bobj)));
 #else
@@ -3700,7 +3957,7 @@ struct obj **pobj; /* object tossed/used, set to NULL
                 if (!test_move(x - ddx, y - ddy, ddx, ddy, TEST_MOVE)) {
                     /* nb: it didn't hit anything directly */
                     if (cansee(x, y))
-#if 0 /*JP*/
+#if 0 /*JP:T*/
                         pline("%s jerks to an abrupt halt.",
                               The(distant_name(obj, xname))); /* lame */
 #else
@@ -3709,8 +3966,7 @@ struct obj **pobj; /* object tossed/used, set to NULL
 #endif
                     range = 0;
                 } else if (Sokoban && (t = t_at(x, y)) != 0
-                           && (t->ttyp == PIT || t->ttyp == SPIKED_PIT
-                               || t->ttyp == HOLE || t->ttyp == TRAPDOOR)) {
+                           && (is_pit(t->ttyp) || is_hole(t->ttyp))) {
                     /* hero falls into the trap, so ball stops */
                     range = 0;
                 }
@@ -3721,7 +3977,7 @@ struct obj **pobj; /* object tossed/used, set to NULL
         point_blank = FALSE; /* affects passing through iron bars */
     }
 
-    if (weapon != ZAPPED_WAND && weapon != INVIS_BEAM)
+    if (weapon != ZAPPED_WAND && weapon != INVIS_BEAM && !tethered_weapon)
         tmp_at(DISP_END, 0);
 
     if (shopdoor)
@@ -3730,7 +3986,11 @@ struct obj **pobj; /* object tossed/used, set to NULL
 */
         pay_for_damage("\94j\89ó\82·\82é", FALSE);
 
-    return (struct monst *) 0;
+ bhit_done:
+    if (weapon == THROWN_WEAPON || weapon == KICKED_WEAPON)
+        transient_light_cleanup();
+
+    return result;
 }
 
 /* process thrown boomerang, which travels a curving path...
@@ -3791,7 +4051,7 @@ int dx, dy;
         if (bhitpos.x == u.ux && bhitpos.y == u.uy) { /* ct == 9 */
             if (Fumbling || rn2(20) >= ACURR(A_DEX)) {
                 /* we hit ourselves */
-                (void) thitu(10 + obj->spe, dmgval(obj, &youmonst), obj,
+                (void) thitu(10 + obj->spe, dmgval(obj, &youmonst), &obj,
 /*JP
                              "boomerang");
 */
@@ -3826,7 +4086,8 @@ int dx, dy;
     return (struct monst *) 0;
 }
 
-/* used by buzz(); also used by munslime(muse.c); returns damage to mon */
+/* used by buzz(); also used by munslime(muse.c); returns damage applied
+   to mon; note: caller is responsible for killing mon if damage is fatal */
 int
 zhitm(mon, type, nd, ootmp)
 register struct monst *mon;
@@ -4135,15 +4396,15 @@ xchar sx, sy;
     case ZT_ACID:
         if (Acid_resistance) {
 /*JP
-            pline_The("acid doesn't hurt.");
+            pline_The("%s doesn't hurt.", hliquid("acid"));
 */
-            pline("\8e_\82Å\82Í\8f\9d\82Â\82©\82È\82©\82Á\82½\81D");
+            pline_The("%s\82Å\82Í\8f\9d\82Â\82©\82È\82©\82Á\82½\81D", hliquid("\8e_"));
             dam = 0;
         } else {
 /*JP
-            pline_The("acid burns!");
+            pline_The("%s burns!", hliquid("acid"));
 */
-            pline("\8e_\82Å\8fÄ\82¯\82½\81I");
+            pline_The("%s\82Å\8fÄ\82¯\82½\81I", hliquid("\8e_"));
             dam = d(nd, 6);
             exercise(A_STR, FALSE);
         }
@@ -4303,7 +4564,16 @@ const char *fltxt;
     if (type < 0)
         monkilled(mon, (char *) 0, -AD_RBRE);
     else
-        xkilled(mon, 2);
+        xkilled(mon, XKILL_NOMSG | XKILL_NOCORPSE);
+}
+
+void
+buzz(type, nd, sx, sy, dx, dy)
+int type, nd;
+xchar sx, sy;
+int dx, dy;
+{
+    dobuzz(type, nd, sx, sy, dx, dy, TRUE);
 }
 
 /*
@@ -4316,13 +4586,13 @@ const char *fltxt;
  * called with dx = dy = 0 with vertical bolts
  */
 void
-buzz(type, nd, sx, sy, dx, dy)
+dobuzz(type, nd, sx, sy, dx, dy, say)
 register int type, nd;
 register xchar sx, sy;
 register int dx, dy;
+boolean say; /* Announce out of sight hit/miss events if true */
 {
     int range, abstype = abs(type) % 10;
-    struct rm *lev;
     register xchar lsx, lsy;
     struct monst *mon;
     coord save_bhitpos;
@@ -4344,7 +4614,7 @@ register int dx, dy;
         if (!u.ustuck)
             u.uswallow = 0;
         else
-#if 0 /*JP*/
+#if 0 /*JP:T*/
             pline("%s rips into %s%s", The(fltxt), mon_nam(u.ustuck),
                   exclam(tmp));
 #else
@@ -4354,7 +4624,7 @@ register int dx, dy;
         /* Using disintegration from the inside only makes a hole... */
         if (tmp == MAGIC_COOKIE)
             u.ustuck->mhp = 0;
-        if (u.ustuck->mhp < 1)
+        if (DEADMONSTER(u.ustuck))
             killed(u.ustuck);
         return;
     }
@@ -4371,41 +4641,45 @@ register int dx, dy;
         sx += dx;
         lsy = sy;
         sy += dy;
-        if (isok(sx, sy) && (lev = &levl[sx][sy])->typ) {
-            mon = m_at(sx, sy);
-            if (cansee(sx, sy)) {
-                /* reveal/unreveal invisible monsters before tmp_at() */
-                if (mon && !canspotmon(mon))
-                    map_invisible(sx, sy);
-                else if (!mon && glyph_is_invisible(levl[sx][sy].glyph)) {
-                    unmap_object(sx, sy);
-                    newsym(sx, sy);
-                }
-                if (ZAP_POS(lev->typ) || (isok(lsx, lsy) && cansee(lsx, lsy)))
-                    tmp_at(sx, sy);
-                delay_output(); /* wait a little */
-            }
-        } else
+        if (!isok(sx, sy) || levl[sx][sy].typ == STONE)
             goto make_bounce;
 
+        mon = m_at(sx, sy);
+        if (cansee(sx, sy)) {
+            /* reveal/unreveal invisible monsters before tmp_at() */
+            if (mon && !canspotmon(mon))
+                map_invisible(sx, sy);
+            else if (!mon)
+                (void) unmap_invisible(sx, sy);
+            if (ZAP_POS(levl[sx][sy].typ)
+                || (isok(lsx, lsy) && cansee(lsx, lsy)))
+                tmp_at(sx, sy);
+            delay_output(); /* wait a little */
+        }
+
         /* hit() and miss() need bhitpos to match the target */
         bhitpos.x = sx, bhitpos.y = sy;
         /* Fireballs only damage when they explode */
-        if (type != ZT_SPELL(ZT_FIRE))
+        if (type != ZT_SPELL(ZT_FIRE)) {
             range += zap_over_floor(sx, sy, type, &shopdamage, 0);
+            /* zap with fire -> melt ice -> drown monster, so monster
+               found and cached above might not be here any more */
+            mon = m_at(sx, sy);
+        }
 
         if (mon) {
             if (type == ZT_SPELL(ZT_FIRE))
                 break;
             if (type >= 0)
                 mon->mstrategy &= ~STRAT_WAITMASK;
-        buzzmonst:
+ buzzmonst:
+            notonhead = (mon->mx != bhitpos.x || mon->my != bhitpos.y);
             if (zap_hit(find_mac(mon), spell_type)) {
                 if (mon_reflects(mon, (char *) 0)) {
                     if (cansee(mon->mx, mon->my)) {
                         hit(fltxt, mon, exclam(0));
                         shieldeff(mon->mx, mon->my);
-#if 0 /*JP*/
+#if 0 /*JP:T*/
                         (void) mon_reflects(mon,
                                             "But it reflects from %s %s!");
 #else
@@ -4454,7 +4728,7 @@ register int dx, dy;
                             hit(fltxt, mon, ".");
 */
                             hit(fltxt, mon, "\81D");
-#if 0 /*JP*/
+#if 0 /*JP:T*/
                             pline("%s absorbs the deadly %s!", Monnam(mon),
                                   type == ZT_BREATH(ZT_DEATH) ? "blast"
                                                               : "ray");
@@ -4473,24 +4747,40 @@ register int dx, dy;
 
                     if (tmp == MAGIC_COOKIE) { /* disintegration */
                         disintegrate_mon(mon, type, fltxt);
-                    } else if (mon->mhp < 1) {
-                        if (type < 0)
+                    } else if (DEADMONSTER(mon)) {
+                        if (type < 0) {
+                            /* mon has just been killed by another monster */
                             monkilled(mon, fltxt, AD_RBRE);
-                        else
-                            killed(mon);
+                        } else {
+                            int xkflags = XKILL_GIVEMSG; /* killed(mon); */
+
+                            /* killed by hero; we know 'type' isn't negative;
+                               if it's fire, highly flammable monsters leave
+                               no corpse; don't bother reporting that they
+                               "burn completely" -- unnecessary verbosity */
+                            if ((type % 10 == ZT_FIRE)
+                                /* paper golem or straw golem */
+                                && completelyburns(mon->data))
+                                xkflags |= XKILL_NOCORPSE;
+                            xkilled(mon, xkflags);
+                        }
                     } else {
                         if (!otmp) {
                             /* normal non-fatal hit */
-                            hit(fltxt, mon, exclam(tmp));
+                            if (say || canseemon(mon))
+                                hit(fltxt, mon, exclam(tmp));
                         } else {
                             /* some armor was destroyed; no damage done */
                             if (canseemon(mon))
-/*JP
+#if 0 /*JP:T*/
                                 pline("%s %s is disintegrated!",
-*/
-                                pline("%s\82Ì%s\82Í\82±\82È\82²\82È\82É\82È\82Á\82½\81I",
                                       s_suffix(Monnam(mon)),
                                       distant_name(otmp, xname));
+#else
+                                pline("%s\82Ì%s\82Í\82±\82È\82²\82È\82É\82È\82Á\82½\81I",
+                                      Monnam(mon),
+                                      distant_name(otmp, xname));
+#endif
                             m_useup(mon, otmp);
                         }
                         if (mon_could_move && !mon->mcanmove) /* ZT_SLEEP */
@@ -4499,7 +4789,8 @@ register int dx, dy;
                 }
                 range -= 2;
             } else {
-                miss(fltxt, mon);
+                if (say || canseemon(mon))
+                    miss(fltxt, mon);
             }
         } else if (sx == u.ux && sy == u.uy && range >= 0) {
             nomul(0);
@@ -4514,7 +4805,7 @@ register int dx, dy;
                 pline("%s\82Í\82 \82È\82½\82É\96½\92\86\82µ\82½\81I", fltxt);
                 if (Reflecting) {
                     if (!Blind) {
-#if 0 /*JP*/
+#if 0 /*JP:T*/
                         (void) ureflects("But %s reflects from your %s!",
                                          "it");
 #else
@@ -4549,33 +4840,39 @@ register int dx, dy;
             nomul(0);
         }
 
-        if (!ZAP_POS(lev->typ) || (closed_door(sx, sy) && (range >= 0))) {
-            int bounce;
+        if (!ZAP_POS(levl[sx][sy].typ)
+            || (closed_door(sx, sy) && range >= 0)) {
+            int bounce, bchance;
             uchar rmn;
+            boolean fireball;
 
-        make_bounce:
-            if (type == ZT_SPELL(ZT_FIRE)) {
-                sx = lsx;
-                sy = lsy;
-                break; /* fireballs explode before the wall */
-            }
+ make_bounce:
+            bchance = (levl[sx][sy].typ == STONE) ? 10
+                : (In_mines(&u.uz) && IS_WALL(levl[sx][sy].typ)) ? 20
+                : 75;
             bounce = 0;
-            range--;
-            if (range && isok(lsx, lsy) && cansee(lsx, lsy)) {
-#if 0 /*JP*/
-                    pline("%s %s!", The(fltxt),
-                          Is_airlevel(&u.uz)
-                          ? "vanishes into the aether"
-                          : "bounces");
-#else
-                    pline("%s\82Í%s\81I", The(fltxt),
-                          Is_airlevel(&u.uz)
-                          ? "\83G\81[\83e\83\8b\8bó\8aÔ\82É\8fÁ\82¦\82½"
-                          : "\94½\8eË\82µ\82½");
-#endif
-                    if (Is_airlevel(&u.uz)) goto get_out_buzz;
+            fireball = (type == ZT_SPELL(ZT_FIRE));
+            if ((--range > 0 && isok(lsx, lsy) && cansee(lsx, lsy))
+                || fireball) {
+                if (Is_airlevel(&u.uz)) { /* nothing to bounce off of */
+/*JP
+                    pline_The("%s vanishes into the aether!", fltxt);
+*/
+                    pline_The("%s\82Í\83G\81[\83e\83\8b\8bó\8aÔ\82É\8fÁ\82¦\82½\81I", fltxt);
+                    if (fireball)
+                        type = ZT_WAND(ZT_FIRE); /* skip pending fireball */
+                    break;
+                } else if (fireball) {
+                    sx = lsx;
+                    sy = lsy;
+                    break; /* fireballs explode before the obstacle */
+                } else
+/*JP
+                    pline_The("%s bounces!", fltxt);
+*/
+                    pline_The("%s\82Í\94½\8eË\82µ\82½\81I", fltxt);
             }
-            if (!dx || !dy || !rn2(20)) {
+            if (!dx || !dy || !rn2(bchance)) {
                 dx = -dx;
                 dy = -dy;
             } else {
@@ -4593,7 +4890,8 @@ register int dx, dy;
 
                 switch (bounce) {
                 case 0:
-                    dx = -dx; /* fall into... */
+                    dx = -dx;
+                    /*FALLTHRU*/
                 case 1:
                     dy = -dy;
                     break;
@@ -4608,16 +4906,15 @@ register int dx, dy;
     tmp_at(DISP_END, 0);
     if (type == ZT_SPELL(ZT_FIRE))
         explode(sx, sy, type, d(12, 6), 0, EXPL_FIERY);
- get_out_buzz:
     if (shopdamage)
-#if 0 /*JP*/
+#if 0 /*JP:T*/
         pay_for_damage(abstype == ZT_FIRE
                           ? "burn away"
                           : abstype == ZT_COLD
                              ? "shatter"
                              /* "damage" indicates wall rather than door */
                              : abstype == ZT_ACID
-                             ? "damage"
+                                ? "damage"
                                 : abstype == ZT_DEATH
                                    ? "disintegrate"
                                    : "destroy",
@@ -4644,13 +4941,14 @@ const char *msg;
 {
     struct rm *lev = &levl[x][y];
     struct obj *otmp;
+    struct monst *mtmp;
 
     if (!msg)
 /*JP
         msg = "The ice crackles and melts.";
 */
         msg = "\95X\82Í\83s\83L\83s\83L\96Â\82è\81C\97n\82¯\82½\81D";
-    if (lev->typ == DRAWBRIDGE_UP) {
+    if (lev->typ == DRAWBRIDGE_UP || lev->typ == DRAWBRIDGE_DOWN) {
         lev->drawbridgemask &= ~DB_ICE; /* revert to DB_MOAT */
     } else { /* lev->typ == ICE */
 #ifdef STUPID
@@ -4687,6 +4985,8 @@ const char *msg;
     }
     if (x == u.ux && y == u.uy)
         spoteffects(TRUE); /* possibly drown, notice objects */
+    else if (is_pool(x, y) && (mtmp = m_at(x, y)) != 0)
+        (void) minliquid(mtmp);
 }
 
 #define MIN_ICE_TIME 50
@@ -4733,7 +5033,10 @@ long timeout UNUSED;
 {
     xchar x, y;
     long where = arg->a_long;
+    boolean save_mon_moving = context.mon_moving; /* will be False */
 
+    /* melt_ice -> minliquid -> mondead|xkilled shouldn't credit/blame hero */
+    context.mon_moving = TRUE; /* hero isn't causing this ice to melt */
     y = (xchar) (where & 0xFFFF);
     x = (xchar) ((where >> 16) & 0xFFFF);
     /* melt_ice does newsym when appropriate */
@@ -4741,6 +5044,7 @@ long timeout UNUSED;
     melt_ice(x, y, "Some ice melts away.");
 */
     melt_ice(x, y, "\95X\82ª\8f­\82µ\97n\82¯\82½\81D");
+    context.mon_moving = save_mon_moving;
 }
 
 /* Burn floor scrolls, evaporate pools, etc... in a single square.
@@ -4779,10 +5083,19 @@ short exploding_wand_typ;
         if (is_ice(x, y)) {
             melt_ice(x, y, (char *) 0);
         } else if (is_pool(x, y)) {
-/*JP
-            const char *msgtxt = "You hear hissing gas.";
-*/
-            const char *msgtxt = "\82µ\82ã\81[\82Á\82Æ\82¢\82¤\83K\83X\82Ì\89¹\82ð\95·\82¢\82½\81D";
+#if 0 /*JP*/
+            const char *msgtxt = (!Deaf)
+                                     ? "You hear hissing gas." /* Deaf-aware */
+                                     : (type >= 0)
+                                         ? "That seemed remarkably uneventful."
+                                         : (const char *) 0;
+#else
+            const char *msgtxt = (!Deaf)
+                                     ? "\82µ\82ã\81[\82Á\82Æ\82¢\82¤\83K\83X\82Ì\89¹\82ð\95·\82¢\82½\81D" /* Deaf-aware */
+                                     : (type >= 0)
+                                         ? "\82±\82ê\82Í\8bÁ\82­\82Ù\82Ç\82È\82ñ\82Å\82à\82È\82³\82»\82¤\82¾\81D"
+                                         : (const char *) 0;
+#endif
 
             if (lev->typ != POOL) { /* MOAT or DRAWBRIDGE_UP */
                 if (see_it)
@@ -4792,7 +5105,7 @@ short exploding_wand_typ;
                     msgtxt = "\82·\82±\82µ\90\85\82ª\8fö\94­\82µ\82½\81D";
             } else {
                 rangemod -= 3;
-                lev->typ = ROOM;
+                lev->typ = ROOM, lev->flags = 0;
                 t = maketrap(x, y, PIT);
                 if (t)
                     t->tseen = 1;
@@ -4802,7 +5115,8 @@ short exploding_wand_typ;
 */
                     msgtxt = "\90\85\82ª\8fö\94­\82µ\82½\81D";
             }
-            Norep("%s", msgtxt);
+            if (msgtxt)
+                Norep("%s", msgtxt);
             if (lev->typ == ROOM)
                 newsym(x, y);
         } else if (IS_FOUNTAIN(lev->typ)) {
@@ -4818,16 +5132,16 @@ short exploding_wand_typ;
 
     case ZT_COLD:
         if (is_pool(x, y) || is_lava(x, y)) {
-            boolean lava = is_lava(x, y);
-            boolean moat = is_moat(x, y);
+            boolean lava = is_lava(x, y),
+                    moat = is_moat(x, y);
 
             if (lev->typ == WATER) {
                 /* For now, don't let WATER freeze. */
                 if (see_it)
 /*JP
-                    pline_The("water freezes for a moment.");
+                    pline_The("%s freezes for a moment.", hliquid("water"));
 */
-                    pline("\90\85\82Í\88ê\8fu\93\80\82Á\82½\81D");
+                    pline_The("%s\82Í\88ê\8fu\93\80\82Á\82½\81D", hliquid("\90\85"));
                 else
 /*JP
                     You_hear("a soft crackling.");
@@ -4835,34 +5149,36 @@ short exploding_wand_typ;
                     You_hear("\83s\83L\81I\82Æ\82¢\82¤\89¹\82ð\95·\82¢\82½\81D");
                 rangemod -= 1000; /* stop */
             } else {
+                char buf[BUFSZ];
+
+                Strcpy(buf, waterbody_name(x, y)); /* for MOAT */
                 rangemod -= 3;
                 if (lev->typ == DRAWBRIDGE_UP) {
                     lev->drawbridgemask &= ~DB_UNDER; /* clear lava */
                     lev->drawbridgemask |= (lava ? DB_FLOOR : DB_ICE);
                 } else {
-                    if (!lava)
-                        lev->icedpool =
-                            (lev->typ == POOL ? ICED_POOL : ICED_MOAT);
-                    lev->typ = (lava ? ROOM : ICE);
+                    lev->icedpool = lava ? 0
+                                         : (lev->typ == POOL) ? ICED_POOL
+                                                              : ICED_MOAT;
+                    lev->typ = lava ? ROOM : ICE;
                 }
                 bury_objs(x, y);
                 if (see_it) {
                     if (lava)
 /*JP
-                        Norep("The lava cools and solidifies.");
+                        Norep("The %s cools and solidifies.", hliquid("lava"));
 */
-                        Norep("\97n\8aâ\82Í\97â\82¦\8cÅ\82Ü\82Á\82½\81D");
+                        Norep("%s\82Í\97â\82¦\8cÅ\82Ü\82Á\82½\81D", hliquid("lava"));
                     else if (moat)
 /*JP
-                        Norep("The %s is bridged with ice!",
+                        Norep("The %s is bridged with ice!", buf);
 */
-                        Norep("%s\82É\95X\82Ì\8b´\82ª\82©\82¯\82ç\82ê\82½\81I",
-                              waterbody_name(x, y));
+                        Norep("%s\82É\95X\82Ì\8b´\82ª\82©\82¯\82ç\82ê\82½\81I", buf);
                     else
 /*JP
-                        Norep("The water freezes.");
+                        Norep("The %s freezes.", hliquid("water"));
 */
-                        Norep("\90\85\82Í\93\80\82Á\82½\81D");
+                        Norep("%s\82Í\93\80\82Á\82½\81D", hliquid("\95X"));
                     newsym(x, y);
                 } else if (!lava)
 /*JP
@@ -4879,14 +5195,13 @@ short exploding_wand_typ;
                         vision_full_recalc = 1;
                     } else if (u.utrap && u.utraptype == TT_LAVA) {
                         if (Passes_walls) {
-                            u.utrap = 0;
 /*JP
                             You("pass through the now-solid rock.");
 */
                             You("\82¢\82Ü\8cÅ\82­\82È\82Á\82½\82Î\82©\82è\82Ì\90Î\82Ì\92\86\82ð\82·\82è\94²\82¯\82½\81D");
+                            reset_utrap(TRUE);
                         } else {
-                            u.utrap = rn1(50, 20);
-                            u.utraptype = TT_INFLOOR;
+                            set_utrap(rn1(50, 20), TT_INFLOOR);
 /*JP
                             You("are firmly stuck in the cooling rock.");
 */
@@ -4920,6 +5235,10 @@ short exploding_wand_typ;
         }
         break; /* ZT_COLD */
 
+    case ZT_POISON_GAS:
+        (void) create_gas_cloud(x, y, 1, 8);
+        break;
+
     case ZT_ACID:
         if (lev->typ == IRONBARS) {
             if ((lev->wall_info & W_NONDIGGABLE) != 0) {
@@ -4939,15 +5258,14 @@ short exploding_wand_typ;
                     Norep("%s\82ª\97n\82¯\82½\81D", defsyms[S_bars].explanation);
                 if (*in_rooms(x, y, SHOPBASE)) {
                     /* in case we ever have a shop bounded by bars */
-                    lev->typ = ROOM;
+                    lev->typ = ROOM, lev->flags = 0;
                     if (see_it)
                         newsym(x, y);
-                    add_damage(x, y, (type >= 0) ? 300L : 0L);
+                    add_damage(x, y, (type >= 0) ? SHOP_BARS_COST : 0L);
                     if (type >= 0)
                         *shopdamage = TRUE;
                 } else {
-                    lev->typ = DOOR;
-                    lev->doormask = D_NODOOR;
+                    lev->typ = DOOR, lev->doormask = D_NODOOR;
                     if (see_it)
                         newsym(x, y);
                 }
@@ -4962,14 +5280,14 @@ short exploding_wand_typ;
     /* set up zap text for possible door feedback; for exploding wand, we
        want "the blast" rather than "your blast" even if hero caused it */
     yourzap = (type >= 0 && !exploding_wand_typ);
-#if 0 /*JP*/
+#if 0 /*JP:T*/
     zapverb = "blast"; /* breath attack or wand explosion */
 #else
     zapverb = "\8fÕ\8c\82"; /* breath attack or wand explosion */
 #endif
     if (!exploding_wand_typ) {
         if (abs(type) < ZT_SPELL(0))
-#if 0 /*JP*/
+#if 0 /*JP:T*/
             zapverb = "bolt"; /* wand zap */
 #else
             zapverb = "\8cõ\90ü"; /* wand zap */
@@ -4988,7 +5306,7 @@ short exploding_wand_typ;
            (except on rogue level) */
         newsym(x, y);
         if (see_it)
-#if 0 /*JP*/
+#if 0 /*JP:T*/
             pline("%s %s reveals a secret door.",
                   yourzap ? "Your" : "The", zapverb);
 #else
@@ -5053,7 +5371,7 @@ short exploding_wand_typ;
             hear_txt = "\83s\83L\83s\83L\82Æ\82¢\82¤\89¹\82ð\95·\82¢\82½\81D";
             break;
         default:
       def_case:
+ def_case:
             if (exploding_wand_typ > 0) {
                 /* Magical explosion from misc exploding wand */
                 if (exploding_wand_typ == WAN_STRIKING) {
@@ -5079,7 +5397,7 @@ short exploding_wand_typ;
 */
                     pline_The("\94à\82Í\96³\8f\9d\82¾\81D");
                 else
-#if 0 /*JP*/
+#if 0 /*JP:T*/
                     pline_The("door absorbs %s %s!", yourzap ? "your" : "the",
                               zapverb);
 #else
@@ -5096,7 +5414,7 @@ short exploding_wand_typ;
         if (new_doormask >= 0) { /* door gets broken */
             if (*in_rooms(x, y, SHOPBASE)) {
                 if (type >= 0) {
-                    add_damage(x, y, 400L);
+                    add_damage(x, y, SHOP_DOOR_COST);
                     *shopdamage = TRUE;
                 } else /* caused by monster */
                     add_damage(x, y, 0L);
@@ -5130,12 +5448,9 @@ short exploding_wand_typ;
             pline("%s\81D", !Blind ? "\82Û\82í\82Á\82Æ\89\8c\82ª\82 \82ª\82Á\82½" : "\89\8c\82Ì\83v\83\93\82Æ\82¢\82¤\93õ\82¢\82ª\82µ\82½");
         }
     if ((mon = m_at(x, y)) != 0) {
-        /* Cannot use wakeup() which also angers the monster */
-        mon->msleeping = 0;
-        if (mon->m_ap_type)
-            seemimic(mon);
+        wakeup(mon, FALSE);
         if (type >= 0) {
-            setmangry(mon);
+            setmangry(mon, TRUE);
             if (mon->ispriest && *in_rooms(mon->mx, mon->my, TEMPLE))
                 ghod_hitsu(mon);
             if (mon->isshk && !*u.ushops)
@@ -5160,7 +5475,10 @@ register struct obj *obj; /* no texts here! */
         if (billable(&shkp, obj, objroom, FALSE)) {
             /* shop message says "you owe <shk> <$> for it!" so we need
                to precede that with a message explaining what "it" is */
+/*JP
             You("fracture %s %s.", s_suffix(shkname(shkp)), xname(obj));
+*/
+            You("%s\82Ì%s\82ð\89ó\82µ\82½\81D", shkname(shkp), xname(obj));
             breakobj(obj, x, y, TRUE, FALSE); /* charges for shop goods */
         }
     }
@@ -5259,175 +5577,255 @@ const char *const destroy_strings[][3] = {
     { "\82Î\82ç\82Î\82ç\82É\89ó\82ê\82Ä\94\9a\94­\82µ\82½", "\82Î\82ç\82Î\82ç\82É\89ó\82ê\82Ä\94\9a\94­\82µ\82½", "\8fñ\82Ì\94\9a\94­\82Å" },
 };
 
-void
-destroy_item(osym, dmgtyp)
-register int osym, dmgtyp;
+/* guts of destroy_item(), which ought to be called maybe_destroy_items();
+   caller must decide whether obj is eligible */
+STATIC_OVL void
+destroy_one_item(obj, osym, dmgtyp)
+struct obj *obj;
+int osym, dmgtyp;
 {
-    register struct obj *obj, *obj2;
-    int dmg, xresist, skip;
     long i, cnt, quan;
-    int dindx;
+    int dmg, xresist, skip, dindx;
     const char *mult;
     boolean physical_damage;
 
-    for (obj = invent; obj; obj = obj2) {
-        obj2 = obj->nobj;
-        physical_damage = FALSE;
-        if (obj->oclass != osym)
-            continue; /* test only objs of type osym */
-        if (obj->oartifact)
-            continue; /* don't destroy artifacts */
-        if (obj->in_use && obj->quan == 1L)
-            continue; /* not available */
-        xresist = skip = 0;
-        /* lint suppression */
-        dmg = dindx = 0;
-        quan = 0L;
-
-        switch (dmgtyp) {
-        case AD_COLD:
-            if (osym == POTION_CLASS && obj->otyp != POT_OIL) {
-                quan = obj->quan;
-                dindx = 0;
-                dmg = rnd(4);
-            } else
-                skip++;
-            break;
-        case AD_FIRE:
-            xresist = (Fire_resistance && obj->oclass != POTION_CLASS
-                       && obj->otyp != GLOB_OF_GREEN_SLIME);
+    physical_damage = FALSE;
+    xresist = skip = 0;
+    /* lint suppression */
+    dmg = dindx = 0;
+    quan = 0L;
 
-            if (obj->otyp == SCR_FIRE || obj->otyp == SPE_FIREBALL)
-                skip++;
-            if (obj->otyp == SPE_BOOK_OF_THE_DEAD) {
-                skip++;
-                if (!Blind)
+    switch (dmgtyp) {
+    case AD_COLD:
+        if (osym == POTION_CLASS && obj->otyp != POT_OIL) {
+            quan = obj->quan;
+            dindx = 0;
+            dmg = rnd(4);
+        } else
+            skip++;
+        break;
+    case AD_FIRE:
+        xresist = (Fire_resistance && obj->oclass != POTION_CLASS
+                   && obj->otyp != GLOB_OF_GREEN_SLIME);
+        if (obj->otyp == SCR_FIRE || obj->otyp == SPE_FIREBALL)
+            skip++;
+        if (obj->otyp == SPE_BOOK_OF_THE_DEAD) {
+            skip++;
+            if (!Blind)
 #if 0 /*JP:T*/
-                    pline("%s glows a strange %s, but remains intact.",
-                          The(xname(obj)), hcolor("dark red"));
+                pline("%s glows a strange %s, but remains intact.",
+                      The(xname(obj)), hcolor("dark red"));
 #else
-                    pline("%s\82Í\8aï\96­\82É%s\8bP\82¢\82½\82ª\89½\82à\95Ï\89»\82µ\82È\82©\82Á\82½\81D",
-                          xname(obj), jconj_adj(hcolor("\88Ã\8a\8c\90F\82Ì")));
+                pline("%s\82Í\8aï\96­\82É%s\8bP\82¢\82½\82ª\89½\82à\95Ï\89»\82µ\82È\82©\82Á\82½\81D",
+                      xname(obj), jconj_adj(hcolor("\88Ã\8a\8c\90F\82Ì")));
 #endif
-
+        }
+        quan = obj->quan;
+        switch (osym) {
+        case POTION_CLASS:
+            dindx = (obj->otyp != POT_OIL) ? 1 : 2;
+            dmg = rnd(6);
+            break;
+        case SCROLL_CLASS:
+            dindx = 3;
+            dmg = 1;
+            break;
+        case SPBOOK_CLASS:
+            dindx = 4;
+            dmg = 1;
+            break;
+        case FOOD_CLASS:
+            if (obj->otyp == GLOB_OF_GREEN_SLIME) {
+                dindx = 1; /* boil and explode */
+                dmg = (obj->owt + 19) / 20;
+            } else {
+                skip++;
             }
-            quan = obj->quan;
-            switch (osym) {
-            case POTION_CLASS:
-                dindx = (obj->otyp != POT_OIL) ? 1 : 2;
-                dmg = rnd(6);
-                break;
-            case SCROLL_CLASS:
-                dindx = 3;
-                dmg = 1;
-                break;
-            case SPBOOK_CLASS:
-                dindx = 4;
-                dmg = 1;
-                break;
-            case FOOD_CLASS:
-                if (obj->otyp == GLOB_OF_GREEN_SLIME) {
-                    dindx = obj->owt / 20;
-                    dmg = 1;
-                } else {
-                    skip++;
-                }
-                break;
-            default:
+            break;
+        default:
+            skip++;
+            break;
+        }
+        break;
+    case AD_ELEC:
+        xresist = (Shock_resistance && obj->oclass != RING_CLASS);
+        quan = obj->quan;
+        switch (osym) {
+        case RING_CLASS:
+            if (obj->otyp == RIN_SHOCK_RESISTANCE) {
                 skip++;
                 break;
             }
+            dindx = 5;
+            dmg = 0;
             break;
-        case AD_ELEC:
-            xresist = (Shock_resistance && obj->oclass != RING_CLASS);
-            quan = obj->quan;
-            switch (osym) {
-            case RING_CLASS:
-                if (obj->otyp == RIN_SHOCK_RESISTANCE) {
-                    skip++;
-                    break;
-                }
-                dindx = 5;
-                dmg = 0;
-                break;
-            case WAND_CLASS:
-                if (obj->otyp == WAN_LIGHTNING) {
-                    skip++;
-                    break;
-                }
-#if 0
-                if (obj == current_wand) {  skip++;  break;  }
-#endif
-                dindx = 6;
-                dmg = rnd(10);
-                break;
-            default:
+        case WAND_CLASS:
+            if (obj->otyp == WAN_LIGHTNING) {
                 skip++;
                 break;
             }
+#if 0
+            if (obj == current_wand) {  skip++;  break;  }
+#endif
+            dindx = 6;
+            dmg = rnd(10);
             break;
         default:
             skip++;
             break;
         }
-        if (!skip) {
-            if (obj->in_use)
-                --quan; /* one will be used up elsewhere */
-            for (i = cnt = 0L; i < quan; i++)
-                if (!rn2(3))
-                    cnt++;
+        break;
+    default:
+        skip++;
+        break;
+    }
 
-            if (!cnt)
-                continue;
-#if 0 /*JP*/
-            mult = (cnt == quan)
-                       ? (quan > 1) ? "All of your " : "Your"
-                       : (cnt == 1L) ? "One of your" : "Some of your";
-#else
-            mult = (cnt == quan)
-                       ? ""
-                       : (cnt == 1L) ? "\82Ì\82Ð\82Æ\82Â" : "\82Ì\82¢\82­\82Â\82©";
-#endif
-#if 0 /*JP*/
-            pline("%s %s %s!", mult, xname(obj),
-                  destroy_strings[dindx][(cnt > 1L)]);
+    if (!skip) {
+        if (obj->in_use)
+            --quan; /* one will be used up elsewhere */
+        for (i = cnt = 0L; i < quan; i++)
+            if (!rn2(3))
+                cnt++;
+
+        if (!cnt)
+            return;
+#if 0 /*JP:T*/
+        mult = (cnt == 1L)
+                ? ((quan == 1L) ? "Your"                         /* 1 of 1 */
+                                : "One of your")                 /* 1 of N */
+                : ((cnt < quan) ? "Some of your"                 /* n of N */
+                                : (quan == 2L) ? "Both of your"  /* 2 of 2 */
+                                               : "All of your"); /* N of N */
+        pline("%s %s %s!", mult, xname(obj),
+              destroy_strings[dindx][(cnt > 1L)]);
 #else
-            pline("\82 \82È\82½\82Ì%s%s\82Í%s\81I", xname(obj), mult, 
-                  destroy_strings[dindx][(cnt > 1L)]);
+        mult = (cnt == 1L)
+                ? ((quan == 1L) ? ""                             /* 1 of 1 */
+                                : "\82Ì\82Ð\82Æ\82Â")                    /* 1 of N */
+                : ((cnt < quan) ? "\82Ì\82¢\82­\82Â\82©"                   /* n of N */
+                                : "\82Ì\91S\82Ä");                     /* N of N */
+        pline("\82 \82È\82½\82Ì%s%s\82Í%s\81I", xname(obj), mult, 
+              destroy_strings[dindx][(cnt > 1L)]);
 #endif
-            if (osym == POTION_CLASS && dmgtyp != AD_COLD) {
-                if (!breathless(youmonst.data) || haseyes(youmonst.data))
-                    potionbreathe(obj);
-            }
-            if (obj->owornmask) {
-                if (obj->owornmask & W_RING) /* ring being worn */
-                    Ring_gone(obj);
-                else
-                    setnotworn(obj);
-            }
-            if (obj == current_wand)
-                current_wand = 0; /* destroyed */
-            for (i = 0; i < cnt; i++)
-                useup(obj);
-            if (dmg) {
-                if (xresist)
-/*JP
-                    You("aren't hurt!");
-*/
-                    You("\8f\9d\82Â\82©\82È\82¢\81I");
-                else {
-                    const char *how = destroy_strings[dindx][2];
-                    boolean one = (cnt == 1L);
-
-                    if (physical_damage)
-                        dmg = Maybe_Half_Phys(dmg);
-                    losehp(dmg, one ? how : (const char *) makeplural(how),
-                           one ? KILLED_BY_AN : KILLED_BY);
-                    exercise(A_STR, FALSE);
-                }
+        if (osym == POTION_CLASS && dmgtyp != AD_COLD) {
+            if (!breathless(youmonst.data) || haseyes(youmonst.data))
+                potionbreathe(obj);
+        }
+        if (obj->owornmask) {
+            if (obj->owornmask & W_RING) /* ring being worn */
+                Ring_gone(obj);
+            else
+                setnotworn(obj);
+        }
+        if (obj == current_wand)
+            current_wand = 0; /* destroyed */
+        for (i = 0; i < cnt; i++)
+            useup(obj);
+        if (dmg) {
+            if (xresist) {
+/*JP
+                You("aren't hurt!");
+*/
+                You("\8f\9d\82Â\82©\82È\82¢\81I");
+            } else {
+                const char *how = destroy_strings[dindx][2];
+                boolean one = (cnt == 1L);
+
+                if (dmgtyp == AD_FIRE && osym == FOOD_CLASS)
+/*JP
+                    how = "exploding glob of slime";
+*/
+                    how = "\83X\83\89\83C\83\80\82Ì\82Ë\82Î\82Ë\82Î\82Ì\94\9a\94­\82Å";
+                if (physical_damage)
+                    dmg = Maybe_Half_Phys(dmg);
+                losehp(dmg, one ? how : (const char *) makeplural(how),
+                       one ? KILLED_BY_AN : KILLED_BY);
+                exercise(A_STR, FALSE);
             }
         }
     }
+}
+
+/* target items of specified class for possible destruction */
+void
+destroy_item(osym, dmgtyp)
+int osym, dmgtyp;
+{
+    register struct obj *obj;
+    int i, deferral_indx = 0;
+    /* 1+52+1: try to handle a full inventory; it doesn't matter if
+      inventory actually has more, even if everything should be deferred */
+    unsigned short deferrals[1 + 52 + 1]; /* +1: gold, overflow */
+
+    (void) memset((genericptr_t) deferrals, 0, sizeof deferrals);
+    /*
+     * Sometimes destroying an item can change inventory aside from
+     * the item itself (cited case was a potion of unholy water; when
+     * boiled, potionbreathe() caused hero to transform into were-beast
+     * form and that resulted in dropping or destroying some worn armor).
+     *
+     * Unlike other uses of the object bybass mechanism, destroy_item()
+     * can be called multiple times for the same event.  So we have to
+     * explicitly clear it before each use and hope no other section of
+     * code expects it to retain previous value.
+     *
+     * Destruction of a ring of levitation or form change which pushes
+     * off levitation boots could drop hero onto a fire trap that
+     * could destroy other items and we'll get called recursively.  Or
+     * onto a trap which transports hero elsewhere, which won't disrupt
+     * traversal but could yield message sequencing issues.  So we
+     * defer handling such things until after rest of inventory has
+     * been processed.  If some other combination of items and events
+     * triggers a recursive call, rest of inventory after the triggering
+     * item will be skipped by the outer call since the inner one will
+     * have set the bypass bits of the whole list.
+     *
+     * [Unfortunately, death while poly'd into flyer and subsequent
+     * rehumanization could also drop hero onto a trap, and there's no
+     * straightforward way to defer that.  Things could be improved by
+     * redoing this to use two passes, first to collect a list or array
+     * of o_id and quantity of what is targetted for destruction,
+     * second pass to handle the destruction.]
+     */
+    bypass_objlist(invent, FALSE); /* clear bypass bit for invent */
+
+    while ((obj = nxt_unbypassed_obj(invent)) != 0) {
+        if (obj->oclass != osym)
+            continue; /* test only objs of type osym */
+        if (obj->oartifact)
+            continue; /* don't destroy artifacts */
+        if (obj->in_use && obj->quan == 1L)
+            continue; /* not available */
+
+        /* if loss of this item might dump us onto a trap, hold off
+           until later because potential recursive destroy_item() will
+           result in setting bypass bits on whole chain--we would skip
+           the rest as already processed once control returns here */
+        if (deferral_indx < SIZE(deferrals)
+            && ((obj->owornmask != 0L
+                 && (objects[obj->otyp].oc_oprop == LEVITATION
+                     || objects[obj->otyp].oc_oprop == FLYING))
+                /* destroyed wands and potions of polymorph don't trigger
+                   polymorph so don't need to be deferred */
+                || (obj->otyp == POT_WATER && u.ulycn >= LOW_PM
+                    && (Upolyd ? obj->blessed : obj->cursed)))) {
+            deferrals[deferral_indx++] = obj->o_id;
+            continue;
+        }
+        /* obj is eligible; maybe destroy it */
+        destroy_one_item(obj, osym, dmgtyp);
+    }
+    /* if we saved some items for later (most likely just a worn ring
+       of levitation) and they're still in inventory, handle them now */
+    for (i = 0; i < deferral_indx; ++i) {
+        /* note: obj->nobj is only referenced when obj is skipped;
+           having obj be dropped or destroyed won't affect traversal */
+        for (obj = invent; obj; obj = obj->nobj)
+            if (obj->o_id == deferrals[i]) {
+                destroy_one_item(obj, osym, dmgtyp);
+                break;
+            }
+    }
     return;
 }
 
@@ -5436,7 +5834,7 @@ destroy_mitem(mtmp, osym, dmgtyp)
 struct monst *mtmp;
 int osym, dmgtyp;
 {
-    struct obj *obj, *obj2;
+    struct obj *obj;
     int skip, tmp = 0;
     long i, cnt, quan;
     int dindx;
@@ -5448,8 +5846,11 @@ int osym, dmgtyp;
     }
 
     vis = canseemon(mtmp);
-    for (obj = mtmp->minvent; obj; obj = obj2) {
-        obj2 = obj->nobj;
+
+    /* see destroy_item(); object destruction could disrupt inventory list */
+    bypass_objlist(mtmp->minvent, FALSE); /* clear bypass bit for minvent */
+
+    while ((obj = nxt_unbypassed_obj(mtmp->minvent)) != 0) {
         if (obj->oclass != osym)
             continue; /* test only objs of type osym */
         skip = 0;
@@ -5471,7 +5872,7 @@ int osym, dmgtyp;
             if (obj->otyp == SPE_BOOK_OF_THE_DEAD) {
                 skip++;
                 if (vis)
-#if 0 /*JP*/
+#if 0 /*JP:T*/
                     pline("%s glows a strange %s, but remains intact.",
                           The(distant_name(obj, xname)), hcolor("dark red"));
 #else
@@ -5495,8 +5896,8 @@ int osym, dmgtyp;
                 break;
             case FOOD_CLASS:
                 if (obj->otyp == GLOB_OF_GREEN_SLIME) {
-                    dindx = obj->owt / 20;
-                    tmp++;
+                    dindx = 1; /* boil and explode */
+                    tmp += (obj->owt + 19) / 20;
                 } else {
                     skip++;
                 }
@@ -5541,7 +5942,7 @@ int osym, dmgtyp;
             if (!cnt)
                 continue;
             if (vis)
-#if 0 /*JP*/
+#if 0 /*JP:T*/
                 pline("%s%s %s!",
                       (cnt == obj->quan) ? "" : (cnt > 1L) ? "Some of "
                                                            : "One of ",
@@ -5570,6 +5971,11 @@ int damage, tell;
     int resisted;
     int alev, dlev;
 
+    /* fake players always pass resistance test against Conflict
+       (this doesn't guarantee that they're never affected by it) */
+    if (oclass == RING_CLASS && !damage && !tell && is_mplayer(mtmp->data))
+        return 1;
+
     /* attack level */
     switch (oclass) {
     case WAND_CLASS:
@@ -5615,7 +6021,7 @@ int damage, tell;
 
     if (damage) {
         mtmp->mhp -= damage;
-        if (mtmp->mhp < 1) {
+        if (DEADMONSTER(mtmp)) {
             if (m_using)
                 monkilled(mtmp, "", AD_RBRE);
             else
@@ -5688,7 +6094,8 @@ int triesleft;
 void
 makewish()
 {
-    char buf[BUFSZ], promptbuf[BUFSZ];
+    char buf[BUFSZ] = DUMMY;
+    char promptbuf[BUFSZ];
     struct obj *otmp, nothing;
     int tries = 0;
 
@@ -5699,7 +6106,7 @@ makewish()
         You("may wish for an object.");
 */
         You("\96]\82Ý\82Ì\82à\82Ì\82ð\8eè\82É\93ü\82ê\82ç\82ê\82é\81D");
-retry:
+ retry:
 /*JP
     Strcpy(promptbuf, "For what do you wish");
 */
@@ -5719,6 +6126,7 @@ retry:
         buf[0] = '\0';
     } else if (!strcmpi(buf, "help")) {
         wishcmdassist(MAXWISHTRY - tries);
+        buf[0] = '\0'; /* for EDIT_GETLIN */
         goto retry;
     }
     /*