OSDN Git Service

update year to 2020
[jnethack/source.git] / src / botl.c
index b143a14..522e90a 100644 (file)
@@ -1,37 +1,67 @@
-/* NetHack 3.6 botl.c  $NHDT-Date: 1447978683 2015/11/20 00:18:03 $  $NHDT-Branch: master $:$NHDT-Revision: 1.69 $ */
+/* NetHack 3.6 botl.c  $NHDT-Date: 1573178085 2019/11/08 01:54:45 $  $NHDT-Branch: NetHack-3.6 $:$NHDT-Revision: 1.148 $ */
 /* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */
+/*-Copyright (c) Michael Allison, 2006. */
 /* 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"
+#ifndef LONG_MAX
 #include <limits.h>
+#endif
 
 extern const char *hu_stat[]; /* defined in eat.c */
 
-#if 0 /*JP*/
+#if 0 /*JP:T*/
 const char *const enc_stat[] = { "",         "Burdened",  "Stressed",
                                  "Strained", "Overtaxed", "Overloaded" };
 #else
+      /*\83I\83v\83V\83\87\83\93\82Ì\83p\81[\83X\82Å\89p\8cê\94Å\82à\95K\97v*/
 const char *const enc_stat[] = { "",     "\82æ\82ë\82ß\82«", "\88³\94\97",
                                  "\8cÀ\8aE", "\89×\8fd",     "\92´\89ß"};
+const char *const enc_stat_opt[] = { "",         "Burdened",  "Stressed",
+                                     "Strained", "Overtaxed", "Overloaded" };
 #endif
 
 STATIC_OVL NEARDATA int mrank_sz = 0; /* loaded by max_rank_sz (from u_init) */
 STATIC_DCL const char *NDECL(rank);
+STATIC_DCL void NDECL(bot_via_windowport);
+STATIC_DCL void NDECL(stat_update_time);
+
+static char *
+get_strength_str()
+{
+    static char buf[32];
+    int st = ACURR(A_STR);
+
+    if (st > 18) {
+        if (st > STR18(100))
+            Sprintf(buf, "%2d", st - 100);
+        else if (st < STR18(100))
+            Sprintf(buf, "18/%02d", st - 18);
+        else
+            Sprintf(buf, "18/**");
+    } else
+        Sprintf(buf, "%-1d", st);
+
+    return buf;
+}
 
-#ifndef STATUS_VIA_WINDOWPORT
+void
+check_gold_symbol()
+{
+    nhsym goldch = showsyms[COIN_CLASS + SYM_OFF_O];
 
-STATIC_DCL void NDECL(bot1);
-STATIC_DCL void NDECL(bot2);
+    iflags.invis_goldsym = (goldch <= (nhsym) ' ');
+}
 
-STATIC_OVL void
-bot1()
+char *
+do_statusline1()
 {
-    char newbot1[MAXCO];
+    static char newbot1[BUFSZ];
     register char *nb;
     register int i, j;
 
@@ -43,10 +73,11 @@ bot1()
         newbot1[9] = '_';
 #endif
     newbot1[10] = 0;
-/*JP
+#if 0 /*JP*/
     Sprintf(nb = eos(newbot1), " the ");
-*/
-    Sprintf(nb = eos(newbot1)," ");
+#else
+    Sprintf(nb = eos(newbot1), " ");
+#endif
 
     if (Upolyd) {
         char mbot[BUFSZ];
@@ -68,34 +99,19 @@ bot1()
     j = (int) ((nb + 2) - newbot1); /* strlen(newbot1) but less computation */
     if ((i - j) > 0)
         Sprintf(nb = eos(nb), "%*s", i - j, " "); /* pad with spaces */
-    if (ACURR(A_STR) > 18) {
-        if (ACURR(A_STR) > STR18(100))
-/*JP
-            Sprintf(nb = eos(nb), "St:%2d ", ACURR(A_STR) - 100);
-*/
-            Sprintf(nb = eos(nb), "\8b­:%2d ", ACURR(A_STR) - 100);
-        else if (ACURR(A_STR) < STR18(100))
-/*JP
-            Sprintf(nb = eos(nb), "St:18/%02d ", ACURR(A_STR) - 18);
-*/
-            Sprintf(nb = eos(nb), "\8b­:18/%02d ", ACURR(A_STR) - 18);
-        else
-/*JP
-            Sprintf(nb = eos(nb), "St:18/** ");
-*/
-            Sprintf(nb = eos(nb), "\8b­:18/** ");
-    } else
-/*JP
-        Sprintf(nb = eos(nb), "St:%-1d ", ACURR(A_STR));
-*/
-        Sprintf(nb = eos(nb), "\8b­:%-1d ", ACURR(A_STR));
-/*JP
-    Sprintf(nb = eos(nb), "Dx:%-1d Co:%-1d In:%-1d Wi:%-1d Ch:%-1d",
-*/
-    Sprintf(nb = eos(nb), "\91\81:%-1d \91Ï:%-1d \92m:%-1d \8c«:%-1d \96£:%-1d ",
+
+#if 0 /*JP:T*/
+    Sprintf(nb = eos(nb), "St:%s Dx:%-1d Co:%-1d In:%-1d Wi:%-1d Ch:%-1d",
+            get_strength_str(),
             ACURR(A_DEX), ACURR(A_CON), ACURR(A_INT), ACURR(A_WIS),
             ACURR(A_CHA));
-#if 0 /*JP*/
+#else
+    Sprintf(nb = eos(nb), "\8b­:%s \91\81:%-1d \91Ï:%-1d \92m:%-1d \8c«:%-1d \96£:%-1d ",
+            get_strength_str(),
+            ACURR(A_DEX), ACURR(A_CON), ACURR(A_INT), ACURR(A_WIS),
+            ACURR(A_CHA));
+#endif
+#if 0 /*JP:T*/
     Sprintf(nb = eos(nb),
             (u.ualign.type == A_CHAOTIC)
                 ? "  Chaotic"
@@ -113,107 +129,228 @@ bot1()
 */
         Sprintf(nb = eos(nb), "%ld\93_", botl_score());
 #endif
-    curs(WIN_STATUS, 1, 0);
-    putstr(WIN_STATUS, 0, newbot1);
+    return newbot1;
 }
 
-STATIC_OVL void
-bot2()
+char *
+do_statusline2()
 {
-    char newbot2[MAXCO];
+    static char newbot2[BUFSZ], /* MAXCO: botl.h */
+         /* dungeon location (and gold), hero health (HP, PW, AC),
+            experience (HD if poly'd, else Exp level and maybe Exp points),
+            time (in moves), varying number of status conditions */
+         dloc[QBUFSZ], hlth[QBUFSZ], expr[QBUFSZ], tmmv[QBUFSZ], cond[QBUFSZ];
     register char *nb;
-    int hp, hpmax;
-    int cap = near_capacity();
+    unsigned dln, dx, hln, xln, tln, cln;
+    int hp, hpmax, cap;
+    long money;
 
+    /*
+     * Various min(x,9999)'s are to avoid having excessive values
+     * violate the field width assumptions in botl.h and should not
+     * impact normal play.  Particularly 64-bit long for gold which
+     * could require many more digits if someone figures out a way
+     * to get and carry a really large (or negative) amount of it.
+     * Turn counter is also long, but we'll risk that.
+     */
+
+    /* dungeon location plus gold */
+    (void) describe_level(dloc); /* includes at least one trailing space */
+    if ((money = money_cnt(invent)) < 0L)
+        money = 0L; /* ought to issue impossible() and then discard gold */
+    Sprintf(eos(dloc), "%s:%-2ld", /* strongest hero can lift ~300000 gold */
+            (iflags.in_dumplog || iflags.invis_goldsym) ? "$"
+              : encglyph(objnum_to_glyph(GOLD_PIECE)),
+            min(money, 999999L));
+    dln = strlen(dloc);
+    /* '$' encoded as \GXXXXNNNN is 9 chars longer than display will need */
+    dx = strstri(dloc, "\\G") ? 9 : 0;
+
+    /* health and armor class (has trailing space for AC 0..9) */
     hp = Upolyd ? u.mh : u.uhp;
     hpmax = Upolyd ? u.mhmax : u.uhpmax;
-
     if (hp < 0)
         hp = 0;
-    (void) describe_level(newbot2);
-/*JP
-    Sprintf(nb = eos(newbot2), "%s:%-2ld HP:%d(%d) Pw:%d(%d) AC:%-2d",
-*/
-    Sprintf(nb = eos(newbot2), "%s:%-2ld \91Ì:%d(%d) \96\82:%d(%d) \8aZ:%-2d",
-            encglyph(objnum_to_glyph(GOLD_PIECE)), money_cnt(invent), hp,
-            hpmax, u.uen, u.uenmax, u.uac);
+#if 0 /*JP:T*/
+    Sprintf(hlth, "HP:%d(%d) Pw:%d(%d) AC:%-2d",
+            min(hp, 9999), min(hpmax, 9999),
+            min(u.uen, 9999), min(u.uenmax, 9999), u.uac);
+#else
+    Sprintf(hlth, "\91Ì:%d(%d) \96\82:%d(%d) \8aZ:%-2d",
+            min(hp, 9999), min(hpmax, 9999),
+            min(u.uen, 9999), min(u.uenmax, 9999), u.uac);
+#endif
+    hln = strlen(hlth);
 
+    /* experience */
     if (Upolyd)
-        Sprintf(nb = eos(nb), " HD:%d", mons[u.umonnum].mlevel);
+        Sprintf(expr, "HD:%d", mons[u.umonnum].mlevel);
     else if (flags.showexp)
 /*JP
-        Sprintf(nb = eos(nb), " Xp:%u/%-1ld", u.ulevel, u.uexp);
+        Sprintf(expr, "Xp:%d/%-1ld", u.ulevel, u.uexp);
 */
-        Sprintf(nb = eos(nb), " \8co\8c±:%u/%-1ld", u.ulevel,u.uexp);
+        Sprintf(expr, "\8co\8c±:%d/%-1ld", u.ulevel, u.uexp);
     else
 /*JP
-        Sprintf(nb = eos(nb), " Exp:%u", u.ulevel);
+        Sprintf(expr, "Exp:%d", u.ulevel);
 */
-        Sprintf(nb = eos(nb), " \8co\8c±:%u", u.ulevel);
+        Sprintf(expr, "\8co\8c±:%d", u.ulevel);
+    xln = strlen(expr);
 
+    /* time/move counter */
     if (flags.time)
 /*JP
-        Sprintf(nb = eos(nb), " T:%ld", moves);
+        Sprintf(tmmv, "T:%ld", moves);
 */
-        Sprintf(nb = eos(nb), " \95à:%ld", moves);
-    if (strcmp(hu_stat[u.uhs], "        ")) {
-        Sprintf(nb = eos(nb), " ");
-        Strcat(newbot2, hu_stat[u.uhs]);
-    }
-    if (Confusion)
+        Sprintf(tmmv, "\95à:%ld", moves);
+    else
+        tmmv[0] = '\0';
+    tln = strlen(tmmv);
+
+    /* status conditions; worst ones first */
+    cond[0] = '\0'; /* once non-empty, cond will have a leading space */
+    nb = cond;
+    /*
+     * Stoned, Slimed, Strangled, and both types of Sick are all fatal
+     * unless remedied before timeout expires.  Should we order them by
+     * shortest time left?  [Probably not worth the effort, since it's
+     * unusual for more than one of them to apply at a time.]
+     */
+    if (Stoned)
+/*JP
+        Strcpy(nb = eos(nb), " Stone");
+*/
+        Strcpy(nb = eos(nb), " \90Î\89»");
+    if (Slimed)
+/*JP
+        Strcpy(nb = eos(nb), " Slime");
+*/
+        Strcpy(nb = eos(nb), " \82Ç\82ë\82Ç\82ë");
+    if (Strangled)
 /*JP
-        Sprintf(nb = eos(nb), " Conf");
+        Strcpy(nb = eos(nb), " Strngl");
 */
-        Sprintf(nb = eos(nb), " \8d¬\97\90");
+        Strcpy(nb = eos(nb), " \92\82\91§");
     if (Sick) {
         if (u.usick_type & SICK_VOMITABLE)
 /*JP
-            Sprintf(nb = eos(nb), " FoodPois");
+            Strcpy(nb = eos(nb), " FoodPois");
 */
-            Sprintf(nb = eos(nb), " \90H\93Å");
+            Strcpy(nb = eos(nb), " \90H\93Å");
         if (u.usick_type & SICK_NONVOMITABLE)
 /*JP
-            Sprintf(nb = eos(nb), " Ill");
+            Strcpy(nb = eos(nb), " TermIll");
 */
-            Sprintf(nb = eos(nb), " \95a\8bC");
+            Strcpy(nb = eos(nb), " \95a\8bC");
     }
+    if (u.uhs != NOT_HUNGRY)
+        Sprintf(nb = eos(nb), " %s", hu_stat[u.uhs]);
+    if ((cap = near_capacity()) > UNENCUMBERED)
+        Sprintf(nb = eos(nb), " %s", enc_stat[cap]);
     if (Blind)
 /*JP
-        Sprintf(nb = eos(nb), " Blind");
+        Strcpy(nb = eos(nb), " Blind");
 */
-        Sprintf(nb = eos(nb), " \96Ó\96Ú");
+        Strcpy(nb = eos(nb), " \96Ó\96Ú");
+    if (Deaf)
+/*JP
+        Strcpy(nb = eos(nb), " Deaf");
+*/
+        Strcpy(nb = eos(nb), " \8e¨\98W");
     if (Stunned)
 /*JP
-        Sprintf(nb = eos(nb), " Stun");
+        Strcpy(nb = eos(nb), " Stun");
+*/
+        Strcpy(nb = eos(nb), " á¿\9dò");
+    if (Confusion)
+/*JP
+        Strcpy(nb = eos(nb), " Conf");
 */
-        Sprintf(nb = eos(nb), " á¿\9dò");
+        Strcpy(nb = eos(nb), " \8d¬\97\90");
     if (Hallucination)
 /*JP
-        Sprintf(nb = eos(nb), " Hallu");
+        Strcpy(nb = eos(nb), " Hallu");
 */
-        Sprintf(nb = eos(nb), " \8c\8ao");
-    if (Slimed)
+        Strcpy(nb = eos(nb), " \8c\8ao");
+    /* levitation and flying are mutually exclusive; riding is not */
+    if (Levitation)
 /*JP
-        Sprintf(nb = eos(nb), " Slime");
+        Strcpy(nb = eos(nb), " Lev");
 */
-        Sprintf(nb = eos(nb), " \82Ç\82ë\82Ç\82ë");
-    if (cap > UNENCUMBERED)
-        Sprintf(nb = eos(nb), " %s", enc_stat[cap]);
-    curs(WIN_STATUS, 1, 1);
-    putmixed(WIN_STATUS, 0, newbot2);
+        Strcpy(nb = eos(nb), " \95\82\97V");
+    if (Flying)
+/*JP
+        Strcpy(nb = eos(nb), " Fly");
+*/
+        Strcpy(nb = eos(nb), " \94ò\8ds");
+    if (u.usteed)
+/*JP
+        Strcpy(nb = eos(nb), " Ride");
+*/
+        Strcpy(nb = eos(nb), " \8bR\8fæ");
+    cln = strlen(cond);
+
+    /*
+     * Put the pieces together.  If they all fit, keep the traditional
+     * sequence.  Otherwise, move least important parts to the end in
+     * case the interface side of things has to truncate.  Note that
+     * dloc[] contains '$' encoded in ten character sequence \GXXXXNNNN
+     * so we want to test its display length rather than buffer length.
+     *
+     * We don't have an actual display limit here, so have to go by the
+     * width of the map.  Since we're reordering rather than truncating,
+     * wider displays can still show wider status than the map if the
+     * interface supports that.
+     */
+    if ((dln - dx) + 1 + hln + 1 + xln + 1 + tln + 1 + cln <= COLNO) {
+        Sprintf(newbot2, "%s %s %s %s %s", dloc, hlth, expr, tmmv, cond);
+    } else {
+        if (dln + 1 + hln + 1 + xln + 1 + tln + 1 + cln + 1 > MAXCO) {
+            panic("bot2: second status line exceeds MAXCO (%u > %d)",
+                  (dln + 1 + hln + 1 + xln + 1 + tln + 1 + cln + 1), MAXCO);
+        } else if ((dln - dx) + 1 + hln + 1 + xln + 1 + cln <= COLNO) {
+            Sprintf(newbot2, "%s %s %s %s %s", dloc, hlth, expr, cond, tmmv);
+        } else if ((dln - dx) + 1 + hln + 1 + cln <= COLNO) {
+            Sprintf(newbot2, "%s %s %s %s %s", dloc, hlth, cond, expr, tmmv);
+        } else {
+            Sprintf(newbot2, "%s %s %s %s %s", hlth, cond, dloc, expr, tmmv);
+        }
+        /* only two or three consecutive spaces available to squeeze out */
+        mungspaces(newbot2);
+    }
+    return newbot2;
 }
 
 void
 bot()
 {
-    if (youmonst.data) {
-        bot1();
-        bot2();
+    /* dosave() flags completion by setting u.uhp to -1 */
+    if ((u.uhp != -1) && youmonst.data && iflags.status_updates) {
+        if (VIA_WINDOWPORT()) {
+            bot_via_windowport();
+        } else {
+            curs(WIN_STATUS, 1, 0);
+            putstr(WIN_STATUS, 0, do_statusline1());
+            curs(WIN_STATUS, 1, 1);
+            putmixed(WIN_STATUS, 0, do_statusline2());
+        }
     }
-    context.botl = context.botlx = 0;
+    context.botl = context.botlx = iflags.time_botl = FALSE;
 }
 
-#endif /* !STATUS_VIA_WINDOWPORT */
+void
+timebot()
+{
+    if (flags.time && iflags.status_updates) {
+        if (VIA_WINDOWPORT()) {
+            stat_update_time();
+        } else {
+            /* old status display updates everything */
+            bot();
+        }
+    }
+    iflags.time_botl = FALSE;
+}
 
 /* convert experience level (1..30) to rank index (0..8) */
 int
@@ -347,19 +484,21 @@ char *buf;
     int ret = 1;
 
     /* TODO:    Add in dungeon name */
-    if (Is_knox(&u.uz))
+    if (Is_knox(&u.uz)) {
         Sprintf(buf, "%s ", dungeons[u.uz.dnum].dname);
-    else if (In_quest(&u.uz))
+    } else if (In_quest(&u.uz)) {
 /*JP
         Sprintf(buf, "Home %d ", dunlev(&u.uz));
 */
         Sprintf(buf, "\8cÌ\8b½ %d ", dunlev(&u.uz));
-    else if (In_endgame(&u.uz))
-/*JP
-        Sprintf(buf, Is_astralevel(&u.uz) ? "Astral Plane " : "End Game ");
-*/
-        Sprintf(buf, Is_astralevel(&u.uz) ? "\90¸\97ì\8aE " : "\8dÅ\8fI\8e\8e\97û ");
-    else {
+    } else if (In_endgame(&u.uz)) {
+        /* [3.6.2: this used to be "Astral Plane" or generic "End Game"] */
+        (void) endgamelevelname(buf, depth(&u.uz));
+#if 0 /*JP*/
+        (void) strsubst(buf, "Plane of ", ""); /* just keep <element> */
+#endif
+        Strcat(buf, " ");
+    } else {
         /* ports with more room may expand this one */
 /*JP
         Sprintf(buf, "Dlvl:%-2d ", depth(&u.uz));
@@ -370,164 +509,228 @@ char *buf;
     return ret;
 }
 
-#ifdef STATUS_VIA_WINDOWPORT
+/* =======================================================================*/
+/*  statusnew routines                                                    */
 /* =======================================================================*/
 
 /* structure that tracks the status details in the core */
+
+#define MAXVALWIDTH 80 /* actually less, but was using 80 to allocate title
+                        * and leveldesc then using QBUFSZ everywhere else   */
+#ifdef STATUS_HILITES
+struct hilite_s {
+    enum statusfields fld;
+    boolean set;
+    unsigned anytype;
+    anything value;
+    int behavior;
+    char textmatch[MAXVALWIDTH];
+    enum relationships rel;
+    int coloridx;
+    struct hilite_s *next;
+};
+#endif /* STATUS_HILITES */
+
 struct istat_s {
-    long time;
+    const char *fldname;
+    const char *fldfmt;
+    long time;  /* moves when this field hilite times out */
+    boolean chg; /* need to recalc time? */
+    boolean percent_matters;
+    short percent_value;
     unsigned anytype;
     anything a;
     char *val;
     int valwidth;
     enum statusfields idxmax;
     enum statusfields fld;
+#ifdef STATUS_HILITES
+    struct hilite_s *hilite_rule; /* the entry, if any, in 'thresholds'
+                                   * list that currently applies        */
+    struct hilite_s *thresholds;
+#endif
 };
 
-
+STATIC_DCL boolean FDECL(eval_notify_windowport_field, (int, boolean *, int));
+STATIC_DCL void FDECL(evaluate_and_notify_windowport, (boolean *, int));
 STATIC_DCL void NDECL(init_blstats);
+STATIC_DCL int FDECL(compare_blstats, (struct istat_s *, struct istat_s *));
 STATIC_DCL char *FDECL(anything_to_s, (char *, anything *, int));
-STATIC_DCL void FDECL(s_to_anything, (anything *, char *, int));
-STATIC_OVL int FDECL(percentage, (struct istat_s *, struct istat_s *));
-STATIC_OVL int FDECL(compare_blstats, (struct istat_s *, struct istat_s *));
+STATIC_DCL int FDECL(percentage, (struct istat_s *, struct istat_s *));
+STATIC_DCL int NDECL(exp_percentage);
 
 #ifdef STATUS_HILITES
-STATIC_DCL boolean FDECL(assign_hilite, (char *, char *, char *, char *,
-                                         BOOLEAN_P));
-STATIC_DCL const char *FDECL(clridx_to_s, (char *, int));
+STATIC_DCL void FDECL(s_to_anything, (anything *, char *, int));
+STATIC_DCL enum statusfields FDECL(fldname_to_bl_indx, (const char *));
+STATIC_DCL boolean FDECL(hilite_reset_needed, (struct istat_s *, long));
+STATIC_DCL boolean FDECL(noneoftheabove, (const char *));
+STATIC_DCL struct hilite_s *FDECL(get_hilite, (int, int, genericptr_t,
+                                               int, int, int *));
+STATIC_DCL void FDECL(split_clridx, (int, int *, int *));
+STATIC_DCL boolean FDECL(is_ltgt_percentnumber, (const char *));
+STATIC_DCL boolean FDECL(has_ltgt_percentnumber, (const char *));
+STATIC_DCL int FDECL(splitsubfields, (char *, char ***, int));
+STATIC_DCL boolean FDECL(is_fld_arrayvalues, (const char *,
+                                              const char *const *,
+                                              int, int, int *));
+STATIC_DCL int FDECL(query_arrayvalue, (const char *, const char *const *,
+                                        int, int));
+STATIC_DCL void FDECL(status_hilite_add_threshold, (int, struct hilite_s *));
+STATIC_DCL boolean FDECL(parse_status_hl2, (char (*)[QBUFSZ], BOOLEAN_P));
+STATIC_DCL char *FDECL(conditionbitmask2str, (unsigned long));
+STATIC_DCL unsigned long FDECL(match_str2conditionbitmask, (const char *));
+STATIC_DCL unsigned long FDECL(str2conditionbitmask, (char *));
+STATIC_DCL boolean FDECL(parse_condition, (char (*)[QBUFSZ], int));
+STATIC_DCL char *FDECL(hlattr2attrname, (int, char *, int));
+STATIC_DCL void FDECL(status_hilite_linestr_add, (int, struct hilite_s *,
+                                                unsigned long, const char *));
+STATIC_DCL void NDECL(status_hilite_linestr_done);
+STATIC_DCL int FDECL(status_hilite_linestr_countfield, (int));
+STATIC_DCL void NDECL(status_hilite_linestr_gather_conditions);
+STATIC_DCL void NDECL(status_hilite_linestr_gather);
+STATIC_DCL char *FDECL(status_hilite2str, (struct hilite_s *));
+STATIC_DCL int NDECL(status_hilite_menu_choose_field);
+STATIC_DCL int FDECL(status_hilite_menu_choose_behavior, (int));
+STATIC_DCL int FDECL(status_hilite_menu_choose_updownboth, (int, const char *,
+                                                       BOOLEAN_P, BOOLEAN_P));
+STATIC_DCL boolean FDECL(status_hilite_menu_add, (int));
+#define has_hilite(i) (blstats[0][(i)].thresholds)
+/* TH_UPDOWN encompasses specific 'up' and 'down' also general 'changed' */
+#define Is_Temp_Hilite(rule) ((rule) && (rule)->behavior == BL_TH_UPDOWN)
+
+/* pointers to current hilite rule and list of this field's defined rules */
+#define INIT_THRESH  , (struct hilite_s *) 0, (struct hilite_s *) 0
+#else /* !STATUS_HILITES */
+#define INIT_THRESH /*empty*/
 #endif
 
-/* If entries are added to this, botl.h will require updating too */
-STATIC_DCL struct istat_s initblstats[MAXBLSTATS] = {
-    { 0L, ANY_STR,  { (genericptr_t) 0 }, (char *) 0, 80,  0, BL_TITLE},
-    { 0L, ANY_INT,  { (genericptr_t) 0 }, (char *) 0, 10,  0, BL_STR},
-    { 0L, ANY_INT,  { (genericptr_t) 0 }, (char *) 0, 10,  0, BL_DX},
-    { 0L, ANY_INT,  { (genericptr_t) 0 }, (char *) 0, 10,  0, BL_CO},
-    { 0L, ANY_INT,  { (genericptr_t) 0 }, (char *) 0, 10,  0, BL_IN},
-    { 0L, ANY_INT,  { (genericptr_t) 0 }, (char *) 0, 10,  0, BL_WI},
-    { 0L, ANY_INT,  { (genericptr_t) 0 }, (char *) 0, 10,  0, BL_CH},
-    { 0L, ANY_STR,  { (genericptr_t) 0 }, (char *) 0, 40,  0, BL_ALIGN},
-    { 0L, ANY_LONG, { (genericptr_t) 0 }, (char *) 0, 20,  0, BL_SCORE},
-    { 0L, ANY_LONG, { (genericptr_t) 0 }, (char *) 0, 20,  0, BL_CAP},
-    { 0L, ANY_LONG, { (genericptr_t) 0 }, (char *) 0, 30,  0, BL_GOLD},
-    { 0L, ANY_INT,  { (genericptr_t) 0 }, (char *) 0, 10,  BL_ENEMAX, BL_ENE},
-    { 0L, ANY_INT,  { (genericptr_t) 0 }, (char *) 0, 10,  0, BL_ENEMAX},
-    { 0L, ANY_LONG, { (genericptr_t) 0 }, (char *) 0, 10,  0, BL_XP},
-    { 0L, ANY_INT,  { (genericptr_t) 0 }, (char *) 0, 10,  0, BL_AC},
-    { 0L, ANY_INT,  { (genericptr_t) 0 }, (char *) 0, 10,  0, BL_HD},
-    { 0L, ANY_INT,  { (genericptr_t) 0 }, (char *) 0, 20,  0, BL_TIME},
-    { 0L, ANY_UINT, { (genericptr_t) 0 }, (char *) 0, 40,  0, BL_HUNGER},
-    { 0L, ANY_INT,  { (genericptr_t) 0 }, (char *) 0, 10,  BL_HPMAX, BL_HP},
-    { 0L, ANY_INT,  { (genericptr_t) 0 }, (char *) 0, 10,  0, BL_HPMAX},
-    { 0L, ANY_STR,  { (genericptr_t) 0 }, (char *) 0, 80,  0, BL_LEVELDESC},
-    { 0L, ANY_LONG, { (genericptr_t) 0 }, (char *) 0, 20,  0, BL_EXP},
-    { 0L, ANY_MASK32,
-                    { (genericptr_t) 0 }, (char *) 0,  0,  0, BL_CONDITION}
+#define INIT_BLSTAT(name, fmtstr, anytyp, wid, fld) \
+    { name, fmtstr, 0L, FALSE, FALSE, 0, anytyp,                        \
+      { (genericptr_t) 0 }, (char *) 0,                                 \
+      wid,  -1, fld INIT_THRESH }
+#define INIT_BLSTATP(name, fmtstr, anytyp, wid, maxfld, fld) \
+    { name, fmtstr, 0L, FALSE, TRUE, 0, anytyp,                         \
+      { (genericptr_t) 0 }, (char *) 0,                                 \
+      wid,  maxfld, fld INIT_THRESH }
+
+/* If entries are added to this, botl.h will require updating too.
+   'max' value of BL_EXP gets special handling since the percentage
+   involved isn't a direct 100*current/maximum calculation. */
+STATIC_VAR struct istat_s initblstats[MAXBLSTATS] = {
+    INIT_BLSTAT("title", "%s", ANY_STR, MAXVALWIDTH, BL_TITLE),
+    INIT_BLSTAT("strength", " St:%s", ANY_INT, 10, BL_STR),
+    INIT_BLSTAT("dexterity", " Dx:%s", ANY_INT,  10, BL_DX),
+    INIT_BLSTAT("constitution", " Co:%s", ANY_INT, 10, BL_CO),
+    INIT_BLSTAT("intelligence", " In:%s", ANY_INT, 10, BL_IN),
+    INIT_BLSTAT("wisdom", " Wi:%s", ANY_INT, 10, BL_WI),
+    INIT_BLSTAT("charisma", " Ch:%s", ANY_INT, 10, BL_CH),
+    INIT_BLSTAT("alignment", " %s", ANY_STR, 40, BL_ALIGN),
+    INIT_BLSTAT("score", " S:%s", ANY_LONG, 20, BL_SCORE),
+    INIT_BLSTAT("carrying-capacity", " %s", ANY_INT, 20, BL_CAP),
+    INIT_BLSTAT("gold", " %s", ANY_LONG, 30, BL_GOLD),
+    INIT_BLSTATP("power", " Pw:%s", ANY_INT, 10, BL_ENEMAX, BL_ENE),
+    INIT_BLSTAT("power-max", "(%s)", ANY_INT, 10, BL_ENEMAX),
+    INIT_BLSTATP("experience-level", " Xp:%s", ANY_INT, 10, BL_EXP, BL_XP),
+    INIT_BLSTAT("armor-class", " AC:%s", ANY_INT, 10, BL_AC),
+    INIT_BLSTAT("HD", " HD:%s", ANY_INT, 10, BL_HD),
+    INIT_BLSTAT("time", " T:%s", ANY_LONG, 20, BL_TIME),
+    /* hunger used to be 'ANY_UINT'; see note below in bot_via_windowport() */
+    INIT_BLSTAT("hunger", " %s", ANY_INT, 40, BL_HUNGER),
+    INIT_BLSTATP("hitpoints", " HP:%s", ANY_INT, 10, BL_HPMAX, BL_HP),
+    INIT_BLSTAT("hitpoints-max", "(%s)", ANY_INT, 10, BL_HPMAX),
+    INIT_BLSTAT("dungeon-level", "%s", ANY_STR, MAXVALWIDTH, BL_LEVELDESC),
+    INIT_BLSTATP("experience", "/%s", ANY_LONG, 20, BL_EXP, BL_EXP),
+    INIT_BLSTAT("condition", "%s", ANY_MASK32, 0, BL_CONDITION)
 };
 
-static struct fieldid_t {
-        const char *fieldname;
-        enum statusfields fldid;
-} fieldids[] = {
-        {"title",               BL_TITLE},
-        {"strength",            BL_STR},
-        {"dexterity",           BL_DX},
-        {"constitution",        BL_CO},
-        {"intelligence",        BL_IN},
-        {"wisdom",              BL_WI},
-        {"charisma",            BL_CH},
-        {"alignment",           BL_ALIGN},
-        {"score",               BL_SCORE},
-        {"carrying-capacity",   BL_CAP},
-        {"gold",                BL_GOLD},
-        {"power",               BL_ENE},
-        {"power-max",           BL_ENEMAX},
-        {"experience-level",    BL_XP},
-        {"armor-class",         BL_AC},
-        {"HD",                  BL_HD},
-        {"time",                BL_TIME},
-        {"hunger",              BL_HUNGER},
-        {"hitpoints",           BL_HP},
-        {"hitpoints-max",       BL_HPMAX},
-        {"dungeon-level",       BL_LEVELDESC},
-        {"experience",          BL_EXP},
-        {"condition",           BL_CONDITION},
-};
+#undef INIT_BLSTATP
+#undef INIT_BLSTAT
+#undef INIT_THRESH
 
 struct istat_s blstats[2][MAXBLSTATS];
 static boolean blinit = FALSE, update_all = FALSE;
+static boolean valset[MAXBLSTATS];
+#ifdef STATUS_HILITES
+static long bl_hilite_moves = 0L;
+#endif
 
-void
-bot()
+/* we don't put this next declaration in #ifdef STATUS_HILITES.
+ * In the absence of STATUS_HILITES, each array
+ * element will be 0 however, and quite meaningless,
+ * but we need to pass the first array element as
+ * the final argument of status_update, with or
+ * without STATUS_HILITES.
+ */
+static unsigned long cond_hilites[BL_ATTCLR_MAX];
+static int now_or_before_idx = 0; /* 0..1 for array[2][] first index */
+
+STATIC_OVL void
+bot_via_windowport()
 {
     char buf[BUFSZ];
+    const char *titl;
     register char *nb;
-    static int idx = 0, idx_p, idxmax;
-    boolean updated = FALSE;
-    unsigned anytype;
-    int i, pc, chg, cap;
-    struct istat_s *curr, *prev;
-    boolean valset[MAXBLSTATS], chgval = FALSE;
+    int i, idx, cap;
+    long money;
 
     if (!blinit)
         panic("bot before init.");
-    if (!youmonst.data) {
-        context.botl = context.botlx = 0;
-        update_all = FALSE;
-        return;
-    }
 
-    cap = near_capacity();
-    idx_p = idx;
-    idx = 1 - idx; /* 0 -> 1, 1 -> 0 */
+    /* toggle from previous iteration */
+    idx = 1 - now_or_before_idx; /* 0 -> 1, 1 -> 0 */
+    now_or_before_idx = idx;
 
     /* clear the "value set" indicators */
-    (void) memset((genericptr_t) valset, 0, MAXBLSTATS * sizeof(boolean));
+    (void) memset((genericptr_t) valset, 0, MAXBLSTATS * sizeof (boolean));
 
     /*
-     *  Player name and title.
+     * Note: min(x,9999) - we enforce the same maximum on hp, maxhp,
+     * pw, maxpw, and gold as basic status formatting so that the two
+     * modes of status display don't produce different information.
      */
-    buf[0] = '\0';
-    Strcpy(buf, plname);
-    if ('a' <= buf[0] && buf[0] <= 'z')
-        buf[0] += 'A' - 'a';
-    buf[10] = 0;
-    Sprintf(nb = eos(buf), " the ");
-    if (Upolyd) {
-        char mbot[BUFSZ];
-        int k = 0;
 
-        Strcpy(mbot, mons[u.umonnum].mname);
-        while (mbot[k] != 0) {
-            if ((k == 0 || (k > 0 && mbot[k - 1] == ' ')) && 'a' <= mbot[k]
-                && mbot[k] <= 'z')
-                mbot[k] += 'A' - 'a';
-            k++;
-        }
-        Sprintf1(nb = eos(nb), mbot);
-    } else
-        Sprintf1(nb = eos(nb), rank());
-    Sprintf(blstats[idx][BL_TITLE].val, "%-29s", buf);
+    /*
+     *  Player name and title.
+     */
+    Strcpy(nb = buf, plname);
+    nb[0] = highc(nb[0]);
+    titl = !Upolyd ? rank() : mons[u.umonnum].mname;
+#if 0 /*JP*/
+    i = (int) (strlen(buf) + sizeof " the " + strlen(titl) - sizeof "");
+#else
+    i = (int) (strlen(buf) + sizeof " " + strlen(titl) - sizeof "");
+#endif
+    /* if "Name the Rank/monster" is too long, we truncate the name
+       but always keep at least 10 characters of it; when hitpintbar is
+       enabled, anything beyond 30 (long monster name) will be truncated */
+    if (i > 30) {
+#if 0 /*JP*/
+        i = 30 - (int) (sizeof " the " + strlen(titl) - sizeof "");
+#else
+        i = 30 - (int) (sizeof " " + strlen(titl) - sizeof "");
+#endif
+        nb[max(i, 10)] = '\0';
+    }
+#if 0 /*JP*/
+    Strcpy(nb = eos(nb), " the ");
+#else
+    Strcpy(nb = eos(nb), " ");
+#endif
+    Strcpy(nb = eos(nb), titl);
+    if (Upolyd) { /* when poly'd, capitalize monster name */
+        for (i = 0; nb[i]; i++)
+            if (i == 0 || nb[i - 1] == ' ')
+                nb[i] = highc(nb[i]);
+    }
+    Sprintf(blstats[idx][BL_TITLE].val, "%-30s", buf);
     valset[BL_TITLE] = TRUE; /* indicate val already set */
 
     /* Strength */
-
-    buf[0] = '\0';
     blstats[idx][BL_STR].a.a_int = ACURR(A_STR);
-    if (ACURR(A_STR) > 18) {
-        if (ACURR(A_STR) > STR18(100))
-            Sprintf(buf, "%2d", ACURR(A_STR) - 100);
-        else if (ACURR(A_STR) < STR18(100))
-            Sprintf(buf, "18/%02d", ACURR(A_STR) - 18);
-        else
-            Sprintf(buf, "18/**");
-    } else
-        Sprintf(buf, "%-1d", ACURR(A_STR));
-    Strcpy(blstats[idx][BL_STR].val, buf);
+    Strcpy(blstats[idx][BL_STR].val, get_strength_str());
     valset[BL_STR] = TRUE; /* indicate val already set */
 
     /*  Dexterity, constitution, intelligence, wisdom, charisma. */
-
     blstats[idx][BL_DX].a.a_int = ACURR(A_DEX);
     blstats[idx][BL_CO].a.a_int = ACURR(A_CON);
     blstats[idx][BL_IN].a.a_int = ACURR(A_INT);
@@ -535,130 +738,276 @@ bot()
     blstats[idx][BL_CH].a.a_int = ACURR(A_CHA);
 
     /* Alignment */
-
-    Strcpy(blstats[idx][BL_ALIGN].val,
-           (u.ualign.type == A_CHAOTIC)
-               ? "Chaotic"
-               : (u.ualign.type == A_NEUTRAL) ? "Neutral" : "Lawful");
+#if 0 /*JP:T*/
+    Strcpy(blstats[idx][BL_ALIGN].val, (u.ualign.type == A_CHAOTIC)
+                                          ? "Chaotic"
+                                          : (u.ualign.type == A_NEUTRAL)
+                                               ? "Neutral"
+                                               : "Lawful");
+#else
+    Strcpy(blstats[idx][BL_ALIGN].val, (u.ualign.type == A_CHAOTIC)
+                                          ? "\8d¬\93×"
+                                          : (u.ualign.type == A_NEUTRAL)
+                                               ? "\92\86\97§"
+                                               : "\92\81\8f\98");
+#endif
 
     /* Score */
-
     blstats[idx][BL_SCORE].a.a_long =
 #ifdef SCORE_ON_BOTL
-        botl_score();
-#else
-        0;
+        flags.showscore ? botl_score() :
 #endif
-    /*  Hit points  */
+        0L;
 
-    blstats[idx][BL_HP].a.a_int = Upolyd ? u.mh : u.uhp;
-    blstats[idx][BL_HPMAX].a.a_int = Upolyd ? u.mhmax : u.uhpmax;
-    if (blstats[idx][BL_HP].a.a_int < 0)
-        blstats[idx][BL_HP].a.a_int = 0;
+    /*  Hit points  */
+    i = Upolyd ? u.mh : u.uhp;
+    if (i < 0)
+        i = 0;
+    blstats[idx][BL_HP].a.a_int = min(i, 9999);
+    i = Upolyd ? u.mhmax : u.uhpmax;
+    blstats[idx][BL_HPMAX].a.a_int = min(i, 9999);
 
     /*  Dungeon level. */
-
     (void) describe_level(blstats[idx][BL_LEVELDESC].val);
     valset[BL_LEVELDESC] = TRUE; /* indicate val already set */
 
     /* Gold */
-
-    blstats[idx][BL_GOLD].a.a_long = money_cnt(invent);
+    if ((money = money_cnt(invent)) < 0L)
+        money = 0L; /* ought to issue impossible() and then discard gold */
+    blstats[idx][BL_GOLD].a.a_long = min(money, 999999L);
     /*
      * The tty port needs to display the current symbol for gold
      * as a field header, so to accommodate that we pass gold with
      * that already included. If a window port needs to use the text
      * gold amount without the leading "$:" the port will have to
-     * add 2 to the value pointer it was passed in status_update()
+     * skip past ':' to the value pointer it was passed in status_update()
      * for the BL_GOLD case.
      *
      * Another quirk of BL_GOLD is that the field display may have
      * changed if a new symbol set was loaded, or we entered or left
      * the rogue level.
+     *
+     * The currency prefix is encoded as ten character \GXXXXNNNN
+     * sequence.
      */
-
     Sprintf(blstats[idx][BL_GOLD].val, "%s:%ld",
-            encglyph(objnum_to_glyph(GOLD_PIECE)),
+            (iflags.in_dumplog || iflags.invis_goldsym) ? "$"
+              : encglyph(objnum_to_glyph(GOLD_PIECE)),
             blstats[idx][BL_GOLD].a.a_long);
     valset[BL_GOLD] = TRUE; /* indicate val already set */
 
     /* Power (magical energy) */
-
-    blstats[idx][BL_ENE].a.a_int = u.uen;
-    blstats[idx][BL_ENEMAX].a.a_int = u.uenmax;
+    blstats[idx][BL_ENE].a.a_int = min(u.uen, 9999);
+    blstats[idx][BL_ENEMAX].a.a_int = min(u.uenmax, 9999);
 
     /* Armor class */
-
     blstats[idx][BL_AC].a.a_int = u.uac;
 
     /* Monster level (if Upolyd) */
-
-    if (Upolyd)
-        blstats[idx][BL_HD].a.a_int = mons[u.umonnum].mlevel;
-    else
-        blstats[idx][BL_HD].a.a_int = 0;
+    blstats[idx][BL_HD].a.a_int = Upolyd ? (int) mons[u.umonnum].mlevel : 0;
 
     /* Experience */
-
     blstats[idx][BL_XP].a.a_int = u.ulevel;
-    blstats[idx][BL_EXP].a.a_int = u.uexp;
+    blstats[idx][BL_EXP].a.a_long = u.uexp;
 
     /* Time (moves) */
-
     blstats[idx][BL_TIME].a.a_long = moves;
 
     /* Hunger */
-
-    blstats[idx][BL_HUNGER].a.a_uint = u.uhs;
-    *(blstats[idx][BL_HUNGER].val) = '\0';
-    if (strcmp(hu_stat[u.uhs], "        ") != 0)
-        Strcpy(blstats[idx][BL_HUNGER].val, hu_stat[u.uhs]);
+    /* note: u.uhs is unsigned, and 3.6.1's STATUS_HILITE defined
+       BL_HUNGER to be ANY_UINT, but that was the only non-int/non-long
+       numeric field so it's far simpler to treat it as plain int and
+       not need ANY_UINT handling at all */
+    blstats[idx][BL_HUNGER].a.a_int = (int) u.uhs;
+    Strcpy(blstats[idx][BL_HUNGER].val,
+           (u.uhs != NOT_HUNGRY) ? hu_stat[u.uhs] : "");
     valset[BL_HUNGER] = TRUE;
 
     /* Carrying capacity */
-
-    *(blstats[idx][BL_CAP].val) = '\0';
+    cap = near_capacity();
     blstats[idx][BL_CAP].a.a_int = cap;
-    if (cap > UNENCUMBERED)
-        Strcpy(blstats[idx][BL_CAP].val, enc_stat[cap]);
+    Strcpy(blstats[idx][BL_CAP].val,
+           (cap > UNENCUMBERED) ? enc_stat[cap] : "");
     valset[BL_CAP] = TRUE;
 
     /* Conditions */
-
+    blstats[idx][BL_CONDITION].a.a_ulong = 0L;
+    if (Stoned)
+        blstats[idx][BL_CONDITION].a.a_ulong |= BL_MASK_STONE;
+    if (Slimed)
+        blstats[idx][BL_CONDITION].a.a_ulong |= BL_MASK_SLIME;
+    if (Strangled)
+        blstats[idx][BL_CONDITION].a.a_ulong |= BL_MASK_STRNGL;
+    if (Sick && (u.usick_type & SICK_VOMITABLE) != 0)
+        blstats[idx][BL_CONDITION].a.a_ulong |= BL_MASK_FOODPOIS;
+    if (Sick && (u.usick_type & SICK_NONVOMITABLE) != 0)
+        blstats[idx][BL_CONDITION].a.a_ulong |= BL_MASK_TERMILL;
+    /*
+     * basic formatting puts hunger status and encumbrance here
+     */
     if (Blind)
         blstats[idx][BL_CONDITION].a.a_ulong |= BL_MASK_BLIND;
-    else
-        blstats[idx][BL_CONDITION].a.a_ulong &= ~BL_MASK_BLIND;
-
+    if (Deaf)
+        blstats[idx][BL_CONDITION].a.a_ulong |= BL_MASK_DEAF;
+    if (Stunned)
+        blstats[idx][BL_CONDITION].a.a_ulong |= BL_MASK_STUN;
     if (Confusion)
         blstats[idx][BL_CONDITION].a.a_ulong |= BL_MASK_CONF;
-    else
-        blstats[idx][BL_CONDITION].a.a_ulong &= ~BL_MASK_CONF;
+    if (Hallucination)
+        blstats[idx][BL_CONDITION].a.a_ulong |= BL_MASK_HALLU;
+    /* levitation and flying are mututally exclusive */
+    if (Levitation)
+        blstats[idx][BL_CONDITION].a.a_ulong |= BL_MASK_LEV;
+    if (Flying)
+        blstats[idx][BL_CONDITION].a.a_ulong |= BL_MASK_FLY;
+    if (u.usteed)
+        blstats[idx][BL_CONDITION].a.a_ulong |= BL_MASK_RIDE;
+    evaluate_and_notify_windowport(valset, idx);
+}
 
-    if (Sick && u.usick_type & SICK_VOMITABLE)
-        blstats[idx][BL_CONDITION].a.a_ulong |= BL_MASK_FOODPOIS;
-    else
-        blstats[idx][BL_CONDITION].a.a_ulong &= ~BL_MASK_FOODPOIS;
+/* update just the status lines' 'time' field */
+STATIC_OVL void
+stat_update_time()
+{
+    int idx = now_or_before_idx; /* no 0/1 toggle */
+    int fld = BL_TIME;
 
-    if (Sick && u.usick_type & SICK_NONVOMITABLE)
-        blstats[idx][BL_CONDITION].a.a_ulong |= BL_MASK_ILL;
-    else
-        blstats[idx][BL_CONDITION].a.a_ulong &= ~BL_MASK_ILL;
+    /* Time (moves) */
+    blstats[idx][fld].a.a_long = moves;
+    valset[fld] = FALSE;
 
-    if (Hallucination)
-        blstats[idx][BL_CONDITION].a.a_ulong |= BL_MASK_HALLU;
-    else
-        blstats[idx][BL_CONDITION].a.a_ulong &= ~BL_MASK_HALLU;
+    eval_notify_windowport_field(fld, valset, idx);
+    if ((windowprocs.wincap2 & WC2_FLUSH_STATUS) != 0L)
+        status_update(BL_FLUSH, (genericptr_t) 0, 0, 0,
+                      NO_COLOR, (unsigned long *) 0);
+    return;
+}
 
-    if (Stunned)
-        blstats[idx][BL_CONDITION].a.a_ulong |= BL_MASK_STUNNED;
-    else
-        blstats[idx][BL_CONDITION].a.a_ulong &= ~BL_MASK_STUNNED;
+STATIC_OVL boolean
+eval_notify_windowport_field(fld, valsetlist, idx)
+int fld, idx;
+boolean *valsetlist;
+{
+    static int oldrndencode = 0;
+    static nhsym oldgoldsym = 0;
+    int pc, chg, color = NO_COLOR;
+    unsigned anytype;
+    boolean updated = FALSE, reset;
+    struct istat_s *curr, *prev;
+    enum statusfields fldmax;
 
-    if (Slimed)
-        blstats[idx][BL_CONDITION].a.a_ulong |= BL_MASK_SLIMED;
-    else
-        blstats[idx][BL_CONDITION].a.a_ulong &= ~BL_MASK_SLIMED;
+    /*
+     *  Now pass the changed values to window port.
+     */
+    anytype = blstats[idx][fld].anytype;
+    curr = &blstats[idx][fld];
+    prev = &blstats[1 - idx][fld];
+    color = NO_COLOR;
+
+    chg = update_all ? 0 : compare_blstats(prev, curr);
+    /*
+     * TODO:
+     *  Dynamically update 'percent_matters' as rules are added or
+     *  removed to track whether any of them are precentage rules.
+     *  Then there'll be no need to assume that non-Null 'thresholds'
+     *  means that percentages need to be kept up to date.
+     *  [Affects exp_percent_changing() too.]
+     */
+    if (((chg || update_all || fld == BL_XP)
+         && curr->percent_matters && curr->thresholds)
+        /* when 'hitpointbar' is On, percent matters even if HP
+           hasn't changed and has no percentage rules (in case HPmax
+           has changed when HP hasn't, where we ordinarily wouldn't
+           update HP so would miss an update of the hitpoint bar) */
+        || (fld == BL_HP && iflags.wc2_hitpointbar)) {
+        fldmax = curr->idxmax;
+        pc = (fldmax == BL_EXP) ? exp_percentage()
+             : (fldmax >= 0) ? percentage(curr, &blstats[idx][fldmax])
+               : 0; /* bullet proofing; can't get here */
+        if (pc != prev->percent_value)
+            chg = 1;
+        curr->percent_value = pc;
+    } else {
+        pc = 0;
+    }
+
+    /* Temporary? hack: moveloop()'s prolog for a new game sets
+     * context.rndencode after the status window has been init'd,
+     * so $:0 has already been encoded and cached by the window
+     * port.  Without this hack, gold's \G sequence won't be
+     * recognized and ends up being displayed as-is for 'update_all'.
+     *
+     * Also, even if context.rndencode hasn't changed and the
+     * gold amount itself hasn't changed, the glyph portion of the
+     * encoding may have changed if a new symset was put into effect.
+     *
+     *  \GXXXXNNNN:25
+     *  XXXX = the context.rndencode portion
+     *  NNNN = the glyph portion
+     *  25   = the gold amount
+     *
+     * Setting 'chg = 2' is enough to render the field properly, but
+     * not to honor an initial highlight, so force 'update_all = TRUE'.
+     */
+    if (fld == BL_GOLD
+        && (context.rndencode != oldrndencode
+            || showsyms[COIN_CLASS + SYM_OFF_O] != oldgoldsym)) {
+        update_all = TRUE; /* chg = 2; */
+        oldrndencode = context.rndencode;
+        oldgoldsym = showsyms[COIN_CLASS + SYM_OFF_O];
+    }
+
+    reset = FALSE;
+#ifdef STATUS_HILITES
+    if (!update_all && !chg && curr->time) {
+        reset = hilite_reset_needed(prev, bl_hilite_moves);
+        if (reset)
+            curr->time = prev->time = 0L;
+    }
+#endif
+
+    if (update_all || chg || reset) {
+        if (!valsetlist[fld])
+            (void) anything_to_s(curr->val, &curr->a, anytype);
+
+        if (anytype != ANY_MASK32) {
+#ifdef STATUS_HILITES
+            if (chg || *curr->val) {
+                /* if Xp percentage changed, we set 'chg' to 1 above;
+                   reset that if the Xp value hasn't actually changed
+                   or possibly went down rather than up (level loss) */
+                if (chg == 1 && fld == BL_XP)
+                    chg = compare_blstats(prev, curr);
+
+                curr->hilite_rule = get_hilite(idx, fld,
+                                               (genericptr_t) &curr->a,
+                                               chg, pc, &color);
+                prev->hilite_rule = curr->hilite_rule;
+                if (chg == 2) {
+                    color = NO_COLOR;
+                    chg = 0;
+                }
+            }
+#endif /* STATUS_HILITES */
+            status_update(fld, (genericptr_t) curr->val,
+                          chg, pc, color, (unsigned long *) 0);
+        } else {
+            /* Color for conditions is done through cond_hilites[] */
+            status_update(fld, (genericptr_t) &curr->a.a_ulong,
+                          chg, pc, color, cond_hilites);
+        }
+        curr->chg = prev->chg = TRUE;
+        updated = TRUE;
+    }
+    return updated;
+}
+
+STATIC_OVL void
+evaluate_and_notify_windowport(valsetlist, idx)
+int idx;
+boolean *valsetlist;
+{
+    int i, updated = 0, notpresent = 0;
 
     /*
      *  Now pass the changed values to window port.
@@ -668,197 +1017,83 @@ bot()
             || ((i == BL_EXP) && !flags.showexp)
             || ((i == BL_TIME) && !flags.time)
             || ((i == BL_HD) && !Upolyd)
-            || ((i == BL_XP || i == BL_EXP) && Upolyd))
+            || ((i == BL_XP || i == BL_EXP) && Upolyd)) {
+            notpresent++;
             continue;
-        anytype = blstats[idx][i].anytype;
-        curr = &blstats[idx][i];
-        prev = &blstats[idx_p][i];
-        chg = 0;
-        if (update_all || ((chg = compare_blstats(prev, curr)) != 0)
-            || ((chgval = (valset[i] && strcmp(blstats[idx][i].val,
-                                               blstats[idx_p][i].val)))
-                != 0)) {
-            idxmax = blstats[idx][i].idxmax;
-            pc = (idxmax) ? percentage(curr, &blstats[idx][idxmax]) : 0;
-            if (!valset[i])
-                (void) anything_to_s(curr->val, &curr->a, anytype);
-            if (anytype != ANY_MASK32) {
-                status_update(i, (genericptr_t) curr->val,
-                              valset[i] ? chgval : chg, pc);
-            } else {
-                status_update(i,
-                              /* send pointer to mask */
-                              (genericptr_t) &curr->a.a_ulong, chg, 0);
-            }
-            updated = TRUE;
         }
+        if (eval_notify_windowport_field(i, valsetlist, idx))
+            updated++;
     }
     /*
-     * It is possible to get here, with nothing having been pushed
-     * to the window port, when none of the info has changed. In that
-     * case, we need to force a call to status_update() when
-     * context.botlx is set. The tty port in particular has a problem
-     * if that isn't done, since it sets context.botlx when a menu or
-     * text display obliterates the status line.
+     * Notes:
+     *  1. It is possible to get here, with nothing having been pushed
+     *     to the window port, when none of the info has changed.
+     *
+     *  2. Some window ports are also known to optimize by only drawing
+     *     fields that have changed since the previous update.
+     *
+     * In both of those situations, we need to force updates to
+     * all of the fields when context.botlx is set. The tty port in
+     * particular has a problem if that isn't done, since the core sets
+     * context.botlx when a menu or text display obliterates the status
+     * line.
+     *
+     * For those situations, to trigger the full update of every field
+     * whether changed or not, call status_update() with BL_RESET.
+     *
+     * For regular processing and to notify the window port that a
+     * bot() round has finished and it's time to trigger a flush of
+     * all buffered changes received thus far but not reflected in
+     * the display, call status_update() with BL_FLUSH.
      *
-     * To work around it, we call status_update() with fictitious
-     * index of BL_FLUSH (-1).
      */
-    if (context.botlx && !updated)
-        status_update(BL_FLUSH, (genericptr_t) 0, 0, 0);
-
-    context.botl = context.botlx = 0;
+    if (context.botlx && (windowprocs.wincap2 & WC2_RESET_STATUS) != 0L)
+        status_update(BL_RESET, (genericptr_t) 0, 0, 0,
+                      NO_COLOR, (unsigned long *) 0);
+    else if ((updated || context.botlx)
+             && (windowprocs.wincap2 & WC2_FLUSH_STATUS) != 0L)
+        status_update(BL_FLUSH, (genericptr_t) 0, 0, 0,
+                      NO_COLOR, (unsigned long *) 0);
+
+    context.botl = context.botlx = iflags.time_botl = FALSE;
     update_all = FALSE;
 }
 
 void
 status_initialize(reassessment)
-boolean
-    reassessment; /* TRUE = just reassess fields w/o other initialization*/
+boolean reassessment; /* TRUE: just recheck fields w/o other initialization */
 {
+    enum statusfields fld;
+    boolean fldenabl;
     int i;
-    const char *fieldfmt = (const char *)0;
-    const char *fieldname = (const char *)0;
+    const char *fieldfmt, *fieldname;
 
     if (!reassessment) {
+        if (blinit)
+            impossible("2nd status_initialize with full init.");
         init_blstats();
         (*windowprocs.win_status_init)();
         blinit = TRUE;
-#ifdef STATUS_HILITES
-        status_notify_windowport(TRUE);
-#endif
+    } else if (!blinit) {
+        panic("status 'reassess' before init");
     }
     for (i = 0; i < MAXBLSTATS; ++i) {
-        enum statusfields fld = initblstats[i].fld;
-
-        switch (fld) {
-        case BL_TITLE:
-            fieldfmt = "%s";
-            fieldname = "title";
-            status_enablefield(fld, fieldname, fieldfmt, TRUE);
-            break;
-        case BL_STR:
-            fieldfmt = " St:%s";
-            fieldname = "strength";
-            status_enablefield(fld, fieldname, fieldfmt, TRUE);
-            break;
-        case BL_DX:
-            fieldfmt = " Dx:%s";
-            fieldname = "dexterity";
-            status_enablefield(fld, fieldname, fieldfmt, TRUE);
-            break;
-        case BL_CO:
-            fieldfmt = " Co:%s";
-            fieldname = "constitution";
-            status_enablefield(fld, fieldname, fieldfmt, TRUE);
-            break;
-        case BL_IN:
-            fieldfmt = " In:%s";
-            fieldname = "intelligence";
-            status_enablefield(fld, fieldname, fieldfmt, TRUE);
-            break;
-        case BL_WI:
-            fieldfmt = " Wi:%s";
-            fieldname = "wisdom";
-            status_enablefield(fld, fieldname, fieldfmt, TRUE);
-            break;
-        case BL_CH:
-            fieldfmt = " Ch:%s";
-            fieldname = "charisma";
-            status_enablefield(fld, fieldname, fieldfmt, TRUE);
-            break;
-        case BL_ALIGN:
-            fieldfmt = " %s";
-            fieldname = "alignment";
-            status_enablefield(fld, fieldname, fieldfmt, TRUE);
-            break;
-        case BL_SCORE:
-            fieldfmt = " S:%s";
-            fieldname = "score";
-            status_enablefield(fld, fieldname, fieldfmt,
-                               (!flags.showscore) ? FALSE : TRUE);
-            break;
-        case BL_CAP:
-            fieldfmt = " %s";
-            fieldname = "carrying-capacity";
-            status_enablefield(fld, fieldname, fieldfmt, TRUE);
-            break;
-        case BL_GOLD:
-            fieldfmt = " %s";
-            fieldname = "gold";
-            status_enablefield(fld, fieldname, fieldfmt, TRUE);
-            break;
-        case BL_ENE:
-            fieldfmt = " Pw:%s";
-            fieldname = "power";
-            status_enablefield(fld, fieldname, fieldfmt, TRUE);
-            break;
-        case BL_ENEMAX:
-            fieldfmt = "(%s)";
-            fieldname = "power-max";
-            status_enablefield(fld, fieldname, fieldfmt, TRUE);
-            break;
-        case BL_XP:
-            fieldfmt = " Xp:%s";
-            fieldname = "experience-level";
-            status_enablefield(fld, fieldname, fieldfmt,
-                                   (Upolyd) ? FALSE : TRUE);
-            break;
-        case BL_AC:
-            fieldfmt = " AC:%s";
-            fieldname = "armor-class";
-            status_enablefield(fld, fieldname, fieldfmt, TRUE);
-            break;
-        case BL_HD:
-            fieldfmt = " HD:%s";
-            fieldname = "HD";
-            status_enablefield(fld, fieldname, fieldfmt,
-                                   (!Upolyd) ? FALSE : TRUE);
-            break;
-        case BL_TIME:
-            fieldfmt = " T:%s";
-            fieldname = "time";
-            status_enablefield(fld, fieldname, fieldfmt,
-                                   (!flags.time) ? FALSE : TRUE);
-            break;
-        case BL_HUNGER:
-            fieldfmt = " %s";
-            fieldname = "hunger";
-            status_enablefield(fld, fieldname, fieldfmt, TRUE);
-            break;
-        case BL_HP:
-            fieldfmt = " HP:%s";
-            fieldname = "hitpoints";
-            status_enablefield(fld, fieldname, fieldfmt, TRUE);
-            break;
-        case BL_HPMAX:
-            fieldfmt = "(%s)";
-            fieldname = "hitpoint-max";
-            status_enablefield(fld, fieldname, fieldfmt, TRUE);
-            break;
-        case BL_LEVELDESC:
-            fieldfmt = "%s";
-            fieldname = "dungeon-level";
-            status_enablefield(fld, fieldname, fieldfmt, TRUE);
-            break;
-        case BL_EXP:
-            fieldfmt = "/%s";
-            fieldname = "experience";
-            status_enablefield(fld, fieldname, fieldfmt,
-                                  (!flags.showexp || Upolyd) ? FALSE : TRUE);
-            break;
-        case BL_CONDITION:
-            fieldfmt = "%S";
-            fieldname = "condition";
-            status_enablefield(fld, fieldname, fieldfmt, TRUE);
-            break;
-        case BL_FLUSH:
-        default:
-            break;
-        }
-    }
-    update_all = TRUE;
-}
+        fld = initblstats[i].fld;
+        fldenabl = (fld == BL_SCORE) ? flags.showscore
+                   : (fld == BL_TIME) ? flags.time
+                     : (fld == BL_EXP) ? (boolean) (flags.showexp && !Upolyd)
+                       : (fld == BL_XP) ? (boolean) !Upolyd
+                         : (fld == BL_HD) ? (boolean) Upolyd
+                           : TRUE;
+
+        fieldname = initblstats[i].fldname;
+        fieldfmt = (fld == BL_TITLE && iflags.wc2_hitpointbar) ? "%-30.30s"
+                   : initblstats[i].fldfmt;
+        status_enablefield(fld, fieldname, fieldfmt, fldenabl);
+    }
+    update_all = TRUE;
+    context.botlx = TRUE;
+}
 
 void
 status_finish()
@@ -866,14 +1101,29 @@ status_finish()
     int i;
 
     /* call the window port cleanup routine first */
-    (*windowprocs.win_status_finish)();
+    if (windowprocs.win_status_finish)
+        (*windowprocs.win_status_finish)();
 
     /* free memory that we alloc'd now */
     for (i = 0; i < MAXBLSTATS; ++i) {
         if (blstats[0][i].val)
-            free((genericptr_t) blstats[0][i].val);
+            free((genericptr_t) blstats[0][i].val), blstats[0][i].val = 0;
         if (blstats[1][i].val)
-            free((genericptr_t) blstats[1][i].val);
+            free((genericptr_t) blstats[1][i].val), blstats[1][i].val = 0;
+#ifdef STATUS_HILITES
+        /* pointer to an entry in thresholds list; Null it out since
+           that list is about to go away */
+        blstats[0][i].hilite_rule = blstats[1][i].hilite_rule = 0;
+        if (blstats[0][i].thresholds) {
+            struct hilite_s *temp, *next;
+
+            for (temp = blstats[0][i].thresholds; temp; temp = next) {
+                next = temp->next;
+                free((genericptr_t) temp);
+            }
+            blstats[0][i].thresholds = blstats[1][i].thresholds = 0;
+        }
+#endif /* STATUS_HILITES */
     }
 }
 
@@ -887,10 +1137,12 @@ init_blstats()
         impossible("init_blstats called more than once.");
         return;
     }
-
-    initalready = TRUE;
-    for (i = BEFORE; i <= NOW; ++i) {
+    for (i = 0; i <= 1; ++i) {
         for (j = 0; j < MAXBLSTATS; ++j) {
+#ifdef STATUS_HILITES
+            struct hilite_s *keep_hilite_chain = blstats[i][j].thresholds;
+#endif
+
             blstats[i][j] = initblstats[j];
             blstats[i][j].a = zeroany;
             if (blstats[i][j].valwidth) {
@@ -898,8 +1150,103 @@ init_blstats()
                 blstats[i][j].val[0] = '\0';
             } else
                 blstats[i][j].val = (char *) 0;
+#ifdef STATUS_HILITES
+            blstats[i][j].thresholds = keep_hilite_chain;
+#endif
         }
     }
+    initalready = TRUE;
+}
+
+/*
+ * This compares the previous stat with the current stat,
+ * and returns one of the following results based on that:
+ *
+ *   if prev_value < new_value (stat went up, increased)
+ *      return 1
+ *
+ *   if prev_value > new_value (stat went down, decreased)
+ *      return  -1
+ *
+ *   if prev_value == new_value (stat stayed the same)
+ *      return 0
+ *
+ *   Special cases:
+ *     - for bitmasks, 0 = stayed the same, 1 = changed
+ *     - for strings,  0 = stayed the same, 1 = changed
+ *
+ */
+STATIC_OVL int
+compare_blstats(bl1, bl2)
+struct istat_s *bl1, *bl2;
+{
+    int anytype, result = 0;
+
+    if (!bl1 || !bl2) {
+        panic("compare_blstat: bad istat pointer %s, %s",
+              fmt_ptr((genericptr_t) bl1), fmt_ptr((genericptr_t) bl2));
+    }
+
+    anytype = bl1->anytype;
+    if ((!bl1->a.a_void || !bl2->a.a_void)
+        && (anytype == ANY_IPTR || anytype == ANY_UPTR || anytype == ANY_LPTR
+            || anytype == ANY_ULPTR)) {
+        panic("compare_blstat: invalid pointer %s, %s",
+              fmt_ptr((genericptr_t) bl1->a.a_void),
+              fmt_ptr((genericptr_t) bl2->a.a_void));
+    }
+
+    switch (anytype) {
+    case ANY_INT:
+        result = (bl1->a.a_int < bl2->a.a_int)
+                     ? 1
+                     : (bl1->a.a_int > bl2->a.a_int) ? -1 : 0;
+        break;
+    case ANY_IPTR:
+        result = (*bl1->a.a_iptr < *bl2->a.a_iptr)
+                     ? 1
+                     : (*bl1->a.a_iptr > *bl2->a.a_iptr) ? -1 : 0;
+        break;
+    case ANY_LONG:
+        result = (bl1->a.a_long < bl2->a.a_long)
+                     ? 1
+                     : (bl1->a.a_long > bl2->a.a_long) ? -1 : 0;
+        break;
+    case ANY_LPTR:
+        result = (*bl1->a.a_lptr < *bl2->a.a_lptr)
+                     ? 1
+                     : (*bl1->a.a_lptr > *bl2->a.a_lptr) ? -1 : 0;
+        break;
+    case ANY_UINT:
+        result = (bl1->a.a_uint < bl2->a.a_uint)
+                     ? 1
+                     : (bl1->a.a_uint > bl2->a.a_uint) ? -1 : 0;
+        break;
+    case ANY_UPTR:
+        result = (*bl1->a.a_uptr < *bl2->a.a_uptr)
+                     ? 1
+                     : (*bl1->a.a_uptr > *bl2->a.a_uptr) ? -1 : 0;
+        break;
+    case ANY_ULONG:
+        result = (bl1->a.a_ulong < bl2->a.a_ulong)
+                     ? 1
+                     : (bl1->a.a_ulong > bl2->a.a_ulong) ? -1 : 0;
+        break;
+    case ANY_ULPTR:
+        result = (*bl1->a.a_ulptr < *bl2->a.a_ulptr)
+                     ? 1
+                     : (*bl1->a.a_ulptr > *bl2->a.a_ulptr) ? -1 : 0;
+        break;
+    case ANY_STR:
+        result = sgn(strcmp(bl1->val, bl2->val));
+        break;
+    case ANY_MASK32:
+        result = (bl1->a.a_ulong != bl2->a.a_ulong);
+        break;
+    default:
+        result = 1;
+    }
+    return result;
 }
 
 STATIC_OVL char *
@@ -948,6 +1295,7 @@ int anytype;
     return buf;
 }
 
+#ifdef STATUS_HILITES
 STATIC_OVL void
 s_to_anything(a, buf, anytype)
 anything *a;
@@ -995,85 +1343,7 @@ int anytype;
     }
     return;
 }
-
-STATIC_OVL int
-compare_blstats(bl1, bl2)
-struct istat_s *bl1, *bl2;
-{
-    int anytype, result = 0;
-
-    if (!bl1 || !bl2) {
-        panic("compare_blstat: bad istat pointer %s, %s",
-              fmt_ptr((genericptr_t) bl1), fmt_ptr((genericptr_t) bl2));
-    }
-
-    anytype = bl1->anytype;
-    if ((!bl1->a.a_void || !bl2->a.a_void)
-        && (anytype == ANY_IPTR || anytype == ANY_UPTR || anytype == ANY_LPTR
-            || anytype == ANY_ULPTR)) {
-        panic("compare_blstat: invalid pointer %s, %s",
-              fmt_ptr((genericptr_t) bl1->a.a_void),
-              fmt_ptr((genericptr_t) bl2->a.a_void));
-    }
-
-    switch (anytype) {
-    case ANY_INT:
-        result = (bl1->a.a_int < bl2->a.a_int)
-                     ? 1
-                     : (bl1->a.a_int > bl2->a.a_int) ? -1 : 0;
-        break;
-    case ANY_IPTR:
-        result = (*bl1->a.a_iptr < *bl2->a.a_iptr)
-                     ? 1
-                     : (*bl1->a.a_iptr > *bl2->a.a_iptr) ? -1 : 0;
-        break;
-    case ANY_LONG:
-        result = (bl1->a.a_long < bl2->a.a_long)
-                     ? 1
-                     : (bl1->a.a_long > bl2->a.a_long) ? -1 : 0;
-        break;
-    case ANY_LPTR:
-        result = (*bl1->a.a_lptr < *bl2->a.a_lptr)
-                     ? 1
-                     : (*bl1->a.a_lptr > *bl2->a.a_lptr) ? -1 : 0;
-        break;
-    case ANY_UINT:
-        result = (bl1->a.a_uint < bl2->a.a_uint)
-                     ? 1
-                     : (bl1->a.a_uint > bl2->a.a_uint) ? -1 : 0;
-        break;
-    case ANY_UPTR:
-        result = (*bl1->a.a_uptr < *bl2->a.a_uptr)
-                     ? 1
-                     : (*bl1->a.a_uptr > *bl2->a.a_uptr) ? -1 : 0;
-        break;
-    case ANY_ULONG:
-        result = (bl1->a.a_ulong < bl2->a.a_ulong)
-                     ? 1
-                     : (bl1->a.a_ulong > bl2->a.a_ulong) ? -1 : 0;
-        break;
-    case ANY_ULPTR:
-        result = (*bl1->a.a_ulptr < *bl2->a.a_ulptr)
-                     ? 1
-                     : (*bl1->a.a_ulptr > *bl2->a.a_ulptr) ? -1 : 0;
-        break;
-    case ANY_STR:
-        if (strcmp(bl1->val, bl2->val) == 0)
-            result = 0;
-        else
-            result = 1;
-        break;
-    case ANY_MASK32:
-        if (bl1->a.a_ulong == bl2->a.a_ulong)
-            result = 0;
-        else
-            result = 1;
-        break;
-    default:
-        result = 1;
-    }
-    return result;
-}
+#endif /* STATUS_HILITES */
 
 STATIC_OVL int
 percentage(bl, maxbl)
@@ -1081,6 +1351,10 @@ struct istat_s *bl, *maxbl;
 {
     int result = 0;
     int anytype;
+    int ival;
+    long lval;
+    unsigned uval;
+    unsigned long ulval;
 
     if (!bl || !maxbl) {
         impossible("percentage: bad istat pointer %s, %s",
@@ -1088,567 +1362,2553 @@ struct istat_s *bl, *maxbl;
         return 0;
     }
 
+    ival = 0, lval = 0L, uval = 0U, ulval = 0UL;
     anytype = bl->anytype;
     if (maxbl->a.a_void) {
         switch (anytype) {
         case ANY_INT:
-            result = ((100 * bl->a.a_int) / maxbl->a.a_int);
+            ival = bl->a.a_int;
+            result = ((100 * ival) / maxbl->a.a_int);
             break;
         case ANY_LONG:
-            result = (int) ((100L * bl->a.a_long) / maxbl->a.a_long);
+            lval  = bl->a.a_long;
+            result = (int) ((100L * lval) / maxbl->a.a_long);
             break;
         case ANY_UINT:
-            result = (int) ((100U * bl->a.a_uint) / maxbl->a.a_uint);
+            uval = bl->a.a_uint;
+            result = (int) ((100U * uval) / maxbl->a.a_uint);
             break;
         case ANY_ULONG:
-            result = (int) ((100UL * bl->a.a_ulong) / maxbl->a.a_ulong);
+            ulval = bl->a.a_ulong;
+            result = (int) ((100UL * ulval) / maxbl->a.a_ulong);
             break;
         case ANY_IPTR:
-            result = ((100 * (*bl->a.a_iptr)) / (*maxbl->a.a_iptr));
+            ival = *bl->a.a_iptr;
+            result = ((100 * ival) / (*maxbl->a.a_iptr));
             break;
         case ANY_LPTR:
-            result = (int) ((100L * (*bl->a.a_lptr)) / (*maxbl->a.a_lptr));
+            lval = *bl->a.a_lptr;
+            result = (int) ((100L * lval) / (*maxbl->a.a_lptr));
             break;
         case ANY_UPTR:
-            result = (int) ((100U * (*bl->a.a_uptr)) / (*maxbl->a.a_uptr));
+            uval = *bl->a.a_uptr;
+            result = (int) ((100U * uval) / (*maxbl->a.a_uptr));
             break;
         case ANY_ULPTR:
-            result = (int) ((100UL * (*bl->a.a_ulptr)) / (*maxbl->a.a_ulptr));
+            ulval = *bl->a.a_ulptr;
+            result = (int) ((100UL * ulval) / (*maxbl->a.a_ulptr));
             break;
         }
     }
+    /* don't let truncation from integer division produce a zero result
+       from a non-zero input; note: if we ever change to something like
+       ((((1000 * val) / max) + 5) / 10) for a rounded result, we'll
+       also need to check for and convert false 100 to 99 */
+    if (result == 0 && (ival != 0 || lval != 0L || uval != 0U || ulval != 0UL))
+        result = 1;
+
     return result;
 }
 
-
-#ifdef STATUS_HILITES
-
-/****************************************************************************/
-/* Core status hiliting support */
-/****************************************************************************/
-
-struct hilite_s {
-    boolean set;
-    unsigned anytype;
-    anything threshold;
-    int behavior;
-    int coloridx[2];
-};
-
-struct hilite_s status_hilites[MAXBLSTATS];
-
-/*
- * This is the parser for the hilite options
- * Example:
- *    OPTION=hilite_status: hitpoints/10%/red/normal
- *
- * set_hilite_status() separates each hilite entry into its 4 component
- * strings, then calls assign_hilite() to make the adjustments.
- */
-boolean
-set_status_hilites(op, from_configfile)
-char *op;
-boolean from_configfile;
+/* percentage for both xp (level) and exp (points) is the percentage for
+   (curr_exp - this_level_start) in (next_level_start - this_level_start) */
+STATIC_OVL int
+exp_percentage()
 {
-    char hsbuf[4][QBUFSZ];
-    boolean rslt, badopt = FALSE;
-    int fldnum, num = 0, ccount = 0;
-    char c;
-
-    num = fldnum = 0;
-    hsbuf[0][0] = hsbuf[1][0] = hsbuf[2][0] = hsbuf[3][0] = '\0';
-    while (*op && fldnum < 4 && ccount < (QBUFSZ - 2)) {
-        c = lowc(*op);
-        if (c == ' ') {
-            if (fldnum >= 2) {
-                rslt = assign_hilite(&hsbuf[0][0], &hsbuf[1][0], &hsbuf[2][0],
-                                     &hsbuf[3][0], from_configfile);
-                if (!rslt) {
-                    badopt = TRUE;
-                    break;
-                }
-            }
-            hsbuf[0][0] = hsbuf[1][0] = '\0';
-            hsbuf[2][0] = hsbuf[3][0] = '\0';
-            fldnum = 0;
-            ccount = 0;
-        } else if (c == '/') {
-            fldnum++;
-            ccount = 0;
+    int res = 0;
+
+    if (u.ulevel < 30) {
+        long exp_val, nxt_exp_val, curlvlstart;
+
+        curlvlstart = newuexp(u.ulevel - 1);
+        exp_val = u.uexp - curlvlstart;
+        nxt_exp_val = newuexp(u.ulevel) - curlvlstart;
+        if (exp_val == nxt_exp_val - 1L) {
+            /*
+             * Full 100% is unattainable since hero gains a level
+             * and the threshold for next level increases, but treat
+             * (next_level_start - 1 point) as a special case.  It's a
+             * key value after being level drained so is something that
+             * some players would like to be able to highlight distinctly.
+             */
+            res = 100;
         } else {
-            hsbuf[fldnum][ccount++] = c;
-            hsbuf[fldnum][ccount] = '\0';
+            struct istat_s curval, maxval;
+
+            curval.anytype = maxval.anytype = ANY_LONG;
+            curval.a = maxval.a = zeroany;
+            curval.a.a_long = exp_val;
+            maxval.a.a_long = nxt_exp_val;
+            /* maximum delta between levels is 10000000; calculation of
+               100 * (10000000 - N) / 10000000 fits within 32-bit long */
+            res = percentage(&curval, &maxval);
         }
-        op++;
-    }
-    if (fldnum >= 2 && !badopt) {
-        rslt = assign_hilite(&hsbuf[0][0], &hsbuf[1][0], &hsbuf[2][0],
-                             &hsbuf[3][0], from_configfile);
-        if (!rslt)
-            badopt = TRUE;
     }
-    if (badopt)
-        return FALSE;
-    return TRUE;
+    return res;
 }
 
-void
-clear_status_hilites(from_configfile)
-boolean from_configfile;
+/* experience points have changed but experience level hasn't; decide whether
+   botl update is needed for a different percentage highlight rule for Xp */
+boolean
+exp_percent_changing()
 {
-    int i;
-    anything it;
-    it = zeroany;
-    for (i = 0; i < MAXBLSTATS; ++i) {
-        (void) memset((genericptr_t) &status_hilites[i], 0,
-                      sizeof(struct hilite_s));
-        /* notify window port */
-        if (!from_configfile)
-            status_threshold(i, blstats[0][i].anytype, it, 0, 0, 0);
+    int pc, color_dummy;
+    anything a;
+    struct hilite_s *rule;
+    struct istat_s *curr;
+
+    /* if status update is already requested, skip this processing */
+    if (!context.botl) {
+        /*
+         * Status update is warranted iff percent integer changes and the new
+         * percentage results in a different highlighting rule being selected.
+         */
+        curr = &blstats[now_or_before_idx][BL_XP];
+        /* TODO: [see eval_notify_windowport_field() about percent_matters
+           and the check against 'thresholds'] */
+        if (curr->percent_matters && curr->thresholds
+            && (pc = exp_percentage()) != curr->percent_value) {
+            a = zeroany;
+            a.a_int = (int) u.ulevel;
+            rule = get_hilite(now_or_before_idx, BL_XP,
+                              (genericptr_t) &a, 0, pc, &color_dummy);
+            if (rule != curr->hilite_rule)
+                return TRUE; /* caller should set 'context.botl' to True */
+        }
     }
+    return FALSE;
 }
 
-STATIC_OVL boolean
-assign_hilite(sa, sb, sc, sd, from_configfile)
-char *sa, *sb, *sc, *sd;
-boolean from_configfile;
+/* callback so that interface can get capacity index rather than trying
+   to reconstruct that from the encumbrance string or asking the general
+   core what the value is */
+int
+stat_cap_indx()
 {
-    char *tmp, *how;
-    int i = -1, dt = -1, idx = -1;
-    int coloridx[2] = { -1, -1 };
-    boolean inverse[2] = { FALSE, FALSE };
-    boolean bold[2] = { FALSE, FALSE };
-    boolean normal[2] = { 0, 0 };
-    boolean percent = FALSE, down_up = FALSE, changed = FALSE;
-    anything threshold;
-    enum statusfields fld = BL_FLUSH;
-    threshold.a_void = 0;
+    int cap;
 
-    /* Example:
-     *  hilite_status: hitpoints/10%/red/normal
-     */
+#ifdef STATUS_HILITES
+    cap = blstats[now_or_before_idx][BL_CAP].a.a_int;
+#else
+    cap = near_capacity();
+#endif
+    return cap;
+}
 
-    /* field name to statusfield */
-    for (i = 0; sa && i < SIZE(fieldids); ++i) {
-        if (strcmpi(sa, fieldids[i].fieldname) == 0) {
-            idx = i;
-            fld = fieldids[i].fldid;
-            break;
-        }
+/* callback so that interface can get hunger index rather than trying to
+   reconstruct that from the hunger string or dipping into core internals */
+int
+stat_hunger_indx()
+{
+    int uhs;
+
+#ifdef STATUS_HILITES
+    uhs = blstats[now_or_before_idx][BL_HUNGER].a.a_int;
+#else
+    uhs = (int) u.uhs;
+#endif
+    return uhs;
+}
+
+/* used by X11 for "tty status" even when STATUS_HILITES is disabled */
+const char *
+bl_idx_to_fldname(idx)
+int idx;
+{
+    if (idx >= 0 && idx < MAXBLSTATS)
+        return initblstats[idx].fldname;
+    return (const char *) 0;
+}
+
+#ifdef STATUS_HILITES
+
+/****************************************************************************/
+/* Core status hiliting support */
+/****************************************************************************/
+
+struct hilite_s status_hilites[MAXBLSTATS];
+
+static struct fieldid_t {
+    const char *fieldname;
+    enum statusfields fldid;
+} fieldids_alias[] = {
+    { "characteristics",   BL_CHARACTERISTICS },
+    { "encumbrance",       BL_CAP },
+    { "experience-points", BL_EXP },
+    { "dx",       BL_DX },
+    { "co",       BL_CO },
+    { "con",      BL_CO },
+    { "points",   BL_SCORE },
+    { "cap",      BL_CAP },
+    { "pw",       BL_ENE },
+    { "pw-max",   BL_ENEMAX },
+    { "xl",       BL_XP },
+    { "xplvl",    BL_XP },
+    { "ac",       BL_AC },
+    { "hit-dice", BL_HD },
+    { "turns",    BL_TIME },
+    { "hp",       BL_HP },
+    { "hp-max",   BL_HPMAX },
+    { "dgn",      BL_LEVELDESC },
+    { "xp",       BL_EXP },
+    { "exp",      BL_EXP },
+    { "flags",    BL_CONDITION },
+    {0,           BL_FLUSH }
+};
+
+/* format arguments */
+static const char threshold_value[] = "hilite_status threshold ",
+                  is_out_of_range[] = " is out of range";
+
+
+/* field name to bottom line index */
+STATIC_OVL enum statusfields
+fldname_to_bl_indx(name)
+const char *name;
+{
+    int i, nmatches = 0, fld = 0;
+
+    if (name && *name) {
+        /* check matches to canonical names */
+        for (i = 0; i < SIZE(initblstats); i++)
+            if (fuzzymatch(initblstats[i].fldname, name, " -_", TRUE)) {
+                fld = initblstats[i].fld;
+                nmatches++;
+            }
+
+        if (!nmatches) {
+            /* check aliases */
+            for (i = 0; fieldids_alias[i].fieldname; i++)
+                if (fuzzymatch(fieldids_alias[i].fieldname, name,
+                               " -_", TRUE)) {
+                    fld = fieldids_alias[i].fldid;
+                    nmatches++;
+                }
+        }
+
+        if (!nmatches) {
+            /* check partial matches to canonical names */
+            int len = (int) strlen(name);
+
+            for (i = 0; i < SIZE(initblstats); i++)
+                if (!strncmpi(name, initblstats[i].fldname, len)) {
+                    fld = initblstats[i].fld;
+                    nmatches++;
+                }
+        }
+
+    }
+    return (nmatches == 1) ? fld : BL_FLUSH;
+}
+
+STATIC_OVL boolean
+hilite_reset_needed(bl_p, augmented_time)
+struct istat_s *bl_p;
+long augmented_time; /* no longer augmented; it once encoded fractional
+                      * amounts for multiple moves within same turn     */
+{
+    /*
+     * This 'multi' handling may need some tuning...
+     */
+    if (multi)
+        return FALSE;
+
+    if (!Is_Temp_Hilite(bl_p->hilite_rule))
+        return FALSE;
+
+    if (bl_p->time == 0 || bl_p->time >= augmented_time)
+        return FALSE;
+
+    return TRUE;
+}
+
+/* called from moveloop(); sets context.botl if temp hilites have timed out */
+void
+status_eval_next_unhilite()
+{
+    int i;
+    struct istat_s *curr;
+    long next_unhilite, this_unhilite;
+
+    bl_hilite_moves = moves; /* simpllfied; used to try to encode fractional
+                              * amounts for multiple moves within same turn */
+    /* figure out whether an unhilight needs to be performed now */
+    next_unhilite = 0L;
+    for (i = 0; i < MAXBLSTATS; ++i) {
+        curr = &blstats[0][i]; /* blstats[0][*].time == blstats[1][*].time */
+
+        if (curr->chg) {
+            struct istat_s *prev = &blstats[1][i];
+
+            if (Is_Temp_Hilite(curr->hilite_rule))
+                curr->time = prev->time = (bl_hilite_moves
+                                           + iflags.hilite_delta);
+            else
+                curr->time = prev->time = 0L;
+
+            curr->chg = prev->chg = FALSE;
+            context.botl = TRUE;
+        }
+        if (context.botl)
+            continue; /* just process other blstats[][].time and .chg */
+
+        this_unhilite = curr->time;
+        if (this_unhilite > 0L
+            && (next_unhilite == 0L || this_unhilite < next_unhilite)
+            && hilite_reset_needed(curr, this_unhilite + 1L)) {
+            next_unhilite = this_unhilite;
+            if (next_unhilite < bl_hilite_moves)
+                context.botl = TRUE;
+        }
+    }
+}
+
+/* called by options handling when 'statushilites' value is changed */
+void
+reset_status_hilites()
+{
+    if (iflags.hilite_delta) {
+        int i;
+
+        for (i = 0; i < MAXBLSTATS; ++i)
+            blstats[0][i].time = blstats[1][i].time = 0L;
+        update_all = TRUE;
+    }
+    context.botlx = TRUE;
+}
+
+/* test whether the text from a title rule matches the string for
+   title-while-polymorphed in the 'textmatch' menu */
+STATIC_OVL boolean
+noneoftheabove(hl_text)
+const char *hl_text;
+{
+    if (fuzzymatch(hl_text, "none of the above", "\" -_", TRUE)
+        || fuzzymatch(hl_text, "(polymorphed)", "\"()", TRUE)
+        || fuzzymatch(hl_text, "none of the above (polymorphed)",
+                      "\" -_()", TRUE))
+        return TRUE;
+    return FALSE;
+}
+
+/*
+ * get_hilite
+ *
+ * Returns, based on the value and the direction it is moving,
+ * the highlight rule that applies to the specified field.
+ *
+ * Provide get_hilite() with the following to work with:
+ *     actual value vp
+ *          useful for BL_TH_VAL_ABSOLUTE
+ *     indicator of down, up, or the same (-1, 1, 0) chg
+ *          useful for BL_TH_UPDOWN or change detection
+ *     percentage (current value percentage of max value) pc
+ *          useful for BL_TH_VAL_PERCENTAGE
+ *
+ * Get back:
+ *     pointer to rule that applies; Null if no rule does.
+ */
+STATIC_OVL struct hilite_s *
+get_hilite(idx, fldidx, vp, chg, pc, colorptr)
+int idx, fldidx, chg, pc;
+genericptr_t vp;
+int *colorptr;
+{
+    struct hilite_s *hl, *rule = 0;
+    anything *value = (anything *) vp;
+    char *txtstr;
+
+    if (fldidx < 0 || fldidx >= MAXBLSTATS)
+        return (struct hilite_s *) 0;
+
+    if (has_hilite(fldidx)) {
+        int dt;
+        /* there are hilites set here */
+        int max_pc = -1, min_pc = 101;
+        /* LARGEST_INT isn't INT_MAX; it fits within 16 bits, but that
+           value is big enough to handle all 'int' status fields */
+        int max_ival = -LARGEST_INT, min_ival = LARGEST_INT;
+        /* LONG_MAX comes from <limits.h> which might not be available for
+           ancient configurations; we don't need LONG_MIN */
+        long max_lval = -LONG_MAX, min_lval = LONG_MAX;
+        boolean exactmatch = FALSE, updown = FALSE, changed = FALSE,
+                perc_or_abs = FALSE;
+
+        /* min_/max_ are used to track best fit */
+        for (hl = blstats[0][fldidx].thresholds; hl; hl = hl->next) {
+            dt = initblstats[fldidx].anytype; /* only needed for 'absolute' */
+            /* if we've already matched a temporary highlight, it takes
+               precedence over all persistent ones; we still process
+               updown rules to get the last one which qualifies */
+            if ((updown || changed) && hl->behavior != BL_TH_UPDOWN)
+                continue;
+            /* among persistent highlights, if a 'percentage' or 'absolute'
+               rule has been matched, it takes precedence over 'always' */
+            if (perc_or_abs && hl->behavior == BL_TH_ALWAYS_HILITE)
+                continue;
+
+            switch (hl->behavior) {
+            case BL_TH_VAL_PERCENTAGE: /* percent values are always ANY_INT */
+                if (hl->rel == EQ_VALUE && pc == hl->value.a_int) {
+                    rule = hl;
+                    min_pc = max_pc = hl->value.a_int;
+                    exactmatch = perc_or_abs = TRUE;
+                } else if (exactmatch) {
+                    ; /* already found best fit, skip lt,ge,&c */
+                } else if (hl->rel == LT_VALUE
+                           && (pc < hl->value.a_int)
+                           && (hl->value.a_int <= min_pc)) {
+                    rule = hl;
+                    min_pc = hl->value.a_int;
+                    perc_or_abs = TRUE;
+                } else if (hl->rel == LE_VALUE
+                           && (pc <= hl->value.a_int)
+                           && (hl->value.a_int <= min_pc)) {
+                    rule = hl;
+                    min_pc = hl->value.a_int;
+                    perc_or_abs = TRUE;
+                } else if (hl->rel == GT_VALUE
+                           && (pc > hl->value.a_int)
+                           && (hl->value.a_int >= max_pc)) {
+                    rule = hl;
+                    max_pc = hl->value.a_int;
+                    perc_or_abs = TRUE;
+                } else if (hl->rel == GE_VALUE
+                           && (pc >= hl->value.a_int)
+                           && (hl->value.a_int >= max_pc)) {
+                    rule = hl;
+                    max_pc = hl->value.a_int;
+                    perc_or_abs = TRUE;
+                }
+                break;
+            case BL_TH_UPDOWN: /* uses 'chg' (set by caller), not 'dt' */
+                /* specific 'up' or 'down' takes precedence over general
+                   'changed' regardless of their order in the rule set */
+                if (chg < 0 && hl->rel == LT_VALUE) {
+                    rule = hl;
+                    updown = TRUE;
+                } else if (chg > 0 && hl->rel == GT_VALUE) {
+                    rule = hl;
+                    updown = TRUE;
+                } else if (chg != 0 && hl->rel == EQ_VALUE && !updown) {
+                    rule = hl;
+                    changed = TRUE;
+                }
+                break;
+            case BL_TH_VAL_ABSOLUTE: /* either ANY_INT or ANY_LONG */
+                /*
+                 * The int and long variations here are identical aside from
+                 * union field and min_/max_ variable names.  If you change
+                 * one, be sure to make a corresponding change in the other.
+                 */
+                if (dt == ANY_INT) {
+                    if (hl->rel == EQ_VALUE
+                        && hl->value.a_int == value->a_int) {
+                        rule = hl;
+                        min_ival = max_ival = hl->value.a_int;
+                        exactmatch = perc_or_abs = TRUE;
+                    } else if (exactmatch) {
+                        ; /* already found best fit, skip lt,ge,&c */
+                    } else if (hl->rel == LT_VALUE
+                               && (value->a_int < hl->value.a_int)
+                               && (hl->value.a_int <= min_ival)) {
+                        rule = hl;
+                        min_ival = hl->value.a_int;
+                        perc_or_abs = TRUE;
+                    } else if (hl->rel == LE_VALUE
+                               && (value->a_int <= hl->value.a_int)
+                               && (hl->value.a_int <= min_ival)) {
+                        rule = hl;
+                        min_ival = hl->value.a_int;
+                        perc_or_abs = TRUE;
+                    } else if (hl->rel == GT_VALUE
+                               && (value->a_int > hl->value.a_int)
+                               && (hl->value.a_int >= max_ival)) {
+                        rule = hl;
+                        max_ival = hl->value.a_int;
+                        perc_or_abs = TRUE;
+                    } else if (hl->rel == GE_VALUE
+                               && (value->a_int >= hl->value.a_int)
+                               && (hl->value.a_int >= max_ival)) {
+                        rule = hl;
+                        max_ival = hl->value.a_int;
+                        perc_or_abs = TRUE;
+                    }
+                } else { /* ANY_LONG */
+                    if (hl->rel == EQ_VALUE
+                        && hl->value.a_long == value->a_long) {
+                        rule = hl;
+                        min_lval = max_lval = hl->value.a_long;
+                        exactmatch = perc_or_abs = TRUE;
+                    } else if (exactmatch) {
+                        ; /* already found best fit, skip lt,ge,&c */
+                    } else if (hl->rel == LT_VALUE
+                               && (value->a_long < hl->value.a_long)
+                               && (hl->value.a_long <= min_lval)) {
+                        rule = hl;
+                        min_lval = hl->value.a_long;
+                        perc_or_abs = TRUE;
+                    } else if (hl->rel == LE_VALUE
+                               && (value->a_long <= hl->value.a_long)
+                               && (hl->value.a_long <= min_lval)) {
+                        rule = hl;
+                        min_lval = hl->value.a_long;
+                        perc_or_abs = TRUE;
+                    } else if (hl->rel == GT_VALUE
+                               && (value->a_long > hl->value.a_long)
+                               && (hl->value.a_long >= max_lval)) {
+                        rule = hl;
+                        max_lval = hl->value.a_long;
+                        perc_or_abs = TRUE;
+                    } else if (hl->rel == GE_VALUE
+                               && (value->a_long >= hl->value.a_long)
+                               && (hl->value.a_long >= max_lval)) {
+                        rule = hl;
+                        max_lval = hl->value.a_long;
+                        perc_or_abs = TRUE;
+                    }
+                }
+                break;
+            case BL_TH_TEXTMATCH: /* ANY_STR */
+                txtstr = blstats[idx][fldidx].val;
+                if (fldidx == BL_TITLE)
+                    /* "<name> the <rank-title>", skip past "<name> the " */
+                    txtstr += (strlen(plname) + sizeof " the " - sizeof "");
+                if (hl->rel == TXT_VALUE && hl->textmatch[0]) {
+                    if (fuzzymatch(hl->textmatch, txtstr, "\" -_", TRUE)) {
+                        rule = hl;
+                        exactmatch = TRUE;
+                    } else if (exactmatch) {
+                        ; /* already found best fit, skip "noneoftheabove" */
+                    } else if (fldidx == BL_TITLE
+                               && Upolyd && noneoftheabove(hl->textmatch)) {
+                        rule = hl;
+                    }
+                }
+                break;
+            case BL_TH_ALWAYS_HILITE:
+                rule = hl;
+                break;
+            case BL_TH_NONE:
+                break;
+            default:
+                break;
+            }
+        }
+    }
+    *colorptr = rule ? rule->coloridx : NO_COLOR;
+    return rule;
+}
+
+STATIC_OVL void
+split_clridx(idx, coloridx, attrib)
+int idx;
+int *coloridx, *attrib;
+{
+    if (coloridx)
+        *coloridx = idx & 0x00FF;
+    if (attrib)
+        *attrib = (idx >> 8) & 0x00FF;
+}
+
+/*
+ * This is the parser for the hilite options.
+ *
+ * parse_status_hl1() separates each hilite entry into
+ * a set of field threshold/action component strings,
+ * then calls parse_status_hl2() to parse further
+ * and configure the hilite.
+ */
+boolean
+parse_status_hl1(op, from_configfile)
+char *op;
+boolean from_configfile;
+{
+#define MAX_THRESH 21
+    char hsbuf[MAX_THRESH][QBUFSZ];
+    boolean rslt, badopt = FALSE;
+    int i, fldnum, ccount = 0;
+    char c;
+
+    fldnum = 0;
+    for (i = 0; i < MAX_THRESH; ++i) {
+        hsbuf[i][0] = '\0';
+    }
+    while (*op && fldnum < MAX_THRESH && ccount < (QBUFSZ - 2)) {
+        c = lowc(*op);
+        if (c == ' ') {
+            if (fldnum >= 1) {
+                if (fldnum == 1 && strcmpi(hsbuf[0], "title") == 0) {
+                    /* spaces are allowed in title */
+                    hsbuf[fldnum][ccount++] = c;
+                    hsbuf[fldnum][ccount] = '\0';
+                    op++;
+                    continue;
+                }
+                rslt = parse_status_hl2(hsbuf, from_configfile);
+                if (!rslt) {
+                    badopt = TRUE;
+                    break;
+                }
+            }
+            for (i = 0; i < MAX_THRESH; ++i) {
+                hsbuf[i][0] = '\0';
+            }
+            fldnum = 0;
+            ccount = 0;
+        } else if (c == '/') {
+            fldnum++;
+            ccount = 0;
+        } else {
+            hsbuf[fldnum][ccount++] = c;
+            hsbuf[fldnum][ccount] = '\0';
+        }
+        op++;
+    }
+    if (fldnum >= 1 && !badopt) {
+        rslt = parse_status_hl2(hsbuf, from_configfile);
+        if (!rslt)
+            badopt = TRUE;
+    }
+    if (badopt)
+        return FALSE;
+    return TRUE;
+}
+
+/* is str in the format of "[<>]?=?[-+]?[0-9]+%?" regex */
+STATIC_OVL boolean
+is_ltgt_percentnumber(str)
+const char *str;
+{
+    const char *s = str;
+
+    if (*s == '<' || *s == '>')
+        s++;
+    if (*s == '=')
+        s++;
+    if (*s == '-' || *s == '+')
+        s++;
+    if (!digit(*s))
+        return FALSE;
+    while (digit(*s))
+        s++;
+    if (*s == '%')
+        s++;
+    return (*s == '\0');
+}
+
+/* does str only contain "<>=-+0-9%" chars */
+STATIC_OVL boolean
+has_ltgt_percentnumber(str)
+const char *str;
+{
+    const char *s = str;
+
+    while (*s) {
+        if (!index("<>=-+0123456789%", *s))
+            return FALSE;
+        s++;
+    }
+    return TRUE;
+}
+
+/* splitsubfields(): splits str in place into '+' or '&' separated strings.
+ * returns number of strings, or -1 if more than maxsf or MAX_SUBFIELDS
+ */
+#define MAX_SUBFIELDS 16
+STATIC_OVL int
+splitsubfields(str, sfarr, maxsf)
+char *str;
+char ***sfarr;
+int maxsf;
+{
+    static char *subfields[MAX_SUBFIELDS];
+    char *st = (char *) 0;
+    int sf = 0;
+
+    if (!str)
+        return 0;
+    for (sf = 0; sf < MAX_SUBFIELDS; ++sf)
+        subfields[sf] = (char *) 0;
+
+    maxsf = (maxsf == 0) ? MAX_SUBFIELDS : min(maxsf, MAX_SUBFIELDS);
+
+    if (index(str, '+') || index(str, '&')) {
+        char *c = str;
+
+        sf = 0;
+        st = c;
+        while (*c && sf < maxsf) {
+            if (*c == '&' || *c == '+') {
+                *c = '\0';
+                subfields[sf] = st;
+                st = c+1;
+                sf++;
+            }
+            c++;
+        }
+        if (sf >= maxsf - 1)
+            return -1;
+        if (!*c && c != st)
+            subfields[sf++] = st;
+    } else {
+        sf = 1;
+        subfields[0] = str;
+    }
+    *sfarr = subfields;
+    return sf;
+}
+#undef MAX_SUBFIELDS
+
+STATIC_OVL boolean
+is_fld_arrayvalues(str, arr, arrmin, arrmax, retidx)
+const char *str;
+const char *const *arr;
+int arrmin, arrmax;
+int *retidx;
+{
+    int i;
+
+    for (i = arrmin; i < arrmax; i++)
+        if (!strcmpi(str, arr[i])) {
+            *retidx = i;
+            return TRUE;
+        }
+    return FALSE;
+}
+
+STATIC_OVL int
+query_arrayvalue(querystr, arr, arrmin, arrmax)
+const char *querystr;
+const char *const *arr;
+int arrmin, arrmax;
+{
+    int i, res, ret = arrmin - 1;
+    winid tmpwin;
+    anything any;
+    menu_item *picks = (menu_item *) 0;
+    int adj = (arrmin > 0) ? 1 : arrmax;
+
+    tmpwin = create_nhwindow(NHW_MENU);
+    start_menu(tmpwin);
+
+    for (i = arrmin; i < arrmax; i++) {
+        any = zeroany;
+        any.a_int = i + adj;
+        add_menu(tmpwin, NO_GLYPH, &any, 0, 0, ATR_NONE,
+                 arr[i], MENU_UNSELECTED);
+    }
+
+    end_menu(tmpwin, querystr);
+
+    res = select_menu(tmpwin, PICK_ONE, &picks);
+    destroy_nhwindow(tmpwin);
+    if (res > 0) {
+        ret = picks->item.a_int - adj;
+        free((genericptr_t) picks);
+    }
+
+    return ret;
+}
+
+STATIC_OVL void
+status_hilite_add_threshold(fld, hilite)
+int fld;
+struct hilite_s *hilite;
+{
+    struct hilite_s *new_hilite;
+
+    if (!hilite)
+        return;
+
+    /* alloc and initialize a new hilite_s struct */
+    new_hilite = (struct hilite_s *) alloc(sizeof (struct hilite_s));
+    *new_hilite = *hilite;   /* copy struct */
+
+    new_hilite->set = TRUE;
+    new_hilite->fld = fld;
+    new_hilite->next = blstats[0][fld].thresholds;
+    blstats[0][fld].thresholds = new_hilite;
+    /* sort_hilites(fld) */
+
+    /* current and prev must both point at the same hilites */
+    blstats[1][fld].thresholds = blstats[0][fld].thresholds;
+}
+
+
+STATIC_OVL boolean
+parse_status_hl2(s, from_configfile)
+char (*s)[QBUFSZ];
+boolean from_configfile;
+{
+    char *tmp, *how;
+    int sidx = 0, i = -1, dt = -1;
+    int coloridx = -1, successes = 0;
+    int disp_attrib = 0;
+    boolean percent, changed, numeric, down, up,
+            gt, lt, ge, le, eq, txtval, always;
+    const char *txt;
+    enum statusfields fld = BL_FLUSH;
+    struct hilite_s hilite;
+    char tmpbuf[BUFSZ];
+    static const char *aligntxt[] = { "chaotic", "neutral", "lawful" };
+    /* hu_stat[] from eat.c has trailing spaces which foul up comparisons */
+    static const char *hutxt[] = { "Satiated", "", "Hungry", "Weak",
+                                   "Fainting", "Fainted", "Starved" };
+
+    /* Examples:
+        3.6.1:
+      OPTION=hilite_status: hitpoints/<10%/red
+      OPTION=hilite_status: hitpoints/<10%/red/<5%/purple/1/red+blink+inverse
+      OPTION=hilite_status: experience/down/red/up/green
+      OPTION=hilite_status: cap/strained/yellow/overtaxed/orange
+      OPTION=hilite_status: title/always/blue
+      OPTION=hilite_status: title/blue
+    */
+
+    /* field name to statusfield */
+    fld = fldname_to_bl_indx(s[sidx]);
+
+    if (fld == BL_CHARACTERISTICS) {
+        boolean res = FALSE;
+
+        /* recursively set each of strength, dexterity, constitution, &c */
+        for (fld = BL_STR; fld <= BL_CH; fld++) {
+            Strcpy(s[sidx], initblstats[fld].fldname);
+            res = parse_status_hl2(s, from_configfile);
+            if (!res)
+                return FALSE;
+        }
+        return TRUE;
+    }
+    if (fld == BL_FLUSH) {
+        config_error_add("Unknown status field '%s'", s[sidx]);
+        return FALSE;
+    }
+    if (fld == BL_CONDITION)
+        return parse_condition(s, sidx);
+
+    ++sidx;
+    while (s[sidx]) {
+        char buf[BUFSZ], **subfields;
+        int sf = 0;     /* subfield count */
+        int kidx;
+
+        txt = (const char *)0;
+        percent = numeric = always = FALSE;
+        down = up = changed = FALSE;
+        gt = ge = eq = le = lt = txtval = FALSE;
+
+        /* threshold value */
+        if (!s[sidx][0])
+            return TRUE;
+
+        memset((genericptr_t) &hilite, 0, sizeof (struct hilite_s));
+        hilite.set = FALSE; /* mark it "unset" */
+        hilite.fld = fld;
+
+        if (*s[sidx + 1] == '\0' || !strcmpi(s[sidx], "always")) {
+            /* "field/always/color" OR "field/color" */
+            always = TRUE;
+            if (*s[sidx + 1] == '\0')
+                sidx--;
+        } else if (!strcmpi(s[sidx], "up") || !strcmpi(s[sidx], "down")) {
+            if (initblstats[fld].anytype == ANY_STR)
+                /* ordered string comparison is supported but LT/GT for
+                   the string fields (title, dungeon-level, alignment)
+                   is pointless; treat 'up' or 'down' for string fields
+                   as 'changed' rather than rejecting them outright */
+                ;
+            else if (!strcmpi(s[sidx], "down"))
+                down = TRUE;
+            else
+                up = TRUE;
+            changed = TRUE;
+        } else if (fld == BL_CAP
+#if 0 /*JP*/
+                   && is_fld_arrayvalues(s[sidx], enc_stat,
+                                         SLT_ENCUMBER, OVERLOADED + 1,
+                                         &kidx)) {
+            txt = enc_stat[kidx];
+#else
+                   && is_fld_arrayvalues(s[sidx], enc_stat_opt,
+                                         SLT_ENCUMBER, OVERLOADED + 1,
+                                         &kidx)) {
+            txt = enc_stat_opt[kidx];
+#endif
+            txtval = TRUE;
+        } else if (fld == BL_ALIGN
+                   && is_fld_arrayvalues(s[sidx], aligntxt, 0, 3, &kidx)) {
+            txt = aligntxt[kidx];
+            txtval = TRUE;
+        } else if (fld == BL_HUNGER
+                   && is_fld_arrayvalues(s[sidx], hutxt,
+                                         SATIATED, STARVED + 1, &kidx)) {
+            txt = hu_stat[kidx];   /* store hu_stat[] val, not hutxt[] */
+            txtval = TRUE;
+        } else if (!strcmpi(s[sidx], "changed")) {
+            changed = TRUE;
+        } else if (is_ltgt_percentnumber(s[sidx])) {
+            const char *op;
+
+            tmp = s[sidx]; /* is_ltgt_() guarantees [<>]?=?[-+]?[0-9]+%? */
+            if (strchr(tmp, '%'))
+               percent = TRUE;
+            if (*tmp == '<') {
+                if (tmp[1] == '=')
+                    le = TRUE;
+                else
+                    lt = TRUE;
+            } else if (*tmp == '>') {
+                if (tmp[1] == '=')
+                    ge = TRUE;
+                else
+                    gt = TRUE;
+            }
+            /* '%', '<', '>' have served their purpose, '=' is either
+               part of '<' or '>' or optional for '=N', unary '+' is
+               just decorative, so get rid of them, leaving -?[0-9]+ */
+            tmp = stripchars(tmpbuf, "%<>=+", tmp);
+            numeric = TRUE;
+            dt = percent ? ANY_INT : initblstats[fld].anytype;
+            (void) s_to_anything(&hilite.value, tmp, dt);
+
+            op = gt ? ">" : ge ? ">=" : lt ? "<" : le ? "<=" : "=";
+            if (dt == ANY_INT
+                /* AC is the only field where negative values make sense but
+                   accept >-1 for other fields; reject <0 for non-AC */
+                && (hilite.value.a_int
+                    < ((fld == BL_AC) ? -128 : gt ? -1 : lt ? 1 : 0)
+                /* percentages have another more comprehensive check below */
+                    || hilite.value.a_int > (percent ? (lt ? 101 : 100)
+                                                     : LARGEST_INT))) {
+                config_error_add("%s'%s%d%s'%s", threshold_value,
+                                 op, hilite.value.a_int, percent ? "%" : "",
+                                 is_out_of_range);
+                return FALSE;
+            } else if (dt == ANY_LONG
+                       && (hilite.value.a_long < (gt ? -1L : lt ? 1L : 0L))) {
+                config_error_add("%s'%s%ld'%s", threshold_value,
+                                 op, hilite.value.a_long, is_out_of_range);
+                return FALSE;
+            }
+        } else if (initblstats[fld].anytype == ANY_STR) {
+            txt = s[sidx];
+            txtval = TRUE;
+        } else {
+            config_error_add(has_ltgt_percentnumber(s[sidx])
+                 ? "Wrong format '%s', expected a threshold number or percent"
+                 : "Unknown behavior '%s'",
+                             s[sidx]);
+            return FALSE;
+        }
+
+        /* relationships {LT_VALUE, LE_VALUE, EQ_VALUE, GE_VALUE, GT_VALUE} */
+        if (gt || up)
+            hilite.rel = GT_VALUE;
+        else if (lt || down)
+            hilite.rel = LT_VALUE;
+        else if (ge)
+            hilite.rel = GE_VALUE;
+        else if (le)
+            hilite.rel = LE_VALUE;
+        else if (eq  || percent || numeric || changed)
+            hilite.rel = EQ_VALUE;
+        else if (txtval)
+            hilite.rel = TXT_VALUE;
+        else
+            hilite.rel = LT_VALUE;
+
+        if (initblstats[fld].anytype == ANY_STR && (percent || numeric)) {
+            config_error_add("Field '%s' does not support numeric values",
+                             initblstats[fld].fldname);
+            return FALSE;
+        }
+
+        if (percent) {
+            if (initblstats[fld].idxmax < 0) {
+                config_error_add("Cannot use percent with '%s'",
+                                 initblstats[fld].fldname);
+                return FALSE;
+            } else if ((hilite.value.a_int < -1)
+                       || (hilite.value.a_int == -1
+                           && hilite.value.a_int != GT_VALUE)
+                       || (hilite.value.a_int == 0
+                           && hilite.rel == LT_VALUE)
+                       || (hilite.value.a_int == 100
+                           && hilite.rel == GT_VALUE)
+                       || (hilite.value.a_int == 101
+                           && hilite.value.a_int != LT_VALUE)
+                       || (hilite.value.a_int > 101)) {
+                config_error_add(
+                           "hilite_status: invalid percentage value '%s%d%%'",
+                                 (hilite.rel == LT_VALUE) ? "<"
+                                   : (hilite.rel == LE_VALUE) ? "<="
+                                     : (hilite.rel == GT_VALUE) ? ">"
+                                       : (hilite.rel == GE_VALUE) ? ">="
+                                         : "=",
+                                 hilite.value.a_int);
+                return FALSE;
+            }
+        }
+
+        /* actions */
+        sidx++;
+        how = s[sidx];
+        if (!how) {
+            if (!successes)
+                return FALSE;
+        }
+        coloridx = -1;
+        Strcpy(buf, how);
+        sf = splitsubfields(buf, &subfields, 0);
+
+        if (sf < 1)
+            return FALSE;
+
+        disp_attrib = HL_UNDEF;
+
+        for (i = 0; i < sf; ++i) {
+            int a = match_str2attr(subfields[i], FALSE);
+
+            if (a == ATR_DIM)
+                disp_attrib |= HL_DIM;
+            else if (a == ATR_BLINK)
+                disp_attrib |= HL_BLINK;
+            else if (a == ATR_ULINE)
+                disp_attrib |= HL_ULINE;
+            else if (a == ATR_INVERSE)
+                disp_attrib |= HL_INVERSE;
+            else if (a == ATR_BOLD)
+                disp_attrib |= HL_BOLD;
+            else if (a == ATR_NONE)
+                disp_attrib = HL_NONE;
+            else {
+                int c = match_str2clr(subfields[i]);
+
+                if (c >= CLR_MAX || coloridx != -1)
+                    return FALSE;
+                coloridx = c;
+            }
+        }
+        if (coloridx == -1)
+            coloridx = NO_COLOR;
+
+        /* Assign the values */
+        hilite.coloridx = coloridx | (disp_attrib << 8);
+
+        if (always)
+            hilite.behavior = BL_TH_ALWAYS_HILITE;
+        else if (percent)
+            hilite.behavior = BL_TH_VAL_PERCENTAGE;
+        else if (changed)
+            hilite.behavior = BL_TH_UPDOWN;
+        else if (numeric)
+            hilite.behavior = BL_TH_VAL_ABSOLUTE;
+        else if (txtval)
+            hilite.behavior = BL_TH_TEXTMATCH;
+        else if (hilite.value.a_void)
+            hilite.behavior = BL_TH_VAL_ABSOLUTE;
+       else
+            hilite.behavior = BL_TH_NONE;
+
+        hilite.anytype = dt;
+
+        if (hilite.behavior == BL_TH_TEXTMATCH && txt) {
+            (void) strncpy(hilite.textmatch, txt, sizeof hilite.textmatch);
+            hilite.textmatch[sizeof hilite.textmatch - 1] = '\0';
+            (void) trimspaces(hilite.textmatch);
+        }
+
+        status_hilite_add_threshold(fld, &hilite);
+
+        successes++;
+        sidx++;
+    }
+
+    return TRUE;
+}
+#endif /* STATUS_HILITES */
+
+const struct condmap valid_conditions[] = {
+    { "stone",    BL_MASK_STONE },
+    { "slime",    BL_MASK_SLIME },
+    { "strngl",   BL_MASK_STRNGL },
+    { "foodPois", BL_MASK_FOODPOIS },
+    { "termIll",  BL_MASK_TERMILL },
+    { "blind",    BL_MASK_BLIND },
+    { "deaf",     BL_MASK_DEAF },
+    { "stun",     BL_MASK_STUN },
+    { "conf",     BL_MASK_CONF },
+    { "hallu",    BL_MASK_HALLU },
+    { "lev",      BL_MASK_LEV },
+    { "fly",      BL_MASK_FLY },
+    { "ride",     BL_MASK_RIDE },
+};
+
+#ifdef STATUS_HILITES
+
+const struct condmap condition_aliases[] = {
+    { "strangled",      BL_MASK_STRNGL },
+    { "all",            BL_MASK_STONE | BL_MASK_SLIME | BL_MASK_STRNGL
+                        | BL_MASK_FOODPOIS | BL_MASK_TERMILL
+                        | BL_MASK_BLIND | BL_MASK_DEAF | BL_MASK_STUN
+                        | BL_MASK_CONF | BL_MASK_HALLU
+                        | BL_MASK_LEV | BL_MASK_FLY | BL_MASK_RIDE },
+    { "major_troubles", BL_MASK_STONE | BL_MASK_SLIME | BL_MASK_STRNGL
+                        | BL_MASK_FOODPOIS | BL_MASK_TERMILL },
+    { "minor_troubles", BL_MASK_BLIND | BL_MASK_DEAF | BL_MASK_STUN
+                        | BL_MASK_CONF | BL_MASK_HALLU },
+    { "movement",       BL_MASK_LEV | BL_MASK_FLY | BL_MASK_RIDE }
+};
+
+unsigned long
+query_conditions()
+{
+    int i,res;
+    unsigned long ret = 0UL;
+    winid tmpwin;
+    anything any;
+    menu_item *picks = (menu_item *) 0;
+
+    tmpwin = create_nhwindow(NHW_MENU);
+    start_menu(tmpwin);
+
+    for (i = 0; i < SIZE(valid_conditions); i++) {
+        any = zeroany;
+        any.a_ulong = valid_conditions[i].bitmask;
+        add_menu(tmpwin, NO_GLYPH, &any, 0, 0, ATR_NONE,
+                 valid_conditions[i].id, MENU_UNSELECTED);
+    }
+
+    end_menu(tmpwin, "Choose status conditions");
+
+    res = select_menu(tmpwin, PICK_ANY, &picks);
+    destroy_nhwindow(tmpwin);
+    if (res > 0) {
+        for (i = 0; i < res; i++)
+            ret |= picks[i].item.a_ulong;
+        free((genericptr_t) picks);
+    }
+    return ret;
+}
+
+STATIC_OVL char *
+conditionbitmask2str(ul)
+unsigned long ul;
+{
+    static char buf[BUFSZ];
+    int i;
+    boolean first = TRUE;
+    const char *alias = (char *) 0;
+
+
+    buf[0] = '\0';
+    if (!ul)
+        return buf;
+
+    for (i = 1; i < SIZE(condition_aliases); i++)
+        if (condition_aliases[i].bitmask == ul)
+            alias = condition_aliases[i].id;
+
+    for (i = 0; i < SIZE(valid_conditions); i++)
+        if ((valid_conditions[i].bitmask & ul) != 0UL) {
+            Sprintf(eos(buf), "%s%s", (first) ? "" : "+",
+                    valid_conditions[i].id);
+            first = FALSE;
+        }
+
+    if (!first && alias)
+        Sprintf(buf, "%s", alias);
+
+    return buf;
+}
+
+STATIC_OVL unsigned long
+match_str2conditionbitmask(str)
+const char *str;
+{
+    int i, nmatches = 0;
+    unsigned long mask = 0UL;
+
+    if (str && *str) {
+        /* check matches to canonical names */
+        for (i = 0; i < SIZE(valid_conditions); i++)
+            if (fuzzymatch(valid_conditions[i].id, str, " -_", TRUE)) {
+                mask |= valid_conditions[i].bitmask;
+                nmatches++;
+            }
+
+        if (!nmatches) {
+            /* check aliases */
+            for (i = 0; i < SIZE(condition_aliases); i++)
+                if (fuzzymatch(condition_aliases[i].id, str, " -_", TRUE)) {
+                    mask |= condition_aliases[i].bitmask;
+                    nmatches++;
+                }
+        }
+
+        if (!nmatches) {
+            /* check partial matches to aliases */
+            int len = (int) strlen(str);
+
+            for (i = 0; i < SIZE(condition_aliases); i++)
+                if (!strncmpi(str, condition_aliases[i].id, len)) {
+                    mask |= condition_aliases[i].bitmask;
+                    nmatches++;
+                }
+        }
+    }
+
+    return mask;
+}
+
+STATIC_OVL unsigned long
+str2conditionbitmask(str)
+char *str;
+{
+    unsigned long conditions_bitmask = 0UL;
+    char **subfields;
+    int i, sf;
+
+    sf = splitsubfields(str, &subfields, SIZE(valid_conditions));
+
+    if (sf < 1)
+        return 0UL;
+
+    for (i = 0; i < sf; ++i) {
+        unsigned long bm = match_str2conditionbitmask(subfields[i]);
+
+        if (!bm) {
+            config_error_add("Unknown condition '%s'", subfields[i]);
+            return 0UL;
+        }
+        conditions_bitmask |= bm;
+    }
+    return conditions_bitmask;
+}
+
+STATIC_OVL boolean
+parse_condition(s, sidx)
+char (*s)[QBUFSZ];
+int sidx;
+{
+    int i;
+    int coloridx = NO_COLOR;
+    char *tmp, *how;
+    unsigned long conditions_bitmask = 0UL;
+    boolean success = FALSE;
+
+    if (!s)
+        return FALSE;
+
+    /*3.6.1:
+      OPTION=hilite_status: condition/stone+slime+foodPois/red&inverse */
+
+    /*
+     * TODO?
+     *  It would be simpler to treat each condition (also hunger state
+     *  and encumbrance level) as if it were a separate field.  That
+     *  way they could have either or both 'changed' temporary rule and
+     *  'always' persistent rule and wouldn't need convoluted access to
+     *  the intended color and attributes.
+     */
+
+    sidx++;
+    while(s[sidx]) {
+        int sf = 0;     /* subfield count */
+        char buf[BUFSZ], **subfields;
+
+        tmp = s[sidx];
+        if (!*tmp) {
+            if (!success)
+                config_error_add("Missing condition(s)");
+            return success;
+        }
+
+        Strcpy(buf, tmp);
+        conditions_bitmask = str2conditionbitmask(buf);
+
+        if (!conditions_bitmask)
+            return FALSE;
+
+        /*
+         * We have the conditions_bitmask with bits set for
+         * each ailment we want in a particular color and/or
+         * attribute, but we need to assign it to an array of
+         * bitmasks indexed by the color chosen
+         *        (0 to (CLR_MAX - 1))
+         * and/or attributes chosen
+         *        (HL_ATTCLR_DIM to (BL_ATTCLR_MAX - 1))
+         * We still have to parse the colors and attributes out.
+         */
+
+        /* actions */
+        sidx++;
+        how = s[sidx];
+        if (!how || !*how) {
+            config_error_add("Missing color+attribute");
+            return FALSE;
+        }
+
+        Strcpy(buf, how);
+        sf = splitsubfields(buf, &subfields, 0);
+
+        /*
+         * conditions_bitmask now has bits set representing
+         * the conditions that player wants represented, but
+         * now we parse out *how* they will be represented.
+         *
+         * Only 1 colour is allowed, but potentially multiple
+         * attributes are allowed.
+         *
+         * We have the following additional array offsets to
+         * use for storing the attributes beyond the end of
+         * the color indexes, all of which are less than CLR_MAX.
+         * HL_ATTCLR_DIM        = CLR_MAX
+         * HL_ATTCLR_BLINK      = CLR_MAX + 1
+         * HL_ATTCLR_ULINE      = CLR_MAX + 2
+         * HL_ATTCLR_INVERSE    = CLR_MAX + 3
+         * HL_ATTCLR_BOLD       = CLR_MAX + 4
+         * HL_ATTCLR_MAX        = CLR_MAX + 5 (this is past array boundary)
+         *
+         */
+
+        for (i = 0; i < sf; ++i) {
+            int a = match_str2attr(subfields[i], FALSE);
+
+            if (a == ATR_DIM)
+                cond_hilites[HL_ATTCLR_DIM] |= conditions_bitmask;
+            else if (a == ATR_BLINK)
+                cond_hilites[HL_ATTCLR_BLINK] |= conditions_bitmask;
+            else if (a == ATR_ULINE)
+                cond_hilites[HL_ATTCLR_ULINE] |= conditions_bitmask;
+            else if (a == ATR_INVERSE)
+                cond_hilites[HL_ATTCLR_INVERSE] |= conditions_bitmask;
+            else if (a == ATR_BOLD)
+                cond_hilites[HL_ATTCLR_BOLD] |= conditions_bitmask;
+            else if (a == ATR_NONE) {
+                cond_hilites[HL_ATTCLR_DIM] &= ~conditions_bitmask;
+                cond_hilites[HL_ATTCLR_BLINK] &= ~conditions_bitmask;
+                cond_hilites[HL_ATTCLR_ULINE] &= ~conditions_bitmask;
+                cond_hilites[HL_ATTCLR_INVERSE] &= ~conditions_bitmask;
+                cond_hilites[HL_ATTCLR_BOLD] &= ~conditions_bitmask;
+            } else {
+                int k = match_str2clr(subfields[i]);
+
+                if (k >= CLR_MAX)
+                    return FALSE;
+                coloridx = k;
+            }
+        }
+        /* set the bits in the appropriate member of the
+           condition array according to color chosen as index */
+
+        cond_hilites[coloridx] |= conditions_bitmask;
+        success = TRUE;
+        sidx++;
+    }
+    return TRUE;
+}
+
+void
+clear_status_hilites()
+{
+    int i;
+
+    for (i = 0; i < MAXBLSTATS; ++i) {
+        struct hilite_s *temp, *next;
+
+        for (temp = blstats[0][i].thresholds; temp; temp = next) {
+            next = temp->next;
+            free(temp);
+        }
+        blstats[0][i].thresholds = blstats[1][i].thresholds = 0;
+        /* pointer into thresholds list, now stale */
+        blstats[0][i].hilite_rule = blstats[1][i].hilite_rule = 0;
+    }
+}
+
+STATIC_OVL char *
+hlattr2attrname(attrib, buf, bufsz)
+int attrib, bufsz;
+char *buf;
+{
+    if (attrib && buf) {
+        char attbuf[BUFSZ];
+        int k, first = 0;
+
+        attbuf[0] = '\0';
+        if (attrib == HL_NONE) {
+            Strcpy(buf, "normal");
+            return buf;
+        }
+
+        if (attrib & HL_BOLD)
+            Strcat(attbuf, first++ ? "+bold" : "bold");
+        if (attrib & HL_INVERSE)
+            Strcat(attbuf, first++ ? "+inverse" : "inverse");
+        if (attrib & HL_ULINE)
+            Strcat(attbuf, first++ ? "+underline" : "underline");
+        if (attrib & HL_BLINK)
+            Strcat(attbuf, first++ ? "+blink" : "blink");
+        if (attrib & HL_DIM)
+            Strcat(attbuf, first++ ? "+dim" : "dim");
+
+        k = strlen(attbuf);
+        if (k < (bufsz - 1))
+            Strcpy(buf, attbuf);
+        return buf;
+    }
+    return (char *) 0;
+}
+
+
+struct _status_hilite_line_str {
+    int id;
+    int fld;
+    struct hilite_s *hl;
+    unsigned long mask;
+    char str[BUFSZ];
+    struct _status_hilite_line_str *next;
+};
+
+static struct _status_hilite_line_str *status_hilite_str = 0;
+static int status_hilite_str_id = 0;
+
+STATIC_OVL void
+status_hilite_linestr_add(fld, hl, mask, str)
+int fld;
+struct hilite_s *hl;
+unsigned long mask;
+const char *str;
+{
+    struct _status_hilite_line_str *tmp, *nxt;
+
+    tmp = (struct _status_hilite_line_str *) alloc(sizeof *tmp);
+    (void) memset(tmp, 0, sizeof *tmp);
+    tmp->next = (struct _status_hilite_line_str *) 0;
+
+    tmp->id = ++status_hilite_str_id;
+    tmp->fld = fld;
+    tmp->hl = hl;
+    tmp->mask = mask;
+    if (fld == BL_TITLE)
+        Strcpy(tmp->str, str);
+    else
+        (void) stripchars(tmp->str, " ", str);
+
+    if ((nxt = status_hilite_str) != 0) {
+        while (nxt->next)
+            nxt = nxt->next;
+        nxt->next = tmp;
+    } else {
+        status_hilite_str = tmp;
+    }
+}
+
+STATIC_OVL void
+status_hilite_linestr_done()
+{
+    struct _status_hilite_line_str *nxt, *tmp = status_hilite_str;
+
+    while (tmp) {
+        nxt = tmp->next;
+        free(tmp);
+        tmp = nxt;
+    }
+    status_hilite_str = (struct _status_hilite_line_str *) 0;
+    status_hilite_str_id = 0;
+}
+
+STATIC_OVL int
+status_hilite_linestr_countfield(fld)
+int fld;
+{
+    struct _status_hilite_line_str *tmp;
+    boolean countall = (fld == BL_FLUSH);
+    int count = 0;
+
+    for (tmp = status_hilite_str; tmp; tmp = tmp->next) {
+        if (countall || tmp->fld == fld)
+            count++;
+    }
+    return count;
+}
+
+/* used by options handling, doset(options.c) */
+int
+count_status_hilites(VOID_ARGS)
+{
+    int count;
+
+    status_hilite_linestr_gather();
+    count = status_hilite_linestr_countfield(BL_FLUSH);
+    status_hilite_linestr_done();
+    return count;
+}
+
+STATIC_OVL void
+status_hilite_linestr_gather_conditions()
+{
+    int i;
+    struct _cond_map {
+        unsigned long bm;
+        unsigned long clratr;
+    } cond_maps[SIZE(valid_conditions)];
+
+    (void) memset(cond_maps, 0,
+                  SIZE(valid_conditions) * sizeof (struct _cond_map));
+
+    for (i = 0; i < SIZE(valid_conditions); i++) {
+        int clr = NO_COLOR;
+        int atr = HL_NONE;
+        int j;
+
+        for (j = 0; j < CLR_MAX; j++)
+            if (cond_hilites[j] & valid_conditions[i].bitmask) {
+                clr = j;
+                break;
+            }
+        if (cond_hilites[HL_ATTCLR_DIM] & valid_conditions[i].bitmask)
+            atr |= HL_DIM;
+        if (cond_hilites[HL_ATTCLR_BOLD] & valid_conditions[i].bitmask)
+            atr |= HL_BOLD;
+        if (cond_hilites[HL_ATTCLR_BLINK] & valid_conditions[i].bitmask)
+            atr |= HL_BLINK;
+        if (cond_hilites[HL_ATTCLR_ULINE] & valid_conditions[i].bitmask)
+            atr |= HL_ULINE;
+        if (cond_hilites[HL_ATTCLR_INVERSE] & valid_conditions[i].bitmask)
+            atr |= HL_INVERSE;
+        if (atr != HL_NONE)
+            atr &= ~HL_NONE;
+
+        if (clr != NO_COLOR || atr != HL_NONE) {
+            unsigned long ca = clr | (atr << 8);
+            boolean added_condmap = FALSE;
+
+            for (j = 0; j < SIZE(valid_conditions); j++)
+                if (cond_maps[j].clratr == ca) {
+                    cond_maps[j].bm |= valid_conditions[i].bitmask;
+                    added_condmap = TRUE;
+                    break;
+                }
+            if (!added_condmap) {
+                for (j = 0; j < SIZE(valid_conditions); j++)
+                    if (!cond_maps[j].bm) {
+                        cond_maps[j].bm = valid_conditions[i].bitmask;
+                        cond_maps[j].clratr = ca;
+                        break;
+                    }
+            }
+        }
+    }
+
+    for (i = 0; i < SIZE(valid_conditions); i++)
+        if (cond_maps[i].bm) {
+            int clr = NO_COLOR, atr = HL_NONE;
+
+            split_clridx(cond_maps[i].clratr, &clr, &atr);
+            if (clr != NO_COLOR || atr != HL_NONE) {
+                char clrbuf[BUFSZ];
+                char attrbuf[BUFSZ];
+                char condbuf[BUFSZ];
+                char *tmpattr;
+
+                (void) strNsubst(strcpy(clrbuf, clr2colorname(clr)),
+                                 " ", "-", 0);
+                tmpattr = hlattr2attrname(atr, attrbuf, BUFSZ);
+                if (tmpattr)
+                    Sprintf(eos(clrbuf), "&%s", tmpattr);
+                Sprintf(condbuf, "condition/%s/%s",
+                        conditionbitmask2str(cond_maps[i].bm), clrbuf);
+                status_hilite_linestr_add(BL_CONDITION, 0,
+                                          cond_maps[i].bm, condbuf);
+            }
+        }
+}
+
+STATIC_OVL void
+status_hilite_linestr_gather()
+{
+    int i;
+    struct hilite_s *hl;
+
+    status_hilite_linestr_done();
+
+    for (i = 0; i < MAXBLSTATS; i++) {
+        hl = blstats[0][i].thresholds;
+        while (hl) {
+            status_hilite_linestr_add(i, hl, 0UL, status_hilite2str(hl));
+            hl = hl->next;
+        }
+    }
+
+    status_hilite_linestr_gather_conditions();
+}
+
+
+STATIC_OVL char *
+status_hilite2str(hl)
+struct hilite_s *hl;
+{
+    static char buf[BUFSZ];
+    int clr = 0, attr = 0;
+    char behavebuf[BUFSZ];
+    char clrbuf[BUFSZ];
+    char attrbuf[BUFSZ];
+    char *tmpattr;
+    const char *op;
+
+    if (!hl)
+        return (char *) 0;
+
+    behavebuf[0] = '\0';
+    clrbuf[0] = '\0';
+    op = (hl->rel == LT_VALUE) ? "<"
+           : (hl->rel == LE_VALUE) ? "<="
+             : (hl->rel == GT_VALUE) ? ">"
+               : (hl->rel == GE_VALUE) ? ">="
+                 : (hl->rel == EQ_VALUE) ? "="
+                   : 0;
+
+    switch (hl->behavior) {
+    case BL_TH_VAL_PERCENTAGE:
+        if (op)
+            Sprintf(behavebuf, "%s%d%%", op, hl->value.a_int);
+        else
+            impossible("hl->behavior=percentage, rel error");
+        break;
+    case BL_TH_UPDOWN:
+        if (hl->rel == LT_VALUE)
+            Sprintf(behavebuf, "down");
+        else if (hl->rel == GT_VALUE)
+            Sprintf(behavebuf, "up");
+        else if (hl->rel == EQ_VALUE)
+            Sprintf(behavebuf, "changed");
+        else
+            impossible("hl->behavior=updown, rel error");
+        break;
+    case BL_TH_VAL_ABSOLUTE:
+        if (op)
+            Sprintf(behavebuf, "%s%d", op, hl->value.a_int);
+        else
+            impossible("hl->behavior=absolute, rel error");
+        break;
+    case BL_TH_TEXTMATCH:
+        if (hl->rel == TXT_VALUE && hl->textmatch[0])
+            Sprintf(behavebuf, "%s", hl->textmatch);
+        else
+            impossible("hl->behavior=textmatch, rel or textmatch error");
+        break;
+    case BL_TH_CONDITION:
+        if (hl->rel == EQ_VALUE)
+            Sprintf(behavebuf, "%s", conditionbitmask2str(hl->value.a_ulong));
+        else
+            impossible("hl->behavior=condition, rel error");
+        break;
+    case BL_TH_ALWAYS_HILITE:
+        Sprintf(behavebuf, "always");
+        break;
+    case BL_TH_NONE:
+        break;
+    default:
+        break;
     }
-    if (idx == -1)
-        return FALSE;
-    status_hilites[idx].set = FALSE; /* mark it "unset" */
 
-    /* threshold */
-    if (!sb)
-        return FALSE;
-    if ((strcmpi(sb, "updown") == 0) || (strcmpi(sb, "downup") == 0)
-        || (strcmpi(sb, "up") == 0) || (strcmpi(sb, "down") == 0)) {
-        down_up = TRUE;
-    } else if ((strcmpi(sb, "changed") == 0)
-               && (fld == BL_TITLE || fld == BL_ALIGN || fld == BL_LEVELDESC
-                   || fld == BL_CONDITION)) {
-        changed = TRUE; /* changed is only thing allowed */
-    } else {
-        tmp = sb;
-        while (*tmp) {
-            if (*tmp == '%') {
-                *tmp = '\0';
-                percent = TRUE;
-                break;
-            } else if (!index("0123456789", *tmp))
-                return FALSE;
-            tmp++;
-        }
-        if (strlen(sb) > 0) {
-            dt = blstats[0][idx].anytype;
-            if (percent)
-                dt = ANY_INT;
-            (void) s_to_anything(&threshold, sb, dt);
-        } else
-            return FALSE;
-        if (percent && (threshold.a_int < 1 || threshold.a_int > 100))
-            return FALSE;
-        if (!threshold.a_void && (strcmp(sb, "0") != 0))
-            return FALSE;
+    split_clridx(hl->coloridx, &clr, &attr);
+    (void) strNsubst(strcpy(clrbuf, clr2colorname(clr)), " ", "-", 0);
+    if (attr != HL_UNDEF) {
+        if ((tmpattr = hlattr2attrname(attr, attrbuf, BUFSZ)) != 0)
+            Sprintf(eos(clrbuf), "&%s", tmpattr);
     }
+    Sprintf(buf, "%s/%s/%s", initblstats[hl->fld].fldname, behavebuf, clrbuf);
 
-    /* actions */
-    for (i = 0; i < 2; ++i) {
-        if (!i)
-            how = sc;
-        else
-            how = sd;
-        if (!how) {
-            if (!i)
-                return FALSE;
-            else
-                break; /* sc is mandatory; sd is not */
-        }
+    return buf;
+}
 
-        if (strcmpi(how, "bold") == 0) {
-            bold[i] = TRUE;
-        } else if (strcmpi(how, "inverse") == 0) {
-            inverse[i] = TRUE;
-        } else if (strcmpi(how, "normal") == 0) {
-            normal[i] = TRUE;
-        } else {
-            int k;
-            char colorname[BUFSZ];
-            for (k = 0; k < CLR_MAX; ++k) {
-                /* we have to make a copy to change space to dash */
-                (void) strcpy(colorname, c_obj_colors[k]);
-                for (tmp = index(colorname, ' '); tmp;
-                     tmp = index(colorname, ' '))
-                    *tmp = '-';
-                if (strcmpi(how, colorname) == 0) {
-                    coloridx[i] = k;
-                    break;
-                }
-            }
-            if (k >= CLR_MAX)
-                return FALSE;
-        }
+STATIC_OVL int
+status_hilite_menu_choose_field()
+{
+    winid tmpwin;
+    int i, res, fld = BL_FLUSH;
+    anything any;
+    menu_item *picks = (menu_item *) 0;
+
+    tmpwin = create_nhwindow(NHW_MENU);
+    start_menu(tmpwin);
+
+    for (i = 0; i < MAXBLSTATS; i++) {
+#ifndef SCORE_ON_BOTL
+        if (initblstats[i].fld == BL_SCORE
+            && !blstats[0][BL_SCORE].thresholds)
+            continue;
+#endif
+        any = zeroany;
+        any.a_int = (i + 1);
+        add_menu(tmpwin, NO_GLYPH, &any, 0, 0, ATR_NONE,
+                 initblstats[i].fldname, MENU_UNSELECTED);
     }
 
-    /* Assign the values */
+    end_menu(tmpwin, "Select a hilite field:");
 
-    for (i = 0; i < 2; ++i) {
-        if (inverse[i])
-            status_hilites[idx].coloridx[i] = BL_HILITE_INVERSE;
-        else if (bold[i])
-            status_hilites[idx].coloridx[i] = BL_HILITE_BOLD;
-        else if (coloridx[i])
-            status_hilites[idx].coloridx[i] = coloridx[i];
-        else
-            status_hilites[idx].coloridx[i] = BL_HILITE_NONE;
+    res = select_menu(tmpwin, PICK_ONE, &picks);
+    destroy_nhwindow(tmpwin);
+    if (res > 0) {
+        fld = picks->item.a_int - 1;
+        free((genericptr_t) picks);
     }
+    return fld;
+}
 
-    if (percent)
-        status_hilites[idx].behavior = BL_TH_VAL_PERCENTAGE;
-    else if (down_up)
-        status_hilites[idx].behavior = BL_TH_UPDOWN;
-    else if (threshold.a_void)
-        status_hilites[idx].behavior = BL_TH_VAL_ABSOLUTE;
-    else
-        status_hilites[idx].behavior = BL_TH_NONE;
+STATIC_OVL int
+status_hilite_menu_choose_behavior(fld)
+int fld;
+{
+    winid tmpwin;
+    int res = 0, beh = BL_TH_NONE-1;
+    anything any;
+    menu_item *picks = (menu_item *) 0;
+    char buf[BUFSZ];
+    int at;
+    int onlybeh = BL_TH_NONE, nopts = 0;
+
+    if (fld < 0 || fld >= MAXBLSTATS)
+        return BL_TH_NONE;
+
+    at = initblstats[fld].anytype;
 
-    if (status_hilites[idx].behavior != BL_TH_NONE) {
-        status_hilites[idx].threshold = threshold;
-        status_hilites[idx].set = TRUE;
+    tmpwin = create_nhwindow(NHW_MENU);
+    start_menu(tmpwin);
+
+    if (fld != BL_CONDITION) {
+        any = zeroany;
+        any.a_int = onlybeh = BL_TH_ALWAYS_HILITE;
+        Sprintf(buf, "Always highlight %s", initblstats[fld].fldname);
+        add_menu(tmpwin, NO_GLYPH, &any, 'a', 0, ATR_NONE,
+                 buf, MENU_UNSELECTED);
+        nopts++;
+    }
+
+    if (fld == BL_CONDITION) {
+        any = zeroany;
+        any.a_int = onlybeh = BL_TH_CONDITION;
+        add_menu(tmpwin, NO_GLYPH, &any, 'b', 0, ATR_NONE,
+                 "Bitmask of conditions", MENU_UNSELECTED);
+        nopts++;
     }
-    status_hilites[idx].anytype = dt;
 
-    /* Now finally, we notify the window port */
-    if (!from_configfile)
-        status_threshold(idx, status_hilites[idx].anytype,
-                        status_hilites[idx].threshold,
-                        status_hilites[idx].behavior,
-                        status_hilites[idx].coloridx[0],
-                        status_hilites[idx].coloridx[1]);
+    if (fld != BL_CONDITION) {
+        any = zeroany;
+        any.a_int = onlybeh = BL_TH_UPDOWN;
+        Sprintf(buf, "%s value changes", initblstats[fld].fldname);
+        add_menu(tmpwin, NO_GLYPH, &any, 'c', 0, ATR_NONE,
+                 buf, MENU_UNSELECTED);
+        nopts++;
+    }
 
-    return TRUE;
+    if (fld != BL_CAP && fld != BL_HUNGER
+        && (at == ANY_INT || at == ANY_LONG)) {
+        any = zeroany;
+        any.a_int = onlybeh = BL_TH_VAL_ABSOLUTE;
+        add_menu(tmpwin, NO_GLYPH, &any, 'n', 0, ATR_NONE,
+                 "Number threshold", MENU_UNSELECTED);
+        nopts++;
+    }
+
+    if (initblstats[fld].idxmax >= 0) {
+        any = zeroany;
+        any.a_int = onlybeh = BL_TH_VAL_PERCENTAGE;
+        add_menu(tmpwin, NO_GLYPH, &any, 'p', 0, ATR_NONE,
+                 "Percentage threshold", MENU_UNSELECTED);
+        nopts++;
+    }
+
+    if (initblstats[fld].anytype == ANY_STR
+        || fld == BL_CAP || fld == BL_HUNGER) {
+        any = zeroany;
+        any.a_int = onlybeh = BL_TH_TEXTMATCH;
+        Sprintf(buf, "%s text match", initblstats[fld].fldname);
+        add_menu(tmpwin, NO_GLYPH, &any, 't', 0, ATR_NONE,
+                 buf, MENU_UNSELECTED);
+        nopts++;
+    }
+
+    Sprintf(buf, "Select %s field hilite behavior:", initblstats[fld].fldname);
+    end_menu(tmpwin, buf);
+
+    if (nopts > 1) {
+        res = select_menu(tmpwin, PICK_ONE, &picks);
+        if (res == 0) /* none chosen*/
+            beh = BL_TH_NONE;
+        else if (res == -1) /* menu cancelled */
+            beh = (BL_TH_NONE - 1);
+    } else if (onlybeh != BL_TH_NONE)
+        beh = onlybeh;
+    destroy_nhwindow(tmpwin);
+    if (res > 0) {
+        beh = picks->item.a_int;
+        free((genericptr_t) picks);
+    }
+    return beh;
 }
 
-void
-status_notify_windowport(all)
-boolean all;
-{
-    int idx;
-    anything it;
-
-    it = zeroany;
-    for (idx = 0; idx < MAXBLSTATS; ++idx) {
-        if (status_hilites[idx].set)
-            status_threshold(idx, status_hilites[idx].anytype,
-                            status_hilites[idx].threshold,
-                            status_hilites[idx].behavior,
-                            status_hilites[idx].coloridx[0],
-                            status_hilites[idx].coloridx[1]);
+STATIC_OVL int
+status_hilite_menu_choose_updownboth(fld, str, ltok, gtok)
+int fld;
+const char *str;
+boolean ltok, gtok;
+{
+    int res, ret = NO_LTEQGT;
+    winid tmpwin;
+    char buf[BUFSZ];
+    anything any;
+    menu_item *picks = (menu_item *) 0;
+
+    tmpwin = create_nhwindow(NHW_MENU);
+    start_menu(tmpwin);
+
+    if (ltok) {
+        if (str)
+            Sprintf(buf, "%s than %s",
+                    (fld == BL_AC) ? "Better (lower)" : "Less", str);
+        else
+            Sprintf(buf, "Value goes down");
+        any = zeroany;
+        any.a_int = 10 + LT_VALUE;
+        add_menu(tmpwin, NO_GLYPH, &any, 0, 0, ATR_NONE,
+                 buf, MENU_UNSELECTED);
+
+        if (str) {
+            Sprintf(buf, "%s or %s",
+                    str, (fld == BL_AC) ? "better (lower)" : "less");
+            any = zeroany;
+            any.a_int = 10 + LE_VALUE;
+            add_menu(tmpwin, NO_GLYPH, &any, 0, 0, ATR_NONE,
+                     buf, MENU_UNSELECTED);
+        }
+    }
+
+    if (str)
+        Sprintf(buf, "Exactly %s", str);
+    else
+        Sprintf(buf, "Value changes");
+    any = zeroany;
+    any.a_int = 10 + EQ_VALUE;
+    add_menu(tmpwin, NO_GLYPH, &any, 0, 0, ATR_NONE,
+             buf, MENU_UNSELECTED);
+
+    if (gtok) {
+        if (str) {
+            Sprintf(buf, "%s or %s",
+                    str, (fld == BL_AC) ? "worse (higher)" : "more");
+            any = zeroany;
+            any.a_int = 10 + GE_VALUE;
+            add_menu(tmpwin, NO_GLYPH, &any, 0, 0, ATR_NONE,
+                     buf, MENU_UNSELECTED);
+        }
+
+        if (str)
+            Sprintf(buf, "%s than %s",
+                    (fld == BL_AC) ? "Worse (higher)" : "More", str);
         else
-            status_threshold(idx, blstats[0][idx].anytype, it, 0, 0, 0);
+            Sprintf(buf, "Value goes up");
+        any = zeroany;
+        any.a_int = 10 + GT_VALUE;
+        add_menu(tmpwin, NO_GLYPH, &any, 0, 0, ATR_NONE,
+             buf, MENU_UNSELECTED);
+    }
+    Sprintf(buf, "Select field %s value:", initblstats[fld].fldname);
+    end_menu(tmpwin, buf);
 
+    res = select_menu(tmpwin, PICK_ONE, &picks);
+    destroy_nhwindow(tmpwin);
+    if (res > 0) {
+        ret = picks->item.a_int - 10;
+        free((genericptr_t) picks);
     }
+
+    return ret;
 }
 
-/*
- * get_status_hilites
- *
- * Returns a string containing all the status hilites in the
- * same format that is used to specify a status hilite preference
- * in the config file.
- */
-char *
-get_status_hilites(buf, bufsiz)
-char *buf;
-int bufsiz;
+STATIC_OVL boolean
+status_hilite_menu_add(origfld)
+int origfld;
 {
-    int i, j, k, coloridx;
-    const char *text = (char *) 0;
-    char tmp[BUFSZ], colorname[BUFSZ];
-    boolean val_percentage, val_absolute, up_down;
-    boolean added_one = FALSE;
+    int fld;
+    int behavior;
+    int lt_gt_eq;
+    int clr = NO_COLOR, atr = HL_UNDEF;
+    struct hilite_s hilite;
+    unsigned long cond = 0UL;
+    char colorqry[BUFSZ];
+    char attrqry[BUFSZ];
+
+choose_field:
+    fld = origfld;
+    if (fld == BL_FLUSH) {
+        fld = status_hilite_menu_choose_field();
+        /* isn't this redundant given what follows? */
+        if (fld == BL_FLUSH)
+            return FALSE;
+    }
 
-    if (!buf)
-        return (char *) 0;
-    *buf = '\0';
+    if (fld == BL_FLUSH)
+        return FALSE;
 
-    bufsiz--; /* required trailing null */
-    for (i = 0; i < MAXBLSTATS; ++i) {
-        val_percentage = val_absolute = up_down = FALSE;
-        if (status_hilites[i].set) {
-            if (!added_one)
-                added_one = TRUE;
-            else {
-                Strcat(buf, " ");
-                bufsiz--;
+    colorqry[0] = '\0';
+    attrqry[0] = '\0';
+
+    memset((genericptr_t) &hilite, 0, sizeof (struct hilite_s));
+    hilite.next = (struct hilite_s *) 0;
+    hilite.set = FALSE; /* mark it "unset" */
+    hilite.fld = fld;
+
+choose_behavior:
+    behavior = status_hilite_menu_choose_behavior(fld);
+
+    if (behavior == (BL_TH_NONE - 1)) {
+        return FALSE;
+    } else if (behavior == BL_TH_NONE) {
+        if (origfld == BL_FLUSH)
+            goto choose_field;
+        return FALSE;
+    }
+
+    hilite.behavior = behavior;
+
+choose_value:
+    if (behavior == BL_TH_VAL_PERCENTAGE
+        || behavior == BL_TH_VAL_ABSOLUTE) {
+        char inbuf[BUFSZ], buf[BUFSZ];
+        anything aval;
+        int val, dt;
+        boolean gotnum = FALSE, percent = (behavior == BL_TH_VAL_PERCENTAGE);
+        char *inp, *numstart;
+        const char *op;
+
+        lt_gt_eq = NO_LTEQGT; /* not set up yet */
+        inbuf[0] = '\0';
+        Sprintf(buf, "Enter %svalue for %s threshold:",
+                percent ? "percentage " : "",
+                initblstats[fld].fldname);
+        getlin(buf, inbuf);
+        if (inbuf[0] == '\0' || inbuf[0] == '\033')
+            goto choose_behavior;
+
+        inp = numstart = trimspaces(inbuf);
+        if (!*inp)
+            goto choose_behavior;
+
+        /* allow user to enter "<50%" or ">50" or just "50"
+           or <=50% or >=50 or =50 */
+        if (*inp == '>' || *inp == '<' || *inp == '=') {
+            lt_gt_eq = (*inp == '>') ? ((inp[1] == '=') ? GE_VALUE : GT_VALUE)
+                     : (*inp == '<') ? ((inp[1] == '=') ? LE_VALUE : LT_VALUE)
+                       : EQ_VALUE;
+            *inp++ = ' ';
+            numstart++;
+            if (lt_gt_eq == GE_VALUE || lt_gt_eq == LE_VALUE) {
+                *inp++ = ' ';
+                numstart++;
             }
-            k = strlen(fieldids[i].fieldname);
-            if (k < bufsiz) {
-                Strcat(buf, fieldids[i].fieldname);
-                bufsiz -= k;
+        }
+        if (*inp == '-') {
+            inp++;
+        } else if (*inp == '+') {
+            *inp++ = ' ';
+            numstart++;
+        }
+        while (digit(*inp)) {
+            inp++;
+            gotnum = TRUE;
+        }
+        if (*inp == '%') {
+            if (!percent) {
+                pline("Not expecting a percentage.");
+                goto choose_behavior;
             }
-            if (bufsiz > 1) {
-                Strcat(buf, "/");
-                bufsiz--;
+            *inp = '\0'; /* strip '%' [this accepts trailing junk!] */
+        } else if (*inp) {
+            /* some random characters */
+            pline("\"%s\" is not a recognized number.", inp);
+            goto choose_value;
+        }
+        if (!gotnum) {
+            pline("Is that an invisible number?");
+            goto choose_value;
+        }
+        op = (lt_gt_eq == LT_VALUE) ? "<"
+               : (lt_gt_eq == LE_VALUE) ? "<="
+                 : (lt_gt_eq == GT_VALUE) ? ">"
+                   : (lt_gt_eq == GE_VALUE) ? ">="
+                     : (lt_gt_eq == EQ_VALUE) ? "="
+                       : ""; /* didn't specify lt_gt_eq with number */
+
+        aval = zeroany;
+        dt = percent ? ANY_INT : initblstats[fld].anytype;
+        (void) s_to_anything(&aval, numstart, dt);
+
+        if (percent) {
+            val = aval.a_int;
+            if (initblstats[fld].idxmax == -1) {
+                pline("Field '%s' does not support percentage values.",
+                      initblstats[fld].fldname);
+                behavior = BL_TH_VAL_ABSOLUTE;
+                goto choose_value;
             }
-            if (status_hilites[i].behavior == BL_TH_VAL_PERCENTAGE) {
-                val_percentage = TRUE;
-            } else if (status_hilites[i].behavior == BL_TH_VAL_ABSOLUTE) {
-                val_absolute = TRUE;
-            } else if (status_hilites[i].behavior == BL_TH_UPDOWN) {
-                up_down = TRUE;
-                text = "updown";
+            /* if player only specified a number then lt_gt_eq isn't set
+               up yet and the >-1 and <101 exceptions can't be honored;
+               deliberate use of those should be uncommon enough for
+               that to be palatable; for 0 and 100, choose_updown_both()
+               will prevent useless operations */
+            if ((val < 0 && (val != -1 || lt_gt_eq != GT_VALUE))
+                || (val == 0 && lt_gt_eq == LT_VALUE)
+                || (val == 100 && lt_gt_eq == GT_VALUE)
+                || (val > 100 && (val != 101 || lt_gt_eq != LT_VALUE))) {
+                pline("'%s%d%%' is not a valid percent value.", op, val);
+                goto choose_value;
             }
+            /* restore suffix for use in color and attribute prompts */
+            if (!index(numstart, '%'))
+                Strcat(numstart, "%");
+
+        /* reject negative values except for AC and >-1; reject 0 for < */
+        } else if (dt == ANY_INT
+                   && (aval.a_int < ((fld == BL_AC) ? -128
+                                     : (lt_gt_eq == GT_VALUE) ? -1
+                                       : (lt_gt_eq == LT_VALUE) ? 1 : 0))) {
+            pline("%s'%s%d'%s", threshold_value,
+                  op, aval.a_int, is_out_of_range);
+            goto choose_value;
+        } else if (dt == ANY_LONG
+                   && (aval.a_long < ((lt_gt_eq == GT_VALUE) ? -1L
+                                      : (lt_gt_eq == LT_VALUE) ? 1L : 0L))) {
+            pline("%s'%s%ld'%s", threshold_value,
+                  op, aval.a_long, is_out_of_range);
+            goto choose_value;
+        }
 
-            if (status_hilites[i].behavior != BL_TH_UPDOWN) {
-                anything_to_s(tmp, &status_hilites[i].threshold,
-                          blstats[0][i].anytype);
-                text = tmp;
-            }
-            k = strlen(text);
-            if (k < (bufsiz - 1)) {
-                Strcat(buf, text);
-                if (val_percentage)
-                    Strcat(buf, "%"), k++;
-                bufsiz -= k;
-            }
-            for (j = 0; j < 2; ++j) {
-                if (bufsiz > 1) {
-                    Strcat(buf, "/");
-                    bufsiz--;
-                }
-                coloridx = status_hilites[i].coloridx[j];
-                if (coloridx < 0) {
-                    if (coloridx == BL_HILITE_BOLD)
-                        text = "bold";
-                    else if (coloridx == BL_HILITE_INVERSE)
-                        text = "inverse";
-                    else
-                        text = "normal";
+        if (lt_gt_eq == NO_LTEQGT) {
+            boolean ltok = ((dt == ANY_INT)
+                            ? (aval.a_int > 0 || fld == BL_AC)
+                            : (aval.a_long > 0L)),
+                    gtok = (!percent || aval.a_long < 100);
+
+            lt_gt_eq = status_hilite_menu_choose_updownboth(fld, inbuf,
+                                                            ltok, gtok);
+            if (lt_gt_eq == NO_LTEQGT)
+                goto choose_value;
+        }
+
+        Sprintf(colorqry, "Choose a color for when %s is %s%s%s:",
+                initblstats[fld].fldname,
+                (lt_gt_eq == LT_VALUE) ? "less than "
+                  : (lt_gt_eq == GT_VALUE) ? "more than "
+                    : "",
+                numstart,
+                (lt_gt_eq == LE_VALUE) ? " or less"
+                  : (lt_gt_eq == GE_VALUE) ? " or more"
+                    : "");
+        Sprintf(attrqry, "Choose attribute for when %s is %s%s%s:",
+                initblstats[fld].fldname,
+                (lt_gt_eq == LT_VALUE) ? "less than "
+                  : (lt_gt_eq == GT_VALUE) ? "more than "
+                    : "",
+                numstart,
+                (lt_gt_eq == LE_VALUE) ? " or less"
+                  : (lt_gt_eq == GE_VALUE) ? " or more"
+                    : "");
+
+        hilite.rel = lt_gt_eq;
+        hilite.value = aval;
+    } else if (behavior == BL_TH_UPDOWN) {
+        if (initblstats[fld].anytype != ANY_STR) {
+            boolean ltok = (fld != BL_TIME), gtok = TRUE;
+
+            lt_gt_eq = status_hilite_menu_choose_updownboth(fld, (char *)0,
+                                                            ltok, gtok);
+            if (lt_gt_eq == NO_LTEQGT)
+                goto choose_behavior;
+        } else { /* ANY_STR */
+            /* player picked '<field> value changes' in outer menu;
+               ordered string comparison is supported but LT/GT for the
+               string status fields (title, dungeon level, alignment)
+               is pointless; rather than calling ..._choose_updownboth()
+               with ltok==False plus gtok=False and having a menu with a
+               single choice, skip it altogether and just use 'changed' */
+            lt_gt_eq = EQ_VALUE;
+        }
+        Sprintf(colorqry, "Choose a color for when %s %s:",
+                initblstats[fld].fldname,
+                (lt_gt_eq == EQ_VALUE) ? "changes"
+                  : (lt_gt_eq == LT_VALUE) ? "decreases"
+                    : "increases");
+        Sprintf(attrqry, "Choose attribute for when %s %s:",
+                initblstats[fld].fldname,
+                (lt_gt_eq == EQ_VALUE) ? "changes"
+                  : (lt_gt_eq == LT_VALUE) ? "decreases"
+                    : "increases");
+        hilite.rel = lt_gt_eq;
+    } else if (behavior == BL_TH_CONDITION) {
+        cond = query_conditions();
+        if (!cond) {
+            if (origfld == BL_FLUSH)
+                goto choose_field;
+            return FALSE;
+        }
+        Sprintf(colorqry, "Choose a color for conditions %s:",
+                conditionbitmask2str(cond));
+        Sprintf(attrqry, "Choose attribute for conditions %s:",
+                conditionbitmask2str(cond));
+    } else if (behavior == BL_TH_TEXTMATCH) {
+        char qry_buf[BUFSZ];
+
+        Sprintf(qry_buf, "%s %s text value to match:",
+                (fld == BL_CAP
+                 || fld == BL_ALIGN
+                 || fld == BL_HUNGER
+                 || fld == BL_TITLE) ? "Choose" : "Enter",
+                initblstats[fld].fldname);
+        if (fld == BL_CAP) {
+#if 0 /*JP*/
+            int rv = query_arrayvalue(qry_buf,
+                                      enc_stat,
+                                      SLT_ENCUMBER, OVERLOADED + 1);
+#else
+            int rv = query_arrayvalue(qry_buf,
+                                      enc_stat_opt,
+                                      SLT_ENCUMBER, OVERLOADED + 1);
+#endif
+
+            if (rv < SLT_ENCUMBER)
+                goto choose_behavior;
+
+            hilite.rel = TXT_VALUE;
+            Strcpy(hilite.textmatch, enc_stat[rv]);
+        } else if (fld == BL_ALIGN) {
+            static const char *aligntxt[] = { "chaotic", "neutral", "lawful" };
+            int rv = query_arrayvalue(qry_buf,
+                                      aligntxt, 0, 2 + 1);
+
+            if (rv < 0)
+                goto choose_behavior;
+
+            hilite.rel = TXT_VALUE;
+            Strcpy(hilite.textmatch, aligntxt[rv]);
+        } else if (fld == BL_HUNGER) {
+            static const char *hutxt[] = { "Satiated", (char *) 0, "Hungry",
+                                           "Weak", "Fainting", "Fainted",
+                                           "Starved" };
+            int rv = query_arrayvalue(qry_buf,
+                                      hutxt,
+                                      SATIATED, STARVED + 1);
+
+            if (rv < SATIATED)
+                goto choose_behavior;
+
+            hilite.rel = TXT_VALUE;
+            Strcpy(hilite.textmatch, hutxt[rv]);
+        } else if (fld == BL_TITLE) {
+            const char *rolelist[3 * 9 + 1];
+            char mbuf[MAXVALWIDTH], fbuf[MAXVALWIDTH], obuf[MAXVALWIDTH];
+            int i, j, rv;
+
+            for (i = j = 0; i < 9; i++) {
+                Sprintf(mbuf, "\"%s\"", urole.rank[i].m);
+                if (urole.rank[i].f) {
+                    Sprintf(fbuf, "\"%s\"", urole.rank[i].f);
+                    Sprintf(obuf, "%s or %s",
+                            flags.female ? fbuf : mbuf,
+                            flags.female ? mbuf : fbuf);
                 } else {
-                    char *blank;
-                    (void) strcpy(colorname, c_obj_colors[coloridx]);
-                    for (blank = index(colorname, ' '); blank;
-                         blank = index(colorname, ' '))
-                        *blank = '-';
-                    text = colorname;
+                    fbuf[0] = obuf[0] = '\0';
                 }
-                k = strlen(text);
-                if (k < bufsiz) {
-                    Strcat(buf, text);
-                    bufsiz -= k;
+                if (flags.female) {
+                    if (*fbuf)
+                        rolelist[j++] = dupstr(fbuf);
+                    rolelist[j++] = dupstr(mbuf);
+                    if (*obuf)
+                        rolelist[j++] = dupstr(obuf);
+                } else {
+                    rolelist[j++] = dupstr(mbuf);
+                    if (*fbuf)
+                        rolelist[j++] = dupstr(fbuf);
+                    if (*obuf)
+                        rolelist[j++] = dupstr(obuf);
                 }
             }
+            rolelist[j++] = dupstr("\"none of the above (polymorphed)\"");
+
+            rv = query_arrayvalue(qry_buf, rolelist, 0, j);
+            if (rv >= 0) {
+                hilite.rel = TXT_VALUE;
+                Strcpy(hilite.textmatch, rolelist[rv]);
+            }
+            for (i = 0; i < j; i++)
+                free((genericptr_t) rolelist[i]), rolelist[i] = 0;
+            if (rv < 0)
+                goto choose_behavior;
+        } else {
+            char inbuf[BUFSZ];
+
+            inbuf[0] = '\0';
+            getlin(qry_buf, inbuf);
+            if (inbuf[0] == '\0' || inbuf[0] == '\033')
+                goto choose_behavior;
+
+            hilite.rel = TXT_VALUE;
+            if (strlen(inbuf) < sizeof hilite.textmatch)
+                Strcpy(hilite.textmatch, inbuf);
+            else
+                return FALSE;
         }
+        Sprintf(colorqry, "Choose a color for when %s is '%s':",
+                initblstats[fld].fldname, hilite.textmatch);
+        Sprintf(attrqry, "Choose attribute for when %s is '%s':",
+                initblstats[fld].fldname, hilite.textmatch);
+    } else if (behavior == BL_TH_ALWAYS_HILITE) {
+        Sprintf(colorqry, "Choose a color to always hilite %s:",
+                initblstats[fld].fldname);
+        Sprintf(attrqry, "Choose attribute to always hilite %s:",
+                initblstats[fld].fldname);
     }
-    return buf;
+
+choose_color:
+    clr = query_color(colorqry);
+    if (clr == -1) {
+        if (behavior != BL_TH_ALWAYS_HILITE)
+            goto choose_value;
+        else
+            goto choose_behavior;
+    }
+    atr = query_attr(attrqry);
+    if (atr == -1)
+        goto choose_color;
+
+    if (behavior == BL_TH_CONDITION) {
+        char clrbuf[BUFSZ];
+        char attrbuf[BUFSZ];
+        char *tmpattr;
+
+        if (atr & HL_DIM)
+            cond_hilites[HL_ATTCLR_DIM] |= cond;
+        if (atr & HL_BLINK)
+            cond_hilites[HL_ATTCLR_BLINK] |= cond;
+        if (atr & HL_ULINE)
+            cond_hilites[HL_ATTCLR_ULINE] |= cond;
+        if (atr & HL_INVERSE)
+            cond_hilites[HL_ATTCLR_INVERSE] |= cond;
+        if (atr & HL_BOLD)
+            cond_hilites[HL_ATTCLR_BOLD] |= cond;
+        if (atr == HL_NONE) {
+            cond_hilites[HL_ATTCLR_DIM] &= ~cond;
+            cond_hilites[HL_ATTCLR_BLINK] &= ~cond;
+            cond_hilites[HL_ATTCLR_ULINE] &= ~cond;
+            cond_hilites[HL_ATTCLR_INVERSE] &= ~cond;
+            cond_hilites[HL_ATTCLR_BOLD] &= ~cond;
+        }
+        cond_hilites[clr] |= cond;
+        (void) strNsubst(strcpy(clrbuf, clr2colorname(clr)), " ", "-", 0);
+        tmpattr = hlattr2attrname(atr, attrbuf, BUFSZ);
+        if (tmpattr)
+            Sprintf(eos(clrbuf), "&%s", tmpattr);
+        pline("Added hilite condition/%s/%s",
+              conditionbitmask2str(cond), clrbuf);
+    } else {
+        char *p, *q;
+
+        hilite.coloridx = clr | (atr << 8);
+        hilite.anytype = initblstats[fld].anytype;
+
+        if (fld == BL_TITLE && (p = strstri(hilite.textmatch, " or ")) != 0) {
+            /* split menu choice "male-rank or female-rank" into two distinct
+               but otherwise identical rules, "male-rank" and "female-rank" */
+            *p = '\0'; /* chop off " or female-rank" */
+            /* new rule for male-rank */
+            status_hilite_add_threshold(fld, &hilite);
+            pline("Added hilite %s", status_hilite2str(&hilite));
+            /* transfer female-rank to start of hilite.textmatch buffer */
+            p += sizeof " or " - sizeof "";
+            q = hilite.textmatch;
+            while ((*q++ = *p++) != '\0')
+                continue;
+            /* proceed with normal addition of new rule */
+        }
+        status_hilite_add_threshold(fld, &hilite);
+        pline("Added hilite %s", status_hilite2str(&hilite));
+    }
+    reset_status_hilites();
+    return TRUE;
 }
 
-STATIC_OVL const char *
-clridx_to_s(buf, idx)
-char *buf;
-int idx;
+boolean
+status_hilite_remove(id)
+int id;
 {
-    static const char *a[] = { "bold", "inverse", "normal" };
-    char* p = 0;
+    struct _status_hilite_line_str *hlstr = status_hilite_str;
 
-    if (buf) {
-        buf[0] = '\0';
-        if (idx < 0 && idx >= BL_HILITE_BOLD)
-            Strcpy(buf, a[idx + 3]);
-        else if (idx >= 0 && idx < CLR_MAX)
-            Strcpy(buf, c_obj_colors[idx]);
-        /* replace spaces with - */
-        for(p = buf; *p; p++)
-            if(*p == ' ') *p = '-';
+    while (hlstr && hlstr->id != id) {
+        hlstr = hlstr->next;
     }
-    return buf;
+
+    if (!hlstr)
+        return FALSE;
+
+    if (hlstr->fld == BL_CONDITION) {
+        int i;
+
+        for (i = 0; i < CLR_MAX; i++)
+            cond_hilites[i] &= ~hlstr->mask;
+        cond_hilites[HL_ATTCLR_DIM] &= ~hlstr->mask;
+        cond_hilites[HL_ATTCLR_BOLD] &= ~hlstr->mask;
+        cond_hilites[HL_ATTCLR_BLINK] &= ~hlstr->mask;
+        cond_hilites[HL_ATTCLR_ULINE] &= ~hlstr->mask;
+        cond_hilites[HL_ATTCLR_INVERSE] &= ~hlstr->mask;
+        return TRUE;
+    } else {
+        int fld = hlstr->fld;
+        struct hilite_s *hl, *hlprev = (struct hilite_s *) 0;
+
+        for (hl = blstats[0][fld].thresholds; hl; hl = hl->next) {
+            if (hlstr->hl == hl) {
+                if (hlprev) {
+                    hlprev->next = hl->next;
+                } else {
+                    blstats[0][fld].thresholds = hl->next;
+                    blstats[1][fld].thresholds = blstats[0][fld].thresholds;
+                }
+                if (blstats[0][fld].hilite_rule == hl) {
+                    blstats[0][fld].hilite_rule
+                        = blstats[1][fld].hilite_rule = (struct hilite_s *) 0;
+                    blstats[0][fld].time = blstats[1][fld].time = 0L;
+                }
+                free((genericptr_t) hl);
+                return TRUE;
+            }
+            hlprev = hl;
+        }
+    }
+    return FALSE;
 }
 
 boolean
-status_hilite_menu()
+status_hilite_menu_fld(fld)
+int fld;
 {
-    int i, j, k, pick_cnt, pick_idx, opt_idx;
-    menu_item *statfield_picks = (menu_item *) 0;
-    const char *fieldname;
-    int field_picks[MAXBLSTATS], res;
-    struct hilite_s hltemp[MAXBLSTATS];
-    char buf[BUFSZ], thresholdbuf[BUFSZ], below[BUFSZ], above[BUFSZ];
     winid tmpwin;
+    int i, res;
+    menu_item *picks = (menu_item *) 0;
     anything any;
+    int count = status_hilite_linestr_countfield(fld);
+    struct _status_hilite_line_str *hlstr;
+    char buf[BUFSZ];
+    boolean acted = FALSE;
+
+    if (!count) {
+        if (status_hilite_menu_add(fld)) {
+            status_hilite_linestr_done();
+            status_hilite_linestr_gather();
+            count = status_hilite_linestr_countfield(fld);
+        } else
+            return FALSE;
+    }
 
     tmpwin = create_nhwindow(NHW_MENU);
     start_menu(tmpwin);
-    for (i = 0; i < MAXBLSTATS; i++) {
-        (void) memset(&hltemp[i], 0, sizeof(struct hilite_s));
-        fieldname = fieldids[i].fieldname;
-        any.a_int = i + 1;
-        add_menu(tmpwin, NO_GLYPH, &any, 0, 0, ATR_NONE, fieldname,
-                 MENU_UNSELECTED);
-        field_picks[i] = 0;
-    }
-    end_menu(tmpwin, "Change hilite on which status field(s):");
-    if ((pick_cnt = select_menu(tmpwin, PICK_ANY, &statfield_picks)) > 0) {
-        for (pick_idx = 0; pick_idx < pick_cnt; ++pick_idx) {
-            opt_idx = statfield_picks[pick_idx].item.a_int - 1;
-            field_picks[opt_idx] = 1;
-        }
-        free((genericptr_t) statfield_picks);
-        statfield_picks = (menu_item *) 0;
-    }
-    destroy_nhwindow(tmpwin);
-    if (pick_cnt < 0)
-        return FALSE;
 
-    for (i = 0; i < MAXBLSTATS; i++) {
-        if (field_picks[i]) {
-            menu_item *pick = (menu_item *) 0;
-            Sprintf(buf, "Threshold behavior options for %s:",
-                    fieldids[i].fieldname);
-            tmpwin = create_nhwindow(NHW_MENU);
-            start_menu(tmpwin);
-            if (i == BL_CONDITION) {
-                any = zeroany;
-                any.a_int = BL_TH_CONDITION + 1;
-                add_menu(tmpwin, NO_GLYPH, &any, 'c', 0, ATR_NONE,
-                         "Condition bitmask threshold.", MENU_UNSELECTED);
-            }
-            any = zeroany;
-            any.a_int = BL_TH_NONE + 1;
-            add_menu(tmpwin, NO_GLYPH, &any, 'n', 0, ATR_NONE, "None",
-                     MENU_UNSELECTED);
-            if (i != BL_CONDITION) {
-                if (blstats[0][i].idxmax > 0) {
-                    any = zeroany;
-                    any.a_int = BL_TH_VAL_PERCENTAGE + 1;
-                    add_menu(tmpwin, NO_GLYPH, &any, 'p', 0, ATR_NONE,
-                             "Percentage threshold.", MENU_UNSELECTED);
-                }
-                any = zeroany;
-                any.a_int = BL_TH_UPDOWN + 1;
-                add_menu(tmpwin, NO_GLYPH, &any, 'u', 0, ATR_NONE,
-                         "UpDown threshold.", MENU_UNSELECTED);
+    if (count) {
+        hlstr = status_hilite_str;
+        while (hlstr) {
+            if (hlstr->fld == fld) {
                 any = zeroany;
-                any.a_int = BL_TH_VAL_ABSOLUTE + 1;
-                add_menu(tmpwin, NO_GLYPH, &any, 'v', 0, ATR_NONE,
-                         "Value threshold.", MENU_UNSELECTED);
+                any.a_int = hlstr->id;
+                add_menu(tmpwin, NO_GLYPH, &any, 0, 0, ATR_NONE,
+                         hlstr->str, MENU_UNSELECTED);
             }
-            end_menu(tmpwin, buf);
-            if ((res = select_menu(tmpwin, PICK_ONE, &pick)) > 0) {
-                hltemp[i].behavior = pick->item.a_int - 1;
-                free((genericptr_t) pick);
-            }
-            destroy_nhwindow(tmpwin);
-            if (res < 0)
-                return FALSE;
+            hlstr = hlstr->next;
+        }
+    } else {
+        any = zeroany;
+        Sprintf(buf, "No current hilites for %s", initblstats[fld].fldname);
+        add_menu(tmpwin, NO_GLYPH, &any, 0, 0, ATR_NONE, buf, MENU_UNSELECTED);
+    }
 
-            if (hltemp[i].behavior == BL_TH_UPDOWN) {
-                Sprintf(below, "%s decreases", fieldids[i].fieldname);
-                Sprintf(above, "%s increases", fieldids[i].fieldname);
-            } else if (hltemp[i].behavior) {
-                /* Have them enter the threshold*/
-                Sprintf(
-                    buf, "Set %s threshold to what%s?", fieldids[i].fieldname,
-                    (hltemp[i].behavior == BL_TH_VAL_PERCENTAGE)
-                        ? " percentage"
-                        : (hltemp[i].behavior == BL_TH_CONDITION) ? " mask"
-                                                                  : "");
-                getlin(buf, thresholdbuf);
-                if (thresholdbuf[0] == '\033')
-                    return FALSE;
-                (void) s_to_anything(&hltemp[i].threshold, thresholdbuf,
-                                     blstats[0][i].anytype);
-                if (!hltemp[i].threshold.a_void)
-                    return FALSE;
+    /* separator line */
+    any = zeroany;
+    add_menu(tmpwin, NO_GLYPH, &any, 0, 0, ATR_NONE, "", MENU_UNSELECTED);
+
+    if (count) {
+        any = zeroany;
+        any.a_int = -1;
+        add_menu(tmpwin, NO_GLYPH, &any, 'X', 0, ATR_NONE,
+                 "Remove selected hilites", MENU_UNSELECTED);
+    }
+
+#ifndef SCORE_ON_BOTL
+    if (fld == BL_SCORE) {
+        /* suppress 'Z - Add a new hilite' for 'score' when SCORE_ON_BOTL
+           is disabled; we wouldn't be called for 'score' unless it has
+           hilite rules from the config file, so count must be positive
+           (hence there's no risk that we're putting up an empty menu) */
+        ;
+    } else
+#endif
+    {
+        any = zeroany;
+        any.a_int = -2;
+        add_menu(tmpwin, NO_GLYPH, &any, 'Z', 0, ATR_NONE,
+                 "Add a new hilite", MENU_UNSELECTED);
+    }
+
+    Sprintf(buf, "Current %s hilites:", initblstats[fld].fldname);
+    end_menu(tmpwin, buf);
+
+    if ((res = select_menu(tmpwin, PICK_ANY, &picks)) > 0) {
+        int mode = 0;
 
-                Sprintf(below, "%s falls below %s%s", fieldids[i].fieldname,
-                        thresholdbuf,
-                        (hltemp[i].behavior == BL_TH_VAL_PERCENTAGE) ? "%"
-                                                                     : "");
-                Sprintf(above, "%s rises above %s%s", fieldids[i].fieldname,
-                        thresholdbuf,
-                        (hltemp[i].behavior == BL_TH_VAL_PERCENTAGE) ? "%"
-                                                                     : "");
+        for (i = 0; i < res; i++) {
+            int idx = picks[i].item.a_int;
+
+            if (idx == -1) {
+                /* delete selected hilites */
+                if (mode)
+                    goto shlmenu_free;
+                mode = -1;
+                break;
+            } else if (idx == -2) {
+                /* create a new hilite */
+                if (mode)
+                    goto shlmenu_free;
+                mode = -2;
+                break;
             }
-            for (j = 0; j < 2 && (hltemp[i].behavior != BL_TH_NONE); ++j) {
-                char prompt[QBUFSZ];
-                /* j == 0 below, j == 1 above */
-                menu_item *pick2 = (menu_item *) 0;
-
-                Sprintf(prompt, "Display how when %s?", j ? above : below);
-                tmpwin = create_nhwindow(NHW_MENU);
-                start_menu(tmpwin);
-                for (k = -3; k < CLR_MAX; ++k) {
-                    /* if (k == -1) continue; */
-                    any = zeroany;
-                    any.a_int = (k >= 0) ? k + 1 : k;
-                    if (k > 0)
-                        add_menu(tmpwin, NO_GLYPH, &any, 0, 0, ATR_NONE,
-                                 c_obj_colors[k], MENU_UNSELECTED);
-                    else if (k == -1)
-                        add_menu(tmpwin, NO_GLYPH, &any, 0, 0, ATR_NONE,
-                                 "normal", MENU_UNSELECTED);
-                    else if (k == -2)
-                        add_menu(tmpwin, NO_GLYPH, &any, 0, 0, ATR_NONE,
-                                 "inverse", MENU_UNSELECTED);
-                    else if (k == -3)
-                        add_menu(tmpwin, NO_GLYPH, &any, 0, 0, ATR_NONE,
-                                 "bold", MENU_UNSELECTED);
-                }
-                end_menu(tmpwin, prompt);
-                if ((res = select_menu(tmpwin, PICK_ONE, &pick2)) > 0) {
-                    hltemp[i].coloridx[j] = (pick2->item.a_char > 0)
-                                                ? pick2->item.a_int - 1
-                                                : pick2->item.a_int;
-                    free((genericptr_t) pick2);
-                }
-                destroy_nhwindow(tmpwin);
-                if (res < 0)
-                    return FALSE;
+        }
+
+        if (mode == -1) {
+            /* delete selected hilites */
+            for (i = 0; i < res; i++) {
+                int idx = picks[i].item.a_int;
+
+                if (idx > 0)
+                    (void) status_hilite_remove(idx);
             }
+            reset_status_hilites();
+            acted = TRUE;
+        } else if (mode == -2) {
+            /* create a new hilite */
+            if (status_hilite_menu_add(fld))
+                acted = TRUE;
         }
+
+        free((genericptr_t) picks);
     }
-    buf[0] = '\0';
+
+shlmenu_free:
+
+    picks = (menu_item *) 0;
+    destroy_nhwindow(tmpwin);
+    return acted;
+}
+
+void
+status_hilites_viewall()
+{
+    winid datawin;
+    struct _status_hilite_line_str *hlstr = status_hilite_str;
+    char buf[BUFSZ];
+
+    datawin = create_nhwindow(NHW_TEXT);
+
+    while (hlstr) {
+        Sprintf(buf, "OPTIONS=hilite_status: %.*s",
+                (int) (BUFSZ - sizeof "OPTIONS=hilite_status: " - 1),
+                hlstr->str);
+        putstr(datawin, 0, buf);
+        hlstr = hlstr->next;
+    }
+
+    display_nhwindow(datawin, FALSE);
+    destroy_nhwindow(datawin);
+}
+
+boolean
+status_hilite_menu()
+{
+    winid tmpwin;
+    int i, res;
+    menu_item *picks = (menu_item *) 0;
+    anything any;
+    boolean redo;
+    int countall;
+
+shlmenu_redo:
+    redo = FALSE;
+
+    tmpwin = create_nhwindow(NHW_MENU);
+    start_menu(tmpwin);
+
+    status_hilite_linestr_gather();
+    countall = status_hilite_linestr_countfield(BL_FLUSH);
+    if (countall) {
+        any = zeroany;
+        any.a_int = -1;
+        add_menu(tmpwin, NO_GLYPH, &any, 0, 0, ATR_NONE,
+                 "View all hilites in config format", MENU_UNSELECTED);
+
+        any = zeroany;
+        add_menu(tmpwin, NO_GLYPH, &any, 0, 0, ATR_NONE, "", MENU_UNSELECTED);
+    }
+
     for (i = 0; i < MAXBLSTATS; i++) {
-        if (field_picks[i]) {
-            Sprintf(eos(buf), "%s/%s%s/", fieldids[i].fieldname,
-                    (hltemp[i].behavior == BL_TH_UPDOWN)
-                        ? "updown"
-                        : anything_to_s(thresholdbuf, &hltemp[i].threshold,
-                                        blstats[0][i].anytype),
-                    (hltemp[i].behavior == BL_TH_VAL_PERCENTAGE) ? "%" : "");
-            /* borrow thresholdbuf for use with these last two */
-            Sprintf(eos(buf), "%s/",
-                    clridx_to_s(thresholdbuf, hltemp[i].coloridx[0]));
-            Sprintf(eos(buf), "%s ",
-                    clridx_to_s(thresholdbuf, hltemp[i].coloridx[1]));
-        }
+        int count = status_hilite_linestr_countfield(i);
+        char buf[BUFSZ];
+
+#ifndef SCORE_ON_BOTL
+        /* config file might contain rules for highlighting 'score'
+           even when SCORE_ON_BOTL is disabled; if so, 'O' command
+           menus will show them and allow deletions but not additions,
+           otherwise, it won't show 'score' at all */
+        if (initblstats[i].fld == BL_SCORE && !count)
+            continue;
+#endif
+        any = zeroany;
+        any.a_int = i + 1;
+        Sprintf(buf, "%-18s", initblstats[i].fldname);
+        if (count)
+            Sprintf(eos(buf), " (%d defined)", count);
+        add_menu(tmpwin, NO_GLYPH, &any, 0, 0, ATR_NONE,
+                 buf, MENU_UNSELECTED);
+    }
+
+    end_menu(tmpwin, "Status hilites:");
+    if ((res = select_menu(tmpwin, PICK_ONE, &picks)) > 0) {
+        i = picks->item.a_int - 1;
+        if (i < 0)
+            status_hilites_viewall();
+        else
+            (void) status_hilite_menu_fld(i);
+        free((genericptr_t) picks), picks = (menu_item *) 0;
+        redo = TRUE;
     }
-    return set_status_hilites(buf, FALSE);
+
+    destroy_nhwindow(tmpwin);
+    countall = status_hilite_linestr_countfield(BL_FLUSH);
+    status_hilite_linestr_done();
+
+    if (redo)
+        goto shlmenu_redo;
+
+    /* hilite_delta=='statushilites' does double duty:  it is the
+       number of turns for temporary highlights to remain visible
+       and also when non-zero it is the flag to enable highlighting */
+    if (countall > 0 && !iflags.hilite_delta)
+        pline(
+ "To have highlights become active, set 'statushilites' option to non-zero.");
+
+    return TRUE;
 }
-#endif /*STATUS_HILITES*/
-#endif /*STATUS_VIA_WINDOWPORT*/
+
+#endif /* STATUS_HILITES */
 
 /*botl.c*/