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