OSDN Git Service

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