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. */
11 /* Status window functions for curses interface */
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.
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];
24 /* Long format fields for vertical window */
25 static char *status_vals_long[MAXBLSTATS];
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;
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);
41 static int FDECL(condcolor, (long, unsigned long *));
43 static int FDECL(condattr, (long, unsigned long *));
44 static int FDECL(nhattr2curses, (int));
45 #endif /* STATUS_HILITES */
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 */
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';
61 curses_condition_bits = 0L;
62 hpbar_percent = 0, hpbar_color = NO_COLOR;
63 vert_status_dirty = 1;
65 /* let genl_status_init do most of the initialization */
71 curses_status_finish()
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;
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
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
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.
137 static int changed_fields = 0;
140 curses_status_update(fldidx, ptr, chg, percent, color_and_attr, colormasks)
141 int fldidx, chg UNUSED,
142 percent, color_and_attr;
144 unsigned long *colormasks;
146 long *condptr = (long *) ptr;
147 char *text = (char *) ptr;
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);
154 changed_fields |= (1 << fldidx);
155 *status_vals[fldidx] = '\0';
156 if (!status_activefields[fldidx])
158 if (fldidx == BL_CONDITION) {
159 curses_condition_bits = *condptr;
160 curses_colormasks = colormasks;
163 color_and_attr = (color_and_attr & ~0x00FF) | NO_COLOR;
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()).
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';
182 Sprintf(status_vals[fldidx],
183 (fldidx == BL_TITLE && iflags.wc2_hitpointbar)
184 ? "%-30s" : status_fieldfmt[fldidx]
185 ? status_fieldfmt[fldidx] : "%s",
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]);
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;
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;
202 } else { /* BL_FLUSH */
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);
216 /* Figure out if we have proper window dimensions for horizontal
220 int cy = (iflags.wc2_statuslines < 3) ? 2 : 3;
222 /* actual y (and x) */
223 getmaxyx(win, ay, ax);
227 if (cy != ay) { /* something changed. Redo everything */
228 curs_reset_windows(TRUE, TRUE);
229 win = curses_get_nhwin(STATUS_WIN);
235 draw_horizontal(border);
237 draw_vertical(border);
244 /* horizontal layout on 2 or 3 lines */
246 draw_horizontal(border)
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] = {
255 /*xspace*/ BL_STR, BL_DX, BL_CO, BL_IN, BL_WI, BL_CH,
258 BL_FLUSH, blPAD, blPAD, blPAD, blPAD, blPAD },
261 /*xspace*/ BL_HP, BL_HPMAX,
262 /*xspace*/ BL_ENE, BL_ENEMAX,
264 /*xspace*/ BL_XP, BL_EXP, BL_HD,
266 /*xspace*/ BL_HUNGER, BL_CAP, BL_CONDITION,
268 { BL_FLUSH, blPAD, blPAD, blPAD, blPAD, blPAD, blPAD, blPAD,
269 blPAD, blPAD, blPAD, blPAD, blPAD, blPAD, blPAD }
271 threelineorder[3][15] = { /* moves align to line 2, leveldesc+ to 3 */
273 /*xspace*/ BL_STR, BL_DX, BL_CO, BL_IN, BL_WI, BL_CH,
275 BL_FLUSH, blPAD, blPAD, blPAD, blPAD, blPAD, blPAD },
278 /*xspace*/ BL_HP, BL_HPMAX,
279 /*xspace*/ BL_ENE, BL_ENEMAX,
281 /*xspace*/ BL_XP, BL_EXP, BL_HD,
282 /*xspace*/ BL_HUNGER, BL_CAP,
283 BL_FLUSH, blPAD, blPAD },
286 /*xspecial*/ BL_CONDITION,
287 BL_FLUSH, blPAD, blPAD, blPAD, blPAD, blPAD, blPAD, blPAD,
288 blPAD, blPAD, blPAD, blPAD }
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];
296 char sbuf[STATVAL_WIDTH];
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);
308 /* note: getmaxyx() is a macro which assigns values to height and width */
309 getmaxyx(win, height, width);
311 height -= 2, width -= 2;
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.
329 number_of_lines = (iflags.wc2_statuslines < 3) ? 2 : 3;
330 fieldorder = (number_of_lines != 3) ? twolineorder : threelineorder;
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);
341 if (*status_vals[BL_HUNGER])
343 if (*status_vals[BL_CAP])
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);
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)
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) {
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 */
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 == ' ')
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 */
376 spacing[fld] = 0; /* no leading or extra space */
382 spacing[fld] = 0; /* no leading or extra space */
389 spacing[fld] = 0; /* leading space but no extra space */
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' */
402 spacing[fld] = (i > 0 ? 1 : 0); /* extra space unless first */
405 spacing[fld] = (cap_and_hunger & 1);
408 spacing[fld] = (cap_and_hunger == 2);
411 text = cbuf; /* for 'w += strlen(text)' below */
412 spacing[fld] = (cap_and_hunger == 0);
419 spacing[fld] = 1; /* always extra space */
424 spacing[fld] = status_activefields[fld] ? 1 : 0;
427 spacing[fld] = sho_score ? 1 : 0;
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))
440 xtra += spacing[fld];
444 /* if the line is too long, first avoid extra spaces */
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];
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]))) {
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 */
467 if (w - 2 <= width) {
468 sho_score |= 2; /* strip "S:" prefix */
475 /* right justify score unless window is very wide */
476 t = COLNO + (int) strlen(status_vals[BL_SCORE]);
480 spacing[BL_SCORE] += (t - w);
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])
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)
497 text = status_vals[fld];
498 if (i == 0 && *text == ' ')
499 ++text; /* for first field of line, discard leading space */
503 /* might be 'active' but suppressed due to lack of room */
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;
517 if (!(cap_and_hunger & 1))
521 /* always enabled but might be empty */
522 if (!(cap_and_hunger & 2))
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 */
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... */
540 if ((sho_score & 2) != 0) { /* strip "S:" prefix */
541 if ((colon = index(text, ':')) != 0)
542 text = strcat(strcpy(sbuf, " "), colon + 1);
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 */
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 == ' ') {
570 attrmask = (coloridx >> 8) & 0x00FF;
572 attrmask = nhattr2curses(attrmask);
573 wattron(win, attrmask);
577 if (coloridx != NO_COLOR && coloridx != CLR_MAX)
578 curses_toggle_color_attr(win, coloridx, NONE, ON);
581 #endif /* STATUS_HILITES */
585 #ifdef STATUS_HILITES
586 if (iflags.hilite_delta) {
588 if (coloridx != NO_COLOR)
589 curses_toggle_color_attr(win, coloridx, NONE, OFF);
592 wattroff(win, attrmask);
594 #endif /* STATUS_HILITES */
597 /* status conditions */
598 if (curses_condition_bits) {
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) {
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.
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);
623 wmove(win, y, width + (border ? 1 : 0) - clen);
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))
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 */
642 wclrtoeol(win); /* [superfluous? draw_status() calls werase()] */
647 /* vertical layout, to left or right of map */
649 draw_vertical(border)
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 */
663 BL_XP, BL_EXP, BL_HD,
665 /* 2:blank (but only if time or score or both enabled) */
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 */
675 static const enum statusfields shrinkorder[] = {
676 BL_STR, BL_SCORE, BL_TIME, BL_LEVELDESC, BL_HP,
677 BL_CONDITION, BL_CAP, BL_HUNGER
679 xchar spacing[MAXBLSTATS];
680 int i, fld, cap_and_hunger, time_and_score, cond_count;
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);
689 /* note: getmaxyx() is a macro which assigns values to height and width */
690 getmaxyx(win, height, width);
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));
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.)
710 if (*status_vals_long[BL_HUNGER])
712 if (*status_vals_long[BL_CAP])
715 if (status_activefields[BL_TIME])
717 if (status_activefields[BL_SCORE])
720 if (curses_condition_bits) {
721 for (i = 0; i < BL_MASK_BITS; ++i)
722 if (curses_condition_bits & (1 << i))
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) {
734 spacing[fld] = 0; /* these will continue the previous line */
739 spacing[fld] = 2; /* simple group separation (no conditionals) */
742 /* time will be separated from gold unless it is inactive */
743 spacing[fld] = (time_and_score & 1) ? 2 : 0;
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;
752 /* separated from characteristics unless blank */
753 spacing[fld] = (cap_and_hunger & 1) ? 2 : 0;
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;
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 */
771 /* might be inactive, otherwise normal case of 'on next line' */
772 spacing[fld] = status_activefields[fld] ? 1 : 0;
775 height_needed += spacing[fld];
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)
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);
805 for (i = 0; (fld = fieldorder[i]) != BL_FLUSH; ++i) {
806 if (!status_activefields[fld])
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)))
815 wmove(win, y++, x); /* move to next line */
816 if (spacing[fld] == 2)
817 wmove(win, y++, x); /* skip a line */
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));
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) */
832 || (fld == BL_CAP && cap_and_hunger != 3)))
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;
844 if ((fld == BL_EXP && *p == '/')
845 || ((fld == BL_HPMAX || fld == BL_ENEMAX) && *p == '('))
847 /* prefix portion, if any, is output without highlighting */
851 waddstr(win, text); /* output the prefix */
853 text = p; /* rest of field */
854 if ((fld == BL_HPMAX || fld == BL_ENEMAX)
855 && (p = index(text, ')')) != 0) {
861 attrmask = (coloridx >> 8) & 0x00FF;
863 attrmask = nhattr2curses(attrmask);
864 wattron(win, attrmask);
868 if (coloridx != NO_COLOR && coloridx != CLR_MAX)
869 curses_toggle_color_attr(win, coloridx, NONE, ON);
871 } /* highlighting active */
872 #endif /* STATUS_HILITES */
876 #ifdef STATUS_HILITES
877 if (iflags.hilite_delta) {
879 if (coloridx != NO_COLOR)
880 curses_toggle_color_attr(win, coloridx, NONE, OFF);
883 wattroff(win, attrmask);
884 } /* resume normal rendition */
885 if ((fld == BL_HPMAX || fld == BL_ENEMAX) && savedch == ')') {
889 #endif /* STATUS_HILITES */
892 /* status conditions */
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);
898 } /* hitpointbar vs regular field vs conditions */
903 /* hitpointbar using hp percent calculation */
905 curs_HPbar(char *text, /* pre-padded with trailing spaces if short */
906 int bar_len) /* width of space within the brackets */
908 #ifdef STATUS_HILITES
912 #endif /* STATUS_HILITES */
914 char bar[STATVAL_WIDTH], *bar2 = (char *) 0, savedch = '\0';
915 boolean twoparts = (hpbar_percent < 100);
916 WINDOW *win = curses_get_nhwin(STATUS_WIN);
918 if (bar_len < 1 || bar_len > 30)
920 if (bar_len > (k = (int) strlen(text))) /* 26 for vertical status */
922 (void) strncpy(bar, text, bar_len);
925 bar_pos = (bar_len * hpbar_percent) / 100;
926 if (bar_pos < 1 && hpbar_percent > 0)
928 if (bar_pos >= bar_len && hpbar_percent < 100)
929 bar_pos = bar_len - 1;
931 bar2 = &bar[bar_pos];
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
942 if (iflags.hilite_delta) {
943 coloridx = hpbar_color & 0x00FF;
944 if (coloridx != NO_COLOR)
945 curses_toggle_color_attr(win, coloridx, NONE, ON);
948 #endif /* STATUS_HILITES */
950 /* portion of title corresponding to current hit points */
953 #ifdef STATUS_HILITES
955 if (iflags.hilite_delta) {
956 if (coloridx != NO_COLOR)
957 curses_toggle_color_attr(win, coloridx, NONE, OFF);
960 #endif /* STATUS_HILITES */
961 wattroff(win, A_REVERSE); /* do this even if hilite_delta is 0 */
962 } /* *bar (current HP > 0) */
965 /* unhighlighted portion, corresponding to current injuries */
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 */
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 */
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 */
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))
1005 #endif /* STATUS_HILITES */
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);
1017 getmaxyx(win, height, width);
1018 border = curses_window_has_border(STATUS_WIN);
1019 cy0 = height - (border ? 2 : 1);
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 */
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';
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)
1044 #ifdef STATUS_HILITES
1045 if (iflags.hilite_delta) {
1046 if ((attrmask = condattr(bitmsk, curses_colormasks))
1048 attrmask = nhattr2curses(attrmask);
1049 wattron(win, attrmask);
1052 if ((color = condcolor(bitmsk, curses_colormasks))
1054 curses_toggle_color_attr(win, color, NONE, ON);
1057 #endif /* STATUS_HILITES */
1059 /* output the condition name */
1060 waddstr(win, upstart(condnam));
1062 #ifdef STATUS_HILITES
1063 if (iflags.hilite_delta) {
1065 if (color != NO_COLOR)
1066 curses_toggle_color_attr(win, color, NONE, OFF);
1069 wattroff(win, attrmask);
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 */
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));
1089 /* status_update() sets up values for horizontal status; do vertical */
1091 curs_vert_status_vals(int win_width)
1093 const char *lbl; /* field name used as label */
1094 const char *text, *colon;
1095 char leadingspace[15]; /* 5 suffices */
1097 int fldidx, hp_width, en_tmp, fld_width, lbl_width;
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)
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]).
1112 for (fldidx = 0; fldidx < MAXBLSTATS; ++fldidx) {
1113 if (!status_activefields[fldidx] || fldidx == BL_CONDITION) {
1114 *status_vals_long[fldidx] = '\0';
1116 text = status_vals[fldidx];
1117 if (fldidx != BL_TITLE && fldidx != BL_LEVELDESC) {
1118 if ((colon = index(text, ':')) != 0)
1121 lbl = status_fieldnm[fldidx];
1123 leadingspace[0] = '\0';
1124 /* classify type of field (labeled or not) and make some fixups */
1125 switch ((enum statusfields) fldidx) {
1127 /* "experience-level : N" is too long and becomes misleading
1128 if value is shown as 'N/experience-points' */
1132 /* "dungeon-level" is redundant when value is "Dlvl-N" */
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"
1142 /* don't want sprintf(": %s") below inserting second space */
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, " ");
1159 /* for vertical orientation, right justify characteristics;
1160 exceptional strength, if present, will protrude to right */
1161 if (strlen(text) == 1)
1162 Strcpy(leadingspace, " ");
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, " ");
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]);
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);
1193 /* check whether 'label : value' is too wide; if so, we'll
1194 shorten the label's allowed width and try again */
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) {
1201 fld_width += (int) strlen(status_vals_long[BL_HP]);
1204 fld_width += (int) strlen(status_vals_long[BL_ENE]);
1207 fld_width += (int) strlen(status_vals_long[BL_XP]);
1212 if (fld_width > win_width && lbl_width > 10) {
1213 lbl_width -= (fld_width - win_width);
1221 vert_status_dirty = 0;
1224 #ifdef STATUS_HILITES
1227 * Return what color this condition should
1228 * be displayed in based on user settings.
1231 condcolor(bm, bmarray)
1233 unsigned long *bmarray;
1238 for (i = 0; i < CLR_MAX; ++i) {
1239 if ((bmarray[i] & bm) != 0)
1244 #endif /* TEXTCOLOR */
1247 condattr(bm, bmarray)
1249 unsigned long *bmarray;
1253 if (bm && bmarray) {
1254 for (i = HL_ATTCLR_DIM; i < BL_ATTCLR_MAX; ++i) {
1255 if ((bmarray[i] & bm) != 0) {
1260 case HL_ATTCLR_BLINK:
1263 case HL_ATTCLR_ULINE:
1266 case HL_ATTCLR_INVERSE:
1269 case HL_ATTCLR_BOLD:
1280 /* convert tty attributes to curses attributes;
1281 despite similar names, the mask fields have different values */
1283 nhattr2curses(attrmask)
1288 if (attrmask & HL_BOLD)
1290 if (attrmask & HL_INVERSE)
1291 result |= A_REVERSE;
1292 if (attrmask & HL_ULINE)
1293 result |= A_UNDERLINE;
1294 if (attrmask & HL_BLINK)
1296 if (attrmask & HL_DIM)
1301 #endif /* STATUS_HILITES */
1303 /* ======================================================================== */
1306 #if 0 /* old stuff; some may be re-incorporated, most should be discarded */
1307 /* Private declarations */
1309 /* Used to track previous value of things, to highlight changes. */
1310 typedef struct nhs {
1312 int highlight_turns;
1313 int highlight_color;
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 *,
1328 static int decrement_highlight(nhstat *, boolean);
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);
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;
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;
1358 #ifdef SCORE_ON_BOTL
1359 static nhstat prevscore;
1362 extern const char *hu_stat[]; /* from eat.c */
1363 extern const char *enc_stat[]; /* from botl.c */
1365 /* If the statuscolors patch isn't enabled, have some default colors for status problems
1369 const char *txt; /* For status problems */
1370 int color; /* Default color assuming STATUS_COLORS isn't enabled */
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},
1395 get_trouble_color(const char *stat)
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 */
1406 struct color_option stat_color;
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);
1412 if (stat_color.color != NO_COLOR)
1413 res = curses_color_attr(stat_color.color, 0);
1415 res = curses_color_attr(stat_color.color, 0);
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);
1425 return curses_color_attr(clr->color, 0);
1433 /* TODO: This is in the wrong place. */
1435 get_playerrank(char *rank)
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';
1450 Strcpy(rank, rank_of(u.ulevel, Role_switch, flags.female));
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. */
1457 print_statdiff(const char *append, nhstat *stat, long new, int type)
1460 WINDOW *win = curses_get_nhwin(STATUS_WIN);
1462 int color = CLR_GRAY;
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)
1473 color = STAT_DOWN_COLOR;
1476 stat->highlight_color = color;
1477 stat->highlight_turns = 5;
1478 } else if (stat->highlight_turns)
1479 color = stat->highlight_color;
1481 attr_t attr = curses_color_attr(color, 0);
1483 wprintw(win, "%s", append);
1484 if (type == STAT_STR && new > 18) {
1486 wprintw(win, "%d", new - 100);
1487 else if (new == 118)
1488 wprintw(win, "18/**");
1490 wprintw(win, "18/%02d", new - 18);
1492 wprintw(win, "%d", new);
1494 wattroff(win, attr);
1498 draw_trouble_str(const char *str)
1500 WINDOW *win = curses_get_nhwin(STATUS_WIN);
1502 attr_t attr = get_trouble_color(str);
1504 wprintw(win, "%s", str);
1505 wattroff(win, attr);
1508 /* Returns a ncurses attribute for foreground and background.
1509 This should probably be in cursinit.c or something. */
1511 curses_color_attr(int nh_color, int bg_color)
1513 int color = nh_color + 1;
1514 attr_t cattr = A_NORMAL;
1518 if (iflags.wc2_darkgray) {
1519 if (!can_change_color() || COLORS <= 16)
1526 if (COLORS < 16 && color > 8) {
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
1543 (Foreground order: Black, Red, Green, Yellow, Blue,
1544 Magenta, Cyan, Gray/White)
1546 To work around these oddities, we define backgrounds
1547 by the following pairs:
1561 57-64: Gray/White */
1563 if (bg_color == nh_color)
1564 color = 1; /* Make foreground black if fg==bg */
1566 if (bg_color == CLR_RED || bg_color == CLR_BLUE) {
1567 /* already defined before extension */
1570 if (bg_color == CLR_RED)
1573 boolean hicolor = FALSE;
1579 color = (hicolor ? 48 : 8) + color;
1582 color = (hicolor ? 64 : 32) + color;
1585 color = (hicolor ? 80 : 40) + color;
1588 color = (hicolor ? 96 : 48) + color;
1591 color = (hicolor ? 112 : 56) + color;
1598 cattr |= COLOR_PAIR(color);
1603 /* Returns a complete curses attribute. Used to possibly bold/underline/etc HP/Pw. */
1604 #ifdef STATUS_COLORS
1606 hpen_color_attr(boolean is_hp, int cur, int max)
1608 struct color_option stat_color;
1611 if (!iflags.use_status_colors)
1612 return curses_color_attr(CLR_GRAY, 0);
1614 stat_color = percentage_color_of(cur, max, is_hp ? hp_colors : pw_colors);
1616 if (stat_color.color != NO_COLOR)
1617 attr |= curses_color_attr(stat_color.color, 0);
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);
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. */
1634 hpen_color(boolean is_hp, int cur, int max)
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);
1641 if (stat_color.color == NO_COLOR)
1644 return stat_color.color;
1649 int 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;
1659 color = is_hp ? CLR_ORANGE : CLR_BRIGHT_MAGENTA;
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 ] */
1670 draw_bar(boolean is_hp, int cur, int max, const char *title)
1672 WINDOW *win = curses_get_nhwin(STATUS_WIN);
1674 #ifdef STATUS_COLORS
1675 if (!iflags.hitpointbar) {
1676 wprintw(win, "%s", !title ? "---" : title);
1686 sprintf(buf, "%*d / %-*d", len, cur, len, max);
1690 attr_t fillattr, attr;
1691 int color = hpen_color(is_hp, cur, max);
1692 int invcolor = color & 7;
1694 fillattr = curses_color_attr(color, invcolor);
1695 attr = curses_color_attr(color, 0);
1697 /* Figure out how much of the bar to fill */
1699 int len = strlen(buf);
1700 if (cur > 0 && max > 0)
1701 fill = len * cur / max;
1706 wattron(win, fillattr);
1707 wprintw(win, "%.*s", fill, buf);
1708 wattroff(win, fillattr);
1710 wprintw(win, "%.*s", len - fill, &buf[fill]);
1711 wattroff(win, attr);
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. */
1719 curses_update_stats(void)
1721 WINDOW *win = curses_get_nhwin(STATUS_WIN);
1723 /* Clear the window */
1726 int orient = curses_get_window_orientation(STATUS_WIN);
1728 boolean horiz = FALSE;
1729 if ((orient != ALIGN_RIGHT) && (orient != ALIGN_LEFT))
1731 boolean border = curses_window_has_border(STATUS_WIN);
1733 /* Figure out if we have proper window dimensions for horizontal statusbar. */
1737 if (iflags.statuslines < 3)
1740 /* actual y (and x) */
1743 getmaxyx(win, ay, ax);
1748 curses_create_main_windows();
1749 curses_last_messages();
1752 /* Reset XP highlight (since classic_status and new show different numbers) */
1753 prevexp.highlight_turns = 0;
1754 curses_update_stats();
1759 /* Starting x/y. Passed to draw_horizontal/draw_vertical to keep track of
1760 window positioning. */
1764 /* Don't start at border position if applicable */
1770 /* Get HP values. */
1772 int hpmax = u.uhpmax;
1778 if (orient != ALIGN_RIGHT && orient != ALIGN_LEFT)
1779 draw_horizontal(x, y, hp, hpmax);
1781 draw_vertical(x, y, hp, hpmax);
1791 /* Zero highlight timers. This will call curses_update_status again if needed */
1792 curses_decrement_highlights(TRUE);
1797 draw_horizontal(int x, int y, int hp, int hpmax)
1799 if (iflags.statuslines >= 3) {
1800 /* Draw new-style statusbar */
1801 draw_horizontal_new(x, y, hp, hpmax);
1806 WINDOW *win = curses_get_nhwin(STATUS_WIN);
1811 get_playerrank(rank);
1812 sprintf(buf, "%s the %s", plname, rank);
1814 /* Use the title as HP bar (similar to hitpointbar) */
1815 draw_bar(TRUE, hp, hpmax, buf);
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);
1825 wprintw(win, (u.ualign.type == A_CHAOTIC ? " Chaotic" :
1826 u.ualign.type == A_NEUTRAL ? " Neutral" : " Lawful"));
1828 #ifdef SCORE_ON_BOTL
1829 if (flags.showscore)
1830 print_statdiff(" S:", &prevscore, botl_score(), STAT_OTHER);
1831 #endif /* SCORE_ON_BOTL */
1838 describe_level(buf);
1840 wprintw(win, "%s", buf);
1842 print_statdiff("$", &prevau, money_cnt(invent), STAT_GOLD);
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);
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);
1856 wprintw(win, " HP:");
1857 wattron(win, hpattr);
1858 wprintw(win, "%d(%d)", (hp < 0) ? 0 : hp, hpmax);
1859 wattroff(win, hpattr);
1861 wprintw(win, " Pw:");
1862 wattron(win, pwattr);
1863 wprintw(win, "%d(%d)", u.uen, u.uenmax);
1864 wattroff(win, pwattr);
1866 print_statdiff(" AC:", &prevac, u.uac, STAT_AC);
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 '/' */
1874 print_statdiff("", &prevexp, u.uexp, STAT_OTHER);
1877 print_statdiff(" Exp:", &prevlevel, u.ulevel, STAT_OTHER);
1880 print_statdiff(" T:", &prevtime, moves, STAT_TIME);
1882 curses_add_statuses(win, FALSE, FALSE, NULL, NULL);
1886 draw_horizontal_new(int x, int y, int hp, int hpmax)
1890 WINDOW *win = curses_get_nhwin(STATUS_WIN);
1895 get_playerrank(rank);
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 ? "" : " ",
1908 wprintw(win, "HP:");
1909 draw_bar(TRUE, hp, hpmax, NULL);
1910 print_statdiff(" AC:", &prevac, u.uac, STAT_AC);
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)
1922 print_statdiff(" Xp:", &prevlevel, u.ulevel, STAT_OTHER);
1923 /* use waddch, we don't want to highlight the '/' */
1926 /* Figure out amount of Xp needed to next level */
1929 xp_left = (newuexp(u.ulevel) - u.uexp);
1932 prevexp.value = (xp_left + 1);
1933 if (levelchange == 2)
1934 prevexp.value = (xp_left - 1);
1936 print_statdiff("", &prevexp, xp_left, STAT_AC);
1940 print_statdiff(" Exp:", &prevlevel, u.ulevel, STAT_OTHER);
1943 describe_level(buf);
1945 wprintw(win, "%s", buf);
1950 wprintw(win, "Pw:");
1951 draw_bar(FALSE, u.uen, u.uenmax, NULL);
1953 print_statdiff(" $", &prevau, money_cnt(invent), STAT_GOLD);
1955 #ifdef SCORE_ON_BOTL
1956 if (flags.showscore)
1957 print_statdiff(" S:", &prevscore, botl_score(), STAT_OTHER);
1958 #endif /* SCORE_ON_BOTL */
1961 print_statdiff(" T:", &prevtime, moves, STAT_TIME);
1963 curses_add_statuses(win, TRUE, FALSE, &x, &y);
1965 /* Right-aligned attributes */
1966 int stat_length = 6; /* " Dx:xx" */
1968 if (ACURR(A_STR) > 18 && ACURR(A_STR) < 119)
1971 getmaxyx(win, y, x);
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. */
1976 if (curses_window_has_border(STATUS_WIN)) {
1984 print_statdiff(" Co:", &prevcon, ACURR(A_CON), STAT_OTHER);
1987 print_statdiff(" Dx:", &prevdex, ACURR(A_DEX), STAT_OTHER);
1990 print_statdiff(" St:", &prevstr, ACURR(A_STR), STAT_STR);
1995 print_statdiff(" Ch:", &prevcha, ACURR(A_CHA), STAT_OTHER);
1998 print_statdiff(" Wi:", &prevwis, ACURR(A_WIS), STAT_OTHER);
2001 print_statdiff(" In:", &prevint, ACURR(A_INT), STAT_OTHER);
2004 /* Personally I never understood the point of a vertical status bar. But removing the
2005 option would be silly, so keep the functionality. */
2007 draw_vertical(int x, int y, int hp, int hpmax)
2011 WINDOW *win = curses_get_nhwin(STATUS_WIN);
2013 /* Print title and dungeon branch */
2016 get_playerrank(rank);
2017 int ranklen = strlen(rank);
2018 int namelen = strlen(plname);
2020 #ifdef STATUS_COLORS
2021 if (!iflags.hitpointbar)
2022 maxlen += 2; /* With no hitpointbar, we can fit more since there's no "[]" */
2025 if ((ranklen + namelen) > maxlen) {
2026 /* The result doesn't fit. Strip name if >10 characters, then strip title */
2028 while (namelen > 10 && (ranklen + namelen) > maxlen)
2032 while ((ranklen + namelen) > maxlen)
2033 ranklen--; /* Still doesn't fit, strip rank */
2035 sprintf(buf, "%-*s the %-*s", namelen, plname, ranklen, rank);
2036 draw_bar(TRUE, hp, hpmax, buf);
2038 wprintw(win, "%s", dungeons[u.uz.dnum].dname);
2040 y++; /* Blank line inbetween */
2043 /* Attributes. Old vertical order is preserved */
2044 print_statdiff("Strength: ", &prevstr, ACURR(A_STR), STAT_STR);
2046 print_statdiff("Intelligence: ", &prevint, ACURR(A_INT), STAT_OTHER);
2048 print_statdiff("Wisdom: ", &prevwis, ACURR(A_WIS), STAT_OTHER);
2050 print_statdiff("Dexterity: ", &prevdex, ACURR(A_DEX), STAT_OTHER);
2052 print_statdiff("Constitution: ", &prevcon, ACURR(A_CON), STAT_OTHER);
2054 print_statdiff("Charisma: ", &prevcha, ACURR(A_CHA), STAT_OTHER);
2056 wprintw(win, "Alignment: ");
2057 wprintw(win, (u.ualign.type == A_CHAOTIC ? "Chaotic" :
2058 u.ualign.type == A_NEUTRAL ? "Neutral" : "Lawful"));
2060 wprintw(win, "Dungeon Level: ");
2062 /* Astral Plane doesn't fit */
2063 if (In_endgame(&u.uz))
2064 wprintw(win, "%s", Is_astralevel(&u.uz) ? "Astral" : "End Game");
2066 wprintw(win, "%d", depth(&u.uz));
2069 print_statdiff("Gold: ", &prevau, money_cnt(invent), STAT_GOLD);
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);
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);
2085 wprintw(win, "Hit Points: ");
2086 wattron(win, hpattr);
2087 wprintw(win, "%d/%d", (hp < 0) ? 0 : hp, hpmax);
2088 wattroff(win, hpattr);
2091 wprintw(win, "Magic Power: ");
2092 wattron(win, pwattr);
2093 wprintw(win, "%d/%d", u.uen, u.uenmax);
2094 wattroff(win, pwattr);
2097 print_statdiff("Armor Class: ", &prevac, u.uac, STAT_AC);
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 '/' */
2106 print_statdiff("", &prevexp, u.uexp, STAT_OTHER);
2109 print_statdiff("Level: ", &prevlevel, u.ulevel, STAT_OTHER);
2113 print_statdiff("Time: ", &prevtime, moves, STAT_TIME);
2117 #ifdef SCORE_ON_BOTL
2118 if (flags.showscore) {
2119 print_statdiff("Score: ", &prevscore, botl_score(), STAT_OTHER);
2122 #endif /* SCORE_ON_BOTL */
2124 curses_add_statuses(win, FALSE, TRUE, &x, &y);
2128 curses_add_statuses(WINDOW *win, boolean align_right,
2129 boolean vertical, int *x, int *y)
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) */
2137 getmaxyx(win, my, mx);
2138 if (!curses_window_has_border(STATUS_WIN))
2144 #define statprob(str, trouble) \
2145 curses_add_status(win, align_right, vertical, x, y, str, trouble)
2148 statprob(hu_stat[u.uhs], u.uhs != 1); /* 1 is NOT_HUNGRY (not defined here) */
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);
2160 int enc = near_capacity();
2161 statprob(enc_stat[enc], enc > UNENCUMBERED);
2166 curses_add_status(WINDOW *win, boolean align_right, boolean vertical,
2167 int *x, int *y, const char *str, int trouble)
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. */
2177 if (!vertical && !align_right)
2180 /* For whatever reason, hunger states have trailing spaces. Get rid of them. */
2184 for (i = 0; (buf[i] != ' ' && buf[i] != '\0'); i++) ;
2188 *x -= (strlen(buf) + 1); /* add spacing */
2192 draw_trouble_str(buf);
2196 *y += 1; /* ++ advances the pointer addr */
2200 /* Decrement a single highlight, return 1 if decremented to zero. zero is TRUE if we're
2201 zeroing the highlight. */
2203 decrement_highlight(nhstat *stat, boolean zero)
2205 if (stat->highlight_turns > 0) {
2207 stat->highlight_turns = 0;
2211 stat->highlight_turns--;
2212 if (stat->highlight_turns == 0)
2218 /* Decrement the highlight_turns for all stats. Call curses_update_stats
2219 if needed to unhighlight a stat */
2221 curses_decrement_highlights(boolean zero)
2223 int unhighlight = 0;
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);
2242 curses_update_stats();