1 /* NetHack 3.6	botl.c	$NHDT-Date: 1573178085 2019/11/08 01:54:45 $  $NHDT-Branch: NetHack-3.6 $:$NHDT-Revision: 1.148 $ */
2 /* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */
3 /*-Copyright (c) Michael Allison, 2006. */
4 /* NetHack may be freely redistributed.  See license for details. */
5 
6 #include "hack.h"
7 #ifndef LONG_MAX
8 #include <limits.h>
9 #endif
10 
11 extern const char *hu_stat[]; /* defined in eat.c */
12 
13 const char *const enc_stat[] = { "",         "Burdened",  "Stressed",
14                                  "Strained", "Overtaxed", "Overloaded" };
15 
16 STATIC_OVL NEARDATA int mrank_sz = 0; /* loaded by max_rank_sz (from u_init) */
17 STATIC_DCL const char *NDECL(rank);
18 STATIC_DCL void NDECL(bot_via_windowport);
19 STATIC_DCL void NDECL(stat_update_time);
20 
21 static char *
get_strength_str()22 get_strength_str()
23 {
24     static char buf[32];
25     int st = ACURR(A_STR);
26 
27     if (st > 18) {
28         if (st > STR18(100))
29             Sprintf(buf, "%2d", st - 100);
30         else if (st < STR18(100))
31             Sprintf(buf, "18/%02d", st - 18);
32         else
33             Sprintf(buf, "18/**");
34     } else
35         Sprintf(buf, "%-1d", st);
36 
37     return buf;
38 }
39 
40 void
check_gold_symbol()41 check_gold_symbol()
42 {
43     nhsym goldch = showsyms[COIN_CLASS + SYM_OFF_O];
44 
45     iflags.invis_goldsym = (goldch <= (nhsym) ' ');
46 }
47 
48 char *
do_statusline1()49 do_statusline1()
50 {
51     static char newbot1[BUFSZ];
52     register char *nb;
53     register int i, j;
54 
55     Strcpy(newbot1, plname);
56     if ('a' <= newbot1[0] && newbot1[0] <= 'z')
57         newbot1[0] += 'A' - 'a';
58     newbot1[10] = 0;
59     Sprintf(nb = eos(newbot1), " the ");
60 
61     if (Upolyd) {
62         char mbot[BUFSZ];
63         int k = 0;
64 
65         Strcpy(mbot, mons[u.umonnum].mname);
66         while (mbot[k] != 0) {
67             if ((k == 0 || (k > 0 && mbot[k - 1] == ' ')) && 'a' <= mbot[k]
68                 && mbot[k] <= 'z')
69                 mbot[k] += 'A' - 'a';
70             k++;
71         }
72         Strcpy(nb = eos(nb), mbot);
73     } else
74         Strcpy(nb = eos(nb), rank());
75 
76     Sprintf(nb = eos(nb), "  ");
77     i = mrank_sz + 15;
78     j = (int) ((nb + 2) - newbot1); /* strlen(newbot1) but less computation */
79     if ((i - j) > 0)
80         Sprintf(nb = eos(nb), "%*s", i - j, " "); /* pad with spaces */
81 
82     Sprintf(nb = eos(nb), "St:%s Dx:%-1d Co:%-1d In:%-1d Wi:%-1d Ch:%-1d",
83             get_strength_str(),
84             ACURR(A_DEX), ACURR(A_CON), ACURR(A_INT), ACURR(A_WIS),
85             ACURR(A_CHA));
86     Sprintf(nb = eos(nb),
87             (u.ualign.type == A_CHAOTIC)
88                 ? "  Chaotic"
89                 : (u.ualign.type == A_NEUTRAL) ? "  Neutral" : "  Lawful");
90 #ifdef SCORE_ON_BOTL
91     if (flags.showscore)
92         Sprintf(nb = eos(nb), " S:%ld", botl_score());
93 #endif
94     return newbot1;
95 }
96 
97 char *
do_statusline2()98 do_statusline2()
99 {
100     static char newbot2[BUFSZ], /* MAXCO: botl.h */
101          /* dungeon location (and gold), hero health (HP, PW, AC),
102             experience (HD if poly'd, else Exp level and maybe Exp points),
103             time (in moves), varying number of status conditions */
104          dloc[QBUFSZ], hlth[QBUFSZ], expr[QBUFSZ], tmmv[QBUFSZ], cond[QBUFSZ];
105     register char *nb;
106     unsigned dln, dx, hln, xln, tln, cln;
107     int hp, hpmax, cap;
108     long money;
109 
110     /*
111      * Various min(x,9999)'s are to avoid having excessive values
112      * violate the field width assumptions in botl.h and should not
113      * impact normal play.  Particularly 64-bit long for gold which
114      * could require many more digits if someone figures out a way
115      * to get and carry a really large (or negative) amount of it.
116      * Turn counter is also long, but we'll risk that.
117      */
118 
119     /* dungeon location plus gold */
120     (void) describe_level(dloc); /* includes at least one trailing space */
121     if ((money = money_cnt(invent)) < 0L)
122         money = 0L; /* ought to issue impossible() and then discard gold */
123     Sprintf(eos(dloc), "%s:%-2ld", /* strongest hero can lift ~300000 gold */
124             (iflags.in_dumplog || iflags.invis_goldsym) ? "$"
125               : encglyph(objnum_to_glyph(GOLD_PIECE)),
126             min(money, 999999L));
127     dln = strlen(dloc);
128     /* '$' encoded as \GXXXXNNNN is 9 chars longer than display will need */
129     dx = strstri(dloc, "\\G") ? 9 : 0;
130 
131     /* health and armor class (has trailing space for AC 0..9) */
132     hp = Upolyd ? u.mh : u.uhp;
133     hpmax = Upolyd ? u.mhmax : u.uhpmax;
134     if (hp < 0)
135         hp = 0;
136     Sprintf(hlth, "HP:%d(%d) Pw:%d(%d) AC:%-2d",
137             min(hp, 9999), min(hpmax, 9999),
138             min(u.uen, 9999), min(u.uenmax, 9999), u.uac);
139     hln = strlen(hlth);
140 
141     /* experience */
142     if (Upolyd)
143         Sprintf(expr, "HD:%d", mons[u.umonnum].mlevel);
144     else if (flags.showexp)
145         Sprintf(expr, "Xp:%d/%-1ld", u.ulevel, u.uexp);
146     else
147         Sprintf(expr, "Exp:%d", u.ulevel);
148     xln = strlen(expr);
149 
150     /* time/move counter */
151     if (flags.time)
152         Sprintf(tmmv, "T:%ld", moves);
153     else
154         tmmv[0] = '\0';
155     tln = strlen(tmmv);
156 
157     /* status conditions; worst ones first */
158     cond[0] = '\0'; /* once non-empty, cond will have a leading space */
159     nb = cond;
160     /*
161      * Stoned, Slimed, Strangled, and both types of Sick are all fatal
162      * unless remedied before timeout expires.  Should we order them by
163      * shortest time left?  [Probably not worth the effort, since it's
164      * unusual for more than one of them to apply at a time.]
165      */
166     if (Stoned)
167         Strcpy(nb = eos(nb), " Stone");
168     if (Slimed)
169         Strcpy(nb = eos(nb), " Slime");
170     if (Strangled)
171         Strcpy(nb = eos(nb), " Strngl");
172     if (Sick) {
173         if (u.usick_type & SICK_VOMITABLE)
174             Strcpy(nb = eos(nb), " FoodPois");
175         if (u.usick_type & SICK_NONVOMITABLE)
176             Strcpy(nb = eos(nb), " TermIll");
177     }
178     if (u.uhs != NOT_HUNGRY)
179         Sprintf(nb = eos(nb), " %s", hu_stat[u.uhs]);
180     if ((cap = near_capacity()) > UNENCUMBERED)
181         Sprintf(nb = eos(nb), " %s", enc_stat[cap]);
182     if (Blind)
183         Strcpy(nb = eos(nb), " Blind");
184     if (Deaf)
185         Strcpy(nb = eos(nb), " Deaf");
186     if (Stunned)
187         Strcpy(nb = eos(nb), " Stun");
188     if (Confusion)
189         Strcpy(nb = eos(nb), " Conf");
190     if (Hallucination)
191         Strcpy(nb = eos(nb), " Hallu");
192     /* levitation and flying are mutually exclusive; riding is not */
193     if (Levitation)
194         Strcpy(nb = eos(nb), " Lev");
195     if (Flying)
196         Strcpy(nb = eos(nb), " Fly");
197     if (u.usteed)
198         Strcpy(nb = eos(nb), " Ride");
199     cln = strlen(cond);
200 
201     /*
202      * Put the pieces together.  If they all fit, keep the traditional
203      * sequence.  Otherwise, move least important parts to the end in
204      * case the interface side of things has to truncate.  Note that
205      * dloc[] contains '$' encoded in ten character sequence \GXXXXNNNN
206      * so we want to test its display length rather than buffer length.
207      *
208      * We don't have an actual display limit here, so have to go by the
209      * width of the map.  Since we're reordering rather than truncating,
210      * wider displays can still show wider status than the map if the
211      * interface supports that.
212      */
213     if ((dln - dx) + 1 + hln + 1 + xln + 1 + tln + 1 + cln <= COLNO) {
214         Sprintf(newbot2, "%s %s %s %s %s", dloc, hlth, expr, tmmv, cond);
215     } else {
216         if (dln + 1 + hln + 1 + xln + 1 + tln + 1 + cln + 1 > MAXCO) {
217             panic("bot2: second status line exceeds MAXCO (%u > %d)",
218                   (dln + 1 + hln + 1 + xln + 1 + tln + 1 + cln + 1), MAXCO);
219         } else if ((dln - dx) + 1 + hln + 1 + xln + 1 + cln <= COLNO) {
220             Sprintf(newbot2, "%s %s %s %s %s", dloc, hlth, expr, cond, tmmv);
221         } else if ((dln - dx) + 1 + hln + 1 + cln <= COLNO) {
222             Sprintf(newbot2, "%s %s %s %s %s", dloc, hlth, cond, expr, tmmv);
223         } else {
224             Sprintf(newbot2, "%s %s %s %s %s", hlth, cond, dloc, expr, tmmv);
225         }
226         /* only two or three consecutive spaces available to squeeze out */
227         mungspaces(newbot2);
228     }
229     return newbot2;
230 }
231 
232 void
bot()233 bot()
234 {
235     /* dosave() flags completion by setting u.uhp to -1 */
236     if ((u.uhp != -1) && youmonst.data && iflags.status_updates) {
237         if (VIA_WINDOWPORT()) {
238             bot_via_windowport();
239         } else {
240             curs(WIN_STATUS, 1, 0);
241             putstr(WIN_STATUS, 0, do_statusline1());
242             curs(WIN_STATUS, 1, 1);
243             putmixed(WIN_STATUS, 0, do_statusline2());
244         }
245     }
246     context.botl = context.botlx = iflags.time_botl = FALSE;
247 }
248 
249 void
timebot()250 timebot()
251 {
252     if (flags.time && iflags.status_updates) {
253         if (VIA_WINDOWPORT()) {
254             stat_update_time();
255         } else {
256             /* old status display updates everything */
257             bot();
258         }
259     }
260     iflags.time_botl = FALSE;
261 }
262 
263 /* convert experience level (1..30) to rank index (0..8) */
264 int
xlev_to_rank(xlev)265 xlev_to_rank(xlev)
266 int xlev;
267 {
268     return (xlev <= 2) ? 0 : (xlev <= 30) ? ((xlev + 2) / 4) : 8;
269 }
270 
271 #if 0 /* not currently needed */
272 /* convert rank index (0..8) to experience level (1..30) */
273 int
274 rank_to_xlev(rank)
275 int rank;
276 {
277     return (rank <= 0) ? 1 : (rank <= 8) ? ((rank * 4) - 2) : 30;
278 }
279 #endif
280 
281 const char *
rank_of(lev,monnum,female)282 rank_of(lev, monnum, female)
283 int lev;
284 short monnum;
285 boolean female;
286 {
287     register const struct Role *role;
288     register int i;
289 
290     /* Find the role */
291     for (role = roles; role->name.m; role++)
292         if (monnum == role->malenum || monnum == role->femalenum)
293             break;
294     if (!role->name.m)
295         role = &urole;
296 
297     /* Find the rank */
298     for (i = xlev_to_rank((int) lev); i >= 0; i--) {
299         if (female && role->rank[i].f)
300             return role->rank[i].f;
301         if (role->rank[i].m)
302             return role->rank[i].m;
303     }
304 
305     /* Try the role name, instead */
306     if (female && role->name.f)
307         return role->name.f;
308     else if (role->name.m)
309         return role->name.m;
310     return "Player";
311 }
312 
313 STATIC_OVL const char *
rank()314 rank()
315 {
316     return rank_of(u.ulevel, Role_switch, flags.female);
317 }
318 
319 int
title_to_mon(str,rank_indx,title_length)320 title_to_mon(str, rank_indx, title_length)
321 const char *str;
322 int *rank_indx, *title_length;
323 {
324     register int i, j;
325 
326     /* Loop through each of the roles */
327     for (i = 0; roles[i].name.m; i++)
328         for (j = 0; j < 9; j++) {
329             if (roles[i].rank[j].m
330                 && !strncmpi(str, roles[i].rank[j].m,
331                              strlen(roles[i].rank[j].m))) {
332                 if (rank_indx)
333                     *rank_indx = j;
334                 if (title_length)
335                     *title_length = strlen(roles[i].rank[j].m);
336                 return roles[i].malenum;
337             }
338             if (roles[i].rank[j].f
339                 && !strncmpi(str, roles[i].rank[j].f,
340                              strlen(roles[i].rank[j].f))) {
341                 if (rank_indx)
342                     *rank_indx = j;
343                 if (title_length)
344                     *title_length = strlen(roles[i].rank[j].f);
345                 return (roles[i].femalenum != NON_PM) ? roles[i].femalenum
346                                                       : roles[i].malenum;
347             }
348         }
349     return NON_PM;
350 }
351 
352 void
max_rank_sz()353 max_rank_sz()
354 {
355     register int i, r, maxr = 0;
356     for (i = 0; i < 9; i++) {
357         if (urole.rank[i].m && (r = strlen(urole.rank[i].m)) > maxr)
358             maxr = r;
359         if (urole.rank[i].f && (r = strlen(urole.rank[i].f)) > maxr)
360             maxr = r;
361     }
362     mrank_sz = maxr;
363     return;
364 }
365 
366 #ifdef SCORE_ON_BOTL
367 long
botl_score()368 botl_score()
369 {
370     long deepest = deepest_lev_reached(FALSE);
371     long utotal;
372 
373     utotal = money_cnt(invent) + hidden_gold();
374     if ((utotal -= u.umoney0) < 0L)
375         utotal = 0L;
376     utotal += u.urexp + (50 * (deepest - 1))
377           + (deepest > 30 ? 10000 : deepest > 20 ? 1000 * (deepest - 20) : 0);
378     if (utotal < u.urexp)
379         utotal = LONG_MAX; /* wrap around */
380     return utotal;
381 }
382 #endif /* SCORE_ON_BOTL */
383 
384 /* provide the name of the current level for display by various ports */
385 int
describe_level(buf)386 describe_level(buf)
387 char *buf;
388 {
389     int ret = 1;
390 
391     /* TODO:    Add in dungeon name */
392     if (Is_knox(&u.uz)) {
393         Sprintf(buf, "%s ", dungeons[u.uz.dnum].dname);
394     } else if (In_quest(&u.uz)) {
395         Sprintf(buf, "Home %d ", dunlev(&u.uz));
396     } else if (In_endgame(&u.uz)) {
397         /* [3.6.2: this used to be "Astral Plane" or generic "End Game"] */
398         (void) endgamelevelname(buf, depth(&u.uz));
399         (void) strsubst(buf, "Plane of ", ""); /* just keep <element> */
400         Strcat(buf, " ");
401     } else {
402         /* ports with more room may expand this one */
403         Sprintf(buf, "Dlvl:%-2d ", depth(&u.uz));
404         ret = 0;
405     }
406     return ret;
407 }
408 
409 /* =======================================================================*/
410 /*  statusnew routines                                                    */
411 /* =======================================================================*/
412 
413 /* structure that tracks the status details in the core */
414 
415 #define MAXVALWIDTH 80 /* actually less, but was using 80 to allocate title
416                         * and leveldesc then using QBUFSZ everywhere else   */
417 #ifdef STATUS_HILITES
418 struct hilite_s {
419     enum statusfields fld;
420     boolean set;
421     unsigned anytype;
422     anything value;
423     int behavior;
424     char textmatch[MAXVALWIDTH];
425     enum relationships rel;
426     int coloridx;
427     struct hilite_s *next;
428 };
429 #endif /* STATUS_HILITES */
430 
431 struct istat_s {
432     const char *fldname;
433     const char *fldfmt;
434     long time;  /* moves when this field hilite times out */
435     boolean chg; /* need to recalc time? */
436     boolean percent_matters;
437     short percent_value;
438     unsigned anytype;
439     anything a;
440     char *val;
441     int valwidth;
442     enum statusfields idxmax;
443     enum statusfields fld;
444 #ifdef STATUS_HILITES
445     struct hilite_s *hilite_rule; /* the entry, if any, in 'thresholds'
446                                    * list that currently applies        */
447     struct hilite_s *thresholds;
448 #endif
449 };
450 
451 STATIC_DCL boolean FDECL(eval_notify_windowport_field, (int, boolean *, int));
452 STATIC_DCL void FDECL(evaluate_and_notify_windowport, (boolean *, int));
453 STATIC_DCL void NDECL(init_blstats);
454 STATIC_DCL int FDECL(compare_blstats, (struct istat_s *, struct istat_s *));
455 STATIC_DCL char *FDECL(anything_to_s, (char *, anything *, int));
456 STATIC_DCL int FDECL(percentage, (struct istat_s *, struct istat_s *));
457 STATIC_DCL int NDECL(exp_percentage);
458 
459 #ifdef STATUS_HILITES
460 STATIC_DCL void FDECL(s_to_anything, (anything *, char *, int));
461 STATIC_DCL enum statusfields FDECL(fldname_to_bl_indx, (const char *));
462 STATIC_DCL boolean FDECL(hilite_reset_needed, (struct istat_s *, long));
463 STATIC_DCL boolean FDECL(noneoftheabove, (const char *));
464 STATIC_DCL struct hilite_s *FDECL(get_hilite, (int, int, genericptr_t,
465                                                int, int, int *));
466 STATIC_DCL void FDECL(split_clridx, (int, int *, int *));
467 STATIC_DCL boolean FDECL(is_ltgt_percentnumber, (const char *));
468 STATIC_DCL boolean FDECL(has_ltgt_percentnumber, (const char *));
469 STATIC_DCL int FDECL(splitsubfields, (char *, char ***, int));
470 STATIC_DCL boolean FDECL(is_fld_arrayvalues, (const char *,
471                                               const char *const *,
472                                               int, int, int *));
473 STATIC_DCL int FDECL(query_arrayvalue, (const char *, const char *const *,
474                                         int, int));
475 STATIC_DCL void FDECL(status_hilite_add_threshold, (int, struct hilite_s *));
476 STATIC_DCL boolean FDECL(parse_status_hl2, (char (*)[QBUFSZ], BOOLEAN_P));
477 STATIC_DCL char *FDECL(conditionbitmask2str, (unsigned long));
478 STATIC_DCL unsigned long FDECL(match_str2conditionbitmask, (const char *));
479 STATIC_DCL unsigned long FDECL(str2conditionbitmask, (char *));
480 STATIC_DCL boolean FDECL(parse_condition, (char (*)[QBUFSZ], int));
481 STATIC_DCL char *FDECL(hlattr2attrname, (int, char *, int));
482 STATIC_DCL void FDECL(status_hilite_linestr_add, (int, struct hilite_s *,
483                                                 unsigned long, const char *));
484 STATIC_DCL void NDECL(status_hilite_linestr_done);
485 STATIC_DCL int FDECL(status_hilite_linestr_countfield, (int));
486 STATIC_DCL void NDECL(status_hilite_linestr_gather_conditions);
487 STATIC_DCL void NDECL(status_hilite_linestr_gather);
488 STATIC_DCL char *FDECL(status_hilite2str, (struct hilite_s *));
489 STATIC_DCL int NDECL(status_hilite_menu_choose_field);
490 STATIC_DCL int FDECL(status_hilite_menu_choose_behavior, (int));
491 STATIC_DCL int FDECL(status_hilite_menu_choose_updownboth, (int, const char *,
492                                                        BOOLEAN_P, BOOLEAN_P));
493 STATIC_DCL boolean FDECL(status_hilite_menu_add, (int));
494 #define has_hilite(i) (blstats[0][(i)].thresholds)
495 /* TH_UPDOWN encompasses specific 'up' and 'down' also general 'changed' */
496 #define Is_Temp_Hilite(rule) ((rule) && (rule)->behavior == BL_TH_UPDOWN)
497 
498 /* pointers to current hilite rule and list of this field's defined rules */
499 #define INIT_THRESH  , (struct hilite_s *) 0, (struct hilite_s *) 0
500 #else /* !STATUS_HILITES */
501 #define INIT_THRESH /*empty*/
502 #endif
503 
504 #define INIT_BLSTAT(name, fmtstr, anytyp, wid, fld) \
505     { name, fmtstr, 0L, FALSE, FALSE, 0, anytyp,                        \
506       { (genericptr_t) 0 }, (char *) 0,                                 \
507       wid,  -1, fld INIT_THRESH }
508 #define INIT_BLSTATP(name, fmtstr, anytyp, wid, maxfld, fld) \
509     { name, fmtstr, 0L, FALSE, TRUE, 0, anytyp,                         \
510       { (genericptr_t) 0 }, (char *) 0,                                 \
511       wid,  maxfld, fld INIT_THRESH }
512 
513 /* If entries are added to this, botl.h will require updating too.
514    'max' value of BL_EXP gets special handling since the percentage
515    involved isn't a direct 100*current/maximum calculation. */
516 STATIC_VAR struct istat_s initblstats[MAXBLSTATS] = {
517     INIT_BLSTAT("title", "%s", ANY_STR, MAXVALWIDTH, BL_TITLE),
518     INIT_BLSTAT("strength", " St:%s", ANY_INT, 10, BL_STR),
519     INIT_BLSTAT("dexterity", " Dx:%s", ANY_INT,  10, BL_DX),
520     INIT_BLSTAT("constitution", " Co:%s", ANY_INT, 10, BL_CO),
521     INIT_BLSTAT("intelligence", " In:%s", ANY_INT, 10, BL_IN),
522     INIT_BLSTAT("wisdom", " Wi:%s", ANY_INT, 10, BL_WI),
523     INIT_BLSTAT("charisma", " Ch:%s", ANY_INT, 10, BL_CH),
524     INIT_BLSTAT("alignment", " %s", ANY_STR, 40, BL_ALIGN),
525     INIT_BLSTAT("score", " S:%s", ANY_LONG, 20, BL_SCORE),
526     INIT_BLSTAT("carrying-capacity", " %s", ANY_INT, 20, BL_CAP),
527     INIT_BLSTAT("gold", " %s", ANY_LONG, 30, BL_GOLD),
528     INIT_BLSTATP("power", " Pw:%s", ANY_INT, 10, BL_ENEMAX, BL_ENE),
529     INIT_BLSTAT("power-max", "(%s)", ANY_INT, 10, BL_ENEMAX),
530     INIT_BLSTATP("experience-level", " Xp:%s", ANY_INT, 10, BL_EXP, BL_XP),
531     INIT_BLSTAT("armor-class", " AC:%s", ANY_INT, 10, BL_AC),
532     INIT_BLSTAT("HD", " HD:%s", ANY_INT, 10, BL_HD),
533     INIT_BLSTAT("time", " T:%s", ANY_LONG, 20, BL_TIME),
534     /* hunger used to be 'ANY_UINT'; see note below in bot_via_windowport() */
535     INIT_BLSTAT("hunger", " %s", ANY_INT, 40, BL_HUNGER),
536     INIT_BLSTATP("hitpoints", " HP:%s", ANY_INT, 10, BL_HPMAX, BL_HP),
537     INIT_BLSTAT("hitpoints-max", "(%s)", ANY_INT, 10, BL_HPMAX),
538     INIT_BLSTAT("dungeon-level", "%s", ANY_STR, MAXVALWIDTH, BL_LEVELDESC),
539     INIT_BLSTATP("experience", "/%s", ANY_LONG, 20, BL_EXP, BL_EXP),
540     INIT_BLSTAT("condition", "%s", ANY_MASK32, 0, BL_CONDITION)
541 };
542 
543 #undef INIT_BLSTATP
544 #undef INIT_BLSTAT
545 #undef INIT_THRESH
546 
547 struct istat_s blstats[2][MAXBLSTATS];
548 static boolean blinit = FALSE, update_all = FALSE;
549 static boolean valset[MAXBLSTATS];
550 #ifdef STATUS_HILITES
551 static long bl_hilite_moves = 0L;
552 #endif
553 
554 /* we don't put this next declaration in #ifdef STATUS_HILITES.
555  * In the absence of STATUS_HILITES, each array
556  * element will be 0 however, and quite meaningless,
557  * but we need to pass the first array element as
558  * the final argument of status_update, with or
559  * without STATUS_HILITES.
560  */
561 static unsigned long cond_hilites[BL_ATTCLR_MAX];
562 static int now_or_before_idx = 0; /* 0..1 for array[2][] first index */
563 
564 STATIC_OVL void
bot_via_windowport()565 bot_via_windowport()
566 {
567     char buf[BUFSZ];
568     const char *titl;
569     register char *nb;
570     int i, idx, cap;
571     long money;
572 
573     if (!blinit)
574         panic("bot before init.");
575 
576     /* toggle from previous iteration */
577     idx = 1 - now_or_before_idx; /* 0 -> 1, 1 -> 0 */
578     now_or_before_idx = idx;
579 
580     /* clear the "value set" indicators */
581     (void) memset((genericptr_t) valset, 0, MAXBLSTATS * sizeof (boolean));
582 
583     /*
584      * Note: min(x,9999) - we enforce the same maximum on hp, maxhp,
585      * pw, maxpw, and gold as basic status formatting so that the two
586      * modes of status display don't produce different information.
587      */
588 
589     /*
590      *  Player name and title.
591      */
592     Strcpy(nb = buf, plname);
593     nb[0] = highc(nb[0]);
594     titl = !Upolyd ? rank() : mons[u.umonnum].mname;
595     i = (int) (strlen(buf) + sizeof " the " + strlen(titl) - sizeof "");
596     /* if "Name the Rank/monster" is too long, we truncate the name
597        but always keep at least 10 characters of it; when hitpintbar is
598        enabled, anything beyond 30 (long monster name) will be truncated */
599     if (i > 30) {
600         i = 30 - (int) (sizeof " the " + strlen(titl) - sizeof "");
601         nb[max(i, 10)] = '\0';
602     }
603     Strcpy(nb = eos(nb), " the ");
604     Strcpy(nb = eos(nb), titl);
605     if (Upolyd) { /* when poly'd, capitalize monster name */
606         for (i = 0; nb[i]; i++)
607             if (i == 0 || nb[i - 1] == ' ')
608                 nb[i] = highc(nb[i]);
609     }
610     Sprintf(blstats[idx][BL_TITLE].val, "%-30s", buf);
611     valset[BL_TITLE] = TRUE; /* indicate val already set */
612 
613     /* Strength */
614     blstats[idx][BL_STR].a.a_int = ACURR(A_STR);
615     Strcpy(blstats[idx][BL_STR].val, get_strength_str());
616     valset[BL_STR] = TRUE; /* indicate val already set */
617 
618     /*  Dexterity, constitution, intelligence, wisdom, charisma. */
619     blstats[idx][BL_DX].a.a_int = ACURR(A_DEX);
620     blstats[idx][BL_CO].a.a_int = ACURR(A_CON);
621     blstats[idx][BL_IN].a.a_int = ACURR(A_INT);
622     blstats[idx][BL_WI].a.a_int = ACURR(A_WIS);
623     blstats[idx][BL_CH].a.a_int = ACURR(A_CHA);
624 
625     /* Alignment */
626     Strcpy(blstats[idx][BL_ALIGN].val, (u.ualign.type == A_CHAOTIC)
627                                           ? "Chaotic"
628                                           : (u.ualign.type == A_NEUTRAL)
629                                                ? "Neutral"
630                                                : "Lawful");
631 
632     /* Score */
633     blstats[idx][BL_SCORE].a.a_long =
634 #ifdef SCORE_ON_BOTL
635         flags.showscore ? botl_score() :
636 #endif
637         0L;
638 
639     /*  Hit points  */
640     i = Upolyd ? u.mh : u.uhp;
641     if (i < 0)
642         i = 0;
643     blstats[idx][BL_HP].a.a_int = min(i, 9999);
644     i = Upolyd ? u.mhmax : u.uhpmax;
645     blstats[idx][BL_HPMAX].a.a_int = min(i, 9999);
646 
647     /*  Dungeon level. */
648     (void) describe_level(blstats[idx][BL_LEVELDESC].val);
649     valset[BL_LEVELDESC] = TRUE; /* indicate val already set */
650 
651     /* Gold */
652     if ((money = money_cnt(invent)) < 0L)
653         money = 0L; /* ought to issue impossible() and then discard gold */
654     blstats[idx][BL_GOLD].a.a_long = min(money, 999999L);
655     /*
656      * The tty port needs to display the current symbol for gold
657      * as a field header, so to accommodate that we pass gold with
658      * that already included. If a window port needs to use the text
659      * gold amount without the leading "$:" the port will have to
660      * skip past ':' to the value pointer it was passed in status_update()
661      * for the BL_GOLD case.
662      *
663      * Another quirk of BL_GOLD is that the field display may have
664      * changed if a new symbol set was loaded, or we entered or left
665      * the rogue level.
666      *
667      * The currency prefix is encoded as ten character \GXXXXNNNN
668      * sequence.
669      */
670     Sprintf(blstats[idx][BL_GOLD].val, "%s:%ld",
671             (iflags.in_dumplog || iflags.invis_goldsym) ? "$"
672               : encglyph(objnum_to_glyph(GOLD_PIECE)),
673             blstats[idx][BL_GOLD].a.a_long);
674     valset[BL_GOLD] = TRUE; /* indicate val already set */
675 
676     /* Power (magical energy) */
677     blstats[idx][BL_ENE].a.a_int = min(u.uen, 9999);
678     blstats[idx][BL_ENEMAX].a.a_int = min(u.uenmax, 9999);
679 
680     /* Armor class */
681     blstats[idx][BL_AC].a.a_int = u.uac;
682 
683     /* Monster level (if Upolyd) */
684     blstats[idx][BL_HD].a.a_int = Upolyd ? (int) mons[u.umonnum].mlevel : 0;
685 
686     /* Experience */
687     blstats[idx][BL_XP].a.a_int = u.ulevel;
688     blstats[idx][BL_EXP].a.a_long = u.uexp;
689 
690     /* Time (moves) */
691     blstats[idx][BL_TIME].a.a_long = moves;
692 
693     /* Hunger */
694     /* note: u.uhs is unsigned, and 3.6.1's STATUS_HILITE defined
695        BL_HUNGER to be ANY_UINT, but that was the only non-int/non-long
696        numeric field so it's far simpler to treat it as plain int and
697        not need ANY_UINT handling at all */
698     blstats[idx][BL_HUNGER].a.a_int = (int) u.uhs;
699     Strcpy(blstats[idx][BL_HUNGER].val,
700            (u.uhs != NOT_HUNGRY) ? hu_stat[u.uhs] : "");
701     valset[BL_HUNGER] = TRUE;
702 
703     /* Carrying capacity */
704     cap = near_capacity();
705     blstats[idx][BL_CAP].a.a_int = cap;
706     Strcpy(blstats[idx][BL_CAP].val,
707            (cap > UNENCUMBERED) ? enc_stat[cap] : "");
708     valset[BL_CAP] = TRUE;
709 
710     /* Conditions */
711     blstats[idx][BL_CONDITION].a.a_ulong = 0L;
712     if (Stoned)
713         blstats[idx][BL_CONDITION].a.a_ulong |= BL_MASK_STONE;
714     if (Slimed)
715         blstats[idx][BL_CONDITION].a.a_ulong |= BL_MASK_SLIME;
716     if (Strangled)
717         blstats[idx][BL_CONDITION].a.a_ulong |= BL_MASK_STRNGL;
718     if (Sick && (u.usick_type & SICK_VOMITABLE) != 0)
719         blstats[idx][BL_CONDITION].a.a_ulong |= BL_MASK_FOODPOIS;
720     if (Sick && (u.usick_type & SICK_NONVOMITABLE) != 0)
721         blstats[idx][BL_CONDITION].a.a_ulong |= BL_MASK_TERMILL;
722     /*
723      * basic formatting puts hunger status and encumbrance here
724      */
725     if (Blind)
726         blstats[idx][BL_CONDITION].a.a_ulong |= BL_MASK_BLIND;
727     if (Deaf)
728         blstats[idx][BL_CONDITION].a.a_ulong |= BL_MASK_DEAF;
729     if (Stunned)
730         blstats[idx][BL_CONDITION].a.a_ulong |= BL_MASK_STUN;
731     if (Confusion)
732         blstats[idx][BL_CONDITION].a.a_ulong |= BL_MASK_CONF;
733     if (Hallucination)
734         blstats[idx][BL_CONDITION].a.a_ulong |= BL_MASK_HALLU;
735     /* levitation and flying are mututally exclusive */
736     if (Levitation)
737         blstats[idx][BL_CONDITION].a.a_ulong |= BL_MASK_LEV;
738     if (Flying)
739         blstats[idx][BL_CONDITION].a.a_ulong |= BL_MASK_FLY;
740     if (u.usteed)
741         blstats[idx][BL_CONDITION].a.a_ulong |= BL_MASK_RIDE;
742     evaluate_and_notify_windowport(valset, idx);
743 }
744 
745 /* update just the status lines' 'time' field */
746 STATIC_OVL void
stat_update_time()747 stat_update_time()
748 {
749     int idx = now_or_before_idx; /* no 0/1 toggle */
750     int fld = BL_TIME;
751 
752     /* Time (moves) */
753     blstats[idx][fld].a.a_long = moves;
754     valset[fld] = FALSE;
755 
756     eval_notify_windowport_field(fld, valset, idx);
757     if ((windowprocs.wincap2 & WC2_FLUSH_STATUS) != 0L)
758         status_update(BL_FLUSH, (genericptr_t) 0, 0, 0,
759                       NO_COLOR, (unsigned long *) 0);
760     return;
761 }
762 
763 STATIC_OVL boolean
eval_notify_windowport_field(fld,valsetlist,idx)764 eval_notify_windowport_field(fld, valsetlist, idx)
765 int fld, idx;
766 boolean *valsetlist;
767 {
768     static int oldrndencode = 0;
769     static nhsym oldgoldsym = 0;
770     int pc, chg, color = NO_COLOR;
771     unsigned anytype;
772     boolean updated = FALSE, reset;
773     struct istat_s *curr, *prev;
774     enum statusfields fldmax;
775 
776     /*
777      *  Now pass the changed values to window port.
778      */
779     anytype = blstats[idx][fld].anytype;
780     curr = &blstats[idx][fld];
781     prev = &blstats[1 - idx][fld];
782     color = NO_COLOR;
783 
784     chg = update_all ? 0 : compare_blstats(prev, curr);
785     /*
786      * TODO:
787      *  Dynamically update 'percent_matters' as rules are added or
788      *  removed to track whether any of them are precentage rules.
789      *  Then there'll be no need to assume that non-Null 'thresholds'
790      *  means that percentages need to be kept up to date.
791      *  [Affects exp_percent_changing() too.]
792      */
793     if (((chg || update_all || fld == BL_XP)
794          && curr->percent_matters && curr->thresholds)
795         /* when 'hitpointbar' is On, percent matters even if HP
796            hasn't changed and has no percentage rules (in case HPmax
797            has changed when HP hasn't, where we ordinarily wouldn't
798            update HP so would miss an update of the hitpoint bar) */
799         || (fld == BL_HP && iflags.wc2_hitpointbar)) {
800         fldmax = curr->idxmax;
801         pc = (fldmax == BL_EXP) ? exp_percentage()
802              : (fldmax >= 0) ? percentage(curr, &blstats[idx][fldmax])
803                : 0; /* bullet proofing; can't get here */
804         if (pc != prev->percent_value)
805             chg = 1;
806         curr->percent_value = pc;
807     } else {
808         pc = 0;
809     }
810 
811     /* Temporary? hack: moveloop()'s prolog for a new game sets
812      * context.rndencode after the status window has been init'd,
813      * so $:0 has already been encoded and cached by the window
814      * port.  Without this hack, gold's \G sequence won't be
815      * recognized and ends up being displayed as-is for 'update_all'.
816      *
817      * Also, even if context.rndencode hasn't changed and the
818      * gold amount itself hasn't changed, the glyph portion of the
819      * encoding may have changed if a new symset was put into effect.
820      *
821      *  \GXXXXNNNN:25
822      *  XXXX = the context.rndencode portion
823      *  NNNN = the glyph portion
824      *  25   = the gold amount
825      *
826      * Setting 'chg = 2' is enough to render the field properly, but
827      * not to honor an initial highlight, so force 'update_all = TRUE'.
828      */
829     if (fld == BL_GOLD
830         && (context.rndencode != oldrndencode
831             || showsyms[COIN_CLASS + SYM_OFF_O] != oldgoldsym)) {
832         update_all = TRUE; /* chg = 2; */
833         oldrndencode = context.rndencode;
834         oldgoldsym = showsyms[COIN_CLASS + SYM_OFF_O];
835     }
836 
837     reset = FALSE;
838 #ifdef STATUS_HILITES
839     if (!update_all && !chg && curr->time) {
840         reset = hilite_reset_needed(prev, bl_hilite_moves);
841         if (reset)
842             curr->time = prev->time = 0L;
843     }
844 #endif
845 
846     if (update_all || chg || reset) {
847         if (!valsetlist[fld])
848             (void) anything_to_s(curr->val, &curr->a, anytype);
849 
850         if (anytype != ANY_MASK32) {
851 #ifdef STATUS_HILITES
852             if (chg || *curr->val) {
853                 /* if Xp percentage changed, we set 'chg' to 1 above;
854                    reset that if the Xp value hasn't actually changed
855                    or possibly went down rather than up (level loss) */
856                 if (chg == 1 && fld == BL_XP)
857                     chg = compare_blstats(prev, curr);
858 
859                 curr->hilite_rule = get_hilite(idx, fld,
860                                                (genericptr_t) &curr->a,
861                                                chg, pc, &color);
862                 prev->hilite_rule = curr->hilite_rule;
863                 if (chg == 2) {
864                     color = NO_COLOR;
865                     chg = 0;
866                 }
867             }
868 #endif /* STATUS_HILITES */
869             status_update(fld, (genericptr_t) curr->val,
870                           chg, pc, color, (unsigned long *) 0);
871         } else {
872             /* Color for conditions is done through cond_hilites[] */
873             status_update(fld, (genericptr_t) &curr->a.a_ulong,
874                           chg, pc, color, cond_hilites);
875         }
876         curr->chg = prev->chg = TRUE;
877         updated = TRUE;
878     }
879     return updated;
880 }
881 
882 STATIC_OVL void
evaluate_and_notify_windowport(valsetlist,idx)883 evaluate_and_notify_windowport(valsetlist, idx)
884 int idx;
885 boolean *valsetlist;
886 {
887     int i, updated = 0, notpresent = 0;
888 
889     /*
890      *  Now pass the changed values to window port.
891      */
892     for (i = 0; i < MAXBLSTATS; i++) {
893         if (((i == BL_SCORE) && !flags.showscore)
894             || ((i == BL_EXP) && !flags.showexp)
895             || ((i == BL_TIME) && !flags.time)
896             || ((i == BL_HD) && !Upolyd)
897             || ((i == BL_XP || i == BL_EXP) && Upolyd)) {
898             notpresent++;
899             continue;
900         }
901         if (eval_notify_windowport_field(i, valsetlist, idx))
902             updated++;
903     }
904     /*
905      * Notes:
906      *  1. It is possible to get here, with nothing having been pushed
907      *     to the window port, when none of the info has changed.
908      *
909      *  2. Some window ports are also known to optimize by only drawing
910      *     fields that have changed since the previous update.
911      *
912      * In both of those situations, we need to force updates to
913      * all of the fields when context.botlx is set. The tty port in
914      * particular has a problem if that isn't done, since the core sets
915      * context.botlx when a menu or text display obliterates the status
916      * line.
917      *
918      * For those situations, to trigger the full update of every field
919      * whether changed or not, call status_update() with BL_RESET.
920      *
921      * For regular processing and to notify the window port that a
922      * bot() round has finished and it's time to trigger a flush of
923      * all buffered changes received thus far but not reflected in
924      * the display, call status_update() with BL_FLUSH.
925      *
926      */
927     if (context.botlx && (windowprocs.wincap2 & WC2_RESET_STATUS) != 0L)
928         status_update(BL_RESET, (genericptr_t) 0, 0, 0,
929                       NO_COLOR, (unsigned long *) 0);
930     else if ((updated || context.botlx)
931              && (windowprocs.wincap2 & WC2_FLUSH_STATUS) != 0L)
932         status_update(BL_FLUSH, (genericptr_t) 0, 0, 0,
933                       NO_COLOR, (unsigned long *) 0);
934 
935     context.botl = context.botlx = iflags.time_botl = FALSE;
936     update_all = FALSE;
937 }
938 
939 void
status_initialize(reassessment)940 status_initialize(reassessment)
941 boolean reassessment; /* TRUE: just recheck fields w/o other initialization */
942 {
943     enum statusfields fld;
944     boolean fldenabl;
945     int i;
946     const char *fieldfmt, *fieldname;
947 
948     if (!reassessment) {
949         if (blinit)
950             impossible("2nd status_initialize with full init.");
951         init_blstats();
952         (*windowprocs.win_status_init)();
953         blinit = TRUE;
954     } else if (!blinit) {
955         panic("status 'reassess' before init");
956     }
957     for (i = 0; i < MAXBLSTATS; ++i) {
958         fld = initblstats[i].fld;
959         fldenabl = (fld == BL_SCORE) ? flags.showscore
960                    : (fld == BL_TIME) ? flags.time
961                      : (fld == BL_EXP) ? (boolean) (flags.showexp && !Upolyd)
962                        : (fld == BL_XP) ? (boolean) !Upolyd
963                          : (fld == BL_HD) ? (boolean) Upolyd
964                            : TRUE;
965 
966         fieldname = initblstats[i].fldname;
967         fieldfmt = (fld == BL_TITLE && iflags.wc2_hitpointbar) ? "%-30.30s"
968                    : initblstats[i].fldfmt;
969         status_enablefield(fld, fieldname, fieldfmt, fldenabl);
970     }
971     update_all = TRUE;
972     context.botlx = TRUE;
973 }
974 
975 void
status_finish()976 status_finish()
977 {
978     int i;
979 
980     /* call the window port cleanup routine first */
981     if (windowprocs.win_status_finish)
982         (*windowprocs.win_status_finish)();
983 
984     /* free memory that we alloc'd now */
985     for (i = 0; i < MAXBLSTATS; ++i) {
986         if (blstats[0][i].val)
987             free((genericptr_t) blstats[0][i].val), blstats[0][i].val = 0;
988         if (blstats[1][i].val)
989             free((genericptr_t) blstats[1][i].val), blstats[1][i].val = 0;
990 #ifdef STATUS_HILITES
991         /* pointer to an entry in thresholds list; Null it out since
992            that list is about to go away */
993         blstats[0][i].hilite_rule = blstats[1][i].hilite_rule = 0;
994         if (blstats[0][i].thresholds) {
995             struct hilite_s *temp, *next;
996 
997             for (temp = blstats[0][i].thresholds; temp; temp = next) {
998                 next = temp->next;
999                 free((genericptr_t) temp);
1000             }
1001             blstats[0][i].thresholds = blstats[1][i].thresholds = 0;
1002         }
1003 #endif /* STATUS_HILITES */
1004     }
1005 }
1006 
1007 STATIC_OVL void
init_blstats()1008 init_blstats()
1009 {
1010     static boolean initalready = FALSE;
1011     int i, j;
1012 
1013     if (initalready) {
1014         impossible("init_blstats called more than once.");
1015         return;
1016     }
1017     for (i = 0; i <= 1; ++i) {
1018         for (j = 0; j < MAXBLSTATS; ++j) {
1019 #ifdef STATUS_HILITES
1020             struct hilite_s *keep_hilite_chain = blstats[i][j].thresholds;
1021 #endif
1022 
1023             blstats[i][j] = initblstats[j];
1024             blstats[i][j].a = zeroany;
1025             if (blstats[i][j].valwidth) {
1026                 blstats[i][j].val = (char *) alloc(blstats[i][j].valwidth);
1027                 blstats[i][j].val[0] = '\0';
1028             } else
1029                 blstats[i][j].val = (char *) 0;
1030 #ifdef STATUS_HILITES
1031             blstats[i][j].thresholds = keep_hilite_chain;
1032 #endif
1033         }
1034     }
1035     initalready = TRUE;
1036 }
1037 
1038 /*
1039  * This compares the previous stat with the current stat,
1040  * and returns one of the following results based on that:
1041  *
1042  *   if prev_value < new_value (stat went up, increased)
1043  *      return 1
1044  *
1045  *   if prev_value > new_value (stat went down, decreased)
1046  *      return  -1
1047  *
1048  *   if prev_value == new_value (stat stayed the same)
1049  *      return 0
1050  *
1051  *   Special cases:
1052  *     - for bitmasks, 0 = stayed the same, 1 = changed
1053  *     - for strings,  0 = stayed the same, 1 = changed
1054  *
1055  */
1056 STATIC_OVL int
compare_blstats(bl1,bl2)1057 compare_blstats(bl1, bl2)
1058 struct istat_s *bl1, *bl2;
1059 {
1060     int anytype, result = 0;
1061 
1062     if (!bl1 || !bl2) {
1063         panic("compare_blstat: bad istat pointer %s, %s",
1064               fmt_ptr((genericptr_t) bl1), fmt_ptr((genericptr_t) bl2));
1065     }
1066 
1067     anytype = bl1->anytype;
1068     if ((!bl1->a.a_void || !bl2->a.a_void)
1069         && (anytype == ANY_IPTR || anytype == ANY_UPTR || anytype == ANY_LPTR
1070             || anytype == ANY_ULPTR)) {
1071         panic("compare_blstat: invalid pointer %s, %s",
1072               fmt_ptr((genericptr_t) bl1->a.a_void),
1073               fmt_ptr((genericptr_t) bl2->a.a_void));
1074     }
1075 
1076     switch (anytype) {
1077     case ANY_INT:
1078         result = (bl1->a.a_int < bl2->a.a_int)
1079                      ? 1
1080                      : (bl1->a.a_int > bl2->a.a_int) ? -1 : 0;
1081         break;
1082     case ANY_IPTR:
1083         result = (*bl1->a.a_iptr < *bl2->a.a_iptr)
1084                      ? 1
1085                      : (*bl1->a.a_iptr > *bl2->a.a_iptr) ? -1 : 0;
1086         break;
1087     case ANY_LONG:
1088         result = (bl1->a.a_long < bl2->a.a_long)
1089                      ? 1
1090                      : (bl1->a.a_long > bl2->a.a_long) ? -1 : 0;
1091         break;
1092     case ANY_LPTR:
1093         result = (*bl1->a.a_lptr < *bl2->a.a_lptr)
1094                      ? 1
1095                      : (*bl1->a.a_lptr > *bl2->a.a_lptr) ? -1 : 0;
1096         break;
1097     case ANY_UINT:
1098         result = (bl1->a.a_uint < bl2->a.a_uint)
1099                      ? 1
1100                      : (bl1->a.a_uint > bl2->a.a_uint) ? -1 : 0;
1101         break;
1102     case ANY_UPTR:
1103         result = (*bl1->a.a_uptr < *bl2->a.a_uptr)
1104                      ? 1
1105                      : (*bl1->a.a_uptr > *bl2->a.a_uptr) ? -1 : 0;
1106         break;
1107     case ANY_ULONG:
1108         result = (bl1->a.a_ulong < bl2->a.a_ulong)
1109                      ? 1
1110                      : (bl1->a.a_ulong > bl2->a.a_ulong) ? -1 : 0;
1111         break;
1112     case ANY_ULPTR:
1113         result = (*bl1->a.a_ulptr < *bl2->a.a_ulptr)
1114                      ? 1
1115                      : (*bl1->a.a_ulptr > *bl2->a.a_ulptr) ? -1 : 0;
1116         break;
1117     case ANY_STR:
1118         result = sgn(strcmp(bl1->val, bl2->val));
1119         break;
1120     case ANY_MASK32:
1121         result = (bl1->a.a_ulong != bl2->a.a_ulong);
1122         break;
1123     default:
1124         result = 1;
1125     }
1126     return result;
1127 }
1128 
1129 STATIC_OVL char *
anything_to_s(buf,a,anytype)1130 anything_to_s(buf, a, anytype)
1131 char *buf;
1132 anything *a;
1133 int anytype;
1134 {
1135     if (!buf)
1136         return (char *) 0;
1137 
1138     switch (anytype) {
1139     case ANY_ULONG:
1140         Sprintf(buf, "%lu", a->a_ulong);
1141         break;
1142     case ANY_MASK32:
1143         Sprintf(buf, "%lx", a->a_ulong);
1144         break;
1145     case ANY_LONG:
1146         Sprintf(buf, "%ld", a->a_long);
1147         break;
1148     case ANY_INT:
1149         Sprintf(buf, "%d", a->a_int);
1150         break;
1151     case ANY_UINT:
1152         Sprintf(buf, "%u", a->a_uint);
1153         break;
1154     case ANY_IPTR:
1155         Sprintf(buf, "%d", *a->a_iptr);
1156         break;
1157     case ANY_LPTR:
1158         Sprintf(buf, "%ld", *a->a_lptr);
1159         break;
1160     case ANY_ULPTR:
1161         Sprintf(buf, "%lu", *a->a_ulptr);
1162         break;
1163     case ANY_UPTR:
1164         Sprintf(buf, "%u", *a->a_uptr);
1165         break;
1166     case ANY_STR: /* do nothing */
1167         ;
1168         break;
1169     default:
1170         buf[0] = '\0';
1171     }
1172     return buf;
1173 }
1174 
1175 #ifdef STATUS_HILITES
1176 STATIC_OVL void
s_to_anything(a,buf,anytype)1177 s_to_anything(a, buf, anytype)
1178 anything *a;
1179 char *buf;
1180 int anytype;
1181 {
1182     if (!buf || !a)
1183         return;
1184 
1185     switch (anytype) {
1186     case ANY_LONG:
1187         a->a_long = atol(buf);
1188         break;
1189     case ANY_INT:
1190         a->a_int = atoi(buf);
1191         break;
1192     case ANY_UINT:
1193         a->a_uint = (unsigned) atoi(buf);
1194         break;
1195     case ANY_ULONG:
1196         a->a_ulong = (unsigned long) atol(buf);
1197         break;
1198     case ANY_IPTR:
1199         if (a->a_iptr)
1200             *a->a_iptr = atoi(buf);
1201         break;
1202     case ANY_UPTR:
1203         if (a->a_uptr)
1204             *a->a_uptr = (unsigned) atoi(buf);
1205         break;
1206     case ANY_LPTR:
1207         if (a->a_lptr)
1208             *a->a_lptr = atol(buf);
1209         break;
1210     case ANY_ULPTR:
1211         if (a->a_ulptr)
1212             *a->a_ulptr = (unsigned long) atol(buf);
1213         break;
1214     case ANY_MASK32:
1215         a->a_ulong = (unsigned long) atol(buf);
1216         break;
1217     default:
1218         a->a_void = 0;
1219         break;
1220     }
1221     return;
1222 }
1223 #endif /* STATUS_HILITES */
1224 
1225 STATIC_OVL int
percentage(bl,maxbl)1226 percentage(bl, maxbl)
1227 struct istat_s *bl, *maxbl;
1228 {
1229     int result = 0;
1230     int anytype;
1231     int ival;
1232     long lval;
1233     unsigned uval;
1234     unsigned long ulval;
1235 
1236     if (!bl || !maxbl) {
1237         impossible("percentage: bad istat pointer %s, %s",
1238                    fmt_ptr((genericptr_t) bl), fmt_ptr((genericptr_t) maxbl));
1239         return 0;
1240     }
1241 
1242     ival = 0, lval = 0L, uval = 0U, ulval = 0UL;
1243     anytype = bl->anytype;
1244     if (maxbl->a.a_void) {
1245         switch (anytype) {
1246         case ANY_INT:
1247             ival = bl->a.a_int;
1248             result = ((100 * ival) / maxbl->a.a_int);
1249             break;
1250         case ANY_LONG:
1251             lval  = bl->a.a_long;
1252             result = (int) ((100L * lval) / maxbl->a.a_long);
1253             break;
1254         case ANY_UINT:
1255             uval = bl->a.a_uint;
1256             result = (int) ((100U * uval) / maxbl->a.a_uint);
1257             break;
1258         case ANY_ULONG:
1259             ulval = bl->a.a_ulong;
1260             result = (int) ((100UL * ulval) / maxbl->a.a_ulong);
1261             break;
1262         case ANY_IPTR:
1263             ival = *bl->a.a_iptr;
1264             result = ((100 * ival) / (*maxbl->a.a_iptr));
1265             break;
1266         case ANY_LPTR:
1267             lval = *bl->a.a_lptr;
1268             result = (int) ((100L * lval) / (*maxbl->a.a_lptr));
1269             break;
1270         case ANY_UPTR:
1271             uval = *bl->a.a_uptr;
1272             result = (int) ((100U * uval) / (*maxbl->a.a_uptr));
1273             break;
1274         case ANY_ULPTR:
1275             ulval = *bl->a.a_ulptr;
1276             result = (int) ((100UL * ulval) / (*maxbl->a.a_ulptr));
1277             break;
1278         }
1279     }
1280     /* don't let truncation from integer division produce a zero result
1281        from a non-zero input; note: if we ever change to something like
1282        ((((1000 * val) / max) + 5) / 10) for a rounded result, we'll
1283        also need to check for and convert false 100 to 99 */
1284     if (result == 0 && (ival != 0 || lval != 0L || uval != 0U || ulval != 0UL))
1285         result = 1;
1286 
1287     return result;
1288 }
1289 
1290 /* percentage for both xp (level) and exp (points) is the percentage for
1291    (curr_exp - this_level_start) in (next_level_start - this_level_start) */
1292 STATIC_OVL int
exp_percentage()1293 exp_percentage()
1294 {
1295     int res = 0;
1296 
1297     if (u.ulevel < 30) {
1298         long exp_val, nxt_exp_val, curlvlstart;
1299 
1300         curlvlstart = newuexp(u.ulevel - 1);
1301         exp_val = u.uexp - curlvlstart;
1302         nxt_exp_val = newuexp(u.ulevel) - curlvlstart;
1303         if (exp_val == nxt_exp_val - 1L) {
1304             /*
1305              * Full 100% is unattainable since hero gains a level
1306              * and the threshold for next level increases, but treat
1307              * (next_level_start - 1 point) as a special case.  It's a
1308              * key value after being level drained so is something that
1309              * some players would like to be able to highlight distinctly.
1310              */
1311             res = 100;
1312         } else {
1313             struct istat_s curval, maxval;
1314 
1315             curval.anytype = maxval.anytype = ANY_LONG;
1316             curval.a = maxval.a = zeroany;
1317             curval.a.a_long = exp_val;
1318             maxval.a.a_long = nxt_exp_val;
1319             /* maximum delta between levels is 10000000; calculation of
1320                100 * (10000000 - N) / 10000000 fits within 32-bit long */
1321             res = percentage(&curval, &maxval);
1322         }
1323     }
1324     return res;
1325 }
1326 
1327 /* experience points have changed but experience level hasn't; decide whether
1328    botl update is needed for a different percentage highlight rule for Xp */
1329 boolean
exp_percent_changing()1330 exp_percent_changing()
1331 {
1332     int pc, color_dummy;
1333     anything a;
1334     struct hilite_s *rule;
1335     struct istat_s *curr;
1336 
1337     /* if status update is already requested, skip this processing */
1338     if (!context.botl) {
1339         /*
1340          * Status update is warranted iff percent integer changes and the new
1341          * percentage results in a different highlighting rule being selected.
1342          */
1343         curr = &blstats[now_or_before_idx][BL_XP];
1344         /* TODO: [see eval_notify_windowport_field() about percent_matters
1345            and the check against 'thresholds'] */
1346         if (curr->percent_matters && curr->thresholds
1347             && (pc = exp_percentage()) != curr->percent_value) {
1348             a = zeroany;
1349             a.a_int = (int) u.ulevel;
1350             rule = get_hilite(now_or_before_idx, BL_XP,
1351                               (genericptr_t) &a, 0, pc, &color_dummy);
1352             if (rule != curr->hilite_rule)
1353                 return TRUE; /* caller should set 'context.botl' to True */
1354         }
1355     }
1356     return FALSE;
1357 }
1358 
1359 /* callback so that interface can get capacity index rather than trying
1360    to reconstruct that from the encumbrance string or asking the general
1361    core what the value is */
1362 int
stat_cap_indx()1363 stat_cap_indx()
1364 {
1365     int cap;
1366 
1367 #ifdef STATUS_HILITES
1368     cap = blstats[now_or_before_idx][BL_CAP].a.a_int;
1369 #else
1370     cap = near_capacity();
1371 #endif
1372     return cap;
1373 }
1374 
1375 /* callback so that interface can get hunger index rather than trying to
1376    reconstruct that from the hunger string or dipping into core internals */
1377 int
stat_hunger_indx()1378 stat_hunger_indx()
1379 {
1380     int uhs;
1381 
1382 #ifdef STATUS_HILITES
1383     uhs = blstats[now_or_before_idx][BL_HUNGER].a.a_int;
1384 #else
1385     uhs = (int) u.uhs;
1386 #endif
1387     return uhs;
1388 }
1389 
1390 /* used by X11 for "tty status" even when STATUS_HILITES is disabled */
1391 const char *
bl_idx_to_fldname(idx)1392 bl_idx_to_fldname(idx)
1393 int idx;
1394 {
1395     if (idx >= 0 && idx < MAXBLSTATS)
1396         return initblstats[idx].fldname;
1397     return (const char *) 0;
1398 }
1399 
1400 #ifdef STATUS_HILITES
1401 
1402 /****************************************************************************/
1403 /* Core status hiliting support */
1404 /****************************************************************************/
1405 
1406 struct hilite_s status_hilites[MAXBLSTATS];
1407 
1408 static struct fieldid_t {
1409     const char *fieldname;
1410     enum statusfields fldid;
1411 } fieldids_alias[] = {
1412     { "characteristics",   BL_CHARACTERISTICS },
1413     { "encumbrance",       BL_CAP },
1414     { "experience-points", BL_EXP },
1415     { "dx",       BL_DX },
1416     { "co",       BL_CO },
1417     { "con",      BL_CO },
1418     { "points",   BL_SCORE },
1419     { "cap",      BL_CAP },
1420     { "pw",       BL_ENE },
1421     { "pw-max",   BL_ENEMAX },
1422     { "xl",       BL_XP },
1423     { "xplvl",    BL_XP },
1424     { "ac",       BL_AC },
1425     { "hit-dice", BL_HD },
1426     { "turns",    BL_TIME },
1427     { "hp",       BL_HP },
1428     { "hp-max",   BL_HPMAX },
1429     { "dgn",      BL_LEVELDESC },
1430     { "xp",       BL_EXP },
1431     { "exp",      BL_EXP },
1432     { "flags",    BL_CONDITION },
1433     {0,           BL_FLUSH }
1434 };
1435 
1436 /* format arguments */
1437 static const char threshold_value[] = "hilite_status threshold ",
1438                   is_out_of_range[] = " is out of range";
1439 
1440 
1441 /* field name to bottom line index */
1442 STATIC_OVL enum statusfields
fldname_to_bl_indx(name)1443 fldname_to_bl_indx(name)
1444 const char *name;
1445 {
1446     int i, nmatches = 0, fld = 0;
1447 
1448     if (name && *name) {
1449         /* check matches to canonical names */
1450         for (i = 0; i < SIZE(initblstats); i++)
1451             if (fuzzymatch(initblstats[i].fldname, name, " -_", TRUE)) {
1452                 fld = initblstats[i].fld;
1453                 nmatches++;
1454             }
1455 
1456         if (!nmatches) {
1457             /* check aliases */
1458             for (i = 0; fieldids_alias[i].fieldname; i++)
1459                 if (fuzzymatch(fieldids_alias[i].fieldname, name,
1460                                " -_", TRUE)) {
1461                     fld = fieldids_alias[i].fldid;
1462                     nmatches++;
1463                 }
1464         }
1465 
1466         if (!nmatches) {
1467             /* check partial matches to canonical names */
1468             int len = (int) strlen(name);
1469 
1470             for (i = 0; i < SIZE(initblstats); i++)
1471                 if (!strncmpi(name, initblstats[i].fldname, len)) {
1472                     fld = initblstats[i].fld;
1473                     nmatches++;
1474                 }
1475         }
1476 
1477     }
1478     return (nmatches == 1) ? fld : BL_FLUSH;
1479 }
1480 
1481 STATIC_OVL boolean
hilite_reset_needed(bl_p,augmented_time)1482 hilite_reset_needed(bl_p, augmented_time)
1483 struct istat_s *bl_p;
1484 long augmented_time; /* no longer augmented; it once encoded fractional
1485                       * amounts for multiple moves within same turn     */
1486 {
1487     /*
1488      * This 'multi' handling may need some tuning...
1489      */
1490     if (multi)
1491         return FALSE;
1492 
1493     if (!Is_Temp_Hilite(bl_p->hilite_rule))
1494         return FALSE;
1495 
1496     if (bl_p->time == 0 || bl_p->time >= augmented_time)
1497         return FALSE;
1498 
1499     return TRUE;
1500 }
1501 
1502 /* called from moveloop(); sets context.botl if temp hilites have timed out */
1503 void
status_eval_next_unhilite()1504 status_eval_next_unhilite()
1505 {
1506     int i;
1507     struct istat_s *curr;
1508     long next_unhilite, this_unhilite;
1509 
1510     bl_hilite_moves = moves; /* simpllfied; used to try to encode fractional
1511                               * amounts for multiple moves within same turn */
1512     /* figure out whether an unhilight needs to be performed now */
1513     next_unhilite = 0L;
1514     for (i = 0; i < MAXBLSTATS; ++i) {
1515         curr = &blstats[0][i]; /* blstats[0][*].time == blstats[1][*].time */
1516 
1517         if (curr->chg) {
1518             struct istat_s *prev = &blstats[1][i];
1519 
1520             if (Is_Temp_Hilite(curr->hilite_rule))
1521                 curr->time = prev->time = (bl_hilite_moves
1522                                            + iflags.hilite_delta);
1523             else
1524                 curr->time = prev->time = 0L;
1525 
1526             curr->chg = prev->chg = FALSE;
1527             context.botl = TRUE;
1528         }
1529         if (context.botl)
1530             continue; /* just process other blstats[][].time and .chg */
1531 
1532         this_unhilite = curr->time;
1533         if (this_unhilite > 0L
1534             && (next_unhilite == 0L || this_unhilite < next_unhilite)
1535             && hilite_reset_needed(curr, this_unhilite + 1L)) {
1536             next_unhilite = this_unhilite;
1537             if (next_unhilite < bl_hilite_moves)
1538                 context.botl = TRUE;
1539         }
1540     }
1541 }
1542 
1543 /* called by options handling when 'statushilites' value is changed */
1544 void
reset_status_hilites()1545 reset_status_hilites()
1546 {
1547     if (iflags.hilite_delta) {
1548         int i;
1549 
1550         for (i = 0; i < MAXBLSTATS; ++i)
1551             blstats[0][i].time = blstats[1][i].time = 0L;
1552         update_all = TRUE;
1553     }
1554     context.botlx = TRUE;
1555 }
1556 
1557 /* test whether the text from a title rule matches the string for
1558    title-while-polymorphed in the 'textmatch' menu */
1559 STATIC_OVL boolean
noneoftheabove(hl_text)1560 noneoftheabove(hl_text)
1561 const char *hl_text;
1562 {
1563     if (fuzzymatch(hl_text, "none of the above", "\" -_", TRUE)
1564         || fuzzymatch(hl_text, "(polymorphed)", "\"()", TRUE)
1565         || fuzzymatch(hl_text, "none of the above (polymorphed)",
1566                       "\" -_()", TRUE))
1567         return TRUE;
1568     return FALSE;
1569 }
1570 
1571 /*
1572  * get_hilite
1573  *
1574  * Returns, based on the value and the direction it is moving,
1575  * the highlight rule that applies to the specified field.
1576  *
1577  * Provide get_hilite() with the following to work with:
1578  *     actual value vp
1579  *          useful for BL_TH_VAL_ABSOLUTE
1580  *     indicator of down, up, or the same (-1, 1, 0) chg
1581  *          useful for BL_TH_UPDOWN or change detection
1582  *     percentage (current value percentage of max value) pc
1583  *          useful for BL_TH_VAL_PERCENTAGE
1584  *
1585  * Get back:
1586  *     pointer to rule that applies; Null if no rule does.
1587  */
1588 STATIC_OVL struct hilite_s *
get_hilite(idx,fldidx,vp,chg,pc,colorptr)1589 get_hilite(idx, fldidx, vp, chg, pc, colorptr)
1590 int idx, fldidx, chg, pc;
1591 genericptr_t vp;
1592 int *colorptr;
1593 {
1594     struct hilite_s *hl, *rule = 0;
1595     anything *value = (anything *) vp;
1596     char *txtstr;
1597 
1598     if (fldidx < 0 || fldidx >= MAXBLSTATS)
1599         return (struct hilite_s *) 0;
1600 
1601     if (has_hilite(fldidx)) {
1602         int dt;
1603         /* there are hilites set here */
1604         int max_pc = -1, min_pc = 101;
1605         /* LARGEST_INT isn't INT_MAX; it fits within 16 bits, but that
1606            value is big enough to handle all 'int' status fields */
1607         int max_ival = -LARGEST_INT, min_ival = LARGEST_INT;
1608         /* LONG_MAX comes from <limits.h> which might not be available for
1609            ancient configurations; we don't need LONG_MIN */
1610         long max_lval = -LONG_MAX, min_lval = LONG_MAX;
1611         boolean exactmatch = FALSE, updown = FALSE, changed = FALSE,
1612                 perc_or_abs = FALSE;
1613 
1614         /* min_/max_ are used to track best fit */
1615         for (hl = blstats[0][fldidx].thresholds; hl; hl = hl->next) {
1616             dt = initblstats[fldidx].anytype; /* only needed for 'absolute' */
1617             /* if we've already matched a temporary highlight, it takes
1618                precedence over all persistent ones; we still process
1619                updown rules to get the last one which qualifies */
1620             if ((updown || changed) && hl->behavior != BL_TH_UPDOWN)
1621                 continue;
1622             /* among persistent highlights, if a 'percentage' or 'absolute'
1623                rule has been matched, it takes precedence over 'always' */
1624             if (perc_or_abs && hl->behavior == BL_TH_ALWAYS_HILITE)
1625                 continue;
1626 
1627             switch (hl->behavior) {
1628             case BL_TH_VAL_PERCENTAGE: /* percent values are always ANY_INT */
1629                 if (hl->rel == EQ_VALUE && pc == hl->value.a_int) {
1630                     rule = hl;
1631                     min_pc = max_pc = hl->value.a_int;
1632                     exactmatch = perc_or_abs = TRUE;
1633                 } else if (exactmatch) {
1634                     ; /* already found best fit, skip lt,ge,&c */
1635                 } else if (hl->rel == LT_VALUE
1636                            && (pc < hl->value.a_int)
1637                            && (hl->value.a_int <= min_pc)) {
1638                     rule = hl;
1639                     min_pc = hl->value.a_int;
1640                     perc_or_abs = TRUE;
1641                 } else if (hl->rel == LE_VALUE
1642                            && (pc <= hl->value.a_int)
1643                            && (hl->value.a_int <= min_pc)) {
1644                     rule = hl;
1645                     min_pc = hl->value.a_int;
1646                     perc_or_abs = TRUE;
1647                 } else if (hl->rel == GT_VALUE
1648                            && (pc > hl->value.a_int)
1649                            && (hl->value.a_int >= max_pc)) {
1650                     rule = hl;
1651                     max_pc = hl->value.a_int;
1652                     perc_or_abs = TRUE;
1653                 } else if (hl->rel == GE_VALUE
1654                            && (pc >= hl->value.a_int)
1655                            && (hl->value.a_int >= max_pc)) {
1656                     rule = hl;
1657                     max_pc = hl->value.a_int;
1658                     perc_or_abs = TRUE;
1659                 }
1660                 break;
1661             case BL_TH_UPDOWN: /* uses 'chg' (set by caller), not 'dt' */
1662                 /* specific 'up' or 'down' takes precedence over general
1663                    'changed' regardless of their order in the rule set */
1664                 if (chg < 0 && hl->rel == LT_VALUE) {
1665                     rule = hl;
1666                     updown = TRUE;
1667                 } else if (chg > 0 && hl->rel == GT_VALUE) {
1668                     rule = hl;
1669                     updown = TRUE;
1670                 } else if (chg != 0 && hl->rel == EQ_VALUE && !updown) {
1671                     rule = hl;
1672                     changed = TRUE;
1673                 }
1674                 break;
1675             case BL_TH_VAL_ABSOLUTE: /* either ANY_INT or ANY_LONG */
1676                 /*
1677                  * The int and long variations here are identical aside from
1678                  * union field and min_/max_ variable names.  If you change
1679                  * one, be sure to make a corresponding change in the other.
1680                  */
1681                 if (dt == ANY_INT) {
1682                     if (hl->rel == EQ_VALUE
1683                         && hl->value.a_int == value->a_int) {
1684                         rule = hl;
1685                         min_ival = max_ival = hl->value.a_int;
1686                         exactmatch = perc_or_abs = TRUE;
1687                     } else if (exactmatch) {
1688                         ; /* already found best fit, skip lt,ge,&c */
1689                     } else if (hl->rel == LT_VALUE
1690                                && (value->a_int < hl->value.a_int)
1691                                && (hl->value.a_int <= min_ival)) {
1692                         rule = hl;
1693                         min_ival = hl->value.a_int;
1694                         perc_or_abs = TRUE;
1695                     } else if (hl->rel == LE_VALUE
1696                                && (value->a_int <= hl->value.a_int)
1697                                && (hl->value.a_int <= min_ival)) {
1698                         rule = hl;
1699                         min_ival = hl->value.a_int;
1700                         perc_or_abs = TRUE;
1701                     } else if (hl->rel == GT_VALUE
1702                                && (value->a_int > hl->value.a_int)
1703                                && (hl->value.a_int >= max_ival)) {
1704                         rule = hl;
1705                         max_ival = hl->value.a_int;
1706                         perc_or_abs = TRUE;
1707                     } else if (hl->rel == GE_VALUE
1708                                && (value->a_int >= hl->value.a_int)
1709                                && (hl->value.a_int >= max_ival)) {
1710                         rule = hl;
1711                         max_ival = hl->value.a_int;
1712                         perc_or_abs = TRUE;
1713                     }
1714                 } else { /* ANY_LONG */
1715                     if (hl->rel == EQ_VALUE
1716                         && hl->value.a_long == value->a_long) {
1717                         rule = hl;
1718                         min_lval = max_lval = hl->value.a_long;
1719                         exactmatch = perc_or_abs = TRUE;
1720                     } else if (exactmatch) {
1721                         ; /* already found best fit, skip lt,ge,&c */
1722                     } else if (hl->rel == LT_VALUE
1723                                && (value->a_long < hl->value.a_long)
1724                                && (hl->value.a_long <= min_lval)) {
1725                         rule = hl;
1726                         min_lval = hl->value.a_long;
1727                         perc_or_abs = TRUE;
1728                     } else if (hl->rel == LE_VALUE
1729                                && (value->a_long <= hl->value.a_long)
1730                                && (hl->value.a_long <= min_lval)) {
1731                         rule = hl;
1732                         min_lval = hl->value.a_long;
1733                         perc_or_abs = TRUE;
1734                     } else if (hl->rel == GT_VALUE
1735                                && (value->a_long > hl->value.a_long)
1736                                && (hl->value.a_long >= max_lval)) {
1737                         rule = hl;
1738                         max_lval = hl->value.a_long;
1739                         perc_or_abs = TRUE;
1740                     } else if (hl->rel == GE_VALUE
1741                                && (value->a_long >= hl->value.a_long)
1742                                && (hl->value.a_long >= max_lval)) {
1743                         rule = hl;
1744                         max_lval = hl->value.a_long;
1745                         perc_or_abs = TRUE;
1746                     }
1747                 }
1748                 break;
1749             case BL_TH_TEXTMATCH: /* ANY_STR */
1750                 txtstr = blstats[idx][fldidx].val;
1751                 if (fldidx == BL_TITLE)
1752                     /* "<name> the <rank-title>", skip past "<name> the " */
1753                     txtstr += (strlen(plname) + sizeof " the " - sizeof "");
1754                 if (hl->rel == TXT_VALUE && hl->textmatch[0]) {
1755                     if (fuzzymatch(hl->textmatch, txtstr, "\" -_", TRUE)) {
1756                         rule = hl;
1757                         exactmatch = TRUE;
1758                     } else if (exactmatch) {
1759                         ; /* already found best fit, skip "noneoftheabove" */
1760                     } else if (fldidx == BL_TITLE
1761                                && Upolyd && noneoftheabove(hl->textmatch)) {
1762                         rule = hl;
1763                     }
1764                 }
1765                 break;
1766             case BL_TH_ALWAYS_HILITE:
1767                 rule = hl;
1768                 break;
1769             case BL_TH_NONE:
1770                 break;
1771             default:
1772                 break;
1773             }
1774         }
1775     }
1776     *colorptr = rule ? rule->coloridx : NO_COLOR;
1777     return rule;
1778 }
1779 
1780 STATIC_OVL void
split_clridx(idx,coloridx,attrib)1781 split_clridx(idx, coloridx, attrib)
1782 int idx;
1783 int *coloridx, *attrib;
1784 {
1785     if (coloridx)
1786         *coloridx = idx & 0x00FF;
1787     if (attrib)
1788         *attrib = (idx >> 8) & 0x00FF;
1789 }
1790 
1791 /*
1792  * This is the parser for the hilite options.
1793  *
1794  * parse_status_hl1() separates each hilite entry into
1795  * a set of field threshold/action component strings,
1796  * then calls parse_status_hl2() to parse further
1797  * and configure the hilite.
1798  */
1799 boolean
parse_status_hl1(op,from_configfile)1800 parse_status_hl1(op, from_configfile)
1801 char *op;
1802 boolean from_configfile;
1803 {
1804 #define MAX_THRESH 21
1805     char hsbuf[MAX_THRESH][QBUFSZ];
1806     boolean rslt, badopt = FALSE;
1807     int i, fldnum, ccount = 0;
1808     char c;
1809 
1810     fldnum = 0;
1811     for (i = 0; i < MAX_THRESH; ++i) {
1812         hsbuf[i][0] = '\0';
1813     }
1814     while (*op && fldnum < MAX_THRESH && ccount < (QBUFSZ - 2)) {
1815         c = lowc(*op);
1816         if (c == ' ') {
1817             if (fldnum >= 1) {
1818                 if (fldnum == 1 && strcmpi(hsbuf[0], "title") == 0) {
1819                     /* spaces are allowed in title */
1820                     hsbuf[fldnum][ccount++] = c;
1821                     hsbuf[fldnum][ccount] = '\0';
1822                     op++;
1823                     continue;
1824                 }
1825                 rslt = parse_status_hl2(hsbuf, from_configfile);
1826                 if (!rslt) {
1827                     badopt = TRUE;
1828                     break;
1829                 }
1830             }
1831             for (i = 0; i < MAX_THRESH; ++i) {
1832                 hsbuf[i][0] = '\0';
1833             }
1834             fldnum = 0;
1835             ccount = 0;
1836         } else if (c == '/') {
1837             fldnum++;
1838             ccount = 0;
1839         } else {
1840             hsbuf[fldnum][ccount++] = c;
1841             hsbuf[fldnum][ccount] = '\0';
1842         }
1843         op++;
1844     }
1845     if (fldnum >= 1 && !badopt) {
1846         rslt = parse_status_hl2(hsbuf, from_configfile);
1847         if (!rslt)
1848             badopt = TRUE;
1849     }
1850     if (badopt)
1851         return FALSE;
1852     return TRUE;
1853 }
1854 
1855 /* is str in the format of "[<>]?=?[-+]?[0-9]+%?" regex */
1856 STATIC_OVL boolean
is_ltgt_percentnumber(str)1857 is_ltgt_percentnumber(str)
1858 const char *str;
1859 {
1860     const char *s = str;
1861 
1862     if (*s == '<' || *s == '>')
1863         s++;
1864     if (*s == '=')
1865         s++;
1866     if (*s == '-' || *s == '+')
1867         s++;
1868     if (!digit(*s))
1869         return FALSE;
1870     while (digit(*s))
1871         s++;
1872     if (*s == '%')
1873         s++;
1874     return (*s == '\0');
1875 }
1876 
1877 /* does str only contain "<>=-+0-9%" chars */
1878 STATIC_OVL boolean
has_ltgt_percentnumber(str)1879 has_ltgt_percentnumber(str)
1880 const char *str;
1881 {
1882     const char *s = str;
1883 
1884     while (*s) {
1885         if (!index("<>=-+0123456789%", *s))
1886             return FALSE;
1887         s++;
1888     }
1889     return TRUE;
1890 }
1891 
1892 /* splitsubfields(): splits str in place into '+' or '&' separated strings.
1893  * returns number of strings, or -1 if more than maxsf or MAX_SUBFIELDS
1894  */
1895 #define MAX_SUBFIELDS 16
1896 STATIC_OVL int
splitsubfields(str,sfarr,maxsf)1897 splitsubfields(str, sfarr, maxsf)
1898 char *str;
1899 char ***sfarr;
1900 int maxsf;
1901 {
1902     static char *subfields[MAX_SUBFIELDS];
1903     char *st = (char *) 0;
1904     int sf = 0;
1905 
1906     if (!str)
1907         return 0;
1908     for (sf = 0; sf < MAX_SUBFIELDS; ++sf)
1909         subfields[sf] = (char *) 0;
1910 
1911     maxsf = (maxsf == 0) ? MAX_SUBFIELDS : min(maxsf, MAX_SUBFIELDS);
1912 
1913     if (index(str, '+') || index(str, '&')) {
1914         char *c = str;
1915 
1916         sf = 0;
1917         st = c;
1918         while (*c && sf < maxsf) {
1919             if (*c == '&' || *c == '+') {
1920                 *c = '\0';
1921                 subfields[sf] = st;
1922                 st = c+1;
1923                 sf++;
1924             }
1925             c++;
1926         }
1927         if (sf >= maxsf - 1)
1928             return -1;
1929         if (!*c && c != st)
1930             subfields[sf++] = st;
1931     } else {
1932         sf = 1;
1933         subfields[0] = str;
1934     }
1935     *sfarr = subfields;
1936     return sf;
1937 }
1938 #undef MAX_SUBFIELDS
1939 
1940 STATIC_OVL boolean
is_fld_arrayvalues(str,arr,arrmin,arrmax,retidx)1941 is_fld_arrayvalues(str, arr, arrmin, arrmax, retidx)
1942 const char *str;
1943 const char *const *arr;
1944 int arrmin, arrmax;
1945 int *retidx;
1946 {
1947     int i;
1948 
1949     for (i = arrmin; i < arrmax; i++)
1950         if (!strcmpi(str, arr[i])) {
1951             *retidx = i;
1952             return TRUE;
1953         }
1954     return FALSE;
1955 }
1956 
1957 STATIC_OVL int
query_arrayvalue(querystr,arr,arrmin,arrmax)1958 query_arrayvalue(querystr, arr, arrmin, arrmax)
1959 const char *querystr;
1960 const char *const *arr;
1961 int arrmin, arrmax;
1962 {
1963     int i, res, ret = arrmin - 1;
1964     winid tmpwin;
1965     anything any;
1966     menu_item *picks = (menu_item *) 0;
1967     int adj = (arrmin > 0) ? 1 : arrmax;
1968 
1969     tmpwin = create_nhwindow(NHW_MENU);
1970     start_menu(tmpwin);
1971 
1972     for (i = arrmin; i < arrmax; i++) {
1973         any = zeroany;
1974         any.a_int = i + adj;
1975         add_menu(tmpwin, NO_GLYPH, &any, 0, 0, ATR_NONE,
1976                  arr[i], MENU_UNSELECTED);
1977     }
1978 
1979     end_menu(tmpwin, querystr);
1980 
1981     res = select_menu(tmpwin, PICK_ONE, &picks);
1982     destroy_nhwindow(tmpwin);
1983     if (res > 0) {
1984         ret = picks->item.a_int - adj;
1985         free((genericptr_t) picks);
1986     }
1987 
1988     return ret;
1989 }
1990 
1991 STATIC_OVL void
status_hilite_add_threshold(fld,hilite)1992 status_hilite_add_threshold(fld, hilite)
1993 int fld;
1994 struct hilite_s *hilite;
1995 {
1996     struct hilite_s *new_hilite;
1997 
1998     if (!hilite)
1999         return;
2000 
2001     /* alloc and initialize a new hilite_s struct */
2002     new_hilite = (struct hilite_s *) alloc(sizeof (struct hilite_s));
2003     *new_hilite = *hilite;   /* copy struct */
2004 
2005     new_hilite->set = TRUE;
2006     new_hilite->fld = fld;
2007     new_hilite->next = blstats[0][fld].thresholds;
2008     blstats[0][fld].thresholds = new_hilite;
2009     /* sort_hilites(fld) */
2010 
2011     /* current and prev must both point at the same hilites */
2012     blstats[1][fld].thresholds = blstats[0][fld].thresholds;
2013 }
2014 
2015 
2016 STATIC_OVL boolean
2017 parse_status_hl2(s, from_configfile)
2018 char (*s)[QBUFSZ];
2019 boolean from_configfile;
2020 {
2021     char *tmp, *how;
2022     int sidx = 0, i = -1, dt = -1;
2023     int coloridx = -1, successes = 0;
2024     int disp_attrib = 0;
2025     boolean percent, changed, numeric, down, up,
2026             gt, lt, ge, le, eq, txtval, always;
2027     const char *txt;
2028     enum statusfields fld = BL_FLUSH;
2029     struct hilite_s hilite;
2030     char tmpbuf[BUFSZ];
2031     static const char *aligntxt[] = { "chaotic", "neutral", "lawful" };
2032     /* hu_stat[] from eat.c has trailing spaces which foul up comparisons */
2033     static const char *hutxt[] = { "Satiated", "", "Hungry", "Weak",
2034                                    "Fainting", "Fainted", "Starved" };
2035 
2036     /* Examples:
2037         3.6.1:
2038       OPTION=hilite_status: hitpoints/<10%/red
2039       OPTION=hilite_status: hitpoints/<10%/red/<5%/purple/1/red+blink+inverse
2040       OPTION=hilite_status: experience/down/red/up/green
2041       OPTION=hilite_status: cap/strained/yellow/overtaxed/orange
2042       OPTION=hilite_status: title/always/blue
2043       OPTION=hilite_status: title/blue
2044     */
2045 
2046     /* field name to statusfield */
2047     fld = fldname_to_bl_indx(s[sidx]);
2048 
2049     if (fld == BL_CHARACTERISTICS) {
2050         boolean res = FALSE;
2051 
2052         /* recursively set each of strength, dexterity, constitution, &c */
2053         for (fld = BL_STR; fld <= BL_CH; fld++) {
2054             Strcpy(s[sidx], initblstats[fld].fldname);
2055             res = parse_status_hl2(s, from_configfile);
2056             if (!res)
2057                 return FALSE;
2058         }
2059         return TRUE;
2060     }
2061     if (fld == BL_FLUSH) {
2062         config_error_add("Unknown status field '%s'", s[sidx]);
2063         return FALSE;
2064     }
2065     if (fld == BL_CONDITION)
2066         return parse_condition(s, sidx);
2067 
2068     ++sidx;
2069     while (s[sidx]) {
2070         char buf[BUFSZ], **subfields;
2071         int sf = 0;     /* subfield count */
2072         int kidx;
2073 
2074         txt = (const char *)0;
2075         percent = numeric = always = FALSE;
2076         down = up = changed = FALSE;
2077         gt = ge = eq = le = lt = txtval = FALSE;
2078 
2079         /* threshold value */
2080         if (!s[sidx][0])
2081             return TRUE;
2082 
2083         memset((genericptr_t) &hilite, 0, sizeof (struct hilite_s));
2084         hilite.set = FALSE; /* mark it "unset" */
2085         hilite.fld = fld;
2086 
2087         if (*s[sidx + 1] == '\0' || !strcmpi(s[sidx], "always")) {
2088             /* "field/always/color" OR "field/color" */
2089             always = TRUE;
2090             if (*s[sidx + 1] == '\0')
2091                 sidx--;
2092         } else if (!strcmpi(s[sidx], "up") || !strcmpi(s[sidx], "down")) {
2093             if (initblstats[fld].anytype == ANY_STR)
2094                 /* ordered string comparison is supported but LT/GT for
2095                    the string fields (title, dungeon-level, alignment)
2096                    is pointless; treat 'up' or 'down' for string fields
2097                    as 'changed' rather than rejecting them outright */
2098                 ;
2099             else if (!strcmpi(s[sidx], "down"))
2100                 down = TRUE;
2101             else
2102                 up = TRUE;
2103             changed = TRUE;
2104         } else if (fld == BL_CAP
2105                    && is_fld_arrayvalues(s[sidx], enc_stat,
2106                                          SLT_ENCUMBER, OVERLOADED + 1,
2107                                          &kidx)) {
2108             txt = enc_stat[kidx];
2109             txtval = TRUE;
2110         } else if (fld == BL_ALIGN
2111                    && is_fld_arrayvalues(s[sidx], aligntxt, 0, 3, &kidx)) {
2112             txt = aligntxt[kidx];
2113             txtval = TRUE;
2114         } else if (fld == BL_HUNGER
2115                    && is_fld_arrayvalues(s[sidx], hutxt,
2116                                          SATIATED, STARVED + 1, &kidx)) {
2117             txt = hu_stat[kidx];   /* store hu_stat[] val, not hutxt[] */
2118             txtval = TRUE;
2119         } else if (!strcmpi(s[sidx], "changed")) {
2120             changed = TRUE;
2121         } else if (is_ltgt_percentnumber(s[sidx])) {
2122             const char *op;
2123 
2124             tmp = s[sidx]; /* is_ltgt_() guarantees [<>]?=?[-+]?[0-9]+%? */
2125             if (strchr(tmp, '%'))
2126                percent = TRUE;
2127             if (*tmp == '<') {
2128                 if (tmp[1] == '=')
2129                     le = TRUE;
2130                 else
2131                     lt = TRUE;
2132             } else if (*tmp == '>') {
2133                 if (tmp[1] == '=')
2134                     ge = TRUE;
2135                 else
2136                     gt = TRUE;
2137             }
2138             /* '%', '<', '>' have served their purpose, '=' is either
2139                part of '<' or '>' or optional for '=N', unary '+' is
2140                just decorative, so get rid of them, leaving -?[0-9]+ */
2141             tmp = stripchars(tmpbuf, "%<>=+", tmp);
2142             numeric = TRUE;
2143             dt = percent ? ANY_INT : initblstats[fld].anytype;
2144             (void) s_to_anything(&hilite.value, tmp, dt);
2145 
2146             op = gt ? ">" : ge ? ">=" : lt ? "<" : le ? "<=" : "=";
2147             if (dt == ANY_INT
2148                 /* AC is the only field where negative values make sense but
2149                    accept >-1 for other fields; reject <0 for non-AC */
2150                 && (hilite.value.a_int
2151                     < ((fld == BL_AC) ? -128 : gt ? -1 : lt ? 1 : 0)
2152                 /* percentages have another more comprehensive check below */
2153                     || hilite.value.a_int > (percent ? (lt ? 101 : 100)
2154                                                      : LARGEST_INT))) {
2155                 config_error_add("%s'%s%d%s'%s", threshold_value,
2156                                  op, hilite.value.a_int, percent ? "%" : "",
2157                                  is_out_of_range);
2158                 return FALSE;
2159             } else if (dt == ANY_LONG
2160                        && (hilite.value.a_long < (gt ? -1L : lt ? 1L : 0L))) {
2161                 config_error_add("%s'%s%ld'%s", threshold_value,
2162                                  op, hilite.value.a_long, is_out_of_range);
2163                 return FALSE;
2164             }
2165         } else if (initblstats[fld].anytype == ANY_STR) {
2166             txt = s[sidx];
2167             txtval = TRUE;
2168         } else {
2169             config_error_add(has_ltgt_percentnumber(s[sidx])
2170                  ? "Wrong format '%s', expected a threshold number or percent"
2171                  : "Unknown behavior '%s'",
2172                              s[sidx]);
2173             return FALSE;
2174         }
2175 
2176         /* relationships {LT_VALUE, LE_VALUE, EQ_VALUE, GE_VALUE, GT_VALUE} */
2177         if (gt || up)
2178             hilite.rel = GT_VALUE;
2179         else if (lt || down)
2180             hilite.rel = LT_VALUE;
2181         else if (ge)
2182             hilite.rel = GE_VALUE;
2183         else if (le)
2184             hilite.rel = LE_VALUE;
2185         else if (eq  || percent || numeric || changed)
2186             hilite.rel = EQ_VALUE;
2187         else if (txtval)
2188             hilite.rel = TXT_VALUE;
2189         else
2190             hilite.rel = LT_VALUE;
2191 
2192         if (initblstats[fld].anytype == ANY_STR && (percent || numeric)) {
2193             config_error_add("Field '%s' does not support numeric values",
2194                              initblstats[fld].fldname);
2195             return FALSE;
2196         }
2197 
2198         if (percent) {
2199             if (initblstats[fld].idxmax < 0) {
2200                 config_error_add("Cannot use percent with '%s'",
2201                                  initblstats[fld].fldname);
2202                 return FALSE;
2203             } else if ((hilite.value.a_int < -1)
2204                        || (hilite.value.a_int == -1
2205                            && hilite.value.a_int != GT_VALUE)
2206                        || (hilite.value.a_int == 0
2207                            && hilite.rel == LT_VALUE)
2208                        || (hilite.value.a_int == 100
2209                            && hilite.rel == GT_VALUE)
2210                        || (hilite.value.a_int == 101
2211                            && hilite.value.a_int != LT_VALUE)
2212                        || (hilite.value.a_int > 101)) {
2213                 config_error_add(
2214                            "hilite_status: invalid percentage value '%s%d%%'",
2215                                  (hilite.rel == LT_VALUE) ? "<"
2216                                    : (hilite.rel == LE_VALUE) ? "<="
2217                                      : (hilite.rel == GT_VALUE) ? ">"
2218                                        : (hilite.rel == GE_VALUE) ? ">="
2219                                          : "=",
2220                                  hilite.value.a_int);
2221                 return FALSE;
2222             }
2223         }
2224 
2225         /* actions */
2226         sidx++;
2227         how = s[sidx];
2228         if (!how) {
2229             if (!successes)
2230                 return FALSE;
2231         }
2232         coloridx = -1;
2233         Strcpy(buf, how);
2234         sf = splitsubfields(buf, &subfields, 0);
2235 
2236         if (sf < 1)
2237             return FALSE;
2238 
2239         disp_attrib = HL_UNDEF;
2240 
2241         for (i = 0; i < sf; ++i) {
2242             int a = match_str2attr(subfields[i], FALSE);
2243 
2244             if (a == ATR_DIM)
2245                 disp_attrib |= HL_DIM;
2246             else if (a == ATR_BLINK)
2247                 disp_attrib |= HL_BLINK;
2248             else if (a == ATR_ULINE)
2249                 disp_attrib |= HL_ULINE;
2250             else if (a == ATR_INVERSE)
2251                 disp_attrib |= HL_INVERSE;
2252             else if (a == ATR_BOLD)
2253                 disp_attrib |= HL_BOLD;
2254             else if (a == ATR_NONE)
2255                 disp_attrib = HL_NONE;
2256             else {
2257                 int c = match_str2clr(subfields[i]);
2258 
2259                 if (c >= CLR_MAX || coloridx != -1)
2260                     return FALSE;
2261                 coloridx = c;
2262             }
2263         }
2264         if (coloridx == -1)
2265             coloridx = NO_COLOR;
2266 
2267         /* Assign the values */
2268         hilite.coloridx = coloridx | (disp_attrib << 8);
2269 
2270         if (always)
2271             hilite.behavior = BL_TH_ALWAYS_HILITE;
2272         else if (percent)
2273             hilite.behavior = BL_TH_VAL_PERCENTAGE;
2274         else if (changed)
2275             hilite.behavior = BL_TH_UPDOWN;
2276         else if (numeric)
2277             hilite.behavior = BL_TH_VAL_ABSOLUTE;
2278         else if (txtval)
2279             hilite.behavior = BL_TH_TEXTMATCH;
2280         else if (hilite.value.a_void)
2281             hilite.behavior = BL_TH_VAL_ABSOLUTE;
2282        else
2283             hilite.behavior = BL_TH_NONE;
2284 
2285         hilite.anytype = dt;
2286 
2287         if (hilite.behavior == BL_TH_TEXTMATCH && txt) {
2288             (void) strncpy(hilite.textmatch, txt, sizeof hilite.textmatch);
2289             hilite.textmatch[sizeof hilite.textmatch - 1] = '\0';
2290             (void) trimspaces(hilite.textmatch);
2291         }
2292 
2293         status_hilite_add_threshold(fld, &hilite);
2294 
2295         successes++;
2296         sidx++;
2297     }
2298 
2299     return TRUE;
2300 }
2301 #endif /* STATUS_HILITES */
2302 
2303 const struct condmap valid_conditions[] = {
2304     { "stone",    BL_MASK_STONE },
2305     { "slime",    BL_MASK_SLIME },
2306     { "strngl",   BL_MASK_STRNGL },
2307     { "foodPois", BL_MASK_FOODPOIS },
2308     { "termIll",  BL_MASK_TERMILL },
2309     { "blind",    BL_MASK_BLIND },
2310     { "deaf",     BL_MASK_DEAF },
2311     { "stun",     BL_MASK_STUN },
2312     { "conf",     BL_MASK_CONF },
2313     { "hallu",    BL_MASK_HALLU },
2314     { "lev",      BL_MASK_LEV },
2315     { "fly",      BL_MASK_FLY },
2316     { "ride",     BL_MASK_RIDE },
2317 };
2318 
2319 #ifdef STATUS_HILITES
2320 
2321 const struct condmap condition_aliases[] = {
2322     { "strangled",      BL_MASK_STRNGL },
2323     { "all",            BL_MASK_STONE | BL_MASK_SLIME | BL_MASK_STRNGL
2324                         | BL_MASK_FOODPOIS | BL_MASK_TERMILL
2325                         | BL_MASK_BLIND | BL_MASK_DEAF | BL_MASK_STUN
2326                         | BL_MASK_CONF | BL_MASK_HALLU
2327                         | BL_MASK_LEV | BL_MASK_FLY | BL_MASK_RIDE },
2328     { "major_troubles", BL_MASK_STONE | BL_MASK_SLIME | BL_MASK_STRNGL
2329                         | BL_MASK_FOODPOIS | BL_MASK_TERMILL },
2330     { "minor_troubles", BL_MASK_BLIND | BL_MASK_DEAF | BL_MASK_STUN
2331                         | BL_MASK_CONF | BL_MASK_HALLU },
2332     { "movement",       BL_MASK_LEV | BL_MASK_FLY | BL_MASK_RIDE }
2333 };
2334 
2335 unsigned long
query_conditions()2336 query_conditions()
2337 {
2338     int i,res;
2339     unsigned long ret = 0UL;
2340     winid tmpwin;
2341     anything any;
2342     menu_item *picks = (menu_item *) 0;
2343 
2344     tmpwin = create_nhwindow(NHW_MENU);
2345     start_menu(tmpwin);
2346 
2347     for (i = 0; i < SIZE(valid_conditions); i++) {
2348         any = zeroany;
2349         any.a_ulong = valid_conditions[i].bitmask;
2350         add_menu(tmpwin, NO_GLYPH, &any, 0, 0, ATR_NONE,
2351                  valid_conditions[i].id, MENU_UNSELECTED);
2352     }
2353 
2354     end_menu(tmpwin, "Choose status conditions");
2355 
2356     res = select_menu(tmpwin, PICK_ANY, &picks);
2357     destroy_nhwindow(tmpwin);
2358     if (res > 0) {
2359         for (i = 0; i < res; i++)
2360             ret |= picks[i].item.a_ulong;
2361         free((genericptr_t) picks);
2362     }
2363     return ret;
2364 }
2365 
2366 STATIC_OVL char *
conditionbitmask2str(ul)2367 conditionbitmask2str(ul)
2368 unsigned long ul;
2369 {
2370     static char buf[BUFSZ];
2371     int i;
2372     boolean first = TRUE;
2373     const char *alias = (char *) 0;
2374 
2375 
2376     buf[0] = '\0';
2377     if (!ul)
2378         return buf;
2379 
2380     for (i = 1; i < SIZE(condition_aliases); i++)
2381         if (condition_aliases[i].bitmask == ul)
2382             alias = condition_aliases[i].id;
2383 
2384     for (i = 0; i < SIZE(valid_conditions); i++)
2385         if ((valid_conditions[i].bitmask & ul) != 0UL) {
2386             Sprintf(eos(buf), "%s%s", (first) ? "" : "+",
2387                     valid_conditions[i].id);
2388             first = FALSE;
2389         }
2390 
2391     if (!first && alias)
2392         Sprintf(buf, "%s", alias);
2393 
2394     return buf;
2395 }
2396 
2397 STATIC_OVL unsigned long
match_str2conditionbitmask(str)2398 match_str2conditionbitmask(str)
2399 const char *str;
2400 {
2401     int i, nmatches = 0;
2402     unsigned long mask = 0UL;
2403 
2404     if (str && *str) {
2405         /* check matches to canonical names */
2406         for (i = 0; i < SIZE(valid_conditions); i++)
2407             if (fuzzymatch(valid_conditions[i].id, str, " -_", TRUE)) {
2408                 mask |= valid_conditions[i].bitmask;
2409                 nmatches++;
2410             }
2411 
2412         if (!nmatches) {
2413             /* check aliases */
2414             for (i = 0; i < SIZE(condition_aliases); i++)
2415                 if (fuzzymatch(condition_aliases[i].id, str, " -_", TRUE)) {
2416                     mask |= condition_aliases[i].bitmask;
2417                     nmatches++;
2418                 }
2419         }
2420 
2421         if (!nmatches) {
2422             /* check partial matches to aliases */
2423             int len = (int) strlen(str);
2424 
2425             for (i = 0; i < SIZE(condition_aliases); i++)
2426                 if (!strncmpi(str, condition_aliases[i].id, len)) {
2427                     mask |= condition_aliases[i].bitmask;
2428                     nmatches++;
2429                 }
2430         }
2431     }
2432 
2433     return mask;
2434 }
2435 
2436 STATIC_OVL unsigned long
str2conditionbitmask(str)2437 str2conditionbitmask(str)
2438 char *str;
2439 {
2440     unsigned long conditions_bitmask = 0UL;
2441     char **subfields;
2442     int i, sf;
2443 
2444     sf = splitsubfields(str, &subfields, SIZE(valid_conditions));
2445 
2446     if (sf < 1)
2447         return 0UL;
2448 
2449     for (i = 0; i < sf; ++i) {
2450         unsigned long bm = match_str2conditionbitmask(subfields[i]);
2451 
2452         if (!bm) {
2453             config_error_add("Unknown condition '%s'", subfields[i]);
2454             return 0UL;
2455         }
2456         conditions_bitmask |= bm;
2457     }
2458     return conditions_bitmask;
2459 }
2460 
2461 STATIC_OVL boolean
2462 parse_condition(s, sidx)
2463 char (*s)[QBUFSZ];
2464 int sidx;
2465 {
2466     int i;
2467     int coloridx = NO_COLOR;
2468     char *tmp, *how;
2469     unsigned long conditions_bitmask = 0UL;
2470     boolean success = FALSE;
2471 
2472     if (!s)
2473         return FALSE;
2474 
2475     /*3.6.1:
2476       OPTION=hilite_status: condition/stone+slime+foodPois/red&inverse */
2477 
2478     /*
2479      * TODO?
2480      *  It would be simpler to treat each condition (also hunger state
2481      *  and encumbrance level) as if it were a separate field.  That
2482      *  way they could have either or both 'changed' temporary rule and
2483      *  'always' persistent rule and wouldn't need convoluted access to
2484      *  the intended color and attributes.
2485      */
2486 
2487     sidx++;
2488     while(s[sidx]) {
2489         int sf = 0;     /* subfield count */
2490         char buf[BUFSZ], **subfields;
2491 
2492         tmp = s[sidx];
2493         if (!*tmp) {
2494             if (!success)
2495                 config_error_add("Missing condition(s)");
2496             return success;
2497         }
2498 
2499         Strcpy(buf, tmp);
2500         conditions_bitmask = str2conditionbitmask(buf);
2501 
2502         if (!conditions_bitmask)
2503             return FALSE;
2504 
2505         /*
2506          * We have the conditions_bitmask with bits set for
2507          * each ailment we want in a particular color and/or
2508          * attribute, but we need to assign it to an array of
2509          * bitmasks indexed by the color chosen
2510          *        (0 to (CLR_MAX - 1))
2511          * and/or attributes chosen
2512          *        (HL_ATTCLR_DIM to (BL_ATTCLR_MAX - 1))
2513          * We still have to parse the colors and attributes out.
2514          */
2515 
2516         /* actions */
2517         sidx++;
2518         how = s[sidx];
2519         if (!how || !*how) {
2520             config_error_add("Missing color+attribute");
2521             return FALSE;
2522         }
2523 
2524         Strcpy(buf, how);
2525         sf = splitsubfields(buf, &subfields, 0);
2526 
2527         /*
2528          * conditions_bitmask now has bits set representing
2529          * the conditions that player wants represented, but
2530          * now we parse out *how* they will be represented.
2531          *
2532          * Only 1 colour is allowed, but potentially multiple
2533          * attributes are allowed.
2534          *
2535          * We have the following additional array offsets to
2536          * use for storing the attributes beyond the end of
2537          * the color indexes, all of which are less than CLR_MAX.
2538          * HL_ATTCLR_DIM        = CLR_MAX
2539          * HL_ATTCLR_BLINK      = CLR_MAX + 1
2540          * HL_ATTCLR_ULINE      = CLR_MAX + 2
2541          * HL_ATTCLR_INVERSE    = CLR_MAX + 3
2542          * HL_ATTCLR_BOLD       = CLR_MAX + 4
2543          * HL_ATTCLR_MAX        = CLR_MAX + 5 (this is past array boundary)
2544          *
2545          */
2546 
2547         for (i = 0; i < sf; ++i) {
2548             int a = match_str2attr(subfields[i], FALSE);
2549 
2550             if (a == ATR_DIM)
2551                 cond_hilites[HL_ATTCLR_DIM] |= conditions_bitmask;
2552             else if (a == ATR_BLINK)
2553                 cond_hilites[HL_ATTCLR_BLINK] |= conditions_bitmask;
2554             else if (a == ATR_ULINE)
2555                 cond_hilites[HL_ATTCLR_ULINE] |= conditions_bitmask;
2556             else if (a == ATR_INVERSE)
2557                 cond_hilites[HL_ATTCLR_INVERSE] |= conditions_bitmask;
2558             else if (a == ATR_BOLD)
2559                 cond_hilites[HL_ATTCLR_BOLD] |= conditions_bitmask;
2560             else if (a == ATR_NONE) {
2561                 cond_hilites[HL_ATTCLR_DIM] &= ~conditions_bitmask;
2562                 cond_hilites[HL_ATTCLR_BLINK] &= ~conditions_bitmask;
2563                 cond_hilites[HL_ATTCLR_ULINE] &= ~conditions_bitmask;
2564                 cond_hilites[HL_ATTCLR_INVERSE] &= ~conditions_bitmask;
2565                 cond_hilites[HL_ATTCLR_BOLD] &= ~conditions_bitmask;
2566             } else {
2567                 int k = match_str2clr(subfields[i]);
2568 
2569                 if (k >= CLR_MAX)
2570                     return FALSE;
2571                 coloridx = k;
2572             }
2573         }
2574         /* set the bits in the appropriate member of the
2575            condition array according to color chosen as index */
2576 
2577         cond_hilites[coloridx] |= conditions_bitmask;
2578         success = TRUE;
2579         sidx++;
2580     }
2581     return TRUE;
2582 }
2583 
2584 void
clear_status_hilites()2585 clear_status_hilites()
2586 {
2587     int i;
2588 
2589     for (i = 0; i < MAXBLSTATS; ++i) {
2590         struct hilite_s *temp, *next;
2591 
2592         for (temp = blstats[0][i].thresholds; temp; temp = next) {
2593             next = temp->next;
2594             free(temp);
2595         }
2596         blstats[0][i].thresholds = blstats[1][i].thresholds = 0;
2597         /* pointer into thresholds list, now stale */
2598         blstats[0][i].hilite_rule = blstats[1][i].hilite_rule = 0;
2599     }
2600 }
2601 
2602 STATIC_OVL char *
hlattr2attrname(attrib,buf,bufsz)2603 hlattr2attrname(attrib, buf, bufsz)
2604 int attrib, bufsz;
2605 char *buf;
2606 {
2607     if (attrib && buf) {
2608         char attbuf[BUFSZ];
2609         int k, first = 0;
2610 
2611         attbuf[0] = '\0';
2612         if (attrib == HL_NONE) {
2613             Strcpy(buf, "normal");
2614             return buf;
2615         }
2616 
2617         if (attrib & HL_BOLD)
2618             Strcat(attbuf, first++ ? "+bold" : "bold");
2619         if (attrib & HL_INVERSE)
2620             Strcat(attbuf, first++ ? "+inverse" : "inverse");
2621         if (attrib & HL_ULINE)
2622             Strcat(attbuf, first++ ? "+underline" : "underline");
2623         if (attrib & HL_BLINK)
2624             Strcat(attbuf, first++ ? "+blink" : "blink");
2625         if (attrib & HL_DIM)
2626             Strcat(attbuf, first++ ? "+dim" : "dim");
2627 
2628         k = strlen(attbuf);
2629         if (k < (bufsz - 1))
2630             Strcpy(buf, attbuf);
2631         return buf;
2632     }
2633     return (char *) 0;
2634 }
2635 
2636 
2637 struct _status_hilite_line_str {
2638     int id;
2639     int fld;
2640     struct hilite_s *hl;
2641     unsigned long mask;
2642     char str[BUFSZ];
2643     struct _status_hilite_line_str *next;
2644 };
2645 
2646 static struct _status_hilite_line_str *status_hilite_str = 0;
2647 static int status_hilite_str_id = 0;
2648 
2649 STATIC_OVL void
status_hilite_linestr_add(fld,hl,mask,str)2650 status_hilite_linestr_add(fld, hl, mask, str)
2651 int fld;
2652 struct hilite_s *hl;
2653 unsigned long mask;
2654 const char *str;
2655 {
2656     struct _status_hilite_line_str *tmp, *nxt;
2657 
2658     tmp = (struct _status_hilite_line_str *) alloc(sizeof *tmp);
2659     (void) memset(tmp, 0, sizeof *tmp);
2660     tmp->next = (struct _status_hilite_line_str *) 0;
2661 
2662     tmp->id = ++status_hilite_str_id;
2663     tmp->fld = fld;
2664     tmp->hl = hl;
2665     tmp->mask = mask;
2666     if (fld == BL_TITLE)
2667         Strcpy(tmp->str, str);
2668     else
2669         (void) stripchars(tmp->str, " ", str);
2670 
2671     if ((nxt = status_hilite_str) != 0) {
2672         while (nxt->next)
2673             nxt = nxt->next;
2674         nxt->next = tmp;
2675     } else {
2676         status_hilite_str = tmp;
2677     }
2678 }
2679 
2680 STATIC_OVL void
status_hilite_linestr_done()2681 status_hilite_linestr_done()
2682 {
2683     struct _status_hilite_line_str *nxt, *tmp = status_hilite_str;
2684 
2685     while (tmp) {
2686         nxt = tmp->next;
2687         free(tmp);
2688         tmp = nxt;
2689     }
2690     status_hilite_str = (struct _status_hilite_line_str *) 0;
2691     status_hilite_str_id = 0;
2692 }
2693 
2694 STATIC_OVL int
status_hilite_linestr_countfield(fld)2695 status_hilite_linestr_countfield(fld)
2696 int fld;
2697 {
2698     struct _status_hilite_line_str *tmp;
2699     boolean countall = (fld == BL_FLUSH);
2700     int count = 0;
2701 
2702     for (tmp = status_hilite_str; tmp; tmp = tmp->next) {
2703         if (countall || tmp->fld == fld)
2704             count++;
2705     }
2706     return count;
2707 }
2708 
2709 /* used by options handling, doset(options.c) */
2710 int
count_status_hilites(VOID_ARGS)2711 count_status_hilites(VOID_ARGS)
2712 {
2713     int count;
2714 
2715     status_hilite_linestr_gather();
2716     count = status_hilite_linestr_countfield(BL_FLUSH);
2717     status_hilite_linestr_done();
2718     return count;
2719 }
2720 
2721 STATIC_OVL void
status_hilite_linestr_gather_conditions()2722 status_hilite_linestr_gather_conditions()
2723 {
2724     int i;
2725     struct _cond_map {
2726         unsigned long bm;
2727         unsigned long clratr;
2728     } cond_maps[SIZE(valid_conditions)];
2729 
2730     (void) memset(cond_maps, 0,
2731                   SIZE(valid_conditions) * sizeof (struct _cond_map));
2732 
2733     for (i = 0; i < SIZE(valid_conditions); i++) {
2734         int clr = NO_COLOR;
2735         int atr = HL_NONE;
2736         int j;
2737 
2738         for (j = 0; j < CLR_MAX; j++)
2739             if (cond_hilites[j] & valid_conditions[i].bitmask) {
2740                 clr = j;
2741                 break;
2742             }
2743         if (cond_hilites[HL_ATTCLR_DIM] & valid_conditions[i].bitmask)
2744             atr |= HL_DIM;
2745         if (cond_hilites[HL_ATTCLR_BOLD] & valid_conditions[i].bitmask)
2746             atr |= HL_BOLD;
2747         if (cond_hilites[HL_ATTCLR_BLINK] & valid_conditions[i].bitmask)
2748             atr |= HL_BLINK;
2749         if (cond_hilites[HL_ATTCLR_ULINE] & valid_conditions[i].bitmask)
2750             atr |= HL_ULINE;
2751         if (cond_hilites[HL_ATTCLR_INVERSE] & valid_conditions[i].bitmask)
2752             atr |= HL_INVERSE;
2753         if (atr != HL_NONE)
2754             atr &= ~HL_NONE;
2755 
2756         if (clr != NO_COLOR || atr != HL_NONE) {
2757             unsigned long ca = clr | (atr << 8);
2758             boolean added_condmap = FALSE;
2759 
2760             for (j = 0; j < SIZE(valid_conditions); j++)
2761                 if (cond_maps[j].clratr == ca) {
2762                     cond_maps[j].bm |= valid_conditions[i].bitmask;
2763                     added_condmap = TRUE;
2764                     break;
2765                 }
2766             if (!added_condmap) {
2767                 for (j = 0; j < SIZE(valid_conditions); j++)
2768                     if (!cond_maps[j].bm) {
2769                         cond_maps[j].bm = valid_conditions[i].bitmask;
2770                         cond_maps[j].clratr = ca;
2771                         break;
2772                     }
2773             }
2774         }
2775     }
2776 
2777     for (i = 0; i < SIZE(valid_conditions); i++)
2778         if (cond_maps[i].bm) {
2779             int clr = NO_COLOR, atr = HL_NONE;
2780 
2781             split_clridx(cond_maps[i].clratr, &clr, &atr);
2782             if (clr != NO_COLOR || atr != HL_NONE) {
2783                 char clrbuf[BUFSZ];
2784                 char attrbuf[BUFSZ];
2785                 char condbuf[BUFSZ];
2786                 char *tmpattr;
2787 
2788                 (void) strNsubst(strcpy(clrbuf, clr2colorname(clr)),
2789                                  " ", "-", 0);
2790                 tmpattr = hlattr2attrname(atr, attrbuf, BUFSZ);
2791                 if (tmpattr)
2792                     Sprintf(eos(clrbuf), "&%s", tmpattr);
2793                 Sprintf(condbuf, "condition/%s/%s",
2794                         conditionbitmask2str(cond_maps[i].bm), clrbuf);
2795                 status_hilite_linestr_add(BL_CONDITION, 0,
2796                                           cond_maps[i].bm, condbuf);
2797             }
2798         }
2799 }
2800 
2801 STATIC_OVL void
status_hilite_linestr_gather()2802 status_hilite_linestr_gather()
2803 {
2804     int i;
2805     struct hilite_s *hl;
2806 
2807     status_hilite_linestr_done();
2808 
2809     for (i = 0; i < MAXBLSTATS; i++) {
2810         hl = blstats[0][i].thresholds;
2811         while (hl) {
2812             status_hilite_linestr_add(i, hl, 0UL, status_hilite2str(hl));
2813             hl = hl->next;
2814         }
2815     }
2816 
2817     status_hilite_linestr_gather_conditions();
2818 }
2819 
2820 
2821 STATIC_OVL char *
status_hilite2str(hl)2822 status_hilite2str(hl)
2823 struct hilite_s *hl;
2824 {
2825     static char buf[BUFSZ];
2826     int clr = 0, attr = 0;
2827     char behavebuf[BUFSZ];
2828     char clrbuf[BUFSZ];
2829     char attrbuf[BUFSZ];
2830     char *tmpattr;
2831     const char *op;
2832 
2833     if (!hl)
2834         return (char *) 0;
2835 
2836     behavebuf[0] = '\0';
2837     clrbuf[0] = '\0';
2838     op = (hl->rel == LT_VALUE) ? "<"
2839            : (hl->rel == LE_VALUE) ? "<="
2840              : (hl->rel == GT_VALUE) ? ">"
2841                : (hl->rel == GE_VALUE) ? ">="
2842                  : (hl->rel == EQ_VALUE) ? "="
2843                    : 0;
2844 
2845     switch (hl->behavior) {
2846     case BL_TH_VAL_PERCENTAGE:
2847         if (op)
2848             Sprintf(behavebuf, "%s%d%%", op, hl->value.a_int);
2849         else
2850             impossible("hl->behavior=percentage, rel error");
2851         break;
2852     case BL_TH_UPDOWN:
2853         if (hl->rel == LT_VALUE)
2854             Sprintf(behavebuf, "down");
2855         else if (hl->rel == GT_VALUE)
2856             Sprintf(behavebuf, "up");
2857         else if (hl->rel == EQ_VALUE)
2858             Sprintf(behavebuf, "changed");
2859         else
2860             impossible("hl->behavior=updown, rel error");
2861         break;
2862     case BL_TH_VAL_ABSOLUTE:
2863         if (op)
2864             Sprintf(behavebuf, "%s%d", op, hl->value.a_int);
2865         else
2866             impossible("hl->behavior=absolute, rel error");
2867         break;
2868     case BL_TH_TEXTMATCH:
2869         if (hl->rel == TXT_VALUE && hl->textmatch[0])
2870             Sprintf(behavebuf, "%s", hl->textmatch);
2871         else
2872             impossible("hl->behavior=textmatch, rel or textmatch error");
2873         break;
2874     case BL_TH_CONDITION:
2875         if (hl->rel == EQ_VALUE)
2876             Sprintf(behavebuf, "%s", conditionbitmask2str(hl->value.a_ulong));
2877         else
2878             impossible("hl->behavior=condition, rel error");
2879         break;
2880     case BL_TH_ALWAYS_HILITE:
2881         Sprintf(behavebuf, "always");
2882         break;
2883     case BL_TH_NONE:
2884         break;
2885     default:
2886         break;
2887     }
2888 
2889     split_clridx(hl->coloridx, &clr, &attr);
2890     (void) strNsubst(strcpy(clrbuf, clr2colorname(clr)), " ", "-", 0);
2891     if (attr != HL_UNDEF) {
2892         if ((tmpattr = hlattr2attrname(attr, attrbuf, BUFSZ)) != 0)
2893             Sprintf(eos(clrbuf), "&%s", tmpattr);
2894     }
2895     Sprintf(buf, "%s/%s/%s", initblstats[hl->fld].fldname, behavebuf, clrbuf);
2896 
2897     return buf;
2898 }
2899 
2900 STATIC_OVL int
status_hilite_menu_choose_field()2901 status_hilite_menu_choose_field()
2902 {
2903     winid tmpwin;
2904     int i, res, fld = BL_FLUSH;
2905     anything any;
2906     menu_item *picks = (menu_item *) 0;
2907 
2908     tmpwin = create_nhwindow(NHW_MENU);
2909     start_menu(tmpwin);
2910 
2911     for (i = 0; i < MAXBLSTATS; i++) {
2912 #ifndef SCORE_ON_BOTL
2913         if (initblstats[i].fld == BL_SCORE
2914             && !blstats[0][BL_SCORE].thresholds)
2915             continue;
2916 #endif
2917         any = zeroany;
2918         any.a_int = (i + 1);
2919         add_menu(tmpwin, NO_GLYPH, &any, 0, 0, ATR_NONE,
2920                  initblstats[i].fldname, MENU_UNSELECTED);
2921     }
2922 
2923     end_menu(tmpwin, "Select a hilite field:");
2924 
2925     res = select_menu(tmpwin, PICK_ONE, &picks);
2926     destroy_nhwindow(tmpwin);
2927     if (res > 0) {
2928         fld = picks->item.a_int - 1;
2929         free((genericptr_t) picks);
2930     }
2931     return fld;
2932 }
2933 
2934 STATIC_OVL int
status_hilite_menu_choose_behavior(fld)2935 status_hilite_menu_choose_behavior(fld)
2936 int fld;
2937 {
2938     winid tmpwin;
2939     int res = 0, beh = BL_TH_NONE-1;
2940     anything any;
2941     menu_item *picks = (menu_item *) 0;
2942     char buf[BUFSZ];
2943     int at;
2944     int onlybeh = BL_TH_NONE, nopts = 0;
2945 
2946     if (fld < 0 || fld >= MAXBLSTATS)
2947         return BL_TH_NONE;
2948 
2949     at = initblstats[fld].anytype;
2950 
2951     tmpwin = create_nhwindow(NHW_MENU);
2952     start_menu(tmpwin);
2953 
2954     if (fld != BL_CONDITION) {
2955         any = zeroany;
2956         any.a_int = onlybeh = BL_TH_ALWAYS_HILITE;
2957         Sprintf(buf, "Always highlight %s", initblstats[fld].fldname);
2958         add_menu(tmpwin, NO_GLYPH, &any, 'a', 0, ATR_NONE,
2959                  buf, MENU_UNSELECTED);
2960         nopts++;
2961     }
2962 
2963     if (fld == BL_CONDITION) {
2964         any = zeroany;
2965         any.a_int = onlybeh = BL_TH_CONDITION;
2966         add_menu(tmpwin, NO_GLYPH, &any, 'b', 0, ATR_NONE,
2967                  "Bitmask of conditions", MENU_UNSELECTED);
2968         nopts++;
2969     }
2970 
2971     if (fld != BL_CONDITION) {
2972         any = zeroany;
2973         any.a_int = onlybeh = BL_TH_UPDOWN;
2974         Sprintf(buf, "%s value changes", initblstats[fld].fldname);
2975         add_menu(tmpwin, NO_GLYPH, &any, 'c', 0, ATR_NONE,
2976                  buf, MENU_UNSELECTED);
2977         nopts++;
2978     }
2979 
2980     if (fld != BL_CAP && fld != BL_HUNGER
2981         && (at == ANY_INT || at == ANY_LONG)) {
2982         any = zeroany;
2983         any.a_int = onlybeh = BL_TH_VAL_ABSOLUTE;
2984         add_menu(tmpwin, NO_GLYPH, &any, 'n', 0, ATR_NONE,
2985                  "Number threshold", MENU_UNSELECTED);
2986         nopts++;
2987     }
2988 
2989     if (initblstats[fld].idxmax >= 0) {
2990         any = zeroany;
2991         any.a_int = onlybeh = BL_TH_VAL_PERCENTAGE;
2992         add_menu(tmpwin, NO_GLYPH, &any, 'p', 0, ATR_NONE,
2993                  "Percentage threshold", MENU_UNSELECTED);
2994         nopts++;
2995     }
2996 
2997     if (initblstats[fld].anytype == ANY_STR
2998         || fld == BL_CAP || fld == BL_HUNGER) {
2999         any = zeroany;
3000         any.a_int = onlybeh = BL_TH_TEXTMATCH;
3001         Sprintf(buf, "%s text match", initblstats[fld].fldname);
3002         add_menu(tmpwin, NO_GLYPH, &any, 't', 0, ATR_NONE,
3003                  buf, MENU_UNSELECTED);
3004         nopts++;
3005     }
3006 
3007     Sprintf(buf, "Select %s field hilite behavior:", initblstats[fld].fldname);
3008     end_menu(tmpwin, buf);
3009 
3010     if (nopts > 1) {
3011         res = select_menu(tmpwin, PICK_ONE, &picks);
3012         if (res == 0) /* none chosen*/
3013             beh = BL_TH_NONE;
3014         else if (res == -1) /* menu cancelled */
3015             beh = (BL_TH_NONE - 1);
3016     } else if (onlybeh != BL_TH_NONE)
3017         beh = onlybeh;
3018     destroy_nhwindow(tmpwin);
3019     if (res > 0) {
3020         beh = picks->item.a_int;
3021         free((genericptr_t) picks);
3022     }
3023     return beh;
3024 }
3025 
3026 STATIC_OVL int
status_hilite_menu_choose_updownboth(fld,str,ltok,gtok)3027 status_hilite_menu_choose_updownboth(fld, str, ltok, gtok)
3028 int fld;
3029 const char *str;
3030 boolean ltok, gtok;
3031 {
3032     int res, ret = NO_LTEQGT;
3033     winid tmpwin;
3034     char buf[BUFSZ];
3035     anything any;
3036     menu_item *picks = (menu_item *) 0;
3037 
3038     tmpwin = create_nhwindow(NHW_MENU);
3039     start_menu(tmpwin);
3040 
3041     if (ltok) {
3042         if (str)
3043             Sprintf(buf, "%s than %s",
3044                     (fld == BL_AC) ? "Better (lower)" : "Less", str);
3045         else
3046             Sprintf(buf, "Value goes down");
3047         any = zeroany;
3048         any.a_int = 10 + LT_VALUE;
3049         add_menu(tmpwin, NO_GLYPH, &any, 0, 0, ATR_NONE,
3050                  buf, MENU_UNSELECTED);
3051 
3052         if (str) {
3053             Sprintf(buf, "%s or %s",
3054                     str, (fld == BL_AC) ? "better (lower)" : "less");
3055             any = zeroany;
3056             any.a_int = 10 + LE_VALUE;
3057             add_menu(tmpwin, NO_GLYPH, &any, 0, 0, ATR_NONE,
3058                      buf, MENU_UNSELECTED);
3059         }
3060     }
3061 
3062     if (str)
3063         Sprintf(buf, "Exactly %s", str);
3064     else
3065         Sprintf(buf, "Value changes");
3066     any = zeroany;
3067     any.a_int = 10 + EQ_VALUE;
3068     add_menu(tmpwin, NO_GLYPH, &any, 0, 0, ATR_NONE,
3069              buf, MENU_UNSELECTED);
3070 
3071     if (gtok) {
3072         if (str) {
3073             Sprintf(buf, "%s or %s",
3074                     str, (fld == BL_AC) ? "worse (higher)" : "more");
3075             any = zeroany;
3076             any.a_int = 10 + GE_VALUE;
3077             add_menu(tmpwin, NO_GLYPH, &any, 0, 0, ATR_NONE,
3078                      buf, MENU_UNSELECTED);
3079         }
3080 
3081         if (str)
3082             Sprintf(buf, "%s than %s",
3083                     (fld == BL_AC) ? "Worse (higher)" : "More", str);
3084         else
3085             Sprintf(buf, "Value goes up");
3086         any = zeroany;
3087         any.a_int = 10 + GT_VALUE;
3088         add_menu(tmpwin, NO_GLYPH, &any, 0, 0, ATR_NONE,
3089              buf, MENU_UNSELECTED);
3090     }
3091     Sprintf(buf, "Select field %s value:", initblstats[fld].fldname);
3092     end_menu(tmpwin, buf);
3093 
3094     res = select_menu(tmpwin, PICK_ONE, &picks);
3095     destroy_nhwindow(tmpwin);
3096     if (res > 0) {
3097         ret = picks->item.a_int - 10;
3098         free((genericptr_t) picks);
3099     }
3100 
3101     return ret;
3102 }
3103 
3104 STATIC_OVL boolean
status_hilite_menu_add(origfld)3105 status_hilite_menu_add(origfld)
3106 int origfld;
3107 {
3108     int fld;
3109     int behavior;
3110     int lt_gt_eq;
3111     int clr = NO_COLOR, atr = HL_UNDEF;
3112     struct hilite_s hilite;
3113     unsigned long cond = 0UL;
3114     char colorqry[BUFSZ];
3115     char attrqry[BUFSZ];
3116 
3117 choose_field:
3118     fld = origfld;
3119     if (fld == BL_FLUSH) {
3120         fld = status_hilite_menu_choose_field();
3121         /* isn't this redundant given what follows? */
3122         if (fld == BL_FLUSH)
3123             return FALSE;
3124     }
3125 
3126     if (fld == BL_FLUSH)
3127         return FALSE;
3128 
3129     colorqry[0] = '\0';
3130     attrqry[0] = '\0';
3131 
3132     memset((genericptr_t) &hilite, 0, sizeof (struct hilite_s));
3133     hilite.next = (struct hilite_s *) 0;
3134     hilite.set = FALSE; /* mark it "unset" */
3135     hilite.fld = fld;
3136 
3137 choose_behavior:
3138     behavior = status_hilite_menu_choose_behavior(fld);
3139 
3140     if (behavior == (BL_TH_NONE - 1)) {
3141         return FALSE;
3142     } else if (behavior == BL_TH_NONE) {
3143         if (origfld == BL_FLUSH)
3144             goto choose_field;
3145         return FALSE;
3146     }
3147 
3148     hilite.behavior = behavior;
3149 
3150 choose_value:
3151     if (behavior == BL_TH_VAL_PERCENTAGE
3152         || behavior == BL_TH_VAL_ABSOLUTE) {
3153         char inbuf[BUFSZ], buf[BUFSZ];
3154         anything aval;
3155         int val, dt;
3156         boolean gotnum = FALSE, percent = (behavior == BL_TH_VAL_PERCENTAGE);
3157         char *inp, *numstart;
3158         const char *op;
3159 
3160         lt_gt_eq = NO_LTEQGT; /* not set up yet */
3161         inbuf[0] = '\0';
3162         Sprintf(buf, "Enter %svalue for %s threshold:",
3163                 percent ? "percentage " : "",
3164                 initblstats[fld].fldname);
3165         getlin(buf, inbuf);
3166         if (inbuf[0] == '\0' || inbuf[0] == '\033')
3167             goto choose_behavior;
3168 
3169         inp = numstart = trimspaces(inbuf);
3170         if (!*inp)
3171             goto choose_behavior;
3172 
3173         /* allow user to enter "<50%" or ">50" or just "50"
3174            or <=50% or >=50 or =50 */
3175         if (*inp == '>' || *inp == '<' || *inp == '=') {
3176             lt_gt_eq = (*inp == '>') ? ((inp[1] == '=') ? GE_VALUE : GT_VALUE)
3177                      : (*inp == '<') ? ((inp[1] == '=') ? LE_VALUE : LT_VALUE)
3178                        : EQ_VALUE;
3179             *inp++ = ' ';
3180             numstart++;
3181             if (lt_gt_eq == GE_VALUE || lt_gt_eq == LE_VALUE) {
3182                 *inp++ = ' ';
3183                 numstart++;
3184             }
3185         }
3186         if (*inp == '-') {
3187             inp++;
3188         } else if (*inp == '+') {
3189             *inp++ = ' ';
3190             numstart++;
3191         }
3192         while (digit(*inp)) {
3193             inp++;
3194             gotnum = TRUE;
3195         }
3196         if (*inp == '%') {
3197             if (!percent) {
3198                 pline("Not expecting a percentage.");
3199                 goto choose_behavior;
3200             }
3201             *inp = '\0'; /* strip '%' [this accepts trailing junk!] */
3202         } else if (*inp) {
3203             /* some random characters */
3204             pline("\"%s\" is not a recognized number.", inp);
3205             goto choose_value;
3206         }
3207         if (!gotnum) {
3208             pline("Is that an invisible number?");
3209             goto choose_value;
3210         }
3211         op = (lt_gt_eq == LT_VALUE) ? "<"
3212                : (lt_gt_eq == LE_VALUE) ? "<="
3213                  : (lt_gt_eq == GT_VALUE) ? ">"
3214                    : (lt_gt_eq == GE_VALUE) ? ">="
3215                      : (lt_gt_eq == EQ_VALUE) ? "="
3216                        : ""; /* didn't specify lt_gt_eq with number */
3217 
3218         aval = zeroany;
3219         dt = percent ? ANY_INT : initblstats[fld].anytype;
3220         (void) s_to_anything(&aval, numstart, dt);
3221 
3222         if (percent) {
3223             val = aval.a_int;
3224             if (initblstats[fld].idxmax == -1) {
3225                 pline("Field '%s' does not support percentage values.",
3226                       initblstats[fld].fldname);
3227                 behavior = BL_TH_VAL_ABSOLUTE;
3228                 goto choose_value;
3229             }
3230             /* if player only specified a number then lt_gt_eq isn't set
3231                up yet and the >-1 and <101 exceptions can't be honored;
3232                deliberate use of those should be uncommon enough for
3233                that to be palatable; for 0 and 100, choose_updown_both()
3234                will prevent useless operations */
3235             if ((val < 0 && (val != -1 || lt_gt_eq != GT_VALUE))
3236                 || (val == 0 && lt_gt_eq == LT_VALUE)
3237                 || (val == 100 && lt_gt_eq == GT_VALUE)
3238                 || (val > 100 && (val != 101 || lt_gt_eq != LT_VALUE))) {
3239                 pline("'%s%d%%' is not a valid percent value.", op, val);
3240                 goto choose_value;
3241             }
3242             /* restore suffix for use in color and attribute prompts */
3243             if (!index(numstart, '%'))
3244                 Strcat(numstart, "%");
3245 
3246         /* reject negative values except for AC and >-1; reject 0 for < */
3247         } else if (dt == ANY_INT
3248                    && (aval.a_int < ((fld == BL_AC) ? -128
3249                                      : (lt_gt_eq == GT_VALUE) ? -1
3250                                        : (lt_gt_eq == LT_VALUE) ? 1 : 0))) {
3251             pline("%s'%s%d'%s", threshold_value,
3252                   op, aval.a_int, is_out_of_range);
3253             goto choose_value;
3254         } else if (dt == ANY_LONG
3255                    && (aval.a_long < ((lt_gt_eq == GT_VALUE) ? -1L
3256                                       : (lt_gt_eq == LT_VALUE) ? 1L : 0L))) {
3257             pline("%s'%s%ld'%s", threshold_value,
3258                   op, aval.a_long, is_out_of_range);
3259             goto choose_value;
3260         }
3261 
3262         if (lt_gt_eq == NO_LTEQGT) {
3263             boolean ltok = ((dt == ANY_INT)
3264                             ? (aval.a_int > 0 || fld == BL_AC)
3265                             : (aval.a_long > 0L)),
3266                     gtok = (!percent || aval.a_long < 100);
3267 
3268             lt_gt_eq = status_hilite_menu_choose_updownboth(fld, inbuf,
3269                                                             ltok, gtok);
3270             if (lt_gt_eq == NO_LTEQGT)
3271                 goto choose_value;
3272         }
3273 
3274         Sprintf(colorqry, "Choose a color for when %s is %s%s%s:",
3275                 initblstats[fld].fldname,
3276                 (lt_gt_eq == LT_VALUE) ? "less than "
3277                   : (lt_gt_eq == GT_VALUE) ? "more than "
3278                     : "",
3279                 numstart,
3280                 (lt_gt_eq == LE_VALUE) ? " or less"
3281                   : (lt_gt_eq == GE_VALUE) ? " or more"
3282                     : "");
3283         Sprintf(attrqry, "Choose attribute for when %s is %s%s%s:",
3284                 initblstats[fld].fldname,
3285                 (lt_gt_eq == LT_VALUE) ? "less than "
3286                   : (lt_gt_eq == GT_VALUE) ? "more than "
3287                     : "",
3288                 numstart,
3289                 (lt_gt_eq == LE_VALUE) ? " or less"
3290                   : (lt_gt_eq == GE_VALUE) ? " or more"
3291                     : "");
3292 
3293         hilite.rel = lt_gt_eq;
3294         hilite.value = aval;
3295     } else if (behavior == BL_TH_UPDOWN) {
3296         if (initblstats[fld].anytype != ANY_STR) {
3297             boolean ltok = (fld != BL_TIME), gtok = TRUE;
3298 
3299             lt_gt_eq = status_hilite_menu_choose_updownboth(fld, (char *)0,
3300                                                             ltok, gtok);
3301             if (lt_gt_eq == NO_LTEQGT)
3302                 goto choose_behavior;
3303         } else { /* ANY_STR */
3304             /* player picked '<field> value changes' in outer menu;
3305                ordered string comparison is supported but LT/GT for the
3306                string status fields (title, dungeon level, alignment)
3307                is pointless; rather than calling ..._choose_updownboth()
3308                with ltok==False plus gtok=False and having a menu with a
3309                single choice, skip it altogether and just use 'changed' */
3310             lt_gt_eq = EQ_VALUE;
3311         }
3312         Sprintf(colorqry, "Choose a color for when %s %s:",
3313                 initblstats[fld].fldname,
3314                 (lt_gt_eq == EQ_VALUE) ? "changes"
3315                   : (lt_gt_eq == LT_VALUE) ? "decreases"
3316                     : "increases");
3317         Sprintf(attrqry, "Choose attribute for when %s %s:",
3318                 initblstats[fld].fldname,
3319                 (lt_gt_eq == EQ_VALUE) ? "changes"
3320                   : (lt_gt_eq == LT_VALUE) ? "decreases"
3321                     : "increases");
3322         hilite.rel = lt_gt_eq;
3323     } else if (behavior == BL_TH_CONDITION) {
3324         cond = query_conditions();
3325         if (!cond) {
3326             if (origfld == BL_FLUSH)
3327                 goto choose_field;
3328             return FALSE;
3329         }
3330         Sprintf(colorqry, "Choose a color for conditions %s:",
3331                 conditionbitmask2str(cond));
3332         Sprintf(attrqry, "Choose attribute for conditions %s:",
3333                 conditionbitmask2str(cond));
3334     } else if (behavior == BL_TH_TEXTMATCH) {
3335         char qry_buf[BUFSZ];
3336 
3337         Sprintf(qry_buf, "%s %s text value to match:",
3338                 (fld == BL_CAP
3339                  || fld == BL_ALIGN
3340                  || fld == BL_HUNGER
3341                  || fld == BL_TITLE) ? "Choose" : "Enter",
3342                 initblstats[fld].fldname);
3343         if (fld == BL_CAP) {
3344             int rv = query_arrayvalue(qry_buf,
3345                                       enc_stat,
3346                                       SLT_ENCUMBER, OVERLOADED + 1);
3347 
3348             if (rv < SLT_ENCUMBER)
3349                 goto choose_behavior;
3350 
3351             hilite.rel = TXT_VALUE;
3352             Strcpy(hilite.textmatch, enc_stat[rv]);
3353         } else if (fld == BL_ALIGN) {
3354             static const char *aligntxt[] = { "chaotic", "neutral", "lawful" };
3355             int rv = query_arrayvalue(qry_buf,
3356                                       aligntxt, 0, 2 + 1);
3357 
3358             if (rv < 0)
3359                 goto choose_behavior;
3360 
3361             hilite.rel = TXT_VALUE;
3362             Strcpy(hilite.textmatch, aligntxt[rv]);
3363         } else if (fld == BL_HUNGER) {
3364             static const char *hutxt[] = { "Satiated", (char *) 0, "Hungry",
3365                                            "Weak", "Fainting", "Fainted",
3366                                            "Starved" };
3367             int rv = query_arrayvalue(qry_buf,
3368                                       hutxt,
3369                                       SATIATED, STARVED + 1);
3370 
3371             if (rv < SATIATED)
3372                 goto choose_behavior;
3373 
3374             hilite.rel = TXT_VALUE;
3375             Strcpy(hilite.textmatch, hutxt[rv]);
3376         } else if (fld == BL_TITLE) {
3377             const char *rolelist[3 * 9 + 1];
3378             char mbuf[MAXVALWIDTH], fbuf[MAXVALWIDTH], obuf[MAXVALWIDTH];
3379             int i, j, rv;
3380 
3381             for (i = j = 0; i < 9; i++) {
3382                 Sprintf(mbuf, "\"%s\"", urole.rank[i].m);
3383                 if (urole.rank[i].f) {
3384                     Sprintf(fbuf, "\"%s\"", urole.rank[i].f);
3385                     Sprintf(obuf, "%s or %s",
3386                             flags.female ? fbuf : mbuf,
3387                             flags.female ? mbuf : fbuf);
3388                 } else {
3389                     fbuf[0] = obuf[0] = '\0';
3390                 }
3391                 if (flags.female) {
3392                     if (*fbuf)
3393                         rolelist[j++] = dupstr(fbuf);
3394                     rolelist[j++] = dupstr(mbuf);
3395                     if (*obuf)
3396                         rolelist[j++] = dupstr(obuf);
3397                 } else {
3398                     rolelist[j++] = dupstr(mbuf);
3399                     if (*fbuf)
3400                         rolelist[j++] = dupstr(fbuf);
3401                     if (*obuf)
3402                         rolelist[j++] = dupstr(obuf);
3403                 }
3404             }
3405             rolelist[j++] = dupstr("\"none of the above (polymorphed)\"");
3406 
3407             rv = query_arrayvalue(qry_buf, rolelist, 0, j);
3408             if (rv >= 0) {
3409                 hilite.rel = TXT_VALUE;
3410                 Strcpy(hilite.textmatch, rolelist[rv]);
3411             }
3412             for (i = 0; i < j; i++)
3413                 free((genericptr_t) rolelist[i]), rolelist[i] = 0;
3414             if (rv < 0)
3415                 goto choose_behavior;
3416         } else {
3417             char inbuf[BUFSZ];
3418 
3419             inbuf[0] = '\0';
3420             getlin(qry_buf, inbuf);
3421             if (inbuf[0] == '\0' || inbuf[0] == '\033')
3422                 goto choose_behavior;
3423 
3424             hilite.rel = TXT_VALUE;
3425             if (strlen(inbuf) < sizeof hilite.textmatch)
3426                 Strcpy(hilite.textmatch, inbuf);
3427             else
3428                 return FALSE;
3429         }
3430         Sprintf(colorqry, "Choose a color for when %s is '%s':",
3431                 initblstats[fld].fldname, hilite.textmatch);
3432         Sprintf(attrqry, "Choose attribute for when %s is '%s':",
3433                 initblstats[fld].fldname, hilite.textmatch);
3434     } else if (behavior == BL_TH_ALWAYS_HILITE) {
3435         Sprintf(colorqry, "Choose a color to always hilite %s:",
3436                 initblstats[fld].fldname);
3437         Sprintf(attrqry, "Choose attribute to always hilite %s:",
3438                 initblstats[fld].fldname);
3439     }
3440 
3441 choose_color:
3442     clr = query_color(colorqry);
3443     if (clr == -1) {
3444         if (behavior != BL_TH_ALWAYS_HILITE)
3445             goto choose_value;
3446         else
3447             goto choose_behavior;
3448     }
3449     atr = query_attr(attrqry);
3450     if (atr == -1)
3451         goto choose_color;
3452 
3453     if (behavior == BL_TH_CONDITION) {
3454         char clrbuf[BUFSZ];
3455         char attrbuf[BUFSZ];
3456         char *tmpattr;
3457 
3458         if (atr & HL_DIM)
3459             cond_hilites[HL_ATTCLR_DIM] |= cond;
3460         if (atr & HL_BLINK)
3461             cond_hilites[HL_ATTCLR_BLINK] |= cond;
3462         if (atr & HL_ULINE)
3463             cond_hilites[HL_ATTCLR_ULINE] |= cond;
3464         if (atr & HL_INVERSE)
3465             cond_hilites[HL_ATTCLR_INVERSE] |= cond;
3466         if (atr & HL_BOLD)
3467             cond_hilites[HL_ATTCLR_BOLD] |= cond;
3468         if (atr == HL_NONE) {
3469             cond_hilites[HL_ATTCLR_DIM] &= ~cond;
3470             cond_hilites[HL_ATTCLR_BLINK] &= ~cond;
3471             cond_hilites[HL_ATTCLR_ULINE] &= ~cond;
3472             cond_hilites[HL_ATTCLR_INVERSE] &= ~cond;
3473             cond_hilites[HL_ATTCLR_BOLD] &= ~cond;
3474         }
3475         cond_hilites[clr] |= cond;
3476         (void) strNsubst(strcpy(clrbuf, clr2colorname(clr)), " ", "-", 0);
3477         tmpattr = hlattr2attrname(atr, attrbuf, BUFSZ);
3478         if (tmpattr)
3479             Sprintf(eos(clrbuf), "&%s", tmpattr);
3480         pline("Added hilite condition/%s/%s",
3481               conditionbitmask2str(cond), clrbuf);
3482     } else {
3483         char *p, *q;
3484 
3485         hilite.coloridx = clr | (atr << 8);
3486         hilite.anytype = initblstats[fld].anytype;
3487 
3488         if (fld == BL_TITLE && (p = strstri(hilite.textmatch, " or ")) != 0) {
3489             /* split menu choice "male-rank or female-rank" into two distinct
3490                but otherwise identical rules, "male-rank" and "female-rank" */
3491             *p = '\0'; /* chop off " or female-rank" */
3492             /* new rule for male-rank */
3493             status_hilite_add_threshold(fld, &hilite);
3494             pline("Added hilite %s", status_hilite2str(&hilite));
3495             /* transfer female-rank to start of hilite.textmatch buffer */
3496             p += sizeof " or " - sizeof "";
3497             q = hilite.textmatch;
3498             while ((*q++ = *p++) != '\0')
3499                 continue;
3500             /* proceed with normal addition of new rule */
3501         }
3502         status_hilite_add_threshold(fld, &hilite);
3503         pline("Added hilite %s", status_hilite2str(&hilite));
3504     }
3505     reset_status_hilites();
3506     return TRUE;
3507 }
3508 
3509 boolean
status_hilite_remove(id)3510 status_hilite_remove(id)
3511 int id;
3512 {
3513     struct _status_hilite_line_str *hlstr = status_hilite_str;
3514 
3515     while (hlstr && hlstr->id != id) {
3516         hlstr = hlstr->next;
3517     }
3518 
3519     if (!hlstr)
3520         return FALSE;
3521 
3522     if (hlstr->fld == BL_CONDITION) {
3523         int i;
3524 
3525         for (i = 0; i < CLR_MAX; i++)
3526             cond_hilites[i] &= ~hlstr->mask;
3527         cond_hilites[HL_ATTCLR_DIM] &= ~hlstr->mask;
3528         cond_hilites[HL_ATTCLR_BOLD] &= ~hlstr->mask;
3529         cond_hilites[HL_ATTCLR_BLINK] &= ~hlstr->mask;
3530         cond_hilites[HL_ATTCLR_ULINE] &= ~hlstr->mask;
3531         cond_hilites[HL_ATTCLR_INVERSE] &= ~hlstr->mask;
3532         return TRUE;
3533     } else {
3534         int fld = hlstr->fld;
3535         struct hilite_s *hl, *hlprev = (struct hilite_s *) 0;
3536 
3537         for (hl = blstats[0][fld].thresholds; hl; hl = hl->next) {
3538             if (hlstr->hl == hl) {
3539                 if (hlprev) {
3540                     hlprev->next = hl->next;
3541                 } else {
3542                     blstats[0][fld].thresholds = hl->next;
3543                     blstats[1][fld].thresholds = blstats[0][fld].thresholds;
3544                 }
3545                 if (blstats[0][fld].hilite_rule == hl) {
3546                     blstats[0][fld].hilite_rule
3547                         = blstats[1][fld].hilite_rule = (struct hilite_s *) 0;
3548                     blstats[0][fld].time = blstats[1][fld].time = 0L;
3549                 }
3550                 free((genericptr_t) hl);
3551                 return TRUE;
3552             }
3553             hlprev = hl;
3554         }
3555     }
3556     return FALSE;
3557 }
3558 
3559 boolean
status_hilite_menu_fld(fld)3560 status_hilite_menu_fld(fld)
3561 int fld;
3562 {
3563     winid tmpwin;
3564     int i, res;
3565     menu_item *picks = (menu_item *) 0;
3566     anything any;
3567     int count = status_hilite_linestr_countfield(fld);
3568     struct _status_hilite_line_str *hlstr;
3569     char buf[BUFSZ];
3570     boolean acted = FALSE;
3571 
3572     if (!count) {
3573         if (status_hilite_menu_add(fld)) {
3574             status_hilite_linestr_done();
3575             status_hilite_linestr_gather();
3576             count = status_hilite_linestr_countfield(fld);
3577         } else
3578             return FALSE;
3579     }
3580 
3581     tmpwin = create_nhwindow(NHW_MENU);
3582     start_menu(tmpwin);
3583 
3584     if (count) {
3585         hlstr = status_hilite_str;
3586         while (hlstr) {
3587             if (hlstr->fld == fld) {
3588                 any = zeroany;
3589                 any.a_int = hlstr->id;
3590                 add_menu(tmpwin, NO_GLYPH, &any, 0, 0, ATR_NONE,
3591                          hlstr->str, MENU_UNSELECTED);
3592             }
3593             hlstr = hlstr->next;
3594         }
3595     } else {
3596         any = zeroany;
3597         Sprintf(buf, "No current hilites for %s", initblstats[fld].fldname);
3598         add_menu(tmpwin, NO_GLYPH, &any, 0, 0, ATR_NONE, buf, MENU_UNSELECTED);
3599     }
3600 
3601     /* separator line */
3602     any = zeroany;
3603     add_menu(tmpwin, NO_GLYPH, &any, 0, 0, ATR_NONE, "", MENU_UNSELECTED);
3604 
3605     if (count) {
3606         any = zeroany;
3607         any.a_int = -1;
3608         add_menu(tmpwin, NO_GLYPH, &any, 'X', 0, ATR_NONE,
3609                  "Remove selected hilites", MENU_UNSELECTED);
3610     }
3611 
3612 #ifndef SCORE_ON_BOTL
3613     if (fld == BL_SCORE) {
3614         /* suppress 'Z - Add a new hilite' for 'score' when SCORE_ON_BOTL
3615            is disabled; we wouldn't be called for 'score' unless it has
3616            hilite rules from the config file, so count must be positive
3617            (hence there's no risk that we're putting up an empty menu) */
3618         ;
3619     } else
3620 #endif
3621     {
3622         any = zeroany;
3623         any.a_int = -2;
3624         add_menu(tmpwin, NO_GLYPH, &any, 'Z', 0, ATR_NONE,
3625                  "Add a new hilite", MENU_UNSELECTED);
3626     }
3627 
3628     Sprintf(buf, "Current %s hilites:", initblstats[fld].fldname);
3629     end_menu(tmpwin, buf);
3630 
3631     if ((res = select_menu(tmpwin, PICK_ANY, &picks)) > 0) {
3632         int mode = 0;
3633 
3634         for (i = 0; i < res; i++) {
3635             int idx = picks[i].item.a_int;
3636 
3637             if (idx == -1) {
3638                 /* delete selected hilites */
3639                 if (mode)
3640                     goto shlmenu_free;
3641                 mode = -1;
3642                 break;
3643             } else if (idx == -2) {
3644                 /* create a new hilite */
3645                 if (mode)
3646                     goto shlmenu_free;
3647                 mode = -2;
3648                 break;
3649             }
3650         }
3651 
3652         if (mode == -1) {
3653             /* delete selected hilites */
3654             for (i = 0; i < res; i++) {
3655                 int idx = picks[i].item.a_int;
3656 
3657                 if (idx > 0)
3658                     (void) status_hilite_remove(idx);
3659             }
3660             reset_status_hilites();
3661             acted = TRUE;
3662         } else if (mode == -2) {
3663             /* create a new hilite */
3664             if (status_hilite_menu_add(fld))
3665                 acted = TRUE;
3666         }
3667 
3668         free((genericptr_t) picks);
3669     }
3670 
3671 shlmenu_free:
3672 
3673     picks = (menu_item *) 0;
3674     destroy_nhwindow(tmpwin);
3675     return acted;
3676 }
3677 
3678 void
status_hilites_viewall()3679 status_hilites_viewall()
3680 {
3681     winid datawin;
3682     struct _status_hilite_line_str *hlstr = status_hilite_str;
3683     char buf[BUFSZ];
3684 
3685     datawin = create_nhwindow(NHW_TEXT);
3686 
3687     while (hlstr) {
3688         Sprintf(buf, "OPTIONS=hilite_status: %.*s",
3689                 (int) (BUFSZ - sizeof "OPTIONS=hilite_status: " - 1),
3690                 hlstr->str);
3691         putstr(datawin, 0, buf);
3692         hlstr = hlstr->next;
3693     }
3694 
3695     display_nhwindow(datawin, FALSE);
3696     destroy_nhwindow(datawin);
3697 }
3698 
3699 boolean
status_hilite_menu()3700 status_hilite_menu()
3701 {
3702     winid tmpwin;
3703     int i, res;
3704     menu_item *picks = (menu_item *) 0;
3705     anything any;
3706     boolean redo;
3707     int countall;
3708 
3709 shlmenu_redo:
3710     redo = FALSE;
3711 
3712     tmpwin = create_nhwindow(NHW_MENU);
3713     start_menu(tmpwin);
3714 
3715     status_hilite_linestr_gather();
3716     countall = status_hilite_linestr_countfield(BL_FLUSH);
3717     if (countall) {
3718         any = zeroany;
3719         any.a_int = -1;
3720         add_menu(tmpwin, NO_GLYPH, &any, 0, 0, ATR_NONE,
3721                  "View all hilites in config format", MENU_UNSELECTED);
3722 
3723         any = zeroany;
3724         add_menu(tmpwin, NO_GLYPH, &any, 0, 0, ATR_NONE, "", MENU_UNSELECTED);
3725     }
3726 
3727     for (i = 0; i < MAXBLSTATS; i++) {
3728         int count = status_hilite_linestr_countfield(i);
3729         char buf[BUFSZ];
3730 
3731 #ifndef SCORE_ON_BOTL
3732         /* config file might contain rules for highlighting 'score'
3733            even when SCORE_ON_BOTL is disabled; if so, 'O' command
3734            menus will show them and allow deletions but not additions,
3735            otherwise, it won't show 'score' at all */
3736         if (initblstats[i].fld == BL_SCORE && !count)
3737             continue;
3738 #endif
3739         any = zeroany;
3740         any.a_int = i + 1;
3741         Sprintf(buf, "%-18s", initblstats[i].fldname);
3742         if (count)
3743             Sprintf(eos(buf), " (%d defined)", count);
3744         add_menu(tmpwin, NO_GLYPH, &any, 0, 0, ATR_NONE,
3745                  buf, MENU_UNSELECTED);
3746     }
3747 
3748     end_menu(tmpwin, "Status hilites:");
3749     if ((res = select_menu(tmpwin, PICK_ONE, &picks)) > 0) {
3750         i = picks->item.a_int - 1;
3751         if (i < 0)
3752             status_hilites_viewall();
3753         else
3754             (void) status_hilite_menu_fld(i);
3755         free((genericptr_t) picks), picks = (menu_item *) 0;
3756         redo = TRUE;
3757     }
3758 
3759     destroy_nhwindow(tmpwin);
3760     countall = status_hilite_linestr_countfield(BL_FLUSH);
3761     status_hilite_linestr_done();
3762 
3763     if (redo)
3764         goto shlmenu_redo;
3765 
3766     /* hilite_delta=='statushilites' does double duty:  it is the
3767        number of turns for temporary highlights to remain visible
3768        and also when non-zero it is the flag to enable highlighting */
3769     if (countall > 0 && !iflags.hilite_delta)
3770         pline(
3771  "To have highlights become active, set 'statushilites' option to non-zero.");
3772 
3773     return TRUE;
3774 }
3775 
3776 #endif /* STATUS_HILITES */
3777 
3778 /*botl.c*/
3779