OSDN Git Service

fix #39134
[jnethack/source.git] / src / timeout.c
index 9246fd9..9120e36 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: 1505214876 2017/09/12 11:14:36 $  $NHDT-Branch: NetHack-3.6.0 $:$NHDT-Revision: 1.75 $ */
 /* 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-2018            */
 /* 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 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 no 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 abilites" },
+    { 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*/
@@ -77,26 +159,29 @@ stoned_dialogue()
         multi_reason = "\90Î\89»\82µ\82Â\82Â\82 \82é\8e\9e\82É";
         nomovemsg = You_can_move_again; /* not unconscious */
         break;
+    case 2:
+        if ((HDeaf & TIMEOUT) > 0L && (HDeaf & TIMEOUT) < 5L)
+            set_itimeout(&HDeaf, 5L); /* avoid Hear_again at tail end */
     default:
         break;
     }
     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 +220,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 +260,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",
@@ -170,7 +279,8 @@ static NEARDATA const char *const choke_texts2[] = {
 #if 0 /*JP*/
     "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",
@@ -204,6 +314,45 @@ 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*/
     "You are turning a little %s.",   /* 5 */
@@ -260,6 +409,8 @@ slime_dialogue()
         if (multi > 0)
             nomul(0);
     }
+    if (i == 2L && (HDeaf & TIMEOUT) > 0L && (HDeaf & TIMEOUT) < 5L)
+        set_itimeout(&HDeaf, 5L); /* avoid Hear_again at tail end */
     exercise(A_DEX, FALSE);
 }
 
@@ -270,10 +421,39 @@ 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");
     }
 }
 
+/* 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*/
+    "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]);
+}
+
 void
 nh_timeout()
 {
@@ -287,7 +467,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 +491,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();
     }
@@ -446,6 +632,7 @@ nh_timeout()
             case DEAF:
                 set_itimeout(&HDeaf, 1L);
                 make_deaf(0L, TRUE);
+                context.botl = TRUE;
                 if (!Deaf)
                     stop_occupation();
                 break;
@@ -503,6 +690,23 @@ nh_timeout()
             case LEVITATION:
                 (void) float_down(I_SPECIAL | TIMEOUT, 0L);
                 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*/
+                        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*/
@@ -577,6 +781,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,6 +916,8 @@ long timeout;
         boolean siblings = (hatchcount > 1), redraw = FALSE;
 
         if (cansee_hatchspot) {
+            /* [bug?  m_monnam() yields accurate monster type
+               regardless of hallucination] */
 #if 0 /*JP*/
             Sprintf(monnambuf, "%s%s", siblings ? "some " : "",
                     siblings ? makeplural(m_monnam(mon)) : an(m_monnam(mon)));
@@ -776,24 +983,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
@@ -1078,12 +1290,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 +1313,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 +1338,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 +1347,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 +1425,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
@@ -1313,8 +1536,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
@@ -1333,6 +1558,9 @@ 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*/
                         pline("%s %s consumed!", Yname2(obj),
@@ -1343,9 +1571,9 @@ long timeout;
                         break;
                     case OBJ_FLOOR:
                         /*
-                        You see some wax candles consumed!
-                        You see a wax candle consumed!
-                        */
+                          You see some wax candles consumed!
+                          You see a wax candle consumed!
+                         */
 #if 0 /*JP*/
                         You_see("%s%s consumed!", many ? "some " : "",
                                 many ? xname(obj) : an(xname(obj)));
@@ -1363,9 +1591,9 @@ long timeout;
                               : 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 +1601,7 @@ long timeout;
 
             if (menorah) {
                 obj->spe = 0;
+                obj->owt = weight(obj);
             } else {
                 if (carried(obj)) {
                     useupall(obj);
@@ -1386,7 +1615,7 @@ long timeout;
                 }
                 obj = (struct obj *) 0;
             }
-            break;
+            break; /* case [age ==] 0 */
 
         default:
             /*
@@ -1399,8 +1628,7 @@ 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));
@@ -1408,6 +1636,8 @@ long timeout;
     }
     if (need_newsym)
         newsym(x, y);
+    if (need_invupdate)
+        update_inventory();
 }
 
 /*
@@ -1616,6 +1846,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 +1993,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 +2017,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 +2032,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,6 +2087,7 @@ 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",
                       fmt_ptr((genericptr_t) obj), curr->tid);
@@ -1861,6 +2136,7 @@ anything *arg;
         panic("start_timer");
 
     gnu = (timer_element *) alloc(sizeof(timer_element));
+    (void) memset((genericptr_t)gnu, 0, sizeof(timer_element));
     gnu->next = 0;
     gnu->tid = timer_id++;
     gnu->timeout = monstermoves + when;
@@ -2326,6 +2602,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)