OSDN Git Service

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