OSDN Git Service

upgrade to 3.6.2
[jnethack/source.git] / win / curses / cursstat.c
1 /* vim:set cin ft=c sw=4 sts=4 ts=8 et ai cino=Ls\:0t0(0 : -*- mode:c;fill-column:80;tab-width:8;c-basic-offset:4;indent-tabs-mode:nil;c-file-style:"k&r" -*-*/
2 /* NetHack 3.6 cursstat.c */
3 /* Copyright (c) Andy Thomson, 2018. */
4 /* NetHack may be freely redistributed.  See license for details. */
5
6 #include "curses.h"
7 #include "hack.h"
8 #include "wincurs.h"
9 #include "cursstat.h"
10
11 /* Status window functions for curses interface */
12
13 /*
14  * The following data structures come from the genl_ routines in
15  * src/windows.c and as such are considered to be on the window-port
16  * "side" of things, rather than the NetHack-core "side" of things.
17  */
18
19 extern const char *status_fieldfmt[MAXBLSTATS];
20 extern const char *status_fieldnm[MAXBLSTATS];
21 extern char *status_vals[MAXBLSTATS];
22 extern boolean status_activefields[MAXBLSTATS];
23
24 /* Long format fields for vertical window */
25 static char *status_vals_long[MAXBLSTATS];
26
27 static unsigned long *curses_colormasks;
28 static long curses_condition_bits;
29 static int curses_status_colors[MAXBLSTATS];
30 static int hpbar_percent, hpbar_color;
31 static int vert_status_dirty;
32
33 static void NDECL(draw_status);
34 static void FDECL(draw_vertical, (BOOLEAN_P));
35 static void FDECL(draw_horizontal, (BOOLEAN_P));
36 static void curs_HPbar(char *, int);
37 static void curs_stat_conds(int, int *, int *, char *, boolean *);
38 static void curs_vert_status_vals(int);
39 #ifdef STATUS_HILITES
40 #ifdef TEXTCOLOR
41 static int FDECL(condcolor, (long, unsigned long *));
42 #endif
43 static int FDECL(condattr, (long, unsigned long *));
44 static int FDECL(nhattr2curses, (int));
45 #endif /* STATUS_HILITES */
46
47 /* width of a single line in vertical status orientation (one field per line;
48    everything but title fits within 30 even with prefix and longest value) */
49 #define STATVAL_WIDTH 60 /* overkill; was MAXCO (200), massive overkill */
50
51 void
52 curses_status_init()
53 {
54     int i;
55
56     for (i = 0; i < MAXBLSTATS; ++i) {
57         curses_status_colors[i] = NO_COLOR; /* no color and no attributes */
58         status_vals_long[i] = (char *) alloc(STATVAL_WIDTH);
59         *status_vals_long[i] = '\0';
60     }
61     curses_condition_bits = 0L;
62     hpbar_percent = 0, hpbar_color = NO_COLOR;
63     vert_status_dirty = 1;
64
65     /* let genl_status_init do most of the initialization */
66     genl_status_init();
67     return;
68 }
69
70 void
71 curses_status_finish()
72 {
73     int i;
74
75     for (i = 0; i < MAXBLSTATS; ++i) {
76         if (status_vals_long[i])
77             free(status_vals_long[i]), status_vals_long[i] = (char *) 0;
78     }
79
80     genl_status_finish();
81     return;
82 }
83
84 /*
85  *  *_status_update()
86  *      -- update the value of a status field.
87  *      -- the fldindex identifies which field is changing and
88  *         is an integer index value from botl.h
89  *      -- fldindex could be any one of the following from botl.h:
90  *         BL_TITLE, BL_STR, BL_DX, BL_CO, BL_IN, BL_WI, BL_CH,
91  *         BL_ALIGN, BL_SCORE, BL_CAP, BL_GOLD, BL_ENE, BL_ENEMAX,
92  *         BL_XP, BL_AC, BL_HD, BL_TIME, BL_HUNGER, BL_HP, BL_HPMAX,
93  *         BL_LEVELDESC, BL_EXP, BL_CONDITION
94  *      -- fldindex could also be BL_FLUSH (-1), which is not really
95  *         a field index, but is a special trigger to tell the
96  *         windowport that it should redisplay all its status fields,
97  *         even if no changes have been presented to it.
98  *      -- ptr is usually a "char *", unless fldindex is BL_CONDITION.
99  *         If fldindex is BL_CONDITION, then ptr is a long value with
100  *         any or none of the following bits set (from botl.h):
101  *              BL_MASK_STONE           0x00000001L
102  *              BL_MASK_SLIME           0x00000002L
103  *              BL_MASK_STRNGL          0x00000004L
104  *              BL_MASK_FOODPOIS        0x00000008L
105  *              BL_MASK_TERMILL         0x00000010L
106  *              BL_MASK_BLIND           0x00000020L
107  *              BL_MASK_DEAF            0x00000040L
108  *              BL_MASK_STUN            0x00000080L
109  *              BL_MASK_CONF            0x00000100L
110  *              BL_MASK_HALLU           0x00000200L
111  *              BL_MASK_LEV             0x00000400L
112  *              BL_MASK_FLY             0x00000800L
113  *              BL_MASK_RIDE            0x00001000L
114  *      -- The value passed for BL_GOLD includes an encoded leading
115  *         symbol for GOLD "\GXXXXNNNN:nnn". If the window port needs to use
116  *         the textual gold amount without the leading "$:" the port will
117  *         have to skip past ':' in the passed "ptr" for the BL_GOLD case.
118  *      -- color is an unsigned int.
119  *               color_index = color & 0x00FF;       CLR_* value
120  *               attribute   = color & 0xFF00 >> 8;  BL_* values
121  *         This holds the color and attribute that the field should
122  *         be displayed in.
123  *         This is relevant for everything except BL_CONDITION fldindex.
124  *         If fldindex is BL_CONDITION, this parameter should be ignored,
125  *         as condition hilighting is done via the next colormasks
126  *         parameter instead.
127  *
128  *      -- colormasks - pointer to cond_hilites[] array of colormasks.
129  *         Only relevant for BL_CONDITION fldindex. The window port
130  *         should ignore this parameter for other fldindex values.
131  *         Each condition bit must only ever appear in one of the
132  *         CLR_ array members, but can appear in multiple HL_ATTCLR_
133  *         offsets (because more than one attribute can co-exist).
134  *         See doc/window.doc for more details.
135  */
136
137 static int changed_fields = 0;
138
139 void
140 curses_status_update(fldidx, ptr, chg, percent, color_and_attr, colormasks)
141 int fldidx, chg UNUSED,
142     percent, color_and_attr;
143 genericptr_t ptr;
144 unsigned long *colormasks;
145 {
146     long *condptr = (long *) ptr;
147     char *text = (char *) ptr;
148
149     if (fldidx != BL_FLUSH) {
150         if (fldidx < 0 || fldidx >= MAXBLSTATS) {
151             context.botlx = context.botl = FALSE; /* avoid another bot() */
152             panic("curses_status_update(%d)", fldidx);
153         }
154         changed_fields |= (1 << fldidx);
155         *status_vals[fldidx] = '\0';
156         if (!status_activefields[fldidx])
157             return;
158         if (fldidx == BL_CONDITION) {
159             curses_condition_bits = *condptr;
160             curses_colormasks = colormasks;
161         } else {
162 #ifndef TEXTCOLOR
163             color_and_attr = (color_and_attr & ~0x00FF) | NO_COLOR;
164 #endif
165             /*
166              * status_vals[] are used for horizontal orientation
167              *  (wide lines of multiple short values).
168              * status_vals_long[] are used for vertical orientation
169              *  (narrow-ish lines of one long value each [mostly]
170              *  and get constructed at need from status_vals[] rather
171              *  than the original values passed to status_update()).
172              */
173             if (fldidx == BL_GOLD) {
174                 /* decode once instead of every time it's displayed */
175                 status_vals[BL_GOLD][0] = ' ';
176                 text = decode_mixed(&status_vals[BL_GOLD][1], text);
177             } else if ((fldidx == BL_HUNGER || fldidx == BL_CAP)
178                        && (!*text || !strcmp(text, " "))) {
179                 /* fieldfmt[] is " %s"; avoid lone space when empty */
180                 *status_vals[fldidx] = '\0';
181             } else {
182                 Sprintf(status_vals[fldidx],
183                         (fldidx == BL_TITLE && iflags.wc2_hitpointbar)
184                         ? "%-30s" : status_fieldfmt[fldidx]
185                                     ? status_fieldfmt[fldidx] : "%s",
186                         text);
187                 /* strip trailing spaces; core ought to do this for us */
188                 if (fldidx == BL_HUNGER || fldidx == BL_LEVELDESC)
189                     (void) trimspaces(status_vals[fldidx]);
190             }
191
192             /* status_vals_long[] used to be set up here even when not
193                in use; it has been moved to curs_vert_status_vals() */
194             vert_status_dirty = 1;
195
196             curses_status_colors[fldidx] = color_and_attr;
197             if (iflags.wc2_hitpointbar && fldidx == BL_HP) {
198                 hpbar_percent = percent;
199                 hpbar_color = color_and_attr;
200             }
201         }
202     } else { /* BL_FLUSH */
203         draw_status();
204         changed_fields = 0;
205     }
206 }
207
208 static void
209 draw_status()
210 {
211     WINDOW *win = curses_get_nhwin(STATUS_WIN);
212     int orient = curses_get_window_orientation(STATUS_WIN);
213     boolean horiz = (orient != ALIGN_RIGHT && orient != ALIGN_LEFT);
214     boolean border = curses_window_has_border(STATUS_WIN);
215
216     /* Figure out if we have proper window dimensions for horizontal
217        statusbar. */
218     if (horiz) {
219         int ax = 0, ay = 0;
220         int cy = (iflags.wc2_statuslines < 3) ? 2 : 3;
221
222         /* actual y (and x) */
223         getmaxyx(win, ay, ax);
224         if (border)
225             ay -= 2;
226
227         if (cy != ay) { /* something changed. Redo everything */
228             curs_reset_windows(TRUE, TRUE);
229             win = curses_get_nhwin(STATUS_WIN);
230         }
231     }
232
233     werase(win);
234     if (horiz)
235         draw_horizontal(border);
236     else
237         draw_vertical(border);
238
239     if (border)
240         box(win, 0, 0);
241     wnoutrefresh(win);
242 }
243
244 /* horizontal layout on 2 or 3 lines */
245 static void
246 draw_horizontal(border)
247 boolean border;
248 {
249 #define blPAD BL_FLUSH
250     /* almost all fields already come with a leading space;
251        "xspace" indicates places where we'll generate an extra one */
252     static const enum statusfields
253     twolineorder[3][15] = {
254         { BL_TITLE,
255           /*xspace*/ BL_STR, BL_DX, BL_CO, BL_IN, BL_WI, BL_CH,
256           /*xspace*/ BL_ALIGN,
257           /*xspace*/ BL_SCORE,
258           BL_FLUSH, blPAD, blPAD, blPAD, blPAD, blPAD },
259         { BL_LEVELDESC,
260           /*xspace*/ BL_GOLD,
261           /*xspace*/ BL_HP, BL_HPMAX,
262           /*xspace*/ BL_ENE, BL_ENEMAX,
263           /*xspace*/ BL_AC,
264           /*xspace*/ BL_XP, BL_EXP, BL_HD,
265           /*xspace*/ BL_TIME,
266           /*xspace*/ BL_HUNGER, BL_CAP, BL_CONDITION,
267           BL_FLUSH },
268         { BL_FLUSH, blPAD, blPAD, blPAD, blPAD, blPAD, blPAD, blPAD,
269           blPAD, blPAD, blPAD, blPAD, blPAD, blPAD, blPAD }
270     },
271     threelineorder[3][15] = { /* moves align to line 2, leveldesc+ to 3 */
272         { BL_TITLE,
273           /*xspace*/ BL_STR, BL_DX, BL_CO, BL_IN, BL_WI, BL_CH,
274           /*xspace*/ BL_SCORE,
275           BL_FLUSH, blPAD, blPAD, blPAD, blPAD, blPAD, blPAD },
276         { BL_ALIGN,
277           /*xspace*/ BL_GOLD,
278           /*xspace*/ BL_HP, BL_HPMAX,
279           /*xspace*/ BL_ENE, BL_ENEMAX,
280           /*xspace*/ BL_AC,
281           /*xspace*/ BL_XP, BL_EXP, BL_HD,
282           /*xspace*/ BL_HUNGER, BL_CAP,
283           BL_FLUSH, blPAD, blPAD },
284         { BL_LEVELDESC,
285           /*xspace*/ BL_TIME,
286           /*xspecial*/ BL_CONDITION,
287           BL_FLUSH, blPAD, blPAD, blPAD, blPAD, blPAD, blPAD, blPAD,
288           blPAD, blPAD, blPAD, blPAD }
289     };
290     const enum statusfields (*fieldorder)[15];
291     xchar spacing[MAXBLSTATS], valline[MAXBLSTATS];
292     enum statusfields fld, prev_fld;
293     char *text, *p, cbuf[BUFSZ], ebuf[STATVAL_WIDTH];
294 #ifdef SCORE_ON_BOTL
295     char *colon;
296     char sbuf[STATVAL_WIDTH];
297 #endif
298     int i, j, number_of_lines,
299         cap_and_hunger, exp_points, sho_score,
300         height, width, w, xtra, clen, x, y, t, ex, ey,
301         condstart = 0, conddummy = 0;
302 #ifdef STATUS_HILITES
303     int coloridx = NO_COLOR, attrmask = 0;
304 #endif /* STATUS_HILITES */
305     boolean asis = FALSE;
306     WINDOW *win = curses_get_nhwin(STATUS_WIN);
307
308     /* note: getmaxyx() is a macro which assigns values to height and width */
309     getmaxyx(win, height, width);
310     if (border)
311         height -= 2, width -= 2;
312
313     /*
314      * Potential revisions:
315      *  If line with gold is too long, strip "$:" prefix like is done
316      *  for score's "S:", and/or convert "$:n" to n /= 1000 and "$:nK"
317      *  if amount takes 4+ digits.  (No realistic chance for 7+ digits
318      *  for gold so would recover only 2 columns.  n >>= 10 might have
319      *  greater geek appeal but could lead to bug reports and couldn't
320      *  be accomplished via simple string truncation.)
321      *  For experience point and score suppression, might try that first
322      *  (better chance for column recovery, with "/nM" freeing 5 out of
323      *  7+ digits; in rare instances, "/nG" could free 8 out of 10+ digits)
324      *  before deciding to remove them altogether.
325      *  tty's shorter condition designations combined with comparable
326      *  trimming of hunger and encumbrance would be better overall.
327      */
328
329     number_of_lines = (iflags.wc2_statuslines < 3) ? 2 : 3;
330     fieldorder = (number_of_lines != 3) ? twolineorder : threelineorder;
331
332     cbuf[0] = '\0';
333     x = y = border ? 1 : 0; /* origin; ignored by curs_stat_conds(0) */
334     /* collect active conditions in cbuf[], space separated, suitable
335        for direct output if no highlighting is requested ('asis') but
336        primarily used to measure the length */
337     curs_stat_conds(0, &x, &y, cbuf, &asis);
338     clen = (int) strlen(cbuf);
339
340     cap_and_hunger = 0;
341     if (*status_vals[BL_HUNGER])
342         cap_and_hunger |= 1;
343     if (*status_vals[BL_CAP])
344         cap_and_hunger |= 2;
345     exp_points = (flags.showexp ? 1 : 0);
346     /* don't bother conditionalizing this; always 0 for !SCORE_ON_BOTL */
347     sho_score = (status_activefields[BL_SCORE] != 0);
348
349     /* simplify testing which fields reside on which lines; assume line #0 */
350     (void) memset((genericptr_t) valline, 0, sizeof valline);
351     for (j = 1; j < number_of_lines; ++j)
352         for (i = 0; (fld = fieldorder[j][i]) != BL_FLUSH; ++i)
353             valline[fld] = j;
354
355     /* iterate 0 and 1 and maybe 2 for status lines 1 and 2 and maybe 3 */
356     for (j = 0; j < number_of_lines; ++j) {
357
358  startover:
359         /* first pass for line #j -- figure out spacing */
360         (void) memset((genericptr_t) spacing, 0, sizeof spacing);
361         w = xtra = 0; /* w: width so far; xtra: number of extra spaces */
362         prev_fld = BL_FLUSH;
363         for (i = 0; (fld = fieldorder[j][i]) != BL_FLUSH; ++i) {
364             /* when the core marks a field as disabled, it doesn't call
365                status_update() to tell us to throw away the old value, so
366                polymorph leaves stale XP and rehumanize leaves stale HD */
367             if (!status_activefields[fld])
368                 *status_vals[fld] = '\0';
369             text = status_vals[fld];
370             if (i == 0 && *text == ' ')
371                 ++text;
372             /* most fields already include a leading space; we don't try to
373                count those separately, they're just part of field's length */
374             switch (fld) {
375             case BL_EXP:
376                 spacing[fld] = 0; /* no leading or extra space */
377                 if (!exp_points)
378                     continue;
379                 break;
380             case BL_HPMAX:
381             case BL_ENEMAX:
382                 spacing[fld] = 0; /* no leading or extra space */
383                 break;
384             case BL_DX:
385             case BL_CO:
386             case BL_IN:
387             case BL_WI:
388             case BL_CH:
389                 spacing[fld] = 0; /* leading space but no extra space */
390                 break;
391             case BL_TITLE:
392                 if (iflags.wc2_hitpointbar) {
393                     w += 2; /* count '[' and ']' */
394                     t = (int) strlen(text);
395                     if (t != 30) /* HPbar() will use modified copy of title */
396                         w -= (t - 30); /* '+= strlen()' below will add 't';
397                                         * functional result being 'w += 30' */
398                 }
399                 /*FALLTHRU*/
400             case BL_ALIGN:
401             case BL_LEVELDESC:
402                 spacing[fld] = (i > 0 ? 1 : 0); /* extra space unless first */
403                 break;
404             case BL_HUNGER:
405                 spacing[fld] = (cap_and_hunger & 1);
406                 break;
407             case BL_CAP:
408                 spacing[fld] = (cap_and_hunger == 2);
409                 break;
410             case BL_CONDITION:
411                 text = cbuf; /* for 'w += strlen(text)' below */
412                 spacing[fld] = (cap_and_hunger == 0);
413                 break;
414             case BL_STR:
415             case BL_HP:
416             case BL_ENE:
417             case BL_AC:
418             case BL_GOLD:
419                 spacing[fld] = 1; /* always extra space */
420                 break;
421             case BL_XP:
422             case BL_HD:
423             case BL_TIME:
424                 spacing[fld] = status_activefields[fld] ? 1 : 0;
425                 break;
426             case BL_SCORE:
427                 spacing[fld] = sho_score ? 1 : 0;
428                 break;
429             default:
430                 break;
431             }
432             w += (int) strlen(text);
433             /* if preceding field has any trailing spaces, don't add extra;
434                (should only apply to prev==title; status_update() handles
435                others that used to have trailing spaces by stripping such) */
436             if (spacing[fld] > 0 && prev_fld != BL_FLUSH
437                 && *(p = status_vals[prev_fld]) && *(eos(p) - 1) == ' '
438                 && (prev_fld != BL_TITLE || !iflags.wc2_hitpointbar))
439                 spacing[fld] = 0;
440             xtra += spacing[fld];
441
442             prev_fld = fld;
443         }
444         /* if the line is too long, first avoid extra spaces */
445         fld = MAXBLSTATS;
446         while (xtra > 0 && w + xtra > width) {
447             while (--fld >= 0) /* [assumes 'fld' is not unsigned!] */
448                 if (spacing[fld] > 0) {
449                     xtra -= spacing[fld];
450                     spacing[fld] = 0;
451                     break;
452                 }
453         }
454         w += xtra; /* simplify further width checks */
455         /* if showing exper points and line is too wide, don't show them */
456         if (w > width && exp_points && j == valline[BL_EXP]
457             && ((*cbuf && j == valline[BL_CONDITION])
458                 || (cap_and_hunger && j == valline[BL_HUNGER]))) {
459             exp_points = 0;
460             goto startover;
461         }
462 #ifdef SCORE_ON_BOTL
463         if (sho_score && j == valline[BL_SCORE]) {
464             /* no point in letting score become truncated on the right
465                because showing fewer than all digits would be useless */
466             if (w > width) {
467                 if (w - 2 <= width) {
468                     sho_score |= 2; /* strip "S:" prefix */
469                     w -= 2;
470                 } else {
471                     sho_score = 0;
472                     goto startover;
473                 }
474             }
475             /* right justify score unless window is very wide */
476             t = COLNO + (int) strlen(status_vals[BL_SCORE]);
477             if (t > width)
478                 t = width;
479             if (w < t)
480                 spacing[BL_SCORE] += (t - w);
481         }
482 #endif
483
484         /* second pass for line #j -- render it */
485         x = y = border ? 1 : 0;
486         wmove(win, y + j, x);
487         for (i = 0; (fld = fieldorder[j][i]) != BL_FLUSH; ++i) {
488             if (!status_activefields[fld])
489                 continue;
490
491             /* output any extra spaces that precede the current field
492                (if current field will be suppressed for any reason then
493                its spacing[] should be 0) */
494             for (t = spacing[fld]; t > 0; --t)
495                 waddch(win, ' ');
496
497             text = status_vals[fld];
498             if (i == 0 && *text == ' ')
499                 ++text; /* for first field of line, discard leading space */
500
501             switch (fld) {
502             case BL_EXP:
503                 /* might be 'active' but suppressed due to lack of room */
504                 if (!exp_points)
505                     continue;
506                 break;
507             case BL_HUNGER:
508                 if (number_of_lines == 3) {
509                     /* remember hunger's position */
510                     getyx(win, conddummy, condstart);
511                     /* if hunger won't be shown, figure out where cap
512                        will be; if cap won't be shown either, use where
513                        conditions would go if they were on this line */
514                     condstart += (cap_and_hunger == 2) ? spacing[BL_CAP]
515                                  : (cap_and_hunger == 0) ? 1 : 0;
516                 }
517                 if (!(cap_and_hunger & 1))
518                     continue;
519                 break;
520             case BL_CAP:
521                 /* always enabled but might be empty */
522                 if (!(cap_and_hunger & 2))
523                     continue;
524                 /* check whether encumbrance is going to go past right edge
525                    and wrap; if so, truncate it; (won't wrap on last line
526                    of borderless window, but will when there's a border);
527                    could only do that after all extra spaces are gone */
528                 if (!xtra) {
529                      getyx(win, ey, ex);
530                      t = (int) strlen(text);
531                      if (ex + t > width - (border ? 0 : 1)) {
532                          text = strcpy(ebuf, text);
533                          t = (width - (border ? 0 : 1)) - (ex - 1);
534                          ebuf[max(t, 2)] = '\0'; /* might still wrap... */
535                      }
536                 }
537                 break;
538             case BL_SCORE:
539 #ifdef SCORE_ON_BOTL
540                 if ((sho_score & 2) != 0) { /* strip "S:" prefix */
541                     if ((colon = index(text, ':')) != 0)
542                         text = strcat(strcpy(sbuf, " "), colon + 1);
543                     else
544                         sho_score = 0;
545                 }
546 #endif
547                 if (!sho_score)
548                     continue;
549                 break;
550             default:
551                 break;
552             }
553
554             if (fld == BL_TITLE && iflags.wc2_hitpointbar) {
555                 /* hitpointbar using hp percent calculation; title width
556                    is padded to 30 if shorter, truncated at 30 if longer;
557                    overall width is 32 because of the enclosing brackets */
558                 curs_HPbar(text, 0);
559
560             } else if (fld != BL_CONDITION) {
561                 /* regular field, including title if no hitpointbar */
562 #ifdef STATUS_HILITES
563                 coloridx = curses_status_colors[fld]; /* includes attribute */
564                 if (iflags.hilite_delta && coloridx != NO_COLOR) {
565                     /* expect 1 leading space; don't highlight it */
566                     while (*text == ' ') {
567                         waddch(win, ' ');
568                         ++text;
569                     }
570                     attrmask = (coloridx >> 8) & 0x00FF;
571                     if (attrmask) {
572                         attrmask = nhattr2curses(attrmask);
573                         wattron(win, attrmask);
574                     }
575 #ifdef TEXTCOLOR
576                     coloridx &= 0x00FF;
577                     if (coloridx != NO_COLOR && coloridx != CLR_MAX)
578                         curses_toggle_color_attr(win, coloridx, NONE, ON);
579 #endif
580                 }
581 #endif /* STATUS_HILITES */
582
583                 waddstr(win, text);
584
585 #ifdef STATUS_HILITES
586                 if (iflags.hilite_delta) {
587 #ifdef TEXTCOLOR
588                     if (coloridx != NO_COLOR)
589                         curses_toggle_color_attr(win, coloridx, NONE, OFF);
590 #endif
591                     if (attrmask)
592                         wattroff(win, attrmask);
593                 }
594 #endif /* STATUS_HILITES */
595
596             } else {
597                 /* status conditions */
598                 if (curses_condition_bits) {
599                     getyx(win, y, x);
600                     /* encumbrance is truncated if too wide, but other fields
601                        aren't; if window is narrower than normal, last field
602                        written might have wrapped to the next line */
603                     if (y > j + (border ? 1 : 0))
604                         x = width - (border ? -1 : 0), /* (width-=2 above) */
605                         y = j + (border ? 1 : 0);
606                     /* cbuf[] was populated above; clen is its length */
607                     if (number_of_lines == 3) {
608                         /*
609                          * For 3-line status, align conditions with hunger
610                          * (or where it would have been, when not shown),
611                          * or if that doesn't provide enough room, right
612                          * align with window's edge, or just put out at
613                          * current spot if too long for right alignment.
614                          */
615                         /* both cbuf[] and hunger start with a leading
616                            space, so clen and condstart reflect that;
617                            'border' adjustments have already been made
618                            for x (offset by 1) and width (reduced by 2) */
619                         if (x + clen < width) {
620                             if (x < condstart && condstart + clen < width)
621                                 wmove(win, y, condstart);
622                             else
623                                 wmove(win, y, width + (border ? 1 : 0) - clen);
624                         }
625                     }
626                     /* 'asis' was set up by first curs_stat_conds() call
627                        above; True means that none of the conditions
628                        need highlighting; but we won't use concatenated
629                        condition string as-is if it will overflow; we
630                        want curs_stat_conds() to write '+' in last column
631                        if any conditions are all the way off the edge */
632                     if (x + clen > width - (border ? 1 : 0))
633                         asis = FALSE;
634
635                     if (asis)
636                         waddstr(win, cbuf);
637                     else /* cond by cond if any cond specifies highlighting */
638                         curs_stat_conds(0, &x, &y, (char *) 0, (boolean *) 0);
639                 } /* curses_condition_bits */
640             } /* hitpointbar vs regular field vs conditions */
641         } /* i (fld) */
642         wclrtoeol(win); /* [superfluous? draw_status() calls werase()] */
643     } /* j (line) */
644     return;
645 }
646
647 /* vertical layout, to left or right of map */
648 static void
649 draw_vertical(border)
650 boolean border;
651 {
652     /* for blank lines, the digit prefix is the order in which they get
653        removed if we need to shrink to fit within height limit (very rare) */
654     static const enum statusfields fieldorder[] = {
655         BL_TITLE, /* might be overlaid by hitpoint bar */
656         /* 4:blank */
657         BL_HP, BL_HPMAX,
658         BL_ENE, BL_ENEMAX,
659         BL_AC,
660         /* 3:blank */
661         BL_LEVELDESC,
662         BL_ALIGN,
663         BL_XP, BL_EXP, BL_HD,
664         BL_GOLD,
665         /* 2:blank (but only if time or score or both enabled) */
666         BL_TIME,
667         BL_SCORE,
668         /* 1:blank */
669         BL_STR, BL_DX, BL_CO, BL_IN, BL_WI, BL_CH,
670         /* 5:blank (if any of hunger, encumbrance, or conditions appear) */
671         BL_HUNGER, BL_CAP, /* these two are shown on same line */
672         BL_CONDITION, /* shown three per line so may take up to four lines */
673         BL_FLUSH
674     };
675     static const enum statusfields shrinkorder[] = {
676          BL_STR, BL_SCORE, BL_TIME, BL_LEVELDESC, BL_HP,
677          BL_CONDITION, BL_CAP, BL_HUNGER
678     };
679     xchar spacing[MAXBLSTATS];
680     int i, fld, cap_and_hunger, time_and_score, cond_count;
681     char *text;
682 #ifdef STATUS_HILITES
683     char *p = 0, savedch = '\0';
684     int coloridx = NO_COLOR, attrmask = 0;
685 #endif /* STATUS_HILITES */
686     int height_needed, height, width, x = 0, y = 0;
687     WINDOW *win = curses_get_nhwin(STATUS_WIN);
688
689     /* note: getmaxyx() is a macro which assigns values to height and width */
690     getmaxyx(win, height, width);
691
692     /* basic update formats fields for horizontal; derive vertical from them */
693     if (vert_status_dirty)
694         curs_vert_status_vals(width - (border ? 2 : 0));
695
696     /*
697      * Possible refinements:
698      *  If "<name> the <rank-or-monster>" is too wide, split it across
699      *  two lines; alternatively, use the old curses status code to
700      *  truncate the two portions separately.  (<name> is already being
701      *  truncated to 10 chars by the botl.c code, so we don't really
702      *  need to do anything further unless we want to override that.)
703      *  Format hunger, encumbrance, and conditions in columns:  12+1+12
704      *  for first two and for 2 conditions, 8+1+8+1+8 for 3+ conditions.
705      *  (Would probably only look good enough to matter when 6 or more
706      *  conditions are present, so not worth bothering with.)
707      */
708
709     cap_and_hunger = 0;
710     if (*status_vals_long[BL_HUNGER])
711         cap_and_hunger |= 1;
712     if (*status_vals_long[BL_CAP])
713         cap_and_hunger |= 2;
714     time_and_score = 0;
715     if (status_activefields[BL_TIME])
716         time_and_score |= 1;
717     if (status_activefields[BL_SCORE])
718         time_and_score |= 2;
719     cond_count = 0;
720     if (curses_condition_bits) {
721         for (i = 0; i < BL_MASK_BITS; ++i)
722             if (curses_condition_bits & (1 << i))
723                 ++cond_count;
724     }
725
726     /* count how many lines we'll need; we normally space several groups of
727        fields with blank lines but might need to compress some of those out */
728     height_needed = border ? 2 : 0;
729     for (i = 0; (fld = i) < SIZE(spacing); ++i) {
730         switch ((enum statusfields) fld) {
731         case BL_HPMAX:
732         case BL_ENEMAX:
733         case BL_EXP:
734             spacing[fld] = 0; /* these will continue the previous line */
735             break;
736         case BL_HP:
737         case BL_LEVELDESC:
738         case BL_STR:
739             spacing[fld] = 2; /* simple group separation (no conditionals) */
740             break;
741         case BL_TIME:
742             /* time will be separated from gold unless it is inactive */
743             spacing[fld] = (time_and_score & 1) ? 2 : 0;
744             break;
745         case BL_SCORE:
746             /* unlike hunger+cap, score is shown on separate line from time;
747                needs time+score separator if time is inactive */
748             spacing[fld] = (time_and_score == 2) ? 2
749                            : (time_and_score & 1) ? 1 : 0;
750             break;
751         case BL_HUNGER:
752             /* separated from characteristics unless blank */
753             spacing[fld] = (cap_and_hunger & 1) ? 2 : 0;
754             break;
755         case BL_CAP:
756             /* on same line as hunger if both are non-blank,
757                otherwise needs blank line if hunger is being omitted */
758             spacing[fld] = (cap_and_hunger == 2) ? 2 : 0;
759             break;
760         case BL_CONDITION:
761             /* need blank line if hunger and encumbrance are both omitted,
762                otherwise just start on next line; if more than 3 conditions
763                are present, this will consume multiple lines from height */
764             spacing[fld] = cond_count ? (!cap_and_hunger ? 2 : 1) : 0;
765             if (cond_count > 3) /* first 3 handled via '+= spacing[]' below */
766                 height_needed += (cond_count - 1) / 3; /* three per line */
767             break;
768         case BL_XP:
769         case BL_HD:
770         default:
771             /* might be inactive, otherwise normal case of 'on next line' */
772             spacing[fld] = status_activefields[fld] ? 1 : 0;
773             break;
774         }
775         height_needed += spacing[fld];
776     }
777     if (height_needed > height) {
778         for (i = 0; i < SIZE(shrinkorder); ++i) {
779             fld = shrinkorder[i];
780             if (spacing[fld] == 2) {
781                 spacing[fld] = 1; /* suppress planned blank line */
782                 if (--height_needed <= height)
783                     break;
784             }
785         }
786 #ifdef SCORE_ON_BOTL
787         /* with all optional fields and every status condition (12 out
788            of the 13 since two are mutually exclusive) active, we need
789            21 non-blank lines; curses_create_main_windows() used to
790            require 24 lines or more in order to enable vertical status,
791            but that has been relaxed to 20 so height_needed might still
792            be too high after suppressing all the blank lines */
793         if (height_needed > height && status_activefields[BL_SCORE]) {
794             height_needed -= spacing[BL_SCORE];
795             spacing[BL_SCORE] = 0;
796             time_and_score &= ~2;
797             /* height_needed isn't used beyond here but we keep it accurate */
798             nhUse(height_needed);
799         }
800 #endif
801     }
802
803     if (border)
804         x++, y++;
805     for (i = 0; (fld = fieldorder[i]) != BL_FLUSH; ++i) {
806         if (!status_activefields[fld])
807             continue;
808         if ((fld == BL_HUNGER && !(cap_and_hunger & 1))
809             || (fld == BL_CAP && !(cap_and_hunger & 2))
810             || (fld == BL_TIME && !(time_and_score & 1))
811             || (fld == BL_SCORE && !(time_and_score & 2)))
812             continue;
813
814         if (spacing[fld]) {
815             wmove(win, y++, x); /* move to next line */
816             if (spacing[fld] == 2)
817                  wmove(win, y++, x); /* skip a line */
818         }
819
820         if (fld == BL_TITLE && iflags.wc2_hitpointbar) {
821             /* 4: left+right borders and open+close brackets; 2: brackets */
822             curs_HPbar(status_vals_long[fld], width - (border ? 4 : 2));
823
824         } else if (fld != BL_CONDITION) {
825             /* regular field (including title if no hitpoint bar) */
826             text = status_vals_long[fld];
827             /* hunger and encumbrance come with a leading space;
828                we'll put them on the same line and omit that space for
829                the first (or only) and keep it for the second (if both) */
830             if (*text == ' '
831                 && (fld == BL_HUNGER
832                     || (fld == BL_CAP && cap_and_hunger != 3)))
833                 ++text;
834 #ifdef STATUS_HILITES
835             coloridx = curses_status_colors[fld]; /* includes attributes */
836             if (iflags.hilite_delta && coloridx != NO_COLOR) {
837                 /* most status_vals_long[] are "long-text : value" and
838                    unlike horizontal status's abbreviated "ab:value",
839                    we highlight just the value portion */
840                 p = (fld != BL_TITLE) ? index(text, ':') : 0;
841                 p = !p ? text : p + 1;
842                 while (*p == ' ')
843                     ++p;
844                 if ((fld == BL_EXP && *p == '/')
845                     || ((fld == BL_HPMAX || fld == BL_ENEMAX) && *p == '('))
846                     ++p;
847                 /* prefix portion, if any, is output without highlighting */
848                 if (p > text) {
849                     savedch = *p;
850                     *p = '\0';
851                     waddstr(win, text); /* output the prefix */
852                     *p = savedch;
853                     text = p; /* rest of field */
854                     if ((fld == BL_HPMAX || fld == BL_ENEMAX)
855                         && (p = index(text, ')')) != 0) {
856                         savedch = *p;
857                         *p = '\0';
858                     } else
859                         savedch = '\0';
860                 }
861                 attrmask = (coloridx >> 8) & 0x00FF;
862                 if (attrmask) {
863                     attrmask = nhattr2curses(attrmask);
864                     wattron(win, attrmask);
865                 }
866 #ifdef TEXTCOLOR
867                 coloridx &= 0x00FF;
868                 if (coloridx != NO_COLOR && coloridx != CLR_MAX)
869                     curses_toggle_color_attr(win, coloridx, NONE, ON);
870 #endif
871             } /* highlighting active */
872 #endif /* STATUS_HILITES */
873
874             waddstr(win, text);
875
876 #ifdef STATUS_HILITES
877             if (iflags.hilite_delta) {
878 #ifdef TEXTCOLOR
879                 if (coloridx != NO_COLOR)
880                     curses_toggle_color_attr(win, coloridx, NONE, OFF);
881 #endif
882                 if (attrmask)
883                     wattroff(win, attrmask);
884             } /* resume normal rendition */
885             if ((fld == BL_HPMAX || fld == BL_ENEMAX) && savedch == ')') {
886                 *p = savedch;
887                 waddstr(win, p);
888             }
889 #endif /* STATUS_HILITES */
890
891         } else {
892             /* status conditions */
893             if (cond_count) {
894                 /* output active conditions, three per line;
895                    cursor is already positioned where they should start */
896                 curs_stat_conds(1, &x, &y, (char *) 0, (boolean *) 0);
897             }
898         } /* hitpointbar vs regular field vs conditions */
899     } /* fld loop */
900     return;
901 }
902
903 /* hitpointbar using hp percent calculation */
904 static void
905 curs_HPbar(char *text, /* pre-padded with trailing spaces if short */
906            int bar_len) /* width of space within the brackets */
907 {
908 #ifdef STATUS_HILITES
909 #ifdef TEXTCOLOR
910     int coloridx;
911 #endif
912 #endif /* STATUS_HILITES */
913     int k, bar_pos;
914     char bar[STATVAL_WIDTH], *bar2 = (char *) 0, savedch = '\0';
915     boolean twoparts = (hpbar_percent < 100);
916     WINDOW *win = curses_get_nhwin(STATUS_WIN);
917
918     if (bar_len < 1 || bar_len > 30)
919         bar_len = 30;
920     if (bar_len > (k = (int) strlen(text))) /* 26 for vertical status */
921         bar_len = k;
922     (void) strncpy(bar, text, bar_len);
923     bar[bar_len] = '\0';
924
925     bar_pos = (bar_len * hpbar_percent) / 100;
926     if (bar_pos < 1 && hpbar_percent > 0)
927         bar_pos = 1;
928     if (bar_pos >= bar_len && hpbar_percent < 100)
929         bar_pos = bar_len - 1;
930     if (twoparts) {
931         bar2 = &bar[bar_pos];
932         savedch = *bar2;
933         *bar2 = '\0';
934     }
935
936     waddch(win, '[');
937     if (*bar) { /* True unless dead (0 HP => bar_pos == 0) */
938         /* fixed attribute, not nhattr2curses((hpbar_color >> 8) & 0x00FF) */
939         wattron(win, A_REVERSE); /* do this even if hilite_delta is 0 */
940 #ifdef STATUS_HILITES
941 #ifdef TEXTCOLOR
942         if (iflags.hilite_delta) {
943             coloridx = hpbar_color & 0x00FF;
944             if (coloridx != NO_COLOR)
945                 curses_toggle_color_attr(win, coloridx, NONE, ON);
946         }
947 #endif
948 #endif /* STATUS_HILITES */
949
950         /* portion of title corresponding to current hit points */
951         waddstr(win, bar);
952
953 #ifdef STATUS_HILITES
954 #ifdef TEXTCOLOR
955         if (iflags.hilite_delta) {
956             if (coloridx != NO_COLOR)
957                 curses_toggle_color_attr(win, coloridx, NONE, OFF);
958         }
959 #endif
960 #endif /* STATUS_HILITES */
961         wattroff(win, A_REVERSE); /* do this even if hilite_delta is 0 */
962     } /* *bar (current HP > 0) */
963
964     if (twoparts) {
965         /* unhighlighted portion, corresponding to current injuries */
966         *bar2 = savedch;
967         waddstr(win, bar2);
968     }
969     waddch(win, ']');
970 }
971
972 /* valid_conditions[] is used primarily for parsing hilite_status rules, but
973    we can use it for condition names and mask bits, avoiding duplication */
974 extern const struct condmap valid_conditions[]; /* botl.c */
975
976 static void
977 curs_stat_conds(int vert_cond, /* 0 => horizontal, 1 => vertical */
978                 int *x, int *y,  /* real for vertical, ignored otherwise */
979                 char *condbuf, /* optional output; collect string of conds */
980                 boolean *nohilite) /* optional output; indicates whether -*/
981 {                                  /*+ condbuf[] could be used as-is      */
982     char condnam[20];
983     int i;
984     long bitmsk;
985
986     if (condbuf) {
987         /* construct string " cond1 cond2 cond3" of all active conditions;
988            if none of them specify highlighting, set the nohilite flag so
989            caller can output the string as is; otherwise its length can be
990            used to decide how to position prior to calling us back without
991            'condbuf' in order to perform cond by cond output */
992         condbuf[0] = '\0';
993         if (nohilite)
994             *nohilite = TRUE; /* assume ok */
995         for (i = 0; i < BL_MASK_BITS; ++i) {
996             bitmsk = valid_conditions[i].bitmask;
997             if (curses_condition_bits & bitmsk) {
998                 Strcpy(condnam, valid_conditions[i].id);
999                 Strcat(strcat(condbuf, " "), upstart(condnam));
1000 #ifdef STATUS_HILITES
1001                 if (nohilite && *nohilite
1002                     && (condcolor(bitmsk, curses_colormasks) != NO_COLOR
1003                         || condattr(bitmsk, curses_colormasks) != 0))
1004                     *nohilite = FALSE;
1005 #endif /* STATUS_HILITES */
1006             }
1007         }
1008     } else if (curses_condition_bits) {
1009         unsigned long cond_bits;
1010         int height = 0, width, cx, cy, cy0, cndlen;
1011 #ifdef STATUS_HILITES
1012         int attrmask = 0, color = NO_COLOR;
1013 #endif /* STATUS_HILITES */
1014         boolean border, do_vert = (vert_cond != 0);
1015         WINDOW *win = curses_get_nhwin(STATUS_WIN);
1016
1017         getmaxyx(win, height, width);
1018         border = curses_window_has_border(STATUS_WIN);
1019         cy0 = height - (border ? 2 : 1);
1020
1021         cond_bits = curses_condition_bits;
1022         /* show active conditions directly; for vertical, three per line */
1023         for (i = 0; i < BL_MASK_BITS; ++i) {
1024             bitmsk = valid_conditions[i].bitmask;
1025             if (cond_bits & bitmsk) {
1026                 Strcpy(condnam, valid_conditions[i].id);
1027                 cndlen = 1 + (int) strlen(condnam); /* count leading space */
1028                 if (!do_vert) {
1029                     getyx(win, cy, cx);
1030                     if (cy > cy0) /* wrap to next line shouldn't happen */
1031                         cx = width, cy = cy0;
1032                     if (cx + cndlen > width - (border ? 2 : 1)) {
1033                         /* not enough room for current condition */
1034                         if (cx + 1 > width - (border ? 2 : 1))
1035                             break; /* no room at all; skip it and the rest */
1036                         /* room for part; truncate it to avoid wrapping */
1037                         condnam[width - (border ? 2 : 1) - cx] = '\0';
1038                     }
1039                 }
1040                 cond_bits &= ~bitmsk; /* nonzero if another cond after this */
1041                 /* output unhighlighted leading space unless at #1 of 3 */
1042                 if (!do_vert || (vert_cond % 3) != 1)
1043                     waddch(win, ' ');
1044 #ifdef STATUS_HILITES
1045                 if (iflags.hilite_delta) {
1046                     if ((attrmask = condattr(bitmsk, curses_colormasks))
1047                         != 0) {
1048                         attrmask = nhattr2curses(attrmask);
1049                         wattron(win, attrmask);
1050                     }
1051 #ifdef TEXTCOLOR
1052                     if ((color = condcolor(bitmsk, curses_colormasks))
1053                         != NO_COLOR)
1054                         curses_toggle_color_attr(win, color, NONE, ON);
1055 #endif
1056                 }
1057 #endif /* STATUS_HILITES */
1058
1059                 /* output the condition name */
1060                 waddstr(win, upstart(condnam));
1061
1062 #ifdef STATUS_HILITES
1063                 if (iflags.hilite_delta) {
1064 #ifdef TEXTCOLOR
1065                     if (color != NO_COLOR)
1066                         curses_toggle_color_attr(win, color, NONE, OFF);
1067 #endif
1068                     if (attrmask)
1069                         wattroff(win, attrmask);
1070                 }
1071 #endif /* STATUS_HILITES */
1072                 /* if that was #3 of 3 advance to next line */
1073                 if (do_vert && (++vert_cond % 3) == 1)
1074                     wmove(win, (*y)++, *x);
1075             } /* if cond_bits & bitmask */
1076         } /* for i */
1077
1078         /* if we stopped before showing all the conditions, put a '+'
1079            in the rightmost column to indicate that there would be more
1080            shown if there were more room available */
1081         if (cond_bits != 0L) {
1082             wmove(win, cy0, width - (border ? 2 : 1));
1083             waddch(win, '+');
1084         }
1085     }
1086     return;
1087 }
1088
1089 /* status_update() sets up values for horizontal status; do vertical */
1090 void
1091 curs_vert_status_vals(int win_width)
1092 {
1093     const char *lbl; /* field name used as label */
1094     const char *text, *colon;
1095     char leadingspace[15]; /* 5 suffices */
1096     boolean use_name;
1097     int fldidx, hp_width, en_tmp, fld_width, lbl_width;
1098
1099     /* width of bigger of full HP and full En regardless of current value */
1100     hp_width = (int) strlen(status_vals[BL_HPMAX]) - 2; /* -2: "("...")" */
1101     en_tmp = (int) strlen(status_vals[BL_ENEMAX]) - 2;
1102     if (en_tmp > hp_width)
1103         hp_width = en_tmp;
1104     /*
1105      * status_vals[] are used for horizontal orientation
1106      *  (wide lines of multiple short values).
1107      * status_vals_long[] are used for vertical orientation
1108      *  (narrow-ish lines of one long value each [mostly]).
1109      */
1110     lbl_width = 13;
1111  startover:
1112     for (fldidx = 0; fldidx < MAXBLSTATS; ++fldidx) {
1113         if (!status_activefields[fldidx] || fldidx == BL_CONDITION) {
1114             *status_vals_long[fldidx] = '\0';
1115         } else {
1116             text = status_vals[fldidx];
1117             if (fldidx != BL_TITLE && fldidx != BL_LEVELDESC) {
1118                 if ((colon = index(text, ':')) != 0)
1119                     text = colon + 1;
1120             }
1121             lbl = status_fieldnm[fldidx];
1122             use_name = TRUE;
1123             leadingspace[0] = '\0';
1124             /* classify type of field (labeled or not) and make some fixups */
1125             switch ((enum statusfields) fldidx) {
1126             case BL_XP:
1127                  /* "experience-level : N" is too long and becomes misleading
1128                     if value is shown as 'N/experience-points' */
1129                 lbl = "experience";
1130                 break;
1131             case BL_LEVELDESC:
1132                 /* "dungeon-level" is redundant when value is "Dlvl-N" */
1133                 lbl = "location";
1134                 break;
1135             case BL_HD:
1136                 /* "HD" is too oscure; 0 actually means 1d4 (so about 1/2);
1137                    "hit-dice" is obscure too but doesn't stand out as such */
1138                 lbl = (!strcmp(text, "1") || !strcmp(text, "0")) ? "hit-die"
1139                       : "hit-dice";
1140                 break;
1141             case BL_ALIGN:
1142                 /* don't want sprintf(": %s") below inserting second space */
1143                 if (*text == ' ')
1144                     ++text;
1145                 break;
1146             case BL_HP:
1147             case BL_ENE:
1148                 /* pad HP and En so that they're right aligned */
1149                 fld_width = (int) strlen(text);
1150                 if (fld_width < hp_width)
1151                     Sprintf(leadingspace, "%*s", hp_width - fld_width, " ");
1152                 break;
1153             case BL_STR:
1154             case BL_DX:
1155             case BL_CO:
1156             case BL_IN:
1157             case BL_WI:
1158             case BL_CH:
1159                 /* for vertical orientation, right justify characteristics;
1160                    exceptional strength, if present, will protrude to right */
1161                 if (strlen(text) == 1)
1162                      Strcpy(leadingspace, " ");
1163                 break;
1164             case BL_HPMAX:
1165             case BL_ENEMAX:
1166                  /* pad HPmax and Enmax so that they're right aligned with
1167                     each other with a one-space gap after current HP/En */
1168                 fld_width = (int) strlen(text);
1169                 if (fld_width < hp_width + 3) /* +3: " " gap and "("...")" */
1170                     Sprintf(leadingspace, "%*s",
1171                             (hp_width + 3) - fld_width, " ");
1172                 /*FALLTHRU*/
1173             case BL_EXP:
1174             case BL_HUNGER:
1175             case BL_CAP:
1176             case BL_TITLE:
1177                 use_name = FALSE;
1178                 break;
1179             default:
1180                 break;
1181             }
1182             if (use_name) {
1183                 Sprintf(status_vals_long[fldidx], "%*.*s: %s%s",
1184                         -lbl_width, lbl_width, lbl, leadingspace, text);
1185                 *status_vals_long[fldidx] = highc(*status_vals_long[fldidx]);
1186             } else {
1187                 /* unlabeled: title, hp-max, en-max, exp-points, hunger+cap */
1188                 Sprintf(status_vals_long[fldidx], "%s%s", leadingspace, text);
1189                 /* when appending, base field uses field name as label */
1190                 use_name = (fldidx == BL_HPMAX || fldidx == BL_ENEMAX
1191                             || fldidx == BL_EXP);
1192             }
1193             /* check whether 'label : value' is too wide; if so, we'll
1194                shorten the label's allowed width and try again */
1195             if (use_name) {
1196                 fld_width = (int) strlen(status_vals_long[fldidx]);
1197                 /* each extension field is preceded by its base field in
1198                    order to append, so base's _vals_long[] has been set */
1199                 switch ((enum statusfields) fldidx) {
1200                 case BL_HPMAX:
1201                     fld_width += (int) strlen(status_vals_long[BL_HP]);
1202                     break;
1203                 case BL_ENEMAX:
1204                     fld_width += (int) strlen(status_vals_long[BL_ENE]);
1205                     break;
1206                 case BL_EXP:
1207                     fld_width += (int) strlen(status_vals_long[BL_XP]);
1208                     break;
1209                 default:
1210                     break;
1211                 }
1212                 if (fld_width > win_width && lbl_width > 10) {
1213                     lbl_width -= (fld_width - win_width);
1214                     if (lbl_width < 10)
1215                         lbl_width = 10;
1216                     goto startover;
1217                 }
1218             }
1219         }
1220     }
1221     vert_status_dirty = 0;
1222 }
1223
1224 #ifdef STATUS_HILITES
1225 #ifdef TEXTCOLOR
1226 /*
1227  * Return what color this condition should
1228  * be displayed in based on user settings.
1229  */
1230 static int
1231 condcolor(bm, bmarray)
1232 long bm;
1233 unsigned long *bmarray;
1234 {
1235     int i;
1236
1237     if (bm && bmarray)
1238         for (i = 0; i < CLR_MAX; ++i) {
1239             if ((bmarray[i] & bm) != 0)
1240                 return i;
1241         }
1242     return NO_COLOR;
1243 }
1244 #endif /* TEXTCOLOR */
1245
1246 static int
1247 condattr(bm, bmarray)
1248 long bm;
1249 unsigned long *bmarray;
1250 {
1251     int i, attr = 0;
1252
1253     if (bm && bmarray) {
1254         for (i = HL_ATTCLR_DIM; i < BL_ATTCLR_MAX; ++i) {
1255             if ((bmarray[i] & bm) != 0) {
1256                 switch (i) {
1257                 case HL_ATTCLR_DIM:
1258                     attr |= HL_DIM;
1259                     break;
1260                 case HL_ATTCLR_BLINK:
1261                     attr |= HL_BLINK;
1262                     break;
1263                 case HL_ATTCLR_ULINE:
1264                     attr |= HL_ULINE;
1265                     break;
1266                 case HL_ATTCLR_INVERSE:
1267                     attr |= HL_INVERSE;
1268                     break;
1269                 case HL_ATTCLR_BOLD:
1270                     attr |= HL_BOLD;
1271                     break;
1272                 default:
1273                     break;
1274                 }
1275             }
1276         }
1277     }
1278     return attr;
1279 }
1280 /* convert tty attributes to curses attributes;
1281    despite similar names, the mask fields have different values */
1282 static int
1283 nhattr2curses(attrmask)
1284 int attrmask;
1285 {
1286     int result = 0;
1287
1288     if (attrmask & HL_BOLD)
1289         result |= A_BOLD;
1290     if (attrmask & HL_INVERSE)
1291         result |= A_REVERSE;
1292     if (attrmask & HL_ULINE)
1293         result |= A_UNDERLINE;
1294     if (attrmask & HL_BLINK)
1295         result |= A_BLINK;
1296     if (attrmask & HL_DIM)
1297         result |= A_DIM;
1298
1299     return result;
1300 }
1301 #endif /* STATUS_HILITES */
1302
1303 /* ======================================================================== */
1304
1305
1306 #if 0 /* old stuff; some may be re-incorporated, most should be discarded */
1307 /* Private declarations */
1308
1309 /* Used to track previous value of things, to highlight changes. */
1310 typedef struct nhs {
1311     long value;
1312     int highlight_turns;
1313     int highlight_color;
1314 } nhstat;
1315
1316 static attr_t get_trouble_color(const char *);
1317 static void draw_trouble_str(const char *);
1318 static void print_statdiff(const char *append, nhstat *, long, int);
1319 static void get_playerrank(char *);
1320 static int hpen_color(boolean, int, int);
1321 static void draw_bar(boolean, int, int, const char *);
1322 static void draw_horizontal(int, int, int, int);
1323 static void draw_horizontal_new(int, int, int, int);
1324 static void draw_vertical(int, int, int, int);
1325 static void curses_add_statuses(WINDOW *, boolean, boolean, int *, int *);
1326 static void curses_add_status(WINDOW *, boolean, boolean, int *, int *,
1327                               const char *, int);
1328 static int decrement_highlight(nhstat *, boolean);
1329
1330 #ifdef STATUS_COLORS
1331 static attr_t hpen_color_attr(boolean, int, int);
1332 extern struct color_option text_color_of(const char *text,
1333                                          const struct text_color_option *color_options);
1334 struct color_option percentage_color_of(int value, int max,
1335                                         const struct percent_color_option *color_options);
1336
1337 extern const struct text_color_option *text_colors;
1338 extern const struct percent_color_option *hp_colors;
1339 extern const struct percent_color_option *pw_colors;
1340 #endif
1341
1342 /* Whether or not we have printed status window content at least once.
1343    Used to ensure that prev* doesn't end up highlighted on game start. */
1344 static boolean first = TRUE;
1345 static nhstat prevdepth;
1346 static nhstat prevstr;
1347 static nhstat prevint;
1348 static nhstat prevwis;
1349 static nhstat prevdex;
1350 static nhstat prevcon;
1351 static nhstat prevcha;
1352 static nhstat prevau;
1353 static nhstat prevlevel;
1354 static nhstat prevac;
1355 static nhstat prevexp;
1356 static nhstat prevtime;
1357
1358 #ifdef SCORE_ON_BOTL
1359 static nhstat prevscore;
1360 #endif
1361
1362 extern const char *hu_stat[];   /* from eat.c */
1363 extern const char *enc_stat[];  /* from botl.c */
1364
1365 /* If the statuscolors patch isn't enabled, have some default colors for status problems
1366    anyway */
1367
1368 struct statcolor {
1369     const char *txt; /* For status problems */
1370     int color; /* Default color assuming STATUS_COLORS isn't enabled */
1371 };
1372
1373 static const struct statcolor default_colors[] = {
1374     {"Satiated", CLR_YELLOW},
1375     {"Hungry", CLR_YELLOW},
1376     {"Weak", CLR_ORANGE},
1377     {"Fainted", CLR_BRIGHT_MAGENTA},
1378     {"Fainting", CLR_BRIGHT_MAGENTA},
1379     {"Burdened", CLR_RED},
1380     {"Stressed", CLR_RED},
1381     {"Strained", CLR_ORANGE},
1382     {"Overtaxed", CLR_ORANGE},
1383     {"Overloaded", CLR_BRIGHT_MAGENTA},
1384     {"Conf", CLR_BRIGHT_BLUE},
1385     {"Blind", CLR_BRIGHT_BLUE},
1386     {"Stun", CLR_BRIGHT_BLUE},
1387     {"Hallu", CLR_BRIGHT_BLUE},
1388     {"Ill", CLR_BRIGHT_MAGENTA},
1389     {"FoodPois", CLR_BRIGHT_MAGENTA},
1390     {"Slime", CLR_BRIGHT_MAGENTA},
1391     {NULL, NULL, NO_COLOR},
1392 };
1393
1394 static attr_t
1395 get_trouble_color(const char *stat)
1396 {
1397     attr_t res = curses_color_attr(CLR_GRAY, 0);
1398     const struct statcolor *clr;
1399     for (clr = default_colors; clr->txt; clr++) {
1400         if (stat && !strcmp(clr->txt, stat)) {
1401 #ifdef STATUS_COLORS
1402             /* Check if we have a color enabled with statuscolors */
1403             if (!iflags.use_status_colors)
1404                 return curses_color_attr(CLR_GRAY, 0); /* no color configured */
1405
1406             struct color_option stat_color;
1407
1408             stat_color = text_color_of(clr->txt, text_colors);
1409             if (stat_color.color == NO_COLOR && !stat_color.attr_bits)
1410                 return curses_color_attr(CLR_GRAY, 0);
1411
1412             if (stat_color.color != NO_COLOR)
1413                 res = curses_color_attr(stat_color.color, 0);
1414
1415             res = curses_color_attr(stat_color.color, 0);
1416             int count;
1417             for (count = 0; (1 << count) <= stat_color.attr_bits; count++) {
1418                 if (count != ATR_NONE &&
1419                     (stat_color.attr_bits & (1 << count)))
1420                     res |= curses_convert_attr(count);
1421             }
1422
1423             return res;
1424 #else
1425             return curses_color_attr(clr->color, 0);
1426 #endif
1427         }
1428     }
1429
1430     return res;
1431 }
1432
1433 /* TODO: This is in the wrong place. */
1434 void
1435 get_playerrank(char *rank)
1436 {
1437     char buf[BUFSZ];
1438     if (Upolyd) {
1439         int k = 0;
1440
1441         Strcpy(buf, mons[u.umonnum].mname);
1442         while(buf[k] != 0) {
1443             if ((k == 0 || (k > 0 && buf[k-1] == ' ')) &&
1444                 'a' <= buf[k] && buf[k] <= 'z')
1445                 buf[k] += 'A' - 'a';
1446             k++;
1447         }
1448         Strcpy(rank, buf);
1449     } else
1450         Strcpy(rank, rank_of(u.ulevel, Role_switch, flags.female));
1451 }
1452
1453 /* Handles numerical stat changes of various kinds.
1454    type is generally STAT_OTHER (generic "do nothing special"),
1455    but is used if the stat needs to be handled in a special way. */
1456 static void
1457 print_statdiff(const char *append, nhstat *stat, long new, int type)
1458 {
1459     char buf[BUFSZ];
1460     WINDOW *win = curses_get_nhwin(STATUS_WIN);
1461
1462     int color = CLR_GRAY;
1463
1464     /* Turncount isn't highlighted, or it would be highlighted constantly. */
1465     if (type != STAT_TIME && new != stat->value) {
1466         /* Less AC is better */
1467         if ((type == STAT_AC && new < stat->value) ||
1468             (type != STAT_AC && new > stat->value)) {
1469             color = STAT_UP_COLOR;
1470             if (type == STAT_GOLD)
1471                 color = HI_GOLD;
1472         } else
1473             color = STAT_DOWN_COLOR;
1474
1475         stat->value = new;
1476         stat->highlight_color = color;
1477         stat->highlight_turns = 5;
1478     } else if (stat->highlight_turns)
1479         color = stat->highlight_color;
1480
1481     attr_t attr = curses_color_attr(color, 0);
1482     wattron(win, attr);
1483     wprintw(win, "%s", append);
1484     if (type == STAT_STR && new > 18) {
1485         if (new > 118)
1486             wprintw(win, "%d", new - 100);
1487         else if (new == 118)
1488             wprintw(win, "18/**");
1489         else
1490             wprintw(win, "18/%02d", new - 18);
1491     } else
1492         wprintw(win, "%d", new);
1493
1494     wattroff(win, attr);
1495 }
1496
1497 static void
1498 draw_trouble_str(const char *str)
1499 {
1500     WINDOW *win = curses_get_nhwin(STATUS_WIN);
1501
1502     attr_t attr = get_trouble_color(str);
1503     wattron(win, attr);
1504     wprintw(win, "%s", str);
1505     wattroff(win, attr);
1506 }
1507
1508 /* Returns a ncurses attribute for foreground and background.
1509    This should probably be in cursinit.c or something. */
1510 attr_t
1511 curses_color_attr(int nh_color, int bg_color)
1512 {
1513     int color = nh_color + 1;
1514     attr_t cattr = A_NORMAL;
1515
1516     if (!nh_color) {
1517 #ifdef USE_DARKGRAY
1518         if (iflags.wc2_darkgray) {
1519             if (!can_change_color() || COLORS <= 16)
1520                 cattr |= A_BOLD;
1521         } else
1522 #endif
1523             color = COLOR_BLUE;
1524     }
1525
1526     if (COLORS < 16 && color > 8) {
1527         color -= 8;
1528         cattr = A_BOLD;
1529     }
1530
1531     /* Can we do background colors? We can if we have more than
1532        16*7 colors (more than 8*7 for terminals with bold) */
1533     if (COLOR_PAIRS > (COLORS >= 16 ? 16 : 8) * 7) {
1534         /* NH3 has a rather overcomplicated way of defining
1535            its colors past the first 16:
1536            Pair    Foreground  Background
1537            17      Black       Red
1538            18      Black       Blue
1539            19      Red         Red
1540            20      Red         Blue
1541            21      Green       Red
1542            ...
1543            (Foreground order: Black, Red, Green, Yellow, Blue,
1544            Magenta, Cyan, Gray/White)
1545
1546            To work around these oddities, we define backgrounds
1547            by the following pairs:
1548
1549            16 COLORS
1550            49-64: Green
1551            65-80: Yellow
1552            81-96: Magenta
1553            97-112: Cyan
1554            113-128: Gray/White
1555
1556            8 COLORS
1557            9-16: Green
1558            33-40: Yellow
1559            41-48: Magenta
1560            49-56: Cyan
1561            57-64: Gray/White */
1562
1563         if (bg_color == nh_color)
1564             color = 1; /* Make foreground black if fg==bg */
1565
1566         if (bg_color == CLR_RED || bg_color == CLR_BLUE) {
1567             /* already defined before extension */
1568             color *= 2;
1569             color += 16;
1570             if (bg_color == CLR_RED)
1571                 color--;
1572         } else {
1573             boolean hicolor = FALSE;
1574             if (COLORS >= 16)
1575                 hicolor = TRUE;
1576
1577             switch (bg_color) {
1578             case CLR_GREEN:
1579                 color = (hicolor ? 48 : 8) + color;
1580                 break;
1581             case CLR_BROWN:
1582                 color = (hicolor ? 64 : 32) + color;
1583                 break;
1584             case CLR_MAGENTA:
1585                 color = (hicolor ? 80 : 40) + color;
1586                 break;
1587             case CLR_CYAN:
1588                 color = (hicolor ? 96 : 48) + color;
1589                 break;
1590             case CLR_GRAY:
1591                 color = (hicolor ? 112 : 56) + color;
1592                 break;
1593             default:
1594                 break;
1595             }
1596         }
1597     }
1598     cattr |= COLOR_PAIR(color);
1599
1600     return cattr;
1601 }
1602
1603 /* Returns a complete curses attribute. Used to possibly bold/underline/etc HP/Pw. */
1604 #ifdef STATUS_COLORS
1605 static attr_t
1606 hpen_color_attr(boolean is_hp, int cur, int max)
1607 {
1608     struct color_option stat_color;
1609     int count;
1610     attr_t attr = 0;
1611     if (!iflags.use_status_colors)
1612         return curses_color_attr(CLR_GRAY, 0);
1613
1614     stat_color = percentage_color_of(cur, max, is_hp ? hp_colors : pw_colors);
1615
1616     if (stat_color.color != NO_COLOR)
1617         attr |= curses_color_attr(stat_color.color, 0);
1618
1619     for (count = 0; (1 << count) <= stat_color.attr_bits; count++) {
1620         if (count != ATR_NONE && (stat_color.attr_bits & (1 << count)))
1621             attr |= curses_convert_attr(count);
1622     }
1623
1624     return attr;
1625 }
1626 #endif
1627
1628 /* Return color for the HP bar.
1629    With status colors ON, this respect its configuration (defaulting to gray), but
1630    only obeys the color (no weird attributes for the HP bar).
1631    With status colors OFF, this returns reasonable defaults which are also used
1632    for the HP/Pw text itself. */
1633 static int
1634 hpen_color(boolean is_hp, int cur, int max)
1635 {
1636 #ifdef STATUS_COLORS
1637     if (iflags.use_status_colors) {
1638         struct color_option stat_color;
1639         stat_color = percentage_color_of(cur, max, is_hp ? hp_colors : pw_colors);
1640
1641         if (stat_color.color == NO_COLOR)
1642             return CLR_GRAY;
1643         else
1644             return stat_color.color;
1645     } else
1646         return CLR_GRAY;
1647 #endif
1648
1649     int color = CLR_GRAY;
1650     if (cur == max)
1651         color = CLR_GRAY;
1652     else if (cur * 3 > max * 2) /* >2/3 */
1653         color = is_hp ? CLR_GREEN : CLR_CYAN;
1654     else if (cur * 3 > max) /* >1/3 */
1655         color = is_hp ? CLR_YELLOW : CLR_BLUE;
1656     else if (cur * 7 > max) /* >1/7 */
1657         color = is_hp ? CLR_RED : CLR_MAGENTA;
1658     else
1659         color = is_hp ? CLR_ORANGE : CLR_BRIGHT_MAGENTA;
1660
1661     return color;
1662 }
1663
1664 /* Draws a bar
1665    is_hp: TRUE if we're drawing HP, Pw otherwise (determines colors)
1666    cur/max: Current/max HP/Pw
1667    title: Not NULL if we are drawing as part of an existing title.
1668    Otherwise, the format is as follows: [   11 / 11   ] */
1669 static void
1670 draw_bar(boolean is_hp, int cur, int max, const char *title)
1671 {
1672     WINDOW *win = curses_get_nhwin(STATUS_WIN);
1673
1674 #ifdef STATUS_COLORS
1675     if (!iflags.hitpointbar) {
1676         wprintw(win, "%s", !title ? "---" : title);
1677         return;
1678     }
1679 #endif
1680
1681     char buf[BUFSZ];
1682     if (title)
1683         Strcpy(buf, title);
1684     else {
1685         int len = 5;
1686         sprintf(buf, "%*d / %-*d", len, cur, len, max);
1687     }
1688
1689     /* Colors */
1690     attr_t fillattr, attr;
1691     int color = hpen_color(is_hp, cur, max);
1692     int invcolor = color & 7;
1693
1694     fillattr = curses_color_attr(color, invcolor);
1695     attr = curses_color_attr(color, 0);
1696
1697     /* Figure out how much of the bar to fill */
1698     int fill = 0;
1699     int len = strlen(buf);
1700     if (cur > 0 && max > 0)
1701         fill = len * cur / max;
1702     if (fill > len)
1703         fill = len;
1704
1705     waddch(win, '[');
1706     wattron(win, fillattr);
1707     wprintw(win, "%.*s", fill, buf);
1708     wattroff(win, fillattr);
1709     wattron(win, attr);
1710     wprintw(win, "%.*s", len - fill, &buf[fill]);
1711     wattroff(win, attr);
1712     waddch(win, ']');
1713 }
1714
1715 /* Update the status win - this is called when NetHack would normally
1716    write to the status window, so we know somwthing has changed.  We
1717    override the write and update what needs to be updated ourselves. */
1718 void
1719 curses_update_stats(void)
1720 {
1721     WINDOW *win = curses_get_nhwin(STATUS_WIN);
1722
1723     /* Clear the window */
1724     werase(win);
1725
1726     int orient = curses_get_window_orientation(STATUS_WIN);
1727
1728     boolean horiz = FALSE;
1729     if ((orient != ALIGN_RIGHT) && (orient != ALIGN_LEFT))
1730         horiz = TRUE;
1731     boolean border = curses_window_has_border(STATUS_WIN);
1732
1733     /* Figure out if we have proper window dimensions for horizontal statusbar. */
1734     if (horiz) {
1735         /* correct y */
1736         int cy = 3;
1737         if (iflags.statuslines < 3)
1738             cy = 2;
1739
1740         /* actual y (and x) */
1741         int ax = 0;
1742         int ay = 0;
1743         getmaxyx(win, ay, ax);
1744         if (border)
1745             ay -= 2;
1746
1747         if (cy != ay) {
1748             curses_create_main_windows();
1749             curses_last_messages();
1750             doredraw();
1751
1752             /* Reset XP highlight (since classic_status and new show different numbers) */
1753             prevexp.highlight_turns = 0;
1754             curses_update_stats();
1755             return;
1756         }
1757     }
1758
1759     /* Starting x/y. Passed to draw_horizontal/draw_vertical to keep track of
1760        window positioning. */
1761     int x = 0;
1762     int y = 0;
1763
1764     /* Don't start at border position if applicable */
1765     if (border) {
1766         x++;
1767         y++;
1768     }
1769
1770     /* Get HP values. */
1771     int hp = u.uhp;
1772     int hpmax = u.uhpmax;
1773     if (Upolyd) {
1774         hp = u.mh;
1775         hpmax = u.mhmax;
1776     }
1777
1778     if (orient != ALIGN_RIGHT && orient != ALIGN_LEFT)
1779         draw_horizontal(x, y, hp, hpmax);
1780     else
1781         draw_vertical(x, y, hp, hpmax);
1782
1783     if (border)
1784         box(win, 0, 0);
1785
1786     wnoutrefresh(win);
1787
1788     if (first) {
1789         first = FALSE;
1790
1791         /* Zero highlight timers. This will call curses_update_status again if needed */
1792         curses_decrement_highlights(TRUE);
1793     }
1794 }
1795
1796 static void
1797 draw_horizontal(int x, int y, int hp, int hpmax)
1798 {
1799     if (iflags.statuslines >= 3) {
1800         /* Draw new-style statusbar */
1801         draw_horizontal_new(x, y, hp, hpmax);
1802         return;
1803     }
1804     char buf[BUFSZ];
1805     char rank[BUFSZ];
1806     WINDOW *win = curses_get_nhwin(STATUS_WIN);
1807
1808     /* Line 1 */
1809     wmove(win, y, x);
1810
1811     get_playerrank(rank);
1812     sprintf(buf, "%s the %s", plname, rank);
1813
1814     /* Use the title as HP bar (similar to hitpointbar) */
1815     draw_bar(TRUE, hp, hpmax, buf);
1816
1817     /* Attributes */
1818     print_statdiff(" St:", &prevstr, ACURR(A_STR), STAT_STR);
1819     print_statdiff(" Dx:", &prevdex, ACURR(A_DEX), STAT_OTHER);
1820     print_statdiff(" Co:", &prevcon, ACURR(A_CON), STAT_OTHER);
1821     print_statdiff(" In:", &prevint, ACURR(A_INT), STAT_OTHER);
1822     print_statdiff(" Wi:", &prevwis, ACURR(A_WIS), STAT_OTHER);
1823     print_statdiff(" Ch:", &prevcha, ACURR(A_CHA), STAT_OTHER);
1824
1825     wprintw(win, (u.ualign.type == A_CHAOTIC ? " Chaotic" :
1826                   u.ualign.type == A_NEUTRAL ? " Neutral" : " Lawful"));
1827
1828 #ifdef SCORE_ON_BOTL
1829     if (flags.showscore)
1830         print_statdiff(" S:", &prevscore, botl_score(), STAT_OTHER);
1831 #endif /* SCORE_ON_BOTL */
1832
1833
1834     /* Line 2 */
1835     y++;
1836     wmove(win, y, x);
1837
1838     describe_level(buf);
1839
1840     wprintw(win, "%s", buf);
1841
1842     print_statdiff("$", &prevau, money_cnt(invent), STAT_GOLD);
1843
1844     /* HP/Pw use special coloring rules */
1845     attr_t hpattr, pwattr;
1846 #ifdef STATUS_COLORS
1847     hpattr = hpen_color_attr(TRUE, hp, hpmax);
1848     pwattr = hpen_color_attr(FALSE, u.uen, u.uenmax);
1849 #else
1850     int hpcolor, pwcolor;
1851     hpcolor = hpen_color(TRUE, hp, hpmax);
1852     pwcolor = hpen_color(FALSE, u.uen, u.uenmax);
1853     hpattr = curses_color_attr(hpcolor, 0);
1854     pwattr = curses_color_attr(pwcolor, 0);
1855 #endif
1856     wprintw(win, " HP:");
1857     wattron(win, hpattr);
1858     wprintw(win, "%d(%d)", (hp < 0) ? 0 : hp, hpmax);
1859     wattroff(win, hpattr);
1860
1861     wprintw(win, " Pw:");
1862     wattron(win, pwattr);
1863     wprintw(win, "%d(%d)", u.uen, u.uenmax);
1864     wattroff(win, pwattr);
1865
1866     print_statdiff(" AC:", &prevac, u.uac, STAT_AC);
1867
1868     if (Upolyd)
1869         print_statdiff(" HD:", &prevlevel, mons[u.umonnum].mlevel, STAT_OTHER);
1870     else if (flags.showexp) {
1871         print_statdiff(" Xp:", &prevlevel, u.ulevel, STAT_OTHER);
1872         /* use waddch, we don't want to highlight the '/' */
1873         waddch(win, '/');
1874         print_statdiff("", &prevexp, u.uexp, STAT_OTHER);
1875     }
1876     else
1877         print_statdiff(" Exp:", &prevlevel, u.ulevel, STAT_OTHER);
1878
1879     if (flags.time)
1880         print_statdiff(" T:", &prevtime, moves, STAT_TIME);
1881
1882     curses_add_statuses(win, FALSE, FALSE, NULL, NULL);
1883 }
1884
1885 static void
1886 draw_horizontal_new(int x, int y, int hp, int hpmax)
1887 {
1888     char buf[BUFSZ];
1889     char rank[BUFSZ];
1890     WINDOW *win = curses_get_nhwin(STATUS_WIN);
1891
1892     /* Line 1 */
1893     wmove(win, y, x);
1894
1895     get_playerrank(rank);
1896     char race[BUFSZ];
1897     Strcpy(race, urace.adj);
1898     race[0] = highc(race[0]);
1899     wprintw(win, "%s the %s %s%s%s", plname,
1900             (u.ualign.type == A_CHAOTIC ? "Chaotic" :
1901              u.ualign.type == A_NEUTRAL ? "Neutral" : "Lawful"),
1902             Upolyd ? "" : race, Upolyd ? "" : " ",
1903             rank);
1904
1905     /* Line 2 */
1906     y++;
1907     wmove(win, y, x);
1908     wprintw(win, "HP:");
1909     draw_bar(TRUE, hp, hpmax, NULL);
1910     print_statdiff(" AC:", &prevac, u.uac, STAT_AC);
1911     if (Upolyd)
1912         print_statdiff(" HD:", &prevlevel, mons[u.umonnum].mlevel, STAT_OTHER);
1913     else if (flags.showexp) {
1914         /* Ensure that Xp have proper highlight on level change. */
1915         int levelchange = 0;
1916         if (prevlevel.value != u.ulevel) {
1917             if (prevlevel.value < u.ulevel)
1918                 levelchange = 1;
1919             else
1920                 levelchange = 2;
1921         }
1922         print_statdiff(" Xp:", &prevlevel, u.ulevel, STAT_OTHER);
1923         /* use waddch, we don't want to highlight the '/' */
1924         waddch(win, '(');
1925
1926         /* Figure out amount of Xp needed to next level */
1927         int xp_left = 0;
1928         if (u.ulevel < 30)
1929             xp_left = (newuexp(u.ulevel) - u.uexp);
1930
1931         if (levelchange) {
1932             prevexp.value = (xp_left + 1);
1933             if (levelchange == 2)
1934                 prevexp.value = (xp_left - 1);
1935         }
1936         print_statdiff("", &prevexp, xp_left, STAT_AC);
1937         waddch(win, ')');
1938     }
1939     else
1940         print_statdiff(" Exp:", &prevlevel, u.ulevel, STAT_OTHER);
1941
1942     waddch(win, ' ');
1943     describe_level(buf);
1944
1945     wprintw(win, "%s", buf);
1946
1947     /* Line 3 */
1948     y++;
1949     wmove(win, y, x);
1950     wprintw(win, "Pw:");
1951     draw_bar(FALSE, u.uen, u.uenmax, NULL);
1952
1953     print_statdiff(" $", &prevau, money_cnt(invent), STAT_GOLD);
1954
1955 #ifdef SCORE_ON_BOTL
1956     if (flags.showscore)
1957         print_statdiff(" S:", &prevscore, botl_score(), STAT_OTHER);
1958 #endif /* SCORE_ON_BOTL */
1959
1960     if (flags.time)
1961         print_statdiff(" T:", &prevtime, moves, STAT_TIME);
1962
1963     curses_add_statuses(win, TRUE, FALSE, &x, &y);
1964
1965     /* Right-aligned attributes */
1966     int stat_length = 6; /* " Dx:xx" */
1967     int str_length = 6;
1968     if (ACURR(A_STR) > 18 && ACURR(A_STR) < 119)
1969         str_length = 9;
1970
1971     getmaxyx(win, y, x);
1972
1973     /* We want to deal with top line of y. getmaxx would do what we want, but it only
1974        exist for compatibility reasons and might not exist at all in some versions. */
1975     y = 0;
1976     if (curses_window_has_border(STATUS_WIN)) {
1977         x--;
1978         y++;
1979     }
1980
1981     x -= stat_length;
1982     int orig_x = x;
1983     wmove(win, y, x);
1984     print_statdiff(" Co:", &prevcon, ACURR(A_CON), STAT_OTHER);
1985     x -= stat_length;
1986     wmove(win, y, x);
1987     print_statdiff(" Dx:", &prevdex, ACURR(A_DEX), STAT_OTHER);
1988     x -= str_length;
1989     wmove(win, y, x);
1990     print_statdiff(" St:", &prevstr, ACURR(A_STR), STAT_STR);
1991
1992     x = orig_x;
1993     y++;
1994     wmove(win, y, x);
1995     print_statdiff(" Ch:", &prevcha, ACURR(A_CHA), STAT_OTHER);
1996     x -= stat_length;
1997     wmove(win, y, x);
1998     print_statdiff(" Wi:", &prevwis, ACURR(A_WIS), STAT_OTHER);
1999     x -= str_length;
2000     wmove(win, y, x);
2001     print_statdiff(" In:", &prevint, ACURR(A_INT), STAT_OTHER);
2002 }
2003
2004 /* Personally I never understood the point of a vertical status bar. But removing the
2005    option would be silly, so keep the functionality. */
2006 static void
2007 draw_vertical(int x, int y, int hp, int hpmax)
2008 {
2009     char buf[BUFSZ];
2010     char rank[BUFSZ];
2011     WINDOW *win = curses_get_nhwin(STATUS_WIN);
2012
2013     /* Print title and dungeon branch */
2014     wmove(win, y++, x);
2015
2016     get_playerrank(rank);
2017     int ranklen = strlen(rank);
2018     int namelen = strlen(plname);
2019     int maxlen = 19;
2020 #ifdef STATUS_COLORS
2021     if (!iflags.hitpointbar)
2022         maxlen += 2; /* With no hitpointbar, we can fit more since there's no "[]" */
2023 #endif
2024
2025     if ((ranklen + namelen) > maxlen) {
2026         /* The result doesn't fit. Strip name if >10 characters, then strip title */
2027         if (namelen > 10) {
2028             while (namelen > 10 && (ranklen + namelen) > maxlen)
2029                 namelen--;
2030         }
2031
2032         while ((ranklen + namelen) > maxlen)
2033             ranklen--; /* Still doesn't fit, strip rank */
2034     }
2035     sprintf(buf, "%-*s the %-*s", namelen, plname, ranklen, rank);
2036     draw_bar(TRUE, hp, hpmax, buf);
2037     wmove(win, y++, x);
2038     wprintw(win, "%s", dungeons[u.uz.dnum].dname);
2039
2040     y++; /* Blank line inbetween */
2041     wmove(win, y++, x);
2042
2043     /* Attributes. Old  vertical order is preserved */
2044     print_statdiff("Strength:      ", &prevstr, ACURR(A_STR), STAT_STR);
2045     wmove(win, y++, x);
2046     print_statdiff("Intelligence:  ", &prevint, ACURR(A_INT), STAT_OTHER);
2047     wmove(win, y++, x);
2048     print_statdiff("Wisdom:        ", &prevwis, ACURR(A_WIS), STAT_OTHER);
2049     wmove(win, y++, x);
2050     print_statdiff("Dexterity:     ", &prevdex, ACURR(A_DEX), STAT_OTHER);
2051     wmove(win, y++, x);
2052     print_statdiff("Constitution:  ", &prevcon, ACURR(A_CON), STAT_OTHER);
2053     wmove(win, y++, x);
2054     print_statdiff("Charisma:      ", &prevcha, ACURR(A_CHA), STAT_OTHER);
2055     wmove(win, y++, x);
2056     wprintw(win,   "Alignment:     ");
2057     wprintw(win, (u.ualign.type == A_CHAOTIC ? "Chaotic" :
2058                   u.ualign.type == A_NEUTRAL ? "Neutral" : "Lawful"));
2059     wmove(win, y++, x);
2060     wprintw(win,   "Dungeon Level: ");
2061
2062     /* Astral Plane doesn't fit */
2063     if (In_endgame(&u.uz))
2064         wprintw(win, "%s", Is_astralevel(&u.uz) ? "Astral" : "End Game");
2065     else
2066         wprintw(win, "%d", depth(&u.uz));
2067     wmove(win, y++, x);
2068
2069     print_statdiff("Gold:          ", &prevau, money_cnt(invent), STAT_GOLD);
2070     wmove(win, y++, x);
2071
2072     /* HP/Pw use special coloring rules */
2073     attr_t hpattr, pwattr;
2074 #ifdef STATUS_COLORS
2075     hpattr = hpen_color_attr(TRUE, hp, hpmax);
2076     pwattr = hpen_color_attr(FALSE, u.uen, u.uenmax);
2077 #else
2078     int hpcolor, pwcolor;
2079     hpcolor = hpen_color(TRUE, hp, hpmax);
2080     pwcolor = hpen_color(FALSE, u.uen, u.uenmax);
2081     hpattr = curses_color_attr(hpcolor, 0);
2082     pwattr = curses_color_attr(pwcolor, 0);
2083 #endif
2084
2085     wprintw(win,   "Hit Points:    ");
2086     wattron(win, hpattr);
2087     wprintw(win, "%d/%d", (hp < 0) ? 0 : hp, hpmax);
2088     wattroff(win, hpattr);
2089     wmove(win, y++, x);
2090
2091     wprintw(win,   "Magic Power:   ");
2092     wattron(win, pwattr);
2093     wprintw(win, "%d/%d", u.uen, u.uenmax);
2094     wattroff(win, pwattr);
2095     wmove(win, y++, x);
2096
2097     print_statdiff("Armor Class:   ", &prevac, u.uac, STAT_AC);
2098     wmove(win, y++, x);
2099
2100     if (Upolyd)
2101         print_statdiff("Hit Dice:      ", &prevlevel, mons[u.umonnum].mlevel, STAT_OTHER);
2102     else if (flags.showexp) {
2103         print_statdiff("Experience:    ", &prevlevel, u.ulevel, STAT_OTHER);
2104         /* use waddch, we don't want to highlight the '/' */
2105         waddch(win, '/');
2106         print_statdiff("", &prevexp, u.uexp, STAT_OTHER);
2107     }
2108     else
2109         print_statdiff("Level:         ", &prevlevel, u.ulevel, STAT_OTHER);
2110     wmove(win, y++, x);
2111
2112     if (flags.time) {
2113         print_statdiff("Time:          ", &prevtime, moves, STAT_TIME);
2114         wmove(win, y++, x);
2115     }
2116
2117 #ifdef SCORE_ON_BOTL
2118     if (flags.showscore) {
2119         print_statdiff("Score:         ", &prevscore, botl_score(), STAT_OTHER);
2120         wmove(win, y++, x);
2121     }
2122 #endif /* SCORE_ON_BOTL */
2123
2124     curses_add_statuses(win, FALSE, TRUE, &x, &y);
2125 }
2126
2127 static void
2128 curses_add_statuses(WINDOW *win, boolean align_right,
2129                     boolean vertical, int *x, int *y)
2130 {
2131     if (align_right) {
2132         /* Right-aligned statuses. Since add_status decrease one x more
2133            (to separate them with spaces), add 1 to x unless we have borders
2134            (which would offset what add_status does) */
2135         int mx = *x;
2136         int my = *y;
2137         getmaxyx(win, my, mx);
2138         if (!curses_window_has_border(STATUS_WIN))
2139             mx++;
2140
2141         *x = mx;
2142     }
2143
2144 #define statprob(str, trouble)                                  \
2145     curses_add_status(win, align_right, vertical, x, y, str, trouble)
2146
2147     /* Hunger */
2148     statprob(hu_stat[u.uhs], u.uhs != 1); /* 1 is NOT_HUNGRY (not defined here) */
2149
2150     /* General troubles */
2151     statprob("Conf",     Confusion);
2152     statprob("Blind",    Blind);
2153     statprob("Stun",     Stunned);
2154     statprob("Hallu",    Hallucination);
2155     statprob("Ill",      (u.usick_type & SICK_NONVOMITABLE));
2156     statprob("FoodPois", (u.usick_type & SICK_VOMITABLE));
2157     statprob("Slime",    Slimed);
2158
2159     /* Encumbrance */
2160     int enc = near_capacity();
2161     statprob(enc_stat[enc], enc > UNENCUMBERED);
2162 #undef statprob
2163 }
2164
2165 static void
2166 curses_add_status(WINDOW *win, boolean align_right, boolean vertical,
2167                   int *x, int *y, const char *str, int trouble)
2168 {
2169     /* If vertical is TRUE here with no x/y, that's an error. But handle
2170        it gracefully since NH3 doesn't recover well in crashes. */
2171     if (!x || !y)
2172         vertical = FALSE;
2173
2174     if (!trouble)
2175         return;
2176
2177     if (!vertical && !align_right)
2178         waddch(win, ' ');
2179
2180     /* For whatever reason, hunger states have trailing spaces. Get rid of them. */
2181     char buf[BUFSZ];
2182     Strcpy(buf, str);
2183     int i;
2184     for (i = 0; (buf[i] != ' ' && buf[i] != '\0'); i++) ;
2185
2186     buf[i] = '\0';
2187     if (align_right) {
2188         *x -= (strlen(buf) + 1); /* add spacing */
2189         wmove(win, *y, *x);
2190     }
2191
2192     draw_trouble_str(buf);
2193
2194     if (vertical) {
2195         wmove(win, *y, *x);
2196         *y += 1; /* ++ advances the pointer addr */
2197     }
2198 }
2199
2200 /* Decrement a single highlight, return 1 if decremented to zero. zero is TRUE if we're
2201    zeroing the highlight. */
2202 static int
2203 decrement_highlight(nhstat *stat, boolean zero)
2204 {
2205     if (stat->highlight_turns > 0) {
2206         if (zero) {
2207             stat->highlight_turns = 0;
2208             return 1;
2209         }
2210
2211         stat->highlight_turns--;
2212         if (stat->highlight_turns == 0)
2213             return 1;
2214     }
2215     return 0;
2216 }
2217
2218 /* Decrement the highlight_turns for all stats.  Call curses_update_stats
2219    if needed to unhighlight a stat */
2220 void
2221 curses_decrement_highlights(boolean zero)
2222 {
2223     int unhighlight = 0;
2224
2225     unhighlight |= decrement_highlight(&prevdepth, zero);
2226     unhighlight |= decrement_highlight(&prevstr, zero);
2227     unhighlight |= decrement_highlight(&prevdex, zero);
2228     unhighlight |= decrement_highlight(&prevcon, zero);
2229     unhighlight |= decrement_highlight(&prevint, zero);
2230     unhighlight |= decrement_highlight(&prevwis, zero);
2231     unhighlight |= decrement_highlight(&prevcha, zero);
2232     unhighlight |= decrement_highlight(&prevau, zero);
2233     unhighlight |= decrement_highlight(&prevlevel, zero);
2234     unhighlight |= decrement_highlight(&prevac, zero);
2235     unhighlight |= decrement_highlight(&prevexp, zero);
2236     unhighlight |= decrement_highlight(&prevtime, zero);
2237 #ifdef SCORE_ON_BOTL
2238     unhighlight |= decrement_highlight(&prevscore, zero);
2239 #endif
2240
2241     if (unhighlight)
2242         curses_update_stats();
2243 }
2244 #endif /*0*/
2245
2246 /*cursstat.c*/