OSDN Git Service

update year to 2023
[jnethack/source.git] / src / timeout.c
index 9246fd9..21295d2 100644 (file)
@@ -1,10 +1,11 @@
-/* NetHack 3.6 timeout.c       $NHDT-Date: 1446861771 2015/11/07 02:02:51 $  $NHDT-Branch: master $:$NHDT-Revision: 1.63 $ */
+/* NetHack 3.6 timeout.c       $NHDT-Date: 1573290422 2019/11/09 09:07:02 $  $NHDT-Branch: NetHack-3.6 $:$NHDT-Revision: 1.93 $ */
 /* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */
+/*-Copyright (c) Robert Patrick Rankin, 2018. */
 /* 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-2023            */
 /* JNetHack may be freely redistributed.  See license for details. */
 
 #include "hack.h"
 STATIC_DCL void NDECL(stoned_dialogue);
 STATIC_DCL void NDECL(vomiting_dialogue);
 STATIC_DCL void NDECL(choke_dialogue);
+STATIC_DCL void NDECL(levitation_dialogue);
 STATIC_DCL void NDECL(slime_dialogue);
+STATIC_DCL void FDECL(slimed_to_death, (struct kinfo *));
+STATIC_DCL void NDECL(phaze_dialogue);
+STATIC_DCL void FDECL(done_timeout, (int, int));
 STATIC_DCL void NDECL(slip_or_trip);
 STATIC_DCL void FDECL(see_lamp_flicker, (struct obj *, const char *));
 STATIC_DCL void FDECL(lantern_message, (struct obj *));
 STATIC_DCL void FDECL(cleanup_burn, (ANY_P *, long));
 
+/* used by wizard mode #timeout and #wizintrinsic; order by 'interest'
+   for timeout countdown, where most won't occur in normal play */
+/*JP \83E\83B\83U\81[\83h\83\82\81[\83h\82Å\82µ\82©\8eg\82í\82ê\82È\82¢\82æ\82¤\82È\82Ì\82Å\96|\96ó\82µ\82È\82¢ */
+const struct propname {
+    int prop_num;
+    const char *prop_name;
+} propertynames[] = {
+    { INVULNERABLE, "invulnerable" },
+    { STONED, "petrifying" },
+    { SLIMED, "becoming slime" },
+    { STRANGLED, "strangling" },
+    { SICK, "fatally sick" },
+    { STUNNED, "stunned" },
+    { CONFUSION, "confused" },
+    { HALLUC, "hallucinating" },
+    { BLINDED, "blinded" },
+    { DEAF, "deafness" },
+    { VOMITING, "vomiting" },
+    { GLIB, "slippery fingers" },
+    { WOUNDED_LEGS, "wounded legs" },
+    { SLEEPY, "sleepy" },
+    { TELEPORT, "teleporting" },
+    { POLYMORPH, "polymorphing" },
+    { LEVITATION, "levitating" },
+    { FAST, "very fast" }, /* timed 'FAST' is very fast */
+    { CLAIRVOYANT, "clairvoyant" },
+    { DETECT_MONSTERS, "monster detection" },
+    { SEE_INVIS, "see invisible" },
+    { INVIS, "invisible" },
+    /* properties beyond here don't have timed values during normal play,
+       so there's not much point in trying to order them sensibly;
+       they're either on or off based on equipment, role, actions, &c */
+    { FIRE_RES, "fire resistance" },
+    { COLD_RES, "cold resistance" },
+    { SLEEP_RES, "sleep resistance" },
+    { DISINT_RES, "disintegration resistance" },
+    { SHOCK_RES, "shock resistance" },
+    { POISON_RES, "poison resistance" },
+    { ACID_RES, "acid resistance" },
+    { STONE_RES, "stoning resistance" },
+    { DRAIN_RES, "drain resistance" },
+    { SICK_RES, "sickness resistance" },
+    { ANTIMAGIC, "magic resistance" },
+    { HALLUC_RES, "hallucination resistance" },
+    { FUMBLING, "fumbling" },
+    { HUNGER, "voracious hunger" },
+    { TELEPAT, "telepathic" },
+    { WARNING, "warning" },
+    { WARN_OF_MON, "warn: monster type or class" },
+    { WARN_UNDEAD, "warn: undead" },
+    { SEARCHING, "searching" },
+    { INFRAVISION, "infravision" },
+    { ADORNED, "adorned (+/- Cha)" },
+    { DISPLACED, "displaced" },
+    { STEALTH, "stealthy" },
+    { AGGRAVATE_MONSTER, "monster aggravation" },
+    { CONFLICT, "conflict" },
+    { JUMPING, "jumping" },
+    { TELEPORT_CONTROL, "teleport control" },
+    { FLYING, "flying" },
+    { WWALKING, "water walking" },
+    { SWIMMING, "swimming" },
+    { MAGICAL_BREATHING, "magical breathing" },
+    { PASSES_WALLS, "pass thru walls" },
+    { SLOW_DIGESTION, "slow digestion" },
+    { HALF_SPDAM, "half spell damage" },
+    { HALF_PHDAM, "half physical damage" },
+    { REGENERATION, "HP regeneration" },
+    { ENERGY_REGENERATION, "energy regeneration" },
+    { PROTECTION, "extra protection" },
+    { PROT_FROM_SHAPE_CHANGERS, "protection from shape changers" },
+    { POLYMORPH_CONTROL, "polymorph control" },
+    { UNCHANGING, "unchanging" },
+    { REFLECTING, "reflecting" },
+    { FREE_ACTION, "free action" },
+    { FIXED_ABIL, "fixed abilities" },
+    { LIFESAVED, "life will be saved" },
+    {  0, 0 },
+};
+
 /* He is being petrified - dialogue by inmet!tower */
 static NEARDATA const char *const stoned_texts[] = {
-#if 0 /*JP*/
+#if 0 /*JP:T*/
     "You are slowing down.",            /* 5 */
     "Your limbs are stiffening.",       /* 4 */
     "Your limbs have turned to stone.", /* 3 */
@@ -45,7 +130,7 @@ stoned_dialogue()
         char buf[BUFSZ];
 
         Strcpy(buf, stoned_texts[SIZE(stoned_texts) - i]);
-#if 0 /*JP*/
+#if 0 /*JP:T*/
         if (nolimbs(youmonst.data) && strstri(buf, "limbs"))
             (void) strsubst(buf, "limbs", "extremities");
 #else
@@ -76,6 +161,18 @@ stoned_dialogue()
 */
         multi_reason = "\90Î\89»\82µ\82Â\82Â\82 \82é\8e\9e\82É";
         nomovemsg = You_can_move_again; /* not unconscious */
+        /* "your limbs have turned to stone" so terminate wounded legs */
+        if (Wounded_legs && !u.usteed)
+            heal_legs(2);
+        break;
+    case 2: /* turned to stone */
+        if ((HDeaf & TIMEOUT) > 0L && (HDeaf & TIMEOUT) < 5L)
+            set_itimeout(&HDeaf, 5L); /* avoid Hear_again at tail end */
+        /* if also vomiting or turning into slime, stop those (no messages) */
+        if (Vomiting)
+            make_vomiting(0L, FALSE);
+        if (Slimed)
+            make_slimed(0L, (char *) 0);
         break;
     default:
         break;
@@ -83,20 +180,20 @@ stoned_dialogue()
     exercise(A_DEX, FALSE);
 }
 
-/* He is getting sicker and sicker prior to vomiting */
+/* hero is getting sicker and sicker prior to vomiting */
 static NEARDATA const char *const vomiting_texts[] = {
-#if 0 /*JP*/
+#if 0 /*JP:T*/
     "are feeling mildly nauseated.", /* 14 */
     "feel slightly confused.",       /* 11 */
     "can't seem to think straight.", /* 8 */
     "feel incredibly sick.",         /* 5 */
-    "suddenly vomit!"                /* 2 */
+    "are about to vomit."            /* 2 */
 #else
     "\82¿\82å\82Á\82Æ\93f\82«\8bC\82ª\82µ\82½\81D",        /* 14 */
     "\8f­\82µ\8d¬\97\90\82µ\82½\81D",                /* 11 */
     "\82Ü\82Æ\82à\82É\8ev\8dl\82Å\82«\82È\82­\82È\82Á\82½\81D",  /* 8 */
     "\82Æ\82Ä\82à\8bC\95ª\82ª\88«\82­\82È\82Á\82½\81D",      /* 5 */
-    "\93Ë\91R\9aq\93f\82µ\82½\81I"                 /* 2 */
+    "\93Ë\91R\9aq\93f\82µ\82½\81D"                 /* 2 */
 #endif
 };
 
@@ -135,14 +232,35 @@ vomiting_dialogue()
         txt = vomiting_texts[4];
         if (cantvomit(youmonst.data))
 /*JP
-            txt = "gag uncontrolably.";
+            txt = "gag uncontrollably.";
 */
             txt = "\8bC\95ª\82Ì\88«\82³\82ª\97}\82¦\82ç\82ê\82È\82­\82È\82Á\82½\81D";
+        else if (Hallucination)
+            /* "hurl" is short for "hurl chunks" which is slang for
+               relatively violent vomiting... */
+/*JP
+            txt = "are about to hurl!";
+*/
+            txt = "\83\8a\83o\81[\83X\90¡\91O\82¾\81I";
         break;
     case 0:
         stop_occupation();
-        if (!cantvomit(youmonst.data))
+        if (!cantvomit(youmonst.data)) {
             morehungry(20);
+            /* case 2 used to be "You suddenly vomit!" but it wasn't sudden
+               since you've just been through the earlier messages of the
+               countdown, and it was still possible to move around between
+               that message and "You can move again." (from vomit()'s
+               nomul(-2)) with no intervening message; give one here to
+               have more specific point at which hero became unable to move
+               [vomit() issues its own message for the cantvomit() case
+               and for the FAINTING-or-worse case where stomach is empty] */
+            if (u.uhs < FAINTING)
+/*JP
+                You("%s!", !Hallucination ? "vomit" : "hurl chunks");
+*/
+                You("%s\81I", !Hallucination ? "\93f\82¢\82½" : "\83\8a\83o\81[\83X\82µ\82½");
+        }
         vomit();
         break;
     default:
@@ -154,9 +272,12 @@ vomiting_dialogue()
 }
 
 static NEARDATA const char *const choke_texts[] = {
-#if 0 /*JP*/
-    "You find it hard to breathe.", "You're gasping for air.",
-    "You can no longer breathe.", "You're turning %s.", "You suffocate."
+#if 0 /*JP:T*/
+    "You find it hard to breathe.",
+    "You're gasping for air.",
+    "You can no longer breathe.",
+    "You're turning %s.",
+    "You suffocate."
 #else
     "\82 \82È\82½\82Í\8cÄ\8bz\82ª\8d¢\93ï\82É\82È\82Á\82½\81D",
     "\82 \82È\82½\82Í\8bê\82µ\82­\82Ä\82 \82¦\82¢\82¾\81D",
@@ -167,10 +288,11 @@ static NEARDATA const char *const choke_texts[] = {
 };
 
 static NEARDATA const char *const choke_texts2[] = {
-#if 0 /*JP*/
+#if 0 /*JP:T*/
     "Your %s is becoming constricted.",
     "Your blood is having trouble reaching your brain.",
-    "The pressure on your %s increases.", "Your consciousness is fading.",
+    "The pressure on your %s increases.",
+    "Your consciousness is fading.",
     "You suffocate."
 #else
     "\82 \82È\82½\82Ì%s\82Í\8di\82ß\82Â\82¯\82ç\82ê\82½\81D",
@@ -196,7 +318,7 @@ choke_dialogue()
 /*JP
                 pline(str, hcolor(NH_BLUE));
 */
-                pline(str, jconj_adj(hcolor(NH_BLUE)));
+                pline(str, hcolor_adv(NH_BLUE));
             else
                 pline1(str);
         }
@@ -204,8 +326,47 @@ choke_dialogue()
     exercise(A_STR, FALSE);
 }
 
+static NEARDATA const char *const levi_texts[] = {
+#if 0 /*JP*//* %s\82Í\8eg\82í\82È\82¢ */
+    "You float slightly lower.",
+    "You wobble unsteadily %s the %s."
+#else
+    "\82 \82È\82½\82Ì\95\82\82«\8bï\8d\87\82Í\8f­\82µ\92á\82­\82È\82Á\82½\81D",
+    "\82 \82È\82½\82Í\82®\82ç\82®\82ç\97h\82ê\82Í\82\82ß\82½\81D"
+#endif
+};
+
+STATIC_OVL void
+levitation_dialogue()
+{
+    /* -1 because the last message comes via float_down() */
+    long i = (((HLevitation & TIMEOUT) - 1L) / 2L);
+
+    if (ELevitation)
+        return;
+
+    if (!ACCESSIBLE(levl[u.ux][u.uy].typ)
+        && !is_pool_or_lava(u.ux,u.uy))
+        return;
+
+    if (((HLevitation & TIMEOUT) % 2L) && i > 0L && i <= SIZE(levi_texts)) {
+        const char *s = levi_texts[SIZE(levi_texts) - i];
+
+#if 0 /*JP*//* %s \82Í\8eg\82Á\82Ä\82¢\82È\82¢\82Ì\82Å\92P\8f\83\89» */
+        if (index(s, '%')) {
+            boolean danger = (is_pool_or_lava(u.ux, u.uy)
+                              && !Is_waterlevel(&u.uz));
+
+            pline(s, danger ? "over" : "in",
+                  danger ? surface(u.ux, u.uy) : "air");
+        } else
+#endif
+            pline1(s);
+    }
+}
+
 static NEARDATA const char *const slime_texts[] = {
-#if 0 /*JP*/
+#if 0 /*JP:T*/
     "You are turning a little %s.",   /* 5 */
     "Your limbs are getting oozy.",   /* 4 */
     "Your skin begins to peel away.", /* 3 */
@@ -225,11 +386,18 @@ slime_dialogue()
 {
     register long i = (Slimed & TIMEOUT) / 2L;
 
+    if (i == 1L) {
+        /* display as green slime during "You have become green slime."
+           but don't worry about not being able to see self; if already
+           mimicking something else at the time, implicitly be revealed */
+        youmonst.m_ap_type = M_AP_MONSTER;
+        youmonst.mappearance = PM_GREEN_SLIME;
+    }
     if (((Slimed & TIMEOUT) % 2L) && i >= 0L && i < SIZE(slime_texts)) {
         char buf[BUFSZ];
 
         Strcpy(buf, slime_texts[SIZE(slime_texts) - i - 1L]);
-#if 0 /*JP*/
+#if 0 /*JP:T*/
         if (nolimbs(youmonst.data) && strstri(buf, "limbs"))
             (void) strsubst(buf, "limbs", "extremities");
 #else
@@ -243,7 +411,7 @@ slime_dialogue()
 /*JP
                     pline(buf, hcolor(NH_GREEN));
 */
-                    pline(buf, jconj_adj(hcolor(NH_GREEN)));
+                    pline(buf, hcolor_adv(NH_GREEN));
             } else
                 pline(buf,
 /*JP
@@ -253,12 +421,24 @@ slime_dialogue()
         } else
             pline1(buf);
     }
-    if (i == 3L) {  /* limbs becoming oozy */
+
+    switch (i) {
+    case 3L:  /* limbs becoming oozy */
         HFast = 0L; /* lose intrinsic speed */
         if (!Popeye(SLIMED))
             stop_occupation();
         if (multi > 0)
             nomul(0);
+        break;
+    case 2L: /* skin begins to peel */
+        if ((HDeaf & TIMEOUT) > 0L && (HDeaf & TIMEOUT) < 5L)
+            set_itimeout(&HDeaf, 5L); /* avoid Hear_again at tail end */
+        break;
+    case 1L: /* turning into slime */
+        /* if also turning to stone, stop doing that (no message) */
+        if (Stoned)
+            make_stoned(0L, (char *) 0, KILLED_BY_AN, (char *) 0);
+        break;
     }
     exercise(A_DEX, FALSE);
 }
@@ -270,8 +450,136 @@ burn_away_slime()
 /*JP
         make_slimed(0L, "The slime that covers you is burned away!");
 */
-        pline("\82 \82È\82½\82ð\95¢\82Á\82Ä\82¢\82½\83X\83\89\83C\83\80\82Í\8fÄ\82¯\97\8e\82¿\82½\81I");
+        make_slimed(0L, "\82 \82È\82½\82ð\95¢\82Á\82Ä\82¢\82½\83X\83\89\83C\83\80\82Í\8fÄ\82¯\97\8e\82¿\82½\81I");
+    }
+}
+
+/* countdown timer for turning into green slime has run out; kill our hero */
+STATIC_OVL void
+slimed_to_death(kptr)
+struct kinfo *kptr;
+{
+    uchar save_mvflags;
+
+    /* redundant: polymon() cures sliming when polying into green slime */
+    if (Upolyd && youmonst.data == &mons[PM_GREEN_SLIME]) {
+        dealloc_killer(kptr);
+        return;
+    }
+    /* more sure killer reason is set up */
+    if (kptr && kptr->name[0]) {
+        killer.format = kptr->format;
+        Strcpy(killer.name, kptr->name);
+    } else {
+        killer.format = NO_KILLER_PREFIX;
+/*JP
+        Strcpy(killer.name, "turned into green slime");
+*/
+        Strcpy(killer.name, "\97Î\83X\83\89\83C\83\80\82É\82È\82Á\82½");
+    }
+    dealloc_killer(kptr);
+
+    /*
+     * Polymorph into a green slime, which might destroy some worn armor
+     * (potentially affecting bones) and dismount from steed.
+     * Can't be Unchanging; wouldn't have turned into slime if we were.
+     * Despite lack of Unchanging, neither done() nor savelife() calls
+     * rehumanize() if hero dies while polymorphed.
+     * polymon() undoes the slime countdown's mimick-green-slime hack
+     * but does not perform polyself()'s light source bookkeeping.
+     * No longer need to manually increment uconduct.polyselfs to reflect
+     * [formerly implicit] change of form; polymon() takes care of that.
+     * Temporarily ungenocide if necessary.
+     */
+    if (emits_light(youmonst.data))
+        del_light_source(LS_MONSTER, monst_to_any(&youmonst));
+    save_mvflags = mvitals[PM_GREEN_SLIME].mvflags;
+    mvitals[PM_GREEN_SLIME].mvflags = save_mvflags & ~G_GENOD;
+    /* become a green slime; also resets youmonst.m_ap_type+.mappearance */
+    (void) polymon(PM_GREEN_SLIME);
+    mvitals[PM_GREEN_SLIME].mvflags = save_mvflags;
+    done_timeout(TURNED_SLIME, SLIMED);
+
+    /* life-saved; even so, hero still has turned into green slime;
+       player may have genocided green slimes after being infected */
+    if ((mvitals[PM_GREEN_SLIME].mvflags & G_GENOD) != 0) {
+        char slimebuf[BUFSZ];
+
+        killer.format = KILLED_BY;
+/*JP
+        Strcpy(killer.name, "slimicide");
+*/
+        Strcpy(killer.name, "\83X\83\89\83C\83\80\8bs\8eE");
+        /* vary the message depending upon whether life-save was due to
+           amulet or due to declining to die in explore or wizard mode */
+/*JP
+        Strcpy(slimebuf, "green slime has been genocided...");
+*/
+        Strcpy(slimebuf, "\97Î\83X\83\89\83C\83\80\82Í\8bs\8eE\82³\82ê\82Ü\82µ\82½\81D\81D\81D");
+        if (iflags.last_msg == PLNMSG_OK_DONT_DIE)
+            /* follows "OK, so you don't die." and arg is second sentence */
+/*JP
+            pline("Yes, you do.  %s", upstart(slimebuf));
+*/
+            pline("\82Í\82¢\81C\82»\82¤\82Å\82·\81D%s", upstart(slimebuf));
+        else
+            /* follows "The medallion crumbles to dust." */
+/*JP
+            pline("Unfortunately, %s", slimebuf);
+*/
+            pline("\82´\82ñ\82Ë\82ñ\82Å\82·\82ª%s", slimebuf);
+        /* die again; no possibility of amulet this time */
+        done(GENOCIDED); /* [should it be done_timeout(GENOCIDED, SLIMED)?] */
+        /* could be life-saved again (only in explore or wizard mode)
+           but green slimes are gone; just stay in current form */
     }
+    return;
+}
+
+/* Intrinsic Passes_walls is temporary when your god is trying to fix
+   all troubles and then TROUBLE_STUCK_IN_WALL calls safe_teleds() but
+   it can't find anywhere to place you.  If that happens you get a small
+   value for (HPasses_walls & TIMEOUT) to move somewhere yourself.
+   Message given is "you feel much slimmer" as a joke hint that you can
+   move between things which are closely packed--like the substance of
+   solid rock! */
+static NEARDATA const char *const phaze_texts[] = {
+#if 0 /*JP:T*/
+    "You start to feel bloated.",
+    "You are feeling rather flabby.",
+#else
+    "\82 \82È\82½\82Í\96c\82ç\82Ý\82Í\82\82ß\82½\8bC\82ª\82·\82é\81D",
+    "\82 \82È\82½\82Í\8f­\82µ\82½\82é\82ñ\82¾\8bC\82ª\82·\82é\81D",
+#endif
+};
+
+STATIC_OVL void
+phaze_dialogue()
+{
+    long i = ((HPasses_walls & TIMEOUT) / 2L);
+
+    if (EPasses_walls || (HPasses_walls & ~TIMEOUT))
+        return;
+
+    if (((HPasses_walls & TIMEOUT) % 2L) && i > 0L && i <= SIZE(phaze_texts))
+        pline1(phaze_texts[SIZE(phaze_texts) - i]);
+}
+
+/* when a status timeout is fatal, keep the status line indicator shown
+   during end of game rundown (and potential dumplog);
+   timeout has already counted down to 0 by the time we get here */
+STATIC_OVL void
+done_timeout(how, which)
+int how, which;
+{
+    long *intrinsic_p = &u.uprops[which].intrinsic;
+
+    *intrinsic_p |= I_SPECIAL; /* affects final disclosure */
+    done(how);
+
+    /* life-saved */
+    *intrinsic_p &= ~I_SPECIAL;
+    context.botl = TRUE;
 }
 
 void
@@ -279,6 +587,7 @@ nh_timeout()
 {
     register struct prop *upp;
     struct kinfo *kptr;
+    boolean was_flying;
     int sleeptime;
     int m_idx;
     int baseluck = (flags.moonphase == FULL_MOON) ? 1 : 0;
@@ -287,7 +596,7 @@ nh_timeout()
         baseluck -= 1;
 
     if (u.uluck != baseluck
-        && moves % (u.uhave.amulet || u.ugangr ? 300 : 600) == 0) {
+        && moves % ((u.uhave.amulet || u.ugangr) ? 300 : 600) == 0) {
         /* Cursed luckstones stop bad luck from timing out; blessed luckstones
          * stop good luck from timing out; normal luckstones stop both;
          * neither is stopped if you don't have a luckstone.
@@ -311,9 +620,15 @@ nh_timeout()
         vomiting_dialogue();
     if (Strangled)
         choke_dialogue();
+    if (HLevitation & TIMEOUT)
+        levitation_dialogue();
+    if (HPasses_walls & TIMEOUT)
+        phaze_dialogue();
     if (u.mtimedone && !--u.mtimedone) {
         if (Unchanging)
             u.mtimedone = rnd(100 * youmonst.data->mlevel + 1);
+        else if (is_were(youmonst.data))
+            you_unwere(FALSE); /* if polycontrl, asks whether to rehumanize */
         else
             rehumanize();
     }
@@ -327,7 +642,7 @@ nh_timeout()
             u.uspellprot--;
             find_ac();
             if (!Blind)
-#if 0 /*JP*/
+#if 0 /*JP:T*/
                 Norep("The %s haze around you %s.", hcolor(NH_GOLDEN),
                       u.uspellprot ? "becomes less dense" : "disappears");
 #else
@@ -345,6 +660,7 @@ nh_timeout()
             pline("%s\82Í\91¬\8bì\82¯\82ð\82â\82ß\82½\81D", Monnam(u.usteed));
     }
 
+    was_flying = Flying;
     for (upp = u.uprops; upp < u.uprops + SIZE(u.uprops); upp++)
         if ((upp->intrinsic & TIMEOUT) && !(--upp->intrinsic & TIMEOUT)) {
             kptr = find_delayed_killer((int) (upp - u.uprops));
@@ -368,23 +684,10 @@ nh_timeout()
                 }
                 dealloc_killer(kptr);
                 /* (unlike sliming, you aren't changing form here) */
-                done(STONING);
+                done_timeout(STONING, STONED);
                 break;
             case SLIMED:
-                if (kptr && kptr->name[0]) {
-                    killer.format = kptr->format;
-                    Strcpy(killer.name, kptr->name);
-                } else {
-                    killer.format = NO_KILLER_PREFIX;
-/*JP
-                    Strcpy(killer.name, "turned into green slime");
-*/
-                    Strcpy(killer.name, "\97Î\83X\83\89\83C\83\80\82É\82È\82Á\82½");
-                }
-                dealloc_killer(kptr);
-                /* involuntarily break "never changed form" conduct */
-                u.uconduct.polyselfs++;
-                done(TURNED_SLIME);
+                slimed_to_death(kptr); /* done_timeout(TURNED_SLIME,SLIMED) */
                 break;
             case VOMITING:
                 make_vomiting(0L, TRUE);
@@ -411,12 +714,12 @@ nh_timeout()
                         killer.format = KILLED_BY;
                     }
                 }
+                done_timeout(POISONING, SICK);
                 u.usick_type = 0;
-                done(POISONING);
                 break;
             case FAST:
                 if (!Very_fast)
-#if 0 /*JP*/
+#if 0 /*JP:T*/
                     You_feel("yourself slowing down%s.",
                              Fast ? " a bit" : "");
 #else
@@ -446,13 +749,14 @@ nh_timeout()
             case DEAF:
                 set_itimeout(&HDeaf, 1L);
                 make_deaf(0L, TRUE);
+                context.botl = TRUE;
                 if (!Deaf)
                     stop_occupation();
                 break;
             case INVIS:
                 newsym(u.ux, u.uy);
                 if (!Invis && !BInvis && !Blind) {
-#if 0 /*JP*/
+#if 0 /*JP:T*/
                     You(!See_invisible
                             ? "are no longer invisible."
                             : "can no longer see through yourself.");
@@ -471,7 +775,7 @@ nh_timeout()
                 stop_occupation();
                 break;
             case WOUNDED_LEGS:
-                heal_legs();
+                heal_legs(0);
                 stop_occupation();
                 break;
 #ifdef JPEXTENSION
@@ -503,16 +807,60 @@ nh_timeout()
             case LEVITATION:
                 (void) float_down(I_SPECIAL | TIMEOUT, 0L);
                 break;
+            case FLYING:
+                /* timed Flying is via #wizintrinsic only */
+                if (was_flying && !Flying) {
+                    context.botl = 1;
+/*JP
+                    You("land.");
+*/
+                    You("\92\85\92n\82µ\82½\81D");
+                    spoteffects(TRUE);
+                }
+                break;
+            case WARN_OF_MON:
+                /* timed Warn_of_mon is via #wizintrinsic only */
+                if (!Warn_of_mon) {
+                    context.warntype.speciesidx = NON_PM;
+                    if (context.warntype.species) {
+#if 0 /*JP:T*/
+                        You("are no longer warned about %s.",
+                            makeplural(context.warntype.species->mname));
+#else
+                        You("\82à\82Í\82â%s\82ð\8cx\8d\90\82µ\82È\82­\82È\82Á\82½\81D",
+                            makeplural(context.warntype.species->mname));
+#endif
+                        context.warntype.species = (struct permonst *) 0;
+                    }
+                }
+                break;
+            case PASSES_WALLS:
+                if (!Passes_walls) {
+                    if (stuck_in_wall())
+/*JP
+                        You_feel("hemmed in again.");
+*/
+                        You_feel("\82Ü\82½\95Â\82\8d\9e\82ß\82ç\82ê\82½\82æ\82¤\82¾\81D");
+                    else
+#if 0 /*JP:T*/
+                        pline("You're back to your %s self again.",
+                              !Upolyd ? "normal" : "unusual");
+#else
+                        pline("\82 \82È\82½\82Í\8dÄ\82Ñ\92Ê\8fí%s\82Ì\8fó\91Ô\82É\82È\82Á\82½\81D",
+                              !Upolyd ? "" : "\82Å\82Í\82È\82¢");
+#endif
+                }
+                break;
             case STRANGLED:
                 killer.format = KILLED_BY;
-#if 0 /*JP*/
+#if 0 /*JP:T*/
                 Strcpy(killer.name,
                        (u.uburied) ? "suffocation" : "strangulation");
 #else
                 Strcpy(killer.name,
                        (u.uburied) ? "\92\82\91§\82µ\82Ä" : "\8eñ\82ð\8di\82ß\82ç\82ê\82Ä");
 #endif
-                done(DIED);
+                done_timeout(DIED, STRANGLED);
                 /* must be declining to die in explore|wizard mode;
                    treat like being cured of strangulation by prayer */
                 if (uamul && uamul->otyp == AMULET_OF_STRANGULATION) {
@@ -555,6 +903,9 @@ nh_timeout()
             case DETECT_MONSTERS:
                 see_monsters();
                 break;
+            case GLIB:
+                make_glib(0); /* might update persistent inventory */
+                break;
             }
         }
 
@@ -577,6 +928,7 @@ boolean wakeup_msg;
         /* caller can follow with a direct call to Hear_again() if
            there's a need to override this when wakeup_msg is true */
         incr_itimeout(&HDeaf, how_long);
+        context.botl = TRUE;
         afternmv = Hear_again; /* this won't give any messages */
     }
     /* early wakeup from combat won't be possible until next monster turn */
@@ -711,7 +1063,9 @@ long timeout;
         boolean siblings = (hatchcount > 1), redraw = FALSE;
 
         if (cansee_hatchspot) {
-#if 0 /*JP*/
+            /* [bug?  m_monnam() yields accurate monster type
+               regardless of hallucination] */
+#if 0 /*JP:T*/
             Sprintf(monnambuf, "%s%s", siblings ? "some " : "",
                     siblings ? makeplural(m_monnam(mon)) : an(m_monnam(mon)));
 #else
@@ -729,7 +1083,7 @@ long timeout;
         case OBJ_INVENT:
             knows_egg = TRUE; /* true even if you are blind */
             if (!cansee_hatchspot)
-#if 0 /*JP*/
+#if 0 /*JP:T*/
                 You_feel("%s %s from your pack!", something,
                          locomotion(mon->data, "drop"));
 #else
@@ -737,7 +1091,7 @@ long timeout;
                       jpast(locomotion(mon->data, "\97\8e\82¿\82é")));
 #endif
             else
-#if 0 /*JP*/
+#if 0 /*JP:T*/
                 You_see("%s %s out of your pack!", monnambuf,
                         locomotion(mon->data, "drop"));
 #else
@@ -745,7 +1099,7 @@ long timeout;
                     jpast(locomotion(mon->data, "\97\8e\82¿\82é")));
 #endif
             if (yours) {
-#if 0 /*JP*/
+#if 0 /*JP:T*/
                 pline("%s cries sound like \"%s%s\"",
                       siblings ? "Their" : "Its",
                       flags.female ? "mommy" : "daddy", egg->spe ? "." : "?");
@@ -754,7 +1108,7 @@ long timeout;
                       flags.female ? "\83}\83}" : "\83p\83p", egg->spe ? "" : "\81H");
 #endif
             } else if (mon->data->mlet == S_DRAGON && !Deaf) {
-#if 0 /*JP*/
+#if 0 /*JP:T*/
                 verbalize("Gleep!"); /* Mything eggs :-) */
 #else
                 verbalize("\83u\83H\81[\81I"); /* Mything eggs :-) */
@@ -776,24 +1130,29 @@ long timeout;
         case OBJ_MINVENT:
             if (cansee_hatchspot) {
                 /* egg carrying monster might be invisible */
-                if (canseemon(egg->ocarry)) {
-/*JP
+                mon2 = egg->ocarry;
+                if (canseemon(mon2)
+                    && (!mon2->wormno || cansee(mon2->mx, mon2->my))) {
+#if 0 /*JP:T*/
                     Sprintf(carriedby, "%s pack",
-*/
+                            s_suffix(a_monnam(mon2)));
+#else
                     Sprintf(carriedby, "%s\82Ì\94w\95\89\82¢\91Ü\82©\82ç",
-                            s_suffix(a_monnam(egg->ocarry)));
+                            a_monnam(mon2));
+#endif
                     knows_egg = TRUE;
-                } else if (is_pool(mon->mx, mon->my))
+                } else if (is_pool(mon->mx, mon->my)) {
 /*JP
                     Strcpy(carriedby, "empty water");
 */
                     Strcpy(carriedby, "\89½\82à\82È\82¢\90\85\92\86\82©\82ç");
-                else
+                } else {
 /*JP
                     Strcpy(carriedby, "thin air");
 */
                     Strcpy(carriedby, "\89½\82à\82È\82¢\8bó\8aÔ\82©\82ç");
-#if 0 /*JP*/
+                }
+#if 0 /*JP:T*/
                 You_see("%s %s out of %s!", monnambuf,
                         locomotion(mon->data, "drop"), carriedby);
 #else
@@ -883,7 +1242,7 @@ slip_or_trip()
           name; if not, look for rocks to trip over; trip over
           anonymous "something" if there aren't any rocks.
         */
-#if 0 /*JP*/
+#if 0 /*JP:T*/
         what = (iflags.last_msg == PLNMSG_ONE_ITEM_HERE)
                 ? ((otmp->quan == 1L) ? "it"
                       : Hallucination ? "they" : "them")
@@ -904,7 +1263,7 @@ slip_or_trip()
         if (Hallucination) {
             what = strcpy(buf, what);
             buf[0] = highc(buf[0]);
-#if 0 /*JP*/
+#if 0 /*JP:T*/
             pline("Egads!  %s bite%s your %s!", what,
                   (!otmp || otmp->quan == 1L) ? "s" : "", body_part(FOOT));
 #else
@@ -918,7 +1277,7 @@ slip_or_trip()
         }
         if (!uarmf && otmp->otyp == CORPSE
             && touch_petrifies(&mons[otmp->corpsenm]) && !Stone_resistance) {
-#if 0 /*JP*/
+#if 0 /*JP:T*/
             Sprintf(killer.name, "tripping over %s corpse",
                     an(mons[otmp->corpsenm].mname));
 #else
@@ -928,7 +1287,7 @@ slip_or_trip()
             instapetrify(killer.name);
         }
     } else if (rn2(3) && is_ice(u.ux, u.uy)) {
-#if 0 /*JP*/
+#if 0 /*JP:T*/
         pline("%s %s%s on the ice.",
               u.usteed ? upstart(x_monnam(u.usteed,
                                           (has_mname(u.usteed)) ? ARTICLE_NONE
@@ -948,7 +1307,7 @@ slip_or_trip()
         if (on_foot) {
             switch (rn2(4)) {
             case 1:
-#if 0 /*JP*/
+#if 0 /*JP:T*/
                 You("trip over your own %s.",
                     Hallucination ? "elbow" : makeplural(body_part(FOOT)));
 #else
@@ -957,7 +1316,7 @@ slip_or_trip()
 #endif
                 break;
             case 2:
-#if 0 /*JP*/
+#if 0 /*JP:T*/
                 You("slip %s.",
                     Hallucination ? "on a banana peel" : "and nearly fall");
 #else
@@ -981,7 +1340,7 @@ slip_or_trip()
         } else {
             switch (rn2(4)) {
             case 1:
-#if 0 /*JP*/
+#if 0 /*JP:T*/
                 Your("%s slip out of the stirrups.",
                      makeplural(body_part(FOOT)));
 #else
@@ -1078,12 +1437,18 @@ anything *arg;
 long timeout;
 {
     struct obj *obj = arg->a_obj;
-    boolean canseeit, many, menorah, need_newsym;
+#if 0 /*JP*/
+    boolean canseeit, many, menorah, need_newsym, need_invupdate;
+#else
+    boolean canseeit, menorah, need_newsym, need_invupdate;
+#endif
     xchar x, y;
     char whose[BUFSZ];
 
     menorah = obj->otyp == CANDELABRUM_OF_INVOCATION;
+#if 0 /*JP*/
     many = menorah ? obj->spe > 1 : obj->quan > 1L;
+#endif
 
     /* timeout while away */
     if (timeout != monstermoves) {
@@ -1095,6 +1460,7 @@ long timeout;
 
             if (menorah) {
                 obj->spe = 0; /* no more candles */
+                obj->owt = weight(obj);
             } else if (Is_candle(obj) || obj->otyp == POT_OIL) {
                 /* get rid of candles and burning oil potions;
                    we know this object isn't carried by hero,
@@ -1119,7 +1485,7 @@ long timeout;
     } else {
         canseeit = FALSE;
     }
-    need_newsym = FALSE;
+    need_newsym = need_invupdate = FALSE;
 
     /* obj->age is the age remaining at this point.  */
     switch (obj->otyp) {
@@ -1128,6 +1494,8 @@ long timeout;
         if (canseeit) {
             switch (obj->where) {
             case OBJ_INVENT:
+                need_invupdate = TRUE;
+                /*FALLTHRU*/
             case OBJ_MINVENT:
 /*JP
                 pline("%spotion of oil has burnt away.", whose);
@@ -1204,6 +1572,8 @@ long timeout;
             if (canseeit || obj->where == OBJ_INVENT) {
                 switch (obj->where) {
                 case OBJ_INVENT:
+                    need_invupdate = TRUE;
+                    /*FALLTHRU*/
                 case OBJ_MINVENT:
                     if (obj->otyp == BRASS_LANTERN)
 /*JP
@@ -1256,7 +1626,7 @@ long timeout;
                 switch (obj->where) {
                 case OBJ_INVENT:
                 case OBJ_MINVENT:
-#if 0 /*JP*/
+#if 0 /*JP:T*/
                     pline("%s%scandle%s getting short.", whose,
                           menorah ? "candelabrum's " : "",
                           many ? "s are" : " is");
@@ -1266,7 +1636,7 @@ long timeout;
 #endif
                     break;
                 case OBJ_FLOOR:
-#if 0 /*JP*/
+#if 0 /*JP:T*/
                     You_see("%scandle%s getting short.",
                             menorah ? "a candelabrum's " : many ? "some "
                                                                 : "a ",
@@ -1284,7 +1654,7 @@ long timeout;
                 switch (obj->where) {
                 case OBJ_INVENT:
                 case OBJ_MINVENT:
-#if 0 /*JP*/
+#if 0 /*JP:T*/
                     pline("%s%scandle%s flame%s flicker%s low!", whose,
                           menorah ? "candelabrum's " : "", many ? "s'" : "'s",
                           many ? "s" : "", many ? "" : "s");
@@ -1294,7 +1664,7 @@ long timeout;
 #endif
                     break;
                 case OBJ_FLOOR:
-#if 0 /*JP*/
+#if 0 /*JP:T*/
                     You_see("%scandle%s flame%s flicker low!",
                             menorah ? "a candelabrum's " : many ? "some "
                                                                 : "a ",
@@ -1313,8 +1683,10 @@ long timeout;
                 if (menorah) {
                     switch (obj->where) {
                     case OBJ_INVENT:
+                        need_invupdate = TRUE;
+                        /*FALLTHRU*/
                     case OBJ_MINVENT:
-#if 0 /*JP*/
+#if 0 /*JP:T*/
                         pline("%scandelabrum's flame%s.", whose,
                               many ? "s die" : " dies");
 #else
@@ -1322,7 +1694,7 @@ long timeout;
 #endif
                         break;
                     case OBJ_FLOOR:
-#if 0 /*JP*/
+#if 0 /*JP:T*/
                         You_see("a candelabrum's flame%s die.",
                                 many ? "s" : "");
 #else
@@ -1333,8 +1705,11 @@ long timeout;
                 } else {
                     switch (obj->where) {
                     case OBJ_INVENT:
+                        /* no need_invupdate for update_inventory() necessary;
+                           useupall() -> freeinv() handles it */
+                        /*FALLTHRU*/
                     case OBJ_MINVENT:
-#if 0 /*JP*/
+#if 0 /*JP:T*/
                         pline("%s %s consumed!", Yname2(obj),
                               many ? "are" : "is");
 #else
@@ -1343,10 +1718,10 @@ long timeout;
                         break;
                     case OBJ_FLOOR:
                         /*
-                        You see some wax candles consumed!
-                        You see a wax candle consumed!
-                        */
-#if 0 /*JP*/
+                          You see some wax candles consumed!
+                          You see a wax candle consumed!
+                         */
+#if 0 /*JP:T*/
                         You_see("%s%s consumed!", many ? "some " : "",
                                 many ? xname(obj) : an(xname(obj)));
 #else
@@ -1357,15 +1732,15 @@ long timeout;
                     }
 
                     /* post message */
-#if 0 /*JP*/
+#if 0 /*JP:T*/
                     pline(Hallucination
                               ? (many ? "They shriek!" : "It shrieks!")
                               : Blind ? "" : (many ? "Their flames die."
                                                    : "Its flame dies."));
 #else
-                    pline(Hallucination ? "\82»\82ê\82Í\90k\82¦\82½\81D"
-                          : Blind ? "" 
-                          : "\89\8a\82Í\8fÁ\82¦\82½\81D");
+                    pline(Hallucination ? "\82»\82ê\82Í\8bà\90Ø\82è\90º\82ð\82 \82°\82½\81I"
+                              : Blind ? "" 
+                              : "\89\8a\82Í\8fÁ\82¦\82½\81D");
 #endif
                 }
             }
@@ -1373,6 +1748,7 @@ long timeout;
 
             if (menorah) {
                 obj->spe = 0;
+                obj->owt = weight(obj);
             } else {
                 if (carried(obj)) {
                     useupall(obj);
@@ -1386,7 +1762,7 @@ long timeout;
                 }
                 obj = (struct obj *) 0;
             }
-            break;
+            break; /* case [age ==] 0 */
 
         default:
             /*
@@ -1399,15 +1775,16 @@ long timeout;
 
         if (obj && obj->age)
             begin_burn(obj, TRUE);
-
-        break;
+        break; /* case [otyp ==] candelabrum|tallow_candle|wax_candle */
 
     default:
-        impossible("burn_object: unexpeced obj %s", xname(obj));
+        impossible("burn_object: unexpected obj %s", xname(obj));
         break;
     }
     if (need_newsym)
         newsym(x, y);
+    if (need_invupdate)
+        update_inventory();
 }
 
 /*
@@ -1459,6 +1836,8 @@ boolean already_lit;
 
     case POT_OIL:
         turns = obj->age;
+        if (obj->odiluted)
+            turns = (3L * turns + 2L) / 4L;
         radius = 1; /* very dim light */
         break;
 
@@ -1616,6 +1995,7 @@ do_storms()
 */
         pline("\83s\83J\83b\81I\81I\83S\83\8d\83S\83\8d\83S\83\8d\83S\83\8d\81I\81I\83h\81[\83\93\81I");
         incr_itimeout(&HDeaf, rn1(20, 30));
+        context.botl = TRUE;
         if (!u.uinvulnerable) {
             stop_occupation();
             nomul(-3);
@@ -1762,7 +2142,7 @@ timer_element *base;
     char buf[BUFSZ];
 
     if (!base) {
-        putstr(win, 0, "<empty>");
+        putstr(win, 0, " <empty>");
     } else {
         putstr(win, 0, "timeout  id   kind   call");
         for (curr = base; curr; curr = curr->next) {
@@ -1786,6 +2166,9 @@ wiz_timeout_queue()
 {
     winid win;
     char buf[BUFSZ];
+    const char *propname;
+    long intrinsic;
+    int i, p, count, longestlen, ln, specindx = 0;
 
     win = create_nhwindow(NHW_MENU); /* corner text window */
     if (win == WIN_ERR)
@@ -1798,6 +2181,46 @@ wiz_timeout_queue()
     putstr(win, 0, "");
     print_queue(win, timer_base);
 
+    /* Timed properies:
+     * check every one; the majority can't obtain temporary timeouts in
+     * normal play but those can be forced via the #wizintrinsic command.
+     */
+    count = longestlen = 0;
+    for (i = 0; (propname = propertynames[i].prop_name) != 0; ++i) {
+        p = propertynames[i].prop_num;
+        intrinsic = u.uprops[p].intrinsic;
+        if (intrinsic & TIMEOUT) {
+            ++count;
+            if ((ln = (int) strlen(propname)) > longestlen)
+                longestlen = ln;
+        }
+        if (specindx == 0 && p == FIRE_RES)
+            specindx = i;
+    }
+    putstr(win, 0, "");
+    if (!count) {
+        putstr(win, 0, "No timed properties.");
+    } else {
+        putstr(win, 0, "Timed properties:");
+        putstr(win, 0, "");
+        for (i = 0; (propname = propertynames[i].prop_name) != 0; ++i) {
+            p = propertynames[i].prop_num;
+            intrinsic = u.uprops[p].intrinsic;
+            if (intrinsic & TIMEOUT) {
+                if (specindx > 0 && i >= specindx) {
+                    putstr(win, 0, " -- settable via #wizinstrinc only --");
+                    specindx = 0;
+                }
+                /* timeout value can be up to 16777215 (0x00ffffff) but
+                   width of 4 digits should result in values lining up
+                   almost all the time (if/when they don't, it won't
+                   look nice but the information will still be accurate) */
+                Sprintf(buf, " %*s %4ld", -longestlen, propname,
+                        (intrinsic & TIMEOUT));
+                putstr(win, 0, buf);
+            }
+        }
+    }
     display_nhwindow(win, FALSE);
     destroy_nhwindow(win);
 
@@ -1813,8 +2236,9 @@ timer_sanity_check()
     for (curr = timer_base; curr; curr = curr->next)
         if (curr->kind == TIMER_OBJECT) {
             struct obj *obj = curr->arg.a_obj;
+
             if (obj->timed == 0) {
-                pline("timer sanity: untimed obj %s, timer %ld",
+                impossible("timer sanity: untimed obj %s, timer %ld",
                       fmt_ptr((genericptr_t) obj), curr->tid);
             }
         }
@@ -1855,12 +2279,32 @@ short kind;
 short func_index;
 anything *arg;
 {
-    timer_element *gnu;
+    timer_element *gnu, *dup;
+
+    if (kind < 0 || kind >= NUM_TIMER_KINDS
+        || func_index < 0 || func_index >= NUM_TIME_FUNCS)
+        panic("start_timer (%s: %d)", kind_name(kind), (int) func_index);
 
-    if (func_index < 0 || func_index >= NUM_TIME_FUNCS)
-        panic("start_timer");
+    /* fail if <arg> already has a <func_index> timer running */
+    for (dup = timer_base; dup; dup = dup->next)
+        if (dup->kind == kind
+            && dup->func_index == func_index
+            && dup->arg.a_void == arg->a_void)
+            break;
+    if (dup) {
+        char idbuf[QBUFSZ];
 
-    gnu = (timer_element *) alloc(sizeof(timer_element));
+#ifdef VERBOSE_TIMER
+        Sprintf(idbuf, "%s timer", timeout_funcs[func_index].name);
+#else
+        Sprintf(idbuf, "%s timer (%d)", kind_name(kind), (int) func_index);
+#endif
+        impossible("Attempted to start duplicate %s, aborted.", idbuf);
+        return FALSE;
+    }
+
+    gnu = (timer_element *) alloc(sizeof *gnu);
+    (void) memset((genericptr_t) gnu, 0, sizeof *gnu);
     gnu->next = 0;
     gnu->tid = timer_id++;
     gnu->timeout = monstermoves + when;
@@ -1873,7 +2317,6 @@ anything *arg;
     if (kind == TIMER_OBJECT) /* increment object's timed count */
         (arg->a_obj)->timed++;
 
-    /* should check for duplicates and fail if any */
     return TRUE;
 }
 
@@ -2326,6 +2769,23 @@ long adjust;     /* how much to adjust timeout */
     }
 }
 
+/* to support '#stats' wizard-mode command */
+void
+timer_stats(hdrfmt, hdrbuf, count, size)
+const char *hdrfmt;
+char *hdrbuf;
+long *count, *size;
+{
+    timer_element *te;
+
+    Sprintf(hdrbuf, hdrfmt, (long) sizeof (timer_element));
+    *count = *size = 0L;
+    for (te = timer_base; te; te = te->next) {
+        ++*count;
+        *size += (long) sizeof *te;
+    }
+}
+
 /* reset all timers that are marked for reseting */
 void
 relink_timers(ghostly)