]> granicus.if.org Git - nethack/commitdiff
revamped curses status display
authorPatR <rankin@nethack.org>
Sun, 24 Mar 2019 00:38:23 +0000 (17:38 -0700)
committerPatR <rankin@nethack.org>
Sun, 24 Mar 2019 00:38:23 +0000 (17:38 -0700)
I've overhauled the status display for curses.  Horizontal layout
supports both 2 lines and 3 lines which can be changed dynamically
via using 'O' to set 'statuslines'.  Fields are spread out a little
more than they used to be, making it more readable--at least to me--
but the extra spaces get squeezed out when lines become too long.
If 'showexp' is on and either conditions or hunger+encumbrance go
off the right edge, experience points are suppressed (but the option
is left on, so they'll come back once there is room).

For traditional 2-line hozizontal status, if hunger+encumbrance+
conditions go off the right edge even after experience points are
knocked out, there will be a '+' in the rightmost column if there
are any conditions that are all the way off.  At present it doesn't
use the tty method of switching to abbreviated condition names to
reduce their legnth.  I'll probably tackle that eventually if no one
beats me to it.

For 3-line horizonal status, there was an older implementation (but
disabled via #if 0) with gold and score moving to the third line.
(I'm not sure how status conditions were handled.)  This one ignored
that and modified 2-line from scratch, moving alignment from line one
to line 2 and level description, time, and conditions from line 2 to
line 3.  It looks like this (view with a fixed-width font...).

Wizard the Hatamoto            St:16 Dx:15 Co:18 In:8 Wi:11 Ch:7    S:25
Lawful  $:21  HP:25(25)  Pw:6(6)  AC:4  Xp:2/21  Hungry Burdened
Dlvl:1  T:36                                     Blind Lev

Score is actually right aligned with the edge but I've deleted several
spaces to keep the line shorter here.  The status conditions line up
with the hunger slot as that shifts due to changes in gold/HP/power/AC/
experience, and conditions prefer that column even when hunger and/or
encumbrance are blank.  Howver, if the number of conditions increase to
the point where they would go off the edge, the whole list shifts left
instead of trying to stay lined up with hunger.  (It's just coincidence
that the lefthand parts of lines 2 and 3 seem to line up in this sample.
In general, they don't.)

The vertical layout has reordered most of the fields and now has a few
blank lines to separate those fields into some groups for readability.
Lines have the form of
Field-name  : Value
and when highlights apply, now they only affect the value portion.
Single digit characteristics are padded with a leading space so that
all six of them line up (for "18/xx", "/xx" protrudes to the right).
HP and Pw are aligned with each other.  Hunger and encumbrance share a
line.  When there are more than three conditions, they're shown three
per line instead of wrapping across lines.  And if too many lines are
present, it will squeeze out enough blank ones to fit.

To see the vertical status, you need a display size of at least 106
columns with 'windowborders' explicitly off, or 110 with them on; also
set option 'align_status' to 'right' or 'left'.  (With borders on,
including the default 'auto' setting, the vertical status appears at
width of 108 columns, but does so by hiding 2 columns of the map; using
110 columns avoids that.)  Resizing from outside the game or changing
align_status via 'O' both cause dynamic reconfiguration of the layout;
there's no need to save, make config changes, then restore.

doc/fixes36.2
include/botl.h
src/botl.c
win/curses/cursstat.c

index fedb1a3aa6d78fec97e91f6c2b412c7d3a53eec6..9c996dbdfed4f284411a4308700f1fa67088632f 100644 (file)
@@ -1,4 +1,4 @@
-$NHDT-Branch: NetHack-3.6.2-beta01 $:$NHDT-Revision: 1.279 $ $NHDT-Date: 1553296396 2019/03/22 23:13:16 $
+$NHDT-Branch: NetHack-3.6.2-beta01 $:$NHDT-Revision: 1.280 $ $NHDT-Date: 1553387147 2019/03/24 00:25:47 $
 
 This fixes36.2 file is here to capture information about updates in the 3.6.x
 lineage following the release of 3.6.1 in April 2018. Please note, however,
@@ -632,6 +632,9 @@ in wizard mode, ^T can be preceded by 'm' prefix in order to test teleporting
 include isaac64 for pseudo random number generation
 core prng and display prng use different contexts
 when healing magic other than unicorn horn cures blindness, cure deafness too
+curses: status display substantially revamped for both horizontal (via
+       'align_status:bottom' or 'top') and vertical (via 'align_status:left'
+       or 'right'); 3-line horizontal layout (via 'statuslines:3') added
 
 
 NetHack Community Patches (or Variation) Included
index 6c3ddb8c608f7523a96a5476958f861394896e9f..91de104f0bd39c6a40f4a1788d774ecbb99de60d 100644 (file)
@@ -1,4 +1,4 @@
-/* NetHack 3.6  botl.h  $NHDT-Date: 1526907469 2018/05/21 12:57:49 $  $NHDT-Branch: NetHack-3.6.2 $:$NHDT-Revision: 1.19 $ */
+/* NetHack 3.6  botl.h  $NHDT-Date: 1553387147 2019/03/24 00:25:47 $  $NHDT-Branch: NetHack-3.6.2-beta01 $:$NHDT-Revision: 1.23 $ */
 /* Copyright (c) Michael Allison, 2003                            */
 /* NetHack may be freely redistributed.  See license for details. */
 
@@ -27,6 +27,13 @@ Astral Plane \GXXXXNNNN:123456 HP:1234(1234) Pw:1234(1234) AC:-127
 #define MAXCO (COLNO + 40)
 #endif
 
+#ifdef STATUS_HILITES
+struct condmap {
+    const char *id;
+    unsigned long bitmask;
+};
+#endif
+
 enum statusfields {
     BL_CHARACTERISTICS = -3, /* alias for BL_STR..BL_CH */
     BL_RESET = -2,           /* Force everything to redisplay */
@@ -36,7 +43,7 @@ enum statusfields {
     BL_ALIGN, BL_SCORE, BL_CAP, BL_GOLD, BL_ENE, BL_ENEMAX, /* 7..12 */
     BL_XP, BL_AC, BL_HD, BL_TIME, BL_HUNGER, BL_HP, /* 13..18 */
     BL_HPMAX, BL_LEVELDESC, BL_EXP, BL_CONDITION, /* 19..22 */
-    MAXBLSTATS
+    MAXBLSTATS /* [23] */
 };
 
 enum relationships { NO_LTEQGT = -1,
index 4a329eec40f5b1422b3d29e01bbaecfe0664a1bc..92a2335e7dcac1c5dc5bba0968e33aa07e5afd15 100644 (file)
@@ -1,4 +1,4 @@
-/* NetHack 3.6 botl.c  $NHDT-Date: 1553217909 2019/03/22 01:25:09 $  $NHDT-Branch: NetHack-3.6.2-beta01 $:$NHDT-Revision: 1.136 $ */
+/* NetHack 3.6 botl.c  $NHDT-Date: 1553387148 2019/03/24 00:25:48 $  $NHDT-Branch: NetHack-3.6.2-beta01 $:$NHDT-Revision: 1.137 $ */
 /* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */
 /*-Copyright (c) Michael Allison, 2006. */
 /* NetHack may be freely redistributed.  See license for details. */
@@ -411,11 +411,6 @@ struct hilite_s {
     int coloridx;
     struct hilite_s *next;
 };
-
-struct condmap {
-    const char *id;
-    unsigned long bitmask;
-};
 #endif /* STATUS_HILITES */
 
 struct istat_s {
@@ -555,7 +550,7 @@ bot_via_windowport()
     Strcpy(nb = buf, plname);
     nb[0] = highc(nb[0]);
     nb[10] = '\0';
-    Sprintf(nb = eos(nb), " the ");
+    Strcpy(nb = eos(nb), " the ");
     if (Upolyd) {
         for (i = 0, nb = strcpy(eos(nb), mons[u.umonnum].mname); nb[i]; i++)
             if (i == 0 || nb[i - 1] == ' ')
@@ -834,7 +829,8 @@ boolean *valsetlist;
     if (context.botlx && (windowprocs.wincap2 & WC2_RESET_STATUS) != 0L)
         status_update(BL_RESET, (genericptr_t) 0, 0, 0,
                       NO_COLOR, &cond_hilites[0]);
-    else if ((windowprocs.wincap2 & WC2_FLUSH_STATUS) != 0L)
+    else if ((updated || context.botlx) &&
+             (windowprocs.wincap2 & WC2_FLUSH_STATUS) != 0L)
         status_update(BL_FLUSH, (genericptr_t) 0, 0, 0,
                       NO_COLOR, &cond_hilites[0]);
 
index 12153aa34a3ccd1c37c4fcbccbea19f13035520b..b12472055002dd8df887218e44f890db9c1b5c82 100644 (file)
@@ -28,16 +28,25 @@ static char *status_vals_long[MAXBLSTATS];
 static long curses_condition_bits;
 static int curses_status_colors[MAXBLSTATS];
 static int hpbar_percent, hpbar_color;
+static int vert_status_dirty;
 
 #ifdef TEXTCOLOR
 static int FDECL(condcolor, (long, unsigned long *));
 #endif
 static int FDECL(condattr, (long, unsigned long *));
+static int FDECL(nhattr2curses, (int));
 #endif /* STATUS_HILITES */
 static void FDECL(draw_status, (unsigned long *));
-static void FDECL(draw_classic, (BOOLEAN_P, unsigned long *));
 static void FDECL(draw_vertical, (BOOLEAN_P, unsigned long *));
 static void FDECL(draw_horizontal, (BOOLEAN_P, unsigned long *));
+static void curs_HPbar(char *, int);
+static void curs_stat_conds(int, int *, int *, unsigned long *,
+                            char *, boolean *);
+static void curs_vert_status_vals(int);
+
+/* width of a single line in vertical status orientation (one field per line;
+   everything but title fits within 30 even with prefix and longest value) */
+#define STATVAL_WIDTH 60 /* overkill; was MAXCO (200), massive overkill */
 
 void
 curses_status_init()
@@ -47,11 +56,12 @@ curses_status_init()
 
     for (i = 0; i < MAXBLSTATS; ++i) {
         curses_status_colors[i] = NO_COLOR; /* no color and no attributes */
-        status_vals_long[i] = (char *) alloc(MAXCO);
+        status_vals_long[i] = (char *) alloc(STATVAL_WIDTH);
         *status_vals_long[i] = '\0';
     }
     curses_condition_bits = 0L;
     hpbar_percent = 0, hpbar_color = NO_COLOR;
+    vert_status_dirty = 1;
 #endif /* STATUS_HILITES */
 
     /* let genl_status_init do most of the initialization */
@@ -65,8 +75,8 @@ curses_status_finish()
     int i;
 
     for (i = 0; i < MAXBLSTATS; ++i) {
-         if (status_vals_long[i])
-              free(status_vals_long[i]), status_vals_long[i] = (char *) 0;
+        if (status_vals_long[i])
+            free(status_vals_long[i]), status_vals_long[i] = (char *) 0;
     }
     genl_status_finish();
 #endif /* STATUS_HILITES */
@@ -126,68 +136,8 @@ curses_status_finish()
  *         See doc/window.doc for more details.
  */
 
-/* new approach through status_update() only */
-#define Begin_Attr(m) \
-            if (m) {                                                          \
-                if ((m) & HL_BOLD)                                            \
-                    wattron(win, A_BOLD);                                     \
-                if ((m) & HL_INVERSE)                                         \
-                    wattron(win,A_REVERSE);                                   \
-                if ((m) & HL_ULINE)                                           \
-                    wattron(win,A_UNDERLINE);                                 \
-                if ((m) & HL_BLINK)                                           \
-                    wattron(win,A_BLINK);                                     \
-                if ((m) & HL_DIM)                                             \
-                    wattron(win,A_DIM);                                       \
-            }
-
-#define End_Attr(m) \
-            if (m) {                                                          \
-                if ((m) & HL_DIM)                                             \
-                    wattroff(win,A_DIM);                                      \
-                if ((m) & HL_BLINK)                                           \
-                    wattroff(win,A_BLINK);                                    \
-                if ((m) & HL_ULINE)                                           \
-                    wattroff(win,A_UNDERLINE);                                \
-                if ((m) & HL_INVERSE)                                         \
-                    wattroff(win,A_REVERSE);                                  \
-                if ((m) & HL_BOLD)                                            \
-                    wattroff(win,A_BOLD);                                     \
-            }
-
 #ifdef STATUS_HILITES
-
-#ifdef TEXTCOLOR
-#define MaybeDisplayCond(bm,txt) \
-            if (curses_condition_bits & (bm)) {                               \
-                putstr(STATUS_WIN, 0, " ");                                   \
-                if (iflags.hilite_delta) {                                    \
-                    attrmask = condattr((bm), colormasks);                    \
-                    Begin_Attr(attrmask);                                 \
-                    if ((coloridx = condcolor((bm), colormasks)) != NO_COLOR) \
-                        curses_toggle_color_attr(win, coloridx, NONE, ON);    \
-                }                                                             \
-                putstr(STATUS_WIN, 0, (txt));                                 \
-                if (iflags.hilite_delta) {                                    \
-                    if (coloridx != NO_COLOR)                                 \
-                        curses_toggle_color_attr(win, coloridx, NONE, OFF);   \
-                    End_Attr(attrmask);                                       \
-                }                                                             \
-            }
-#else
-#define MaybeDisplayCond(bm,txt) \
-            if (curses_condition_bits & (bm)) {                               \
-                putstr(STATUS_WIN, 0, " ");                                   \
-                if (iflags.hilite_delta) {                                    \
-                    attrmask = condattr((bm), colormasks);                    \
-                    Begin_Attr(attrmask);                                 \
-                }                                                             \
-                putstr(STATUS_WIN, 0, (txt));                                 \
-                if (iflags.hilite_delta) {                                    \
-                    End_Attr(attrmask);                                   \
-                }                                                             \
-            }
-#endif
+static int changed_fields = 0;
 
 void
 curses_status_update(fldidx, ptr, chg, percent, color_and_attr, colormasks)
@@ -198,28 +148,19 @@ unsigned long *colormasks;
 {
     long *condptr = (long *) ptr;
     char *text = (char *) ptr;
-    char *goldnum = NULL;
-    boolean use_name = TRUE;
 
     if (fldidx != BL_FLUSH) {
+        if (fldidx < 0 || fldidx >= MAXBLSTATS) {
+            context.botlx = context.botl = FALSE; /* avoid another bot() */
+            panic("curses_status_update(%d)", fldidx);
+        }
+        changed_fields |= (1 << fldidx);
         *status_vals[fldidx] = '\0';
         if (!status_activefields[fldidx])
             return;
-        if (fldidx == BL_GOLD)
-           goldnum = index(text,':') + 1;
-        switch (fldidx) {
-        case BL_CONDITION:
+        if (fldidx == BL_CONDITION) {
             curses_condition_bits = *condptr;
-            break;
-        case BL_TITLE:
-        case BL_HPMAX:
-        case BL_ENEMAX:
-        case BL_HUNGER:
-        case BL_CAP:
-        case BL_EXP:
-            use_name = FALSE;
-            /* FALLTHROUGH */
-        default:
+        } else {
 #ifndef TEXTCOLOR
             color_and_attr = (color_and_attr & ~0x00FF) | NO_COLOR;
 #endif
@@ -227,28 +168,47 @@ unsigned long *colormasks;
              * status_vals[] are used for horizontal orientation
              *  (wide lines of multiple short values).
              * status_vals_long[] are used for vertical orientation
-             *  (narrow-ish lines of one long value each).
+             *  (narrow-ish lines of one long value each [mostly]
+             *  and get constructed at need from status_vals[] rather
+             *  than the original values passed to status_update()).
              */
-            Sprintf(status_vals[fldidx],
-                    (fldidx == BL_TITLE && iflags.wc2_hitpointbar) ? "%-30s" :
-                    status_fieldfmt[fldidx] ? status_fieldfmt[fldidx] : "%s",
-                    text);
-            if (use_name) {
-                Sprintf(status_vals_long[fldidx], "%-16s: %s",
-                        status_fieldnm[fldidx], goldnum ? goldnum : text);
-                *(status_vals_long[fldidx]) = highc(*status_vals_long[fldidx]);
-            } else
-                Strcpy(status_vals_long[fldidx], status_vals[fldidx]);
+            if (fldidx == BL_GOLD) {
+                /* decode once instead of every time it's displayed */
+                status_vals[BL_GOLD][0] = ' ';
+                text = decode_mixed(&status_vals[BL_GOLD][1], text);
+            } else if ((fldidx == BL_HUNGER || fldidx == BL_CAP)
+                       && (!*text || !strcmp(text, " "))) {
+                /* fieldfmt[] is " %s"; avoid lone space when empty */
+                *status_vals[fldidx] = '\0';
+            } else {
+                Sprintf(status_vals[fldidx],
+                        (fldidx == BL_TITLE && iflags.wc2_hitpointbar)
+                        ? "%-30s" : status_fieldfmt[fldidx]
+                                    ? status_fieldfmt[fldidx] : "%s",
+                        text);
+                /* strip trailing spaces; core ought to do this for us */
+                if (fldidx == BL_HUNGER || fldidx == BL_LEVELDESC)
+                    (void) trimspaces(status_vals[fldidx]);
+            }
+
+            /* status_vals_long[] used to be set up here even when not
+               in use; it has been moved to curs_vert_status_vals() */
+            vert_status_dirty = 1;
 
             curses_status_colors[fldidx] = color_and_attr;
             if (iflags.wc2_hitpointbar && fldidx == BL_HP) {
                 hpbar_percent = percent;
                 hpbar_color = color_and_attr;
             }
-            break;
         }
     } else { /* BL_FLUSH */
+        if (!changed_fields && !context.botlx) {
+            ; /* TODO:  this isn't impossible but we want to track
+               * down the circumstances where it happens in order to
+               * minimize occurrences */
+        }
         draw_status(colormasks);
+        changed_fields = 0;
     }
 }
 
@@ -279,12 +239,9 @@ unsigned long *colormasks;
     }
 
     werase(win);
-    if (horiz) {
-        if (iflags.wc2_statuslines < 3)
-            draw_classic(border, colormasks);
-        else
-            draw_horizontal(border, colormasks);
-    } else
+    if (horiz)
+        draw_horizontal(border, colormasks);
+    else
         draw_vertical(border, colormasks);
 
     if (border)
@@ -292,129 +249,316 @@ unsigned long *colormasks;
     wnoutrefresh(win);
 }
 
-/* The 'classic' NetHack 3.x status layout */
+/* horizontal layout on 2 or 3 lines */
 void
-draw_classic(border, colormasks)
+draw_horizontal(border, colormasks)
 boolean border;
 unsigned long *colormasks;
 {
-    static const enum statusfields fieldorder[2][15] = {
-        { BL_TITLE, BL_STR, BL_DX, BL_CO, BL_IN, BL_WI, BL_CH, BL_ALIGN,
-          BL_SCORE, BL_FLUSH, BL_FLUSH, BL_FLUSH, BL_FLUSH, BL_FLUSH,
+#define blPAD BL_FLUSH
+    /* almost all fields already come with a leading space;
+       "xspace" indicates places where we'll generate an extra one */
+    static const enum statusfields
+    twolineorder[3][15] = {
+        { BL_TITLE,
+          /*xspace*/ BL_STR, BL_DX, BL_CO, BL_IN, BL_WI, BL_CH,
+          /*xspace*/ BL_ALIGN,
+          /*xspace*/ BL_SCORE,
+          BL_FLUSH, blPAD, blPAD, blPAD, blPAD, blPAD },
+        { BL_LEVELDESC,
+          /*xspace*/ BL_GOLD,
+          /*xspace*/ BL_HP, BL_HPMAX,
+          /*xspace*/ BL_ENE, BL_ENEMAX,
+          /*xspace*/ BL_AC,
+          /*xspace*/ BL_XP, BL_EXP, BL_HD,
+          /*xspace*/ BL_TIME,
+          /*xspace*/ BL_HUNGER, BL_CAP, BL_CONDITION,
           BL_FLUSH },
-        { BL_LEVELDESC, BL_GOLD, BL_HP, BL_HPMAX, BL_ENE, BL_ENEMAX,
-          BL_AC, BL_XP, BL_EXP, BL_HD, BL_TIME, BL_HUNGER,
-          BL_CAP, BL_CONDITION, BL_FLUSH }
+        { BL_FLUSH, blPAD, blPAD, blPAD, blPAD, blPAD, blPAD, blPAD,
+          blPAD, blPAD, blPAD, blPAD, blPAD, blPAD, blPAD }
+    },
+    threelineorder[3][15] = { /* moves align to line 2, leveldesc+ to 3 */
+        { BL_TITLE,
+          /*xspace*/ BL_STR, BL_DX, BL_CO, BL_IN, BL_WI, BL_CH,
+          /*xspace*/ BL_SCORE,
+          BL_FLUSH, blPAD, blPAD, blPAD, blPAD, blPAD, blPAD },
+        { BL_ALIGN,
+          /*xspace*/ BL_GOLD,
+          /*xspace*/ BL_HP, BL_HPMAX,
+          /*xspace*/ BL_ENE, BL_ENEMAX,
+          /*xspace*/ BL_AC,
+          /*xspace*/ BL_XP, BL_EXP, BL_HD,
+          /*xspace*/ BL_HUNGER, BL_CAP,
+          BL_FLUSH, blPAD, blPAD },
+        { BL_LEVELDESC,
+          /*xspace*/ BL_TIME,
+          /*xspecial*/ BL_CONDITION,
+          BL_FLUSH, blPAD, blPAD, blPAD, blPAD, blPAD, blPAD, blPAD,
+          blPAD, blPAD, blPAD, blPAD }
     };
-    int i, coloridx = NO_COLOR, attrmask = 0;
-    char *text;
-    int attridx = 0;
-
+    const enum statusfields (*fieldorder)[3][15];
+    xchar spacing[MAXBLSTATS], valline[MAXBLSTATS];
+    enum statusfields fld, prev_fld;
+    char *text, *colon, *p, cbuf[BUFSZ];
+#ifdef SCORE_ON_BOTL
+    char sbuf[STATVAL_WIDTH];
+#endif
+    int i, j, number_of_lines,
+        cap_and_hunger, exp_points, sho_score,
+        height, width, w, xtra, clen, x, y, t,
+        condstart = 0, conddummy = 0;
+    int coloridx = NO_COLOR, attrmask = 0;
+    boolean asis = FALSE;
     WINDOW *win = curses_get_nhwin(STATUS_WIN);
+
+    /* note: getmaxyx() is a macro which assigns values to height and width */
+    getmaxyx(win, height, width);
     if (border)
-        wmove(win, 1, 1);
-    else
-        wmove(win, 0, 0);
-    for (i = 0; fieldorder[0][i] != BL_FLUSH; ++i) {
-        int fldidx1 = fieldorder[0][i];
-
-        if (status_activefields[fldidx1]) {
-            if (fldidx1 != BL_TITLE || !iflags.wc2_hitpointbar) {
-                text = status_vals[fldidx1];
-                coloridx = curses_status_colors[fldidx1]; /* plus attributes */
-                if (iflags.hilite_delta && coloridx != NO_COLOR) {
-                    if (*text == ' ') {
-                        putstr(STATUS_WIN, 0, " ");
-                        text++;
-                    }
-                    /* multiple attributes can be in effect concurrently */
-                    attridx = (coloridx >> 8) & 0x00FF;
-                    Begin_Attr(attridx);
-#ifdef TEXTCOLOR
-                    coloridx &= 0x00FF;
-                    if (coloridx != NO_COLOR && coloridx != CLR_MAX)
-                        curses_toggle_color_attr(win, coloridx, NONE, ON);
-#endif
+        height -= 2, width -= 2;
+
+    /*
+     * Potential revisions:
+     *  If line with gold is too long, strip "$:" prefix like is done
+     *  for score's "S:", and/or convert "$:n" to n /= 1000 and "$:nK"
+     *  if amount takes 4+ digits.  (No realistic chance for 7+ digits
+     *  for gold so would recover only 2 columns.  n >>= 10 might have
+     *  greater geek appeal but could lead to bug reports and couldn't
+     *  be accomplished via simple string truncation.)
+     *  For experience point and score suppression, might try that first
+     *  (better chance for column recovery, with "/nM" freeing 5 out of
+     *  7+ digits; in rare instances, "/nG" could free 8 out of 10+ digits)
+     *  before deciding to remove them altogether.
+     *  tty's shorter condition designations combined with comparable
+     *  trimming of hunger and encumbrance would be better overall.
+     */
+
+    number_of_lines = (iflags.wc2_statuslines < 3) ? 2 : 3;
+    fieldorder = (number_of_lines != 3) ? &twolineorder : &threelineorder;
+
+    cbuf[0] = '\0';
+    x = y = border ? 1 : 0; /* origin; ignored by curs_stat_conds(0) */
+    /* collect active conditions in cbuf[], space separated, suitable
+       for direct output if no highlighting is requested ('asis') but
+       primarily used to measure the length */
+    curs_stat_conds(0, &x, &y, colormasks, cbuf, &asis);
+    clen = (int) strlen(cbuf);
+
+    cap_and_hunger = 0;
+    if (*status_vals[BL_HUNGER])
+        cap_and_hunger |= 1;
+    if (*status_vals[BL_CAP])
+        cap_and_hunger |= 2;
+    exp_points = (flags.showexp ? 1 : 0);
+    /* don't bother conditionalizing this; always 0 for !SCORE_ON_BOTL */
+    sho_score = (status_activefields[BL_SCORE] != 0);
+
+    /* simplify testing which fields reside on which lines; assume line #0 */
+    (void) memset((genericptr_t) valline, 0, sizeof valline);
+    for (j = 1; j < number_of_lines; ++j)
+        for (i = 0; (fld = (*fieldorder)[j][i]) != BL_FLUSH; ++i)
+            valline[fld] = j;
+
+    /* iterate 0 and 1 and maybe 2 for status lines 1 and 2 and maybe 3 */
+    for (j = 0; j < number_of_lines; ++j) {
+
+ startover:
+        /* first pass for line #j -- figure out spacing */
+        (void) memset((genericptr_t) spacing, 0, sizeof spacing);
+        w = xtra = 0;
+        prev_fld = BL_FLUSH;
+        for (i = 0; (fld = (*fieldorder)[j][i]) != BL_FLUSH; ++i) {
+            text = status_vals[fld];
+            if (i == 0 && *text == ' ')
+                ++text;
+            /* most fields already include a leading space; we don't try to
+               count those separately, they're just part of field's length */
+            switch (fld) {
+            case BL_EXP:
+                spacing[fld] = 0; /* no leading or extra space */
+                if (!exp_points)
+                    continue;
+                break;
+            case BL_HPMAX:
+            case BL_ENEMAX:
+                spacing[fld] = 0; /* no leading or extra space */
+                break;
+            case BL_DX:
+            case BL_CO:
+            case BL_IN:
+            case BL_WI:
+            case BL_CH:
+                spacing[fld] = 0; /* leading space but no extra space */
+                break;
+            case BL_TITLE:
+                if (iflags.wc2_hitpointbar) {
+                    w += 2; /* count '[' and ']' */
+                    t = (int) strlen(text);
+                    if (t != 30) /* HPbar() will use modified copy of title */
+                        w -= (t - 30); /* '+= strlen()' below will add 't';
+                                        * functional result being 'w += 30' */
                 }
-
-                putstr(STATUS_WIN, 0, text);
-
-                if (iflags.hilite_delta) {
-#ifdef TEXTCOLOR
-                    if (coloridx != NO_COLOR)
-                        curses_toggle_color_attr(win, coloridx, NONE, OFF);
-#endif
-                    End_Attr(attridx);
+                /*FALLTHRU*/
+            case BL_ALIGN:
+            case BL_LEVELDESC:
+                spacing[fld] = (i > 0 ? 1 : 0);
+                break;
+            case BL_HUNGER:
+                spacing[fld] = (cap_and_hunger & 1);
+                break;
+            case BL_CAP:
+                spacing[fld] = (cap_and_hunger == 2);
+                break;
+            case BL_CONDITION:
+                text = cbuf; /* for 'w += strlen(text)' below */
+                spacing[fld] = (cap_and_hunger == 0);
+                break;
+            case BL_STR:
+            case BL_HP:
+            case BL_ENE:
+            case BL_AC:
+            case BL_GOLD:
+                spacing[fld] = 1; /* always extra space */
+                break;
+            case BL_XP:
+            case BL_HD:
+            case BL_TIME:
+                spacing[fld] = status_activefields[fld] ? 1 : 0;
+                break;
+            case BL_SCORE:
+                spacing[fld] = sho_score ? 1 : 0;
+                break;
+            default:
+                break;
+            }
+            w += (int) strlen(text);
+            /* if preceding field has any trailing spaces, don't add extra;
+               (should only apply to prev==title; status_update() handles
+               others that used to have trailing spaces by stripping such */
+            if (spacing[fld] > 0 && prev_fld != BL_FLUSH
+                && *(p = status_vals[prev_fld]) && *(eos(p) - 1) == ' '
+                && (prev_fld != BL_TITLE || !iflags.wc2_hitpointbar))
+                spacing[fld] = 0;
+            xtra += spacing[fld];
+
+            prev_fld = fld;
+        }
+        /* if the line is too long, first avoid extra spaces */
+        fld = MAXBLSTATS;
+        while (xtra > 0 && w + xtra > width) {
+            while (--fld >= 0) /* [assumes 'fld' is not unsigned!] */
+                if (spacing[fld] > 0) {
+                    xtra -= spacing[fld];
+                    spacing[fld] = 0;
+                    break;
                 }
-            } else {
-                /* hitpointbar using hp percent calculation */
-                int bar_pos, bar_len;
-                char *bar2 = (char *)0;
-                char bar[MAXCO], savedch = 0;
-                boolean twoparts = FALSE;
-
-                text = status_vals[fldidx1];
-                bar_len = strlen(text);
-                if (bar_len < MAXCO-1) {
-                    Strcpy(bar, text);
-                    bar_pos = (bar_len * hpbar_percent) / 100;
-                    if (bar_pos < 1 && hpbar_percent > 0)
-                        bar_pos = 1;
-                    if (bar_pos >= bar_len && hpbar_percent < 100)
-                        bar_pos = bar_len - 1;
-                    if (bar_pos > 0 && bar_pos < bar_len) {
-                        twoparts = TRUE;
-                        bar2 = &bar[bar_pos];
-                        savedch = *bar2;
-                        *bar2 = '\0';
-                    }
+        }
+        w += xtra; /* simplify further width checks */
+        /* if showing exper points and line is too wide, don't show them */
+        if (w > width && exp_points && j == valline[BL_EXP]
+            && ((*cbuf && j == valline[BL_CONDITION])
+                || (cap_and_hunger && j == valline[BL_HUNGER]))) {
+            exp_points = 0;
+            goto startover;
+        }
+#ifdef SCORE_ON_BOTL
+        if (sho_score && j == valline[BL_SCORE]) {
+            /* no point in letting score become truncated on the right
+               because showing fewer than all digits would be useless */
+            if (w > width) {
+                if (w - 2 <= width) {
+                    sho_score |= 2; /* strip "S:" prefix */
+                    w -= 2;
+                } else {
+                    sho_score = 0;
+                    goto startover;
                 }
+            }
+            /* right justify score unless window is very wide */
+            t = COLNO + (int) strlen(status_vals[BL_SCORE]);
+            if (t > width)
+                t = width;
+            if (w < t)
+                spacing[BL_SCORE] += (t - w);
+        }
+#endif
 
-                putstr(STATUS_WIN, 0, "[");
-                /* fixed attribute, not ((hpbar_color >> 8) & 0x00FF) */
-                wattron(win, A_REVERSE);
-#ifdef TEXTCOLOR
-                if (iflags.hilite_delta) {
-                    coloridx = hpbar_color & 0x00FF;
-                    if (coloridx != NO_COLOR)
-                        curses_toggle_color_attr(win, coloridx, NONE, ON);
+        /* second pass for line #j -- render it */
+        x = y = border ? 1 : 0;
+        wmove(win, y + j, x);
+        for (i = 0; (fld = (*fieldorder)[j][i]) != BL_FLUSH; ++i) {
+            if (!status_activefields[fld])
+                continue;
+
+            /* output any extra spaces that precede the current field
+               (if current field will be suppressed for any reason then
+               its spacing[] should be 0) */
+            for (t = spacing[fld]; t > 0; --t)
+                waddch(win, ' ');
+
+            text = status_vals[fld];
+            if (i == 0 && *text == ' ')
+                ++text; /* for first field of line, discard leading space */
+
+            switch (fld) {
+            case BL_EXP:
+                /* might be 'active' but suppressed due to lack of room */
+                if (!exp_points)
+                    continue;
+                break;
+            case BL_HUNGER:
+                if (number_of_lines == 3) {
+                    /* remember hunger's position */
+                    getyx(win, conddummy, condstart);
+                    /* if hunger won't be shown, figure out where cap
+                       will be; if cap won't be shown either, use where
+                       conditions would go if they were on this line */
+                    condstart += (cap_and_hunger == 2) ? spacing[BL_CAP]
+                                 : (cap_and_hunger == 0) ? 1 : 0;
                 }
-#endif
-                putstr(STATUS_WIN, 0, bar);
-#ifdef TEXTCOLOR
-                if (iflags.hilite_delta && coloridx != NO_COLOR)
-                    curses_toggle_color_attr(win, coloridx, NONE, OFF);
-#endif
-                wattroff(win, A_REVERSE);
-                if (twoparts) {
-                    *bar2 = savedch;
-                    putstr(STATUS_WIN, 0, bar2);
+                if (!(cap_and_hunger & 1))
+                    continue;
+                break;
+            case BL_CAP:
+                /* always enabled but might be empty */
+                if (!(cap_and_hunger & 2))
+                    continue;
+                break;
+            case BL_SCORE:
+#ifdef SCORE_ON_BOTL
+                if ((sho_score & 2) != 0) { /* strip "S:" prefix */
+                    if ((colon = index(text, ':')) != 0)
+                        text = strcat(strcpy(sbuf, " "), colon + 1);
+                    else
+                        sho_score = 0;
                 }
-                putstr(STATUS_WIN, 0, "]");
+#endif
+                if (!sho_score)
+                    continue;
+                break;
+            default:
+                break;
             }
-        }
-    }
-    wclrtoeol(win);
-    if (border)
-        wmove(win, 2, 1);
-    else
-        wmove (win, 1, 0);
-    for (i = 0; fieldorder[1][i] != BL_FLUSH; ++i) {
-        int fldidx2 = fieldorder[1][i];
 
-        if (status_activefields[fldidx2]) {
-            if (fldidx2 != BL_CONDITION) {
+            if (fld == BL_TITLE && iflags.wc2_hitpointbar) {
+                /* hitpointbar using hp percent calculation; title width
+                   is padded to 30 if shorter, truncated at 30 if longer;
+                   otherall width is 32 because of the enclosing brackets */
+                curs_HPbar(text, 0);
+
+            } else if (fld != BL_CONDITION) {
                 /* regular field, including title if no hitpointbar */
-                text = status_vals[fldidx2];
-                coloridx = curses_status_colors[fldidx2]; /* plus attribute */
+                coloridx = curses_status_colors[fld]; /* includes attribute */
                 if (iflags.hilite_delta && coloridx != NO_COLOR) {
-                    if (*text == ' ') {
-                        putstr(STATUS_WIN, 0, " ");
-                        text++;
+                    /* expect 1 leading space; don't highlight it */
+                    while (*text == ' ') {
+                        waddch(win, ' ');
+                        ++text;
+                    }
+                    attrmask = (coloridx >> 8) & 0x00FF;
+                    if (attrmask) {
+                        attrmask = nhattr2curses(attrmask);
+                        wattron(win, attrmask);
                     }
-                    attridx = (coloridx >> 8) & 0x00FF;
-                    /* multiple attributes can be in effect concurrently */
-                    Begin_Attr(attridx);
 #ifdef TEXTCOLOR
                     coloridx &= 0x00FF;
                     if (coloridx != NO_COLOR && coloridx != CLR_MAX)
@@ -422,188 +566,610 @@ unsigned long *colormasks;
 #endif
                 }
 
-                if (fldidx2 == BL_GOLD) {
-                    /* putmixed() due to GOLD glyph */
-                    putmixed(STATUS_WIN, 0, text);
-                } else {
-                    putstr(STATUS_WIN, 0, text);
-                }
+                waddstr(win, text);
 
                 if (iflags.hilite_delta) {
 #ifdef TEXTCOLOR
                     if (coloridx != NO_COLOR)
                         curses_toggle_color_attr(win, coloridx, NONE, OFF);
 #endif
-                    End_Attr(attridx);
+                    if (attrmask)
+                        wattroff(win, attrmask);
                 }
-            } else if (curses_condition_bits) {
-                MaybeDisplayCond(BL_MASK_STONE, "Stone");
-                MaybeDisplayCond(BL_MASK_SLIME, "Slime");
-                MaybeDisplayCond(BL_MASK_STRNGL, "Strngl");
-                MaybeDisplayCond(BL_MASK_FOODPOIS, "FoodPois");
-                MaybeDisplayCond(BL_MASK_TERMILL, "TermIll");
-                MaybeDisplayCond(BL_MASK_BLIND, "Blind");
-                MaybeDisplayCond(BL_MASK_DEAF, "Deaf");
-                MaybeDisplayCond(BL_MASK_STUN, "Stun");
-                MaybeDisplayCond(BL_MASK_CONF, "Conf");
-                MaybeDisplayCond(BL_MASK_HALLU, "Hallu");
-                MaybeDisplayCond(BL_MASK_LEV, "Lev");
-                MaybeDisplayCond(BL_MASK_FLY, "Fly");
-                MaybeDisplayCond(BL_MASK_RIDE, "Ride");
-            }
-        }
-    }
-    wclrtoeol(win);
-    return;
-}
 
-/* The new NH4-style horizontal layout on 3 lines */
-void
-draw_horizontal(border, colormasks)
-boolean border;
-unsigned long *colormasks;
-{
-    /* TODO: implement this */
-    /* for now, just draw classic */
-    draw_classic(border, colormasks);
+            } else {
+                /* status conditions */
+                if (curses_condition_bits) {
+                    getyx(win, y, x);
+                    /* cbuf[] was populated above; clen is its length */
+                    if (number_of_lines == 3) {
+                        /*
+                         * For 3-line status, align conditions with hunger
+                         * (or where it would have been, when not shown),
+                         * or if that doesn't provide enough room, right
+                         * align with window's edge, or just put out at
+                         * current spot if too long for right alignment.
+                         */
+                        /* both cbuf[] and hunger start with a leading
+                           space, so clen and condstart reflect that;
+                           'border' adjustments have already been made
+                           for x (offset by 1) and width (reduced by 2) */
+                        if (x + clen < width) {
+                            if (x < condstart && condstart + clen < width)
+                                wmove(win, y, condstart);
+                            else
+                                wmove(win, y, width + (border ? 1 : 0) - clen);
+                        }
+                    }
+                    /* 'asis' was set up by first curs_stat_conds() call
+                       above; True means that none of the conditions
+                       need highlighting; but we won't use concatenated
+                       condition string as-is if it will overflow; we
+                       want curs_stat_conds() to write '+' in last column
+                       if any conditions are all the way off the edge */
+                    if (x + clen <= width - (border ? 1 : 0))
+                        asis = FALSE;
+
+                    if (asis)
+                        waddstr(win, cbuf);
+                    else /* cond by cond if any cond specifies highlighting */
+                        curs_stat_conds(0, &x, &y, colormasks,
+                                        (char *) 0, (boolean *) 0);
+                } /* curses_condition_bits */
+            } /* hitpointbar vs regular field vs conditions */
+        } /* i (fld) */
+        wclrtoeol(win); /* [superfluous? draw_status() calls werase()] */
+    } /* j (line) */
+    return;
 }
 
-/* The vertical layout from the original curses implementation */
+/* vertical layout, to left or right of map */
 void
 draw_vertical(border, colormasks)
 boolean border;
 unsigned long *colormasks;
 {
-    static const enum statusfields fieldorder[24] = {
-         BL_TITLE, BL_STR, BL_DX, BL_CO, BL_IN, BL_WI, BL_CH, BL_ALIGN,
-         BL_SCORE, BL_LEVELDESC, BL_GOLD, BL_HP, BL_HPMAX, BL_ENE, BL_ENEMAX,
-         BL_AC, BL_XP, BL_EXP, BL_HD, BL_TIME, BL_HUNGER,
-         BL_CAP, BL_CONDITION, BL_FLUSH
+    /* for blank lines, the digit prefix is the order in which they get
+       removed if we need to shrink to fit within height limit (very rare) */
+    static const enum statusfields fieldorder[] = {
+        BL_TITLE, /* might be overlaid by hitpoint bar */
+        /* 4:blank */
+        BL_HP, BL_HPMAX,
+        BL_ENE, BL_ENEMAX,
+        BL_AC,
+        /* 3:blank */
+        BL_LEVELDESC,
+        BL_ALIGN,
+        BL_XP, BL_EXP, BL_HD,
+        BL_GOLD,
+        /* 2:blank (but only if time or score or both enabled) */
+        BL_TIME,
+        BL_SCORE,
+        /* 1:blank */
+        BL_STR, BL_DX, BL_CO, BL_IN, BL_WI, BL_CH,
+        /* 5:blank (if any of hunger, encumbrance, or conditions appear) */
+        BL_HUNGER, BL_CAP, /* these two are shown on same line */
+        BL_CONDITION, /* shown three per line so may take up to four lines */
+        BL_FLUSH
     };
-#ifdef TEXTCOLOR
-    int coloridx = NO_COLOR;
-#endif
-    int i, attrmask = 0;
-    char *text;
-    int attridx = 0;
-    int x = 0, y = 0;
+    static const enum statusfields shrinkorder[] = {
+         BL_STR, BL_SCORE, BL_TIME, BL_LEVELDESC, BL_HP,
+         BL_CONDITION, BL_CAP, BL_HUNGER
+    };
+    xchar spacing[MAXBLSTATS];
+    int i, fld, cap_and_hunger, time_and_score, cond_count;
+    char *text, *p, savedch = '\0';
+    int coloridx = NO_COLOR, attrmask = 0;
+    int height_needed, height, width, x = 0, y = 0;
     WINDOW *win = curses_get_nhwin(STATUS_WIN);
 
+    /* note: getmaxyx() is a macro which assigns values to height and width */
+    getmaxyx(win, height, width);
+
+    /* basic update formats fields for horizontal; derive vertical from them */
+    if (vert_status_dirty)
+        curs_vert_status_vals(width - (border ? 2 : 0));
+
+    /*
+     * Possible refinements:
+     *  If "<name> the <rank-or-monster>" is too wide, split it across
+     *  two lines; alternatively, use the old curses status code to
+     *  truncate the two portions separately.  (<name> is already being
+     *  truncated to 10 chars by the botl.c code, so we don't really
+     *  need to do anything further unless we want to override that.)
+     *  Format hunger, encumbrance, and conditions in columns:  12+1+12
+     *  for first two and for 2 conditions, 8+1+8+1+8 for 3+ conditions.
+     *  (Would probably only look good enough to matter when 6 or more
+     *  conditions are present, so not worth bothering with.)
+     */
+
+    cap_and_hunger = 0;
+    if (*status_vals_long[BL_HUNGER])
+        cap_and_hunger |= 1;
+    if (*status_vals_long[BL_CAP])
+        cap_and_hunger |= 2;
+    time_and_score = 0;
+    if (status_activefields[BL_TIME])
+        time_and_score |= 1;
+    if (status_activefields[BL_SCORE])
+        time_and_score |= 2;
+    cond_count = 0;
+    if (curses_condition_bits) {
+        for (i = 0; i < BL_MASK_BITS; ++i)
+            if (curses_condition_bits & (1 << i))
+                ++cond_count;
+    }
+
+    /* count how many lines we'll need; we normally space several groups of
+       fields with blank lines but might need to compress some of those out */
+    height_needed = border ? 2 : 0;
+    for (i = 0; (fld = i) < SIZE(spacing); ++i) {
+        switch ((enum statusfields) fld) {
+        case BL_HPMAX:
+        case BL_ENEMAX:
+        case BL_EXP:
+            spacing[fld] = 0; /* these will continue the previous line */
+            break;
+        case BL_HP:
+        case BL_LEVELDESC:
+        case BL_STR:
+            spacing[fld] = 2; /* simple group separation (no conditionals) */
+            break;
+        case BL_TIME:
+            /* time will be separated from gold unless it is inactive */
+            spacing[fld] = (time_and_score & 1) ? 2 : 0;
+            break;
+        case BL_SCORE:
+            /* unlike hunger+cap, score is shown on separate line from time;
+               needs time+score separator if time is inactive */
+            spacing[fld] = (time_and_score == 2) ? 2
+                           : (time_and_score & 1) ? 1 : 0;
+            break;
+        case BL_HUNGER:
+            /* separated from characteristics unless blank */
+            spacing[fld] = (cap_and_hunger & 1) ? 2 : 0;
+            break;
+        case BL_CAP:
+            /* on same line as hunger if both are non-blank,
+               otherwise needs blank line if hunger is being omitted */
+            spacing[fld] = (cap_and_hunger == 2) ? 2 : 0;
+            break;
+        case BL_CONDITION:
+            /* need blank line if hunger and encumbrance are both omitted,
+               otherwise just start on next line; if more than 3 conditions
+               are present, this will consume multiple lines from height */
+            spacing[fld] = cond_count ? (!cap_and_hunger ? 2 : 1) : 0;
+            if (cond_count > 3) /* first 3 handled via '+= spacing[]' below */
+                height_needed += (cond_count - 1) / 3; /* three per line */
+            break;
+        case BL_XP:
+        case BL_HD:
+        default:
+            /* might be inactive, otherwise normal case of 'on next line' */
+            spacing[fld] = status_activefields[fld] ? 1 : 0;
+            break;
+        }
+        height_needed += spacing[fld];
+    }
+    if (height_needed > height) {
+        for (i = 0; i < SIZE(shrinkorder); ++i) {
+            fld = shrinkorder[i];
+            if (spacing[fld] == 2) {
+                spacing[fld] = 1; /* suppress planned blank line */
+                if (--height_needed <= height)
+                    break;
+            }
+        }
+#ifdef SCORE_ON_BOTL
+        /* with all optional fields and every status condition (12 out
+           of the 13 since two are mutually exclusive) active, we need
+           21 non-blank lines; curses_create_main_windows() used to
+           require 24 lines or more in order to enable vertical status,
+           but that has been relaxed to 20 so height_needed might still
+           be too high after suppressing all the blank lines */
+        if (height_needed > height && status_activefields[BL_SCORE]) {
+            height_needed -= spacing[BL_SCORE];
+            spacing[BL_SCORE] = 0;
+            time_and_score &= ~2;
+            /* height_needed isn't used beyond here but we keep it accurate */
+            nhUse(height_needed);
+        }
+#endif
+    }
+
     if (border)
         x++, y++;
-    for (i = 0; fieldorder[i] != BL_FLUSH; ++i) {
-        int fldidx1 = fieldorder[i];
-
-        if (status_activefields[fldidx1]) {
-            if (fldidx1 != BL_TITLE || !iflags.wc2_hitpointbar) {
-
-                if (fldidx1 != BL_CONDITION) {
-                    text = status_vals_long[fldidx1];
-                    coloridx = curses_status_colors[fldidx1]; /* plus attr */
-                    if (iflags.hilite_delta && coloridx != NO_COLOR) {
-                        if (*text == ' ') {
-                            putstr(STATUS_WIN, 0, " ");
-                            text++;
-                        }
-                        /* multiple attributes can be in effect concurrently */
-                        attridx = (coloridx >> 8) & 0x00FF;
-                        Begin_Attr(attridx);
+    for (i = 0; (fld = fieldorder[i]) != BL_FLUSH; ++i) {
+        if (!status_activefields[fld])
+            continue;
+        if ((fld == BL_HUNGER && !(cap_and_hunger & 1))
+            || (fld == BL_CAP && !(cap_and_hunger & 2))
+            || (fld == BL_TIME && !(time_and_score & 1))
+            || (fld == BL_SCORE && !(time_and_score & 2)))
+            continue;
+
+        if (spacing[fld]) {
+            wmove(win, y++, x); /* move to next line */
+            if (spacing[fld] == 2)
+                 wmove(win, y++, x); /* skip a line */
+        }
+
+        if (fld == BL_TITLE && iflags.wc2_hitpointbar) {
+            /* 4: left+right borders and open+close brackets; 2: brackets */
+            curs_HPbar(status_vals_long[fld], width - (border ? 4 : 2));
+
+        } else if (fld != BL_CONDITION) {
+            /* regular field (including title if no hitpoint bar) */
+            text = status_vals_long[fld];
+            /* hunger and encumbrance come with a leading space;
+               we'll put them on the same line and omit that space for
+               the first (or only) and keep it for the second (if both) */
+            if (*text == ' '
+                && (fld == BL_HUNGER
+                    || (fld == BL_CAP && cap_and_hunger != 3)))
+                ++text;
+            coloridx = curses_status_colors[fld]; /* includes attributes */
+            if (iflags.hilite_delta && coloridx != NO_COLOR) {
+                /* most status_vals_long[] are "long-text : value" and
+                   unlike horizontal status's abbreviated "ab:value",
+                   we highlight just the value portion */
+                p = (fld != BL_TITLE) ? index(text, ':') : 0;
+                p = !p ? text : p + 1;
+                while (*p == ' ')
+                    ++p;
+                if ((fld == BL_EXP && *p == '/')
+                    || ((fld == BL_HPMAX || fld == BL_ENEMAX) && *p == '('))
+                    ++p;
+                /* prefix portion, if any, is output without highlighting */
+                if (p > text) {
+                    savedch = *p;
+                    *p = '\0';
+                    waddstr(win, text); /* output the prefix */
+                    *p = savedch;
+                    text = p; /* rest of field */
+                    if ((fld == BL_HPMAX || fld == BL_ENEMAX)
+                        && (p = index(text, ')')) != 0) {
+                        savedch = *p;
+                        *p = '\0';
+                    } else
+                        savedch = '\0';
+                }
+                attrmask = (coloridx >> 8) & 0x00FF;
+                if (attrmask) {
+                    attrmask = nhattr2curses(attrmask);
+                    wattron(win, attrmask);
+                }
 #ifdef TEXTCOLOR
-                        coloridx &= 0x00FF;
-                        if (coloridx != NO_COLOR && coloridx != CLR_MAX)
-                            curses_toggle_color_attr(win, coloridx, NONE, ON);
+                coloridx &= 0x00FF;
+                if (coloridx != NO_COLOR && coloridx != CLR_MAX)
+                    curses_toggle_color_attr(win, coloridx, NONE, ON);
 #endif
-                    }
+            } /* highlighting active */
 
-                    if (fldidx1 != BL_HPMAX
-                        && fldidx1 != BL_ENEMAX
-                        && fldidx1 != BL_EXP)
-                        wmove(win, y++, x); /* on next line unless the above */
+            waddstr(win, text);
 
-                    putstr(STATUS_WIN, 0, text);
+            if (iflags.hilite_delta) {
+#ifdef TEXTCOLOR
+                if (coloridx != NO_COLOR)
+                    curses_toggle_color_attr(win, coloridx, NONE, OFF);
+#endif
+                if (attrmask)
+                    wattroff(win, attrmask);
+            } /* resume normal rendition */
+            if ((fld == BL_HPMAX || fld == BL_ENEMAX) && savedch == ')') {
+                *p = savedch;
+                waddstr(win, p);
+            }
 
-                    if (fldidx1 == BL_TITLE)
-                        y++;
+        } else {
+            /* status conditions */
+            if (cond_count) {
+                /* output active conditions, three per line;
+                   cursor is already positioned where they should start */
+                curs_stat_conds(1, &x, &y, colormasks,
+                                (char *) 0, (boolean *) 0);
+            }
+        } /* hitpointbar vs regular field vs conditions */
+    } /* fld loop */
+    return;
+}
 
-                    if (iflags.hilite_delta) {
+/* hitpointbar using hp percent calculation */
+static void
+curs_HPbar(char *text, /* pre-padded with trailing spaces if short */
+           int bar_len) /* width of space within the brackets */
+{
 #ifdef TEXTCOLOR
-                        if (coloridx != NO_COLOR)
-                            curses_toggle_color_attr(win, coloridx, NONE, OFF);
+    int coloridx;
 #endif
-                        End_Attr(attridx);
-                    }
-                } else if (curses_condition_bits) { /* BL_CONDITION */
-                    MaybeDisplayCond(BL_MASK_STONE, "Stone");
-                    MaybeDisplayCond(BL_MASK_SLIME, "Slime");
-                    MaybeDisplayCond(BL_MASK_STRNGL, "Strngl");
-                    MaybeDisplayCond(BL_MASK_FOODPOIS, "FoodPois");
-                    MaybeDisplayCond(BL_MASK_TERMILL, "TermIll");
-                    MaybeDisplayCond(BL_MASK_BLIND, "Blind");
-                    MaybeDisplayCond(BL_MASK_DEAF, "Deaf");
-                    MaybeDisplayCond(BL_MASK_STUN, "Stun");
-                    MaybeDisplayCond(BL_MASK_CONF, "Conf");
-                    MaybeDisplayCond(BL_MASK_HALLU, "Hallu");
-                    MaybeDisplayCond(BL_MASK_LEV, "Lev");
-                    MaybeDisplayCond(BL_MASK_FLY, "Fly");
-                    MaybeDisplayCond(BL_MASK_RIDE, "Ride");
+    int k, bar_pos;
+    char bar[STATVAL_WIDTH], *bar2 = (char *) 0, savedch = '\0';
+    boolean twoparts = (hpbar_percent < 100);
+    WINDOW *win = curses_get_nhwin(STATUS_WIN);
+
+    if (bar_len < 1 || bar_len > 30)
+        bar_len = 30;
+    if (bar_len > (k = (int) strlen(text))) /* 26 for vertical status */
+        bar_len = k;
+    (void) strncpy(bar, text, bar_len);
+    bar[bar_len] = '\0';
+
+    bar_pos = (bar_len * hpbar_percent) / 100;
+    if (bar_pos < 1 && hpbar_percent > 0)
+        bar_pos = 1;
+    if (bar_pos >= bar_len && hpbar_percent < 100)
+        bar_pos = bar_len - 1;
+    if (twoparts) {
+        bar2 = &bar[bar_pos];
+        savedch = *bar2;
+        *bar2 = '\0';
+    }
+
+    waddch(win, '[');
+    if (*bar) { /* True unless dead (0 HP => bar_pos == 0) */
+        /* fixed attribute, not nhattr2curses((hpbar_color >> 8) & 0x00FF) */
+        wattron(win, A_REVERSE); /* do this even if hilite_delta is 0 */
+#ifdef TEXTCOLOR
+        if (iflags.hilite_delta) {
+            coloridx = hpbar_color & 0x00FF;
+            if (coloridx != NO_COLOR)
+                curses_toggle_color_attr(win, coloridx, NONE, ON);
+        }
+#endif
+
+        /* portion of title corresponding to current hit points */
+        waddstr(win, bar);
+
+#ifdef TEXTCOLOR
+        if (iflags.hilite_delta) {
+            if (coloridx != NO_COLOR)
+                curses_toggle_color_attr(win, coloridx, NONE, OFF);
+        }
+#endif
+        wattroff(win, A_REVERSE); /* do this even if hilite_delta is 0 */
+    } /* *bar (current HP > 0) */
+
+    if (twoparts) {
+        /* unhighlighted portion, corresponding to current injuries */
+        *bar2 = savedch;
+        waddstr(win, bar2);
+    }
+    waddch(win, ']');
+}
+
+/* valid_conditions[] is used primarily for parsing hilite_status rules, but
+   we can use it for condition names and mask bits, avoiding duplication */
+extern const struct condmap valid_conditions[]; /* botl.c */
+
+static void
+curs_stat_conds(int vert_cond, /* 0 => horizontal, 1 => vertical */
+                int *x, int *y,  /* real for vertical, ignored otherwise */
+                unsigned long *colormasks, /* input */
+                char *condbuf, /* optional output; collect string of conds */
+                boolean *nohilite) /* optional output; indicates whether -*/
+{                                  /*+ condbuf[] could be used as-is      */
+    char condnam[20];
+    int i;
+    long bitmsk;
+
+    if (condbuf) {
+        /* construct string " cond1 cond2 cond3" of all active conditions;
+           if none of them specify highlighting, set the nohilite flag so
+           caller can output the string as is; otherwise its length can be
+           used to decide how to position prior to calling us back without
+           'condbuf' in order to perform cond by cond output */
+        condbuf[0] = '\0';
+        if (nohilite)
+            *nohilite = TRUE; /* assume ok */
+        for (i = 0; i < BL_MASK_BITS; ++i) {
+            bitmsk = valid_conditions[i].bitmask;
+            if (curses_condition_bits & bitmsk) {
+                Strcat(strcat(condbuf, " "),
+                       upstart(strcpy(condnam, valid_conditions[i].id)));
+                if (nohilite && *nohilite
+                    && (condcolor(bitmsk, colormasks) != NO_COLOR
+                        || condattr(bitmsk, colormasks) != 0))
+                    *nohilite = FALSE;
+            }
+        }
+    } else if (curses_condition_bits) {
+        unsigned long cond_bits;
+        int height = 0, width, cx, cy, cy0, attrmask = 0, color = NO_COLOR;
+        boolean border, do_vert = (vert_cond != 0);
+        WINDOW *win = curses_get_nhwin(STATUS_WIN);
+
+        getmaxyx(win, height, width);
+        border = curses_window_has_border(STATUS_WIN);
+        cy0 = height - (border ? 2 : 1);
+
+        cond_bits = curses_condition_bits;
+        /* show active conditions directly; for vertical, three per line */
+        for (i = 0; i < BL_MASK_BITS; ++i) {
+            bitmsk = valid_conditions[i].bitmask;
+            if (cond_bits & bitmsk) {
+                if (!do_vert) {
+                    getyx(win, cy, cx);
+                    if (cx >= width - (border ? 2 : 1) || (border && cy > cy0))
+                        break; /* skip rest if not enough room for any more */
                 }
-            } else {
-                /* hitpointbar using hp percent calculation */
-                int bar_pos, bar_len;
-                char *bar2 = (char *)0;
-                char bar[MAXCO], savedch = 0;
-                boolean twoparts = FALSE;
-                int height,width;
-
-                text = status_vals[fldidx1];
-                getmaxyx(win, height, width);
-                bar_len = min((int) strlen(text), width - (border ? 4 : 2));
-                text[bar_len] = '\0';
-                if (bar_len < MAXCO-1) {
-                    Strcpy(bar, text);
-                    bar_pos = (bar_len * hpbar_percent) / 100;
-                    if (bar_pos < 1 && hpbar_percent > 0)
-                        bar_pos = 1;
-                    if (bar_pos >= bar_len && hpbar_percent < 100)
-                        bar_pos = bar_len - 1;
-                    if (bar_pos > 0 && bar_pos < bar_len) {
-                        twoparts = TRUE;
-                        bar2 = &bar[bar_pos];
-                        savedch = *bar2;
-                        *bar2 = '\0';
+                cond_bits &= ~bitmsk; /* nonzero if another cond after this */
+                /* output unhighlighted leading space unless at #1 of 3 */
+                if (!do_vert || (vert_cond % 3) != 1)
+                    waddch(win, ' ');
+                if (iflags.hilite_delta) {
+                    if ((attrmask = condattr(bitmsk, colormasks)) != 0) {
+                        attrmask = nhattr2curses(attrmask);
+                        wattron(win, attrmask);
                     }
-                }
-                wmove(win, y++, x);
-                putstr(STATUS_WIN, 0, "[");
-                wattron(win, A_REVERSE); /* not (hpbar_color >> 8) & 0x00FF) */
 #ifdef TEXTCOLOR
-                coloridx = hpbar_color & 0x00FF;
-                if (iflags.hilite_delta) {
-                    /* attridx = (hpbar_color & 0xFF00) >> 8; */
-                    if (coloridx != NO_COLOR)
-                        curses_toggle_color_attr(win, coloridx, NONE, ON);
+                    if ((color = condcolor(bitmsk, colormasks)) != NO_COLOR)
+                        curses_toggle_color_attr(win, color, NONE, ON);
 #endif
                 }
-                putstr(STATUS_WIN, 0, bar);
-#ifdef TEXTCOLOR
+
+                /* output the condition name */
+                waddstr(win, upstart(strcpy(condnam, valid_conditions[i].id)));
+
                 if (iflags.hilite_delta) {
-                    if (coloridx != NO_COLOR)
-                        curses_toggle_color_attr(win, coloridx, NONE, OFF);
-                }
+#ifdef TEXTCOLOR
+                    if (color != NO_COLOR)
+                        curses_toggle_color_attr(win, color, NONE, OFF);
 #endif
-                wattroff(win,A_REVERSE);
-                if (twoparts) {
-                     *bar2 = savedch;
-                     putstr(STATUS_WIN, 0, bar2);
+                    if (attrmask)
+                        wattroff(win, attrmask);
                 }
-                putstr(STATUS_WIN, 0, "]");
-                y++; /* blank line after title */
-            }
+                /* if that was #3 of 3 advance to next line */
+                if (do_vert && (++vert_cond % 3) == 1)
+                    wmove(win, (*y)++, *x);
+            } /* if cond_bits & bitmask */
+        } /* for i */
+
+        /* if we stopped before showing all the conditions, put a '+'
+           in the rightmost column to indicate that there would be more
+           shown if there were more room available */
+        if (cond_bits != 0L) {
+            wmove(win, cy0, width - (border ? 2 : 1));
+            waddch(win, '+');
         }
     }
     return;
 }
 
+/* status_update() sets up values for horizontal status; do vertical */
+void
+curs_vert_status_vals(int win_width)
+{
+    const char *lbl; /* field name used as label */
+    const char *text, *colon;
+    char leadingspace[15]; /* 5 suffices */
+    boolean use_name;
+    int fldidx, hp_width, en_tmp, fld_width, lbl_width;
+
+    /* width of bigger of full HP and full En regardless of current value */
+    hp_width = (int) strlen(status_vals[BL_HPMAX]) - 2; /* -2: "("...")" */
+    en_tmp = (int) strlen(status_vals[BL_ENEMAX]) - 2;
+    if (en_tmp > hp_width)
+        hp_width = en_tmp;
+    /*
+     * status_vals[] are used for horizontal orientation
+     *  (wide lines of multiple short values).
+     * status_vals_long[] are used for vertical orientation
+     *  (narrow-ish lines of one long value each [mostly]).
+     */
+    lbl_width = 13;
+ startover:
+    for (fldidx = 0; fldidx < MAXBLSTATS; ++fldidx) {
+        if (!status_activefields[fldidx] || fldidx == BL_CONDITION) {
+            *status_vals_long[fldidx] = '\0';
+        } else {
+            text = status_vals[fldidx];
+            if (fldidx != BL_TITLE && fldidx != BL_LEVELDESC) {
+                if ((colon = index(text, ':')) != 0)
+                    text = colon + 1;
+            }
+            lbl = status_fieldnm[fldidx];
+            use_name = TRUE;
+            leadingspace[0] = '\0';
+            /* classify type of field (labeled or not) and make some fixups */
+            switch ((enum statusfields) fldidx) {
+            case BL_XP:
+                 /* "experience-level : N" is too long and becomes misleading
+                    if value is shown as 'N/experience-points' */
+                lbl = "experience";
+                break;
+            case BL_LEVELDESC:
+                /* "dungeon-level" is redundant when value is "Dlvl-N" */
+                lbl = "location";
+                break;
+            case BL_HD:
+                /* "HD" is too oscure; 0 actually means 1d4 (so about 1/2);
+                   "hit-dice" is obscure too but doesn't stand out as such */
+                lbl = (!strcmp(text, "1") || !strcmp(text, "0")) ? "hit-die"
+                      : "hit-dice";
+                break;
+            case BL_ALIGN:
+                /* don't want sprintf(": %s") below inserting second space */
+                if (*text == ' ')
+                    ++text;
+                break;
+            case BL_HP:
+            case BL_ENE:
+                /* pad HP and En so that they're right aligned */
+                fld_width = (int) strlen(text);
+                if (fld_width < hp_width)
+                    Sprintf(leadingspace, "%*s", hp_width - fld_width, " ");
+                break;
+            case BL_STR:
+            case BL_DX:
+            case BL_CO:
+            case BL_IN:
+            case BL_WI:
+            case BL_CH:
+                /* for vertical orientation, right justify characteristics;
+                   exceptional strength, if present, will protrude to right */
+                if (strlen(text) == 1)
+                     Strcpy(leadingspace, " ");
+                break;
+            case BL_HPMAX:
+            case BL_ENEMAX:
+                 /* pad HPmax and Enmax so that they're right aligned with
+                    each other with a one-space gap after current HP/En */
+                fld_width = (int) strlen(text);
+                if (fld_width < hp_width + 3) /* +3: " " gap and "("...")" */
+                    Sprintf(leadingspace, "%*s",
+                            (hp_width + 3) - fld_width, " ");
+                /*FALLTHRU*/
+            case BL_EXP:
+            case BL_HUNGER:
+            case BL_CAP:
+            case BL_TITLE:
+                use_name = FALSE;
+                break;
+            default:
+                break;
+            }
+            if (use_name) {
+                Sprintf(status_vals_long[fldidx], "%*.*s: %s%s",
+                        -lbl_width, lbl_width, lbl, leadingspace, text);
+                *status_vals_long[fldidx] = highc(*status_vals_long[fldidx]);
+            } else {
+                /* unlabeled: title, hp-max, en-max, exp-points, hunger+cap */
+                Sprintf(status_vals_long[fldidx], "%s%s", leadingspace, text);
+                /* when appending, base field uses field name as label */
+                use_name = (fldidx == BL_HPMAX || fldidx == BL_ENEMAX
+                            || fldidx == BL_EXP);
+            }
+            /* check whether 'label : value' is too wide; if so, we'll
+               shorten the label's allowed width and try again */
+            if (use_name) {
+                fld_width = (int) strlen(status_vals_long[fldidx]);
+                /* each extension field is preceded by its base field in
+                   order to append, so base's _vals_long[] has been set */
+                switch ((enum statusfields) fldidx) {
+                case BL_HPMAX:
+                    fld_width += (int) strlen(status_vals_long[BL_HP]);
+                    break;
+                case BL_ENEMAX:
+                    fld_width += (int) strlen(status_vals_long[BL_ENE]);
+                    break;
+                case BL_EXP:
+                    fld_width += (int) strlen(status_vals_long[BL_XP]);
+                    break;
+                default:
+                    break;
+                }
+                if (fld_width > win_width && lbl_width > 10) {
+                    lbl_width -= (fld_width - win_width);
+                    if (lbl_width < 10)
+                        lbl_width = 10;
+                    goto startover;
+                }
+            }
+        }
+    }
+    vert_status_dirty = 0;
+}
+
 #ifdef TEXTCOLOR
 /*
  * Return what color this condition should
@@ -659,6 +1225,27 @@ unsigned long *bmarray;
     }
     return attr;
 }
+/* convert tty attributes to curses attributes;
+   despite similar names, the mask fields have different values */
+static int
+nhattr2curses(attrmask)
+int attrmask;
+{
+    int result = 0;
+
+    if (attrmask & HL_BOLD)
+        result |= A_BOLD;
+    if (attrmask & HL_INVERSE)
+        result |= A_REVERSE;
+    if (attrmask & HL_ULINE)
+        result |= A_UNDERLINE;
+    if (attrmask & HL_BLINK)
+        result |= A_BLINK;
+    if (attrmask & HL_DIM)
+        result |= A_DIM;
+
+    return result;
+}
 #endif /* STATUS_HILITES */