Files
nethack/win/curses/cursstat.c
PatR 150b331189 analyzer lint for win/curses/*.c
There is still an issue in cursmesg.c.

The last diff band (curses_putch) isn't related to static analysis.
2025-01-23 13:52:19 -08:00

1384 lines
54 KiB
C

/* 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" -*-*/
/* NetHack 3.7 cursstat.c */
/* Copyright (c) Andy Thomson, 2018. */
/* NetHack may be freely redistributed. See license for details. */
#include "curses.h"
#include "hack.h"
#include "wincurs.h"
#include "cursstat.h"
/* Status window functions for curses interface */
/*
* The following data structures come from the genl_ routines in
* src/windows.c and as such are considered to be on the window-port
* "side" of things, rather than the NetHack-core "side" of things.
*/
extern const char *status_fieldfmt[MAXBLSTATS];
extern const char *status_fieldnm[MAXBLSTATS];
extern char *status_vals[MAXBLSTATS];
extern boolean status_activefields[MAXBLSTATS];
/* Long format fields for vertical window */
static char *status_vals_long[MAXBLSTATS];
static unsigned long *curses_colormasks;
static long curses_condition_bits;
static int curses_status_colors[MAXBLSTATS];
static int hpbar_percent, hpbar_crit_hp, hpbar_color;
static int vert_status_dirty;
static void draw_status(void);
static void draw_vertical(boolean);
static void draw_horizontal(boolean);
static void curs_HPbar(char *, int);
static void curs_stat_conds(int, int, int *, int *, char *, boolean *);
static void curs_vert_status_vals(int);
#ifdef STATUS_HILITES
static int condcolor(long, unsigned long *);
static int condattr(long, unsigned long *);
static int nhattr2curses(int);
#endif /* STATUS_HILITES */
/* width of a single line in vertical status orientation (one field per line;
everything but title fits within 30 even with prefix and longest value) */
#define STATVAL_WIDTH 60 /* overkill; was MAXCO (200), massive overkill */
void
curses_status_init(void)
{
int i;
for (i = 0; i < MAXBLSTATS; ++i) {
curses_status_colors[i] = NO_COLOR; /* no color and no attributes */
status_vals_long[i] = (char *) alloc(STATVAL_WIDTH);
*status_vals_long[i] = '\0';
}
curses_condition_bits = 0L;
hpbar_percent = hpbar_crit_hp = 0, hpbar_color = NO_COLOR;
vert_status_dirty = 1;
/* let genl_status_init do most of the initialization */
genl_status_init();
return;
}
void
curses_status_finish(void)
{
int i;
for (i = 0; i < MAXBLSTATS; ++i) {
if (status_vals_long[i])
free(status_vals_long[i]), status_vals_long[i] = (char *) 0;
}
genl_status_finish();
return;
}
/*
* *_status_update()
* -- update the value of a status field.
* -- the fldindex identifies which field is changing and
* is an integer index value from botl.h
* -- fldindex could be any one of the following from botl.h:
* BL_TITLE, BL_STR, BL_DX, BL_CO, BL_IN, BL_WI, BL_CH,
* BL_ALIGN, BL_SCORE, BL_CAP, BL_GOLD, BL_ENE, BL_ENEMAX,
* BL_XP, BL_AC, BL_HD, BL_TIME, BL_HUNGER, BL_HP, BL_HPMAX,
* BL_LEVELDESC, BL_EXP, BL_CONDITION
* -- fldindex could also be BL_FLUSH (-1), which is not really
* a field index, but is a special trigger to tell the
* windowport that it should redisplay all its status fields,
* even if no changes have been presented to it.
* -- ptr is usually a "char *", unless fldindex is BL_CONDITION.
* If fldindex is BL_CONDITION, then ptr is a long value with
* any or none of the following bits set (from botl.h):
* BL_MASK_STONE 0x00000001L
* BL_MASK_SLIME 0x00000002L
* BL_MASK_STRNGL 0x00000004L
* BL_MASK_FOODPOIS 0x00000008L
* BL_MASK_TERMILL 0x00000010L
* BL_MASK_BLIND 0x00000020L
* BL_MASK_DEAF 0x00000040L
* BL_MASK_STUN 0x00000080L
* BL_MASK_CONF 0x00000100L
* BL_MASK_HALLU 0x00000200L
* BL_MASK_LEV 0x00000400L
* BL_MASK_FLY 0x00000800L
* BL_MASK_RIDE 0x00001000L
* -- The value passed for BL_GOLD includes an encoded leading
* symbol for GOLD "\GXXXXNNNN:nnn". If the window port needs to use
* the textual gold amount without the leading "$:" the port will
* have to skip past ':' in the passed "ptr" for the BL_GOLD case.
* -- color is an unsigned int.
* color_index = color & 0x00FF; CLR_* value
* attribute = color & 0xFF00 >> 8; BL_* values
* This holds the color and attribute that the field should
* be displayed in.
* This is relevant for everything except BL_CONDITION fldindex.
* If fldindex is BL_CONDITION, this parameter should be ignored,
* as condition hilighting is done via the next colormasks
* parameter instead.
*
* -- colormasks - pointer to cond_hilites[] array of colormasks.
* Only relevant for BL_CONDITION fldindex. The window port
* should ignore this parameter for other fldindex values.
* Each condition bit must only ever appear in one of the
* CLR_ array members, but can appear in multiple HL_ATTCLR_
* offsets (because more than one attribute can co-exist).
* See doc/window.txt for more details.
*/
static int changed_fields = 0;
DISABLE_WARNING_FORMAT_NONLITERAL
void
curses_status_update(
int fldidx,
genericptr_t ptr,
int chg UNUSED,
int percent,
int color_and_attr,
unsigned long *colormasks)
{
long *condptr = (long *) ptr;
char *text = (char *) ptr;
if (fldidx != BL_FLUSH) {
if (fldidx < 0 || fldidx >= MAXBLSTATS) {
/* panic immediately sets gb.bot_disabled to avoid bot() */
panic("curses_status_update(%d)", fldidx);
}
changed_fields |= (1 << fldidx);
*status_vals[fldidx] = '\0';
if (!status_activefields[fldidx])
return;
if (fldidx == BL_CONDITION) {
curses_condition_bits = *condptr;
curses_colormasks = colormasks;
} else {
/*
* status_vals[] are used for horizontal orientation
* (wide lines of multiple short values).
* status_vals_long[] are used for vertical orientation
* (narrow-ish lines of one long value each [mostly]
* and get constructed at need from status_vals[] rather
* than the original values passed to status_update()).
*/
if (fldidx == BL_GOLD) {
/* decode once instead of every time it's displayed */
status_vals[BL_GOLD][0] = ' ';
text = decode_mixed(&status_vals[BL_GOLD][1], text);
nhUse(text);
} else if ((fldidx == BL_HUNGER || fldidx == BL_CAP)
&& (!*text || !strcmp(text, " "))) {
/* fieldfmt[] is " %s"; avoid lone space when empty */
*status_vals[fldidx] = '\0';
} else {
Sprintf(status_vals[fldidx],
(fldidx == BL_TITLE && iflags.wc2_hitpointbar)
? "%-30.30s" : status_fieldfmt[fldidx]
? status_fieldfmt[fldidx] : "%s",
text);
/* strip trailing spaces; core ought to do this for us */
if (fldidx == BL_HUNGER || fldidx == BL_LEVELDESC)
(void) trimspaces(status_vals[fldidx]);
}
/* status_vals_long[] used to be set up here even when not
in use; it has been moved to curs_vert_status_vals() */
vert_status_dirty = 1;
curses_status_colors[fldidx] = color_and_attr;
if (iflags.wc2_hitpointbar && fldidx == BL_HP) {
hpbar_percent = percent;
hpbar_crit_hp = critically_low_hp(TRUE) ? 1 : 0;
hpbar_color = ((color_and_attr & 0x00ff) | (HL_INVERSE << 8)
| (hpbar_crit_hp ? (HL_BLINK << 8) : 0));
}
}
} else { /* BL_FLUSH */
draw_status();
changed_fields = 0;
}
}
RESTORE_WARNING_FORMAT_NONLITERAL
static void
draw_status(void)
{
WINDOW *win = curses_get_nhwin(STATUS_WIN);
orient statorient = (orient) curses_get_window_orientation(STATUS_WIN);
boolean horiz = (statorient != ALIGN_RIGHT && statorient != ALIGN_LEFT);
boolean border = curses_window_has_border(STATUS_WIN);
/* Figure out if we have proper window dimensions for horizontal
statusbar. */
if (horiz) {
int ax = 0, ay = 0;
int cy = (iflags.wc2_statuslines < 3) ? 2 : 3;
/* actual y (and x) */
getmaxyx(win, ay, ax);
if (border)
ay -= 2;
if (cy != ay) { /* something changed. Redo everything */
curs_reset_windows(TRUE, TRUE);
win = curses_get_nhwin(STATUS_WIN);
}
nhUse(ax); /* getmaxyx macro isn't sufficient */
}
werase(win);
if (horiz)
draw_horizontal(border);
else
draw_vertical(border);
if (border)
box(win, 0, 0);
wnoutrefresh(win);
}
/* horizontal layout on 2 or 3 lines */
static void
draw_horizontal(boolean border)
{
#define blPAD BL_FLUSH
/* almost all fields already come with a leading space;
"xspace" indicates places where we'll generate an extra one */
static const enum statusfields
twolineorder[3][16] = {
{ BL_TITLE,
/*xspace*/ BL_STR, BL_DX, BL_CO, BL_IN, BL_WI, BL_CH,
/*xspace*/ BL_ALIGN,
/*xspace*/ BL_SCORE,
BL_FLUSH, blPAD, blPAD, blPAD, blPAD, blPAD, blPAD },
{ BL_LEVELDESC,
/*xspace*/ BL_GOLD,
/*xspace*/ BL_HP, BL_HPMAX,
/*xspace*/ BL_ENE, BL_ENEMAX,
/*xspace*/ BL_AC,
/*xspace*/ BL_XP, BL_EXP, BL_HD,
/*xspace*/ BL_TIME,
/*xspace*/ BL_HUNGER, BL_CAP, BL_CONDITION, BL_VERS,
BL_FLUSH },
{ BL_FLUSH, blPAD, blPAD, blPAD, blPAD, blPAD, blPAD, blPAD,
blPAD, blPAD, blPAD, blPAD, blPAD, blPAD, blPAD, blPAD }
},
threelineorder[3][16] = { /* moves align to line 2, leveldesc+ to 3 */
{ BL_TITLE,
/*xspace*/ BL_STR, BL_DX, BL_CO, BL_IN, BL_WI, BL_CH,
/*xspace*/ BL_SCORE,
BL_FLUSH, blPAD, blPAD, blPAD, blPAD, blPAD, blPAD, blPAD },
{ BL_ALIGN,
/*xspace*/ BL_GOLD,
/*xspace*/ BL_HP, BL_HPMAX,
/*xspace*/ BL_ENE, BL_ENEMAX,
/*xspace*/ BL_AC,
/*xspace*/ BL_XP, BL_EXP, BL_HD,
/*xspace*/ BL_HUNGER, BL_CAP,
BL_FLUSH, blPAD, blPAD, blPAD },
{ BL_LEVELDESC,
/*xspace*/ BL_TIME,
/*xspecial*/ BL_CONDITION,
/*xspecial*/ BL_VERS,
BL_FLUSH, blPAD, blPAD, blPAD, blPAD, blPAD, blPAD, blPAD,
blPAD, blPAD, blPAD, blPAD }
};
const enum statusfields (*fieldorder)[16];
coordxy spacing[MAXBLSTATS], valline[MAXBLSTATS];
enum statusfields fld, prev_fld;
char *text, *p, cbuf[BUFSZ], ebuf[STATVAL_WIDTH];
#ifdef SCORE_ON_BOTL
char *colon;
char sbuf[STATVAL_WIDTH];
#endif
int i, j, number_of_lines,
cap_and_hunger, exp_points, sho_score, sho_vers,
/* both height and width get their values set,
* but only width gets used in this function */
height, width, w, xtra, clen, x, y, t, ex, ey,
condstart = 0, conddummy = 0, versstart = 0;
#ifdef STATUS_HILITES
int coloridx = NO_COLOR, attrmask = 0;
#endif /* STATUS_HILITES */
boolean asis = FALSE;
WINDOW *win = curses_get_nhwin(STATUS_WIN);
/* note: getmaxyx() is a macro which assigns values to height and width */
getmaxyx(win, height, width);
if (border)
height -= 2, width -= 2;
/*
* Potential revisions:
* If line with gold is too long, strip "$:" prefix like is done
* for score's "S:", and/or convert "$:n" to n /= 1000 and "$:nK"
* if amount takes 4+ digits. (No realistic chance for 7+ digits
* for gold so would recover only 2 columns. n >>= 10 might have
* greater geek appeal but could lead to bug reports and couldn't
* be accomplished via simple string truncation.)
*
* For experience point and score suppression, might try that first
* (better chance for column recovery, with "/nM" freeing 5 out of
* 7+ digits; in rare instances, "/nG" could free 8 out of 10+ digits)
* before deciding to remove them altogether.
* tty's shorter condition designations combined with comparable
* trimming of hunger and encumbrance would be better overall.
*
* For first line when hitpointbar is off, treat trailing spaces
* on Title as discardable leading spaces on Str. (Enabling
* perm_invent without having a wide terminal size results in status
* being narrower than usual and possibly truncating by omitting
* right hand fields, emphasizing the wasted space devoted to
* title's trailing spaces. Same issue without perm_invent if main
* window gets clipped to fit a narrow terminal.)
*/
number_of_lines = (iflags.wc2_statuslines < 3) ? 2 : 3;
fieldorder = (number_of_lines != 3) ? twolineorder : threelineorder;
cbuf[0] = '\0';
x = y = border ? 1 : 0; /* origin; ignored by curs_stat_conds(0) */
/* collect active conditions in cbuf[], space separated, suitable
for direct output if no highlighting is requested ('asis') but
primarily used to measure the length */
curs_stat_conds(0, 0, &x, &y, cbuf, &asis);
clen = (int) strlen(cbuf);
cap_and_hunger = 0;
if (*status_vals[BL_HUNGER])
cap_and_hunger |= 1;
if (*status_vals[BL_CAP])
cap_and_hunger |= 2;
exp_points = (flags.showexp ? 1 : 0);
/* don't bother conditionalizing this; always 0 for !SCORE_ON_BOTL */
sho_score = (status_activefields[BL_SCORE] != 0);
sho_vers = (status_activefields[BL_VERS] != 0);
versstart = sho_vers ? (width - (int) strlen(status_vals[BL_VERS])
+ (border ? 1 : 0))
: 0;
/* simplify testing which fields reside on which lines; assume line #0 */
(void) memset((genericptr_t) valline, 0, sizeof valline);
for (j = 1; j < number_of_lines; ++j)
for (i = 0; (fld = fieldorder[j][i]) != BL_FLUSH; ++i)
valline[fld] = j;
/* iterate 0 and 1 and maybe 2 for status lines 1 and 2 and maybe 3 */
for (j = 0; j < number_of_lines; ++j) {
startover:
/* first pass for line #j -- figure out spacing */
(void) memset((genericptr_t) spacing, 0, sizeof spacing);
w = xtra = 0; /* w: width so far; xtra: number of extra spaces */
prev_fld = BL_FLUSH;
for (i = 0; (fld = fieldorder[j][i]) != BL_FLUSH; ++i) {
/* when the core marks a field as disabled, it doesn't call
status_update() to tell us to throw away the old value, so
polymorph leaves stale XP and rehumanize leaves stale HD */
if (!status_activefields[fld])
*status_vals[fld] = '\0';
text = status_vals[fld];
if (i == 0 && *text == ' ')
++text;
/* most fields already include a leading space; we don't try to
count those separately, they're just part of field's length */
switch (fld) {
case BL_EXP:
spacing[fld] = 0; /* no leading or extra space */
if (!exp_points)
continue;
break;
case BL_HPMAX:
case BL_ENEMAX:
spacing[fld] = 0; /* no leading or extra space */
break;
case BL_DX:
case BL_CO:
case BL_IN:
case BL_WI:
case BL_CH:
spacing[fld] = 0; /* leading space but no extra space */
break;
case BL_TITLE:
if (iflags.wc2_hitpointbar) {
w += 2; /* count '[' and ']' */
t = (int) strlen(text);
if (t != 30) /* HPbar() will use modified copy of title */
w -= (t - 30); /* '+= strlen()' below will add 't';
* functional result being 'w += 30' */
}
FALLTHROUGH;
/*FALLTHRU*/
case BL_ALIGN:
case BL_LEVELDESC:
spacing[fld] = (i > 0 ? 1 : 0); /* extra space unless first */
break;
case BL_HUNGER:
spacing[fld] = (cap_and_hunger & 1);
break;
case BL_CAP:
spacing[fld] = (cap_and_hunger == 2);
break;
case BL_CONDITION:
text = cbuf; /* for 'w += strlen(text)' below */
spacing[fld] = (cap_and_hunger == 0);
break;
case BL_VERS:
case BL_STR:
case BL_HP:
case BL_ENE:
case BL_AC:
case BL_GOLD:
spacing[fld] = 1; /* always extra space */
break;
case BL_XP:
case BL_HD:
case BL_TIME:
spacing[fld] = status_activefields[fld] ? 1 : 0;
break;
case BL_SCORE:
spacing[fld] = sho_score ? 1 : 0;
break;
default:
break;
}
w += (int) strlen(text);
/* if preceding field has any trailing spaces, don't add extra;
(should only apply to prev==title; status_update() handles
others that used to have trailing spaces by stripping such) */
if (spacing[fld] > 0 && prev_fld != BL_FLUSH
&& *(p = status_vals[prev_fld]) && *(eos(p) - 1) == ' '
&& (prev_fld != BL_TITLE || !iflags.wc2_hitpointbar))
spacing[fld] = 0;
xtra += spacing[fld];
prev_fld = fld;
}
/* if the line is too long, first avoid extra spaces */
fld = MAXBLSTATS;
while (xtra > 0 && w + xtra > width) {
while (--fld >= 0) /* [assumes 'fld' is not unsigned!] */
if (spacing[fld] > 0) {
xtra -= spacing[fld];
spacing[fld] = 0;
break;
}
}
w += xtra; /* simplify further width checks */
/* if showing exper points and line is too wide, don't show them */
if (w > width && exp_points && j == valline[BL_EXP]
&& ((*cbuf && j == valline[BL_CONDITION])
|| (cap_and_hunger && j == valline[BL_HUNGER]))) {
exp_points = 0;
goto startover;
}
#ifdef SCORE_ON_BOTL
if (sho_score && j == valline[BL_SCORE]) {
/* no point in letting score become truncated on the right
because showing fewer than all digits would be useless */
if (w > width) {
if (w - 2 <= width) {
sho_score |= 2; /* strip "S:" prefix */
w -= 2;
} else {
sho_score = 0;
goto startover;
}
}
/* right justify score unless window is very wide */
t = COLNO + (int) strlen(status_vals[BL_SCORE]);
if (t > width)
t = width;
if (w < t)
spacing[BL_SCORE] += (t - w);
}
#endif
/* second pass for line #j -- render it */
x = y = border ? 1 : 0;
wmove(win, y + j, x);
for (i = 0; (fld = fieldorder[j][i]) != BL_FLUSH; ++i) {
if (!status_activefields[fld])
continue;
/* output any extra spaces that precede the current field
(if current field will be suppressed for any reason then
its spacing[] should be 0) */
for (t = spacing[fld]; t > 0; --t)
waddch(win, ' ');
text = status_vals[fld];
if (i == 0 && *text == ' ')
++text; /* for first field of line, discard leading space */
switch (fld) {
case BL_EXP:
/* might be 'active' but suppressed due to lack of room */
if (!exp_points)
continue;
break;
case BL_HUNGER:
if (number_of_lines == 3) {
/* remember hunger's position */
getyx(win, conddummy, condstart);
/* if hunger won't be shown, figure out where cap
will be; if cap won't be shown either, use where
conditions would go if they were on this line */
condstart += (cap_and_hunger == 2) ? spacing[BL_CAP]
: (cap_and_hunger == 0) ? 1 : 0;
nhUse(conddummy); /* getyx needed 3 args */
}
if (!(cap_and_hunger & 1))
continue;
break;
case BL_CAP:
/* always enabled but might be empty */
if (!(cap_and_hunger & 2))
continue;
/* check whether encumbrance is going to go past right edge
and wrap; if so, truncate it; (won't wrap on last line
of borderless window, but will when there's a border);
could only do that after all extra spaces are gone */
if (!xtra) {
getyx(win, ey, ex);
t = (int) strlen(text);
if (ex + t > width - (border ? 0 : 1)) {
text = strcpy(ebuf, text);
t = (width - (border ? 0 : 1)) - (ex - 1);
ebuf[max(t, 2)] = '\0'; /* might still wrap... */
}
nhUse(ey); /* getyx needed 3 args */
}
break;
case BL_SCORE:
#ifdef SCORE_ON_BOTL
if ((sho_score & 2) != 0) { /* strip "S:" prefix */
if ((colon = strchr(text, ':')) != 0)
text = strcat(strcpy(sbuf, " "), colon + 1);
else
sho_score = 0;
}
#endif
if (!sho_score)
continue;
break;
default:
break;
}
if (fld == BL_TITLE && iflags.wc2_hitpointbar) {
/* hitpointbar using hp percent calculation; title width
is padded to 30 if shorter, truncated at 30 if longer;
overall width is 32 because of the enclosing brackets */
curs_HPbar(text, 0);
} else if (fld != BL_CONDITION) {
/* regular field, including title if no hitpointbar */
if (fld == BL_VERS) {
getyx(win, y, x);
if (x < versstart)
wmove(win, y, versstart); /* right justify */
}
#ifdef STATUS_HILITES
coloridx = curses_status_colors[fld]; /* includes attribute */
if (iflags.hilite_delta && coloridx != NO_COLOR) {
/* expect 1 leading space; don't highlight it */
while (*text == ' ') {
waddch(win, ' ');
++text;
}
attrmask = (coloridx >> 8) & 0x00FF;
if (attrmask) {
attrmask = nhattr2curses(attrmask);
wattron(win, attrmask);
}
coloridx &= 0x00FF;
if (coloridx != NO_COLOR && coloridx != CLR_MAX)
curses_toggle_color_attr(win, coloridx, NONE, ON);
}
#endif /* STATUS_HILITES */
waddstr(win, text);
#ifdef STATUS_HILITES
if (iflags.hilite_delta) {
if (coloridx != NO_COLOR)
curses_toggle_color_attr(win, coloridx, NONE, OFF);
if (attrmask)
wattroff(win, attrmask);
}
#endif /* STATUS_HILITES */
} else {
/* status conditions */
if (curses_condition_bits) {
getyx(win, y, x);
/* encumbrance is truncated if too wide, but other fields
aren't; if window is narrower than normal, last field
written might have wrapped to the next line */
if (y > j + (border ? 1 : 0))
x = width - (border ? -1 : 0), /* (width-=2 above) */
y = j + (border ? 1 : 0);
/* cbuf[] was populated above; clen is its length */
if (number_of_lines == 3) {
int vlen = (sho_vers
&& fieldorder[j][i + 1] == BL_VERS)
? ((int) strlen(status_vals[BL_VERS])
+ spacing[BL_VERS])
: 0;
clen += vlen; /* when aligning conditions, treat
* version as if an added condition */
/*
* For 3-line status, align conditions with hunger
* (or where it would have been, when not shown),
* or if that doesn't provide enough room, right
* align with window's edge, or just put out at
* current spot if too long for right alignment.
*/
/* both cbuf[] and hunger start with a leading
space, so clen and condstart reflect that;
'border' adjustments have already been made
for x (offset by 1) and width (reduced by 2) */
if (x + clen < width) {
if (x < condstart && condstart + clen < width)
wmove(win, y, condstart);
else
wmove(win, y, width + (border ? 1 : 0) - clen);
}
clen -= vlen;
}
/* 'asis' was set up by first curs_stat_conds() call
above; True means that none of the conditions
need highlighting; but we won't use concatenated
condition string as-is if it will overflow; we
want curs_stat_conds() to write '+' in last column
if any conditions are all the way off the edge */
if (x + clen > width - (border ? 1 : 0))
asis = FALSE;
if (asis)
waddstr(win, cbuf);
else /* cond by cond if any cond specifies highlighting */
curs_stat_conds(0, 0, &x, &y,
(char *) 0, (boolean *) 0);
} /* curses_condition_bits */
} /* hitpointbar vs regular field vs conditions */
} /* i (fld) */
wclrtoeol(win); /* [superfluous? draw_status() calls werase()] */
} /* j (line) */
nhUse(height);
return;
}
/* vertical layout, to left or right of map */
static void
draw_vertical(boolean border)
{
/* for blank lines, the digit prefix is the order in which they get
removed if we need to shrink to fit within height limit (very rare) */
static const enum statusfields fieldorder[] = {
BL_TITLE, /* might be overlaid by hitpoint bar */
/* 5:blank */
BL_HP, BL_HPMAX,
BL_ENE, BL_ENEMAX,
BL_AC,
/* 4:blank */
BL_LEVELDESC,
BL_ALIGN,
BL_XP, BL_EXP, BL_HD,
BL_GOLD,
/* 3:blank (but only if time or score or both enabled) */
BL_TIME,
BL_SCORE,
/* 2:blank */
BL_STR, BL_DX, BL_CO, BL_IN, BL_WI, BL_CH,
/* 6:blank (if any of hunger, encumbrance, or conditions appear) */
BL_HUNGER, BL_CAP, /* these two are shown on same line */
BL_CONDITION, /* shown three per line so may take up to four lines */
/* 1:blank (bottom justified) */
BL_VERS,
BL_FLUSH
};
static const enum statusfields shrinkorder[] = {
BL_VERS, BL_STR, BL_SCORE, BL_TIME, BL_LEVELDESC, BL_HP,
BL_CONDITION, BL_CAP, BL_HUNGER
};
coordxy spacing[MAXBLSTATS];
int i, fld, cap_and_hunger, time_and_score, cond_count,
sho_vers, per_line;
char *text;
#ifdef STATUS_HILITES
char *p = 0, savedch = '\0';
int coloridx = NO_COLOR, attrmask = 0;
#endif /* STATUS_HILITES */
int height_needed, height, width, x = 0, y = 0;
WINDOW *win = curses_get_nhwin(STATUS_WIN);
/* note: getmaxyx() is a macro which assigns values to height and width */
getmaxyx(win, height, width);
/* basic update formats fields for horizontal; derive vertical from them */
if (vert_status_dirty)
curs_vert_status_vals(width - (border ? 2 : 0));
/*
* Possible refinements:
* If "<name> the <rank-or-monster>" is too wide, split it across
* two lines; alternatively, use the old curses status code to
* truncate the two portions separately. (<name> is already being
* truncated to 10 chars by the botl.c code, so we don't really
* need to do anything further unless we want to override that.)
*/
cap_and_hunger = 0;
if (*status_vals_long[BL_HUNGER])
cap_and_hunger |= 1;
if (*status_vals_long[BL_CAP])
cap_and_hunger |= 2;
time_and_score = 0;
if (status_activefields[BL_TIME])
time_and_score |= 1;
if (status_activefields[BL_SCORE])
time_and_score |= 2;
cond_count = 0;
if (curses_condition_bits) {
for (i = 0; i < CONDITION_COUNT; ++i)
if (curses_condition_bits & (1 << i))
++cond_count;
}
per_line = 2; /* will be changed to 3 if status becomes too tall */
sho_vers = (status_activefields[BL_VERS] ? 1 : 0);
/* count how many lines we'll need; we normally space several groups of
fields with blank lines but might need to compress some of those out */
height_needed = border ? 2 : 0;
for (i = 0; (fld = i) < SIZE(spacing); ++i) {
switch ((enum statusfields) fld) {
case BL_HPMAX:
case BL_ENEMAX:
case BL_EXP:
spacing[fld] = 0; /* these will continue the previous line */
break;
case BL_HP:
case BL_LEVELDESC:
case BL_STR:
spacing[fld] = 2; /* simple group separation (no conditionals) */
break;
case BL_TIME:
/* time will be separated from gold unless it is inactive */
spacing[fld] = (time_and_score & 1) ? 2 : 0;
break;
case BL_SCORE:
/* unlike hunger+cap, score is shown on separate line from time;
needs time+score separator if time is inactive */
spacing[fld] = (time_and_score == 2) ? 2
: (time_and_score == 3) ? 1 : 0;
break;
case BL_HUNGER:
/* separated from characteristics unless blank */
spacing[fld] = (cap_and_hunger & 1) ? 2 : 0;
break;
case BL_CAP:
/* on same line as hunger if both are non-blank,
otherwise needs blank line if hunger is being omitted */
spacing[fld] = (cap_and_hunger == 2) ? 2 : 0;
break;
case BL_CONDITION:
/* need blank line if hunger and encumbrance are both omitted,
otherwise just start on next line; if more than 3 conditions
are present, this will consume multiple lines from height */
spacing[fld] = cond_count ? (!cap_and_hunger ? 2 : 1) : 0;
/* first line handled via '+= spacing[]' below */
if (cond_count > per_line)
height_needed += (cond_count - 1) / per_line;
break;
case BL_VERS:
spacing[fld] = sho_vers ? 2 : 0;
break;
case BL_XP:
case BL_HD:
default:
/* might be inactive, otherwise normal case of 'on next line' */
spacing[fld] = status_activefields[fld] ? 1 : 0;
break;
}
height_needed += spacing[fld];
}
if (height_needed > height) {
/* if there are a lot of status conditions, compress them first */
if (per_line == 2 && cond_count > per_line) {
height_needed -= (cond_count - 1) / per_line;
per_line = 3;
height_needed += (cond_count - 1) / per_line;
}
if (height_needed > height) {
for (i = 0; i < SIZE(shrinkorder); ++i) {
fld = shrinkorder[i];
if (spacing[fld] == 2) {
spacing[fld] = 1; /* suppress planned blank line */
if (--height_needed <= height)
break;
}
}
}
#ifdef SCORE_ON_BOTL
/* with all optional fields and every status condition (12 out
of the 13 since two are mutually exclusive) active, we need
22 non-blank lines; curses_create_main_windows() used to
require 24 lines or more in order to enable vertical status,
but that has been relaxed to 20 so height_needed might still
be too high after suppressing all the blank lines */
if (height_needed > height && status_activefields[BL_SCORE]) {
height_needed -= spacing[BL_SCORE];
spacing[BL_SCORE] = 0;
time_and_score &= ~2;
}
#endif
} else if (height_needed < height) {
if (sho_vers) {
/* bottom justify 'version' */
spacing[BL_VERS] += height - height_needed; /* 2 + (h - h') */
height_needed = height;
}
}
/* height_needed isn't used beyond here but was updated (for BL_SCORE
or BL_VERS) to keep it accurate in case that changes someday */
nhUse(height_needed);
if (border)
x++, y++;
for (i = 0; (fld = fieldorder[i]) != BL_FLUSH; ++i) {
if (!status_activefields[fld])
continue;
if ((fld == BL_HUNGER && !(cap_and_hunger & 1))
|| (fld == BL_CAP && !(cap_and_hunger & 2))
|| (fld == BL_TIME && !(time_and_score & 1))
|| (fld == BL_SCORE && !(time_and_score & 2)))
continue;
if (spacing[fld]) {
y += spacing[fld];
wmove(win, y - 1, x); /* move to next (or further) line */
}
if (fld == BL_TITLE && iflags.wc2_hitpointbar) {
/* 4: left+right borders and open+close brackets; 2: brackets */
curs_HPbar(status_vals_long[fld], width - (border ? 4 : 2));
} else if (fld != BL_CONDITION) {
/* regular field (including title if no hitpoint bar) */
text = status_vals_long[fld];
/* hunger and encumbrance come with a leading space;
we'll put them on the same line and omit that space for
the first (or only) and keep it for the second (if both) */
if (*text == ' '
&& (fld == BL_HUNGER
|| (fld == BL_CAP && cap_and_hunger != 3)))
++text;
#ifdef STATUS_HILITES
coloridx = curses_status_colors[fld]; /* includes attributes */
if (iflags.hilite_delta && coloridx != NO_COLOR) {
/* most status_vals_long[] are "long-text : value" and
unlike horizontal status's abbreviated "ab:value",
we highlight just the value portion */
p = (fld != BL_TITLE) ? strchr(text, ':') : 0;
p = !p ? text : p + 1;
while (*p == ' ')
++p;
if ((fld == BL_EXP && *p == '/')
|| ((fld == BL_HPMAX || fld == BL_ENEMAX) && *p == '('))
++p;
/* prefix portion, if any, is output without highlighting */
if (p > text) {
savedch = *p;
*p = '\0';
waddstr(win, text); /* output the prefix */
*p = savedch;
text = p; /* rest of field */
if ((fld == BL_HPMAX || fld == BL_ENEMAX)
&& (p = strchr(text, ')')) != 0) {
savedch = *p;
*p = '\0';
} else
savedch = '\0';
}
attrmask = (coloridx >> 8) & 0x00FF;
if (attrmask) {
attrmask = nhattr2curses(attrmask);
wattron(win, attrmask);
}
coloridx &= 0x00FF;
if (coloridx != NO_COLOR && coloridx != CLR_MAX)
curses_toggle_color_attr(win, coloridx, NONE, ON);
} /* highlighting active */
#endif /* STATUS_HILITES */
waddstr(win, text);
#ifdef STATUS_HILITES
if (iflags.hilite_delta) {
if (coloridx != NO_COLOR)
curses_toggle_color_attr(win, coloridx, NONE, OFF);
if (attrmask)
wattroff(win, attrmask);
} /* resume normal rendition */
if ((fld == BL_HPMAX || fld == BL_ENEMAX) && savedch == ')') {
*p = savedch;
waddstr(win, p);
}
#endif /* STATUS_HILITES */
} else {
/* status conditions */
if (cond_count) {
/* output active conditions; usually two per line, but if
window isn't tall enough, it's increased to three per line;
cursor is already positioned where they should start */
curs_stat_conds(1, per_line, &x, &y,
(char *) 0, (boolean *) 0);
}
} /* hitpointbar vs regular field vs conditions */
} /* fld loop */
return;
}
/* hitpointbar using hp percent calculation */
static void
curs_HPbar(
char *text, /* pre-padded with trailing spaces if short */
int bar_len) /* width of space within the brackets */
{
#ifdef STATUS_HILITES
int coloridx = 0;
#endif /* STATUS_HILITES */
int k, bar_pos;
char bar[STATVAL_WIDTH], *bar2 = (char *) 0, savedch = '\0';
boolean twoparts = (hpbar_percent < 100);
WINDOW *win = curses_get_nhwin(STATUS_WIN);
if (bar_len < 1 || bar_len > 30)
bar_len = 30;
if (bar_len > (k = (int) strlen(text))) /* 26 for vertical status */
bar_len = k;
(void) strncpy(bar, text, bar_len);
bar[bar_len] = '\0';
if (hpbar_crit_hp)
repad_with_dashes(bar);
bar_pos = (bar_len * hpbar_percent) / 100;
if (bar_pos < 1 && hpbar_percent > 0)
bar_pos = 1;
if (bar_pos >= bar_len && hpbar_percent < 100)
bar_pos = bar_len - 1;
if (twoparts) {
bar2 = &bar[bar_pos];
savedch = *bar2;
*bar2 = '\0';
}
waddch(win, '[');
if (hpbar_crit_hp)
wattron(win, A_BLINK);
if (*bar) { /* True unless dead (0 HP => bar_pos == 0) */
/* fixed attribute, not nhattr2curses((hpbar_color >> 8) & 0x00FF) */
wattron(win, A_REVERSE); /* do this even if hilite_delta is 0 */
#ifdef STATUS_HILITES
if (iflags.hilite_delta) {
coloridx = hpbar_color & 0x00FF;
if (coloridx != NO_COLOR)
curses_toggle_color_attr(win, coloridx, NONE, ON);
}
#endif /* STATUS_HILITES */
/* portion of title corresponding to current hit points */
waddstr(win, bar);
#ifdef STATUS_HILITES
if (iflags.hilite_delta) {
if (coloridx != NO_COLOR)
curses_toggle_color_attr(win, coloridx, NONE, OFF);
}
#endif /* STATUS_HILITES */
wattroff(win, A_REVERSE); /* do this even if hilite_delta is 0 */
} /* *bar (current HP > 0) */
if (twoparts) {
/* unhighlighted portion, corresponding to current injuries */
*bar2 = savedch;
waddstr(win, bar2);
}
if (hpbar_crit_hp)
wattroff(win, A_BLINK);
waddch(win, ']');
}
/* conditions[] is used primarily for parsing hilite_status rules, but
we can use it for condition names and mask bits, avoiding duplication */
extern const struct conditions_t conditions[]; /* botl.c */
extern int cond_idx[CONDITION_COUNT];
DISABLE_WARNING_FORMAT_NONLITERAL
static void
curs_stat_conds(
int vert_cond, /* 0 => horizontal, 1 => vertical */
int per_line, /* for vertical, number of conditions per line */
int *x, int *y, /* real for vertical, ignored otherwise */
char *condbuf, /* optional output; collect string of conds */
boolean *nohilite) /* optional output; indicates whether -*/
{ /*+ condbuf[] could be used as-is */
char condnam[20];
int i, ci;
long bitmsk;
if (condbuf) {
/* construct string " cond1 cond2 cond3" of all active conditions;
if none of them specify highlighting, set the nohilite flag so
caller can output the string as is; otherwise its length can be
used to decide how to position prior to calling us back without
'condbuf' in order to perform cond by cond output */
condbuf[0] = '\0';
if (nohilite)
*nohilite = TRUE; /* assume ok */
for (i = 0; i < CONDITION_COUNT; ++i) {
ci = cond_idx[i];
bitmsk = conditions[ci].mask;
if (curses_condition_bits & bitmsk) {
Strcpy(condnam, conditions[ci].text[0]);
Strcat(strcat(condbuf, " "), upstart(condnam));
#ifdef STATUS_HILITES
if (nohilite && *nohilite
&& (condcolor(bitmsk, curses_colormasks) != NO_COLOR
|| condattr(bitmsk, curses_colormasks) != 0))
*nohilite = FALSE;
#endif /* STATUS_HILITES */
}
}
} else if (curses_condition_bits) {
unsigned long cond_bits;
const char *vert_fmt = 0;
int height = 0, width, cx, cy, cy0, cndlen;
#ifdef STATUS_HILITES
int attrmask = 0, color = NO_COLOR;
#endif /* STATUS_HILITES */
boolean border, do_vert = (vert_cond != 0);
WINDOW *win = curses_get_nhwin(STATUS_WIN);
getmaxyx(win, height, width);
border = curses_window_has_border(STATUS_WIN);
cy0 = height - (border ? 2 : 1);
if (vert_cond)
vert_fmt = (per_line == 2) ? "%-12.12s" : "%-8.8s";
cond_bits = curses_condition_bits;
/* show active conditions directly; for vertical, 2 or 3 per line */
for (i = 0; i < CONDITION_COUNT; ++i) {
ci = cond_idx[i];
bitmsk = conditions[ci].mask;
if (cond_bits & bitmsk) {
if (!vert_fmt)
Strcpy(condnam, conditions[ci].text[0]);
else
Sprintf(condnam, vert_fmt, conditions[ci].text[0]);
cndlen = 1 + (int) strlen(condnam); /* count leading space */
if (!do_vert) {
getyx(win, cy, cx);
if (cy > cy0) /* wrap to next line shouldn't happen */
cx = width, cy = cy0;
if (cx + cndlen > width - (border ? 2 : 1)) {
/* not enough room for current condition */
if (cx + 1 > width - (border ? 2 : 1))
break; /* no room at all; skip it and the rest */
/* room for part; truncate it to avoid wrapping */
condnam[width - (border ? 2 : 1) - cx] = '\0';
}
}
cond_bits &= ~bitmsk; /* nonzero if another cond after this */
/* output unhighlighted leading space unless at #1 of 3 */
if (!do_vert || (vert_cond % per_line) != 1)
waddch(win, ' ');
#ifdef STATUS_HILITES
if (iflags.hilite_delta) {
if ((attrmask = condattr(bitmsk, curses_colormasks))
!= 0) {
attrmask = nhattr2curses(attrmask);
wattron(win, attrmask);
}
if ((color = condcolor(bitmsk, curses_colormasks))
!= NO_COLOR)
curses_toggle_color_attr(win, color, NONE, ON);
}
#endif /* STATUS_HILITES */
/* output the condition name */
waddstr(win, upstart(condnam));
#ifdef STATUS_HILITES
if (iflags.hilite_delta) {
if (color != NO_COLOR)
curses_toggle_color_attr(win, color, NONE, OFF);
if (attrmask)
wattroff(win, attrmask);
}
#endif /* STATUS_HILITES */
/* if that was #3 of 3 advance to next line */
if (do_vert && cond_bits && (++vert_cond % per_line) == 1)
wmove(win, (*y)++, *x);
} /* if cond_bits & bitmask */
} /* for i */
/* if we stopped before showing all the conditions, put a '+'
in the rightmost column to indicate that there would be more
shown if there were more room available */
if (cond_bits != 0L) {
wmove(win, cy0, width - (border ? 2 : 1));
waddch(win, '+');
}
}
return;
}
RESTORE_WARNING_FORMAT_NONLITERAL
/* status_update() sets up values for horizontal status; do vertical */
void
curs_vert_status_vals(int win_width)
{
const char *lbl; /* field name used as label */
const char *text, *colon;
char leadingspace[15]; /* 5 suffices */
boolean use_name;
int fldidx, hp_width, en_tmp, fld_width, lbl_width;
/* width of bigger of full HP and full En regardless of current value */
hp_width = (int) strlen(status_vals[BL_HPMAX]) - 2; /* -2: "("...")" */
en_tmp = (int) strlen(status_vals[BL_ENEMAX]) - 2;
if (en_tmp > hp_width)
hp_width = en_tmp;
/*
* status_vals[] are used for horizontal orientation
* (wide lines of multiple short values).
* status_vals_long[] are used for vertical orientation
* (narrow-ish lines of one long value each [mostly]).
*/
lbl_width = 13;
startover:
for (fldidx = 0; fldidx < MAXBLSTATS; ++fldidx) {
if (!status_activefields[fldidx] || fldidx == BL_CONDITION) {
*status_vals_long[fldidx] = '\0';
} else {
text = status_vals[fldidx];
if (fldidx != BL_TITLE && fldidx != BL_LEVELDESC) {
if ((colon = strchr(text, ':')) != 0)
text = colon + 1;
}
lbl = status_fieldnm[fldidx];
use_name = TRUE;
leadingspace[0] = '\0';
/* classify type of field (labeled or not) and make some fixups */
switch ((enum statusfields) fldidx) {
case BL_XP:
/* "experience-level : N" is too long and becomes misleading
if value is shown as 'N/experience-points' */
lbl = "experience";
break;
case BL_LEVELDESC:
/* "dungeon-level" is redundant when value is "Dlvl-N" */
lbl = "location";
break;
case BL_HD:
/* "HD" is too oscure; 0 actually means 1d4 (so about 1/2);
"hit-dice" is obscure too but doesn't stand out as such */
lbl = (!strcmp(text, "1") || !strcmp(text, "0")) ? "hit-die"
: "hit-dice";
break;
case BL_ALIGN:
/* don't want sprintf(": %s") below inserting second space */
if (*text == ' ')
++text;
break;
case BL_HP:
case BL_ENE:
/* pad HP and En so that they're right aligned */
fld_width = (int) strlen(text);
if (fld_width < hp_width)
Sprintf(leadingspace, "%*s", hp_width - fld_width, " ");
break;
case BL_STR:
case BL_DX:
case BL_CO:
case BL_IN:
case BL_WI:
case BL_CH:
/* for vertical orientation, right justify characteristics;
exceptional strength, if present, will protrude to right */
if (strlen(text) == 1)
Strcpy(leadingspace, " ");
break;
case BL_HPMAX:
case BL_ENEMAX:
/* pad HPmax and Enmax so that they're right aligned with
each other with a one-space gap after current HP/En */
fld_width = (int) strlen(text);
if (fld_width < hp_width + 3) /* +3: " " gap and "("...")" */
Sprintf(leadingspace, "%*s",
(hp_width + 3) - fld_width, " ");
FALLTHROUGH;
/*FALLTHRU*/
case BL_VERS:
case BL_EXP:
case BL_HUNGER:
case BL_CAP:
case BL_TITLE:
use_name = FALSE;
break;
default:
break;
}
if (use_name) {
Sprintf(status_vals_long[fldidx], "%*.*s: %s%s",
-lbl_width, lbl_width, lbl, leadingspace, text);
*status_vals_long[fldidx] = highc(*status_vals_long[fldidx]);
} else if (fldidx == BL_VERS && *text) {
int txtlen = (int) strlen(text);
/* right justify without "Version :" prefix; if longer than
width, keep only the *end* of the value */
if (txtlen >= win_width)
Strcpy(status_vals_long[BL_VERS],
eos((char *) text) - win_width);
else
Sprintf(status_vals_long[BL_VERS],
"%*s%s", win_width - txtlen, " ", text);
} else if ((fldidx == BL_HUNGER || fldidx == BL_CAP) && *text) {
/* hunger and encumbrance are shown side-by-side in
a 26 character or wider window; if leading space is
present, get rid of it, then add one we're sure about */
if (*text == ' ')
++text;
Sprintf(status_vals_long[fldidx], " %-12.12s", text);
} else {
/* unlabeled: title, hp-max, en-max, exp-points, hunger+cap */
Sprintf(status_vals_long[fldidx], "%s%s", leadingspace, text);
/* when appending, base field uses field name as label */
use_name = (fldidx == BL_HPMAX || fldidx == BL_ENEMAX
|| fldidx == BL_EXP);
}
/* check whether 'label : value' is too wide; if so, we'll
shorten the label's allowed width and try again */
if (use_name) {
fld_width = (int) strlen(status_vals_long[fldidx]);
/* each extension field is preceded by its base field in
order to append, so base's _vals_long[] has been set */
switch ((enum statusfields) fldidx) {
case BL_HPMAX:
fld_width += (int) strlen(status_vals_long[BL_HP]);
break;
case BL_ENEMAX:
fld_width += (int) strlen(status_vals_long[BL_ENE]);
break;
case BL_EXP:
fld_width += (int) strlen(status_vals_long[BL_XP]);
break;
default:
break;
}
if (fld_width > win_width && lbl_width > 10) {
lbl_width -= (fld_width - win_width);
if (lbl_width < 10)
lbl_width = 10;
goto startover;
}
}
}
}
vert_status_dirty = 0;
}
#ifdef STATUS_HILITES
/*
* Return what color this condition should
* be displayed in based on user settings.
*/
static int
condcolor(long bm, unsigned long *bmarray)
{
int i;
if (bm && bmarray)
for (i = 0; i < CLR_MAX; ++i) {
if ((bmarray[i] & bm) != 0)
return i;
}
return NO_COLOR;
}
static int
condattr(long bm, unsigned long *bmarray)
{
int i, attr = 0;
if (bm && bmarray) {
for (i = HL_ATTCLR_BOLD; i < BL_ATTCLR_MAX; ++i) {
if ((bmarray[i] & bm) != 0) {
switch(i) {
case HL_ATTCLR_BOLD:
attr |= HL_BOLD;
break;
case HL_ATTCLR_DIM:
attr |= HL_DIM;
break;
case HL_ATTCLR_ITALIC:
attr |= HL_ITALIC;
break;
case HL_ATTCLR_ULINE:
attr |= HL_ULINE;
break;
case HL_ATTCLR_BLINK:
attr |= HL_BLINK;
break;
case HL_ATTCLR_INVERSE:
attr |= HL_INVERSE;
break;
}
}
}
}
return attr;
}
/* convert tty attributes to curses attributes;
despite similar names, the mask fields have different values */
static int
nhattr2curses(int attrmask)
{
int result = 0;
if (attrmask & HL_BOLD)
result |= A_BOLD;
if (attrmask & HL_DIM)
result |= A_DIM;
if (attrmask & HL_ITALIC)
result |= A_ITALIC;
if (attrmask & HL_ULINE)
result |= A_UNDERLINE;
if (attrmask & HL_BLINK)
result |= A_BLINK;
if (attrmask & HL_INVERSE)
result |= A_REVERSE;
return result;
}
#endif /* STATUS_HILITES */
/*cursstat.c*/