OSDN Git Service

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