OSDN Git Service

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