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. */
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 /*
\83I
\83v
\83V
\83\87\83\93\82Ì
\83p
\81[
\83X
\82Å
\89p
\8cê
\94Å
\82à
\95K
\97v*/
23 const char *const enc_stat[] = { "", "
\82æ
\82ë
\82ß
\82«", "
\88³
\94\97",
24 "
\8cÀ
\8aE", "
\89×
\8fd", "
\92´
\89ß"};
25 const char *const enc_stat_opt[] = { "", "Burdened", "Stressed",
26 "Strained", "Overtaxed", "Overloaded" };
29 STATIC_OVL NEARDATA int mrank_sz = 0; /* loaded by max_rank_sz (from u_init) */
30 STATIC_DCL const char *NDECL(rank);
31 STATIC_DCL void NDECL(bot_via_windowport);
32 STATIC_DCL void NDECL(stat_update_time);
38 int st = ACURR(A_STR);
42 Sprintf(buf, "%2d", st - 100);
43 else if (st < STR18(100))
44 Sprintf(buf, "18/%02d", st - 18);
46 Sprintf(buf, "18/**");
48 Sprintf(buf, "%-1d", st);
56 nhsym goldch = showsyms[COIN_CLASS + SYM_OFF_O];
58 iflags.invis_goldsym = (goldch <= (nhsym) ' ');
64 static char newbot1[BUFSZ];
68 Strcpy(newbot1, plname);
69 if ('a' <= newbot1[0] && newbot1[0] <= 'z')
70 newbot1[0] += 'A' - 'a';
72 if(is_kanji1(newbot1, 9))
77 Sprintf(nb = eos(newbot1), " the ");
79 Sprintf(nb = eos(newbot1), " ");
86 Strcpy(mbot, mons[u.umonnum].mname);
87 while (mbot[k] != 0) {
88 if ((k == 0 || (k > 0 && mbot[k - 1] == ' ')) && 'a' <= mbot[k]
93 Strcpy(nb = eos(nb), mbot);
95 Strcpy(nb = eos(nb), rank());
97 Sprintf(nb = eos(nb), " ");
99 j = (int) ((nb + 2) - newbot1); /* strlen(newbot1) but less computation */
101 Sprintf(nb = eos(nb), "%*s", i - j, " "); /* pad with spaces */
104 Sprintf(nb = eos(nb), "St:%s Dx:%-1d Co:%-1d In:%-1d Wi:%-1d Ch:%-1d",
106 ACURR(A_DEX), ACURR(A_CON), ACURR(A_INT), ACURR(A_WIS),
109 Sprintf(nb = eos(nb), "
\8b:%s
\91\81:%-1d
\91Ï:%-1d
\92m:%-1d
\8c«:%-1d
\96£:%-1d ",
111 ACURR(A_DEX), ACURR(A_CON), ACURR(A_INT), ACURR(A_WIS),
115 Sprintf(nb = eos(nb),
116 (u.ualign.type == A_CHAOTIC)
118 : (u.ualign.type == A_NEUTRAL) ? " Neutral" : " Lawful");
120 Sprintf(nb = eos(nb),
121 (u.ualign.type == A_CHAOTIC)
123 : (u.ualign.type == A_NEUTRAL) ? "
\92\86\97§" : "
\92\81\8f\98");
128 Sprintf(nb = eos(nb), " S:%ld", botl_score());
130 Sprintf(nb = eos(nb), "%ld
\93_", botl_score());
138 static char newbot2[BUFSZ], /* MAXCO: botl.h */
139 /* dungeon location (and gold), hero health (HP, PW, AC),
140 experience (HD if poly'd, else Exp level and maybe Exp points),
141 time (in moves), varying number of status conditions */
142 dloc[QBUFSZ], hlth[QBUFSZ], expr[QBUFSZ], tmmv[QBUFSZ], cond[QBUFSZ];
144 unsigned dln, dx, hln, xln, tln, cln;
149 * Various min(x,9999)'s are to avoid having excessive values
150 * violate the field width assumptions in botl.h and should not
151 * impact normal play. Particularly 64-bit long for gold which
152 * could require many more digits if someone figures out a way
153 * to get and carry a really large (or negative) amount of it.
154 * Turn counter is also long, but we'll risk that.
157 /* dungeon location plus gold */
158 (void) describe_level(dloc); /* includes at least one trailing space */
159 if ((money = money_cnt(invent)) < 0L)
160 money = 0L; /* ought to issue impossible() and then discard gold */
161 Sprintf(eos(dloc), "%s:%-2ld", /* strongest hero can lift ~300000 gold */
162 (iflags.in_dumplog || iflags.invis_goldsym) ? "$"
163 : encglyph(objnum_to_glyph(GOLD_PIECE)),
164 min(money, 999999L));
166 /* '$' encoded as \GXXXXNNNN is 9 chars longer than display will need */
167 dx = strstri(dloc, "\\G") ? 9 : 0;
169 /* health and armor class (has trailing space for AC 0..9) */
170 hp = Upolyd ? u.mh : u.uhp;
171 hpmax = Upolyd ? u.mhmax : u.uhpmax;
175 Sprintf(hlth, "HP:%d(%d) Pw:%d(%d) AC:%-2d",
176 min(hp, 9999), min(hpmax, 9999),
177 min(u.uen, 9999), min(u.uenmax, 9999), u.uac);
179 Sprintf(hlth, "
\91Ì:%d(%d)
\96\82:%d(%d)
\8aZ:%-2d",
180 min(hp, 9999), min(hpmax, 9999),
181 min(u.uen, 9999), min(u.uenmax, 9999), u.uac);
187 Sprintf(expr, "HD:%d", mons[u.umonnum].mlevel);
188 else if (flags.showexp)
190 Sprintf(expr, "Xp:%d/%-1ld", u.ulevel, u.uexp);
192 Sprintf(expr, "
\8co
\8c±:%d/%-1ld", u.ulevel, u.uexp);
195 Sprintf(expr, "Exp:%d", u.ulevel);
197 Sprintf(expr, "
\8co
\8c±:%d", u.ulevel);
200 /* time/move counter */
203 Sprintf(tmmv, "T:%ld", moves);
205 Sprintf(tmmv, "
\95à:%ld", moves);
210 /* status conditions; worst ones first */
211 cond[0] = '\0'; /* once non-empty, cond will have a leading space */
214 * Stoned, Slimed, Strangled, and both types of Sick are all fatal
215 * unless remedied before timeout expires. Should we order them by
216 * shortest time left? [Probably not worth the effort, since it's
217 * unusual for more than one of them to apply at a time.]
221 Strcpy(nb = eos(nb), " Stone");
223 Strcpy(nb = eos(nb), "
\90Î
\89»");
226 Strcpy(nb = eos(nb), " Slime");
228 Strcpy(nb = eos(nb), "
\82Ç
\82ë
\82Ç
\82ë");
231 Strcpy(nb = eos(nb), " Strngl");
233 Strcpy(nb = eos(nb), "
\92\82\91§");
235 if (u.usick_type & SICK_VOMITABLE)
237 Strcpy(nb = eos(nb), " FoodPois");
239 Strcpy(nb = eos(nb), "
\90H
\93Å");
240 if (u.usick_type & SICK_NONVOMITABLE)
242 Strcpy(nb = eos(nb), " TermIll");
244 Strcpy(nb = eos(nb), "
\95a
\8bC");
246 if (u.uhs != NOT_HUNGRY)
247 Sprintf(nb = eos(nb), " %s", hu_stat[u.uhs]);
248 if ((cap = near_capacity()) > UNENCUMBERED)
249 Sprintf(nb = eos(nb), " %s", enc_stat[cap]);
252 Strcpy(nb = eos(nb), " Blind");
254 Strcpy(nb = eos(nb), "
\96Ó
\96Ú");
257 Strcpy(nb = eos(nb), " Deaf");
259 Strcpy(nb = eos(nb), "
\8e¨
\98W");
262 Strcpy(nb = eos(nb), " Stun");
264 Strcpy(nb = eos(nb), " á¿
\9dò");
267 Strcpy(nb = eos(nb), " Conf");
269 Strcpy(nb = eos(nb), "
\8d¬
\97\90");
272 Strcpy(nb = eos(nb), " Hallu");
274 Strcpy(nb = eos(nb), "
\8c¶
\8ao");
275 /* levitation and flying are mutually exclusive; riding is not */
278 Strcpy(nb = eos(nb), " Lev");
280 Strcpy(nb = eos(nb), "
\95\82\97V");
283 Strcpy(nb = eos(nb), " Fly");
285 Strcpy(nb = eos(nb), "
\94ò
\8ds");
288 Strcpy(nb = eos(nb), " Ride");
290 Strcpy(nb = eos(nb), "
\8bR
\8fæ");
294 * Put the pieces together. If they all fit, keep the traditional
295 * sequence. Otherwise, move least important parts to the end in
296 * case the interface side of things has to truncate. Note that
297 * dloc[] contains '$' encoded in ten character sequence \GXXXXNNNN
298 * so we want to test its display length rather than buffer length.
300 * We don't have an actual display limit here, so have to go by the
301 * width of the map. Since we're reordering rather than truncating,
302 * wider displays can still show wider status than the map if the
303 * interface supports that.
305 if ((dln - dx) + 1 + hln + 1 + xln + 1 + tln + 1 + cln <= COLNO) {
306 Sprintf(newbot2, "%s %s %s %s %s", dloc, hlth, expr, tmmv, cond);
308 if (dln + 1 + hln + 1 + xln + 1 + tln + 1 + cln + 1 > MAXCO) {
309 panic("bot2: second status line exceeds MAXCO (%u > %d)",
310 (dln + 1 + hln + 1 + xln + 1 + tln + 1 + cln + 1), MAXCO);
311 } else if ((dln - dx) + 1 + hln + 1 + xln + 1 + cln <= COLNO) {
312 Sprintf(newbot2, "%s %s %s %s %s", dloc, hlth, expr, cond, tmmv);
313 } else if ((dln - dx) + 1 + hln + 1 + cln <= COLNO) {
314 Sprintf(newbot2, "%s %s %s %s %s", dloc, hlth, cond, expr, tmmv);
316 Sprintf(newbot2, "%s %s %s %s %s", hlth, cond, dloc, expr, tmmv);
318 /* only two or three consecutive spaces available to squeeze out */
327 /* dosave() flags completion by setting u.uhp to -1 */
328 if ((u.uhp != -1) && youmonst.data && iflags.status_updates) {
329 if (VIA_WINDOWPORT()) {
330 bot_via_windowport();
332 curs(WIN_STATUS, 1, 0);
333 putstr(WIN_STATUS, 0, do_statusline1());
334 curs(WIN_STATUS, 1, 1);
335 putmixed(WIN_STATUS, 0, do_statusline2());
338 context.botl = context.botlx = iflags.time_botl = FALSE;
344 if (flags.time && iflags.status_updates) {
345 if (VIA_WINDOWPORT()) {
348 /* old status display updates everything */
352 iflags.time_botl = FALSE;
355 /* convert experience level (1..30) to rank index (0..8) */
360 return (xlev <= 2) ? 0 : (xlev <= 30) ? ((xlev + 2) / 4) : 8;
363 #if 0 /* not currently needed */
364 /* convert rank index (0..8) to experience level (1..30) */
369 return (rank <= 0) ? 1 : (rank <= 8) ? ((rank * 4) - 2) : 30;
374 rank_of(lev, monnum, female)
379 register const struct Role *role;
383 for (role = roles; role->name.m; role++)
384 if (monnum == role->malenum || monnum == role->femalenum)
390 for (i = xlev_to_rank((int) lev); i >= 0; i--) {
391 if (female && role->rank[i].f)
392 return role->rank[i].f;
394 return role->rank[i].m;
397 /* Try the role name, instead */
398 if (female && role->name.f)
400 else if (role->name.m)
405 return "
\83v
\83\8c\83C
\83\84\81[";
408 STATIC_OVL const char *
411 return rank_of(u.ulevel, Role_switch, flags.female);
415 title_to_mon(str, rank_indx, title_length)
417 int *rank_indx, *title_length;
421 /* Loop through each of the roles */
422 for (i = 0; roles[i].name.m; i++)
423 for (j = 0; j < 9; j++) {
424 if (roles[i].rank[j].m
425 && !strncmpi(str, roles[i].rank[j].m,
426 strlen(roles[i].rank[j].m))) {
430 *title_length = strlen(roles[i].rank[j].m);
431 return roles[i].malenum;
433 if (roles[i].rank[j].f
434 && !strncmpi(str, roles[i].rank[j].f,
435 strlen(roles[i].rank[j].f))) {
439 *title_length = strlen(roles[i].rank[j].f);
440 return (roles[i].femalenum != NON_PM) ? roles[i].femalenum
450 register int i, r, maxr = 0;
451 for (i = 0; i < 9; i++) {
452 if (urole.rank[i].m && (r = strlen(urole.rank[i].m)) > maxr)
454 if (urole.rank[i].f && (r = strlen(urole.rank[i].f)) > maxr)
465 long deepest = deepest_lev_reached(FALSE);
468 utotal = money_cnt(invent) + hidden_gold();
469 if ((utotal -= u.umoney0) < 0L)
471 utotal += u.urexp + (50 * (deepest - 1))
472 + (deepest > 30 ? 10000 : deepest > 20 ? 1000 * (deepest - 20) : 0);
473 if (utotal < u.urexp)
474 utotal = LONG_MAX; /* wrap around */
477 #endif /* SCORE_ON_BOTL */
479 /* provide the name of the current level for display by various ports */
486 /* TODO: Add in dungeon name */
487 if (Is_knox(&u.uz)) {
488 Sprintf(buf, "%s ", dungeons[u.uz.dnum].dname);
489 } else if (In_quest(&u.uz)) {
491 Sprintf(buf, "Home %d ", dunlev(&u.uz));
493 Sprintf(buf, "
\8cÌ
\8b½ %d ", dunlev(&u.uz));
494 } else if (In_endgame(&u.uz)) {
495 /* [3.6.2: this used to be "Astral Plane" or generic "End Game"] */
496 (void) endgamelevelname(buf, depth(&u.uz));
498 (void) strsubst(buf, "Plane of ", ""); /* just keep <element> */
502 /* ports with more room may expand this one */
504 Sprintf(buf, "Dlvl:%-2d ", depth(&u.uz));
506 Sprintf(buf, "
\92n
\89º:%-2d ", depth(&u.uz));
512 /* =======================================================================*/
513 /* statusnew routines */
514 /* =======================================================================*/
516 /* structure that tracks the status details in the core */
518 #define MAXVALWIDTH 80 /* actually less, but was using 80 to allocate title
519 * and leveldesc then using QBUFSZ everywhere else */
520 #ifdef STATUS_HILITES
522 enum statusfields fld;
527 char textmatch[MAXVALWIDTH];
528 enum relationships rel;
530 struct hilite_s *next;
532 #endif /* STATUS_HILITES */
537 long time; /* moves when this field hilite times out */
538 boolean chg; /* need to recalc time? */
539 boolean percent_matters;
545 enum statusfields idxmax;
546 enum statusfields fld;
547 #ifdef STATUS_HILITES
548 struct hilite_s *hilite_rule; /* the entry, if any, in 'thresholds'
549 * list that currently applies */
550 struct hilite_s *thresholds;
554 STATIC_DCL boolean FDECL(eval_notify_windowport_field, (int, boolean *, int));
555 STATIC_DCL void FDECL(evaluate_and_notify_windowport, (boolean *, int));
556 STATIC_DCL void NDECL(init_blstats);
557 STATIC_DCL int FDECL(compare_blstats, (struct istat_s *, struct istat_s *));
558 STATIC_DCL char *FDECL(anything_to_s, (char *, anything *, int));
559 STATIC_DCL int FDECL(percentage, (struct istat_s *, struct istat_s *));
560 STATIC_DCL int NDECL(exp_percentage);
562 #ifdef STATUS_HILITES
563 STATIC_DCL void FDECL(s_to_anything, (anything *, char *, int));
564 STATIC_DCL enum statusfields FDECL(fldname_to_bl_indx, (const char *));
565 STATIC_DCL boolean FDECL(hilite_reset_needed, (struct istat_s *, long));
566 STATIC_DCL boolean FDECL(noneoftheabove, (const char *));
567 STATIC_DCL struct hilite_s *FDECL(get_hilite, (int, int, genericptr_t,
569 STATIC_DCL void FDECL(split_clridx, (int, int *, int *));
570 STATIC_DCL boolean FDECL(is_ltgt_percentnumber, (const char *));
571 STATIC_DCL boolean FDECL(has_ltgt_percentnumber, (const char *));
572 STATIC_DCL int FDECL(splitsubfields, (char *, char ***, int));
573 STATIC_DCL boolean FDECL(is_fld_arrayvalues, (const char *,
576 STATIC_DCL int FDECL(query_arrayvalue, (const char *, const char *const *,
578 STATIC_DCL void FDECL(status_hilite_add_threshold, (int, struct hilite_s *));
579 STATIC_DCL boolean FDECL(parse_status_hl2, (char (*)[QBUFSZ], BOOLEAN_P));
580 STATIC_DCL char *FDECL(conditionbitmask2str, (unsigned long));
581 STATIC_DCL unsigned long FDECL(match_str2conditionbitmask, (const char *));
582 STATIC_DCL unsigned long FDECL(str2conditionbitmask, (char *));
583 STATIC_DCL boolean FDECL(parse_condition, (char (*)[QBUFSZ], int));
584 STATIC_DCL char *FDECL(hlattr2attrname, (int, char *, int));
585 STATIC_DCL void FDECL(status_hilite_linestr_add, (int, struct hilite_s *,
586 unsigned long, const char *));
587 STATIC_DCL void NDECL(status_hilite_linestr_done);
588 STATIC_DCL int FDECL(status_hilite_linestr_countfield, (int));
589 STATIC_DCL void NDECL(status_hilite_linestr_gather_conditions);
590 STATIC_DCL void NDECL(status_hilite_linestr_gather);
591 STATIC_DCL char *FDECL(status_hilite2str, (struct hilite_s *));
592 STATIC_DCL int NDECL(status_hilite_menu_choose_field);
593 STATIC_DCL int FDECL(status_hilite_menu_choose_behavior, (int));
594 STATIC_DCL int FDECL(status_hilite_menu_choose_updownboth, (int, const char *,
595 BOOLEAN_P, BOOLEAN_P));
596 STATIC_DCL boolean FDECL(status_hilite_menu_add, (int));
597 #define has_hilite(i) (blstats[0][(i)].thresholds)
598 /* TH_UPDOWN encompasses specific 'up' and 'down' also general 'changed' */
599 #define Is_Temp_Hilite(rule) ((rule) && (rule)->behavior == BL_TH_UPDOWN)
601 /* pointers to current hilite rule and list of this field's defined rules */
602 #define INIT_THRESH , (struct hilite_s *) 0, (struct hilite_s *) 0
603 #else /* !STATUS_HILITES */
604 #define INIT_THRESH /*empty*/
607 #define INIT_BLSTAT(name, fmtstr, anytyp, wid, fld) \
608 { name, fmtstr, 0L, FALSE, FALSE, 0, anytyp, \
609 { (genericptr_t) 0 }, (char *) 0, \
610 wid, -1, fld INIT_THRESH }
611 #define INIT_BLSTATP(name, fmtstr, anytyp, wid, maxfld, fld) \
612 { name, fmtstr, 0L, FALSE, TRUE, 0, anytyp, \
613 { (genericptr_t) 0 }, (char *) 0, \
614 wid, maxfld, fld INIT_THRESH }
616 /* If entries are added to this, botl.h will require updating too.
617 'max' value of BL_EXP gets special handling since the percentage
618 involved isn't a direct 100*current/maximum calculation. */
619 STATIC_VAR struct istat_s initblstats[MAXBLSTATS] = {
620 INIT_BLSTAT("title", "%s", ANY_STR, MAXVALWIDTH, BL_TITLE),
621 INIT_BLSTAT("strength", " St:%s", ANY_INT, 10, BL_STR),
622 INIT_BLSTAT("dexterity", " Dx:%s", ANY_INT, 10, BL_DX),
623 INIT_BLSTAT("constitution", " Co:%s", ANY_INT, 10, BL_CO),
624 INIT_BLSTAT("intelligence", " In:%s", ANY_INT, 10, BL_IN),
625 INIT_BLSTAT("wisdom", " Wi:%s", ANY_INT, 10, BL_WI),
626 INIT_BLSTAT("charisma", " Ch:%s", ANY_INT, 10, BL_CH),
627 INIT_BLSTAT("alignment", " %s", ANY_STR, 40, BL_ALIGN),
628 INIT_BLSTAT("score", " S:%s", ANY_LONG, 20, BL_SCORE),
629 INIT_BLSTAT("carrying-capacity", " %s", ANY_INT, 20, BL_CAP),
630 INIT_BLSTAT("gold", " %s", ANY_LONG, 30, BL_GOLD),
631 INIT_BLSTATP("power", " Pw:%s", ANY_INT, 10, BL_ENEMAX, BL_ENE),
632 INIT_BLSTAT("power-max", "(%s)", ANY_INT, 10, BL_ENEMAX),
633 INIT_BLSTATP("experience-level", " Xp:%s", ANY_INT, 10, BL_EXP, BL_XP),
634 INIT_BLSTAT("armor-class", " AC:%s", ANY_INT, 10, BL_AC),
635 INIT_BLSTAT("HD", " HD:%s", ANY_INT, 10, BL_HD),
636 INIT_BLSTAT("time", " T:%s", ANY_LONG, 20, BL_TIME),
637 /* hunger used to be 'ANY_UINT'; see note below in bot_via_windowport() */
638 INIT_BLSTAT("hunger", " %s", ANY_INT, 40, BL_HUNGER),
639 INIT_BLSTATP("hitpoints", " HP:%s", ANY_INT, 10, BL_HPMAX, BL_HP),
640 INIT_BLSTAT("hitpoints-max", "(%s)", ANY_INT, 10, BL_HPMAX),
641 INIT_BLSTAT("dungeon-level", "%s", ANY_STR, MAXVALWIDTH, BL_LEVELDESC),
642 INIT_BLSTATP("experience", "/%s", ANY_LONG, 20, BL_EXP, BL_EXP),
643 INIT_BLSTAT("condition", "%s", ANY_MASK32, 0, BL_CONDITION)
650 struct istat_s blstats[2][MAXBLSTATS];
651 static boolean blinit = FALSE, update_all = FALSE;
652 static boolean valset[MAXBLSTATS];
653 #ifdef STATUS_HILITES
654 static long bl_hilite_moves = 0L;
657 /* we don't put this next declaration in #ifdef STATUS_HILITES.
658 * In the absence of STATUS_HILITES, each array
659 * element will be 0 however, and quite meaningless,
660 * but we need to pass the first array element as
661 * the final argument of status_update, with or
662 * without STATUS_HILITES.
664 static unsigned long cond_hilites[BL_ATTCLR_MAX];
665 static int now_or_before_idx = 0; /* 0..1 for array[2][] first index */
677 panic("bot before init.");
679 /* toggle from previous iteration */
680 idx = 1 - now_or_before_idx; /* 0 -> 1, 1 -> 0 */
681 now_or_before_idx = idx;
683 /* clear the "value set" indicators */
684 (void) memset((genericptr_t) valset, 0, MAXBLSTATS * sizeof (boolean));
687 * Note: min(x,9999) - we enforce the same maximum on hp, maxhp,
688 * pw, maxpw, and gold as basic status formatting so that the two
689 * modes of status display don't produce different information.
693 * Player name and title.
695 Strcpy(nb = buf, plname);
696 nb[0] = highc(nb[0]);
697 titl = !Upolyd ? rank() : mons[u.umonnum].mname;
699 i = (int) (strlen(buf) + sizeof " the " + strlen(titl) - sizeof "");
701 i = (int) (strlen(buf) + sizeof " " + strlen(titl) - sizeof "");
703 /* if "Name the Rank/monster" is too long, we truncate the name
704 but always keep at least 10 characters of it; when hitpintbar is
705 enabled, anything beyond 30 (long monster name) will be truncated */
708 i = 30 - (int) (sizeof " the " + strlen(titl) - sizeof "");
710 i = 30 - (int) (sizeof " " + strlen(titl) - sizeof "");
712 nb[max(i, 10)] = '\0';
715 Strcpy(nb = eos(nb), " the ");
717 Strcpy(nb = eos(nb), " ");
719 Strcpy(nb = eos(nb), titl);
720 if (Upolyd) { /* when poly'd, capitalize monster name */
721 for (i = 0; nb[i]; i++)
722 if (i == 0 || nb[i - 1] == ' ')
723 nb[i] = highc(nb[i]);
725 Sprintf(blstats[idx][BL_TITLE].val, "%-30s", buf);
726 valset[BL_TITLE] = TRUE; /* indicate val already set */
729 blstats[idx][BL_STR].a.a_int = ACURR(A_STR);
730 Strcpy(blstats[idx][BL_STR].val, get_strength_str());
731 valset[BL_STR] = TRUE; /* indicate val already set */
733 /* Dexterity, constitution, intelligence, wisdom, charisma. */
734 blstats[idx][BL_DX].a.a_int = ACURR(A_DEX);
735 blstats[idx][BL_CO].a.a_int = ACURR(A_CON);
736 blstats[idx][BL_IN].a.a_int = ACURR(A_INT);
737 blstats[idx][BL_WI].a.a_int = ACURR(A_WIS);
738 blstats[idx][BL_CH].a.a_int = ACURR(A_CHA);
742 Strcpy(blstats[idx][BL_ALIGN].val, (u.ualign.type == A_CHAOTIC)
744 : (u.ualign.type == A_NEUTRAL)
748 Strcpy(blstats[idx][BL_ALIGN].val, (u.ualign.type == A_CHAOTIC)
750 : (u.ualign.type == A_NEUTRAL)
756 blstats[idx][BL_SCORE].a.a_long =
758 flags.showscore ? botl_score() :
763 i = Upolyd ? u.mh : u.uhp;
766 blstats[idx][BL_HP].a.a_int = min(i, 9999);
767 i = Upolyd ? u.mhmax : u.uhpmax;
768 blstats[idx][BL_HPMAX].a.a_int = min(i, 9999);
771 (void) describe_level(blstats[idx][BL_LEVELDESC].val);
772 valset[BL_LEVELDESC] = TRUE; /* indicate val already set */
775 if ((money = money_cnt(invent)) < 0L)
776 money = 0L; /* ought to issue impossible() and then discard gold */
777 blstats[idx][BL_GOLD].a.a_long = min(money, 999999L);
779 * The tty port needs to display the current symbol for gold
780 * as a field header, so to accommodate that we pass gold with
781 * that already included. If a window port needs to use the text
782 * gold amount without the leading "$:" the port will have to
783 * skip past ':' to the value pointer it was passed in status_update()
784 * for the BL_GOLD case.
786 * Another quirk of BL_GOLD is that the field display may have
787 * changed if a new symbol set was loaded, or we entered or left
790 * The currency prefix is encoded as ten character \GXXXXNNNN
793 Sprintf(blstats[idx][BL_GOLD].val, "%s:%ld",
794 (iflags.in_dumplog || iflags.invis_goldsym) ? "$"
795 : encglyph(objnum_to_glyph(GOLD_PIECE)),
796 blstats[idx][BL_GOLD].a.a_long);
797 valset[BL_GOLD] = TRUE; /* indicate val already set */
799 /* Power (magical energy) */
800 blstats[idx][BL_ENE].a.a_int = min(u.uen, 9999);
801 blstats[idx][BL_ENEMAX].a.a_int = min(u.uenmax, 9999);
804 blstats[idx][BL_AC].a.a_int = u.uac;
806 /* Monster level (if Upolyd) */
807 blstats[idx][BL_HD].a.a_int = Upolyd ? (int) mons[u.umonnum].mlevel : 0;
810 blstats[idx][BL_XP].a.a_int = u.ulevel;
811 blstats[idx][BL_EXP].a.a_long = u.uexp;
814 blstats[idx][BL_TIME].a.a_long = moves;
817 /* note: u.uhs is unsigned, and 3.6.1's STATUS_HILITE defined
818 BL_HUNGER to be ANY_UINT, but that was the only non-int/non-long
819 numeric field so it's far simpler to treat it as plain int and
820 not need ANY_UINT handling at all */
821 blstats[idx][BL_HUNGER].a.a_int = (int) u.uhs;
822 Strcpy(blstats[idx][BL_HUNGER].val,
823 (u.uhs != NOT_HUNGRY) ? hu_stat[u.uhs] : "");
824 valset[BL_HUNGER] = TRUE;
826 /* Carrying capacity */
827 cap = near_capacity();
828 blstats[idx][BL_CAP].a.a_int = cap;
829 Strcpy(blstats[idx][BL_CAP].val,
830 (cap > UNENCUMBERED) ? enc_stat[cap] : "");
831 valset[BL_CAP] = TRUE;
834 blstats[idx][BL_CONDITION].a.a_ulong = 0L;
836 blstats[idx][BL_CONDITION].a.a_ulong |= BL_MASK_STONE;
838 blstats[idx][BL_CONDITION].a.a_ulong |= BL_MASK_SLIME;
840 blstats[idx][BL_CONDITION].a.a_ulong |= BL_MASK_STRNGL;
841 if (Sick && (u.usick_type & SICK_VOMITABLE) != 0)
842 blstats[idx][BL_CONDITION].a.a_ulong |= BL_MASK_FOODPOIS;
843 if (Sick && (u.usick_type & SICK_NONVOMITABLE) != 0)
844 blstats[idx][BL_CONDITION].a.a_ulong |= BL_MASK_TERMILL;
846 * basic formatting puts hunger status and encumbrance here
849 blstats[idx][BL_CONDITION].a.a_ulong |= BL_MASK_BLIND;
851 blstats[idx][BL_CONDITION].a.a_ulong |= BL_MASK_DEAF;
853 blstats[idx][BL_CONDITION].a.a_ulong |= BL_MASK_STUN;
855 blstats[idx][BL_CONDITION].a.a_ulong |= BL_MASK_CONF;
857 blstats[idx][BL_CONDITION].a.a_ulong |= BL_MASK_HALLU;
858 /* levitation and flying are mututally exclusive */
860 blstats[idx][BL_CONDITION].a.a_ulong |= BL_MASK_LEV;
862 blstats[idx][BL_CONDITION].a.a_ulong |= BL_MASK_FLY;
864 blstats[idx][BL_CONDITION].a.a_ulong |= BL_MASK_RIDE;
865 evaluate_and_notify_windowport(valset, idx);
868 /* update just the status lines' 'time' field */
872 int idx = now_or_before_idx; /* no 0/1 toggle */
876 blstats[idx][fld].a.a_long = moves;
879 eval_notify_windowport_field(fld, valset, idx);
880 if ((windowprocs.wincap2 & WC2_FLUSH_STATUS) != 0L)
881 status_update(BL_FLUSH, (genericptr_t) 0, 0, 0,
882 NO_COLOR, (unsigned long *) 0);
887 eval_notify_windowport_field(fld, valsetlist, idx)
891 static int oldrndencode = 0;
892 static nhsym oldgoldsym = 0;
893 int pc, chg, color = NO_COLOR;
895 boolean updated = FALSE, reset;
896 struct istat_s *curr, *prev;
897 enum statusfields fldmax;
900 * Now pass the changed values to window port.
902 anytype = blstats[idx][fld].anytype;
903 curr = &blstats[idx][fld];
904 prev = &blstats[1 - idx][fld];
907 chg = update_all ? 0 : compare_blstats(prev, curr);
910 * Dynamically update 'percent_matters' as rules are added or
911 * removed to track whether any of them are precentage rules.
912 * Then there'll be no need to assume that non-Null 'thresholds'
913 * means that percentages need to be kept up to date.
914 * [Affects exp_percent_changing() too.]
916 if (((chg || update_all || fld == BL_XP)
917 && curr->percent_matters && curr->thresholds)
918 /* when 'hitpointbar' is On, percent matters even if HP
919 hasn't changed and has no percentage rules (in case HPmax
920 has changed when HP hasn't, where we ordinarily wouldn't
921 update HP so would miss an update of the hitpoint bar) */
922 || (fld == BL_HP && iflags.wc2_hitpointbar)) {
923 fldmax = curr->idxmax;
924 pc = (fldmax == BL_EXP) ? exp_percentage()
925 : (fldmax >= 0) ? percentage(curr, &blstats[idx][fldmax])
926 : 0; /* bullet proofing; can't get here */
927 if (pc != prev->percent_value)
929 curr->percent_value = pc;
934 /* Temporary? hack: moveloop()'s prolog for a new game sets
935 * context.rndencode after the status window has been init'd,
936 * so $:0 has already been encoded and cached by the window
937 * port. Without this hack, gold's \G sequence won't be
938 * recognized and ends up being displayed as-is for 'update_all'.
940 * Also, even if context.rndencode hasn't changed and the
941 * gold amount itself hasn't changed, the glyph portion of the
942 * encoding may have changed if a new symset was put into effect.
945 * XXXX = the context.rndencode portion
946 * NNNN = the glyph portion
947 * 25 = the gold amount
949 * Setting 'chg = 2' is enough to render the field properly, but
950 * not to honor an initial highlight, so force 'update_all = TRUE'.
953 && (context.rndencode != oldrndencode
954 || showsyms[COIN_CLASS + SYM_OFF_O] != oldgoldsym)) {
955 update_all = TRUE; /* chg = 2; */
956 oldrndencode = context.rndencode;
957 oldgoldsym = showsyms[COIN_CLASS + SYM_OFF_O];
961 #ifdef STATUS_HILITES
962 if (!update_all && !chg && curr->time) {
963 reset = hilite_reset_needed(prev, bl_hilite_moves);
965 curr->time = prev->time = 0L;
969 if (update_all || chg || reset) {
970 if (!valsetlist[fld])
971 (void) anything_to_s(curr->val, &curr->a, anytype);
973 if (anytype != ANY_MASK32) {
974 #ifdef STATUS_HILITES
975 if (chg || *curr->val) {
976 /* if Xp percentage changed, we set 'chg' to 1 above;
977 reset that if the Xp value hasn't actually changed
978 or possibly went down rather than up (level loss) */
979 if (chg == 1 && fld == BL_XP)
980 chg = compare_blstats(prev, curr);
982 curr->hilite_rule = get_hilite(idx, fld,
983 (genericptr_t) &curr->a,
985 prev->hilite_rule = curr->hilite_rule;
991 #endif /* STATUS_HILITES */
992 status_update(fld, (genericptr_t) curr->val,
993 chg, pc, color, (unsigned long *) 0);
995 /* Color for conditions is done through cond_hilites[] */
996 status_update(fld, (genericptr_t) &curr->a.a_ulong,
997 chg, pc, color, cond_hilites);
999 curr->chg = prev->chg = TRUE;
1006 evaluate_and_notify_windowport(valsetlist, idx)
1008 boolean *valsetlist;
1010 int i, updated = 0, notpresent = 0;
1013 * Now pass the changed values to window port.
1015 for (i = 0; i < MAXBLSTATS; i++) {
1016 if (((i == BL_SCORE) && !flags.showscore)
1017 || ((i == BL_EXP) && !flags.showexp)
1018 || ((i == BL_TIME) && !flags.time)
1019 || ((i == BL_HD) && !Upolyd)
1020 || ((i == BL_XP || i == BL_EXP) && Upolyd)) {
1024 if (eval_notify_windowport_field(i, valsetlist, idx))
1029 * 1. It is possible to get here, with nothing having been pushed
1030 * to the window port, when none of the info has changed.
1032 * 2. Some window ports are also known to optimize by only drawing
1033 * fields that have changed since the previous update.
1035 * In both of those situations, we need to force updates to
1036 * all of the fields when context.botlx is set. The tty port in
1037 * particular has a problem if that isn't done, since the core sets
1038 * context.botlx when a menu or text display obliterates the status
1041 * For those situations, to trigger the full update of every field
1042 * whether changed or not, call status_update() with BL_RESET.
1044 * For regular processing and to notify the window port that a
1045 * bot() round has finished and it's time to trigger a flush of
1046 * all buffered changes received thus far but not reflected in
1047 * the display, call status_update() with BL_FLUSH.
1050 if (context.botlx && (windowprocs.wincap2 & WC2_RESET_STATUS) != 0L)
1051 status_update(BL_RESET, (genericptr_t) 0, 0, 0,
1052 NO_COLOR, (unsigned long *) 0);
1053 else if ((updated || context.botlx)
1054 && (windowprocs.wincap2 & WC2_FLUSH_STATUS) != 0L)
1055 status_update(BL_FLUSH, (genericptr_t) 0, 0, 0,
1056 NO_COLOR, (unsigned long *) 0);
1058 context.botl = context.botlx = iflags.time_botl = FALSE;
1063 status_initialize(reassessment)
1064 boolean reassessment; /* TRUE: just recheck fields w/o other initialization */
1066 enum statusfields fld;
1069 const char *fieldfmt, *fieldname;
1071 if (!reassessment) {
1073 impossible("2nd status_initialize with full init.");
1075 (*windowprocs.win_status_init)();
1077 } else if (!blinit) {
1078 panic("status 'reassess' before init");
1080 for (i = 0; i < MAXBLSTATS; ++i) {
1081 fld = initblstats[i].fld;
1082 fldenabl = (fld == BL_SCORE) ? flags.showscore
1083 : (fld == BL_TIME) ? flags.time
1084 : (fld == BL_EXP) ? (boolean) (flags.showexp && !Upolyd)
1085 : (fld == BL_XP) ? (boolean) !Upolyd
1086 : (fld == BL_HD) ? (boolean) Upolyd
1089 fieldname = initblstats[i].fldname;
1090 fieldfmt = (fld == BL_TITLE && iflags.wc2_hitpointbar) ? "%-30.30s"
1091 : initblstats[i].fldfmt;
1092 status_enablefield(fld, fieldname, fieldfmt, fldenabl);
1095 context.botlx = TRUE;
1103 /* call the window port cleanup routine first */
1104 if (windowprocs.win_status_finish)
1105 (*windowprocs.win_status_finish)();
1107 /* free memory that we alloc'd now */
1108 for (i = 0; i < MAXBLSTATS; ++i) {
1109 if (blstats[0][i].val)
1110 free((genericptr_t) blstats[0][i].val), blstats[0][i].val = 0;
1111 if (blstats[1][i].val)
1112 free((genericptr_t) blstats[1][i].val), blstats[1][i].val = 0;
1113 #ifdef STATUS_HILITES
1114 /* pointer to an entry in thresholds list; Null it out since
1115 that list is about to go away */
1116 blstats[0][i].hilite_rule = blstats[1][i].hilite_rule = 0;
1117 if (blstats[0][i].thresholds) {
1118 struct hilite_s *temp, *next;
1120 for (temp = blstats[0][i].thresholds; temp; temp = next) {
1122 free((genericptr_t) temp);
1124 blstats[0][i].thresholds = blstats[1][i].thresholds = 0;
1126 #endif /* STATUS_HILITES */
1133 static boolean initalready = FALSE;
1137 impossible("init_blstats called more than once.");
1140 for (i = 0; i <= 1; ++i) {
1141 for (j = 0; j < MAXBLSTATS; ++j) {
1142 #ifdef STATUS_HILITES
1143 struct hilite_s *keep_hilite_chain = blstats[i][j].thresholds;
1146 blstats[i][j] = initblstats[j];
1147 blstats[i][j].a = zeroany;
1148 if (blstats[i][j].valwidth) {
1149 blstats[i][j].val = (char *) alloc(blstats[i][j].valwidth);
1150 blstats[i][j].val[0] = '\0';
1152 blstats[i][j].val = (char *) 0;
1153 #ifdef STATUS_HILITES
1154 blstats[i][j].thresholds = keep_hilite_chain;
1162 * This compares the previous stat with the current stat,
1163 * and returns one of the following results based on that:
1165 * if prev_value < new_value (stat went up, increased)
1168 * if prev_value > new_value (stat went down, decreased)
1171 * if prev_value == new_value (stat stayed the same)
1175 * - for bitmasks, 0 = stayed the same, 1 = changed
1176 * - for strings, 0 = stayed the same, 1 = changed
1180 compare_blstats(bl1, bl2)
1181 struct istat_s *bl1, *bl2;
1183 int anytype, result = 0;
1186 panic("compare_blstat: bad istat pointer %s, %s",
1187 fmt_ptr((genericptr_t) bl1), fmt_ptr((genericptr_t) bl2));
1190 anytype = bl1->anytype;
1191 if ((!bl1->a.a_void || !bl2->a.a_void)
1192 && (anytype == ANY_IPTR || anytype == ANY_UPTR || anytype == ANY_LPTR
1193 || anytype == ANY_ULPTR)) {
1194 panic("compare_blstat: invalid pointer %s, %s",
1195 fmt_ptr((genericptr_t) bl1->a.a_void),
1196 fmt_ptr((genericptr_t) bl2->a.a_void));
1201 result = (bl1->a.a_int < bl2->a.a_int)
1203 : (bl1->a.a_int > bl2->a.a_int) ? -1 : 0;
1206 result = (*bl1->a.a_iptr < *bl2->a.a_iptr)
1208 : (*bl1->a.a_iptr > *bl2->a.a_iptr) ? -1 : 0;
1211 result = (bl1->a.a_long < bl2->a.a_long)
1213 : (bl1->a.a_long > bl2->a.a_long) ? -1 : 0;
1216 result = (*bl1->a.a_lptr < *bl2->a.a_lptr)
1218 : (*bl1->a.a_lptr > *bl2->a.a_lptr) ? -1 : 0;
1221 result = (bl1->a.a_uint < bl2->a.a_uint)
1223 : (bl1->a.a_uint > bl2->a.a_uint) ? -1 : 0;
1226 result = (*bl1->a.a_uptr < *bl2->a.a_uptr)
1228 : (*bl1->a.a_uptr > *bl2->a.a_uptr) ? -1 : 0;
1231 result = (bl1->a.a_ulong < bl2->a.a_ulong)
1233 : (bl1->a.a_ulong > bl2->a.a_ulong) ? -1 : 0;
1236 result = (*bl1->a.a_ulptr < *bl2->a.a_ulptr)
1238 : (*bl1->a.a_ulptr > *bl2->a.a_ulptr) ? -1 : 0;
1241 result = sgn(strcmp(bl1->val, bl2->val));
1244 result = (bl1->a.a_ulong != bl2->a.a_ulong);
1253 anything_to_s(buf, a, anytype)
1263 Sprintf(buf, "%lu", a->a_ulong);
1266 Sprintf(buf, "%lx", a->a_ulong);
1269 Sprintf(buf, "%ld", a->a_long);
1272 Sprintf(buf, "%d", a->a_int);
1275 Sprintf(buf, "%u", a->a_uint);
1278 Sprintf(buf, "%d", *a->a_iptr);
1281 Sprintf(buf, "%ld", *a->a_lptr);
1284 Sprintf(buf, "%lu", *a->a_ulptr);
1287 Sprintf(buf, "%u", *a->a_uptr);
1289 case ANY_STR: /* do nothing */
1298 #ifdef STATUS_HILITES
1300 s_to_anything(a, buf, anytype)
1310 a->a_long = atol(buf);
1313 a->a_int = atoi(buf);
1316 a->a_uint = (unsigned) atoi(buf);
1319 a->a_ulong = (unsigned long) atol(buf);
1323 *a->a_iptr = atoi(buf);
1327 *a->a_uptr = (unsigned) atoi(buf);
1331 *a->a_lptr = atol(buf);
1335 *a->a_ulptr = (unsigned long) atol(buf);
1338 a->a_ulong = (unsigned long) atol(buf);
1346 #endif /* STATUS_HILITES */
1349 percentage(bl, maxbl)
1350 struct istat_s *bl, *maxbl;
1357 unsigned long ulval;
1359 if (!bl || !maxbl) {
1360 impossible("percentage: bad istat pointer %s, %s",
1361 fmt_ptr((genericptr_t) bl), fmt_ptr((genericptr_t) maxbl));
1365 ival = 0, lval = 0L, uval = 0U, ulval = 0UL;
1366 anytype = bl->anytype;
1367 if (maxbl->a.a_void) {
1371 result = ((100 * ival) / maxbl->a.a_int);
1374 lval = bl->a.a_long;
1375 result = (int) ((100L * lval) / maxbl->a.a_long);
1378 uval = bl->a.a_uint;
1379 result = (int) ((100U * uval) / maxbl->a.a_uint);
1382 ulval = bl->a.a_ulong;
1383 result = (int) ((100UL * ulval) / maxbl->a.a_ulong);
1386 ival = *bl->a.a_iptr;
1387 result = ((100 * ival) / (*maxbl->a.a_iptr));
1390 lval = *bl->a.a_lptr;
1391 result = (int) ((100L * lval) / (*maxbl->a.a_lptr));
1394 uval = *bl->a.a_uptr;
1395 result = (int) ((100U * uval) / (*maxbl->a.a_uptr));
1398 ulval = *bl->a.a_ulptr;
1399 result = (int) ((100UL * ulval) / (*maxbl->a.a_ulptr));
1403 /* don't let truncation from integer division produce a zero result
1404 from a non-zero input; note: if we ever change to something like
1405 ((((1000 * val) / max) + 5) / 10) for a rounded result, we'll
1406 also need to check for and convert false 100 to 99 */
1407 if (result == 0 && (ival != 0 || lval != 0L || uval != 0U || ulval != 0UL))
1413 /* percentage for both xp (level) and exp (points) is the percentage for
1414 (curr_exp - this_level_start) in (next_level_start - this_level_start) */
1420 if (u.ulevel < 30) {
1421 long exp_val, nxt_exp_val, curlvlstart;
1423 curlvlstart = newuexp(u.ulevel - 1);
1424 exp_val = u.uexp - curlvlstart;
1425 nxt_exp_val = newuexp(u.ulevel) - curlvlstart;
1426 if (exp_val == nxt_exp_val - 1L) {
1428 * Full 100% is unattainable since hero gains a level
1429 * and the threshold for next level increases, but treat
1430 * (next_level_start - 1 point) as a special case. It's a
1431 * key value after being level drained so is something that
1432 * some players would like to be able to highlight distinctly.
1436 struct istat_s curval, maxval;
1438 curval.anytype = maxval.anytype = ANY_LONG;
1439 curval.a = maxval.a = zeroany;
1440 curval.a.a_long = exp_val;
1441 maxval.a.a_long = nxt_exp_val;
1442 /* maximum delta between levels is 10000000; calculation of
1443 100 * (10000000 - N) / 10000000 fits within 32-bit long */
1444 res = percentage(&curval, &maxval);
1450 /* experience points have changed but experience level hasn't; decide whether
1451 botl update is needed for a different percentage highlight rule for Xp */
1453 exp_percent_changing()
1455 int pc, color_dummy;
1457 struct hilite_s *rule;
1458 struct istat_s *curr;
1460 /* if status update is already requested, skip this processing */
1461 if (!context.botl) {
1463 * Status update is warranted iff percent integer changes and the new
1464 * percentage results in a different highlighting rule being selected.
1466 curr = &blstats[now_or_before_idx][BL_XP];
1467 /* TODO: [see eval_notify_windowport_field() about percent_matters
1468 and the check against 'thresholds'] */
1469 if (curr->percent_matters && curr->thresholds
1470 && (pc = exp_percentage()) != curr->percent_value) {
1472 a.a_int = (int) u.ulevel;
1473 rule = get_hilite(now_or_before_idx, BL_XP,
1474 (genericptr_t) &a, 0, pc, &color_dummy);
1475 if (rule != curr->hilite_rule)
1476 return TRUE; /* caller should set 'context.botl' to True */
1482 /* callback so that interface can get capacity index rather than trying
1483 to reconstruct that from the encumbrance string or asking the general
1484 core what the value is */
1490 #ifdef STATUS_HILITES
1491 cap = blstats[now_or_before_idx][BL_CAP].a.a_int;
1493 cap = near_capacity();
1498 /* callback so that interface can get hunger index rather than trying to
1499 reconstruct that from the hunger string or dipping into core internals */
1505 #ifdef STATUS_HILITES
1506 uhs = blstats[now_or_before_idx][BL_HUNGER].a.a_int;
1513 /* used by X11 for "tty status" even when STATUS_HILITES is disabled */
1515 bl_idx_to_fldname(idx)
1518 if (idx >= 0 && idx < MAXBLSTATS)
1519 return initblstats[idx].fldname;
1520 return (const char *) 0;
1523 #ifdef STATUS_HILITES
1525 /****************************************************************************/
1526 /* Core status hiliting support */
1527 /****************************************************************************/
1529 struct hilite_s status_hilites[MAXBLSTATS];
1531 static struct fieldid_t {
1532 const char *fieldname;
1533 enum statusfields fldid;
1534 } fieldids_alias[] = {
1535 { "characteristics", BL_CHARACTERISTICS },
1536 { "encumbrance", BL_CAP },
1537 { "experience-points", BL_EXP },
1541 { "points", BL_SCORE },
1544 { "pw-max", BL_ENEMAX },
1548 { "hit-dice", BL_HD },
1549 { "turns", BL_TIME },
1551 { "hp-max", BL_HPMAX },
1552 { "dgn", BL_LEVELDESC },
1555 { "flags", BL_CONDITION },
1559 /* format arguments */
1560 static const char threshold_value[] = "hilite_status threshold ",
1561 is_out_of_range[] = " is out of range";
1564 /* field name to bottom line index */
1565 STATIC_OVL enum statusfields
1566 fldname_to_bl_indx(name)
1569 int i, nmatches = 0, fld = 0;
1571 if (name && *name) {
1572 /* check matches to canonical names */
1573 for (i = 0; i < SIZE(initblstats); i++)
1574 if (fuzzymatch(initblstats[i].fldname, name, " -_", TRUE)) {
1575 fld = initblstats[i].fld;
1581 for (i = 0; fieldids_alias[i].fieldname; i++)
1582 if (fuzzymatch(fieldids_alias[i].fieldname, name,
1584 fld = fieldids_alias[i].fldid;
1590 /* check partial matches to canonical names */
1591 int len = (int) strlen(name);
1593 for (i = 0; i < SIZE(initblstats); i++)
1594 if (!strncmpi(name, initblstats[i].fldname, len)) {
1595 fld = initblstats[i].fld;
1601 return (nmatches == 1) ? fld : BL_FLUSH;
1605 hilite_reset_needed(bl_p, augmented_time)
1606 struct istat_s *bl_p;
1607 long augmented_time; /* no longer augmented; it once encoded fractional
1608 * amounts for multiple moves within same turn */
1611 * This 'multi' handling may need some tuning...
1616 if (!Is_Temp_Hilite(bl_p->hilite_rule))
1619 if (bl_p->time == 0 || bl_p->time >= augmented_time)
1625 /* called from moveloop(); sets context.botl if temp hilites have timed out */
1627 status_eval_next_unhilite()
1630 struct istat_s *curr;
1631 long next_unhilite, this_unhilite;
1633 bl_hilite_moves = moves; /* simpllfied; used to try to encode fractional
1634 * amounts for multiple moves within same turn */
1635 /* figure out whether an unhilight needs to be performed now */
1637 for (i = 0; i < MAXBLSTATS; ++i) {
1638 curr = &blstats[0][i]; /* blstats[0][*].time == blstats[1][*].time */
1641 struct istat_s *prev = &blstats[1][i];
1643 if (Is_Temp_Hilite(curr->hilite_rule))
1644 curr->time = prev->time = (bl_hilite_moves
1645 + iflags.hilite_delta);
1647 curr->time = prev->time = 0L;
1649 curr->chg = prev->chg = FALSE;
1650 context.botl = TRUE;
1653 continue; /* just process other blstats[][].time and .chg */
1655 this_unhilite = curr->time;
1656 if (this_unhilite > 0L
1657 && (next_unhilite == 0L || this_unhilite < next_unhilite)
1658 && hilite_reset_needed(curr, this_unhilite + 1L)) {
1659 next_unhilite = this_unhilite;
1660 if (next_unhilite < bl_hilite_moves)
1661 context.botl = TRUE;
1666 /* called by options handling when 'statushilites' value is changed */
1668 reset_status_hilites()
1670 if (iflags.hilite_delta) {
1673 for (i = 0; i < MAXBLSTATS; ++i)
1674 blstats[0][i].time = blstats[1][i].time = 0L;
1677 context.botlx = TRUE;
1680 /* test whether the text from a title rule matches the string for
1681 title-while-polymorphed in the 'textmatch' menu */
1683 noneoftheabove(hl_text)
1684 const char *hl_text;
1686 if (fuzzymatch(hl_text, "none of the above", "\" -_", TRUE)
1687 || fuzzymatch(hl_text, "(polymorphed)", "\"()", TRUE)
1688 || fuzzymatch(hl_text, "none of the above (polymorphed)",
1697 * Returns, based on the value and the direction it is moving,
1698 * the highlight rule that applies to the specified field.
1700 * Provide get_hilite() with the following to work with:
1702 * useful for BL_TH_VAL_ABSOLUTE
1703 * indicator of down, up, or the same (-1, 1, 0) chg
1704 * useful for BL_TH_UPDOWN or change detection
1705 * percentage (current value percentage of max value) pc
1706 * useful for BL_TH_VAL_PERCENTAGE
1709 * pointer to rule that applies; Null if no rule does.
1711 STATIC_OVL struct hilite_s *
1712 get_hilite(idx, fldidx, vp, chg, pc, colorptr)
1713 int idx, fldidx, chg, pc;
1717 struct hilite_s *hl, *rule = 0;
1718 anything *value = (anything *) vp;
1721 if (fldidx < 0 || fldidx >= MAXBLSTATS)
1722 return (struct hilite_s *) 0;
1724 if (has_hilite(fldidx)) {
1726 /* there are hilites set here */
1727 int max_pc = -1, min_pc = 101;
1728 /* LARGEST_INT isn't INT_MAX; it fits within 16 bits, but that
1729 value is big enough to handle all 'int' status fields */
1730 int max_ival = -LARGEST_INT, min_ival = LARGEST_INT;
1731 /* LONG_MAX comes from <limits.h> which might not be available for
1732 ancient configurations; we don't need LONG_MIN */
1733 long max_lval = -LONG_MAX, min_lval = LONG_MAX;
1734 boolean exactmatch = FALSE, updown = FALSE, changed = FALSE,
1735 perc_or_abs = FALSE;
1737 /* min_/max_ are used to track best fit */
1738 for (hl = blstats[0][fldidx].thresholds; hl; hl = hl->next) {
1739 dt = initblstats[fldidx].anytype; /* only needed for 'absolute' */
1740 /* if we've already matched a temporary highlight, it takes
1741 precedence over all persistent ones; we still process
1742 updown rules to get the last one which qualifies */
1743 if ((updown || changed) && hl->behavior != BL_TH_UPDOWN)
1745 /* among persistent highlights, if a 'percentage' or 'absolute'
1746 rule has been matched, it takes precedence over 'always' */
1747 if (perc_or_abs && hl->behavior == BL_TH_ALWAYS_HILITE)
1750 switch (hl->behavior) {
1751 case BL_TH_VAL_PERCENTAGE: /* percent values are always ANY_INT */
1752 if (hl->rel == EQ_VALUE && pc == hl->value.a_int) {
1754 min_pc = max_pc = hl->value.a_int;
1755 exactmatch = perc_or_abs = TRUE;
1756 } else if (exactmatch) {
1757 ; /* already found best fit, skip lt,ge,&c */
1758 } else if (hl->rel == LT_VALUE
1759 && (pc < hl->value.a_int)
1760 && (hl->value.a_int <= min_pc)) {
1762 min_pc = hl->value.a_int;
1764 } else if (hl->rel == LE_VALUE
1765 && (pc <= hl->value.a_int)
1766 && (hl->value.a_int <= min_pc)) {
1768 min_pc = hl->value.a_int;
1770 } else if (hl->rel == GT_VALUE
1771 && (pc > hl->value.a_int)
1772 && (hl->value.a_int >= max_pc)) {
1774 max_pc = hl->value.a_int;
1776 } else if (hl->rel == GE_VALUE
1777 && (pc >= hl->value.a_int)
1778 && (hl->value.a_int >= max_pc)) {
1780 max_pc = hl->value.a_int;
1784 case BL_TH_UPDOWN: /* uses 'chg' (set by caller), not 'dt' */
1785 /* specific 'up' or 'down' takes precedence over general
1786 'changed' regardless of their order in the rule set */
1787 if (chg < 0 && hl->rel == LT_VALUE) {
1790 } else if (chg > 0 && hl->rel == GT_VALUE) {
1793 } else if (chg != 0 && hl->rel == EQ_VALUE && !updown) {
1798 case BL_TH_VAL_ABSOLUTE: /* either ANY_INT or ANY_LONG */
1800 * The int and long variations here are identical aside from
1801 * union field and min_/max_ variable names. If you change
1802 * one, be sure to make a corresponding change in the other.
1804 if (dt == ANY_INT) {
1805 if (hl->rel == EQ_VALUE
1806 && hl->value.a_int == value->a_int) {
1808 min_ival = max_ival = hl->value.a_int;
1809 exactmatch = perc_or_abs = TRUE;
1810 } else if (exactmatch) {
1811 ; /* already found best fit, skip lt,ge,&c */
1812 } else if (hl->rel == LT_VALUE
1813 && (value->a_int < hl->value.a_int)
1814 && (hl->value.a_int <= min_ival)) {
1816 min_ival = hl->value.a_int;
1818 } else if (hl->rel == LE_VALUE
1819 && (value->a_int <= hl->value.a_int)
1820 && (hl->value.a_int <= min_ival)) {
1822 min_ival = hl->value.a_int;
1824 } else if (hl->rel == GT_VALUE
1825 && (value->a_int > hl->value.a_int)
1826 && (hl->value.a_int >= max_ival)) {
1828 max_ival = hl->value.a_int;
1830 } else if (hl->rel == GE_VALUE
1831 && (value->a_int >= hl->value.a_int)
1832 && (hl->value.a_int >= max_ival)) {
1834 max_ival = hl->value.a_int;
1837 } else { /* ANY_LONG */
1838 if (hl->rel == EQ_VALUE
1839 && hl->value.a_long == value->a_long) {
1841 min_lval = max_lval = hl->value.a_long;
1842 exactmatch = perc_or_abs = TRUE;
1843 } else if (exactmatch) {
1844 ; /* already found best fit, skip lt,ge,&c */
1845 } else if (hl->rel == LT_VALUE
1846 && (value->a_long < hl->value.a_long)
1847 && (hl->value.a_long <= min_lval)) {
1849 min_lval = hl->value.a_long;
1851 } else if (hl->rel == LE_VALUE
1852 && (value->a_long <= hl->value.a_long)
1853 && (hl->value.a_long <= min_lval)) {
1855 min_lval = hl->value.a_long;
1857 } else if (hl->rel == GT_VALUE
1858 && (value->a_long > hl->value.a_long)
1859 && (hl->value.a_long >= max_lval)) {
1861 max_lval = hl->value.a_long;
1863 } else if (hl->rel == GE_VALUE
1864 && (value->a_long >= hl->value.a_long)
1865 && (hl->value.a_long >= max_lval)) {
1867 max_lval = hl->value.a_long;
1872 case BL_TH_TEXTMATCH: /* ANY_STR */
1873 txtstr = blstats[idx][fldidx].val;
1874 if (fldidx == BL_TITLE)
1875 /* "<name> the <rank-title>", skip past "<name> the " */
1876 txtstr += (strlen(plname) + sizeof " the " - sizeof "");
1877 if (hl->rel == TXT_VALUE && hl->textmatch[0]) {
1878 if (fuzzymatch(hl->textmatch, txtstr, "\" -_", TRUE)) {
1881 } else if (exactmatch) {
1882 ; /* already found best fit, skip "noneoftheabove" */
1883 } else if (fldidx == BL_TITLE
1884 && Upolyd && noneoftheabove(hl->textmatch)) {
1889 case BL_TH_ALWAYS_HILITE:
1899 *colorptr = rule ? rule->coloridx : NO_COLOR;
1904 split_clridx(idx, coloridx, attrib)
1906 int *coloridx, *attrib;
1909 *coloridx = idx & 0x00FF;
1911 *attrib = (idx >> 8) & 0x00FF;
1915 * This is the parser for the hilite options.
1917 * parse_status_hl1() separates each hilite entry into
1918 * a set of field threshold/action component strings,
1919 * then calls parse_status_hl2() to parse further
1920 * and configure the hilite.
1923 parse_status_hl1(op, from_configfile)
1925 boolean from_configfile;
1927 #define MAX_THRESH 21
1928 char hsbuf[MAX_THRESH][QBUFSZ];
1929 boolean rslt, badopt = FALSE;
1930 int i, fldnum, ccount = 0;
1934 for (i = 0; i < MAX_THRESH; ++i) {
1937 while (*op && fldnum < MAX_THRESH && ccount < (QBUFSZ - 2)) {
1941 if (fldnum == 1 && strcmpi(hsbuf[0], "title") == 0) {
1942 /* spaces are allowed in title */
1943 hsbuf[fldnum][ccount++] = c;
1944 hsbuf[fldnum][ccount] = '\0';
1948 rslt = parse_status_hl2(hsbuf, from_configfile);
1954 for (i = 0; i < MAX_THRESH; ++i) {
1959 } else if (c == '/') {
1963 hsbuf[fldnum][ccount++] = c;
1964 hsbuf[fldnum][ccount] = '\0';
1968 if (fldnum >= 1 && !badopt) {
1969 rslt = parse_status_hl2(hsbuf, from_configfile);
1978 /* is str in the format of "[<>]?=?[-+]?[0-9]+%?" regex */
1980 is_ltgt_percentnumber(str)
1983 const char *s = str;
1985 if (*s == '<' || *s == '>')
1989 if (*s == '-' || *s == '+')
1997 return (*s == '\0');
2000 /* does str only contain "<>=-+0-9%" chars */
2002 has_ltgt_percentnumber(str)
2005 const char *s = str;
2008 if (!index("<>=-+0123456789%", *s))
2015 /* splitsubfields(): splits str in place into '+' or '&' separated strings.
2016 * returns number of strings, or -1 if more than maxsf or MAX_SUBFIELDS
2018 #define MAX_SUBFIELDS 16
2020 splitsubfields(str, sfarr, maxsf)
2025 static char *subfields[MAX_SUBFIELDS];
2026 char *st = (char *) 0;
2031 for (sf = 0; sf < MAX_SUBFIELDS; ++sf)
2032 subfields[sf] = (char *) 0;
2034 maxsf = (maxsf == 0) ? MAX_SUBFIELDS : min(maxsf, MAX_SUBFIELDS);
2036 if (index(str, '+') || index(str, '&')) {
2041 while (*c && sf < maxsf) {
2042 if (*c == '&' || *c == '+') {
2050 if (sf >= maxsf - 1)
2053 subfields[sf++] = st;
2061 #undef MAX_SUBFIELDS
2064 is_fld_arrayvalues(str, arr, arrmin, arrmax, retidx)
2066 const char *const *arr;
2072 for (i = arrmin; i < arrmax; i++)
2073 if (!strcmpi(str, arr[i])) {
2081 query_arrayvalue(querystr, arr, arrmin, arrmax)
2082 const char *querystr;
2083 const char *const *arr;
2086 int i, res, ret = arrmin - 1;
2089 menu_item *picks = (menu_item *) 0;
2090 int adj = (arrmin > 0) ? 1 : arrmax;
2092 tmpwin = create_nhwindow(NHW_MENU);
2095 for (i = arrmin; i < arrmax; i++) {
2097 any.a_int = i + adj;
2098 add_menu(tmpwin, NO_GLYPH, &any, 0, 0, ATR_NONE,
2099 arr[i], MENU_UNSELECTED);
2102 end_menu(tmpwin, querystr);
2104 res = select_menu(tmpwin, PICK_ONE, &picks);
2105 destroy_nhwindow(tmpwin);
2107 ret = picks->item.a_int - adj;
2108 free((genericptr_t) picks);
2115 status_hilite_add_threshold(fld, hilite)
2117 struct hilite_s *hilite;
2119 struct hilite_s *new_hilite;
2124 /* alloc and initialize a new hilite_s struct */
2125 new_hilite = (struct hilite_s *) alloc(sizeof (struct hilite_s));
2126 *new_hilite = *hilite; /* copy struct */
2128 new_hilite->set = TRUE;
2129 new_hilite->fld = fld;
2130 new_hilite->next = blstats[0][fld].thresholds;
2131 blstats[0][fld].thresholds = new_hilite;
2132 /* sort_hilites(fld) */
2134 /* current and prev must both point at the same hilites */
2135 blstats[1][fld].thresholds = blstats[0][fld].thresholds;
2140 parse_status_hl2(s, from_configfile)
2142 boolean from_configfile;
2145 int sidx = 0, i = -1, dt = -1;
2146 int coloridx = -1, successes = 0;
2147 int disp_attrib = 0;
2148 boolean percent, changed, numeric, down, up,
2149 gt, lt, ge, le, eq, txtval, always;
2151 enum statusfields fld = BL_FLUSH;
2152 struct hilite_s hilite;
2154 static const char *aligntxt[] = { "chaotic", "neutral", "lawful" };
2155 /* hu_stat[] from eat.c has trailing spaces which foul up comparisons */
2156 static const char *hutxt[] = { "Satiated", "", "Hungry", "Weak",
2157 "Fainting", "Fainted", "Starved" };
2161 OPTION=hilite_status: hitpoints/<10%/red
2162 OPTION=hilite_status: hitpoints/<10%/red/<5%/purple/1/red+blink+inverse
2163 OPTION=hilite_status: experience/down/red/up/green
2164 OPTION=hilite_status: cap/strained/yellow/overtaxed/orange
2165 OPTION=hilite_status: title/always/blue
2166 OPTION=hilite_status: title/blue
2169 /* field name to statusfield */
2170 fld = fldname_to_bl_indx(s[sidx]);
2172 if (fld == BL_CHARACTERISTICS) {
2173 boolean res = FALSE;
2175 /* recursively set each of strength, dexterity, constitution, &c */
2176 for (fld = BL_STR; fld <= BL_CH; fld++) {
2177 Strcpy(s[sidx], initblstats[fld].fldname);
2178 res = parse_status_hl2(s, from_configfile);
2184 if (fld == BL_FLUSH) {
2185 config_error_add("Unknown status field '%s'", s[sidx]);
2188 if (fld == BL_CONDITION)
2189 return parse_condition(s, sidx);
2193 char buf[BUFSZ], **subfields;
2194 int sf = 0; /* subfield count */
2197 txt = (const char *)0;
2198 percent = numeric = always = FALSE;
2199 down = up = changed = FALSE;
2200 gt = ge = eq = le = lt = txtval = FALSE;
2202 /* threshold value */
2206 memset((genericptr_t) &hilite, 0, sizeof (struct hilite_s));
2207 hilite.set = FALSE; /* mark it "unset" */
2210 if (*s[sidx + 1] == '\0' || !strcmpi(s[sidx], "always")) {
2211 /* "field/always/color" OR "field/color" */
2213 if (*s[sidx + 1] == '\0')
2215 } else if (!strcmpi(s[sidx], "up") || !strcmpi(s[sidx], "down")) {
2216 if (initblstats[fld].anytype == ANY_STR)
2217 /* ordered string comparison is supported but LT/GT for
2218 the string fields (title, dungeon-level, alignment)
2219 is pointless; treat 'up' or 'down' for string fields
2220 as 'changed' rather than rejecting them outright */
2222 else if (!strcmpi(s[sidx], "down"))
2227 } else if (fld == BL_CAP
2229 && is_fld_arrayvalues(s[sidx], enc_stat,
2230 SLT_ENCUMBER, OVERLOADED + 1,
2232 txt = enc_stat[kidx];
2234 && is_fld_arrayvalues(s[sidx], enc_stat_opt,
2235 SLT_ENCUMBER, OVERLOADED + 1,
2237 txt = enc_stat_opt[kidx];
2240 } else if (fld == BL_ALIGN
2241 && is_fld_arrayvalues(s[sidx], aligntxt, 0, 3, &kidx)) {
2242 txt = aligntxt[kidx];
2244 } else if (fld == BL_HUNGER
2245 && is_fld_arrayvalues(s[sidx], hutxt,
2246 SATIATED, STARVED + 1, &kidx)) {
2247 txt = hu_stat[kidx]; /* store hu_stat[] val, not hutxt[] */
2249 } else if (!strcmpi(s[sidx], "changed")) {
2251 } else if (is_ltgt_percentnumber(s[sidx])) {
2254 tmp = s[sidx]; /* is_ltgt_() guarantees [<>]?=?[-+]?[0-9]+%? */
2255 if (strchr(tmp, '%'))
2262 } else if (*tmp == '>') {
2268 /* '%', '<', '>' have served their purpose, '=' is either
2269 part of '<' or '>' or optional for '=N', unary '+' is
2270 just decorative, so get rid of them, leaving -?[0-9]+ */
2271 tmp = stripchars(tmpbuf, "%<>=+", tmp);
2273 dt = percent ? ANY_INT : initblstats[fld].anytype;
2274 (void) s_to_anything(&hilite.value, tmp, dt);
2276 op = gt ? ">" : ge ? ">=" : lt ? "<" : le ? "<=" : "=";
2278 /* AC is the only field where negative values make sense but
2279 accept >-1 for other fields; reject <0 for non-AC */
2280 && (hilite.value.a_int
2281 < ((fld == BL_AC) ? -128 : gt ? -1 : lt ? 1 : 0)
2282 /* percentages have another more comprehensive check below */
2283 || hilite.value.a_int > (percent ? (lt ? 101 : 100)
2285 config_error_add("%s'%s%d%s'%s", threshold_value,
2286 op, hilite.value.a_int, percent ? "%" : "",
2289 } else if (dt == ANY_LONG
2290 && (hilite.value.a_long < (gt ? -1L : lt ? 1L : 0L))) {
2291 config_error_add("%s'%s%ld'%s", threshold_value,
2292 op, hilite.value.a_long, is_out_of_range);
2295 } else if (initblstats[fld].anytype == ANY_STR) {
2299 config_error_add(has_ltgt_percentnumber(s[sidx])
2300 ? "Wrong format '%s', expected a threshold number or percent"
2301 : "Unknown behavior '%s'",
2306 /* relationships {LT_VALUE, LE_VALUE, EQ_VALUE, GE_VALUE, GT_VALUE} */
2308 hilite.rel = GT_VALUE;
2309 else if (lt || down)
2310 hilite.rel = LT_VALUE;
2312 hilite.rel = GE_VALUE;
2314 hilite.rel = LE_VALUE;
2315 else if (eq || percent || numeric || changed)
2316 hilite.rel = EQ_VALUE;
2318 hilite.rel = TXT_VALUE;
2320 hilite.rel = LT_VALUE;
2322 if (initblstats[fld].anytype == ANY_STR && (percent || numeric)) {
2323 config_error_add("Field '%s' does not support numeric values",
2324 initblstats[fld].fldname);
2329 if (initblstats[fld].idxmax < 0) {
2330 config_error_add("Cannot use percent with '%s'",
2331 initblstats[fld].fldname);
2333 } else if ((hilite.value.a_int < -1)
2334 || (hilite.value.a_int == -1
2335 && hilite.value.a_int != GT_VALUE)
2336 || (hilite.value.a_int == 0
2337 && hilite.rel == LT_VALUE)
2338 || (hilite.value.a_int == 100
2339 && hilite.rel == GT_VALUE)
2340 || (hilite.value.a_int == 101
2341 && hilite.value.a_int != LT_VALUE)
2342 || (hilite.value.a_int > 101)) {
2344 "hilite_status: invalid percentage value '%s%d%%'",
2345 (hilite.rel == LT_VALUE) ? "<"
2346 : (hilite.rel == LE_VALUE) ? "<="
2347 : (hilite.rel == GT_VALUE) ? ">"
2348 : (hilite.rel == GE_VALUE) ? ">="
2350 hilite.value.a_int);
2364 sf = splitsubfields(buf, &subfields, 0);
2369 disp_attrib = HL_UNDEF;
2371 for (i = 0; i < sf; ++i) {
2372 int a = match_str2attr(subfields[i], FALSE);
2375 disp_attrib |= HL_DIM;
2376 else if (a == ATR_BLINK)
2377 disp_attrib |= HL_BLINK;
2378 else if (a == ATR_ULINE)
2379 disp_attrib |= HL_ULINE;
2380 else if (a == ATR_INVERSE)
2381 disp_attrib |= HL_INVERSE;
2382 else if (a == ATR_BOLD)
2383 disp_attrib |= HL_BOLD;
2384 else if (a == ATR_NONE)
2385 disp_attrib = HL_NONE;
2387 int c = match_str2clr(subfields[i]);
2389 if (c >= CLR_MAX || coloridx != -1)
2395 coloridx = NO_COLOR;
2397 /* Assign the values */
2398 hilite.coloridx = coloridx | (disp_attrib << 8);
2401 hilite.behavior = BL_TH_ALWAYS_HILITE;
2403 hilite.behavior = BL_TH_VAL_PERCENTAGE;
2405 hilite.behavior = BL_TH_UPDOWN;
2407 hilite.behavior = BL_TH_VAL_ABSOLUTE;
2409 hilite.behavior = BL_TH_TEXTMATCH;
2410 else if (hilite.value.a_void)
2411 hilite.behavior = BL_TH_VAL_ABSOLUTE;
2413 hilite.behavior = BL_TH_NONE;
2415 hilite.anytype = dt;
2417 if (hilite.behavior == BL_TH_TEXTMATCH && txt) {
2418 (void) strncpy(hilite.textmatch, txt, sizeof hilite.textmatch);
2419 hilite.textmatch[sizeof hilite.textmatch - 1] = '\0';
2420 (void) trimspaces(hilite.textmatch);
2423 status_hilite_add_threshold(fld, &hilite);
2431 #endif /* STATUS_HILITES */
2433 const struct condmap valid_conditions[] = {
2434 { "stone", BL_MASK_STONE },
2435 { "slime", BL_MASK_SLIME },
2436 { "strngl", BL_MASK_STRNGL },
2437 { "foodPois", BL_MASK_FOODPOIS },
2438 { "termIll", BL_MASK_TERMILL },
2439 { "blind", BL_MASK_BLIND },
2440 { "deaf", BL_MASK_DEAF },
2441 { "stun", BL_MASK_STUN },
2442 { "conf", BL_MASK_CONF },
2443 { "hallu", BL_MASK_HALLU },
2444 { "lev", BL_MASK_LEV },
2445 { "fly", BL_MASK_FLY },
2446 { "ride", BL_MASK_RIDE },
2449 #ifdef STATUS_HILITES
2451 const struct condmap condition_aliases[] = {
2452 { "strangled", BL_MASK_STRNGL },
2453 { "all", BL_MASK_STONE | BL_MASK_SLIME | BL_MASK_STRNGL
2454 | BL_MASK_FOODPOIS | BL_MASK_TERMILL
2455 | BL_MASK_BLIND | BL_MASK_DEAF | BL_MASK_STUN
2456 | BL_MASK_CONF | BL_MASK_HALLU
2457 | BL_MASK_LEV | BL_MASK_FLY | BL_MASK_RIDE },
2458 { "major_troubles", BL_MASK_STONE | BL_MASK_SLIME | BL_MASK_STRNGL
2459 | BL_MASK_FOODPOIS | BL_MASK_TERMILL },
2460 { "minor_troubles", BL_MASK_BLIND | BL_MASK_DEAF | BL_MASK_STUN
2461 | BL_MASK_CONF | BL_MASK_HALLU },
2462 { "movement", BL_MASK_LEV | BL_MASK_FLY | BL_MASK_RIDE }
2469 unsigned long ret = 0UL;
2472 menu_item *picks = (menu_item *) 0;
2474 tmpwin = create_nhwindow(NHW_MENU);
2477 for (i = 0; i < SIZE(valid_conditions); i++) {
2479 any.a_ulong = valid_conditions[i].bitmask;
2480 add_menu(tmpwin, NO_GLYPH, &any, 0, 0, ATR_NONE,
2481 valid_conditions[i].id, MENU_UNSELECTED);
2484 end_menu(tmpwin, "Choose status conditions");
2486 res = select_menu(tmpwin, PICK_ANY, &picks);
2487 destroy_nhwindow(tmpwin);
2489 for (i = 0; i < res; i++)
2490 ret |= picks[i].item.a_ulong;
2491 free((genericptr_t) picks);
2497 conditionbitmask2str(ul)
2500 static char buf[BUFSZ];
2502 boolean first = TRUE;
2503 const char *alias = (char *) 0;
2510 for (i = 1; i < SIZE(condition_aliases); i++)
2511 if (condition_aliases[i].bitmask == ul)
2512 alias = condition_aliases[i].id;
2514 for (i = 0; i < SIZE(valid_conditions); i++)
2515 if ((valid_conditions[i].bitmask & ul) != 0UL) {
2516 Sprintf(eos(buf), "%s%s", (first) ? "" : "+",
2517 valid_conditions[i].id);
2521 if (!first && alias)
2522 Sprintf(buf, "%s", alias);
2527 STATIC_OVL unsigned long
2528 match_str2conditionbitmask(str)
2531 int i, nmatches = 0;
2532 unsigned long mask = 0UL;
2535 /* check matches to canonical names */
2536 for (i = 0; i < SIZE(valid_conditions); i++)
2537 if (fuzzymatch(valid_conditions[i].id, str, " -_", TRUE)) {
2538 mask |= valid_conditions[i].bitmask;
2544 for (i = 0; i < SIZE(condition_aliases); i++)
2545 if (fuzzymatch(condition_aliases[i].id, str, " -_", TRUE)) {
2546 mask |= condition_aliases[i].bitmask;
2552 /* check partial matches to aliases */
2553 int len = (int) strlen(str);
2555 for (i = 0; i < SIZE(condition_aliases); i++)
2556 if (!strncmpi(str, condition_aliases[i].id, len)) {
2557 mask |= condition_aliases[i].bitmask;
2566 STATIC_OVL unsigned long
2567 str2conditionbitmask(str)
2570 unsigned long conditions_bitmask = 0UL;
2574 sf = splitsubfields(str, &subfields, SIZE(valid_conditions));
2579 for (i = 0; i < sf; ++i) {
2580 unsigned long bm = match_str2conditionbitmask(subfields[i]);
2583 config_error_add("Unknown condition '%s'", subfields[i]);
2586 conditions_bitmask |= bm;
2588 return conditions_bitmask;
2592 parse_condition(s, sidx)
2597 int coloridx = NO_COLOR;
2599 unsigned long conditions_bitmask = 0UL;
2600 boolean success = FALSE;
2606 OPTION=hilite_status: condition/stone+slime+foodPois/red&inverse */
2610 * It would be simpler to treat each condition (also hunger state
2611 * and encumbrance level) as if it were a separate field. That
2612 * way they could have either or both 'changed' temporary rule and
2613 * 'always' persistent rule and wouldn't need convoluted access to
2614 * the intended color and attributes.
2619 int sf = 0; /* subfield count */
2620 char buf[BUFSZ], **subfields;
2625 config_error_add("Missing condition(s)");
2630 conditions_bitmask = str2conditionbitmask(buf);
2632 if (!conditions_bitmask)
2636 * We have the conditions_bitmask with bits set for
2637 * each ailment we want in a particular color and/or
2638 * attribute, but we need to assign it to an array of
2639 * bitmasks indexed by the color chosen
2640 * (0 to (CLR_MAX - 1))
2641 * and/or attributes chosen
2642 * (HL_ATTCLR_DIM to (BL_ATTCLR_MAX - 1))
2643 * We still have to parse the colors and attributes out.
2649 if (!how || !*how) {
2650 config_error_add("Missing color+attribute");
2655 sf = splitsubfields(buf, &subfields, 0);
2658 * conditions_bitmask now has bits set representing
2659 * the conditions that player wants represented, but
2660 * now we parse out *how* they will be represented.
2662 * Only 1 colour is allowed, but potentially multiple
2663 * attributes are allowed.
2665 * We have the following additional array offsets to
2666 * use for storing the attributes beyond the end of
2667 * the color indexes, all of which are less than CLR_MAX.
2668 * HL_ATTCLR_DIM = CLR_MAX
2669 * HL_ATTCLR_BLINK = CLR_MAX + 1
2670 * HL_ATTCLR_ULINE = CLR_MAX + 2
2671 * HL_ATTCLR_INVERSE = CLR_MAX + 3
2672 * HL_ATTCLR_BOLD = CLR_MAX + 4
2673 * HL_ATTCLR_MAX = CLR_MAX + 5 (this is past array boundary)
2677 for (i = 0; i < sf; ++i) {
2678 int a = match_str2attr(subfields[i], FALSE);
2681 cond_hilites[HL_ATTCLR_DIM] |= conditions_bitmask;
2682 else if (a == ATR_BLINK)
2683 cond_hilites[HL_ATTCLR_BLINK] |= conditions_bitmask;
2684 else if (a == ATR_ULINE)
2685 cond_hilites[HL_ATTCLR_ULINE] |= conditions_bitmask;
2686 else if (a == ATR_INVERSE)
2687 cond_hilites[HL_ATTCLR_INVERSE] |= conditions_bitmask;
2688 else if (a == ATR_BOLD)
2689 cond_hilites[HL_ATTCLR_BOLD] |= conditions_bitmask;
2690 else if (a == ATR_NONE) {
2691 cond_hilites[HL_ATTCLR_DIM] &= ~conditions_bitmask;
2692 cond_hilites[HL_ATTCLR_BLINK] &= ~conditions_bitmask;
2693 cond_hilites[HL_ATTCLR_ULINE] &= ~conditions_bitmask;
2694 cond_hilites[HL_ATTCLR_INVERSE] &= ~conditions_bitmask;
2695 cond_hilites[HL_ATTCLR_BOLD] &= ~conditions_bitmask;
2697 int k = match_str2clr(subfields[i]);
2704 /* set the bits in the appropriate member of the
2705 condition array according to color chosen as index */
2707 cond_hilites[coloridx] |= conditions_bitmask;
2715 clear_status_hilites()
2719 for (i = 0; i < MAXBLSTATS; ++i) {
2720 struct hilite_s *temp, *next;
2722 for (temp = blstats[0][i].thresholds; temp; temp = next) {
2726 blstats[0][i].thresholds = blstats[1][i].thresholds = 0;
2727 /* pointer into thresholds list, now stale */
2728 blstats[0][i].hilite_rule = blstats[1][i].hilite_rule = 0;
2733 hlattr2attrname(attrib, buf, bufsz)
2737 if (attrib && buf) {
2742 if (attrib == HL_NONE) {
2743 Strcpy(buf, "normal");
2747 if (attrib & HL_BOLD)
2748 Strcat(attbuf, first++ ? "+bold" : "bold");
2749 if (attrib & HL_INVERSE)
2750 Strcat(attbuf, first++ ? "+inverse" : "inverse");
2751 if (attrib & HL_ULINE)
2752 Strcat(attbuf, first++ ? "+underline" : "underline");
2753 if (attrib & HL_BLINK)
2754 Strcat(attbuf, first++ ? "+blink" : "blink");
2755 if (attrib & HL_DIM)
2756 Strcat(attbuf, first++ ? "+dim" : "dim");
2759 if (k < (bufsz - 1))
2760 Strcpy(buf, attbuf);
2767 struct _status_hilite_line_str {
2770 struct hilite_s *hl;
2773 struct _status_hilite_line_str *next;
2776 static struct _status_hilite_line_str *status_hilite_str = 0;
2777 static int status_hilite_str_id = 0;
2780 status_hilite_linestr_add(fld, hl, mask, str)
2782 struct hilite_s *hl;
2786 struct _status_hilite_line_str *tmp, *nxt;
2788 tmp = (struct _status_hilite_line_str *) alloc(sizeof *tmp);
2789 (void) memset(tmp, 0, sizeof *tmp);
2790 tmp->next = (struct _status_hilite_line_str *) 0;
2792 tmp->id = ++status_hilite_str_id;
2796 if (fld == BL_TITLE)
2797 Strcpy(tmp->str, str);
2799 (void) stripchars(tmp->str, " ", str);
2801 if ((nxt = status_hilite_str) != 0) {
2806 status_hilite_str = tmp;
2811 status_hilite_linestr_done()
2813 struct _status_hilite_line_str *nxt, *tmp = status_hilite_str;
2820 status_hilite_str = (struct _status_hilite_line_str *) 0;
2821 status_hilite_str_id = 0;
2825 status_hilite_linestr_countfield(fld)
2828 struct _status_hilite_line_str *tmp;
2829 boolean countall = (fld == BL_FLUSH);
2832 for (tmp = status_hilite_str; tmp; tmp = tmp->next) {
2833 if (countall || tmp->fld == fld)
2839 /* used by options handling, doset(options.c) */
2841 count_status_hilites(VOID_ARGS)
2845 status_hilite_linestr_gather();
2846 count = status_hilite_linestr_countfield(BL_FLUSH);
2847 status_hilite_linestr_done();
2852 status_hilite_linestr_gather_conditions()
2857 unsigned long clratr;
2858 } cond_maps[SIZE(valid_conditions)];
2860 (void) memset(cond_maps, 0,
2861 SIZE(valid_conditions) * sizeof (struct _cond_map));
2863 for (i = 0; i < SIZE(valid_conditions); i++) {
2868 for (j = 0; j < CLR_MAX; j++)
2869 if (cond_hilites[j] & valid_conditions[i].bitmask) {
2873 if (cond_hilites[HL_ATTCLR_DIM] & valid_conditions[i].bitmask)
2875 if (cond_hilites[HL_ATTCLR_BOLD] & valid_conditions[i].bitmask)
2877 if (cond_hilites[HL_ATTCLR_BLINK] & valid_conditions[i].bitmask)
2879 if (cond_hilites[HL_ATTCLR_ULINE] & valid_conditions[i].bitmask)
2881 if (cond_hilites[HL_ATTCLR_INVERSE] & valid_conditions[i].bitmask)
2886 if (clr != NO_COLOR || atr != HL_NONE) {
2887 unsigned long ca = clr | (atr << 8);
2888 boolean added_condmap = FALSE;
2890 for (j = 0; j < SIZE(valid_conditions); j++)
2891 if (cond_maps[j].clratr == ca) {
2892 cond_maps[j].bm |= valid_conditions[i].bitmask;
2893 added_condmap = TRUE;
2896 if (!added_condmap) {
2897 for (j = 0; j < SIZE(valid_conditions); j++)
2898 if (!cond_maps[j].bm) {
2899 cond_maps[j].bm = valid_conditions[i].bitmask;
2900 cond_maps[j].clratr = ca;
2907 for (i = 0; i < SIZE(valid_conditions); i++)
2908 if (cond_maps[i].bm) {
2909 int clr = NO_COLOR, atr = HL_NONE;
2911 split_clridx(cond_maps[i].clratr, &clr, &atr);
2912 if (clr != NO_COLOR || atr != HL_NONE) {
2914 char attrbuf[BUFSZ];
2915 char condbuf[BUFSZ];
2918 (void) strNsubst(strcpy(clrbuf, clr2colorname(clr)),
2920 tmpattr = hlattr2attrname(atr, attrbuf, BUFSZ);
2922 Sprintf(eos(clrbuf), "&%s", tmpattr);
2923 Sprintf(condbuf, "condition/%s/%s",
2924 conditionbitmask2str(cond_maps[i].bm), clrbuf);
2925 status_hilite_linestr_add(BL_CONDITION, 0,
2926 cond_maps[i].bm, condbuf);
2932 status_hilite_linestr_gather()
2935 struct hilite_s *hl;
2937 status_hilite_linestr_done();
2939 for (i = 0; i < MAXBLSTATS; i++) {
2940 hl = blstats[0][i].thresholds;
2942 status_hilite_linestr_add(i, hl, 0UL, status_hilite2str(hl));
2947 status_hilite_linestr_gather_conditions();
2952 status_hilite2str(hl)
2953 struct hilite_s *hl;
2955 static char buf[BUFSZ];
2956 int clr = 0, attr = 0;
2957 char behavebuf[BUFSZ];
2959 char attrbuf[BUFSZ];
2966 behavebuf[0] = '\0';
2968 op = (hl->rel == LT_VALUE) ? "<"
2969 : (hl->rel == LE_VALUE) ? "<="
2970 : (hl->rel == GT_VALUE) ? ">"
2971 : (hl->rel == GE_VALUE) ? ">="
2972 : (hl->rel == EQ_VALUE) ? "="
2975 switch (hl->behavior) {
2976 case BL_TH_VAL_PERCENTAGE:
2978 Sprintf(behavebuf, "%s%d%%", op, hl->value.a_int);
2980 impossible("hl->behavior=percentage, rel error");
2983 if (hl->rel == LT_VALUE)
2984 Sprintf(behavebuf, "down");
2985 else if (hl->rel == GT_VALUE)
2986 Sprintf(behavebuf, "up");
2987 else if (hl->rel == EQ_VALUE)
2988 Sprintf(behavebuf, "changed");
2990 impossible("hl->behavior=updown, rel error");
2992 case BL_TH_VAL_ABSOLUTE:
2994 Sprintf(behavebuf, "%s%d", op, hl->value.a_int);
2996 impossible("hl->behavior=absolute, rel error");
2998 case BL_TH_TEXTMATCH:
2999 if (hl->rel == TXT_VALUE && hl->textmatch[0])
3000 Sprintf(behavebuf, "%s", hl->textmatch);
3002 impossible("hl->behavior=textmatch, rel or textmatch error");
3004 case BL_TH_CONDITION:
3005 if (hl->rel == EQ_VALUE)
3006 Sprintf(behavebuf, "%s", conditionbitmask2str(hl->value.a_ulong));
3008 impossible("hl->behavior=condition, rel error");
3010 case BL_TH_ALWAYS_HILITE:
3011 Sprintf(behavebuf, "always");
3019 split_clridx(hl->coloridx, &clr, &attr);
3020 (void) strNsubst(strcpy(clrbuf, clr2colorname(clr)), " ", "-", 0);
3021 if (attr != HL_UNDEF) {
3022 if ((tmpattr = hlattr2attrname(attr, attrbuf, BUFSZ)) != 0)
3023 Sprintf(eos(clrbuf), "&%s", tmpattr);
3025 Sprintf(buf, "%s/%s/%s", initblstats[hl->fld].fldname, behavebuf, clrbuf);
3031 status_hilite_menu_choose_field()
3034 int i, res, fld = BL_FLUSH;
3036 menu_item *picks = (menu_item *) 0;
3038 tmpwin = create_nhwindow(NHW_MENU);
3041 for (i = 0; i < MAXBLSTATS; i++) {
3042 #ifndef SCORE_ON_BOTL
3043 if (initblstats[i].fld == BL_SCORE
3044 && !blstats[0][BL_SCORE].thresholds)
3048 any.a_int = (i + 1);
3049 add_menu(tmpwin, NO_GLYPH, &any, 0, 0, ATR_NONE,
3050 initblstats[i].fldname, MENU_UNSELECTED);
3053 end_menu(tmpwin, "Select a hilite field:");
3055 res = select_menu(tmpwin, PICK_ONE, &picks);
3056 destroy_nhwindow(tmpwin);
3058 fld = picks->item.a_int - 1;
3059 free((genericptr_t) picks);
3065 status_hilite_menu_choose_behavior(fld)
3069 int res = 0, beh = BL_TH_NONE-1;
3071 menu_item *picks = (menu_item *) 0;
3074 int onlybeh = BL_TH_NONE, nopts = 0;
3076 if (fld < 0 || fld >= MAXBLSTATS)
3079 at = initblstats[fld].anytype;
3081 tmpwin = create_nhwindow(NHW_MENU);
3084 if (fld != BL_CONDITION) {
3086 any.a_int = onlybeh = BL_TH_ALWAYS_HILITE;
3087 Sprintf(buf, "Always highlight %s", initblstats[fld].fldname);
3088 add_menu(tmpwin, NO_GLYPH, &any, 'a', 0, ATR_NONE,
3089 buf, MENU_UNSELECTED);
3093 if (fld == BL_CONDITION) {
3095 any.a_int = onlybeh = BL_TH_CONDITION;
3096 add_menu(tmpwin, NO_GLYPH, &any, 'b', 0, ATR_NONE,
3097 "Bitmask of conditions", MENU_UNSELECTED);
3101 if (fld != BL_CONDITION) {
3103 any.a_int = onlybeh = BL_TH_UPDOWN;
3104 Sprintf(buf, "%s value changes", initblstats[fld].fldname);
3105 add_menu(tmpwin, NO_GLYPH, &any, 'c', 0, ATR_NONE,
3106 buf, MENU_UNSELECTED);
3110 if (fld != BL_CAP && fld != BL_HUNGER
3111 && (at == ANY_INT || at == ANY_LONG)) {
3113 any.a_int = onlybeh = BL_TH_VAL_ABSOLUTE;
3114 add_menu(tmpwin, NO_GLYPH, &any, 'n', 0, ATR_NONE,
3115 "Number threshold", MENU_UNSELECTED);
3119 if (initblstats[fld].idxmax >= 0) {
3121 any.a_int = onlybeh = BL_TH_VAL_PERCENTAGE;
3122 add_menu(tmpwin, NO_GLYPH, &any, 'p', 0, ATR_NONE,
3123 "Percentage threshold", MENU_UNSELECTED);
3127 if (initblstats[fld].anytype == ANY_STR
3128 || fld == BL_CAP || fld == BL_HUNGER) {
3130 any.a_int = onlybeh = BL_TH_TEXTMATCH;
3131 Sprintf(buf, "%s text match", initblstats[fld].fldname);
3132 add_menu(tmpwin, NO_GLYPH, &any, 't', 0, ATR_NONE,
3133 buf, MENU_UNSELECTED);
3137 Sprintf(buf, "Select %s field hilite behavior:", initblstats[fld].fldname);
3138 end_menu(tmpwin, buf);
3141 res = select_menu(tmpwin, PICK_ONE, &picks);
3142 if (res == 0) /* none chosen*/
3144 else if (res == -1) /* menu cancelled */
3145 beh = (BL_TH_NONE - 1);
3146 } else if (onlybeh != BL_TH_NONE)
3148 destroy_nhwindow(tmpwin);
3150 beh = picks->item.a_int;
3151 free((genericptr_t) picks);
3157 status_hilite_menu_choose_updownboth(fld, str, ltok, gtok)
3162 int res, ret = NO_LTEQGT;
3166 menu_item *picks = (menu_item *) 0;
3168 tmpwin = create_nhwindow(NHW_MENU);
3173 Sprintf(buf, "%s than %s",
3174 (fld == BL_AC) ? "Better (lower)" : "Less", str);
3176 Sprintf(buf, "Value goes down");
3178 any.a_int = 10 + LT_VALUE;
3179 add_menu(tmpwin, NO_GLYPH, &any, 0, 0, ATR_NONE,
3180 buf, MENU_UNSELECTED);
3183 Sprintf(buf, "%s or %s",
3184 str, (fld == BL_AC) ? "better (lower)" : "less");
3186 any.a_int = 10 + LE_VALUE;
3187 add_menu(tmpwin, NO_GLYPH, &any, 0, 0, ATR_NONE,
3188 buf, MENU_UNSELECTED);
3193 Sprintf(buf, "Exactly %s", str);
3195 Sprintf(buf, "Value changes");
3197 any.a_int = 10 + EQ_VALUE;
3198 add_menu(tmpwin, NO_GLYPH, &any, 0, 0, ATR_NONE,
3199 buf, MENU_UNSELECTED);
3203 Sprintf(buf, "%s or %s",
3204 str, (fld == BL_AC) ? "worse (higher)" : "more");
3206 any.a_int = 10 + GE_VALUE;
3207 add_menu(tmpwin, NO_GLYPH, &any, 0, 0, ATR_NONE,
3208 buf, MENU_UNSELECTED);
3212 Sprintf(buf, "%s than %s",
3213 (fld == BL_AC) ? "Worse (higher)" : "More", str);
3215 Sprintf(buf, "Value goes up");
3217 any.a_int = 10 + GT_VALUE;
3218 add_menu(tmpwin, NO_GLYPH, &any, 0, 0, ATR_NONE,
3219 buf, MENU_UNSELECTED);
3221 Sprintf(buf, "Select field %s value:", initblstats[fld].fldname);
3222 end_menu(tmpwin, buf);
3224 res = select_menu(tmpwin, PICK_ONE, &picks);
3225 destroy_nhwindow(tmpwin);
3227 ret = picks->item.a_int - 10;
3228 free((genericptr_t) picks);
3235 status_hilite_menu_add(origfld)
3241 int clr = NO_COLOR, atr = HL_UNDEF;
3242 struct hilite_s hilite;
3243 unsigned long cond = 0UL;
3244 char colorqry[BUFSZ];
3245 char attrqry[BUFSZ];
3249 if (fld == BL_FLUSH) {
3250 fld = status_hilite_menu_choose_field();
3251 /* isn't this redundant given what follows? */
3252 if (fld == BL_FLUSH)
3256 if (fld == BL_FLUSH)
3262 memset((genericptr_t) &hilite, 0, sizeof (struct hilite_s));
3263 hilite.next = (struct hilite_s *) 0;
3264 hilite.set = FALSE; /* mark it "unset" */
3268 behavior = status_hilite_menu_choose_behavior(fld);
3270 if (behavior == (BL_TH_NONE - 1)) {
3272 } else if (behavior == BL_TH_NONE) {
3273 if (origfld == BL_FLUSH)
3278 hilite.behavior = behavior;
3281 if (behavior == BL_TH_VAL_PERCENTAGE
3282 || behavior == BL_TH_VAL_ABSOLUTE) {
3283 char inbuf[BUFSZ], buf[BUFSZ];
3286 boolean gotnum = FALSE, percent = (behavior == BL_TH_VAL_PERCENTAGE);
3287 char *inp, *numstart;
3290 lt_gt_eq = NO_LTEQGT; /* not set up yet */
3292 Sprintf(buf, "Enter %svalue for %s threshold:",
3293 percent ? "percentage " : "",
3294 initblstats[fld].fldname);
3296 if (inbuf[0] == '\0' || inbuf[0] == '\033')
3297 goto choose_behavior;
3299 inp = numstart = trimspaces(inbuf);
3301 goto choose_behavior;
3303 /* allow user to enter "<50%" or ">50" or just "50"
3304 or <=50% or >=50 or =50 */
3305 if (*inp == '>' || *inp == '<' || *inp == '=') {
3306 lt_gt_eq = (*inp == '>') ? ((inp[1] == '=') ? GE_VALUE : GT_VALUE)
3307 : (*inp == '<') ? ((inp[1] == '=') ? LE_VALUE : LT_VALUE)
3311 if (lt_gt_eq == GE_VALUE || lt_gt_eq == LE_VALUE) {
3318 } else if (*inp == '+') {
3322 while (digit(*inp)) {
3328 pline("Not expecting a percentage.");
3329 goto choose_behavior;
3331 *inp = '\0'; /* strip '%' [this accepts trailing junk!] */
3333 /* some random characters */
3334 pline("\"%s\" is not a recognized number.", inp);
3338 pline("Is that an invisible number?");
3341 op = (lt_gt_eq == LT_VALUE) ? "<"
3342 : (lt_gt_eq == LE_VALUE) ? "<="
3343 : (lt_gt_eq == GT_VALUE) ? ">"
3344 : (lt_gt_eq == GE_VALUE) ? ">="
3345 : (lt_gt_eq == EQ_VALUE) ? "="
3346 : ""; /* didn't specify lt_gt_eq with number */
3349 dt = percent ? ANY_INT : initblstats[fld].anytype;
3350 (void) s_to_anything(&aval, numstart, dt);
3354 if (initblstats[fld].idxmax == -1) {
3355 pline("Field '%s' does not support percentage values.",
3356 initblstats[fld].fldname);
3357 behavior = BL_TH_VAL_ABSOLUTE;
3360 /* if player only specified a number then lt_gt_eq isn't set
3361 up yet and the >-1 and <101 exceptions can't be honored;
3362 deliberate use of those should be uncommon enough for
3363 that to be palatable; for 0 and 100, choose_updown_both()
3364 will prevent useless operations */
3365 if ((val < 0 && (val != -1 || lt_gt_eq != GT_VALUE))
3366 || (val == 0 && lt_gt_eq == LT_VALUE)
3367 || (val == 100 && lt_gt_eq == GT_VALUE)
3368 || (val > 100 && (val != 101 || lt_gt_eq != LT_VALUE))) {
3369 pline("'%s%d%%' is not a valid percent value.", op, val);
3372 /* restore suffix for use in color and attribute prompts */
3373 if (!index(numstart, '%'))
3374 Strcat(numstart, "%");
3376 /* reject negative values except for AC and >-1; reject 0 for < */
3377 } else if (dt == ANY_INT
3378 && (aval.a_int < ((fld == BL_AC) ? -128
3379 : (lt_gt_eq == GT_VALUE) ? -1
3380 : (lt_gt_eq == LT_VALUE) ? 1 : 0))) {
3381 pline("%s'%s%d'%s", threshold_value,
3382 op, aval.a_int, is_out_of_range);
3384 } else if (dt == ANY_LONG
3385 && (aval.a_long < ((lt_gt_eq == GT_VALUE) ? -1L
3386 : (lt_gt_eq == LT_VALUE) ? 1L : 0L))) {
3387 pline("%s'%s%ld'%s", threshold_value,
3388 op, aval.a_long, is_out_of_range);
3392 if (lt_gt_eq == NO_LTEQGT) {
3393 boolean ltok = ((dt == ANY_INT)
3394 ? (aval.a_int > 0 || fld == BL_AC)
3395 : (aval.a_long > 0L)),
3396 gtok = (!percent || aval.a_long < 100);
3398 lt_gt_eq = status_hilite_menu_choose_updownboth(fld, inbuf,
3400 if (lt_gt_eq == NO_LTEQGT)
3404 Sprintf(colorqry, "Choose a color for when %s is %s%s%s:",
3405 initblstats[fld].fldname,
3406 (lt_gt_eq == LT_VALUE) ? "less than "
3407 : (lt_gt_eq == GT_VALUE) ? "more than "
3410 (lt_gt_eq == LE_VALUE) ? " or less"
3411 : (lt_gt_eq == GE_VALUE) ? " or more"
3413 Sprintf(attrqry, "Choose attribute for when %s is %s%s%s:",
3414 initblstats[fld].fldname,
3415 (lt_gt_eq == LT_VALUE) ? "less than "
3416 : (lt_gt_eq == GT_VALUE) ? "more than "
3419 (lt_gt_eq == LE_VALUE) ? " or less"
3420 : (lt_gt_eq == GE_VALUE) ? " or more"
3423 hilite.rel = lt_gt_eq;
3424 hilite.value = aval;
3425 } else if (behavior == BL_TH_UPDOWN) {
3426 if (initblstats[fld].anytype != ANY_STR) {
3427 boolean ltok = (fld != BL_TIME), gtok = TRUE;
3429 lt_gt_eq = status_hilite_menu_choose_updownboth(fld, (char *)0,
3431 if (lt_gt_eq == NO_LTEQGT)
3432 goto choose_behavior;
3433 } else { /* ANY_STR */
3434 /* player picked '<field> value changes' in outer menu;
3435 ordered string comparison is supported but LT/GT for the
3436 string status fields (title, dungeon level, alignment)
3437 is pointless; rather than calling ..._choose_updownboth()
3438 with ltok==False plus gtok=False and having a menu with a
3439 single choice, skip it altogether and just use 'changed' */
3440 lt_gt_eq = EQ_VALUE;
3442 Sprintf(colorqry, "Choose a color for when %s %s:",
3443 initblstats[fld].fldname,
3444 (lt_gt_eq == EQ_VALUE) ? "changes"
3445 : (lt_gt_eq == LT_VALUE) ? "decreases"
3447 Sprintf(attrqry, "Choose attribute for when %s %s:",
3448 initblstats[fld].fldname,
3449 (lt_gt_eq == EQ_VALUE) ? "changes"
3450 : (lt_gt_eq == LT_VALUE) ? "decreases"
3452 hilite.rel = lt_gt_eq;
3453 } else if (behavior == BL_TH_CONDITION) {
3454 cond = query_conditions();
3456 if (origfld == BL_FLUSH)
3460 Sprintf(colorqry, "Choose a color for conditions %s:",
3461 conditionbitmask2str(cond));
3462 Sprintf(attrqry, "Choose attribute for conditions %s:",
3463 conditionbitmask2str(cond));
3464 } else if (behavior == BL_TH_TEXTMATCH) {
3465 char qry_buf[BUFSZ];
3467 Sprintf(qry_buf, "%s %s text value to match:",
3471 || fld == BL_TITLE) ? "Choose" : "Enter",
3472 initblstats[fld].fldname);
3473 if (fld == BL_CAP) {
3475 int rv = query_arrayvalue(qry_buf,
3477 SLT_ENCUMBER, OVERLOADED + 1);
3479 int rv = query_arrayvalue(qry_buf,
3481 SLT_ENCUMBER, OVERLOADED + 1);
3484 if (rv < SLT_ENCUMBER)
3485 goto choose_behavior;
3487 hilite.rel = TXT_VALUE;
3488 Strcpy(hilite.textmatch, enc_stat[rv]);
3489 } else if (fld == BL_ALIGN) {
3490 static const char *aligntxt[] = { "chaotic", "neutral", "lawful" };
3491 int rv = query_arrayvalue(qry_buf,
3492 aligntxt, 0, 2 + 1);
3495 goto choose_behavior;
3497 hilite.rel = TXT_VALUE;
3498 Strcpy(hilite.textmatch, aligntxt[rv]);
3499 } else if (fld == BL_HUNGER) {
3500 static const char *hutxt[] = { "Satiated", (char *) 0, "Hungry",
3501 "Weak", "Fainting", "Fainted",
3503 int rv = query_arrayvalue(qry_buf,
3505 SATIATED, STARVED + 1);
3508 goto choose_behavior;
3510 hilite.rel = TXT_VALUE;
3511 Strcpy(hilite.textmatch, hutxt[rv]);
3512 } else if (fld == BL_TITLE) {
3513 const char *rolelist[3 * 9 + 1];
3514 char mbuf[MAXVALWIDTH], fbuf[MAXVALWIDTH], obuf[MAXVALWIDTH];
3517 for (i = j = 0; i < 9; i++) {
3518 Sprintf(mbuf, "\"%s\"", urole.rank[i].m);
3519 if (urole.rank[i].f) {
3520 Sprintf(fbuf, "\"%s\"", urole.rank[i].f);
3521 Sprintf(obuf, "%s or %s",
3522 flags.female ? fbuf : mbuf,
3523 flags.female ? mbuf : fbuf);
3525 fbuf[0] = obuf[0] = '\0';
3529 rolelist[j++] = dupstr(fbuf);
3530 rolelist[j++] = dupstr(mbuf);
3532 rolelist[j++] = dupstr(obuf);
3534 rolelist[j++] = dupstr(mbuf);
3536 rolelist[j++] = dupstr(fbuf);
3538 rolelist[j++] = dupstr(obuf);
3541 rolelist[j++] = dupstr("\"none of the above (polymorphed)\"");
3543 rv = query_arrayvalue(qry_buf, rolelist, 0, j);
3545 hilite.rel = TXT_VALUE;
3546 Strcpy(hilite.textmatch, rolelist[rv]);
3548 for (i = 0; i < j; i++)
3549 free((genericptr_t) rolelist[i]), rolelist[i] = 0;
3551 goto choose_behavior;
3556 getlin(qry_buf, inbuf);
3557 if (inbuf[0] == '\0' || inbuf[0] == '\033')
3558 goto choose_behavior;
3560 hilite.rel = TXT_VALUE;
3561 if (strlen(inbuf) < sizeof hilite.textmatch)
3562 Strcpy(hilite.textmatch, inbuf);
3566 Sprintf(colorqry, "Choose a color for when %s is '%s':",
3567 initblstats[fld].fldname, hilite.textmatch);
3568 Sprintf(attrqry, "Choose attribute for when %s is '%s':",
3569 initblstats[fld].fldname, hilite.textmatch);
3570 } else if (behavior == BL_TH_ALWAYS_HILITE) {
3571 Sprintf(colorqry, "Choose a color to always hilite %s:",
3572 initblstats[fld].fldname);
3573 Sprintf(attrqry, "Choose attribute to always hilite %s:",
3574 initblstats[fld].fldname);
3578 clr = query_color(colorqry);
3580 if (behavior != BL_TH_ALWAYS_HILITE)
3583 goto choose_behavior;
3585 atr = query_attr(attrqry);
3589 if (behavior == BL_TH_CONDITION) {
3591 char attrbuf[BUFSZ];
3595 cond_hilites[HL_ATTCLR_DIM] |= cond;
3597 cond_hilites[HL_ATTCLR_BLINK] |= cond;
3599 cond_hilites[HL_ATTCLR_ULINE] |= cond;
3600 if (atr & HL_INVERSE)
3601 cond_hilites[HL_ATTCLR_INVERSE] |= cond;
3603 cond_hilites[HL_ATTCLR_BOLD] |= cond;
3604 if (atr == HL_NONE) {
3605 cond_hilites[HL_ATTCLR_DIM] &= ~cond;
3606 cond_hilites[HL_ATTCLR_BLINK] &= ~cond;
3607 cond_hilites[HL_ATTCLR_ULINE] &= ~cond;
3608 cond_hilites[HL_ATTCLR_INVERSE] &= ~cond;
3609 cond_hilites[HL_ATTCLR_BOLD] &= ~cond;
3611 cond_hilites[clr] |= cond;
3612 (void) strNsubst(strcpy(clrbuf, clr2colorname(clr)), " ", "-", 0);
3613 tmpattr = hlattr2attrname(atr, attrbuf, BUFSZ);
3615 Sprintf(eos(clrbuf), "&%s", tmpattr);
3616 pline("Added hilite condition/%s/%s",
3617 conditionbitmask2str(cond), clrbuf);
3621 hilite.coloridx = clr | (atr << 8);
3622 hilite.anytype = initblstats[fld].anytype;
3624 if (fld == BL_TITLE && (p = strstri(hilite.textmatch, " or ")) != 0) {
3625 /* split menu choice "male-rank or female-rank" into two distinct
3626 but otherwise identical rules, "male-rank" and "female-rank" */
3627 *p = '\0'; /* chop off " or female-rank" */
3628 /* new rule for male-rank */
3629 status_hilite_add_threshold(fld, &hilite);
3630 pline("Added hilite %s", status_hilite2str(&hilite));
3631 /* transfer female-rank to start of hilite.textmatch buffer */
3632 p += sizeof " or " - sizeof "";
3633 q = hilite.textmatch;
3634 while ((*q++ = *p++) != '\0')
3636 /* proceed with normal addition of new rule */
3638 status_hilite_add_threshold(fld, &hilite);
3639 pline("Added hilite %s", status_hilite2str(&hilite));
3641 reset_status_hilites();
3646 status_hilite_remove(id)
3649 struct _status_hilite_line_str *hlstr = status_hilite_str;
3651 while (hlstr && hlstr->id != id) {
3652 hlstr = hlstr->next;
3658 if (hlstr->fld == BL_CONDITION) {
3661 for (i = 0; i < CLR_MAX; i++)
3662 cond_hilites[i] &= ~hlstr->mask;
3663 cond_hilites[HL_ATTCLR_DIM] &= ~hlstr->mask;
3664 cond_hilites[HL_ATTCLR_BOLD] &= ~hlstr->mask;
3665 cond_hilites[HL_ATTCLR_BLINK] &= ~hlstr->mask;
3666 cond_hilites[HL_ATTCLR_ULINE] &= ~hlstr->mask;
3667 cond_hilites[HL_ATTCLR_INVERSE] &= ~hlstr->mask;
3670 int fld = hlstr->fld;
3671 struct hilite_s *hl, *hlprev = (struct hilite_s *) 0;
3673 for (hl = blstats[0][fld].thresholds; hl; hl = hl->next) {
3674 if (hlstr->hl == hl) {
3676 hlprev->next = hl->next;
3678 blstats[0][fld].thresholds = hl->next;
3679 blstats[1][fld].thresholds = blstats[0][fld].thresholds;
3681 if (blstats[0][fld].hilite_rule == hl) {
3682 blstats[0][fld].hilite_rule
3683 = blstats[1][fld].hilite_rule = (struct hilite_s *) 0;
3684 blstats[0][fld].time = blstats[1][fld].time = 0L;
3686 free((genericptr_t) hl);
3696 status_hilite_menu_fld(fld)
3701 menu_item *picks = (menu_item *) 0;
3703 int count = status_hilite_linestr_countfield(fld);
3704 struct _status_hilite_line_str *hlstr;
3706 boolean acted = FALSE;
3709 if (status_hilite_menu_add(fld)) {
3710 status_hilite_linestr_done();
3711 status_hilite_linestr_gather();
3712 count = status_hilite_linestr_countfield(fld);
3717 tmpwin = create_nhwindow(NHW_MENU);
3721 hlstr = status_hilite_str;
3723 if (hlstr->fld == fld) {
3725 any.a_int = hlstr->id;
3726 add_menu(tmpwin, NO_GLYPH, &any, 0, 0, ATR_NONE,
3727 hlstr->str, MENU_UNSELECTED);
3729 hlstr = hlstr->next;
3733 Sprintf(buf, "No current hilites for %s", initblstats[fld].fldname);
3734 add_menu(tmpwin, NO_GLYPH, &any, 0, 0, ATR_NONE, buf, MENU_UNSELECTED);
3737 /* separator line */
3739 add_menu(tmpwin, NO_GLYPH, &any, 0, 0, ATR_NONE, "", MENU_UNSELECTED);
3744 add_menu(tmpwin, NO_GLYPH, &any, 'X', 0, ATR_NONE,
3745 "Remove selected hilites", MENU_UNSELECTED);
3748 #ifndef SCORE_ON_BOTL
3749 if (fld == BL_SCORE) {
3750 /* suppress 'Z - Add a new hilite' for 'score' when SCORE_ON_BOTL
3751 is disabled; we wouldn't be called for 'score' unless it has
3752 hilite rules from the config file, so count must be positive
3753 (hence there's no risk that we're putting up an empty menu) */
3760 add_menu(tmpwin, NO_GLYPH, &any, 'Z', 0, ATR_NONE,
3761 "Add a new hilite", MENU_UNSELECTED);
3764 Sprintf(buf, "Current %s hilites:", initblstats[fld].fldname);
3765 end_menu(tmpwin, buf);
3767 if ((res = select_menu(tmpwin, PICK_ANY, &picks)) > 0) {
3770 for (i = 0; i < res; i++) {
3771 int idx = picks[i].item.a_int;
3774 /* delete selected hilites */
3779 } else if (idx == -2) {
3780 /* create a new hilite */
3789 /* delete selected hilites */
3790 for (i = 0; i < res; i++) {
3791 int idx = picks[i].item.a_int;
3794 (void) status_hilite_remove(idx);
3796 reset_status_hilites();
3798 } else if (mode == -2) {
3799 /* create a new hilite */
3800 if (status_hilite_menu_add(fld))
3804 free((genericptr_t) picks);
3809 picks = (menu_item *) 0;
3810 destroy_nhwindow(tmpwin);
3815 status_hilites_viewall()
3818 struct _status_hilite_line_str *hlstr = status_hilite_str;
3821 datawin = create_nhwindow(NHW_TEXT);
3824 Sprintf(buf, "OPTIONS=hilite_status: %.*s",
3825 (int) (BUFSZ - sizeof "OPTIONS=hilite_status: " - 1),
3827 putstr(datawin, 0, buf);
3828 hlstr = hlstr->next;
3831 display_nhwindow(datawin, FALSE);
3832 destroy_nhwindow(datawin);
3836 status_hilite_menu()
3840 menu_item *picks = (menu_item *) 0;
3848 tmpwin = create_nhwindow(NHW_MENU);
3851 status_hilite_linestr_gather();
3852 countall = status_hilite_linestr_countfield(BL_FLUSH);
3856 add_menu(tmpwin, NO_GLYPH, &any, 0, 0, ATR_NONE,
3857 "View all hilites in config format", MENU_UNSELECTED);
3860 add_menu(tmpwin, NO_GLYPH, &any, 0, 0, ATR_NONE, "", MENU_UNSELECTED);
3863 for (i = 0; i < MAXBLSTATS; i++) {
3864 int count = status_hilite_linestr_countfield(i);
3867 #ifndef SCORE_ON_BOTL
3868 /* config file might contain rules for highlighting 'score'
3869 even when SCORE_ON_BOTL is disabled; if so, 'O' command
3870 menus will show them and allow deletions but not additions,
3871 otherwise, it won't show 'score' at all */
3872 if (initblstats[i].fld == BL_SCORE && !count)
3877 Sprintf(buf, "%-18s", initblstats[i].fldname);
3879 Sprintf(eos(buf), " (%d defined)", count);
3880 add_menu(tmpwin, NO_GLYPH, &any, 0, 0, ATR_NONE,
3881 buf, MENU_UNSELECTED);
3884 end_menu(tmpwin, "Status hilites:");
3885 if ((res = select_menu(tmpwin, PICK_ONE, &picks)) > 0) {
3886 i = picks->item.a_int - 1;
3888 status_hilites_viewall();
3890 (void) status_hilite_menu_fld(i);
3891 free((genericptr_t) picks), picks = (menu_item *) 0;
3895 destroy_nhwindow(tmpwin);
3896 countall = status_hilite_linestr_countfield(BL_FLUSH);
3897 status_hilite_linestr_done();
3902 /* hilite_delta=='statushilites' does double duty: it is the
3903 number of turns for temporary highlights to remain visible
3904 and also when non-zero it is the flag to enable highlighting */
3905 if (countall > 0 && !iflags.hilite_delta)
3907 "To have highlights become active, set 'statushilites' option to non-zero.");
3912 #endif /* STATUS_HILITES */