1 /* NetHack 3.6 botl.c $NHDT-Date: 1557094795 2019/05/05 22:19:55 $ $NHDT-Branch: NetHack-3.6.2-beta01 $:$NHDT-Revision: 1.145 $ */
2 /* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */
3 /*-Copyright (c) Michael Allison, 2006. */
4 /* NetHack may be freely redistributed. See license for details. */
6 /* JNetHack Copyright */
7 /* (c) Issei Numata, Naoki Hamada, Shigehiro Miyashita, 1994-2000 */
8 /* For 3.4-, Copyright (c) SHIRAKATA Kentaro, 2002-2019 */
9 /* JNetHack may be freely redistributed. See license for details. */
16 extern const char *hu_stat[]; /* defined in eat.c */
19 const char *const enc_stat[] = { "", "Burdened", "Stressed",
20 "Strained", "Overtaxed", "Overloaded" };
22 const char *const enc_stat[] = { "", "
\82æ
\82ë
\82ß
\82«", "
\88³
\94\97",
23 "
\8cÀ
\8aE", "
\89×
\8fd", "
\92´
\89ß"};
26 STATIC_OVL NEARDATA int mrank_sz = 0; /* loaded by max_rank_sz (from u_init) */
27 STATIC_DCL const char *NDECL(rank);
28 STATIC_DCL void NDECL(bot_via_windowport);
29 STATIC_DCL void NDECL(stat_update_time);
35 int st = ACURR(A_STR);
39 Sprintf(buf, "%2d", st - 100);
40 else if (st < STR18(100))
41 Sprintf(buf, "18/%02d", st - 18);
43 Sprintf(buf, "18/**");
45 Sprintf(buf, "%-1d", st);
53 nhsym goldch = showsyms[COIN_CLASS + SYM_OFF_O];
55 iflags.invis_goldsym = (goldch <= (nhsym) ' ');
61 static char newbot1[BUFSZ];
65 Strcpy(newbot1, plname);
66 if ('a' <= newbot1[0] && newbot1[0] <= 'z')
67 newbot1[0] += 'A' - 'a';
69 if(is_kanji1(newbot1, 9))
74 Sprintf(nb = eos(newbot1), " the ");
76 Sprintf(nb = eos(newbot1), " ");
83 Strcpy(mbot, mons[u.umonnum].mname);
84 while (mbot[k] != 0) {
85 if ((k == 0 || (k > 0 && mbot[k - 1] == ' ')) && 'a' <= mbot[k]
90 Strcpy(nb = eos(nb), mbot);
92 Strcpy(nb = eos(nb), rank());
94 Sprintf(nb = eos(nb), " ");
96 j = (int) ((nb + 2) - newbot1); /* strlen(newbot1) but less computation */
98 Sprintf(nb = eos(nb), "%*s", i - j, " "); /* pad with spaces */
101 Sprintf(nb = eos(nb), "St:%s Dx:%-1d Co:%-1d In:%-1d Wi:%-1d Ch:%-1d",
103 ACURR(A_DEX), ACURR(A_CON), ACURR(A_INT), ACURR(A_WIS),
106 Sprintf(nb = eos(nb), "
\8b:%s
\91\81:%-1d
\91Ï:%-1d
\92m:%-1d
\8c«:%-1d
\96£:%-1d ",
108 ACURR(A_DEX), ACURR(A_CON), ACURR(A_INT), ACURR(A_WIS),
112 Sprintf(nb = eos(nb),
113 (u.ualign.type == A_CHAOTIC)
115 : (u.ualign.type == A_NEUTRAL) ? " Neutral" : " Lawful");
117 Sprintf(nb = eos(nb),
118 (u.ualign.type == A_CHAOTIC)
120 : (u.ualign.type == A_NEUTRAL) ? "
\92\86\97§" : "
\92\81\8f\98");
125 Sprintf(nb = eos(nb), " S:%ld", botl_score());
127 Sprintf(nb = eos(nb), "%ld
\93_", botl_score());
135 static char newbot2[BUFSZ], /* MAXCO: botl.h */
136 /* dungeon location (and gold), hero health (HP, PW, AC),
137 experience (HD if poly'd, else Exp level and maybe Exp points),
138 time (in moves), varying number of status conditions */
139 dloc[QBUFSZ], hlth[QBUFSZ], expr[QBUFSZ], tmmv[QBUFSZ], cond[QBUFSZ];
141 unsigned dln, dx, hln, xln, tln, cln;
146 * Various min(x,9999)'s are to avoid having excessive values
147 * violate the field width assumptions in botl.h and should not
148 * impact normal play. Particularly 64-bit long for gold which
149 * could require many more digits if someone figures out a way
150 * to get and carry a really large (or negative) amount of it.
151 * Turn counter is also long, but we'll risk that.
154 /* dungeon location plus gold */
155 (void) describe_level(dloc); /* includes at least one trailing space */
156 if ((money = money_cnt(invent)) < 0L)
157 money = 0L; /* ought to issue impossible() and then discard gold */
158 Sprintf(eos(dloc), "%s:%-2ld", /* strongest hero can lift ~300000 gold */
159 (iflags.in_dumplog || iflags.invis_goldsym) ? "$"
160 : encglyph(objnum_to_glyph(GOLD_PIECE)),
161 min(money, 999999L));
163 /* '$' encoded as \GXXXXNNNN is 9 chars longer than display will need */
164 dx = strstri(dloc, "\\G") ? 9 : 0;
166 /* health and armor class (has trailing space for AC 0..9) */
167 hp = Upolyd ? u.mh : u.uhp;
168 hpmax = Upolyd ? u.mhmax : u.uhpmax;
172 Sprintf(hlth, "HP:%d(%d) Pw:%d(%d) AC:%-2d",
173 min(hp, 9999), min(hpmax, 9999),
174 min(u.uen, 9999), min(u.uenmax, 9999), u.uac);
176 Sprintf(hlth, "
\91Ì:%d(%d)
\96\82:%d(%d)
\8aZ:%-2d",
177 min(hp, 9999), min(hpmax, 9999),
178 min(u.uen, 9999), min(u.uenmax, 9999), u.uac);
184 Sprintf(expr, "HD:%d", mons[u.umonnum].mlevel);
185 else if (flags.showexp)
187 Sprintf(expr, "Xp:%u/%-1ld", u.ulevel, u.uexp);
189 Sprintf(expr, "
\8co
\8c±:%u/%-1ld", u.ulevel, u.uexp);
192 Sprintf(expr, "Exp:%u", u.ulevel);
194 Sprintf(expr, "
\8co
\8c±:%u", u.ulevel);
197 /* time/move counter */
200 Sprintf(tmmv, "T:%ld", moves);
202 Sprintf(tmmv, "
\95à:%ld", moves);
207 /* status conditions; worst ones first */
208 cond[0] = '\0'; /* once non-empty, cond will have a leading space */
211 * Stoned, Slimed, Strangled, and both types of Sick are all fatal
212 * unless remedied before timeout expires. Should we order them by
213 * shortest time left? [Probably not worth the effort, since it's
214 * unusual for more than one of them to apply at a time.]
218 Strcpy(nb = eos(nb), " Stone");
220 Strcpy(nb = eos(nb), "
\90Î
\89»");
223 Strcpy(nb = eos(nb), " Slime");
225 Strcpy(nb = eos(nb), "
\82Ç
\82ë
\82Ç
\82ë");
228 Strcpy(nb = eos(nb), " Strngl");
230 Strcpy(nb = eos(nb), "
\92\82\91§");
232 if (u.usick_type & SICK_VOMITABLE)
234 Strcpy(nb = eos(nb), " FoodPois");
236 Strcpy(nb = eos(nb), "
\90H
\93Å");
237 if (u.usick_type & SICK_NONVOMITABLE)
239 Strcpy(nb = eos(nb), " TermIll");
241 Strcpy(nb = eos(nb), "
\95a
\8bC");
243 if (u.uhs != NOT_HUNGRY)
244 Sprintf(nb = eos(nb), " %s", hu_stat[u.uhs]);
245 if ((cap = near_capacity()) > UNENCUMBERED)
246 Sprintf(nb = eos(nb), " %s", enc_stat[cap]);
249 Strcpy(nb = eos(nb), " Blind");
251 Strcpy(nb = eos(nb), "
\96Ó
\96Ú");
254 Strcpy(nb = eos(nb), " Deaf");
256 Strcpy(nb = eos(nb), "
\8e¨
\98W");
259 Strcpy(nb = eos(nb), " Stun");
261 Strcpy(nb = eos(nb), " á¿
\9dò");
264 Strcpy(nb = eos(nb), " Conf");
266 Strcpy(nb = eos(nb), "
\8d¬
\97\90");
269 Strcpy(nb = eos(nb), " Hallu");
271 Strcpy(nb = eos(nb), "
\8c¶
\8ao");
272 /* levitation and flying are mutually exclusive; riding is not */
275 Strcpy(nb = eos(nb), " Lev");
277 Strcpy(nb = eos(nb), "
\95\82\97V");
280 Strcpy(nb = eos(nb), " Fly");
282 Strcpy(nb = eos(nb), "
\94ò
\8ds");
285 Strcpy(nb = eos(nb), " Ride");
287 Strcpy(nb = eos(nb), "
\8bR
\8fæ");
291 * Put the pieces together. If they all fit, keep the traditional
292 * sequence. Otherwise, move least important parts to the end in
293 * case the interface side of things has to truncate. Note that
294 * dloc[] contains '$' encoded in ten character sequence \GXXXXNNNN
295 * so we want to test its display length rather than buffer length.
297 * We don't have an actual display limit here, so have to go by the
298 * width of the map. Since we're reordering rather than truncating,
299 * wider displays can still show wider status than the map if the
300 * interface supports that.
302 if ((dln - dx) + 1 + hln + 1 + xln + 1 + tln + 1 + cln <= COLNO) {
303 Sprintf(newbot2, "%s %s %s %s %s", dloc, hlth, expr, tmmv, cond);
305 if (dln + 1 + hln + 1 + xln + 1 + tln + 1 + cln + 1 > MAXCO) {
306 panic("bot2: second status line exceeds MAXCO (%u > %d)",
307 (dln + 1 + hln + 1 + xln + 1 + tln + 1 + cln + 1), MAXCO);
308 } else if ((dln - dx) + 1 + hln + 1 + xln + 1 + cln <= COLNO) {
309 Sprintf(newbot2, "%s %s %s %s %s", dloc, hlth, expr, cond, tmmv);
310 } else if ((dln - dx) + 1 + hln + 1 + cln <= COLNO) {
311 Sprintf(newbot2, "%s %s %s %s %s", dloc, hlth, cond, expr, tmmv);
313 Sprintf(newbot2, "%s %s %s %s %s", hlth, cond, dloc, expr, tmmv);
315 /* only two or three consecutive spaces available to squeeze out */
324 /* dosave() flags completion by setting u.uhp to -1 */
325 if ((u.uhp != -1) && youmonst.data && iflags.status_updates) {
326 if (VIA_WINDOWPORT()) {
327 bot_via_windowport();
329 curs(WIN_STATUS, 1, 0);
330 putstr(WIN_STATUS, 0, do_statusline1());
331 curs(WIN_STATUS, 1, 1);
332 putmixed(WIN_STATUS, 0, do_statusline2());
335 context.botl = context.botlx = iflags.time_botl = FALSE;
341 if (flags.time && iflags.status_updates) {
342 if (VIA_WINDOWPORT()) {
345 /* old status display updates everything */
349 iflags.time_botl = FALSE;
352 /* convert experience level (1..30) to rank index (0..8) */
357 return (xlev <= 2) ? 0 : (xlev <= 30) ? ((xlev + 2) / 4) : 8;
360 #if 0 /* not currently needed */
361 /* convert rank index (0..8) to experience level (1..30) */
366 return (rank <= 0) ? 1 : (rank <= 8) ? ((rank * 4) - 2) : 30;
371 rank_of(lev, monnum, female)
376 register const struct Role *role;
380 for (role = roles; role->name.m; role++)
381 if (monnum == role->malenum || monnum == role->femalenum)
387 for (i = xlev_to_rank((int) lev); i >= 0; i--) {
388 if (female && role->rank[i].f)
389 return role->rank[i].f;
391 return role->rank[i].m;
394 /* Try the role name, instead */
395 if (female && role->name.f)
397 else if (role->name.m)
402 return "
\83v
\83\8c\83C
\83\84\81[";
405 STATIC_OVL const char *
408 return rank_of(u.ulevel, Role_switch, flags.female);
412 title_to_mon(str, rank_indx, title_length)
414 int *rank_indx, *title_length;
418 /* Loop through each of the roles */
419 for (i = 0; roles[i].name.m; i++)
420 for (j = 0; j < 9; j++) {
421 if (roles[i].rank[j].m
422 && !strncmpi(str, roles[i].rank[j].m,
423 strlen(roles[i].rank[j].m))) {
427 *title_length = strlen(roles[i].rank[j].m);
428 return roles[i].malenum;
430 if (roles[i].rank[j].f
431 && !strncmpi(str, roles[i].rank[j].f,
432 strlen(roles[i].rank[j].f))) {
436 *title_length = strlen(roles[i].rank[j].f);
437 return (roles[i].femalenum != NON_PM) ? roles[i].femalenum
447 register int i, r, maxr = 0;
448 for (i = 0; i < 9; i++) {
449 if (urole.rank[i].m && (r = strlen(urole.rank[i].m)) > maxr)
451 if (urole.rank[i].f && (r = strlen(urole.rank[i].f)) > maxr)
462 long deepest = deepest_lev_reached(FALSE);
465 utotal = money_cnt(invent) + hidden_gold();
466 if ((utotal -= u.umoney0) < 0L)
468 utotal += u.urexp + (50 * (deepest - 1))
469 + (deepest > 30 ? 10000 : deepest > 20 ? 1000 * (deepest - 20) : 0);
470 if (utotal < u.urexp)
471 utotal = LONG_MAX; /* wrap around */
474 #endif /* SCORE_ON_BOTL */
476 /* provide the name of the current level for display by various ports */
483 /* TODO: Add in dungeon name */
484 if (Is_knox(&u.uz)) {
485 Sprintf(buf, "%s ", dungeons[u.uz.dnum].dname);
486 } else if (In_quest(&u.uz)) {
488 Sprintf(buf, "Home %d ", dunlev(&u.uz));
490 Sprintf(buf, "
\8cÌ
\8b½ %d ", dunlev(&u.uz));
491 } else if (In_endgame(&u.uz)) {
492 /* [3.6.2: this used to be "Astral Plane" or generic "End Game"] */
493 (void) endgamelevelname(buf, depth(&u.uz));
495 (void) strsubst(buf, "Plane of ", ""); /* just keep <element> */
499 /* ports with more room may expand this one */
501 Sprintf(buf, "Dlvl:%-2d ", depth(&u.uz));
503 Sprintf(buf, "
\92n
\89º:%-2d ", depth(&u.uz));
509 /* =======================================================================*/
510 /* statusnew routines */
511 /* =======================================================================*/
513 /* structure that tracks the status details in the core */
515 #define MAXVALWIDTH 80 /* actually less, but was using 80 to allocate title
516 * and leveldesc then using QBUFSZ everywhere else */
517 #ifdef STATUS_HILITES
519 enum statusfields fld;
524 char textmatch[MAXVALWIDTH];
525 enum relationships rel;
527 struct hilite_s *next;
529 #endif /* STATUS_HILITES */
534 long time; /* moves when this field hilite times out */
535 boolean chg; /* need to recalc time? */
540 enum statusfields idxmax;
541 enum statusfields fld;
542 #ifdef STATUS_HILITES
543 struct hilite_s *hilite_rule; /* the entry, if any, in 'thresholds'
544 * list that currently applies */
545 struct hilite_s *thresholds;
549 STATIC_DCL boolean FDECL(eval_notify_windowport_field, (int, boolean *, int));
550 STATIC_DCL void FDECL(evaluate_and_notify_windowport, (boolean *, int));
551 STATIC_DCL void NDECL(init_blstats);
552 STATIC_DCL int FDECL(compare_blstats, (struct istat_s *, struct istat_s *));
553 STATIC_DCL char *FDECL(anything_to_s, (char *, anything *, int));
554 STATIC_DCL int FDECL(percentage, (struct istat_s *, struct istat_s *));
556 #ifdef STATUS_HILITES
557 STATIC_DCL void FDECL(s_to_anything, (anything *, char *, int));
558 STATIC_DCL enum statusfields FDECL(fldname_to_bl_indx, (const char *));
559 STATIC_DCL boolean FDECL(hilite_reset_needed, (struct istat_s *, long));
560 STATIC_DCL boolean FDECL(noneoftheabove, (const char *));
561 STATIC_DCL struct hilite_s *FDECL(get_hilite, (int, int, genericptr_t,
563 STATIC_DCL void FDECL(split_clridx, (int, int *, int *));
564 STATIC_DCL boolean FDECL(is_ltgt_percentnumber, (const char *));
565 STATIC_DCL boolean FDECL(has_ltgt_percentnumber, (const char *));
566 STATIC_DCL int FDECL(splitsubfields, (char *, char ***, int));
567 STATIC_DCL boolean FDECL(is_fld_arrayvalues, (const char *,
570 STATIC_DCL int FDECL(query_arrayvalue, (const char *, const char *const *,
572 STATIC_DCL void FDECL(status_hilite_add_threshold, (int, struct hilite_s *));
573 STATIC_DCL boolean FDECL(parse_status_hl2, (char (*)[QBUFSZ], BOOLEAN_P));
574 STATIC_DCL char *FDECL(conditionbitmask2str, (unsigned long));
575 STATIC_DCL unsigned long FDECL(match_str2conditionbitmask, (const char *));
576 STATIC_DCL unsigned long FDECL(str2conditionbitmask, (char *));
577 STATIC_DCL boolean FDECL(parse_condition, (char (*)[QBUFSZ], int));
578 STATIC_DCL char *FDECL(hlattr2attrname, (int, char *, int));
579 STATIC_DCL void FDECL(status_hilite_linestr_add, (int, struct hilite_s *,
580 unsigned long, const char *));
581 STATIC_DCL void NDECL(status_hilite_linestr_done);
582 STATIC_DCL int FDECL(status_hilite_linestr_countfield, (int));
583 STATIC_DCL void NDECL(status_hilite_linestr_gather_conditions);
584 STATIC_DCL void NDECL(status_hilite_linestr_gather);
585 STATIC_DCL char *FDECL(status_hilite2str, (struct hilite_s *));
586 STATIC_DCL int NDECL(status_hilite_menu_choose_field);
587 STATIC_DCL int FDECL(status_hilite_menu_choose_behavior, (int));
588 STATIC_DCL int FDECL(status_hilite_menu_choose_updownboth, (int, const char *,
589 BOOLEAN_P, BOOLEAN_P));
590 STATIC_DCL boolean FDECL(status_hilite_menu_add, (int));
591 #define has_hilite(i) (blstats[0][(i)].thresholds)
592 /* TH_UPDOWN encompasses specific 'up' and 'down' also general 'changed' */
593 #define Is_Temp_Hilite(rule) ((rule) && (rule)->behavior == BL_TH_UPDOWN)
595 /* pointers to current hilite rule and list of this field's defined rules */
596 #define INIT_THRESH , (struct hilite_s *) 0, (struct hilite_s *) 0
597 #else /* !STATUS_HILITES */
598 #define INIT_THRESH /*empty*/
601 #define INIT_BLSTAT(name, fmtstr, anytyp, wid, fld) \
602 { name, fmtstr, 0L, FALSE, anytyp, { (genericptr_t) 0 }, (char *) 0, \
603 wid, -1, fld INIT_THRESH }
604 #define INIT_BLSTATP(name, fmtstr, anytyp, wid, maxfld, fld) \
605 { name, fmtstr, 0L, FALSE, anytyp, { (genericptr_t) 0 }, (char *) 0, \
606 wid, maxfld, fld INIT_THRESH }
608 /* If entries are added to this, botl.h will require updating too */
609 STATIC_VAR struct istat_s initblstats[MAXBLSTATS] = {
610 INIT_BLSTAT("title", "%s", ANY_STR, MAXVALWIDTH, BL_TITLE),
611 INIT_BLSTAT("strength", " St:%s", ANY_INT, 10, BL_STR),
612 INIT_BLSTAT("dexterity", " Dx:%s", ANY_INT, 10, BL_DX),
613 INIT_BLSTAT("constitution", " Co:%s", ANY_INT, 10, BL_CO),
614 INIT_BLSTAT("intelligence", " In:%s", ANY_INT, 10, BL_IN),
615 INIT_BLSTAT("wisdom", " Wi:%s", ANY_INT, 10, BL_WI),
616 INIT_BLSTAT("charisma", " Ch:%s", ANY_INT, 10, BL_CH),
617 INIT_BLSTAT("alignment", " %s", ANY_STR, 40, BL_ALIGN),
618 INIT_BLSTAT("score", " S:%s", ANY_LONG, 20, BL_SCORE),
619 INIT_BLSTAT("carrying-capacity", " %s", ANY_INT, 20, BL_CAP),
620 INIT_BLSTAT("gold", " %s", ANY_LONG, 30, BL_GOLD),
621 INIT_BLSTATP("power", " Pw:%s", ANY_INT, 10, BL_ENEMAX, BL_ENE),
622 INIT_BLSTAT("power-max", "(%s)", ANY_INT, 10, BL_ENEMAX),
623 INIT_BLSTAT("experience-level", " Xp:%s", ANY_INT, 10, BL_XP),
624 INIT_BLSTAT("armor-class", " AC:%s", ANY_INT, 10, BL_AC),
625 INIT_BLSTAT("HD", " HD:%s", ANY_INT, 10, BL_HD),
626 INIT_BLSTAT("time", " T:%s", ANY_LONG, 20, BL_TIME),
627 /* hunger used to be 'ANY_UINT'; see note below in bot_via_windowport() */
628 INIT_BLSTAT("hunger", " %s", ANY_INT, 40, BL_HUNGER),
629 INIT_BLSTATP("hitpoints", " HP:%s", ANY_INT, 10, BL_HPMAX, BL_HP),
630 INIT_BLSTAT("hitpoints-max", "(%s)", ANY_INT, 10, BL_HPMAX),
631 INIT_BLSTAT("dungeon-level", "%s", ANY_STR, MAXVALWIDTH, BL_LEVELDESC),
632 INIT_BLSTAT("experience", "/%s", ANY_LONG, 20, BL_EXP),
633 INIT_BLSTAT("condition", "%s", ANY_MASK32, 0, BL_CONDITION)
640 struct istat_s blstats[2][MAXBLSTATS];
641 static boolean blinit = FALSE, update_all = FALSE;
642 static boolean valset[MAXBLSTATS];
643 #ifdef STATUS_HILITES
644 static long bl_hilite_moves = 0L;
647 /* we don't put this next declaration in #ifdef STATUS_HILITES.
648 * In the absence of STATUS_HILITES, each array
649 * element will be 0 however, and quite meaningless,
650 * but we need to pass the first array element as
651 * the final argument of status_update, with or
652 * without STATUS_HILITES.
654 static unsigned long cond_hilites[BL_ATTCLR_MAX];
655 static int now_or_before_idx = 0; /* 0..1 for array[2][] first index */
667 panic("bot before init.");
669 /* toggle from previous iteration */
670 idx = 1 - now_or_before_idx; /* 0 -> 1, 1 -> 0 */
671 now_or_before_idx = idx;
673 /* clear the "value set" indicators */
674 (void) memset((genericptr_t) valset, 0, MAXBLSTATS * sizeof (boolean));
677 * Note: min(x,9999) - we enforce the same maximum on hp, maxhp,
678 * pw, maxpw, and gold as basic status formatting so that the two
679 * modes of status display don't produce different information.
683 * Player name and title.
685 Strcpy(nb = buf, plname);
686 nb[0] = highc(nb[0]);
687 titl = !Upolyd ? rank() : mons[u.umonnum].mname;
688 i = (int) (strlen(buf) + sizeof " the " + strlen(titl) - sizeof "");
689 /* if "Name the Rank/monster" is too long, we truncate the name
690 but always keep at least 10 characters of it; when hitpintbar is
691 enabled, anything beyond 30 (long monster name) will be truncated */
693 i = 30 - (int) (sizeof " the " + strlen(titl) - sizeof "");
694 nb[max(i, 10)] = '\0';
696 Strcpy(nb = eos(nb), " the ");
697 Strcpy(nb = eos(nb), titl);
698 if (Upolyd) { /* when poly'd, capitalize monster name */
699 for (i = 0; nb[i]; i++)
700 if (i == 0 || nb[i - 1] == ' ')
701 nb[i] = highc(nb[i]);
703 Sprintf(blstats[idx][BL_TITLE].val, "%-30s", buf);
704 valset[BL_TITLE] = TRUE; /* indicate val already set */
707 blstats[idx][BL_STR].a.a_int = ACURR(A_STR);
708 Strcpy(blstats[idx][BL_STR].val, get_strength_str());
709 valset[BL_STR] = TRUE; /* indicate val already set */
711 /* Dexterity, constitution, intelligence, wisdom, charisma. */
712 blstats[idx][BL_DX].a.a_int = ACURR(A_DEX);
713 blstats[idx][BL_CO].a.a_int = ACURR(A_CON);
714 blstats[idx][BL_IN].a.a_int = ACURR(A_INT);
715 blstats[idx][BL_WI].a.a_int = ACURR(A_WIS);
716 blstats[idx][BL_CH].a.a_int = ACURR(A_CHA);
720 Strcpy(blstats[idx][BL_ALIGN].val, (u.ualign.type == A_CHAOTIC)
722 : (u.ualign.type == A_NEUTRAL)
726 Strcpy(blstats[idx][BL_ALIGN].val, (u.ualign.type == A_CHAOTIC)
728 : (u.ualign.type == A_NEUTRAL)
734 blstats[idx][BL_SCORE].a.a_long =
736 flags.showscore ? botl_score() :
741 i = Upolyd ? u.mh : u.uhp;
744 blstats[idx][BL_HP].a.a_int = min(i, 9999);
745 i = Upolyd ? u.mhmax : u.uhpmax;
746 blstats[idx][BL_HPMAX].a.a_int = min(i, 9999);
749 (void) describe_level(blstats[idx][BL_LEVELDESC].val);
750 valset[BL_LEVELDESC] = TRUE; /* indicate val already set */
753 if ((money = money_cnt(invent)) < 0L)
754 money = 0L; /* ought to issue impossible() and then discard gold */
755 blstats[idx][BL_GOLD].a.a_long = min(money, 999999L);
757 * The tty port needs to display the current symbol for gold
758 * as a field header, so to accommodate that we pass gold with
759 * that already included. If a window port needs to use the text
760 * gold amount without the leading "$:" the port will have to
761 * skip past ':' to the value pointer it was passed in status_update()
762 * for the BL_GOLD case.
764 * Another quirk of BL_GOLD is that the field display may have
765 * changed if a new symbol set was loaded, or we entered or left
768 * The currency prefix is encoded as ten character \GXXXXNNNN
771 Sprintf(blstats[idx][BL_GOLD].val, "%s:%ld",
772 (iflags.in_dumplog || iflags.invis_goldsym) ? "$"
773 : encglyph(objnum_to_glyph(GOLD_PIECE)),
774 blstats[idx][BL_GOLD].a.a_long);
775 valset[BL_GOLD] = TRUE; /* indicate val already set */
777 /* Power (magical energy) */
778 blstats[idx][BL_ENE].a.a_int = min(u.uen, 9999);
779 blstats[idx][BL_ENEMAX].a.a_int = min(u.uenmax, 9999);
782 blstats[idx][BL_AC].a.a_int = u.uac;
784 /* Monster level (if Upolyd) */
785 blstats[idx][BL_HD].a.a_int = Upolyd ? (int) mons[u.umonnum].mlevel : 0;
788 blstats[idx][BL_XP].a.a_int = u.ulevel;
789 blstats[idx][BL_EXP].a.a_long = u.uexp;
792 blstats[idx][BL_TIME].a.a_long = moves;
795 /* note: u.uhs is unsigned, and 3.6.1's STATUS_HILITE defined
796 BL_HUNGER to be ANY_UINT, but that was the only non-int/non-long
797 numeric field so it's far simpler to treat it as plain int and
798 not need ANY_UINT handling at all */
799 blstats[idx][BL_HUNGER].a.a_int = (int) u.uhs;
800 Strcpy(blstats[idx][BL_HUNGER].val,
801 (u.uhs != NOT_HUNGRY) ? hu_stat[u.uhs] : "");
802 valset[BL_HUNGER] = TRUE;
804 /* Carrying capacity */
805 cap = near_capacity();
806 blstats[idx][BL_CAP].a.a_int = cap;
807 Strcpy(blstats[idx][BL_CAP].val,
808 (cap > UNENCUMBERED) ? enc_stat[cap] : "");
809 valset[BL_CAP] = TRUE;
812 blstats[idx][BL_CONDITION].a.a_ulong = 0L;
814 blstats[idx][BL_CONDITION].a.a_ulong |= BL_MASK_STONE;
816 blstats[idx][BL_CONDITION].a.a_ulong |= BL_MASK_SLIME;
818 blstats[idx][BL_CONDITION].a.a_ulong |= BL_MASK_STRNGL;
819 if (Sick && (u.usick_type & SICK_VOMITABLE) != 0)
820 blstats[idx][BL_CONDITION].a.a_ulong |= BL_MASK_FOODPOIS;
821 if (Sick && (u.usick_type & SICK_NONVOMITABLE) != 0)
822 blstats[idx][BL_CONDITION].a.a_ulong |= BL_MASK_TERMILL;
824 * basic formatting puts hunger status and encumbrance here
827 blstats[idx][BL_CONDITION].a.a_ulong |= BL_MASK_BLIND;
829 blstats[idx][BL_CONDITION].a.a_ulong |= BL_MASK_DEAF;
831 blstats[idx][BL_CONDITION].a.a_ulong |= BL_MASK_STUN;
833 blstats[idx][BL_CONDITION].a.a_ulong |= BL_MASK_CONF;
835 blstats[idx][BL_CONDITION].a.a_ulong |= BL_MASK_HALLU;
836 /* levitation and flying are mututally exclusive */
838 blstats[idx][BL_CONDITION].a.a_ulong |= BL_MASK_LEV;
840 blstats[idx][BL_CONDITION].a.a_ulong |= BL_MASK_FLY;
842 blstats[idx][BL_CONDITION].a.a_ulong |= BL_MASK_RIDE;
843 evaluate_and_notify_windowport(valset, idx);
846 /* update just the status lines' 'time' field */
850 int idx = now_or_before_idx; /* no 0/1 toggle */
854 blstats[idx][fld].a.a_long = moves;
857 eval_notify_windowport_field(fld, valset, idx);
858 if ((windowprocs.wincap2 & WC2_FLUSH_STATUS) != 0L)
859 status_update(BL_FLUSH, (genericptr_t) 0, 0, 0,
860 NO_COLOR, (unsigned long *) 0);
865 eval_notify_windowport_field(fld, valsetlist, idx)
869 static int oldrndencode = 0;
870 static nhsym oldgoldsym = 0;
871 int pc, chg, color = NO_COLOR;
873 boolean updated = FALSE, reset;
874 struct istat_s *curr = NULL, *prev = NULL;
875 enum statusfields idxmax;
878 * Now pass the changed values to window port.
880 anytype = blstats[idx][fld].anytype;
881 curr = &blstats[idx][fld];
882 prev = &blstats[1 - idx][fld];
885 chg = update_all ? 0 : compare_blstats(prev, curr);
887 /* Temporary? hack: moveloop()'s prolog for a new game sets
888 * context.rndencode after the status window has been init'd,
889 * so $:0 has already been encoded and cached by the window
890 * port. Without this hack, gold's \G sequence won't be
891 * recognized and ends up being displayed as-is for 'update_all'.
893 * Also, even if context.rndencode hasn't changed and the
894 * gold amount itself hasn't changed, the glyph portion of the
895 * encoding may have changed if a new symset was put into effect.
898 * XXXX = the context.rndencode portion
899 * NNNN = the glyph portion
900 * 25 = the gold amount
902 * Setting 'chg = 2' is enough to render the field properly, but
903 * not to honor an initial highlight, so force 'update_all = TRUE'.
906 && (context.rndencode != oldrndencode
907 || showsyms[COIN_CLASS + SYM_OFF_O] != oldgoldsym)) {
908 update_all = TRUE; /* chg = 2; */
909 oldrndencode = context.rndencode;
910 oldgoldsym = showsyms[COIN_CLASS + SYM_OFF_O];
914 #ifdef STATUS_HILITES
915 if (!update_all && !chg && curr->time) {
916 reset = hilite_reset_needed(prev, bl_hilite_moves);
918 curr->time = prev->time = 0L;
924 * It's possible for HPmax (or ENEmax) to change while current
925 * HP (or energy) stays the same. [Perhaps current and maximum
926 * both go up, then before the next status update takes place
927 * current goes down again.] If that happens with HPmax, we
928 * ought to force the windowport to treat current HP as changed
929 * if hitpointbar is On, in order for that to be re-rendered.
931 if (update_all || chg || reset) {
932 idxmax = curr->idxmax;
933 pc = (idxmax >= 0) ? percentage(curr, &blstats[idx][idxmax]) : 0;
935 if (!valsetlist[fld])
936 (void) anything_to_s(curr->val, &curr->a, anytype);
938 if (anytype != ANY_MASK32) {
939 #ifdef STATUS_HILITES
940 if (chg || *curr->val) {
941 curr->hilite_rule = get_hilite(idx, fld,
942 (genericptr_t) &curr->a,
944 prev->hilite_rule = curr->hilite_rule;
950 #endif /* STATUS_HILITES */
951 status_update(fld, (genericptr_t) curr->val,
952 chg, pc, color, (unsigned long *) 0);
954 /* Color for conditions is done through cond_hilites[] */
955 status_update(fld, (genericptr_t) &curr->a.a_ulong,
956 chg, pc, color, cond_hilites);
958 curr->chg = prev->chg = TRUE;
965 evaluate_and_notify_windowport(valsetlist, idx)
969 int i, updated = 0, notpresent = 0;
972 * Now pass the changed values to window port.
974 for (i = 0; i < MAXBLSTATS; i++) {
975 if (((i == BL_SCORE) && !flags.showscore)
976 || ((i == BL_EXP) && !flags.showexp)
977 || ((i == BL_TIME) && !flags.time)
978 || ((i == BL_HD) && !Upolyd)
979 || ((i == BL_XP || i == BL_EXP) && Upolyd)) {
983 if (eval_notify_windowport_field(i, valsetlist, idx))
988 * 1. It is possible to get here, with nothing having been pushed
989 * to the window port, when none of the info has changed.
991 * 2. Some window ports are also known to optimize by only drawing
992 * fields that have changed since the previous update.
994 * In both of those situations, we need to force updates to
995 * all of the fields when context.botlx is set. The tty port in
996 * particular has a problem if that isn't done, since the core sets
997 * context.botlx when a menu or text display obliterates the status
1000 * For those situations, to trigger the full update of every field
1001 * whether changed or not, call status_update() with BL_RESET.
1003 * For regular processing and to notify the window port that a
1004 * bot() round has finished and it's time to trigger a flush of
1005 * all buffered changes received thus far but not reflected in
1006 * the display, call status_update() with BL_FLUSH.
1009 if (context.botlx && (windowprocs.wincap2 & WC2_RESET_STATUS) != 0L)
1010 status_update(BL_RESET, (genericptr_t) 0, 0, 0,
1011 NO_COLOR, (unsigned long *) 0);
1012 else if ((updated || context.botlx)
1013 && (windowprocs.wincap2 & WC2_FLUSH_STATUS) != 0L)
1014 status_update(BL_FLUSH, (genericptr_t) 0, 0, 0,
1015 NO_COLOR, (unsigned long *) 0);
1017 context.botl = context.botlx = iflags.time_botl = FALSE;
1022 status_initialize(reassessment)
1023 boolean reassessment; /* TRUE: just recheck fields w/o other initialization */
1025 enum statusfields fld;
1028 const char *fieldfmt, *fieldname;
1030 if (!reassessment) {
1032 impossible("2nd status_initialize with full init.");
1034 (*windowprocs.win_status_init)();
1036 } else if (!blinit) {
1037 panic("status 'reassess' before init");
1039 for (i = 0; i < MAXBLSTATS; ++i) {
1040 fld = initblstats[i].fld;
1041 fldenabl = (fld == BL_SCORE) ? flags.showscore
1042 : (fld == BL_TIME) ? flags.time
1043 : (fld == BL_EXP) ? (boolean) (flags.showexp && !Upolyd)
1044 : (fld == BL_XP) ? (boolean) !Upolyd
1045 : (fld == BL_HD) ? (boolean) Upolyd
1048 fieldname = initblstats[i].fldname;
1049 fieldfmt = (fld == BL_TITLE && iflags.wc2_hitpointbar) ? "%-30.30s"
1050 : initblstats[i].fldfmt;
1051 status_enablefield(fld, fieldname, fieldfmt, fldenabl);
1061 /* call the window port cleanup routine first */
1062 if (windowprocs.win_status_finish)
1063 (*windowprocs.win_status_finish)();
1065 /* free memory that we alloc'd now */
1066 for (i = 0; i < MAXBLSTATS; ++i) {
1067 if (blstats[0][i].val)
1068 free((genericptr_t) blstats[0][i].val), blstats[0][i].val = 0;
1069 if (blstats[1][i].val)
1070 free((genericptr_t) blstats[1][i].val), blstats[1][i].val = 0;
1071 #ifdef STATUS_HILITES
1072 /* pointer to an entry in thresholds list; Null it out since
1073 that list is about to go away */
1074 blstats[0][i].hilite_rule = blstats[1][i].hilite_rule = 0;
1075 if (blstats[0][i].thresholds) {
1076 struct hilite_s *temp, *next;
1078 for (temp = blstats[0][i].thresholds; temp; temp = next) {
1080 free((genericptr_t) temp);
1082 blstats[0][i].thresholds = blstats[1][i].thresholds = 0;
1084 #endif /* STATUS_HILITES */
1091 static boolean initalready = FALSE;
1095 impossible("init_blstats called more than once.");
1098 for (i = 0; i <= 1; ++i) {
1099 for (j = 0; j < MAXBLSTATS; ++j) {
1100 #ifdef STATUS_HILITES
1101 struct hilite_s *keep_hilite_chain = blstats[i][j].thresholds;
1104 blstats[i][j] = initblstats[j];
1105 blstats[i][j].a = zeroany;
1106 if (blstats[i][j].valwidth) {
1107 blstats[i][j].val = (char *) alloc(blstats[i][j].valwidth);
1108 blstats[i][j].val[0] = '\0';
1110 blstats[i][j].val = (char *) 0;
1111 #ifdef STATUS_HILITES
1112 blstats[i][j].thresholds = keep_hilite_chain;
1120 * This compares the previous stat with the current stat,
1121 * and returns one of the following results based on that:
1123 * if prev_value < new_value (stat went up, increased)
1126 * if prev_value > new_value (stat went down, decreased)
1129 * if prev_value == new_value (stat stayed the same)
1133 * - for bitmasks, 0 = stayed the same, 1 = changed
1134 * - for strings, 0 = stayed the same, 1 = changed
1138 compare_blstats(bl1, bl2)
1139 struct istat_s *bl1, *bl2;
1141 int anytype, result = 0;
1144 panic("compare_blstat: bad istat pointer %s, %s",
1145 fmt_ptr((genericptr_t) bl1), fmt_ptr((genericptr_t) bl2));
1148 anytype = bl1->anytype;
1149 if ((!bl1->a.a_void || !bl2->a.a_void)
1150 && (anytype == ANY_IPTR || anytype == ANY_UPTR || anytype == ANY_LPTR
1151 || anytype == ANY_ULPTR)) {
1152 panic("compare_blstat: invalid pointer %s, %s",
1153 fmt_ptr((genericptr_t) bl1->a.a_void),
1154 fmt_ptr((genericptr_t) bl2->a.a_void));
1159 result = (bl1->a.a_int < bl2->a.a_int)
1161 : (bl1->a.a_int > bl2->a.a_int) ? -1 : 0;
1164 result = (*bl1->a.a_iptr < *bl2->a.a_iptr)
1166 : (*bl1->a.a_iptr > *bl2->a.a_iptr) ? -1 : 0;
1169 result = (bl1->a.a_long < bl2->a.a_long)
1171 : (bl1->a.a_long > bl2->a.a_long) ? -1 : 0;
1174 result = (*bl1->a.a_lptr < *bl2->a.a_lptr)
1176 : (*bl1->a.a_lptr > *bl2->a.a_lptr) ? -1 : 0;
1179 result = (bl1->a.a_uint < bl2->a.a_uint)
1181 : (bl1->a.a_uint > bl2->a.a_uint) ? -1 : 0;
1184 result = (*bl1->a.a_uptr < *bl2->a.a_uptr)
1186 : (*bl1->a.a_uptr > *bl2->a.a_uptr) ? -1 : 0;
1189 result = (bl1->a.a_ulong < bl2->a.a_ulong)
1191 : (bl1->a.a_ulong > bl2->a.a_ulong) ? -1 : 0;
1194 result = (*bl1->a.a_ulptr < *bl2->a.a_ulptr)
1196 : (*bl1->a.a_ulptr > *bl2->a.a_ulptr) ? -1 : 0;
1199 result = sgn(strcmp(bl1->val, bl2->val));
1202 result = (bl1->a.a_ulong != bl2->a.a_ulong);
1211 anything_to_s(buf, a, anytype)
1221 Sprintf(buf, "%lu", a->a_ulong);
1224 Sprintf(buf, "%lx", a->a_ulong);
1227 Sprintf(buf, "%ld", a->a_long);
1230 Sprintf(buf, "%d", a->a_int);
1233 Sprintf(buf, "%u", a->a_uint);
1236 Sprintf(buf, "%d", *a->a_iptr);
1239 Sprintf(buf, "%ld", *a->a_lptr);
1242 Sprintf(buf, "%lu", *a->a_ulptr);
1245 Sprintf(buf, "%u", *a->a_uptr);
1247 case ANY_STR: /* do nothing */
1256 #ifdef STATUS_HILITES
1258 s_to_anything(a, buf, anytype)
1268 a->a_long = atol(buf);
1271 a->a_int = atoi(buf);
1274 a->a_uint = (unsigned) atoi(buf);
1277 a->a_ulong = (unsigned long) atol(buf);
1281 *a->a_iptr = atoi(buf);
1285 *a->a_uptr = (unsigned) atoi(buf);
1289 *a->a_lptr = atol(buf);
1293 *a->a_ulptr = (unsigned long) atol(buf);
1296 a->a_ulong = (unsigned long) atol(buf);
1304 #endif /* STATUS_HILITES */
1307 percentage(bl, maxbl)
1308 struct istat_s *bl, *maxbl;
1315 unsigned long ulval;
1317 if (!bl || !maxbl) {
1318 impossible("percentage: bad istat pointer %s, %s",
1319 fmt_ptr((genericptr_t) bl), fmt_ptr((genericptr_t) maxbl));
1323 ival = 0, lval = 0L, uval = 0U, ulval = 0UL;
1324 anytype = bl->anytype;
1325 if (maxbl->a.a_void) {
1329 result = ((100 * ival) / maxbl->a.a_int);
1332 lval = bl->a.a_long;
1333 result = (int) ((100L * lval) / maxbl->a.a_long);
1336 uval = bl->a.a_uint;
1337 result = (int) ((100U * uval) / maxbl->a.a_uint);
1340 ulval = bl->a.a_ulong;
1341 result = (int) ((100UL * ulval) / maxbl->a.a_ulong);
1344 ival = *bl->a.a_iptr;
1345 result = ((100 * ival) / (*maxbl->a.a_iptr));
1348 lval = *bl->a.a_lptr;
1349 result = (int) ((100L * lval) / (*maxbl->a.a_lptr));
1352 uval = *bl->a.a_uptr;
1353 result = (int) ((100U * uval) / (*maxbl->a.a_uptr));
1356 ulval = *bl->a.a_ulptr;
1357 result = (int) ((100UL * ulval) / (*maxbl->a.a_ulptr));
1361 /* don't let truncation from integer division produce a zero result
1362 from a non-zero input; note: if we ever change to something like
1363 ((((1000 * val) / max) + 5) / 10) for a rounded result, we'll
1364 also need to check for and convert false 100 to 99 */
1365 if (result == 0 && (ival != 0 || lval != 0L || uval != 0U || ulval != 0UL))
1371 /* callback so that interface can get capacity index rather than trying
1372 to reconstruct that from the encumbrance string or asking the general
1373 core what the value is */
1379 #ifdef STATUS_HILITES
1380 cap = blstats[now_or_before_idx][BL_CAP].a.a_int;
1382 cap = near_capacity();
1387 /* callback so that interface can get hunger index rather than trying to
1388 reconstruct that from the hunger string or dipping into core internals */
1394 #ifdef STATUS_HILITES
1395 uhs = blstats[now_or_before_idx][BL_HUNGER].a.a_int;
1402 /* used by X11 for "tty status" even when STATUS_HILITES is disabled */
1404 bl_idx_to_fldname(idx)
1407 if (idx >= 0 && idx < MAXBLSTATS)
1408 return initblstats[idx].fldname;
1409 return (const char *) 0;
1412 #ifdef STATUS_HILITES
1414 /****************************************************************************/
1415 /* Core status hiliting support */
1416 /****************************************************************************/
1418 struct hilite_s status_hilites[MAXBLSTATS];
1420 static struct fieldid_t {
1421 const char *fieldname;
1422 enum statusfields fldid;
1423 } fieldids_alias[] = {
1424 { "characteristics", BL_CHARACTERISTICS },
1425 { "encumbrance", BL_CAP },
1426 { "experience-points", BL_EXP },
1430 { "points", BL_SCORE },
1433 { "pw-max", BL_ENEMAX },
1437 { "hit-dice", BL_HD },
1438 { "turns", BL_TIME },
1440 { "hp-max", BL_HPMAX },
1441 { "dgn", BL_LEVELDESC },
1444 { "flags", BL_CONDITION },
1448 /* format arguments */
1449 static const char threshold_value[] = "hilite_status threshold ",
1450 is_out_of_range[] = " is out of range";
1453 /* field name to bottom line index */
1454 STATIC_OVL enum statusfields
1455 fldname_to_bl_indx(name)
1458 int i, nmatches = 0, fld = 0;
1460 if (name && *name) {
1461 /* check matches to canonical names */
1462 for (i = 0; i < SIZE(initblstats); i++)
1463 if (fuzzymatch(initblstats[i].fldname, name, " -_", TRUE)) {
1464 fld = initblstats[i].fld;
1470 for (i = 0; fieldids_alias[i].fieldname; i++)
1471 if (fuzzymatch(fieldids_alias[i].fieldname, name,
1473 fld = fieldids_alias[i].fldid;
1479 /* check partial matches to canonical names */
1480 int len = (int) strlen(name);
1482 for (i = 0; i < SIZE(initblstats); i++)
1483 if (!strncmpi(name, initblstats[i].fldname, len)) {
1484 fld = initblstats[i].fld;
1490 return (nmatches == 1) ? fld : BL_FLUSH;
1494 hilite_reset_needed(bl_p, augmented_time)
1495 struct istat_s *bl_p;
1496 long augmented_time; /* no longer augmented; it once encoded fractional
1497 * amounts for multiple moves within same turn */
1500 * This 'multi' handling may need some tuning...
1505 if (!Is_Temp_Hilite(bl_p->hilite_rule))
1508 if (bl_p->time == 0 || bl_p->time >= augmented_time)
1514 /* called from moveloop(); sets context.botl if temp hilites have timed out */
1516 status_eval_next_unhilite()
1519 struct istat_s *curr;
1520 long next_unhilite, this_unhilite;
1522 bl_hilite_moves = moves; /* simpllfied; used to try to encode fractional
1523 * amounts for multiple moves within same turn */
1524 /* figure out whether an unhilight needs to be performed now */
1526 for (i = 0; i < MAXBLSTATS; ++i) {
1527 curr = &blstats[0][i]; /* blstats[0][*].time == blstats[1][*].time */
1530 struct istat_s *prev = &blstats[1][i];
1532 if (Is_Temp_Hilite(curr->hilite_rule))
1533 curr->time = prev->time = (bl_hilite_moves
1534 + iflags.hilite_delta);
1536 curr->time = prev->time = 0L;
1538 curr->chg = prev->chg = FALSE;
1539 context.botl = TRUE;
1542 continue; /* just process other blstats[][].time and .chg */
1544 this_unhilite = curr->time;
1545 if (this_unhilite > 0L
1546 && (next_unhilite == 0L || this_unhilite < next_unhilite)
1547 && hilite_reset_needed(curr, this_unhilite + 1L)) {
1548 next_unhilite = this_unhilite;
1549 if (next_unhilite < bl_hilite_moves)
1550 context.botl = TRUE;
1555 /* called by options handling when 'statushilites' value is changed */
1557 reset_status_hilites()
1559 if (iflags.hilite_delta) {
1562 for (i = 0; i < MAXBLSTATS; ++i)
1563 blstats[0][i].time = blstats[1][i].time = 0L;
1566 context.botlx = TRUE;
1569 /* test whether the text from a title rule matches the string for
1570 title-while-polymorphed in the 'textmatch' menu */
1572 noneoftheabove(hl_text)
1573 const char *hl_text;
1575 if (fuzzymatch(hl_text, "none of the above", "\" -_", TRUE)
1576 || fuzzymatch(hl_text, "(polymorphed)", "\"()", TRUE)
1577 || fuzzymatch(hl_text, "none of the above (polymorphed)",
1586 * Returns, based on the value and the direction it is moving,
1587 * the highlight rule that applies to the specified field.
1589 * Provide get_hilite() with the following to work with:
1591 * useful for BL_TH_VAL_ABSOLUTE
1592 * indicator of down, up, or the same (-1, 1, 0) chg
1593 * useful for BL_TH_UPDOWN or change detection
1594 * percentage (current value percentage of max value) pc
1595 * useful for BL_TH_VAL_PERCENTAGE
1598 * pointer to rule that applies; Null if no rule does.
1600 STATIC_OVL struct hilite_s *
1601 get_hilite(idx, fldidx, vp, chg, pc, colorptr)
1602 int idx, fldidx, chg, pc;
1606 struct hilite_s *hl, *rule = 0;
1607 anything *value = (anything *) vp;
1610 if (fldidx < 0 || fldidx >= MAXBLSTATS)
1611 return (struct hilite_s *) 0;
1613 if (has_hilite(fldidx)) {
1615 /* there are hilites set here */
1616 int max_pc = -1, min_pc = 101;
1617 /* LARGEST_INT isn't INT_MAX; it fits within 16 bits, but that
1618 value is big enough to handle all 'int' status fields */
1619 int max_ival = -LARGEST_INT, min_ival = LARGEST_INT;
1620 /* LONG_MAX comes from <limits.h> which might not be available for
1621 ancient configurations; we don't need LONG_MIN */
1622 long max_lval = -LONG_MAX, min_lval = LONG_MAX;
1623 boolean exactmatch = FALSE, updown = FALSE, changed = FALSE,
1624 perc_or_abs = FALSE;
1626 /* min_/max_ are used to track best fit */
1627 for (hl = blstats[0][fldidx].thresholds; hl; hl = hl->next) {
1628 dt = initblstats[fldidx].anytype; /* only needed for 'absolute' */
1629 /* if we've already matched a temporary highlight, it takes
1630 precedence over all persistent ones; we still process
1631 updown rules to get the last one which qualifies */
1632 if ((updown || changed) && hl->behavior != BL_TH_UPDOWN)
1634 /* among persistent highlights, if a 'percentage' or 'absolute'
1635 rule has been matched, it takes precedence over 'always' */
1636 if (perc_or_abs && hl->behavior == BL_TH_ALWAYS_HILITE)
1639 switch (hl->behavior) {
1640 case BL_TH_VAL_PERCENTAGE: /* percent values are always ANY_INT */
1641 if (hl->rel == EQ_VALUE && pc == hl->value.a_int) {
1643 min_pc = max_pc = hl->value.a_int;
1644 exactmatch = perc_or_abs = TRUE;
1645 } else if (exactmatch) {
1646 ; /* already found best fit, skip lt,ge,&c */
1647 } else if (hl->rel == LT_VALUE
1648 && (pc < hl->value.a_int)
1649 && (hl->value.a_int <= min_pc)) {
1651 min_pc = hl->value.a_int;
1653 } else if (hl->rel == LE_VALUE
1654 && (pc <= hl->value.a_int)
1655 && (hl->value.a_int <= min_pc)) {
1657 min_pc = hl->value.a_int;
1659 } else if (hl->rel == GT_VALUE
1660 && (pc > hl->value.a_int)
1661 && (hl->value.a_int >= max_pc)) {
1663 max_pc = hl->value.a_int;
1665 } else if (hl->rel == GE_VALUE
1666 && (pc >= hl->value.a_int)
1667 && (hl->value.a_int >= max_pc)) {
1669 max_pc = hl->value.a_int;
1673 case BL_TH_UPDOWN: /* uses 'chg' (set by caller), not 'dt' */
1674 /* specific 'up' or 'down' takes precedence over general
1675 'changed' regardless of their order in the rule set */
1676 if (chg < 0 && hl->rel == LT_VALUE) {
1679 } else if (chg > 0 && hl->rel == GT_VALUE) {
1682 } else if (chg != 0 && hl->rel == EQ_VALUE && !updown) {
1687 case BL_TH_VAL_ABSOLUTE: /* either ANY_INT or ANY_LONG */
1689 * The int and long variations here are identical aside from
1690 * union field and min_/max_ variable names. If you change
1691 * one, be sure to make a corresponding change in the other.
1693 if (dt == ANY_INT) {
1694 if (hl->rel == EQ_VALUE
1695 && hl->value.a_int == value->a_int) {
1697 min_ival = max_ival = hl->value.a_int;
1698 exactmatch = perc_or_abs = TRUE;
1699 } else if (exactmatch) {
1700 ; /* already found best fit, skip lt,ge,&c */
1701 } else if (hl->rel == LT_VALUE
1702 && (value->a_int < hl->value.a_int)
1703 && (hl->value.a_int <= min_ival)) {
1705 min_ival = hl->value.a_int;
1707 } else if (hl->rel == LE_VALUE
1708 && (value->a_int <= hl->value.a_int)
1709 && (hl->value.a_int <= min_ival)) {
1711 min_ival = hl->value.a_int;
1713 } else if (hl->rel == GT_VALUE
1714 && (value->a_int > hl->value.a_int)
1715 && (hl->value.a_int >= max_ival)) {
1717 max_ival = hl->value.a_int;
1719 } else if (hl->rel == GE_VALUE
1720 && (value->a_int >= hl->value.a_int)
1721 && (hl->value.a_int >= max_ival)) {
1723 max_ival = hl->value.a_int;
1726 } else { /* ANY_LONG */
1727 if (hl->rel == EQ_VALUE
1728 && hl->value.a_long == value->a_long) {
1730 min_lval = max_lval = hl->value.a_long;
1731 exactmatch = perc_or_abs = TRUE;
1732 } else if (exactmatch) {
1733 ; /* already found best fit, skip lt,ge,&c */
1734 } else if (hl->rel == LT_VALUE
1735 && (value->a_long < hl->value.a_long)
1736 && (hl->value.a_long <= min_lval)) {
1738 min_lval = hl->value.a_long;
1740 } else if (hl->rel == LE_VALUE
1741 && (value->a_long <= hl->value.a_long)
1742 && (hl->value.a_long <= min_lval)) {
1744 min_lval = hl->value.a_long;
1746 } else if (hl->rel == GT_VALUE
1747 && (value->a_long > hl->value.a_long)
1748 && (hl->value.a_long >= max_lval)) {
1750 max_lval = hl->value.a_long;
1752 } else if (hl->rel == GE_VALUE
1753 && (value->a_long >= hl->value.a_long)
1754 && (hl->value.a_long >= max_lval)) {
1756 max_lval = hl->value.a_long;
1761 case BL_TH_TEXTMATCH: /* ANY_STR */
1762 txtstr = blstats[idx][fldidx].val;
1763 if (fldidx == BL_TITLE)
1764 /* "<name> the <rank-title>", skip past "<name> the " */
1765 txtstr += (strlen(plname) + sizeof " the " - sizeof "");
1766 if (hl->rel == TXT_VALUE && hl->textmatch[0]) {
1767 if (fuzzymatch(hl->textmatch, txtstr, "\" -_", TRUE)) {
1770 } else if (exactmatch) {
1771 ; /* already found best fit, skip "noneoftheabove" */
1772 } else if (fldidx == BL_TITLE
1773 && Upolyd && noneoftheabove(hl->textmatch)) {
1778 case BL_TH_ALWAYS_HILITE:
1788 *colorptr = rule ? rule->coloridx : NO_COLOR;
1793 split_clridx(idx, coloridx, attrib)
1795 int *coloridx, *attrib;
1798 *coloridx = idx & 0x00FF;
1800 *attrib = (idx >> 8) & 0x00FF;
1804 * This is the parser for the hilite options.
1806 * parse_status_hl1() separates each hilite entry into
1807 * a set of field threshold/action component strings,
1808 * then calls parse_status_hl2() to parse further
1809 * and configure the hilite.
1812 parse_status_hl1(op, from_configfile)
1814 boolean from_configfile;
1816 #define MAX_THRESH 21
1817 char hsbuf[MAX_THRESH][QBUFSZ];
1818 boolean rslt, badopt = FALSE;
1819 int i, fldnum, ccount = 0;
1823 for (i = 0; i < MAX_THRESH; ++i) {
1826 while (*op && fldnum < MAX_THRESH && ccount < (QBUFSZ - 2)) {
1830 if (fldnum == 1 && strcmpi(hsbuf[0], "title") == 0) {
1831 /* spaces are allowed in title */
1832 hsbuf[fldnum][ccount++] = c;
1833 hsbuf[fldnum][ccount] = '\0';
1837 rslt = parse_status_hl2(hsbuf, from_configfile);
1843 for (i = 0; i < MAX_THRESH; ++i) {
1848 } else if (c == '/') {
1852 hsbuf[fldnum][ccount++] = c;
1853 hsbuf[fldnum][ccount] = '\0';
1857 if (fldnum >= 1 && !badopt) {
1858 rslt = parse_status_hl2(hsbuf, from_configfile);
1867 /* is str in the format of "[<>]?=?[-+]?[0-9]+%?" regex */
1869 is_ltgt_percentnumber(str)
1872 const char *s = str;
1874 if (*s == '<' || *s == '>')
1878 if (*s == '-' || *s == '+')
1886 return (*s == '\0');
1889 /* does str only contain "<>=-+0-9%" chars */
1891 has_ltgt_percentnumber(str)
1894 const char *s = str;
1897 if (!index("<>=-+0123456789%", *s))
1904 /* splitsubfields(): splits str in place into '+' or '&' separated strings.
1905 * returns number of strings, or -1 if more than maxsf or MAX_SUBFIELDS
1907 #define MAX_SUBFIELDS 16
1909 splitsubfields(str, sfarr, maxsf)
1914 static char *subfields[MAX_SUBFIELDS];
1915 char *st = (char *) 0;
1920 for (sf = 0; sf < MAX_SUBFIELDS; ++sf)
1921 subfields[sf] = (char *) 0;
1923 maxsf = (maxsf == 0) ? MAX_SUBFIELDS : min(maxsf, MAX_SUBFIELDS);
1925 if (index(str, '+') || index(str, '&')) {
1930 while (*c && sf < maxsf) {
1931 if (*c == '&' || *c == '+') {
1939 if (sf >= maxsf - 1)
1942 subfields[sf++] = st;
1950 #undef MAX_SUBFIELDS
1953 is_fld_arrayvalues(str, arr, arrmin, arrmax, retidx)
1955 const char *const *arr;
1961 for (i = arrmin; i < arrmax; i++)
1962 if (!strcmpi(str, arr[i])) {
1970 query_arrayvalue(querystr, arr, arrmin, arrmax)
1971 const char *querystr;
1972 const char *const *arr;
1975 int i, res, ret = arrmin - 1;
1978 menu_item *picks = (menu_item *) 0;
1979 int adj = (arrmin > 0) ? 1 : arrmax;
1981 tmpwin = create_nhwindow(NHW_MENU);
1984 for (i = arrmin; i < arrmax; i++) {
1986 any.a_int = i + adj;
1987 add_menu(tmpwin, NO_GLYPH, &any, 0, 0, ATR_NONE,
1988 arr[i], MENU_UNSELECTED);
1991 end_menu(tmpwin, querystr);
1993 res = select_menu(tmpwin, PICK_ONE, &picks);
1994 destroy_nhwindow(tmpwin);
1996 ret = picks->item.a_int - adj;
1997 free((genericptr_t) picks);
2004 status_hilite_add_threshold(fld, hilite)
2006 struct hilite_s *hilite;
2008 struct hilite_s *new_hilite;
2013 /* alloc and initialize a new hilite_s struct */
2014 new_hilite = (struct hilite_s *) alloc(sizeof (struct hilite_s));
2015 *new_hilite = *hilite; /* copy struct */
2017 new_hilite->set = TRUE;
2018 new_hilite->fld = fld;
2019 new_hilite->next = blstats[0][fld].thresholds;
2020 blstats[0][fld].thresholds = new_hilite;
2021 /* sort_hilites(fld) */
2023 /* current and prev must both point at the same hilites */
2024 blstats[1][fld].thresholds = blstats[0][fld].thresholds;
2029 parse_status_hl2(s, from_configfile)
2031 boolean from_configfile;
2034 int sidx = 0, i = -1, dt = -1;
2035 int coloridx = -1, successes = 0;
2036 int disp_attrib = 0;
2037 boolean percent, changed, numeric, down, up,
2038 gt, lt, ge, le, eq, txtval, always;
2040 enum statusfields fld = BL_FLUSH;
2041 struct hilite_s hilite;
2043 static const char *aligntxt[] = { "chaotic", "neutral", "lawful" };
2044 /* hu_stat[] from eat.c has trailing spaces which foul up comparisons */
2045 static const char *hutxt[] = { "Satiated", "", "Hungry", "Weak",
2046 "Fainting", "Fainted", "Starved" };
2050 OPTION=hilite_status: hitpoints/<10%/red
2051 OPTION=hilite_status: hitpoints/<10%/red/<5%/purple/1/red+blink+inverse
2052 OPTION=hilite_status: experience/down/red/up/green
2053 OPTION=hilite_status: cap/strained/yellow/overtaxed/orange
2054 OPTION=hilite_status: title/always/blue
2055 OPTION=hilite_status: title/blue
2058 /* field name to statusfield */
2059 fld = fldname_to_bl_indx(s[sidx]);
2061 if (fld == BL_CHARACTERISTICS) {
2062 boolean res = FALSE;
2064 /* recursively set each of strength, dexterity, constitution, &c */
2065 for (fld = BL_STR; fld <= BL_CH; fld++) {
2066 Strcpy(s[sidx], initblstats[fld].fldname);
2067 res = parse_status_hl2(s, from_configfile);
2073 if (fld == BL_FLUSH) {
2074 config_error_add("Unknown status field '%s'", s[sidx]);
2077 if (fld == BL_CONDITION)
2078 return parse_condition(s, sidx);
2082 char buf[BUFSZ], **subfields;
2083 int sf = 0; /* subfield count */
2086 txt = (const char *)0;
2087 percent = numeric = always = FALSE;
2088 down = up = changed = FALSE;
2089 gt = ge = eq = le = lt = txtval = FALSE;
2091 /* threshold value */
2095 memset((genericptr_t) &hilite, 0, sizeof (struct hilite_s));
2096 hilite.set = FALSE; /* mark it "unset" */
2099 if (*s[sidx + 1] == '\0' || !strcmpi(s[sidx], "always")) {
2100 /* "field/always/color" OR "field/color" */
2102 if (*s[sidx + 1] == '\0')
2104 } else if (!strcmpi(s[sidx], "up") || !strcmpi(s[sidx], "down")) {
2105 if (initblstats[fld].anytype == ANY_STR)
2106 /* ordered string comparison is supported but LT/GT for
2107 the string fields (title, dungeon-level, alignment)
2108 is pointless; treat 'up' or 'down' for string fields
2109 as 'changed' rather than rejecting them outright */
2111 else if (!strcmpi(s[sidx], "down"))
2116 } else if (fld == BL_CAP
2117 && is_fld_arrayvalues(s[sidx], enc_stat,
2118 SLT_ENCUMBER, OVERLOADED + 1,
2120 txt = enc_stat[kidx];
2122 } else if (fld == BL_ALIGN
2123 && is_fld_arrayvalues(s[sidx], aligntxt, 0, 3, &kidx)) {
2124 txt = aligntxt[kidx];
2126 } else if (fld == BL_HUNGER
2127 && is_fld_arrayvalues(s[sidx], hutxt,
2128 SATIATED, STARVED + 1, &kidx)) {
2129 txt = hu_stat[kidx]; /* store hu_stat[] val, not hutxt[] */
2131 } else if (!strcmpi(s[sidx], "changed")) {
2133 } else if (is_ltgt_percentnumber(s[sidx])) {
2136 tmp = s[sidx]; /* is_ltgt_() guarantees [<>]?=?[-+]?[0-9]+%? */
2137 if (strchr(tmp, '%'))
2144 } else if (*tmp == '>') {
2150 /* '%', '<', '>' have served their purpose, '=' is either
2151 part of '<' or '>' or optional for '=N', unary '+' is
2152 just decorative, so get rid of them, leaving -?[0-9]+ */
2153 tmp = stripchars(tmpbuf, "%<>=+", tmp);
2155 dt = percent ? ANY_INT : initblstats[fld].anytype;
2156 (void) s_to_anything(&hilite.value, tmp, dt);
2158 op = gt ? ">" : ge ? ">=" : lt ? "<" : le ? "<=" : "=";
2160 /* AC is the only field where negative values make sense but
2161 accept >-1 for other fields; reject <0 for non-AC */
2162 && (hilite.value.a_int
2163 < ((fld == BL_AC) ? -128 : gt ? -1 : lt ? 1 : 0)
2164 /* percentages have another more comprehensive check below */
2165 || hilite.value.a_int > (percent ? (lt ? 101 : 100)
2167 config_error_add("%s'%s%d%s'%s", threshold_value,
2168 op, hilite.value.a_int, percent ? "%" : "",
2171 } else if (dt == ANY_LONG
2172 && (hilite.value.a_long < (gt ? -1L : lt ? 1L : 0L))) {
2173 config_error_add("%s'%s%ld'%s", threshold_value,
2174 op, hilite.value.a_long, is_out_of_range);
2177 } else if (initblstats[fld].anytype == ANY_STR) {
2181 config_error_add(has_ltgt_percentnumber(s[sidx])
2182 ? "Wrong format '%s', expected a threshold number or percent"
2183 : "Unknown behavior '%s'",
2188 /* relationships {LT_VALUE, LE_VALUE, EQ_VALUE, GE_VALUE, GT_VALUE} */
2190 hilite.rel = GT_VALUE;
2191 else if (lt || down)
2192 hilite.rel = LT_VALUE;
2194 hilite.rel = GE_VALUE;
2196 hilite.rel = LE_VALUE;
2197 else if (eq || percent || numeric || changed)
2198 hilite.rel = EQ_VALUE;
2200 hilite.rel = TXT_VALUE;
2202 hilite.rel = LT_VALUE;
2204 if (initblstats[fld].anytype == ANY_STR && (percent || numeric)) {
2205 config_error_add("Field '%s' does not support numeric values",
2206 initblstats[fld].fldname);
2211 if (initblstats[fld].idxmax < 0) {
2212 config_error_add("Cannot use percent with '%s'",
2213 initblstats[fld].fldname);
2215 } else if ((hilite.value.a_int < -1)
2216 || (hilite.value.a_int == -1
2217 && hilite.value.a_int != GT_VALUE)
2218 || (hilite.value.a_int == 0
2219 && hilite.rel == LT_VALUE)
2220 || (hilite.value.a_int == 100
2221 && hilite.rel == GT_VALUE)
2222 || (hilite.value.a_int == 101
2223 && hilite.value.a_int != LT_VALUE)
2224 || (hilite.value.a_int > 101)) {
2226 "hilite_status: invalid percentage value '%s%d%%'",
2227 (hilite.rel == LT_VALUE) ? "<"
2228 : (hilite.rel == LE_VALUE) ? "<="
2229 : (hilite.rel == GT_VALUE) ? ">"
2230 : (hilite.rel == GE_VALUE) ? ">="
2232 hilite.value.a_int);
2246 sf = splitsubfields(buf, &subfields, 0);
2251 disp_attrib = HL_UNDEF;
2253 for (i = 0; i < sf; ++i) {
2254 int a = match_str2attr(subfields[i], FALSE);
2257 disp_attrib |= HL_DIM;
2258 else if (a == ATR_BLINK)
2259 disp_attrib |= HL_BLINK;
2260 else if (a == ATR_ULINE)
2261 disp_attrib |= HL_ULINE;
2262 else if (a == ATR_INVERSE)
2263 disp_attrib |= HL_INVERSE;
2264 else if (a == ATR_BOLD)
2265 disp_attrib |= HL_BOLD;
2266 else if (a == ATR_NONE)
2267 disp_attrib = HL_NONE;
2269 int c = match_str2clr(subfields[i]);
2271 if (c >= CLR_MAX || coloridx != -1)
2277 coloridx = NO_COLOR;
2279 /* Assign the values */
2280 hilite.coloridx = coloridx | (disp_attrib << 8);
2283 hilite.behavior = BL_TH_ALWAYS_HILITE;
2285 hilite.behavior = BL_TH_VAL_PERCENTAGE;
2287 hilite.behavior = BL_TH_UPDOWN;
2289 hilite.behavior = BL_TH_VAL_ABSOLUTE;
2291 hilite.behavior = BL_TH_TEXTMATCH;
2292 else if (hilite.value.a_void)
2293 hilite.behavior = BL_TH_VAL_ABSOLUTE;
2295 hilite.behavior = BL_TH_NONE;
2297 hilite.anytype = dt;
2299 if (hilite.behavior == BL_TH_TEXTMATCH && txt) {
2300 (void) strncpy(hilite.textmatch, txt, sizeof hilite.textmatch);
2301 hilite.textmatch[sizeof hilite.textmatch - 1] = '\0';
2302 (void) trimspaces(hilite.textmatch);
2305 status_hilite_add_threshold(fld, &hilite);
2313 #endif /* STATUS_HILITES */
2315 const struct condmap valid_conditions[] = {
2316 { "stone", BL_MASK_STONE },
2317 { "slime", BL_MASK_SLIME },
2318 { "strngl", BL_MASK_STRNGL },
2319 { "foodPois", BL_MASK_FOODPOIS },
2320 { "termIll", BL_MASK_TERMILL },
2321 { "blind", BL_MASK_BLIND },
2322 { "deaf", BL_MASK_DEAF },
2323 { "stun", BL_MASK_STUN },
2324 { "conf", BL_MASK_CONF },
2325 { "hallu", BL_MASK_HALLU },
2326 { "lev", BL_MASK_LEV },
2327 { "fly", BL_MASK_FLY },
2328 { "ride", BL_MASK_RIDE },
2331 #ifdef STATUS_HILITES
2333 const struct condmap condition_aliases[] = {
2334 { "strangled", BL_MASK_STRNGL },
2335 { "all", BL_MASK_STONE | BL_MASK_SLIME | BL_MASK_STRNGL
2336 | BL_MASK_FOODPOIS | BL_MASK_TERMILL
2337 | BL_MASK_BLIND | BL_MASK_DEAF | BL_MASK_STUN
2338 | BL_MASK_CONF | BL_MASK_HALLU
2339 | BL_MASK_LEV | BL_MASK_FLY | BL_MASK_RIDE },
2340 { "major_troubles", BL_MASK_STONE | BL_MASK_SLIME | BL_MASK_STRNGL
2341 | BL_MASK_FOODPOIS | BL_MASK_TERMILL },
2342 { "minor_troubles", BL_MASK_BLIND | BL_MASK_DEAF | BL_MASK_STUN
2343 | BL_MASK_CONF | BL_MASK_HALLU },
2344 { "movement", BL_MASK_LEV | BL_MASK_FLY | BL_MASK_RIDE }
2351 unsigned long ret = 0UL;
2354 menu_item *picks = (menu_item *) 0;
2356 tmpwin = create_nhwindow(NHW_MENU);
2359 for (i = 0; i < SIZE(valid_conditions); i++) {
2361 any.a_ulong = valid_conditions[i].bitmask;
2362 add_menu(tmpwin, NO_GLYPH, &any, 0, 0, ATR_NONE,
2363 valid_conditions[i].id, MENU_UNSELECTED);
2366 end_menu(tmpwin, "Choose status conditions");
2368 res = select_menu(tmpwin, PICK_ANY, &picks);
2369 destroy_nhwindow(tmpwin);
2371 for (i = 0; i < res; i++)
2372 ret |= picks[i].item.a_ulong;
2373 free((genericptr_t) picks);
2379 conditionbitmask2str(ul)
2382 static char buf[BUFSZ];
2384 boolean first = TRUE;
2385 const char *alias = (char *) 0;
2392 for (i = 1; i < SIZE(condition_aliases); i++)
2393 if (condition_aliases[i].bitmask == ul)
2394 alias = condition_aliases[i].id;
2396 for (i = 0; i < SIZE(valid_conditions); i++)
2397 if ((valid_conditions[i].bitmask & ul) != 0UL) {
2398 Sprintf(eos(buf), "%s%s", (first) ? "" : "+",
2399 valid_conditions[i].id);
2403 if (!first && alias)
2404 Sprintf(buf, "%s", alias);
2409 STATIC_OVL unsigned long
2410 match_str2conditionbitmask(str)
2413 int i, nmatches = 0;
2414 unsigned long mask = 0UL;
2417 /* check matches to canonical names */
2418 for (i = 0; i < SIZE(valid_conditions); i++)
2419 if (fuzzymatch(valid_conditions[i].id, str, " -_", TRUE)) {
2420 mask |= valid_conditions[i].bitmask;
2426 for (i = 0; i < SIZE(condition_aliases); i++)
2427 if (fuzzymatch(condition_aliases[i].id, str, " -_", TRUE)) {
2428 mask |= condition_aliases[i].bitmask;
2434 /* check partial matches to aliases */
2435 int len = (int) strlen(str);
2437 for (i = 0; i < SIZE(condition_aliases); i++)
2438 if (!strncmpi(str, condition_aliases[i].id, len)) {
2439 mask |= condition_aliases[i].bitmask;
2448 STATIC_OVL unsigned long
2449 str2conditionbitmask(str)
2452 unsigned long conditions_bitmask = 0UL;
2456 sf = splitsubfields(str, &subfields, SIZE(valid_conditions));
2461 for (i = 0; i < sf; ++i) {
2462 unsigned long bm = match_str2conditionbitmask(subfields[i]);
2465 config_error_add("Unknown condition '%s'", subfields[i]);
2468 conditions_bitmask |= bm;
2470 return conditions_bitmask;
2474 parse_condition(s, sidx)
2479 int coloridx = NO_COLOR;
2481 unsigned long conditions_bitmask = 0UL;
2482 boolean success = FALSE;
2488 OPTION=hilite_status: condition/stone+slime+foodPois/red&inverse */
2492 * It would be simpler to treat each condition (also hunger state
2493 * and encumbrance level) as if it were a separate field. That
2494 * way they could have either or both 'changed' temporary rule and
2495 * 'always' persistent rule and wouldn't need convoluted access to
2496 * the intended color and attributes.
2501 int sf = 0; /* subfield count */
2502 char buf[BUFSZ], **subfields;
2507 config_error_add("Missing condition(s)");
2512 conditions_bitmask = str2conditionbitmask(buf);
2514 if (!conditions_bitmask)
2518 * We have the conditions_bitmask with bits set for
2519 * each ailment we want in a particular color and/or
2520 * attribute, but we need to assign it to an array of
2521 * bitmasks indexed by the color chosen
2522 * (0 to (CLR_MAX - 1))
2523 * and/or attributes chosen
2524 * (HL_ATTCLR_DIM to (BL_ATTCLR_MAX - 1))
2525 * We still have to parse the colors and attributes out.
2531 if (!how || !*how) {
2532 config_error_add("Missing color+attribute");
2537 sf = splitsubfields(buf, &subfields, 0);
2540 * conditions_bitmask now has bits set representing
2541 * the conditions that player wants represented, but
2542 * now we parse out *how* they will be represented.
2544 * Only 1 colour is allowed, but potentially multiple
2545 * attributes are allowed.
2547 * We have the following additional array offsets to
2548 * use for storing the attributes beyond the end of
2549 * the color indexes, all of which are less than CLR_MAX.
2550 * HL_ATTCLR_DIM = CLR_MAX
2551 * HL_ATTCLR_BLINK = CLR_MAX + 1
2552 * HL_ATTCLR_ULINE = CLR_MAX + 2
2553 * HL_ATTCLR_INVERSE = CLR_MAX + 3
2554 * HL_ATTCLR_BOLD = CLR_MAX + 4
2555 * HL_ATTCLR_MAX = CLR_MAX + 5 (this is past array boundary)
2559 for (i = 0; i < sf; ++i) {
2560 int a = match_str2attr(subfields[i], FALSE);
2563 cond_hilites[HL_ATTCLR_DIM] |= conditions_bitmask;
2564 else if (a == ATR_BLINK)
2565 cond_hilites[HL_ATTCLR_BLINK] |= conditions_bitmask;
2566 else if (a == ATR_ULINE)
2567 cond_hilites[HL_ATTCLR_ULINE] |= conditions_bitmask;
2568 else if (a == ATR_INVERSE)
2569 cond_hilites[HL_ATTCLR_INVERSE] |= conditions_bitmask;
2570 else if (a == ATR_BOLD)
2571 cond_hilites[HL_ATTCLR_BOLD] |= conditions_bitmask;
2572 else if (a == ATR_NONE) {
2573 cond_hilites[HL_ATTCLR_DIM] &= ~conditions_bitmask;
2574 cond_hilites[HL_ATTCLR_BLINK] &= ~conditions_bitmask;
2575 cond_hilites[HL_ATTCLR_ULINE] &= ~conditions_bitmask;
2576 cond_hilites[HL_ATTCLR_INVERSE] &= ~conditions_bitmask;
2577 cond_hilites[HL_ATTCLR_BOLD] &= ~conditions_bitmask;
2579 int k = match_str2clr(subfields[i]);
2586 /* set the bits in the appropriate member of the
2587 condition array according to color chosen as index */
2589 cond_hilites[coloridx] |= conditions_bitmask;
2597 clear_status_hilites()
2601 for (i = 0; i < MAXBLSTATS; ++i) {
2602 struct hilite_s *temp, *next;
2604 for (temp = blstats[0][i].thresholds; temp; temp = next) {
2608 blstats[0][i].thresholds = blstats[1][i].thresholds = 0;
2609 /* pointer into thresholds list, now stale */
2610 blstats[0][i].hilite_rule = blstats[1][i].hilite_rule = 0;
2615 hlattr2attrname(attrib, buf, bufsz)
2619 if (attrib && buf) {
2624 if (attrib == HL_NONE) {
2625 Strcpy(buf, "normal");
2629 if (attrib & HL_BOLD)
2630 Strcat(attbuf, first++ ? "+bold" : "bold");
2631 if (attrib & HL_INVERSE)
2632 Strcat(attbuf, first++ ? "+inverse" : "inverse");
2633 if (attrib & HL_ULINE)
2634 Strcat(attbuf, first++ ? "+underline" : "underline");
2635 if (attrib & HL_BLINK)
2636 Strcat(attbuf, first++ ? "+blink" : "blink");
2637 if (attrib & HL_DIM)
2638 Strcat(attbuf, first++ ? "+dim" : "dim");
2641 if (k < (bufsz - 1))
2642 Strcpy(buf, attbuf);
2649 struct _status_hilite_line_str {
2652 struct hilite_s *hl;
2655 struct _status_hilite_line_str *next;
2658 static struct _status_hilite_line_str *status_hilite_str = 0;
2659 static int status_hilite_str_id = 0;
2662 status_hilite_linestr_add(fld, hl, mask, str)
2664 struct hilite_s *hl;
2668 struct _status_hilite_line_str *tmp, *nxt;
2670 tmp = (struct _status_hilite_line_str *) alloc(sizeof *tmp);
2671 (void) memset(tmp, 0, sizeof *tmp);
2672 tmp->next = (struct _status_hilite_line_str *) 0;
2674 tmp->id = ++status_hilite_str_id;
2678 if (fld == BL_TITLE)
2679 Strcpy(tmp->str, str);
2681 (void) stripchars(tmp->str, " ", str);
2683 if ((nxt = status_hilite_str) != 0) {
2688 status_hilite_str = tmp;
2693 status_hilite_linestr_done()
2695 struct _status_hilite_line_str *nxt, *tmp = status_hilite_str;
2702 status_hilite_str = (struct _status_hilite_line_str *) 0;
2703 status_hilite_str_id = 0;
2707 status_hilite_linestr_countfield(fld)
2710 struct _status_hilite_line_str *tmp;
2711 boolean countall = (fld == BL_FLUSH);
2714 for (tmp = status_hilite_str; tmp; tmp = tmp->next) {
2715 if (countall || tmp->fld == fld)
2721 /* used by options handling, doset(options.c) */
2723 count_status_hilites(VOID_ARGS)
2727 status_hilite_linestr_gather();
2728 count = status_hilite_linestr_countfield(BL_FLUSH);
2729 status_hilite_linestr_done();
2734 status_hilite_linestr_gather_conditions()
2739 unsigned long clratr;
2740 } cond_maps[SIZE(valid_conditions)];
2742 (void) memset(cond_maps, 0,
2743 SIZE(valid_conditions) * sizeof (struct _cond_map));
2745 for (i = 0; i < SIZE(valid_conditions); i++) {
2750 for (j = 0; j < CLR_MAX; j++)
2751 if (cond_hilites[j] & valid_conditions[i].bitmask) {
2755 if (cond_hilites[HL_ATTCLR_DIM] & valid_conditions[i].bitmask)
2757 if (cond_hilites[HL_ATTCLR_BOLD] & valid_conditions[i].bitmask)
2759 if (cond_hilites[HL_ATTCLR_BLINK] & valid_conditions[i].bitmask)
2761 if (cond_hilites[HL_ATTCLR_ULINE] & valid_conditions[i].bitmask)
2763 if (cond_hilites[HL_ATTCLR_INVERSE] & valid_conditions[i].bitmask)
2768 if (clr != NO_COLOR || atr != HL_NONE) {
2769 unsigned long ca = clr | (atr << 8);
2770 boolean added_condmap = FALSE;
2772 for (j = 0; j < SIZE(valid_conditions); j++)
2773 if (cond_maps[j].clratr == ca) {
2774 cond_maps[j].bm |= valid_conditions[i].bitmask;
2775 added_condmap = TRUE;
2778 if (!added_condmap) {
2779 for (j = 0; j < SIZE(valid_conditions); j++)
2780 if (!cond_maps[j].bm) {
2781 cond_maps[j].bm = valid_conditions[i].bitmask;
2782 cond_maps[j].clratr = ca;
2789 for (i = 0; i < SIZE(valid_conditions); i++)
2790 if (cond_maps[i].bm) {
2791 int clr = NO_COLOR, atr = HL_NONE;
2793 split_clridx(cond_maps[i].clratr, &clr, &atr);
2794 if (clr != NO_COLOR || atr != HL_NONE) {
2796 char attrbuf[BUFSZ];
2797 char condbuf[BUFSZ];
2800 (void) strNsubst(strcpy(clrbuf, clr2colorname(clr)),
2802 tmpattr = hlattr2attrname(atr, attrbuf, BUFSZ);
2804 Sprintf(eos(clrbuf), "&%s", tmpattr);
2805 Sprintf(condbuf, "condition/%s/%s",
2806 conditionbitmask2str(cond_maps[i].bm), clrbuf);
2807 status_hilite_linestr_add(BL_CONDITION, 0,
2808 cond_maps[i].bm, condbuf);
2814 status_hilite_linestr_gather()
2817 struct hilite_s *hl;
2819 status_hilite_linestr_done();
2821 for (i = 0; i < MAXBLSTATS; i++) {
2822 hl = blstats[0][i].thresholds;
2824 status_hilite_linestr_add(i, hl, 0UL, status_hilite2str(hl));
2829 status_hilite_linestr_gather_conditions();
2834 status_hilite2str(hl)
2835 struct hilite_s *hl;
2837 static char buf[BUFSZ];
2838 int clr = 0, attr = 0;
2839 char behavebuf[BUFSZ];
2841 char attrbuf[BUFSZ];
2848 behavebuf[0] = '\0';
2850 op = (hl->rel == LT_VALUE) ? "<"
2851 : (hl->rel == LE_VALUE) ? "<="
2852 : (hl->rel == GT_VALUE) ? ">"
2853 : (hl->rel == GE_VALUE) ? ">="
2854 : (hl->rel == EQ_VALUE) ? "="
2857 switch (hl->behavior) {
2858 case BL_TH_VAL_PERCENTAGE:
2860 Sprintf(behavebuf, "%s%d%%", op, hl->value.a_int);
2862 impossible("hl->behavior=percentage, rel error");
2865 if (hl->rel == LT_VALUE)
2866 Sprintf(behavebuf, "down");
2867 else if (hl->rel == GT_VALUE)
2868 Sprintf(behavebuf, "up");
2869 else if (hl->rel == EQ_VALUE)
2870 Sprintf(behavebuf, "changed");
2872 impossible("hl->behavior=updown, rel error");
2874 case BL_TH_VAL_ABSOLUTE:
2876 Sprintf(behavebuf, "%s%d", op, hl->value.a_int);
2878 impossible("hl->behavior=absolute, rel error");
2880 case BL_TH_TEXTMATCH:
2881 if (hl->rel == TXT_VALUE && hl->textmatch[0])
2882 Sprintf(behavebuf, "%s", hl->textmatch);
2884 impossible("hl->behavior=textmatch, rel or textmatch error");
2886 case BL_TH_CONDITION:
2887 if (hl->rel == EQ_VALUE)
2888 Sprintf(behavebuf, "%s", conditionbitmask2str(hl->value.a_ulong));
2890 impossible("hl->behavior=condition, rel error");
2892 case BL_TH_ALWAYS_HILITE:
2893 Sprintf(behavebuf, "always");
2901 split_clridx(hl->coloridx, &clr, &attr);
2902 (void) strNsubst(strcpy(clrbuf, clr2colorname(clr)), " ", "-", 0);
2903 if (attr != HL_UNDEF) {
2904 if ((tmpattr = hlattr2attrname(attr, attrbuf, BUFSZ)) != 0)
2905 Sprintf(eos(clrbuf), "&%s", tmpattr);
2907 Sprintf(buf, "%s/%s/%s", initblstats[hl->fld].fldname, behavebuf, clrbuf);
2913 status_hilite_menu_choose_field()
2916 int i, res, fld = BL_FLUSH;
2918 menu_item *picks = (menu_item *) 0;
2920 tmpwin = create_nhwindow(NHW_MENU);
2923 for (i = 0; i < MAXBLSTATS; i++) {
2924 #ifndef SCORE_ON_BOTL
2925 if (initblstats[i].fld == BL_SCORE
2926 && !blstats[0][BL_SCORE].thresholds)
2930 any.a_int = (i + 1);
2931 add_menu(tmpwin, NO_GLYPH, &any, 0, 0, ATR_NONE,
2932 initblstats[i].fldname, MENU_UNSELECTED);
2935 end_menu(tmpwin, "Select a hilite field:");
2937 res = select_menu(tmpwin, PICK_ONE, &picks);
2938 destroy_nhwindow(tmpwin);
2940 fld = picks->item.a_int - 1;
2941 free((genericptr_t) picks);
2947 status_hilite_menu_choose_behavior(fld)
2951 int res = 0, beh = BL_TH_NONE-1;
2953 menu_item *picks = (menu_item *) 0;
2956 int onlybeh = BL_TH_NONE, nopts = 0;
2958 if (fld < 0 || fld >= MAXBLSTATS)
2961 at = initblstats[fld].anytype;
2963 tmpwin = create_nhwindow(NHW_MENU);
2966 if (fld != BL_CONDITION) {
2968 any.a_int = onlybeh = BL_TH_ALWAYS_HILITE;
2969 Sprintf(buf, "Always highlight %s", initblstats[fld].fldname);
2970 add_menu(tmpwin, NO_GLYPH, &any, 'a', 0, ATR_NONE,
2971 buf, MENU_UNSELECTED);
2975 if (fld == BL_CONDITION) {
2977 any.a_int = onlybeh = BL_TH_CONDITION;
2978 add_menu(tmpwin, NO_GLYPH, &any, 'b', 0, ATR_NONE,
2979 "Bitmask of conditions", MENU_UNSELECTED);
2983 if (fld != BL_CONDITION) {
2985 any.a_int = onlybeh = BL_TH_UPDOWN;
2986 Sprintf(buf, "%s value changes", initblstats[fld].fldname);
2987 add_menu(tmpwin, NO_GLYPH, &any, 'c', 0, ATR_NONE,
2988 buf, MENU_UNSELECTED);
2992 if (fld != BL_CAP && fld != BL_HUNGER
2993 && (at == ANY_INT || at == ANY_LONG)) {
2995 any.a_int = onlybeh = BL_TH_VAL_ABSOLUTE;
2996 add_menu(tmpwin, NO_GLYPH, &any, 'n', 0, ATR_NONE,
2997 "Number threshold", MENU_UNSELECTED);
3001 if (initblstats[fld].idxmax >= 0) {
3003 any.a_int = onlybeh = BL_TH_VAL_PERCENTAGE;
3004 add_menu(tmpwin, NO_GLYPH, &any, 'p', 0, ATR_NONE,
3005 "Percentage threshold", MENU_UNSELECTED);
3009 if (initblstats[fld].anytype == ANY_STR
3010 || fld == BL_CAP || fld == BL_HUNGER) {
3012 any.a_int = onlybeh = BL_TH_TEXTMATCH;
3013 Sprintf(buf, "%s text match", initblstats[fld].fldname);
3014 add_menu(tmpwin, NO_GLYPH, &any, 't', 0, ATR_NONE,
3015 buf, MENU_UNSELECTED);
3019 Sprintf(buf, "Select %s field hilite behavior:", initblstats[fld].fldname);
3020 end_menu(tmpwin, buf);
3023 res = select_menu(tmpwin, PICK_ONE, &picks);
3024 if (res == 0) /* none chosen*/
3026 else if (res == -1) /* menu cancelled */
3027 beh = (BL_TH_NONE - 1);
3028 } else if (onlybeh != BL_TH_NONE)
3030 destroy_nhwindow(tmpwin);
3032 beh = picks->item.a_int;
3033 free((genericptr_t) picks);
3039 status_hilite_menu_choose_updownboth(fld, str, ltok, gtok)
3044 int res, ret = NO_LTEQGT;
3048 menu_item *picks = (menu_item *) 0;
3050 tmpwin = create_nhwindow(NHW_MENU);
3055 Sprintf(buf, "%s than %s",
3056 (fld == BL_AC) ? "Better (lower)" : "Less", str);
3058 Sprintf(buf, "Value goes down");
3060 any.a_int = 10 + LT_VALUE;
3061 add_menu(tmpwin, NO_GLYPH, &any, 0, 0, ATR_NONE,
3062 buf, MENU_UNSELECTED);
3065 Sprintf(buf, "%s or %s",
3066 str, (fld == BL_AC) ? "better (lower)" : "less");
3068 any.a_int = 10 + LE_VALUE;
3069 add_menu(tmpwin, NO_GLYPH, &any, 0, 0, ATR_NONE,
3070 buf, MENU_UNSELECTED);
3075 Sprintf(buf, "Exactly %s", str);
3077 Sprintf(buf, "Value changes");
3079 any.a_int = 10 + EQ_VALUE;
3080 add_menu(tmpwin, NO_GLYPH, &any, 0, 0, ATR_NONE,
3081 buf, MENU_UNSELECTED);
3085 Sprintf(buf, "%s or %s",
3086 str, (fld == BL_AC) ? "worse (higher)" : "more");
3088 any.a_int = 10 + GE_VALUE;
3089 add_menu(tmpwin, NO_GLYPH, &any, 0, 0, ATR_NONE,
3090 buf, MENU_UNSELECTED);
3094 Sprintf(buf, "%s than %s",
3095 (fld == BL_AC) ? "Worse (higher)" : "More", str);
3097 Sprintf(buf, "Value goes up");
3099 any.a_int = 10 + GT_VALUE;
3100 add_menu(tmpwin, NO_GLYPH, &any, 0, 0, ATR_NONE,
3101 buf, MENU_UNSELECTED);
3103 Sprintf(buf, "Select field %s value:", initblstats[fld].fldname);
3104 end_menu(tmpwin, buf);
3106 res = select_menu(tmpwin, PICK_ONE, &picks);
3107 destroy_nhwindow(tmpwin);
3109 ret = picks->item.a_int - 10;
3110 free((genericptr_t) picks);
3117 status_hilite_menu_add(origfld)
3123 int clr = NO_COLOR, atr = HL_UNDEF;
3124 struct hilite_s hilite;
3125 unsigned long cond = 0UL;
3126 char colorqry[BUFSZ];
3127 char attrqry[BUFSZ];
3131 if (fld == BL_FLUSH) {
3132 fld = status_hilite_menu_choose_field();
3133 /* isn't this redundant given what follows? */
3134 if (fld == BL_FLUSH)
3138 if (fld == BL_FLUSH)
3144 memset((genericptr_t) &hilite, 0, sizeof (struct hilite_s));
3145 hilite.next = (struct hilite_s *) 0;
3146 hilite.set = FALSE; /* mark it "unset" */
3150 behavior = status_hilite_menu_choose_behavior(fld);
3152 if (behavior == (BL_TH_NONE - 1)) {
3154 } else if (behavior == BL_TH_NONE) {
3155 if (origfld == BL_FLUSH)
3160 hilite.behavior = behavior;
3163 if (behavior == BL_TH_VAL_PERCENTAGE
3164 || behavior == BL_TH_VAL_ABSOLUTE) {
3165 char inbuf[BUFSZ], buf[BUFSZ];
3168 boolean gotnum = FALSE, percent = (behavior == BL_TH_VAL_PERCENTAGE);
3169 char *inp, *numstart;
3172 lt_gt_eq = NO_LTEQGT; /* not set up yet */
3174 Sprintf(buf, "Enter %svalue for %s threshold:",
3175 percent ? "percentage " : "",
3176 initblstats[fld].fldname);
3178 if (inbuf[0] == '\0' || inbuf[0] == '\033')
3179 goto choose_behavior;
3181 inp = numstart = trimspaces(inbuf);
3183 goto choose_behavior;
3185 /* allow user to enter "<50%" or ">50" or just "50"
3186 or <=50% or >=50 or =50 */
3187 if (*inp == '>' || *inp == '<' || *inp == '=') {
3188 lt_gt_eq = (*inp == '>') ? ((inp[1] == '=') ? GE_VALUE : GT_VALUE)
3189 : (*inp == '<') ? ((inp[1] == '=') ? LE_VALUE : LT_VALUE)
3193 if (lt_gt_eq == GE_VALUE || lt_gt_eq == LE_VALUE) {
3200 } else if (*inp == '+') {
3204 while (digit(*inp)) {
3210 pline("Not expecting a percentage.");
3211 goto choose_behavior;
3213 *inp = '\0'; /* strip '%' [this accepts trailing junk!] */
3215 /* some random characters */
3216 pline("\"%s\" is not a recognized number.", inp);
3220 pline("Is that an invisible number?");
3223 op = (lt_gt_eq == LT_VALUE) ? "<"
3224 : (lt_gt_eq == LE_VALUE) ? "<="
3225 : (lt_gt_eq == GT_VALUE) ? ">"
3226 : (lt_gt_eq == GE_VALUE) ? ">="
3227 : (lt_gt_eq == EQ_VALUE) ? "="
3228 : ""; /* didn't specify lt_gt_eq with number */
3231 dt = percent ? ANY_INT : initblstats[fld].anytype;
3232 (void) s_to_anything(&aval, numstart, dt);
3236 if (initblstats[fld].idxmax == -1) {
3237 pline("Field '%s' does not support percentage values.",
3238 initblstats[fld].fldname);
3239 behavior = BL_TH_VAL_ABSOLUTE;
3242 /* if player only specified a number then lt_gt_eq isn't set
3243 up yet and the >-1 and <101 exceptions can't be honored;
3244 deliberate use of those should be uncommon enough for
3245 that to be palatable; for 0 and 100, choose_updown_both()
3246 will prevent useless operations */
3247 if ((val < 0 && (val != -1 || lt_gt_eq != GT_VALUE))
3248 || (val == 0 && lt_gt_eq == LT_VALUE)
3249 || (val == 100 && lt_gt_eq == GT_VALUE)
3250 || (val > 100 && (val != 101 || lt_gt_eq != LT_VALUE))) {
3251 pline("'%s%d%%' is not a valid percent value.", op, val);
3254 /* restore suffix for use in color and attribute prompts */
3255 if (!index(numstart, '%'))
3256 Strcat(numstart, "%");
3258 /* reject negative values except for AC and >-1; reject 0 for < */
3259 } else if (dt == ANY_INT
3260 && (aval.a_int < ((fld == BL_AC) ? -128
3261 : (lt_gt_eq == GT_VALUE) ? -1
3262 : (lt_gt_eq == LT_VALUE) ? 1 : 0))) {
3263 pline("%s'%s%d'%s", threshold_value,
3264 op, aval.a_int, is_out_of_range);
3266 } else if (dt == ANY_LONG
3267 && (aval.a_long < ((lt_gt_eq == GT_VALUE) ? -1L
3268 : (lt_gt_eq == LT_VALUE) ? 1L : 0L))) {
3269 pline("%s'%s%ld'%s", threshold_value,
3270 op, aval.a_long, is_out_of_range);
3274 if (lt_gt_eq == NO_LTEQGT) {
3275 boolean ltok = ((dt == ANY_INT)
3276 ? (aval.a_int > 0 || fld == BL_AC)
3277 : (aval.a_long > 0L)),
3278 gtok = (!percent || aval.a_long < 100);
3280 lt_gt_eq = status_hilite_menu_choose_updownboth(fld, inbuf,
3282 if (lt_gt_eq == NO_LTEQGT)
3286 Sprintf(colorqry, "Choose a color for when %s is %s%s%s:",
3287 initblstats[fld].fldname,
3288 (lt_gt_eq == LT_VALUE) ? "less than "
3289 : (lt_gt_eq == GT_VALUE) ? "more than "
3292 (lt_gt_eq == LE_VALUE) ? " or less"
3293 : (lt_gt_eq == GE_VALUE) ? " or more"
3295 Sprintf(attrqry, "Choose attribute for when %s is %s%s%s:",
3296 initblstats[fld].fldname,
3297 (lt_gt_eq == LT_VALUE) ? "less than "
3298 : (lt_gt_eq == GT_VALUE) ? "more than "
3301 (lt_gt_eq == LE_VALUE) ? " or less"
3302 : (lt_gt_eq == GE_VALUE) ? " or more"
3305 hilite.rel = lt_gt_eq;
3306 hilite.value = aval;
3307 } else if (behavior == BL_TH_UPDOWN) {
3308 if (initblstats[fld].anytype != ANY_STR) {
3309 boolean ltok = (fld != BL_TIME), gtok = TRUE;
3311 lt_gt_eq = status_hilite_menu_choose_updownboth(fld, (char *)0,
3313 if (lt_gt_eq == NO_LTEQGT)
3314 goto choose_behavior;
3315 } else { /* ANY_STR */
3316 /* player picked '<field> value changes' in outer menu;
3317 ordered string comparison is supported but LT/GT for the
3318 string status fields (title, dungeon level, alignment)
3319 is pointless; rather than calling ..._choose_updownboth()
3320 with ltok==False plus gtok=False and having a menu with a
3321 single choice, skip it altogether and just use 'changed' */
3322 lt_gt_eq = EQ_VALUE;
3324 Sprintf(colorqry, "Choose a color for when %s %s:",
3325 initblstats[fld].fldname,
3326 (lt_gt_eq == EQ_VALUE) ? "changes"
3327 : (lt_gt_eq == LT_VALUE) ? "decreases"
3329 Sprintf(attrqry, "Choose attribute for when %s %s:",
3330 initblstats[fld].fldname,
3331 (lt_gt_eq == EQ_VALUE) ? "changes"
3332 : (lt_gt_eq == LT_VALUE) ? "decreases"
3334 hilite.rel = lt_gt_eq;
3335 } else if (behavior == BL_TH_CONDITION) {
3336 cond = query_conditions();
3338 if (origfld == BL_FLUSH)
3342 Sprintf(colorqry, "Choose a color for conditions %s:",
3343 conditionbitmask2str(cond));
3344 Sprintf(attrqry, "Choose attribute for conditions %s:",
3345 conditionbitmask2str(cond));
3346 } else if (behavior == BL_TH_TEXTMATCH) {
3347 char qry_buf[BUFSZ];
3349 Sprintf(qry_buf, "%s %s text value to match:",
3353 || fld == BL_TITLE) ? "Choose" : "Enter",
3354 initblstats[fld].fldname);
3355 if (fld == BL_CAP) {
3356 int rv = query_arrayvalue(qry_buf,
3358 SLT_ENCUMBER, OVERLOADED + 1);
3360 if (rv < SLT_ENCUMBER)
3361 goto choose_behavior;
3363 hilite.rel = TXT_VALUE;
3364 Strcpy(hilite.textmatch, enc_stat[rv]);
3365 } else if (fld == BL_ALIGN) {
3366 static const char *aligntxt[] = { "chaotic", "neutral", "lawful" };
3367 int rv = query_arrayvalue(qry_buf,
3368 aligntxt, 0, 2 + 1);
3371 goto choose_behavior;
3373 hilite.rel = TXT_VALUE;
3374 Strcpy(hilite.textmatch, aligntxt[rv]);
3375 } else if (fld == BL_HUNGER) {
3376 static const char *hutxt[] = { "Satiated", (char *) 0, "Hungry",
3377 "Weak", "Fainting", "Fainted",
3379 int rv = query_arrayvalue(qry_buf,
3381 SATIATED, STARVED + 1);
3384 goto choose_behavior;
3386 hilite.rel = TXT_VALUE;
3387 Strcpy(hilite.textmatch, hutxt[rv]);
3388 } else if (fld == BL_TITLE) {
3389 const char *rolelist[3 * 9 + 1];
3390 char mbuf[MAXVALWIDTH], fbuf[MAXVALWIDTH], obuf[MAXVALWIDTH];
3393 for (i = j = 0; i < 9; i++) {
3394 Sprintf(mbuf, "\"%s\"", urole.rank[i].m);
3395 if (urole.rank[i].f) {
3396 Sprintf(fbuf, "\"%s\"", urole.rank[i].f);
3397 Sprintf(obuf, "%s or %s",
3398 flags.female ? fbuf : mbuf,
3399 flags.female ? mbuf : fbuf);
3401 fbuf[0] = obuf[0] = '\0';
3405 rolelist[j++] = dupstr(fbuf);
3406 rolelist[j++] = dupstr(mbuf);
3408 rolelist[j++] = dupstr(obuf);
3410 rolelist[j++] = dupstr(mbuf);
3412 rolelist[j++] = dupstr(fbuf);
3414 rolelist[j++] = dupstr(obuf);
3417 rolelist[j++] = dupstr("\"none of the above (polymorphed)\"");
3419 rv = query_arrayvalue(qry_buf, rolelist, 0, j);
3421 hilite.rel = TXT_VALUE;
3422 Strcpy(hilite.textmatch, rolelist[rv]);
3424 for (i = 0; i < j; i++)
3425 free((genericptr_t) rolelist[i]), rolelist[i] = 0;
3427 goto choose_behavior;
3432 getlin(qry_buf, inbuf);
3433 if (inbuf[0] == '\0' || inbuf[0] == '\033')
3434 goto choose_behavior;
3436 hilite.rel = TXT_VALUE;
3437 if (strlen(inbuf) < sizeof hilite.textmatch)
3438 Strcpy(hilite.textmatch, inbuf);
3442 Sprintf(colorqry, "Choose a color for when %s is '%s':",
3443 initblstats[fld].fldname, hilite.textmatch);
3444 Sprintf(attrqry, "Choose attribute for when %s is '%s':",
3445 initblstats[fld].fldname, hilite.textmatch);
3446 } else if (behavior == BL_TH_ALWAYS_HILITE) {
3447 Sprintf(colorqry, "Choose a color to always hilite %s:",
3448 initblstats[fld].fldname);
3449 Sprintf(attrqry, "Choose attribute to always hilite %s:",
3450 initblstats[fld].fldname);
3454 clr = query_color(colorqry);
3456 if (behavior != BL_TH_ALWAYS_HILITE)
3459 goto choose_behavior;
3461 atr = query_attr(attrqry);
3465 if (behavior == BL_TH_CONDITION) {
3467 char attrbuf[BUFSZ];
3471 cond_hilites[HL_ATTCLR_DIM] |= cond;
3473 cond_hilites[HL_ATTCLR_BLINK] |= cond;
3475 cond_hilites[HL_ATTCLR_ULINE] |= cond;
3476 if (atr & HL_INVERSE)
3477 cond_hilites[HL_ATTCLR_INVERSE] |= cond;
3479 cond_hilites[HL_ATTCLR_BOLD] |= cond;
3480 if (atr == HL_NONE) {
3481 cond_hilites[HL_ATTCLR_DIM] &= ~cond;
3482 cond_hilites[HL_ATTCLR_BLINK] &= ~cond;
3483 cond_hilites[HL_ATTCLR_ULINE] &= ~cond;
3484 cond_hilites[HL_ATTCLR_INVERSE] &= ~cond;
3485 cond_hilites[HL_ATTCLR_BOLD] &= ~cond;
3487 cond_hilites[clr] |= cond;
3488 (void) strNsubst(strcpy(clrbuf, clr2colorname(clr)), " ", "-", 0);
3489 tmpattr = hlattr2attrname(atr, attrbuf, BUFSZ);
3491 Sprintf(eos(clrbuf), "&%s", tmpattr);
3492 pline("Added hilite condition/%s/%s",
3493 conditionbitmask2str(cond), clrbuf);
3497 hilite.coloridx = clr | (atr << 8);
3498 hilite.anytype = initblstats[fld].anytype;
3500 if (fld == BL_TITLE && (p = strstri(hilite.textmatch, " or ")) != 0) {
3501 /* split menu choice "male-rank or female-rank" into two distinct
3502 but otherwise identical rules, "male-rank" and "female-rank" */
3503 *p = '\0'; /* chop off " or female-rank" */
3504 /* new rule for male-rank */
3505 status_hilite_add_threshold(fld, &hilite);
3506 pline("Added hilite %s", status_hilite2str(&hilite));
3507 /* transfer female-rank to start of hilite.textmatch buffer */
3508 p += sizeof " or " - sizeof "";
3509 q = hilite.textmatch;
3510 while ((*q++ = *p++) != '\0')
3512 /* proceed with normal addition of new rule */
3514 status_hilite_add_threshold(fld, &hilite);
3515 pline("Added hilite %s", status_hilite2str(&hilite));
3517 reset_status_hilites();
3522 status_hilite_remove(id)
3525 struct _status_hilite_line_str *hlstr = status_hilite_str;
3527 while (hlstr && hlstr->id != id) {
3528 hlstr = hlstr->next;
3534 if (hlstr->fld == BL_CONDITION) {
3537 for (i = 0; i < CLR_MAX; i++)
3538 cond_hilites[i] &= ~hlstr->mask;
3539 cond_hilites[HL_ATTCLR_DIM] &= ~hlstr->mask;
3540 cond_hilites[HL_ATTCLR_BOLD] &= ~hlstr->mask;
3541 cond_hilites[HL_ATTCLR_BLINK] &= ~hlstr->mask;
3542 cond_hilites[HL_ATTCLR_ULINE] &= ~hlstr->mask;
3543 cond_hilites[HL_ATTCLR_INVERSE] &= ~hlstr->mask;
3546 int fld = hlstr->fld;
3547 struct hilite_s *hl, *hlprev = (struct hilite_s *) 0;
3549 for (hl = blstats[0][fld].thresholds; hl; hl = hl->next) {
3550 if (hlstr->hl == hl) {
3552 hlprev->next = hl->next;
3554 blstats[0][fld].thresholds = hl->next;
3555 blstats[1][fld].thresholds = blstats[0][fld].thresholds;
3557 if (blstats[0][fld].hilite_rule == hl) {
3558 blstats[0][fld].hilite_rule
3559 = blstats[1][fld].hilite_rule = (struct hilite_s *) 0;
3560 blstats[0][fld].time = blstats[1][fld].time = 0L;
3562 free((genericptr_t) hl);
3572 status_hilite_menu_fld(fld)
3577 menu_item *picks = (menu_item *) 0;
3579 int count = status_hilite_linestr_countfield(fld);
3580 struct _status_hilite_line_str *hlstr;
3582 boolean acted = FALSE;
3585 if (status_hilite_menu_add(fld)) {
3586 status_hilite_linestr_done();
3587 status_hilite_linestr_gather();
3588 count = status_hilite_linestr_countfield(fld);
3593 tmpwin = create_nhwindow(NHW_MENU);
3597 hlstr = status_hilite_str;
3599 if (hlstr->fld == fld) {
3601 any.a_int = hlstr->id;
3602 add_menu(tmpwin, NO_GLYPH, &any, 0, 0, ATR_NONE,
3603 hlstr->str, MENU_UNSELECTED);
3605 hlstr = hlstr->next;
3609 Sprintf(buf, "No current hilites for %s", initblstats[fld].fldname);
3610 add_menu(tmpwin, NO_GLYPH, &any, 0, 0, ATR_NONE, buf, MENU_UNSELECTED);
3613 /* separator line */
3615 add_menu(tmpwin, NO_GLYPH, &any, 0, 0, ATR_NONE, "", MENU_UNSELECTED);
3620 add_menu(tmpwin, NO_GLYPH, &any, 'X', 0, ATR_NONE,
3621 "Remove selected hilites", MENU_UNSELECTED);
3624 #ifndef SCORE_ON_BOTL
3625 if (fld == BL_SCORE) {
3626 /* suppress 'Z - Add a new hilite' for 'score' when SCORE_ON_BOTL
3627 is disabled; we wouldn't be called for 'score' unless it has
3628 hilite rules from the config file, so count must be positive
3629 (hence there's no risk that we're putting up an empty menu) */
3636 add_menu(tmpwin, NO_GLYPH, &any, 'Z', 0, ATR_NONE,
3637 "Add a new hilite", MENU_UNSELECTED);
3640 Sprintf(buf, "Current %s hilites:", initblstats[fld].fldname);
3641 end_menu(tmpwin, buf);
3643 if ((res = select_menu(tmpwin, PICK_ANY, &picks)) > 0) {
3646 for (i = 0; i < res; i++) {
3647 int idx = picks[i].item.a_int;
3650 /* delete selected hilites */
3655 } else if (idx == -2) {
3656 /* create a new hilite */
3665 /* delete selected hilites */
3666 for (i = 0; i < res; i++) {
3667 int idx = picks[i].item.a_int;
3670 (void) status_hilite_remove(idx);
3672 reset_status_hilites();
3674 } else if (mode == -2) {
3675 /* create a new hilite */
3676 if (status_hilite_menu_add(fld))
3680 free((genericptr_t) picks);
3685 picks = (menu_item *) 0;
3686 destroy_nhwindow(tmpwin);
3691 status_hilites_viewall()
3694 struct _status_hilite_line_str *hlstr = status_hilite_str;
3697 datawin = create_nhwindow(NHW_TEXT);
3700 Sprintf(buf, "OPTIONS=hilite_status: %.*s",
3701 (int) (BUFSZ - sizeof "OPTIONS=hilite_status: " - 1),
3703 putstr(datawin, 0, buf);
3704 hlstr = hlstr->next;
3707 display_nhwindow(datawin, FALSE);
3708 destroy_nhwindow(datawin);
3712 status_hilite_menu()
3716 menu_item *picks = (menu_item *) 0;
3724 tmpwin = create_nhwindow(NHW_MENU);
3727 status_hilite_linestr_gather();
3728 countall = status_hilite_linestr_countfield(BL_FLUSH);
3732 add_menu(tmpwin, NO_GLYPH, &any, 0, 0, ATR_NONE,
3733 "View all hilites in config format", MENU_UNSELECTED);
3736 add_menu(tmpwin, NO_GLYPH, &any, 0, 0, ATR_NONE, "", MENU_UNSELECTED);
3739 for (i = 0; i < MAXBLSTATS; i++) {
3740 int count = status_hilite_linestr_countfield(i);
3743 #ifndef SCORE_ON_BOTL
3744 /* config file might contain rules for highlighting 'score'
3745 even when SCORE_ON_BOTL is disabled; if so, 'O' command
3746 menus will show them and allow deletions but not additions,
3747 otherwise, it won't show 'score' at all */
3748 if (initblstats[i].fld == BL_SCORE && !count)
3753 Sprintf(buf, "%-18s", initblstats[i].fldname);
3755 Sprintf(eos(buf), " (%d defined)", count);
3756 add_menu(tmpwin, NO_GLYPH, &any, 0, 0, ATR_NONE,
3757 buf, MENU_UNSELECTED);
3760 end_menu(tmpwin, "Status hilites:");
3761 if ((res = select_menu(tmpwin, PICK_ONE, &picks)) > 0) {
3762 i = picks->item.a_int - 1;
3764 status_hilites_viewall();
3766 (void) status_hilite_menu_fld(i);
3767 free((genericptr_t) picks), picks = (menu_item *) 0;
3771 destroy_nhwindow(tmpwin);
3772 countall = status_hilite_linestr_countfield(BL_FLUSH);
3773 status_hilite_linestr_done();
3778 /* hilite_delta=='statushilites' does double duty: it is the
3779 number of turns for temporary highlights to remain visible
3780 and also when non-zero it is the flag to enable highlighting */
3781 if (countall > 0 && !iflags.hilite_delta)
3783 "To have highlights become active, set 'statushilites' option to non-zero.");
3788 #endif /* STATUS_HILITES */