Files
nethack/src/botl.c
2018-11-15 02:51:27 -05:00

3559 lines
110 KiB
C

/* NetHack 3.6 botl.c $NHDT-Date: 1527042178 2018/05/23 02:22:58 $ $NHDT-Branch: NetHack-3.6.2 $:$NHDT-Revision: 1.101 $ */
/* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */
/*-Copyright (c) Michael Allison, 2006. */
/* NetHack may be freely redistributed. See license for details. */
#include "hack.h"
#include <limits.h>
extern const char *hu_stat[]; /* defined in eat.c */
const char *const enc_stat[] = { "", "Burdened", "Stressed",
"Strained", "Overtaxed", "Overloaded" };
STATIC_OVL NEARDATA int mrank_sz = 0; /* loaded by max_rank_sz (from u_init) */
STATIC_DCL const char *NDECL(rank);
#ifdef STATUS_HILITES
STATIC_DCL void NDECL(bot_via_windowport);
#endif
static char *
get_strength_str()
{
static char buf[32];
int st = ACURR(A_STR);
if (st > 18) {
if (st > STR18(100))
Sprintf(buf, "%2d", st - 100);
else if (st < STR18(100))
Sprintf(buf, "18/%02d", st - 18);
else
Sprintf(buf, "18/**");
} else
Sprintf(buf, "%-1d", st);
return buf;
}
char *
do_statusline1()
{
static char newbot1[BUFSZ];
register char *nb;
register int i, j;
Strcpy(newbot1, plname);
if ('a' <= newbot1[0] && newbot1[0] <= 'z')
newbot1[0] += 'A' - 'a';
newbot1[10] = 0;
Sprintf(nb = eos(newbot1), " the ");
if (Upolyd) {
char mbot[BUFSZ];
int k = 0;
Strcpy(mbot, mons[u.umonnum].mname);
while (mbot[k] != 0) {
if ((k == 0 || (k > 0 && mbot[k - 1] == ' ')) && 'a' <= mbot[k]
&& mbot[k] <= 'z')
mbot[k] += 'A' - 'a';
k++;
}
Strcpy(nb = eos(nb), mbot);
} else
Strcpy(nb = eos(nb), rank());
Sprintf(nb = eos(nb), " ");
i = mrank_sz + 15;
j = (int) ((nb + 2) - newbot1); /* strlen(newbot1) but less computation */
if ((i - j) > 0)
Sprintf(nb = eos(nb), "%*s", i - j, " "); /* pad with spaces */
Sprintf(nb = eos(nb), "St:%s Dx:%-1d Co:%-1d In:%-1d Wi:%-1d Ch:%-1d",
get_strength_str(),
ACURR(A_DEX), ACURR(A_CON), ACURR(A_INT), ACURR(A_WIS),
ACURR(A_CHA));
Sprintf(nb = eos(nb),
(u.ualign.type == A_CHAOTIC)
? " Chaotic"
: (u.ualign.type == A_NEUTRAL) ? " Neutral" : " Lawful");
#ifdef SCORE_ON_BOTL
if (flags.showscore)
Sprintf(nb = eos(nb), " S:%ld", botl_score());
#endif
return newbot1;
}
void
check_gold_symbol()
{
int goldch, goldoc;
unsigned int goldos;
int goldglyph = objnum_to_glyph(GOLD_PIECE);
(void) mapglyph(goldglyph, &goldch, &goldoc, &goldos, 0, 0);
iflags.invis_goldsym = ((char)goldch <= ' ');
}
char *
do_statusline2()
{
static char newbot2[BUFSZ], /* MAXCO: botl.h */
/* dungeon location (and gold), hero health (HP, PW, AC),
experience (HD if poly'd, else Exp level and maybe Exp points),
time (in moves), varying number of status conditions */
dloc[QBUFSZ], hlth[QBUFSZ], expr[QBUFSZ], tmmv[QBUFSZ], cond[QBUFSZ];
register char *nb;
unsigned dln, dx, hln, xln, tln, cln;
int hp, hpmax, cap;
long money;
/*
* Various min(x,9999)'s are to avoid having excessive values
* violate the field width assumptions in botl.h and should not
* impact normal play. Particularly 64-bit long for gold which
* could require many more digits if someone figures out a way
* to get and carry a really large (or negative) amount of it.
* Turn counter is also long, but we'll risk that.
*/
/* dungeon location plus gold */
(void) describe_level(dloc); /* includes at least one trailing space */
if ((money = money_cnt(invent)) < 0L)
money = 0L; /* ought to issue impossible() and then discard gold */
Sprintf(eos(dloc), "%s:%-2ld", /* strongest hero can lift ~300000 gold */
(iflags.in_dumplog || iflags.invis_goldsym) ? "$"
: encglyph(objnum_to_glyph(GOLD_PIECE)),
min(money, 999999L));
dln = strlen(dloc);
/* '$' encoded as \GXXXXNNNN is 9 chars longer than display will need */
dx = strstri(dloc, "\\G") ? 9 : 0;
/* health and armor class (has trailing space for AC 0..9) */
hp = Upolyd ? u.mh : u.uhp;
hpmax = Upolyd ? u.mhmax : u.uhpmax;
if (hp < 0)
hp = 0;
Sprintf(hlth, "HP:%d(%d) Pw:%d(%d) AC:%-2d",
min(hp, 9999), min(hpmax, 9999),
min(u.uen, 9999), min(u.uenmax, 9999), u.uac);
hln = strlen(hlth);
/* experience */
if (Upolyd)
Sprintf(expr, "HD:%d", mons[u.umonnum].mlevel);
else if (flags.showexp)
Sprintf(expr, "Xp:%u/%-1ld", u.ulevel, u.uexp);
else
Sprintf(expr, "Exp:%u", u.ulevel);
xln = strlen(expr);
/* time/move counter */
if (flags.time)
Sprintf(tmmv, "T:%ld", moves);
else
tmmv[0] = '\0';
tln = strlen(tmmv);
/* status conditions; worst ones first */
cond[0] = '\0'; /* once non-empty, cond will have a leading space */
nb = cond;
/*
* Stoned, Slimed, Strangled, and both types of Sick are all fatal
* unless remedied before timeout expires. Should we order them by
* shortest time left? [Probably not worth the effort, since it's
* unusual for more than one of them to apply at a time.]
*/
if (Stoned)
Strcpy(nb = eos(nb), " Stone");
if (Slimed)
Strcpy(nb = eos(nb), " Slime");
if (Strangled)
Strcpy(nb = eos(nb), " Strngl");
if (Sick) {
if (u.usick_type & SICK_VOMITABLE)
Strcpy(nb = eos(nb), " FoodPois");
if (u.usick_type & SICK_NONVOMITABLE)
Strcpy(nb = eos(nb), " TermIll");
}
if (u.uhs != NOT_HUNGRY)
Sprintf(nb = eos(nb), " %s", hu_stat[u.uhs]);
if ((cap = near_capacity()) > UNENCUMBERED)
Sprintf(nb = eos(nb), " %s", enc_stat[cap]);
if (Blind)
Strcpy(nb = eos(nb), " Blind");
if (Deaf)
Strcpy(nb = eos(nb), " Deaf");
if (Stunned)
Strcpy(nb = eos(nb), " Stun");
if (Confusion)
Strcpy(nb = eos(nb), " Conf");
if (Hallucination)
Strcpy(nb = eos(nb), " Hallu");
/* levitation and flying are mutually exclusive; riding is not */
if (Levitation)
Strcpy(nb = eos(nb), " Lev");
if (Flying)
Strcpy(nb = eos(nb), " Fly");
if (u.usteed)
Strcpy(nb = eos(nb), " Ride");
cln = strlen(cond);
/*
* Put the pieces together. If they all fit, keep the traditional
* sequence. Otherwise, move least important parts to the end in
* case the interface side of things has to truncate. Note that
* dloc[] contains '$' encoded in ten character sequence \GXXXXNNNN
* so we want to test its display length rather than buffer length.
*
* We don't have an actual display limit here, so have to go by the
* width of the map. Since we're reordering rather than truncating,
* wider displays can still show wider status than the map if the
* interface supports that.
*/
if ((dln - dx) + 1 + hln + 1 + xln + 1 + tln + 1 + cln <= COLNO) {
Sprintf(newbot2, "%s %s %s %s %s", dloc, hlth, expr, tmmv, cond);
} else {
if (dln + 1 + hln + 1 + xln + 1 + tln + 1 + cln + 1 > MAXCO) {
panic("bot2: second status line exceeds MAXCO (%u > %d)",
(dln + 1 + hln + 1 + xln + 1 + tln + 1 + cln + 1), MAXCO);
} else if ((dln - dx) + 1 + hln + 1 + xln + 1 + cln <= COLNO) {
Sprintf(newbot2, "%s %s %s %s %s", dloc, hlth, expr, cond, tmmv);
} else if ((dln - dx) + 1 + hln + 1 + cln <= COLNO) {
Sprintf(newbot2, "%s %s %s %s %s", dloc, hlth, cond, expr, tmmv);
} else {
Sprintf(newbot2, "%s %s %s %s %s", hlth, cond, dloc, expr, tmmv);
}
/* only two or three consecutive spaces available to squeeze out */
mungspaces(newbot2);
}
return newbot2;
}
void
bot()
{
if (youmonst.data && iflags.status_updates) {
#ifdef STATUS_HILITES
bot_via_windowport();
#else
curs(WIN_STATUS, 1, 0);
putstr(WIN_STATUS, 0, do_statusline1());
curs(WIN_STATUS, 1, 1);
putmixed(WIN_STATUS, 0, do_statusline2());
#endif
}
context.botl = context.botlx = 0;
}
/* convert experience level (1..30) to rank index (0..8) */
int
xlev_to_rank(xlev)
int xlev;
{
return (xlev <= 2) ? 0 : (xlev <= 30) ? ((xlev + 2) / 4) : 8;
}
#if 0 /* not currently needed */
/* convert rank index (0..8) to experience level (1..30) */
int
rank_to_xlev(rank)
int rank;
{
return (rank <= 0) ? 1 : (rank <= 8) ? ((rank * 4) - 2) : 30;
}
#endif
const char *
rank_of(lev, monnum, female)
int lev;
short monnum;
boolean female;
{
register const struct Role *role;
register int i;
/* Find the role */
for (role = roles; role->name.m; role++)
if (monnum == role->malenum || monnum == role->femalenum)
break;
if (!role->name.m)
role = &urole;
/* Find the rank */
for (i = xlev_to_rank((int) lev); i >= 0; i--) {
if (female && role->rank[i].f)
return role->rank[i].f;
if (role->rank[i].m)
return role->rank[i].m;
}
/* Try the role name, instead */
if (female && role->name.f)
return role->name.f;
else if (role->name.m)
return role->name.m;
return "Player";
}
STATIC_OVL const char *
rank()
{
return rank_of(u.ulevel, Role_switch, flags.female);
}
int
title_to_mon(str, rank_indx, title_length)
const char *str;
int *rank_indx, *title_length;
{
register int i, j;
/* Loop through each of the roles */
for (i = 0; roles[i].name.m; i++)
for (j = 0; j < 9; j++) {
if (roles[i].rank[j].m
&& !strncmpi(str, roles[i].rank[j].m,
strlen(roles[i].rank[j].m))) {
if (rank_indx)
*rank_indx = j;
if (title_length)
*title_length = strlen(roles[i].rank[j].m);
return roles[i].malenum;
}
if (roles[i].rank[j].f
&& !strncmpi(str, roles[i].rank[j].f,
strlen(roles[i].rank[j].f))) {
if (rank_indx)
*rank_indx = j;
if (title_length)
*title_length = strlen(roles[i].rank[j].f);
return (roles[i].femalenum != NON_PM) ? roles[i].femalenum
: roles[i].malenum;
}
}
return NON_PM;
}
void
max_rank_sz()
{
register int i, r, maxr = 0;
for (i = 0; i < 9; i++) {
if (urole.rank[i].m && (r = strlen(urole.rank[i].m)) > maxr)
maxr = r;
if (urole.rank[i].f && (r = strlen(urole.rank[i].f)) > maxr)
maxr = r;
}
mrank_sz = maxr;
return;
}
#ifdef SCORE_ON_BOTL
long
botl_score()
{
long deepest = deepest_lev_reached(FALSE);
long utotal;
utotal = money_cnt(invent) + hidden_gold();
if ((utotal -= u.umoney0) < 0L)
utotal = 0L;
utotal += u.urexp + (50 * (deepest - 1))
+ (deepest > 30 ? 10000 : deepest > 20 ? 1000 * (deepest - 20) : 0);
if (utotal < u.urexp)
utotal = LONG_MAX; /* wrap around */
return utotal;
}
#endif /* SCORE_ON_BOTL */
/* provide the name of the current level for display by various ports */
int
describe_level(buf)
char *buf;
{
int ret = 1;
/* TODO: Add in dungeon name */
if (Is_knox(&u.uz))
Sprintf(buf, "%s ", dungeons[u.uz.dnum].dname);
else if (In_quest(&u.uz))
Sprintf(buf, "Home %d ", dunlev(&u.uz));
else if (In_endgame(&u.uz))
Sprintf(buf, Is_astralevel(&u.uz) ? "Astral Plane " : "End Game ");
else {
/* ports with more room may expand this one */
Sprintf(buf, "Dlvl:%-2d ", depth(&u.uz));
ret = 0;
}
return ret;
}
/* =======================================================================*/
/* statusnew routines */
/* =======================================================================*/
/* structure that tracks the status details in the core */
#ifdef STATUS_HILITES
struct hilite_s {
enum statusfields fld;
boolean set;
unsigned anytype;
anything value;
int behavior;
char textmatch[QBUFSZ];
enum relationships rel;
int coloridx;
struct hilite_s *next;
};
struct condmap {
const char *id;
unsigned long bitmask;
};
#endif /* STATUS_HILITES */
struct istat_s {
const char *fldname;
const char *fldfmt;
long time; /* moves when this field hilite times out */
boolean chg; /* need to recalc time? */
unsigned anytype;
anything a;
char *val;
int valwidth;
enum statusfields idxmax;
enum statusfields fld;
#ifdef STATUS_HILITES
struct hilite_s *thresholds;
#endif
};
STATIC_DCL void NDECL(init_blstats);
STATIC_DCL char *FDECL(anything_to_s, (char *, anything *, int));
STATIC_OVL int FDECL(percentage, (struct istat_s *, struct istat_s *));
STATIC_OVL int FDECL(compare_blstats, (struct istat_s *, struct istat_s *));
STATIC_DCL boolean FDECL(evaluate_and_notify_windowport_field,
(int, boolean *, int, int));
STATIC_DCL void FDECL(evaluate_and_notify_windowport, (boolean *, int, int));
#ifdef STATUS_HILITES
STATIC_DCL boolean FDECL(hilite_reset_needed, (struct istat_s *, long));
STATIC_DCL void FDECL(s_to_anything, (anything *, char *, int));
STATIC_DCL boolean FDECL(is_ltgt_percentnumber, (const char *));
STATIC_DCL boolean FDECL(has_ltgt_percentnumber, (const char *));
STATIC_DCL boolean FDECL(parse_status_hl2, (char (*)[QBUFSZ],BOOLEAN_P));
STATIC_DCL boolean FDECL(parse_condition, (char (*)[QBUFSZ], int));
STATIC_DCL boolean FDECL(noneoftheabove, (const char *));
STATIC_DCL void FDECL(merge_bestcolor, (int *, int));
STATIC_DCL void FDECL(get_hilite_color, (int, int, genericptr_t, int,
int, int *));
STATIC_DCL unsigned long FDECL(match_str2conditionbitmask, (const char *));
STATIC_DCL unsigned long FDECL(str2conditionbitmask, (char *));
STATIC_DCL void FDECL(split_clridx, (int, int *, int *));
STATIC_DCL char *FDECL(hlattr2attrname, (int, char *, int));
STATIC_DCL void FDECL(status_hilite_linestr_add, (int, struct hilite_s *,
unsigned long, const char *));
STATIC_DCL void NDECL(status_hilite_linestr_done);
STATIC_DCL int FDECL(status_hilite_linestr_countfield, (int));
STATIC_DCL void NDECL(status_hilite_linestr_gather_conditions);
STATIC_DCL void NDECL(status_hilite_linestr_gather);
STATIC_DCL char *FDECL(status_hilite2str, (struct hilite_s *));
STATIC_DCL int NDECL(status_hilite_menu_choose_field);
STATIC_DCL int FDECL(status_hilite_menu_choose_behavior, (int));
STATIC_DCL int FDECL(status_hilite_menu_choose_updownboth, (int, const char *,
BOOLEAN_P, BOOLEAN_P));
STATIC_DCL boolean FDECL(status_hilite_menu_add, (int));
#define has_hilite(i) (blstats[0][(i)].thresholds)
#endif
#define INIT_BLSTAT(name, fmtstr, anytyp, wid, fld) \
{ name, fmtstr, 0L, FALSE, anytyp, { (genericptr_t) 0 }, (char *) 0, \
wid, -1, fld }
#define INIT_BLSTATP(name, fmtstr, anytyp, wid, maxfld, fld) \
{ name, fmtstr, 0L, FALSE, anytyp, { (genericptr_t) 0 }, (char *) 0, \
wid, maxfld, fld }
/* If entries are added to this, botl.h will require updating too */
STATIC_DCL struct istat_s initblstats[MAXBLSTATS] = {
INIT_BLSTAT("title", "%s", ANY_STR, 80, BL_TITLE),
INIT_BLSTAT("strength", " St:%s", ANY_INT, 10, BL_STR),
INIT_BLSTAT("dexterity", " Dx:%s", ANY_INT, 10, BL_DX),
INIT_BLSTAT("constitution", " Co:%s", ANY_INT, 10, BL_CO),
INIT_BLSTAT("intelligence", " In:%s", ANY_INT, 10, BL_IN),
INIT_BLSTAT("wisdom", " Wi:%s", ANY_INT, 10, BL_WI),
INIT_BLSTAT("charisma", " Ch:%s", ANY_INT, 10, BL_CH),
INIT_BLSTAT("alignment", " %s", ANY_STR, 40, BL_ALIGN),
INIT_BLSTAT("score", " S:%s", ANY_LONG, 20, BL_SCORE),
INIT_BLSTAT("carrying-capacity", " %s", ANY_INT, 20, BL_CAP),
INIT_BLSTAT("gold", " %s", ANY_LONG, 30, BL_GOLD),
INIT_BLSTATP("power", " Pw:%s", ANY_INT, 10, BL_ENEMAX, BL_ENE),
INIT_BLSTAT("power-max", "(%s)", ANY_INT, 10, BL_ENEMAX),
INIT_BLSTAT("experience-level", " Xp:%s", ANY_INT, 10, BL_XP),
INIT_BLSTAT("armor-class", " AC:%s", ANY_INT, 10, BL_AC),
INIT_BLSTAT("HD", " HD:%s", ANY_INT, 10, BL_HD),
INIT_BLSTAT("time", " T:%s", ANY_LONG, 20, BL_TIME),
/* hunger used to be 'ANY_UINT'; see note below in bot_via_windowport() */
INIT_BLSTAT("hunger", " %s", ANY_INT, 40, BL_HUNGER),
INIT_BLSTATP("hitpoints", " HP:%s", ANY_INT, 10, BL_HPMAX, BL_HP),
INIT_BLSTAT("hitpoints-max", "(%s)", ANY_INT, 10, BL_HPMAX),
INIT_BLSTAT("dungeon-level", "%s", ANY_STR, 80, BL_LEVELDESC),
INIT_BLSTAT("experience", "/%s", ANY_LONG, 20, BL_EXP),
INIT_BLSTAT("condition", "%s", ANY_MASK32, 0, BL_CONDITION)
};
#undef INIT_BLSTATP
#undef INIT_BLSTAT
struct istat_s blstats[2][MAXBLSTATS];
static boolean blinit = FALSE, update_all = FALSE;
static boolean valset[MAXBLSTATS];
unsigned long blcolormasks[CLR_MAX];
static long bl_hilite_moves = 0L;
/* we don't put this next declaration in #ifdef STATUS_HILITES.
* In the absence of STATUS_HILITES, each array
* element will be 0 however, and quite meaningless,
* but we need to pass the first array element as
* the final argument of status_update, with or
* without STATUS_HILITES.
*/
unsigned long cond_hilites[BL_ATTCLR_MAX];
void
bot_via_windowport()
{
char buf[BUFSZ];
register char *nb;
static int i, idx = 0, idx_p, cap;
long money;
if (!blinit)
panic("bot before init.");
idx_p = idx;
idx = 1 - idx; /* 0 -> 1, 1 -> 0 */
/* clear the "value set" indicators */
(void) memset((genericptr_t) valset, 0, MAXBLSTATS * sizeof (boolean));
/*
* Note: min(x,9999) - we enforce the same maximum on hp, maxhp,
* pw, maxpw, and gold as basic status formatting so that the two
* modes of status display don't produce different information.
*/
/*
* Player name and title.
*/
Strcpy(nb = buf, plname);
nb[0] = highc(nb[0]);
nb[10] = '\0';
Sprintf(nb = eos(nb), " the ");
if (Upolyd) {
for (i = 0, nb = strcpy(eos(nb), mons[u.umonnum].mname); nb[i]; i++)
if (i == 0 || nb[i - 1] == ' ')
nb[i] = highc(nb[i]);
} else
Strcpy(nb = eos(nb), rank());
Sprintf(blstats[idx][BL_TITLE].val, "%-29s", buf);
valset[BL_TITLE] = TRUE; /* indicate val already set */
/* Strength */
blstats[idx][BL_STR].a.a_int = ACURR(A_STR);
Strcpy(blstats[idx][BL_STR].val, get_strength_str());
valset[BL_STR] = TRUE; /* indicate val already set */
/* Dexterity, constitution, intelligence, wisdom, charisma. */
blstats[idx][BL_DX].a.a_int = ACURR(A_DEX);
blstats[idx][BL_CO].a.a_int = ACURR(A_CON);
blstats[idx][BL_IN].a.a_int = ACURR(A_INT);
blstats[idx][BL_WI].a.a_int = ACURR(A_WIS);
blstats[idx][BL_CH].a.a_int = ACURR(A_CHA);
/* Alignment */
Strcpy(blstats[idx][BL_ALIGN].val, (u.ualign.type == A_CHAOTIC)
? "Chaotic"
: (u.ualign.type == A_NEUTRAL)
? "Neutral"
: "Lawful");
/* Score */
blstats[idx][BL_SCORE].a.a_long =
#ifdef SCORE_ON_BOTL
flags.showscore ? botl_score() :
#endif
0L;
/* Hit points */
i = Upolyd ? u.mh : u.uhp;
if (i < 0)
i = 0;
blstats[idx][BL_HP].a.a_int = min(i, 9999);
i = Upolyd ? u.mhmax : u.uhpmax;
blstats[idx][BL_HPMAX].a.a_int = min(i, 9999);
/* Dungeon level. */
(void) describe_level(blstats[idx][BL_LEVELDESC].val);
valset[BL_LEVELDESC] = TRUE; /* indicate val already set */
/* Gold */
if ((money = money_cnt(invent)) < 0L)
money = 0L; /* ought to issue impossible() and then discard gold */
blstats[idx][BL_GOLD].a.a_long = min(money, 999999L);
/*
* The tty port needs to display the current symbol for gold
* as a field header, so to accommodate that we pass gold with
* that already included. If a window port needs to use the text
* gold amount without the leading "$:" the port will have to
* skip past ':' to the value pointer it was passed in status_update()
* for the BL_GOLD case.
*
* Another quirk of BL_GOLD is that the field display may have
* changed if a new symbol set was loaded, or we entered or left
* the rogue level.
*
* The currency prefix is encoded as ten character \GXXXXNNNN
* sequence.
*/
Sprintf(blstats[idx][BL_GOLD].val, "%s:%ld",
encglyph(objnum_to_glyph(GOLD_PIECE)),
blstats[idx][BL_GOLD].a.a_long);
valset[BL_GOLD] = TRUE; /* indicate val already set */
/* Power (magical energy) */
blstats[idx][BL_ENE].a.a_int = min(u.uen, 9999);
blstats[idx][BL_ENEMAX].a.a_int = min(u.uenmax, 9999);
/* Armor class */
blstats[idx][BL_AC].a.a_int = u.uac;
/* Monster level (if Upolyd) */
blstats[idx][BL_HD].a.a_int = Upolyd ? (int) mons[u.umonnum].mlevel : 0;
/* Experience */
blstats[idx][BL_XP].a.a_int = u.ulevel;
blstats[idx][BL_EXP].a.a_long = u.uexp;
/* Time (moves) */
blstats[idx][BL_TIME].a.a_long = moves;
/* Hunger */
/* note: u.uhs is unsigned, and 3.6.1's STATUS_HILITE defined
BL_HUNGER to be ANY_UINT, but that was the only non-int/non-long
numeric field so it's far simpler to treat it as plain int and
not need ANY_UINT handling at all */
blstats[idx][BL_HUNGER].a.a_int = (int) u.uhs;
Strcpy(blstats[idx][BL_HUNGER].val,
(u.uhs != NOT_HUNGRY) ? hu_stat[u.uhs] : "");
valset[BL_HUNGER] = TRUE;
/* Carrying capacity */
cap = near_capacity();
blstats[idx][BL_CAP].a.a_int = cap;
Strcpy(blstats[idx][BL_CAP].val,
(cap > UNENCUMBERED) ? enc_stat[cap] : "");
valset[BL_CAP] = TRUE;
/* Conditions */
blstats[idx][BL_CONDITION].a.a_ulong = 0L;
if (Stoned)
blstats[idx][BL_CONDITION].a.a_ulong |= BL_MASK_STONE;
if (Slimed)
blstats[idx][BL_CONDITION].a.a_ulong |= BL_MASK_SLIME;
if (Strangled)
blstats[idx][BL_CONDITION].a.a_ulong |= BL_MASK_STRNGL;
if (Sick && (u.usick_type & SICK_VOMITABLE) != 0)
blstats[idx][BL_CONDITION].a.a_ulong |= BL_MASK_FOODPOIS;
if (Sick && (u.usick_type & SICK_NONVOMITABLE) != 0)
blstats[idx][BL_CONDITION].a.a_ulong |= BL_MASK_TERMILL;
/*
* basic formatting puts hunger status and encumbrance here
*/
if (Blind)
blstats[idx][BL_CONDITION].a.a_ulong |= BL_MASK_BLIND;
if (Deaf)
blstats[idx][BL_CONDITION].a.a_ulong |= BL_MASK_DEAF;
if (Stunned)
blstats[idx][BL_CONDITION].a.a_ulong |= BL_MASK_STUN;
if (Confusion)
blstats[idx][BL_CONDITION].a.a_ulong |= BL_MASK_CONF;
if (Hallucination)
blstats[idx][BL_CONDITION].a.a_ulong |= BL_MASK_HALLU;
/* levitation and flying are mututally exclusive */
if (Levitation)
blstats[idx][BL_CONDITION].a.a_ulong |= BL_MASK_LEV;
if (Flying)
blstats[idx][BL_CONDITION].a.a_ulong |= BL_MASK_FLY;
if (u.usteed)
blstats[idx][BL_CONDITION].a.a_ulong |= BL_MASK_RIDE;
evaluate_and_notify_windowport(valset, idx, idx_p);
}
STATIC_OVL boolean
evaluate_and_notify_windowport_field(fld, valsetlist, idx, idx_p)
int fld, idx, idx_p;
boolean *valsetlist;
{
static int oldrndencode = 0;
static nhsym oldgoldsym = 0;
int pc, chg, color = NO_COLOR;
unsigned anytype;
boolean updated = FALSE, reset;
struct istat_s *curr = NULL, *prev = NULL;
enum statusfields idxmax;
/*
* Now pass the changed values to window port.
*/
anytype = blstats[idx][fld].anytype;
curr = &blstats[idx][fld];
prev = &blstats[idx_p][fld];
color = NO_COLOR;
chg = update_all ? 0 : compare_blstats(prev, curr);
/* Temporary? hack: moveloop()'s prolog for a new game sets
* context.rndencode after the status window has been init'd,
* so $:0 has already been encoded and cached by the window
* port. Without this hack, gold's \G sequence won't be
* recognized and ends up being displayed as-is for 'update_all'.
*
* Also, even if context.rndencode hasn't changed and the
* gold amount itself hasn't changed, the glyph portion of the
* encoding may have changed if a new symset was put into
* effect.
*
* \GXXXXNNNN:25
* XXXX = the context.rndencode portion
* NNNN = the glyph portion
* 25 = the gold amount
*
*/
if (fld == BL_GOLD) {
if (context.rndencode != oldrndencode) {
chg = 2;
oldrndencode = context.rndencode;
}
if (oldgoldsym != showsyms[COIN_CLASS + SYM_OFF_O]) {
chg = 2;
oldgoldsym = showsyms[COIN_CLASS + SYM_OFF_O];
}
}
reset = FALSE;
#ifdef STATUS_HILITES
if (!update_all && !chg) {
reset = hilite_reset_needed(prev, bl_hilite_moves);
if (reset)
curr->time = prev->time = 0L;
}
#endif
if (update_all || chg || reset) {
idxmax = curr->idxmax;
pc = (idxmax >= 0) ? percentage(curr, &blstats[idx][idxmax]) : 0;
if (!valsetlist[fld])
(void) anything_to_s(curr->val, &curr->a, anytype);
if (anytype != ANY_MASK32) {
#ifdef STATUS_HILITES
if ((chg || *curr->val)) {
get_hilite_color(idx, fld, (genericptr_t)&curr->a,
chg, pc, &color);
if (chg == 2) {
color = NO_COLOR;
chg = 0;
}
}
#endif /* STATUS_HILITES */
status_update(fld, (genericptr_t) curr->val,
chg, pc, color, &cond_hilites[0]);
} else {
/* Color for conditions is done through cond_hilites[] */
status_update(fld, (genericptr_t) &curr->a.a_ulong, chg, pc,
color, &cond_hilites[0]);
}
curr->chg = prev->chg = TRUE;
updated = TRUE;
}
return updated;
}
static void
evaluate_and_notify_windowport(valsetlist, idx, idx_p)
int idx, idx_p;
boolean *valsetlist;
{
int i, updated = 0, notpresent = 0;
/*
* Now pass the changed values to window port.
*/
for (i = 0; i < MAXBLSTATS; i++) {
if (((i == BL_SCORE) && !flags.showscore)
|| ((i == BL_EXP) && !flags.showexp)
|| ((i == BL_TIME) && !flags.time)
|| ((i == BL_HD) && !Upolyd)
|| ((i == BL_XP || i == BL_EXP) && Upolyd)) {
notpresent++;
continue;
}
if (evaluate_and_notify_windowport_field(i, valsetlist, idx, idx_p))
updated++;
}
/*
* Notes:
* 1. It is possible to get here, with nothing having been pushed
* to the window port, when none of the info has changed.
*
* 2. Some window ports are also known to optimize by only drawing
* fields that have changed since the previous update.
*
* In both of those situations, we need to force updates to
* all of the fields when context.botlx is set. The tty port in
* particular has a problem if that isn't done, since the core sets
* context.botlx when a menu or text display obliterates the status
* line.
*
* For those situations, to trigger the full update of every field
* whether changed or not, call status_update() with BL_RESET.
*
* For regular processing and to notify the window port that a
* bot() round has finished and it's time to trigger a flush of
* all buffered changes received thus far but not reflected in
* the display, call status_update() with BL_FLUSH.
*
*/
if (context.botlx &&
(windowprocs.wincap2 & WC2_RESET_STATUS) != 0L)
status_update(BL_RESET, (genericptr_t) 0, 0, 0,
NO_COLOR, &cond_hilites[0]);
else if ((windowprocs.wincap2 & WC2_FLUSH_STATUS) != 0L)
status_update(BL_FLUSH, (genericptr_t) 0, 0, 0,
NO_COLOR, &cond_hilites[0]);
context.botl = context.botlx = 0;
update_all = FALSE;
}
void
status_eval_next_unhilite()
{
int i;
struct istat_s *curr = NULL;
long next_unhilite, this_unhilite;
bl_hilite_moves = moves;
/* figure out when the next unhilight needs to be performed */
next_unhilite = 0L;
for (i = 0; i < MAXBLSTATS; ++i) {
curr = &blstats[0][i]; /* blstats[0][*].time == blstats[1][*].time */
if (curr->chg) {
struct istat_s *prev = &blstats[1][i];
#ifdef STATUS_HILITES
curr->time = prev->time = (bl_hilite_moves + iflags.hilite_delta);
#endif
curr->chg = prev->chg = FALSE;
}
this_unhilite = curr->time;
if (this_unhilite > 0L
&& (next_unhilite == 0L || this_unhilite < next_unhilite)
#ifdef STATUS_HILITES
&& hilite_reset_needed(curr, this_unhilite + 1L)
#endif
)
next_unhilite = this_unhilite;
}
if (next_unhilite > 0L && next_unhilite < bl_hilite_moves)
context.botl = TRUE;
}
void
status_initialize(reassessment)
boolean
reassessment; /* TRUE = just reassess fields w/o other initialization*/
{
int i;
const char *fieldfmt = (const char *) 0;
const char *fieldname = (const char *) 0;
if (!reassessment) {
if (blinit)
impossible("2nd status_initialize with full init.");
init_blstats();
(*windowprocs.win_status_init)();
blinit = TRUE;
}
for (i = 0; i < MAXBLSTATS; ++i) {
enum statusfields fld = initblstats[i].fld;
boolean fldenabled = (fld == BL_SCORE) ? flags.showscore
: (fld == BL_XP) ? (boolean) !Upolyd
: (fld == BL_HD) ? (boolean) Upolyd
: (fld == BL_TIME) ? flags.time
: (fld == BL_EXP) ? (boolean) (flags.showexp && !Upolyd)
: TRUE;
fieldname = initblstats[i].fldname;
if (fld == BL_TITLE && iflags.wc2_hitpointbar)
fieldfmt = "%-30s";
else
fieldfmt = initblstats[i].fldfmt;
status_enablefield(fld, fieldname, fieldfmt, fldenabled);
}
update_all = TRUE;
}
void
status_finish()
{
int i;
/* call the window port cleanup routine first */
if (windowprocs.win_status_finish)
(*windowprocs.win_status_finish)();
/* free memory that we alloc'd now */
for (i = 0; i < MAXBLSTATS; ++i) {
if (blstats[0][i].val)
free((genericptr_t) blstats[0][i].val), blstats[0][i].val = 0;
if (blstats[1][i].val)
free((genericptr_t) blstats[1][i].val), blstats[1][i].val = 0;
#ifdef STATUS_HILITES
if (blstats[0][i].thresholds) {
struct hilite_s *temp = blstats[0][i].thresholds,
*next = (struct hilite_s *)0;
while (temp) {
next = temp->next;
free(temp);
blstats[0][i].thresholds = (struct hilite_s *)0;
blstats[1][i].thresholds = blstats[0][i].thresholds;
temp = next;
}
}
#endif /* STATUS_HILITES */
}
}
STATIC_OVL void
init_blstats()
{
static boolean initalready = FALSE;
int i, j;
if (initalready) {
impossible("init_blstats called more than once.");
return;
}
initalready = TRUE;
for (i = BEFORE; i <= NOW; ++i) {
for (j = 0; j < MAXBLSTATS; ++j) {
#ifdef STATUS_HILITES
struct hilite_s *keep_hilite_chain = blstats[i][j].thresholds;
#endif
blstats[i][j] = initblstats[j];
blstats[i][j].a = zeroany;
if (blstats[i][j].valwidth) {
blstats[i][j].val = (char *) alloc(blstats[i][j].valwidth);
blstats[i][j].val[0] = '\0';
} else
blstats[i][j].val = (char *) 0;
#ifdef STATUS_HILITES
blstats[i][j].thresholds = keep_hilite_chain;
#endif
}
}
}
/*
* This compares the previous stat with the current stat,
* and returns one of the following results based on that:
*
* if prev_value < new_value (stat went up, increased)
* return 1
*
* if prev_value > new_value (stat went down, decreased)
* return -1
*
* if prev_value == new_value (stat stayed the same)
* return 0
*
* Special cases:
* - for bitmasks, 0 = stayed the same, 1 = changed
* - for strings, 0 = stayed the same, 1 = changed
*
*/
STATIC_OVL int
compare_blstats(bl1, bl2)
struct istat_s *bl1, *bl2;
{
int anytype, result = 0;
if (!bl1 || !bl2) {
panic("compare_blstat: bad istat pointer %s, %s",
fmt_ptr((genericptr_t) bl1), fmt_ptr((genericptr_t) bl2));
}
anytype = bl1->anytype;
if ((!bl1->a.a_void || !bl2->a.a_void)
&& (anytype == ANY_IPTR || anytype == ANY_UPTR || anytype == ANY_LPTR
|| anytype == ANY_ULPTR)) {
panic("compare_blstat: invalid pointer %s, %s",
fmt_ptr((genericptr_t) bl1->a.a_void),
fmt_ptr((genericptr_t) bl2->a.a_void));
}
switch (anytype) {
case ANY_INT:
result = (bl1->a.a_int < bl2->a.a_int)
? 1
: (bl1->a.a_int > bl2->a.a_int) ? -1 : 0;
break;
case ANY_IPTR:
result = (*bl1->a.a_iptr < *bl2->a.a_iptr)
? 1
: (*bl1->a.a_iptr > *bl2->a.a_iptr) ? -1 : 0;
break;
case ANY_LONG:
result = (bl1->a.a_long < bl2->a.a_long)
? 1
: (bl1->a.a_long > bl2->a.a_long) ? -1 : 0;
break;
case ANY_LPTR:
result = (*bl1->a.a_lptr < *bl2->a.a_lptr)
? 1
: (*bl1->a.a_lptr > *bl2->a.a_lptr) ? -1 : 0;
break;
case ANY_UINT:
result = (bl1->a.a_uint < bl2->a.a_uint)
? 1
: (bl1->a.a_uint > bl2->a.a_uint) ? -1 : 0;
break;
case ANY_UPTR:
result = (*bl1->a.a_uptr < *bl2->a.a_uptr)
? 1
: (*bl1->a.a_uptr > *bl2->a.a_uptr) ? -1 : 0;
break;
case ANY_ULONG:
result = (bl1->a.a_ulong < bl2->a.a_ulong)
? 1
: (bl1->a.a_ulong > bl2->a.a_ulong) ? -1 : 0;
break;
case ANY_ULPTR:
result = (*bl1->a.a_ulptr < *bl2->a.a_ulptr)
? 1
: (*bl1->a.a_ulptr > *bl2->a.a_ulptr) ? -1 : 0;
break;
case ANY_STR:
result = sgn(strcmp(bl1->val, bl2->val));
break;
case ANY_MASK32:
result = (bl1->a.a_ulong != bl2->a.a_ulong);
break;
default:
result = 1;
}
return result;
}
STATIC_OVL char *
anything_to_s(buf, a, anytype)
char *buf;
anything *a;
int anytype;
{
if (!buf)
return (char *) 0;
switch (anytype) {
case ANY_ULONG:
Sprintf(buf, "%lu", a->a_ulong);
break;
case ANY_MASK32:
Sprintf(buf, "%lx", a->a_ulong);
break;
case ANY_LONG:
Sprintf(buf, "%ld", a->a_long);
break;
case ANY_INT:
Sprintf(buf, "%d", a->a_int);
break;
case ANY_UINT:
Sprintf(buf, "%u", a->a_uint);
break;
case ANY_IPTR:
Sprintf(buf, "%d", *a->a_iptr);
break;
case ANY_LPTR:
Sprintf(buf, "%ld", *a->a_lptr);
break;
case ANY_ULPTR:
Sprintf(buf, "%lu", *a->a_ulptr);
break;
case ANY_UPTR:
Sprintf(buf, "%u", *a->a_uptr);
break;
case ANY_STR: /* do nothing */
;
break;
default:
buf[0] = '\0';
}
return buf;
}
#ifdef STATUS_HILITES
STATIC_OVL void
s_to_anything(a, buf, anytype)
anything *a;
char *buf;
int anytype;
{
if (!buf || !a)
return;
switch (anytype) {
case ANY_LONG:
a->a_long = atol(buf);
break;
case ANY_INT:
a->a_int = atoi(buf);
break;
case ANY_UINT:
a->a_uint = (unsigned) atoi(buf);
break;
case ANY_ULONG:
a->a_ulong = (unsigned long) atol(buf);
break;
case ANY_IPTR:
if (a->a_iptr)
*a->a_iptr = atoi(buf);
break;
case ANY_UPTR:
if (a->a_uptr)
*a->a_uptr = (unsigned) atoi(buf);
break;
case ANY_LPTR:
if (a->a_lptr)
*a->a_lptr = atol(buf);
break;
case ANY_ULPTR:
if (a->a_ulptr)
*a->a_ulptr = (unsigned long) atol(buf);
break;
case ANY_MASK32:
a->a_ulong = (unsigned long) atol(buf);
break;
default:
a->a_void = 0;
break;
}
return;
}
#endif /* STATUS_HILITES */
STATIC_OVL int
percentage(bl, maxbl)
struct istat_s *bl, *maxbl;
{
int result = 0;
int anytype;
int ival;
long lval;
unsigned uval;
unsigned long ulval;
if (!bl || !maxbl) {
impossible("percentage: bad istat pointer %s, %s",
fmt_ptr((genericptr_t) bl), fmt_ptr((genericptr_t) maxbl));
return 0;
}
ival = 0, lval = 0L, uval = 0U, ulval = 0UL;
anytype = bl->anytype;
if (maxbl->a.a_void) {
switch (anytype) {
case ANY_INT:
ival = bl->a.a_int;
result = ((100 * ival) / maxbl->a.a_int);
break;
case ANY_LONG:
lval = bl->a.a_long;
result = (int) ((100L * lval) / maxbl->a.a_long);
break;
case ANY_UINT:
uval = bl->a.a_uint;
result = (int) ((100U * uval) / maxbl->a.a_uint);
break;
case ANY_ULONG:
ulval = bl->a.a_ulong;
result = (int) ((100UL * ulval) / maxbl->a.a_ulong);
break;
case ANY_IPTR:
ival = *bl->a.a_iptr;
result = ((100 * ival) / (*maxbl->a.a_iptr));
break;
case ANY_LPTR:
lval = *bl->a.a_lptr;
result = (int) ((100L * lval) / (*maxbl->a.a_lptr));
break;
case ANY_UPTR:
uval = *bl->a.a_uptr;
result = (int) ((100U * uval) / (*maxbl->a.a_uptr));
break;
case ANY_ULPTR:
ulval = *bl->a.a_ulptr;
result = (int) ((100UL * ulval) / (*maxbl->a.a_ulptr));
break;
}
}
/* don't let truncation from integer division produce a zero result
from a non-zero input; note: if we ever change to something like
((((1000 * val) / max) + 5) / 10) for a rounded result, we'll
also need to check for and convert false 100 to 99 */
if (result == 0 && (ival != 0 || lval != 0L || uval != 0U || ulval != 0UL))
result = 1;
return result;
}
#ifdef STATUS_HILITES
/****************************************************************************/
/* Core status hiliting support */
/****************************************************************************/
struct hilite_s status_hilites[MAXBLSTATS];
static struct fieldid_t {
const char *fieldname;
enum statusfields fldid;
} fieldids_alias[] = {
{ "characteristics", BL_CHARACTERISTICS },
{ "encumbrance", BL_CAP },
{ "experience-points", BL_EXP },
{ "dx", BL_DX },
{ "co", BL_CO },
{ "con", BL_CO },
{ "points", BL_SCORE },
{ "cap", BL_CAP },
{ "pw", BL_ENE },
{ "pw-max", BL_ENEMAX },
{ "xl", BL_XP },
{ "xplvl", BL_XP },
{ "ac", BL_AC },
{ "hit-dice", BL_HD },
{ "turns", BL_TIME },
{ "hp", BL_HP },
{ "hp-max", BL_HPMAX },
{ "dgn", BL_LEVELDESC },
{ "xp", BL_EXP },
{ "exp", BL_EXP },
{ "flags", BL_CONDITION },
{0, BL_FLUSH }
};
/* format arguments */
static const char threshold_value[] = "hilite_status threshold ",
is_out_of_range[] = " is out of range";
const char *
bl_idx_to_fldname(idx)
int idx;
{
if (idx >= 0 && idx < MAXBLSTATS)
return initblstats[idx].fldname;
return (const char *) 0;
}
/* field name to bottom line index */
STATIC_OVL enum statusfields
fldname_to_bl_indx(name)
const char *name;
{
int i, nmatches = 0, fld = 0;
if (name && *name) {
/* check matches to canonical names */
for (i = 0; i < SIZE(initblstats); i++)
if (fuzzymatch(initblstats[i].fldname, name, " -_", TRUE)) {
fld = initblstats[i].fld;
nmatches++;
}
if (!nmatches) {
/* check aliases */
for (i = 0; fieldids_alias[i].fieldname; i++)
if (fuzzymatch(fieldids_alias[i].fieldname, name,
" -_", TRUE)) {
fld = fieldids_alias[i].fldid;
nmatches++;
}
}
if (!nmatches) {
/* check partial matches to canonical names */
int len = (int) strlen(name);
for (i = 0; i < SIZE(initblstats); i++)
if (!strncmpi(name, initblstats[i].fldname, len)) {
fld = initblstats[i].fld;
nmatches++;
}
}
}
return (nmatches == 1) ? fld : BL_FLUSH;
}
STATIC_OVL boolean
hilite_reset_needed(bl_p, augmented_time)
struct istat_s *bl_p;
long augmented_time;
{
struct hilite_s *tl = bl_p->thresholds;
/*
* This 'multi' handling may need some tuning...
*/
if (multi)
return FALSE;
if (bl_p->time == 0 || bl_p->time >= augmented_time)
return FALSE;
while (tl) {
/* only this style times out (includes general 'changed'
as well as specific 'up' and 'down') */
if (tl->behavior == BL_TH_UPDOWN)
return TRUE;
tl = tl->next;
}
return FALSE;
}
/* called by options handling when 'statushilites' boolean is toggled */
void
reset_status_hilites()
{
if (iflags.hilite_delta) {
int i;
for (i = 0; i < MAXBLSTATS; ++i)
blstats[0][i].time = blstats[1][i].time = 0L;
update_all = TRUE;
}
context.botlx = TRUE;
}
/* test whether the text from a title rule matches the string for
title-while-polymorphed in the 'textmatch' menu */
STATIC_OVL boolean
noneoftheabove(hl_text)
const char *hl_text;
{
if (fuzzymatch(hl_text, "none of the above", "\" -_", TRUE)
|| fuzzymatch(hl_text, "(polymorphed)", "\"()", TRUE)
|| fuzzymatch(hl_text, "none of the above (polymorphed)",
"\" -_()", TRUE))
return TRUE;
return FALSE;
}
STATIC_OVL void
merge_bestcolor(bestcolor, newcolor)
int *bestcolor;
int newcolor;
{
int natr = HL_UNDEF, nclr = NO_COLOR;
split_clridx(newcolor, &nclr, &natr);
if (nclr != NO_COLOR)
*bestcolor = (*bestcolor & 0xff00) | nclr;
if (natr != HL_UNDEF) {
if (natr == HL_NONE)
*bestcolor = *bestcolor & 0x00ff; /* reset all attributes */
else
*bestcolor |= (natr << 8); /* merge attributes */
}
}
/*
* get_hilite_color
*
* Figures out, based on the value and the
* direction it is moving, the color that the field
* should be displayed in.
*
*
* Provide get_hilite_color() with the following
* to work with:
* actual value vp
* useful for BL_TH_VAL_ABSOLUTE
* indicator of down, up, or the same (-1, 1, 0) chg
* useful for BL_TH_UPDOWN or change detection
* percentage (current value percentage of max value) pc
* useful for BL_TH_VAL_PERCENTAGE
*
* Get back:
* color based on user thresholds set in config file.
* The rightmost 8 bits contain a color index.
* The 8 bits to the left of that contain
* the attribute bits.
* color = 0x00FF
* attrib= 0xFF00
*/
STATIC_OVL void
get_hilite_color(idx, fldidx, vp, chg, pc, colorptr)
int idx, fldidx, chg, pc;
genericptr_t vp;
int *colorptr;
{
int bestcolor = NO_COLOR;
struct hilite_s *hl;
anything *value = (anything *) vp;
char *txtstr;
if (!colorptr || fldidx < 0 || fldidx >= MAXBLSTATS)
return;
if (blstats[idx][fldidx].thresholds) {
/* there are hilites set here */
int max_pc = -1, min_pc = 101;
int max_val = -LARGEST_INT, min_val = LARGEST_INT;
boolean exactmatch = FALSE, updown = FALSE, changed = FALSE,
perc_or_abs = FALSE;
/* min_/max_ are used to track best fit */
for (hl = blstats[idx][fldidx].thresholds; hl; hl = hl->next) {
/* if we've already matched a temporary highlight, it takes
precedence over all persistent ones; we still process
updown rules to get the last one which qualifies */
if ((updown || changed) && hl->behavior != BL_TH_UPDOWN)
continue;
/* among persistent highlights, if a 'percentage' or 'absolute'
rule has been matched, it takes precedence over 'always' */
if (perc_or_abs && hl->behavior == BL_TH_ALWAYS_HILITE)
continue;
switch (hl->behavior) {
case BL_TH_VAL_PERCENTAGE:
if (hl->rel == EQ_VALUE && pc == hl->value.a_int) {
merge_bestcolor(&bestcolor, hl->coloridx);
min_pc = max_pc = hl->value.a_int;
exactmatch = perc_or_abs = TRUE;
} else if (exactmatch) {
; /* already found best fit, skip lt,ge,&c */
} else if (hl->rel == LT_VALUE
&& (pc < hl->value.a_int)
&& (hl->value.a_int <= min_pc)) {
merge_bestcolor(&bestcolor, hl->coloridx);
min_pc = hl->value.a_int;
perc_or_abs = TRUE;
} else if (hl->rel == LE_VALUE
&& (pc <= hl->value.a_int)
&& (hl->value.a_int <= min_pc)) {
merge_bestcolor(&bestcolor, hl->coloridx);
min_pc = hl->value.a_int;
perc_or_abs = TRUE;
} else if (hl->rel == GT_VALUE
&& (pc > hl->value.a_int)
&& (hl->value.a_int >= max_pc)) {
merge_bestcolor(&bestcolor, hl->coloridx);
max_pc = hl->value.a_int;
perc_or_abs = TRUE;
} else if (hl->rel == GE_VALUE
&& (pc >= hl->value.a_int)
&& (hl->value.a_int >= max_pc)) {
merge_bestcolor(&bestcolor, hl->coloridx);
max_pc = hl->value.a_int;
perc_or_abs = TRUE;
}
break;
case BL_TH_UPDOWN:
/* specific 'up' or 'down' takes precedence over general
'changed' regardless of their order in the rule set */
if (chg < 0 && hl->rel == LT_VALUE) {
merge_bestcolor(&bestcolor, hl->coloridx);
updown = TRUE;
} else if (chg > 0 && hl->rel == GT_VALUE) {
merge_bestcolor(&bestcolor, hl->coloridx);
updown = TRUE;
} else if (chg != 0 && hl->rel == EQ_VALUE && !updown) {
merge_bestcolor(&bestcolor, hl->coloridx);
changed = TRUE;
}
break;
case BL_TH_VAL_ABSOLUTE:
/*
* TODO:
* This covers data type ANY_INT. We need to handle ANY_LONG
* separately using a_long and new min_lval, max_lval.
*/
if (hl->rel == EQ_VALUE && hl->value.a_int == value->a_int) {
merge_bestcolor(&bestcolor, hl->coloridx);
min_val = max_val = hl->value.a_int;
exactmatch = perc_or_abs = TRUE;
} else if (exactmatch) {
; /* already found best fit, skip lt,ge,&c */
} else if (hl->rel == LT_VALUE
&& (value->a_int < hl->value.a_int)
&& (hl->value.a_int <= min_val)) {
merge_bestcolor(&bestcolor, hl->coloridx);
min_val = hl->value.a_int;
perc_or_abs = TRUE;
} else if (hl->rel == LE_VALUE
&& (value->a_int <= hl->value.a_int)
&& (hl->value.a_int <= min_val)) {
merge_bestcolor(&bestcolor, hl->coloridx);
min_val = hl->value.a_int;
perc_or_abs = TRUE;
} else if (hl->rel == GT_VALUE
&& (value->a_int > hl->value.a_int)
&& (hl->value.a_int >= max_val)) {
merge_bestcolor(&bestcolor, hl->coloridx);
max_val = hl->value.a_int;
perc_or_abs = TRUE;
} else if (hl->rel == GE_VALUE
&& (value->a_int >= hl->value.a_int)
&& (hl->value.a_int >= max_val)) {
merge_bestcolor(&bestcolor, hl->coloridx);
max_val = hl->value.a_int;
perc_or_abs = TRUE;
}
break;
case BL_TH_TEXTMATCH:
txtstr = blstats[idx][fldidx].val;
if (fldidx == BL_TITLE)
/* "<name> the <rank-title>", skip past "<name> the " */
txtstr += (strlen(plname) + sizeof " the " - sizeof "");
if (hl->rel == TXT_VALUE && hl->textmatch[0]) {
if (fuzzymatch(hl->textmatch, txtstr, "\" -_", TRUE)) {
merge_bestcolor(&bestcolor, hl->coloridx);
exactmatch = TRUE;
} else if (exactmatch) {
; /* already found best fit, skip "noneoftheabove" */
} else if (fldidx == BL_TITLE
&& Upolyd && noneoftheabove(hl->textmatch)) {
merge_bestcolor(&bestcolor, hl->coloridx);
}
}
break;
case BL_TH_ALWAYS_HILITE:
merge_bestcolor(&bestcolor, hl->coloridx);
break;
case BL_TH_NONE:
break;
default:
break;
}
}
}
*colorptr = bestcolor;
return;
}
STATIC_OVL void
split_clridx(idx, coloridx, attrib)
int idx;
int *coloridx, *attrib;
{
if (coloridx)
*coloridx = idx & 0x00FF;
if (attrib)
*attrib = (idx >> 8) & 0x00FF;
}
/*
* This is the parser for the hilite options.
*
* parse_status_hl1() separates each hilite entry into
* a set of field threshold/action component strings,
* then calls parse_status_hl2() to parse further
* and configure the hilite.
*/
boolean
parse_status_hl1(op, from_configfile)
char *op;
boolean from_configfile;
{
#define MAX_THRESH 21
char hsbuf[MAX_THRESH][QBUFSZ];
boolean rslt, badopt = FALSE;
int i, fldnum, ccount = 0;
char c;
fldnum = 0;
for (i = 0; i < MAX_THRESH; ++i) {
hsbuf[i][0] = '\0';
}
while (*op && fldnum < MAX_THRESH && ccount < (QBUFSZ - 2)) {
c = lowc(*op);
if (c == ' ') {
if (fldnum >= 1) {
if (fldnum == 1 && strcmpi(hsbuf[0], "title") == 0) {
/* spaces are allowed in title */
hsbuf[fldnum][ccount++] = c;
hsbuf[fldnum][ccount] = '\0';
op++;
continue;
}
rslt = parse_status_hl2(hsbuf, from_configfile);
if (!rslt) {
badopt = TRUE;
break;
}
}
for (i = 0; i < MAX_THRESH; ++i) {
hsbuf[i][0] = '\0';
}
fldnum = 0;
ccount = 0;
} else if (c == '/') {
fldnum++;
ccount = 0;
} else {
hsbuf[fldnum][ccount++] = c;
hsbuf[fldnum][ccount] = '\0';
}
op++;
}
if (fldnum >= 1 && !badopt) {
rslt = parse_status_hl2(hsbuf, from_configfile);
if (!rslt)
badopt = TRUE;
}
if (badopt)
return FALSE;
return TRUE;
}
/* is str in the format of "[<>]?=?[-+]?[0-9]+%?" regex */
STATIC_OVL boolean
is_ltgt_percentnumber(str)
const char *str;
{
const char *s = str;
if (*s == '<' || *s == '>')
s++;
if (*s == '=')
s++;
if (*s == '-' || *s == '+')
s++;
if (!digit(*s))
return FALSE;
while (digit(*s))
s++;
if (*s == '%')
s++;
return (*s == '\0');
}
/* does str only contain "<>=-+0-9%" chars */
STATIC_OVL boolean
has_ltgt_percentnumber(str)
const char *str;
{
const char *s = str;
while (*s) {
if (!index("<>=-+0123456789%", *s))
return FALSE;
s++;
}
return TRUE;
}
/* splitsubfields(): splits str in place into '+' or '&' separated strings.
* returns number of strings, or -1 if more than maxsf or MAX_SUBFIELDS
*/
#define MAX_SUBFIELDS 16
STATIC_OVL int
splitsubfields(str, sfarr, maxsf)
char *str;
char ***sfarr;
int maxsf;
{
static char *subfields[MAX_SUBFIELDS];
char *st = (char *) 0;
int sf = 0;
if (!str)
return 0;
for (sf = 0; sf < MAX_SUBFIELDS; ++sf)
subfields[sf] = (char *) 0;
maxsf = (maxsf == 0) ? MAX_SUBFIELDS : min(maxsf, MAX_SUBFIELDS);
if (index(str, '+') || index(str, '&')) {
char *c = str;
sf = 0;
st = c;
while (*c && sf < maxsf) {
if (*c == '&' || *c == '+') {
*c = '\0';
subfields[sf] = st;
st = c+1;
sf++;
}
c++;
}
if (sf >= maxsf - 1)
return -1;
if (!*c && c != st)
subfields[sf++] = st;
} else {
sf = 1;
subfields[0] = str;
}
*sfarr = subfields;
return sf;
}
#undef MAX_SUBFIELDS
boolean
is_fld_arrayvalues(str, arr, arrmin, arrmax, retidx)
const char *str;
const char *const *arr;
int arrmin, arrmax;
int *retidx;
{
int i;
for (i = arrmin; i < arrmax; i++)
if (!strcmpi(str, arr[i])) {
*retidx = i;
return TRUE;
}
return FALSE;
}
int
query_arrayvalue(querystr, arr, arrmin, arrmax)
const char *querystr;
const char *const *arr;
int arrmin, arrmax;
{
int i, res, ret = arrmin - 1;
winid tmpwin;
anything any;
menu_item *picks = (menu_item *) 0;
int adj = (arrmin > 0) ? 1 : arrmax;
tmpwin = create_nhwindow(NHW_MENU);
start_menu(tmpwin);
for (i = arrmin; i < arrmax; i++) {
any = zeroany;
any.a_int = i + adj;
add_menu(tmpwin, NO_GLYPH, &any, 0, 0, ATR_NONE,
arr[i], MENU_UNSELECTED);
}
end_menu(tmpwin, querystr);
res = select_menu(tmpwin, PICK_ONE, &picks);
destroy_nhwindow(tmpwin);
if (res > 0) {
ret = picks->item.a_int - adj;
free((genericptr_t) picks);
}
return ret;
}
void
status_hilite_add_threshold(fld, hilite)
int fld;
struct hilite_s *hilite;
{
struct hilite_s *new_hilite;
if (!hilite)
return;
/* alloc and initialize a new hilite_s struct */
new_hilite = (struct hilite_s *) alloc(sizeof (struct hilite_s));
*new_hilite = *hilite; /* copy struct */
new_hilite->set = TRUE;
new_hilite->fld = fld;
new_hilite->next = blstats[0][fld].thresholds;
blstats[0][fld].thresholds = new_hilite;
/* sort_hilites(fld) */
/* current and prev must both point at the same hilites */
blstats[1][fld].thresholds = blstats[0][fld].thresholds;
}
STATIC_OVL boolean
parse_status_hl2(s, from_configfile)
char (*s)[QBUFSZ];
boolean from_configfile;
{
char *tmp, *how;
int sidx = 0, i = -1, dt = -1;
int coloridx = -1, successes = 0;
int disp_attrib = 0;
boolean percent, changed, numeric, down, up,
gt, lt, ge, le, eq, txtval, always;
const char *txt;
enum statusfields fld = BL_FLUSH;
struct hilite_s hilite;
char tmpbuf[BUFSZ];
static const char *aligntxt[] = { "chaotic", "neutral", "lawful" };
/* hu_stat[] from eat.c has trailing spaces which foul up comparisons */
static const char *hutxt[] = { "Satiated", "", "Hungry", "Weak",
"Fainting", "Fainted", "Starved" };
/* Examples:
3.6.1:
OPTION=hilite_status: hitpoints/<10%/red
OPTION=hilite_status: hitpoints/<10%/red/<5%/purple/1/red+blink+inverse
OPTION=hilite_status: experience/down/red/up/green
OPTION=hilite_status: cap/strained/yellow/overtaxed/orange
OPTION=hilite_status: title/always/blue
OPTION=hilite_status: title/blue
*/
/* field name to statusfield */
fld = fldname_to_bl_indx(s[sidx]);
if (fld == BL_CHARACTERISTICS) {
boolean res = FALSE;
/* recursively set each of strength, dexterity, constitution, &c */
for (fld = BL_STR; fld <= BL_CH; fld++) {
Strcpy(s[sidx], initblstats[fld].fldname);
res = parse_status_hl2(s, from_configfile);
if (!res)
return FALSE;
}
return TRUE;
}
if (fld == BL_FLUSH) {
config_error_add("Unknown status field '%s'", s[sidx]);
return FALSE;
}
if (fld == BL_CONDITION)
return parse_condition(s, sidx);
++sidx;
while (s[sidx]) {
char buf[BUFSZ], **subfields;
int sf = 0; /* subfield count */
int kidx;
txt = (const char *)0;
percent = numeric = always = FALSE;
down = up = changed = FALSE;
gt = ge = eq = le = lt = txtval = FALSE;
/* threshold value */
if (!s[sidx][0])
return TRUE;
memset((genericptr_t) &hilite, 0, sizeof (struct hilite_s));
hilite.set = FALSE; /* mark it "unset" */
hilite.fld = fld;
if (*s[sidx + 1] == '\0' || !strcmpi(s[sidx], "always")) {
/* "field/always/color" OR "field/color" */
always = TRUE;
if (*s[sidx + 1] == '\0')
sidx--;
} else if (!strcmpi(s[sidx], "up") || !strcmpi(s[sidx], "down")) {
if (initblstats[fld].anytype == ANY_STR)
/* ordered string comparison is supported but LT/GT for
the string fields (title, dungeon-level, alignment)
is pointless; treat 'up' or 'down' for string fields
as 'changed' rather than rejecting them outright */
;
else if (!strcmpi(s[sidx], "down"))
down = TRUE;
else
up = TRUE;
changed = TRUE;
} else if (fld == BL_CAP
&& is_fld_arrayvalues(s[sidx], enc_stat,
SLT_ENCUMBER, OVERLOADED + 1,
&kidx)) {
txt = enc_stat[kidx];
txtval = TRUE;
} else if (fld == BL_ALIGN
&& is_fld_arrayvalues(s[sidx], aligntxt, 0, 3, &kidx)) {
txt = aligntxt[kidx];
txtval = TRUE;
} else if (fld == BL_HUNGER
&& is_fld_arrayvalues(s[sidx], hutxt,
SATIATED, STARVED + 1, &kidx)) {
txt = hu_stat[kidx]; /* store hu_stat[] val, not hutxt[] */
txtval = TRUE;
} else if (!strcmpi(s[sidx], "changed")) {
changed = TRUE;
} else if (is_ltgt_percentnumber(s[sidx])) {
const char *op;
tmp = s[sidx]; /* is_ltgt_() guarantees [<>]?=?[-+]?[0-9]+%? */
if (strchr(tmp, '%'))
percent = TRUE;
if (*tmp == '<') {
if (tmp[1] == '=')
le = TRUE;
else
lt = TRUE;
} else if (*tmp == '>') {
if (tmp[1] == '=')
ge = TRUE;
else
gt = TRUE;
}
/* '%', '<', '>' have served their purpose, '=' is either
part of '<' or '>' or optional for '=N', unary '+' is
just decorative, so get rid of them, leaving -?[0-9]+ */
tmp = stripchars(tmpbuf, "%<>=+", tmp);
numeric = TRUE;
dt = percent ? ANY_INT : initblstats[fld].anytype;
(void) s_to_anything(&hilite.value, tmp, dt);
op = gt ? ">" : ge ? ">=" : lt ? "<" : le ? "<=" : "=";
if (dt == ANY_INT
/* AC is the only field where negative values make sense but
accept >-1 for other fields; reject <0 for non-AC */
&& (hilite.value.a_int
< ((fld == BL_AC) ? -128 : gt ? -1 : lt ? 1 : 0)
/* percentages have another more comprehensive check below */
|| hilite.value.a_int > (percent ? (lt ? 101 : 100)
: LARGEST_INT))) {
config_error_add("%s'%s%d%s'%s", threshold_value,
op, hilite.value.a_int, percent ? "%" : "",
is_out_of_range);
return FALSE;
} else if (dt == ANY_LONG
&& (hilite.value.a_long < (gt ? -1L : lt ? 1L : 0L))) {
config_error_add("%s'%s%ld'%s", threshold_value,
op, hilite.value.a_long, is_out_of_range);
return FALSE;
}
} else if (initblstats[fld].anytype == ANY_STR) {
txt = s[sidx];
txtval = TRUE;
} else {
config_error_add(has_ltgt_percentnumber(s[sidx])
? "Wrong format '%s', expected a threshold number or percent"
: "Unknown behavior '%s'",
s[sidx]);
return FALSE;
}
/* relationships {LT_VALUE, LE_VALUE, EQ_VALUE, GE_VALUE, GT_VALUE} */
if (gt || up)
hilite.rel = GT_VALUE;
else if (lt || down)
hilite.rel = LT_VALUE;
else if (ge)
hilite.rel = GE_VALUE;
else if (le)
hilite.rel = LE_VALUE;
else if (eq || percent || numeric || changed)
hilite.rel = EQ_VALUE;
else if (txtval)
hilite.rel = TXT_VALUE;
else
hilite.rel = LT_VALUE;
if (initblstats[fld].anytype == ANY_STR && (percent || numeric)) {
config_error_add("Field '%s' does not support numeric values",
initblstats[fld].fldname);
return FALSE;
}
if (percent) {
if (initblstats[fld].idxmax < 0) {
config_error_add("Cannot use percent with '%s'",
initblstats[fld].fldname);
return FALSE;
} else if ((hilite.value.a_int < -1)
|| (hilite.value.a_int == -1
&& hilite.value.a_int != GT_VALUE)
|| (hilite.value.a_int == 0
&& hilite.rel == LT_VALUE)
|| (hilite.value.a_int == 100
&& hilite.rel == GT_VALUE)
|| (hilite.value.a_int == 101
&& hilite.value.a_int != LT_VALUE)
|| (hilite.value.a_int > 101)) {
config_error_add(
"hilite_status: invalid percentage value '%s%d%%'",
(hilite.rel == LT_VALUE) ? "<"
: (hilite.rel == LE_VALUE) ? "<="
: (hilite.rel == GT_VALUE) ? ">"
: (hilite.rel == GE_VALUE) ? ">="
: "=",
hilite.value.a_int);
return FALSE;
}
}
/* actions */
sidx++;
how = s[sidx];
if (!how) {
if (!successes)
return FALSE;
}
coloridx = -1;
Strcpy(buf, how);
sf = splitsubfields(buf, &subfields, 0);
if (sf < 1)
return FALSE;
disp_attrib = HL_UNDEF;
for (i = 0; i < sf; ++i) {
int a = match_str2attr(subfields[i], FALSE);
if (a == ATR_DIM)
disp_attrib |= HL_DIM;
else if (a == ATR_BLINK)
disp_attrib |= HL_BLINK;
else if (a == ATR_ULINE)
disp_attrib |= HL_ULINE;
else if (a == ATR_INVERSE)
disp_attrib |= HL_INVERSE;
else if (a == ATR_BOLD)
disp_attrib |= HL_BOLD;
else if (a == ATR_NONE)
disp_attrib = HL_NONE;
else {
int c = match_str2clr(subfields[i]);
if (c >= CLR_MAX || coloridx != -1)
return FALSE;
coloridx = c;
}
}
if (coloridx == -1)
coloridx = NO_COLOR;
/* Assign the values */
hilite.coloridx = coloridx | (disp_attrib << 8);
if (always)
hilite.behavior = BL_TH_ALWAYS_HILITE;
else if (percent)
hilite.behavior = BL_TH_VAL_PERCENTAGE;
else if (changed)
hilite.behavior = BL_TH_UPDOWN;
else if (numeric)
hilite.behavior = BL_TH_VAL_ABSOLUTE;
else if (txtval)
hilite.behavior = BL_TH_TEXTMATCH;
else if (hilite.value.a_void)
hilite.behavior = BL_TH_VAL_ABSOLUTE;
else
hilite.behavior = BL_TH_NONE;
hilite.anytype = dt;
if (hilite.behavior == BL_TH_TEXTMATCH && txt
&& strlen(txt) < QBUFSZ - 1) {
Strcpy(hilite.textmatch, txt);
(void) trimspaces(hilite.textmatch);
}
status_hilite_add_threshold(fld, &hilite);
successes++;
sidx++;
}
return TRUE;
}
const struct condmap valid_conditions[] = {
{"stone", BL_MASK_STONE},
{"slime", BL_MASK_SLIME},
{"strngl", BL_MASK_STRNGL},
{"foodPois", BL_MASK_FOODPOIS},
{"termIll", BL_MASK_TERMILL},
{"blind", BL_MASK_BLIND},
{"deaf", BL_MASK_DEAF},
{"stun", BL_MASK_STUN},
{"conf", BL_MASK_CONF},
{"hallu", BL_MASK_HALLU},
{"lev", BL_MASK_LEV},
{"fly", BL_MASK_FLY},
{"ride", BL_MASK_RIDE},
};
const struct condmap condition_aliases[] = {
{"strangled", BL_MASK_STRNGL},
{"all", BL_MASK_STONE | BL_MASK_SLIME | BL_MASK_STRNGL |
BL_MASK_FOODPOIS | BL_MASK_TERMILL |
BL_MASK_BLIND | BL_MASK_DEAF | BL_MASK_STUN |
BL_MASK_CONF | BL_MASK_HALLU |
BL_MASK_LEV | BL_MASK_FLY | BL_MASK_RIDE },
{"major_troubles", BL_MASK_STONE | BL_MASK_SLIME | BL_MASK_STRNGL |
BL_MASK_FOODPOIS | BL_MASK_TERMILL},
{"minor_troubles", BL_MASK_BLIND | BL_MASK_DEAF | BL_MASK_STUN |
BL_MASK_CONF | BL_MASK_HALLU},
{"movement", BL_MASK_LEV | BL_MASK_FLY | BL_MASK_RIDE}
};
unsigned long
query_conditions()
{
int i,res;
unsigned long ret = 0UL;
winid tmpwin;
anything any;
menu_item *picks = (menu_item *) 0;
tmpwin = create_nhwindow(NHW_MENU);
start_menu(tmpwin);
for (i = 0; i < SIZE(valid_conditions); i++) {
any = zeroany;
any.a_ulong = valid_conditions[i].bitmask;
add_menu(tmpwin, NO_GLYPH, &any, 0, 0, ATR_NONE,
valid_conditions[i].id, MENU_UNSELECTED);
}
end_menu(tmpwin, "Choose status conditions");
res = select_menu(tmpwin, PICK_ANY, &picks);
destroy_nhwindow(tmpwin);
if (res > 0) {
for (i = 0; i < res; i++)
ret |= picks[i].item.a_ulong;
free((genericptr_t) picks);
}
return ret;
}
STATIC_OVL char *
conditionbitmask2str(ul)
unsigned long ul;
{
static char buf[BUFSZ];
int i;
boolean first = TRUE;
const char *alias = (char *) 0;
buf[0] = '\0';
if (!ul)
return buf;
for (i = 1; i < SIZE(condition_aliases); i++)
if (condition_aliases[i].bitmask == ul)
alias = condition_aliases[i].id;
for (i = 0; i < SIZE(valid_conditions); i++)
if ((valid_conditions[i].bitmask & ul) != 0UL) {
Sprintf(eos(buf), "%s%s", (first) ? "" : "+",
valid_conditions[i].id);
first = FALSE;
}
if (!first && alias)
Sprintf(buf, "%s", alias);
return buf;
}
STATIC_OVL unsigned long
match_str2conditionbitmask(str)
const char *str;
{
int i, nmatches = 0;
unsigned long mask = 0UL;
if (str && *str) {
/* check matches to canonical names */
for (i = 0; i < SIZE(valid_conditions); i++)
if (fuzzymatch(valid_conditions[i].id, str, " -_", TRUE)) {
mask |= valid_conditions[i].bitmask;
nmatches++;
}
if (!nmatches) {
/* check aliases */
for (i = 0; i < SIZE(condition_aliases); i++)
if (fuzzymatch(condition_aliases[i].id, str, " -_", TRUE)) {
mask |= condition_aliases[i].bitmask;
nmatches++;
}
}
if (!nmatches) {
/* check partial matches to aliases */
int len = (int) strlen(str);
for (i = 0; i < SIZE(condition_aliases); i++)
if (!strncmpi(str, condition_aliases[i].id, len)) {
mask |= condition_aliases[i].bitmask;
nmatches++;
}
}
}
return mask;
}
STATIC_OVL unsigned long
str2conditionbitmask(str)
char *str;
{
unsigned long conditions_bitmask = 0UL;
char **subfields;
int i, sf;
sf = splitsubfields(str, &subfields, SIZE(valid_conditions));
if (sf < 1)
return 0UL;
for (i = 0; i < sf; ++i) {
unsigned long bm = match_str2conditionbitmask(subfields[i]);
if (!bm) {
config_error_add("Unknown condition '%s'", subfields[i]);
return 0UL;
}
conditions_bitmask |= bm;
}
return conditions_bitmask;
}
STATIC_OVL boolean
parse_condition(s, sidx)
char (*s)[QBUFSZ];
int sidx;
{
int i;
int coloridx = NO_COLOR;
char *tmp, *how;
unsigned long conditions_bitmask = 0UL;
boolean success = FALSE;
if (!s)
return FALSE;
/*3.6.1:
OPTION=hilite_status: condition/stone+slime+foodPois/red&inverse */
sidx++;
while(s[sidx]) {
int sf = 0; /* subfield count */
char buf[BUFSZ], **subfields;
tmp = s[sidx];
if (!*tmp) {
if (!success)
config_error_add("Missing condition(s)");
return success;
}
Strcpy(buf, tmp);
conditions_bitmask = str2conditionbitmask(buf);
if (!conditions_bitmask)
return FALSE;
/*
* We have the conditions_bitmask with bits set for
* each ailment we want in a particular color and/or
* attribute, but we need to assign it to an arry of
* bitmasks indexed by the color chosen
* (0 to (CLR_MAX - 1))
* and/or attributes chosen
* (HL_ATTCLR_DIM to (BL_ATTCLR_MAX - 1))
* We still have to parse the colors and attributes out.
*/
/* actions */
sidx++;
how = s[sidx];
if (!how || !*how) {
config_error_add("Missing color+attribute");
return FALSE;
}
Strcpy(buf, how);
sf = splitsubfields(buf, &subfields, 0);
/*
* conditions_bitmask now has bits set representing
* the conditions that player wants represented, but
* now we parse out *how* they will be represented.
*
* Only 1 colour is allowed, but potentially multiple
* attributes are allowed.
*
* We have the following additional array offsets to
* use for storing the attributes beyond the end of
* the color indexes, all of which are less than CLR_MAX.
* HL_ATTCLR_DIM = CLR_MAX
* HL_ATTCLR_BLINK = CLR_MAX + 1
* HL_ATTCLR_ULINE = CLR_MAX + 2
* HL_ATTCLR_INVERSE = CLR_MAX + 3
* HL_ATTCLR_BOLD = CLR_MAX + 4
* HL_ATTCLR_MAX = CLR_MAX + 5 (this is past array boundary)
*
*/
for (i = 0; i < sf; ++i) {
int a = match_str2attr(subfields[i], FALSE);
if (a == ATR_DIM)
cond_hilites[HL_ATTCLR_DIM] |= conditions_bitmask;
else if (a == ATR_BLINK)
cond_hilites[HL_ATTCLR_BLINK] |= conditions_bitmask;
else if (a == ATR_ULINE)
cond_hilites[HL_ATTCLR_ULINE] |= conditions_bitmask;
else if (a == ATR_INVERSE)
cond_hilites[HL_ATTCLR_INVERSE] |= conditions_bitmask;
else if (a == ATR_BOLD)
cond_hilites[HL_ATTCLR_BOLD] |= conditions_bitmask;
else if (a == ATR_NONE) {
cond_hilites[HL_ATTCLR_DIM] &= ~conditions_bitmask;
cond_hilites[HL_ATTCLR_BLINK] &= ~conditions_bitmask;
cond_hilites[HL_ATTCLR_ULINE] &= ~conditions_bitmask;
cond_hilites[HL_ATTCLR_INVERSE] &= ~conditions_bitmask;
cond_hilites[HL_ATTCLR_BOLD] &= ~conditions_bitmask;
} else {
int k = match_str2clr(subfields[i]);
if (k >= CLR_MAX)
return FALSE;
coloridx = k;
}
}
/* set the bits in the appropriate member of the
condition array according to color chosen as index */
cond_hilites[coloridx] |= conditions_bitmask;
success = TRUE;
sidx++;
}
return TRUE;
}
void
clear_status_hilites()
{
int i;
for (i = 0; i < MAXBLSTATS; ++i) {
if (blstats[0][i].thresholds) {
struct hilite_s *temp = blstats[0][i].thresholds,
*next = (struct hilite_s *)0;
while (temp) {
next = temp->next;
free(temp);
blstats[0][i].thresholds = (struct hilite_s *)0;
blstats[1][i].thresholds = blstats[0][i].thresholds;
temp = next;
}
}
}
}
STATIC_OVL char *
hlattr2attrname(attrib, buf, bufsz)
int attrib, bufsz;
char *buf;
{
if (attrib && buf) {
char attbuf[BUFSZ];
int k, first = 0;
attbuf[0] = '\0';
if (attrib == HL_NONE) {
Strcpy(buf, "normal");
return buf;
}
if (attrib & HL_BOLD)
Strcat(attbuf, first++ ? "+bold" : "bold");
if (attrib & HL_INVERSE)
Strcat(attbuf, first++ ? "+inverse" : "inverse");
if (attrib & HL_ULINE)
Strcat(attbuf, first++ ? "+underline" : "underline");
if (attrib & HL_BLINK)
Strcat(attbuf, first++ ? "+blink" : "blink");
if (attrib & HL_DIM)
Strcat(attbuf, first++ ? "+dim" : "dim");
k = strlen(attbuf);
if (k < (bufsz - 1))
Strcpy(buf, attbuf);
return buf;
}
return (char *) 0;
}
struct _status_hilite_line_str {
int id;
int fld;
struct hilite_s *hl;
unsigned long mask;
char str[BUFSZ];
struct _status_hilite_line_str *next;
};
static struct _status_hilite_line_str *status_hilite_str = 0;
static int status_hilite_str_id = 0;
STATIC_OVL void
status_hilite_linestr_add(fld, hl, mask, str)
int fld;
struct hilite_s *hl;
unsigned long mask;
const char *str;
{
struct _status_hilite_line_str *tmp, *nxt;
tmp = (struct _status_hilite_line_str *) alloc(sizeof *tmp);
(void) memset(tmp, 0, sizeof *tmp);
tmp->next = (struct _status_hilite_line_str *) 0;
tmp->id = ++status_hilite_str_id;
tmp->fld = fld;
tmp->hl = hl;
tmp->mask = mask;
if (fld == BL_TITLE)
Strcpy(tmp->str, str);
else
(void) stripchars(tmp->str, " ", str);
if ((nxt = status_hilite_str) != 0) {
while (nxt->next)
nxt = nxt->next;
nxt->next = tmp;
} else {
status_hilite_str = tmp;
}
}
STATIC_OVL void
status_hilite_linestr_done()
{
struct _status_hilite_line_str *nxt, *tmp = status_hilite_str;
while (tmp) {
nxt = tmp->next;
free(tmp);
tmp = nxt;
}
status_hilite_str = (struct _status_hilite_line_str *) 0;
status_hilite_str_id = 0;
}
STATIC_OVL int
status_hilite_linestr_countfield(fld)
int fld;
{
struct _status_hilite_line_str *tmp;
boolean countall = (fld == BL_FLUSH);
int count = 0;
for (tmp = status_hilite_str; tmp; tmp = tmp->next) {
if (countall || tmp->fld == fld)
count++;
}
return count;
}
/* used by options handling, doset(options.c) */
int
count_status_hilites(VOID_ARGS)
{
int count;
status_hilite_linestr_gather();
count = status_hilite_linestr_countfield(BL_FLUSH);
status_hilite_linestr_done();
return count;
}
STATIC_OVL void
status_hilite_linestr_gather_conditions()
{
int i;
struct _cond_map {
unsigned long bm;
unsigned long clratr;
} cond_maps[SIZE(valid_conditions)];
(void) memset(cond_maps, 0,
SIZE(valid_conditions) * sizeof (struct _cond_map));
for (i = 0; i < SIZE(valid_conditions); i++) {
int clr = NO_COLOR;
int atr = HL_NONE;
int j;
for (j = 0; j < CLR_MAX; j++)
if (cond_hilites[j] & valid_conditions[i].bitmask) {
clr = j;
break;
}
if (cond_hilites[HL_ATTCLR_DIM] & valid_conditions[i].bitmask)
atr |= HL_DIM;
if (cond_hilites[HL_ATTCLR_BOLD] & valid_conditions[i].bitmask)
atr |= HL_BOLD;
if (cond_hilites[HL_ATTCLR_BLINK] & valid_conditions[i].bitmask)
atr |= HL_BLINK;
if (cond_hilites[HL_ATTCLR_ULINE] & valid_conditions[i].bitmask)
atr |= HL_ULINE;
if (cond_hilites[HL_ATTCLR_INVERSE] & valid_conditions[i].bitmask)
atr |= HL_INVERSE;
if (atr != HL_NONE)
atr &= ~HL_NONE;
if (clr != NO_COLOR || atr != HL_NONE) {
unsigned long ca = clr | (atr << 8);
boolean added_condmap = FALSE;
for (j = 0; j < SIZE(valid_conditions); j++)
if (cond_maps[j].clratr == ca) {
cond_maps[j].bm |= valid_conditions[i].bitmask;
added_condmap = TRUE;
break;
}
if (!added_condmap) {
for (j = 0; j < SIZE(valid_conditions); j++)
if (!cond_maps[j].bm) {
cond_maps[j].bm = valid_conditions[i].bitmask;
cond_maps[j].clratr = ca;
break;
}
}
}
}
for (i = 0; i < SIZE(valid_conditions); i++)
if (cond_maps[i].bm) {
int clr = NO_COLOR, atr = HL_NONE;
split_clridx(cond_maps[i].clratr, &clr, &atr);
if (clr != NO_COLOR || atr != HL_NONE) {
char clrbuf[BUFSZ];
char attrbuf[BUFSZ];
char condbuf[BUFSZ];
char *tmpattr;
(void) strNsubst(strcpy(clrbuf, clr2colorname(clr)),
" ", "-", 0);
tmpattr = hlattr2attrname(atr, attrbuf, BUFSZ);
if (tmpattr)
Sprintf(eos(clrbuf), "&%s", tmpattr);
Sprintf(condbuf, "condition/%s/%s",
conditionbitmask2str(cond_maps[i].bm), clrbuf);
status_hilite_linestr_add(BL_CONDITION, 0,
cond_maps[i].bm, condbuf);
}
}
}
STATIC_OVL void
status_hilite_linestr_gather()
{
int i;
struct hilite_s *hl;
status_hilite_linestr_done();
for (i = 0; i < MAXBLSTATS; i++) {
hl = blstats[0][i].thresholds;
while (hl) {
status_hilite_linestr_add(i, hl, 0UL, status_hilite2str(hl));
hl = hl->next;
}
}
status_hilite_linestr_gather_conditions();
}
char *
status_hilite2str(hl)
struct hilite_s *hl;
{
static char buf[BUFSZ];
int clr = 0, attr = 0;
char behavebuf[BUFSZ];
char clrbuf[BUFSZ];
char attrbuf[BUFSZ];
char *tmpattr;
const char *op;
if (!hl)
return (char *) 0;
behavebuf[0] = '\0';
clrbuf[0] = '\0';
op = (hl->rel == LT_VALUE) ? "<"
: (hl->rel == LE_VALUE) ? "<="
: (hl->rel == GT_VALUE) ? ">"
: (hl->rel == GE_VALUE) ? ">="
: (hl->rel == EQ_VALUE) ? "="
: 0;
switch (hl->behavior) {
case BL_TH_VAL_PERCENTAGE:
if (op)
Sprintf(behavebuf, "%s%d%%", op, hl->value.a_int);
else
impossible("hl->behavior=percentage, rel error");
break;
case BL_TH_UPDOWN:
if (hl->rel == LT_VALUE)
Sprintf(behavebuf, "down");
else if (hl->rel == GT_VALUE)
Sprintf(behavebuf, "up");
else if (hl->rel == EQ_VALUE)
Sprintf(behavebuf, "changed");
else
impossible("hl->behavior=updown, rel error");
break;
case BL_TH_VAL_ABSOLUTE:
if (op)
Sprintf(behavebuf, "%s%d", op, hl->value.a_int);
else
impossible("hl->behavior=absolute, rel error");
break;
case BL_TH_TEXTMATCH:
if (hl->rel == TXT_VALUE && hl->textmatch[0])
Sprintf(behavebuf, "%s", hl->textmatch);
else
impossible("hl->behavior=textmatch, rel or textmatch error");
break;
case BL_TH_CONDITION:
if (hl->rel == EQ_VALUE)
Sprintf(behavebuf, "%s", conditionbitmask2str(hl->value.a_ulong));
else
impossible("hl->behavior=condition, rel error");
break;
case BL_TH_ALWAYS_HILITE:
Sprintf(behavebuf, "always");
break;
case BL_TH_NONE:
break;
default:
break;
}
split_clridx(hl->coloridx, &clr, &attr);
(void) strNsubst(strcpy(clrbuf, clr2colorname(clr)), " ", "-", 0);
if (attr != HL_UNDEF) {
if ((tmpattr = hlattr2attrname(attr, attrbuf, BUFSZ)) != 0)
Sprintf(eos(clrbuf), "&%s", tmpattr);
}
Sprintf(buf, "%s/%s/%s", initblstats[hl->fld].fldname, behavebuf, clrbuf);
return buf;
}
STATIC_OVL int
status_hilite_menu_choose_field()
{
winid tmpwin;
int i, res, fld = BL_FLUSH;
anything any;
menu_item *picks = (menu_item *) 0;
tmpwin = create_nhwindow(NHW_MENU);
start_menu(tmpwin);
for (i = 0; i < MAXBLSTATS; i++) {
#ifndef SCORE_ON_BOTL
if (initblstats[i].fld == BL_SCORE
&& !blstats[0][BL_SCORE].thresholds)
continue;
#endif
any = zeroany;
any.a_int = (i + 1);
add_menu(tmpwin, NO_GLYPH, &any, 0, 0, ATR_NONE,
initblstats[i].fldname, MENU_UNSELECTED);
}
end_menu(tmpwin, "Select a hilite field:");
res = select_menu(tmpwin, PICK_ONE, &picks);
destroy_nhwindow(tmpwin);
if (res > 0) {
fld = picks->item.a_int - 1;
free((genericptr_t) picks);
}
return fld;
}
STATIC_OVL int
status_hilite_menu_choose_behavior(fld)
int fld;
{
winid tmpwin;
int res = 0, beh = BL_TH_NONE-1;
anything any;
menu_item *picks = (menu_item *) 0;
char buf[BUFSZ];
int at;
int onlybeh = BL_TH_NONE, nopts = 0;
if (fld < 0 || fld >= MAXBLSTATS)
return BL_TH_NONE;
at = initblstats[fld].anytype;
tmpwin = create_nhwindow(NHW_MENU);
start_menu(tmpwin);
if (fld != BL_CONDITION) {
any = zeroany;
any.a_int = onlybeh = BL_TH_ALWAYS_HILITE;
Sprintf(buf, "Always highlight %s", initblstats[fld].fldname);
add_menu(tmpwin, NO_GLYPH, &any, 'a', 0, ATR_NONE,
buf, MENU_UNSELECTED);
nopts++;
}
if (fld == BL_CONDITION) {
any = zeroany;
any.a_int = onlybeh = BL_TH_CONDITION;
add_menu(tmpwin, NO_GLYPH, &any, 'b', 0, ATR_NONE,
"Bitmask of conditions", MENU_UNSELECTED);
nopts++;
}
if (fld != BL_CONDITION) {
any = zeroany;
any.a_int = onlybeh = BL_TH_UPDOWN;
Sprintf(buf, "%s value changes", initblstats[fld].fldname);
add_menu(tmpwin, NO_GLYPH, &any, 'c', 0, ATR_NONE,
buf, MENU_UNSELECTED);
nopts++;
}
if (fld != BL_CAP && fld != BL_HUNGER
&& (at == ANY_INT || at == ANY_LONG)) {
any = zeroany;
any.a_int = onlybeh = BL_TH_VAL_ABSOLUTE;
add_menu(tmpwin, NO_GLYPH, &any, 'n', 0, ATR_NONE,
"Number threshold", MENU_UNSELECTED);
nopts++;
}
if (initblstats[fld].idxmax >= 0) {
any = zeroany;
any.a_int = onlybeh = BL_TH_VAL_PERCENTAGE;
add_menu(tmpwin, NO_GLYPH, &any, 'p', 0, ATR_NONE,
"Percentage threshold", MENU_UNSELECTED);
nopts++;
}
if (initblstats[fld].anytype == ANY_STR
|| fld == BL_CAP || fld == BL_HUNGER) {
any = zeroany;
any.a_int = onlybeh = BL_TH_TEXTMATCH;
Sprintf(buf, "%s text match", initblstats[fld].fldname);
add_menu(tmpwin, NO_GLYPH, &any, 't', 0, ATR_NONE,
buf, MENU_UNSELECTED);
nopts++;
}
Sprintf(buf, "Select %s field hilite behavior:", initblstats[fld].fldname);
end_menu(tmpwin, buf);
if (nopts > 1) {
res = select_menu(tmpwin, PICK_ONE, &picks);
if (res == 0) /* none chosen*/
beh = BL_TH_NONE;
else if (res == -1) /* menu cancelled */
beh = (BL_TH_NONE - 1);
} else if (onlybeh != BL_TH_NONE)
beh = onlybeh;
destroy_nhwindow(tmpwin);
if (res > 0) {
beh = picks->item.a_int;
free((genericptr_t) picks);
}
return beh;
}
STATIC_OVL int
status_hilite_menu_choose_updownboth(fld, str, ltok, gtok)
int fld;
const char *str;
boolean ltok, gtok;
{
int res, ret = NO_LTEQGT;
winid tmpwin;
char buf[BUFSZ];
anything any;
menu_item *picks = (menu_item *) 0;
tmpwin = create_nhwindow(NHW_MENU);
start_menu(tmpwin);
if (ltok) {
if (str)
Sprintf(buf, "%s than %s",
(fld == BL_AC) ? "Better (lower)" : "Less", str);
else
Sprintf(buf, "Value goes down");
any = zeroany;
any.a_int = 10 + LT_VALUE;
add_menu(tmpwin, NO_GLYPH, &any, 0, 0, ATR_NONE,
buf, MENU_UNSELECTED);
if (str) {
Sprintf(buf, "%s or %s",
str, (fld == BL_AC) ? "better (lower)" : "less");
any = zeroany;
any.a_int = 10 + LE_VALUE;
add_menu(tmpwin, NO_GLYPH, &any, 0, 0, ATR_NONE,
buf, MENU_UNSELECTED);
}
}
if (str)
Sprintf(buf, "Exactly %s", str);
else
Sprintf(buf, "Value changes");
any = zeroany;
any.a_int = 10 + EQ_VALUE;
add_menu(tmpwin, NO_GLYPH, &any, 0, 0, ATR_NONE,
buf, MENU_UNSELECTED);
if (gtok) {
if (str) {
Sprintf(buf, "%s or %s",
str, (fld == BL_AC) ? "worse (higher)" : "more");
any = zeroany;
any.a_int = 10 + GE_VALUE;
add_menu(tmpwin, NO_GLYPH, &any, 0, 0, ATR_NONE,
buf, MENU_UNSELECTED);
}
if (str)
Sprintf(buf, "%s than %s",
(fld == BL_AC) ? "Worse (higher)" : "More", str);
else
Sprintf(buf, "Value goes up");
any = zeroany;
any.a_int = 10 + GT_VALUE;
add_menu(tmpwin, NO_GLYPH, &any, 0, 0, ATR_NONE,
buf, MENU_UNSELECTED);
}
Sprintf(buf, "Select field %s value:", initblstats[fld].fldname);
end_menu(tmpwin, buf);
res = select_menu(tmpwin, PICK_ONE, &picks);
destroy_nhwindow(tmpwin);
if (res > 0) {
ret = picks->item.a_int - 10;
free((genericptr_t) picks);
}
return ret;
}
STATIC_OVL boolean
status_hilite_menu_add(origfld)
int origfld;
{
int fld;
int behavior;
int lt_gt_eq;
int clr = NO_COLOR, atr = HL_UNDEF;
struct hilite_s hilite;
unsigned long cond = 0UL;
char colorqry[BUFSZ];
char attrqry[BUFSZ];
choose_field:
fld = origfld;
if (fld == BL_FLUSH) {
fld = status_hilite_menu_choose_field();
/* isn't this redundant given what follows? */
if (fld == BL_FLUSH)
return FALSE;
}
if (fld == BL_FLUSH)
return FALSE;
colorqry[0] = '\0';
attrqry[0] = '\0';
memset((genericptr_t) &hilite, 0, sizeof (struct hilite_s));
hilite.next = (struct hilite_s *) 0;
hilite.set = FALSE; /* mark it "unset" */
hilite.fld = fld;
choose_behavior:
behavior = status_hilite_menu_choose_behavior(fld);
if (behavior == (BL_TH_NONE - 1)) {
return FALSE;
} else if (behavior == BL_TH_NONE) {
if (origfld == BL_FLUSH)
goto choose_field;
return FALSE;
}
hilite.behavior = behavior;
choose_value:
if (behavior == BL_TH_VAL_PERCENTAGE
|| behavior == BL_TH_VAL_ABSOLUTE) {
char inbuf[BUFSZ], buf[BUFSZ];
anything aval;
int val, dt;
boolean gotnum = FALSE, percent = (behavior == BL_TH_VAL_PERCENTAGE);
char *inp, *numstart;
const char *op;
lt_gt_eq = NO_LTEQGT; /* not set up yet */
inbuf[0] = '\0';
Sprintf(buf, "Enter %svalue for %s threshold:",
percent ? "percentage " : "",
initblstats[fld].fldname);
getlin(buf, inbuf);
if (inbuf[0] == '\0' || inbuf[0] == '\033')
goto choose_behavior;
inp = numstart = trimspaces(inbuf);
if (!*inp)
goto choose_behavior;
/* allow user to enter "<50%" or ">50" or just "50"
or <=50% or >=50 or =50 */
if (*inp == '>' || *inp == '<' || *inp == '=') {
lt_gt_eq = (*inp == '>') ? ((inp[1] == '=') ? GE_VALUE : GT_VALUE)
: (*inp == '<') ? ((inp[1] == '=') ? LE_VALUE : LT_VALUE)
: EQ_VALUE;
*inp++ = ' ';
numstart++;
if (lt_gt_eq == GE_VALUE || lt_gt_eq == LE_VALUE) {
*inp++ = ' ';
numstart++;
}
}
if (*inp == '-') {
inp++;
} else if (*inp == '+') {
*inp++ = ' ';
numstart++;
}
while (digit(*inp)) {
inp++;
gotnum = TRUE;
}
if (*inp == '%') {
if (!percent) {
pline("Not expecting a percentage.");
goto choose_behavior;
}
*inp = '\0'; /* strip '%' [this accepts trailing junk!] */
} else if (*inp) {
/* some random characters */
pline("\"%s\" is not a recognized number.", inp);
goto choose_value;
}
if (!gotnum) {
pline("Is that an invisible number?");
goto choose_value;
}
op = (lt_gt_eq == LT_VALUE) ? "<"
: (lt_gt_eq == LE_VALUE) ? "<="
: (lt_gt_eq == GT_VALUE) ? ">"
: (lt_gt_eq == GE_VALUE) ? ">="
: (lt_gt_eq == EQ_VALUE) ? "="
: ""; /* didn't specify lt_gt_eq with number */
aval = zeroany;
dt = percent ? ANY_INT : initblstats[fld].anytype;
(void) s_to_anything(&aval, numstart, dt);
if (percent) {
val = aval.a_int;
if (initblstats[fld].idxmax == -1) {
pline("Field '%s' does not support percentage values.",
initblstats[fld].fldname);
behavior = BL_TH_VAL_ABSOLUTE;
goto choose_value;
}
/* if player only specified a number then lt_gt_eq isn't set
up yet and the >-1 and <101 exceptions can't be honored;
deliberate use of those should be uncommon enough for
that to be palatable; for 0 and 100, choose_updown_both()
will prevent useless operations */
if ((val < 0 && (val != -1 || lt_gt_eq != GT_VALUE))
|| (val == 0 && lt_gt_eq == LT_VALUE)
|| (val == 100 && lt_gt_eq == GT_VALUE)
|| (val > 100 && (val != 101 || lt_gt_eq != LT_VALUE))) {
pline("'%s%d%%' is not a valid percent value.", op, val);
goto choose_value;
}
/* restore suffix for use in color and attribute prompts */
if (!index(numstart, '%'))
Strcat(numstart, "%");
/* reject negative values except for AC and >-1; reject 0 for < */
} else if (dt == ANY_INT
&& (aval.a_int < ((fld == BL_AC) ? -128
: (lt_gt_eq == GT_VALUE) ? -1
: (lt_gt_eq == LT_VALUE) ? 1 : 0))) {
pline("%s'%s%d'%s", threshold_value,
op, aval.a_int, is_out_of_range);
goto choose_value;
} else if (dt == ANY_LONG
&& (aval.a_long < (lt_gt_eq == GT_VALUE) ? -1L
: (lt_gt_eq == LT_VALUE) ? 1L : 0L)) {
pline("%s'%s%ld'%s", threshold_value,
op, aval.a_long, is_out_of_range);
goto choose_value;
}
if (lt_gt_eq == NO_LTEQGT) {
boolean ltok = ((dt == ANY_INT)
? (aval.a_int > 0 || fld == BL_AC)
: (aval.a_long > 0L)),
gtok = (!percent || aval.a_long < 100);
lt_gt_eq = status_hilite_menu_choose_updownboth(fld, inbuf,
ltok, gtok);
if (lt_gt_eq == NO_LTEQGT)
goto choose_value;
}
Sprintf(colorqry, "Choose a color for when %s is %s%s%s:",
initblstats[fld].fldname,
(lt_gt_eq == LT_VALUE) ? "less than "
: (lt_gt_eq == GT_VALUE) ? "more than "
: "",
numstart,
(lt_gt_eq == LE_VALUE) ? " or less"
: (lt_gt_eq == GE_VALUE) ? " or more"
: "");
Sprintf(attrqry, "Choose attribute for when %s is %s%s%s:",
initblstats[fld].fldname,
(lt_gt_eq == LT_VALUE) ? "less than "
: (lt_gt_eq == GT_VALUE) ? "more than "
: "",
numstart,
(lt_gt_eq == LE_VALUE) ? " or less"
: (lt_gt_eq == GE_VALUE) ? " or more"
: "");
hilite.rel = lt_gt_eq;
hilite.value = aval;
} else if (behavior == BL_TH_UPDOWN) {
if (initblstats[fld].anytype != ANY_STR) {
boolean ltok = (fld != BL_TIME), gtok = TRUE;
lt_gt_eq = status_hilite_menu_choose_updownboth(fld, (char *)0,
ltok, gtok);
if (lt_gt_eq == NO_LTEQGT)
goto choose_behavior;
} else { /* ANY_STR */
/* player picked '<field> value changes' in outer menu;
ordered string comparison is supported but LT/GT for the
string status fields (title, dungeon level, alignment)
is pointless; rather than calling ..._choose_updownboth()
with ltok==False plus gtok=False and having a menu with a
single choice, skip it altogether and just use 'changed' */
lt_gt_eq = EQ_VALUE;
}
Sprintf(colorqry, "Choose a color for when %s %s:",
initblstats[fld].fldname,
(lt_gt_eq == EQ_VALUE) ? "changes"
: (lt_gt_eq == LT_VALUE) ? "decreases"
: "increases");
Sprintf(attrqry, "Choose attribute for when %s %s:",
initblstats[fld].fldname,
(lt_gt_eq == EQ_VALUE) ? "changes"
: (lt_gt_eq == LT_VALUE) ? "decreases"
: "increases");
hilite.rel = lt_gt_eq;
} else if (behavior == BL_TH_CONDITION) {
cond = query_conditions();
if (!cond) {
if (origfld == BL_FLUSH)
goto choose_field;
return FALSE;
}
Sprintf(colorqry, "Choose a color for conditions %s:",
conditionbitmask2str(cond));
Sprintf(attrqry, "Choose attribute for conditions %s:",
conditionbitmask2str(cond));
} else if (behavior == BL_TH_TEXTMATCH) {
char qry_buf[BUFSZ];
Sprintf(qry_buf, "%s %s text value to match:",
(fld == BL_CAP
|| fld == BL_ALIGN
|| fld == BL_HUNGER
|| fld == BL_TITLE) ? "Choose" : "Enter",
initblstats[fld].fldname);
if (fld == BL_CAP) {
int rv = query_arrayvalue(qry_buf,
enc_stat,
SLT_ENCUMBER, OVERLOADED + 1);
if (rv < SLT_ENCUMBER)
goto choose_behavior;
hilite.rel = TXT_VALUE;
Strcpy(hilite.textmatch, enc_stat[rv]);
} else if (fld == BL_ALIGN) {
static const char *aligntxt[] = { "chaotic", "neutral", "lawful" };
int rv = query_arrayvalue(qry_buf,
aligntxt, 0, 2 + 1);
if (rv < 0)
goto choose_behavior;
hilite.rel = TXT_VALUE;
Strcpy(hilite.textmatch, aligntxt[rv]);
} else if (fld == BL_HUNGER) {
static const char *hutxt[] = { "Satiated", (char *) 0, "Hungry",
"Weak", "Fainting", "Fainted",
"Starved" };
int rv = query_arrayvalue(qry_buf,
hutxt,
SATIATED, STARVED + 1);
if (rv < SATIATED)
goto choose_behavior;
hilite.rel = TXT_VALUE;
Strcpy(hilite.textmatch, hutxt[rv]);
} else if (fld == BL_TITLE) {
const char *rolelist[3 * 9 + 1];
char mbuf[QBUFSZ], fbuf[QBUFSZ], obuf[QBUFSZ];
int i, j, rv;
for (i = j = 0; i < 9; i++) {
Sprintf(mbuf, "\"%s\"", urole.rank[i].m);
if (urole.rank[i].f) {
Sprintf(fbuf, "\"%s\"", urole.rank[i].f);
Sprintf(obuf, "%s or %s",
flags.female ? fbuf : mbuf,
flags.female ? mbuf : fbuf);
} else {
fbuf[0] = obuf[0] = '\0';
}
if (flags.female) {
if (*fbuf)
rolelist[j++] = dupstr(fbuf);
rolelist[j++] = dupstr(mbuf);
if (*obuf)
rolelist[j++] = dupstr(obuf);
} else {
rolelist[j++] = dupstr(mbuf);
if (*fbuf)
rolelist[j++] = dupstr(fbuf);
if (*obuf)
rolelist[j++] = dupstr(obuf);
}
}
rolelist[j++] = dupstr("\"none of the above (polymorphed)\"");
rv = query_arrayvalue(qry_buf, rolelist, 0, j);
if (rv >= 0) {
hilite.rel = TXT_VALUE;
Strcpy(hilite.textmatch, rolelist[rv]);
}
for (i = 0; i < j; i++)
free((genericptr_t) rolelist[i]), rolelist[i] = 0;
if (rv < 0)
goto choose_behavior;
} else {
char inbuf[BUFSZ];
inbuf[0] = '\0';
getlin(qry_buf, inbuf);
if (inbuf[0] == '\0' || inbuf[0] == '\033')
goto choose_behavior;
hilite.rel = TXT_VALUE;
if (strlen(inbuf) < QBUFSZ - 1)
Strcpy(hilite.textmatch, inbuf);
else
return FALSE;
}
Sprintf(colorqry, "Choose a color for when %s is '%s':",
initblstats[fld].fldname, hilite.textmatch);
Sprintf(attrqry, "Choose attribute for when %s is '%s':",
initblstats[fld].fldname, hilite.textmatch);
} else if (behavior == BL_TH_ALWAYS_HILITE) {
Sprintf(colorqry, "Choose a color to always hilite %s:",
initblstats[fld].fldname);
Sprintf(attrqry, "Choose attribute to always hilite %s:",
initblstats[fld].fldname);
}
choose_color:
clr = query_color(colorqry);
if (clr == -1) {
if (behavior != BL_TH_ALWAYS_HILITE)
goto choose_value;
else
goto choose_behavior;
}
atr = query_attr(attrqry);
if (atr == -1)
goto choose_color;
if (behavior == BL_TH_CONDITION) {
char clrbuf[BUFSZ];
char attrbuf[BUFSZ];
char *tmpattr;
if (atr & HL_DIM)
cond_hilites[HL_ATTCLR_DIM] |= cond;
else if (atr & HL_BLINK)
cond_hilites[HL_ATTCLR_BLINK] |= cond;
else if (atr & HL_ULINE)
cond_hilites[HL_ATTCLR_ULINE] |= cond;
else if (atr & HL_INVERSE)
cond_hilites[HL_ATTCLR_INVERSE] |= cond;
else if (atr & HL_BOLD)
cond_hilites[HL_ATTCLR_BOLD] |= cond;
else if (atr == HL_NONE) {
cond_hilites[HL_ATTCLR_DIM] &= ~cond;
cond_hilites[HL_ATTCLR_BLINK] &= ~cond;
cond_hilites[HL_ATTCLR_ULINE] &= ~cond;
cond_hilites[HL_ATTCLR_INVERSE] &= ~cond;
cond_hilites[HL_ATTCLR_BOLD] &= ~cond;
}
cond_hilites[clr] |= cond;
(void) strNsubst(strcpy(clrbuf, clr2colorname(clr)), " ", "-", 0);
tmpattr = hlattr2attrname(atr, attrbuf, BUFSZ);
if (tmpattr)
Sprintf(eos(clrbuf), "&%s", tmpattr);
pline("Added hilite condition/%s/%s",
conditionbitmask2str(cond), clrbuf);
} else {
char *p, *q;
hilite.coloridx = clr | (atr << 8);
hilite.anytype = initblstats[fld].anytype;
if (fld == BL_TITLE && (p = strstri(hilite.textmatch, " or ")) != 0) {
/* split menu choice "male-rank or female-rank" into two distinct
but otherwise identical rules, "male-rank" and "female-rank" */
*p = '\0'; /* chop off " or female-rank" */
/* new rule for male-rank */
status_hilite_add_threshold(fld, &hilite);
pline("Added hilite %s", status_hilite2str(&hilite));
/* transfer female-rank to start of hilite.textmatch buffer */
p += sizeof " or " - sizeof "";
q = hilite.textmatch;
while ((*q++ = *p++) != '\0')
continue;
/* proceed with normal addition of new rule */
}
status_hilite_add_threshold(fld, &hilite);
pline("Added hilite %s", status_hilite2str(&hilite));
}
reset_status_hilites();
return TRUE;
}
boolean
status_hilite_remove(id)
int id;
{
struct _status_hilite_line_str *hlstr = status_hilite_str;
while (hlstr && hlstr->id != id) {
hlstr = hlstr->next;
}
if (!hlstr)
return FALSE;
if (hlstr->fld == BL_CONDITION) {
int i;
for (i = 0; i < CLR_MAX; i++)
cond_hilites[i] &= ~hlstr->mask;
cond_hilites[HL_ATTCLR_DIM] &= ~hlstr->mask;
cond_hilites[HL_ATTCLR_BOLD] &= ~hlstr->mask;
cond_hilites[HL_ATTCLR_BLINK] &= ~hlstr->mask;
cond_hilites[HL_ATTCLR_ULINE] &= ~hlstr->mask;
cond_hilites[HL_ATTCLR_INVERSE] &= ~hlstr->mask;
return TRUE;
} else {
int fld = hlstr->fld;
struct hilite_s *hl = blstats[0][fld].thresholds;
struct hilite_s *hlprev = (struct hilite_s *) 0;
if (hl) {
while (hl) {
if (hlstr->hl == hl) {
if (hlprev) {
hlprev->next = hl->next;
} else {
blstats[0][fld].thresholds = hl->next;
blstats[1][fld].thresholds =
blstats[0][fld].thresholds;
}
free((genericptr_t) hl);
return TRUE;
}
hlprev = hl;
hl = hl->next;
}
}
}
return FALSE;
}
boolean
status_hilite_menu_fld(fld)
int fld;
{
winid tmpwin;
int i, res;
menu_item *picks = (menu_item *) 0;
anything any;
int count = status_hilite_linestr_countfield(fld);
struct _status_hilite_line_str *hlstr;
char buf[BUFSZ];
boolean acted = FALSE;
if (!count) {
if (status_hilite_menu_add(fld)) {
status_hilite_linestr_done();
status_hilite_linestr_gather();
count = status_hilite_linestr_countfield(fld);
} else
return FALSE;
}
tmpwin = create_nhwindow(NHW_MENU);
start_menu(tmpwin);
if (count) {
hlstr = status_hilite_str;
while (hlstr) {
if (hlstr->fld == fld) {
any = zeroany;
any.a_int = hlstr->id;
add_menu(tmpwin, NO_GLYPH, &any, 0, 0, ATR_NONE,
hlstr->str, MENU_UNSELECTED);
}
hlstr = hlstr->next;
}
} else {
any = zeroany;
Sprintf(buf, "No current hilites for %s", initblstats[fld].fldname);
add_menu(tmpwin, NO_GLYPH, &any, 0, 0, ATR_NONE, buf, MENU_UNSELECTED);
}
/* separator line */
any = zeroany;
add_menu(tmpwin, NO_GLYPH, &any, 0, 0, ATR_NONE, "", MENU_UNSELECTED);
if (count) {
any = zeroany;
any.a_int = -1;
add_menu(tmpwin, NO_GLYPH, &any, 'X', 0, ATR_NONE,
"Remove selected hilites", MENU_UNSELECTED);
}
#ifndef SCORE_ON_BOTL
if (fld == BL_SCORE) {
/* suppress 'Z - Add a new hilite' for 'score' when SCORE_ON_BOTL
is disabled; we wouldn't be called for 'score' unless it has
hilite rules from the config file, so count must be positive
(hence there's no risk that we're putting up an empty menu) */
;
} else
#endif
{
any = zeroany;
any.a_int = -2;
add_menu(tmpwin, NO_GLYPH, &any, 'Z', 0, ATR_NONE,
"Add a new hilite", MENU_UNSELECTED);
}
Sprintf(buf, "Current %s hilites:", initblstats[fld].fldname);
end_menu(tmpwin, buf);
if ((res = select_menu(tmpwin, PICK_ANY, &picks)) > 0) {
int mode = 0;
for (i = 0; i < res; i++) {
int idx = picks[i].item.a_int;
if (idx == -1) {
/* delete selected hilites */
if (mode)
goto shlmenu_free;
mode = -1;
break;
} else if (idx == -2) {
/* create a new hilite */
if (mode)
goto shlmenu_free;
mode = -2;
break;
}
}
if (mode == -1) {
/* delete selected hilites */
for (i = 0; i < res; i++) {
int idx = picks[i].item.a_int;
if (idx > 0)
(void) status_hilite_remove(idx);
}
reset_status_hilites();
acted = TRUE;
} else if (mode == -2) {
/* create a new hilite */
if (status_hilite_menu_add(fld))
acted = TRUE;
}
free((genericptr_t) picks);
}
shlmenu_free:
picks = (menu_item *) 0;
destroy_nhwindow(tmpwin);
return acted;
}
void
status_hilites_viewall()
{
winid datawin;
struct _status_hilite_line_str *hlstr = status_hilite_str;
char buf[BUFSZ];
datawin = create_nhwindow(NHW_TEXT);
while (hlstr) {
Sprintf(buf, "OPTIONS=hilite_status: %.*s",
(int) (BUFSZ - sizeof "OPTIONS=hilite_status: " - 1),
hlstr->str);
putstr(datawin, 0, buf);
hlstr = hlstr->next;
}
display_nhwindow(datawin, FALSE);
destroy_nhwindow(datawin);
}
boolean
status_hilite_menu()
{
winid tmpwin;
int i, res;
menu_item *picks = (menu_item *) 0;
anything any;
boolean redo;
int countall;
shlmenu_redo:
redo = FALSE;
tmpwin = create_nhwindow(NHW_MENU);
start_menu(tmpwin);
status_hilite_linestr_gather();
countall = status_hilite_linestr_countfield(BL_FLUSH);
if (countall) {
any = zeroany;
any.a_int = -1;
add_menu(tmpwin, NO_GLYPH, &any, 0, 0, ATR_NONE,
"View all hilites in config format", MENU_UNSELECTED);
any = zeroany;
add_menu(tmpwin, NO_GLYPH, &any, 0, 0, ATR_NONE, "", MENU_UNSELECTED);
}
for (i = 0; i < MAXBLSTATS; i++) {
int count = status_hilite_linestr_countfield(i);
char buf[BUFSZ];
#ifndef SCORE_ON_BOTL
/* config file might contain rules for highlighting 'score'
even when SCORE_ON_BOTL is disabled; if so, 'O' command
menus will show them and allow deletions but not additions,
otherwise, it won't show 'score' at all */
if (initblstats[i].fld == BL_SCORE && !count)
continue;
#endif
any = zeroany;
any.a_int = i + 1;
Sprintf(buf, "%-18s", initblstats[i].fldname);
if (count)
Sprintf(eos(buf), " (%d defined)", count);
add_menu(tmpwin, NO_GLYPH, &any, 0, 0, ATR_NONE,
buf, MENU_UNSELECTED);
}
end_menu(tmpwin, "Status hilites:");
if ((res = select_menu(tmpwin, PICK_ONE, &picks)) > 0) {
i = picks->item.a_int - 1;
if (i < 0)
status_hilites_viewall();
else
(void) status_hilite_menu_fld(i);
free((genericptr_t) picks), picks = (menu_item *) 0;
redo = TRUE;
}
destroy_nhwindow(tmpwin);
countall = status_hilite_linestr_countfield(BL_FLUSH);
status_hilite_linestr_done();
if (redo)
goto shlmenu_redo;
/* hilite_delta=='statushilites' does double duty: it is the
number of turns for temporary highlights to remain visible
and also when non-zero it is the flag to enable highlighting */
if (countall > 0 && !iflags.hilite_delta)
pline(
"To have highlights become active, set 'statushilites' option to non-zero.");
return TRUE;
}
#endif /* STATUS_HILITES */
/*botl.c*/